summaryrefslogtreecommitdiff
path: root/chromium/net
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net')
-rw-r--r--chromium/net/DEPS15
-rw-r--r--chromium/net/OWNERS20
-rw-r--r--chromium/net/PRESUBMIT.py21
-rw-r--r--chromium/net/android/OWNERS3
-rw-r--r--chromium/net/android/cert_verify_result_android.h26
-rw-r--r--chromium/net/android/cert_verify_result_android_list.h31
-rw-r--r--chromium/net/android/gurl_utils.cc31
-rw-r--r--chromium/net/android/gurl_utils.h16
-rw-r--r--chromium/net/android/java/CertVerifyResultAndroid.template10
-rw-r--r--chromium/net/android/java/CertificateMimeType.template11
-rw-r--r--chromium/net/android/java/NetError.template10
-rw-r--r--chromium/net/android/java/PrivateKeyType.template10
-rw-r--r--chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java308
-rw-r--r--chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java232
-rw-r--r--chromium/net/android/java/src/org/chromium/net/GURLUtils.java38
-rw-r--r--chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java224
-rw-r--r--chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java196
-rw-r--r--chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java115
-rw-r--r--chromium/net/android/java/src/org/chromium/net/X509Util.java233
-rw-r--r--chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java67
-rw-r--r--chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java295
-rw-r--r--chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java32
-rw-r--r--chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java134
-rw-r--r--chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java102
-rw-r--r--chromium/net/android/keystore.cc130
-rw-r--r--chromium/net/android/keystore.h117
-rw-r--r--chromium/net/android/keystore_openssl.cc699
-rw-r--r--chromium/net/android/keystore_openssl.h48
-rw-r--r--chromium/net/android/keystore_unittest.cc725
-rw-r--r--chromium/net/android/net_jni_registrar.cc34
-rw-r--r--chromium/net/android/net_jni_registrar.h21
-rw-r--r--chromium/net/android/network_change_notifier_android.cc104
-rw-r--r--chromium/net/android/network_change_notifier_android.h71
-rw-r--r--chromium/net/android/network_change_notifier_android_unittest.cc216
-rw-r--r--chromium/net/android/network_change_notifier_delegate_android.cc112
-rw-r--r--chromium/net/android/network_change_notifier_delegate_android.h82
-rw-r--r--chromium/net/android/network_change_notifier_factory_android.cc20
-rw-r--r--chromium/net/android/network_change_notifier_factory_android.h43
-rw-r--r--chromium/net/android/network_library.cc122
-rw-r--r--chromium/net/android/network_library.h77
-rw-r--r--chromium/net/android/private_key_type_list.h12
-rwxr-xr-xchromium/net/android/tools/proxy_test_cases.py358
-rw-r--r--chromium/net/base/DEPS5
-rw-r--r--chromium/net/base/OWNERS2
-rw-r--r--chromium/net/base/address_family.h32
-rw-r--r--chromium/net/base/address_list.cc95
-rw-r--r--chromium/net/base/address_list.h87
-rw-r--r--chromium/net/base/address_list_unittest.cc138
-rw-r--r--chromium/net/base/address_tracker_linux.cc315
-rw-r--r--chromium/net/base/address_tracker_linux.h99
-rw-r--r--chromium/net/base/address_tracker_linux_unittest.cc366
-rw-r--r--chromium/net/base/auth.cc59
-rw-r--r--chromium/net/base/auth.h107
-rw-r--r--chromium/net/base/backoff_entry.cc154
-rw-r--r--chromium/net/base/backoff_entry.h115
-rw-r--r--chromium/net/base/backoff_entry_unittest.cc296
-rw-r--r--chromium/net/base/bandwidth_metrics.cc36
-rw-r--r--chromium/net/base/bandwidth_metrics.h136
-rw-r--r--chromium/net/base/big_endian.cc98
-rw-r--r--chromium/net/base/big_endian.h103
-rw-r--r--chromium/net/base/big_endian_unittest.cc100
-rw-r--r--chromium/net/base/cache_type.h28
-rw-r--r--chromium/net/base/capturing_net_log.cc184
-rw-r--r--chromium/net/base/capturing_net_log.h159
-rw-r--r--chromium/net/base/completion_callback.h25
-rw-r--r--chromium/net/base/connection_type_histograms.cc41
-rw-r--r--chromium/net/base/connection_type_histograms.h40
-rw-r--r--chromium/net/base/crypto_module.h52
-rw-r--r--chromium/net/base/crypto_module_nss.cc28
-rw-r--r--chromium/net/base/crypto_module_openssl.cc27
-rw-r--r--chromium/net/base/data_url.cc123
-rw-r--r--chromium/net/base/data_url.h52
-rw-r--r--chromium/net/base/data_url_unittest.cc181
-rw-r--r--chromium/net/base/dir_header.html154
-rw-r--r--chromium/net/base/directory_lister.cc215
-rw-r--r--chromium/net/base/directory_lister.h123
-rw-r--r--chromium/net/base/directory_lister_unittest.cc195
-rw-r--r--chromium/net/base/dns_reloader.cc125
-rw-r--r--chromium/net/base/dns_reloader.h23
-rw-r--r--chromium/net/base/dns_util.cc106
-rw-r--r--chromium/net/base/dns_util.h38
-rw-r--r--chromium/net/base/dns_util_unittest.cc86
-rw-r--r--chromium/net/base/escape.cc392
-rw-r--r--chromium/net/base/escape.h155
-rw-r--r--chromium/net/base/escape_unittest.cc430
-rw-r--r--chromium/net/base/expiring_cache.h218
-rw-r--r--chromium/net/base/expiring_cache_unittest.cc311
-rw-r--r--chromium/net/base/file_stream.cc285
-rw-r--r--chromium/net/base/file_stream.h255
-rw-r--r--chromium/net/base/file_stream_context.cc260
-rw-r--r--chromium/net/base/file_stream_context.h232
-rw-r--r--chromium/net/base/file_stream_context_posix.cc187
-rw-r--r--chromium/net/base/file_stream_context_win.cc243
-rw-r--r--chromium/net/base/file_stream_metrics.cc101
-rw-r--r--chromium/net/base/file_stream_metrics.h43
-rw-r--r--chromium/net/base/file_stream_metrics_posix.cc21
-rw-r--r--chromium/net/base/file_stream_metrics_win.cc146
-rw-r--r--chromium/net/base/file_stream_net_log_parameters.cc25
-rw-r--r--chromium/net/base/file_stream_net_log_parameters.h27
-rw-r--r--chromium/net/base/file_stream_unittest.cc1082
-rw-r--r--chromium/net/base/file_stream_whence.h20
-rw-r--r--chromium/net/base/filter.cc406
-rw-r--r--chromium/net/base/filter.h276
-rw-r--r--chromium/net/base/filter_unittest.cc350
-rw-r--r--chromium/net/base/gzip_filter.cc298
-rw-r--r--chromium/net/base/gzip_filter.h151
-rw-r--r--chromium/net/base/gzip_filter_unittest.cc392
-rw-r--r--chromium/net/base/gzip_header.cc179
-rw-r--r--chromium/net/base/gzip_header.h94
-rw-r--r--chromium/net/base/hash_value.cc123
-rw-r--r--chromium/net/base/hash_value.h125
-rw-r--r--chromium/net/base/host_mapping_rules.cc110
-rw-r--r--chromium/net/base/host_mapping_rules.h54
-rw-r--r--chromium/net/base/host_mapping_rules_unittest.cc86
-rw-r--r--chromium/net/base/host_port_pair.cc59
-rw-r--r--chromium/net/base/host_port_pair.h83
-rw-r--r--chromium/net/base/host_port_pair_unittest.cc40
-rw-r--r--chromium/net/base/int128.cc16
-rw-r--r--chromium/net/base/int128.h330
-rw-r--r--chromium/net/base/int128_unittest.cc265
-rw-r--r--chromium/net/base/io_buffer.cc139
-rw-r--r--chromium/net/base/io_buffer.h244
-rw-r--r--chromium/net/base/iovec.h18
-rw-r--r--chromium/net/base/ip_endpoint.cc127
-rw-r--r--chromium/net/base/ip_endpoint.h74
-rw-r--r--chromium/net/base/ip_endpoint_unittest.cc173
-rw-r--r--chromium/net/base/keygen_handler.cc25
-rw-r--r--chromium/net/base/keygen_handler.h66
-rw-r--r--chromium/net/base/keygen_handler_mac.cc325
-rw-r--r--chromium/net/base/keygen_handler_nss.cc46
-rw-r--r--chromium/net/base/keygen_handler_openssl.cc42
-rw-r--r--chromium/net/base/keygen_handler_unittest.cc134
-rw-r--r--chromium/net/base/keygen_handler_win.cc224
-rw-r--r--chromium/net/base/linked_hash_map.h210
-rw-r--r--chromium/net/base/load_flags.h22
-rw-r--r--chromium/net/base/load_flags_list.h121
-rw-r--r--chromium/net/base/load_states.h35
-rw-r--r--chromium/net/base/load_states_list.h98
-rw-r--r--chromium/net/base/load_timing_info.cc22
-rw-r--r--chromium/net/base/load_timing_info.h141
-rw-r--r--chromium/net/base/load_timing_info_test_util.cc59
-rw-r--r--chromium/net/base/load_timing_info_test_util.h39
-rw-r--r--chromium/net/base/mime_sniffer.cc971
-rw-r--r--chromium/net/base/mime_sniffer.h62
-rw-r--r--chromium/net/base/mime_sniffer_unittest.cc487
-rw-r--r--chromium/net/base/mime_util.cc1003
-rw-r--r--chromium/net/base/mime_util.h133
-rw-r--r--chromium/net/base/mime_util_certificate_type_list.h13
-rw-r--r--chromium/net/base/mime_util_unittest.cc315
-rw-r--r--chromium/net/base/mock_file_stream.cc67
-rw-r--r--chromium/net/base/mock_file_stream.h83
-rw-r--r--chromium/net/base/mock_filter_context.cc45
-rw-r--r--chromium/net/base/mock_filter_context.h70
-rw-r--r--chromium/net/base/net_error_list.h703
-rw-r--r--chromium/net/base/net_errors.cc63
-rw-r--r--chromium/net/base/net_errors.h60
-rw-r--r--chromium/net/base/net_errors_posix.cc122
-rw-r--r--chromium/net/base/net_errors_win.cc123
-rw-r--r--chromium/net/base/net_export.h38
-rw-r--r--chromium/net/base/net_log.cc493
-rw-r--r--chromium/net/base/net_log.h404
-rw-r--r--chromium/net/base/net_log_event_type_list.h2229
-rw-r--r--chromium/net/base/net_log_logger.cc217
-rw-r--r--chromium/net/base/net_log_logger.h57
-rw-r--r--chromium/net/base/net_log_logger_unittest.cc118
-rw-r--r--chromium/net/base/net_log_source_type_list.h30
-rw-r--r--chromium/net/base/net_log_unittest.cc336
-rw-r--r--chromium/net/base/net_log_unittest.h152
-rw-r--r--chromium/net/base/net_module.cc21
-rw-r--r--chromium/net/base/net_module.h39
-rw-r--r--chromium/net/base/net_resources.grd16
-rw-r--r--chromium/net/base/net_util.cc2090
-rw-r--r--chromium/net/base/net_util.h542
-rw-r--r--chromium/net/base/net_util_posix.cc153
-rw-r--r--chromium/net/base/net_util_unittest.cc3569
-rw-r--r--chromium/net/base/net_util_win.cc273
-rw-r--r--chromium/net/base/network_change_notifier.cc713
-rw-r--r--chromium/net/base/network_change_notifier.h323
-rw-r--r--chromium/net/base/network_change_notifier_factory.h24
-rw-r--r--chromium/net/base/network_change_notifier_linux.cc111
-rw-r--r--chromium/net/base/network_change_notifier_linux.h45
-rw-r--r--chromium/net/base/network_change_notifier_mac.cc273
-rw-r--r--chromium/net/base/network_change_notifier_mac.h83
-rw-r--r--chromium/net/base/network_change_notifier_win.cc310
-rw-r--r--chromium/net/base/network_change_notifier_win.h109
-rw-r--r--chromium/net/base/network_change_notifier_win_unittest.cc260
-rw-r--r--chromium/net/base/network_config_watcher_mac.cc132
-rw-r--r--chromium/net/base/network_config_watcher_mac.h62
-rw-r--r--chromium/net/base/network_delegate.cc150
-rw-r--r--chromium/net/base/network_delegate.h251
-rw-r--r--chromium/net/base/network_time_notifier.cc91
-rw-r--r--chromium/net/base/network_time_notifier.h84
-rw-r--r--chromium/net/base/nss_memio.c533
-rw-r--r--chromium/net/base/nss_memio.h101
-rw-r--r--chromium/net/base/openssl_private_key_store.h53
-rw-r--r--chromium/net/base/openssl_private_key_store_android.cc51
-rw-r--r--chromium/net/base/openssl_private_key_store_memory.cc74
-rw-r--r--chromium/net/base/platform_mime_util.h39
-rw-r--r--chromium/net/base/platform_mime_util_linux.cc109
-rw-r--r--chromium/net/base/platform_mime_util_mac.mm105
-rw-r--r--chromium/net/base/platform_mime_util_win.cc54
-rw-r--r--chromium/net/base/prioritized_dispatcher.cc101
-rw-r--r--chromium/net/base/prioritized_dispatcher.h117
-rw-r--r--chromium/net/base/prioritized_dispatcher_unittest.cc365
-rw-r--r--chromium/net/base/priority_queue.h238
-rw-r--r--chromium/net/base/priority_queue_unittest.cc105
-rw-r--r--chromium/net/base/privacy_mode.h20
-rw-r--r--chromium/net/base/rand_callback.h16
-rw-r--r--chromium/net/base/registry_controlled_domains/OWNERS2
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names.cc36030
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names.dat7010
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names.gperf6117
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.cc218
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf26
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.cc161
-rw-r--r--chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf17
-rw-r--r--chromium/net/base/registry_controlled_domains/registry_controlled_domain.cc274
-rw-r--r--chromium/net/base/registry_controlled_domains/registry_controlled_domain.h237
-rw-r--r--chromium/net/base/registry_controlled_domains/registry_controlled_domain_unittest.cc352
-rw-r--r--chromium/net/base/request_priority.h25
-rw-r--r--chromium/net/base/sdch_filter.cc388
-rw-r--r--chromium/net/base/sdch_filter.h128
-rw-r--r--chromium/net/base/sdch_filter_unittest.cc1393
-rw-r--r--chromium/net/base/sdch_manager.cc566
-rw-r--r--chromium/net/base/sdch_manager.h372
-rw-r--r--chromium/net/base/static_cookie_policy.cc60
-rw-r--r--chromium/net/base/static_cookie_policy.h62
-rw-r--r--chromium/net/base/static_cookie_policy_unittest.cc115
-rw-r--r--chromium/net/base/sys_addrinfo.h25
-rw-r--r--chromium/net/base/test_completion_callback.cc69
-rw-r--r--chromium/net/base/test_completion_callback.h128
-rw-r--r--chromium/net/base/test_completion_callback_unittest.cc123
-rw-r--r--chromium/net/base/test_data_directory.cc36
-rw-r--r--chromium/net/base/test_data_directory.h29
-rw-r--r--chromium/net/base/test_data_stream.cc68
-rw-r--r--chromium/net/base/test_data_stream.h47
-rw-r--r--chromium/net/base/upload_bytes_element_reader.cc79
-rw-r--r--chromium/net/base/upload_bytes_element_reader.h66
-rw-r--r--chromium/net/base/upload_bytes_element_reader_unittest.cc93
-rw-r--r--chromium/net/base/upload_data.cc37
-rw-r--r--chromium/net/base/upload_data.h83
-rw-r--r--chromium/net/base/upload_data_stream.cc264
-rw-r--r--chromium/net/base/upload_data_stream.h159
-rw-r--r--chromium/net/base/upload_data_stream_unittest.cc814
-rw-r--r--chromium/net/base/upload_element.cc25
-rw-r--r--chromium/net/base/upload_element.h110
-rw-r--r--chromium/net/base/upload_element_reader.cc21
-rw-r--r--chromium/net/base/upload_element_reader.h62
-rw-r--r--chromium/net/base/upload_file_element_reader.cc290
-rw-r--r--chromium/net/base/upload_file_element_reader.h142
-rw-r--r--chromium/net/base/upload_file_element_reader_unittest.cc367
-rw-r--r--chromium/net/base/upload_progress.h28
-rw-r--r--chromium/net/base/url_util.cc100
-rw-r--r--chromium/net/base/url_util.h56
-rw-r--r--chromium/net/base/url_util_unittest.cc112
-rw-r--r--chromium/net/base/winsock_init.cc51
-rw-r--r--chromium/net/base/winsock_init.h20
-rw-r--r--chromium/net/base/winsock_util.cc65
-rw-r--r--chromium/net/base/winsock_util.h49
-rw-r--r--chromium/net/base/zap.cc23
-rw-r--r--chromium/net/base/zap.h27
-rw-r--r--chromium/net/cert/asn1_util.cc331
-rw-r--r--chromium/net/cert/asn1_util.h93
-rw-r--r--chromium/net/cert/cert_database.cc39
-rw-r--r--chromium/net/cert/cert_database.h105
-rw-r--r--chromium/net/cert/cert_database_android.cc39
-rw-r--r--chromium/net/cert/cert_database_ios.cc30
-rw-r--r--chromium/net/cert/cert_database_mac.cc172
-rw-r--r--chromium/net/cert/cert_database_nss.cc112
-rw-r--r--chromium/net/cert/cert_database_openssl.cc58
-rw-r--r--chromium/net/cert/cert_database_win.cc61
-rw-r--r--chromium/net/cert/cert_status_flags.cc83
-rw-r--r--chromium/net/cert/cert_status_flags.h61
-rw-r--r--chromium/net/cert/cert_trust_anchor_provider.h33
-rw-r--r--chromium/net/cert/cert_type.h28
-rw-r--r--chromium/net/cert/cert_verifier.cc16
-rw-r--r--chromium/net/cert/cert_verifier.h124
-rw-r--r--chromium/net/cert/cert_verify_proc.cc392
-rw-r--r--chromium/net/cert/cert_verify_proc.h98
-rw-r--r--chromium/net/cert/cert_verify_proc_android.cc120
-rw-r--r--chromium/net/cert/cert_verify_proc_android.h34
-rw-r--r--chromium/net/cert/cert_verify_proc_mac.cc714
-rw-r--r--chromium/net/cert/cert_verify_proc_mac.h34
-rw-r--r--chromium/net/cert/cert_verify_proc_nss.cc892
-rw-r--r--chromium/net/cert/cert_verify_proc_nss.h34
-rw-r--r--chromium/net/cert/cert_verify_proc_openssl.cc226
-rw-r--r--chromium/net/cert/cert_verify_proc_openssl.h33
-rw-r--r--chromium/net/cert/cert_verify_proc_unittest.cc1359
-rw-r--r--chromium/net/cert/cert_verify_proc_win.cc827
-rw-r--r--chromium/net/cert/cert_verify_proc_win.h34
-rw-r--r--chromium/net/cert/cert_verify_result.cc30
-rw-r--r--chromium/net/cert/cert_verify_result.h69
-rw-r--r--chromium/net/cert/crl_set.cc611
-rw-r--r--chromium/net/cert/crl_set.h128
-rw-r--r--chromium/net/cert/crl_set_unittest.cc326
-rw-r--r--chromium/net/cert/ev_root_ca_metadata.cc571
-rw-r--r--chromium/net/cert/ev_root_ca_metadata.h89
-rw-r--r--chromium/net/cert/ev_root_ca_metadata_unittest.cc144
-rw-r--r--chromium/net/cert/jwk_serializer.h30
-rw-r--r--chromium/net/cert/jwk_serializer_nss.cc118
-rw-r--r--chromium/net/cert/jwk_serializer_openssl.cc23
-rw-r--r--chromium/net/cert/jwk_serializer_unittest.cc148
-rw-r--r--chromium/net/cert/mock_cert_verifier.cc64
-rw-r--r--chromium/net/cert/mock_cert_verifier.h87
-rw-r--r--chromium/net/cert/multi_threaded_cert_verifier.cc566
-rw-r--r--chromium/net/cert/multi_threaded_cert_verifier.h169
-rw-r--r--chromium/net/cert/multi_threaded_cert_verifier_unittest.cc479
-rw-r--r--chromium/net/cert/nss_cert_database.cc345
-rw-r--r--chromium/net/cert/nss_cert_database.h208
-rw-r--r--chromium/net/cert/nss_cert_database_unittest.cc1042
-rw-r--r--chromium/net/cert/pem_tokenizer.cc105
-rw-r--r--chromium/net/cert/pem_tokenizer.h77
-rw-r--r--chromium/net/cert/pem_tokenizer_unittest.cc169
-rw-r--r--chromium/net/cert/single_request_cert_verifier.cc70
-rw-r--r--chromium/net/cert/single_request_cert_verifier.h52
-rw-r--r--chromium/net/cert/test_root_certs.cc76
-rw-r--r--chromium/net/cert/test_root_certs.h134
-rw-r--r--chromium/net/cert/test_root_certs_android.cc43
-rw-r--r--chromium/net/cert/test_root_certs_mac.cc117
-rw-r--r--chromium/net/cert/test_root_certs_nss.cc125
-rw-r--r--chromium/net/cert/test_root_certs_openssl.cc51
-rw-r--r--chromium/net/cert/test_root_certs_unittest.cc142
-rw-r--r--chromium/net/cert/test_root_certs_win.cc213
-rw-r--r--chromium/net/cert/x509_cert_types.cc142
-rw-r--r--chromium/net/cert/x509_cert_types.h144
-rw-r--r--chromium/net/cert/x509_cert_types_mac.cc291
-rw-r--r--chromium/net/cert/x509_cert_types_unittest.cc243
-rw-r--r--chromium/net/cert/x509_cert_types_win.cc139
-rw-r--r--chromium/net/cert/x509_certificate.cc734
-rw-r--r--chromium/net/cert/x509_certificate.h487
-rw-r--r--chromium/net/cert/x509_certificate_ios.cc234
-rw-r--r--chromium/net/cert/x509_certificate_known_roots_mac.h433
-rw-r--r--chromium/net/cert/x509_certificate_known_roots_win.h726
-rw-r--r--chromium/net/cert/x509_certificate_mac.cc611
-rw-r--r--chromium/net/cert/x509_certificate_net_log_param.cc27
-rw-r--r--chromium/net/cert/x509_certificate_net_log_param.h21
-rw-r--r--chromium/net/cert/x509_certificate_nss.cc269
-rw-r--r--chromium/net/cert/x509_certificate_openssl.cc520
-rw-r--r--chromium/net/cert/x509_certificate_unittest.cc1147
-rw-r--r--chromium/net/cert/x509_certificate_win.cc453
-rw-r--r--chromium/net/cert/x509_util.cc49
-rw-r--r--chromium/net/cert/x509_util.h99
-rw-r--r--chromium/net/cert/x509_util_ios.cc141
-rw-r--r--chromium/net/cert/x509_util_ios.h72
-rw-r--r--chromium/net/cert/x509_util_mac.cc231
-rw-r--r--chromium/net/cert/x509_util_mac.h139
-rw-r--r--chromium/net/cert/x509_util_nss.cc624
-rw-r--r--chromium/net/cert/x509_util_nss.h99
-rw-r--r--chromium/net/cert/x509_util_nss_unittest.cc171
-rw-r--r--chromium/net/cert/x509_util_openssl.cc129
-rw-r--r--chromium/net/cert/x509_util_openssl.h45
-rw-r--r--chromium/net/cert/x509_util_openssl_unittest.cc57
-rw-r--r--chromium/net/cert/x509_util_unittest.cc190
-rw-r--r--chromium/net/cookies/OWNERS1
-rw-r--r--chromium/net/cookies/canonical_cookie.cc397
-rw-r--r--chromium/net/cookies/canonical_cookie.h162
-rw-r--r--chromium/net/cookies/canonical_cookie_unittest.cc364
-rw-r--r--chromium/net/cookies/cookie_constants.cc46
-rw-r--r--chromium/net/cookies/cookie_constants.h30
-rw-r--r--chromium/net/cookies/cookie_constants_unittest.cc40
-rw-r--r--chromium/net/cookies/cookie_monster.cc2223
-rw-r--r--chromium/net/cookies/cookie_monster.h764
-rw-r--r--chromium/net/cookies/cookie_monster_perftest.cc385
-rw-r--r--chromium/net/cookies/cookie_monster_store_test.cc227
-rw-r--r--chromium/net/cookies/cookie_monster_store_test.h205
-rw-r--r--chromium/net/cookies/cookie_monster_unittest.cc2688
-rw-r--r--chromium/net/cookies/cookie_options.h42
-rw-r--r--chromium/net/cookies/cookie_store.cc15
-rw-r--r--chromium/net/cookies/cookie_store.h84
-rw-r--r--chromium/net/cookies/cookie_store_test_callbacks.cc61
-rw-r--r--chromium/net/cookies/cookie_store_test_callbacks.h111
-rw-r--r--chromium/net/cookies/cookie_store_test_helpers.cc124
-rw-r--r--chromium/net/cookies/cookie_store_test_helpers.h89
-rw-r--r--chromium/net/cookies/cookie_store_unittest.h1160
-rw-r--r--chromium/net/cookies/cookie_util.cc214
-rw-r--r--chromium/net/cookies/cookie_util.h46
-rw-r--r--chromium/net/cookies/cookie_util_unittest.cc111
-rw-r--r--chromium/net/cookies/parsed_cookie.cc517
-rw-r--r--chromium/net/cookies/parsed_cookie.h147
-rw-r--r--chromium/net/cookies/parsed_cookie_unittest.cc425
-rw-r--r--chromium/net/data/cache_tests/bad_entry/contents.txt66
-rw-r--r--chromium/net/data/cache_tests/bad_entry/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_entry/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_entry/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_entry/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_entry/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/contents.txt66
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/contents.txt66
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings2/indexbin0 -> 524656 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/contents.txt92
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/bad_rankings3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/contents.txt183
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/contents.txt183
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry4/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/contents.txt166
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/dirty_entry5/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_empty3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_load2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/insert_one3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop/contents.txt228
-rw-r--r--chromium/net/data/cache_tests/list_loop/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop2/contents.txt145
-rw-r--r--chromium/net/data/cache_tests/list_loop2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop3/contents.txt265
-rw-r--r--chromium/net/data/cache_tests/list_loop3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/list_loop3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head4/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head4/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head4/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head4/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_head4/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_load3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one4/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one4/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one4/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one4/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_one4/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail1/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail1/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail1/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail1/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail1/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail2/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail2/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail2/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail2/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail2/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail3/data_0bin0 -> 45056 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail3/data_1bin0 -> 270336 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail3/data_2bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail3/data_3bin0 -> 8192 bytes
-rw-r--r--chromium/net/data/cache_tests/remove_tail3/indexbin0 -> 262512 bytes
-rw-r--r--chromium/net/data/cache_tests/wrong_version/indexbin0 -> 262176 bytes
-rw-r--r--chromium/net/data/filter_unittests/google.txt19
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-16
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-1-utf86
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-1-utf8.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-1.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-1010
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-10.expected80
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-118
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-11.expected62
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-128
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-12.expected62
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-133
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-13.expected26
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-143
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-14.expected26
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-155
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-15.expected35
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-167
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-16.expected62
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-171
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-17.expected0
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-183
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-18.expected26
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-192
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-19.expected8
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-27
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-2.expected62
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-2018
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-20.expected161
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-2127
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-21.expected242
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-2232
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-22.expected287
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-232
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-23.expected0
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-242
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-24.expected8
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-256
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-25.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-266
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-26.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-276
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-27.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-285
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-28.expected44
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-299
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-29.expected62
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-37
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-3.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-302
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-30.expected8
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-3115
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-31.expected118
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-410
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-4.expected80
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-51
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-5.expected8
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-67
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-6.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-76
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-7.expected53
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-81
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-8.expected0
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-94
-rw-r--r--chromium/net/data/ftp/dir-listing-ls-9.expected26
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-13
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-1.expected17
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-24
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-2.expected26
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-322
-rw-r--r--chromium/net/data/ftp/dir-listing-netware-3.expected188
-rw-r--r--chromium/net/data/ftp/dir-listing-os2-19
-rw-r--r--chromium/net/data/ftp/dir-listing-os2-1.expected80
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-119
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-1.expected98
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-235
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-2.expected260
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-33
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-3.expected0
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-415
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-4.expected71
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-512
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-5.expected35
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-613
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-6.expected35
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-74
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-7.expected0
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-837
-rw-r--r--chromium/net/data/ftp/dir-listing-vms-8.expected251
-rw-r--r--chromium/net/data/ftp/dir-listing-windows-12
-rw-r--r--chromium/net/data/ftp/dir-listing-windows-1.expected17
-rw-r--r--chromium/net/data/ftp/dir-listing-windows-216
-rw-r--r--chromium/net/data/ftp/dir-listing-windows-2.expected143
-rw-r--r--chromium/net/data/proxy_resolver_perftest/no-ads.pac1362
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/dns.js31
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/dns_during_init.js14
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/error.js8
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects1.js14
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects2.js18
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects3.js13
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects4.js15
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/simple.js3
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/simple_dns.js8
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/terminate.js20
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_alerts.js13
-rw-r--r--chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_empty_alerts.js13
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/binding_from_global.js8
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/bindings.js62
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/direct.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/dns_fail.js27
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/ends_with_comment.js8
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/ends_with_statement_no_semicolon.js3
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/international_domain_names.js16
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/missing_close_brace.js6
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/no_entrypoint.js2
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js366
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/passthrough.js45
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/resolve_host.js9
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_empty_string.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_function.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_integer.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_null.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_object.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_undefined.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/return_unicode.js4
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/side_effects.js10
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/simple.js21
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/terminate.js26
-rw-r--r--chromium/net/data/proxy_resolver_v8_unittest/unhandled_exception.js5
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/404.pac1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/404.pac.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/500.pac1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/500.pac.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac.mock-http-headers3
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac.mock-http-headers3
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/gzipped_pacbin0 -> 43 bytes
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac.mock-http-headers3
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.html1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.html.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.txt1
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/pac.txt.mock-http-headers2
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/utf16be_pacbin0 -> 60 bytes
-rw-r--r--chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac.mock-http-headers2
-rw-r--r--chromium/net/data/quic_in_memory_cache_data/quic-datatesturl.com/index.html14
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-ee-by-1024-rsa-intermediate.pem50
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-ee-by-2048-rsa-intermediate.pem59
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-ee-by-768-rsa-intermediate.pem47
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-ee-by-prime256v1-ecdsa-intermediate.pem44
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-ee-by-secp256k1-ecdsa-intermediate.pem44
-rw-r--r--chromium/net/data/ssl/certificates/1024-rsa-intermediate.pem63
-rw-r--r--chromium/net/data/ssl/certificates/2029_globalsign_com_cert.pem31
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-ee-by-1024-rsa-intermediate.pem61
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-ee-by-2048-rsa-intermediate.pem71
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-ee-by-768-rsa-intermediate.pem59
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-ee-by-prime256v1-ecdsa-intermediate.pem56
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-ee-by-secp256k1-ecdsa-intermediate.pem56
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-intermediate.pem75
-rw-r--r--chromium/net/data/ssl/certificates/2048-rsa-root.pem17
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-ee-by-1024-rsa-intermediate.pem47
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-ee-by-2048-rsa-intermediate.pem56
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-ee-by-768-rsa-intermediate.pem44
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-ee-by-prime256v1-ecdsa-intermediate.pem42
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-ee-by-secp256k1-ecdsa-intermediate.pem42
-rw-r--r--chromium/net/data/ssl/certificates/768-rsa-intermediate.pem60
-rw-r--r--chromium/net/data/ssl/certificates/README228
-rw-r--r--chromium/net/data/ssl/certificates/aia-cert.pem76
-rw-r--r--chromium/net/data/ssl/certificates/aia-intermediate.derbin0 -> 737 bytes
-rw-r--r--chromium/net/data/ssl/certificates/aia-root.pem72
-rw-r--r--chromium/net/data/ssl/certificates/android-test-key-dsa-public.pem20
-rw-r--r--chromium/net/data/ssl/certificates/android-test-key-dsa.pem20
-rw-r--r--chromium/net/data/ssl/certificates/android-test-key-ecdsa-public.pem4
-rw-r--r--chromium/net/data/ssl/certificates/android-test-key-ecdsa.pem8
-rw-r--r--chromium/net/data/ssl/certificates/android-test-key-rsa.pem27
-rw-r--r--chromium/net/data/ssl/certificates/client-nokey.p12bin0 -> 895 bytes
-rw-r--r--chromium/net/data/ssl/certificates/client.p12bin0 -> 1701 bytes
-rw-r--r--chromium/net/data/ssl/certificates/client_1.key27
-rw-r--r--chromium/net/data/ssl/certificates/client_1.pem72
-rw-r--r--chromium/net/data/ssl/certificates/client_1_ca.pem71
-rw-r--r--chromium/net/data/ssl/certificates/client_2.key27
-rw-r--r--chromium/net/data/ssl/certificates/client_2.pem72
-rw-r--r--chromium/net/data/ssl/certificates/client_2_ca.pem71
-rw-r--r--chromium/net/data/ssl/certificates/comodo.chain.pem317
-rw-r--r--chromium/net/data/ssl/certificates/crit-codeSigning-chain.pem105
-rw-r--r--chromium/net/data/ssl/certificates/cross-signed-leaf.pem82
-rw-r--r--chromium/net/data/ssl/certificates/cross-signed-root-md5.pem75
-rw-r--r--chromium/net/data/ssl/certificates/cross-signed-root-sha1.pem75
-rw-r--r--chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_1.pem82
-rw-r--r--chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_2.pem85
-rw-r--r--chromium/net/data/ssl/certificates/cybertrust_baltimore_root.pem77
-rw-r--r--chromium/net/data/ssl/certificates/cybertrust_gte_root.pem48
-rw-r--r--chromium/net/data/ssl/certificates/cybertrust_omniroot_chain.pem48
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_cyber_ca.pem32
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_pkioverheid.pem28
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_pkioverheid_g2.pem38
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_public_ca_2025.pem35
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_root_ca.pem32
-rw-r--r--chromium/net/data/ssl/certificates/diginotar_services_1024_ca.pem23
-rw-r--r--chromium/net/data/ssl/certificates/dod_ca_13_cert.derbin0 -> 1080 bytes
-rw-r--r--chromium/net/data/ssl/certificates/dod_ca_17_cert.derbin0 -> 1072 bytes
-rw-r--r--chromium/net/data/ssl/certificates/dod_root_ca_2_cert.derbin0 -> 884 bytes
-rw-r--r--chromium/net/data/ssl/certificates/duplicate_cn_1.p120
-rw-r--r--chromium/net/data/ssl/certificates/duplicate_cn_1.pem78
-rw-r--r--chromium/net/data/ssl/certificates/duplicate_cn_2.p120
-rw-r--r--chromium/net/data/ssl/certificates/duplicate_cn_2.pem78
-rw-r--r--chromium/net/data/ssl/certificates/eku-test-root.pem66
-rw-r--r--chromium/net/data/ssl/certificates/empty_subject_cert.derbin0 -> 418 bytes
-rw-r--r--chromium/net/data/ssl/certificates/expired_cert.pem110
-rw-r--r--chromium/net/data/ssl/certificates/explicit-policy-chain.pem228
-rw-r--r--chromium/net/data/ssl/certificates/foaf.me.chromium-test-cert.derbin0 -> 990 bytes
-rw-r--r--chromium/net/data/ssl/certificates/globalsign_ev_sha256_ca_cert.pem25
-rw-r--r--chromium/net/data/ssl/certificates/google.binary.p7bbin0 -> 1661 bytes
-rw-r--r--chromium/net/data/ssl/certificates/google.chain.pem38
-rw-r--r--chromium/net/data/ssl/certificates/google.pem_cert.p7b37
-rw-r--r--chromium/net/data/ssl/certificates/google.pem_pkcs7.p7b37
-rw-r--r--chromium/net/data/ssl/certificates/google.single.derbin0 -> 805 bytes
-rw-r--r--chromium/net/data/ssl/certificates/google.single.pem19
-rw-r--r--chromium/net/data/ssl/certificates/google_diginotar.pem30
-rw-r--r--chromium/net/data/ssl/certificates/googlenew.chain.pem38
-rw-r--r--chromium/net/data/ssl/certificates/invalid_key_usage_cert.derbin0 -> 940 bytes
-rw-r--r--chromium/net/data/ssl/certificates/mit.davidben.derbin0 -> 965 bytes
-rw-r--r--chromium/net/data/ssl/certificates/multivalue_rdn.pem59
-rw-r--r--chromium/net/data/ssl/certificates/ndn.ca.crt35
-rw-r--r--chromium/net/data/ssl/certificates/nist.derbin0 -> 1322 bytes
-rw-r--r--chromium/net/data/ssl/certificates/no_subject_common_name_cert.pem109
-rw-r--r--chromium/net/data/ssl/certificates/non-crit-codeSigning-chain.pem105
-rw-r--r--chromium/net/data/ssl/certificates/ocsp-test-root.pem51
-rw-r--r--chromium/net/data/ssl/certificates/ok_cert.pem110
-rw-r--r--chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem44
-rw-r--r--chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-2048-rsa-intermediate.pem54
-rw-r--r--chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-768-rsa-intermediate.pem41
-rw-r--r--chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-prime256v1-ecdsa-intermediate.pem39
-rw-r--r--chromium/net/data/ssl/certificates/prime256v1-ecdsa-intermediate.pem58
-rw-r--r--chromium/net/data/ssl/certificates/punycodetest.derbin0 -> 1422 bytes
-rw-r--r--chromium/net/data/ssl/certificates/quic_intermediate.crt53
-rw-r--r--chromium/net/data/ssl/certificates/quic_root.crt106
-rw-r--r--chromium/net/data/ssl/certificates/quic_test.example.com.crt56
-rw-r--r--chromium/net/data/ssl/certificates/quic_test_ecc.example.com.crt50
-rw-r--r--chromium/net/data/ssl/certificates/redundant-server-chain.pem271
-rw-r--r--chromium/net/data/ssl/certificates/redundant-validated-chain-root.pem16
-rw-r--r--chromium/net/data/ssl/certificates/redundant-validated-chain.pem196
-rw-r--r--chromium/net/data/ssl/certificates/root_ca_cert.pem102
-rw-r--r--chromium/net/data/ssl/certificates/salesforce_com_test.pem81
-rw-r--r--chromium/net/data/ssl/certificates/satveda.pem207
-rw-r--r--chromium/net/data/ssl/certificates/spdy_pooling.pem53
-rw-r--r--chromium/net/data/ssl/certificates/subjectAltName_sanity_check.pem54
-rw-r--r--chromium/net/data/ssl/certificates/test_mail_google_com.pem26
-rw-r--r--chromium/net/data/ssl/certificates/thawte.single.pem19
-rw-r--r--chromium/net/data/ssl/certificates/unescaped.pem62
-rw-r--r--chromium/net/data/ssl/certificates/unittest.key.binbin0 -> 635 bytes
-rw-r--r--chromium/net/data/ssl/certificates/unittest.originbound.derbin0 -> 488 bytes
-rw-r--r--chromium/net/data/ssl/certificates/unittest.originbound.key.derbin0 -> 633 bytes
-rw-r--r--chromium/net/data/ssl/certificates/unittest.selfsigned.derbin0 -> 414 bytes
-rw-r--r--chromium/net/data/ssl/certificates/verisign_intermediate_ca_2011.pem71
-rw-r--r--chromium/net/data/ssl/certificates/verisign_intermediate_ca_2016.pem71
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md2_ee.pem61
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md2_intermediate.pem57
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md2_root.pem14
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md4_ee.pem61
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md4_intermediate.pem57
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md4_root.pem14
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md5_ee.pem61
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md5_intermediate.pem57
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_md5_root.pem14
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_sha1_ee.pem61
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem57
-rw-r--r--chromium/net/data/ssl/certificates/weak_digest_sha1_root.pem14
-rw-r--r--chromium/net/data/ssl/certificates/websocket_cacert.pem61
-rw-r--r--chromium/net/data/ssl/certificates/websocket_client_cert.p12bin0 -> 2550 bytes
-rw-r--r--chromium/net/data/ssl/certificates/www_us_army_mil_cert.derbin0 -> 1222 bytes
-rw-r--r--chromium/net/data/ssl/certificates/x509_verify_results.chain.pem50
-rw-r--r--chromium/net/data/ssl/scripts/aia-test.cnf55
-rw-r--r--chromium/net/data/ssl/scripts/ca.cnf93
-rw-r--r--chromium/net/data/ssl/scripts/client-certs.cnf51
-rw-r--r--chromium/net/data/ssl/scripts/ee.cnf51
-rw-r--r--chromium/net/data/ssl/scripts/eku-test.cnf26
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-aia-certs.sh91
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-android-test-keys.sh56
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-bad-eku-certs.sh77
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-client-certificates.sh163
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-cross-signed-certs.sh92
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-duplicate-cn-certs.sh132
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-policy-certs.sh96
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-redundant-test-chains.sh187
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-test-certs.sh81
-rwxr-xr-xchromium/net/data/ssl/scripts/generate-weak-test-chains.sh168
-rw-r--r--chromium/net/data/ssl/scripts/policy.cnf60
-rw-r--r--chromium/net/data/ssl/scripts/redundant-ca.cnf80
-rw-r--r--chromium/net/data/test.html1
-rw-r--r--chromium/net/data/url_request_unittest/BullRunSpeech.txt78
-rw-r--r--chromium/net/data/url_request_unittest/content-type-normalization.html8
-rw-r--r--chromium/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers5
-rw-r--r--chromium/net/data/url_request_unittest/hpkp-headers.html1
-rw-r--r--chromium/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers6
-rw-r--r--chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html1
-rw-r--r--chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html.mock-http-headers8
-rw-r--r--chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html1
-rw-r--r--chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers8
-rw-r--r--chromium/net/data/url_request_unittest/hsts-headers.html1
-rw-r--r--chromium/net/data/url_request_unittest/hsts-headers.html.mock-http-headers6
-rw-r--r--chromium/net/data/url_request_unittest/hsts-multiple-headers.html1
-rw-r--r--chromium/net/data/url_request_unittest/hsts-multiple-headers.html.mock-http-headers7
-rw-r--r--chromium/net/data/url_request_unittest/redirect-test.html1
-rw-r--r--chromium/net/data/url_request_unittest/redirect-test.html.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-echoall1
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-echoall.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-file.html1
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-invalid-url.html1
-rw-r--r--chromium/net/data/url_request_unittest/redirect-to-invalid-url.html.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect301-to-echo1
-rw-r--r--chromium/net/data/url_request_unittest/redirect301-to-echo.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect302-to-echo1
-rw-r--r--chromium/net/data/url_request_unittest/redirect302-to-echo.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect303-to-echo1
-rw-r--r--chromium/net/data/url_request_unittest/redirect303-to-echo.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/redirect307-to-echo1
-rw-r--r--chromium/net/data/url_request_unittest/redirect307-to-echo.mock-http-headers2
-rw-r--r--chromium/net/data/url_request_unittest/with-headers.html1
-rw-r--r--chromium/net/data/url_request_unittest/with-headers.html.mock-http-headers5
-rw-r--r--chromium/net/data/websocket/OWNERS3
-rw-r--r--chromium/net/data/websocket/README49
-rw-r--r--chromium/net/data/websocket/close-code-and-reason_wsh.py25
-rw-r--r--chromium/net/data/websocket/close-with-split-packet_wsh.py30
-rw-r--r--chromium/net/data/websocket/close_wsh.py26
-rw-r--r--chromium/net/data/websocket/connect_check.html35
-rw-r--r--chromium/net/data/websocket/echo-with-no-extension_wsh.py22
-rw-r--r--chromium/net/data/websocket/protocol-test_wsh.py19
-rw-r--r--chromium/net/data/websocket/split_packet_check.html37
-rw-r--r--chromium/net/data/websocket/websocket_shared_worker.html31
-rw-r--r--chromium/net/data/websocket/websocket_worker_simple.js46
-rw-r--r--chromium/net/disk_cache/addr.cc92
-rw-r--r--chromium/net/disk_cache/addr.h184
-rw-r--r--chromium/net/disk_cache/addr_unittest.cc59
-rw-r--r--chromium/net/disk_cache/backend_impl.cc2120
-rw-r--r--chromium/net/disk_cache/backend_impl.h400
-rw-r--r--chromium/net/disk_cache/backend_unittest.cc3415
-rw-r--r--chromium/net/disk_cache/bitmap.cc311
-rw-r--r--chromium/net/disk_cache/bitmap.h136
-rw-r--r--chromium/net/disk_cache/bitmap_unittest.cc293
-rw-r--r--chromium/net/disk_cache/block_files.cc695
-rw-r--r--chromium/net/disk_cache/block_files.h152
-rw-r--r--chromium/net/disk_cache/block_files_unittest.cc350
-rw-r--r--chromium/net/disk_cache/cache_creator.cc163
-rw-r--r--chromium/net/disk_cache/cache_util.cc114
-rw-r--r--chromium/net/disk_cache/cache_util.h41
-rw-r--r--chromium/net/disk_cache/cache_util_posix.cc46
-rw-r--r--chromium/net/disk_cache/cache_util_unittest.cc96
-rw-r--r--chromium/net/disk_cache/cache_util_win.cc46
-rw-r--r--chromium/net/disk_cache/disk_cache.h324
-rw-r--r--chromium/net/disk_cache/disk_cache_perftest.cc250
-rw-r--r--chromium/net/disk_cache/disk_cache_test_base.cc307
-rw-r--r--chromium/net/disk_cache/disk_cache_test_base.h176
-rw-r--r--chromium/net/disk_cache/disk_cache_test_util.cc146
-rw-r--r--chromium/net/disk_cache/disk_cache_test_util.h105
-rw-r--r--chromium/net/disk_cache/disk_format.cc15
-rw-r--r--chromium/net/disk_cache/disk_format.h153
-rw-r--r--chromium/net/disk_cache/disk_format_base.h130
-rw-r--r--chromium/net/disk_cache/entry_impl.cc1550
-rw-r--r--chromium/net/disk_cache/entry_impl.h278
-rw-r--r--chromium/net/disk_cache/entry_unittest.cc3405
-rw-r--r--chromium/net/disk_cache/errors.h33
-rw-r--r--chromium/net/disk_cache/eviction.cc597
-rw-r--r--chromium/net/disk_cache/eviction.h91
-rw-r--r--chromium/net/disk_cache/experiments.h28
-rw-r--r--chromium/net/disk_cache/file.cc16
-rw-r--r--chromium/net/disk_cache/file.h95
-rw-r--r--chromium/net/disk_cache/file_block.h31
-rw-r--r--chromium/net/disk_cache/file_lock.cc47
-rw-r--r--chromium/net/disk_cache/file_lock.h45
-rw-r--r--chromium/net/disk_cache/file_posix.cc309
-rw-r--r--chromium/net/disk_cache/file_win.cc275
-rw-r--r--chromium/net/disk_cache/flash/flash_cache_test_base.cc29
-rw-r--r--chromium/net/disk_cache/flash/flash_cache_test_base.h43
-rw-r--r--chromium/net/disk_cache/flash/flash_entry_impl.cc150
-rw-r--r--chromium/net/disk_cache/flash/flash_entry_impl.h98
-rw-r--r--chromium/net/disk_cache/flash/format.h32
-rw-r--r--chromium/net/disk_cache/flash/internal_entry.cc86
-rw-r--r--chromium/net/disk_cache/flash/internal_entry.h63
-rw-r--r--chromium/net/disk_cache/flash/log_store.cc185
-rw-r--r--chromium/net/disk_cache/flash/log_store.h101
-rw-r--r--chromium/net/disk_cache/flash/log_store_entry.cc171
-rw-r--r--chromium/net/disk_cache/flash/log_store_entry.h65
-rw-r--r--chromium/net/disk_cache/flash/log_store_entry_unittest.cc69
-rw-r--r--chromium/net/disk_cache/flash/log_store_unittest.cc131
-rw-r--r--chromium/net/disk_cache/flash/segment.cc122
-rw-r--r--chromium/net/disk_cache/flash/segment.h118
-rw-r--r--chromium/net/disk_cache/flash/segment_unittest.cc152
-rw-r--r--chromium/net/disk_cache/flash/storage.cc63
-rw-r--r--chromium/net/disk_cache/flash/storage.h35
-rw-r--r--chromium/net/disk_cache/flash/storage_unittest.cc41
-rw-r--r--chromium/net/disk_cache/histogram_macros.h124
-rw-r--r--chromium/net/disk_cache/in_flight_backend_io.cc522
-rw-r--r--chromium/net/disk_cache/in_flight_backend_io.h223
-rw-r--r--chromium/net/disk_cache/in_flight_io.cc110
-rw-r--r--chromium/net/disk_cache/in_flight_io.h136
-rw-r--r--chromium/net/disk_cache/mapped_file.cc65
-rw-r--r--chromium/net/disk_cache/mapped_file.h73
-rw-r--r--chromium/net/disk_cache/mapped_file_avoid_mmap_posix.cc73
-rw-r--r--chromium/net/disk_cache/mapped_file_posix.cc64
-rw-r--r--chromium/net/disk_cache/mapped_file_unittest.cc91
-rw-r--r--chromium/net/disk_cache/mapped_file_win.cc65
-rw-r--r--chromium/net/disk_cache/mem_backend_impl.cc337
-rw-r--r--chromium/net/disk_cache/mem_backend_impl.h120
-rw-r--r--chromium/net/disk_cache/mem_entry_impl.cc631
-rw-r--r--chromium/net/disk_cache/mem_entry_impl.h189
-rw-r--r--chromium/net/disk_cache/mem_rankings.cc67
-rw-r--r--chromium/net/disk_cache/mem_rankings.h44
-rw-r--r--chromium/net/disk_cache/net_log_parameters.cc133
-rw-r--r--chromium/net/disk_cache/net_log_parameters.h62
-rw-r--r--chromium/net/disk_cache/rankings.cc922
-rw-r--r--chromium/net/disk_cache/rankings.h214
-rw-r--r--chromium/net/disk_cache/simple/OWNERS2
-rw-r--r--chromium/net/disk_cache/simple/simple_backend_impl.cc570
-rw-r--r--chromium/net/disk_cache/simple/simple_backend_impl.h182
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_format.cc21
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_format.h57
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_impl.cc1187
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_impl.h290
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_operation.cc184
-rw-r--r--chromium/net/disk_cache/simple/simple_entry_operation.h125
-rw-r--r--chromium/net/disk_cache/simple/simple_index.cc461
-rw-r--r--chromium/net/disk_cache/simple/simple_index.h203
-rw-r--r--chromium/net/disk_cache/simple/simple_index_file.cc423
-rw-r--r--chromium/net/disk_cache/simple/simple_index_file.h156
-rw-r--r--chromium/net/disk_cache/simple/simple_index_file_unittest.cc243
-rw-r--r--chromium/net/disk_cache/simple/simple_index_unittest.cc581
-rw-r--r--chromium/net/disk_cache/simple/simple_net_log_parameters.cc55
-rw-r--r--chromium/net/disk_cache/simple/simple_net_log_parameters.h32
-rw-r--r--chromium/net/disk_cache/simple/simple_synchronous_entry.cc635
-rw-r--r--chromium/net/disk_cache/simple/simple_synchronous_entry.h166
-rw-r--r--chromium/net/disk_cache/simple/simple_test_util.cc34
-rw-r--r--chromium/net/disk_cache/simple/simple_test_util.h29
-rw-r--r--chromium/net/disk_cache/simple/simple_util.cc100
-rw-r--r--chromium/net/disk_cache/simple/simple_util.h73
-rw-r--r--chromium/net/disk_cache/simple/simple_util_unittest.cc75
-rw-r--r--chromium/net/disk_cache/sparse_control.cc884
-rw-r--r--chromium/net/disk_cache/sparse_control.h177
-rw-r--r--chromium/net/disk_cache/stats.cc309
-rw-r--r--chromium/net/disk_cache/stats.h105
-rw-r--r--chromium/net/disk_cache/stats_histogram.cc89
-rw-r--r--chromium/net/disk_cache/stats_histogram.h55
-rw-r--r--chromium/net/disk_cache/storage_block-inl.h175
-rw-r--r--chromium/net/disk_cache/storage_block.h95
-rw-r--r--chromium/net/disk_cache/storage_block_unittest.cc72
-rw-r--r--chromium/net/disk_cache/stress_cache.cc294
-rw-r--r--chromium/net/disk_cache/stress_support.h39
-rw-r--r--chromium/net/disk_cache/trace.cc192
-rw-r--r--chromium/net/disk_cache/trace.h41
-rw-r--r--chromium/net/disk_cache/tracing_cache_backend.cc317
-rw-r--r--chromium/net/disk_cache/tracing_cache_backend.h81
-rw-r--r--chromium/net/disk_cache/v3/backend_impl_v3.cc1640
-rw-r--r--chromium/net/disk_cache/v3/backend_impl_v3.h288
-rw-r--r--chromium/net/disk_cache/v3/backend_worker.cc485
-rw-r--r--chromium/net/disk_cache/v3/backend_worker.h60
-rw-r--r--chromium/net/disk_cache/v3/block_bitmaps.cc332
-rw-r--r--chromium/net/disk_cache/v3/block_bitmaps.h75
-rw-r--r--chromium/net/disk_cache/v3/block_bitmaps_unittest.cc350
-rw-r--r--chromium/net/disk_cache/v3/disk_format_v3.h190
-rw-r--r--chromium/net/disk_cache/v3/entry_impl_v3.cc1395
-rw-r--r--chromium/net/disk_cache/v3/entry_impl_v3.h223
-rw-r--r--chromium/net/disk_cache/v3/eviction_v3.cc502
-rw-r--r--chromium/net/disk_cache/v3/eviction_v3.h74
-rw-r--r--chromium/net/disk_cache/v3/sparse_control_v3.cc868
-rw-r--r--chromium/net/disk_cache/v3/sparse_control_v3.h175
-rw-r--r--chromium/net/dns/address_sorter.h46
-rw-r--r--chromium/net/dns/address_sorter_posix.cc426
-rw-r--r--chromium/net/dns/address_sorter_posix.h94
-rw-r--r--chromium/net/dns/address_sorter_posix_unittest.cc327
-rw-r--r--chromium/net/dns/address_sorter_unittest.cc66
-rw-r--r--chromium/net/dns/address_sorter_win.cc198
-rw-r--r--chromium/net/dns/dns_client.cc71
-rw-r--r--chromium/net/dns/dns_client.h44
-rw-r--r--chromium/net/dns/dns_config_service.cc226
-rw-r--r--chromium/net/dns/dns_config_service.h174
-rw-r--r--chromium/net/dns/dns_config_service_posix.cc404
-rw-r--r--chromium/net/dns/dns_config_service_posix.h67
-rw-r--r--chromium/net/dns/dns_config_service_posix_unittest.cc156
-rw-r--r--chromium/net/dns/dns_config_service_unittest.cc258
-rw-r--r--chromium/net/dns/dns_config_service_win.cc737
-rw-r--r--chromium/net/dns/dns_config_service_win.h154
-rw-r--r--chromium/net/dns/dns_config_service_win_unittest.cc430
-rw-r--r--chromium/net/dns/dns_hosts.cc169
-rw-r--r--chromium/net/dns/dns_hosts.h79
-rw-r--r--chromium/net/dns/dns_hosts_unittest.cc124
-rw-r--r--chromium/net/dns/dns_protocol.h143
-rw-r--r--chromium/net/dns/dns_query.cc89
-rw-r--r--chromium/net/dns/dns_query.h58
-rw-r--r--chromium/net/dns/dns_query_unittest.cc69
-rw-r--r--chromium/net/dns/dns_response.cc337
-rw-r--r--chromium/net/dns/dns_response.h169
-rw-r--r--chromium/net/dns/dns_response_unittest.cc581
-rw-r--r--chromium/net/dns/dns_session.cc298
-rw-r--r--chromium/net/dns/dns_session.h147
-rw-r--r--chromium/net/dns/dns_session_unittest.cc252
-rw-r--r--chromium/net/dns/dns_socket_pool.cc234
-rw-r--r--chromium/net/dns/dns_socket_pool.h91
-rw-r--r--chromium/net/dns/dns_test_util.cc210
-rw-r--r--chromium/net/dns/dns_test_util.h205
-rw-r--r--chromium/net/dns/dns_transaction.cc963
-rw-r--r--chromium/net/dns/dns_transaction.h78
-rw-r--r--chromium/net/dns/dns_transaction_unittest.cc940
-rw-r--r--chromium/net/dns/host_cache.cc122
-rw-r--r--chromium/net/dns/host_cache.h124
-rw-r--r--chromium/net/dns/host_cache_unittest.cc388
-rw-r--r--chromium/net/dns/host_resolver.cc145
-rw-r--r--chromium/net/dns/host_resolver.h204
-rw-r--r--chromium/net/dns/host_resolver_impl.cc2206
-rw-r--r--chromium/net/dns/host_resolver_impl.h285
-rw-r--r--chromium/net/dns/host_resolver_impl_unittest.cc1641
-rw-r--r--chromium/net/dns/host_resolver_proc.cc267
-rw-r--r--chromium/net/dns/host_resolver_proc.h111
-rw-r--r--chromium/net/dns/mapped_host_resolver.cc63
-rw-r--r--chromium/net/dns/mapped_host_resolver.h71
-rw-r--r--chromium/net/dns/mapped_host_resolver_unittest.cc219
-rw-r--r--chromium/net/dns/mdns_cache.cc212
-rw-r--r--chromium/net/dns/mdns_cache.h119
-rw-r--r--chromium/net/dns/mdns_cache_unittest.cc375
-rw-r--r--chromium/net/dns/mdns_client.cc17
-rw-r--r--chromium/net/dns/mdns_client.h158
-rw-r--r--chromium/net/dns/mdns_client_impl.cc671
-rw-r--r--chromium/net/dns/mdns_client_impl.h298
-rw-r--r--chromium/net/dns/mdns_client_unittest.cc1176
-rw-r--r--chromium/net/dns/mock_host_resolver.cc408
-rw-r--r--chromium/net/dns/mock_host_resolver.h284
-rw-r--r--chromium/net/dns/mock_mdns_socket_factory.cc115
-rw-r--r--chromium/net/dns/mock_mdns_socket_factory.h101
-rw-r--r--chromium/net/dns/notify_watcher_mac.cc64
-rw-r--r--chromium/net/dns/notify_watcher_mac.h47
-rw-r--r--chromium/net/dns/record_parsed.cc86
-rw-r--r--chromium/net/dns/record_parsed.h64
-rw-r--r--chromium/net/dns/record_parsed_unittest.cc75
-rw-r--r--chromium/net/dns/record_rdata.cc287
-rw-r--r--chromium/net/dns/record_rdata.h217
-rw-r--r--chromium/net/dns/record_rdata_unittest.cc222
-rw-r--r--chromium/net/dns/serial_worker.cc104
-rw-r--r--chromium/net/dns/serial_worker.h96
-rw-r--r--chromium/net/dns/serial_worker_unittest.cc163
-rw-r--r--chromium/net/dns/single_request_host_resolver.cc77
-rw-r--r--chromium/net/dns/single_request_host_resolver.h56
-rw-r--r--chromium/net/dns/single_request_host_resolver_unittest.cc124
-rw-r--r--chromium/net/ftp/OWNERS1
-rw-r--r--chromium/net/ftp/ftp_auth_cache.cc62
-rw-r--r--chromium/net/ftp/ftp_auth_cache.h61
-rw-r--r--chromium/net/ftp/ftp_auth_cache_unittest.cc159
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer.cc152
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer.h101
-rw-r--r--chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc175
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser.cc145
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser.h46
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls.cc233
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls.h29
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc218
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware.cc94
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware.h29
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc77
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2.cc77
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc116
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_unittest.cc166
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_unittest.h76
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms.cc293
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc168
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows.cc72
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows.h24
-rw-r--r--chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc126
-rw-r--r--chromium/net/ftp/ftp_network_layer.cc44
-rw-r--r--chromium/net/ftp/ftp_network_layer.h38
-rw-r--r--chromium/net/ftp/ftp_network_session.cc15
-rw-r--r--chromium/net/ftp/ftp_network_session.h33
-rw-r--r--chromium/net/ftp/ftp_network_transaction.cc1400
-rw-r--r--chromium/net/ftp/ftp_network_transaction.h262
-rw-r--r--chromium/net/ftp/ftp_network_transaction_unittest.cc1602
-rw-r--r--chromium/net/ftp/ftp_request_info.h20
-rw-r--r--chromium/net/ftp/ftp_response_info.cc17
-rw-r--r--chromium/net/ftp/ftp_response_info.h43
-rw-r--r--chromium/net/ftp/ftp_server_type_histograms.cc33
-rw-r--r--chromium/net/ftp/ftp_server_type_histograms.h32
-rw-r--r--chromium/net/ftp/ftp_transaction.h80
-rw-r--r--chromium/net/ftp/ftp_transaction_factory.h29
-rw-r--r--chromium/net/ftp/ftp_util.cc376
-rw-r--r--chromium/net/ftp/ftp_util.h58
-rw-r--r--chromium/net/ftp/ftp_util_unittest.cc253
-rw-r--r--chromium/net/http/des.cc220
-rw-r--r--chromium/net/http/des.h29
-rw-r--r--chromium/net/http/des_unittest.cc50
-rw-r--r--chromium/net/http/http_atom_list.h61
-rw-r--r--chromium/net/http/http_auth.cc198
-rw-r--r--chromium/net/http/http_auth.h216
-rw-r--r--chromium/net/http/http_auth_cache.cc244
-rw-r--r--chromium/net/http/http_auth_cache.h181
-rw-r--r--chromium/net/http/http_auth_cache_unittest.cc626
-rw-r--r--chromium/net/http/http_auth_controller.cc573
-rw-r--r--chromium/net/http/http_auth_controller.h170
-rw-r--r--chromium/net/http/http_auth_controller_unittest.cc238
-rw-r--r--chromium/net/http/http_auth_filter.cc58
-rw-r--r--chromium/net/http/http_auth_filter.h65
-rw-r--r--chromium/net/http/http_auth_filter_unittest.cc99
-rw-r--r--chromium/net/http/http_auth_filter_win.h37
-rw-r--r--chromium/net/http/http_auth_gssapi_posix.cc893
-rw-r--r--chromium/net/http/http_auth_gssapi_posix.h277
-rw-r--r--chromium/net/http/http_auth_gssapi_posix_unittest.cc274
-rw-r--r--chromium/net/http/http_auth_handler.cc108
-rw-r--r--chromium/net/http/http_auth_handler.h198
-rw-r--r--chromium/net/http/http_auth_handler_basic.cc128
-rw-r--r--chromium/net/http/http_auth_handler_basic.h53
-rw-r--r--chromium/net/http/http_auth_handler_basic_unittest.cc196
-rw-r--r--chromium/net/http/http_auth_handler_digest.cc381
-rw-r--r--chromium/net/http/http_auth_handler_digest.h179
-rw-r--r--chromium/net/http/http_auth_handler_digest_unittest.cc696
-rw-r--r--chromium/net/http/http_auth_handler_factory.cc198
-rw-r--r--chromium/net/http/http_auth_handler_factory.h202
-rw-r--r--chromium/net/http/http_auth_handler_factory_unittest.cc190
-rw-r--r--chromium/net/http/http_auth_handler_mock.cc186
-rw-r--r--chromium/net/http/http_auth_handler_mock.h124
-rw-r--r--chromium/net/http/http_auth_handler_negotiate.cc338
-rw-r--r--chromium/net/http/http_auth_handler_negotiate.h168
-rw-r--r--chromium/net/http/http_auth_handler_negotiate_unittest.cc368
-rw-r--r--chromium/net/http/http_auth_handler_ntlm.cc147
-rw-r--r--chromium/net/http/http_auth_handler_ntlm.h173
-rw-r--r--chromium/net/http/http_auth_handler_ntlm_portable.cc730
-rw-r--r--chromium/net/http/http_auth_handler_ntlm_win.cc83
-rw-r--r--chromium/net/http/http_auth_handler_unittest.cc62
-rw-r--r--chromium/net/http/http_auth_sspi_win.cc429
-rw-r--r--chromium/net/http/http_auth_sspi_win.h207
-rw-r--r--chromium/net/http/http_auth_sspi_win_unittest.cc152
-rw-r--r--chromium/net/http/http_auth_unittest.cc435
-rw-r--r--chromium/net/http/http_basic_stream.cc134
-rw-r--r--chromium/net/http/http_basic_stream.h104
-rw-r--r--chromium/net/http/http_byte_range.cc76
-rw-r--r--chromium/net/http/http_byte_range.h59
-rw-r--r--chromium/net/http/http_byte_range_unittest.cc78
-rw-r--r--chromium/net/http/http_cache.cc1147
-rw-r--r--chromium/net/http/http_cache.h404
-rw-r--r--chromium/net/http/http_cache_transaction.cc2612
-rw-r--r--chromium/net/http/http_cache_transaction.h466
-rw-r--r--chromium/net/http/http_cache_unittest.cc6003
-rw-r--r--chromium/net/http/http_chunked_decoder.cc214
-rw-r--r--chromium/net/http/http_chunked_decoder.h132
-rw-r--r--chromium/net/http/http_chunked_decoder_unittest.cc356
-rw-r--r--chromium/net/http/http_content_disposition.cc456
-rw-r--r--chromium/net/http/http_content_disposition.h79
-rw-r--r--chromium/net/http/http_content_disposition_unittest.cc590
-rw-r--r--chromium/net/http/http_network_layer.cc78
-rw-r--r--chromium/net/http/http_network_layer.h61
-rw-r--r--chromium/net/http/http_network_layer_unittest.cc447
-rw-r--r--chromium/net/http/http_network_session.cc229
-rw-r--r--chromium/net/http/http_network_session.h209
-rw-r--r--chromium/net/http/http_network_session_peer.cc42
-rw-r--r--chromium/net/http/http_network_session_peer.h40
-rw-r--r--chromium/net/http/http_network_transaction.cc1488
-rw-r--r--chromium/net/http/http_network_transaction.h314
-rw-r--r--chromium/net/http/http_network_transaction_ssl_unittest.cc302
-rw-r--r--chromium/net/http/http_network_transaction_unittest.cc11981
-rw-r--r--chromium/net/http/http_pipelined_connection.h93
-rw-r--r--chromium/net/http/http_pipelined_connection_impl.cc838
-rw-r--r--chromium/net/http/http_pipelined_connection_impl.h328
-rw-r--r--chromium/net/http/http_pipelined_connection_impl_unittest.cc1606
-rw-r--r--chromium/net/http/http_pipelined_host.cc17
-rw-r--r--chromium/net/http/http_pipelined_host.h102
-rw-r--r--chromium/net/http/http_pipelined_host_capability.h23
-rw-r--r--chromium/net/http/http_pipelined_host_forced.cc103
-rw-r--r--chromium/net/http/http_pipelined_host_forced.h83
-rw-r--r--chromium/net/http/http_pipelined_host_forced_unittest.cc106
-rw-r--r--chromium/net/http/http_pipelined_host_impl.cc210
-rw-r--r--chromium/net/http/http_pipelined_host_impl.h117
-rw-r--r--chromium/net/http/http_pipelined_host_impl_unittest.cc308
-rw-r--r--chromium/net/http/http_pipelined_host_pool.cc145
-rw-r--r--chromium/net/http/http_pipelined_host_pool.h102
-rw-r--r--chromium/net/http/http_pipelined_host_pool_unittest.cc262
-rw-r--r--chromium/net/http/http_pipelined_host_test_util.cc33
-rw-r--r--chromium/net/http/http_pipelined_host_test_util.h70
-rw-r--r--chromium/net/http/http_pipelined_network_transaction_unittest.cc1035
-rw-r--r--chromium/net/http/http_pipelined_stream.cc149
-rw-r--r--chromium/net/http/http_pipelined_stream.h113
-rw-r--r--chromium/net/http/http_proxy_client_socket.cc538
-rw-r--r--chromium/net/http/http_proxy_client_socket.h173
-rw-r--r--chromium/net/http/http_proxy_client_socket_pool.cc542
-rw-r--r--chromium/net/http/http_proxy_client_socket_pool.h282
-rw-r--r--chromium/net/http/http_proxy_client_socket_pool_unittest.cc656
-rw-r--r--chromium/net/http/http_request_headers.cc258
-rw-r--r--chromium/net/http/http_request_headers.h177
-rw-r--r--chromium/net/http/http_request_headers_unittest.cc188
-rw-r--r--chromium/net/http/http_request_info.cc19
-rw-r--r--chromium/net/http/http_request_info.h63
-rw-r--r--chromium/net/http/http_response_body_drainer.cc145
-rw-r--r--chromium/net/http/http_response_body_drainer.h71
-rw-r--r--chromium/net/http/http_response_body_drainer_unittest.cc325
-rw-r--r--chromium/net/http/http_response_headers.cc1357
-rw-r--r--chromium/net/http/http_response_headers.h377
-rw-r--r--chromium/net/http/http_response_headers_unittest.cc1879
-rw-r--r--chromium/net/http/http_response_info.cc382
-rw-r--r--chromium/net/http/http_response_info.h145
-rw-r--r--chromium/net/http/http_security_headers.cc334
-rw-r--r--chromium/net/http/http_security_headers.h59
-rw-r--r--chromium/net/http/http_security_headers_unittest.cc505
-rw-r--r--chromium/net/http/http_server_properties.cc97
-rw-r--r--chromium/net/http/http_server_properties.h141
-rw-r--r--chromium/net/http/http_server_properties_impl.cc303
-rw-r--r--chromium/net/http/http_server_properties_impl.h161
-rw-r--r--chromium/net/http/http_server_properties_impl_unittest.cc416
-rw-r--r--chromium/net/http/http_status_code.cc25
-rw-r--r--chromium/net/http/http_status_code.h33
-rw-r--r--chromium/net/http/http_status_code_list.h67
-rw-r--r--chromium/net/http/http_status_code_unittest.cc20
-rw-r--r--chromium/net/http/http_stream.h45
-rw-r--r--chromium/net/http/http_stream_base.h153
-rw-r--r--chromium/net/http/http_stream_factory.cc243
-rw-r--r--chromium/net/http/http_stream_factory.h314
-rw-r--r--chromium/net/http/http_stream_factory_impl.cc361
-rw-r--r--chromium/net/http/http_stream_factory_impl.h158
-rw-r--r--chromium/net/http/http_stream_factory_impl_job.cc1480
-rw-r--r--chromium/net/http/http_stream_factory_impl_job.h334
-rw-r--r--chromium/net/http/http_stream_factory_impl_request.cc389
-rw-r--r--chromium/net/http/http_stream_factory_impl_request.h148
-rw-r--r--chromium/net/http/http_stream_factory_impl_request_unittest.cc98
-rw-r--r--chromium/net/http/http_stream_factory_impl_unittest.cc1226
-rw-r--r--chromium/net/http/http_stream_parser.cc955
-rw-r--r--chromium/net/http/http_stream_parser.h238
-rw-r--r--chromium/net/http/http_stream_parser_unittest.cc416
-rw-r--r--chromium/net/http/http_transaction.h138
-rw-r--r--chromium/net/http/http_transaction_delegate.h26
-rw-r--r--chromium/net/http/http_transaction_factory.h39
-rw-r--r--chromium/net/http/http_transaction_unittest.cc445
-rw-r--r--chromium/net/http/http_transaction_unittest.h280
-rw-r--r--chromium/net/http/http_util.cc920
-rw-r--r--chromium/net/http/http_util.h359
-rw-r--r--chromium/net/http/http_util_icu.cc32
-rw-r--r--chromium/net/http/http_util_unittest.cc1051
-rw-r--r--chromium/net/http/http_vary_data.cc126
-rw-r--r--chromium/net/http/http_vary_data.h86
-rw-r--r--chromium/net/http/http_vary_data_unittest.cc147
-rw-r--r--chromium/net/http/http_version.h58
-rw-r--r--chromium/net/http/md4.cc184
-rw-r--r--chromium/net/http/md4.h74
-rw-r--r--chromium/net/http/mock_allow_url_security_manager.cc22
-rw-r--r--chromium/net/http/mock_allow_url_security_manager.h28
-rw-r--r--chromium/net/http/mock_gssapi_library_posix.cc480
-rw-r--r--chromium/net/http/mock_gssapi_library_posix.h199
-rw-r--r--chromium/net/http/mock_http_cache.cc619
-rw-r--r--chromium/net/http/mock_http_cache.h241
-rw-r--r--chromium/net/http/mock_sspi_library_win.cc106
-rw-r--r--chromium/net/http/mock_sspi_library_win.h111
-rw-r--r--chromium/net/http/partial_data.cc496
-rw-r--r--chromium/net/http/partial_data.h145
-rw-r--r--chromium/net/http/proxy_client_socket.cc99
-rw-r--r--chromium/net/http/proxy_client_socket.h91
-rw-r--r--chromium/net/http/proxy_connect_redirect_http_stream.cc126
-rw-r--r--chromium/net/http/proxy_connect_redirect_http_stream.h76
-rw-r--r--chromium/net/http/transport_security_state.cc894
-rw-r--r--chromium/net/http/transport_security_state.h328
-rw-r--r--chromium/net/http/transport_security_state_static.certs1218
-rw-r--r--chromium/net/http/transport_security_state_static.h850
-rw-r--r--chromium/net/http/transport_security_state_static.json636
-rw-r--r--chromium/net/http/transport_security_state_unittest.cc842
-rw-r--r--chromium/net/http/url_security_manager.cc33
-rw-r--r--chromium/net/http/url_security_manager.h79
-rw-r--r--chromium/net/http/url_security_manager_posix.cc18
-rw-r--r--chromium/net/http/url_security_manager_unittest.cc96
-rw-r--r--chromium/net/http/url_security_manager_win.cc137
-rw-r--r--chromium/net/net.gyp2927
-rw-r--r--chromium/net/net_unittests.isolate70
-rw-r--r--chromium/net/ocsp/nss_ocsp.cc974
-rw-r--r--chromium/net/ocsp/nss_ocsp.h39
-rw-r--r--chromium/net/ocsp/nss_ocsp_unittest.cc163
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc288
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h176
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc299
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher.cc34
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher.h99
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_factory.cc55
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_factory.h68
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc59
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_win.cc370
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_win.h169
-rw-r--r--chromium/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc644
-rw-r--r--chromium/net/proxy/dhcpcsvc_init_win.cc40
-rw-r--r--chromium/net/proxy/dhcpcsvc_init_win.h19
-rw-r--r--chromium/net/proxy/mock_proxy_resolver.cc115
-rw-r--r--chromium/net/proxy/mock_proxy_resolver.h129
-rw-r--r--chromium/net/proxy/mock_proxy_script_fetcher.cc69
-rw-r--r--chromium/net/proxy/mock_proxy_script_fetcher.h48
-rw-r--r--chromium/net/proxy/multi_threaded_proxy_resolver.cc583
-rw-r--r--chromium/net/proxy/multi_threaded_proxy_resolver.h143
-rw-r--r--chromium/net/proxy/multi_threaded_proxy_resolver_unittest.cc786
-rw-r--r--chromium/net/proxy/network_delegate_error_observer.cc82
-rw-r--r--chromium/net/proxy/network_delegate_error_observer.h43
-rw-r--r--chromium/net/proxy/network_delegate_error_observer_unittest.cc132
-rw-r--r--chromium/net/proxy/polling_proxy_config_service.cc194
-rw-r--r--chromium/net/proxy/polling_proxy_config_service.h55
-rw-r--r--chromium/net/proxy/proxy_bypass_rules.cc347
-rw-r--r--chromium/net/proxy/proxy_bypass_rules.h182
-rw-r--r--chromium/net/proxy/proxy_bypass_rules_unittest.cc315
-rw-r--r--chromium/net/proxy/proxy_config.cc277
-rw-r--r--chromium/net/proxy/proxy_config.h263
-rw-r--r--chromium/net/proxy/proxy_config_service.h68
-rw-r--r--chromium/net/proxy/proxy_config_service_android.cc339
-rw-r--r--chromium/net/proxy/proxy_config_service_android.h79
-rw-r--r--chromium/net/proxy/proxy_config_service_android_unittest.cc352
-rw-r--r--chromium/net/proxy/proxy_config_service_common_unittest.cc191
-rw-r--r--chromium/net/proxy/proxy_config_service_common_unittest.h80
-rw-r--r--chromium/net/proxy/proxy_config_service_fixed.cc21
-rw-r--r--chromium/net/proxy/proxy_config_service_fixed.h32
-rw-r--r--chromium/net/proxy/proxy_config_service_ios.cc109
-rw-r--r--chromium/net/proxy/proxy_config_service_ios.h24
-rw-r--r--chromium/net/proxy/proxy_config_service_linux.cc1766
-rw-r--r--chromium/net/proxy/proxy_config_service_linux.h309
-rw-r--r--chromium/net/proxy/proxy_config_service_linux_unittest.cc1617
-rw-r--r--chromium/net/proxy/proxy_config_service_mac.cc286
-rw-r--r--chromium/net/proxy/proxy_config_service_mac.h88
-rw-r--r--chromium/net/proxy/proxy_config_service_win.cc194
-rw-r--r--chromium/net/proxy/proxy_config_service_win.h80
-rw-r--r--chromium/net/proxy/proxy_config_service_win_unittest.cc203
-rw-r--r--chromium/net/proxy/proxy_config_source.cc35
-rw-r--r--chromium/net/proxy/proxy_config_source.h37
-rw-r--r--chromium/net/proxy/proxy_config_unittest.cc357
-rw-r--r--chromium/net/proxy/proxy_info.cc90
-rw-r--r--chromium/net/proxy/proxy_info.h169
-rw-r--r--chromium/net/proxy/proxy_info_unittest.cc41
-rw-r--r--chromium/net/proxy/proxy_list.cc230
-rw-r--r--chromium/net/proxy/proxy_list.h103
-rw-r--r--chromium/net/proxy/proxy_list_unittest.cc183
-rw-r--r--chromium/net/proxy/proxy_resolver.h83
-rw-r--r--chromium/net/proxy/proxy_resolver_error_observer.h37
-rw-r--r--chromium/net/proxy/proxy_resolver_mac.cc204
-rw-r--r--chromium/net/proxy/proxy_resolver_mac.h46
-rw-r--r--chromium/net/proxy/proxy_resolver_perftest.cc229
-rw-r--r--chromium/net/proxy/proxy_resolver_script.h276
-rw-r--r--chromium/net/proxy/proxy_resolver_script_data.cc76
-rw-r--r--chromium/net/proxy/proxy_resolver_script_data.h74
-rw-r--r--chromium/net/proxy/proxy_resolver_v8.cc808
-rw-r--r--chromium/net/proxy/proxy_resolver_v8.h131
-rw-r--r--chromium/net/proxy/proxy_resolver_v8_tracing.cc1181
-rw-r--r--chromium/net/proxy/proxy_resolver_v8_tracing.h85
-rw-r--r--chromium/net/proxy/proxy_resolver_v8_tracing_unittest.cc1098
-rw-r--r--chromium/net/proxy/proxy_resolver_v8_unittest.cc631
-rw-r--r--chromium/net/proxy/proxy_resolver_winhttp.cc173
-rw-r--r--chromium/net/proxy/proxy_resolver_winhttp.h53
-rw-r--r--chromium/net/proxy/proxy_retry_info.h30
-rw-r--r--chromium/net/proxy/proxy_script_decider.cc414
-rw-r--r--chromium/net/proxy/proxy_script_decider.h184
-rw-r--r--chromium/net/proxy/proxy_script_decider_unittest.cc599
-rw-r--r--chromium/net/proxy/proxy_script_fetcher.h60
-rw-r--r--chromium/net/proxy/proxy_script_fetcher_impl.cc321
-rw-r--r--chromium/net/proxy/proxy_script_fetcher_impl.h127
-rw-r--r--chromium/net/proxy/proxy_script_fetcher_impl_unittest.cc481
-rw-r--r--chromium/net/proxy/proxy_server.cc243
-rw-r--r--chromium/net/proxy/proxy_server.h165
-rw-r--r--chromium/net/proxy/proxy_server_mac.cc49
-rw-r--r--chromium/net/proxy/proxy_server_unittest.cc368
-rw-r--r--chromium/net/proxy/proxy_service.cc1576
-rw-r--r--chromium/net/proxy/proxy_service.h441
-rw-r--r--chromium/net/proxy/proxy_service_unittest.cc2791
-rw-r--r--chromium/net/proxy/proxy_service_v8.cc44
-rw-r--r--chromium/net/proxy/proxy_service_v8.h50
-rw-r--r--chromium/net/quic/blocked_list.h91
-rw-r--r--chromium/net/quic/blocked_list_test.cc83
-rw-r--r--chromium/net/quic/congestion_control/available_channel_estimator.cc78
-rw-r--r--chromium/net/quic/congestion_control/available_channel_estimator.h64
-rw-r--r--chromium/net/quic/congestion_control/available_channel_estimator_test.cc109
-rw-r--r--chromium/net/quic/congestion_control/channel_estimator.cc110
-rw-r--r--chromium/net/quic/congestion_control/channel_estimator.h61
-rw-r--r--chromium/net/quic/congestion_control/channel_estimator_test.cc224
-rw-r--r--chromium/net/quic/congestion_control/cube_root.cc87
-rw-r--r--chromium/net/quic/congestion_control/cube_root.h21
-rw-r--r--chromium/net/quic/congestion_control/cube_root_test.cc47
-rw-r--r--chromium/net/quic/congestion_control/cubic.cc198
-rw-r--r--chromium/net/quic/congestion_control/cubic.h87
-rw-r--r--chromium/net/quic/congestion_control/cubic_test.cc150
-rw-r--r--chromium/net/quic/congestion_control/fix_rate_receiver.cc35
-rw-r--r--chromium/net/quic/congestion_control/fix_rate_receiver.h44
-rw-r--r--chromium/net/quic/congestion_control/fix_rate_sender.cc121
-rw-r--r--chromium/net/quic/congestion_control/fix_rate_sender.h65
-rw-r--r--chromium/net/quic/congestion_control/fix_rate_test.cc120
-rw-r--r--chromium/net/quic/congestion_control/hybrid_slow_start.cc112
-rw-r--r--chromium/net/quic/congestion_control/hybrid_slow_start.h67
-rw-r--r--chromium/net/quic/congestion_control/hybrid_slow_start_test.cc108
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.cc174
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h65
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up_test.cc404
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_overuse_detector.cc258
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_overuse_detector.h173
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_overuse_detector_test.cc1114
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_probe.cc117
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_probe.h58
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_probe_test.cc81
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_receiver.cc48
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_receiver.h46
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_receiver_test.cc55
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_sender.cc505
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_sender.h100
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_sender_test.cc565
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_state_machine.cc163
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_state_machine.h94
-rw-r--r--chromium/net/quic/congestion_control/inter_arrival_state_machine_test.cc126
-rw-r--r--chromium/net/quic/congestion_control/leaky_bucket.cc50
-rw-r--r--chromium/net/quic/congestion_control/leaky_bucket.h50
-rw-r--r--chromium/net/quic/congestion_control/leaky_bucket_test.cc75
-rw-r--r--chromium/net/quic/congestion_control/paced_sender.cc50
-rw-r--r--chromium/net/quic/congestion_control/paced_sender.h41
-rw-r--r--chromium/net/quic/congestion_control/paced_sender_test.cc77
-rw-r--r--chromium/net/quic/congestion_control/quic_congestion_control_test.cc136
-rw-r--r--chromium/net/quic/congestion_control/quic_congestion_manager.cc214
-rw-r--r--chromium/net/quic/congestion_control/quic_congestion_manager.h121
-rw-r--r--chromium/net/quic/congestion_control/quic_congestion_manager_test.cc236
-rw-r--r--chromium/net/quic/congestion_control/quic_max_sized_map.h77
-rw-r--r--chromium/net/quic/congestion_control/quic_max_sized_map_test.cc66
-rw-r--r--chromium/net/quic/congestion_control/receive_algorithm_interface.cc27
-rw-r--r--chromium/net/quic/congestion_control/receive_algorithm_interface.h44
-rw-r--r--chromium/net/quic/congestion_control/send_algorithm_interface.cc34
-rw-r--r--chromium/net/quic/congestion_control/send_algorithm_interface.h90
-rw-r--r--chromium/net/quic/congestion_control/tcp_cubic_sender.cc271
-rw-r--r--chromium/net/quic/congestion_control/tcp_cubic_sender.h118
-rw-r--r--chromium/net/quic/congestion_control/tcp_cubic_sender_test.cc256
-rw-r--r--chromium/net/quic/congestion_control/tcp_receiver.cc36
-rw-r--r--chromium/net/quic/congestion_control/tcp_receiver.h41
-rw-r--r--chromium/net/quic/congestion_control/tcp_receiver_test.cc38
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_decrypter.h69
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc387
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc152
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc386
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_encrypter.h74
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc397
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc165
-rw-r--r--chromium/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc348
-rw-r--r--chromium/net/quic/crypto/cert_compressor.cc646
-rw-r--r--chromium/net/quic/crypto/cert_compressor.h55
-rw-r--r--chromium/net/quic/crypto/cert_compressor_test.cc140
-rw-r--r--chromium/net/quic/crypto/channel_id.cc14
-rw-r--r--chromium/net/quic/crypto/channel_id.h64
-rw-r--r--chromium/net/quic/crypto/channel_id_nss.cc83
-rw-r--r--chromium/net/quic/crypto/channel_id_openssl.cc89
-rw-r--r--chromium/net/quic/crypto/channel_id_test.cc303
-rw-r--r--chromium/net/quic/crypto/common_cert_set.cc158
-rw-r--r--chromium/net/quic/crypto/common_cert_set.h47
-rw-r--r--chromium/net/quic/crypto/common_cert_set_0.c223
-rw-r--r--chromium/net/quic/crypto/common_cert_set_1_50.inc9794
-rw-r--r--chromium/net/quic/crypto/common_cert_set_51_100.inc11308
-rw-r--r--chromium/net/quic/crypto/common_cert_set_test.cc109
-rw-r--r--chromium/net/quic/crypto/crypto_framer.cc292
-rw-r--r--chromium/net/quic/crypto/crypto_framer.h118
-rw-r--r--chromium/net/quic/crypto/crypto_framer_test.cc485
-rw-r--r--chromium/net/quic/crypto/crypto_handshake.cc897
-rw-r--r--chromium/net/quic/crypto/crypto_handshake.h399
-rw-r--r--chromium/net/quic/crypto/crypto_handshake_test.cc360
-rw-r--r--chromium/net/quic/crypto/crypto_protocol.h131
-rw-r--r--chromium/net/quic/crypto/crypto_secret_boxer.cc90
-rw-r--r--chromium/net/quic/crypto/crypto_secret_boxer.h49
-rw-r--r--chromium/net/quic/crypto/crypto_secret_boxer_test.cc41
-rw-r--r--chromium/net/quic/crypto/crypto_server_config.cc1070
-rw-r--r--chromium/net/quic/crypto/crypto_server_config.h364
-rw-r--r--chromium/net/quic/crypto/crypto_server_config_protobuf.cc20
-rw-r--r--chromium/net/quic/crypto/crypto_server_config_protobuf.h101
-rw-r--r--chromium/net/quic/crypto/crypto_server_test.cc256
-rw-r--r--chromium/net/quic/crypto/crypto_utils.cc114
-rw-r--r--chromium/net/quic/crypto/crypto_utils.h69
-rw-r--r--chromium/net/quic/crypto/crypto_utils_test.cc49
-rw-r--r--chromium/net/quic/crypto/curve25519_key_exchange.cc86
-rw-r--r--chromium/net/quic/crypto/curve25519_key_exchange.h49
-rw-r--r--chromium/net/quic/crypto/curve25519_key_exchange_test.cc42
-rw-r--r--chromium/net/quic/crypto/ephemeral_key_source.h42
-rw-r--r--chromium/net/quic/crypto/key_exchange.h47
-rw-r--r--chromium/net/quic/crypto/null_decrypter.cc74
-rw-r--r--chromium/net/quic/crypto/null_decrypter.h38
-rw-r--r--chromium/net/quic/crypto/null_decrypter_test.cc66
-rw-r--r--chromium/net/quic/crypto/null_encrypter.cc61
-rw-r--r--chromium/net/quic/crypto/null_encrypter.h41
-rw-r--r--chromium/net/quic/crypto/null_encrypter_test.cc48
-rw-r--r--chromium/net/quic/crypto/p256_key_exchange.h80
-rw-r--r--chromium/net/quic/crypto/p256_key_exchange_nss.cc233
-rw-r--r--chromium/net/quic/crypto/p256_key_exchange_openssl.cc119
-rw-r--r--chromium/net/quic/crypto/p256_key_exchange_test.cc44
-rw-r--r--chromium/net/quic/crypto/proof_source.h62
-rw-r--r--chromium/net/quic/crypto/proof_source_chromium.cc24
-rw-r--r--chromium/net/quic/crypto/proof_source_chromium.h39
-rw-r--r--chromium/net/quic/crypto/proof_test.cc443
-rw-r--r--chromium/net/quic/crypto/proof_verifier.cc15
-rw-r--r--chromium/net/quic/crypto/proof_verifier.h89
-rw-r--r--chromium/net/quic/crypto/proof_verifier_chromium.cc259
-rw-r--r--chromium/net/quic/crypto/proof_verifier_chromium.h91
-rw-r--r--chromium/net/quic/crypto/quic_decrypter.cc25
-rw-r--r--chromium/net/quic/crypto/quic_decrypter.h72
-rw-r--r--chromium/net/quic/crypto/quic_encrypter.cc25
-rw-r--r--chromium/net/quic/crypto/quic_encrypter.h87
-rw-r--r--chromium/net/quic/crypto/quic_random.cc66
-rw-r--r--chromium/net/quic/crypto/quic_random.h41
-rw-r--r--chromium/net/quic/crypto/quic_random_test.cc40
-rw-r--r--chromium/net/quic/crypto/scoped_evp_cipher_ctx.cc22
-rw-r--r--chromium/net/quic/crypto/scoped_evp_cipher_ctx.h30
-rw-r--r--chromium/net/quic/crypto/source_address_token.cc46
-rw-r--r--chromium/net/quic/crypto/source_address_token.h48
-rw-r--r--chromium/net/quic/crypto/strike_register.cc465
-rw-r--r--chromium/net/quic/crypto/strike_register.h189
-rw-r--r--chromium/net/quic/crypto/strike_register_test.cc303
-rw-r--r--chromium/net/quic/quic_alarm.cc48
-rw-r--r--chromium/net/quic/quic_alarm.h77
-rw-r--r--chromium/net/quic/quic_alarm_test.cc126
-rw-r--r--chromium/net/quic/quic_bandwidth.cc102
-rw-r--r--chromium/net/quic/quic_bandwidth.h85
-rw-r--r--chromium/net/quic/quic_bandwidth_test.cc82
-rw-r--r--chromium/net/quic/quic_blocked_writer_interface.h28
-rw-r--r--chromium/net/quic/quic_client_session.cc392
-rw-r--r--chromium/net/quic/quic_client_session.h172
-rw-r--r--chromium/net/quic/quic_client_session_test.cc134
-rw-r--r--chromium/net/quic/quic_clock.cc29
-rw-r--r--chromium/net/quic/quic_clock.h37
-rw-r--r--chromium/net/quic/quic_clock_test.cc38
-rw-r--r--chromium/net/quic/quic_config.cc355
-rw-r--r--chromium/net/quic/quic_config.h197
-rw-r--r--chromium/net/quic/quic_config_test.cc167
-rw-r--r--chromium/net/quic/quic_connection.cc1661
-rw-r--r--chromium/net/quic/quic_connection.h698
-rw-r--r--chromium/net/quic/quic_connection_helper.cc153
-rw-r--r--chromium/net/quic/quic_connection_helper.h72
-rw-r--r--chromium/net/quic/quic_connection_helper_test.cc474
-rw-r--r--chromium/net/quic/quic_connection_logger.cc416
-rw-r--r--chromium/net/quic/quic_connection_logger.h78
-rw-r--r--chromium/net/quic/quic_connection_test.cc2445
-rw-r--r--chromium/net/quic/quic_crypto_client_stream.cc375
-rw-r--r--chromium/net/quic/quic_crypto_client_stream.h123
-rw-r--r--chromium/net/quic/quic_crypto_client_stream_factory.h31
-rw-r--r--chromium/net/quic/quic_crypto_client_stream_test.cc194
-rw-r--r--chromium/net/quic/quic_crypto_server_stream.cc142
-rw-r--r--chromium/net/quic/quic_crypto_server_stream.h57
-rw-r--r--chromium/net/quic/quic_crypto_server_stream_test.cc265
-rw-r--r--chromium/net/quic/quic_crypto_stream.cc71
-rw-r--r--chromium/net/quic/quic_crypto_stream.h70
-rw-r--r--chromium/net/quic/quic_crypto_stream_test.cc114
-rw-r--r--chromium/net/quic/quic_data_reader.cc133
-rw-r--r--chromium/net/quic/quic_data_reader.h126
-rw-r--r--chromium/net/quic/quic_data_writer.cc152
-rw-r--r--chromium/net/quic/quic_data_writer.h80
-rw-r--r--chromium/net/quic/quic_data_writer_test.cc46
-rw-r--r--chromium/net/quic/quic_fec_group.cc166
-rw-r--r--chromium/net/quic/quic_fec_group.h98
-rw-r--r--chromium/net/quic/quic_fec_group_test.cc219
-rw-r--r--chromium/net/quic/quic_framer.cc1859
-rw-r--r--chromium/net/quic/quic_framer.h455
-rw-r--r--chromium/net/quic/quic_framer_test.cc3639
-rw-r--r--chromium/net/quic/quic_http_stream.cc512
-rw-r--r--chromium/net/quic/quic_http_stream.h149
-rw-r--r--chromium/net/quic/quic_http_stream_test.cc562
-rw-r--r--chromium/net/quic/quic_network_transaction_unittest.cc752
-rw-r--r--chromium/net/quic/quic_packet_creator.cc300
-rw-r--r--chromium/net/quic/quic_packet_creator.h182
-rw-r--r--chromium/net/quic/quic_packet_creator_test.cc333
-rw-r--r--chromium/net/quic/quic_packet_generator.cc221
-rw-r--r--chromium/net/quic/quic_packet_generator.h145
-rw-r--r--chromium/net/quic/quic_packet_generator_test.cc536
-rw-r--r--chromium/net/quic/quic_protocol.cc429
-rw-r--r--chromium/net/quic/quic_protocol.h853
-rw-r--r--chromium/net/quic/quic_protocol_test.cc146
-rw-r--r--chromium/net/quic/quic_received_packet_manager.cc188
-rw-r--r--chromium/net/quic/quic_received_packet_manager.h124
-rw-r--r--chromium/net/quic/quic_received_packet_manager_test.cc119
-rw-r--r--chromium/net/quic/quic_reliable_client_stream.cc62
-rw-r--r--chromium/net/quic/quic_reliable_client_stream.h87
-rw-r--r--chromium/net/quic/quic_reliable_client_stream_test.cc82
-rw-r--r--chromium/net/quic/quic_sent_entropy_manager.cc87
-rw-r--r--chromium/net/quic/quic_sent_entropy_manager.h62
-rw-r--r--chromium/net/quic/quic_sent_entropy_manager_test.cc73
-rw-r--r--chromium/net/quic/quic_session.cc414
-rw-r--r--chromium/net/quic/quic_session.h266
-rw-r--r--chromium/net/quic/quic_session_test.cc287
-rw-r--r--chromium/net/quic/quic_spdy_compressor.cc50
-rw-r--r--chromium/net/quic/quic_spdy_compressor.h38
-rw-r--r--chromium/net/quic/quic_spdy_compressor_test.cc46
-rw-r--r--chromium/net/quic/quic_spdy_decompressor.cc140
-rw-r--r--chromium/net/quic/quic_spdy_decompressor.h65
-rw-r--r--chromium/net/quic/quic_spdy_decompressor_test.cc103
-rw-r--r--chromium/net/quic/quic_stats.cc25
-rw-r--r--chromium/net/quic/quic_stats.h50
-rw-r--r--chromium/net/quic/quic_stream_factory.cc489
-rw-r--r--chromium/net/quic/quic_stream_factory.h183
-rw-r--r--chromium/net/quic/quic_stream_factory_test.cc365
-rw-r--r--chromium/net/quic/quic_stream_sequencer.cc279
-rw-r--r--chromium/net/quic/quic_stream_sequencer.h102
-rw-r--r--chromium/net/quic/quic_stream_sequencer_test.cc577
-rw-r--r--chromium/net/quic/quic_time.cc163
-rw-r--r--chromium/net/quic/quic_time.h184
-rw-r--r--chromium/net/quic/quic_time_test.cc113
-rw-r--r--chromium/net/quic/quic_utils.cc270
-rw-r--r--chromium/net/quic/quic_utils.h81
-rw-r--r--chromium/net/quic/quic_utils_test.cc73
-rw-r--r--chromium/net/quic/reliable_quic_stream.cc436
-rw-r--r--chromium/net/quic/reliable_quic_stream.h199
-rw-r--r--chromium/net/quic/reliable_quic_stream_test.cc535
-rw-r--r--chromium/net/quic/spdy_utils.cc25
-rw-r--r--chromium/net/quic/spdy_utils.h23
-rw-r--r--chromium/net/quic/test_tools/crypto_test_utils.cc505
-rw-r--r--chromium/net/quic/test_tools/crypto_test_utils.h132
-rw-r--r--chromium/net/quic/test_tools/crypto_test_utils_chromium.cc53
-rw-r--r--chromium/net/quic/test_tools/crypto_test_utils_nss.cc138
-rw-r--r--chromium/net/quic/test_tools/crypto_test_utils_openssl.cc173
-rw-r--r--chromium/net/quic/test_tools/mock_clock.cc38
-rw-r--r--chromium/net/quic/test_tools/mock_clock.h38
-rw-r--r--chromium/net/quic/test_tools/mock_crypto_client_stream.cc75
-rw-r--r--chromium/net/quic/test_tools/mock_crypto_client_stream.h57
-rw-r--r--chromium/net/quic/test_tools/mock_crypto_client_stream_factory.cc27
-rw-r--r--chromium/net/quic/test_tools/mock_crypto_client_stream_factory.h39
-rw-r--r--chromium/net/quic/test_tools/mock_random.cc32
-rw-r--r--chromium/net/quic/test_tools/mock_random.h38
-rw-r--r--chromium/net/quic/test_tools/quic_client_session_peer.cc21
-rw-r--r--chromium/net/quic/test_tools/quic_client_session_peer.h29
-rw-r--r--chromium/net/quic/test_tools/quic_connection_peer.cc181
-rw-r--r--chromium/net/quic/test_tools/quic_connection_peer.h105
-rw-r--r--chromium/net/quic/test_tools/quic_framer_peer.cc42
-rw-r--r--chromium/net/quic/test_tools/quic_framer_peer.h37
-rw-r--r--chromium/net/quic/test_tools/quic_packet_creator_peer.cc30
-rw-r--r--chromium/net/quic/test_tools/quic_packet_creator_peer.h32
-rw-r--r--chromium/net/quic/test_tools/quic_received_packet_manager_peer.cc30
-rw-r--r--chromium/net/quic/test_tools/quic_received_packet_manager_peer.h35
-rw-r--r--chromium/net/quic/test_tools/quic_session_peer.cc37
-rw-r--r--chromium/net/quic/test_tools/quic_session_peer.h34
-rw-r--r--chromium/net/quic/test_tools/quic_test_utils.cc453
-rw-r--r--chromium/net/quic/test_tools/quic_test_utils.h376
-rw-r--r--chromium/net/quic/test_tools/reliable_quic_stream_peer.cc32
-rw-r--r--chromium/net/quic/test_tools/reliable_quic_stream_peer.h32
-rw-r--r--chromium/net/quic/test_tools/simple_quic_framer.cc198
-rw-r--r--chromium/net/quic/test_tools/simple_quic_framer.h58
-rw-r--r--chromium/net/quic/test_tools/test_task_runner.cc68
-rw-r--r--chromium/net/quic/test_tools/test_task_runner.h54
-rw-r--r--chromium/net/server/http_connection.cc51
-rw-r--r--chromium/net/server/http_connection.h53
-rw-r--r--chromium/net/server/http_server.cc330
-rw-r--r--chromium/net/server/http_server.h103
-rw-r--r--chromium/net/server/http_server_request_info.cc25
-rw-r--r--chromium/net/server/http_server_request_info.h43
-rw-r--r--chromium/net/server/http_server_response_info.cc67
-rw-r--r--chromium/net/server/http_server_response_info.h46
-rw-r--r--chromium/net/server/http_server_response_info_unittest.cc51
-rw-r--r--chromium/net/server/http_server_unittest.cc319
-rw-r--r--chromium/net/server/web_socket.cc406
-rw-r--r--chromium/net/server/web_socket.h53
-rw-r--r--chromium/net/socket/buffered_write_stream_socket.cc161
-rw-r--r--chromium/net/socket/buffered_write_stream_socket.h82
-rw-r--r--chromium/net/socket/buffered_write_stream_socket_unittest.cc124
-rw-r--r--chromium/net/socket/client_socket_factory.cc142
-rw-r--r--chromium/net/socket/client_socket_factory.h65
-rw-r--r--chromium/net/socket/client_socket_handle.cc180
-rw-r--r--chromium/net/socket/client_socket_handle.h241
-rw-r--r--chromium/net/socket/client_socket_pool.cc50
-rw-r--r--chromium/net/socket/client_socket_pool.h221
-rw-r--r--chromium/net/socket/client_socket_pool_base.cc1266
-rw-r--r--chromium/net/socket/client_socket_pool_base.h819
-rw-r--r--chromium/net/socket/client_socket_pool_base_unittest.cc4168
-rw-r--r--chromium/net/socket/client_socket_pool_histograms.cc83
-rw-r--r--chromium/net/socket/client_socket_pool_histograms.h46
-rw-r--r--chromium/net/socket/client_socket_pool_manager.cc467
-rw-r--r--chromium/net/socket/client_socket_pool_manager.h169
-rw-r--r--chromium/net/socket/client_socket_pool_manager_impl.cc392
-rw-r--r--chromium/net/socket/client_socket_pool_manager_impl.h150
-rw-r--r--chromium/net/socket/deterministic_socket_data_unittest.cc621
-rw-r--r--chromium/net/socket/mock_client_socket_pool_manager.cc94
-rw-r--r--chromium/net/socket/mock_client_socket_pool_manager.h63
-rw-r--r--chromium/net/socket/next_proto.h39
-rw-r--r--chromium/net/socket/nss_ssl_util.cc276
-rw-r--r--chromium/net/socket/nss_ssl_util.h35
-rw-r--r--chromium/net/socket/server_socket.h40
-rw-r--r--chromium/net/socket/socket.h62
-rw-r--r--chromium/net/socket/socket_net_log_params.cc72
-rw-r--r--chromium/net/socket/socket_net_log_params.h38
-rw-r--r--chromium/net/socket/socket_test_util.cc1888
-rw-r--r--chromium/net/socket/socket_test_util.h1198
-rw-r--r--chromium/net/socket/socks5_client_socket.cc487
-rw-r--r--chromium/net/socket/socks5_client_socket.h155
-rw-r--r--chromium/net/socket/socks5_client_socket_unittest.cc375
-rw-r--r--chromium/net/socket/socks_client_socket.cc432
-rw-r--r--chromium/net/socket/socks_client_socket.h134
-rw-r--r--chromium/net/socket/socks_client_socket_pool.cc310
-rw-r--r--chromium/net/socket/socks_client_socket_pool.h211
-rw-r--r--chromium/net/socket/socks_client_socket_pool_unittest.cc297
-rw-r--r--chromium/net/socket/socks_client_socket_unittest.cc415
-rw-r--r--chromium/net/socket/ssl_client_socket.cc148
-rw-r--r--chromium/net/socket/ssl_client_socket.h141
-rw-r--r--chromium/net/socket/ssl_client_socket_nss.cc3504
-rw-r--r--chromium/net/socket/ssl_client_socket_nss.h196
-rw-r--r--chromium/net/socket/ssl_client_socket_openssl.cc1435
-rw-r--r--chromium/net/socket/ssl_client_socket_openssl.h203
-rw-r--r--chromium/net/socket/ssl_client_socket_openssl_unittest.cc279
-rw-r--r--chromium/net/socket/ssl_client_socket_pool.cc664
-rw-r--r--chromium/net/socket/ssl_client_socket_pool.h297
-rw-r--r--chromium/net/socket/ssl_client_socket_pool_unittest.cc857
-rw-r--r--chromium/net/socket/ssl_client_socket_unittest.cc1798
-rw-r--r--chromium/net/socket/ssl_error_params.cc31
-rw-r--r--chromium/net/socket/ssl_error_params.h18
-rw-r--r--chromium/net/socket/ssl_server_socket.h64
-rw-r--r--chromium/net/socket/ssl_server_socket_nss.cc828
-rw-r--r--chromium/net/socket/ssl_server_socket_nss.h150
-rw-r--r--chromium/net/socket/ssl_server_socket_openssl.cc28
-rw-r--r--chromium/net/socket/ssl_server_socket_unittest.cc588
-rw-r--r--chromium/net/socket/ssl_socket.h37
-rw-r--r--chromium/net/socket/stream_listen_socket.cc308
-rw-r--r--chromium/net/socket/stream_listen_socket.h155
-rw-r--r--chromium/net/socket/stream_socket.cc101
-rw-r--r--chromium/net/socket/stream_socket.h138
-rw-r--r--chromium/net/socket/tcp_client_socket.cc59
-rw-r--r--chromium/net/socket/tcp_client_socket.h35
-rw-r--r--chromium/net/socket/tcp_client_socket_libevent.cc830
-rw-r--r--chromium/net/socket/tcp_client_socket_libevent.h256
-rw-r--r--chromium/net/socket/tcp_client_socket_unittest.cc113
-rw-r--r--chromium/net/socket/tcp_client_socket_win.cc1045
-rw-r--r--chromium/net/socket/tcp_client_socket_win.h162
-rw-r--r--chromium/net/socket/tcp_listen_socket.cc128
-rw-r--r--chromium/net/socket/tcp_listen_socket.h64
-rw-r--r--chromium/net/socket/tcp_listen_socket_unittest.cc291
-rw-r--r--chromium/net/socket/tcp_listen_socket_unittest.h122
-rw-r--r--chromium/net/socket/tcp_server_socket.h26
-rw-r--r--chromium/net/socket/tcp_server_socket_libevent.cc223
-rw-r--r--chromium/net/socket/tcp_server_socket_libevent.h55
-rw-r--r--chromium/net/socket/tcp_server_socket_unittest.cc251
-rw-r--r--chromium/net/socket/tcp_server_socket_win.cc217
-rw-r--r--chromium/net/socket/tcp_server_socket_win.h58
-rw-r--r--chromium/net/socket/transport_client_socket_pool.cc477
-rw-r--r--chromium/net/socket/transport_client_socket_pool.h221
-rw-r--r--chromium/net/socket/transport_client_socket_pool_unittest.cc1355
-rw-r--r--chromium/net/socket/transport_client_socket_unittest.cc449
-rw-r--r--chromium/net/socket/unix_domain_socket_posix.cc193
-rw-r--r--chromium/net/socket/unix_domain_socket_posix.h126
-rw-r--r--chromium/net/socket/unix_domain_socket_posix_unittest.cc338
-rw-r--r--chromium/net/socket_stream/OWNERS8
-rw-r--r--chromium/net/socket_stream/socket_stream.cc1336
-rw-r--r--chromium/net/socket_stream/socket_stream.h395
-rw-r--r--chromium/net/socket_stream/socket_stream_job.cc87
-rw-r--r--chromium/net/socket_stream/socket_stream_job.h88
-rw-r--r--chromium/net/socket_stream/socket_stream_job_manager.cc66
-rw-r--r--chromium/net/socket_stream/socket_stream_job_manager.h45
-rw-r--r--chromium/net/socket_stream/socket_stream_metrics.cc86
-rw-r--r--chromium/net/socket_stream/socket_stream_metrics.h70
-rw-r--r--chromium/net/socket_stream/socket_stream_metrics_unittest.cc221
-rw-r--r--chromium/net/socket_stream/socket_stream_unittest.cc965
-rw-r--r--chromium/net/spdy/buffered_spdy_framer.cc331
-rw-r--r--chromium/net/spdy/buffered_spdy_framer.h262
-rw-r--r--chromium/net/spdy/buffered_spdy_framer_unittest.cc283
-rw-r--r--chromium/net/spdy/spdy_bitmasks.h31
-rw-r--r--chromium/net/spdy/spdy_buffer.cc105
-rw-r--r--chromium/net/spdy/spdy_buffer.h104
-rw-r--r--chromium/net/spdy/spdy_buffer_producer.cc27
-rw-r--r--chromium/net/spdy/spdy_buffer_producer.h50
-rw-r--r--chromium/net/spdy/spdy_buffer_unittest.cc137
-rw-r--r--chromium/net/spdy/spdy_credential_builder.cc86
-rw-r--r--chromium/net/spdy/spdy_credential_builder.h36
-rw-r--r--chromium/net/spdy/spdy_credential_builder_unittest.cc149
-rw-r--r--chromium/net/spdy/spdy_credential_state.cc69
-rw-r--r--chromium/net/spdy/spdy_credential_state.h56
-rw-r--r--chromium/net/spdy/spdy_credential_state_unittest.cc108
-rw-r--r--chromium/net/spdy/spdy_frame_builder.cc191
-rw-r--r--chromium/net/spdy/spdy_frame_builder.h129
-rw-r--r--chromium/net/spdy/spdy_frame_builder_test.cc62
-rw-r--r--chromium/net/spdy/spdy_frame_reader.cc184
-rw-r--r--chromium/net/spdy/spdy_frame_reader.h123
-rw-r--r--chromium/net/spdy/spdy_frame_reader_test.cc249
-rw-r--r--chromium/net/spdy/spdy_framer.cc2361
-rw-r--r--chromium/net/spdy/spdy_framer.h716
-rw-r--r--chromium/net/spdy/spdy_framer_test.cc4592
-rw-r--r--chromium/net/spdy/spdy_header_block.cc52
-rw-r--r--chromium/net/spdy/spdy_header_block.h36
-rw-r--r--chromium/net/spdy/spdy_header_block_unittest.cc31
-rw-r--r--chromium/net/spdy/spdy_http_stream.cc532
-rw-r--r--chromium/net/spdy/spdy_http_stream.h167
-rw-r--r--chromium/net/spdy/spdy_http_stream_unittest.cc898
-rw-r--r--chromium/net/spdy/spdy_http_utils.cc203
-rw-r--r--chromium/net/spdy/spdy_http_utils.h59
-rw-r--r--chromium/net/spdy/spdy_http_utils_unittest.cc67
-rw-r--r--chromium/net/spdy/spdy_network_transaction_unittest.cc6405
-rw-r--r--chromium/net/spdy/spdy_priority_forest.h527
-rw-r--r--chromium/net/spdy/spdy_priority_forest_test.cc282
-rw-r--r--chromium/net/spdy/spdy_protocol.cc82
-rw-r--r--chromium/net/spdy/spdy_protocol.h804
-rw-r--r--chromium/net/spdy/spdy_protocol_test.cc86
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket.cc520
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket.h175
-rw-r--r--chromium/net/spdy/spdy_proxy_client_socket_unittest.cc1435
-rw-r--r--chromium/net/spdy/spdy_read_queue.cc59
-rw-r--r--chromium/net/spdy/spdy_read_queue.h51
-rw-r--r--chromium/net/spdy/spdy_read_queue_unittest.cc106
-rw-r--r--chromium/net/spdy/spdy_session.cc2917
-rw-r--r--chromium/net/spdy/spdy_session.h1143
-rw-r--r--chromium/net/spdy/spdy_session_key.cc50
-rw-r--r--chromium/net/spdy/spdy_session_key.h58
-rw-r--r--chromium/net/spdy/spdy_session_pool.cc399
-rw-r--r--chromium/net/spdy/spdy_session_pool.h235
-rw-r--r--chromium/net/spdy/spdy_session_pool_unittest.cc494
-rw-r--r--chromium/net/spdy/spdy_session_test_util.cc38
-rw-r--r--chromium/net/spdy/spdy_session_test_util.h46
-rw-r--r--chromium/net/spdy/spdy_session_unittest.cc4116
-rw-r--r--chromium/net/spdy/spdy_stream.cc1035
-rw-r--r--chromium/net/spdy/spdy_stream.h557
-rw-r--r--chromium/net/spdy/spdy_stream_test_util.cc146
-rw-r--r--chromium/net/spdy/spdy_stream_test_util.h127
-rw-r--r--chromium/net/spdy/spdy_stream_unittest.cc999
-rw-r--r--chromium/net/spdy/spdy_test_util_common.cc1235
-rw-r--r--chromium/net/spdy/spdy_test_util_common.h538
-rw-r--r--chromium/net/spdy/spdy_test_utils.cc129
-rw-r--r--chromium/net/spdy/spdy_test_utils.h34
-rw-r--r--chromium/net/spdy/spdy_websocket_stream.cc130
-rw-r--r--chromium/net/spdy/spdy_websocket_stream.h103
-rw-r--r--chromium/net/spdy/spdy_websocket_stream_unittest.cc606
-rw-r--r--chromium/net/spdy/spdy_websocket_test_util.cc164
-rw-r--r--chromium/net/spdy/spdy_websocket_test_util.h77
-rw-r--r--chromium/net/spdy/spdy_write_queue.cc132
-rw-r--r--chromium/net/spdy/spdy_write_queue.h89
-rw-r--r--chromium/net/spdy/spdy_write_queue_unittest.cc252
-rw-r--r--chromium/net/ssl/client_cert_store.h31
-rw-r--r--chromium/net/ssl/client_cert_store_impl.h57
-rw-r--r--chromium/net/ssl/client_cert_store_impl_mac.cc268
-rw-r--r--chromium/net/ssl/client_cert_store_impl_nss.cc111
-rw-r--r--chromium/net/ssl/client_cert_store_impl_unittest.cc169
-rw-r--r--chromium/net/ssl/client_cert_store_impl_win.cc205
-rw-r--r--chromium/net/ssl/default_server_bound_cert_store.cc473
-rw-r--r--chromium/net/ssl/default_server_bound_cert_store.h192
-rw-r--r--chromium/net/ssl/default_server_bound_cert_store_unittest.cc559
-rw-r--r--chromium/net/ssl/openssl_client_key_store.cc140
-rw-r--r--chromium/net/ssl/openssl_client_key_store.h108
-rw-r--r--chromium/net/ssl/openssl_client_key_store_unittest.cc173
-rw-r--r--chromium/net/ssl/server_bound_cert_service.cc679
-rw-r--r--chromium/net/ssl/server_bound_cert_service.h214
-rw-r--r--chromium/net/ssl/server_bound_cert_service_unittest.cc774
-rw-r--r--chromium/net/ssl/server_bound_cert_store.cc34
-rw-r--r--chromium/net/ssl/server_bound_cert_store.h131
-rw-r--r--chromium/net/ssl/ssl_cert_request_info.cc25
-rw-r--r--chromium/net/ssl/ssl_cert_request_info.h68
-rw-r--r--chromium/net/ssl/ssl_cipher_suite_names.cc342
-rw-r--r--chromium/net/ssl/ssl_cipher_suite_names.h51
-rw-r--r--chromium/net/ssl/ssl_cipher_suite_names_unittest.cc61
-rw-r--r--chromium/net/ssl/ssl_client_auth_cache.cc48
-rw-r--r--chromium/net/ssl/ssl_client_auth_cache.h63
-rw-r--r--chromium/net/ssl/ssl_client_auth_cache_unittest.cc171
-rw-r--r--chromium/net/ssl/ssl_client_cert_type.h22
-rw-r--r--chromium/net/ssl/ssl_config_service.cc183
-rw-r--r--chromium/net/ssl/ssl_config_service.h230
-rw-r--r--chromium/net/ssl/ssl_config_service_defaults.cc20
-rw-r--r--chromium/net/ssl/ssl_config_service_defaults.h34
-rw-r--r--chromium/net/ssl/ssl_config_service_unittest.cc129
-rw-r--r--chromium/net/ssl/ssl_connection_status_flags.h63
-rw-r--r--chromium/net/ssl/ssl_info.cc54
-rw-r--r--chromium/net/ssl/ssl_info.h81
-rw-r--r--chromium/net/test/OWNERS5
-rw-r--r--chromium/net/test/android/OWNERS3
-rw-r--r--chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java557
-rw-r--r--chromium/net/test/cert_test_util.cc57
-rw-r--r--chromium/net/test/cert_test_util.h53
-rw-r--r--chromium/net/test/embedded_test_server/OWNERS2
-rw-r--r--chromium/net/test/embedded_test_server/embedded_test_server.cc276
-rw-r--r--chromium/net/test/embedded_test_server/embedded_test_server.h172
-rw-r--r--chromium/net/test/embedded_test_server/embedded_test_server_unittest.cc244
-rw-r--r--chromium/net/test/embedded_test_server/http_connection.cc35
-rw-r--r--chromium/net/test/embedded_test_server/http_connection.h58
-rw-r--r--chromium/net/test/embedded_test_server/http_request.cc202
-rw-r--r--chromium/net/test/embedded_test_server/http_request.h115
-rw-r--r--chromium/net/test/embedded_test_server/http_request_unittest.cc82
-rw-r--r--chromium/net/test/embedded_test_server/http_response.cc58
-rw-r--r--chromium/net/test/embedded_test_server/http_response.h71
-rw-r--r--chromium/net/test/embedded_test_server/http_response_unittest.cc31
-rw-r--r--chromium/net/test/net_test_suite.cc67
-rw-r--r--chromium/net/test/net_test_suite.h55
-rw-r--r--chromium/net/test/openssl_helper.cc264
-rw-r--r--chromium/net/test/python_utils.cc127
-rw-r--r--chromium/net/test/python_utils.h28
-rw-r--r--chromium/net/test/python_utils_unittest.cc61
-rw-r--r--chromium/net/test/run_all_unittests.cc53
-rw-r--r--chromium/net/test/spawned_test_server/base_test_server.cc407
-rw-r--r--chromium/net/test/spawned_test_server/base_test_server.h263
-rw-r--r--chromium/net/test/spawned_test_server/local_test_server.cc252
-rw-r--r--chromium/net/test/spawned_test_server/local_test_server.h117
-rw-r--r--chromium/net/test/spawned_test_server/local_test_server_posix.cc179
-rw-r--r--chromium/net/test/spawned_test_server/local_test_server_win.cc175
-rw-r--r--chromium/net/test/spawned_test_server/remote_test_server.cc204
-rw-r--r--chromium/net/test/spawned_test_server/remote_test_server.h71
-rw-r--r--chromium/net/test/spawned_test_server/spawned_test_server.h27
-rw-r--r--chromium/net/test/spawned_test_server/spawner_communicator.cc379
-rw-r--r--chromium/net/test/spawned_test_server/spawner_communicator.h151
-rw-r--r--chromium/net/test/test_certificate_data.h786
-rw-r--r--chromium/net/third_party/mozilla_security_manager/LICENSE35
-rw-r--r--chromium/net/third_party/mozilla_security_manager/README.chromium17
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp261
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.h69
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.cpp287
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.h71
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.cpp486
-rw-r--r--chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.h79
-rw-r--r--chromium/net/third_party/nss/LICENSE35
-rw-r--r--chromium/net/third_party/nss/README.chromium114
-rw-r--r--chromium/net/third_party/nss/patches/aesgcm.patch1179
-rw-r--r--chromium/net/third_party/nss/patches/aesgcmchromium.patch117
-rw-r--r--chromium/net/third_party/nss/patches/alpn.patch245
-rwxr-xr-xchromium/net/third_party/nss/patches/applypatches.sh53
-rw-r--r--chromium/net/third_party/nss/patches/cachecerts.patch118
-rw-r--r--chromium/net/third_party/nss/patches/cbc.patch81
-rw-r--r--chromium/net/third_party/nss/patches/channelid.patch593
-rw-r--r--chromium/net/third_party/nss/patches/clientauth.patch437
-rw-r--r--chromium/net/third_party/nss/patches/didhandshakeresume.patch37
-rw-r--r--chromium/net/third_party/nss/patches/ecpointform.patch19
-rw-r--r--chromium/net/third_party/nss/patches/getrequestedclientcerttypes.patch87
-rw-r--r--chromium/net/third_party/nss/patches/negotiatedextension.patch27
-rw-r--r--chromium/net/third_party/nss/patches/peercertchain.patch67
-rw-r--r--chromium/net/third_party/nss/patches/renegoscsv.patch15
-rw-r--r--chromium/net/third_party/nss/patches/restartclientauth.patch209
-rw-r--r--chromium/net/third_party/nss/patches/secitemarray.patch42
-rw-r--r--chromium/net/third_party/nss/patches/secretexporterlocks.patch44
-rw-r--r--chromium/net/third_party/nss/patches/sslsock_903565.patch20
-rw-r--r--chromium/net/third_party/nss/patches/suitebonly.patch21
-rw-r--r--chromium/net/third_party/nss/patches/tls12chromium.patch100
-rw-r--r--chromium/net/third_party/nss/patches/tlsunique.patch149
-rw-r--r--chromium/net/third_party/nss/patches/versionskew.patch45
-rw-r--r--chromium/net/third_party/nss/ssl.gyp192
-rw-r--r--chromium/net/third_party/nss/ssl/Makefile63
-rw-r--r--chromium/net/third_party/nss/ssl/SSLerrs.h423
-rw-r--r--chromium/net/third_party/nss/ssl/authcert.c89
-rw-r--r--chromium/net/third_party/nss/ssl/bodge/nssrenam.h47
-rw-r--r--chromium/net/third_party/nss/ssl/bodge/secitem_array.c151
-rw-r--r--chromium/net/third_party/nss/ssl/cmpcert.c90
-rw-r--r--chromium/net/third_party/nss/ssl/derive.c896
-rw-r--r--chromium/net/third_party/nss/ssl/dtlscon.c1139
-rw-r--r--chromium/net/third_party/nss/ssl/exports_win.def60
-rw-r--r--chromium/net/third_party/nss/ssl/manifest.mn53
-rw-r--r--chromium/net/third_party/nss/ssl/notes.txt134
-rw-r--r--chromium/net/third_party/nss/ssl/os2_err.c280
-rw-r--r--chromium/net/third_party/nss/ssl/os2_err.h53
-rw-r--r--chromium/net/third_party/nss/ssl/preenc.h113
-rw-r--r--chromium/net/third_party/nss/ssl/prelib.c34
-rw-r--r--chromium/net/third_party/nss/ssl/ssl.h1110
-rw-r--r--chromium/net/third_party/nss/ssl/ssl.rc68
-rw-r--r--chromium/net/third_party/nss/ssl/ssl3con.c12310
-rw-r--r--chromium/net/third_party/nss/ssl/ssl3ecc.c1280
-rw-r--r--chromium/net/third_party/nss/ssl/ssl3ext.c2299
-rw-r--r--chromium/net/third_party/nss/ssl/ssl3gthr.c413
-rw-r--r--chromium/net/third_party/nss/ssl/ssl3prot.h363
-rw-r--r--chromium/net/third_party/nss/ssl/sslauth.c332
-rw-r--r--chromium/net/third_party/nss/ssl/sslcon.c3696
-rw-r--r--chromium/net/third_party/nss/ssl/ssldef.c210
-rw-r--r--chromium/net/third_party/nss/ssl/sslenum.c150
-rw-r--r--chromium/net/third_party/nss/ssl/sslerr.c41
-rw-r--r--chromium/net/third_party/nss/ssl/sslerr.h204
-rw-r--r--chromium/net/third_party/nss/ssl/sslerrstrs.c34
-rw-r--r--chromium/net/third_party/nss/ssl/sslgathr.c453
-rw-r--r--chromium/net/third_party/nss/ssl/sslimpl.h1941
-rw-r--r--chromium/net/third_party/nss/ssl/sslinfo.c413
-rw-r--r--chromium/net/third_party/nss/ssl/sslinit.c28
-rw-r--r--chromium/net/third_party/nss/ssl/sslmutex.c640
-rw-r--r--chromium/net/third_party/nss/ssl/sslmutex.h125
-rw-r--r--chromium/net/third_party/nss/ssl/sslnonce.c508
-rw-r--r--chromium/net/third_party/nss/ssl/sslplatf.c732
-rw-r--r--chromium/net/third_party/nss/ssl/sslproto.h231
-rw-r--r--chromium/net/third_party/nss/ssl/sslreveal.c112
-rw-r--r--chromium/net/third_party/nss/ssl/sslsecur.c1598
-rw-r--r--chromium/net/third_party/nss/ssl/sslsnce.c2213
-rw-r--r--chromium/net/third_party/nss/ssl/sslsock.c3128
-rw-r--r--chromium/net/third_party/nss/ssl/sslt.h208
-rw-r--r--chromium/net/third_party/nss/ssl/ssltrace.c243
-rw-r--r--chromium/net/third_party/nss/ssl/sslver.c24
-rw-r--r--chromium/net/third_party/nss/ssl/unix_err.c517
-rw-r--r--chromium/net/third_party/nss/ssl/unix_err.h57
-rw-r--r--chromium/net/third_party/nss/ssl/win32err.c343
-rw-r--r--chromium/net/third_party/nss/ssl/win32err.h51
-rw-r--r--chromium/net/tools/DEPS3
-rw-r--r--chromium/net/tools/crash_cache/crash_cache.cc382
-rw-r--r--chromium/net/tools/crl_set_dump/crl_set_dump.cc83
-rw-r--r--chromium/net/tools/dns_fuzz_stub/dns_fuzz_stub.cc215
-rw-r--r--chromium/net/tools/dump_cache/cache_dumper.cc223
-rw-r--r--chromium/net/tools/dump_cache/cache_dumper.h89
-rw-r--r--chromium/net/tools/dump_cache/dump_cache.cc183
-rw-r--r--chromium/net/tools/dump_cache/dump_files.cc374
-rw-r--r--chromium/net/tools/dump_cache/dump_files.h23
-rw-r--r--chromium/net/tools/dump_cache/simple_cache_dumper.cc273
-rw-r--r--chromium/net/tools/dump_cache/simple_cache_dumper.h94
-rw-r--r--chromium/net/tools/dump_cache/upgrade_win.cc927
-rw-r--r--chromium/net/tools/dump_cache/upgrade_win.h20
-rw-r--r--chromium/net/tools/dump_cache/url_to_filename_encoder.cc292
-rw-r--r--chromium/net/tools/dump_cache/url_to_filename_encoder.h211
-rw-r--r--chromium/net/tools/dump_cache/url_to_filename_encoder_unittest.cc345
-rw-r--r--chromium/net/tools/dump_cache/url_utilities.cc126
-rw-r--r--chromium/net/tools/dump_cache/url_utilities.h35
-rw-r--r--chromium/net/tools/dump_cache/url_utilities_unittest.cc114
-rw-r--r--chromium/net/tools/fetch/fetch_client.cc231
-rw-r--r--chromium/net/tools/fetch/fetch_server.cc57
-rw-r--r--chromium/net/tools/fetch/http_listen_socket.cc247
-rw-r--r--chromium/net/tools/fetch/http_listen_socket.h66
-rw-r--r--chromium/net/tools/fetch/http_server.cc12
-rw-r--r--chromium/net/tools/fetch/http_server.h26
-rw-r--r--chromium/net/tools/fetch/http_server_request_info.cc11
-rw-r--r--chromium/net/tools/fetch/http_server_request_info.h26
-rw-r--r--chromium/net/tools/fetch/http_server_response_info.cc11
-rw-r--r--chromium/net/tools/fetch/http_server_response_info.h39
-rw-r--r--chromium/net/tools/fetch/http_session.cc33
-rw-r--r--chromium/net/tools/fetch/http_session.h27
-rw-r--r--chromium/net/tools/flip_server/acceptor_thread.cc212
-rw-r--r--chromium/net/tools/flip_server/acceptor_thread.h97
-rw-r--r--chromium/net/tools/flip_server/balsa_enums.h111
-rw-r--r--chromium/net/tools/flip_server/balsa_frame.cc1597
-rw-r--r--chromium/net/tools/flip_server/balsa_frame.h265
-rw-r--r--chromium/net/tools/flip_server/balsa_frame_test.cc599
-rw-r--r--chromium/net/tools/flip_server/balsa_headers.cc965
-rw-r--r--chromium/net/tools/flip_server/balsa_headers.h1138
-rw-r--r--chromium/net/tools/flip_server/balsa_headers_test.cc392
-rw-r--r--chromium/net/tools/flip_server/balsa_headers_token_utils.cc142
-rw-r--r--chromium/net/tools/flip_server/balsa_headers_token_utils.h61
-rw-r--r--chromium/net/tools/flip_server/balsa_visitor_interface.h181
-rw-r--r--chromium/net/tools/flip_server/buffer_interface.h121
-rw-r--r--chromium/net/tools/flip_server/constants.h30
-rw-r--r--chromium/net/tools/flip_server/create_listener.cc300
-rw-r--r--chromium/net/tools/flip_server/create_listener.h57
-rw-r--r--chromium/net/tools/flip_server/epoll_server.cc822
-rw-r--r--chromium/net/tools/flip_server/epoll_server.h1054
-rw-r--r--chromium/net/tools/flip_server/flip_config.cc141
-rw-r--r--chromium/net/tools/flip_server/flip_config.h99
-rw-r--r--chromium/net/tools/flip_server/flip_in_mem_edsm_server.cc424
-rw-r--r--chromium/net/tools/flip_server/http_interface.cc362
-rw-r--r--chromium/net/tools/flip_server/http_interface.h139
-rw-r--r--chromium/net/tools/flip_server/http_message_constants.cc146
-rw-r--r--chromium/net/tools/flip_server/http_message_constants.h17
-rw-r--r--chromium/net/tools/flip_server/loadtime_measurement.h123
-rw-r--r--chromium/net/tools/flip_server/mem_cache.cc249
-rw-r--r--chromium/net/tools/flip_server/mem_cache.h153
-rw-r--r--chromium/net/tools/flip_server/mem_cache_test.cc98
-rw-r--r--chromium/net/tools/flip_server/output_ordering.cc183
-rw-r--r--chromium/net/tools/flip_server/output_ordering.h88
-rw-r--r--chromium/net/tools/flip_server/ring_buffer.cc279
-rw-r--r--chromium/net/tools/flip_server/ring_buffer.h113
-rw-r--r--chromium/net/tools/flip_server/simple_buffer.cc208
-rw-r--r--chromium/net/tools/flip_server/simple_buffer.h92
-rw-r--r--chromium/net/tools/flip_server/sm_connection.cc667
-rw-r--r--chromium/net/tools/flip_server/sm_connection.h162
-rw-r--r--chromium/net/tools/flip_server/sm_interface.h81
-rw-r--r--chromium/net/tools/flip_server/spdy_interface.cc569
-rw-r--r--chromium/net/tools/flip_server/spdy_interface.h198
-rw-r--r--chromium/net/tools/flip_server/spdy_ssl.cc110
-rw-r--r--chromium/net/tools/flip_server/spdy_ssl.h31
-rw-r--r--chromium/net/tools/flip_server/spdy_util.cc34
-rw-r--r--chromium/net/tools/flip_server/spdy_util.h21
-rw-r--r--chromium/net/tools/flip_server/split.cc72
-rw-r--r--chromium/net/tools/flip_server/split.h23
-rw-r--r--chromium/net/tools/flip_server/streamer_interface.cc207
-rw-r--r--chromium/net/tools/flip_server/streamer_interface.h132
-rw-r--r--chromium/net/tools/flip_server/string_piece_utils.h83
-rw-r--r--chromium/net/tools/gdig/file_net_log.cc48
-rw-r--r--chromium/net/tools/gdig/file_net_log.h37
-rw-r--r--chromium/net/tools/gdig/gdig.cc511
-rw-r--r--chromium/net/tools/get_server_time/get_server_time.cc354
-rw-r--r--chromium/net/tools/net_watcher/net_watcher.cc208
-rwxr-xr-xchromium/net/tools/quic/benchmark/run_client.py186
-rw-r--r--chromium/net/tools/quic/benchmark/test_urls.json41
-rw-r--r--chromium/net/tools/quic/end_to_end_test.cc651
-rw-r--r--chromium/net/tools/quic/quic_client.cc289
-rw-r--r--chromium/net/tools/quic/quic_client.h203
-rw-r--r--chromium/net/tools/quic/quic_client_bin.cc56
-rw-r--r--chromium/net/tools/quic/quic_client_session.cc65
-rw-r--r--chromium/net/tools/quic/quic_client_session.h56
-rw-r--r--chromium/net/tools/quic/quic_client_session_test.cc96
-rw-r--r--chromium/net/tools/quic/quic_dispatcher.cc203
-rw-r--r--chromium/net/tools/quic/quic_dispatcher.h147
-rw-r--r--chromium/net/tools/quic/quic_dispatcher_test.cc390
-rw-r--r--chromium/net/tools/quic/quic_epoll_clock.cc29
-rw-r--r--chromium/net/tools/quic/quic_epoll_clock.h39
-rw-r--r--chromium/net/tools/quic/quic_epoll_clock_test.cc57
-rw-r--r--chromium/net/tools/quic/quic_epoll_connection_helper.cc140
-rw-r--r--chromium/net/tools/quic/quic_epoll_connection_helper.h72
-rw-r--r--chromium/net/tools/quic/quic_epoll_connection_helper_test.cc207
-rw-r--r--chromium/net/tools/quic/quic_in_memory_cache.cc225
-rw-r--r--chromium/net/tools/quic/quic_in_memory_cache.h88
-rw-r--r--chromium/net/tools/quic/quic_in_memory_cache_test.cc137
-rw-r--r--chromium/net/tools/quic/quic_packet_writer.h33
-rw-r--r--chromium/net/tools/quic/quic_reliable_client_stream.cc27
-rw-r--r--chromium/net/tools/quic/quic_reliable_client_stream.h66
-rw-r--r--chromium/net/tools/quic/quic_reliable_client_stream_test.cc96
-rw-r--r--chromium/net/tools/quic/quic_reliable_server_stream.cc56
-rw-r--r--chromium/net/tools/quic/quic_reliable_server_stream.h65
-rw-r--r--chromium/net/tools/quic/quic_reliable_server_stream_test.cc219
-rw-r--r--chromium/net/tools/quic/quic_server.cc223
-rw-r--r--chromium/net/tools/quic/quic_server.h115
-rw-r--r--chromium/net/tools/quic/quic_server_bin.cc51
-rw-r--r--chromium/net/tools/quic/quic_server_session.cc74
-rw-r--r--chromium/net/tools/quic/quic_server_session.h78
-rw-r--r--chromium/net/tools/quic/quic_server_test.cc74
-rw-r--r--chromium/net/tools/quic/quic_socket_utils.cc191
-rw-r--r--chromium/net/tools/quic/quic_socket_utils.h59
-rw-r--r--chromium/net/tools/quic/quic_spdy_client_stream.cc104
-rw-r--r--chromium/net/tools/quic/quic_spdy_client_stream.h47
-rw-r--r--chromium/net/tools/quic/quic_spdy_server_stream.cc104
-rw-r--r--chromium/net/tools/quic/quic_spdy_server_stream.h45
-rw-r--r--chromium/net/tools/quic/quic_spdy_server_stream_test.cc70
-rw-r--r--chromium/net/tools/quic/quic_time_wait_list_manager.cc322
-rw-r--r--chromium/net/tools/quic/quic_time_wait_list_manager.h194
-rw-r--r--chromium/net/tools/quic/spdy_utils.cc266
-rw-r--r--chromium/net/tools/quic/spdy_utils.h45
-rw-r--r--chromium/net/tools/quic/test_tools/http_message_test_utils.cc175
-rw-r--r--chromium/net/tools/quic/test_tools/http_message_test_utils.h133
-rw-r--r--chromium/net/tools/quic/test_tools/mock_epoll_server.cc68
-rw-r--r--chromium/net/tools/quic/test_tools/mock_epoll_server.h106
-rw-r--r--chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc21
-rw-r--r--chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h38
-rw-r--r--chromium/net/tools/quic/test_tools/quic_client_peer.cc27
-rw-r--r--chromium/net/tools/quic/test_tools/quic_client_peer.h25
-rw-r--r--chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc21
-rw-r--r--chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h31
-rw-r--r--chromium/net/tools/quic/test_tools/quic_test_client.cc342
-rw-r--r--chromium/net/tools/quic/test_tools/quic_test_client.h143
-rw-r--r--chromium/net/tools/quic/test_tools/quic_test_utils.cc81
-rw-r--r--chromium/net/tools/quic/test_tools/quic_test_utils.h112
-rw-r--r--chromium/net/tools/quic/test_tools/run_all_unittests.cc11
-rw-r--r--chromium/net/tools/spdyshark/AUTHORS2
-rw-r--r--chromium/net/tools/spdyshark/COPYING340
-rw-r--r--chromium/net/tools/spdyshark/ChangeLog0
-rw-r--r--chromium/net/tools/spdyshark/INSTALL0
-rw-r--r--chromium/net/tools/spdyshark/Makefile.am126
-rw-r--r--chromium/net/tools/spdyshark/Makefile.common40
-rw-r--r--chromium/net/tools/spdyshark/Makefile.nmake104
-rw-r--r--chromium/net/tools/spdyshark/NEWS0
-rw-r--r--chromium/net/tools/spdyshark/README49
-rw-r--r--chromium/net/tools/spdyshark/README.chromium4
-rw-r--r--chromium/net/tools/spdyshark/moduleinfo.h16
-rw-r--r--chromium/net/tools/spdyshark/moduleinfo.nmake28
-rw-r--r--chromium/net/tools/spdyshark/packet-spdy.c1532
-rw-r--r--chromium/net/tools/spdyshark/packet-spdy.h46
-rw-r--r--chromium/net/tools/spdyshark/plugin.rc.in34
-rw-r--r--chromium/net/tools/testserver/OWNERS3
-rw-r--r--chromium/net/tools/testserver/asn1.py165
-rwxr-xr-xchromium/net/tools/testserver/backoff_server.py107
-rw-r--r--chromium/net/tools/testserver/dist/_socket.pydbin0 -> 49152 bytes
-rw-r--r--chromium/net/tools/testserver/dist/_ssl.pydbin0 -> 499712 bytes
-rw-r--r--chromium/net/tools/testserver/echo_message.py385
-rw-r--r--chromium/net/tools/testserver/minica.py349
-rw-r--r--chromium/net/tools/testserver/run_testserver.cc125
-rwxr-xr-xchromium/net/tools/testserver/testserver.py2070
-rw-r--r--chromium/net/tools/testserver/testserver_base.py250
-rw-r--r--chromium/net/tools/tld_cleanup/OWNERS1
-rw-r--r--chromium/net/tools/tld_cleanup/README31
-rw-r--r--chromium/net/tools/tld_cleanup/tld_cleanup.cc94
-rw-r--r--chromium/net/tools/tld_cleanup/tld_cleanup.gyp23
-rw-r--r--chromium/net/tools/tld_cleanup/tld_cleanup_util.cc254
-rw-r--r--chromium/net/tools/tld_cleanup/tld_cleanup_util.h48
-rw-r--r--chromium/net/tools/tld_cleanup/tld_cleanup_util_unittest.cc168
-rw-r--r--chromium/net/udp/datagram_client_socket.h27
-rw-r--r--chromium/net/udp/datagram_server_socket.h100
-rw-r--r--chromium/net/udp/datagram_socket.h43
-rw-r--r--chromium/net/udp/udp_client_socket.cc61
-rw-r--r--chromium/net/udp/udp_client_socket.h46
-rw-r--r--chromium/net/udp/udp_net_log_parameters.cc52
-rw-r--r--chromium/net/udp/udp_net_log_parameters.h31
-rw-r--r--chromium/net/udp/udp_server_socket.cc88
-rw-r--r--chromium/net/udp/udp_server_socket.h55
-rw-r--r--chromium/net/udp/udp_socket.h44
-rw-r--r--chromium/net/udp/udp_socket_libevent.cc654
-rw-r--r--chromium/net/udp/udp_socket_libevent.h283
-rw-r--r--chromium/net/udp/udp_socket_unittest.cc585
-rw-r--r--chromium/net/udp/udp_socket_win.cc755
-rw-r--r--chromium/net/udp/udp_socket_win.h246
-rw-r--r--chromium/net/url_request/data_protocol_handler.cc19
-rw-r--r--chromium/net/url_request/data_protocol_handler.h30
-rw-r--r--chromium/net/url_request/file_protocol_handler.cc51
-rw-r--r--chromium/net/url_request/file_protocol_handler.h35
-rw-r--r--chromium/net/url_request/fraudulent_certificate_reporter.h33
-rw-r--r--chromium/net/url_request/ftp_protocol_handler.cc42
-rw-r--r--chromium/net/url_request/ftp_protocol_handler.h40
-rw-r--r--chromium/net/url_request/http_user_agent_settings.h38
-rw-r--r--chromium/net/url_request/protocol_intercept_job_factory.cc47
-rw-r--r--chromium/net/url_request/protocol_intercept_job_factory.h51
-rw-r--r--chromium/net/url_request/static_http_user_agent_settings.cc28
-rw-r--r--chromium/net/url_request/static_http_user_agent_settings.h39
-rw-r--r--chromium/net/url_request/test_url_fetcher_factory.cc383
-rw-r--r--chromium/net/url_request/test_url_fetcher_factory.h422
-rw-r--r--chromium/net/url_request/url_fetcher.cc48
-rw-r--r--chromium/net/url_request/url_fetcher.h300
-rw-r--r--chromium/net/url_request/url_fetcher_core.cc940
-rw-r--r--chromium/net/url_request/url_fetcher_core.h349
-rw-r--r--chromium/net/url_request/url_fetcher_delegate.cc24
-rw-r--r--chromium/net/url_request/url_fetcher_delegate.h56
-rw-r--r--chromium/net/url_request/url_fetcher_factory.h29
-rw-r--r--chromium/net/url_request/url_fetcher_impl.cc221
-rw-r--r--chromium/net/url_request/url_fetcher_impl.h128
-rw-r--r--chromium/net/url_request/url_fetcher_impl_unittest.cc1541
-rw-r--r--chromium/net/url_request/url_fetcher_response_writer.cc171
-rw-r--r--chromium/net/url_request/url_fetcher_response_writer.h142
-rw-r--r--chromium/net/url_request/url_request.cc1117
-rw-r--r--chromium/net/url_request/url_request.h868
-rw-r--r--chromium/net/url_request/url_request_about_job.cc43
-rw-r--r--chromium/net/url_request/url_request_about_job.h34
-rw-r--r--chromium/net/url_request/url_request_context.cc113
-rw-r--r--chromium/net/url_request/url_request_context.h239
-rw-r--r--chromium/net/url_request/url_request_context_builder.cc320
-rw-r--r--chromium/net/url_request/url_request_context_builder.h152
-rw-r--r--chromium/net/url_request/url_request_context_builder_unittest.cc83
-rw-r--r--chromium/net/url_request/url_request_context_getter.cc38
-rw-r--r--chromium/net/url_request/url_request_context_getter.h58
-rw-r--r--chromium/net/url_request/url_request_context_storage.cc127
-rw-r--r--chromium/net/url_request/url_request_context_storage.h103
-rw-r--r--chromium/net/url_request/url_request_data_job.cc34
-rw-r--r--chromium/net/url_request/url_request_data_job.h35
-rw-r--r--chromium/net/url_request/url_request_error_job.cc33
-rw-r--r--chromium/net/url_request/url_request_error_job.h37
-rw-r--r--chromium/net/url_request/url_request_file_dir_job.cc187
-rw-r--r--chromium/net/url_request/url_request_file_dir_job.h80
-rw-r--r--chromium/net/url_request/url_request_file_job.cc300
-rw-r--r--chromium/net/url_request/url_request_file_job.h105
-rw-r--r--chromium/net/url_request/url_request_filter.cc195
-rw-r--r--chromium/net/url_request/url_request_filter.h105
-rw-r--r--chromium/net/url_request/url_request_filter_unittest.cc153
-rw-r--r--chromium/net/url_request/url_request_ftp_job.cc407
-rw-r--r--chromium/net/url_request/url_request_ftp_job.h107
-rw-r--r--chromium/net/url_request/url_request_ftp_job_unittest.cc710
-rw-r--r--chromium/net/url_request/url_request_http_job.cc1513
-rw-r--r--chromium/net/url_request/url_request_http_job.h273
-rw-r--r--chromium/net/url_request/url_request_http_job_unittest.cc120
-rw-r--r--chromium/net/url_request/url_request_job.cc736
-rw-r--r--chromium/net/url_request/url_request_job.h411
-rw-r--r--chromium/net/url_request/url_request_job_factory.cc20
-rw-r--r--chromium/net/url_request/url_request_job_factory.h62
-rw-r--r--chromium/net/url_request/url_request_job_factory_impl.cc83
-rw-r--r--chromium/net/url_request/url_request_job_factory_impl.h48
-rw-r--r--chromium/net/url_request/url_request_job_factory_impl_unittest.cc94
-rw-r--r--chromium/net/url_request/url_request_job_manager.cc251
-rw-r--r--chromium/net/url_request/url_request_job_manager.h116
-rw-r--r--chromium/net/url_request/url_request_job_unittest.cc80
-rw-r--r--chromium/net/url_request/url_request_netlog_params.cc40
-rw-r--r--chromium/net/url_request/url_request_netlog_params.h40
-rw-r--r--chromium/net/url_request/url_request_redirect_job.cc53
-rw-r--r--chromium/net/url_request/url_request_redirect_job.h57
-rw-r--r--chromium/net/url_request/url_request_simple_job.cc74
-rw-r--r--chromium/net/url_request/url_request_simple_job.h63
-rw-r--r--chromium/net/url_request/url_request_status.h62
-rw-r--r--chromium/net/url_request/url_request_test_job.cc320
-rw-r--r--chromium/net/url_request/url_request_test_job.h169
-rw-r--r--chromium/net/url_request/url_request_test_util.cc607
-rw-r--r--chromium/net/url_request/url_request_test_util.h353
-rw-r--r--chromium/net/url_request/url_request_throttler_entry.cc321
-rw-r--r--chromium/net/url_request/url_request_throttler_entry.h175
-rw-r--r--chromium/net/url_request/url_request_throttler_entry_interface.h73
-rw-r--r--chromium/net/url_request/url_request_throttler_header_adapter.cc29
-rw-r--r--chromium/net/url_request/url_request_throttler_header_adapter.h35
-rw-r--r--chromium/net/url_request/url_request_throttler_header_interface.h28
-rw-r--r--chromium/net/url_request/url_request_throttler_manager.cc203
-rw-r--r--chromium/net/url_request/url_request_throttler_manager.h166
-rw-r--r--chromium/net/url_request/url_request_throttler_simulation_unittest.cc755
-rw-r--r--chromium/net/url_request/url_request_throttler_test_support.cc58
-rw-r--r--chromium/net/url_request/url_request_throttler_test_support.h61
-rw-r--r--chromium/net/url_request/url_request_throttler_unittest.cc566
-rw-r--r--chromium/net/url_request/url_request_unittest.cc6251
-rw-r--r--chromium/net/url_request/view_cache_helper.cc367
-rw-r--r--chromium/net/url_request/view_cache_helper.h124
-rw-r--r--chromium/net/url_request/view_cache_helper_unittest.cc210
-rw-r--r--chromium/net/websockets/OWNERS8
-rw-r--r--chromium/net/websockets/PRESUBMIT.py57
-rw-r--r--chromium/net/websockets/README53
-rw-r--r--chromium/net/websockets/websocket_channel.cc703
-rw-r--r--chromium/net/websockets/websocket_channel.h267
-rw-r--r--chromium/net/websockets/websocket_channel_test.cc1886
-rw-r--r--chromium/net/websockets/websocket_errors.cc42
-rw-r--r--chromium/net/websockets/websocket_errors.h53
-rw-r--r--chromium/net/websockets/websocket_errors_unittest.cc29
-rw-r--r--chromium/net/websockets/websocket_event_interface.h76
-rw-r--r--chromium/net/websockets/websocket_frame.cc230
-rw-r--r--chromium/net/websockets/websocket_frame.h175
-rw-r--r--chromium/net/websockets/websocket_frame_parser.cc212
-rw-r--r--chromium/net/websockets/websocket_frame_parser.h87
-rw-r--r--chromium/net/websockets/websocket_frame_parser_unittest.cc576
-rw-r--r--chromium/net/websockets/websocket_frame_unittest.cc462
-rw-r--r--chromium/net/websockets/websocket_handshake_handler.cc573
-rw-r--r--chromium/net/websockets/websocket_handshake_handler.h171
-rw-r--r--chromium/net/websockets/websocket_handshake_handler_spdy_unittest.cc191
-rw-r--r--chromium/net/websockets/websocket_handshake_handler_unittest.cc583
-rw-r--r--chromium/net/websockets/websocket_job.cc704
-rw-r--r--chromium/net/websockets/websocket_job.h163
-rw-r--r--chromium/net/websockets/websocket_job_unittest.cc1112
-rw-r--r--chromium/net/websockets/websocket_mux.h39
-rw-r--r--chromium/net/websockets/websocket_net_log_params.cc49
-rw-r--r--chromium/net/websockets/websocket_net_log_params.h21
-rw-r--r--chromium/net/websockets/websocket_net_log_params_unittest.cc50
-rw-r--r--chromium/net/websockets/websocket_stream.cc32
-rw-r--r--chromium/net/websockets/websocket_stream.h211
-rw-r--r--chromium/net/websockets/websocket_stream_base.h55
-rw-r--r--chromium/net/websockets/websocket_throttle.cc144
-rw-r--r--chromium/net/websockets/websocket_throttle.h75
-rw-r--r--chromium/net/websockets/websocket_throttle_unittest.cc357
2330 files changed, 595278 insertions, 0 deletions
diff --git a/chromium/net/DEPS b/chromium/net/DEPS
new file mode 100644
index 00000000000..edf5377dfbf
--- /dev/null
+++ b/chromium/net/DEPS
@@ -0,0 +1,15 @@
+include_rules = [
+ "+crypto",
+ "+jni",
+ "+third_party/apple_apsl",
+ "+third_party/libevent",
+ "+third_party/nss",
+ "+third_party/zlib",
+ "+sdch/open-vcdiff",
+ "+v8",
+]
+
+skip_child_includes = [
+ "third_party",
+ "tools/flip_server",
+]
diff --git a/chromium/net/OWNERS b/chromium/net/OWNERS
new file mode 100644
index 00000000000..87e11faef93
--- /dev/null
+++ b/chromium/net/OWNERS
@@ -0,0 +1,20 @@
+agl@chromium.org
+akalin@chromium.org
+asanka@chromium.org
+cbentzel@chromium.org
+eroman@chromium.org
+gavinp@chromium.org
+jar@chromium.org
+mattm@chromium.org
+mmenke@chromium.org
+rch@chromium.org
+rdsmith@chromium.org
+rsleevi@chromium.org
+rtenneti@chromium.org
+rvargas@chromium.org
+szym@chromium.org
+willchan@chromium.org
+wtc@chromium.org
+
+per-file *.isolate=csharp@chromium.org
+per-file *.isolate=maruel@chromium.org \ No newline at end of file
diff --git a/chromium/net/PRESUBMIT.py b/chromium/net/PRESUBMIT.py
new file mode 100644
index 00000000000..3a2f21323b6
--- /dev/null
+++ b/chromium/net/PRESUBMIT.py
@@ -0,0 +1,21 @@
+# Copyright (c) 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.
+
+"""Chromium presubmit script for src/net.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into gcl.
+"""
+
+def GetPreferredTrySlaves(project, change):
+ slaves = [
+ 'linux_rel:sync_integration_tests',
+ 'mac_rel:sync_integration_tests',
+ 'win_rel:sync_integration_tests',
+ ]
+ # Changes that touch NSS files will likely need a corresponding OpenSSL edit.
+ # Conveniently, this one glob also matches _openssl.* changes too.
+ if any('nss' in f.LocalPath() for f in change.AffectedFiles()):
+ slaves.append('linux_redux')
+ return slaves
diff --git a/chromium/net/android/OWNERS b/chromium/net/android/OWNERS
new file mode 100644
index 00000000000..4bcb60f934c
--- /dev/null
+++ b/chromium/net/android/OWNERS
@@ -0,0 +1,3 @@
+digit@chromium.org
+pliard@chromium.org
+yfriedman@chromium.org
diff --git a/chromium/net/android/cert_verify_result_android.h b/chromium/net/android/cert_verify_result_android.h
new file mode 100644
index 00000000000..ac18c218cfd
--- /dev/null
+++ b/chromium/net/android/cert_verify_result_android.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 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_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
+#define NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
+
+#include "base/basictypes.h"
+#include "base/platform_file.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+namespace android {
+
+enum CertVerifyResultAndroid {
+#define CERT_VERIFY_RESULT_ANDROID(label, value) VERIFY_ ## label = value,
+#include "net/android/cert_verify_result_android_list.h"
+#undef CERT_VERIFY_RESULT_ANDROID
+};
+
+} // namespace android
+
+} // namespace net
+
+#endif // NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
diff --git a/chromium/net/android/cert_verify_result_android_list.h b/chromium/net/android/cert_verify_result_android_list.h
new file mode 100644
index 00000000000..a6d882f1e03
--- /dev/null
+++ b/chromium/net/android/cert_verify_result_android_list.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 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.
+
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum values.
+
+// This file contains the list of certificate verification results returned
+// from Java side to the C++ side.
+
+// Certificate is trusted.
+CERT_VERIFY_RESULT_ANDROID(OK, 0)
+
+// Certificate verification could not be conducted.
+CERT_VERIFY_RESULT_ANDROID(FAILED, -1)
+
+// Certificate is not trusted due to non-trusted root of the certificate chain.
+CERT_VERIFY_RESULT_ANDROID(NO_TRUSTED_ROOT, -2)
+
+// Certificate is not trusted because it has expired.
+CERT_VERIFY_RESULT_ANDROID(EXPIRED, -3)
+
+// Certificate is not trusted because it is not valid yet.
+CERT_VERIFY_RESULT_ANDROID(NOT_YET_VALID, -4)
+
+// Certificate is not trusted because it could not be parsed.
+CERT_VERIFY_RESULT_ANDROID(UNABLE_TO_PARSE, -5)
+
+// Certificate is not trusted because it has an extendedKeyUsage field, but
+// its value is not correct for a web server.
+CERT_VERIFY_RESULT_ANDROID(INCORRECT_KEY_USAGE, -6)
diff --git a/chromium/net/android/gurl_utils.cc b/chromium/net/android/gurl_utils.cc
new file mode 100644
index 00000000000..c8cb1e4e998
--- /dev/null
+++ b/chromium/net/android/gurl_utils.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#include "net/android/gurl_utils.h"
+
+#include "base/android/jni_string.h"
+#include "jni/GURLUtils_jni.h"
+#include "url/gurl.h"
+
+namespace net {
+
+jstring GetOrigin(JNIEnv* env, jclass clazz, jstring url) {
+ GURL host(base::android::ConvertJavaStringToUTF16(env, url));
+
+ return base::android::ConvertUTF8ToJavaString(env,
+ host.GetOrigin().spec()).Release();
+}
+
+jstring GetScheme(JNIEnv* env, jclass clazz, jstring url) {
+ GURL host(base::android::ConvertJavaStringToUTF16(env, url));
+
+ return base::android::ConvertUTF8ToJavaString(env,
+ host.scheme()).Release();
+}
+
+bool RegisterGURLUtils(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // net namespace
diff --git a/chromium/net/android/gurl_utils.h b/chromium/net/android/gurl_utils.h
new file mode 100644
index 00000000000..aeeecf7b4f4
--- /dev/null
+++ b/chromium/net/android/gurl_utils.h
@@ -0,0 +1,16 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_GURL_UTILS_H_
+#define NET_ANDROID_GURL_UTILS_H_
+
+#include <jni.h>
+
+namespace net {
+
+bool RegisterGURLUtils(JNIEnv* env);
+
+} // net namespace
+
+#endif // NET_ANDROID_GURL_UTILS_H_
diff --git a/chromium/net/android/java/CertVerifyResultAndroid.template b/chromium/net/android/java/CertVerifyResultAndroid.template
new file mode 100644
index 00000000000..b19e937fcb9
--- /dev/null
+++ b/chromium/net/android/java/CertVerifyResultAndroid.template
@@ -0,0 +1,10 @@
+// Copyright (c) 2013 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;
+
+public class CertVerifyResultAndroid {
+#define CERT_VERIFY_RESULT_ANDROID(name, value) public static final int VERIFY_##name = value;
+#include "net/android/cert_verify_result_android_list.h"
+}
diff --git a/chromium/net/android/java/CertificateMimeType.template b/chromium/net/android/java/CertificateMimeType.template
new file mode 100644
index 00000000000..5a21171e88b
--- /dev/null
+++ b/chromium/net/android/java/CertificateMimeType.template
@@ -0,0 +1,11 @@
+// Copyright (c) 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;
+
+public class CertificateMimeType {
+#define CERTIFICATE_MIME_TYPE(name, value) public static final int name = value;
+#include "net/base/mime_util_certificate_type_list.h"
+#undef CERTIFICATE_MIME_TYPE
+}
diff --git a/chromium/net/android/java/NetError.template b/chromium/net/android/java/NetError.template
new file mode 100644
index 00000000000..f6c16617b3b
--- /dev/null
+++ b/chromium/net/android/java/NetError.template
@@ -0,0 +1,10 @@
+// Copyright (c) 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;
+
+public class NetError {
+#define NET_ERROR(name, value) public static final int ERR_##name = value;
+#include "net/base/net_error_list.h"
+}
diff --git a/chromium/net/android/java/PrivateKeyType.template b/chromium/net/android/java/PrivateKeyType.template
new file mode 100644
index 00000000000..aa7f76f8881
--- /dev/null
+++ b/chromium/net/android/java/PrivateKeyType.template
@@ -0,0 +1,10 @@
+// Copyright (c) 2013 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;
+
+public class PrivateKeyType {
+#define DEFINE_PRIVATE_KEY_TYPE(name,value) public static final int name = value;
+#include "net/android/private_key_type_list.h"
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java b/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java
new file mode 100644
index 00000000000..2edaa895b7e
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/AndroidKeyStore.java
@@ -0,0 +1,308 @@
+// Copyright (c) 2013 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;
+
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.math.BigInteger;
+import java.security.interfaces.DSAKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.spec.ECParameterSpec;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.net.PrivateKeyType;;
+
+@JNINamespace("net::android")
+public class AndroidKeyStore {
+
+ private static final String TAG = "AndroidKeyStore";
+
+ ////////////////////////////////////////////////////////////////////
+ //
+ // Message signing support.
+
+ /**
+ * Returns the public modulus of a given RSA private key as a byte
+ * buffer.
+ * This can be used by native code to convert the modulus into
+ * an OpenSSL BIGNUM object. Required to craft a custom native RSA
+ * object where RSA_size() works as expected.
+ *
+ * @param key A PrivateKey instance, must implement RSAKey.
+ * @return A byte buffer corresponding to the modulus. This is
+ * big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getRSAKeyModulus(PrivateKey key) {
+ if (key instanceof RSAKey) {
+ return ((RSAKey) key).getModulus().toByteArray();
+ } else {
+ Log.w(TAG, "Not a RSAKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the 'Q' parameter of a given DSA private key as a byte
+ * buffer.
+ * This can be used by native code to convert it into an OpenSSL BIGNUM
+ * object where DSA_size() works as expected.
+ *
+ * @param key A PrivateKey instance. Must implement DSAKey.
+ * @return A byte buffer corresponding to the Q parameter. This is
+ * a big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getDSAKeyParamQ(PrivateKey key) {
+ if (key instanceof DSAKey) {
+ DSAParams params = ((DSAKey) key).getParams();
+ return params.getQ().toByteArray();
+ } else {
+ Log.w(TAG, "Not a DSAKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the 'order' parameter of a given ECDSA private key as a
+ * a byte buffer.
+ * @param key A PrivateKey instance. Must implement ECKey.
+ * @return A byte buffer corresponding to the 'order' parameter.
+ * This is a big-endian representation of a BigInteger.
+ */
+ @CalledByNative
+ public static byte[] getECKeyOrder(PrivateKey key) {
+ if (key instanceof ECKey) {
+ ECParameterSpec params = ((ECKey) key).getParams();
+ return params.getOrder().toByteArray();
+ } else {
+ Log.w(TAG, "Not an ECKey instance!");
+ return null;
+ }
+ }
+
+ /**
+ * Returns the encoded data corresponding to a given PrivateKey.
+ * Note that this will fail for platform keys on Android 4.0.4
+ * and higher. It can be used on 4.0.3 and older platforms to
+ * route around the platform bug described below.
+ * @param key A PrivateKey instance
+ * @return encoded key as PKCS#8 byte array, can be null.
+ */
+ @CalledByNative
+ public static byte[] getPrivateKeyEncodedBytes(PrivateKey key) {
+ return key.getEncoded();
+ }
+
+ /**
+ * Sign a given message with a given PrivateKey object. This method
+ * shall only be used to implement signing in the context of SSL
+ * client certificate support.
+ *
+ * The message will actually be a hash, computed and padded by OpenSSL,
+ * itself, depending on the type of the key. The result should match
+ * exactly what the vanilla implementations of the following OpenSSL
+ * function calls do:
+ *
+ * - For a RSA private key, this should be equivalent to calling
+ * RSA_sign(NDI_md5_sha1,....), i.e. it must generate a raw RSA
+ * signature. The message must a combined, 36-byte MD5+SHA1 message
+ * digest padded to the length of the modulus using PKCS#1 padding.
+ *
+ * - For a DSA and ECDSA private keys, this should be equivalent to
+ * calling DSA_sign(0,...) and ECDSA_sign(0,...) respectively. The
+ * message must be a 20-byte SHA1 hash and the function shall
+ * compute a direct DSA/ECDSA signature for it.
+ *
+ * @param privateKey The PrivateKey handle.
+ * @param message The message to sign.
+ * @return signature as a byte buffer.
+ *
+ * Important: Due to a platform bug, this function will always fail on
+ * Android < 4.2 for RSA PrivateKey objects. See the
+ * getOpenSSLHandleForPrivateKey() below for work-around.
+ */
+ @CalledByNative
+ public static byte[] rawSignDigestWithPrivateKey(PrivateKey privateKey,
+ byte[] message) {
+ // Get the Signature for this key.
+ Signature signature = null;
+ // Hint: Algorithm names come from:
+ // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
+ try {
+ if (privateKey instanceof RSAPrivateKey) {
+ // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException
+ // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
+ // See https://android-review.googlesource.com/#/c/40352/
+ signature = Signature.getInstance("NONEwithRSA");
+ } else if (privateKey instanceof DSAPrivateKey) {
+ signature = Signature.getInstance("NONEwithDSA");
+ } else if (privateKey instanceof ECPrivateKey) {
+ signature = Signature.getInstance("NONEwithECDSA");
+ }
+ } catch (NoSuchAlgorithmException e) {
+ ;
+ }
+
+ if (signature == null) {
+ Log.e(TAG, "Unsupported private key algorithm: " + privateKey.getAlgorithm());
+ return null;
+ }
+
+ // Sign the message.
+ try {
+ signature.initSign(privateKey);
+ signature.update(message);
+ return signature.sign();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while signing message with " + privateKey.getAlgorithm() +
+ " private key: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Return the type of a given PrivateKey object. This is an integer
+ * that maps to one of the values defined by org.chromium.net.PrivateKeyType,
+ * which is itself auto-generated from net/android/private_key_type_list.h
+ * @param privateKey The PrivateKey handle
+ * @return key type, or PrivateKeyType.INVALID if unknown.
+ */
+ @CalledByNative
+ public static int getPrivateKeyType(PrivateKey privateKey) {
+ if (privateKey instanceof RSAPrivateKey)
+ return PrivateKeyType.RSA;
+ if (privateKey instanceof DSAPrivateKey)
+ return PrivateKeyType.DSA;
+ if (privateKey instanceof ECPrivateKey)
+ return PrivateKeyType.ECDSA;
+ else
+ return PrivateKeyType.INVALID;
+ }
+
+ /**
+ * Return the system EVP_PKEY handle corresponding to a given PrivateKey
+ * object, obtained through reflection.
+ *
+ * This shall only be used when the "NONEwithRSA" signature is not
+ * available, as described in rawSignDigestWithPrivateKey(). I.e.
+ * never use this on Android 4.2 or higher.
+ *
+ * This can only work in Android 4.0.4 and higher, for older versions
+ * of the platform (e.g. 4.0.3), there is no system OpenSSL EVP_PKEY,
+ * but the private key contents can be retrieved directly with
+ * the getEncoded() method.
+ *
+ * This assumes that the target device uses a vanilla AOSP
+ * implementation of its java.security classes, which is also
+ * based on OpenSSL (fortunately, no OEM has apperently changed to
+ * a different implementation, according to the Android team).
+ *
+ * Note that the object returned was created with the platform version
+ * of OpenSSL, and _not_ the one that comes with Chromium. Whether the
+ * object can be used safely with the Chromium OpenSSL library depends
+ * on differences between their actual ABI / implementation details.
+ *
+ * To better understand what's going on below, please refer to the
+ * following source files in the Android 4.0.4 and 4.1 source trees:
+ * libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java
+ * libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+ *
+ * @param privateKey The PrivateKey handle.
+ * @return The EVP_PKEY handle, as a 32-bit integer (0 if not available)
+ */
+ @CalledByNative
+ public static int getOpenSSLHandleForPrivateKey(PrivateKey privateKey) {
+ // Sanity checks
+ if (privateKey == null) {
+ Log.e(TAG, "privateKey == null");
+ return 0;
+ }
+ if (!(privateKey instanceof RSAPrivateKey)) {
+ Log.e(TAG, "does not implement RSAPrivateKey");
+ return 0;
+ }
+ // First, check that this is a proper instance of OpenSSLRSAPrivateKey
+ // or one of its sub-classes.
+ Class<?> superClass;
+ try {
+ superClass = Class.forName(
+ "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
+ } catch (Exception e) {
+ // This may happen if the target device has a completely different
+ // implementation of the java.security APIs, compared to vanilla
+ // Android. Highly unlikely, but still possible.
+ Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
+ return 0;
+ }
+ if (!superClass.isInstance(privateKey)) {
+ // This may happen if the PrivateKey was not created by the "AndroidOpenSSL"
+ // provider, which should be the default. That could happen if an OEM decided
+ // to implement a different default provider. Also highly unlikely.
+ Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" +
+ privateKey.getClass().getCanonicalName());
+ return 0;
+ }
+
+ try {
+ // Use reflection to invoke the 'getOpenSSLKey()' method on
+ // the private key. This returns another Java object that wraps
+ // a native EVP_PKEY. Note that the method is final, so calling
+ // the superclass implementation is ok.
+ Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
+ getKey.setAccessible(true);
+ Object opensslKey = null;
+ try {
+ opensslKey = getKey.invoke(privateKey);
+ } finally {
+ getKey.setAccessible(false);
+ }
+ if (opensslKey == null) {
+ // Bail when detecting OEM "enhancement".
+ Log.e(TAG, "getOpenSSLKey() returned null");
+ return 0;
+ }
+
+ // Use reflection to invoke the 'getPkeyContext' method on the
+ // result of the getOpenSSLKey(). This is an 32-bit integer
+ // which is the address of an EVP_PKEY object.
+ Method getPkeyContext;
+ try {
+ getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
+ } catch (Exception e) {
+ // Bail here too, something really not working as expected.
+ Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
+ return 0;
+ }
+ getPkeyContext.setAccessible(true);
+ int evp_pkey = 0;
+ try {
+ evp_pkey = (Integer) getPkeyContext.invoke(opensslKey);
+ } finally {
+ getPkeyContext.setAccessible(false);
+ }
+ if (evp_pkey == 0) {
+ // The PrivateKey is probably rotten for some reason.
+ Log.e(TAG, "getPkeyContext() returned null");
+ }
+ return evp_pkey;
+
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e);
+ return 0;
+ }
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java b/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
new file mode 100644
index 00000000000..95752cca8b2
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java
@@ -0,0 +1,232 @@
+// Copyright (c) 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;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.security.KeyChain;
+import android.util.Log;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.CalledByNativeUnchecked;
+import org.chromium.net.CertVerifyResultAndroid;
+import org.chromium.net.CertificateMimeType;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.URLConnection;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.Enumeration;
+
+/**
+ * This class implements net utilities required by the net component.
+ */
+class AndroidNetworkLibrary {
+
+ private static final String TAG = "AndroidNetworkLibrary";
+
+ /**
+ * Stores the key pair through the CertInstaller activity.
+ * @param context: current application context.
+ * @param public_key: The public key bytes as DER-encoded SubjectPublicKeyInfo (X.509)
+ * @param private_key: The private key as DER-encoded PrivateKeyInfo (PKCS#8).
+ * @return: true on success, false on failure.
+ *
+ * Note that failure means that the function could not launch the CertInstaller
+ * activity. Whether the keys are valid or properly installed will be indicated
+ * by the CertInstaller UI itself.
+ */
+ @CalledByNative
+ static public boolean storeKeyPair(Context context, byte[] public_key, byte[] private_key) {
+ // TODO(digit): Use KeyChain official extra values to pass the public and private
+ // keys when they're available. The "KEY" and "PKEY" hard-coded constants were taken
+ // from the platform sources, since there are no official KeyChain.EXTRA_XXX definitions
+ // for them. b/5859651
+ try {
+ Intent intent = KeyChain.createInstallIntent();
+ intent.putExtra("PKEY", private_key);
+ intent.putExtra("KEY", public_key);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "could not store key pair: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * Adds a cryptographic file (User certificate, a CA certificate or
+ * PKCS#12 keychain) through the system's CertInstaller activity.
+ *
+ * @param context: current application context.
+ * @param cert_type: cryptographic file type. E.g. CertificateMimeType.X509_USER_CERT
+ * @param data: certificate/keychain data bytes.
+ * @return true on success, false on failure.
+ *
+ * Note that failure only indicates that the function couldn't launch the
+ * CertInstaller activity, not that the certificate/keychain was properly
+ * installed to the keystore.
+ */
+ @CalledByNative
+ static public boolean storeCertificate(Context context, int cert_type, byte[] data) {
+ try {
+ Intent intent = KeyChain.createInstallIntent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ switch (cert_type) {
+ case CertificateMimeType.X509_USER_CERT:
+ case CertificateMimeType.X509_CA_CERT:
+ intent.putExtra(KeyChain.EXTRA_CERTIFICATE, data);
+ break;
+
+ case CertificateMimeType.PKCS12_ARCHIVE:
+ intent.putExtra(KeyChain.EXTRA_PKCS12, data);
+ break;
+
+ default:
+ Log.w(TAG, "invalid certificate type: " + cert_type);
+ return false;
+ }
+ context.startActivity(intent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "could not store crypto file: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * @return the mime type (if any) that is associated with the file
+ * extension. Returns null if no corresponding mime type exists.
+ */
+ @CalledByNative
+ static public String getMimeTypeFromExtension(String extension) {
+ return URLConnection.guessContentTypeFromName("foo." + extension);
+ }
+
+ /**
+ * @return true if it can determine that only loopback addresses are
+ * configured. i.e. if only 127.0.0.1 and ::1 are routable. Also
+ * returns false if it cannot determine this.
+ */
+ @CalledByNative
+ static public boolean haveOnlyLoopbackAddresses() {
+ Enumeration<NetworkInterface> list = null;
+ try {
+ list = NetworkInterface.getNetworkInterfaces();
+ if (list == null) return false;
+ } catch (Exception e) {
+ Log.w(TAG, "could not get network interfaces: " + e);
+ return false;
+ }
+
+ while (list.hasMoreElements()) {
+ NetworkInterface netIf = list.nextElement();
+ try {
+ if (netIf.isUp() && !netIf.isLoopback()) return false;
+ } catch (SocketException e) {
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return the network interfaces list (if any) string. The items in
+ * the list string are delimited by a semicolon ";", each item
+ * is a network interface name and address pair and formatted
+ * as "name,address". e.g.
+ * eth0,10.0.0.2;eth0,fe80::5054:ff:fe12:3456
+ * represents a network list string which containts two items.
+ */
+ @CalledByNative
+ static public String getNetworkList() {
+ Enumeration<NetworkInterface> list = null;
+ try {
+ list = NetworkInterface.getNetworkInterfaces();
+ if (list == null) return "";
+ } catch (SocketException e) {
+ Log.w(TAG, "Unable to get network interfaces: " + e);
+ return "";
+ }
+
+ StringBuilder result = new StringBuilder();
+ while (list.hasMoreElements()) {
+ NetworkInterface netIf = list.nextElement();
+ try {
+ // Skip loopback interfaces, and ones which are down.
+ if (!netIf.isUp() || netIf.isLoopback())
+ continue;
+ Enumeration<InetAddress> addressList = netIf.getInetAddresses();
+ while (addressList.hasMoreElements()) {
+ InetAddress address = addressList.nextElement();
+ // Skip loopback addresses configured on non-loopback interfaces.
+ if (address.isLoopbackAddress())
+ continue;
+ StringBuilder addressString = new StringBuilder();
+ addressString.append(netIf.getName());
+ addressString.append(",");
+
+ String ipAddress = address.getHostAddress();
+ if (address instanceof Inet6Address && ipAddress.contains("%")) {
+ ipAddress = ipAddress.substring(0, ipAddress.lastIndexOf("%"));
+ }
+ addressString.append(ipAddress);
+
+ if (result.length() != 0)
+ result.append(";");
+ result.append(addressString.toString());
+ }
+ } catch (SocketException e) {
+ continue;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Validate the server's certificate chain is trusted.
+ *
+ * @param certChain The ASN.1 DER encoded bytes for certificates.
+ * @param authType The key exchange algorithm name (e.g. RSA)
+ * @return Android certificate verification result code.
+ */
+ @CalledByNative
+ public static int verifyServerCertificates(byte[][] certChain, String authType) {
+ try {
+ return X509Util.verifyServerCertificates(certChain, authType);
+ } catch (KeyStoreException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ } catch (NoSuchAlgorithmException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+ }
+
+ /**
+ * Adds a test root certificate to the local trust store.
+ * @param rootCert DER encoded bytes of the certificate.
+ */
+ @CalledByNativeUnchecked
+ public static void addTestRootCertificate(byte[] rootCert) throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ X509Util.addTestRootCertificate(rootCert);
+ }
+
+ /**
+ * Removes all test root certificates added by |addTestRootCertificate| calls from the local
+ * trust store.
+ */
+ @CalledByNativeUnchecked
+ public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
+ CertificateException, KeyStoreException {
+ X509Util.clearTestRootCertificates();
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/GURLUtils.java b/chromium/net/android/java/src/org/chromium/net/GURLUtils.java
new file mode 100644
index 00000000000..719ddeabb23
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/GURLUtils.java
@@ -0,0 +1,38 @@
+// Copyright (c) 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;
+
+import org.chromium.base.JNINamespace;
+
+/**
+ * Class to access the GURL library from java.
+ */
+@JNINamespace("net")
+public final class GURLUtils {
+
+ /**
+ * Get the origin of an url: Ex getOrigin("http://www.example.com:8080/index.html?bar=foo")
+ * would return "http://www.example.com:8080". It will return an empty string for an
+ * invalid url.
+ *
+ * @return The origin of the url
+ */
+ public static String getOrigin(String url) {
+ return nativeGetOrigin(url);
+ }
+
+ /**
+ * Get the scheme of the url (e.g. http, https, file). The returned string
+ * contains everything before the "://".
+ *
+ * @return The scheme of the url.
+ */
+ public static String getScheme(String url) {
+ return nativeGetScheme(url);
+ }
+
+ private static native String nativeGetOrigin(String url);
+ private static native String nativeGetScheme(String url);
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
new file mode 100644
index 00000000000..a5de98313c2
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
@@ -0,0 +1,224 @@
+// Copyright (c) 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;
+
+import android.content.Context;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+import org.chromium.base.ObserverList;
+
+import java.util.ArrayList;
+
+/**
+ * Triggers updates to the underlying network state in Chrome.
+ *
+ * By default, connectivity is assumed and changes must pushed from the embedder via the
+ * forceConnectivityState function.
+ * Embedders may choose to have this class auto-detect changes in network connectivity by invoking
+ * the setAutoDetectConnectivityState function.
+ *
+ * WARNING: This class is not thread-safe.
+ */
+@JNINamespace("net")
+public class NetworkChangeNotifier {
+ /**
+ * Alerted when the connection type of the network changes.
+ * The alert is fired on the UI thread.
+ */
+ public interface ConnectionTypeObserver {
+ public void onConnectionTypeChanged(int connectionType);
+ }
+
+ // These constants must always match the ones in network_change_notifier.h.
+ public static final int CONNECTION_UNKNOWN = 0;
+ public static final int CONNECTION_ETHERNET = 1;
+ public static final int CONNECTION_WIFI = 2;
+ public static final int CONNECTION_2G = 3;
+ public static final int CONNECTION_3G = 4;
+ public static final int CONNECTION_4G = 5;
+ public static final int CONNECTION_NONE = 6;
+
+ private final Context mContext;
+ private final ArrayList<Integer> mNativeChangeNotifiers;
+ private final ObserverList<ConnectionTypeObserver> mConnectionTypeObservers;
+ private NetworkChangeNotifierAutoDetect mAutoDetector;
+ private int mCurrentConnectionType = CONNECTION_UNKNOWN;
+
+ private static NetworkChangeNotifier sInstance;
+
+ private NetworkChangeNotifier(Context context) {
+ mContext = context;
+ mNativeChangeNotifiers = new ArrayList<Integer>();
+ mConnectionTypeObservers = new ObserverList<ConnectionTypeObserver>();
+ }
+
+ /**
+ * Initializes the singleton once.
+ */
+ @CalledByNative
+ public static NetworkChangeNotifier init(Context context) {
+ if (sInstance == null) {
+ sInstance = new NetworkChangeNotifier(context);
+ }
+ return sInstance;
+ }
+
+ public static boolean isInitialized() {
+ return sInstance != null;
+ }
+
+ static void resetInstanceForTests(Context context) {
+ sInstance = new NetworkChangeNotifier(context);
+ }
+
+ @CalledByNative
+ public int getCurrentConnectionType() {
+ return mCurrentConnectionType;
+ }
+
+ /**
+ * Adds a native-side observer.
+ */
+ @CalledByNative
+ public void addNativeObserver(int nativeChangeNotifier) {
+ mNativeChangeNotifiers.add(nativeChangeNotifier);
+ }
+
+ /**
+ * Removes a native-side observer.
+ */
+ @CalledByNative
+ public void removeNativeObserver(int nativeChangeNotifier) {
+ // Please keep the cast performing the boxing below. It ensures that the right method
+ // overload is used. ArrayList<T> has both remove(int index) and remove(T element).
+ mNativeChangeNotifiers.remove((Integer) nativeChangeNotifier);
+ }
+
+ /**
+ * Returns the singleton instance.
+ */
+ public static NetworkChangeNotifier getInstance() {
+ assert sInstance != null;
+ return sInstance;
+ }
+
+ /**
+ * Enables auto detection of the current network state based on notifications from the system.
+ * Note that passing true here requires the embedding app have the platform ACCESS_NETWORK_STATE
+ * permission.
+ *
+ * @param shouldAutoDetect true if the NetworkChangeNotifier should listen for system changes in
+ * network connectivity.
+ */
+ public static void setAutoDetectConnectivityState(boolean shouldAutoDetect) {
+ getInstance().setAutoDetectConnectivityStateInternal(shouldAutoDetect);
+ }
+
+ private void destroyAutoDetector() {
+ if (mAutoDetector != null) {
+ mAutoDetector.destroy();
+ mAutoDetector = null;
+ }
+ }
+
+ private void setAutoDetectConnectivityStateInternal(boolean shouldAutoDetect) {
+ if (shouldAutoDetect) {
+ if (mAutoDetector == null) {
+ mAutoDetector = new NetworkChangeNotifierAutoDetect(
+ new NetworkChangeNotifierAutoDetect.Observer() {
+ @Override
+ public void onConnectionTypeChanged(int newConnectionType) {
+ updateCurrentConnectionType(newConnectionType);
+ }
+ },
+ mContext);
+ mCurrentConnectionType = mAutoDetector.getCurrentConnectionType();
+ }
+ } else {
+ destroyAutoDetector();
+ }
+ }
+
+ /**
+ * Updates the perceived network state when not auto-detecting changes to connectivity.
+ *
+ * @param networkAvailable True if the NetworkChangeNotifier should perceive a "connected"
+ * state, false implies "disconnected".
+ */
+ @CalledByNative
+ public static void forceConnectivityState(boolean networkAvailable) {
+ setAutoDetectConnectivityState(false);
+ getInstance().forceConnectivityStateInternal(networkAvailable);
+ }
+
+ private void forceConnectivityStateInternal(boolean forceOnline) {
+ boolean connectionCurrentlyExists = mCurrentConnectionType != CONNECTION_NONE;
+ if (connectionCurrentlyExists != forceOnline) {
+ updateCurrentConnectionType(forceOnline ? CONNECTION_UNKNOWN : CONNECTION_NONE);
+ }
+ }
+
+ private void updateCurrentConnectionType(int newConnectionType) {
+ mCurrentConnectionType = newConnectionType;
+ notifyObserversOfConnectionTypeChange(newConnectionType);
+ }
+
+ /**
+ * Alerts all observers of a connection change.
+ */
+ void notifyObserversOfConnectionTypeChange(int newConnectionType) {
+ for (Integer nativeChangeNotifier : mNativeChangeNotifiers) {
+ nativeNotifyConnectionTypeChanged(nativeChangeNotifier, newConnectionType);
+ }
+ for (ConnectionTypeObserver observer : mConnectionTypeObservers) {
+ observer.onConnectionTypeChanged(newConnectionType);
+ }
+ }
+
+ /**
+ * Adds an observer for any connection type changes.
+ */
+ public static void addConnectionTypeObserver(ConnectionTypeObserver observer) {
+ getInstance().addConnectionTypeObserverInternal(observer);
+ }
+
+ private void addConnectionTypeObserverInternal(ConnectionTypeObserver observer) {
+ if (!mConnectionTypeObservers.hasObserver(observer)) {
+ mConnectionTypeObservers.addObserver(observer);
+ }
+ }
+
+ /**
+ * Removes an observer for any connection type changes.
+ */
+ public static void removeConnectionTypeObserver(ConnectionTypeObserver observer) {
+ getInstance().removeConnectionTypeObserverInternal(observer);
+ }
+
+ private void removeConnectionTypeObserverInternal(ConnectionTypeObserver observer) {
+ mConnectionTypeObservers.removeObserver(observer);
+ }
+
+ @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
+ private native void nativeNotifyConnectionTypeChanged(int nativePtr, int newConnectionType);
+
+ @NativeClassQualifiedName("NetworkChangeNotifierDelegateAndroid")
+ private native int nativeGetConnectionType(int nativePtr);
+
+ // For testing only.
+ public static NetworkChangeNotifierAutoDetect getAutoDetectorForTest() {
+ return getInstance().mAutoDetector;
+ }
+
+ /**
+ * Checks if there currently is connectivity.
+ */
+ public static boolean isOnline() {
+ int connectionType = getInstance().getCurrentConnectionType();
+ return connectionType != CONNECTION_UNKNOWN && connectionType != CONNECTION_NONE;
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
new file mode 100644
index 00000000000..038cb3124ac
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -0,0 +1,196 @@
+// Copyright (c) 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import org.chromium.base.ActivityStatus;
+
+/**
+ * Used by the NetworkChangeNotifier to listens to platform changes in connectivity.
+ * Note that use of this class requires that the app have the platform
+ * ACCESS_NETWORK_STATE permission.
+ */
+public class NetworkChangeNotifierAutoDetect extends BroadcastReceiver
+ implements ActivityStatus.StateListener {
+
+ /** Queries the ConnectivityManager for information about the current connection. */
+ static class ConnectivityManagerDelegate {
+ private final ConnectivityManager mConnectivityManager;
+
+ ConnectivityManagerDelegate(Context context) {
+ mConnectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ // For testing.
+ ConnectivityManagerDelegate() {
+ // All the methods below should be overridden.
+ mConnectivityManager = null;
+ }
+
+ boolean activeNetworkExists() {
+ return mConnectivityManager.getActiveNetworkInfo() != null;
+ }
+
+ boolean isConnected() {
+ return mConnectivityManager.getActiveNetworkInfo().isConnected();
+ }
+
+ int getNetworkType() {
+ return mConnectivityManager.getActiveNetworkInfo().getType();
+ }
+
+ int getNetworkSubtype() {
+ return mConnectivityManager.getActiveNetworkInfo().getSubtype();
+ }
+ }
+
+ private static final String TAG = "NetworkChangeNotifierAutoDetect";
+
+ private final NetworkConnectivityIntentFilter mIntentFilter =
+ new NetworkConnectivityIntentFilter();
+
+ private final Observer mObserver;
+
+ private final Context mContext;
+ private ConnectivityManagerDelegate mConnectivityManagerDelegate;
+ private boolean mRegistered;
+ private int mConnectionType;
+
+ /**
+ * Observer notified on the UI thread whenever a new connection type was detected.
+ */
+ public static interface Observer {
+ public void onConnectionTypeChanged(int newConnectionType);
+ }
+
+ public NetworkChangeNotifierAutoDetect(Observer observer, Context context) {
+ mObserver = observer;
+ mContext = context.getApplicationContext();
+ mConnectivityManagerDelegate = new ConnectivityManagerDelegate(context);
+ mConnectionType = getCurrentConnectionType();
+ ActivityStatus.registerStateListener(this);
+ }
+
+ /**
+ * Allows overriding the ConnectivityManagerDelegate for tests.
+ */
+ void setConnectivityManagerDelegateForTests(ConnectivityManagerDelegate delegate) {
+ mConnectivityManagerDelegate = delegate;
+ }
+
+ public void destroy() {
+ unregisterReceiver();
+ }
+
+ /**
+ * Register a BroadcastReceiver in the given context.
+ */
+ private void registerReceiver() {
+ if (!mRegistered) {
+ mRegistered = true;
+ mContext.registerReceiver(this, mIntentFilter);
+ }
+ }
+
+ /**
+ * Unregister the BroadcastReceiver in the given context.
+ */
+ private void unregisterReceiver() {
+ if (mRegistered) {
+ mRegistered = false;
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ public int getCurrentConnectionType() {
+ // Track exactly what type of connection we have.
+ if (!mConnectivityManagerDelegate.activeNetworkExists() ||
+ !mConnectivityManagerDelegate.isConnected()) {
+ return NetworkChangeNotifier.CONNECTION_NONE;
+ }
+
+ switch (mConnectivityManagerDelegate.getNetworkType()) {
+ case ConnectivityManager.TYPE_ETHERNET:
+ return NetworkChangeNotifier.CONNECTION_ETHERNET;
+ case ConnectivityManager.TYPE_WIFI:
+ return NetworkChangeNotifier.CONNECTION_WIFI;
+ case ConnectivityManager.TYPE_WIMAX:
+ return NetworkChangeNotifier.CONNECTION_4G;
+ case ConnectivityManager.TYPE_MOBILE:
+ // Use information from TelephonyManager to classify the connection.
+ switch (mConnectivityManagerDelegate.getNetworkSubtype()) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return NetworkChangeNotifier.CONNECTION_2G;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return NetworkChangeNotifier.CONNECTION_3G;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return NetworkChangeNotifier.CONNECTION_4G;
+ default:
+ return NetworkChangeNotifier.CONNECTION_UNKNOWN;
+ }
+ default:
+ return NetworkChangeNotifier.CONNECTION_UNKNOWN;
+ }
+ }
+
+ // BroadcastReceiver
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ connectionTypeChanged();
+ }
+
+ // ActivityStatus.StateListener
+ @Override
+ public void onActivityStateChange(int state) {
+ if (state == ActivityStatus.RESUMED) {
+ // Note that this also covers the case where the main activity is created. The CREATED
+ // event is always followed by the RESUMED event. This is a temporary "hack" until
+ // http://crbug.com/176837 is fixed. The CREATED event can't be used reliably for now
+ // since its notification is deferred. This means that it can immediately follow a
+ // DESTROYED/STOPPED/... event which is problematic.
+ // TODO(pliard): fix http://crbug.com/176837.
+ connectionTypeChanged();
+ registerReceiver();
+ } else if (state == ActivityStatus.PAUSED) {
+ unregisterReceiver();
+ }
+ }
+
+ private void connectionTypeChanged() {
+ int newConnectionType = getCurrentConnectionType();
+ if (newConnectionType == mConnectionType) return;
+
+ mConnectionType = newConnectionType;
+ Log.d(TAG, "Network connectivity changed, type is: " + mConnectionType);
+ mObserver.onConnectionTypeChanged(newConnectionType);
+ }
+
+ private static class NetworkConnectivityIntentFilter extends IntentFilter {
+ NetworkConnectivityIntentFilter() {
+ addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ }
+ }
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java
new file mode 100644
index 00000000000..9c59bcccf6d
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -0,0 +1,115 @@
+// Copyright (c) 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Proxy;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.NativeClassQualifiedName;
+
+// This class partners with native ProxyConfigServiceAndroid to listen for
+// proxy change notifications from Android.
+@JNINamespace("net")
+public class ProxyChangeListener {
+ private static final String TAG = "ProxyChangeListener";
+ private static boolean sEnabled = true;
+
+ private int mNativePtr;
+ private Context mContext;
+ private ProxyReceiver mProxyReceiver;
+ private Delegate mDelegate;
+
+ public interface Delegate {
+ public void proxySettingsChanged();
+ }
+
+ private ProxyChangeListener(Context context) {
+ mContext = context;
+ }
+
+ public static void setEnabled(boolean enabled) {
+ sEnabled = enabled;
+ }
+
+ public void setDelegateForTesting(Delegate delegate) {
+ mDelegate = delegate;
+ }
+
+ @CalledByNative
+ static public ProxyChangeListener create(Context context) {
+ return new ProxyChangeListener(context);
+ }
+
+ @CalledByNative
+ static public String getProperty(String property) {
+ return System.getProperty(property);
+ }
+
+ @CalledByNative
+ public void start(int nativePtr) {
+ assert mNativePtr == 0;
+ mNativePtr = nativePtr;
+ registerReceiver();
+ }
+
+ @CalledByNative
+ public void stop() {
+ mNativePtr = 0;
+ unregisterReceiver();
+ }
+
+ private class ProxyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
+ proxySettingsChanged();
+ }
+ }
+ }
+
+ private void proxySettingsChanged() {
+ if (!sEnabled) {
+ return;
+ }
+ if (mDelegate != null) {
+ mDelegate.proxySettingsChanged();
+ }
+ if (mNativePtr == 0) {
+ return;
+ }
+ // Note that this code currently runs on a MESSAGE_LOOP_UI thread, but
+ // the C++ code must run the callbacks on the network thread.
+ nativeProxySettingsChanged(mNativePtr);
+ }
+
+ private void registerReceiver() {
+ if (mProxyReceiver != null) {
+ return;
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Proxy.PROXY_CHANGE_ACTION);
+ mProxyReceiver = new ProxyReceiver();
+ mContext.getApplicationContext().registerReceiver(mProxyReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (mProxyReceiver == null) {
+ return;
+ }
+ mContext.unregisterReceiver(mProxyReceiver);
+ mProxyReceiver = null;
+ }
+
+ /**
+ * See net/proxy/proxy_config_service_android.cc
+ */
+ @NativeClassQualifiedName("ProxyConfigServiceAndroid::JNIDelegate")
+ private native void nativeProxySettingsChanged(int nativePtr);
+}
diff --git a/chromium/net/android/java/src/org/chromium/net/X509Util.java b/chromium/net/android/java/src/org/chromium/net/X509Util.java
new file mode 100644
index 00000000000..30007caab17
--- /dev/null
+++ b/chromium/net/android/java/src/org/chromium/net/X509Util.java
@@ -0,0 +1,233 @@
+// Copyright (c) 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;
+
+import android.util.Log;
+
+import org.chromium.net.CertVerifyResultAndroid;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class X509Util {
+
+ private static final String TAG = "X509Util";
+
+ private static CertificateFactory sCertificateFactory;
+
+ private static final String OID_TLS_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
+ private static final String OID_ANY_EKU = "2.5.29.37.0";
+ // Server-Gated Cryptography (necessary to support a few legacy issuers):
+ // Netscape:
+ private static final String OID_SERVER_GATED_NETSCAPE = "2.16.840.1.113730.4.1";
+ // Microsoft:
+ private static final String OID_SERVER_GATED_MICROSOFT = "1.3.6.1.4.1.311.10.3.3";
+
+ /**
+ * Trust manager backed up by the read-only system certificate store.
+ */
+ private static X509TrustManager sDefaultTrustManager;
+
+ /**
+ * Trust manager backed up by a custom certificate store. We need such manager to plant test
+ * root CA to the trust store in testing.
+ */
+ private static X509TrustManager sTestTrustManager;
+ private static KeyStore sTestKeyStore;
+
+ /**
+ * Lock object used to synchronize all calls that modify or depend on the trust managers.
+ */
+ private static final Object sLock = new Object();
+
+ /**
+ * Ensures that the trust managers and certificate factory are initialized.
+ */
+ private static void ensureInitialized() throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ synchronized(sLock) {
+ if (sCertificateFactory == null) {
+ sCertificateFactory = CertificateFactory.getInstance("X.509");
+ }
+ if (sDefaultTrustManager == null) {
+ sDefaultTrustManager = X509Util.createTrustManager(null);
+ }
+ if (sTestKeyStore == null) {
+ sTestKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ try {
+ sTestKeyStore.load(null);
+ } catch(IOException e) {} // No IO operation is attempted.
+ }
+ if (sTestTrustManager == null) {
+ sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
+ }
+ }
+ }
+
+ /**
+ * Creates a X509TrustManager backed up by the given key store. When null is passed as a key
+ * store, system default trust store is used.
+ * @throws KeyStoreException, NoSuchAlgorithmException on error initializing the TrustManager.
+ */
+ private static X509TrustManager createTrustManager(KeyStore keyStore) throws KeyStoreException,
+ NoSuchAlgorithmException {
+ String algorithm = TrustManagerFactory.getDefaultAlgorithm();
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+ tmf.init(keyStore);
+
+ for (TrustManager tm : tmf.getTrustManagers()) {
+ if (tm instanceof X509TrustManager) {
+ return (X509TrustManager) tm;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * After each modification of test key store, trust manager has to be generated again.
+ */
+ private static void reloadTestTrustManager() throws KeyStoreException,
+ NoSuchAlgorithmException {
+ sTestTrustManager = X509Util.createTrustManager(sTestKeyStore);
+ }
+
+ /**
+ * Convert a DER encoded certificate to an X509Certificate.
+ */
+ public static X509Certificate createCertificateFromBytes(byte[] derBytes) throws
+ CertificateException, KeyStoreException, NoSuchAlgorithmException {
+ ensureInitialized();
+ return (X509Certificate) sCertificateFactory.generateCertificate(
+ new ByteArrayInputStream(derBytes));
+ }
+
+ public static void addTestRootCertificate(byte[] rootCertBytes) throws CertificateException,
+ KeyStoreException, NoSuchAlgorithmException {
+ ensureInitialized();
+ X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
+ synchronized (sLock) {
+ sTestKeyStore.setCertificateEntry(
+ "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
+ reloadTestTrustManager();
+ }
+ }
+
+ public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
+ CertificateException, KeyStoreException {
+ ensureInitialized();
+ synchronized (sLock) {
+ try {
+ sTestKeyStore.load(null);
+ reloadTestTrustManager();
+ } catch (IOException e) {} // No IO operation is attempted.
+ }
+ }
+
+ /**
+ * If an EKU extension is present in the end-entity certificate, it MUST contain either the
+ * anyEKU or serverAuth or netscapeSGC or Microsoft SGC EKUs.
+ *
+ * @return true if there is no EKU extension or if any of the EKU extensions is one of the valid
+ * OIDs for web server certificates.
+ *
+ * TODO(palmer): This can be removed after the equivalent change is made to the Android default
+ * TrustManager and that change is shipped to a large majority of Android users.
+ */
+ static boolean verifyKeyUsage(X509Certificate certificate) throws CertificateException {
+ List<String> ekuOids;
+ try {
+ ekuOids = certificate.getExtendedKeyUsage();
+ } catch (NullPointerException e) {
+ // getExtendedKeyUsage() can crash due to an Android platform bug. This probably
+ // happens when the EKU extension data is malformed so return false here.
+ // See http://crbug.com/233610
+ return false;
+ }
+ if (ekuOids == null)
+ return true;
+
+ for (String ekuOid : ekuOids) {
+ if (ekuOid.equals(OID_TLS_SERVER_AUTH) ||
+ ekuOid.equals(OID_ANY_EKU) ||
+ ekuOid.equals(OID_SERVER_GATED_NETSCAPE) ||
+ ekuOid.equals(OID_SERVER_GATED_MICROSOFT)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static int verifyServerCertificates(byte[][] certChain, String authType)
+ throws KeyStoreException, NoSuchAlgorithmException {
+ if (certChain == null || certChain.length == 0 || certChain[0] == null) {
+ throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
+ "chain passed as |certChain|. |certChain|=" + certChain);
+ }
+
+ try {
+ ensureInitialized();
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+
+ X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
+ try {
+ for (int i = 0; i < certChain.length; ++i) {
+ serverCertificates[i] = createCertificateFromBytes(certChain[i]);
+ }
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
+ }
+
+ // Expired and not yet valid certificates would be rejected by the trust managers, but the
+ // trust managers report all certificate errors using the general CertificateException. In
+ // order to get more granular error information, cert validity time range is being checked
+ // separately.
+ try {
+ serverCertificates[0].checkValidity();
+ if (!verifyKeyUsage(serverCertificates[0]))
+ return CertVerifyResultAndroid.VERIFY_INCORRECT_KEY_USAGE;
+ } catch (CertificateExpiredException e) {
+ return CertVerifyResultAndroid.VERIFY_EXPIRED;
+ } catch (CertificateNotYetValidException e) {
+ return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
+ } catch (CertificateException e) {
+ return CertVerifyResultAndroid.VERIFY_FAILED;
+ }
+
+ synchronized (sLock) {
+ try {
+ sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
+ return CertVerifyResultAndroid.VERIFY_OK;
+ } catch (CertificateException eDefaultManager) {
+ try {
+ sTestTrustManager.checkServerTrusted(serverCertificates, authType);
+ return CertVerifyResultAndroid.VERIFY_OK;
+ } catch (CertificateException eTestManager) {
+ // Neither of the trust managers confirms the validity of the certificate chain,
+ // log the error message returned by the system trust manager.
+ Log.i(TAG, "Failed to validate the certificate chain, error: " +
+ eDefaultManager.getMessage());
+ return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
+ }
+ }
+ }
+ }
+}
diff --git a/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java b/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java
new file mode 100644
index 00000000000..460dc50cabb
--- /dev/null
+++ b/chromium/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2013 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;
+
+import android.os.Build;
+import android.util.Log;
+
+import java.security.PrivateKey;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.KeyFactory;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.KeyStoreException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.net.PrivateKeyType;
+
+@JNINamespace("net::android")
+public class AndroidKeyStoreTestUtil {
+
+ private static final String TAG = "AndroidKeyStoreTestUtil";
+
+ /**
+ * Called from native code to create a PrivateKey object from its
+ * encoded PKCS#8 representation.
+ * @param type The key type, accoding to PrivateKeyType.
+ * @return new PrivateKey handle, or null in case of error.
+ */
+ @CalledByNative
+ public static PrivateKey createPrivateKeyFromPKCS8(int type,
+ byte[] encoded_key) {
+ String algorithm = null;
+ switch (type) {
+ case PrivateKeyType.RSA:
+ algorithm = "RSA";
+ break;
+ case PrivateKeyType.DSA:
+ algorithm = "DSA";
+ break;
+ case PrivateKeyType.ECDSA:
+ algorithm = "EC";
+ break;
+ default:
+ return null;
+ }
+
+ try {
+ KeyFactory factory = KeyFactory.getInstance(algorithm);
+ KeySpec ks = new PKCS8EncodedKeySpec(encoded_key);
+ PrivateKey key = factory.generatePrivate(ks);
+ return key;
+
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "Could not create " + algorithm + " factory instance!");
+ return null;
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG, "Could not load " + algorithm + " private key from bytes!");
+ return null;
+ }
+ }
+}
diff --git a/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java b/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java
new file mode 100644
index 00000000000..c705f69be37
--- /dev/null
+++ b/chromium/net/android/javatests/src/org/chromium/net/AndroidProxySelectorTest.java
@@ -0,0 +1,295 @@
+// Copyright (c) 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.
+
+/**
+ * Test suite for Android's default ProxySelector implementation. The purpose of these tests
+ * is to check that the behaviour of the ProxySelector implementation matches what we have
+ * implemented in net/proxy/proxy_config_service_android.cc.
+ *
+ * IMPORTANT: These test cases are generated from net/android/tools/proxy_test_cases.py, so if any
+ * of these tests fail, please be sure to edit that file and regenerate the test cases here and also
+ * in net/proxy/proxy_config_service_android_unittests.cc if required.
+ */
+
+package org.chromium.net;
+
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Properties;
+
+import org.chromium.base.test.util.Feature;
+
+public class AndroidProxySelectorTest extends InstrumentationTestCase {
+ Properties mProperties;
+
+ public AndroidProxySelectorTest() {
+ // Start with a clean slate in case there is a system proxy configured.
+ mProperties = new Properties();
+ }
+
+ @Override
+ public void setUp() {
+ System.setProperties(mProperties);
+ }
+
+ static String toString(Proxy proxy) {
+ if (proxy == Proxy.NO_PROXY)
+ return "DIRECT";
+ // java.net.Proxy only knows about http and socks proxies.
+ Proxy.Type type = proxy.type();
+ switch (type) {
+ case HTTP: return "PROXY " + proxy.address().toString();
+ case SOCKS: return "SOCKS5 " + proxy.address().toString();
+ case DIRECT: return "DIRECT";
+ default:
+ // If a new proxy type is supported in future, add a case to match it.
+ fail("Unknown proxy type" + type);
+ return "unknown://";
+ }
+ }
+
+ static String toString(List<Proxy> proxies) {
+ StringBuilder builder = new StringBuilder();
+ for (Proxy proxy : proxies) {
+ if (builder.length() > 0)
+ builder.append(';');
+ builder.append(toString(proxy));
+ }
+ return builder.toString();
+ }
+
+ static void checkMapping(String url, String expected) throws URISyntaxException {
+ URI uri = new URI(url);
+ List<Proxy> proxies = ProxySelector.getDefault().select(uri);
+ assertEquals("Mapping", expected, toString(proxies));
+ }
+
+ /**
+ * Test direct mapping when no proxy defined.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testNoProxy() throws Exception {
+ checkMapping("ftp://example.com/", "DIRECT");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * Test http.proxyHost and http.proxyPort works.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpProxyHostAndPort() throws Exception {
+ System.setProperty("http.proxyHost", "httpproxy.com");
+ System.setProperty("http.proxyPort", "8080");
+ checkMapping("ftp://example.com/", "DIRECT");
+ checkMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * We should get the default port (80) for proxied hosts.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpProxyHostOnly() throws Exception {
+ System.setProperty("http.proxyHost", "httpproxy.com");
+ checkMapping("ftp://example.com/", "DIRECT");
+ checkMapping("http://example.com/", "PROXY httpproxy.com:80");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * http.proxyPort only should not result in any hosts being proxied.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpProxyPortOnly() throws Exception {
+ System.setProperty("http.proxyPort", "8080");
+ checkMapping("ftp://example.com/", "DIRECT");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * Test that HTTP non proxy hosts are mapped correctly
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpNonProxyHosts1() throws Exception {
+ System.setProperty("http.nonProxyHosts", "slashdot.org");
+ System.setProperty("http.proxyHost", "httpproxy.com");
+ System.setProperty("http.proxyPort", "8080");
+ checkMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("http://slashdot.org/", "DIRECT");
+ }
+
+ /**
+ * Test that | pattern works.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpNonProxyHosts2() throws Exception {
+ System.setProperty("http.nonProxyHosts", "slashdot.org|freecode.net");
+ System.setProperty("http.proxyHost", "httpproxy.com");
+ System.setProperty("http.proxyPort", "8080");
+ checkMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("http://freecode.net/", "DIRECT");
+ checkMapping("http://slashdot.org/", "DIRECT");
+ }
+
+ /**
+ * Test that * pattern works.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpNonProxyHosts3() throws Exception {
+ System.setProperty("http.nonProxyHosts", "*example.com");
+ System.setProperty("http.proxyHost", "httpproxy.com");
+ System.setProperty("http.proxyPort", "8080");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("http://slashdot.org/", "PROXY httpproxy.com:8080");
+ checkMapping("http://www.example.com/", "DIRECT");
+ }
+
+ /**
+ * Test that FTP non proxy hosts are mapped correctly
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testFtpNonProxyHosts() throws Exception {
+ System.setProperty("ftp.nonProxyHosts", "slashdot.org");
+ System.setProperty("ftp.proxyHost", "httpproxy.com");
+ System.setProperty("ftp.proxyPort", "8080");
+ checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("http://example.com/", "DIRECT");
+ }
+
+ /**
+ * Test ftp.proxyHost and ftp.proxyPort works.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testFtpProxyHostAndPort() throws Exception {
+ System.setProperty("ftp.proxyHost", "httpproxy.com");
+ System.setProperty("ftp.proxyPort", "8080");
+ checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * Test ftp.proxyHost and default port.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testFtpProxyHostOnly() throws Exception {
+ System.setProperty("ftp.proxyHost", "httpproxy.com");
+ checkMapping("ftp://example.com/", "PROXY httpproxy.com:80");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("https://example.com/", "DIRECT");
+ }
+
+ /**
+ * Test https.proxyHost and https.proxyPort works.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpsProxyHostAndPort() throws Exception {
+ System.setProperty("https.proxyHost", "httpproxy.com");
+ System.setProperty("https.proxyPort", "8080");
+ checkMapping("ftp://example.com/", "DIRECT");
+ checkMapping("http://example.com/", "DIRECT");
+ checkMapping("https://example.com/", "PROXY httpproxy.com:8080");
+ }
+
+ /**
+ * Default http proxy is used if a scheme-specific one is not found.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testDefaultProxyExplictPort() throws Exception {
+ System.setProperty("ftp.proxyHost", "httpproxy.com");
+ System.setProperty("ftp.proxyPort", "8080");
+ System.setProperty("proxyHost", "defaultproxy.com");
+ System.setProperty("proxyPort", "8080");
+ checkMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ checkMapping("http://example.com/", "PROXY defaultproxy.com:8080");
+ checkMapping("https://example.com/", "PROXY defaultproxy.com:8080");
+ }
+
+ /**
+ * SOCKS proxy is used if scheme-specific one is not found.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testFallbackToSocks() throws Exception {
+ System.setProperty("http.proxyHost", "defaultproxy.com");
+ System.setProperty("socksProxyHost", "socksproxy.com");
+ checkMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080");
+ checkMapping("http://example.com/", "PROXY defaultproxy.com:80");
+ checkMapping("https://example.com/", "SOCKS5 socksproxy.com:1080");
+ }
+
+ /**
+ * SOCKS proxy port is used if specified
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testSocksExplicitPort() throws Exception {
+ System.setProperty("socksProxyHost", "socksproxy.com");
+ System.setProperty("socksProxyPort", "9000");
+ checkMapping("http://example.com/", "SOCKS5 socksproxy.com:9000");
+ }
+
+ /**
+ * SOCKS proxy is ignored if default HTTP proxy defined.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testHttpProxySupercedesSocks() throws Exception {
+ System.setProperty("proxyHost", "defaultproxy.com");
+ System.setProperty("socksProxyHost", "socksproxy.com");
+ System.setProperty("socksProxyPort", "9000");
+ checkMapping("http://example.com/", "PROXY defaultproxy.com:80");
+ }
+}
+
diff --git a/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java b/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java
new file mode 100644
index 00000000000..57885b2efe0
--- /dev/null
+++ b/chromium/net/android/javatests/src/org/chromium/net/NetErrorsTest.java
@@ -0,0 +1,32 @@
+// Copyright (c) 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.
+
+/**
+ * Tests to verify that NetError.java is created succesfully.
+ */
+
+package org.chromium.net;
+
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.base.test.util.Feature;
+
+public class NetErrorsTest extends InstrumentationTestCase {
+ // These are manually copied and should be kept in sync with net_error_list.h.
+ private static int IO_PENDING_ERROR = -1;
+ private static int FAILED_ERROR = -2;
+
+ /**
+ * Test whether we can include NetError.java and call to static integers defined in the file.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ @Feature({"Android-AppBase"})
+ public void testExampleErrorDefined() throws Exception {
+ assertEquals(IO_PENDING_ERROR, NetError.ERR_IO_PENDING);
+ assertEquals(FAILED_ERROR, NetError.ERR_FAILED);
+ }
+}
diff --git a/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
new file mode 100644
index 00000000000..b52d184de67
--- /dev/null
+++ b/chromium/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import org.chromium.base.ActivityStatus;
+import org.chromium.base.test.util.Feature;
+
+public class NetworkChangeNotifierTest extends InstrumentationTestCase {
+ /**
+ * Listens for alerts fired by the NetworkChangeNotifier when network status changes.
+ */
+ private static class NetworkChangeNotifierTestObserver
+ implements NetworkChangeNotifier.ConnectionTypeObserver {
+ private boolean mReceivedNotification = false;
+
+ @Override
+ public void onConnectionTypeChanged(int connectionType) {
+ mReceivedNotification = true;
+ }
+
+ public boolean hasReceivedNotification() {
+ return mReceivedNotification;
+ }
+
+ public void resetHasReceivedNotification() {
+ mReceivedNotification = false;
+ }
+ }
+
+ /**
+ * Mocks out calls to the ConnectivityManager.
+ */
+ class MockConnectivityManagerDelegate
+ extends NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate {
+ private boolean mActiveNetworkExists;
+ private int mNetworkType;
+ private int mNetworkSubtype;
+
+ @Override
+ boolean activeNetworkExists() {
+ return mActiveNetworkExists;
+ }
+
+ @Override
+ boolean isConnected() {
+ return getNetworkType() != NetworkChangeNotifier.CONNECTION_NONE;
+ }
+
+ void setActiveNetworkExists(boolean networkExists) {
+ mActiveNetworkExists = networkExists;
+ }
+
+ @Override
+ int getNetworkType() {
+ return mNetworkType;
+ }
+
+ void setNetworkType(int networkType) {
+ mNetworkType = networkType;
+ }
+
+ @Override
+ int getNetworkSubtype() {
+ return mNetworkSubtype;
+ }
+
+ void setNetworkSubtype(int networkSubtype) {
+ mNetworkSubtype = networkSubtype;
+ }
+ }
+
+ /**
+ * Tests that when Chrome gets an intent indicating a change in network connectivity, it sends a
+ * notification to Java observers.
+ */
+ @UiThreadTest
+ @MediumTest
+ @Feature({"Android-AppBase"})
+ public void testNetworkChangeNotifierJavaObservers() throws InterruptedException {
+ // Create a new notifier that doesn't have a native-side counterpart.
+ Context context = getInstrumentation().getTargetContext();
+ NetworkChangeNotifier.resetInstanceForTests(context);
+
+ NetworkChangeNotifier.setAutoDetectConnectivityState(true);
+ NetworkChangeNotifierAutoDetect receiver = NetworkChangeNotifier.getAutoDetectorForTest();
+ assertTrue(receiver != null);
+
+ MockConnectivityManagerDelegate connectivityDelegate =
+ new MockConnectivityManagerDelegate();
+ connectivityDelegate.setActiveNetworkExists(true);
+ connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_UNKNOWN);
+ connectivityDelegate.setNetworkSubtype(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ receiver.setConnectivityManagerDelegateForTests(connectivityDelegate);
+
+ // Initialize the NetworkChangeNotifier with a connection.
+ Intent connectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+
+ // We shouldn't be re-notified if the connection hasn't actually changed.
+ NetworkChangeNotifierTestObserver observer = new NetworkChangeNotifierTestObserver();
+ NetworkChangeNotifier.addConnectionTypeObserver(observer);
+ receiver.onReceive(getInstrumentation().getTargetContext(), connectivityIntent);
+ assertFalse(observer.hasReceivedNotification());
+
+ // Mimic that connectivity has been lost and ensure that Chrome notifies our observer.
+ connectivityDelegate.setActiveNetworkExists(false);
+ connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_NONE);
+ Intent noConnectivityIntent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+ receiver.onReceive(getInstrumentation().getTargetContext(), noConnectivityIntent);
+ assertTrue(observer.hasReceivedNotification());
+
+ observer.resetHasReceivedNotification();
+ // Pretend we got moved to the background.
+ receiver.onActivityStateChange(ActivityStatus.PAUSED);
+ // Change the state.
+ connectivityDelegate.setActiveNetworkExists(true);
+ connectivityDelegate.setNetworkType(NetworkChangeNotifier.CONNECTION_WIFI);
+ // The NetworkChangeNotifierAutoDetect doesn't receive any notification while we are in the
+ // background, but when we get back to the foreground the state changed should be detected
+ // and a notification sent.
+ receiver.onActivityStateChange(ActivityStatus.RESUMED);
+ assertTrue(observer.hasReceivedNotification());
+ }
+}
diff --git a/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java b/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java
new file mode 100644
index 00000000000..7dcbc685cd4
--- /dev/null
+++ b/chromium/net/android/javatests/src/org/chromium/net/X509UtilTest.java
@@ -0,0 +1,102 @@
+// Copyright 2013 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.telephony.TelephonyManager;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.InstrumentationTestCase;
+import android.util.Base64;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.security.GeneralSecurityException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import org.chromium.base.PathUtils;
+
+/**
+ * Tests for org.chromium.net.X509Util.
+ */
+public class X509UtilTest extends InstrumentationTestCase {
+ private static final String CERTS_DIRECTORY =
+ PathUtils.getExternalStorageDirectory() + "/net/data/ssl/certificates/";
+ private static final String BAD_EKU_TEST_ROOT = "eku-test-root.pem";
+ private static final String CRITICAL_CODE_SIGNING_EE = "crit-codeSigning-chain.pem";
+ private static final String NON_CRITICAL_CODE_SIGNING_EE = "non-crit-codeSigning-chain.pem";
+ private static final String WEB_CLIENT_AUTH_EE = "invalid_key_usage_cert.der";
+ private static final String OK_CERT = "ok_cert.pem";
+ private static final String GOOD_ROOT_CA = "root_ca_cert.pem";
+
+ private static final String BEGIN_MARKER = "-----BEGIN CERTIFICATE-----";
+ private static final String END_MARKER = "-----END CERTIFICATE-----";
+
+ private 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);
+ }
+
+ private static byte[] readFileBytes(String pathname) throws IOException {
+ RandomAccessFile file = new RandomAccessFile(pathname, "r");
+ byte[] bytes = new byte[(int) file.length()];
+ int bytesRead = file.read(bytes);
+ if (bytesRead != bytes.length)
+ return Arrays.copyOfRange(bytes, 0, bytesRead);
+ return bytes;
+ }
+
+ @MediumTest
+ public void testEkusVerified() throws GeneralSecurityException, IOException {
+ X509Util.addTestRootCertificate(pemToDer(CERTS_DIRECTORY + BAD_EKU_TEST_ROOT));
+ X509Util.addTestRootCertificate(pemToDer(CERTS_DIRECTORY + GOOD_ROOT_CA));
+
+ assertFalse(X509Util.verifyKeyUsage(
+ X509Util.createCertificateFromBytes(
+ pemToDer(CERTS_DIRECTORY + CRITICAL_CODE_SIGNING_EE))));
+
+ assertFalse(X509Util.verifyKeyUsage(
+ X509Util.createCertificateFromBytes(
+ pemToDer(CERTS_DIRECTORY + NON_CRITICAL_CODE_SIGNING_EE))));
+
+ assertFalse(X509Util.verifyKeyUsage(
+ X509Util.createCertificateFromBytes(
+ readFileBytes(CERTS_DIRECTORY + WEB_CLIENT_AUTH_EE))));
+
+ assertTrue(X509Util.verifyKeyUsage(
+ X509Util.createCertificateFromBytes(
+ pemToDer(CERTS_DIRECTORY + OK_CERT))));
+
+ try {
+ X509Util.clearTestRootCertificates();
+ } catch (Exception e) {
+ fail("Could not clear test root certificates: " + e.toString());
+ }
+ }
+}
+
diff --git a/chromium/net/android/keystore.cc b/chromium/net/android/keystore.cc
new file mode 100644
index 00000000000..a3d8cc1771f
--- /dev/null
+++ b/chromium/net/android/keystore.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2013 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/android/keystore.h"
+
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/logging.h"
+
+#include "jni/AndroidKeyStore_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::HasException;
+using base::android::JavaByteArrayToByteVector;
+using base::android::ScopedJavaLocalRef;
+using base::android::ToJavaByteArray;
+using base::android::JavaArrayOfByteArrayToStringVector;
+
+namespace net {
+namespace android {
+
+bool GetRSAKeyModulus(
+ jobject private_key_ref,
+ std::vector<uint8>* result) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jbyteArray> modulus_ref =
+ Java_AndroidKeyStore_getRSAKeyModulus(env, private_key_ref);
+ if (modulus_ref.is_null())
+ return false;
+
+ JavaByteArrayToByteVector(env, modulus_ref.obj(), result);
+ return true;
+}
+
+bool GetDSAKeyParamQ(jobject private_key_ref,
+ std::vector<uint8>* result) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jbyteArray> q_ref =
+ Java_AndroidKeyStore_getDSAKeyParamQ(env, private_key_ref);
+ if (q_ref.is_null())
+ return false;
+
+ JavaByteArrayToByteVector(env, q_ref.obj(), result);
+ return true;
+}
+
+bool GetECKeyOrder(jobject private_key_ref,
+ std::vector<uint8>* result) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jbyteArray> order_ref =
+ Java_AndroidKeyStore_getECKeyOrder(env, private_key_ref);
+ if (order_ref.is_null())
+ return false;
+
+ JavaByteArrayToByteVector(env, order_ref.obj(), result);
+ return true;
+}
+
+bool GetPrivateKeyEncodedBytes(jobject private_key,
+ std::vector<uint8>* result) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jbyteArray> encoded_ref =
+ Java_AndroidKeyStore_getPrivateKeyEncodedBytes(env, private_key);
+ if (encoded_ref.is_null())
+ return false;
+
+ JavaByteArrayToByteVector(env, encoded_ref.obj(), result);
+ return true;
+}
+
+bool RawSignDigestWithPrivateKey(
+ jobject private_key_ref,
+ const base::StringPiece& digest,
+ std::vector<uint8>* signature) {
+ JNIEnv* env = AttachCurrentThread();
+
+ // Convert message to byte[] array.
+ ScopedJavaLocalRef<jbyteArray> digest_ref =
+ ToJavaByteArray(env,
+ reinterpret_cast<const uint8*>(digest.data()),
+ digest.length());
+ DCHECK(!digest_ref.is_null());
+
+ // Invoke platform API
+ ScopedJavaLocalRef<jbyteArray> signature_ref =
+ Java_AndroidKeyStore_rawSignDigestWithPrivateKey(
+ env, private_key_ref, digest_ref.obj());
+ if (HasException(env) || signature_ref.is_null())
+ return false;
+
+ // Write signature to string.
+ JavaByteArrayToByteVector(env, signature_ref.obj(), signature);
+ return true;
+}
+
+PrivateKeyType GetPrivateKeyType(jobject private_key) {
+ JNIEnv* env = AttachCurrentThread();
+ int type = Java_AndroidKeyStore_getPrivateKeyType(
+ env, private_key);
+ return static_cast<PrivateKeyType>(type);
+}
+
+EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key) {
+ JNIEnv* env = AttachCurrentThread();
+ // Note: the pointer is passed as a jint here because that's how it
+ // is stored in the Java object. Java doesn't have a primitive type
+ // like intptr_t that matches the size of pointers on the host
+ // machine, and Android only runs on 32-bit CPUs.
+ //
+ // Given that this routine shall only be called on Android < 4.2,
+ // this won't be a problem in the far future (e.g. when Android gets
+ // ported to 64-bit environments, if ever).
+ int pkey =
+ Java_AndroidKeyStore_getOpenSSLHandleForPrivateKey(env, private_key);
+ return reinterpret_cast<EVP_PKEY*>(pkey);
+}
+
+bool RegisterKeyStore(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+} // namespace net
diff --git a/chromium/net/android/keystore.h b/chromium/net/android/keystore.h
new file mode 100644
index 00000000000..f14fa872edd
--- /dev/null
+++ b/chromium/net/android/keystore.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2013 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_ANDROID_KEYSTORE_H
+#define NET_ANDROID_KEYSTORE_H
+
+#include <jni.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/ssl/ssl_client_cert_type.h"
+
+// Avoid including <openssl/evp.h> here.
+typedef struct evp_pkey_st EVP_PKEY;
+
+// Misc functions to access the Android platform KeyStore.
+
+namespace net {
+namespace android {
+
+// Define a list of constants describing private key types. The
+// values are shared with Java through org.chromium.net.PrivateKeyType.
+// Example: PRIVATE_KEY_TYPE_RSA.
+enum PrivateKeyType {
+#define DEFINE_PRIVATE_KEY_TYPE(name,value) PRIVATE_KEY_TYPE_ ## name = value,
+#include "net/android/private_key_type_list.h"
+#undef DEFINE_PRIVATE_KEY_TYPE
+};
+
+// Returns the modulus of a given RSAPrivateKey platform object,
+// as a series of bytes, in big-endian representation. This can be
+// used with BN_bin2bn() to convert to an OpenSSL BIGNUM.
+//
+// |private_key| is a JNI reference for the private key.
+// |modulus| will receive the modulus bytes on success.
+// Returns true on success, or false on failure (e.g. if the key
+// is not RSA).
+NET_EXPORT bool GetRSAKeyModulus(jobject private_key,
+ std::vector<uint8>* modulus);
+
+// Returns the Q parameter of a given DSAPrivateKey platform object,
+// as a series of bytes, in big-endian representation. This can be used
+// with BN_bin2bn() to convert to an OpenSSL BIGNUM.
+// |private_key| is a JNI reference for the private key.
+// |q| will receive the result bytes on success.
+// Returns true on success, or false on failure (e.g. if the key is
+// not DSA).
+NET_EXPORT bool GetDSAKeyParamQ(jobject private_key,
+ std::vector<uint8>* q);
+
+// Returns the order parameter of a given ECPrivateKey platform object,
+// as a series of bytes, in big-endian representation. This can be used
+// with BN_bin2bn() to convert to an OpenSSL BIGNUM.
+// |private_key| is a JNI reference for the private key.
+// |order| will receive the result bytes on success.
+// Returns true on success, or false on failure (e.g. if the key is
+// not EC).
+bool GetECKeyOrder(jobject private_key,
+ std::vector<uint8>* order);
+
+// Returns the encoded PKCS#8 representation of a private key.
+// This only works on Android 4.0.3 and older releases for platform keys
+// (i.e. all keys except those explicitely generated by the application).
+// |private_key| is a JNI reference for the private key.
+// |encoded| will receive the encoded data on success.
+// Returns true on success, or false on failure (e.g. on 4.0.4 or higher).
+bool GetPrivateKeyEncodedBytes(jobject private_key,
+ std::vector<uint8>* encoded);
+
+// Compute the signature of a given message, which is actually a hash,
+// using a private key. For more details, please read the comments for the
+// rawSignDigestWithPrivateKey method in AndroidKeyStore.java.
+//
+// |private_key| is a JNI reference for the private key.
+// |digest| is the input digest.
+// |signature| will receive the signature on success.
+// Returns true on success, false on failure.
+//
+NET_EXPORT bool RawSignDigestWithPrivateKey(
+ jobject private_key,
+ const base::StringPiece& digest,
+ std::vector<uint8>* signature);
+
+
+// Return the PrivateKeyType of a given private key.
+// |private_key| is a JNI reference for the private key.
+// Returns a PrivateKeyType, while will be CLIENT_CERT_INVALID_TYPE
+// on error.
+NET_EXPORT PrivateKeyType GetPrivateKeyType(jobject private_key);
+
+// Returns a handle to the system EVP_PKEY object used to back a given
+// private_key object. This must *only* be used for RSA private keys
+// on Android < 4.2. Technically, this is only guaranteed to work if
+// the system image contains a vanilla implementation of the Java
+// API frameworks based on Harmony + OpenSSL.
+//
+// |private_key| is a JNI reference for the private key.
+// Returns an EVP_PKEY* handle, or NULL in case of error.
+//
+// Note: Despite its name and return type, this function doesn't know
+// anything about OpenSSL, it just type-casts a system pointer that
+// is passed as an int through JNI. As such, it never increments
+// the returned key's reference count.
+EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key);
+
+// Register JNI methods
+NET_EXPORT bool RegisterKeyStore(JNIEnv* env);
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_KEYSTORE_H
diff --git a/chromium/net/android/keystore_openssl.cc b/chromium/net/android/keystore_openssl.cc
new file mode 100644
index 00000000000..cc463f49539
--- /dev/null
+++ b/chromium/net/android/keystore_openssl.cc
@@ -0,0 +1,699 @@
+// Copyright (c) 2013 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/android/keystore_openssl.h"
+
+#include <jni.h>
+#include <openssl/bn.h>
+// This include is required to get the ECDSA_METHOD structure definition
+// which isn't currently part of the OpenSSL official ABI. This should
+// not be a concern for Chromium which always links against its own
+// version of the library on Android.
+#include <openssl/crypto/ecdsa/ecs_locl.h>
+// And this one is needed for the EC_GROUP definition.
+#include <openssl/crypto/ec/ec_lcl.h>
+#include <openssl/dsa.h>
+#include <openssl/ec.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+
+#include "base/android/build_info.h"
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "crypto/openssl_util.h"
+#include "net/android/keystore.h"
+#include "net/ssl/ssl_client_cert_type.h"
+
+// IMPORTANT NOTE: The following code will currently only work when used
+// to implement client certificate support with OpenSSL. That's because
+// only the signing operations used in this use case are implemented here.
+//
+// Generally speaking, OpenSSL provides many different ways to sign
+// digests. This code doesn't support all these cases, only the ones that
+// are required to sign the MAC during the OpenSSL handshake for TLS < 1.2.
+//
+// The OpenSSL EVP_PKEY type is a generic wrapper around key pairs.
+// Internally, it can hold a pointer to a RSA, DSA or ECDSA structure,
+// which model keypair implementations of each respective crypto
+// algorithm.
+//
+// The RSA type has a 'method' field pointer to a vtable-like structure
+// called a RSA_METHOD. This contains several function pointers that
+// correspond to operations on RSA keys (e.g. decode/encode with public
+// key, decode/encode with private key, signing, validation), as well as
+// a few flags.
+//
+// For example, the RSA_sign() function will call "method->rsa_sign()" if
+// method->rsa_sign is not NULL, otherwise, it will perform a regular
+// signing operation using the other fields in the RSA structure (which
+// are used to hold the typical modulus / exponent / parameters for the
+// key pair).
+//
+// This source file thus defines a custom RSA_METHOD structure, which
+// fields points to static methods used to implement the corresponding
+// RSA operation using platform Android APIs.
+//
+// However, the platform APIs require a jobject JNI reference to work.
+// It must be stored in the RSA instance, or made accessible when the
+// custom RSA methods are called. This is done by using RSA_set_app_data()
+// and RSA_get_app_data().
+//
+// One can thus _directly_ create a new EVP_PKEY that uses a custom RSA
+// object with the following:
+//
+// RSA* rsa = RSA_new()
+// RSA_set_method(&custom_rsa_method);
+// RSA_set_app_data(rsa, jni_private_key);
+//
+// EVP_PKEY* pkey = EVP_PKEY_new();
+// EVP_PKEY_assign_RSA(pkey, rsa);
+//
+// Note that because EVP_PKEY_assign_RSA() is used, instead of
+// EVP_PKEY_set1_RSA(), the new EVP_PKEY now owns the RSA object, and
+// will destroy it when it is itself destroyed.
+//
+// Unfortunately, such objects cannot be used with RSA_size(), which
+// totally ignores the RSA_METHOD pointers. Instead, it is necessary
+// to manually setup the modulus field (n) in the RSA object, with a
+// value that matches the wrapped PrivateKey object. See GetRsaPkeyWrapper
+// for full details.
+//
+// Similarly, custom DSA_METHOD and ECDSA_METHOD are defined by this source
+// file, and appropriate field setups are performed to ensure that
+// DSA_size() and ECDSA_size() work properly with the wrapper EVP_PKEY.
+//
+// Note that there is no need to define an OpenSSL ENGINE here. These
+// are objects that can be used to expose custom methods (i.e. either
+// RSA_METHOD, DSA_METHOD, ECDSA_METHOD, and a large number of other ones
+// for types not related to this source file), and make them used by
+// default for a lot of operations. Very fortunately, this is not needed
+// here, which saves a lot of complexity.
+
+using base::android::ScopedJavaGlobalRef;
+
+namespace net {
+namespace android {
+
+namespace {
+
+typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY;
+typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA;
+typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA;
+typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY;
+typedef crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> ScopedEC_GROUP;
+
+// Custom RSA_METHOD that uses the platform APIs.
+// Note that for now, only signing through RSA_sign() is really supported.
+// all other method pointers are either stubs returning errors, or no-ops.
+// See <openssl/rsa.h> for exact declaration of RSA_METHOD.
+
+int RsaMethodPubEnc(int flen,
+ const unsigned char* from,
+ unsigned char* to,
+ RSA* rsa,
+ int padding) {
+ NOTIMPLEMENTED();
+ RSAerr(RSA_F_RSA_PUBLIC_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+ return -1;
+}
+
+int RsaMethodPubDec(int flen,
+ const unsigned char* from,
+ unsigned char* to,
+ RSA* rsa,
+ int padding) {
+ NOTIMPLEMENTED();
+ RSAerr(RSA_F_RSA_PUBLIC_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+ return -1;
+}
+
+int RsaMethodPrivEnc(int flen,
+ const unsigned char *from,
+ unsigned char *to,
+ RSA *rsa,
+ int padding) {
+ NOTIMPLEMENTED();
+ RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+ return -1;
+}
+
+int RsaMethodPrivDec(int flen,
+ const unsigned char* from,
+ unsigned char* to,
+ RSA* rsa,
+ int padding) {
+ NOTIMPLEMENTED();
+ RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+ return -1;
+}
+
+int RsaMethodInit(RSA* rsa) {
+ // Required to ensure that RsaMethodSign will be called.
+ rsa->flags |= RSA_FLAG_SIGN_VER;
+ return 0;
+}
+
+int RsaMethodFinish(RSA* rsa) {
+ // Ensure the global JNI reference created with this wrapper is
+ // properly destroyed with it.
+ jobject key = reinterpret_cast<jobject>(RSA_get_app_data(rsa));
+ if (key != NULL) {
+ RSA_set_app_data(rsa, NULL);
+ JNIEnv* env = base::android::AttachCurrentThread();
+ env->DeleteGlobalRef(key);
+ }
+ // Actual return value is ignored by OpenSSL. There are no docs
+ // explaining what this is supposed to be.
+ return 0;
+}
+
+int RsaMethodSign(int type,
+ const unsigned char* message,
+ unsigned int message_len,
+ unsigned char* signature,
+ unsigned int* signature_len,
+ const RSA* rsa) {
+ // This is only used for client certificate support, which
+ // will always pass the NID_md5_sha1 |type| value.
+ DCHECK_EQ(NID_md5_sha1, type);
+ if (type != NID_md5_sha1) {
+ RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE);
+ return 0;
+ }
+ // Retrieve private key JNI reference.
+ jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa));
+ if (!private_key) {
+ LOG(WARNING) << "Null JNI reference passed to RsaMethodSign!";
+ return 0;
+ }
+ // Sign message with it through JNI.
+ base::StringPiece message_piece(reinterpret_cast<const char*>(message),
+ static_cast<size_t>(message_len));
+ std::vector<uint8> result;
+
+ if (!RawSignDigestWithPrivateKey(
+ private_key, message_piece, &result)) {
+ LOG(WARNING) << "Could not sign message in RsaMethodSign!";
+ return 0;
+ }
+
+ size_t expected_size = static_cast<size_t>(RSA_size(rsa));
+ if (result.size() > expected_size) {
+ LOG(ERROR) << "RSA Signature size mismatch, actual: "
+ << result.size() << ", expected <= " << expected_size;
+ return 0;
+ }
+
+ // Copy result to OpenSSL-provided buffer
+ memcpy(signature, &result[0], result.size());
+ *signature_len = static_cast<unsigned int>(result.size());
+ return 1;
+}
+
+const RSA_METHOD android_rsa_method = {
+ /* .name = */ "Android signing-only RSA method",
+ /* .rsa_pub_enc = */ RsaMethodPubEnc,
+ /* .rsa_pub_dec = */ RsaMethodPubDec,
+ /* .rsa_priv_enc = */ RsaMethodPrivEnc,
+ /* .rsa_priv_dec = */ RsaMethodPrivDec,
+ /* .rsa_mod_exp = */ NULL,
+ /* .bn_mod_exp = */ NULL,
+ /* .init = */ RsaMethodInit,
+ /* .finish = */ RsaMethodFinish,
+ // This flag is necessary to tell OpenSSL to avoid checking the content
+ // (i.e. internal fields) of the private key. Otherwise, it will complain
+ // it's not valid for the certificate.
+ /* .flags = */ RSA_METHOD_FLAG_NO_CHECK,
+ /* .app_data = */ NULL,
+ /* .rsa_sign = */ RsaMethodSign,
+ /* .rsa_verify = */ NULL,
+ /* .rsa_keygen = */ NULL,
+};
+
+// Copy the contents of an encoded big integer into an existing BIGNUM.
+// This function modifies |*num| in-place.
+// |new_bytes| is the byte encoding of the new value.
+// |num| points to the BIGNUM which will be assigned with the new value.
+// Returns true on success, false otherwise. On failure, |*num| is
+// not modified.
+bool CopyBigNumFromBytes(const std::vector<uint8>& new_bytes,
+ BIGNUM* num) {
+ BIGNUM* ret = BN_bin2bn(
+ reinterpret_cast<const unsigned char*>(&new_bytes[0]),
+ static_cast<int>(new_bytes.size()),
+ num);
+ return (ret != NULL);
+}
+
+// Decode the contents of an encoded big integer and either create a new
+// BIGNUM object (if |*num_ptr| is NULL on input) or copy it (if
+// |*num_ptr| is not NULL).
+// |new_bytes| is the byte encoding of the new value.
+// |num_ptr| is the address of a BIGNUM pointer. |*num_ptr| can be NULL.
+// Returns true on success, false otherwise. On failure, |*num_ptr| is
+// not modified. On success, |*num_ptr| will always be non-NULL and
+// point to a valid BIGNUM object.
+bool SwapBigNumPtrFromBytes(const std::vector<uint8>& new_bytes,
+ BIGNUM** num_ptr) {
+ BIGNUM* old_num = *num_ptr;
+ BIGNUM* new_num = BN_bin2bn(
+ reinterpret_cast<const unsigned char*>(&new_bytes[0]),
+ static_cast<int>(new_bytes.size()),
+ old_num);
+ if (new_num == NULL)
+ return false;
+
+ if (old_num == NULL)
+ *num_ptr = new_num;
+ return true;
+}
+
+// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object.
+// |private_key| is the JNI reference (local or global) to the object.
+// |pkey| is the EVP_PKEY to setup as a wrapper.
+// Returns true on success, false otherwise.
+// On success, this creates a new global JNI reference to the object
+// that is owned by and destroyed with the EVP_PKEY. I.e. caller can
+// free |private_key| after the call.
+// IMPORTANT: The EVP_PKEY will *only* work on Android >= 4.2. For older
+// platforms, use GetRsaLegacyKey() instead.
+bool GetRsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) {
+ ScopedRSA rsa(RSA_new());
+ RSA_set_method(rsa.get(), &android_rsa_method);
+
+ // HACK: RSA_size() doesn't work with custom RSA_METHODs. To ensure that
+ // it will return the right value, set the 'n' field of the RSA object
+ // to match the private key's modulus.
+ std::vector<uint8> modulus;
+ if (!GetRSAKeyModulus(private_key, &modulus)) {
+ LOG(ERROR) << "Failed to get private key modulus";
+ return false;
+ }
+ if (!SwapBigNumPtrFromBytes(modulus, &rsa.get()->n)) {
+ LOG(ERROR) << "Failed to decode private key modulus";
+ return false;
+ }
+
+ ScopedJavaGlobalRef<jobject> global_key;
+ global_key.Reset(NULL, private_key);
+ if (global_key.is_null()) {
+ LOG(ERROR) << "Could not create global JNI reference";
+ return false;
+ }
+ RSA_set_app_data(rsa.get(), global_key.Release());
+ EVP_PKEY_assign_RSA(pkey, rsa.release());
+ return true;
+}
+
+// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object
+// for Android 4.0 to 4.1.x. Must only be used on Android < 4.2.
+// |private_key| is a JNI reference (local or global) to the object.
+// |pkey| is the EVP_PKEY to setup as a wrapper.
+// Returns true on success, false otherwise.
+EVP_PKEY* GetRsaLegacyKey(jobject private_key) {
+ EVP_PKEY* sys_pkey =
+ GetOpenSSLSystemHandleForPrivateKey(private_key);
+ if (sys_pkey != NULL) {
+ CRYPTO_add(&sys_pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
+ } else {
+ // GetOpenSSLSystemHandleForPrivateKey() will fail on Android
+ // 4.0.3 and earlier. However, it is possible to get the key
+ // content with PrivateKey.getEncoded() on these platforms.
+ // Note that this method may return NULL on 4.0.4 and later.
+ std::vector<uint8> encoded;
+ if (!GetPrivateKeyEncodedBytes(private_key, &encoded)) {
+ LOG(ERROR) << "Can't get private key data!";
+ return NULL;
+ }
+ const unsigned char* p =
+ reinterpret_cast<const unsigned char*>(&encoded[0]);
+ int len = static_cast<int>(encoded.size());
+ sys_pkey = d2i_AutoPrivateKey(NULL, &p, len);
+ if (sys_pkey == NULL) {
+ LOG(ERROR) << "Can't convert private key data!";
+ return NULL;
+ }
+ }
+ return sys_pkey;
+}
+
+// Custom DSA_METHOD that uses the platform APIs.
+// Note that for now, only signing through DSA_sign() is really supported.
+// all other method pointers are either stubs returning errors, or no-ops.
+// See <openssl/dsa.h> for exact declaration of DSA_METHOD.
+//
+// Note: There is no DSA_set_app_data() and DSA_get_app_data() functions,
+// but RSA_set_app_data() is defined as a simple macro that calls
+// RSA_set_ex_data() with a hard-coded index of 0, so this code
+// does the same thing here.
+
+DSA_SIG* DsaMethodDoSign(const unsigned char* dgst,
+ int dlen,
+ DSA* dsa) {
+ // Extract the JNI reference to the PrivateKey object.
+ jobject private_key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa, 0));
+ if (private_key == NULL)
+ return NULL;
+
+ // Sign the message with it, calling platform APIs.
+ std::vector<uint8> signature;
+ if (!RawSignDigestWithPrivateKey(
+ private_key,
+ base::StringPiece(
+ reinterpret_cast<const char*>(dgst),
+ static_cast<size_t>(dlen)),
+ &signature)) {
+ return NULL;
+ }
+
+ // Note: With DSA, the actual signature might be smaller than DSA_size().
+ size_t max_expected_size = static_cast<size_t>(DSA_size(dsa));
+ if (signature.size() > max_expected_size) {
+ LOG(ERROR) << "DSA Signature size mismatch, actual: "
+ << signature.size() << ", expected <= "
+ << max_expected_size;
+ return NULL;
+ }
+
+ // Convert the signature into a DSA_SIG object.
+ const unsigned char* sigbuf =
+ reinterpret_cast<const unsigned char*>(&signature[0]);
+ int siglen = static_cast<size_t>(signature.size());
+ DSA_SIG* dsa_sig = d2i_DSA_SIG(NULL, &sigbuf, siglen);
+ return dsa_sig;
+}
+
+int DsaMethodSignSetup(DSA* dsa,
+ BN_CTX* ctx_in,
+ BIGNUM** kinvp,
+ BIGNUM** rp) {
+ NOTIMPLEMENTED();
+ DSAerr(DSA_F_DSA_SIGN_SETUP, DSA_R_INVALID_DIGEST_TYPE);
+ return -1;
+}
+
+int DsaMethodDoVerify(const unsigned char* dgst,
+ int dgst_len,
+ DSA_SIG* sig,
+ DSA* dsa) {
+ NOTIMPLEMENTED();
+ DSAerr(DSA_F_DSA_DO_VERIFY, DSA_R_INVALID_DIGEST_TYPE);
+ return -1;
+}
+
+int DsaMethodFinish(DSA* dsa) {
+ // Free the global JNI reference that was created with this
+ // wrapper key.
+ jobject key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa,0));
+ if (key != NULL) {
+ DSA_set_ex_data(dsa, 0, NULL);
+ JNIEnv* env = base::android::AttachCurrentThread();
+ env->DeleteGlobalRef(key);
+ }
+ // Actual return value is ignored by OpenSSL. There are no docs
+ // explaining what this is supposed to be.
+ return 0;
+}
+
+const DSA_METHOD android_dsa_method = {
+ /* .name = */ "Android signing-only DSA method",
+ /* .dsa_do_sign = */ DsaMethodDoSign,
+ /* .dsa_sign_setup = */ DsaMethodSignSetup,
+ /* .dsa_do_verify = */ DsaMethodDoVerify,
+ /* .dsa_mod_exp = */ NULL,
+ /* .bn_mod_exp = */ NULL,
+ /* .init = */ NULL, // nothing to do here.
+ /* .finish = */ DsaMethodFinish,
+ /* .flags = */ 0,
+ /* .app_data = */ NULL,
+ /* .dsa_paramgem = */ NULL,
+ /* .dsa_keygen = */ NULL
+};
+
+// Setup an EVP_PKEY to wrap an existing DSA platform PrivateKey object.
+// |private_key| is a JNI reference (local or global) to the object.
+// |pkey| is the EVP_PKEY to setup as a wrapper.
+// Returns true on success, false otherwise.
+// On success, this creates a global JNI reference to the same object
+// that will be owned by and destroyed with the EVP_PKEY.
+bool GetDsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) {
+ ScopedDSA dsa(DSA_new());
+ DSA_set_method(dsa.get(), &android_dsa_method);
+
+ // DSA_size() doesn't work with custom DSA_METHODs. To ensure it
+ // returns the right value, set the 'q' field in the DSA object to
+ // match the parameter from the platform key.
+ std::vector<uint8> q;
+ if (!GetDSAKeyParamQ(private_key, &q)) {
+ LOG(ERROR) << "Can't extract Q parameter from DSA private key";
+ return false;
+ }
+ if (!SwapBigNumPtrFromBytes(q, &dsa.get()->q)) {
+ LOG(ERROR) << "Can't decode Q parameter from DSA private key";
+ return false;
+ }
+
+ ScopedJavaGlobalRef<jobject> global_key;
+ global_key.Reset(NULL, private_key);
+ if (global_key.is_null()) {
+ LOG(ERROR) << "Could not create global JNI reference";
+ return false;
+ }
+ DSA_set_ex_data(dsa.get(), 0, global_key.Release());
+ EVP_PKEY_assign_DSA(pkey, dsa.release());
+ return true;
+}
+
+// Custom ECDSA_METHOD that uses the platform APIs.
+// Note that for now, only signing through ECDSA_sign() is really supported.
+// all other method pointers are either stubs returning errors, or no-ops.
+//
+// Note: The ECDSA_METHOD structure doesn't have init/finish
+// methods. As such, the only way to to ensure the global
+// JNI reference is properly released when the EVP_PKEY is
+// destroyed is to use a custom EX_DATA type.
+
+// Used to ensure that the global JNI reference associated with a custom
+// EC_KEY + ECDSA_METHOD wrapper is released when its EX_DATA is destroyed
+// (this function is called when EVP_PKEY_free() is called on the wrapper).
+void ExDataFree(void* parent,
+ void* ptr,
+ CRYPTO_EX_DATA* ad,
+ int idx,
+ long argl,
+ void* argp) {
+ jobject private_key = reinterpret_cast<jobject>(ptr);
+ if (private_key == NULL)
+ return;
+
+ CRYPTO_set_ex_data(ad, idx, NULL);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ env->DeleteGlobalRef(private_key);
+}
+
+int ExDataDup(CRYPTO_EX_DATA* to,
+ CRYPTO_EX_DATA* from,
+ void* from_d,
+ int idx,
+ long argl,
+ void* argp) {
+ // This callback shall never be called with the current OpenSSL
+ // implementation (the library only ever duplicates EX_DATA items
+ // for SSL and BIO objects). But provide this to catch regressions
+ // in the future.
+ CHECK(false) << "ExDataDup was called for ECDSA custom key !?";
+ // Return value is currently ignored by OpenSSL.
+ return 0;
+}
+
+class EcdsaExDataIndex {
+public:
+ int ex_data_index() { return ex_data_index_; }
+
+ EcdsaExDataIndex() {
+ ex_data_index_ = ECDSA_get_ex_new_index(0, // argl
+ NULL, // argp
+ NULL, // new_func
+ ExDataDup, // dup_func
+ ExDataFree); // free_func
+ }
+
+private:
+ int ex_data_index_;
+};
+
+// Returns the index of the custom EX_DATA used to store the JNI reference.
+int EcdsaGetExDataIndex(void) {
+ // Use a LazyInstance to perform thread-safe lazy initialization.
+ // Use a leaky one, since OpenSSL doesn't provide a way to release
+ // allocated EX_DATA indices.
+ static base::LazyInstance<EcdsaExDataIndex>::Leaky s_instance =
+ LAZY_INSTANCE_INITIALIZER;
+ return s_instance.Get().ex_data_index();
+}
+
+ECDSA_SIG* EcdsaMethodDoSign(const unsigned char* dgst,
+ int dgst_len,
+ const BIGNUM* inv,
+ const BIGNUM* rp,
+ EC_KEY* eckey) {
+ // Retrieve private key JNI reference.
+ jobject private_key = reinterpret_cast<jobject>(
+ ECDSA_get_ex_data(eckey, EcdsaGetExDataIndex()));
+ if (!private_key) {
+ LOG(WARNING) << "Null JNI reference passed to EcdsaMethodDoSign!";
+ return NULL;
+ }
+ // Sign message with it through JNI.
+ std::vector<uint8> signature;
+ base::StringPiece digest(
+ reinterpret_cast<const char*>(dgst),
+ static_cast<size_t>(dgst_len));
+ if (!RawSignDigestWithPrivateKey(
+ private_key, digest, &signature)) {
+ LOG(WARNING) << "Could not sign message in EcdsaMethodDoSign!";
+ return NULL;
+ }
+
+ // Note: With ECDSA, the actual signature may be smaller than
+ // ECDSA_size().
+ size_t max_expected_size = static_cast<size_t>(ECDSA_size(eckey));
+ if (signature.size() > max_expected_size) {
+ LOG(ERROR) << "ECDSA Signature size mismatch, actual: "
+ << signature.size() << ", expected <= "
+ << max_expected_size;
+ return NULL;
+ }
+
+ // Convert signature to ECDSA_SIG object
+ const unsigned char* sigbuf =
+ reinterpret_cast<const unsigned char*>(&signature[0]);
+ long siglen = static_cast<long>(signature.size());
+ return d2i_ECDSA_SIG(NULL, &sigbuf, siglen);
+}
+
+int EcdsaMethodSignSetup(EC_KEY* eckey,
+ BN_CTX* ctx,
+ BIGNUM** kinv,
+ BIGNUM** r) {
+ NOTIMPLEMENTED();
+ ECDSAerr(ECDSA_F_ECDSA_SIGN_SETUP, ECDSA_R_ERR_EC_LIB);
+ return -1;
+}
+
+int EcdsaMethodDoVerify(const unsigned char* dgst,
+ int dgst_len,
+ const ECDSA_SIG* sig,
+ EC_KEY* eckey) {
+ NOTIMPLEMENTED();
+ ECDSAerr(ECDSA_F_ECDSA_DO_VERIFY, ECDSA_R_ERR_EC_LIB);
+ return -1;
+}
+
+const ECDSA_METHOD android_ecdsa_method = {
+ /* .name = */ "Android signing-only ECDSA method",
+ /* .ecdsa_do_sign = */ EcdsaMethodDoSign,
+ /* .ecdsa_sign_setup = */ EcdsaMethodSignSetup,
+ /* .ecdsa_do_verify = */ EcdsaMethodDoVerify,
+ /* .flags = */ 0,
+ /* .app_data = */ NULL,
+};
+
+// Setup an EVP_PKEY to wrap an existing platform PrivateKey object.
+// |private_key| is the JNI reference (local or global) to the object.
+// |pkey| is the EVP_PKEY to setup as a wrapper.
+// Returns true on success, false otherwise.
+// On success, this creates a global JNI reference to the object that
+// is owned by and destroyed with the EVP_PKEY. I.e. the caller shall
+// always free |private_key| after the call.
+bool GetEcdsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) {
+ ScopedEC_KEY eckey(EC_KEY_new());
+ ECDSA_set_method(eckey.get(), &android_ecdsa_method);
+
+ // To ensure that ECDSA_size() works properly, craft a custom EC_GROUP
+ // that has the same order than the private key.
+ std::vector<uint8> order;
+ if (!GetECKeyOrder(private_key, &order)) {
+ LOG(ERROR) << "Can't extract order parameter from EC private key";
+ return false;
+ }
+ ScopedEC_GROUP group(EC_GROUP_new(EC_GFp_nist_method()));
+ if (!group.get()) {
+ LOG(ERROR) << "Can't create new EC_GROUP";
+ return false;
+ }
+ if (!CopyBigNumFromBytes(order, &group.get()->order)) {
+ LOG(ERROR) << "Can't decode order from PrivateKey";
+ return false;
+ }
+ EC_KEY_set_group(eckey.get(), group.release());
+
+ ScopedJavaGlobalRef<jobject> global_key;
+ global_key.Reset(NULL, private_key);
+ if (global_key.is_null()) {
+ LOG(ERROR) << "Can't create global JNI reference";
+ return false;
+ }
+ ECDSA_set_ex_data(eckey.get(),
+ EcdsaGetExDataIndex(),
+ global_key.Release());
+
+ EVP_PKEY_assign_EC_KEY(pkey, eckey.release());
+ return true;
+}
+
+} // namespace
+
+EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key) {
+ // Create new empty EVP_PKEY instance.
+ ScopedEVP_PKEY pkey(EVP_PKEY_new());
+ if (!pkey.get())
+ return NULL;
+
+ // Create sub key type, depending on private key's algorithm type.
+ PrivateKeyType key_type = GetPrivateKeyType(private_key);
+ switch (key_type) {
+ case PRIVATE_KEY_TYPE_RSA:
+ {
+ // Route around platform bug: if Android < 4.2, then
+ // base::android::RawSignDigestWithPrivateKey() cannot work, so
+ // instead, obtain a raw EVP_PKEY* to the system object
+ // backing this PrivateKey object.
+ const int kAndroid42ApiLevel = 17;
+ if (base::android::BuildInfo::GetInstance()->sdk_int() <
+ kAndroid42ApiLevel) {
+ EVP_PKEY* legacy_key = GetRsaLegacyKey(private_key);
+ if (legacy_key == NULL)
+ return NULL;
+ pkey.reset(legacy_key);
+ } else {
+ // Running on Android 4.2.
+ if (!GetRsaPkeyWrapper(private_key, pkey.get()))
+ return NULL;
+ }
+ }
+ break;
+ case PRIVATE_KEY_TYPE_DSA:
+ if (!GetDsaPkeyWrapper(private_key, pkey.get()))
+ return NULL;
+ break;
+ case PRIVATE_KEY_TYPE_ECDSA:
+ if (!GetEcdsaPkeyWrapper(private_key, pkey.get()))
+ return NULL;
+ break;
+ default:
+ LOG(WARNING)
+ << "GetOpenSSLPrivateKeyWrapper() called with invalid key type";
+ return NULL;
+ }
+ return pkey.release();
+}
+
+} // namespace android
+} // namespace net
diff --git a/chromium/net/android/keystore_openssl.h b/chromium/net/android/keystore_openssl.h
new file mode 100644
index 00000000000..ceb900c4be6
--- /dev/null
+++ b/chromium/net/android/keystore_openssl.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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_ANDROID_KEYSTORE_OPENSSL_H
+#define NET_ANDROID_KEYSTORE_OPENSSL_H
+
+#include <jni.h>
+#include <openssl/evp.h>
+
+#include "net/base/net_export.h"
+
+// OpenSSL-specific functions to use the Android platform keystore.
+// The features provided here are highly specific to OpenSSL and are
+// segregated from net/android/keystore.h because the latter only provides
+// simply JNI stubs to call Java code which only uses platform APIs.
+
+namespace net {
+namespace android {
+
+// Create a custom OpenSSL EVP_PKEY instance that wraps a platform
+// java.security.PrivateKey object, and will call the platform APIs
+// through JNI to implement signing (and only signing).
+//
+// This method can be called from any thread. It shall only be used
+// to implement client certificate handling though.
+//
+// |private_key| is a JNI local (or global) reference to the Java
+// PrivateKey object.
+//
+// Returns a new EVP_PKEY* object with the following features:
+//
+// - Only contains a private key.
+//
+// - Owns its own _global_ JNI reference to the object. This means the
+// caller can free |private_key| safely after the call, and that the
+// the returned EVP_PKEY instance can be used from any thread.
+//
+// - Uses a custom method to implement the minimum functions required to
+// *sign* the digest that is part of the "Verify Certificate" message
+// during the OpenSSL handshake. Anything else will result in undefined
+// behaviour.
+NET_EXPORT EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key);
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_KEYSTORE_OPENSSL_H
diff --git a/chromium/net/android/keystore_unittest.cc b/chromium/net/android/keystore_unittest.cc
new file mode 100644
index 00000000000..98944e29bd0
--- /dev/null
+++ b/chromium/net/android/keystore_unittest.cc
@@ -0,0 +1,725 @@
+// Copyright (c) 2013 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 <openssl/bn.h>
+#include <openssl/dsa.h>
+#include <openssl/ecdsa.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#include "base/android/build_info.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_handle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "crypto/openssl_util.h"
+#include "jni/AndroidKeyStoreTestUtil_jni.h"
+#include "net/android/keystore.h"
+#include "net/android/keystore_openssl.h"
+#include "net/base/test_data_directory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Technical note:
+//
+// This source file not only checks that signing with
+// RawSignDigestWithPrivateKey() works correctly, it also verifies that
+// the generated signature matches 100% of what OpenSSL generates when
+// calling RSA_sign(NID_md5_sha1,...), DSA_sign(0, ...) or
+// ECDSA_sign(0, ...).
+//
+// That's crucial to ensure that this function can later be used to
+// implement client certificate support. More specifically, that it is
+// possible to create a custom EVP_PKEY that uses
+// RawSignDigestWithPrivateKey() internally to perform RSA/DSA/ECDSA
+// signing, as invoked by the OpenSSL code at
+// openssl/ssl/s3_clnt.c:ssl3_send_client_verify().
+//
+// For more details, read the comments in AndroidKeyStore.java.
+//
+// Finally, it also checks that using the EVP_PKEY generated with
+// GetOpenSSLPrivateKeyWrapper() works correctly.
+
+namespace net {
+namespace android {
+
+namespace {
+
+typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY;
+typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA;
+typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA;
+typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY;
+typedef crypto::ScopedOpenSSL<BIGNUM, BN_free> ScopedBIGNUM;
+
+typedef crypto::ScopedOpenSSL<
+ PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>
+ ScopedPKCS8_PRIV_KEY_INFO;
+
+typedef base::android::ScopedJavaLocalRef<jobject> ScopedJava;
+
+JNIEnv* InitEnv() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ static bool inited = false;
+ if (!inited) {
+ RegisterNativesImpl(env);
+ inited = true;
+ }
+ return env;
+}
+
+// Returns true if running on an Android version older than 4.2
+bool IsOnAndroidOlderThan_4_2(void) {
+ const int kAndroid42ApiLevel = 17;
+ int level = base::android::BuildInfo::GetInstance()->sdk_int();
+ return level < kAndroid42ApiLevel;
+}
+
+// Implements the callback expected by ERR_print_errors_cb().
+// used by GetOpenSSLErrorString below.
+int openssl_print_error_callback(const char* msg, size_t msglen, void* u) {
+ std::string* result = reinterpret_cast<std::string*>(u);
+ result->append(msg, msglen);
+ return 1;
+}
+
+// Retrieves the OpenSSL error as a string
+std::string GetOpenSSLErrorString(void) {
+ std::string result;
+ ERR_print_errors_cb(openssl_print_error_callback, &result);
+ return result;
+}
+
+// Resize a string to |size| bytes of data, then return its data buffer
+// address cast as an 'unsigned char*', as expected by OpenSSL functions.
+// |str| the target string.
+// |size| the number of bytes to write into the string.
+// Return the string's new buffer in memory, as an 'unsigned char*'
+// pointer.
+unsigned char* OpenSSLWriteInto(std::string* str, size_t size) {
+ return reinterpret_cast<unsigned char*>(WriteInto(str, size + 1));
+}
+
+// Load a given private key file into an EVP_PKEY.
+// |filename| is the key file path.
+// Returns a new EVP_PKEY on success, NULL on failure.
+EVP_PKEY* ImportPrivateKeyFile(const char* filename) {
+ // Load file in memory.
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ base::FilePath file_path = certs_dir.AppendASCII(filename);
+ ScopedStdioHandle handle(
+ file_util::OpenFile(file_path, "rb"));
+ if (!handle.get()) {
+ LOG(ERROR) << "Could not open private key file: " << filename;
+ return NULL;
+ }
+ // Assume it is PEM_encoded. Load it as an EVP_PKEY.
+ EVP_PKEY* pkey = PEM_read_PrivateKey(handle.get(), NULL, NULL, NULL);
+ if (!pkey) {
+ LOG(ERROR) << "Could not load public key file: " << filename
+ << ", " << GetOpenSSLErrorString();
+ return NULL;
+ }
+ return pkey;
+}
+
+// Convert a private key into its PKCS#8 encoded representation.
+// |pkey| is the EVP_PKEY handle for the private key.
+// |pkcs8| will receive the PKCS#8 bytes.
+// Returns true on success, false otherwise.
+bool GetPrivateKeyPkcs8Bytes(const ScopedEVP_PKEY& pkey,
+ std::string* pkcs8) {
+ // Convert to PKCS#8 object.
+ ScopedPKCS8_PRIV_KEY_INFO p8_info(EVP_PKEY2PKCS8(pkey.get()));
+ if (!p8_info.get()) {
+ LOG(ERROR) << "Can't get PKCS#8 private key from EVP_PKEY: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+
+ // Then convert it
+ int len = i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), NULL);
+ unsigned char* p = OpenSSLWriteInto(pkcs8, static_cast<size_t>(len));
+ i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), &p);
+ return true;
+}
+
+bool ImportPrivateKeyFileAsPkcs8(const char* filename,
+ std::string* pkcs8) {
+ ScopedEVP_PKEY pkey(ImportPrivateKeyFile(filename));
+ if (!pkey.get())
+ return false;
+ return GetPrivateKeyPkcs8Bytes(pkey, pkcs8);
+}
+
+// Same as ImportPrivateKey, but for public ones.
+EVP_PKEY* ImportPublicKeyFile(const char* filename) {
+ // Load file as PEM data.
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ base::FilePath file_path = certs_dir.AppendASCII(filename);
+ ScopedStdioHandle handle(file_util::OpenFile(file_path, "rb"));
+ if (!handle.get()) {
+ LOG(ERROR) << "Could not open public key file: " << filename;
+ return NULL;
+ }
+ EVP_PKEY* pkey = PEM_read_PUBKEY(handle.get(), NULL, NULL, NULL);
+ if (!pkey) {
+ LOG(ERROR) << "Could not load public key file: " << filename
+ << ", " << GetOpenSSLErrorString();
+ return NULL;
+ }
+ return pkey;
+}
+
+// Retrieve a JNI local ref from encoded PKCS#8 data.
+ScopedJava GetPKCS8PrivateKeyJava(PrivateKeyType key_type,
+ const std::string& pkcs8_key) {
+ JNIEnv* env = InitEnv();
+ base::android::ScopedJavaLocalRef<jbyteArray> bytes(
+ base::android::ToJavaByteArray(
+ env,
+ reinterpret_cast<const uint8*>(pkcs8_key.data()),
+ pkcs8_key.size()));
+
+ ScopedJava key(
+ Java_AndroidKeyStoreTestUtil_createPrivateKeyFromPKCS8(
+ env, key_type, bytes.obj()));
+
+ return key;
+}
+
+const char kTestRsaKeyFile[] = "android-test-key-rsa.pem";
+
+// The RSA test hash must be 36 bytes exactly.
+const char kTestRsaHash[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+// Retrieve a JNI local ref for our test RSA key.
+ScopedJava GetRSATestKeyJava() {
+ std::string key;
+ if (!ImportPrivateKeyFileAsPkcs8(kTestRsaKeyFile, &key))
+ return ScopedJava();
+ return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA, key);
+}
+
+const char kTestDsaKeyFile[] = "android-test-key-dsa.pem";
+const char kTestDsaPublicKeyFile[] = "android-test-key-dsa-public.pem";
+
+// The DSA test hash must be 20 bytes exactly.
+const char kTestDsaHash[] = "0123456789ABCDEFGHIJ";
+
+// Retrieve a JNI local ref for our test DSA key.
+ScopedJava GetDSATestKeyJava() {
+ std::string key;
+ if (!ImportPrivateKeyFileAsPkcs8(kTestDsaKeyFile, &key))
+ return ScopedJava();
+ return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA, key);
+}
+
+// Call this function to verify that one message signed with our
+// test DSA private key is correct. Since DSA signing introduces
+// random elements in the signature, it is not possible to compare
+// signature bits directly. However, one can use the public key
+// to do the check.
+bool VerifyTestDSASignature(const base::StringPiece& message,
+ const base::StringPiece& signature) {
+ ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestDsaPublicKeyFile));
+ if (!pkey.get())
+ return false;
+
+ ScopedDSA pub_key(EVP_PKEY_get1_DSA(pkey.get()));
+ if (!pub_key.get()) {
+ LOG(ERROR) << "Could not get DSA public key: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+
+ const unsigned char* digest =
+ reinterpret_cast<const unsigned char*>(message.data());
+ int digest_len = static_cast<int>(message.size());
+ const unsigned char* sigbuf =
+ reinterpret_cast<const unsigned char*>(signature.data());
+ int siglen = static_cast<int>(signature.size());
+
+ int ret = DSA_verify(
+ 0, digest, digest_len, sigbuf, siglen, pub_key.get());
+ if (ret != 1) {
+ LOG(ERROR) << "DSA_verify() failed: " << GetOpenSSLErrorString();
+ return false;
+ }
+ return true;
+}
+
+const char kTestEcdsaKeyFile[] = "android-test-key-ecdsa.pem";
+const char kTestEcdsaPublicKeyFile[] = "android-test-key-ecdsa-public.pem";
+
+// The test hash for ECDSA keys must be 20 bytes exactly.
+const char kTestEcdsaHash[] = "0123456789ABCDEFGHIJ";
+
+// Retrieve a JNI local ref for our test ECDSA key.
+ScopedJava GetECDSATestKeyJava() {
+ std::string key;
+ if (!ImportPrivateKeyFileAsPkcs8(kTestEcdsaKeyFile, &key))
+ return ScopedJava();
+ return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_ECDSA, key);
+}
+
+// Call this function to verify that one message signed with our
+// test DSA private key is correct. Since DSA signing introduces
+// random elements in the signature, it is not possible to compare
+// signature bits directly. However, one can use the public key
+// to do the check.
+bool VerifyTestECDSASignature(const base::StringPiece& message,
+ const base::StringPiece& signature) {
+ ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestEcdsaPublicKeyFile));
+ if (!pkey.get())
+ return false;
+ ScopedEC_KEY pub_key(EVP_PKEY_get1_EC_KEY(pkey.get()));
+ if (!pub_key.get()) {
+ LOG(ERROR) << "Could not get ECDSA public key: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+
+ const unsigned char* digest =
+ reinterpret_cast<const unsigned char*>(message.data());
+ int digest_len = static_cast<int>(message.size());
+ const unsigned char* sigbuf =
+ reinterpret_cast<const unsigned char*>(signature.data());
+ int siglen = static_cast<int>(signature.size());
+
+ int ret = ECDSA_verify(
+ 0, digest, digest_len, sigbuf, siglen, pub_key.get());
+ if (ret != 1) {
+ LOG(ERROR) << "ECDSA_verify() failed: " << GetOpenSSLErrorString();
+ return false;
+ }
+ return true;
+}
+
+// Sign a message with OpenSSL, return the result as a string.
+// |message| is the message to be signed.
+// |openssl_key| is an OpenSSL EVP_PKEY to use.
+// |result| receives the result.
+// Returns true on success, false otherwise.
+bool SignWithOpenSSL(const base::StringPiece& message,
+ EVP_PKEY* openssl_key,
+ std::string* result) {
+ const unsigned char* digest =
+ reinterpret_cast<const unsigned char*>(message.data());
+ unsigned int digest_len = static_cast<unsigned int>(message.size());
+ std::string signature;
+ size_t signature_size;
+ size_t max_signature_size;
+ int key_type = EVP_PKEY_id(openssl_key);
+ switch (key_type) {
+ case EVP_PKEY_RSA:
+ {
+ ScopedRSA rsa(EVP_PKEY_get1_RSA(openssl_key));
+ if (!rsa.get()) {
+ LOG(ERROR) << "Could not get RSA from EVP_PKEY: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+ // With RSA, the signature will always be RSA_size() bytes.
+ max_signature_size = static_cast<size_t>(RSA_size(rsa.get()));
+ unsigned char* p = OpenSSLWriteInto(&signature,
+ max_signature_size);
+ unsigned int p_len = 0;
+ int ret = RSA_sign(
+ NID_md5_sha1, digest, digest_len, p, &p_len, rsa.get());
+ if (ret != 1) {
+ LOG(ERROR) << "RSA_sign() failed: " << GetOpenSSLErrorString();
+ return false;
+ }
+ signature_size = static_cast<size_t>(p_len);
+ break;
+ }
+ case EVP_PKEY_DSA:
+ {
+ ScopedDSA dsa(EVP_PKEY_get1_DSA(openssl_key));
+ if (!dsa.get()) {
+ LOG(ERROR) << "Could not get DSA from EVP_PKEY: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+ // Note, the actual signature can be smaller than DSA_size()
+ max_signature_size = static_cast<size_t>(DSA_size(dsa.get()));
+ unsigned char* p = OpenSSLWriteInto(&signature,
+ max_signature_size);
+ unsigned int p_len = 0;
+ // Note: first parameter is ignored by function.
+ int ret = DSA_sign(0, digest, digest_len, p, &p_len, dsa.get());
+ if (ret != 1) {
+ LOG(ERROR) << "DSA_sign() failed: " << GetOpenSSLErrorString();
+ return false;
+ }
+ signature_size = static_cast<size_t>(p_len);
+ break;
+ }
+ case EVP_PKEY_EC:
+ {
+ ScopedEC_KEY ecdsa(EVP_PKEY_get1_EC_KEY(openssl_key));
+ if (!ecdsa.get()) {
+ LOG(ERROR) << "Could not get EC_KEY from EVP_PKEY: "
+ << GetOpenSSLErrorString();
+ return false;
+ }
+ // Note, the actual signature can be smaller than ECDSA_size()
+ max_signature_size = ECDSA_size(ecdsa.get());
+ unsigned char* p = OpenSSLWriteInto(&signature,
+ max_signature_size);
+ unsigned int p_len = 0;
+ // Note: first parameter is ignored by function.
+ int ret = ECDSA_sign(
+ 0, digest, digest_len, p, &p_len, ecdsa.get());
+ if (ret != 1) {
+ LOG(ERROR) << "ECDSA_sign() fialed: " << GetOpenSSLErrorString();
+ return false;
+ }
+ signature_size = static_cast<size_t>(p_len);
+ break;
+ }
+ default:
+ LOG(WARNING) << "Invalid OpenSSL key type: " << key_type;
+ return false;
+ }
+
+ if (signature_size == 0) {
+ LOG(ERROR) << "Signature is empty!";
+ return false;
+ }
+ if (signature_size > max_signature_size) {
+ LOG(ERROR) << "Signature size mismatch, actual " << signature_size
+ << ", expected <= " << max_signature_size;
+ return false;
+ }
+ signature.resize(signature_size);
+ result->swap(signature);
+ return true;
+}
+
+// Check that a generated signature for a given message matches
+// OpenSSL output byte-by-byte.
+// |message| is the input message.
+// |signature| is the generated signature for the message.
+// |openssl_key| is a raw EVP_PKEY for the same private key than the
+// one which was used to generate the signature.
+// Returns true on success, false otherwise.
+bool CompareSignatureWithOpenSSL(const base::StringPiece& message,
+ const base::StringPiece& signature,
+ EVP_PKEY* openssl_key) {
+ std::string openssl_signature;
+ SignWithOpenSSL(message, openssl_key, &openssl_signature);
+
+ if (signature.size() != openssl_signature.size()) {
+ LOG(ERROR) << "Signature size mismatch, actual "
+ << signature.size() << ", expected "
+ << openssl_signature.size();
+ return false;
+ }
+ for (size_t n = 0; n < signature.size(); ++n) {
+ if (openssl_signature[n] != signature[n]) {
+ LOG(ERROR) << "Signature byte mismatch at index " << n
+ << "actual " << signature[n] << ", expected "
+ << openssl_signature[n];
+ LOG(ERROR) << "Actual signature : "
+ << base::HexEncode(signature.data(), signature.size());
+ LOG(ERROR) << "Expected signature: "
+ << base::HexEncode(openssl_signature.data(),
+ openssl_signature.size());
+ return false;
+ }
+ }
+ return true;
+}
+
+// Sign a message with our platform API.
+//
+// |android_key| is a JNI reference to the platform PrivateKey object.
+// |openssl_key| is a pointer to an OpenSSL key object for the exact
+// same key content.
+// |message| is a message.
+// |result| will receive the result.
+void DoKeySigning(jobject android_key,
+ EVP_PKEY* openssl_key,
+ const base::StringPiece& message,
+ std::string* result) {
+ // First, get the platform signature.
+ std::vector<uint8> android_signature;
+ ASSERT_TRUE(
+ RawSignDigestWithPrivateKey(android_key,
+ message,
+ &android_signature));
+
+ result->assign(
+ reinterpret_cast<const char*>(&android_signature[0]),
+ android_signature.size());
+}
+
+// Sign a message with our OpenSSL EVP_PKEY wrapper around platform
+// APIS.
+//
+// |android_key| is a JNI reference to the platform PrivateKey object.
+// |openssl_key| is a pointer to an OpenSSL key object for the exact
+// same key content.
+// |message| is a message.
+// |result| will receive the result.
+void DoKeySigningWithWrapper(EVP_PKEY* wrapper_key,
+ EVP_PKEY* openssl_key,
+ const base::StringPiece& message,
+ std::string* result) {
+ // First, get the platform signature.
+ std::string wrapper_signature;
+ SignWithOpenSSL(message, wrapper_key, &wrapper_signature);
+ ASSERT_NE(0U, wrapper_signature.size());
+
+ result->assign(
+ reinterpret_cast<const char*>(&wrapper_signature[0]),
+ wrapper_signature.size());
+}
+
+} // namespace
+
+TEST(AndroidKeyStore,GetRSAKeyModulus) {
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+ InitEnv();
+
+ // Load the test RSA key.
+ ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestRsaKeyFile));
+ ASSERT_TRUE(pkey.get());
+
+ // Convert it to encoded PKCS#8 bytes.
+ std::string pkcs8_data;
+ ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data));
+
+ // Create platform PrivateKey object from it.
+ ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA,
+ pkcs8_data);
+ ASSERT_FALSE(key_java.is_null());
+
+ // Retrieve the corresponding modulus through JNI
+ std::vector<uint8> modulus_java;
+ ASSERT_TRUE(GetRSAKeyModulus(key_java.obj(), &modulus_java));
+
+ // Create an OpenSSL BIGNUM from it.
+ ScopedBIGNUM bn(
+ BN_bin2bn(
+ reinterpret_cast<const unsigned char*>(&modulus_java[0]),
+ static_cast<int>(modulus_java.size()),
+ NULL));
+ ASSERT_TRUE(bn.get());
+
+ // Compare it to the one in the RSA key, they must be identical.
+ ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get()));
+ ASSERT_TRUE(rsa.get()) << GetOpenSSLErrorString();
+
+ ASSERT_EQ(0, BN_cmp(bn.get(), rsa.get()->n));
+}
+
+TEST(AndroidKeyStore,GetDSAKeyParamQ) {
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+ InitEnv();
+
+ // Load the test DSA key.
+ ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestDsaKeyFile));
+ ASSERT_TRUE(pkey.get());
+
+ // Convert it to encoded PKCS#8 bytes.
+ std::string pkcs8_data;
+ ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data));
+
+ // Create platform PrivateKey object from it.
+ ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA,
+ pkcs8_data);
+ ASSERT_FALSE(key_java.is_null());
+
+ // Retrieve the corresponding Q parameter through JNI
+ std::vector<uint8> q_java;
+ ASSERT_TRUE(GetDSAKeyParamQ(key_java.obj(), &q_java));
+
+ // Create an OpenSSL BIGNUM from it.
+ ScopedBIGNUM bn(
+ BN_bin2bn(
+ reinterpret_cast<const unsigned char*>(&q_java[0]),
+ static_cast<int>(q_java.size()),
+ NULL));
+ ASSERT_TRUE(bn.get());
+
+ // Compare it to the one in the RSA key, they must be identical.
+ ScopedDSA dsa(EVP_PKEY_get1_DSA(pkey.get()));
+ ASSERT_TRUE(dsa.get()) << GetOpenSSLErrorString();
+
+ ASSERT_EQ(0, BN_cmp(bn.get(), dsa.get()->q));
+}
+
+TEST(AndroidKeyStore,GetPrivateKeyTypeRSA) {
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+
+ ScopedJava rsa_key = GetRSATestKeyJava();
+ ASSERT_FALSE(rsa_key.is_null());
+ EXPECT_EQ(PRIVATE_KEY_TYPE_RSA,
+ GetPrivateKeyType(rsa_key.obj()));
+}
+
+TEST(AndroidKeyStore,SignWithPrivateKeyRSA) {
+ ScopedJava rsa_key = GetRSATestKeyJava();
+ ASSERT_FALSE(rsa_key.is_null());
+
+ if (IsOnAndroidOlderThan_4_2()) {
+ LOG(INFO) << "This test can't run on Android < 4.2";
+ return;
+ }
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ std::string message = kTestRsaHash;
+ ASSERT_EQ(36U, message.size());
+
+ std::string signature;
+ DoKeySigning(rsa_key.obj(), openssl_key.get(), message, &signature);
+ ASSERT_TRUE(
+ CompareSignatureWithOpenSSL(message, signature, openssl_key.get()));
+ // All good.
+}
+
+TEST(AndroidKeyStore,SignWithWrapperKeyRSA) {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ ScopedJava rsa_key = GetRSATestKeyJava();
+ ASSERT_FALSE(rsa_key.is_null());
+
+ ScopedEVP_PKEY wrapper_key(GetOpenSSLPrivateKeyWrapper(rsa_key.obj()));
+ ASSERT_TRUE(wrapper_key.get() != NULL);
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ // Check that RSA_size() works properly on the wrapper key.
+ EXPECT_EQ(EVP_PKEY_size(openssl_key.get()),
+ EVP_PKEY_size(wrapper_key.get()));
+
+ // Message size must be 36 for RSA_sign(NID_md5_sha1,...) to return
+ // without an error.
+ std::string message = kTestRsaHash;
+ ASSERT_EQ(36U, message.size());
+
+ std::string signature;
+ DoKeySigningWithWrapper(wrapper_key.get(),
+ openssl_key.get(),
+ message,
+ &signature);
+ ASSERT_TRUE(
+ CompareSignatureWithOpenSSL(message, signature, openssl_key.get()));
+}
+
+TEST(AndroidKeyStore,GetPrivateKeyTypeDSA) {
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+
+ ScopedJava dsa_key = GetDSATestKeyJava();
+ ASSERT_FALSE(dsa_key.is_null());
+ EXPECT_EQ(PRIVATE_KEY_TYPE_DSA,
+ GetPrivateKeyType(dsa_key.obj()));
+}
+
+TEST(AndroidKeyStore,SignWithPrivateKeyDSA) {
+ ScopedJava dsa_key = GetDSATestKeyJava();
+ ASSERT_FALSE(dsa_key.is_null());
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ std::string message = kTestDsaHash;
+ ASSERT_EQ(20U, message.size());
+
+ std::string signature;
+ DoKeySigning(dsa_key.obj(), openssl_key.get(), message, &signature);
+ ASSERT_TRUE(VerifyTestDSASignature(message, signature));
+}
+
+TEST(AndroidKeyStore,SignWithWrapperKeyDSA) {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ ScopedJava dsa_key = GetDSATestKeyJava();
+ ASSERT_FALSE(dsa_key.is_null());
+
+ ScopedEVP_PKEY wrapper_key(
+ GetOpenSSLPrivateKeyWrapper(dsa_key.obj()));
+ ASSERT_TRUE(wrapper_key.get());
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ // Check that DSA_size() works correctly on the wrapper.
+ EXPECT_EQ(EVP_PKEY_size(openssl_key.get()),
+ EVP_PKEY_size(wrapper_key.get()));
+
+ std::string message = kTestDsaHash;
+ std::string signature;
+ DoKeySigningWithWrapper(wrapper_key.get(),
+ openssl_key.get(),
+ message,
+ &signature);
+ ASSERT_TRUE(VerifyTestDSASignature(message, signature));
+}
+
+TEST(AndroidKeyStore,GetPrivateKeyTypeECDSA) {
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+
+ ScopedJava ecdsa_key = GetECDSATestKeyJava();
+ ASSERT_FALSE(ecdsa_key.is_null());
+ EXPECT_EQ(PRIVATE_KEY_TYPE_ECDSA,
+ GetPrivateKeyType(ecdsa_key.obj()));
+}
+
+TEST(AndroidKeyStore,SignWithPrivateKeyECDSA) {
+ ScopedJava ecdsa_key = GetECDSATestKeyJava();
+ ASSERT_FALSE(ecdsa_key.is_null());
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ std::string message = kTestEcdsaHash;
+ std::string signature;
+ DoKeySigning(ecdsa_key.obj(), openssl_key.get(), message, &signature);
+ ASSERT_TRUE(VerifyTestECDSASignature(message, signature));
+}
+
+TEST(AndroidKeyStore, SignWithWrapperKeyECDSA) {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ ScopedJava ecdsa_key = GetECDSATestKeyJava();
+ ASSERT_FALSE(ecdsa_key.is_null());
+
+ ScopedEVP_PKEY wrapper_key(
+ GetOpenSSLPrivateKeyWrapper(ecdsa_key.obj()));
+ ASSERT_TRUE(wrapper_key.get());
+
+ ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile));
+ ASSERT_TRUE(openssl_key.get());
+
+ // Check that ECDSA size works correctly on the wrapper.
+ EXPECT_EQ(EVP_PKEY_size(openssl_key.get()),
+ EVP_PKEY_size(wrapper_key.get()));
+
+ std::string message = kTestEcdsaHash;
+ std::string signature;
+ DoKeySigningWithWrapper(wrapper_key.get(),
+ openssl_key.get(),
+ message,
+ &signature);
+ ASSERT_TRUE(VerifyTestECDSASignature(message, signature));
+}
+
+} // namespace android
+} // namespace net
diff --git a/chromium/net/android/net_jni_registrar.cc b/chromium/net/android/net_jni_registrar.cc
new file mode 100644
index 00000000000..a6e09b65080
--- /dev/null
+++ b/chromium/net/android/net_jni_registrar.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#include "net/android/net_jni_registrar.h"
+
+#include "base/basictypes.h"
+#include "base/android/jni_android.h"
+#include "base/android/jni_registrar.h"
+#include "net/android/gurl_utils.h"
+#include "net/android/keystore.h"
+#include "net/android/network_change_notifier_android.h"
+#include "net/android/network_library.h"
+#include "net/proxy/proxy_config_service_android.h"
+
+namespace net {
+namespace android {
+
+static base::android::RegistrationMethod kNetRegisteredMethods[] = {
+ { "AndroidKeyStore", net::android::RegisterKeyStore },
+ { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary },
+ { "GURLUtils", net::RegisterGURLUtils },
+ { "NetworkChangeNotifierAndroid",
+ net::NetworkChangeNotifierAndroid::Register },
+ { "ProxyConfigService", net::ProxyConfigServiceAndroid::Register },
+};
+
+bool RegisterJni(JNIEnv* env) {
+ return base::android::RegisterNativeMethods(
+ env, kNetRegisteredMethods, arraysize(kNetRegisteredMethods));
+}
+
+} // namespace android
+} // namespace net
diff --git a/chromium/net/android/net_jni_registrar.h b/chromium/net/android/net_jni_registrar.h
new file mode 100644
index 00000000000..2b45fb26d07
--- /dev/null
+++ b/chromium/net/android/net_jni_registrar.h
@@ -0,0 +1,21 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_NET_JNI_REGISTRAR_H_
+#define NET_ANDROID_NET_JNI_REGISTRAR_H_
+
+#include <jni.h>
+
+#include "net/base/net_export.h"
+
+namespace net {
+namespace android {
+
+// Register all JNI bindings necessary for net.
+NET_EXPORT bool RegisterJni(JNIEnv* env);
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_NET_JNI_REGISTRAR_H_
diff --git a/chromium/net/android/network_change_notifier_android.cc b/chromium/net/android/network_change_notifier_android.cc
new file mode 100644
index 00000000000..d4e1a5c8524
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_android.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// Threading considerations:
+//
+// This class is designed to meet various threading guarantees starting from the
+// ones imposed by NetworkChangeNotifier:
+// - The notifier can be constructed on any thread.
+// - GetCurrentConnectionType() can be called on any thread.
+//
+// The fact that this implementation of NetworkChangeNotifier is backed by a
+// Java side singleton class (see NetworkChangeNotifier.java) adds another
+// threading constraint:
+// - The calls to the Java side (stateful) object must be performed from a
+// single thread. This object happens to be a singleton which is used on the
+// application side on the main thread. Therefore all the method calls from
+// the native NetworkChangeNotifierAndroid class to its Java counterpart are
+// performed on the main thread.
+//
+// This leads to a design involving the following native classes:
+// 1) NetworkChangeNotifierFactoryAndroid ('factory')
+// 2) NetworkChangeNotifierDelegateAndroid ('delegate')
+// 3) NetworkChangeNotifierAndroid ('notifier')
+//
+// The factory constructs and owns the delegate. The factory is constructed and
+// destroyed on the main thread which makes it construct and destroy the
+// delegate on the main thread too. This guarantees that the calls to the Java
+// side are performed on the main thread.
+// Note that after the factory's construction, the factory's creation method can
+// be called from any thread since the delegate's construction (performing the
+// JNI calls) already happened on the main thread (when the factory was
+// constructed).
+//
+////////////////////////////////////////////////////////////////////////////////
+// Propagation of network change notifications:
+//
+// When the factory is requested to create a new instance of the notifier, the
+// factory passes the delegate to the notifier (without transferring ownership).
+// Note that there is a one-to-one mapping between the factory and the
+// delegate as explained above. But the factory naturally creates multiple
+// instances of the notifier. That means that there is a one-to-many mapping
+// between delegate and notifier (i.e. a single delegate can be shared by
+// multiple notifiers).
+// At construction the notifier (which is also an observer) subscribes to
+// notifications fired by the delegate. These notifications, received by the
+// delegate (and forwarded to the notifier(s)), are sent by the Java side
+// notifier (see NetworkChangeNotifier.java) and are initiated by the Android
+// platform.
+// Notifications from the Java side always arrive on the main thread. The
+// delegate then forwards these notifications to the threads of each observer
+// (network change notifier). The network change notifier than processes the
+// state change, and notifies each of its observers on their threads.
+//
+// This can also be seen as:
+// Android platform -> NetworkChangeNotifier (Java) ->
+// NetworkChangeNotifierDelegateAndroid -> NetworkChangeNotifierAndroid.
+
+#include "net/android/network_change_notifier_android.h"
+
+namespace net {
+
+NetworkChangeNotifierAndroid::~NetworkChangeNotifierAndroid() {
+ delegate_->RemoveObserver(this);
+}
+
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifierAndroid::GetCurrentConnectionType() const {
+ return delegate_->GetCurrentConnectionType();
+}
+
+void NetworkChangeNotifierAndroid::OnConnectionTypeChanged() {
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChange();
+ NetworkChangeNotifier::NotifyObserversOfConnectionTypeChange();
+}
+
+// static
+bool NetworkChangeNotifierAndroid::Register(JNIEnv* env) {
+ return NetworkChangeNotifierDelegateAndroid::Register(env);
+}
+
+NetworkChangeNotifierAndroid::NetworkChangeNotifierAndroid(
+ NetworkChangeNotifierDelegateAndroid* delegate)
+ : NetworkChangeNotifier(NetworkChangeCalculatorParamsAndroid()),
+ delegate_(delegate) {
+ delegate_->AddObserver(this);
+}
+
+// static
+NetworkChangeNotifier::NetworkChangeCalculatorParams
+NetworkChangeNotifierAndroid::NetworkChangeCalculatorParamsAndroid() {
+ NetworkChangeCalculatorParams params;
+ // IPAddressChanged is produced immediately prior to ConnectionTypeChanged
+ // so delay IPAddressChanged so they get merged with the following
+ // ConnectionTypeChanged signal.
+ params.ip_address_offline_delay_ = base::TimeDelta::FromSeconds(1);
+ params.ip_address_online_delay_ = base::TimeDelta::FromSeconds(1);
+ params.connection_type_offline_delay_ = base::TimeDelta::FromSeconds(0);
+ params.connection_type_online_delay_ = base::TimeDelta::FromSeconds(0);
+ return params;
+}
+
+} // namespace net
diff --git a/chromium/net/android/network_change_notifier_android.h b/chromium/net/android/network_change_notifier_android.h
new file mode 100644
index 00000000000..f167cfc283b
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_android.h
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_
+#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_
+
+#include "base/android/jni_android.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/android/network_change_notifier_delegate_android.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+
+class NetworkChangeNotifierAndroidTest;
+class NetworkChangeNotifierFactoryAndroid;
+
+// NetworkChangeNotifierAndroid observes network events from the Android
+// notification system and forwards them to observers.
+//
+// The implementation is complicated by the differing lifetime and thread
+// affinity requirements of Android notifications and of NetworkChangeNotifier.
+//
+// High-level overview:
+// NetworkChangeNotifier.java - Receives notifications from Android system, and
+// notifies native code via JNI (on the main application thread).
+// NetworkChangeNotifierDelegateAndroid ('Delegate') - Listens for notifications
+// sent via JNI on the main application thread, and forwards them to observers
+// on their threads. Owned by Factory, lives exclusively on main application
+// thread.
+// NetworkChangeNotifierFactoryAndroid ('Factory') - Creates the Delegate on the
+// main thread to receive JNI events, and vends Notifiers. Lives exclusively
+// on main application thread, and outlives all other classes.
+// NetworkChangeNotifierAndroid ('Notifier') - Receives event notifications from
+// the Delegate. Processes and forwards these events to the
+// NetworkChangeNotifier observers on their threads. May live on any thread
+// and be called by any thread.
+//
+// For more details, see the implementation file.
+class NET_EXPORT_PRIVATE NetworkChangeNotifierAndroid
+ : public NetworkChangeNotifier,
+ public NetworkChangeNotifierDelegateAndroid::Observer {
+ public:
+ virtual ~NetworkChangeNotifierAndroid();
+
+ // NetworkChangeNotifier:
+ virtual ConnectionType GetCurrentConnectionType() const OVERRIDE;
+
+ // NetworkChangeNotifierDelegateAndroid::Observer:
+ virtual void OnConnectionTypeChanged() OVERRIDE;
+
+ static bool Register(JNIEnv* env);
+
+ private:
+ friend class NetworkChangeNotifierAndroidTest;
+ friend class NetworkChangeNotifierFactoryAndroid;
+
+ explicit NetworkChangeNotifierAndroid(
+ NetworkChangeNotifierDelegateAndroid* delegate);
+
+ static NetworkChangeCalculatorParams NetworkChangeCalculatorParamsAndroid();
+
+ NetworkChangeNotifierDelegateAndroid* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierAndroid);
+};
+
+} // namespace net
+
+#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_ANDROID_H_
diff --git a/chromium/net/android/network_change_notifier_android_unittest.cc b/chromium/net/android/network_change_notifier_android_unittest.cc
new file mode 100644
index 00000000000..6ab894cef64
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_android_unittest.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 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.
+
+// See network_change_notifier_android.h for design explanations.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "net/android/network_change_notifier_android.h"
+#include "net/android/network_change_notifier_delegate_android.h"
+#include "net/base/network_change_notifier.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class NetworkChangeNotifierDelegateAndroidObserver
+ : public NetworkChangeNotifierDelegateAndroid::Observer {
+ public:
+ NetworkChangeNotifierDelegateAndroidObserver() : notifications_count_(0) {}
+
+ // NetworkChangeNotifierDelegateAndroid::Observer:
+ virtual void OnConnectionTypeChanged() OVERRIDE {
+ notifications_count_++;
+ }
+
+ int notifications_count() const {
+ return notifications_count_;
+ }
+
+ private:
+ int notifications_count_;
+};
+
+class NetworkChangeNotifierObserver
+ : public NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ NetworkChangeNotifierObserver() : notifications_count_(0) {}
+
+ // NetworkChangeNotifier::Observer:
+ virtual void OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType connection_type) OVERRIDE {
+ notifications_count_++;
+ }
+
+ int notifications_count() const {
+ return notifications_count_;
+ }
+
+ private:
+ int notifications_count_;
+};
+
+} // namespace
+
+class BaseNetworkChangeNotifierAndroidTest : public testing::Test {
+ protected:
+ typedef NetworkChangeNotifier::ConnectionType ConnectionType;
+
+ virtual ~BaseNetworkChangeNotifierAndroidTest() {}
+
+ void RunTest(
+ const base::Callback<int(void)>& notifications_count_getter,
+ const base::Callback<ConnectionType(void)>& connection_type_getter) {
+ EXPECT_EQ(0, notifications_count_getter.Run());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN,
+ connection_type_getter.Run());
+
+ // Changing from online to offline should trigger a notification.
+ SetOffline();
+ EXPECT_EQ(1, notifications_count_getter.Run());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE,
+ connection_type_getter.Run());
+
+ // No notification should be triggered when the offline state hasn't
+ // changed.
+ SetOffline();
+ EXPECT_EQ(1, notifications_count_getter.Run());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE,
+ connection_type_getter.Run());
+
+ // Going from offline to online should trigger a notification.
+ SetOnline();
+ EXPECT_EQ(2, notifications_count_getter.Run());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN,
+ connection_type_getter.Run());
+ }
+
+ void SetOnline() {
+ delegate_.SetOnline();
+ // Note that this is needed because ObserverListThreadSafe uses PostTask().
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void SetOffline() {
+ delegate_.SetOffline();
+ // See comment above.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ NetworkChangeNotifierDelegateAndroid delegate_;
+};
+
+// Tests that NetworkChangeNotifierDelegateAndroid is initialized with the
+// actual connection type rather than a hardcoded one (e.g.
+// CONNECTION_UNKNOWN). Initializing the connection type to CONNECTION_UNKNOWN
+// and relying on the first network change notification to set it correctly can
+// be problematic in case there is a long delay between the delegate's
+// construction and the notification.
+TEST_F(BaseNetworkChangeNotifierAndroidTest,
+ DelegateIsInitializedWithCurrentConnectionType) {
+ SetOffline();
+ ASSERT_EQ(NetworkChangeNotifier::CONNECTION_NONE,
+ delegate_.GetCurrentConnectionType());
+ // Instantiate another delegate to validate that it uses the actual
+ // connection type at construction.
+ scoped_ptr<NetworkChangeNotifierDelegateAndroid> other_delegate(
+ new NetworkChangeNotifierDelegateAndroid());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_NONE,
+ other_delegate->GetCurrentConnectionType());
+
+ // Toggle the global connectivity state and instantiate another delegate
+ // again.
+ SetOnline();
+ ASSERT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN,
+ delegate_.GetCurrentConnectionType());
+ other_delegate.reset(new NetworkChangeNotifierDelegateAndroid());
+ EXPECT_EQ(NetworkChangeNotifier::CONNECTION_UNKNOWN,
+ other_delegate->GetCurrentConnectionType());
+}
+
+class NetworkChangeNotifierDelegateAndroidTest
+ : public BaseNetworkChangeNotifierAndroidTest {
+ protected:
+ NetworkChangeNotifierDelegateAndroidTest() {
+ delegate_.AddObserver(&delegate_observer_);
+ delegate_.AddObserver(&other_delegate_observer_);
+ }
+
+ virtual ~NetworkChangeNotifierDelegateAndroidTest() {
+ delegate_.RemoveObserver(&delegate_observer_);
+ delegate_.RemoveObserver(&other_delegate_observer_);
+ }
+
+ NetworkChangeNotifierDelegateAndroidObserver delegate_observer_;
+ NetworkChangeNotifierDelegateAndroidObserver other_delegate_observer_;
+};
+
+// Tests that the NetworkChangeNotifierDelegateAndroid's observers are notified.
+// A testing-only observer is used here for testing. In production the
+// delegate's observers are instances of NetworkChangeNotifierAndroid.
+TEST_F(NetworkChangeNotifierDelegateAndroidTest, DelegateObserverNotified) {
+ // Test the logic with a single observer.
+ RunTest(
+ base::Bind(
+ &NetworkChangeNotifierDelegateAndroidObserver::notifications_count,
+ base::Unretained(&delegate_observer_)),
+ base::Bind(
+ &NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType,
+ base::Unretained(&delegate_)));
+ // Check that *all* the observers are notified. Both observers should have the
+ // same state.
+ EXPECT_EQ(delegate_observer_.notifications_count(),
+ other_delegate_observer_.notifications_count());
+}
+
+class NetworkChangeNotifierAndroidTest
+ : public BaseNetworkChangeNotifierAndroidTest {
+ protected:
+ NetworkChangeNotifierAndroidTest() : notifier_(&delegate_) {
+ NetworkChangeNotifier::AddConnectionTypeObserver(
+ &connection_type_observer_);
+ NetworkChangeNotifier::AddConnectionTypeObserver(
+ &other_connection_type_observer_);
+ }
+
+ NetworkChangeNotifierObserver connection_type_observer_;
+ NetworkChangeNotifierObserver other_connection_type_observer_;
+ NetworkChangeNotifier::DisableForTest disable_for_test_;
+ NetworkChangeNotifierAndroid notifier_;
+};
+
+// When a NetworkChangeNotifierAndroid is observing a
+// NetworkChangeNotifierDelegateAndroid for network state changes, and the
+// NetworkChangeNotifierDelegateAndroid's connectivity state changes, the
+// NetworkChangeNotifierAndroid should reflect that state.
+TEST_F(NetworkChangeNotifierAndroidTest,
+ NotificationsSentToNetworkChangeNotifierAndroid) {
+ RunTest(
+ base::Bind(
+ &NetworkChangeNotifierObserver::notifications_count,
+ base::Unretained(&connection_type_observer_)),
+ base::Bind(
+ &NetworkChangeNotifierAndroid::GetCurrentConnectionType,
+ base::Unretained(&notifier_)));
+}
+
+// When a NetworkChangeNotifierAndroid's connection state changes, it should
+// notify all of its observers.
+TEST_F(NetworkChangeNotifierAndroidTest,
+ NotificationsSentToClientsOfNetworkChangeNotifier) {
+ RunTest(
+ base::Bind(
+ &NetworkChangeNotifierObserver::notifications_count,
+ base::Unretained(&connection_type_observer_)),
+ base::Bind(&NetworkChangeNotifier::GetConnectionType));
+ // Check that *all* the observers are notified.
+ EXPECT_EQ(connection_type_observer_.notifications_count(),
+ other_connection_type_observer_.notifications_count());
+}
+
+} // namespace net
diff --git a/chromium/net/android/network_change_notifier_delegate_android.cc b/chromium/net/android/network_change_notifier_delegate_android.cc
new file mode 100644
index 00000000000..0031260b73e
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_delegate_android.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 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.
+
+#include "net/android/network_change_notifier_delegate_android.h"
+
+#include "base/logging.h"
+#include "jni/NetworkChangeNotifier_jni.h"
+
+namespace net {
+
+namespace {
+
+// Converts a Java side connection type (integer) to
+// the native side NetworkChangeNotifier::ConnectionType.
+NetworkChangeNotifier::ConnectionType ConvertConnectionType(
+ jint connection_type) {
+ switch (connection_type) {
+ case NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ case NetworkChangeNotifier::CONNECTION_ETHERNET:
+ case NetworkChangeNotifier::CONNECTION_WIFI:
+ case NetworkChangeNotifier::CONNECTION_2G:
+ case NetworkChangeNotifier::CONNECTION_3G:
+ case NetworkChangeNotifier::CONNECTION_4G:
+ case NetworkChangeNotifier::CONNECTION_NONE:
+ break;
+ default:
+ NOTREACHED() << "Unknown connection type received: " << connection_type;
+ return NetworkChangeNotifier::CONNECTION_UNKNOWN;
+ }
+ return static_cast<NetworkChangeNotifier::ConnectionType>(connection_type);
+}
+
+} // namespace
+
+NetworkChangeNotifierDelegateAndroid::NetworkChangeNotifierDelegateAndroid()
+ : observers_(new ObserverListThreadSafe<Observer>()) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ java_network_change_notifier_.Reset(
+ Java_NetworkChangeNotifier_init(
+ env, base::android::GetApplicationContext()));
+ Java_NetworkChangeNotifier_addNativeObserver(
+ env, java_network_change_notifier_.obj(), reinterpret_cast<jint>(this));
+ SetCurrentConnectionType(
+ ConvertConnectionType(
+ Java_NetworkChangeNotifier_getCurrentConnectionType(
+ env, java_network_change_notifier_.obj())));
+}
+
+NetworkChangeNotifierDelegateAndroid::~NetworkChangeNotifierDelegateAndroid() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_->AssertEmpty();
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_NetworkChangeNotifier_removeNativeObserver(
+ env, java_network_change_notifier_.obj(), reinterpret_cast<jint>(this));
+}
+
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType() const {
+ base::AutoLock auto_lock(connection_type_lock_);
+ return connection_type_;
+}
+
+void NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged(
+ JNIEnv* env,
+ jobject obj,
+ jint new_connection_type) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ const ConnectionType actual_connection_type = ConvertConnectionType(
+ new_connection_type);
+ SetCurrentConnectionType(actual_connection_type);
+ observers_->Notify(&Observer::OnConnectionTypeChanged);
+}
+
+jint NetworkChangeNotifierDelegateAndroid::GetConnectionType(JNIEnv*,
+ jobject) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return GetCurrentConnectionType();
+}
+
+void NetworkChangeNotifierDelegateAndroid::AddObserver(
+ Observer* observer) {
+ observers_->AddObserver(observer);
+}
+
+void NetworkChangeNotifierDelegateAndroid::RemoveObserver(
+ Observer* observer) {
+ observers_->RemoveObserver(observer);
+}
+
+// static
+bool NetworkChangeNotifierDelegateAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void NetworkChangeNotifierDelegateAndroid::SetCurrentConnectionType(
+ ConnectionType new_connection_type) {
+ base::AutoLock auto_lock(connection_type_lock_);
+ connection_type_ = new_connection_type;
+}
+
+void NetworkChangeNotifierDelegateAndroid::SetOnline() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_NetworkChangeNotifier_forceConnectivityState(env, true);
+}
+
+void NetworkChangeNotifierDelegateAndroid::SetOffline() {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_NetworkChangeNotifier_forceConnectivityState(env, false);
+}
+
+} // namespace net
diff --git a/chromium/net/android/network_change_notifier_delegate_android.h b/chromium/net/android/network_change_notifier_delegate_android.h
new file mode 100644
index 00000000000..f93c30b0b25
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_delegate_android.h
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_
+#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_ANDROID_H_
+
+#include "base/android/jni_android.h"
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+
+// Delegate used to thread-safely notify NetworkChangeNotifierAndroid whenever a
+// network connection change notification is signaled by the Java side (on the
+// JNI thread).
+// All the methods exposed below must be called exclusively on the JNI thread
+// unless otherwise stated (e.g. AddObserver()/RemoveObserver()).
+class NET_EXPORT_PRIVATE NetworkChangeNotifierDelegateAndroid {
+ public:
+ typedef NetworkChangeNotifier::ConnectionType ConnectionType;
+
+ // Observer interface implemented by NetworkChangeNotifierAndroid which
+ // subscribes to network change notifications fired by the delegate (and
+ // initiated by the Java side).
+ class Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Updates the current connection type.
+ virtual void OnConnectionTypeChanged() = 0;
+ };
+
+ NetworkChangeNotifierDelegateAndroid();
+ ~NetworkChangeNotifierDelegateAndroid();
+
+ // Called from NetworkChangeNotifierAndroid.java on the JNI thread whenever
+ // the connection type changes. This updates the current connection type seen
+ // by this class and forwards the notification to the observers that
+ // subscribed through AddObserver().
+ void NotifyConnectionTypeChanged(JNIEnv* env,
+ jobject obj,
+ jint new_connection_type);
+ jint GetConnectionType(JNIEnv* env, jobject obj) const;
+
+ // These methods can be called on any thread. Note that the provided observer
+ // will be notified on the thread AddObserver() is called on.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ // Can be called from any thread.
+ ConnectionType GetCurrentConnectionType() const;
+
+ // Initializes JNI bindings.
+ static bool Register(JNIEnv* env);
+
+ private:
+ friend class BaseNetworkChangeNotifierAndroidTest;
+
+ void SetCurrentConnectionType(ConnectionType connection_type);
+
+ // Methods calling the Java side exposed for testing.
+ void SetOnline();
+ void SetOffline();
+
+ base::ThreadChecker thread_checker_;
+ scoped_refptr<ObserverListThreadSafe<Observer> > observers_;
+ scoped_refptr<base::SingleThreadTaskRunner> jni_task_runner_;
+ base::android::ScopedJavaGlobalRef<jobject> java_network_change_notifier_;
+ mutable base::Lock connection_type_lock_; // Protects the state below.
+ ConnectionType connection_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierDelegateAndroid);
+};
+
+} // namespace net
+
+#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_DELEGATE_H_
diff --git a/chromium/net/android/network_change_notifier_factory_android.cc b/chromium/net/android/network_change_notifier_factory_android.cc
new file mode 100644
index 00000000000..64b9ca2a215
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_factory_android.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 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.
+
+#include "net/android/network_change_notifier_factory_android.h"
+
+#include "net/android/network_change_notifier_android.h"
+#include "net/android/network_change_notifier_delegate_android.h"
+
+namespace net {
+
+NetworkChangeNotifierFactoryAndroid::NetworkChangeNotifierFactoryAndroid() {}
+
+NetworkChangeNotifierFactoryAndroid::~NetworkChangeNotifierFactoryAndroid() {}
+
+NetworkChangeNotifier* NetworkChangeNotifierFactoryAndroid::CreateInstance() {
+ return new NetworkChangeNotifierAndroid(&delegate_);
+}
+
+} // namespace net
diff --git a/chromium/net/android/network_change_notifier_factory_android.h b/chromium/net/android/network_change_notifier_factory_android.h
new file mode 100644
index 00000000000..e71ec27dca5
--- /dev/null
+++ b/chromium/net/android/network_change_notifier_factory_android.h
@@ -0,0 +1,43 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_ANDROID_H_
+#define NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_ANDROID_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/android/network_change_notifier_delegate_android.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier_factory.h"
+
+namespace net {
+
+class NetworkChangeNotifier;
+class NetworkChangeNotifierDelegateAndroid;
+
+// NetworkChangeNotifierFactory creates Android-specific specialization of
+// NetworkChangeNotifier. See network_change_notifier_android.h for more
+// details.
+class NET_EXPORT NetworkChangeNotifierFactoryAndroid :
+ public NetworkChangeNotifierFactory {
+ public:
+ // Must be called on the JNI thread.
+ NetworkChangeNotifierFactoryAndroid();
+
+ // Must be called on the JNI thread.
+ virtual ~NetworkChangeNotifierFactoryAndroid();
+
+ // NetworkChangeNotifierFactory:
+ virtual NetworkChangeNotifier* CreateInstance() OVERRIDE;
+
+ private:
+ // Delegate passed to the instances created by this class.
+ NetworkChangeNotifierDelegateAndroid delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierFactoryAndroid);
+};
+
+} // namespace net
+
+#endif // NET_ANDROID_NETWORK_CHANGE_NOTIFIER_FACTORY_H_
diff --git a/chromium/net/android/network_library.cc b/chromium/net/android/network_library.cc
new file mode 100644
index 00000000000..2407100cdc3
--- /dev/null
+++ b/chromium/net/android/network_library.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 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.
+
+#include "net/android/network_library.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/logging.h"
+#include "jni/AndroidNetworkLibrary_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ClearException;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::GetApplicationContext;
+using base::android::ScopedJavaLocalRef;
+using base::android::ToJavaArrayOfByteArray;
+using base::android::ToJavaByteArray;
+
+namespace net {
+namespace android {
+
+CertVerifyResultAndroid VerifyX509CertChain(
+ const std::vector<std::string>& cert_chain,
+ const std::string& auth_type) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jobjectArray> chain_byte_array =
+ ToJavaArrayOfByteArray(env, cert_chain);
+ DCHECK(!chain_byte_array.is_null());
+
+ ScopedJavaLocalRef<jstring> auth_string =
+ ConvertUTF8ToJavaString(env, auth_type);
+ DCHECK(!auth_string.is_null());
+
+ jint result = Java_AndroidNetworkLibrary_verifyServerCertificates(
+ env, chain_byte_array.obj(), auth_string.obj());
+
+ return static_cast<CertVerifyResultAndroid>(result);
+}
+
+void AddTestRootCertificate(const uint8* cert, size_t len) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> cert_array = ToJavaByteArray(env, cert, len);
+ DCHECK(!cert_array.is_null());
+ Java_AndroidNetworkLibrary_addTestRootCertificate(env, cert_array.obj());
+}
+
+void ClearTestRootCertificates() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_AndroidNetworkLibrary_clearTestRootCertificates(env);
+}
+
+bool StoreKeyPair(const uint8* public_key,
+ size_t public_len,
+ const uint8* private_key,
+ size_t private_len) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> public_array =
+ ToJavaByteArray(env, public_key, public_len);
+ ScopedJavaLocalRef<jbyteArray> private_array =
+ ToJavaByteArray(env, private_key, private_len);
+ jboolean ret = Java_AndroidNetworkLibrary_storeKeyPair(env,
+ GetApplicationContext(), public_array.obj(), private_array.obj());
+ LOG_IF(WARNING, !ret) <<
+ "Call to Java_AndroidNetworkLibrary_storeKeyPair failed";
+ return ret;
+}
+
+void StoreCertificate(net::CertificateMimeType cert_type,
+ const void* data,
+ size_t data_len) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jbyteArray> data_array =
+ ToJavaByteArray(env, reinterpret_cast<const uint8*>(data), data_len);
+ jboolean ret = Java_AndroidNetworkLibrary_storeCertificate(env,
+ GetApplicationContext(), cert_type, data_array.obj());
+ LOG_IF(WARNING, !ret) <<
+ "Call to Java_AndroidNetworkLibrary_storeCertificate"
+ " failed";
+ // Intentionally do not return 'ret', there is little the caller can
+ // do in case of failure (the CertInstaller itself will deal with
+ // incorrect data and display the appropriate toast).
+}
+
+bool HaveOnlyLoopbackAddresses() {
+ JNIEnv* env = AttachCurrentThread();
+ return Java_AndroidNetworkLibrary_haveOnlyLoopbackAddresses(env);
+}
+
+std::string GetNetworkList() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> ret =
+ Java_AndroidNetworkLibrary_getNetworkList(env);
+ return ConvertJavaStringToUTF8(ret);
+}
+
+bool GetMimeTypeFromExtension(const std::string& extension,
+ std::string* result) {
+ JNIEnv* env = AttachCurrentThread();
+
+ ScopedJavaLocalRef<jstring> extension_string =
+ ConvertUTF8ToJavaString(env, extension);
+ ScopedJavaLocalRef<jstring> ret =
+ Java_AndroidNetworkLibrary_getMimeTypeFromExtension(
+ env, extension_string.obj());
+
+ if (!ret.obj())
+ return false;
+ *result = ConvertJavaStringToUTF8(ret);
+ return true;
+}
+
+bool RegisterNetworkLibrary(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+} // namespace net
diff --git a/chromium/net/android/network_library.h b/chromium/net/android/network_library.h
new file mode 100644
index 00000000000..6bdc1ae427c
--- /dev/null
+++ b/chromium/net/android/network_library.h
@@ -0,0 +1,77 @@
+// Copyright (c) 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.
+
+#ifndef NET_ANDROID_NETWORK_LIBRARY_H_
+#define NET_ANDROID_NETWORK_LIBRARY_H_
+
+#include <jni.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/android/cert_verify_result_android.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_export.h"
+
+namespace net {
+namespace android {
+
+// |cert_chain| is DER encoded chain of certificates, with the server's own
+// certificate listed first.
+// |auth_type| is as per the Java X509Certificate.checkServerTrusted method.
+CertVerifyResultAndroid VerifyX509CertChain(
+ const std::vector<std::string>& cert_chain,
+ const std::string& auth_type);
+
+// Adds a certificate as a root trust certificate to the trust manager.
+// |cert| is DER encoded certificate, |len| is its length in bytes.
+void AddTestRootCertificate(const uint8* cert, size_t len);
+
+// Removes all root certificates added by |AddTestRootCertificate| calls.
+void ClearTestRootCertificates();
+
+// Helper for the <keygen> handler. Passes the DER-encoded key pair via
+// JNI to the Credentials store. Note that the public key must be a DER
+// encoded SubjectPublicKeyInfo (X.509), as returned by i2d_PUBKEY()
+// (and *not* i2d_PublicKey(), which returns a PKCS#1 key).
+//
+// Also, the private key must be in PKCS#8 format, as returned by
+// i2d_PKCS8_PRIV_KEY_INFO(EVP_PKEY2PKCS8(pkey)), which is a different
+// format than what i2d_PrivateKey() returns, so don't use it either.
+//
+bool StoreKeyPair(const uint8* public_key,
+ size_t public_len,
+ const uint8* private_key,
+ size_t private_len);
+
+// Helper used to pass the DER-encoded bytes of an X.509 certificate or
+// a PKCS#12 archive holding a private key to the CertInstaller activity.
+NET_EXPORT void StoreCertificate(net::CertificateMimeType cert_type,
+ const void* data,
+ size_t data_len);
+
+// Returns true if it can determine that only loopback addresses are configured.
+// i.e. if only 127.0.0.1 and ::1 are routable.
+// Also returns false if it cannot determine this.
+bool HaveOnlyLoopbackAddresses();
+
+// Return a string containing a list of network interfaces, each item is a
+// network name and address pair.
+// e.g. "eth0,10.0.0.2;eth0,fe80::5054:ff:fe12:3456" is a result string
+// containing two items.
+std::string GetNetworkList();
+
+// Get the mime type (if any) that is associated with the file extension.
+// Returns true if a corresponding mime type exists.
+bool GetMimeTypeFromExtension(const std::string& extension,
+ std::string* result);
+
+// Register JNI methods
+NET_EXPORT bool RegisterNetworkLibrary(JNIEnv* env);
+
+} // namespace android
+} // namespace net
+
+#endif // NET_ANDROID_NETWORK_LIBRARY_H_
diff --git a/chromium/net/android/private_key_type_list.h b/chromium/net/android/private_key_type_list.h
new file mode 100644
index 00000000000..1eeea805fe9
--- /dev/null
+++ b/chromium/net/android/private_key_type_list.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2013 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 DEFINE_PRIVATE_KEY_TYPE
+#error "Please define DEFINE_PRIVATE_KEY_TYPE before including this file."
+#endif
+
+DEFINE_PRIVATE_KEY_TYPE(RSA, 0)
+DEFINE_PRIVATE_KEY_TYPE(DSA, 1)
+DEFINE_PRIVATE_KEY_TYPE(ECDSA, 2)
+DEFINE_PRIVATE_KEY_TYPE(INVALID, 255)
diff --git a/chromium/net/android/tools/proxy_test_cases.py b/chromium/net/android/tools/proxy_test_cases.py
new file mode 100755
index 00000000000..3d6e8ec7479
--- /dev/null
+++ b/chromium/net/android/tools/proxy_test_cases.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+# Copyright (c) 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.
+
+"""Generator script for proxy tests.
+
+See AndroidProxySelectorTest.java
+and net/proxy/proxy_config_service_android_unittest.cc
+
+To generate C++, run this script without arguments.
+To generate Java, run this script with -j argument.
+
+Note that this generator is not run as part of the build process because
+we are assuming that these test cases will not change often.
+"""
+
+import optparse
+
+test_cases = [
+ {
+ "name": "NoProxy",
+ "description" : "Test direct mapping when no proxy defined.",
+ "properties" : {
+ },
+ "mappings" : {
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyHostAndPort",
+ "description" : "Test http.proxyHost and http.proxyPort works.",
+ "properties" : {
+ "http.proxyHost" : "httpproxy.com",
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY httpproxy.com:8080",
+ "ftp://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyHostOnly",
+ "description" : "We should get the default port (80) for proxied hosts.",
+ "properties" : {
+ "http.proxyHost" : "httpproxy.com",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY httpproxy.com:80",
+ "ftp://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyPortOnly",
+ "description" :
+ "http.proxyPort only should not result in any hosts being proxied.",
+ "properties" : {
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT"
+ }
+ },
+ {
+ "name": "HttpNonProxyHosts1",
+ "description" : "Test that HTTP non proxy hosts are mapped correctly",
+ "properties" : {
+ "http.nonProxyHosts" : "slashdot.org",
+ "http.proxyHost" : "httpproxy.com",
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY httpproxy.com:8080",
+ "http://slashdot.org/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpNonProxyHosts2",
+ "description" : "Test that | pattern works.",
+ "properties" : {
+ "http.nonProxyHosts" : "slashdot.org|freecode.net",
+ "http.proxyHost" : "httpproxy.com",
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY httpproxy.com:8080",
+ "http://slashdot.org/" : "DIRECT",
+ "http://freecode.net/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpNonProxyHosts3",
+ "description" : "Test that * pattern works.",
+ "properties" : {
+ "http.nonProxyHosts" : "*example.com",
+ "http.proxyHost" : "httpproxy.com",
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "DIRECT",
+ "http://www.example.com/" : "DIRECT",
+ "http://slashdot.org/" : "PROXY httpproxy.com:8080",
+ }
+ },
+ {
+ "name": "FtpNonProxyHosts",
+ "description" : "Test that FTP non proxy hosts are mapped correctly",
+ "properties" : {
+ "ftp.nonProxyHosts" : "slashdot.org",
+ "ftp.proxyHost" : "httpproxy.com",
+ "ftp.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "PROXY httpproxy.com:8080",
+ }
+ },
+ {
+ "name": "FtpProxyHostAndPort",
+ "description" : "Test ftp.proxyHost and ftp.proxyPort works.",
+ "properties" : {
+ "ftp.proxyHost" : "httpproxy.com",
+ "ftp.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "ftp://example.com/" : "PROXY httpproxy.com:8080",
+ "http://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "FtpProxyHostOnly",
+ "description" : "Test ftp.proxyHost and default port.",
+ "properties" : {
+ "ftp.proxyHost" : "httpproxy.com",
+ },
+ "mappings" : {
+ "ftp://example.com/" : "PROXY httpproxy.com:80",
+ "http://example.com/" : "DIRECT",
+ "https://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpsProxyHostAndPort",
+ "description" : "Test https.proxyHost and https.proxyPort works.",
+ "properties" : {
+ "https.proxyHost" : "httpproxy.com",
+ "https.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "https://example.com/" : "PROXY httpproxy.com:8080",
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpsProxyHostOnly",
+ "description" : "Test https.proxyHost and default port.",
+ # Chromium differs from the Android platform by connecting to port 80 for
+ # HTTPS connections by default, hence cpp-only.
+ "cpp-only" : "",
+ "properties" : {
+ "https.proxyHost" : "httpproxy.com",
+ },
+ "mappings" : {
+ "https://example.com/" : "PROXY httpproxy.com:80",
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyHostIPv6",
+ "description" : "Test IPv6 https.proxyHost and default port.",
+ "cpp-only" : "",
+ "properties" : {
+ "http.proxyHost" : "a:b:c::d:1",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY [a:b:c::d:1]:80",
+ "ftp://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyHostAndPortIPv6",
+ "description" : "Test IPv6 http.proxyHost and http.proxyPort works.",
+ "cpp-only" : "",
+ "properties" : {
+ "http.proxyHost" : "a:b:c::d:1",
+ "http.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY [a:b:c::d:1]:8080",
+ "ftp://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "HttpProxyHostAndInvalidPort",
+ "description" : "Test invalid http.proxyPort does not crash.",
+ "cpp-only" : "",
+ "properties" : {
+ "http.proxyHost" : "a:b:c::d:1",
+ "http.proxyPort" : "65536",
+ },
+ "mappings" : {
+ "http://example.com/" : "DIRECT",
+ "ftp://example.com/" : "DIRECT",
+ }
+ },
+ {
+ "name": "DefaultProxyExplictPort",
+ "description" :
+ "Default http proxy is used if a scheme-specific one is not found.",
+ "properties" : {
+ "proxyHost" : "defaultproxy.com",
+ "proxyPort" : "8080",
+ "ftp.proxyHost" : "httpproxy.com",
+ "ftp.proxyPort" : "8080",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY defaultproxy.com:8080",
+ "https://example.com/" : "PROXY defaultproxy.com:8080",
+ "ftp://example.com/" : "PROXY httpproxy.com:8080",
+ }
+ },
+ {
+ "name": "DefaultProxyDefaultPort",
+ "description" : "Check that the default proxy port is as expected.",
+ # Chromium differs from the Android platform by connecting to port 80 for
+ # HTTPS connections by default, hence cpp-only.
+ "cpp-only" : "",
+ "properties" : {
+ "proxyHost" : "defaultproxy.com",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY defaultproxy.com:80",
+ "https://example.com/" : "PROXY defaultproxy.com:80",
+ }
+ },
+ {
+ "name": "FallbackToSocks",
+ "description" : "SOCKS proxy is used if scheme-specific one is not found.",
+ "properties" : {
+ "http.proxyHost" : "defaultproxy.com",
+ "socksProxyHost" : "socksproxy.com"
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY defaultproxy.com:80",
+ "https://example.com/" : "SOCKS5 socksproxy.com:1080",
+ "ftp://example.com" : "SOCKS5 socksproxy.com:1080",
+ }
+ },
+ {
+ "name": "SocksExplicitPort",
+ "description" : "SOCKS proxy port is used if specified",
+ "properties" : {
+ "socksProxyHost" : "socksproxy.com",
+ "socksProxyPort" : "9000",
+ },
+ "mappings" : {
+ "http://example.com/" : "SOCKS5 socksproxy.com:9000",
+ }
+ },
+ {
+ "name": "HttpProxySupercedesSocks",
+ "description" : "SOCKS proxy is ignored if default HTTP proxy defined.",
+ "properties" : {
+ "proxyHost" : "defaultproxy.com",
+ "socksProxyHost" : "socksproxy.com",
+ "socksProxyPort" : "9000",
+ },
+ "mappings" : {
+ "http://example.com/" : "PROXY defaultproxy.com:80",
+ }
+ },
+]
+
+class GenerateCPlusPlus:
+ """Generate C++ test cases"""
+
+ def Generate(self):
+ for test_case in test_cases:
+ print ("TEST_F(ProxyConfigServiceAndroidTest, %s) {" % test_case["name"])
+ if "description" in test_case:
+ self._GenerateDescription(test_case["description"]);
+ self._GenerateConfiguration(test_case["properties"])
+ self._GenerateMappings(test_case["mappings"])
+ print "}"
+ print ""
+
+ def _GenerateDescription(self, description):
+ print " // %s" % description
+
+ def _GenerateConfiguration(self, properties):
+ for key in sorted(properties.iterkeys()):
+ print " AddProperty(\"%s\", \"%s\");" % (key, properties[key])
+ print " ProxySettingsChanged();"
+
+ def _GenerateMappings(self, mappings):
+ for url in sorted(mappings.iterkeys()):
+ print " TestMapping(\"%s\", \"%s\");" % (url, mappings[url])
+
+
+class GenerateJava:
+ """Generate Java test cases"""
+
+ def Generate(self):
+ for test_case in test_cases:
+ if test_case.has_key("cpp-only"):
+ continue
+ if "description" in test_case:
+ self._GenerateDescription(test_case["description"]);
+ print " @SmallTest"
+ print " @Feature({\"AndroidWebView\"})"
+ print " public void test%s() throws Exception {" % test_case["name"]
+ self._GenerateConfiguration(test_case["properties"])
+ self._GenerateMappings(test_case["mappings"])
+ print " }"
+ print ""
+
+ def _GenerateDescription(self, description):
+ print " /**"
+ print " * %s" % description
+ print " *"
+ print " * @throws Exception"
+ print " */"
+
+ def _GenerateConfiguration(self, properties):
+ for key in sorted(properties.iterkeys()):
+ print " System.setProperty(\"%s\", \"%s\");" % (
+ key, properties[key])
+
+ def _GenerateMappings(self, mappings):
+ for url in sorted(mappings.iterkeys()):
+ mapping = mappings[url]
+ if 'HTTPS' in mapping:
+ mapping = mapping.replace('HTTPS', 'PROXY')
+ print " checkMapping(\"%s\", \"%s\");" % (url, mapping)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-j", "--java",
+ action="store_true", dest="java");
+ (options, args) = parser.parse_args();
+ if options.java:
+ generator = GenerateJava()
+ else:
+ generator = GenerateCPlusPlus()
+ generator.Generate()
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/net/base/DEPS b/chromium/net/base/DEPS
new file mode 100644
index 00000000000..a9837b3987a
--- /dev/null
+++ b/chromium/net/base/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+third_party/npapi",
+ "+third_party/zlib",
+ "+grit", # For generated headers
+]
diff --git a/chromium/net/base/OWNERS b/chromium/net/base/OWNERS
new file mode 100644
index 00000000000..7ce152b5b12
--- /dev/null
+++ b/chromium/net/base/OWNERS
@@ -0,0 +1,2 @@
+per-file mime_sniffer*=set noparent
+per-file mime_sniffer*=abarth@chromium.org
diff --git a/chromium/net/base/address_family.h b/chromium/net/base/address_family.h
new file mode 100644
index 00000000000..75beb29d649
--- /dev/null
+++ b/chromium/net/base/address_family.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2010 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_BASE_ADDRESS_FAMILY_H_
+#define NET_BASE_ADDRESS_FAMILY_H_
+
+namespace net {
+
+// Enum wrapper around the address family types supported by host resolver
+// procedures.
+enum AddressFamily {
+ ADDRESS_FAMILY_UNSPECIFIED, // AF_UNSPEC
+ ADDRESS_FAMILY_IPV4, // AF_INET
+ ADDRESS_FAMILY_IPV6, // AF_INET6
+};
+
+// HostResolverFlags is a bitflag enum used by host resolver procedures to
+// determine the value of addrinfo.ai_flags and work around getaddrinfo
+// peculiarities.
+enum {
+ HOST_RESOLVER_CANONNAME = 1 << 0, // AI_CANONNAME
+ // Hint to the resolver proc that only loopback addresses are configured.
+ HOST_RESOLVER_LOOPBACK_ONLY = 1 << 1,
+ // Indicate the address family was set because no IPv6 support was detected.
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6 = 1 << 2,
+};
+typedef int HostResolverFlags;
+
+} // namespace net
+
+#endif // NET_BASE_ADDRESS_FAMILY_H_
diff --git a/chromium/net/base/address_list.cc b/chromium/net/base/address_list.cc
new file mode 100644
index 00000000000..02a762bf76a
--- /dev/null
+++ b/chromium/net/base/address_list.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 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.
+
+#include "net/base/address_list.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogAddressListCallback(const AddressList* address_list,
+ NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* list = new base::ListValue();
+
+ for (AddressList::const_iterator it = address_list->begin();
+ it != address_list->end(); ++it) {
+ list->Append(new base::StringValue(it->ToString()));
+ }
+
+ dict->Set("address_list", list);
+ return dict;
+}
+
+} // namespace
+
+AddressList::AddressList() {}
+
+AddressList::~AddressList() {}
+
+AddressList::AddressList(const IPEndPoint& endpoint) {
+ push_back(endpoint);
+}
+
+// static
+AddressList AddressList::CreateFromIPAddress(const IPAddressNumber& address,
+ uint16 port) {
+ return AddressList(IPEndPoint(address, port));
+}
+
+// static
+AddressList AddressList::CreateFromIPAddressList(
+ const IPAddressList& addresses,
+ const std::string& canonical_name) {
+ AddressList list;
+ list.set_canonical_name(canonical_name);
+ for (IPAddressList::const_iterator iter = addresses.begin();
+ iter != addresses.end(); ++iter) {
+ list.push_back(IPEndPoint(*iter, 0));
+ }
+ return list;
+}
+
+// static
+AddressList AddressList::CreateFromAddrinfo(const struct addrinfo* head) {
+ DCHECK(head);
+ AddressList list;
+ if (head->ai_canonname)
+ list.set_canonical_name(std::string(head->ai_canonname));
+ for (const struct addrinfo* ai = head; ai; ai = ai->ai_next) {
+ IPEndPoint ipe;
+ // NOTE: Ignoring non-INET* families.
+ if (ipe.FromSockAddr(ai->ai_addr, ai->ai_addrlen))
+ list.push_back(ipe);
+ else
+ DLOG(WARNING) << "Unknown family found in addrinfo: " << ai->ai_family;
+ }
+ return list;
+}
+
+// static
+AddressList AddressList::CopyWithPort(const AddressList& list, uint16 port) {
+ AddressList out;
+ out.set_canonical_name(list.canonical_name());
+ for (size_t i = 0; i < list.size(); ++i)
+ out.push_back(IPEndPoint(list[i].address(), port));
+ return out;
+}
+
+void AddressList::SetDefaultCanonicalName() {
+ DCHECK(!empty());
+ set_canonical_name(front().ToStringWithoutPort());
+}
+
+NetLog::ParametersCallback AddressList::CreateNetLogCallback() const {
+ return base::Bind(&NetLogAddressListCallback, this);
+}
+
+} // namespace net
diff --git a/chromium/net/base/address_list.h b/chromium/net/base/address_list.h
new file mode 100644
index 00000000000..1487e11c306
--- /dev/null
+++ b/chromium/net/base/address_list.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_ADDRESS_LIST_H_
+#define NET_BASE_ADDRESS_LIST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+
+struct addrinfo;
+
+namespace net {
+
+class NET_EXPORT AddressList
+ : NON_EXPORTED_BASE(private std::vector<IPEndPoint>) {
+ public:
+ AddressList();
+ ~AddressList();
+
+ // Creates an address list for a single IP literal.
+ explicit AddressList(const IPEndPoint& endpoint);
+
+ static AddressList CreateFromIPAddress(const IPAddressNumber& address,
+ uint16 port);
+
+ static AddressList CreateFromIPAddressList(
+ const IPAddressList& addresses,
+ const std::string& canonical_name);
+
+ // Copies the data from |head| and the chained list into an AddressList.
+ static AddressList CreateFromAddrinfo(const struct addrinfo* head);
+
+ // Returns a copy of |list| with port on each element set to |port|.
+ static AddressList CopyWithPort(const AddressList& list, uint16 port);
+
+ // TODO(szym): Remove all three. http://crbug.com/126134
+ const std::string& canonical_name() const {
+ return canonical_name_;
+ }
+
+ void set_canonical_name(const std::string& canonical_name) {
+ canonical_name_ = canonical_name;
+ }
+
+ // Sets canonical name to the literal of the first IP address on the list.
+ void SetDefaultCanonicalName();
+
+ // Creates a callback for use with the NetLog that returns a Value
+ // representation of the address list. The callback must be destroyed before
+ // |this| is.
+ NetLog::ParametersCallback CreateNetLogCallback() const;
+
+ // Exposed methods from std::vector.
+ using std::vector<IPEndPoint>::size;
+ using std::vector<IPEndPoint>::empty;
+ using std::vector<IPEndPoint>::clear;
+ using std::vector<IPEndPoint>::reserve;
+ using std::vector<IPEndPoint>::capacity;
+ using std::vector<IPEndPoint>::operator[];
+ using std::vector<IPEndPoint>::front;
+ using std::vector<IPEndPoint>::back;
+ using std::vector<IPEndPoint>::push_back;
+ using std::vector<IPEndPoint>::insert;
+ using std::vector<IPEndPoint>::erase;
+ using std::vector<IPEndPoint>::iterator;
+ using std::vector<IPEndPoint>::const_iterator;
+ using std::vector<IPEndPoint>::begin;
+ using std::vector<IPEndPoint>::end;
+ using std::vector<IPEndPoint>::rbegin;
+ using std::vector<IPEndPoint>::rend;
+
+ private:
+ // TODO(szym): Remove. http://crbug.com/126134
+ std::string canonical_name_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_ADDRESS_LIST_H_
diff --git a/chromium/net/base/address_list_unittest.cc b/chromium/net/base/address_list_unittest.cc
new file mode 100644
index 00000000000..32c3e521642
--- /dev/null
+++ b/chromium/net/base/address_list_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 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.
+
+#include "net/base/address_list.h"
+
+#include "base/strings/string_util.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_util.h"
+#include "net/base/sys_addrinfo.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+static const char* kCanonicalHostname = "canonical.bar.com";
+
+TEST(AddressListTest, Canonical) {
+ // Create an addrinfo with a canonical name.
+ struct sockaddr_in address;
+ // The contents of address do not matter for this test,
+ // so just zero-ing them out for consistency.
+ memset(&address, 0x0, sizeof(address));
+ // But we need to set the family.
+ address.sin_family = AF_INET;
+ struct addrinfo ai;
+ memset(&ai, 0x0, sizeof(ai));
+ ai.ai_family = AF_INET;
+ ai.ai_socktype = SOCK_STREAM;
+ ai.ai_addrlen = sizeof(address);
+ ai.ai_addr = reinterpret_cast<sockaddr*>(&address);
+ ai.ai_canonname = const_cast<char *>(kCanonicalHostname);
+
+ // Copy the addrinfo struct into an AddressList object and
+ // make sure it seems correct.
+ AddressList addrlist1 = AddressList::CreateFromAddrinfo(&ai);
+ EXPECT_EQ("canonical.bar.com", addrlist1.canonical_name());
+
+ // Copy the AddressList to another one.
+ AddressList addrlist2 = addrlist1;
+ EXPECT_EQ("canonical.bar.com", addrlist2.canonical_name());
+}
+
+TEST(AddressListTest, CreateFromAddrinfo) {
+ // Create an 4-element addrinfo.
+ const unsigned kNumElements = 4;
+ SockaddrStorage storage[kNumElements];
+ struct addrinfo ai[kNumElements];
+ for (unsigned i = 0; i < kNumElements; ++i) {
+ struct sockaddr_in* addr =
+ reinterpret_cast<struct sockaddr_in*>(storage[i].addr);
+ storage[i].addr_len = sizeof(struct sockaddr_in);
+ // Populating the address with { i, i, i, i }.
+ memset(&addr->sin_addr, i, kIPv4AddressSize);
+ addr->sin_family = AF_INET;
+ // Set port to i << 2;
+ addr->sin_port = base::HostToNet16(static_cast<uint16>(i << 2));
+ memset(&ai[i], 0x0, sizeof(ai[i]));
+ ai[i].ai_family = addr->sin_family;
+ ai[i].ai_socktype = SOCK_STREAM;
+ ai[i].ai_addrlen = storage[i].addr_len;
+ ai[i].ai_addr = storage[i].addr;
+ if (i + 1 < kNumElements)
+ ai[i].ai_next = &ai[i + 1];
+ }
+
+ AddressList list = AddressList::CreateFromAddrinfo(&ai[0]);
+
+ ASSERT_EQ(kNumElements, list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, list[i].GetFamily());
+ // Only check the first byte of the address.
+ EXPECT_EQ(i, list[i].address()[0]);
+ EXPECT_EQ(static_cast<int>(i << 2), list[i].port());
+ }
+
+ // Check if operator= works.
+ AddressList copy;
+ copy = list;
+ ASSERT_EQ(kNumElements, copy.size());
+
+ // Check if copy is independent.
+ copy[1] = IPEndPoint(copy[2].address(), 0xBEEF);
+ // Original should be unchanged.
+ EXPECT_EQ(1u, list[1].address()[0]);
+ EXPECT_EQ(1 << 2, list[1].port());
+}
+
+TEST(AddressListTest, CreateFromIPAddressList) {
+ struct TestData {
+ std::string ip_address;
+ const char* in_addr;
+ int ai_family;
+ size_t ai_addrlen;
+ size_t in_addr_offset;
+ size_t in_addr_size;
+ } tests[] = {
+ { "127.0.0.1",
+ "\x7f\x00\x00\x01",
+ AF_INET,
+ sizeof(struct sockaddr_in),
+ offsetof(struct sockaddr_in, sin_addr),
+ sizeof(struct in_addr),
+ },
+ { "2001:db8:0::42",
+ "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42",
+ AF_INET6,
+ sizeof(struct sockaddr_in6),
+ offsetof(struct sockaddr_in6, sin6_addr),
+ sizeof(struct in6_addr),
+ },
+ { "192.168.1.1",
+ "\xc0\xa8\x01\x01",
+ AF_INET,
+ sizeof(struct sockaddr_in),
+ offsetof(struct sockaddr_in, sin_addr),
+ sizeof(struct in_addr),
+ },
+ };
+ const std::string kCanonicalName = "canonical.example.com";
+
+ // Construct a list of ip addresses.
+ IPAddressList ip_list;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ IPAddressNumber ip_number;
+ ASSERT_TRUE(ParseIPLiteralToNumber(tests[i].ip_address, &ip_number));
+ ip_list.push_back(ip_number);
+ }
+
+ AddressList test_list = AddressList::CreateFromIPAddressList(ip_list,
+ kCanonicalName);
+ std::string canonical_name;
+ EXPECT_EQ(kCanonicalName, test_list.canonical_name());
+ EXPECT_EQ(ARRAYSIZE_UNSAFE(tests), test_list.size());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/base/address_tracker_linux.cc b/chromium/net/base/address_tracker_linux.cc
new file mode 100644
index 00000000000..886811a7919
--- /dev/null
+++ b/chromium/net/base/address_tracker_linux.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 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.
+
+#include "net/base/address_tracker_linux.h"
+
+#include <errno.h>
+#include <linux/if.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/network_change_notifier_linux.h"
+
+namespace net {
+namespace internal {
+
+namespace {
+
+// Retrieves address from NETLINK address message.
+bool GetAddress(const struct nlmsghdr* header, IPAddressNumber* out) {
+ const struct ifaddrmsg* msg =
+ reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
+ size_t address_length = 0;
+ switch (msg->ifa_family) {
+ case AF_INET:
+ address_length = kIPv4AddressSize;
+ break;
+ case AF_INET6:
+ address_length = kIPv6AddressSize;
+ break;
+ default:
+ // Unknown family.
+ return false;
+ }
+ // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on
+ // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of
+ // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6
+ // have the IFA_LOCAL attribute.
+ unsigned char* address = NULL;
+ unsigned char* local = NULL;
+ size_t length = IFA_PAYLOAD(header);
+ for (const struct rtattr* attr =
+ reinterpret_cast<const struct rtattr*>(IFA_RTA(msg));
+ RTA_OK(attr, length);
+ attr = RTA_NEXT(attr, length)) {
+ switch (attr->rta_type) {
+ case IFA_ADDRESS:
+ DCHECK_GE(RTA_PAYLOAD(attr), address_length);
+ address = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
+ break;
+ case IFA_LOCAL:
+ DCHECK_GE(RTA_PAYLOAD(attr), address_length);
+ local = reinterpret_cast<unsigned char*>(RTA_DATA(attr));
+ break;
+ default:
+ break;
+ }
+ }
+ if (local)
+ address = local;
+ if (!address)
+ return false;
+ out->assign(address, address + address_length);
+ return true;
+}
+
+} // namespace
+
+AddressTrackerLinux::AddressTrackerLinux(const base::Closure& address_callback,
+ const base::Closure& link_callback)
+ : address_callback_(address_callback),
+ link_callback_(link_callback),
+ netlink_fd_(-1),
+ is_offline_(true),
+ is_offline_initialized_(false),
+ is_offline_initialized_cv_(&is_offline_lock_) {
+ DCHECK(!address_callback.is_null());
+ DCHECK(!link_callback.is_null());
+}
+
+AddressTrackerLinux::~AddressTrackerLinux() {
+ CloseSocket();
+}
+
+void AddressTrackerLinux::Init() {
+ netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (netlink_fd_ < 0) {
+ PLOG(ERROR) << "Could not create NETLINK socket";
+ AbortAndForceOnline();
+ return;
+ }
+
+ // Request notifications.
+ struct sockaddr_nl addr = {};
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pid = getpid();
+ // TODO(szym): Track RTMGRP_LINK as well for ifi_type, http://crbug.com/113993
+ addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY |
+ RTMGRP_LINK;
+ int rv = bind(netlink_fd_,
+ reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr));
+ if (rv < 0) {
+ PLOG(ERROR) << "Could not bind NETLINK socket";
+ AbortAndForceOnline();
+ return;
+ }
+
+ // Request dump of addresses.
+ struct sockaddr_nl peer = {};
+ peer.nl_family = AF_NETLINK;
+
+ struct {
+ struct nlmsghdr header;
+ struct rtgenmsg msg;
+ } request = {};
+
+ request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
+ request.header.nlmsg_type = RTM_GETADDR;
+ request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ request.header.nlmsg_pid = getpid();
+ request.msg.rtgen_family = AF_UNSPEC;
+
+ rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len,
+ 0, reinterpret_cast<struct sockaddr*>(&peer),
+ sizeof(peer)));
+ if (rv < 0) {
+ PLOG(ERROR) << "Could not send NETLINK request";
+ AbortAndForceOnline();
+ return;
+ }
+
+ // Consume pending message to populate the AddressMap, but don't notify.
+ // Sending another request without first reading responses results in EBUSY.
+ bool address_changed;
+ bool link_changed;
+ ReadMessages(&address_changed, &link_changed);
+
+ // Request dump of link state
+ request.header.nlmsg_type = RTM_GETLINK;
+
+ rv = HANDLE_EINTR(sendto(netlink_fd_, &request, request.header.nlmsg_len, 0,
+ reinterpret_cast<struct sockaddr*>(&peer),
+ sizeof(peer)));
+ if (rv < 0) {
+ PLOG(ERROR) << "Could not send NETLINK request";
+ AbortAndForceOnline();
+ return;
+ }
+
+ // Consume pending message to populate links_online_, but don't notify.
+ ReadMessages(&address_changed, &link_changed);
+ {
+ base::AutoLock lock(is_offline_lock_);
+ is_offline_initialized_ = true;
+ is_offline_initialized_cv_.Signal();
+ }
+
+ rv = base::MessageLoopForIO::current()->WatchFileDescriptor(
+ netlink_fd_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this);
+ if (rv < 0) {
+ PLOG(ERROR) << "Could not watch NETLINK socket";
+ AbortAndForceOnline();
+ return;
+ }
+}
+
+void AddressTrackerLinux::AbortAndForceOnline() {
+ CloseSocket();
+ base::AutoLock lock(is_offline_lock_);
+ is_offline_ = false;
+ is_offline_initialized_ = true;
+ is_offline_initialized_cv_.Signal();
+}
+
+AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const {
+ base::AutoLock lock(address_map_lock_);
+ return address_map_;
+}
+
+NetworkChangeNotifier::ConnectionType
+AddressTrackerLinux::GetCurrentConnectionType() {
+ // http://crbug.com/125097
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ base::AutoLock lock(is_offline_lock_);
+ // Make sure the initial offline state is set before returning.
+ while (!is_offline_initialized_) {
+ is_offline_initialized_cv_.Wait();
+ }
+ // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
+ // http://crbug.com/160537
+ return is_offline_ ? NetworkChangeNotifier::CONNECTION_NONE :
+ NetworkChangeNotifier::CONNECTION_UNKNOWN;
+}
+
+void AddressTrackerLinux::ReadMessages(bool* address_changed,
+ bool* link_changed) {
+ *address_changed = false;
+ *link_changed = false;
+ char buffer[4096];
+ bool first_loop = true;
+ for (;;) {
+ int rv = HANDLE_EINTR(recv(netlink_fd_,
+ buffer,
+ sizeof(buffer),
+ // Block the first time through loop.
+ first_loop ? 0 : MSG_DONTWAIT));
+ first_loop = false;
+ if (rv == 0) {
+ LOG(ERROR) << "Unexpected shutdown of NETLINK socket.";
+ return;
+ }
+ if (rv < 0) {
+ if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+ break;
+ PLOG(ERROR) << "Failed to recv from netlink socket";
+ return;
+ }
+ HandleMessage(buffer, rv, address_changed, link_changed);
+ };
+ if (*link_changed) {
+ base::AutoLock lock(is_offline_lock_);
+ is_offline_ = online_links_.empty();
+ }
+}
+
+void AddressTrackerLinux::HandleMessage(const char* buffer,
+ size_t length,
+ bool* address_changed,
+ bool* link_changed) {
+ DCHECK(buffer);
+ for (const struct nlmsghdr* header =
+ reinterpret_cast<const struct nlmsghdr*>(buffer);
+ NLMSG_OK(header, length);
+ header = NLMSG_NEXT(header, length)) {
+ switch (header->nlmsg_type) {
+ case NLMSG_DONE:
+ return;
+ case NLMSG_ERROR: {
+ const struct nlmsgerr* msg =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header));
+ LOG(ERROR) << "Unexpected netlink error " << msg->error << ".";
+ } return;
+ case RTM_NEWADDR: {
+ IPAddressNumber address;
+ if (GetAddress(header, &address)) {
+ base::AutoLock lock(address_map_lock_);
+ const struct ifaddrmsg* msg =
+ reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(header));
+ // Only indicate change if the address is new or ifaddrmsg info has
+ // changed.
+ AddressMap::iterator it = address_map_.find(address);
+ if (it == address_map_.end()) {
+ address_map_.insert(it, std::make_pair(address, *msg));
+ *address_changed = true;
+ } else if (memcmp(&it->second, msg, sizeof(*msg))) {
+ it->second = *msg;
+ *address_changed = true;
+ }
+ }
+ } break;
+ case RTM_DELADDR: {
+ IPAddressNumber address;
+ if (GetAddress(header, &address)) {
+ base::AutoLock lock(address_map_lock_);
+ if (address_map_.erase(address))
+ *address_changed = true;
+ }
+ } break;
+ case RTM_NEWLINK: {
+ const struct ifinfomsg* msg =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
+ if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) &&
+ (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) {
+ if (online_links_.insert(msg->ifi_index).second)
+ *link_changed = true;
+ } else {
+ if (online_links_.erase(msg->ifi_index))
+ *link_changed = true;
+ }
+ } break;
+ case RTM_DELLINK: {
+ const struct ifinfomsg* msg =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
+ if (online_links_.erase(msg->ifi_index))
+ *link_changed = true;
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+void AddressTrackerLinux::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK_EQ(netlink_fd_, fd);
+ bool address_changed;
+ bool link_changed;
+ ReadMessages(&address_changed, &link_changed);
+ if (address_changed)
+ address_callback_.Run();
+ if (link_changed)
+ link_callback_.Run();
+}
+
+void AddressTrackerLinux::OnFileCanWriteWithoutBlocking(int /* fd */) {}
+
+void AddressTrackerLinux::CloseSocket() {
+ if (netlink_fd_ >= 0 && HANDLE_EINTR(close(netlink_fd_)) < 0)
+ PLOG(ERROR) << "Could not close NETLINK socket.";
+ netlink_fd_ = -1;
+}
+
+} // namespace internal
+} // namespace net
diff --git a/chromium/net/base/address_tracker_linux.h b/chromium/net/base/address_tracker_linux.h
new file mode 100644
index 00000000000..e5ab692c233
--- /dev/null
+++ b/chromium/net/base/address_tracker_linux.h
@@ -0,0 +1,99 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_ADDRESS_TRACKER_LINUX_H_
+#define NET_BASE_ADDRESS_TRACKER_LINUX_H_
+
+#include <sys/socket.h> // Needed to include netlink.
+// Mask superfluous definition of |struct net|. This is fixed in Linux 2.6.38.
+#define net net_kernel
+#include <linux/rtnetlink.h>
+#undef net
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+namespace internal {
+
+// Keeps track of network interface addresses using rtnetlink. Used by
+// NetworkChangeNotifier to provide signals to registered IPAddressObservers.
+class NET_EXPORT_PRIVATE AddressTrackerLinux :
+ public base::MessageLoopForIO::Watcher {
+ public:
+ typedef std::map<IPAddressNumber, struct ifaddrmsg> AddressMap;
+
+ // Will run |address_callback| when the AddressMap changes and will run
+ // |link_callback| when the list of online links changes.
+ AddressTrackerLinux(const base::Closure& address_callback,
+ const base::Closure& link_callback);
+ virtual ~AddressTrackerLinux();
+
+ // Starts watching system configuration for changes. The current thread must
+ // have a MessageLoopForIO.
+ void Init();
+
+ AddressMap GetAddressMap() const;
+
+ // Implementation of NetworkChangeNotifierLinux::GetCurrentConnectionType().
+ // Safe to call from any thread, but will block until Init() has completed.
+ NetworkChangeNotifier::ConnectionType GetCurrentConnectionType();
+
+ private:
+ friend class AddressTrackerLinuxTest;
+
+ // Sets |*address_changed| to indicate whether |address_map_| changed and
+ // sets |*link_changed| to indicate if |online_links_| changed while reading
+ // messages from |netlink_fd_|.
+ void ReadMessages(bool* address_changed, bool* link_changed);
+
+ // Sets |*address_changed| to true if |address_map_| changed, sets
+ // |*link_changed| to true if |online_links_| changed while reading the
+ // message from |buffer|.
+ void HandleMessage(const char* buffer,
+ size_t length,
+ bool* address_changed,
+ bool* link_changed);
+
+ // Call when some part of initialization failed; forces online and unblocks.
+ void AbortAndForceOnline();
+
+ // MessageLoopForIO::Watcher:
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE;
+
+ // Close |netlink_fd_|
+ void CloseSocket();
+
+ base::Closure address_callback_;
+ base::Closure link_callback_;
+
+ int netlink_fd_;
+ base::MessageLoopForIO::FileDescriptorWatcher watcher_;
+
+ mutable base::Lock address_map_lock_;
+ AddressMap address_map_;
+
+ // Set of interface indices for links that are currently online.
+ base::hash_set<int> online_links_;
+
+ base::Lock is_offline_lock_;
+ bool is_offline_;
+ bool is_offline_initialized_;
+ base::ConditionVariable is_offline_initialized_cv_;
+};
+
+} // namespace internal
+} // namespace net
+
+#endif // NET_BASE_ADDRESS_TRACKER_LINUX_H_
diff --git a/chromium/net/base/address_tracker_linux_unittest.cc b/chromium/net/base/address_tracker_linux_unittest.cc
new file mode 100644
index 00000000000..a9feed491dc
--- /dev/null
+++ b/chromium/net/base/address_tracker_linux_unittest.cc
@@ -0,0 +1,366 @@
+// Copyright (c) 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.
+
+#include "net/base/address_tracker_linux.h"
+
+#include <linux/if.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace internal {
+
+typedef std::vector<char> Buffer;
+
+void Noop() {}
+
+class AddressTrackerLinuxTest : public testing::Test {
+ protected:
+ AddressTrackerLinuxTest() : tracker_(base::Bind(&Noop), base::Bind(&Noop)) {}
+
+ bool HandleAddressMessage(const Buffer& buf) {
+ bool address_changed = false;
+ bool link_changed = false;
+ tracker_.HandleMessage(&buf[0], buf.size(),
+ &address_changed, &link_changed);
+ EXPECT_FALSE(link_changed);
+ return address_changed;
+ }
+
+ bool HandleLinkMessage(const Buffer& buf) {
+ bool address_changed = false;
+ bool link_changed = false;
+ tracker_.HandleMessage(&buf[0], buf.size(),
+ &address_changed, &link_changed);
+ EXPECT_FALSE(address_changed);
+ return link_changed;
+ }
+
+ AddressTrackerLinux::AddressMap GetAddressMap() {
+ return tracker_.GetAddressMap();
+ }
+
+ const base::hash_set<int>* GetOnlineLinks() const {
+ return &tracker_.online_links_;
+ }
+
+ AddressTrackerLinux tracker_;
+};
+
+namespace {
+
+class NetlinkMessage {
+ public:
+ explicit NetlinkMessage(uint16 type) : buffer_(NLMSG_HDRLEN) {
+ header()->nlmsg_type = type;
+ Align();
+ }
+
+ void AddPayload(const void* data, size_t length) {
+ CHECK_EQ(static_cast<size_t>(NLMSG_HDRLEN),
+ buffer_.size()) << "Payload must be added first";
+ Append(data, length);
+ Align();
+ }
+
+ void AddAttribute(uint16 type, const void* data, size_t length) {
+ struct nlattr attr;
+ attr.nla_len = NLA_HDRLEN + length;
+ attr.nla_type = type;
+ Append(&attr, sizeof(attr));
+ Align();
+ Append(data, length);
+ Align();
+ }
+
+ void AppendTo(Buffer* output) const {
+ CHECK_EQ(NLMSG_ALIGN(output->size()), output->size());
+ output->reserve(output->size() + NLMSG_LENGTH(buffer_.size()));
+ output->insert(output->end(), buffer_.begin(), buffer_.end());
+ }
+
+ private:
+ void Append(const void* data, size_t length) {
+ const char* chardata = reinterpret_cast<const char*>(data);
+ buffer_.insert(buffer_.end(), chardata, chardata + length);
+ }
+
+ void Align() {
+ header()->nlmsg_len = buffer_.size();
+ buffer_.insert(buffer_.end(), NLMSG_ALIGN(buffer_.size()) - buffer_.size(),
+ 0);
+ CHECK(NLMSG_OK(header(), buffer_.size()));
+ }
+
+ struct nlmsghdr* header() {
+ return reinterpret_cast<struct nlmsghdr*>(&buffer_[0]);
+ }
+
+ Buffer buffer_;
+};
+
+void MakeAddrMessage(uint16 type,
+ uint8 flags,
+ uint8 family,
+ const IPAddressNumber& address,
+ const IPAddressNumber& local,
+ Buffer* output) {
+ NetlinkMessage nlmsg(type);
+ struct ifaddrmsg msg = {};
+ msg.ifa_family = family;
+ msg.ifa_flags = flags;
+ nlmsg.AddPayload(&msg, sizeof(msg));
+ if (address.size())
+ nlmsg.AddAttribute(IFA_ADDRESS, &address[0], address.size());
+ if (local.size())
+ nlmsg.AddAttribute(IFA_LOCAL, &local[0], local.size());
+ nlmsg.AppendTo(output);
+}
+
+void MakeLinkMessage(uint16 type, uint32 flags, uint32 index, Buffer* output) {
+ NetlinkMessage nlmsg(type);
+ struct ifinfomsg msg = {};
+ msg.ifi_index = index;
+ msg.ifi_flags = flags;
+ nlmsg.AddPayload(&msg, sizeof(msg));
+ output->clear();
+ nlmsg.AppendTo(output);
+}
+
+const unsigned char kAddress0[] = { 127, 0, 0, 1 };
+const unsigned char kAddress1[] = { 10, 0, 0, 1 };
+const unsigned char kAddress2[] = { 192, 168, 0, 1 };
+const unsigned char kAddress3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1 };
+const unsigned char kAddress4[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255,
+ 169, 254, 0, 1 };
+
+TEST_F(AddressTrackerLinuxTest, NewAddress) {
+ const IPAddressNumber kEmpty;
+ const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+ const IPAddressNumber kAddr1(kAddress1, kAddress1 + arraysize(kAddress1));
+ const IPAddressNumber kAddr2(kAddress2, kAddress2 + arraysize(kAddress2));
+ const IPAddressNumber kAddr3(kAddress3, kAddress3 + arraysize(kAddress3));
+
+ Buffer buffer;
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ AddressTrackerLinux::AddressMap map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr0));
+ EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+ buffer.clear();
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr1, kAddr2,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(2u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr0));
+ EXPECT_EQ(1u, map.count(kAddr2));
+ EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr2].ifa_flags);
+
+ buffer.clear();
+ MakeAddrMessage(RTM_NEWADDR, 0, AF_INET6, kEmpty, kAddr3, &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(3u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr3));
+}
+
+TEST_F(AddressTrackerLinuxTest, NewAddressChange) {
+ const IPAddressNumber kEmpty;
+ const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+
+ Buffer buffer;
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ AddressTrackerLinux::AddressMap map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr0));
+ EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+ buffer.clear();
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr0, kEmpty,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr0));
+ EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr0].ifa_flags);
+
+ // Both messages in one buffer.
+ buffer.clear();
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kEmpty,
+ &buffer);
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_HOMEADDRESS, AF_INET, kAddr0, kEmpty,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(IFA_F_HOMEADDRESS, map[kAddr0].ifa_flags);
+}
+
+TEST_F(AddressTrackerLinuxTest, NewAddressDuplicate) {
+ const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+
+ Buffer buffer;
+ MakeAddrMessage(RTM_NEWADDR, IFA_F_TEMPORARY, AF_INET, kAddr0, kAddr0,
+ &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ AddressTrackerLinux::AddressMap map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(1u, map.count(kAddr0));
+ EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+
+ EXPECT_FALSE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(IFA_F_TEMPORARY, map[kAddr0].ifa_flags);
+}
+
+TEST_F(AddressTrackerLinuxTest, DeleteAddress) {
+ const IPAddressNumber kEmpty;
+ const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+ const IPAddressNumber kAddr1(kAddress1, kAddress1 + arraysize(kAddress1));
+ const IPAddressNumber kAddr2(kAddress2, kAddress2 + arraysize(kAddress2));
+
+ Buffer buffer;
+ MakeAddrMessage(RTM_NEWADDR, 0, AF_INET, kAddr0, kEmpty, &buffer);
+ MakeAddrMessage(RTM_NEWADDR, 0, AF_INET, kAddr1, kAddr2, &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ AddressTrackerLinux::AddressMap map = GetAddressMap();
+ EXPECT_EQ(2u, map.size());
+
+ buffer.clear();
+ MakeAddrMessage(RTM_DELADDR, 0, AF_INET, kEmpty, kAddr0, &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+ EXPECT_EQ(0u, map.count(kAddr0));
+ EXPECT_EQ(1u, map.count(kAddr2));
+
+ buffer.clear();
+ MakeAddrMessage(RTM_DELADDR, 0, AF_INET, kAddr2, kAddr1, &buffer);
+ // kAddr1 does not exist in the map.
+ EXPECT_FALSE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(1u, map.size());
+
+ buffer.clear();
+ MakeAddrMessage(RTM_DELADDR, 0, AF_INET, kAddr2, kEmpty, &buffer);
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ map = GetAddressMap();
+ EXPECT_EQ(0u, map.size());
+}
+
+TEST_F(AddressTrackerLinuxTest, IgnoredMessage) {
+ const IPAddressNumber kEmpty;
+ const IPAddressNumber kAddr0(kAddress0, kAddress0 + arraysize(kAddress0));
+ const IPAddressNumber kAddr3(kAddress3, kAddress3 + arraysize(kAddress3));
+
+ Buffer buffer;
+ // Ignored family.
+ MakeAddrMessage(RTM_NEWADDR, 0, AF_UNSPEC, kAddr3, kAddr0, &buffer);
+ // No address.
+ MakeAddrMessage(RTM_NEWADDR, 0, AF_INET, kEmpty, kEmpty, &buffer);
+ // Ignored type.
+ MakeAddrMessage(RTM_DELROUTE, 0, AF_INET6, kAddr3, kEmpty, &buffer);
+ EXPECT_FALSE(HandleAddressMessage(buffer));
+ EXPECT_TRUE(GetAddressMap().empty());
+
+ // Valid message after ignored messages.
+ NetlinkMessage nlmsg(RTM_NEWADDR);
+ struct ifaddrmsg msg = {};
+ msg.ifa_family = AF_INET;
+ nlmsg.AddPayload(&msg, sizeof(msg));
+ // Ignored attribute.
+ struct ifa_cacheinfo cache_info = {};
+ nlmsg.AddAttribute(IFA_CACHEINFO, &cache_info, sizeof(cache_info));
+ nlmsg.AddAttribute(IFA_ADDRESS, &kAddr0[0], kAddr0.size());
+ nlmsg.AppendTo(&buffer);
+
+ EXPECT_TRUE(HandleAddressMessage(buffer));
+ EXPECT_EQ(1u, GetAddressMap().size());
+}
+
+TEST_F(AddressTrackerLinuxTest, AddInterface) {
+ Buffer buffer;
+
+ // Ignores loopback.
+ MakeLinkMessage(RTM_NEWLINK,
+ IFF_LOOPBACK | IFF_UP | IFF_LOWER_UP | IFF_RUNNING,
+ 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Ignores not IFF_LOWER_UP.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Ignores deletion.
+ MakeLinkMessage(RTM_DELLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Verify success.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_EQ(1u, GetOnlineLinks()->count(0));
+ EXPECT_EQ(1u, GetOnlineLinks()->size());
+
+ // Ignores redundant enables.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_EQ(1u, GetOnlineLinks()->count(0));
+ EXPECT_EQ(1u, GetOnlineLinks()->size());
+
+ // Verify adding another online device (e.g. VPN) is considered a change.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 1, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_EQ(1u, GetOnlineLinks()->count(0));
+ EXPECT_EQ(1u, GetOnlineLinks()->count(1));
+ EXPECT_EQ(2u, GetOnlineLinks()->size());
+}
+
+TEST_F(AddressTrackerLinuxTest, RemoveInterface) {
+ Buffer buffer;
+
+ // Should disappear when not IFF_LOWER_UP.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_FALSE(GetOnlineLinks()->empty());
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Ignores redundant disables.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Ignores deleting down interfaces.
+ MakeLinkMessage(RTM_DELLINK, IFF_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_FALSE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+
+ // Should disappear when deleted.
+ MakeLinkMessage(RTM_NEWLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_FALSE(GetOnlineLinks()->empty());
+ MakeLinkMessage(RTM_DELLINK, IFF_UP | IFF_LOWER_UP | IFF_RUNNING, 0, &buffer);
+ EXPECT_TRUE(HandleLinkMessage(buffer));
+ EXPECT_TRUE(GetOnlineLinks()->empty());
+}
+
+} // namespace
+
+} // namespace internal
+} // namespace net
diff --git a/chromium/net/base/auth.cc b/chromium/net/base/auth.cc
new file mode 100644
index 00000000000..332e1883855
--- /dev/null
+++ b/chromium/net/base/auth.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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/base/auth.h"
+#include "net/base/zap.h"
+
+namespace net {
+
+AuthChallengeInfo::AuthChallengeInfo() : is_proxy(false) {
+}
+
+bool AuthChallengeInfo::Equals(const AuthChallengeInfo& that) const {
+ return (this->is_proxy == that.is_proxy &&
+ this->challenger.Equals(that.challenger) &&
+ this->scheme == that.scheme &&
+ this->realm == that.realm);
+}
+
+AuthChallengeInfo::~AuthChallengeInfo() {
+}
+
+AuthData::AuthData() : state(AUTH_STATE_NEED_AUTH) {
+}
+
+AuthData::~AuthData() {
+}
+
+AuthCredentials::AuthCredentials() {
+}
+
+AuthCredentials::AuthCredentials(const base::string16& username,
+ const base::string16& password)
+ : username_(username),
+ password_(password) {
+}
+
+AuthCredentials::~AuthCredentials() {
+}
+
+void AuthCredentials::Set(const base::string16& username,
+ const base::string16& password) {
+ username_ = username;
+ password_ = password;
+}
+
+bool AuthCredentials::Equals(const AuthCredentials& other) const {
+ return username_ == other.username_ && password_ == other.password_;
+}
+
+bool AuthCredentials::Empty() const {
+ return username_.empty() && password_.empty();
+}
+
+void AuthCredentials::Zap() {
+ ZapString(&password_);
+}
+
+} // namespace net
diff --git a/chromium/net/base/auth.h b/chromium/net/base/auth.h
new file mode 100644
index 00000000000..62a59c8f9fd
--- /dev/null
+++ b/chromium/net/base/auth.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2011 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_BASE_AUTH_H__
+#define NET_BASE_AUTH_H__
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Holds info about an authentication challenge that we may want to display
+// to the user.
+class NET_EXPORT AuthChallengeInfo :
+ public base::RefCountedThreadSafe<AuthChallengeInfo> {
+ public:
+ AuthChallengeInfo();
+
+ // Determines whether two AuthChallengeInfo's are equivalent.
+ bool Equals(const AuthChallengeInfo& other) const;
+
+ // Whether this came from a server or a proxy.
+ bool is_proxy;
+
+ // The service issuing the challenge.
+ HostPortPair challenger;
+
+ // The authentication scheme used, such as "basic" or "digest". If the
+ // |source| is FTP_SERVER, this is an empty string. The encoding is ASCII.
+ std::string scheme;
+
+ // The realm of the challenge. May be empty. The encoding is UTF-8.
+ std::string realm;
+
+ private:
+ friend class base::RefCountedThreadSafe<AuthChallengeInfo>;
+ ~AuthChallengeInfo();
+};
+
+// Authentication Credentials for an authentication credentials.
+class NET_EXPORT AuthCredentials {
+ public:
+ AuthCredentials();
+ AuthCredentials(const base::string16& username,
+ const base::string16& password);
+ ~AuthCredentials();
+
+ // Set the |username| and |password|.
+ void Set(const base::string16& username, const base::string16& password);
+
+ // Determines if |this| is equivalent to |other|.
+ bool Equals(const AuthCredentials& other) const;
+
+ // Returns true if all credentials are empty.
+ bool Empty() const;
+
+ // Overwrites the password memory to prevent it from being read if
+ // it's paged out to disk.
+ void Zap();
+
+ const base::string16& username() const { return username_; }
+ const base::string16& password() const { return password_; }
+
+ private:
+ // The username to provide, possibly empty. This should be ASCII only to
+ // minimize compatibility problems, but arbitrary UTF-16 strings are allowed
+ // and will be attempted.
+ base::string16 username_;
+
+ // The password to provide, possibly empty. This should be ASCII only to
+ // minimize compatibility problems, but arbitrary UTF-16 strings are allowed
+ // and will be attempted.
+ base::string16 password_;
+
+ // Intentionally allowing the implicit copy constructor and assignment
+ // operators.
+};
+
+// Authentication structures
+enum AuthState {
+ AUTH_STATE_DONT_NEED_AUTH,
+ AUTH_STATE_NEED_AUTH,
+ AUTH_STATE_HAVE_AUTH,
+ AUTH_STATE_CANCELED
+};
+
+class AuthData : public base::RefCountedThreadSafe<AuthData> {
+ public:
+ AuthState state; // whether we need, have, or gave up on authentication.
+ AuthCredentials credentials; // The credentials to use for auth.
+
+ // We wouldn't instantiate this class if we didn't need authentication.
+ AuthData();
+
+ private:
+ friend class base::RefCountedThreadSafe<AuthData>;
+ ~AuthData();
+};
+
+} // namespace net
+
+#endif // NET_BASE_AUTH_H__
diff --git a/chromium/net/base/backoff_entry.cc b/chromium/net/base/backoff_entry.cc
new file mode 100644
index 00000000000..b1826b7af46
--- /dev/null
+++ b/chromium/net/base/backoff_entry.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 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.
+
+#include "net/base/backoff_entry.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+
+namespace net {
+
+BackoffEntry::BackoffEntry(const BackoffEntry::Policy* const policy)
+ : policy_(policy) {
+ DCHECK(policy_);
+ Reset();
+}
+
+BackoffEntry::~BackoffEntry() {
+ // TODO(joi): Remove this once our clients (e.g. URLRequestThrottlerManager)
+ // always destroy from the I/O thread.
+ DetachFromThread();
+}
+
+void BackoffEntry::InformOfRequest(bool succeeded) {
+ if (!succeeded) {
+ ++failure_count_;
+ exponential_backoff_release_time_ = CalculateReleaseTime();
+ } else {
+ // We slowly decay the number of times delayed instead of
+ // resetting it to 0 in order to stay stable if we receive
+ // successes interleaved between lots of failures. Note that in
+ // the normal case, the calculated release time (in the next
+ // statement) will be in the past once the method returns.
+ if (failure_count_ > 0)
+ --failure_count_;
+
+ // The reason why we are not just cutting the release time to
+ // ImplGetTimeNow() is on the one hand, it would unset a release
+ // time set by SetCustomReleaseTime and on the other we would like
+ // to push every request up to our "horizon" when dealing with
+ // multiple in-flight requests. Ex: If we send three requests and
+ // we receive 2 failures and 1 success. The success that follows
+ // those failures will not reset the release time, further
+ // requests will then need to wait the delay caused by the 2
+ // failures.
+ base::TimeDelta delay;
+ if (policy_->always_use_initial_delay)
+ delay = base::TimeDelta::FromMilliseconds(policy_->initial_delay_ms);
+ exponential_backoff_release_time_ = std::max(
+ ImplGetTimeNow() + delay, exponential_backoff_release_time_);
+ }
+}
+
+bool BackoffEntry::ShouldRejectRequest() const {
+ return exponential_backoff_release_time_ > ImplGetTimeNow();
+}
+
+base::TimeDelta BackoffEntry::GetTimeUntilRelease() const {
+ base::TimeTicks now = ImplGetTimeNow();
+ if (exponential_backoff_release_time_ <= now)
+ return base::TimeDelta();
+ return exponential_backoff_release_time_ - now;
+}
+
+base::TimeTicks BackoffEntry::GetReleaseTime() const {
+ return exponential_backoff_release_time_;
+}
+
+void BackoffEntry::SetCustomReleaseTime(const base::TimeTicks& release_time) {
+ exponential_backoff_release_time_ = release_time;
+}
+
+bool BackoffEntry::CanDiscard() const {
+ if (policy_->entry_lifetime_ms == -1)
+ return false;
+
+ base::TimeTicks now = ImplGetTimeNow();
+
+ int64 unused_since_ms =
+ (now - exponential_backoff_release_time_).InMilliseconds();
+
+ // Release time is further than now, we are managing it.
+ if (unused_since_ms < 0)
+ return false;
+
+ if (failure_count_ > 0) {
+ // Need to keep track of failures until maximum back-off period
+ // has passed (since further failures can add to back-off).
+ return unused_since_ms >= std::max(policy_->maximum_backoff_ms,
+ policy_->entry_lifetime_ms);
+ }
+
+ // Otherwise, consider the entry is outdated if it hasn't been used for the
+ // specified lifetime period.
+ return unused_since_ms >= policy_->entry_lifetime_ms;
+}
+
+void BackoffEntry::Reset() {
+ failure_count_ = 0;
+
+ // We leave exponential_backoff_release_time_ unset, meaning 0. We could
+ // initialize to ImplGetTimeNow() but because it's a virtual method it's
+ // not safe to call in the constructor (and the constructor calls Reset()).
+ // The effects are the same, i.e. ShouldRejectRequest() will return false
+ // right after Reset().
+ exponential_backoff_release_time_ = base::TimeTicks();
+}
+
+base::TimeTicks BackoffEntry::ImplGetTimeNow() const {
+ return base::TimeTicks::Now();
+}
+
+base::TimeTicks BackoffEntry::CalculateReleaseTime() const {
+ int effective_failure_count =
+ std::max(0, failure_count_ - policy_->num_errors_to_ignore);
+
+ // If always_use_initial_delay is true, it's equivalent to
+ // the effective_failure_count always being one greater than when it's false.
+ if (policy_->always_use_initial_delay)
+ ++effective_failure_count;
+
+ if (effective_failure_count == 0) {
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(ImplGetTimeNow(), exponential_backoff_release_time_);
+ }
+
+ // The delay is calculated with this formula:
+ // delay = initial_backoff * multiply_factor^(
+ // effective_failure_count - 1) * Uniform(1 - jitter_factor, 1]
+ double delay = policy_->initial_delay_ms;
+ delay *= pow(policy_->multiply_factor, effective_failure_count - 1);
+ delay -= base::RandDouble() * policy_->jitter_factor * delay;
+
+ const int64 kMaxInt64 = std::numeric_limits<int64>::max();
+ int64 delay_int = (delay > kMaxInt64) ?
+ kMaxInt64 : static_cast<int64>(delay + 0.5);
+
+ // Ensure that we do not exceed maximum delay.
+ if (policy_->maximum_backoff_ms >= 0)
+ delay_int = std::min(delay_int, policy_->maximum_backoff_ms);
+
+ // Never reduce previously set release horizon, e.g. due to Retry-After
+ // header.
+ return std::max(
+ ImplGetTimeNow() + base::TimeDelta::FromMilliseconds(delay_int),
+ exponential_backoff_release_time_);
+}
+
+} // namespace net
diff --git a/chromium/net/base/backoff_entry.h b/chromium/net/base/backoff_entry.h
new file mode 100644
index 00000000000..d8f03d3476b
--- /dev/null
+++ b/chromium/net/base/backoff_entry.h
@@ -0,0 +1,115 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_BACKOFF_ENTRY_H_
+#define NET_BASE_BACKOFF_ENTRY_H_
+
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Provides the core logic needed for randomized exponential back-off
+// on requests to a given resource, given a back-off policy.
+//
+// This utility class knows nothing about network specifics; it is
+// intended for reuse in various networking scenarios.
+class NET_EXPORT BackoffEntry : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // The set of parameters that define a back-off policy.
+ struct Policy {
+ // Number of initial errors (in sequence) to ignore before applying
+ // exponential back-off rules.
+ int num_errors_to_ignore;
+
+ // Initial delay. The interpretation of this value depends on
+ // always_use_initial_delay. It's either how long we wait between
+ // requests before backoff starts, or how much we delay the first request
+ // after backoff starts.
+ int initial_delay_ms;
+
+ // Factor by which the waiting time will be multiplied.
+ double multiply_factor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ double jitter_factor;
+
+ // Maximum amount of time we are willing to delay our request, -1
+ // for no maximum.
+ int64 maximum_backoff_ms;
+
+ // Time to keep an entry from being discarded even when it
+ // has no significant state, -1 to never discard.
+ int64 entry_lifetime_ms;
+
+ // If true, we always use a delay of initial_delay_ms, even before
+ // we've seen num_errors_to_ignore errors. Otherwise, initial_delay_ms
+ // is the first delay once we start exponential backoff.
+ //
+ // So if we're ignoring 1 error, we'll see (N, N, Nm, Nm^2, ...) if true,
+ // and (0, 0, N, Nm, ...) when false, where N is initial_backoff_ms and
+ // m is multiply_factor, assuming we've already seen one success.
+ bool always_use_initial_delay;
+ };
+
+ // Lifetime of policy must enclose lifetime of BackoffEntry. The
+ // pointer must be valid but is not dereferenced during construction.
+ explicit BackoffEntry(const Policy* const policy);
+ virtual ~BackoffEntry();
+
+ // Inform this item that a request for the network resource it is
+ // tracking was made, and whether it failed or succeeded.
+ void InformOfRequest(bool succeeded);
+
+ // Returns true if a request for the resource this item tracks should
+ // be rejected at the present time due to exponential back-off policy.
+ bool ShouldRejectRequest() const;
+
+ // Returns the absolute time after which this entry (given its present
+ // state) will no longer reject requests.
+ base::TimeTicks GetReleaseTime() const;
+
+ // Returns the time until a request can be sent.
+ base::TimeDelta GetTimeUntilRelease() const;
+
+ // Causes this object reject requests until the specified absolute time.
+ // This can be used to e.g. implement support for a Retry-After header.
+ void SetCustomReleaseTime(const base::TimeTicks& release_time);
+
+ // Returns true if this object has no significant state (i.e. you could
+ // just as well start with a fresh BackoffEntry object), and hasn't
+ // had for Policy::entry_lifetime_ms.
+ bool CanDiscard() const;
+
+ // Resets this entry to a fresh (as if just constructed) state.
+ void Reset();
+
+ // Returns the failure count for this entry.
+ int failure_count() const { return failure_count_; }
+
+ protected:
+ // Equivalent to TimeTicks::Now(), virtual so unit tests can override.
+ virtual base::TimeTicks ImplGetTimeNow() const;
+
+ private:
+ // Calculates when requests should again be allowed through.
+ base::TimeTicks CalculateReleaseTime() const;
+
+ // Timestamp calculated by the exponential back-off algorithm at which we are
+ // allowed to start sending requests again.
+ base::TimeTicks exponential_backoff_release_time_;
+
+ // Counts request errors; decremented on success.
+ int failure_count_;
+
+ const Policy* const policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackoffEntry);
+};
+
+} // namespace net
+
+#endif // NET_BASE_BACKOFF_ENTRY_H_
diff --git a/chromium/net/base/backoff_entry_unittest.cc b/chromium/net/base/backoff_entry_unittest.cc
new file mode 100644
index 00000000000..9a9f4cfca0a
--- /dev/null
+++ b/chromium/net/base/backoff_entry_unittest.cc
@@ -0,0 +1,296 @@
+// Copyright (c) 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.
+
+#include "net/base/backoff_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using base::TimeDelta;
+using base::TimeTicks;
+using net::BackoffEntry;
+
+BackoffEntry::Policy base_policy = { 0, 1000, 2.0, 0.0, 20000, 2000, false };
+
+class TestBackoffEntry : public BackoffEntry {
+ public:
+ explicit TestBackoffEntry(const Policy* const policy)
+ : BackoffEntry(policy),
+ now_(TimeTicks()) {
+ // Work around initialization in constructor not picking up
+ // fake time.
+ SetCustomReleaseTime(TimeTicks());
+ }
+
+ virtual ~TestBackoffEntry() {}
+
+ virtual TimeTicks ImplGetTimeNow() const OVERRIDE {
+ return now_;
+ }
+
+ void set_now(const TimeTicks& now) {
+ now_ = now;
+ }
+
+ private:
+ TimeTicks now_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestBackoffEntry);
+};
+
+TEST(BackoffEntryTest, BaseTest) {
+ TestBackoffEntry entry(&base_policy);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+}
+
+TEST(BackoffEntryTest, CanDiscardNeverExpires) {
+ BackoffEntry::Policy never_expires_policy = base_policy;
+ never_expires_policy.entry_lifetime_ms = -1;
+ TestBackoffEntry never_expires(&never_expires_policy);
+ EXPECT_FALSE(never_expires.CanDiscard());
+ never_expires.set_now(TimeTicks() + TimeDelta::FromDays(100));
+ EXPECT_FALSE(never_expires.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscard) {
+ TestBackoffEntry entry(&base_policy);
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the "being used" case.
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Test the case where there are errors but we can time out.
+ entry.set_now(
+ entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.maximum_backoff_ms + 1));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Test the final case (no errors, dependent only on specified lifetime).
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms - 1));
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(
+ base_policy.entry_lifetime_ms));
+ EXPECT_TRUE(entry.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscardAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.entry_lifetime_ms = 0;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Because lifetime is non-zero, we shouldn't be able to discard yet.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+ EXPECT_TRUE(entry.CanDiscard());
+
+ // Even with no failures, we wait until the delay before we allow discard.
+ entry.InformOfRequest(true);
+ EXPECT_FALSE(entry.CanDiscard());
+
+ // Wait until the delay expires, and we can discard the entry again.
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(1000));
+ EXPECT_TRUE(entry.CanDiscard());
+}
+
+TEST(BackoffEntryTest, CanDiscardNotStored) {
+ BackoffEntry::Policy no_store_policy = base_policy;
+ no_store_policy.entry_lifetime_ms = 0;
+ TestBackoffEntry not_stored(&no_store_policy);
+ EXPECT_TRUE(not_stored.CanDiscard());
+}
+
+TEST(BackoffEntryTest, ShouldIgnoreFirstTwo) {
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 2;
+
+ BackoffEntry entry(&lenient_policy);
+
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+
+ entry.InformOfRequest(false);
+ EXPECT_FALSE(entry.ShouldRejectRequest());
+
+ entry.InformOfRequest(false);
+ EXPECT_TRUE(entry.ShouldRejectRequest());
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculation) {
+ TestBackoffEntry entry(&base_policy);
+
+ // With zero errors, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow(), result);
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(1000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(2000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 3 errors.
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 6 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(20000), result);
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 2;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // With previous requests, should return "now".
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta(), entry.GetTimeUntilRelease());
+
+ // 1 error.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 2 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // 3 errors, exponential backoff starts.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+
+ // 4 errors.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(4000), entry.GetTimeUntilRelease());
+
+ // 8 errors (to check it doesn't pass maximum).
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ result = entry.GetReleaseTime();
+ EXPECT_EQ(TimeDelta::FromMilliseconds(20000), entry.GetTimeUntilRelease());
+}
+
+TEST(BackoffEntryTest, ReleaseTimeCalculationWithJitter) {
+ for (int i = 0; i < 10; ++i) {
+ BackoffEntry::Policy jittery_policy = base_policy;
+ jittery_policy.jitter_factor = 0.2;
+
+ TestBackoffEntry entry(&jittery_policy);
+
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ entry.InformOfRequest(false);
+ TimeTicks result = entry.GetReleaseTime();
+ EXPECT_LE(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(3200), result);
+ EXPECT_GE(
+ entry.ImplGetTimeNow() + TimeDelta::FromMilliseconds(4000), result);
+ }
+}
+
+TEST(BackoffEntryTest, FailureThenSuccess) {
+ TestBackoffEntry entry(&base_policy);
+
+ // Failure count 1, establishes horizon.
+ entry.InformOfRequest(false);
+ TimeTicks release_time = entry.GetReleaseTime();
+ EXPECT_EQ(TimeTicks() + TimeDelta::FromMilliseconds(1000), release_time);
+
+ // Success, failure count 0, should not advance past
+ // the horizon that was already set.
+ entry.set_now(release_time - TimeDelta::FromMilliseconds(200));
+ entry.InformOfRequest(true);
+ EXPECT_EQ(release_time, entry.GetReleaseTime());
+
+ // Failure, failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(release_time + TimeDelta::FromMilliseconds(800),
+ entry.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, FailureThenSuccessAlwaysDelay) {
+ BackoffEntry::Policy always_delay_policy = base_policy;
+ always_delay_policy.always_use_initial_delay = true;
+ always_delay_policy.num_errors_to_ignore = 1;
+
+ TestBackoffEntry entry(&always_delay_policy);
+
+ // Failure count 1.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count 2.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+
+ // Success. We should go back to the original delay.
+ entry.InformOfRequest(true);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(1000), entry.GetTimeUntilRelease());
+
+ // Failure count reaches 2 again. We should increase the delay once more.
+ entry.InformOfRequest(false);
+ EXPECT_EQ(TimeDelta::FromMilliseconds(2000), entry.GetTimeUntilRelease());
+ entry.set_now(entry.GetReleaseTime() + TimeDelta::FromMilliseconds(2000));
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizon) {
+ TestBackoffEntry custom(&base_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(2));
+ custom.InformOfRequest(false);
+ custom.InformOfRequest(true);
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+
+ // Now check that once we are at or past the custom horizon,
+ // we get normal behavior.
+ custom.set_now(TimeTicks() + TimeDelta::FromDays(3));
+ custom.InformOfRequest(false);
+ EXPECT_EQ(
+ TimeTicks() + TimeDelta::FromDays(3) + TimeDelta::FromMilliseconds(1000),
+ custom.GetReleaseTime());
+}
+
+TEST(BackoffEntryTest, RetainCustomHorizonWhenInitialErrorsIgnored) {
+ // Regression test for a bug discovered during code review.
+ BackoffEntry::Policy lenient_policy = base_policy;
+ lenient_policy.num_errors_to_ignore = 1;
+ TestBackoffEntry custom(&lenient_policy);
+ TimeTicks custom_horizon = TimeTicks() + TimeDelta::FromDays(3);
+ custom.SetCustomReleaseTime(custom_horizon);
+ custom.InformOfRequest(false); // This must not reset the horizon.
+ EXPECT_EQ(custom_horizon, custom.GetReleaseTime());
+}
+
+} // namespace
diff --git a/chromium/net/base/bandwidth_metrics.cc b/chromium/net/base/bandwidth_metrics.cc
new file mode 100644
index 00000000000..0644122d7dd
--- /dev/null
+++ b/chromium/net/base/bandwidth_metrics.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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 "base/lazy_instance.h"
+#include "net/base/bandwidth_metrics.h"
+
+static base::LazyInstance<net::BandwidthMetrics> g_bandwidth_metrics =
+ LAZY_INSTANCE_INITIALIZER;
+
+namespace net {
+
+ScopedBandwidthMetrics::ScopedBandwidthMetrics()
+ : started_(false) {
+}
+
+ScopedBandwidthMetrics::~ScopedBandwidthMetrics() {
+ if (started_)
+ g_bandwidth_metrics.Get().StopStream();
+}
+
+void ScopedBandwidthMetrics::StartStream() {
+ started_ = true;
+ g_bandwidth_metrics.Get().StartStream();
+}
+
+void ScopedBandwidthMetrics::StopStream() {
+ started_ = false;
+ g_bandwidth_metrics.Get().StopStream();
+}
+
+void ScopedBandwidthMetrics::RecordBytes(int bytes) {
+ g_bandwidth_metrics.Get().RecordBytes(bytes);
+}
+
+} // namespace net
diff --git a/chromium/net/base/bandwidth_metrics.h b/chromium/net/base/bandwidth_metrics.h
new file mode 100644
index 00000000000..e5f2646c7dc
--- /dev/null
+++ b/chromium/net/base/bandwidth_metrics.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2011 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_BASE_BANDWIDTH_METRICS_H_
+#define NET_BASE_BANDWIDTH_METRICS_H_
+
+#include <list>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+
+namespace net {
+
+// Tracks statistics about the bandwidth metrics over time. In order to
+// measure, this class needs to know when individual streams are in progress,
+// so that it can know when to discount idle time. The BandwidthMetrics
+// is unidirectional - it should only be used to record upload or download
+// bandwidth, but not both.
+//
+// Note, the easiest thing to do is to just measure each stream and average
+// them or add them. However, this does not work. If multiple streams are in
+// progress concurrently, you have to look at the aggregate bandwidth at any
+// point in time.
+//
+// Example:
+// Imagine 4 streams opening and closing with overlapping time.
+// We can't measure bandwidth by looking at any individual stream.
+// We can only measure actual bandwidth by looking at the bandwidth
+// across all open streams.
+//
+// Time --------------------------------------->
+// s1 +----------------+
+// s2 +----------------+
+// s3 +--------------+
+// s4 +--------------+
+//
+// Example usage:
+//
+// BandwidthMetrics tracker;
+//
+// // When a stream is created
+// tracker.StartStream();
+//
+// // When data is transferred on any stream
+// tracker.RecordSample(bytes);
+//
+// // When the stream is finished
+// tracker.StopStream();
+//
+// NOTE: This class is not thread safe.
+//
+class BandwidthMetrics {
+ public:
+ BandwidthMetrics()
+ : num_streams_in_progress_(0),
+ num_data_samples_(0),
+ data_sum_(0.0),
+ bytes_since_last_start_(0) {
+ }
+
+ // Get the bandwidth. Returns Kbps (kilo-bits-per-second).
+ double bandwidth() const {
+ return data_sum_ / num_data_samples_;
+ }
+
+ // Record that we've started a stream.
+ void StartStream() {
+ // If we're the only stream, we've finished some idle time. Record a new
+ // timestamp to indicate the start of data flow.
+ if (++num_streams_in_progress_ == 1) {
+ last_start_ = base::TimeTicks::HighResNow();
+ bytes_since_last_start_ = 0;
+ }
+ }
+
+ // Track that we've completed a stream.
+ void StopStream() {
+ if (--num_streams_in_progress_ == 0) {
+ // We don't use small streams when tracking bandwidth because they are not
+ // precise; imagine a 25 byte stream. The sample is too small to make
+ // a good measurement.
+ // 20KB is an arbitrary value. We might want to use a lesser value.
+ static const int64 kRecordSizeThreshold = 20 * 1024;
+ if (bytes_since_last_start_ < kRecordSizeThreshold)
+ return;
+
+ base::TimeDelta delta = base::TimeTicks::HighResNow() - last_start_;
+ double ms = delta.InMillisecondsF();
+ if (ms > 0.0) {
+ double kbps = static_cast<double>(bytes_since_last_start_) * 8 / ms;
+ ++num_data_samples_;
+ data_sum_ += kbps;
+ VLOG(1) << "Bandwidth: " << kbps
+ << "Kbps (avg " << bandwidth() << "Kbps)";
+ int kbps_int = static_cast<int>(kbps);
+ UMA_HISTOGRAM_COUNTS_10000("Net.DownloadBandwidth", kbps_int);
+ }
+ }
+ }
+
+ // Add a sample of the number of bytes read from the network into the tracker.
+ void RecordBytes(int bytes) {
+ DCHECK(num_streams_in_progress_);
+ bytes_since_last_start_ += static_cast<int64>(bytes);
+ }
+
+ private:
+ int num_streams_in_progress_; // The number of streams in progress.
+ // TODO(mbelshe): Use a rolling buffer of 30 samples instead of an average.
+ int num_data_samples_; // The number of samples collected.
+ double data_sum_; // The sum of all samples collected.
+ int64 bytes_since_last_start_; // Bytes tracked during this "session".
+ base::TimeTicks last_start_; // Timestamp of the begin of this "session".
+};
+
+// A utility class for managing the lifecycle of a measured stream.
+// It is important that we not leave unclosed streams, and this class helps
+// ensure we always stop them.
+class ScopedBandwidthMetrics {
+ public:
+ ScopedBandwidthMetrics();
+ ~ScopedBandwidthMetrics();
+
+ void StartStream();
+ void StopStream();
+ void RecordBytes(int bytes);
+
+ private:
+ bool started_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_BANDWIDTH_METRICS_H_
diff --git a/chromium/net/base/big_endian.cc b/chromium/net/base/big_endian.cc
new file mode 100644
index 00000000000..888cf35c6d6
--- /dev/null
+++ b/chromium/net/base/big_endian.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 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/base/big_endian.h"
+
+#include "base/strings/string_piece.h"
+
+namespace net {
+
+BigEndianReader::BigEndianReader(const void* buf, size_t len)
+ : ptr_(reinterpret_cast<const char*>(buf)), end_(ptr_ + len) {}
+
+bool BigEndianReader::Skip(size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianReader::ReadBytes(void* out, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ memcpy(out, ptr_, len);
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianReader::ReadPiece(base::StringPiece* out, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ *out = base::StringPiece(ptr_, len);
+ ptr_ += len;
+ return true;
+}
+
+template<typename T>
+bool BigEndianReader::Read(T* value) {
+ if (ptr_ + sizeof(T) > end_)
+ return false;
+ ReadBigEndian<T>(ptr_, value);
+ ptr_ += sizeof(T);
+ return true;
+}
+
+bool BigEndianReader::ReadU8(uint8* value) {
+ return Read(value);
+}
+
+bool BigEndianReader::ReadU16(uint16* value) {
+ return Read(value);
+}
+
+bool BigEndianReader::ReadU32(uint32* value) {
+ return Read(value);
+}
+
+BigEndianWriter::BigEndianWriter(void* buf, size_t len)
+ : ptr_(reinterpret_cast<char*>(buf)), end_(ptr_ + len) {}
+
+bool BigEndianWriter::Skip(size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ ptr_ += len;
+ return true;
+}
+
+bool BigEndianWriter::WriteBytes(const void* buf, size_t len) {
+ if (ptr_ + len > end_)
+ return false;
+ memcpy(ptr_, buf, len);
+ ptr_ += len;
+ return true;
+}
+
+template<typename T>
+bool BigEndianWriter::Write(T value) {
+ if (ptr_ + sizeof(T) > end_)
+ return false;
+ WriteBigEndian<T>(ptr_, value);
+ ptr_ += sizeof(T);
+ return true;
+}
+
+bool BigEndianWriter::WriteU8(uint8 value) {
+ return Write(value);
+}
+
+bool BigEndianWriter::WriteU16(uint16 value) {
+ return Write(value);
+}
+
+bool BigEndianWriter::WriteU32(uint32 value) {
+ return Write(value);
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/big_endian.h b/chromium/net/base/big_endian.h
new file mode 100644
index 00000000000..911f3c5074d
--- /dev/null
+++ b/chromium/net/base/big_endian.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011 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_BASE_BIG_ENDIAN_H_
+#define NET_BASE_BIG_ENDIAN_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Read an integer (signed or unsigned) from |buf| in Big Endian order.
+// Note: this loop is unrolled with -O1 and above.
+// NOTE(szym): glibc dns-canon.c and SpdyFrameBuilder use
+// ntohs(*(uint16_t*)ptr) which is potentially unaligned.
+// This would cause SIGBUS on ARMv5 or earlier and ARMv6-M.
+template<typename T>
+inline void ReadBigEndian(const char buf[], T* out) {
+ *out = buf[0];
+ for (size_t i = 1; i < sizeof(T); ++i) {
+ *out <<= 8;
+ // Must cast to uint8 to avoid clobbering by sign extension.
+ *out |= static_cast<uint8>(buf[i]);
+ }
+}
+
+// Write an integer (signed or unsigned) |val| to |buf| in Big Endian order.
+// Note: this loop is unrolled with -O1 and above.
+template<typename T>
+inline void WriteBigEndian(char buf[], T val) {
+ for (size_t i = 0; i < sizeof(T); ++i) {
+ buf[sizeof(T)-i-1] = static_cast<char>(val & 0xFF);
+ val >>= 8;
+ }
+}
+
+// Specializations to make clang happy about the (dead code) shifts above.
+template<>
+inline void ReadBigEndian<uint8>(const char buf[], uint8* out) {
+ *out = buf[0];
+}
+
+template<>
+inline void WriteBigEndian<uint8>(char buf[], uint8 val) {
+ buf[0] = static_cast<char>(val);
+}
+
+// Allows reading integers in network order (big endian) while iterating over
+// an underlying buffer. All the reading functions advance the internal pointer.
+class NET_EXPORT BigEndianReader {
+ public:
+ BigEndianReader(const void* buf, size_t len);
+
+ const char* ptr() const { return ptr_; }
+ int remaining() const { return end_ - ptr_; }
+
+ bool Skip(size_t len);
+ bool ReadBytes(void* out, size_t len);
+ // Creates a StringPiece in |out| that points to the underlying buffer.
+ bool ReadPiece(base::StringPiece* out, size_t len);
+ bool ReadU8(uint8* value);
+ bool ReadU16(uint16* value);
+ bool ReadU32(uint32* value);
+
+ private:
+ // Hidden to promote type safety.
+ template<typename T>
+ bool Read(T* v);
+
+ const char* ptr_;
+ const char* end_;
+};
+
+// Allows writing integers in network order (big endian) while iterating over
+// an underlying buffer. All the writing functions advance the internal pointer.
+class NET_EXPORT BigEndianWriter {
+ public:
+ BigEndianWriter(void* buf, size_t len);
+
+ char* ptr() const { return ptr_; }
+ int remaining() const { return end_ - ptr_; }
+
+ bool Skip(size_t len);
+ bool WriteBytes(const void* buf, size_t len);
+ bool WriteU8(uint8 value);
+ bool WriteU16(uint16 value);
+ bool WriteU32(uint32 value);
+
+ private:
+ // Hidden to promote type safety.
+ template<typename T>
+ bool Write(T v);
+
+ char* ptr_;
+ char* end_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_BIG_ENDIAN_H_
+
diff --git a/chromium/net/base/big_endian_unittest.cc b/chromium/net/base/big_endian_unittest.cc
new file mode 100644
index 00000000000..e758286efe1
--- /dev/null
+++ b/chromium/net/base/big_endian_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2011 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 "base/strings/string_piece.h"
+#include "net/base/big_endian.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(BigEndianReaderTest, ReadsValues) {
+ char data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC };
+ char buf[2];
+ uint8 u8;
+ uint16 u16;
+ uint32 u32;
+ base::StringPiece piece;
+ BigEndianReader reader(data, sizeof(data));
+
+ EXPECT_TRUE(reader.Skip(2));
+ EXPECT_EQ(data + 2, reader.ptr());
+ EXPECT_EQ(reader.remaining(), static_cast<int>(sizeof(data)) - 2);
+ EXPECT_TRUE(reader.ReadBytes(buf, sizeof(buf)));
+ EXPECT_EQ(0x2, buf[0]);
+ EXPECT_EQ(0x3, buf[1]);
+ EXPECT_TRUE(reader.ReadU8(&u8));
+ EXPECT_EQ(0x4, u8);
+ EXPECT_TRUE(reader.ReadU16(&u16));
+ EXPECT_EQ(0x0506, u16);
+ EXPECT_TRUE(reader.ReadU32(&u32));
+ EXPECT_EQ(0x0708090Au, u32);
+ base::StringPiece expected(reader.ptr(), 2);
+ EXPECT_TRUE(reader.ReadPiece(&piece, 2));
+ EXPECT_EQ(2u, piece.size());
+ EXPECT_EQ(expected.data(), piece.data());
+}
+
+TEST(BigEndianReaderTest, RespectsLength) {
+ char data[4];
+ char buf[2];
+ uint8 u8;
+ uint16 u16;
+ uint32 u32;
+ base::StringPiece piece;
+ BigEndianReader reader(data, sizeof(data));
+ // 4 left
+ EXPECT_FALSE(reader.Skip(6));
+ EXPECT_TRUE(reader.Skip(1));
+ // 3 left
+ EXPECT_FALSE(reader.ReadU32(&u32));
+ EXPECT_FALSE(reader.ReadPiece(&piece, 4));
+ EXPECT_TRUE(reader.Skip(2));
+ // 1 left
+ EXPECT_FALSE(reader.ReadU16(&u16));
+ EXPECT_FALSE(reader.ReadBytes(buf, 2));
+ EXPECT_TRUE(reader.Skip(1));
+ // 0 left
+ EXPECT_FALSE(reader.ReadU8(&u8));
+ EXPECT_EQ(0, reader.remaining());
+}
+
+TEST(BigEndianWriterTest, WritesValues) {
+ char expected[] = { 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 0xA };
+ char data[sizeof(expected)];
+ char buf[] = { 0x2, 0x3 };
+ memset(data, 0, sizeof(data));
+ BigEndianWriter writer(data, sizeof(data));
+
+ EXPECT_TRUE(writer.Skip(2));
+ EXPECT_TRUE(writer.WriteBytes(buf, sizeof(buf)));
+ EXPECT_TRUE(writer.WriteU8(0x4));
+ EXPECT_TRUE(writer.WriteU16(0x0506));
+ EXPECT_TRUE(writer.WriteU32(0x0708090A));
+ EXPECT_EQ(0, memcmp(expected, data, sizeof(expected)));
+}
+
+TEST(BigEndianWriterTest, RespectsLength) {
+ char data[4];
+ char buf[2];
+ uint8 u8 = 0;
+ uint16 u16 = 0;
+ uint32 u32 = 0;
+ BigEndianWriter writer(data, sizeof(data));
+ // 4 left
+ EXPECT_FALSE(writer.Skip(6));
+ EXPECT_TRUE(writer.Skip(1));
+ // 3 left
+ EXPECT_FALSE(writer.WriteU32(u32));
+ EXPECT_TRUE(writer.Skip(2));
+ // 1 left
+ EXPECT_FALSE(writer.WriteU16(u16));
+ EXPECT_FALSE(writer.WriteBytes(buf, 2));
+ EXPECT_TRUE(writer.Skip(1));
+ // 0 left
+ EXPECT_FALSE(writer.WriteU8(u8));
+ EXPECT_EQ(0, writer.remaining());
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/cache_type.h b/chromium/net/base/cache_type.h
new file mode 100644
index 00000000000..69b5646ece7
--- /dev/null
+++ b/chromium/net/base/cache_type.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2009 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_BASE_CACHE_TYPE_H_
+#define NET_BASE_CACHE_TYPE_H_
+
+namespace net {
+
+// The types of caches that can be created.
+enum CacheType {
+ DISK_CACHE, // Disk is used as the backing storage.
+ MEMORY_CACHE, // Data is stored only in memory.
+ MEDIA_CACHE, // Optimized to handle media files.
+ APP_CACHE, // Backing store for an AppCache.
+ SHADER_CACHE // Backing store for the GL shader cache.
+};
+
+// The types of disk cache backend, only used at backend instantiation.
+enum BackendType {
+ CACHE_BACKEND_DEFAULT,
+ CACHE_BACKEND_BLOCKFILE, // The |BackendImpl|.
+ CACHE_BACKEND_SIMPLE // The |SimpleBackendImpl|.
+};
+
+} // namespace disk_cache
+
+#endif // NET_BASE_CACHE_TYPE_H_
diff --git a/chromium/net/base/capturing_net_log.cc b/chromium/net/base/capturing_net_log.cc
new file mode 100644
index 00000000000..c7c2516e4a8
--- /dev/null
+++ b/chromium/net/base/capturing_net_log.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+
+#include "net/base/capturing_net_log.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+
+namespace net {
+
+CapturingNetLog::CapturedEntry::CapturedEntry(
+ EventType type,
+ const base::TimeTicks& time,
+ Source source,
+ EventPhase phase,
+ scoped_ptr<base::DictionaryValue> params)
+ : type(type),
+ time(time),
+ source(source),
+ phase(phase),
+ params(params.Pass()) {
+}
+
+CapturingNetLog::CapturedEntry::CapturedEntry(const CapturedEntry& entry) {
+ *this = entry;
+}
+
+CapturingNetLog::CapturedEntry::~CapturedEntry() {}
+
+CapturingNetLog::CapturedEntry&
+CapturingNetLog::CapturedEntry::operator=(const CapturedEntry& entry) {
+ type = entry.type;
+ time = entry.time;
+ source = entry.source;
+ phase = entry.phase;
+ params.reset(entry.params ? entry.params->DeepCopy() : NULL);
+ return *this;
+}
+
+bool CapturingNetLog::CapturedEntry::GetStringValue(
+ const std::string& name,
+ std::string* value) const {
+ if (!params)
+ return false;
+ return params->GetString(name, value);
+}
+
+bool CapturingNetLog::CapturedEntry::GetIntegerValue(
+ const std::string& name,
+ int* value) const {
+ if (!params)
+ return false;
+ return params->GetInteger(name, value);
+}
+
+bool CapturingNetLog::CapturedEntry::GetNetErrorCode(int* value) const {
+ return GetIntegerValue("net_error", value);
+}
+
+std::string CapturingNetLog::CapturedEntry::GetParamsJson() const {
+ if (!params)
+ return std::string();
+ std::string json;
+ base::JSONWriter::Write(params.get(), &json);
+ return json;
+}
+
+CapturingNetLog::Observer::Observer() {}
+
+CapturingNetLog::Observer::~Observer() {}
+
+void CapturingNetLog::Observer::GetEntries(
+ CapturedEntryList* entry_list) const {
+ base::AutoLock lock(lock_);
+ *entry_list = captured_entries_;
+}
+
+void CapturingNetLog::Observer::GetEntriesForSource(
+ NetLog::Source source,
+ CapturedEntryList* entry_list) const {
+ base::AutoLock lock(lock_);
+ entry_list->clear();
+ for (CapturedEntryList::const_iterator entry = captured_entries_.begin();
+ entry != captured_entries_.end(); ++entry) {
+ if (entry->source.id == source.id)
+ entry_list->push_back(*entry);
+ }
+}
+
+size_t CapturingNetLog::Observer::GetSize() const {
+ base::AutoLock lock(lock_);
+ return captured_entries_.size();
+}
+
+void CapturingNetLog::Observer::Clear() {
+ base::AutoLock lock(lock_);
+ captured_entries_.clear();
+}
+
+void CapturingNetLog::Observer::OnAddEntry(const net::NetLog::Entry& entry) {
+ // Only BoundNetLogs without a NetLog should have an invalid source.
+ CHECK(entry.source().IsValid());
+
+ // Using Dictionaries instead of Values makes checking values a little
+ // simpler.
+ base::DictionaryValue* param_dict = NULL;
+ Value* param_value = entry.ParametersToValue();
+ if (param_value && !param_value->GetAsDictionary(&param_dict))
+ delete param_value;
+
+ // Only need to acquire the lock when accessing class variables.
+ base::AutoLock lock(lock_);
+ captured_entries_.push_back(
+ CapturedEntry(entry.type(),
+ base::TimeTicks::Now(),
+ entry.source(),
+ entry.phase(),
+ scoped_ptr<base::DictionaryValue>(param_dict)));
+}
+
+CapturingNetLog::CapturingNetLog() {
+ AddThreadSafeObserver(&capturing_net_log_observer_, LOG_ALL_BUT_BYTES);
+}
+
+CapturingNetLog::~CapturingNetLog() {
+ RemoveThreadSafeObserver(&capturing_net_log_observer_);
+}
+
+void CapturingNetLog::SetLogLevel(NetLog::LogLevel log_level) {
+ SetObserverLogLevel(&capturing_net_log_observer_, log_level);
+}
+
+void CapturingNetLog::GetEntries(
+ CapturingNetLog::CapturedEntryList* entry_list) const {
+ capturing_net_log_observer_.GetEntries(entry_list);
+}
+
+void CapturingNetLog::GetEntriesForSource(
+ NetLog::Source source,
+ CapturedEntryList* entry_list) const {
+ capturing_net_log_observer_.GetEntriesForSource(source, entry_list);
+}
+
+size_t CapturingNetLog::GetSize() const {
+ return capturing_net_log_observer_.GetSize();
+}
+
+void CapturingNetLog::Clear() {
+ capturing_net_log_observer_.Clear();
+}
+
+CapturingBoundNetLog::CapturingBoundNetLog()
+ : net_log_(BoundNetLog::Make(&capturing_net_log_,
+ net::NetLog::SOURCE_NONE)) {
+}
+
+CapturingBoundNetLog::~CapturingBoundNetLog() {}
+
+void CapturingBoundNetLog::GetEntries(
+ CapturingNetLog::CapturedEntryList* entry_list) const {
+ capturing_net_log_.GetEntries(entry_list);
+}
+
+void CapturingBoundNetLog::GetEntriesForSource(
+ NetLog::Source source,
+ CapturingNetLog::CapturedEntryList* entry_list) const {
+ capturing_net_log_.GetEntriesForSource(source, entry_list);
+}
+
+size_t CapturingBoundNetLog::GetSize() const {
+ return capturing_net_log_.GetSize();
+}
+
+void CapturingBoundNetLog::Clear() {
+ capturing_net_log_.Clear();
+}
+
+void CapturingBoundNetLog::SetLogLevel(NetLog::LogLevel log_level) {
+ capturing_net_log_.SetLogLevel(log_level);
+}
+
+} // namespace net
diff --git a/chromium/net/base/capturing_net_log.h b/chromium/net/base/capturing_net_log.h
new file mode 100644
index 00000000000..156a61d34a4
--- /dev/null
+++ b/chromium/net/base/capturing_net_log.h
@@ -0,0 +1,159 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_CAPTURING_NET_LOG_H_
+#define NET_BASE_CAPTURING_NET_LOG_H_
+
+#include <string>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/base/net_log.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace net {
+
+// CapturingNetLog is a NetLog which instantiates Observer that saves messages
+// to a bounded buffer. It is intended for testing only, and is part of the
+// net_test_support project. This is provided for convinience and compatilbility
+// with the old unittests.
+class CapturingNetLog : public NetLog {
+ public:
+ struct CapturedEntry {
+ CapturedEntry(EventType type,
+ const base::TimeTicks& time,
+ Source source,
+ EventPhase phase,
+ scoped_ptr<base::DictionaryValue> params);
+ // Copy constructor needed to store in a std::vector because of the
+ // scoped_ptr.
+ CapturedEntry(const CapturedEntry& entry);
+
+ ~CapturedEntry();
+
+ // Equality operator needed to store in a std::vector because of the
+ // scoped_ptr.
+ CapturedEntry& operator=(const CapturedEntry& entry);
+
+ // Attempt to retrieve an value of the specified type with the given name
+ // from |params|. Returns true on success, false on failure. Does not
+ // modify |value| on failure.
+ bool GetStringValue(const std::string& name, std::string* value) const;
+ bool GetIntegerValue(const std::string& name, int* value) const;
+
+ // Same as GetIntegerValue, but returns the error code associated with a
+ // log entry.
+ bool GetNetErrorCode(int* value) const;
+
+ // Returns the parameters as a JSON string, or empty string if there are no
+ // parameters.
+ std::string GetParamsJson() const;
+
+ EventType type;
+ base::TimeTicks time;
+ Source source;
+ EventPhase phase;
+ scoped_ptr<base::DictionaryValue> params;
+ };
+
+ // Ordered set of entries that were logged.
+ typedef std::vector<CapturedEntry> CapturedEntryList;
+
+ CapturingNetLog();
+ virtual ~CapturingNetLog();
+
+ void SetLogLevel(LogLevel log_level);
+
+ // Below methods are forwarded to capturing_net_log_observer_.
+ void GetEntries(CapturedEntryList* entry_list) const;
+ void GetEntriesForSource(Source source, CapturedEntryList* entry_list) const;
+ size_t GetSize() const;
+ void Clear();
+
+ private:
+ // Observer is an implementation of NetLog::ThreadSafeObserver
+ // that saves messages to a bounded buffer. It is intended for testing only,
+ // and is part of the net_test_support project.
+ class Observer : public NetLog::ThreadSafeObserver {
+ public:
+ Observer();
+ virtual ~Observer();
+
+ // Returns the list of all entries in the log.
+ void GetEntries(CapturedEntryList* entry_list) const;
+
+ // Fills |entry_list| with all entries in the log from the specified Source.
+ void GetEntriesForSource(Source source,
+ CapturedEntryList* entry_list) const;
+
+ // Returns number of entries in the log.
+ size_t GetSize() const;
+
+ void Clear();
+
+ private:
+ // ThreadSafeObserver implementation:
+ virtual void OnAddEntry(const Entry& entry) OVERRIDE;
+
+ // Needs to be "mutable" so can use it in GetEntries().
+ mutable base::Lock lock_;
+
+ CapturedEntryList captured_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ Observer capturing_net_log_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingNetLog);
+};
+
+// Helper class that exposes a similar API as BoundNetLog, but uses a
+// CapturingNetLog rather than the more generic NetLog.
+//
+// CapturingBoundNetLog can easily be converted to a BoundNetLog using the
+// bound() method.
+class CapturingBoundNetLog {
+ public:
+ CapturingBoundNetLog();
+ ~CapturingBoundNetLog();
+
+ // The returned BoundNetLog is only valid while |this| is alive.
+ BoundNetLog bound() const { return net_log_; }
+
+ // Fills |entry_list| with all entries in the log.
+ void GetEntries(CapturingNetLog::CapturedEntryList* entry_list) const;
+
+ // Fills |entry_list| with all entries in the log from the specified Source.
+ void GetEntriesForSource(
+ NetLog::Source source,
+ CapturingNetLog::CapturedEntryList* entry_list) const;
+
+ // Returns number of entries in the log.
+ size_t GetSize() const;
+
+ void Clear();
+
+ // Sets the log level of the underlying CapturingNetLog.
+ void SetLogLevel(NetLog::LogLevel log_level);
+
+ private:
+ CapturingNetLog capturing_net_log_;
+ const BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingBoundNetLog);
+};
+
+} // namespace net
+
+#endif // NET_BASE_CAPTURING_NET_LOG_H_
diff --git a/chromium/net/base/completion_callback.h b/chromium/net/base/completion_callback.h
new file mode 100644
index 00000000000..7e5c90e8bab
--- /dev/null
+++ b/chromium/net/base/completion_callback.h
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_COMPLETION_CALLBACK_H__
+#define NET_BASE_COMPLETION_CALLBACK_H__
+
+#include "base/callback.h"
+#include "base/cancelable_callback.h"
+
+namespace net {
+
+// A callback specialization that takes a single int parameter. Usually this is
+// used to report a byte count or network error code.
+typedef base::Callback<void(int)> CompletionCallback;
+
+// 64bit version of callback specialization that takes a single int64 parameter.
+// Usually this is used to report a file offset, size or network error code.
+typedef base::Callback<void(int64)> Int64CompletionCallback;
+
+typedef base::CancelableCallback<void(int)> CancelableCompletionCallback;
+
+} // namespace net
+
+#endif // NET_BASE_COMPLETION_CALLBACK_H__
diff --git a/chromium/net/base/connection_type_histograms.cc b/chromium/net/base/connection_type_histograms.cc
new file mode 100644
index 00000000000..e64619c337e
--- /dev/null
+++ b/chromium/net/base/connection_type_histograms.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 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/base/connection_type_histograms.h"
+
+#include "base/metrics/histogram.h"
+
+namespace net {
+
+// We're using a histogram as a group of counters. We're only interested in
+// the values of the counters. Ignore the shape, average, and standard
+// deviation of the histograms because they are meaningless.
+//
+// We use two groups of counters. In the first group (counter1), each counter
+// is a boolean (0 or 1) that indicates whether the user has seen a connection
+// of that type during that session. In the second group (counter2), each
+// counter is the number of connections of that type the user has seen during
+// that session.
+//
+// Each histogram has an unused bucket at the end to allow seamless future
+// expansion.
+void UpdateConnectionTypeHistograms(ConnectionType type) {
+ // TODO(wtc): Bug 74467 Move these stats up to a higher level.
+ static bool had_connection_type[NUM_OF_CONNECTION_TYPES]; // Default false.
+
+ if (type >= 0 && type < NUM_OF_CONNECTION_TYPES) {
+ if (!had_connection_type[type]) {
+ had_connection_type[type] = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.HadConnectionType3",
+ type, NUM_OF_CONNECTION_TYPES);
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Net.ConnectionTypeCount3",
+ type, NUM_OF_CONNECTION_TYPES);
+ } else {
+ NOTREACHED(); // Someone's logging an invalid type!
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/connection_type_histograms.h b/chromium/net/base/connection_type_histograms.h
new file mode 100644
index 00000000000..7203cf1c564
--- /dev/null
+++ b/chromium/net/base/connection_type_histograms.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2009 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_BASE_CONNECTION_TYPE_HISTOGRAMS_H_
+#define NET_BASE_CONNECTION_TYPE_HISTOGRAMS_H_
+
+// The UpdateConnectionTypeHistograms function collects statistics related
+// to the number of MD5 certificates that our users are encountering. The
+// information will help us decide when it is fine for browsers to stop
+// supporting MD5 certificates, in light of the recent MD5 certificate
+// collision attack (see "MD5 considered harmful today: Creating a rogue CA
+// certificate" at http://www.win.tue.nl/hashclash/rogue-ca/).
+
+namespace net {
+
+enum ConnectionType {
+ CONNECTION_ANY = 0, // Any connection (SSL, HTTP, SPDY, etc)
+ CONNECTION_SSL = 1, // An SSL connection
+ // 2 - Reserved - Was CONNECTION_SSL_MD5
+ // 3 - Reserved - Was CONNECTION_SSL_MD2
+ // 4 - Reserved - Was CONNECTION_SSL_MD4
+ // 5 - Reserved - Was CONNECTION_SSL_MD5_CA
+ // 6 - Reserved - Was CONNECTION_SSL_MD2_CA
+ CONNECTION_HTTP = 7, // An HTTP connection
+ CONNECTION_SPDY = 8, // A SPDY connection
+ CONNECTION_SSL_SSL2 = 9, // An SSL connection that uses SSL 2.0
+ CONNECTION_SSL_SSL3 = 10, // An SSL connection that uses SSL 3.0
+ CONNECTION_SSL_TLS1 = 11, // An SSL connection that uses TLS 1.0
+ CONNECTION_SSL_TLS1_1 = 12, // An SSL connection that uses TLS 1.1
+ CONNECTION_SSL_TLS1_2 = 13, // An SSL connection that uses TLS 1.2
+ NUM_OF_CONNECTION_TYPES
+};
+
+// Update the connection type histograms. |type| is the connection type.
+void UpdateConnectionTypeHistograms(ConnectionType type);
+
+} // namespace net
+
+#endif // NET_BASE_CONNECTION_TYPE_HISTOGRAMS_H_
diff --git a/chromium/net/base/crypto_module.h b/chromium/net/base/crypto_module.h
new file mode 100644
index 00000000000..be876ef7807
--- /dev/null
+++ b/chromium/net/base/crypto_module.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 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_BASE_CRYPTO_MODULE_H_
+#define NET_BASE_CRYPTO_MODULE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+#if defined(USE_NSS)
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+#endif
+
+namespace net {
+
+class CryptoModule;
+
+typedef std::vector<scoped_refptr<CryptoModule> > CryptoModuleList;
+
+class NET_EXPORT CryptoModule
+ : public base::RefCountedThreadSafe<CryptoModule> {
+ public:
+#if defined(USE_NSS)
+ typedef PK11SlotInfo* OSModuleHandle;
+#else
+ typedef void* OSModuleHandle;
+#endif
+
+ OSModuleHandle os_module_handle() const { return module_handle_; }
+
+ std::string GetTokenName() const;
+
+ static CryptoModule* CreateFromHandle(OSModuleHandle handle);
+
+ private:
+ friend class base::RefCountedThreadSafe<CryptoModule>;
+
+ explicit CryptoModule(OSModuleHandle handle);
+ ~CryptoModule();
+
+ OSModuleHandle module_handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(CryptoModule);
+};
+
+} // namespace net
+
+#endif // NET_BASE_CRYPTO_MODULE_H_
diff --git a/chromium/net/base/crypto_module_nss.cc b/chromium/net/base/crypto_module_nss.cc
new file mode 100644
index 00000000000..df52ae9cb4f
--- /dev/null
+++ b/chromium/net/base/crypto_module_nss.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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/base/crypto_module.h"
+
+#include <pk11pub.h>
+
+namespace net {
+
+std::string CryptoModule::GetTokenName() const {
+ return PK11_GetTokenName(module_handle_);
+}
+
+// static
+CryptoModule* CryptoModule::CreateFromHandle(OSModuleHandle handle) {
+ return new CryptoModule(handle);
+}
+
+CryptoModule::CryptoModule(OSModuleHandle handle) : module_handle_(handle) {
+ PK11_ReferenceSlot(module_handle_);
+}
+
+CryptoModule::~CryptoModule() {
+ PK11_FreeSlot(module_handle_);
+}
+
+} // namespace net
diff --git a/chromium/net/base/crypto_module_openssl.cc b/chromium/net/base/crypto_module_openssl.cc
new file mode 100644
index 00000000000..3ef050fc6b3
--- /dev/null
+++ b/chromium/net/base/crypto_module_openssl.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 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 "base/logging.h"
+#include "net/base/crypto_module.h"
+
+namespace net {
+
+std::string CryptoModule::GetTokenName() const {
+ NOTIMPLEMENTED();
+ return "";
+}
+
+// static
+CryptoModule* CryptoModule::CreateFromHandle(OSModuleHandle handle) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+CryptoModule::CryptoModule(OSModuleHandle handle) : module_handle_(handle) {
+}
+
+CryptoModule::~CryptoModule() {
+}
+
+} // namespace net
diff --git a/chromium/net/base/data_url.cc b/chromium/net/base/data_url.cc
new file mode 100644
index 00000000000..e7b46cd44ce
--- /dev/null
+++ b/chromium/net/base/data_url.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 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.
+
+// NOTE: based loosely on mozilla's nsDataChannel.cpp
+
+#include <algorithm>
+
+#include "net/base/data_url.h"
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "net/base/escape.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// static
+bool DataURL::Parse(const GURL& url, std::string* mime_type,
+ std::string* charset, std::string* data) {
+ DCHECK(mime_type->empty());
+ DCHECK(charset->empty());
+ std::string::const_iterator begin = url.spec().begin();
+ std::string::const_iterator end = url.spec().end();
+
+ std::string::const_iterator after_colon = std::find(begin, end, ':');
+ if (after_colon == end)
+ return false;
+ ++after_colon;
+
+ std::string::const_iterator comma = std::find(after_colon, end, ',');
+ if (comma == end)
+ return false;
+
+ std::vector<std::string> meta_data;
+ std::string unparsed_meta_data(after_colon, comma);
+ base::SplitString(unparsed_meta_data, ';', &meta_data);
+
+ std::vector<std::string>::iterator iter = meta_data.begin();
+ if (iter != meta_data.end()) {
+ mime_type->swap(*iter);
+ StringToLowerASCII(mime_type);
+ ++iter;
+ }
+
+ static const char kBase64Tag[] = "base64";
+ static const char kCharsetTag[] = "charset=";
+ const size_t kCharsetTagLength = arraysize(kCharsetTag) - 1;
+
+ bool base64_encoded = false;
+ for (; iter != meta_data.end(); ++iter) {
+ if (!base64_encoded && *iter == kBase64Tag) {
+ base64_encoded = true;
+ } else if (charset->empty() &&
+ iter->compare(0, kCharsetTagLength, kCharsetTag) == 0) {
+ charset->assign(iter->substr(kCharsetTagLength));
+ }
+ }
+
+ // fallback to defaults if nothing specified in the URL:
+ if (mime_type->empty())
+ mime_type->assign("text/plain");
+ if (charset->empty())
+ charset->assign("US-ASCII");
+
+ // The caller may not be interested in receiving the data.
+ if (!data)
+ return true;
+
+ // Preserve spaces if dealing with text or xml input, same as mozilla:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=138052
+ // but strip them otherwise:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=37200
+ // (Spaces in a data URL should be escaped, which is handled below, so any
+ // spaces now are wrong. People expect to be able to enter them in the URL
+ // bar for text, and it can't hurt, so we allow it.)
+ std::string temp_data = std::string(comma + 1, end);
+
+ // For base64, we may have url-escaped whitespace which is not part
+ // of the data, and should be stripped. Otherwise, the escaped whitespace
+ // could be part of the payload, so don't strip it.
+ if (base64_encoded) {
+ temp_data = UnescapeURLComponent(temp_data,
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS |
+ UnescapeRule::CONTROL_CHARS);
+ }
+
+ // Strip whitespace.
+ if (base64_encoded || !(mime_type->compare(0, 5, "text/") == 0 ||
+ mime_type->find("xml") != std::string::npos)) {
+ temp_data.erase(std::remove_if(temp_data.begin(), temp_data.end(),
+ IsAsciiWhitespace<wchar_t>),
+ temp_data.end());
+ }
+
+ if (!base64_encoded) {
+ temp_data = UnescapeURLComponent(temp_data,
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS |
+ UnescapeRule::CONTROL_CHARS);
+ }
+
+ if (base64_encoded) {
+ size_t length = temp_data.length();
+ size_t padding_needed = 4 - (length % 4);
+ // If the input wasn't padded, then we pad it as necessary until we have a
+ // length that is a multiple of 4 as required by our decoder. We don't
+ // correct if the input was incorrectly padded. If |padding_needed| == 3,
+ // then the input isn't well formed and decoding will fail with or without
+ // padding.
+ if ((padding_needed == 1 || padding_needed == 2) &&
+ temp_data[length - 1] != '=') {
+ temp_data.resize(length + padding_needed, '=');
+ }
+ return base::Base64Decode(temp_data, data);
+ }
+
+ temp_data.swap(*data);
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/base/data_url.h b/chromium/net/base/data_url.h
new file mode 100644
index 00000000000..3c1e3033dc6
--- /dev/null
+++ b/chromium/net/base/data_url.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 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_BASE_DATA_URL_H_
+#define NET_BASE_DATA_URL_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// See RFC 2397 for a complete description of the 'data' URL scheme.
+//
+// Briefly, a 'data' URL has the form:
+//
+// data:[<mediatype>][;base64],<data>
+//
+// The <mediatype> is an Internet media type specification (with optional
+// parameters.) The appearance of ";base64" means that the data is encoded as
+// base64. Without ";base64", the data (as a sequence of octets) is represented
+// using ASCII encoding for octets inside the range of safe URL characters and
+// using the standard %xx hex encoding of URLs for octets outside that range.
+// If <mediatype> is omitted, it defaults to text/plain;charset=US-ASCII. As a
+// shorthand, "text/plain" can be omitted but the charset parameter supplied.
+//
+class NET_EXPORT DataURL {
+ public:
+ // This method can be used to parse a 'data' URL into its component pieces.
+ //
+ // The resulting mime_type is normalized to lowercase. The data is the
+ // decoded data (e.g.., if the data URL specifies base64 encoding, then the
+ // returned data is base64 decoded, and any %-escaped bytes are unescaped).
+ //
+ // If the URL is malformed, then this method will return false, and its
+ // output variables will remain unchanged. On success, true is returned.
+ //
+ // OPTIONAL: If |data| is NULL, then the <data> section will not be parsed
+ // or validated.
+ //
+ static bool Parse(const GURL& url,
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data);
+};
+
+} // namespace net
+
+#endif // NET_BASE_DATA_URL_H_
diff --git a/chromium/net/base/data_url_unittest.cc b/chromium/net/base/data_url_unittest.cc
new file mode 100644
index 00000000000..2d8e817c980
--- /dev/null
+++ b/chromium/net/base/data_url_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "net/base/data_url.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace {
+
+struct ParseTestData {
+ const char* url;
+ bool is_valid;
+ const char* mime_type;
+ const char* charset;
+ const char* data;
+};
+
+}
+
+TEST(DataURLTest, Parse) {
+ const ParseTestData tests[] = {
+ { "data:",
+ false,
+ "",
+ "",
+ "" },
+
+ { "data:,",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "" },
+
+ { "data:;base64,",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "" },
+
+ { "data:;charset=,test",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "test" },
+
+ { "data:TeXt/HtMl,<b>x</b>",
+ true,
+ "text/html",
+ "US-ASCII",
+ "<b>x</b>" },
+
+ { "data:,foo",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "foo" },
+
+ { "data:;base64,aGVsbG8gd29ybGQ=",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "hello world" },
+
+ { "data:foo/bar;baz=1;charset=kk,boo",
+ true,
+ "foo/bar",
+ "kk",
+ "boo" },
+
+ { "data:foo/bar;charset=kk;baz=1,boo",
+ true,
+ "foo/bar",
+ "kk",
+ "boo" },
+
+ { "data:text/html,%3Chtml%3E%3Cbody%3E%3Cb%3Ehello%20world"
+ "%3C%2Fb%3E%3C%2Fbody%3E%3C%2Fhtml%3E",
+ true,
+ "text/html",
+ "US-ASCII",
+ "<html><body><b>hello world</b></body></html>" },
+
+ { "data:text/html,<html><body><b>hello world</b></body></html>",
+ true,
+ "text/html",
+ "US-ASCII",
+ "<html><body><b>hello world</b></body></html>" },
+
+ // the comma cannot be url-escaped!
+ { "data:%2Cblah",
+ false,
+ "",
+ "",
+ "" },
+
+ // invalid base64 content
+ { "data:;base64,aGVs_-_-",
+ false,
+ "",
+ "",
+ "" },
+
+ // Spaces should be removed from non-text data URLs (we already tested
+ // spaces above).
+ { "data:image/fractal,a b c d e f g",
+ true,
+ "image/fractal",
+ "US-ASCII",
+ "abcdefg" },
+
+ // Spaces should also be removed from anything base-64 encoded
+ { "data:;base64,aGVs bG8gd2 9ybGQ=",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "hello world" },
+
+ // Other whitespace should also be removed from anything base-64 encoded.
+ { "data:;base64,aGVs bG8gd2 \n9ybGQ=",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "hello world" },
+
+ // In base64 encoding, escaped whitespace should be stripped.
+ // (This test was taken from acid3)
+ // http://b/1054495
+ { "data:text/javascript;base64,%20ZD%20Qg%0D%0APS%20An%20Zm91cic%0D%0A%207"
+ "%20",
+ true,
+ "text/javascript",
+ "US-ASCII",
+ "d4 = 'four';" },
+
+ // Only unescaped whitespace should be stripped in non-base64.
+ // http://b/1157796
+ { "data:img/png,A B %20 %0A C",
+ true,
+ "img/png",
+ "US-ASCII",
+ "AB \nC" },
+
+ { "data:text/plain;charset=utf-8;base64,SGVsbMO2",
+ true,
+ "text/plain",
+ "utf-8",
+ "Hell\xC3\xB6" },
+
+ // Not sufficiently padded.
+ { "data:;base64,aGVsbG8gd29ybGQ",
+ true,
+ "text/plain",
+ "US-ASCII",
+ "hello world" },
+
+ // Bad encoding (truncated).
+ { "data:;base64,aGVsbG8gd29yb",
+ false,
+ "",
+ "",
+ "" },
+
+ // TODO(darin): add more interesting tests
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::string mime_type;
+ std::string charset;
+ std::string data;
+ bool ok =
+ net::DataURL::Parse(GURL(tests[i].url), &mime_type, &charset, &data);
+ EXPECT_EQ(ok, tests[i].is_valid);
+ if (tests[i].is_valid) {
+ EXPECT_EQ(tests[i].mime_type, mime_type);
+ EXPECT_EQ(tests[i].charset, charset);
+ EXPECT_EQ(tests[i].data, data);
+ }
+ }
+}
diff --git a/chromium/net/base/dir_header.html b/chromium/net/base/dir_header.html
new file mode 100644
index 00000000000..b281a980376
--- /dev/null
+++ b/chromium/net/base/dir_header.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+
+<html>
+
+<head>
+
+<script>
+function addRow(name, url, isdir, size, date_modified) {
+ if (name == ".")
+ return;
+
+ var root = "" + document.location;
+ if (root.substr(-1) !== "/")
+ root += "/";
+
+ var table = document.getElementById("table");
+ var row = document.createElement("tr");
+ var file_cell = document.createElement("td");
+ var link = document.createElement("a");
+
+ link.className = isdir ? "icon dir" : "icon file";
+
+ if (name == "..") {
+ link.href = root + "..";
+ link.innerText = document.getElementById("parentDirText").innerText;
+ link.className = "icon up";
+ size = "";
+ date_modified = "";
+ } else {
+ if (isdir) {
+ name = name + "/";
+ url = url + "/";
+ size = "";
+ } else {
+ link.draggable = "true";
+ link.addEventListener("dragstart", onDragStart, false);
+ }
+ link.innerText = name;
+ link.href = root + url;
+ }
+ file_cell.appendChild(link);
+
+ row.appendChild(file_cell);
+ row.appendChild(createCell(size));
+ row.appendChild(createCell(date_modified));
+
+ table.appendChild(row);
+}
+
+function onDragStart(e) {
+ var el = e.srcElement;
+ var name = el.innerText.replace(":", "");
+ var download_url_data = "application/octet-stream:" + name + ":" + el.href;
+ e.dataTransfer.setData("DownloadURL", download_url_data);
+ e.dataTransfer.effectAllowed = "copy";
+}
+
+function createCell(text) {
+ var cell = document.createElement("td");
+ cell.setAttribute("class", "detailsColumn");
+ cell.innerText = text;
+ return cell;
+}
+
+function start(location) {
+ var header = document.getElementById("header");
+ header.innerText = header.innerText.replace("LOCATION", location);
+
+ document.getElementById("title").innerText = header.innerText;
+}
+
+function onListingParsingError() {
+ var box = document.getElementById("listingParsingErrorBox");
+ box.innerHTML = box.innerHTML.replace("LOCATION", encodeURI(document.location)
+ + "?raw");
+ box.style.display = "block";
+}
+</script>
+
+<style>
+
+ h1 {
+ border-bottom: 1px solid #c0c0c0;
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+ white-space: nowrap;
+ }
+
+ table {
+ border-collapse: collapse;
+ }
+
+ tr.header {
+ font-weight: bold;
+ }
+
+ td.detailsColumn {
+ padding-left: 2em;
+ text-align: right;
+ white-space: nowrap;
+ }
+
+ a.icon {
+ padding-left: 1.5em;
+ text-decoration: none;
+ }
+
+ a.icon:hover {
+ text-decoration: underline;
+ }
+
+ a.file {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABHUlEQVR42o2RMW7DIBiF3498iHRJD5JKHurL+CRVBp+i2T16tTynF2gO0KSb5ZrBBl4HHDBuK/WXACH4eO9/CAAAbdvijzLGNE1TVZXfZuHg6XCAQESAZXbOKaXO57eiKG6ft9PrKQIkCQqFoIiQFBGlFIB5nvM8t9aOX2Nd18oDzjnPgCDpn/BH4zh2XZdlWVmWiUK4IgCBoFMUz9eP6zRN75cLgEQhcmTQIbl72O0f9865qLAAsURAAgKBJKEtgLXWvyjLuFsThCSstb8rBCaAQhDYWgIZ7myM+TUBjDHrHlZcbMYYk34cN0YSLcgS+wL0fe9TXDMbY33fR2AYBvyQ8L0Gk8MwREBrTfKe4TpTzwhArXWi8HI84h/1DfwI5mhxJamFAAAAAElFTkSuQmCC ") left top no-repeat;
+ }
+
+ a.dir {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAd5JREFUeNqMU79rFUEQ/vbuodFEEkzAImBpkUabFP4ldpaJhZXYm/RiZWsv/hkWFglBUyTIgyAIIfgIRjHv3r39MePM7N3LcbxAFvZ2b2bn22/mm3XMjF+HL3YW7q28YSIw8mBKoBihhhgCsoORot9d3/ywg3YowMXwNde/PzGnk2vn6PitrT+/PGeNaecg4+qNY3D43vy16A5wDDd4Aqg/ngmrjl/GoN0U5V1QquHQG3q+TPDVhVwyBffcmQGJmSVfyZk7R3SngI4JKfwDJ2+05zIg8gbiereTZRHhJ5KCMOwDFLjhoBTn2g0ghagfKeIYJDPFyibJVBtTREwq60SpYvh5++PpwatHsxSm9QRLSQpEVSd7/TYJUb49TX7gztpjjEffnoVw66+Ytovs14Yp7HaKmUXeX9rKUoMoLNW3srqI5fWn8JejrVkK0QcrkFLOgS39yoKUQe292WJ1guUHG8K2o8K00oO1BTvXoW4yasclUTgZYJY9aFNfAThX5CZRmczAV52oAPoupHhWRIUUAOoyUIlYVaAa/VbLbyiZUiyFbjQFNwiZQSGl4IDy9sO5Wrty0QLKhdZPxmgGcDo8ejn+c/6eiK9poz15Kw7Dr/vN/z6W7q++091/AQYA5mZ8GYJ9K0AAAAAASUVORK5CYII= ") left top no-repeat;
+ }
+
+ a.up {
+ background : url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAmlJREFUeNpsU0toU0EUPfPysx/tTxuDH9SCWhUDooIbd7oRUUTMouqi2iIoCO6lceHWhegy4EJFinWjrlQUpVm0IIoFpVDEIthm0dpikpf3ZuZ6Z94nrXhhMjM3c8895977BBHB2PznK8WPtDgyWH5q77cPH8PpdXuhpQT4ifR9u5sfJb1bmw6VivahATDrxcRZ2njfoaMv+2j7mLDn93MPiNRMvGbL18L9IpF8h9/TN+EYkMffSiOXJ5+hkD+PdqcLpICWHOHc2CC+LEyA/K+cKQMnlQHJX8wqYG3MAJy88Wa4OLDvEqAEOpJd0LxHIMdHBziowSwVlF8D6QaicK01krw/JynwcKoEwZczewroTvZirlKJs5CqQ5CG8pb57FnJUA0LYCXMX5fibd+p8LWDDemcPZbzQyjvH+Ki1TlIciElA7ghwLKV4kRZstt2sANWRjYTAGzuP2hXZFpJ/GsxgGJ0ox1aoFWsDXyyxqCs26+ydmagFN/rRjymJ1898bzGzmQE0HCZpmk5A0RFIv8Pn0WYPsiu6t/Rsj6PauVTwffTSzGAGZhUG2F06hEc9ibS7OPMNp6ErYFlKavo7MkhmTqCxZ/jwzGA9Hx82H2BZSw1NTN9Gx8ycHkajU/7M+jInsDC7DiaEmo1bNl1AMr9ASFgqVu9MCTIzoGUimXVAnnaN0PdBBDCCYbEtMk6wkpQwIG0sn0PQIUF4GsTwLSIFKNqF6DVrQq+IWVrQDxAYQC/1SsYOI4pOxKZrfifiUSbDUisif7XlpGIPufXd/uvdvZm760M0no1FZcnrzUdjw7au3vu/BVgAFLXeuTxhTXVAAAAAElFTkSuQmCC ") left top no-repeat;
+ }
+
+ #listingParsingErrorBox {
+ border: 1px solid black;
+ background: #fae691;
+ padding: 10px;
+ display: none;
+ }
+</style>
+
+<title id="title"></title>
+
+</head>
+
+<body>
+
+<div id="listingParsingErrorBox" i18n-values=".innerHTML:listingParsingErrorBoxText"></div>
+
+<span id="parentDirText" style="display:none" i18n-content="parentDirText"></span>
+
+<h1 id="header" i18n-content="header"></h1>
+
+<table id="table">
+ <tr class="header">
+ <td i18n-content="headerName"></td>
+ <td class="detailsColumn" i18n-content="headerSize"></td>
+ <td class="detailsColumn" i18n-content="headerDateModified"></td>
+ </tr>
+</table>
+
+</body>
+
+</html>
diff --git a/chromium/net/base/directory_lister.cc b/chromium/net/base/directory_lister.cc
new file mode 100644
index 00000000000..046be32d49f
--- /dev/null
+++ b/chromium/net/base/directory_lister.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 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.
+
+#include "net/base/directory_lister.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/i18n/file_util_icu.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+const int kFilesPerEvent = 8;
+
+bool IsDotDot(const base::FilePath& path) {
+ return FILE_PATH_LITERAL("..") == path.BaseName().value();
+}
+
+// Comparator for sorting lister results. This uses the locale aware filename
+// comparison function on the filenames for sorting in the user's locale.
+// Static.
+bool CompareAlphaDirsFirst(const DirectoryLister::DirectoryListerData& a,
+ const DirectoryLister::DirectoryListerData& b) {
+ // Parent directory before all else.
+ if (IsDotDot(a.info.GetName()))
+ return true;
+ if (IsDotDot(b.info.GetName()))
+ return false;
+
+ // Directories before regular files.
+ bool a_is_directory = a.info.IsDirectory();
+ bool b_is_directory = b.info.IsDirectory();
+ if (a_is_directory != b_is_directory)
+ return a_is_directory;
+
+ return file_util::LocaleAwareCompareFilenames(a.info.GetName(),
+ b.info.GetName());
+}
+
+bool CompareDate(const DirectoryLister::DirectoryListerData& a,
+ const DirectoryLister::DirectoryListerData& b) {
+ // Parent directory before all else.
+ if (IsDotDot(a.info.GetName()))
+ return true;
+ if (IsDotDot(b.info.GetName()))
+ return false;
+
+ // Directories before regular files.
+ bool a_is_directory = a.info.IsDirectory();
+ bool b_is_directory = b.info.IsDirectory();
+ if (a_is_directory != b_is_directory)
+ return a_is_directory;
+ return a.info.GetLastModifiedTime() > b.info.GetLastModifiedTime();
+}
+
+// Comparator for sorting find result by paths. This uses the locale-aware
+// comparison function on the filenames for sorting in the user's locale.
+// Static.
+bool CompareFullPath(const DirectoryLister::DirectoryListerData& a,
+ const DirectoryLister::DirectoryListerData& b) {
+ return file_util::LocaleAwareCompareFilenames(a.path, b.path);
+}
+
+void SortData(std::vector<DirectoryLister::DirectoryListerData>* data,
+ DirectoryLister::SortType sort_type) {
+ // Sort the results. See the TODO below (this sort should be removed and we
+ // should do it from JS).
+ if (sort_type == DirectoryLister::DATE)
+ std::sort(data->begin(), data->end(), CompareDate);
+ else if (sort_type == DirectoryLister::FULL_PATH)
+ std::sort(data->begin(), data->end(), CompareFullPath);
+ else if (sort_type == DirectoryLister::ALPHA_DIRS_FIRST)
+ std::sort(data->begin(), data->end(), CompareAlphaDirsFirst);
+ else
+ DCHECK_EQ(DirectoryLister::NO_SORT, sort_type);
+}
+
+} // namespace
+
+DirectoryLister::DirectoryLister(const base::FilePath& dir,
+ DirectoryListerDelegate* delegate)
+ : core_(new Core(dir, false, ALPHA_DIRS_FIRST, this)),
+ delegate_(delegate) {
+ DCHECK(delegate_);
+ DCHECK(!dir.value().empty());
+}
+
+DirectoryLister::DirectoryLister(const base::FilePath& dir,
+ bool recursive,
+ SortType sort,
+ DirectoryListerDelegate* delegate)
+ : core_(new Core(dir, recursive, sort, this)),
+ delegate_(delegate) {
+ DCHECK(delegate_);
+ DCHECK(!dir.value().empty());
+}
+
+DirectoryLister::~DirectoryLister() {
+ Cancel();
+}
+
+bool DirectoryLister::Start() {
+ return core_->Start();
+}
+
+void DirectoryLister::Cancel() {
+ return core_->Cancel();
+}
+
+DirectoryLister::Core::Core(const base::FilePath& dir,
+ bool recursive,
+ SortType sort,
+ DirectoryLister* lister)
+ : dir_(dir),
+ recursive_(recursive),
+ sort_(sort),
+ lister_(lister) {
+ DCHECK(lister_);
+}
+
+DirectoryLister::Core::~Core() {}
+
+bool DirectoryLister::Core::Start() {
+ origin_loop_ = base::MessageLoopProxy::current();
+
+ return base::WorkerPool::PostTask(
+ FROM_HERE, base::Bind(&Core::StartInternal, this), true);
+}
+
+void DirectoryLister::Core::Cancel() {
+ lister_ = NULL;
+}
+
+void DirectoryLister::Core::StartInternal() {
+
+ if (!base::DirectoryExists(dir_)) {
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&DirectoryLister::Core::OnDone, this, ERR_FILE_NOT_FOUND));
+ return;
+ }
+
+ int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
+ if (!recursive_)
+ types |= base::FileEnumerator::INCLUDE_DOT_DOT;
+
+ base::FileEnumerator file_enum(dir_, recursive_, types);
+
+ base::FilePath path;
+ std::vector<DirectoryListerData> file_data;
+ while (lister_ && !(path = file_enum.Next()).empty()) {
+ DirectoryListerData data;
+ data.info = file_enum.GetInfo();
+ data.path = path;
+ file_data.push_back(data);
+
+ /* TODO(brettw) bug 24107: It would be nice to send incremental updates.
+ We gather them all so they can be sorted, but eventually the sorting
+ should be done from JS to give more flexibility in the page. When we do
+ that, we can uncomment this to send incremental updates to the page.
+
+ if (file_data.size() < kFilesPerEvent)
+ continue;
+
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&DirectoryLister::Core::SendData, file_data));
+ file_data.clear();
+ */
+ }
+
+ SortData(&file_data, sort_);
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&DirectoryLister::Core::SendData, this, file_data));
+
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&DirectoryLister::Core::OnDone, this, OK));
+}
+
+void DirectoryLister::Core::SendData(
+ const std::vector<DirectoryLister::DirectoryListerData>& data) {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ // We need to check for cancellation (indicated by NULL'ing of |lister_|)
+ // which can happen during each callback.
+ for (size_t i = 0; lister_ && i < data.size(); ++i)
+ lister_->OnReceivedData(data[i]);
+}
+
+void DirectoryLister::Core::OnDone(int error) {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ if (lister_)
+ lister_->OnDone(error);
+}
+
+void DirectoryLister::OnReceivedData(const DirectoryListerData& data) {
+ delegate_->OnListFile(data);
+}
+
+void DirectoryLister::OnDone(int error) {
+ delegate_->OnListDone(error);
+}
+
+} // namespace net
diff --git a/chromium/net/base/directory_lister.h b/chromium/net/base/directory_lister.h
new file mode 100644
index 00000000000..5fbc9dc6fa1
--- /dev/null
+++ b/chromium/net/base/directory_lister.h
@@ -0,0 +1,123 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_DIRECTORY_LISTER_H_
+#define NET_BASE_DIRECTORY_LISTER_H_
+
+#include <vector>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+//
+// This class provides an API for listing the contents of a directory on the
+// filesystem asynchronously. It spawns a background thread, and enumerates
+// the specified directory on that thread. It marshalls WIN32_FIND_DATA
+// structs over to the main application thread. The consumer of this class
+// is insulated from any of the multi-threading details.
+//
+class NET_EXPORT DirectoryLister {
+ public:
+ // Represents one file found.
+ struct DirectoryListerData {
+ base::FileEnumerator::FileInfo info;
+ base::FilePath path;
+ };
+
+ // Implement this class to receive directory entries.
+ class DirectoryListerDelegate {
+ public:
+ // Called for each file found by the lister.
+ virtual void OnListFile(const DirectoryListerData& data) = 0;
+
+ // Called when the listing is complete.
+ virtual void OnListDone(int error) = 0;
+
+ protected:
+ virtual ~DirectoryListerDelegate() {}
+ };
+
+ // Sort options
+ // ALPHA_DIRS_FIRST is the default sort :
+ // directories first in name order, then files by name order
+ // FULL_PATH sorts by paths as strings, ignoring files v. directories
+ // DATE sorts by last modified date
+ enum SortType {
+ NO_SORT,
+ DATE,
+ ALPHA_DIRS_FIRST,
+ FULL_PATH
+ };
+
+ DirectoryLister(const base::FilePath& dir,
+ DirectoryListerDelegate* delegate);
+
+ DirectoryLister(const base::FilePath& dir,
+ bool recursive,
+ SortType sort,
+ DirectoryListerDelegate* delegate);
+
+ // Will invoke Cancel().
+ ~DirectoryLister();
+
+ // Call this method to start the directory enumeration thread.
+ bool Start();
+
+ // Call this method to asynchronously stop directory enumeration. The
+ // delegate will not be called back.
+ void Cancel();
+
+ private:
+ class Core : public base::RefCountedThreadSafe<Core> {
+ public:
+ Core(const base::FilePath& dir,
+ bool recursive,
+ SortType sort,
+ DirectoryLister* lister);
+
+ bool Start();
+
+ void Cancel();
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ class DataEvent;
+
+ ~Core();
+
+ // This method runs on a WorkerPool thread.
+ void StartInternal();
+
+ void SendData(const std::vector<DirectoryListerData>& data);
+
+ void OnDone(int error);
+
+ base::FilePath dir_;
+ bool recursive_;
+ SortType sort_;
+ scoped_refptr<base::MessageLoopProxy> origin_loop_;
+
+ // |lister_| gets set to NULL when canceled.
+ DirectoryLister* lister_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+ };
+
+ void OnReceivedData(const DirectoryListerData& data);
+ void OnDone(int error);
+
+ const scoped_refptr<Core> core_;
+ DirectoryListerDelegate* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(DirectoryLister);
+};
+
+} // namespace net
+
+#endif // NET_BASE_DIRECTORY_LISTER_H_
diff --git a/chromium/net/base/directory_lister_unittest.cc b/chromium/net/base/directory_lister_unittest.cc
new file mode 100644
index 00000000000..0ad71af827c
--- /dev/null
+++ b/chromium/net/base/directory_lister_unittest.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 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.
+
+#include <list>
+#include <utility>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/i18n/file_util_icu.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/directory_lister.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class ListerDelegate : public DirectoryLister::DirectoryListerDelegate {
+ public:
+ ListerDelegate(bool recursive,
+ bool quit_loop_after_each_file)
+ : error_(-1),
+ recursive_(recursive),
+ quit_loop_after_each_file_(quit_loop_after_each_file) {
+ }
+
+ virtual void OnListFile(
+ const DirectoryLister::DirectoryListerData& data) OVERRIDE {
+ file_list_.push_back(data.info);
+ paths_.push_back(data.path);
+ if (quit_loop_after_each_file_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void OnListDone(int error) OVERRIDE {
+ error_ = error;
+ base::MessageLoop::current()->Quit();
+ if (recursive_)
+ CheckRecursiveSort();
+ else
+ CheckSort();
+ }
+
+ void CheckRecursiveSort() {
+ // Check that we got files in the right order.
+ if (!file_list_.empty()) {
+ for (size_t previous = 0, current = 1;
+ current < file_list_.size();
+ previous++, current++) {
+ EXPECT_TRUE(file_util::LocaleAwareCompareFilenames(
+ paths_[previous], paths_[current]));
+ }
+ }
+ }
+
+ void CheckSort() {
+ // Check that we got files in the right order.
+ if (!file_list_.empty()) {
+ for (size_t previous = 0, current = 1;
+ current < file_list_.size();
+ previous++, current++) {
+ // Directories should come before files.
+ if (file_list_[previous].IsDirectory() &&
+ !file_list_[current].IsDirectory()) {
+ continue;
+ }
+ EXPECT_NE(FILE_PATH_LITERAL(".."),
+ file_list_[current].GetName().BaseName().value());
+ EXPECT_EQ(file_list_[previous].IsDirectory(),
+ file_list_[current].IsDirectory());
+ EXPECT_TRUE(file_util::LocaleAwareCompareFilenames(
+ file_list_[previous].GetName(),
+ file_list_[current].GetName()));
+ }
+ }
+ }
+
+ int error() const { return error_; }
+
+ int num_files() const { return file_list_.size(); }
+
+ private:
+ int error_;
+ bool recursive_;
+ bool quit_loop_after_each_file_;
+ std::vector<base::FileEnumerator::FileInfo> file_list_;
+ std::vector<base::FilePath> paths_;
+};
+
+class DirectoryListerTest : public PlatformTest {
+ public:
+
+ virtual void SetUp() OVERRIDE {
+ const int kMaxDepth = 3;
+ const int kBranchingFactor = 4;
+ const int kFilesPerDirectory = 5;
+
+ // Randomly create a directory structure of depth 3 in a temporary root
+ // directory.
+ std::list<std::pair<base::FilePath, int> > directories;
+ ASSERT_TRUE(temp_root_dir_.CreateUniqueTempDir());
+ directories.push_back(std::make_pair(temp_root_dir_.path(), 0));
+ while (!directories.empty()) {
+ std::pair<base::FilePath, int> dir_data = directories.front();
+ directories.pop_front();
+ for (int i = 0; i < kFilesPerDirectory; i++) {
+ std::string file_name = base::StringPrintf("file_id_%d", i);
+ base::FilePath file_path = dir_data.first.AppendASCII(file_name);
+ base::PlatformFile file = base::CreatePlatformFile(
+ file_path,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ NULL,
+ NULL);
+ ASSERT_NE(base::kInvalidPlatformFileValue, file);
+ ASSERT_TRUE(base::ClosePlatformFile(file));
+ }
+ if (dir_data.second < kMaxDepth - 1) {
+ for (int i = 0; i < kBranchingFactor; i++) {
+ std::string dir_name = base::StringPrintf("child_dir_%d", i);
+ base::FilePath dir_path = dir_data.first.AppendASCII(dir_name);
+ ASSERT_TRUE(file_util::CreateDirectory(dir_path));
+ directories.push_back(std::make_pair(dir_path, dir_data.second + 1));
+ }
+ }
+ }
+ PlatformTest::SetUp();
+ }
+
+ const base::FilePath& root_path() const {
+ return temp_root_dir_.path();
+ }
+
+ private:
+ base::ScopedTempDir temp_root_dir_;
+};
+
+TEST_F(DirectoryListerTest, BigDirTest) {
+ ListerDelegate delegate(false, false);
+ DirectoryLister lister(root_path(), &delegate);
+ lister.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(OK, delegate.error());
+}
+
+TEST_F(DirectoryListerTest, BigDirRecursiveTest) {
+ ListerDelegate delegate(true, false);
+ DirectoryLister lister(root_path(), true, DirectoryLister::FULL_PATH,
+ &delegate);
+ lister.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(OK, delegate.error());
+}
+
+TEST_F(DirectoryListerTest, CancelTest) {
+ ListerDelegate delegate(false, true);
+ DirectoryLister lister(root_path(), &delegate);
+ lister.Start();
+
+ base::MessageLoop::current()->Run();
+
+ int num_files = delegate.num_files();
+
+ lister.Cancel();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(num_files, delegate.num_files());
+}
+
+TEST_F(DirectoryListerTest, EmptyDirTest) {
+ base::ScopedTempDir tempDir;
+ EXPECT_TRUE(tempDir.CreateUniqueTempDir());
+
+ bool kRecursive = false;
+ bool kQuitLoopAfterEachFile = false;
+ ListerDelegate delegate(kRecursive, kQuitLoopAfterEachFile);
+ DirectoryLister lister(tempDir.path(), &delegate);
+ lister.Start();
+
+ base::MessageLoop::current()->Run();
+
+ // Contains only the parent directory ("..")
+ EXPECT_EQ(1, delegate.num_files());
+ EXPECT_EQ(OK, delegate.error());
+}
+
+} // namespace net
diff --git a/chromium/net/base/dns_reloader.cc b/chromium/net/base/dns_reloader.cc
new file mode 100644
index 00000000000..1902820177e
--- /dev/null
+++ b/chromium/net/base/dns_reloader.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 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.
+
+#include "net/base/dns_reloader.h"
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \
+ !defined(OS_ANDROID)
+
+#include <resolv.h>
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_local_storage.h"
+#include "net/base/network_change_notifier.h"
+
+namespace {
+
+// On Linux/BSD, changes to /etc/resolv.conf can go unnoticed thus resulting
+// in DNS queries failing either because nameservers are unknown on startup
+// or because nameserver info has changed as a result of e.g. connecting to
+// a new network. Some distributions patch glibc to stat /etc/resolv.conf
+// to try to automatically detect such changes but these patches are not
+// universal and even patched systems such as Jaunty appear to need calls
+// to res_ninit to reload the nameserver information in different threads.
+//
+// To fix this, on systems with FilePathWatcher support, we use
+// NetworkChangeNotifier::DNSObserver to monitor /etc/resolv.conf to
+// enable us to respond to DNS changes and reload the resolver state.
+//
+// OpenBSD does not have thread-safe res_ninit/res_nclose so we can't do
+// the same trick there and most *BSD's don't yet have support for
+// FilePathWatcher (but perhaps the new kqueue mac code just needs to be
+// ported to *BSD to support that).
+//
+// Android does not have /etc/resolv.conf. The system takes care of nameserver
+// changes, so none of this is needed.
+
+class DnsReloader : public net::NetworkChangeNotifier::DNSObserver {
+ public:
+ struct ReloadState {
+ int resolver_generation;
+ };
+
+ // NetworkChangeNotifier::DNSObserver:
+ virtual void OnDNSChanged() OVERRIDE {
+ DCHECK_EQ(base::MessageLoop::current()->type(), base::MessageLoop::TYPE_IO);
+ base::AutoLock l(lock_);
+ resolver_generation_++;
+ }
+
+ void MaybeReload() {
+ ReloadState* reload_state = static_cast<ReloadState*>(tls_index_.Get());
+ base::AutoLock l(lock_);
+
+ if (!reload_state) {
+ reload_state = new ReloadState();
+ reload_state->resolver_generation = resolver_generation_;
+ res_ninit(&_res);
+ tls_index_.Set(reload_state);
+ } else if (reload_state->resolver_generation != resolver_generation_) {
+ reload_state->resolver_generation = resolver_generation_;
+ // It is safe to call res_nclose here since we know res_ninit will have
+ // been called above.
+ res_nclose(&_res);
+ res_ninit(&_res);
+ }
+ }
+
+ // Free the allocated state.
+ static void SlotReturnFunction(void* data) {
+ ReloadState* reload_state = static_cast<ReloadState*>(data);
+ if (reload_state)
+ res_nclose(&_res);
+ delete reload_state;
+ }
+
+ private:
+ DnsReloader() : resolver_generation_(0) {
+ tls_index_.Initialize(SlotReturnFunction);
+ net::NetworkChangeNotifier::AddDNSObserver(this);
+ }
+
+ virtual ~DnsReloader() {
+ NOTREACHED(); // LeakyLazyInstance is not destructed.
+ }
+
+ base::Lock lock_; // Protects resolver_generation_.
+ int resolver_generation_;
+ friend struct base::DefaultLazyInstanceTraits<DnsReloader>;
+
+ // We use thread local storage to identify which ReloadState to interact with.
+ static base::ThreadLocalStorage::StaticSlot tls_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsReloader);
+};
+
+// A TLS slot to the ReloadState for the current thread.
+// static
+base::ThreadLocalStorage::StaticSlot DnsReloader::tls_index_ = TLS_INITIALIZER;
+
+base::LazyInstance<DnsReloader>::Leaky
+ g_dns_reloader = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace net {
+
+void EnsureDnsReloaderInit() {
+ DnsReloader* t ALLOW_UNUSED = g_dns_reloader.Pointer();
+}
+
+void DnsReloaderMaybeReload() {
+ // This routine can be called by any of the DNS worker threads.
+ DnsReloader* dns_reloader = g_dns_reloader.Pointer();
+ dns_reloader->MaybeReload();
+}
+
+} // namespace net
+
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) &&
+ // !defined(OS_ANDROID)
diff --git a/chromium/net/base/dns_reloader.h b/chromium/net/base/dns_reloader.h
new file mode 100644
index 00000000000..889d404794a
--- /dev/null
+++ b/chromium/net/base/dns_reloader.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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_BASE_DNS_RELOADER_H_
+#define NET_BASE_DNS_RELOADER_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
+namespace net {
+
+// Call on the network thread before calling DnsReloaderMaybeReload() anywhere.
+void EnsureDnsReloaderInit();
+
+// Call on the worker thread before doing a DNS lookup to reload the
+// resolver for that thread by doing res_ninit() if required.
+void DnsReloaderMaybeReload();
+
+} // namespace net
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD)
+
+#endif // NET_BASE_DNS_RELOADER_H_
diff --git a/chromium/net/base/dns_util.cc b/chromium/net/base/dns_util.cc
new file mode 100644
index 00000000000..5d47d4b8b5a
--- /dev/null
+++ b/chromium/net/base/dns_util.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2011 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/base/dns_util.h"
+
+#include <cstring>
+
+namespace net {
+
+// Based on DJB's public domain code.
+bool DNSDomainFromDot(const base::StringPiece& dotted, std::string* out) {
+ const char* buf = dotted.data();
+ unsigned n = dotted.size();
+ char label[63];
+ unsigned int labellen = 0; /* <= sizeof label */
+ char name[255];
+ unsigned int namelen = 0; /* <= sizeof name */
+ char ch;
+
+ for (;;) {
+ if (!n)
+ break;
+ ch = *buf++;
+ --n;
+ if (ch == '.') {
+ if (labellen) {
+ if (namelen + labellen + 1 > sizeof name)
+ return false;
+ name[namelen++] = labellen;
+ memcpy(name + namelen, label, labellen);
+ namelen += labellen;
+ labellen = 0;
+ }
+ continue;
+ }
+ if (labellen >= sizeof label)
+ return false;
+ label[labellen++] = ch;
+ }
+
+ if (labellen) {
+ if (namelen + labellen + 1 > sizeof name)
+ return false;
+ name[namelen++] = labellen;
+ memcpy(name + namelen, label, labellen);
+ namelen += labellen;
+ labellen = 0;
+ }
+
+ if (namelen + 1 > sizeof name)
+ return false;
+ if (namelen == 0) // Empty names e.g. "", "." are not valid.
+ return false;
+ name[namelen++] = 0; // This is the root label (of length 0).
+
+ *out = std::string(name, namelen);
+ return true;
+}
+
+std::string DNSDomainToString(const base::StringPiece& domain) {
+ std::string ret;
+
+ for (unsigned i = 0; i < domain.size() && domain[i]; i += domain[i] + 1) {
+#if CHAR_MIN < 0
+ if (domain[i] < 0)
+ return std::string();
+#endif
+ if (domain[i] > 63)
+ return std::string();
+
+ if (i)
+ ret += ".";
+
+ if (static_cast<unsigned>(domain[i]) + i + 1 > domain.size())
+ return std::string();
+
+ domain.substr(i + 1, domain[i]).AppendToString(&ret);
+ }
+ return ret;
+}
+
+bool IsSTD3ASCIIValidCharacter(char c) {
+ if (c <= 0x2c)
+ return false;
+ if (c >= 0x7b)
+ return false;
+ if (c >= 0x2e && c <= 0x2f)
+ return false;
+ if (c >= 0x3a && c <= 0x40)
+ return false;
+ if (c >= 0x5b && c <= 0x60)
+ return false;
+ return true;
+}
+
+std::string TrimEndingDot(const base::StringPiece& host) {
+ base::StringPiece host_trimmed = host;
+ size_t len = host_trimmed.length();
+ if (len > 1 && host_trimmed[len - 1] == '.') {
+ host_trimmed.remove_suffix(1);
+ }
+ return host_trimmed.as_string();
+}
+
+} // namespace net
diff --git a/chromium/net/base/dns_util.h b/chromium/net/base/dns_util.h
new file mode 100644
index 00000000000..f0d9574b99a
--- /dev/null
+++ b/chromium/net/base/dns_util.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_DNS_UTIL_H_
+#define NET_BASE_DNS_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// DNSDomainFromDot - convert a domain string to DNS format. From DJB's
+// public domain DNS library.
+//
+// dotted: a string in dotted form: "www.google.com"
+// out: a result in DNS form: "\x03www\x06google\x03com\x00"
+NET_EXPORT_PRIVATE bool DNSDomainFromDot(const base::StringPiece& dotted,
+ std::string* out);
+
+// DNSDomainToString converts a domain in DNS format to a dotted string.
+// Excludes the dot at the end.
+NET_EXPORT_PRIVATE std::string DNSDomainToString(
+ const base::StringPiece& domain);
+
+// Returns true iff the given character is in the set of valid DNS label
+// characters as given in RFC 3490, 4.1, 3(a)
+NET_EXPORT_PRIVATE bool IsSTD3ASCIIValidCharacter(char c);
+
+// Returns the hostname by trimming the ending dot, if one exists.
+NET_EXPORT std::string TrimEndingDot(const base::StringPiece& host);
+
+} // namespace net
+
+#endif // NET_BASE_DNS_UTIL_H_
diff --git a/chromium/net/base/dns_util_unittest.cc b/chromium/net/base/dns_util_unittest.cc
new file mode 100644
index 00000000000..1e1e4f03811
--- /dev/null
+++ b/chromium/net/base/dns_util_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2009 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/base/dns_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class DNSUtilTest : public testing::Test {
+};
+
+// IncludeNUL converts a char* to a std::string and includes the terminating
+// NUL in the result.
+static std::string IncludeNUL(const char* in) {
+ return std::string(in, strlen(in) + 1);
+}
+
+TEST_F(DNSUtilTest, DNSDomainFromDot) {
+ std::string out;
+
+ EXPECT_FALSE(DNSDomainFromDot("", &out));
+ EXPECT_FALSE(DNSDomainFromDot(".", &out));
+ EXPECT_FALSE(DNSDomainFromDot("..", &out));
+
+ EXPECT_TRUE(DNSDomainFromDot("com", &out));
+ EXPECT_EQ(out, IncludeNUL("\003com"));
+ EXPECT_TRUE(DNSDomainFromDot("google.com", &out));
+ EXPECT_EQ(out, IncludeNUL("\x006google\003com"));
+ EXPECT_TRUE(DNSDomainFromDot("www.google.com", &out));
+ EXPECT_EQ(out, IncludeNUL("\003www\006google\003com"));
+
+ // Label is 63 chars: still valid
+ EXPECT_TRUE(DNSDomainFromDot("123456789a123456789a123456789a123456789a123456789a123456789a123", &out));
+ EXPECT_EQ(out, IncludeNUL("\077123456789a123456789a123456789a123456789a123456789a123456789a123"));
+
+ // Label is too long: invalid
+ EXPECT_FALSE(DNSDomainFromDot("123456789a123456789a123456789a123456789a123456789a123456789a1234", &out));
+
+ // 253 characters in the name: still valid
+ EXPECT_TRUE(DNSDomainFromDot("123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123", &out));
+ EXPECT_EQ(out, IncludeNUL("\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\011123456789\003123"));
+
+ // 254 characters in the name: invalid
+ EXPECT_FALSE(DNSDomainFromDot("123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.123456789.1234", &out));
+
+ // Zero length labels should be dropped.
+ EXPECT_TRUE(DNSDomainFromDot("www.google.com.", &out));
+ EXPECT_EQ(out, IncludeNUL("\003www\006google\003com"));
+
+ EXPECT_TRUE(DNSDomainFromDot(".google.com", &out));
+ EXPECT_EQ(out, IncludeNUL("\006google\003com"));
+
+ EXPECT_TRUE(DNSDomainFromDot("www..google.com", &out));
+ EXPECT_EQ(out, IncludeNUL("\003www\006google\003com"));
+}
+
+TEST_F(DNSUtilTest, DNSDomainToString) {
+ EXPECT_EQ("", DNSDomainToString(IncludeNUL("")));
+ EXPECT_EQ("foo", DNSDomainToString(IncludeNUL("\003foo")));
+ EXPECT_EQ("foo.bar", DNSDomainToString(IncludeNUL("\003foo\003bar")));
+ EXPECT_EQ("foo.bar.uk",
+ DNSDomainToString(IncludeNUL("\003foo\003bar\002uk")));
+
+ // It should cope with a lack of root label.
+ EXPECT_EQ("foo.bar", DNSDomainToString("\003foo\003bar"));
+
+ // Invalid inputs should return an empty string.
+ EXPECT_EQ("", DNSDomainToString(IncludeNUL("\x80")));
+ EXPECT_EQ("", DNSDomainToString("\x06"));
+}
+
+TEST_F(DNSUtilTest, STD3ASCII) {
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('a'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('b'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('c'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('1'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('2'));
+ EXPECT_TRUE(IsSTD3ASCIIValidCharacter('3'));
+
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('.'));
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('/'));
+ EXPECT_FALSE(IsSTD3ASCIIValidCharacter('\x00'));
+}
+
+} // namespace net
diff --git a/chromium/net/base/escape.cc b/chromium/net/base/escape.cc
new file mode 100644
index 00000000000..d1b592c7171
--- /dev/null
+++ b/chromium/net/base/escape.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 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.
+
+#include "net/base/escape.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace net {
+
+namespace {
+
+const char kHexString[] = "0123456789ABCDEF";
+inline char IntToHex(int i) {
+ DCHECK_GE(i, 0) << i << " not a hex value";
+ DCHECK_LE(i, 15) << i << " not a hex value";
+ return kHexString[i];
+}
+
+// A fast bit-vector map for ascii characters.
+//
+// Internally stores 256 bits in an array of 8 ints.
+// Does quick bit-flicking to lookup needed characters.
+struct Charmap {
+ bool Contains(unsigned char c) const {
+ return ((map[c >> 5] & (1 << (c & 31))) != 0);
+ }
+
+ uint32 map[8];
+};
+
+// Given text to escape and a Charmap defining which values to escape,
+// return an escaped string. If use_plus is true, spaces are converted
+// to +, otherwise, if spaces are in the charmap, they are converted to
+// %20.
+std::string Escape(const std::string& text, const Charmap& charmap,
+ bool use_plus) {
+ std::string escaped;
+ escaped.reserve(text.length() * 3);
+ for (unsigned int i = 0; i < text.length(); ++i) {
+ unsigned char c = static_cast<unsigned char>(text[i]);
+ if (use_plus && ' ' == c) {
+ escaped.push_back('+');
+ } else if (charmap.Contains(c)) {
+ escaped.push_back('%');
+ escaped.push_back(IntToHex(c >> 4));
+ escaped.push_back(IntToHex(c & 0xf));
+ } else {
+ escaped.push_back(c);
+ }
+ }
+ return escaped;
+}
+
+// Contains nonzero when the corresponding character is unescapable for normal
+// URLs. These characters are the ones that may change the parsing of a URL, so
+// we don't want to unescape them sometimes. In many case we won't want to
+// unescape spaces, but that is controlled by parameters to Unescape*.
+//
+// The basic rule is that we can't unescape anything that would changing parsing
+// like # or ?. We also can't unescape &, =, or + since that could be part of a
+// query and that could change the server's parsing of the query. Nor can we
+// unescape \ since src/url/ will convert it to a /.
+//
+// Lastly, we can't unescape anything that doesn't have a canonical
+// representation in a URL. This means that unescaping will change the URL, and
+// you could get different behavior if you copy and paste the URL, or press
+// enter in the URL bar. The list of characters that fall into this category
+// are the ones labeled PASS (allow either escaped or unescaped) in the big
+// lookup table at the top of url/url_canon_path.cc. Also, characters
+// that have CHAR_QUERY set in url/url_canon_internal.cc but are not
+// allowed in query strings according to http://www.ietf.org/rfc/rfc3261.txt are
+// not unescaped, to avoid turning a valid url according to spec into an
+// invalid one.
+const char kUrlUnescape[128] = {
+// NULL, control chars...
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+// ' ' ! " # $ % & ' ( ) * + , - . /
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0,
+// @ A B C D E F G H I J K L M N O
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+// P Q R S T U V W X Y Z [ \ ] ^ _
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+// ` a b c d e f g h i j k l m n o
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+// p q r s t u v w x y z { | } ~ <NBSP>
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0
+};
+
+template<typename STR>
+STR UnescapeURLWithOffsetsImpl(const STR& escaped_text,
+ UnescapeRule::Type rules,
+ std::vector<size_t>* offsets_for_adjustment) {
+ if (offsets_for_adjustment) {
+ std::for_each(offsets_for_adjustment->begin(),
+ offsets_for_adjustment->end(),
+ base::LimitOffset<STR>(escaped_text.length()));
+ }
+ // Do not unescape anything, return the |escaped_text| text.
+ if (rules == UnescapeRule::NONE)
+ return escaped_text;
+
+ // The output of the unescaping is always smaller than the input, so we can
+ // reserve the input size to make sure we have enough buffer and don't have
+ // to allocate in the loop below.
+ STR result;
+ result.reserve(escaped_text.length());
+
+ // Locations of adjusted text.
+ net::internal::AdjustEncodingOffset::Adjustments adjustments;
+ for (size_t i = 0, max = escaped_text.size(); i < max; ++i) {
+ if (static_cast<unsigned char>(escaped_text[i]) >= 128) {
+ // Non ASCII character, append as is.
+ result.push_back(escaped_text[i]);
+ continue;
+ }
+
+ char current_char = static_cast<char>(escaped_text[i]);
+ if (current_char == '%' && i + 2 < max) {
+ const typename STR::value_type most_sig_digit(
+ static_cast<typename STR::value_type>(escaped_text[i + 1]));
+ const typename STR::value_type least_sig_digit(
+ static_cast<typename STR::value_type>(escaped_text[i + 2]));
+ if (IsHexDigit(most_sig_digit) && IsHexDigit(least_sig_digit)) {
+ unsigned char value = HexDigitToInt(most_sig_digit) * 16 +
+ HexDigitToInt(least_sig_digit);
+ if (value >= 0x80 || // Unescape all high-bit characters.
+ // For 7-bit characters, the lookup table tells us all valid chars.
+ (kUrlUnescape[value] ||
+ // ...and we allow some additional unescaping when flags are set.
+ (value == ' ' && (rules & UnescapeRule::SPACES)) ||
+ // Allow any of the prohibited but non-control characters when
+ // we're doing "special" chars.
+ (value > ' ' && (rules & UnescapeRule::URL_SPECIAL_CHARS)) ||
+ // Additionally allow control characters if requested.
+ (value < ' ' && (rules & UnescapeRule::CONTROL_CHARS)))) {
+ // Use the unescaped version of the character.
+ adjustments.push_back(i);
+ result.push_back(value);
+ i += 2;
+ } else {
+ // Keep escaped. Append a percent and we'll get the following two
+ // digits on the next loops through.
+ result.push_back('%');
+ }
+ } else {
+ // Invalid escape sequence, just pass the percent through and continue
+ // right after it.
+ result.push_back('%');
+ }
+ } else if ((rules & UnescapeRule::REPLACE_PLUS_WITH_SPACE) &&
+ escaped_text[i] == '+') {
+ result.push_back(' ');
+ } else {
+ // Normal case for unescaped characters.
+ result.push_back(escaped_text[i]);
+ }
+ }
+
+ // Make offset adjustment.
+ if (offsets_for_adjustment && !adjustments.empty()) {
+ std::for_each(offsets_for_adjustment->begin(),
+ offsets_for_adjustment->end(),
+ net::internal::AdjustEncodingOffset(adjustments));
+ }
+
+ return result;
+}
+
+template <class str>
+void AppendEscapedCharForHTMLImpl(typename str::value_type c, str* output) {
+ static const struct {
+ char key;
+ const char* replacement;
+ } kCharsToEscape[] = {
+ { '<', "&lt;" },
+ { '>', "&gt;" },
+ { '&', "&amp;" },
+ { '"', "&quot;" },
+ { '\'', "&#39;" },
+ };
+ size_t k;
+ for (k = 0; k < ARRAYSIZE_UNSAFE(kCharsToEscape); ++k) {
+ if (c == kCharsToEscape[k].key) {
+ const char* p = kCharsToEscape[k].replacement;
+ while (*p)
+ output->push_back(*p++);
+ break;
+ }
+ }
+ if (k == ARRAYSIZE_UNSAFE(kCharsToEscape))
+ output->push_back(c);
+}
+
+template <class str>
+str EscapeForHTMLImpl(const str& input) {
+ str result;
+ result.reserve(input.size()); // Optimize for no escaping.
+
+ for (typename str::const_iterator i = input.begin(); i != input.end(); ++i)
+ AppendEscapedCharForHTMLImpl(*i, &result);
+
+ return result;
+}
+
+// Everything except alphanumerics and !'()*-._~
+// See RFC 2396 for the list of reserved characters.
+static const Charmap kQueryCharmap = {{
+ 0xffffffffL, 0xfc00987dL, 0x78000001L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL
+}};
+
+// non-printable, non-7bit, and (including space) "#%:<>?[\]^`{|}
+static const Charmap kPathCharmap = {{
+ 0xffffffffL, 0xd400002dL, 0x78000000L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL
+}};
+
+// non-printable, non-7bit, and (including space) ?>=<;+'&%$#"![\]^`{|}
+static const Charmap kUrlEscape = {{
+ 0xffffffffL, 0xf80008fdL, 0x78000001L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL
+}};
+
+// non-7bit
+static const Charmap kNonASCIICharmap = {{
+ 0x00000000L, 0x00000000L, 0x00000000L, 0x00000000L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL
+}};
+
+// Everything except alphanumerics, the reserved characters(;/?:@&=+$,) and
+// !'()*-._~%
+static const Charmap kExternalHandlerCharmap = {{
+ 0xffffffffL, 0x5000080dL, 0x68000000L, 0xb8000001L,
+ 0xffffffffL, 0xffffffffL, 0xffffffffL, 0xffffffffL
+}};
+
+} // namespace
+
+std::string EscapeQueryParamValue(const std::string& text, bool use_plus) {
+ return Escape(text, kQueryCharmap, use_plus);
+}
+
+std::string EscapePath(const std::string& path) {
+ return Escape(path, kPathCharmap, false);
+}
+
+std::string EscapeUrlEncodedData(const std::string& path, bool use_plus) {
+ return Escape(path, kUrlEscape, use_plus);
+}
+
+std::string EscapeNonASCII(const std::string& input) {
+ return Escape(input, kNonASCIICharmap, false);
+}
+
+std::string EscapeExternalHandlerValue(const std::string& text) {
+ return Escape(text, kExternalHandlerCharmap, false);
+}
+
+void AppendEscapedCharForHTML(char c, std::string* output) {
+ AppendEscapedCharForHTMLImpl(c, output);
+}
+
+std::string EscapeForHTML(const std::string& input) {
+ return EscapeForHTMLImpl(input);
+}
+
+base::string16 EscapeForHTML(const base::string16& input) {
+ return EscapeForHTMLImpl(input);
+}
+
+std::string UnescapeURLComponent(const std::string& escaped_text,
+ UnescapeRule::Type rules) {
+ return UnescapeURLWithOffsetsImpl(escaped_text, rules, NULL);
+}
+
+base::string16 UnescapeURLComponent(const base::string16& escaped_text,
+ UnescapeRule::Type rules) {
+ return UnescapeURLWithOffsetsImpl(escaped_text, rules, NULL);
+}
+
+base::string16 UnescapeAndDecodeUTF8URLComponent(
+ const std::string& text,
+ UnescapeRule::Type rules,
+ size_t* offset_for_adjustment) {
+ std::vector<size_t> offsets;
+ if (offset_for_adjustment)
+ offsets.push_back(*offset_for_adjustment);
+ base::string16 result =
+ UnescapeAndDecodeUTF8URLComponentWithOffsets(text, rules, &offsets);
+ if (offset_for_adjustment)
+ *offset_for_adjustment = offsets[0];
+ return result;
+}
+
+base::string16 UnescapeAndDecodeUTF8URLComponentWithOffsets(
+ const std::string& text,
+ UnescapeRule::Type rules,
+ std::vector<size_t>* offsets_for_adjustment) {
+ base::string16 result;
+ std::vector<size_t> original_offsets;
+ if (offsets_for_adjustment)
+ original_offsets = *offsets_for_adjustment;
+ std::string unescaped_url(
+ UnescapeURLWithOffsetsImpl(text, rules, offsets_for_adjustment));
+ if (base::UTF8ToUTF16AndAdjustOffsets(unescaped_url.data(),
+ unescaped_url.length(),
+ &result, offsets_for_adjustment))
+ return result; // Character set looks like it's valid.
+
+ // Not valid. Return the escaped version. Undo our changes to
+ // |offset_for_adjustment| since we haven't changed the string after all.
+ if (offsets_for_adjustment)
+ *offsets_for_adjustment = original_offsets;
+ return base::UTF8ToUTF16AndAdjustOffsets(text, offsets_for_adjustment);
+}
+
+base::string16 UnescapeForHTML(const base::string16& input) {
+ static const struct {
+ const char* ampersand_code;
+ const char replacement;
+ } kEscapeToChars[] = {
+ { "&lt;", '<' },
+ { "&gt;", '>' },
+ { "&amp;", '&' },
+ { "&quot;", '"' },
+ { "&#39;", '\''},
+ };
+
+ if (input.find(ASCIIToUTF16("&")) == std::string::npos)
+ return input;
+
+ base::string16 ampersand_chars[ARRAYSIZE_UNSAFE(kEscapeToChars)];
+ base::string16 text(input);
+ for (base::string16::iterator iter = text.begin();
+ iter != text.end(); ++iter) {
+ if (*iter == '&') {
+ // Potential ampersand encode char.
+ size_t index = iter - text.begin();
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kEscapeToChars); i++) {
+ if (ampersand_chars[i].empty())
+ ampersand_chars[i] = ASCIIToUTF16(kEscapeToChars[i].ampersand_code);
+ if (text.find(ampersand_chars[i], index) == index) {
+ text.replace(iter, iter + ampersand_chars[i].length(),
+ 1, kEscapeToChars[i].replacement);
+ break;
+ }
+ }
+ }
+ }
+ return text;
+}
+
+namespace internal {
+
+AdjustEncodingOffset::AdjustEncodingOffset(const Adjustments& adjustments)
+ : adjustments(adjustments) {}
+
+void AdjustEncodingOffset::operator()(size_t& offset) {
+ // For each encoded character occurring before an offset subtract 2.
+ if (offset == base::string16::npos)
+ return;
+ size_t adjusted_offset = offset;
+ for (Adjustments::const_iterator i = adjustments.begin();
+ i != adjustments.end(); ++i) {
+ size_t location = *i;
+ if (offset <= location) {
+ offset = adjusted_offset;
+ return;
+ }
+ if (offset <= (location + 2)) {
+ offset = base::string16::npos;
+ return;
+ }
+ adjusted_offset -= 2;
+ }
+ offset = adjusted_offset;
+}
+
+} // namespace internal
+
+} // namespace net
diff --git a/chromium/net/base/escape.h b/chromium/net/base/escape.h
new file mode 100644
index 00000000000..69eb2a5c656
--- /dev/null
+++ b/chromium/net/base/escape.h
@@ -0,0 +1,155 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_ESCAPE_H_
+#define NET_BASE_ESCAPE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Escaping --------------------------------------------------------------------
+
+// Escapes characters in text suitable for use as a query parameter value.
+// We %XX everything except alphanumerics and -_.!~*'()
+// Spaces change to "+" unless you pass usePlus=false.
+// This is basically the same as encodeURIComponent in javascript.
+NET_EXPORT std::string EscapeQueryParamValue(const std::string& text,
+ bool use_plus);
+
+// Escapes a partial or complete file/pathname. This includes:
+// non-printable, non-7bit, and (including space) "#%:<>?[\]^`{|}
+// For the base::string16 version, we attempt a conversion to |codepage| before
+// encoding the string. If this conversion fails, we return false.
+NET_EXPORT std::string EscapePath(const std::string& path);
+
+// Escapes application/x-www-form-urlencoded content. This includes:
+// non-printable, non-7bit, and (including space) ?>=<;+'&%$#"![\]^`{|}
+// Space is escaped as + (if use_plus is true) and other special characters
+// as %XX (hex).
+NET_EXPORT std::string EscapeUrlEncodedData(const std::string& path,
+ bool use_plus);
+
+// Escapes all non-ASCII input.
+NET_EXPORT std::string EscapeNonASCII(const std::string& input);
+
+// Escapes characters in text suitable for use as an external protocol handler
+// command.
+// We %XX everything except alphanumerics and %-_.!~*'() and the restricted
+// chracters (;/?:@&=+$,).
+NET_EXPORT std::string EscapeExternalHandlerValue(const std::string& text);
+
+// Appends the given character to the output string, escaping the character if
+// the character would be interpretted as an HTML delimiter.
+NET_EXPORT void AppendEscapedCharForHTML(char c, std::string* output);
+
+// Escapes chars that might cause this text to be interpretted as HTML tags.
+NET_EXPORT std::string EscapeForHTML(const std::string& text);
+NET_EXPORT base::string16 EscapeForHTML(const base::string16& text);
+
+// Unescaping ------------------------------------------------------------------
+
+class UnescapeRule {
+ public:
+ // A combination of the following flags that is passed to the unescaping
+ // functions.
+ typedef uint32 Type;
+
+ enum {
+ // Don't unescape anything at all.
+ NONE = 0,
+
+ // Don't unescape anything special, but all normal unescaping will happen.
+ // This is a placeholder and can't be combined with other flags (since it's
+ // just the absence of them). All other unescape rules imply "normal" in
+ // addition to their special meaning. Things like escaped letters, digits,
+ // and most symbols will get unescaped with this mode.
+ NORMAL = 1,
+
+ // Convert %20 to spaces. In some places where we're showing URLs, we may
+ // want this. In places where the URL may be copied and pasted out, then
+ // you wouldn't want this since it might not be interpreted in one piece
+ // by other applications.
+ SPACES = 2,
+
+ // Unescapes various characters that will change the meaning of URLs,
+ // including '%', '+', '&', '/', '#'. If we unescaped these characters, the
+ // resulting URL won't be the same as the source one. This flag is used when
+ // generating final output like filenames for URLs where we won't be
+ // interpreting as a URL and want to do as much unescaping as possible.
+ URL_SPECIAL_CHARS = 4,
+
+ // Unescapes control characters such as %01. This INCLUDES NULLs. This is
+ // used for rare cases such as data: URL decoding where the result is binary
+ // data. You should not use this for normal URLs!
+ CONTROL_CHARS = 8,
+
+ // URL queries use "+" for space. This flag controls that replacement.
+ REPLACE_PLUS_WITH_SPACE = 16,
+ };
+};
+
+// Unescapes |escaped_text| and returns the result.
+// Unescaping consists of looking for the exact pattern "%XX", where each X is
+// a hex digit, and converting to the character with the numerical value of
+// those digits. Thus "i%20=%203%3b" unescapes to "i = 3;".
+//
+// Watch out: this doesn't necessarily result in the correct final result,
+// because the encoding may be unknown. For example, the input might be ASCII,
+// which, after unescaping, is supposed to be interpreted as UTF-8, and then
+// converted into full UTF-16 chars. This function won't tell you if any
+// conversions need to take place, it only unescapes.
+NET_EXPORT std::string UnescapeURLComponent(const std::string& escaped_text,
+ UnescapeRule::Type rules);
+NET_EXPORT base::string16 UnescapeURLComponent(
+ const base::string16& escaped_text,
+ UnescapeRule::Type rules);
+
+// Unescapes the given substring as a URL, and then tries to interpret the
+// result as being encoded as UTF-8. If the result is convertable into UTF-8, it
+// will be returned as converted. If it is not, the original escaped string will
+// be converted into a base::string16 and returned. (|offset[s]_for_adjustment|)
+// specifies one or more offsets into the source strings; each offset will be
+// adjusted to point at the same logical place in the result strings during
+// decoding. If this isn't possible because an offset points past the end of
+// the source strings or into the middle of a multibyte sequence, the offending
+// offset will be set to string16::npos. |offset[s]_for_adjustment| may be NULL.
+NET_EXPORT base::string16 UnescapeAndDecodeUTF8URLComponent(
+ const std::string& text,
+ UnescapeRule::Type rules,
+ size_t* offset_for_adjustment);
+NET_EXPORT base::string16 UnescapeAndDecodeUTF8URLComponentWithOffsets(
+ const std::string& text,
+ UnescapeRule::Type rules,
+ std::vector<size_t>* offsets_for_adjustment);
+
+// Unescapes the following ampersand character codes from |text|:
+// &lt; &gt; &amp; &quot; &#39;
+NET_EXPORT base::string16 UnescapeForHTML(const base::string16& text);
+
+namespace internal {
+
+// Private Functions (Exposed for Unit Testing) --------------------------------
+
+// A function called by std::for_each that will adjust any offset which occurs
+// after one or more encoded characters.
+struct NET_EXPORT_PRIVATE AdjustEncodingOffset {
+ typedef std::vector<size_t> Adjustments;
+
+ explicit AdjustEncodingOffset(const Adjustments& adjustments);
+ void operator()(size_t& offset);
+
+ const Adjustments& adjustments;
+};
+
+} // namespace internal
+
+} // namespace net
+
+#endif // NET_BASE_ESCAPE_H_
diff --git a/chromium/net/base/escape_unittest.cc b/chromium/net/base/escape_unittest.cc
new file mode 100644
index 00000000000..bed49a5e1d5
--- /dev/null
+++ b/chromium/net/base/escape_unittest.cc
@@ -0,0 +1,430 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <string>
+
+#include "net/base/escape.h"
+
+#include "base/basictypes.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+const size_t kNpos = base::string16::npos;
+
+struct EscapeCase {
+ const char* input;
+ const char* output;
+};
+
+struct UnescapeURLCase {
+ const wchar_t* input;
+ UnescapeRule::Type rules;
+ const wchar_t* output;
+};
+
+struct UnescapeURLCaseASCII {
+ const char* input;
+ UnescapeRule::Type rules;
+ const char* output;
+};
+
+struct UnescapeAndDecodeCase {
+ const char* input;
+
+ // The expected output when run through UnescapeURL.
+ const char* url_unescaped;
+
+ // The expected output when run through UnescapeQuery.
+ const char* query_unescaped;
+
+ // The expected output when run through UnescapeAndDecodeURLComponent.
+ const wchar_t* decoded;
+};
+
+struct AdjustOffsetCase {
+ const char* input;
+ size_t input_offset;
+ size_t output_offset;
+};
+
+struct EscapeForHTMLCase {
+ const char* input;
+ const char* expected_output;
+};
+
+TEST(EscapeTest, EscapeTextForFormSubmission) {
+ const EscapeCase escape_cases[] = {
+ {"foo", "foo"},
+ {"foo bar", "foo+bar"},
+ {"foo++", "foo%2B%2B"}
+ };
+ for (size_t i = 0; i < arraysize(escape_cases); ++i) {
+ EscapeCase value = escape_cases[i];
+ EXPECT_EQ(value.output, EscapeQueryParamValue(value.input, true));
+ }
+
+ const EscapeCase escape_cases_no_plus[] = {
+ {"foo", "foo"},
+ {"foo bar", "foo%20bar"},
+ {"foo++", "foo%2B%2B"}
+ };
+ for (size_t i = 0; i < arraysize(escape_cases_no_plus); ++i) {
+ EscapeCase value = escape_cases_no_plus[i];
+ EXPECT_EQ(value.output, EscapeQueryParamValue(value.input, false));
+ }
+
+ // Test all the values in we're supposed to be escaping.
+ const std::string no_escape(
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "!'()*-._~");
+ for (int i = 0; i < 256; ++i) {
+ std::string in;
+ in.push_back(i);
+ std::string out = EscapeQueryParamValue(in, true);
+ if (0 == i) {
+ EXPECT_EQ(out, std::string("%00"));
+ } else if (32 == i) {
+ // Spaces are plus escaped like web forms.
+ EXPECT_EQ(out, std::string("+"));
+ } else if (no_escape.find(in) == std::string::npos) {
+ // Check %hex escaping
+ std::string expected = base::StringPrintf("%%%02X", i);
+ EXPECT_EQ(expected, out);
+ } else {
+ // No change for things in the no_escape list.
+ EXPECT_EQ(out, in);
+ }
+ }
+}
+
+TEST(EscapeTest, EscapePath) {
+ ASSERT_EQ(
+ // Most of the character space we care about, un-escaped
+ EscapePath(
+ "\x02\n\x1d !\"#$%&'()*+,-./0123456789:;"
+ "<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\]^_`abcdefghijklmnopqrstuvwxyz"
+ "{|}~\x7f\x80\xff"),
+ // Escaped
+ "%02%0A%1D%20!%22%23$%25&'()*+,-./0123456789%3A;"
+ "%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz"
+ "%7B%7C%7D~%7F%80%FF");
+}
+
+TEST(EscapeTest, EscapeUrlEncodedData) {
+ ASSERT_EQ(
+ // Most of the character space we care about, un-escaped
+ EscapeUrlEncodedData(
+ "\x02\n\x1d !\"#$%&'()*+,-./0123456789:;"
+ "<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "[\\]^_`abcdefghijklmnopqrstuvwxyz"
+ "{|}~\x7f\x80\xff", true),
+ // Escaped
+ "%02%0A%1D+!%22%23%24%25%26%27()*%2B,-./0123456789:%3B"
+ "%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz"
+ "%7B%7C%7D~%7F%80%FF");
+}
+
+TEST(EscapeTest, EscapeUrlEncodedDataSpace) {
+ ASSERT_EQ(EscapeUrlEncodedData("a b", true), "a+b");
+ ASSERT_EQ(EscapeUrlEncodedData("a b", false), "a%20b");
+}
+
+TEST(EscapeTest, UnescapeURLComponentASCII) {
+ const UnescapeURLCaseASCII unescape_cases[] = {
+ {"", UnescapeRule::NORMAL, ""},
+ {"%2", UnescapeRule::NORMAL, "%2"},
+ {"%%%%%%", UnescapeRule::NORMAL, "%%%%%%"},
+ {"Don't escape anything", UnescapeRule::NORMAL, "Don't escape anything"},
+ {"Invalid %escape %2", UnescapeRule::NORMAL, "Invalid %escape %2"},
+ {"Some%20random text %25%2dOK", UnescapeRule::NONE,
+ "Some%20random text %25%2dOK"},
+ {"Some%20random text %25%2dOK", UnescapeRule::NORMAL,
+ "Some%20random text %25-OK"},
+ {"Some%20random text %25%2dOK", UnescapeRule::SPACES,
+ "Some random text %25-OK"},
+ {"Some%20random text %25%2dOK", UnescapeRule::URL_SPECIAL_CHARS,
+ "Some%20random text %-OK"},
+ {"Some%20random text %25%2dOK",
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS,
+ "Some random text %-OK"},
+ {"%A0%B1%C2%D3%E4%F5", UnescapeRule::NORMAL, "\xA0\xB1\xC2\xD3\xE4\xF5"},
+ {"%Aa%Bb%Cc%Dd%Ee%Ff", UnescapeRule::NORMAL, "\xAa\xBb\xCc\xDd\xEe\xFf"},
+ // Certain URL-sensitive characters should not be unescaped unless asked.
+ {"Hello%20%13%10world %23# %3F? %3D= %26& %25% %2B+", UnescapeRule::SPACES,
+ "Hello %13%10world %23# %3F? %3D= %26& %25% %2B+"},
+ {"Hello%20%13%10world %23# %3F? %3D= %26& %25% %2B+",
+ UnescapeRule::URL_SPECIAL_CHARS,
+ "Hello%20%13%10world ## ?? == && %% ++"},
+ // We can neither escape nor unescape '@' since some websites expect it to
+ // be preserved as either '@' or "%40".
+ // See http://b/996720 and http://crbug.com/23933 .
+ {"me@my%40example", UnescapeRule::NORMAL, "me@my%40example"},
+ // Control characters.
+ {"%01%02%03%04%05%06%07%08%09 %25", UnescapeRule::URL_SPECIAL_CHARS,
+ "%01%02%03%04%05%06%07%08%09 %"},
+ {"%01%02%03%04%05%06%07%08%09 %25", UnescapeRule::CONTROL_CHARS,
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09 %25"},
+ {"Hello%20%13%10%02", UnescapeRule::SPACES, "Hello %13%10%02"},
+ {"Hello%20%13%10%02", UnescapeRule::CONTROL_CHARS, "Hello%20\x13\x10\x02"},
+ };
+
+ for (size_t i = 0; i < arraysize(unescape_cases); i++) {
+ std::string str(unescape_cases[i].input);
+ EXPECT_EQ(std::string(unescape_cases[i].output),
+ UnescapeURLComponent(str, unescape_cases[i].rules));
+ }
+
+ // Test the NULL character unescaping (which wouldn't work above since those
+ // are just char pointers).
+ std::string input("Null");
+ input.push_back(0); // Also have a NULL in the input.
+ input.append("%00%39Test");
+
+ // When we're unescaping NULLs
+ std::string expected("Null");
+ expected.push_back(0);
+ expected.push_back(0);
+ expected.append("9Test");
+ EXPECT_EQ(expected, UnescapeURLComponent(input, UnescapeRule::CONTROL_CHARS));
+
+ // When we're not unescaping NULLs.
+ expected = "Null";
+ expected.push_back(0);
+ expected.append("%009Test");
+ EXPECT_EQ(expected, UnescapeURLComponent(input, UnescapeRule::NORMAL));
+}
+
+TEST(EscapeTest, UnescapeURLComponent) {
+ const UnescapeURLCase unescape_cases[] = {
+ {L"", UnescapeRule::NORMAL, L""},
+ {L"%2", UnescapeRule::NORMAL, L"%2"},
+ {L"%%%%%%", UnescapeRule::NORMAL, L"%%%%%%"},
+ {L"Don't escape anything", UnescapeRule::NORMAL, L"Don't escape anything"},
+ {L"Invalid %escape %2", UnescapeRule::NORMAL, L"Invalid %escape %2"},
+ {L"Some%20random text %25%2dOK", UnescapeRule::NONE,
+ L"Some%20random text %25%2dOK"},
+ {L"Some%20random text %25%2dOK", UnescapeRule::NORMAL,
+ L"Some%20random text %25-OK"},
+ {L"Some%20random text %25%2dOK", UnescapeRule::SPACES,
+ L"Some random text %25-OK"},
+ {L"Some%20random text %25%2dOK", UnescapeRule::URL_SPECIAL_CHARS,
+ L"Some%20random text %-OK"},
+ {L"Some%20random text %25%2dOK",
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS,
+ L"Some random text %-OK"},
+ {L"%A0%B1%C2%D3%E4%F5", UnescapeRule::NORMAL, L"\xA0\xB1\xC2\xD3\xE4\xF5"},
+ {L"%Aa%Bb%Cc%Dd%Ee%Ff", UnescapeRule::NORMAL, L"\xAa\xBb\xCc\xDd\xEe\xFf"},
+ // Certain URL-sensitive characters should not be unescaped unless asked.
+ {L"Hello%20%13%10world %23# %3F? %3D= %26& %25% %2B+", UnescapeRule::SPACES,
+ L"Hello %13%10world %23# %3F? %3D= %26& %25% %2B+"},
+ {L"Hello%20%13%10world %23# %3F? %3D= %26& %25% %2B+",
+ UnescapeRule::URL_SPECIAL_CHARS,
+ L"Hello%20%13%10world ## ?? == && %% ++"},
+ // We can neither escape nor unescape '@' since some websites expect it to
+ // be preserved as either '@' or "%40".
+ // See http://b/996720 and http://crbug.com/23933 .
+ {L"me@my%40example", UnescapeRule::NORMAL, L"me@my%40example"},
+ // Control characters.
+ {L"%01%02%03%04%05%06%07%08%09 %25", UnescapeRule::URL_SPECIAL_CHARS,
+ L"%01%02%03%04%05%06%07%08%09 %"},
+ {L"%01%02%03%04%05%06%07%08%09 %25", UnescapeRule::CONTROL_CHARS,
+ L"\x01\x02\x03\x04\x05\x06\x07\x08\x09 %25"},
+ {L"Hello%20%13%10%02", UnescapeRule::SPACES, L"Hello %13%10%02"},
+ {L"Hello%20%13%10%02", UnescapeRule::CONTROL_CHARS,
+ L"Hello%20\x13\x10\x02"},
+ {L"Hello\x9824\x9827", UnescapeRule::CONTROL_CHARS,
+ L"Hello\x9824\x9827"},
+ };
+
+ for (size_t i = 0; i < arraysize(unescape_cases); i++) {
+ base::string16 str(WideToUTF16(unescape_cases[i].input));
+ EXPECT_EQ(WideToUTF16(unescape_cases[i].output),
+ UnescapeURLComponent(str, unescape_cases[i].rules));
+ }
+
+ // Test the NULL character unescaping (which wouldn't work above since those
+ // are just char pointers).
+ base::string16 input(WideToUTF16(L"Null"));
+ input.push_back(0); // Also have a NULL in the input.
+ input.append(WideToUTF16(L"%00%39Test"));
+
+ // When we're unescaping NULLs
+ base::string16 expected(WideToUTF16(L"Null"));
+ expected.push_back(0);
+ expected.push_back(0);
+ expected.append(ASCIIToUTF16("9Test"));
+ EXPECT_EQ(expected, UnescapeURLComponent(input, UnescapeRule::CONTROL_CHARS));
+
+ // When we're not unescaping NULLs.
+ expected = WideToUTF16(L"Null");
+ expected.push_back(0);
+ expected.append(WideToUTF16(L"%009Test"));
+ EXPECT_EQ(expected, UnescapeURLComponent(input, UnescapeRule::NORMAL));
+}
+
+TEST(EscapeTest, UnescapeAndDecodeUTF8URLComponent) {
+ const UnescapeAndDecodeCase unescape_cases[] = {
+ { "%",
+ "%",
+ "%",
+ L"%"},
+ { "+",
+ "+",
+ " ",
+ L"+"},
+ { "%2+",
+ "%2+",
+ "%2 ",
+ L"%2+"},
+ { "+%%%+%%%",
+ "+%%%+%%%",
+ " %%% %%%",
+ L"+%%%+%%%"},
+ { "Don't escape anything",
+ "Don't escape anything",
+ "Don't escape anything",
+ L"Don't escape anything"},
+ { "+Invalid %escape %2+",
+ "+Invalid %escape %2+",
+ " Invalid %escape %2 ",
+ L"+Invalid %escape %2+"},
+ { "Some random text %25%2dOK",
+ "Some random text %25-OK",
+ "Some random text %25-OK",
+ L"Some random text %25-OK"},
+ { "%01%02%03%04%05%06%07%08%09",
+ "%01%02%03%04%05%06%07%08%09",
+ "%01%02%03%04%05%06%07%08%09",
+ L"%01%02%03%04%05%06%07%08%09"},
+ { "%E4%BD%A0+%E5%A5%BD",
+ "\xE4\xBD\xA0+\xE5\xA5\xBD",
+ "\xE4\xBD\xA0 \xE5\xA5\xBD",
+ L"\x4f60+\x597d"},
+ { "%ED%ED", // Invalid UTF-8.
+ "\xED\xED",
+ "\xED\xED",
+ L"%ED%ED"}, // Invalid UTF-8 -> kept unescaped.
+ };
+
+ for (size_t i = 0; i < arraysize(unescape_cases); i++) {
+ std::string unescaped = UnescapeURLComponent(unescape_cases[i].input,
+ UnescapeRule::NORMAL);
+ EXPECT_EQ(std::string(unescape_cases[i].url_unescaped), unescaped);
+
+ unescaped = UnescapeURLComponent(unescape_cases[i].input,
+ UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+ EXPECT_EQ(std::string(unescape_cases[i].query_unescaped), unescaped);
+
+ // TODO: Need to test unescape_spaces and unescape_percent.
+ base::string16 decoded = UnescapeAndDecodeUTF8URLComponent(
+ unescape_cases[i].input, UnescapeRule::NORMAL, NULL);
+ EXPECT_EQ(WideToUTF16(unescape_cases[i].decoded), decoded);
+ }
+}
+
+TEST(EscapeTest, AdjustOffset) {
+ const AdjustOffsetCase adjust_cases[] = {
+ {"", 0, std::string::npos},
+ {"test", 0, 0},
+ {"test", 2, 2},
+ {"test", 4, std::string::npos},
+ {"test", std::string::npos, std::string::npos},
+ {"%2dtest", 6, 4},
+ {"%2dtest", 2, std::string::npos},
+ {"test%2d", 2, 2},
+ {"%E4%BD%A0+%E5%A5%BD", 9, 1},
+ {"%E4%BD%A0+%E5%A5%BD", 6, std::string::npos},
+ {"%ED%B0%80+%E5%A5%BD", 6, 6},
+ };
+
+ for (size_t i = 0; i < arraysize(adjust_cases); i++) {
+ size_t offset = adjust_cases[i].input_offset;
+ UnescapeAndDecodeUTF8URLComponent(adjust_cases[i].input,
+ UnescapeRule::NORMAL, &offset);
+ EXPECT_EQ(adjust_cases[i].output_offset, offset);
+ }
+}
+
+TEST(EscapeTest, EscapeForHTML) {
+ const EscapeForHTMLCase tests[] = {
+ { "hello", "hello" },
+ { "<hello>", "&lt;hello&gt;" },
+ { "don\'t mess with me", "don&#39;t mess with me" },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::string result = EscapeForHTML(std::string(tests[i].input));
+ EXPECT_EQ(std::string(tests[i].expected_output), result);
+ }
+}
+
+TEST(EscapeTest, UnescapeForHTML) {
+ const EscapeForHTMLCase tests[] = {
+ { "", "" },
+ { "&lt;hello&gt;", "<hello>" },
+ { "don&#39;t mess with me", "don\'t mess with me" },
+ { "&lt;&gt;&amp;&quot;&#39;", "<>&\"'" },
+ { "& lt; &amp ; &; '", "& lt; &amp ; &; '" },
+ { "&amp;", "&" },
+ { "&quot;", "\"" },
+ { "&#39;", "'" },
+ { "&lt;", "<" },
+ { "&gt;", ">" },
+ { "&amp; &", "& &" },
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ base::string16 result = UnescapeForHTML(ASCIIToUTF16(tests[i].input));
+ EXPECT_EQ(ASCIIToUTF16(tests[i].expected_output), result);
+ }
+}
+
+TEST(EscapeTest, AdjustEncodingOffset) {
+ // Imagine we have strings as shown in the following cases where the
+ // %XX's represent encoded characters
+
+ // 1: abc%ECdef ==> abcXdef
+ std::vector<size_t> offsets;
+ for (size_t t = 0; t < 9; ++t)
+ offsets.push_back(t);
+ internal::AdjustEncodingOffset::Adjustments adjustments;
+ adjustments.push_back(3);
+ std::for_each(offsets.begin(), offsets.end(),
+ internal::AdjustEncodingOffset(adjustments));
+ size_t expected_1[] = {0, 1, 2, 3, kNpos, kNpos, 4, 5, 6};
+ EXPECT_EQ(offsets.size(), arraysize(expected_1));
+ for (size_t i = 0; i < arraysize(expected_1); ++i)
+ EXPECT_EQ(expected_1[i], offsets[i]);
+
+
+ // 2: %ECabc%EC%ECdef%EC ==> XabcXXdefX
+ offsets.clear();
+ for (size_t t = 0; t < 18; ++t)
+ offsets.push_back(t);
+ adjustments.clear();
+ adjustments.push_back(0);
+ adjustments.push_back(6);
+ adjustments.push_back(9);
+ adjustments.push_back(15);
+ std::for_each(offsets.begin(), offsets.end(),
+ internal::AdjustEncodingOffset(adjustments));
+ size_t expected_2[] = {0, kNpos, kNpos, 1, 2, 3, 4, kNpos, kNpos, 5, kNpos,
+ kNpos, 6, 7, 8, 9, kNpos, kNpos};
+ EXPECT_EQ(offsets.size(), arraysize(expected_2));
+ for (size_t i = 0; i < arraysize(expected_2); ++i)
+ EXPECT_EQ(expected_2[i], offsets[i]);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/base/expiring_cache.h b/chromium/net/base/expiring_cache.h
new file mode 100644
index 00000000000..3fbde6594ff
--- /dev/null
+++ b/chromium/net/base/expiring_cache.h
@@ -0,0 +1,218 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_EXPIRING_CACHE_H_
+#define NET_BASE_EXPIRING_CACHE_H_
+
+#include <map>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+
+namespace net {
+
+template <typename KeyType,
+ typename ValueType,
+ typename ExpirationType>
+class NoopEvictionHandler {
+ public:
+ void Handle(const KeyType& key,
+ const ValueType& value,
+ const ExpirationType& expiration,
+ const ExpirationType& now,
+ bool onGet) const {
+ }
+};
+
+// Cache implementation where all entries have an explicit expiration policy. As
+// new items are added, expired items will be removed first.
+// The template types have the following requirements:
+// KeyType must be LessThanComparable, Assignable, and CopyConstructible.
+// ValueType must be CopyConstructible and Assignable.
+// ExpirationType must be CopyConstructible and Assignable.
+// ExpirationCompare is a function class that takes two arguments of the
+// type ExpirationType and returns a bool. If |comp| is an instance of
+// ExpirationCompare, then the expression |comp(current, expiration)| shall
+// return true iff |current| is still valid within |expiration|.
+//
+// A simple use of this class may use base::TimeTicks, which provides a
+// monotonically increasing clock, for the expiration type. Because it's always
+// increasing, std::less<> can be used, which will simply ensure that |now| is
+// sorted before |expiration|:
+//
+// ExpiringCache<std::string, std::string, base::TimeTicks,
+// std::less<base::TimeTicks> > cache(0);
+// // Add a value that expires in 5 minutes
+// cache.Put("key1", "value1", base::TimeTicks::Now(),
+// base::TimeTicks::Now() + base::TimeDelta::FromMinutes(5));
+// // Add another value that expires in 10 minutes.
+// cache.Put("key2", "value2", base::TimeTicks::Now(),
+// base::TimeTicks::Now() + base::TimeDelta::FromMinutes(10));
+//
+// Alternatively, there may be some more complex expiration criteria, at which
+// point a custom functor may be used:
+//
+// struct ComplexExpirationFunctor {
+// bool operator()(const ComplexExpiration& now,
+// const ComplexExpiration& expiration) const;
+// };
+// ExpiringCache<std::string, std::string, ComplexExpiration,
+// ComplexExpirationFunctor> cache(15);
+// // Add a value that expires once the 'sprocket' has 'cog'-ified.
+// cache.Put("key1", "value1", ComplexExpiration("sprocket"),
+// ComplexExpiration("cog"));
+template <typename KeyType,
+ typename ValueType,
+ typename ExpirationType,
+ typename ExpirationCompare,
+ typename EvictionHandler = NoopEvictionHandler<KeyType,
+ ValueType,
+ ExpirationType> >
+class ExpiringCache {
+ private:
+ // Intentionally violate the C++ Style Guide so that EntryMap is known to be
+ // a dependent type. Without this, Clang's two-phase lookup complains when
+ // using EntryMap::const_iterator, while GCC and MSVC happily resolve the
+ // typename.
+
+ // Tuple to represent the value and when it expires.
+ typedef std::pair<ValueType, ExpirationType> Entry;
+ typedef std::map<KeyType, Entry> EntryMap;
+
+ public:
+ typedef KeyType key_type;
+ typedef ValueType value_type;
+ typedef ExpirationType expiration_type;
+
+ // This class provides a read-only iterator over items in the ExpiringCache
+ class Iterator {
+ public:
+ explicit Iterator(const ExpiringCache& cache)
+ : cache_(cache),
+ it_(cache_.entries_.begin()) {
+ }
+ ~Iterator() {}
+
+ bool HasNext() const { return it_ != cache_.entries_.end(); }
+ void Advance() { ++it_; }
+
+ const KeyType& key() const { return it_->first; }
+ const ValueType& value() const { return it_->second.first; }
+ const ExpirationType& expiration() const { return it_->second.second; }
+
+ private:
+ const ExpiringCache& cache_;
+
+ // Use a second layer of type indirection, as both EntryMap and
+ // EntryMap::const_iterator are dependent types.
+ typedef typename ExpiringCache::EntryMap EntryMap;
+ typename EntryMap::const_iterator it_;
+ };
+
+
+ // Constructs an ExpiringCache that stores up to |max_entries|.
+ explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {}
+ ~ExpiringCache() {}
+
+ // Returns the value matching |key|, which must be valid at the time |now|.
+ // Returns NULL if the item is not found or has expired. If the item has
+ // expired, it is immediately removed from the cache.
+ // Note: The returned pointer remains owned by the ExpiringCache and is
+ // invalidated by a call to a non-const method.
+ const ValueType* Get(const KeyType& key, const ExpirationType& now) {
+ typename EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end())
+ return NULL;
+
+ // Immediately remove expired entries.
+ if (!expiration_comp_(now, it->second.second)) {
+ Evict(it, now, true);
+ return NULL;
+ }
+
+ return &it->second.first;
+ }
+
+ // Updates or replaces the value associated with |key|.
+ void Put(const KeyType& key,
+ const ValueType& value,
+ const ExpirationType& now,
+ const ExpirationType& expiration) {
+ typename EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end()) {
+ // Compact the cache if it grew beyond the limit.
+ if (entries_.size() == max_entries_ )
+ Compact(now);
+
+ // No existing entry. Creating a new one.
+ entries_.insert(std::make_pair(key, Entry(value, expiration)));
+ } else {
+ // Update an existing cache entry.
+ it->second.first = value;
+ it->second.second = expiration;
+ }
+ }
+
+ // Empties the cache.
+ void Clear() {
+ entries_.clear();
+ }
+
+ // Returns the number of entries in the cache.
+ size_t size() const { return entries_.size(); }
+
+ // Returns the maximum number of entries in the cache.
+ size_t max_entries() const { return max_entries_; }
+
+ bool empty() const { return entries_.empty(); }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact);
+ FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor);
+
+ // Prunes entries from the cache to bring it below |max_entries()|.
+ void Compact(const ExpirationType& now) {
+ // Clear out expired entries.
+ typename EntryMap::iterator it;
+ for (it = entries_.begin(); it != entries_.end(); ) {
+ if (!expiration_comp_(now, it->second.second)) {
+ Evict(it++, now, false);
+ } else {
+ ++it;
+ }
+ }
+
+ if (entries_.size() < max_entries_)
+ return;
+
+ // If the cache is still too full, start deleting items 'randomly'.
+ for (it = entries_.begin();
+ it != entries_.end() && entries_.size() >= max_entries_;) {
+ Evict(it++, now, false);
+ }
+ }
+
+ void Evict(typename EntryMap::iterator it,
+ const ExpirationType& now,
+ bool on_get) {
+ eviction_handler_.Handle(it->first, it->second.first, it->second.second,
+ now, on_get);
+ entries_.erase(it);
+ }
+
+ // Bound on total size of the cache.
+ size_t max_entries_;
+
+ EntryMap entries_;
+ ExpirationCompare expiration_comp_;
+ EvictionHandler eviction_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExpiringCache);
+};
+
+} // namespace net
+
+#endif // NET_BASE_EXPIRING_CACHE_H_
diff --git a/chromium/net/base/expiring_cache_unittest.cc b/chromium/net/base/expiring_cache_unittest.cc
new file mode 100644
index 00000000000..74b069dd8d8
--- /dev/null
+++ b/chromium/net/base/expiring_cache_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright (c) 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.
+
+#include "net/base/expiring_cache.h"
+
+#include <functional>
+#include <string>
+
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Pointee;
+using testing::StrEq;
+
+namespace net {
+
+namespace {
+
+const int kMaxCacheEntries = 10;
+typedef ExpiringCache<std::string, std::string, base::TimeTicks,
+ std::less<base::TimeTicks> > Cache;
+
+struct TestFunctor {
+ bool operator()(const std::string& now,
+ const std::string& expiration) const {
+ return now != expiration;
+ }
+};
+
+} // namespace
+
+TEST(ExpiringCacheTest, Basic) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ Cache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry at t=0
+ EXPECT_FALSE(cache.Get("entry1", now));
+ cache.Put("entry1", "test1", now, now + kTTL);
+ EXPECT_THAT(cache.Get("entry1", now), Pointee(StrEq("test1")));
+ EXPECT_EQ(1U, cache.size());
+
+ // Advance to t=5.
+ now += base::TimeDelta::FromSeconds(5);
+
+ // Add an entry at t=5.
+ EXPECT_FALSE(cache.Get("entry2", now));
+ cache.Put("entry2", "test2", now, now + kTTL);
+ EXPECT_THAT(cache.Get("entry2", now), Pointee(StrEq("test2")));
+ EXPECT_EQ(2U, cache.size());
+
+ // Advance to t=9.
+ now += base::TimeDelta::FromSeconds(4);
+
+ // Verify that the entries added are still retrievable and usable.
+ EXPECT_THAT(cache.Get("entry1", now), Pointee(StrEq("test1")));
+ EXPECT_THAT(cache.Get("entry2", now), Pointee(StrEq("test2")));
+
+ // Advance to t=10; entry1 is now expired.
+ now += base::TimeDelta::FromSeconds(1);
+
+ EXPECT_FALSE(cache.Get("entry1", now));
+ EXPECT_THAT(cache.Get("entry2", now), Pointee(StrEq("test2")));
+
+ // The expired element should no longer be in the cache.
+ EXPECT_EQ(1U, cache.size());
+
+ // Update entry1 so it is no longer expired.
+ cache.Put("entry1", "test1", now, now + kTTL);
+
+ // Both entries should be retrievable and usable.
+ EXPECT_EQ(2U, cache.size());
+ EXPECT_THAT(cache.Get("entry1", now), Pointee(StrEq("test1")));
+ EXPECT_THAT(cache.Get("entry2", now), Pointee(StrEq("test2")));
+
+ // Advance to t=20; both entries are now expired.
+ now += base::TimeDelta::FromSeconds(10);
+
+ EXPECT_FALSE(cache.Get("entry1", now));
+ EXPECT_FALSE(cache.Get("entry2", now));
+}
+
+TEST(ExpiringCacheTest, Compact) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ Cache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+ EXPECT_EQ(0U, cache.size());
+
+ // Add five valid entries at t=10 that expire at t=20.
+ base::TimeTicks t10 = now + kTTL;
+ for (int i = 0; i < 5; ++i) {
+ std::string name = base::StringPrintf("valid%d", i);
+ cache.Put(name, "I'm valid!", t10, t10 + kTTL);
+ }
+ EXPECT_EQ(5U, cache.size());
+
+ // Add three entries at t=0 that expire at t=10.
+ for (int i = 0; i < 3; ++i) {
+ std::string name = base::StringPrintf("expired%d", i);
+ cache.Put(name, "I'm expired.", now, t10);
+ }
+ EXPECT_EQ(8U, cache.size());
+
+ // Add two negative (instantly expired) entries at t=0 that expire at t=0.
+ for (int i = 0; i < 2; ++i) {
+ std::string name = base::StringPrintf("negative%d", i);
+ cache.Put(name, "I was never valid.", now, now);
+ }
+ EXPECT_EQ(10U, cache.size());
+
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid0"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid1"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid2"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid3"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid4"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "expired0"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "expired1"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "expired2"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "negative0"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "negative1"));
+
+ // Shrink the new max constraints bound and compact. The "negative" and
+ // "expired" entries should be dropped.
+ cache.max_entries_ = 6;
+ cache.Compact(now);
+ EXPECT_EQ(5U, cache.size());
+
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid0"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid1"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid2"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid3"));
+ EXPECT_TRUE(ContainsKey(cache.entries_, "valid4"));
+ EXPECT_FALSE(ContainsKey(cache.entries_, "expired0"));
+ EXPECT_FALSE(ContainsKey(cache.entries_, "expired1"));
+ EXPECT_FALSE(ContainsKey(cache.entries_, "expired2"));
+ EXPECT_FALSE(ContainsKey(cache.entries_, "negative0"));
+ EXPECT_FALSE(ContainsKey(cache.entries_, "negative1"));
+
+ // Shrink further -- this time the compact will start dropping valid entries
+ // to make space.
+ cache.max_entries_ = 4;
+ cache.Compact(now);
+ EXPECT_EQ(3U, cache.size());
+}
+
+// Add entries while the cache is at capacity, causing evictions.
+TEST(ExpiringCacheTest, SetWithCompact) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ Cache cache(3);
+
+ // t=10
+ base::TimeTicks now = base::TimeTicks() + kTTL;
+
+ cache.Put("test1", "test1", now, now + kTTL);
+ cache.Put("test2", "test2", now, now + kTTL);
+ cache.Put("expired", "expired", now, now);
+
+ EXPECT_EQ(3U, cache.size());
+
+ // Should all be retrievable except "expired".
+ EXPECT_THAT(cache.Get("test1", now), Pointee(StrEq("test1")));
+ EXPECT_THAT(cache.Get("test2", now), Pointee(StrEq("test2")));
+ EXPECT_FALSE(cache.Get("expired", now));
+
+ // Adding the fourth entry will cause "expired" to be evicted.
+ cache.Put("test3", "test3", now, now + kTTL);
+ EXPECT_EQ(3U, cache.size());
+
+ EXPECT_FALSE(cache.Get("expired", now));
+ EXPECT_THAT(cache.Get("test1", now), Pointee(StrEq("test1")));
+ EXPECT_THAT(cache.Get("test2", now), Pointee(StrEq("test2")));
+ EXPECT_THAT(cache.Get("test3", now), Pointee(StrEq("test3")));
+
+ // Add two more entries. Something should be evicted, however "test5"
+ // should definitely be in there (since it was last inserted).
+ cache.Put("test4", "test4", now, now + kTTL);
+ EXPECT_EQ(3U, cache.size());
+ cache.Put("test5", "test5", now, now + kTTL);
+ EXPECT_EQ(3U, cache.size());
+ EXPECT_THAT(cache.Get("test5", now), Pointee(StrEq("test5")));
+}
+
+TEST(ExpiringCacheTest, Clear) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ Cache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+ EXPECT_EQ(0U, cache.size());
+
+ // Add three entries.
+ cache.Put("test1", "foo", now, now + kTTL);
+ cache.Put("test2", "foo", now, now + kTTL);
+ cache.Put("test3", "foo", now, now + kTTL);
+ EXPECT_EQ(3U, cache.size());
+
+ cache.Clear();
+
+ EXPECT_EQ(0U, cache.size());
+}
+
+TEST(ExpiringCacheTest, GetTruncatesExpiredEntries) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ Cache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+ EXPECT_EQ(0U, cache.size());
+
+ // Add three entries at t=0.
+ cache.Put("test1", "foo1", now, now + kTTL);
+ cache.Put("test2", "foo2", now, now + kTTL);
+ cache.Put("test3", "foo3", now, now + kTTL);
+ EXPECT_EQ(3U, cache.size());
+
+ // Ensure the entries were added.
+ EXPECT_THAT(cache.Get("test1", now), Pointee(StrEq("foo1")));
+ EXPECT_THAT(cache.Get("test2", now), Pointee(StrEq("foo2")));
+ EXPECT_THAT(cache.Get("test3", now), Pointee(StrEq("foo3")));
+
+ // Add five entries at t=10.
+ now += kTTL;
+ for (int i = 0; i < 5; ++i) {
+ std::string name = base::StringPrintf("valid%d", i);
+ cache.Put(name, name, now, now + kTTL); // Expire at t=20.
+ }
+ EXPECT_EQ(8U, cache.size());
+
+ // Now access two expired entries and ensure the cache size goes down.
+ EXPECT_FALSE(cache.Get("test1", now));
+ EXPECT_FALSE(cache.Get("test2", now));
+ EXPECT_EQ(6U, cache.size());
+
+ // Accessing non-expired entries should return entries and not adjust the
+ // cache size.
+ for (int i = 0; i < 5; ++i) {
+ std::string name = base::StringPrintf("valid%d", i);
+ EXPECT_THAT(cache.Get(name, now), Pointee(StrEq(name)));
+ }
+ EXPECT_EQ(6U, cache.size());
+}
+
+TEST(ExpiringCacheTest, CustomFunctor) {
+ ExpiringCache<std::string, std::string, std::string, TestFunctor> cache(5);
+
+ const std::string kNow("Now");
+ const std::string kLater("A little bit later");
+ const std::string kMuchLater("Much later");
+ const std::string kHeatDeath("The heat death of the universe");
+
+ EXPECT_EQ(0u, cache.size());
+
+ // Add three entries at t=kNow that expire at kLater.
+ cache.Put("test1", "foo1", kNow, kLater);
+ cache.Put("test2", "foo2", kNow, kLater);
+ cache.Put("test3", "foo3", kNow, kLater);
+ EXPECT_EQ(3U, cache.size());
+
+ // Add two entries at t=kNow that expire at kMuchLater
+ cache.Put("test4", "foo4", kNow, kMuchLater);
+ cache.Put("test5", "foo5", kNow, kMuchLater);
+ EXPECT_EQ(5U, cache.size());
+
+ // Ensure the entries were added.
+ EXPECT_THAT(cache.Get("test1", kNow), Pointee(StrEq("foo1")));
+ EXPECT_THAT(cache.Get("test2", kNow), Pointee(StrEq("foo2")));
+ EXPECT_THAT(cache.Get("test3", kNow), Pointee(StrEq("foo3")));
+ EXPECT_THAT(cache.Get("test4", kNow), Pointee(StrEq("foo4")));
+ EXPECT_THAT(cache.Get("test5", kNow), Pointee(StrEq("foo5")));
+
+ // Add one entry at t=kLater that expires at kHeatDeath, which will expire
+ // one of test1-3.
+ cache.Put("test6", "foo6", kLater, kHeatDeath);
+ EXPECT_THAT(cache.Get("test6", kLater), Pointee(StrEq("foo6")));
+ EXPECT_EQ(3U, cache.size());
+
+ // Now compact at kMuchLater, which should remove all but "test6".
+ cache.max_entries_ = 2;
+ cache.Compact(kMuchLater);
+
+ EXPECT_EQ(1U, cache.size());
+ EXPECT_THAT(cache.Get("test6", kMuchLater), Pointee(StrEq("foo6")));
+
+ // Finally, "test6" should not be valid at the end of the universe.
+ EXPECT_FALSE(cache.Get("test6", kHeatDeath));
+
+ // Because comparison is based on equality, not strict weak ordering, we
+ // should be able to add something at kHeatDeath that expires at kMuchLater.
+ cache.Put("test7", "foo7", kHeatDeath, kMuchLater);
+ EXPECT_EQ(1U, cache.size());
+ EXPECT_THAT(cache.Get("test7", kNow), Pointee(StrEq("foo7")));
+ EXPECT_THAT(cache.Get("test7", kLater), Pointee(StrEq("foo7")));
+ EXPECT_THAT(cache.Get("test7", kHeatDeath), Pointee(StrEq("foo7")));
+ EXPECT_FALSE(cache.Get("test7", kMuchLater));
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream.cc b/chromium/net/base/file_stream.cc
new file mode 100644
index 00000000000..85adaece3cd
--- /dev/null
+++ b/chromium/net/base/file_stream.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream.h"
+
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/file_stream_context.h"
+#include "net/base/file_stream_net_log_parameters.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+FileStream::FileStream(NetLog* net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ /* To allow never opened stream to be destroyed on any thread we set flags
+ as if stream was opened asynchronously. */
+ : open_flags_(base::PLATFORM_FILE_ASYNC),
+ bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
+ context_(new Context(bound_net_log_, task_runner)) {
+ bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
+}
+
+FileStream::FileStream(NetLog* net_log)
+ /* To allow never opened stream to be destroyed on any thread we set flags
+ as if stream was opened asynchronously. */
+ : open_flags_(base::PLATFORM_FILE_ASYNC),
+ bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
+ context_(new Context(bound_net_log_,
+ base::WorkerPool::GetTaskRunner(true /* slow */))) {
+ bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
+}
+
+FileStream::FileStream(base::PlatformFile file,
+ int flags,
+ NetLog* net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : open_flags_(flags),
+ bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
+ context_(new Context(file, bound_net_log_, open_flags_, task_runner)) {
+ bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
+}
+
+FileStream::FileStream(base::PlatformFile file, int flags, NetLog* net_log)
+ : open_flags_(flags),
+ bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
+ context_(new Context(file,
+ bound_net_log_,
+ open_flags_,
+ base::WorkerPool::GetTaskRunner(true /* slow */))) {
+ bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
+}
+
+FileStream::~FileStream() {
+ if (!is_async()) {
+ base::ThreadRestrictions::AssertIOAllowed();
+ context_->CloseSync();
+ context_.reset();
+ } else {
+ context_.release()->Orphan();
+ }
+
+ bound_net_log_.EndEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
+}
+
+int FileStream::Open(const base::FilePath& path, int open_flags,
+ const CompletionCallback& callback) {
+ if (IsOpen()) {
+ DLOG(FATAL) << "File is already open!";
+ return ERR_UNEXPECTED;
+ }
+
+ open_flags_ = open_flags;
+ DCHECK(is_async());
+ context_->OpenAsync(path, open_flags, callback);
+ return ERR_IO_PENDING;
+}
+
+int FileStream::OpenSync(const base::FilePath& path, int open_flags) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (IsOpen()) {
+ DLOG(FATAL) << "File is already open!";
+ return ERR_UNEXPECTED;
+ }
+
+ open_flags_ = open_flags;
+ DCHECK(!is_async());
+ return context_->OpenSync(path, open_flags_);
+}
+
+bool FileStream::IsOpen() const {
+ return context_->file() != base::kInvalidPlatformFileValue;
+}
+
+int FileStream::Seek(Whence whence,
+ int64 offset,
+ const Int64CompletionCallback& callback) {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ // Make sure we're async.
+ DCHECK(is_async());
+ context_->SeekAsync(whence, offset, callback);
+ return ERR_IO_PENDING;
+}
+
+int64 FileStream::SeekSync(Whence whence, int64 offset) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ // If we're in async, make sure we don't have a request in flight.
+ DCHECK(!is_async() || !context_->async_in_progress());
+ return context_->SeekSync(whence, offset);
+}
+
+int64 FileStream::Available() {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ int64 cur_pos = SeekSync(FROM_CURRENT, 0);
+ if (cur_pos < 0)
+ return cur_pos;
+
+ int64 size = context_->GetFileSize();
+ if (size < 0)
+ return size;
+
+ DCHECK_GE(size, cur_pos);
+ return size - cur_pos;
+}
+
+int FileStream::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ // read(..., 0) will return 0, which indicates end-of-file.
+ DCHECK_GT(buf_len, 0);
+ DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
+ DCHECK(is_async());
+
+ return context_->ReadAsync(buf, buf_len, callback);
+}
+
+int FileStream::ReadSync(char* buf, int buf_len) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(!is_async());
+ // read(..., 0) will return 0, which indicates end-of-file.
+ DCHECK_GT(buf_len, 0);
+ DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
+
+ return context_->ReadSync(buf, buf_len);
+}
+
+int FileStream::ReadUntilComplete(char *buf, int buf_len) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ int to_read = buf_len;
+ int bytes_total = 0;
+
+ do {
+ int bytes_read = ReadSync(buf, to_read);
+ if (bytes_read <= 0) {
+ if (bytes_total == 0)
+ return bytes_read;
+
+ return bytes_total;
+ }
+
+ bytes_total += bytes_read;
+ buf += bytes_read;
+ to_read -= bytes_read;
+ } while (bytes_total < buf_len);
+
+ return bytes_total;
+}
+
+int FileStream::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(is_async());
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+ // write(..., 0) will return 0, which indicates end-of-file.
+ DCHECK_GT(buf_len, 0);
+
+ return context_->WriteAsync(buf, buf_len, callback);
+}
+
+int FileStream::WriteSync(const char* buf, int buf_len) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(!is_async());
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+ // write(..., 0) will return 0, which indicates end-of-file.
+ DCHECK_GT(buf_len, 0);
+
+ return context_->WriteSync(buf, buf_len);
+}
+
+int64 FileStream::Truncate(int64 bytes) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ // We'd better be open for writing.
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+
+ // Seek to the position to truncate from.
+ int64 seek_position = SeekSync(FROM_BEGIN, bytes);
+ if (seek_position != bytes)
+ return ERR_UNEXPECTED;
+
+ // And truncate the file.
+ return context_->Truncate(bytes);
+}
+
+int FileStream::Flush(const CompletionCallback& callback) {
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+ // Make sure we're async.
+ DCHECK(is_async());
+
+ context_->FlushAsync(callback);
+ return ERR_IO_PENDING;
+}
+
+int FileStream::FlushSync() {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ if (!IsOpen())
+ return ERR_UNEXPECTED;
+
+ DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
+ return context_->FlushSync();
+}
+
+void FileStream::EnableErrorStatistics() {
+ context_->set_record_uma(true);
+}
+
+void FileStream::SetBoundNetLogSource(const BoundNetLog& owner_bound_net_log) {
+ if ((owner_bound_net_log.source().id == NetLog::Source::kInvalidId) &&
+ (bound_net_log_.source().id == NetLog::Source::kInvalidId)) {
+ // Both |BoundNetLog|s are invalid.
+ return;
+ }
+
+ // Should never connect to itself.
+ DCHECK_NE(bound_net_log_.source().id, owner_bound_net_log.source().id);
+
+ bound_net_log_.AddEvent(NetLog::TYPE_FILE_STREAM_BOUND_TO_OWNER,
+ owner_bound_net_log.source().ToEventParametersCallback());
+
+ owner_bound_net_log.AddEvent(NetLog::TYPE_FILE_STREAM_SOURCE,
+ bound_net_log_.source().ToEventParametersCallback());
+}
+
+base::PlatformFile FileStream::GetPlatformFileForTesting() {
+ return context_->file();
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream.h b/chromium/net/base/file_stream.h
new file mode 100644
index 00000000000..0fb3fb26569
--- /dev/null
+++ b/chromium/net/base/file_stream.h
@@ -0,0 +1,255 @@
+// Copyright (c) 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.
+
+// This file defines FileStream, a basic interface for reading and writing files
+// synchronously or asynchronously with support for seeking to an offset.
+// Note that even when used asynchronously, only one operation is supported at
+// a time.
+
+#ifndef NET_BASE_FILE_STREAM_H_
+#define NET_BASE_FILE_STREAM_H_
+
+#include "base/platform_file.h"
+#include "base/task_runner.h"
+#include "net/base/completion_callback.h"
+#include "net/base/file_stream_whence.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+
+class IOBuffer;
+
+class NET_EXPORT FileStream {
+ public:
+ // Creates a |FileStream| with a new |BoundNetLog| (based on |net_log|)
+ // attached. |net_log| may be NULL if no logging is needed.
+ // Uses |task_runner| for asynchronous operations.
+ FileStream(net::NetLog* net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ // Same as above, but runs async tasks in base::WorkerPool.
+ explicit FileStream(net::NetLog* net_log);
+
+ // Construct a FileStream with an existing file handle and opening flags.
+ // |file| is valid file handle.
+ // |flags| is a bitfield of base::PlatformFileFlags when the file handle was
+ // opened.
+ // |net_log| is the net log pointer to use to create a |BoundNetLog|. May be
+ // NULL if logging is not needed.
+ // Uses |task_runner| for asynchronous operations.
+ // Note: the new FileStream object takes ownership of the PlatformFile and
+ // will close it on destruction.
+ FileStream(base::PlatformFile file,
+ int flags,
+ net::NetLog* net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ // Same as above, but runs async tasks in base::WorkerPool.
+ FileStream(base::PlatformFile file, int flags, net::NetLog* net_log);
+
+ // The underlying file is closed automatically.
+ virtual ~FileStream();
+
+ // Call this method to open the FileStream asynchronously. The remaining
+ // methods cannot be used unless the file is opened successfully. Returns
+ // ERR_IO_PENDING if the operation is started. If the operation cannot be
+ // started then an error code is returned.
+ //
+ // Once the operation is done, |callback| will be run on the thread where
+ // Open() was called, with the result code. open_flags is a bitfield of
+ // base::PlatformFileFlags.
+ //
+ // If the file stream is not closed manually, the underlying file will be
+ // automatically closed when FileStream is destructed in an asynchronous
+ // manner (i.e. the file stream is closed in the background but you don't
+ // know when).
+ virtual int Open(const base::FilePath& path, int open_flags,
+ const CompletionCallback& callback);
+
+ // Call this method to open the FileStream synchronously.
+ // The remaining methods cannot be used unless this method returns OK. If
+ // the file cannot be opened then an error code is returned. open_flags is
+ // a bitfield of base::PlatformFileFlags
+ //
+ // If the file stream is not closed manually, the underlying file will be
+ // automatically closed when FileStream is destructed.
+ virtual int OpenSync(const base::FilePath& path, int open_flags);
+
+ // Returns true if Open succeeded and Close has not been called.
+ virtual bool IsOpen() const;
+
+ // Adjust the position from where data is read asynchronously.
+ // Upon success, ERR_IO_PENDING is returned and |callback| will be run
+ // on the thread where Seek() was called with the the stream position
+ // relative to the start of the file. Otherwise, an error code is returned.
+ // It is invalid to request any asynchronous operations while there is an
+ // in-flight asynchronous operation.
+ virtual int Seek(Whence whence, int64 offset,
+ const Int64CompletionCallback& callback);
+
+ // Adjust the position from where data is read synchronously.
+ // Upon success, the stream position relative to the start of the file is
+ // returned. Otherwise, an error code is returned. It is not valid to
+ // call SeekSync while a Read call has a pending completion.
+ virtual int64 SeekSync(Whence whence, int64 offset);
+
+ // Returns the number of bytes available to read from the current stream
+ // position until the end of the file. Otherwise, an error code is returned.
+ virtual int64 Available();
+
+ // Call this method to read data from the current stream position
+ // asynchronously. Up to buf_len bytes will be copied into buf. (In
+ // other words, partial reads are allowed.) Returns the number of bytes
+ // copied, 0 if at end-of-file, or an error code if the operation could
+ // not be performed.
+ //
+ // The file must be opened with PLATFORM_FILE_ASYNC, and a non-null
+ // callback must be passed to this method. If the read could not
+ // complete synchronously, then ERR_IO_PENDING is returned, and the
+ // callback will be run on the thread where Read() was called, when the
+ // read has completed.
+ //
+ // It is valid to destroy or close the file stream while there is an
+ // asynchronous read in progress. That will cancel the read and allow
+ // the buffer to be freed.
+ //
+ // It is invalid to request any asynchronous operations while there is an
+ // in-flight asynchronous operation.
+ //
+ // This method must not be called if the stream was opened WRITE_ONLY.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ // Call this method to read data from the current stream position
+ // synchronously. Up to buf_len bytes will be copied into buf. (In
+ // other words, partial reads are allowed.) Returns the number of bytes
+ // copied, 0 if at end-of-file, or an error code if the operation could
+ // not be performed.
+ //
+ // The file must not be opened with PLATFORM_FILE_ASYNC.
+ // This method must not be called if the stream was opened WRITE_ONLY.
+ virtual int ReadSync(char* buf, int buf_len);
+
+ // Performs the same as ReadSync, but ensures that exactly buf_len bytes
+ // are copied into buf. A partial read may occur, but only as a result of
+ // end-of-file or fatal error. Returns the number of bytes copied into buf,
+ // 0 if at end-of-file and no bytes have been read into buf yet,
+ // or an error code if the operation could not be performed.
+ virtual int ReadUntilComplete(char *buf, int buf_len);
+
+ // Call this method to write data at the current stream position
+ // asynchronously. Up to buf_len bytes will be written from buf. (In
+ // other words, partial writes are allowed.) Returns the number of
+ // bytes written, or an error code if the operation could not be
+ // performed.
+ //
+ // The file must be opened with PLATFORM_FILE_ASYNC, and a non-null
+ // callback must be passed to this method. If the write could not
+ // complete synchronously, then ERR_IO_PENDING is returned, and the
+ // callback will be run on the thread where Write() was called when
+ // the write has completed.
+ //
+ // It is valid to destroy or close the file stream while there is an
+ // asynchronous write in progress. That will cancel the write and allow
+ // the buffer to be freed.
+ //
+ // It is invalid to request any asynchronous operations while there is an
+ // in-flight asynchronous operation.
+ //
+ // This method must not be called if the stream was opened READ_ONLY.
+ //
+ // Zero byte writes are not allowed.
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ // Call this method to write data at the current stream position
+ // synchronously. Up to buf_len bytes will be written from buf. (In
+ // other words, partial writes are allowed.) Returns the number of
+ // bytes written, or an error code if the operation could not be
+ // performed.
+ //
+ // The file must not be opened with PLATFORM_FILE_ASYNC.
+ // This method must not be called if the stream was opened READ_ONLY.
+ //
+ // Zero byte writes are not allowed.
+ virtual int WriteSync(const char* buf, int buf_len);
+
+ // Truncates the file to be |bytes| length. This is only valid for writable
+ // files. After truncation the file stream is positioned at |bytes|. The new
+ // position is returned, or a value < 0 on error.
+ // WARNING: one may not truncate a file beyond its current length on any
+ // platform with this call.
+ virtual int64 Truncate(int64 bytes);
+
+ // Forces out a filesystem sync on this file to make sure that the file was
+ // written out to disk and is not currently sitting in the buffer. This does
+ // not have to be called, it just forces one to happen at the time of
+ // calling.
+ //
+ // The file must be opened with PLATFORM_FILE_ASYNC, and a non-null
+ // callback must be passed to this method. If the write could not
+ // complete synchronously, then ERR_IO_PENDING is returned, and the
+ // callback will be run on the thread where Flush() was called when
+ // the write has completed.
+ //
+ // It is valid to destroy or close the file stream while there is an
+ // asynchronous flush in progress. That will cancel the flush and allow
+ // the buffer to be freed.
+ //
+ // It is invalid to request any asynchronous operations while there is an
+ // in-flight asynchronous operation.
+ //
+ // This method should not be called if the stream was opened READ_ONLY.
+ virtual int Flush(const CompletionCallback& callback);
+
+ // Forces out a filesystem sync on this file to make sure that the file was
+ // written out to disk and is not currently sitting in the buffer. This does
+ // not have to be called, it just forces one to happen at the time of
+ // calling.
+ //
+ // Returns an error code if the operation could not be performed.
+ //
+ // This method should not be called if the stream was opened READ_ONLY.
+ virtual int FlushSync();
+
+ // Turns on UMA error statistics gathering.
+ void EnableErrorStatistics();
+
+ // Sets the source reference for net-internals logging.
+ // Creates source dependency events between |owner_bound_net_log| and
+ // |bound_net_log_|. Each gets an event showing the dependency on the other.
+ // If only one of those is valid, it gets an event showing that a change
+ // of ownership happened, but without details.
+ void SetBoundNetLogSource(const net::BoundNetLog& owner_bound_net_log);
+
+ // Returns the underlying platform file for testing.
+ base::PlatformFile GetPlatformFileForTesting();
+
+ private:
+ class Context;
+
+ bool is_async() const { return !!(open_flags_ & base::PLATFORM_FILE_ASYNC); }
+
+ int open_flags_;
+ net::BoundNetLog bound_net_log_;
+
+ // Context performing I/O operations. It was extracted into separate class
+ // to perform asynchronous operations because FileStream can be destroyed
+ // before completion of async operation. Also if async FileStream is destroyed
+ // without explicit closing file should be closed asynchronously without
+ // delaying FileStream's destructor. To perform all that separate object is
+ // necessary.
+ scoped_ptr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileStream);
+};
+
+} // namespace net
+
+#endif // NET_BASE_FILE_STREAM_H_
diff --git a/chromium/net/base/file_stream_context.cc b/chromium/net/base/file_stream_context.cc
new file mode 100644
index 00000000000..abc058a9ca8
--- /dev/null
+++ b/chromium/net/base/file_stream_context.cc
@@ -0,0 +1,260 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream_context.h"
+
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/file_stream_net_log_parameters.h"
+#include "net/base/net_errors.h"
+
+namespace {
+
+void CallInt64ToInt(const net::CompletionCallback& callback, int64 result) {
+ callback.Run(static_cast<int>(result));
+}
+
+}
+
+namespace net {
+
+FileStream::Context::IOResult::IOResult()
+ : result(OK),
+ os_error(0) {
+}
+
+FileStream::Context::IOResult::IOResult(int64 result, int os_error)
+ : result(result),
+ os_error(os_error) {
+}
+
+// static
+FileStream::Context::IOResult FileStream::Context::IOResult::FromOSError(
+ int64 os_error) {
+ return IOResult(MapSystemError(os_error), os_error);
+}
+
+FileStream::Context::OpenResult::OpenResult()
+ : file(base::kInvalidPlatformFileValue) {
+}
+
+FileStream::Context::OpenResult::OpenResult(base::PlatformFile file,
+ IOResult error_code)
+ : file(file),
+ error_code(error_code) {
+}
+
+void FileStream::Context::Orphan() {
+ DCHECK(!orphaned_);
+
+ orphaned_ = true;
+ if (file_ != base::kInvalidPlatformFileValue)
+ bound_net_log_.EndEvent(NetLog::TYPE_FILE_STREAM_OPEN);
+
+ if (!async_in_progress_) {
+ CloseAndDelete();
+ } else if (file_ != base::kInvalidPlatformFileValue) {
+ CancelIo(file_);
+ }
+}
+
+void FileStream::Context::OpenAsync(const base::FilePath& path,
+ int open_flags,
+ const CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+
+ BeginOpenEvent(path);
+
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(
+ &Context::OpenFileImpl, base::Unretained(this), path, open_flags),
+ base::Bind(&Context::OnOpenCompleted, base::Unretained(this), callback));
+ DCHECK(posted);
+
+ async_in_progress_ = true;
+}
+
+int FileStream::Context::OpenSync(const base::FilePath& path, int open_flags) {
+ DCHECK(!async_in_progress_);
+
+ BeginOpenEvent(path);
+ OpenResult result = OpenFileImpl(path, open_flags);
+ file_ = result.file;
+ if (file_ == base::kInvalidPlatformFileValue) {
+ ProcessOpenError(result.error_code);
+ } else {
+ // TODO(satorux): Remove this once all async clients are migrated to use
+ // Open(). crbug.com/114783
+ if (open_flags & base::PLATFORM_FILE_ASYNC)
+ OnAsyncFileOpened();
+ }
+ return result.error_code.result;
+}
+
+void FileStream::Context::CloseSync() {
+ DCHECK(!async_in_progress_);
+ if (file_ != base::kInvalidPlatformFileValue) {
+ base::ClosePlatformFile(file_);
+ file_ = base::kInvalidPlatformFileValue;
+ bound_net_log_.EndEvent(NetLog::TYPE_FILE_STREAM_OPEN);
+ }
+}
+
+void FileStream::Context::SeekAsync(Whence whence,
+ int64 offset,
+ const Int64CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(
+ &Context::SeekFileImpl, base::Unretained(this), whence, offset),
+ base::Bind(&Context::ProcessAsyncResult,
+ base::Unretained(this),
+ callback,
+ FILE_ERROR_SOURCE_SEEK));
+ DCHECK(posted);
+
+ async_in_progress_ = true;
+}
+
+int64 FileStream::Context::SeekSync(Whence whence, int64 offset) {
+ IOResult result = SeekFileImpl(whence, offset);
+ RecordError(result, FILE_ERROR_SOURCE_SEEK);
+ return result.result;
+}
+
+void FileStream::Context::FlushAsync(const CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&Context::FlushFileImpl, base::Unretained(this)),
+ base::Bind(&Context::ProcessAsyncResult,
+ base::Unretained(this),
+ IntToInt64(callback),
+ FILE_ERROR_SOURCE_FLUSH));
+ DCHECK(posted);
+
+ async_in_progress_ = true;
+}
+
+int FileStream::Context::FlushSync() {
+ IOResult result = FlushFileImpl();
+ RecordError(result, FILE_ERROR_SOURCE_FLUSH);
+ return result.result;
+}
+
+void FileStream::Context::RecordError(const IOResult& result,
+ FileErrorSource source) const {
+ if (result.result >= 0) {
+ // |result| is not an error.
+ return;
+ }
+
+ // The following check is against incorrect use or bug. File descriptor
+ // shouldn't ever be closed outside of FileStream while it still tries to do
+ // something with it.
+ DCHECK_NE(result.result, ERR_INVALID_HANDLE);
+
+ if (!orphaned_) {
+ bound_net_log_.AddEvent(
+ NetLog::TYPE_FILE_STREAM_ERROR,
+ base::Bind(&NetLogFileStreamErrorCallback,
+ source, result.os_error,
+ static_cast<net::Error>(result.result)));
+ }
+
+ RecordFileError(result.os_error, source, record_uma_);
+}
+
+void FileStream::Context::BeginOpenEvent(const base::FilePath& path) {
+ std::string file_name = path.AsUTF8Unsafe();
+ bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_OPEN,
+ NetLog::StringCallback("file_name", &file_name));
+}
+
+FileStream::Context::OpenResult FileStream::Context::OpenFileImpl(
+ const base::FilePath& path, int open_flags) {
+ // FileStream::Context actually closes the file asynchronously, independently
+ // from FileStream's destructor. It can cause problems for users wanting to
+ // delete the file right after FileStream deletion. Thus we are always
+ // adding SHARE_DELETE flag to accommodate such use case.
+ open_flags |= base::PLATFORM_FILE_SHARE_DELETE;
+ base::PlatformFile file =
+ base::CreatePlatformFile(path, open_flags, NULL, NULL);
+ if (file == base::kInvalidPlatformFileValue)
+ return OpenResult(file, IOResult::FromOSError(GetLastErrno()));
+
+ return OpenResult(file, IOResult(OK, 0));
+}
+
+void FileStream::Context::ProcessOpenError(const IOResult& error_code) {
+ bound_net_log_.EndEvent(NetLog::TYPE_FILE_STREAM_OPEN);
+ RecordError(error_code, FILE_ERROR_SOURCE_OPEN);
+}
+
+void FileStream::Context::OnOpenCompleted(const CompletionCallback& callback,
+ OpenResult open_result) {
+ file_ = open_result.file;
+ if (file_ == base::kInvalidPlatformFileValue)
+ ProcessOpenError(open_result.error_code);
+ else if (!orphaned_)
+ OnAsyncFileOpened();
+ OnAsyncCompleted(IntToInt64(callback), open_result.error_code.result);
+}
+
+void FileStream::Context::CloseAndDelete() {
+ DCHECK(!async_in_progress_);
+
+ if (file_ == base::kInvalidPlatformFileValue) {
+ delete this;
+ } else {
+ const bool posted = task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&base::ClosePlatformFile), file_),
+ base::Bind(&Context::OnCloseCompleted, base::Unretained(this)));
+ DCHECK(posted);
+ file_ = base::kInvalidPlatformFileValue;
+ }
+}
+
+void FileStream::Context::OnCloseCompleted() {
+ delete this;
+}
+
+Int64CompletionCallback FileStream::Context::IntToInt64(
+ const CompletionCallback& callback) {
+ return base::Bind(&CallInt64ToInt, callback);
+}
+
+void FileStream::Context::ProcessAsyncResult(
+ const Int64CompletionCallback& callback,
+ FileErrorSource source,
+ const IOResult& result) {
+ RecordError(result, source);
+ OnAsyncCompleted(callback, result.result);
+}
+
+void FileStream::Context::OnAsyncCompleted(
+ const Int64CompletionCallback& callback,
+ int64 result) {
+ // Reset this before Run() as Run() may issue a new async operation. Also it
+ // should be reset before CloseAsync() because it shouldn't run if any async
+ // operation is in progress.
+ async_in_progress_ = false;
+ if (orphaned_)
+ CloseAndDelete();
+ else
+ callback.Run(result);
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/file_stream_context.h b/chromium/net/base/file_stream_context.h
new file mode 100644
index 00000000000..15c25bb8d4a
--- /dev/null
+++ b/chromium/net/base/file_stream_context.h
@@ -0,0 +1,232 @@
+// Copyright (c) 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.
+
+// This file defines FileStream::Context class.
+// The general design of FileStream is as follows: file_stream.h defines
+// FileStream class which basically is just an "wrapper" not containing any
+// specific implementation details. It re-routes all its method calls to
+// the instance of FileStream::Context (FileStream holds a scoped_ptr to
+// FileStream::Context instance). Context was extracted into a different class
+// to be able to do and finish all async operations even when FileStream
+// instance is deleted. So FileStream's destructor can schedule file
+// closing to be done by Context in WorkerPool (or the TaskRunner passed to
+// constructor) and then just return (releasing Context pointer from
+// scoped_ptr) without waiting for actual closing to complete.
+// Implementation of FileStream::Context is divided in two parts: some methods
+// and members are platform-independent and some depend on the platform. This
+// header file contains the complete definition of Context class including all
+// platform-dependent parts (because of that it has a lot of #if-#else
+// branching). Implementations of all platform-independent methods are
+// located in file_stream_context.cc, and all platform-dependent methods are
+// in file_stream_context_{win,posix}.cc. This separation provides better
+// readability of Context's code. And we tried to make as much Context code
+// platform-independent as possible. So file_stream_context_{win,posix}.cc are
+// much smaller than file_stream_context.cc now.
+
+#ifndef NET_BASE_FILE_STREAM_CONTEXT_H_
+#define NET_BASE_FILE_STREAM_CONTEXT_H_
+
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/task_runner.h"
+#include "net/base/completion_callback.h"
+#include "net/base/file_stream.h"
+#include "net/base/file_stream_metrics.h"
+#include "net/base/file_stream_whence.h"
+#include "net/base/net_log.h"
+
+#if defined(OS_POSIX)
+#include <errno.h>
+#endif
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+
+class IOBuffer;
+
+#if defined(OS_WIN)
+class FileStream::Context : public base::MessageLoopForIO::IOHandler {
+#elif defined(OS_POSIX)
+class FileStream::Context {
+#endif
+ public:
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform-dependent methods implemented in
+ // file_stream_context_{win,posix}.cc.
+ ////////////////////////////////////////////////////////////////////////////
+
+ Context(const BoundNetLog& bound_net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+ Context(base::PlatformFile file,
+ const BoundNetLog& bound_net_log,
+ int open_flags,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+#if defined(OS_WIN)
+ virtual ~Context();
+#elif defined(OS_POSIX)
+ ~Context();
+#endif
+
+ int64 GetFileSize() const;
+
+ int ReadAsync(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback);
+ int ReadSync(char* buf, int buf_len);
+
+ int WriteAsync(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback);
+ int WriteSync(const char* buf, int buf_len);
+
+ int Truncate(int64 bytes);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Inline methods.
+ ////////////////////////////////////////////////////////////////////////////
+
+ void set_record_uma(bool value) { record_uma_ = value; }
+ base::PlatformFile file() const { return file_; }
+ bool async_in_progress() const { return async_in_progress_; }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform-independent methods implemented in file_stream_context.cc.
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Destroys the context. It can be deleted in the method or deletion can be
+ // deferred if some asynchronous operation is now in progress or if file is
+ // not closed yet.
+ void Orphan();
+
+ void OpenAsync(const base::FilePath& path,
+ int open_flags,
+ const CompletionCallback& callback);
+ int OpenSync(const base::FilePath& path, int open_flags);
+
+ void CloseSync();
+
+ void SeekAsync(Whence whence,
+ int64 offset,
+ const Int64CompletionCallback& callback);
+ int64 SeekSync(Whence whence, int64 offset);
+
+ void FlushAsync(const CompletionCallback& callback);
+ int FlushSync();
+
+ private:
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform-independent methods implemented in file_stream_context.cc.
+ ////////////////////////////////////////////////////////////////////////////
+
+ struct IOResult {
+ IOResult();
+ IOResult(int64 result, int os_error);
+ static IOResult FromOSError(int64 os_error);
+
+ int64 result;
+ int os_error; // Set only when result < 0.
+ };
+
+ struct OpenResult {
+ OpenResult();
+ OpenResult(base::PlatformFile file, IOResult error_code);
+ base::PlatformFile file;
+ IOResult error_code;
+ };
+
+ // Log the error from |result| to |bound_net_log_|.
+ void RecordError(const IOResult& result, FileErrorSource source) const;
+
+ void BeginOpenEvent(const base::FilePath& path);
+
+ OpenResult OpenFileImpl(const base::FilePath& path, int open_flags);
+
+ void ProcessOpenError(const IOResult& result);
+ void OnOpenCompleted(const CompletionCallback& callback,
+ OpenResult open_result);
+
+ void CloseAndDelete();
+ void OnCloseCompleted();
+
+ Int64CompletionCallback IntToInt64(const CompletionCallback& callback);
+
+ // Called when asynchronous Seek() is completed.
+ // Reports error if needed and calls callback.
+ void ProcessAsyncResult(const Int64CompletionCallback& callback,
+ FileErrorSource source,
+ const IOResult& result);
+
+ // Called when asynchronous Open() or Seek()
+ // is completed. |result| contains the result or a network error code.
+ void OnAsyncCompleted(const Int64CompletionCallback& callback, int64 result);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helper stuff which is platform-dependent but is used in the platform-
+ // independent code implemented in file_stream_context.cc. These helpers were
+ // introduced solely to implement as much of the Context methods as
+ // possible independently from platform.
+ ////////////////////////////////////////////////////////////////////////////
+
+#if defined(OS_WIN)
+ int GetLastErrno() { return GetLastError(); }
+ void OnAsyncFileOpened();
+#elif defined(OS_POSIX)
+ int GetLastErrno() { return errno; }
+ void OnAsyncFileOpened() {}
+ void CancelIo(base::PlatformFile) {}
+#endif
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform-dependent methods implemented in
+ // file_stream_context_{win,posix}.cc.
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Adjusts the position from where the data is read.
+ IOResult SeekFileImpl(Whence whence, int64 offset);
+
+ // Flushes all data written to the stream.
+ IOResult FlushFileImpl();
+
+#if defined(OS_WIN)
+ void IOCompletionIsPending(const CompletionCallback& callback, IOBuffer* buf);
+
+ // Implementation of MessageLoopForIO::IOHandler.
+ virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_read,
+ DWORD error) OVERRIDE;
+#elif defined(OS_POSIX)
+ // ReadFileImpl() is a simple wrapper around read() that handles EINTR
+ // signals and calls RecordAndMapError() to map errno to net error codes.
+ IOResult ReadFileImpl(scoped_refptr<IOBuffer> buf, int buf_len);
+
+ // WriteFileImpl() is a simple wrapper around write() that handles EINTR
+ // signals and calls MapSystemError() to map errno to net error codes.
+ // It tries to write to completion.
+ IOResult WriteFileImpl(scoped_refptr<IOBuffer> buf, int buf_len);
+#endif
+
+ base::PlatformFile file_;
+ bool record_uma_;
+ bool async_in_progress_;
+ bool orphaned_;
+ BoundNetLog bound_net_log_;
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+#if defined(OS_WIN)
+ base::MessageLoopForIO::IOContext io_context_;
+ CompletionCallback callback_;
+ scoped_refptr<IOBuffer> in_flight_buf_;
+ FileErrorSource error_source_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+} // namespace net
+
+#endif // NET_BASE_FILE_STREAM_CONTEXT_H_
+
diff --git a/chromium/net/base/file_stream_context_posix.cc b/chromium/net/base/file_stream_context_posix.cc
new file mode 100644
index 00000000000..1ef5be5fc16
--- /dev/null
+++ b/chromium/net/base/file_stream_context_posix.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 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.
+
+// For 64-bit file access (off_t = off64_t, lseek64, etc).
+#define _FILE_OFFSET_BITS 64
+
+#include "net/base/file_stream_context.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/task_runner_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+#if defined(OS_ANDROID)
+// Android's bionic libc only supports the LFS transitional API.
+#define off_t off64_t
+#define lseek lseek64
+#define stat stat64
+#define fstat fstat64
+#endif
+
+namespace net {
+
+// We cast back and forth, so make sure it's the size we're expecting.
+COMPILE_ASSERT(sizeof(int64) == sizeof(off_t), off_t_64_bit);
+
+// Make sure our Whence mappings match the system headers.
+COMPILE_ASSERT(FROM_BEGIN == SEEK_SET &&
+ FROM_CURRENT == SEEK_CUR &&
+ FROM_END == SEEK_END, whence_matches_system);
+
+FileStream::Context::Context(const BoundNetLog& bound_net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : file_(base::kInvalidPlatformFileValue),
+ record_uma_(false),
+ async_in_progress_(false),
+ orphaned_(false),
+ bound_net_log_(bound_net_log),
+ task_runner_(task_runner) {
+}
+
+FileStream::Context::Context(base::PlatformFile file,
+ const BoundNetLog& bound_net_log,
+ int /* open_flags */,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : file_(file),
+ record_uma_(false),
+ async_in_progress_(false),
+ orphaned_(false),
+ bound_net_log_(bound_net_log),
+ task_runner_(task_runner) {
+}
+
+FileStream::Context::~Context() {
+}
+
+int64 FileStream::Context::GetFileSize() const {
+ struct stat info;
+ if (fstat(file_, &info) != 0) {
+ IOResult result = IOResult::FromOSError(errno);
+ RecordError(result, FILE_ERROR_SOURCE_GET_SIZE);
+ return result.result;
+ }
+
+ return static_cast<int64>(info.st_size);
+}
+
+int FileStream::Context::ReadAsync(IOBuffer* in_buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+
+ scoped_refptr<IOBuffer> buf = in_buf;
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&Context::ReadFileImpl, base::Unretained(this), buf, buf_len),
+ base::Bind(&Context::ProcessAsyncResult,
+ base::Unretained(this),
+ IntToInt64(callback),
+ FILE_ERROR_SOURCE_READ));
+ DCHECK(posted);
+
+ async_in_progress_ = true;
+ return ERR_IO_PENDING;
+}
+
+int FileStream::Context::ReadSync(char* in_buf, int buf_len) {
+ scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(in_buf);
+ IOResult result = ReadFileImpl(buf, buf_len);
+ RecordError(result, FILE_ERROR_SOURCE_READ);
+ return result.result;
+}
+
+int FileStream::Context::WriteAsync(IOBuffer* in_buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+
+ scoped_refptr<IOBuffer> buf = in_buf;
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&Context::WriteFileImpl, base::Unretained(this), buf, buf_len),
+ base::Bind(&Context::ProcessAsyncResult,
+ base::Unretained(this),
+ IntToInt64(callback),
+ FILE_ERROR_SOURCE_WRITE));
+ DCHECK(posted);
+
+ async_in_progress_ = true;
+ return ERR_IO_PENDING;
+}
+
+int FileStream::Context::WriteSync(const char* in_buf, int buf_len) {
+ scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(in_buf);
+ IOResult result = WriteFileImpl(buf, buf_len);
+ RecordError(result, FILE_ERROR_SOURCE_WRITE);
+ return result.result;
+}
+
+int FileStream::Context::Truncate(int64 bytes) {
+ if (ftruncate(file_, bytes) != 0) {
+ IOResult result = IOResult::FromOSError(errno);
+ RecordError(result, FILE_ERROR_SOURCE_SET_EOF);
+ return result.result;
+ }
+
+ return bytes;
+}
+
+FileStream::Context::IOResult FileStream::Context::SeekFileImpl(Whence whence,
+ int64 offset) {
+ off_t res = lseek(file_, static_cast<off_t>(offset),
+ static_cast<int>(whence));
+ if (res == static_cast<off_t>(-1))
+ return IOResult::FromOSError(errno);
+
+ return IOResult(res, 0);
+}
+
+FileStream::Context::IOResult FileStream::Context::FlushFileImpl() {
+ ssize_t res = HANDLE_EINTR(fsync(file_));
+ if (res == -1)
+ return IOResult::FromOSError(errno);
+
+ return IOResult(res, 0);
+}
+
+FileStream::Context::IOResult FileStream::Context::ReadFileImpl(
+ scoped_refptr<IOBuffer> buf,
+ int buf_len) {
+ // Loop in the case of getting interrupted by a signal.
+ ssize_t res = HANDLE_EINTR(read(file_, buf->data(),
+ static_cast<size_t>(buf_len)));
+ if (res == -1)
+ return IOResult::FromOSError(errno);
+
+ return IOResult(res, 0);
+}
+
+FileStream::Context::IOResult FileStream::Context::WriteFileImpl(
+ scoped_refptr<IOBuffer> buf,
+ int buf_len) {
+ ssize_t res = HANDLE_EINTR(write(file_, buf->data(), buf_len));
+ if (res == -1)
+ return IOResult::FromOSError(errno);
+
+ return IOResult(res, 0);
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_context_win.cc b/chromium/net/base/file_stream_context_win.cc
new file mode 100644
index 00000000000..5fb30859e75
--- /dev/null
+++ b/chromium/net/base/file_stream_context_win.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream_context.h"
+
+#include <windows.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/task_runner_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// Ensure that we can just use our Whence values directly.
+COMPILE_ASSERT(FROM_BEGIN == FILE_BEGIN, bad_whence_begin);
+COMPILE_ASSERT(FROM_CURRENT == FILE_CURRENT, bad_whence_current);
+COMPILE_ASSERT(FROM_END == FILE_END, bad_whence_end);
+
+namespace {
+
+void SetOffset(OVERLAPPED* overlapped, const LARGE_INTEGER& offset) {
+ overlapped->Offset = offset.LowPart;
+ overlapped->OffsetHigh = offset.HighPart;
+}
+
+void IncrementOffset(OVERLAPPED* overlapped, DWORD count) {
+ LARGE_INTEGER offset;
+ offset.LowPart = overlapped->Offset;
+ offset.HighPart = overlapped->OffsetHigh;
+ offset.QuadPart += static_cast<LONGLONG>(count);
+ SetOffset(overlapped, offset);
+}
+
+} // namespace
+
+FileStream::Context::Context(const BoundNetLog& bound_net_log,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : io_context_(),
+ file_(base::kInvalidPlatformFileValue),
+ record_uma_(false),
+ async_in_progress_(false),
+ orphaned_(false),
+ bound_net_log_(bound_net_log),
+ error_source_(FILE_ERROR_SOURCE_COUNT),
+ task_runner_(task_runner) {
+ io_context_.handler = this;
+ memset(&io_context_.overlapped, 0, sizeof(io_context_.overlapped));
+}
+
+FileStream::Context::Context(base::PlatformFile file,
+ const BoundNetLog& bound_net_log,
+ int open_flags,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : io_context_(),
+ file_(file),
+ record_uma_(false),
+ async_in_progress_(false),
+ orphaned_(false),
+ bound_net_log_(bound_net_log),
+ error_source_(FILE_ERROR_SOURCE_COUNT),
+ task_runner_(task_runner) {
+ io_context_.handler = this;
+ memset(&io_context_.overlapped, 0, sizeof(io_context_.overlapped));
+ if (file_ != base::kInvalidPlatformFileValue &&
+ (open_flags & base::PLATFORM_FILE_ASYNC)) {
+ OnAsyncFileOpened();
+ }
+}
+
+FileStream::Context::~Context() {
+}
+
+int64 FileStream::Context::GetFileSize() const {
+ LARGE_INTEGER file_size;
+ if (!GetFileSizeEx(file_, &file_size)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ LOG(WARNING) << "GetFileSizeEx failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_GET_SIZE);
+ return error.result;
+ }
+
+ return file_size.QuadPart;
+}
+
+int FileStream::Context::ReadAsync(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(!async_in_progress_);
+ error_source_ = FILE_ERROR_SOURCE_READ;
+
+ DWORD bytes_read;
+ if (!ReadFile(file_, buf->data(), buf_len,
+ &bytes_read, &io_context_.overlapped)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ if (error.os_error == ERROR_IO_PENDING) {
+ IOCompletionIsPending(callback, buf);
+ } else if (error.os_error == ERROR_HANDLE_EOF) {
+ return 0; // Report EOF by returning 0 bytes read.
+ } else {
+ LOG(WARNING) << "ReadFile failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_READ);
+ }
+ return error.result;
+ }
+
+ IOCompletionIsPending(callback, buf);
+ return ERR_IO_PENDING;
+}
+
+int FileStream::Context::ReadSync(char* buf, int buf_len) {
+ DWORD bytes_read;
+ if (!ReadFile(file_, buf, buf_len, &bytes_read, NULL)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ if (error.os_error == ERROR_HANDLE_EOF) {
+ return 0; // Report EOF by returning 0 bytes read.
+ } else {
+ LOG(WARNING) << "ReadFile failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_READ);
+ }
+ return error.result;
+ }
+
+ return bytes_read;
+}
+
+int FileStream::Context::WriteAsync(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ error_source_ = FILE_ERROR_SOURCE_WRITE;
+
+ DWORD bytes_written = 0;
+ if (!WriteFile(file_, buf->data(), buf_len,
+ &bytes_written, &io_context_.overlapped)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ if (error.os_error == ERROR_IO_PENDING) {
+ IOCompletionIsPending(callback, buf);
+ } else {
+ LOG(WARNING) << "WriteFile failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_WRITE);
+ }
+ return error.result;
+ }
+
+ IOCompletionIsPending(callback, buf);
+ return ERR_IO_PENDING;
+}
+
+int FileStream::Context::WriteSync(const char* buf, int buf_len) {
+ DWORD bytes_written = 0;
+ if (!WriteFile(file_, buf, buf_len, &bytes_written, NULL)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ LOG(WARNING) << "WriteFile failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_WRITE);
+ return error.result;
+ }
+
+ return bytes_written;
+}
+
+int FileStream::Context::Truncate(int64 bytes) {
+ if (!SetEndOfFile(file_)) {
+ IOResult error = IOResult::FromOSError(GetLastError());
+ LOG(WARNING) << "SetEndOfFile failed: " << error.os_error;
+ RecordError(error, FILE_ERROR_SOURCE_SET_EOF);
+ return error.result;
+ }
+
+ return bytes;
+}
+
+void FileStream::Context::OnAsyncFileOpened() {
+ base::MessageLoopForIO::current()->RegisterIOHandler(file_, this);
+}
+
+FileStream::Context::IOResult FileStream::Context::SeekFileImpl(Whence whence,
+ int64 offset) {
+ LARGE_INTEGER distance, res;
+ distance.QuadPart = offset;
+ DWORD move_method = static_cast<DWORD>(whence);
+ if (SetFilePointerEx(file_, distance, &res, move_method)) {
+ SetOffset(&io_context_.overlapped, res);
+ return IOResult(res.QuadPart, 0);
+ }
+
+ return IOResult::FromOSError(GetLastError());
+}
+
+FileStream::Context::IOResult FileStream::Context::FlushFileImpl() {
+ if (FlushFileBuffers(file_))
+ return IOResult(OK, 0);
+
+ return IOResult::FromOSError(GetLastError());
+}
+
+void FileStream::Context::IOCompletionIsPending(
+ const CompletionCallback& callback,
+ IOBuffer* buf) {
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ in_flight_buf_ = buf; // Hold until the async operation ends.
+ async_in_progress_ = true;
+}
+
+void FileStream::Context::OnIOCompleted(
+ base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_read,
+ DWORD error) {
+ DCHECK_EQ(&io_context_, context);
+ DCHECK(!callback_.is_null());
+ DCHECK(async_in_progress_);
+
+ async_in_progress_ = false;
+ if (orphaned_) {
+ callback_.Reset();
+ in_flight_buf_ = NULL;
+ CloseAndDelete();
+ return;
+ }
+
+ int result;
+ if (error == ERROR_HANDLE_EOF) {
+ result = 0;
+ } else if (error) {
+ IOResult error_result = IOResult::FromOSError(error);
+ RecordError(error_result, error_source_);
+ result = error_result.result;
+ } else {
+ result = bytes_read;
+ IncrementOffset(&io_context_.overlapped, bytes_read);
+ }
+
+ CompletionCallback temp_callback = callback_;
+ callback_.Reset();
+ scoped_refptr<IOBuffer> temp_buf = in_flight_buf_;
+ in_flight_buf_ = NULL;
+ temp_callback.Run(result);
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_metrics.cc b/chromium/net/base/file_stream_metrics.cc
new file mode 100644
index 00000000000..4dc0576325f
--- /dev/null
+++ b/chromium/net/base/file_stream_metrics.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream_metrics.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+
+namespace net {
+
+namespace {
+
+const char* FileErrorSourceStrings[] = {
+ "OPEN",
+ "WRITE",
+ "READ",
+ "SEEK",
+ "FLUSH",
+ "SET_EOF",
+ "GET_SIZE"
+};
+
+COMPILE_ASSERT(ARRAYSIZE_UNSAFE(FileErrorSourceStrings) ==
+ FILE_ERROR_SOURCE_COUNT,
+ file_error_source_enum_has_changed);
+
+void RecordFileErrorTypeCount(FileErrorSource source) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.FileErrorType_Counts", source, FILE_ERROR_SOURCE_COUNT);
+}
+
+} // namespace
+
+void RecordFileError(int error, FileErrorSource source, bool record) {
+ LOG(ERROR) << " " << __FUNCTION__ << "()"
+ << " error = " << error
+ << " source = " << source
+ << " record = " << record;
+
+ if (!record)
+ return;
+
+ RecordFileErrorTypeCount(source);
+
+ int bucket = GetFileErrorUmaBucket(error);
+
+ // Fixed values per platform.
+ static const int max_bucket = MaxFileErrorUmaBucket();
+ static const int max_error = MaxFileErrorUmaValue();
+
+ switch(source) {
+ case FILE_ERROR_SOURCE_OPEN:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_Open", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_Open", bucket, max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_WRITE:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_Write", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_Write", bucket, max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_READ:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_Read", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_Read", bucket, max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_SEEK:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_Seek", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_Seek", bucket, max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_FLUSH:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_Flush", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_Flush", bucket, max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_SET_EOF:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_SetEof", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_SetEof", bucket,
+ max_bucket);
+ break;
+
+ case FILE_ERROR_SOURCE_GET_SIZE:
+ UMA_HISTOGRAM_ENUMERATION("Net.FileError_GetSize", error, max_error);
+ UMA_HISTOGRAM_ENUMERATION("Net.FileErrorRange_GetSize", bucket,
+ max_bucket);
+ break;
+
+ default:
+ break;
+ }
+}
+
+const char* GetFileErrorSourceName(FileErrorSource source) {
+ DCHECK_NE(FILE_ERROR_SOURCE_COUNT, source);
+ return FileErrorSourceStrings[source];
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_metrics.h b/chromium/net/base/file_stream_metrics.h
new file mode 100644
index 00000000000..14988aad82e
--- /dev/null
+++ b/chromium/net/base/file_stream_metrics.h
@@ -0,0 +1,43 @@
+// Copyright (c) 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.
+
+// File error statistics gathering.
+
+#ifndef NET_BASE_FILE_STREAM_METRICS_H_
+#define NET_BASE_FILE_STREAM_METRICS_H_
+
+namespace net {
+
+enum FileErrorSource {
+ FILE_ERROR_SOURCE_OPEN = 0,
+ FILE_ERROR_SOURCE_WRITE,
+ FILE_ERROR_SOURCE_READ,
+ FILE_ERROR_SOURCE_SEEK,
+ FILE_ERROR_SOURCE_FLUSH,
+ FILE_ERROR_SOURCE_SET_EOF,
+ FILE_ERROR_SOURCE_GET_SIZE,
+ FILE_ERROR_SOURCE_COUNT,
+};
+
+// UMA error statistics gathering.
+// Put the error value into a bucket.
+int GetFileErrorUmaBucket(int error);
+
+// The largest bucket number, plus 1.
+int MaxFileErrorUmaBucket();
+
+// The highest error value we want to individually report.
+int MaxFileErrorUmaValue();
+
+// |error| is a platform-specific error (Windows or Posix).
+// |source| indicates the operation that resulted in the error.
+// |record| is a flag indicating that we are interested in this error.
+void RecordFileError(int error, FileErrorSource source, bool record);
+
+// Gets a description for the source of a file error.
+const char* GetFileErrorSourceName(FileErrorSource source);
+
+} // namespace net
+
+#endif // NET_BASE_FILE_STREAM_METRICS_H_
diff --git a/chromium/net/base/file_stream_metrics_posix.cc b/chromium/net/base/file_stream_metrics_posix.cc
new file mode 100644
index 00000000000..7407d50c6e7
--- /dev/null
+++ b/chromium/net/base/file_stream_metrics_posix.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2011 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/base/file_stream_metrics.h"
+
+namespace net {
+
+int GetFileErrorUmaBucket(int error) {
+ return 1;
+}
+
+int MaxFileErrorUmaBucket() {
+ return 2;
+}
+
+int MaxFileErrorUmaValue() {
+ return 160;
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_metrics_win.cc b/chromium/net/base/file_stream_metrics_win.cc
new file mode 100644
index 00000000000..c397e4b5ee9
--- /dev/null
+++ b/chromium/net/base/file_stream_metrics_win.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2011 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/base/file_stream_metrics.h"
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+
+namespace net {
+
+namespace {
+
+struct Range {
+ int low;
+ int high;
+};
+
+// The error range list is extracted from WinError.h.
+//
+// NOTE: The gaps between the ranges need to be recorded too.
+// They will have odd-numbered buckets.
+const Range kErrorRangeList[] = {
+ { 0, 321 }, // 2.
+ { 335, 371 }, // 4.
+ { 383, 387 }, // 6.
+ { 399, 404 }, // etc.
+ { 415, 418 },
+ { 431, 433 },
+ { 447, 868 },
+ { 994, 1471 },
+ { 1500, 1513 },
+ { 1536, 1553 },
+ { 1601, 1654 },
+ { 1700, 1834 },
+ { 1898, 1938 },
+ { 2000, 2024 },
+ { 2048, 2085 },
+ { 2108, 2110 },
+ { 2202, 2203 },
+ { 2250, 2251 },
+ { 2401, 2405 },
+ { 3000, 3021 },
+ { 3950, 3951 },
+ { 4000, 4007 },
+ { 4050, 4066 },
+ { 4096, 4116 },
+ { 4200, 4215 },
+ { 4300, 4353 },
+ { 4390, 4395 },
+ { 4500, 4501 },
+ { 4864, 4905 },
+ { 5001, 5090 },
+ { 5890, 5953 },
+ { 6000, 6023 },
+ { 6118, 6119 },
+ { 6200, 6201 },
+ { 6600, 6649 },
+ { 6700, 6732 },
+ { 6800, 6856 },
+ { 7001, 7071 },
+ { 8001, 8018 },
+ { 8192, 8263 },
+ { 8301, 8640 },
+ { 8704, 8705 },
+ { 8960, 9053 },
+ { 9216, 9218 },
+ { 9263, 9276 },
+ { 9472, 9506 },
+ { 9550, 9573 },
+ { 9600, 9622 },
+ { 9650, 9656 },
+ { 9688, 9723 },
+ { 9750, 9754 },
+ { 9800, 9802 },
+ { 9850, 9853 },
+ { 9900, 9907 },
+ { 10000, 10072 },
+ { 10091, 10113 },
+ { 11001, 11034 },
+ { 12288, 12335 },
+ { 12544, 12559 },
+ { 12595, 12597 },
+ { 12801, 12803 },
+ { 13000, 13026 },
+ { 13800, 13933 },
+ { 14000, 14111 },
+ { 15000, 15039 },
+ { 15080, 15086 },
+ { 15100, 15109 },
+ { 15200, 15208 },
+ { 15250, 15251 },
+ { 15299, 15302 },
+ { 16385, 16436 },
+ { 18432, 18454 },
+ { 20480, 20486 },
+ { 24577, 24607 },
+ { 28673, 28698 },
+ { 32790, 32816 },
+ { 33281, 33322 },
+ { 35005, 35024 },
+ { 36000, 36004 },
+ { 40010, 40011 },
+ { 40067, 40069 },
+ { 53248, 53293 },
+ { 53376, 53382 },
+ { 57344, 57360 },
+ { 57377, 57394 },
+ { 65535, 65536 } // 2 * kNumErrorRanges.
+};
+const size_t kNumErrorRanges = ARRAYSIZE_UNSAFE(kErrorRangeList);
+
+} // namespace
+
+// Windows has very many errors. We're not interested in most of them, but we
+// don't know which ones are significant.
+// This function maps error ranges to specific buckets.
+// If we get hits on the buckets, we can add those values to the values we
+// record individually.
+// If we get values *between* the buckets, we record those as buckets too.
+int GetFileErrorUmaBucket(int error) {
+ error = HRESULT_CODE(error);
+
+ // This is a linear search, but of a short fixed-size array.
+ // It also gets called infrequently, on errors.
+ for (size_t n = 0; n < kNumErrorRanges; ++n) {
+ if (error < kErrorRangeList[n].low)
+ return (2 * (n + 1)) - 1; // In gap before the range.
+ if (error <= kErrorRangeList[n].high)
+ return 2 * (n + 1); // In the range.
+ }
+
+ // After the last bucket.
+ return 2 * kNumErrorRanges + 1;
+}
+
+int MaxFileErrorUmaBucket() {
+ return 2 * kNumErrorRanges + 2;
+}
+
+int MaxFileErrorUmaValue() {
+ return kErrorRangeList[0].high + 1;
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_net_log_parameters.cc b/chromium/net/base/file_stream_net_log_parameters.cc
new file mode 100644
index 00000000000..e85b62592b8
--- /dev/null
+++ b/chromium/net/base/file_stream_net_log_parameters.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream_net_log_parameters.h"
+
+#include "base/values.h"
+
+namespace net {
+
+base::Value* NetLogFileStreamErrorCallback(
+ FileErrorSource source,
+ int os_error,
+ net::Error net_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("operation", GetFileErrorSourceName(source));
+ dict->SetInteger("os_error", os_error);
+ dict->SetInteger("net_error", net_error);
+
+ return dict;
+}
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_net_log_parameters.h b/chromium/net/base/file_stream_net_log_parameters.h
new file mode 100644
index 00000000000..09f96c8750e
--- /dev/null
+++ b/chromium/net/base/file_stream_net_log_parameters.h
@@ -0,0 +1,27 @@
+// Copyright (c) 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.
+
+// File stream error reporting.
+
+#ifndef NET_BASE_FILE_STREAM_NET_LOG_PARAMETERS_H_
+#define NET_BASE_FILE_STREAM_NET_LOG_PARAMETERS_H_
+
+#include <string>
+
+#include "net/base/file_stream_metrics.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// Creates NetLog parameters when a FileStream has an error.
+base::Value* NetLogFileStreamErrorCallback(
+ FileErrorSource source,
+ int os_error,
+ net::Error net_error,
+ NetLog::LogLevel log_level);
+
+} // namespace net
+
+#endif // NET_BASE_FILE_STREAM_NET_LOG_PARAMETERS_H_
diff --git a/chromium/net/base/file_stream_unittest.cc b/chromium/net/base/file_stream_unittest.cc
new file mode 100644
index 00000000000..4be58b738e8
--- /dev/null
+++ b/chromium/net/base/file_stream_unittest.cc
@@ -0,0 +1,1082 @@
+// Copyright (c) 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.
+
+#include "net/base/file_stream.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/platform_file.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+const char kTestData[] = "0123456789";
+const int kTestDataSize = arraysize(kTestData) - 1;
+
+// Creates an IOBufferWithSize that contains the kTestDataSize.
+IOBufferWithSize* CreateTestDataBuffer() {
+ IOBufferWithSize* buf = new IOBufferWithSize(kTestDataSize);
+ memcpy(buf->data(), kTestData, kTestDataSize);
+ return buf;
+}
+
+} // namespace
+
+class FileStreamTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+
+ file_util::CreateTemporaryFile(&temp_file_path_);
+ file_util::WriteFile(temp_file_path_, kTestData, kTestDataSize);
+ }
+ virtual void TearDown() {
+ EXPECT_TRUE(base::DeleteFile(temp_file_path_, false));
+
+ PlatformTest::TearDown();
+ }
+
+ const base::FilePath temp_file_path() const { return temp_file_path_; }
+
+ private:
+ base::FilePath temp_file_path_;
+};
+
+namespace {
+
+TEST_F(FileStreamTest, BasicOpenClose) {
+ base::PlatformFile file = base::kInvalidPlatformFileValue;
+ {
+ FileStream stream(NULL);
+ int rv = stream.OpenSync(temp_file_path(),
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ);
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(stream.IsOpen());
+ file = stream.GetPlatformFileForTesting();
+ }
+ EXPECT_NE(base::kInvalidPlatformFileValue, file);
+ base::PlatformFileInfo info;
+ // The file should be closed.
+ EXPECT_FALSE(base::GetPlatformFileInfo(file, &info));
+}
+
+TEST_F(FileStreamTest, FileHandleNotLeftOpen) {
+ bool created = false;
+ ASSERT_EQ(kTestDataSize,
+ file_util::WriteFile(temp_file_path(), kTestData, kTestDataSize));
+ int flags = base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ;
+ base::PlatformFile file = base::CreatePlatformFile(
+ temp_file_path(), flags, &created, NULL);
+
+ {
+ // Seek to the beginning of the file and read.
+ FileStream read_stream(file, flags, NULL);
+ EXPECT_TRUE(read_stream.IsOpen());
+ }
+
+ EXPECT_NE(base::kInvalidPlatformFileValue, file);
+ base::PlatformFileInfo info;
+ // The file should be closed.
+ EXPECT_FALSE(base::GetPlatformFileInfo(file, &info));
+}
+
+// Test the use of FileStream with a file handle provided at construction.
+TEST_F(FileStreamTest, UseFileHandle) {
+ bool created = false;
+
+ // 1. Test reading with a file handle.
+ ASSERT_EQ(kTestDataSize,
+ file_util::WriteFile(temp_file_path(), kTestData, kTestDataSize));
+ int flags = base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ;
+ base::PlatformFile file = base::CreatePlatformFile(
+ temp_file_path(), flags, &created, NULL);
+
+ // Seek to the beginning of the file and read.
+ scoped_ptr<FileStream> read_stream(new FileStream(file, flags, NULL));
+ ASSERT_EQ(0, read_stream->SeekSync(FROM_BEGIN, 0));
+ ASSERT_EQ(kTestDataSize, read_stream->Available());
+ // Read into buffer and compare.
+ char buffer[kTestDataSize];
+ ASSERT_EQ(kTestDataSize,
+ read_stream->ReadSync(buffer, kTestDataSize));
+ ASSERT_EQ(0, memcmp(kTestData, buffer, kTestDataSize));
+ read_stream.reset();
+
+ // 2. Test writing with a file handle.
+ base::DeleteFile(temp_file_path(), false);
+ flags = base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE;
+ file = base::CreatePlatformFile(temp_file_path(), flags, &created, NULL);
+
+ scoped_ptr<FileStream> write_stream(new FileStream(file, flags, NULL));
+ ASSERT_EQ(0, write_stream->SeekSync(FROM_BEGIN, 0));
+ ASSERT_EQ(kTestDataSize,
+ write_stream->WriteSync(kTestData, kTestDataSize));
+ write_stream.reset();
+
+ // Read into buffer and compare to make sure the handle worked fine.
+ ASSERT_EQ(kTestDataSize,
+ file_util::ReadFile(temp_file_path(), buffer, kTestDataSize));
+ ASSERT_EQ(0, memcmp(kTestData, buffer, kTestDataSize));
+}
+
+TEST_F(FileStreamTest, UseClosedStream) {
+ FileStream stream(NULL);
+
+ EXPECT_FALSE(stream.IsOpen());
+
+ // Try seeking...
+ int64 new_offset = stream.SeekSync(FROM_BEGIN, 5);
+ EXPECT_EQ(ERR_UNEXPECTED, new_offset);
+
+ // Try available...
+ int64 avail = stream.Available();
+ EXPECT_EQ(ERR_UNEXPECTED, avail);
+
+ // Try reading...
+ char buf[10];
+ int rv = stream.ReadSync(buf, arraysize(buf));
+ EXPECT_EQ(ERR_UNEXPECTED, rv);
+}
+
+TEST_F(FileStreamTest, BasicRead) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ;
+ int rv = stream.OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ int64 total_bytes_avail = stream.Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ char buf[4];
+ rv = stream.ReadSync(buf, arraysize(buf));
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf, rv);
+ }
+ EXPECT_EQ(file_size, total_bytes_read);
+ EXPECT_EQ(kTestData, data_read);
+}
+
+TEST_F(FileStreamTest, AsyncRead) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream.Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 total_bytes_avail = stream.Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream.Read(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf->data(), rv);
+ }
+ EXPECT_EQ(file_size, total_bytes_read);
+ EXPECT_EQ(kTestData, data_read);
+}
+
+TEST_F(FileStreamTest, AsyncRead_EarlyDelete) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream->Read(buf.get(), buf->size(), callback.callback());
+ stream.reset(); // Delete instead of closing it.
+ if (rv < 0) {
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // The callback should not be called if the request is cancelled.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(callback.have_result());
+ } else {
+ EXPECT_EQ(std::string(kTestData, rv), std::string(buf->data(), rv));
+ }
+}
+
+TEST_F(FileStreamTest, BasicRead_FromOffset) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ;
+ int rv = stream.OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ const int64 kOffset = 3;
+ int64 new_offset = stream.SeekSync(FROM_BEGIN, kOffset);
+ EXPECT_EQ(kOffset, new_offset);
+
+ int64 total_bytes_avail = stream.Available();
+ EXPECT_EQ(file_size - kOffset, total_bytes_avail);
+
+ int64 total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ char buf[4];
+ rv = stream.ReadSync(buf, arraysize(buf));
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf, rv);
+ }
+ EXPECT_EQ(file_size - kOffset, total_bytes_read);
+ EXPECT_TRUE(data_read == kTestData + kOffset);
+ EXPECT_EQ(kTestData + kOffset, data_read);
+}
+
+TEST_F(FileStreamTest, AsyncRead_FromOffset) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream.Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ TestInt64CompletionCallback callback64;
+ const int64 kOffset = 3;
+ rv = stream.Seek(FROM_BEGIN, kOffset, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ int64 new_offset = callback64.WaitForResult();
+ EXPECT_EQ(kOffset, new_offset);
+
+ int64 total_bytes_avail = stream.Available();
+ EXPECT_EQ(file_size - kOffset, total_bytes_avail);
+
+ int total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream.Read(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf->data(), rv);
+ }
+ EXPECT_EQ(file_size - kOffset, total_bytes_read);
+ EXPECT_EQ(kTestData + kOffset, data_read);
+}
+
+TEST_F(FileStreamTest, SeekAround) {
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ;
+ int rv = stream.OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ const int64 kOffset = 3;
+ int64 new_offset = stream.SeekSync(FROM_BEGIN, kOffset);
+ EXPECT_EQ(kOffset, new_offset);
+
+ new_offset = stream.SeekSync(FROM_CURRENT, kOffset);
+ EXPECT_EQ(2 * kOffset, new_offset);
+
+ new_offset = stream.SeekSync(FROM_CURRENT, -kOffset);
+ EXPECT_EQ(kOffset, new_offset);
+
+ const int kTestDataLen = arraysize(kTestData) - 1;
+
+ new_offset = stream.SeekSync(FROM_END, -kTestDataLen);
+ EXPECT_EQ(0, new_offset);
+}
+
+TEST_F(FileStreamTest, AsyncSeekAround) {
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_READ;
+ TestCompletionCallback callback;
+ int rv = stream.Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ TestInt64CompletionCallback callback64;
+
+ const int64 kOffset = 3;
+ rv = stream.Seek(FROM_BEGIN, kOffset, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ int64 new_offset = callback64.WaitForResult();
+ EXPECT_EQ(kOffset, new_offset);
+
+ rv = stream.Seek(FROM_CURRENT, kOffset, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ new_offset = callback64.WaitForResult();
+ EXPECT_EQ(2 * kOffset, new_offset);
+
+ rv = stream.Seek(FROM_CURRENT, -kOffset, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ new_offset = callback64.WaitForResult();
+ EXPECT_EQ(kOffset, new_offset);
+
+ const int kTestDataLen = arraysize(kTestData) - 1;
+
+ rv = stream.Seek(FROM_END, -kTestDataLen, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ new_offset = callback64.WaitForResult();
+ EXPECT_EQ(0, new_offset);
+}
+
+TEST_F(FileStreamTest, BasicWrite) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_WRITE;
+ int rv = stream->OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(0, file_size);
+
+ rv = stream->WriteSync(kTestData, kTestDataSize);
+ EXPECT_EQ(kTestDataSize, rv);
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize, file_size);
+}
+
+TEST_F(FileStreamTest, AsyncWrite) {
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream.Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(0, file_size);
+
+ int total_bytes_written = 0;
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ scoped_refptr<DrainableIOBuffer> drainable =
+ new DrainableIOBuffer(buf.get(), buf->size());
+ while (total_bytes_written != kTestDataSize) {
+ rv = stream.Write(
+ drainable.get(), drainable->BytesRemaining(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(0, rv);
+ if (rv <= 0)
+ break;
+ drainable->DidConsume(rv);
+ total_bytes_written += rv;
+ }
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(file_size, total_bytes_written);
+}
+
+TEST_F(FileStreamTest, AsyncWrite_EarlyDelete) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(0, file_size);
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ rv = stream->Write(buf.get(), buf->size(), callback.callback());
+ stream.reset();
+ if (rv < 0) {
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // The callback should not be called if the request is cancelled.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(callback.have_result());
+ } else {
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(file_size, rv);
+ }
+}
+
+TEST_F(FileStreamTest, BasicWrite_FromOffset) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE;
+ int rv = stream->OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize, file_size);
+
+ const int64 kOffset = 0;
+ int64 new_offset = stream->SeekSync(FROM_END, kOffset);
+ EXPECT_EQ(kTestDataSize, new_offset);
+
+ rv = stream->WriteSync(kTestData, kTestDataSize);
+ EXPECT_EQ(kTestDataSize, rv);
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+}
+
+TEST_F(FileStreamTest, AsyncWrite_FromOffset) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ FileStream stream(NULL);
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream.Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ TestInt64CompletionCallback callback64;
+ const int64 kOffset = 0;
+ rv = stream.Seek(FROM_END, kOffset, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ int64 new_offset = callback64.WaitForResult();
+ EXPECT_EQ(kTestDataSize, new_offset);
+
+ int total_bytes_written = 0;
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ scoped_refptr<DrainableIOBuffer> drainable =
+ new DrainableIOBuffer(buf.get(), buf->size());
+ while (total_bytes_written != kTestDataSize) {
+ rv = stream.Write(
+ drainable.get(), drainable->BytesRemaining(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(0, rv);
+ if (rv <= 0)
+ break;
+ drainable->DidConsume(rv);
+ total_bytes_written += rv;
+ }
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(file_size, kTestDataSize * 2);
+}
+
+TEST_F(FileStreamTest, BasicReadWrite) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE;
+ int rv = stream->OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ char buf[4];
+ rv = stream->ReadSync(buf, arraysize(buf));
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf, rv);
+ }
+ EXPECT_EQ(file_size, total_bytes_read);
+ EXPECT_TRUE(data_read == kTestData);
+
+ rv = stream->WriteSync(kTestData, kTestDataSize);
+ EXPECT_EQ(kTestDataSize, rv);
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+}
+
+TEST_F(FileStreamTest, BasicWriteRead) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE;
+ int rv = stream->OpenSync(temp_file_path(), flags);
+ EXPECT_EQ(OK, rv);
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int64 offset = stream->SeekSync(FROM_END, 0);
+ EXPECT_EQ(offset, file_size);
+
+ rv = stream->WriteSync(kTestData, kTestDataSize);
+ EXPECT_EQ(kTestDataSize, rv);
+
+ offset = stream->SeekSync(FROM_BEGIN, 0);
+ EXPECT_EQ(0, offset);
+
+ int64 total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ char buf[4];
+ rv = stream->ReadSync(buf, arraysize(buf));
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf, rv);
+ }
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+ EXPECT_EQ(kTestDataSize * 2, total_bytes_read);
+
+ const std::string kExpectedFileData =
+ std::string(kTestData) + std::string(kTestData);
+ EXPECT_EQ(kExpectedFileData, data_read);
+}
+
+TEST_F(FileStreamTest, BasicAsyncReadWrite) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int64 total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf->data(), rv);
+ }
+ EXPECT_EQ(file_size, total_bytes_read);
+ EXPECT_TRUE(data_read == kTestData);
+
+ int total_bytes_written = 0;
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ scoped_refptr<DrainableIOBuffer> drainable =
+ new DrainableIOBuffer(buf.get(), buf->size());
+ while (total_bytes_written != kTestDataSize) {
+ rv = stream->Write(
+ drainable.get(), drainable->BytesRemaining(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(0, rv);
+ if (rv <= 0)
+ break;
+ drainable->DidConsume(rv);
+ total_bytes_written += rv;
+ }
+
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+}
+
+TEST_F(FileStreamTest, BasicAsyncWriteRead) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ TestInt64CompletionCallback callback64;
+ rv = stream->Seek(FROM_END, 0, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ int64 offset = callback64.WaitForResult();
+ EXPECT_EQ(offset, file_size);
+
+ int total_bytes_written = 0;
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ scoped_refptr<DrainableIOBuffer> drainable =
+ new DrainableIOBuffer(buf.get(), buf->size());
+ while (total_bytes_written != kTestDataSize) {
+ rv = stream->Write(
+ drainable.get(), drainable->BytesRemaining(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(0, rv);
+ if (rv <= 0)
+ break;
+ drainable->DidConsume(rv);
+ total_bytes_written += rv;
+ }
+
+ EXPECT_EQ(kTestDataSize, total_bytes_written);
+
+ rv = stream->Seek(FROM_BEGIN, 0, callback64.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ offset = callback64.WaitForResult();
+ EXPECT_EQ(0, offset);
+
+ int total_bytes_read = 0;
+
+ std::string data_read;
+ for (;;) {
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data_read.append(buf->data(), rv);
+ }
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+
+ EXPECT_EQ(kTestDataSize * 2, total_bytes_read);
+ const std::string kExpectedFileData =
+ std::string(kTestData) + std::string(kTestData);
+ EXPECT_EQ(kExpectedFileData, data_read);
+}
+
+class TestWriteReadCompletionCallback {
+ public:
+ TestWriteReadCompletionCallback(FileStream* stream,
+ int* total_bytes_written,
+ int* total_bytes_read,
+ std::string* data_read)
+ : result_(0),
+ have_result_(false),
+ waiting_for_result_(false),
+ stream_(stream),
+ total_bytes_written_(total_bytes_written),
+ total_bytes_read_(total_bytes_read),
+ data_read_(data_read),
+ callback_(base::Bind(&TestWriteReadCompletionCallback::OnComplete,
+ base::Unretained(this))),
+ test_data_(CreateTestDataBuffer()),
+ drainable_(new DrainableIOBuffer(test_data_.get(), kTestDataSize)) {}
+
+ int WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // auto-reset for next callback
+ return result_;
+ }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ DCHECK_LT(0, result);
+ *total_bytes_written_ += result;
+
+ int rv;
+
+ if (*total_bytes_written_ != kTestDataSize) {
+ // Recurse to finish writing all data.
+ int total_bytes_written = 0, total_bytes_read = 0;
+ std::string data_read;
+ TestWriteReadCompletionCallback callback(
+ stream_, &total_bytes_written, &total_bytes_read, &data_read);
+ rv = stream_->Write(
+ drainable_.get(), drainable_->BytesRemaining(), callback.callback());
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ drainable_->DidConsume(total_bytes_written);
+ *total_bytes_written_ += total_bytes_written;
+ *total_bytes_read_ += total_bytes_read;
+ *data_read_ += data_read;
+ } else { // We're done writing all data. Start reading the data.
+ stream_->SeekSync(FROM_BEGIN, 0);
+
+ TestCompletionCallback callback;
+ for (;;) {
+ scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(4);
+ rv = stream_->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING) {
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ rv = callback.WaitForResult();
+ }
+ EXPECT_LE(0, rv);
+ if (rv <= 0)
+ break;
+ *total_bytes_read_ += rv;
+ data_read_->append(buf->data(), rv);
+ }
+ }
+
+ result_ = *total_bytes_written_;
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ int result_;
+ bool have_result_;
+ bool waiting_for_result_;
+ FileStream* stream_;
+ int* total_bytes_written_;
+ int* total_bytes_read_;
+ std::string* data_read_;
+ const CompletionCallback callback_;
+ scoped_refptr<IOBufferWithSize> test_data_;
+ scoped_refptr<DrainableIOBuffer> drainable_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWriteReadCompletionCallback);
+};
+
+TEST_F(FileStreamTest, AsyncWriteRead) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback open_callback;
+ int rv = stream->Open(temp_file_path(), flags, open_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, open_callback.WaitForResult());
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int64 offset = stream->SeekSync(FROM_END, 0);
+ EXPECT_EQ(offset, file_size);
+
+ int total_bytes_written = 0;
+ int total_bytes_read = 0;
+ std::string data_read;
+ TestWriteReadCompletionCallback callback(stream.get(), &total_bytes_written,
+ &total_bytes_read, &data_read);
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ rv = stream->Write(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(0, rv);
+ EXPECT_EQ(kTestDataSize, total_bytes_written);
+
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+
+ EXPECT_EQ(kTestDataSize * 2, total_bytes_read);
+ const std::string kExpectedFileData =
+ std::string(kTestData) + std::string(kTestData);
+ EXPECT_EQ(kExpectedFileData, data_read);
+}
+
+class TestWriteCloseCompletionCallback {
+ public:
+ TestWriteCloseCompletionCallback(FileStream* stream, int* total_bytes_written)
+ : result_(0),
+ have_result_(false),
+ waiting_for_result_(false),
+ stream_(stream),
+ total_bytes_written_(total_bytes_written),
+ callback_(base::Bind(&TestWriteCloseCompletionCallback::OnComplete,
+ base::Unretained(this))),
+ test_data_(CreateTestDataBuffer()),
+ drainable_(new DrainableIOBuffer(test_data_.get(), kTestDataSize)) {}
+
+ int WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // auto-reset for next callback
+ return result_;
+ }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ DCHECK_LT(0, result);
+ *total_bytes_written_ += result;
+
+ int rv;
+
+ if (*total_bytes_written_ != kTestDataSize) {
+ // Recurse to finish writing all data.
+ int total_bytes_written = 0;
+ TestWriteCloseCompletionCallback callback(stream_, &total_bytes_written);
+ rv = stream_->Write(
+ drainable_.get(), drainable_->BytesRemaining(), callback.callback());
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ drainable_->DidConsume(total_bytes_written);
+ *total_bytes_written_ += total_bytes_written;
+ }
+
+ result_ = *total_bytes_written_;
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ int result_;
+ bool have_result_;
+ bool waiting_for_result_;
+ FileStream* stream_;
+ int* total_bytes_written_;
+ const CompletionCallback callback_;
+ scoped_refptr<IOBufferWithSize> test_data_;
+ scoped_refptr<DrainableIOBuffer> drainable_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWriteCloseCompletionCallback);
+};
+
+TEST_F(FileStreamTest, AsyncWriteClose) {
+ int64 file_size;
+ bool ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback open_callback;
+ int rv = stream->Open(temp_file_path(), flags, open_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, open_callback.WaitForResult());
+
+ int64 total_bytes_avail = stream->Available();
+ EXPECT_EQ(file_size, total_bytes_avail);
+
+ int64 offset = stream->SeekSync(FROM_END, 0);
+ EXPECT_EQ(offset, file_size);
+
+ int total_bytes_written = 0;
+ TestWriteCloseCompletionCallback callback(stream.get(), &total_bytes_written);
+
+ scoped_refptr<IOBufferWithSize> buf = CreateTestDataBuffer();
+ rv = stream->Write(buf.get(), buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ total_bytes_written = callback.WaitForResult();
+ EXPECT_LT(0, total_bytes_written);
+ EXPECT_EQ(kTestDataSize, total_bytes_written);
+
+ stream.reset();
+
+ ok = file_util::GetFileSize(temp_file_path(), &file_size);
+ EXPECT_TRUE(ok);
+ EXPECT_EQ(kTestDataSize * 2, file_size);
+}
+
+// Tests truncating a file.
+TEST_F(FileStreamTest, Truncate) {
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE;
+
+ scoped_ptr<FileStream> write_stream(new FileStream(NULL));
+ ASSERT_EQ(OK, write_stream->OpenSync(temp_file_path(), flags));
+
+ // Write some data to the file.
+ const char test_data[] = "0123456789";
+ write_stream->WriteSync(test_data, arraysize(test_data));
+
+ // Truncate the file.
+ ASSERT_EQ(4, write_stream->Truncate(4));
+
+ // Write again.
+ write_stream->WriteSync(test_data, 4);
+
+ // Close the stream.
+ write_stream.reset();
+
+ // Read in the contents and make sure we get back what we expected.
+ std::string read_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(temp_file_path(), &read_contents));
+
+ EXPECT_EQ("01230123", read_contents);
+}
+
+TEST_F(FileStreamTest, AsyncOpenAndDelete) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback open_callback;
+ int rv = stream->Open(temp_file_path(), flags, open_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Delete the stream without waiting for the open operation to be
+ // complete. Should be safe.
+ stream.reset();
+ // open_callback won't be called.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(open_callback.have_result());
+}
+
+// Verify that async Write() errors are mapped correctly.
+TEST_F(FileStreamTest, AsyncWriteError) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Try passing NULL buffer to Write() and check that it fails.
+ scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(NULL);
+ rv = stream->Write(buf.get(), 1, callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(rv, 0);
+}
+
+// Verify that async Read() errors are mapped correctly.
+TEST_F(FileStreamTest, AsyncReadError) {
+ scoped_ptr<FileStream> stream(new FileStream(NULL));
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ TestCompletionCallback callback;
+ int rv = stream->Open(temp_file_path(), flags, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Try passing NULL buffer to Read() and check that it fails.
+ scoped_refptr<IOBuffer> buf = new WrappedIOBuffer(NULL);
+ rv = stream->Read(buf.get(), 1, callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_LT(rv, 0);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/file_stream_whence.h b/chromium/net/base/file_stream_whence.h
new file mode 100644
index 00000000000..a962341f93d
--- /dev/null
+++ b/chromium/net/base/file_stream_whence.h
@@ -0,0 +1,20 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_FILE_STREAM_WHENCE_H_
+#define NET_BASE_FILE_STREAM_WHENCE_H_
+
+namespace net {
+
+// TODO(darin): Move this to a more generic location.
+// This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux.
+enum Whence {
+ FROM_BEGIN = 0,
+ FROM_CURRENT = 1,
+ FROM_END = 2
+};
+
+} // namespace net
+
+#endif // NET_BASE_FILE_STREAM_WHENCE_H_
diff --git a/chromium/net/base/filter.cc b/chromium/net/base/filter.cc
new file mode 100644
index 00000000000..36d301426f9
--- /dev/null
+++ b/chromium/net/base/filter.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 2011 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/base/filter.h"
+
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "net/base/gzip_filter.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_util.h"
+#include "net/base/sdch_filter.h"
+
+namespace {
+
+// Filter types (using canonical lower case only):
+const char kDeflate[] = "deflate";
+const char kGZip[] = "gzip";
+const char kXGZip[] = "x-gzip";
+const char kSdch[] = "sdch";
+// compress and x-compress are currently not supported. If we decide to support
+// them, we'll need the same mime type compatibility hack we have for gzip. For
+// more information, see Firefox's nsHttpChannel::ProcessNormal.
+const char kCompress[] = "compress";
+const char kXCompress[] = "x-compress";
+const char kIdentity[] = "identity";
+const char kUncompressed[] = "uncompressed";
+
+// Mime types:
+const char kApplicationXGzip[] = "application/x-gzip";
+const char kApplicationGzip[] = "application/gzip";
+const char kApplicationXGunzip[] = "application/x-gunzip";
+const char kApplicationXCompress[] = "application/x-compress";
+const char kApplicationCompress[] = "application/compress";
+const char kTextHtml[] = "text/html";
+
+// Buffer size allocated when de-compressing data.
+const int kFilterBufSize = 32 * 1024;
+
+} // namespace
+
+namespace net {
+
+FilterContext::~FilterContext() {
+}
+
+Filter::~Filter() {}
+
+// static
+Filter* Filter::Factory(const std::vector<FilterType>& filter_types,
+ const FilterContext& filter_context) {
+ if (filter_types.empty())
+ return NULL;
+
+ Filter* filter_list = NULL; // Linked list of filters.
+ for (size_t i = 0; i < filter_types.size(); i++) {
+ filter_list = PrependNewFilter(filter_types[i], filter_context,
+ kFilterBufSize, filter_list);
+ if (!filter_list)
+ return NULL;
+ }
+ return filter_list;
+}
+
+// static
+Filter* Filter::GZipFactory() {
+ return InitGZipFilter(FILTER_TYPE_GZIP, kFilterBufSize);
+}
+
+// static
+Filter* Filter::FactoryForTests(const std::vector<FilterType>& filter_types,
+ const FilterContext& filter_context,
+ int buffer_size) {
+ if (filter_types.empty())
+ return NULL;
+
+ Filter* filter_list = NULL; // Linked list of filters.
+ for (size_t i = 0; i < filter_types.size(); i++) {
+ filter_list = PrependNewFilter(filter_types[i], filter_context,
+ buffer_size, filter_list);
+ if (!filter_list)
+ return NULL;
+ }
+ return filter_list;
+}
+
+Filter::FilterStatus Filter::ReadData(char* dest_buffer, int* dest_len) {
+ const int dest_buffer_capacity = *dest_len;
+ if (last_status_ == FILTER_ERROR)
+ return last_status_;
+ if (!next_filter_.get())
+ return last_status_ = ReadFilteredData(dest_buffer, dest_len);
+ if (last_status_ == FILTER_NEED_MORE_DATA && !stream_data_len())
+ return next_filter_->ReadData(dest_buffer, dest_len);
+
+ do {
+ if (next_filter_->last_status() == FILTER_NEED_MORE_DATA) {
+ PushDataIntoNextFilter();
+ if (FILTER_ERROR == last_status_)
+ return FILTER_ERROR;
+ }
+ *dest_len = dest_buffer_capacity; // Reset the input/output parameter.
+ next_filter_->ReadData(dest_buffer, dest_len);
+ if (FILTER_NEED_MORE_DATA == last_status_)
+ return next_filter_->last_status();
+
+ // In the case where this filter has data internally, and is indicating such
+ // with a last_status_ of FILTER_OK, but at the same time the next filter in
+ // the chain indicated it FILTER_NEED_MORE_DATA, we have to be cautious
+ // about confusing the caller. The API confusion can appear if we return
+ // FILTER_OK (suggesting we have more data in aggregate), but yet we don't
+ // populate our output buffer. When that is the case, we need to
+ // alternately call our filter element, and the next_filter element until we
+ // get out of this state (by pumping data into the next filter until it
+ // outputs data, or it runs out of data and reports that it NEED_MORE_DATA.)
+ } while (FILTER_OK == last_status_ &&
+ FILTER_NEED_MORE_DATA == next_filter_->last_status() &&
+ 0 == *dest_len);
+
+ if (next_filter_->last_status() == FILTER_ERROR)
+ return FILTER_ERROR;
+ return FILTER_OK;
+}
+
+bool Filter::FlushStreamBuffer(int stream_data_len) {
+ DCHECK_LE(stream_data_len, stream_buffer_size_);
+ if (stream_data_len <= 0 || stream_data_len > stream_buffer_size_)
+ return false;
+
+ DCHECK(stream_buffer());
+ // Bail out if there is more data in the stream buffer to be filtered.
+ if (!stream_buffer() || stream_data_len_)
+ return false;
+
+ next_stream_data_ = stream_buffer()->data();
+ stream_data_len_ = stream_data_len;
+ return true;
+}
+
+// static
+Filter::FilterType Filter::ConvertEncodingToType(
+ const std::string& filter_type) {
+ FilterType type_id;
+ if (LowerCaseEqualsASCII(filter_type, kDeflate)) {
+ type_id = FILTER_TYPE_DEFLATE;
+ } else if (LowerCaseEqualsASCII(filter_type, kGZip) ||
+ LowerCaseEqualsASCII(filter_type, kXGZip)) {
+ type_id = FILTER_TYPE_GZIP;
+ } else if (LowerCaseEqualsASCII(filter_type, kSdch)) {
+ type_id = FILTER_TYPE_SDCH;
+ } else {
+ // Note we also consider "identity" and "uncompressed" UNSUPPORTED as
+ // filter should be disabled in such cases.
+ type_id = FILTER_TYPE_UNSUPPORTED;
+ }
+ return type_id;
+}
+
+// static
+void Filter::FixupEncodingTypes(
+ const FilterContext& filter_context,
+ std::vector<FilterType>* encoding_types) {
+ std::string mime_type;
+ bool success = filter_context.GetMimeType(&mime_type);
+ DCHECK(success || mime_type.empty());
+
+ if ((1 == encoding_types->size()) &&
+ (FILTER_TYPE_GZIP == encoding_types->front())) {
+ if (LowerCaseEqualsASCII(mime_type, kApplicationXGzip) ||
+ LowerCaseEqualsASCII(mime_type, kApplicationGzip) ||
+ LowerCaseEqualsASCII(mime_type, kApplicationXGunzip))
+ // The server has told us that it sent us gziped content with a gzip
+ // content encoding. Sadly, Apache mistakenly sets these headers for all
+ // .gz files. We match Firefox's nsHttpChannel::ProcessNormal and ignore
+ // the Content-Encoding here.
+ encoding_types->clear();
+
+ GURL url;
+ success = filter_context.GetURL(&url);
+ DCHECK(success);
+ base::FilePath filename =
+ base::FilePath().AppendASCII(url.ExtractFileName());
+ base::FilePath::StringType extension = filename.Extension();
+
+ if (filter_context.IsDownload()) {
+ // We don't want to decompress gzipped files when the user explicitly
+ // asks to download them.
+ // For the case of svgz files, we use the extension to distinguish
+ // between svgz files and svg files compressed with gzip by the server.
+ // When viewing a .svgz file, we need to uncompress it, but we don't
+ // want to do that when downloading.
+ // See Firefox's nonDecodableExtensions in nsExternalHelperAppService.cpp
+ if (EndsWith(extension, FILE_PATH_LITERAL(".gz"), false) ||
+ LowerCaseEqualsASCII(extension, ".tgz") ||
+ LowerCaseEqualsASCII(extension, ".svgz"))
+ encoding_types->clear();
+ } else {
+ // When the user does not explicitly ask to download a file, if we get a
+ // supported mime type, then we attempt to decompress in order to view it.
+ // However, if it's not a supported mime type, then we will attempt to
+ // download it, and in that case, don't decompress .gz/.tgz files.
+ if ((EndsWith(extension, FILE_PATH_LITERAL(".gz"), false) ||
+ LowerCaseEqualsASCII(extension, ".tgz")) &&
+ !IsSupportedMimeType(mime_type))
+ encoding_types->clear();
+ }
+ }
+
+ // If the request was for SDCH content, then we might need additional fixups.
+ if (!filter_context.IsSdchResponse()) {
+ // It was not an SDCH request, so we'll just record stats.
+ if (1 < encoding_types->size()) {
+ // Multiple filters were intended to only be used for SDCH (thus far!)
+ SdchManager::SdchErrorRecovery(
+ SdchManager::MULTIENCODING_FOR_NON_SDCH_REQUEST);
+ }
+ if ((1 == encoding_types->size()) &&
+ (FILTER_TYPE_SDCH == encoding_types->front())) {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST);
+ }
+ return;
+ }
+
+ // The request was tagged as an SDCH request, which means the server supplied
+ // a dictionary, and we advertised it in the request. Some proxies will do
+ // very strange things to the request, or the response, so we have to handle
+ // them gracefully.
+
+ // If content encoding included SDCH, then everything is "relatively" fine.
+ if (!encoding_types->empty() &&
+ (FILTER_TYPE_SDCH == encoding_types->front())) {
+ // Some proxies (found currently in Argentina) strip the Content-Encoding
+ // text from "sdch,gzip" to a mere "sdch" without modifying the compressed
+ // payload. To handle this gracefully, we simulate the "probably" deleted
+ // ",gzip" by appending a tentative gzip decode, which will default to a
+ // no-op pass through filter if it doesn't get gzip headers where expected.
+ if (1 == encoding_types->size()) {
+ encoding_types->push_back(FILTER_TYPE_GZIP_HELPING_SDCH);
+ SdchManager::SdchErrorRecovery(
+ SdchManager::OPTIONAL_GUNZIP_ENCODING_ADDED);
+ }
+ return;
+ }
+
+ // There are now several cases to handle for an SDCH request. Foremost, if
+ // the outbound request was stripped so as not to advertise support for
+ // encodings, we might get back content with no encoding, or (for example)
+ // just gzip. We have to be sure that any changes we make allow for such
+ // minimal coding to work. That issue is why we use TENTATIVE filters if we
+ // add any, as those filters sniff the content, and act as pass-through
+ // filters if headers are not found.
+
+ // If the outbound GET is not modified, then the server will generally try to
+ // send us SDCH encoded content. As that content returns, there are several
+ // corruptions of the header "content-encoding" that proxies may perform (and
+ // have been detected in the wild). We already dealt with the a honest
+ // content encoding of "sdch,gzip" being corrupted into "sdch" with on change
+ // of the actual content. Another common corruption is to either disscard
+ // the accurate content encoding, or to replace it with gzip only (again, with
+ // no change in actual content). The last observed corruption it to actually
+ // change the content, such as by re-gzipping it, and that may happen along
+ // with corruption of the stated content encoding (wow!).
+
+ // The one unresolved failure mode comes when we advertise a dictionary, and
+ // the server tries to *send* a gzipped file (not gzip encode content), and
+ // then we could do a gzip decode :-(. Since SDCH is only (currently)
+ // supported server side on paths that only send HTML content, this mode has
+ // never surfaced in the wild (and is unlikely to).
+ // We will gather a lot of stats as we perform the fixups
+ if (StartsWithASCII(mime_type, kTextHtml, false)) {
+ // Suspicious case: Advertised dictionary, but server didn't use sdch, and
+ // we're HTML tagged.
+ if (encoding_types->empty()) {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::ADDED_CONTENT_ENCODING);
+ } else if (1 == encoding_types->size()) {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::FIXED_CONTENT_ENCODING);
+ } else {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::FIXED_CONTENT_ENCODINGS);
+ }
+ } else {
+ // Remarkable case!?! We advertised an SDCH dictionary, content-encoding
+ // was not marked for SDCH processing: Why did the server suggest an SDCH
+ // dictionary in the first place??. Also, the content isn't
+ // tagged as HTML, despite the fact that SDCH encoding is mostly likely for
+ // HTML: Did some anti-virus system strip this tag (sometimes they strip
+ // accept-encoding headers on the request)?? Does the content encoding not
+ // start with "text/html" for some other reason?? We'll report this as a
+ // fixup to a binary file, but it probably really is text/html (some how).
+ if (encoding_types->empty()) {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::BINARY_ADDED_CONTENT_ENCODING);
+ } else if (1 == encoding_types->size()) {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::BINARY_FIXED_CONTENT_ENCODING);
+ } else {
+ SdchManager::SdchErrorRecovery(
+ SdchManager::BINARY_FIXED_CONTENT_ENCODINGS);
+ }
+ }
+
+ // Leave the existing encoding type to be processed first, and add our
+ // tentative decodings to be done afterwards. Vodaphone UK reportedyl will
+ // perform a second layer of gzip encoding atop the server's sdch,gzip
+ // encoding, and then claim that the content encoding is a mere gzip. As a
+ // result we'll need (in that case) to do the gunzip, plus our tentative
+ // gunzip and tentative SDCH decoding.
+ // This approach nicely handles the empty() list as well, and should work with
+ // other (as yet undiscovered) proxies the choose to re-compressed with some
+ // other encoding (such as bzip2, etc.).
+ encoding_types->insert(encoding_types->begin(),
+ FILTER_TYPE_GZIP_HELPING_SDCH);
+ encoding_types->insert(encoding_types->begin(), FILTER_TYPE_SDCH_POSSIBLE);
+ return;
+}
+
+Filter::Filter()
+ : stream_buffer_(NULL),
+ stream_buffer_size_(0),
+ next_stream_data_(NULL),
+ stream_data_len_(0),
+ last_status_(FILTER_NEED_MORE_DATA) {}
+
+Filter::FilterStatus Filter::CopyOut(char* dest_buffer, int* dest_len) {
+ int out_len;
+ int input_len = *dest_len;
+ *dest_len = 0;
+
+ if (0 == stream_data_len_)
+ return Filter::FILTER_NEED_MORE_DATA;
+
+ out_len = std::min(input_len, stream_data_len_);
+ memcpy(dest_buffer, next_stream_data_, out_len);
+ *dest_len += out_len;
+ stream_data_len_ -= out_len;
+ if (0 == stream_data_len_) {
+ next_stream_data_ = NULL;
+ return Filter::FILTER_NEED_MORE_DATA;
+ } else {
+ next_stream_data_ += out_len;
+ return Filter::FILTER_OK;
+ }
+}
+
+// static
+Filter* Filter::InitGZipFilter(FilterType type_id, int buffer_size) {
+ scoped_ptr<GZipFilter> gz_filter(new GZipFilter());
+ gz_filter->InitBuffer(buffer_size);
+ return gz_filter->InitDecoding(type_id) ? gz_filter.release() : NULL;
+}
+
+// static
+Filter* Filter::InitSdchFilter(FilterType type_id,
+ const FilterContext& filter_context,
+ int buffer_size) {
+ scoped_ptr<SdchFilter> sdch_filter(new SdchFilter(filter_context));
+ sdch_filter->InitBuffer(buffer_size);
+ return sdch_filter->InitDecoding(type_id) ? sdch_filter.release() : NULL;
+}
+
+// static
+Filter* Filter::PrependNewFilter(FilterType type_id,
+ const FilterContext& filter_context,
+ int buffer_size,
+ Filter* filter_list) {
+ scoped_ptr<Filter> first_filter; // Soon to be start of chain.
+ switch (type_id) {
+ case FILTER_TYPE_GZIP_HELPING_SDCH:
+ case FILTER_TYPE_DEFLATE:
+ case FILTER_TYPE_GZIP:
+ first_filter.reset(InitGZipFilter(type_id, buffer_size));
+ break;
+ case FILTER_TYPE_SDCH:
+ case FILTER_TYPE_SDCH_POSSIBLE:
+ first_filter.reset(InitSdchFilter(type_id, filter_context, buffer_size));
+ break;
+ default:
+ break;
+ }
+
+ if (!first_filter.get())
+ return NULL;
+
+ first_filter->next_filter_.reset(filter_list);
+ return first_filter.release();
+}
+
+void Filter::InitBuffer(int buffer_size) {
+ DCHECK(!stream_buffer());
+ DCHECK_GT(buffer_size, 0);
+ stream_buffer_ = new IOBuffer(buffer_size);
+ stream_buffer_size_ = buffer_size;
+}
+
+void Filter::PushDataIntoNextFilter() {
+ IOBuffer* next_buffer = next_filter_->stream_buffer();
+ int next_size = next_filter_->stream_buffer_size();
+ last_status_ = ReadFilteredData(next_buffer->data(), &next_size);
+ if (FILTER_ERROR != last_status_)
+ next_filter_->FlushStreamBuffer(next_size);
+}
+
+} // namespace net
diff --git a/chromium/net/base/filter.h b/chromium/net/base/filter.h
new file mode 100644
index 00000000000..9511511f778
--- /dev/null
+++ b/chromium/net/base/filter.h
@@ -0,0 +1,276 @@
+// Copyright (c) 2011 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.
+//
+// Filter performs filtering on data streams. Sample usage:
+//
+// IStream* pre_filter_source;
+// ...
+// Filter* filter = Filter::Factory(filter_type, size);
+// int pre_filter_data_len = filter->stream_buffer_size();
+// pre_filter_source->read(filter->stream_buffer(), pre_filter_data_len);
+//
+// filter->FlushStreamBuffer(pre_filter_data_len);
+//
+// char post_filter_buf[kBufferSize];
+// int post_filter_data_len = kBufferSize;
+// filter->ReadFilteredData(post_filter_buf, &post_filter_data_len);
+//
+// To filter a data stream, the caller first gets filter's stream_buffer_
+// through its accessor and fills in stream_buffer_ with pre-filter data, next
+// calls FlushStreamBuffer to notify Filter, then calls ReadFilteredData
+// repeatedly to get all the filtered data. After all data have been fitlered
+// and read out, the caller may fill in stream_buffer_ again. This
+// WriteBuffer-Flush-Read cycle is repeated until reaching the end of data
+// stream.
+//
+// The lifetime of a Filter instance is completely controlled by its caller.
+
+#ifndef NET_BASE_FILTER_H__
+#define NET_BASE_FILTER_H__
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class IOBuffer;
+
+//------------------------------------------------------------------------------
+// Define an interface class that allows access to contextual information
+// supplied by the owner of this filter. In the case where there are a chain of
+// filters, there is only one owner of all the chained filters, and that context
+// is passed to the constructor of all those filters. To be clear, the context
+// does NOT reflect the position in a chain, or the fact that there are prior
+// or later filters in a chain.
+class NET_EXPORT_PRIVATE FilterContext {
+ public:
+ // Enum to control what histograms are emitted near end-of-life of this
+ // instance.
+ enum StatisticSelector {
+ SDCH_DECODE,
+ SDCH_PASSTHROUGH,
+ SDCH_EXPERIMENT_DECODE,
+ SDCH_EXPERIMENT_HOLDBACK,
+ };
+
+ virtual ~FilterContext();
+
+ // What mime type was specified in the header for this data?
+ // Only makes senses for some types of contexts, and returns false
+ // when not applicable.
+ virtual bool GetMimeType(std::string* mime_type) const = 0;
+
+ // What URL was used to access this data?
+ // Return false if gurl is not present.
+ virtual bool GetURL(GURL* gurl) const = 0;
+
+ // When was this data requested from a server?
+ virtual base::Time GetRequestTime() const = 0;
+
+ // Is data supplied from cache, or fresh across the net?
+ virtual bool IsCachedContent() const = 0;
+
+ // Is this a download?
+ virtual bool IsDownload() const = 0;
+
+ // Was this data flagged as a response to a request with an SDCH dictionary?
+ virtual bool IsSdchResponse() const = 0;
+
+ // How many bytes were read from the net or cache so far (and potentially
+ // pushed into a filter for processing)?
+ virtual int64 GetByteReadCount() const = 0;
+
+ // What response code was received with the associated network transaction?
+ // For example: 200 is ok. 4xx are error codes. etc.
+ virtual int GetResponseCode() const = 0;
+
+ // The following method forces the context to emit a specific set of
+ // statistics as selected by the argument.
+ virtual void RecordPacketStats(StatisticSelector statistic) const = 0;
+};
+
+//------------------------------------------------------------------------------
+class NET_EXPORT_PRIVATE Filter {
+ public:
+ // Return values of function ReadFilteredData.
+ enum FilterStatus {
+ // Read filtered data successfully
+ FILTER_OK,
+ // Read filtered data successfully, and the data in the buffer has been
+ // consumed by the filter, but more data is needed in order to continue
+ // filtering. At this point, the caller is free to reuse the filter
+ // buffer to provide more data.
+ FILTER_NEED_MORE_DATA,
+ // Read filtered data successfully, and filter reaches the end of the data
+ // stream.
+ FILTER_DONE,
+ // There is an error during filtering.
+ FILTER_ERROR
+ };
+
+ // Specifies type of filters that can be created.
+ enum FilterType {
+ FILTER_TYPE_DEFLATE,
+ FILTER_TYPE_GZIP,
+ FILTER_TYPE_GZIP_HELPING_SDCH, // Gzip possible, but pass through allowed.
+ FILTER_TYPE_SDCH,
+ FILTER_TYPE_SDCH_POSSIBLE, // Sdch possible, but pass through allowed.
+ FILTER_TYPE_UNSUPPORTED,
+ };
+
+ virtual ~Filter();
+
+ // Creates a Filter object.
+ // Parameters: Filter_types specifies the type of filter created;
+ // filter_context allows filters to acquire additional details needed for
+ // construction and operation, such as a specification of requisite input
+ // buffer size.
+ // If success, the function returns the pointer to the Filter object created.
+ // If failed or a filter is not needed, the function returns NULL.
+ //
+ // Note: filter_types is an array of filter types (content encoding types as
+ // provided in an HTTP header), which will be chained together serially to do
+ // successive filtering of data. The types in the vector are ordered based on
+ // encoding order, and the filters are chained to operate in the reverse
+ // (decoding) order. For example, types[0] = FILTER_TYPE_SDCH,
+ // types[1] = FILTER_TYPE_GZIP will cause data to first be gunzip filtered,
+ // and the resulting output from that filter will be sdch decoded.
+ static Filter* Factory(const std::vector<FilterType>& filter_types,
+ const FilterContext& filter_context);
+
+ // A simpler version of Factory() which creates a single, unchained
+ // Filter of type FILTER_TYPE_GZIP, or NULL if the filter could not be
+ // initialized.
+ static Filter* GZipFactory();
+
+ // External call to obtain data from this filter chain. If ther is no
+ // next_filter_, then it obtains data from this specific filter.
+ FilterStatus ReadData(char* dest_buffer, int* dest_len);
+
+ // Returns a pointer to the stream_buffer_.
+ IOBuffer* stream_buffer() const { return stream_buffer_.get(); }
+
+ // Returns the maximum size of stream_buffer_ in number of chars.
+ int stream_buffer_size() const { return stream_buffer_size_; }
+
+ // Returns the total number of chars remaining in stream_buffer_ to be
+ // filtered.
+ //
+ // If the function returns 0 then all data has been filtered, and the caller
+ // is safe to copy new data into stream_buffer_.
+ int stream_data_len() const { return stream_data_len_; }
+
+ // Flushes stream_buffer_ for next round of filtering. After copying data to
+ // stream_buffer_, the caller should call this function to notify Filter to
+ // start filtering. Then after this function is called, the caller can get
+ // post-filtered data using ReadFilteredData. The caller must not write to
+ // stream_buffer_ and call this function again before stream_buffer_ is
+ // emptied out by ReadFilteredData.
+ //
+ // The input stream_data_len is the length (in number of chars) of valid
+ // data in stream_buffer_. It can not be greater than stream_buffer_size_.
+ // The function returns true if success, and false otherwise.
+ bool FlushStreamBuffer(int stream_data_len);
+
+ // Translate the text of a filter name (from Content-Encoding header) into a
+ // FilterType.
+ static FilterType ConvertEncodingToType(const std::string& filter_type);
+
+ // Given a array of encoding_types, try to do some error recovery adjustment
+ // to the list. This includes handling known bugs in the Apache server (where
+ // redundant gzip encoding is specified), as well as issues regarding SDCH
+ // encoding, where various proxies and anti-virus products modify or strip the
+ // encodings. These fixups require context, which includes whether this
+ // response was made to an SDCH request (i.e., an available dictionary was
+ // advertised in the GET), as well as the mime type of the content.
+ static void FixupEncodingTypes(const FilterContext& filter_context,
+ std::vector<FilterType>* encoding_types);
+
+ protected:
+ friend class GZipUnitTest;
+ friend class SdchFilterChainingTest;
+
+ Filter();
+
+ // Filters the data stored in stream_buffer_ and writes the output into the
+ // dest_buffer passed in.
+ //
+ // Upon entry, *dest_len is the total size (in number of chars) of the
+ // destination buffer. Upon exit, *dest_len is the actual number of chars
+ // written into the destination buffer.
+ //
+ // This function will fail if there is no pre-filter data in the
+ // stream_buffer_. On the other hand, *dest_len can be 0 upon successful
+ // return. For example, a decoding filter may process some pre-filter data
+ // but not produce output yet.
+ virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len) = 0;
+
+ // Copy pre-filter data directly to destination buffer without decoding.
+ FilterStatus CopyOut(char* dest_buffer, int* dest_len);
+
+ FilterStatus last_status() const { return last_status_; }
+
+ // Buffer to hold the data to be filtered (the input queue).
+ scoped_refptr<IOBuffer> stream_buffer_;
+
+ // Maximum size of stream_buffer_ in number of chars.
+ int stream_buffer_size_;
+
+ // Pointer to the next data in stream_buffer_ to be filtered.
+ char* next_stream_data_;
+
+ // Total number of remaining chars in stream_buffer_ to be filtered.
+ int stream_data_len_;
+
+ private:
+ // Allocates and initializes stream_buffer_ and stream_buffer_size_.
+ void InitBuffer(int size);
+
+ // A factory helper for creating filters for within a chain of potentially
+ // multiple encodings. If a chain of filters is created, then this may be
+ // called multiple times during the filter creation process. In most simple
+ // cases, this is only called once. Returns NULL and cleans up (deleting
+ // filter_list) if a new filter can't be constructed.
+ static Filter* PrependNewFilter(FilterType type_id,
+ const FilterContext& filter_context,
+ int buffer_size,
+ Filter* filter_list);
+
+ // Helper methods for PrependNewFilter. If initialization is successful,
+ // they return a fully initialized Filter. Otherwise, return NULL.
+ static Filter* InitGZipFilter(FilterType type_id, int buffer_size);
+ static Filter* InitSdchFilter(FilterType type_id,
+ const FilterContext& filter_context,
+ int buffer_size);
+
+ // Helper function to empty our output into the next filter's input.
+ void PushDataIntoNextFilter();
+
+ // Constructs a filter with an internal buffer of the given size.
+ // Only meant to be called by unit tests that need to control the buffer size.
+ static Filter* FactoryForTests(const std::vector<FilterType>& filter_types,
+ const FilterContext& filter_context,
+ int buffer_size);
+
+ // An optional filter to process output from this filter.
+ scoped_ptr<Filter> next_filter_;
+ // Remember what status or local filter last returned so we can better handle
+ // chained filters.
+ FilterStatus last_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(Filter);
+};
+
+} // namespace net
+
+#endif // NET_BASE_FILTER_H__
diff --git a/chromium/net/base/filter_unittest.cc b/chromium/net/base/filter_unittest.cc
new file mode 100644
index 00000000000..73bde73810d
--- /dev/null
+++ b/chromium/net/base/filter_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 2011 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/base/filter.h"
+#include "net/base/mock_filter_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class FilterTest : public testing::Test {
+};
+
+TEST(FilterTest, ContentTypeId) {
+ // Check for basic translation of Content-Encoding, including case variations.
+ EXPECT_EQ(Filter::FILTER_TYPE_DEFLATE,
+ Filter::ConvertEncodingToType("deflate"));
+ EXPECT_EQ(Filter::FILTER_TYPE_DEFLATE,
+ Filter::ConvertEncodingToType("deflAte"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("GzIp"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("x-gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("X-GzIp"));
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH,
+ Filter::ConvertEncodingToType("sdch"));
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH,
+ Filter::ConvertEncodingToType("sDcH"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("weird"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("strange"));
+}
+
+// Check various fixups that modify content encoding lists.
+TEST(FilterTest, ApacheGzip) {
+ MockFilterContext filter_context;
+ filter_context.SetSdchResponse(false);
+
+ // Check that redundant gzip mime type removes only solo gzip encoding.
+ const std::string kGzipMime1("application/x-gzip");
+ const std::string kGzipMime2("application/gzip");
+ const std::string kGzipMime3("application/x-gunzip");
+ std::vector<Filter::FilterType> encoding_types;
+
+ // First show it removes the gzip, given any gzip style mime type.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType(kGzipMime1);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType(kGzipMime2);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType(kGzipMime3);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ // Check to be sure it doesn't remove everything when it has such a type.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_SDCH);
+ filter_context.SetMimeType(kGzipMime1);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH, encoding_types.front());
+
+ // Check to be sure that gzip can survive with other mime types.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType("other/mime");
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+}
+
+TEST(FilterTest, SdchEncoding) {
+ // Handle content encodings including SDCH.
+ const std::string kTextHtmlMime("text/html");
+ MockFilterContext filter_context;
+ filter_context.SetSdchResponse(true);
+
+ std::vector<Filter::FilterType> encoding_types;
+
+ // Check for most common encoding, and verify it survives unchanged.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_SDCH);
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType(kTextHtmlMime);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types[1]);
+
+ // Unchanged even with other mime types.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_SDCH);
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetMimeType("other/type");
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types[1]);
+
+ // Solo SDCH is extended to include optional gunzip.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_SDCH);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP_HELPING_SDCH, encoding_types[1]);
+}
+
+TEST(FilterTest, MissingSdchEncoding) {
+ // Handle interesting case where entire SDCH encoding assertion "got lost."
+ const std::string kTextHtmlMime("text/html");
+ MockFilterContext filter_context;
+ filter_context.SetSdchResponse(true);
+
+ std::vector<Filter::FilterType> encoding_types;
+
+ // Loss of encoding, but it was an SDCH response with html type.
+ encoding_types.clear();
+ filter_context.SetMimeType(kTextHtmlMime);
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH_POSSIBLE, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP_HELPING_SDCH, encoding_types[1]);
+
+ // Loss of encoding, but it was an SDCH response with a prefix that says it
+ // was an html type. Note that it *should* be the case that a precise match
+ // with "text/html" we be collected by GetMimeType() and passed in, but we
+ // coded the fixup defensively (scanning for a prefix of "text/html", so this
+ // is an example which could survive such confusion in the caller).
+ encoding_types.clear();
+ filter_context.SetMimeType("text/html; charset=UTF-8");
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH_POSSIBLE, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP_HELPING_SDCH, encoding_types[1]);
+
+ // No encoding, but it was an SDCH response with non-html type.
+ encoding_types.clear();
+ filter_context.SetMimeType("other/mime");
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(2U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH_POSSIBLE, encoding_types[0]);
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP_HELPING_SDCH, encoding_types[1]);
+}
+
+TEST(FilterTest, Svgz) {
+ MockFilterContext filter_context;
+
+ // Check that svgz files are only decompressed when not downloading.
+ const std::string kSvgzMime("image/svg+xml");
+ const std::string kSvgzUrl("http://ignore.com/foo.svgz");
+ const std::string kSvgUrl("http://ignore.com/foo.svg");
+ std::vector<Filter::FilterType> encoding_types;
+
+ // Test svgz extension
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kSvgzMime);
+ filter_context.SetURL(GURL(kSvgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kSvgzMime);
+ filter_context.SetURL(GURL(kSvgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ // Test svg extension
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kSvgzMime);
+ filter_context.SetURL(GURL(kSvgUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kSvgzMime);
+ filter_context.SetURL(GURL(kSvgUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+}
+
+TEST(FilterTest, UnsupportedMimeGzip) {
+ // From issue 8170 - handling files with Content-Encoding: x-gzip
+ MockFilterContext filter_context;
+ std::vector<Filter::FilterType> encoding_types;
+ const std::string kTarMime("application/x-tar");
+ const std::string kCpioMime("application/x-cpio");
+ const std::string kTarUrl("http://ignore.com/foo.tar");
+ const std::string kTargzUrl("http://ignore.com/foo.tar.gz");
+ const std::string kTgzUrl("http://ignore.com/foo.tgz");
+ const std::string kBadTgzUrl("http://ignore.com/foo.targz");
+ const std::string kUrl("http://ignore.com/foo");
+
+ // Firefox 3 does not decompress when we have unsupported mime types for
+ // certain filenames.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kTargzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kTgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kCpioMime);
+ filter_context.SetURL(GURL(kTgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ // Same behavior for downloads.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kCpioMime);
+ filter_context.SetURL(GURL(kTgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+
+ // Unsupported mime type with wrong file name, decompressed.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kTarUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kBadTgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ // Same behavior for downloads.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kTarMime);
+ filter_context.SetURL(GURL(kBadTgzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+}
+
+TEST(FilterTest, SupportedMimeGzip) {
+ // From issue 16430 - Files with supported mime types should be decompressed,
+ // even though these files end in .gz/.tgz.
+ MockFilterContext filter_context;
+ std::vector<Filter::FilterType> encoding_types;
+ const std::string kGzUrl("http://ignore.com/foo.gz");
+ const std::string kUrl("http://ignore.com/foo");
+ const std::string kHtmlMime("text/html");
+ const std::string kJavascriptMime("text/javascript");
+
+ // For files that does not end in .gz/.tgz, we always decompress.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kHtmlMime);
+ filter_context.SetURL(GURL(kUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kHtmlMime);
+ filter_context.SetURL(GURL(kUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ // And also decompress files that end in .gz/.tgz.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kHtmlMime);
+ filter_context.SetURL(GURL(kGzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(false);
+ filter_context.SetMimeType(kJavascriptMime);
+ filter_context.SetURL(GURL(kGzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ ASSERT_EQ(1U, encoding_types.size());
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP, encoding_types.front());
+
+ // Except on downloads, where they just get saved.
+ encoding_types.clear();
+ encoding_types.push_back(Filter::FILTER_TYPE_GZIP);
+ filter_context.SetDownload(true);
+ filter_context.SetMimeType(kHtmlMime);
+ filter_context.SetURL(GURL(kGzUrl));
+ Filter::FixupEncodingTypes(filter_context, &encoding_types);
+ EXPECT_TRUE(encoding_types.empty());
+}
+
+} // namespace net
diff --git a/chromium/net/base/gzip_filter.cc b/chromium/net/base/gzip_filter.cc
new file mode 100644
index 00000000000..2d02bce7a01
--- /dev/null
+++ b/chromium/net/base/gzip_filter.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2011 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/base/gzip_filter.h"
+
+#include "base/logging.h"
+#include "net/base/gzip_header.h"
+#include "third_party/zlib/zlib.h"
+
+namespace net {
+
+GZipFilter::GZipFilter()
+ : decoding_status_(DECODING_UNINITIALIZED),
+ decoding_mode_(DECODE_MODE_UNKNOWN),
+ gzip_header_status_(GZIP_CHECK_HEADER_IN_PROGRESS),
+ zlib_header_added_(false),
+ gzip_footer_bytes_(0),
+ possible_sdch_pass_through_(false) {
+}
+
+GZipFilter::~GZipFilter() {
+ if (decoding_status_ != DECODING_UNINITIALIZED) {
+ inflateEnd(zlib_stream_.get());
+ }
+}
+
+bool GZipFilter::InitDecoding(Filter::FilterType filter_type) {
+ if (decoding_status_ != DECODING_UNINITIALIZED)
+ return false;
+
+ // Initialize zlib control block
+ zlib_stream_.reset(new z_stream);
+ if (!zlib_stream_.get())
+ return false;
+ memset(zlib_stream_.get(), 0, sizeof(z_stream));
+
+ // Set decoding mode
+ switch (filter_type) {
+ case Filter::FILTER_TYPE_DEFLATE: {
+ if (inflateInit(zlib_stream_.get()) != Z_OK)
+ return false;
+ decoding_mode_ = DECODE_MODE_DEFLATE;
+ break;
+ }
+ case Filter::FILTER_TYPE_GZIP_HELPING_SDCH:
+ possible_sdch_pass_through_ = true; // Needed to optionally help sdch.
+ // Fall through to GZIP case.
+ case Filter::FILTER_TYPE_GZIP: {
+ gzip_header_.reset(new GZipHeader());
+ if (!gzip_header_.get())
+ return false;
+ if (inflateInit2(zlib_stream_.get(), -MAX_WBITS) != Z_OK)
+ return false;
+ decoding_mode_ = DECODE_MODE_GZIP;
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ decoding_status_ = DECODING_IN_PROGRESS;
+ return true;
+}
+
+Filter::FilterStatus GZipFilter::ReadFilteredData(char* dest_buffer,
+ int* dest_len) {
+ if (!dest_buffer || !dest_len || *dest_len <= 0)
+ return Filter::FILTER_ERROR;
+
+ if (decoding_status_ == DECODING_DONE) {
+ if (GZIP_GET_INVALID_HEADER != gzip_header_status_)
+ SkipGZipFooter();
+ // Some server might send extra data after the gzip footer. We just copy
+ // them out. Mozilla does this too.
+ return CopyOut(dest_buffer, dest_len);
+ }
+
+ if (decoding_status_ != DECODING_IN_PROGRESS)
+ return Filter::FILTER_ERROR;
+
+ Filter::FilterStatus status;
+
+ if (decoding_mode_ == DECODE_MODE_GZIP &&
+ gzip_header_status_ == GZIP_CHECK_HEADER_IN_PROGRESS) {
+ // With gzip encoding the content is wrapped with a gzip header.
+ // We need to parse and verify the header first.
+ status = CheckGZipHeader();
+ switch (status) {
+ case Filter::FILTER_NEED_MORE_DATA: {
+ // We have consumed all input data, either getting a complete header or
+ // a partial header. Return now to get more data.
+ *dest_len = 0;
+ // Partial header means it can't be an SDCH header.
+ // Reason: SDCH *always* starts with 8 printable characters [a-zA-Z/_].
+ // Gzip always starts with two non-printable characters. Hence even a
+ // single character (partial header) means that this can't be an SDCH
+ // encoded body masquerading as a GZIP body.
+ possible_sdch_pass_through_ = false;
+ return status;
+ }
+ case Filter::FILTER_OK: {
+ // The header checking succeeds, and there are more data in the input.
+ // We must have got a complete header here.
+ DCHECK_EQ(gzip_header_status_, GZIP_GET_COMPLETE_HEADER);
+ break;
+ }
+ case Filter::FILTER_ERROR: {
+ if (possible_sdch_pass_through_ &&
+ GZIP_GET_INVALID_HEADER == gzip_header_status_) {
+ decoding_status_ = DECODING_DONE; // Become a pass through filter.
+ return CopyOut(dest_buffer, dest_len);
+ }
+ decoding_status_ = DECODING_ERROR;
+ return status;
+ }
+ default: {
+ status = Filter::FILTER_ERROR; // Unexpected.
+ decoding_status_ = DECODING_ERROR;
+ return status;
+ }
+ }
+ }
+
+ int dest_orig_size = *dest_len;
+ status = DoInflate(dest_buffer, dest_len);
+
+ if (decoding_mode_ == DECODE_MODE_DEFLATE && status == Filter::FILTER_ERROR) {
+ // As noted in Mozilla implementation, some servers such as Apache with
+ // mod_deflate don't generate zlib headers.
+ // See 677409 for instances where this work around is needed.
+ // Insert a dummy zlib header and try again.
+ if (InsertZlibHeader()) {
+ *dest_len = dest_orig_size;
+ status = DoInflate(dest_buffer, dest_len);
+ }
+ }
+
+ if (status == Filter::FILTER_DONE) {
+ decoding_status_ = DECODING_DONE;
+ } else if (status == Filter::FILTER_ERROR) {
+ decoding_status_ = DECODING_ERROR;
+ }
+
+ return status;
+}
+
+Filter::FilterStatus GZipFilter::CheckGZipHeader() {
+ DCHECK_EQ(gzip_header_status_, GZIP_CHECK_HEADER_IN_PROGRESS);
+
+ // Check input data in pre-filter buffer.
+ if (!next_stream_data_ || stream_data_len_ <= 0)
+ return Filter::FILTER_ERROR;
+
+ const char* header_end = NULL;
+ GZipHeader::Status header_status;
+ header_status = gzip_header_->ReadMore(next_stream_data_, stream_data_len_,
+ &header_end);
+
+ switch (header_status) {
+ case GZipHeader::INCOMPLETE_HEADER: {
+ // We read all the data but only got a partial header.
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return Filter::FILTER_NEED_MORE_DATA;
+ }
+ case GZipHeader::COMPLETE_HEADER: {
+ // We have a complete header. Check whether there are more data.
+ int num_chars_left = static_cast<int>(stream_data_len_ -
+ (header_end - next_stream_data_));
+ gzip_header_status_ = GZIP_GET_COMPLETE_HEADER;
+
+ if (num_chars_left > 0) {
+ next_stream_data_ = const_cast<char*>(header_end);
+ stream_data_len_ = num_chars_left;
+ return Filter::FILTER_OK;
+ } else {
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return Filter::FILTER_NEED_MORE_DATA;
+ }
+ }
+ case GZipHeader::INVALID_HEADER: {
+ gzip_header_status_ = GZIP_GET_INVALID_HEADER;
+ return Filter::FILTER_ERROR;
+ }
+ default: {
+ break;
+ }
+ }
+
+ return Filter::FILTER_ERROR;
+}
+
+Filter::FilterStatus GZipFilter::DoInflate(char* dest_buffer, int* dest_len) {
+ // Make sure we have both valid input data and output buffer.
+ if (!dest_buffer || !dest_len || *dest_len <= 0) // output
+ return Filter::FILTER_ERROR;
+
+ if (!next_stream_data_ || stream_data_len_ <= 0) { // input
+ *dest_len = 0;
+ return Filter::FILTER_NEED_MORE_DATA;
+ }
+
+ // Fill in zlib control block
+ zlib_stream_.get()->next_in = bit_cast<Bytef*>(next_stream_data_);
+ zlib_stream_.get()->avail_in = stream_data_len_;
+ zlib_stream_.get()->next_out = bit_cast<Bytef*>(dest_buffer);
+ zlib_stream_.get()->avail_out = *dest_len;
+
+ int inflate_code = inflate(zlib_stream_.get(), Z_NO_FLUSH);
+ int bytesWritten = *dest_len - zlib_stream_.get()->avail_out;
+
+ Filter::FilterStatus status;
+
+ switch (inflate_code) {
+ case Z_STREAM_END: {
+ *dest_len = bytesWritten;
+
+ stream_data_len_ = zlib_stream_.get()->avail_in;
+ next_stream_data_ = bit_cast<char*>(zlib_stream_.get()->next_in);
+
+ SkipGZipFooter();
+
+ status = Filter::FILTER_DONE;
+ break;
+ }
+ case Z_BUF_ERROR: {
+ // According to zlib documentation, when calling inflate with Z_NO_FLUSH,
+ // getting Z_BUF_ERROR means no progress is possible. Neither processing
+ // more input nor producing more output can be done.
+ // Since we have checked both input data and output buffer before calling
+ // inflate, this result is unexpected.
+ status = Filter::FILTER_ERROR;
+ break;
+ }
+ case Z_OK: {
+ // Some progress has been made (more input processed or more output
+ // produced).
+ *dest_len = bytesWritten;
+
+ // Check whether we have consumed all input data.
+ stream_data_len_ = zlib_stream_.get()->avail_in;
+ if (stream_data_len_ == 0) {
+ next_stream_data_ = NULL;
+ status = Filter::FILTER_NEED_MORE_DATA;
+ } else {
+ next_stream_data_ = bit_cast<char*>(zlib_stream_.get()->next_in);
+ status = Filter::FILTER_OK;
+ }
+ break;
+ }
+ default: {
+ status = Filter::FILTER_ERROR;
+ break;
+ }
+ }
+
+ return status;
+}
+
+bool GZipFilter::InsertZlibHeader() {
+ static char dummy_head[2] = { 0x78, 0x1 };
+
+ char dummy_output[4];
+
+ // We only try add additional header once.
+ if (zlib_header_added_)
+ return false;
+
+ inflateReset(zlib_stream_.get());
+ zlib_stream_.get()->next_in = bit_cast<Bytef*>(&dummy_head[0]);
+ zlib_stream_.get()->avail_in = sizeof(dummy_head);
+ zlib_stream_.get()->next_out = bit_cast<Bytef*>(&dummy_output[0]);
+ zlib_stream_.get()->avail_out = sizeof(dummy_output);
+
+ int code = inflate(zlib_stream_.get(), Z_NO_FLUSH);
+ zlib_header_added_ = true;
+
+ return (code == Z_OK);
+}
+
+
+void GZipFilter::SkipGZipFooter() {
+ int footer_bytes_expected = kGZipFooterSize - gzip_footer_bytes_;
+ if (footer_bytes_expected > 0) {
+ int footer_byte_avail = std::min(footer_bytes_expected, stream_data_len_);
+ stream_data_len_ -= footer_byte_avail;
+ next_stream_data_ += footer_byte_avail;
+ gzip_footer_bytes_ += footer_byte_avail;
+
+ if (stream_data_len_ == 0)
+ next_stream_data_ = NULL;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/gzip_filter.h b/chromium/net/base/gzip_filter.h
new file mode 100644
index 00000000000..0365a920056
--- /dev/null
+++ b/chromium/net/base/gzip_filter.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2011 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.
+
+// GZipFilter applies gzip and deflate content encoding/decoding to a data
+// stream. As specified by HTTP 1.1, with gzip encoding the content is
+// wrapped with a gzip header, and with deflate encoding the content is in
+// a raw, headerless DEFLATE stream.
+//
+// Internally GZipFilter uses zlib inflate to do decoding.
+//
+// GZipFilter is a subclass of Filter. See the latter's header file filter.h
+// for sample usage.
+
+#ifndef NET_BASE_GZIP_FILTER_H_
+#define NET_BASE_GZIP_FILTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/filter.h"
+
+typedef struct z_stream_s z_stream;
+
+namespace net {
+
+class GZipHeader;
+
+class GZipFilter : public Filter {
+ public:
+ virtual ~GZipFilter();
+
+ // Initializes filter decoding mode and internal control blocks.
+ // Parameter filter_type specifies the type of filter, which corresponds to
+ // either gzip or deflate decoding. The function returns true if success and
+ // false otherwise.
+ // The filter can only be initialized once.
+ bool InitDecoding(Filter::FilterType filter_type);
+
+ // Decodes the pre-filter data and writes the output into the dest_buffer
+ // passed in.
+ // The function returns FilterStatus. See filter.h for its description.
+ //
+ // Upon entry, *dest_len is the total size (in number of chars) of the
+ // destination buffer. Upon exit, *dest_len is the actual number of chars
+ // written into the destination buffer.
+ //
+ // This function will fail if there is no pre-filter data in the
+ // stream_buffer_. On the other hand, *dest_len can be 0 upon successful
+ // return. For example, the internal zlib may process some pre-filter data
+ // but not produce output yet.
+ virtual FilterStatus ReadFilteredData(char* dest_buffer,
+ int* dest_len) OVERRIDE;
+
+ private:
+ enum DecodingStatus {
+ DECODING_UNINITIALIZED,
+ DECODING_IN_PROGRESS,
+ DECODING_DONE,
+ DECODING_ERROR
+ };
+
+ enum DecodingMode {
+ DECODE_MODE_GZIP,
+ DECODE_MODE_DEFLATE,
+ DECODE_MODE_UNKNOWN
+ };
+
+ enum GZipCheckHeaderState {
+ GZIP_CHECK_HEADER_IN_PROGRESS,
+ GZIP_GET_COMPLETE_HEADER,
+ GZIP_GET_INVALID_HEADER
+ };
+
+ static const int kGZipFooterSize = 8;
+
+ // Only to be instantiated by Filter::Factory.
+ GZipFilter();
+ friend class Filter;
+
+ // Parses and verifies the GZip header.
+ // Upon exit, the function updates gzip_header_status_ accordingly.
+ //
+ // The function returns Filter::FILTER_OK if it gets a complete header and
+ // there are more data in the pre-filter buffer.
+ // The function returns Filter::FILTER_NEED_MORE_DATA if it parses all data
+ // in the pre-filter buffer, either getting a complete header or a partial
+ // header. The caller needs to check gzip_header_status_ and call this
+ // function again for partial header.
+ // The function returns Filter::FILTER_ERROR if error occurs.
+ FilterStatus CheckGZipHeader();
+
+ // Internal function to decode the pre-filter data and writes the output into
+ // the dest_buffer passed in.
+ //
+ // This is the internal version of ReadFilteredData. See the latter's
+ // comments for the use of function.
+ FilterStatus DoInflate(char* dest_buffer, int* dest_len);
+
+ // Inserts a zlib header to the data stream before calling zlib inflate.
+ // This is used to work around server bugs. See more comments at the place
+ // it is called in gzip_filter.cc.
+ // The function returns true on success and false otherwise.
+ bool InsertZlibHeader();
+
+ // Skip the 8 byte GZip footer after z_stream_end
+ void SkipGZipFooter();
+
+ // Tracks the status of decoding.
+ // This variable is initialized by InitDecoding and updated only by
+ // ReadFilteredData.
+ DecodingStatus decoding_status_;
+
+ // Indicates the type of content decoding the GZipFilter is performing.
+ // This variable is set only once by InitDecoding.
+ DecodingMode decoding_mode_;
+
+ // Used to parse the gzip header in gzip stream.
+ // It is used when the decoding_mode_ is DECODE_MODE_GZIP.
+ scoped_ptr<GZipHeader> gzip_header_;
+
+ // Tracks the progress of parsing gzip header.
+ // This variable is maintained by gzip_header_.
+ GZipCheckHeaderState gzip_header_status_;
+
+ // A flag used by InsertZlibHeader to record whether we've successfully added
+ // a zlib header to this stream.
+ bool zlib_header_added_;
+
+ // Tracks how many bytes of gzip footer have been received.
+ int gzip_footer_bytes_;
+
+ // The control block of zlib which actually does the decoding.
+ // This data structure is initialized by InitDecoding and updated only by
+ // DoInflate, with InsertZlibHeader being the exception as a workaround.
+ scoped_ptr<z_stream> zlib_stream_;
+
+ // For robustness, when we see the solo sdch filter, we chain in a gzip filter
+ // in front of it, with this flag to indicate that the gzip decoding might not
+ // be needed. This handles a strange case where "Content-Encoding: sdch,gzip"
+ // is reduced by an errant proxy to "Content-Encoding: sdch", while the
+ // content is indeed really gzipped result of sdch :-/.
+ // If this flag is set, then we will revert to being a pass through filter if
+ // we don't get a valid gzip header.
+ bool possible_sdch_pass_through_;
+
+ DISALLOW_COPY_AND_ASSIGN(GZipFilter);
+};
+
+} // namespace net
+
+#endif // NET_BASE_GZIP_FILTER_H__
diff --git a/chromium/net/base/gzip_filter_unittest.cc b/chromium/net/base/gzip_filter_unittest.cc
new file mode 100644
index 00000000000..be98bae00d8
--- /dev/null
+++ b/chromium/net/base/gzip_filter_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 2011 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 <fstream>
+#include <ostream>
+
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "net/base/gzip_filter.h"
+#include "net/base/mock_filter_context.h"
+#include "net/base/io_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+const int kDefaultBufferSize = 4096;
+const int kSmallBufferSize = 128;
+
+const char kApplicationOctetStream[] = "application/octet-stream";
+const char kApplicationXGzip[] = "application/x-gzip";
+const char kApplicationGzip[] = "application/gzip";
+const char kApplicationXGunzip[] = "application/x-gunzip";
+
+// The GZIP header (see RFC 1952):
+// +---+---+---+---+---+---+---+---+---+---+
+// |ID1|ID2|CM |FLG| MTIME |XFL|OS |
+// +---+---+---+---+---+---+---+---+---+---+
+// ID1 \037
+// ID2 \213
+// CM \010 (compression method == DEFLATE)
+// FLG \000 (special flags that we do not support)
+// MTIME Unix format modification time (0 means not available)
+// XFL 2-4? DEFLATE flags
+// OS ???? Operating system indicator (255 means unknown)
+//
+// Header value we generate:
+const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000',
+ '\000', '\000', '\000', '\002', '\377' };
+
+enum EncodeMode {
+ ENCODE_GZIP, // Wrap the deflate with a GZip header.
+ ENCODE_DEFLATE // Raw deflate.
+};
+
+} // namespace
+
+namespace net {
+
+// These tests use the path service, which uses autoreleased objects on the
+// Mac, so this needs to be a PlatformTest.
+class GZipUnitTest : public PlatformTest {
+ protected:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+
+ deflate_encode_buffer_ = NULL;
+ gzip_encode_buffer_ = NULL;
+
+ // Get the path of source data file.
+ base::FilePath file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_path = file_path.AppendASCII("net");
+ file_path = file_path.AppendASCII("data");
+ file_path = file_path.AppendASCII("filter_unittests");
+ file_path = file_path.AppendASCII("google.txt");
+
+ // Read data from the file into buffer.
+ ASSERT_TRUE(file_util::ReadFileToString(file_path, &source_buffer_));
+
+ // Encode the data with deflate
+ deflate_encode_buffer_ = new char[kDefaultBufferSize];
+ ASSERT_TRUE(deflate_encode_buffer_ != NULL);
+
+ deflate_encode_len_ = kDefaultBufferSize;
+ int code = CompressAll(ENCODE_DEFLATE , source_buffer(), source_len(),
+ deflate_encode_buffer_, &deflate_encode_len_);
+ ASSERT_TRUE(code == Z_STREAM_END);
+ ASSERT_GT(deflate_encode_len_, 0);
+ ASSERT_TRUE(deflate_encode_len_ <= kDefaultBufferSize);
+
+ // Encode the data with gzip
+ gzip_encode_buffer_ = new char[kDefaultBufferSize];
+ ASSERT_TRUE(gzip_encode_buffer_ != NULL);
+
+ gzip_encode_len_ = kDefaultBufferSize;
+ code = CompressAll(ENCODE_GZIP, source_buffer(), source_len(),
+ gzip_encode_buffer_, &gzip_encode_len_);
+ ASSERT_TRUE(code == Z_STREAM_END);
+ ASSERT_GT(gzip_encode_len_, 0);
+ ASSERT_TRUE(gzip_encode_len_ <= kDefaultBufferSize);
+ }
+
+ virtual void TearDown() {
+ delete[] deflate_encode_buffer_;
+ deflate_encode_buffer_ = NULL;
+
+ delete[] gzip_encode_buffer_;
+ gzip_encode_buffer_ = NULL;
+
+ PlatformTest::TearDown();
+ }
+
+ // Compress the data in source with deflate encoding and write output to the
+ // buffer provided by dest. The function returns Z_OK if success, and returns
+ // other zlib error code if fail.
+ // The parameter mode specifies the encoding mechanism.
+ // The dest buffer should be large enough to hold all the output data.
+ int CompressAll(EncodeMode mode, const char* source, int source_size,
+ char* dest, int* dest_len) {
+ z_stream zlib_stream;
+ memset(&zlib_stream, 0, sizeof(zlib_stream));
+ int code;
+
+ // Initialize zlib
+ if (mode == ENCODE_GZIP) {
+ code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -MAX_WBITS,
+ 8, // DEF_MEM_LEVEL
+ Z_DEFAULT_STRATEGY);
+ } else {
+ code = deflateInit(&zlib_stream, Z_DEFAULT_COMPRESSION);
+ }
+
+ if (code != Z_OK)
+ return code;
+
+ // Fill in zlib control block
+ zlib_stream.next_in = bit_cast<Bytef*>(source);
+ zlib_stream.avail_in = source_size;
+ zlib_stream.next_out = bit_cast<Bytef*>(dest);
+ zlib_stream.avail_out = *dest_len;
+
+ // Write header if needed
+ if (mode == ENCODE_GZIP) {
+ if (zlib_stream.avail_out < sizeof(kGZipHeader))
+ return Z_BUF_ERROR;
+ memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader));
+ zlib_stream.next_out += sizeof(kGZipHeader);
+ zlib_stream.avail_out -= sizeof(kGZipHeader);
+ }
+
+ // Do deflate
+ code = deflate(&zlib_stream, Z_FINISH);
+ *dest_len = *dest_len - zlib_stream.avail_out;
+
+ deflateEnd(&zlib_stream);
+ return code;
+ }
+
+ // Use filter to decode compressed data, and compare the decoding result with
+ // the orginal Data.
+ // Parameters: Source and source_len are original data and its size.
+ // Encoded_source and encoded_source_len are compressed data and its size.
+ // Output_buffer_size specifies the size of buffer to read out data from
+ // filter.
+ void DecodeAndCompareWithFilter(Filter* filter,
+ const char* source,
+ int source_len,
+ const char* encoded_source,
+ int encoded_source_len,
+ int output_buffer_size) {
+ // Make sure we have enough space to hold the decoding output.
+ ASSERT_TRUE(source_len <= kDefaultBufferSize);
+ ASSERT_TRUE(output_buffer_size <= kDefaultBufferSize);
+
+ char decode_buffer[kDefaultBufferSize];
+ char* decode_next = decode_buffer;
+ int decode_avail_size = kDefaultBufferSize;
+
+ const char* encode_next = encoded_source;
+ int encode_avail_size = encoded_source_len;
+
+ int code = Filter::FILTER_OK;
+ while (code != Filter::FILTER_DONE) {
+ int encode_data_len;
+ encode_data_len = std::min(encode_avail_size,
+ filter->stream_buffer_size());
+ memcpy(filter->stream_buffer()->data(), encode_next, encode_data_len);
+ filter->FlushStreamBuffer(encode_data_len);
+ encode_next += encode_data_len;
+ encode_avail_size -= encode_data_len;
+
+ while (1) {
+ int decode_data_len = std::min(decode_avail_size, output_buffer_size);
+
+ code = filter->ReadData(decode_next, &decode_data_len);
+ decode_next += decode_data_len;
+ decode_avail_size -= decode_data_len;
+
+ ASSERT_TRUE(code != Filter::FILTER_ERROR);
+
+ if (code == Filter::FILTER_NEED_MORE_DATA ||
+ code == Filter::FILTER_DONE) {
+ break;
+ }
+ }
+ }
+
+ // Compare the decoding result with source data
+ int decode_total_data_len = kDefaultBufferSize - decode_avail_size;
+ EXPECT_TRUE(decode_total_data_len == source_len);
+ EXPECT_EQ(memcmp(source, decode_buffer, source_len), 0);
+ }
+
+ // Unsafe function to use filter to decode compressed data.
+ // Parameters: Source and source_len are compressed data and its size.
+ // Dest is the buffer for decoding results. Upon entry, *dest_len is the size
+ // of the dest buffer. Upon exit, *dest_len is the number of chars written
+ // into the buffer.
+ int DecodeAllWithFilter(Filter* filter, const char* source, int source_len,
+ char* dest, int* dest_len) {
+ memcpy(filter->stream_buffer()->data(), source, source_len);
+ filter->FlushStreamBuffer(source_len);
+ return filter->ReadData(dest, dest_len);
+ }
+
+ void InitFilter(Filter::FilterType type) {
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(type);
+ filter_.reset(Filter::Factory(filter_types, filter_context_));
+ ASSERT_TRUE(filter_.get());
+ ASSERT_GE(filter_->stream_buffer_size(), kDefaultBufferSize);
+ }
+
+ void InitFilterWithBufferSize(Filter::FilterType type, int buffer_size) {
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(type);
+ filter_.reset(Filter::FactoryForTests(filter_types, filter_context_,
+ buffer_size));
+ ASSERT_TRUE(filter_.get());
+ }
+
+ const char* source_buffer() const { return source_buffer_.data(); }
+ int source_len() const { return static_cast<int>(source_buffer_.size()); }
+
+ scoped_ptr<Filter> filter_;
+
+ std::string source_buffer_;
+
+ char* deflate_encode_buffer_;
+ int deflate_encode_len_;
+
+ char* gzip_encode_buffer_;
+ int gzip_encode_len_;
+
+ private:
+ MockFilterContext filter_context_;
+};
+
+// Basic scenario: decoding deflate data with big enough buffer.
+TEST_F(GZipUnitTest, DecodeDeflate) {
+ // Decode the compressed data with filter
+ InitFilter(Filter::FILTER_TYPE_DEFLATE);
+ memcpy(filter_->stream_buffer()->data(), deflate_encode_buffer_,
+ deflate_encode_len_);
+ filter_->FlushStreamBuffer(deflate_encode_len_);
+
+ char deflate_decode_buffer[kDefaultBufferSize];
+ int deflate_decode_size = kDefaultBufferSize;
+ filter_->ReadData(deflate_decode_buffer, &deflate_decode_size);
+
+ // Compare the decoding result with source data
+ EXPECT_TRUE(deflate_decode_size == source_len());
+ EXPECT_EQ(memcmp(source_buffer(), deflate_decode_buffer, source_len()), 0);
+}
+
+// Basic scenario: decoding gzip data with big enough buffer.
+TEST_F(GZipUnitTest, DecodeGZip) {
+ // Decode the compressed data with filter
+ InitFilter(Filter::FILTER_TYPE_GZIP);
+ memcpy(filter_->stream_buffer()->data(), gzip_encode_buffer_,
+ gzip_encode_len_);
+ filter_->FlushStreamBuffer(gzip_encode_len_);
+
+ char gzip_decode_buffer[kDefaultBufferSize];
+ int gzip_decode_size = kDefaultBufferSize;
+ filter_->ReadData(gzip_decode_buffer, &gzip_decode_size);
+
+ // Compare the decoding result with source data
+ EXPECT_TRUE(gzip_decode_size == source_len());
+ EXPECT_EQ(memcmp(source_buffer(), gzip_decode_buffer, source_len()), 0);
+}
+
+// Tests we can call filter repeatedly to get all the data decoded.
+// To do that, we create a filter with a small buffer that can not hold all
+// the input data.
+TEST_F(GZipUnitTest, DecodeWithSmallBuffer) {
+ InitFilterWithBufferSize(Filter::FILTER_TYPE_DEFLATE, kSmallBufferSize);
+ EXPECT_EQ(kSmallBufferSize, filter_->stream_buffer_size());
+ DecodeAndCompareWithFilter(filter_.get(), source_buffer(), source_len(),
+ deflate_encode_buffer_, deflate_encode_len_,
+ kDefaultBufferSize);
+}
+
+// Tests we can still decode with just 1 byte buffer in the filter.
+// The purpose of this tests are two: (1) Verify filter can parse partial GZip
+// header correctly. (2) Sometimes the filter will consume input without
+// generating output. Verify filter can handle it correctly.
+TEST_F(GZipUnitTest, DecodeWithOneByteBuffer) {
+ InitFilterWithBufferSize(Filter::FILTER_TYPE_GZIP, 1);
+ EXPECT_EQ(1, filter_->stream_buffer_size());
+ DecodeAndCompareWithFilter(filter_.get(), source_buffer(), source_len(),
+ gzip_encode_buffer_, gzip_encode_len_,
+ kDefaultBufferSize);
+}
+
+// Tests we can decode when caller has small buffer to read out from filter.
+TEST_F(GZipUnitTest, DecodeWithSmallOutputBuffer) {
+ InitFilter(Filter::FILTER_TYPE_DEFLATE);
+ DecodeAndCompareWithFilter(filter_.get(), source_buffer(), source_len(),
+ deflate_encode_buffer_, deflate_encode_len_,
+ kSmallBufferSize);
+}
+
+// Tests we can still decode with just 1 byte buffer in the filter and just 1
+// byte buffer in the caller.
+TEST_F(GZipUnitTest, DecodeWithOneByteInputAndOutputBuffer) {
+ InitFilterWithBufferSize(Filter::FILTER_TYPE_GZIP, 1);
+ EXPECT_EQ(1, filter_->stream_buffer_size());
+ DecodeAndCompareWithFilter(filter_.get(), source_buffer(), source_len(),
+ gzip_encode_buffer_, gzip_encode_len_, 1);
+}
+
+// Decoding deflate stream with corrupted data.
+TEST_F(GZipUnitTest, DecodeCorruptedData) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = deflate_encode_len_;
+ memcpy(corrupt_data, deflate_encode_buffer_, deflate_encode_len_);
+
+ int pos = corrupt_data_len / 2;
+ corrupt_data[pos] = !corrupt_data[pos];
+
+ // Decode the corrupted data with filter
+ InitFilter(Filter::FILTER_TYPE_DEFLATE);
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ int code = DecodeAllWithFilter(filter_.get(), corrupt_data, corrupt_data_len,
+ corrupt_decode_buffer, &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_TRUE(code == Filter::FILTER_ERROR);
+}
+
+// Decoding deflate stream with missing data.
+TEST_F(GZipUnitTest, DecodeMissingData) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = deflate_encode_len_;
+ memcpy(corrupt_data, deflate_encode_buffer_, deflate_encode_len_);
+
+ int pos = corrupt_data_len / 2;
+ int len = corrupt_data_len - pos - 1;
+ memmove(&corrupt_data[pos], &corrupt_data[pos+1], len);
+ --corrupt_data_len;
+
+ // Decode the corrupted data with filter
+ InitFilter(Filter::FILTER_TYPE_DEFLATE);
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ int code = DecodeAllWithFilter(filter_.get(), corrupt_data, corrupt_data_len,
+ corrupt_decode_buffer, &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_EQ(Filter::FILTER_ERROR, code);
+}
+
+// Decoding gzip stream with corrupted header.
+TEST_F(GZipUnitTest, DecodeCorruptedHeader) {
+ char corrupt_data[kDefaultBufferSize];
+ int corrupt_data_len = gzip_encode_len_;
+ memcpy(corrupt_data, gzip_encode_buffer_, gzip_encode_len_);
+
+ corrupt_data[2] = !corrupt_data[2];
+
+ // Decode the corrupted data with filter
+ InitFilter(Filter::FILTER_TYPE_GZIP);
+ char corrupt_decode_buffer[kDefaultBufferSize];
+ int corrupt_decode_size = kDefaultBufferSize;
+
+ int code = DecodeAllWithFilter(filter_.get(), corrupt_data, corrupt_data_len,
+ corrupt_decode_buffer, &corrupt_decode_size);
+
+ // Expect failures
+ EXPECT_TRUE(code == Filter::FILTER_ERROR);
+}
+
+} // namespace net
diff --git a/chromium/net/base/gzip_header.cc b/chromium/net/base/gzip_header.cc
new file mode 100644
index 00000000000..81cb1d8efd3
--- /dev/null
+++ b/chromium/net/base/gzip_header.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 2011 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/base/gzip_header.h"
+
+#include "base/logging.h"
+#include "third_party/zlib/zlib.h"
+
+namespace net {
+
+const uint8 GZipHeader::magic[] = { 0x1f, 0x8b };
+
+GZipHeader::GZipHeader() {
+ Reset();
+}
+
+GZipHeader::~GZipHeader() {
+}
+
+void GZipHeader::Reset() {
+ state_ = IN_HEADER_ID1;
+ flags_ = 0;
+ extra_length_ = 0;
+}
+
+GZipHeader::Status GZipHeader::ReadMore(const char* inbuf, int inbuf_len,
+ const char** header_end) {
+ DCHECK_GE(inbuf_len, 0);
+ const uint8* pos = reinterpret_cast<const uint8*>(inbuf);
+ const uint8* const end = pos + inbuf_len;
+
+ while ( pos < end ) {
+ switch ( state_ ) {
+ case IN_HEADER_ID1:
+ if ( *pos != magic[0] ) return INVALID_HEADER;
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_ID2:
+ if ( *pos != magic[1] ) return INVALID_HEADER;
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_CM:
+ if ( *pos != Z_DEFLATED ) return INVALID_HEADER;
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_FLG:
+ flags_ = (*pos) & (FLAG_FHCRC | FLAG_FEXTRA |
+ FLAG_FNAME | FLAG_FCOMMENT);
+ pos++;
+ state_++;
+ break;
+
+ case IN_HEADER_MTIME_BYTE_0:
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_MTIME_BYTE_1:
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_MTIME_BYTE_2:
+ pos++;
+ state_++;
+ break;
+ case IN_HEADER_MTIME_BYTE_3:
+ pos++;
+ state_++;
+ break;
+
+ case IN_HEADER_XFL:
+ pos++;
+ state_++;
+ break;
+
+ case IN_HEADER_OS:
+ pos++;
+ state_++;
+ break;
+
+ case IN_XLEN_BYTE_0:
+ if ( !(flags_ & FLAG_FEXTRA) ) {
+ state_ = IN_FNAME;
+ break;
+ }
+ // We have a two-byte little-endian length, followed by a
+ // field of that length.
+ extra_length_ = *pos;
+ pos++;
+ state_++;
+ break;
+ case IN_XLEN_BYTE_1:
+ extra_length_ += *pos << 8;
+ pos++;
+ state_++;
+ // We intentionally fall through, because if we have a
+ // zero-length FEXTRA, we want to check to notice that we're
+ // done reading the FEXTRA before we exit this loop...
+
+ case IN_FEXTRA: {
+ // Grab the rest of the bytes in the extra field, or as many
+ // of them as are actually present so far.
+ const int num_extra_bytes = static_cast<const int>(std::min(
+ static_cast<ptrdiff_t>(extra_length_),
+ (end - pos)));
+ pos += num_extra_bytes;
+ extra_length_ -= num_extra_bytes;
+ if ( extra_length_ == 0 ) {
+ state_ = IN_FNAME; // advance when we've seen extra_length_ bytes
+ flags_ &= ~FLAG_FEXTRA; // we're done with the FEXTRA stuff
+ }
+ break;
+ }
+
+ case IN_FNAME:
+ if ( !(flags_ & FLAG_FNAME) ) {
+ state_ = IN_FCOMMENT;
+ break;
+ }
+ // See if we can find the end of the \0-terminated FNAME field.
+ pos = reinterpret_cast<const uint8*>(memchr(pos, '\0', (end - pos)));
+ if ( pos != NULL ) {
+ pos++; // advance past the '\0'
+ flags_ &= ~FLAG_FNAME; // we're done with the FNAME stuff
+ state_ = IN_FCOMMENT;
+ } else {
+ pos = end; // everything we have so far is part of the FNAME
+ }
+ break;
+
+ case IN_FCOMMENT:
+ if ( !(flags_ & FLAG_FCOMMENT) ) {
+ state_ = IN_FHCRC_BYTE_0;
+ break;
+ }
+ // See if we can find the end of the \0-terminated FCOMMENT field.
+ pos = reinterpret_cast<const uint8*>(memchr(pos, '\0', (end - pos)));
+ if ( pos != NULL ) {
+ pos++; // advance past the '\0'
+ flags_ &= ~FLAG_FCOMMENT; // we're done with the FCOMMENT stuff
+ state_ = IN_FHCRC_BYTE_0;
+ } else {
+ pos = end; // everything we have so far is part of the FNAME
+ }
+ break;
+
+ case IN_FHCRC_BYTE_0:
+ if ( !(flags_ & FLAG_FHCRC) ) {
+ state_ = IN_DONE;
+ break;
+ }
+ pos++;
+ state_++;
+ break;
+
+ case IN_FHCRC_BYTE_1:
+ pos++;
+ flags_ &= ~FLAG_FHCRC; // we're done with the FHCRC stuff
+ state_++;
+ break;
+
+ case IN_DONE:
+ *header_end = reinterpret_cast<const char*>(pos);
+ return COMPLETE_HEADER;
+ }
+ }
+
+ if ( (state_ > IN_HEADER_OS) && (flags_ == 0) ) {
+ *header_end = reinterpret_cast<const char*>(pos);
+ return COMPLETE_HEADER;
+ } else {
+ return INCOMPLETE_HEADER;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/gzip_header.h b/chromium/net/base/gzip_header.h
new file mode 100644
index 00000000000..82e55c24e07
--- /dev/null
+++ b/chromium/net/base/gzip_header.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 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.
+
+// The GZipHeader class allows you to parse a gzip header, such as you
+// might find at the beginning of a file compressed by gzip (ie, a .gz
+// file), or at the beginning of an HTTP response that uses a gzip
+// Content-Encoding. See RFC 1952 for the specification for the gzip
+// header.
+//
+// The model is that you call ReadMore() for each chunk of bytes
+// you've read from a file or socket.
+//
+
+#ifndef NET_BASE_GZIP_HEADER_H_
+#define NET_BASE_GZIP_HEADER_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+class GZipHeader {
+ public:
+ enum Status {
+ INCOMPLETE_HEADER, // don't have all the bits yet...
+ COMPLETE_HEADER, // complete, valid header
+ INVALID_HEADER, // found something invalid in the header
+ };
+
+ GZipHeader();
+ ~GZipHeader();
+
+ // Wipe the slate clean and start from scratch.
+ void Reset();
+
+ // Attempt to parse the given buffer as the next installment of
+ // bytes from a gzip header. If the bytes we've seen so far do not
+ // yet constitute a complete gzip header, return
+ // INCOMPLETE_HEADER. If these bytes do not constitute a *valid*
+ // gzip header, return INVALID_HEADER. When we've seen a complete
+ // gzip header, return COMPLETE_HEADER and set the pointer pointed
+ // to by header_end to the first byte beyond the gzip header.
+ Status ReadMore(const char* inbuf,
+ int inbuf_len,
+ const char** header_end);
+ private:
+ enum { // flags (see RFC)
+ FLAG_FTEXT = 0x01, // bit 0 set: file probably ascii text
+ FLAG_FHCRC = 0x02, // bit 1 set: header CRC present
+ FLAG_FEXTRA = 0x04, // bit 2 set: extra field present
+ FLAG_FNAME = 0x08, // bit 3 set: original file name present
+ FLAG_FCOMMENT = 0x10, // bit 4 set: file comment present
+ FLAG_RESERVED = 0xE0, // bits 5..7: reserved
+ };
+
+ enum State {
+ // The first 10 bytes are the fixed-size header:
+ IN_HEADER_ID1,
+ IN_HEADER_ID2,
+ IN_HEADER_CM,
+ IN_HEADER_FLG,
+ IN_HEADER_MTIME_BYTE_0,
+ IN_HEADER_MTIME_BYTE_1,
+ IN_HEADER_MTIME_BYTE_2,
+ IN_HEADER_MTIME_BYTE_3,
+ IN_HEADER_XFL,
+ IN_HEADER_OS,
+
+ IN_XLEN_BYTE_0,
+ IN_XLEN_BYTE_1,
+ IN_FEXTRA,
+
+ IN_FNAME,
+
+ IN_FCOMMENT,
+
+ IN_FHCRC_BYTE_0,
+ IN_FHCRC_BYTE_1,
+
+ IN_DONE,
+ };
+
+ static const uint8 magic[]; // gzip magic header
+
+ int state_; // our current State in the parsing FSM: an int so we can ++
+ uint8 flags_; // the flags byte of the header ("FLG" in the RFC)
+ uint16 extra_length_; // how much of the "extra field" we have yet to read
+
+ DISALLOW_COPY_AND_ASSIGN(GZipHeader);
+};
+
+} // namespace net
+
+#endif // NET_BASE_GZIP_HEADER_H_
diff --git a/chromium/net/base/hash_value.cc b/chromium/net/base/hash_value.cc
new file mode 100644
index 00000000000..9ae1412058d
--- /dev/null
+++ b/chromium/net/base/hash_value.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 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.
+
+#include "net/base/hash_value.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+
+namespace net {
+
+namespace {
+
+// CompareSHA1Hashes is a helper function for using bsearch() with an array of
+// SHA1 hashes.
+int CompareSHA1Hashes(const void* a, const void* b) {
+ return memcmp(a, b, base::kSHA1Length);
+}
+
+} // namespace
+
+
+bool SHA1HashValue::Equals(const SHA1HashValue& other) const {
+ return memcmp(data, other.data, sizeof(data)) == 0;
+}
+
+bool SHA256HashValue::Equals(const SHA256HashValue& other) const {
+ return memcmp(data, other.data, sizeof(data)) == 0;
+}
+
+bool HashValue::Equals(const HashValue& other) const {
+ if (tag != other.tag)
+ return false;
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return fingerprint.sha1.Equals(other.fingerprint.sha1);
+ case HASH_VALUE_SHA256:
+ return fingerprint.sha256.Equals(other.fingerprint.sha256);
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return false;
+ }
+}
+
+bool HashValue::FromString(const base::StringPiece value) {
+ base::StringPiece base64_str;
+ if (value.starts_with("sha1/")) {
+ tag = HASH_VALUE_SHA1;
+ base64_str = value.substr(5);
+ } else if (value.starts_with("sha256/")) {
+ tag = HASH_VALUE_SHA256;
+ base64_str = value.substr(7);
+ } else {
+ return false;
+ }
+
+ std::string decoded;
+ if (!base::Base64Decode(base64_str, &decoded) || decoded.size() != size())
+ return false;
+
+ memcpy(data(), decoded.data(), size());
+ return true;
+}
+
+std::string HashValue::ToString() const {
+ std::string base64_str;
+ base::Base64Encode(base::StringPiece(reinterpret_cast<const char*>(data()),
+ size()), &base64_str);
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return std::string("sha1/") + base64_str;
+ case HASH_VALUE_SHA256:
+ return std::string("sha256/") + base64_str;
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return std::string("unknown/" + base64_str);
+ }
+}
+
+size_t HashValue::size() const {
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return sizeof(fingerprint.sha1.data);
+ case HASH_VALUE_SHA256:
+ return sizeof(fingerprint.sha256.data);
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ // While an invalid tag should not happen, return a non-zero length
+ // to avoid compiler warnings when the result of size() is
+ // used with functions like memset.
+ return sizeof(fingerprint.sha1.data);
+ }
+}
+
+unsigned char* HashValue::data() {
+ return const_cast<unsigned char*>(const_cast<const HashValue*>(this)->data());
+}
+
+const unsigned char* HashValue::data() const {
+ switch (tag) {
+ case HASH_VALUE_SHA1:
+ return fingerprint.sha1.data;
+ case HASH_VALUE_SHA256:
+ return fingerprint.sha256.data;
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << tag;
+ return NULL;
+ }
+}
+
+bool IsSHA1HashInSortedArray(const SHA1HashValue& hash,
+ const uint8* array,
+ size_t array_byte_len) {
+ DCHECK_EQ(0u, array_byte_len % base::kSHA1Length);
+ const size_t arraylen = array_byte_len / base::kSHA1Length;
+ return NULL != bsearch(hash.data, array, arraylen, base::kSHA1Length,
+ CompareSHA1Hashes);
+}
+
+} // namespace net
diff --git a/chromium/net/base/hash_value.h b/chromium/net/base/hash_value.h
new file mode 100644
index 00000000000..aa0b9f6e9f8
--- /dev/null
+++ b/chromium/net/base/hash_value.h
@@ -0,0 +1,125 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_HASH_VALUE_H_
+#define NET_BASE_HASH_VALUE_H_
+
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct NET_EXPORT SHA1HashValue {
+ bool Equals(const SHA1HashValue& other) const;
+
+ unsigned char data[20];
+};
+
+struct NET_EXPORT SHA256HashValue {
+ bool Equals(const SHA256HashValue& other) const;
+
+ unsigned char data[32];
+};
+
+enum HashValueTag {
+ HASH_VALUE_SHA1,
+ HASH_VALUE_SHA256,
+
+ // This must always be last.
+ HASH_VALUE_TAGS_COUNT
+};
+
+class NET_EXPORT HashValue {
+ public:
+ explicit HashValue(HashValueTag tag) : tag(tag) {}
+ HashValue() : tag(HASH_VALUE_SHA1) {}
+
+ // Check for equality of hash values
+ // This function may have VARIABLE timing which leaks information
+ // about its inputs. For example it may exit early once a
+ // nonequal character is discovered. Thus, for security reasons
+ // this function MUST NOT be used with secret values (such as
+ // password hashes, MAC tags, etc.)
+ bool Equals(const HashValue& other) const;
+
+ // Serializes/Deserializes hashes in the form of
+ // <hash-name>"/"<base64-hash-value>
+ // (eg: "sha1/...")
+ // This format may be persisted to permanent storage, so
+ // care should be taken before changing the serialization.
+ //
+ // This format is used for:
+ // - net_internals display/setting public-key pins
+ // - logging public-key pins
+ // - serializing public-key pins
+
+ // Deserializes a HashValue from a string. On error, returns
+ // false and MAY change the contents of HashValue to contain invalid data.
+ bool FromString(const base::StringPiece input);
+
+ // Serializes the HashValue to a string. If an invalid HashValue
+ // is supplied (eg: an unknown hash tag), returns "unknown"/<base64>
+ std::string ToString() const;
+
+ size_t size() const;
+ unsigned char* data();
+ const unsigned char* data() const;
+
+ HashValueTag tag;
+
+ private:
+ union {
+ SHA1HashValue sha1;
+ SHA256HashValue sha256;
+ } fingerprint;
+};
+
+typedef std::vector<HashValue> HashValueVector;
+
+
+class SHA1HashValueLessThan {
+ public:
+ bool operator()(const SHA1HashValue& lhs,
+ const SHA1HashValue& rhs) const {
+ return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
+ }
+};
+
+class SHA256HashValueLessThan {
+ public:
+ bool operator()(const SHA256HashValue& lhs,
+ const SHA256HashValue& rhs) const {
+ return memcmp(lhs.data, rhs.data, sizeof(lhs.data)) < 0;
+ }
+};
+
+class HashValuesEqual {
+ public:
+ explicit HashValuesEqual(const HashValue& fingerprint) :
+ fingerprint_(fingerprint) {}
+
+ bool operator()(const HashValue& other) const {
+ return fingerprint_.Equals(other);
+ }
+
+ const HashValue& fingerprint_;
+};
+
+
+// IsSHA1HashInSortedArray returns true iff |hash| is in |array|, a sorted
+// array of SHA1 hashes.
+bool IsSHA1HashInSortedArray(const SHA1HashValue& hash,
+ const uint8* array,
+ size_t array_byte_len);
+
+} // namespace net
+
+#endif // NET_BASE_HASH_VALUE_H_
diff --git a/chromium/net/base/host_mapping_rules.cc b/chromium/net/base/host_mapping_rules.cc
new file mode 100644
index 00000000000..cd82f513286
--- /dev/null
+++ b/chromium/net/base/host_mapping_rules.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2010 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/base/host_mapping_rules.h"
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+struct HostMappingRules::MapRule {
+ MapRule() : replacement_port(-1) {}
+
+ std::string hostname_pattern;
+ std::string replacement_hostname;
+ int replacement_port;
+};
+
+struct HostMappingRules::ExclusionRule {
+ std::string hostname_pattern;
+};
+
+HostMappingRules::HostMappingRules() {}
+
+HostMappingRules::~HostMappingRules() {}
+
+bool HostMappingRules::RewriteHost(HostPortPair* host_port) const {
+ // Check if the hostname was excluded.
+ for (ExclusionRuleList::const_iterator it = exclusion_rules_.begin();
+ it != exclusion_rules_.end(); ++it) {
+ const ExclusionRule& rule = *it;
+ if (MatchPattern(host_port->host(), rule.hostname_pattern))
+ return false;
+ }
+
+ // Check if the hostname was remapped.
+ for (MapRuleList::const_iterator it = map_rules_.begin();
+ it != map_rules_.end(); ++it) {
+ const MapRule& rule = *it;
+
+ // The rule's hostname_pattern will be something like:
+ // www.foo.com
+ // *.foo.com
+ // www.foo.com:1234
+ // *.foo.com:1234
+ // First, we'll check for a match just on hostname.
+ // If that fails, we'll check for a match with both hostname and port.
+ if (!MatchPattern(host_port->host(), rule.hostname_pattern)) {
+ std::string host_port_string = host_port->ToString();
+ if (!MatchPattern(host_port_string, rule.hostname_pattern))
+ continue; // This rule doesn't apply.
+ }
+
+ host_port->set_host(rule.replacement_hostname);
+ if (rule.replacement_port != -1)
+ host_port->set_port(rule.replacement_port);
+ return true;
+ }
+
+ return false;
+}
+
+bool HostMappingRules::AddRuleFromString(const std::string& rule_string) {
+ std::string trimmed;
+ TrimWhitespaceASCII(rule_string, TRIM_ALL, &trimmed);
+ std::vector<std::string> parts;
+ base::SplitString(trimmed, ' ', &parts);
+
+ // Test for EXCLUSION rule.
+ if (parts.size() == 2 && LowerCaseEqualsASCII(parts[0], "exclude")) {
+ ExclusionRule rule;
+ rule.hostname_pattern = StringToLowerASCII(parts[1]);
+ exclusion_rules_.push_back(rule);
+ return true;
+ }
+
+ // Test for MAP rule.
+ if (parts.size() == 3 && LowerCaseEqualsASCII(parts[0], "map")) {
+ MapRule rule;
+ rule.hostname_pattern = StringToLowerASCII(parts[1]);
+
+ if (!ParseHostAndPort(parts[2], &rule.replacement_hostname,
+ &rule.replacement_port)) {
+ return false; // Failed parsing the hostname/port.
+ }
+
+ map_rules_.push_back(rule);
+ return true;
+ }
+
+ return false;
+}
+
+void HostMappingRules::SetRulesFromString(const std::string& rules_string) {
+ exclusion_rules_.clear();
+ map_rules_.clear();
+
+ base::StringTokenizer rules(rules_string, ",");
+ while (rules.GetNext()) {
+ bool ok = AddRuleFromString(rules.token());
+ LOG_IF(ERROR, !ok) << "Failed parsing rule: " << rules.token();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/host_mapping_rules.h b/chromium/net/base/host_mapping_rules.h
new file mode 100644
index 00000000000..aa0fb77d85e
--- /dev/null
+++ b/chromium/net/base/host_mapping_rules.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2011 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_BASE_HOST_MAPPING_RULES_H_
+#define NET_BASE_HOST_MAPPING_RULES_H_
+
+#include <string>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class HostPortPair;
+
+class NET_EXPORT_PRIVATE HostMappingRules {
+ public:
+ HostMappingRules();
+ ~HostMappingRules();
+
+ // Modifies |*host_port| based on the current rules. Returns true if the
+ // RequestInfo was modified, false otherwise.
+ bool RewriteHost(HostPortPair* host_port) const;
+
+ // Adds a rule to this mapper. The format of the rule can be one of:
+ //
+ // "MAP" <hostname_pattern> <replacement_host> [":" <replacement_port>]
+ // "EXCLUDE" <hostname_pattern>
+ //
+ // The <replacement_host> can be either a hostname, or an IP address literal.
+ //
+ // Returns true if the rule was successfully parsed and added.
+ bool AddRuleFromString(const std::string& rule_string);
+
+ // Sets the rules from a comma separated list of rules.
+ void SetRulesFromString(const std::string& rules_string);
+
+ private:
+ struct MapRule;
+ struct ExclusionRule;
+
+ typedef std::vector<MapRule> MapRuleList;
+ typedef std::vector<ExclusionRule> ExclusionRuleList;
+
+ MapRuleList map_rules_;
+ ExclusionRuleList exclusion_rules_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostMappingRules);
+};
+
+} // namespace net
+
+#endif // NET_BASE_HOST_MAPPING_RULES_H_
diff --git a/chromium/net/base/host_mapping_rules_unittest.cc b/chromium/net/base/host_mapping_rules_unittest.cc
new file mode 100644
index 00000000000..8d8f7b1c746
--- /dev/null
+++ b/chromium/net/base/host_mapping_rules_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2010 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/base/host_mapping_rules.h"
+
+#include "net/base/host_port_pair.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HostMappingRulesTest, SetRulesFromString) {
+ HostMappingRules rules;
+ rules.SetRulesFromString(
+ "map *.com baz , map *.net bar:60, EXCLUDE *.foo.com");
+
+ HostPortPair host_port("test", 1234);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("test", host_port.host());
+ EXPECT_EQ(1234u, host_port.port());
+
+ host_port = HostPortPair("chrome.net", 80);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("bar", host_port.host());
+ EXPECT_EQ(60u, host_port.port());
+
+ host_port = HostPortPair("crack.com", 80);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("baz", host_port.host());
+ EXPECT_EQ(80u, host_port.port());
+
+ host_port = HostPortPair("wtf.foo.com", 666);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("wtf.foo.com", host_port.host());
+ EXPECT_EQ(666u, host_port.port());
+}
+
+TEST(HostMappingRulesTest, PortSpecificMatching) {
+ HostMappingRules rules;
+ rules.SetRulesFromString(
+ "map *.com:80 baz:111 , map *.com:443 blat:333, EXCLUDE *.foo.com");
+
+ // No match
+ HostPortPair host_port("test.com", 1234);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("test.com", host_port.host());
+ EXPECT_EQ(1234u, host_port.port());
+
+ // Match port 80
+ host_port = HostPortPair("crack.com", 80);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("baz", host_port.host());
+ EXPECT_EQ(111u, host_port.port());
+
+ // Match port 443
+ host_port = HostPortPair("wtf.com", 443);
+ EXPECT_TRUE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("blat", host_port.host());
+ EXPECT_EQ(333u, host_port.port());
+
+ // Match port 443, but excluded.
+ host_port = HostPortPair("wtf.foo.com", 443);
+ EXPECT_FALSE(rules.RewriteHost(&host_port));
+ EXPECT_EQ("wtf.foo.com", host_port.host());
+ EXPECT_EQ(443u, host_port.port());
+}
+
+// Parsing bad rules should silently discard the rule (and never crash).
+TEST(HostMappingRulesTest, ParseInvalidRules) {
+ HostMappingRules rules;
+
+ EXPECT_FALSE(rules.AddRuleFromString("xyz"));
+ EXPECT_FALSE(rules.AddRuleFromString(std::string()));
+ EXPECT_FALSE(rules.AddRuleFromString(" "));
+ EXPECT_FALSE(rules.AddRuleFromString("EXCLUDE"));
+ EXPECT_FALSE(rules.AddRuleFromString("EXCLUDE foo bar"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE x"));
+ EXPECT_FALSE(rules.AddRuleFromString("INCLUDE x :10"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/host_port_pair.cc b/chromium/net/base/host_port_pair.cc
new file mode 100644
index 00000000000..bc23a9a8621
--- /dev/null
+++ b/chromium/net/base/host_port_pair.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 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.
+
+#include "net/base/host_port_pair.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/ip_endpoint.h"
+#include "url/gurl.h"
+
+namespace net {
+
+HostPortPair::HostPortPair() : port_(0) {}
+HostPortPair::HostPortPair(const std::string& in_host, uint16 in_port)
+ : host_(in_host), port_(in_port) {}
+
+// static
+HostPortPair HostPortPair::FromURL(const GURL& url) {
+ return HostPortPair(url.HostNoBrackets(), url.EffectiveIntPort());
+}
+
+// static
+HostPortPair HostPortPair::FromIPEndPoint(const IPEndPoint& ipe) {
+ return HostPortPair(ipe.ToStringWithoutPort(), ipe.port());
+}
+
+HostPortPair HostPortPair::FromString(const std::string& str) {
+ std::vector<std::string> key_port;
+ base::SplitString(str, ':', &key_port);
+ if (key_port.size() != 2)
+ return HostPortPair();
+ int port;
+ if (!base::StringToInt(key_port[1], &port))
+ return HostPortPair();
+ DCHECK_LT(port, 1 << 16);
+ HostPortPair host_port_pair;
+ host_port_pair.set_host(key_port[0]);
+ host_port_pair.set_port(port);
+ return host_port_pair;
+}
+
+std::string HostPortPair::ToString() const {
+ return base::StringPrintf("%s:%u", HostForURL().c_str(), port_);
+}
+
+std::string HostPortPair::HostForURL() const {
+ // Check to see if the host is an IPv6 address. If so, added brackets.
+ if (host_.find(':') != std::string::npos) {
+ DCHECK_NE(host_[0], '[');
+ return base::StringPrintf("[%s]", host_.c_str());
+ }
+
+ return host_;
+}
+
+} // namespace net
diff --git a/chromium/net/base/host_port_pair.h b/chromium/net/base/host_port_pair.h
new file mode 100644
index 00000000000..a4e5761bccc
--- /dev/null
+++ b/chromium/net/base/host_port_pair.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_HOST_PORT_PAIR_H_
+#define NET_BASE_HOST_PORT_PAIR_H_
+
+#include <string>
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class IPEndPoint;
+
+class NET_EXPORT HostPortPair {
+ public:
+ HostPortPair();
+ // If |in_host| represents an IPv6 address, it should not bracket the address.
+ HostPortPair(const std::string& in_host, uint16 in_port);
+
+ // Creates a HostPortPair for the origin of |url|.
+ static HostPortPair FromURL(const GURL& url);
+
+ // Creates a HostPortPair from an IPEndPoint.
+ static HostPortPair FromIPEndPoint(const IPEndPoint& ipe);
+
+ // Creates a HostPortPair from a string formatted in same manner as
+ // ToString().
+ static HostPortPair FromString(const std::string& str);
+
+ // TODO(willchan): Define a functor instead.
+ // Comparator function so this can be placed in a std::map.
+ bool operator<(const HostPortPair& other) const {
+ if (port_ != other.port_)
+ return port_ < other.port_;
+ return host_ < other.host_;
+ }
+
+ // Equality test of contents. (Probably another violation of style guide).
+ bool Equals(const HostPortPair& other) const {
+ return host_ == other.host_ && port_ == other.port_;
+ }
+
+ bool IsEmpty() const {
+ return host_.empty() && port_ == 0;
+ }
+
+ const std::string& host() const {
+ return host_;
+ }
+
+ uint16 port() const {
+ return port_;
+ }
+
+ void set_host(const std::string& in_host) {
+ host_ = in_host;
+ }
+
+ void set_port(uint16 in_port) {
+ port_ = in_port;
+ }
+
+ // ToString() will convert the HostPortPair to "host:port". If |host_| is an
+ // IPv6 literal, it will add brackets around |host_|.
+ std::string ToString() const;
+
+ // Returns |host_|, adding IPv6 brackets if needed.
+ std::string HostForURL() const;
+
+ private:
+ // If |host_| represents an IPv6 address, this string will not contain
+ // brackets around the address.
+ std::string host_;
+ uint16 port_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_HOST_PORT_PAIR_H_
diff --git a/chromium/net/base/host_port_pair_unittest.cc b/chromium/net/base/host_port_pair_unittest.cc
new file mode 100644
index 00000000000..d654622dd00
--- /dev/null
+++ b/chromium/net/base/host_port_pair_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#include "net/base/host_port_pair.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HostPortPairTest, Parsing) {
+ HostPortPair foo("foo.com", 10);
+ std::string foo_str = foo.ToString();
+ EXPECT_EQ("foo.com:10", foo_str);
+ HostPortPair bar = HostPortPair::FromString(foo_str);
+ EXPECT_TRUE(foo.Equals(bar));
+}
+
+TEST(HostPortPairTest, BadString) {
+ HostPortPair foo = HostPortPair::FromString("foo.com:2:3");
+ EXPECT_TRUE(foo.host().empty());
+ EXPECT_EQ(0, foo.port());
+
+ HostPortPair bar = HostPortPair::FromString("bar.com:two");
+ EXPECT_TRUE(bar.host().empty());
+ EXPECT_EQ(0, bar.port());
+}
+
+TEST(HostPortPairTest, Emptiness) {
+ HostPortPair foo;
+ EXPECT_TRUE(foo.IsEmpty());
+ foo = HostPortPair::FromString("foo.com:8080");
+ EXPECT_FALSE(foo.IsEmpty());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/int128.cc b/chromium/net/base/int128.cc
new file mode 100644
index 00000000000..745d8ed6e0c
--- /dev/null
+++ b/chromium/net/base/int128.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 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.
+
+#include <iostream>
+#include "base/basictypes.h"
+#include "net/base/int128.h"
+
+const uint128_pod kuint128max = {
+ static_cast<uint64>(GG_LONGLONG(0xFFFFFFFFFFFFFFFF)),
+ static_cast<uint64>(GG_LONGLONG(0xFFFFFFFFFFFFFFFF))
+};
+
+std::ostream& operator<<(std::ostream& o, const uint128& b) {
+ return (o << b.hi_ << "::" << b.lo_);
+}
diff --git a/chromium/net/base/int128.h b/chromium/net/base/int128.h
new file mode 100644
index 00000000000..b17801be575
--- /dev/null
+++ b/chromium/net/base/int128.h
@@ -0,0 +1,330 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_INT128_H_
+#define NET_BASE_INT128_H_
+
+#include <iosfwd>
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+struct uint128_pod;
+
+// An unsigned 128-bit integer type. Thread-compatible.
+class uint128 {
+public:
+ uint128(); // Sets to 0, but don't trust on this behavior.
+ uint128(uint64 top, uint64 bottom);
+ uint128(int bottom);
+ uint128(uint32 bottom); // Top 96 bits = 0
+ uint128(uint64 bottom); // hi_ = 0
+ uint128(const uint128 &val);
+ uint128(const uint128_pod &val);
+
+ void Initialize(uint64 top, uint64 bottom);
+
+ uint128& operator=(const uint128& b);
+
+ // Arithmetic operators.
+ // TODO: division, etc.
+ uint128& operator+=(const uint128& b);
+ uint128& operator-=(const uint128& b);
+ uint128& operator*=(const uint128& b);
+ uint128 operator++(int);
+ uint128 operator--(int);
+ uint128& operator<<=(int);
+ uint128& operator>>=(int);
+ uint128& operator&=(const uint128& b);
+ uint128& operator|=(const uint128& b);
+ uint128& operator^=(const uint128& b);
+ uint128& operator++();
+ uint128& operator--();
+
+ friend uint64 Uint128Low64(const uint128& v);
+ friend uint64 Uint128High64(const uint128& v);
+
+ // We add "std::" to avoid including all of port.h.
+ friend NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& o,
+ const uint128& b);
+
+private:
+ // Little-endian memory order optimizations can benefit from
+ // having lo_ first, hi_ last.
+ // See util/endian/endian.h and Load128/Store128 for storing a uint128.
+ uint64 lo_;
+ uint64 hi_;
+
+ // Not implemented, just declared for catching automatic type conversions.
+ uint128(uint8);
+ uint128(uint16);
+ uint128(float v);
+ uint128(double v);
+};
+
+// This is a POD form of uint128 which can be used for static variables which
+// need to be operated on as uint128.
+struct uint128_pod {
+ // Note: The ordering of fields is different than 'class uint128' but the
+ // same as its 2-arg constructor. This enables more obvious initialization
+ // of static instances, which is the primary reason for this struct in the
+ // first place. This does not seem to defeat any optimizations wrt
+ // operations involving this struct.
+ uint64 hi;
+ uint64 lo;
+};
+
+NET_EXPORT_PRIVATE extern const uint128_pod kuint128max;
+
+// allow uint128 to be logged
+NET_EXPORT_PRIVATE extern std::ostream& operator<<(std::ostream& o,
+ const uint128& b);
+
+// Methods to access low and high pieces of 128-bit value.
+// Defined externally from uint128 to facilitate conversion
+// to native 128-bit types when compilers support them.
+inline uint64 Uint128Low64(const uint128& v) { return v.lo_; }
+inline uint64 Uint128High64(const uint128& v) { return v.hi_; }
+
+// TODO: perhaps it would be nice to have int128, a signed 128-bit type?
+
+// --------------------------------------------------------------------------
+// Implementation details follow
+// --------------------------------------------------------------------------
+inline bool operator==(const uint128& lhs, const uint128& rhs) {
+ return (Uint128Low64(lhs) == Uint128Low64(rhs) &&
+ Uint128High64(lhs) == Uint128High64(rhs));
+}
+inline bool operator!=(const uint128& lhs, const uint128& rhs) {
+ return !(lhs == rhs);
+}
+inline uint128& uint128::operator=(const uint128& b) {
+ lo_ = b.lo_;
+ hi_ = b.hi_;
+ return *this;
+}
+
+inline uint128::uint128(): lo_(0), hi_(0) { }
+inline uint128::uint128(uint64 top, uint64 bottom) : lo_(bottom), hi_(top) { }
+inline uint128::uint128(const uint128 &v) : lo_(v.lo_), hi_(v.hi_) { }
+inline uint128::uint128(const uint128_pod &v) : lo_(v.lo), hi_(v.hi) { }
+inline uint128::uint128(uint64 bottom) : lo_(bottom), hi_(0) { }
+inline uint128::uint128(uint32 bottom) : lo_(bottom), hi_(0) { }
+inline uint128::uint128(int bottom) : lo_(bottom), hi_(0) {
+ if (bottom < 0) {
+ --hi_;
+ }
+}
+inline void uint128::Initialize(uint64 top, uint64 bottom) {
+ hi_ = top;
+ lo_ = bottom;
+}
+
+// Comparison operators.
+
+#define CMP128(op) \
+inline bool operator op(const uint128& lhs, const uint128& rhs) { \
+ return (Uint128High64(lhs) == Uint128High64(rhs)) ? \
+ (Uint128Low64(lhs) op Uint128Low64(rhs)) : \
+ (Uint128High64(lhs) op Uint128High64(rhs)); \
+}
+
+CMP128(<)
+CMP128(>)
+CMP128(>=)
+CMP128(<=)
+
+#undef CMP128
+
+// Unary operators
+
+inline uint128 operator-(const uint128& val) {
+ const uint64 hi_flip = ~Uint128High64(val);
+ const uint64 lo_flip = ~Uint128Low64(val);
+ const uint64 lo_add = lo_flip + 1;
+ if (lo_add < lo_flip) {
+ return uint128(hi_flip + 1, lo_add);
+ }
+ return uint128(hi_flip, lo_add);
+}
+
+inline bool operator!(const uint128& val) {
+ return !Uint128High64(val) && !Uint128Low64(val);
+}
+
+// Logical operators.
+
+inline uint128 operator~(const uint128& val) {
+ return uint128(~Uint128High64(val), ~Uint128Low64(val));
+}
+
+#define LOGIC128(op) \
+inline uint128 operator op(const uint128& lhs, const uint128& rhs) { \
+ return uint128(Uint128High64(lhs) op Uint128High64(rhs), \
+ Uint128Low64(lhs) op Uint128Low64(rhs)); \
+}
+
+LOGIC128(|)
+LOGIC128(&)
+LOGIC128(^)
+
+#undef LOGIC128
+
+#define LOGICASSIGN128(op) \
+inline uint128& uint128::operator op(const uint128& other) { \
+ hi_ op other.hi_; \
+ lo_ op other.lo_; \
+ return *this; \
+}
+
+LOGICASSIGN128(|=)
+LOGICASSIGN128(&=)
+LOGICASSIGN128(^=)
+
+#undef LOGICASSIGN128
+
+// Shift operators.
+
+inline uint128 operator<<(const uint128& val, int amount) {
+ // uint64 shifts of >= 64 are undefined, so we will need some special-casing.
+ if (amount < 64) {
+ if (amount == 0) {
+ return val;
+ }
+ uint64 new_hi = (Uint128High64(val) << amount) |
+ (Uint128Low64(val) >> (64 - amount));
+ uint64 new_lo = Uint128Low64(val) << amount;
+ return uint128(new_hi, new_lo);
+ } else if (amount < 128) {
+ return uint128(Uint128Low64(val) << (amount - 64), 0);
+ } else {
+ return uint128(0, 0);
+ }
+}
+
+inline uint128 operator>>(const uint128& val, int amount) {
+ // uint64 shifts of >= 64 are undefined, so we will need some special-casing.
+ if (amount < 64) {
+ if (amount == 0) {
+ return val;
+ }
+ uint64 new_hi = Uint128High64(val) >> amount;
+ uint64 new_lo = (Uint128Low64(val) >> amount) |
+ (Uint128High64(val) << (64 - amount));
+ return uint128(new_hi, new_lo);
+ } else if (amount < 128) {
+ return uint128(0, Uint128High64(val) >> (amount - 64));
+ } else {
+ return uint128(0, 0);
+ }
+}
+
+inline uint128& uint128::operator<<=(int amount) {
+ // uint64 shifts of >= 64 are undefined, so we will need some special-casing.
+ if (amount < 64) {
+ if (amount != 0) {
+ hi_ = (hi_ << amount) | (lo_ >> (64 - amount));
+ lo_ = lo_ << amount;
+ }
+ } else if (amount < 128) {
+ hi_ = lo_ << (amount - 64);
+ lo_ = 0;
+ } else {
+ hi_ = 0;
+ lo_ = 0;
+ }
+ return *this;
+}
+
+inline uint128& uint128::operator>>=(int amount) {
+ // uint64 shifts of >= 64 are undefined, so we will need some special-casing.
+ if (amount < 64) {
+ if (amount != 0) {
+ lo_ = (lo_ >> amount) | (hi_ << (64 - amount));
+ hi_ = hi_ >> amount;
+ }
+ } else if (amount < 128) {
+ hi_ = 0;
+ lo_ = hi_ >> (amount - 64);
+ } else {
+ hi_ = 0;
+ lo_ = 0;
+ }
+ return *this;
+}
+
+inline uint128 operator+(const uint128& lhs, const uint128& rhs) {
+ return uint128(lhs) += rhs;
+}
+
+inline uint128 operator-(const uint128& lhs, const uint128& rhs) {
+ return uint128(lhs) -= rhs;
+}
+
+inline uint128 operator*(const uint128& lhs, const uint128& rhs) {
+ return uint128(lhs) *= rhs;
+}
+
+inline uint128& uint128::operator+=(const uint128& b) {
+ hi_ += b.hi_;
+ uint64 lolo = lo_ + b.lo_;
+ if (lolo < lo_)
+ ++hi_;
+ lo_ = lolo;
+ return *this;
+}
+
+inline uint128& uint128::operator-=(const uint128& b) {
+ hi_ -= b.hi_;
+ if (b.lo_ > lo_)
+ --hi_;
+ lo_ -= b.lo_;
+ return *this;
+}
+
+inline uint128& uint128::operator*=(const uint128& b) {
+ uint64 a96 = hi_ >> 32;
+ uint64 a64 = hi_ & 0xffffffffu;
+ uint64 a32 = lo_ >> 32;
+ uint64 a00 = lo_ & 0xffffffffu;
+ uint64 b96 = b.hi_ >> 32;
+ uint64 b64 = b.hi_ & 0xffffffffu;
+ uint64 b32 = b.lo_ >> 32;
+ uint64 b00 = b.lo_ & 0xffffffffu;
+ // multiply [a96 .. a00] x [b96 .. b00]
+ // terms higher than c96 disappear off the high side
+ // terms c96 and c64 are safe to ignore carry bit
+ uint64 c96 = a96 * b00 + a64 * b32 + a32 * b64 + a00 * b96;
+ uint64 c64 = a64 * b00 + a32 * b32 + a00 * b64;
+ this->hi_ = (c96 << 32) + c64;
+ this->lo_ = 0;
+ // add terms after this one at a time to capture carry
+ *this += uint128(a32 * b00) << 32;
+ *this += uint128(a00 * b32) << 32;
+ *this += a00 * b00;
+ return *this;
+}
+
+inline uint128 uint128::operator++(int) {
+ uint128 tmp(*this);
+ *this += 1;
+ return tmp;
+}
+
+inline uint128 uint128::operator--(int) {
+ uint128 tmp(*this);
+ *this -= 1;
+ return tmp;
+}
+
+inline uint128& uint128::operator++() {
+ *this += 1;
+ return *this;
+}
+
+inline uint128& uint128::operator--() {
+ *this -= 1;
+ return *this;
+}
+
+#endif // NET_BASE_INT128_H_
diff --git a/chromium/net/base/int128_unittest.cc b/chromium/net/base/int128_unittest.cc
new file mode 100644
index 00000000000..78790e765fd
--- /dev/null
+++ b/chromium/net/base/int128_unittest.cc
@@ -0,0 +1,265 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/base/int128.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+
+TEST(Int128, AllTests) {
+ uint128 zero(0);
+ uint128 one(1);
+ uint128 one_2arg(0, 1);
+ uint128 two(0, 2);
+ uint128 three(0, 3);
+ uint128 big(2000, 2);
+ uint128 big_minus_one(2000, 1);
+ uint128 bigger(2001, 1);
+ uint128 biggest(kuint128max);
+ uint128 high_low(1, 0);
+ uint128 low_high(0, kuint64max);
+ EXPECT_LT(one, two);
+ EXPECT_GT(two, one);
+ EXPECT_LT(one, big);
+ EXPECT_LT(one, big);
+ EXPECT_EQ(one, one_2arg);
+ EXPECT_NE(one, two);
+ EXPECT_GT(big, one);
+ EXPECT_GE(big, two);
+ EXPECT_GE(big, big_minus_one);
+ EXPECT_GT(big, big_minus_one);
+ EXPECT_LT(big_minus_one, big);
+ EXPECT_LE(big_minus_one, big);
+ EXPECT_NE(big_minus_one, big);
+ EXPECT_LT(big, biggest);
+ EXPECT_LE(big, biggest);
+ EXPECT_GT(biggest, big);
+ EXPECT_GE(biggest, big);
+ EXPECT_EQ(big, ~~big);
+ EXPECT_EQ(one, one | one);
+ EXPECT_EQ(big, big | big);
+ EXPECT_EQ(one, one | zero);
+ EXPECT_EQ(one, one & one);
+ EXPECT_EQ(big, big & big);
+ EXPECT_EQ(zero, one & zero);
+ EXPECT_EQ(zero, big & ~big);
+ EXPECT_EQ(zero, one ^ one);
+ EXPECT_EQ(zero, big ^ big);
+ EXPECT_EQ(one, one ^ zero);
+ EXPECT_EQ(big, big << 0);
+ EXPECT_EQ(big, big >> 0);
+ EXPECT_GT(big << 1, big);
+ EXPECT_LT(big >> 1, big);
+ EXPECT_EQ(big, (big << 10) >> 10);
+ EXPECT_EQ(big, (big >> 1) << 1);
+ EXPECT_EQ(one, (one << 80) >> 80);
+ EXPECT_EQ(zero, (one >> 80) << 80);
+ EXPECT_EQ(zero, big >> 128);
+ EXPECT_EQ(zero, big << 128);
+ EXPECT_EQ(Uint128High64(biggest), kuint64max);
+ EXPECT_EQ(Uint128Low64(biggest), kuint64max);
+ EXPECT_EQ(zero + one, one);
+ EXPECT_EQ(one + one, two);
+ EXPECT_EQ(big_minus_one + one, big);
+ EXPECT_EQ(one - one, zero);
+ EXPECT_EQ(one - zero, one);
+ EXPECT_EQ(zero - one, biggest);
+ EXPECT_EQ(big - big, zero);
+ EXPECT_EQ(big - one, big_minus_one);
+ EXPECT_EQ(big + kuint64max, bigger);
+ EXPECT_EQ(biggest + 1, zero);
+ EXPECT_EQ(zero - 1, biggest);
+ EXPECT_EQ(high_low - one, low_high);
+ EXPECT_EQ(low_high + one, high_low);
+ EXPECT_EQ(Uint128High64((uint128(1) << 64) - 1), 0u);
+ EXPECT_EQ(Uint128Low64((uint128(1) << 64) - 1), kuint64max);
+ EXPECT_TRUE(!!one);
+ EXPECT_TRUE(!!high_low);
+ EXPECT_FALSE(!!zero);
+ EXPECT_FALSE(!one);
+ EXPECT_FALSE(!high_low);
+ EXPECT_TRUE(!zero);
+ EXPECT_TRUE(zero == 0);
+ EXPECT_FALSE(zero != 0);
+ EXPECT_FALSE(one == 0);
+ EXPECT_TRUE(one != 0);
+
+ uint128 test = zero;
+ EXPECT_EQ(++test, one);
+ EXPECT_EQ(test, one);
+ EXPECT_EQ(test++, one);
+ EXPECT_EQ(test, two);
+ EXPECT_EQ(test -= 2, zero);
+ EXPECT_EQ(test, zero);
+ EXPECT_EQ(test += 2, two);
+ EXPECT_EQ(test, two);
+ EXPECT_EQ(--test, one);
+ EXPECT_EQ(test, one);
+ EXPECT_EQ(test--, one);
+ EXPECT_EQ(test, zero);
+ EXPECT_EQ(test |= three, three);
+ EXPECT_EQ(test &= one, one);
+ EXPECT_EQ(test ^= three, two);
+ EXPECT_EQ(test >>= 1, one);
+ EXPECT_EQ(test <<= 1, two);
+
+ EXPECT_EQ(big, -(-big));
+ EXPECT_EQ(two, -((-one) - 1));
+ EXPECT_EQ(kuint128max, -one);
+ EXPECT_EQ(zero, -zero);
+
+ LOG(INFO) << one;
+ LOG(INFO) << big_minus_one;
+}
+
+TEST(Int128, PodTests) {
+ uint128_pod pod = { 12345, 67890 };
+ uint128 from_pod(pod);
+ EXPECT_EQ(12345u, Uint128High64(from_pod));
+ EXPECT_EQ(67890u, Uint128Low64(from_pod));
+
+ uint128 zero(0);
+ uint128_pod zero_pod = {0, 0};
+ uint128 one(1);
+ uint128_pod one_pod = {0, 1};
+ uint128 two(2);
+ uint128_pod two_pod = {0, 2};
+ uint128 three(3);
+ uint128_pod three_pod = {0, 3};
+ uint128 big(1, 0);
+ uint128_pod big_pod = {1, 0};
+
+ EXPECT_EQ(zero, zero_pod);
+ EXPECT_EQ(zero_pod, zero);
+ EXPECT_EQ(zero_pod, zero_pod);
+ EXPECT_EQ(one, one_pod);
+ EXPECT_EQ(one_pod, one);
+ EXPECT_EQ(one_pod, one_pod);
+ EXPECT_EQ(two, two_pod);
+ EXPECT_EQ(two_pod, two);
+ EXPECT_EQ(two_pod, two_pod);
+
+ EXPECT_NE(one, two_pod);
+ EXPECT_NE(one_pod, two);
+ EXPECT_NE(one_pod, two_pod);
+
+ EXPECT_LT(one, two_pod);
+ EXPECT_LT(one_pod, two);
+ EXPECT_LT(one_pod, two_pod);
+ EXPECT_LE(one, one_pod);
+ EXPECT_LE(one_pod, one);
+ EXPECT_LE(one_pod, one_pod);
+ EXPECT_LE(one, two_pod);
+ EXPECT_LE(one_pod, two);
+ EXPECT_LE(one_pod, two_pod);
+
+ EXPECT_GT(two, one_pod);
+ EXPECT_GT(two_pod, one);
+ EXPECT_GT(two_pod, one_pod);
+ EXPECT_GE(two, two_pod);
+ EXPECT_GE(two_pod, two);
+ EXPECT_GE(two_pod, two_pod);
+ EXPECT_GE(two, one_pod);
+ EXPECT_GE(two_pod, one);
+ EXPECT_GE(two_pod, one_pod);
+
+ EXPECT_EQ(three, one | two_pod);
+ EXPECT_EQ(three, one_pod | two);
+ EXPECT_EQ(three, one_pod | two_pod);
+ EXPECT_EQ(one, three & one_pod);
+ EXPECT_EQ(one, three_pod & one);
+ EXPECT_EQ(one, three_pod & one_pod);
+ EXPECT_EQ(two, three ^ one_pod);
+ EXPECT_EQ(two, three_pod ^ one);
+ EXPECT_EQ(two, three_pod ^ one_pod);
+ EXPECT_EQ(two, three & (~one));
+ EXPECT_EQ(three, ~~three);
+
+ EXPECT_EQ(two, two_pod << 0);
+ EXPECT_EQ(two, one_pod << 1);
+ EXPECT_EQ(big, one_pod << 64);
+ EXPECT_EQ(zero, one_pod << 128);
+ EXPECT_EQ(two, two_pod >> 0);
+ EXPECT_EQ(one, two_pod >> 1);
+ EXPECT_EQ(one, big_pod >> 64);
+
+ EXPECT_EQ(one, zero + one_pod);
+ EXPECT_EQ(one, zero_pod + one);
+ EXPECT_EQ(one, zero_pod + one_pod);
+ EXPECT_EQ(one, two - one_pod);
+ EXPECT_EQ(one, two_pod - one);
+ EXPECT_EQ(one, two_pod - one_pod);
+}
+
+TEST(Int128, OperatorAssignReturnRef) {
+ uint128 v(1);
+ (v += 4) -= 3;
+ EXPECT_EQ(2, v);
+}
+
+TEST(Int128, Multiply) {
+ uint128 a, b, c;
+
+ // Zero test.
+ a = 0;
+ b = 0;
+ c = a * b;
+ EXPECT_EQ(0, c);
+
+ // Max carries.
+ a = uint128(0) - 1;
+ b = uint128(0) - 1;
+ c = a * b;
+ EXPECT_EQ(1, c);
+
+ // Self-operation with max carries.
+ c = uint128(0) - 1;
+ c *= c;
+ EXPECT_EQ(1, c);
+
+ // 1-bit x 1-bit.
+ for (int i = 0; i < 64; ++i) {
+ for (int j = 0; j < 64; ++j) {
+ a = uint128(1) << i;
+ b = uint128(1) << j;
+ c = a * b;
+ EXPECT_EQ(uint128(1) << (i+j), c);
+ }
+ }
+
+ // Verified with dc.
+ a = uint128(GG_ULONGLONG(0xffffeeeeddddcccc),
+ GG_ULONGLONG(0xbbbbaaaa99998888));
+ b = uint128(GG_ULONGLONG(0x7777666655554444),
+ GG_ULONGLONG(0x3333222211110000));
+ c = a * b;
+ EXPECT_EQ(uint128(GG_ULONGLONG(0x530EDA741C71D4C3),
+ GG_ULONGLONG(0xBF25975319080000)), c);
+ EXPECT_EQ(0, c - b * a);
+ EXPECT_EQ(a*a - b*b, (a+b) * (a-b));
+
+ // Verified with dc.
+ a = uint128(GG_ULONGLONG(0x0123456789abcdef),
+ GG_ULONGLONG(0xfedcba9876543210));
+ b = uint128(GG_ULONGLONG(0x02468ace13579bdf),
+ GG_ULONGLONG(0xfdb97531eca86420));
+ c = a * b;
+ EXPECT_EQ(uint128(GG_ULONGLONG(0x97a87f4f261ba3f2),
+ GG_ULONGLONG(0x342d0bbf48948200)), c);
+ EXPECT_EQ(0, c - b * a);
+ EXPECT_EQ(a*a - b*b, (a+b) * (a-b));
+}
+
+TEST(Int128, AliasTests) {
+ uint128 x1(1, 2);
+ uint128 x2(2, 4);
+ x1 += x1;
+ EXPECT_EQ(x2, x1);
+
+ uint128 x3(1, 1ull << 63);
+ uint128 x4(3, 0);
+ x3 += x3;
+ EXPECT_EQ(x4, x3);
+}
diff --git a/chromium/net/base/io_buffer.cc b/chromium/net/base/io_buffer.cc
new file mode 100644
index 00000000000..dd1d4517eea
--- /dev/null
+++ b/chromium/net/base/io_buffer.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2011 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/base/io_buffer.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+IOBuffer::IOBuffer()
+ : data_(NULL) {
+}
+
+IOBuffer::IOBuffer(int buffer_size) {
+ CHECK_GE(buffer_size, 0);
+ data_ = new char[buffer_size];
+}
+
+IOBuffer::IOBuffer(char* data)
+ : data_(data) {
+}
+
+IOBuffer::~IOBuffer() {
+ delete[] data_;
+ data_ = NULL;
+}
+
+IOBufferWithSize::IOBufferWithSize(int size)
+ : IOBuffer(size),
+ size_(size) {
+}
+
+IOBufferWithSize::IOBufferWithSize(char* data, int size)
+ : IOBuffer(data),
+ size_(size) {
+}
+
+IOBufferWithSize::~IOBufferWithSize() {
+}
+
+StringIOBuffer::StringIOBuffer(const std::string& s)
+ : IOBuffer(static_cast<char*>(NULL)),
+ string_data_(s) {
+ CHECK_LT(s.size(), static_cast<size_t>(INT_MAX));
+ data_ = const_cast<char*>(string_data_.data());
+}
+
+StringIOBuffer::~StringIOBuffer() {
+ // We haven't allocated the buffer, so remove it before the base class
+ // destructor tries to delete[] it.
+ data_ = NULL;
+}
+
+DrainableIOBuffer::DrainableIOBuffer(IOBuffer* base, int size)
+ : IOBuffer(base->data()),
+ base_(base),
+ size_(size),
+ used_(0) {
+}
+
+void DrainableIOBuffer::DidConsume(int bytes) {
+ SetOffset(used_ + bytes);
+}
+
+int DrainableIOBuffer::BytesRemaining() const {
+ return size_ - used_;
+}
+
+// Returns the number of consumed bytes.
+int DrainableIOBuffer::BytesConsumed() const {
+ return used_;
+}
+
+void DrainableIOBuffer::SetOffset(int bytes) {
+ DCHECK_GE(bytes, 0);
+ DCHECK_LE(bytes, size_);
+ used_ = bytes;
+ data_ = base_->data() + used_;
+}
+
+DrainableIOBuffer::~DrainableIOBuffer() {
+ // The buffer is owned by the |base_| instance.
+ data_ = NULL;
+}
+
+GrowableIOBuffer::GrowableIOBuffer()
+ : IOBuffer(),
+ capacity_(0),
+ offset_(0) {
+}
+
+void GrowableIOBuffer::SetCapacity(int capacity) {
+ DCHECK_GE(capacity, 0);
+ // realloc will crash if it fails.
+ real_data_.reset(static_cast<char*>(realloc(real_data_.release(), capacity)));
+ capacity_ = capacity;
+ if (offset_ > capacity)
+ set_offset(capacity);
+ else
+ set_offset(offset_); // The pointer may have changed.
+}
+
+void GrowableIOBuffer::set_offset(int offset) {
+ DCHECK_GE(offset, 0);
+ DCHECK_LE(offset, capacity_);
+ offset_ = offset;
+ data_ = real_data_.get() + offset;
+}
+
+int GrowableIOBuffer::RemainingCapacity() {
+ return capacity_ - offset_;
+}
+
+char* GrowableIOBuffer::StartOfBuffer() {
+ return real_data_.get();
+}
+
+GrowableIOBuffer::~GrowableIOBuffer() {
+ data_ = NULL;
+}
+
+PickledIOBuffer::PickledIOBuffer() : IOBuffer() {}
+
+void PickledIOBuffer::Done() {
+ data_ = const_cast<char*>(static_cast<const char*>(pickle_.data()));
+}
+
+PickledIOBuffer::~PickledIOBuffer() { data_ = NULL; }
+
+WrappedIOBuffer::WrappedIOBuffer(const char* data)
+ : IOBuffer(const_cast<char*>(data)) {
+}
+
+WrappedIOBuffer::~WrappedIOBuffer() {
+ data_ = NULL;
+}
+
+} // namespace net
diff --git a/chromium/net/base/io_buffer.h b/chromium/net/base/io_buffer.h
new file mode 100644
index 00000000000..77712e7e7af
--- /dev/null
+++ b/chromium/net/base/io_buffer.h
@@ -0,0 +1,244 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_IO_BUFFER_H_
+#define NET_BASE_IO_BUFFER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// IOBuffers are reference counted data buffers used for easier asynchronous
+// IO handling.
+//
+// They are often used as the destination buffers for Read() operations, or as
+// the source buffers for Write() operations.
+//
+// IMPORTANT: Never re-use an IOBuffer after cancelling the IO operation that
+// was using it, since this may lead to memory corruption!
+//
+// -----------------------
+// Ownership of IOBuffers:
+// -----------------------
+//
+// Although IOBuffers are RefCountedThreadSafe, they are not intended to be
+// used as a shared buffer, nor should they be used simultaneously across
+// threads. The fact that they are reference counted is an implementation
+// detail for allowing them to outlive cancellation of asynchronous
+// operations.
+//
+// Instead, think of the underlying |char*| buffer contained by the IOBuffer
+// as having exactly one owner at a time.
+//
+// Whenever you call an asynchronous operation that takes an IOBuffer,
+// ownership is implicitly transferred to the called function, until the
+// operation has completed (at which point it transfers back to the caller).
+//
+// ==> The IOBuffer's data should NOT be manipulated, destroyed, or read
+// until the operation has completed.
+//
+// ==> Cancellation does NOT count as completion. If an operation using
+// an IOBuffer is cancelled, the caller should release their
+// reference to this IOBuffer at the time of cancellation since
+// they can no longer use it.
+//
+// For instance, if you were to call a Read() operation on some class which
+// takes an IOBuffer, and then delete that class (which generally will
+// trigger cancellation), the IOBuffer which had been passed to Read() should
+// never be re-used.
+//
+// This usage contract is assumed by any API which takes an IOBuffer, even
+// though it may not be explicitly mentioned in the function's comments.
+//
+// -----------------------
+// Motivation
+// -----------------------
+//
+// The motivation for transferring ownership during cancellation is
+// to make it easier to work with un-cancellable operations.
+//
+// For instance, let's say under the hood your API called out to the
+// operating system's synchronous ReadFile() function on a worker thread.
+// When cancelling through our asynchronous interface, we have no way of
+// actually aborting the in progress ReadFile(). We must let it keep running,
+// and hence the buffer it was reading into must remain alive. Using
+// reference counting we can add a reference to the IOBuffer and make sure
+// it is not destroyed until after the synchronous operation has completed.
+class NET_EXPORT IOBuffer : public base::RefCountedThreadSafe<IOBuffer> {
+ public:
+ IOBuffer();
+ explicit IOBuffer(int buffer_size);
+
+ char* data() { return data_; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<IOBuffer>;
+
+ // Only allow derived classes to specify data_.
+ // In all other cases, we own data_, and must delete it at destruction time.
+ explicit IOBuffer(char* data);
+
+ virtual ~IOBuffer();
+
+ char* data_;
+};
+
+// This version stores the size of the buffer so that the creator of the object
+// doesn't have to keep track of that value.
+// NOTE: This doesn't mean that we want to stop sending the size as an explicit
+// argument to IO functions. Please keep using IOBuffer* for API declarations.
+class NET_EXPORT IOBufferWithSize : public IOBuffer {
+ public:
+ explicit IOBufferWithSize(int size);
+
+ int size() const { return size_; }
+
+ protected:
+ // Purpose of this constructor is to give a subclass access to the base class
+ // constructor IOBuffer(char*) thus allowing subclass to use underlying
+ // memory it does not own.
+ IOBufferWithSize(char* data, int size);
+ virtual ~IOBufferWithSize();
+
+ int size_;
+};
+
+// This is a read only IOBuffer. The data is stored in a string and
+// the IOBuffer interface does not provide a proper way to modify it.
+class NET_EXPORT StringIOBuffer : public IOBuffer {
+ public:
+ explicit StringIOBuffer(const std::string& s);
+
+ int size() const { return static_cast<int>(string_data_.size()); }
+
+ private:
+ virtual ~StringIOBuffer();
+
+ std::string string_data_;
+};
+
+// This version wraps an existing IOBuffer and provides convenient functions
+// to progressively read all the data.
+//
+// DrainableIOBuffer is useful when you have an IOBuffer that contains data
+// to be written progressively, and Write() function takes an IOBuffer rather
+// than char*. DrainableIOBuffer can be used as follows:
+//
+// // payload is the IOBuffer containing the data to be written.
+// buf = new DrainableIOBuffer(payload, payload_size);
+//
+// while (buf->BytesRemaining() > 0) {
+// // Write() takes an IOBuffer. If it takes char*, we could
+// // simply use the regular IOBuffer like payload->data() + offset.
+// int bytes_written = Write(buf, buf->BytesRemaining());
+// buf->DidConsume(bytes_written);
+// }
+//
+class NET_EXPORT DrainableIOBuffer : public IOBuffer {
+ public:
+ DrainableIOBuffer(IOBuffer* base, int size);
+
+ // DidConsume() changes the |data_| pointer so that |data_| always points
+ // to the first unconsumed byte.
+ void DidConsume(int bytes);
+
+ // Returns the number of unconsumed bytes.
+ int BytesRemaining() const;
+
+ // Returns the number of consumed bytes.
+ int BytesConsumed() const;
+
+ // Seeks to an arbitrary point in the buffer. The notion of bytes consumed
+ // and remaining are updated appropriately.
+ void SetOffset(int bytes);
+
+ int size() const { return size_; }
+
+ private:
+ virtual ~DrainableIOBuffer();
+
+ scoped_refptr<IOBuffer> base_;
+ int size_;
+ int used_;
+};
+
+// This version provides a resizable buffer and a changeable offset.
+//
+// GrowableIOBuffer is useful when you read data progressively without
+// knowing the total size in advance. GrowableIOBuffer can be used as
+// follows:
+//
+// buf = new GrowableIOBuffer;
+// buf->SetCapacity(1024); // Initial capacity.
+//
+// while (!some_stream->IsEOF()) {
+// // Double the capacity if the remaining capacity is empty.
+// if (buf->RemainingCapacity() == 0)
+// buf->SetCapacity(buf->capacity() * 2);
+// int bytes_read = some_stream->Read(buf, buf->RemainingCapacity());
+// buf->set_offset(buf->offset() + bytes_read);
+// }
+//
+class NET_EXPORT GrowableIOBuffer : public IOBuffer {
+ public:
+ GrowableIOBuffer();
+
+ // realloc memory to the specified capacity.
+ void SetCapacity(int capacity);
+ int capacity() { return capacity_; }
+
+ // |offset| moves the |data_| pointer, allowing "seeking" in the data.
+ void set_offset(int offset);
+ int offset() { return offset_; }
+
+ int RemainingCapacity();
+ char* StartOfBuffer();
+
+ private:
+ virtual ~GrowableIOBuffer();
+
+ scoped_ptr_malloc<char> real_data_;
+ int capacity_;
+ int offset_;
+};
+
+// This versions allows a pickle to be used as the storage for a write-style
+// operation, avoiding an extra data copy.
+class NET_EXPORT PickledIOBuffer : public IOBuffer {
+ public:
+ PickledIOBuffer();
+
+ Pickle* pickle() { return &pickle_; }
+
+ // Signals that we are done writing to the pickle and we can use it for a
+ // write-style IO operation.
+ void Done();
+
+ private:
+ virtual ~PickledIOBuffer();
+
+ Pickle pickle_;
+};
+
+// This class allows the creation of a temporary IOBuffer that doesn't really
+// own the underlying buffer. Please use this class only as a last resort.
+// A good example is the buffer for a synchronous operation, where we can be
+// sure that nobody is keeping an extra reference to this object so the lifetime
+// of the buffer can be completely managed by its intended owner.
+class NET_EXPORT WrappedIOBuffer : public IOBuffer {
+ public:
+ explicit WrappedIOBuffer(const char* data);
+
+ protected:
+ virtual ~WrappedIOBuffer();
+};
+
+} // namespace net
+
+#endif // NET_BASE_IO_BUFFER_H_
diff --git a/chromium/net/base/iovec.h b/chromium/net/base/iovec.h
new file mode 100644
index 00000000000..f1a85e3b53c
--- /dev/null
+++ b/chromium/net/base/iovec.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2013 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_BASE_IOVEC_H_
+#define NET_BASE_IOVEC_H_
+
+#if defined(OS_POSIX)
+#include <sys/uio.h>
+#else
+/* Structure for scatter/gather I/O. */
+struct iovec {
+ void* iov_base; /* Pointer to data. */
+ size_t iov_len; /* Length of data. */
+};
+#endif // defined(OS_LINUX)
+
+#endif // NET_BASE_IOVEC_H_
diff --git a/chromium/net/base/ip_endpoint.cc b/chromium/net/base/ip_endpoint.cc
new file mode 100644
index 00000000000..a0d378ee4cc
--- /dev/null
+++ b/chromium/net/base/ip_endpoint.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 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.
+
+#include "net/base/ip_endpoint.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/sys_byteorder.h"
+#if defined(OS_WIN)
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
+
+namespace net {
+
+namespace {
+// By definition, socklen_t is large enough to hold both sizes.
+const socklen_t kSockaddrInSize = sizeof(struct sockaddr_in);
+const socklen_t kSockaddrIn6Size = sizeof(struct sockaddr_in6);
+}
+
+IPEndPoint::IPEndPoint() : port_(0) {}
+
+IPEndPoint::~IPEndPoint() {}
+
+IPEndPoint::IPEndPoint(const IPAddressNumber& address, int port)
+ : address_(address),
+ port_(port) {}
+
+IPEndPoint::IPEndPoint(const IPEndPoint& endpoint) {
+ address_ = endpoint.address_;
+ port_ = endpoint.port_;
+}
+
+AddressFamily IPEndPoint::GetFamily() const {
+ return GetAddressFamily(address_);
+}
+
+int IPEndPoint::GetSockAddrFamily() const {
+ switch (address_.size()) {
+ case kIPv4AddressSize:
+ return AF_INET;
+ case kIPv6AddressSize:
+ return AF_INET6;
+ default:
+ NOTREACHED() << "Bad IP address";
+ return AF_UNSPEC;
+ }
+}
+
+bool IPEndPoint::ToSockAddr(struct sockaddr* address,
+ socklen_t* address_length) const {
+ DCHECK(address);
+ DCHECK(address_length);
+ switch (address_.size()) {
+ case kIPv4AddressSize: {
+ if (*address_length < kSockaddrInSize)
+ return false;
+ *address_length = kSockaddrInSize;
+ struct sockaddr_in* addr = reinterpret_cast<struct sockaddr_in*>(address);
+ memset(addr, 0, sizeof(struct sockaddr_in));
+ addr->sin_family = AF_INET;
+ addr->sin_port = base::HostToNet16(port_);
+ memcpy(&addr->sin_addr, &address_[0], kIPv4AddressSize);
+ break;
+ }
+ case kIPv6AddressSize: {
+ if (*address_length < kSockaddrIn6Size)
+ return false;
+ *address_length = kSockaddrIn6Size;
+ struct sockaddr_in6* addr6 =
+ reinterpret_cast<struct sockaddr_in6*>(address);
+ memset(addr6, 0, sizeof(struct sockaddr_in6));
+ addr6->sin6_family = AF_INET6;
+ addr6->sin6_port = base::HostToNet16(port_);
+ memcpy(&addr6->sin6_addr, &address_[0], kIPv6AddressSize);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool IPEndPoint::FromSockAddr(const struct sockaddr* sock_addr,
+ socklen_t sock_addr_len) {
+ DCHECK(sock_addr);
+
+ const uint8* address;
+ size_t address_len;
+ uint16 port;
+ if (!GetIPAddressFromSockAddr(sock_addr, sock_addr_len, &address,
+ &address_len, &port)) {
+ return false;
+ }
+
+ address_.assign(address, address + address_len);
+ port_ = port;
+ return true;
+}
+
+std::string IPEndPoint::ToString() const {
+ return IPAddressToStringWithPort(address_, port_);
+}
+
+std::string IPEndPoint::ToStringWithoutPort() const {
+ return IPAddressToString(address_);
+}
+
+bool IPEndPoint::operator<(const IPEndPoint& that) const {
+ // Sort IPv4 before IPv6.
+ if (address_.size() != that.address_.size()) {
+ return address_.size() < that.address_.size();
+ }
+ if (address_ != that.address_) {
+ return address_ < that.address_;
+ }
+ return port_ < that.port_;
+}
+
+bool IPEndPoint::operator==(const IPEndPoint& that) const {
+ return address_ == that.address_ && port_ == that.port_;
+}
+
+} // namespace net
diff --git a/chromium/net/base/ip_endpoint.h b/chromium/net/base/ip_endpoint.h
new file mode 100644
index 00000000000..1fc7e0420b8
--- /dev/null
+++ b/chromium/net/base/ip_endpoint.h
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_IP_ENDPOINT_H_
+#define NET_BASE_IP_ENDPOINT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/address_family.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h"
+
+struct sockaddr;
+
+namespace net {
+
+// An IPEndPoint represents the address of a transport endpoint:
+// * IP address (either v4 or v6)
+// * Port
+class NET_EXPORT IPEndPoint {
+ public:
+ IPEndPoint();
+ virtual ~IPEndPoint();
+ IPEndPoint(const IPAddressNumber& address, int port);
+ IPEndPoint(const IPEndPoint& endpoint);
+
+ const IPAddressNumber& address() const { return address_; }
+ int port() const { return port_; }
+
+ // Returns AddressFamily of the address.
+ AddressFamily GetFamily() const;
+
+ // Returns the sockaddr family of the address, AF_INET or AF_INET6.
+ int GetSockAddrFamily() const;
+
+ // Convert to a provided sockaddr struct.
+ // |address| is the sockaddr to copy into. Should be at least
+ // sizeof(struct sockaddr_storage) bytes.
+ // |address_length| is an input/output parameter. On input, it is the
+ // size of data in |address| available. On output, it is the size of
+ // the address that was copied into |address|.
+ // Returns true on success, false on failure.
+ bool ToSockAddr(struct sockaddr* address, socklen_t* address_length) const
+ WARN_UNUSED_RESULT;
+
+ // Convert from a sockaddr struct.
+ // |address| is the address.
+ // |address_length| is the length of |address|.
+ // Returns true on success, false on failure.
+ bool FromSockAddr(const struct sockaddr* address, socklen_t address_length)
+ WARN_UNUSED_RESULT;
+
+ // Returns value as a string (e.g. "127.0.0.1:80"). Returns empty
+ // string if the address is invalid, and cannot not be converted to a
+ // string.
+ std::string ToString() const;
+
+ // As above, but without port.
+ std::string ToStringWithoutPort() const;
+
+ bool operator<(const IPEndPoint& that) const;
+ bool operator==(const IPEndPoint& that) const;
+
+ private:
+ IPAddressNumber address_;
+ int port_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_IP_ENDPOINT_H_
diff --git a/chromium/net/base/ip_endpoint_unittest.cc b/chromium/net/base/ip_endpoint_unittest.cc
new file mode 100644
index 00000000000..f10e8e99737
--- /dev/null
+++ b/chromium/net/base/ip_endpoint_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright (c) 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.
+
+#include "net/base/ip_endpoint.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "net/base/net_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#if defined(OS_WIN)
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
+
+namespace net {
+
+namespace {
+
+struct TestData {
+ std::string host;
+ std::string host_normalized;
+ bool ipv6;
+ IPAddressNumber ip_address;
+} tests[] = {
+ { "127.0.00.1", "127.0.0.1", false},
+ { "192.168.1.1", "192.168.1.1", false },
+ { "::1", "[::1]", true },
+ { "2001:db8:0::42", "[2001:db8::42]", true },
+};
+int test_count = ARRAYSIZE_UNSAFE(tests);
+
+class IPEndPointTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ // This is where we populate the TestData.
+ for (int index = 0; index < test_count; ++index) {
+ EXPECT_TRUE(ParseIPLiteralToNumber(tests[index].host,
+ &tests[index].ip_address));
+ }
+ }
+};
+
+TEST_F(IPEndPointTest, Constructor) {
+ IPEndPoint endpoint;
+ EXPECT_EQ(0, endpoint.port());
+
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint endpoint(tests[index].ip_address, 80);
+ EXPECT_EQ(80, endpoint.port());
+ EXPECT_EQ(tests[index].ip_address, endpoint.address());
+ }
+}
+
+TEST_F(IPEndPointTest, Assignment) {
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint src(tests[index].ip_address, index);
+ IPEndPoint dest = src;
+
+ EXPECT_EQ(src.port(), dest.port());
+ EXPECT_EQ(src.address(), dest.address());
+ }
+}
+
+TEST_F(IPEndPointTest, Copy) {
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint src(tests[index].ip_address, index);
+ IPEndPoint dest(src);
+
+ EXPECT_EQ(src.port(), dest.port());
+ EXPECT_EQ(src.address(), dest.address());
+ }
+}
+
+TEST_F(IPEndPointTest, ToFromSockAddr) {
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint ip_endpoint(tests[index].ip_address, index);
+
+ // Convert to a sockaddr.
+ SockaddrStorage storage;
+ EXPECT_TRUE(ip_endpoint.ToSockAddr(storage.addr, &storage.addr_len));
+
+ // Basic verification.
+ socklen_t expected_size = tests[index].ipv6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
+ EXPECT_EQ(expected_size, storage.addr_len);
+ EXPECT_EQ(ip_endpoint.port(), GetPortFromSockaddr(storage.addr,
+ storage.addr_len));
+
+ // And convert back to an IPEndPoint.
+ IPEndPoint ip_endpoint2;
+ EXPECT_TRUE(ip_endpoint2.FromSockAddr(storage.addr, storage.addr_len));
+ EXPECT_EQ(ip_endpoint.port(), ip_endpoint2.port());
+ EXPECT_EQ(ip_endpoint.address(), ip_endpoint2.address());
+ }
+}
+
+TEST_F(IPEndPointTest, ToSockAddrBufTooSmall) {
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint ip_endpoint(tests[index].ip_address, index);
+
+ SockaddrStorage storage;
+ storage.addr_len = index; // size is too small!
+ EXPECT_FALSE(ip_endpoint.ToSockAddr(storage.addr, &storage.addr_len));
+ }
+}
+
+TEST_F(IPEndPointTest, FromSockAddrBufTooSmall) {
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ IPEndPoint ip_endpoint;
+ struct sockaddr* sockaddr = reinterpret_cast<struct sockaddr*>(&addr);
+ EXPECT_FALSE(ip_endpoint.FromSockAddr(sockaddr, sizeof(addr) - 1));
+}
+
+TEST_F(IPEndPointTest, Equality) {
+ for (int index = 0; index < test_count; ++index) {
+ IPEndPoint src(tests[index].ip_address, index);
+ IPEndPoint dest(src);
+ EXPECT_TRUE(src == dest);
+ }
+}
+
+TEST_F(IPEndPointTest, LessThan) {
+ // Vary by port.
+ IPEndPoint ip_endpoint1(tests[0].ip_address, 100);
+ IPEndPoint ip_endpoint2(tests[0].ip_address, 1000);
+ EXPECT_TRUE(ip_endpoint1 < ip_endpoint2);
+ EXPECT_FALSE(ip_endpoint2 < ip_endpoint1);
+
+ // IPv4 vs IPv6
+ ip_endpoint1 = IPEndPoint(tests[0].ip_address, 81);
+ ip_endpoint2 = IPEndPoint(tests[2].ip_address, 80);
+ EXPECT_TRUE(ip_endpoint1 < ip_endpoint2);
+ EXPECT_FALSE(ip_endpoint2 < ip_endpoint1);
+
+ // IPv4 vs IPv4
+ ip_endpoint1 = IPEndPoint(tests[0].ip_address, 81);
+ ip_endpoint2 = IPEndPoint(tests[1].ip_address, 80);
+ EXPECT_TRUE(ip_endpoint1 < ip_endpoint2);
+ EXPECT_FALSE(ip_endpoint2 < ip_endpoint1);
+
+ // IPv6 vs IPv6
+ ip_endpoint1 = IPEndPoint(tests[2].ip_address, 81);
+ ip_endpoint2 = IPEndPoint(tests[3].ip_address, 80);
+ EXPECT_TRUE(ip_endpoint1 < ip_endpoint2);
+ EXPECT_FALSE(ip_endpoint2 < ip_endpoint1);
+
+ // Compare equivalent endpoints.
+ ip_endpoint1 = IPEndPoint(tests[0].ip_address, 80);
+ ip_endpoint2 = IPEndPoint(tests[0].ip_address, 80);
+ EXPECT_FALSE(ip_endpoint1 < ip_endpoint2);
+ EXPECT_FALSE(ip_endpoint2 < ip_endpoint1);
+}
+
+TEST_F(IPEndPointTest, ToString) {
+ IPEndPoint endpoint;
+ EXPECT_EQ(0, endpoint.port());
+
+ for (int index = 0; index < test_count; ++index) {
+ int port = 100 + index;
+ IPEndPoint endpoint(tests[index].ip_address, port);
+ const std::string result = endpoint.ToString();
+ EXPECT_EQ(tests[index].host_normalized + ":" + base::IntToString(port),
+ result);
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/keygen_handler.cc b/chromium/net/base/keygen_handler.cc
new file mode 100644
index 00000000000..7d63f4d5e12
--- /dev/null
+++ b/chromium/net/base/keygen_handler.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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/base/keygen_handler.h"
+
+namespace net {
+
+// The constructor and destructor must be defined in a .cc file so that
+// CryptoModuleBlockingPasswordDelegate can be forward-declared on platforms
+// which use NSS.
+
+KeygenHandler::KeygenHandler(int key_size_in_bits,
+ const std::string& challenge,
+ const GURL& url)
+ : key_size_in_bits_(key_size_in_bits),
+ challenge_(challenge),
+ url_(url),
+ stores_key_(true) {
+}
+
+KeygenHandler::~KeygenHandler() {
+}
+
+} // namespace net
diff --git a/chromium/net/base/keygen_handler.h b/chromium/net/base/keygen_handler.h
new file mode 100644
index 00000000000..4895dacb57e
--- /dev/null
+++ b/chromium/net/base/keygen_handler.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 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_BASE_KEYGEN_HANDLER_H_
+#define NET_BASE_KEYGEN_HANDLER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+#if defined(USE_NSS)
+#include "crypto/crypto_module_blocking_password_delegate.h"
+#endif // defined(USE_NSS)
+
+namespace net {
+
+// This class handles keypair generation for generating client
+// certificates via the <keygen> tag.
+// <http://dev.w3.org/html5/spec/Overview.html#the-keygen-element>
+// <https://developer.mozilla.org/En/HTML/HTML_Extensions/KEYGEN_Tag>
+
+class NET_EXPORT KeygenHandler {
+ public:
+ // Creates a handler that will generate a key with the given key size and
+ // incorporate the |challenge| into the Netscape SPKAC structure. The request
+ // for the key originated from |url|.
+ KeygenHandler(int key_size_in_bits,
+ const std::string& challenge,
+ const GURL& url);
+ ~KeygenHandler();
+
+ // Actually generates the key-pair and the cert request (SPKAC), and returns
+ // a base64-encoded string suitable for use as the form value of <keygen>.
+ std::string GenKeyAndSignChallenge();
+
+ // Exposed only for unit tests.
+ void set_stores_key(bool store) { stores_key_ = store;}
+
+#if defined(USE_NSS)
+ // Register the password delegate to be used if the token is unauthenticated.
+ // GenKeyAndSignChallenge runs on a worker thread, so using the blocking
+ // password callback is okay here.
+ // Takes ownership of the delegate.
+ void set_crypto_module_password_delegate(
+ crypto::CryptoModuleBlockingPasswordDelegate* delegate);
+#endif // defined(USE_NSS)
+
+ private:
+ int key_size_in_bits_; // key size in bits (usually 2048)
+ std::string challenge_; // challenge string sent by server
+ GURL url_; // the URL that requested the key
+ bool stores_key_; // should the generated key-pair be stored persistently?
+#if defined(USE_NSS)
+ // The callback for requesting a password to the PKCS#11 token.
+ scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate>
+ crypto_module_password_delegate_;
+#endif // defined(USE_NSS)
+};
+
+} // namespace net
+
+#endif // NET_BASE_KEYGEN_HANDLER_H_
diff --git a/chromium/net/base/keygen_handler_mac.cc b/chromium/net/base/keygen_handler_mac.cc
new file mode 100644
index 00000000000..63ea84751aa
--- /dev/null
+++ b/chromium/net/base/keygen_handler_mac.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 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.
+
+#include "net/base/keygen_handler.h"
+
+#include <Security/SecAsn1Coder.h>
+#include <Security/SecAsn1Templates.h>
+#include <Security/Security.h>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "crypto/cssm_init.h"
+#include "crypto/mac_security_services_lock.h"
+
+// These are in Security.framework but not declared in a public header.
+extern const SecAsn1Template kSecAsn1AlgorithmIDTemplate[];
+extern const SecAsn1Template kSecAsn1SubjectPublicKeyInfoTemplate[];
+
+namespace net {
+
+// Declarations of Netscape keygen cert structures for ASN.1 encoding:
+
+struct PublicKeyAndChallenge {
+ CSSM_X509_SUBJECT_PUBLIC_KEY_INFO spki;
+ CSSM_DATA challenge_string;
+};
+
+// This is a copy of the built-in kSecAsn1IA5StringTemplate, but without the
+// 'streamable' flag, which was causing bogus data to be written.
+const SecAsn1Template kIA5StringTemplate[] = {
+ { SEC_ASN1_IA5_STRING, 0, NULL, sizeof(CSSM_DATA) }
+};
+
+static const SecAsn1Template kPublicKeyAndChallengeTemplate[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(PublicKeyAndChallenge)
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(PublicKeyAndChallenge, spki),
+ kSecAsn1SubjectPublicKeyInfoTemplate
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(PublicKeyAndChallenge, challenge_string),
+ kIA5StringTemplate
+ },
+ {
+ 0
+ }
+};
+
+struct SignedPublicKeyAndChallenge {
+ PublicKeyAndChallenge pkac;
+ CSSM_X509_ALGORITHM_IDENTIFIER signature_algorithm;
+ CSSM_DATA signature;
+};
+
+static const SecAsn1Template kSignedPublicKeyAndChallengeTemplate[] = {
+ {
+ SEC_ASN1_SEQUENCE,
+ 0,
+ NULL,
+ sizeof(SignedPublicKeyAndChallenge)
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(SignedPublicKeyAndChallenge, pkac),
+ kPublicKeyAndChallengeTemplate
+ },
+ {
+ SEC_ASN1_INLINE,
+ offsetof(SignedPublicKeyAndChallenge, signature_algorithm),
+ kSecAsn1AlgorithmIDTemplate
+ },
+ {
+ SEC_ASN1_BIT_STRING,
+ offsetof(SignedPublicKeyAndChallenge, signature)
+ },
+ {
+ 0
+ }
+};
+
+
+static OSStatus CreateRSAKeyPair(int size_in_bits,
+ SecAccessRef initial_access,
+ SecKeyRef* out_pub_key,
+ SecKeyRef* out_priv_key);
+static OSStatus SignData(CSSM_DATA data,
+ SecKeyRef private_key,
+ CSSM_DATA* signature);
+
+std::string KeygenHandler::GenKeyAndSignChallenge() {
+ std::string result;
+ OSStatus err;
+ SecAccessRef initial_access = NULL;
+ SecKeyRef public_key = NULL;
+ SecKeyRef private_key = NULL;
+ SecAsn1CoderRef coder = NULL;
+ CSSM_DATA signature = {0, NULL};
+
+ {
+ if (url_.has_host()) {
+ // TODO(davidben): Use something like "Key generated for
+ // example.com", but localize it.
+ base::ScopedCFTypeRef<CFStringRef> label(
+ base::SysUTF8ToCFStringRef(url_.host()));
+ // Create an initial access object to set the SecAccessRef. This
+ // sets a label on the Keychain dialogs. Pass NULL as the second
+ // argument to use the default trusted list; only allow the
+ // current application to access without user confirmation.
+ err = SecAccessCreate(label, NULL, &initial_access);
+ // If we fail, just continue without a label.
+ if (err)
+ crypto::LogCSSMError("SecAccessCreate", err);
+ }
+
+ // Create the key-pair.
+ err = CreateRSAKeyPair(key_size_in_bits_, initial_access,
+ &public_key, &private_key);
+ if (err)
+ goto failure;
+
+ // Get the public key data (DER sequence of modulus, exponent).
+ CFDataRef key_data = NULL;
+ err = SecKeychainItemExport(public_key, kSecFormatBSAFE, 0, NULL,
+ &key_data);
+ if (err) {
+ crypto::LogCSSMError("SecKeychainItemExpor", err);
+ goto failure;
+ }
+ base::ScopedCFTypeRef<CFDataRef> scoped_key_data(key_data);
+
+ // Create an ASN.1 encoder.
+ err = SecAsn1CoderCreate(&coder);
+ if (err) {
+ crypto::LogCSSMError("SecAsn1CoderCreate", err);
+ goto failure;
+ }
+
+ // Fill in and DER-encode the PublicKeyAndChallenge:
+ SignedPublicKeyAndChallenge spkac;
+ memset(&spkac, 0, sizeof(spkac));
+ spkac.pkac.spki.algorithm.algorithm = CSSMOID_RSA;
+ spkac.pkac.spki.subjectPublicKey.Length =
+ CFDataGetLength(key_data) * 8; // interpreted as a _bit_ count
+ spkac.pkac.spki.subjectPublicKey.Data =
+ const_cast<uint8_t*>(CFDataGetBytePtr(key_data));
+ spkac.pkac.challenge_string.Length = challenge_.length();
+ spkac.pkac.challenge_string.Data =
+ reinterpret_cast<uint8_t*>(const_cast<char*>(challenge_.data()));
+
+ CSSM_DATA encoded;
+ err = SecAsn1EncodeItem(coder, &spkac.pkac,
+ kPublicKeyAndChallengeTemplate, &encoded);
+ if (err) {
+ crypto::LogCSSMError("SecAsn1EncodeItem", err);
+ goto failure;
+ }
+
+ // Compute a signature of the result:
+ err = SignData(encoded, private_key, &signature);
+ if (err)
+ goto failure;
+ spkac.signature.Data = signature.Data;
+ spkac.signature.Length = signature.Length * 8; // a _bit_ count
+ spkac.signature_algorithm.algorithm = CSSMOID_MD5WithRSA;
+ // TODO(snej): MD5 is weak. Can we use SHA1 instead?
+ // See <https://bugzilla.mozilla.org/show_bug.cgi?id=549460>
+
+ // DER-encode the entire SignedPublicKeyAndChallenge:
+ err = SecAsn1EncodeItem(coder, &spkac,
+ kSignedPublicKeyAndChallengeTemplate, &encoded);
+ if (err) {
+ crypto::LogCSSMError("SecAsn1EncodeItem", err);
+ goto failure;
+ }
+
+ // Base64 encode the result.
+ std::string input(reinterpret_cast<char*>(encoded.Data), encoded.Length);
+ base::Base64Encode(input, &result);
+ }
+
+ failure:
+ if (err)
+ OSSTATUS_LOG(ERROR, err) << "SSL Keygen failed!";
+ else
+ VLOG(1) << "SSL Keygen succeeded! Output is: " << result;
+
+ // Remove keys from keychain if asked to during unit testing:
+ if (!stores_key_) {
+ if (public_key)
+ SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(public_key));
+ if (private_key)
+ SecKeychainItemDelete(reinterpret_cast<SecKeychainItemRef>(private_key));
+ }
+
+ // Clean up:
+ free(signature.Data);
+ if (coder)
+ SecAsn1CoderRelease(coder);
+ if (initial_access)
+ CFRelease(initial_access);
+ if (public_key)
+ CFRelease(public_key);
+ if (private_key)
+ CFRelease(private_key);
+ return result;
+}
+
+
+// Create an RSA key pair with size |size_in_bits|. |initial_access|
+// is passed as the initial access control list in Keychain. The
+// public and private keys are placed in |out_pub_key| and
+// |out_priv_key|, respectively.
+static OSStatus CreateRSAKeyPair(int size_in_bits,
+ SecAccessRef initial_access,
+ SecKeyRef* out_pub_key,
+ SecKeyRef* out_priv_key) {
+ OSStatus err;
+ SecKeychainRef keychain;
+ err = SecKeychainCopyDefault(&keychain);
+ if (err) {
+ crypto::LogCSSMError("SecKeychainCopyDefault", err);
+ return err;
+ }
+ base::ScopedCFTypeRef<SecKeychainRef> scoped_keychain(keychain);
+ {
+ base::AutoLock locked(crypto::GetMacSecurityServicesLock());
+ err = SecKeyCreatePair(
+ keychain,
+ CSSM_ALGID_RSA,
+ size_in_bits,
+ 0LL,
+ // public key usage and attributes:
+ CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP,
+ CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT,
+ // private key usage and attributes:
+ CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP,
+ CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_PERMANENT |
+ CSSM_KEYATTR_SENSITIVE,
+ initial_access,
+ out_pub_key, out_priv_key);
+ }
+ if (err)
+ crypto::LogCSSMError("SecKeyCreatePair", err);
+ return err;
+}
+
+static OSStatus CreateSignatureContext(SecKeyRef key,
+ CSSM_ALGORITHMS algorithm,
+ CSSM_CC_HANDLE* out_cc_handle) {
+ OSStatus err;
+ const CSSM_ACCESS_CREDENTIALS* credentials = NULL;
+ {
+ base::AutoLock locked(crypto::GetMacSecurityServicesLock());
+ err = SecKeyGetCredentials(key,
+ CSSM_ACL_AUTHORIZATION_SIGN,
+ kSecCredentialTypeDefault,
+ &credentials);
+ }
+ if (err) {
+ crypto::LogCSSMError("SecKeyGetCredentials", err);
+ return err;
+ }
+
+ CSSM_CSP_HANDLE csp_handle = 0;
+ {
+ base::AutoLock locked(crypto::GetMacSecurityServicesLock());
+ err = SecKeyGetCSPHandle(key, &csp_handle);
+ }
+ if (err) {
+ crypto::LogCSSMError("SecKeyGetCSPHandle", err);
+ return err;
+ }
+
+ const CSSM_KEY* cssm_key = NULL;
+ {
+ base::AutoLock locked(crypto::GetMacSecurityServicesLock());
+ err = SecKeyGetCSSMKey(key, &cssm_key);
+ }
+ if (err) {
+ crypto::LogCSSMError("SecKeyGetCSSMKey", err);
+ return err;
+ }
+
+ err = CSSM_CSP_CreateSignatureContext(csp_handle,
+ algorithm,
+ credentials,
+ cssm_key,
+ out_cc_handle);
+ if (err)
+ crypto::LogCSSMError("CSSM_CSP_CreateSignatureContext", err);
+ return err;
+}
+
+static OSStatus SignData(CSSM_DATA data,
+ SecKeyRef private_key,
+ CSSM_DATA* signature) {
+ CSSM_CC_HANDLE cc_handle;
+ OSStatus err = CreateSignatureContext(private_key,
+ CSSM_ALGID_MD5WithRSA,
+ &cc_handle);
+ if (err) {
+ crypto::LogCSSMError("CreateSignatureContext", err);
+ return err;
+ }
+ err = CSSM_SignData(cc_handle, &data, 1, CSSM_ALGID_NONE, signature);
+ if (err)
+ crypto::LogCSSMError("CSSM_SignData", err);
+ CSSM_DeleteContext(cc_handle);
+ return err;
+}
+
+} // namespace net
diff --git a/chromium/net/base/keygen_handler_nss.cc b/chromium/net/base/keygen_handler_nss.cc
new file mode 100644
index 00000000000..5e97807866a
--- /dev/null
+++ b/chromium/net/base/keygen_handler_nss.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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/base/keygen_handler.h"
+
+#include "base/logging.h"
+#include "crypto/crypto_module_blocking_password_delegate.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/third_party/mozilla_security_manager/nsKeygenHandler.h"
+
+// PSM = Mozilla's Personal Security Manager.
+namespace psm = mozilla_security_manager;
+
+namespace net {
+
+std::string KeygenHandler::GenKeyAndSignChallenge() {
+ // Ensure NSS is initialized.
+ crypto::EnsureNSSInit();
+
+ // TODO(mattm): allow choosing which slot to generate and store the key.
+ crypto::ScopedPK11Slot slot(crypto::GetPrivateNSSKeySlot());
+ if (!slot.get()) {
+ LOG(ERROR) << "Couldn't get private key slot from NSS!";
+ return std::string();
+ }
+
+ // Authenticate to the token.
+ if (SECSuccess != PK11_Authenticate(slot.get(), PR_TRUE,
+ crypto_module_password_delegate_.get())) {
+ LOG(ERROR) << "Couldn't authenticate to private key slot!";
+ return std::string();
+ }
+
+ return psm::GenKeyAndSignChallenge(key_size_in_bits_, challenge_, url_,
+ slot.get(), stores_key_);
+}
+
+void KeygenHandler::set_crypto_module_password_delegate(
+ crypto::CryptoModuleBlockingPasswordDelegate* delegate) {
+ crypto_module_password_delegate_.reset(delegate);
+}
+
+} // namespace net
diff --git a/chromium/net/base/keygen_handler_openssl.cc b/chromium/net/base/keygen_handler_openssl.cc
new file mode 100644
index 00000000000..edd0eb110b4
--- /dev/null
+++ b/chromium/net/base/keygen_handler_openssl.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 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/base/keygen_handler.h"
+
+#include <openssl/ssl.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/openssl_util.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/openssl_private_key_store.h"
+
+namespace net {
+
+std::string KeygenHandler::GenKeyAndSignChallenge() {
+ scoped_ptr<crypto::RSAPrivateKey> key(
+ crypto::RSAPrivateKey::Create(key_size_in_bits_));
+ EVP_PKEY* pkey = key->key();
+
+ if (stores_key_)
+ OpenSSLPrivateKeyStore::StoreKeyPair(url_, pkey);
+
+ crypto::ScopedOpenSSL<NETSCAPE_SPKI, NETSCAPE_SPKI_free> spki(
+ NETSCAPE_SPKI_new());
+ ASN1_STRING_set(spki.get()->spkac->challenge,
+ challenge_.data(), challenge_.size());
+ NETSCAPE_SPKI_set_pubkey(spki.get(), pkey);
+ // Using MD5 as this is what is required in HTML5, even though the SPKI
+ // structure does allow the use of a SHA-1 signature.
+ NETSCAPE_SPKI_sign(spki.get(), pkey, EVP_md5());
+ char* spkistr = NETSCAPE_SPKI_b64_encode(spki.get());
+
+ std::string result(spkistr);
+ OPENSSL_free(spkistr);
+
+ return result;
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/keygen_handler_unittest.cc b/chromium/net/base/keygen_handler_unittest.cc
new file mode 100644
index 00000000000..5188d9d745c
--- /dev/null
+++ b/chromium/net/base/keygen_handler_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 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/base/keygen_handler.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/synchronization/waitable_event.h"
+#include "build/build_config.h"
+#include "crypto/nss_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS)
+#include <private/pprthred.h> // PR_DetachThread
+#endif
+
+namespace net {
+
+namespace {
+
+class KeygenHandlerTest : public ::testing::Test {
+ public:
+ KeygenHandlerTest() {}
+ virtual ~KeygenHandlerTest() {}
+
+ virtual void SetUp() {
+#if defined(OS_CHROMEOS)
+ crypto::OpenPersistentNSSDB();
+#endif
+ }
+};
+
+// Assert that |result| is a valid output for KeygenHandler given challenge
+// string of |challenge|.
+void AssertValidSignedPublicKeyAndChallenge(const std::string& result,
+ const std::string& challenge) {
+ ASSERT_GT(result.length(), 0U);
+
+ // Verify it's valid base64:
+ std::string spkac;
+ ASSERT_TRUE(base::Base64Decode(result, &spkac));
+ // In lieu of actually parsing and validating the DER data,
+ // just check that it exists and has a reasonable length.
+ // (It's almost always 590 bytes, but the DER encoding of the random key
+ // and signature could sometimes be a few bytes different.)
+ ASSERT_GE(spkac.length(), 200U);
+ ASSERT_LE(spkac.length(), 300U);
+
+ // NOTE:
+ // The value of |result| can be validated by prefixing 'SPKAC=' to it
+ // and piping it through
+ // openssl spkac -verify
+ // whose output should look like:
+ // Netscape SPKI:
+ // Public Key Algorithm: rsaEncryption
+ // RSA Public Key: (2048 bit)
+ // Modulus (2048 bit):
+ // 00:b6:cc:14:c9:43:b5:2d:51:65:7e:11:8b:80:9e: .....
+ // Exponent: 65537 (0x10001)
+ // Challenge String: some challenge
+ // Signature Algorithm: md5WithRSAEncryption
+ // 92:f3:cc:ff:0b:d3:d0:4a:3a:4c:ba:ff:d6:38:7f:a5:4b:b5: .....
+ // Signature OK
+ //
+ // The value of |spkac| can be ASN.1-parsed with:
+ // openssl asn1parse -inform DER
+}
+
+TEST_F(KeygenHandlerTest, SmokeTest) {
+ KeygenHandler handler(768, "some challenge", GURL("http://www.example.com"));
+ handler.set_stores_key(false); // Don't leave the key-pair behind
+ std::string result = handler.GenKeyAndSignChallenge();
+ VLOG(1) << "KeygenHandler produced: " << result;
+ AssertValidSignedPublicKeyAndChallenge(result, "some challenge");
+}
+
+void ConcurrencyTestCallback(base::WaitableEvent* event,
+ const std::string& challenge,
+ std::string* result) {
+ // We allow Singleton use on the worker thread here since we use a
+ // WaitableEvent to synchronize, so it's safe.
+ base::ThreadRestrictions::ScopedAllowSingleton scoped_allow_singleton;
+ KeygenHandler handler(768, challenge, GURL("http://www.example.com"));
+ handler.set_stores_key(false); // Don't leave the key-pair behind.
+ *result = handler.GenKeyAndSignChallenge();
+ event->Signal();
+#if defined(USE_NSS)
+ // Detach the thread from NSPR.
+ // Calling NSS functions attaches the thread to NSPR, which stores
+ // the NSPR thread ID in thread-specific data.
+ // The threads in our thread pool terminate after we have called
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
+ // segfaults on shutdown when the threads' thread-specific data
+ // destructors run.
+ PR_DetachThread();
+#endif
+}
+
+// We asynchronously generate the keys so as not to hang up the IO thread. This
+// test tries to catch concurrency problems in the keygen implementation.
+TEST_F(KeygenHandlerTest, ConcurrencyTest) {
+ const int NUM_HANDLERS = 5;
+ base::WaitableEvent* events[NUM_HANDLERS] = { NULL };
+ std::string results[NUM_HANDLERS];
+ for (int i = 0; i < NUM_HANDLERS; i++) {
+ events[i] = new base::WaitableEvent(false, false);
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(ConcurrencyTestCallback, events[i], "some challenge",
+ &results[i]),
+ true);
+ }
+
+ for (int i = 0; i < NUM_HANDLERS; i++) {
+ // Make sure the job completed
+ events[i]->Wait();
+ delete events[i];
+ events[i] = NULL;
+
+ VLOG(1) << "KeygenHandler " << i << " produced: " << results[i];
+ AssertValidSignedPublicKeyAndChallenge(results[i], "some challenge");
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/keygen_handler_win.cc b/chromium/net/base/keygen_handler_win.cc
new file mode 100644
index 00000000000..e1d432ce401
--- /dev/null
+++ b/chromium/net/base/keygen_handler_win.cc
@@ -0,0 +1,224 @@
+// Copyright (c) 2011 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/base/keygen_handler.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32.lib")
+#include <rpc.h>
+#pragma comment(lib, "rpcrt4.lib")
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "crypto/capi_util.h"
+#include "crypto/scoped_capi_types.h"
+
+
+namespace net {
+
+// Assigns the contents of a CERT_PUBLIC_KEY_INFO structure for the signing
+// key in |prov| to |output|. Returns true if encoding was successful.
+bool GetSubjectPublicKeyInfo(HCRYPTPROV prov, std::vector<BYTE>* output) {
+ BOOL ok;
+ DWORD size = 0;
+
+ // From the private key stored in HCRYPTPROV, obtain the public key, stored
+ // as a CERT_PUBLIC_KEY_INFO structure. Currently, only RSA public keys are
+ // supported.
+ ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ szOID_RSA_RSA, 0, NULL, NULL, &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->resize(size);
+
+ PCERT_PUBLIC_KEY_INFO public_key_casted =
+ reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&(*output)[0]);
+ ok = CryptExportPublicKeyInfoEx(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ szOID_RSA_RSA, 0, NULL, public_key_casted,
+ &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->resize(size);
+
+ return true;
+}
+
+// Generates a DER encoded SignedPublicKeyAndChallenge structure from the
+// signing key of |prov| and the specified ASCII |challenge| string and
+// appends it to |output|.
+// True if the encoding was successfully generated.
+bool GetSignedPublicKeyAndChallenge(HCRYPTPROV prov,
+ const std::string& challenge,
+ std::string* output) {
+ std::wstring wide_challenge = ASCIIToWide(challenge);
+ std::vector<BYTE> spki;
+
+ if (!GetSubjectPublicKeyInfo(prov, &spki))
+ return false;
+
+ // PublicKeyAndChallenge ::= SEQUENCE {
+ // spki SubjectPublicKeyInfo,
+ // challenge IA5STRING
+ // }
+ CERT_KEYGEN_REQUEST_INFO pkac;
+ pkac.dwVersion = CERT_KEYGEN_REQUEST_V1;
+ pkac.SubjectPublicKeyInfo =
+ *reinterpret_cast<PCERT_PUBLIC_KEY_INFO>(&spki[0]);
+ pkac.pwszChallengeString = const_cast<wchar_t*>(wide_challenge.c_str());
+
+ CRYPT_ALGORITHM_IDENTIFIER sig_alg;
+ memset(&sig_alg, 0, sizeof(sig_alg));
+ sig_alg.pszObjId = szOID_RSA_MD5RSA;
+
+ BOOL ok;
+ DWORD size = 0;
+ std::vector<BYTE> signed_pkac;
+ ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ X509_KEYGEN_REQUEST_TO_BE_SIGNED,
+ &pkac, &sig_alg, NULL,
+ NULL, &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ signed_pkac.resize(size);
+ ok = CryptSignAndEncodeCertificate(prov, AT_KEYEXCHANGE, X509_ASN_ENCODING,
+ X509_KEYGEN_REQUEST_TO_BE_SIGNED,
+ &pkac, &sig_alg, NULL,
+ &signed_pkac[0], &size);
+ DCHECK(ok);
+ if (!ok)
+ return false;
+
+ output->assign(reinterpret_cast<char*>(&signed_pkac[0]), size);
+ return true;
+}
+
+// Generates a unique name for the container which will store the key that is
+// generated. The traditional Windows approach is to use a GUID here.
+std::wstring GetNewKeyContainerId() {
+ RPC_STATUS status = RPC_S_OK;
+ std::wstring result;
+
+ UUID id = { 0 };
+ status = UuidCreateSequential(&id);
+ if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
+ return result;
+
+ RPC_WSTR rpc_string = NULL;
+ status = UuidToString(&id, &rpc_string);
+ if (status != RPC_S_OK)
+ return result;
+
+ // RPC_WSTR is unsigned short*. wchar_t is a built-in type of Visual C++,
+ // so the type cast is necessary.
+ result.assign(reinterpret_cast<wchar_t*>(rpc_string));
+ RpcStringFree(&rpc_string);
+
+ return result;
+}
+
+// This is a helper struct designed to optionally delete a key after releasing
+// the associated provider.
+struct KeyContainer {
+ public:
+ explicit KeyContainer(bool delete_keyset)
+ : delete_keyset_(delete_keyset) {}
+
+ ~KeyContainer() {
+ if (provider_) {
+ provider_.reset();
+ if (delete_keyset_ && !key_id_.empty()) {
+ HCRYPTPROV provider;
+ crypto::CryptAcquireContextLocked(&provider, key_id_.c_str(), NULL,
+ PROV_RSA_FULL, CRYPT_SILENT | CRYPT_DELETEKEYSET);
+ }
+ }
+ }
+
+ crypto::ScopedHCRYPTPROV provider_;
+ std::wstring key_id_;
+
+ private:
+ bool delete_keyset_;
+};
+
+std::string KeygenHandler::GenKeyAndSignChallenge() {
+ KeyContainer key_container(!stores_key_);
+
+ // TODO(rsleevi): Have the user choose which provider they should use, which
+ // needs to be filtered by those providers which can provide the key type
+ // requested or the key size requested. This is especially important for
+ // generating certificates that will be stored on smart cards.
+ const int kMaxAttempts = 5;
+ int attempt;
+ for (attempt = 0; attempt < kMaxAttempts; ++attempt) {
+ // Per MSDN documentation for CryptAcquireContext, if applications will be
+ // creating their own keys, they should ensure unique naming schemes to
+ // prevent overlap with any other applications or consumers of CSPs, and
+ // *should not* store new keys within the default, NULL key container.
+ key_container.key_id_ = GetNewKeyContainerId();
+ if (key_container.key_id_.empty())
+ return std::string();
+
+ // Only create new key containers, so that existing key containers are not
+ // overwritten.
+ if (crypto::CryptAcquireContextLocked(key_container.provider_.receive(),
+ key_container.key_id_.c_str(), NULL, PROV_RSA_FULL,
+ CRYPT_SILENT | CRYPT_NEWKEYSET))
+ break;
+
+ if (GetLastError() != NTE_BAD_KEYSET) {
+ LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
+ "context: " << GetLastError();
+ return std::string();
+ }
+ }
+ if (attempt == kMaxAttempts) {
+ LOG(ERROR) << "Keygen failed: Couldn't acquire a CryptoAPI provider "
+ "context: Max retries exceeded";
+ return std::string();
+ }
+
+ {
+ crypto::ScopedHCRYPTKEY key;
+ if (!CryptGenKey(key_container.provider_, CALG_RSA_KEYX,
+ (key_size_in_bits_ << 16) | CRYPT_EXPORTABLE, key.receive())) {
+ LOG(ERROR) << "Keygen failed: Couldn't generate an RSA key";
+ return std::string();
+ }
+
+ std::string spkac;
+ if (!GetSignedPublicKeyAndChallenge(key_container.provider_, challenge_,
+ &spkac)) {
+ LOG(ERROR) << "Keygen failed: Couldn't generate the signed public key "
+ "and challenge";
+ return std::string();
+ }
+
+ std::string result;
+ if (!base::Base64Encode(spkac, &result)) {
+ LOG(ERROR) << "Keygen failed: Couldn't convert signed key into base64";
+ return std::string();
+ }
+
+ VLOG(1) << "Keygen succeeded";
+ return result;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/linked_hash_map.h b/chromium/net/base/linked_hash_map.h
new file mode 100644
index 00000000000..d08b68d2592
--- /dev/null
+++ b/chromium/net/base/linked_hash_map.h
@@ -0,0 +1,210 @@
+// Copyright (c) 2013 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.
+//
+// This is a simplistic insertion-ordered map. It behaves similarly to an STL
+// map, but only implements a small subset of the map's methods. Internally, we
+// just keep a map and a list going in parallel.
+//
+// This class provides no thread safety guarantees, beyond what you would
+// normally see with std::list.
+//
+// Iterators should be stable in the face of mutations, except for an
+// iterator pointing to an element that was just deleted.
+
+#ifndef UTIL_GTL_LINKED_HASH_MAP_H_
+#define UTIL_GTL_LINKED_HASH_MAP_H_
+
+#include <list>
+#include <utility>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+
+// This holds a list of pair<Key, Value> items. This list is what gets
+// traversed, and it's iterators from this list that we return from
+// begin/end/find.
+//
+// We also keep a map<Key, list::iterator> for find. Since std::list is a
+// doubly-linked list, the iterators should remain stable.
+template<class Key, class Value>
+class linked_hash_map {
+ private:
+ typedef std::list<std::pair<Key, Value> > ListType;
+ typedef base::hash_map<Key, typename ListType::iterator> MapType;
+
+ public:
+ typedef typename ListType::iterator iterator;
+ typedef typename ListType::reverse_iterator reverse_iterator;
+ typedef typename ListType::const_iterator const_iterator;
+ typedef typename ListType::const_reverse_iterator const_reverse_iterator;
+ typedef typename MapType::key_type key_type;
+ typedef typename ListType::value_type value_type;
+ typedef typename ListType::size_type size_type;
+
+ linked_hash_map() : map_(), list_() {
+ }
+
+ // Returns an iterator to the first (insertion-ordered) element. Like a map,
+ // this can be dereferenced to a pair<Key, Value>.
+ iterator begin() {
+ return list_.begin();
+ }
+ const_iterator begin() const {
+ return list_.begin();
+ }
+
+ // Returns an iterator beyond the last element.
+ iterator end() {
+ return list_.end();
+ }
+ const_iterator end() const {
+ return list_.end();
+ }
+
+ // Returns an iterator to the last (insertion-ordered) element. Like a map,
+ // this can be dereferenced to a pair<Key, Value>.
+ reverse_iterator rbegin() {
+ return list_.rbegin();
+ }
+ const_reverse_iterator rbegin() const {
+ return list_.rbegin();
+ }
+
+ // Returns an iterator beyond the first element.
+ reverse_iterator rend() {
+ return list_.rend();
+ }
+ const_reverse_iterator rend() const {
+ return list_.rend();
+ }
+
+ // Clears the map of all values.
+ void clear() {
+ map_.clear();
+ list_.clear();
+ }
+
+ // Returns true iff the map is empty.
+ bool empty() const {
+ return list_.empty();
+ }
+
+ // Erases values with the provided key. Returns the number of elements
+ // erased. In this implementation, this will be 0 or 1.
+ size_type erase(const Key& key) {
+ typename MapType::iterator found = map_.find(key);
+ if (found == map_.end()) return 0;
+
+ list_.erase(found->second);
+ map_.erase(found);
+
+ return 1;
+ }
+
+ // Erases values with the provided iterator. If the provided iterator is
+ // invalid or there is inconsistency between the map and list, a CHECK() error
+ // will occur.
+ void erase(iterator position) {
+ typename MapType::iterator found = map_.find(position->first);
+ CHECK(found->second == position)
+ << "Inconsisent iterator for map and list, or the iterator is invalid.";
+
+ list_.erase(position);
+ map_.erase(found);
+ }
+
+ // Erases values between first and last, not including last.
+ void erase(iterator first, iterator last) {
+ while (first != last && first != end()) {
+ erase(first++);
+ }
+ }
+
+ // Finds the element with the given key. Returns an iterator to the
+ // value found, or to end() if the value was not found. Like a map, this
+ // iterator points to a pair<Key, Value>.
+ iterator find(const Key& key) {
+ typename MapType::iterator found = map_.find(key);
+ if (found == map_.end()) {
+ return end();
+ }
+ return found->second;
+ }
+
+ const_iterator find(const Key& key) const {
+ typename MapType::const_iterator found = map_.find(key);
+ if (found == map_.end()) {
+ return end();
+ }
+ return found->second;
+ }
+
+ // Returns the bounds of a range that includes all the elements in the
+ // container with a key that compares equal to x.
+ std::pair<iterator, iterator> equal_range(const key_type& key) {
+ std::pair<typename MapType::iterator, typename MapType::iterator> eq_range =
+ map_.equal_range(key);
+
+ return make_pair(eq_range.first->second, eq_range.second->second);
+ }
+
+ std::pair<const_iterator, const_iterator> equal_range(
+ const key_type& key) const {
+ std::pair<typename MapType::const_iterator,
+ typename MapType::const_iterator> eq_range =
+ map_.equal_range(key);
+ const const_iterator& start_iter = eq_range.first != map_.end() ?
+ eq_range.first->second : end();
+ const const_iterator& end_iter = eq_range.second != map_.end() ?
+ eq_range.second->second : end();
+
+ return make_pair(start_iter, end_iter);
+ }
+
+ // Returns the value mapped to key, or an inserted iterator to that position
+ // in the map.
+ Value& operator[](const key_type& key) {
+ return (*((this->insert(make_pair(key, Value()))).first)).second;
+ }
+
+ // Inserts an element into the map
+ std::pair<iterator, bool> insert(const std::pair<Key, Value>& pair) {
+ // First make sure the map doesn't have a key with this value. If it does,
+ // return a pair with an iterator to it, and false indicating that we
+ // didn't insert anything.
+ typename MapType::iterator found = map_.find(pair.first);
+ if (found != map_.end()) return make_pair(found->second, false);
+
+ // Otherwise, insert into the list first.
+ list_.push_back(pair);
+
+ // Obtain an iterator to the newly added element. We do -- instead of -
+ // since list::iterator doesn't implement operator-().
+ typename ListType::iterator last = list_.end();
+ --last;
+
+ CHECK(map_.insert(make_pair(pair.first, last)).second)
+ << "Map and list are inconsistent";
+
+ return make_pair(last, true);
+ }
+
+ size_type size() const {
+ return list_.size();
+ }
+
+ void swap(linked_hash_map& other) {
+ map_.swap(other.map_);
+ list_.swap(other.list_);
+ }
+
+ private:
+ // The map component, used for speedy lookups
+ MapType map_;
+
+ // The list component, used for maintaining insertion order
+ ListType list_;
+};
+
+#endif // UTIL_GTL_LINKED_HASH_MAP_H_
diff --git a/chromium/net/base/load_flags.h b/chromium/net/base/load_flags.h
new file mode 100644
index 00000000000..53b2d58d208
--- /dev/null
+++ b/chromium/net/base/load_flags.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 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_BASE_LOAD_FLAGS_H_
+#define NET_BASE_LOAD_FLAGS_H_
+
+namespace net {
+
+// These flags provide metadata about the type of the load request. They are
+// intended to be OR'd together.
+enum {
+
+#define LOAD_FLAG(label, value) LOAD_ ## label = value,
+#include "net/base/load_flags_list.h"
+#undef LOAD_FLAG
+
+};
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_FLAGS_H_
diff --git a/chromium/net/base/load_flags_list.h b/chromium/net/base/load_flags_list.h
new file mode 100644
index 00000000000..968c122496d
--- /dev/null
+++ b/chromium/net/base/load_flags_list.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2011 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.
+
+// This is the list of load flags and their values. For the enum values,
+// include the file "net/base/load_flags.h".
+//
+// Here we define the values using a macro LOAD_FLAG, so it can be
+// expanded differently in some places (for example, to automatically
+// map a load flag value to its symbolic name).
+
+LOAD_FLAG(NORMAL, 0)
+
+// This is "normal reload", meaning an if-none-match/if-modified-since query
+LOAD_FLAG(VALIDATE_CACHE, 1 << 0)
+
+// This is "shift-reload", meaning a "pragma: no-cache" end-to-end fetch
+LOAD_FLAG(BYPASS_CACHE, 1 << 1)
+
+// This is a back/forward style navigation where the cached content should
+// be preferred over any protocol specific cache validation.
+LOAD_FLAG(PREFERRING_CACHE, 1 << 2)
+
+// This is a navigation that will fail if it cannot serve the requested
+// resource from the cache (or some equivalent local store).
+LOAD_FLAG(ONLY_FROM_CACHE, 1 << 3)
+
+// Indicate that if the request fails at the network level in a way that
+// indicates the source is unreachable, the request should fail over
+// to as if LOAD_PREFERRING_CACHE had been set.
+LOAD_FLAG(FROM_CACHE_IF_OFFLINE, 1 << 4)
+
+// This is a navigation that will not use the cache at all. It does not
+// impact the HTTP request headers.
+LOAD_FLAG(DISABLE_CACHE, 1 << 5)
+
+// This is a navigation that will not be intercepted by any registered
+// URLRequest::Interceptors.
+LOAD_FLAG(DISABLE_INTERCEPT, 1 << 6)
+
+// If present, upload progress messages should be provided to initiator.
+LOAD_FLAG(ENABLE_UPLOAD_PROGRESS, 1 << 7)
+
+// If present, collect load timing for the request.
+LOAD_FLAG(ENABLE_LOAD_TIMING, 1 << 8)
+
+// If present, ignores certificate mismatches with the domain name.
+// (The default behavior is to trigger an OnSSLCertificateError callback.)
+LOAD_FLAG(IGNORE_CERT_COMMON_NAME_INVALID, 1 << 9)
+
+// If present, ignores certificate expiration dates
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_DATE_INVALID, 1 << 10)
+
+// If present, trusts all certificate authorities
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_AUTHORITY_INVALID, 1 << 11)
+
+// If present, causes certificate revocation checks to be skipped on secure
+// connections.
+LOAD_FLAG(DISABLE_CERT_REVOCATION_CHECKING, 1 << 12)
+
+// If present, ignores wrong key usage of the certificate
+// (The default behavior is to trigger an OnSSLCertificateError callback).
+LOAD_FLAG(IGNORE_CERT_WRONG_USAGE, 1 << 13)
+
+// This load will not make any changes to cookies, including storing new
+// cookies or updating existing ones.
+LOAD_FLAG(DO_NOT_SAVE_COOKIES, 1 << 14)
+
+// Do not resolve proxies. This override is used when downloading PAC files
+// to avoid having a circular dependency.
+LOAD_FLAG(BYPASS_PROXY, 1 << 15)
+
+// Indicate this request is for a download, as opposed to viewing.
+LOAD_FLAG(IS_DOWNLOAD, 1 << 16)
+
+// Requires EV certificate verification.
+LOAD_FLAG(VERIFY_EV_CERT, 1 << 17)
+
+// This load will not send any cookies.
+LOAD_FLAG(DO_NOT_SEND_COOKIES, 1 << 18)
+
+// This load will not send authentication data (user name/password)
+// to the server (as opposed to the proxy).
+LOAD_FLAG(DO_NOT_SEND_AUTH_DATA, 1 << 19)
+
+// This should only be used for testing (set by HttpNetworkTransaction).
+LOAD_FLAG(IGNORE_ALL_CERT_ERRORS, 1 << 20)
+
+// Indicate that this is a top level frame, so that we don't assume it is a
+// subresource and speculatively pre-connect or pre-resolve when a referring
+// page is loaded.
+LOAD_FLAG(MAIN_FRAME, 1 << 21)
+
+// Indicate that this is a sub frame, and hence it might have subresources that
+// should be speculatively resolved, or even speculatively preconnected.
+LOAD_FLAG(SUB_FRAME, 1 << 22)
+
+// If present, intercept actual request/response headers from network stack
+// and report them to renderer. This includes cookies, so the flag is only
+// respected if renderer has CanReadRawCookies capability in the security
+// policy.
+LOAD_FLAG(REPORT_RAW_HEADERS, 1 << 23)
+
+// Indicates that this load was motivated by the rel=prefetch feature,
+// and is (in theory) not intended for the current frame.
+LOAD_FLAG(PREFETCH, 1 << 24)
+
+// Indicates that this is a load that ignores limits and should complete
+// immediately.
+LOAD_FLAG(IGNORE_LIMITS, 1 << 25)
+
+// Suppress login prompts for this request. Cached credentials or
+// default credentials may still be used for authentication.
+LOAD_FLAG(DO_NOT_PROMPT_FOR_LOGIN, 1 << 26)
+
+// Indicates that the operation is somewhat likely to be due to an
+// explicit user action. This can be used as a hint to treat the
+// request with higher priority.
+LOAD_FLAG(MAYBE_USER_GESTURE, 1 << 27)
diff --git a/chromium/net/base/load_states.h b/chromium/net/base/load_states.h
new file mode 100644
index 00000000000..e0915bacbe3
--- /dev/null
+++ b/chromium/net/base/load_states.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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_BASE_LOAD_STATES_H__
+#define NET_BASE_LOAD_STATES_H__
+
+#include "base/strings/string16.h"
+
+namespace net {
+
+// These states correspond to the lengthy periods of time that a resource load
+// may be blocked and unable to make progress.
+enum LoadState {
+
+#define LOAD_STATE(label) LOAD_STATE_ ## label,
+#include "net/base/load_states_list.h"
+#undef LOAD_STATE
+
+};
+
+// Some states, like LOAD_STATE_WAITING_FOR_DELEGATE, are associated with extra
+// data that describes more precisely what the delegate (for example) is doing.
+// This class provides an easy way to hold a load state with an extra parameter.
+struct LoadStateWithParam {
+ LoadState state;
+ base::string16 param;
+ LoadStateWithParam() : state(LOAD_STATE_IDLE) {}
+ LoadStateWithParam(LoadState state, const base::string16& param)
+ : state(state), param(param) {}
+};
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_STATES_H__
diff --git a/chromium/net/base/load_states_list.h b/chromium/net/base/load_states_list.h
new file mode 100644
index 00000000000..23d14c9986a
--- /dev/null
+++ b/chromium/net/base/load_states_list.h
@@ -0,0 +1,98 @@
+// Copyright (c) 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.
+
+// This is the list of load states and their values. For the enum values,
+// include the file "net/base/load_states.h".
+//
+// Here we define the values using a macro LOAD_STATE, so it can be
+// expanded differently in some places (for example, to automatically
+// map a load flag value to its symbolic name).
+
+// This is the default state. It corresponds to a resource load that has
+// either not yet begun or is idle waiting for the consumer to do something
+// to move things along (e.g., the consumer of an URLRequest may not have
+// called Read yet).
+LOAD_STATE(IDLE)
+
+// When a socket pool group is below the maximum number of sockets allowed per
+// group, but a new socket cannot be created due to the per-pool socket limit,
+// this state is returned by all requests for the group waiting on an idle
+// connection, except those that may be serviced by a pending new connection.
+LOAD_STATE(WAITING_FOR_STALLED_SOCKET_POOL)
+
+// When a socket pool group has reached the maximum number of sockets allowed
+// per group, this state is returned for all requests that don't have a socket,
+// except those that correspond to a pending new connection.
+LOAD_STATE(WAITING_FOR_AVAILABLE_SOCKET)
+
+// This state indicates that the URLRequest delegate has chosen to block this
+// request before it was sent over the network. When in this state, the
+// delegate should set a load state parameter on the URLRequest describing
+// the nature of the delay (i.e. "Waiting for <description given by
+// delegate>").
+LOAD_STATE(WAITING_FOR_DELEGATE)
+
+// This state corresponds to a resource load that is blocked waiting for
+// access to a resource in the cache. If multiple requests are made for the
+// same resource, the first request will be responsible for writing (or
+// updating) the cache entry and the second request will be deferred until
+// the first completes. This may be done to optimize for cache reuse.
+LOAD_STATE(WAITING_FOR_CACHE)
+
+// This state corresponds to a resource load that is blocked waiting for
+// access to a resource in the AppCache.
+// Note: This is a layering violation, but being the only one it's not that
+// bad. TODO(rvargas): Reconsider what to do if we need to add more.
+LOAD_STATE(WAITING_FOR_APPCACHE)
+
+// This state corresponds to a resource being blocked waiting for the
+// PAC script to be downloaded.
+LOAD_STATE(DOWNLOADING_PROXY_SCRIPT)
+
+// This state corresponds to a resource load that is blocked waiting for a
+// proxy autoconfig script to return a proxy server to use.
+LOAD_STATE(RESOLVING_PROXY_FOR_URL)
+
+// This state corresponds to a resource load that is blocked waiting for a
+// proxy autoconfig script to return a proxy server to use, but that proxy
+// script is busy resolving the IP address of a host.
+LOAD_STATE(RESOLVING_HOST_IN_PROXY_SCRIPT)
+
+// This state indicates that we're in the process of establishing a tunnel
+// through the proxy server.
+LOAD_STATE(ESTABLISHING_PROXY_TUNNEL)
+
+// This state corresponds to a resource load that is blocked waiting for a
+// host name to be resolved. This could either indicate resolution of the
+// origin server corresponding to the resource or to the host name of a proxy
+// server used to fetch the resource.
+LOAD_STATE(RESOLVING_HOST)
+
+// This state corresponds to a resource load that is blocked waiting for a
+// TCP connection (or other network connection) to be established. HTTP
+// requests that reuse a keep-alive connection skip this state.
+LOAD_STATE(CONNECTING)
+
+// This state corresponds to a resource load that is blocked waiting for the
+// SSL handshake to complete.
+LOAD_STATE(SSL_HANDSHAKE)
+
+// This state corresponds to a resource load that is blocked waiting to
+// completely upload a request to a server. In the case of a HTTP POST
+// request, this state includes the period of time during which the message
+// body is being uploaded.
+LOAD_STATE(SENDING_REQUEST)
+
+// This state corresponds to a resource load that is blocked waiting for the
+// response to a network request. In the case of a HTTP transaction, this
+// corresponds to the period after the request is sent and before all of the
+// response headers have been received.
+LOAD_STATE(WAITING_FOR_RESPONSE)
+
+// This state corresponds to a resource load that is blocked waiting for a
+// read to complete. In the case of a HTTP transaction, this corresponds to
+// the period after the response headers have been received and before all of
+// the response body has been downloaded. (NOTE: This state only applies for
+// an URLRequest while there is an outstanding Read operation.)
+LOAD_STATE(READING_RESPONSE)
diff --git a/chromium/net/base/load_timing_info.cc b/chromium/net/base/load_timing_info.cc
new file mode 100644
index 00000000000..ab5b750edf9
--- /dev/null
+++ b/chromium/net/base/load_timing_info.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 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.
+
+#include "net/base/load_timing_info.h"
+
+#include "net/base/net_log.h"
+
+namespace net {
+
+LoadTimingInfo::ConnectTiming::ConnectTiming() {}
+
+LoadTimingInfo::ConnectTiming::~ConnectTiming() {}
+
+LoadTimingInfo::LoadTimingInfo() : socket_reused(false),
+ socket_log_id(NetLog::Source::kInvalidId) {
+}
+
+LoadTimingInfo::~LoadTimingInfo() {}
+
+} // namespace net
+
diff --git a/chromium/net/base/load_timing_info.h b/chromium/net/base/load_timing_info.h
new file mode 100644
index 00000000000..b4acd58b255
--- /dev/null
+++ b/chromium/net/base/load_timing_info.h
@@ -0,0 +1,141 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_LOAD_TIMING_INFO_H_
+#define NET_BASE_LOAD_TIMING_INFO_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Structure containing timing information for a request.
+// It addresses the needs of
+// http://groups.google.com/group/http-archive-specification/web/har-1-1-spec,
+// http://dev.w3.org/2006/webapi/WebTiming/, and
+// http://www.w3.org/TR/resource-timing/.
+//
+// All events that do not apply to a request have null times. For non-HTTP
+// requests, all times other than the request_start times are null.
+//
+// Requests with connection errors generally only have request start times as
+// well, since they never received an established socket.
+//
+// The general order for events is:
+// request_start
+// proxy_start
+// proxy_end
+// dns_start
+// dns_end
+// connect_start
+// ssl_start
+// ssl_end
+// connect_end
+// send_start
+// send_end
+// receive_headers_end
+//
+// Times represent when a request starts/stops blocking on an event, not the
+// time the events actually occurred. In particular, in the case of preconnects
+// and socket reuse, no time may be spent blocking on establishing a connection.
+// In the case of SPDY and HTTP pipelining, PAC scripts are only run once for
+// each shared session, so no time may be spent blocking on them.
+//
+// DNS and SSL times are both times for the host, not the proxy, so DNS times
+// when using proxies are null, and only requests to HTTPS hosts (Not proxies)
+// have SSL times. One exception to this is when a proxy server itself returns
+// a redirect response. In this case, the connect times treat the proxy as the
+// host. The send and receive times will all be null, however.
+// See HttpNetworkTransaction::OnHttpsProxyTunnelResponse.
+// TODO(mmenke): Is this worth fixing?
+//
+// Note that internal to the network stack, times are when events actually
+// occurred. URLRequest converts them to time which the network stack was
+// blocked on each state.
+struct NET_EXPORT LoadTimingInfo {
+ // Contains the LoadTimingInfo events related to establishing a connection.
+ // These are all set by ConnectJobs.
+ struct NET_EXPORT_PRIVATE ConnectTiming {
+ ConnectTiming();
+ ~ConnectTiming();
+
+ // The time spent looking up the host's DNS address. Null for requests that
+ // used proxies to look up the DNS address. Also null for SOCKS4 proxies,
+ // since the DNS address is only looked up after the connection is
+ // established, which results in unexpected event ordering.
+ // TODO(mmenke): The SOCKS4 event ordering could be refactored to allow
+ // these times to be non-null.
+ base::TimeTicks dns_start;
+ base::TimeTicks dns_end;
+
+ // The time spent establishing the connection. Connect time includes proxy
+ // connect times (Though not proxy_resolve times), DNS lookup times, time
+ // spent waiting in certain queues, TCP, and SSL time.
+ // TODO(mmenke): For proxies, this includes time spent blocking on higher
+ // level socket pools. Fix this.
+ // TODO(mmenke): Retried connections to the same server should apparently
+ // be included in this time. Consider supporting that.
+ // Since the network stack has multiple notions of a "retry",
+ // handled at different levels, this may not be worth
+ // worrying about - backup jobs, reused socket failure,
+ // multiple round authentication.
+ base::TimeTicks connect_start;
+ base::TimeTicks connect_end;
+
+ // The time when the SSL handshake started / completed. For non-HTTPS
+ // requests these are null. These times are only for the SSL connection to
+ // the final destination server, not an SSL/SPDY proxy.
+ base::TimeTicks ssl_start;
+ base::TimeTicks ssl_end;
+ };
+
+ LoadTimingInfo();
+ ~LoadTimingInfo();
+
+ // True if the socket was reused. When true, DNS, connect, and SSL times
+ // will all be null. When false, those times may be null, too, for non-HTTP
+ // requests, or when they don't apply to a request.
+ //
+ // For requests that are sent again after an AUTH challenge, this will be true
+ // if the original socket is reused, and false if a new socket is used.
+ // Responding to a proxy AUTH challenge is never considered to be reusing a
+ // socket, since a connection to the host wasn't established when the
+ // challenge was received.
+ bool socket_reused;
+
+ // Unique socket ID, can be used to identify requests served by the same
+ // socket. For connections tunnelled over SPDY proxies, this is the ID of
+ // the virtual connection (The SpdyProxyClientSocket), not the ID of the
+ // actual socket. HTTP requests handled by the SPDY proxy itself all use the
+ // actual socket's ID.
+ //
+ // 0 when there is no socket associated with the request, or it's not an HTTP
+ // request.
+ uint32 socket_log_id;
+
+ // Start time as a base::Time, so times can be coverted into actual times.
+ // Other times are recorded as TimeTicks so they are not affected by clock
+ // changes.
+ base::Time request_start_time;
+
+ base::TimeTicks request_start;
+
+ // The time spent determing which proxy to use. Null when there is no PAC.
+ base::TimeTicks proxy_resolve_start;
+ base::TimeTicks proxy_resolve_end;
+
+ ConnectTiming connect_timing;
+
+ // The time that sending HTTP request started / ended.
+ base::TimeTicks send_start;
+ base::TimeTicks send_end;
+
+ // The time at which the end of the HTTP headers were received.
+ base::TimeTicks receive_headers_end;
+};
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_TIMING_INFO_H_
diff --git a/chromium/net/base/load_timing_info_test_util.cc b/chromium/net/base/load_timing_info_test_util.cc
new file mode 100644
index 00000000000..a34a5832400
--- /dev/null
+++ b/chromium/net/base/load_timing_info_test_util.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2013 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/base/load_timing_info_test_util.h"
+
+#include "net/base/load_timing_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+void ExpectConnectTimingHasNoTimes(
+ const LoadTimingInfo::ConnectTiming& connect_timing) {
+ EXPECT_TRUE(connect_timing.dns_start.is_null());
+ EXPECT_TRUE(connect_timing.dns_end.is_null());
+ EXPECT_TRUE(connect_timing.connect_start.is_null());
+ EXPECT_TRUE(connect_timing.connect_end.is_null());
+ EXPECT_TRUE(connect_timing.ssl_start.is_null());
+ EXPECT_TRUE(connect_timing.ssl_end.is_null());
+}
+
+void ExpectConnectTimingHasTimes(
+ const LoadTimingInfo::ConnectTiming& connect_timing,
+ int connect_timing_flags) {
+ EXPECT_FALSE(connect_timing.connect_start.is_null());
+ EXPECT_LE(connect_timing.connect_start, connect_timing.connect_end);
+
+ if (!(connect_timing_flags & CONNECT_TIMING_HAS_DNS_TIMES)) {
+ EXPECT_TRUE(connect_timing.dns_start.is_null());
+ EXPECT_TRUE(connect_timing.dns_end.is_null());
+ } else {
+ EXPECT_FALSE(connect_timing.dns_start.is_null());
+ EXPECT_LE(connect_timing.dns_start, connect_timing.dns_end);
+ EXPECT_LE(connect_timing.dns_end, connect_timing.connect_start);
+ }
+
+ if (!(connect_timing_flags & CONNECT_TIMING_HAS_SSL_TIMES)) {
+ EXPECT_TRUE(connect_timing.ssl_start.is_null());
+ EXPECT_TRUE(connect_timing.ssl_start.is_null());
+ } else {
+ EXPECT_FALSE(connect_timing.ssl_start.is_null());
+ EXPECT_LE(connect_timing.connect_start, connect_timing.ssl_start);
+ EXPECT_LE(connect_timing.ssl_start, connect_timing.ssl_end);
+ EXPECT_LE(connect_timing.ssl_end, connect_timing.connect_end);
+ }
+}
+
+void ExpectLoadTimingHasOnlyConnectionTimes(
+ const LoadTimingInfo& load_timing_info) {
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+ EXPECT_TRUE(load_timing_info.send_start.is_null());
+ EXPECT_TRUE(load_timing_info.send_end.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+} // namespace net
diff --git a/chromium/net/base/load_timing_info_test_util.h b/chromium/net/base/load_timing_info_test_util.h
new file mode 100644
index 00000000000..5c2fd172a77
--- /dev/null
+++ b/chromium/net/base/load_timing_info_test_util.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2013 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_BASE_LOAD_TIMING_INFO_TEST_UTIL_H_
+#define NET_BASE_LOAD_TIMING_INFO_TEST_UTIL_H_
+
+#include "net/base/load_timing_info.h"
+
+namespace net {
+
+// Flags indicating which times in a LoadTimingInfo::ConnectTiming struct should
+// be non-null.
+enum ConnectTimeFlags {
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY = 0,
+ CONNECT_TIMING_HAS_DNS_TIMES = 1 << 0,
+ CONNECT_TIMING_HAS_SSL_TIMES = 1 << 1,
+};
+
+// Checks that all times in |connect_timing| are null.
+void ExpectConnectTimingHasNoTimes(
+ const LoadTimingInfo::ConnectTiming& connect_timing);
+
+// Checks that |connect_timing|'s times are in the correct order.
+// |connect_start| and |connect_end| must be non-null. Checks null state and
+// order of DNS times and SSL times based on |flags|, which must be a
+// combination of ConnectTimeFlags.
+void ExpectConnectTimingHasTimes(
+ const LoadTimingInfo::ConnectTiming& connect_timing,
+ int connect_timing_flags);
+
+// Tests that all non-connection establishment times in |load_timing_info| are
+// null. Its |connect_timing| field is ignored.
+void ExpectLoadTimingHasOnlyConnectionTimes(
+ const LoadTimingInfo& load_timing_info);
+
+} // namespace net
+
+#endif // NET_BASE_LOAD_TIMING_INFO_TEST_UTIL_H_
diff --git a/chromium/net/base/mime_sniffer.cc b/chromium/net/base/mime_sniffer.cc
new file mode 100644
index 00000000000..cc83c7c3bda
--- /dev/null
+++ b/chromium/net/base/mime_sniffer.cc
@@ -0,0 +1,971 @@
+// Copyright (c) 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.
+
+// Detecting mime types is a tricky business because we need to balance
+// compatibility concerns with security issues. Here is a survey of how other
+// browsers behave and then a description of how we intend to behave.
+//
+// HTML payload, no Content-Type header:
+// * IE 7: Render as HTML
+// * Firefox 2: Render as HTML
+// * Safari 3: Render as HTML
+// * Opera 9: Render as HTML
+//
+// Here the choice seems clear:
+// => Chrome: Render as HTML
+//
+// HTML payload, Content-Type: "text/plain":
+// * IE 7: Render as HTML
+// * Firefox 2: Render as text
+// * Safari 3: Render as text (Note: Safari will Render as HTML if the URL
+// has an HTML extension)
+// * Opera 9: Render as text
+//
+// Here we choose to follow the majority (and break some compatibility with IE).
+// Many folks dislike IE's behavior here.
+// => Chrome: Render as text
+// We generalize this as follows. If the Content-Type header is text/plain
+// we won't detect dangerous mime types (those that can execute script).
+//
+// HTML payload, Content-Type: "application/octet-stream":
+// * IE 7: Render as HTML
+// * Firefox 2: Download as application/octet-stream
+// * Safari 3: Render as HTML
+// * Opera 9: Render as HTML
+//
+// We follow Firefox.
+// => Chrome: Download as application/octet-stream
+// One factor in this decision is that IIS 4 and 5 will send
+// application/octet-stream for .xhtml files (because they don't recognize
+// the extension). We did some experiments and it looks like this doesn't occur
+// very often on the web. We choose the more secure option.
+//
+// GIF payload, no Content-Type header:
+// * IE 7: Render as GIF
+// * Firefox 2: Render as GIF
+// * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
+// URL has an GIF extension)
+// * Opera 9: Render as GIF
+//
+// The choice is clear.
+// => Chrome: Render as GIF
+// Once we decide to render HTML without a Content-Type header, there isn't much
+// reason not to render GIFs.
+//
+// GIF payload, Content-Type: "text/plain":
+// * IE 7: Render as GIF
+// * Firefox 2: Download as application/octet-stream (Note: Firefox will
+// Download as GIF if the URL has an GIF extension)
+// * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
+// URL has an GIF extension)
+// * Opera 9: Render as GIF
+//
+// Displaying as text/plain makes little sense as the content will look like
+// gibberish. Here, we could change our minds and download.
+// => Chrome: Render as GIF
+//
+// GIF payload, Content-Type: "application/octet-stream":
+// * IE 7: Render as GIF
+// * Firefox 2: Download as application/octet-stream (Note: Firefox will
+// Download as GIF if the URL has an GIF extension)
+// * Safari 3: Download as Unknown (Note: Safari will Render as GIF if the
+// URL has an GIF extension)
+// * Opera 9: Render as GIF
+//
+// We used to render as GIF here, but the problem is that some sites want to
+// trigger downloads by sending application/octet-stream (even though they
+// should be sending Content-Disposition: attachment). Although it is safe
+// to render as GIF from a security perspective, we actually get better
+// compatibility if we don't sniff from application/octet stream at all.
+// => Chrome: Download as application/octet-stream
+//
+// XHTML payload, Content-Type: "text/xml":
+// * IE 7: Render as XML
+// * Firefox 2: Render as HTML
+// * Safari 3: Render as HTML
+// * Opera 9: Render as HTML
+// The layout tests rely on us rendering this as HTML.
+// But we're conservative in XHTML detection, as this runs afoul of the
+// "don't detect dangerous mime types" rule.
+//
+// Note that our definition of HTML payload is much stricter than IE's
+// definition and roughly the same as Firefox's definition.
+
+#include <string>
+
+#include "net/base/mime_sniffer.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "net/base/mime_util.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// The number of content bytes we need to use all our magic numbers. Feel free
+// to increase this number if you add a longer magic number.
+static const size_t kBytesRequiredForMagic = 42;
+
+struct MagicNumber {
+ const char* mime_type;
+ const char* magic;
+ size_t magic_len;
+ bool is_string;
+ const char* mask; // if set, must have same length as |magic|
+};
+
+#define MAGIC_NUMBER(mime_type, magic) \
+ { (mime_type), (magic), sizeof(magic)-1, false, NULL },
+
+template <int MagicSize, int MaskSize>
+class VerifySizes {
+ COMPILE_ASSERT(MagicSize == MaskSize, sizes_must_be_equal);
+ public:
+ enum { SIZES = MagicSize };
+};
+
+#define verified_sizeof(magic, mask) \
+VerifySizes<sizeof(magic), sizeof(mask)>::SIZES
+
+#define MAGIC_MASK(mime_type, magic, mask) \
+ { (mime_type), (magic), verified_sizeof(magic, mask)-1, false, (mask) },
+
+// Magic strings are case insensitive and must not include '\0' characters
+#define MAGIC_STRING(mime_type, magic) \
+ { (mime_type), (magic), sizeof(magic)-1, true, NULL },
+
+static const MagicNumber kMagicNumbers[] = {
+ // Source: HTML 5 specification
+ MAGIC_NUMBER("application/pdf", "%PDF-")
+ MAGIC_NUMBER("application/postscript", "%!PS-Adobe-")
+ MAGIC_NUMBER("image/gif", "GIF87a")
+ MAGIC_NUMBER("image/gif", "GIF89a")
+ MAGIC_NUMBER("image/png", "\x89" "PNG\x0D\x0A\x1A\x0A")
+ MAGIC_NUMBER("image/jpeg", "\xFF\xD8\xFF")
+ MAGIC_NUMBER("image/bmp", "BM")
+ // Source: Mozilla
+ MAGIC_NUMBER("text/plain", "#!") // Script
+ MAGIC_NUMBER("text/plain", "%!") // Script, similar to PS
+ MAGIC_NUMBER("text/plain", "From")
+ MAGIC_NUMBER("text/plain", ">From")
+ // Chrome specific
+ MAGIC_NUMBER("application/x-gzip", "\x1F\x8B\x08")
+ MAGIC_NUMBER("audio/x-pn-realaudio", "\x2E\x52\x4D\x46")
+ MAGIC_NUMBER("video/x-ms-asf",
+ "\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C")
+ MAGIC_NUMBER("image/tiff", "I I")
+ MAGIC_NUMBER("image/tiff", "II*")
+ MAGIC_NUMBER("image/tiff", "MM\x00*")
+ MAGIC_NUMBER("audio/mpeg", "ID3")
+ MAGIC_NUMBER("image/webp", "RIFF....WEBPVP8 ")
+ MAGIC_NUMBER("video/webm", "\x1A\x45\xDF\xA3")
+ // TODO(abarth): we don't handle partial byte matches yet
+ // MAGIC_NUMBER("video/mpeg", "\x00\x00\x01\xB")
+ // MAGIC_NUMBER("audio/mpeg", "\xFF\xE")
+ // MAGIC_NUMBER("audio/mpeg", "\xFF\xF")
+ MAGIC_NUMBER("application/zip", "PK\x03\x04")
+ MAGIC_NUMBER("application/x-rar-compressed", "Rar!\x1A\x07\x00")
+ MAGIC_NUMBER("application/x-msmetafile", "\xD7\xCD\xC6\x9A")
+ MAGIC_NUMBER("application/octet-stream", "MZ") // EXE
+ // Sniffing for Flash:
+ //
+ // MAGIC_NUMBER("application/x-shockwave-flash", "CWS")
+ // MAGIC_NUMBER("application/x-shockwave-flash", "FLV")
+ // MAGIC_NUMBER("application/x-shockwave-flash", "FWS")
+ //
+ // Including these magic number for Flash is a trade off.
+ //
+ // Pros:
+ // * Flash is an important and popular file format
+ //
+ // Cons:
+ // * These patterns are fairly weak
+ // * If we mistakenly decide something is Flash, we will execute it
+ // in the origin of an unsuspecting site. This could be a security
+ // vulnerability if the site allows users to upload content.
+ //
+ // On balance, we do not include these patterns.
+};
+
+// The number of content bytes we need to use all our Microsoft Office magic
+// numbers.
+static const size_t kBytesRequiredForOfficeMagic = 8;
+
+static const MagicNumber kOfficeMagicNumbers[] = {
+ MAGIC_NUMBER("CFB", "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1")
+ MAGIC_NUMBER("OOXML", "PK\x03\x04")
+};
+
+enum OfficeDocType {
+ DOC_TYPE_WORD,
+ DOC_TYPE_EXCEL,
+ DOC_TYPE_POWERPOINT,
+ DOC_TYPE_NONE
+};
+
+struct OfficeExtensionType {
+ OfficeDocType doc_type;
+ const char* extension;
+ size_t extension_len;
+};
+
+#define OFFICE_EXTENSION(type, extension) \
+ { (type), (extension), sizeof(extension) - 1 },
+
+static const OfficeExtensionType kOfficeExtensionTypes[] = {
+ OFFICE_EXTENSION(DOC_TYPE_WORD, ".doc")
+ OFFICE_EXTENSION(DOC_TYPE_EXCEL, ".xls")
+ OFFICE_EXTENSION(DOC_TYPE_POWERPOINT, ".ppt")
+ OFFICE_EXTENSION(DOC_TYPE_WORD, ".docx")
+ OFFICE_EXTENSION(DOC_TYPE_EXCEL, ".xlsx")
+ OFFICE_EXTENSION(DOC_TYPE_POWERPOINT, ".pptx")
+};
+
+static const MagicNumber kExtraMagicNumbers[] = {
+ MAGIC_NUMBER("image/x-xbitmap", "#define")
+ MAGIC_NUMBER("image/x-icon", "\x00\x00\x01\x00")
+ MAGIC_NUMBER("image/svg+xml", "<?xml_version=")
+ MAGIC_NUMBER("audio/wav", "RIFF....WAVEfmt ")
+ MAGIC_NUMBER("video/avi", "RIFF....AVI LIST")
+ MAGIC_NUMBER("audio/ogg", "OggS")
+ MAGIC_MASK("video/mpeg", "\x00\x00\x01\xB0", "\xFF\xFF\xFF\xF0")
+ MAGIC_MASK("audio/mpeg", "\xFF\xE0", "\xFF\xE0")
+ MAGIC_NUMBER("video/3gpp", "....ftyp3g")
+ MAGIC_NUMBER("video/3gpp", "....ftypavcl")
+ MAGIC_NUMBER("video/mp4", "....ftyp")
+ MAGIC_NUMBER("video/quicktime", "MOVI")
+ MAGIC_NUMBER("application/x-shockwave-flash", "CWS")
+ MAGIC_NUMBER("application/x-shockwave-flash", "FWS")
+ MAGIC_NUMBER("video/x-flv", "FLV")
+ MAGIC_NUMBER("audio/x-flac", "fLaC")
+
+ // RAW image types.
+ MAGIC_NUMBER("image/x-canon-cr2", "II\x2a\x00\x10\x00\x00\x00CR")
+ MAGIC_NUMBER("image/x-canon-crw", "II\x1a\x00\x00\x00HEAPCCDR")
+ MAGIC_NUMBER("image/x-minolta-mrw", "\x00MRM")
+ MAGIC_NUMBER("image/x-olympus-orf", "MMOR") // big-endian
+ MAGIC_NUMBER("image/x-olympus-orf", "IIRO") // little-endian
+ MAGIC_NUMBER("image/x-olympus-orf", "IIRS") // little-endian
+ MAGIC_NUMBER("image/x-fuji-raf", "FUJIFILMCCD-RAW ")
+ MAGIC_NUMBER("image/x-panasonic-raw",
+ "IIU\x00\x08\x00\x00\x00") // Panasonic .raw
+ MAGIC_NUMBER("image/x-panasonic-raw",
+ "IIU\x00\x18\x00\x00\x00") // Panasonic .rw2
+ MAGIC_NUMBER("image/x-phaseone-raw", "MMMMRaw")
+ MAGIC_NUMBER("image/x-x3f", "FOVb")
+};
+
+// Our HTML sniffer differs slightly from Mozilla. For example, Mozilla will
+// decide that a document that begins "<!DOCTYPE SOAP-ENV:Envelope PUBLIC " is
+// HTML, but we will not.
+
+#define MAGIC_HTML_TAG(tag) \
+ MAGIC_STRING("text/html", "<" tag)
+
+static const MagicNumber kSniffableTags[] = {
+ // XML processing directive. Although this is not an HTML mime type, we sniff
+ // for this in the HTML phase because text/xml is just as powerful as HTML and
+ // we want to leverage our white space skipping technology.
+ MAGIC_NUMBER("text/xml", "<?xml") // Mozilla
+ // DOCTYPEs
+ MAGIC_HTML_TAG("!DOCTYPE html") // HTML5 spec
+ // Sniffable tags, ordered by how often they occur in sniffable documents.
+ MAGIC_HTML_TAG("script") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("html") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("!--")
+ MAGIC_HTML_TAG("head") // HTML5 spec, Mozilla
+ MAGIC_HTML_TAG("iframe") // Mozilla
+ MAGIC_HTML_TAG("h1") // Mozilla
+ MAGIC_HTML_TAG("div") // Mozilla
+ MAGIC_HTML_TAG("font") // Mozilla
+ MAGIC_HTML_TAG("table") // Mozilla
+ MAGIC_HTML_TAG("a") // Mozilla
+ MAGIC_HTML_TAG("style") // Mozilla
+ MAGIC_HTML_TAG("title") // Mozilla
+ MAGIC_HTML_TAG("b") // Mozilla
+ MAGIC_HTML_TAG("body") // Mozilla
+ MAGIC_HTML_TAG("br")
+ MAGIC_HTML_TAG("p") // Mozilla
+};
+
+static base::HistogramBase* UMASnifferHistogramGet(const char* name,
+ int array_size) {
+ base::HistogramBase* counter =
+ base::LinearHistogram::FactoryGet(name, 1, array_size - 1, array_size,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ return counter;
+}
+
+// Compare content header to a magic number where magic_entry can contain '.'
+// for single character of anything, allowing some bytes to be skipped.
+static bool MagicCmp(const char* magic_entry, const char* content, size_t len) {
+ while (len) {
+ if ((*magic_entry != '.') && (*magic_entry != *content))
+ return false;
+ ++magic_entry;
+ ++content;
+ --len;
+ }
+ return true;
+}
+
+// Like MagicCmp() except that it ANDs each byte with a mask before
+// the comparison, because there are some bits we don't care about.
+static bool MagicMaskCmp(const char* magic_entry,
+ const char* content,
+ size_t len,
+ const char* mask) {
+ while (len) {
+ if ((*magic_entry != '.') && (*magic_entry != (*mask & *content)))
+ return false;
+ ++magic_entry;
+ ++content;
+ ++mask;
+ --len;
+ }
+ return true;
+}
+
+static bool MatchMagicNumber(const char* content,
+ size_t size,
+ const MagicNumber& magic_entry,
+ std::string* result) {
+ const size_t len = magic_entry.magic_len;
+
+ // Keep kBytesRequiredForMagic honest.
+ DCHECK_LE(len, kBytesRequiredForMagic);
+
+ // To compare with magic strings, we need to compute strlen(content), but
+ // content might not actually have a null terminator. In that case, we
+ // pretend the length is content_size.
+ const char* end = static_cast<const char*>(memchr(content, '\0', size));
+ const size_t content_strlen =
+ (end != NULL) ? static_cast<size_t>(end - content) : size;
+
+ bool match = false;
+ if (magic_entry.is_string) {
+ if (content_strlen >= len) {
+ // String comparisons are case-insensitive
+ match = (base::strncasecmp(magic_entry.magic, content, len) == 0);
+ }
+ } else {
+ if (size >= len) {
+ if (!magic_entry.mask) {
+ match = MagicCmp(magic_entry.magic, content, len);
+ } else {
+ match = MagicMaskCmp(magic_entry.magic, content, len, magic_entry.mask);
+ }
+ }
+ }
+
+ if (match) {
+ result->assign(magic_entry.mime_type);
+ return true;
+ }
+ return false;
+}
+
+static bool CheckForMagicNumbers(const char* content, size_t size,
+ const MagicNumber* magic, size_t magic_len,
+ base::HistogramBase* counter,
+ std::string* result) {
+ for (size_t i = 0; i < magic_len; ++i) {
+ if (MatchMagicNumber(content, size, magic[i], result)) {
+ if (counter) counter->Add(static_cast<int>(i));
+ return true;
+ }
+ }
+ return false;
+}
+
+// Truncates |size| to |max_size| and returns true if |size| is at least
+// |max_size|.
+static bool TruncateSize(const size_t max_size, size_t* size) {
+ // Keep kMaxBytesToSniff honest.
+ DCHECK_LE(static_cast<int>(max_size), kMaxBytesToSniff);
+
+ if (*size >= max_size) {
+ *size = max_size;
+ return true;
+ }
+ return false;
+}
+
+// Returns true and sets result if the content appears to be HTML.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffForHTML(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ // For HTML, we are willing to consider up to 512 bytes. This may be overly
+ // conservative as IE only considers 256.
+ *have_enough_content &= TruncateSize(512, &size);
+
+ // We adopt a strategy similar to that used by Mozilla to sniff HTML tags,
+ // but with some modifications to better match the HTML5 spec.
+ const char* const end = content + size;
+ const char* pos;
+ for (pos = content; pos < end; ++pos) {
+ if (!IsAsciiWhitespace(*pos))
+ break;
+ }
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kSniffableTags2",
+ arraysize(kSniffableTags));
+ }
+ // |pos| now points to first non-whitespace character (or at end).
+ return CheckForMagicNumbers(pos, end - pos,
+ kSniffableTags, arraysize(kSniffableTags),
+ counter, result);
+}
+
+// Returns true and sets result if the content matches any of kMagicNumbers.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffForMagicNumbers(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ *have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
+
+ // Check our big table of Magic Numbers
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kMagicNumbers2",
+ arraysize(kMagicNumbers));
+ }
+ return CheckForMagicNumbers(content, size,
+ kMagicNumbers, arraysize(kMagicNumbers),
+ counter, result);
+}
+
+// Returns true and sets result if the content matches any of
+// kOfficeMagicNumbers, and the URL has the proper extension.
+// Clears |have_enough_content| if more data could possibly change the result.
+static bool SniffForOfficeDocs(const char* content,
+ size_t size,
+ const GURL& url,
+ bool* have_enough_content,
+ std::string* result) {
+ *have_enough_content &= TruncateSize(kBytesRequiredForOfficeMagic, &size);
+
+ // Check our table of magic numbers for Office file types.
+ std::string office_version;
+ if (!CheckForMagicNumbers(content, size,
+ kOfficeMagicNumbers, arraysize(kOfficeMagicNumbers),
+ NULL, &office_version))
+ return false;
+
+ OfficeDocType type = DOC_TYPE_NONE;
+ for (size_t i = 0; i < arraysize(kOfficeExtensionTypes); ++i) {
+ std::string url_path = url.path();
+
+ if (url_path.length() < kOfficeExtensionTypes[i].extension_len)
+ continue;
+
+ const char* extension =
+ &url_path[url_path.length() - kOfficeExtensionTypes[i].extension_len];
+
+ if (0 == base::strncasecmp(extension, kOfficeExtensionTypes[i].extension,
+ kOfficeExtensionTypes[i].extension_len)) {
+ type = kOfficeExtensionTypes[i].doc_type;
+ break;
+ }
+ }
+
+ if (type == DOC_TYPE_NONE)
+ return false;
+
+ if (office_version == "CFB") {
+ switch (type) {
+ case DOC_TYPE_WORD:
+ *result = "application/msword";
+ return true;
+ case DOC_TYPE_EXCEL:
+ *result = "application/vnd.ms-excel";
+ return true;
+ case DOC_TYPE_POWERPOINT:
+ *result = "application/vnd.ms-powerpoint";
+ return true;
+ case DOC_TYPE_NONE:
+ NOTREACHED();
+ return false;
+ }
+ } else if (office_version == "OOXML") {
+ switch (type) {
+ case DOC_TYPE_WORD:
+ *result = "application/vnd.openxmlformats-officedocument."
+ "wordprocessingml.document";
+ return true;
+ case DOC_TYPE_EXCEL:
+ *result = "application/vnd.openxmlformats-officedocument."
+ "spreadsheetml.sheet";
+ return true;
+ case DOC_TYPE_POWERPOINT:
+ *result = "application/vnd.openxmlformats-officedocument."
+ "presentationml.presentation";
+ return true;
+ case DOC_TYPE_NONE:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+static bool IsOfficeType(const std::string& type_hint) {
+ return (type_hint == "application/msword" ||
+ type_hint == "application/vnd.ms-excel" ||
+ type_hint == "application/vnd.ms-powerpoint" ||
+ type_hint == "application/vnd.openxmlformats-officedocument."
+ "wordprocessingml.document" ||
+ type_hint == "application/vnd.openxmlformats-officedocument."
+ "spreadsheetml.sheet" ||
+ type_hint == "application/vnd.openxmlformats-officedocument."
+ "presentationml.presentation" ||
+ type_hint == "application/vnd.ms-excel.sheet.macroenabled.12" ||
+ type_hint == "application/vnd.ms-word.document.macroenabled.12" ||
+ type_hint == "application/vnd.ms-powerpoint.presentation."
+ "macroenabled.12" ||
+ type_hint == "application/mspowerpoint" ||
+ type_hint == "application/msexcel" ||
+ type_hint == "application/vnd.ms-word" ||
+ type_hint == "application/vnd.ms-word.document.12" ||
+ type_hint == "application/vnd.msword");
+}
+
+// This function checks for files that have a Microsoft Office MIME type
+// set, but are not actually Office files.
+//
+// If this is not actually an Office file, |*result| is set to
+// "application/octet-stream", otherwise it is not modified.
+//
+// Returns false if additional data is required to determine the file type, or
+// true if there is enough data to make a decision.
+static bool SniffForInvalidOfficeDocs(const char* content,
+ size_t size,
+ const GURL& url,
+ std::string* result) {
+ if (!TruncateSize(kBytesRequiredForOfficeMagic, &size))
+ return false;
+
+ // Check our table of magic numbers for Office file types. If it does not
+ // match one, the MIME type was invalid. Set it instead to a safe value.
+ std::string office_version;
+ if (!CheckForMagicNumbers(content, size,
+ kOfficeMagicNumbers, arraysize(kOfficeMagicNumbers),
+ NULL, &office_version)) {
+ *result = "application/octet-stream";
+ }
+
+ // We have enough information to determine if this was a Microsoft Office
+ // document or not, so sniffing is completed.
+ return true;
+}
+
+// Byte order marks
+static const MagicNumber kMagicXML[] = {
+ // We want to be very conservative in interpreting text/xml content as
+ // XHTML -- we just want to sniff enough to make unit tests pass.
+ // So we match explicitly on this, and don't match other ways of writing
+ // it in semantically-equivalent ways.
+ MAGIC_STRING("application/xhtml+xml",
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\"")
+ MAGIC_STRING("application/atom+xml", "<feed")
+ MAGIC_STRING("application/rss+xml", "<rss") // UTF-8
+};
+
+// Returns true and sets result if the content appears to contain XHTML or a
+// feed.
+// Clears have_enough_content if more data could possibly change the result.
+//
+// TODO(evanm): this is similar but more conservative than what Safari does,
+// while HTML5 has a different recommendation -- what should we do?
+// TODO(evanm): this is incorrect for documents whose encoding isn't a superset
+// of ASCII -- do we care?
+static bool SniffXML(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ // We allow at most 300 bytes of content before we expect the opening tag.
+ *have_enough_content &= TruncateSize(300, &size);
+ const char* pos = content;
+ const char* const end = content + size;
+
+ // This loop iterates through tag-looking offsets in the file.
+ // We want to skip XML processing instructions (of the form "<?xml ...")
+ // and stop at the first "plain" tag, then make a decision on the mime-type
+ // based on the name (or possibly attributes) of that tag.
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kMagicXML2",
+ arraysize(kMagicXML));
+ }
+ const int kMaxTagIterations = 5;
+ for (int i = 0; i < kMaxTagIterations && pos < end; ++i) {
+ pos = reinterpret_cast<const char*>(memchr(pos, '<', end - pos));
+ if (!pos)
+ return false;
+
+ if (base::strncasecmp(pos, "<?xml", sizeof("<?xml") - 1) == 0) {
+ // Skip XML declarations.
+ ++pos;
+ continue;
+ } else if (base::strncasecmp(pos, "<!DOCTYPE",
+ sizeof("<!DOCTYPE") - 1) == 0) {
+ // Skip DOCTYPE declarations.
+ ++pos;
+ continue;
+ }
+
+ if (CheckForMagicNumbers(pos, end - pos,
+ kMagicXML, arraysize(kMagicXML),
+ counter, result))
+ return true;
+
+ // TODO(evanm): handle RSS 1.0, which is an RDF format and more difficult
+ // to identify.
+
+ // If we get here, we've hit an initial tag that hasn't matched one of the
+ // above tests. Abort.
+ return true;
+ }
+
+ // We iterated too far without finding a start tag.
+ // If we have more content to look at, we aren't going to change our mind by
+ // seeing more bytes from the network.
+ return pos < end;
+}
+
+// Byte order marks
+static const MagicNumber kByteOrderMark[] = {
+ MAGIC_NUMBER("text/plain", "\xFE\xFF") // UTF-16BE
+ MAGIC_NUMBER("text/plain", "\xFF\xFE") // UTF-16LE
+ MAGIC_NUMBER("text/plain", "\xEF\xBB\xBF") // UTF-8
+};
+
+// Whether a given byte looks like it might be part of binary content.
+// Source: HTML5 spec
+static char kByteLooksBinary[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, // 0x00 - 0x0F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 - 0x2F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x4F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x5F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x6F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x7F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0x8F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0 - 0xAF
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0 - 0xBF
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0 - 0xCF
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0 - 0xDF
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0 - 0xEF
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xF0 - 0xFF
+};
+
+// Returns true and sets result to "application/octet-stream" if the content
+// appears to be binary data. Otherwise, returns false and sets "text/plain".
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffBinary(const char* content,
+ size_t size,
+ bool* have_enough_content,
+ std::string* result) {
+ // There is no concensus about exactly how to sniff for binary content.
+ // * IE 7: Don't sniff for binary looking bytes, but trust the file extension.
+ // * Firefox 3.5: Sniff first 4096 bytes for a binary looking byte.
+ // Here, we side with FF, but with a smaller buffer. This size was chosen
+ // because it is small enough to comfortably fit into a single packet (after
+ // allowing for headers) and yet large enough to account for binary formats
+ // that have a significant amount of ASCII at the beginning (crbug.com/15314).
+ const bool is_truncated = TruncateSize(kMaxBytesToSniff, &size);
+
+ // First, we look for a BOM.
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kByteOrderMark2",
+ arraysize(kByteOrderMark));
+ }
+ std::string unused;
+ if (CheckForMagicNumbers(content, size,
+ kByteOrderMark, arraysize(kByteOrderMark),
+ counter, &unused)) {
+ // If there is BOM, we think the buffer is not binary.
+ result->assign("text/plain");
+ return false;
+ }
+
+ // Next we look to see if any of the bytes "look binary."
+ for (size_t i = 0; i < size; ++i) {
+ // If we a see a binary-looking byte, we think the content is binary.
+ if (kByteLooksBinary[static_cast<unsigned char>(content[i])]) {
+ result->assign("application/octet-stream");
+ return true;
+ }
+ }
+
+ // No evidence either way. Default to non-binary and, if truncated, clear
+ // have_enough_content because there could be a binary looking byte in the
+ // truncated data.
+ *have_enough_content &= is_truncated;
+ result->assign("text/plain");
+ return false;
+}
+
+static bool IsUnknownMimeType(const std::string& mime_type) {
+ // TODO(tc): Maybe reuse some code in net/http/http_response_headers.* here.
+ // If we do, please be careful not to alter the semantics at all.
+ static const char* kUnknownMimeTypes[] = {
+ // Empty mime types are as unknown as they get.
+ "",
+ // The unknown/unknown type is popular and uninformative
+ "unknown/unknown",
+ // The second most popular unknown mime type is application/unknown
+ "application/unknown",
+ // Firefox rejects a mime type if it is exactly */*
+ "*/*",
+ };
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kUnknownMimeTypes2",
+ arraysize(kUnknownMimeTypes) + 1);
+ }
+ for (size_t i = 0; i < arraysize(kUnknownMimeTypes); ++i) {
+ if (mime_type == kUnknownMimeTypes[i]) {
+ counter->Add(i);
+ return true;
+ }
+ }
+ if (mime_type.find('/') == std::string::npos) {
+ // Firefox rejects a mime type if it does not contain a slash
+ counter->Add(arraysize(kUnknownMimeTypes));
+ return true;
+ }
+ return false;
+}
+
+// Returns true and sets result if the content appears to be a crx (Chrome
+// extension) file.
+// Clears have_enough_content if more data could possibly change the result.
+static bool SniffCRX(const char* content,
+ size_t size,
+ const GURL& url,
+ const std::string& type_hint,
+ bool* have_enough_content,
+ std::string* result) {
+ static base::HistogramBase* counter(NULL);
+ if (!counter)
+ counter = UMASnifferHistogramGet("mime_sniffer.kSniffCRX", 3);
+
+ // Technically, the crx magic number is just Cr24, but the bytes after that
+ // are a version number which changes infrequently. Including it in the
+ // sniffing gives us less room for error. If the version number ever changes,
+ // we can just add an entry to this list.
+ //
+ // TODO(aa): If we ever have another magic number, we'll want to pass a
+ // histogram into CheckForMagicNumbers(), below, to see which one matched.
+ static const struct MagicNumber kCRXMagicNumbers[] = {
+ MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x02\x00\x00\x00")
+ };
+
+ // Only consider files that have the extension ".crx".
+ static const char kCRXExtension[] = ".crx";
+ // Ignore null by subtracting 1.
+ static const int kExtensionLength = arraysize(kCRXExtension) - 1;
+ if (url.path().rfind(kCRXExtension, std::string::npos, kExtensionLength) ==
+ url.path().size() - kExtensionLength) {
+ counter->Add(1);
+ } else {
+ return false;
+ }
+
+ *have_enough_content &= TruncateSize(kBytesRequiredForMagic, &size);
+ if (CheckForMagicNumbers(content, size,
+ kCRXMagicNumbers, arraysize(kCRXMagicNumbers),
+ NULL, result)) {
+ counter->Add(2);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool ShouldSniffMimeType(const GURL& url, const std::string& mime_type) {
+ static base::HistogramBase* should_sniff_counter(NULL);
+ if (!should_sniff_counter) {
+ should_sniff_counter =
+ UMASnifferHistogramGet("mime_sniffer.ShouldSniffMimeType2", 3);
+ }
+ bool sniffable_scheme = url.is_empty() ||
+ url.SchemeIs("http") ||
+ url.SchemeIs("https") ||
+ url.SchemeIs("ftp") ||
+#if defined(OS_ANDROID)
+ url.SchemeIs("content") ||
+#endif
+ url.SchemeIsFile() ||
+ url.SchemeIsFileSystem();
+ if (!sniffable_scheme) {
+ should_sniff_counter->Add(1);
+ return false;
+ }
+
+ static const char* kSniffableTypes[] = {
+ // Many web servers are misconfigured to send text/plain for many
+ // different types of content.
+ "text/plain",
+ // We want to sniff application/octet-stream for
+ // application/x-chrome-extension, but nothing else.
+ "application/octet-stream",
+ // XHTML and Atom/RSS feeds are often served as plain xml instead of
+ // their more specific mime types.
+ "text/xml",
+ "application/xml",
+ // Check for false Microsoft Office MIME types.
+ "application/msword",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-powerpoint",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
+ "application/vnd.ms-excel.sheet.macroenabled.12",
+ "application/vnd.ms-word.document.macroenabled.12",
+ "application/vnd.ms-powerpoint.presentation.macroenabled.12",
+ "application/mspowerpoint",
+ "application/msexcel",
+ "application/vnd.ms-word",
+ "application/vnd.ms-word.document.12",
+ "application/vnd.msword",
+ };
+ static base::HistogramBase* counter(NULL);
+ if (!counter) {
+ counter = UMASnifferHistogramGet("mime_sniffer.kSniffableTypes2",
+ arraysize(kSniffableTypes) + 1);
+ }
+ for (size_t i = 0; i < arraysize(kSniffableTypes); ++i) {
+ if (mime_type == kSniffableTypes[i]) {
+ counter->Add(i);
+ should_sniff_counter->Add(2);
+ return true;
+ }
+ }
+ if (IsUnknownMimeType(mime_type)) {
+ // The web server didn't specify a content type or specified a mime
+ // type that we ignore.
+ counter->Add(arraysize(kSniffableTypes));
+ should_sniff_counter->Add(2);
+ return true;
+ }
+ should_sniff_counter->Add(1);
+ return false;
+}
+
+bool SniffMimeType(const char* content,
+ size_t content_size,
+ const GURL& url,
+ const std::string& type_hint,
+ std::string* result) {
+ DCHECK_LT(content_size, 1000000U); // sanity check
+ DCHECK(content);
+ DCHECK(result);
+
+ // By default, we assume we have enough content.
+ // Each sniff routine may unset this if it wasn't provided enough content.
+ bool have_enough_content = true;
+
+ // By default, we'll return the type hint.
+ // Each sniff routine may modify this if it has a better guess..
+ result->assign(type_hint);
+
+ // If the file has a Microsoft Office MIME type, we should only check that it
+ // is a valid Office file. Because this is the only reason we sniff files
+ // with a Microsoft Office MIME type, we can return early.
+ if (IsOfficeType(type_hint))
+ return SniffForInvalidOfficeDocs(content, content_size, url, result);
+
+ // Cache information about the type_hint
+ const bool hint_is_unknown_mime_type = IsUnknownMimeType(type_hint);
+
+ // First check for HTML
+ if (hint_is_unknown_mime_type) {
+ // We're only willing to sniff HTML if the server has not supplied a mime
+ // type, or if the type it did supply indicates that it doesn't know what
+ // the type should be.
+ if (SniffForHTML(content, content_size, &have_enough_content, result))
+ return true; // We succeeded in sniffing HTML. No more content needed.
+ }
+
+ // We're only willing to sniff for binary in 3 cases:
+ // 1. The server has not supplied a mime type.
+ // 2. The type it did supply indicates that it doesn't know what the type
+ // should be.
+ // 3. The type is "text/plain" which is the default on some web servers and
+ // could be indicative of a mis-configuration that we shield the user from.
+ const bool hint_is_text_plain = (type_hint == "text/plain");
+ if (hint_is_unknown_mime_type || hint_is_text_plain) {
+ if (!SniffBinary(content, content_size, &have_enough_content, result)) {
+ // If the server said the content was text/plain and it doesn't appear
+ // to be binary, then we trust it.
+ if (hint_is_text_plain) {
+ return have_enough_content;
+ }
+ }
+ }
+
+ // If we have plain XML, sniff XML subtypes.
+ if (type_hint == "text/xml" || type_hint == "application/xml") {
+ // We're not interested in sniffing these types for images and the like.
+ // Instead, we're looking explicitly for a feed. If we don't find one
+ // we're done and return early.
+ if (SniffXML(content, content_size, &have_enough_content, result))
+ return true;
+ return have_enough_content;
+ }
+
+ // CRX files (Chrome extensions) have a special sniffing algorithm. It is
+ // tighter than the others because we don't have to match legacy behavior.
+ if (SniffCRX(content, content_size, url, type_hint,
+ &have_enough_content, result))
+ return true;
+
+ // Check the file extension and magic numbers to see if this is an Office
+ // document. This needs to be checked before the general magic numbers
+ // because zip files and Office documents (OOXML) have the same magic number.
+ if (SniffForOfficeDocs(content, content_size, url,
+ &have_enough_content, result))
+ return true; // We've matched a magic number. No more content needed.
+
+ // We're not interested in sniffing for magic numbers when the type_hint
+ // is application/octet-stream. Time to bail out.
+ if (type_hint == "application/octet-stream")
+ return have_enough_content;
+
+ // Now we look in our large table of magic numbers to see if we can find
+ // anything that matches the content.
+ if (SniffForMagicNumbers(content, content_size,
+ &have_enough_content, result))
+ return true; // We've matched a magic number. No more content needed.
+
+ return have_enough_content;
+}
+
+bool SniffMimeTypeFromLocalData(const char* content,
+ size_t size,
+ std::string* result) {
+ // First check the extra table.
+ if (CheckForMagicNumbers(content, size, kExtraMagicNumbers,
+ arraysize(kExtraMagicNumbers), NULL, result))
+ return true;
+ // Finally check the original table.
+ return CheckForMagicNumbers(content, size, kMagicNumbers,
+ arraysize(kMagicNumbers), NULL, result);
+}
+
+} // namespace net
diff --git a/chromium/net/base/mime_sniffer.h b/chromium/net/base/mime_sniffer.h
new file mode 100644
index 00000000000..77f3833fe85
--- /dev/null
+++ b/chromium/net/base/mime_sniffer.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_BASE_MIME_SNIFFER_H__
+#define NET_BASE_MIME_SNIFFER_H__
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// The maximum number of bytes used by any internal mime sniffing routine. May
+// be useful for callers to determine an efficient buffer size to pass to
+// |SniffMimeType|.
+// This must be updated if any internal sniffing routine needs more bytes.
+const int kMaxBytesToSniff = 1024;
+
+// Examine the URL and the mime_type and decide whether we should sniff a
+// replacement mime type from the content.
+//
+// @param url The URL from which we obtained the content.
+// @param mime_type The current mime type, e.g. from the Content-Type header.
+// @return Returns true if we should sniff the mime type.
+NET_EXPORT bool ShouldSniffMimeType(const GURL& url,
+ const std::string& mime_type);
+
+// Guess a mime type from the first few bytes of content an its URL. Always
+// assigns |result| with its best guess of a mime type.
+//
+// @param content A buffer containing the bytes to sniff.
+// @param content_size The number of bytes in the |content| buffer.
+// @param url The URL from which we obtained this content.
+// @param type_hint The current mime type, e.g. from the Content-Type header.
+// @param result Address at which to place the sniffed mime type.
+// @return Returns true if we have enough content to guess the mime type.
+NET_EXPORT bool SniffMimeType(const char* content, size_t content_size,
+ const GURL& url, const std::string& type_hint,
+ std::string* result);
+
+// Attempt to identify a MIME type from the first few bytes of content only.
+// Uses a bigger set of media file searches than |SniffMimeType()|.
+// If finds a match, fills in |result| and returns true,
+// otherwise returns false.
+//
+// The caller should understand the security ramifications of trusting
+// uncontrolled data before accepting the results of this function.
+//
+// @param content A buffer containing the bytes to sniff.
+// @param content_size The number of bytes in the |content| buffer.
+// @param result Address at which to place the sniffed mime type.
+// @return Returns true if a MIME type match was found.
+NET_EXPORT bool SniffMimeTypeFromLocalData(const char* content,
+ size_t content_size,
+ std::string* result);
+
+} // namespace net
+
+#endif // NET_BASE_MIME_SNIFFER_H__
diff --git a/chromium/net/base/mime_sniffer_unittest.cc b/chromium/net/base/mime_sniffer_unittest.cc
new file mode 100644
index 00000000000..e4f2d5cf3a0
--- /dev/null
+++ b/chromium/net/base/mime_sniffer_unittest.cc
@@ -0,0 +1,487 @@
+// Copyright (c) 2011 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 "base/basictypes.h"
+#include "net/base/mime_sniffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+struct SnifferTest {
+ const char* content;
+ size_t content_len;
+ std::string url;
+ std::string type_hint;
+ const char* mime_type;
+};
+
+static void TestArray(SnifferTest* tests, size_t count) {
+ std::string mime_type;
+
+ for (size_t i = 0; i < count; ++i) {
+ SniffMimeType(tests[i].content,
+ tests[i].content_len,
+ GURL(tests[i].url),
+ tests[i].type_hint,
+ &mime_type);
+ EXPECT_EQ(tests[i].mime_type, mime_type);
+ }
+}
+
+// TODO(evanm): convert other tests to use SniffMimeType instead of TestArray,
+// so the error messages produced by test failures are more useful.
+static std::string SniffMimeType(const std::string& content,
+ const std::string& url,
+ const std::string& mime_type_hint) {
+ std::string mime_type;
+ SniffMimeType(content.data(), content.size(), GURL(url),
+ mime_type_hint, &mime_type);
+ return mime_type;
+}
+
+TEST(MimeSnifferTest, BoundaryConditionsTest) {
+ std::string mime_type;
+ std::string type_hint;
+
+ char buf[] = {
+ 'd', '\x1f', '\xFF'
+ };
+
+ GURL url;
+
+ SniffMimeType(buf, 0, url, type_hint, &mime_type);
+ EXPECT_EQ("text/plain", mime_type);
+ SniffMimeType(buf, 1, url, type_hint, &mime_type);
+ EXPECT_EQ("text/plain", mime_type);
+ SniffMimeType(buf, 2, url, type_hint, &mime_type);
+ EXPECT_EQ("application/octet-stream", mime_type);
+}
+
+TEST(MimeSnifferTest, BasicSniffingTest) {
+ SnifferTest tests[] = {
+ { "<!DOCTYPE html PUBLIC", sizeof("<!DOCTYPE html PUBLIC")-1,
+ "http://www.example.com/",
+ "", "text/html" },
+ { "<HtMl><Body></body></htMl>", sizeof("<HtMl><Body></body></htMl>")-1,
+ "http://www.example.com/foo.gif",
+ "application/octet-stream", "application/octet-stream" },
+ { "GIF89a\x1F\x83\x94", sizeof("GIF89a\xAF\x83\x94")-1,
+ "http://www.example.com/foo",
+ "text/plain", "image/gif" },
+ { "Gif87a\x1F\x83\x94", sizeof("Gif87a\xAF\x83\x94")-1,
+ "http://www.example.com/foo?param=tt.gif",
+ "", "application/octet-stream" },
+ { "%!PS-Adobe-3.0", sizeof("%!PS-Adobe-3.0")-1,
+ "http://www.example.com/foo",
+ "text/plain", "text/plain" },
+ { "\x89" "PNG\x0D\x0A\x1A\x0A", sizeof("\x89" "PNG\x0D\x0A\x1A\x0A")-1,
+ "http://www.example.com/foo",
+ "application/octet-stream", "application/octet-stream" },
+ { "\xFF\xD8\xFF\x23\x49\xAF", sizeof("\xFF\xD8\xFF\x23\x49\xAF")-1,
+ "http://www.example.com/foo",
+ "", "image/jpeg" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, ChromeExtensionsTest) {
+ SnifferTest tests[] = {
+ // schemes
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx",
+ "", "application/x-chrome-extension" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "https://www.example.com/foo.crx",
+ "", "application/x-chrome-extension" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "ftp://www.example.com/foo.crx",
+ "", "application/x-chrome-extension" },
+
+ // some other mimetypes that should get converted
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx",
+ "text/plain", "application/x-chrome-extension" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx",
+ "application/octet-stream", "application/x-chrome-extension" },
+
+ // success edge cases
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx?query=string",
+ "", "application/x-chrome-extension" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo..crx",
+ "", "application/x-chrome-extension" },
+
+ // wrong file extension
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.bin",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.bin?monkey",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "invalid-url",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foocrx",
+ "", "application/octet-stream" },
+ { "Cr24\x02\x00\x00\x00", sizeof("Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx.blech",
+ "", "application/octet-stream" },
+
+ // wrong magic
+ { "Cr24\x02\x00\x00\x01", sizeof("Cr24\x02\x00\x00\x01")-1,
+ "http://www.example.com/foo.crx?monkey",
+ "", "application/octet-stream" },
+ { "PADDING_Cr24\x02\x00\x00\x00", sizeof("PADDING_Cr24\x02\x00\x00\x00")-1,
+ "http://www.example.com/foo.crx?monkey",
+ "", "application/octet-stream" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, MozillaCompatibleTest) {
+ SnifferTest tests[] = {
+ { " \n <hTmL>\n <hea", sizeof(" \n <hTmL>\n <hea")-1,
+ "http://www.example.com/",
+ "", "text/html" },
+ { " \n <hTmL>\n <hea", sizeof(" \n <hTmL>\n <hea")-1,
+ "http://www.example.com/",
+ "text/plain", "text/plain" },
+ { "BMjlakdsfk", sizeof("BMjlakdsfk")-1,
+ "http://www.example.com/foo",
+ "", "image/bmp" },
+ { "\x00\x00\x30\x00", sizeof("\x00\x00\x30\x00")-1,
+ "http://www.example.com/favicon.ico",
+ "", "application/octet-stream" },
+ { "#!/bin/sh\nls /\n", sizeof("#!/bin/sh\nls /\n")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "From: Fred\nTo: Bob\n\nHi\n.\n",
+ sizeof("From: Fred\nTo: Bob\n\nHi\n.\n")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ sizeof("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")-1,
+ "http://www.example.com/foo",
+ "", "text/xml" },
+ { "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
+ sizeof("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")-1,
+ "http://www.example.com/foo",
+ "application/octet-stream", "application/octet-stream" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, DontAllowPrivilegeEscalationTest) {
+ SnifferTest tests[] = {
+ { "GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo",
+ "", "image/gif" },
+ { "GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo?q=ttt.html",
+ "", "image/gif" },
+ { "GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("GIF87a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo#ttt.html",
+ "", "image/gif" },
+ { "a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo?q=ttt.html",
+ "", "text/plain" },
+ { "a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo#ttt.html",
+ "", "text/plain" },
+ { "a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n",
+ sizeof("a\n<html>\n<body>"
+ "<script>alert('haxorzed');\n</script>"
+ "</body></html>\n")-1,
+ "http://www.example.com/foo.html",
+ "", "text/plain" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, UnicodeTest) {
+ SnifferTest tests[] = {
+ { "\xEF\xBB\xBF" "Hi there", sizeof("\xEF\xBB\xBF" "Hi there")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "\xEF\xBB\xBF\xED\x7A\xAD\x7A\x0D\x79",
+ sizeof("\xEF\xBB\xBF\xED\x7A\xAD\x7A\x0D\x79")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "\xFE\xFF\xD0\xA5\xD0\xBE\xD0\xBB\xD1\x83\xD0\xB9",
+ sizeof("\xFE\xFF\xD0\xA5\xD0\xBE\xD0\xBB\xD1\x83\xD0\xB9")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ { "\xFE\xFF\x00\x41\x00\x20\xD8\x00\xDC\x00\xD8\x00\xDC\x01",
+ sizeof("\xFE\xFF\x00\x41\x00\x20\xD8\x00\xDC\x00\xD8\x00\xDC\x01")-1,
+ "http://www.example.com/foo",
+ "", "text/plain" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, FlashTest) {
+ SnifferTest tests[] = {
+ { "CWSdd\x00\xB3", sizeof("CWSdd\x00\xB3")-1,
+ "http://www.example.com/foo",
+ "", "application/octet-stream" },
+ { "FLVjdkl*(#)0sdj\x00", sizeof("FLVjdkl*(#)0sdj\x00")-1,
+ "http://www.example.com/foo?q=ttt.swf",
+ "", "application/octet-stream" },
+ { "FWS3$9\r\b\x00", sizeof("FWS3$9\r\b\x00")-1,
+ "http://www.example.com/foo#ttt.swf",
+ "", "application/octet-stream" },
+ { "FLVjdkl*(#)0sdj", sizeof("FLVjdkl*(#)0sdj")-1,
+ "http://www.example.com/foo.swf",
+ "", "text/plain" },
+ { "FLVjdkl*(#)0s\x01dj", sizeof("FLVjdkl*(#)0s\x01dj")-1,
+ "http://www.example.com/foo/bar.swf",
+ "", "application/octet-stream" },
+ { "FWS3$9\r\b\x1A", sizeof("FWS3$9\r\b\x1A")-1,
+ "http://www.example.com/foo.swf?clickTAG=http://www.adnetwork.com/bar",
+ "", "application/octet-stream" },
+ { "FWS3$9\r\x1C\b", sizeof("FWS3$9\r\x1C\b")-1,
+ "http://www.example.com/foo.swf?clickTAG=http://www.adnetwork.com/bar",
+ "text/plain", "application/octet-stream" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+TEST(MimeSnifferTest, XMLTest) {
+ // An easy feed to identify.
+ EXPECT_EQ("application/atom+xml",
+ SniffMimeType("<?xml?><feed", std::string(), "text/xml"));
+ // Don't sniff out of plain text.
+ EXPECT_EQ("text/plain",
+ SniffMimeType("<?xml?><feed", std::string(), "text/plain"));
+ // Simple RSS.
+ EXPECT_EQ("application/rss+xml",
+ SniffMimeType(
+ "<?xml version='1.0'?>\r\n<rss", std::string(), "text/xml"));
+
+ // The top of CNN's RSS feed, which we'd like to recognize as RSS.
+ static const char kCNNRSS[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<?xml-stylesheet href=\"http://rss.cnn.com/~d/styles/rss2full.xsl\" "
+ "type=\"text/xsl\" media=\"screen\"?>"
+ "<?xml-stylesheet href=\"http://rss.cnn.com/~d/styles/itemcontent.css\" "
+ "type=\"text/css\" media=\"screen\"?>"
+ "<rss xmlns:feedburner=\"http://rssnamespace.org/feedburner/ext/1.0\" "
+ "version=\"2.0\">";
+ // CNN's RSS
+ EXPECT_EQ("application/rss+xml",
+ SniffMimeType(kCNNRSS, std::string(), "text/xml"));
+ EXPECT_EQ("text/plain", SniffMimeType(kCNNRSS, std::string(), "text/plain"));
+
+ // Don't sniff random XML as something different.
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<?xml?><notafeed", std::string(), "text/xml"));
+ // Don't sniff random plain-text as something different.
+ EXPECT_EQ("text/plain",
+ SniffMimeType("<?xml?><notafeed", std::string(), "text/plain"));
+
+ // Positive test for the two instances we upgrade to XHTML.
+ EXPECT_EQ("application/xhtml+xml",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ std::string(),
+ "text/xml"));
+ EXPECT_EQ("application/xhtml+xml",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ std::string(),
+ "application/xml"));
+
+ // Following our behavior with HTML, don't call other mime types XHTML.
+ EXPECT_EQ("text/plain",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ std::string(),
+ "text/plain"));
+ EXPECT_EQ("application/rss+xml",
+ SniffMimeType("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ std::string(),
+ "application/rss+xml"));
+
+ // Don't sniff other HTML-looking bits as HTML.
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<html><head>", std::string(), "text/xml"));
+ EXPECT_EQ("text/xml",
+ SniffMimeType("<foo><html xmlns=\"http://www.w3.org/1999/xhtml\">",
+ std::string(),
+ "text/xml"));
+}
+
+// Test content which is >= 1024 bytes, and includes no open angle bracket.
+// http://code.google.com/p/chromium/issues/detail?id=3521
+TEST(MimeSnifferTest, XMLTestLargeNoAngledBracket) {
+ // Make a large input, with 1024 bytes of "x".
+ std::string content;
+ content.resize(1024);
+ std::fill(content.begin(), content.end(), 'x');
+
+ // content.size() >= 1024 so the sniff is unambiguous.
+ std::string mime_type;
+ EXPECT_TRUE(SniffMimeType(content.data(), content.size(), GURL(),
+ "text/xml", &mime_type));
+ EXPECT_EQ("text/xml", mime_type);
+}
+
+// Test content which is >= 1024 bytes, and includes a binary looking byte.
+// http://code.google.com/p/chromium/issues/detail?id=15314
+TEST(MimeSnifferTest, LooksBinary) {
+ // Make a large input, with 1024 bytes of "x" and 1 byte of 0x01.
+ std::string content;
+ content.resize(1024);
+ std::fill(content.begin(), content.end(), 'x');
+ content[1000] = 0x01;
+
+ // content.size() >= 1024 so the sniff is unambiguous.
+ std::string mime_type;
+ EXPECT_TRUE(SniffMimeType(content.data(), content.size(), GURL(),
+ "text/plain", &mime_type));
+ EXPECT_EQ("application/octet-stream", mime_type);
+}
+
+TEST(MimeSnifferTest, OfficeTest) {
+ SnifferTest tests[] = {
+ // Check for URLs incorrectly reported as Microsoft Office files.
+ { "Hi there",
+ sizeof("Hi there")-1,
+ "http://www.example.com/foo.doc",
+ "application/msword", "application/octet-stream" },
+ { "Hi there",
+ sizeof("Hi there")-1,
+ "http://www.example.com/foo.xls",
+ "application/vnd.ms-excel", "application/octet-stream" },
+ { "Hi there",
+ sizeof("Hi there")-1,
+ "http://www.example.com/foo.ppt",
+ "application/vnd.ms-powerpoint", "application/octet-stream" },
+ // Check for Microsoft Office files incorrectly reported as text.
+ { "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there",
+ sizeof("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there")-1,
+ "http://www.example.com/foo.doc",
+ "text/plain", "application/msword" },
+ { "PK\x03\x04" "Hi there",
+ sizeof("PK\x03\x04" "Hi there")-1,
+ "http://www.example.com/foo.doc",
+ "text/plain",
+ "application/vnd.openxmlformats-officedocument."
+ "wordprocessingml.document" },
+ { "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there",
+ sizeof("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there")-1,
+ "http://www.example.com/foo.xls",
+ "text/plain", "application/vnd.ms-excel" },
+ { "PK\x03\x04" "Hi there",
+ sizeof("PK\x03\x04" "Hi there")-1,
+ "http://www.example.com/foo.xls",
+ "text/plain",
+ "application/vnd.openxmlformats-officedocument."
+ "spreadsheetml.sheet" },
+ { "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there",
+ sizeof("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" "Hi there")-1,
+ "http://www.example.com/foo.ppt",
+ "text/plain", "application/vnd.ms-powerpoint" },
+ { "PK\x03\x04" "Hi there",
+ sizeof("PK\x03\x04" "Hi there")-1,
+ "http://www.example.com/foo.ppt",
+ "text/plain",
+ "application/vnd.openxmlformats-officedocument."
+ "presentationml.presentation" },
+ };
+
+ TestArray(tests, arraysize(tests));
+}
+
+// TODO(thestig) Add more tests for other AV formats. Add another test case for
+// RAW images.
+TEST(MimeSnifferTest, AudioVideoTest) {
+ std::string mime_type;
+ const char kFlacTestData[] =
+ "fLaC\x00\x00\x00\x22\x12\x00\x12\x00\x00\x00\x00\x00";
+ EXPECT_TRUE(SniffMimeTypeFromLocalData(kFlacTestData,
+ sizeof(kFlacTestData),
+ &mime_type));
+ EXPECT_EQ("audio/x-flac", mime_type);
+ mime_type.clear();
+
+ const char kWMATestData[] =
+ "\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c";
+ EXPECT_TRUE(SniffMimeTypeFromLocalData(kWMATestData,
+ sizeof(kWMATestData),
+ &mime_type));
+ EXPECT_EQ("video/x-ms-asf", mime_type);
+ mime_type.clear();
+
+ // mp4a, m4b, m4p, and alac extension files which share the same container
+ // format.
+ const char kMP4TestData[] =
+ "\x00\x00\x00\x20\x66\x74\x79\x70\x4d\x34\x41\x20\x00\x00\x00\x00";
+ EXPECT_TRUE(SniffMimeTypeFromLocalData(kMP4TestData,
+ sizeof(kMP4TestData),
+ &mime_type));
+ EXPECT_EQ("video/mp4", mime_type);
+ mime_type.clear();
+
+ const char kAACTestData[] =
+ "\xff\xf1\x50\x80\x02\x20\xb0\x23\x0a\x83\x20\x7d\x61\x90\x3e\xb1";
+ EXPECT_TRUE(SniffMimeTypeFromLocalData(kAACTestData,
+ sizeof(kAACTestData),
+ &mime_type));
+ EXPECT_EQ("audio/mpeg", mime_type);
+ mime_type.clear();
+}
+
+} // namespace net
diff --git a/chromium/net/base/mime_util.cc b/chromium/net/base/mime_util.cc
new file mode 100644
index 00000000000..da3a4c9aee8
--- /dev/null
+++ b/chromium/net/base/mime_util.cc
@@ -0,0 +1,1003 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/mime_util.h"
+#include "net/base/platform_mime_util.h"
+
+using std::string;
+
+namespace {
+
+struct MediaType {
+ const char name[12];
+ const char matcher[13];
+};
+
+static const MediaType kIanaMediaTypes[] = {
+ { "application", "application/" },
+ { "audio", "audio/" },
+ { "example", "example/" },
+ { "image", "image/" },
+ { "message", "message/" },
+ { "model", "model/" },
+ { "multipart", "multipart/" },
+ { "text", "text/" },
+ { "video", "video/" },
+};
+
+} // namespace
+
+namespace net {
+
+// Singleton utility class for mime types.
+class MimeUtil : public PlatformMimeUtil {
+ public:
+ bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type) const;
+
+ bool GetMimeTypeFromFile(const base::FilePath& file_path,
+ std::string* mime_type) const;
+
+ bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type) const;
+
+ bool IsSupportedImageMimeType(const std::string& mime_type) const;
+ bool IsSupportedMediaMimeType(const std::string& mime_type) const;
+ bool IsSupportedNonImageMimeType(const std::string& mime_type) const;
+ bool IsUnsupportedTextMimeType(const std::string& mime_type) const;
+ bool IsSupportedJavascriptMimeType(const std::string& mime_type) const;
+
+ bool IsSupportedMimeType(const std::string& mime_type) const;
+
+ bool MatchesMimeType(const std::string &mime_type_pattern,
+ const std::string &mime_type) const;
+
+ bool IsMimeType(const std::string& type_string) const;
+
+ bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs) const;
+
+ void ParseCodecString(const std::string& codecs,
+ std::vector<std::string>* codecs_out,
+ bool strip);
+
+ bool IsStrictMediaMimeType(const std::string& mime_type) const;
+ bool IsSupportedStrictMediaMimeType(
+ const std::string& mime_type,
+ const std::vector<std::string>& codecs) const;
+
+ void RemoveProprietaryMediaTypesAndCodecsForTests();
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MimeUtil>;
+
+ typedef base::hash_set<std::string> MimeMappings;
+ typedef std::map<std::string, MimeMappings> StrictMappings;
+
+ MimeUtil();
+
+ // Returns true if |codecs| is nonempty and all the items in it are present in
+ // |supported_codecs|.
+ static bool AreSupportedCodecs(const MimeMappings& supported_codecs,
+ const std::vector<std::string>& codecs);
+
+ // For faster lookup, keep hash sets.
+ void InitializeMimeTypeMaps();
+
+ bool GetMimeTypeFromExtensionHelper(const base::FilePath::StringType& ext,
+ bool include_platform_types,
+ std::string* mime_type) const;
+
+ MimeMappings image_map_;
+ MimeMappings media_map_;
+ MimeMappings non_image_map_;
+ MimeMappings unsupported_text_map_;
+ MimeMappings javascript_map_;
+ MimeMappings codecs_map_;
+
+ StrictMappings strict_format_map_;
+}; // class MimeUtil
+
+// This variable is Leaky because we need to access it from WorkerPool threads.
+static base::LazyInstance<MimeUtil>::Leaky g_mime_util =
+ LAZY_INSTANCE_INITIALIZER;
+
+struct MimeInfo {
+ const char* mime_type;
+ const char* extensions; // comma separated list
+};
+
+static const MimeInfo primary_mappings[] = {
+ { "text/html", "html,htm" },
+ { "text/css", "css" },
+ { "text/xml", "xml" },
+ { "image/gif", "gif" },
+ { "image/jpeg", "jpeg,jpg" },
+ { "image/webp", "webp" },
+ { "image/png", "png" },
+ { "video/mp4", "mp4,m4v" },
+ { "audio/x-m4a", "m4a" },
+ { "audio/mp3", "mp3" },
+ { "video/ogg", "ogv,ogm" },
+ { "audio/ogg", "ogg,oga,opus" },
+ { "video/webm", "webm" },
+ { "audio/webm", "webm" },
+ { "audio/wav", "wav" },
+ { "application/xhtml+xml", "xhtml,xht" },
+ { "application/x-chrome-extension", "crx" },
+ { "multipart/related", "mhtml,mht" }
+};
+
+static const MimeInfo secondary_mappings[] = {
+ { "application/octet-stream", "exe,com,bin" },
+ { "application/gzip", "gz" },
+ { "application/pdf", "pdf" },
+ { "application/postscript", "ps,eps,ai" },
+ { "application/javascript", "js" },
+ { "application/font-woff", "woff" },
+ { "image/bmp", "bmp" },
+ { "image/x-icon", "ico" },
+ { "image/vnd.microsoft.icon", "ico" },
+ { "image/jpeg", "jfif,pjpeg,pjp" },
+ { "image/tiff", "tiff,tif" },
+ { "image/x-xbitmap", "xbm" },
+ { "image/svg+xml", "svg,svgz" },
+ { "message/rfc822", "eml" },
+ { "text/plain", "txt,text" },
+ { "text/html", "shtml,ehtml" },
+ { "application/rss+xml", "rss" },
+ { "application/rdf+xml", "rdf" },
+ { "text/xml", "xsl,xbl" },
+ { "application/vnd.mozilla.xul+xml", "xul" },
+ { "application/x-shockwave-flash", "swf,swl" },
+ { "application/pkcs7-mime", "p7m,p7c,p7z" },
+ { "application/pkcs7-signature", "p7s" }
+};
+
+static const char* FindMimeType(const MimeInfo* mappings,
+ size_t mappings_len,
+ const char* ext) {
+ size_t ext_len = strlen(ext);
+
+ for (size_t i = 0; i < mappings_len; ++i) {
+ const char* extensions = mappings[i].extensions;
+ for (;;) {
+ size_t end_pos = strcspn(extensions, ",");
+ if (end_pos == ext_len &&
+ base::strncasecmp(extensions, ext, ext_len) == 0)
+ return mappings[i].mime_type;
+ extensions += end_pos;
+ if (!*extensions)
+ break;
+ extensions += 1; // skip over comma
+ }
+ }
+ return NULL;
+}
+
+bool MimeUtil::GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ string* result) const {
+ return GetMimeTypeFromExtensionHelper(ext, true, result);
+}
+
+bool MimeUtil::GetWellKnownMimeTypeFromExtension(
+ const base::FilePath::StringType& ext,
+ string* result) const {
+ return GetMimeTypeFromExtensionHelper(ext, false, result);
+}
+
+bool MimeUtil::GetMimeTypeFromFile(const base::FilePath& file_path,
+ string* result) const {
+ base::FilePath::StringType file_name_str = file_path.Extension();
+ if (file_name_str.empty())
+ return false;
+ return GetMimeTypeFromExtension(file_name_str.substr(1), result);
+}
+
+bool MimeUtil::GetMimeTypeFromExtensionHelper(
+ const base::FilePath::StringType& ext,
+ bool include_platform_types,
+ string* result) const {
+ // Avoids crash when unable to handle a long file path. See crbug.com/48733.
+ const unsigned kMaxFilePathSize = 65536;
+ if (ext.length() > kMaxFilePathSize)
+ return false;
+
+ // We implement the same algorithm as Mozilla for mapping a file extension to
+ // a mime type. That is, we first check a hard-coded list (that cannot be
+ // overridden), and then if not found there, we defer to the system registry.
+ // Finally, we scan a secondary hard-coded list to catch types that we can
+ // deduce but that we also want to allow the OS to override.
+
+ base::FilePath path_ext(ext);
+ const string ext_narrow_str = path_ext.AsUTF8Unsafe();
+ const char* mime_type;
+
+ mime_type = FindMimeType(primary_mappings, arraysize(primary_mappings),
+ ext_narrow_str.c_str());
+ if (mime_type) {
+ *result = mime_type;
+ return true;
+ }
+
+ if (include_platform_types && GetPlatformMimeTypeFromExtension(ext, result))
+ return true;
+
+ mime_type = FindMimeType(secondary_mappings, arraysize(secondary_mappings),
+ ext_narrow_str.c_str());
+ if (mime_type) {
+ *result = mime_type;
+ return true;
+ }
+
+ return false;
+}
+
+// From WebKit's WebCore/platform/MIMETypeRegistry.cpp:
+
+static const char* const supported_image_types[] = {
+ "image/jpeg",
+ "image/pjpeg",
+ "image/jpg",
+ "image/webp",
+ "image/png",
+ "image/gif",
+ "image/bmp",
+ "image/vnd.microsoft.icon", // ico
+ "image/x-icon", // ico
+ "image/x-xbitmap" // xbm
+};
+
+// A list of media types: http://en.wikipedia.org/wiki/Internet_media_type
+// A comprehensive mime type list: http://plugindoc.mozdev.org/winmime.php
+// This set of codecs is supported by all variations of Chromium.
+static const char* const common_media_types[] = {
+ // Ogg.
+ "audio/ogg",
+ "application/ogg",
+#if !defined(OS_ANDROID) // Android doesn't support Ogg Theora.
+ "video/ogg",
+#endif
+
+ // WebM.
+ "video/webm",
+ "audio/webm",
+
+ // Wav.
+ "audio/wav",
+ "audio/x-wav",
+};
+
+// List of proprietary types only supported by Google Chrome.
+static const char* const proprietary_media_types[] = {
+ // MPEG-4.
+ "video/mp4",
+ "video/x-m4v",
+ "audio/mp4",
+ "audio/x-m4a",
+
+ // MP3.
+ "audio/mp3",
+ "audio/x-mp3",
+ "audio/mpeg",
+};
+
+// List of supported codecs when passed in with <source type="...">.
+// This set of codecs is supported by all variations of Chromium.
+//
+// Refer to http://wiki.whatwg.org/wiki/Video_type_parameters#Browser_Support
+// for more information.
+//
+// The codecs for WAV are integers as defined in Appendix A of RFC2361:
+// http://tools.ietf.org/html/rfc2361
+static const char* const common_media_codecs[] = {
+#if !defined(OS_ANDROID) // Android doesn't support Ogg Theora.
+ "theora",
+#endif
+ "vorbis",
+ "vp8",
+ "vp9",
+ "1" // WAVE_FORMAT_PCM.
+};
+
+// List of proprietary codecs only supported by Google Chrome.
+static const char* const proprietary_media_codecs[] = {
+ "avc1",
+ "mp4a"
+};
+
+// Note: does not include javascript types list (see supported_javascript_types)
+static const char* const supported_non_image_types[] = {
+ "text/cache-manifest",
+ "text/html",
+ "text/xml",
+ "text/xsl",
+ "text/plain",
+ // Many users complained about css files served for
+ // download instead of displaying in the browser:
+ // http://code.google.com/p/chromium/issues/detail?id=7192
+ // So, by including "text/css" into this list we choose Firefox
+ // behavior - css files will be displayed:
+ "text/css",
+ "text/vnd.chromium.ftp-dir",
+ "text/",
+ "image/svg+xml", // SVG is text-based XML, even though it has an image/ type
+ "application/xml",
+ "application/atom+xml",
+ "application/rss+xml",
+ "application/xhtml+xml",
+ "application/json",
+ "multipart/related", // For MHTML support.
+ "multipart/x-mixed-replace"
+ // Note: ADDING a new type here will probably render it AS HTML. This can
+ // result in cross site scripting.
+};
+
+// Dictionary of cryptographic file mime types.
+struct CertificateMimeTypeInfo {
+ const char* mime_type;
+ CertificateMimeType cert_type;
+};
+
+static const CertificateMimeTypeInfo supported_certificate_types[] = {
+ { "application/x-x509-user-cert",
+ CERTIFICATE_MIME_TYPE_X509_USER_CERT },
+#if defined(OS_ANDROID)
+ { "application/x-x509-ca-cert", CERTIFICATE_MIME_TYPE_X509_CA_CERT },
+ { "application/x-pkcs12", CERTIFICATE_MIME_TYPE_PKCS12_ARCHIVE },
+#endif
+};
+
+// These types are excluded from the logic that allows all text/ types because
+// while they are technically text, it's very unlikely that a user expects to
+// see them rendered in text form.
+static const char* const unsupported_text_types[] = {
+ "text/calendar",
+ "text/x-calendar",
+ "text/x-vcalendar",
+ "text/vcalendar",
+ "text/vcard",
+ "text/x-vcard",
+ "text/directory",
+ "text/ldif",
+ "text/qif",
+ "text/x-qif",
+ "text/x-csv",
+ "text/x-vcf",
+ "text/rtf",
+ "text/comma-separated-values",
+ "text/csv",
+ "text/tab-separated-values",
+ "text/tsv",
+ "text/ofx", // http://crbug.com/162238
+ "text/vnd.sun.j2me.app-descriptor" // http://crbug.com/176450
+};
+
+// Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript.
+// Mozilla 1.8 accepts application/javascript, application/ecmascript, and
+// application/x-javascript, but WinIE 7 doesn't.
+// WinIE 7 accepts text/javascript1.1 - text/javascript1.3, text/jscript, and
+// text/livescript, but Mozilla 1.8 doesn't.
+// Mozilla 1.8 allows leading and trailing whitespace, but WinIE 7 doesn't.
+// Mozilla 1.8 and WinIE 7 both accept the empty string, but neither accept a
+// whitespace-only string.
+// We want to accept all the values that either of these browsers accept, but
+// not other values.
+static const char* const supported_javascript_types[] = {
+ "text/javascript",
+ "text/ecmascript",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "text/javascript1.1",
+ "text/javascript1.2",
+ "text/javascript1.3",
+ "text/jscript",
+ "text/livescript"
+};
+
+struct MediaFormatStrict {
+ const char* mime_type;
+ const char* codecs_list;
+};
+
+static const MediaFormatStrict format_codec_mappings[] = {
+ { "video/webm", "vorbis,vp8,vp8.0,vp9,vp9.0" },
+ { "audio/webm", "vorbis" },
+ { "audio/wav", "1" }
+};
+
+MimeUtil::MimeUtil() {
+ InitializeMimeTypeMaps();
+}
+
+// static
+bool MimeUtil::AreSupportedCodecs(const MimeMappings& supported_codecs,
+ const std::vector<std::string>& codecs) {
+ for (size_t i = 0; i < codecs.size(); ++i) {
+ if (supported_codecs.find(codecs[i]) == supported_codecs.end())
+ return false;
+ }
+ return !codecs.empty();
+}
+
+void MimeUtil::InitializeMimeTypeMaps() {
+ for (size_t i = 0; i < arraysize(supported_image_types); ++i)
+ image_map_.insert(supported_image_types[i]);
+
+ // Initialize the supported non-image types.
+ for (size_t i = 0; i < arraysize(supported_non_image_types); ++i)
+ non_image_map_.insert(supported_non_image_types[i]);
+ for (size_t i = 0; i < arraysize(supported_certificate_types); ++i)
+ non_image_map_.insert(supported_certificate_types[i].mime_type);
+ for (size_t i = 0; i < arraysize(unsupported_text_types); ++i)
+ unsupported_text_map_.insert(unsupported_text_types[i]);
+ for (size_t i = 0; i < arraysize(supported_javascript_types); ++i)
+ non_image_map_.insert(supported_javascript_types[i]);
+ for (size_t i = 0; i < arraysize(common_media_types); ++i)
+ non_image_map_.insert(common_media_types[i]);
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+ for (size_t i = 0; i < arraysize(proprietary_media_types); ++i)
+ non_image_map_.insert(proprietary_media_types[i]);
+#endif
+
+ // Initialize the supported media types.
+ for (size_t i = 0; i < arraysize(common_media_types); ++i)
+ media_map_.insert(common_media_types[i]);
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+ for (size_t i = 0; i < arraysize(proprietary_media_types); ++i)
+ media_map_.insert(proprietary_media_types[i]);
+#endif
+
+ for (size_t i = 0; i < arraysize(supported_javascript_types); ++i)
+ javascript_map_.insert(supported_javascript_types[i]);
+
+ for (size_t i = 0; i < arraysize(common_media_codecs); ++i)
+ codecs_map_.insert(common_media_codecs[i]);
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+ for (size_t i = 0; i < arraysize(proprietary_media_codecs); ++i)
+ codecs_map_.insert(proprietary_media_codecs[i]);
+#endif
+
+ // Initialize the strict supported media types.
+ for (size_t i = 0; i < arraysize(format_codec_mappings); ++i) {
+ std::vector<std::string> mime_type_codecs;
+ ParseCodecString(format_codec_mappings[i].codecs_list,
+ &mime_type_codecs,
+ false);
+
+ MimeMappings codecs;
+ for (size_t j = 0; j < mime_type_codecs.size(); ++j)
+ codecs.insert(mime_type_codecs[j]);
+ strict_format_map_[format_codec_mappings[i].mime_type] = codecs;
+ }
+}
+
+bool MimeUtil::IsSupportedImageMimeType(const std::string& mime_type) const {
+ return image_map_.find(mime_type) != image_map_.end();
+}
+
+bool MimeUtil::IsSupportedMediaMimeType(const std::string& mime_type) const {
+ return media_map_.find(mime_type) != media_map_.end();
+}
+
+bool MimeUtil::IsSupportedNonImageMimeType(const std::string& mime_type) const {
+ return non_image_map_.find(mime_type) != non_image_map_.end() ||
+ (mime_type.compare(0, 5, "text/") == 0 &&
+ !IsUnsupportedTextMimeType(mime_type));
+}
+
+bool MimeUtil::IsUnsupportedTextMimeType(const std::string& mime_type) const {
+ return unsupported_text_map_.find(mime_type) != unsupported_text_map_.end();
+}
+
+bool MimeUtil::IsSupportedJavascriptMimeType(
+ const std::string& mime_type) const {
+ return javascript_map_.find(mime_type) != javascript_map_.end();
+}
+
+// Mirrors WebViewImpl::CanShowMIMEType()
+bool MimeUtil::IsSupportedMimeType(const std::string& mime_type) const {
+ return (mime_type.compare(0, 6, "image/") == 0 &&
+ IsSupportedImageMimeType(mime_type)) ||
+ IsSupportedNonImageMimeType(mime_type);
+}
+
+// Tests for MIME parameter equality. Each parameter in the |mime_type_pattern|
+// must be matched by a parameter in the |mime_type|. If there are no
+// parameters in the pattern, the match is a success.
+bool MatchesMimeTypeParameters(const std::string& mime_type_pattern,
+ const std::string& mime_type) {
+ const std::string::size_type semicolon = mime_type_pattern.find(';');
+ const std::string::size_type test_semicolon = mime_type.find(';');
+ if (semicolon != std::string::npos) {
+ if (test_semicolon == std::string::npos)
+ return false;
+
+ std::vector<std::string> pattern_parameters;
+ base::SplitString(mime_type_pattern.substr(semicolon + 1),
+ ';', &pattern_parameters);
+
+ std::vector<std::string> test_parameters;
+ base::SplitString(mime_type.substr(test_semicolon + 1),
+ ';', &test_parameters);
+
+ sort(pattern_parameters.begin(), pattern_parameters.end());
+ sort(test_parameters.begin(), test_parameters.end());
+ std::vector<std::string> difference;
+ std::set_difference(pattern_parameters.begin(), pattern_parameters.end(),
+ test_parameters.begin(), test_parameters.end(),
+ std::inserter(difference, difference.begin()));
+
+ return difference.size() == 0;
+ }
+ return true;
+}
+
+// This comparison handles absolute maching and also basic
+// wildcards. The plugin mime types could be:
+// application/x-foo
+// application/*
+// application/*+xml
+// *
+// Also tests mime parameters -- all parameters in the pattern must be present
+// in the tested type for a match to succeed.
+bool MimeUtil::MatchesMimeType(const std::string& mime_type_pattern,
+ const std::string& mime_type) const {
+ // Verify caller is passing lowercase strings.
+ DCHECK_EQ(StringToLowerASCII(mime_type_pattern), mime_type_pattern);
+ DCHECK_EQ(StringToLowerASCII(mime_type), mime_type);
+
+ if (mime_type_pattern.empty())
+ return false;
+
+ std::string::size_type semicolon = mime_type_pattern.find(';');
+ const std::string base_pattern(mime_type_pattern.substr(0, semicolon));
+ semicolon = mime_type.find(';');
+ const std::string base_type(mime_type.substr(0, semicolon));
+
+ if (base_pattern == "*" || base_pattern == "*/*")
+ return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
+
+ const std::string::size_type star = base_pattern.find('*');
+ if (star == std::string::npos) {
+ if (base_pattern == base_type)
+ return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
+ else
+ return false;
+ }
+
+ // Test length to prevent overlap between |left| and |right|.
+ if (base_type.length() < base_pattern.length() - 1)
+ return false;
+
+ const std::string left(base_pattern.substr(0, star));
+ const std::string right(base_pattern.substr(star + 1));
+
+ if (base_type.find(left) != 0)
+ return false;
+
+ if (!right.empty() &&
+ base_type.rfind(right) != base_type.length() - right.length())
+ return false;
+
+ return MatchesMimeTypeParameters(mime_type_pattern, mime_type);
+}
+
+// See http://www.iana.org/assignments/media-types/index.html
+static const char* legal_top_level_types[] = {
+ "application/",
+ "audio/",
+ "example/",
+ "image/",
+ "message/",
+ "model/",
+ "multipart/",
+ "text/",
+ "video/",
+};
+
+bool MimeUtil::IsMimeType(const std::string& type_string) const {
+ // MIME types are always ASCII and case-insensitive (at least, the top-level
+ // and secondary types we care about).
+ if (!IsStringASCII(type_string))
+ return false;
+
+ if (type_string == "*/*" || type_string == "*")
+ return true;
+
+ for (size_t i = 0; i < arraysize(legal_top_level_types); ++i) {
+ if (StartsWithASCII(type_string, legal_top_level_types[i], false) &&
+ type_string.length() > strlen(legal_top_level_types[i])) {
+ return true;
+ }
+ }
+
+ // If there's a "/" separator character, and the token before it is
+ // "x-" + (ascii characters), it is also a MIME type.
+ size_t slash = type_string.find('/');
+ if (slash < 3 ||
+ slash == std::string::npos || slash == type_string.length() - 1) {
+ return false;
+ }
+
+ if (StartsWithASCII(type_string, "x-", false))
+ return true;
+
+ return false;
+}
+
+bool MimeUtil::AreSupportedMediaCodecs(
+ const std::vector<std::string>& codecs) const {
+ return AreSupportedCodecs(codecs_map_, codecs);
+}
+
+void MimeUtil::ParseCodecString(const std::string& codecs,
+ std::vector<std::string>* codecs_out,
+ bool strip) {
+ std::string no_quote_codecs;
+ TrimString(codecs, "\"", &no_quote_codecs);
+ base::SplitString(no_quote_codecs, ',', codecs_out);
+
+ if (!strip)
+ return;
+
+ // Strip everything past the first '.'
+ for (std::vector<std::string>::iterator it = codecs_out->begin();
+ it != codecs_out->end();
+ ++it) {
+ size_t found = it->find_first_of('.');
+ if (found != std::string::npos)
+ it->resize(found);
+ }
+}
+
+bool MimeUtil::IsStrictMediaMimeType(const std::string& mime_type) const {
+ if (strict_format_map_.find(mime_type) == strict_format_map_.end())
+ return false;
+ return true;
+}
+
+bool MimeUtil::IsSupportedStrictMediaMimeType(
+ const std::string& mime_type,
+ const std::vector<std::string>& codecs) const {
+ StrictMappings::const_iterator it = strict_format_map_.find(mime_type);
+ return (it != strict_format_map_.end()) &&
+ AreSupportedCodecs(it->second, codecs);
+}
+
+void MimeUtil::RemoveProprietaryMediaTypesAndCodecsForTests() {
+ for (size_t i = 0; i < arraysize(proprietary_media_types); ++i) {
+ non_image_map_.erase(proprietary_media_types[i]);
+ media_map_.erase(proprietary_media_types[i]);
+ }
+ for (size_t i = 0; i < arraysize(proprietary_media_codecs); ++i)
+ codecs_map_.erase(proprietary_media_codecs[i]);
+}
+
+//----------------------------------------------------------------------------
+// Wrappers for the singleton
+//----------------------------------------------------------------------------
+
+bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type) {
+ return g_mime_util.Get().GetMimeTypeFromExtension(ext, mime_type);
+}
+
+bool GetMimeTypeFromFile(const base::FilePath& file_path,
+ std::string* mime_type) {
+ return g_mime_util.Get().GetMimeTypeFromFile(file_path, mime_type);
+}
+
+bool GetWellKnownMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type) {
+ return g_mime_util.Get().GetWellKnownMimeTypeFromExtension(ext, mime_type);
+}
+
+bool GetPreferredExtensionForMimeType(const std::string& mime_type,
+ base::FilePath::StringType* extension) {
+ return g_mime_util.Get().GetPreferredExtensionForMimeType(mime_type,
+ extension);
+}
+
+bool IsSupportedImageMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsSupportedImageMimeType(mime_type);
+}
+
+bool IsSupportedMediaMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsSupportedMediaMimeType(mime_type);
+}
+
+bool IsSupportedNonImageMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsSupportedNonImageMimeType(mime_type);
+}
+
+bool IsUnsupportedTextMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsUnsupportedTextMimeType(mime_type);
+}
+
+bool IsSupportedJavascriptMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsSupportedJavascriptMimeType(mime_type);
+}
+
+bool IsSupportedMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsSupportedMimeType(mime_type);
+}
+
+bool MatchesMimeType(const std::string& mime_type_pattern,
+ const std::string& mime_type) {
+ return g_mime_util.Get().MatchesMimeType(mime_type_pattern, mime_type);
+}
+
+bool IsMimeType(const std::string& type_string) {
+ return g_mime_util.Get().IsMimeType(type_string);
+}
+
+bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs) {
+ return g_mime_util.Get().AreSupportedMediaCodecs(codecs);
+}
+
+bool IsStrictMediaMimeType(const std::string& mime_type) {
+ return g_mime_util.Get().IsStrictMediaMimeType(mime_type);
+}
+
+bool IsSupportedStrictMediaMimeType(const std::string& mime_type,
+ const std::vector<std::string>& codecs) {
+ return g_mime_util.Get().IsSupportedStrictMediaMimeType(mime_type, codecs);
+}
+
+void ParseCodecString(const std::string& codecs,
+ std::vector<std::string>* codecs_out,
+ const bool strip) {
+ g_mime_util.Get().ParseCodecString(codecs, codecs_out, strip);
+}
+
+namespace {
+
+// From http://www.w3schools.com/media/media_mimeref.asp and
+// http://plugindoc.mozdev.org/winmime.php
+static const char* const kStandardImageTypes[] = {
+ "image/bmp",
+ "image/cis-cod",
+ "image/gif",
+ "image/ief",
+ "image/jpeg",
+ "image/webp",
+ "image/pict",
+ "image/pipeg",
+ "image/png",
+ "image/svg+xml",
+ "image/tiff",
+ "image/vnd.microsoft.icon",
+ "image/x-cmu-raster",
+ "image/x-cmx",
+ "image/x-icon",
+ "image/x-portable-anymap",
+ "image/x-portable-bitmap",
+ "image/x-portable-graymap",
+ "image/x-portable-pixmap",
+ "image/x-rgb",
+ "image/x-xbitmap",
+ "image/x-xpixmap",
+ "image/x-xwindowdump"
+};
+static const char* const kStandardAudioTypes[] = {
+ "audio/aac",
+ "audio/aiff",
+ "audio/amr",
+ "audio/basic",
+ "audio/midi",
+ "audio/mp3",
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/mpeg3",
+ "audio/ogg",
+ "audio/vorbis",
+ "audio/wav",
+ "audio/webm",
+ "audio/x-m4a",
+ "audio/x-ms-wma",
+ "audio/vnd.rn-realaudio",
+ "audio/vnd.wave"
+};
+static const char* const kStandardVideoTypes[] = {
+ "video/avi",
+ "video/divx",
+ "video/flc",
+ "video/mp4",
+ "video/mpeg",
+ "video/ogg",
+ "video/quicktime",
+ "video/sd-video",
+ "video/webm",
+ "video/x-dv",
+ "video/x-m4v",
+ "video/x-mpeg",
+ "video/x-ms-asf",
+ "video/x-ms-wmv"
+};
+
+struct StandardType {
+ const char* leading_mime_type;
+ const char* const* standard_types;
+ size_t standard_types_len;
+};
+static const StandardType kStandardTypes[] = {
+ { "image/", kStandardImageTypes, arraysize(kStandardImageTypes) },
+ { "audio/", kStandardAudioTypes, arraysize(kStandardAudioTypes) },
+ { "video/", kStandardVideoTypes, arraysize(kStandardVideoTypes) },
+ { NULL, NULL, 0 }
+};
+
+void GetExtensionsFromHardCodedMappings(
+ const MimeInfo* mappings,
+ size_t mappings_len,
+ const std::string& leading_mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) {
+ base::FilePath::StringType extension;
+ for (size_t i = 0; i < mappings_len; ++i) {
+ if (StartsWithASCII(mappings[i].mime_type, leading_mime_type, false)) {
+ std::vector<string> this_extensions;
+ base::SplitStringUsingSubstr(mappings[i].extensions, ",",
+ &this_extensions);
+ for (size_t j = 0; j < this_extensions.size(); ++j) {
+#if defined(OS_WIN)
+ base::FilePath::StringType extension(UTF8ToWide(this_extensions[j]));
+#else
+ base::FilePath::StringType extension(this_extensions[j]);
+#endif
+ extensions->insert(extension);
+ }
+ }
+ }
+}
+
+void GetExtensionsHelper(
+ const char* const* standard_types,
+ size_t standard_types_len,
+ const std::string& leading_mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) {
+ for (size_t i = 0; i < standard_types_len; ++i) {
+ g_mime_util.Get().GetPlatformExtensionsForMimeType(standard_types[i],
+ extensions);
+ }
+
+ // Also look up the extensions from hard-coded mappings in case that some
+ // supported extensions are not registered in the system registry, like ogg.
+ GetExtensionsFromHardCodedMappings(primary_mappings,
+ arraysize(primary_mappings),
+ leading_mime_type,
+ extensions);
+
+ GetExtensionsFromHardCodedMappings(secondary_mappings,
+ arraysize(secondary_mappings),
+ leading_mime_type,
+ extensions);
+}
+
+// Note that the elements in the source set will be appended to the target
+// vector.
+template<class T>
+void HashSetToVector(base::hash_set<T>* source, std::vector<T>* target) {
+ size_t old_target_size = target->size();
+ target->resize(old_target_size + source->size());
+ size_t i = 0;
+ for (typename base::hash_set<T>::iterator iter = source->begin();
+ iter != source->end(); ++iter, ++i)
+ (*target)[old_target_size + i] = *iter;
+}
+}
+
+void GetExtensionsForMimeType(
+ const std::string& unsafe_mime_type,
+ std::vector<base::FilePath::StringType>* extensions) {
+ if (unsafe_mime_type == "*/*" || unsafe_mime_type == "*")
+ return;
+
+ const std::string mime_type = StringToLowerASCII(unsafe_mime_type);
+ base::hash_set<base::FilePath::StringType> unique_extensions;
+
+ if (EndsWith(mime_type, "/*", true)) {
+ std::string leading_mime_type = mime_type.substr(0, mime_type.length() - 1);
+
+ // Find the matching StandardType from within kStandardTypes, or fall
+ // through to the last (default) StandardType.
+ const StandardType* type = NULL;
+ for (size_t i = 0; i < arraysize(kStandardTypes); ++i) {
+ type = &(kStandardTypes[i]);
+ if (type->leading_mime_type &&
+ leading_mime_type == type->leading_mime_type)
+ break;
+ }
+ DCHECK(type);
+ GetExtensionsHelper(type->standard_types,
+ type->standard_types_len,
+ leading_mime_type,
+ &unique_extensions);
+ } else {
+ g_mime_util.Get().GetPlatformExtensionsForMimeType(mime_type,
+ &unique_extensions);
+
+ // Also look up the extensions from hard-coded mappings in case that some
+ // supported extensions are not registered in the system registry, like ogg.
+ GetExtensionsFromHardCodedMappings(primary_mappings,
+ arraysize(primary_mappings),
+ mime_type,
+ &unique_extensions);
+
+ GetExtensionsFromHardCodedMappings(secondary_mappings,
+ arraysize(secondary_mappings),
+ mime_type,
+ &unique_extensions);
+ }
+
+ HashSetToVector(&unique_extensions, extensions);
+}
+
+void RemoveProprietaryMediaTypesAndCodecsForTests() {
+ g_mime_util.Get().RemoveProprietaryMediaTypesAndCodecsForTests();
+}
+
+const std::string GetIANAMediaType(const std::string& mime_type) {
+ for (size_t i = 0; i < arraysize(kIanaMediaTypes); ++i) {
+ if (StartsWithASCII(mime_type, kIanaMediaTypes[i].matcher, true)) {
+ return kIanaMediaTypes[i].name;
+ }
+ }
+ return std::string();
+}
+
+CertificateMimeType GetCertificateMimeTypeForMimeType(
+ const std::string& mime_type) {
+ // Don't create a map, there is only one entry in the table,
+ // except on Android.
+ for (size_t i = 0; i < arraysize(supported_certificate_types); ++i) {
+ if (mime_type == net::supported_certificate_types[i].mime_type)
+ return net::supported_certificate_types[i].cert_type;
+ }
+ return CERTIFICATE_MIME_TYPE_UNKNOWN;
+}
+
+bool IsSupportedCertificateMimeType(const std::string& mime_type) {
+ CertificateMimeType file_type =
+ GetCertificateMimeTypeForMimeType(mime_type);
+ return file_type != CERTIFICATE_MIME_TYPE_UNKNOWN;
+}
+
+void AddMultipartValueForUpload(const std::string& value_name,
+ const std::string& value,
+ const std::string& mime_boundary,
+ const std::string& content_type,
+ std::string* post_data) {
+ DCHECK(post_data);
+ // First line is the boundary.
+ post_data->append("--" + mime_boundary + "\r\n");
+ // Next line is the Content-disposition.
+ post_data->append("Content-Disposition: form-data; name=\"" +
+ value_name + "\"\r\n");
+ if (!content_type.empty()) {
+ // If Content-type is specified, the next line is that.
+ post_data->append("Content-Type: " + content_type + "\r\n");
+ }
+ // Leave an empty line and append the value.
+ post_data->append("\r\n" + value + "\r\n");
+}
+
+void AddMultipartFinalDelimiterForUpload(const std::string& mime_boundary,
+ std::string* post_data) {
+ DCHECK(post_data);
+ post_data->append("--" + mime_boundary + "--\r\n");
+}
+
+} // namespace net
diff --git a/chromium/net/base/mime_util.h b/chromium/net/base/mime_util.h
new file mode 100644
index 00000000000..9662e9656e3
--- /dev/null
+++ b/chromium/net/base/mime_util.h
@@ -0,0 +1,133 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_MIME_UTIL_H__
+#define NET_BASE_MIME_UTIL_H__
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Get the mime type (if any) that is associated with the given file extension.
+// Returns true if a corresponding mime type exists.
+NET_EXPORT bool GetMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type);
+
+// Get the mime type (if any) that is associated with the given file extension.
+// Returns true if a corresponding mime type exists. In this method,
+// the search for a mime type is constrained to a limited set of
+// types known to the net library, the OS/registry is not consulted.
+NET_EXPORT bool GetWellKnownMimeTypeFromExtension(
+ const base::FilePath::StringType& ext,
+ std::string* mime_type);
+
+// Get the mime type (if any) that is associated with the given file. Returns
+// true if a corresponding mime type exists.
+NET_EXPORT bool GetMimeTypeFromFile(const base::FilePath& file_path,
+ std::string* mime_type);
+
+// Get the preferred extension (if any) associated with the given mime type.
+// Returns true if a corresponding file extension exists. The extension is
+// returned without a prefixed dot, ex "html".
+NET_EXPORT bool GetPreferredExtensionForMimeType(
+ const std::string& mime_type,
+ base::FilePath::StringType* extension);
+
+// Check to see if a particular MIME type is in our list.
+NET_EXPORT bool IsSupportedImageMimeType(const std::string& mime_type);
+NET_EXPORT bool IsSupportedMediaMimeType(const std::string& mime_type);
+NET_EXPORT bool IsSupportedNonImageMimeType(const std::string& mime_type);
+NET_EXPORT bool IsUnsupportedTextMimeType(const std::string& mime_type);
+NET_EXPORT bool IsSupportedJavascriptMimeType(const std::string& mime_type);
+NET_EXPORT bool IsSupportedCertificateMimeType(const std::string& mime_type);
+
+// Convenience function.
+NET_EXPORT bool IsSupportedMimeType(const std::string& mime_type);
+
+// Returns true if this the mime_type_pattern matches a given mime-type.
+// Checks for absolute matching and wildcards. mime-types should be in
+// lower case.
+NET_EXPORT bool MatchesMimeType(const std::string& mime_type_pattern,
+ const std::string& mime_type);
+
+// Returns true if the |type_string| is a correctly-formed mime type specifier.
+// Allows strings of the form x/y[;params], where "x" is a legal mime type name.
+// Also allows wildcard types -- "x/*", "*/*", and "*".
+NET_EXPORT bool IsMimeType(const std::string& type_string);
+
+// Returns true if and only if all codecs are supported, false otherwise.
+NET_EXPORT bool AreSupportedMediaCodecs(const std::vector<std::string>& codecs);
+
+// Parses a codec string, populating |codecs_out| with the prefix of each codec
+// in the string |codecs_in|. For example, passed "aaa.b.c,dd.eee", if
+// |strip| == true |codecs_out| will contain {"aaa", "dd"}, if |strip| == false
+// |codecs_out| will contain {"aaa.b.c", "dd.eee"}.
+// See http://www.ietf.org/rfc/rfc4281.txt.
+NET_EXPORT void ParseCodecString(const std::string& codecs,
+ std::vector<std::string>* codecs_out,
+ bool strip);
+
+// Check to see if a particular MIME type is in our list which only supports a
+// certain subset of codecs.
+NET_EXPORT bool IsStrictMediaMimeType(const std::string& mime_type);
+
+// Check to see if a particular MIME type is in our list which only supports a
+// certain subset of codecs. Returns true if and only if all codecs are
+// supported for that specific MIME type, false otherwise. If this returns
+// false you will still need to check if the media MIME tpyes and codecs are
+// supported.
+NET_EXPORT bool IsSupportedStrictMediaMimeType(
+ const std::string& mime_type,
+ const std::vector<std::string>& codecs);
+
+// Get the extensions associated with the given mime type. This should be passed
+// in lower case. There could be multiple extensions for a given mime type, like
+// "html,htm" for "text/html", or "txt,text,html,..." for "text/*".
+// Note that we do not erase the existing elements in the the provided vector.
+// Instead, we append the result to it.
+NET_EXPORT void GetExtensionsForMimeType(
+ const std::string& mime_type,
+ std::vector<base::FilePath::StringType>* extensions);
+
+// Test only method that removes proprietary media types and codecs from the
+// list of supported MIME types and codecs. These types and codecs must be
+// removed to ensure consistent layout test results across all Chromium
+// variations.
+NET_EXPORT void RemoveProprietaryMediaTypesAndCodecsForTests();
+
+// Returns the IANA media type contained in |mime_type|, or an empty
+// string if |mime_type| does not specifify a known media type.
+// Supported media types are defined at:
+// http://www.iana.org/assignments/media-types/index.html
+NET_EXPORT const std::string GetIANAMediaType(const std::string& mime_type);
+
+// A list of supported certificate-related mime types.
+enum CertificateMimeType {
+#define CERTIFICATE_MIME_TYPE(name, value) CERTIFICATE_MIME_TYPE_ ## name = value,
+#include "net/base/mime_util_certificate_type_list.h"
+#undef CERTIFICATE_MIME_TYPE
+};
+
+NET_EXPORT CertificateMimeType GetCertificateMimeTypeForMimeType(
+ const std::string& mime_type);
+
+// Prepares one value as part of a multi-part upload request.
+NET_EXPORT void AddMultipartValueForUpload(const std::string& value_name,
+ const std::string& value,
+ const std::string& mime_boundary,
+ const std::string& content_type,
+ std::string* post_data);
+
+// Adds the final delimiter to a multi-part upload request.
+NET_EXPORT void AddMultipartFinalDelimiterForUpload(
+ const std::string& mime_boundary,
+ std::string* post_data);
+
+} // namespace net
+
+#endif // NET_BASE_MIME_UTIL_H__
diff --git a/chromium/net/base/mime_util_certificate_type_list.h b/chromium/net/base/mime_util_certificate_type_list.h
new file mode 100644
index 00000000000..b7e1be0096c
--- /dev/null
+++ b/chromium/net/base/mime_util_certificate_type_list.h
@@ -0,0 +1,13 @@
+// Copyright (c) 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.
+
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum values.
+
+// This file contains the list of certificate MIME types.
+
+CERTIFICATE_MIME_TYPE(UNKNOWN, 0)
+CERTIFICATE_MIME_TYPE(X509_USER_CERT, 1)
+CERTIFICATE_MIME_TYPE(X509_CA_CERT, 2)
+CERTIFICATE_MIME_TYPE(PKCS12_ARCHIVE, 3)
diff --git a/chromium/net/base/mime_util_unittest.cc b/chromium/net/base/mime_util_unittest.cc
new file mode 100644
index 00000000000..1a260147bfd
--- /dev/null
+++ b/chromium/net/base/mime_util_unittest.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/mime_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(MimeUtilTest, ExtensionTest) {
+ const struct {
+ const base::FilePath::CharType* extension;
+ const char* mime_type;
+ bool valid;
+ } tests[] = {
+ { FILE_PATH_LITERAL("png"), "image/png", true },
+ { FILE_PATH_LITERAL("css"), "text/css", true },
+ { FILE_PATH_LITERAL("pjp"), "image/jpeg", true },
+ { FILE_PATH_LITERAL("pjpeg"), "image/jpeg", true },
+ { FILE_PATH_LITERAL("not an extension / for sure"), "", false },
+ };
+
+ std::string mime_type;
+ bool rv;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ rv = GetMimeTypeFromExtension(tests[i].extension, &mime_type);
+ EXPECT_EQ(tests[i].valid, rv);
+ if (rv)
+ EXPECT_EQ(tests[i].mime_type, mime_type);
+ }
+}
+
+TEST(MimeUtilTest, FileTest) {
+ const struct {
+ const base::FilePath::CharType* file_path;
+ const char* mime_type;
+ bool valid;
+ } tests[] = {
+ { FILE_PATH_LITERAL("c:\\foo\\bar.css"), "text/css", true },
+ { FILE_PATH_LITERAL("c:\\blah"), "", false },
+ { FILE_PATH_LITERAL("/usr/local/bin/mplayer"), "", false },
+ { FILE_PATH_LITERAL("/home/foo/bar.css"), "text/css", true },
+ { FILE_PATH_LITERAL("/blah."), "", false },
+ { FILE_PATH_LITERAL("c:\\blah."), "", false },
+ };
+
+ std::string mime_type;
+ bool rv;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ rv = GetMimeTypeFromFile(base::FilePath(tests[i].file_path),
+ &mime_type);
+ EXPECT_EQ(tests[i].valid, rv);
+ if (rv)
+ EXPECT_EQ(tests[i].mime_type, mime_type);
+ }
+}
+
+TEST(MimeUtilTest, LookupTypes) {
+ EXPECT_FALSE(IsUnsupportedTextMimeType("text/banana"));
+ EXPECT_TRUE(IsUnsupportedTextMimeType("text/vcard"));
+
+ EXPECT_TRUE(IsSupportedImageMimeType("image/jpeg"));
+ EXPECT_FALSE(IsSupportedImageMimeType("image/lolcat"));
+ EXPECT_TRUE(IsSupportedNonImageMimeType("text/html"));
+ EXPECT_TRUE(IsSupportedNonImageMimeType("text/banana"));
+ EXPECT_FALSE(IsSupportedNonImageMimeType("text/vcard"));
+ EXPECT_FALSE(IsSupportedNonImageMimeType("application/virus"));
+ EXPECT_TRUE(IsSupportedNonImageMimeType("application/x-x509-user-cert"));
+#if defined(OS_ANDROID)
+ EXPECT_TRUE(IsSupportedNonImageMimeType("application/x-x509-ca-cert"));
+ EXPECT_TRUE(IsSupportedNonImageMimeType("application/x-pkcs12"));
+#endif
+
+ EXPECT_TRUE(IsSupportedMimeType("image/jpeg"));
+ EXPECT_FALSE(IsSupportedMimeType("image/lolcat"));
+ EXPECT_TRUE(IsSupportedMimeType("text/html"));
+ EXPECT_TRUE(IsSupportedMimeType("text/banana"));
+ EXPECT_FALSE(IsSupportedMimeType("text/vcard"));
+ EXPECT_FALSE(IsSupportedMimeType("application/virus"));
+}
+
+TEST(MimeUtilTest, MatchesMimeType) {
+ EXPECT_TRUE(MatchesMimeType("*", "video/x-mpeg"));
+ EXPECT_TRUE(MatchesMimeType("video/*", "video/x-mpeg"));
+ EXPECT_TRUE(MatchesMimeType("video/*", "video/*"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg", "video/x-mpeg"));
+ EXPECT_TRUE(MatchesMimeType("application/*+xml",
+ "application/html+xml"));
+ EXPECT_TRUE(MatchesMimeType("application/*+xml", "application/+xml"));
+ EXPECT_TRUE(MatchesMimeType("aaa*aaa", "aaaaaa"));
+ EXPECT_TRUE(MatchesMimeType("*", std::string()));
+ EXPECT_FALSE(MatchesMimeType("video/", "video/x-mpeg"));
+ EXPECT_FALSE(MatchesMimeType(std::string(), "video/x-mpeg"));
+ EXPECT_FALSE(MatchesMimeType(std::string(), std::string()));
+ EXPECT_FALSE(MatchesMimeType("video/x-mpeg", std::string()));
+ EXPECT_FALSE(MatchesMimeType("application/*+xml", "application/xml"));
+ EXPECT_FALSE(MatchesMimeType("application/*+xml",
+ "application/html+xmlz"));
+ EXPECT_FALSE(MatchesMimeType("application/*+xml",
+ "applcation/html+xml"));
+ EXPECT_FALSE(MatchesMimeType("aaa*aaa", "aaaaa"));
+
+ EXPECT_TRUE(MatchesMimeType("*", "video/x-mpeg;param=val"));
+ EXPECT_TRUE(MatchesMimeType("video/*", "video/x-mpeg;param=val"));
+ EXPECT_FALSE(MatchesMimeType("video/*;param=val", "video/mpeg"));
+ EXPECT_FALSE(MatchesMimeType("video/*;param=val", "video/mpeg;param=other"));
+ EXPECT_TRUE(MatchesMimeType("video/*;param=val", "video/mpeg;param=val"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg", "video/x-mpeg;param=val"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg;param=val",
+ "video/x-mpeg;param=val"));
+ EXPECT_FALSE(MatchesMimeType("video/x-mpeg;param2=val2",
+ "video/x-mpeg;param=val"));
+ EXPECT_FALSE(MatchesMimeType("video/x-mpeg;param2=val2",
+ "video/x-mpeg;param2=val"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg;param=val",
+ "video/x-mpeg;param=val;param2=val2"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg;param=val;param2=val2",
+ "video/x-mpeg;param=val;param2=val2"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg;param2=val2;param=val",
+ "video/x-mpeg;param=val;param2=val2"));
+ EXPECT_FALSE(MatchesMimeType("video/x-mpeg;param3=val3;param=val",
+ "video/x-mpeg;param=val;param2=val2"));
+ EXPECT_TRUE(MatchesMimeType("video/x-mpeg;param=val ;param2=val2 ",
+ "video/x-mpeg;param=val;param2=val2"));
+
+ EXPECT_TRUE(MatchesMimeType("*/*;param=val", "video/x-mpeg;param=val"));
+ EXPECT_FALSE(MatchesMimeType("*/*;param=val", "video/x-mpeg;param=val2"));
+
+ EXPECT_TRUE(MatchesMimeType("*", "*"));
+ EXPECT_TRUE(MatchesMimeType("*", "*/*"));
+ EXPECT_TRUE(MatchesMimeType("*/*", "*/*"));
+ EXPECT_TRUE(MatchesMimeType("*/*", "*"));
+ EXPECT_TRUE(MatchesMimeType("video/*", "video/*"));
+ EXPECT_FALSE(MatchesMimeType("video/*", "*/*"));
+ EXPECT_FALSE(MatchesMimeType("video/*;param=val", "video/*"));
+ EXPECT_TRUE(MatchesMimeType("video/*;param=val", "video/*;param=val"));
+ EXPECT_FALSE(MatchesMimeType("video/*;param=val", "video/*;param=val2"));
+
+ EXPECT_TRUE(MatchesMimeType("ab*cd", "abxxxcd"));
+ EXPECT_TRUE(MatchesMimeType("ab*cd", "abx/xcd"));
+ EXPECT_TRUE(MatchesMimeType("ab/*cd", "ab/xxxcd"));
+}
+
+// Note: codecs should only be a list of 2 or fewer; hence the restriction of
+// results' length to 2.
+TEST(MimeUtilTest, ParseCodecString) {
+ const struct {
+ const char* original;
+ size_t expected_size;
+ const char* results[2];
+ } tests[] = {
+ { "\"bogus\"", 1, { "bogus" } },
+ { "0", 1, { "0" } },
+ { "avc1.42E01E, mp4a.40.2", 2, { "avc1", "mp4a" } },
+ { "\"mp4v.20.240, mp4a.40.2\"", 2, { "mp4v", "mp4a" } },
+ { "mp4v.20.8, samr", 2, { "mp4v", "samr" } },
+ { "\"theora, vorbis\"", 2, { "theora", "vorbis" } },
+ { "", 0, { } },
+ { "\"\"", 0, { } },
+ { "\" \"", 0, { } },
+ { ",", 2, { "", "" } },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::vector<std::string> codecs_out;
+ ParseCodecString(tests[i].original, &codecs_out, true);
+ ASSERT_EQ(tests[i].expected_size, codecs_out.size());
+ for (size_t j = 0; j < tests[i].expected_size; ++j)
+ EXPECT_EQ(tests[i].results[j], codecs_out[j]);
+ }
+
+ // Test without stripping the codec type.
+ std::vector<std::string> codecs_out;
+ ParseCodecString("avc1.42E01E, mp4a.40.2", &codecs_out, false);
+ ASSERT_EQ(2u, codecs_out.size());
+ EXPECT_EQ("avc1.42E01E", codecs_out[0]);
+ EXPECT_EQ("mp4a.40.2", codecs_out[1]);
+}
+
+TEST(MimeUtilTest, TestIsMimeType) {
+ std::string nonAscii("application/nonutf8");
+ EXPECT_TRUE(IsMimeType(nonAscii));
+#if defined(OS_WIN)
+ nonAscii.append(WideToUTF8(std::wstring(L"\u2603")));
+#else
+ nonAscii.append("\u2603"); // unicode snowman
+#endif
+ EXPECT_FALSE(IsMimeType(nonAscii));
+
+ EXPECT_TRUE(IsMimeType("application/mime"));
+ EXPECT_TRUE(IsMimeType("audio/mime"));
+ EXPECT_TRUE(IsMimeType("example/mime"));
+ EXPECT_TRUE(IsMimeType("image/mime"));
+ EXPECT_TRUE(IsMimeType("message/mime"));
+ EXPECT_TRUE(IsMimeType("model/mime"));
+ EXPECT_TRUE(IsMimeType("multipart/mime"));
+ EXPECT_TRUE(IsMimeType("text/mime"));
+ EXPECT_TRUE(IsMimeType("TEXT/mime"));
+ EXPECT_TRUE(IsMimeType("Text/mime"));
+ EXPECT_TRUE(IsMimeType("TeXt/mime"));
+ EXPECT_TRUE(IsMimeType("video/mime"));
+ EXPECT_TRUE(IsMimeType("video/mime;parameter"));
+ EXPECT_TRUE(IsMimeType("*/*"));
+ EXPECT_TRUE(IsMimeType("*"));
+
+ EXPECT_TRUE(IsMimeType("x-video/mime"));
+ EXPECT_TRUE(IsMimeType("X-Video/mime"));
+ EXPECT_FALSE(IsMimeType("x-video/"));
+ EXPECT_FALSE(IsMimeType("x-/mime"));
+ EXPECT_FALSE(IsMimeType("mime/looking"));
+ EXPECT_FALSE(IsMimeType("text/"));
+}
+
+TEST(MimeUtilTest, TestToIANAMediaType) {
+ EXPECT_EQ("", GetIANAMediaType("texting/driving"));
+ EXPECT_EQ("", GetIANAMediaType("ham/sandwich"));
+ EXPECT_EQ("", GetIANAMediaType(std::string()));
+ EXPECT_EQ("", GetIANAMediaType("/application/hamsandwich"));
+
+ EXPECT_EQ("application", GetIANAMediaType("application/poodle-wrestler"));
+ EXPECT_EQ("audio", GetIANAMediaType("audio/mpeg"));
+ EXPECT_EQ("example", GetIANAMediaType("example/yomomma"));
+ EXPECT_EQ("image", GetIANAMediaType("image/png"));
+ EXPECT_EQ("message", GetIANAMediaType("message/sipfrag"));
+ EXPECT_EQ("model", GetIANAMediaType("model/vrml"));
+ EXPECT_EQ("multipart", GetIANAMediaType("multipart/mixed"));
+ EXPECT_EQ("text", GetIANAMediaType("text/plain"));
+ EXPECT_EQ("video", GetIANAMediaType("video/H261"));
+}
+
+TEST(MimeUtilTest, TestGetExtensionsForMimeType) {
+ const struct {
+ const char* mime_type;
+ size_t min_expected_size;
+ const char* contained_result;
+ } tests[] = {
+ { "text/plain", 2, "txt" },
+ { "*", 0, NULL },
+ { "message/*", 1, "eml" },
+ { "MeSsAge/*", 1, "eml" },
+ { "image/bmp", 1, "bmp" },
+ { "video/*", 6, "mp4" },
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_IOS)
+ { "video/*", 6, "mpg" },
+#else
+ { "video/*", 6, "mpeg" },
+#endif
+ { "audio/*", 6, "oga" },
+ { "aUDIo/*", 6, "wav" },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::vector<base::FilePath::StringType> extensions;
+ GetExtensionsForMimeType(tests[i].mime_type, &extensions);
+ ASSERT_TRUE(tests[i].min_expected_size <= extensions.size());
+
+ if (!tests[i].contained_result)
+ continue;
+
+ bool found = false;
+ for (size_t j = 0; !found && j < extensions.size(); ++j) {
+#if defined(OS_WIN)
+ if (extensions[j] == UTF8ToWide(tests[i].contained_result))
+ found = true;
+#else
+ if (extensions[j] == tests[i].contained_result)
+ found = true;
+#endif
+ }
+ ASSERT_TRUE(found) << "Must find at least the contained result within "
+ << tests[i].mime_type;
+ }
+}
+
+TEST(MimeUtilTest, TestGetCertificateMimeTypeForMimeType) {
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_X509_USER_CERT,
+ GetCertificateMimeTypeForMimeType("application/x-x509-user-cert"));
+#if defined(OS_ANDROID)
+ // Only Android supports CA Certs and PKCS12 archives.
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_X509_CA_CERT,
+ GetCertificateMimeTypeForMimeType("application/x-x509-ca-cert"));
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_PKCS12_ARCHIVE,
+ GetCertificateMimeTypeForMimeType("application/x-pkcs12"));
+#else
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_UNKNOWN,
+ GetCertificateMimeTypeForMimeType("application/x-x509-ca-cert"));
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_UNKNOWN,
+ GetCertificateMimeTypeForMimeType("application/x-pkcs12"));
+#endif
+ EXPECT_EQ(CERTIFICATE_MIME_TYPE_UNKNOWN,
+ GetCertificateMimeTypeForMimeType("text/plain"));
+}
+
+TEST(MimeUtilTest, TestAddMultipartValueForUpload) {
+ const char* ref_output = "--boundary\r\nContent-Disposition: form-data;"
+ " name=\"value name\"\r\nContent-Type: content type"
+ "\r\n\r\nvalue\r\n"
+ "--boundary\r\nContent-Disposition: form-data;"
+ " name=\"value name\"\r\n\r\nvalue\r\n"
+ "--boundary--\r\n";
+ std::string post_data;
+ AddMultipartValueForUpload("value name", "value", "boundary",
+ "content type", &post_data);
+ AddMultipartValueForUpload("value name", "value", "boundary",
+ "", &post_data);
+ AddMultipartFinalDelimiterForUpload("boundary", &post_data);
+ EXPECT_STREQ(ref_output, post_data.c_str());
+}
+
+} // namespace net
diff --git a/chromium/net/base/mock_file_stream.cc b/chromium/net/base/mock_file_stream.cc
new file mode 100644
index 00000000000..d160f0c9385
--- /dev/null
+++ b/chromium/net/base/mock_file_stream.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 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.
+
+#include "net/base/mock_file_stream.h"
+
+namespace net {
+
+namespace testing {
+
+int MockFileStream::OpenSync(const base::FilePath& path, int open_flags) {
+ path_ = path;
+ return ReturnError(FileStream::OpenSync(path, open_flags));
+}
+
+int MockFileStream::Seek(Whence whence, int64 offset,
+ const Int64CompletionCallback& callback) {
+ return ReturnError(FileStream::Seek(whence, offset, callback));
+}
+
+int64 MockFileStream::SeekSync(Whence whence, int64 offset) {
+ return ReturnError64(FileStream::SeekSync(whence, offset));
+}
+
+int64 MockFileStream::Available() {
+ return ReturnError64(FileStream::Available());
+}
+
+int MockFileStream::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return ReturnError(FileStream::Read(buf, buf_len, callback));
+}
+
+int MockFileStream::ReadSync(char* buf, int buf_len) {
+ return ReturnError(FileStream::ReadSync(buf, buf_len));
+}
+
+int MockFileStream::ReadUntilComplete(char *buf, int buf_len) {
+ return ReturnError(FileStream::ReadUntilComplete(buf, buf_len));
+}
+
+int MockFileStream::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return ReturnError(FileStream::Write(buf, buf_len, callback));
+}
+
+int MockFileStream::WriteSync(const char* buf, int buf_len) {
+ return ReturnError(FileStream::WriteSync(buf, buf_len));
+}
+
+int64 MockFileStream::Truncate(int64 bytes) {
+ return ReturnError64(FileStream::Truncate(bytes));
+}
+
+int MockFileStream::Flush(const CompletionCallback& callback) {
+ return ReturnError(FileStream::Flush(callback));
+}
+
+int MockFileStream::FlushSync() {
+ return ReturnError(FileStream::FlushSync());
+}
+
+} // namespace testing
+
+} // namespace net
diff --git a/chromium/net/base/mock_file_stream.h b/chromium/net/base/mock_file_stream.h
new file mode 100644
index 00000000000..a37b49546d4
--- /dev/null
+++ b/chromium/net/base/mock_file_stream.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+// This file defines MockFileStream, a test class for FileStream.
+
+#ifndef NET_BASE_MOCK_FILE_STREAM_H_
+#define NET_BASE_MOCK_FILE_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+class IOBuffer;
+
+namespace testing {
+
+class MockFileStream : public net::FileStream {
+ public:
+ MockFileStream(net::NetLog* net_log)
+ : net::FileStream(net_log), forced_error_(net::OK) {}
+
+ MockFileStream(base::PlatformFile file, int flags, net::NetLog* net_log)
+ : net::FileStream(file, flags, net_log), forced_error_(net::OK) {}
+
+ // FileStream methods.
+ virtual int OpenSync(const base::FilePath& path, int open_flags) OVERRIDE;
+ virtual int Seek(net::Whence whence, int64 offset,
+ const Int64CompletionCallback& callback) OVERRIDE;
+ virtual int64 SeekSync(net::Whence whence, int64 offset) OVERRIDE;
+ virtual int64 Available() OVERRIDE;
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int ReadSync(char* buf, int buf_len) OVERRIDE;
+ virtual int ReadUntilComplete(char *buf, int buf_len) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSync(const char* buf, int buf_len) OVERRIDE;
+ virtual int64 Truncate(int64 bytes) OVERRIDE;
+ virtual int Flush(const CompletionCallback& callback) OVERRIDE;
+ virtual int FlushSync() OVERRIDE;
+
+ void set_forced_error(int error) { forced_error_ = error; }
+ void clear_forced_error() { forced_error_ = net::OK; }
+ int forced_error() const { return forced_error_; }
+ const base::FilePath& get_path() const { return path_; }
+
+ private:
+ int ReturnError(int function_error) {
+ if (forced_error_ != net::OK) {
+ int ret = forced_error_;
+ clear_forced_error();
+ return ret;
+ }
+
+ return function_error;
+ }
+
+ int64 ReturnError64(int64 function_error) {
+ if (forced_error_ != net::OK) {
+ int64 ret = forced_error_;
+ clear_forced_error();
+ return ret;
+ }
+
+ return function_error;
+ }
+
+ int forced_error_;
+ base::FilePath path_;
+};
+
+} // namespace testing
+
+} // namespace net
+
+#endif // NET_BASE_MOCK_FILE_STREAM_H_
diff --git a/chromium/net/base/mock_filter_context.cc b/chromium/net/base/mock_filter_context.cc
new file mode 100644
index 00000000000..70345892c23
--- /dev/null
+++ b/chromium/net/base/mock_filter_context.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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/base/mock_filter_context.h"
+
+namespace net {
+
+MockFilterContext::MockFilterContext()
+ : is_cached_content_(false),
+ is_download_(false),
+ is_sdch_response_(false),
+ response_code_(-1) {
+}
+
+MockFilterContext::~MockFilterContext() {}
+
+bool MockFilterContext::GetMimeType(std::string* mime_type) const {
+ *mime_type = mime_type_;
+ return true;
+}
+
+// What URL was used to access this data?
+// Return false if gurl is not present.
+bool MockFilterContext::GetURL(GURL* gurl) const {
+ *gurl = gurl_;
+ return true;
+}
+
+// What was this data requested from a server?
+base::Time MockFilterContext::GetRequestTime() const {
+ return request_time_;
+}
+
+bool MockFilterContext::IsCachedContent() const { return is_cached_content_; }
+
+bool MockFilterContext::IsDownload() const { return is_download_; }
+
+bool MockFilterContext::IsSdchResponse() const { return is_sdch_response_; }
+
+int64 MockFilterContext::GetByteReadCount() const { return 0; }
+
+int MockFilterContext::GetResponseCode() const { return response_code_; }
+
+} // namespace net
diff --git a/chromium/net/base/mock_filter_context.h b/chromium/net/base/mock_filter_context.h
new file mode 100644
index 00000000000..592be5505b2
--- /dev/null
+++ b/chromium/net/base/mock_filter_context.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 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_BASE_MOCK_FILTER_CONTEXT_H_
+#define NET_BASE_MOCK_FILTER_CONTEXT_H_
+
+#include <string>
+
+#include "net/base/filter.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class MockFilterContext : public FilterContext {
+ public:
+ MockFilterContext();
+ virtual ~MockFilterContext();
+
+ void SetMimeType(const std::string& mime_type) { mime_type_ = mime_type; }
+ void SetURL(const GURL& gurl) { gurl_ = gurl; }
+ void SetRequestTime(const base::Time time) { request_time_ = time; }
+ void SetCached(bool is_cached) { is_cached_content_ = is_cached; }
+ void SetDownload(bool is_download) { is_download_ = is_download; }
+ void SetResponseCode(int response_code) { response_code_ = response_code; }
+ void SetSdchResponse(bool is_sdch_response) {
+ is_sdch_response_ = is_sdch_response;
+ }
+
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+
+ // What URL was used to access this data?
+ // Return false if gurl is not present.
+ virtual bool GetURL(GURL* gurl) const OVERRIDE;
+
+ // What was this data requested from a server?
+ virtual base::Time GetRequestTime() const OVERRIDE;
+
+ // Is data supplied from cache, or fresh across the net?
+ virtual bool IsCachedContent() const OVERRIDE;
+
+ // Is this a download?
+ virtual bool IsDownload() const OVERRIDE;
+
+ // Was this data flagged as a response to a request with an SDCH dictionary?
+ virtual bool IsSdchResponse() const OVERRIDE;
+
+ // How many bytes were fed to filter(s) so far?
+ virtual int64 GetByteReadCount() const OVERRIDE;
+
+ virtual int GetResponseCode() const OVERRIDE;
+
+ virtual void RecordPacketStats(StatisticSelector statistic) const OVERRIDE {}
+
+ private:
+ int buffer_size_;
+ std::string mime_type_;
+ GURL gurl_;
+ base::Time request_time_;
+ bool is_cached_content_;
+ bool is_download_;
+ bool is_sdch_response_;
+ int response_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFilterContext);
+};
+
+} // namespace net
+
+#endif // NET_BASE_MOCK_FILTER_CONTEXT_H_
diff --git a/chromium/net/base/net_error_list.h b/chromium/net/base/net_error_list.h
new file mode 100644
index 00000000000..5ec421ed7b5
--- /dev/null
+++ b/chromium/net/base/net_error_list.h
@@ -0,0 +1,703 @@
+// Copyright (c) 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.
+
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum values.
+
+// This file contains the list of network errors.
+
+//
+// Ranges:
+// 0- 99 System related errors
+// 100-199 Connection related errors
+// 200-299 Certificate errors
+// 300-399 HTTP errors
+// 400-499 Cache errors
+// 500-599 ?
+// 600-699 FTP errors
+// 700-799 Certificate manager errors
+// 800-899 DNS resolver errors
+
+// An asynchronous IO operation is not yet complete. This usually does not
+// indicate a fatal error. Typically this error will be generated as a
+// notification to wait for some external notification that the IO operation
+// finally completed.
+NET_ERROR(IO_PENDING, -1)
+
+// A generic failure occurred.
+NET_ERROR(FAILED, -2)
+
+// An operation was aborted (due to user action).
+NET_ERROR(ABORTED, -3)
+
+// An argument to the function is incorrect.
+NET_ERROR(INVALID_ARGUMENT, -4)
+
+// The handle or file descriptor is invalid.
+NET_ERROR(INVALID_HANDLE, -5)
+
+// The file or directory cannot be found.
+NET_ERROR(FILE_NOT_FOUND, -6)
+
+// An operation timed out.
+NET_ERROR(TIMED_OUT, -7)
+
+// The file is too large.
+NET_ERROR(FILE_TOO_BIG, -8)
+
+// An unexpected error. This may be caused by a programming mistake or an
+// invalid assumption.
+NET_ERROR(UNEXPECTED, -9)
+
+// Permission to access a resource, other than the network, was denied.
+NET_ERROR(ACCESS_DENIED, -10)
+
+// The operation failed because of unimplemented functionality.
+NET_ERROR(NOT_IMPLEMENTED, -11)
+
+// There were not enough resources to complete the operation.
+NET_ERROR(INSUFFICIENT_RESOURCES, -12)
+
+// Memory allocation failed.
+NET_ERROR(OUT_OF_MEMORY, -13)
+
+// The file upload failed because the file's modification time was different
+// from the expectation.
+NET_ERROR(UPLOAD_FILE_CHANGED, -14)
+
+// The socket is not connected.
+NET_ERROR(SOCKET_NOT_CONNECTED, -15)
+
+// The file already exists.
+NET_ERROR(FILE_EXISTS, -16)
+
+// The path or file name is too long.
+NET_ERROR(FILE_PATH_TOO_LONG, -17)
+
+// Not enough room left on the disk.
+NET_ERROR(FILE_NO_SPACE, -18)
+
+// The file has a virus.
+NET_ERROR(FILE_VIRUS_INFECTED, -19)
+
+// The client chose to block the request.
+NET_ERROR(BLOCKED_BY_CLIENT, -20)
+
+// The network changed.
+NET_ERROR(NETWORK_CHANGED, -21)
+
+// The request was blocked by the URL blacklist configured by the domain
+// administrator.
+NET_ERROR(BLOCKED_BY_ADMINISTRATOR, -22)
+
+// The socket is already connected.
+NET_ERROR(SOCKET_IS_CONNECTED, -23)
+
+// A connection was closed (corresponding to a TCP FIN).
+NET_ERROR(CONNECTION_CLOSED, -100)
+
+// A connection was reset (corresponding to a TCP RST).
+NET_ERROR(CONNECTION_RESET, -101)
+
+// A connection attempt was refused.
+NET_ERROR(CONNECTION_REFUSED, -102)
+
+// A connection timed out as a result of not receiving an ACK for data sent.
+// This can include a FIN packet that did not get ACK'd.
+NET_ERROR(CONNECTION_ABORTED, -103)
+
+// A connection attempt failed.
+NET_ERROR(CONNECTION_FAILED, -104)
+
+// The host name could not be resolved.
+NET_ERROR(NAME_NOT_RESOLVED, -105)
+
+// The Internet connection has been lost.
+NET_ERROR(INTERNET_DISCONNECTED, -106)
+
+// An SSL protocol error occurred.
+NET_ERROR(SSL_PROTOCOL_ERROR, -107)
+
+// The IP address or port number is invalid (e.g., cannot connect to the IP
+// address 0 or the port 0).
+NET_ERROR(ADDRESS_INVALID, -108)
+
+// The IP address is unreachable. This usually means that there is no route to
+// the specified host or network.
+NET_ERROR(ADDRESS_UNREACHABLE, -109)
+
+// The server requested a client certificate for SSL client authentication.
+NET_ERROR(SSL_CLIENT_AUTH_CERT_NEEDED, -110)
+
+// A tunnel connection through the proxy could not be established.
+NET_ERROR(TUNNEL_CONNECTION_FAILED, -111)
+
+// No SSL protocol versions are enabled.
+NET_ERROR(NO_SSL_VERSIONS_ENABLED, -112)
+
+// The client and server don't support a common SSL protocol version or
+// cipher suite.
+NET_ERROR(SSL_VERSION_OR_CIPHER_MISMATCH, -113)
+
+// The server requested a renegotiation (rehandshake).
+NET_ERROR(SSL_RENEGOTIATION_REQUESTED, -114)
+
+// The proxy requested authentication (for tunnel establishment) with an
+// unsupported method.
+NET_ERROR(PROXY_AUTH_UNSUPPORTED, -115)
+
+// During SSL renegotiation (rehandshake), the server sent a certificate with
+// an error.
+//
+// Note: this error is not in the -2xx range so that it won't be handled as a
+// certificate error.
+NET_ERROR(CERT_ERROR_IN_SSL_RENEGOTIATION, -116)
+
+// The SSL handshake failed because of a bad or missing client certificate.
+NET_ERROR(BAD_SSL_CLIENT_AUTH_CERT, -117)
+
+// A connection attempt timed out.
+NET_ERROR(CONNECTION_TIMED_OUT, -118)
+
+// There are too many pending DNS resolves, so a request in the queue was
+// aborted.
+NET_ERROR(HOST_RESOLVER_QUEUE_TOO_LARGE, -119)
+
+// Failed establishing a connection to the SOCKS proxy server for a target host.
+NET_ERROR(SOCKS_CONNECTION_FAILED, -120)
+
+// The SOCKS proxy server failed establishing connection to the target host
+// because that host is unreachable.
+NET_ERROR(SOCKS_CONNECTION_HOST_UNREACHABLE, -121)
+
+// The request to negotiate an alternate protocol failed.
+NET_ERROR(NPN_NEGOTIATION_FAILED, -122)
+
+// The peer sent an SSL no_renegotiation alert message.
+NET_ERROR(SSL_NO_RENEGOTIATION, -123)
+
+// Winsock sometimes reports more data written than passed. This is probably
+// due to a broken LSP.
+NET_ERROR(WINSOCK_UNEXPECTED_WRITTEN_BYTES, -124)
+
+// An SSL peer sent us a fatal decompression_failure alert. This typically
+// occurs when a peer selects DEFLATE compression in the mistaken belief that
+// it supports it.
+NET_ERROR(SSL_DECOMPRESSION_FAILURE_ALERT, -125)
+
+// An SSL peer sent us a fatal bad_record_mac alert. This has been observed
+// from servers with buggy DEFLATE support.
+NET_ERROR(SSL_BAD_RECORD_MAC_ALERT, -126)
+
+// The proxy requested authentication (for tunnel establishment).
+NET_ERROR(PROXY_AUTH_REQUESTED, -127)
+
+// A known TLS strict server didn't offer the renegotiation extension.
+NET_ERROR(SSL_UNSAFE_NEGOTIATION, -128)
+
+// The SSL server attempted to use a weak ephemeral Diffie-Hellman key.
+NET_ERROR(SSL_WEAK_SERVER_EPHEMERAL_DH_KEY, -129)
+
+// Could not create a connection to the proxy server. An error occurred
+// either in resolving its name, or in connecting a socket to it.
+// Note that this does NOT include failures during the actual "CONNECT" method
+// of an HTTP proxy.
+NET_ERROR(PROXY_CONNECTION_FAILED, -130)
+
+// A mandatory proxy configuration could not be used. Currently this means
+// that a mandatory PAC script could not be fetched, parsed or executed.
+NET_ERROR(MANDATORY_PROXY_CONFIGURATION_FAILED, -131)
+
+// -132 was formerly ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION
+
+// We've hit the max socket limit for the socket pool while preconnecting. We
+// don't bother trying to preconnect more sockets.
+NET_ERROR(PRECONNECT_MAX_SOCKET_LIMIT, -133)
+
+// The permission to use the SSL client certificate's private key was denied.
+NET_ERROR(SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED, -134)
+
+// The SSL client certificate has no private key.
+NET_ERROR(SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY, -135)
+
+// The certificate presented by the HTTPS Proxy was invalid.
+NET_ERROR(PROXY_CERTIFICATE_INVALID, -136)
+
+// An error occurred when trying to do a name resolution (DNS).
+NET_ERROR(NAME_RESOLUTION_FAILED, -137)
+
+// Permission to access the network was denied. This is used to distinguish
+// errors that were most likely caused by a firewall from other access denied
+// errors. See also ERR_ACCESS_DENIED.
+NET_ERROR(NETWORK_ACCESS_DENIED, -138)
+
+// The request throttler module cancelled this request to avoid DDOS.
+NET_ERROR(TEMPORARILY_THROTTLED, -139)
+
+// A request to create an SSL tunnel connection through the HTTPS proxy
+// received a non-200 (OK) and non-407 (Proxy Auth) response. The response
+// body might include a description of why the request failed.
+NET_ERROR(HTTPS_PROXY_TUNNEL_RESPONSE, -140)
+
+// We were unable to sign the CertificateVerify data of an SSL client auth
+// handshake with the client certificate's private key.
+//
+// Possible causes for this include the user implicitly or explicitly
+// denying access to the private key, the private key may not be valid for
+// signing, the key may be relying on a cached handle which is no longer
+// valid, or the CSP won't allow arbitrary data to be signed.
+NET_ERROR(SSL_CLIENT_AUTH_SIGNATURE_FAILED, -141)
+
+// The message was too large for the transport. (for example a UDP message
+// which exceeds size threshold).
+NET_ERROR(MSG_TOO_BIG, -142)
+
+// A SPDY session already exists, and should be used instead of this connection.
+NET_ERROR(SPDY_SESSION_ALREADY_EXISTS, -143)
+
+// Error -144 was removed (LIMIT_VIOLATION).
+
+// Websocket protocol error. Indicates that we are terminating the connection
+// due to a malformed frame or other protocol violation.
+NET_ERROR(WS_PROTOCOL_ERROR, -145)
+
+// Connection was aborted for switching to another ptotocol.
+// WebSocket abort SocketStream connection when alternate protocol is found.
+NET_ERROR(PROTOCOL_SWITCHED, -146)
+
+// Returned when attempting to bind an address that is already in use.
+NET_ERROR(ADDRESS_IN_USE, -147)
+
+// An operation failed because the SSL handshake has not completed.
+NET_ERROR(SSL_HANDSHAKE_NOT_COMPLETED, -148)
+
+// SSL peer's public key is invalid.
+NET_ERROR(SSL_BAD_PEER_PUBLIC_KEY, -149)
+
+// The certificate didn't match the built-in public key pins for the host name.
+// The pins are set in net/http/transport_security_state.cc and require that
+// one of a set of public keys exist on the path from the leaf to the root.
+NET_ERROR(SSL_PINNED_KEY_NOT_IN_CERT_CHAIN, -150)
+
+// Server request for client certificate did not contain any types we support.
+NET_ERROR(CLIENT_AUTH_CERT_TYPE_UNSUPPORTED, -151)
+
+// Server requested one type of cert, then requested a different type while the
+// first was still being generated.
+NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_TYPE_MISMATCH, -152)
+
+// An SSL peer sent us a fatal decrypt_error alert. This typically occurs when
+// a peer could not correctly verify a signature (in CertificateVerify or
+// ServerKeyExchange) or validate a Finished message.
+NET_ERROR(SSL_DECRYPT_ERROR_ALERT, -153)
+
+// There are too many pending WebSocketJob instances, so the new job was not
+// pushed to the queue.
+NET_ERROR(WS_THROTTLE_QUEUE_TOO_LARGE, -154)
+
+// There are too many active SocketStream instances, so the new connect request
+// was rejected.
+NET_ERROR(TOO_MANY_SOCKET_STREAMS, -155)
+
+// Certificate error codes
+//
+// The values of certificate error codes must be consecutive.
+
+// The server responded with a certificate whose common name did not match
+// the host name. This could mean:
+//
+// 1. An attacker has redirected our traffic to his server and is
+// presenting a certificate for which he knows the private key.
+//
+// 2. The server is misconfigured and responding with the wrong cert.
+//
+// 3. The user is on a wireless network and is being redirected to the
+// network's login page.
+//
+// 4. The OS has used a DNS search suffix and the server doesn't have
+// a certificate for the abbreviated name in the address bar.
+//
+NET_ERROR(CERT_COMMON_NAME_INVALID, -200)
+
+// The server responded with a certificate that, by our clock, appears to
+// either not yet be valid or to have expired. This could mean:
+//
+// 1. An attacker is presenting an old certificate for which he has
+// managed to obtain the private key.
+//
+// 2. The server is misconfigured and is not presenting a valid cert.
+//
+// 3. Our clock is wrong.
+//
+NET_ERROR(CERT_DATE_INVALID, -201)
+
+// The server responded with a certificate that is signed by an authority
+// we don't trust. The could mean:
+//
+// 1. An attacker has substituted the real certificate for a cert that
+// contains his public key and is signed by his cousin.
+//
+// 2. The server operator has a legitimate certificate from a CA we don't
+// know about, but should trust.
+//
+// 3. The server is presenting a self-signed certificate, providing no
+// defense against active attackers (but foiling passive attackers).
+//
+NET_ERROR(CERT_AUTHORITY_INVALID, -202)
+
+// The server responded with a certificate that contains errors.
+// This error is not recoverable.
+//
+// MSDN describes this error as follows:
+// "The SSL certificate contains errors."
+// NOTE: It's unclear how this differs from ERR_CERT_INVALID. For consistency,
+// use that code instead of this one from now on.
+//
+NET_ERROR(CERT_CONTAINS_ERRORS, -203)
+
+// The certificate has no mechanism for determining if it is revoked. In
+// effect, this certificate cannot be revoked.
+NET_ERROR(CERT_NO_REVOCATION_MECHANISM, -204)
+
+// Revocation information for the security certificate for this site is not
+// available. This could mean:
+//
+// 1. An attacker has compromised the private key in the certificate and is
+// blocking our attempt to find out that the cert was revoked.
+//
+// 2. The certificate is unrevoked, but the revocation server is busy or
+// unavailable.
+//
+NET_ERROR(CERT_UNABLE_TO_CHECK_REVOCATION, -205)
+
+// The server responded with a certificate has been revoked.
+// We have the capability to ignore this error, but it is probably not the
+// thing to do.
+NET_ERROR(CERT_REVOKED, -206)
+
+// The server responded with a certificate that is invalid.
+// This error is not recoverable.
+//
+// MSDN describes this error as follows:
+// "The SSL certificate is invalid."
+//
+NET_ERROR(CERT_INVALID, -207)
+
+// The server responded with a certificate that is signed using a weak
+// signature algorithm.
+NET_ERROR(CERT_WEAK_SIGNATURE_ALGORITHM, -208)
+
+// -209 is availible: was CERT_NOT_IN_DNS.
+
+// The host name specified in the certificate is not unique.
+NET_ERROR(CERT_NON_UNIQUE_NAME, -210)
+
+// The server responded with a certificate that contains a weak key (e.g.
+// a too-small RSA key).
+NET_ERROR(CERT_WEAK_KEY, -211)
+
+// Add new certificate error codes here.
+//
+// Update the value of CERT_END whenever you add a new certificate error
+// code.
+
+// The value immediately past the last certificate error code.
+NET_ERROR(CERT_END, -212)
+
+// The URL is invalid.
+NET_ERROR(INVALID_URL, -300)
+
+// The scheme of the URL is disallowed.
+NET_ERROR(DISALLOWED_URL_SCHEME, -301)
+
+// The scheme of the URL is unknown.
+NET_ERROR(UNKNOWN_URL_SCHEME, -302)
+
+// Attempting to load an URL resulted in too many redirects.
+NET_ERROR(TOO_MANY_REDIRECTS, -310)
+
+// Attempting to load an URL resulted in an unsafe redirect (e.g., a redirect
+// to file:// is considered unsafe).
+NET_ERROR(UNSAFE_REDIRECT, -311)
+
+// Attempting to load an URL with an unsafe port number. These are port
+// numbers that correspond to services, which are not robust to spurious input
+// that may be constructed as a result of an allowed web construct (e.g., HTTP
+// looks a lot like SMTP, so form submission to port 25 is denied).
+NET_ERROR(UNSAFE_PORT, -312)
+
+// The server's response was invalid.
+NET_ERROR(INVALID_RESPONSE, -320)
+
+// Error in chunked transfer encoding.
+NET_ERROR(INVALID_CHUNKED_ENCODING, -321)
+
+// The server did not support the request method.
+NET_ERROR(METHOD_NOT_SUPPORTED, -322)
+
+// The response was 407 (Proxy Authentication Required), yet we did not send
+// the request to a proxy.
+NET_ERROR(UNEXPECTED_PROXY_AUTH, -323)
+
+// The server closed the connection without sending any data.
+NET_ERROR(EMPTY_RESPONSE, -324)
+
+// The headers section of the response is too large.
+NET_ERROR(RESPONSE_HEADERS_TOO_BIG, -325)
+
+// The PAC requested by HTTP did not have a valid status code (non-200).
+NET_ERROR(PAC_STATUS_NOT_OK, -326)
+
+// The evaluation of the PAC script failed.
+NET_ERROR(PAC_SCRIPT_FAILED, -327)
+
+// The response was 416 (Requested range not satisfiable) and the server cannot
+// satisfy the range requested.
+NET_ERROR(REQUEST_RANGE_NOT_SATISFIABLE, -328)
+
+// The identity used for authentication is invalid.
+NET_ERROR(MALFORMED_IDENTITY, -329)
+
+// Content decoding of the response body failed.
+NET_ERROR(CONTENT_DECODING_FAILED, -330)
+
+// An operation could not be completed because all network IO
+// is suspended.
+NET_ERROR(NETWORK_IO_SUSPENDED, -331)
+
+// FLIP data received without receiving a SYN_REPLY on the stream.
+NET_ERROR(SYN_REPLY_NOT_RECEIVED, -332)
+
+// Converting the response to target encoding failed.
+NET_ERROR(ENCODING_CONVERSION_FAILED, -333)
+
+// The server sent an FTP directory listing in a format we do not understand.
+NET_ERROR(UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT, -334)
+
+// Attempted use of an unknown SPDY stream id.
+NET_ERROR(INVALID_SPDY_STREAM, -335)
+
+// There are no supported proxies in the provided list.
+NET_ERROR(NO_SUPPORTED_PROXIES, -336)
+
+// There is a SPDY protocol error.
+NET_ERROR(SPDY_PROTOCOL_ERROR, -337)
+
+// Credentials could not be established during HTTP Authentication.
+NET_ERROR(INVALID_AUTH_CREDENTIALS, -338)
+
+// An HTTP Authentication scheme was tried which is not supported on this
+// machine.
+NET_ERROR(UNSUPPORTED_AUTH_SCHEME, -339)
+
+// Detecting the encoding of the response failed.
+NET_ERROR(ENCODING_DETECTION_FAILED, -340)
+
+// (GSSAPI) No Kerberos credentials were available during HTTP Authentication.
+NET_ERROR(MISSING_AUTH_CREDENTIALS, -341)
+
+// An unexpected, but documented, SSPI or GSSAPI status code was returned.
+NET_ERROR(UNEXPECTED_SECURITY_LIBRARY_STATUS, -342)
+
+// The environment was not set up correctly for authentication (for
+// example, no KDC could be found or the principal is unknown.
+NET_ERROR(MISCONFIGURED_AUTH_ENVIRONMENT, -343)
+
+// An undocumented SSPI or GSSAPI status code was returned.
+NET_ERROR(UNDOCUMENTED_SECURITY_LIBRARY_STATUS, -344)
+
+// The HTTP response was too big to drain.
+NET_ERROR(RESPONSE_BODY_TOO_BIG_TO_DRAIN, -345)
+
+// The HTTP response contained multiple distinct Content-Length headers.
+NET_ERROR(RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, -346)
+
+// SPDY Headers have been received, but not all of them - status or version
+// headers are missing, so we're expecting additional frames to complete them.
+NET_ERROR(INCOMPLETE_SPDY_HEADERS, -347)
+
+// No PAC URL configuration could be retrieved from DHCP. This can indicate
+// either a failure to retrieve the DHCP configuration, or that there was no
+// PAC URL configured in DHCP.
+NET_ERROR(PAC_NOT_IN_DHCP, -348)
+
+// The HTTP response contained multiple Content-Disposition headers.
+NET_ERROR(RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, -349)
+
+// The HTTP response contained multiple Location headers.
+NET_ERROR(RESPONSE_HEADERS_MULTIPLE_LOCATION, -350)
+
+// SPDY server refused the stream. Client should retry. This should never be a
+// user-visible error.
+NET_ERROR(SPDY_SERVER_REFUSED_STREAM, -351)
+
+// SPDY server didn't respond to the PING message.
+NET_ERROR(SPDY_PING_FAILED, -352)
+
+// The request couldn't be completed on an HTTP pipeline. Client should retry.
+NET_ERROR(PIPELINE_EVICTION, -353)
+
+// The HTTP response body transferred fewer bytes than were advertised by the
+// Content-Length header when the connection is closed.
+NET_ERROR(CONTENT_LENGTH_MISMATCH, -354)
+
+// The HTTP response body is transferred with Chunked-Encoding, but the
+// terminating zero-length chunk was never sent when the connection is closed.
+NET_ERROR(INCOMPLETE_CHUNKED_ENCODING, -355)
+
+// There is a QUIC protocol error.
+NET_ERROR(QUIC_PROTOCOL_ERROR, -356)
+
+// The HTTP headers were truncated by an EOF.
+NET_ERROR(RESPONSE_HEADERS_TRUNCATED, -357)
+
+// The cache does not have the requested entry.
+NET_ERROR(CACHE_MISS, -400)
+
+// Unable to read from the disk cache.
+NET_ERROR(CACHE_READ_FAILURE, -401)
+
+// Unable to write to the disk cache.
+NET_ERROR(CACHE_WRITE_FAILURE, -402)
+
+// The operation is not supported for this entry.
+NET_ERROR(CACHE_OPERATION_NOT_SUPPORTED, -403)
+
+// The disk cache is unable to open this entry.
+NET_ERROR(CACHE_OPEN_FAILURE, -404)
+
+// The disk cache is unable to create this entry.
+NET_ERROR(CACHE_CREATE_FAILURE, -405)
+
+// Multiple transactions are racing to create disk cache entries. This is an
+// internal error returned from the HttpCache to the HttpCacheTransaction that
+// tells the transaction to restart the entry-creation logic because the state
+// of the cache has changed.
+NET_ERROR(CACHE_RACE, -406)
+
+// The cache was unable to read a checksum record on an entry. This can be
+// returned from attempts to read from the cache. It is an internal error,
+// returned by the SimpleCache backend, but not by any URLRequest methods
+// or members.
+NET_ERROR(CACHE_CHECKSUM_READ_FAILURE, -407)
+
+// The cache found an entry with an invalid checksum. This can be returned from
+// attempts to read from the cache. It is an internal error, returned by the
+// SimpleCache backend, but not by any URLRequest methods or members.
+NET_ERROR(CACHE_CHECKSUM_MISMATCH, -408)
+
+// The server's response was insecure (e.g. there was a cert error).
+NET_ERROR(INSECURE_RESPONSE, -501)
+
+// The server responded to a <keygen> with a generated client cert that we
+// don't have the matching private key for.
+NET_ERROR(NO_PRIVATE_KEY_FOR_CERT, -502)
+
+// An error adding to the OS certificate database (e.g. OS X Keychain).
+NET_ERROR(ADD_USER_CERT_FAILED, -503)
+
+// *** Code -600 is reserved (was FTP_PASV_COMMAND_FAILED). ***
+
+// A generic error for failed FTP control connection command.
+// If possible, please use or add a more specific error code.
+NET_ERROR(FTP_FAILED, -601)
+
+// The server cannot fulfill the request at this point. This is a temporary
+// error.
+// FTP response code 421.
+NET_ERROR(FTP_SERVICE_UNAVAILABLE, -602)
+
+// The server has aborted the transfer.
+// FTP response code 426.
+NET_ERROR(FTP_TRANSFER_ABORTED, -603)
+
+// The file is busy, or some other temporary error condition on opening
+// the file.
+// FTP response code 450.
+NET_ERROR(FTP_FILE_BUSY, -604)
+
+// Server rejected our command because of syntax errors.
+// FTP response codes 500, 501.
+NET_ERROR(FTP_SYNTAX_ERROR, -605)
+
+// Server does not support the command we issued.
+// FTP response codes 502, 504.
+NET_ERROR(FTP_COMMAND_NOT_SUPPORTED, -606)
+
+// Server rejected our command because we didn't issue the commands in right
+// order.
+// FTP response code 503.
+NET_ERROR(FTP_BAD_COMMAND_SEQUENCE, -607)
+
+// PKCS #12 import failed due to incorrect password.
+NET_ERROR(PKCS12_IMPORT_BAD_PASSWORD, -701)
+
+// PKCS #12 import failed due to other error.
+NET_ERROR(PKCS12_IMPORT_FAILED, -702)
+
+// CA import failed - not a CA cert.
+NET_ERROR(IMPORT_CA_CERT_NOT_CA, -703)
+
+// Import failed - certificate already exists in database.
+// Note it's a little weird this is an error but reimporting a PKCS12 is ok
+// (no-op). That's how Mozilla does it, though.
+NET_ERROR(IMPORT_CERT_ALREADY_EXISTS, -704)
+
+// CA import failed due to some other error.
+NET_ERROR(IMPORT_CA_CERT_FAILED, -705)
+
+// Server certificate import failed due to some internal error.
+NET_ERROR(IMPORT_SERVER_CERT_FAILED, -706)
+
+// PKCS #12 import failed due to invalid MAC.
+NET_ERROR(PKCS12_IMPORT_INVALID_MAC, -707)
+
+// PKCS #12 import failed due to invalid/corrupt file.
+NET_ERROR(PKCS12_IMPORT_INVALID_FILE, -708)
+
+// PKCS #12 import failed due to unsupported features.
+NET_ERROR(PKCS12_IMPORT_UNSUPPORTED, -709)
+
+// Key generation failed.
+NET_ERROR(KEY_GENERATION_FAILED, -710)
+
+// Server-bound certificate generation failed.
+NET_ERROR(ORIGIN_BOUND_CERT_GENERATION_FAILED, -711)
+
+// Failure to export private key.
+NET_ERROR(PRIVATE_KEY_EXPORT_FAILED, -712)
+
+// Self-signed certificate generation failed.
+NET_ERROR(SELF_SIGNED_CERT_GENERATION_FAILED, -713)
+
+// DNS error codes.
+
+// DNS resolver received a malformed response.
+NET_ERROR(DNS_MALFORMED_RESPONSE, -800)
+
+// DNS server requires TCP
+NET_ERROR(DNS_SERVER_REQUIRES_TCP, -801)
+
+// DNS server failed. This error is returned for all of the following
+// error conditions:
+// 1 - Format error - The name server was unable to interpret the query.
+// 2 - Server failure - The name server was unable to process this query
+// due to a problem with the name server.
+// 4 - Not Implemented - The name server does not support the requested
+// kind of query.
+// 5 - Refused - The name server refuses to perform the specified
+// operation for policy reasons.
+NET_ERROR(DNS_SERVER_FAILED, -802)
+
+// DNS transaction timed out.
+NET_ERROR(DNS_TIMED_OUT, -803)
+
+// The entry was not found in cache, for cache-only lookups.
+NET_ERROR(DNS_CACHE_MISS, -804)
+
+// Suffix search list rules prevent resolution of the given host name.
+NET_ERROR(DNS_SEARCH_EMPTY, -805)
+
+// Failed to sort addresses according to RFC3484.
+NET_ERROR(DNS_SORT_ERROR, -806)
diff --git a/chromium/net/base/net_errors.cc b/chromium/net/base/net_errors.cc
new file mode 100644
index 00000000000..01fda3908ca
--- /dev/null
+++ b/chromium/net/base/net_errors.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#include "net/base/net_errors.h"
+
+#include "base/basictypes.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringize_macros.h"
+
+namespace {
+
+// Get all valid error codes into an array as positive numbers, for use in the
+// |GetAllErrorCodesForUma| function below.
+#define NET_ERROR(label, value) -(value),
+const int kAllErrorCodes[] = {
+#include "net/base/net_error_list.h"
+};
+#undef NET_ERROR
+
+} // namespace
+
+namespace net {
+
+const char kErrorDomain[] = "net";
+
+const char* ErrorToString(int error) {
+ if (error == 0)
+ return "net::OK";
+
+ switch (error) {
+#define NET_ERROR(label, value) \
+ case ERR_ ## label: \
+ return "net::" STRINGIZE_NO_EXPANSION(ERR_ ## label);
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+ default:
+ return "net::<unknown>";
+ }
+}
+
+std::vector<int> GetAllErrorCodesForUma() {
+ return base::CustomHistogram::ArrayToCustomRanges(
+ kAllErrorCodes, arraysize(kAllErrorCodes));
+}
+
+Error PlatformFileErrorToNetError(
+ base::PlatformFileError file_error) {
+ switch (file_error) {
+ case base::PLATFORM_FILE_OK:
+ return net::OK;
+ case base::PLATFORM_FILE_ERROR_ACCESS_DENIED:
+ return net::ERR_ACCESS_DENIED;
+ case base::PLATFORM_FILE_ERROR_INVALID_URL:
+ return net::ERR_INVALID_URL;
+ case base::PLATFORM_FILE_ERROR_NOT_FOUND:
+ return net::ERR_FILE_NOT_FOUND;
+ default:
+ return net::ERR_FAILED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_errors.h b/chromium/net/base/net_errors.h
new file mode 100644
index 00000000000..21749d8b76b
--- /dev/null
+++ b/chromium/net/base/net_errors.h
@@ -0,0 +1,60 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NET_ERRORS_H__
+#define NET_BASE_NET_ERRORS_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/platform_file.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Error domain of the net module's error codes.
+NET_EXPORT extern const char kErrorDomain[];
+
+// Error values are negative.
+enum Error {
+ // No error.
+ OK = 0,
+
+#define NET_ERROR(label, value) ERR_ ## label = value,
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+
+ // The value of the first certificate error code.
+ ERR_CERT_BEGIN = ERR_CERT_COMMON_NAME_INVALID,
+};
+
+// Returns a textual representation of the error code for logging purposes.
+NET_EXPORT const char* ErrorToString(int error);
+
+// Returns true if |error| is a certificate error code.
+inline bool IsCertificateError(int error) {
+ // Certificate errors are negative integers from net::ERR_CERT_BEGIN
+ // (inclusive) to net::ERR_CERT_END (exclusive) in *decreasing* order.
+ return error <= ERR_CERT_BEGIN && error > ERR_CERT_END;
+}
+
+// Map system error code to Error.
+NET_EXPORT Error MapSystemError(int os_error);
+
+// Returns a list of all the possible net error codes (not counting OK). This
+// is intended for use with UMA histograms that are reporting the result of
+// an action that is represented as a net error code.
+//
+// Note that the error codes are all positive (since histograms expect positive
+// sample values). Also note that a guard bucket is created after any valid
+// error code that is not followed immediately by a valid error code.
+NET_EXPORT std::vector<int> GetAllErrorCodesForUma();
+
+// A convenient function to translate platform file error to net error code.
+NET_EXPORT Error PlatformFileErrorToNetError(
+ base::PlatformFileError file_error);
+
+} // namespace net
+
+#endif // NET_BASE_NET_ERRORS_H__
diff --git a/chromium/net/base/net_errors_posix.cc b/chromium/net/base/net_errors_posix.cc
new file mode 100644
index 00000000000..751e4486c4a
--- /dev/null
+++ b/chromium/net/base/net_errors_posix.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2011 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/base/net_errors.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace net {
+
+Error MapSystemError(int os_error) {
+ if (os_error != 0)
+ DVLOG(2) << "Error " << os_error;
+
+ // There are numerous posix error codes, but these are the ones we thus far
+ // find interesting.
+ switch (os_error) {
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ return ERR_IO_PENDING;
+ case EACCES:
+ return ERR_ACCESS_DENIED;
+ case ENETDOWN:
+ return ERR_INTERNET_DISCONNECTED;
+ case ETIMEDOUT:
+ return ERR_TIMED_OUT;
+ case ECONNRESET:
+ case ENETRESET: // Related to keep-alive.
+ case EPIPE:
+ return ERR_CONNECTION_RESET;
+ case ECONNABORTED:
+ return ERR_CONNECTION_ABORTED;
+ case ECONNREFUSED:
+ return ERR_CONNECTION_REFUSED;
+ case EHOSTUNREACH:
+ case EHOSTDOWN:
+ case ENETUNREACH:
+ case EAFNOSUPPORT:
+ return ERR_ADDRESS_UNREACHABLE;
+ case EADDRNOTAVAIL:
+ return ERR_ADDRESS_INVALID;
+ case EMSGSIZE:
+ return ERR_MSG_TOO_BIG;
+ case ENOTCONN:
+ return ERR_SOCKET_NOT_CONNECTED;
+ case EISCONN:
+ return ERR_SOCKET_IS_CONNECTED;
+ case EINVAL:
+ return ERR_INVALID_ARGUMENT;
+ case EADDRINUSE:
+ return ERR_ADDRESS_IN_USE;
+ case E2BIG: // Argument list too long.
+ return ERR_INVALID_ARGUMENT;
+ case EBADF: // Bad file descriptor.
+ return ERR_INVALID_HANDLE;
+ case EBUSY: // Device or resource busy.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ECANCELED: // Operation canceled.
+ return ERR_ABORTED;
+ case EDEADLK: // Resource deadlock avoided.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case EDQUOT: // Disk quota exceeded.
+ return ERR_FILE_NO_SPACE;
+ case EEXIST: // File exists.
+ return ERR_FILE_EXISTS;
+ case EFAULT: // Bad address.
+ return ERR_INVALID_ARGUMENT;
+ case EFBIG: // File too large.
+ return ERR_FILE_TOO_BIG;
+ case EISDIR: // Operation not allowed for a directory.
+ return ERR_ACCESS_DENIED;
+ case ENAMETOOLONG: // Filename too long.
+ return ERR_FILE_PATH_TOO_LONG;
+ case ENFILE: // Too many open files in system.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ENOBUFS: // No buffer space available.
+ return ERR_OUT_OF_MEMORY;
+ case ENODEV: // No such device.
+ return ERR_INVALID_ARGUMENT;
+ case ENOENT: // No such file or directory.
+ return ERR_FILE_NOT_FOUND;
+ case ENOLCK: // No locks available.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ENOMEM: // Not enough space.
+ return ERR_OUT_OF_MEMORY;
+ case ENOSPC: // No space left on device.
+ return ERR_FILE_NO_SPACE;
+ case ENOSYS: // Function not implemented.
+ return ERR_NOT_IMPLEMENTED;
+ case ENOTDIR: // Not a directory.
+ return ERR_FILE_NOT_FOUND;
+ case ENOTSUP: // Operation not supported.
+ return ERR_NOT_IMPLEMENTED;
+ case EPERM: // Operation not permitted.
+ return ERR_ACCESS_DENIED;
+ case EROFS: // Read-only file system.
+ return ERR_ACCESS_DENIED;
+ case ETXTBSY: // Text file busy.
+ return ERR_ACCESS_DENIED;
+ case EUSERS: // Too many users.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case EMFILE: // Too many open files.
+ return ERR_INSUFFICIENT_RESOURCES;
+
+ case 0:
+ return OK;
+ default:
+ LOG(WARNING) << "Unknown error " << os_error
+ << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_errors_win.cc b/chromium/net/base/net_errors_win.cc
new file mode 100644
index 00000000000..2cde82f4d25
--- /dev/null
+++ b/chromium/net/base/net_errors_win.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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/base/net_errors.h"
+
+#include <winsock2.h>
+
+#include "base/logging.h"
+
+namespace net {
+
+// Map winsock and system errors to Chromium errors.
+Error MapSystemError(int os_error) {
+ if (os_error != 0)
+ DVLOG(2) << "Error " << os_error;
+
+ // There are numerous Winsock error codes, but these are the ones we thus far
+ // find interesting.
+ switch (os_error) {
+ case WSAEWOULDBLOCK:
+ case WSA_IO_PENDING:
+ return ERR_IO_PENDING;
+ case WSAEACCES:
+ return ERR_ACCESS_DENIED;
+ case WSAENETDOWN:
+ return ERR_INTERNET_DISCONNECTED;
+ case WSAETIMEDOUT:
+ return ERR_TIMED_OUT;
+ case WSAECONNRESET:
+ case WSAENETRESET: // Related to keep-alive
+ return ERR_CONNECTION_RESET;
+ case WSAECONNABORTED:
+ return ERR_CONNECTION_ABORTED;
+ case WSAECONNREFUSED:
+ return ERR_CONNECTION_REFUSED;
+ case WSA_IO_INCOMPLETE:
+ case WSAEDISCON:
+ return ERR_CONNECTION_CLOSED;
+ case WSAEISCONN:
+ return ERR_SOCKET_IS_CONNECTED;
+ case WSAEHOSTUNREACH:
+ case WSAENETUNREACH:
+ return ERR_ADDRESS_UNREACHABLE;
+ case WSAEADDRNOTAVAIL:
+ return ERR_ADDRESS_INVALID;
+ case WSAEMSGSIZE:
+ return ERR_MSG_TOO_BIG;
+ case WSAENOTCONN:
+ return ERR_SOCKET_NOT_CONNECTED;
+ case WSAEAFNOSUPPORT:
+ return ERR_ADDRESS_UNREACHABLE;
+ case WSAEINVAL:
+ return ERR_INVALID_ARGUMENT;
+ case WSAEADDRINUSE:
+ return ERR_ADDRESS_IN_USE;
+
+ // System errors.
+ case ERROR_FILE_NOT_FOUND: // The system cannot find the file specified.
+ return ERR_FILE_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND: // The system cannot find the path specified.
+ return ERR_FILE_NOT_FOUND;
+ case ERROR_TOO_MANY_OPEN_FILES: // The system cannot open the file.
+ return ERR_INSUFFICIENT_RESOURCES;
+ case ERROR_ACCESS_DENIED: // Access is denied.
+ return ERR_ACCESS_DENIED;
+ case ERROR_INVALID_HANDLE: // The handle is invalid.
+ return ERR_INVALID_HANDLE;
+ case ERROR_NOT_ENOUGH_MEMORY: // Not enough storage is available to
+ return ERR_OUT_OF_MEMORY; // process this command.
+ case ERROR_OUTOFMEMORY: // Not enough storage is available to complete
+ return ERR_OUT_OF_MEMORY; // this operation.
+ case ERROR_WRITE_PROTECT: // The media is write protected.
+ return ERR_ACCESS_DENIED;
+ case ERROR_SHARING_VIOLATION: // Cannot access the file because it is
+ return ERR_ACCESS_DENIED; // being used by another process.
+ case ERROR_LOCK_VIOLATION: // The process cannot access the file because
+ return ERR_ACCESS_DENIED; // another process has locked the file.
+ case ERROR_HANDLE_EOF: // Reached the end of the file.
+ return ERR_FAILED;
+ case ERROR_HANDLE_DISK_FULL: // The disk is full.
+ return ERR_FILE_NO_SPACE;
+ case ERROR_FILE_EXISTS: // The file exists.
+ return ERR_FILE_EXISTS;
+ case ERROR_INVALID_PARAMETER: // The parameter is incorrect.
+ return ERR_INVALID_ARGUMENT;
+ case ERROR_BUFFER_OVERFLOW: // The file name is too long.
+ return ERR_FILE_PATH_TOO_LONG;
+ case ERROR_DISK_FULL: // There is not enough space on the disk.
+ return ERR_FILE_NO_SPACE;
+ case ERROR_CALL_NOT_IMPLEMENTED: // This function is not supported on
+ return ERR_NOT_IMPLEMENTED; // this system.
+ case ERROR_INVALID_NAME: // The filename, directory name, or volume
+ return ERR_INVALID_ARGUMENT; // label syntax is incorrect.
+ case ERROR_DIR_NOT_EMPTY: // The directory is not empty.
+ return ERR_FAILED;
+ case ERROR_BUSY: // The requested resource is in use.
+ return ERR_ACCESS_DENIED;
+ case ERROR_ALREADY_EXISTS: // Cannot create a file when that file
+ return ERR_FILE_EXISTS; // already exists.
+ case ERROR_FILENAME_EXCED_RANGE: // The filename or extension is too long.
+ return ERR_FILE_PATH_TOO_LONG;
+ case ERROR_FILE_TOO_LARGE: // The file size exceeds the limit allowed
+ return ERR_FILE_NO_SPACE; // and cannot be saved.
+ case ERROR_VIRUS_INFECTED: // Operation failed because the file
+ return ERR_FILE_VIRUS_INFECTED; // contains a virus.
+ case ERROR_IO_DEVICE: // The request could not be performed
+ return ERR_ACCESS_DENIED; // because of an I/O device error.
+ case ERROR_POSSIBLE_DEADLOCK: // A potential deadlock condition has
+ return ERR_ACCESS_DENIED; // been detected.
+ case ERROR_BAD_DEVICE: // The specified device name is invalid.
+ return ERR_INVALID_ARGUMENT;
+
+ case ERROR_SUCCESS:
+ return OK;
+ default:
+ LOG(WARNING) << "Unknown error " << os_error
+ << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_export.h b/chromium/net/base/net_export.h
new file mode 100644
index 00000000000..55cf9861239
--- /dev/null
+++ b/chromium/net/base/net_export.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011 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_BASE_NET_EXPORT_H_
+#define NET_BASE_NET_EXPORT_H_
+
+// Defines NET_EXPORT so that functionality implemented by the net module can
+// be exported to consumers, and NET_EXPORT_PRIVATE that allows unit tests to
+// access features not intended to be used directly by real consumers.
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(NET_IMPLEMENTATION)
+#define NET_EXPORT __declspec(dllexport)
+#define NET_EXPORT_PRIVATE __declspec(dllexport)
+#else
+#define NET_EXPORT __declspec(dllimport)
+#define NET_EXPORT_PRIVATE __declspec(dllimport)
+#endif // defined(NET_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(NET_IMPLEMENTATION)
+#define NET_EXPORT __attribute__((visibility("default")))
+#define NET_EXPORT_PRIVATE __attribute__((visibility("default")))
+#else
+#define NET_EXPORT
+#define NET_EXPORT_PRIVATE
+#endif
+#endif
+
+#else /// defined(COMPONENT_BUILD)
+#define NET_EXPORT
+#define NET_EXPORT_PRIVATE
+#endif
+
+#endif // NET_BASE_NET_EXPORT_H_
diff --git a/chromium/net/base/net_log.cc b/chromium/net/base/net_log.cc
new file mode 100644
index 00000000000..d1e9c98de31
--- /dev/null
+++ b/chromium/net/base/net_log.cc
@@ -0,0 +1,493 @@
+// Copyright (c) 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.
+
+#include "net/base/net_log.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+// Returns parameters for logging data transferred events. Includes number of
+// bytes transferred and, if the log level indicates bytes should be logged and
+// |byte_count| > 0, the bytes themselves. The bytes are hex-encoded, since
+// base::StringValue only supports UTF-8.
+base::Value* BytesTransferredCallback(int byte_count,
+ const char* bytes,
+ NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("byte_count", byte_count);
+ if (NetLog::IsLoggingBytes(log_level) && byte_count > 0)
+ dict->SetString("hex_encoded_bytes", base::HexEncode(bytes, byte_count));
+ return dict;
+}
+
+base::Value* SourceEventParametersCallback(const NetLog::Source source,
+ NetLog::LogLevel /* log_level */) {
+ if (!source.IsValid())
+ return NULL;
+ base::DictionaryValue* event_params = new base::DictionaryValue();
+ source.AddToEventParameters(event_params);
+ return event_params;
+}
+
+base::Value* NetLogIntegerCallback(const char* name,
+ int value,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* event_params = new base::DictionaryValue();
+ event_params->SetInteger(name, value);
+ return event_params;
+}
+
+base::Value* NetLogInt64Callback(const char* name,
+ int64 value,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* event_params = new base::DictionaryValue();
+ event_params->SetString(name, base::Int64ToString(value));
+ return event_params;
+}
+
+base::Value* NetLogStringCallback(const char* name,
+ const std::string* value,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* event_params = new base::DictionaryValue();
+ event_params->SetString(name, *value);
+ return event_params;
+}
+
+base::Value* NetLogString16Callback(const char* name,
+ const base::string16* value,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* event_params = new base::DictionaryValue();
+ event_params->SetString(name, *value);
+ return event_params;
+}
+
+} // namespace
+
+// LoadTimingInfo requires this be 0.
+const uint32 NetLog::Source::kInvalidId = 0;
+
+NetLog::Source::Source() : type(SOURCE_NONE), id(kInvalidId) {
+}
+
+NetLog::Source::Source(SourceType type, uint32 id) : type(type), id(id) {
+}
+
+bool NetLog::Source::IsValid() const {
+ return id != kInvalidId;
+}
+
+void NetLog::Source::AddToEventParameters(
+ base::DictionaryValue* event_params) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("type", static_cast<int>(type));
+ dict->SetInteger("id", static_cast<int>(id));
+ event_params->Set("source_dependency", dict);
+}
+
+NetLog::ParametersCallback NetLog::Source::ToEventParametersCallback() const {
+ return base::Bind(&SourceEventParametersCallback, *this);
+}
+
+// static
+bool NetLog::Source::FromEventParameters(base::Value* event_params,
+ Source* source) {
+ base::DictionaryValue* dict;
+ base::DictionaryValue* source_dict;
+ int source_id;
+ int source_type;
+ if (!event_params ||
+ !event_params->GetAsDictionary(&dict) ||
+ !dict->GetDictionary("source_dependency", &source_dict) ||
+ !source_dict->GetInteger("id", &source_id) ||
+ !source_dict->GetInteger("type", &source_type)) {
+ *source = Source();
+ return false;
+ }
+
+ DCHECK_LE(0, source_id);
+ DCHECK_LT(source_type, NetLog::SOURCE_COUNT);
+ *source = Source(static_cast<SourceType>(source_type), source_id);
+ return true;
+}
+
+base::Value* NetLog::Entry::ToValue() const {
+ base::DictionaryValue* entry_dict(new base::DictionaryValue());
+
+ entry_dict->SetString("time", TickCountToString(time_));
+
+ // Set the entry source.
+ base::DictionaryValue* source_dict = new base::DictionaryValue();
+ source_dict->SetInteger("id", source_.id);
+ source_dict->SetInteger("type", static_cast<int>(source_.type));
+ entry_dict->Set("source", source_dict);
+
+ // Set the event info.
+ entry_dict->SetInteger("type", static_cast<int>(type_));
+ entry_dict->SetInteger("phase", static_cast<int>(phase_));
+
+ // Set the event-specific parameters.
+ if (parameters_callback_) {
+ base::Value* value = parameters_callback_->Run(log_level_);
+ if (value)
+ entry_dict->Set("params", value);
+ }
+
+ return entry_dict;
+}
+
+base::Value* NetLog::Entry::ParametersToValue() const {
+ if (parameters_callback_)
+ return parameters_callback_->Run(log_level_);
+ return NULL;
+}
+
+NetLog::Entry::Entry(
+ EventType type,
+ Source source,
+ EventPhase phase,
+ base::TimeTicks time,
+ const ParametersCallback* parameters_callback,
+ LogLevel log_level)
+ : type_(type),
+ source_(source),
+ phase_(phase),
+ time_(time),
+ parameters_callback_(parameters_callback),
+ log_level_(log_level) {
+};
+
+NetLog::Entry::~Entry() {
+}
+
+NetLog::ThreadSafeObserver::ThreadSafeObserver() : log_level_(LOG_BASIC),
+ net_log_(NULL) {
+}
+
+NetLog::ThreadSafeObserver::~ThreadSafeObserver() {
+ // Make sure we aren't watching a NetLog on destruction. Because the NetLog
+ // may pass events to each observer on multiple threads, we cannot safely
+ // stop watching a NetLog automatically from a parent class.
+ DCHECK(!net_log_);
+}
+
+NetLog::LogLevel NetLog::ThreadSafeObserver::log_level() const {
+ DCHECK(net_log_);
+ return log_level_;
+}
+
+NetLog* NetLog::ThreadSafeObserver::net_log() const {
+ return net_log_;
+}
+
+NetLog::NetLog()
+ : last_id_(0),
+ base_log_level_(LOG_NONE),
+ effective_log_level_(LOG_NONE) {
+}
+
+NetLog::~NetLog() {
+}
+
+void NetLog::AddGlobalEntry(EventType type) {
+ AddEntry(type,
+ Source(net::NetLog::SOURCE_NONE, NextID()),
+ net::NetLog::PHASE_NONE,
+ NULL);
+}
+
+void NetLog::AddGlobalEntry(
+ EventType type,
+ const NetLog::ParametersCallback& parameters_callback) {
+ AddEntry(type,
+ Source(net::NetLog::SOURCE_NONE, NextID()),
+ net::NetLog::PHASE_NONE,
+ &parameters_callback);
+}
+
+uint32 NetLog::NextID() {
+ return base::subtle::NoBarrier_AtomicIncrement(&last_id_, 1);
+}
+
+void NetLog::SetBaseLogLevel(LogLevel log_level) {
+ base::AutoLock lock(lock_);
+ base_log_level_ = log_level;
+
+ UpdateLogLevel();
+}
+
+NetLog::LogLevel NetLog::GetLogLevel() const {
+ base::subtle::Atomic32 log_level =
+ base::subtle::NoBarrier_Load(&effective_log_level_);
+ return static_cast<net::NetLog::LogLevel>(log_level);
+}
+
+void NetLog::AddThreadSafeObserver(
+ net::NetLog::ThreadSafeObserver* observer,
+ LogLevel log_level) {
+ base::AutoLock lock(lock_);
+
+ DCHECK(!observer->net_log_);
+ observers_.AddObserver(observer);
+ observer->net_log_ = this;
+ observer->log_level_ = log_level;
+ UpdateLogLevel();
+}
+
+void NetLog::SetObserverLogLevel(
+ net::NetLog::ThreadSafeObserver* observer,
+ LogLevel log_level) {
+ base::AutoLock lock(lock_);
+
+ DCHECK(observers_.HasObserver(observer));
+ DCHECK_EQ(this, observer->net_log_);
+ observer->log_level_ = log_level;
+ UpdateLogLevel();
+}
+
+void NetLog::RemoveThreadSafeObserver(
+ net::NetLog::ThreadSafeObserver* observer) {
+ base::AutoLock lock(lock_);
+
+ DCHECK(observers_.HasObserver(observer));
+ DCHECK_EQ(this, observer->net_log_);
+ observers_.RemoveObserver(observer);
+ observer->net_log_ = NULL;
+ UpdateLogLevel();
+}
+
+void NetLog::UpdateLogLevel() {
+ lock_.AssertAcquired();
+
+ // Look through all the observers and find the finest granularity
+ // log level (higher values of the enum imply *lower* log levels).
+ LogLevel new_effective_log_level = base_log_level_;
+ ObserverListBase<ThreadSafeObserver>::Iterator it(observers_);
+ ThreadSafeObserver* observer;
+ while ((observer = it.GetNext()) != NULL) {
+ new_effective_log_level =
+ std::min(new_effective_log_level, observer->log_level());
+ }
+ base::subtle::NoBarrier_Store(&effective_log_level_,
+ new_effective_log_level);
+}
+
+// static
+std::string NetLog::TickCountToString(const base::TimeTicks& time) {
+ int64 delta_time = (time - base::TimeTicks()).InMilliseconds();
+ return base::Int64ToString(delta_time);
+}
+
+// static
+const char* NetLog::EventTypeToString(EventType event) {
+ switch (event) {
+#define EVENT_TYPE(label) case TYPE_ ## label: return #label;
+#include "net/base/net_log_event_type_list.h"
+#undef EVENT_TYPE
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+// static
+base::Value* NetLog::GetEventTypesAsValue() {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ for (int i = 0; i < EVENT_COUNT; ++i) {
+ dict->SetInteger(EventTypeToString(static_cast<EventType>(i)), i);
+ }
+ return dict;
+}
+
+// static
+const char* NetLog::SourceTypeToString(SourceType source) {
+ switch (source) {
+#define SOURCE_TYPE(label) case SOURCE_ ## label: return #label;
+#include "net/base/net_log_source_type_list.h"
+#undef SOURCE_TYPE
+ default:
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+// static
+base::Value* NetLog::GetSourceTypesAsValue() {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ for (int i = 0; i < SOURCE_COUNT; ++i) {
+ dict->SetInteger(SourceTypeToString(static_cast<SourceType>(i)), i);
+ }
+ return dict;
+}
+
+// static
+const char* NetLog::EventPhaseToString(EventPhase phase) {
+ switch (phase) {
+ case PHASE_BEGIN:
+ return "PHASE_BEGIN";
+ case PHASE_END:
+ return "PHASE_END";
+ case PHASE_NONE:
+ return "PHASE_NONE";
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+// static
+bool NetLog::IsLoggingBytes(LogLevel log_level) {
+ return log_level == NetLog::LOG_ALL;
+}
+
+// static
+bool NetLog::IsLoggingAllEvents(LogLevel log_level) {
+ return log_level <= NetLog::LOG_ALL_BUT_BYTES;
+}
+
+// static
+NetLog::ParametersCallback NetLog::IntegerCallback(const char* name,
+ int value) {
+ return base::Bind(&NetLogIntegerCallback, name, value);
+}
+
+// static
+NetLog::ParametersCallback NetLog::Int64Callback(const char* name,
+ int64 value) {
+ return base::Bind(&NetLogInt64Callback, name, value);
+}
+
+// static
+NetLog::ParametersCallback NetLog::StringCallback(const char* name,
+ const std::string* value) {
+ DCHECK(value);
+ return base::Bind(&NetLogStringCallback, name, value);
+}
+
+// static
+NetLog::ParametersCallback NetLog::StringCallback(const char* name,
+ const base::string16* value) {
+ DCHECK(value);
+ return base::Bind(&NetLogString16Callback, name, value);
+}
+
+void NetLog::AddEntry(EventType type,
+ const Source& source,
+ EventPhase phase,
+ const NetLog::ParametersCallback* parameters_callback) {
+ LogLevel log_level = GetLogLevel();
+ if (log_level == LOG_NONE)
+ return;
+ Entry entry(type, source, phase, base::TimeTicks::Now(),
+ parameters_callback, log_level);
+
+ // Notify all of the log observers.
+ base::AutoLock lock(lock_);
+ FOR_EACH_OBSERVER(ThreadSafeObserver, observers_, OnAddEntry(entry));
+}
+
+void BoundNetLog::AddEntry(NetLog::EventType type,
+ NetLog::EventPhase phase) const {
+ if (!net_log_)
+ return;
+ net_log_->AddEntry(type, source_, phase, NULL);
+}
+
+void BoundNetLog::AddEntry(
+ NetLog::EventType type,
+ NetLog::EventPhase phase,
+ const NetLog::ParametersCallback& get_parameters) const {
+ if (!net_log_)
+ return;
+ net_log_->AddEntry(type, source_, phase, &get_parameters);
+}
+
+void BoundNetLog::AddEvent(NetLog::EventType type) const {
+ AddEntry(type, NetLog::PHASE_NONE);
+}
+
+void BoundNetLog::AddEvent(
+ NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const {
+ AddEntry(type, NetLog::PHASE_NONE, get_parameters);
+}
+
+void BoundNetLog::BeginEvent(NetLog::EventType type) const {
+ AddEntry(type, NetLog::PHASE_BEGIN);
+}
+
+void BoundNetLog::BeginEvent(
+ NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const {
+ AddEntry(type, NetLog::PHASE_BEGIN, get_parameters);
+}
+
+void BoundNetLog::EndEvent(NetLog::EventType type) const {
+ AddEntry(type, NetLog::PHASE_END);
+}
+
+void BoundNetLog::EndEvent(
+ NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const {
+ AddEntry(type, NetLog::PHASE_END, get_parameters);
+}
+
+void BoundNetLog::AddEventWithNetErrorCode(NetLog::EventType event_type,
+ int net_error) const {
+ DCHECK_NE(ERR_IO_PENDING, net_error);
+ if (net_error >= 0) {
+ AddEvent(event_type);
+ } else {
+ AddEvent(event_type, NetLog::IntegerCallback("net_error", net_error));
+ }
+}
+
+void BoundNetLog::EndEventWithNetErrorCode(NetLog::EventType event_type,
+ int net_error) const {
+ DCHECK_NE(ERR_IO_PENDING, net_error);
+ if (net_error >= 0) {
+ EndEvent(event_type);
+ } else {
+ EndEvent(event_type, NetLog::IntegerCallback("net_error", net_error));
+ }
+}
+
+void BoundNetLog::AddByteTransferEvent(NetLog::EventType event_type,
+ int byte_count,
+ const char* bytes) const {
+ AddEvent(event_type, base::Bind(BytesTransferredCallback, byte_count, bytes));
+}
+
+NetLog::LogLevel BoundNetLog::GetLogLevel() const {
+ if (net_log_)
+ return net_log_->GetLogLevel();
+ return NetLog::LOG_BASIC;
+}
+
+bool BoundNetLog::IsLoggingBytes() const {
+ return NetLog::IsLoggingBytes(GetLogLevel());
+}
+
+bool BoundNetLog::IsLoggingAllEvents() const {
+ return NetLog::IsLoggingAllEvents(GetLogLevel());
+}
+
+// static
+BoundNetLog BoundNetLog::Make(NetLog* net_log,
+ NetLog::SourceType source_type) {
+ if (!net_log)
+ return BoundNetLog();
+
+ NetLog::Source source(source_type, net_log->NextID());
+ return BoundNetLog(source, net_log);
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_log.h b/chromium/net/base/net_log.h
new file mode 100644
index 00000000000..875d785fbc6
--- /dev/null
+++ b/chromium/net/base/net_log.h
@@ -0,0 +1,404 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NET_LOG_H_
+#define NET_BASE_NET_LOG_H_
+
+#include <string>
+
+#include "base/atomicops.h"
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace net {
+
+// NetLog is the destination for log messages generated by the network stack.
+// Each log message has a "source" field which identifies the specific entity
+// that generated the message (for example, which URLRequest or which
+// SocketStream).
+//
+// To avoid needing to pass in the "source ID" to the logging functions, NetLog
+// is usually accessed through a BoundNetLog, which will always pass in a
+// specific source ID.
+//
+// All methods are thread safe, with the exception that no NetLog or
+// NetLog::ThreadSafeObserver functions may be called by an observer's
+// OnAddEntry() method. Doing so will result in a deadlock.
+//
+// For a broader introduction see the design document:
+// https://sites.google.com/a/chromium.org/dev/developers/design-documents/network-stack/netlog
+class NET_EXPORT NetLog {
+ public:
+ enum EventType {
+#define EVENT_TYPE(label) TYPE_ ## label,
+#include "net/base/net_log_event_type_list.h"
+#undef EVENT_TYPE
+ EVENT_COUNT
+ };
+
+ // The 'phase' of an event trace (whether it marks the beginning or end
+ // of an event.).
+ enum EventPhase {
+ PHASE_NONE,
+ PHASE_BEGIN,
+ PHASE_END,
+ };
+
+ // The "source" identifies the entity that generated the log message.
+ enum SourceType {
+#define SOURCE_TYPE(label) SOURCE_ ## label,
+#include "net/base/net_log_source_type_list.h"
+#undef SOURCE_TYPE
+ SOURCE_COUNT
+ };
+
+ // Specifies the granularity of events that should be emitted to the log.
+ //
+ // Since the LogLevel may be read and set on any thread without locking, it
+ // may be possible for an Observer to receive an event or parameters that
+ // normally wouldn't be logged at the currently active log level.
+ enum LogLevel {
+ // Log everything possible, even if it is slow and memory expensive.
+ // Includes logging of transferred bytes.
+ LOG_ALL,
+
+ // Log all events, but do not include the actual transferred bytes as
+ // parameters for bytes sent/received events.
+ LOG_ALL_BUT_BYTES,
+
+ // Only log events which are cheap, and don't consume much memory. This is
+ // the default value for observers.
+ LOG_BASIC,
+
+ // Don't log any events.
+ LOG_NONE,
+ };
+
+ // A callback function that return a Value representation of the parameters
+ // associated with an event. If called, it will be called synchonously,
+ // so it need not have owning references. May be called more than once, or
+ // not at all. May return NULL.
+ typedef base::Callback<base::Value*(LogLevel)> ParametersCallback;
+
+ // Identifies the entity that generated this log. The |id| field should
+ // uniquely identify the source, and is used by log observers to infer
+ // message groupings. Can use NetLog::NextID() to create unique IDs.
+ struct NET_EXPORT Source {
+ static const uint32 kInvalidId;
+
+ Source();
+ Source(SourceType type, uint32 id);
+ bool IsValid() const;
+
+ // Adds the source to a DictionaryValue containing event parameters,
+ // using the name "source_dependency".
+ void AddToEventParameters(base::DictionaryValue* event_params) const;
+
+ // Returns a callback that returns a dictionary with a single entry
+ // named "source_dependecy" that describes |this|.
+ ParametersCallback ToEventParametersCallback() const;
+
+ // Attempts to extract a Source from a set of event parameters. Returns
+ // true and writes the result to |source| on success. Returns false and
+ // makes |source| an invalid source on failure.
+ // TODO(mmenke): Long term, we want to remove this.
+ static bool FromEventParameters(base::Value* event_params, Source* source);
+
+ SourceType type;
+ uint32 id;
+ };
+
+ class NET_EXPORT Entry {
+ public:
+ Entry(EventType type,
+ Source source,
+ EventPhase phase,
+ base::TimeTicks time,
+ const ParametersCallback* parameters_callback,
+ LogLevel log_level);
+ ~Entry();
+
+ EventType type() const { return type_; }
+ Source source() const { return source_; }
+ EventPhase phase() const { return phase_; }
+
+ // Serializes the specified event to a Value. The Value also includes the
+ // current time. Caller takes ownership of returned Value. Takes in a time
+ // to allow back-dating entries.
+ base::Value* ToValue() const;
+
+ // Returns the parameters as a Value. Returns NULL if there are no
+ // parameters. Caller takes ownership of returned Value.
+ base::Value* ParametersToValue() const;
+
+ private:
+ const EventType type_;
+ const Source source_;
+ const EventPhase phase_;
+ const base::TimeTicks time_;
+ const ParametersCallback* parameters_callback_;
+
+ // Log level when the event occurred.
+ const LogLevel log_level_;
+
+ // It is not safe to copy this class, since |parameters_callback_| may
+ // include pointers that become stale immediately after the event is added,
+ // even if the code were modified to keep its own copy of the callback.
+ DISALLOW_COPY_AND_ASSIGN(Entry);
+ };
+
+ // An observer, that must ensure its own thread safety, for events
+ // being added to a NetLog.
+ class NET_EXPORT ThreadSafeObserver {
+ public:
+ // Constructs an observer that wants to see network events, with
+ // the specified minimum event granularity. A ThreadSafeObserver can only
+ // observe a single NetLog at a time.
+ //
+ // Observers will be called on the same thread an entry is added on,
+ // and are responsible for ensuring their own thread safety.
+ //
+ // Observers must stop watching a NetLog before either the Observer or the
+ // NetLog is destroyed.
+ ThreadSafeObserver();
+
+ // Returns the minimum log level for events this observer wants to
+ // receive. Must not be called when not watching a NetLog.
+ LogLevel log_level() const;
+
+ // Returns the NetLog we are currently watching, if any. Returns NULL
+ // otherwise.
+ NetLog* net_log() const;
+
+ // This method will be called on the thread that the event occurs on. It
+ // is the responsibility of the observer to handle it in a thread safe
+ // manner.
+ //
+ // It is illegal for an Observer to call any NetLog or
+ // NetLog::Observer functions in response to a call to OnAddEntry.
+ virtual void OnAddEntry(const Entry& entry) = 0;
+
+ protected:
+ virtual ~ThreadSafeObserver();
+
+ private:
+ friend class NetLog;
+
+ // Both of these values are only modified by the NetLog.
+ LogLevel log_level_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThreadSafeObserver);
+ };
+
+ NetLog();
+ virtual ~NetLog();
+
+ // Emits a global event to the log stream, with its own unique source ID.
+ void AddGlobalEntry(EventType type);
+ void AddGlobalEntry(EventType type,
+ const NetLog::ParametersCallback& parameters_callback);
+
+ // Returns a unique ID which can be used as a source ID. All returned IDs
+ // will be unique and greater than 0.
+ uint32 NextID();
+
+ // Returns the logging level for this NetLog. This is used to avoid computing
+ // and saving expensive log entries.
+ LogLevel GetLogLevel() const;
+
+ // Adds an observer and sets its log level. The observer must not be
+ // watching any NetLog, including this one, when this is called.
+ //
+ // Typical observers should specify LOG_BASIC.
+ //
+ // Observers that need to see the full granularity of events can specify
+ // LOG_ALL_BUT_BYTES. However, doing so will have performance consequences.
+ //
+ // NetLog implementations must call NetLog::OnAddObserver to update the
+ // observer's internal state.
+ void AddThreadSafeObserver(ThreadSafeObserver* observer, LogLevel log_level);
+
+ // Sets the log level of |observer| to |log_level|. |observer| must be
+ // watching |this|. NetLog implementations must call
+ // NetLog::OnSetObserverLogLevel to update the observer's internal state.
+ void SetObserverLogLevel(ThreadSafeObserver* observer, LogLevel log_level);
+
+ // Removes an observer. NetLog implementations must call
+ // NetLog::OnAddObserver to update the observer's internal state.
+ //
+ // For thread safety reasons, it is recommended that this not be called in
+ // an object's destructor.
+ void RemoveThreadSafeObserver(ThreadSafeObserver* observer);
+
+ // Converts a time to the string format that the NetLog uses to represent
+ // times. Strings are used since integers may overflow.
+ static std::string TickCountToString(const base::TimeTicks& time);
+
+ // Returns a C-String symbolic name for |event_type|.
+ static const char* EventTypeToString(EventType event_type);
+
+ // Returns a dictionary that maps event type symbolic names to their enum
+ // values. Caller takes ownership of the returned Value.
+ static base::Value* GetEventTypesAsValue();
+
+ // Returns a C-String symbolic name for |source_type|.
+ static const char* SourceTypeToString(SourceType source_type);
+
+ // Returns a dictionary that maps source type symbolic names to their enum
+ // values. Caller takes ownership of the returned Value.
+ static base::Value* GetSourceTypesAsValue();
+
+ // Returns a C-String symbolic name for |event_phase|.
+ static const char* EventPhaseToString(EventPhase event_phase);
+
+ // Returns true if |log_level| indicates the actual bytes transferred should
+ // be logged. This is only the case when |log_level| is LOG_ALL.
+ static bool IsLoggingBytes(LogLevel log_level);
+
+ // Returns true if |log_level| indicates that all events should be logged,
+ // including frequently occuring ones that may impact performances.
+ // This is the case when |log_level| is LOG_ALL or LOG_ALL_BUT_BYTES.
+ static bool IsLoggingAllEvents(LogLevel log_level);
+
+ // Creates a ParametersCallback that encapsulates a single integer.
+ // Warning: |name| must remain valid for the life of the callback.
+ // TODO(mmenke): Rename this to be consistent with Int64Callback.
+ static ParametersCallback IntegerCallback(const char* name, int value);
+
+ // Creates a ParametersCallback that encapsulates a single int64. The
+ // callback will return the value as a StringValue, since IntegerValues
+ // only support 32-bit values.
+ // Warning: |name| must remain valid for the life of the callback.
+ static ParametersCallback Int64Callback(const char* name, int64 value);
+
+ // Creates a ParametersCallback that encapsulates a single UTF8 string. Takes
+ // |value| as a pointer to avoid copying, and emphasize it must be valid for
+ // the life of the callback. |value| may not be NULL.
+ // Warning: |name| and |value| must remain valid for the life of the callback.
+ static ParametersCallback StringCallback(const char* name,
+ const std::string* value);
+
+ // Same as above, but takes in a UTF16 string.
+ static ParametersCallback StringCallback(const char* name,
+ const base::string16* value);
+
+ protected:
+ // Set the lowest allowed log level, regardless of any Observers.
+ void SetBaseLogLevel(LogLevel log_level);
+
+ private:
+ friend class BoundNetLog;
+
+ void AddEntry(EventType type,
+ const Source& source,
+ EventPhase phase,
+ const NetLog::ParametersCallback* parameters_callback);
+
+ // Called whenever an observer is added or removed, or has its log level
+ // changed. Must have acquired |lock_| prior to calling.
+ void UpdateLogLevel();
+
+ // |lock_| protects access to |observers_|.
+ base::Lock lock_;
+
+ // Last assigned source ID. Incremented to get the next one.
+ base::subtle::Atomic32 last_id_;
+
+ // The lowest allowed log level, regardless of any Observers.
+ // Normally defaults to LOG_NONE, but can be changed with SetBaseLogLevel
+ LogLevel base_log_level_;
+
+ // The current log level.
+ base::subtle::Atomic32 effective_log_level_;
+
+ // |lock_| must be acquired whenever reading or writing to this.
+ ObserverList<ThreadSafeObserver, true> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLog);
+};
+
+// Helper that binds a Source to a NetLog, and exposes convenience methods to
+// output log messages without needing to pass in the source.
+class NET_EXPORT BoundNetLog {
+ public:
+ BoundNetLog() : net_log_(NULL) {}
+
+ // Add a log entry to the NetLog for the bound source.
+ void AddEntry(NetLog::EventType type, NetLog::EventPhase phase) const;
+ void AddEntry(NetLog::EventType type,
+ NetLog::EventPhase phase,
+ const NetLog::ParametersCallback& get_parameters) const;
+
+ // Convenience methods that call AddEntry with a fixed "capture phase"
+ // (begin, end, or none).
+ void BeginEvent(NetLog::EventType type) const;
+ void BeginEvent(NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const;
+
+ void EndEvent(NetLog::EventType type) const;
+ void EndEvent(NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const;
+
+ void AddEvent(NetLog::EventType type) const;
+ void AddEvent(NetLog::EventType type,
+ const NetLog::ParametersCallback& get_parameters) const;
+
+ // Just like AddEvent, except |net_error| is a net error code. A parameter
+ // called "net_error" with the indicated value will be recorded for the event.
+ // |net_error| must be negative, and not ERR_IO_PENDING, as it's not a true
+ // error.
+ void AddEventWithNetErrorCode(NetLog::EventType event_type,
+ int net_error) const;
+
+ // Just like EndEvent, except |net_error| is a net error code. If it's
+ // negative, a parameter called "net_error" with a value of |net_error| is
+ // associated with the event. Otherwise, the end event has no parameters.
+ // |net_error| must not be ERR_IO_PENDING, as it's not a true error.
+ void EndEventWithNetErrorCode(NetLog::EventType event_type,
+ int net_error) const;
+
+ // Logs a byte transfer event to the NetLog. Determines whether to log the
+ // received bytes or not based on the current logging level.
+ void AddByteTransferEvent(NetLog::EventType event_type,
+ int byte_count, const char* bytes) const;
+
+ NetLog::LogLevel GetLogLevel() const;
+
+ // Shortcut for NetLog::IsLoggingBytes(this->GetLogLevel()).
+ bool IsLoggingBytes() const;
+
+ // Shortcut for NetLog::IsLoggingAllEvents(this->GetLogLevel()).
+ bool IsLoggingAllEvents() const;
+
+ // Helper to create a BoundNetLog given a NetLog and a SourceType. Takes care
+ // of creating a unique source ID, and handles the case of NULL net_log.
+ static BoundNetLog Make(NetLog* net_log, NetLog::SourceType source_type);
+
+ const NetLog::Source& source() const { return source_; }
+ NetLog* net_log() const { return net_log_; }
+
+ private:
+ BoundNetLog(const NetLog::Source& source, NetLog* net_log)
+ : source_(source), net_log_(net_log) {
+ }
+
+ NetLog::Source source_;
+ NetLog* net_log_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_NET_LOG_H_
diff --git a/chromium/net/base/net_log_event_type_list.h b/chromium/net/base/net_log_event_type_list.h
new file mode 100644
index 00000000000..b1f0afe096d
--- /dev/null
+++ b/chromium/net/base/net_log_event_type_list.h
@@ -0,0 +1,2229 @@
+// Copyright (c) 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.
+
+// NOTE: No header guards are used, since this file is intended to be expanded
+// directly into net_log.h. DO NOT include this file anywhere else.
+
+// In the event of a failure, a many end events will have a |net_error|
+// parameter with the integer error code associated with the failure. Most
+// of these parameters are not individually documented.
+
+// --------------------------------------------------------------------------
+// General pseudo-events
+// --------------------------------------------------------------------------
+
+// Something got cancelled (we determine what is cancelled based on the
+// log context around it.)
+EVENT_TYPE(CANCELLED)
+
+// Something failed (we determine what failed based on the log context
+// around it.)
+// The event has the following parameters:
+//
+// {
+// "net_error": <The net error code integer for the failure>,
+// }
+EVENT_TYPE(FAILED)
+
+// Marks the creation/destruction of a request (net::URLRequest or
+// SocketStream).
+EVENT_TYPE(REQUEST_ALIVE)
+
+// ------------------------------------------------------------------------
+// HostResolverImpl
+// ------------------------------------------------------------------------
+
+// The start/end of waiting on a host resolve (DNS) request.
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "source_dependency": <Source id of the request being waited on>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL)
+
+// The start/end of a host resolve (DNS) request. Note that these events are
+// logged for all DNS requests, though not all requests result in the creation
+// of a HostResolvedImpl::Request object.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "host": <Hostname associated with the request>,
+// "source_dependency": <Source id, if any, of what created the request>,
+// }
+//
+// If an error occurred, the END phase will contain these parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_REQUEST)
+
+// This event is logged when IPv6 support is determined via IPv6 connect probe.
+EVENT_TYPE(HOST_RESOLVER_IMPL_IPV6_SUPPORTED)
+
+// This event is logged when a request is handled by a cache entry.
+EVENT_TYPE(HOST_RESOLVER_IMPL_CACHE_HIT)
+
+// This event is logged when a request is handled by a HOSTS entry.
+EVENT_TYPE(HOST_RESOLVER_IMPL_HOSTS_HIT)
+
+// This event is created when a new HostResolverImpl::Job is about to be created
+// for a request.
+EVENT_TYPE(HOST_RESOLVER_IMPL_CREATE_JOB)
+
+// The creation/completion of a HostResolverImpl::Job which is created for
+// Requests that cannot be resolved synchronously.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "host": <Hostname associated with the request>,
+// "source_dependency": <Source id, if any, of what created the request>,
+// }
+//
+// On success, the END phase has these parameters:
+// {
+// "address_list": <The host name being resolved>,
+// }
+// If an error occurred, the END phase will contain these parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB)
+
+// This event is created when a HostResolverImpl::Job is evicted from
+// PriorityDispatch before it can start, because the limit on number of queued
+// Jobs was reached.
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB_EVICTED)
+
+// This event is created when a HostResolverImpl::Job is started by
+// PriorityDispatch.
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB_STARTED)
+
+// This event is created when HostResolverImpl::ProcJob is about to start a new
+// attempt to resolve the host.
+//
+// The ATTEMPT_STARTED event has the parameters:
+//
+// {
+// "attempt_number": <the number of the attempt that is resolving the host>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_ATTEMPT_STARTED)
+
+// This event is created when HostResolverImpl::ProcJob has finished resolving
+// the host.
+//
+// The ATTEMPT_FINISHED event has the parameters:
+//
+// {
+// "attempt_number": <the number of the attempt that has resolved the host>,
+// }
+// If an error occurred, the END phase will contain these additional parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// "os_error": <The exact error code integer that getaddrinfo() returned>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_ATTEMPT_FINISHED)
+
+// This is logged for a request when it's attached to a
+// HostResolverImpl::Job. When this occurs without a preceding
+// HOST_RESOLVER_IMPL_CREATE_JOB entry, it means the request was attached to an
+// existing HostResolverImpl::Job.
+//
+// The event contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the attached Job>,
+// }
+//
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB_ATTACH)
+
+// This event is logged for the job to which the request is attached.
+// In that case, the event contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the attached Request>,
+// "priority": <New priority of the job>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB_REQUEST_ATTACH)
+
+// This is logged for a job when a request is cancelled and detached.
+//
+// The event contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the detached Request>,
+// "priority": <New priority of the job>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_JOB_REQUEST_DETACH)
+
+// The creation/completion of a HostResolverImpl::ProcTask to call getaddrinfo.
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "hostname": <Hostname associated with the request>,
+// }
+//
+// On success, the END phase has these parameters:
+// {
+// "address_list": <The resolved addresses>,
+// }
+// If an error occurred, the END phase will contain these parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// "os_error": <The exact error code integer that getaddrinfo() returned>,
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_PROC_TASK)
+
+// The creation/completion of a HostResolverImpl::DnsTask to manage a
+// DnsTransaction. The BEGIN phase contains the following parameters:
+//
+// {
+// "source_dependency": <Source id of DnsTransaction>,
+// }
+//
+// On success, the END phase has these parameters:
+// {
+// "address_list": <The resolved addresses>,
+// }
+// If an error occurred, the END phase will contain these parameters:
+// {
+// "net_error": <The net error code integer for the failure>,
+// "dns_error": <The detailed DnsResponse::Result>
+// }
+EVENT_TYPE(HOST_RESOLVER_IMPL_DNS_TASK)
+
+// ------------------------------------------------------------------------
+// InitProxyResolver
+// ------------------------------------------------------------------------
+
+// The start/end of auto-detect + custom PAC URL configuration.
+EVENT_TYPE(PROXY_SCRIPT_DECIDER)
+
+// The start/end of when proxy autoconfig was artificially paused following
+// a network change event. (We wait some amount of time after being told of
+// network changes to avoid hitting spurious errors during auto-detect).
+EVENT_TYPE(PROXY_SCRIPT_DECIDER_WAIT)
+
+// The start/end of download of a PAC script. This could be the well-known
+// WPAD URL (if testing auto-detect), or a custom PAC URL.
+//
+// The START event has the parameters:
+// {
+// "source": <String describing where PAC script comes from>,
+// }
+//
+// If the fetch failed, then the END phase has these parameters:
+// {
+// "net_error": <Net error code integer>,
+// }
+EVENT_TYPE(PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT)
+
+// This event means that initialization failed because there was no
+// configured script fetcher. (This indicates a configuration error).
+EVENT_TYPE(PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER)
+
+// This event is emitted after deciding to fall-back to the next source
+// of PAC scripts in the list.
+EVENT_TYPE(PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE)
+
+// ------------------------------------------------------------------------
+// ProxyService
+// ------------------------------------------------------------------------
+
+// The start/end of a proxy resolve request.
+EVENT_TYPE(PROXY_SERVICE)
+
+// The time while a request is waiting on InitProxyResolver to configure
+// against either WPAD or custom PAC URL. The specifics on this time
+// are found from ProxyService::init_proxy_resolver_log().
+EVENT_TYPE(PROXY_SERVICE_WAITING_FOR_INIT_PAC)
+
+// This event is emitted to show what the PAC script returned. It can contain
+// extra parameters that are either:
+// {
+// "pac_string": <List of valid proxy servers, in PAC format>,
+// }
+//
+// Or if the the resolver failed:
+// {
+// "net_error": <Net error code that resolver failed with>,
+// }
+EVENT_TYPE(PROXY_SERVICE_RESOLVED_PROXY_LIST)
+
+// This event is emitted whenever the proxy settings used by ProxyService
+// change.
+//
+// It contains these parameters:
+// {
+// "old_config": <Dump of the previous proxy settings>,
+// "new_config": <Dump of the new proxy settings>,
+// }
+//
+// Note that the "old_config" key will be omitted on the first fetch of the
+// proxy settings (since there wasn't a previous value).
+EVENT_TYPE(PROXY_CONFIG_CHANGED)
+
+// Emitted when a list of bad proxies is reported to the proxy service.
+//
+// Parameters:
+// {
+// "bad_proxy_list": <List of bad proxies>,
+// }
+EVENT_TYPE(BAD_PROXY_LIST_REPORTED)
+
+// ------------------------------------------------------------------------
+// ProxyList
+// ------------------------------------------------------------------------
+
+// Emitted when the first proxy server in a list is being marked as
+// bad and proxy resolution is going to failover to the next one in
+// the list. The fallback is local to the request.
+//
+// Parameters:
+// {
+// "bad_proxy": <URI representation of the failed proxy server>,
+// }
+EVENT_TYPE(PROXY_LIST_FALLBACK)
+
+// ------------------------------------------------------------------------
+// ProxyResolverV8Tracing
+// ------------------------------------------------------------------------
+
+// This event is emitted when a javascript error has been triggered by a
+// PAC script. It contains the following event parameters:
+// {
+// "line_number": <The line number in the PAC script
+// (or -1 if not applicable)>,
+// "message": <The error message>,
+// }
+EVENT_TYPE(PAC_JAVASCRIPT_ERROR)
+
+// This event is emitted when a PAC script called alert(). It contains the
+// following event parameters:
+// {
+// "message": <The string of the alert>,
+// }
+EVENT_TYPE(PAC_JAVASCRIPT_ALERT)
+
+// ------------------------------------------------------------------------
+// MultiThreadedProxyResolver
+// ------------------------------------------------------------------------
+
+// Measures the time that a proxy resolve request was stalled waiting for a
+// proxy resolver thread to free-up.
+EVENT_TYPE(WAITING_FOR_PROXY_RESOLVER_THREAD)
+
+// This event is emitted just before a PAC request is bound to a thread. It
+// contains these parameters:
+//
+// {
+// "thread_number": <Identifier for the PAC thread that is going to
+// run this request>,
+// }
+EVENT_TYPE(SUBMITTED_TO_RESOLVER_THREAD)
+
+// ------------------------------------------------------------------------
+// Socket (Shared by stream and datagram sockets)
+// ------------------------------------------------------------------------
+
+// Marks the begin/end of a socket (TCP/SOCKS/SSL/UDP/"SpdyProxyClientSocket").
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the controlling entity>,
+// }
+EVENT_TYPE(SOCKET_ALIVE)
+
+// ------------------------------------------------------------------------
+// StreamSocket
+// ------------------------------------------------------------------------
+
+// The start/end of a TCP connect(). This corresponds with a call to
+// TCPClientSocket::Connect().
+//
+// The START event contains these parameters:
+//
+// {
+// "address_list": <List of network address strings>,
+// }
+//
+// And the END event will contain the following parameters:
+//
+// {
+// "net_error": <Net integer error code, on error>,
+// "source_address": <Local source address of the connection, on success>,
+// }
+EVENT_TYPE(TCP_CONNECT)
+
+// Nested within TCP_CONNECT, there may be multiple attempts to connect
+// to the individual addresses. The START event will describe the
+// address the attempt is for:
+//
+// {
+// "address": <String of the network address>,
+// }
+//
+// And the END event will contain the system error code if it failed:
+//
+// {
+// "os_error": <Integer error code the operating system returned>,
+// }
+EVENT_TYPE(TCP_CONNECT_ATTEMPT)
+
+// The start/end of a TCP accept(). This corresponds with a call to
+// TCPServerSocket::Accept().
+//
+// The END event will contain the following parameters on success:
+// {
+// "address": <Remote address of the accepted connection>,
+// }
+// On failure it contains the following parameters
+// {
+// "net_error": <Net integer error code>,
+// }
+EVENT_TYPE(TCP_ACCEPT)
+
+// This event is logged to the socket stream whenever the socket is
+// acquired/released via a ClientSocketHandle.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "source_dependency": <Source identifier for the controlling entity>,
+// }
+EVENT_TYPE(SOCKET_IN_USE)
+
+// The start/end of a SOCKS connect().
+EVENT_TYPE(SOCKS_CONNECT)
+
+// The start/end of a SOCKS5 connect().
+EVENT_TYPE(SOCKS5_CONNECT)
+
+// This event is emitted when the SOCKS connect fails because the provided
+// was longer than 255 characters.
+EVENT_TYPE(SOCKS_HOSTNAME_TOO_BIG)
+
+// These events are emitted when insufficient data was read while
+// trying to establish a connection to the SOCKS proxy server
+// (during the greeting phase or handshake phase, respectively).
+EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING)
+EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE)
+
+// This event indicates that a bad version number was received in the
+// proxy server's response. The extra parameters show its value:
+// {
+// "version": <Integer version number in the response>,
+// }
+EVENT_TYPE(SOCKS_UNEXPECTED_VERSION)
+
+// This event indicates that the SOCKS proxy server returned an error while
+// trying to create a connection. The following parameters will be attached
+// to the event:
+// {
+// "error_code": <Integer error code returned by the server>,
+// }
+EVENT_TYPE(SOCKS_SERVER_ERROR)
+
+// This event indicates that the SOCKS proxy server asked for an authentication
+// method that we don't support. The following parameters are attached to the
+// event:
+// {
+// "method": <Integer method code>,
+// }
+EVENT_TYPE(SOCKS_UNEXPECTED_AUTH)
+
+// This event indicates that the SOCKS proxy server's response indicated an
+// address type which we are not prepared to handle.
+// The following parameters are attached to the event:
+// {
+// "address_type": <Integer code for the address type>,
+// }
+EVENT_TYPE(SOCKS_UNKNOWN_ADDRESS_TYPE)
+
+// The start/end of an SSL "connect" (aka client handshake).
+EVENT_TYPE(SSL_CONNECT)
+
+// The start/end of an SSL server handshake (aka "accept").
+EVENT_TYPE(SSL_SERVER_HANDSHAKE)
+
+// The SSL server requested a client certificate.
+EVENT_TYPE(SSL_CLIENT_CERT_REQUESTED)
+
+// The start/end of getting a domain-bound certificate and private key.
+//
+// The END event will contain the following parameters on failure:
+//
+// {
+// "net_error": <Net integer error code>,
+// }
+EVENT_TYPE(SSL_GET_DOMAIN_BOUND_CERT)
+
+// The SSL server requested a channel id.
+EVENT_TYPE(SSL_CHANNEL_ID_REQUESTED)
+
+// A channel ID was provided to the SSL library to be sent to the SSL server.
+EVENT_TYPE(SSL_CHANNEL_ID_PROVIDED)
+
+// A client certificate (or none) was provided to the SSL library to be sent
+// to the SSL server.
+// The following parameters are attached to the event:
+// {
+// "cert_count": <Number of certificates>,
+// }
+// A cert_count of 0 means no client certificate was provided.
+// A cert_count of -1 means a client certificate was provided but we don't
+// know the size of the certificate chain.
+EVENT_TYPE(SSL_CLIENT_CERT_PROVIDED)
+
+// An SSL error occurred while trying to do the indicated activity.
+// The following parameters are attached to the event:
+// {
+// "net_error": <Integer code for the specific error type>,
+// "ssl_lib_error": <SSL library's integer code for the specific error type>
+// }
+EVENT_TYPE(SSL_HANDSHAKE_ERROR)
+EVENT_TYPE(SSL_READ_ERROR)
+EVENT_TYPE(SSL_WRITE_ERROR)
+
+// An SSL connection needs to be retried with a lower protocol version because
+// the server may be intolerant of the protocol version we offered.
+// The following parameters are attached to the event:
+// {
+// "host_and_port": <String encoding the host and port>,
+// "net_error": <Net integer error code>,
+// "version_before": <SSL version before the fallback>,
+// "version_after": <SSL version after the fallback>,
+// }
+EVENT_TYPE(SSL_VERSION_FALLBACK)
+
+// We found that our prediction of the server's certificates was correct and
+// we merged the verification with the SSLHostInfo. (Note: now obsolete.)
+EVENT_TYPE(SSL_VERIFICATION_MERGED)
+
+// An SSL error occurred while calling an NSS function not directly related to
+// one of the above activities. Can also be used when more information than
+// is provided by just an error code is needed:
+// {
+// "function": <Name of the NSS function, as a string>,
+// "param": <Most relevant parameter, if any>,
+// "ssl_lib_error": <NSS library's integer code for the specific error type>
+// }
+EVENT_TYPE(SSL_NSS_ERROR)
+
+// The specified number of bytes were sent on the socket. Depending on the
+// source of the event, may be logged either once the data is sent, or when it
+// is queued to be sent.
+// The following parameters are attached:
+// {
+// "byte_count": <Number of bytes that were just sent>,
+// "hex_encoded_bytes": <The exact bytes sent, as a hexadecimal string.
+// Only present when byte logging is enabled>,
+// }
+EVENT_TYPE(SOCKET_BYTES_SENT)
+EVENT_TYPE(SSL_SOCKET_BYTES_SENT)
+
+// The specified number of bytes were received on the socket.
+// The following parameters are attached:
+// {
+// "byte_count": <Number of bytes that were just received>,
+// "hex_encoded_bytes": <The exact bytes received, as a hexadecimal string.
+// Only present when byte logging is enabled>,
+// }
+EVENT_TYPE(SOCKET_BYTES_RECEIVED)
+EVENT_TYPE(SSL_SOCKET_BYTES_RECEIVED)
+
+// A socket error occurred while trying to do the indicated activity.
+// The following parameters are attached to the event:
+// {
+// "net_error": <Integer code for the specific error type>,
+// "os_error": <Integer error code the operating system returned>
+// }
+EVENT_TYPE(SOCKET_READ_ERROR)
+EVENT_TYPE(SOCKET_WRITE_ERROR)
+
+// Certificates were received from the SSL server (during a handshake or
+// renegotiation). This event is only present when logging at LOG_ALL.
+// The following parameters are attached to the event:
+// {
+// "certificates": <A list of PEM encoded certificates in the order that
+// they were sent by the server>,
+// }
+EVENT_TYPE(SSL_CERTIFICATES_RECEIVED)
+
+// ------------------------------------------------------------------------
+// DatagramSocket
+// ------------------------------------------------------------------------
+
+// The start/end of a UDP client connecting.
+//
+// The START event contains these parameters:
+//
+// {
+// "address": <Remote address being connected to>,
+// }
+//
+// And the END event will contain the following parameter:
+//
+// {
+// "net_error": <Net integer error code, on failure>,
+// }
+EVENT_TYPE(UDP_CONNECT)
+
+// The local address of the UDP socket, retrieved via getsockname.
+// The following parameters are attached:
+// {
+// "address": <Local address bound to the socket>,
+// }
+EVENT_TYPE(UDP_LOCAL_ADDRESS)
+
+// The specified number of bytes were transferred on the socket.
+// The following parameters are attached:
+// {
+// "address": <Remote address of data transfer. Not present when not
+// specified for UDP_BYTES_SENT events>,
+// "byte_count": <Number of bytes that were just received>,
+// "hex_encoded_bytes": <The exact bytes received, as a hexadecimal string.
+// Only present when byte logging is enabled>,
+// }
+EVENT_TYPE(UDP_BYTES_RECEIVED)
+EVENT_TYPE(UDP_BYTES_SENT)
+
+// Logged when an error occurs while reading or writing to/from a UDP socket.
+// The following parameters are attached:
+// {
+// "net_error": <Net error code>,
+// }
+EVENT_TYPE(UDP_RECEIVE_ERROR)
+EVENT_TYPE(UDP_SEND_ERROR)
+
+// ------------------------------------------------------------------------
+// ClientSocketPoolBase::ConnectJob
+// ------------------------------------------------------------------------
+
+// The start/end of a ConnectJob.
+//
+// The BEGIN phase has these parameters:
+//
+// {
+// "group_name": <The group name for the socket request.>,
+// }
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB)
+
+// The start/end of the ConnectJob::Connect().
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB_CONNECT)
+
+// This event is logged whenever the ConnectJob gets a new socket
+// association. The event parameters point to that socket:
+//
+// {
+// "source_dependency": <The source identifier for the new socket.>,
+// }
+EVENT_TYPE(CONNECT_JOB_SET_SOCKET)
+
+// Whether the connect job timed out.
+EVENT_TYPE(SOCKET_POOL_CONNECT_JOB_TIMED_OUT)
+
+// ------------------------------------------------------------------------
+// ClientSocketPoolBaseHelper
+// ------------------------------------------------------------------------
+
+// The start/end of a client socket pool request for a socket.
+EVENT_TYPE(SOCKET_POOL)
+
+// The request stalled because there are too many sockets in the pool.
+EVENT_TYPE(SOCKET_POOL_STALLED_MAX_SOCKETS)
+
+// The request stalled because there are too many sockets in the group.
+EVENT_TYPE(SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP)
+
+// Indicates that we reused an existing socket. Attached to the event are
+// the parameters:
+// {
+// "idle_ms": <The number of milliseconds the socket was sitting idle for>,
+// }
+EVENT_TYPE(SOCKET_POOL_REUSED_AN_EXISTING_SOCKET)
+
+// This event simply describes the host:port that were requested from the
+// socket pool. Its parameters are:
+// {
+// "host_and_port": <String encoding the host and port>,
+// }
+EVENT_TYPE(TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET)
+
+// This event simply describes the host:port that were requested from the
+// socket pool. Its parameters are:
+// {
+// "host_and_port": <String encoding the host and port>,
+// }
+EVENT_TYPE(TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS)
+
+
+// A backup socket is created due to slow connect
+EVENT_TYPE(SOCKET_BACKUP_CREATED)
+
+// This event is sent when a connect job is eventually bound to a request
+// (because of late binding and socket backup jobs, we don't assign the job to
+// a request until it has completed).
+//
+// The event parameters are:
+// {
+// "source_dependency": <Source identifer for the connect job we are
+// bound to>,
+// }
+EVENT_TYPE(SOCKET_POOL_BOUND_TO_CONNECT_JOB)
+
+// Identifies the NetLog::Source() for the Socket assigned to the pending
+// request. The event parameters are:
+// {
+// "source_dependency": <Source identifier for the socket we acquired>,
+// }
+EVENT_TYPE(SOCKET_POOL_BOUND_TO_SOCKET)
+
+// The start/end of a client socket pool request for multiple sockets.
+// The event parameters are:
+// {
+// "num_sockets": <Number of sockets we're trying to ensure are connected>,
+// }
+EVENT_TYPE(SOCKET_POOL_CONNECTING_N_SOCKETS)
+
+// ------------------------------------------------------------------------
+// URLRequest
+// ------------------------------------------------------------------------
+
+// Measures the time it took a net::URLRequestJob to start. For the most part
+// this corresponds with the time between net::URLRequest::Start() and
+// net::URLRequest::ResponseStarted(), however it is also repeated for every
+// redirect, and every intercepted job that handles the request.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "url": <String of URL being loaded>,
+// "method": <The method ("POST" or "GET" or "HEAD" etc..)>,
+// "load_flags": <Numeric value of the combined load flags>,
+// "priority": <Numeric priority of the request>,
+// "upload_id" <String of upload body identifier, if present>,
+// }
+//
+// For the END phase, if there was an error, the following parameters are
+// attached:
+// {
+// "net_error": <Net error code of the failure>,
+// }
+EVENT_TYPE(URL_REQUEST_START_JOB)
+
+// This event is sent once a net::URLRequest receives a redirect. The parameters
+// attached to the event are:
+// {
+// "location": <The URL that was redirected to>,
+// }
+EVENT_TYPE(URL_REQUEST_REDIRECTED)
+
+// Measures the time a net::URLRequest is blocked waiting for either the
+// NetworkDelegate or a URLRequest::Delegate to respond.
+//
+// The parameters attached to the event are:
+// {
+// "delegate": <What's blocking the request, if known>,
+// }
+EVENT_TYPE(URL_REQUEST_BLOCKED_ON_DELEGATE)
+
+// The specified number of bytes were read from the net::URLRequest.
+// The filtered event is used when the bytes were passed through a filter before
+// being read. This event is only present when byte logging is enabled.
+// The following parameters are attached:
+// {
+// "byte_count": <Number of bytes that were just sent>,
+// "hex_encoded_bytes": <The exact bytes sent, as a hexadecimal string>,
+// }
+EVENT_TYPE(URL_REQUEST_JOB_BYTES_READ)
+EVENT_TYPE(URL_REQUEST_JOB_FILTERED_BYTES_READ)
+
+// This event is sent when the priority of a net::URLRequest is
+// changed after it has started. The parameters attached to this event
+// are:
+// {
+// "priority": <Numerical value of the priority (higher is more important)>,
+// }
+EVENT_TYPE(URL_REQUEST_SET_PRIORITY)
+
+// ------------------------------------------------------------------------
+// HttpCache
+// ------------------------------------------------------------------------
+
+// Measures the time while getting a reference to the back end.
+EVENT_TYPE(HTTP_CACHE_GET_BACKEND)
+
+// Measures the time while opening a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_OPEN_ENTRY)
+
+// Measures the time while creating a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_CREATE_ENTRY)
+
+// Measures the time it takes to add a HttpCache::Transaction to an http cache
+// entry's list of active Transactions.
+EVENT_TYPE(HTTP_CACHE_ADD_TO_ENTRY)
+
+// Measures the time while deleting a disk cache entry.
+EVENT_TYPE(HTTP_CACHE_DOOM_ENTRY)
+
+// Measures the time while reading/writing a disk cache entry's response headers
+// or metadata.
+EVENT_TYPE(HTTP_CACHE_READ_INFO)
+EVENT_TYPE(HTTP_CACHE_WRITE_INFO)
+
+// Measures the time while reading/writing a disk cache entry's body.
+EVENT_TYPE(HTTP_CACHE_READ_DATA)
+EVENT_TYPE(HTTP_CACHE_WRITE_DATA)
+
+// ------------------------------------------------------------------------
+// Disk Cache / Memory Cache
+// ------------------------------------------------------------------------
+
+// The creation/destruction of a disk_cache::EntryImpl object. The "creation"
+// is considered to be the point at which an Entry is first considered to be
+// good and associated with a key. Note that disk and memory cache entries
+// share event types.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "created": <true if the Entry was created, rather than being opened>,
+// "key": <The Entry's key>,
+// }
+EVENT_TYPE(DISK_CACHE_ENTRY_IMPL)
+EVENT_TYPE(DISK_CACHE_MEM_ENTRY_IMPL)
+
+// Logs the time required to read/write data from/to a cache entry.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "index": <Index being read/written>,
+// "offset": <Offset being read/written>,
+// "buf_len": <Length of buffer being read to/written from>,
+// "truncate": <If present for a write, the truncate flag is set to true.
+// Not present in reads or writes where it is false>,
+// }
+//
+// For the END phase, the following parameters are attached:
+// {
+// "bytes_copied": <Number of bytes copied. Not present on error>,
+// "net_error": <Network error code. Only present on error>,
+// }
+EVENT_TYPE(ENTRY_READ_DATA)
+EVENT_TYPE(ENTRY_WRITE_DATA)
+
+// Logged when sparse read/write operation starts/stops for an Entry.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "offset": <Offset at which to start reading>,
+// "buff_len": <Bytes to read/write>,
+// }
+EVENT_TYPE(SPARSE_READ)
+EVENT_TYPE(SPARSE_WRITE)
+
+// Logged when a parent Entry starts/stops reading/writing a child Entry's data.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "source_dependency": <Source id of the child entry>,
+// "child_len": <Bytes to read/write from/to child>,
+// }
+EVENT_TYPE(SPARSE_READ_CHILD_DATA)
+EVENT_TYPE(SPARSE_WRITE_CHILD_DATA)
+
+// Logged when sparse GetAvailableRange operation starts/stops for an Entry.
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "buff_len": <Bytes to read/write>,
+// "offset": <Offset at which to start reading>,
+// }
+//
+// For the END phase, the following parameters are attached. No parameters are
+// attached when cancelled:
+// {
+// "length": <Length of returned range. Only present on success>,
+// "start": <Position where returned range starts. Only present on success>,
+// "net_error": <Resulting error code. Only present on failure. This may be
+// "OK" when there's no error, but no available bytes in the
+// range>,
+// }
+EVENT_TYPE(SPARSE_GET_RANGE)
+
+// Indicates the children of a sparse EntryImpl are about to be deleted.
+// Not logged for MemEntryImpls.
+EVENT_TYPE(SPARSE_DELETE_CHILDREN)
+
+// Logged when an EntryImpl is closed. Not logged for MemEntryImpls.
+EVENT_TYPE(ENTRY_CLOSE)
+
+// Logged when an entry is doomed.
+EVENT_TYPE(ENTRY_DOOM)
+
+// ------------------------------------------------------------------------
+// HttpStreamFactoryImpl
+// ------------------------------------------------------------------------
+
+// Measures the time taken to fulfill the HttpStreamRequest.
+EVENT_TYPE(HTTP_STREAM_REQUEST)
+
+// Measures the time taken to execute the HttpStreamFactoryImpl::Job
+EVENT_TYPE(HTTP_STREAM_JOB)
+
+// Identifies the NetLog::Source() for the Job that fulfilled the Request.
+// The event parameters are:
+// {
+// "source_dependency": <Source identifier for Job we acquired>,
+// }
+EVENT_TYPE(HTTP_STREAM_REQUEST_BOUND_TO_JOB)
+
+// Identifies the NetLog::Source() for the Request that the Job was attached to.
+// The event parameters are:
+// {
+// "source_dependency": <Source identifier for the Request to which we were
+// attached>,
+// }
+EVENT_TYPE(HTTP_STREAM_JOB_BOUND_TO_REQUEST)
+
+// Logs the protocol negotiated with the server. The event parameters are:
+// {
+// "status": <The NPN status ("negotiated", "unsupported", "no-overlap")>,
+// "proto": <The NPN protocol negotiated>,
+// "server_protos": <The list of server advertised protocols>,
+// }
+EVENT_TYPE(HTTP_STREAM_REQUEST_PROTO)
+
+// ------------------------------------------------------------------------
+// HttpNetworkTransaction
+// ------------------------------------------------------------------------
+
+// Measures the time taken to send the tunnel request to the server.
+EVENT_TYPE(HTTP_TRANSACTION_TUNNEL_SEND_REQUEST)
+
+// This event is sent for a tunnel request.
+// The following parameters are attached:
+// {
+// "line": <The HTTP request line, CRLF terminated>,
+// "headers": <The list of header:value pairs>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SEND_TUNNEL_HEADERS)
+
+// Measures the time to read the tunnel response headers from the server.
+EVENT_TYPE(HTTP_TRANSACTION_TUNNEL_READ_HEADERS)
+
+// This event is sent on receipt of the HTTP response headers to a tunnel
+// request.
+// The following parameters are attached:
+// {
+// "headers": <The list of header:value pairs>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS)
+
+// Measures the time taken to send the request to the server.
+EVENT_TYPE(HTTP_TRANSACTION_SEND_REQUEST)
+
+// This event is sent for a HTTP request.
+// The following parameters are attached:
+// {
+// "line": <The HTTP request line, CRLF terminated>,
+// "headers": <The list of header:value pairs>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SEND_REQUEST_HEADERS)
+
+// Logged when a request body is sent.
+// The following parameters are attached:
+// {
+// "did_merge": <True if the body was merged with the headers for writing>,
+// "is_chunked": <True if chunked>,
+// "length": <The length of the body. May not be accurate when body is not
+// in memory>
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SEND_REQUEST_BODY)
+
+// This event is sent for a HTTP request over a SPDY stream.
+// The following parameters are attached:
+// {
+// "headers": <The list of header:value pairs>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS)
+
+// Measures the time to read HTTP response headers from the server.
+EVENT_TYPE(HTTP_TRANSACTION_READ_HEADERS)
+
+// This event is sent on receipt of the HTTP response headers.
+// The following parameters are attached:
+// {
+// "headers": <The list of header:value pairs>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_READ_RESPONSE_HEADERS)
+
+// Measures the time to read the entity body from the server.
+EVENT_TYPE(HTTP_TRANSACTION_READ_BODY)
+
+// Measures the time taken to read the response out of the socket before
+// restarting for authentication, on keep alive connections.
+EVENT_TYPE(HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART)
+
+// This event is sent when we try to restart a transaction after an error.
+// The following parameters are attached:
+// {
+// "net_error": <The net error code integer for the failure>,
+// }
+EVENT_TYPE(HTTP_TRANSACTION_RESTART_AFTER_ERROR)
+
+// ------------------------------------------------------------------------
+// SpdySession
+// ------------------------------------------------------------------------
+
+// The start/end of a SpdySession.
+// {
+// "host": <The host-port string>,
+// "proxy": <The Proxy PAC string>,
+// }
+EVENT_TYPE(SPDY_SESSION)
+
+// The SpdySession has been initilized with a socket.
+// {
+// "source_dependency": <Source identifier for the underlying socket>,
+// }
+EVENT_TYPE(SPDY_SESSION_INITIALIZED)
+
+// This event is sent for a SPDY SYN_STREAM.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>,
+// }
+EVENT_TYPE(SPDY_SESSION_SYN_STREAM)
+
+// This event is sent for a SPDY SYN_STREAM pushed by the server, where a
+// net::URLRequest is already waiting for the stream.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>,
+// "associated_stream": <The stream id>,
+// }
+EVENT_TYPE(SPDY_SESSION_PUSHED_SYN_STREAM)
+
+// This event is sent for a sending SPDY HEADERS frame.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>,
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_HEADERS)
+
+// This event is sent for a receiving SPDY HEADERS frame.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>,
+// }
+EVENT_TYPE(SPDY_SESSION_RECV_HEADERS)
+
+// This event is sent for a SPDY SYN_REPLY.
+// The following parameters are attached:
+// {
+// "flags": <The control frame flags>,
+// "headers": <The list of header:value pairs>,
+// "id": <The stream id>,
+// }
+EVENT_TYPE(SPDY_SESSION_SYN_REPLY)
+
+// On sending a SPDY SETTINGS frame.
+// The following parameters are attached:
+// {
+// "settings": <The list of setting id, flags and value>,
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_SETTINGS)
+
+// Receipt of a SPDY SETTINGS frame is received.
+// The following parameters are attached:
+// {
+// "host": <The host-port string>,
+// "clear_persisted": <Boolean indicating whether to clear all persisted
+// settings data for the given host>,
+// }
+EVENT_TYPE(SPDY_SESSION_RECV_SETTINGS)
+
+// Receipt of a SPDY SETTING frame.
+// The following parameters are attached:
+// {
+// "id": <The setting id>,
+// "flags": <The setting flags>,
+// "value": <The setting value>,
+// }
+EVENT_TYPE(SPDY_SESSION_RECV_SETTING)
+
+// The receipt of a RST_STREAM
+// The following parameters are attached:
+// {
+// "stream_id": <The stream ID for the window update>,
+// "status": <The reason for the RST_STREAM>,
+// }
+EVENT_TYPE(SPDY_SESSION_RST_STREAM)
+
+// Sending of a RST_STREAM
+// The following parameters are attached:
+// {
+// "stream_id": <The stream ID for the window update>,
+// "status": <The reason for the RST_STREAM>,
+// "description": <The textual description for the RST_STREAM>,
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_RST_STREAM)
+
+// Sending of a SPDY PING frame.
+// The following parameters are attached:
+// {
+// "unique_id": <The unique id of the PING message>,
+// "type": <The PING type ("sent", "received")>,
+// }
+EVENT_TYPE(SPDY_SESSION_PING)
+
+// Receipt of a SPDY GOAWAY frame.
+// The following parameters are attached:
+// {
+// "last_accepted_stream_id": <Last stream id accepted by the server, duh>,
+// "active_streams": <Number of active streams>,
+// "unclaimed_streams": <Number of unclaimed push streams>,
+// "status": <The reason for the GOAWAY>,
+// }
+EVENT_TYPE(SPDY_SESSION_GOAWAY)
+
+// Receipt of a SPDY WINDOW_UPDATE frame (which controls the send window).
+// {
+// "stream_id": <The stream ID for the window update>,
+// "delta" : <The delta window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_RECEIVED_WINDOW_UPDATE_FRAME)
+
+// Sending of a SPDY WINDOW_UPDATE frame (which controls the receive window).
+// {
+// "stream_id": <The stream ID for the window update>,
+// "delta" : <The delta window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_SENT_WINDOW_UPDATE_FRAME)
+
+// This event indicates that the send window has been updated for a session.
+// {
+// "delta": <The window size delta>,
+// "new_window": <The new window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_UPDATE_SEND_WINDOW)
+
+// This event indicates that the recv window has been updated for a session.
+// {
+// "delta": <The window size delta>,
+// "new_window": <The new window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_UPDATE_RECV_WINDOW)
+
+// Sending of a SPDY CREDENTIAL frame (which sends a certificate or
+// certificate chain to the server).
+// {
+// "slot" : <The slot that this certificate should be stored in>,
+// "origin" : <The origin this certificate should be used for>,
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_CREDENTIAL)
+
+// Sending a data frame
+// {
+// "stream_id": <The stream ID for the window update>,
+// "length" : <The size of data sent>,
+// "flags" : <Send data flags>,
+// }
+EVENT_TYPE(SPDY_SESSION_SEND_DATA)
+
+// Receiving a data frame
+// {
+// "stream_id": <The stream ID for the window update>,
+// "length" : <The size of data received>,
+// "flags" : <Receive data flags>,
+// }
+EVENT_TYPE(SPDY_SESSION_RECV_DATA)
+
+// A stream is stalled by the session send window being closed.
+EVENT_TYPE(SPDY_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW)
+
+// A stream is stalled by its send window being closed.
+EVENT_TYPE(SPDY_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW)
+
+// Session is closing
+// {
+// "net_error" : <The error status of the closure>,
+// "description": <The textual description for the closure>,
+// }
+EVENT_TYPE(SPDY_SESSION_CLOSE)
+
+// Event when the creation of a stream is stalled because we're at
+// the maximum number of concurrent streams.
+EVENT_TYPE(SPDY_SESSION_STALLED_MAX_STREAMS)
+
+// Received a value for initial window size in SETTINGS frame with
+// flow control turned off.
+EVENT_TYPE(SPDY_SESSION_INITIAL_WINDOW_SIZE_NO_FLOW_CONTROL)
+
+// Received an out-of-range value for initial window size in SETTINGS
+// frame.
+// {
+// "initial_window_size" : <The initial window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_INITIAL_WINDOW_SIZE_OUT_OF_RANGE)
+
+// Updating streams send window size by the delta window size.
+// {
+// "delta_window_size" : <The delta window size>,
+// }
+EVENT_TYPE(SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE)
+
+// ------------------------------------------------------------------------
+// SpdySessionPool
+// ------------------------------------------------------------------------
+
+// This event indicates the pool is reusing an existing session
+// {
+// "source_dependency": <The session id>,
+// }
+EVENT_TYPE(SPDY_SESSION_POOL_FOUND_EXISTING_SESSION)
+
+// This event indicates the pool is reusing an existing session from an
+// IP pooling match.
+// {
+// "source_dependency": <The session id>,
+// }
+EVENT_TYPE(SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL)
+
+// This event indicates the pool created a new session
+// {
+// "source_dependency": <The session id>,
+// }
+EVENT_TYPE(SPDY_SESSION_POOL_CREATED_NEW_SESSION)
+
+// This event indicates that a SSL socket has been upgraded to a SPDY session.
+// {
+// "source_dependency": <The session id>,
+// }
+EVENT_TYPE(SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET)
+
+// This event indicates that the session has been removed.
+// {
+// "source_dependency": <The session id>,
+// }
+EVENT_TYPE(SPDY_SESSION_POOL_REMOVE_SESSION)
+
+// ------------------------------------------------------------------------
+// SpdyStream
+// ------------------------------------------------------------------------
+
+// The begin and end of a SPDY STREAM.
+EVENT_TYPE(SPDY_STREAM)
+
+// A stream is attached to a pushed stream.
+EVENT_TYPE(SPDY_STREAM_ADOPTED_PUSH_STREAM)
+
+// A stream is unstalled by flow control.
+EVENT_TYPE(SPDY_STREAM_FLOW_CONTROL_UNSTALLED)
+
+// This event indicates that the send window has been updated for a stream.
+// {
+// "id": <The stream id>,
+// "delta": <The window size delta>,
+// "new_window": <The new window size>,
+// }
+EVENT_TYPE(SPDY_STREAM_UPDATE_SEND_WINDOW)
+
+// This event indicates that the recv window has been updated for a stream.
+// {
+// "id": <The stream id>,
+// "delta": <The window size delta>,
+// "new_window": <The new window size>,
+// }
+EVENT_TYPE(SPDY_STREAM_UPDATE_RECV_WINDOW)
+
+// This event indicates a stream error
+// {
+// "id": <The stream id>,
+// "status": <The error status>,
+// "description": <The textual description for the error>,
+// }
+EVENT_TYPE(SPDY_STREAM_ERROR)
+
+// ------------------------------------------------------------------------
+// SpdyProxyClientSocket
+// ------------------------------------------------------------------------
+
+EVENT_TYPE(SPDY_PROXY_CLIENT_SESSION)
+// Identifies the SPDY session a source is using.
+// {
+// "source_dependency": <Source identifier for the underlying session>,
+// }
+
+// ------------------------------------------------------------------------
+// QuicSession
+// ------------------------------------------------------------------------
+
+// The start/end of a QuicSession.
+// {
+// "host": <The host-port string>,
+// }
+EVENT_TYPE(QUIC_SESSION)
+
+// Session is closing because of an error.
+// {
+// "net_error": <Net error code for the closure>,
+// }
+EVENT_TYPE(QUIC_SESSION_CLOSE_ON_ERROR)
+
+// Session received a QUIC packet.
+// {
+// "peer_address": <The ip:port of the peer>,
+// "self_address": <The local ip:port which received the packet>,
+// }
+EVENT_TYPE(QUIC_SESSION_PACKET_RECEIVED)
+
+// Session sent a QUIC packet.
+// {
+// "encryption_level": <The EncryptionLevel of the packet>
+// "packet_sequence_number": <The packet's full 64-bit sequence number,
+// as a base-10 string.>,
+// "size": <The size of the packet in bytes>
+// }
+EVENT_TYPE(QUIC_SESSION_PACKET_RETRANSMITTED)
+
+// Session retransmitted a QUIC packet.
+// {
+// "old_packet_sequence_number": <The old packet's full 64-bit sequence
+// number, as a base-10 string.>,
+// "new_packet_sequence_number": <The new packet's full 64-bit sequence
+// number, as a base-10 string.>,
+// }
+EVENT_TYPE(QUIC_SESSION_PACKET_SENT)
+
+// Session received a QUIC packet header for a valid packet.
+// {
+// "guid": <The 64-bit GUID for this connection, as a base-10 string>,
+// "public_flags": <The public flags set for this packet>,
+// "packet_sequence_number": <The packet's full 64-bit sequence number,
+// as a base-10 string.>,
+// "private_flags": <The private flags set for this packet>,
+// "fec_group": <The FEC group of this packet>,
+// }
+EVENT_TYPE(QUIC_SESSION_PACKET_HEADER_RECEIVED)
+
+// Session received a STREAM frame.
+// {
+// "stream_id": <The id of the stream which this data is for>,
+// "fin": <True if this is the final data set by the peer on this stream>,
+// "offset": <Offset in the byte stream where this data starts>,
+// "length": <Length of the data in this frame>,
+// }
+EVENT_TYPE(QUIC_SESSION_STREAM_FRAME_RECEIVED)
+
+// Session sent a STREAM frame.
+// {
+// "stream_id": <The id of the stream which this data is for>,
+// "fin": <True if this is the final data set by the peer on this stream>,
+// "offset": <Offset in the byte stream where this data starts>,
+// "length": <Length of the data in this frame>,
+// }
+EVENT_TYPE(QUIC_SESSION_STREAM_FRAME_SENT)
+
+// Session received an ACK frame.
+// {
+// "sent_info": <Details of packet sent by the peer>
+// {
+// "least_unacked": <Lowest sequence number of a packet sent by the peer
+// for which it has not received an ACK>,
+// }
+// "received_info": <Details of packet received by the peer>
+// {
+// "largest_observed": <The largest sequence number of a packet received
+// by (or inferred by) the peer>,
+// "missing": <List of sequence numbers of packets lower than
+// largest_observed which have not been received by the
+// peer>,
+// }
+// }
+EVENT_TYPE(QUIC_SESSION_ACK_FRAME_RECEIVED)
+
+// Session sent an ACK frame.
+// {
+// "sent_info": <Details of packet sent by the peer>
+// {
+// "least_unacked": <Lowest sequence number of a packet sent by the peer
+// for which it has not received an ACK>,
+// }
+// "received_info": <Details of packet received by the peer>
+// {
+// "largest_observed": <The largest sequence number of a packet received
+// by (or inferred by) the peer>,
+// "missing": <List of sequence numbers of packets lower than
+// largest_observed which have not been received by the
+// peer>,
+// }
+// }
+EVENT_TYPE(QUIC_SESSION_ACK_FRAME_SENT)
+
+// Session recevied a RST_STREAM frame.
+// {
+// "offset": <Offset in the byte stream which triggered the reset>,
+// "quic_rst_stream_error": <QuicRstStreamErrorCode in the frame>,
+// "details": <Human readable description>,
+// }
+EVENT_TYPE(QUIC_SESSION_RST_STREAM_FRAME_RECEIVED)
+
+// Session sent a RST_STREAM frame.
+// {
+// "offset": <Offset in the byte stream which triggered the reset>,
+// "quic_rst_stream_error": <QuicRstStreamErrorCode in the frame>,
+// "details": <Human readable description>,
+// }
+EVENT_TYPE(QUIC_SESSION_RST_STREAM_FRAME_SENT)
+
+// Session received a CONGESTION_FEEDBACK frame.
+// {
+// "type": <The specific type of feedback being provided>,
+// Other per-feedback type details:
+//
+// for InterArrival:
+// "accumulated_number_of_lost_packets": <Total number of lost packets
+// over the life of this session>,
+// "received_packets": <List of strings of the form:
+// <sequence_number>@<receive_time_in_ms>>,
+//
+// for FixRate:
+// "bitrate_in_bytes_per_second": <The configured bytes per second>,
+//
+// for TCP:
+// "accumulated_number_of_lost_packets": <Total number of lost packets
+// over the life of this session>,
+// "receive_window": <Number of bytes in the receive window>,
+// }
+EVENT_TYPE(QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_RECEIVED)
+
+// Session received a CONGESTION_FEEDBACK frame.
+// {
+// "type": <The specific type of feedback being provided>,
+// Other per-feedback type details:
+//
+// for InterArrival:
+// "accumulated_number_of_lost_packets": <Total number of lost packets
+// over the life of this session>,
+// "received_packets": <List of strings of the form:
+// <sequence_number>@<receive_time_in_ms>>,
+//
+// for FixRate:
+// "bitrate_in_bytes_per_second": <The configured bytes per second>,
+//
+// for TCP:
+// "accumulated_number_of_lost_packets": <Total number of lost packets
+// over the life of this session>,
+// "receive_window": <Number of bytes in the receive window>,
+// }
+EVENT_TYPE(QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_SENT)
+
+// Session received a CONNECTION_CLOSE frame.
+// {
+// "quic_error": <QuicErrorCode in the frame>,
+// "details": <Human readable description>,
+// }
+EVENT_TYPE(QUIC_SESSION_CONNECTION_CLOSE_FRAME_RECEIVED)
+
+// Session received a CONNECTION_CLOSE frame.
+// {
+// "quic_error": <QuicErrorCode in the frame>,
+// "details": <Human readable description>,
+// }
+EVENT_TYPE(QUIC_SESSION_CONNECTION_CLOSE_FRAME_SENT)
+
+// Session received a public reset packet.
+// {
+// }
+EVENT_TYPE(QUIC_SESSION_PUBLIC_RESET_PACKET_RECEIVED)
+
+// Session received a version negotiation packet.
+// {
+// "versions": <List of QUIC versions supported by the server>,
+// }
+EVENT_TYPE(QUIC_SESSION_VERSION_NEGOTIATION_PACKET_RECEIVED)
+
+// Session revived a QUIC packet packet via FEC.
+// {
+// "guid": <The 64-bit GUID for this connection, as a base-10 string>,
+// "public_flags": <The public flags set for this packet>,
+// "packet_sequence_number": <The packet's full 64-bit sequence number,
+// as a base-10 string.>,
+// "private_flags": <The private flags set for this packet>,
+// "fec_group": <The FEC group of this packet>,
+// }
+EVENT_TYPE(QUIC_SESSION_PACKET_HEADER_REVIVED)
+
+// Session received a crypto handshake message.
+// {
+// "quic_crypto_handshake_message": <The human readable dump of the message
+// contents>
+// }
+EVENT_TYPE(QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_RECEIVED)
+
+// Session sent a crypto handshake message.
+// {
+// "quic_crypto_handshake_message": <The human readable dump of the message
+// contents>
+// }
+EVENT_TYPE(QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_SENT)
+
+// Session was closed, either remotely or by the peer.
+// {
+// "quic_error": <QuicErrorCode which caused the connection to be closed>,
+// "from_peer": <True if the peer closed the connection>
+// }
+EVENT_TYPE(QUIC_SESSION_CLOSED)
+
+// ------------------------------------------------------------------------
+// QuicHttpStream
+// ------------------------------------------------------------------------
+
+// The stream is sending the request headers.
+// {
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(QUIC_HTTP_STREAM_SEND_REQUEST_HEADERS)
+
+// The stream has read the response headers.
+// {
+// "headers": <The list of header:value pairs>
+// }
+EVENT_TYPE(QUIC_HTTP_STREAM_READ_RESPONSE_HEADERS)
+
+// ------------------------------------------------------------------------
+// HttpStreamParser
+// ------------------------------------------------------------------------
+
+// Measures the time to read HTTP response headers from the server.
+EVENT_TYPE(HTTP_STREAM_PARSER_READ_HEADERS)
+
+// ------------------------------------------------------------------------
+// SocketStream
+// ------------------------------------------------------------------------
+
+// Measures the time between SocketStream::Connect() and
+// SocketStream::DidEstablishConnection()
+//
+// For the BEGIN phase, the following parameters are attached:
+// {
+// "url": <String of URL being loaded>,
+// }
+//
+// For the END phase, if there was an error, the following parameters are
+// attached:
+// {
+// "net_error": <Net error code of the failure>,
+// }
+EVENT_TYPE(SOCKET_STREAM_CONNECT)
+
+// A message sent on the SocketStream.
+EVENT_TYPE(SOCKET_STREAM_SENT)
+
+// A message received on the SocketStream.
+EVENT_TYPE(SOCKET_STREAM_RECEIVED)
+
+// ------------------------------------------------------------------------
+// WebSocketJob
+// ------------------------------------------------------------------------
+
+// This event is sent for a WebSocket handshake request.
+// The following parameters are attached:
+// {
+// "headers": <handshake request message>,
+// }
+EVENT_TYPE(WEB_SOCKET_SEND_REQUEST_HEADERS)
+
+// This event is sent on receipt of the WebSocket handshake response headers.
+// The following parameters are attached:
+// {
+// "headers": <handshake response message>,
+// }
+EVENT_TYPE(WEB_SOCKET_READ_RESPONSE_HEADERS)
+
+// ------------------------------------------------------------------------
+// SOCKS5ClientSocket
+// ------------------------------------------------------------------------
+
+// The time spent sending the "greeting" to the SOCKS server.
+EVENT_TYPE(SOCKS5_GREET_WRITE)
+
+// The time spent waiting for the "greeting" response from the SOCKS server.
+EVENT_TYPE(SOCKS5_GREET_READ)
+
+// The time spent sending the CONNECT request to the SOCKS server.
+EVENT_TYPE(SOCKS5_HANDSHAKE_WRITE)
+
+// The time spent waiting for the response to the CONNECT request.
+EVENT_TYPE(SOCKS5_HANDSHAKE_READ)
+
+// ------------------------------------------------------------------------
+// HTTP Authentication
+// ------------------------------------------------------------------------
+
+// The time spent authenticating to the proxy.
+EVENT_TYPE(AUTH_PROXY)
+
+// The time spent authentication to the server.
+EVENT_TYPE(AUTH_SERVER)
+
+// ------------------------------------------------------------------------
+// HTML5 Application Cache
+// ------------------------------------------------------------------------
+
+// This event is emitted whenever a request is satistifed directly from
+// the appache.
+EVENT_TYPE(APPCACHE_DELIVERING_CACHED_RESPONSE)
+
+// This event is emitted whenever the appcache uses a fallback response.
+EVENT_TYPE(APPCACHE_DELIVERING_FALLBACK_RESPONSE)
+
+// This event is emitted whenever the appcache generates an error response.
+EVENT_TYPE(APPCACHE_DELIVERING_ERROR_RESPONSE)
+
+// This event is emitted whenever the appcache executes script to compute
+// a response.
+EVENT_TYPE(APPCACHE_DELIVERING_EXECUTABLE_RESPONSE)
+
+// ------------------------------------------------------------------------
+// Global events
+// ------------------------------------------------------------------------
+// These are events which are not grouped by source id, as they have no
+// context.
+
+// This event is emitted whenever NetworkChangeNotifier determines that an
+// active network adapter's IP address has changed.
+EVENT_TYPE(NETWORK_IP_ADDRESSES_CHANGED)
+
+// This event is emitted whenever NetworkChangeNotifier determines that an
+// active network adapter's connectivity status has changed.
+// {
+// "new_connection_type": <Type of the new connection>
+// }
+EVENT_TYPE(NETWORK_CONNECTIVITY_CHANGED)
+
+// This event is emitted whenever NetworkChangeNotifier determines that a change
+// occurs to the host computer's hardware or software that affects the route
+// network packets take to any network server.
+// {
+// "new_connection_type": <Type of the new connection>
+// }
+EVENT_TYPE(NETWORK_CHANGED)
+
+// This event is emitted whenever HostResolverImpl receives a new DnsConfig
+// from the DnsConfigService.
+// {
+// "nameservers": <List of name server IPs>,
+// "search": <List of domain suffixes>,
+// "append_to_multi_label_name": <See DnsConfig>,
+// "ndots": <See DnsConfig>,
+// "timeout": <See DnsConfig>,
+// "attempts": <See DnsConfig>,
+// "rotate": <See DnsConfig>,
+// "edns0": <See DnsConfig>,
+// "num_hosts": <Number of entries in the HOSTS file>
+// }
+EVENT_TYPE(DNS_CONFIG_CHANGED)
+
+// ------------------------------------------------------------------------
+// Exponential back-off throttling events
+// ------------------------------------------------------------------------
+
+// Emitted when back-off is disabled for a given host, or the first time
+// a localhost URL is used (back-off is always disabled for localhost).
+// {
+// "host": <The hostname back-off was disabled for>
+// }
+EVENT_TYPE(THROTTLING_DISABLED_FOR_HOST)
+
+// Emitted when a request is denied due to exponential back-off throttling.
+// {
+// "url": <URL that was being requested>,
+// "num_failures": <Failure count for the URL>,
+// "release_after_ms": <Number of milliseconds until URL will be unblocked>
+// }
+EVENT_TYPE(THROTTLING_REJECTED_REQUEST)
+
+// ------------------------------------------------------------------------
+// DnsTransaction
+// ------------------------------------------------------------------------
+
+// The start/end of a DnsTransaction.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "hostname": <The hostname it is trying to resolve>,
+// "query_type": <Type of the query>,
+// }
+//
+// The END phase contains the following parameters:
+//
+// {
+// "net_error": <The net error code for the failure, if any>,
+// }
+EVENT_TYPE(DNS_TRANSACTION)
+
+// The start/end of a DnsTransaction query for a fully-qualified domain name.
+//
+// The BEGIN phase contains the following parameters:
+//
+// {
+// "qname": <The fully-qualified domain name it is trying to resolve>,
+// }
+//
+// The END phase contains the following parameters:
+//
+// {
+// "net_error": <The net error code for the failure, if any>,
+// }
+EVENT_TYPE(DNS_TRANSACTION_QUERY)
+
+// This event is created when DnsTransaction creates a new UDP socket and
+// tries to resolve the fully-qualified name.
+//
+// It has a single parameter:
+//
+// {
+// "source_dependency": <Source id of the UDP socket created for the
+// attempt>,
+// }
+EVENT_TYPE(DNS_TRANSACTION_ATTEMPT)
+
+// This event is created when DnsTransaction creates a new TCP socket and
+// tries to resolve the fully-qualified name.
+//
+// It has a single parameter:
+//
+// {
+// "source_dependency": <Source id of the TCP socket created for the
+// attempt>,
+// }
+EVENT_TYPE(DNS_TRANSACTION_TCP_ATTEMPT)
+
+// This event is created when DnsTransaction receives a matching response.
+//
+// It has the following parameters:
+//
+// {
+// "rcode": <rcode in the received response>,
+// "answer_count": <answer_count in the received response>,
+// "source_dependency": <Source id of the UDP socket that received the
+// response>,
+// }
+EVENT_TYPE(DNS_TRANSACTION_RESPONSE)
+
+// ------------------------------------------------------------------------
+// ChromeExtension
+// ------------------------------------------------------------------------
+
+// TODO(eroman): This is a layering violation. Fix this in the context
+// of http://crbug.com/90674.
+
+// This event is created when a Chrome extension aborts a request.
+//
+// {
+// "extension_id": <Extension ID that caused the abortion>
+// }
+EVENT_TYPE(CHROME_EXTENSION_ABORTED_REQUEST)
+
+// This event is created when a Chrome extension redirects a request.
+//
+// {
+// "extension_id": <Extension ID that caused the redirection>
+// }
+EVENT_TYPE(CHROME_EXTENSION_REDIRECTED_REQUEST)
+
+// This event is created when a Chrome extension modifieds the headers of a
+// request.
+//
+// {
+// "extension_id": <Extension ID that caused the modification>,
+// "modified_headers": [ "<header>: <value>", ... ],
+// "deleted_headers": [ "<header>", ... ]
+// }
+EVENT_TYPE(CHROME_EXTENSION_MODIFIED_HEADERS)
+
+// This event is created when a Chrome extension tried to modify a request
+// but was ignored due to a conflict.
+//
+// {
+// "extension_id": <Extension ID that was ignored>
+// }
+EVENT_TYPE(CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT)
+
+// This event is created when a Chrome extension provides authentication
+// credentials.
+//
+// {
+// "extension_id": <Extension ID that provides credentials>
+// }
+EVENT_TYPE(CHROME_EXTENSION_PROVIDE_AUTH_CREDENTIALS)
+
+// ------------------------------------------------------------------------
+// HostBlacklistManager
+// ------------------------------------------------------------------------
+
+// TODO(joaodasilva): Layering violation, see comment above.
+// http://crbug.com/90674.
+
+// This event is created when a request is blocked by a policy.
+EVENT_TYPE(CHROME_POLICY_ABORTED_REQUEST)
+
+// ------------------------------------------------------------------------
+// CertVerifier
+// ------------------------------------------------------------------------
+
+// This event is created when we start a CertVerifier request.
+EVENT_TYPE(CERT_VERIFIER_REQUEST)
+
+// This event is created when we start a CertVerifier job.
+// The END phase event parameters are:
+// {
+// "certificates": <A list of PEM encoded certificates, the first one
+// being the certificate to verify and the remaining
+// being intermediate certificates to assist path
+// building. Only present when byte logging is enabled.>
+// }
+EVENT_TYPE(CERT_VERIFIER_JOB)
+
+// This event is created when a CertVerifier request attaches to a job.
+//
+// The event parameters are:
+// {
+// "source_dependency": <Source identifer for the job we are bound to>,
+// }
+EVENT_TYPE(CERT_VERIFIER_REQUEST_BOUND_TO_JOB)
+
+// ------------------------------------------------------------------------
+// HttpPipelinedConnection
+// ------------------------------------------------------------------------
+
+// The start/end of a HttpPipelinedConnection.
+// {
+// "host_and_port": <The host-port string>,
+// }
+EVENT_TYPE(HTTP_PIPELINED_CONNECTION)
+
+// This event is created when a pipelined connection finishes sending a request.
+// {
+// "source_dependency": <Source id of the requesting stream>,
+// }
+EVENT_TYPE(HTTP_PIPELINED_CONNECTION_SENT_REQUEST)
+
+// This event is created when a pipelined connection finishes receiving the
+// response headers.
+// {
+// "source_dependency": <Source id of the requesting stream>,
+// "feedback": <The value of HttpPipelinedConnection::Feedback indicating
+// pipeline capability>,
+// }
+EVENT_TYPE(HTTP_PIPELINED_CONNECTION_RECEIVED_HEADERS)
+
+// This event is created when a pipelined stream closes.
+// {
+// "source_dependency": <Source id of the requesting stream>,
+// "must_close": <True if the pipeline must shut down>,
+// }
+EVENT_TYPE(HTTP_PIPELINED_CONNECTION_STREAM_CLOSED)
+
+// ------------------------------------------------------------------------
+// Download start events.
+// ------------------------------------------------------------------------
+
+// This event is created when a download is started, and lets the URL request
+// event source know what download source it is using.
+// {
+// "source_dependency": <Source id of the download>,
+// }
+EVENT_TYPE(DOWNLOAD_STARTED)
+
+// This event is created when a download is started, and lets the download
+// event source know what URL request it's associated with.
+// {
+// "source_dependency": <Source id of the request being waited on>,
+// }
+EVENT_TYPE(DOWNLOAD_URL_REQUEST)
+
+// ------------------------------------------------------------------------
+// DownloadItem events.
+// ------------------------------------------------------------------------
+
+// This event lives for as long as a download item is active.
+// The BEGIN event occurs right after constrction, and has the following
+// parameters:
+// {
+// "type": <New/history/save page>,
+// "id": <Download ID>,
+// "original_url": <URL that initiated the download>,
+// "final_url": <URL of the actual download file>,
+// "file_name": <initial file name, based on DownloadItem's members:
+// For History downloads it's the |full_path_|
+// For other downloads, uses the first non-empty variable of:
+// |state_info.force_filename|
+// |suggested_filename_|
+// the filename specified in the final URL>,
+// "danger_type": <NOT_DANGEROUS, DANGEROUS_FILE, DANGEROUS_URL,
+// DANGEROUS_CONTENT, MAYBE_DANGEROUS_CONTENT,
+// UNCOMMON_CONTENT, USER_VALIDATED, DANGEROUS_HOST,
+// POTENTIALLY_UNWANTED>,
+// "start_offset": <Where to start writing (defaults to 0)>,
+// "has_user_gesture": <Whether or not we think the user initiated
+// the download>
+// }
+// The END event will occur when the download is interrupted, canceled or
+// completed.
+// DownloadItems that are loaded from history and are never active simply ADD
+// one of these events.
+EVENT_TYPE(DOWNLOAD_ITEM_ACTIVE)
+
+// This event is created when a download item's danger type
+// has been modified.
+// {
+// "danger_type": <The new danger type. See above for possible values.>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_SAFETY_STATE_UPDATED)
+
+// This event is created when a download item is updated.
+// {
+// "bytes_so_far": <Number of bytes received>,
+// "hash_state": <Current hash state, as a hex-encoded binary string>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_UPDATED)
+
+// This event is created when a download item is renamed.
+// {
+// "old_filename": <Old file name>,
+// "new_filename": <New file name>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_RENAMED)
+
+// This event is created when a download item is interrupted.
+// {
+// "interrupt_reason": <The reason for the interruption>,
+// "bytes_so_far": <Number of bytes received>,
+// "hash_state": <Current hash state, as a hex-encoded binary string>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_INTERRUPTED)
+
+// This event is created when a download item is resumed.
+// {
+// "user_initiated": <True if user initiated resume>,
+// "reason": <The reason for the interruption>,
+// "bytes_so_far": <Number of bytes received>,
+// "hash_state": <Current hash state, as a hex-encoded binary string>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_RESUMED)
+
+// This event is created when a download item is completing.
+// {
+// "bytes_so_far": <Number of bytes received>,
+// "final_hash": <Final hash, as a hex-encoded binary string>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_COMPLETING)
+
+// This event is created when a download item is finished.
+// {
+// "auto_opened": <Whether or not the download was auto-opened>
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_FINISHED)
+
+// This event is created when a download item is canceled.
+// {
+// "bytes_so_far": <Number of bytes received>,
+// "hash_state": <Current hash state, as a hex-encoded binary string>,
+// }
+EVENT_TYPE(DOWNLOAD_ITEM_CANCELED)
+
+// ------------------------------------------------------------------------
+// DownloadFile events.
+// ------------------------------------------------------------------------
+
+// This event is created when a download file is opened, and lasts until
+// the file is closed.
+// The BEGIN event has the following parameters:
+// {
+// "file_name": <The name of the file>,
+// "start_offset": <The position at which to start writing>,
+// }
+EVENT_TYPE(DOWNLOAD_FILE_OPENED)
+
+// This event is created when the stream between download source
+// and download file is drained.
+// {
+// "stream_size": <Total size of all bytes drained from the stream>
+// "num_buffers": <How many separate buffers those bytes were in>
+// }
+EVENT_TYPE(DOWNLOAD_STREAM_DRAINED)
+
+// This event is created when a download file is renamed.
+// {
+// "old_filename": <Old filename>,
+// "new_filename": <New filename>,
+// }
+EVENT_TYPE(DOWNLOAD_FILE_RENAMED)
+
+// This event is created when a download file is closed. This event is allowed
+// to occur even if the file is not open.
+EVENT_TYPE(DOWNLOAD_FILE_CLOSED)
+
+// This event is created when a download file is detached.
+EVENT_TYPE(DOWNLOAD_FILE_DETACHED)
+
+// This event is created when a download file is deleted.
+EVENT_TYPE(DOWNLOAD_FILE_DELETED)
+
+// This event is created when a download file operation has an error.
+// {
+// "operation": <open, write, close, etc>,
+// "net_error": <net::Error code>,
+// "os_error": <OS depedent error code>
+// "interrupt_reason": <Download interrupt reason>
+// }
+EVENT_TYPE(DOWNLOAD_FILE_ERROR)
+
+// This event is created when a download file is annotating with source
+// information (for Mark Of The Web and anti-virus integration).
+EVENT_TYPE(DOWNLOAD_FILE_ANNOTATED)
+
+// ------------------------------------------------------------------------
+// FileStream events.
+// ------------------------------------------------------------------------
+
+// This event lasts the lifetime of a file stream.
+EVENT_TYPE(FILE_STREAM_ALIVE)
+
+// This event is created when a file stream is associated with a NetLog source.
+// It indicates what file stream event source is used.
+// {
+// "source_dependency": <Source id of the file stream>,
+// }
+EVENT_TYPE(FILE_STREAM_SOURCE)
+
+// This event is created when a file stream is associated with a NetLog source.
+// It indicates what event source owns the file stream source.
+// {
+// "source_dependency": <Source id of the owner of the file stream>,
+// }
+EVENT_TYPE(FILE_STREAM_BOUND_TO_OWNER)
+
+// Mark the opening/closing of a file stream.
+// The BEGIN event has the following parameters:
+// {
+// "file_name".
+// }
+EVENT_TYPE(FILE_STREAM_OPEN)
+
+// This event is created when a file stream operation has an error.
+// {
+// "operation": <open, write, close, etc>,
+// "os_error": <OS-dependent error code>,
+// "net_error": <net::Error code>,
+// }
+EVENT_TYPE(FILE_STREAM_ERROR)
+
+// -----------------------------------------------------------------------------
+// FTP events.
+// -----------------------------------------------------------------------------
+
+// This event is created when an FTP command is sent. It contains following
+// parameters:
+// {
+// "command": <String - the command sent to remote server>
+// }
+EVENT_TYPE(FTP_COMMAND_SENT)
+
+// This event is created when FTP control connection is made. It contains
+// following parameters:
+// {
+// "source_dependency": <id of log for control connection socket>
+// }
+EVENT_TYPE(FTP_CONTROL_CONNECTION)
+
+// This event is created when FTP data connection is made. It contains
+// following parameters:
+// {
+// "source_dependency": <id of log for data connection socket>
+// }
+EVENT_TYPE(FTP_DATA_CONNECTION)
+
+// This event is created when FTP control connection response is processed.
+// It contains following parameters:
+// {
+// "lines": <list of strings - each representing a line of the response>
+// "status_code": <numeric status code of the response>
+// }
+EVENT_TYPE(FTP_CONTROL_RESPONSE)
+
+// -----------------------------------------------------------------------------
+// Simple Cache events.
+// -----------------------------------------------------------------------------
+
+// This event lasts the lifetime of a Simple Cache entry.
+// It contains the following parameter:
+// {
+// "entry_hash": <hash of the entry, formatted as a hex string>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY)
+
+// This event is created when the entry's key is set.
+// It contains the following parameter:
+// {
+// "key": <key of the entry>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_SET_KEY)
+
+// This event is created when OpenEntry is called. It has no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_OPEN_CALL)
+
+// This event is created when the Simple Cache actually begins opening the
+// cache entry. It has no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_OPEN_BEGIN)
+
+// This event is created when the Simple Cache finishes the OpenEntry call.
+// It contains the following parameter:
+// {
+// "net_error": <net error code returned from the call>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_OPEN_END)
+
+// This event is created when CreateEntry is called. It has no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CREATE_CALL)
+
+// This event is created when the Simple Cache optimistically returns a result
+// from a CreateEntry call before it performs the create operation.
+// It contains the following parameter:
+// {
+// "net_error": <net error code returned from the call>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CREATE_OPTIMISTIC)
+
+// This event is created when the Simple Cache actually begins creating the
+// cache entry. It has no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CREATE_BEGIN)
+
+// This event is created when the Simple Cache finishes the CreateEntry call.
+// It contains the following parameter:
+// {
+// "net_error": <net error code returned from the call>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CREATE_END)
+
+// This event is created when ReadEntry is called.
+// It contains the following parameters:
+// {
+// "index": <Index being read/written>,
+// "offset": <Offset being read/written>,
+// "buf_len": <Length of buffer being read to/written from>,
+// "truncate": <If present for a write, the truncate flag is set to true.
+// Not present in reads or writes where it is false>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_READ_CALL)
+
+// This event is created when the Simple Cache actually begins reading data
+// from the cache entry.
+// It contains the following parameters:
+// {
+// "index": <Index being read/written>,
+// "offset": <Offset being read/written>,
+// "buf_len": <Length of buffer being read to/written from>,
+// "truncate": <If present for a write, the truncate flag is set to true.
+// Not present in reads or writes where it is false>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_READ_BEGIN)
+
+// This event is created when the Simple Cache finishes a ReadEntry call.
+// It contains the following parameters:
+// {
+// "bytes_copied": <Number of bytes copied. Not present on error>,
+// "net_error": <Network error code. Only present on error>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_READ_END)
+
+// This event is created when the Simple Cache begins to verify the checksum of
+// cached data it has just read. It occurs before READ_END, and contains no
+// parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CHECKSUM_BEGIN)
+
+// This event is created when the Simple Cache finishes verifying the checksum
+// of cached data. It occurs after CHECKSUM_BEGIN but before READ_END, and
+// contains one parameter:
+// {
+// "net_error": <net error code returned from the internal checksum call>
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CHECKSUM_END)
+
+// This event is created when WriteEntry is called.
+// It contains the following parameters:
+// {
+// "index": <Index being read/written>,
+// "offset": <Offset being read/written>,
+// "buf_len": <Length of buffer being read to/written from>,
+// "truncate": <If present for a write, the truncate flag is set to true.
+// Not present in reads or writes where it is false>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_WRITE_CALL)
+
+// This event is created when the Simple Cache optimistically returns a result
+// from a WriteData call before it performs the write operation.
+// It contains the following parameters:
+// {
+// "bytes_copied": <Number of bytes copied. Not present on error>,
+// "net_error": <Network error code. Only present on error>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_WRITE_OPTIMISTIC)
+
+// This event is created when the Simple Cache actually begins writing data to
+// the cache entry.
+// It contains the following parameters:
+// {
+// "index": <Index being read/written>,
+// "offset": <Offset being read/written>,
+// "buf_len": <Length of buffer being read to/written from>,
+// "truncate": <If present for a write, the truncate flag is set to true.
+// Not present in reads or writes where it is false>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_WRITE_BEGIN)
+
+// This event is created when the Simple Cache finishes a WriteEntry call.
+// It contains the following parameters:
+// {
+// "bytes_copied": <Number of bytes copied. Not present on error>,
+// "net_error": <Network error code. Only present on error>,
+// }
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_WRITE_END)
+
+// This event is created when DoomEntry is called. It contains no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_DOOM_CALL)
+
+// This event is created when the Simple Cache actually starts dooming a cache
+// entry. It contains no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_DOOM_BEGIN)
+
+// This event is created when the Simple Cache finishes dooming an entry.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_DOOM_END)
+
+// This event is created when CloseEntry is called. It contains no parameters.
+// A Close call may not result in CLOSE_BEGIN and CLOSE_END if there are still
+// more references to the entry remaining.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CLOSE_CALL)
+
+// This event is created when the Simple Cache actually starts closing a cache
+// entry. It contains no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CLOSE_BEGIN)
+
+// This event is created when the Simple Cache finishes a CloseEntry call. It
+// contains no parameters.
+EVENT_TYPE(SIMPLE_CACHE_ENTRY_CLOSE_END)
diff --git a/chromium/net/base/net_log_logger.cc b/chromium/net/base/net_log_logger.cc
new file mode 100644
index 00000000000..bd68bd55a3f
--- /dev/null
+++ b/chromium/net/base/net_log_logger.cc
@@ -0,0 +1,217 @@
+// Copyright 2013 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/base/net_log_logger.h"
+
+#include <stdio.h>
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/address_family.h"
+#include "net/base/load_states.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+
+namespace net {
+
+// This should be incremented when significant changes are made that will
+// invalidate the old loading code.
+static const int kLogFormatVersion = 1;
+
+NetLogLogger::NetLogLogger(FILE* file, const base::Value& constants)
+ : file_(file), added_events_(false) {
+ DCHECK(file);
+
+ // Write constants to the output file. This allows loading files that have
+ // different source and event types, as they may be added and removed
+ // between Chrome versions.
+ std::string json;
+ base::JSONWriter::Write(&constants, &json);
+ fprintf(file_.get(), "{\"constants\": %s,\n", json.c_str());
+ fprintf(file_.get(), "\"events\": [\n");
+}
+
+NetLogLogger::~NetLogLogger() {
+ if (file_.get())
+ fprintf(file_.get(), "]}");
+}
+
+void NetLogLogger::StartObserving(net::NetLog* net_log) {
+ net_log->AddThreadSafeObserver(this, net::NetLog::LOG_ALL_BUT_BYTES);
+}
+
+void NetLogLogger::StopObserving() {
+ net_log()->RemoveThreadSafeObserver(this);
+}
+
+void NetLogLogger::OnAddEntry(const net::NetLog::Entry& entry) {
+ // Add a comma and newline for every event but the first. Newlines are needed
+ // so can load partial log files by just ignoring the last line. For this to
+ // work, lines cannot be pretty printed.
+ scoped_ptr<Value> value(entry.ToValue());
+ std::string json;
+ base::JSONWriter::Write(value.get(), &json);
+ fprintf(file_.get(), "%s%s",
+ (added_events_ ? ",\n" : ""),
+ json.c_str());
+ added_events_ = true;
+}
+
+base::DictionaryValue* NetLogLogger::GetConstants() {
+ DictionaryValue* constants_dict = new DictionaryValue();
+
+ // Version of the file format.
+ constants_dict->SetInteger("logFormatVersion", kLogFormatVersion);
+
+ // Add a dictionary with information on the relationship between event type
+ // enums and their symbolic names.
+ constants_dict->Set("logEventTypes", net::NetLog::GetEventTypesAsValue());
+
+ // Add a dictionary with information about the relationship between load flag
+ // enums and their symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+#define LOAD_FLAG(label, value) \
+ dict->SetInteger(# label, static_cast<int>(value));
+#include "net/base/load_flags_list.h"
+#undef LOAD_FLAG
+
+ constants_dict->Set("loadFlag", dict);
+ }
+
+ // Add a dictionary with information about the relationship between load state
+ // enums and their symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+#define LOAD_STATE(label) \
+ dict->SetInteger(# label, net::LOAD_STATE_ ## label);
+#include "net/base/load_states_list.h"
+#undef LOAD_STATE
+
+ constants_dict->Set("loadState", dict);
+ }
+
+ // Add information on the relationship between net error codes and their
+ // symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+#define NET_ERROR(label, value) \
+ dict->SetInteger(# label, static_cast<int>(value));
+#include "net/base/net_error_list.h"
+#undef NET_ERROR
+
+ constants_dict->Set("netError", dict);
+ }
+
+ // Add information on the relationship between QUIC error codes and their
+ // symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+ for (net::QuicErrorCode error = net::QUIC_NO_ERROR;
+ error < net::QUIC_LAST_ERROR;
+ error = static_cast<net::QuicErrorCode>(error + 1)) {
+ dict->SetInteger(net::QuicUtils::ErrorToString(error),
+ static_cast<int>(error));
+ }
+
+ constants_dict->Set("quicError", dict);
+ }
+
+ // Add information on the relationship between QUIC RST_STREAM error codes
+ // and their symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+ for (net::QuicRstStreamErrorCode error = net::QUIC_STREAM_NO_ERROR;
+ error < net::QUIC_STREAM_LAST_ERROR;
+ error = static_cast<net::QuicRstStreamErrorCode>(error + 1)) {
+ dict->SetInteger(net::QuicUtils::StreamErrorToString(error),
+ static_cast<int>(error));
+ }
+
+ constants_dict->Set("quicRstStreamError", dict);
+ }
+
+ // Information about the relationship between event phase enums and their
+ // symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+ dict->SetInteger("PHASE_BEGIN", net::NetLog::PHASE_BEGIN);
+ dict->SetInteger("PHASE_END", net::NetLog::PHASE_END);
+ dict->SetInteger("PHASE_NONE", net::NetLog::PHASE_NONE);
+
+ constants_dict->Set("logEventPhase", dict);
+ }
+
+ // Information about the relationship between source type enums and
+ // their symbolic names.
+ constants_dict->Set("logSourceType", net::NetLog::GetSourceTypesAsValue());
+
+ // Information about the relationship between LogLevel enums and their
+ // symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+ dict->SetInteger("LOG_ALL", net::NetLog::LOG_ALL);
+ dict->SetInteger("LOG_ALL_BUT_BYTES", net::NetLog::LOG_ALL_BUT_BYTES);
+ dict->SetInteger("LOG_BASIC", net::NetLog::LOG_BASIC);
+
+ constants_dict->Set("logLevelType", dict);
+ }
+
+ // Information about the relationship between address family enums and
+ // their symbolic names.
+ {
+ DictionaryValue* dict = new DictionaryValue();
+
+ dict->SetInteger("ADDRESS_FAMILY_UNSPECIFIED",
+ net::ADDRESS_FAMILY_UNSPECIFIED);
+ dict->SetInteger("ADDRESS_FAMILY_IPV4",
+ net::ADDRESS_FAMILY_IPV4);
+ dict->SetInteger("ADDRESS_FAMILY_IPV6",
+ net::ADDRESS_FAMILY_IPV6);
+
+ constants_dict->Set("addressFamily", dict);
+ }
+
+ // Information about how the "time ticks" values we have given it relate to
+ // actual system times. (We used time ticks throughout since they are stable
+ // across system clock changes).
+ {
+ int64 cur_time_ms = (base::Time::Now() - base::Time()).InMilliseconds();
+
+ int64 cur_time_ticks_ms =
+ (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds();
+
+ // If we add this number to a time tick value, it gives the timestamp.
+ int64 tick_to_time_ms = cur_time_ms - cur_time_ticks_ms;
+
+ // Chrome on all platforms stores times using the Windows epoch
+ // (Jan 1 1601), but the javascript wants a unix epoch.
+ // TODO(eroman): Getting the timestamp relative to the unix epoch should
+ // be part of the time library.
+ const int64 kUnixEpochMs = 11644473600000LL;
+ int64 tick_to_unix_time_ms = tick_to_time_ms - kUnixEpochMs;
+
+ // Pass it as a string, since it may be too large to fit in an integer.
+ constants_dict->SetString("timeTickOffset",
+ base::Int64ToString(tick_to_unix_time_ms));
+ }
+
+ // "clientInfo" key is required for some NetLogLogger log readers.
+ // Provide a default empty value for compatibility.
+ constants_dict->Set("clientInfo", new DictionaryValue());
+
+ return constants_dict;
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_log_logger.h b/chromium/net/base/net_log_logger.h
new file mode 100644
index 00000000000..1d0bc5b2355
--- /dev/null
+++ b/chromium/net/base/net_log_logger.h
@@ -0,0 +1,57 @@
+// Copyright 2013 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_BASE_NET_LOG_LOGGER_H_
+#define NET_BASE_NET_LOG_LOGGER_H_
+
+#include <stdio.h>
+
+#include "base/memory/scoped_handle.h"
+#include "net/base/net_log.h"
+
+namespace base {
+class FilePath;
+class Value;
+}
+
+namespace net {
+
+// NetLogLogger watches the NetLog event stream, and sends all entries to
+// a file specified on creation.
+//
+// The text file will contain a single JSON object.
+class NET_EXPORT NetLogLogger : public NetLog::ThreadSafeObserver {
+ public:
+ // Takes ownership of |file| and will write network events to it once logging
+ // starts. |file| must be non-NULL handle and be open for writing.
+ // |constants| is a legend for decoding constant values used in the log.
+ NetLogLogger(FILE* file, const base::Value& constants);
+ virtual ~NetLogLogger();
+
+ // Starts observing specified NetLog. Must not already be watching a NetLog.
+ // Separate from constructor to enforce thread safety.
+ void StartObserving(NetLog* net_log);
+
+ // Stops observing net_log(). Must already be watching.
+ void StopObserving();
+
+ // net::NetLog::ThreadSafeObserver implementation:
+ virtual void OnAddEntry(const NetLog::Entry& entry) OVERRIDE;
+
+ // Create a dictionary containing legend for net/ constants. Caller takes
+ // ownership of returned value.
+ static base::DictionaryValue* GetConstants();
+
+ private:
+ ScopedStdioHandle file_;
+
+ // True if OnAddEntry() has been called at least once.
+ bool added_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogLogger);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NET_LOG_LOGGER_H_
diff --git a/chromium/net/base/net_log_logger_unittest.cc b/chromium/net/base/net_log_logger_unittest.cc
new file mode 100644
index 00000000000..c4ee98a28c9
--- /dev/null
+++ b/chromium/net/base/net_log_logger_unittest.cc
@@ -0,0 +1,118 @@
+// Copyright 2013 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/base/net_log_logger.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/json/json_reader.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class NetLogLoggerTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ log_path_ = temp_dir_.path().AppendASCII("NetLogFile");
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+ base::FilePath log_path_;
+};
+
+TEST_F(NetLogLoggerTest, GeneratesValidJSONForNoEvents) {
+ {
+ // Create and destroy a logger.
+ FILE* file = file_util::OpenFile(log_path_, "w");
+ ASSERT_TRUE(file);
+ scoped_ptr<base::Value> constants(NetLogLogger::GetConstants());
+ NetLogLogger logger(file, *constants);
+ }
+
+ std::string input;
+ ASSERT_TRUE(file_util::ReadFileToString(log_path_, &input));
+
+ base::JSONReader reader;
+ scoped_ptr<base::Value> root(reader.ReadToValue(input));
+ ASSERT_TRUE(root) << reader.GetErrorMessage();
+
+ base::DictionaryValue* dict;
+ ASSERT_TRUE(root->GetAsDictionary(&dict));
+ base::ListValue* events;
+ ASSERT_TRUE(dict->GetList("events", &events));
+ ASSERT_EQ(0u, events->GetSize());
+}
+
+TEST_F(NetLogLoggerTest, GeneratesValidJSONWithOneEvent) {
+ {
+ FILE* file = file_util::OpenFile(log_path_, "w");
+ ASSERT_TRUE(file);
+ scoped_ptr<base::Value> constants(NetLogLogger::GetConstants());
+ NetLogLogger logger(file, *constants);
+
+ const int kDummyId = 1;
+ NetLog::Source source(NetLog::SOURCE_SPDY_SESSION, kDummyId);
+ NetLog::Entry entry(NetLog::TYPE_PROXY_SERVICE,
+ source,
+ NetLog::PHASE_BEGIN,
+ base::TimeTicks::Now(),
+ NULL,
+ NetLog::LOG_BASIC);
+ logger.OnAddEntry(entry);
+ }
+
+ std::string input;
+ ASSERT_TRUE(file_util::ReadFileToString(log_path_, &input));
+
+ base::JSONReader reader;
+ scoped_ptr<base::Value> root(reader.ReadToValue(input));
+ ASSERT_TRUE(root) << reader.GetErrorMessage();
+
+ base::DictionaryValue* dict;
+ ASSERT_TRUE(root->GetAsDictionary(&dict));
+ base::ListValue* events;
+ ASSERT_TRUE(dict->GetList("events", &events));
+ ASSERT_EQ(1u, events->GetSize());
+}
+
+TEST_F(NetLogLoggerTest, GeneratesValidJSONWithMultipleEvents) {
+ {
+ FILE* file = file_util::OpenFile(log_path_, "w");
+ ASSERT_TRUE(file);
+ scoped_ptr<base::Value> constants(NetLogLogger::GetConstants());
+ NetLogLogger logger(file, *constants);
+
+ const int kDummyId = 1;
+ NetLog::Source source(NetLog::SOURCE_SPDY_SESSION, kDummyId);
+ NetLog::Entry entry(NetLog::TYPE_PROXY_SERVICE,
+ source,
+ NetLog::PHASE_BEGIN,
+ base::TimeTicks::Now(),
+ NULL,
+ NetLog::LOG_BASIC);
+
+ // Add the entry multiple times.
+ logger.OnAddEntry(entry);
+ logger.OnAddEntry(entry);
+ }
+
+ std::string input;
+ ASSERT_TRUE(file_util::ReadFileToString(log_path_, &input));
+
+ base::JSONReader reader;
+ scoped_ptr<base::Value> root(reader.ReadToValue(input));
+ ASSERT_TRUE(root) << reader.GetErrorMessage();
+
+ base::DictionaryValue* dict;
+ ASSERT_TRUE(root->GetAsDictionary(&dict));
+ base::ListValue* events;
+ ASSERT_TRUE(dict->GetList("events", &events));
+ ASSERT_EQ(2u, events->GetSize());
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_log_source_type_list.h b/chromium/net/base/net_log_source_type_list.h
new file mode 100644
index 00000000000..2219f7e5f17
--- /dev/null
+++ b/chromium/net/base/net_log_source_type_list.h
@@ -0,0 +1,30 @@
+// Copyright (c) 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.
+
+// NOTE: No header guards are used, since this file is intended to be expanded
+// directly within a block where the SOURCE_TYPE macro is defined.
+
+// Used for global events which don't correspond to a particular entity.
+SOURCE_TYPE(NONE)
+
+SOURCE_TYPE(URL_REQUEST)
+SOURCE_TYPE(SOCKET_STREAM)
+SOURCE_TYPE(PROXY_SCRIPT_DECIDER)
+SOURCE_TYPE(CONNECT_JOB)
+SOURCE_TYPE(SOCKET)
+SOURCE_TYPE(SPDY_SESSION)
+SOURCE_TYPE(QUIC_SESSION)
+SOURCE_TYPE(HOST_RESOLVER_IMPL_REQUEST)
+SOURCE_TYPE(HOST_RESOLVER_IMPL_JOB)
+SOURCE_TYPE(DISK_CACHE_ENTRY)
+SOURCE_TYPE(MEMORY_CACHE_ENTRY)
+SOURCE_TYPE(HTTP_STREAM_JOB)
+SOURCE_TYPE(EXPONENTIAL_BACKOFF_THROTTLING)
+SOURCE_TYPE(UDP_SOCKET)
+SOURCE_TYPE(CERT_VERIFIER_JOB)
+SOURCE_TYPE(HTTP_PIPELINED_CONNECTION)
+SOURCE_TYPE(DOWNLOAD)
+SOURCE_TYPE(FILESTREAM)
+SOURCE_TYPE(DNS_PROBER)
+SOURCE_TYPE(PROXY_CLIENT_SOCKET)
diff --git a/chromium/net/base/net_log_unittest.cc b/chromium/net/base/net_log_unittest.cc
new file mode 100644
index 00000000000..7a98f797a4c
--- /dev/null
+++ b/chromium/net/base/net_log_unittest.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2013 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/base/net_log_unittest.h"
+
+#include "base/bind.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+const int kThreads = 10;
+const int kEvents = 100;
+
+base::Value* NetLogLevelCallback(NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("log_level", log_level);
+ return dict;
+}
+
+TEST(NetLogTest, Basic) {
+ CapturingNetLog net_log;
+ CapturingNetLog::CapturedEntryList entries;
+ net_log.GetEntries(&entries);
+ EXPECT_EQ(0u, entries.size());
+
+ net_log.AddGlobalEntry(NetLog::TYPE_CANCELLED);
+
+ net_log.GetEntries(&entries);
+ ASSERT_EQ(1u, entries.size());
+ EXPECT_EQ(NetLog::TYPE_CANCELLED, entries[0].type);
+ EXPECT_EQ(NetLog::SOURCE_NONE, entries[0].source.type);
+ EXPECT_NE(NetLog::Source::kInvalidId, entries[0].source.id);
+ EXPECT_EQ(NetLog::PHASE_NONE, entries[0].phase);
+ EXPECT_GE(base::TimeTicks::Now(), entries[0].time);
+ EXPECT_FALSE(entries[0].params);
+}
+
+// Check that the correct LogLevel is sent to NetLog Value callbacks, and that
+// LOG_NONE logs no events.
+TEST(NetLogTest, LogLevels) {
+ CapturingNetLog net_log;
+ for (int log_level = NetLog::LOG_ALL; log_level <= NetLog::LOG_NONE;
+ ++log_level) {
+ net_log.SetLogLevel(static_cast<NetLog::LogLevel>(log_level));
+ EXPECT_EQ(log_level, net_log.GetLogLevel());
+
+ net_log.AddGlobalEntry(NetLog::TYPE_SOCKET_ALIVE,
+ base::Bind(NetLogLevelCallback));
+
+ CapturingNetLog::CapturedEntryList entries;
+ net_log.GetEntries(&entries);
+
+ if (log_level == NetLog::LOG_NONE) {
+ EXPECT_EQ(0u, entries.size());
+ } else {
+ ASSERT_EQ(1u, entries.size());
+ EXPECT_EQ(NetLog::TYPE_SOCKET_ALIVE, entries[0].type);
+ EXPECT_EQ(NetLog::SOURCE_NONE, entries[0].source.type);
+ EXPECT_NE(NetLog::Source::kInvalidId, entries[0].source.id);
+ EXPECT_EQ(NetLog::PHASE_NONE, entries[0].phase);
+ EXPECT_GE(base::TimeTicks::Now(), entries[0].time);
+
+ int logged_log_level;
+ ASSERT_TRUE(entries[0].GetIntegerValue("log_level", &logged_log_level));
+ EXPECT_EQ(log_level, logged_log_level);
+ }
+
+ net_log.Clear();
+ }
+}
+
+class CountingObserver : public NetLog::ThreadSafeObserver {
+ public:
+ CountingObserver() : count_(0) {}
+
+ virtual ~CountingObserver() {
+ if (net_log())
+ net_log()->RemoveThreadSafeObserver(this);
+ }
+
+ virtual void OnAddEntry(const NetLog::Entry& entry) OVERRIDE {
+ ++count_;
+ }
+
+ int count() const { return count_; }
+
+ private:
+ int count_;
+};
+
+void AddEvent(NetLog* net_log) {
+ net_log->AddGlobalEntry(NetLog::TYPE_CANCELLED);
+}
+
+// A thread that waits until an event has been signalled before calling
+// RunTestThread.
+class NetLogTestThread : public base::SimpleThread {
+ public:
+ NetLogTestThread()
+ : base::SimpleThread("NetLogTest"),
+ net_log_(NULL),
+ start_event_(NULL) {
+ }
+
+ // We'll wait for |start_event| to be triggered before calling a subclass's
+ // subclass's RunTestThread() function.
+ void Init(NetLog* net_log, base::WaitableEvent* start_event) {
+ start_event_ = start_event;
+ net_log_ = net_log;
+ }
+
+ virtual void Run() OVERRIDE {
+ start_event_->Wait();
+ RunTestThread();
+ }
+
+ // Subclasses must override this with the code they want to run on their
+ // thread.
+ virtual void RunTestThread() = 0;
+
+ protected:
+ NetLog* net_log_;
+
+ private:
+ // Only triggered once all threads have been created, to make it less likely
+ // each thread completes before the next one starts.
+ base::WaitableEvent* start_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetLogTestThread);
+};
+
+// A thread that adds a bunch of events to the NetLog.
+class AddEventsTestThread : public NetLogTestThread {
+ public:
+ AddEventsTestThread() {}
+ virtual ~AddEventsTestThread() {}
+
+ private:
+ virtual void RunTestThread() OVERRIDE {
+ for (int i = 0; i < kEvents; ++i)
+ AddEvent(net_log_);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AddEventsTestThread);
+};
+
+// A thread that adds and removes an observer from the NetLog repeatedly.
+class AddRemoveObserverTestThread : public NetLogTestThread {
+ public:
+ AddRemoveObserverTestThread() {}
+
+ virtual ~AddRemoveObserverTestThread() {
+ EXPECT_TRUE(!observer_.net_log());
+ }
+
+ private:
+ virtual void RunTestThread() OVERRIDE {
+ for (int i = 0; i < kEvents; ++i) {
+ ASSERT_FALSE(observer_.net_log());
+
+ net_log_->AddThreadSafeObserver(&observer_, NetLog::LOG_BASIC);
+ ASSERT_EQ(net_log_, observer_.net_log());
+ ASSERT_EQ(NetLog::LOG_BASIC, observer_.log_level());
+
+ net_log_->SetObserverLogLevel(&observer_, NetLog::LOG_ALL_BUT_BYTES);
+ ASSERT_EQ(net_log_, observer_.net_log());
+ ASSERT_EQ(NetLog::LOG_ALL_BUT_BYTES, observer_.log_level());
+ ASSERT_LE(net_log_->GetLogLevel(), NetLog::LOG_ALL_BUT_BYTES);
+
+ net_log_->SetObserverLogLevel(&observer_, NetLog::LOG_ALL);
+ ASSERT_EQ(net_log_, observer_.net_log());
+ ASSERT_EQ(NetLog::LOG_ALL, observer_.log_level());
+ ASSERT_LE(net_log_->GetLogLevel(), NetLog::LOG_ALL);
+
+ net_log_->RemoveThreadSafeObserver(&observer_);
+ ASSERT_TRUE(!observer_.net_log());
+ }
+ }
+
+ CountingObserver observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddRemoveObserverTestThread);
+};
+
+// Creates |kThreads| threads of type |ThreadType| and then runs them all
+// to completion.
+template<class ThreadType>
+void RunTestThreads(NetLog* net_log) {
+ ThreadType threads[kThreads];
+ base::WaitableEvent start_event(true, false);
+
+ for (size_t i = 0; i < arraysize(threads); ++i) {
+ threads[i].Init(net_log, &start_event);
+ threads[i].Start();
+ }
+
+ start_event.Signal();
+
+ for (size_t i = 0; i < arraysize(threads); ++i)
+ threads[i].Join();
+}
+
+// Makes sure that events on multiple threads are dispatched to all observers.
+TEST(NetLogTest, NetLogEventThreads) {
+ NetLog net_log;
+
+ // Attach some observers. Since they're created after |net_log|, they'll
+ // safely detach themselves on destruction.
+ CountingObserver observers[3];
+ for (size_t i = 0; i < arraysize(observers); ++i)
+ net_log.AddThreadSafeObserver(&observers[i], NetLog::LOG_BASIC);
+
+ // Run a bunch of threads to completion, each of which will emit events to
+ // |net_log|.
+ RunTestThreads<AddEventsTestThread>(&net_log);
+
+ // Check that each observer saw the emitted events.
+ const int kTotalEvents = kThreads * kEvents;
+ for (size_t i = 0; i < arraysize(observers); ++i)
+ EXPECT_EQ(kTotalEvents, observers[i].count());
+}
+
+// Test adding and removing a single observer.
+TEST(NetLogTest, NetLogAddRemoveObserver) {
+ NetLog net_log;
+ CountingObserver observer;
+
+ AddEvent(&net_log);
+ EXPECT_EQ(0, observer.count());
+ EXPECT_EQ(NULL, observer.net_log());
+ EXPECT_EQ(NetLog::LOG_NONE, net_log.GetLogLevel());
+
+ // Add the observer and add an event.
+ net_log.AddThreadSafeObserver(&observer, NetLog::LOG_BASIC);
+ EXPECT_EQ(&net_log, observer.net_log());
+ EXPECT_EQ(NetLog::LOG_BASIC, observer.log_level());
+ EXPECT_EQ(NetLog::LOG_BASIC, net_log.GetLogLevel());
+
+ AddEvent(&net_log);
+ EXPECT_EQ(1, observer.count());
+
+ // Change the observer's logging level and add an event.
+ net_log.SetObserverLogLevel(&observer, NetLog::LOG_ALL);
+ EXPECT_EQ(&net_log, observer.net_log());
+ EXPECT_EQ(NetLog::LOG_ALL, observer.log_level());
+ EXPECT_EQ(NetLog::LOG_ALL, net_log.GetLogLevel());
+
+ AddEvent(&net_log);
+ EXPECT_EQ(2, observer.count());
+
+ // Remove observer and add an event.
+ net_log.RemoveThreadSafeObserver(&observer);
+ EXPECT_EQ(NULL, observer.net_log());
+ EXPECT_EQ(NetLog::LOG_NONE, net_log.GetLogLevel());
+
+ AddEvent(&net_log);
+ EXPECT_EQ(2, observer.count());
+
+ // Add the observer a final time, and add an event.
+ net_log.AddThreadSafeObserver(&observer, NetLog::LOG_ALL);
+ EXPECT_EQ(&net_log, observer.net_log());
+ EXPECT_EQ(NetLog::LOG_ALL, observer.log_level());
+ EXPECT_EQ(NetLog::LOG_ALL, net_log.GetLogLevel());
+
+ AddEvent(&net_log);
+ EXPECT_EQ(3, observer.count());
+}
+
+// Test adding and removing two observers.
+TEST(NetLogTest, NetLogTwoObservers) {
+ NetLog net_log;
+ CountingObserver observer[2];
+
+ // Add first observer.
+ net_log.AddThreadSafeObserver(&observer[0], NetLog::LOG_ALL_BUT_BYTES);
+ EXPECT_EQ(&net_log, observer[0].net_log());
+ EXPECT_EQ(NULL, observer[1].net_log());
+ EXPECT_EQ(NetLog::LOG_ALL_BUT_BYTES, observer[0].log_level());
+ EXPECT_EQ(NetLog::LOG_ALL_BUT_BYTES, net_log.GetLogLevel());
+
+ // Add second observer observer.
+ net_log.AddThreadSafeObserver(&observer[1], NetLog::LOG_ALL);
+ EXPECT_EQ(&net_log, observer[0].net_log());
+ EXPECT_EQ(&net_log, observer[1].net_log());
+ EXPECT_EQ(NetLog::LOG_ALL_BUT_BYTES, observer[0].log_level());
+ EXPECT_EQ(NetLog::LOG_ALL, observer[1].log_level());
+ EXPECT_EQ(NetLog::LOG_ALL, net_log.GetLogLevel());
+
+ // Add event and make sure both observers receive it.
+ AddEvent(&net_log);
+ EXPECT_EQ(1, observer[0].count());
+ EXPECT_EQ(1, observer[1].count());
+
+ // Remove second observer.
+ net_log.RemoveThreadSafeObserver(&observer[1]);
+ EXPECT_EQ(&net_log, observer[0].net_log());
+ EXPECT_EQ(NULL, observer[1].net_log());
+ EXPECT_EQ(NetLog::LOG_ALL_BUT_BYTES, observer[0].log_level());
+ EXPECT_EQ(NetLog::LOG_ALL_BUT_BYTES, net_log.GetLogLevel());
+
+ // Add event and make sure only second observer gets it.
+ AddEvent(&net_log);
+ EXPECT_EQ(2, observer[0].count());
+ EXPECT_EQ(1, observer[1].count());
+
+ // Remove first observer.
+ net_log.RemoveThreadSafeObserver(&observer[0]);
+ EXPECT_EQ(NULL, observer[0].net_log());
+ EXPECT_EQ(NULL, observer[1].net_log());
+ EXPECT_EQ(NetLog::LOG_NONE, net_log.GetLogLevel());
+
+ // Add event and make sure neither observer gets it.
+ AddEvent(&net_log);
+ EXPECT_EQ(2, observer[0].count());
+ EXPECT_EQ(1, observer[1].count());
+}
+
+// Makes sure that adding and removing observers simultaneously on different
+// threads works.
+TEST(NetLogTest, NetLogAddRemoveObserverThreads) {
+ NetLog net_log;
+
+ // Run a bunch of threads to completion, each of which will repeatedly add
+ // and remove an observer, and set its logging level.
+ RunTestThreads<AddRemoveObserverTestThread>(&net_log);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/net_log_unittest.h b/chromium/net/base/net_log_unittest.h
new file mode 100644
index 00000000000..32fb5f82399
--- /dev/null
+++ b/chromium/net/base/net_log_unittest.h
@@ -0,0 +1,152 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NET_LOG_UNITTEST_H_
+#define NET_BASE_NET_LOG_UNITTEST_H_
+
+#include <cstddef>
+
+#include "net/base/capturing_net_log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// Create a timestamp with internal value of |t| milliseconds from the epoch.
+inline base::TimeTicks MakeTime(int t) {
+ base::TimeTicks ticks; // initialized to 0.
+ ticks += base::TimeDelta::FromMilliseconds(t);
+ return ticks;
+}
+
+inline ::testing::AssertionResult LogContainsEventHelper(
+ const CapturingNetLog::CapturedEntryList& entries,
+ int i, // Negative indices are reverse indices.
+ const base::TimeTicks& expected_time,
+ bool check_time,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ // Negative indices are reverse indices.
+ size_t j = (i < 0) ?
+ static_cast<size_t>(static_cast<int>(entries.size()) + i) :
+ static_cast<size_t>(i);
+ if (j >= entries.size())
+ return ::testing::AssertionFailure() << j << " is out of bounds.";
+ const CapturingNetLog::CapturedEntry& entry = entries[j];
+ if (expected_event != entry.type) {
+ return ::testing::AssertionFailure()
+ << "Actual event: " << NetLog::EventTypeToString(entry.type)
+ << ". Expected event: " << NetLog::EventTypeToString(expected_event)
+ << ".";
+ }
+ if (expected_phase != entry.phase) {
+ return ::testing::AssertionFailure()
+ << "Actual phase: " << entry.phase
+ << ". Expected phase: " << expected_phase << ".";
+ }
+ if (check_time) {
+ if (expected_time != entry.time) {
+ return ::testing::AssertionFailure()
+ << "Actual time: " << entry.time.ToInternalValue()
+ << ". Expected time: " << expected_time.ToInternalValue()
+ << ".";
+ }
+ }
+ return ::testing::AssertionSuccess();
+}
+
+inline ::testing::AssertionResult LogContainsEventAtTime(
+ const CapturingNetLog::CapturedEntryList& log,
+ int i, // Negative indices are reverse indices.
+ const base::TimeTicks& expected_time,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ return LogContainsEventHelper(log, i, expected_time, true,
+ expected_event, expected_phase);
+}
+
+// Version without timestamp.
+inline ::testing::AssertionResult LogContainsEvent(
+ const CapturingNetLog::CapturedEntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ return LogContainsEventHelper(log, i, base::TimeTicks(), false,
+ expected_event, expected_phase);
+}
+
+// Version for PHASE_BEGIN (and no timestamp).
+inline ::testing::AssertionResult LogContainsBeginEvent(
+ const CapturingNetLog::CapturedEntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event) {
+ return LogContainsEvent(log, i, expected_event, NetLog::PHASE_BEGIN);
+}
+
+// Version for PHASE_END (and no timestamp).
+inline ::testing::AssertionResult LogContainsEndEvent(
+ const CapturingNetLog::CapturedEntryList& log,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType expected_event) {
+ return LogContainsEvent(log, i, expected_event, NetLog::PHASE_END);
+}
+
+inline ::testing::AssertionResult LogContainsEntryWithType(
+ const CapturingNetLog::CapturedEntryList& entries,
+ int i, // Negative indices are reverse indices.
+ NetLog::EventType type) {
+ // Negative indices are reverse indices.
+ size_t j = (i < 0) ?
+ static_cast<size_t>(static_cast<int>(entries.size()) + i) :
+ static_cast<size_t>(i);
+ if (j >= entries.size())
+ return ::testing::AssertionFailure() << j << " is out of bounds.";
+ const CapturingNetLog::CapturedEntry& entry = entries[j];
+ if (entry.type != type)
+ return ::testing::AssertionFailure() << "Type does not match.";
+ return ::testing::AssertionSuccess();
+}
+
+
+// Expect that the log contains an event, but don't care about where
+// as long as the first index where it is found is at least |min_index|.
+// Returns the position where the event was found.
+inline size_t ExpectLogContainsSomewhere(
+ const CapturingNetLog::CapturedEntryList& entries,
+ size_t min_index,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ size_t i = 0;
+ for (; i < entries.size(); ++i) {
+ const CapturingNetLog::CapturedEntry& entry = entries[i];
+ if (entry.type == expected_event &&
+ entry.phase == expected_phase)
+ break;
+ }
+ EXPECT_LT(i, entries.size());
+ EXPECT_GE(i, min_index);
+ return i;
+}
+
+// Expect that the log contains an event, but don't care about where
+// as long as one index where it is found is at least |min_index|.
+// Returns the first such position where the event was found.
+inline size_t ExpectLogContainsSomewhereAfter(
+ const CapturingNetLog::CapturedEntryList& entries,
+ size_t min_index,
+ NetLog::EventType expected_event,
+ NetLog::EventPhase expected_phase) {
+ size_t i = min_index;
+ for (; i < entries.size(); ++i) {
+ const CapturingNetLog::CapturedEntry& entry = entries[i];
+ if (entry.type == expected_event &&
+ entry.phase == expected_phase)
+ break;
+ }
+ EXPECT_LT(i, entries.size());
+ return i;
+}
+
+} // namespace net
+
+#endif // NET_BASE_NET_LOG_UNITTEST_H_
diff --git a/chromium/net/base/net_module.cc b/chromium/net/base/net_module.cc
new file mode 100644
index 00000000000..d7f19189b45
--- /dev/null
+++ b/chromium/net/base/net_module.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2006-2008 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/base/net_module.h"
+
+namespace net {
+
+static NetModule::ResourceProvider resource_provider;
+
+// static
+void NetModule::SetResourceProvider(ResourceProvider func) {
+ resource_provider = func;
+}
+
+// static
+base::StringPiece NetModule::GetResource(int key) {
+ return resource_provider ? resource_provider(key) : base::StringPiece();
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_module.h b/chromium/net/base/net_module.h
new file mode 100644
index 00000000000..4ac8ab3a965
--- /dev/null
+++ b/chromium/net/base/net_module.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 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_BASE_NET_MODULE_H__
+#define NET_BASE_NET_MODULE_H__
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Defines global initializers and associated methods for the net module.
+//
+// The network module does not have direct access to the way application
+// resources are stored and fetched by the embedding application (e.g., it
+// cannot see the ResourceBundle class used by Chrome), so it uses this API to
+// get access to such resources.
+//
+class NET_EXPORT NetModule {
+ public:
+ typedef base::StringPiece (*ResourceProvider)(int key);
+
+ // Set the function to call when the net module needs resources
+ static void SetResourceProvider(ResourceProvider func);
+
+ // Call the resource provider (if one exists) to get the specified resource.
+ // Returns an empty string if the resource does not exist or if there is no
+ // resource provider.
+ static base::StringPiece GetResource(int key);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NetModule);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NET_MODULE_H__
diff --git a/chromium/net/base/net_resources.grd b/chromium/net/base/net_resources.grd
new file mode 100644
index 00000000000..327778a2799
--- /dev/null
+++ b/chromium/net/base/net_resources.grd
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<grit latest_public_release="0" current_release="1">
+ <outputs>
+ <output filename="grit/net_resources.h" type="rc_header">
+ <emit emit_type='prepend'></emit>
+ </output>
+ <output filename="net_resources.pak" type="data_package" />
+ <output filename="net_resources.rc" type="rc_all" />
+ </outputs>
+ <translations />
+ <release seq="1">
+ <includes>
+ <include name="IDR_DIR_HEADER_HTML" file="dir_header.html" type="BINDATA" />
+ </includes>
+ </release>
+</grit>
diff --git a/chromium/net/base/net_util.cc b/chromium/net/base/net_util.cc
new file mode 100644
index 00000000000..958e3c3bca8
--- /dev/null
+++ b/chromium/net/base/net_util.cc
@@ -0,0 +1,2090 @@
+// Copyright (c) 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.
+
+#include "net/base/net_util.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <iphlpapi.h>
+#include <winsock2.h>
+#pragma comment(lib, "iphlpapi.lib")
+#elif defined(OS_POSIX)
+#include <fcntl.h>
+#if !defined(OS_ANDROID)
+#include <ifaddrs.h>
+#endif
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/i18n/file_util_icu.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/i18n/time_formatting.h"
+#include "base/json/string_escape.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_offset_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/sys_byteorder.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "grit/net_resources.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
+#include "url/url_canon_ip.h"
+#include "url/url_parse.h"
+#if defined(OS_ANDROID)
+#include "net/android/network_library.h"
+#endif
+#include "net/base/dns_util.h"
+#include "net/base/escape.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_module.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
+#include "net/http/http_content_disposition.h"
+#include "third_party/icu/source/common/unicode/uidna.h"
+#include "third_party/icu/source/common/unicode/uniset.h"
+#include "third_party/icu/source/common/unicode/uscript.h"
+#include "third_party/icu/source/common/unicode/uset.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+#include "third_party/icu/source/i18n/unicode/ulocdata.h"
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+// what we prepend to get a file URL
+static const base::FilePath::CharType kFileURLPrefix[] =
+ FILE_PATH_LITERAL("file:///");
+
+// The general list of blocked ports. Will be blocked unless a specific
+// protocol overrides it. (Ex: ftp can use ports 20 and 21)
+static const int kRestrictedPorts[] = {
+ 1, // tcpmux
+ 7, // echo
+ 9, // discard
+ 11, // systat
+ 13, // daytime
+ 15, // netstat
+ 17, // qotd
+ 19, // chargen
+ 20, // ftp data
+ 21, // ftp access
+ 22, // ssh
+ 23, // telnet
+ 25, // smtp
+ 37, // time
+ 42, // name
+ 43, // nicname
+ 53, // domain
+ 77, // priv-rjs
+ 79, // finger
+ 87, // ttylink
+ 95, // supdup
+ 101, // hostriame
+ 102, // iso-tsap
+ 103, // gppitnp
+ 104, // acr-nema
+ 109, // pop2
+ 110, // pop3
+ 111, // sunrpc
+ 113, // auth
+ 115, // sftp
+ 117, // uucp-path
+ 119, // nntp
+ 123, // NTP
+ 135, // loc-srv /epmap
+ 139, // netbios
+ 143, // imap2
+ 179, // BGP
+ 389, // ldap
+ 465, // smtp+ssl
+ 512, // print / exec
+ 513, // login
+ 514, // shell
+ 515, // printer
+ 526, // tempo
+ 530, // courier
+ 531, // chat
+ 532, // netnews
+ 540, // uucp
+ 556, // remotefs
+ 563, // nntp+ssl
+ 587, // stmp?
+ 601, // ??
+ 636, // ldap+ssl
+ 993, // ldap+ssl
+ 995, // pop3+ssl
+ 2049, // nfs
+ 3659, // apple-sasl / PasswordServer
+ 4045, // lockd
+ 6000, // X11
+ 6665, // Alternate IRC [Apple addition]
+ 6666, // Alternate IRC [Apple addition]
+ 6667, // Standard IRC [Apple addition]
+ 6668, // Alternate IRC [Apple addition]
+ 6669, // Alternate IRC [Apple addition]
+ 0xFFFF, // Used to block all invalid port numbers (see
+ // third_party/WebKit/Source/WebCore/platform/KURLGoogle.cpp, port())
+};
+
+// FTP overrides the following restricted ports.
+static const int kAllowedFtpPorts[] = {
+ 21, // ftp data
+ 22, // ssh
+};
+
+// Does some simple normalization of scripts so we can allow certain scripts
+// to exist together.
+// TODO(brettw) bug 880223: we should allow some other languages to be
+// oombined such as Chinese and Latin. We will probably need a more
+// complicated system of language pairs to have more fine-grained control.
+UScriptCode NormalizeScript(UScriptCode code) {
+ switch (code) {
+ case USCRIPT_KATAKANA:
+ case USCRIPT_HIRAGANA:
+ case USCRIPT_KATAKANA_OR_HIRAGANA:
+ case USCRIPT_HANGUL: // This one is arguable.
+ return USCRIPT_HAN;
+ default:
+ return code;
+ }
+}
+
+bool IsIDNComponentInSingleScript(const base::char16* str, int str_len) {
+ UScriptCode first_script = USCRIPT_INVALID_CODE;
+ bool is_first = true;
+
+ int i = 0;
+ while (i < str_len) {
+ unsigned code_point;
+ U16_NEXT(str, i, str_len, code_point);
+
+ UErrorCode err = U_ZERO_ERROR;
+ UScriptCode cur_script = uscript_getScript(code_point, &err);
+ if (err != U_ZERO_ERROR)
+ return false; // Report mixed on error.
+ cur_script = NormalizeScript(cur_script);
+
+ // TODO(brettw) We may have to check for USCRIPT_INHERENT as well.
+ if (is_first && cur_script != USCRIPT_COMMON) {
+ first_script = cur_script;
+ is_first = false;
+ } else {
+ if (cur_script != USCRIPT_COMMON && cur_script != first_script)
+ return false;
+ }
+ }
+ return true;
+}
+
+// Check if the script of a language can be 'safely' mixed with
+// Latin letters in the ASCII range.
+bool IsCompatibleWithASCIILetters(const std::string& lang) {
+ // For now, just list Chinese, Japanese and Korean (positive list).
+ // An alternative is negative-listing (languages using Greek and
+ // Cyrillic letters), but it can be more dangerous.
+ return !lang.substr(0, 2).compare("zh") ||
+ !lang.substr(0, 2).compare("ja") ||
+ !lang.substr(0, 2).compare("ko");
+}
+
+typedef std::map<std::string, icu::UnicodeSet*> LangToExemplarSetMap;
+
+class LangToExemplarSet {
+ public:
+ static LangToExemplarSet* GetInstance() {
+ return Singleton<LangToExemplarSet>::get();
+ }
+
+ private:
+ LangToExemplarSetMap map;
+ LangToExemplarSet() { }
+ ~LangToExemplarSet() {
+ STLDeleteContainerPairSecondPointers(map.begin(), map.end());
+ }
+
+ friend class Singleton<LangToExemplarSet>;
+ friend struct DefaultSingletonTraits<LangToExemplarSet>;
+ friend bool GetExemplarSetForLang(const std::string&, icu::UnicodeSet**);
+ friend void SetExemplarSetForLang(const std::string&, icu::UnicodeSet*);
+
+ DISALLOW_COPY_AND_ASSIGN(LangToExemplarSet);
+};
+
+bool GetExemplarSetForLang(const std::string& lang,
+ icu::UnicodeSet** lang_set) {
+ const LangToExemplarSetMap& map = LangToExemplarSet::GetInstance()->map;
+ LangToExemplarSetMap::const_iterator pos = map.find(lang);
+ if (pos != map.end()) {
+ *lang_set = pos->second;
+ return true;
+ }
+ return false;
+}
+
+void SetExemplarSetForLang(const std::string& lang,
+ icu::UnicodeSet* lang_set) {
+ LangToExemplarSetMap& map = LangToExemplarSet::GetInstance()->map;
+ map.insert(std::make_pair(lang, lang_set));
+}
+
+static base::LazyInstance<base::Lock>::Leaky
+ g_lang_set_lock = LAZY_INSTANCE_INITIALIZER;
+
+// Returns true if all the characters in component_characters are used by
+// the language |lang|.
+bool IsComponentCoveredByLang(const icu::UnicodeSet& component_characters,
+ const std::string& lang) {
+ CR_DEFINE_STATIC_LOCAL(
+ const icu::UnicodeSet, kASCIILetters, ('a', 'z'));
+ icu::UnicodeSet* lang_set = NULL;
+ // We're called from both the UI thread and the history thread.
+ {
+ base::AutoLock lock(g_lang_set_lock.Get());
+ if (!GetExemplarSetForLang(lang, &lang_set)) {
+ UErrorCode status = U_ZERO_ERROR;
+ ULocaleData* uld = ulocdata_open(lang.c_str(), &status);
+ // TODO(jungshik) Turn this check on when the ICU data file is
+ // rebuilt with the minimal subset of locale data for languages
+ // to which Chrome is not localized but which we offer in the list
+ // of languages selectable for Accept-Languages. With the rebuilt ICU
+ // data, ulocdata_open never should fall back to the default locale.
+ // (issue 2078)
+ // DCHECK(U_SUCCESS(status) && status != U_USING_DEFAULT_WARNING);
+ if (U_SUCCESS(status) && status != U_USING_DEFAULT_WARNING) {
+ lang_set = reinterpret_cast<icu::UnicodeSet *>(
+ ulocdata_getExemplarSet(uld, NULL, 0,
+ ULOCDATA_ES_STANDARD, &status));
+ // If |lang| is compatible with ASCII Latin letters, add them.
+ if (IsCompatibleWithASCIILetters(lang))
+ lang_set->addAll(kASCIILetters);
+ } else {
+ lang_set = new icu::UnicodeSet(1, 0);
+ }
+ lang_set->freeze();
+ SetExemplarSetForLang(lang, lang_set);
+ ulocdata_close(uld);
+ }
+ }
+ return !lang_set->isEmpty() && lang_set->containsAll(component_characters);
+}
+
+// Returns true if the given Unicode host component is safe to display to the
+// user.
+bool IsIDNComponentSafe(const base::char16* str,
+ int str_len,
+ const std::string& languages) {
+ // Most common cases (non-IDN) do not reach here so that we don't
+ // need a fast return path.
+ // TODO(jungshik) : Check if there's any character inappropriate
+ // (although allowed) for domain names.
+ // See http://www.unicode.org/reports/tr39/#IDN_Security_Profiles and
+ // http://www.unicode.org/reports/tr39/data/xidmodifications.txt
+ // For now, we borrow the list from Mozilla and tweaked it slightly.
+ // (e.g. Characters like U+00A0, U+3000, U+3002 are omitted because
+ // they're gonna be canonicalized to U+0020 and full stop before
+ // reaching here.)
+ // The original list is available at
+ // http://kb.mozillazine.org/Network.IDN.blacklist_chars and
+ // at http://mxr.mozilla.org/seamonkey/source/modules/libpref/src/init/all.js#703
+
+ UErrorCode status = U_ZERO_ERROR;
+#ifdef U_WCHAR_IS_UTF16
+ icu::UnicodeSet dangerous_characters(icu::UnicodeString(
+ L"[[\\ \u00bc\u00bd\u01c3\u0337\u0338"
+ L"\u05c3\u05f4\u06d4\u0702\u115f\u1160][\u2000-\u200b]"
+ L"[\u2024\u2027\u2028\u2029\u2039\u203a\u2044\u205f]"
+ L"[\u2154-\u2156][\u2159-\u215b][\u215f\u2215\u23ae"
+ L"\u29f6\u29f8\u2afb\u2afd][\u2ff0-\u2ffb][\u3014"
+ L"\u3015\u3033\u3164\u321d\u321e\u33ae\u33af\u33c6\u33df\ufe14"
+ L"\ufe15\ufe3f\ufe5d\ufe5e\ufeff\uff0e\uff06\uff61\uffa0\ufff9]"
+ L"[\ufffa-\ufffd]]"), status);
+ DCHECK(U_SUCCESS(status));
+ icu::RegexMatcher dangerous_patterns(icu::UnicodeString(
+ // Lone katakana no, so, or n
+ L"[^\\p{Katakana}][\u30ce\u30f3\u30bd][^\\p{Katakana}]"
+ // Repeating Japanese accent characters
+ L"|[\u3099\u309a\u309b\u309c][\u3099\u309a\u309b\u309c]"),
+ 0, status);
+#else
+ icu::UnicodeSet dangerous_characters(icu::UnicodeString(
+ "[[\\u0020\\u00bc\\u00bd\\u01c3\\u0337\\u0338"
+ "\\u05c3\\u05f4\\u06d4\\u0702\\u115f\\u1160][\\u2000-\\u200b]"
+ "[\\u2024\\u2027\\u2028\\u2029\\u2039\\u203a\\u2044\\u205f]"
+ "[\\u2154-\\u2156][\\u2159-\\u215b][\\u215f\\u2215\\u23ae"
+ "\\u29f6\\u29f8\\u2afb\\u2afd][\\u2ff0-\\u2ffb][\\u3014"
+ "\\u3015\\u3033\\u3164\\u321d\\u321e\\u33ae\\u33af\\u33c6\\u33df\\ufe14"
+ "\\ufe15\\ufe3f\\ufe5d\\ufe5e\\ufeff\\uff0e\\uff06\\uff61\\uffa0\\ufff9]"
+ "[\\ufffa-\\ufffd]]", -1, US_INV), status);
+ DCHECK(U_SUCCESS(status));
+ icu::RegexMatcher dangerous_patterns(icu::UnicodeString(
+ // Lone katakana no, so, or n
+ "[^\\p{Katakana}][\\u30ce\\u30f3\u30bd][^\\p{Katakana}]"
+ // Repeating Japanese accent characters
+ "|[\\u3099\\u309a\\u309b\\u309c][\\u3099\\u309a\\u309b\\u309c]"),
+ 0, status);
+#endif
+ DCHECK(U_SUCCESS(status));
+ icu::UnicodeSet component_characters;
+ icu::UnicodeString component_string(str, str_len);
+ component_characters.addAll(component_string);
+ if (dangerous_characters.containsSome(component_characters))
+ return false;
+
+ DCHECK(U_SUCCESS(status));
+ dangerous_patterns.reset(component_string);
+ if (dangerous_patterns.find())
+ return false;
+
+ // If the language list is empty, the result is completely determined
+ // by whether a component is a single script or not. This will block
+ // even "safe" script mixing cases like <Chinese, Latin-ASCII> that are
+ // allowed with |languages| (while it blocks Chinese + Latin letters with
+ // an accent as should be the case), but we want to err on the safe side
+ // when |languages| is empty.
+ if (languages.empty())
+ return IsIDNComponentInSingleScript(str, str_len);
+
+ // |common_characters| is made up of ASCII numbers, hyphen, plus and
+ // underscore that are used across scripts and allowed in domain names.
+ // (sync'd with characters allowed in url_canon_host with square
+ // brackets excluded.) See kHostCharLookup[] array in url_canon_host.cc.
+ icu::UnicodeSet common_characters(UNICODE_STRING_SIMPLE("[[0-9]\\-_+\\ ]"),
+ status);
+ DCHECK(U_SUCCESS(status));
+ // Subtract common characters because they're always allowed so that
+ // we just have to check if a language-specific set contains
+ // the remainder.
+ component_characters.removeAll(common_characters);
+
+ base::StringTokenizer t(languages, ",");
+ while (t.GetNext()) {
+ if (IsComponentCoveredByLang(component_characters, t.token()))
+ return true;
+ }
+ return false;
+}
+
+// Converts one component of a host (between dots) to IDN if safe. The result
+// will be APPENDED to the given output string and will be the same as the input
+// if it is not IDN or the IDN is unsafe to display. Returns whether any
+// conversion was performed.
+bool IDNToUnicodeOneComponent(const base::char16* comp,
+ size_t comp_len,
+ const std::string& languages,
+ base::string16* out) {
+ DCHECK(out);
+ if (comp_len == 0)
+ return false;
+
+ // Only transform if the input can be an IDN component.
+ static const base::char16 kIdnPrefix[] = {'x', 'n', '-', '-'};
+ if ((comp_len > arraysize(kIdnPrefix)) &&
+ !memcmp(comp, kIdnPrefix, arraysize(kIdnPrefix) * sizeof(base::char16))) {
+ // Repeatedly expand the output string until it's big enough. It looks like
+ // ICU will return the required size of the buffer, but that's not
+ // documented, so we'll just grow by 2x. This should be rare and is not on a
+ // critical path.
+ size_t original_length = out->length();
+ for (int extra_space = 64; ; extra_space *= 2) {
+ UErrorCode status = U_ZERO_ERROR;
+ out->resize(out->length() + extra_space);
+ int output_chars = uidna_IDNToUnicode(comp,
+ static_cast<int32_t>(comp_len), &(*out)[original_length], extra_space,
+ UIDNA_DEFAULT, NULL, &status);
+ if (status == U_ZERO_ERROR) {
+ // Converted successfully.
+ out->resize(original_length + output_chars);
+ if (IsIDNComponentSafe(out->data() + original_length, output_chars,
+ languages))
+ return true;
+ }
+
+ if (status != U_BUFFER_OVERFLOW_ERROR)
+ break;
+ }
+ // Failed, revert back to original string.
+ out->resize(original_length);
+ }
+
+ // We get here with no IDN or on error, in which case we just append the
+ // literal input.
+ out->append(comp, comp_len);
+ return false;
+}
+
+// Clamps the offsets in |offsets_for_adjustment| to the length of |str|.
+void LimitOffsets(const base::string16& str,
+ std::vector<size_t>* offsets_for_adjustment) {
+ if (offsets_for_adjustment) {
+ std::for_each(offsets_for_adjustment->begin(),
+ offsets_for_adjustment->end(),
+ base::LimitOffset<base::string16>(str.length()));
+ }
+}
+
+// TODO(brettw) bug 734373: check the scripts for each host component and
+// don't un-IDN-ize if there is more than one. Alternatively, only IDN for
+// scripts that the user has installed. For now, just put the entire
+// path through IDN. Maybe this feature can be implemented in ICU itself?
+//
+// We may want to skip this step in the case of file URLs to allow unicode
+// UNC hostnames regardless of encodings.
+base::string16 IDNToUnicodeWithOffsets(
+ const std::string& host,
+ const std::string& languages,
+ std::vector<size_t>* offsets_for_adjustment) {
+ // Convert the ASCII input to a base::string16 for ICU.
+ base::string16 input16;
+ input16.reserve(host.length());
+ input16.insert(input16.end(), host.begin(), host.end());
+
+ // Do each component of the host separately, since we enforce script matching
+ // on a per-component basis.
+ base::string16 out16;
+ {
+ base::OffsetAdjuster offset_adjuster(offsets_for_adjustment);
+ for (size_t component_start = 0, component_end;
+ component_start < input16.length();
+ component_start = component_end + 1) {
+ // Find the end of the component.
+ component_end = input16.find('.', component_start);
+ if (component_end == base::string16::npos)
+ component_end = input16.length(); // For getting the last component.
+ size_t component_length = component_end - component_start;
+ size_t new_component_start = out16.length();
+ bool converted_idn = false;
+ if (component_end > component_start) {
+ // Add the substring that we just found.
+ converted_idn = IDNToUnicodeOneComponent(
+ input16.data() + component_start, component_length, languages,
+ &out16);
+ }
+ size_t new_component_length = out16.length() - new_component_start;
+
+ if (converted_idn && offsets_for_adjustment) {
+ offset_adjuster.Add(base::OffsetAdjuster::Adjustment(component_start,
+ component_length, new_component_length));
+ }
+
+ // Need to add the dot we just found (if we found one).
+ if (component_end < input16.length())
+ out16.push_back('.');
+ }
+ }
+
+ LimitOffsets(out16, offsets_for_adjustment);
+ return out16;
+}
+
+// Transforms |original_offsets| by subtracting |component_begin| from all
+// offsets. Any offset which was not at least this large to begin with is set
+// to std::string::npos.
+std::vector<size_t> OffsetsIntoComponent(
+ const std::vector<size_t>& original_offsets,
+ size_t component_begin) {
+ DCHECK_NE(std::string::npos, component_begin);
+ std::vector<size_t> offsets_into_component(original_offsets);
+ for (std::vector<size_t>::iterator i(offsets_into_component.begin());
+ i != offsets_into_component.end(); ++i) {
+ if (*i != std::string::npos)
+ *i = (*i < component_begin) ? std::string::npos : (*i - component_begin);
+ }
+ return offsets_into_component;
+}
+
+// Called after we transform a component and append it to an output string.
+// Maps |transformed_offsets|, which represent offsets into the transformed
+// component itself, into appropriate offsets for the output string, by adding
+// |output_component_begin| to each. Determines which offsets need mapping by
+// checking to see which of the |original_offsets| were within the designated
+// original component, using its provided endpoints.
+void AdjustForComponentTransform(
+ const std::vector<size_t>& original_offsets,
+ size_t original_component_begin,
+ size_t original_component_end,
+ const std::vector<size_t>& transformed_offsets,
+ size_t output_component_begin,
+ std::vector<size_t>* offsets_for_adjustment) {
+ if (!offsets_for_adjustment)
+ return;
+
+ DCHECK_NE(std::string::npos, original_component_begin);
+ DCHECK_NE(std::string::npos, original_component_end);
+ DCHECK_NE(base::string16::npos, output_component_begin);
+ size_t offsets_size = offsets_for_adjustment->size();
+ DCHECK_EQ(offsets_size, original_offsets.size());
+ DCHECK_EQ(offsets_size, transformed_offsets.size());
+ for (size_t i = 0; i < offsets_size; ++i) {
+ size_t original_offset = original_offsets[i];
+ if ((original_offset >= original_component_begin) &&
+ (original_offset < original_component_end)) {
+ size_t transformed_offset = transformed_offsets[i];
+ (*offsets_for_adjustment)[i] =
+ (transformed_offset == base::string16::npos) ?
+ base::string16::npos : (output_component_begin + transformed_offset);
+ }
+ }
+}
+
+// If |component| is valid, its begin is incremented by |delta|.
+void AdjustComponent(int delta, url_parse::Component* component) {
+ if (!component->is_valid())
+ return;
+
+ DCHECK(delta >= 0 || component->begin >= -delta);
+ component->begin += delta;
+}
+
+// Adjusts all the components of |parsed| by |delta|, except for the scheme.
+void AdjustComponents(int delta, url_parse::Parsed* parsed) {
+ AdjustComponent(delta, &(parsed->username));
+ AdjustComponent(delta, &(parsed->password));
+ AdjustComponent(delta, &(parsed->host));
+ AdjustComponent(delta, &(parsed->port));
+ AdjustComponent(delta, &(parsed->path));
+ AdjustComponent(delta, &(parsed->query));
+ AdjustComponent(delta, &(parsed->ref));
+}
+
+// Helper for FormatUrlWithOffsets().
+base::string16 FormatViewSourceUrl(
+ const GURL& url,
+ const std::vector<size_t>& original_offsets,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ url_parse::Parsed* new_parsed,
+ size_t* prefix_end,
+ std::vector<size_t>* offsets_for_adjustment) {
+ DCHECK(new_parsed);
+ const char kViewSource[] = "view-source:";
+ const size_t kViewSourceLength = arraysize(kViewSource) - 1;
+ std::vector<size_t> offsets_into_url(
+ OffsetsIntoComponent(original_offsets, kViewSourceLength));
+
+ GURL real_url(url.possibly_invalid_spec().substr(kViewSourceLength));
+ base::string16 result(ASCIIToUTF16(kViewSource) +
+ FormatUrlWithOffsets(real_url, languages, format_types, unescape_rules,
+ new_parsed, prefix_end, &offsets_into_url));
+
+ // Adjust position values.
+ if (new_parsed->scheme.is_nonempty()) {
+ // Assume "view-source:real-scheme" as a scheme.
+ new_parsed->scheme.len += kViewSourceLength;
+ } else {
+ new_parsed->scheme.begin = 0;
+ new_parsed->scheme.len = kViewSourceLength - 1;
+ }
+ AdjustComponents(kViewSourceLength, new_parsed);
+ if (prefix_end)
+ *prefix_end += kViewSourceLength;
+ AdjustForComponentTransform(original_offsets, kViewSourceLength,
+ url.possibly_invalid_spec().length(), offsets_into_url, kViewSourceLength,
+ offsets_for_adjustment);
+ LimitOffsets(result, offsets_for_adjustment);
+ return result;
+}
+
+class AppendComponentTransform {
+ public:
+ AppendComponentTransform() {}
+ virtual ~AppendComponentTransform() {}
+
+ virtual base::string16 Execute(
+ const std::string& component_text,
+ std::vector<size_t>* offsets_into_component) const = 0;
+
+ // NOTE: No DISALLOW_COPY_AND_ASSIGN here, since gcc < 4.3.0 requires an
+ // accessible copy constructor in order to call AppendFormattedComponent()
+ // with an inline temporary (see http://gcc.gnu.org/bugs/#cxx%5Frvalbind ).
+};
+
+class HostComponentTransform : public AppendComponentTransform {
+ public:
+ explicit HostComponentTransform(const std::string& languages)
+ : languages_(languages) {
+ }
+
+ private:
+ virtual base::string16 Execute(
+ const std::string& component_text,
+ std::vector<size_t>* offsets_into_component) const OVERRIDE {
+ return IDNToUnicodeWithOffsets(component_text, languages_,
+ offsets_into_component);
+ }
+
+ const std::string& languages_;
+};
+
+class NonHostComponentTransform : public AppendComponentTransform {
+ public:
+ explicit NonHostComponentTransform(UnescapeRule::Type unescape_rules)
+ : unescape_rules_(unescape_rules) {
+ }
+
+ private:
+ virtual base::string16 Execute(
+ const std::string& component_text,
+ std::vector<size_t>* offsets_into_component) const OVERRIDE {
+ return (unescape_rules_ == UnescapeRule::NONE) ?
+ base::UTF8ToUTF16AndAdjustOffsets(component_text,
+ offsets_into_component) :
+ UnescapeAndDecodeUTF8URLComponentWithOffsets(component_text,
+ unescape_rules_, offsets_into_component);
+ }
+
+ const UnescapeRule::Type unescape_rules_;
+};
+
+void AppendFormattedComponent(const std::string& spec,
+ const url_parse::Component& original_component,
+ const std::vector<size_t>& original_offsets,
+ const AppendComponentTransform& transform,
+ base::string16* output,
+ url_parse::Component* output_component,
+ std::vector<size_t>* offsets_for_adjustment) {
+ DCHECK(output);
+ if (original_component.is_nonempty()) {
+ size_t original_component_begin =
+ static_cast<size_t>(original_component.begin);
+ size_t output_component_begin = output->length();
+ if (output_component)
+ output_component->begin = static_cast<int>(output_component_begin);
+
+ std::vector<size_t> offsets_into_component =
+ OffsetsIntoComponent(original_offsets, original_component_begin);
+ output->append(transform.Execute(std::string(spec, original_component_begin,
+ static_cast<size_t>(original_component.len)), &offsets_into_component));
+
+ if (output_component) {
+ output_component->len =
+ static_cast<int>(output->length() - output_component_begin);
+ }
+ AdjustForComponentTransform(original_offsets, original_component_begin,
+ static_cast<size_t>(original_component.end()),
+ offsets_into_component, output_component_begin,
+ offsets_for_adjustment);
+ } else if (output_component) {
+ output_component->reset();
+ }
+}
+
+void SanitizeGeneratedFileName(base::FilePath::StringType* filename,
+ bool replace_trailing) {
+ const base::FilePath::CharType kReplace[] = FILE_PATH_LITERAL("-");
+ if (filename->empty())
+ return;
+ if (replace_trailing) {
+ // Handle CreateFile() stripping trailing dots and spaces on filenames
+ // http://support.microsoft.com/kb/115827
+ size_t length = filename->size();
+ size_t pos = filename->find_last_not_of(FILE_PATH_LITERAL(" ."));
+ filename->resize((pos == std::string::npos) ? 0 : (pos + 1));
+ TrimWhitespace(*filename, TRIM_TRAILING, filename);
+ if (filename->empty())
+ return;
+ size_t trimmed = length - filename->size();
+ if (trimmed)
+ filename->insert(filename->end(), trimmed, kReplace[0]);
+ }
+ TrimString(*filename, FILE_PATH_LITERAL("."), filename);
+ if (filename->empty())
+ return;
+ // Replace any path information by changing path separators.
+ ReplaceSubstringsAfterOffset(filename, 0, FILE_PATH_LITERAL("/"), kReplace);
+ ReplaceSubstringsAfterOffset(filename, 0, FILE_PATH_LITERAL("\\"), kReplace);
+}
+
+// Returns the filename determined from the last component of the path portion
+// of the URL. Returns an empty string if the URL doesn't have a path or is
+// invalid. If the generated filename is not reliable,
+// |should_overwrite_extension| will be set to true, in which case a better
+// extension should be determined based on the content type.
+std::string GetFileNameFromURL(const GURL& url,
+ const std::string& referrer_charset,
+ bool* should_overwrite_extension) {
+ // about: and data: URLs don't have file names, but esp. data: URLs may
+ // contain parts that look like ones (i.e., contain a slash). Therefore we
+ // don't attempt to divine a file name out of them.
+ if (!url.is_valid() || url.SchemeIs("about") || url.SchemeIs("data"))
+ return std::string();
+
+ const std::string unescaped_url_filename = UnescapeURLComponent(
+ url.ExtractFileName(),
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
+
+ // The URL's path should be escaped UTF-8, but may not be.
+ std::string decoded_filename = unescaped_url_filename;
+ if (!IsStringUTF8(decoded_filename)) {
+ // TODO(jshin): this is probably not robust enough. To be sure, we need
+ // encoding detection.
+ base::string16 utf16_output;
+ if (!referrer_charset.empty() &&
+ base::CodepageToUTF16(unescaped_url_filename,
+ referrer_charset.c_str(),
+ base::OnStringConversionError::FAIL,
+ &utf16_output)) {
+ decoded_filename = UTF16ToUTF8(utf16_output);
+ } else {
+ decoded_filename = WideToUTF8(
+ base::SysNativeMBToWide(unescaped_url_filename));
+ }
+ }
+ // If the URL contains a (possibly empty) query, assume it is a generator, and
+ // allow the determined extension to be overwritten.
+ *should_overwrite_extension = !decoded_filename.empty() && url.has_query();
+
+ return decoded_filename;
+}
+
+// Returns whether the specified extension is automatically integrated into the
+// windows shell.
+bool IsShellIntegratedExtension(const base::FilePath::StringType& extension) {
+ base::FilePath::StringType extension_lower = StringToLowerASCII(extension);
+
+ // http://msdn.microsoft.com/en-us/library/ms811694.aspx
+ // Right-clicking on shortcuts can be magical.
+ if ((extension_lower == FILE_PATH_LITERAL("local")) ||
+ (extension_lower == FILE_PATH_LITERAL("lnk")))
+ return true;
+
+ // http://www.juniper.net/security/auto/vulnerabilities/vuln2612.html
+ // Files become magical if they end in a CLSID, so block such extensions.
+ if (!extension_lower.empty() &&
+ (extension_lower[0] == FILE_PATH_LITERAL('{')) &&
+ (extension_lower[extension_lower.length() - 1] == FILE_PATH_LITERAL('}')))
+ return true;
+ return false;
+}
+
+// Returns whether the specified file name is a reserved name on windows.
+// This includes names like "com2.zip" (which correspond to devices) and
+// desktop.ini and thumbs.db which have special meaning to the windows shell.
+bool IsReservedName(const base::FilePath::StringType& filename) {
+ // This list is taken from the MSDN article "Naming a file"
+ // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx
+ // I also added clock$ because GetSaveFileName seems to consider it as a
+ // reserved name too.
+ static const char* const known_devices[] = {
+ "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5",
+ "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
+ "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "clock$"
+ };
+#if defined(OS_WIN)
+ std::string filename_lower = StringToLowerASCII(WideToUTF8(filename));
+#elif defined(OS_POSIX)
+ std::string filename_lower = StringToLowerASCII(filename);
+#endif
+
+ for (size_t i = 0; i < arraysize(known_devices); ++i) {
+ // Exact match.
+ if (filename_lower == known_devices[i])
+ return true;
+ // Starts with "DEVICE.".
+ if (filename_lower.find(std::string(known_devices[i]) + ".") == 0)
+ return true;
+ }
+
+ static const char* const magic_names[] = {
+ // These file names are used by the "Customize folder" feature of the shell.
+ "desktop.ini",
+ "thumbs.db",
+ };
+
+ for (size_t i = 0; i < arraysize(magic_names); ++i) {
+ if (filename_lower == magic_names[i])
+ return true;
+ }
+
+ return false;
+}
+
+// Examines the current extension in |file_name| and modifies it if necessary in
+// order to ensure the filename is safe. If |file_name| doesn't contain an
+// extension or if |ignore_extension| is true, then a new extension will be
+// constructed based on the |mime_type|.
+//
+// We're addressing two things here:
+//
+// 1) Usability. If there is no reliable file extension, we want to guess a
+// reasonable file extension based on the content type.
+//
+// 2) Shell integration. Some file extensions automatically integrate with the
+// shell. We block these extensions to prevent a malicious web site from
+// integrating with the user's shell.
+void EnsureSafeExtension(const std::string& mime_type,
+ bool ignore_extension,
+ base::FilePath* file_name) {
+ // See if our file name already contains an extension.
+ base::FilePath::StringType extension = file_name->Extension();
+ if (!extension.empty())
+ extension.erase(extension.begin()); // Erase preceding '.'.
+
+ if ((ignore_extension || extension.empty()) && !mime_type.empty()) {
+ base::FilePath::StringType preferred_mime_extension;
+ std::vector<base::FilePath::StringType> all_mime_extensions;
+ // The GetPreferredExtensionForMimeType call will end up going to disk. Do
+ // this on another thread to avoid slowing the IO thread.
+ // http://crbug.com/61827
+ // TODO(asanka): Remove this ScopedAllowIO once all callers have switched
+ // over to IO safe threads.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ net::GetPreferredExtensionForMimeType(mime_type, &preferred_mime_extension);
+ net::GetExtensionsForMimeType(mime_type, &all_mime_extensions);
+ // If the existing extension is in the list of valid extensions for the
+ // given type, use it. This avoids doing things like pointlessly renaming
+ // "foo.jpg" to "foo.jpeg".
+ if (std::find(all_mime_extensions.begin(),
+ all_mime_extensions.end(),
+ extension) != all_mime_extensions.end()) {
+ // leave |extension| alone
+ } else if (!preferred_mime_extension.empty()) {
+ extension = preferred_mime_extension;
+ }
+ }
+
+#if defined(OS_WIN)
+ static const base::FilePath::CharType default_extension[] =
+ FILE_PATH_LITERAL("download");
+
+ // Rename shell-integrated extensions.
+ // TODO(asanka): Consider stripping out the bad extension and replacing it
+ // with the preferred extension for the MIME type if one is available.
+ if (IsShellIntegratedExtension(extension))
+ extension.assign(default_extension);
+#endif
+
+ *file_name = file_name->ReplaceExtension(extension);
+}
+
+bool FilePathToString16(const base::FilePath& path, base::string16* converted) {
+#if defined(OS_WIN)
+ return WideToUTF16(path.value().c_str(), path.value().size(), converted);
+#elif defined(OS_POSIX)
+ std::string component8 = path.AsUTF8Unsafe();
+ return !component8.empty() &&
+ UTF8ToUTF16(component8.c_str(), component8.size(), converted);
+#endif
+}
+
+} // namespace
+
+const FormatUrlType kFormatUrlOmitNothing = 0;
+const FormatUrlType kFormatUrlOmitUsernamePassword = 1 << 0;
+const FormatUrlType kFormatUrlOmitHTTP = 1 << 1;
+const FormatUrlType kFormatUrlOmitTrailingSlashOnBareHostname = 1 << 2;
+const FormatUrlType kFormatUrlOmitAll = kFormatUrlOmitUsernamePassword |
+ kFormatUrlOmitHTTP | kFormatUrlOmitTrailingSlashOnBareHostname;
+
+static base::LazyInstance<std::multiset<int> >::Leaky
+ g_explicitly_allowed_ports = LAZY_INSTANCE_INITIALIZER;
+
+size_t GetCountOfExplicitlyAllowedPorts() {
+ return g_explicitly_allowed_ports.Get().size();
+}
+
+GURL FilePathToFileURL(const base::FilePath& path) {
+ // Produce a URL like "file:///C:/foo" for a regular file, or
+ // "file://///server/path" for UNC. The URL canonicalizer will fix up the
+ // latter case to be the canonical UNC form: "file://server/path"
+ base::FilePath::StringType url_string(kFileURLPrefix);
+ url_string.append(path.value());
+
+ // Now do replacement of some characters. Since we assume the input is a
+ // literal filename, anything the URL parser might consider special should
+ // be escaped here.
+
+ // must be the first substitution since others will introduce percents as the
+ // escape character
+ ReplaceSubstringsAfterOffset(&url_string, 0,
+ FILE_PATH_LITERAL("%"), FILE_PATH_LITERAL("%25"));
+
+ // semicolon is supposed to be some kind of separator according to RFC 2396
+ ReplaceSubstringsAfterOffset(&url_string, 0,
+ FILE_PATH_LITERAL(";"), FILE_PATH_LITERAL("%3B"));
+
+ ReplaceSubstringsAfterOffset(&url_string, 0,
+ FILE_PATH_LITERAL("#"), FILE_PATH_LITERAL("%23"));
+
+ ReplaceSubstringsAfterOffset(&url_string, 0,
+ FILE_PATH_LITERAL("?"), FILE_PATH_LITERAL("%3F"));
+
+#if defined(OS_POSIX)
+ ReplaceSubstringsAfterOffset(&url_string, 0,
+ FILE_PATH_LITERAL("\\"), FILE_PATH_LITERAL("%5C"));
+#endif
+
+ return GURL(url_string);
+}
+
+std::string GetSpecificHeader(const std::string& headers,
+ const std::string& name) {
+ // We want to grab the Value from the "Key: Value" pairs in the headers,
+ // which should look like this (no leading spaces, \n-separated) (we format
+ // them this way in url_request_inet.cc):
+ // HTTP/1.1 200 OK\n
+ // ETag: "6d0b8-947-24f35ec0"\n
+ // Content-Length: 2375\n
+ // Content-Type: text/html; charset=UTF-8\n
+ // Last-Modified: Sun, 03 Sep 2006 04:34:43 GMT\n
+ if (headers.empty())
+ return std::string();
+
+ std::string match('\n' + name + ':');
+
+ std::string::const_iterator begin =
+ std::search(headers.begin(), headers.end(), match.begin(), match.end(),
+ base::CaseInsensitiveCompareASCII<char>());
+
+ if (begin == headers.end())
+ return std::string();
+
+ begin += match.length();
+
+ std::string ret;
+ TrimWhitespace(std::string(begin, std::find(begin, headers.end(), '\n')),
+ TRIM_ALL, &ret);
+ return ret;
+}
+
+base::string16 IDNToUnicode(const std::string& host,
+ const std::string& languages) {
+ return IDNToUnicodeWithOffsets(host, languages, NULL);
+}
+
+std::string CanonicalizeHost(const std::string& host,
+ url_canon::CanonHostInfo* host_info) {
+ // Try to canonicalize the host.
+ const url_parse::Component raw_host_component(
+ 0, static_cast<int>(host.length()));
+ std::string canon_host;
+ url_canon::StdStringCanonOutput canon_host_output(&canon_host);
+ url_canon::CanonicalizeHostVerbose(host.c_str(), raw_host_component,
+ &canon_host_output, host_info);
+
+ if (host_info->out_host.is_nonempty() &&
+ host_info->family != url_canon::CanonHostInfo::BROKEN) {
+ // Success! Assert that there's no extra garbage.
+ canon_host_output.Complete();
+ DCHECK_EQ(host_info->out_host.len, static_cast<int>(canon_host.length()));
+ } else {
+ // Empty host, or canonicalization failed. We'll return empty.
+ canon_host.clear();
+ }
+
+ return canon_host;
+}
+
+std::string GetDirectoryListingHeader(const base::string16& title) {
+ static const base::StringPiece header(
+ NetModule::GetResource(IDR_DIR_HEADER_HTML));
+ // This can be null in unit tests.
+ DLOG_IF(WARNING, header.empty()) <<
+ "Missing resource: directory listing header";
+
+ std::string result;
+ if (!header.empty())
+ result.assign(header.data(), header.size());
+
+ result.append("<script>start(");
+ base::JsonDoubleQuote(title, true, &result);
+ result.append(");</script>\n");
+
+ return result;
+}
+
+inline bool IsHostCharAlphanumeric(char c) {
+ // We can just check lowercase because uppercase characters have already been
+ // normalized.
+ return ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9'));
+}
+
+bool IsCanonicalizedHostCompliant(const std::string& host,
+ const std::string& desired_tld) {
+ if (host.empty())
+ return false;
+
+ bool in_component = false;
+ bool most_recent_component_started_alphanumeric = false;
+ bool last_char_was_underscore = false;
+
+ for (std::string::const_iterator i(host.begin()); i != host.end(); ++i) {
+ const char c = *i;
+ if (!in_component) {
+ most_recent_component_started_alphanumeric = IsHostCharAlphanumeric(c);
+ if (!most_recent_component_started_alphanumeric && (c != '-'))
+ return false;
+ in_component = true;
+ } else {
+ if (c == '.') {
+ if (last_char_was_underscore)
+ return false;
+ in_component = false;
+ } else if (IsHostCharAlphanumeric(c) || (c == '-')) {
+ last_char_was_underscore = false;
+ } else if (c == '_') {
+ last_char_was_underscore = true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return most_recent_component_started_alphanumeric ||
+ (!desired_tld.empty() && IsHostCharAlphanumeric(desired_tld[0]));
+}
+
+std::string GetDirectoryListingEntry(const base::string16& name,
+ const std::string& raw_bytes,
+ bool is_dir,
+ int64 size,
+ Time modified) {
+ std::string result;
+ result.append("<script>addRow(");
+ base::JsonDoubleQuote(name, true, &result);
+ result.append(",");
+ if (raw_bytes.empty()) {
+ base::JsonDoubleQuote(EscapePath(UTF16ToUTF8(name)),
+ true, &result);
+ } else {
+ base::JsonDoubleQuote(EscapePath(raw_bytes), true, &result);
+ }
+ if (is_dir) {
+ result.append(",1,");
+ } else {
+ result.append(",0,");
+ }
+
+ // Negative size means unknown or not applicable (e.g. directory).
+ base::string16 size_string;
+ if (size >= 0)
+ size_string = FormatBytesUnlocalized(size);
+ base::JsonDoubleQuote(size_string, true, &result);
+
+ result.append(",");
+
+ base::string16 modified_str;
+ // |modified| can be NULL in FTP listings.
+ if (!modified.is_null()) {
+ modified_str = base::TimeFormatShortDateAndTime(modified);
+ }
+ base::JsonDoubleQuote(modified_str, true, &result);
+
+ result.append(");</script>\n");
+
+ return result;
+}
+
+base::string16 StripWWW(const base::string16& text) {
+ const base::string16 www(ASCIIToUTF16("www."));
+ return StartsWith(text, www, true) ? text.substr(www.length()) : text;
+}
+
+base::string16 StripWWWFromHost(const GURL& url) {
+ DCHECK(url.is_valid());
+ return StripWWW(ASCIIToUTF16(url.host()));
+}
+
+bool IsSafePortablePathComponent(const base::FilePath& component) {
+ base::string16 component16;
+ base::FilePath::StringType sanitized = component.value();
+ SanitizeGeneratedFileName(&sanitized, true);
+ base::FilePath::StringType extension = component.Extension();
+ if (!extension.empty())
+ extension.erase(extension.begin()); // Erase preceding '.'.
+ return !component.empty() &&
+ (component == component.BaseName()) &&
+ (component == component.StripTrailingSeparators()) &&
+ FilePathToString16(component, &component16) &&
+ file_util::IsFilenameLegal(component16) &&
+ !IsShellIntegratedExtension(extension) &&
+ (sanitized == component.value());
+}
+
+bool IsSafePortableBasename(const base::FilePath& filename) {
+ return IsSafePortablePathComponent(filename) &&
+ !IsReservedName(filename.value());
+}
+
+bool IsSafePortableRelativePath(const base::FilePath& path) {
+ if (path.empty() || path.IsAbsolute() || path.EndsWithSeparator())
+ return false;
+ std::vector<base::FilePath::StringType> components;
+ path.GetComponents(&components);
+ if (components.empty())
+ return false;
+ for (size_t i = 0; i < components.size() - 1; ++i) {
+ if (!IsSafePortablePathComponent(base::FilePath(components[i])))
+ return false;
+ }
+ return IsSafePortableBasename(path.BaseName());
+}
+
+void GenerateSafeFileName(const std::string& mime_type,
+ bool ignore_extension,
+ base::FilePath* file_path) {
+ // Make sure we get the right file extension
+ EnsureSafeExtension(mime_type, ignore_extension, file_path);
+
+#if defined(OS_WIN)
+ // Prepend "_" to the file name if it's a reserved name
+ base::FilePath::StringType leaf_name = file_path->BaseName().value();
+ DCHECK(!leaf_name.empty());
+ if (IsReservedName(leaf_name)) {
+ leaf_name = base::FilePath::StringType(FILE_PATH_LITERAL("_")) + leaf_name;
+ *file_path = file_path->DirName();
+ if (file_path->value() == base::FilePath::kCurrentDirectory) {
+ *file_path = base::FilePath(leaf_name);
+ } else {
+ *file_path = file_path->Append(leaf_name);
+ }
+ }
+#endif
+}
+
+base::string16 GetSuggestedFilename(const GURL& url,
+ const std::string& content_disposition,
+ const std::string& referrer_charset,
+ const std::string& suggested_name,
+ const std::string& mime_type,
+ const std::string& default_name) {
+ // TODO: this function to be updated to match the httpbis recommendations.
+ // Talk to abarth for the latest news.
+
+ // We don't translate this fallback string, "download". If localization is
+ // needed, the caller should provide localized fallback in |default_name|.
+ static const base::FilePath::CharType kFinalFallbackName[] =
+ FILE_PATH_LITERAL("download");
+ std::string filename; // In UTF-8
+ bool overwrite_extension = false;
+
+ // Try to extract a filename from content-disposition first.
+ if (!content_disposition.empty()) {
+ HttpContentDisposition header(content_disposition, referrer_charset);
+ filename = header.filename();
+ }
+
+ // Then try to use the suggested name.
+ if (filename.empty() && !suggested_name.empty())
+ filename = suggested_name;
+
+ // Now try extracting the filename from the URL. GetFileNameFromURL() only
+ // looks at the last component of the URL and doesn't return the hostname as a
+ // failover.
+ if (filename.empty())
+ filename = GetFileNameFromURL(url, referrer_charset, &overwrite_extension);
+
+ // Finally try the URL hostname, but only if there's no default specified in
+ // |default_name|. Some schemes (e.g.: file:, about:, data:) do not have a
+ // host name.
+ if (filename.empty() &&
+ default_name.empty() &&
+ url.is_valid() &&
+ !url.host().empty()) {
+ // TODO(jungshik) : Decode a 'punycoded' IDN hostname. (bug 1264451)
+ filename = url.host();
+ }
+
+ bool replace_trailing = false;
+ base::FilePath::StringType result_str, default_name_str;
+#if defined(OS_WIN)
+ replace_trailing = true;
+ result_str = UTF8ToUTF16(filename);
+ default_name_str = UTF8ToUTF16(default_name);
+#else
+ result_str = filename;
+ default_name_str = default_name;
+#endif
+ SanitizeGeneratedFileName(&result_str, replace_trailing);
+ if (result_str.find_last_not_of(FILE_PATH_LITERAL("-_")) ==
+ base::FilePath::StringType::npos) {
+ result_str = !default_name_str.empty() ? default_name_str :
+ base::FilePath::StringType(kFinalFallbackName);
+ overwrite_extension = false;
+ }
+ file_util::ReplaceIllegalCharactersInPath(&result_str, '-');
+ base::FilePath result(result_str);
+ GenerateSafeFileName(mime_type, overwrite_extension, &result);
+
+ base::string16 result16;
+ if (!FilePathToString16(result, &result16)) {
+ result = base::FilePath(default_name_str);
+ if (!FilePathToString16(result, &result16)) {
+ result = base::FilePath(kFinalFallbackName);
+ FilePathToString16(result, &result16);
+ }
+ }
+ return result16;
+}
+
+base::FilePath GenerateFileName(const GURL& url,
+ const std::string& content_disposition,
+ const std::string& referrer_charset,
+ const std::string& suggested_name,
+ const std::string& mime_type,
+ const std::string& default_file_name) {
+ base::string16 file_name = GetSuggestedFilename(url,
+ content_disposition,
+ referrer_charset,
+ suggested_name,
+ mime_type,
+ default_file_name);
+
+#if defined(OS_WIN)
+ base::FilePath generated_name(file_name);
+#else
+ base::FilePath generated_name(
+ base::SysWideToNativeMB(UTF16ToWide(file_name)));
+#endif
+
+#if defined(OS_CHROMEOS)
+ // When doing file manager operations on ChromeOS, the file paths get
+ // normalized in WebKit layer, so let's ensure downloaded files have
+ // normalized names. Otherwise, we won't be able to handle files with NFD
+ // utf8 encoded characters in name.
+ file_util::NormalizeFileNameEncoding(&generated_name);
+#endif
+
+ DCHECK(!generated_name.empty());
+
+ return generated_name;
+}
+
+bool IsPortAllowedByDefault(int port) {
+ int array_size = arraysize(kRestrictedPorts);
+ for (int i = 0; i < array_size; i++) {
+ if (kRestrictedPorts[i] == port) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsPortAllowedByFtp(int port) {
+ int array_size = arraysize(kAllowedFtpPorts);
+ for (int i = 0; i < array_size; i++) {
+ if (kAllowedFtpPorts[i] == port) {
+ return true;
+ }
+ }
+ // Port not explicitly allowed by FTP, so return the default restrictions.
+ return IsPortAllowedByDefault(port);
+}
+
+bool IsPortAllowedByOverride(int port) {
+ if (g_explicitly_allowed_ports.Get().empty())
+ return false;
+
+ return g_explicitly_allowed_ports.Get().count(port) > 0;
+}
+
+int SetNonBlocking(int fd) {
+#if defined(OS_WIN)
+ unsigned long no_block = 1;
+ return ioctlsocket(fd, FIONBIO, &no_block);
+#elif defined(OS_POSIX)
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (-1 == flags)
+ return flags;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+bool ParseHostAndPort(std::string::const_iterator host_and_port_begin,
+ std::string::const_iterator host_and_port_end,
+ std::string* host,
+ int* port) {
+ if (host_and_port_begin >= host_and_port_end)
+ return false;
+
+ // When using url_parse, we use char*.
+ const char* auth_begin = &(*host_and_port_begin);
+ int auth_len = host_and_port_end - host_and_port_begin;
+
+ url_parse::Component auth_component(0, auth_len);
+ url_parse::Component username_component;
+ url_parse::Component password_component;
+ url_parse::Component hostname_component;
+ url_parse::Component port_component;
+
+ url_parse::ParseAuthority(auth_begin, auth_component, &username_component,
+ &password_component, &hostname_component, &port_component);
+
+ // There shouldn't be a username/password.
+ if (username_component.is_valid() || password_component.is_valid())
+ return false;
+
+ if (!hostname_component.is_nonempty())
+ return false; // Failed parsing.
+
+ int parsed_port_number = -1;
+ if (port_component.is_nonempty()) {
+ parsed_port_number = url_parse::ParsePort(auth_begin, port_component);
+
+ // If parsing failed, port_number will be either PORT_INVALID or
+ // PORT_UNSPECIFIED, both of which are negative.
+ if (parsed_port_number < 0)
+ return false; // Failed parsing the port number.
+ }
+
+ if (port_component.len == 0)
+ return false; // Reject inputs like "foo:"
+
+ // Pass results back to caller.
+ host->assign(auth_begin + hostname_component.begin, hostname_component.len);
+ *port = parsed_port_number;
+
+ return true; // Success.
+}
+
+bool ParseHostAndPort(const std::string& host_and_port,
+ std::string* host,
+ int* port) {
+ return ParseHostAndPort(
+ host_and_port.begin(), host_and_port.end(), host, port);
+}
+
+std::string GetHostAndPort(const GURL& url) {
+ // For IPv6 literals, GURL::host() already includes the brackets so it is
+ // safe to just append a colon.
+ return base::StringPrintf("%s:%d", url.host().c_str(),
+ url.EffectiveIntPort());
+}
+
+std::string GetHostAndOptionalPort(const GURL& url) {
+ // For IPv6 literals, GURL::host() already includes the brackets
+ // so it is safe to just append a colon.
+ if (url.has_port())
+ return base::StringPrintf("%s:%s", url.host().c_str(), url.port().c_str());
+ return url.host();
+}
+
+// static
+bool IsHostnameNonUnique(const std::string& hostname) {
+ // CanonicalizeHost requires surrounding brackets to parse an IPv6 address.
+ const std::string host_or_ip = hostname.find(':') != std::string::npos ?
+ "[" + hostname + "]" : hostname;
+ url_canon::CanonHostInfo host_info;
+ std::string canonical_name = CanonicalizeHost(host_or_ip, &host_info);
+
+ // If canonicalization fails, then the input is truly malformed. However,
+ // to avoid mis-reporting bad inputs as "non-unique", treat them as unique.
+ if (canonical_name.empty())
+ return false;
+
+ // If |hostname| is an IP address, presume it's unique.
+ // TODO(rsleevi): In the future, this should also reject IP addresses in
+ // IANA-reserved ranges.
+ if (host_info.IsIPAddress())
+ return false;
+
+ // Check for a registry controlled portion of |hostname|, ignoring private
+ // registries, as they already chain to ICANN-administered registries,
+ // and explicitly ignoring unknown registries.
+ //
+ // Note: This means that as new gTLDs are introduced on the Internet, they
+ // will be treated as non-unique until the registry controlled domain list
+ // is updated. However, because gTLDs are expected to provide significant
+ // advance notice to deprecate older versions of this code, this an
+ // acceptable tradeoff.
+ return 0 == registry_controlled_domains::GetRegistryLength(
+ canonical_name,
+ registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES,
+ registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+// Extracts the address and port portions of a sockaddr.
+bool GetIPAddressFromSockAddr(const struct sockaddr* sock_addr,
+ socklen_t sock_addr_len,
+ const uint8** address,
+ size_t* address_len,
+ uint16* port) {
+ if (sock_addr->sa_family == AF_INET) {
+ if (sock_addr_len < static_cast<socklen_t>(sizeof(struct sockaddr_in)))
+ return false;
+ const struct sockaddr_in* addr =
+ reinterpret_cast<const struct sockaddr_in*>(sock_addr);
+ *address = reinterpret_cast<const uint8*>(&addr->sin_addr);
+ *address_len = kIPv4AddressSize;
+ if (port)
+ *port = base::NetToHost16(addr->sin_port);
+ return true;
+ }
+
+ if (sock_addr->sa_family == AF_INET6) {
+ if (sock_addr_len < static_cast<socklen_t>(sizeof(struct sockaddr_in6)))
+ return false;
+ const struct sockaddr_in6* addr =
+ reinterpret_cast<const struct sockaddr_in6*>(sock_addr);
+ *address = reinterpret_cast<const unsigned char*>(&addr->sin6_addr);
+ *address_len = kIPv6AddressSize;
+ if (port)
+ *port = base::NetToHost16(addr->sin6_port);
+ return true;
+ }
+
+ return false; // Unrecognized |sa_family|.
+}
+
+std::string IPAddressToString(const uint8* address,
+ size_t address_len) {
+ std::string str;
+ url_canon::StdStringCanonOutput output(&str);
+
+ if (address_len == kIPv4AddressSize) {
+ url_canon::AppendIPv4Address(address, &output);
+ } else if (address_len == kIPv6AddressSize) {
+ url_canon::AppendIPv6Address(address, &output);
+ } else {
+ CHECK(false) << "Invalid IP address with length: " << address_len;
+ }
+
+ output.Complete();
+ return str;
+}
+
+std::string IPAddressToStringWithPort(const uint8* address,
+ size_t address_len,
+ uint16 port) {
+ std::string address_str = IPAddressToString(address, address_len);
+
+ if (address_len == kIPv6AddressSize) {
+ // Need to bracket IPv6 addresses since they contain colons.
+ return base::StringPrintf("[%s]:%d", address_str.c_str(), port);
+ }
+ return base::StringPrintf("%s:%d", address_str.c_str(), port);
+}
+
+std::string NetAddressToString(const struct sockaddr* sa,
+ socklen_t sock_addr_len) {
+ const uint8* address;
+ size_t address_len;
+ if (!GetIPAddressFromSockAddr(sa, sock_addr_len, &address,
+ &address_len, NULL)) {
+ NOTREACHED();
+ return std::string();
+ }
+ return IPAddressToString(address, address_len);
+}
+
+std::string NetAddressToStringWithPort(const struct sockaddr* sa,
+ socklen_t sock_addr_len) {
+ const uint8* address;
+ size_t address_len;
+ uint16 port;
+ if (!GetIPAddressFromSockAddr(sa, sock_addr_len, &address,
+ &address_len, &port)) {
+ NOTREACHED();
+ return std::string();
+ }
+ return IPAddressToStringWithPort(address, address_len, port);
+}
+
+std::string IPAddressToString(const IPAddressNumber& addr) {
+ return IPAddressToString(&addr.front(), addr.size());
+}
+
+std::string IPAddressToStringWithPort(const IPAddressNumber& addr,
+ uint16 port) {
+ return IPAddressToStringWithPort(&addr.front(), addr.size(), port);
+}
+
+std::string GetHostName() {
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+
+ // Host names are limited to 255 bytes.
+ char buffer[256];
+ int result = gethostname(buffer, sizeof(buffer));
+ if (result != 0) {
+ DVLOG(1) << "gethostname() failed with " << result;
+ buffer[0] = '\0';
+ }
+ return std::string(buffer);
+}
+
+void GetIdentityFromURL(const GURL& url,
+ base::string16* username,
+ base::string16* password) {
+ UnescapeRule::Type flags =
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS;
+ *username = UnescapeAndDecodeUTF8URLComponent(url.username(), flags, NULL);
+ *password = UnescapeAndDecodeUTF8URLComponent(url.password(), flags, NULL);
+}
+
+std::string GetHostOrSpecFromURL(const GURL& url) {
+ return url.has_host() ? TrimEndingDot(url.host()) : url.spec();
+}
+
+void AppendFormattedHost(const GURL& url,
+ const std::string& languages,
+ base::string16* output) {
+ std::vector<size_t> offsets;
+ AppendFormattedComponent(url.possibly_invalid_spec(),
+ url.parsed_for_possibly_invalid_spec().host, offsets,
+ HostComponentTransform(languages), output, NULL, NULL);
+}
+
+base::string16 FormatUrlWithOffsets(
+ const GURL& url,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ url_parse::Parsed* new_parsed,
+ size_t* prefix_end,
+ std::vector<size_t>* offsets_for_adjustment) {
+ url_parse::Parsed parsed_temp;
+ if (!new_parsed)
+ new_parsed = &parsed_temp;
+ else
+ *new_parsed = url_parse::Parsed();
+ std::vector<size_t> original_offsets;
+ if (offsets_for_adjustment)
+ original_offsets = *offsets_for_adjustment;
+
+ // Special handling for view-source:. Don't use content::kViewSourceScheme
+ // because this library shouldn't depend on chrome.
+ const char* const kViewSource = "view-source";
+ // Reject "view-source:view-source:..." to avoid deep recursion.
+ const char* const kViewSourceTwice = "view-source:view-source:";
+ if (url.SchemeIs(kViewSource) &&
+ !StartsWithASCII(url.possibly_invalid_spec(), kViewSourceTwice, false)) {
+ return FormatViewSourceUrl(url, original_offsets, languages, format_types,
+ unescape_rules, new_parsed, prefix_end, offsets_for_adjustment);
+ }
+
+ // We handle both valid and invalid URLs (this will give us the spec
+ // regardless of validity).
+ const std::string& spec = url.possibly_invalid_spec();
+ const url_parse::Parsed& parsed = url.parsed_for_possibly_invalid_spec();
+
+ // Scheme & separators. These are ASCII.
+ base::string16 url_string;
+ url_string.insert(url_string.end(), spec.begin(),
+ spec.begin() + parsed.CountCharactersBefore(url_parse::Parsed::USERNAME,
+ true));
+ const char kHTTP[] = "http://";
+ const char kFTP[] = "ftp.";
+ // URLFixerUpper::FixupURL() treats "ftp.foo.com" as ftp://ftp.foo.com. This
+ // means that if we trim "http://" off a URL whose host starts with "ftp." and
+ // the user inputs this into any field subject to fixup (which is basically
+ // all input fields), the meaning would be changed. (In fact, often the
+ // formatted URL is directly pre-filled into an input field.) For this reason
+ // we avoid stripping "http://" in this case.
+ bool omit_http = (format_types & kFormatUrlOmitHTTP) &&
+ EqualsASCII(url_string, kHTTP) &&
+ !StartsWithASCII(url.host(), kFTP, true);
+ new_parsed->scheme = parsed.scheme;
+
+ // Username & password.
+ if ((format_types & kFormatUrlOmitUsernamePassword) != 0) {
+ // Remove the username and password fields. We don't want to display those
+ // to the user since they can be used for attacks,
+ // e.g. "http://google.com:search@evil.ru/"
+ new_parsed->username.reset();
+ new_parsed->password.reset();
+ // Update the offsets based on removed username and/or password.
+ if (offsets_for_adjustment && !offsets_for_adjustment->empty() &&
+ (parsed.username.is_nonempty() || parsed.password.is_nonempty())) {
+ base::OffsetAdjuster offset_adjuster(offsets_for_adjustment);
+ if (parsed.username.is_nonempty() && parsed.password.is_nonempty()) {
+ // The seeming off-by-one and off-by-two in these first two lines are to
+ // account for the ':' after the username and '@' after the password.
+ offset_adjuster.Add(base::OffsetAdjuster::Adjustment(
+ static_cast<size_t>(parsed.username.begin),
+ static_cast<size_t>(parsed.username.len + parsed.password.len + 2),
+ 0));
+ } else {
+ const url_parse::Component* nonempty_component =
+ parsed.username.is_nonempty() ? &parsed.username : &parsed.password;
+ // The seeming off-by-one in below is to account for the '@' after the
+ // username/password.
+ offset_adjuster.Add(base::OffsetAdjuster::Adjustment(
+ static_cast<size_t>(nonempty_component->begin),
+ static_cast<size_t>(nonempty_component->len + 1), 0));
+ }
+ }
+ } else {
+ AppendFormattedComponent(spec, parsed.username, original_offsets,
+ NonHostComponentTransform(unescape_rules), &url_string,
+ &new_parsed->username, offsets_for_adjustment);
+ if (parsed.password.is_valid()) {
+ size_t colon = parsed.username.end();
+ DCHECK_EQ(static_cast<size_t>(parsed.password.begin - 1), colon);
+ std::vector<size_t>::const_iterator colon_iter =
+ std::find(original_offsets.begin(), original_offsets.end(), colon);
+ if (colon_iter != original_offsets.end()) {
+ (*offsets_for_adjustment)[colon_iter - original_offsets.begin()] =
+ url_string.length();
+ }
+ url_string.push_back(':');
+ }
+ AppendFormattedComponent(spec, parsed.password, original_offsets,
+ NonHostComponentTransform(unescape_rules), &url_string,
+ &new_parsed->password, offsets_for_adjustment);
+ if (parsed.username.is_valid() || parsed.password.is_valid()) {
+ size_t at_sign = (parsed.password.is_valid() ?
+ parsed.password : parsed.username).end();
+ DCHECK_EQ(static_cast<size_t>(parsed.host.begin - 1), at_sign);
+ std::vector<size_t>::const_iterator at_sign_iter =
+ std::find(original_offsets.begin(), original_offsets.end(), at_sign);
+ if (at_sign_iter != original_offsets.end()) {
+ (*offsets_for_adjustment)[at_sign_iter - original_offsets.begin()] =
+ url_string.length();
+ }
+ url_string.push_back('@');
+ }
+ }
+ if (prefix_end)
+ *prefix_end = static_cast<size_t>(url_string.length());
+
+ // Host.
+ AppendFormattedComponent(spec, parsed.host, original_offsets,
+ HostComponentTransform(languages), &url_string, &new_parsed->host,
+ offsets_for_adjustment);
+
+ // Port.
+ if (parsed.port.is_nonempty()) {
+ url_string.push_back(':');
+ new_parsed->port.begin = url_string.length();
+ url_string.insert(url_string.end(),
+ spec.begin() + parsed.port.begin,
+ spec.begin() + parsed.port.end());
+ new_parsed->port.len = url_string.length() - new_parsed->port.begin;
+ } else {
+ new_parsed->port.reset();
+ }
+
+ // Path & query. Both get the same general unescape & convert treatment.
+ if (!(format_types & kFormatUrlOmitTrailingSlashOnBareHostname) ||
+ !CanStripTrailingSlash(url)) {
+ AppendFormattedComponent(spec, parsed.path, original_offsets,
+ NonHostComponentTransform(unescape_rules), &url_string,
+ &new_parsed->path, offsets_for_adjustment);
+ }
+ if (parsed.query.is_valid())
+ url_string.push_back('?');
+ AppendFormattedComponent(spec, parsed.query, original_offsets,
+ NonHostComponentTransform(unescape_rules), &url_string,
+ &new_parsed->query, offsets_for_adjustment);
+
+ // Ref. This is valid, unescaped UTF-8, so we can just convert.
+ if (parsed.ref.is_valid()) {
+ url_string.push_back('#');
+ size_t original_ref_begin = static_cast<size_t>(parsed.ref.begin);
+ size_t output_ref_begin = url_string.length();
+ new_parsed->ref.begin = static_cast<int>(output_ref_begin);
+
+ std::vector<size_t> offsets_into_ref(
+ OffsetsIntoComponent(original_offsets, original_ref_begin));
+ if (parsed.ref.len > 0) {
+ url_string.append(base::UTF8ToUTF16AndAdjustOffsets(
+ spec.substr(original_ref_begin, static_cast<size_t>(parsed.ref.len)),
+ &offsets_into_ref));
+ }
+
+ new_parsed->ref.len =
+ static_cast<int>(url_string.length() - new_parsed->ref.begin);
+ AdjustForComponentTransform(original_offsets, original_ref_begin,
+ static_cast<size_t>(parsed.ref.end()), offsets_into_ref,
+ output_ref_begin, offsets_for_adjustment);
+ }
+
+ // If we need to strip out http do it after the fact. This way we don't need
+ // to worry about how offset_for_adjustment is interpreted.
+ if (omit_http && StartsWith(url_string, ASCIIToUTF16(kHTTP), true)) {
+ const size_t kHTTPSize = arraysize(kHTTP) - 1;
+ url_string = url_string.substr(kHTTPSize);
+ if (offsets_for_adjustment && !offsets_for_adjustment->empty()) {
+ base::OffsetAdjuster offset_adjuster(offsets_for_adjustment);
+ offset_adjuster.Add(base::OffsetAdjuster::Adjustment(0, kHTTPSize, 0));
+ }
+ if (prefix_end)
+ *prefix_end -= kHTTPSize;
+
+ // Adjust new_parsed.
+ DCHECK(new_parsed->scheme.is_valid());
+ int delta = -(new_parsed->scheme.len + 3); // +3 for ://.
+ new_parsed->scheme.reset();
+ AdjustComponents(delta, new_parsed);
+ }
+
+ LimitOffsets(url_string, offsets_for_adjustment);
+ return url_string;
+}
+
+base::string16 FormatUrl(const GURL& url,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ url_parse::Parsed* new_parsed,
+ size_t* prefix_end,
+ size_t* offset_for_adjustment) {
+ std::vector<size_t> offsets;
+ if (offset_for_adjustment)
+ offsets.push_back(*offset_for_adjustment);
+ base::string16 result = FormatUrlWithOffsets(url, languages, format_types,
+ unescape_rules, new_parsed, prefix_end, &offsets);
+ if (offset_for_adjustment)
+ *offset_for_adjustment = offsets[0];
+ return result;
+}
+
+bool CanStripTrailingSlash(const GURL& url) {
+ // Omit the path only for standard, non-file URLs with nothing but "/" after
+ // the hostname.
+ return url.IsStandard() && !url.SchemeIsFile() &&
+ !url.SchemeIsFileSystem() && !url.has_query() && !url.has_ref()
+ && url.path() == "/";
+}
+
+GURL SimplifyUrlForRequest(const GURL& url) {
+ DCHECK(url.is_valid());
+ GURL::Replacements replacements;
+ replacements.ClearUsername();
+ replacements.ClearPassword();
+ replacements.ClearRef();
+ return url.ReplaceComponents(replacements);
+}
+
+// Specifies a comma separated list of port numbers that should be accepted
+// despite bans. If the string is invalid no allowed ports are stored.
+void SetExplicitlyAllowedPorts(const std::string& allowed_ports) {
+ if (allowed_ports.empty())
+ return;
+
+ std::multiset<int> ports;
+ size_t last = 0;
+ size_t size = allowed_ports.size();
+ // The comma delimiter.
+ const std::string::value_type kComma = ',';
+
+ // Overflow is still possible for evil user inputs.
+ for (size_t i = 0; i <= size; ++i) {
+ // The string should be composed of only digits and commas.
+ if (i != size && !IsAsciiDigit(allowed_ports[i]) &&
+ (allowed_ports[i] != kComma))
+ return;
+ if (i == size || allowed_ports[i] == kComma) {
+ if (i > last) {
+ int port;
+ base::StringToInt(base::StringPiece(allowed_ports.begin() + last,
+ allowed_ports.begin() + i),
+ &port);
+ ports.insert(port);
+ }
+ last = i + 1;
+ }
+ }
+ g_explicitly_allowed_ports.Get() = ports;
+}
+
+ScopedPortException::ScopedPortException(int port) : port_(port) {
+ g_explicitly_allowed_ports.Get().insert(port);
+}
+
+ScopedPortException::~ScopedPortException() {
+ std::multiset<int>::iterator it =
+ g_explicitly_allowed_ports.Get().find(port_);
+ if (it != g_explicitly_allowed_ports.Get().end())
+ g_explicitly_allowed_ports.Get().erase(it);
+ else
+ NOTREACHED();
+}
+
+bool HaveOnlyLoopbackAddresses() {
+#if defined(OS_ANDROID)
+ return android::HaveOnlyLoopbackAddresses();
+#elif defined(OS_POSIX)
+ struct ifaddrs* interface_addr = NULL;
+ int rv = getifaddrs(&interface_addr);
+ if (rv != 0) {
+ DVLOG(1) << "getifaddrs() failed with errno = " << errno;
+ return false;
+ }
+
+ bool result = true;
+ for (struct ifaddrs* interface = interface_addr;
+ interface != NULL;
+ interface = interface->ifa_next) {
+ if (!(IFF_UP & interface->ifa_flags))
+ continue;
+ if (IFF_LOOPBACK & interface->ifa_flags)
+ continue;
+ const struct sockaddr* addr = interface->ifa_addr;
+ if (!addr)
+ continue;
+ if (addr->sa_family == AF_INET6) {
+ // Safe cast since this is AF_INET6.
+ const struct sockaddr_in6* addr_in6 =
+ reinterpret_cast<const struct sockaddr_in6*>(addr);
+ const struct in6_addr* sin6_addr = &addr_in6->sin6_addr;
+ if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr))
+ continue;
+ }
+ if (addr->sa_family != AF_INET6 && addr->sa_family != AF_INET)
+ continue;
+
+ result = false;
+ break;
+ }
+ freeifaddrs(interface_addr);
+ return result;
+#elif defined(OS_WIN)
+ // TODO(wtc): implement with the GetAdaptersAddresses function.
+ NOTIMPLEMENTED();
+ return false;
+#else
+ NOTIMPLEMENTED();
+ return false;
+#endif // defined(various platforms)
+}
+
+AddressFamily GetAddressFamily(const IPAddressNumber& address) {
+ switch (address.size()) {
+ case kIPv4AddressSize:
+ return ADDRESS_FAMILY_IPV4;
+ case kIPv6AddressSize:
+ return ADDRESS_FAMILY_IPV6;
+ default:
+ return ADDRESS_FAMILY_UNSPECIFIED;
+ }
+}
+
+bool ParseIPLiteralToNumber(const std::string& ip_literal,
+ IPAddressNumber* ip_number) {
+ // |ip_literal| could be either a IPv4 or an IPv6 literal. If it contains
+ // a colon however, it must be an IPv6 address.
+ if (ip_literal.find(':') != std::string::npos) {
+ // GURL expects IPv6 hostnames to be surrounded with brackets.
+ std::string host_brackets = "[" + ip_literal + "]";
+ url_parse::Component host_comp(0, host_brackets.size());
+
+ // Try parsing the hostname as an IPv6 literal.
+ ip_number->resize(16); // 128 bits.
+ return url_canon::IPv6AddressToNumber(host_brackets.data(),
+ host_comp,
+ &(*ip_number)[0]);
+ }
+
+ // Otherwise the string is an IPv4 address.
+ ip_number->resize(4); // 32 bits.
+ url_parse::Component host_comp(0, ip_literal.size());
+ int num_components;
+ url_canon::CanonHostInfo::Family family = url_canon::IPv4AddressToNumber(
+ ip_literal.data(), host_comp, &(*ip_number)[0], &num_components);
+ return family == url_canon::CanonHostInfo::IPV4;
+}
+
+namespace {
+
+const unsigned char kIPv4MappedPrefix[] =
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
+}
+
+IPAddressNumber ConvertIPv4NumberToIPv6Number(
+ const IPAddressNumber& ipv4_number) {
+ DCHECK(ipv4_number.size() == 4);
+
+ // IPv4-mapped addresses are formed by:
+ // <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>.
+ IPAddressNumber ipv6_number;
+ ipv6_number.reserve(16);
+ ipv6_number.insert(ipv6_number.end(),
+ kIPv4MappedPrefix,
+ kIPv4MappedPrefix + arraysize(kIPv4MappedPrefix));
+ ipv6_number.insert(ipv6_number.end(), ipv4_number.begin(), ipv4_number.end());
+ return ipv6_number;
+}
+
+bool IsIPv4Mapped(const IPAddressNumber& address) {
+ if (address.size() != kIPv6AddressSize)
+ return false;
+ return std::equal(address.begin(),
+ address.begin() + arraysize(kIPv4MappedPrefix),
+ kIPv4MappedPrefix);
+}
+
+IPAddressNumber ConvertIPv4MappedToIPv4(const IPAddressNumber& address) {
+ DCHECK(IsIPv4Mapped(address));
+ return IPAddressNumber(address.begin() + arraysize(kIPv4MappedPrefix),
+ address.end());
+}
+
+bool ParseCIDRBlock(const std::string& cidr_literal,
+ IPAddressNumber* ip_number,
+ size_t* prefix_length_in_bits) {
+ // We expect CIDR notation to match one of these two templates:
+ // <IPv4-literal> "/" <number of bits>
+ // <IPv6-literal> "/" <number of bits>
+
+ std::vector<std::string> parts;
+ base::SplitString(cidr_literal, '/', &parts);
+ if (parts.size() != 2)
+ return false;
+
+ // Parse the IP address.
+ if (!ParseIPLiteralToNumber(parts[0], ip_number))
+ return false;
+
+ // Parse the prefix length.
+ int number_of_bits = -1;
+ if (!base::StringToInt(parts[1], &number_of_bits))
+ return false;
+
+ // Make sure the prefix length is in a valid range.
+ if (number_of_bits < 0 ||
+ number_of_bits > static_cast<int>(ip_number->size() * 8))
+ return false;
+
+ *prefix_length_in_bits = static_cast<size_t>(number_of_bits);
+ return true;
+}
+
+bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits) {
+ // Both the input IP address and the prefix IP address should be
+ // either IPv4 or IPv6.
+ DCHECK(ip_number.size() == 4 || ip_number.size() == 16);
+ DCHECK(ip_prefix.size() == 4 || ip_prefix.size() == 16);
+
+ DCHECK_LE(prefix_length_in_bits, ip_prefix.size() * 8);
+
+ // In case we have an IPv6 / IPv4 mismatch, convert the IPv4 addresses to
+ // IPv6 addresses in order to do the comparison.
+ if (ip_number.size() != ip_prefix.size()) {
+ if (ip_number.size() == 4) {
+ return IPNumberMatchesPrefix(ConvertIPv4NumberToIPv6Number(ip_number),
+ ip_prefix, prefix_length_in_bits);
+ }
+ return IPNumberMatchesPrefix(ip_number,
+ ConvertIPv4NumberToIPv6Number(ip_prefix),
+ 96 + prefix_length_in_bits);
+ }
+
+ // Otherwise we are comparing two IPv4 addresses, or two IPv6 addresses.
+ // Compare all the bytes that fall entirely within the prefix.
+ int num_entire_bytes_in_prefix = prefix_length_in_bits / 8;
+ for (int i = 0; i < num_entire_bytes_in_prefix; ++i) {
+ if (ip_number[i] != ip_prefix[i])
+ return false;
+ }
+
+ // In case the prefix was not a multiple of 8, there will be 1 byte
+ // which is only partially masked.
+ int remaining_bits = prefix_length_in_bits % 8;
+ if (remaining_bits != 0) {
+ unsigned char mask = 0xFF << (8 - remaining_bits);
+ int i = num_entire_bytes_in_prefix;
+ if ((ip_number[i] & mask) != (ip_prefix[i] & mask))
+ return false;
+ }
+
+ return true;
+}
+
+const uint16* GetPortFieldFromSockaddr(const struct sockaddr* address,
+ socklen_t address_len) {
+ if (address->sa_family == AF_INET) {
+ DCHECK_LE(sizeof(sockaddr_in), static_cast<size_t>(address_len));
+ const struct sockaddr_in* sockaddr =
+ reinterpret_cast<const struct sockaddr_in*>(address);
+ return &sockaddr->sin_port;
+ } else if (address->sa_family == AF_INET6) {
+ DCHECK_LE(sizeof(sockaddr_in6), static_cast<size_t>(address_len));
+ const struct sockaddr_in6* sockaddr =
+ reinterpret_cast<const struct sockaddr_in6*>(address);
+ return &sockaddr->sin6_port;
+ } else {
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+int GetPortFromSockaddr(const struct sockaddr* address, socklen_t address_len) {
+ const uint16* port_field = GetPortFieldFromSockaddr(address, address_len);
+ if (!port_field)
+ return -1;
+ return base::NetToHost16(*port_field);
+}
+
+bool IsLocalhost(const std::string& host) {
+ if (host == "localhost" ||
+ host == "localhost.localdomain" ||
+ host == "localhost6" ||
+ host == "localhost6.localdomain6")
+ return true;
+
+ IPAddressNumber ip_number;
+ if (ParseIPLiteralToNumber(host, &ip_number)) {
+ size_t size = ip_number.size();
+ switch (size) {
+ case kIPv4AddressSize: {
+ IPAddressNumber localhost_prefix;
+ localhost_prefix.push_back(127);
+ for (int i = 0; i < 3; ++i) {
+ localhost_prefix.push_back(0);
+ }
+ return IPNumberMatchesPrefix(ip_number, localhost_prefix, 8);
+ }
+
+ case kIPv6AddressSize: {
+ struct in6_addr sin6_addr;
+ memcpy(&sin6_addr, &ip_number[0], kIPv6AddressSize);
+ return !!IN6_IS_ADDR_LOOPBACK(&sin6_addr);
+ }
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ return false;
+}
+
+NetworkInterface::NetworkInterface() {
+}
+
+NetworkInterface::NetworkInterface(const std::string& name,
+ const IPAddressNumber& address)
+ : name(name), address(address) {
+}
+
+NetworkInterface::~NetworkInterface() {
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_util.h b/chromium/net/base/net_util.h
new file mode 100644
index 00000000000..839735e5cd5
--- /dev/null
+++ b/chromium/net/base/net_util.h
@@ -0,0 +1,542 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NET_UTIL_H_
+#define NET_BASE_NET_UTIL_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <ws2tcpip.h>
+#elif defined(OS_POSIX)
+#include <sys/types.h>
+#include <sys/socket.h>
+#endif
+
+#include <list>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "net/base/address_family.h"
+#include "net/base/escape.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace url_canon {
+struct CanonHostInfo;
+}
+
+namespace url_parse {
+struct Parsed;
+}
+
+namespace net {
+
+// Used by FormatUrl to specify handling of certain parts of the url.
+typedef uint32 FormatUrlType;
+typedef uint32 FormatUrlTypes;
+
+// IPAddressNumber is used to represent an IP address's numeric value as an
+// array of bytes, from most significant to least significant. This is the
+// network byte ordering.
+//
+// IPv4 addresses will have length 4, whereas IPv6 address will have length 16.
+typedef std::vector<unsigned char> IPAddressNumber;
+typedef std::vector<IPAddressNumber> IPAddressList;
+
+static const size_t kIPv4AddressSize = 4;
+static const size_t kIPv6AddressSize = 16;
+
+// Nothing is ommitted.
+NET_EXPORT extern const FormatUrlType kFormatUrlOmitNothing;
+
+// If set, any username and password are removed.
+NET_EXPORT extern const FormatUrlType kFormatUrlOmitUsernamePassword;
+
+// If the scheme is 'http://', it's removed.
+NET_EXPORT extern const FormatUrlType kFormatUrlOmitHTTP;
+
+// Omits the path if it is just a slash and there is no query or ref. This is
+// meaningful for non-file "standard" URLs.
+NET_EXPORT extern const FormatUrlType kFormatUrlOmitTrailingSlashOnBareHostname;
+
+// Convenience for omitting all unecessary types.
+NET_EXPORT extern const FormatUrlType kFormatUrlOmitAll;
+
+// Returns the number of explicitly allowed ports; for testing.
+NET_EXPORT_PRIVATE extern size_t GetCountOfExplicitlyAllowedPorts();
+
+// Given the full path to a file name, creates a file: URL. The returned URL
+// may not be valid if the input is malformed.
+NET_EXPORT GURL FilePathToFileURL(const base::FilePath& path);
+
+// Converts a file: URL back to a filename that can be passed to the OS. The
+// file URL must be well-formed (GURL::is_valid() must return true); we don't
+// handle degenerate cases here. Returns true on success, false if it isn't a
+// valid file URL. On failure, *file_path will be empty.
+NET_EXPORT bool FileURLToFilePath(const GURL& url, base::FilePath* file_path);
+
+// Splits an input of the form <host>[":"<port>] into its consitituent parts.
+// Saves the result into |*host| and |*port|. If the input did not have
+// the optional port, sets |*port| to -1.
+// Returns true if the parsing was successful, false otherwise.
+// The returned host is NOT canonicalized, and may be invalid. If <host> is
+// an IPv6 literal address, the returned host includes the square brackets.
+NET_EXPORT bool ParseHostAndPort(
+ std::string::const_iterator host_and_port_begin,
+ std::string::const_iterator host_and_port_end,
+ std::string* host,
+ int* port);
+NET_EXPORT bool ParseHostAndPort(
+ const std::string& host_and_port,
+ std::string* host,
+ int* port);
+
+// Returns a host:port string for the given URL.
+NET_EXPORT std::string GetHostAndPort(const GURL& url);
+
+// Returns a host[:port] string for the given URL, where the port is omitted
+// if it is the default for the URL's scheme.
+NET_EXPORT_PRIVATE std::string GetHostAndOptionalPort(const GURL& url);
+
+// Returns true if |hostname| contains a non-registerable or non-assignable
+// domain name (eg: a gTLD that has not been assigned by IANA)
+//
+// TODO(rsleevi): http://crbug.com/119212 - Also match internal IP
+// address ranges.
+NET_EXPORT bool IsHostnameNonUnique(const std::string& hostname);
+
+// Convenience struct for when you need a |struct sockaddr|.
+struct SockaddrStorage {
+ SockaddrStorage() : addr_len(sizeof(addr_storage)),
+ addr(reinterpret_cast<struct sockaddr*>(&addr_storage)) {}
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_len;
+ struct sockaddr* const addr;
+};
+
+// Extracts the IP address and port portions of a sockaddr. |port| is optional,
+// and will not be filled in if NULL.
+bool GetIPAddressFromSockAddr(const struct sockaddr* sock_addr,
+ socklen_t sock_addr_len,
+ const unsigned char** address,
+ size_t* address_len,
+ uint16* port);
+
+// Returns the string representation of an IP address.
+// For example: "192.168.0.1" or "::1".
+NET_EXPORT std::string IPAddressToString(const uint8* address,
+ size_t address_len);
+
+// Returns the string representation of an IP address along with its port.
+// For example: "192.168.0.1:99" or "[::1]:80".
+NET_EXPORT std::string IPAddressToStringWithPort(const uint8* address,
+ size_t address_len,
+ uint16 port);
+
+// Same as IPAddressToString() but for a sockaddr. This output will not include
+// the IPv6 scope ID.
+NET_EXPORT std::string NetAddressToString(const struct sockaddr* sa,
+ socklen_t sock_addr_len);
+
+// Same as IPAddressToStringWithPort() but for a sockaddr. This output will not
+// include the IPv6 scope ID.
+NET_EXPORT std::string NetAddressToStringWithPort(const struct sockaddr* sa,
+ socklen_t sock_addr_len);
+
+// Same as IPAddressToString() but for an IPAddressNumber.
+NET_EXPORT std::string IPAddressToString(const IPAddressNumber& addr);
+
+// Same as IPAddressToStringWithPort() but for an IPAddressNumber.
+NET_EXPORT std::string IPAddressToStringWithPort(
+ const IPAddressNumber& addr, uint16 port);
+
+// Returns the hostname of the current system. Returns empty string on failure.
+NET_EXPORT std::string GetHostName();
+
+// Extracts the unescaped username/password from |url|, saving the results
+// into |*username| and |*password|.
+NET_EXPORT_PRIVATE void GetIdentityFromURL(const GURL& url,
+ base::string16* username,
+ base::string16* password);
+
+// Returns either the host from |url|, or, if the host is empty, the full spec.
+NET_EXPORT std::string GetHostOrSpecFromURL(const GURL& url);
+
+// Return the value of the HTTP response header with name 'name'. 'headers'
+// should be in the format that URLRequest::GetResponseHeaders() returns.
+// Returns the empty string if the header is not found.
+NET_EXPORT std::string GetSpecificHeader(const std::string& headers,
+ const std::string& name);
+
+// Converts the given host name to unicode characters. This can be called for
+// any host name, if the input is not IDN or is invalid in some way, we'll just
+// return the ASCII source so it is still usable.
+//
+// The input should be the canonicalized ASCII host name from GURL. This
+// function does NOT accept UTF-8!
+//
+// |languages| is a comma separated list of ISO 639 language codes. It
+// is used to determine whether a hostname is 'comprehensible' to a user
+// who understands languages listed. |host| will be converted to a
+// human-readable form (Unicode) ONLY when each component of |host| is
+// regarded as 'comprehensible'. Scipt-mixing is not allowed except that
+// Latin letters in the ASCII range can be mixed with a limited set of
+// script-language pairs (currently Han, Kana and Hangul for zh,ja and ko).
+// When |languages| is empty, even that mixing is not allowed.
+NET_EXPORT base::string16 IDNToUnicode(const std::string& host,
+ const std::string& languages);
+
+// Canonicalizes |host| and returns it. Also fills |host_info| with
+// IP address information. |host_info| must not be NULL.
+NET_EXPORT std::string CanonicalizeHost(const std::string& host,
+ url_canon::CanonHostInfo* host_info);
+
+// Returns true if |host| is not an IP address and is compliant with a set of
+// rules based on RFC 1738 and tweaked to be compatible with the real world.
+// The rules are:
+// * One or more components separated by '.'
+// * Each component begins with an alphanumeric character or '-'
+// * Each component contains only alphanumeric characters and '-' or '_'
+// * Each component ends with an alphanumeric character or '-'
+// * The last component begins with an alphanumeric character
+// * Optional trailing dot after last component (means "treat as FQDN")
+// If |desired_tld| is non-NULL, the host will only be considered invalid if
+// appending it as a trailing component still results in an invalid host. This
+// helps us avoid marking as "invalid" user attempts to open, say, "www.-9.com"
+// by typing -, 9, <ctrl>+<enter>.
+//
+// NOTE: You should only pass in hosts that have been returned from
+// CanonicalizeHost(), or you may not get accurate results.
+NET_EXPORT bool IsCanonicalizedHostCompliant(const std::string& host,
+ const std::string& desired_tld);
+
+// Call these functions to get the html snippet for a directory listing.
+// The return values of both functions are in UTF-8.
+NET_EXPORT std::string GetDirectoryListingHeader(const base::string16& title);
+
+// Given the name of a file in a directory (ftp or local) and
+// other information (is_dir, size, modification time), it returns
+// the html snippet to add the entry for the file to the directory listing.
+// Currently, it's a script tag containing a call to a Javascript function
+// |addRow|.
+//
+// |name| is the file name to be displayed. |raw_bytes| will be used
+// as the actual target of the link (so for example, ftp links should use
+// server's encoding). If |raw_bytes| is an empty string, UTF-8 encoded |name|
+// will be used.
+//
+// Both |name| and |raw_bytes| are escaped internally.
+NET_EXPORT std::string GetDirectoryListingEntry(const base::string16& name,
+ const std::string& raw_bytes,
+ bool is_dir, int64 size,
+ base::Time modified);
+
+// If text starts with "www." it is removed, otherwise text is returned
+// unmodified.
+NET_EXPORT base::string16 StripWWW(const base::string16& text);
+
+// Runs |url|'s host through StripWWW(). |url| must be valid.
+NET_EXPORT base::string16 StripWWWFromHost(const GURL& url);
+
+// Generates a filename using the first successful method from the following (in
+// order):
+//
+// 1) The raw Content-Disposition header in |content_disposition| as read from
+// the network. |referrer_charset| is used to decode non-ASCII strings.
+// 2) |suggested_name| if specified. |suggested_name| is assumed to be in
+// UTF-8.
+// 3) The filename extracted from the |url|. |referrer_charset| will be used to
+// interpret the URL if there are non-ascii characters.
+// 4) |default_name|. If non-empty, |default_name| is assumed to be a filename
+// and shouldn't contain a path. |default_name| is not subject to validation
+// or sanitization, and therefore shouldn't be a user supplied string.
+// 5) The hostname portion from the |url|
+//
+// Then, leading and trailing '.'s will be removed. On Windows, trailing spaces
+// are also removed. The string "download" is the final fallback if no filename
+// is found or the filename is empty.
+//
+// Any illegal characters in the filename will be replaced by '-'. If the
+// filename doesn't contain an extension, and a |mime_type| is specified, the
+// preferred extension for the |mime_type| will be appended to the filename.
+// The resulting filename is then checked against a list of reserved names on
+// Windows. If the name is reserved, an underscore will be prepended to the
+// filename.
+//
+// Note: |mime_type| should only be specified if this function is called from a
+// thread that allows IO.
+NET_EXPORT base::string16 GetSuggestedFilename(
+ const GURL& url,
+ const std::string& content_disposition,
+ const std::string& referrer_charset,
+ const std::string& suggested_name,
+ const std::string& mime_type,
+ const std::string& default_name);
+
+// Similar to GetSuggestedFilename(), but returns a FilePath.
+NET_EXPORT base::FilePath GenerateFileName(
+ const GURL& url,
+ const std::string& content_disposition,
+ const std::string& referrer_charset,
+ const std::string& suggested_name,
+ const std::string& mime_type,
+ const std::string& default_name);
+
+// Valid basenames:
+// * are not empty
+// * are not Windows reserved names (CON, NUL.zip, etc.)
+// * are just basenames
+// * do not have trailing separators
+// * do not equal kCurrentDirectory
+// * do not reference the parent directory
+// * are valid path components, which:
+// - * are not the empty string
+// - * do not contain illegal characters
+// - * do not end with Windows shell-integrated extensions (even on posix)
+// - * do not begin with '.' (which would hide them in most file managers)
+// - * do not end with ' ' or '.'
+NET_EXPORT bool IsSafePortableBasename(const base::FilePath& path);
+
+// Basenames of valid relative paths are IsSafePortableBasename(), and internal
+// path components of valid relative paths are valid path components as
+// described above IsSafePortableBasename(). Valid relative paths are not
+// absolute paths.
+NET_EXPORT bool IsSafePortableRelativePath(const base::FilePath& path);
+
+// Ensures that the filename and extension is safe to use in the filesystem.
+//
+// Assumes that |file_path| already contains a valid path or file name. On
+// Windows if the extension causes the file to have an unsafe interaction with
+// the shell (see net_util::IsShellIntegratedExtension()), then it will be
+// replaced by the string 'download'. If |file_path| doesn't contain an
+// extension or |ignore_extension| is true then the preferred extension, if one
+// exists, for |mime_type| will be used as the extension.
+//
+// On Windows, the filename will be checked against a set of reserved names, and
+// if so, an underscore will be prepended to the name.
+//
+// |file_name| can either be just the file name or it can be a full path to a
+// file.
+//
+// Note: |mime_type| should only be non-empty if this function is called from a
+// thread that allows IO.
+NET_EXPORT void GenerateSafeFileName(const std::string& mime_type,
+ bool ignore_extension,
+ base::FilePath* file_path);
+
+// Checks |port| against a list of ports which are restricted by default.
+// Returns true if |port| is allowed, false if it is restricted.
+NET_EXPORT bool IsPortAllowedByDefault(int port);
+
+// Checks |port| against a list of ports which are restricted by the FTP
+// protocol. Returns true if |port| is allowed, false if it is restricted.
+NET_EXPORT_PRIVATE bool IsPortAllowedByFtp(int port);
+
+// Check if banned |port| has been overriden by an entry in
+// |explicitly_allowed_ports_|.
+NET_EXPORT_PRIVATE bool IsPortAllowedByOverride(int port);
+
+// Set socket to non-blocking mode
+NET_EXPORT int SetNonBlocking(int fd);
+
+// Formats the host in |url| and appends it to |output|. The host formatter
+// takes the same accept languages component as ElideURL().
+NET_EXPORT void AppendFormattedHost(const GURL& url,
+ const std::string& languages,
+ base::string16* output);
+
+// Creates a string representation of |url|. The IDN host name may be in Unicode
+// if |languages| accepts the Unicode representation. |format_type| is a bitmask
+// of FormatUrlTypes, see it for details. |unescape_rules| defines how to clean
+// the URL for human readability. You will generally want |UnescapeRule::SPACES|
+// for display to the user if you can handle spaces, or |UnescapeRule::NORMAL|
+// if not. If the path part and the query part seem to be encoded in %-encoded
+// UTF-8, decodes %-encoding and UTF-8.
+//
+// The last three parameters may be NULL.
+// |new_parsed| will be set to the parsing parameters of the resultant URL.
+// |prefix_end| will be the length before the hostname of the resultant URL.
+//
+// (|offset[s]_for_adjustment|) specifies one or more offsets into the original
+// |url|'s spec(); each offset will be modified to reflect changes this function
+// makes to the output string. For example, if |url| is "http://a:b@c.com/",
+// |omit_username_password| is true, and an offset is 12 (the offset of '.'),
+// then on return the output string will be "http://c.com/" and the offset will
+// be 8. If an offset cannot be successfully adjusted (e.g. because it points
+// into the middle of a component that was entirely removed, past the end of the
+// string, or into the middle of an encoding sequence), it will be set to
+// base::string16::npos.
+NET_EXPORT base::string16 FormatUrl(const GURL& url,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ url_parse::Parsed* new_parsed,
+ size_t* prefix_end,
+ size_t* offset_for_adjustment);
+NET_EXPORT base::string16 FormatUrlWithOffsets(
+ const GURL& url,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ url_parse::Parsed* new_parsed,
+ size_t* prefix_end,
+ std::vector<size_t>* offsets_for_adjustment);
+
+// This is a convenience function for FormatUrl() with
+// format_types = kFormatUrlOmitAll and unescape = SPACES. This is the typical
+// set of flags for "URLs to display to the user". You should be cautious about
+// using this for URLs which will be parsed or sent to other applications.
+inline base::string16 FormatUrl(const GURL& url, const std::string& languages) {
+ return FormatUrl(url, languages, kFormatUrlOmitAll, UnescapeRule::SPACES,
+ NULL, NULL, NULL);
+}
+
+// Returns whether FormatUrl() would strip a trailing slash from |url|, given a
+// format flag including kFormatUrlOmitTrailingSlashOnBareHostname.
+NET_EXPORT bool CanStripTrailingSlash(const GURL& url);
+
+// Strip the portions of |url| that aren't core to the network request.
+// - user name / password
+// - reference section
+NET_EXPORT_PRIVATE GURL SimplifyUrlForRequest(const GURL& url);
+
+NET_EXPORT void SetExplicitlyAllowedPorts(const std::string& allowed_ports);
+
+class NET_EXPORT ScopedPortException {
+ public:
+ ScopedPortException(int port);
+ ~ScopedPortException();
+
+ private:
+ int port_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPortException);
+};
+
+// Returns true if it can determine that only loopback addresses are configured.
+// i.e. if only 127.0.0.1 and ::1 are routable.
+// Also returns false if it cannot determine this.
+bool HaveOnlyLoopbackAddresses();
+
+// Returns AddressFamily of the address.
+NET_EXPORT_PRIVATE AddressFamily GetAddressFamily(
+ const IPAddressNumber& address);
+
+// Parses an IP address literal (either IPv4 or IPv6) to its numeric value.
+// Returns true on success and fills |ip_number| with the numeric value.
+NET_EXPORT_PRIVATE bool ParseIPLiteralToNumber(const std::string& ip_literal,
+ IPAddressNumber* ip_number);
+
+// Converts an IPv4 address to an IPv4-mapped IPv6 address.
+// For example 192.168.0.1 would be converted to ::ffff:192.168.0.1.
+NET_EXPORT_PRIVATE IPAddressNumber ConvertIPv4NumberToIPv6Number(
+ const IPAddressNumber& ipv4_number);
+
+// Returns true iff |address| is an IPv4-mapped IPv6 address.
+NET_EXPORT_PRIVATE bool IsIPv4Mapped(const IPAddressNumber& address);
+
+// Converts an IPv4-mapped IPv6 address to IPv4 address. Should only be called
+// on IPv4-mapped IPv6 addresses.
+NET_EXPORT_PRIVATE IPAddressNumber ConvertIPv4MappedToIPv4(
+ const IPAddressNumber& address);
+
+// Parses an IP block specifier from CIDR notation to an
+// (IP address, prefix length) pair. Returns true on success and fills
+// |*ip_number| with the numeric value of the IP address and sets
+// |*prefix_length_in_bits| with the length of the prefix.
+//
+// CIDR notation literals can use either IPv4 or IPv6 literals. Some examples:
+//
+// 10.10.3.1/20
+// a:b:c::/46
+// ::1/128
+NET_EXPORT bool ParseCIDRBlock(const std::string& cidr_literal,
+ IPAddressNumber* ip_number,
+ size_t* prefix_length_in_bits);
+
+// Compares an IP address to see if it falls within the specified IP block.
+// Returns true if it does, false otherwise.
+//
+// The IP block is given by (|ip_prefix|, |prefix_length_in_bits|) -- any
+// IP address whose |prefix_length_in_bits| most significant bits match
+// |ip_prefix| will be matched.
+//
+// In cases when an IPv4 address is being compared to an IPv6 address prefix
+// and vice versa, the IPv4 addresses will be converted to IPv4-mapped
+// (IPv6) addresses.
+NET_EXPORT_PRIVATE bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits);
+
+// Retuns the port field of the |sockaddr|.
+const uint16* GetPortFieldFromSockaddr(const struct sockaddr* address,
+ socklen_t address_len);
+// Returns the value of port in |sockaddr| (in host byte ordering).
+NET_EXPORT_PRIVATE int GetPortFromSockaddr(const struct sockaddr* address,
+ socklen_t address_len);
+
+// Returns true if |host| is one of the names (e.g. "localhost") or IP
+// addresses (IPv4 127.0.0.0/8 or IPv6 ::1) that indicate a loopback.
+//
+// Note that this function does not check for IP addresses other than
+// the above, although other IP addresses may point to the local
+// machine.
+NET_EXPORT_PRIVATE bool IsLocalhost(const std::string& host);
+
+// struct that is used by GetNetworkList() to represent a network
+// interface.
+struct NET_EXPORT NetworkInterface {
+ NetworkInterface();
+ NetworkInterface(const std::string& name, const IPAddressNumber& address);
+ ~NetworkInterface();
+
+ std::string name;
+ IPAddressNumber address;
+};
+
+typedef std::vector<NetworkInterface> NetworkInterfaceList;
+
+// Returns list of network interfaces except loopback interface. If an
+// interface has more than one address, a separate entry is added to
+// the list for each address.
+// Can be called only on a thread that allows IO.
+NET_EXPORT bool GetNetworkList(NetworkInterfaceList* networks);
+
+// General category of the IEEE 802.11 (wifi) physical layer operating mode.
+enum WifiPHYLayerProtocol {
+ // No wifi support or no associated AP.
+ WIFI_PHY_LAYER_PROTOCOL_NONE,
+ // An obsolete modes introduced by the original 802.11, e.g. IR, FHSS,
+ WIFI_PHY_LAYER_PROTOCOL_ANCIENT,
+ // 802.11a, OFDM-based rates.
+ WIFI_PHY_LAYER_PROTOCOL_A,
+ // 802.11b, DSSS or HR DSSS.
+ WIFI_PHY_LAYER_PROTOCOL_B,
+ // 802.11g, same rates as 802.11a but compatible with 802.11b.
+ WIFI_PHY_LAYER_PROTOCOL_G,
+ // 802.11n, HT rates.
+ WIFI_PHY_LAYER_PROTOCOL_N,
+ // Unclassified mode or failure to identify.
+ WIFI_PHY_LAYER_PROTOCOL_UNKNOWN
+};
+
+// Characterize the PHY mode of the currently associated access point.
+// Currently only available on OS_WIN.
+NET_EXPORT WifiPHYLayerProtocol GetWifiPHYLayerProtocol();
+
+} // namespace net
+
+#endif // NET_BASE_NET_UTIL_H_
diff --git a/chromium/net/base/net_util_posix.cc b/chromium/net/base/net_util_posix.cc
new file mode 100644
index 00000000000..904d8028bc2
--- /dev/null
+++ b/chromium/net/base/net_util_posix.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2011 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/base/net_util.h"
+
+#include <sys/types.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/escape.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+#if !defined(OS_ANDROID)
+#include <ifaddrs.h>
+#endif
+#include <net/if.h>
+#include <netinet/in.h>
+
+#if defined(OS_ANDROID)
+#include "net/android/network_library.h"
+#endif
+
+namespace net {
+
+bool FileURLToFilePath(const GURL& url, base::FilePath* path) {
+ *path = base::FilePath();
+ std::string& file_path_str = const_cast<std::string&>(path->value());
+ file_path_str.clear();
+
+ if (!url.is_valid())
+ return false;
+
+ // Firefox seems to ignore the "host" of a file url if there is one. That is,
+ // file://foo/bar.txt maps to /bar.txt.
+ // TODO(dhg): This should probably take into account UNCs which could
+ // include a hostname other than localhost or blank
+ std::string old_path = url.path();
+
+ if (old_path.empty())
+ return false;
+
+ // GURL stores strings as percent-encoded 8-bit, this will undo if possible.
+ old_path = UnescapeURLComponent(old_path,
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
+
+ // Collapse multiple path slashes into a single path slash.
+ std::string new_path;
+ do {
+ new_path = old_path;
+ ReplaceSubstringsAfterOffset(&new_path, 0, "//", "/");
+ old_path.swap(new_path);
+ } while (new_path != old_path);
+
+ file_path_str.assign(old_path);
+
+ return !file_path_str.empty();
+}
+
+bool GetNetworkList(NetworkInterfaceList* networks) {
+#if defined(OS_ANDROID)
+ std::string network_list = android::GetNetworkList();
+ base::StringTokenizer network_interfaces(network_list, ";");
+ while (network_interfaces.GetNext()) {
+ std::string network_item = network_interfaces.token();
+ base::StringTokenizer network_tokenizer(network_item, ",");
+ std::string name;
+ if (!network_tokenizer.GetNext())
+ continue;
+ name = network_tokenizer.token();
+
+ std::string literal_address;
+ if (!network_tokenizer.GetNext())
+ continue;
+ literal_address = network_tokenizer.token();
+
+ IPAddressNumber address;
+ if (!ParseIPLiteralToNumber(literal_address, &address))
+ continue;
+ networks->push_back(NetworkInterface(name, address));
+ }
+ return true;
+#else
+ // getifaddrs() may require IO operations.
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ ifaddrs *interfaces;
+ if (getifaddrs(&interfaces) < 0) {
+ PLOG(ERROR) << "getifaddrs";
+ return false;
+ }
+
+ // Enumerate the addresses assigned to network interfaces which are up.
+ for (ifaddrs *interface = interfaces;
+ interface != NULL;
+ interface = interface->ifa_next) {
+ // Skip loopback interfaces, and ones which are down.
+ if (!(IFF_UP & interface->ifa_flags))
+ continue;
+ if (IFF_LOOPBACK & interface->ifa_flags)
+ continue;
+ // Skip interfaces with no address configured.
+ struct sockaddr* addr = interface->ifa_addr;
+ if (!addr)
+ continue;
+ // Skip unspecified addresses (i.e. made of zeroes) and loopback addresses
+ // configured on non-loopback interfaces.
+ int addr_size = 0;
+ if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6* addr_in6 =
+ reinterpret_cast<struct sockaddr_in6*>(addr);
+ struct in6_addr* sin6_addr = &addr_in6->sin6_addr;
+ addr_size = sizeof(*addr_in6);
+ if (IN6_IS_ADDR_LOOPBACK(sin6_addr) ||
+ IN6_IS_ADDR_UNSPECIFIED(sin6_addr)) {
+ continue;
+ }
+ } else if (addr->sa_family == AF_INET) {
+ struct sockaddr_in* addr_in =
+ reinterpret_cast<struct sockaddr_in*>(addr);
+ addr_size = sizeof(*addr_in);
+ if (addr_in->sin_addr.s_addr == INADDR_LOOPBACK ||
+ addr_in->sin_addr.s_addr == 0) {
+ continue;
+ }
+ } else {
+ // Skip non-IP addresses.
+ continue;
+ }
+ IPEndPoint address;
+ std::string name = interface->ifa_name;
+ if (address.FromSockAddr(addr, addr_size)) {
+ networks->push_back(NetworkInterface(name, address.address()));
+ }
+ }
+
+ freeifaddrs(interfaces);
+
+ return true;
+#endif
+}
+
+WifiPHYLayerProtocol GetWifiPHYLayerProtocol() {
+ return WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+}
+
+} // namespace net
diff --git a/chromium/net/base/net_util_unittest.cc b/chromium/net/base/net_util_unittest.cc
new file mode 100644
index 00000000000..7c6bb572d77
--- /dev/null
+++ b/chromium/net/base/net_util_unittest.cc
@@ -0,0 +1,3569 @@
+// Copyright (c) 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.
+
+#include "net/base/net_util.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_byteorder.h"
+#include "base/test/test_file_util.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+static const size_t kNpos = base::string16::npos;
+
+struct FileCase {
+ const wchar_t* file;
+ const char* url;
+};
+
+struct HeaderCase {
+ const char* header_name;
+ const char* expected;
+};
+
+struct HeaderParamCase {
+ const char* header_name;
+ const char* param_name;
+ const char* expected;
+};
+
+struct FileNameCDCase {
+ const char* header_field;
+ const char* referrer_charset;
+ const wchar_t* expected;
+};
+
+const char* kLanguages[] = {
+ "", "en", "zh-CN", "ja", "ko",
+ "he", "ar", "ru", "el", "fr",
+ "de", "pt", "sv", "th", "hi",
+ "de,en", "el,en", "zh-TW,en", "ko,ja", "he,ru,en",
+ "zh,ru,en"
+};
+
+struct IDNTestCase {
+ const char* input;
+ const wchar_t* unicode_output;
+ const bool unicode_allowed[arraysize(kLanguages)];
+};
+
+// TODO(jungshik) This is just a random sample of languages and is far
+// from exhaustive. We may have to generate all the combinations
+// of languages (powerset of a set of all the languages).
+const IDNTestCase idn_cases[] = {
+ // No IDN
+ {"www.google.com", L"www.google.com",
+ {true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true}},
+ {"www.google.com.", L"www.google.com.",
+ {true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true}},
+ {".", L".",
+ {true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true}},
+ {"", L"",
+ {true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true}},
+ // IDN
+ // Hanzi (Traditional Chinese)
+ {"xn--1lq90ic7f1rc.cn", L"\x5317\x4eac\x5927\x5b78.cn",
+ {true, false, true, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, true, true, false,
+ true}},
+ // Hanzi ('video' in Simplified Chinese : will pass only in zh-CN,zh)
+ {"xn--cy2a840a.com", L"\x89c6\x9891.com",
+ {true, false, true, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ true}},
+ // Hanzi + '123'
+ {"www.xn--123-p18d.com", L"www.\x4e00" L"123.com",
+ {true, false, true, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, true, true, false,
+ true}},
+ // Hanzi + Latin : U+56FD is simplified and is regarded
+ // as not supported in zh-TW.
+ {"www.xn--hello-9n1hm04c.com", L"www.hello\x4e2d\x56fd.com",
+ {false, false, true, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ true}},
+ // Kanji + Kana (Japanese)
+ {"xn--l8jvb1ey91xtjb.jp", L"\x671d\x65e5\x3042\x3055\x3072.jp",
+ {true, false, false, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ false}},
+ // Katakana including U+30FC
+ {"xn--tckm4i2e.jp", L"\x30b3\x30de\x30fc\x30b9.jp",
+ {true, false, false, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ }},
+ {"xn--3ck7a7g.jp", L"\u30ce\u30f3\u30bd.jp",
+ {true, false, false, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ }},
+ // Katakana + Latin (Japanese)
+ // TODO(jungshik): Change 'false' in the first element to 'true'
+ // after upgrading to ICU 4.2.1 to use new uspoof_* APIs instead
+ // of our IsIDNComponentInSingleScript().
+ {"xn--e-efusa1mzf.jp", L"e\x30b3\x30de\x30fc\x30b9.jp",
+ {false, false, false, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ }},
+ {"xn--3bkxe.jp", L"\x30c8\x309a.jp",
+ {false, false, false, true, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ }},
+ // Hangul (Korean)
+ {"www.xn--or3b17p6jjc.kr", L"www.\xc804\xc790\xc815\xbd80.kr",
+ {true, false, false, false, true,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ false}},
+ // b<u-umlaut>cher (German)
+ {"xn--bcher-kva.de", L"b\x00fc" L"cher.de",
+ {true, false, false, false, false,
+ false, false, false, false, true,
+ true, false, false, false, false,
+ true, false, false, false, false,
+ false}},
+ // a with diaeresis
+ {"www.xn--frgbolaget-q5a.se", L"www.f\x00e4rgbolaget.se",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ true, false, true, false, false,
+ true, false, false, false, false,
+ false}},
+ // c-cedilla (French)
+ {"www.xn--alliancefranaise-npb.fr", L"www.alliancefran\x00e7" L"aise.fr",
+ {true, false, false, false, false,
+ false, false, false, false, true,
+ false, true, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // caf'e with acute accent' (French)
+ {"xn--caf-dma.fr", L"caf\x00e9.fr",
+ {true, false, false, false, false,
+ false, false, false, false, true,
+ false, true, true, false, false,
+ false, false, false, false, false,
+ false}},
+ // c-cedillla and a with tilde (Portuguese)
+ {"xn--poema-9qae5a.com.br", L"p\x00e3oema\x00e7\x00e3.com.br",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ false, true, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // s with caron
+ {"xn--achy-f6a.com", L"\x0161" L"achy.com",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // TODO(jungshik) : Add examples with Cyrillic letters
+ // only used in some languages written in Cyrillic.
+ // Eutopia (Greek)
+ {"xn--kxae4bafwg.gr", L"\x03bf\x03c5\x03c4\x03bf\x03c0\x03af\x03b1.gr",
+ {true, false, false, false, false,
+ false, false, false, true, false,
+ false, false, false, false, false,
+ false, true, false, false, false,
+ false}},
+ // Eutopia + 123 (Greek)
+ {"xn---123-pldm0haj2bk.gr",
+ L"\x03bf\x03c5\x03c4\x03bf\x03c0\x03af\x03b1-123.gr",
+ {true, false, false, false, false,
+ false, false, false, true, false,
+ false, false, false, false, false,
+ false, true, false, false, false,
+ false}},
+ // Cyrillic (Russian)
+ {"xn--n1aeec9b.ru", L"\x0442\x043e\x0440\x0442\x044b.ru",
+ {true, false, false, false, false,
+ false, false, true, false, false,
+ false, false, false, false, false,
+ false, false, false, false, true,
+ true}},
+ // Cyrillic + 123 (Russian)
+ {"xn---123-45dmmc5f.ru", L"\x0442\x043e\x0440\x0442\x044b-123.ru",
+ {true, false, false, false, false,
+ false, false, true, false, false,
+ false, false, false, false, false,
+ false, false, false, false, true,
+ true}},
+ // Arabic
+ {"xn--mgba1fmg.ar", L"\x0627\x0641\x0644\x0627\x0645.ar",
+ {true, false, false, false, false,
+ false, true, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // Hebrew
+ {"xn--4dbib.he", L"\x05d5\x05d0\x05d4.he",
+ {true, false, false, false, false,
+ true, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, true,
+ false}},
+ // Thai
+ {"xn--12c2cc4ag3b4ccu.th",
+ L"\x0e2a\x0e32\x0e22\x0e01\x0e32\x0e23\x0e1a\x0e34\x0e19.th",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, true, false,
+ false, false, false, false, false,
+ false}},
+ // Devangari (Hindi)
+ {"www.xn--l1b6a9e1b7c.in", L"www.\x0905\x0915\x094b\x0932\x093e.in",
+ {true, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, true,
+ false, false, false, false, false,
+ false}},
+ // Invalid IDN
+ {"xn--hello?world.com", NULL,
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // Unsafe IDNs
+ // "payp<alpha>l.com"
+ {"www.xn--paypl-g9d.com", L"payp\x03b1l.com",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // google.gr with Greek omicron and epsilon
+ {"xn--ggl-6xc1ca.gr", L"g\x03bf\x03bfgl\x03b5.gr",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // google.ru with Cyrillic o
+ {"xn--ggl-tdd6ba.ru", L"g\x043e\x043egl\x0435.ru",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // h<e with acute>llo<China in Han>.cn
+ {"xn--hllo-bpa7979ih5m.cn", L"h\x00e9llo\x4e2d\x56fd.cn",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // <Greek rho><Cyrillic a><Cyrillic u>.ru
+ {"xn--2xa6t2b.ru", L"\x03c1\x0430\x0443.ru",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ // One that's really long that will force a buffer realloc
+ {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaa",
+ L"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ L"aaaaaaaa",
+ {true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true, true, true, true, true,
+ true}},
+ // Test cases for characters we blacklisted although allowed in IDN.
+ // Embedded spaces will be turned to %20 in the display.
+ // TODO(jungshik): We need to have more cases. This is a typical
+ // data-driven trap. The following test cases need to be separated
+ // and tested only for a couple of languages.
+ {"xn--osd3820f24c.kr", L"\xac00\xb098\x115f.kr",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false}},
+ {"www.xn--google-ho0coa.com", L"www.\x2039google\x203a.com",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ }},
+ {"google.xn--comabc-k8d", L"google.com\x0338" L"abc",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ }},
+ {"google.xn--com-oh4ba.evil.jp", L"google.com\x309a\x309a.evil.jp",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ }},
+ {"google.xn--comevil-v04f.jp", L"google.com\x30ce" L"evil.jp",
+ {false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ }},
+#if 0
+ // These two cases are special. We need a separate test.
+ // U+3000 and U+3002 are normalized to ASCII space and dot.
+ {"xn-- -kq6ay5z.cn", L"\x4e2d\x56fd\x3000.cn",
+ {false, false, true, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, true, false, false,
+ true}},
+ {"xn--fiqs8s.cn", L"\x4e2d\x56fd\x3002" L"cn",
+ {false, false, true, false, false,
+ false, false, false, false, false,
+ false, false, false, false, false,
+ false, false, true, false, false,
+ true}},
+#endif
+};
+
+struct AdjustOffsetCase {
+ size_t input_offset;
+ size_t output_offset;
+};
+
+struct CompliantHostCase {
+ const char* host;
+ const char* desired_tld;
+ bool expected_output;
+};
+
+struct GenerateFilenameCase {
+ int lineno;
+ const char* url;
+ const char* content_disp_header;
+ const char* referrer_charset;
+ const char* suggested_filename;
+ const char* mime_type;
+ const wchar_t* default_filename;
+ const wchar_t* expected_filename;
+};
+
+struct UrlTestData {
+ const char* description;
+ const char* input;
+ const char* languages;
+ FormatUrlTypes format_types;
+ UnescapeRule::Type escape_rules;
+ const wchar_t* output; // Use |wchar_t| to handle Unicode constants easily.
+ size_t prefix_len;
+};
+
+// Fills in sockaddr for the given 32-bit address (IPv4.)
+// |bytes| should be an array of length 4.
+void MakeIPv4Address(const uint8* bytes, int port, SockaddrStorage* storage) {
+ memset(&storage->addr_storage, 0, sizeof(storage->addr_storage));
+ storage->addr_len = sizeof(struct sockaddr_in);
+ struct sockaddr_in* addr4 = reinterpret_cast<sockaddr_in*>(storage->addr);
+ addr4->sin_port = base::HostToNet16(port);
+ addr4->sin_family = AF_INET;
+ memcpy(&addr4->sin_addr, bytes, 4);
+}
+
+// Fills in sockaddr for the given 128-bit address (IPv6.)
+// |bytes| should be an array of length 16.
+void MakeIPv6Address(const uint8* bytes, int port, SockaddrStorage* storage) {
+ memset(&storage->addr_storage, 0, sizeof(storage->addr_storage));
+ storage->addr_len = sizeof(struct sockaddr_in6);
+ struct sockaddr_in6* addr6 = reinterpret_cast<sockaddr_in6*>(storage->addr);
+ addr6->sin6_port = base::HostToNet16(port);
+ addr6->sin6_family = AF_INET6;
+ memcpy(&addr6->sin6_addr, bytes, 16);
+}
+
+// A helper for IDN*{Fast,Slow}.
+// Append "::<language list>" to |expected| and |actual| to make it
+// easy to tell which sub-case fails without debugging.
+void AppendLanguagesToOutputs(const char* languages,
+ base::string16* expected,
+ base::string16* actual) {
+ base::string16 to_append = ASCIIToUTF16("::") + ASCIIToUTF16(languages);
+ expected->append(to_append);
+ actual->append(to_append);
+}
+
+// A pair of helpers for the FormatUrlWithOffsets() test.
+void VerboseExpect(size_t expected,
+ size_t actual,
+ const std::string& original_url,
+ size_t position,
+ const base::string16& formatted_url) {
+ EXPECT_EQ(expected, actual) << "Original URL: " << original_url
+ << " (at char " << position << ")\nFormatted URL: " << formatted_url;
+}
+
+void CheckAdjustedOffsets(const std::string& url_string,
+ const std::string& languages,
+ FormatUrlTypes format_types,
+ UnescapeRule::Type unescape_rules,
+ const AdjustOffsetCase* cases,
+ size_t num_cases,
+ const size_t* all_offsets) {
+ GURL url(url_string);
+ for (size_t i = 0; i < num_cases; ++i) {
+ size_t offset = cases[i].input_offset;
+ base::string16 formatted_url = FormatUrl(url, languages, format_types,
+ unescape_rules, NULL, NULL, &offset);
+ VerboseExpect(cases[i].output_offset, offset, url_string, i, formatted_url);
+ }
+
+ size_t url_size = url_string.length();
+ std::vector<size_t> offsets;
+ for (size_t i = 0; i < url_size + 1; ++i)
+ offsets.push_back(i);
+ base::string16 formatted_url = FormatUrlWithOffsets(url, languages,
+ format_types, unescape_rules, NULL, NULL, &offsets);
+ for (size_t i = 0; i < url_size; ++i)
+ VerboseExpect(all_offsets[i], offsets[i], url_string, i, formatted_url);
+ VerboseExpect(kNpos, offsets[url_size], url_string, url_size, formatted_url);
+}
+
+// Helper to strignize an IP number (used to define expectations).
+std::string DumpIPNumber(const IPAddressNumber& v) {
+ std::string out;
+ for (size_t i = 0; i < v.size(); ++i) {
+ if (i != 0)
+ out.append(",");
+ out.append(base::IntToString(static_cast<int>(v[i])));
+ }
+ return out;
+}
+
+void RunGenerateFileNameTestCase(const GenerateFilenameCase* test_case) {
+ std::string default_filename(WideToUTF8(test_case->default_filename));
+ base::FilePath file_path = GenerateFileName(
+ GURL(test_case->url), test_case->content_disp_header,
+ test_case->referrer_charset, test_case->suggested_filename,
+ test_case->mime_type, default_filename);
+ EXPECT_EQ(test_case->expected_filename,
+ file_util::FilePathAsWString(file_path))
+ << "test case at line number: " << test_case->lineno;
+}
+
+} // anonymous namespace
+
+TEST(NetUtilTest, FileURLConversion) {
+ // a list of test file names and the corresponding URLs
+ const FileCase round_trip_cases[] = {
+#if defined(OS_WIN)
+ {L"C:\\foo\\bar.txt", "file:///C:/foo/bar.txt"},
+ {L"\\\\some computer\\foo\\bar.txt",
+ "file://some%20computer/foo/bar.txt"}, // UNC
+ {L"D:\\Name;with%some symbols*#",
+ "file:///D:/Name%3Bwith%25some%20symbols*%23"},
+ // issue 14153: To be tested with the OS default codepage other than 1252.
+ {L"D:\\latin1\\caf\x00E9\x00DD.txt",
+ "file:///D:/latin1/caf%C3%A9%C3%9D.txt"},
+ {L"D:\\otherlatin\\caf\x0119.txt",
+ "file:///D:/otherlatin/caf%C4%99.txt"},
+ {L"D:\\greek\\\x03B1\x03B2\x03B3.txt",
+ "file:///D:/greek/%CE%B1%CE%B2%CE%B3.txt"},
+ {L"D:\\Chinese\\\x6240\x6709\x4e2d\x6587\x7f51\x9875.doc",
+ "file:///D:/Chinese/%E6%89%80%E6%9C%89%E4%B8%AD%E6%96%87%E7%BD%91"
+ "%E9%A1%B5.doc"},
+ {L"D:\\plane1\\\xD835\xDC00\xD835\xDC01.txt", // Math alphabet "AB"
+ "file:///D:/plane1/%F0%9D%90%80%F0%9D%90%81.txt"},
+#elif defined(OS_POSIX)
+ {L"/foo/bar.txt", "file:///foo/bar.txt"},
+ {L"/foo/BAR.txt", "file:///foo/BAR.txt"},
+ {L"/C:/foo/bar.txt", "file:///C:/foo/bar.txt"},
+ {L"/foo/bar?.txt", "file:///foo/bar%3F.txt"},
+ {L"/some computer/foo/bar.txt", "file:///some%20computer/foo/bar.txt"},
+ {L"/Name;with%some symbols*#", "file:///Name%3Bwith%25some%20symbols*%23"},
+ {L"/latin1/caf\x00E9\x00DD.txt", "file:///latin1/caf%C3%A9%C3%9D.txt"},
+ {L"/otherlatin/caf\x0119.txt", "file:///otherlatin/caf%C4%99.txt"},
+ {L"/greek/\x03B1\x03B2\x03B3.txt", "file:///greek/%CE%B1%CE%B2%CE%B3.txt"},
+ {L"/Chinese/\x6240\x6709\x4e2d\x6587\x7f51\x9875.doc",
+ "file:///Chinese/%E6%89%80%E6%9C%89%E4%B8%AD%E6%96%87%E7%BD"
+ "%91%E9%A1%B5.doc"},
+ {L"/plane1/\x1D400\x1D401.txt", // Math alphabet "AB"
+ "file:///plane1/%F0%9D%90%80%F0%9D%90%81.txt"},
+#endif
+ };
+
+ // First, we'll test that we can round-trip all of the above cases of URLs
+ base::FilePath output;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(round_trip_cases); i++) {
+ // convert to the file URL
+ GURL file_url(FilePathToFileURL(
+ file_util::WStringAsFilePath(round_trip_cases[i].file)));
+ EXPECT_EQ(round_trip_cases[i].url, file_url.spec());
+
+ // Back to the filename.
+ EXPECT_TRUE(FileURLToFilePath(file_url, &output));
+ EXPECT_EQ(round_trip_cases[i].file, file_util::FilePathAsWString(output));
+ }
+
+ // Test that various file: URLs get decoded into the correct file type
+ FileCase url_cases[] = {
+#if defined(OS_WIN)
+ {L"C:\\foo\\bar.txt", "file:c|/foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", "file:/c:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", "file://foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", "file:///c:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", "file:////foo\\bar.txt"},
+ {L"\\\\foo\\bar.txt", "file:/foo/bar.txt"},
+ {L"\\\\foo\\bar.txt", "file://foo\\bar.txt"},
+ {L"C:\\foo\\bar.txt", "file:\\\\\\c:/foo/bar.txt"},
+#elif defined(OS_POSIX)
+ {L"/c:/foo/bar.txt", "file:/c:/foo/bar.txt"},
+ {L"/c:/foo/bar.txt", "file:///c:/foo/bar.txt"},
+ {L"/foo/bar.txt", "file:/foo/bar.txt"},
+ {L"/c:/foo/bar.txt", "file:\\\\\\c:/foo/bar.txt"},
+ {L"/foo/bar.txt", "file:foo/bar.txt"},
+ {L"/bar.txt", "file://foo/bar.txt"},
+ {L"/foo/bar.txt", "file:///foo/bar.txt"},
+ {L"/foo/bar.txt", "file:////foo/bar.txt"},
+ {L"/foo/bar.txt", "file:////foo//bar.txt"},
+ {L"/foo/bar.txt", "file:////foo///bar.txt"},
+ {L"/foo/bar.txt", "file:////foo////bar.txt"},
+ {L"/c:/foo/bar.txt", "file:\\\\\\c:/foo/bar.txt"},
+ {L"/c:/foo/bar.txt", "file:c:/foo/bar.txt"},
+ // We get these wrong because GURL turns back slashes into forward
+ // slashes.
+ //{L"/foo%5Cbar.txt", "file://foo\\bar.txt"},
+ //{L"/c|/foo%5Cbar.txt", "file:c|/foo\\bar.txt"},
+ //{L"/foo%5Cbar.txt", "file://foo\\bar.txt"},
+ //{L"/foo%5Cbar.txt", "file:////foo\\bar.txt"},
+ //{L"/foo%5Cbar.txt", "file://foo\\bar.txt"},
+#endif
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(url_cases); i++) {
+ FileURLToFilePath(GURL(url_cases[i].url), &output);
+ EXPECT_EQ(url_cases[i].file, file_util::FilePathAsWString(output));
+ }
+
+ // Unfortunately, UTF8ToWide discards invalid UTF8 input.
+#ifdef BUG_878908_IS_FIXED
+ // Test that no conversion happens if the UTF-8 input is invalid, and that
+ // the input is preserved in UTF-8
+ const char invalid_utf8[] = "file:///d:/Blah/\xff.doc";
+ const wchar_t invalid_wide[] = L"D:\\Blah\\\xff.doc";
+ EXPECT_TRUE(FileURLToFilePath(
+ GURL(std::string(invalid_utf8)), &output));
+ EXPECT_EQ(std::wstring(invalid_wide), output);
+#endif
+
+ // Test that if a file URL is malformed, we get a failure
+ EXPECT_FALSE(FileURLToFilePath(GURL("filefoobar"), &output));
+}
+
+TEST(NetUtilTest, GetIdentityFromURL) {
+ struct {
+ const char* input_url;
+ const char* expected_username;
+ const char* expected_password;
+ } tests[] = {
+ {
+ "http://username:password@google.com",
+ "username",
+ "password",
+ },
+ { // Test for http://crbug.com/19200
+ "http://username:p@ssword@google.com",
+ "username",
+ "p@ssword",
+ },
+ { // Special URL characters should be unescaped.
+ "http://username:p%3fa%26s%2fs%23@google.com",
+ "username",
+ "p?a&s/s#",
+ },
+ { // Username contains %20.
+ "http://use rname:password@google.com",
+ "use rname",
+ "password",
+ },
+ { // Keep %00 as is.
+ "http://use%00rname:password@google.com",
+ "use%00rname",
+ "password",
+ },
+ { // Use a '+' in the username.
+ "http://use+rname:password@google.com",
+ "use+rname",
+ "password",
+ },
+ { // Use a '&' in the password.
+ "http://username:p&ssword@google.com",
+ "username",
+ "p&ssword",
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ tests[i].input_url));
+ GURL url(tests[i].input_url);
+
+ base::string16 username, password;
+ GetIdentityFromURL(url, &username, &password);
+
+ EXPECT_EQ(ASCIIToUTF16(tests[i].expected_username), username);
+ EXPECT_EQ(ASCIIToUTF16(tests[i].expected_password), password);
+ }
+}
+
+// Try extracting a username which was encoded with UTF8.
+TEST(NetUtilTest, GetIdentityFromURL_UTF8) {
+ GURL url(WideToUTF16(L"http://foo:\x4f60\x597d@blah.com"));
+
+ EXPECT_EQ("foo", url.username());
+ EXPECT_EQ("%E4%BD%A0%E5%A5%BD", url.password());
+
+ // Extract the unescaped identity.
+ base::string16 username, password;
+ GetIdentityFromURL(url, &username, &password);
+
+ // Verify that it was decoded as UTF8.
+ EXPECT_EQ(ASCIIToUTF16("foo"), username);
+ EXPECT_EQ(WideToUTF16(L"\x4f60\x597d"), password);
+}
+
+// Just a bunch of fake headers.
+const char* google_headers =
+ "HTTP/1.1 200 OK\n"
+ "Content-TYPE: text/html; charset=utf-8\n"
+ "Content-disposition: attachment; filename=\"download.pdf\"\n"
+ "Content-Length: 378557\n"
+ "X-Google-Google1: 314159265\n"
+ "X-Google-Google2: aaaa2:7783,bbb21:9441\n"
+ "X-Google-Google4: home\n"
+ "Transfer-Encoding: chunked\n"
+ "Set-Cookie: HEHE_AT=6666x66beef666x6-66xx6666x66; Path=/mail\n"
+ "Set-Cookie: HEHE_HELP=owned:0;Path=/\n"
+ "Set-Cookie: S=gmail=Xxx-beefbeefbeef_beefb:gmail_yj=beefbeef000beefbee"
+ "fbee:gmproxy=bee-fbeefbe; Domain=.google.com; Path=/\n"
+ "X-Google-Google2: /one/two/three/four/five/six/seven-height/nine:9411\n"
+ "Server: GFE/1.3\n"
+ "Transfer-Encoding: chunked\n"
+ "Date: Mon, 13 Nov 2006 21:38:09 GMT\n"
+ "Expires: Tue, 14 Nov 2006 19:23:58 GMT\n"
+ "X-Malformed: bla; arg=test\"\n"
+ "X-Malformed2: bla; arg=\n"
+ "X-Test: bla; arg1=val1; arg2=val2";
+
+TEST(NetUtilTest, GetSpecificHeader) {
+ const HeaderCase tests[] = {
+ {"content-type", "text/html; charset=utf-8"},
+ {"CONTENT-LENGTH", "378557"},
+ {"Date", "Mon, 13 Nov 2006 21:38:09 GMT"},
+ {"Bad-Header", ""},
+ {"", ""},
+ };
+
+ // Test first with google_headers.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string result =
+ GetSpecificHeader(google_headers, tests[i].header_name);
+ EXPECT_EQ(result, tests[i].expected);
+ }
+
+ // Test again with empty headers.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string result = GetSpecificHeader(std::string(), tests[i].header_name);
+ EXPECT_EQ(result, std::string());
+ }
+}
+
+TEST(NetUtilTest, IDNToUnicodeFast) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(idn_cases); i++) {
+ for (size_t j = 0; j < arraysize(kLanguages); j++) {
+ // ja || zh-TW,en || ko,ja -> IDNToUnicodeSlow
+ if (j == 3 || j == 17 || j == 18)
+ continue;
+ base::string16 output(IDNToUnicode(idn_cases[i].input, kLanguages[j]));
+ base::string16 expected(idn_cases[i].unicode_allowed[j] ?
+ WideToUTF16(idn_cases[i].unicode_output) :
+ ASCIIToUTF16(idn_cases[i].input));
+ AppendLanguagesToOutputs(kLanguages[j], &expected, &output);
+ EXPECT_EQ(expected, output);
+ }
+ }
+}
+
+TEST(NetUtilTest, IDNToUnicodeSlow) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(idn_cases); i++) {
+ for (size_t j = 0; j < arraysize(kLanguages); j++) {
+ // !(ja || zh-TW,en || ko,ja) -> IDNToUnicodeFast
+ if (!(j == 3 || j == 17 || j == 18))
+ continue;
+ base::string16 output(IDNToUnicode(idn_cases[i].input, kLanguages[j]));
+ base::string16 expected(idn_cases[i].unicode_allowed[j] ?
+ WideToUTF16(idn_cases[i].unicode_output) :
+ ASCIIToUTF16(idn_cases[i].input));
+ AppendLanguagesToOutputs(kLanguages[j], &expected, &output);
+ EXPECT_EQ(expected, output);
+ }
+ }
+}
+
+TEST(NetUtilTest, CompliantHost) {
+ const CompliantHostCase compliant_host_cases[] = {
+ {"", "", false},
+ {"a", "", true},
+ {"-", "", false},
+ {".", "", false},
+ {"9", "", true},
+ {"9a", "", true},
+ {"a.", "", true},
+ {"a.a", "", true},
+ {"9.a", "", true},
+ {"a.9", "", true},
+ {"_9a", "", false},
+ {"-9a", "", false},
+ {"-9a", "a", true},
+ {"a.a9", "", true},
+ {"a.-a9", "", false},
+ {"a+9a", "", false},
+ {"-a.a9", "", true},
+ {"1-.a-b", "", true},
+ {"1_.a-b", "", false},
+ {"1-2.a_b", "", true},
+ {"a.b.c.d.e", "", true},
+ {"1.2.3.4.5", "", true},
+ {"1.2.3.4.5.", "", true},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(compliant_host_cases); ++i) {
+ EXPECT_EQ(compliant_host_cases[i].expected_output,
+ IsCanonicalizedHostCompliant(compliant_host_cases[i].host,
+ compliant_host_cases[i].desired_tld));
+ }
+}
+
+TEST(NetUtilTest, StripWWW) {
+ EXPECT_EQ(base::string16(), StripWWW(base::string16()));
+ EXPECT_EQ(base::string16(), StripWWW(ASCIIToUTF16("www.")));
+ EXPECT_EQ(ASCIIToUTF16("blah"), StripWWW(ASCIIToUTF16("www.blah")));
+ EXPECT_EQ(ASCIIToUTF16("blah"), StripWWW(ASCIIToUTF16("blah")));
+}
+
+#if defined(OS_WIN)
+#define JPEG_EXT L".jpg"
+#define HTML_EXT L".htm"
+#elif defined(OS_MACOSX)
+#define JPEG_EXT L".jpeg"
+#define HTML_EXT L".html"
+#else
+#define JPEG_EXT L".jpg"
+#define HTML_EXT L".html"
+#endif
+#define TXT_EXT L".txt"
+#define TAR_EXT L".tar"
+
+TEST(NetUtilTest, GenerateSafeFileName) {
+ const struct {
+ const char* mime_type;
+ const base::FilePath::CharType* filename;
+ const base::FilePath::CharType* expected_filename;
+ } safe_tests[] = {
+#if defined(OS_WIN)
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\bar.htm"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.htm")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\bar.html"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.html")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\bar"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.htm")
+ },
+ {
+ "image/png",
+ FILE_PATH_LITERAL("C:\\bar.html"),
+ FILE_PATH_LITERAL("C:\\bar.html")
+ },
+ {
+ "image/png",
+ FILE_PATH_LITERAL("C:\\bar"),
+ FILE_PATH_LITERAL("C:\\bar.png")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\bar.exe"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.exe")
+ },
+ {
+ "image/gif",
+ FILE_PATH_LITERAL("C:\\foo\\bar.exe"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.exe")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\google.com"),
+ FILE_PATH_LITERAL("C:\\foo\\google.com")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\con.htm"),
+ FILE_PATH_LITERAL("C:\\foo\\_con.htm")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\con"),
+ FILE_PATH_LITERAL("C:\\foo\\_con.htm")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\harmless.{not-really-this-may-be-a-guid}"),
+ FILE_PATH_LITERAL("C:\\foo\\harmless.download")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\harmless.local"),
+ FILE_PATH_LITERAL("C:\\foo\\harmless.download")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\harmless.lnk"),
+ FILE_PATH_LITERAL("C:\\foo\\harmless.download")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("C:\\foo\\harmless.{mismatched-"),
+ FILE_PATH_LITERAL("C:\\foo\\harmless.{mismatched-")
+ },
+ // Allow extension synonyms.
+ {
+ "image/jpeg",
+ FILE_PATH_LITERAL("C:\\foo\\bar.jpg"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.jpg")
+ },
+ {
+ "image/jpeg",
+ FILE_PATH_LITERAL("C:\\foo\\bar.jpeg"),
+ FILE_PATH_LITERAL("C:\\foo\\bar.jpeg")
+ },
+#else // !defined(OS_WIN)
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/bar.htm"),
+ FILE_PATH_LITERAL("/foo/bar.htm")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/bar.html"),
+ FILE_PATH_LITERAL("/foo/bar.html")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/bar"),
+ FILE_PATH_LITERAL("/foo/bar.html")
+ },
+ {
+ "image/png",
+ FILE_PATH_LITERAL("/bar.html"),
+ FILE_PATH_LITERAL("/bar.html")
+ },
+ {
+ "image/png",
+ FILE_PATH_LITERAL("/bar"),
+ FILE_PATH_LITERAL("/bar.png")
+ },
+ {
+ "image/gif",
+ FILE_PATH_LITERAL("/foo/bar.exe"),
+ FILE_PATH_LITERAL("/foo/bar.exe")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/google.com"),
+ FILE_PATH_LITERAL("/foo/google.com")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/con.htm"),
+ FILE_PATH_LITERAL("/foo/con.htm")
+ },
+ {
+ "text/html",
+ FILE_PATH_LITERAL("/foo/con"),
+ FILE_PATH_LITERAL("/foo/con.html")
+ },
+ // Allow extension synonyms.
+ {
+ "image/jpeg",
+ FILE_PATH_LITERAL("/bar.jpg"),
+ FILE_PATH_LITERAL("/bar.jpg")
+ },
+ {
+ "image/jpeg",
+ FILE_PATH_LITERAL("/bar.jpeg"),
+ FILE_PATH_LITERAL("/bar.jpeg")
+ },
+#endif // !defined(OS_WIN)
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(safe_tests); ++i) {
+ base::FilePath file_path(safe_tests[i].filename);
+ GenerateSafeFileName(safe_tests[i].mime_type, false, &file_path);
+ EXPECT_EQ(safe_tests[i].expected_filename, file_path.value())
+ << "Iteration " << i;
+ }
+}
+
+TEST(NetUtilTest, GenerateFileName) {
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ // This test doesn't run when the locale is not UTF-8 because some of the
+ // string conversions fail. This is OK (we have the default value) but they
+ // don't match our expectations.
+ std::string locale = setlocale(LC_CTYPE, NULL);
+ StringToLowerASCII(&locale);
+ EXPECT_TRUE(locale.find("utf-8") != std::string::npos ||
+ locale.find("utf8") != std::string::npos)
+ << "Your locale (" << locale << ") must be set to UTF-8 "
+ << "for this test to pass!";
+#endif
+
+ // Tests whether the correct filename is selected from the the given
+ // parameters and that Content-Disposition headers are properly
+ // handled including failovers when the header is malformed.
+ const GenerateFilenameCase selection_tests[] = {
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=test.html",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename= \"test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename = \"test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ { // filename is whitespace. Should failover to URL host
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename= ",
+ "",
+ "",
+ "",
+ L"",
+ L"www.google.com"
+ },
+ { // No filename.
+ __LINE__,
+ "http://www.google.com/path/test.html",
+ "attachment",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ { // Ditto
+ __LINE__,
+ "http://www.google.com/path/test.html",
+ "attachment;",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ { // No C-D
+ __LINE__,
+ "http://www.google.com/",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"www.google.com"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/test.html",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ { // Now that we use src/url's ExtractFileName, this case falls back to
+ // the hostname. If this behavior is not desirable, we'd better change
+ // ExtractFileName (in url_parse).
+ __LINE__,
+ "http://www.google.com/path/",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"www.google.com"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/path",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"path"
+ },
+ {
+ __LINE__,
+ "file:///",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"download"
+ },
+ {
+ __LINE__,
+ "file:///path/testfile",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"testfile"
+ },
+ {
+ __LINE__,
+ "non-standard-scheme:",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"download"
+ },
+ { // C-D should override default
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename =\"test.html\"",
+ "",
+ "",
+ "",
+ L"download",
+ L"test.html"
+ },
+ { // But the URL shouldn't
+ __LINE__,
+ "http://www.google.com/",
+ "",
+ "",
+ "",
+ "",
+ L"download",
+ L"download"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"../test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"-test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"..\\test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"..\\\\test.html\"",
+ "",
+ "",
+ "",
+ L"",
+ L"-test.html"
+ },
+ { // Filename disappears after leading and trailing periods are removed.
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"..\"",
+ "",
+ "",
+ "",
+ L"default",
+ L"default"
+ },
+ { // C-D specified filename disappears. Failover to final filename.
+ __LINE__,
+ "http://www.google.com/test.html",
+ "attachment; filename=\"..\"",
+ "",
+ "",
+ "",
+ L"default",
+ L"default"
+ },
+ // Below is a small subset of cases taken from HttpContentDisposition tests.
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"%EC%98%88%EC%88%A0%20"
+ "%EC%98%88%EC%88%A0.jpg\"",
+ "",
+ "",
+ "",
+ L"",
+ L"\uc608\uc220 \uc608\uc220.jpg"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg",
+ "",
+ "",
+ "",
+ "",
+ L"download",
+ L"\uc608\uc220 \uc608\uc220.jpg"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment;",
+ "",
+ "",
+ "",
+ L"\uB2E4\uC6B4\uB85C\uB4DC",
+ L"\uB2E4\uC6B4\uB85C\uB4DC"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/",
+ "attachment; filename=\"=?EUC-JP?Q?=B7=DD=BD="
+ "D13=2Epng?=\"",
+ "",
+ "",
+ "",
+ L"download",
+ L"\u82b8\u88533.png"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/images?id=3",
+ "attachment; filename=caf\xc3\xa9.png",
+ "iso-8859-1",
+ "",
+ "",
+ L"",
+ L"caf\u00e9.png"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/images?id=3",
+ "attachment; filename=caf\xe5.png",
+ "windows-1253",
+ "",
+ "",
+ L"",
+ L"caf\u03b5.png"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/file?id=3",
+ "attachment; name=\xcf\xc2\xd4\xd8.zip",
+ "GBK",
+ "",
+ "",
+ L"",
+ L"\u4e0b\u8f7d.zip"
+ },
+ { // Invalid C-D header. Extracts filename from url.
+ __LINE__,
+ "http://www.google.com/test.html",
+ "attachment; filename==?iiso88591?Q?caf=EG?=",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ // about: and data: URLs
+ {
+ __LINE__,
+ "about:chrome",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"download"
+ },
+ {
+ __LINE__,
+ "data:,looks/like/a.path",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"download"
+ },
+ {
+ __LINE__,
+ "data:text/plain;base64,VG8gYmUgb3Igbm90IHRvIGJlLg=",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"download"
+ },
+ {
+ __LINE__,
+ "data:,looks/like/a.path",
+ "",
+ "",
+ "",
+ "",
+ L"default_filename_is_given",
+ L"default_filename_is_given"
+ },
+ {
+ __LINE__,
+ "data:,looks/like/a.path",
+ "",
+ "",
+ "",
+ "",
+ L"\u65e5\u672c\u8a9e", // Japanese Kanji.
+ L"\u65e5\u672c\u8a9e"
+ },
+ { // The filename encoding is specified by the referrer charset.
+ __LINE__,
+ "http://example.com/V%FDvojov%E1%20psychologie.doc",
+ "",
+ "iso-8859-1",
+ "",
+ "",
+ L"",
+ L"V\u00fdvojov\u00e1 psychologie.doc"
+ },
+ { // Suggested filename takes precedence over URL
+ __LINE__,
+ "http://www.google.com/test",
+ "",
+ "",
+ "suggested",
+ "",
+ L"",
+ L"suggested"
+ },
+ { // The content-disposition has higher precedence over the suggested name.
+ __LINE__,
+ "http://www.google.com/test",
+ "attachment; filename=test.html",
+ "",
+ "suggested",
+ "",
+ L"",
+ L"test.html"
+ },
+#if 0
+ { // The filename encoding doesn't match the referrer charset, the system
+ // charset, or UTF-8.
+ // TODO(jshin): we need to handle this case.
+ __LINE__,
+ "http://example.com/V%FDvojov%E1%20psychologie.doc",
+ "",
+ "utf-8",
+ "",
+ "",
+ L"",
+ L"V\u00fdvojov\u00e1 psychologie.doc",
+ },
+#endif
+ // Raw 8bit characters in C-D
+ {
+ __LINE__,
+ "http://www.example.com/images?id=3",
+ "attachment; filename=caf\xc3\xa9.png",
+ "iso-8859-1",
+ "",
+ "image/png",
+ L"",
+ L"caf\u00e9.png"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/images?id=3",
+ "attachment; filename=caf\xe5.png",
+ "windows-1253",
+ "",
+ "image/png",
+ L"",
+ L"caf\u03b5.png"
+ },
+ { // No 'filename' keyword in the disposition, use the URL
+ __LINE__,
+ "http://www.evil.com/my_download.txt",
+ "a_file_name.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"my_download.txt"
+ },
+ { // Spaces in the disposition file name
+ __LINE__,
+ "http://www.frontpagehacker.com/a_download.exe",
+ "filename=My Downloaded File.exe",
+ "",
+ "",
+ "application/octet-stream",
+ L"download",
+ L"My Downloaded File.exe"
+ },
+ { // % encoded
+ __LINE__,
+ "http://www.examples.com/",
+ "attachment; "
+ "filename=\"%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg\"",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"\uc608\uc220 \uc608\uc220.jpg"
+ },
+ { // name= parameter
+ __LINE__,
+ "http://www.examples.com/q.cgi?id=abc",
+ "attachment; name=abc de.pdf",
+ "",
+ "",
+ "application/octet-stream",
+ L"download",
+ L"abc de.pdf"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/path",
+ "filename=\"=?EUC-JP?Q?=B7=DD=BD=D13=2Epng?=\"",
+ "",
+ "",
+ "image/png",
+ L"download",
+ L"\x82b8\x8853" L"3.png"
+ },
+ { // The following two have invalid CD headers and filenames come from the
+ // URL.
+ __LINE__,
+ "http://www.example.com/test%20123",
+ "attachment; filename==?iiso88591?Q?caf=EG?=",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"test 123" JPEG_EXT
+ },
+ {
+ __LINE__,
+ "http://www.google.com/%EC%98%88%EC%88%A0%20%EC%98%88%EC%88%A0.jpg",
+ "malformed_disposition",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"\uc608\uc220 \uc608\uc220.jpg"
+ },
+ { // Invalid C-D. No filename from URL. Falls back to 'download'.
+ __LINE__,
+ "http://www.google.com/path1/path2/",
+ "attachment; filename==?iso88591?Q?caf=E3?",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"download" JPEG_EXT
+ },
+ };
+
+ // Tests filename generation. Once the correct filename is
+ // selected, they should be passed through the validation steps and
+ // a correct extension should be added if necessary.
+ const GenerateFilenameCase generation_tests[] = {
+ // Dotfiles. Ensures preceeding period(s) stripped.
+ {
+ __LINE__,
+ "http://www.google.com/.test.html",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"test.html"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/.test",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"test"
+ },
+ {
+ __LINE__,
+ "http://www.google.com/..test",
+ "",
+ "",
+ "",
+ "",
+ L"",
+ L"test"
+ },
+ { // Disposition has relative paths, remove directory separators
+ __LINE__,
+ "http://www.evil.com/my_download.txt",
+ "filename=../../../../././../a_file_name.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"-..-..-..-.-.-..-a_file_name.txt"
+ },
+ { // Disposition has parent directories, remove directory separators
+ __LINE__,
+ "http://www.evil.com/my_download.txt",
+ "filename=dir1/dir2/a_file_name.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"dir1-dir2-a_file_name.txt"
+ },
+ { // Disposition has relative paths, remove directory separators
+ __LINE__,
+ "http://www.evil.com/my_download.txt",
+ "filename=..\\..\\..\\..\\.\\.\\..\\a_file_name.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"-..-..-..-.-.-..-a_file_name.txt"
+ },
+ { // Disposition has parent directories, remove directory separators
+ __LINE__,
+ "http://www.evil.com/my_download.txt",
+ "filename=dir1\\dir2\\a_file_name.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"dir1-dir2-a_file_name.txt"
+ },
+ { // No useful information in disposition or URL, use default
+ __LINE__,
+ "http://www.truncated.com/path/",
+ "",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"download" TXT_EXT
+ },
+ { // Filename looks like HTML?
+ __LINE__,
+ "http://www.evil.com/get/malware/here",
+ "filename=\"<blink>Hello kitty</blink>\"",
+ "",
+ "",
+ "text/plain",
+ L"default",
+ L"-blink-Hello kitty--blink-" TXT_EXT
+ },
+ { // A normal avi should get .avi and not .avi.avi
+ __LINE__,
+ "https://blah.google.com/misc/2.avi",
+ "",
+ "",
+ "",
+ "video/x-msvideo",
+ L"download",
+ L"2.avi"
+ },
+ { // Extension generation
+ __LINE__,
+ "http://www.example.com/my-cat",
+ "filename=my-cat",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"my-cat" JPEG_EXT
+ },
+ {
+ __LINE__,
+ "http://www.example.com/my-cat",
+ "filename=my-cat",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"my-cat.txt"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/my-cat",
+ "filename=my-cat",
+ "",
+ "",
+ "text/html",
+ L"download",
+ L"my-cat" HTML_EXT
+ },
+ { // Unknown MIME type
+ __LINE__,
+ "http://www.example.com/my-cat",
+ "filename=my-cat",
+ "",
+ "",
+ "dance/party",
+ L"download",
+ L"my-cat"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/my-cat.jpg",
+ "filename=my-cat.jpg",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"my-cat.jpg"
+ },
+ // Windows specific tests
+#if defined(OS_WIN)
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.exe",
+ "filename=evil.exe",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"evil.exe"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/ok.exe",
+ "filename=ok.exe",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"ok.exe"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.dll",
+ "filename=evil.dll",
+ "",
+ "",
+ "dance/party",
+ L"download",
+ L"evil.dll"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.exe",
+ "filename=evil",
+ "",
+ "",
+ "application/rss+xml",
+ L"download",
+ L"evil"
+ },
+ // Test truncation of trailing dots and spaces
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.exe ",
+ "filename=evil.exe ",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"evil.exe"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.exe.",
+ "filename=evil.exe.",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"evil.exe-"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.exe. . .",
+ "filename=evil.exe. . .",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"evil.exe-------"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/evil.",
+ "filename=evil.",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"evil-"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/. . . . .",
+ "filename=. . . . .",
+ "",
+ "",
+ "binary/octet-stream",
+ L"download",
+ L"download"
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/attachment?name=meh.exe%C2%A0",
+ "attachment; filename=\"meh.exe\xC2\xA0\"",
+ "",
+ "",
+ "binary/octet-stream",
+ L"",
+ L"meh.exe-"
+ },
+#endif // OS_WIN
+ {
+ __LINE__,
+ "http://www.goodguy.com/utils.js",
+ "filename=utils.js",
+ "",
+ "",
+ "application/x-javascript",
+ L"download",
+ L"utils.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/contacts.js",
+ "filename=contacts.js",
+ "",
+ "",
+ "application/json",
+ L"download",
+ L"contacts.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/utils.js",
+ "filename=utils.js",
+ "",
+ "",
+ "text/javascript",
+ L"download",
+ L"utils.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/utils.js",
+ "filename=utils.js",
+ "",
+ "",
+ "text/javascript;version=2",
+ L"download",
+ L"utils.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/utils.js",
+ "filename=utils.js",
+ "",
+ "",
+ "application/ecmascript",
+ L"download",
+ L"utils.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/utils.js",
+ "filename=utils.js",
+ "",
+ "",
+ "application/ecmascript;version=4",
+ L"download",
+ L"utils.js"
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/program.exe",
+ "filename=program.exe",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+ L"program.exe"
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/../foo.txt",
+ "filename=../foo.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"-foo.txt"
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/..\\foo.txt",
+ "filename=..\\foo.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"-foo.txt"
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/.hidden",
+ "filename=.hidden",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"hidden" TXT_EXT
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/trailing.",
+ "filename=trailing.",
+ "",
+ "",
+ "dance/party",
+ L"download",
+#if defined(OS_WIN)
+ L"trailing-"
+#else
+ L"trailing"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/trailing.",
+ "filename=trailing.",
+ "",
+ "",
+ "text/plain",
+ L"download",
+#if defined(OS_WIN)
+ L"trailing-" TXT_EXT
+#else
+ L"trailing" TXT_EXT
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/.",
+ "filename=.",
+ "",
+ "",
+ "dance/party",
+ L"download",
+ L"download"
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/..",
+ "filename=..",
+ "",
+ "",
+ "dance/party",
+ L"download",
+ L"download"
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/...",
+ "filename=...",
+ "",
+ "",
+ "dance/party",
+ L"download",
+ L"download"
+ },
+ { // Note that this one doesn't have "filename=" on it.
+ __LINE__,
+ "http://www.evil.com/",
+ "a_file_name.txt",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"download" JPEG_EXT
+ },
+ {
+ __LINE__,
+ "http://www.evil.com/",
+ "filename=",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"download" JPEG_EXT
+ },
+ {
+ __LINE__,
+ "http://www.example.com/simple",
+ "filename=simple",
+ "",
+ "",
+ "application/octet-stream",
+ L"download",
+ L"simple"
+ },
+ // Reserved words on Windows
+ {
+ __LINE__,
+ "http://www.goodguy.com/COM1",
+ "filename=COM1",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"_COM1"
+#else
+ L"COM1"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/COM4.txt",
+ "filename=COM4.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+#if defined(OS_WIN)
+ L"_COM4.txt"
+#else
+ L"COM4.txt"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/lpt1.TXT",
+ "filename=lpt1.TXT",
+ "",
+ "",
+ "text/plain",
+ L"download",
+#if defined(OS_WIN)
+ L"_lpt1.TXT"
+#else
+ L"lpt1.TXT"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/clock$.txt",
+ "filename=clock$.txt",
+ "",
+ "",
+ "text/plain",
+ L"download",
+#if defined(OS_WIN)
+ L"_clock$.txt"
+#else
+ L"clock$.txt"
+#endif
+ },
+ { // Validation should also apply to sugested name
+ __LINE__,
+ "http://www.goodguy.com/blah$.txt",
+ "filename=clock$.txt",
+ "",
+ "clock$.txt",
+ "text/plain",
+ L"download",
+#if defined(OS_WIN)
+ L"_clock$.txt"
+#else
+ L"clock$.txt"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.goodguy.com/mycom1.foo",
+ "filename=mycom1.foo",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"mycom1.foo"
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/Setup.exe.local",
+ "filename=Setup.exe.local",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"Setup.exe.download"
+#else
+ L"Setup.exe.local"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/Setup.exe.local",
+ "filename=Setup.exe.local.local",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"Setup.exe.local.download"
+#else
+ L"Setup.exe.local.local"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/Setup.exe.lnk",
+ "filename=Setup.exe.lnk",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"Setup.exe.download"
+#else
+ L"Setup.exe.lnk"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/Desktop.ini",
+ "filename=Desktop.ini",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"_Desktop.ini"
+#else
+ L"Desktop.ini"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.badguy.com/Thumbs.db",
+ "filename=Thumbs.db",
+ "",
+ "",
+ "application/foo-bar",
+ L"download",
+#if defined(OS_WIN)
+ L"_Thumbs.db"
+#else
+ L"Thumbs.db"
+#endif
+ },
+ {
+ __LINE__,
+ "http://www.hotmail.com",
+ "filename=source.jpg",
+ "",
+ "",
+ "application/x-javascript",
+ L"download",
+ L"source.jpg"
+ },
+ { // http://crbug.com/5772.
+ __LINE__,
+ "http://www.example.com/foo.tar.gz",
+ "",
+ "",
+ "",
+ "application/x-tar",
+ L"download",
+ L"foo.tar.gz"
+ },
+ { // http://crbug.com/52250.
+ __LINE__,
+ "http://www.example.com/foo.tgz",
+ "",
+ "",
+ "",
+ "application/x-tar",
+ L"download",
+ L"foo.tgz"
+ },
+ { // http://crbug.com/7337.
+ __LINE__,
+ "http://maged.lordaeron.org/blank.reg",
+ "",
+ "",
+ "",
+ "text/x-registry",
+ L"download",
+ L"blank.reg"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/bar.tar",
+ "",
+ "",
+ "",
+ "application/x-tar",
+ L"download",
+ L"bar.tar"
+ },
+ {
+ __LINE__,
+ "http://www.example.com/bar.bogus",
+ "",
+ "",
+ "",
+ "application/x-tar",
+ L"download",
+ L"bar.bogus"
+ },
+ { // http://crbug.com/20337
+ __LINE__,
+ "http://www.example.com/.download.txt",
+ "filename=.download.txt",
+ "",
+ "",
+ "text/plain",
+ L"-download",
+ L"download.txt"
+ },
+ { // http://crbug.com/56855.
+ __LINE__,
+ "http://www.example.com/bar.sh",
+ "",
+ "",
+ "",
+ "application/x-sh",
+ L"download",
+ L"bar.sh"
+ },
+ { // http://crbug.com/61571
+ __LINE__,
+ "http://www.example.com/npdf.php?fn=foobar.pdf",
+ "",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"npdf" TXT_EXT
+ },
+ { // Shouldn't overwrite C-D specified extension.
+ __LINE__,
+ "http://www.example.com/npdf.php?fn=foobar.pdf",
+ "filename=foobar.jpg",
+ "",
+ "",
+ "text/plain",
+ L"download",
+ L"foobar.jpg"
+ },
+ { // http://crbug.com/87719
+ __LINE__,
+ "http://www.example.com/image.aspx?id=blargh",
+ "",
+ "",
+ "",
+ "image/jpeg",
+ L"download",
+ L"image" JPEG_EXT
+ },
+#if defined(OS_CHROMEOS)
+ { // http://crosbug.com/26028
+ __LINE__,
+ "http://www.example.com/fooa%cc%88.txt",
+ "",
+ "",
+ "",
+ "image/jpeg",
+ L"foo\xe4",
+ L"foo\xe4.txt"
+ },
+#endif
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(selection_tests); ++i)
+ RunGenerateFileNameTestCase(&selection_tests[i]);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(generation_tests); ++i)
+ RunGenerateFileNameTestCase(&generation_tests[i]);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(generation_tests); ++i) {
+ GenerateFilenameCase test_case = generation_tests[i];
+ test_case.referrer_charset = "GBK";
+ RunGenerateFileNameTestCase(&test_case);
+ }
+}
+
+// This is currently a windows specific function.
+#if defined(OS_WIN)
+namespace {
+
+struct GetDirectoryListingEntryCase {
+ const wchar_t* name;
+ const char* raw_bytes;
+ bool is_dir;
+ int64 filesize;
+ base::Time time;
+ const char* expected;
+};
+
+} // namespace
+TEST(NetUtilTest, GetDirectoryListingEntry) {
+ const GetDirectoryListingEntryCase test_cases[] = {
+ {L"Foo",
+ "",
+ false,
+ 10000,
+ base::Time(),
+ "<script>addRow(\"Foo\",\"Foo\",0,\"9.8 kB\",\"\");</script>\n"},
+ {L"quo\"tes",
+ "",
+ false,
+ 10000,
+ base::Time(),
+ "<script>addRow(\"quo\\\"tes\",\"quo%22tes\",0,\"9.8 kB\",\"\");</script>"
+ "\n"},
+ {L"quo\"tes",
+ "quo\"tes",
+ false,
+ 10000,
+ base::Time(),
+ "<script>addRow(\"quo\\\"tes\",\"quo%22tes\",0,\"9.8 kB\",\"\");</script>"
+ "\n"},
+ // U+D55C0 U+AE00. raw_bytes is empty (either a local file with
+ // UTF-8/UTF-16 encoding or a remote file on an ftp server using UTF-8
+ {L"\xD55C\xAE00.txt",
+ "",
+ false,
+ 10000,
+ base::Time(),
+ "<script>addRow(\"\\uD55C\\uAE00.txt\",\"%ED%95%9C%EA%B8%80.txt\""
+ ",0,\"9.8 kB\",\"\");</script>\n"},
+ // U+D55C0 U+AE00. raw_bytes is the corresponding EUC-KR sequence:
+ // a local or remote file in EUC-KR.
+ {L"\xD55C\xAE00.txt",
+ "\xC7\xD1\xB1\xDB.txt",
+ false,
+ 10000,
+ base::Time(),
+ "<script>addRow(\"\\uD55C\\uAE00.txt\",\"%C7%D1%B1%DB.txt\""
+ ",0,\"9.8 kB\",\"\");</script>\n"},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const std::string results = GetDirectoryListingEntry(
+ WideToUTF16(test_cases[i].name),
+ test_cases[i].raw_bytes,
+ test_cases[i].is_dir,
+ test_cases[i].filesize,
+ test_cases[i].time);
+ EXPECT_EQ(test_cases[i].expected, results);
+ }
+}
+
+#endif
+
+TEST(NetUtilTest, ParseHostAndPort) {
+ const struct {
+ const char* input;
+ bool success;
+ const char* expected_host;
+ int expected_port;
+ } tests[] = {
+ // Valid inputs:
+ {"foo:10", true, "foo", 10},
+ {"foo", true, "foo", -1},
+ {
+ "[1080:0:0:0:8:800:200C:4171]:11",
+ true,
+ "[1080:0:0:0:8:800:200C:4171]",
+ 11,
+ },
+ // Invalid inputs:
+ {"foo:bar", false, "", -1},
+ {"foo:", false, "", -1},
+ {":", false, "", -1},
+ {":80", false, "", -1},
+ {"", false, "", -1},
+ {"porttoolong:300000", false, "", -1},
+ {"usrname@host", false, "", -1},
+ {"usrname:password@host", false, "", -1},
+ {":password@host", false, "", -1},
+ {":password@host:80", false, "", -1},
+ {":password@host", false, "", -1},
+ {"@host", false, "", -1},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string host;
+ int port;
+ bool ok = ParseHostAndPort(tests[i].input, &host, &port);
+
+ EXPECT_EQ(tests[i].success, ok);
+
+ if (tests[i].success) {
+ EXPECT_EQ(tests[i].expected_host, host);
+ EXPECT_EQ(tests[i].expected_port, port);
+ }
+ }
+}
+
+TEST(NetUtilTest, GetHostAndPort) {
+ const struct {
+ GURL url;
+ const char* expected_host_and_port;
+ } tests[] = {
+ { GURL("http://www.foo.com/x"), "www.foo.com:80"},
+ { GURL("http://www.foo.com:21/x"), "www.foo.com:21"},
+
+ // For IPv6 literals should always include the brackets.
+ { GURL("http://[1::2]/x"), "[1::2]:80"},
+ { GURL("http://[::a]:33/x"), "[::a]:33"},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string host_and_port = GetHostAndPort(tests[i].url);
+ EXPECT_EQ(std::string(tests[i].expected_host_and_port), host_and_port);
+ }
+}
+
+TEST(NetUtilTest, GetHostAndOptionalPort) {
+ const struct {
+ GURL url;
+ const char* expected_host_and_port;
+ } tests[] = {
+ { GURL("http://www.foo.com/x"), "www.foo.com"},
+ { GURL("http://www.foo.com:21/x"), "www.foo.com:21"},
+
+ // For IPv6 literals should always include the brackets.
+ { GURL("http://[1::2]/x"), "[1::2]"},
+ { GURL("http://[::a]:33/x"), "[::a]:33"},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string host_and_port = GetHostAndOptionalPort(tests[i].url);
+ EXPECT_EQ(std::string(tests[i].expected_host_and_port), host_and_port);
+ }
+}
+
+TEST(NetUtilTest, IPAddressToString) {
+ uint8 addr1[4] = {0, 0, 0, 0};
+ EXPECT_EQ("0.0.0.0", IPAddressToString(addr1, sizeof(addr1)));
+
+ uint8 addr2[4] = {192, 168, 0, 1};
+ EXPECT_EQ("192.168.0.1", IPAddressToString(addr2, sizeof(addr2)));
+
+ uint8 addr3[16] = {0xFE, 0xDC, 0xBA, 0x98};
+ EXPECT_EQ("fedc:ba98::", IPAddressToString(addr3, sizeof(addr3)));
+}
+
+TEST(NetUtilTest, IPAddressToStringWithPort) {
+ uint8 addr1[4] = {0, 0, 0, 0};
+ EXPECT_EQ("0.0.0.0:3", IPAddressToStringWithPort(addr1, sizeof(addr1), 3));
+
+ uint8 addr2[4] = {192, 168, 0, 1};
+ EXPECT_EQ("192.168.0.1:99",
+ IPAddressToStringWithPort(addr2, sizeof(addr2), 99));
+
+ uint8 addr3[16] = {0xFE, 0xDC, 0xBA, 0x98};
+ EXPECT_EQ("[fedc:ba98::]:8080",
+ IPAddressToStringWithPort(addr3, sizeof(addr3), 8080));
+}
+
+TEST(NetUtilTest, NetAddressToString_IPv4) {
+ const struct {
+ uint8 addr[4];
+ const char* result;
+ } tests[] = {
+ {{0, 0, 0, 0}, "0.0.0.0"},
+ {{127, 0, 0, 1}, "127.0.0.1"},
+ {{192, 168, 0, 1}, "192.168.0.1"},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SockaddrStorage storage;
+ MakeIPv4Address(tests[i].addr, 80, &storage);
+ std::string result = NetAddressToString(storage.addr, storage.addr_len);
+ EXPECT_EQ(std::string(tests[i].result), result);
+ }
+}
+
+TEST(NetUtilTest, NetAddressToString_IPv6) {
+ const struct {
+ uint8 addr[16];
+ const char* result;
+ } tests[] = {
+ {{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA,
+ 0x98, 0x76, 0x54, 0x32, 0x10},
+ "fedc:ba98:7654:3210:fedc:ba98:7654:3210"},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SockaddrStorage storage;
+ MakeIPv6Address(tests[i].addr, 80, &storage);
+ EXPECT_EQ(std::string(tests[i].result),
+ NetAddressToString(storage.addr, storage.addr_len));
+ }
+}
+
+TEST(NetUtilTest, NetAddressToStringWithPort_IPv4) {
+ uint8 addr[] = {127, 0, 0, 1};
+ SockaddrStorage storage;
+ MakeIPv4Address(addr, 166, &storage);
+ std::string result = NetAddressToStringWithPort(storage.addr,
+ storage.addr_len);
+ EXPECT_EQ("127.0.0.1:166", result);
+}
+
+TEST(NetUtilTest, NetAddressToStringWithPort_IPv6) {
+ uint8 addr[] = {
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xFE, 0xDC, 0xBA,
+ 0x98, 0x76, 0x54, 0x32, 0x10
+ };
+ SockaddrStorage storage;
+ MakeIPv6Address(addr, 361, &storage);
+ std::string result = NetAddressToStringWithPort(storage.addr,
+ storage.addr_len);
+
+ // May fail on systems that don't support IPv6.
+ if (!result.empty())
+ EXPECT_EQ("[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:361", result);
+}
+
+TEST(NetUtilTest, GetHostName) {
+ // We can't check the result of GetHostName() directly, since the result
+ // will differ across machines. Our goal here is to simply exercise the
+ // code path, and check that things "look about right".
+ std::string hostname = GetHostName();
+ EXPECT_FALSE(hostname.empty());
+}
+
+TEST(NetUtilTest, FormatUrl) {
+ FormatUrlTypes default_format_type = kFormatUrlOmitUsernamePassword;
+ const UrlTestData tests[] = {
+ {"Empty URL", "", "", default_format_type, UnescapeRule::NORMAL, L"", 0},
+
+ {"Simple URL",
+ "http://www.google.com/", "", default_format_type, UnescapeRule::NORMAL,
+ L"http://www.google.com/", 7},
+
+ {"With a port number and a reference",
+ "http://www.google.com:8080/#\xE3\x82\xB0", "", default_format_type,
+ UnescapeRule::NORMAL,
+ L"http://www.google.com:8080/#\x30B0", 7},
+
+ // -------- IDN tests --------
+ {"Japanese IDN with ja",
+ "http://xn--l8jvb1ey91xtjb.jp", "ja", default_format_type,
+ UnescapeRule::NORMAL, L"http://\x671d\x65e5\x3042\x3055\x3072.jp/", 7},
+
+ {"Japanese IDN with en",
+ "http://xn--l8jvb1ey91xtjb.jp", "en", default_format_type,
+ UnescapeRule::NORMAL, L"http://xn--l8jvb1ey91xtjb.jp/", 7},
+
+ {"Japanese IDN without any languages",
+ "http://xn--l8jvb1ey91xtjb.jp", "", default_format_type,
+ UnescapeRule::NORMAL,
+ // Single script is safe for empty languages.
+ L"http://\x671d\x65e5\x3042\x3055\x3072.jp/", 7},
+
+ {"mailto: with Japanese IDN",
+ "mailto:foo@xn--l8jvb1ey91xtjb.jp", "ja", default_format_type,
+ UnescapeRule::NORMAL,
+ // GURL doesn't assume an email address's domain part as a host name.
+ L"mailto:foo@xn--l8jvb1ey91xtjb.jp", 7},
+
+ {"file: with Japanese IDN",
+ "file://xn--l8jvb1ey91xtjb.jp/config.sys", "ja", default_format_type,
+ UnescapeRule::NORMAL,
+ L"file://\x671d\x65e5\x3042\x3055\x3072.jp/config.sys", 7},
+
+ {"ftp: with Japanese IDN",
+ "ftp://xn--l8jvb1ey91xtjb.jp/config.sys", "ja", default_format_type,
+ UnescapeRule::NORMAL,
+ L"ftp://\x671d\x65e5\x3042\x3055\x3072.jp/config.sys", 6},
+
+ // -------- omit_username_password flag tests --------
+ {"With username and password, omit_username_password=false",
+ "http://user:passwd@example.com/foo", "",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL,
+ L"http://user:passwd@example.com/foo", 19},
+
+ {"With username and password, omit_username_password=true",
+ "http://user:passwd@example.com/foo", "", default_format_type,
+ UnescapeRule::NORMAL, L"http://example.com/foo", 7},
+
+ {"With username and no password",
+ "http://user@example.com/foo", "", default_format_type,
+ UnescapeRule::NORMAL, L"http://example.com/foo", 7},
+
+ {"Just '@' without username and password",
+ "http://@example.com/foo", "", default_format_type, UnescapeRule::NORMAL,
+ L"http://example.com/foo", 7},
+
+ // GURL doesn't think local-part of an email address is username for URL.
+ {"mailto:, omit_username_password=true",
+ "mailto:foo@example.com", "", default_format_type, UnescapeRule::NORMAL,
+ L"mailto:foo@example.com", 7},
+
+ // -------- unescape flag tests --------
+ {"Do not unescape",
+ "http://%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB.jp/"
+ "%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
+ "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", "en", default_format_type,
+ UnescapeRule::NONE,
+ // GURL parses %-encoded hostnames into Punycode.
+ L"http://xn--qcka1pmc.jp/%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
+ L"?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", 7},
+
+ {"Unescape normally",
+ "http://%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB.jp/"
+ "%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB"
+ "?q=%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB", "en", default_format_type,
+ UnescapeRule::NORMAL,
+ L"http://xn--qcka1pmc.jp/\x30B0\x30FC\x30B0\x30EB"
+ L"?q=\x30B0\x30FC\x30B0\x30EB", 7},
+
+ {"Unescape normally including unescape spaces",
+ "http://www.google.com/search?q=Hello%20World", "en", default_format_type,
+ UnescapeRule::SPACES, L"http://www.google.com/search?q=Hello World", 7},
+
+ /*
+ {"unescape=true with some special characters",
+ "http://user%3A:%40passwd@example.com/foo%3Fbar?q=b%26z", "",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL,
+ L"http://user%3A:%40passwd@example.com/foo%3Fbar?q=b%26z", 25},
+ */
+ // Disabled: the resultant URL becomes "...user%253A:%2540passwd...".
+
+ // -------- omit http: --------
+ {"omit http with user name",
+ "http://user@example.com/foo", "", kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"example.com/foo", 0},
+
+ {"omit http",
+ "http://www.google.com/", "en", kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"www.google.com/",
+ 0},
+
+ {"omit http with https",
+ "https://www.google.com/", "en", kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"https://www.google.com/",
+ 8},
+
+ {"omit http starts with ftp.",
+ "http://ftp.google.com/", "en", kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, L"http://ftp.google.com/",
+ 7},
+
+ // -------- omit trailing slash on bare hostname --------
+ {"omit slash when it's the entire path",
+ "http://www.google.com/", "en",
+ kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com", 7},
+ {"omit slash when there's a ref",
+ "http://www.google.com/#ref", "en",
+ kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/#ref", 7},
+ {"omit slash when there's a query",
+ "http://www.google.com/?", "en",
+ kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/?", 7},
+ {"omit slash when it's not the entire path",
+ "http://www.google.com/foo", "en",
+ kFormatUrlOmitTrailingSlashOnBareHostname, UnescapeRule::NORMAL,
+ L"http://www.google.com/foo", 7},
+ {"omit slash for nonstandard URLs",
+ "data:/", "en", kFormatUrlOmitTrailingSlashOnBareHostname,
+ UnescapeRule::NORMAL, L"data:/", 5},
+ {"omit slash for file URLs",
+ "file:///", "en", kFormatUrlOmitTrailingSlashOnBareHostname,
+ UnescapeRule::NORMAL, L"file:///", 7},
+
+ // -------- view-source: --------
+ {"view-source",
+ "view-source:http://xn--qcka1pmc.jp/", "ja", default_format_type,
+ UnescapeRule::NORMAL, L"view-source:http://\x30B0\x30FC\x30B0\x30EB.jp/",
+ 19},
+
+ {"view-source of view-source",
+ "view-source:view-source:http://xn--qcka1pmc.jp/", "ja",
+ default_format_type, UnescapeRule::NORMAL,
+ L"view-source:view-source:http://xn--qcka1pmc.jp/", 12},
+
+ // view-source should omit http and trailing slash where non-view-source
+ // would.
+ {"view-source omit http",
+ "view-source:http://a.b/c", "en", kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:a.b/c",
+ 12},
+ {"view-source omit http starts with ftp.",
+ "view-source:http://ftp.b/c", "en", kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:http://ftp.b/c",
+ 19},
+ {"view-source omit slash when it's the entire path",
+ "view-source:http://a.b/", "en", kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, L"view-source:a.b",
+ 12},
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ size_t prefix_len;
+ base::string16 formatted = FormatUrl(
+ GURL(tests[i].input), tests[i].languages, tests[i].format_types,
+ tests[i].escape_rules, NULL, &prefix_len, NULL);
+ EXPECT_EQ(WideToUTF16(tests[i].output), formatted) << tests[i].description;
+ EXPECT_EQ(tests[i].prefix_len, prefix_len) << tests[i].description;
+ }
+}
+
+TEST(NetUtilTest, FormatUrlParsed) {
+ // No unescape case.
+ url_parse::Parsed parsed;
+ base::string16 formatted = FormatUrl(
+ GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
+ "%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
+ "ja", kFormatUrlOmitNothing, UnescapeRule::NONE, &parsed, NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(
+ L"http://%E3%82%B0:%E3%83%BC@\x30B0\x30FC\x30B0\x30EB.jp:8080"
+ L"/%E3%82%B0/?q=%E3%82%B0#\x30B0"), formatted);
+ EXPECT_EQ(WideToUTF16(L"%E3%82%B0"),
+ formatted.substr(parsed.username.begin, parsed.username.len));
+ EXPECT_EQ(WideToUTF16(L"%E3%83%BC"),
+ formatted.substr(parsed.password.begin, parsed.password.len));
+ EXPECT_EQ(WideToUTF16(L"\x30B0\x30FC\x30B0\x30EB.jp"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"8080"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/%E3%82%B0/"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"q=%E3%82%B0"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"\x30B0"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // Unescape case.
+ formatted = FormatUrl(
+ GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
+ "%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
+ "ja", kFormatUrlOmitNothing, UnescapeRule::NORMAL, &parsed, NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(L"http://\x30B0:\x30FC@\x30B0\x30FC\x30B0\x30EB.jp:8080"
+ L"/\x30B0/?q=\x30B0#\x30B0"), formatted);
+ EXPECT_EQ(WideToUTF16(L"\x30B0"),
+ formatted.substr(parsed.username.begin, parsed.username.len));
+ EXPECT_EQ(WideToUTF16(L"\x30FC"),
+ formatted.substr(parsed.password.begin, parsed.password.len));
+ EXPECT_EQ(WideToUTF16(L"\x30B0\x30FC\x30B0\x30EB.jp"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"8080"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/\x30B0/"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"q=\x30B0"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"\x30B0"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // Omit_username_password + unescape case.
+ formatted = FormatUrl(
+ GURL("http://\xE3\x82\xB0:\xE3\x83\xBC@xn--qcka1pmc.jp:8080/"
+ "%E3%82%B0/?q=%E3%82%B0#\xE3\x82\xB0"),
+ "ja", kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, &parsed,
+ NULL, NULL);
+ EXPECT_EQ(WideToUTF16(L"http://\x30B0\x30FC\x30B0\x30EB.jp:8080"
+ L"/\x30B0/?q=\x30B0#\x30B0"), formatted);
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(WideToUTF16(L"\x30B0\x30FC\x30B0\x30EB.jp"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"8080"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/\x30B0/"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"q=\x30B0"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"\x30B0"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // View-source case.
+ formatted =
+ FormatUrl(GURL("view-source:http://user:passwd@host:81/path?query#ref"),
+ std::string(),
+ kFormatUrlOmitUsernamePassword,
+ UnescapeRule::NORMAL,
+ &parsed,
+ NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(L"view-source:http://host:81/path?query#ref"),
+ formatted);
+ EXPECT_EQ(WideToUTF16(L"view-source:http"),
+ formatted.substr(parsed.scheme.begin, parsed.scheme.len));
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(WideToUTF16(L"host"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"81"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/path"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"query"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"ref"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http case.
+ formatted = FormatUrl(GURL("http://host:8000/a?b=c#d"),
+ std::string(),
+ kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL,
+ &parsed,
+ NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(L"host:8000/a?b=c#d"), formatted);
+ EXPECT_FALSE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(WideToUTF16(L"host"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"8000"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/a"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"b=c"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"d"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http starts with ftp case.
+ formatted = FormatUrl(GURL("http://ftp.host:8000/a?b=c#d"),
+ std::string(),
+ kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL,
+ &parsed,
+ NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(L"http://ftp.host:8000/a?b=c#d"), formatted);
+ EXPECT_TRUE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_EQ(WideToUTF16(L"http"),
+ formatted.substr(parsed.scheme.begin, parsed.scheme.len));
+ EXPECT_EQ(WideToUTF16(L"ftp.host"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"8000"),
+ formatted.substr(parsed.port.begin, parsed.port.len));
+ EXPECT_EQ(WideToUTF16(L"/a"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+ EXPECT_EQ(WideToUTF16(L"b=c"),
+ formatted.substr(parsed.query.begin, parsed.query.len));
+ EXPECT_EQ(WideToUTF16(L"d"),
+ formatted.substr(parsed.ref.begin, parsed.ref.len));
+
+ // omit http starts with 'f' case.
+ formatted = FormatUrl(GURL("http://f/"),
+ std::string(),
+ kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL,
+ &parsed,
+ NULL,
+ NULL);
+ EXPECT_EQ(WideToUTF16(L"f/"), formatted);
+ EXPECT_FALSE(parsed.scheme.is_valid());
+ EXPECT_FALSE(parsed.username.is_valid());
+ EXPECT_FALSE(parsed.password.is_valid());
+ EXPECT_FALSE(parsed.port.is_valid());
+ EXPECT_TRUE(parsed.path.is_valid());
+ EXPECT_FALSE(parsed.query.is_valid());
+ EXPECT_FALSE(parsed.ref.is_valid());
+ EXPECT_EQ(WideToUTF16(L"f"),
+ formatted.substr(parsed.host.begin, parsed.host.len));
+ EXPECT_EQ(WideToUTF16(L"/"),
+ formatted.substr(parsed.path.begin, parsed.path.len));
+}
+
+// Make sure that calling FormatUrl on a GURL and then converting back to a GURL
+// results in the original GURL, for each ASCII character in the path.
+TEST(NetUtilTest, FormatUrlRoundTripPathASCII) {
+ for (unsigned char test_char = 32; test_char < 128; ++test_char) {
+ GURL url(std::string("http://www.google.com/") +
+ static_cast<char>(test_char));
+ size_t prefix_len;
+ base::string16 formatted = FormatUrl(url,
+ std::string(),
+ kFormatUrlOmitUsernamePassword,
+ UnescapeRule::NORMAL,
+ NULL,
+ &prefix_len,
+ NULL);
+ EXPECT_EQ(url.spec(), GURL(formatted).spec());
+ }
+}
+
+// Make sure that calling FormatUrl on a GURL and then converting back to a GURL
+// results in the original GURL, for each escaped ASCII character in the path.
+TEST(NetUtilTest, FormatUrlRoundTripPathEscaped) {
+ for (unsigned char test_char = 32; test_char < 128; ++test_char) {
+ std::string original_url("http://www.google.com/");
+ original_url.push_back('%');
+ original_url.append(base::HexEncode(&test_char, 1));
+
+ GURL url(original_url);
+ size_t prefix_len;
+ base::string16 formatted = FormatUrl(url,
+ std::string(),
+ kFormatUrlOmitUsernamePassword,
+ UnescapeRule::NORMAL,
+ NULL,
+ &prefix_len,
+ NULL);
+ EXPECT_EQ(url.spec(), GURL(formatted).spec());
+ }
+}
+
+// Make sure that calling FormatUrl on a GURL and then converting back to a GURL
+// results in the original GURL, for each ASCII character in the query.
+TEST(NetUtilTest, FormatUrlRoundTripQueryASCII) {
+ for (unsigned char test_char = 32; test_char < 128; ++test_char) {
+ GURL url(std::string("http://www.google.com/?") +
+ static_cast<char>(test_char));
+ size_t prefix_len;
+ base::string16 formatted = FormatUrl(url,
+ std::string(),
+ kFormatUrlOmitUsernamePassword,
+ UnescapeRule::NORMAL,
+ NULL,
+ &prefix_len,
+ NULL);
+ EXPECT_EQ(url.spec(), GURL(formatted).spec());
+ }
+}
+
+// Make sure that calling FormatUrl on a GURL and then converting back to a GURL
+// only results in a different GURL for certain characters.
+TEST(NetUtilTest, FormatUrlRoundTripQueryEscaped) {
+ // A full list of characters which FormatURL should unescape and GURL should
+ // not escape again, when they appear in a query string.
+ const char* kUnescapedCharacters =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~";
+ for (unsigned char test_char = 0; test_char < 128; ++test_char) {
+ std::string original_url("http://www.google.com/?");
+ original_url.push_back('%');
+ original_url.append(base::HexEncode(&test_char, 1));
+
+ GURL url(original_url);
+ size_t prefix_len;
+ base::string16 formatted = FormatUrl(url,
+ std::string(),
+ kFormatUrlOmitUsernamePassword,
+ UnescapeRule::NORMAL,
+ NULL,
+ &prefix_len,
+ NULL);
+
+ if (test_char &&
+ strchr(kUnescapedCharacters, static_cast<char>(test_char))) {
+ EXPECT_NE(url.spec(), GURL(formatted).spec());
+ } else {
+ EXPECT_EQ(url.spec(), GURL(formatted).spec());
+ }
+ }
+}
+
+TEST(NetUtilTest, FormatUrlWithOffsets) {
+ const AdjustOffsetCase null_cases[] = {
+ {0, base::string16::npos},
+ };
+ CheckAdjustedOffsets(std::string(), "en", kFormatUrlOmitNothing,
+ UnescapeRule::NORMAL, null_cases, arraysize(null_cases), NULL);
+
+ const AdjustOffsetCase basic_cases[] = {
+ {0, 0},
+ {3, 3},
+ {5, 5},
+ {6, 6},
+ {13, 13},
+ {21, 21},
+ {22, 22},
+ {23, 23},
+ {25, 25},
+ {26, base::string16::npos},
+ {500000, base::string16::npos},
+ {base::string16::npos, base::string16::npos},
+ };
+ const size_t basic_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
+ CheckAdjustedOffsets("http://www.google.com/foo/", "en",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL, basic_cases,
+ arraysize(basic_cases), basic_offsets);
+
+ const AdjustOffsetCase omit_auth_cases_1[] = {
+ {6, 6},
+ {7, base::string16::npos},
+ {8, base::string16::npos},
+ {10, base::string16::npos},
+ {12, base::string16::npos},
+ {14, base::string16::npos},
+ {15, 7},
+ {25, 17},
+ };
+ const size_t omit_auth_offsets_1[] = {0, 1, 2, 3, 4, 5, 6, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21};
+ CheckAdjustedOffsets("http://foo:bar@www.google.com/", "en",
+ kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, omit_auth_cases_1,
+ arraysize(omit_auth_cases_1), omit_auth_offsets_1);
+
+ const AdjustOffsetCase omit_auth_cases_2[] = {
+ {9, base::string16::npos},
+ {11, 7},
+ };
+ const size_t omit_auth_offsets_2[] = {0, 1, 2, 3, 4, 5, 6, kNpos, kNpos,
+ kNpos, kNpos, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21};
+ CheckAdjustedOffsets("http://foo@www.google.com/", "en",
+ kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, omit_auth_cases_2,
+ arraysize(omit_auth_cases_2), omit_auth_offsets_2);
+
+ // "http://foo\x30B0:\x30B0bar@www.google.com"
+ const AdjustOffsetCase dont_omit_auth_cases[] = {
+ {0, 0},
+ /*{3, base::string16::npos},
+ {7, 0},
+ {11, 4},
+ {12, base::string16::npos},
+ {20, 5},
+ {24, 9},*/
+ };
+ const size_t dont_omit_auth_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 11, 12, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+ CheckAdjustedOffsets("http://foo%E3%82%B0:%E3%82%B0bar@www.google.com/", "en",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL, dont_omit_auth_cases,
+ arraysize(dont_omit_auth_cases), dont_omit_auth_offsets);
+
+ const AdjustOffsetCase view_source_cases[] = {
+ {0, 0},
+ {3, 3},
+ {11, 11},
+ {12, 12},
+ {13, 13},
+ {18, 18},
+ {19, base::string16::npos},
+ {20, base::string16::npos},
+ {23, 19},
+ {26, 22},
+ {base::string16::npos, base::string16::npos},
+ };
+ const size_t view_source_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, kNpos, kNpos, kNpos, kNpos, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33};
+ CheckAdjustedOffsets("view-source:http://foo@www.google.com/", "en",
+ kFormatUrlOmitUsernamePassword, UnescapeRule::NORMAL, view_source_cases,
+ arraysize(view_source_cases), view_source_offsets);
+
+ // "http://\x671d\x65e5\x3042\x3055\x3072.jp/foo/"
+ const AdjustOffsetCase idn_hostname_cases_1[] = {
+ {8, base::string16::npos},
+ {16, base::string16::npos},
+ {24, base::string16::npos},
+ {25, 12},
+ {30, 17},
+ };
+ const size_t idn_hostname_offsets_1[] = {0, 1, 2, 3, 4, 5, 6, 7, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, 12, 13, 14, 15, 16, 17, 18, 19};
+ CheckAdjustedOffsets("http://xn--l8jvb1ey91xtjb.jp/foo/", "ja",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL, idn_hostname_cases_1,
+ arraysize(idn_hostname_cases_1), idn_hostname_offsets_1);
+
+ // "http://test.\x89c6\x9891.\x5317\x4eac\x5927\x5b78.test/"
+ const AdjustOffsetCase idn_hostname_cases_2[] = {
+ {7, 7},
+ {9, 9},
+ {11, 11},
+ {12, 12},
+ {13, base::string16::npos},
+ {23, base::string16::npos},
+ {24, 14},
+ {25, 15},
+ {26, base::string16::npos},
+ {32, base::string16::npos},
+ {41, 19},
+ {42, 20},
+ {45, 23},
+ {46, 24},
+ {47, base::string16::npos},
+ {base::string16::npos, base::string16::npos},
+ };
+ const size_t idn_hostname_offsets_2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos,
+ kNpos, 14, 15, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 19, 20, 21, 22, 23, 24};
+ CheckAdjustedOffsets("http://test.xn--cy2a840a.xn--1lq90ic7f1rc.test/",
+ "zh-CN", kFormatUrlOmitNothing, UnescapeRule::NORMAL,
+ idn_hostname_cases_2, arraysize(idn_hostname_cases_2),
+ idn_hostname_offsets_2);
+
+ // "http://www.google.com/foo bar/\x30B0\x30FC\x30B0\x30EB"
+ const AdjustOffsetCase unescape_cases[] = {
+ {25, 25},
+ {26, base::string16::npos},
+ {27, base::string16::npos},
+ {28, 26},
+ {35, base::string16::npos},
+ {41, 31},
+ {59, 33},
+ {60, base::string16::npos},
+ {67, base::string16::npos},
+ {68, base::string16::npos},
+ };
+ const size_t unescape_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, kNpos, kNpos, 26, 27,
+ 28, 29, 30, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 31,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 32, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 33, kNpos, kNpos, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos};
+ CheckAdjustedOffsets(
+ "http://www.google.com/foo%20bar/%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB",
+ "en", kFormatUrlOmitNothing, UnescapeRule::SPACES, unescape_cases,
+ arraysize(unescape_cases), unescape_offsets);
+
+ // "http://www.google.com/foo.html#\x30B0\x30B0z"
+ const AdjustOffsetCase ref_cases[] = {
+ {30, 30},
+ {31, 31},
+ {32, base::string16::npos},
+ {34, 32},
+ {35, base::string16::npos},
+ {37, 33},
+ {38, base::string16::npos},
+ };
+ const size_t ref_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ kNpos, kNpos, 32, kNpos, kNpos, 33};
+ CheckAdjustedOffsets(
+ "http://www.google.com/foo.html#\xE3\x82\xB0\xE3\x82\xB0z", "en",
+ kFormatUrlOmitNothing, UnescapeRule::NORMAL, ref_cases,
+ arraysize(ref_cases), ref_offsets);
+
+ const AdjustOffsetCase omit_http_cases[] = {
+ {0, base::string16::npos},
+ {3, base::string16::npos},
+ {7, 0},
+ {8, 1},
+ };
+ const size_t omit_http_offsets[] = {kNpos, kNpos, kNpos, kNpos, kNpos, kNpos,
+ kNpos, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ CheckAdjustedOffsets("http://www.google.com/", "en",
+ kFormatUrlOmitHTTP, UnescapeRule::NORMAL, omit_http_cases,
+ arraysize(omit_http_cases), omit_http_offsets);
+
+ const AdjustOffsetCase omit_http_start_with_ftp_cases[] = {
+ {0, 0},
+ {3, 3},
+ {8, 8},
+ };
+ const size_t omit_http_start_with_ftp_offsets[] = {0, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21};
+ CheckAdjustedOffsets("http://ftp.google.com/", "en", kFormatUrlOmitHTTP,
+ UnescapeRule::NORMAL, omit_http_start_with_ftp_cases,
+ arraysize(omit_http_start_with_ftp_cases),
+ omit_http_start_with_ftp_offsets);
+
+ const AdjustOffsetCase omit_all_cases[] = {
+ {12, 0},
+ {13, 1},
+ {0, base::string16::npos},
+ {3, base::string16::npos},
+ };
+ const size_t omit_all_offsets[] = {kNpos, kNpos, kNpos, kNpos, kNpos, kNpos,
+ kNpos, kNpos, kNpos, kNpos, kNpos, kNpos, 0, 1, 2, 3, 4, 5, 6, kNpos};
+ CheckAdjustedOffsets("http://user@foo.com/", "en", kFormatUrlOmitAll,
+ UnescapeRule::NORMAL, omit_all_cases,
+ arraysize(omit_all_cases), omit_all_offsets);
+}
+
+TEST(NetUtilTest, SimplifyUrlForRequest) {
+ struct {
+ const char* input_url;
+ const char* expected_simplified_url;
+ } tests[] = {
+ {
+ // Reference section should be stripped.
+ "http://www.google.com:78/foobar?query=1#hash",
+ "http://www.google.com:78/foobar?query=1",
+ },
+ {
+ // Reference section can itself contain #.
+ "http://192.168.0.1?query=1#hash#10#11#13#14",
+ "http://192.168.0.1?query=1",
+ },
+ { // Strip username/password.
+ "http://user:pass@google.com",
+ "http://google.com/",
+ },
+ { // Strip both the reference and the username/password.
+ "http://user:pass@google.com:80/sup?yo#X#X",
+ "http://google.com/sup?yo",
+ },
+ { // Try an HTTPS URL -- strip both the reference and the username/password.
+ "https://user:pass@google.com:80/sup?yo#X#X",
+ "https://google.com:80/sup?yo",
+ },
+ { // Try an FTP URL -- strip both the reference and the username/password.
+ "ftp://user:pass@google.com:80/sup?yo#X#X",
+ "ftp://google.com:80/sup?yo",
+ },
+ { // Try an nonstandard URL
+ "foobar://user:pass@google.com:80/sup?yo#X#X",
+ "foobar://user:pass@google.com:80/sup?yo#X#X",
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ tests[i].input_url));
+ GURL input_url(GURL(tests[i].input_url));
+ GURL expected_url(GURL(tests[i].expected_simplified_url));
+ EXPECT_EQ(expected_url, SimplifyUrlForRequest(input_url));
+ }
+}
+
+TEST(NetUtilTest, SetExplicitlyAllowedPortsTest) {
+ std::string invalid[] = { "1,2,a", "'1','2'", "1, 2, 3", "1 0,11,12" };
+ std::string valid[] = { "", "1", "1,2", "1,2,3", "10,11,12,13" };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(invalid); ++i) {
+ SetExplicitlyAllowedPorts(invalid[i]);
+ EXPECT_EQ(0, static_cast<int>(GetCountOfExplicitlyAllowedPorts()));
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(valid); ++i) {
+ SetExplicitlyAllowedPorts(valid[i]);
+ EXPECT_EQ(i, GetCountOfExplicitlyAllowedPorts());
+ }
+}
+
+TEST(NetUtilTest, GetHostOrSpecFromURL) {
+ EXPECT_EQ("example.com",
+ GetHostOrSpecFromURL(GURL("http://example.com/test")));
+ EXPECT_EQ("example.com",
+ GetHostOrSpecFromURL(GURL("http://example.com./test")));
+ EXPECT_EQ("file:///tmp/test.html",
+ GetHostOrSpecFromURL(GURL("file:///tmp/test.html")));
+}
+
+TEST(NetUtilTest, GetAddressFamily) {
+ IPAddressNumber number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("192.168.0.1", &number));
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, GetAddressFamily(number));
+ EXPECT_TRUE(ParseIPLiteralToNumber("1:abcd::3:4:ff", &number));
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, GetAddressFamily(number));
+}
+
+// Test that invalid IP literals fail to parse.
+TEST(NetUtilTest, ParseIPLiteralToNumber_FailParse) {
+ IPAddressNumber number;
+
+ EXPECT_FALSE(ParseIPLiteralToNumber("bad value", &number));
+ EXPECT_FALSE(ParseIPLiteralToNumber("bad:value", &number));
+ EXPECT_FALSE(ParseIPLiteralToNumber(std::string(), &number));
+ EXPECT_FALSE(ParseIPLiteralToNumber("192.168.0.1:30", &number));
+ EXPECT_FALSE(ParseIPLiteralToNumber(" 192.168.0.1 ", &number));
+ EXPECT_FALSE(ParseIPLiteralToNumber("[::1]", &number));
+}
+
+// Test parsing an IPv4 literal.
+TEST(NetUtilTest, ParseIPLiteralToNumber_IPv4) {
+ IPAddressNumber number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("192.168.0.1", &number));
+ EXPECT_EQ("192,168,0,1", DumpIPNumber(number));
+ EXPECT_EQ("192.168.0.1", IPAddressToString(number));
+}
+
+// Test parsing an IPv6 literal.
+TEST(NetUtilTest, ParseIPLiteralToNumber_IPv6) {
+ IPAddressNumber number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("1:abcd::3:4:ff", &number));
+ EXPECT_EQ("0,1,171,205,0,0,0,0,0,0,0,3,0,4,0,255", DumpIPNumber(number));
+ EXPECT_EQ("1:abcd::3:4:ff", IPAddressToString(number));
+}
+
+// Test mapping an IPv4 address to an IPv6 address.
+TEST(NetUtilTest, ConvertIPv4NumberToIPv6Number) {
+ IPAddressNumber ipv4_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("192.168.0.1", &ipv4_number));
+
+ IPAddressNumber ipv6_number =
+ ConvertIPv4NumberToIPv6Number(ipv4_number);
+
+ // ::ffff:192.168.0.1
+ EXPECT_EQ("0,0,0,0,0,0,0,0,0,0,255,255,192,168,0,1",
+ DumpIPNumber(ipv6_number));
+ EXPECT_EQ("::ffff:c0a8:1", IPAddressToString(ipv6_number));
+}
+
+TEST(NetUtilTest, IsIPv4Mapped) {
+ IPAddressNumber ipv4_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("192.168.0.1", &ipv4_number));
+ EXPECT_FALSE(IsIPv4Mapped(ipv4_number));
+
+ IPAddressNumber ipv6_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("::1", &ipv4_number));
+ EXPECT_FALSE(IsIPv4Mapped(ipv6_number));
+
+ IPAddressNumber ipv4mapped_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("::ffff:0101:1", &ipv4mapped_number));
+ EXPECT_TRUE(IsIPv4Mapped(ipv4mapped_number));
+}
+
+TEST(NetUtilTest, ConvertIPv4MappedToIPv4) {
+ IPAddressNumber ipv4mapped_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber("::ffff:0101:1", &ipv4mapped_number));
+ IPAddressNumber expected;
+ EXPECT_TRUE(ParseIPLiteralToNumber("1.1.0.1", &expected));
+ IPAddressNumber result = ConvertIPv4MappedToIPv4(ipv4mapped_number);
+ EXPECT_EQ(expected, result);
+}
+
+// Test parsing invalid CIDR notation literals.
+TEST(NetUtilTest, ParseCIDRBlock_Invalid) {
+ const char* bad_literals[] = {
+ "foobar",
+ "",
+ "192.168.0.1",
+ "::1",
+ "/",
+ "/1",
+ "1",
+ "192.168.1.1/-1",
+ "192.168.1.1/33",
+ "::1/-3",
+ "a::3/129",
+ "::1/x",
+ "192.168.0.1//11"
+ };
+
+ for (size_t i = 0; i < arraysize(bad_literals); ++i) {
+ IPAddressNumber ip_number;
+ size_t prefix_length_in_bits;
+
+ EXPECT_FALSE(ParseCIDRBlock(bad_literals[i],
+ &ip_number,
+ &prefix_length_in_bits));
+ }
+}
+
+// Test parsing a valid CIDR notation literal.
+TEST(NetUtilTest, ParseCIDRBlock_Valid) {
+ IPAddressNumber ip_number;
+ size_t prefix_length_in_bits;
+
+ EXPECT_TRUE(ParseCIDRBlock("192.168.0.1/11",
+ &ip_number,
+ &prefix_length_in_bits));
+
+ EXPECT_EQ("192,168,0,1", DumpIPNumber(ip_number));
+ EXPECT_EQ(11u, prefix_length_in_bits);
+}
+
+TEST(NetUtilTest, IPNumberMatchesPrefix) {
+ struct {
+ const char* cidr_literal;
+ const char* ip_literal;
+ bool expected_to_match;
+ } tests[] = {
+ // IPv4 prefix with IPv4 inputs.
+ {
+ "10.10.1.32/27",
+ "10.10.1.44",
+ true
+ },
+ {
+ "10.10.1.32/27",
+ "10.10.1.90",
+ false
+ },
+ {
+ "10.10.1.32/27",
+ "10.10.1.90",
+ false
+ },
+
+ // IPv6 prefix with IPv6 inputs.
+ {
+ "2001:db8::/32",
+ "2001:DB8:3:4::5",
+ true
+ },
+ {
+ "2001:db8::/32",
+ "2001:c8::",
+ false
+ },
+
+ // IPv6 prefix with IPv4 inputs.
+ {
+ "2001:db8::/33",
+ "192.168.0.1",
+ false
+ },
+ {
+ "::ffff:192.168.0.1/112",
+ "192.168.33.77",
+ true
+ },
+
+ // IPv4 prefix with IPv6 inputs.
+ {
+ "10.11.33.44/16",
+ "::ffff:0a0b:89",
+ true
+ },
+ {
+ "10.11.33.44/16",
+ "::ffff:10.12.33.44",
+ false
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s, %s", i,
+ tests[i].cidr_literal,
+ tests[i].ip_literal));
+
+ IPAddressNumber ip_number;
+ EXPECT_TRUE(ParseIPLiteralToNumber(tests[i].ip_literal, &ip_number));
+
+ IPAddressNumber ip_prefix;
+ size_t prefix_length_in_bits;
+
+ EXPECT_TRUE(ParseCIDRBlock(tests[i].cidr_literal,
+ &ip_prefix,
+ &prefix_length_in_bits));
+
+ EXPECT_EQ(tests[i].expected_to_match,
+ IPNumberMatchesPrefix(ip_number,
+ ip_prefix,
+ prefix_length_in_bits));
+ }
+}
+
+TEST(NetUtilTest, IsLocalhost) {
+ EXPECT_TRUE(net::IsLocalhost("localhost"));
+ EXPECT_TRUE(net::IsLocalhost("localhost.localdomain"));
+ EXPECT_TRUE(net::IsLocalhost("localhost6"));
+ EXPECT_TRUE(net::IsLocalhost("localhost6.localdomain6"));
+ EXPECT_TRUE(net::IsLocalhost("127.0.0.1"));
+ EXPECT_TRUE(net::IsLocalhost("127.0.1.0"));
+ EXPECT_TRUE(net::IsLocalhost("127.1.0.0"));
+ EXPECT_TRUE(net::IsLocalhost("127.0.0.255"));
+ EXPECT_TRUE(net::IsLocalhost("127.0.255.0"));
+ EXPECT_TRUE(net::IsLocalhost("127.255.0.0"));
+ EXPECT_TRUE(net::IsLocalhost("::1"));
+ EXPECT_TRUE(net::IsLocalhost("0:0:0:0:0:0:0:1"));
+
+ EXPECT_FALSE(net::IsLocalhost("localhostx"));
+ EXPECT_FALSE(net::IsLocalhost("foo.localdomain"));
+ EXPECT_FALSE(net::IsLocalhost("localhost6x"));
+ EXPECT_FALSE(net::IsLocalhost("localhost.localdomain6"));
+ EXPECT_FALSE(net::IsLocalhost("localhost6.localdomain"));
+ EXPECT_FALSE(net::IsLocalhost("127.0.0.1.1"));
+ EXPECT_FALSE(net::IsLocalhost(".127.0.0.255"));
+ EXPECT_FALSE(net::IsLocalhost("::2"));
+ EXPECT_FALSE(net::IsLocalhost("::1:1"));
+ EXPECT_FALSE(net::IsLocalhost("0:0:0:0:1:0:0:1"));
+ EXPECT_FALSE(net::IsLocalhost("::1:1"));
+ EXPECT_FALSE(net::IsLocalhost("0:0:0:0:0:0:0:0:1"));
+}
+
+// Verify GetNetworkList().
+TEST(NetUtilTest, GetNetworkList) {
+ NetworkInterfaceList list;
+ ASSERT_TRUE(GetNetworkList(&list));
+
+ for (NetworkInterfaceList::iterator it = list.begin();
+ it != list.end(); ++it) {
+ // Verify that the name is not empty.
+ EXPECT_FALSE(it->name.empty());
+
+ // Verify that the address is correct.
+ EXPECT_TRUE(it->address.size() == kIPv4AddressSize ||
+ it->address.size() == kIPv6AddressSize)
+ << "Invalid address of size " << it->address.size();
+ bool all_zeroes = true;
+ for (size_t i = 0; i < it->address.size(); ++i) {
+ if (it->address[i] != 0) {
+ all_zeroes = false;
+ break;
+ }
+ }
+ EXPECT_FALSE(all_zeroes);
+ }
+}
+
+static const base::FilePath::CharType* kSafePortableBasenames[] = {
+ FILE_PATH_LITERAL("a"),
+ FILE_PATH_LITERAL("a.txt"),
+ FILE_PATH_LITERAL("a b.txt"),
+ FILE_PATH_LITERAL("a-b.txt"),
+ FILE_PATH_LITERAL("My Computer"),
+ FILE_PATH_LITERAL(" Computer"),
+};
+
+static const base::FilePath::CharType* kUnsafePortableBasenames[] = {
+ FILE_PATH_LITERAL(""),
+ FILE_PATH_LITERAL("."),
+ FILE_PATH_LITERAL(".."),
+ FILE_PATH_LITERAL("..."),
+ FILE_PATH_LITERAL("con"),
+ FILE_PATH_LITERAL("con.zip"),
+ FILE_PATH_LITERAL("NUL"),
+ FILE_PATH_LITERAL("NUL.zip"),
+ FILE_PATH_LITERAL(".a"),
+ FILE_PATH_LITERAL("a."),
+ FILE_PATH_LITERAL("a\"a"),
+ FILE_PATH_LITERAL("a<a"),
+ FILE_PATH_LITERAL("a>a"),
+ FILE_PATH_LITERAL("a?a"),
+ FILE_PATH_LITERAL("a/"),
+ FILE_PATH_LITERAL("a\\"),
+ FILE_PATH_LITERAL("a "),
+ FILE_PATH_LITERAL("a . ."),
+ FILE_PATH_LITERAL("My Computer.{a}"),
+ FILE_PATH_LITERAL("My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}"),
+#if !defined(OS_WIN)
+ FILE_PATH_LITERAL("a\\a"),
+#endif
+};
+
+static const base::FilePath::CharType* kSafePortableRelativePaths[] = {
+ FILE_PATH_LITERAL("a/a"),
+#if defined(OS_WIN)
+ FILE_PATH_LITERAL("a\\a"),
+#endif
+};
+
+TEST(NetUtilTest, IsSafePortableBasename) {
+ for (size_t i = 0 ; i < arraysize(kSafePortableBasenames); ++i) {
+ EXPECT_TRUE(IsSafePortableBasename(base::FilePath(
+ kSafePortableBasenames[i]))) << kSafePortableBasenames[i];
+ }
+ for (size_t i = 0 ; i < arraysize(kUnsafePortableBasenames); ++i) {
+ EXPECT_FALSE(IsSafePortableBasename(base::FilePath(
+ kUnsafePortableBasenames[i]))) << kUnsafePortableBasenames[i];
+ }
+ for (size_t i = 0 ; i < arraysize(kSafePortableRelativePaths); ++i) {
+ EXPECT_FALSE(IsSafePortableBasename(base::FilePath(
+ kSafePortableRelativePaths[i]))) << kSafePortableRelativePaths[i];
+ }
+}
+
+TEST(NetUtilTest, IsSafePortableRelativePath) {
+ base::FilePath safe_dirname(FILE_PATH_LITERAL("a"));
+ for (size_t i = 0 ; i < arraysize(kSafePortableBasenames); ++i) {
+ EXPECT_TRUE(IsSafePortableRelativePath(base::FilePath(
+ kSafePortableBasenames[i]))) << kSafePortableBasenames[i];
+ EXPECT_TRUE(IsSafePortableRelativePath(safe_dirname.Append(base::FilePath(
+ kSafePortableBasenames[i])))) << kSafePortableBasenames[i];
+ }
+ for (size_t i = 0 ; i < arraysize(kSafePortableRelativePaths); ++i) {
+ EXPECT_TRUE(IsSafePortableRelativePath(base::FilePath(
+ kSafePortableRelativePaths[i]))) << kSafePortableRelativePaths[i];
+ EXPECT_TRUE(IsSafePortableRelativePath(safe_dirname.Append(base::FilePath(
+ kSafePortableRelativePaths[i])))) << kSafePortableRelativePaths[i];
+ }
+ for (size_t i = 0 ; i < arraysize(kUnsafePortableBasenames); ++i) {
+ EXPECT_FALSE(IsSafePortableRelativePath(base::FilePath(
+ kUnsafePortableBasenames[i]))) << kUnsafePortableBasenames[i];
+ if (!base::FilePath::StringType(kUnsafePortableBasenames[i]).empty()) {
+ EXPECT_FALSE(IsSafePortableRelativePath(safe_dirname.Append(
+ base::FilePath(kUnsafePortableBasenames[i]))))
+ << kUnsafePortableBasenames[i];
+ }
+ }
+}
+
+struct NonUniqueNameTestData {
+ bool is_unique;
+ const char* hostname;
+};
+
+// Google Test pretty-printer.
+void PrintTo(const NonUniqueNameTestData& data, std::ostream* os) {
+ ASSERT_TRUE(data.hostname);
+ *os << " hostname: " << testing::PrintToString(data.hostname)
+ << "; is_unique: " << testing::PrintToString(data.is_unique);
+}
+
+const NonUniqueNameTestData kNonUniqueNameTestData[] = {
+ // Domains under ICANN-assigned domains.
+ { true, "google.com" },
+ { true, "google.co.uk" },
+ // Domains under private registries.
+ { true, "appspot.com" },
+ { true, "test.appspot.com" },
+ // IPv4 addresses (in various forms).
+ { true, "8.8.8.8" },
+ { true, "1.2.3" },
+ { true, "14.15" },
+ { true, "676768" },
+ // IPv6 addresses.
+ { true, "FEDC:ba98:7654:3210:FEDC:BA98:7654:3210" },
+ { true, "::192.9.5.5" },
+ { true, "FEED::BEEF" },
+ // 'internal'/non-IANA assigned domains.
+ { false, "intranet" },
+ { false, "intranet." },
+ { false, "intranet.example" },
+ { false, "host.intranet.example" },
+ // gTLDs under discussion, but not yet assigned.
+ { false, "intranet.corp" },
+ { false, "example.tech" },
+ { false, "intranet.internal" },
+ // Invalid host names are treated as unique - but expected to be
+ // filtered out before then.
+ { true, "junk)(£)$*!@~#" },
+ { true, "w$w.example.com" },
+ { true, "nocolonsallowed:example" },
+ { true, "[::4.5.6.9]" },
+};
+
+class NetUtilNonUniqueNameTest
+ : public testing::TestWithParam<NonUniqueNameTestData> {
+ public:
+ virtual ~NetUtilNonUniqueNameTest() {}
+
+ protected:
+ bool IsUnique(const std::string& hostname) {
+ return !IsHostnameNonUnique(hostname);
+ }
+};
+
+// Test that internal/non-unique names are properly identified as such, but
+// that IP addresses and hosts beneath registry-controlled domains are flagged
+// as unique names.
+TEST_P(NetUtilNonUniqueNameTest, IsHostnameNonUnique) {
+ const NonUniqueNameTestData& test_data = GetParam();
+
+ EXPECT_EQ(test_data.is_unique, IsUnique(test_data.hostname));
+}
+
+INSTANTIATE_TEST_CASE_P(, NetUtilNonUniqueNameTest,
+ testing::ValuesIn(kNonUniqueNameTestData));
+
+} // namespace net
diff --git a/chromium/net/base/net_util_win.cc b/chromium/net/base/net_util_win.cc
new file mode 100644
index 00000000000..986af79eac7
--- /dev/null
+++ b/chromium/net/base/net_util_win.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 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.
+
+#include "net/base/net_util.h"
+
+#include <iphlpapi.h>
+#include <wlanapi.h>
+
+#include <algorithm>
+
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/win/scoped_handle.h"
+#include "net/base/escape.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+namespace {
+
+struct WlanApi {
+ typedef DWORD (WINAPI *WlanOpenHandleFunc)(
+ DWORD, VOID*, DWORD*, HANDLE*);
+ typedef DWORD (WINAPI *WlanEnumInterfacesFunc)(
+ HANDLE, VOID*, WLAN_INTERFACE_INFO_LIST**);
+ typedef DWORD (WINAPI *WlanQueryInterfaceFunc)(
+ HANDLE, const GUID*, WLAN_INTF_OPCODE, VOID*, DWORD*, VOID**,
+ WLAN_OPCODE_VALUE_TYPE*);
+ typedef VOID (WINAPI *WlanFreeMemoryFunc)(VOID*);
+ typedef DWORD (WINAPI *WlanCloseHandleFunc)(HANDLE, VOID*);
+
+ WlanApi() : initialized(false) {
+ // Use an absolute path to load the DLL to avoid DLL preloading attacks.
+ static const wchar_t* const kDLL = L"%WINDIR%\\system32\\wlanapi.dll";
+ wchar_t path[MAX_PATH] = {0};
+ ExpandEnvironmentStrings(kDLL, path, arraysize(path));
+ module = ::LoadLibraryEx(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+ if (!module)
+ return;
+
+ open_handle_func = reinterpret_cast<WlanOpenHandleFunc>(
+ ::GetProcAddress(module, "WlanOpenHandle"));
+ enum_interfaces_func = reinterpret_cast<WlanEnumInterfacesFunc>(
+ ::GetProcAddress(module, "WlanEnumInterfaces"));
+ query_interface_func = reinterpret_cast<WlanQueryInterfaceFunc>(
+ ::GetProcAddress(module, "WlanQueryInterface"));
+ free_memory_func = reinterpret_cast<WlanFreeMemoryFunc>(
+ ::GetProcAddress(module, "WlanFreeMemory"));
+ close_handle_func = reinterpret_cast<WlanCloseHandleFunc>(
+ ::GetProcAddress(module, "WlanCloseHandle"));
+ initialized = open_handle_func && enum_interfaces_func &&
+ query_interface_func && free_memory_func &&
+ close_handle_func;
+ }
+
+ HMODULE module;
+ WlanOpenHandleFunc open_handle_func;
+ WlanEnumInterfacesFunc enum_interfaces_func;
+ WlanQueryInterfaceFunc query_interface_func;
+ WlanFreeMemoryFunc free_memory_func;
+ WlanCloseHandleFunc close_handle_func;
+ bool initialized;
+};
+
+} // namespace
+
+namespace net {
+
+bool FileURLToFilePath(const GURL& url, base::FilePath* file_path) {
+ *file_path = base::FilePath();
+ std::wstring& file_path_str = const_cast<std::wstring&>(file_path->value());
+ file_path_str.clear();
+
+ if (!url.is_valid())
+ return false;
+
+ std::string path;
+ std::string host = url.host();
+ if (host.empty()) {
+ // URL contains no host, the path is the filename. In this case, the path
+ // will probably be preceeded with a slash, as in "/C:/foo.txt", so we
+ // trim out that here.
+ path = url.path();
+ size_t first_non_slash = path.find_first_not_of("/\\");
+ if (first_non_slash != std::string::npos && first_non_slash > 0)
+ path.erase(0, first_non_slash);
+ } else {
+ // URL contains a host: this means it's UNC. We keep the preceeding slash
+ // on the path.
+ path = "\\\\";
+ path.append(host);
+ path.append(url.path());
+ }
+
+ if (path.empty())
+ return false;
+ std::replace(path.begin(), path.end(), '/', '\\');
+
+ // GURL stores strings as percent-encoded UTF-8, this will undo if possible.
+ path = UnescapeURLComponent(path,
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS);
+
+ if (!IsStringUTF8(path)) {
+ // Not UTF-8, assume encoding is native codepage and we're done. We know we
+ // are giving the conversion function a nonempty string, and it may fail if
+ // the given string is not in the current encoding and give us an empty
+ // string back. We detect this and report failure.
+ file_path_str = base::SysNativeMBToWide(path);
+ return !file_path_str.empty();
+ }
+ file_path_str.assign(UTF8ToWide(path));
+
+ // We used to try too hard and see if |path| made up entirely of
+ // the 1st 256 characters in the Unicode was a zero-extended UTF-16.
+ // If so, we converted it to 'Latin-1' and checked if the result was UTF-8.
+ // If the check passed, we converted the result to UTF-8.
+ // Otherwise, we treated the result as the native OS encoding.
+ // However, that led to http://crbug.com/4619 and http://crbug.com/14153
+ return true;
+}
+
+bool GetNetworkList(NetworkInterfaceList* networks) {
+ // GetAdaptersAddresses() may require IO operations.
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ IP_ADAPTER_ADDRESSES info_temp;
+ ULONG len = 0;
+
+ // First get number of networks.
+ ULONG result = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, &info_temp, &len);
+ if (result != ERROR_BUFFER_OVERFLOW) {
+ // There are 0 networks.
+ return true;
+ }
+
+ scoped_ptr<char[]> buf(new char[len]);
+ IP_ADAPTER_ADDRESSES *adapters =
+ reinterpret_cast<IP_ADAPTER_ADDRESSES *>(buf.get());
+ result = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, adapters, &len);
+ if (result != NO_ERROR) {
+ LOG(ERROR) << "GetAdaptersAddresses failed: " << result;
+ return false;
+ }
+
+ for (IP_ADAPTER_ADDRESSES *adapter = adapters; adapter != NULL;
+ adapter = adapter->Next) {
+ // Ignore the loopback device.
+ if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) {
+ continue;
+ }
+
+ if (adapter->OperStatus != IfOperStatusUp) {
+ continue;
+ }
+
+ IP_ADAPTER_UNICAST_ADDRESS* address;
+ for (address = adapter->FirstUnicastAddress; address != NULL;
+ address = address->Next) {
+ int family = address->Address.lpSockaddr->sa_family;
+ if (family == AF_INET || family == AF_INET6) {
+ IPEndPoint endpoint;
+ if (endpoint.FromSockAddr(address->Address.lpSockaddr,
+ address->Address.iSockaddrLength)) {
+ std::string name = adapter->AdapterName;
+ networks->push_back(NetworkInterface(name, endpoint.address()));
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+WifiPHYLayerProtocol GetWifiPHYLayerProtocol() {
+ static base::LazyInstance<WlanApi>::Leaky lazy_wlanapi =
+ LAZY_INSTANCE_INITIALIZER;
+
+ struct WlanApiHandleTraits {
+ typedef HANDLE Handle;
+
+ static bool CloseHandle(HANDLE handle) {
+ return lazy_wlanapi.Get().close_handle_func(handle, NULL) ==
+ ERROR_SUCCESS;
+ }
+ static bool IsHandleValid(HANDLE handle) {
+ return base::win::HandleTraits::IsHandleValid(handle);
+ }
+ static HANDLE NullHandle() {
+ return base::win::HandleTraits::NullHandle();
+ }
+ };
+
+ typedef base::win::GenericScopedHandle<
+ WlanApiHandleTraits,
+ base::win::DummyVerifierTraits> WlanHandle;
+
+ struct WlanApiDeleter {
+ inline void operator()(void* ptr) const {
+ lazy_wlanapi.Get().free_memory_func(ptr);
+ }
+ };
+
+ const WlanApi& wlanapi = lazy_wlanapi.Get();
+ if (!wlanapi.initialized)
+ return WIFI_PHY_LAYER_PROTOCOL_NONE;
+
+ WlanHandle client;
+ DWORD cur_version = 0;
+ const DWORD kMaxClientVersion = 2;
+ DWORD result = wlanapi.open_handle_func(kMaxClientVersion, NULL, &cur_version,
+ client.Receive());
+ if (result != ERROR_SUCCESS)
+ return WIFI_PHY_LAYER_PROTOCOL_NONE;
+
+ WLAN_INTERFACE_INFO_LIST* interface_list_ptr = NULL;
+ result = wlanapi.enum_interfaces_func(client, NULL, &interface_list_ptr);
+ if (result != ERROR_SUCCESS)
+ return WIFI_PHY_LAYER_PROTOCOL_NONE;
+ scoped_ptr<WLAN_INTERFACE_INFO_LIST, WlanApiDeleter> interface_list(
+ interface_list_ptr);
+
+ // Assume at most one connected wifi interface.
+ WLAN_INTERFACE_INFO* info = NULL;
+ for (unsigned i = 0; i < interface_list->dwNumberOfItems; ++i) {
+ if (interface_list->InterfaceInfo[i].isState ==
+ wlan_interface_state_connected) {
+ info = &interface_list->InterfaceInfo[i];
+ break;
+ }
+ }
+
+ if (info == NULL)
+ return WIFI_PHY_LAYER_PROTOCOL_NONE;
+
+ WLAN_CONNECTION_ATTRIBUTES* conn_info_ptr;
+ DWORD conn_info_size = 0;
+ WLAN_OPCODE_VALUE_TYPE op_code;
+ result = wlanapi.query_interface_func(
+ client, &info->InterfaceGuid, wlan_intf_opcode_current_connection, NULL,
+ &conn_info_size, reinterpret_cast<VOID**>(&conn_info_ptr), &op_code);
+ if (result != ERROR_SUCCESS)
+ return WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+ scoped_ptr<WLAN_CONNECTION_ATTRIBUTES, WlanApiDeleter> conn_info(
+ conn_info_ptr);
+
+ switch (conn_info->wlanAssociationAttributes.dot11PhyType) {
+ case dot11_phy_type_fhss:
+ return WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
+ case dot11_phy_type_dsss:
+ return WIFI_PHY_LAYER_PROTOCOL_B;
+ case dot11_phy_type_irbaseband:
+ return WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
+ case dot11_phy_type_ofdm:
+ return WIFI_PHY_LAYER_PROTOCOL_A;
+ case dot11_phy_type_hrdsss:
+ return WIFI_PHY_LAYER_PROTOCOL_B;
+ case dot11_phy_type_erp:
+ return WIFI_PHY_LAYER_PROTOCOL_G;
+ case dot11_phy_type_ht:
+ return WIFI_PHY_LAYER_PROTOCOL_N;
+ default:
+ return WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_change_notifier.cc b/chromium/net/base/network_change_notifier.cc
new file mode 100644
index 00000000000..a6335d03dc2
--- /dev/null
+++ b/chromium/net/base/network_change_notifier.cc
@@ -0,0 +1,713 @@
+// Copyright (c) 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.
+
+#include "net/base/network_change_notifier.h"
+
+#include "base/metrics/histogram.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier_factory.h"
+#include "net/dns/dns_config_service.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "net/base/network_change_notifier_win.h"
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "net/base/network_change_notifier_linux.h"
+#elif defined(OS_MACOSX)
+#include "net/base/network_change_notifier_mac.h"
+#endif
+
+namespace net {
+
+namespace {
+
+// The actual singleton notifier. The class contract forbids usage of the API
+// in ways that would require us to place locks around access to this object.
+// (The prohibition on global non-POD objects makes it tricky to do such a thing
+// anyway.)
+NetworkChangeNotifier* g_network_change_notifier = NULL;
+
+// Class factory singleton.
+NetworkChangeNotifierFactory* g_network_change_notifier_factory = NULL;
+
+class MockNetworkChangeNotifier : public NetworkChangeNotifier {
+ public:
+ virtual ConnectionType GetCurrentConnectionType() const OVERRIDE {
+ return CONNECTION_UNKNOWN;
+ }
+};
+
+} // namespace
+
+// The main observer class that records UMAs for network events.
+class HistogramWatcher
+ : public NetworkChangeNotifier::ConnectionTypeObserver,
+ public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::DNSObserver,
+ public NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+ HistogramWatcher()
+ : last_ip_address_change_(base::TimeTicks::Now()),
+ last_connection_change_(base::TimeTicks::Now()),
+ last_dns_change_(base::TimeTicks::Now()),
+ last_network_change_(base::TimeTicks::Now()),
+ last_connection_type_(NetworkChangeNotifier::CONNECTION_UNKNOWN),
+ offline_packets_received_(0),
+ bytes_read_since_last_connection_change_(0),
+ peak_kbps_since_last_connection_change_(0) {}
+
+ // Registers our three Observer implementations. This is called from the
+ // network thread so that our Observer implementations are also called
+ // from the network thread. This avoids multi-threaded race conditions
+ // because the only other interface, |NotifyDataReceived| is also
+ // only called from the network thread.
+ void Init() {
+ NetworkChangeNotifier::AddConnectionTypeObserver(this);
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ NetworkChangeNotifier::AddDNSObserver(this);
+ NetworkChangeNotifier::AddNetworkChangeObserver(this);
+ }
+
+ virtual ~HistogramWatcher() {}
+
+ // NetworkChangeNotifier::IPAddressObserver implementation.
+ virtual void OnIPAddressChanged() OVERRIDE {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.IPAddressChange",
+ SinceLast(&last_ip_address_change_));
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ "NCN.ConnectionTypeChangeToIPAddressChange",
+ last_ip_address_change_ - last_connection_change_);
+ }
+
+ // NetworkChangeNotifier::ConnectionTypeObserver implementation.
+ virtual void OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType type) OVERRIDE {
+ base::TimeTicks now = base::TimeTicks::Now();
+ int32 kilobytes_read = bytes_read_since_last_connection_change_ / 1000;
+ base::TimeDelta state_duration = SinceLast(&last_connection_change_);
+ if (bytes_read_since_last_connection_change_) {
+ switch (last_connection_type_) {
+ case NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOnUnknown",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOnUnknown",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_ETHERNET:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOnEthernet",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOnEthernet",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_WIFI:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOnWifi",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOnWifi",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_2G:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOn2G",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOn2G",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_3G:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOn3G",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOn3G",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_4G:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOn4G",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOn4G",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_NONE:
+ UMA_HISTOGRAM_TIMES("NCN.CM.FirstReadOnNone",
+ first_byte_after_connection_change_);
+ UMA_HISTOGRAM_TIMES("NCN.CM.FastestRTTOnNone",
+ fastest_RTT_since_last_connection_change_);
+ break;
+ }
+ }
+ if (peak_kbps_since_last_connection_change_) {
+ switch (last_connection_type_) {
+ case NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOnUnknown",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_ETHERNET:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOnEthernet",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_WIFI:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOnWifi",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_2G:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOn2G",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_3G:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOn3G",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_4G:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOn4G",
+ peak_kbps_since_last_connection_change_);
+ break;
+ case NetworkChangeNotifier::CONNECTION_NONE:
+ UMA_HISTOGRAM_COUNTS("NCN.CM.PeakKbpsOnNone",
+ peak_kbps_since_last_connection_change_);
+ break;
+ }
+ }
+ switch (last_connection_type_) {
+ case NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOnUnknown", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOnUnknown", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_ETHERNET:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOnEthernet", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOnEthernet", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_WIFI:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOnWifi", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOnWifi", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_2G:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOn2G", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOn2G", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_3G:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOn3G", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOn3G", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_4G:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOn4G", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOn4G", kilobytes_read);
+ break;
+ case NetworkChangeNotifier::CONNECTION_NONE:
+ UMA_HISTOGRAM_LONG_TIMES("NCN.CM.TimeOnNone", state_duration);
+ UMA_HISTOGRAM_COUNTS("NCN.CM.KBTransferedOnNone", kilobytes_read);
+ break;
+ }
+
+ if (type != NetworkChangeNotifier::CONNECTION_NONE) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OnlineChange", state_duration);
+
+ if (offline_packets_received_) {
+ if ((now - last_offline_packet_received_) <
+ base::TimeDelta::FromSeconds(5)) {
+ // We can compare this sum with the sum of NCN.OfflineDataRecv.
+ UMA_HISTOGRAM_COUNTS_10000(
+ "NCN.OfflineDataRecvAny5sBeforeOnline",
+ offline_packets_received_);
+ }
+
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineDataRecvUntilOnline",
+ now - last_offline_packet_received_);
+ }
+ } else {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineChange", state_duration);
+ }
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ "NCN.IPAddressChangeToConnectionTypeChange",
+ now - last_ip_address_change_);
+
+ offline_packets_received_ = 0;
+ bytes_read_since_last_connection_change_ = 0;
+ peak_kbps_since_last_connection_change_ = 0;
+ last_connection_type_ = type;
+ polling_interval_ = base::TimeDelta::FromSeconds(1);
+ }
+
+ // NetworkChangeNotifier::DNSObserver implementation.
+ virtual void OnDNSChanged() OVERRIDE {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.DNSConfigChange",
+ SinceLast(&last_dns_change_));
+ }
+
+ // NetworkChangeNotifier::NetworkChangeObserver implementation.
+ virtual void OnNetworkChanged(
+ NetworkChangeNotifier::ConnectionType type) OVERRIDE {
+ if (type != NetworkChangeNotifier::CONNECTION_NONE) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.NetworkOnlineChange",
+ SinceLast(&last_network_change_));
+ } else {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.NetworkOfflineChange",
+ SinceLast(&last_network_change_));
+ }
+ }
+
+ // Record histogram data whenever we receive a packet. Should only be called
+ // from the network thread.
+ void NotifyDataReceived(const URLRequest& request, int bytes_read) {
+ if (IsLocalhost(request.url().host()) ||
+ !(request.url().SchemeIs("http") || request.url().SchemeIs("https"))) {
+ return;
+ }
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta request_duration = now - request.creation_time();
+ if (bytes_read_since_last_connection_change_ == 0) {
+ first_byte_after_connection_change_ = now - last_connection_change_;
+ fastest_RTT_since_last_connection_change_ = request_duration;
+ }
+ bytes_read_since_last_connection_change_ += bytes_read;
+ if (request_duration < fastest_RTT_since_last_connection_change_)
+ fastest_RTT_since_last_connection_change_ = request_duration;
+ // Ignore tiny transfers which will not produce accurate rates.
+ // Ignore zero duration transfers which might cause divide by zero.
+ if (bytes_read > 10000 &&
+ request_duration > base::TimeDelta::FromMilliseconds(1) &&
+ request.creation_time() > last_connection_change_) {
+ int32 kbps = bytes_read * 8 / request_duration.InMilliseconds();
+ if (kbps > peak_kbps_since_last_connection_change_)
+ peak_kbps_since_last_connection_change_ = kbps;
+ }
+
+ if (last_connection_type_ != NetworkChangeNotifier::CONNECTION_NONE)
+ return;
+
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.OfflineDataRecv",
+ now - last_connection_change_);
+ offline_packets_received_++;
+ last_offline_packet_received_ = now;
+
+ if ((now - last_polled_connection_) > polling_interval_) {
+ polling_interval_ *= 2;
+ last_polled_connection_ = now;
+ last_polled_connection_type_ =
+ NetworkChangeNotifier::GetConnectionType();
+ }
+ if (last_polled_connection_type_ ==
+ NetworkChangeNotifier::CONNECTION_NONE) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("NCN.PollingOfflineDataRecv",
+ now - last_connection_change_);
+ }
+ }
+
+ private:
+ static base::TimeDelta SinceLast(base::TimeTicks *last_time) {
+ base::TimeTicks current_time = base::TimeTicks::Now();
+ base::TimeDelta delta = current_time - *last_time;
+ *last_time = current_time;
+ return delta;
+ }
+
+ base::TimeTicks last_ip_address_change_;
+ base::TimeTicks last_connection_change_;
+ base::TimeTicks last_dns_change_;
+ base::TimeTicks last_network_change_;
+ base::TimeTicks last_offline_packet_received_;
+ base::TimeTicks last_polled_connection_;
+ // |polling_interval_| is initialized by |OnConnectionTypeChanged| on our
+ // first transition to offline and on subsequent transitions. Once offline,
+ // |polling_interval_| doubles as offline data is received and we poll
+ // with |NetworkChangeNotifier::GetConnectionType| to verify the connection
+ // state.
+ base::TimeDelta polling_interval_;
+ // |last_connection_type_| is the last value passed to
+ // |OnConnectionTypeChanged|.
+ NetworkChangeNotifier::ConnectionType last_connection_type_;
+ // |last_polled_connection_type_| is last result from calling
+ // |NetworkChangeNotifier::GetConnectionType| in |NotifyDataReceived|.
+ NetworkChangeNotifier::ConnectionType last_polled_connection_type_;
+ // Count of how many times NotifyDataReceived() has been called while the
+ // NetworkChangeNotifier thought network connection was offline.
+ int32 offline_packets_received_;
+ // Number of bytes of network data received since last connectivity change.
+ int32 bytes_read_since_last_connection_change_;
+ // Fastest round-trip-time (RTT) since last connectivity change. RTT measured
+ // from URLRequest creation until first byte received.
+ base::TimeDelta fastest_RTT_since_last_connection_change_;
+ // Time between connectivity change and first network data byte received.
+ base::TimeDelta first_byte_after_connection_change_;
+ // Rough measurement of peak KB/s witnessed since last connectivity change.
+ // The accuracy is decreased by ignoring these factors:
+ // 1) Multiple URLRequests can occur concurrently.
+ // 2) NotifyDataReceived() may be called repeatedly for one URLRequest.
+ // 3) The transfer time includes at least one RTT while no bytes are read.
+ // Erring on the conservative side is hopefully offset by taking the maximum.
+ int32 peak_kbps_since_last_connection_change_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistogramWatcher);
+};
+
+// NetworkState is thread safe.
+class NetworkChangeNotifier::NetworkState {
+ public:
+ NetworkState() {}
+ ~NetworkState() {}
+
+ void GetDnsConfig(DnsConfig* config) const {
+ base::AutoLock lock(lock_);
+ *config = dns_config_;
+ }
+
+ void SetDnsConfig(const DnsConfig& dns_config) {
+ base::AutoLock lock(lock_);
+ dns_config_ = dns_config;
+ }
+
+ private:
+ mutable base::Lock lock_;
+ DnsConfig dns_config_;
+};
+
+NetworkChangeNotifier::NetworkChangeCalculatorParams::
+ NetworkChangeCalculatorParams() {
+}
+
+// Calculates NetworkChange signal from IPAddress and ConnectionType signals.
+class NetworkChangeNotifier::NetworkChangeCalculator
+ : public ConnectionTypeObserver,
+ public IPAddressObserver {
+ public:
+ NetworkChangeCalculator(const NetworkChangeCalculatorParams& params)
+ : params_(params),
+ have_announced_(false),
+ last_announced_connection_type_(CONNECTION_NONE),
+ pending_connection_type_(CONNECTION_NONE) {}
+
+ void Init() {
+ AddConnectionTypeObserver(this);
+ AddIPAddressObserver(this);
+ }
+
+ virtual ~NetworkChangeCalculator() {
+ RemoveConnectionTypeObserver(this);
+ RemoveIPAddressObserver(this);
+ }
+
+ // NetworkChangeNotifier::IPAddressObserver implementation.
+ virtual void OnIPAddressChanged() OVERRIDE {
+ base::TimeDelta delay = last_announced_connection_type_ == CONNECTION_NONE
+ ? params_.ip_address_offline_delay_ : params_.ip_address_online_delay_;
+ // Cancels any previous timer.
+ timer_.Start(FROM_HERE, delay, this, &NetworkChangeCalculator::Notify);
+ }
+
+ // NetworkChangeNotifier::ConnectionTypeObserver implementation.
+ virtual void OnConnectionTypeChanged(ConnectionType type) OVERRIDE {
+ pending_connection_type_ = type;
+ base::TimeDelta delay = last_announced_connection_type_ == CONNECTION_NONE
+ ? params_.connection_type_offline_delay_
+ : params_.connection_type_online_delay_;
+ // Cancels any previous timer.
+ timer_.Start(FROM_HERE, delay, this, &NetworkChangeCalculator::Notify);
+ }
+
+ private:
+ void Notify() {
+ // Don't bother signaling about dead connections.
+ if (have_announced_ &&
+ (last_announced_connection_type_ == CONNECTION_NONE) &&
+ (pending_connection_type_ == CONNECTION_NONE)) {
+ return;
+ }
+ have_announced_ = true;
+ last_announced_connection_type_ = pending_connection_type_;
+ // Immediately before sending out an online signal, send out an offline
+ // signal to perform any destructive actions before constructive actions.
+ if (pending_connection_type_ != CONNECTION_NONE)
+ NetworkChangeNotifier::NotifyObserversOfNetworkChange(CONNECTION_NONE);
+ NetworkChangeNotifier::NotifyObserversOfNetworkChange(
+ pending_connection_type_);
+ }
+
+ const NetworkChangeCalculatorParams params_;
+
+ // Indicates if NotifyObserversOfNetworkChange has been called yet.
+ bool have_announced_;
+ // Last value passed to NotifyObserversOfNetworkChange.
+ ConnectionType last_announced_connection_type_;
+ // Value to pass to NotifyObserversOfNetworkChange when Notify is called.
+ ConnectionType pending_connection_type_;
+ // Used to delay notifications so duplicates can be combined.
+ base::OneShotTimer<NetworkChangeCalculator> timer_;
+};
+
+NetworkChangeNotifier::~NetworkChangeNotifier() {
+ DCHECK_EQ(this, g_network_change_notifier);
+ g_network_change_notifier = NULL;
+}
+
+// static
+void NetworkChangeNotifier::SetFactory(
+ NetworkChangeNotifierFactory* factory) {
+ CHECK(!g_network_change_notifier_factory);
+ g_network_change_notifier_factory = factory;
+}
+
+// static
+NetworkChangeNotifier* NetworkChangeNotifier::Create() {
+ if (g_network_change_notifier_factory)
+ return g_network_change_notifier_factory->CreateInstance();
+
+#if defined(OS_WIN)
+ NetworkChangeNotifierWin* network_change_notifier =
+ new NetworkChangeNotifierWin();
+ network_change_notifier->WatchForAddressChange();
+ return network_change_notifier;
+#elif defined(OS_CHROMEOS) || defined(OS_ANDROID)
+ // ChromeOS and Android builds MUST use their own class factory.
+#if !defined(OS_CHROMEOS)
+ // TODO(oshima): ash_shell do not have access to chromeos'es
+ // notifier yet. Re-enable this when chromeos'es notifier moved to
+ // chromeos root directory. crbug.com/119298.
+ CHECK(false);
+#endif
+ return NULL;
+#elif defined(OS_LINUX)
+ return NetworkChangeNotifierLinux::Create();
+#elif defined(OS_MACOSX)
+ return new NetworkChangeNotifierMac();
+#else
+ NOTIMPLEMENTED();
+ return NULL;
+#endif
+}
+
+// static
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifier::GetConnectionType() {
+ return g_network_change_notifier ?
+ g_network_change_notifier->GetCurrentConnectionType() :
+ CONNECTION_UNKNOWN;
+}
+
+// static
+void NetworkChangeNotifier::GetDnsConfig(DnsConfig* config) {
+ if (!g_network_change_notifier) {
+ *config = DnsConfig();
+ } else {
+ g_network_change_notifier->network_state_->GetDnsConfig(config);
+ }
+}
+
+// static
+const char* NetworkChangeNotifier::ConnectionTypeToString(
+ ConnectionType type) {
+ static const char* kConnectionTypeNames[] = {
+ "CONNECTION_UNKNOWN",
+ "CONNECTION_ETHERNET",
+ "CONNECTION_WIFI",
+ "CONNECTION_2G",
+ "CONNECTION_3G",
+ "CONNECTION_4G",
+ "CONNECTION_NONE"
+ };
+ COMPILE_ASSERT(
+ arraysize(kConnectionTypeNames) ==
+ NetworkChangeNotifier::CONNECTION_NONE + 1,
+ ConnectionType_name_count_mismatch);
+ if (type < CONNECTION_UNKNOWN || type > CONNECTION_NONE) {
+ NOTREACHED();
+ return "CONNECTION_INVALID";
+ }
+ return kConnectionTypeNames[type];
+}
+
+// static
+void NetworkChangeNotifier::NotifyDataReceived(const URLRequest& request,
+ int bytes_read) {
+ if (!g_network_change_notifier)
+ return;
+ g_network_change_notifier->histogram_watcher_->NotifyDataReceived(request,
+ bytes_read);
+}
+
+// static
+void NetworkChangeNotifier::InitHistogramWatcher() {
+ if (!g_network_change_notifier)
+ return;
+ g_network_change_notifier->histogram_watcher_->Init();
+}
+
+#if defined(OS_LINUX)
+// static
+const internal::AddressTrackerLinux*
+NetworkChangeNotifier::GetAddressTracker() {
+ return g_network_change_notifier ?
+ g_network_change_notifier->GetAddressTrackerInternal() : NULL;
+}
+#endif
+
+// static
+bool NetworkChangeNotifier::IsOffline() {
+ return GetConnectionType() == CONNECTION_NONE;
+}
+
+// static
+bool NetworkChangeNotifier::IsConnectionCellular(ConnectionType type) {
+ bool is_cellular = false;
+ switch (type) {
+ case CONNECTION_2G:
+ case CONNECTION_3G:
+ case CONNECTION_4G:
+ is_cellular = true;
+ break;
+ case CONNECTION_UNKNOWN:
+ case CONNECTION_ETHERNET:
+ case CONNECTION_WIFI:
+ case CONNECTION_NONE:
+ is_cellular = false;
+ break;
+ }
+ return is_cellular;
+}
+
+// static
+NetworkChangeNotifier* NetworkChangeNotifier::CreateMock() {
+ return new MockNetworkChangeNotifier();
+}
+
+void NetworkChangeNotifier::AddIPAddressObserver(IPAddressObserver* observer) {
+ if (g_network_change_notifier)
+ g_network_change_notifier->ip_address_observer_list_->AddObserver(observer);
+}
+
+void NetworkChangeNotifier::AddConnectionTypeObserver(
+ ConnectionTypeObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->connection_type_observer_list_->AddObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::AddDNSObserver(DNSObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->resolver_state_observer_list_->AddObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::AddNetworkChangeObserver(
+ NetworkChangeObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->network_change_observer_list_->AddObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::RemoveIPAddressObserver(
+ IPAddressObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->ip_address_observer_list_->RemoveObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::RemoveConnectionTypeObserver(
+ ConnectionTypeObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->connection_type_observer_list_->RemoveObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::RemoveDNSObserver(DNSObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->resolver_state_observer_list_->RemoveObserver(
+ observer);
+ }
+}
+
+void NetworkChangeNotifier::RemoveNetworkChangeObserver(
+ NetworkChangeObserver* observer) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->network_change_observer_list_->RemoveObserver(
+ observer);
+ }
+}
+
+NetworkChangeNotifier::NetworkChangeNotifier(
+ const NetworkChangeCalculatorParams& params
+ /*= NetworkChangeCalculatorParams()*/)
+ : ip_address_observer_list_(
+ new ObserverListThreadSafe<IPAddressObserver>(
+ ObserverListBase<IPAddressObserver>::NOTIFY_EXISTING_ONLY)),
+ connection_type_observer_list_(
+ new ObserverListThreadSafe<ConnectionTypeObserver>(
+ ObserverListBase<ConnectionTypeObserver>::NOTIFY_EXISTING_ONLY)),
+ resolver_state_observer_list_(
+ new ObserverListThreadSafe<DNSObserver>(
+ ObserverListBase<DNSObserver>::NOTIFY_EXISTING_ONLY)),
+ network_change_observer_list_(
+ new ObserverListThreadSafe<NetworkChangeObserver>(
+ ObserverListBase<NetworkChangeObserver>::NOTIFY_EXISTING_ONLY)),
+ network_state_(new NetworkState()),
+ histogram_watcher_(new HistogramWatcher()),
+ network_change_calculator_(new NetworkChangeCalculator(params)) {
+ DCHECK(!g_network_change_notifier);
+ g_network_change_notifier = this;
+ network_change_calculator_->Init();
+}
+
+#if defined(OS_LINUX)
+const internal::AddressTrackerLinux*
+NetworkChangeNotifier::GetAddressTrackerInternal() const {
+ return NULL;
+}
+#endif
+
+// static
+void NetworkChangeNotifier::NotifyObserversOfIPAddressChange() {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->ip_address_observer_list_->Notify(
+ &IPAddressObserver::OnIPAddressChanged);
+ }
+}
+
+// static
+void NetworkChangeNotifier::NotifyObserversOfDNSChange() {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->resolver_state_observer_list_->Notify(
+ &DNSObserver::OnDNSChanged);
+ }
+}
+
+// static
+void NetworkChangeNotifier::SetDnsConfig(const DnsConfig& config) {
+ if (!g_network_change_notifier)
+ return;
+ g_network_change_notifier->network_state_->SetDnsConfig(config);
+ NotifyObserversOfDNSChange();
+}
+
+void NetworkChangeNotifier::NotifyObserversOfConnectionTypeChange() {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->connection_type_observer_list_->Notify(
+ &ConnectionTypeObserver::OnConnectionTypeChanged,
+ GetConnectionType());
+ }
+}
+
+void NetworkChangeNotifier::NotifyObserversOfNetworkChange(
+ ConnectionType type) {
+ if (g_network_change_notifier) {
+ g_network_change_notifier->network_change_observer_list_->Notify(
+ &NetworkChangeObserver::OnNetworkChanged,
+ type);
+ }
+}
+
+NetworkChangeNotifier::DisableForTest::DisableForTest()
+ : network_change_notifier_(g_network_change_notifier) {
+ DCHECK(g_network_change_notifier);
+ g_network_change_notifier = NULL;
+}
+
+NetworkChangeNotifier::DisableForTest::~DisableForTest() {
+ DCHECK(!g_network_change_notifier);
+ g_network_change_notifier = network_change_notifier_;
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_change_notifier.h b/chromium/net/base/network_change_notifier.h
new file mode 100644
index 00000000000..2ac6f578109
--- /dev/null
+++ b/chromium/net/base/network_change_notifier.h
@@ -0,0 +1,323 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_H_
+
+#include "base/basictypes.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+struct DnsConfig;
+class HistogramWatcher;
+class NetworkChangeNotifierFactory;
+class URLRequest;
+
+#if defined(OS_LINUX)
+namespace internal {
+class AddressTrackerLinux;
+}
+#endif
+
+// NetworkChangeNotifier monitors the system for network changes, and notifies
+// registered observers of those events. Observers may register on any thread,
+// and will be called back on the thread from which they registered.
+// NetworkChangeNotifiers are threadsafe, though they must be created and
+// destroyed on the same thread.
+class NET_EXPORT NetworkChangeNotifier {
+ public:
+ // Using the terminology of the Network Information API:
+ // http://www.w3.org/TR/netinfo-api.
+ enum ConnectionType {
+ CONNECTION_UNKNOWN = 0, // A connection exists, but its type is unknown.
+ CONNECTION_ETHERNET = 1,
+ CONNECTION_WIFI = 2,
+ CONNECTION_2G = 3,
+ CONNECTION_3G = 4,
+ CONNECTION_4G = 5,
+ CONNECTION_NONE = 6 // No connection.
+ };
+
+ class NET_EXPORT IPAddressObserver {
+ public:
+ // Will be called when the IP address of the primary interface changes.
+ // This includes when the primary interface itself changes.
+ virtual void OnIPAddressChanged() = 0;
+
+ protected:
+ IPAddressObserver() {}
+ virtual ~IPAddressObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IPAddressObserver);
+ };
+
+ class NET_EXPORT ConnectionTypeObserver {
+ public:
+ // Will be called when the connection type of the system has changed.
+ // See NetworkChangeNotifier::GetConnectionType() for important caveats
+ // about the unreliability of using this signal to infer the ability to
+ // reach remote sites.
+ virtual void OnConnectionTypeChanged(ConnectionType type) = 0;
+
+ protected:
+ ConnectionTypeObserver() {}
+ virtual ~ConnectionTypeObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConnectionTypeObserver);
+ };
+
+ class NET_EXPORT DNSObserver {
+ public:
+ // Will be called when the DNS settings of the system may have changed.
+ // Use GetDnsConfig to obtain the current settings.
+ virtual void OnDNSChanged() = 0;
+
+ protected:
+ DNSObserver() {}
+ virtual ~DNSObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DNSObserver);
+ };
+
+ class NET_EXPORT NetworkChangeObserver {
+ public:
+ // OnNetworkChanged will be called when a change occurs to the host
+ // computer's hardware or software that affects the route network packets
+ // take to any network server. Some examples:
+ // 1. A network connection becoming available or going away. For example
+ // plugging or unplugging an Ethernet cable, WiFi or cellular modem
+ // connecting or disconnecting from a network, or a VPN tunnel being
+ // established or taken down.
+ // 2. An active network connection's IP address changes.
+ // 3. A change to the local IP routing tables.
+ // The signal shall only be produced when the change is complete. For
+ // example if a new network connection has become available, only give the
+ // signal once we think the O/S has finished establishing the connection
+ // (i.e. DHCP is done) to the point where the new connection is usable.
+ // The signal shall not be produced spuriously as it will be triggering some
+ // expensive operations, like socket pools closing all connections and
+ // sockets and then re-establishing them.
+ // |type| indicates the type of the active primary network connection after
+ // the change. Observers performing "constructive" activities like trying
+ // to establish a connection to a server should only do so when
+ // |type != CONNECTION_NONE|. Observers performing "destructive" activities
+ // like resetting already established server connections should only do so
+ // when |type == CONNECTION_NONE|. OnNetworkChanged will always be called
+ // with CONNECTION_NONE immediately prior to being called with an online
+ // state; this is done to make sure that destructive actions take place
+ // prior to constructive actions.
+ virtual void OnNetworkChanged(ConnectionType type) = 0;
+
+ protected:
+ NetworkChangeObserver() {}
+ virtual ~NetworkChangeObserver() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeObserver);
+ };
+
+ virtual ~NetworkChangeNotifier();
+
+ // See the description of NetworkChangeNotifier::GetConnectionType().
+ // Implementations must be thread-safe. Implementations must also be
+ // cheap as this could be called (repeatedly) from the network thread.
+ virtual ConnectionType GetCurrentConnectionType() const = 0;
+
+ // Replaces the default class factory instance of NetworkChangeNotifier class.
+ // The method will take over the ownership of |factory| object.
+ static void SetFactory(NetworkChangeNotifierFactory* factory);
+
+ // Creates the process-wide, platform-specific NetworkChangeNotifier. The
+ // caller owns the returned pointer. You may call this on any thread. You
+ // may also avoid creating this entirely (in which case nothing will be
+ // monitored), but if you do create it, you must do so before any other
+ // threads try to access the API below, and it must outlive all other threads
+ // which might try to use it.
+ static NetworkChangeNotifier* Create();
+
+ // Returns the connection type.
+ // A return value of |CONNECTION_NONE| is a pretty strong indicator that the
+ // user won't be able to connect to remote sites. However, another return
+ // value doesn't imply that the user will be able to connect to remote sites;
+ // even if some link is up, it is uncertain whether a particular connection
+ // attempt to a particular remote site will be successful.
+ // The returned value only describes the connection currently used by the
+ // device, and does not take into account other machines on the network. For
+ // example, if the device is connected using Wifi to a 3G gateway to access
+ // the internet, the connection type is CONNECTION_WIFI.
+ static ConnectionType GetConnectionType();
+
+ // Retrieve the last read DnsConfig. This could be expensive if the system has
+ // a large HOSTS file.
+ static void GetDnsConfig(DnsConfig* config);
+
+#if defined(OS_LINUX)
+ // Returns the AddressTrackerLinux if present.
+ static const internal::AddressTrackerLinux* GetAddressTracker();
+#endif
+
+ // Convenience method to determine if the user is offline.
+ // Returns true if there is currently no internet connection.
+ //
+ // A return value of |true| is a pretty strong indicator that the user
+ // won't be able to connect to remote sites. However, a return value of
+ // |false| is inconclusive; even if some link is up, it is uncertain
+ // whether a particular connection attempt to a particular remote site
+ // will be successfully.
+ static bool IsOffline();
+
+ // Returns true if |type| is a cellular connection.
+ // Returns false if |type| is CONNECTION_UNKNOWN, and thus, depending on the
+ // implementation of GetConnectionType(), it is possible that
+ // IsConnectionCellular(GetConnectionType()) returns false even if the
+ // current connection is cellular.
+ static bool IsConnectionCellular(ConnectionType type);
+
+ // Like Create(), but for use in tests. The mock object doesn't monitor any
+ // events, it merely rebroadcasts notifications when requested.
+ static NetworkChangeNotifier* CreateMock();
+
+ // Registers |observer| to receive notifications of network changes. The
+ // thread on which this is called is the thread on which |observer| will be
+ // called back with notifications. This is safe to call if Create() has not
+ // been called (as long as it doesn't race the Create() call on another
+ // thread), in which case it will simply do nothing.
+ static void AddIPAddressObserver(IPAddressObserver* observer);
+ static void AddConnectionTypeObserver(ConnectionTypeObserver* observer);
+ static void AddDNSObserver(DNSObserver* observer);
+ static void AddNetworkChangeObserver(NetworkChangeObserver* observer);
+
+ // Unregisters |observer| from receiving notifications. This must be called
+ // on the same thread on which AddObserver() was called. Like AddObserver(),
+ // this is safe to call if Create() has not been called (as long as it doesn't
+ // race the Create() call on another thread), in which case it will simply do
+ // nothing. Technically, it's also safe to call after the notifier object has
+ // been destroyed, if the call doesn't race the notifier's destruction, but
+ // there's no reason to use the API in this risky way, so don't do it.
+ static void RemoveIPAddressObserver(IPAddressObserver* observer);
+ static void RemoveConnectionTypeObserver(ConnectionTypeObserver* observer);
+ static void RemoveDNSObserver(DNSObserver* observer);
+ static void RemoveNetworkChangeObserver(NetworkChangeObserver* observer);
+
+ // Allow unit tests to trigger notifications.
+ static void NotifyObserversOfIPAddressChangeForTests() {
+ NotifyObserversOfIPAddressChange();
+ }
+
+ // Return a string equivalent to |type|.
+ static const char* ConnectionTypeToString(ConnectionType type);
+
+ // Let the NetworkChangeNotifier know we received some data.
+ // This is used for producing histogram data about the accuracy of
+ // the NetworkChangenotifier's online detection and rough network
+ // connection measurements.
+ static void NotifyDataReceived(const URLRequest& request, int bytes_read);
+
+ // Register the Observer callbacks for producing histogram data. This
+ // should be called from the network thread to avoid race conditions.
+ static void InitHistogramWatcher();
+
+ // Allows a second NetworkChangeNotifier to be created for unit testing, so
+ // the test suite can create a MockNetworkChangeNotifier, but platform
+ // specific NetworkChangeNotifiers can also be created for testing. To use,
+ // create an DisableForTest object, and then create the new
+ // NetworkChangeNotifier object. The NetworkChangeNotifier must be
+ // destroyed before the DisableForTest object, as its destruction will restore
+ // the original NetworkChangeNotifier.
+ class NET_EXPORT DisableForTest {
+ public:
+ DisableForTest();
+ ~DisableForTest();
+
+ private:
+ // The original NetworkChangeNotifier to be restored on destruction.
+ NetworkChangeNotifier* network_change_notifier_;
+ };
+
+ protected:
+ // NetworkChanged signal is calculated from the IPAddressChanged and
+ // ConnectionTypeChanged signals. Delay parameters control how long to delay
+ // producing NetworkChanged signal after particular input signals so as to
+ // combine duplicates. In other words if an input signal is repeated within
+ // the corresponding delay period, only one resulting NetworkChange signal is
+ // produced.
+ struct NET_EXPORT NetworkChangeCalculatorParams {
+ NetworkChangeCalculatorParams();
+ // Controls delay after OnIPAddressChanged when transitioning from an
+ // offline state.
+ base::TimeDelta ip_address_offline_delay_;
+ // Controls delay after OnIPAddressChanged when transitioning from an
+ // online state.
+ base::TimeDelta ip_address_online_delay_;
+ // Controls delay after OnConnectionTypeChanged when transitioning from an
+ // offline state.
+ base::TimeDelta connection_type_offline_delay_;
+ // Controls delay after OnConnectionTypeChanged when transitioning from an
+ // online state.
+ base::TimeDelta connection_type_online_delay_;
+ };
+
+ explicit NetworkChangeNotifier(
+ const NetworkChangeCalculatorParams& params =
+ NetworkChangeCalculatorParams());
+
+#if defined(OS_LINUX)
+ // Returns the AddressTrackerLinux if present.
+ // TODO(szym): Retrieve AddressMap from NetworkState. http://crbug.com/144212
+ virtual const internal::AddressTrackerLinux*
+ GetAddressTrackerInternal() const;
+#endif
+
+ // Broadcasts a notification to all registered observers. Note that this
+ // happens asynchronously, even for observers on the current thread, even in
+ // tests.
+ static void NotifyObserversOfIPAddressChange();
+ static void NotifyObserversOfConnectionTypeChange();
+ static void NotifyObserversOfDNSChange();
+ static void NotifyObserversOfNetworkChange(ConnectionType type);
+
+ // Stores |config| in NetworkState and notifies observers.
+ static void SetDnsConfig(const DnsConfig& config);
+
+ private:
+ friend class HostResolverImplDnsTest;
+ friend class NetworkChangeNotifierAndroidTest;
+ friend class NetworkChangeNotifierLinuxTest;
+ friend class NetworkChangeNotifierWinTest;
+
+ class NetworkState;
+ class NetworkChangeCalculator;
+
+ const scoped_refptr<ObserverListThreadSafe<IPAddressObserver> >
+ ip_address_observer_list_;
+ const scoped_refptr<ObserverListThreadSafe<ConnectionTypeObserver> >
+ connection_type_observer_list_;
+ const scoped_refptr<ObserverListThreadSafe<DNSObserver> >
+ resolver_state_observer_list_;
+ const scoped_refptr<ObserverListThreadSafe<NetworkChangeObserver> >
+ network_change_observer_list_;
+
+ // The current network state. Hosts DnsConfig, exposed via GetDnsConfig.
+ scoped_ptr<NetworkState> network_state_;
+
+ // A little-piggy-back observer that simply logs UMA histogram data.
+ scoped_ptr<HistogramWatcher> histogram_watcher_;
+
+ // Computes NetworkChange signal from IPAddress and ConnectionType signals.
+ scoped_ptr<NetworkChangeCalculator> network_change_calculator_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifier);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_H_
diff --git a/chromium/net/base/network_change_notifier_factory.h b/chromium/net/base/network_change_notifier_factory.h
new file mode 100644
index 00000000000..88cc15ab44d
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_factory.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_BASE_NETWORK_CHANGE_NOTIFIER_FACTORY_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_FACTORY_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NetworkChangeNotifier;
+// NetworkChangeNotifierFactory provides a mechanism for overriding the default
+// instance creation process of NetworkChangeNotifier.
+class NET_EXPORT NetworkChangeNotifierFactory {
+ public:
+ NetworkChangeNotifierFactory() {}
+ virtual ~NetworkChangeNotifierFactory() {}
+ virtual NetworkChangeNotifier* CreateInstance() = 0;
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_FACTORY_H_
diff --git a/chromium/net/base/network_change_notifier_linux.cc b/chromium/net/base/network_change_notifier_linux.cc
new file mode 100644
index 00000000000..19da249679a
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_linux.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 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.
+
+#include "net/base/network_change_notifier_linux.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/threading/thread.h"
+#include "net/base/address_tracker_linux.h"
+#include "net/dns/dns_config_service.h"
+
+namespace net {
+
+class NetworkChangeNotifierLinux::Thread : public base::Thread {
+ public:
+ Thread();
+ virtual ~Thread();
+
+ // Plumbing for NetworkChangeNotifier::GetCurrentConnectionType.
+ // Safe to call from any thread.
+ NetworkChangeNotifier::ConnectionType GetCurrentConnectionType() {
+ return address_tracker_.GetCurrentConnectionType();
+ }
+
+ const internal::AddressTrackerLinux* address_tracker() const {
+ return &address_tracker_;
+ }
+
+ protected:
+ // base::Thread
+ virtual void Init() OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ private:
+ scoped_ptr<DnsConfigService> dns_config_service_;
+ // Used to detect online/offline state and IP address changes.
+ internal::AddressTrackerLinux address_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(Thread);
+};
+
+NetworkChangeNotifierLinux::Thread::Thread()
+ : base::Thread("NetworkChangeNotifier"),
+ address_tracker_(
+ base::Bind(&NetworkChangeNotifier::
+ NotifyObserversOfIPAddressChange),
+ base::Bind(&NetworkChangeNotifier::
+ NotifyObserversOfConnectionTypeChange)) {
+}
+
+NetworkChangeNotifierLinux::Thread::~Thread() {
+ DCHECK(!Thread::IsRunning());
+}
+
+void NetworkChangeNotifierLinux::Thread::Init() {
+ address_tracker_.Init();
+ dns_config_service_ = DnsConfigService::CreateSystemService();
+ dns_config_service_->WatchConfig(
+ base::Bind(&NetworkChangeNotifier::SetDnsConfig));
+}
+
+void NetworkChangeNotifierLinux::Thread::CleanUp() {
+ dns_config_service_.reset();
+}
+
+NetworkChangeNotifierLinux* NetworkChangeNotifierLinux::Create() {
+ return new NetworkChangeNotifierLinux();
+}
+
+NetworkChangeNotifierLinux::NetworkChangeNotifierLinux()
+ : NetworkChangeNotifier(NetworkChangeCalculatorParamsLinux()),
+ notifier_thread_(new Thread()) {
+ // We create this notifier thread because the notification implementation
+ // needs a MessageLoopForIO, and there's no guarantee that
+ // MessageLoop::current() meets that criterion.
+ base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0);
+ notifier_thread_->StartWithOptions(thread_options);
+}
+
+NetworkChangeNotifierLinux::~NetworkChangeNotifierLinux() {
+ // Stopping from here allows us to sanity- check that the notifier
+ // thread shut down properly.
+ notifier_thread_->Stop();
+}
+
+// static
+NetworkChangeNotifier::NetworkChangeCalculatorParams
+NetworkChangeNotifierLinux::NetworkChangeCalculatorParamsLinux() {
+ NetworkChangeCalculatorParams params;
+ // Delay values arrived at by simple experimentation and adjusted so as to
+ // produce a single signal when switching between network connections.
+ params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(2000);
+ params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(2000);
+ params.connection_type_offline_delay_ =
+ base::TimeDelta::FromMilliseconds(1500);
+ params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500);
+ return params;
+}
+
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifierLinux::GetCurrentConnectionType() const {
+ return notifier_thread_->GetCurrentConnectionType();
+}
+
+const internal::AddressTrackerLinux*
+NetworkChangeNotifierLinux::GetAddressTrackerInternal() const {
+ return notifier_thread_->address_tracker();
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_change_notifier_linux.h b/chromium/net/base/network_change_notifier_linux.h
new file mode 100644
index 00000000000..a7080d9effb
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_linux.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE NetworkChangeNotifierLinux
+ : public NetworkChangeNotifier {
+ public:
+ static NetworkChangeNotifierLinux* Create();
+
+ private:
+ class Thread;
+
+ NetworkChangeNotifierLinux();
+ virtual ~NetworkChangeNotifierLinux();
+ static NetworkChangeCalculatorParams NetworkChangeCalculatorParamsLinux();
+
+ // NetworkChangeNotifier:
+ virtual ConnectionType GetCurrentConnectionType() const OVERRIDE;
+
+ virtual const internal::AddressTrackerLinux*
+ GetAddressTrackerInternal() const OVERRIDE;
+
+ // The thread used to listen for notifications. This relays the notification
+ // to the registered observers without posting back to the thread the object
+ // was created on.
+ // Also used for DnsConfigService which requires TYPE_IO message loop.
+ scoped_ptr<Thread> notifier_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierLinux);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_LINUX_H_
diff --git a/chromium/net/base/network_change_notifier_mac.cc b/chromium/net/base/network_change_notifier_mac.cc
new file mode 100644
index 00000000000..d75f0f8cae3
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_mac.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 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.
+
+#include "net/base/network_change_notifier_mac.h"
+
+#include <netinet/in.h>
+#include <resolv.h>
+
+#include "base/basictypes.h"
+#include "base/threading/thread.h"
+#include "net/dns/dns_config_service.h"
+
+namespace net {
+
+static bool CalculateReachability(SCNetworkConnectionFlags flags) {
+ bool reachable = flags & kSCNetworkFlagsReachable;
+ bool connection_required = flags & kSCNetworkFlagsConnectionRequired;
+ return reachable && !connection_required;
+}
+
+NetworkChangeNotifier::ConnectionType CalculateConnectionType(
+ SCNetworkConnectionFlags flags) {
+ bool reachable = CalculateReachability(flags);
+ if (reachable) {
+#if defined(OS_IOS)
+ return (flags & kSCNetworkReachabilityFlagsIsWWAN) ?
+ NetworkChangeNotifier::CONNECTION_3G :
+ NetworkChangeNotifier::CONNECTION_WIFI;
+#else
+ // TODO(droger): Get something more detailed than CONNECTION_UNKNOWN.
+ // http://crbug.com/112937
+ return NetworkChangeNotifier::CONNECTION_UNKNOWN;
+#endif // defined(OS_IOS)
+ } else {
+ return NetworkChangeNotifier::CONNECTION_NONE;
+ }
+}
+
+// Thread on which we can run DnsConfigService, which requires a TYPE_IO
+// message loop.
+class NetworkChangeNotifierMac::DnsConfigServiceThread : public base::Thread {
+ public:
+ DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
+
+ virtual ~DnsConfigServiceThread() {
+ Stop();
+ }
+
+ virtual void Init() OVERRIDE {
+ service_ = DnsConfigService::CreateSystemService();
+ service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
+ }
+
+ virtual void CleanUp() OVERRIDE {
+ service_.reset();
+ }
+
+ private:
+ scoped_ptr<DnsConfigService> service_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
+};
+
+NetworkChangeNotifierMac::NetworkChangeNotifierMac()
+ : NetworkChangeNotifier(NetworkChangeCalculatorParamsMac()),
+ connection_type_(CONNECTION_UNKNOWN),
+ connection_type_initialized_(false),
+ initial_connection_type_cv_(&connection_type_lock_),
+ forwarder_(this),
+ dns_config_service_thread_(new DnsConfigServiceThread()) {
+ // Must be initialized after the rest of this object, as it may call back into
+ // SetInitialConnectionType().
+ config_watcher_.reset(new NetworkConfigWatcherMac(&forwarder_));
+ dns_config_service_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+}
+
+NetworkChangeNotifierMac::~NetworkChangeNotifierMac() {
+ // Delete the ConfigWatcher to join the notifier thread, ensuring that
+ // StartReachabilityNotifications() has an opportunity to run to completion.
+ config_watcher_.reset();
+
+ // Now that StartReachabilityNotifications() has either run to completion or
+ // never run at all, unschedule reachability_ if it was previously scheduled.
+ if (reachability_.get() && run_loop_.get()) {
+ SCNetworkReachabilityUnscheduleFromRunLoop(reachability_.get(),
+ run_loop_.get(),
+ kCFRunLoopCommonModes);
+ }
+}
+
+// static
+NetworkChangeNotifier::NetworkChangeCalculatorParams
+NetworkChangeNotifierMac::NetworkChangeCalculatorParamsMac() {
+ NetworkChangeCalculatorParams params;
+ // Delay values arrived at by simple experimentation and adjusted so as to
+ // produce a single signal when switching between network connections.
+ params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(500);
+ params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(500);
+ params.connection_type_offline_delay_ =
+ base::TimeDelta::FromMilliseconds(1000);
+ params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500);
+ return params;
+}
+
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifierMac::GetCurrentConnectionType() const {
+ base::AutoLock lock(connection_type_lock_);
+ // Make sure the initial connection type is set before returning.
+ while (!connection_type_initialized_) {
+ initial_connection_type_cv_.Wait();
+ }
+ return connection_type_;
+}
+
+void NetworkChangeNotifierMac::Forwarder::Init() {
+ net_config_watcher_->SetInitialConnectionType();
+}
+
+void NetworkChangeNotifierMac::Forwarder::StartReachabilityNotifications() {
+ net_config_watcher_->StartReachabilityNotifications();
+}
+
+void NetworkChangeNotifierMac::Forwarder::SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) {
+ net_config_watcher_->SetDynamicStoreNotificationKeys(store);
+}
+
+void NetworkChangeNotifierMac::Forwarder::OnNetworkConfigChange(
+ CFArrayRef changed_keys) {
+ net_config_watcher_->OnNetworkConfigChange(changed_keys);
+}
+
+void NetworkChangeNotifierMac::SetInitialConnectionType() {
+ // Called on notifier thread.
+
+ // Try to reach 0.0.0.0. This is the approach taken by Firefox:
+ //
+ // http://mxr.mozilla.org/mozilla2.0/source/netwerk/system/mac/nsNetworkLinkService.mm
+ //
+ // From my (adamk) testing on Snow Leopard, 0.0.0.0
+ // seems to be reachable if any network connection is available.
+ struct sockaddr_in addr = {0};
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ reachability_.reset(SCNetworkReachabilityCreateWithAddress(
+ kCFAllocatorDefault, reinterpret_cast<struct sockaddr*>(&addr)));
+
+ SCNetworkConnectionFlags flags;
+ ConnectionType connection_type = CONNECTION_UNKNOWN;
+ if (SCNetworkReachabilityGetFlags(reachability_, &flags)) {
+ connection_type = CalculateConnectionType(flags);
+ } else {
+ LOG(ERROR) << "Could not get initial network connection type,"
+ << "assuming online.";
+ }
+ {
+ base::AutoLock lock(connection_type_lock_);
+ connection_type_ = connection_type;
+ connection_type_initialized_ = true;
+ initial_connection_type_cv_.Signal();
+ }
+}
+
+void NetworkChangeNotifierMac::StartReachabilityNotifications() {
+ // Called on notifier thread.
+ run_loop_.reset(CFRunLoopGetCurrent());
+ CFRetain(run_loop_.get());
+
+ DCHECK(reachability_);
+ SCNetworkReachabilityContext reachability_context = {
+ 0, // version
+ this, // user data
+ NULL, // retain
+ NULL, // release
+ NULL // description
+ };
+ if (!SCNetworkReachabilitySetCallback(
+ reachability_,
+ &NetworkChangeNotifierMac::ReachabilityCallback,
+ &reachability_context)) {
+ LOG(DFATAL) << "Could not set network reachability callback";
+ reachability_.reset();
+ } else if (!SCNetworkReachabilityScheduleWithRunLoop(reachability_,
+ run_loop_,
+ kCFRunLoopCommonModes)) {
+ LOG(DFATAL) << "Could not schedule network reachability on run loop";
+ reachability_.reset();
+ }
+}
+
+void NetworkChangeNotifierMac::SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) {
+#if defined(OS_IOS)
+ // SCDynamicStore API does not exist on iOS.
+ NOTREACHED();
+#else
+ base::ScopedCFTypeRef<CFMutableArrayRef> notification_keys(
+ CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
+ base::ScopedCFTypeRef<CFStringRef> key(
+ SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetInterface));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+ key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+ key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6));
+ CFArrayAppendValue(notification_keys.get(), key.get());
+
+ // Set the notification keys. This starts us receiving notifications.
+ bool ret = SCDynamicStoreSetNotificationKeys(
+ store, notification_keys.get(), NULL);
+ // TODO(willchan): Figure out a proper way to handle this rather than crash.
+ CHECK(ret);
+#endif // defined(OS_IOS)
+}
+
+void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
+#if defined(OS_IOS)
+ // SCDynamicStore API does not exist on iOS.
+ NOTREACHED();
+#else
+ DCHECK_EQ(run_loop_.get(), CFRunLoopGetCurrent());
+
+ for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) {
+ CFStringRef key = static_cast<CFStringRef>(
+ CFArrayGetValueAtIndex(changed_keys, i));
+ if (CFStringHasSuffix(key, kSCEntNetIPv4) ||
+ CFStringHasSuffix(key, kSCEntNetIPv6)) {
+ NotifyObserversOfIPAddressChange();
+ return;
+ }
+ if (CFStringHasSuffix(key, kSCEntNetInterface)) {
+ // TODO(willchan): Does not appear to be working. Look into this.
+ // Perhaps this isn't needed anyway.
+ } else {
+ NOTREACHED();
+ }
+ }
+#endif // defined(OS_IOS)
+}
+
+// static
+void NetworkChangeNotifierMac::ReachabilityCallback(
+ SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void* notifier) {
+ NetworkChangeNotifierMac* notifier_mac =
+ static_cast<NetworkChangeNotifierMac*>(notifier);
+
+ DCHECK_EQ(notifier_mac->run_loop_.get(), CFRunLoopGetCurrent());
+
+ ConnectionType new_type = CalculateConnectionType(flags);
+ ConnectionType old_type;
+ {
+ base::AutoLock lock(notifier_mac->connection_type_lock_);
+ old_type = notifier_mac->connection_type_;
+ notifier_mac->connection_type_ = new_type;
+ }
+ if (old_type != new_type)
+ NotifyObserversOfConnectionTypeChange();
+
+#if defined(OS_IOS)
+ // On iOS, the SCDynamicStore API does not exist, and we use the reachability
+ // API to detect IP address changes instead.
+ if (new_type != CONNECTION_NONE)
+ NotifyObserversOfIPAddressChange();
+#endif // defined(OS_IOS)
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_change_notifier_mac.h b/chromium/net/base/network_change_notifier_mac.h
new file mode 100644
index 00000000000..168ea1480b7
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_mac.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_MAC_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_MAC_H_
+
+#include <SystemConfiguration/SystemConfiguration.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_config_watcher_mac.h"
+
+namespace net {
+
+class NetworkChangeNotifierMac: public NetworkChangeNotifier {
+ public:
+ NetworkChangeNotifierMac();
+ virtual ~NetworkChangeNotifierMac();
+
+ // NetworkChangeNotifier implementation:
+ virtual ConnectionType GetCurrentConnectionType() const OVERRIDE;
+
+ // Forwarder just exists to keep the NetworkConfigWatcherMac API out of
+ // NetworkChangeNotifierMac's public API.
+ class Forwarder : public NetworkConfigWatcherMac::Delegate {
+ public:
+ explicit Forwarder(NetworkChangeNotifierMac* net_config_watcher)
+ : net_config_watcher_(net_config_watcher) {}
+
+ // NetworkConfigWatcherMac::Delegate implementation:
+ virtual void Init() OVERRIDE;
+ virtual void StartReachabilityNotifications() OVERRIDE;
+ virtual void SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) OVERRIDE;
+ virtual void OnNetworkConfigChange(CFArrayRef changed_keys) OVERRIDE;
+
+ private:
+ NetworkChangeNotifierMac* const net_config_watcher_;
+ DISALLOW_COPY_AND_ASSIGN(Forwarder);
+ };
+
+ private:
+ class DnsConfigServiceThread;
+
+ // Methods directly called by the NetworkConfigWatcherMac::Delegate:
+ void StartReachabilityNotifications();
+ void SetDynamicStoreNotificationKeys(SCDynamicStoreRef store);
+ void OnNetworkConfigChange(CFArrayRef changed_keys);
+
+ void SetInitialConnectionType();
+
+ static void ReachabilityCallback(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void* notifier);
+
+ static NetworkChangeCalculatorParams NetworkChangeCalculatorParamsMac();
+
+ // These must be constructed before config_watcher_ to ensure
+ // the lock is in a valid state when Forwarder::Init is called.
+ ConnectionType connection_type_;
+ bool connection_type_initialized_;
+ mutable base::Lock connection_type_lock_;
+ mutable base::ConditionVariable initial_connection_type_cv_;
+ base::ScopedCFTypeRef<SCNetworkReachabilityRef> reachability_;
+ base::ScopedCFTypeRef<CFRunLoopRef> run_loop_;
+
+ Forwarder forwarder_;
+ scoped_ptr<const NetworkConfigWatcherMac> config_watcher_;
+
+ scoped_ptr<DnsConfigServiceThread> dns_config_service_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierMac);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_MAC_H_
diff --git a/chromium/net/base/network_change_notifier_win.cc b/chromium/net/base/network_change_notifier_win.cc
new file mode 100644
index 00000000000..d53b346c440
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_win.cc
@@ -0,0 +1,310 @@
+// Copyright (c) 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.
+
+#include "net/base/network_change_notifier_win.h"
+
+#include <iphlpapi.h>
+#include <winsock2.h>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "net/base/winsock_init.h"
+#include "net/dns/dns_config_service.h"
+
+#pragma comment(lib, "iphlpapi.lib")
+
+namespace net {
+
+namespace {
+
+// Time between NotifyAddrChange retries, on failure.
+const int kWatchForAddressChangeRetryIntervalMs = 500;
+
+} // namespace
+
+// Thread on which we can run DnsConfigService, which requires AssertIOAllowed
+// to open registry keys and to handle FilePathWatcher updates.
+class NetworkChangeNotifierWin::DnsConfigServiceThread : public base::Thread {
+ public:
+ DnsConfigServiceThread() : base::Thread("DnsConfigService") {}
+
+ virtual ~DnsConfigServiceThread() {
+ Stop();
+ }
+
+ virtual void Init() OVERRIDE {
+ service_ = DnsConfigService::CreateSystemService();
+ service_->WatchConfig(base::Bind(&NetworkChangeNotifier::SetDnsConfig));
+ }
+
+ virtual void CleanUp() OVERRIDE {
+ service_.reset();
+ }
+
+ private:
+ scoped_ptr<DnsConfigService> service_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceThread);
+};
+
+NetworkChangeNotifierWin::NetworkChangeNotifierWin()
+ : NetworkChangeNotifier(NetworkChangeCalculatorParamsWin()),
+ is_watching_(false),
+ sequential_failures_(0),
+ weak_factory_(this),
+ dns_config_service_thread_(new DnsConfigServiceThread()),
+ last_announced_offline_(IsOffline()) {
+ memset(&addr_overlapped_, 0, sizeof addr_overlapped_);
+ addr_overlapped_.hEvent = WSACreateEvent();
+ dns_config_service_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+}
+
+NetworkChangeNotifierWin::~NetworkChangeNotifierWin() {
+ if (is_watching_) {
+ CancelIPChangeNotify(&addr_overlapped_);
+ addr_watcher_.StopWatching();
+ }
+ WSACloseEvent(addr_overlapped_.hEvent);
+}
+
+// static
+NetworkChangeNotifier::NetworkChangeCalculatorParams
+NetworkChangeNotifierWin::NetworkChangeCalculatorParamsWin() {
+ NetworkChangeCalculatorParams params;
+ // Delay values arrived at by simple experimentation and adjusted so as to
+ // produce a single signal when switching between network connections.
+ params.ip_address_offline_delay_ = base::TimeDelta::FromMilliseconds(1500);
+ params.ip_address_online_delay_ = base::TimeDelta::FromMilliseconds(1500);
+ params.connection_type_offline_delay_ =
+ base::TimeDelta::FromMilliseconds(1500);
+ params.connection_type_online_delay_ = base::TimeDelta::FromMilliseconds(500);
+ return params;
+}
+
+// This implementation does not return the actual connection type but merely
+// determines if the user is "online" (in which case it returns
+// CONNECTION_UNKNOWN) or "offline" (and then it returns CONNECTION_NONE).
+// This is challenging since the only thing we can test with certainty is
+// whether a *particular* host is reachable.
+//
+// While we can't conclusively determine when a user is "online", we can at
+// least reliably recognize some of the situtations when they are clearly
+// "offline". For example, if the user's laptop is not plugged into an ethernet
+// network and is not connected to any wireless networks, it must be offline.
+//
+// There are a number of different ways to implement this on Windows, each with
+// their pros and cons. Here is a comparison of various techniques considered:
+//
+// (1) Use InternetGetConnectedState (wininet.dll). This function is really easy
+// to use (literally a one-liner), and runs quickly. The drawback is it adds a
+// dependency on the wininet DLL.
+//
+// (2) Enumerate all of the network interfaces using GetAdaptersAddresses
+// (iphlpapi.dll), and assume we are "online" if there is at least one interface
+// that is connected, and that interface is not a loopback or tunnel.
+//
+// Safari on Windows has a fairly simple implementation that does this:
+// http://trac.webkit.org/browser/trunk/WebCore/platform/network/win/NetworkStateNotifierWin.cpp.
+//
+// Mozilla similarly uses this approach:
+// http://mxr.mozilla.org/mozilla1.9.2/source/netwerk/system/win32/nsNotifyAddrListener.cpp
+//
+// The biggest drawback to this approach is it is quite complicated.
+// WebKit's implementation for example doesn't seem to test for ICS gateways
+// (internet connection sharing), whereas Mozilla's implementation has extra
+// code to guess that.
+//
+// (3) The method used in this file comes from google talk, and is similar to
+// method (2). The main difference is it enumerates the winsock namespace
+// providers rather than the actual adapters.
+//
+// I ran some benchmarks comparing the performance of each on my Windows 7
+// workstation. Here is what I found:
+// * Approach (1) was pretty much zero-cost after the initial call.
+// * Approach (2) took an average of 3.25 milliseconds to enumerate the
+// adapters.
+// * Approach (3) took an average of 0.8 ms to enumerate the providers.
+//
+// In terms of correctness, all three approaches were comparable for the simple
+// experiments I ran... However none of them correctly returned "offline" when
+// executing 'ipconfig /release'.
+//
+NetworkChangeNotifier::ConnectionType
+NetworkChangeNotifierWin::GetCurrentConnectionType() const {
+
+ // TODO(eroman): We could cache this value, and only re-calculate it on
+ // network changes. For now we recompute it each time asked,
+ // since it is relatively fast (sub 1ms) and not called often.
+
+ EnsureWinsockInit();
+
+ // The following code was adapted from:
+ // http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/net/notifier/base/win/async_network_alive_win32.cc?view=markup&pathrev=47343
+ // The main difference is we only call WSALookupServiceNext once, whereas
+ // the earlier code would traverse the entire list and pass LUP_FLUSHPREVIOUS
+ // to skip past the large results.
+
+ HANDLE ws_handle;
+ WSAQUERYSET query_set = {0};
+ query_set.dwSize = sizeof(WSAQUERYSET);
+ query_set.dwNameSpace = NS_NLA;
+ // Initiate a client query to iterate through the
+ // currently connected networks.
+ if (0 != WSALookupServiceBegin(&query_set, LUP_RETURN_ALL,
+ &ws_handle)) {
+ LOG(ERROR) << "WSALookupServiceBegin failed with: " << WSAGetLastError();
+ return NetworkChangeNotifier::CONNECTION_UNKNOWN;
+ }
+
+ bool found_connection = false;
+
+ // Retrieve the first available network. In this function, we only
+ // need to know whether or not there is network connection.
+ // Allocate 256 bytes for name, it should be enough for most cases.
+ // If the name is longer, it is OK as we will check the code returned and
+ // set correct network status.
+ char result_buffer[sizeof(WSAQUERYSET) + 256] = {0};
+ DWORD length = sizeof(result_buffer);
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0])->dwSize =
+ sizeof(WSAQUERYSET);
+ int result = WSALookupServiceNext(
+ ws_handle,
+ LUP_RETURN_NAME,
+ &length,
+ reinterpret_cast<WSAQUERYSET*>(&result_buffer[0]));
+
+ if (result == 0) {
+ // Found a connection!
+ found_connection = true;
+ } else {
+ DCHECK_EQ(SOCKET_ERROR, result);
+ result = WSAGetLastError();
+
+ // Error code WSAEFAULT means there is a network connection but the
+ // result_buffer size is too small to contain the results. The
+ // variable "length" returned from WSALookupServiceNext is the minimum
+ // number of bytes required. We do not need to retrieve detail info,
+ // it is enough knowing there was a connection.
+ if (result == WSAEFAULT) {
+ found_connection = true;
+ } else if (result == WSA_E_NO_MORE || result == WSAENOMORE) {
+ // There was nothing to iterate over!
+ } else {
+ LOG(WARNING) << "WSALookupServiceNext() failed with:" << result;
+ }
+ }
+
+ result = WSALookupServiceEnd(ws_handle);
+ LOG_IF(ERROR, result != 0)
+ << "WSALookupServiceEnd() failed with: " << result;
+
+ // TODO(droger): Return something more detailed than CONNECTION_UNKNOWN.
+ return found_connection ? NetworkChangeNotifier::CONNECTION_UNKNOWN :
+ NetworkChangeNotifier::CONNECTION_NONE;
+}
+
+void NetworkChangeNotifierWin::OnObjectSignaled(HANDLE object) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(is_watching_);
+ is_watching_ = false;
+
+ // Start watching for the next address change.
+ WatchForAddressChange();
+
+ NotifyObservers();
+}
+
+void NetworkChangeNotifierWin::NotifyObservers() {
+ DCHECK(CalledOnValidThread());
+ NotifyObserversOfIPAddressChange();
+
+ // Calling GetConnectionType() at this very moment is likely to give
+ // the wrong result, so we delay that until a little bit later.
+ //
+ // The one second delay chosen here was determined experimentally
+ // by adamk on Windows 7.
+ // If after one second we determine we are still offline, we will
+ // delay again.
+ offline_polls_ = 0;
+ timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this,
+ &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
+}
+
+void NetworkChangeNotifierWin::WatchForAddressChange() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_watching_);
+
+ // NotifyAddrChange occasionally fails with ERROR_OPEN_FAILED for unknown
+ // reasons. More rarely, it's also been observed failing with
+ // ERROR_NO_SYSTEM_RESOURCES. When either of these happens, we retry later.
+ if (!WatchForAddressChangeInternal()) {
+ ++sequential_failures_;
+
+ // TODO(mmenke): If the UMA histograms indicate that this fixes
+ // http://crbug.com/69198, remove this histogram and consider reducing the
+ // retry interval.
+ if (sequential_failures_ == 2000) {
+ UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
+ sequential_failures_);
+ }
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NetworkChangeNotifierWin::WatchForAddressChange,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(
+ kWatchForAddressChangeRetryIntervalMs));
+ return;
+ }
+
+ // Treat the transition from NotifyAddrChange failing to succeeding as a
+ // network change event, since network changes were not being observed in
+ // that interval.
+ if (sequential_failures_ > 0)
+ NotifyObservers();
+
+ if (sequential_failures_ < 2000) {
+ UMA_HISTOGRAM_COUNTS_10000("Net.NotifyAddrChangeFailures",
+ sequential_failures_);
+ }
+
+ is_watching_ = true;
+ sequential_failures_ = 0;
+}
+
+bool NetworkChangeNotifierWin::WatchForAddressChangeInternal() {
+ DCHECK(CalledOnValidThread());
+ HANDLE handle = NULL;
+ DWORD ret = NotifyAddrChange(&handle, &addr_overlapped_);
+ if (ret != ERROR_IO_PENDING)
+ return false;
+
+ addr_watcher_.StartWatching(addr_overlapped_.hEvent, this);
+ return true;
+}
+
+void NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange() {
+ bool current_offline = IsOffline();
+ offline_polls_++;
+ // If we continue to appear offline, delay sending out the notification in
+ // case we appear to go online within 20 seconds. UMA histogram data shows
+ // we may not detect the transition to online state after 1 second but within
+ // 20 seconds we generally do.
+ if (last_announced_offline_ && current_offline && offline_polls_ <= 20) {
+ timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(1), this,
+ &NetworkChangeNotifierWin::NotifyParentOfConnectionTypeChange);
+ return;
+ }
+ if (last_announced_offline_)
+ UMA_HISTOGRAM_CUSTOM_COUNTS("NCN.OfflinePolls", offline_polls_, 1, 50, 50);
+ last_announced_offline_ = current_offline;
+
+ NotifyObserversOfConnectionTypeChange();
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_change_notifier_win.h b/chromium/net/base/network_change_notifier_win.h
new file mode 100644
index 00000000000..47a97dfac9c
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_win.h
@@ -0,0 +1,109 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_CHANGE_NOTIFIER_WIN_H_
+#define NET_BASE_NETWORK_CHANGE_NOTIFIER_WIN_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/timer/timer.h"
+#include "base/win/object_watcher.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+
+namespace net {
+
+// NetworkChangeNotifierWin inherits from NonThreadSafe, as all its internal
+// notification code must be called on the thread it is created and destroyed
+// on. All the NetworkChangeNotifier methods it implements are threadsafe.
+class NET_EXPORT_PRIVATE NetworkChangeNotifierWin
+ : public NetworkChangeNotifier,
+ public base::win::ObjectWatcher::Delegate,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ NetworkChangeNotifierWin();
+
+ // Begins listening for a single subsequent address change. If it fails to
+ // start watching, it retries on a timer. Must be called only once, on the
+ // thread |this| was created on. This cannot be called in the constructor, as
+ // WatchForAddressChangeInternal is mocked out in unit tests.
+ // TODO(mmenke): Consider making this function a part of the
+ // NetworkChangeNotifier interface, so other subclasses can be
+ // unit tested in similar fashion, as needed.
+ void WatchForAddressChange();
+
+ protected:
+ virtual ~NetworkChangeNotifierWin();
+
+ // For unit tests only.
+ bool is_watching() { return is_watching_; }
+ void set_is_watching(bool is_watching) { is_watching_ = is_watching; }
+ int sequential_failures() { return sequential_failures_; }
+
+ private:
+ class DnsConfigServiceThread;
+ friend class NetworkChangeNotifierWinTest;
+
+ // NetworkChangeNotifier methods:
+ virtual ConnectionType GetCurrentConnectionType() const OVERRIDE;
+
+ // ObjectWatcher::Delegate methods:
+ // Must only be called on the thread |this| was created on.
+ virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
+
+ // Notifies IP address change observers of a change immediately, and notifies
+ // network state change observers on a delay. Must only be called on the
+ // thread |this| was created on.
+ void NotifyObservers();
+
+ // Forwards connection type notifications to parent class.
+ void NotifyParentOfConnectionTypeChange();
+
+ // Tries to start listening for a single subsequent address change. Returns
+ // false on failure. The caller is responsible for updating |is_watching_|.
+ // Virtual for unit tests. Must only be called on the thread |this| was
+ // created on.
+ virtual bool WatchForAddressChangeInternal();
+
+ static NetworkChangeCalculatorParams NetworkChangeCalculatorParamsWin();
+
+ // All member variables may only be accessed on the thread |this| was created
+ // on.
+
+ // False when not currently watching for network change events. This only
+ // happens on initialization and when WatchForAddressChangeInternal fails and
+ // there is a pending task to try again. Needed for safe cleanup.
+ bool is_watching_;
+
+ base::win::ObjectWatcher addr_watcher_;
+ OVERLAPPED addr_overlapped_;
+
+ base::OneShotTimer<NetworkChangeNotifierWin> timer_;
+
+ // Number of times WatchForAddressChange has failed in a row.
+ int sequential_failures_;
+
+ // Used for calling WatchForAddressChange again on failure.
+ base::WeakPtrFactory<NetworkChangeNotifierWin> weak_factory_;
+
+ // Thread on which we can run DnsConfigService.
+ scoped_ptr<DnsConfigServiceThread> dns_config_service_thread_;
+
+ // Result of IsOffline() when NotifyObserversOfConnectionTypeChange()
+ // was last called.
+ bool last_announced_offline_;
+ // Number of times polled to check if still offline.
+ int offline_polls_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierWin);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CHANGE_NOTIFIER_WIN_H_
diff --git a/chromium/net/base/network_change_notifier_win_unittest.cc b/chromium/net/base/network_change_notifier_win_unittest.cc
new file mode 100644
index 00000000000..96dcd13e587
--- /dev/null
+++ b/chromium/net/base/network_change_notifier_win_unittest.cc
@@ -0,0 +1,260 @@
+// Copyright (c) 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.
+
+#include "base/message_loop/message_loop.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_change_notifier_factory.h"
+#include "net/base/network_change_notifier_win.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AtLeast;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+namespace net {
+
+namespace {
+
+// Subclass of NetworkChangeNotifierWin that overrides functions so that no
+// Windows API networking functions are ever called.
+class TestNetworkChangeNotifierWin : public NetworkChangeNotifierWin {
+ public:
+ TestNetworkChangeNotifierWin() {}
+
+ virtual ~TestNetworkChangeNotifierWin() {
+ // This is needed so we don't try to stop watching for IP address changes,
+ // as we never actually started.
+ set_is_watching(false);
+ }
+
+ // From NetworkChangeNotifier.
+ virtual NetworkChangeNotifier::ConnectionType
+ GetCurrentConnectionType() const OVERRIDE {
+ return NetworkChangeNotifier::CONNECTION_UNKNOWN;
+ }
+
+ // From NetworkChangeNotifierWin.
+ MOCK_METHOD0(WatchForAddressChangeInternal, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestNetworkChangeNotifierWin);
+};
+
+class TestIPAddressObserver
+ : public net::NetworkChangeNotifier::IPAddressObserver {
+ public:
+ TestIPAddressObserver() {
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ }
+
+ ~TestIPAddressObserver() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ }
+
+ MOCK_METHOD0(OnIPAddressChanged, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestIPAddressObserver);
+};
+
+bool ExitMessageLoopAndReturnFalse() {
+ base::MessageLoop::current()->Quit();
+ return false;
+}
+
+} // namespace
+
+class NetworkChangeNotifierWinTest : public testing::Test {
+ public:
+ // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal
+ // success. Expects that |network_change_notifier_| has just been created, so
+ // it's not watching anything yet, and there have been no previous
+ // WatchForAddressChangeInternal failures.
+ void StartWatchingAndSucceed() {
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ .Times(1)
+ .WillOnce(Return(true));
+
+ network_change_notifier_.WatchForAddressChange();
+
+ EXPECT_TRUE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ // If a task to notify observers of the IP address change event was
+ // incorrectly posted, make sure it gets run to trigger a failure.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Calls WatchForAddressChange, and simulates a WatchForAddressChangeInternal
+ // failure.
+ void StartWatchingAndFail() {
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ // Due to an expected race, it's theoretically possible for more than
+ // one call to occur, though unlikely.
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(false));
+
+ network_change_notifier_.WatchForAddressChange();
+
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_LT(0, network_change_notifier_.sequential_failures());
+
+ // If a task to notify observers of the IP address change event was
+ // incorrectly posted, make sure it gets run.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Simulates a network change event, resulting in a call to OnObjectSignaled.
+ // The resulting call to WatchForAddressChangeInternal then succeeds.
+ void SignalAndSucceed() {
+ EXPECT_TRUE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1);
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ .Times(1)
+ .WillOnce(Return(true));
+
+ network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE);
+
+ EXPECT_TRUE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ // Run the task to notify observers of the IP address change event.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Simulates a network change event, resulting in a call to OnObjectSignaled.
+ // The resulting call to WatchForAddressChangeInternal then fails.
+ void SignalAndFail() {
+ EXPECT_TRUE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1);
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ // Due to an expected race, it's theoretically possible for more than
+ // one call to occur, though unlikely.
+ .Times(AtLeast(1))
+ .WillRepeatedly(Return(false));
+
+ network_change_notifier_.OnObjectSignaled(INVALID_HANDLE_VALUE);
+
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_LT(0, network_change_notifier_.sequential_failures());
+
+ // Run the task to notify observers of the IP address change event.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Runs the message loop until WatchForAddressChange is called again, as a
+ // result of the already posted task after a WatchForAddressChangeInternal
+ // failure. Simulates a success on the resulting call to
+ // WatchForAddressChangeInternal.
+ void RetryAndSucceed() {
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_LT(0, network_change_notifier_.sequential_failures());
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(1)
+ .WillOnce(
+ Invoke(base::MessageLoop::current(), &base::MessageLoop::Quit));
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ .Times(1).WillOnce(Return(true));
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(network_change_notifier_.is_watching());
+ EXPECT_EQ(0, network_change_notifier_.sequential_failures());
+ }
+
+ // Runs the message loop until WatchForAddressChange is called again, as a
+ // result of the already posted task after a WatchForAddressChangeInternal
+ // failure. Simulates a failure on the resulting call to
+ // WatchForAddressChangeInternal.
+ void RetryAndFail() {
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_LT(0, network_change_notifier_.sequential_failures());
+
+ int initial_sequential_failures =
+ network_change_notifier_.sequential_failures();
+
+ EXPECT_CALL(test_ip_address_observer_, OnIPAddressChanged()).Times(0);
+ EXPECT_CALL(network_change_notifier_, WatchForAddressChangeInternal())
+ // Due to an expected race, it's theoretically possible for more than
+ // one call to occur, though unlikely.
+ .Times(AtLeast(1))
+ .WillRepeatedly(Invoke(ExitMessageLoopAndReturnFalse));
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_FALSE(network_change_notifier_.is_watching());
+ EXPECT_LT(initial_sequential_failures,
+ network_change_notifier_.sequential_failures());
+
+ // If a task to notify observers of the IP address change event was
+ // incorrectly posted, make sure it gets run.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ private:
+ // Note that the order of declaration here is important.
+
+ // Allows creating a new NetworkChangeNotifier. Must be created before
+ // |network_change_notifier_| and destroyed after it to avoid DCHECK failures.
+ NetworkChangeNotifier::DisableForTest disable_for_test_;
+
+ StrictMock<TestNetworkChangeNotifierWin> network_change_notifier_;
+
+ // Must be created after |network_change_notifier_|, so it can add itself as
+ // an IPAddressObserver.
+ StrictMock<TestIPAddressObserver> test_ip_address_observer_;
+};
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinBasic) {
+ StartWatchingAndSucceed();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStart) {
+ StartWatchingAndFail();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartOnce) {
+ StartWatchingAndFail();
+ RetryAndSucceed();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailStartTwice) {
+ StartWatchingAndFail();
+ RetryAndFail();
+ RetryAndSucceed();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinSignal) {
+ StartWatchingAndSucceed();
+ SignalAndSucceed();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalOnce) {
+ StartWatchingAndSucceed();
+ SignalAndFail();
+ RetryAndSucceed();
+}
+
+TEST_F(NetworkChangeNotifierWinTest, NetChangeWinFailSignalTwice) {
+ StartWatchingAndSucceed();
+ SignalAndFail();
+ RetryAndFail();
+ RetryAndSucceed();
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_config_watcher_mac.cc b/chromium/net/base/network_config_watcher_mac.cc
new file mode 100644
index 00000000000..8579a358391
--- /dev/null
+++ b/chromium/net/base/network_config_watcher_mac.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 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.
+
+#include "net/base/network_config_watcher_mac.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace net {
+
+namespace {
+
+#if !defined(OS_IOS)
+// Called back by OS. Calls OnNetworkConfigChange().
+void DynamicStoreCallback(SCDynamicStoreRef /* store */,
+ CFArrayRef changed_keys,
+ void* config_delegate) {
+ NetworkConfigWatcherMac::Delegate* net_config_delegate =
+ static_cast<NetworkConfigWatcherMac::Delegate*>(config_delegate);
+ net_config_delegate->OnNetworkConfigChange(changed_keys);
+}
+#endif // !defined(OS_IOS)
+
+class NetworkConfigWatcherMacThread : public base::Thread {
+ public:
+ NetworkConfigWatcherMacThread(NetworkConfigWatcherMac::Delegate* delegate);
+ virtual ~NetworkConfigWatcherMacThread();
+
+ protected:
+ // base::Thread
+ virtual void Init() OVERRIDE;
+ virtual void CleanUp() OVERRIDE;
+
+ private:
+ // The SystemConfiguration calls in this function can lead to contention early
+ // on, so we invoke this function later on in startup to keep it fast.
+ void InitNotifications();
+
+ base::ScopedCFTypeRef<CFRunLoopSourceRef> run_loop_source_;
+ NetworkConfigWatcherMac::Delegate* const delegate_;
+ base::WeakPtrFactory<NetworkConfigWatcherMacThread> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkConfigWatcherMacThread);
+};
+
+NetworkConfigWatcherMacThread::NetworkConfigWatcherMacThread(
+ NetworkConfigWatcherMac::Delegate* delegate)
+ : base::Thread("NetworkConfigWatcher"),
+ delegate_(delegate),
+ weak_factory_(this) {}
+
+NetworkConfigWatcherMacThread::~NetworkConfigWatcherMacThread() {
+ // Allow IO because Stop() calls PlatformThread::Join(), which is a blocking
+ // operation. This is expected during shutdown.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ Stop();
+}
+
+void NetworkConfigWatcherMacThread::Init() {
+ // Disallow IO to make sure NetworkConfigWatcherMacThread's helper thread does
+ // not perform blocking operations.
+ base::ThreadRestrictions::SetIOAllowed(false);
+
+ delegate_->Init();
+
+ // TODO(willchan): Look to see if there's a better signal for when it's ok to
+ // initialize this, rather than just delaying it by a fixed time.
+ const base::TimeDelta kInitializationDelay = base::TimeDelta::FromSeconds(1);
+ message_loop()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&NetworkConfigWatcherMacThread::InitNotifications,
+ weak_factory_.GetWeakPtr()),
+ kInitializationDelay);
+}
+
+void NetworkConfigWatcherMacThread::CleanUp() {
+ if (!run_loop_source_.get())
+ return;
+
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
+ kCFRunLoopCommonModes);
+ run_loop_source_.reset();
+}
+
+void NetworkConfigWatcherMacThread::InitNotifications() {
+#if !defined(OS_IOS)
+ // SCDynamicStore API does not exist on iOS.
+ // Add a run loop source for a dynamic store to the current run loop.
+ SCDynamicStoreContext context = {
+ 0, // Version 0.
+ delegate_, // User data.
+ NULL, // This is not reference counted. No retain function.
+ NULL, // This is not reference counted. No release function.
+ NULL, // No description for this.
+ };
+ base::ScopedCFTypeRef<SCDynamicStoreRef> store(SCDynamicStoreCreate(
+ NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context));
+ run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource(
+ NULL, store.get(), 0));
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(),
+ kCFRunLoopCommonModes);
+#endif // !defined(OS_IOS)
+
+ // Set up notifications for interface and IP address changes.
+ delegate_->StartReachabilityNotifications();
+#if !defined(OS_IOS)
+ delegate_->SetDynamicStoreNotificationKeys(store.get());
+#endif // !defined(OS_IOS)
+}
+
+} // namespace
+
+NetworkConfigWatcherMac::NetworkConfigWatcherMac(Delegate* delegate)
+ : notifier_thread_(new NetworkConfigWatcherMacThread(delegate)) {
+ // We create this notifier thread because the notification implementation
+ // needs a thread with a CFRunLoop, and there's no guarantee that
+ // MessageLoop::current() meets that criterion.
+ base::Thread::Options thread_options(base::MessageLoop::TYPE_UI, 0);
+ notifier_thread_->StartWithOptions(thread_options);
+}
+
+NetworkConfigWatcherMac::~NetworkConfigWatcherMac() {}
+
+} // namespace net
diff --git a/chromium/net/base/network_config_watcher_mac.h b/chromium/net/base/network_config_watcher_mac.h
new file mode 100644
index 00000000000..850ad8a1c10
--- /dev/null
+++ b/chromium/net/base/network_config_watcher_mac.h
@@ -0,0 +1,62 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_CONFIG_WATCHER_MAC_H_
+#define NET_BASE_NETWORK_CONFIG_WATCHER_MAC_H_
+
+#include <SystemConfiguration/SCDynamicStore.h>
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace base {
+class Thread;
+}
+
+namespace net {
+
+// Helper class for watching the Mac OS system network settings.
+class NetworkConfigWatcherMac {
+ public:
+ // NOTE: The lifetime of Delegate is expected to exceed the lifetime of
+ // NetworkConfigWatcherMac.
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called to let the delegate do any setup work the must be run on the
+ // notifier thread immediately after it starts.
+ virtual void Init() {}
+
+ // Called to start receiving notifications from the SCNetworkReachability
+ // API.
+ // Will be called on the notifier thread.
+ virtual void StartReachabilityNotifications() = 0;
+
+ // Called to register the notification keys on |store|.
+ // Implementors are expected to call SCDynamicStoreSetNotificationKeys().
+ // Will be called on the notifier thread.
+ virtual void SetDynamicStoreNotificationKeys(SCDynamicStoreRef store) = 0;
+
+ // Called when one of the notification keys has changed.
+ // Will be called on the notifier thread.
+ virtual void OnNetworkConfigChange(CFArrayRef changed_keys) = 0;
+ };
+
+ explicit NetworkConfigWatcherMac(Delegate* delegate);
+ ~NetworkConfigWatcherMac();
+
+ private:
+ // The thread used to listen for notifications. This relays the notification
+ // to the registered observers without posting back to the thread the object
+ // was created on.
+ scoped_ptr<base::Thread> notifier_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkConfigWatcherMac);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_CONFIG_WATCHER_MAC_H_
diff --git a/chromium/net/base/network_delegate.cc b/chromium/net/base/network_delegate.cc
new file mode 100644
index 00000000000..5a69b863e62
--- /dev/null
+++ b/chromium/net/base/network_delegate.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 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.
+
+#include "net/base/network_delegate.h"
+
+#include "base/logging.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+int NetworkDelegate::NotifyBeforeURLRequest(
+ URLRequest* request, const CompletionCallback& callback,
+ GURL* new_url) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(request);
+ DCHECK(!callback.is_null());
+ return OnBeforeURLRequest(request, callback, new_url);
+}
+
+int NetworkDelegate::NotifyBeforeSendHeaders(
+ URLRequest* request, const CompletionCallback& callback,
+ HttpRequestHeaders* headers) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(headers);
+ DCHECK(!callback.is_null());
+ return OnBeforeSendHeaders(request, callback, headers);
+}
+
+void NetworkDelegate::NotifySendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) {
+ DCHECK(CalledOnValidThread());
+ OnSendHeaders(request, headers);
+}
+
+int NetworkDelegate::NotifyHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(original_response_headers);
+ DCHECK(!callback.is_null());
+ return OnHeadersReceived(request, callback, original_response_headers,
+ override_response_headers);
+}
+
+void NetworkDelegate::NotifyResponseStarted(URLRequest* request) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(request);
+ OnResponseStarted(request);
+}
+
+void NetworkDelegate::NotifyRawBytesRead(const URLRequest& request,
+ int bytes_read) {
+ DCHECK(CalledOnValidThread());
+ OnRawBytesRead(request, bytes_read);
+}
+
+void NetworkDelegate::NotifyBeforeRedirect(URLRequest* request,
+ const GURL& new_location) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(request);
+ OnBeforeRedirect(request, new_location);
+}
+
+void NetworkDelegate::NotifyCompleted(URLRequest* request, bool started) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(request);
+ OnCompleted(request, started);
+}
+
+void NetworkDelegate::NotifyURLRequestDestroyed(URLRequest* request) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(request);
+ OnURLRequestDestroyed(request);
+}
+
+void NetworkDelegate::NotifyPACScriptError(int line_number,
+ const base::string16& error) {
+ DCHECK(CalledOnValidThread());
+ OnPACScriptError(line_number, error);
+}
+
+NetworkDelegate::AuthRequiredResponse NetworkDelegate::NotifyAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) {
+ DCHECK(CalledOnValidThread());
+ return OnAuthRequired(request, auth_info, callback, credentials);
+}
+
+bool NetworkDelegate::CanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!(request.load_flags() & net::LOAD_DO_NOT_SEND_COOKIES));
+ return OnCanGetCookies(request, cookie_list);
+}
+
+bool NetworkDelegate::CanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!(request.load_flags() & net::LOAD_DO_NOT_SAVE_COOKIES));
+ return OnCanSetCookie(request, cookie_line, options);
+}
+
+bool NetworkDelegate::CanAccessFile(const URLRequest& request,
+ const base::FilePath& path) const {
+ DCHECK(CalledOnValidThread());
+ return OnCanAccessFile(request, path);
+}
+
+bool NetworkDelegate::CanThrottleRequest(const URLRequest& request) const {
+ DCHECK(CalledOnValidThread());
+ return OnCanThrottleRequest(request);
+}
+
+bool NetworkDelegate::CanEnablePrivacyMode(
+ const GURL& url,
+ const GURL& first_party_for_cookies) const {
+ DCHECK(CalledOnValidThread());
+ return OnCanEnablePrivacyMode(url, first_party_for_cookies);
+}
+
+bool NetworkDelegate::OnCanEnablePrivacyMode(
+ const GURL& url,
+ const GURL& first_party_for_cookies) const {
+ // Default implementation disables privacy mode.
+ return false;
+}
+
+int NetworkDelegate::NotifyBeforeSocketStreamConnect(
+ SocketStream* socket,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(socket);
+ DCHECK(!callback.is_null());
+ return OnBeforeSocketStreamConnect(socket, callback);
+}
+
+void NetworkDelegate::NotifyRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) {
+ DCHECK(CalledOnValidThread());
+ OnRequestWaitStateChange(request, state);
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_delegate.h b/chromium/net/base/network_delegate.h
new file mode 100644
index 00000000000..4b649642dd1
--- /dev/null
+++ b/chromium/net/base/network_delegate.h
@@ -0,0 +1,251 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_NETWORK_DELEGATE_H_
+#define NET_BASE_NETWORK_DELEGATE_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/cookies/canonical_cookie.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+
+// NOTE: Layering violations!
+// We decided to accept these violations (depending
+// on other net/ submodules from net/base/), because otherwise NetworkDelegate
+// would have to be broken up into too many smaller interfaces targeted to each
+// submodule. Also, since the lower levels in net/ may callback into higher
+// levels, we may encounter dangerous casting issues.
+//
+// NOTE: It is not okay to add any compile-time dependencies on symbols outside
+// of net/base here, because we have a net_base library. Forward declarations
+// are ok.
+class CookieOptions;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+class SocketStream;
+class URLRequest;
+
+class NET_EXPORT NetworkDelegate : public base::NonThreadSafe {
+ public:
+ // AuthRequiredResponse indicates how a NetworkDelegate handles an
+ // OnAuthRequired call. It's placed in this file to prevent url_request.h
+ // from having to include network_delegate.h.
+ enum AuthRequiredResponse {
+ AUTH_REQUIRED_RESPONSE_NO_ACTION,
+ AUTH_REQUIRED_RESPONSE_SET_AUTH,
+ AUTH_REQUIRED_RESPONSE_CANCEL_AUTH,
+ AUTH_REQUIRED_RESPONSE_IO_PENDING,
+ };
+ typedef base::Callback<void(AuthRequiredResponse)> AuthCallback;
+
+ enum RequestWaitState {
+ REQUEST_WAIT_STATE_CACHE_START,
+ REQUEST_WAIT_STATE_CACHE_FINISH,
+ REQUEST_WAIT_STATE_NETWORK_START,
+ REQUEST_WAIT_STATE_NETWORK_FINISH,
+ REQUEST_WAIT_STATE_RESET
+ };
+
+ virtual ~NetworkDelegate() {}
+
+ // Notification interface called by the network stack. Note that these
+ // functions mostly forward to the private virtuals. They also add some sanity
+ // checking on parameters. See the corresponding virtuals for explanations of
+ // the methods and their arguments.
+ int NotifyBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url);
+ int NotifyBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers);
+ void NotifySendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers);
+ int NotifyHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers);
+ void NotifyBeforeRedirect(URLRequest* request,
+ const GURL& new_location);
+ void NotifyResponseStarted(URLRequest* request);
+ void NotifyRawBytesRead(const URLRequest& request, int bytes_read);
+ void NotifyCompleted(URLRequest* request, bool started);
+ void NotifyURLRequestDestroyed(URLRequest* request);
+ void NotifyPACScriptError(int line_number, const base::string16& error);
+ AuthRequiredResponse NotifyAuthRequired(URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials);
+ bool CanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list);
+ bool CanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options);
+ bool CanAccessFile(const URLRequest& request,
+ const base::FilePath& path) const;
+ bool CanThrottleRequest(const URLRequest& request) const;
+ bool CanEnablePrivacyMode(const GURL& url,
+ const GURL& first_party_for_cookies) const;
+
+ int NotifyBeforeSocketStreamConnect(SocketStream* socket,
+ const CompletionCallback& callback);
+
+ void NotifyRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state);
+
+ private:
+ // This is the interface for subclasses of NetworkDelegate to implement. These
+ // member functions will be called by the respective public notification
+ // member function, which will perform basic sanity checking.
+
+ // Called before a request is sent. Allows the delegate to rewrite the URL
+ // being fetched by modifying |new_url|. |callback| and |new_url| are valid
+ // only until OnURLRequestDestroyed is called for this request. Returns a net
+ // status code, generally either OK to continue with the request or
+ // ERR_IO_PENDING if the result is not ready yet. A status code other than OK
+ // and ERR_IO_PENDING will cancel the request and report the status code as
+ // the reason.
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) = 0;
+
+ // Called right before the HTTP headers are sent. Allows the delegate to
+ // read/write |headers| before they get sent out. |callback| and |headers| are
+ // valid only until OnCompleted or OnURLRequestDestroyed is called for this
+ // request.
+ // Returns a net status code.
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) = 0;
+
+ // Called right before the HTTP request(s) are being sent to the network.
+ // |headers| is only valid until OnCompleted or OnURLRequestDestroyed is
+ // called for this request.
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) = 0;
+
+ // Called for HTTP requests when the headers have been received. Returns a net
+ // status code, generally either OK to continue with the request or
+ // ERR_IO_PENDING if the result is not ready yet. A status code other than OK
+ // and ERR_IO_PENDING will cancel the request and report the status code as
+ // the reason.
+ // |original_response_headers| contains the headers as received over the
+ // network, these must not be modified. |override_response_headers| can be set
+ // to new values, that should be considered as overriding
+ // |original_response_headers|.
+ // |callback|, |original_response_headers|, and |override_response_headers|
+ // are only valid until OnURLRequestDestroyed is called for this request.
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) = 0;
+
+ // Called right after a redirect response code was received.
+ // |new_location| is only valid until OnURLRequestDestroyed is called for this
+ // request.
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) = 0;
+
+ // This corresponds to URLRequestDelegate::OnResponseStarted.
+ virtual void OnResponseStarted(URLRequest* request) = 0;
+
+ // Called every time we read raw bytes.
+ virtual void OnRawBytesRead(const URLRequest& request, int bytes_read) = 0;
+
+ // Indicates that the URL request has been completed or failed.
+ // |started| indicates whether the request has been started. If false,
+ // some information like the socket address is not available.
+ virtual void OnCompleted(URLRequest* request, bool started) = 0;
+
+ // Called when an URLRequest is being destroyed. Note that the request is
+ // being deleted, so it's not safe to call any methods that may result in
+ // a virtual method call.
+ virtual void OnURLRequestDestroyed(URLRequest* request) = 0;
+
+ // Corresponds to ProxyResolverJSBindings::OnError.
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) = 0;
+
+ // Called when a request receives an authentication challenge
+ // specified by |auth_info|, and is unable to respond using cached
+ // credentials. |callback| and |credentials| must be non-NULL, and must
+ // be valid until OnURLRequestDestroyed is called for |request|.
+ //
+ // The following return values are allowed:
+ // - AUTH_REQUIRED_RESPONSE_NO_ACTION: |auth_info| is observed, but
+ // no action is being taken on it.
+ // - AUTH_REQUIRED_RESPONSE_SET_AUTH: |credentials| is filled in with
+ // a username and password, which should be used in a response to
+ // |auth_info|.
+ // - AUTH_REQUIRED_RESPONSE_CANCEL_AUTH: The authentication challenge
+ // should not be attempted.
+ // - AUTH_REQUIRED_RESPONSE_IO_PENDING: The action will be decided
+ // asynchronously. |callback| will be invoked when the decision is made,
+ // and one of the other AuthRequiredResponse values will be passed in with
+ // the same semantics as described above.
+ virtual AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) = 0;
+
+ // Called when reading cookies to allow the network delegate to block access
+ // to the cookie. This method will never be invoked when
+ // LOAD_DO_NOT_SEND_COOKIES is specified.
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) = 0;
+
+ // Called when a cookie is set to allow the network delegate to block access
+ // to the cookie. This method will never be invoked when
+ // LOAD_DO_NOT_SAVE_COOKIES is specified.
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) = 0;
+
+ // Called when a file access is attempted to allow the network delegate to
+ // allow or block access to the given file path. Returns true if access is
+ // allowed.
+ virtual bool OnCanAccessFile(const URLRequest& request,
+ const base::FilePath& path) const = 0;
+
+ // Returns true if the given request may be rejected when the
+ // URLRequestThrottlerManager believes the server servicing the
+ // request is overloaded or down.
+ virtual bool OnCanThrottleRequest(const URLRequest& request) const = 0;
+
+ // Returns true if the given |url| has to be requested over connection that
+ // is not tracked by the server. Usually is false, unless user privacy
+ // settings block cookies from being get or set.
+ virtual bool OnCanEnablePrivacyMode(
+ const GURL& url,
+ const GURL& first_party_for_cookies) const;
+
+ // Called before a SocketStream tries to connect.
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* socket, const CompletionCallback& callback) = 0;
+
+ // Called when the completion of a URLRequest is blocking on a cache
+ // action or a network action, or when that is no longer the case.
+ // REQUEST_WAIT_STATE_RESET indicates for a given URLRequest
+ // cancellation of any pending waits for this request.
+ virtual void OnRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) = 0;
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_DELEGATE_H_
diff --git a/chromium/net/base/network_time_notifier.cc b/chromium/net/base/network_time_notifier.cc
new file mode 100644
index 00000000000..6dd0d3dde1e
--- /dev/null
+++ b/chromium/net/base/network_time_notifier.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2013 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/base/network_time_notifier.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/i18n/time_formatting.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace {
+
+// Clock resolution is platform dependent.
+#if defined(OS_WIN)
+const int64 kTicksResolutionMs = base::Time::kMinLowResolutionThresholdMs;
+#else
+const int64 kTicksResolutionMs = 1; // Assume 1ms for non-windows platforms.
+#endif
+
+// Number of time measurements performed in a given network time calculation.
+const int kNumTimeMeasurements = 5;
+
+} // namespace
+
+namespace net {
+
+NetworkTimeNotifier::NetworkTimeNotifier(
+ scoped_ptr<base::TickClock> tick_clock) {
+ tick_clock_ = tick_clock.Pass();
+}
+
+NetworkTimeNotifier::~NetworkTimeNotifier() {}
+
+void NetworkTimeNotifier::UpdateNetworkTime(const base::Time& network_time,
+ const base::TimeDelta& resolution,
+ const base::TimeDelta& latency,
+ const base::TimeTicks& post_time) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "Network time updating to "
+ << UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(network_time));
+ // Update network time on every request to limit dependency on ticks lag.
+ // TODO(mad): Find a heuristic to avoid augmenting the
+ // network_time_uncertainty_ too much by a particularly long latency.
+ // Maybe only update when the the new time either improves in accuracy or
+ // drifts too far from |network_time_|.
+ network_time_ = network_time;
+
+ // Calculate the delay since the network time was received.
+ base::TimeTicks now = tick_clock_->NowTicks();
+ base::TimeDelta task_delay = now - post_time;
+ // Estimate that the time was set midway through the latency time.
+ network_time_ticks_ = now - task_delay - latency / 2;
+
+ // Can't assume a better time than the resolution of the given time
+ // and 5 ticks measurements are involved, each with their own uncertainty.
+ // 1 & 2 are the ones used to compute the latency, 3 is the Now() from when
+ // this task was posted, 4 is the Now() above and 5 will be the Now() used in
+ // GetNetworkTime().
+ network_time_uncertainty_ =
+ resolution + latency + kNumTimeMeasurements *
+ base::TimeDelta::FromMilliseconds(kTicksResolutionMs);
+
+ for (size_t i = 0; i < observers_.size(); ++i) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(observers_[i],
+ network_time_,
+ network_time_ticks_,
+ network_time_uncertainty_));
+ }
+}
+
+void NetworkTimeNotifier::AddObserver(
+ const ObserverCallback& observer_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.push_back(observer_callback);
+ if (!network_time_.is_null()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(observer_callback,
+ network_time_,
+ network_time_ticks_,
+ network_time_uncertainty_));
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/network_time_notifier.h b/chromium/net/base/network_time_notifier.h
new file mode 100644
index 00000000000..62e234863a0
--- /dev/null
+++ b/chromium/net/base/network_time_notifier.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2013 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_BASE_NETWORK_TIME_NOTIFIER_H_
+#define NET_BASE_NETWORK_TIME_NOTIFIER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A class that receives updates for and maintains network time. Network time
+// sources can pass updates through UpdateNetworkTime, and network time
+// consumers can register as observers. This class is not thread-safe.
+class NET_EXPORT NetworkTimeNotifier {
+ public:
+ // Callback for observers to receive network time updates.
+ // The parameters are:
+ // const base::Time& network_time - the new network time.
+ // const base::TimeTicks& network_time_ticks - the ticks time that corresponds
+ // with |network_time|.
+ // const base::TimeDelta& network_time_uncertainty - the uncertainty
+ // associated with the new network time.
+ typedef base::Callback<void(const base::Time&,
+ const base::TimeTicks&,
+ const base::TimeDelta&)> ObserverCallback;
+
+ // Takes ownership of |tick_clock|.
+ explicit NetworkTimeNotifier(scoped_ptr<base::TickClock> tick_clock);
+ ~NetworkTimeNotifier();
+
+ // Calculates corresponding time ticks according to the given parameters and
+ // notifies observers. The provided |network_time| is precise at the given
+ // |resolution| and represent the time between now and up to |latency| +
+ // (now - |post_time|) ago.
+ void UpdateNetworkTime(const base::Time& network_time,
+ const base::TimeDelta& resolution,
+ const base::TimeDelta& latency,
+ const base::TimeTicks& post_time);
+
+ // |observer_callback| will invoked every time the network time is updated, or
+ // if a network time is already available when AddObserver is called.
+ void AddObserver(const ObserverCallback& observer_callback);
+
+ private:
+ base::ThreadChecker thread_checker_;
+
+ // For querying current time ticks.
+ scoped_ptr<base::TickClock> tick_clock_;
+
+ // The network time based on last call to UpdateNetworkTime().
+ base::Time network_time_;
+
+ // The estimated local time from |tick_clock| that corresponds with
+ // |network_time|. Assumes the actual network time measurement was performed
+ // midway through the latency time, and does not account for suspect/resume
+ // events since the network time was measured.
+ // See UpdateNetworkTime(..) implementation for details.
+ base::TimeTicks network_time_ticks_;
+
+ // Uncertainty of |network_time_| based on added inaccuracies/resolution.
+ // See UpdateNetworkTime(..) implementation for details.
+ base::TimeDelta network_time_uncertainty_;
+
+ // List of network time update observers.
+ // A vector of callbacks is used, rather than an ObserverList, so that the
+ // lifetime of the observer can be bound to the callback.
+ std::vector<ObserverCallback> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkTimeNotifier);
+};
+
+} // namespace net
+
+#endif // NET_BASE_NETWORK_TIME_NOTIFIER_H_
diff --git a/chromium/net/base/nss_memio.c b/chromium/net/base/nss_memio.c
new file mode 100644
index 00000000000..51012e608c4
--- /dev/null
+++ b/chromium/net/base/nss_memio.c
@@ -0,0 +1,533 @@
+// Copyright (c) 2008 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.
+// Written in NSPR style to also be suitable for adding to the NSS demo suite
+
+/* memio is a simple NSPR I/O layer that lets you decouple NSS from
+ * the real network. It's rather like openssl's memory bio,
+ * and is useful when your app absolutely, positively doesn't
+ * want to let NSS do its own networking.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <prerror.h>
+#include <prinit.h>
+#include <prlog.h>
+
+#include "nss_memio.h"
+
+/*--------------- private memio types -----------------------*/
+
+/*----------------------------------------------------------------------
+ Simple private circular buffer class. Size cannot be changed once allocated.
+----------------------------------------------------------------------*/
+
+struct memio_buffer {
+ int head; /* where to take next byte out of buf */
+ int tail; /* where to put next byte into buf */
+ int bufsize; /* number of bytes allocated to buf */
+ /* TODO(port): error handling is pessimistic right now.
+ * Once an error is set, the socket is considered broken
+ * (PR_WOULD_BLOCK_ERROR not included).
+ */
+ PRErrorCode last_err;
+ char *buf;
+};
+
+
+/* The 'secret' field of a PRFileDesc created by memio_CreateIOLayer points
+ * to one of these.
+ * In the public header, we use struct memio_Private as a typesafe alias
+ * for this. This causes a few ugly typecasts in the private file, but
+ * seems safer.
+ */
+struct PRFilePrivate {
+ /* read requests are satisfied from this buffer */
+ struct memio_buffer readbuf;
+
+ /* write requests are satisfied from this buffer */
+ struct memio_buffer writebuf;
+
+ /* SSL needs to know socket peer's name */
+ PRNetAddr peername;
+
+ /* if set, empty I/O returns EOF instead of EWOULDBLOCK */
+ int eof;
+
+ /* if set, the number of bytes requested from readbuf that were not
+ * fulfilled (due to readbuf being empty) */
+ int read_requested;
+};
+
+/*--------------- private memio_buffer functions ---------------------*/
+
+/* Forward declarations. */
+
+/* Allocate a memio_buffer of given size. */
+static void memio_buffer_new(struct memio_buffer *mb, int size);
+
+/* Deallocate a memio_buffer allocated by memio_buffer_new. */
+static void memio_buffer_destroy(struct memio_buffer *mb);
+
+/* How many bytes can be read out of the buffer without wrapping */
+static int memio_buffer_used_contiguous(const struct memio_buffer *mb);
+
+/* How many bytes exist after the wrap? */
+static int memio_buffer_wrapped_bytes(const struct memio_buffer *mb);
+
+/* How many bytes can be written into the buffer without wrapping */
+static int memio_buffer_unused_contiguous(const struct memio_buffer *mb);
+
+/* Write n bytes into the buffer. Returns number of bytes written. */
+static int memio_buffer_put(struct memio_buffer *mb, const char *buf, int n);
+
+/* Read n bytes from the buffer. Returns number of bytes read. */
+static int memio_buffer_get(struct memio_buffer *mb, char *buf, int n);
+
+/* Allocate a memio_buffer of given size. */
+static void memio_buffer_new(struct memio_buffer *mb, int size)
+{
+ mb->head = 0;
+ mb->tail = 0;
+ mb->bufsize = size;
+ mb->buf = malloc(size);
+}
+
+/* Deallocate a memio_buffer allocated by memio_buffer_new. */
+static void memio_buffer_destroy(struct memio_buffer *mb)
+{
+ free(mb->buf);
+ mb->buf = NULL;
+ mb->head = 0;
+ mb->tail = 0;
+}
+
+/* How many bytes can be read out of the buffer without wrapping */
+static int memio_buffer_used_contiguous(const struct memio_buffer *mb)
+{
+ return (((mb->tail >= mb->head) ? mb->tail : mb->bufsize) - mb->head);
+}
+
+/* How many bytes exist after the wrap? */
+static int memio_buffer_wrapped_bytes(const struct memio_buffer *mb)
+{
+ return (mb->tail >= mb->head) ? 0 : mb->tail;
+}
+
+/* How many bytes can be written into the buffer without wrapping */
+static int memio_buffer_unused_contiguous(const struct memio_buffer *mb)
+{
+ if (mb->head > mb->tail) return mb->head - mb->tail - 1;
+ return mb->bufsize - mb->tail - (mb->head == 0);
+}
+
+/* Write n bytes into the buffer. Returns number of bytes written. */
+static int memio_buffer_put(struct memio_buffer *mb, const char *buf, int n)
+{
+ int len;
+ int transferred = 0;
+
+ /* Handle part before wrap */
+ len = PR_MIN(n, memio_buffer_unused_contiguous(mb));
+ if (len > 0) {
+ /* Buffer not full */
+ memcpy(&mb->buf[mb->tail], buf, len);
+ mb->tail += len;
+ if (mb->tail == mb->bufsize)
+ mb->tail = 0;
+ n -= len;
+ buf += len;
+ transferred += len;
+
+ /* Handle part after wrap */
+ len = PR_MIN(n, memio_buffer_unused_contiguous(mb));
+ if (len > 0) {
+ /* Output buffer still not full, input buffer still not empty */
+ memcpy(&mb->buf[mb->tail], buf, len);
+ mb->tail += len;
+ if (mb->tail == mb->bufsize)
+ mb->tail = 0;
+ transferred += len;
+ }
+ }
+
+ return transferred;
+}
+
+
+/* Read n bytes from the buffer. Returns number of bytes read. */
+static int memio_buffer_get(struct memio_buffer *mb, char *buf, int n)
+{
+ int len;
+ int transferred = 0;
+
+ /* Handle part before wrap */
+ len = PR_MIN(n, memio_buffer_used_contiguous(mb));
+ if (len) {
+ memcpy(buf, &mb->buf[mb->head], len);
+ mb->head += len;
+ if (mb->head == mb->bufsize)
+ mb->head = 0;
+ n -= len;
+ buf += len;
+ transferred += len;
+
+ /* Handle part after wrap */
+ len = PR_MIN(n, memio_buffer_used_contiguous(mb));
+ if (len) {
+ memcpy(buf, &mb->buf[mb->head], len);
+ mb->head += len;
+ if (mb->head == mb->bufsize)
+ mb->head = 0;
+ transferred += len;
+ }
+ }
+
+ return transferred;
+}
+
+/*--------------- private memio functions -----------------------*/
+
+static PRStatus PR_CALLBACK memio_Close(PRFileDesc *fd)
+{
+ struct PRFilePrivate *secret = fd->secret;
+ memio_buffer_destroy(&secret->readbuf);
+ memio_buffer_destroy(&secret->writebuf);
+ free(secret);
+ fd->dtor(fd);
+ return PR_SUCCESS;
+}
+
+static PRStatus PR_CALLBACK memio_Shutdown(PRFileDesc *fd, PRIntn how)
+{
+ /* TODO: pass shutdown status to app somehow */
+ return PR_SUCCESS;
+}
+
+/* If there was a network error in the past taking bytes
+ * out of the buffer, return it to the next call that
+ * tries to read from an empty buffer.
+ */
+static int PR_CALLBACK memio_Recv(PRFileDesc *fd, void *buf, PRInt32 len,
+ PRIntn flags, PRIntervalTime timeout)
+{
+ struct PRFilePrivate *secret;
+ struct memio_buffer *mb;
+ int rv;
+
+ if (flags) {
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return -1;
+ }
+
+ secret = fd->secret;
+ mb = &secret->readbuf;
+ PR_ASSERT(mb->bufsize);
+ rv = memio_buffer_get(mb, buf, len);
+ if (rv == 0 && !secret->eof) {
+ secret->read_requested = len;
+ /* If there is no more data in the buffer, report any pending errors
+ * that were previously observed. Note that both the readbuf and the
+ * writebuf are checked for errors, since the application may have
+ * encountered a socket error while writing that would otherwise not
+ * be reported until the application attempted to write again - which
+ * it may never do.
+ */
+ if (mb->last_err)
+ PR_SetError(mb->last_err, 0);
+ else if (secret->writebuf.last_err)
+ PR_SetError(secret->writebuf.last_err, 0);
+ else
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+
+ secret->read_requested = 0;
+ return rv;
+}
+
+static int PR_CALLBACK memio_Read(PRFileDesc *fd, void *buf, PRInt32 len)
+{
+ /* pull bytes from buffer */
+ return memio_Recv(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT);
+}
+
+static int PR_CALLBACK memio_Send(PRFileDesc *fd, const void *buf, PRInt32 len,
+ PRIntn flags, PRIntervalTime timeout)
+{
+ struct PRFilePrivate *secret;
+ struct memio_buffer *mb;
+ int rv;
+
+ secret = fd->secret;
+ mb = &secret->writebuf;
+ PR_ASSERT(mb->bufsize);
+
+ /* Note that the read error state is not reported, because it cannot be
+ * reported until all buffered data has been read. If there is an error
+ * with the next layer, attempting to call Send again will report the
+ * error appropriately.
+ */
+ if (mb->last_err) {
+ PR_SetError(mb->last_err, 0);
+ return -1;
+ }
+ rv = memio_buffer_put(mb, buf, len);
+ if (rv == 0) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+ return rv;
+}
+
+static int PR_CALLBACK memio_Write(PRFileDesc *fd, const void *buf, PRInt32 len)
+{
+ /* append bytes to buffer */
+ return memio_Send(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT);
+}
+
+static PRStatus PR_CALLBACK memio_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
+{
+ /* TODO: fail if memio_SetPeerName has not been called */
+ struct PRFilePrivate *secret = fd->secret;
+ *addr = secret->peername;
+ return PR_SUCCESS;
+}
+
+static PRStatus memio_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
+{
+ /*
+ * Even in the original version for real tcp sockets,
+ * PR_SockOpt_Nonblocking is a special case that does not
+ * translate to a getsockopt() call
+ */
+ if (PR_SockOpt_Nonblocking == data->option) {
+ data->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ }
+ PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
+ return PR_FAILURE;
+}
+
+/*--------------- private memio data -----------------------*/
+
+/*
+ * Implement just the bare minimum number of methods needed to make ssl happy.
+ *
+ * Oddly, PR_Recv calls ssl_Recv calls ssl_SocketIsBlocking calls
+ * PR_GetSocketOption, so we have to provide an implementation of
+ * PR_GetSocketOption that just says "I'm nonblocking".
+ */
+
+static struct PRIOMethods memio_layer_methods = {
+ PR_DESC_LAYERED,
+ memio_Close,
+ memio_Read,
+ memio_Write,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ memio_Shutdown,
+ memio_Recv,
+ memio_Send,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ memio_GetPeerName,
+ NULL,
+ NULL,
+ memio_GetSocketOption,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+static PRDescIdentity memio_identity = PR_INVALID_IO_LAYER;
+
+static PRStatus memio_InitializeLayerName(void)
+{
+ memio_identity = PR_GetUniqueIdentity("memio");
+ return PR_SUCCESS;
+}
+
+/*--------------- public memio functions -----------------------*/
+
+PRFileDesc *memio_CreateIOLayer(int readbufsize, int writebufsize)
+{
+ PRFileDesc *fd;
+ struct PRFilePrivate *secret;
+ static PRCallOnceType once;
+
+ PR_CallOnce(&once, memio_InitializeLayerName);
+
+ fd = PR_CreateIOLayerStub(memio_identity, &memio_layer_methods);
+ secret = malloc(sizeof(struct PRFilePrivate));
+ memset(secret, 0, sizeof(*secret));
+
+ memio_buffer_new(&secret->readbuf, readbufsize);
+ memio_buffer_new(&secret->writebuf, writebufsize);
+ fd->secret = secret;
+ return fd;
+}
+
+void memio_SetPeerName(PRFileDesc *fd, const PRNetAddr *peername)
+{
+ PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity);
+ struct PRFilePrivate *secret = memiofd->secret;
+ secret->peername = *peername;
+}
+
+memio_Private *memio_GetSecret(PRFileDesc *fd)
+{
+ PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity);
+ struct PRFilePrivate *secret = memiofd->secret;
+ return (memio_Private *)secret;
+}
+
+int memio_GetReadRequest(memio_Private *secret)
+{
+ return ((PRFilePrivate *)secret)->read_requested;
+}
+
+int memio_GetReadParams(memio_Private *secret, char **buf)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf;
+ PR_ASSERT(mb->bufsize);
+
+ *buf = &mb->buf[mb->tail];
+ return memio_buffer_unused_contiguous(mb);
+}
+
+int memio_GetReadableBufferSize(memio_Private *secret)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf;
+ PR_ASSERT(mb->bufsize);
+
+ return memio_buffer_used_contiguous(mb);
+}
+
+void memio_PutReadResult(memio_Private *secret, int bytes_read)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->readbuf;
+ PR_ASSERT(mb->bufsize);
+
+ if (bytes_read > 0) {
+ mb->tail += bytes_read;
+ if (mb->tail == mb->bufsize)
+ mb->tail = 0;
+ } else if (bytes_read == 0) {
+ /* Record EOF condition and report to caller when buffer runs dry */
+ ((PRFilePrivate *)secret)->eof = PR_TRUE;
+ } else /* if (bytes_read < 0) */ {
+ mb->last_err = bytes_read;
+ }
+}
+
+void memio_GetWriteParams(memio_Private *secret,
+ const char **buf1, unsigned int *len1,
+ const char **buf2, unsigned int *len2)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->writebuf;
+ PR_ASSERT(mb->bufsize);
+
+ *buf1 = &mb->buf[mb->head];
+ *len1 = memio_buffer_used_contiguous(mb);
+ *buf2 = mb->buf;
+ *len2 = memio_buffer_wrapped_bytes(mb);
+}
+
+void memio_PutWriteResult(memio_Private *secret, int bytes_written)
+{
+ struct memio_buffer* mb = &((PRFilePrivate *)secret)->writebuf;
+ PR_ASSERT(mb->bufsize);
+
+ if (bytes_written > 0) {
+ mb->head += bytes_written;
+ if (mb->head >= mb->bufsize)
+ mb->head -= mb->bufsize;
+ } else if (bytes_written < 0) {
+ mb->last_err = bytes_written;
+ }
+}
+
+/*--------------- private memio_buffer self-test -----------------*/
+
+/* Even a trivial unit test is very helpful when doing circular buffers. */
+/*#define TRIVIAL_SELF_TEST*/
+#ifdef TRIVIAL_SELF_TEST
+#include <stdio.h>
+
+#define TEST_BUFLEN 7
+
+#define CHECKEQ(a, b) { \
+ if ((a) != (b)) { \
+ printf("%d != %d, Test failed line %d\n", a, b, __LINE__); \
+ exit(1); \
+ } \
+}
+
+int main()
+{
+ struct memio_buffer mb;
+ char buf[100];
+ int i;
+
+ memio_buffer_new(&mb, TEST_BUFLEN);
+
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1);
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 0);
+
+ CHECKEQ(memio_buffer_put(&mb, "howdy", 5), 5);
+
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5);
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 5);
+ CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0);
+
+ CHECKEQ(memio_buffer_put(&mb, "!", 1), 1);
+
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), 0);
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 6);
+ CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0);
+
+ CHECKEQ(memio_buffer_get(&mb, buf, 6), 6);
+ CHECKEQ(memcmp(buf, "howdy!", 6), 0);
+
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), 1);
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 0);
+
+ CHECKEQ(memio_buffer_put(&mb, "01234", 5), 5);
+
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 1);
+ CHECKEQ(memio_buffer_wrapped_bytes(&mb), 4);
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5);
+
+ CHECKEQ(memio_buffer_put(&mb, "5", 1), 1);
+
+ CHECKEQ(memio_buffer_unused_contiguous(&mb), 0);
+ CHECKEQ(memio_buffer_used_contiguous(&mb), 1);
+
+ /* TODO: add more cases */
+
+ printf("Test passed\n");
+ exit(0);
+}
+
+#endif
diff --git a/chromium/net/base/nss_memio.h b/chromium/net/base/nss_memio.h
new file mode 100644
index 00000000000..8481d15e7a7
--- /dev/null
+++ b/chromium/net/base/nss_memio.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 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.
+// Written in NSPR style to also be suitable for adding to the NSS demo suite
+
+#ifndef __MEMIO_H
+#define __MEMIO_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "prio.h"
+
+/* Opaque structure. Really just a more typesafe alias for PRFilePrivate. */
+struct memio_Private;
+typedef struct memio_Private memio_Private;
+
+/*----------------------------------------------------------------------
+ NSPR I/O layer that terminates in a pair of circular buffers
+ rather than talking to the real network.
+ To use this with NSS:
+ 1) call memio_CreateIOLayer to create a fake NSPR socket
+ 2) call SSL_ImportFD to ssl-ify the socket
+ 3) Do your own networking calls to set up a TCP connection
+ 4) call memio_SetPeerName to tell NSS about the other end of the connection
+ 5) While at the same time doing plaintext nonblocking NSPR I/O as
+ usual to the nspr file descriptor returned by SSL_ImportFD,
+ your app must shuttle encrypted data between
+ the real network and memio's network buffers.
+ memio_GetReadParams/memio_PutReadResult
+ are the hooks you need to pump data into memio's input buffer,
+ and memio_GetWriteParams/memio_PutWriteResult
+ are the hooks you need to pump data out of memio's output buffer.
+----------------------------------------------------------------------*/
+
+/* Create the I/O layer and its two circular buffers. */
+PRFileDesc *memio_CreateIOLayer(int readbufsize, int writebufsize);
+
+/* Must call before trying to make an ssl connection */
+void memio_SetPeerName(PRFileDesc *fd, const PRNetAddr *peername);
+
+/* Return a private pointer needed by the following
+ * four functions. (We could have passed a PRFileDesc to
+ * them, but that would be slower. Better for the caller
+ * to grab the pointer once and cache it.
+ * This may be a premature optimization.)
+ */
+memio_Private *memio_GetSecret(PRFileDesc *fd);
+
+/* Ask memio how many bytes were requested by a higher layer if the
+ * last attempt to read data resulted in PR_WOULD_BLOCK_ERROR, due to the
+ * transport buffer being empty. If the last attempt to read data from the
+ * memio did not result in PR_WOULD_BLOCK_ERROR, returns 0.
+ */
+int memio_GetReadRequest(memio_Private *secret);
+
+/* Ask memio where to put bytes from the network, and how many it can handle.
+ * Returns bytes available to write, or 0 if none available.
+ * Puts current buffer position into *buf.
+ */
+int memio_GetReadParams(memio_Private *secret, char **buf);
+
+/* Ask memio how many bytes are contained in the internal buffer.
+ * Returns bytes available to read, or 0 if none available.
+ */
+int memio_GetReadableBufferSize(memio_Private *secret);
+
+/* Tell memio how many bytes were read from the network.
+ * If bytes_read is 0, causes EOF to be reported to
+ * NSS after it reads the last byte from the circular buffer.
+ * If bytes_read is < 0, it is treated as an NSPR error code.
+ * See nspr/pr/src/md/unix/unix_errors.c for how to
+ * map from Unix errors to NSPR error codes.
+ * On EWOULDBLOCK or the equivalent, don't call this function.
+ */
+void memio_PutReadResult(memio_Private *secret, int bytes_read);
+
+/* Ask memio what data it has to send to the network.
+ * Returns up to two buffers of data by writing the positions and lengths into
+ * |buf1|, |len1| and |buf2|, |len2|.
+ */
+void memio_GetWriteParams(memio_Private *secret,
+ const char **buf1, unsigned int *len1,
+ const char **buf2, unsigned int *len2);
+
+/* Tell memio how many bytes were sent to the network.
+ * If bytes_written is < 0, it is treated as an NSPR error code.
+ * See nspr/pr/src/md/unix/unix_errors.c for how to
+ * map from Unix errors to NSPR error codes.
+ * On EWOULDBLOCK or the equivalent, don't call this function.
+ */
+void memio_PutWriteResult(memio_Private *secret, int bytes_written);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/chromium/net/base/openssl_private_key_store.h b/chromium/net/base/openssl_private_key_store.h
new file mode 100644
index 00000000000..0ad3b1a99c0
--- /dev/null
+++ b/chromium/net/base/openssl_private_key_store.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2010 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_BASE_OPENSSL_PRIVATE_KEY_STORE_H_
+#define NET_BASE_OPENSSL_PRIVATE_KEY_STORE_H_
+
+#include <vector>
+
+// Avoid including <openssl/evp.h>
+typedef struct evp_pkey_st EVP_PKEY;
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class X509Certificate;
+
+// OpenSSLPrivateKeyStore provides an interface for storing
+// public/private key pairs to system storage on platforms where
+// OpenSSL is used.
+// This class shall only be used from the network thread.
+class NET_EXPORT OpenSSLPrivateKeyStore {
+ public:
+ // Called to permanently store a private/public key pair, generated
+ // via <keygen> while visiting |url|, to an appropriate system
+ // location. Increments |pkey|'s reference count, so the caller is still
+ // responsible for calling EVP_PKEY_free on it.
+ // |url| is the corresponding server URL.
+ // |pkey| is the key pair handle.
+ // Returns false if an error occurred whilst attempting to store the key.
+ static bool StoreKeyPair(const GURL& url, EVP_PKEY* pkey);
+
+ // Checks that the private key for a given public key is installed.
+ // |pub_key| a public key.
+ // Returns true if there is a private key that was previously
+ // recorded through StoreKeyPair().
+ // NOTE: Intentionally not implemented on Android because there is no
+ // platform API that can perform this operation silently.
+ static bool HasPrivateKey(EVP_PKEY* pub_key);
+
+ private:
+ OpenSSLPrivateKeyStore(); // not implemented.
+ ~OpenSSLPrivateKeyStore(); // not implemented.
+ DISALLOW_COPY_AND_ASSIGN(OpenSSLPrivateKeyStore);
+};
+
+} // namespace net
+
+#endif // NET_BASE_OPENSSL_PRIVATE_KEY_STORE_H_
diff --git a/chromium/net/base/openssl_private_key_store_android.cc b/chromium/net/base/openssl_private_key_store_android.cc
new file mode 100644
index 00000000000..e9851077faa
--- /dev/null
+++ b/chromium/net/base/openssl_private_key_store_android.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 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.
+
+#include "net/base/openssl_private_key_store.h"
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "crypto/openssl_util.h"
+#include "net/android/network_library.h"
+
+namespace net {
+
+bool OpenSSLPrivateKeyStore::StoreKeyPair(const GURL& url,
+ EVP_PKEY* pkey) {
+ // Always clear openssl errors on exit.
+ crypto::OpenSSLErrStackTracer err_trace(FROM_HERE);
+
+ // Important: Do not use i2d_PublicKey() here, which returns data in
+ // PKCS#1 format, use i2d_PUBKEY() which returns it as DER-encoded
+ // SubjectPublicKeyInfo (X.509), as expected by the platform.
+ unsigned char* public_key = NULL;
+ int public_len = i2d_PUBKEY(pkey, &public_key);
+
+ // Important: Do not use i2d_PrivateKey() here, it returns data
+ // in a format that is incompatible with what the platform expects.
+ unsigned char* private_key = NULL;
+ int private_len = 0;
+ crypto::ScopedOpenSSL<
+ PKCS8_PRIV_KEY_INFO,
+ PKCS8_PRIV_KEY_INFO_free> pkcs8(EVP_PKEY2PKCS8(pkey));
+ if (pkcs8.get() != NULL) {
+ private_len = i2d_PKCS8_PRIV_KEY_INFO(pkcs8.get(), &private_key);
+ }
+ bool ret = false;
+ if (public_len > 0 && private_len > 0) {
+ ret = net::android::StoreKeyPair(
+ static_cast<const uint8*>(public_key), public_len,
+ static_cast<const uint8*>(private_key), private_len);
+ }
+ LOG_IF(ERROR, !ret) << "StoreKeyPair failed. pub len = " << public_len
+ << " priv len = " << private_len;
+ OPENSSL_free(public_key);
+ OPENSSL_free(private_key);
+ return ret;
+}
+
+} // namespace net
diff --git a/chromium/net/base/openssl_private_key_store_memory.cc b/chromium/net/base/openssl_private_key_store_memory.cc
new file mode 100644
index 00000000000..0913e460bd2
--- /dev/null
+++ b/chromium/net/base/openssl_private_key_store_memory.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2011 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.
+
+// Defines an in-memory private key store, primarily used for testing.
+
+#include "net/base/openssl_private_key_store.h"
+
+#include <openssl/evp.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+
+namespace net {
+
+namespace {
+
+// A small in-memory store for public/private key pairs held in
+// a single EVP_PKEY object. This is intentionally distinct from
+// net::SSLClientKeyStore.
+class MemoryKeyPairStore {
+ public:
+ MemoryKeyPairStore() {}
+
+ static MemoryKeyPairStore* GetInstance() {
+ return Singleton<MemoryKeyPairStore>::get();
+ }
+
+ ~MemoryKeyPairStore() {
+ base::AutoLock lock(lock_);
+ for (std::vector<EVP_PKEY*>::iterator it = keys_.begin();
+ it != keys_.end(); ++it) {
+ EVP_PKEY_free(*it);
+ }
+ }
+
+ bool StoreKeyPair(EVP_PKEY* pkey) {
+ CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
+ base::AutoLock lock(lock_);
+ keys_.push_back(pkey);
+ return true;
+ }
+
+ bool HasPrivateKey(EVP_PKEY* pkey) {
+ base::AutoLock lock(lock_);
+ for (std::vector<EVP_PKEY*>::iterator it = keys_.begin();
+ it != keys_.end(); ++it) {
+ if (EVP_PKEY_cmp(*it, pkey) == 1)
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ std::vector<EVP_PKEY*> keys_;
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemoryKeyPairStore);
+};
+
+} // namespace
+
+bool OpenSSLPrivateKeyStore::StoreKeyPair(const GURL& url,
+ EVP_PKEY* pkey) {
+ return MemoryKeyPairStore::GetInstance()->StoreKeyPair(pkey);
+}
+
+bool OpenSSLPrivateKeyStore::HasPrivateKey(EVP_PKEY* pub_key) {
+ return MemoryKeyPairStore::GetInstance()->HasPrivateKey(pub_key);
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/platform_mime_util.h b/chromium/net/base/platform_mime_util.h
new file mode 100644
index 00000000000..726cea43414
--- /dev/null
+++ b/chromium/net/base/platform_mime_util.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_PLATFORM_MIME_UTIL_H_
+#define NET_BASE_PLATFORM_MIME_UTIL_H_
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+
+namespace net {
+
+// Encapsulates the platform-specific functionality in mime_util.
+class PlatformMimeUtil {
+ public:
+ // See documentation for base::GetPreferredExtensionForMimeType [mime_util.h]
+ bool GetPreferredExtensionForMimeType(
+ const std::string& mime_type,
+ base::FilePath::StringType* extension) const;
+
+ // Adds all the extensions that the platform associates with the type
+ // |mime_type| to the set |extensions|. Returns at least the value returned
+ // by GetPreferredExtensionForMimeType.
+ void GetPlatformExtensionsForMimeType(
+ const std::string& mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) const;
+
+ protected:
+ // Get the mime type (if any) that is associated with the file extension.
+ // Returns true if a corresponding mime type exists.
+ bool GetPlatformMimeTypeFromExtension(const base::FilePath::StringType& ext,
+ std::string* mime_type) const;
+};
+
+} // namespace net
+
+#endif // NET_BASE_PLATFORM_MIME_UTIL_H_
diff --git a/chromium/net/base/platform_mime_util_linux.cc b/chromium/net/base/platform_mime_util_linux.cc
new file mode 100644
index 00000000000..f4099e4b1d4
--- /dev/null
+++ b/chromium/net/base/platform_mime_util_linux.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 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.
+
+#include "net/base/platform_mime_util.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include "net/android/network_library.h"
+#else
+#include "base/nix/mime_util_xdg.h"
+#endif
+
+namespace net {
+
+#if defined(OS_ANDROID)
+bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
+ const base::FilePath::StringType& ext, std::string* result) const {
+ return android::GetMimeTypeFromExtension(ext, result);
+}
+#else
+bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
+ const base::FilePath::StringType& ext, std::string* result) const {
+ // TODO(thestig): This is a temporary hack until we can fix this
+ // properly in test shell / webkit.
+ // We have to play dumb and not return application/x-perl here
+ // to make the reload-subframe-object layout test happy.
+ if (ext == "pl")
+ return false;
+
+ base::FilePath dummy_path("foo." + ext);
+ std::string out = base::nix::GetFileMimeType(dummy_path);
+
+ // GetFileMimeType likes to return application/octet-stream
+ // for everything it doesn't know - ignore that.
+ if (out == "application/octet-stream" || out.empty())
+ return false;
+
+ // GetFileMimeType returns image/x-ico because that's what's in the XDG
+ // mime database. That database is the merger of the Gnome and KDE mime
+ // databases. Apparently someone working on KDE in 2001 decided .ico
+ // resolves to image/x-ico, whereas the rest of the world uses image/x-icon.
+ // FWIW, image/vnd.microsoft.icon is the official IANA assignment.
+ if (out == "image/x-ico")
+ out = "image/x-icon";
+
+ *result = out;
+ return true;
+}
+
+#endif // defined(OS_ANDROID)
+
+struct MimeToExt {
+ const char* mime_type;
+ const char* ext;
+};
+
+const struct MimeToExt mime_type_ext_map[] = {
+ {"application/pdf", "pdf"},
+ {"application/x-tar", "tar"},
+ {"audio/mpeg", "mp3"},
+ {"image/gif", "gif"},
+ {"image/jpeg", "jpg"},
+ {"image/png", "png"},
+ {"text/html", "html"},
+ {"video/mp4", "mp4"},
+ {"video/mpeg", "mpg"},
+ {"text/plain", "txt"},
+ {"text/x-sh", "sh"},
+};
+
+bool PlatformMimeUtil::GetPreferredExtensionForMimeType(
+ const std::string& mime_type, base::FilePath::StringType* ext) const {
+
+ for (size_t x = 0;
+ x < (sizeof(mime_type_ext_map) / sizeof(MimeToExt));
+ x++) {
+ if (mime_type_ext_map[x].mime_type == mime_type) {
+ *ext = mime_type_ext_map[x].ext;
+ return true;
+ }
+ }
+
+ // TODO(dhg): Fix this the right way by implementing what's said below.
+ // Unlike GetPlatformMimeTypeFromExtension, this method doesn't have a
+ // default list that it uses, but for now we are also returning false since
+ // this doesn't really matter as much under Linux.
+ //
+ // If we wanted to do this properly, we would read the mime.cache file which
+ // has a section where they assign a glob (*.gif) to a mimetype
+ // (image/gif). We look up the "heaviest" glob for a certain mime type and
+ // then then try to chop off "*.".
+
+ return false;
+}
+
+void PlatformMimeUtil::GetPlatformExtensionsForMimeType(
+ const std::string& mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) const {
+ base::FilePath::StringType ext;
+ if (GetPreferredExtensionForMimeType(mime_type, &ext))
+ extensions->insert(ext);
+}
+
+} // namespace net
diff --git a/chromium/net/base/platform_mime_util_mac.mm b/chromium/net/base/platform_mime_util_mac.mm
new file mode 100644
index 00000000000..cf5b2625a58
--- /dev/null
+++ b/chromium/net/base/platform_mime_util_mac.mm
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#include "net/base/platform_mime_util.h"
+
+#import <Foundation/Foundation.h>
+
+#include <string>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+
+#if defined(OS_IOS)
+#include <MobileCoreServices/MobileCoreServices.h>
+#else
+#include <CoreServices/CoreServices.h>
+#endif // defined(OS_IOS)
+
+#if !defined(OS_IOS)
+// SPI declaration; see the commentary in GetPlatformExtensionsForMimeType.
+// iOS must not use any private API, per Apple guideline.
+
+@interface NSURLFileTypeMappings : NSObject
++ (NSURLFileTypeMappings*)sharedMappings;
+- (NSArray*)extensionsForMIMEType:(NSString*)mimeType;
+@end
+#endif // !defined(OS_IOS)
+
+namespace net {
+
+bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
+ const base::FilePath::StringType& ext, std::string* result) const {
+ std::string ext_nodot = ext;
+ if (ext_nodot.length() >= 1 && ext_nodot[0] == L'.')
+ ext_nodot.erase(ext_nodot.begin());
+ base::ScopedCFTypeRef<CFStringRef> ext_ref(
+ base::SysUTF8ToCFStringRef(ext_nodot));
+ if (!ext_ref)
+ return false;
+ base::ScopedCFTypeRef<CFStringRef> uti(UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassFilenameExtension, ext_ref, NULL));
+ if (!uti)
+ return false;
+ base::ScopedCFTypeRef<CFStringRef> mime_ref(
+ UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType));
+ if (!mime_ref)
+ return false;
+
+ *result = base::SysCFStringRefToUTF8(mime_ref);
+ return true;
+}
+
+bool PlatformMimeUtil::GetPreferredExtensionForMimeType(
+ const std::string& mime_type, base::FilePath::StringType* ext) const {
+ base::ScopedCFTypeRef<CFStringRef> mime_ref(
+ base::SysUTF8ToCFStringRef(mime_type));
+ if (!mime_ref)
+ return false;
+ base::ScopedCFTypeRef<CFStringRef> uti(UTTypeCreatePreferredIdentifierForTag(
+ kUTTagClassMIMEType, mime_ref, NULL));
+ if (!uti)
+ return false;
+ base::ScopedCFTypeRef<CFStringRef> ext_ref(
+ UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension));
+ if (!ext_ref)
+ return false;
+
+ *ext = base::SysCFStringRefToUTF8(ext_ref);
+ return true;
+}
+
+void PlatformMimeUtil::GetPlatformExtensionsForMimeType(
+ const std::string& mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) const {
+#if defined(OS_IOS)
+ NSArray* extensions_list = nil;
+#else
+ // There is no API for this that uses UTIs. The WebKitSystemInterface call
+ // WKGetExtensionsForMIMEType() is a thin wrapper around
+ // [[NSURLFileTypeMappings sharedMappings] extensionsForMIMEType:], which is
+ // used by Firefox as well.
+ //
+ // See:
+ // http://mxr.mozilla.org/mozilla-central/search?string=extensionsForMIMEType
+ // http://www.openradar.me/11384153
+ // rdar://11384153
+ NSArray* extensions_list =
+ [[NSURLFileTypeMappings sharedMappings]
+ extensionsForMIMEType:base::SysUTF8ToNSString(mime_type)];
+#endif // defined(OS_IOS)
+
+ if (extensions_list) {
+ for (NSString* extension in extensions_list)
+ extensions->insert(base::SysNSStringToUTF8(extension));
+ } else {
+ // Huh? Give up.
+ base::FilePath::StringType ext;
+ if (GetPreferredExtensionForMimeType(mime_type, &ext))
+ extensions->insert(ext);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/platform_mime_util_win.cc b/chromium/net/base/platform_mime_util_win.cc
new file mode 100644
index 00000000000..421ddba1077
--- /dev/null
+++ b/chromium/net/base/platform_mime_util_win.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 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.
+
+#include "net/base/platform_mime_util.h"
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+
+namespace net {
+
+bool PlatformMimeUtil::GetPlatformMimeTypeFromExtension(
+ const base::FilePath::StringType& ext, std::string* result) const {
+ // check windows registry for file extension's mime type (registry key
+ // names are not case-sensitive).
+ std::wstring value, key = L"." + ext;
+ base::win::RegKey(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ).ReadValue(
+ L"Content Type", &value);
+ if (!value.empty()) {
+ *result = WideToUTF8(value);
+ return true;
+ }
+ return false;
+}
+
+bool PlatformMimeUtil::GetPreferredExtensionForMimeType(
+ const std::string& mime_type, base::FilePath::StringType* ext) const {
+ std::wstring key(L"MIME\\Database\\Content Type\\" + UTF8ToWide(mime_type));
+ if (base::win::RegKey(HKEY_CLASSES_ROOT, key.c_str(), KEY_READ).ReadValue(
+ L"Extension", ext) != ERROR_SUCCESS) {
+ return false;
+ }
+ // Strip off the leading dot, this should always be the case.
+ if (!ext->empty() && ext->at(0) == L'.')
+ ext->erase(ext->begin());
+
+ return true;
+}
+
+void PlatformMimeUtil::GetPlatformExtensionsForMimeType(
+ const std::string& mime_type,
+ base::hash_set<base::FilePath::StringType>* extensions) const {
+ // Multiple extensions could have the given mime type specified as their types
+ // in their 'HKCR\.<extension>\Content Type' keys. Iterating all the HKCR
+ // entries, though, is wildly impractical. Cheat by returning just the
+ // preferred extension.
+ base::FilePath::StringType ext;
+ if (GetPreferredExtensionForMimeType(mime_type, &ext))
+ extensions->insert(ext);
+}
+
+} // namespace net
diff --git a/chromium/net/base/prioritized_dispatcher.cc b/chromium/net/base/prioritized_dispatcher.cc
new file mode 100644
index 00000000000..44348e6f5e3
--- /dev/null
+++ b/chromium/net/base/prioritized_dispatcher.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 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.
+
+#include "net/base/prioritized_dispatcher.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+PrioritizedDispatcher::Limits::Limits(Priority num_priorities,
+ size_t total_jobs)
+ : total_jobs(total_jobs), reserved_slots(num_priorities) {}
+
+PrioritizedDispatcher::Limits::~Limits() {}
+
+PrioritizedDispatcher::PrioritizedDispatcher(const Limits& limits)
+ : queue_(limits.reserved_slots.size()),
+ max_running_jobs_(limits.reserved_slots.size()),
+ num_running_jobs_(0) {
+ size_t total = 0;
+ for (size_t i = 0; i < limits.reserved_slots.size(); ++i) {
+ total += limits.reserved_slots[i];
+ max_running_jobs_[i] = total;
+ }
+ // Unreserved slots are available for all priorities.
+ DCHECK_LE(total, limits.total_jobs) << "sum(reserved_slots) <= total_jobs";
+ size_t spare = limits.total_jobs - total;
+ for (size_t i = limits.reserved_slots.size(); i > 0; --i) {
+ max_running_jobs_[i - 1] += spare;
+ }
+}
+
+PrioritizedDispatcher::~PrioritizedDispatcher() {}
+
+PrioritizedDispatcher::Handle PrioritizedDispatcher::Add(
+ Job* job, Priority priority) {
+ DCHECK(job);
+ DCHECK_LT(priority, num_priorities());
+ if (num_running_jobs_ < max_running_jobs_[priority]) {
+ ++num_running_jobs_;
+ job->Start();
+ return Handle();
+ }
+ return queue_.Insert(job, priority);
+}
+
+void PrioritizedDispatcher::Cancel(const Handle& handle) {
+ queue_.Erase(handle);
+}
+
+PrioritizedDispatcher::Job* PrioritizedDispatcher::EvictOldestLowest() {
+ Handle handle = queue_.FirstMin();
+ if (handle.is_null())
+ return NULL;
+ Job* job = handle.value();
+ Cancel(handle);
+ return job;
+}
+
+PrioritizedDispatcher::Handle PrioritizedDispatcher::ChangePriority(
+ const Handle& handle, Priority priority) {
+ DCHECK(!handle.is_null());
+ DCHECK_LT(priority, num_priorities());
+ DCHECK_GE(num_running_jobs_, max_running_jobs_[handle.priority()]) <<
+ "Job should not be in queue when limits permit it to start.";
+
+ if (handle.priority() == priority)
+ return handle;
+
+ if (MaybeDispatchJob(handle, priority))
+ return Handle();
+ Job* job = handle.value();
+ queue_.Erase(handle);
+ return queue_.Insert(job, priority);
+}
+
+void PrioritizedDispatcher::OnJobFinished() {
+ DCHECK_GT(num_running_jobs_, 0u);
+ --num_running_jobs_;
+ Handle handle = queue_.FirstMax();
+ if (handle.is_null()) {
+ DCHECK_EQ(0u, queue_.size());
+ return;
+ }
+ MaybeDispatchJob(handle, handle.priority());
+}
+
+bool PrioritizedDispatcher::MaybeDispatchJob(const Handle& handle,
+ Priority job_priority) {
+ DCHECK_LT(job_priority, num_priorities());
+ if (num_running_jobs_ >= max_running_jobs_[job_priority])
+ return false;
+ Job* job = handle.value();
+ queue_.Erase(handle);
+ ++num_running_jobs_;
+ job->Start();
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/base/prioritized_dispatcher.h b/chromium/net/base/prioritized_dispatcher.h
new file mode 100644
index 00000000000..708f9d6011d
--- /dev/null
+++ b/chromium/net/base/prioritized_dispatcher.h
@@ -0,0 +1,117 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_PRIORITIZED_DISPATCHER_H_
+#define NET_BASE_PRIORITIZED_DISPATCHER_H_
+
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/base/priority_queue.h"
+
+namespace net {
+
+// A priority-based dispatcher of jobs. Dispatch order is by priority (highest
+// first) and then FIFO. The dispatcher enforces limits on the number of running
+// jobs. It never revokes a job once started. The job must call OnJobFinished
+// once it finishes in order to dispatch further jobs.
+//
+// This class is NOT thread-safe which is enforced by the underlying
+// non-thread-safe PriorityQueue. All operations are O(p) time for p priority
+// levels. It is safe to execute any method, including destructor, from within
+// Job::Start.
+//
+class NET_EXPORT_PRIVATE PrioritizedDispatcher {
+ public:
+ class Job;
+ typedef PriorityQueue<Job*>::Priority Priority;
+
+ // Describes the limits for the number of jobs started by the dispatcher.
+ // For example, |total_jobs| = 30 and |reserved_slots| = { 0, 5, 10, 5 } allow
+ // for at most 30 running jobs in total. Jobs at priority 0 can't use slots
+ // reserved for higher priorities, so they are limited to 10.
+ // If there are already 24 jobs running, then only 6 more jobs can start. No
+ // jobs at priority 1 or below can start. After one more job starts, no jobs
+ // at priority 2 or below can start, since the remaining 5 slots are reserved
+ // for priority 3 or above.
+ struct NET_EXPORT_PRIVATE Limits {
+ Limits(Priority num_priorities, size_t total_jobs);
+ ~Limits();
+
+ // Total allowed running jobs.
+ size_t total_jobs;
+ // Number of slots reserved for each priority and higher.
+ // Sum of |reserved_slots| must be no greater than |total_jobs|.
+ std::vector<size_t> reserved_slots;
+ };
+
+ // An interface to the job dispatched by PrioritizedDispatcher. The dispatcher
+ // does not own the Job but expects it to live as long as the Job is queued.
+ // Use Cancel to remove Job from queue before it is dispatched. The Job can be
+ // deleted after it is dispatched or canceled, or the dispatcher is destroyed.
+ class Job {
+ public:
+ // Note: PrioritizedDispatcher will never delete a Job.
+ virtual ~Job() {}
+ // Called when the dispatcher starts the job. Once the job finishes, it must
+ // call OnJobFinished.
+ virtual void Start() = 0;
+ };
+
+ // A handle to the enqueued job. The handle becomes invalid when the job is
+ // canceled, updated, or started.
+ typedef PriorityQueue<Job*>::Pointer Handle;
+
+ // Creates a dispatcher enforcing |limits| on number of running jobs.
+ explicit PrioritizedDispatcher(const Limits& limits);
+
+ ~PrioritizedDispatcher();
+
+ size_t num_running_jobs() const { return num_running_jobs_; }
+ size_t num_queued_jobs() const { return queue_.size(); }
+ size_t num_priorities() const { return max_running_jobs_.size(); }
+
+ // Adds |job| with |priority| to the dispatcher. If limits permit, |job| is
+ // started immediately. Returns handle to the job or null-handle if the job is
+ // started. The dispatcher does not own |job|, but |job| must live as long as
+ // it is queued in the dispatcher.
+ Handle Add(Job* job, Priority priority);
+
+ // Removes the job with |handle| from the queue. Invalidates |handle|.
+ // Note: a Handle is valid iff the job is in the queue, i.e. has not Started.
+ void Cancel(const Handle& handle);
+
+ // Cancels and returns the oldest-lowest-priority Job invalidating any
+ // handles to it. Returns NULL if the queue is empty.
+ Job* EvictOldestLowest();
+
+ // Moves the queued job with |handle| to the end of all values with priority
+ // |priority| and returns the updated handle, or null-handle if it starts the
+ // job. Invalidates |handle|. No-op if priority did not change.
+ Handle ChangePriority(const Handle& handle, Priority priority);
+
+ // Notifies the dispatcher that a running job has finished. Could start a job.
+ void OnJobFinished();
+
+ private:
+ // Attempts to dispatch the job with |handle| at priority |priority| (might be
+ // different than |handle.priority()|. Returns true if successful. If so
+ // the |handle| becomes invalid.
+ bool MaybeDispatchJob(const Handle& handle, Priority priority);
+
+ // Queue for jobs that need to wait for a spare slot.
+ PriorityQueue<Job*> queue_;
+ // Maximum total number of running jobs allowed after a job at a particular
+ // priority is started. If a greater or equal number of jobs are running, then
+ // another job cannot be started.
+ std::vector<size_t> max_running_jobs_;
+ // Total number of running jobs.
+ size_t num_running_jobs_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrioritizedDispatcher);
+};
+
+} // namespace net
+
+#endif // NET_BASE_PRIORITIZED_DISPATCHER_H_
diff --git a/chromium/net/base/prioritized_dispatcher_unittest.cc b/chromium/net/base/prioritized_dispatcher_unittest.cc
new file mode 100644
index 00000000000..41a09c5cb92
--- /dev/null
+++ b/chromium/net/base/prioritized_dispatcher_unittest.cc
@@ -0,0 +1,365 @@
+// Copyright (c) 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.
+
+#include <ctype.h>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/prioritized_dispatcher.h"
+#include "net/base/request_priority.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// We rely on the priority enum values being sequential having starting at 0,
+// and increasing for higher priorities.
+COMPILE_ASSERT(MINIMUM_PRIORITY == 0u &&
+ MINIMUM_PRIORITY == IDLE &&
+ IDLE < LOWEST &&
+ LOWEST < HIGHEST &&
+ HIGHEST < NUM_PRIORITIES,
+ priority_indexes_incompatible);
+
+class PrioritizedDispatcherTest : public testing::Test {
+ public:
+ typedef PrioritizedDispatcher::Priority Priority;
+ // A job that appends |tag| to |log| when started and '.' when finished.
+ // This is intended to confirm the execution order of a sequence of jobs added
+ // to the dispatcher. Note that finishing order of jobs does not matter.
+ class TestJob : public PrioritizedDispatcher::Job {
+ public:
+ TestJob(PrioritizedDispatcher* dispatcher,
+ char tag,
+ Priority priority,
+ std::string* log)
+ : dispatcher_(dispatcher),
+ tag_(tag),
+ priority_(priority),
+ running_(false),
+ log_(log) {}
+
+ bool running() const {
+ return running_;
+ }
+
+ const PrioritizedDispatcher::Handle handle() const {
+ return handle_;
+ }
+
+ void Add() {
+ CHECK(handle_.is_null());
+ CHECK(!running_);
+ size_t num_queued = dispatcher_->num_queued_jobs();
+ size_t num_running = dispatcher_->num_running_jobs();
+
+ handle_ = dispatcher_->Add(this, priority_);
+
+ if (handle_.is_null()) {
+ EXPECT_EQ(num_queued, dispatcher_->num_queued_jobs());
+ EXPECT_TRUE(running_);
+ EXPECT_EQ(num_running + 1, dispatcher_->num_running_jobs());
+ } else {
+ EXPECT_FALSE(running_);
+ EXPECT_EQ(priority_, handle_.priority());
+ EXPECT_EQ(tag_, reinterpret_cast<TestJob*>(handle_.value())->tag_);
+ EXPECT_EQ(num_running, dispatcher_->num_running_jobs());
+ }
+ }
+
+ void ChangePriority(Priority priority) {
+ CHECK(!handle_.is_null());
+ CHECK(!running_);
+ size_t num_queued = dispatcher_->num_queued_jobs();
+ size_t num_running = dispatcher_->num_running_jobs();
+
+ handle_ = dispatcher_->ChangePriority(handle_, priority);
+
+ if (handle_.is_null()) {
+ EXPECT_TRUE(running_);
+ EXPECT_EQ(num_queued - 1, dispatcher_->num_queued_jobs());
+ EXPECT_EQ(num_running + 1, dispatcher_->num_running_jobs());
+ } else {
+ EXPECT_FALSE(running_);
+ EXPECT_EQ(priority, handle_.priority());
+ EXPECT_EQ(tag_, reinterpret_cast<TestJob*>(handle_.value())->tag_);
+ EXPECT_EQ(num_queued, dispatcher_->num_queued_jobs());
+ EXPECT_EQ(num_running, dispatcher_->num_running_jobs());
+ }
+ }
+
+ void Cancel() {
+ CHECK(!handle_.is_null());
+ CHECK(!running_);
+ size_t num_queued = dispatcher_->num_queued_jobs();
+
+ dispatcher_->Cancel(handle_);
+
+ EXPECT_EQ(num_queued - 1, dispatcher_->num_queued_jobs());
+ handle_ = PrioritizedDispatcher::Handle();
+ }
+
+ void Finish() {
+ CHECK(running_);
+ running_ = false;
+ log_->append(1u, '.');
+
+ dispatcher_->OnJobFinished();
+ }
+
+ // PriorityDispatch::Job interface
+ virtual void Start() OVERRIDE {
+ EXPECT_FALSE(running_);
+ handle_ = PrioritizedDispatcher::Handle();
+ running_ = true;
+ log_->append(1u, tag_);
+ }
+
+ private:
+ PrioritizedDispatcher* dispatcher_;
+
+ char tag_;
+ Priority priority_;
+
+ PrioritizedDispatcher::Handle handle_;
+ bool running_;
+
+ std::string* log_;
+ };
+
+ protected:
+ void Prepare(const PrioritizedDispatcher::Limits& limits) {
+ dispatcher_.reset(new PrioritizedDispatcher(limits));
+ }
+
+ TestJob* AddJob(char data, Priority priority) {
+ TestJob* job = new TestJob(dispatcher_.get(), data, priority, &log_);
+ jobs_.push_back(job);
+ job->Add();
+ return job;
+ }
+
+ void Expect(std::string log) {
+ EXPECT_EQ(0u, dispatcher_->num_queued_jobs());
+ EXPECT_EQ(0u, dispatcher_->num_running_jobs());
+ EXPECT_EQ(log, log_);
+ log_.clear();
+ }
+
+ std::string log_;
+ scoped_ptr<PrioritizedDispatcher> dispatcher_;
+ ScopedVector<TestJob> jobs_;
+};
+
+TEST_F(PrioritizedDispatcherTest, AddAFIFO) {
+ // Allow only one running job.
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', IDLE);
+ TestJob* job_c = AddJob('c', IDLE);
+ TestJob* job_d = AddJob('d', IDLE);
+
+ ASSERT_TRUE(job_a->running());
+ job_a->Finish();
+ ASSERT_TRUE(job_b->running());
+ job_b->Finish();
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+ ASSERT_TRUE(job_d->running());
+ job_d->Finish();
+
+ Expect("a.b.c.d.");
+}
+
+TEST_F(PrioritizedDispatcherTest, AddPriority) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', MEDIUM);
+ TestJob* job_c = AddJob('c', HIGHEST);
+ TestJob* job_d = AddJob('d', HIGHEST);
+ TestJob* job_e = AddJob('e', MEDIUM);
+
+ ASSERT_TRUE(job_a->running());
+ job_a->Finish();
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+ ASSERT_TRUE(job_d->running());
+ job_d->Finish();
+ ASSERT_TRUE(job_b->running());
+ job_b->Finish();
+ ASSERT_TRUE(job_e->running());
+ job_e->Finish();
+
+ Expect("a.c.d.b.e.");
+}
+
+TEST_F(PrioritizedDispatcherTest, EnforceLimits) {
+ // Reserve 2 for HIGHEST and 1 for LOW or higher.
+ // This leaves 2 for LOWEST or lower.
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 5);
+ limits.reserved_slots[HIGHEST] = 2;
+ limits.reserved_slots[LOW] = 1;
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE); // Uses unreserved slot.
+ TestJob* job_b = AddJob('b', IDLE); // Uses unreserved slot.
+ TestJob* job_c = AddJob('c', LOWEST); // Must wait.
+ TestJob* job_d = AddJob('d', LOW); // Uses reserved slot.
+ TestJob* job_e = AddJob('e', MEDIUM); // Must wait.
+ TestJob* job_f = AddJob('f', HIGHEST); // Uses reserved slot.
+ TestJob* job_g = AddJob('g', HIGHEST); // Uses reserved slot.
+ TestJob* job_h = AddJob('h', HIGHEST); // Must wait.
+
+ EXPECT_EQ(5u, dispatcher_->num_running_jobs());
+ EXPECT_EQ(3u, dispatcher_->num_queued_jobs());
+
+ ASSERT_TRUE(job_a->running());
+ ASSERT_TRUE(job_b->running());
+ ASSERT_TRUE(job_d->running());
+ ASSERT_TRUE(job_f->running());
+ ASSERT_TRUE(job_g->running());
+ // a, b, d, f, g are running. Finish them in any order.
+ job_b->Finish(); // Releases h.
+ job_f->Finish();
+ job_a->Finish();
+ job_g->Finish(); // Releases e.
+ job_d->Finish();
+ ASSERT_TRUE(job_e->running());
+ ASSERT_TRUE(job_h->running());
+ // h, e are running.
+ job_e->Finish(); // Releases c.
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+ job_h->Finish();
+
+ Expect("abdfg.h...e..c..");
+}
+
+TEST_F(PrioritizedDispatcherTest, ChangePriority) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', MEDIUM);
+ TestJob* job_c = AddJob('c', HIGHEST);
+ TestJob* job_d = AddJob('d', HIGHEST);
+
+ ASSERT_FALSE(job_b->running());
+ ASSERT_FALSE(job_c->running());
+ job_b->ChangePriority(HIGHEST);
+ job_c->ChangePriority(MEDIUM);
+
+ ASSERT_TRUE(job_a->running());
+ job_a->Finish();
+ ASSERT_TRUE(job_d->running());
+ job_d->Finish();
+ ASSERT_TRUE(job_b->running());
+ job_b->Finish();
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+
+ Expect("a.d.b.c.");
+}
+
+TEST_F(PrioritizedDispatcherTest, Cancel) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', IDLE);
+ TestJob* job_c = AddJob('c', IDLE);
+ TestJob* job_d = AddJob('d', IDLE);
+ TestJob* job_e = AddJob('e', IDLE);
+
+ ASSERT_FALSE(job_b->running());
+ ASSERT_FALSE(job_d->running());
+ job_b->Cancel();
+ job_d->Cancel();
+
+ ASSERT_TRUE(job_a->running());
+ job_a->Finish();
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+ ASSERT_TRUE(job_e->running());
+ job_e->Finish();
+
+ Expect("a.c.e.");
+}
+
+TEST_F(PrioritizedDispatcherTest, Evict) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+
+ TestJob* job_a = AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', LOW);
+ TestJob* job_c = AddJob('c', HIGHEST);
+ TestJob* job_d = AddJob('d', LOW);
+ TestJob* job_e = AddJob('e', HIGHEST);
+
+ EXPECT_EQ(job_b, dispatcher_->EvictOldestLowest());
+ EXPECT_EQ(job_d, dispatcher_->EvictOldestLowest());
+
+ ASSERT_TRUE(job_a->running());
+ job_a->Finish();
+ ASSERT_TRUE(job_c->running());
+ job_c->Finish();
+ ASSERT_TRUE(job_e->running());
+ job_e->Finish();
+
+ Expect("a.c.e.");
+}
+
+TEST_F(PrioritizedDispatcherTest, EvictFromEmpty) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+ EXPECT_TRUE(dispatcher_->EvictOldestLowest() == NULL);
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST_F(PrioritizedDispatcherTest, CancelNull) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+ EXPECT_DEBUG_DEATH(dispatcher_->Cancel(PrioritizedDispatcher::Handle()), "");
+}
+
+TEST_F(PrioritizedDispatcherTest, CancelMissing) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+ AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', IDLE);
+ PrioritizedDispatcher::Handle handle = job_b->handle();
+ ASSERT_FALSE(handle.is_null());
+ dispatcher_->Cancel(handle);
+ EXPECT_DEBUG_DEATH(dispatcher_->Cancel(handle), "");
+}
+
+// TODO(szym): Fix the PriorityQueue::Pointer check to die here.
+// http://crbug.com/130846
+TEST_F(PrioritizedDispatcherTest, DISABLED_CancelIncompatible) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ Prepare(limits);
+ AddJob('a', IDLE);
+ TestJob* job_b = AddJob('b', IDLE);
+ PrioritizedDispatcher::Handle handle = job_b->handle();
+ ASSERT_FALSE(handle.is_null());
+
+ // New dispatcher.
+ Prepare(limits);
+ AddJob('a', IDLE);
+ AddJob('b', IDLE);
+ EXPECT_DEBUG_DEATH(dispatcher_->Cancel(handle), "");
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/priority_queue.h b/chromium/net/base/priority_queue.h
new file mode 100644
index 00000000000..b758ca45dea
--- /dev/null
+++ b/chromium/net/base/priority_queue.h
@@ -0,0 +1,238 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_PRIORITY_QUEUE_H_
+#define NET_BASE_PRIORITY_QUEUE_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+
+#if !defined(NDEBUG)
+#include "base/containers/hash_tables.h"
+#endif
+
+namespace net {
+
+// A simple priority queue. The order of values is by priority and then FIFO.
+// Unlike the std::priority_queue, this implementation allows erasing elements
+// from the queue, and all operations are O(p) time for p priority levels.
+// The queue is agnostic to priority ordering (whether 0 precedes 1).
+// If the highest priority is 0, FirstMin() returns the first in order.
+//
+// In debug-mode, the internal queues store (id, value) pairs where id is used
+// to validate Pointers.
+//
+template<typename T>
+class PriorityQueue : public base::NonThreadSafe {
+ private:
+ // This section is up-front for Pointer only.
+#if !defined(NDEBUG)
+ typedef std::list<std::pair<unsigned, T> > List;
+#else
+ typedef std::list<T> List;
+#endif
+
+ public:
+ typedef uint32 Priority;
+
+ // A pointer to a value stored in the queue. The pointer becomes invalid
+ // when the queue is destroyed or cleared, or the value is erased.
+ class Pointer {
+ public:
+ // Constructs a null pointer.
+ Pointer() : priority_(kNullPriority) {
+#if !defined(NDEBUG)
+ id_ = static_cast<unsigned>(-1);
+#endif
+ }
+
+ Pointer(const Pointer& p) : priority_(p.priority_), iterator_(p.iterator_) {
+#if !defined(NDEBUG)
+ id_ = p.id_;
+#endif
+ }
+
+ Pointer& operator=(const Pointer& p) {
+ // Self-assignment is benign.
+ priority_ = p.priority_;
+ iterator_ = p.iterator_;
+#if !defined(NDEBUG)
+ id_ = p.id_;
+#endif
+ return *this;
+ }
+
+ bool is_null() const { return priority_ == kNullPriority; }
+
+ Priority priority() const { return priority_; }
+
+#if !defined(NDEBUG)
+ const T& value() const { return iterator_->second; }
+#else
+ const T& value() const { return *iterator_; }
+#endif
+
+ // Comparing to Pointer from a different PriorityQueue is undefined.
+ bool Equals(const Pointer& other) const {
+ return (priority_ == other.priority_) && (iterator_ == other.iterator_);
+ }
+
+ void Reset() {
+ *this = Pointer();
+ }
+
+ private:
+ friend class PriorityQueue;
+
+ // Note that we need iterator not const_iterator to pass to List::erase.
+ // When C++0x comes, this could be changed to const_iterator and const could
+ // be added to First, Last, and OldestLowest.
+ typedef typename PriorityQueue::List::iterator ListIterator;
+
+ static const Priority kNullPriority = static_cast<Priority>(-1);
+
+ Pointer(Priority priority, const ListIterator& iterator)
+ : priority_(priority), iterator_(iterator) {
+#if !defined(NDEBUG)
+ id_ = iterator_->first;
+#endif
+ }
+
+ Priority priority_;
+ ListIterator iterator_;
+
+#if !defined(NDEBUG)
+ // Used by the queue to check if a Pointer is valid.
+ unsigned id_;
+#endif
+ };
+
+ // Creates a new queue for |num_priorities|.
+ explicit PriorityQueue(Priority num_priorities)
+ : lists_(num_priorities), size_(0) {
+#if !defined(NDEBUG)
+ next_id_ = 0;
+#endif
+ }
+
+ // Adds |value| with |priority| to the queue. Returns a pointer to the
+ // created element.
+ Pointer Insert(const T& value, Priority priority) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(priority, lists_.size());
+ ++size_;
+ List& list = lists_[priority];
+#if !defined(NDEBUG)
+ unsigned id = next_id_;
+ valid_ids_.insert(id);
+ ++next_id_;
+ return Pointer(priority, list.insert(list.end(),
+ std::make_pair(id, value)));
+#else
+ return Pointer(priority, list.insert(list.end(), value));
+#endif
+ }
+
+ // Removes the value pointed by |pointer| from the queue. All pointers to this
+ // value including |pointer| become invalid.
+ void Erase(const Pointer& pointer) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(pointer.priority_, lists_.size());
+ DCHECK_GT(size_, 0u);
+
+#if !defined(NDEBUG)
+ DCHECK_EQ(1u, valid_ids_.erase(pointer.id_));
+ DCHECK_EQ(pointer.iterator_->first, pointer.id_);
+#endif
+
+ --size_;
+ lists_[pointer.priority_].erase(pointer.iterator_);
+ }
+
+ // Returns a pointer to the first value of minimum priority or a null-pointer
+ // if empty.
+ Pointer FirstMin() {
+ DCHECK(CalledOnValidThread());
+ for (size_t i = 0; i < lists_.size(); ++i) {
+ if (!lists_[i].empty())
+ return Pointer(i, lists_[i].begin());
+ }
+ return Pointer();
+ }
+
+ // Returns a pointer to the last value of minimum priority or a null-pointer
+ // if empty.
+ Pointer LastMin() {
+ DCHECK(CalledOnValidThread());
+ for (size_t i = 0; i < lists_.size(); ++i) {
+ if (!lists_[i].empty())
+ return Pointer(i, --lists_[i].end());
+ }
+ return Pointer();
+ }
+
+ // Returns a pointer to the first value of maximum priority or a null-pointer
+ // if empty.
+ Pointer FirstMax() {
+ DCHECK(CalledOnValidThread());
+ for (size_t i = lists_.size(); i > 0; --i) {
+ size_t index = i - 1;
+ if (!lists_[index].empty())
+ return Pointer(index, lists_[index].begin());
+ }
+ return Pointer();
+ }
+
+ // Returns a pointer to the last value of maximum priority or a null-pointer
+ // if empty.
+ Pointer LastMax() {
+ DCHECK(CalledOnValidThread());
+ for (size_t i = lists_.size(); i > 0; --i) {
+ size_t index = i - 1;
+ if (!lists_[index].empty())
+ return Pointer(index, --lists_[index].end());
+ }
+ return Pointer();
+ }
+
+ // Empties the queue. All pointers become invalid.
+ void Clear() {
+ DCHECK(CalledOnValidThread());
+ for (size_t i = 0; i < lists_.size(); ++i) {
+ lists_[i].clear();
+ }
+#if !defined(NDEBUG)
+ valid_ids_.clear();
+#endif
+ size_ = 0u;
+ }
+
+ // Returns number of queued values.
+ size_t size() const {
+ DCHECK(CalledOnValidThread());
+ return size_;
+ }
+
+ private:
+ typedef std::vector<List> ListVector;
+
+#if !defined(NDEBUG)
+ unsigned next_id_;
+ base::hash_set<unsigned> valid_ids_;
+#endif
+
+ ListVector lists_;
+ size_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(PriorityQueue);
+};
+
+} // namespace net
+
+#endif // NET_BASE_PRIORITY_QUEUE_H_
diff --git a/chromium/net/base/priority_queue_unittest.cc b/chromium/net/base/priority_queue_unittest.cc
new file mode 100644
index 00000000000..c8449bbd3fd
--- /dev/null
+++ b/chromium/net/base/priority_queue_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#include "net/base/priority_queue.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+typedef PriorityQueue<int>::Priority Priority;
+const Priority kPriorities[] = { 2, 1, 2, 0, 4, 3, 1, 4, 0 };
+const Priority kNumPriorities = 5; // max(kPriorities) + 1
+const size_t kNumElements = arraysize(kPriorities);
+const int kFirstMinOrder[kNumElements] = { 3, 8, 1, 6, 0, 2, 5, 4, 7 };
+const int kLastMaxOrder[kNumElements] = { 7, 4, 5, 2, 0, 6, 1, 8, 3 };
+const int kFirstMaxOrder[kNumElements] = { 4, 7, 5, 0, 2, 1, 6, 3, 8 };
+const int kLastMinOrder[kNumElements] = { 8, 3, 6, 1, 2, 0, 5, 7, 4 };
+
+class PriorityQueueTest : public testing::Test {
+ protected:
+ PriorityQueueTest() : queue_(kNumPriorities) {}
+
+ virtual void SetUp() OVERRIDE {
+ CheckEmpty();
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(i, queue_.size());
+ pointers_[i] = queue_.Insert(static_cast<int>(i), kPriorities[i]);
+ }
+ EXPECT_EQ(kNumElements, queue_.size());
+ }
+
+ void CheckEmpty() {
+ EXPECT_EQ(0u, queue_.size());
+ EXPECT_TRUE(queue_.FirstMin().is_null());
+ EXPECT_TRUE(queue_.LastMin().is_null());
+ EXPECT_TRUE(queue_.FirstMax().is_null());
+ EXPECT_TRUE(queue_.LastMax().is_null());
+ }
+
+ PriorityQueue<int> queue_;
+ PriorityQueue<int>::Pointer pointers_[kNumElements];
+};
+
+TEST_F(PriorityQueueTest, AddAndClear) {
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(kPriorities[i], pointers_[i].priority());
+ EXPECT_EQ(static_cast<int>(i), pointers_[i].value());
+ }
+ queue_.Clear();
+ CheckEmpty();
+}
+
+TEST_F(PriorityQueueTest, FirstMinOrder) {
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(kNumElements - i, queue_.size());
+ // Also check Equals.
+ EXPECT_TRUE(queue_.FirstMin().Equals(pointers_[kFirstMinOrder[i]]));
+ EXPECT_EQ(kFirstMinOrder[i], queue_.FirstMin().value());
+ queue_.Erase(queue_.FirstMin());
+ }
+ CheckEmpty();
+}
+
+TEST_F(PriorityQueueTest, LastMinOrder) {
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(kLastMinOrder[i], queue_.LastMin().value());
+ queue_.Erase(queue_.LastMin());
+ }
+ CheckEmpty();
+}
+
+TEST_F(PriorityQueueTest, FirstMaxOrder) {
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(kFirstMaxOrder[i], queue_.FirstMax().value());
+ queue_.Erase(queue_.FirstMax());
+ }
+ CheckEmpty();
+}
+
+TEST_F(PriorityQueueTest, LastMaxOrder) {
+ for (size_t i = 0; i < kNumElements; ++i) {
+ EXPECT_EQ(kLastMaxOrder[i], queue_.LastMax().value());
+ queue_.Erase(queue_.LastMax());
+ }
+ CheckEmpty();
+}
+
+TEST_F(PriorityQueueTest, EraseFromMiddle) {
+ queue_.Erase(pointers_[2]);
+ queue_.Erase(pointers_[3]);
+
+ int expected_order[] = { 8, 1, 6, 0, 5, 4, 7 };
+
+ for (size_t i = 0; i < arraysize(expected_order); ++i) {
+ EXPECT_EQ(expected_order[i], queue_.FirstMin().value());
+ queue_.Erase(queue_.FirstMin());
+ }
+ CheckEmpty();
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/base/privacy_mode.h b/chromium/net/base/privacy_mode.h
new file mode 100644
index 00000000000..082ef26fb2f
--- /dev/null
+++ b/chromium/net/base/privacy_mode.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2013 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_BASE_PRIVACY_MODE_H_
+#define NET_BASE_PRIVACY_MODE_H_
+
+namespace net {
+
+// Privacy Mode is enabled if cookies to particular site are blocked, so
+// Channel ID is disabled on that connection (https or spdy).
+enum PrivacyMode {
+ kPrivacyModeDisabled = 0,
+ kPrivacyModeEnabled = 1,
+};
+
+}; // namespace net
+
+#endif // NET_BASE_PRIVACY_MODE_H_
+
diff --git a/chromium/net/base/rand_callback.h b/chromium/net/base/rand_callback.h
new file mode 100644
index 00000000000..3aae2185d9d
--- /dev/null
+++ b/chromium/net/base/rand_callback.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2011 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_BASE_RAND_CALLBACK_H_
+#define NET_BASE_RAND_CALLBACK_H_
+
+#include "base/callback.h"
+
+namespace net {
+
+typedef base::Callback<int(int, int)> RandIntCallback;
+
+} // namespace net
+
+#endif // NET_BASE_RAND_CALLBACK_H_
diff --git a/chromium/net/base/registry_controlled_domains/OWNERS b/chromium/net/base/registry_controlled_domains/OWNERS
new file mode 100644
index 00000000000..9f8b1799602
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/OWNERS
@@ -0,0 +1,2 @@
+pam@chromium.org
+pkasting@chromium.org
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names.cc b/chromium/net/base/registry_controlled_domains/effective_tld_names.cc
new file mode 100644
index 00000000000..68d0464ef13
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names.cc
@@ -0,0 +1,36030 @@
+/* C++ code produced by gperf version 3.0.3 */
+/* Command-line: gperf -a -L C++ -C -c -o -t -k '*' -NFindDomain -P -K name_offset -D -m 10 effective_tld_names.gperf */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
+#endif
+
+#line 1 "effective_tld_names.gperf"
+
+// 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.
+
+// This file is generated by net/tools/tld_cleanup/.
+// DO NOT MANUALLY EDIT!
+#line 9 "effective_tld_names.gperf"
+struct DomainRule {
+ int name_offset;
+ int type; // flags: 1: exception, 2: wildcard, 4: private
+};
+
+#define TOTAL_KEYWORDS 6103
+#define MIN_WORD_LENGTH 2
+#define MAX_WORD_LENGTH 43
+#define MIN_HASH_VALUE 15
+#define MAX_HASH_VALUE 90773
+/* maximum key range = 90759, duplicates = 0 */
+
+class Perfect_Hash
+{
+private:
+ static inline unsigned int hash (const char *str, unsigned int len);
+public:
+ static const struct DomainRule *FindDomain (const char *str, unsigned int len);
+};
+
+inline unsigned int
+Perfect_Hash::hash (register const char *str, register unsigned int len)
+{
+ static const unsigned int asso_values[] =
+ {
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 175, 27, 4476, 6, 5,
+ 1274, 11, 5, 93, 137, 6, 6, 5, 5, 90774,
+ 21, 5, 189, 5981, 90774, 5, 5, 5, 5, 5,
+ 5, 5, 90774, 21, 10, 8, 18, 17, 16, 7,
+ 15, 13, 11, 90774, 90774, 90774, 90774, 888, 127, 90774,
+ 90774, 90774, 90774, 5, 5, 5, 90774, 670, 742, 10326,
+ 10, 200, 12855, 2916, 941, 425, 261, 964, 592, 18,
+ 461, 6, 872, 2438, 74, 180, 134, 8, 7, 1701,
+ 40, 2147, 23, 656, 14, 506, 6, 398, 5, 1971,
+ 18836, 396, 95, 322, 271, 13922, 9787, 26534, 6705, 28,
+ 20, 318, 21887, 801, 19816, 32348, 18578, 4947, 162, 29168,
+ 30467, 23403, 1115, 14117, 32320, 13, 22934, 91, 12, 25882,
+ 1815, 880, 6, 15905, 820, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774,
+ 90774, 90774, 90774, 90774, 90774, 90774, 90774, 90774
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[42]];
+ /*FALLTHROUGH*/
+ case 42:
+ hval += asso_values[(unsigned char)str[41]];
+ /*FALLTHROUGH*/
+ case 41:
+ hval += asso_values[(unsigned char)str[40]];
+ /*FALLTHROUGH*/
+ case 40:
+ hval += asso_values[(unsigned char)str[39]];
+ /*FALLTHROUGH*/
+ case 39:
+ hval += asso_values[(unsigned char)str[38]];
+ /*FALLTHROUGH*/
+ case 38:
+ hval += asso_values[(unsigned char)str[37]];
+ /*FALLTHROUGH*/
+ case 37:
+ hval += asso_values[(unsigned char)str[36]];
+ /*FALLTHROUGH*/
+ case 36:
+ hval += asso_values[(unsigned char)str[35]];
+ /*FALLTHROUGH*/
+ case 35:
+ hval += asso_values[(unsigned char)str[34]];
+ /*FALLTHROUGH*/
+ case 34:
+ hval += asso_values[(unsigned char)str[33]];
+ /*FALLTHROUGH*/
+ case 33:
+ hval += asso_values[(unsigned char)str[32]];
+ /*FALLTHROUGH*/
+ case 32:
+ hval += asso_values[(unsigned char)str[31]];
+ /*FALLTHROUGH*/
+ case 31:
+ hval += asso_values[(unsigned char)str[30]];
+ /*FALLTHROUGH*/
+ case 30:
+ hval += asso_values[(unsigned char)str[29]];
+ /*FALLTHROUGH*/
+ case 29:
+ hval += asso_values[(unsigned char)str[28]];
+ /*FALLTHROUGH*/
+ case 28:
+ hval += asso_values[(unsigned char)str[27]];
+ /*FALLTHROUGH*/
+ case 27:
+ hval += asso_values[(unsigned char)str[26]];
+ /*FALLTHROUGH*/
+ case 26:
+ hval += asso_values[(unsigned char)str[25]];
+ /*FALLTHROUGH*/
+ case 25:
+ hval += asso_values[(unsigned char)str[24]];
+ /*FALLTHROUGH*/
+ case 24:
+ hval += asso_values[(unsigned char)str[23]];
+ /*FALLTHROUGH*/
+ case 23:
+ hval += asso_values[(unsigned char)str[22]];
+ /*FALLTHROUGH*/
+ case 22:
+ hval += asso_values[(unsigned char)str[21]];
+ /*FALLTHROUGH*/
+ case 21:
+ hval += asso_values[(unsigned char)str[20]];
+ /*FALLTHROUGH*/
+ case 20:
+ hval += asso_values[(unsigned char)str[19]];
+ /*FALLTHROUGH*/
+ case 19:
+ hval += asso_values[(unsigned char)str[18]];
+ /*FALLTHROUGH*/
+ case 18:
+ hval += asso_values[(unsigned char)str[17]];
+ /*FALLTHROUGH*/
+ case 17:
+ hval += asso_values[(unsigned char)str[16]];
+ /*FALLTHROUGH*/
+ case 16:
+ hval += asso_values[(unsigned char)str[15]];
+ /*FALLTHROUGH*/
+ case 15:
+ hval += asso_values[(unsigned char)str[14]];
+ /*FALLTHROUGH*/
+ case 14:
+ hval += asso_values[(unsigned char)str[13]];
+ /*FALLTHROUGH*/
+ case 13:
+ hval += asso_values[(unsigned char)str[12]];
+ /*FALLTHROUGH*/
+ case 12:
+ hval += asso_values[(unsigned char)str[11]+1];
+ /*FALLTHROUGH*/
+ case 11:
+ hval += asso_values[(unsigned char)str[10]];
+ /*FALLTHROUGH*/
+ case 10:
+ hval += asso_values[(unsigned char)str[9]];
+ /*FALLTHROUGH*/
+ case 9:
+ hval += asso_values[(unsigned char)str[8]];
+ /*FALLTHROUGH*/
+ case 8:
+ hval += asso_values[(unsigned char)str[7]+1];
+ /*FALLTHROUGH*/
+ case 7:
+ hval += asso_values[(unsigned char)str[6]+8];
+ /*FALLTHROUGH*/
+ case 6:
+ hval += asso_values[(unsigned char)str[5]+17];
+ /*FALLTHROUGH*/
+ case 5:
+ hval += asso_values[(unsigned char)str[4]+42];
+ /*FALLTHROUGH*/
+ case 4:
+ hval += asso_values[(unsigned char)str[3]+15];
+ /*FALLTHROUGH*/
+ case 3:
+ hval += asso_values[(unsigned char)str[2]];
+ /*FALLTHROUGH*/
+ case 2:
+ hval += asso_values[(unsigned char)str[1]];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]+25];
+ break;
+ }
+ return hval;
+}
+
+struct stringpool_t
+ {
+ char stringpool_str0[sizeof("gu")];
+ char stringpool_str1[sizeof("eu")];
+ char stringpool_str2[sizeof("gd")];
+ char stringpool_str3[sizeof("gov")];
+ char stringpool_str4[sizeof("co")];
+ char stringpool_str5[sizeof("cv")];
+ char stringpool_str6[sizeof("cu")];
+ char stringpool_str7[sizeof("gm")];
+ char stringpool_str8[sizeof("cd")];
+ char stringpool_str9[sizeof("edu")];
+ char stringpool_str10[sizeof("so")];
+ char stringpool_str11[sizeof("sv")];
+ char stringpool_str12[sizeof("su")];
+ char stringpool_str13[sizeof("ao")];
+ char stringpool_str14[sizeof("sd")];
+ char stringpool_str15[sizeof("au")];
+ char stringpool_str16[sizeof("cm")];
+ char stringpool_str17[sizeof("ad")];
+ char stringpool_str18[sizeof("ro")];
+ char stringpool_str19[sizeof("ru")];
+ char stringpool_str20[sizeof("cz")];
+ char stringpool_str21[sizeof("sm")];
+ char stringpool_str22[sizeof("com")];
+ char stringpool_str23[sizeof("am")];
+ char stringpool_str24[sizeof("sz")];
+ char stringpool_str25[sizeof("gov.to")];
+ char stringpool_str26[sizeof("gov.tm")];
+ char stringpool_str27[sizeof("az")];
+ char stringpool_str28[sizeof("edu.to")];
+ char stringpool_str29[sizeof("edu.tm")];
+ char stringpool_str30[sizeof("gov.bo")];
+ char stringpool_str31[sizeof("gov.bm")];
+ char stringpool_str32[sizeof("cx")];
+ char stringpool_str33[sizeof("edu.bo")];
+ char stringpool_str34[sizeof("edu.bm")];
+ char stringpool_str35[sizeof("sx")];
+ char stringpool_str36[sizeof("gov.ae")];
+ char stringpool_str37[sizeof("ax")];
+ char stringpool_str38[sizeof("com.to")];
+ char stringpool_str39[sizeof("com.tm")];
+ char stringpool_str40[sizeof("com.bo")];
+ char stringpool_str41[sizeof("com.bm")];
+ char stringpool_str42[sizeof("gov.bz")];
+ char stringpool_str43[sizeof("edu.bi")];
+ char stringpool_str44[sizeof("gr")];
+ char stringpool_str45[sizeof("er")];
+ char stringpool_str46[sizeof("edu.bz")];
+ char stringpool_str47[sizeof("gov.az")];
+ char stringpool_str48[sizeof("cr")];
+ char stringpool_str49[sizeof("edu.az")];
+ char stringpool_str50[sizeof("com.bi")];
+ char stringpool_str51[sizeof("co.ma")];
+ char stringpool_str52[sizeof("go.it")];
+ char stringpool_str53[sizeof("sr")];
+ char stringpool_str54[sizeof("com.bz")];
+ char stringpool_str55[sizeof("ar")];
+ char stringpool_str56[sizeof("com.ai")];
+ char stringpool_str57[sizeof("co.it")];
+ char stringpool_str58[sizeof("com.az")];
+ char stringpool_str59[sizeof("so.it")];
+ char stringpool_str60[sizeof("sv.it")];
+ char stringpool_str61[sizeof("ao.it")];
+ char stringpool_str62[sizeof("av.it")];
+ char stringpool_str63[sizeof("km")];
+ char stringpool_str64[sizeof("gov.qa")];
+ char stringpool_str65[sizeof("com.ag")];
+ char stringpool_str66[sizeof("ro.it")];
+ char stringpool_str67[sizeof("kz")];
+ char stringpool_str68[sizeof("cz.it")];
+ char stringpool_str69[sizeof("edu.qa")];
+ char stringpool_str70[sizeof("gov.ba")];
+ char stringpool_str71[sizeof("gov.sd")];
+ char stringpool_str72[sizeof("edu.ba")];
+ char stringpool_str73[sizeof("rm.it")];
+ char stringpool_str74[sizeof("edu.sd")];
+ char stringpool_str75[sizeof("com.qa")];
+ char stringpool_str76[sizeof("gt")];
+ char stringpool_str77[sizeof("et")];
+ char stringpool_str78[sizeof("com.ba")];
+ char stringpool_str79[sizeof("gov.bs")];
+ char stringpool_str80[sizeof("com.so")];
+ char stringpool_str81[sizeof("com.sd")];
+ char stringpool_str82[sizeof("edu.bs")];
+ char stringpool_str83[sizeof("gov.as")];
+ char stringpool_str84[sizeof("co.ca")];
+ char stringpool_str85[sizeof("st")];
+ char stringpool_str86[sizeof("at")];
+ char stringpool_str87[sizeof("gov.sg")];
+ char stringpool_str88[sizeof("gr.it")];
+ char stringpool_str89[sizeof("com.bs")];
+ char stringpool_str90[sizeof("edu.sg")];
+ char stringpool_str91[sizeof("kr")];
+ char stringpool_str92[sizeof("cr.it")];
+ char stringpool_str93[sizeof("co.ua")];
+ char stringpool_str94[sizeof("cv.ua")];
+ char stringpool_str95[sizeof("sr.it")];
+ char stringpool_str96[sizeof("com.sg")];
+ char stringpool_str97[sizeof("ar.it")];
+ char stringpool_str98[sizeof("gs")];
+ char stringpool_str99[sizeof("es")];
+ char stringpool_str100[sizeof("rv.ua")];
+ char stringpool_str101[sizeof("gov.ac")];
+ char stringpool_str102[sizeof("sm.ua")];
+ char stringpool_str103[sizeof("gov.sa")];
+ char stringpool_str104[sizeof("gov.jo")];
+ char stringpool_str105[sizeof("edu.ac")];
+ char stringpool_str106[sizeof("gov.je")];
+ char stringpool_str107[sizeof("edu.sa")];
+ char stringpool_str108[sizeof("edu.jo")];
+ char stringpool_str109[sizeof("as")];
+ char stringpool_str110[sizeof("ge")];
+ char stringpool_str111[sizeof("ee")];
+ char stringpool_str112[sizeof("rs")];
+ char stringpool_str113[sizeof("com.ac")];
+ char stringpool_str114[sizeof("com.sa")];
+ char stringpool_str115[sizeof("como.it")];
+ char stringpool_str116[sizeof("com.jo")];
+ char stringpool_str117[sizeof("se")];
+ char stringpool_str118[sizeof("ae")];
+ char stringpool_str119[sizeof("gov.bb")];
+ char stringpool_str120[sizeof("re")];
+ char stringpool_str121[sizeof("gu.us")];
+ char stringpool_str122[sizeof("ct.it")];
+ char stringpool_str123[sizeof("edu.bb")];
+ char stringpool_str124[sizeof("co.us")];
+ char stringpool_str125[sizeof("at.it")];
+ char stringpool_str126[sizeof("cr.ua")];
+ char stringpool_str127[sizeof("sd.us")];
+ char stringpool_str128[sizeof("com.bb")];
+ char stringpool_str129[sizeof("kr.it")];
+ char stringpool_str130[sizeof("gov.sc")];
+ char stringpool_str131[sizeof("kv.ua")];
+ char stringpool_str132[sizeof("edu.sc")];
+ char stringpool_str133[sizeof("az.us")];
+ char stringpool_str134[sizeof("km.ua")];
+ char stringpool_str135[sizeof("com.sc")];
+ char stringpool_str136[sizeof("cs.it")];
+ char stringpool_str137[sizeof("mo")];
+ char stringpool_str138[sizeof("mv")];
+ char stringpool_str139[sizeof("mu")];
+ char stringpool_str140[sizeof("md")];
+ char stringpool_str141[sizeof("ss.it")];
+ char stringpool_str142[sizeof("ge.it")];
+ char stringpool_str143[sizeof("mm")];
+ char stringpool_str144[sizeof("mz")];
+ char stringpool_str145[sizeof("ke")];
+ char stringpool_str146[sizeof("ce.it")];
+ char stringpool_str147[sizeof("gov.sb")];
+ char stringpool_str148[sizeof("edu.sb")];
+ char stringpool_str149[sizeof("aero")];
+ char stringpool_str150[sizeof("re.it")];
+ char stringpool_str151[sizeof("mx")];
+ char stringpool_str152[sizeof("ar.us")];
+ char stringpool_str153[sizeof("com.sb")];
+ char stringpool_str154[sizeof("kr.ua")];
+ char stringpool_str155[sizeof("to")];
+ char stringpool_str156[sizeof("tv")];
+ char stringpool_str157[sizeof("gov.au")];
+ char stringpool_str158[sizeof("educ.ar")];
+ char stringpool_str159[sizeof("td")];
+ char stringpool_str160[sizeof("lv")];
+ char stringpool_str161[sizeof("lu")];
+ char stringpool_str162[sizeof("edu.au")];
+ char stringpool_str163[sizeof("tm")];
+ char stringpool_str164[sizeof("tz")];
+ char stringpool_str165[sizeof("mr")];
+ char stringpool_str166[sizeof("com.au")];
+ char stringpool_str167[sizeof("g.se")];
+ char stringpool_str168[sizeof("e.se")];
+ char stringpool_str169[sizeof("gov.co")];
+ char stringpool_str170[sizeof("gov.cm")];
+ char stringpool_str171[sizeof("gov.cd")];
+ char stringpool_str172[sizeof("rome.it")];
+ char stringpool_str173[sizeof("edu.co")];
+ char stringpool_str174[sizeof("c.se")];
+ char stringpool_str175[sizeof("mo.it")];
+ char stringpool_str176[sizeof("gov.tt")];
+ char stringpool_str177[sizeof("s.se")];
+ char stringpool_str178[sizeof("ct.us")];
+ char stringpool_str179[sizeof("a.se")];
+ char stringpool_str180[sizeof("edu.tt")];
+ char stringpool_str181[sizeof("gov.bt")];
+ char stringpool_str182[sizeof("com.co")];
+ char stringpool_str183[sizeof("r.se")];
+ char stringpool_str184[sizeof("edu.ci")];
+ char stringpool_str185[sizeof("edu.bt")];
+ char stringpool_str186[sizeof("com.tt")];
+ char stringpool_str187[sizeof("com.ci")];
+ char stringpool_str188[sizeof("com.bt")];
+ char stringpool_str189[sizeof("tr")];
+ char stringpool_str190[sizeof("lr")];
+ char stringpool_str191[sizeof("jo")];
+ char stringpool_str192[sizeof("fo")];
+ char stringpool_str193[sizeof("mt")];
+ char stringpool_str194[sizeof("to.it")];
+ char stringpool_str195[sizeof("tv.it")];
+ char stringpool_str196[sizeof("lo.it")];
+ char stringpool_str197[sizeof("lu.it")];
+ char stringpool_str198[sizeof("jm")];
+ char stringpool_str199[sizeof("fm")];
+ char stringpool_str200[sizeof("as.us")];
+ char stringpool_str201[sizeof("coop")];
+ char stringpool_str202[sizeof("ks.ua")];
+ char stringpool_str203[sizeof("gi")];
+ char stringpool_str204[sizeof("gov.tn")];
+ char stringpool_str205[sizeof("k.se")];
+ char stringpool_str206[sizeof("ci")];
+ char stringpool_str207[sizeof("gov.st")];
+ char stringpool_str208[sizeof("gov.br")];
+ char stringpool_str209[sizeof("si")];
+ char stringpool_str210[sizeof("edu.st")];
+ char stringpool_str211[sizeof("aero.mv")];
+ char stringpool_str212[sizeof("ai")];
+ char stringpool_str213[sizeof("edu.br")];
+ char stringpool_str214[sizeof("ms")];
+ char stringpool_str215[sizeof("tt")];
+ char stringpool_str216[sizeof("lt")];
+ char stringpool_str217[sizeof("com.tn")];
+ char stringpool_str218[sizeof("co.tt")];
+ char stringpool_str219[sizeof("edu.an")];
+ char stringpool_str220[sizeof("com.st")];
+ char stringpool_str221[sizeof("com.br")];
+ char stringpool_str222[sizeof("adv.br")];
+ char stringpool_str223[sizeof("gn")];
+ char stringpool_str224[sizeof("me")];
+ char stringpool_str225[sizeof("fr")];
+ char stringpool_str226[sizeof("com.an")];
+ char stringpool_str227[sizeof("tr.it")];
+ char stringpool_str228[sizeof("cn")];
+ char stringpool_str229[sizeof("adm.br")];
+ char stringpool_str230[sizeof("sn")];
+ char stringpool_str231[sizeof("lv.ua")];
+ char stringpool_str232[sizeof("an")];
+ char stringpool_str233[sizeof("mt.it")];
+ char stringpool_str234[sizeof("ks.us")];
+ char stringpool_str235[sizeof("mo.us")];
+ char stringpool_str236[sizeof("gouv.km")];
+ char stringpool_str237[sizeof("md.us")];
+ char stringpool_str238[sizeof("fm.it")];
+ char stringpool_str239[sizeof("ls")];
+ char stringpool_str240[sizeof("do")];
+ char stringpool_str241[sizeof("ki")];
+ char stringpool_str242[sizeof("ci.it")];
+ char stringpool_str243[sizeof("edu.sn")];
+ char stringpool_str244[sizeof("dm")];
+ char stringpool_str245[sizeof("srv.br")];
+ char stringpool_str246[sizeof("si.it")];
+ char stringpool_str247[sizeof("dz")];
+ char stringpool_str248[sizeof("ms.it")];
+ char stringpool_str249[sizeof("ri.it")];
+ char stringpool_str250[sizeof("com.sn")];
+ char stringpool_str251[sizeof("lt.it")];
+ char stringpool_str252[sizeof("asso.km")];
+ char stringpool_str253[sizeof("gov.tl")];
+ char stringpool_str254[sizeof("asti.it")];
+ char stringpool_str255[sizeof("lodi.it")];
+ char stringpool_str256[sizeof("en.it")];
+ char stringpool_str257[sizeof("me.it")];
+ char stringpool_str258[sizeof("fr.it")];
+ char stringpool_str259[sizeof("kn")];
+ char stringpool_str260[sizeof("cn.it")];
+ char stringpool_str261[sizeof("gov.al")];
+ char stringpool_str262[sizeof("an.it")];
+ char stringpool_str263[sizeof("edu.al")];
+ char stringpool_str264[sizeof("coop.mv")];
+ char stringpool_str265[sizeof("rn.it")];
+ char stringpool_str266[sizeof("tx.us")];
+ char stringpool_str267[sizeof("gouv.sn")];
+ char stringpool_str268[sizeof("coop.km")];
+ char stringpool_str269[sizeof("tj")];
+ char stringpool_str270[sizeof("ts.it")];
+ char stringpool_str271[sizeof("com.al")];
+ char stringpool_str272[sizeof("co.na")];
+ char stringpool_str273[sizeof("ato.br")];
+ char stringpool_str274[sizeof("med.sd")];
+ char stringpool_str275[sizeof("je")];
+ char stringpool_str276[sizeof("gl")];
+ char stringpool_str277[sizeof("go.tj")];
+ char stringpool_str278[sizeof("te.it")];
+ char stringpool_str279[sizeof("asso.gp")];
+ char stringpool_str280[sizeof("le.it")];
+ char stringpool_str281[sizeof("cl")];
+ char stringpool_str282[sizeof("co.tj")];
+ char stringpool_str283[sizeof("lt.ua")];
+ char stringpool_str284[sizeof("sl")];
+ char stringpool_str285[sizeof("gouv.ml")];
+ char stringpool_str286[sizeof("m.se")];
+ char stringpool_str287[sizeof("al")];
+ char stringpool_str288[sizeof("gov.cu")];
+ char stringpool_str289[sizeof("mt.us")];
+ char stringpool_str290[sizeof("edu.cu")];
+ char stringpool_str291[sizeof("gov.sl")];
+ char stringpool_str292[sizeof("cn.ua")];
+ char stringpool_str293[sizeof("edu.sl")];
+ char stringpool_str294[sizeof("com.cu")];
+ char stringpool_str295[sizeof("com.sl")];
+ char stringpool_str296[sizeof("med.sa")];
+ char stringpool_str297[sizeof("art.br")];
+ char stringpool_str298[sizeof("fj")];
+ char stringpool_str299[sizeof("t.se")];
+ char stringpool_str300[sizeof("bo")];
+ char stringpool_str301[sizeof("tromso.no")];
+ char stringpool_str302[sizeof("l.se")];
+ char stringpool_str303[sizeof("bd")];
+ char stringpool_str304[sizeof("ms.us")];
+ char stringpool_str305[sizeof("ri.us")];
+ char stringpool_str306[sizeof("te.ua")];
+ char stringpool_str307[sizeof("bm")];
+ char stringpool_str308[sizeof("ga")];
+ char stringpool_str309[sizeof("bz")];
+ char stringpool_str310[sizeof("fe.it")];
+ char stringpool_str311[sizeof("ca")];
+ char stringpool_str312[sizeof("me.us")];
+ char stringpool_str313[sizeof("cl.it")];
+ char stringpool_str314[sizeof("sa")];
+ char stringpool_str315[sizeof("gov.tj")];
+ char stringpool_str316[sizeof("al.it")];
+ char stringpool_str317[sizeof("tm.mc")];
+ char stringpool_str318[sizeof("edu.tj")];
+ char stringpool_str319[sizeof("de")];
+ char stringpool_str320[sizeof("com.tj")];
+ char stringpool_str321[sizeof("gouv.bj")];
+ char stringpool_str322[sizeof("art.sn")];
+ char stringpool_str323[sizeof("br")];
+ char stringpool_str324[sizeof("mn")];
+ char stringpool_str325[sizeof("tromsa.no")];
+ char stringpool_str326[sizeof("f.se")];
+ char stringpool_str327[sizeof("gov.cn")];
+ char stringpool_str328[sizeof("bo.it")];
+ char stringpool_str329[sizeof("li")];
+ char stringpool_str330[sizeof("edu.cn")];
+ char stringpool_str331[sizeof("coop.br")];
+ char stringpool_str332[sizeof("bz.it")];
+ char stringpool_str333[sizeof("sb")];
+ char stringpool_str334[sizeof("com.cn")];
+ char stringpool_str335[sizeof("ca.it")];
+ char stringpool_str336[sizeof("dj")];
+ char stringpool_str337[sizeof("aero.tt")];
+ char stringpool_str338[sizeof("sa.it")];
+ char stringpool_str339[sizeof("asso.bj")];
+ char stringpool_str340[sizeof("mi.it")];
+ char stringpool_str341[sizeof("tn")];
+ char stringpool_str342[sizeof("ra.it")];
+ char stringpool_str343[sizeof("g.bg")];
+ char stringpool_str344[sizeof("e.bg")];
+ char stringpool_str345[sizeof("6.bg")];
+ char stringpool_str346[sizeof("2.bg")];
+ char stringpool_str347[sizeof("gob.bo")];
+ char stringpool_str348[sizeof("1.bg")];
+ char stringpool_str349[sizeof("9.bg")];
+ char stringpool_str350[sizeof("bt")];
+ char stringpool_str351[sizeof("8.bg")];
+ char stringpool_str352[sizeof("c.bg")];
+ char stringpool_str353[sizeof("7.bg")];
+ char stringpool_str354[sizeof("5.bg")];
+ char stringpool_str355[sizeof("4.bg")];
+ char stringpool_str356[sizeof("3.bg")];
+ char stringpool_str357[sizeof("co.ls")];
+ char stringpool_str358[sizeof("s.bg")];
+ char stringpool_str359[sizeof("0.bg")];
+ char stringpool_str360[sizeof("a.bg")];
+ char stringpool_str361[sizeof("r.bg")];
+ char stringpool_str362[sizeof("vu")];
+ char stringpool_str363[sizeof("br.it")];
+ char stringpool_str364[sizeof("mn.it")];
+ char stringpool_str365[sizeof("cat")];
+ char stringpool_str366[sizeof("fi")];
+ char stringpool_str367[sizeof("tur.br")];
+ char stringpool_str368[sizeof("trd.br")];
+ char stringpool_str369[sizeof("li.it")];
+ char stringpool_str370[sizeof("al.us")];
+ char stringpool_str371[sizeof("gov.ee")];
+ char stringpool_str372[sizeof("bs")];
+ char stringpool_str373[sizeof("cb.it")];
+ char stringpool_str374[sizeof("edu.ee")];
+ char stringpool_str375[sizeof("d.se")];
+ char stringpool_str376[sizeof("gov.cl")];
+ char stringpool_str377[sizeof("com.ee")];
+ char stringpool_str378[sizeof("be")];
+ char stringpool_str379[sizeof("tn.it")];
+ char stringpool_str380[sizeof("ml")];
+ char stringpool_str381[sizeof("co.me")];
+ char stringpool_str382[sizeof("gov.eg")];
+ char stringpool_str383[sizeof("ruovat.no")];
+ char stringpool_str384[sizeof("bt.it")];
+ char stringpool_str385[sizeof("k.bg")];
+ char stringpool_str386[sizeof("edu.eg")];
+ char stringpool_str387[sizeof("mx.na")];
+ char stringpool_str388[sizeof("gp")];
+ char stringpool_str389[sizeof("cim.br")];
+ char stringpool_str390[sizeof("mus.br")];
+ char stringpool_str391[sizeof("com.eg")];
+ char stringpool_str392[sizeof("tv.na")];
+ char stringpool_str393[sizeof("vv.it")];
+ char stringpool_str394[sizeof("ga.us")];
+ char stringpool_str395[sizeof("coop.tt")];
+ char stringpool_str396[sizeof("ab.ca")];
+ char stringpool_str397[sizeof("ca.us")];
+ char stringpool_str398[sizeof("jor.br")];
+ char stringpool_str399[sizeof("fi.it")];
+ char stringpool_str400[sizeof("med.br")];
+ char stringpool_str401[sizeof("tl")];
+ char stringpool_str402[sizeof("mi.us")];
+ char stringpool_str403[sizeof("sb.ua")];
+ char stringpool_str404[sizeof("rnu.tn")];
+ char stringpool_str405[sizeof("bj")];
+ char stringpool_str406[sizeof("bs.it")];
+ char stringpool_str407[sizeof("de.us")];
+ char stringpool_str408[sizeof("gv.at")];
+ char stringpool_str409[sizeof("edu.es")];
+ char stringpool_str410[sizeof("co.at")];
+ char stringpool_str411[sizeof("asso.re")];
+ char stringpool_str412[sizeof("ma")];
+ char stringpool_str413[sizeof("kurgan.ru")];
+ char stringpool_str414[sizeof("com.es")];
+ char stringpool_str415[sizeof("co.mw")];
+ char stringpool_str416[sizeof("gh")];
+ char stringpool_str417[sizeof("mn.us")];
+ char stringpool_str418[sizeof("teo.br")];
+ char stringpool_str419[sizeof("ch")];
+ char stringpool_str420[sizeof("vr.it")];
+ char stringpool_str421[sizeof("coop.mw")];
+ char stringpool_str422[sizeof("sh")];
+ char stringpool_str423[sizeof("gov.ec")];
+ char stringpool_str424[sizeof("fot.br")];
+ char stringpool_str425[sizeof("co.uz")];
+ char stringpool_str426[sizeof("kp")];
+ char stringpool_str427[sizeof("edu.ec")];
+ char stringpool_str428[sizeof("crew.aero")];
+ char stringpool_str429[sizeof("asn.au")];
+ char stringpool_str430[sizeof("sp.it")];
+ char stringpool_str431[sizeof("ap.it")];
+ char stringpool_str432[sizeof("ck")];
+ char stringpool_str433[sizeof("com.ec")];
+ char stringpool_str434[sizeof("sk")];
+ char stringpool_str435[sizeof("eti.br")];
+ char stringpool_str436[sizeof("la")];
+ char stringpool_str437[sizeof("tn.us")];
+ char stringpool_str438[sizeof("b.se")];
+ char stringpool_str439[sizeof("ve")];
+ char stringpool_str440[sizeof("gov.ws")];
+ char stringpool_str441[sizeof("edu.ws")];
+ char stringpool_str442[sizeof("jus.br")];
+ char stringpool_str443[sizeof("vt.it")];
+ char stringpool_str444[sizeof("com.ws")];
+ char stringpool_str445[sizeof("co.lc")];
+ char stringpool_str446[sizeof("cnt.br")];
+ char stringpool_str447[sizeof("kh")];
+ char stringpool_str448[sizeof("ch.it")];
+ char stringpool_str449[sizeof("m.bg")];
+ char stringpool_str450[sizeof("riik.ee")];
+ char stringpool_str451[sizeof("mobi")];
+ char stringpool_str452[sizeof("vs.it")];
+ char stringpool_str453[sizeof("lb")];
+ char stringpool_str454[sizeof("ens.tn")];
+ char stringpool_str455[sizeof("ta.it")];
+ char stringpool_str456[sizeof("firm.in")];
+ char stringpool_str457[sizeof("andebu.no")];
+ char stringpool_str458[sizeof("bi")];
+ char stringpool_str459[sizeof("ve.it")];
+ char stringpool_str460[sizeof("rns.tn")];
+ char stringpool_str461[sizeof("roma.it")];
+ char stringpool_str462[sizeof("mb.it")];
+ char stringpool_str463[sizeof("t.bg")];
+ char stringpool_str464[sizeof("l.bg")];
+ char stringpool_str465[sizeof("biz")];
+ char stringpool_str466[sizeof("bmd.br")];
+ char stringpool_str467[sizeof("tel")];
+ char stringpool_str468[sizeof("sk.ca")];
+ char stringpool_str469[sizeof("bn")];
+ char stringpool_str470[sizeof("dn.ua")];
+ char stringpool_str471[sizeof("jeju.kr")];
+ char stringpool_str472[sizeof("ck.ua")];
+ char stringpool_str473[sizeof("conf.lv")];
+ char stringpool_str474[sizeof("fst.br")];
+ char stringpool_str475[sizeof("mp")];
+ char stringpool_str476[sizeof("dr.na")];
+ char stringpool_str477[sizeof("mb.ca")];
+ char stringpool_str478[sizeof("vt.us")];
+ char stringpool_str479[sizeof("ma.us")];
+ char stringpool_str480[sizeof("bi.it")];
+ char stringpool_str481[sizeof("biz.az")];
+ char stringpool_str482[sizeof("co.ve")];
+ char stringpool_str483[sizeof("j.bg")];
+ char stringpool_str484[sizeof("f.bg")];
+ char stringpool_str485[sizeof("gov.ng")];
+ char stringpool_str486[sizeof("edu.ng")];
+ char stringpool_str487[sizeof("kh.ua")];
+ char stringpool_str488[sizeof("komi.ru")];
+ char stringpool_str489[sizeof("bn.it")];
+ char stringpool_str490[sizeof("conf.au")];
+ char stringpool_str491[sizeof("com.ng")];
+ char stringpool_str492[sizeof("ak.us")];
+ char stringpool_str493[sizeof("co.in")];
+ char stringpool_str494[sizeof("fl.us")];
+ char stringpool_str495[sizeof("la.us")];
+ char stringpool_str496[sizeof("mh")];
+ char stringpool_str497[sizeof("ltd.co.im")];
+ char stringpool_str498[sizeof("vi")];
+ char stringpool_str499[sizeof("gd.cn")];
+ char stringpool_str500[sizeof("mk")];
+ char stringpool_str501[sizeof("com.na")];
+ char stringpool_str502[sizeof("gz.cn")];
+ char stringpool_str503[sizeof("ca.na")];
+ char stringpool_str504[sizeof("sd.cn")];
+ char stringpool_str505[sizeof("go.tz")];
+ char stringpool_str506[sizeof("th")];
+ char stringpool_str507[sizeof("vn")];
+ char stringpool_str508[sizeof("gx.cn")];
+ char stringpool_str509[sizeof("co.tz")];
+ char stringpool_str510[sizeof("gouv.rw")];
+ char stringpool_str511[sizeof("jp")];
+ char stringpool_str512[sizeof("mobi.gp")];
+ char stringpool_str513[sizeof("tp.it")];
+ char stringpool_str514[sizeof("sx.cn")];
+ char stringpool_str515[sizeof("tk")];
+ char stringpool_str516[sizeof("d.bg")];
+ char stringpool_str517[sizeof("lk")];
+ char stringpool_str518[sizeof("mil")];
+ char stringpool_str519[sizeof("fnd.br")];
+ char stringpool_str520[sizeof("med.ee")];
+ char stringpool_str521[sizeof("vi.it")];
+ char stringpool_str522[sizeof("biz.bb")];
+ char stringpool_str523[sizeof("mil.to")];
+ char stringpool_str524[sizeof("mil.tm")];
+ char stringpool_str525[sizeof("go.pw")];
+ char stringpool_str526[sizeof("emp.br")];
+ char stringpool_str527[sizeof("mil.bo")];
+ char stringpool_str528[sizeof("ed.pw")];
+ char stringpool_str529[sizeof("eun.eg")];
+ char stringpool_str530[sizeof("ba")];
+ char stringpool_str531[sizeof("co.pw")];
+ char stringpool_str532[sizeof("bl.it")];
+ char stringpool_str533[sizeof("mil.ae")];
+ char stringpool_str534[sizeof("mil.tz")];
+ char stringpool_str535[sizeof("flog.br")];
+ char stringpool_str536[sizeof("eidfjord.no")];
+ char stringpool_str537[sizeof("res.aero")];
+ char stringpool_str538[sizeof("mil.az")];
+ char stringpool_str539[sizeof("fk")];
+ char stringpool_str540[sizeof("mil.qa")];
+ char stringpool_str541[sizeof("mk.ua")];
+ char stringpool_str542[sizeof("sirdal.no")];
+ char stringpool_str543[sizeof("mil.ba")];
+ char stringpool_str544[sizeof("bb")];
+ char stringpool_str545[sizeof("taxi.br")];
+ char stringpool_str546[sizeof("gs.cn")];
+ char stringpool_str547[sizeof("ba.it")];
+ char stringpool_str548[sizeof("vn.ua")];
+ char stringpool_str549[sizeof("rnrt.tn")];
+ char stringpool_str550[sizeof("med.ec")];
+ char stringpool_str551[sizeof("b.bg")];
+ char stringpool_str552[sizeof("vi.us")];
+ char stringpool_str553[sizeof("biz.tt")];
+ char stringpool_str554[sizeof("fhv.se")];
+ char stringpool_str555[sizeof("mil.ac")];
+ char stringpool_str556[sizeof("biz.at")];
+ char stringpool_str557[sizeof("mil.jo")];
+ char stringpool_str558[sizeof("lerdal.no")];
+ char stringpool_str559[sizeof("dk")];
+ char stringpool_str560[sizeof("va")];
+ char stringpool_str561[sizeof("lom.no")];
+ char stringpool_str562[sizeof("tas.au")];
+ char stringpool_str563[sizeof("taxi.aero")];
+ char stringpool_str564[sizeof("esp.br")];
+ char stringpool_str565[sizeof("suldal.no")];
+ char stringpool_str566[sizeof("asso.mc")];
+ char stringpool_str567[sizeof("alvdal.no")];
+ char stringpool_str568[sizeof("frog.museum")];
+ char stringpool_str569[sizeof("skodje.no")];
+ char stringpool_str570[sizeof("mo.cn")];
+ char stringpool_str571[sizeof("gov.nc.tr")];
+ char stringpool_str572[sizeof("mat.br")];
+ char stringpool_str573[sizeof("asia")];
+ char stringpool_str574[sizeof("rollag.no")];
+ char stringpool_str575[sizeof("c.la")];
+ char stringpool_str576[sizeof("bio.br")];
+ char stringpool_str577[sizeof("antiques.museum")];
+ char stringpool_str578[sizeof("eu.int")];
+ char stringpool_str579[sizeof("mobi.tt")];
+ char stringpool_str580[sizeof("dp.ua")];
+ char stringpool_str581[sizeof("gov.nr")];
+ char stringpool_str582[sizeof("lel.br")];
+ char stringpool_str583[sizeof("edu.nr")];
+ char stringpool_str584[sizeof("va.it")];
+ char stringpool_str585[sizeof("com.nr")];
+ char stringpool_str586[sizeof("assisi.museum")];
+ char stringpool_str587[sizeof("vet.br")];
+ char stringpool_str588[sizeof("co.pn")];
+ char stringpool_str589[sizeof("far.br")];
+ char stringpool_str590[sizeof("eid.no")];
+ char stringpool_str591[sizeof("tv.tz")];
+ char stringpool_str592[sizeof("v.bg")];
+ char stringpool_str593[sizeof("gob.cl")];
+ char stringpool_str594[sizeof("surnadal.no")];
+ char stringpool_str595[sizeof("blog.br")];
+ char stringpool_str596[sizeof("bh")];
+ char stringpool_str597[sizeof("bari.it")];
+ char stringpool_str598[sizeof("mil.co")];
+ char stringpool_str599[sizeof("test.tj")];
+ char stringpool_str600[sizeof("vb.it")];
+ char stringpool_str601[sizeof("tmp.br")];
+ char stringpool_str602[sizeof("asmatart.museum")];
+ char stringpool_str603[sizeof("show.aero")];
+ char stringpool_str604[sizeof("trento.it")];
+ char stringpool_str605[sizeof("jx.cn")];
+ char stringpool_str606[sizeof("gob.es")];
+ char stringpool_str607[sizeof("lindas.no")];
+ char stringpool_str608[sizeof("kvafjord.no")];
+ char stringpool_str609[sizeof("fuel.aero")];
+ char stringpool_str610[sizeof("game.tw")];
+ char stringpool_str611[sizeof("snoasa.no")];
+ char stringpool_str612[sizeof("va.us")];
+ char stringpool_str613[sizeof("gob.ec")];
+ char stringpool_str614[sizeof("sn.cn")];
+ char stringpool_str615[sizeof("fin.tn")];
+ char stringpool_str616[sizeof("gw")];
+ char stringpool_str617[sizeof("assn.lk")];
+ char stringpool_str618[sizeof("g12.br")];
+ char stringpool_str619[sizeof("mil.st")];
+ char stringpool_str620[sizeof("mil.br")];
+ char stringpool_str621[sizeof("gotdns.com")];
+ char stringpool_str622[sizeof("cw")];
+ char stringpool_str623[sizeof("me.tz")];
+ char stringpool_str624[sizeof("co.ae")];
+ char stringpool_str625[sizeof("aw")];
+ char stringpool_str626[sizeof("rw")];
+ char stringpool_str627[sizeof("gol.no")];
+ char stringpool_str628[sizeof("jondal.no")];
+ char stringpool_str629[sizeof("tree.museum")];
+ char stringpool_str630[sizeof("vlog.br")];
+ char stringpool_str631[sizeof("gov.bf")];
+ char stringpool_str632[sizeof("k12.ak.us")];
+ char stringpool_str633[sizeof("gov.af")];
+ char stringpool_str634[sizeof("vdonsk.ru")];
+ char stringpool_str635[sizeof("edu.af")];
+ char stringpool_str636[sizeof("k12.az.us")];
+ char stringpool_str637[sizeof("evenes.no")];
+ char stringpool_str638[sizeof("com.af")];
+ char stringpool_str639[sizeof("biz.tj")];
+ char stringpool_str640[sizeof("rindal.no")];
+ char stringpool_str641[sizeof("fhsk.se")];
+ char stringpool_str642[sizeof("js.cn")];
+ char stringpool_str643[sizeof("kw")];
+ char stringpool_str644[sizeof("tj.cn")];
+ char stringpool_str645[sizeof("kobe.jp")];
+ char stringpool_str646[sizeof("k12.sd.us")];
+ char stringpool_str647[sizeof("stuttgart.museum")];
+ char stringpool_str648[sizeof("mil.al")];
+ char stringpool_str649[sizeof("k12.as.us")];
+ char stringpool_str650[sizeof("fie.ee")];
+ char stringpool_str651[sizeof("arpa")];
+ char stringpool_str652[sizeof("juif.museum")];
+ char stringpool_str653[sizeof("gov.ve")];
+ char stringpool_str654[sizeof("jevnaker.no")];
+ char stringpool_str655[sizeof("edu.ve")];
+ char stringpool_str656[sizeof("fet.no")];
+ char stringpool_str657[sizeof("lib.ak.us")];
+ char stringpool_str658[sizeof("com.ve")];
+ char stringpool_str659[sizeof("kiev.ua")];
+ char stringpool_str660[sizeof("fj.cn")];
+ char stringpool_str661[sizeof("se.net")];
+ char stringpool_str662[sizeof("lib.az.us")];
+ char stringpool_str663[sizeof("asso.nc")];
+ char stringpool_str664[sizeof("com.vi")];
+ char stringpool_str665[sizeof("military.museum")];
+ char stringpool_str666[sizeof("sshn.se")];
+ char stringpool_str667[sizeof("crafts.museum")];
+ char stringpool_str668[sizeof("firm.nf")];
+ char stringpool_str669[sizeof("time.museum")];
+ char stringpool_str670[sizeof("lviv.ua")];
+ char stringpool_str671[sizeof("kustanai.ru")];
+ char stringpool_str672[sizeof("gaular.no")];
+ char stringpool_str673[sizeof("lib.sd.us")];
+ char stringpool_str674[sizeof("rendalen.no")];
+ char stringpool_str675[sizeof("roma.museum")];
+ char stringpool_str676[sizeof("meldal.no")];
+ char stringpool_str677[sizeof("k12.sc.us")];
+ char stringpool_str678[sizeof("lardal.no")];
+ char stringpool_str679[sizeof("sel.no")];
+ char stringpool_str680[sizeof("verdal.no")];
+ char stringpool_str681[sizeof("trogstad.no")];
+ char stringpool_str682[sizeof("lib.as.us")];
+ char stringpool_str683[sizeof("sor-aurdal.no")];
+ char stringpool_str684[sizeof("mil.tj")];
+ char stringpool_str685[sizeof("enna.it")];
+ char stringpool_str686[sizeof("mw")];
+ char stringpool_str687[sizeof("gov.vc")];
+ char stringpool_str688[sizeof("hu")];
+ char stringpool_str689[sizeof("edu.vc")];
+ char stringpool_str690[sizeof("hm")];
+ char stringpool_str691[sizeof("voagat.no")];
+ char stringpool_str692[sizeof("solund.no")];
+ char stringpool_str693[sizeof("com.vc")];
+ char stringpool_str694[sizeof("ln.cn")];
+ char stringpool_str695[sizeof("mil.cn")];
+ char stringpool_str696[sizeof("mobi.na")];
+ char stringpool_str697[sizeof("tw")];
+ char stringpool_str698[sizeof("muenster.museum")];
+ char stringpool_str699[sizeof("k12.co.us")];
+ char stringpool_str700[sizeof("hr")];
+ char stringpool_str701[sizeof("lib.sc.us")];
+ char stringpool_str702[sizeof("grimstad.no")];
+ char stringpool_str703[sizeof("lodingen.no")];
+ char stringpool_str704[sizeof("silk.museum")];
+ char stringpool_str705[sizeof("jpn.com")];
+ char stringpool_str706[sizeof("chieti.it")];
+ char stringpool_str707[sizeof("mari.ru")];
+ char stringpool_str708[sizeof("torino.it")];
+ char stringpool_str709[sizeof("k12.ca.us")];
+ char stringpool_str710[sizeof("ht")];
+ char stringpool_str711[sizeof("bievat.no")];
+ char stringpool_str712[sizeof("co.rs")];
+ char stringpool_str713[sizeof("k12.tn.us")];
+ char stringpool_str714[sizeof("mil.cl")];
+ char stringpool_str715[sizeof("test.ru")];
+ char stringpool_str716[sizeof("k12.ar.us")];
+ char stringpool_str717[sizeof("bj.cn")];
+ char stringpool_str718[sizeof("aip.ee")];
+ char stringpool_str719[sizeof("mil.eg")];
+ char stringpool_str720[sizeof("stalbans.museum")];
+ char stringpool_str721[sizeof("fosnes.no")];
+ char stringpool_str722[sizeof("lib.co.us")];
+ char stringpool_str723[sizeof("modena.it")];
+ char stringpool_str724[sizeof("gy")];
+ char stringpool_str725[sizeof("murmansk.ru")];
+ char stringpool_str726[sizeof("cy")];
+ char stringpool_str727[sizeof("farm.museum")];
+ char stringpool_str728[sizeof("sy")];
+ char stringpool_str729[sizeof("svalbard.no")];
+ char stringpool_str730[sizeof("moma.museum")];
+ char stringpool_str731[sizeof("sh.cn")];
+ char stringpool_str732[sizeof("ah.cn")];
+ char stringpool_str733[sizeof("rimini.it")];
+ char stringpool_str734[sizeof("gov.bh")];
+ char stringpool_str735[sizeof("gouv.fr")];
+ char stringpool_str736[sizeof("edu.bh")];
+ char stringpool_str737[sizeof("jl.cn")];
+ char stringpool_str738[sizeof("com.bh")];
+ char stringpool_str739[sizeof("lib.ca.us")];
+ char stringpool_str740[sizeof("crimea.ua")];
+ char stringpool_str741[sizeof("fin.ec")];
+ char stringpool_str742[sizeof("masfjorden.no")];
+ char stringpool_str743[sizeof("mil.ec")];
+ char stringpool_str744[sizeof("estate.museum")];
+ char stringpool_str745[sizeof("santacruz.museum")];
+ char stringpool_str746[sizeof("lib.tn.us")];
+ char stringpool_str747[sizeof("tuva.ru")];
+ char stringpool_str748[sizeof("gov.vn")];
+ char stringpool_str749[sizeof("ky")];
+ char stringpool_str750[sizeof("k12.al.us")];
+ char stringpool_str751[sizeof("asso.fr")];
+ char stringpool_str752[sizeof("edu.vn")];
+ char stringpool_str753[sizeof("lib.ar.us")];
+ char stringpool_str754[sizeof("luster.no")];
+ char stringpool_str755[sizeof("com.vn")];
+ char stringpool_str756[sizeof("evenassi.no")];
+ char stringpool_str757[sizeof("gov.sh")];
+ char stringpool_str758[sizeof("edunet.tn")];
+ char stringpool_str759[sizeof("mandal.no")];
+ char stringpool_str760[sizeof("snaase.no")];
+ char stringpool_str761[sizeof("com.sh")];
+ char stringpool_str762[sizeof("bergen.no")];
+ char stringpool_str763[sizeof("caa.aero")];
+ char stringpool_str764[sizeof("lib.ee")];
+ char stringpool_str765[sizeof("baseball.museum")];
+ char stringpool_str766[sizeof("h.se")];
+ char stringpool_str767[sizeof("varese.it")];
+ char stringpool_str768[sizeof("k12.ec")];
+ char stringpool_str769[sizeof("mine.nu")];
+ char stringpool_str770[sizeof("moskenes.no")];
+ char stringpool_str771[sizeof("k12.ct.us")];
+ char stringpool_str772[sizeof("coal.museum")];
+ char stringpool_str773[sizeof("bw")];
+ char stringpool_str774[sizeof("lib.al.us")];
+ char stringpool_str775[sizeof("venice.it")];
+ char stringpool_str776[sizeof("strand.no")];
+ char stringpool_str777[sizeof("beauxarts.museum")];
+ char stringpool_str778[sizeof("marker.no")];
+ char stringpool_str779[sizeof("freiburg.museum")];
+ char stringpool_str780[sizeof("maritimo.museum")];
+ char stringpool_str781[sizeof("suedtirol.it")];
+ char stringpool_str782[sizeof("gb.net")];
+ char stringpool_str783[sizeof("auto.pl")];
+ char stringpool_str784[sizeof("bindal.no")];
+ char stringpool_str785[sizeof("finearts.museum")];
+ char stringpool_str786[sizeof("mil.no")];
+ char stringpool_str787[sizeof("my")];
+ char stringpool_str788[sizeof("cadaques.museum")];
+ char stringpool_str789[sizeof("savona.it")];
+ char stringpool_str790[sizeof("hn")];
+ char stringpool_str791[sizeof("film.museum")];
+ char stringpool_str792[sizeof("gq")];
+ char stringpool_str793[sizeof("bern.museum")];
+ char stringpool_str794[sizeof("grue.no")];
+ char stringpool_str795[sizeof("amursk.ru")];
+ char stringpool_str796[sizeof("ky.us")];
+ char stringpool_str797[sizeof("reklam.hu")];
+ char stringpool_str798[sizeof("aq")];
+ char stringpool_str799[sizeof("lib.ct.us")];
+ char stringpool_str800[sizeof("ly")];
+ char stringpool_str801[sizeof("aure.no")];
+ char stringpool_str802[sizeof("kirkenes.no")];
+ char stringpool_str803[sizeof("tank.museum")];
+ char stringpool_str804[sizeof("rubtsovsk.ru")];
+ char stringpool_str805[sizeof("kvalsund.no")];
+ char stringpool_str806[sizeof("marine.ru")];
+ char stringpool_str807[sizeof("baidar.no")];
+ char stringpool_str808[sizeof("co.ba")];
+ char stringpool_str809[sizeof("fuettertdasnetz.de")];
+ char stringpool_str810[sizeof("ski.no")];
+ char stringpool_str811[sizeof("aq.it")];
+ char stringpool_str812[sizeof("retina.ar")];
+ char stringpool_str813[sizeof("torino.museum")];
+ char stringpool_str814[sizeof("casino.hu")];
+ char stringpool_str815[sizeof("trentino.it")];
+ char stringpool_str816[sizeof("hi.us")];
+ char stringpool_str817[sizeof("k12.wi.us")];
+ char stringpool_str818[sizeof("biz.nr")];
+ char stringpool_str819[sizeof("gouv.ht")];
+ char stringpool_str820[sizeof("bonn.museum")];
+ char stringpool_str821[sizeof("baikal.ru")];
+ char stringpool_str822[sizeof("fhs.no")];
+ char stringpool_str823[sizeof("lunner.no")];
+ char stringpool_str824[sizeof("k12.wa.us")];
+ char stringpool_str825[sizeof("amot.no")];
+ char stringpool_str826[sizeof("dinosaur.museum")];
+ char stringpool_str827[sizeof("asso.ht")];
+ char stringpool_str828[sizeof("travel")];
+ char stringpool_str829[sizeof("servebbs.net")];
+ char stringpool_str830[sizeof("rs.ba")];
+ char stringpool_str831[sizeof("dep.no")];
+ char stringpool_str832[sizeof("mq")];
+ char stringpool_str833[sizeof("coop.ht")];
+ char stringpool_str834[sizeof("berkeley.museum")];
+ char stringpool_str835[sizeof("lib.wi.us")];
+ char stringpool_str836[sizeof("exhibition.museum")];
+ char stringpool_str837[sizeof("museet.museum")];
+ char stringpool_str838[sizeof("eastcoast.museum")];
+ char stringpool_str839[sizeof("h.bg")];
+ char stringpool_str840[sizeof("karate.museum")];
+ char stringpool_str841[sizeof("lib.wa.us")];
+ char stringpool_str842[sizeof("research.museum")];
+ char stringpool_str843[sizeof("fla.no")];
+ char stringpool_str844[sizeof("from.hr")];
+ char stringpool_str845[sizeof("by")];
+ char stringpool_str846[sizeof("verona.it")];
+ char stringpool_str847[sizeof("farmstead.museum")];
+ char stringpool_str848[sizeof("dali.museum")];
+ char stringpool_str849[sizeof("tula.ru")];
+ char stringpool_str850[sizeof("k12.nm.us")];
+ char stringpool_str851[sizeof("k12.ne.us")];
+ char stringpool_str852[sizeof("k12.nd.us")];
+ char stringpool_str853[sizeof("rost.no")];
+ char stringpool_str854[sizeof("chel.ru")];
+ char stringpool_str855[sizeof("exeter.museum")];
+ char stringpool_str856[sizeof("com.nf")];
+ char stringpool_str857[sizeof("firm.co")];
+ char stringpool_str858[sizeof("sendai.jp")];
+ char stringpool_str859[sizeof("latina.it")];
+ char stringpool_str860[sizeof("sund.no")];
+ char stringpool_str861[sizeof("co.rw")];
+ char stringpool_str862[sizeof("amli.no")];
+ char stringpool_str863[sizeof("bodo.no")];
+ char stringpool_str864[sizeof("gg")];
+ char stringpool_str865[sizeof("eg")];
+ char stringpool_str866[sizeof("hurdal.no")];
+ char stringpool_str867[sizeof("jp.net")];
+ char stringpool_str868[sizeof("cg")];
+ char stringpool_str869[sizeof("hk")];
+ char stringpool_str870[sizeof("sg")];
+ char stringpool_str871[sizeof("ag")];
+ char stringpool_str872[sizeof("frei.no")];
+ char stringpool_str873[sizeof("lib.nm.us")];
+ char stringpool_str874[sizeof("lib.ne.us")];
+ char stringpool_str875[sizeof("lib.nd.us")];
+ char stringpool_str876[sizeof("mill.museum")];
+ char stringpool_str877[sizeof("k12.nc.us")];
+ char stringpool_str878[sizeof("arq.br")];
+ char stringpool_str879[sizeof("etne.no")];
+ char stringpool_str880[sizeof("living.museum")];
+ char stringpool_str881[sizeof("travel.tt")];
+ char stringpool_str882[sizeof("co.je")];
+ char stringpool_str883[sizeof("kg")];
+ char stringpool_str884[sizeof("ralingen.no")];
+ char stringpool_str885[sizeof("kvam.no")];
+ char stringpool_str886[sizeof("ag.it")];
+ char stringpool_str887[sizeof("rg.it")];
+ char stringpool_str888[sizeof("birkenes.no")];
+ char stringpool_str889[sizeof("mie.jp")];
+ char stringpool_str890[sizeof("gouv.ci")];
+ char stringpool_str891[sizeof("servebbs.org")];
+ char stringpool_str892[sizeof("amur.ru")];
+ char stringpool_str893[sizeof("bale.museum")];
+ char stringpool_str894[sizeof("aseral.no")];
+ char stringpool_str895[sizeof("rade.no")];
+ char stringpool_str896[sizeof("lib.nc.us")];
+ char stringpool_str897[sizeof("asso.ci")];
+ char stringpool_str898[sizeof("genova.it")];
+ char stringpool_str899[sizeof("e12.ve")];
+ char stringpool_str900[sizeof("mil.ve")];
+ char stringpool_str901[sizeof("broker.aero")];
+ char stringpool_str902[sizeof("time.no")];
+ char stringpool_str903[sizeof("firm.ro")];
+ char stringpool_str904[sizeof("garden.museum")];
+ char stringpool_str905[sizeof("krym.ua")];
+ char stringpool_str906[sizeof("berg.no")];
+ char stringpool_str907[sizeof("mg")];
+ char stringpool_str908[sizeof("divtasvuodna.no")];
+ char stringpool_str909[sizeof("lund.no")];
+ char stringpool_str910[sizeof("fortworth.museum")];
+ char stringpool_str911[sizeof("firm.ht")];
+ char stringpool_str912[sizeof("meland.no")];
+ char stringpool_str913[sizeof("k12.vi")];
+ char stringpool_str914[sizeof("drobak.no")];
+ char stringpool_str915[sizeof("flakstad.no")];
+ char stringpool_str916[sizeof("tg")];
+ char stringpool_str917[sizeof("tw.cn")];
+ char stringpool_str918[sizeof("mil.vc")];
+ char stringpool_str919[sizeof("levanger.no")];
+ char stringpool_str920[sizeof("smolensk.ru")];
+ char stringpool_str921[sizeof("sumoto.kumamoto.jp")];
+ char stringpool_str922[sizeof("krokstadelva.no")];
+ char stringpool_str923[sizeof("center.museum")];
+ char stringpool_str924[sizeof("deatnu.no")];
+ char stringpool_str925[sizeof("vik.no")];
+ char stringpool_str926[sizeof("biz.vn")];
+ char stringpool_str927[sizeof("creation.museum")];
+ char stringpool_str928[sizeof("chuvashia.ru")];
+ char stringpool_str929[sizeof("bill.museum")];
+ char stringpool_str930[sizeof("fjaler.no")];
+ char stringpool_str931[sizeof("durham.museum")];
+ char stringpool_str932[sizeof("co.bw")];
+ char stringpool_str933[sizeof("mining.museum")];
+ char stringpool_str934[sizeof("he.cn")];
+ char stringpool_str935[sizeof("lg.ua")];
+ char stringpool_str936[sizeof("fg.it")];
+ char stringpool_str937[sizeof("b.br")];
+ char stringpool_str938[sizeof("educator.aero")];
+ char stringpool_str939[sizeof("agr.br")];
+ char stringpool_str940[sizeof("roan.no")];
+ char stringpool_str941[sizeof("suli.hu")];
+ char stringpool_str942[sizeof("vercelli.it")];
+ char stringpool_str943[sizeof("research.aero")];
+ char stringpool_str944[sizeof("stat.no")];
+ char stringpool_str945[sizeof("vantaa.museum")];
+ char stringpool_str946[sizeof("club.aero")];
+ char stringpool_str947[sizeof("kviteseid.no")];
+ char stringpool_str948[sizeof("k12.nj.us")];
+ char stringpool_str949[sizeof("gran.no")];
+ char stringpool_str950[sizeof("e164.arpa")];
+ char stringpool_str951[sizeof("aviation.museum")];
+ char stringpool_str952[sizeof("capebreton.museum")];
+ char stringpool_str953[sizeof("kunisaki.oita.jp")];
+ char stringpool_str954[sizeof("krodsherad.no")];
+ char stringpool_str955[sizeof("tver.ru")];
+ char stringpool_str956[sizeof("mil.sh")];
+ char stringpool_str957[sizeof("town.museum")];
+ char stringpool_str958[sizeof("tysfjord.no")];
+ char stringpool_str959[sizeof("tysvar.no")];
+ char stringpool_str960[sizeof("k12.vi.us")];
+ char stringpool_str961[sizeof("landes.museum")];
+ char stringpool_str962[sizeof("lavangen.no")];
+ char stringpool_str963[sizeof("khakassia.ru")];
+ char stringpool_str964[sizeof("royken.no")];
+ char stringpool_str965[sizeof("dallas.museum")];
+ char stringpool_str966[sizeof("bg")];
+ char stringpool_str967[sizeof("brunel.museum")];
+ char stringpool_str968[sizeof("stpetersburg.museum")];
+ char stringpool_str969[sizeof("avocat.fr")];
+ char stringpool_str970[sizeof("lib.nj.us")];
+ char stringpool_str971[sizeof("k12.va.us")];
+ char stringpool_str972[sizeof("mosreg.ru")];
+ char stringpool_str973[sizeof("shop.pl")];
+ char stringpool_str974[sizeof("trader.aero")];
+ char stringpool_str975[sizeof("hi.cn")];
+ char stringpool_str976[sizeof("verran.no")];
+ char stringpool_str977[sizeof("audnedaln.no")];
+ char stringpool_str978[sizeof("amsterdam.museum")];
+ char stringpool_str979[sizeof("hu.net")];
+ char stringpool_str980[sizeof("hn.cn")];
+ char stringpool_str981[sizeof("bg.it")];
+ char stringpool_str982[sizeof("shop.ht")];
+ char stringpool_str983[sizeof("arna.no")];
+ char stringpool_str984[sizeof("lib.vi.us")];
+ char stringpool_str985[sizeof("cq.cn")];
+ char stringpool_str986[sizeof("fitjar.no")];
+ char stringpool_str987[sizeof("tingvoll.no")];
+ char stringpool_str988[sizeof("fusa.no")];
+ char stringpool_str989[sizeof("hol.no")];
+ char stringpool_str990[sizeof("evje-og-hornnes.no")];
+ char stringpool_str991[sizeof("lib.va.us")];
+ char stringpool_str992[sizeof("vg")];
+ char stringpool_str993[sizeof("sola.no")];
+ char stringpool_str994[sizeof("sula.no")];
+ char stringpool_str995[sizeof("hemnes.no")];
+ char stringpool_str996[sizeof("balestrand.no")];
+ char stringpool_str997[sizeof("surgeonshall.museum")];
+ char stringpool_str998[sizeof("arao.kumamoto.jp")];
+ char stringpool_str999[sizeof("benevento.it")];
+ char stringpool_str1000[sizeof("siljan.no")];
+ char stringpool_str1001[sizeof("bahn.museum")];
+ char stringpool_str1002[sizeof("hl.cn")];
+ char stringpool_str1003[sizeof("fyresdal.no")];
+ char stringpool_str1004[sizeof("horten.no")];
+ char stringpool_str1005[sizeof("eng.br")];
+ char stringpool_str1006[sizeof("cng.br")];
+ char stringpool_str1007[sizeof("kyiv.ua")];
+ char stringpool_str1008[sizeof("milano.it")];
+ char stringpool_str1009[sizeof("eisenbahn.museum")];
+ char stringpool_str1010[sizeof("salangen.no")];
+ char stringpool_str1011[sizeof("filatelia.museum")];
+ char stringpool_str1012[sizeof("k12.vt.us")];
+ char stringpool_str1013[sizeof("alta.no")];
+ char stringpool_str1014[sizeof("ha.cn")];
+ char stringpool_str1015[sizeof("guernsey.museum")];
+ char stringpool_str1016[sizeof("collection.museum")];
+ char stringpool_str1017[sizeof("leg.br")];
+ char stringpool_str1018[sizeof("cheltenham.museum")];
+ char stringpool_str1019[sizeof("losangeles.museum")];
+ char stringpool_str1020[sizeof("kurotaki.nara.jp")];
+ char stringpool_str1021[sizeof("mypets.ws")];
+ char stringpool_str1022[sizeof("muenchen.museum")];
+ char stringpool_str1023[sizeof("kuji.iwate.jp")];
+ char stringpool_str1024[sizeof("club.tw")];
+ char stringpool_str1025[sizeof("contemporary.museum")];
+ char stringpool_str1026[sizeof("catanzaro.it")];
+ char stringpool_str1027[sizeof("hb.cn")];
+ char stringpool_str1028[sizeof("tinn.no")];
+ char stringpool_str1029[sizeof("loabat.no")];
+ char stringpool_str1030[sizeof("slg.br")];
+ char stringpool_str1031[sizeof("lib.vt.us")];
+ char stringpool_str1032[sizeof("siracusa.it")];
+ char stringpool_str1033[sizeof("lancashire.museum")];
+ char stringpool_str1034[sizeof("gjerstad.no")];
+ char stringpool_str1035[sizeof("jorpeland.no")];
+ char stringpool_str1036[sizeof("kirovograd.ua")];
+ char stringpool_str1037[sizeof("tyumen.ru")];
+ char stringpool_str1038[sizeof("holtalen.no")];
+ char stringpool_str1039[sizeof("hokksund.no")];
+ char stringpool_str1040[sizeof("kosa.kumamoto.jp")];
+ char stringpool_str1041[sizeof("k-uralsk.ru")];
+ char stringpool_str1042[sizeof("shop.hu")];
+ char stringpool_str1043[sizeof("hk.cn")];
+ char stringpool_str1044[sizeof("avoues.fr")];
+ char stringpool_str1045[sizeof("vang.no")];
+ char stringpool_str1046[sizeof("tananger.no")];
+ char stringpool_str1047[sizeof("toon.ehime.jp")];
+ char stringpool_str1048[sizeof("lierne.no")];
+ char stringpool_str1049[sizeof("tysnes.no")];
+ char stringpool_str1050[sizeof("film.hu")];
+ char stringpool_str1051[sizeof("sandnessjoen.no")];
+ char stringpool_str1052[sizeof("sigdal.no")];
+ char stringpool_str1053[sizeof("mesaverde.museum")];
+ char stringpool_str1054[sizeof("vennesla.no")];
+ char stringpool_str1055[sizeof("mo-i-rana.no")];
+ char stringpool_str1056[sizeof("mail.pl")];
+ char stringpool_str1057[sizeof("rana.no")];
+ char stringpool_str1058[sizeof("tono.iwate.jp")];
+ char stringpool_str1059[sizeof("lyngen.no")];
+ char stringpool_str1060[sizeof("steiermark.museum")];
+ char stringpool_str1061[sizeof("slupsk.pl")];
+ char stringpool_str1062[sizeof("rennesoy.no")];
+ char stringpool_str1063[sizeof("sweden.museum")];
+ char stringpool_str1064[sizeof("komono.mie.jp")];
+ char stringpool_str1065[sizeof("geelvinck.museum")];
+ char stringpool_str1066[sizeof("bokn.no")];
+ char stringpool_str1067[sizeof("viking.museum")];
+ char stringpool_str1068[sizeof("ragusa.it")];
+ char stringpool_str1069[sizeof("matta-varjjat.no")];
+ char stringpool_str1070[sizeof("giehtavuoatna.no")];
+ char stringpool_str1071[sizeof("bolt.hu")];
+ char stringpool_str1072[sizeof("kazo.saitama.jp")];
+ char stringpool_str1073[sizeof("gdansk.pl")];
+ char stringpool_str1074[sizeof("coop.py")];
+ char stringpool_str1075[sizeof("fuji.shizuoka.jp")];
+ char stringpool_str1076[sizeof("astrakhan.ru")];
+ char stringpool_str1077[sizeof("tana.no")];
+ char stringpool_str1078[sizeof("tobe.ehime.jp")];
+ char stringpool_str1079[sizeof("meeres.museum")];
+ char stringpool_str1080[sizeof("hellas.museum")];
+ char stringpool_str1081[sizeof("riodejaneiro.museum")];
+ char stringpool_str1082[sizeof("kamijima.ehime.jp")];
+ char stringpool_str1083[sizeof("leka.no")];
+ char stringpool_str1084[sizeof("bahccavuotna.no")];
+ char stringpool_str1085[sizeof("cahcesuolo.no")];
+ char stringpool_str1086[sizeof("arai.shizuoka.jp")];
+ char stringpool_str1087[sizeof("hvaler.no")];
+ char stringpool_str1088[sizeof("dyndns.tv")];
+ char stringpool_str1089[sizeof("vindafjord.no")];
+ char stringpool_str1090[sizeof("kalisz.pl")];
+ char stringpool_str1091[sizeof("education.museum")];
+ char stringpool_str1092[sizeof("lillesand.no")];
+ char stringpool_str1093[sizeof("rakkestad.no")];
+ char stringpool_str1094[sizeof("sauherad.no")];
+ char stringpool_str1095[sizeof("likescandy.com")];
+ char stringpool_str1096[sizeof("habmer.no")];
+ char stringpool_str1097[sizeof("kuki.saitama.jp")];
+ char stringpool_str1098[sizeof("halden.no")];
+ char stringpool_str1099[sizeof("contemporaryart.museum")];
+ char stringpool_str1100[sizeof("sorreisa.no")];
+ char stringpool_str1101[sizeof("andasuolo.no")];
+ char stringpool_str1102[sizeof("altoadige.it")];
+ char stringpool_str1103[sizeof("toda.saitama.jp")];
+ char stringpool_str1104[sizeof("gyeonggi.kr")];
+ char stringpool_str1105[sizeof("gose.nara.jp")];
+ char stringpool_str1106[sizeof("dyndns.ws")];
+ char stringpool_str1107[sizeof("misasa.tottori.jp")];
+ char stringpool_str1108[sizeof("joso.ibaraki.jp")];
+ char stringpool_str1109[sizeof("jogasz.hu")];
+ char stringpool_str1110[sizeof("sells-it.net")];
+ char stringpool_str1111[sizeof("broke-it.net")];
+ char stringpool_str1112[sizeof("sandefjord.no")];
+ char stringpool_str1113[sizeof("syzran.ru")];
+ char stringpool_str1114[sizeof("agents.aero")];
+ char stringpool_str1115[sizeof("hole.no")];
+ char stringpool_str1116[sizeof("fundacio.museum")];
+ char stringpool_str1117[sizeof("zm")];
+ char stringpool_str1118[sizeof("k12.nh.us")];
+ char stringpool_str1119[sizeof("gov.im")];
+ char stringpool_str1120[sizeof("gov.ie")];
+ char stringpool_str1121[sizeof("gildeskal.no")];
+ char stringpool_str1122[sizeof("donostia.museum")];
+ char stringpool_str1123[sizeof("com.io")];
+ char stringpool_str1124[sizeof("vgs.no")];
+ char stringpool_str1125[sizeof("ando.nara.jp")];
+ char stringpool_str1126[sizeof("kumatori.osaka.jp")];
+ char stringpool_str1127[sizeof("go.ci")];
+ char stringpool_str1128[sizeof("soni.nara.jp")];
+ char stringpool_str1129[sizeof("santabarbara.museum")];
+ char stringpool_str1130[sizeof("ed.ci")];
+ char stringpool_str1131[sizeof("gov.is")];
+ char stringpool_str1132[sizeof("co.ci")];
+ char stringpool_str1133[sizeof("edu.is")];
+ char stringpool_str1134[sizeof("journalism.museum")];
+ char stringpool_str1135[sizeof("lib.nh.us")];
+ char stringpool_str1136[sizeof("com.is")];
+ char stringpool_str1137[sizeof("divttasvuotna.no")];
+ char stringpool_str1138[sizeof("kawasaki.jp")];
+ char stringpool_str1139[sizeof("blogspot.mx")];
+ char stringpool_str1140[sizeof("mito.ibaraki.jp")];
+ char stringpool_str1141[sizeof("blogspot.ro")];
+ char stringpool_str1142[sizeof("lier.no")];
+ char stringpool_str1143[sizeof("blogspot.mr")];
+ char stringpool_str1144[sizeof("bilbao.museum")];
+ char stringpool_str1145[sizeof("dagestan.ru")];
+ char stringpool_str1146[sizeof("kumano.mie.jp")];
+ char stringpool_str1147[sizeof("blogspot.td")];
+ char stringpool_str1148[sizeof("tone.ibaraki.jp")];
+ char stringpool_str1149[sizeof("kvinnherad.no")];
+ char stringpool_str1150[sizeof("kvinesdal.no")];
+ char stringpool_str1151[sizeof("zt.ua")];
+ char stringpool_str1152[sizeof("elblag.pl")];
+ char stringpool_str1153[sizeof("marnardal.no")];
+ char stringpool_str1154[sizeof("karasjok.no")];
+ char stringpool_str1155[sizeof("agro.pl")];
+ char stringpool_str1156[sizeof("blogspot.de")];
+ char stringpool_str1157[sizeof("francaise.museum")];
+ char stringpool_str1158[sizeof("z.se")];
+ char stringpool_str1159[sizeof("gov.it")];
+ char stringpool_str1160[sizeof("edu.it")];
+ char stringpool_str1161[sizeof("saga.jp")];
+ char stringpool_str1162[sizeof("kunitomi.miyazaki.jp")];
+ char stringpool_str1163[sizeof("ama.aichi.jp")];
+ char stringpool_str1164[sizeof("co.vi")];
+ char stringpool_str1165[sizeof("suzu.ishikawa.jp")];
+ char stringpool_str1166[sizeof("kongsberg.no")];
+ char stringpool_str1167[sizeof("md.ci")];
+ char stringpool_str1168[sizeof("troandin.no")];
+ char stringpool_str1169[sizeof("journalist.aero")];
+ char stringpool_str1170[sizeof("blogspot.re")];
+ char stringpool_str1171[sizeof("saintlouis.museum")];
+ char stringpool_str1172[sizeof("gov.ir")];
+ char stringpool_str1173[sizeof("gov.in")];
+ char stringpool_str1174[sizeof("edu.in")];
+ char stringpool_str1175[sizeof("television.museum")];
+ char stringpool_str1176[sizeof("shinto.gunma.jp")];
+ char stringpool_str1177[sizeof("misawa.aomori.jp")];
+ char stringpool_str1178[sizeof("limanowa.pl")];
+ char stringpool_str1179[sizeof("rakpetroleum.om")];
+ char stringpool_str1180[sizeof("minnesota.museum")];
+ char stringpool_str1181[sizeof("haugesund.no")];
+ char stringpool_str1182[sizeof("blogspot.se")];
+ char stringpool_str1183[sizeof("gyeongnam.kr")];
+ char stringpool_str1184[sizeof("futtsu.chiba.jp")];
+ char stringpool_str1185[sizeof("kurobe.toyama.jp")];
+ char stringpool_str1186[sizeof("dell-ogliastra.it")];
+ char stringpool_str1187[sizeof("soka.saitama.jp")];
+ char stringpool_str1188[sizeof("salvadordali.museum")];
+ char stringpool_str1189[sizeof("blogspot.no")];
+ char stringpool_str1190[sizeof("gotdns.org")];
+ char stringpool_str1191[sizeof("tomobe.ibaraki.jp")];
+ char stringpool_str1192[sizeof("botanical.museum")];
+ char stringpool_str1193[sizeof("humanities.museum")];
+ char stringpool_str1194[sizeof("lowicz.pl")];
+ char stringpool_str1195[sizeof("tosu.saga.jp")];
+ char stringpool_str1196[sizeof("jewish.museum")];
+ char stringpool_str1197[sizeof("chernivtsi.ua")];
+ char stringpool_str1198[sizeof("figueres.museum")];
+ char stringpool_str1199[sizeof("stateofdelaware.museum")];
+ char stringpool_str1200[sizeof("za")];
+ char stringpool_str1201[sizeof("blogspot.it")];
+ char stringpool_str1202[sizeof("barcelona.museum")];
+ char stringpool_str1203[sizeof("kizu.kyoto.jp")];
+ char stringpool_str1204[sizeof("hokkaido.jp")];
+ char stringpool_str1205[sizeof("chuo.chiba.jp")];
+ char stringpool_str1206[sizeof("dyndns-web.com")];
+ char stringpool_str1207[sizeof("fujisato.akita.jp")];
+ char stringpool_str1208[sizeof("mielec.pl")];
+ char stringpool_str1209[sizeof("kamisato.saitama.jp")];
+ char stringpool_str1210[sizeof("servegame.org")];
+ char stringpool_str1211[sizeof("saskatchewan.museum")];
+ char stringpool_str1212[sizeof("cranbrook.museum")];
+ char stringpool_str1213[sizeof("blogspot.ie")];
+ char stringpool_str1214[sizeof("gjovik.no")];
+ char stringpool_str1215[sizeof("engine.aero")];
+ char stringpool_str1216[sizeof("z.bg")];
+ char stringpool_str1217[sizeof("franziskaner.museum")];
+ char stringpool_str1218[sizeof("heimatunduhren.museum")];
+ char stringpool_str1219[sizeof("goto.nagasaki.jp")];
+ char stringpool_str1220[sizeof("sosa.chiba.jp")];
+ char stringpool_str1221[sizeof("res.in")];
+ char stringpool_str1222[sizeof("jewishart.museum")];
+ char stringpool_str1223[sizeof("kamo.kyoto.jp")];
+ char stringpool_str1224[sizeof("susono.shizuoka.jp")];
+ char stringpool_str1225[sizeof("davvesiida.no")];
+ char stringpool_str1226[sizeof("vlaanderen.museum")];
+ char stringpool_str1227[sizeof("epilepsy.museum")];
+ char stringpool_str1228[sizeof("engineer.aero")];
+ char stringpool_str1229[sizeof("hjartdal.no")];
+ char stringpool_str1230[sizeof("mielno.pl")];
+ char stringpool_str1231[sizeof("settsu.osaka.jp")];
+ char stringpool_str1232[sizeof("mediaphone.om")];
+ char stringpool_str1233[sizeof("travel.pl")];
+ char stringpool_str1234[sizeof("aremark.no")];
+ char stringpool_str1235[sizeof("misato.saitama.jp")];
+ char stringpool_str1236[sizeof("carrara-massa.it")];
+ char stringpool_str1237[sizeof("mosvik.no")];
+ char stringpool_str1238[sizeof("kraanghke.no")];
+ char stringpool_str1239[sizeof("kami.kochi.jp")];
+ char stringpool_str1240[sizeof("gyeongbuk.kr")];
+ char stringpool_str1241[sizeof("hirosaki.aomori.jp")];
+ char stringpool_str1242[sizeof("chernigov.ua")];
+ char stringpool_str1243[sizeof("miho.ibaraki.jp")];
+ char stringpool_str1244[sizeof("blogspot.in")];
+ char stringpool_str1245[sizeof("fylkesbibl.no")];
+ char stringpool_str1246[sizeof("zp.ua")];
+ char stringpool_str1247[sizeof("fareast.ru")];
+ char stringpool_str1248[sizeof("dvrdns.org")];
+ char stringpool_str1249[sizeof("misato.shimane.jp")];
+ char stringpool_str1250[sizeof("chuo.osaka.jp")];
+ char stringpool_str1251[sizeof("blogspot.be")];
+ char stringpool_str1252[sizeof("fuoisku.no")];
+ char stringpool_str1253[sizeof("blogspot.hu")];
+ char stringpool_str1254[sizeof("gen.in")];
+ char stringpool_str1255[sizeof("randaberg.no")];
+ char stringpool_str1256[sizeof("blogspot.dk")];
+ char stringpool_str1257[sizeof("chonan.chiba.jp")];
+ char stringpool_str1258[sizeof("biz.id")];
+ char stringpool_str1259[sizeof("blogspot.bj")];
+ char stringpool_str1260[sizeof("blogspot.pt")];
+ char stringpool_str1261[sizeof("misato.akita.jp")];
+ char stringpool_str1262[sizeof("shikatsu.aichi.jp")];
+ char stringpool_str1263[sizeof("blogspot.kr")];
+ char stringpool_str1264[sizeof("transport.museum")];
+ char stringpool_str1265[sizeof("alto-adige.it")];
+ char stringpool_str1266[sizeof("blogspot.nl")];
+ char stringpool_str1267[sizeof("koenig.ru")];
+ char stringpool_str1268[sizeof("gamvik.no")];
+ char stringpool_str1269[sizeof("egersund.no")];
+ char stringpool_str1270[sizeof("laspezia.it")];
+ char stringpool_str1271[sizeof("chernihiv.ua")];
+ char stringpool_str1272[sizeof("blogspot.jp")];
+ char stringpool_str1273[sizeof("furubira.hokkaido.jp")];
+ char stringpool_str1274[sizeof("livinghistory.museum")];
+ char stringpool_str1275[sizeof("blogspot.sk")];
+ char stringpool_str1276[sizeof("miyazaki.jp")];
+ char stringpool_str1277[sizeof("saratov.ru")];
+ char stringpool_str1278[sizeof("muko.kyoto.jp")];
+ char stringpool_str1279[sizeof("seto.aichi.jp")];
+ char stringpool_str1280[sizeof("mil.id")];
+ char stringpool_str1281[sizeof("cesena-forli.it")];
+ char stringpool_str1282[sizeof("karasjohka.no")];
+ char stringpool_str1283[sizeof("shiojiri.nagano.jp")];
+ char stringpool_str1284[sizeof("serveftp.net")];
+ char stringpool_str1285[sizeof("takatori.nara.jp")];
+ char stringpool_str1286[sizeof("kazuno.akita.jp")];
+ char stringpool_str1287[sizeof("sennan.osaka.jp")];
+ char stringpool_str1288[sizeof("egyptian.museum")];
+ char stringpool_str1289[sizeof("kusu.oita.jp")];
+ char stringpool_str1290[sizeof("minobu.yamanashi.jp")];
+ char stringpool_str1291[sizeof("kamisu.ibaraki.jp")];
+ char stringpool_str1292[sizeof("assabu.hokkaido.jp")];
+ char stringpool_str1293[sizeof("carraramassa.it")];
+ char stringpool_str1294[sizeof("fujisawa.iwate.jp")];
+ char stringpool_str1295[sizeof("lenvik.no")];
+ char stringpool_str1296[sizeof("trieste.it")];
+ char stringpool_str1297[sizeof("zj.cn")];
+ char stringpool_str1298[sizeof("medecin.km")];
+ char stringpool_str1299[sizeof("kitaaiki.nagano.jp")];
+ char stringpool_str1300[sizeof("gorizia.it")];
+ char stringpool_str1301[sizeof("kuju.oita.jp")];
+ char stringpool_str1302[sizeof("sakhalin.ru")];
+ char stringpool_str1303[sizeof("toei.aichi.jp")];
+ char stringpool_str1304[sizeof("caltanissetta.it")];
+ char stringpool_str1305[sizeof("tosa.kochi.jp")];
+ char stringpool_str1306[sizeof("fuso.aichi.jp")];
+ char stringpool_str1307[sizeof("larvik.no")];
+ char stringpool_str1308[sizeof("mizusawa.iwate.jp")];
+ char stringpool_str1309[sizeof("kita.kyoto.jp")];
+ char stringpool_str1310[sizeof("kumano.hiroshima.jp")];
+ char stringpool_str1311[sizeof("donetsk.ua")];
+ char stringpool_str1312[sizeof("ebetsu.hokkaido.jp")];
+ char stringpool_str1313[sizeof("kita.osaka.jp")];
+ char stringpool_str1314[sizeof("media.pl")];
+ char stringpool_str1315[sizeof("shinjuku.tokyo.jp")];
+ char stringpool_str1316[sizeof("shintomi.miyazaki.jp")];
+ char stringpool_str1317[sizeof("lipetsk.ru")];
+ char stringpool_str1318[sizeof("realestate.pl")];
+ char stringpool_str1319[sizeof("kainan.tokushima.jp")];
+ char stringpool_str1320[sizeof("serveftp.org")];
+ char stringpool_str1321[sizeof("shonai.yamagata.jp")];
+ char stringpool_str1322[sizeof("tako.chiba.jp")];
+ char stringpool_str1323[sizeof("anjo.aichi.jp")];
+ char stringpool_str1324[sizeof("mil.in")];
+ char stringpool_str1325[sizeof("zw")];
+ char stringpool_str1326[sizeof("mari-el.ru")];
+ char stringpool_str1327[sizeof("k12.id.us")];
+ char stringpool_str1328[sizeof("hino.tottori.jp")];
+ char stringpool_str1329[sizeof("swidnica.pl")];
+ char stringpool_str1330[sizeof("health.museum")];
+ char stringpool_str1331[sizeof("dudinka.ru")];
+ char stringpool_str1332[sizeof("augustow.pl")];
+ char stringpool_str1333[sizeof("ringerike.no")];
+ char stringpool_str1334[sizeof("aurland.no")];
+ char stringpool_str1335[sizeof("k12.ia.us")];
+ char stringpool_str1336[sizeof("kimino.wakayama.jp")];
+ char stringpool_str1337[sizeof("rawa-maz.pl")];
+ char stringpool_str1338[sizeof("gov.by")];
+ char stringpool_str1339[sizeof("com.by")];
+ char stringpool_str1340[sizeof("shoo.okayama.jp")];
+ char stringpool_str1341[sizeof("lib.id.us")];
+ char stringpool_str1342[sizeof("minato.osaka.jp")];
+ char stringpool_str1343[sizeof("kimobetsu.hokkaido.jp")];
+ char stringpool_str1344[sizeof("chuo.fukuoka.jp")];
+ char stringpool_str1345[sizeof("muncie.museum")];
+ char stringpool_str1346[sizeof("alaheadju.no")];
+ char stringpool_str1347[sizeof("shimamoto.osaka.jp")];
+ char stringpool_str1348[sizeof("misato.wakayama.jp")];
+ char stringpool_str1349[sizeof("gov.sy")];
+ char stringpool_str1350[sizeof("edu.sy")];
+ char stringpool_str1351[sizeof("com.sy")];
+ char stringpool_str1352[sizeof("lib.ia.us")];
+ char stringpool_str1353[sizeof("hoyanger.no")];
+ char stringpool_str1354[sizeof("gs.jan-mayen.no")];
+ char stringpool_str1355[sizeof("sowa.ibaraki.jp")];
+ char stringpool_str1356[sizeof("blogspot.tw")];
+ char stringpool_str1357[sizeof("kawaba.gunma.jp")];
+ char stringpool_str1358[sizeof("shintoku.hokkaido.jp")];
+ char stringpool_str1359[sizeof("slattum.no")];
+ char stringpool_str1360[sizeof("assassination.museum")];
+ char stringpool_str1361[sizeof("tara.saga.jp")];
+ char stringpool_str1362[sizeof("kijo.miyazaki.jp")];
+ char stringpool_str1363[sizeof("koebenhavn.museum")];
+ char stringpool_str1364[sizeof("services.aero")];
+ char stringpool_str1365[sizeof("cosenza.it")];
+ char stringpool_str1366[sizeof("koge.tottori.jp")];
+ char stringpool_str1367[sizeof("sopot.pl")];
+ char stringpool_str1368[sizeof("kuwana.mie.jp")];
+ char stringpool_str1369[sizeof("soja.okayama.jp")];
+ char stringpool_str1370[sizeof("sevastopol.ua")];
+ char stringpool_str1371[sizeof("malvik.no")];
+ char stringpool_str1372[sizeof("blogspot.hk")];
+ char stringpool_str1373[sizeof("austin.museum")];
+ char stringpool_str1374[sizeof("association.museum")];
+ char stringpool_str1375[sizeof("kota.aichi.jp")];
+ char stringpool_str1376[sizeof("koto.tokyo.jp")];
+ char stringpool_str1377[sizeof("babia-gora.pl")];
+ char stringpool_str1378[sizeof("vega.no")];
+ char stringpool_str1379[sizeof("siemens.om")];
+ char stringpool_str1380[sizeof("gmina.pl")];
+ char stringpool_str1381[sizeof("sor-varanger.no")];
+ char stringpool_str1382[sizeof("taku.saga.jp")];
+ char stringpool_str1383[sizeof("k12.in.us")];
+ char stringpool_str1384[sizeof("koto.shiga.jp")];
+ char stringpool_str1385[sizeof("roros.no")];
+ char stringpool_str1386[sizeof("budejju.no")];
+ char stringpool_str1387[sizeof("lorenskog.no")];
+ char stringpool_str1388[sizeof("toho.fukuoka.jp")];
+ char stringpool_str1389[sizeof("soma.fukushima.jp")];
+ char stringpool_str1390[sizeof("jeonbuk.kr")];
+ char stringpool_str1391[sizeof("froland.no")];
+ char stringpool_str1392[sizeof("sugito.saitama.jp")];
+ char stringpool_str1393[sizeof("hagebostad.no")];
+ char stringpool_str1394[sizeof("kumejima.okinawa.jp")];
+ char stringpool_str1395[sizeof("mitane.akita.jp")];
+ char stringpool_str1396[sizeof("biratori.hokkaido.jp")];
+ char stringpool_str1397[sizeof("kainan.wakayama.jp")];
+ char stringpool_str1398[sizeof("k12.il.us")];
+ char stringpool_str1399[sizeof("shinjo.yamagata.jp")];
+ char stringpool_str1400[sizeof("lib.in.us")];
+ char stringpool_str1401[sizeof("gjemnes.no")];
+ char stringpool_str1402[sizeof("copenhagen.museum")];
+ char stringpool_str1403[sizeof("firenze.it")];
+ char stringpool_str1404[sizeof("minano.saitama.jp")];
+ char stringpool_str1405[sizeof("kakinoki.shimane.jp")];
+ char stringpool_str1406[sizeof("za.net")];
+ char stringpool_str1407[sizeof("lib.il.us")];
+ char stringpool_str1408[sizeof("tozawa.yamagata.jp")];
+ char stringpool_str1409[sizeof("stavern.no")];
+ char stringpool_str1410[sizeof("chernovtsy.ua")];
+ char stringpool_str1411[sizeof("celtic.museum")];
+ char stringpool_str1412[sizeof("council.aero")];
+ char stringpool_str1413[sizeof("furano.hokkaido.jp")];
+ char stringpool_str1414[sizeof("greta.fr")];
+ char stringpool_str1415[sizeof("kyonan.chiba.jp")];
+ char stringpool_str1416[sizeof("minato.tokyo.jp")];
+ char stringpool_str1417[sizeof("kira.aichi.jp")];
+ char stringpool_str1418[sizeof("ringebu.no")];
+ char stringpool_str1419[sizeof("essex.museum")];
+ char stringpool_str1420[sizeof("tondabayashi.osaka.jp")];
+ char stringpool_str1421[sizeof("tsumagoi.gunma.jp")];
+ char stringpool_str1422[sizeof("qa")];
+ char stringpool_str1423[sizeof("amagasaki.hyogo.jp")];
+ char stringpool_str1424[sizeof("tanabe.wakayama.jp")];
+ char stringpool_str1425[sizeof("media.museum")];
+ char stringpool_str1426[sizeof("tanabe.kyoto.jp")];
+ char stringpool_str1427[sizeof("media.hu")];
+ char stringpool_str1428[sizeof("ashibetsu.hokkaido.jp")];
+ char stringpool_str1429[sizeof("gs.va.no")];
+ char stringpool_str1430[sizeof("co.bi")];
+ char stringpool_str1431[sizeof("somna.no")];
+ char stringpool_str1432[sizeof("shimane.jp")];
+ char stringpool_str1433[sizeof("gs.oslo.no")];
+ char stringpool_str1434[sizeof("q.bg")];
+ char stringpool_str1435[sizeof("kagawa.jp")];
+ char stringpool_str1436[sizeof("vaga.no")];
+ char stringpool_str1437[sizeof("kamoenai.hokkaido.jp")];
+ char stringpool_str1438[sizeof("association.aero")];
+ char stringpool_str1439[sizeof("swiebodzin.pl")];
+ char stringpool_str1440[sizeof("gamo.shiga.jp")];
+ char stringpool_str1441[sizeof("kaho.fukuoka.jp")];
+ char stringpool_str1442[sizeof("mikasa.hokkaido.jp")];
+ char stringpool_str1443[sizeof("joyo.kyoto.jp")];
+ char stringpool_str1444[sizeof("gangwon.kr")];
+ char stringpool_str1445[sizeof("aejrie.no")];
+ char stringpool_str1446[sizeof("toyo.kochi.jp")];
+ char stringpool_str1447[sizeof("adult.ht")];
+ char stringpool_str1448[sizeof("mima.tokushima.jp")];
+ char stringpool_str1449[sizeof("toyotomi.hokkaido.jp")];
+ char stringpool_str1450[sizeof("skanit.no")];
+ char stringpool_str1451[sizeof("saitama.jp")];
+ char stringpool_str1452[sizeof("sandiego.museum")];
+ char stringpool_str1453[sizeof("from-la.net")];
+ char stringpool_str1454[sizeof("smola.no")];
+ char stringpool_str1455[sizeof("gs.nt.no")];
+ char stringpool_str1456[sizeof("stranda.no")];
+ char stringpool_str1457[sizeof("sarpsborg.no")];
+ char stringpool_str1458[sizeof("furniture.museum")];
+ char stringpool_str1459[sizeof("rauma.no")];
+ char stringpool_str1460[sizeof("qld.au")];
+ char stringpool_str1461[sizeof("date.fukushima.jp")];
+ char stringpool_str1462[sizeof("katano.osaka.jp")];
+ char stringpool_str1463[sizeof("rennebu.no")];
+ char stringpool_str1464[sizeof("tas.edu.au")];
+ char stringpool_str1465[sizeof("anan.tokushima.jp")];
+ char stringpool_str1466[sizeof("turen.tn")];
+ char stringpool_str1467[sizeof("music.museum")];
+ char stringpool_str1468[sizeof("starnberg.museum")];
+ char stringpool_str1469[sizeof("murata.miyagi.jp")];
+ char stringpool_str1470[sizeof("sebastopol.ua")];
+ char stringpool_str1471[sizeof("minowa.nagano.jp")];
+ char stringpool_str1472[sizeof("verbania.it")];
+ char stringpool_str1473[sizeof("bergamo.it")];
+ char stringpool_str1474[sizeof("sakata.yamagata.jp")];
+ char stringpool_str1475[sizeof("chuo.tokyo.jp")];
+ char stringpool_str1476[sizeof("sumoto.hyogo.jp")];
+ char stringpool_str1477[sizeof("gorge.museum")];
+ char stringpool_str1478[sizeof("rikubetsu.hokkaido.jp")];
+ char stringpool_str1479[sizeof("asnes.no")];
+ char stringpool_str1480[sizeof("konin.pl")];
+ char stringpool_str1481[sizeof("monticello.museum")];
+ char stringpool_str1482[sizeof("sauda.no")];
+ char stringpool_str1483[sizeof("semine.miyagi.jp")];
+ char stringpool_str1484[sizeof("games.hu")];
+ char stringpool_str1485[sizeof("lezajsk.pl")];
+ char stringpool_str1486[sizeof("tolga.no")];
+ char stringpool_str1487[sizeof("ravenna.it")];
+ char stringpool_str1488[sizeof("chita.ru")];
+ char stringpool_str1489[sizeof("jeonnam.kr")];
+ char stringpool_str1490[sizeof("kunitachi.tokyo.jp")];
+ char stringpool_str1491[sizeof("dominic.ua")];
+ char stringpool_str1492[sizeof("svelvik.no")];
+ char stringpool_str1493[sizeof("motobu.okinawa.jp")];
+ char stringpool_str1494[sizeof("student.aero")];
+ char stringpool_str1495[sizeof("vestvagoy.no")];
+ char stringpool_str1496[sizeof("stadt.museum")];
+ char stringpool_str1497[sizeof("tvedestrand.no")];
+ char stringpool_str1498[sizeof("qsl.br")];
+ char stringpool_str1499[sizeof("venezia.it")];
+ char stringpool_str1500[sizeof("hokuto.hokkaido.jp")];
+ char stringpool_str1501[sizeof("kuzbass.ru")];
+ char stringpool_str1502[sizeof("selfip.com")];
+ char stringpool_str1503[sizeof("vaapste.no")];
+ char stringpool_str1504[sizeof("mukawa.hokkaido.jp")];
+ char stringpool_str1505[sizeof("kvitsoy.no")];
+ char stringpool_str1506[sizeof("campidanomedio.it")];
+ char stringpool_str1507[sizeof("berlin.museum")];
+ char stringpool_str1508[sizeof("hareid.no")];
+ char stringpool_str1509[sizeof("health.vn")];
+ char stringpool_str1510[sizeof("hokuto.yamanashi.jp")];
+ char stringpool_str1511[sizeof("gs.aa.no")];
+ char stringpool_str1512[sizeof("joetsu.niigata.jp")];
+ char stringpool_str1513[sizeof("mil.by")];
+ char stringpool_str1514[sizeof("hamatama.saga.jp")];
+ char stringpool_str1515[sizeof("takasu.hokkaido.jp")];
+ char stringpool_str1516[sizeof("granvin.no")];
+ char stringpool_str1517[sizeof("blogspot.gr")];
+ char stringpool_str1518[sizeof("sandnes.no")];
+ char stringpool_str1519[sizeof("tsunan.niigata.jp")];
+ char stringpool_str1520[sizeof("kiyose.tokyo.jp")];
+ char stringpool_str1521[sizeof("kawajima.saitama.jp")];
+ char stringpool_str1522[sizeof("alstahaug.no")];
+ char stringpool_str1523[sizeof("mil.sy")];
+ char stringpool_str1524[sizeof("takatsuki.osaka.jp")];
+ char stringpool_str1525[sizeof("6bone.pl")];
+ char stringpool_str1526[sizeof("skedsmo.no")];
+ char stringpool_str1527[sizeof("shiraoka.saitama.jp")];
+ char stringpool_str1528[sizeof("misato.miyagi.jp")];
+ char stringpool_str1529[sizeof("american.museum")];
+ char stringpool_str1530[sizeof("jelenia-gora.pl")];
+ char stringpool_str1531[sizeof("barlettatraniandria.it")];
+ char stringpool_str1532[sizeof("blogspot.sg")];
+ char stringpool_str1533[sizeof("kokubunji.tokyo.jp")];
+ char stringpool_str1534[sizeof("koga.ibaraki.jp")];
+ char stringpool_str1535[sizeof("sherbrooke.museum")];
+ char stringpool_str1536[sizeof("force.museum")];
+ char stringpool_str1537[sizeof("selfip.net")];
+ char stringpool_str1538[sizeof("lukow.pl")];
+ char stringpool_str1539[sizeof("skoczow.pl")];
+ char stringpool_str1540[sizeof("takasaki.gunma.jp")];
+ char stringpool_str1541[sizeof("hasvik.no")];
+ char stringpool_str1542[sizeof("tjome.no")];
+ char stringpool_str1543[sizeof("lubin.pl")];
+ char stringpool_str1544[sizeof("county.museum")];
+ char stringpool_str1545[sizeof("enebakk.no")];
+ char stringpool_str1546[sizeof("godo.gifu.jp")];
+ char stringpool_str1547[sizeof("kita.tokyo.jp")];
+ char stringpool_str1548[sizeof("catania.it")];
+ char stringpool_str1549[sizeof("giske.no")];
+ char stringpool_str1550[sizeof("vestnes.no")];
+ char stringpool_str1551[sizeof("taketomi.okinawa.jp")];
+ char stringpool_str1552[sizeof("forde.no")];
+ char stringpool_str1553[sizeof("toyono.osaka.jp")];
+ char stringpool_str1554[sizeof("for-the.biz")];
+ char stringpool_str1555[sizeof("toyonaka.osaka.jp")];
+ char stringpool_str1556[sizeof("mbone.pl")];
+ char stringpool_str1557[sizeof("donna.no")];
+ char stringpool_str1558[sizeof("texas.museum")];
+ char stringpool_str1559[sizeof("kitaakita.akita.jp")];
+ char stringpool_str1560[sizeof("hyllestad.no")];
+ char stringpool_str1561[sizeof("volkenkunde.museum")];
+ char stringpool_str1562[sizeof("komatsushima.tokushima.jp")];
+ char stringpool_str1563[sizeof("shinagawa.tokyo.jp")];
+ char stringpool_str1564[sizeof("takizawa.iwate.jp")];
+ char stringpool_str1565[sizeof("kirov.ru")];
+ char stringpool_str1566[sizeof("savannahga.museum")];
+ char stringpool_str1567[sizeof("tranby.no")];
+ char stringpool_str1568[sizeof("depot.museum")];
+ char stringpool_str1569[sizeof("kure.hiroshima.jp")];
+ char stringpool_str1570[sizeof("vestby.no")];
+ char stringpool_str1571[sizeof("trana.no")];
+ char stringpool_str1572[sizeof("kariwa.niigata.jp")];
+ char stringpool_str1573[sizeof("frogn.no")];
+ char stringpool_str1574[sizeof("kherson.ua")];
+ char stringpool_str1575[sizeof("volda.no")];
+ char stringpool_str1576[sizeof("loppa.no")];
+ char stringpool_str1577[sizeof("from-co.net")];
+ char stringpool_str1578[sizeof("dyndns.org")];
+ char stringpool_str1579[sizeof("juedisches.museum")];
+ char stringpool_str1580[sizeof("karlsoy.no")];
+ char stringpool_str1581[sizeof("miyota.nagano.jp")];
+ char stringpool_str1582[sizeof("suginami.tokyo.jp")];
+ char stringpool_str1583[sizeof("seranishi.hiroshima.jp")];
+ char stringpool_str1584[sizeof("ferrara.it")];
+ char stringpool_str1585[sizeof("frana.no")];
+ char stringpool_str1586[sizeof("kiyosato.hokkaido.jp")];
+ char stringpool_str1587[sizeof("cincinnati.museum")];
+ char stringpool_str1588[sizeof("gujo.gifu.jp")];
+ char stringpool_str1589[sizeof("trainer.aero")];
+ char stringpool_str1590[sizeof("loten.no")];
+ char stringpool_str1591[sizeof("agematsu.nagano.jp")];
+ char stringpool_str1592[sizeof("takazaki.miyazaki.jp")];
+ char stringpool_str1593[sizeof("lebesby.no")];
+ char stringpool_str1594[sizeof("mikawa.yamagata.jp")];
+ char stringpool_str1595[sizeof("tainai.niigata.jp")];
+ char stringpool_str1596[sizeof("chtr.k12.ma.us")];
+ char stringpool_str1597[sizeof("groundhandling.aero")];
+ char stringpool_str1598[sizeof("morotsuka.miyazaki.jp")];
+ char stringpool_str1599[sizeof("tama.tokyo.jp")];
+ char stringpool_str1600[sizeof("hazu.aichi.jp")];
+ char stringpool_str1601[sizeof("kayabe.hokkaido.jp")];
+ char stringpool_str1602[sizeof("johana.toyama.jp")];
+ char stringpool_str1603[sizeof("hiji.oita.jp")];
+ char stringpool_str1604[sizeof("spydeberg.no")];
+ char stringpool_str1605[sizeof("kragero.no")];
+ char stringpool_str1606[sizeof("mytis.ru")];
+ char stringpool_str1607[sizeof("moareke.no")];
+ char stringpool_str1608[sizeof("dolls.museum")];
+ char stringpool_str1609[sizeof("kunneppu.hokkaido.jp")];
+ char stringpool_str1610[sizeof("molde.no")];
+ char stringpool_str1611[sizeof("gulen.no")];
+ char stringpool_str1612[sizeof("koka.shiga.jp")];
+ char stringpool_str1613[sizeof("hannan.osaka.jp")];
+ char stringpool_str1614[sizeof("kurate.fukuoka.jp")];
+ char stringpool_str1615[sizeof("2000.hu")];
+ char stringpool_str1616[sizeof("kazan.ru")];
+ char stringpool_str1617[sizeof("sasebo.nagasaki.jp")];
+ char stringpool_str1618[sizeof("kuban.ru")];
+ char stringpool_str1619[sizeof("qh.cn")];
+ char stringpool_str1620[sizeof("americana.museum")];
+ char stringpool_str1621[sizeof("state.museum")];
+ char stringpool_str1622[sizeof("zlg.br")];
+ char stringpool_str1623[sizeof("from-me.org")];
+ char stringpool_str1624[sizeof("foggia.it")];
+ char stringpool_str1625[sizeof("land-4-sale.us")];
+ char stringpool_str1626[sizeof("madrid.museum")];
+ char stringpool_str1627[sizeof("futaba.fukushima.jp")];
+ char stringpool_str1628[sizeof("kawazu.shizuoka.jp")];
+ char stringpool_str1629[sizeof("bjugn.no")];
+ char stringpool_str1630[sizeof("grane.no")];
+ char stringpool_str1631[sizeof("koeln.museum")];
+ char stringpool_str1632[sizeof("shimamaki.hokkaido.jp")];
+ char stringpool_str1633[sizeof("ginoza.okinawa.jp")];
+ char stringpool_str1634[sizeof("saikai.nagasaki.jp")];
+ char stringpool_str1635[sizeof("game-host.org")];
+ char stringpool_str1636[sizeof("tokke.no")];
+ char stringpool_str1637[sizeof("agriculture.museum")];
+ char stringpool_str1638[sizeof("rivne.ua")];
+ char stringpool_str1639[sizeof("kyotamba.kyoto.jp")];
+ char stringpool_str1640[sizeof("gunma.jp")];
+ char stringpool_str1641[sizeof("hapmir.no")];
+ char stringpool_str1642[sizeof("from-ny.net")];
+ char stringpool_str1643[sizeof("tomi.nagano.jp")];
+ char stringpool_str1644[sizeof("salat.no")];
+ char stringpool_str1645[sizeof("sano.tochigi.jp")];
+ char stringpool_str1646[sizeof("barletta-trani-andria.it")];
+ char stringpool_str1647[sizeof("aosta.it")];
+ char stringpool_str1648[sizeof("kawanishi.nara.jp")];
+ char stringpool_str1649[sizeof("genoa.it")];
+ char stringpool_str1650[sizeof("togo.aichi.jp")];
+ char stringpool_str1651[sizeof("tsugaru.aomori.jp")];
+ char stringpool_str1652[sizeof("kokonoe.oita.jp")];
+ char stringpool_str1653[sizeof("busan.kr")];
+ char stringpool_str1654[sizeof("cesenaforli.it")];
+ char stringpool_str1655[sizeof("kuroishi.aomori.jp")];
+ char stringpool_str1656[sizeof("mugi.tokushima.jp")];
+ char stringpool_str1657[sizeof("shinjo.nara.jp")];
+ char stringpool_str1658[sizeof("shonai.fukuoka.jp")];
+ char stringpool_str1659[sizeof("kadena.okinawa.jp")];
+ char stringpool_str1660[sizeof("shiiba.miyazaki.jp")];
+ char stringpool_str1661[sizeof("agrinet.tn")];
+ char stringpool_str1662[sizeof("kyotango.kyoto.jp")];
+ char stringpool_str1663[sizeof("bronnoy.no")];
+ char stringpool_str1664[sizeof("fujinomiya.shizuoka.jp")];
+ char stringpool_str1665[sizeof("space.museum")];
+ char stringpool_str1666[sizeof("k12.wy.us")];
+ char stringpool_str1667[sizeof("skole.museum")];
+ char stringpool_str1668[sizeof("sera.hiroshima.jp")];
+ char stringpool_str1669[sizeof("biei.hokkaido.jp")];
+ char stringpool_str1670[sizeof("mombetsu.hokkaido.jp")];
+ char stringpool_str1671[sizeof("khabarovsk.ru")];
+ char stringpool_str1672[sizeof("bato.tochigi.jp")];
+ char stringpool_str1673[sizeof("kiso.nagano.jp")];
+ char stringpool_str1674[sizeof("klodzko.pl")];
+ char stringpool_str1675[sizeof("trapani.it")];
+ char stringpool_str1676[sizeof("esan.hokkaido.jp")];
+ char stringpool_str1677[sizeof("hakuba.nagano.jp")];
+ char stringpool_str1678[sizeof("better-than.tv")];
+ char stringpool_str1679[sizeof("hita.oita.jp")];
+ char stringpool_str1680[sizeof("mimata.miyazaki.jp")];
+ char stringpool_str1681[sizeof("togane.chiba.jp")];
+ char stringpool_str1682[sizeof("mibu.tochigi.jp")];
+ char stringpool_str1683[sizeof("lib.wy.us")];
+ char stringpool_str1684[sizeof("bandai.fukushima.jp")];
+ char stringpool_str1685[sizeof("hikawa.shimane.jp")];
+ char stringpool_str1686[sizeof("mino.gifu.jp")];
+ char stringpool_str1687[sizeof("date.hokkaido.jp")];
+ char stringpool_str1688[sizeof("shinjo.okayama.jp")];
+ char stringpool_str1689[sizeof("saga.saga.jp")];
+ char stringpool_str1690[sizeof("miyazaki.miyazaki.jp")];
+ char stringpool_str1691[sizeof("seki.gifu.jp")];
+ char stringpool_str1692[sizeof("lanbib.se")];
+ char stringpool_str1693[sizeof("christiansburg.museum")];
+ char stringpool_str1694[sizeof("skien.no")];
+ char stringpool_str1695[sizeof("k12.ny.us")];
+ char stringpool_str1696[sizeof("kani.gifu.jp")];
+ char stringpool_str1697[sizeof("homeip.net")];
+ char stringpool_str1698[sizeof("bo.telemark.no")];
+ char stringpool_str1699[sizeof("tsubetsu.hokkaido.jp")];
+ char stringpool_str1700[sizeof("sado.niigata.jp")];
+ char stringpool_str1701[sizeof("delmenhorst.museum")];
+ char stringpool_str1702[sizeof("store.st")];
+ char stringpool_str1703[sizeof("vikna.no")];
+ char stringpool_str1704[sizeof("toki.gifu.jp")];
+ char stringpool_str1705[sizeof("simbirsk.ru")];
+ char stringpool_str1706[sizeof("siena.it")];
+ char stringpool_str1707[sizeof("kawasaki.miyagi.jp")];
+ char stringpool_str1708[sizeof("hino.tokyo.jp")];
+ char stringpool_str1709[sizeof("lib.ny.us")];
+ char stringpool_str1710[sizeof("kamo.niigata.jp")];
+ char stringpool_str1711[sizeof("maebashi.gunma.jp")];
+ char stringpool_str1712[sizeof("tsaritsyn.ru")];
+ char stringpool_str1713[sizeof("balat.no")];
+ char stringpool_str1714[sizeof("rikuzentakata.iwate.jp")];
+ char stringpool_str1715[sizeof("gokase.miyazaki.jp")];
+ char stringpool_str1716[sizeof("aoki.nagano.jp")];
+ char stringpool_str1717[sizeof("toyosato.shiga.jp")];
+ char stringpool_str1718[sizeof("bryansk.ru")];
+ char stringpool_str1719[sizeof("koga.fukuoka.jp")];
+ char stringpool_str1720[sizeof("kaga.ishikawa.jp")];
+ char stringpool_str1721[sizeof("skaun.no")];
+ char stringpool_str1722[sizeof("mizunami.gifu.jp")];
+ char stringpool_str1723[sizeof("lesja.no")];
+ char stringpool_str1724[sizeof("sport.hu")];
+ char stringpool_str1725[sizeof("sannan.hyogo.jp")];
+ char stringpool_str1726[sizeof("kamiichi.toyama.jp")];
+ char stringpool_str1727[sizeof("holmestrand.no")];
+ char stringpool_str1728[sizeof("lakas.hu")];
+ char stringpool_str1729[sizeof("maniwa.okayama.jp")];
+ char stringpool_str1730[sizeof("makinohara.shizuoka.jp")];
+ char stringpool_str1731[sizeof("om")];
+ char stringpool_str1732[sizeof("agdenes.no")];
+ char stringpool_str1733[sizeof("store.ro")];
+ char stringpool_str1734[sizeof("baltimore.museum")];
+ char stringpool_str1735[sizeof("gov.tw")];
+ char stringpool_str1736[sizeof("edu.tw")];
+ char stringpool_str1737[sizeof("aoste.it")];
+ char stringpool_str1738[sizeof("com.tw")];
+ char stringpool_str1739[sizeof("daejeon.kr")];
+ char stringpool_str1740[sizeof("aukra.no")];
+ char stringpool_str1741[sizeof("flora.no")];
+ char stringpool_str1742[sizeof("com.aw")];
+ char stringpool_str1743[sizeof("miyazu.kyoto.jp")];
+ char stringpool_str1744[sizeof("leirvik.no")];
+ char stringpool_str1745[sizeof("gniezno.pl")];
+ char stringpool_str1746[sizeof("bronnoysund.no")];
+ char stringpool_str1747[sizeof("shiga.jp")];
+ char stringpool_str1748[sizeof("tokushima.jp")];
+ char stringpool_str1749[sizeof("vladimir.ru")];
+ char stringpool_str1750[sizeof("charter.aero")];
+ char stringpool_str1751[sizeof("or.it")];
+ char stringpool_str1752[sizeof("od.ua")];
+ char stringpool_str1753[sizeof("tempio-olbia.it")];
+ char stringpool_str1754[sizeof("turin.it")];
+ char stringpool_str1755[sizeof("moka.tochigi.jp")];
+ char stringpool_str1756[sizeof("ot.it")];
+ char stringpool_str1757[sizeof("journal.aero")];
+ char stringpool_str1758[sizeof("fukushima.jp")];
+ char stringpool_str1759[sizeof("botanicalgarden.museum")];
+ char stringpool_str1760[sizeof("taketa.oita.jp")];
+ char stringpool_str1761[sizeof("akaiwa.okayama.jp")];
+ char stringpool_str1762[sizeof("or.us")];
+ char stringpool_str1763[sizeof("baths.museum")];
+ char stringpool_str1764[sizeof("taranto.it")];
+ char stringpool_str1765[sizeof("kumenan.okayama.jp")];
+ char stringpool_str1766[sizeof("o.se")];
+ char stringpool_str1767[sizeof("bible.museum")];
+ char stringpool_str1768[sizeof("edu.cw")];
+ char stringpool_str1769[sizeof("com.cw")];
+ char stringpool_str1770[sizeof("fukusaki.hyogo.jp")];
+ char stringpool_str1771[sizeof("cagliari.it")];
+ char stringpool_str1772[sizeof("dovre.no")];
+ char stringpool_str1773[sizeof("fujiidera.osaka.jp")];
+ char stringpool_str1774[sizeof("kamiamakusa.kumamoto.jp")];
+ char stringpool_str1775[sizeof("odo.br")];
+ char stringpool_str1776[sizeof("barreau.bj")];
+ char stringpool_str1777[sizeof("virginia.museum")];
+ char stringpool_str1778[sizeof("saitama.saitama.jp")];
+ char stringpool_str1779[sizeof("takatsuki.shiga.jp")];
+ char stringpool_str1780[sizeof("soeda.fukuoka.jp")];
+ char stringpool_str1781[sizeof("anan.nagano.jp")];
+ char stringpool_str1782[sizeof("ec")];
+ char stringpool_str1783[sizeof("akita.jp")];
+ char stringpool_str1784[sizeof("cc")];
+ char stringpool_str1785[sizeof("sc")];
+ char stringpool_str1786[sizeof("ac")];
+ char stringpool_str1787[sizeof("kawatana.nagasaki.jp")];
+ char stringpool_str1788[sizeof("hemne.no")];
+ char stringpool_str1789[sizeof("bozen.it")];
+ char stringpool_str1790[sizeof("on.ca")];
+ char stringpool_str1791[sizeof("meiwa.mie.jp")];
+ char stringpool_str1792[sizeof("valle.no")];
+ char stringpool_str1793[sizeof("tamano.okayama.jp")];
+ char stringpool_str1794[sizeof("saku.nagano.jp")];
+ char stringpool_str1795[sizeof("educational.museum")];
+ char stringpool_str1796[sizeof("aioi.hyogo.jp")];
+ char stringpool_str1797[sizeof("himeji.hyogo.jp")];
+ char stringpool_str1798[sizeof("ac.ma")];
+ char stringpool_str1799[sizeof("fedje.no")];
+ char stringpool_str1800[sizeof("or.na")];
+ char stringpool_str1801[sizeof("shibetsu.hokkaido.jp")];
+ char stringpool_str1802[sizeof("rc.it")];
+ char stringpool_str1803[sizeof("colonialwilliamsburg.museum")];
+ char stringpool_str1804[sizeof("gc.ca")];
+ char stringpool_str1805[sizeof("kamitonda.wakayama.jp")];
+ char stringpool_str1806[sizeof("botany.museum")];
+ char stringpool_str1807[sizeof("sc.us")];
+ char stringpool_str1808[sizeof("o.bg")];
+ char stringpool_str1809[sizeof("kawanehon.shizuoka.jp")];
+ char stringpool_str1810[sizeof("kusatsu.shiga.jp")];
+ char stringpool_str1811[sizeof("mc")];
+ char stringpool_str1812[sizeof("surrey.museum")];
+ char stringpool_str1813[sizeof("lewismiller.museum")];
+ char stringpool_str1814[sizeof("shima.mie.jp")];
+ char stringpool_str1815[sizeof("takata.fukuoka.jp")];
+ char stringpool_str1816[sizeof("selje.no")];
+ char stringpool_str1817[sizeof("himeshima.oita.jp")];
+ char stringpool_str1818[sizeof("tc")];
+ char stringpool_str1819[sizeof("lc")];
+ char stringpool_str1820[sizeof("sumy.ua")];
+ char stringpool_str1821[sizeof("saka.hiroshima.jp")];
+ char stringpool_str1822[sizeof("lugansk.ua")];
+ char stringpool_str1823[sizeof("magnitka.ru")];
+ char stringpool_str1824[sizeof("mc.it")];
+ char stringpool_str1825[sizeof("ehime.jp")];
+ char stringpool_str1826[sizeof("aridagawa.wakayama.jp")];
+ char stringpool_str1827[sizeof("lc.it")];
+ char stringpool_str1828[sizeof("express.aero")];
+ char stringpool_str1829[sizeof("eco.br")];
+ char stringpool_str1830[sizeof("or.at")];
+ char stringpool_str1831[sizeof("embetsu.hokkaido.jp")];
+ char stringpool_str1832[sizeof("sobetsu.hokkaido.jp")];
+ char stringpool_str1833[sizeof("takino.hyogo.jp")];
+ char stringpool_str1834[sizeof("act.au")];
+ char stringpool_str1835[sizeof("kanazawa.ishikawa.jp")];
+ char stringpool_str1836[sizeof("ac.pa")];
+ char stringpool_str1837[sizeof("fc.it")];
+ char stringpool_str1838[sizeof("cody.museum")];
+ char stringpool_str1839[sizeof("hirata.fukushima.jp")];
+ char stringpool_str1840[sizeof("shimane.shimane.jp")];
+ char stringpool_str1841[sizeof("britishcolumbia.museum")];
+ char stringpool_str1842[sizeof("gdynia.pl")];
+ char stringpool_str1843[sizeof("rec.co")];
+ char stringpool_str1844[sizeof("genkai.saga.jp")];
+ char stringpool_str1845[sizeof("etc.br")];
+ char stringpool_str1846[sizeof("shikabe.hokkaido.jp")];
+ char stringpool_str1847[sizeof("cc.na")];
+ char stringpool_str1848[sizeof("aikawa.kanagawa.jp")];
+ char stringpool_str1849[sizeof("ac.tj")];
+ char stringpool_str1850[sizeof("oh.us")];
+ char stringpool_str1851[sizeof("hikone.shiga.jp")];
+ char stringpool_str1852[sizeof("ok.us")];
+ char stringpool_str1853[sizeof("rec.br")];
+ char stringpool_str1854[sizeof("hirono.fukushima.jp")];
+ char stringpool_str1855[sizeof("hiroshima.jp")];
+ char stringpool_str1856[sizeof("omsk.ru")];
+ char stringpool_str1857[sizeof("dc.us")];
+ char stringpool_str1858[sizeof("bryne.no")];
+ char stringpool_str1859[sizeof("bushey.museum")];
+ char stringpool_str1860[sizeof("lillehammer.no")];
+ char stringpool_str1861[sizeof("kawanishi.hyogo.jp")];
+ char stringpool_str1862[sizeof("tobetsu.hokkaido.jp")];
+ char stringpool_str1863[sizeof("stor-elvdal.no")];
+ char stringpool_str1864[sizeof("mil.tw")];
+ char stringpool_str1865[sizeof("or.tz")];
+ char stringpool_str1866[sizeof("bc.ca")];
+ char stringpool_str1867[sizeof("sogne.no")];
+ char stringpool_str1868[sizeof("shimabara.nagasaki.jp")];
+ char stringpool_str1869[sizeof("royrvik.no")];
+ char stringpool_str1870[sizeof("vc")];
+ char stringpool_str1871[sizeof("or.pw")];
+ char stringpool_str1872[sizeof("shibecha.hokkaido.jp")];
+ char stringpool_str1873[sizeof("ac.me")];
+ char stringpool_str1874[sizeof("hornindal.no")];
+ char stringpool_str1875[sizeof("aibetsu.hokkaido.jp")];
+ char stringpool_str1876[sizeof("hawaii.museum")];
+ char stringpool_str1877[sizeof("vc.it")];
+ char stringpool_str1878[sizeof("sydney.museum")];
+ char stringpool_str1879[sizeof("ecn.br")];
+ char stringpool_str1880[sizeof("kamiizumi.saitama.jp")];
+ char stringpool_str1881[sizeof("valley.museum")];
+ char stringpool_str1882[sizeof("suwa.nagano.jp")];
+ char stringpool_str1883[sizeof("ac.at")];
+ char stringpool_str1884[sizeof("ac.mw")];
+ char stringpool_str1885[sizeof("erotika.hu")];
+ char stringpool_str1886[sizeof("quebec.museum")];
+ char stringpool_str1887[sizeof("kanmaki.nara.jp")];
+ char stringpool_str1888[sizeof("tagajo.miyagi.jp")];
+ char stringpool_str1889[sizeof("sch.ae")];
+ char stringpool_str1890[sizeof("musashino.tokyo.jp")];
+ char stringpool_str1891[sizeof("toya.hokkaido.jp")];
+ char stringpool_str1892[sizeof("jgora.pl")];
+ char stringpool_str1893[sizeof("shimada.shizuoka.jp")];
+ char stringpool_str1894[sizeof("reggioemilia.it")];
+ char stringpool_str1895[sizeof("sch.qa")];
+ char stringpool_str1896[sizeof("hitra.no")];
+ char stringpool_str1897[sizeof("kesennuma.miyagi.jp")];
+ char stringpool_str1898[sizeof("erotica.hu")];
+ char stringpool_str1899[sizeof("fujieda.shizuoka.jp")];
+ char stringpool_str1900[sizeof("sch.sa")];
+ char stringpool_str1901[sizeof("sch.jo")];
+ char stringpool_str1902[sizeof("sch.je")];
+ char stringpool_str1903[sizeof("honbetsu.hokkaido.jp")];
+ char stringpool_str1904[sizeof("fujisawa.kanagawa.jp")];
+ char stringpool_str1905[sizeof("ac.gn")];
+ char stringpool_str1906[sizeof("chattanooga.museum")];
+ char stringpool_str1907[sizeof("ac.in")];
+ char stringpool_str1908[sizeof("hida.gifu.jp")];
+ char stringpool_str1909[sizeof("rybnik.pl")];
+ char stringpool_str1910[sizeof("sc.cn")];
+ char stringpool_str1911[sizeof("kibichuo.okayama.jp")];
+ char stringpool_str1912[sizeof("ac.cn")];
+ char stringpool_str1913[sizeof("showa.fukushima.jp")];
+ char stringpool_str1914[sizeof("sc.tz")];
+ char stringpool_str1915[sizeof("ac.tz")];
+ char stringpool_str1916[sizeof("himi.toyama.jp")];
+ char stringpool_str1917[sizeof("shiranuka.hokkaido.jp")];
+ char stringpool_str1918[sizeof("miki.hyogo.jp")];
+ char stringpool_str1919[sizeof("sci.eg")];
+ char stringpool_str1920[sizeof("for-better.biz")];
+ char stringpool_str1921[sizeof("orkdal.no")];
+ char stringpool_str1922[sizeof("kaita.hiroshima.jp")];
+ char stringpool_str1923[sizeof("milan.it")];
+ char stringpool_str1924[sizeof("selfip.org")];
+ char stringpool_str1925[sizeof("gorlice.pl")];
+ char stringpool_str1926[sizeof("buzen.fukuoka.jp")];
+ char stringpool_str1927[sizeof("ac.vn")];
+ char stringpool_str1928[sizeof("tranibarlettaandria.it")];
+ char stringpool_str1929[sizeof("asaka.saitama.jp")];
+ char stringpool_str1930[sizeof("store.nf")];
+ char stringpool_str1931[sizeof("vic.au")];
+ char stringpool_str1932[sizeof("tsuwano.shimane.jp")];
+ char stringpool_str1933[sizeof("kusatsu.gunma.jp")];
+ char stringpool_str1934[sizeof("orenburg.ru")];
+ char stringpool_str1935[sizeof("takasago.hyogo.jp")];
+ char stringpool_str1936[sizeof("ginowan.okinawa.jp")];
+ char stringpool_str1937[sizeof("daiwa.hiroshima.jp")];
+ char stringpool_str1938[sizeof("oita.jp")];
+ char stringpool_str1939[sizeof("ac.ae")];
+ char stringpool_str1940[sizeof("targi.pl")];
+ char stringpool_str1941[sizeof("bearalvahki.no")];
+ char stringpool_str1942[sizeof("bykle.no")];
+ char stringpool_str1943[sizeof("shinshinotsu.hokkaido.jp")];
+ char stringpool_str1944[sizeof("koshigaya.saitama.jp")];
+ char stringpool_str1945[sizeof("davvenjarga.no")];
+ char stringpool_str1946[sizeof("kagoshima.jp")];
+ char stringpool_str1947[sizeof("kamaishi.iwate.jp")];
+ char stringpool_str1948[sizeof("hara.nagano.jp")];
+ char stringpool_str1949[sizeof("asaminami.hiroshima.jp")];
+ char stringpool_str1950[sizeof("hakata.fukuoka.jp")];
+ char stringpool_str1951[sizeof("kaminoyama.yamagata.jp")];
+ char stringpool_str1952[sizeof("hanawa.fukushima.jp")];
+ char stringpool_str1953[sizeof("hadano.kanagawa.jp")];
+ char stringpool_str1954[sizeof("gliwice.pl")];
+ char stringpool_str1955[sizeof("bygland.no")];
+ char stringpool_str1956[sizeof("chiba.jp")];
+ char stringpool_str1957[sizeof("tsubame.niigata.jp")];
+ char stringpool_str1958[sizeof("home.dyndns.org")];
+ char stringpool_str1959[sizeof("hiraizumi.iwate.jp")];
+ char stringpool_str1960[sizeof("trani-andria-barletta.it")];
+ char stringpool_str1961[sizeof("katashina.gunma.jp")];
+ char stringpool_str1962[sizeof("oppdal.no")];
+ char stringpool_str1963[sizeof("hakone.kanagawa.jp")];
+ char stringpool_str1964[sizeof("tourism.tn")];
+ char stringpool_str1965[sizeof("ac.rs")];
+ char stringpool_str1966[sizeof("trani-barletta-andria.it")];
+ char stringpool_str1967[sizeof("karatsu.saga.jp")];
+ char stringpool_str1968[sizeof("oksnes.no")];
+ char stringpool_str1969[sizeof("andriabarlettatrani.it")];
+ char stringpool_str1970[sizeof("ancona.it")];
+ char stringpool_str1971[sizeof("americanantiques.museum")];
+ char stringpool_str1972[sizeof("altai.ru")];
+ char stringpool_str1973[sizeof("orland.no")];
+ char stringpool_str1974[sizeof("tokushima.tokushima.jp")];
+ char stringpool_str1975[sizeof("miyashiro.saitama.jp")];
+ char stringpool_str1976[sizeof("zarow.pl")];
+ char stringpool_str1977[sizeof("seika.kyoto.jp")];
+ char stringpool_str1978[sizeof("tsukigata.hokkaido.jp")];
+ char stringpool_str1979[sizeof("treviso.it")];
+ char stringpool_str1980[sizeof("tome.miyagi.jp")];
+ char stringpool_str1981[sizeof("kurashiki.okayama.jp")];
+ char stringpool_str1982[sizeof("sciences.museum")];
+ char stringpool_str1983[sizeof("resistance.museum")];
+ char stringpool_str1984[sizeof("taka.hyogo.jp")];
+ char stringpool_str1985[sizeof("forlicesena.it")];
+ char stringpool_str1986[sizeof("sayo.hyogo.jp")];
+ char stringpool_str1987[sizeof("takaishi.osaka.jp")];
+ char stringpool_str1988[sizeof("rygge.no")];
+ char stringpool_str1989[sizeof("org")];
+ char stringpool_str1990[sizeof("og.it")];
+ char stringpool_str1991[sizeof("oslo.no")];
+ char stringpool_str1992[sizeof("org.to")];
+ char stringpool_str1993[sizeof("org.tm")];
+ char stringpool_str1994[sizeof("ama.shimane.jp")];
+ char stringpool_str1995[sizeof("org.bo")];
+ char stringpool_str1996[sizeof("org.bm")];
+ char stringpool_str1997[sizeof("org.ae")];
+ char stringpool_str1998[sizeof("servebbs.com")];
+ char stringpool_str1999[sizeof("org.bi")];
+ char stringpool_str2000[sizeof("org.bz")];
+ char stringpool_str2001[sizeof("org.ai")];
+ char stringpool_str2002[sizeof("org.az")];
+ char stringpool_str2003[sizeof("airline.aero")];
+ char stringpool_str2004[sizeof("org.ag")];
+ char stringpool_str2005[sizeof("gliding.aero")];
+ char stringpool_str2006[sizeof("gf")];
+ char stringpool_str2007[sizeof("vinnica.ua")];
+ char stringpool_str2008[sizeof("muika.niigata.jp")];
+ char stringpool_str2009[sizeof("cf")];
+ char stringpool_str2010[sizeof("org.qa")];
+ char stringpool_str2011[sizeof("af")];
+ char stringpool_str2012[sizeof("org.ba")];
+ char stringpool_str2013[sizeof("org.so")];
+ char stringpool_str2014[sizeof("org.se")];
+ char stringpool_str2015[sizeof("org.sd")];
+ char stringpool_str2016[sizeof("trading.aero")];
+ char stringpool_str2017[sizeof("org.bs")];
+ char stringpool_str2018[sizeof("osen.no")];
+ char stringpool_str2019[sizeof("org.sz")];
+ char stringpool_str2020[sizeof("odda.no")];
+ char stringpool_str2021[sizeof("org.sg")];
+ char stringpool_str2022[sizeof("satte.saitama.jp")];
+ char stringpool_str2023[sizeof("org.ac")];
+ char stringpool_str2024[sizeof("org.sa")];
+ char stringpool_str2025[sizeof("chigasaki.kanagawa.jp")];
+ char stringpool_str2026[sizeof("org.jo")];
+ char stringpool_str2027[sizeof("org.je")];
+ char stringpool_str2028[sizeof("eniwa.hokkaido.jp")];
+ char stringpool_str2029[sizeof("ryokami.saitama.jp")];
+ char stringpool_str2030[sizeof("ome.tokyo.jp")];
+ char stringpool_str2031[sizeof("org.bb")];
+ char stringpool_str2032[sizeof("org.sc")];
+ char stringpool_str2033[sizeof("online.museum")];
+ char stringpool_str2034[sizeof("kami.miyagi.jp")];
+ char stringpool_str2035[sizeof("andria-trani-barletta.it")];
+ char stringpool_str2036[sizeof("aeroclub.aero")];
+ char stringpool_str2037[sizeof("org.sb")];
+ char stringpool_str2038[sizeof("musashimurayama.tokyo.jp")];
+ char stringpool_str2039[sizeof("tateshina.nagano.jp")];
+ char stringpool_str2040[sizeof("komatsu.ishikawa.jp")];
+ char stringpool_str2041[sizeof("tohma.hokkaido.jp")];
+ char stringpool_str2042[sizeof("org.au")];
+ char stringpool_str2043[sizeof("shinichi.hiroshima.jp")];
+ char stringpool_str2044[sizeof("org.co")];
+ char stringpool_str2045[sizeof("org.tt")];
+ char stringpool_str2046[sizeof("org.ci")];
+ char stringpool_str2047[sizeof("org.bt")];
+ char stringpool_str2048[sizeof("city.hu")];
+ char stringpool_str2049[sizeof("shikaoi.hokkaido.jp")];
+ char stringpool_str2050[sizeof("kamisunagawa.hokkaido.jp")];
+ char stringpool_str2051[sizeof("chuo.yamanashi.jp")];
+ char stringpool_str2052[sizeof("tf")];
+ char stringpool_str2053[sizeof("org.tn")];
+ char stringpool_str2054[sizeof("org.st")];
+ char stringpool_str2055[sizeof("org.br")];
+ char stringpool_str2056[sizeof("org.an")];
+ char stringpool_str2057[sizeof("tamatsukuri.ibaraki.jp")];
+ char stringpool_str2058[sizeof("zama.kanagawa.jp")];
+ char stringpool_str2059[sizeof("ac.rw")];
+ char stringpool_str2060[sizeof("vagan.no")];
+ char stringpool_str2061[sizeof("toga.toyama.jp")];
+ char stringpool_str2062[sizeof("org.sn")];
+ char stringpool_str2063[sizeof("conference.aero")];
+ char stringpool_str2064[sizeof("tourism.pl")];
+ char stringpool_str2065[sizeof("org.al")];
+ char stringpool_str2066[sizeof("fukushima.hokkaido.jp")];
+ char stringpool_str2067[sizeof("certification.aero")];
+ char stringpool_str2068[sizeof("british-library.uk")];
+ char stringpool_str2069[sizeof("org.cu")];
+ char stringpool_str2070[sizeof("rec.nf")];
+ char stringpool_str2071[sizeof("org.sl")];
+ char stringpool_str2072[sizeof("ftpaccess.cc")];
+ char stringpool_str2073[sizeof("satosho.okayama.jp")];
+ char stringpool_str2074[sizeof("tagawa.fukuoka.jp")];
+ char stringpool_str2075[sizeof("org.tj")];
+ char stringpool_str2076[sizeof("maibara.shiga.jp")];
+ char stringpool_str2077[sizeof("org.cn")];
+ char stringpool_str2078[sizeof("karuizawa.nagano.jp")];
+ char stringpool_str2079[sizeof("bf")];
+ char stringpool_str2080[sizeof("zentsuji.kagawa.jp")];
+ char stringpool_str2081[sizeof("americanart.museum")];
+ char stringpool_str2082[sizeof("kodaira.tokyo.jp")];
+ char stringpool_str2083[sizeof("gosen.niigata.jp")];
+ char stringpool_str2084[sizeof("orkanger.no")];
+ char stringpool_str2085[sizeof("ota.tokyo.jp")];
+ char stringpool_str2086[sizeof("tokashiki.okinawa.jp")];
+ char stringpool_str2087[sizeof("org.ee")];
+ char stringpool_str2088[sizeof("est-a-la-maison.com")];
+ char stringpool_str2089[sizeof("oji.nara.jp")];
+ char stringpool_str2090[sizeof("ac.be")];
+ char stringpool_str2091[sizeof("org.eg")];
+ char stringpool_str2092[sizeof("org.es")];
+ char stringpool_str2093[sizeof("reggiocalabria.it")];
+ char stringpool_str2094[sizeof("kannami.shizuoka.jp")];
+ char stringpool_str2095[sizeof("est-a-la-masion.com")];
+ char stringpool_str2096[sizeof("org.ec")];
+ char stringpool_str2097[sizeof("org.ws")];
+ char stringpool_str2098[sizeof("arita.saga.jp")];
+ char stringpool_str2099[sizeof("qld.edu.au")];
+ char stringpool_str2100[sizeof("operaunite.com")];
+ char stringpool_str2101[sizeof("muroto.kochi.jp")];
+ char stringpool_str2102[sizeof("kitanakagusuku.okinawa.jp")];
+ char stringpool_str2103[sizeof("likes-pie.com")];
+ char stringpool_str2104[sizeof("ami.ibaraki.jp")];
+ char stringpool_str2105[sizeof("lapy.pl")];
+ char stringpool_str2106[sizeof("gushikami.okinawa.jp")];
+ char stringpool_str2107[sizeof("no")];
+ char stringpool_str2108[sizeof("nu")];
+ char stringpool_str2109[sizeof("beskidy.pl")];
+ char stringpool_str2110[sizeof("org.ng")];
+ char stringpool_str2111[sizeof("oirase.aomori.jp")];
+ char stringpool_str2112[sizeof("nz")];
+ char stringpool_str2113[sizeof("rebun.hokkaido.jp")];
+ char stringpool_str2114[sizeof("meiwa.gunma.jp")];
+ char stringpool_str2115[sizeof("nom.tm")];
+ char stringpool_str2116[sizeof("org.na")];
+ char stringpool_str2117[sizeof("shibata.niigata.jp")];
+ char stringpool_str2118[sizeof("nom.ad")];
+ char stringpool_str2119[sizeof("nr")];
+ char stringpool_str2120[sizeof("no.it")];
+ char stringpool_str2121[sizeof("nu.it")];
+ char stringpool_str2122[sizeof("nom.ag")];
+ char stringpool_str2123[sizeof("showa.gunma.jp")];
+ char stringpool_str2124[sizeof("kikuchi.kumamoto.jp")];
+ char stringpool_str2125[sizeof("nu.ca")];
+ char stringpool_str2126[sizeof("hashikami.aomori.jp")];
+ char stringpool_str2127[sizeof("ne")];
+ char stringpool_str2128[sizeof("nv.us")];
+ char stringpool_str2129[sizeof("nd.us")];
+ char stringpool_str2130[sizeof("nm.us")];
+ char stringpool_str2131[sizeof("konyvelo.hu")];
+ char stringpool_str2132[sizeof("kashihara.nara.jp")];
+ char stringpool_str2133[sizeof("buryatia.ru")];
+ char stringpool_str2134[sizeof("nt.ca")];
+ char stringpool_str2135[sizeof("shikama.miyagi.jp")];
+ char stringpool_str2136[sizeof("gifu.jp")];
+ char stringpool_str2137[sizeof("elb.amazonaws.com")];
+ char stringpool_str2138[sizeof("koganei.tokyo.jp")];
+ char stringpool_str2139[sizeof("ns.ca")];
+ char stringpool_str2140[sizeof("arida.wakayama.jp")];
+ char stringpool_str2141[sizeof("eu.com")];
+ char stringpool_str2142[sizeof("ovre-eiker.no")];
+ char stringpool_str2143[sizeof("net")];
+ char stringpool_str2144[sizeof("ru.com")];
+ char stringpool_str2145[sizeof("n.se")];
+ char stringpool_str2146[sizeof("nom.co")];
+ char stringpool_str2147[sizeof("net.to")];
+ char stringpool_str2148[sizeof("net.tm")];
+ char stringpool_str2149[sizeof("net.bo")];
+ char stringpool_str2150[sizeof("net.bm")];
+ char stringpool_str2151[sizeof("org.nr")];
+ char stringpool_str2152[sizeof("net.ae")];
+ char stringpool_str2153[sizeof("haga.tochigi.jp")];
+ char stringpool_str2154[sizeof("gr.com")];
+ char stringpool_str2155[sizeof("net.bz")];
+ char stringpool_str2156[sizeof("net.ai")];
+ char stringpool_str2157[sizeof("net.az")];
+ char stringpool_str2158[sizeof("kyowa.hokkaido.jp")];
+ char stringpool_str2159[sizeof("ar.com")];
+ char stringpool_str2160[sizeof("net.ag")];
+ char stringpool_str2161[sizeof("ne.us")];
+ char stringpool_str2162[sizeof("os.hedmark.no")];
+ char stringpool_str2163[sizeof("ni")];
+ char stringpool_str2164[sizeof("net.qa")];
+ char stringpool_str2165[sizeof("groks-the.info")];
+ char stringpool_str2166[sizeof("net.ba")];
+ char stringpool_str2167[sizeof("net.so")];
+ char stringpool_str2168[sizeof("net.sd")];
+ char stringpool_str2169[sizeof("toshima.tokyo.jp")];
+ char stringpool_str2170[sizeof("kchr.ru")];
+ char stringpool_str2171[sizeof("nom.br")];
+ char stringpool_str2172[sizeof("maritime.museum")];
+ char stringpool_str2173[sizeof("net.bs")];
+ char stringpool_str2174[sizeof("kikonai.hokkaido.jp")];
+ char stringpool_str2175[sizeof("net.sg")];
+ char stringpool_str2176[sizeof("kr.com")];
+ char stringpool_str2177[sizeof("nj.us")];
+ char stringpool_str2178[sizeof("jerusalem.museum")];
+ char stringpool_str2179[sizeof("net.ac")];
+ char stringpool_str2180[sizeof("rieti.it")];
+ char stringpool_str2181[sizeof("net.sa")];
+ char stringpool_str2182[sizeof("net.jo")];
+ char stringpool_str2183[sizeof("net.je")];
+ char stringpool_str2184[sizeof("laquila.it")];
+ char stringpool_str2185[sizeof("se.com")];
+ char stringpool_str2186[sizeof("takashima.shiga.jp")];
+ char stringpool_str2187[sizeof("shobara.hiroshima.jp")];
+ char stringpool_str2188[sizeof("net.bb")];
+ char stringpool_str2189[sizeof("terni.it")];
+ char stringpool_str2190[sizeof("not.br")];
+ char stringpool_str2191[sizeof("tempioolbia.it")];
+ char stringpool_str2192[sizeof("net.sc")];
+ char stringpool_str2193[sizeof("space-to-rent.com")];
+ char stringpool_str2194[sizeof("oshu.iwate.jp")];
+ char stringpool_str2195[sizeof("omi.nagano.jp")];
+ char stringpool_str2196[sizeof("nl")];
+ char stringpool_str2197[sizeof("org.af")];
+ char stringpool_str2198[sizeof("kanna.gunma.jp")];
+ char stringpool_str2199[sizeof("net.sb")];
+ char stringpool_str2200[sizeof("ntr.br")];
+ char stringpool_str2201[sizeof("net.au")];
+ char stringpool_str2202[sizeof("shirataka.yamagata.jp")];
+ char stringpool_str2203[sizeof("showa.yamanashi.jp")];
+ char stringpool_str2204[sizeof("net.co")];
+ char stringpool_str2205[sizeof("na")];
+ char stringpool_str2206[sizeof("net.tt")];
+ char stringpool_str2207[sizeof("net.ci")];
+ char stringpool_str2208[sizeof("net.bt")];
+ char stringpool_str2209[sizeof("org.ve")];
+ char stringpool_str2210[sizeof("org.vi")];
+ char stringpool_str2211[sizeof("nl.ca")];
+ char stringpool_str2212[sizeof("co.no")];
+ char stringpool_str2213[sizeof("na.it")];
+ char stringpool_str2214[sizeof("net.tn")];
+ char stringpool_str2215[sizeof("city.kawasaki.jp")];
+ char stringpool_str2216[sizeof("net.st")];
+ char stringpool_str2217[sizeof("net.br")];
+ char stringpool_str2218[sizeof("adygeya.ru")];
+ char stringpool_str2219[sizeof("hiranai.aomori.jp")];
+ char stringpool_str2220[sizeof("net.an")];
+ char stringpool_str2221[sizeof("sellsyourhome.org")];
+ char stringpool_str2222[sizeof("n.bg")];
+ char stringpool_str2223[sizeof("cn.com")];
+ char stringpool_str2224[sizeof("asahikawa.hokkaido.jp")];
+ char stringpool_str2225[sizeof("olkusz.pl")];
+ char stringpool_str2226[sizeof("org.vc")];
+ char stringpool_str2227[sizeof("name")];
+ char stringpool_str2228[sizeof("turystyka.pl")];
+ char stringpool_str2229[sizeof("codespot.com")];
+ char stringpool_str2230[sizeof("ogliastra.it")];
+ char stringpool_str2231[sizeof("np")];
+ char stringpool_str2232[sizeof("nb.ca")];
+ char stringpool_str2233[sizeof("net.al")];
+ char stringpool_str2234[sizeof("st.no")];
+ char stringpool_str2235[sizeof("corvette.museum")];
+ char stringpool_str2236[sizeof("nom.es")];
+ char stringpool_str2237[sizeof("net.cu")];
+ char stringpool_str2238[sizeof("net.sl")];
+ char stringpool_str2239[sizeof("name.mv")];
+ char stringpool_str2240[sizeof("name.eg")];
+ char stringpool_str2241[sizeof("fukui.jp")];
+ char stringpool_str2242[sizeof("or.ci")];
+ char stringpool_str2243[sizeof("sa.com")];
+ char stringpool_str2244[sizeof("artcenter.museum")];
+ char stringpool_str2245[sizeof("tadotsu.kagawa.jp")];
+ char stringpool_str2246[sizeof("net.tj")];
+ char stringpool_str2247[sizeof("de.com")];
+ char stringpool_str2248[sizeof("aogashima.tokyo.jp")];
+ char stringpool_str2249[sizeof("org.bh")];
+ char stringpool_str2250[sizeof("br.com")];
+ char stringpool_str2251[sizeof("abira.hokkaido.jp")];
+ char stringpool_str2252[sizeof("gb.com")];
+ char stringpool_str2253[sizeof("net.cn")];
+ char stringpool_str2254[sizeof("tm.no")];
+ char stringpool_str2255[sizeof("hitachiota.ibaraki.jp")];
+ char stringpool_str2256[sizeof("mr.no")];
+ char stringpool_str2257[sizeof("org.vn")];
+ char stringpool_str2258[sizeof("akune.kagoshima.jp")];
+ char stringpool_str2259[sizeof("org.sh")];
+ char stringpool_str2260[sizeof("kicks-ass.org")];
+ char stringpool_str2261[sizeof("taira.toyama.jp")];
+ char stringpool_str2262[sizeof("gv.ao")];
+ char stringpool_str2263[sizeof("dgca.aero")];
+ char stringpool_str2264[sizeof("ed.ao")];
+ char stringpool_str2265[sizeof("co.ao")];
+ char stringpool_str2266[sizeof("tr.no")];
+ char stringpool_str2267[sizeof("groks-this.info")];
+ char stringpool_str2268[sizeof("name.mk")];
+ char stringpool_str2269[sizeof("fm.no")];
+ char stringpool_str2270[sizeof("nh.us")];
+ char stringpool_str2271[sizeof("shiraoi.hokkaido.jp")];
+ char stringpool_str2272[sizeof("net.eg")];
+ char stringpool_str2273[sizeof("taiwa.miyagi.jp")];
+ char stringpool_str2274[sizeof("obu.aichi.jp")];
+ char stringpool_str2275[sizeof("nat.tn")];
+ char stringpool_str2276[sizeof("kawanishi.yamagata.jp")];
+ char stringpool_str2277[sizeof("statecouncil.om")];
+ char stringpool_str2278[sizeof("nm.cn")];
+ char stringpool_str2279[sizeof("eastafrica.museum")];
+ char stringpool_str2280[sizeof("nx.cn")];
+ char stringpool_str2281[sizeof("tsubata.ishikawa.jp")];
+ char stringpool_str2282[sizeof("sor-odal.no")];
+ char stringpool_str2283[sizeof("net.ec")];
+ char stringpool_str2284[sizeof("recreation.aero")];
+ char stringpool_str2285[sizeof("name.tt")];
+ char stringpool_str2286[sizeof("ouda.nara.jp")];
+ char stringpool_str2287[sizeof("ogi.saga.jp")];
+ char stringpool_str2288[sizeof("hiratsuka.kanagawa.jp")];
+ char stringpool_str2289[sizeof("aga.niigata.jp")];
+ char stringpool_str2290[sizeof("net.ws")];
+ char stringpool_str2291[sizeof("al.no")];
+ char stringpool_str2292[sizeof("name.tj")];
+ char stringpool_str2293[sizeof("rl.no")];
+ char stringpool_str2294[sizeof("blogsite.org")];
+ char stringpool_str2295[sizeof("bu.no")];
+ char stringpool_str2296[sizeof("for-some.biz")];
+ char stringpool_str2297[sizeof("tatebayashi.gunma.jp")];
+ char stringpool_str2298[sizeof("hakusan.ishikawa.jp")];
+ char stringpool_str2299[sizeof("aa.no")];
+ char stringpool_str2300[sizeof("ne.tz")];
+ char stringpool_str2301[sizeof("legnica.pl")];
+ char stringpool_str2302[sizeof("ac.ci")];
+ char stringpool_str2303[sizeof("blogspot.cv")];
+ char stringpool_str2304[sizeof("chambagri.fr")];
+ char stringpool_str2305[sizeof("blogspot.cz")];
+ char stringpool_str2306[sizeof("net.ng")];
+ char stringpool_str2307[sizeof("dyndns.biz")];
+ char stringpool_str2308[sizeof("ne.pw")];
+ char stringpool_str2309[sizeof("teshikaga.hokkaido.jp")];
+ char stringpool_str2310[sizeof("sande.more-og-romsdal.no")];
+ char stringpool_str2311[sizeof("higashiizu.shizuoka.jp")];
+ char stringpool_str2312[sizeof("vladikavkaz.ru")];
+ char stringpool_str2313[sizeof("ringsaker.no")];
+ char stringpool_str2314[sizeof("taiki.mie.jp")];
+ char stringpool_str2315[sizeof("kashima.kumamoto.jp")];
+ char stringpool_str2316[sizeof("tomisato.chiba.jp")];
+ char stringpool_str2317[sizeof("ainan.ehime.jp")];
+ char stringpool_str2318[sizeof("dyndns-remote.com")];
+ char stringpool_str2319[sizeof("ah.no")];
+ char stringpool_str2320[sizeof("omantel.om")];
+ char stringpool_str2321[sizeof("kamishihoro.hokkaido.jp")];
+ char stringpool_str2322[sizeof("heritage.museum")];
+ char stringpool_str2323[sizeof("bremanger.no")];
+ char stringpool_str2324[sizeof("akita.akita.jp")];
+ char stringpool_str2325[sizeof("name.na")];
+ char stringpool_str2326[sizeof("leikanger.no")];
+ char stringpool_str2327[sizeof("nnov.ru")];
+ char stringpool_str2328[sizeof("satx.museum")];
+ char stringpool_str2329[sizeof("net.nr")];
+ char stringpool_str2330[sizeof("kashiba.nara.jp")];
+ char stringpool_str2331[sizeof("kisosaki.mie.jp")];
+ char stringpool_str2332[sizeof("miyoshi.saitama.jp")];
+ char stringpool_str2333[sizeof("higashiyoshino.nara.jp")];
+ char stringpool_str2334[sizeof("k12.ok.us")];
+ char stringpool_str2335[sizeof("haibara.shizuoka.jp")];
+ char stringpool_str2336[sizeof("blogspot.com")];
+ char stringpool_str2337[sizeof("shirahama.wakayama.jp")];
+ char stringpool_str2338[sizeof("stavanger.no")];
+ char stringpool_str2339[sizeof("ketrzyn.pl")];
+ char stringpool_str2340[sizeof("steinkjer.no")];
+ char stringpool_str2341[sizeof("sciencesnaturelles.museum")];
+ char stringpool_str2342[sizeof("zgora.pl")];
+ char stringpool_str2343[sizeof("samnanger.no")];
+ char stringpool_str2344[sizeof("lib.ok.us")];
+ char stringpool_str2345[sizeof("hof.no")];
+ char stringpool_str2346[sizeof("co.gy")];
+ char stringpool_str2347[sizeof("mincom.tn")];
+ char stringpool_str2348[sizeof("kanra.gunma.jp")];
+ char stringpool_str2349[sizeof("net.af")];
+ char stringpool_str2350[sizeof("monmouth.museum")];
+ char stringpool_str2351[sizeof("higashinaruse.akita.jp")];
+ char stringpool_str2352[sizeof("akkeshi.hokkaido.jp")];
+ char stringpool_str2353[sizeof("blogspot.ca")];
+ char stringpool_str2354[sizeof("net.ve")];
+ char stringpool_str2355[sizeof("saves-the-whales.com")];
+ char stringpool_str2356[sizeof("gobiernoelectronico.ar")];
+ char stringpool_str2357[sizeof("koshimizu.hokkaido.jp")];
+ char stringpool_str2358[sizeof("kautokeino.no")];
+ char stringpool_str2359[sizeof("net.vi")];
+ char stringpool_str2360[sizeof("nsw.au")];
+ char stringpool_str2361[sizeof("va.no")];
+ char stringpool_str2362[sizeof("higashiizumo.shimane.jp")];
+ char stringpool_str2363[sizeof("shimizu.hokkaido.jp")];
+ char stringpool_str2364[sizeof("sakae.nagano.jp")];
+ char stringpool_str2365[sizeof("akagi.shimane.jp")];
+ char stringpool_str2366[sizeof("nome.pt")];
+ char stringpool_str2367[sizeof("ggf.br")];
+ char stringpool_str2368[sizeof("omaezaki.shizuoka.jp")];
+ char stringpool_str2369[sizeof("hu.com")];
+ char stringpool_str2370[sizeof("kawachinagano.osaka.jp")];
+ char stringpool_str2371[sizeof("database.museum")];
+ char stringpool_str2372[sizeof("net.vc")];
+ char stringpool_str2373[sizeof("medizinhistorisches.museum")];
+ char stringpool_str2374[sizeof("k12.or.us")];
+ char stringpool_str2375[sizeof("from-mn.com")];
+ char stringpool_str2376[sizeof("from-mt.com")];
+ char stringpool_str2377[sizeof("from-de.com")];
+ char stringpool_str2378[sizeof("from-or.com")];
+ char stringpool_str2379[sizeof("from-in.com")];
+ char stringpool_str2380[sizeof("sch.id")];
+ char stringpool_str2381[sizeof("gratangen.no")];
+ char stringpool_str2382[sizeof("skiptvet.no")];
+ char stringpool_str2383[sizeof("blogspot.com.es")];
+ char stringpool_str2384[sizeof("ny.us")];
+ char stringpool_str2385[sizeof("niepce.museum")];
+ char stringpool_str2386[sizeof("naumburg.museum")];
+ char stringpool_str2387[sizeof("haebaru.okinawa.jp")];
+ char stringpool_str2388[sizeof("from-ar.com")];
+ char stringpool_str2389[sizeof("nes.akershus.no")];
+ char stringpool_str2390[sizeof("manx.museum")];
+ char stringpool_str2391[sizeof("blogspot.ch")];
+ char stringpool_str2392[sizeof("oyer.no")];
+ char stringpool_str2393[sizeof("lib.or.us")];
+ char stringpool_str2394[sizeof("kyotanabe.kyoto.jp")];
+ char stringpool_str2395[sizeof("from-ct.com")];
+ char stringpool_str2396[sizeof("nara.jp")];
+ char stringpool_str2397[sizeof("from-il.com")];
+ char stringpool_str2398[sizeof("tenei.fukushima.jp")];
+ char stringpool_str2399[sizeof("shimizu.shizuoka.jp")];
+ char stringpool_str2400[sizeof("kyowa.akita.jp")];
+ char stringpool_str2401[sizeof("kvanangen.no")];
+ char stringpool_str2402[sizeof("from-ok.com")];
+ char stringpool_str2403[sizeof("bizen.okayama.jp")];
+ char stringpool_str2404[sizeof("from-al.com")];
+ char stringpool_str2405[sizeof("net.th")];
+ char stringpool_str2406[sizeof("delaware.museum")];
+ char stringpool_str2407[sizeof("net.bh")];
+ char stringpool_str2408[sizeof("city.sapporo.jp")];
+ char stringpool_str2409[sizeof("shiriuchi.hokkaido.jp")];
+ char stringpool_str2410[sizeof("oyamazaki.kyoto.jp")];
+ char stringpool_str2411[sizeof("tm.ro")];
+ char stringpool_str2412[sizeof("volgograd.ru")];
+ char stringpool_str2413[sizeof("chukotka.ru")];
+ char stringpool_str2414[sizeof("from-ak.com")];
+ char stringpool_str2415[sizeof("net.vn")];
+ char stringpool_str2416[sizeof("from-ut.com")];
+ char stringpool_str2417[sizeof("net.sh")];
+ char stringpool_str2418[sizeof("london.museum")];
+ char stringpool_str2419[sizeof("hizen.saga.jp")];
+ char stringpool_str2420[sizeof("knowsitall.info")];
+ char stringpool_str2421[sizeof("k12.wv.us")];
+ char stringpool_str2422[sizeof("air-surveillance.aero")];
+ char stringpool_str2423[sizeof("miyoshi.tokushima.jp")];
+ char stringpool_str2424[sizeof("from-tx.com")];
+ char stringpool_str2425[sizeof("from-tn.com")];
+ char stringpool_str2426[sizeof("hyuga.miyazaki.jp")];
+ char stringpool_str2427[sizeof("zagan.pl")];
+ char stringpool_str2428[sizeof("toyota.aichi.jp")];
+ char stringpool_str2429[sizeof("blogspot.com.au")];
+ char stringpool_str2430[sizeof("sabae.fukui.jp")];
+ char stringpool_str2431[sizeof("togitsu.nagasaki.jp")];
+ char stringpool_str2432[sizeof("campobasso.it")];
+ char stringpool_str2433[sizeof("laakesvuemie.no")];
+ char stringpool_str2434[sizeof("kamitsue.oita.jp")];
+ char stringpool_str2435[sizeof("shika.ishikawa.jp")];
+ char stringpool_str2436[sizeof("suita.osaka.jp")];
+ char stringpool_str2437[sizeof("from-nv.com")];
+ char stringpool_str2438[sizeof("from-nm.com")];
+ char stringpool_str2439[sizeof("lib.wv.us")];
+ char stringpool_str2440[sizeof("from-ne.com")];
+ char stringpool_str2441[sizeof("nittedal.no")];
+ char stringpool_str2442[sizeof("blogspot.com.ar")];
+ char stringpool_str2443[sizeof("sch.ir")];
+ char stringpool_str2444[sizeof("hm.no")];
+ char stringpool_str2445[sizeof("from-ma.com")];
+ char stringpool_str2446[sizeof("fukuchiyama.kyoto.jp")];
+ char stringpool_str2447[sizeof("from-ia.com")];
+ char stringpool_str2448[sizeof("higashikurume.tokyo.jp")];
+ char stringpool_str2449[sizeof("from-ga.com")];
+ char stringpool_str2450[sizeof("from-nj.com")];
+ char stringpool_str2451[sizeof("forgot.her.name")];
+ char stringpool_str2452[sizeof("lindesnes.no")];
+ char stringpool_str2453[sizeof("okuizumo.shimane.jp")];
+ char stringpool_str2454[sizeof("saiki.oita.jp")];
+ char stringpool_str2455[sizeof("asahi.mie.jp")];
+ char stringpool_str2456[sizeof("blogspot.com.br")];
+ char stringpool_str2457[sizeof("higashiyamato.tokyo.jp")];
+ char stringpool_str2458[sizeof("higashisumiyoshi.osaka.jp")];
+ char stringpool_str2459[sizeof("folkebibl.no")];
+ char stringpool_str2460[sizeof("k12.nv.us")];
+ char stringpool_str2461[sizeof("chocolate.museum")];
+ char stringpool_str2462[sizeof("shiwa.iwate.jp")];
+ char stringpool_str2463[sizeof("from-ca.com")];
+ char stringpool_str2464[sizeof("boston.museum")];
+ char stringpool_str2465[sizeof("sanda.hyogo.jp")];
+ char stringpool_str2466[sizeof("flatanger.no")];
+ char stringpool_str2467[sizeof("from-md.com")];
+ char stringpool_str2468[sizeof("from-id.com")];
+ char stringpool_str2469[sizeof("nes.buskerud.no")];
+ char stringpool_str2470[sizeof("ng")];
+ char stringpool_str2471[sizeof("elvendrell.museum")];
+ char stringpool_str2472[sizeof("scienceandindustry.museum")];
+ char stringpool_str2473[sizeof("name.my")];
+ char stringpool_str2474[sizeof("lib.nv.us")];
+ char stringpool_str2475[sizeof("name.pr")];
+ char stringpool_str2476[sizeof("from-ms.com")];
+ char stringpool_str2477[sizeof("kyuragi.saga.jp")];
+ char stringpool_str2478[sizeof("from-ks.com")];
+ char stringpool_str2479[sizeof("artgallery.museum")];
+ char stringpool_str2480[sizeof("onna.okinawa.jp")];
+ char stringpool_str2481[sizeof("from-sd.com")];
+ char stringpool_str2482[sizeof("sciencehistory.museum")];
+ char stringpool_str2483[sizeof("tv.bo")];
+ char stringpool_str2484[sizeof("sakawa.kochi.jp")];
+ char stringpool_str2485[sizeof("shibata.miyagi.jp")];
+ char stringpool_str2486[sizeof("nore-og-uvdal.no")];
+ char stringpool_str2487[sizeof("ebina.kanagawa.jp")];
+ char stringpool_str2488[sizeof("szex.hu")];
+ char stringpool_str2489[sizeof("act.edu.au")];
+ char stringpool_str2490[sizeof("dyndns-ip.com")];
+ char stringpool_str2491[sizeof("oshino.yamanashi.jp")];
+ char stringpool_str2492[sizeof("atami.shizuoka.jp")];
+ char stringpool_str2493[sizeof("otsu.shiga.jp")];
+ char stringpool_str2494[sizeof("cc.mt.us")];
+ char stringpool_str2495[sizeof("forgot.his.name")];
+ char stringpool_str2496[sizeof("cc.ga.us")];
+ char stringpool_str2497[sizeof("cc.ma.us")];
+ char stringpool_str2498[sizeof("kiyosu.aichi.jp")];
+ char stringpool_str2499[sizeof("name.hr")];
+ char stringpool_str2500[sizeof("edogawa.tokyo.jp")];
+ char stringpool_str2501[sizeof("cc.ia.us")];
+ char stringpool_str2502[sizeof("mashike.hokkaido.jp")];
+ char stringpool_str2503[sizeof("cc.ct.us")];
+ char stringpool_str2504[sizeof("net.nf")];
+ char stringpool_str2505[sizeof("from-oh.com")];
+ char stringpool_str2506[sizeof("cc.ca.us")];
+ char stringpool_str2507[sizeof("cc.ms.us")];
+ char stringpool_str2508[sizeof("cc.ut.us")];
+ char stringpool_str2509[sizeof("sumita.iwate.jp")];
+ char stringpool_str2510[sizeof("cc.ks.us")];
+ char stringpool_str2511[sizeof("oiso.kanagawa.jp")];
+ char stringpool_str2512[sizeof("jefferson.museum")];
+ char stringpool_str2513[sizeof("katowice.pl")];
+ char stringpool_str2514[sizeof("funabashi.chiba.jp")];
+ char stringpool_str2515[sizeof("cartoonart.museum")];
+ char stringpool_str2516[sizeof("froya.no")];
+ char stringpool_str2517[sizeof("qc.ca")];
+ char stringpool_str2518[sizeof("from-dc.com")];
+ char stringpool_str2519[sizeof("oita.oita.jp")];
+ char stringpool_str2520[sizeof("from-nd.com")];
+ char stringpool_str2521[sizeof("hl.no")];
+ char stringpool_str2522[sizeof("name.jo")];
+ char stringpool_str2523[sizeof("shunan.yamaguchi.jp")];
+ char stringpool_str2524[sizeof("tenri.nara.jp")];
+ char stringpool_str2525[sizeof("tabuse.yamaguchi.jp")];
+ char stringpool_str2526[sizeof("shiki.saitama.jp")];
+ char stringpool_str2527[sizeof("from-sc.com")];
+ char stringpool_str2528[sizeof("or.bi")];
+ char stringpool_str2529[sizeof("cc.wa.us")];
+ char stringpool_str2530[sizeof("ha.no")];
+ char stringpool_str2531[sizeof("kosai.shizuoka.jp")];
+ char stringpool_str2532[sizeof("sakaiminato.tottori.jp")];
+ char stringpool_str2533[sizeof("cc.vt.us")];
+ char stringpool_str2534[sizeof("orsta.no")];
+ char stringpool_str2535[sizeof("city.sendai.jp")];
+ char stringpool_str2536[sizeof("koori.fukushima.jp")];
+ char stringpool_str2537[sizeof("cc.va.us")];
+ char stringpool_str2538[sizeof("ookuwa.nagano.jp")];
+ char stringpool_str2539[sizeof("olawa.pl")];
+ char stringpool_str2540[sizeof("cc.tx.us")];
+ char stringpool_str2541[sizeof("komae.tokyo.jp")];
+ char stringpool_str2542[sizeof("cc.pa.us")];
+ char stringpool_str2543[sizeof("countryestate.museum")];
+ char stringpool_str2544[sizeof("kushimoto.wakayama.jp")];
+ char stringpool_str2545[sizeof("hayashima.okayama.jp")];
+ char stringpool_str2546[sizeof("mishima.shizuoka.jp")];
+ char stringpool_str2547[sizeof("gobo.wakayama.jp")];
+ char stringpool_str2548[sizeof("from-nh.com")];
+ char stringpool_str2549[sizeof("cc.dc.us")];
+ char stringpool_str2550[sizeof("skedsmokorset.no")];
+ char stringpool_str2551[sizeof("harvestcelebration.museum")];
+ char stringpool_str2552[sizeof("author.aero")];
+ char stringpool_str2553[sizeof("hammerfest.no")];
+ char stringpool_str2554[sizeof("naples.it")];
+ char stringpool_str2555[sizeof("kagoshima.kagoshima.jp")];
+ char stringpool_str2556[sizeof("from-nc.com")];
+ char stringpool_str2557[sizeof("tado.mie.jp")];
+ char stringpool_str2558[sizeof("toyone.aichi.jp")];
+ char stringpool_str2559[sizeof("scienceandhistory.museum")];
+ char stringpool_str2560[sizeof("kawai.nara.jp")];
+ char stringpool_str2561[sizeof("cc.nj.us")];
+ char stringpool_str2562[sizeof("cc.la.us")];
+ char stringpool_str2563[sizeof("hitachiomiya.ibaraki.jp")];
+ char stringpool_str2564[sizeof("ballangen.no")];
+ char stringpool_str2565[sizeof("koza.wakayama.jp")];
+ char stringpool_str2566[sizeof("miyoshi.hiroshima.jp")];
+ char stringpool_str2567[sizeof("org.im")];
+ char stringpool_str2568[sizeof("kashima.ibaraki.jp")];
+ char stringpool_str2569[sizeof("mashiki.kumamoto.jp")];
+ char stringpool_str2570[sizeof("cc.me.us")];
+ char stringpool_str2571[sizeof("arakawa.tokyo.jp")];
+ char stringpool_str2572[sizeof("cc.nc.us")];
+ char stringpool_str2573[sizeof("org.is")];
+ char stringpool_str2574[sizeof("noheji.aomori.jp")];
+ char stringpool_str2575[sizeof("notteroy.no")];
+ char stringpool_str2576[sizeof("toyota.yamaguchi.jp")];
+ char stringpool_str2577[sizeof("oumu.hokkaido.jp")];
+ char stringpool_str2578[sizeof("higashimurayama.tokyo.jp")];
+ char stringpool_str2579[sizeof("from-mo.com")];
+ char stringpool_str2580[sizeof("miura.kanagawa.jp")];
+ char stringpool_str2581[sizeof("dlugoleka.pl")];
+ char stringpool_str2582[sizeof("cc.as.us")];
+ char stringpool_str2583[sizeof("k12.oh.us")];
+ char stringpool_str2584[sizeof("cc.de.us")];
+ char stringpool_str2585[sizeof("mifune.kumamoto.jp")];
+ char stringpool_str2586[sizeof("higashiosaka.osaka.jp")];
+ char stringpool_str2587[sizeof("belgorod.ru")];
+ char stringpool_str2588[sizeof("stryn.no")];
+ char stringpool_str2589[sizeof("blogspot.fr")];
+ char stringpool_str2590[sizeof("azumino.nagano.jp")];
+ char stringpool_str2591[sizeof("ayabe.kyoto.jp")];
+ char stringpool_str2592[sizeof("hitachi.ibaraki.jp")];
+ char stringpool_str2593[sizeof("vic.edu.au")];
+ char stringpool_str2594[sizeof("tonosho.kagawa.jp")];
+ char stringpool_str2595[sizeof("from-fl.com")];
+ char stringpool_str2596[sizeof("langevag.no")];
+ char stringpool_str2597[sizeof("kiho.mie.jp")];
+ char stringpool_str2598[sizeof("dyndns-at-home.com")];
+ char stringpool_str2599[sizeof("betainabox.com")];
+ char stringpool_str2600[sizeof("lib.oh.us")];
+ char stringpool_str2601[sizeof("at-band-camp.net")];
+ char stringpool_str2602[sizeof("anamizu.ishikawa.jp")];
+ char stringpool_str2603[sizeof("cc.mn.us")];
+ char stringpool_str2604[sizeof("bielawa.pl")];
+ char stringpool_str2605[sizeof("org.ir")];
+ char stringpool_str2606[sizeof("org.in")];
+ char stringpool_str2607[sizeof("cc.in.us")];
+ char stringpool_str2608[sizeof("sakae.chiba.jp")];
+ char stringpool_str2609[sizeof("taiki.hokkaido.jp")];
+ char stringpool_str2610[sizeof("opole.pl")];
+ char stringpool_str2611[sizeof("sells-for-u.com")];
+ char stringpool_str2612[sizeof("berlevag.no")];
+ char stringpool_str2613[sizeof("kashima.saga.jp")];
+ char stringpool_str2614[sizeof("from-ky.com")];
+ char stringpool_str2615[sizeof("from-pr.com")];
+ char stringpool_str2616[sizeof("dyndns.info")];
+ char stringpool_str2617[sizeof("joboji.iwate.jp")];
+ char stringpool_str2618[sizeof("kushiro.hokkaido.jp")];
+ char stringpool_str2619[sizeof("vicenza.it")];
+ char stringpool_str2620[sizeof("cc.ne.us")];
+ char stringpool_str2621[sizeof("nebraska.museum")];
+ char stringpool_str2622[sizeof("noda.iwate.jp")];
+ char stringpool_str2623[sizeof("midatlantic.museum")];
+ char stringpool_str2624[sizeof("olbiatempio.it")];
+ char stringpool_str2625[sizeof("blogspot.fi")];
+ char stringpool_str2626[sizeof("nord-odal.no")];
+ char stringpool_str2627[sizeof("medecin.fr")];
+ char stringpool_str2628[sizeof("numata.gunma.jp")];
+ char stringpool_str2629[sizeof("amami.kagoshima.jp")];
+ char stringpool_str2630[sizeof("okutama.tokyo.jp")];
+ char stringpool_str2631[sizeof("kushima.miyazaki.jp")];
+ char stringpool_str2632[sizeof("bungoono.oita.jp")];
+ char stringpool_str2633[sizeof("higashiomi.shiga.jp")];
+ char stringpool_str2634[sizeof("cc.tn.us")];
+ char stringpool_str2635[sizeof("mecon.ar")];
+ char stringpool_str2636[sizeof("ryugasaki.ibaraki.jp")];
+ char stringpool_str2637[sizeof("toba.mie.jp")];
+ char stringpool_str2638[sizeof("etajima.hiroshima.jp")];
+ char stringpool_str2639[sizeof("michigan.museum")];
+ char stringpool_str2640[sizeof("florence.it")];
+ char stringpool_str2641[sizeof("higashiyodogawa.osaka.jp")];
+ char stringpool_str2642[sizeof("taki.mie.jp")];
+ char stringpool_str2643[sizeof("nannestad.no")];
+ char stringpool_str2644[sizeof("ozu.ehime.jp")];
+ char stringpool_str2645[sizeof("from-pa.com")];
+ char stringpool_str2646[sizeof("gov.hk")];
+ char stringpool_str2647[sizeof("medical.museum")];
+ char stringpool_str2648[sizeof("edu.hk")];
+ char stringpool_str2649[sizeof("com.hk")];
+ char stringpool_str2650[sizeof("hammarfeasta.no")];
+ char stringpool_str2651[sizeof("homebuilt.aero")];
+ char stringpool_str2652[sizeof("selfip.biz")];
+ char stringpool_str2653[sizeof("from-mi.com")];
+ char stringpool_str2654[sizeof("cc.az.us")];
+ char stringpool_str2655[sizeof("yt")];
+ char stringpool_str2656[sizeof("kanonji.kagawa.jp")];
+ char stringpool_str2657[sizeof("mediocampidano.it")];
+ char stringpool_str2658[sizeof("ye")];
+ char stringpool_str2659[sizeof("rokunohe.aomori.jp")];
+ char stringpool_str2660[sizeof("schoenbrunn.museum")];
+ char stringpool_str2661[sizeof("jamison.museum")];
+ char stringpool_str2662[sizeof("mallorca.museum")];
+ char stringpool_str2663[sizeof("tenkawa.nara.jp")];
+ char stringpool_str2664[sizeof("io")];
+ char stringpool_str2665[sizeof("id")];
+ char stringpool_str2666[sizeof("im")];
+ char stringpool_str2667[sizeof("marketplace.aero")];
+ char stringpool_str2668[sizeof("chihayaakasaka.osaka.jp")];
+ char stringpool_str2669[sizeof("higashihiroshima.hiroshima.jp")];
+ char stringpool_str2670[sizeof("tamba.hyogo.jp")];
+ char stringpool_str2671[sizeof("co.st")];
+ char stringpool_str2672[sizeof("ir")];
+ char stringpool_str2673[sizeof("y.se")];
+ char stringpool_str2674[sizeof("edu.ht")];
+ char stringpool_str2675[sizeof("im.it")];
+ char stringpool_str2676[sizeof("dellogliastra.it")];
+ char stringpool_str2677[sizeof("com.ht")];
+ char stringpool_str2678[sizeof("kanan.osaka.jp")];
+ char stringpool_str2679[sizeof("narita.chiba.jp")];
+ char stringpool_str2680[sizeof("it")];
+ char stringpool_str2681[sizeof("archaeological.museum")];
+ char stringpool_str2682[sizeof("goshiki.hyogo.jp")];
+ char stringpool_str2683[sizeof("dyn-o-saur.com")];
+ char stringpool_str2684[sizeof("edu.hn")];
+ char stringpool_str2685[sizeof("austevoll.no")];
+ char stringpool_str2686[sizeof("go.th")];
+ char stringpool_str2687[sizeof("is")];
+ char stringpool_str2688[sizeof("tokoname.aichi.jp")];
+ char stringpool_str2689[sizeof("com.hr")];
+ char stringpool_str2690[sizeof("com.hn")];
+ char stringpool_str2691[sizeof("co.th")];
+ char stringpool_str2692[sizeof("ie")];
+ char stringpool_str2693[sizeof("city.kitakyushu.jp")];
+ char stringpool_str2694[sizeof("from-ri.com")];
+ char stringpool_str2695[sizeof("omaha.museum")];
+ char stringpool_str2696[sizeof("id.us")];
+ char stringpool_str2697[sizeof("maintenance.aero")];
+ char stringpool_str2698[sizeof("cc.wv.us")];
+ char stringpool_str2699[sizeof("aizubange.fukushima.jp")];
+ char stringpool_str2700[sizeof("is.it")];
+ char stringpool_str2701[sizeof("bergbau.museum")];
+ char stringpool_str2702[sizeof("fribourg.museum")];
+ char stringpool_str2703[sizeof("sex.hu")];
+ char stringpool_str2704[sizeof("akabira.hokkaido.jp")];
+ char stringpool_str2705[sizeof("clinton.museum")];
+ char stringpool_str2706[sizeof("asahi.toyama.jp")];
+ char stringpool_str2707[sizeof("art.ht")];
+ char stringpool_str2708[sizeof("nomi.ishikawa.jp")];
+ char stringpool_str2709[sizeof("tsukiyono.gunma.jp")];
+ char stringpool_str2710[sizeof("i.se")];
+ char stringpool_str2711[sizeof("osaka.jp")];
+ char stringpool_str2712[sizeof("lucca.it")];
+ char stringpool_str2713[sizeof("vibovalentia.it")];
+ char stringpool_str2714[sizeof("net.im")];
+ char stringpool_str2715[sizeof("net.id")];
+ char stringpool_str2716[sizeof("noto.ishikawa.jp")];
+ char stringpool_str2717[sizeof("konan.shiga.jp")];
+ char stringpool_str2718[sizeof("botanicgarden.museum")];
+ char stringpool_str2719[sizeof("naruto.tokushima.jp")];
+ char stringpool_str2720[sizeof("oga.akita.jp")];
+ char stringpool_str2721[sizeof("nose.osaka.jp")];
+ char stringpool_str2722[sizeof("in")];
+ char stringpool_str2723[sizeof("net.is")];
+ char stringpool_str2724[sizeof("cc.nv.us")];
+ char stringpool_str2725[sizeof("arakawa.saitama.jp")];
+ char stringpool_str2726[sizeof("coldwar.museum")];
+ char stringpool_str2727[sizeof("birdart.museum")];
+ char stringpool_str2728[sizeof("boleslawiec.pl")];
+ char stringpool_str2729[sizeof("y.bg")];
+ char stringpool_str2730[sizeof("nysa.pl")];
+ char stringpool_str2731[sizeof("denmark.museum")];
+ char stringpool_str2732[sizeof("asso.dz")];
+ char stringpool_str2733[sizeof("rishiri.hokkaido.jp")];
+ char stringpool_str2734[sizeof("med.ht")];
+ char stringpool_str2735[sizeof("bibai.hokkaido.jp")];
+ char stringpool_str2736[sizeof("nawras.om")];
+ char stringpool_str2737[sizeof("tsuyama.okayama.jp")];
+ char stringpool_str2738[sizeof("il")];
+ char stringpool_str2739[sizeof("fineart.museum")];
+ char stringpool_str2740[sizeof("int")];
+ char stringpool_str2741[sizeof("s3-website-us-east-1.amazonaws.com")];
+ char stringpool_str2742[sizeof("in.ua")];
+ char stringpool_str2743[sizeof("hagi.yamaguchi.jp")];
+ char stringpool_str2744[sizeof("int.bo")];
+ char stringpool_str2745[sizeof("gangaviika.no")];
+ char stringpool_str2746[sizeof("noda.chiba.jp")];
+ char stringpool_str2747[sizeof("int.az")];
+ char stringpool_str2748[sizeof("nord-fron.no")];
+ char stringpool_str2749[sizeof("store.bb")];
+ char stringpool_str2750[sizeof("in.us")];
+ char stringpool_str2751[sizeof("ohtawara.tochigi.jp")];
+ char stringpool_str2752[sizeof("magazine.aero")];
+ char stringpool_str2753[sizeof("volyn.ua")];
+ char stringpool_str2754[sizeof("idrett.no")];
+ char stringpool_str2755[sizeof("vibo-valentia.it")];
+ char stringpool_str2756[sizeof("frosta.no")];
+ char stringpool_str2757[sizeof("org.sy")];
+ char stringpool_str2758[sizeof("convent.museum")];
+ char stringpool_str2759[sizeof("hamatonbetsu.hokkaido.jp")];
+ char stringpool_str2760[sizeof("opoczno.pl")];
+ char stringpool_str2761[sizeof("net.ir")];
+ char stringpool_str2762[sizeof("net.in")];
+ char stringpool_str2763[sizeof("i.bg")];
+ char stringpool_str2764[sizeof("muosat.no")];
+ char stringpool_str2765[sizeof("otoineppu.hokkaido.jp")];
+ char stringpool_str2766[sizeof("il.us")];
+ char stringpool_str2767[sizeof("dyndns-at-work.com")];
+ char stringpool_str2768[sizeof("yk.ca")];
+ char stringpool_str2769[sizeof("artanddesign.museum")];
+ char stringpool_str2770[sizeof("kiwa.mie.jp")];
+ char stringpool_str2771[sizeof("york.museum")];
+ char stringpool_str2772[sizeof("ostre-toten.no")];
+ char stringpool_str2773[sizeof("mi.th")];
+ char stringpool_str2774[sizeof("co.sz")];
+ char stringpool_str2775[sizeof("ia.us")];
+ char stringpool_str2776[sizeof("ind.tn")];
+ char stringpool_str2777[sizeof("rel.ht")];
+ char stringpool_str2778[sizeof("ind.br")];
+ char stringpool_str2779[sizeof("gob.hn")];
+ char stringpool_str2780[sizeof("numata.hokkaido.jp")];
+ char stringpool_str2781[sizeof("int.co")];
+ char stringpool_str2782[sizeof("komforb.se")];
+ char stringpool_str2783[sizeof("int.tt")];
+ char stringpool_str2784[sizeof("int.ci")];
+ char stringpool_str2785[sizeof("medio-campidano.it")];
+ char stringpool_str2786[sizeof("nativeamerican.museum")];
+ char stringpool_str2787[sizeof("higashimatsuyama.saitama.jp")];
+ char stringpool_str2788[sizeof("koya.wakayama.jp")];
+ char stringpool_str2789[sizeof("za.com")];
+ char stringpool_str2790[sizeof("onga.fukuoka.jp")];
+ char stringpool_str2791[sizeof("in.na")];
+ char stringpool_str2792[sizeof("fukui.fukui.jp")];
+ char stringpool_str2793[sizeof("media.aero")];
+ char stringpool_str2794[sizeof("theater.museum")];
+ char stringpool_str2795[sizeof("logistics.aero")];
+ char stringpool_str2796[sizeof("hashimoto.wakayama.jp")];
+ char stringpool_str2797[sizeof("achi.nagano.jp")];
+ char stringpool_str2798[sizeof("jobs")];
+ char stringpool_str2799[sizeof("ws")];
+ char stringpool_str2800[sizeof("defense.tn")];
+ char stringpool_str2801[sizeof("tm.se")];
+ char stringpool_str2802[sizeof("sande.xn--mre-og-romsdal-qqb.no")];
+ char stringpool_str2803[sizeof("imb.br")];
+ char stringpool_str2804[sizeof("saotome.st")];
+ char stringpool_str2805[sizeof("wv.us")];
+ char stringpool_str2806[sizeof("arts.nf")];
+ char stringpool_str2807[sizeof("s3-website-sa-east-1.amazonaws.com")];
+ char stringpool_str2808[sizeof("crotone.it")];
+ char stringpool_str2809[sizeof("lebtimnetz.de")];
+ char stringpool_str2810[sizeof("nara.nara.jp")];
+ char stringpool_str2811[sizeof("arts.museum")];
+ char stringpool_str2812[sizeof("dyndns-home.com")];
+ char stringpool_str2813[sizeof("hioki.kagoshima.jp")];
+ char stringpool_str2814[sizeof("omuta.fukuoka.jp")];
+ char stringpool_str2815[sizeof("narvik.no")];
+ char stringpool_str2816[sizeof("int.tj")];
+ char stringpool_str2817[sizeof("aguni.okinawa.jp")];
+ char stringpool_str2818[sizeof("village.museum")];
+ char stringpool_str2819[sizeof("nagasaki.jp")];
+ char stringpool_str2820[sizeof("lecce.it")];
+ char stringpool_str2821[sizeof("hoylandet.no")];
+ char stringpool_str2822[sizeof("w.se")];
+ char stringpool_str2823[sizeof("sibenik.museum")];
+ char stringpool_str2824[sizeof("higashine.yamagata.jp")];
+ char stringpool_str2825[sizeof("hitachinaka.ibaraki.jp")];
+ char stringpool_str2826[sizeof("yn.cn")];
+ char stringpool_str2827[sizeof("com.fr")];
+ char stringpool_str2828[sizeof("mil.hn")];
+ char stringpool_str2829[sizeof("sorfold.no")];
+ char stringpool_str2830[sizeof("cremona.it")];
+ char stringpool_str2831[sizeof("sagae.yamagata.jp")];
+ char stringpool_str2832[sizeof("shari.hokkaido.jp")];
+ char stringpool_str2833[sizeof("fujishiro.ibaraki.jp")];
+ char stringpool_str2834[sizeof("axis.museum")];
+ char stringpool_str2835[sizeof("bd.se")];
+ char stringpool_str2836[sizeof("asahi.nagano.jp")];
+ char stringpool_str2837[sizeof("mobi.tz")];
+ char stringpool_str2838[sizeof("ternopil.ua")];
+ char stringpool_str2839[sizeof("blogspot.co.nz")];
+ char stringpool_str2840[sizeof("akrehamn.no")];
+ char stringpool_str2841[sizeof("novosibirsk.ru")];
+ char stringpool_str2842[sizeof("brussels.museum")];
+ char stringpool_str2843[sizeof("kids.museum")];
+ char stringpool_str2844[sizeof("numazu.shizuoka.jp")];
+ char stringpool_str2845[sizeof("suisse.museum")];
+ char stringpool_str2846[sizeof("stjohn.museum")];
+ char stringpool_str2847[sizeof("kids.us")];
+ char stringpool_str2848[sizeof("lib.hi.us")];
+ char stringpool_str2849[sizeof("iron.museum")];
+ char stringpool_str2850[sizeof("go.dyndns.org")];
+ char stringpool_str2851[sizeof("jobs.tt")];
+ char stringpool_str2852[sizeof("wi.us")];
+ char stringpool_str2853[sizeof("toyoake.aichi.jp")];
+ char stringpool_str2854[sizeof("s3-website-us-west-1.amazonaws.com")];
+ char stringpool_str2855[sizeof("grosseto.it")];
+ char stringpool_str2856[sizeof("shimoichi.nara.jp")];
+ char stringpool_str2857[sizeof("dyndns-work.com")];
+ char stringpool_str2858[sizeof("hirono.iwate.jp")];
+ char stringpool_str2859[sizeof("finland.museum")];
+ char stringpool_str2860[sizeof("intl.tn")];
+ char stringpool_str2861[sizeof("shitara.aichi.jp")];
+ char stringpool_str2862[sizeof("ws.na")];
+ char stringpool_str2863[sizeof("shinshiro.aichi.jp")];
+ char stringpool_str2864[sizeof("campidano-medio.it")];
+ char stringpool_str2865[sizeof("farmers.museum")];
+ char stringpool_str2866[sizeof("kalmykia.ru")];
+ char stringpool_str2867[sizeof("iraq.museum")];
+ char stringpool_str2868[sizeof("w.bg")];
+ char stringpool_str2869[sizeof("artdeco.museum")];
+ char stringpool_str2870[sizeof("doshi.yamanashi.jp")];
+ char stringpool_str2871[sizeof("technology.museum")];
+ char stringpool_str2872[sizeof("blogspot.co.at")];
+ char stringpool_str2873[sizeof("aizuwakamatsu.fukushima.jp")];
+ char stringpool_str2874[sizeof("motoyama.kochi.jp")];
+ char stringpool_str2875[sizeof("city.nagoya.jp")];
+ char stringpool_str2876[sizeof("wa.us")];
+ char stringpool_str2877[sizeof("tateyama.toyama.jp")];
+ char stringpool_str2878[sizeof("oi.kanagawa.jp")];
+ char stringpool_str2879[sizeof("naka.ibaraki.jp")];
+ char stringpool_str2880[sizeof("osakasayama.osaka.jp")];
+ char stringpool_str2881[sizeof("tobishima.aichi.jp")];
+ char stringpool_str2882[sizeof("motosu.gifu.jp")];
+ char stringpool_str2883[sizeof("eidsberg.no")];
+ char stringpool_str2884[sizeof("vinnytsia.ua")];
+ char stringpool_str2885[sizeof("indian.museum")];
+ char stringpool_str2886[sizeof("chicago.museum")];
+ char stringpool_str2887[sizeof("from-hi.com")];
+ char stringpool_str2888[sizeof("omitama.ibaraki.jp")];
+ char stringpool_str2889[sizeof("blogspot.co.uk")];
+ char stringpool_str2890[sizeof("santafe.museum")];
+ char stringpool_str2891[sizeof("moroyama.saitama.jp")];
+ char stringpool_str2892[sizeof("blogspot.co.il")];
+ char stringpool_str2893[sizeof("kamchatka.ru")];
+ char stringpool_str2894[sizeof("kutno.pl")];
+ char stringpool_str2895[sizeof("nantan.kyoto.jp")];
+ char stringpool_str2896[sizeof("sakai.fukui.jp")];
+ char stringpool_str2897[sizeof("honai.ehime.jp")];
+ char stringpool_str2898[sizeof("otama.fukushima.jp")];
+ char stringpool_str2899[sizeof("higashi.okinawa.jp")];
+ char stringpool_str2900[sizeof("handson.museum")];
+ char stringpool_str2901[sizeof("nuernberg.museum")];
+ char stringpool_str2902[sizeof("fh.se")];
+ char stringpool_str2903[sizeof("arts.co")];
+ char stringpool_str2904[sizeof("songfest.om")];
+ char stringpool_str2905[sizeof("atlanta.museum")];
+ char stringpool_str2906[sizeof("net.sy")];
+ char stringpool_str2907[sizeof("ip6.arpa")];
+ char stringpool_str2908[sizeof("web.co")];
+ char stringpool_str2909[sizeof("higashikagura.hokkaido.jp")];
+ char stringpool_str2910[sizeof("seihi.nagasaki.jp")];
+ char stringpool_str2911[sizeof("glas.museum")];
+ char stringpool_str2912[sizeof("horonobe.hokkaido.jp")];
+ char stringpool_str2913[sizeof("tonsberg.no")];
+ char stringpool_str2914[sizeof("okuma.fukushima.jp")];
+ char stringpool_str2915[sizeof("grajewo.pl")];
+ char stringpool_str2916[sizeof("higashiyama.kyoto.jp")];
+ char stringpool_str2917[sizeof("niigata.jp")];
+ char stringpool_str2918[sizeof("bialystok.pl")];
+ char stringpool_str2919[sizeof("askvoll.no")];
+ char stringpool_str2920[sizeof("neues.museum")];
+ char stringpool_str2921[sizeof("here-for-more.info")];
+ char stringpool_str2922[sizeof("sveio.no")];
+ char stringpool_str2923[sizeof("kristiansund.no")];
+ char stringpool_str2924[sizeof("corporation.museum")];
+ char stringpool_str2925[sizeof("kai.yamanashi.jp")];
+ char stringpool_str2926[sizeof("iq")];
+ char stringpool_str2927[sizeof("dyndns-wiki.com")];
+ char stringpool_str2928[sizeof("takinoue.hokkaido.jp")];
+ char stringpool_str2929[sizeof("radio.br")];
+ char stringpool_str2930[sizeof("arts.ro")];
+ char stringpool_str2931[sizeof("lans.museum")];
+ char stringpool_str2932[sizeof("imageandsound.museum")];
+ char stringpool_str2933[sizeof("fudai.iwate.jp")];
+ char stringpool_str2934[sizeof("shakotan.hokkaido.jp")];
+ char stringpool_str2935[sizeof("rankoshi.hokkaido.jp")];
+ char stringpool_str2936[sizeof("id.lv")];
+ char stringpool_str2937[sizeof("traniandriabarletta.it")];
+ char stringpool_str2938[sizeof("narviika.no")];
+ char stringpool_str2939[sizeof("voronezh.ru")];
+ char stringpool_str2940[sizeof("in.rs")];
+ char stringpool_str2941[sizeof("ballooning.aero")];
+ char stringpool_str2942[sizeof("versailles.museum")];
+ char stringpool_str2943[sizeof("higashiagatsuma.gunma.jp")];
+ char stringpool_str2944[sizeof("web.tj")];
+ char stringpool_str2945[sizeof("i.ph")];
+ char stringpool_str2946[sizeof("gateway.museum")];
+ char stringpool_str2947[sizeof("notaires.km")];
+ char stringpool_str2948[sizeof("selfip.info")];
+ char stringpool_str2949[sizeof("ethnology.museum")];
+ char stringpool_str2950[sizeof("onomichi.hiroshima.jp")];
+ char stringpool_str2951[sizeof("nagano.jp")];
+ char stringpool_str2952[sizeof("kaminokawa.tochigi.jp")];
+ char stringpool_str2953[sizeof("flight.aero")];
+ char stringpool_str2954[sizeof("moriyoshi.akita.jp")];
+ char stringpool_str2955[sizeof("otake.hiroshima.jp")];
+ char stringpool_str2956[sizeof("flesberg.no")];
+ char stringpool_str2957[sizeof("moss.no")];
+ char stringpool_str2958[sizeof("stathelle.no")];
+ char stringpool_str2959[sizeof("takanabe.miyazaki.jp")];
+ char stringpool_str2960[sizeof("moriya.ibaraki.jp")];
+ char stringpool_str2961[sizeof("safety.aero")];
+ char stringpool_str2962[sizeof("wiki.br")];
+ char stringpool_str2963[sizeof("okawa.fukuoka.jp")];
+ char stringpool_str2964[sizeof("dyndns-mail.com")];
+ char stringpool_str2965[sizeof("astronomy.museum")];
+ char stringpool_str2966[sizeof("fukuoka.jp")];
+ char stringpool_str2967[sizeof("tsuga.tochigi.jp")];
+ char stringpool_str2968[sizeof("gifu.gifu.jp")];
+ char stringpool_str2969[sizeof("int.vn")];
+ char stringpool_str2970[sizeof("niikappu.hokkaido.jp")];
+ char stringpool_str2971[sizeof("shiroi.chiba.jp")];
+ char stringpool_str2972[sizeof("mosjoen.no")];
+ char stringpool_str2973[sizeof("nesna.no")];
+ char stringpool_str2974[sizeof("balsan.it")];
+ char stringpool_str2975[sizeof("naha.okinawa.jp")];
+ char stringpool_str2976[sizeof("karasuyama.tochigi.jp")];
+ char stringpool_str2977[sizeof("s3-website-us-west-2.amazonaws.com")];
+ char stringpool_str2978[sizeof("airport.aero")];
+ char stringpool_str2979[sizeof("gonohe.aomori.jp")];
+ char stringpool_str2980[sizeof("eidsvoll.no")];
+ char stringpool_str2981[sizeof("alabama.museum")];
+ char stringpool_str2982[sizeof("kuriyama.hokkaido.jp")];
+ char stringpool_str2983[sizeof("shirakawa.gifu.jp")];
+ char stringpool_str2984[sizeof("shirakawa.fukushima.jp")];
+ char stringpool_str2985[sizeof("karmoy.no")];
+ char stringpool_str2986[sizeof("kepno.pl")];
+ char stringpool_str2987[sizeof("omi.niigata.jp")];
+ char stringpool_str2988[sizeof("historichouses.museum")];
+ char stringpool_str2989[sizeof("miyawaka.fukuoka.jp")];
+ char stringpool_str2990[sizeof("rovno.ua")];
+ char stringpool_str2991[sizeof("uz")];
+ char stringpool_str2992[sizeof("kristiansand.no")];
+ char stringpool_str2993[sizeof("manchester.museum")];
+ char stringpool_str2994[sizeof("gov.do")];
+ char stringpool_str2995[sizeof("gov.dm")];
+ char stringpool_str2996[sizeof("southcarolina.museum")];
+ char stringpool_str2997[sizeof("edu.do")];
+ char stringpool_str2998[sizeof("edu.dm")];
+ char stringpool_str2999[sizeof("com.do")];
+ char stringpool_str3000[sizeof("com.dm")];
+ char stringpool_str3001[sizeof("com.de")];
+ char stringpool_str3002[sizeof("gov.dz")];
+ char stringpool_str3003[sizeof("edu.dz")];
+ char stringpool_str3004[sizeof("nagasu.kumamoto.jp")];
+ char stringpool_str3005[sizeof("cc.mi.us")];
+ char stringpool_str3006[sizeof("com.dz")];
+ char stringpool_str3007[sizeof("go.id")];
+ char stringpool_str3008[sizeof("co.id")];
+ char stringpool_str3009[sizeof("ud.it")];
+ char stringpool_str3010[sizeof("dynathome.net")];
+ char stringpool_str3011[sizeof("taiji.wakayama.jp")];
+ char stringpool_str3012[sizeof("k12.fl.us")];
+ char stringpool_str3013[sizeof("halloffame.museum")];
+ char stringpool_str3014[sizeof("voss.no")];
+ char stringpool_str3015[sizeof("kosei.shiga.jp")];
+ char stringpool_str3016[sizeof("nord-aurdal.no")];
+ char stringpool_str3017[sizeof("uz.ua")];
+ char stringpool_str3018[sizeof("us")];
+ char stringpool_str3019[sizeof("obanazawa.yamagata.jp")];
+ char stringpool_str3020[sizeof("kishiwada.osaka.jp")];
+ char stringpool_str3021[sizeof("historical.museum")];
+ char stringpool_str3022[sizeof("art.do")];
+ char stringpool_str3023[sizeof("s3-website-ap-southeast-1.amazonaws.com")];
+ char stringpool_str3024[sizeof("lib.fl.us")];
+ char stringpool_str3025[sizeof("art.dz")];
+ char stringpool_str3026[sizeof("wy.us")];
+ char stringpool_str3027[sizeof("kasai.hyogo.jp")];
+ char stringpool_str3028[sizeof("shimotsuma.ibaraki.jp")];
+ char stringpool_str3029[sizeof("wroc.pl")];
+ char stringpool_str3030[sizeof("cc.wi.us")];
+ char stringpool_str3031[sizeof("kumiyama.kyoto.jp")];
+ char stringpool_str3032[sizeof("u.se")];
+ char stringpool_str3033[sizeof("reggio-calabria.it")];
+ char stringpool_str3034[sizeof("ut.us")];
+ char stringpool_str3035[sizeof("indianmarket.museum")];
+ char stringpool_str3036[sizeof("cc.vi.us")];
+ char stringpool_str3037[sizeof("forli-cesena.it")];
+ char stringpool_str3038[sizeof("railway.museum")];
+ char stringpool_str3039[sizeof("nsw.edu.au")];
+ char stringpool_str3040[sizeof("tamayu.shimane.jp")];
+ char stringpool_str3041[sizeof("hekinan.aichi.jp")];
+ char stringpool_str3042[sizeof("kashiwara.osaka.jp")];
+ char stringpool_str3043[sizeof("sandoy.no")];
+ char stringpool_str3044[sizeof("bomlo.no")];
+ char stringpool_str3045[sizeof("fukaya.saitama.jp")];
+ char stringpool_str3046[sizeof("illustration.museum")];
+ char stringpool_str3047[sizeof("asahi.chiba.jp")];
+ char stringpool_str3048[sizeof("video.hu")];
+ char stringpool_str3049[sizeof("arkhangelsk.ru")];
+ char stringpool_str3050[sizeof("higashimatsushima.miyagi.jp")];
+ char stringpool_str3051[sizeof("bunkyo.tokyo.jp")];
+ char stringpool_str3052[sizeof("starostwo.gov.pl")];
+ char stringpool_str3053[sizeof("bo.nordland.no")];
+ char stringpool_str3054[sizeof("is-a-nurse.com")];
+ char stringpool_str3055[sizeof("s3-website-ap-northeast-1.amazonaws.com")];
+ char stringpool_str3056[sizeof("sld.do")];
+ char stringpool_str3057[sizeof("asahi.yamagata.jp")];
+ char stringpool_str3058[sizeof("sor-fron.no")];
+ char stringpool_str3059[sizeof("tranoy.no")];
+ char stringpool_str3060[sizeof("ua")];
+ char stringpool_str3061[sizeof("hobby-site.com")];
+ char stringpool_str3062[sizeof("org.tw")];
+ char stringpool_str3063[sizeof("web.ve")];
+ char stringpool_str3064[sizeof("org.bw")];
+ char stringpool_str3065[sizeof("isla.pr")];
+ char stringpool_str3066[sizeof("happou.akita.jp")];
+ char stringpool_str3067[sizeof("us.na")];
+ char stringpool_str3068[sizeof("england.museum")];
+ char stringpool_str3069[sizeof("andriatranibarletta.it")];
+ char stringpool_str3070[sizeof("gob.do")];
+ char stringpool_str3071[sizeof("u.bg")];
+ char stringpool_str3072[sizeof("gallery.museum")];
+ char stringpool_str3073[sizeof("yono.saitama.jp")];
+ char stringpool_str3074[sizeof("vardo.no")];
+ char stringpool_str3075[sizeof("shimokawa.hokkaido.jp")];
+ char stringpool_str3076[sizeof("tarnobrzeg.pl")];
+ char stringpool_str3077[sizeof("tomiya.miyagi.jp")];
+ char stringpool_str3078[sizeof("nasu.tochigi.jp")];
+ char stringpool_str3079[sizeof("yaroslavl.ru")];
+ char stringpool_str3080[sizeof("finnoy.no")];
+ char stringpool_str3081[sizeof("hemsedal.no")];
+ char stringpool_str3082[sizeof("rodoy.no")];
+ char stringpool_str3083[sizeof("dyndns-free.com")];
+ char stringpool_str3084[sizeof("nago.okinawa.jp")];
+ char stringpool_str3085[sizeof("uk")];
+ char stringpool_str3086[sizeof("nakano.tokyo.jp")];
+ char stringpool_str3087[sizeof("massacarrara.it")];
+ char stringpool_str3088[sizeof("org.cw")];
+ char stringpool_str3089[sizeof("sejny.pl")];
+ char stringpool_str3090[sizeof("hadsel.no")];
+ char stringpool_str3091[sizeof("hitoyoshi.kumamoto.jp")];
+ char stringpool_str3092[sizeof("com.ro")];
+ char stringpool_str3093[sizeof("com.re")];
+ char stringpool_str3094[sizeof("go.kr")];
+ char stringpool_str3095[sizeof("co.kr")];
+ char stringpool_str3096[sizeof("seljord.no")];
+ char stringpool_str3097[sizeof("co.ir")];
+ char stringpool_str3098[sizeof("tynset.no")];
+ char stringpool_str3099[sizeof("go.cr")];
+ char stringpool_str3100[sizeof("ed.cr")];
+ char stringpool_str3101[sizeof("gov.rs")];
+ char stringpool_str3102[sizeof("co.cr")];
+ char stringpool_str3103[sizeof("edu.rs")];
+ char stringpool_str3104[sizeof("shinanomachi.nagano.jp")];
+ char stringpool_str3105[sizeof("dnsdojo.net")];
+ char stringpool_str3106[sizeof("is-a-llama.com")];
+ char stringpool_str3107[sizeof("nagatoro.saitama.jp")];
+ char stringpool_str3108[sizeof("yokkaichi.mie.jp")];
+ char stringpool_str3109[sizeof("nakano.nagano.jp")];
+ char stringpool_str3110[sizeof("naie.hokkaido.jp")];
+ char stringpool_str3111[sizeof("annefrank.museum")];
+ char stringpool_str3112[sizeof("cuneo.it")];
+ char stringpool_str3113[sizeof("yazu.tottori.jp")];
+ char stringpool_str3114[sizeof("es.kr")];
+ char stringpool_str3115[sizeof("california.museum")];
+ char stringpool_str3116[sizeof("averoy.no")];
+ char stringpool_str3117[sizeof("luroy.no")];
+ char stringpool_str3118[sizeof("fukuyama.hiroshima.jp")];
+ char stringpool_str3119[sizeof("mil.do")];
+ char stringpool_str3120[sizeof("sakai.osaka.jp")];
+ char stringpool_str3121[sizeof("re.kr")];
+ char stringpool_str3122[sizeof("nagiso.nagano.jp")];
+ char stringpool_str3123[sizeof("gov.ru")];
+ char stringpool_str3124[sizeof("ae.org")];
+ char stringpool_str3125[sizeof("fermo.it")];
+ char stringpool_str3126[sizeof("edu.ru")];
+ char stringpool_str3127[sizeof("com.ru")];
+ char stringpool_str3128[sizeof("farmequipment.museum")];
+ char stringpool_str3129[sizeof("andoy.no")];
+ char stringpool_str3130[sizeof("shimosuwa.nagano.jp")];
+ char stringpool_str3131[sizeof("moriyama.shiga.jp")];
+ char stringpool_str3132[sizeof("tm.fr")];
+ char stringpool_str3133[sizeof("nakanojo.gunma.jp")];
+ char stringpool_str3134[sizeof("hatoyama.saitama.jp")];
+ char stringpool_str3135[sizeof("univ.sn")];
+ char stringpool_str3136[sizeof("nakasatsunai.hokkaido.jp")];
+ char stringpool_str3137[sizeof("nagi.okayama.jp")];
+ char stringpool_str3138[sizeof("niki.hokkaido.jp")];
+ char stringpool_str3139[sizeof("yamato.kumamoto.jp")];
+ char stringpool_str3140[sizeof("rishirifuji.hokkaido.jp")];
+ char stringpool_str3141[sizeof("kawai.iwate.jp")];
+ char stringpool_str3142[sizeof("stv.ru")];
+ char stringpool_str3143[sizeof("uvic.museum")];
+ char stringpool_str3144[sizeof("city.yokohama.jp")];
+ char stringpool_str3145[sizeof("s3-website-ap-southeast-2.amazonaws.com")];
+ char stringpool_str3146[sizeof("tottori.jp")];
+ char stringpool_str3147[sizeof("gov.mo")];
+ char stringpool_str3148[sizeof("gov.me")];
+ char stringpool_str3149[sizeof("ms.kr")];
+ char stringpool_str3150[sizeof("edu.mo")];
+ char stringpool_str3151[sizeof("edu.me")];
+ char stringpool_str3152[sizeof("gov.mk")];
+ char stringpool_str3153[sizeof("edu.mk")];
+ char stringpool_str3154[sizeof("com.mo")];
+ char stringpool_str3155[sizeof("miasta.pl")];
+ char stringpool_str3156[sizeof("com.mk")];
+ char stringpool_str3157[sizeof("gov.mg")];
+ char stringpool_str3158[sizeof("edu.mg")];
+ char stringpool_str3159[sizeof("radoy.no")];
+ char stringpool_str3160[sizeof("com.mg")];
+ char stringpool_str3161[sizeof("co.im")];
+ char stringpool_str3162[sizeof("gov.ma")];
+ char stringpool_str3163[sizeof("kms.ru")];
+ char stringpool_str3164[sizeof("tom.ru")];
+ char stringpool_str3165[sizeof("www.ck")];
+ char stringpool_str3166[sizeof("nedre-eiker.no")];
+ char stringpool_str3167[sizeof("web.nf")];
+ char stringpool_str3168[sizeof("s3-website-us-gov-west-1.amazonaws.com")];
+ char stringpool_str3169[sizeof("ozora.hokkaido.jp")];
+ char stringpool_str3170[sizeof("morioka.iwate.jp")];
+ char stringpool_str3171[sizeof("uri.arpa")];
+ char stringpool_str3172[sizeof("k12.de.us")];
+ char stringpool_str3173[sizeof("mitoyo.kagawa.jp")];
+ char stringpool_str3174[sizeof("reggio-emilia.it")];
+ char stringpool_str3175[sizeof("kounosu.saitama.jp")];
+ char stringpool_str3176[sizeof("money.museum")];
+ char stringpool_str3177[sizeof("seirou.niigata.jp")];
+ char stringpool_str3178[sizeof("urn.arpa")];
+ char stringpool_str3179[sizeof("osakikamijima.hiroshima.jp")];
+ char stringpool_str3180[sizeof("cc.hi.us")];
+ char stringpool_str3181[sizeof("kawanabe.kagoshima.jp")];
+ char stringpool_str3182[sizeof("kouyama.kagoshima.jp")];
+ char stringpool_str3183[sizeof("honefoss.no")];
+ char stringpool_str3184[sizeof("gov.mu")];
+ char stringpool_str3185[sizeof("rnd.ru")];
+ char stringpool_str3186[sizeof("snz.ru")];
+ char stringpool_str3187[sizeof("lib.de.us")];
+ char stringpool_str3188[sizeof("com.mu")];
+ char stringpool_str3189[sizeof("sa.cr")];
+ char stringpool_str3190[sizeof("nirasaki.yamanashi.jp")];
+ char stringpool_str3191[sizeof("k12.dc.us")];
+ char stringpool_str3192[sizeof("uba.ar")];
+ char stringpool_str3193[sizeof("otsuchi.iwate.jp")];
+ char stringpool_str3194[sizeof("station.museum")];
+ char stringpool_str3195[sizeof("kamioka.akita.jp")];
+ char stringpool_str3196[sizeof("sapporo.jp")];
+ char stringpool_str3197[sizeof("tm.km")];
+ char stringpool_str3198[sizeof("misconfused.org")];
+ char stringpool_str3199[sizeof("gov.iq")];
+ char stringpool_str3200[sizeof("edu.iq")];
+ char stringpool_str3201[sizeof("hakui.ishikawa.jp")];
+ char stringpool_str3202[sizeof("gov.mr")];
+ char stringpool_str3203[sizeof("gov.mn")];
+ char stringpool_str3204[sizeof("com.iq")];
+ char stringpool_str3205[sizeof("edu.mn")];
+ char stringpool_str3206[sizeof("meloy.no")];
+ char stringpool_str3207[sizeof("lib.dc.us")];
+ char stringpool_str3208[sizeof("co.tm")];
+ char stringpool_str3209[sizeof("fi.cr")];
+ char stringpool_str3210[sizeof("ginan.gifu.jp")];
+ char stringpool_str3211[sizeof("unbi.ba")];
+ char stringpool_str3212[sizeof("archaeology.museum")];
+ char stringpool_str3213[sizeof("kui.hiroshima.jp")];
+ char stringpool_str3214[sizeof("yuki.ibaraki.jp")];
+ char stringpool_str3215[sizeof("bahcavuotna.no")];
+ char stringpool_str3216[sizeof("yokote.akita.jp")];
+ char stringpool_str3217[sizeof("narusawa.yamanashi.jp")];
+ char stringpool_str3218[sizeof("masoy.no")];
+ char stringpool_str3219[sizeof("sa.edu.au")];
+ char stringpool_str3220[sizeof("cc.ri.us")];
+ char stringpool_str3221[sizeof("tochio.niigata.jp")];
+ char stringpool_str3222[sizeof("gov.ml")];
+ char stringpool_str3223[sizeof("edu.ml")];
+ char stringpool_str3224[sizeof("askoy.no")];
+ char stringpool_str3225[sizeof("floro.no")];
+ char stringpool_str3226[sizeof("com.ml")];
+ char stringpool_str3227[sizeof("takayama.nagano.jp")];
+ char stringpool_str3228[sizeof("malatvuopmi.no")];
+ char stringpool_str3229[sizeof("textile.museum")];
+ char stringpool_str3230[sizeof("uy")];
+ char stringpool_str3231[sizeof("teledata.mz")];
+ char stringpool_str3232[sizeof("hachioji.tokyo.jp")];
+ char stringpool_str3233[sizeof("net.tw")];
+ char stringpool_str3234[sizeof("ivgu.no")];
+ char stringpool_str3235[sizeof("chichibu.saitama.jp")];
+ char stringpool_str3236[sizeof("higashikawa.hokkaido.jp")];
+ char stringpool_str3237[sizeof("unsa.ba")];
+ char stringpool_str3238[sizeof("massa-carrara.it")];
+ char stringpool_str3239[sizeof("zhitomir.ua")];
+ char stringpool_str3240[sizeof("tadaoka.osaka.jp")];
+ char stringpool_str3241[sizeof("yorkshire.museum")];
+ char stringpool_str3242[sizeof("toyotsu.fukuoka.jp")];
+ char stringpool_str3243[sizeof("tottori.tottori.jp")];
+ char stringpool_str3244[sizeof("nc")];
+ char stringpool_str3245[sizeof("ina.nagano.jp")];
+ char stringpool_str3246[sizeof("kameoka.kyoto.jp")];
+ char stringpool_str3247[sizeof("khv.ru")];
+ char stringpool_str3248[sizeof("indianapolis.museum")];
+ char stringpool_str3249[sizeof("arboretum.museum")];
+ char stringpool_str3250[sizeof("shimonita.gunma.jp")];
+ char stringpool_str3251[sizeof("stjordal.no")];
+ char stringpool_str3252[sizeof("memorial.museum")];
+ char stringpool_str3253[sizeof("varoy.no")];
+ char stringpool_str3254[sizeof("e-burg.ru")];
+ char stringpool_str3255[sizeof("jar.ru")];
+ char stringpool_str3256[sizeof("judaica.museum")];
+ char stringpool_str3257[sizeof("bir.ru")];
+ char stringpool_str3258[sizeof("net.cw")];
+ char stringpool_str3259[sizeof("takayama.gunma.jp")];
+ char stringpool_str3260[sizeof("my.id")];
+ char stringpool_str3261[sizeof("orskog.no")];
+ char stringpool_str3262[sizeof("kemerovo.ru")];
+ char stringpool_str3263[sizeof("kunstunddesign.museum")];
+ char stringpool_str3264[sizeof("zoological.museum")];
+ char stringpool_str3265[sizeof("kunstsammlung.museum")];
+ char stringpool_str3266[sizeof("tachikawa.tokyo.jp")];
+ char stringpool_str3267[sizeof("shikokuchuo.ehime.jp")];
+ char stringpool_str3268[sizeof("nc.us")];
+ char stringpool_str3269[sizeof("isen.kagoshima.jp")];
+ char stringpool_str3270[sizeof("int.is")];
+ char stringpool_str3271[sizeof("kostroma.ru")];
+ char stringpool_str3272[sizeof("yokoze.saitama.jp")];
+ char stringpool_str3273[sizeof("chelyabinsk.ru")];
+ char stringpool_str3274[sizeof("mil.ru")];
+ char stringpool_str3275[sizeof("uk.net")];
+ char stringpool_str3276[sizeof("soundandvision.museum")];
+ char stringpool_str3277[sizeof("foundation.museum")];
+ char stringpool_str3278[sizeof("fukushima.fukushima.jp")];
+ char stringpool_str3279[sizeof("vrn.ru")];
+ char stringpool_str3280[sizeof("shichinohe.aomori.jp")];
+ char stringpool_str3281[sizeof("naka.hiroshima.jp")];
+ char stringpool_str3282[sizeof("ogawa.saitama.jp")];
+ char stringpool_str3283[sizeof("kitahata.saga.jp")];
+ char stringpool_str3284[sizeof("malbork.pl")];
+ char stringpool_str3285[sizeof("louvre.museum")];
+ char stringpool_str3286[sizeof("sango.nara.jp")];
+ char stringpool_str3287[sizeof("msk.ru")];
+ char stringpool_str3288[sizeof("aerobatic.aero")];
+ char stringpool_str3289[sizeof("flanders.museum")];
+ char stringpool_str3290[sizeof("ind.in")];
+ char stringpool_str3291[sizeof("k12.ri.us")];
+ char stringpool_str3292[sizeof("tsk.ru")];
+ char stringpool_str3293[sizeof("adachi.tokyo.jp")];
+ char stringpool_str3294[sizeof("istmein.de")];
+ char stringpool_str3295[sizeof("mizuho.tokyo.jp")];
+ char stringpool_str3296[sizeof("obama.fukui.jp")];
+ char stringpool_str3297[sizeof("shiroishi.saga.jp")];
+ char stringpool_str3298[sizeof("obira.hokkaido.jp")];
+ char stringpool_str3299[sizeof("kasuya.fukuoka.jp")];
+ char stringpool_str3300[sizeof("mil.mg")];
+ char stringpool_str3301[sizeof("kawahara.tottori.jp")];
+ char stringpool_str3302[sizeof("matera.it")];
+ char stringpool_str3303[sizeof("nogi.tochigi.jp")];
+ char stringpool_str3304[sizeof("children.museum")];
+ char stringpool_str3305[sizeof("modern.museum")];
+ char stringpool_str3306[sizeof("lib.ri.us")];
+ char stringpool_str3307[sizeof("chosei.chiba.jp")];
+ char stringpool_str3308[sizeof("broadcast.museum")];
+ char stringpool_str3309[sizeof("ug")];
+ char stringpool_str3310[sizeof("niihama.ehime.jp")];
+ char stringpool_str3311[sizeof("honjyo.akita.jp")];
+ char stringpool_str3312[sizeof("irkutsk.ru")];
+ char stringpool_str3313[sizeof("jolster.no")];
+ char stringpool_str3314[sizeof("bungotakada.oita.jp")];
+ char stringpool_str3315[sizeof("ashiya.hyogo.jp")];
+ char stringpool_str3316[sizeof("luzern.museum")];
+ char stringpool_str3317[sizeof("spb.ru")];
+ char stringpool_str3318[sizeof("ashiya.fukuoka.jp")];
+ char stringpool_str3319[sizeof("kanoya.kagoshima.jp")];
+ char stringpool_str3320[sizeof("takko.aomori.jp")];
+ char stringpool_str3321[sizeof("hiraya.nagano.jp")];
+ char stringpool_str3322[sizeof("awaji.hyogo.jp")];
+ char stringpool_str3323[sizeof("nagawa.nagano.jp")];
+ char stringpool_str3324[sizeof("samara.ru")];
+ char stringpool_str3325[sizeof("niigata.niigata.jp")];
+ char stringpool_str3326[sizeof("otobe.hokkaido.jp")];
+ char stringpool_str3327[sizeof("montreal.museum")];
+ char stringpool_str3328[sizeof("cmw.ru")];
+ char stringpool_str3329[sizeof("automotive.museum")];
+ char stringpool_str3330[sizeof("aomori.jp")];
+ char stringpool_str3331[sizeof("childrens.museum")];
+ char stringpool_str3332[sizeof("canada.museum")];
+ char stringpool_str3333[sizeof("asuke.aichi.jp")];
+ char stringpool_str3334[sizeof("ingatlan.hu")];
+ char stringpool_str3335[sizeof("building.museum")];
+ char stringpool_str3336[sizeof("ol.no")];
+ char stringpool_str3337[sizeof("british.museum")];
+ char stringpool_str3338[sizeof("obama.nagasaki.jp")];
+ char stringpool_str3339[sizeof("kasaoka.okayama.jp")];
+ char stringpool_str3340[sizeof("mil.iq")];
+ char stringpool_str3341[sizeof("brindisi.it")];
+ char stringpool_str3342[sizeof("nagano.nagano.jp")];
+ char stringpool_str3343[sizeof("nic.tr")];
+ char stringpool_str3344[sizeof("russia.museum")];
+ char stringpool_str3345[sizeof("nakanoto.ishikawa.jp")];
+ char stringpool_str3346[sizeof("tomioka.gunma.jp")];
+ char stringpool_str3347[sizeof("nic.ar")];
+ char stringpool_str3348[sizeof("k12.mo.us")];
+ char stringpool_str3349[sizeof("k12.me.us")];
+ char stringpool_str3350[sizeof("k12.md.us")];
+ char stringpool_str3351[sizeof("czest.pl")];
+ char stringpool_str3352[sizeof("machida.tokyo.jp")];
+ char stringpool_str3353[sizeof("k12.mi.us")];
+ char stringpool_str3354[sizeof("hs.kr")];
+ char stringpool_str3355[sizeof("australia.museum")];
+ char stringpool_str3356[sizeof("naganohara.gunma.jp")];
+ char stringpool_str3357[sizeof("omihachiman.shiga.jp")];
+ char stringpool_str3358[sizeof("aircraft.aero")];
+ char stringpool_str3359[sizeof("kongsvinger.no")];
+ char stringpool_str3360[sizeof("nagasaki.nagasaki.jp")];
+ char stringpool_str3361[sizeof("k12.ma.us")];
+ char stringpool_str3362[sizeof("kudoyama.wakayama.jp")];
+ char stringpool_str3363[sizeof("k12.ms.us")];
+ char stringpool_str3364[sizeof("higashikagawa.kagawa.jp")];
+ char stringpool_str3365[sizeof("yame.fukuoka.jp")];
+ char stringpool_str3366[sizeof("lib.mo.us")];
+ char stringpool_str3367[sizeof("lib.me.us")];
+ char stringpool_str3368[sizeof("lib.md.us")];
+ char stringpool_str3369[sizeof("kofu.yamanashi.jp")];
+ char stringpool_str3370[sizeof("lib.mi.us")];
+ char stringpool_str3371[sizeof("kyoto.jp")];
+ char stringpool_str3372[sizeof("mochizuki.nagano.jp")];
+ char stringpool_str3373[sizeof("tanohata.iwate.jp")];
+ char stringpool_str3374[sizeof("chitose.hokkaido.jp")];
+ char stringpool_str3375[sizeof("arteducation.museum")];
+ char stringpool_str3376[sizeof("carrier.museum")];
+ char stringpool_str3377[sizeof("dnsdojo.org")];
+ char stringpool_str3378[sizeof("forsand.no")];
+ char stringpool_str3379[sizeof("tosashimizu.kochi.jp")];
+ char stringpool_str3380[sizeof("lib.ma.us")];
+ char stringpool_str3381[sizeof("tjeldsund.no")];
+ char stringpool_str3382[sizeof("dielddanuorri.no")];
+ char stringpool_str3383[sizeof("lib.ms.us")];
+ char stringpool_str3384[sizeof("yakutia.ru")];
+ char stringpool_str3385[sizeof("miners.museum")];
+ char stringpool_str3386[sizeof("yekaterinburg.ru")];
+ char stringpool_str3387[sizeof("nic.tj")];
+ char stringpool_str3388[sizeof("columbus.museum")];
+ char stringpool_str3389[sizeof("kicks-ass.net")];
+ char stringpool_str3390[sizeof("gets-it.net")];
+ char stringpool_str3391[sizeof("tochigi.jp")];
+ char stringpool_str3392[sizeof("utah.museum")];
+ char stringpool_str3393[sizeof("teramo.it")];
+ char stringpool_str3394[sizeof("iki.nagasaki.jp")];
+ char stringpool_str3395[sizeof("zhytomyr.ua")];
+ char stringpool_str3396[sizeof("badaddja.no")];
+ char stringpool_str3397[sizeof("fujioka.gunma.jp")];
+ char stringpool_str3398[sizeof("elburg.museum")];
+ char stringpool_str3399[sizeof("k12.mt.us")];
+ char stringpool_str3400[sizeof("am.br")];
+ char stringpool_str3401[sizeof("miyoshi.aichi.jp")];
+ char stringpool_str3402[sizeof("wakasa.tottori.jp")];
+ char stringpool_str3403[sizeof("shimoda.shizuoka.jp")];
+ char stringpool_str3404[sizeof("moscow.museum")];
+ char stringpool_str3405[sizeof("city.kobe.jp")];
+ char stringpool_str3406[sizeof("vestre-toten.no")];
+ char stringpool_str3407[sizeof("hattfjelldal.no")];
+ char stringpool_str3408[sizeof("dyroy.no")];
+ char stringpool_str3409[sizeof("k12.mn.us")];
+ char stringpool_str3410[sizeof("odate.akita.jp")];
+ char stringpool_str3411[sizeof("nogata.fukuoka.jp")];
+ char stringpool_str3412[sizeof("cinema.museum")];
+ char stringpool_str3413[sizeof("lib.mt.us")];
+ char stringpool_str3414[sizeof("federation.aero")];
+ char stringpool_str3415[sizeof("yamanashi.jp")];
+ char stringpool_str3416[sizeof("from-az.net")];
+ char stringpool_str3417[sizeof("kochi.jp")];
+ char stringpool_str3418[sizeof("brescia.it")];
+ char stringpool_str3419[sizeof("warabi.saitama.jp")];
+ char stringpool_str3420[sizeof("kobayashi.miyazaki.jp")];
+ char stringpool_str3421[sizeof("oregon.museum")];
+ char stringpool_str3422[sizeof("hachinohe.aomori.jp")];
+ char stringpool_str3423[sizeof("omura.nagasaki.jp")];
+ char stringpool_str3424[sizeof("lib.mn.us")];
+ char stringpool_str3425[sizeof("rifu.miyagi.jp")];
+ char stringpool_str3426[sizeof("beiarn.no")];
+ char stringpool_str3427[sizeof("sannohe.aomori.jp")];
+ char stringpool_str3428[sizeof("iveland.no")];
+ char stringpool_str3429[sizeof("higashiura.aichi.jp")];
+ char stringpool_str3430[sizeof("shimokitayama.nara.jp")];
+ char stringpool_str3431[sizeof("uda.nara.jp")];
+ char stringpool_str3432[sizeof("aomori.aomori.jp")];
+ char stringpool_str3433[sizeof("semboku.akita.jp")];
+ char stringpool_str3434[sizeof("stavropol.ru")];
+ char stringpool_str3435[sizeof("czeladz.pl")];
+ char stringpool_str3436[sizeof("web.id")];
+ char stringpool_str3437[sizeof("tv.br")];
+ char stringpool_str3438[sizeof("does-it.net")];
+ char stringpool_str3439[sizeof("ullensaker.no")];
+ char stringpool_str3440[sizeof("mansions.museum")];
+ char stringpool_str3441[sizeof("namie.fukushima.jp")];
+ char stringpool_str3442[sizeof("railroad.museum")];
+ char stringpool_str3443[sizeof("romsa.no")];
+ char stringpool_str3444[sizeof("chita.aichi.jp")];
+ char stringpool_str3445[sizeof("ueno.gunma.jp")];
+ char stringpool_str3446[sizeof("beardu.no")];
+ char stringpool_str3447[sizeof("fm.br")];
+ char stringpool_str3448[sizeof("bauern.museum")];
+ char stringpool_str3449[sizeof("usarts.museum")];
+ char stringpool_str3450[sizeof("touch.museum")];
+ char stringpool_str3451[sizeof("storfjord.no")];
+ char stringpool_str3452[sizeof("nakijin.okinawa.jp")];
+ char stringpool_str3453[sizeof("takahama.aichi.jp")];
+ char stringpool_str3454[sizeof("sakuho.nagano.jp")];
+ char stringpool_str3455[sizeof("aichi.jp")];
+ char stringpool_str3456[sizeof("florida.museum")];
+ char stringpool_str3457[sizeof("gs.st.no")];
+ char stringpool_str3458[sizeof("takaharu.miyazaki.jp")];
+ char stringpool_str3459[sizeof("repbody.aero")];
+ char stringpool_str3460[sizeof("co.mu")];
+ char stringpool_str3461[sizeof("catering.aero")];
+ char stringpool_str3462[sizeof("daisen.akita.jp")];
+ char stringpool_str3463[sizeof("com.uz")];
+ char stringpool_str3464[sizeof("com.ug")];
+ char stringpool_str3465[sizeof("yonabaru.okinawa.jp")];
+ char stringpool_str3466[sizeof("gov.ua")];
+ char stringpool_str3467[sizeof("edu.ua")];
+ char stringpool_str3468[sizeof("kg.kr")];
+ char stringpool_str3469[sizeof("com.ua")];
+ char stringpool_str3470[sizeof("fuossko.no")];
+ char stringpool_str3471[sizeof("from-wv.com")];
+ char stringpool_str3472[sizeof("odawara.kanagawa.jp")];
+ char stringpool_str3473[sizeof("kanie.aichi.jp")];
+ char stringpool_str3474[sizeof("is-a-candidate.org")];
+ char stringpool_str3475[sizeof("beeldengeluid.museum")];
+ char stringpool_str3476[sizeof("trust.museum")];
+ char stringpool_str3477[sizeof("iizuna.nagano.jp")];
+ char stringpool_str3478[sizeof("cloudfront.net")];
+ char stringpool_str3479[sizeof("karpacz.pl")];
+ char stringpool_str3480[sizeof("ochi.kochi.jp")];
+ char stringpool_str3481[sizeof("gjerdrum.no")];
+ char stringpool_str3482[sizeof("mod.uk")];
+ char stringpool_str3483[sizeof("iwatsuki.saitama.jp")];
+ char stringpool_str3484[sizeof("kin.okinawa.jp")];
+ char stringpool_str3485[sizeof("alessandria.it")];
+ char stringpool_str3486[sizeof("higashitsuno.kochi.jp")];
+ char stringpool_str3487[sizeof("rzeszow.pl")];
+ char stringpool_str3488[sizeof("kunst.museum")];
+ char stringpool_str3489[sizeof("anthro.museum")];
+ char stringpool_str3490[sizeof("yasu.shiga.jp")];
+ char stringpool_str3491[sizeof("tatarstan.ru")];
+ char stringpool_str3492[sizeof("rissa.no")];
+ char stringpool_str3493[sizeof("hinohara.tokyo.jp")];
+ char stringpool_str3494[sizeof("rochester.museum")];
+ char stringpool_str3495[sizeof("yamatotakada.nara.jp")];
+ char stringpool_str3496[sizeof("sciencecenter.museum")];
+ char stringpool_str3497[sizeof("is-very-evil.org")];
+ char stringpool_str3498[sizeof("ayagawa.kagawa.jp")];
+ char stringpool_str3499[sizeof("hobby-site.org")];
+ char stringpool_str3500[sizeof("erimo.hokkaido.jp")];
+ char stringpool_str3501[sizeof("owani.aomori.jp")];
+ char stringpool_str3502[sizeof("kotohira.kagawa.jp")];
+ char stringpool_str3503[sizeof("from-wa.com")];
+ char stringpool_str3504[sizeof("ilawa.pl")];
+ char stringpool_str3505[sizeof("exchange.aero")];
+ char stringpool_str3506[sizeof("kumamoto.jp")];
+ char stringpool_str3507[sizeof("otaki.saitama.jp")];
+ char stringpool_str3508[sizeof("museum")];
+ char stringpool_str3509[sizeof("entertainment.aero")];
+ char stringpool_str3510[sizeof("ogawa.nagano.jp")];
+ char stringpool_str3511[sizeof("takehara.hiroshima.jp")];
+ char stringpool_str3512[sizeof("gs.ah.no")];
+ char stringpool_str3513[sizeof("konan.aichi.jp")];
+ char stringpool_str3514[sizeof("s3-fips-us-gov-west-1.amazonaws.com")];
+ char stringpool_str3515[sizeof("pm")];
+ char stringpool_str3516[sizeof("sasayama.hyogo.jp")];
+ char stringpool_str3517[sizeof("itoman.okinawa.jp")];
+ char stringpool_str3518[sizeof("sciencecenters.museum")];
+ char stringpool_str3519[sizeof("ohira.miyagi.jp")];
+ char stringpool_str3520[sizeof("kitashiobara.fukushima.jp")];
+ char stringpool_str3521[sizeof("izhevsk.ru")];
+ char stringpool_str3522[sizeof("fed.us")];
+ char stringpool_str3523[sizeof("kitayama.wakayama.jp")];
+ char stringpool_str3524[sizeof("kouhoku.saga.jp")];
+ char stringpool_str3525[sizeof("trondheim.no")];
+ char stringpool_str3526[sizeof("pr")];
+ char stringpool_str3527[sizeof("pro")];
+ char stringpool_str3528[sizeof("iwanai.hokkaido.jp")];
+ char stringpool_str3529[sizeof("po.it")];
+ char stringpool_str3530[sizeof("pv.it")];
+ char stringpool_str3531[sizeof("pu.it")];
+ char stringpool_str3532[sizeof("pd.it")];
+ char stringpool_str3533[sizeof("malselv.no")];
+ char stringpool_str3534[sizeof("jet.uk")];
+ char stringpool_str3535[sizeof("museum.no")];
+ char stringpool_str3536[sizeof("pz.it")];
+ char stringpool_str3537[sizeof("shiroishi.miyagi.jp")];
+ char stringpool_str3538[sizeof("surgut.ru")];
+ char stringpool_str3539[sizeof("hongo.hiroshima.jp")];
+ char stringpool_str3540[sizeof("leirfjord.no")];
+ char stringpool_str3541[sizeof("gov.sx")];
+ char stringpool_str3542[sizeof("vestre-slidre.no")];
+ char stringpool_str3543[sizeof("encyclopedic.museum")];
+ char stringpool_str3544[sizeof("pt")];
+ char stringpool_str3545[sizeof("pro.az")];
+ char stringpool_str3546[sizeof("carbonia-iglesias.it")];
+ char stringpool_str3547[sizeof("pr.it")];
+ char stringpool_str3548[sizeof("brasil.museum")];
+ char stringpool_str3549[sizeof("ps")];
+ char stringpool_str3550[sizeof("workshop.museum")];
+ char stringpool_str3551[sizeof("toyooka.hyogo.jp")];
+ char stringpool_str3552[sizeof("usantiques.museum")];
+ char stringpool_str3553[sizeof("pe")];
+ char stringpool_str3554[sizeof("school.na")];
+ char stringpool_str3555[sizeof("hyogo.jp")];
+ char stringpool_str3556[sizeof("takaoka.toyama.jp")];
+ char stringpool_str3557[sizeof("pt.it")];
+ char stringpool_str3558[sizeof("museum.tt")];
+ char stringpool_str3559[sizeof("nf")];
+ char stringpool_str3560[sizeof("pe.it")];
+ char stringpool_str3561[sizeof("ogawara.miyagi.jp")];
+ char stringpool_str3562[sizeof("kamifurano.hokkaido.jp")];
+ char stringpool_str3563[sizeof("pr.us")];
+ char stringpool_str3564[sizeof("raisa.no")];
+ char stringpool_str3565[sizeof("pe.ca")];
+ char stringpool_str3566[sizeof("p.se")];
+ char stringpool_str3567[sizeof("gov.cx")];
+ char stringpool_str3568[sizeof("snasa.no")];
+ char stringpool_str3569[sizeof("cbg.ru")];
+ char stringpool_str3570[sizeof("gs.of.no")];
+ char stringpool_str3571[sizeof("nf.ca")];
+ char stringpool_str3572[sizeof("fortmissoula.museum")];
+ char stringpool_str3573[sizeof("yoro.gifu.jp")];
+ char stringpool_str3574[sizeof("school.museum")];
+ char stringpool_str3575[sizeof("per.sg")];
+ char stringpool_str3576[sizeof("pro.tt")];
+ char stringpool_str3577[sizeof("stjordalshalsen.no")];
+ char stringpool_str3578[sizeof("yawata.kyoto.jp")];
+ char stringpool_str3579[sizeof("trysil.no")];
+ char stringpool_str3580[sizeof("takahagi.ibaraki.jp")];
+ char stringpool_str3581[sizeof("pn")];
+ char stringpool_str3582[sizeof("galsa.no")];
+ char stringpool_str3583[sizeof("iwaizumi.iwate.jp")];
+ char stringpool_str3584[sizeof("takayama.gifu.jp")];
+ char stringpool_str3585[sizeof("zushi.kanagawa.jp")];
+ char stringpool_str3586[sizeof("olbia-tempio.it")];
+ char stringpool_str3587[sizeof("frosinone.it")];
+ char stringpool_str3588[sizeof("pro.br")];
+ char stringpool_str3589[sizeof("pi.it")];
+ char stringpool_str3590[sizeof("glass.museum")];
+ char stringpool_str3591[sizeof("yuzawa.niigata.jp")];
+ char stringpool_str3592[sizeof("usenet.pl")];
+ char stringpool_str3593[sizeof("pn.it")];
+ char stringpool_str3594[sizeof("kuchinotsu.nagasaki.jp")];
+ char stringpool_str3595[sizeof("tarui.gifu.jp")];
+ char stringpool_str3596[sizeof("museum.mv")];
+ char stringpool_str3597[sizeof("izu.shizuoka.jp")];
+ char stringpool_str3598[sizeof("post")];
+ char stringpool_str3599[sizeof("pl")];
+ char stringpool_str3600[sizeof("airguard.museum")];
+ char stringpool_str3601[sizeof("midori.gunma.jp")];
+ char stringpool_str3602[sizeof("sarufutsu.hokkaido.jp")];
+ char stringpool_str3603[sizeof("tgory.pl")];
+ char stringpool_str3604[sizeof("tabayama.yamanashi.jp")];
+ char stringpool_str3605[sizeof("motorcycle.museum")];
+ char stringpool_str3606[sizeof("harstad.no")];
+ char stringpool_str3607[sizeof("pa")];
+ char stringpool_str3608[sizeof("ito.shizuoka.jp")];
+ char stringpool_str3609[sizeof("gs.vf.no")];
+ char stringpool_str3610[sizeof("neat-url.com")];
+ char stringpool_str3611[sizeof("oguni.kumamoto.jp")];
+ char stringpool_str3612[sizeof("kuroiso.tochigi.jp")];
+ char stringpool_str3613[sizeof("computer.museum")];
+ char stringpool_str3614[sizeof("nonoichi.ishikawa.jp")];
+ char stringpool_str3615[sizeof("pl.ua")];
+ char stringpool_str3616[sizeof("okayama.jp")];
+ char stringpool_str3617[sizeof("pa.it")];
+ char stringpool_str3618[sizeof("fukudomi.saga.jp")];
+ char stringpool_str3619[sizeof("handa.aichi.jp")];
+ char stringpool_str3620[sizeof("leksvik.no")];
+ char stringpool_str3621[sizeof("square.museum")];
+ char stringpool_str3622[sizeof("p.bg")];
+ char stringpool_str3623[sizeof("nyny.museum")];
+ char stringpool_str3624[sizeof("sumida.tokyo.jp")];
+ char stringpool_str3625[sizeof("austrheim.no")];
+ char stringpool_str3626[sizeof("dni.us")];
+ char stringpool_str3627[sizeof("wake.okayama.jp")];
+ char stringpool_str3628[sizeof("lebork.pl")];
+ char stringpool_str3629[sizeof("wakkanai.hokkaido.jp")];
+ char stringpool_str3630[sizeof("engerdal.no")];
+ char stringpool_str3631[sizeof("masuda.shimane.jp")];
+ char stringpool_str3632[sizeof("heroy.nordland.no")];
+ char stringpool_str3633[sizeof("pa.us")];
+ char stringpool_str3634[sizeof("itabashi.tokyo.jp")];
+ char stringpool_str3635[sizeof("game-server.cc")];
+ char stringpool_str3636[sizeof("pub.sa")];
+ char stringpool_str3637[sizeof("oyabe.toyama.jp")];
+ char stringpool_str3638[sizeof("sa.au")];
+ char stringpool_str3639[sizeof("ph")];
+ char stringpool_str3640[sizeof("schlesisches.museum")];
+ char stringpool_str3641[sizeof("fukuchi.fukuoka.jp")];
+ char stringpool_str3642[sizeof("murayama.yamagata.jp")];
+ char stringpool_str3643[sizeof("pk")];
+ char stringpool_str3644[sizeof("yalta.ua")];
+ char stringpool_str3645[sizeof("algard.no")];
+ char stringpool_str3646[sizeof("priv.me")];
+ char stringpool_str3647[sizeof("toride.ibaraki.jp")];
+ char stringpool_str3648[sizeof("tsuno.miyazaki.jp")];
+ char stringpool_str3649[sizeof("sf.no")];
+ char stringpool_str3650[sizeof("sells-for-less.com")];
+ char stringpool_str3651[sizeof("okinawa.jp")];
+ char stringpool_str3652[sizeof("towada.aomori.jp")];
+ char stringpool_str3653[sizeof("yamato.fukushima.jp")];
+ char stringpool_str3654[sizeof("pro.ec")];
+ char stringpool_str3655[sizeof("pp.ua")];
+ char stringpool_str3656[sizeof("psi.br")];
+ char stringpool_str3657[sizeof("priv.at")];
+ char stringpool_str3658[sizeof("circus.museum")];
+ char stringpool_str3659[sizeof("homeftp.net")];
+ char stringpool_str3660[sizeof("sassari.it")];
+ char stringpool_str3661[sizeof("funahashi.toyama.jp")];
+ char stringpool_str3662[sizeof("is-a-linux-user.org")];
+ char stringpool_str3663[sizeof("nishihara.kumamoto.jp")];
+ char stringpool_str3664[sizeof("kaneyama.yamagata.jp")];
+ char stringpool_str3665[sizeof("co.hu")];
+ char stringpool_str3666[sizeof("izumiotsu.osaka.jp")];
+ char stringpool_str3667[sizeof("ogata.akita.jp")];
+ char stringpool_str3668[sizeof("shijonawate.osaka.jp")];
+ char stringpool_str3669[sizeof("iitate.fukushima.jp")];
+ char stringpool_str3670[sizeof("glogow.pl")];
+ char stringpool_str3671[sizeof("aquarium.museum")];
+ char stringpool_str3672[sizeof("og.ao")];
+ char stringpool_str3673[sizeof("yuzhno-sakhalinsk.ru")];
+ char stringpool_str3674[sizeof("sue.fukuoka.jp")];
+ char stringpool_str3675[sizeof("etnedal.no")];
+ char stringpool_str3676[sizeof("isernia.it")];
+ char stringpool_str3677[sizeof("helsinki.museum")];
+ char stringpool_str3678[sizeof("pro.na")];
+ char stringpool_str3679[sizeof("yamashina.kyoto.jp")];
+ char stringpool_str3680[sizeof("midori.chiba.jp")];
+ char stringpool_str3681[sizeof("kinko.kagoshima.jp")];
+ char stringpool_str3682[sizeof("yamato.kanagawa.jp")];
+ char stringpool_str3683[sizeof("arendal.no")];
+ char stringpool_str3684[sizeof("pri.ee")];
+ char stringpool_str3685[sizeof("perm.ru")];
+ char stringpool_str3686[sizeof("hjelmeland.no")];
+ char stringpool_str3687[sizeof("columbia.museum")];
+ char stringpool_str3688[sizeof("k12.ut.us")];
+ char stringpool_str3689[sizeof("nishiarita.saga.jp")];
+ char stringpool_str3690[sizeof("usui.fukuoka.jp")];
+ char stringpool_str3691[sizeof("krakow.pl")];
+ char stringpool_str3692[sizeof("urausu.hokkaido.jp")];
+ char stringpool_str3693[sizeof("tm.hu")];
+ char stringpool_str3694[sizeof("wakasa.fukui.jp")];
+ char stringpool_str3695[sizeof("ath.cx")];
+ char stringpool_str3696[sizeof("ushistory.museum")];
+ char stringpool_str3697[sizeof("ooshika.nagano.jp")];
+ char stringpool_str3698[sizeof("from-wy.com")];
+ char stringpool_str3699[sizeof("gojome.akita.jp")];
+ char stringpool_str3700[sizeof("izumisano.osaka.jp")];
+ char stringpool_str3701[sizeof("chesapeakebay.museum")];
+ char stringpool_str3702[sizeof("saito.miyazaki.jp")];
+ char stringpool_str3703[sizeof("lib.ut.us")];
+ char stringpool_str3704[sizeof("katori.chiba.jp")];
+ char stringpool_str3705[sizeof("sunndal.no")];
+ char stringpool_str3706[sizeof("upow.gov.pl")];
+ char stringpool_str3707[sizeof("jaworzno.pl")];
+ char stringpool_str3708[sizeof("higashichichibu.saitama.jp")];
+ char stringpool_str3709[sizeof("ohda.shimane.jp")];
+ char stringpool_str3710[sizeof("no.com")];
+ char stringpool_str3711[sizeof("cultural.museum")];
+ char stringpool_str3712[sizeof("wales.museum")];
+ char stringpool_str3713[sizeof("pisa.it")];
+ char stringpool_str3714[sizeof("ebino.miyazaki.jp")];
+ char stringpool_str3715[sizeof("arezzo.it")];
+ char stringpool_str3716[sizeof("software.aero")];
+ char stringpool_str3717[sizeof("urbino-pesaro.it")];
+ char stringpool_str3718[sizeof("pw")];
+ char stringpool_str3719[sizeof("kutchan.hokkaido.jp")];
+ char stringpool_str3720[sizeof("fujimi.saitama.jp")];
+ char stringpool_str3721[sizeof("blogspot.cf")];
+ char stringpool_str3722[sizeof("k12.tx.us")];
+ char stringpool_str3723[sizeof("graz.museum")];
+ char stringpool_str3724[sizeof("oe.yamagata.jp")];
+ char stringpool_str3725[sizeof("ustka.pl")];
+ char stringpool_str3726[sizeof("cambridge.museum")];
+ char stringpool_str3727[sizeof("vf.no")];
+ char stringpool_str3728[sizeof("midtre-gauldal.no")];
+ char stringpool_str3729[sizeof("dyndns-server.com")];
+ char stringpool_str3730[sizeof("oceanographique.museum")];
+ char stringpool_str3731[sizeof("yamatokoriyama.nara.jp")];
+ char stringpool_str3732[sizeof("hiroo.hokkaido.jp")];
+ char stringpool_str3733[sizeof("lib.tx.us")];
+ char stringpool_str3734[sizeof("otaki.nagano.jp")];
+ char stringpool_str3735[sizeof("rockart.museum")];
+ char stringpool_str3736[sizeof("eigersund.no")];
+ char stringpool_str3737[sizeof("university.museum")];
+ char stringpool_str3738[sizeof("nishimera.miyazaki.jp")];
+ char stringpool_str3739[sizeof("from-wi.com")];
+ char stringpool_str3740[sizeof("keisen.fukuoka.jp")];
+ char stringpool_str3741[sizeof("house.museum")];
+ char stringpool_str3742[sizeof("aizumi.tokushima.jp")];
+ char stringpool_str3743[sizeof("yamanakako.yamanashi.jp")];
+ char stringpool_str3744[sizeof("nanae.hokkaido.jp")];
+ char stringpool_str3745[sizeof("sakado.saitama.jp")];
+ char stringpool_str3746[sizeof("kaisei.kanagawa.jp")];
+ char stringpool_str3747[sizeof("sokndal.no")];
+ char stringpool_str3748[sizeof("kuzumaki.iwate.jp")];
+ char stringpool_str3749[sizeof("ebiz.tw")];
+ char stringpool_str3750[sizeof("portal.museum")];
+ char stringpool_str3751[sizeof("yamazoe.nara.jp")];
+ char stringpool_str3752[sizeof("coloradoplateau.museum")];
+ char stringpool_str3753[sizeof("ullensvang.no")];
+ char stringpool_str3754[sizeof("vladivostok.ru")];
+ char stringpool_str3755[sizeof("za.org")];
+ char stringpool_str3756[sizeof("hanno.saitama.jp")];
+ char stringpool_str3757[sizeof("komoro.nagano.jp")];
+ char stringpool_str3758[sizeof("idv.tw")];
+ char stringpool_str3759[sizeof("myoko.niigata.jp")];
+ char stringpool_str3760[sizeof("py")];
+ char stringpool_str3761[sizeof("pilots.museum")];
+ char stringpool_str3762[sizeof("washingtondc.museum")];
+ char stringpool_str3763[sizeof("scientist.aero")];
+ char stringpool_str3764[sizeof("folldal.no")];
+ char stringpool_str3765[sizeof("kasahara.gifu.jp")];
+ char stringpool_str3766[sizeof("bajddar.no")];
+ char stringpool_str3767[sizeof("shimoji.okinawa.jp")];
+ char stringpool_str3768[sizeof("nt.no")];
+ char stringpool_str3769[sizeof("saltdal.no")];
+ char stringpool_str3770[sizeof("github.io")];
+ char stringpool_str3771[sizeof("prof.pr")];
+ char stringpool_str3772[sizeof("linz.museum")];
+ char stringpool_str3773[sizeof("hachijo.tokyo.jp")];
+ char stringpool_str3774[sizeof("kumamoto.kumamoto.jp")];
+ char stringpool_str3775[sizeof("atsuma.hokkaido.jp")];
+ char stringpool_str3776[sizeof("warmia.pl")];
+ char stringpool_str3777[sizeof("museum.mw")];
+ char stringpool_str3778[sizeof("hofu.yamaguchi.jp")];
+ char stringpool_str3779[sizeof("iida.nagano.jp")];
+ char stringpool_str3780[sizeof("toyama.jp")];
+ char stringpool_str3781[sizeof("name.vn")];
+ char stringpool_str3782[sizeof("fujimino.saitama.jp")];
+ char stringpool_str3783[sizeof("chino.nagano.jp")];
+ char stringpool_str3784[sizeof("yomitan.okinawa.jp")];
+ char stringpool_str3785[sizeof("pro.vn")];
+ char stringpool_str3786[sizeof("sakahogi.gifu.jp")];
+ char stringpool_str3787[sizeof("seiro.niigata.jp")];
+ char stringpool_str3788[sizeof("leitungsen.de")];
+ char stringpool_str3789[sizeof("or.th")];
+ char stringpool_str3790[sizeof("port.fr")];
+ char stringpool_str3791[sizeof("is-very-good.org")];
+ char stringpool_str3792[sizeof("williamsburg.museum")];
+ char stringpool_str3793[sizeof("kitahiroshima.hokkaido.jp")];
+ char stringpool_str3794[sizeof("melhus.no")];
+ char stringpool_str3795[sizeof("ostroleka.pl")];
+ char stringpool_str3796[sizeof("elverum.no")];
+ char stringpool_str3797[sizeof("inawashiro.fukushima.jp")];
+ char stringpool_str3798[sizeof("sakura.chiba.jp")];
+ char stringpool_str3799[sizeof("ashoro.hokkaido.jp")];
+ char stringpool_str3800[sizeof("takahashi.okayama.jp")];
+ char stringpool_str3801[sizeof("halsa.no")];
+ char stringpool_str3802[sizeof("obihiro.hokkaido.jp")];
+ char stringpool_str3803[sizeof("hasuda.saitama.jp")];
+ char stringpool_str3804[sizeof("anpachi.gifu.jp")];
+ char stringpool_str3805[sizeof("tajiri.osaka.jp")];
+ char stringpool_str3806[sizeof("kadoma.osaka.jp")];
+ char stringpool_str3807[sizeof("svizzera.museum")];
+ char stringpool_str3808[sizeof("openair.museum")];
+ char stringpool_str3809[sizeof("osaki.miyagi.jp")];
+ char stringpool_str3810[sizeof("tomari.hokkaido.jp")];
+ char stringpool_str3811[sizeof("labour.museum")];
+ char stringpool_str3812[sizeof("yokawa.hyogo.jp")];
+ char stringpool_str3813[sizeof("mobara.chiba.jp")];
+ char stringpool_str3814[sizeof("yamanouchi.nagano.jp")];
+ char stringpool_str3815[sizeof("mihama.mie.jp")];
+ char stringpool_str3816[sizeof("saroma.hokkaido.jp")];
+ char stringpool_str3817[sizeof("pp.az")];
+ char stringpool_str3818[sizeof("brumunddal.no")];
+ char stringpool_str3819[sizeof("s3-us-west-2.amazonaws.com")];
+ char stringpool_str3820[sizeof("nozawaonsen.nagano.jp")];
+ char stringpool_str3821[sizeof("nl.no")];
+ char stringpool_str3822[sizeof("stordal.no")];
+ char stringpool_str3823[sizeof("ikoma.nara.jp")];
+ char stringpool_str3824[sizeof("gov.km")];
+ char stringpool_str3825[sizeof("edu.km")];
+ char stringpool_str3826[sizeof("gov.ki")];
+ char stringpool_str3827[sizeof("com.km")];
+ char stringpool_str3828[sizeof("gov.kz")];
+ char stringpool_str3829[sizeof("edu.ki")];
+ char stringpool_str3830[sizeof("edu.kz")];
+ char stringpool_str3831[sizeof("gov.kg")];
+ char stringpool_str3832[sizeof("com.ki")];
+ char stringpool_str3833[sizeof("edu.kg")];
+ char stringpool_str3834[sizeof("com.kz")];
+ char stringpool_str3835[sizeof("com.kg")];
+ char stringpool_str3836[sizeof("imakane.hokkaido.jp")];
+ char stringpool_str3837[sizeof("krasnoyarsk.ru")];
+ char stringpool_str3838[sizeof("is-a-bruinsfan.org")];
+ char stringpool_str3839[sizeof("namdalseid.no")];
+ char stringpool_str3840[sizeof("ac.th")];
+ char stringpool_str3841[sizeof("gaivuotna.no")];
+ char stringpool_str3842[sizeof("plants.museum")];
+ char stringpool_str3843[sizeof("childrensgarden.museum")];
+ char stringpool_str3844[sizeof("pg")];
+ char stringpool_str3845[sizeof("eiheiji.fukui.jp")];
+ char stringpool_str3846[sizeof("tokai.ibaraki.jp")];
+ char stringpool_str3847[sizeof("blogdns.net")];
+ char stringpool_str3848[sizeof("ogori.fukuoka.jp")];
+ char stringpool_str3849[sizeof("okegawa.saitama.jp")];
+ char stringpool_str3850[sizeof("salzburg.museum")];
+ char stringpool_str3851[sizeof("stokke.no")];
+ char stringpool_str3852[sizeof("sanjo.niigata.jp")];
+ char stringpool_str3853[sizeof("kimitsu.chiba.jp")];
+ char stringpool_str3854[sizeof("pg.it")];
+ char stringpool_str3855[sizeof("southwest.museum")];
+ char stringpool_str3856[sizeof("ina.saitama.jp")];
+ char stringpool_str3857[sizeof("ass.km")];
+ char stringpool_str3858[sizeof("tsu.mie.jp")];
+ char stringpool_str3859[sizeof("priv.no")];
+ char stringpool_str3860[sizeof("morimachi.shizuoka.jp")];
+ char stringpool_str3861[sizeof("priv.pl")];
+ char stringpool_str3862[sizeof("gov.kn")];
+ char stringpool_str3863[sizeof("edu.kn")];
+ char stringpool_str3864[sizeof("bruxelles.museum")];
+ char stringpool_str3865[sizeof("honjo.saitama.jp")];
+ char stringpool_str3866[sizeof("kitamoto.saitama.jp")];
+ char stringpool_str3867[sizeof("drammen.no")];
+ char stringpool_str3868[sizeof("per.nf")];
+ char stringpool_str3869[sizeof("traeumtgerade.de")];
+ char stringpool_str3870[sizeof("nic.im")];
+ char stringpool_str3871[sizeof("nishinoshima.shimane.jp")];
+ char stringpool_str3872[sizeof("environment.museum")];
+ char stringpool_str3873[sizeof("padova.it")];
+ char stringpool_str3874[sizeof("sa.gov.au")];
+ char stringpool_str3875[sizeof("mishima.fukushima.jp")];
+ char stringpool_str3876[sizeof("przeworsk.pl")];
+ char stringpool_str3877[sizeof("luxembourg.museum")];
+ char stringpool_str3878[sizeof("fujimi.nagano.jp")];
+ char stringpool_str3879[sizeof("caserta.it")];
+ char stringpool_str3880[sizeof("hamada.shimane.jp")];
+ char stringpool_str3881[sizeof("workinggroup.aero")];
+ char stringpool_str3882[sizeof("minamata.kumamoto.jp")];
+ char stringpool_str3883[sizeof("uslivinghistory.museum")];
+ char stringpool_str3884[sizeof("science.museum")];
+ char stringpool_str3885[sizeof("iyo.ehime.jp")];
+ char stringpool_str3886[sizeof("homeftp.org")];
+ char stringpool_str3887[sizeof("mashiko.tochigi.jp")];
+ char stringpool_str3888[sizeof("ogimi.okinawa.jp")];
+ char stringpool_str3889[sizeof("kitami.hokkaido.jp")];
+ char stringpool_str3890[sizeof("sondre-land.no")];
+ char stringpool_str3891[sizeof("odessa.ua")];
+ char stringpool_str3892[sizeof("bihoro.hokkaido.jp")];
+ char stringpool_str3893[sizeof("sakai.ibaraki.jp")];
+ char stringpool_str3894[sizeof("asahi.ibaraki.jp")];
+ char stringpool_str3895[sizeof("iruma.saitama.jp")];
+ char stringpool_str3896[sizeof("fredrikstad.no")];
+ char stringpool_str3897[sizeof("minamiise.mie.jp")];
+ char stringpool_str3898[sizeof("minami.tokushima.jp")];
+ char stringpool_str3899[sizeof("mihara.hiroshima.jp")];
+ char stringpool_str3900[sizeof("os.hordaland.no")];
+ char stringpool_str3901[sizeof("lyngdal.no")];
+ char stringpool_str3902[sizeof("takamori.kumamoto.jp")];
+ char stringpool_str3903[sizeof("yabu.hyogo.jp")];
+ char stringpool_str3904[sizeof("ham-radio-op.net")];
+ char stringpool_str3905[sizeof("palace.museum")];
+ char stringpool_str3906[sizeof("ac.se")];
+ char stringpool_str3907[sizeof("wada.nagano.jp")];
+ char stringpool_str3908[sizeof("nishikatsura.yamanashi.jp")];
+ char stringpool_str3909[sizeof("gs.mr.no")];
+ char stringpool_str3910[sizeof("nic.in")];
+ char stringpool_str3911[sizeof("ac.sz")];
+ char stringpool_str3912[sizeof("plantation.museum")];
+ char stringpool_str3913[sizeof("kisofukushima.nagano.jp")];
+ char stringpool_str3914[sizeof("publ.pt")];
+ char stringpool_str3915[sizeof("hinode.tokyo.jp")];
+ char stringpool_str3916[sizeof("saijo.ehime.jp")];
+ char stringpool_str3917[sizeof("manno.kagawa.jp")];
+ char stringpool_str3918[sizeof("gov.my")];
+ char stringpool_str3919[sizeof("ryazan.ru")];
+ char stringpool_str3920[sizeof("priv.hu")];
+ char stringpool_str3921[sizeof("edu.my")];
+ char stringpool_str3922[sizeof("national.museum")];
+ char stringpool_str3923[sizeof("from-vt.com")];
+ char stringpool_str3924[sizeof("com.my")];
+ char stringpool_str3925[sizeof("mihama.chiba.jp")];
+ char stringpool_str3926[sizeof("abiko.chiba.jp")];
+ char stringpool_str3927[sizeof("radom.pl")];
+ char stringpool_str3928[sizeof("kakuda.miyagi.jp")];
+ char stringpool_str3929[sizeof("nt.ro")];
+ char stringpool_str3930[sizeof("anthropology.museum")];
+ char stringpool_str3931[sizeof("yashio.saitama.jp")];
+ char stringpool_str3932[sizeof("palana.ru")];
+ char stringpool_str3933[sizeof("tawaramoto.nara.jp")];
+ char stringpool_str3934[sizeof("kazimierz-dolny.pl")];
+ char stringpool_str3935[sizeof("control.aero")];
+ char stringpool_str3936[sizeof("monzabrianza.it")];
+ char stringpool_str3937[sizeof("miyama.mie.jp")];
+ char stringpool_str3938[sizeof("yamanashi.yamanashi.jp")];
+ char stringpool_str3939[sizeof("tendo.yamagata.jp")];
+ char stringpool_str3940[sizeof("messina.it")];
+ char stringpool_str3941[sizeof("biz.ki")];
+ char stringpool_str3942[sizeof("yokosuka.kanagawa.jp")];
+ char stringpool_str3943[sizeof("ibaraki.jp")];
+ char stringpool_str3944[sizeof("nishihara.okinawa.jp")];
+ char stringpool_str3945[sizeof("otari.nagano.jp")];
+ char stringpool_str3946[sizeof("yoka.hyogo.jp")];
+ char stringpool_str3947[sizeof("okayama.okayama.jp")];
+ char stringpool_str3948[sizeof("agano.niigata.jp")];
+ char stringpool_str3949[sizeof("dazaifu.fukuoka.jp")];
+ char stringpool_str3950[sizeof("tonami.toyama.jp")];
+ char stringpool_str3951[sizeof("sondrio.it")];
+ char stringpool_str3952[sizeof("pordenone.it")];
+ char stringpool_str3953[sizeof("stalowa-wola.pl")];
+ char stringpool_str3954[sizeof("navuotna.no")];
+ char stringpool_str3955[sizeof("aland.fi")];
+ char stringpool_str3956[sizeof("otaki.chiba.jp")];
+ char stringpool_str3957[sizeof("kuromatsunai.hokkaido.jp")];
+ char stringpool_str3958[sizeof("gs.tr.no")];
+ char stringpool_str3959[sizeof("histoire.museum")];
+ char stringpool_str3960[sizeof("field.museum")];
+ char stringpool_str3961[sizeof("asago.hyogo.jp")];
+ char stringpool_str3962[sizeof("s3-us-west-1.amazonaws.com")];
+ char stringpool_str3963[sizeof("aisai.aichi.jp")];
+ char stringpool_str3964[sizeof("risor.no")];
+ char stringpool_str3965[sizeof("geometre-expert.fr")];
+ char stringpool_str3966[sizeof("sorum.no")];
+ char stringpool_str3967[sizeof("mil.km")];
+ char stringpool_str3968[sizeof("marumori.miyagi.jp")];
+ char stringpool_str3969[sizeof("kasama.ibaraki.jp")];
+ char stringpool_str3970[sizeof("gs.fm.no")];
+ char stringpool_str3971[sizeof("mil.kz")];
+ char stringpool_str3972[sizeof("sogndal.no")];
+ char stringpool_str3973[sizeof("mil.kg")];
+ char stringpool_str3974[sizeof("gov.lk")];
+ char stringpool_str3975[sizeof("edu.lk")];
+ char stringpool_str3976[sizeof("minami.kyoto.jp")];
+ char stringpool_str3977[sizeof("shichikashuku.miyagi.jp")];
+ char stringpool_str3978[sizeof("com.lk")];
+ char stringpool_str3979[sizeof("nishiawakura.okayama.jp")];
+ char stringpool_str3980[sizeof("on-the-web.tv")];
+ char stringpool_str3981[sizeof("bl.uk")];
+ char stringpool_str3982[sizeof("from-va.com")];
+ char stringpool_str3983[sizeof("gov.la")];
+ char stringpool_str3984[sizeof("edu.la")];
+ char stringpool_str3985[sizeof("com.la")];
+ char stringpool_str3986[sizeof("co.cl")];
+ char stringpool_str3987[sizeof("yoshikawa.saitama.jp")];
+ char stringpool_str3988[sizeof("bellevue.museum")];
+ char stringpool_str3989[sizeof("gov.lc")];
+ char stringpool_str3990[sizeof("edu.lc")];
+ char stringpool_str3991[sizeof("fujiyoshida.yamanashi.jp")];
+ char stringpool_str3992[sizeof("com.lc")];
+ char stringpool_str3993[sizeof("modum.no")];
+ char stringpool_str3994[sizeof("pila.pl")];
+ char stringpool_str3995[sizeof("poznan.pl")];
+ char stringpool_str3996[sizeof("gov.lb")];
+ char stringpool_str3997[sizeof("edu.lb")];
+ char stringpool_str3998[sizeof("oristano.it")];
+ char stringpool_str3999[sizeof("karikatur.museum")];
+ char stringpool_str4000[sizeof("com.lb")];
+ char stringpool_str4001[sizeof("amber.museum")];
+ char stringpool_str4002[sizeof("yugawa.fukushima.jp")];
+ char stringpool_str4003[sizeof("ppg.br")];
+ char stringpool_str4004[sizeof("yonezawa.yamagata.jp")];
+ char stringpool_str4005[sizeof("fukumitsu.toyama.jp")];
+ char stringpool_str4006[sizeof("minamiboso.chiba.jp")];
+ char stringpool_str4007[sizeof("gov.lt")];
+ char stringpool_str4008[sizeof("livorno.it")];
+ char stringpool_str4009[sizeof("forum.hu")];
+ char stringpool_str4010[sizeof("mil.kr")];
+ char stringpool_str4011[sizeof("kariya.aichi.jp")];
+ char stringpool_str4012[sizeof("gs.tm.no")];
+ char stringpool_str4013[sizeof("stord.no")];
+ char stringpool_str4014[sizeof("gov.lr")];
+ char stringpool_str4015[sizeof("edu.lr")];
+ char stringpool_str4016[sizeof("mulhouse.museum")];
+ char stringpool_str4017[sizeof("iwate.jp")];
+ char stringpool_str4018[sizeof("com.lr")];
+ char stringpool_str4019[sizeof("naamesjevuemie.no")];
+ char stringpool_str4020[sizeof("co.pl")];
+ char stringpool_str4021[sizeof("shimodate.ibaraki.jp")];
+ char stringpool_str4022[sizeof("takamori.nagano.jp")];
+ char stringpool_str4023[sizeof("doomdns.org")];
+ char stringpool_str4024[sizeof("ltd.lk")];
+ char stringpool_str4025[sizeof("omiya.saitama.jp")];
+ char stringpool_str4026[sizeof("architecture.museum")];
+ char stringpool_str4027[sizeof("k12.ks.us")];
+ char stringpool_str4028[sizeof("cci.fr")];
+ char stringpool_str4029[sizeof("ulsan.kr")];
+ char stringpool_str4030[sizeof("entomology.museum")];
+ char stringpool_str4031[sizeof("uhren.museum")];
+ char stringpool_str4032[sizeof("co.nl")];
+ char stringpool_str4033[sizeof("mihama.wakayama.jp")];
+ char stringpool_str4034[sizeof("nishi.fukuoka.jp")];
+ char stringpool_str4035[sizeof("hikimi.shimane.jp")];
+ char stringpool_str4036[sizeof("society.museum")];
+ char stringpool_str4037[sizeof("hamura.tokyo.jp")];
+ char stringpool_str4038[sizeof("hamamatsu.shizuoka.jp")];
+ char stringpool_str4039[sizeof("tokai.aichi.jp")];
+ char stringpool_str4040[sizeof("lib.ks.us")];
+ char stringpool_str4041[sizeof("aarborte.no")];
+ char stringpool_str4042[sizeof("oishida.yamagata.jp")];
+ char stringpool_str4043[sizeof("computerhistory.museum")];
+ char stringpool_str4044[sizeof("asker.no")];
+ char stringpool_str4045[sizeof("cc.mo.us")];
+ char stringpool_str4046[sizeof("tamamura.gunma.jp")];
+ char stringpool_str4047[sizeof("veterinaire.km")];
+ char stringpool_str4048[sizeof("getmyip.com")];
+ char stringpool_str4049[sizeof("yoshioka.gunma.jp")];
+ char stringpool_str4050[sizeof("uozu.toyama.jp")];
+ char stringpool_str4051[sizeof("gov.kp")];
+ char stringpool_str4052[sizeof("edu.kp")];
+ char stringpool_str4053[sizeof("haboro.hokkaido.jp")];
+ char stringpool_str4054[sizeof("cc.co.us")];
+ char stringpool_str4055[sizeof("com.kp")];
+ char stringpool_str4056[sizeof("oamishirasato.chiba.jp")];
+ char stringpool_str4057[sizeof("monzaebrianza.it")];
+ char stringpool_str4058[sizeof("tm.pl")];
+ char stringpool_str4059[sizeof("noshiro.akita.jp")];
+ char stringpool_str4060[sizeof("qc.com")];
+ char stringpool_str4061[sizeof("miyada.nagano.jp")];
+ char stringpool_str4062[sizeof("magadan.ru")];
+ char stringpool_str4063[sizeof("so.gov.pl")];
+ char stringpool_str4064[sizeof("sayama.saitama.jp")];
+ char stringpool_str4065[sizeof("izena.okinawa.jp")];
+ char stringpool_str4066[sizeof("abeno.osaka.jp")];
+ char stringpool_str4067[sizeof("sr.gov.pl")];
+ char stringpool_str4068[sizeof("viterbo.it")];
+ char stringpool_str4069[sizeof("chijiwa.nagasaki.jp")];
+ char stringpool_str4070[sizeof("doesntexist.org")];
+ char stringpool_str4071[sizeof("org.hk")];
+ char stringpool_str4072[sizeof("kommune.no")];
+ char stringpool_str4073[sizeof("oregontrail.museum")];
+ char stringpool_str4074[sizeof("kurume.fukuoka.jp")];
+ char stringpool_str4075[sizeof("community.museum")];
+ char stringpool_str4076[sizeof("mil.my")];
+ char stringpool_str4077[sizeof("monza-brianza.it")];
+ char stringpool_str4078[sizeof("kunimi.fukushima.jp")];
+ char stringpool_str4079[sizeof("grp.lk")];
+ char stringpool_str4080[sizeof("mihama.fukui.jp")];
+ char stringpool_str4081[sizeof("askim.no")];
+ char stringpool_str4082[sizeof("steam.museum")];
+ char stringpool_str4083[sizeof("salerno.it")];
+ char stringpool_str4084[sizeof("heroy.more-og-romsdal.no")];
+ char stringpool_str4085[sizeof("nishikawa.yamagata.jp")];
+ char stringpool_str4086[sizeof("ishinomaki.miyagi.jp")];
+ char stringpool_str4087[sizeof("starachowice.pl")];
+ char stringpool_str4088[sizeof("s3-us-gov-west-1.amazonaws.com")];
+ char stringpool_str4089[sizeof("labor.museum")];
+ char stringpool_str4090[sizeof("taito.tokyo.jp")];
+ char stringpool_str4091[sizeof("yoita.niigata.jp")];
+ char stringpool_str4092[sizeof("academy.museum")];
+ char stringpool_str4093[sizeof("philately.museum")];
+ char stringpool_str4094[sizeof("otago.museum")];
+ char stringpool_str4095[sizeof("chiropractic.museum")];
+ char stringpool_str4096[sizeof("is-very-bad.org")];
+ char stringpool_str4097[sizeof("ueda.nagano.jp")];
+ char stringpool_str4098[sizeof("org.hu")];
+ char stringpool_str4099[sizeof("ako.hyogo.jp")];
+ char stringpool_str4100[sizeof("bv.nl")];
+ char stringpool_str4101[sizeof("kitadaito.okinawa.jp")];
+ char stringpool_str4102[sizeof("salem.museum")];
+ char stringpool_str4103[sizeof("leasing.aero")];
+ char stringpool_str4104[sizeof("blogdns.org")];
+ char stringpool_str4105[sizeof("org.ht")];
+ char stringpool_str4106[sizeof("meguro.tokyo.jp")];
+ char stringpool_str4107[sizeof("sortland.no")];
+ char stringpool_str4108[sizeof("hashima.gifu.jp")];
+ char stringpool_str4109[sizeof("org.hn")];
+ char stringpool_str4110[sizeof("barum.no")];
+ char stringpool_str4111[sizeof("okinoshima.shimane.jp")];
+ char stringpool_str4112[sizeof("takahata.yamagata.jp")];
+ char stringpool_str4113[sizeof("ritto.shiga.jp")];
+ char stringpool_str4114[sizeof("shacknet.nu")];
+ char stringpool_str4115[sizeof("powiat.pl")];
+ char stringpool_str4116[sizeof("settlers.museum")];
+ char stringpool_str4117[sizeof("tambov.ru")];
+ char stringpool_str4118[sizeof("or.id")];
+ char stringpool_str4119[sizeof("communication.museum")];
+ char stringpool_str4120[sizeof("if.ua")];
+ char stringpool_str4121[sizeof("daito.osaka.jp")];
+ char stringpool_str4122[sizeof("mordovia.ru")];
+ char stringpool_str4123[sizeof("tohnosho.chiba.jp")];
+ char stringpool_str4124[sizeof("herad.no")];
+ char stringpool_str4125[sizeof("kashiwazaki.niigata.jp")];
+ char stringpool_str4126[sizeof("sayama.osaka.jp")];
+ char stringpool_str4127[sizeof("togura.nagano.jp")];
+ char stringpool_str4128[sizeof("grandrapids.museum")];
+ char stringpool_str4129[sizeof("jinsekikogen.hiroshima.jp")];
+ char stringpool_str4130[sizeof("ikeda.hokkaido.jp")];
+ char stringpool_str4131[sizeof("higashi.fukuoka.jp")];
+ char stringpool_str4132[sizeof("communications.museum")];
+ char stringpool_str4133[sizeof("okinawa.okinawa.jp")];
+ char stringpool_str4134[sizeof("yokaichiba.chiba.jp")];
+ char stringpool_str4135[sizeof("discovery.museum")];
+ char stringpool_str4136[sizeof("echizen.fukui.jp")];
+ char stringpool_str4137[sizeof("udine.it")];
+ char stringpool_str4138[sizeof("aya.miyazaki.jp")];
+ char stringpool_str4139[sizeof("philadelphia.museum")];
+ char stringpool_str4140[sizeof("toyama.toyama.jp")];
+ char stringpool_str4141[sizeof("ambulance.museum")];
+ char stringpool_str4142[sizeof("tateyama.chiba.jp")];
+ char stringpool_str4143[sizeof("topology.museum")];
+ char stringpool_str4144[sizeof("historisches.museum")];
+ char stringpool_str4145[sizeof("is-a-student.com")];
+ char stringpool_str4146[sizeof("kameyama.mie.jp")];
+ char stringpool_str4147[sizeof("congresodelalengua3.ar")];
+ char stringpool_str4148[sizeof("info")];
+ char stringpool_str4149[sizeof("simple-url.com")];
+ char stringpool_str4150[sizeof("gs.hm.no")];
+ char stringpool_str4151[sizeof("uryu.hokkaido.jp")];
+ char stringpool_str4152[sizeof("tra.kp")];
+ char stringpool_str4153[sizeof("ranzan.saitama.jp")];
+ char stringpool_str4154[sizeof("historisch.museum")];
+ char stringpool_str4155[sizeof("rep.kp")];
+ char stringpool_str4156[sizeof("k12.la.us")];
+ char stringpool_str4157[sizeof("hanamaki.iwate.jp")];
+ char stringpool_str4158[sizeof("info.mv")];
+ char stringpool_str4159[sizeof("ac.id")];
+ char stringpool_str4160[sizeof("shimofusa.chiba.jp")];
+ char stringpool_str4161[sizeof("edu.pe")];
+ char stringpool_str4162[sizeof("gov.pk")];
+ char stringpool_str4163[sizeof("lib.la.us")];
+ char stringpool_str4164[sizeof("edu.pk")];
+ char stringpool_str4165[sizeof("com.pe")];
+ char stringpool_str4166[sizeof("info.at")];
+ char stringpool_str4167[sizeof("com.pk")];
+ char stringpool_str4168[sizeof("gov.ge")];
+ char stringpool_str4169[sizeof("edu.ge")];
+ char stringpool_str4170[sizeof("bytom.pl")];
+ char stringpool_str4171[sizeof("inabe.mie.jp")];
+ char stringpool_str4172[sizeof("gov.gi")];
+ char stringpool_str4173[sizeof("ojiya.niigata.jp")];
+ char stringpool_str4174[sizeof("com.ge")];
+ char stringpool_str4175[sizeof("edu.gi")];
+ char stringpool_str4176[sizeof("co.gg")];
+ char stringpool_str4177[sizeof("gov.gg")];
+ char stringpool_str4178[sizeof("hurum.no")];
+ char stringpool_str4179[sizeof("com.gi")];
+ char stringpool_str4180[sizeof("nakhodka.ru")];
+ char stringpool_str4181[sizeof("edu.pa")];
+ char stringpool_str4182[sizeof("xxx")];
+ char stringpool_str4183[sizeof("cechire.com")];
+ char stringpool_str4184[sizeof("com.pa")];
+ char stringpool_str4185[sizeof("gov.ps")];
+ char stringpool_str4186[sizeof("edu.ps")];
+ char stringpool_str4187[sizeof("com.ps")];
+ char stringpool_str4188[sizeof("minamiizu.shizuoka.jp")];
+ char stringpool_str4189[sizeof("ibaraki.osaka.jp")];
+ char stringpool_str4190[sizeof("go.ug")];
+ char stringpool_str4191[sizeof("suzuka.mie.jp")];
+ char stringpool_str4192[sizeof("saigawa.fukuoka.jp")];
+ char stringpool_str4193[sizeof("co.ug")];
+ char stringpool_str4194[sizeof("nakatombetsu.hokkaido.jp")];
+ char stringpool_str4195[sizeof("chiyoda.tokyo.jp")];
+ char stringpool_str4196[sizeof("newhampshire.museum")];
+ char stringpool_str4197[sizeof("moseushi.hokkaido.jp")];
+ char stringpool_str4198[sizeof("gos.pk")];
+ char stringpool_str4199[sizeof("honjo.akita.jp")];
+ char stringpool_str4200[sizeof("homedns.org")];
+ char stringpool_str4201[sizeof("tarama.okinawa.jp")];
+ char stringpool_str4202[sizeof("is-a-caterer.com")];
+ char stringpool_str4203[sizeof("ostroda.pl")];
+ char stringpool_str4204[sizeof("mragowo.pl")];
+ char stringpool_str4205[sizeof("inf.br")];
+ char stringpool_str4206[sizeof("higashi.fukushima.jp")];
+ char stringpool_str4207[sizeof("raholt.no")];
+ char stringpool_str4208[sizeof("aso.kumamoto.jp")];
+ char stringpool_str4209[sizeof("googlecode.com")];
+ char stringpool_str4210[sizeof("info.tn")];
+ char stringpool_str4211[sizeof("edu.uy")];
+ char stringpool_str4212[sizeof("info.tt")];
+ char stringpool_str4213[sizeof("com.uy")];
+ char stringpool_str4214[sizeof("dnsdojo.com")];
+ char stringpool_str4215[sizeof("muroran.hokkaido.jp")];
+ char stringpool_str4216[sizeof("hachirogata.akita.jp")];
+ char stringpool_str4217[sizeof("mod.gi")];
+ char stringpool_str4218[sizeof("gov.pt")];
+ char stringpool_str4219[sizeof("wf")];
+ char stringpool_str4220[sizeof("edu.pt")];
+ char stringpool_str4221[sizeof("com.pt")];
+ char stringpool_str4222[sizeof("x.se")];
+ char stringpool_str4223[sizeof("is-a-cpa.com")];
+ char stringpool_str4224[sizeof("imabari.ehime.jp")];
+ char stringpool_str4225[sizeof("edu.gt")];
+ char stringpool_str4226[sizeof("is-a-knight.org")];
+ char stringpool_str4227[sizeof("info.nr")];
+ char stringpool_str4228[sizeof("com.gt")];
+ char stringpool_str4229[sizeof("tm.mg")];
+ char stringpool_str4230[sizeof("info.bb")];
+ char stringpool_str4231[sizeof("gov.pr")];
+ char stringpool_str4232[sizeof("gov.pn")];
+ char stringpool_str4233[sizeof("gov.rw")];
+ char stringpool_str4234[sizeof("edu.pr")];
+ char stringpool_str4235[sizeof("inf.cu")];
+ char stringpool_str4236[sizeof("edu.pn")];
+ char stringpool_str4237[sizeof("edu.rw")];
+ char stringpool_str4238[sizeof("castle.museum")];
+ char stringpool_str4239[sizeof("com.pr")];
+ char stringpool_str4240[sizeof("shirosato.ibaraki.jp")];
+ char stringpool_str4241[sizeof("com.rw")];
+ char stringpool_str4242[sizeof("gov.gr")];
+ char stringpool_str4243[sizeof("gov.gn")];
+ char stringpool_str4244[sizeof("edu.gr")];
+ char stringpool_str4245[sizeof("edu.gn")];
+ char stringpool_str4246[sizeof("abu.yamaguchi.jp")];
+ char stringpool_str4247[sizeof("com.gr")];
+ char stringpool_str4248[sizeof("com.gn")];
+ char stringpool_str4249[sizeof("dyndns-pics.com")];
+ char stringpool_str4250[sizeof("overhalla.no")];
+ char stringpool_str4251[sizeof("gon.pk")];
+ char stringpool_str4252[sizeof("cyber.museum")];
+ char stringpool_str4253[sizeof("iwata.shizuoka.jp")];
+ char stringpool_str4254[sizeof("ltd.gi")];
+ char stringpool_str4255[sizeof("or.kr")];
+ char stringpool_str4256[sizeof("avellino.it")];
+ char stringpool_str4257[sizeof("gov.pl")];
+ char stringpool_str4258[sizeof("net.hk")];
+ char stringpool_str4259[sizeof("asakawa.fukushima.jp")];
+ char stringpool_str4260[sizeof("edu.pl")];
+ char stringpool_str4261[sizeof("aizumisato.fukushima.jp")];
+ char stringpool_str4262[sizeof("info.au")];
+ char stringpool_str4263[sizeof("monza-e-della-brianza.it")];
+ char stringpool_str4264[sizeof("com.pl")];
+ char stringpool_str4265[sizeof("pescara.it")];
+ char stringpool_str4266[sizeof("med.pa")];
+ char stringpool_str4267[sizeof("sakuragawa.ibaraki.jp")];
+ char stringpool_str4268[sizeof("or.cr")];
+ char stringpool_str4269[sizeof("chikusei.ibaraki.jp")];
+ char stringpool_str4270[sizeof("is-an-actress.com")];
+ char stringpool_str4271[sizeof("cc.ky.us")];
+ char stringpool_str4272[sizeof("nakaniikawa.toyama.jp")];
+ char stringpool_str4273[sizeof("is-a-guru.com")];
+ char stringpool_str4274[sizeof("lomza.pl")];
+ char stringpool_str4275[sizeof("cieszyn.pl")];
+ char stringpool_str4276[sizeof("rovigo.it")];
+ char stringpool_str4277[sizeof("info.sd")];
+ char stringpool_str4278[sizeof("horology.museum")];
+ char stringpool_str4279[sizeof("hirado.nagasaki.jp")];
+ char stringpool_str4280[sizeof("atm.pl")];
+ char stringpool_str4281[sizeof("biella.it")];
+ char stringpool_str4282[sizeof("sld.pa")];
+ char stringpool_str4283[sizeof("ikeda.nagano.jp")];
+ char stringpool_str4284[sizeof("gsm.pl")];
+ char stringpool_str4285[sizeof("sos.pl")];
+ char stringpool_str4286[sizeof("est.pr")];
+ char stringpool_str4287[sizeof("miasa.nagano.jp")];
+ char stringpool_str4288[sizeof("art.pl")];
+ char stringpool_str4289[sizeof("gob.pe")];
+ char stringpool_str4290[sizeof("sex.pl")];
+ char stringpool_str4291[sizeof("gob.pk")];
+ char stringpool_str4292[sizeof("is-an-artist.com")];
+ char stringpool_str4293[sizeof("miharu.fukushima.jp")];
+ char stringpool_str4294[sizeof("info.na")];
+ char stringpool_str4295[sizeof("x.bg")];
+ char stringpool_str4296[sizeof("microlight.aero")];
+ char stringpool_str4297[sizeof("aisho.shiga.jp")];
+ char stringpool_str4298[sizeof("paroch.k12.ma.us")];
+ char stringpool_str4299[sizeof("info.nf")];
+ char stringpool_str4300[sizeof("s3-website-eu-west-1.amazonaws.com")];
+ char stringpool_str4301[sizeof("gob.pa")];
+ char stringpool_str4302[sizeof("net.ht")];
+ char stringpool_str4303[sizeof("minami.fukuoka.jp")];
+ char stringpool_str4304[sizeof("cc.wy.us")];
+ char stringpool_str4305[sizeof("abo.pa")];
+ char stringpool_str4306[sizeof("oguni.yamagata.jp")];
+ char stringpool_str4307[sizeof("minamifurano.hokkaido.jp")];
+ char stringpool_str4308[sizeof("gov.mw")];
+ char stringpool_str4309[sizeof("pittsburgh.museum")];
+ char stringpool_str4310[sizeof("info.la")];
+ char stringpool_str4311[sizeof("edu.mw")];
+ char stringpool_str4312[sizeof("gop.pk")];
+ char stringpool_str4313[sizeof("iide.yamagata.jp")];
+ char stringpool_str4314[sizeof("com.mw")];
+ char stringpool_str4315[sizeof("net.hn")];
+ char stringpool_str4316[sizeof("info.ec")];
+ char stringpool_str4317[sizeof("co.ag")];
+ char stringpool_str4318[sizeof("dyndns-blog.com")];
+ char stringpool_str4319[sizeof("aid.pl")];
+ char stringpool_str4320[sizeof("yuza.yamagata.jp")];
+ char stringpool_str4321[sizeof("med.pl")];
+ char stringpool_str4322[sizeof("gok.pk")];
+ char stringpool_str4323[sizeof("hamar.no")];
+ char stringpool_str4324[sizeof("sc.kr")];
+ char stringpool_str4325[sizeof("ac.kr")];
+ char stringpool_str4326[sizeof("gub.uy")];
+ char stringpool_str4327[sizeof("ac.ir")];
+ char stringpool_str4328[sizeof("kotoura.tottori.jp")];
+ char stringpool_str4329[sizeof("ina.ibaraki.jp")];
+ char stringpool_str4330[sizeof("ac.cr")];
+ char stringpool_str4331[sizeof("iwamizawa.hokkaido.jp")];
+ char stringpool_str4332[sizeof("seaport.museum")];
+ char stringpool_str4333[sizeof("hamaroy.no")];
+ char stringpool_str4334[sizeof("notaires.fr")];
+ char stringpool_str4335[sizeof("cc.ny.us")];
+ char stringpool_str4336[sizeof("skanland.no")];
+ char stringpool_str4337[sizeof("freemasonry.museum")];
+ char stringpool_str4338[sizeof("potenza.it")];
+ char stringpool_str4339[sizeof("civilisation.museum")];
+ char stringpool_str4340[sizeof("koryo.nara.jp")];
+ char stringpool_str4341[sizeof("fam.pk")];
+ char stringpool_str4342[sizeof("gob.gt")];
+ char stringpool_str4343[sizeof("shirako.chiba.jp")];
+ char stringpool_str4344[sizeof("biz.pk")];
+ char stringpool_str4345[sizeof("embroidery.museum")];
+ char stringpool_str4346[sizeof("minamiaiki.nagano.jp")];
+ char stringpool_str4347[sizeof("bashkiria.ru")];
+ char stringpool_str4348[sizeof("lecco.it")];
+ char stringpool_str4349[sizeof("lincoln.museum")];
+ char stringpool_str4350[sizeof("onagawa.miyagi.jp")];
+ char stringpool_str4351[sizeof("amusement.aero")];
+ char stringpool_str4352[sizeof("hirara.okinawa.jp")];
+ char stringpool_str4353[sizeof("rec.ro")];
+ char stringpool_str4354[sizeof("gda.pl")];
+ char stringpool_str4355[sizeof("minamimaki.nagano.jp")];
+ char stringpool_str4356[sizeof("chiryu.aichi.jp")];
+ char stringpool_str4357[sizeof("yugawara.kanagawa.jp")];
+ char stringpool_str4358[sizeof("shioya.tochigi.jp")];
+ char stringpool_str4359[sizeof("merseine.nu")];
+ char stringpool_str4360[sizeof("ikata.ehime.jp")];
+ char stringpool_str4361[sizeof("vyatka.ru")];
+ char stringpool_str4362[sizeof("aquila.it")];
+ char stringpool_str4363[sizeof("xz.cn")];
+ char stringpool_str4364[sizeof("thruhere.net")];
+ char stringpool_str4365[sizeof("daegu.kr")];
+ char stringpool_str4366[sizeof("mil.pe")];
+ char stringpool_str4367[sizeof("mil.ge")];
+ char stringpool_str4368[sizeof("rel.pl")];
+ char stringpool_str4369[sizeof("ikeda.fukui.jp")];
+ char stringpool_str4370[sizeof("agrar.hu")];
+ char stringpool_str4371[sizeof("tokyo.jp")];
+ char stringpool_str4372[sizeof("ono.fukui.jp")];
+ char stringpool_str4373[sizeof("homeunix.net")];
+ char stringpool_str4374[sizeof("philadelphiaarea.museum")];
+ char stringpool_str4375[sizeof("ac.pr")];
+ char stringpool_str4376[sizeof("uruma.okinawa.jp")];
+ char stringpool_str4377[sizeof("geisei.kochi.jp")];
+ char stringpool_str4378[sizeof("nagato.yamaguchi.jp")];
+ char stringpool_str4379[sizeof("oyama.tochigi.jp")];
+ char stringpool_str4380[sizeof("stockholm.museum")];
+ char stringpool_str4381[sizeof("cherkasy.ua")];
+ char stringpool_str4382[sizeof("dnsalias.net")];
+ char stringpool_str4383[sizeof("name.az")];
+ char stringpool_str4384[sizeof("gotemba.shizuoka.jp")];
+ char stringpool_str4385[sizeof("niimi.okayama.jp")];
+ char stringpool_str4386[sizeof("frankfurt.museum")];
+ char stringpool_str4387[sizeof("nishiokoppe.hokkaido.jp")];
+ char stringpool_str4388[sizeof("ac.im")];
+ char stringpool_str4389[sizeof("xj.cn")];
+ char stringpool_str4390[sizeof("biz.pr")];
+ char stringpool_str4391[sizeof("vevelstad.no")];
+ char stringpool_str4392[sizeof("stange.no")];
+ char stringpool_str4393[sizeof("urawa.saitama.jp")];
+ char stringpool_str4394[sizeof("koriyama.fukushima.jp")];
+ char stringpool_str4395[sizeof("haram.no")];
+ char stringpool_str4396[sizeof("ofunato.iwate.jp")];
+ char stringpool_str4397[sizeof("mazury.pl")];
+ char stringpool_str4398[sizeof("mil.uy")];
+ char stringpool_str4399[sizeof("harima.hyogo.jp")];
+ char stringpool_str4400[sizeof("shimotsuke.tochigi.jp")];
+ char stringpool_str4401[sizeof("choyo.kumamoto.jp")];
+ char stringpool_str4402[sizeof("setouchi.okayama.jp")];
+ char stringpool_str4403[sizeof("biz.pl")];
+ char stringpool_str4404[sizeof("mil.gt")];
+ char stringpool_str4405[sizeof("it.ao")];
+ char stringpool_str4406[sizeof("soo.kagoshima.jp")];
+ char stringpool_str4407[sizeof("for-our.info")];
+ char stringpool_str4408[sizeof("narashino.chiba.jp")];
+ char stringpool_str4409[sizeof("mil.rw")];
+ char stringpool_str4410[sizeof("is-an-entertainer.com")];
+ char stringpool_str4411[sizeof("googleapis.com")];
+ char stringpool_str4412[sizeof("public.museum")];
+ char stringpool_str4413[sizeof("homeunix.org")];
+ char stringpool_str4414[sizeof("edu.pf")];
+ char stringpool_str4415[sizeof("okaya.nagano.jp")];
+ char stringpool_str4416[sizeof("kawara.fukuoka.jp")];
+ char stringpool_str4417[sizeof("com.pf")];
+ char stringpool_str4418[sizeof("mamurogawa.yamagata.jp")];
+ char stringpool_str4419[sizeof("k12.pa.us")];
+ char stringpool_str4420[sizeof("tsukui.kanagawa.jp")];
+ char stringpool_str4421[sizeof("mil.pl")];
+ char stringpool_str4422[sizeof("info.co")];
+ char stringpool_str4423[sizeof("dnsalias.org")];
+ char stringpool_str4424[sizeof("k12.ga.us")];
+ char stringpool_str4425[sizeof("name.qa")];
+ char stringpool_str4426[sizeof("yoshida.saitama.jp")];
+ char stringpool_str4427[sizeof("nichinan.tottori.jp")];
+ char stringpool_str4428[sizeof("swinoujscie.pl")];
+ char stringpool_str4429[sizeof("tamaki.mie.jp")];
+ char stringpool_str4430[sizeof("nom.fr")];
+ char stringpool_str4431[sizeof("tokamachi.niigata.jp")];
+ char stringpool_str4432[sizeof("kaluga.ru")];
+ char stringpool_str4433[sizeof("lib.pa.us")];
+ char stringpool_str4434[sizeof("cargo.aero")];
+ char stringpool_str4435[sizeof("lib.ga.us")];
+ char stringpool_str4436[sizeof("ohira.tochigi.jp")];
+ char stringpool_str4437[sizeof("sykkylven.no")];
+ char stringpool_str4438[sizeof("paris.museum")];
+ char stringpool_str4439[sizeof("belau.pw")];
+ char stringpool_str4440[sizeof("info.pr")];
+ char stringpool_str4441[sizeof("omasvuotna.no")];
+ char stringpool_str4442[sizeof("biz.mw")];
+ char stringpool_str4443[sizeof("bjerkreim.no")];
+ char stringpool_str4444[sizeof("bardu.no")];
+ char stringpool_str4445[sizeof("k12.gu.us")];
+ char stringpool_str4446[sizeof("unnan.shimane.jp")];
+ char stringpool_str4447[sizeof("edu.gp")];
+ char stringpool_str4448[sizeof("kozaki.chiba.jp")];
+ char stringpool_str4449[sizeof("artsandcrafts.museum")];
+ char stringpool_str4450[sizeof("com.gp")];
+ char stringpool_str4451[sizeof("ono.fukushima.jp")];
+ char stringpool_str4452[sizeof("info.ro")];
+ char stringpool_str4453[sizeof("info.pl")];
+ char stringpool_str4454[sizeof("elk.pl")];
+ char stringpool_str4455[sizeof("naustdal.no")];
+ char stringpool_str4456[sizeof("bamble.no")];
+ char stringpool_str4457[sizeof("nesset.no")];
+ char stringpool_str4458[sizeof("birthplace.museum")];
+ char stringpool_str4459[sizeof("k12.pr.us")];
+ char stringpool_str4460[sizeof("ibaraki.ibaraki.jp")];
+ char stringpool_str4461[sizeof("minamiyamashiro.kyoto.jp")];
+ char stringpool_str4462[sizeof("info.pk")];
+ char stringpool_str4463[sizeof("lib.gu.us")];
+ char stringpool_str4464[sizeof("info.ht")];
+ char stringpool_str4465[sizeof("asakuchi.okayama.jp")];
+ char stringpool_str4466[sizeof("omotego.fukushima.jp")];
+ char stringpool_str4467[sizeof("gov.ph")];
+ char stringpool_str4468[sizeof("minamitane.kagoshima.jp")];
+ char stringpool_str4469[sizeof("edu.ph")];
+ char stringpool_str4470[sizeof("go.jp")];
+ char stringpool_str4471[sizeof("hanamigawa.chiba.jp")];
+ char stringpool_str4472[sizeof("ed.jp")];
+ char stringpool_str4473[sizeof("com.ph")];
+ char stringpool_str4474[sizeof("co.jp")];
+ char stringpool_str4475[sizeof("gov.gh")];
+ char stringpool_str4476[sizeof("edu.gh")];
+ char stringpool_str4477[sizeof("ad.jp")];
+ char stringpool_str4478[sizeof("kobierzyce.pl")];
+ char stringpool_str4479[sizeof("annaka.gunma.jp")];
+ char stringpool_str4480[sizeof("detroit.museum")];
+ char stringpool_str4481[sizeof("lib.pr.us")];
+ char stringpool_str4482[sizeof("com.gh")];
+ char stringpool_str4483[sizeof("kamikoani.akita.jp")];
+ char stringpool_str4484[sizeof("telekommunikation.museum")];
+ char stringpool_str4485[sizeof("dontexist.org")];
+ char stringpool_str4486[sizeof("yorii.saitama.jp")];
+ char stringpool_str4487[sizeof("geology.museum")];
+ char stringpool_str4488[sizeof("gr.jp")];
+ char stringpool_str4489[sizeof("ohi.fukui.jp")];
+ char stringpool_str4490[sizeof("higashishirakawa.gifu.jp")];
+ char stringpool_str4491[sizeof("is-a-green.com")];
+ char stringpool_str4492[sizeof("ikawa.akita.jp")];
+ char stringpool_str4493[sizeof("embaixada.st")];
+ char stringpool_str4494[sizeof("org.do")];
+ char stringpool_str4495[sizeof("org.dm")];
+ char stringpool_str4496[sizeof("amakusa.kumamoto.jp")];
+ char stringpool_str4497[sizeof("design.museum")];
+ char stringpool_str4498[sizeof("org.dz")];
+ char stringpool_str4499[sizeof("kosaka.akita.jp")];
+ char stringpool_str4500[sizeof("kembuchi.hokkaido.jp")];
+ char stringpool_str4501[sizeof("ichiba.tokushima.jp")];
+ char stringpool_str4502[sizeof("is-uberleet.com")];
+ char stringpool_str4503[sizeof("szkola.pl")];
+ char stringpool_str4504[sizeof("omachi.nagano.jp")];
+ char stringpool_str4505[sizeof("info.ki")];
+ char stringpool_str4506[sizeof("stuff-4-sale.us")];
+ char stringpool_str4507[sizeof("podhale.pl")];
+ char stringpool_str4508[sizeof("grozny.ru")];
+ char stringpool_str4509[sizeof("sakurai.nara.jp")];
+ char stringpool_str4510[sizeof("snillfjord.no")];
+ char stringpool_str4511[sizeof("lahppi.no")];
+ char stringpool_str4512[sizeof("tagami.niigata.jp")];
+ char stringpool_str4513[sizeof("design.aero")];
+ char stringpool_str4514[sizeof("civilaviation.aero")];
+ char stringpool_str4515[sizeof("is-found.org")];
+ char stringpool_str4516[sizeof("ukiha.fukuoka.jp")];
+ char stringpool_str4517[sizeof("hasami.nagasaki.jp")];
+ char stringpool_str4518[sizeof("endofinternet.net")];
+ char stringpool_str4519[sizeof("jur.pro")];
+ char stringpool_str4520[sizeof("miyama.fukuoka.jp")];
+ char stringpool_str4521[sizeof("med.pro")];
+ char stringpool_str4522[sizeof("zgorzelec.pl")];
+ char stringpool_str4523[sizeof("itayanagi.aomori.jp")];
+ char stringpool_str4524[sizeof("loyalist.museum")];
+ char stringpool_str4525[sizeof("info.hu")];
+ char stringpool_str4526[sizeof("kawaminami.miyazaki.jp")];
+ char stringpool_str4527[sizeof("bolzano.it")];
+ char stringpool_str4528[sizeof("minamiuonuma.niigata.jp")];
+ char stringpool_str4529[sizeof("is-a-painter.com")];
+ char stringpool_str4530[sizeof("kaneyama.fukushima.jp")];
+ char stringpool_str4531[sizeof("is-into-anime.com")];
+ char stringpool_str4532[sizeof("xn--j1amh")];
+ char stringpool_str4533[sizeof("kamikawa.saitama.jp")];
+ char stringpool_str4534[sizeof("xn--wgbl6a")];
+ char stringpool_str4535[sizeof("minamiminowa.nagano.jp")];
+ char stringpool_str4536[sizeof("hasama.oita.jp")];
+ char stringpool_str4537[sizeof("okagaki.fukuoka.jp")];
+ char stringpool_str4538[sizeof("fussa.tokyo.jp")];
+ char stringpool_str4539[sizeof("tsurugashima.saitama.jp")];
+ char stringpool_str4540[sizeof("kartuzy.pl")];
+ char stringpool_str4541[sizeof("yoshino.nara.jp")];
+ char stringpool_str4542[sizeof("nissedal.no")];
+ char stringpool_str4543[sizeof("belluno.it")];
+ char stringpool_str4544[sizeof("mazowsze.pl")];
+ char stringpool_str4545[sizeof("aki.kochi.jp")];
+ char stringpool_str4546[sizeof("ena.gifu.jp")];
+ char stringpool_str4547[sizeof("nishio.aichi.jp")];
+ char stringpool_str4548[sizeof("xn--nnx388a")];
+ char stringpool_str4549[sizeof("xn--asky-ira.no")];
+ char stringpool_str4550[sizeof("kagamino.okayama.jp")];
+ char stringpool_str4551[sizeof("tsuchiura.ibaraki.jp")];
+ char stringpool_str4552[sizeof("newjersey.museum")];
+ char stringpool_str4553[sizeof("mantova.it")];
+ char stringpool_str4554[sizeof("heguri.nara.jp")];
+ char stringpool_str4555[sizeof("takamatsu.kagawa.jp")];
+ char stringpool_str4556[sizeof("spjelkavik.no")];
+ char stringpool_str4557[sizeof("aknoluokta.no")];
+ char stringpool_str4558[sizeof("fujikawa.shizuoka.jp")];
+ char stringpool_str4559[sizeof("yoshida.shizuoka.jp")];
+ char stringpool_str4560[sizeof("xn--skjk-soa.no")];
+ char stringpool_str4561[sizeof("nakatane.kagoshima.jp")];
+ char stringpool_str4562[sizeof("gs.bu.no")];
+ char stringpool_str4563[sizeof("missoula.museum")];
+ char stringpool_str4564[sizeof("planetarium.museum")];
+ char stringpool_str4565[sizeof("is-a-socialist.com")];
+ char stringpool_str4566[sizeof("id.ly")];
+ char stringpool_str4567[sizeof("kitakami.iwate.jp")];
+ char stringpool_str4568[sizeof("nagoya.jp")];
+ char stringpool_str4569[sizeof("shinonsen.hyogo.jp")];
+ char stringpool_str4570[sizeof("minoh.osaka.jp")];
+ char stringpool_str4571[sizeof("karumai.iwate.jp")];
+ char stringpool_str4572[sizeof("is-leet.com")];
+ char stringpool_str4573[sizeof("pskov.ru")];
+ char stringpool_str4574[sizeof("is-a-rockstar.com")];
+ char stringpool_str4575[sizeof("off.ai")];
+ char stringpool_str4576[sizeof("meraker.no")];
+ char stringpool_str4577[sizeof("cc.oh.us")];
+ char stringpool_str4578[sizeof("andria-barletta-trani.it")];
+ char stringpool_str4579[sizeof("oyodo.nara.jp")];
+ char stringpool_str4580[sizeof("kasumigaura.ibaraki.jp")];
+ char stringpool_str4581[sizeof("nationalheritage.museum")];
+ char stringpool_str4582[sizeof("xn--trgstad-r1a.no")];
+ char stringpool_str4583[sizeof("misaki.osaka.jp")];
+ char stringpool_str4584[sizeof("iwade.wakayama.jp")];
+ char stringpool_str4585[sizeof("pavia.it")];
+ char stringpool_str4586[sizeof("chikushino.fukuoka.jp")];
+ char stringpool_str4587[sizeof("org.ro")];
+ char stringpool_str4588[sizeof("monza.it")];
+ char stringpool_str4589[sizeof("dynalias.net")];
+ char stringpool_str4590[sizeof("turek.pl")];
+ char stringpool_str4591[sizeof("suzaka.nagano.jp")];
+ char stringpool_str4592[sizeof("parma.it")];
+ char stringpool_str4593[sizeof("macerata.it")];
+ char stringpool_str4594[sizeof("mil.ph")];
+ char stringpool_str4595[sizeof("padua.it")];
+ char stringpool_str4596[sizeof("or.mu")];
+ char stringpool_str4597[sizeof("mil.gh")];
+ char stringpool_str4598[sizeof("tsukumi.oita.jp")];
+ char stringpool_str4599[sizeof("homelinux.org")];
+ char stringpool_str4600[sizeof("org.rs")];
+ char stringpool_str4601[sizeof("kafjord.no")];
+ char stringpool_str4602[sizeof("xn--brum-voa.no")];
+ char stringpool_str4603[sizeof("bar.pro")];
+ char stringpool_str4604[sizeof("chiyoda.gunma.jp")];
+ char stringpool_str4605[sizeof("agrigento.it")];
+ char stringpool_str4606[sizeof("gov.ky")];
+ char stringpool_str4607[sizeof("tachiarai.fukuoka.jp")];
+ char stringpool_str4608[sizeof("edu.ky")];
+ char stringpool_str4609[sizeof("bedzin.pl")];
+ char stringpool_str4610[sizeof("com.ky")];
+ char stringpool_str4611[sizeof("kitakata.miyazaki.jp")];
+ char stringpool_str4612[sizeof("xn--snes-poa.no")];
+ char stringpool_str4613[sizeof("ikeda.osaka.jp")];
+ char stringpool_str4614[sizeof("historicalsociety.museum")];
+ char stringpool_str4615[sizeof("xn--andy-ira.no")];
+ char stringpool_str4616[sizeof("owariasahi.aichi.jp")];
+ char stringpool_str4617[sizeof("cpa.pro")];
+ char stringpool_str4618[sizeof("org.ru")];
+ char stringpool_str4619[sizeof("selbu.no")];
+ char stringpool_str4620[sizeof("xn--slat-5na.no")];
+ char stringpool_str4621[sizeof("ogawa.ibaraki.jp")];
+ char stringpool_str4622[sizeof("kamikawa.hokkaido.jp")];
+ char stringpool_str4623[sizeof("kawakami.nara.jp")];
+ char stringpool_str4624[sizeof("oceanographic.museum")];
+ char stringpool_str4625[sizeof("okawa.kochi.jp")];
+ char stringpool_str4626[sizeof("cc.nh.us")];
+ char stringpool_str4627[sizeof("kunohe.iwate.jp")];
+ char stringpool_str4628[sizeof("cherkassy.ua")];
+ char stringpool_str4629[sizeof("dynalias.org")];
+ char stringpool_str4630[sizeof("odesa.ua")];
+ char stringpool_str4631[sizeof("sande.vestfold.no")];
+ char stringpool_str4632[sizeof("shibukawa.gunma.jp")];
+ char stringpool_str4633[sizeof("cc.sc.us")];
+ char stringpool_str4634[sizeof("yoshimi.saitama.jp")];
+ char stringpool_str4635[sizeof("net.do")];
+ char stringpool_str4636[sizeof("net.dm")];
+ char stringpool_str4637[sizeof("chungbuk.kr")];
+ char stringpool_str4638[sizeof("decorativearts.museum")];
+ char stringpool_str4639[sizeof("chungnam.kr")];
+ char stringpool_str4640[sizeof("net.dz")];
+ char stringpool_str4641[sizeof("org.mo")];
+ char stringpool_str4642[sizeof("org.me")];
+ char stringpool_str4643[sizeof("org.mk")];
+ char stringpool_str4644[sizeof("isehara.kanagawa.jp")];
+ char stringpool_str4645[sizeof("minamisanriku.miyagi.jp")];
+ char stringpool_str4646[sizeof("org.mg")];
+ char stringpool_str4647[sizeof("afjord.no")];
+ char stringpool_str4648[sizeof("xn--snase-nra.no")];
+ char stringpool_str4649[sizeof("mitaka.tokyo.jp")];
+ char stringpool_str4650[sizeof("yura.wakayama.jp")];
+ char stringpool_str4651[sizeof("kahoku.yamagata.jp")];
+ char stringpool_str4652[sizeof("org.ma")];
+ char stringpool_str4653[sizeof("ac.mu")];
+ char stringpool_str4654[sizeof("us.com")];
+ char stringpool_str4655[sizeof("ibara.okayama.jp")];
+ char stringpool_str4656[sizeof("xn--bievt-0qa.no")];
+ char stringpool_str4657[sizeof("xn--stjrdal-s1a.no")];
+ char stringpool_str4658[sizeof("fukuroi.shizuoka.jp")];
+ char stringpool_str4659[sizeof("minakami.gunma.jp")];
+ char stringpool_str4660[sizeof("sanok.pl")];
+ char stringpool_str4661[sizeof("naklo.pl")];
+ char stringpool_str4662[sizeof("onojo.fukuoka.jp")];
+ char stringpool_str4663[sizeof("xn--bdddj-mrabd.no")];
+ char stringpool_str4664[sizeof("ujitawara.kyoto.jp")];
+ char stringpool_str4665[sizeof("xn--snsa-roa.no")];
+ char stringpool_str4666[sizeof("xn--smla-hra.no")];
+ char stringpool_str4667[sizeof("misugi.mie.jp")];
+ char stringpool_str4668[sizeof("tychy.pl")];
+ char stringpool_str4669[sizeof("karelia.ru")];
+ char stringpool_str4670[sizeof("org.mu")];
+ char stringpool_str4671[sizeof("toyohashi.aichi.jp")];
+ char stringpool_str4672[sizeof("sanfrancisco.museum")];
+ char stringpool_str4673[sizeof("minamioguni.kumamoto.jp")];
+ char stringpool_str4674[sizeof("hisayama.fukuoka.jp")];
+ char stringpool_str4675[sizeof("notodden.no")];
+ char stringpool_str4676[sizeof("iwaki.fukushima.jp")];
+ char stringpool_str4677[sizeof("yusui.kagoshima.jp")];
+ char stringpool_str4678[sizeof("toyokawa.aichi.jp")];
+ char stringpool_str4679[sizeof("klabu.no")];
+ char stringpool_str4680[sizeof("inami.toyama.jp")];
+ char stringpool_str4681[sizeof("izumi.kagoshima.jp")];
+ char stringpool_str4682[sizeof("org.iq")];
+ char stringpool_str4683[sizeof("nichinan.miyazaki.jp")];
+ char stringpool_str4684[sizeof("brandywinevalley.museum")];
+ char stringpool_str4685[sizeof("xn--seral-lra.no")];
+ char stringpool_str4686[sizeof("org.mn")];
+ char stringpool_str4687[sizeof("modalen.no")];
+ char stringpool_str4688[sizeof("oz.au")];
+ char stringpool_str4689[sizeof("s3-sa-east-1.amazonaws.com")];
+ char stringpool_str4690[sizeof("nakai.kanagawa.jp")];
+ char stringpool_str4691[sizeof("hidaka.saitama.jp")];
+ char stringpool_str4692[sizeof("omanmobile.om")];
+ char stringpool_str4693[sizeof("furukawa.miyagi.jp")];
+ char stringpool_str4694[sizeof("kolobrzeg.pl")];
+ char stringpool_str4695[sizeof("org.ml")];
+ char stringpool_str4696[sizeof("clock.museum")];
+ char stringpool_str4697[sizeof("zgrad.ru")];
+ char stringpool_str4698[sizeof("writesthisblog.com")];
+ char stringpool_str4699[sizeof("nesodden.no")];
+ char stringpool_str4700[sizeof("teaches-yoga.com")];
+ char stringpool_str4701[sizeof("tsukuba.ibaraki.jp")];
+ char stringpool_str4702[sizeof("est-mon-blogueur.com")];
+ char stringpool_str4703[sizeof("is-a-conservative.com")];
+ char stringpool_str4704[sizeof("guovdageaidnu.no")];
+ char stringpool_str4705[sizeof("khmelnitskiy.ua")];
+ char stringpool_str4706[sizeof("xn--xkc2dl3a5ee0h")];
+ char stringpool_str4707[sizeof("tsuruta.aomori.jp")];
+ char stringpool_str4708[sizeof("bydgoszcz.pl")];
+ char stringpool_str4709[sizeof("kanagawa.jp")];
+ char stringpool_str4710[sizeof("rahkkeravju.no")];
+ char stringpool_str4711[sizeof("trustee.museum")];
+ char stringpool_str4712[sizeof("is-an-actor.com")];
+ char stringpool_str4713[sizeof("flekkefjord.no")];
+ char stringpool_str4714[sizeof("xn--vard-jra.no")];
+ char stringpool_str4715[sizeof("cymru.museum")];
+ char stringpool_str4716[sizeof("nom.ro")];
+ char stringpool_str4717[sizeof("nom.re")];
+ char stringpool_str4718[sizeof("nishi.osaka.jp")];
+ char stringpool_str4719[sizeof("shingo.aomori.jp")];
+ char stringpool_str4720[sizeof("stargard.pl")];
+ char stringpool_str4721[sizeof("pc.it")];
+ char stringpool_str4722[sizeof("siellak.no")];
+ char stringpool_str4723[sizeof("zao.miyagi.jp")];
+ char stringpool_str4724[sizeof("shinyoshitomi.fukuoka.jp")];
+ char stringpool_str4725[sizeof("history.museum")];
+ char stringpool_str4726[sizeof("is-a-bulls-fan.com")];
+ char stringpool_str4727[sizeof("uk.com")];
+ char stringpool_str4728[sizeof("zamami.okinawa.jp")];
+ char stringpool_str4729[sizeof("xn--sknit-yqa.no")];
+ char stringpool_str4730[sizeof("endofinternet.org")];
+ char stringpool_str4731[sizeof("hirakata.osaka.jp")];
+ char stringpool_str4732[sizeof("gs.svalbard.no")];
+ char stringpool_str4733[sizeof("ne.kr")];
+ char stringpool_str4734[sizeof("law.pro")];
+ char stringpool_str4735[sizeof("seoul.kr")];
+ char stringpool_str4736[sizeof("xn--troms-zua.no")];
+ char stringpool_str4737[sizeof("giessen.museum")];
+ char stringpool_str4738[sizeof("nov.ru")];
+ char stringpool_str4739[sizeof("kiyama.saga.jp")];
+ char stringpool_str4740[sizeof("science-fiction.museum")];
+ char stringpool_str4741[sizeof("sch.uk")];
+ char stringpool_str4742[sizeof("takikawa.hokkaido.jp")];
+ char stringpool_str4743[sizeof("gov.ly")];
+ char stringpool_str4744[sizeof("edu.ly")];
+ char stringpool_str4745[sizeof("com.ly")];
+ char stringpool_str4746[sizeof("ashikaga.tochigi.jp")];
+ char stringpool_str4747[sizeof("oizumi.gunma.jp")];
+ char stringpool_str4748[sizeof("seiyo.ehime.jp")];
+ char stringpool_str4749[sizeof("oketo.hokkaido.jp")];
+ char stringpool_str4750[sizeof("isleofman.museum")];
+ char stringpool_str4751[sizeof("habikino.osaka.jp")];
+ char stringpool_str4752[sizeof("kawakami.nagano.jp")];
+ char stringpool_str4753[sizeof("nt.edu.au")];
+ char stringpool_str4754[sizeof("songdalen.no")];
+ char stringpool_str4755[sizeof("xn--trna-woa.no")];
+ char stringpool_str4756[sizeof("of.no")];
+ char stringpool_str4757[sizeof("sakaki.nagano.jp")];
+ char stringpool_str4758[sizeof("sanuki.kagawa.jp")];
+ char stringpool_str4759[sizeof("takahama.fukui.jp")];
+ char stringpool_str4760[sizeof("gov.mv")];
+ char stringpool_str4761[sizeof("edu.mv")];
+ char stringpool_str4762[sizeof("burghof.museum")];
+ char stringpool_str4763[sizeof("toyako.hokkaido.jp")];
+ char stringpool_str4764[sizeof("com.mv")];
+ char stringpool_str4765[sizeof("namsskogan.no")];
+ char stringpool_str4766[sizeof("hidaka.hokkaido.jp")];
+ char stringpool_str4767[sizeof("xn--vads-jra.no")];
+ char stringpool_str4768[sizeof("brussel.museum")];
+ char stringpool_str4769[sizeof("siedlce.pl")];
+ char stringpool_str4770[sizeof("nom.mg")];
+ char stringpool_str4771[sizeof("drangedal.no")];
+ char stringpool_str4772[sizeof("iglesias-carbonia.it")];
+ char stringpool_str4773[sizeof("joshkar-ola.ru")];
+ char stringpool_str4774[sizeof("unzen.nagasaki.jp")];
+ char stringpool_str4775[sizeof("kamikitayama.nara.jp")];
+ char stringpool_str4776[sizeof("naroy.no")];
+ char stringpool_str4777[sizeof("news.hu")];
+ char stringpool_str4778[sizeof("tsuiki.fukuoka.jp")];
+ char stringpool_str4779[sizeof("idv.hk")];
+ char stringpool_str4780[sizeof("art.museum")];
+ char stringpool_str4781[sizeof("ryuoh.shiga.jp")];
+ char stringpool_str4782[sizeof("psc.br")];
+ char stringpool_str4783[sizeof("oto.fukuoka.jp")];
+ char stringpool_str4784[sizeof("net.ru")];
+ char stringpool_str4785[sizeof("inatsuki.fukuoka.jp")];
+ char stringpool_str4786[sizeof("murakami.niigata.jp")];
+ char stringpool_str4787[sizeof("iwate.iwate.jp")];
+ char stringpool_str4788[sizeof("nishiwaki.hyogo.jp")];
+ char stringpool_str4789[sizeof("sakyo.kyoto.jp")];
+ char stringpool_str4790[sizeof("yufu.oita.jp")];
+ char stringpool_str4791[sizeof("is-a-personaltrainer.com")];
+ char stringpool_str4792[sizeof("gs.ol.no")];
+ char stringpool_str4793[sizeof("stuff-4-sale.org")];
+ char stringpool_str4794[sizeof("hidaka.wakayama.jp")];
+ char stringpool_str4795[sizeof("larsson.museum")];
+ char stringpool_str4796[sizeof("k12.ky.us")];
+ char stringpool_str4797[sizeof("oystre-slidre.no")];
+ char stringpool_str4798[sizeof("tomakomai.hokkaido.jp")];
+ char stringpool_str4799[sizeof("gyokuto.kumamoto.jp")];
+ char stringpool_str4800[sizeof("kosuge.yamanashi.jp")];
+ char stringpool_str4801[sizeof("incheon.kr")];
+ char stringpool_str4802[sizeof("med.ly")];
+ char stringpool_str4803[sizeof("usuki.oita.jp")];
+ char stringpool_str4804[sizeof("net.mo")];
+ char stringpool_str4805[sizeof("net.me")];
+ char stringpool_str4806[sizeof("net.mk")];
+ char stringpool_str4807[sizeof("lib.ky.us")];
+ char stringpool_str4808[sizeof("xn--berlevg-jxa.no")];
+ char stringpool_str4809[sizeof("is-a-techie.com")];
+ char stringpool_str4810[sizeof("steigen.no")];
+ char stringpool_str4811[sizeof("xn--sr-aurdal-l8a.no")];
+ char stringpool_str4812[sizeof("ayase.kanagawa.jp")];
+ char stringpool_str4813[sizeof("bristol.museum")];
+ char stringpool_str4814[sizeof("and.museum")];
+ char stringpool_str4815[sizeof("is-a-doctor.com")];
+ char stringpool_str4816[sizeof("lg.jp")];
+ char stringpool_str4817[sizeof("net.ma")];
+ char stringpool_str4818[sizeof("air.museum")];
+ char stringpool_str4819[sizeof("xn--zf0avx.hk")];
+ char stringpool_str4820[sizeof("indiana.museum")];
+ char stringpool_str4821[sizeof("xn--vrggt-xqad.no")];
+ char stringpool_str4822[sizeof("xn--j6w193g")];
+ char stringpool_str4823[sizeof("eng.pro")];
+ char stringpool_str4824[sizeof("ddr.museum")];
+ char stringpool_str4825[sizeof("mjondalen.no")];
+ char stringpool_str4826[sizeof("yashiro.hyogo.jp")];
+ char stringpool_str4827[sizeof("gamagori.aichi.jp")];
+ char stringpool_str4828[sizeof("tokuyama.yamaguchi.jp")];
+ char stringpool_str4829[sizeof("yao.osaka.jp")];
+ char stringpool_str4830[sizeof("kahoku.ishikawa.jp")];
+ char stringpool_str4831[sizeof("minamidaito.okinawa.jp")];
+ char stringpool_str4832[sizeof("xn--55qx5d.cn")];
+ char stringpool_str4833[sizeof("sukumo.kochi.jp")];
+ char stringpool_str4834[sizeof("net.mu")];
+ char stringpool_str4835[sizeof("is-a-geek.org")];
+ char stringpool_str4836[sizeof("pyatigorsk.ru")];
+ char stringpool_str4837[sizeof("tonaki.okinawa.jp")];
+ char stringpool_str4838[sizeof("government.aero")];
+ char stringpool_str4839[sizeof("kharkov.ua")];
+ char stringpool_str4840[sizeof("kharkiv.ua")];
+ char stringpool_str4841[sizeof("net.iq")];
+ char stringpool_str4842[sizeof("gs.nl.no")];
+ char stringpool_str4843[sizeof("rotorcraft.aero")];
+ char stringpool_str4844[sizeof("miyagi.jp")];
+ char stringpool_str4845[sizeof("bus.museum")];
+ char stringpool_str4846[sizeof("daigo.ibaraki.jp")];
+ char stringpool_str4847[sizeof("yahaba.iwate.jp")];
+ char stringpool_str4848[sizeof("nkz.ru")];
+ char stringpool_str4849[sizeof("akiruno.tokyo.jp")];
+ char stringpool_str4850[sizeof("skjak.no")];
+ char stringpool_str4851[sizeof("is-a-hard-worker.com")];
+ char stringpool_str4852[sizeof("net.ml")];
+ char stringpool_str4853[sizeof("mad.museum")];
+ char stringpool_str4854[sizeof("chikuhoku.nagano.jp")];
+ char stringpool_str4855[sizeof("plc.co.im")];
+ char stringpool_str4856[sizeof("ac.ru")];
+ char stringpool_str4857[sizeof("xn--s-1fa.no")];
+ char stringpool_str4858[sizeof("usdecorativearts.museum")];
+ char stringpool_str4859[sizeof("uy.com")];
+ char stringpool_str4860[sizeof("xn--smna-gra.no")];
+ char stringpool_str4861[sizeof("nsk.ru")];
+ char stringpool_str4862[sizeof("gloppen.no")];
+ char stringpool_str4863[sizeof("readmyblog.org")];
+ char stringpool_str4864[sizeof("in.th")];
+ char stringpool_str4865[sizeof("khmelnytskyi.ua")];
+ char stringpool_str4866[sizeof("doomdns.com")];
+ char stringpool_str4867[sizeof("ardal.no")];
+ char stringpool_str4868[sizeof("misaki.okayama.jp")];
+ char stringpool_str4869[sizeof("ichinomiya.chiba.jp")];
+ char stringpool_str4870[sizeof("nagai.yamagata.jp")];
+ char stringpool_str4871[sizeof("sekikawa.niigata.jp")];
+ char stringpool_str4872[sizeof("izunokuni.shizuoka.jp")];
+ char stringpool_str4873[sizeof("barrell-of-knowledge.info")];
+ char stringpool_str4874[sizeof("novara.it")];
+ char stringpool_str4875[sizeof("katagami.akita.jp")];
+ char stringpool_str4876[sizeof("kitagata.saga.jp")];
+ char stringpool_str4877[sizeof("can.museum")];
+ char stringpool_str4878[sizeof("ulan-ude.ru")];
+ char stringpool_str4879[sizeof("bando.ibaraki.jp")];
+ char stringpool_str4880[sizeof("inami.wakayama.jp")];
+ char stringpool_str4881[sizeof("biz.mv")];
+ char stringpool_str4882[sizeof("jamal.ru")];
+ char stringpool_str4883[sizeof("toyoura.hokkaido.jp")];
+ char stringpool_str4884[sizeof("xn--bmlo-gra.no")];
+ char stringpool_str4885[sizeof("nuoro.it")];
+ char stringpool_str4886[sizeof("moriguchi.osaka.jp")];
+ char stringpool_str4887[sizeof("oseto.nagasaki.jp")];
+ char stringpool_str4888[sizeof("xn--b-5ga.nordland.no")];
+ char stringpool_str4889[sizeof("horokanai.hokkaido.jp")];
+ char stringpool_str4890[sizeof("aerodrome.aero")];
+ char stringpool_str4891[sizeof("nalchik.ru")];
+ char stringpool_str4892[sizeof("mil.mv")];
+ char stringpool_str4893[sizeof("chikuma.nagano.jp")];
+ char stringpool_str4894[sizeof("beppu.oita.jp")];
+ char stringpool_str4895[sizeof("yamatsuri.fukushima.jp")];
+ char stringpool_str4896[sizeof("ski.museum")];
+ char stringpool_str4897[sizeof("kitagawa.kochi.jp")];
+ char stringpool_str4898[sizeof("palmsprings.museum")];
+ char stringpool_str4899[sizeof("for-more.biz")];
+ char stringpool_str4900[sizeof("nishinomiya.hyogo.jp")];
+ char stringpool_str4901[sizeof("xn--tn0ag.hk")];
+ char stringpool_str4902[sizeof("tas.gov.au")];
+ char stringpool_str4903[sizeof("org.uz")];
+ char stringpool_str4904[sizeof("tokigawa.saitama.jp")];
+ char stringpool_str4905[sizeof("oarai.ibaraki.jp")];
+ char stringpool_str4906[sizeof("org.ug")];
+ char stringpool_str4907[sizeof("oki.fukuoka.jp")];
+ char stringpool_str4908[sizeof("doesntexist.com")];
+ char stringpool_str4909[sizeof("nishitosa.kochi.jp")];
+ char stringpool_str4910[sizeof("is-a-blogger.com")];
+ char stringpool_str4911[sizeof("org.ua")];
+ char stringpool_str4912[sizeof("minokamo.gifu.jp")];
+ char stringpool_str4913[sizeof("vologda.ru")];
+ char stringpool_str4914[sizeof("pulawy.pl")];
+ char stringpool_str4915[sizeof("kadogawa.miyazaki.jp")];
+ char stringpool_str4916[sizeof("mykolaiv.ua")];
+ char stringpool_str4917[sizeof("nature.museum")];
+ char stringpool_str4918[sizeof("badajoz.museum")];
+ char stringpool_str4919[sizeof("usa.oita.jp")];
+ char stringpool_str4920[sizeof("isumi.chiba.jp")];
+ char stringpool_str4921[sizeof("tsuruoka.yamagata.jp")];
+ char stringpool_str4922[sizeof("malopolska.pl")];
+ char stringpool_str4923[sizeof("xn--55qx5d.hk")];
+ char stringpool_str4924[sizeof("xn--btsfjord-9za.no")];
+ char stringpool_str4925[sizeof("fjell.no")];
+ char stringpool_str4926[sizeof("is-a-hunter.com")];
+ char stringpool_str4927[sizeof("xn--srum-gra.no")];
+ char stringpool_str4928[sizeof("cc.md.us")];
+ char stringpool_str4929[sizeof("torahime.shiga.jp")];
+ char stringpool_str4930[sizeof("ogano.saitama.jp")];
+ char stringpool_str4931[sizeof("fujikawa.yamanashi.jp")];
+ char stringpool_str4932[sizeof("cc.id.us")];
+ char stringpool_str4933[sizeof("varggat.no")];
+ char stringpool_str4934[sizeof("minamiawaji.hyogo.jp")];
+ char stringpool_str4935[sizeof("mihara.kochi.jp")];
+ char stringpool_str4936[sizeof("basel.museum")];
+ char stringpool_str4937[sizeof("shimogo.fukushima.jp")];
+ char stringpool_str4938[sizeof("casadelamoneda.museum")];
+ char stringpool_str4939[sizeof("nasushiobara.tochigi.jp")];
+ char stringpool_str4940[sizeof("xn--sandy-yua.no")];
+ char stringpool_str4941[sizeof("webhop.net")];
+ char stringpool_str4942[sizeof("blogdns.com")];
+ char stringpool_str4943[sizeof("xn--zf0ao64a.tw")];
+ char stringpool_str4944[sizeof("gov.py")];
+ char stringpool_str4945[sizeof("edu.py")];
+ char stringpool_str4946[sizeof("office-on-the.net")];
+ char stringpool_str4947[sizeof("barrel-of-knowledge.info")];
+ char stringpool_str4948[sizeof("com.py")];
+ char stringpool_str4949[sizeof("nikaho.akita.jp")];
+ char stringpool_str4950[sizeof("isteingeek.de")];
+ char stringpool_str4951[sizeof("com.gy")];
+ char stringpool_str4952[sizeof("airtraffic.aero")];
+ char stringpool_str4953[sizeof("shell.museum")];
+ char stringpool_str4954[sizeof("xn--bjddar-pta.no")];
+ char stringpool_str4955[sizeof("urbinopesaro.it")];
+ char stringpool_str4956[sizeof("xn--vgan-qoa.no")];
+ char stringpool_str4957[sizeof("is-a-lawyer.com")];
+ char stringpool_str4958[sizeof("oygarden.no")];
+ char stringpool_str4959[sizeof("ivano-frankivsk.ua")];
+ char stringpool_str4960[sizeof("kunigami.okinawa.jp")];
+ char stringpool_str4961[sizeof("gs.hl.no")];
+ char stringpool_str4962[sizeof("kagamiishi.fukushima.jp")];
+ char stringpool_str4963[sizeof("imari.saga.jp")];
+ char stringpool_str4964[sizeof("kamikawa.hyogo.jp")];
+ char stringpool_str4965[sizeof("kitaura.miyazaki.jp")];
+ char stringpool_str4966[sizeof("omachi.saga.jp")];
+ char stringpool_str4967[sizeof("zoology.museum")];
+ char stringpool_str4968[sizeof("family.museum")];
+ char stringpool_str4969[sizeof("ogaki.gifu.jp")];
+ char stringpool_str4970[sizeof("works.aero")];
+ char stringpool_str4971[sizeof("hotel.tz")];
+ char stringpool_str4972[sizeof("western.museum")];
+ char stringpool_str4973[sizeof("xn--sr-odal-q1a.no")];
+ char stringpool_str4974[sizeof("cc.nd.us")];
+ char stringpool_str4975[sizeof("pf")];
+ char stringpool_str4976[sizeof("kikugawa.shizuoka.jp")];
+ char stringpool_str4977[sizeof("hotel.lk")];
+ char stringpool_str4978[sizeof("bologna.it")];
+ char stringpool_str4979[sizeof("tsuno.kochi.jp")];
+ char stringpool_str4980[sizeof("culturalcenter.museum")];
+ char stringpool_str4981[sizeof("environmentalconservation.museum")];
+ char stringpool_str4982[sizeof("nango.fukushima.jp")];
+ char stringpool_str4983[sizeof("xn--trany-yua.no")];
+ char stringpool_str4984[sizeof("kitagawa.miyazaki.jp")];
+ char stringpool_str4985[sizeof("gs.rl.no")];
+ char stringpool_str4986[sizeof("nishikata.tochigi.jp")];
+ char stringpool_str4987[sizeof("shingu.wakayama.jp")];
+ char stringpool_str4988[sizeof("izumi.osaka.jp")];
+ char stringpool_str4989[sizeof("hotel.hu")];
+ char stringpool_str4990[sizeof("xn--bhcavuotna-s4a.no")];
+ char stringpool_str4991[sizeof("ouchi.saga.jp")];
+ char stringpool_str4992[sizeof("xn--sandnessjen-ogb.no")];
+ char stringpool_str4993[sizeof("xn--io0a7i.cn")];
+ char stringpool_str4994[sizeof("bjarkoy.no")];
+ char stringpool_str4995[sizeof("uwajima.ehime.jp")];
+ char stringpool_str4996[sizeof("xn--vgsy-qoa0j.no")];
+ char stringpool_str4997[sizeof("kakegawa.shizuoka.jp")];
+ char stringpool_str4998[sizeof("yoshinogari.saga.jp")];
+ char stringpool_str4999[sizeof("kinokawa.wakayama.jp")];
+ char stringpool_str5000[sizeof("oshima.tokyo.jp")];
+ char stringpool_str5001[sizeof("carboniaiglesias.it")];
+ char stringpool_str5002[sizeof("sunagawa.hokkaido.jp")];
+ char stringpool_str5003[sizeof("nagaokakyo.kyoto.jp")];
+ char stringpool_str5004[sizeof("minami-alps.yamanashi.jp")];
+ char stringpool_str5005[sizeof("grong.no")];
+ char stringpool_str5006[sizeof("tahara.aichi.jp")];
+ char stringpool_str5007[sizeof("sagamihara.kanagawa.jp")];
+ char stringpool_str5008[sizeof("north.museum")];
+ char stringpool_str5009[sizeof("lavagis.no")];
+ char stringpool_str5010[sizeof("yawatahama.ehime.jp")];
+ char stringpool_str5011[sizeof("nanmoku.gunma.jp")];
+ char stringpool_str5012[sizeof("ohkura.yamagata.jp")];
+ char stringpool_str5013[sizeof("inagawa.hyogo.jp")];
+ char stringpool_str5014[sizeof("cc.or.us")];
+ char stringpool_str5015[sizeof("kumagaya.saitama.jp")];
+ char stringpool_str5016[sizeof("kawakita.ishikawa.jp")];
+ char stringpool_str5017[sizeof("xn--ciqpn.hk")];
+ char stringpool_str5018[sizeof("inzai.chiba.jp")];
+ char stringpool_str5019[sizeof("dreamhosters.com")];
+ char stringpool_str5020[sizeof("uzhgorod.ua")];
+ char stringpool_str5021[sizeof("mihama.aichi.jp")];
+ char stringpool_str5022[sizeof("yaotsu.gifu.jp")];
+ char stringpool_str5023[sizeof("xn--xkc2al3hye2a")];
+ char stringpool_str5024[sizeof("itami.hyogo.jp")];
+ char stringpool_str5025[sizeof("hobol.no")];
+ char stringpool_str5026[sizeof("miyake.nara.jp")];
+ char stringpool_str5027[sizeof("iki.fi")];
+ char stringpool_str5028[sizeof("sklep.pl")];
+ char stringpool_str5029[sizeof("tochigi.tochigi.jp")];
+ char stringpool_str5030[sizeof("osteroy.no")];
+ char stringpool_str5031[sizeof("is-a-patsfan.org")];
+ char stringpool_str5032[sizeof("yokohama.jp")];
+ char stringpool_str5033[sizeof("net.uz")];
+ char stringpool_str5034[sizeof("wakayama.jp")];
+ char stringpool_str5035[sizeof("net.ua")];
+ char stringpool_str5036[sizeof("xn--bearalvhki-y4a.no")];
+ char stringpool_str5037[sizeof("shiogama.miyagi.jp")];
+ char stringpool_str5038[sizeof("furudono.fukushima.jp")];
+ char stringpool_str5039[sizeof("spy.museum")];
+ char stringpool_str5040[sizeof("kurogi.fukuoka.jp")];
+ char stringpool_str5041[sizeof("mil.py")];
+ char stringpool_str5042[sizeof("cc.pr.us")];
+ char stringpool_str5043[sizeof("parachuting.aero")];
+ char stringpool_str5044[sizeof("kanzaki.saga.jp")];
+ char stringpool_str5045[sizeof("nobeoka.miyazaki.jp")];
+ char stringpool_str5046[sizeof("nacion.ar")];
+ char stringpool_str5047[sizeof("setagaya.tokyo.jp")];
+ char stringpool_str5048[sizeof("skjervoy.no")];
+ char stringpool_str5049[sizeof("klepp.no")];
+ char stringpool_str5050[sizeof("atsugi.kanagawa.jp")];
+ char stringpool_str5051[sizeof("air-traffic-control.aero")];
+ char stringpool_str5052[sizeof("inagi.tokyo.jp")];
+ char stringpool_str5053[sizeof("nsn.us")];
+ char stringpool_str5054[sizeof("xn--io0a7i.hk")];
+ char stringpool_str5055[sizeof("nls.uk")];
+ char stringpool_str5056[sizeof("miyako.fukuoka.jp")];
+ char stringpool_str5057[sizeof("nel.uk")];
+ char stringpool_str5058[sizeof("fukagawa.hokkaido.jp")];
+ char stringpool_str5059[sizeof("samukawa.kanagawa.jp")];
+ char stringpool_str5060[sizeof("championship.aero")];
+ char stringpool_str5061[sizeof("vefsn.no")];
+ char stringpool_str5062[sizeof("tydal.no")];
+ char stringpool_str5063[sizeof("aogaki.hyogo.jp")];
+ char stringpool_str5064[sizeof("xn--sr-fron-q1a.no")];
+ char stringpool_str5065[sizeof("ariake.saga.jp")];
+ char stringpool_str5066[sizeof("kanegasaki.iwate.jp")];
+ char stringpool_str5067[sizeof("kamimine.saga.jp")];
+ char stringpool_str5068[sizeof("takagi.nagano.jp")];
+ char stringpool_str5069[sizeof("cc.ar.us")];
+ char stringpool_str5070[sizeof("assedic.fr")];
+ char stringpool_str5071[sizeof("is-very-sweet.org")];
+ char stringpool_str5072[sizeof("nt.au")];
+ char stringpool_str5073[sizeof("soc.lk")];
+ char stringpool_str5074[sizeof("fuefuki.yamanashi.jp")];
+ char stringpool_str5075[sizeof("kitakyushu.jp")];
+ char stringpool_str5076[sizeof("cc.nm.us")];
+ char stringpool_str5077[sizeof("ono.hyogo.jp")];
+ char stringpool_str5078[sizeof("nanporo.hokkaido.jp")];
+ char stringpool_str5079[sizeof("sakura.tochigi.jp")];
+ char stringpool_str5080[sizeof("kanuma.tochigi.jp")];
+ char stringpool_str5081[sizeof("xn--bod-2na.no")];
+ char stringpool_str5082[sizeof("of.by")];
+ char stringpool_str5083[sizeof("miyakonojo.miyazaki.jp")];
+ char stringpool_str5084[sizeof("mizumaki.fukuoka.jp")];
+ char stringpool_str5085[sizeof("nesseby.no")];
+ char stringpool_str5086[sizeof("nesoddtangen.no")];
+ char stringpool_str5087[sizeof("is-a-photographer.com")];
+ char stringpool_str5088[sizeof("is-a-player.com")];
+ char stringpool_str5089[sizeof("tv.sd")];
+ char stringpool_str5090[sizeof("principe.st")];
+ char stringpool_str5091[sizeof("kasuga.hyogo.jp")];
+ char stringpool_str5092[sizeof("ushuaia.museum")];
+ char stringpool_str5093[sizeof("koshu.yamanashi.jp")];
+ char stringpool_str5094[sizeof("kasuga.fukuoka.jp")];
+ char stringpool_str5095[sizeof("dnepropetrovsk.ua")];
+ char stringpool_str5096[sizeof("nanto.toyama.jp")];
+ char stringpool_str5097[sizeof("skydiving.aero")];
+ char stringpool_str5098[sizeof("ikeda.gifu.jp")];
+ char stringpool_str5099[sizeof("yaita.tochigi.jp")];
+ char stringpool_str5100[sizeof("izumozaki.niigata.jp")];
+ char stringpool_str5101[sizeof("isahaya.nagasaki.jp")];
+ char stringpool_str5102[sizeof("norddal.no")];
+ char stringpool_str5103[sizeof("timekeeping.museum")];
+ char stringpool_str5104[sizeof("ostrowwlkp.pl")];
+ char stringpool_str5105[sizeof("xn--bhccavuotna-k7a.no")];
+ char stringpool_str5106[sizeof("vadso.no")];
+ char stringpool_str5107[sizeof("nuremberg.museum")];
+ char stringpool_str5108[sizeof("kamakura.kanagawa.jp")];
+ char stringpool_str5109[sizeof("worse-than.tv")];
+ char stringpool_str5110[sizeof("leangaviika.no")];
+ char stringpool_str5111[sizeof("uchinomi.kagawa.jp")];
+ char stringpool_str5112[sizeof("dnipropetrovsk.ua")];
+ char stringpool_str5113[sizeof("kagami.kochi.jp")];
+ char stringpool_str5114[sizeof("kochi.kochi.jp")];
+ char stringpool_str5115[sizeof("suwalki.pl")];
+ char stringpool_str5116[sizeof("kopervik.no")];
+ char stringpool_str5117[sizeof("iheya.okinawa.jp")];
+ char stringpool_str5118[sizeof("iris.arpa")];
+ char stringpool_str5119[sizeof("togakushi.nagano.jp")];
+ char stringpool_str5120[sizeof("freight.aero")];
+ char stringpool_str5121[sizeof("kamigori.hyogo.jp")];
+ char stringpool_str5122[sizeof("is-lost.org")];
+ char stringpool_str5123[sizeof("jessheim.no")];
+ char stringpool_str5124[sizeof("tomigusuku.okinawa.jp")];
+ char stringpool_str5125[sizeof("sanagochi.tokushima.jp")];
+ char stringpool_str5126[sizeof("nishiaizu.fukushima.jp")];
+ char stringpool_str5127[sizeof("buyshouses.net")];
+ char stringpool_str5128[sizeof("tozsde.hu")];
+ char stringpool_str5129[sizeof("tsuru.yamanashi.jp")];
+ char stringpool_str5130[sizeof("jan-mayen.no")];
+ char stringpool_str5131[sizeof("minamiashigara.kanagawa.jp")];
+ char stringpool_str5132[sizeof("hatogaya.saitama.jp")];
+ char stringpool_str5133[sizeof("nordreisa.no")];
+ char stringpool_str5134[sizeof("museumcenter.museum")];
+ char stringpool_str5135[sizeof("xn--czrw28b.tw")];
+ char stringpool_str5136[sizeof("rhcloud.com")];
+ char stringpool_str5137[sizeof("xn--vg-yiab.no")];
+ char stringpool_str5138[sizeof("civilwar.museum")];
+ char stringpool_str5139[sizeof("sch.lk")];
+ char stringpool_str5140[sizeof("nachikatsuura.wakayama.jp")];
+ char stringpool_str5141[sizeof("szczecin.pl")];
+ char stringpool_str5142[sizeof("kawaguchi.saitama.jp")];
+ char stringpool_str5143[sizeof("yusuhara.kochi.jp")];
+ char stringpool_str5144[sizeof("id.ir")];
+ char stringpool_str5145[sizeof("gwangju.kr")];
+ char stringpool_str5146[sizeof("scotland.museum")];
+ char stringpool_str5147[sizeof("myphotos.cc")];
+ char stringpool_str5148[sizeof("yurihonjo.akita.jp")];
+ char stringpool_str5149[sizeof("org.km")];
+ char stringpool_str5150[sizeof("maryland.museum")];
+ char stringpool_str5151[sizeof("org.ki")];
+ char stringpool_str5152[sizeof("org.kz")];
+ char stringpool_str5153[sizeof("org.kg")];
+ char stringpool_str5154[sizeof("nemuro.hokkaido.jp")];
+ char stringpool_str5155[sizeof("nakadomari.aomori.jp")];
+ char stringpool_str5156[sizeof("takanezawa.tochigi.jp")];
+ char stringpool_str5157[sizeof("urakawa.hokkaido.jp")];
+ char stringpool_str5158[sizeof("kitagata.gifu.jp")];
+ char stringpool_str5159[sizeof("xn--stjrdalshalsen-sqb.no")];
+ char stringpool_str5160[sizeof("shingu.hyogo.jp")];
+ char stringpool_str5161[sizeof("xn--aurskog-hland-jnb.no")];
+ char stringpool_str5162[sizeof("dyndns-office.com")];
+ char stringpool_str5163[sizeof("ozu.kumamoto.jp")];
+ char stringpool_str5164[sizeof("shingu.fukuoka.jp")];
+ char stringpool_str5165[sizeof("nabari.mie.jp")];
+ char stringpool_str5166[sizeof("pb.ao")];
+ char stringpool_str5167[sizeof("tsurugi.ishikawa.jp")];
+ char stringpool_str5168[sizeof("xn--vler-qoa.hedmark.no")];
+ char stringpool_str5169[sizeof("nagaoka.niigata.jp")];
+ char stringpool_str5170[sizeof("or.ug")];
+ char stringpool_str5171[sizeof("org.kn")];
+ char stringpool_str5172[sizeof("tsushima.aichi.jp")];
+ char stringpool_str5173[sizeof("xn--vestvgy-ixa6o.no")];
+ char stringpool_str5174[sizeof("uchinada.ishikawa.jp")];
+ char stringpool_str5175[sizeof("castres.museum")];
+ char stringpool_str5176[sizeof("hokuryu.hokkaido.jp")];
+ char stringpool_str5177[sizeof("chofu.tokyo.jp")];
+ char stringpool_str5178[sizeof("hikari.yamaguchi.jp")];
+ char stringpool_str5179[sizeof("kiyokawa.kanagawa.jp")];
+ char stringpool_str5180[sizeof("yoichi.hokkaido.jp")];
+ char stringpool_str5181[sizeof("nowaruda.pl")];
+ char stringpool_str5182[sizeof("izumo.shimane.jp")];
+ char stringpool_str5183[sizeof("webhop.org")];
+ char stringpool_str5184[sizeof("choshi.chiba.jp")];
+ char stringpool_str5185[sizeof("its.me")];
+ char stringpool_str5186[sizeof("izumizaki.fukushima.jp")];
+ char stringpool_str5187[sizeof("int.ru")];
+ char stringpool_str5188[sizeof("web.do")];
+ char stringpool_str5189[sizeof("dontexist.com")];
+ char stringpool_str5190[sizeof("kasamatsu.gifu.jp")];
+ char stringpool_str5191[sizeof("naturalhistory.museum")];
+ char stringpool_str5192[sizeof("nakayama.yamagata.jp")];
+ char stringpool_str5193[sizeof("utsunomiya.tochigi.jp")];
+ char stringpool_str5194[sizeof("missile.museum")];
+ char stringpool_str5195[sizeof("mansion.museum")];
+ char stringpool_str5196[sizeof("sc.ug")];
+ char stringpool_str5197[sizeof("ac.ug")];
+ char stringpool_str5198[sizeof("other.nf")];
+ char stringpool_str5199[sizeof("osoyro.no")];
+ char stringpool_str5200[sizeof("nakatsugawa.gifu.jp")];
+ char stringpool_str5201[sizeof("cc.gu.us")];
+ char stringpool_str5202[sizeof("org.my")];
+ char stringpool_str5203[sizeof("kozagawa.wakayama.jp")];
+ char stringpool_str5204[sizeof("warszawa.pl")];
+ char stringpool_str5205[sizeof("naturbruksgymn.se")];
+ char stringpool_str5206[sizeof("sec.ps")];
+ char stringpool_str5207[sizeof("obuse.nagano.jp")];
+ char stringpool_str5208[sizeof("olecko.pl")];
+ char stringpool_str5209[sizeof("hichiso.gifu.jp")];
+ char stringpool_str5210[sizeof("nerima.tokyo.jp")];
+ char stringpool_str5211[sizeof("nt.gov.au")];
+ char stringpool_str5212[sizeof("nanjo.okinawa.jp")];
+ char stringpool_str5213[sizeof("veterinaire.fr")];
+ char stringpool_str5214[sizeof("xn--wcvs22d.hk")];
+ char stringpool_str5215[sizeof("is-a-democrat.com")];
+ char stringpool_str5216[sizeof("shimonoseki.yamaguchi.jp")];
+ char stringpool_str5217[sizeof("nom.km")];
+ char stringpool_str5218[sizeof("naturalhistorymuseum.museum")];
+ char stringpool_str5219[sizeof("esashi.hokkaido.jp")];
+ char stringpool_str5220[sizeof("ac.ng")];
+ char stringpool_str5221[sizeof("union.aero")];
+ char stringpool_str5222[sizeof("ogose.saitama.jp")];
+ char stringpool_str5223[sizeof("hakodate.hokkaido.jp")];
+ char stringpool_str5224[sizeof("org.lk")];
+ char stringpool_str5225[sizeof("brand.se")];
+ char stringpool_str5226[sizeof("org.la")];
+ char stringpool_str5227[sizeof("nawrastelecom.om")];
+ char stringpool_str5228[sizeof("nordre-land.no")];
+ char stringpool_str5229[sizeof("vegarshei.no")];
+ char stringpool_str5230[sizeof("org.ls")];
+ char stringpool_str5231[sizeof("kakogawa.hyogo.jp")];
+ char stringpool_str5232[sizeof("org.lc")];
+ char stringpool_str5233[sizeof("org.lb")];
+ char stringpool_str5234[sizeof("net.ki")];
+ char stringpool_str5235[sizeof("net.kz")];
+ char stringpool_str5236[sizeof("net.kg")];
+ char stringpool_str5237[sizeof("xn--nry-yla5g.no")];
+ char stringpool_str5238[sizeof("shinkamigoto.nagasaki.jp")];
+ char stringpool_str5239[sizeof("vossevangen.no")];
+ char stringpool_str5240[sizeof("iz.hr")];
+ char stringpool_str5241[sizeof("taishi.osaka.jp")];
+ char stringpool_str5242[sizeof("iwama.ibaraki.jp")];
+ char stringpool_str5243[sizeof("shibuya.tokyo.jp")];
+ char stringpool_str5244[sizeof("chikuzen.fukuoka.jp")];
+ char stringpool_str5245[sizeof("org.lr")];
+ char stringpool_str5246[sizeof("sch.gg")];
+ char stringpool_str5247[sizeof("akishima.tokyo.jp")];
+ char stringpool_str5248[sizeof("xn--tysvr-vra.no")];
+ char stringpool_str5249[sizeof("wa.edu.au")];
+ char stringpool_str5250[sizeof("susaki.kochi.jp")];
+ char stringpool_str5251[sizeof("skierva.no")];
+ char stringpool_str5252[sizeof("xn--bjarky-fya.no")];
+ char stringpool_str5253[sizeof("homeunix.com")];
+ char stringpool_str5254[sizeof("net.kn")];
+ char stringpool_str5255[sizeof("hayakawa.yamanashi.jp")];
+ char stringpool_str5256[sizeof("dnsalias.com")];
+ char stringpool_str5257[sizeof("wakuya.miyagi.jp")];
+ char stringpool_str5258[sizeof("yamanobe.yamagata.jp")];
+ char stringpool_str5259[sizeof("org.kp")];
+ char stringpool_str5260[sizeof("homelinux.com")];
+ char stringpool_str5261[sizeof("is-an-accountant.com")];
+ char stringpool_str5262[sizeof("nagahama.shiga.jp")];
+ char stringpool_str5263[sizeof("abashiri.hokkaido.jp")];
+ char stringpool_str5264[sizeof("is-into-cars.com")];
+ char stringpool_str5265[sizeof("kawagoe.saitama.jp")];
+ char stringpool_str5266[sizeof("kudamatsu.yamaguchi.jp")];
+ char stringpool_str5267[sizeof("batsfjord.no")];
+ char stringpool_str5268[sizeof("omanpost.om")];
+ char stringpool_str5269[sizeof("xn--45brj9c")];
+ char stringpool_str5270[sizeof("natori.miyagi.jp")];
+ char stringpool_str5271[sizeof("is-a-teacher.com")];
+ char stringpool_str5272[sizeof("costume.museum")];
+ char stringpool_str5273[sizeof("is-a-anarchist.com")];
+ char stringpool_str5274[sizeof("podlasie.pl")];
+ char stringpool_str5275[sizeof("xn--s9brj9c")];
+ char stringpool_str5276[sizeof("is-by.us")];
+ char stringpool_str5277[sizeof("xn--vry-yla5g.no")];
+ char stringpool_str5278[sizeof("komaki.aichi.jp")];
+ char stringpool_str5279[sizeof("vagsoy.no")];
+ char stringpool_str5280[sizeof("coastaldefence.museum")];
+ char stringpool_str5281[sizeof("onjuku.chiba.jp")];
+ char stringpool_str5282[sizeof("ibestad.no")];
+ char stringpool_str5283[sizeof("nishiizu.shizuoka.jp")];
+ char stringpool_str5284[sizeof("otsuki.yamanashi.jp")];
+ char stringpool_str5285[sizeof("kujukuri.chiba.jp")];
+ char stringpool_str5286[sizeof("or.jp")];
+ char stringpool_str5287[sizeof("is-a-musician.com")];
+ char stringpool_str5288[sizeof("net.my")];
+ char stringpool_str5289[sizeof("hirogawa.wakayama.jp")];
+ char stringpool_str5290[sizeof("nakamura.kochi.jp")];
+ char stringpool_str5291[sizeof("gov.lv")];
+ char stringpool_str5292[sizeof("youth.museum")];
+ char stringpool_str5293[sizeof("edu.lv")];
+ char stringpool_str5294[sizeof("is-an-anarchist.com")];
+ char stringpool_str5295[sizeof("com.lv")];
+ char stringpool_str5296[sizeof("funagata.yamagata.jp")];
+ char stringpool_str5297[sizeof("kawamata.fukushima.jp")];
+ char stringpool_str5298[sizeof("balsfjord.no")];
+ char stringpool_str5299[sizeof("ujiie.tochigi.jp")];
+ char stringpool_str5300[sizeof("is-into-cartoons.com")];
+ char stringpool_str5301[sizeof("nayoro.hokkaido.jp")];
+ char stringpool_str5302[sizeof("nishigo.fukushima.jp")];
+ char stringpool_str5303[sizeof("is-a-landscaper.com")];
+ char stringpool_str5304[sizeof("tajimi.gifu.jp")];
+ char stringpool_str5305[sizeof("net.lk")];
+ char stringpool_str5306[sizeof("net.la")];
+ char stringpool_str5307[sizeof("is-certified.com")];
+ char stringpool_str5308[sizeof("oppegard.no")];
+ char stringpool_str5309[sizeof("qld.gov.au")];
+ char stringpool_str5310[sizeof("ac.jp")];
+ char stringpool_str5311[sizeof("shishikui.tokushima.jp")];
+ char stringpool_str5312[sizeof("net.lc")];
+ char stringpool_str5313[sizeof("tsushima.nagasaki.jp")];
+ char stringpool_str5314[sizeof("culture.museum")];
+ char stringpool_str5315[sizeof("windmill.museum")];
+ char stringpool_str5316[sizeof("sakegawa.yamagata.jp")];
+ char stringpool_str5317[sizeof("dontexist.net")];
+ char stringpool_str5318[sizeof("net.lb")];
+ char stringpool_str5319[sizeof("xn--wgbh1c")];
+ char stringpool_str5320[sizeof("yasuoka.nagano.jp")];
+ char stringpool_str5321[sizeof("itano.tokushima.jp")];
+ char stringpool_str5322[sizeof("whaling.museum")];
+ char stringpool_str5323[sizeof("asn.lv")];
+ char stringpool_str5324[sizeof("us.org")];
+ char stringpool_str5325[sizeof("org.pe")];
+ char stringpool_str5326[sizeof("otofuke.hokkaido.jp")];
+ char stringpool_str5327[sizeof("org.pk")];
+ char stringpool_str5328[sizeof("net.lr")];
+ char stringpool_str5329[sizeof("udm.ru")];
+ char stringpool_str5330[sizeof("org.ge")];
+ char stringpool_str5331[sizeof("org.gi")];
+ char stringpool_str5332[sizeof("nishinoomote.kagoshima.jp")];
+ char stringpool_str5333[sizeof("org.gg")];
+ char stringpool_str5334[sizeof("org.pa")];
+ char stringpool_str5335[sizeof("org.ps")];
+ char stringpool_str5336[sizeof("nanao.ishikawa.jp")];
+ char stringpool_str5337[sizeof("ambulance.aero")];
+ char stringpool_str5338[sizeof("virtuel.museum")];
+ char stringpool_str5339[sizeof("marylhurst.museum")];
+ char stringpool_str5340[sizeof("virtual.museum")];
+ char stringpool_str5341[sizeof("bialowieza.pl")];
+ char stringpool_str5342[sizeof("org.uy")];
+ char stringpool_str5343[sizeof("oguchi.aichi.jp")];
+ char stringpool_str5344[sizeof("utsira.no")];
+ char stringpool_str5345[sizeof("org.pt")];
+ char stringpool_str5346[sizeof("aca.pro")];
+ char stringpool_str5347[sizeof("org.gt")];
+ char stringpool_str5348[sizeof("org.pr")];
+ char stringpool_str5349[sizeof("org.pn")];
+ char stringpool_str5350[sizeof("hembygdsforbund.museum")];
+ char stringpool_str5351[sizeof("pro.ht")];
+ char stringpool_str5352[sizeof("org.gr")];
+ char stringpool_str5353[sizeof("org.gn")];
+ char stringpool_str5354[sizeof("promocion.ar")];
+ char stringpool_str5355[sizeof("nagara.chiba.jp")];
+ char stringpool_str5356[sizeof("is-a-liberal.com")];
+ char stringpool_str5357[sizeof("settlement.museum")];
+ char stringpool_str5358[sizeof("org.pl")];
+ char stringpool_str5359[sizeof("aeroport.fr")];
+ char stringpool_str5360[sizeof("id.au")];
+ char stringpool_str5361[sizeof("elasticbeanstalk.com")];
+ char stringpool_str5362[sizeof("chirurgiens-dentistes.fr")];
+ char stringpool_str5363[sizeof("isa.us")];
+ char stringpool_str5364[sizeof("mil.lv")];
+ char stringpool_str5365[sizeof("udono.mie.jp")];
+ char stringpool_str5366[sizeof("namerikawa.toyama.jp")];
+ char stringpool_str5367[sizeof("gs.sf.no")];
+ char stringpool_str5368[sizeof("dynalias.com")];
+ char stringpool_str5369[sizeof("xn--blt-elab.no")];
+ char stringpool_str5370[sizeof("bieszczady.pl")];
+ char stringpool_str5371[sizeof("xn--slt-elab.no")];
+ char stringpool_str5372[sizeof("est-le-patron.com")];
+ char stringpool_str5373[sizeof("hidaka.kochi.jp")];
+ char stringpool_str5374[sizeof("kumakogen.ehime.jp")];
+ char stringpool_str5375[sizeof("org.mw")];
+ char stringpool_str5376[sizeof("pol.ht")];
+ char stringpool_str5377[sizeof("baghdad.museum")];
+ char stringpool_str5378[sizeof("is-a-geek.com")];
+ char stringpool_str5379[sizeof("newmexico.museum")];
+ char stringpool_str5380[sizeof("xn--bidr-5nac.no")];
+ char stringpool_str5381[sizeof("kashiwa.chiba.jp")];
+ char stringpool_str5382[sizeof("homelinux.net")];
+ char stringpool_str5383[sizeof("www.ro")];
+ char stringpool_str5384[sizeof("cc.ok.us")];
+ char stringpool_str5385[sizeof("huissier-justice.fr")];
+ char stringpool_str5386[sizeof("isshiki.aichi.jp")];
+ char stringpool_str5387[sizeof("info.vn")];
+ char stringpool_str5388[sizeof("info.ve")];
+ char stringpool_str5389[sizeof("nom.pe")];
+ char stringpool_str5390[sizeof("presse.ci")];
+ char stringpool_str5391[sizeof("phoenix.museum")];
+ char stringpool_str5392[sizeof("gjesdal.no")];
+ char stringpool_str5393[sizeof("nom.pa")];
+ char stringpool_str5394[sizeof("is-a-soxfan.org")];
+ char stringpool_str5395[sizeof("norfolk.museum")];
+ char stringpool_str5396[sizeof("porsangu.no")];
+ char stringpool_str5397[sizeof("nordkapp.no")];
+ char stringpool_str5398[sizeof("nieruchomosci.pl")];
+ char stringpool_str5399[sizeof("kawagoe.mie.jp")];
+ char stringpool_str5400[sizeof("valer.ostfold.no")];
+ char stringpool_str5401[sizeof("presse.km")];
+ char stringpool_str5402[sizeof("napoli.it")];
+ char stringpool_str5403[sizeof("taishi.hyogo.jp")];
+ char stringpool_str5404[sizeof("consulado.st")];
+ char stringpool_str5405[sizeof("gausdal.no")];
+ char stringpool_str5406[sizeof("net.pe")];
+ char stringpool_str5407[sizeof("ne.ug")];
+ char stringpool_str5408[sizeof("net.pk")];
+ char stringpool_str5409[sizeof("kakamigahara.gifu.jp")];
+ char stringpool_str5410[sizeof("net.ge")];
+ char stringpool_str5411[sizeof("oxford.museum")];
+ char stringpool_str5412[sizeof("net.gg")];
+ char stringpool_str5413[sizeof("net.pa")];
+ char stringpool_str5414[sizeof("net.ps")];
+ char stringpool_str5415[sizeof("undersea.museum")];
+ char stringpool_str5416[sizeof("iglesiascarbonia.it")];
+ char stringpool_str5417[sizeof("akashi.hyogo.jp")];
+ char stringpool_str5418[sizeof("nom.pl")];
+ char stringpool_str5419[sizeof("org.pf")];
+ char stringpool_str5420[sizeof("prd.fr")];
+ char stringpool_str5421[sizeof("net.uy")];
+ char stringpool_str5422[sizeof("net.pt")];
+ char stringpool_str5423[sizeof("pilot.aero")];
+ char stringpool_str5424[sizeof("net.gt")];
+ char stringpool_str5425[sizeof("schokoladen.museum")];
+ char stringpool_str5426[sizeof("presse.ml")];
+ char stringpool_str5427[sizeof("net.pr")];
+ char stringpool_str5428[sizeof("net.pn")];
+ char stringpool_str5429[sizeof("net.rw")];
+ char stringpool_str5430[sizeof("net.gr")];
+ char stringpool_str5431[sizeof("net.gn")];
+ char stringpool_str5432[sizeof("ora.gunma.jp")];
+ char stringpool_str5433[sizeof("cc.ak.us")];
+ char stringpool_str5434[sizeof("poltava.ua")];
+ char stringpool_str5435[sizeof("nishiazai.shiga.jp")];
+ char stringpool_str5436[sizeof("pp.se")];
+ char stringpool_str5437[sizeof("ota.gunma.jp")];
+ char stringpool_str5438[sizeof("net.pl")];
+ char stringpool_str5439[sizeof("org.gp")];
+ char stringpool_str5440[sizeof("pistoia.it")];
+ char stringpool_str5441[sizeof("xn--sr-varanger-ggb.no")];
+ char stringpool_str5442[sizeof("valer.hedmark.no")];
+ char stringpool_str5443[sizeof("kasukabe.saitama.jp")];
+ char stringpool_str5444[sizeof("org.ph")];
+ char stringpool_str5445[sizeof("is-a-republican.com")];
+ char stringpool_str5446[sizeof("wa.au")];
+ char stringpool_str5447[sizeof("org.gh")];
+ char stringpool_str5448[sizeof("project.museum")];
+ char stringpool_str5449[sizeof("marburg.museum")];
+ char stringpool_str5450[sizeof("ngo.lk")];
+ char stringpool_str5451[sizeof("yamada.toyama.jp")];
+ char stringpool_str5452[sizeof("wakayama.wakayama.jp")];
+ char stringpool_str5453[sizeof("is-a-libertarian.com")];
+ char stringpool_str5454[sizeof("issmarterthanyou.com")];
+ char stringpool_str5455[sizeof("is-a-nascarfan.com")];
+ char stringpool_str5456[sizeof("oskol.ru")];
+ char stringpool_str5457[sizeof("parliament.uk")];
+ char stringpool_str5458[sizeof("localhistory.museum")];
+ char stringpool_str5459[sizeof("net.mw")];
+ char stringpool_str5460[sizeof("cc.fl.us")];
+ char stringpool_str5461[sizeof("namikata.ehime.jp")];
+ char stringpool_str5462[sizeof("inuyama.aichi.jp")];
+ char stringpool_str5463[sizeof("gemological.museum")];
+ char stringpool_str5464[sizeof("xn--brnnysund-m8ac.no")];
+ char stringpool_str5465[sizeof("cc.il.us")];
+ char stringpool_str5466[sizeof("nankoku.kochi.jp")];
+ char stringpool_str5467[sizeof("museumvereniging.museum")];
+ char stringpool_str5468[sizeof("xn--node")];
+ char stringpool_str5469[sizeof("is-a-therapist.com")];
+ char stringpool_str5470[sizeof("scrapper-site.net")];
+ char stringpool_str5471[sizeof("nakama.fukuoka.jp")];
+ char stringpool_str5472[sizeof("sodegaura.chiba.jp")];
+ char stringpool_str5473[sizeof("yanaizu.fukushima.jp")];
+ char stringpool_str5474[sizeof("namsos.no")];
+ char stringpool_str5475[sizeof("hanggliding.aero")];
+ char stringpool_str5476[sizeof("tcm.museum")];
+ char stringpool_str5477[sizeof("munakata.fukuoka.jp")];
+ char stringpool_str5478[sizeof("yakumo.shimane.jp")];
+ char stringpool_str5479[sizeof("act.gov.au")];
+ char stringpool_str5480[sizeof("consultant.aero")];
+ char stringpool_str5481[sizeof("bifuka.hokkaido.jp")];
+ char stringpool_str5482[sizeof("xn--nmesjevuemie-tcba.no")];
+ char stringpool_str5483[sizeof("xn--sndre-land-0cb.no")];
+ char stringpool_str5484[sizeof("is-a-designer.com")];
+ char stringpool_str5485[sizeof("ise.mie.jp")];
+ char stringpool_str5486[sizeof("xn--vegrshei-c0a.no")];
+ char stringpool_str5487[sizeof("imperia.it")];
+ char stringpool_str5488[sizeof("nikolaev.ua")];
+ char stringpool_str5489[sizeof("is-a-geek.net")];
+ char stringpool_str5490[sizeof("vaksdal.no")];
+ char stringpool_str5491[sizeof("newport.museum")];
+ char stringpool_str5492[sizeof("cc.al.us")];
+ char stringpool_str5493[sizeof("okoppe.hokkaido.jp")];
+ char stringpool_str5494[sizeof("xn--sknland-fxa.no")];
+ char stringpool_str5495[sizeof("fuchu.hiroshima.jp")];
+ char stringpool_str5496[sizeof("ichinomiya.aichi.jp")];
+ char stringpool_str5497[sizeof("kitakata.fukushima.jp")];
+ char stringpool_str5498[sizeof("xn--comunicaes-v6a2o.museum")];
+ char stringpool_str5499[sizeof("xn--jlster-bya.no")];
+ char stringpool_str5500[sizeof("sch.ly")];
+ char stringpool_str5501[sizeof("kamogawa.chiba.jp")];
+ char stringpool_str5502[sizeof("oryol.ru")];
+ char stringpool_str5503[sizeof("udmurtia.ru")];
+ char stringpool_str5504[sizeof("is-gone.com")];
+ char stringpool_str5505[sizeof("yakumo.hokkaido.jp")];
+ char stringpool_str5506[sizeof("net.gp")];
+ char stringpool_str5507[sizeof("ne.jp")];
+ char stringpool_str5508[sizeof("equipment.aero")];
+ char stringpool_str5509[sizeof("iijima.nagano.jp")];
+ char stringpool_str5510[sizeof("ogasawara.tokyo.jp")];
+ char stringpool_str5511[sizeof("preservation.museum")];
+ char stringpool_str5512[sizeof("org.ky")];
+ char stringpool_str5513[sizeof("suifu.ibaraki.jp")];
+ char stringpool_str5514[sizeof("nagareyama.chiba.jp")];
+ char stringpool_str5515[sizeof("is-an-engineer.com")];
+ char stringpool_str5516[sizeof("judygarland.museum")];
+ char stringpool_str5517[sizeof("net.ph")];
+ char stringpool_str5518[sizeof("tsuruga.fukui.jp")];
+ char stringpool_str5519[sizeof("is-with-theband.com")];
+ char stringpool_str5520[sizeof("schweiz.museum")];
+ char stringpool_str5521[sizeof("omigawa.chiba.jp")];
+ char stringpool_str5522[sizeof("navigation.aero")];
+ char stringpool_str5523[sizeof("presse.fr")];
+ char stringpool_str5524[sizeof("paleo.museum")];
+ char stringpool_str5525[sizeof("hamburg.museum")];
+ char stringpool_str5526[sizeof("xn--brnny-wuac.no")];
+ char stringpool_str5527[sizeof("xn--tjme-hra.no")];
+ char stringpool_str5528[sizeof("kiryu.gunma.jp")];
+ char stringpool_str5529[sizeof("pol.dz")];
+ char stringpool_str5530[sizeof("piacenza.it")];
+ char stringpool_str5531[sizeof("vic.gov.au")];
+ char stringpool_str5532[sizeof("nyuzen.toyama.jp")];
+ char stringpool_str5533[sizeof("xn--o3cw4h")];
+ char stringpool_str5534[sizeof("interactive.museum")];
+ char stringpool_str5535[sizeof("in-addr.arpa")];
+ char stringpool_str5536[sizeof("motegi.tochigi.jp")];
+ char stringpool_str5537[sizeof("nakamichi.yamanashi.jp")];
+ char stringpool_str5538[sizeof("tamakawa.fukushima.jp")];
+ char stringpool_str5539[sizeof("hirokawa.fukuoka.jp")];
+ char stringpool_str5540[sizeof("is-a-celticsfan.org")];
+ char stringpool_str5541[sizeof("xn--9dbhblg6di.museum")];
+ char stringpool_str5542[sizeof("kawaue.gifu.jp")];
+ char stringpool_str5543[sizeof("fauske.no")];
+ char stringpool_str5544[sizeof("usgarden.museum")];
+ char stringpool_str5545[sizeof("xn--srfold-bya.no")];
+ char stringpool_str5546[sizeof("matsusaka.mie.jp")];
+ char stringpool_str5547[sizeof("inderoy.no")];
+ char stringpool_str5548[sizeof("wassamu.hokkaido.jp")];
+ char stringpool_str5549[sizeof("norilsk.ru")];
+ char stringpool_str5550[sizeof("ichinohe.iwate.jp")];
+ char stringpool_str5551[sizeof("is-into-games.com")];
+ char stringpool_str5552[sizeof("tarumizu.kagoshima.jp")];
+ char stringpool_str5553[sizeof("wa.gov.au")];
+ char stringpool_str5554[sizeof("boldlygoingnowhere.org")];
+ char stringpool_str5555[sizeof("webhop.biz")];
+ char stringpool_str5556[sizeof("xn--skjervy-v1a.no")];
+ char stringpool_str5557[sizeof("pruszkow.pl")];
+ char stringpool_str5558[sizeof("tomika.gifu.jp")];
+ char stringpool_str5559[sizeof("ngo.pl")];
+ char stringpool_str5560[sizeof("hanyu.saitama.jp")];
+ char stringpool_str5561[sizeof("pe.kr")];
+ char stringpool_str5562[sizeof("oshima.yamaguchi.jp")];
+ char stringpool_str5563[sizeof("shiso.hyogo.jp")];
+ char stringpool_str5564[sizeof("olsztyn.pl")];
+ char stringpool_str5565[sizeof("wolomin.pl")];
+ char stringpool_str5566[sizeof("uenohara.yamanashi.jp")];
+ char stringpool_str5567[sizeof("is-very-nice.org")];
+ char stringpool_str5568[sizeof("yamada.fukuoka.jp")];
+ char stringpool_str5569[sizeof("miyako.iwate.jp")];
+ char stringpool_str5570[sizeof("xn--jrpeland-54a.no")];
+ char stringpool_str5571[sizeof("is-a-financialadvisor.com")];
+ char stringpool_str5572[sizeof("umi.fukuoka.jp")];
+ char stringpool_str5573[sizeof("ptz.ru")];
+ char stringpool_str5574[sizeof("int.lk")];
+ char stringpool_str5575[sizeof("utazas.hu")];
+ char stringpool_str5576[sizeof("fuchu.toyama.jp")];
+ char stringpool_str5577[sizeof("org.ly")];
+ char stringpool_str5578[sizeof("edu.mx")];
+ char stringpool_str5579[sizeof("xn--skierv-uta.no")];
+ char stringpool_str5580[sizeof("int.la")];
+ char stringpool_str5581[sizeof("com.mx")];
+ char stringpool_str5582[sizeof("ikusaka.nagano.jp")];
+ char stringpool_str5583[sizeof("yawara.ibaraki.jp")];
+ char stringpool_str5584[sizeof("matsue.shimane.jp")];
+ char stringpool_str5585[sizeof("prd.mg")];
+ char stringpool_str5586[sizeof("tokorozawa.saitama.jp")];
+ char stringpool_str5587[sizeof("org.mv")];
+ char stringpool_str5588[sizeof("yamamoto.miyagi.jp")];
+ char stringpool_str5589[sizeof("net.ky")];
+ char stringpool_str5590[sizeof("chikugo.fukuoka.jp")];
+ char stringpool_str5591[sizeof("nyc.mn")];
+ char stringpool_str5592[sizeof("otaru.hokkaido.jp")];
+ char stringpool_str5593[sizeof("prato.it")];
+ char stringpool_str5594[sizeof("is-a-bookkeeper.com")];
+ char stringpool_str5595[sizeof("lajolla.museum")];
+ char stringpool_str5596[sizeof("shisui.chiba.jp")];
+ char stringpool_str5597[sizeof("walbrzych.pl")];
+ char stringpool_str5598[sizeof("ninohe.iwate.jp")];
+ char stringpool_str5599[sizeof("ichinoseki.iwate.jp")];
+ char stringpool_str5600[sizeof("emergency.aero")];
+ char stringpool_str5601[sizeof("alaska.museum")];
+ char stringpool_str5602[sizeof("modelling.aero")];
+ char stringpool_str5603[sizeof("xn--sgne-gra.no")];
+ char stringpool_str5604[sizeof("consulting.aero")];
+ char stringpool_str5605[sizeof("ninomiya.kanagawa.jp")];
+ char stringpool_str5606[sizeof("nikko.tochigi.jp")];
+ char stringpool_str5607[sizeof("wielun.pl")];
+ char stringpool_str5608[sizeof("nic.uk")];
+ char stringpool_str5609[sizeof("yuu.yamaguchi.jp")];
+ char stringpool_str5610[sizeof("komagane.nagano.jp")];
+ char stringpool_str5611[sizeof("isesaki.gunma.jp")];
+ char stringpool_str5612[sizeof("gob.mx")];
+ char stringpool_str5613[sizeof("chikujo.fukuoka.jp")];
+ char stringpool_str5614[sizeof("yaese.okinawa.jp")];
+ char stringpool_str5615[sizeof("is-a-chef.org")];
+ char stringpool_str5616[sizeof("watari.miyagi.jp")];
+ char stringpool_str5617[sizeof("mitake.gifu.jp")];
+ char stringpool_str5618[sizeof("xn--b-5ga.telemark.no")];
+ char stringpool_str5619[sizeof("matsubara.osaka.jp")];
+ char stringpool_str5620[sizeof("kamagaya.chiba.jp")];
+ char stringpool_str5621[sizeof("sasaguri.fukuoka.jp")];
+ char stringpool_str5622[sizeof("katsuragi.nara.jp")];
+ char stringpool_str5623[sizeof("sologne.museum")];
+ char stringpool_str5624[sizeof("nosegawa.nara.jp")];
+ char stringpool_str5625[sizeof("wajima.ishikawa.jp")];
+ char stringpool_str5626[sizeof("kommunalforbund.se")];
+ char stringpool_str5627[sizeof("trolley.museum")];
+ char stringpool_str5628[sizeof("yachimata.chiba.jp")];
+ char stringpool_str5629[sizeof("pomorskie.pl")];
+ char stringpool_str5630[sizeof("sosnowiec.pl")];
+ char stringpool_str5631[sizeof("matsuzaki.shizuoka.jp")];
+ char stringpool_str5632[sizeof("info.az")];
+ char stringpool_str5633[sizeof("xn--tnsberg-q1a.no")];
+ char stringpool_str5634[sizeof("web.lk")];
+ char stringpool_str5635[sizeof("xn--srreisa-q1a.no")];
+ char stringpool_str5636[sizeof("samegawa.fukushima.jp")];
+ char stringpool_str5637[sizeof("ngo.ph")];
+ char stringpool_str5638[sizeof("pasadena.museum")];
+ char stringpool_str5639[sizeof("iiyama.nagano.jp")];
+ char stringpool_str5640[sizeof("midsund.no")];
+ char stringpool_str5641[sizeof("fetsund.no")];
+ char stringpool_str5642[sizeof("aurskog-holand.no")];
+ char stringpool_str5643[sizeof("sekigahara.gifu.jp")];
+ char stringpool_str5644[sizeof("net.ly")];
+ char stringpool_str5645[sizeof("jfk.museum")];
+ char stringpool_str5646[sizeof("info.tz")];
+ char stringpool_str5647[sizeof("alesund.no")];
+ char stringpool_str5648[sizeof("ishikawa.jp")];
+ char stringpool_str5649[sizeof("zaporizhzhia.ua")];
+ char stringpool_str5650[sizeof("net.mv")];
+ char stringpool_str5651[sizeof("civilization.museum")];
+ char stringpool_str5652[sizeof("yuasa.wakayama.jp")];
+ char stringpool_str5653[sizeof("photography.museum")];
+ char stringpool_str5654[sizeof("lucerne.museum")];
+ char stringpool_str5655[sizeof("farsund.no")];
+ char stringpool_str5656[sizeof("org.py")];
+ char stringpool_str5657[sizeof("unjarga.no")];
+ char stringpool_str5658[sizeof("group.aero")];
+ char stringpool_str5659[sizeof("zachpomor.pl")];
+ char stringpool_str5660[sizeof("mitou.yamaguchi.jp")];
+ char stringpool_str5661[sizeof("xn--od0alg.cn")];
+ char stringpool_str5662[sizeof("matsubushi.saitama.jp")];
+ char stringpool_str5663[sizeof("watchandclock.museum")];
+ char stringpool_str5664[sizeof("accident-investigation.aero")];
+ char stringpool_str5665[sizeof("xn--ygbi2ammx")];
+ char stringpool_str5666[sizeof("wallonie.museum")];
+ char stringpool_str5667[sizeof("wegrow.pl")];
+ char stringpool_str5668[sizeof("ind.gt")];
+ char stringpool_str5669[sizeof("int.pt")];
+ char stringpool_str5670[sizeof("xn--yfro4i67o")];
+ char stringpool_str5671[sizeof("ibigawa.gifu.jp")];
+ char stringpool_str5672[sizeof("xn--vre-eiker-k8a.no")];
+ char stringpool_str5673[sizeof("namegawa.saitama.jp")];
+ char stringpool_str5674[sizeof("parti.se")];
+ char stringpool_str5675[sizeof("endoftheinternet.org")];
+ char stringpool_str5676[sizeof("int.rw")];
+ char stringpool_str5677[sizeof("xn--aroport-bya.ci")];
+ char stringpool_str5678[sizeof("xn--ystre-slidre-ujb.no")];
+ char stringpool_str5679[sizeof("niiza.saitama.jp")];
+ char stringpool_str5680[sizeof("chikuho.fukuoka.jp")];
+ char stringpool_str5681[sizeof("webhop.info")];
+ char stringpool_str5682[sizeof("nanbu.tottori.jp")];
+ char stringpool_str5683[sizeof("xn--ygarden-p1a.no")];
+ char stringpool_str5684[sizeof("sukagawa.fukushima.jp")];
+ char stringpool_str5685[sizeof("xn--osyro-wua.no")];
+ char stringpool_str5686[sizeof("press.ma")];
+ char stringpool_str5687[sizeof("georgia.museum")];
+ char stringpool_str5688[sizeof("uonuma.niigata.jp")];
+ char stringpool_str5689[sizeof("press.museum")];
+ char stringpool_str5690[sizeof("tanagura.fukushima.jp")];
+ char stringpool_str5691[sizeof("int.mw")];
+ char stringpool_str5692[sizeof("yokoshibahikari.chiba.jp")];
+ char stringpool_str5693[sizeof("pesaro-urbino.it")];
+ char stringpool_str5694[sizeof("mitsue.nara.jp")];
+ char stringpool_str5695[sizeof("naval.museum")];
+ char stringpool_str5696[sizeof("nahari.kochi.jp")];
+ char stringpool_str5697[sizeof("xn--avery-yua.no")];
+ char stringpool_str5698[sizeof("xn--od0alg.hk")];
+ char stringpool_str5699[sizeof("katsushika.tokyo.jp")];
+ char stringpool_str5700[sizeof("otsuki.kochi.jp")];
+ char stringpool_str5701[sizeof("jewelry.museum")];
+ char stringpool_str5702[sizeof("nrw.museum")];
+ char stringpool_str5703[sizeof("takarazuka.hyogo.jp")];
+ char stringpool_str5704[sizeof("matsuura.nagasaki.jp")];
+ char stringpool_str5705[sizeof("romskog.no")];
+ char stringpool_str5706[sizeof("ino.kochi.jp")];
+ char stringpool_str5707[sizeof("panama.museum")];
+ char stringpool_str5708[sizeof("web.pk")];
+ char stringpool_str5709[sizeof("um.gov.pl")];
+ char stringpool_str5710[sizeof("namegata.ibaraki.jp")];
+ char stringpool_str5711[sizeof("isa-hockeynut.com")];
+ char stringpool_str5712[sizeof("xn--eveni-0qa01ga.no")];
+ char stringpool_str5713[sizeof("marugame.kagawa.jp")];
+ char stringpool_str5714[sizeof("is-saved.org")];
+ char stringpool_str5715[sizeof("experts-comptables.fr")];
+ char stringpool_str5716[sizeof("paderborn.museum")];
+ char stringpool_str5717[sizeof("net.py")];
+ char stringpool_str5718[sizeof("net.gy")];
+ char stringpool_str5719[sizeof("komvux.se")];
+ char stringpool_str5720[sizeof("zakopane.pl")];
+ char stringpool_str5721[sizeof("torsken.no")];
+ char stringpool_str5722[sizeof("isa-geek.net")];
+ char stringpool_str5723[sizeof("matsukawa.nagano.jp")];
+ char stringpool_str5724[sizeof("eidskog.no")];
+ char stringpool_str5725[sizeof("ontario.museum")];
+ char stringpool_str5726[sizeof("fujikawaguchiko.yamanashi.jp")];
+ char stringpool_str5727[sizeof("makurazaki.kagoshima.jp")];
+ char stringpool_str5728[sizeof("isa.kagoshima.jp")];
+ char stringpool_str5729[sizeof("isa-geek.org")];
+ char stringpool_str5730[sizeof("ascolipiceno.it")];
+ char stringpool_str5731[sizeof("xn--hobl-ira.no")];
+ char stringpool_str5732[sizeof("notogawa.shiga.jp")];
+ char stringpool_str5733[sizeof("kasugai.aichi.jp")];
+ char stringpool_str5734[sizeof("fuchu.tokyo.jp")];
+ char stringpool_str5735[sizeof("mutsu.aomori.jp")];
+ char stringpool_str5736[sizeof("matsumoto.nagano.jp")];
+ char stringpool_str5737[sizeof("nanyo.yamagata.jp")];
+ char stringpool_str5738[sizeof("umaji.kochi.jp")];
+ char stringpool_str5739[sizeof("nakagawa.tokushima.jp")];
+ char stringpool_str5740[sizeof("konskowola.pl")];
+ char stringpool_str5741[sizeof("kursk.ru")];
+ char stringpool_str5742[sizeof("ivanovo.ru")];
+ char stringpool_str5743[sizeof("presidio.museum")];
+ char stringpool_str5744[sizeof("ishikari.hokkaido.jp")];
+ char stringpool_str5745[sizeof("yachiyo.ibaraki.jp")];
+ char stringpool_str5746[sizeof("tomsk.ru")];
+ char stringpool_str5747[sizeof("nakagawa.nagano.jp")];
+ char stringpool_str5748[sizeof("westfalen.museum")];
+ char stringpool_str5749[sizeof("gotsu.shimane.jp")];
+ char stringpool_str5750[sizeof("uto.kumamoto.jp")];
+ char stringpool_str5751[sizeof("wajiki.tokushima.jp")];
+ char stringpool_str5752[sizeof("neyagawa.osaka.jp")];
+ char stringpool_str5753[sizeof("yamagata.jp")];
+ char stringpool_str5754[sizeof("pesarourbino.it")];
+ char stringpool_str5755[sizeof("xn--nvuotna-hwa.no")];
+ char stringpool_str5756[sizeof("matsushige.tokushima.jp")];
+ char stringpool_str5757[sizeof("production.aero")];
+ char stringpool_str5758[sizeof("ing.pa")];
+ char stringpool_str5759[sizeof("polkowice.pl")];
+ char stringpool_str5760[sizeof("xn--90a3ac")];
+ char stringpool_str5761[sizeof("nakagawa.hokkaido.jp")];
+ char stringpool_str5762[sizeof("uw.gov.pl")];
+ char stringpool_str5763[sizeof("ube.yamaguchi.jp")];
+ char stringpool_str5764[sizeof("wazuka.kyoto.jp")];
+ char stringpool_str5765[sizeof("wodzislaw.pl")];
+ char stringpool_str5766[sizeof("yasuda.kochi.jp")];
+ char stringpool_str5767[sizeof("xn--vler-qoa.xn--stfold-9xa.no")];
+ char stringpool_str5768[sizeof("mitsuke.niigata.jp")];
+ char stringpool_str5769[sizeof("niyodogawa.kochi.jp")];
+ char stringpool_str5770[sizeof("pharmacy.museum")];
+ char stringpool_str5771[sizeof("waw.pl")];
+ char stringpool_str5772[sizeof("zaporizhzhe.ua")];
+ char stringpool_str5773[sizeof("xn--yer-zna.no")];
+ char stringpool_str5774[sizeof("ascoli-piceno.it")];
+ char stringpool_str5775[sizeof("yasaka.nagano.jp")];
+ char stringpool_str5776[sizeof("xn--holtlen-hxa.no")];
+ char stringpool_str5777[sizeof("xn--fl-zia.no")];
+ char stringpool_str5778[sizeof("yonago.tottori.jp")];
+ char stringpool_str5779[sizeof("naturhistorisches.museum")];
+ char stringpool_str5780[sizeof("pp.ru")];
+ char stringpool_str5781[sizeof("in-the-band.net")];
+ char stringpool_str5782[sizeof("xn--fiqs8s")];
+ char stringpool_str5783[sizeof("slask.pl")];
+ char stringpool_str5784[sizeof("xn--hpmir-xqa.no")];
+ char stringpool_str5785[sizeof("pomorze.pl")];
+ char stringpool_str5786[sizeof("szczytno.pl")];
+ char stringpool_str5787[sizeof("wanouchi.gifu.jp")];
+ char stringpool_str5788[sizeof("inf.mk")];
+ char stringpool_str5789[sizeof("matsumoto.kagoshima.jp")];
+ char stringpool_str5790[sizeof("historyofscience.museum")];
+ char stringpool_str5791[sizeof("yosemite.museum")];
+ char stringpool_str5792[sizeof("minamiechizen.fukui.jp")];
+ char stringpool_str5793[sizeof("uki.kumamoto.jp")];
+ char stringpool_str5794[sizeof("lutsk.ua")];
+ char stringpool_str5795[sizeof("yamaga.kumamoto.jp")];
+ char stringpool_str5796[sizeof("matsuno.ehime.jp")];
+ char stringpool_str5797[sizeof("prd.km")];
+ char stringpool_str5798[sizeof("xn--fiqz9s")];
+ char stringpool_str5799[sizeof("monzaedellabrianza.it")];
+ char stringpool_str5800[sizeof("xn--hery-ira.nordland.no")];
+ char stringpool_str5801[sizeof("shizuoka.jp")];
+ char stringpool_str5802[sizeof("pisz.pl")];
+ char stringpool_str5803[sizeof("satsumasendai.kagoshima.jp")];
+ char stringpool_str5804[sizeof("matsushima.miyagi.jp")];
+ char stringpool_str5805[sizeof("yahiko.niigata.jp")];
+ char stringpool_str5806[sizeof("ug.gov.pl")];
+ char stringpool_str5807[sizeof("yasugi.shimane.jp")];
+ char stringpool_str5808[sizeof("itako.ibaraki.jp")];
+ char stringpool_str5809[sizeof("xn--3e0b707e")];
+ char stringpool_str5810[sizeof("s3-eu-west-1.amazonaws.com")];
+ char stringpool_str5811[sizeof("ishikawa.okinawa.jp")];
+ char stringpool_str5812[sizeof("iizuka.fukuoka.jp")];
+ char stringpool_str5813[sizeof("ikaruga.nara.jp")];
+ char stringpool_str5814[sizeof("insurance.aero")];
+ char stringpool_str5815[sizeof("yukuhashi.fukuoka.jp")];
+ char stringpool_str5816[sizeof("matsuyama.ehime.jp")];
+ char stringpool_str5817[sizeof("itakura.gunma.jp")];
+ char stringpool_str5818[sizeof("katsuragi.wakayama.jp")];
+ char stringpool_str5819[sizeof("yamal.ru")];
+ char stringpool_str5820[sizeof("vanylven.no")];
+ char stringpool_str5821[sizeof("yabuki.fukushima.jp")];
+ char stringpool_str5822[sizeof("nanbu.yamanashi.jp")];
+ char stringpool_str5823[sizeof("xn--h-2fa.no")];
+ char stringpool_str5824[sizeof("int.mv")];
+ char stringpool_str5825[sizeof("yatomi.aichi.jp")];
+ char stringpool_str5826[sizeof("wroclaw.pl")];
+ char stringpool_str5827[sizeof("xn--flor-jra.no")];
+ char stringpool_str5828[sizeof("wlocl.pl")];
+ char stringpool_str5829[sizeof("xn--mgb2ddes")];
+ char stringpool_str5830[sizeof("xn--frna-woa.no")];
+ char stringpool_str5831[sizeof("org.lv")];
+ char stringpool_str5832[sizeof("yamaguchi.jp")];
+ char stringpool_str5833[sizeof("nationalfirearms.museum")];
+ char stringpool_str5834[sizeof("per.la")];
+ char stringpool_str5835[sizeof("chippubetsu.hokkaido.jp")];
+ char stringpool_str5836[sizeof("xn--od0aq3b.hk")];
+ char stringpool_str5837[sizeof("s3.amazonaws.com")];
+ char stringpool_str5838[sizeof("xn--fjord-lra.no")];
+ char stringpool_str5839[sizeof("kisarazu.chiba.jp")];
+ char stringpool_str5840[sizeof("palermo.it")];
+ char stringpool_str5841[sizeof("tatsuno.hyogo.jp")];
+ char stringpool_str5842[sizeof("nakagusuku.okinawa.jp")];
+ char stringpool_str5843[sizeof("ushiku.ibaraki.jp")];
+ char stringpool_str5844[sizeof("watarai.mie.jp")];
+ char stringpool_str5845[sizeof("xn--hcesuolo-7ya35b.no")];
+ char stringpool_str5846[sizeof("cc.sd.us")];
+ char stringpool_str5847[sizeof("yamagata.nagano.jp")];
+ char stringpool_str5848[sizeof("war.museum")];
+ char stringpool_str5849[sizeof("national-library-scotland.uk")];
+ char stringpool_str5850[sizeof("po.gov.pl")];
+ char stringpool_str5851[sizeof("uchihara.ibaraki.jp")];
+ char stringpool_str5852[sizeof("newyork.museum")];
+ char stringpool_str5853[sizeof("otoyo.kochi.jp")];
+ char stringpool_str5854[sizeof("portland.museum")];
+ char stringpool_str5855[sizeof("xn--rland-uua.no")];
+ char stringpool_str5856[sizeof("okazaki.aichi.jp")];
+ char stringpool_str5857[sizeof("xn--frya-hra.no")];
+ char stringpool_str5858[sizeof("pa.gov.pl")];
+ char stringpool_str5859[sizeof("xn--mely-ira.no")];
+ char stringpool_str5860[sizeof("pharmacien.fr")];
+ char stringpool_str5861[sizeof("xn--finny-yua.no")];
+ char stringpool_str5862[sizeof("xn--rady-ira.no")];
+ char stringpool_str5863[sizeof("ulvik.no")];
+ char stringpool_str5864[sizeof("yamagata.ibaraki.jp")];
+ char stringpool_str5865[sizeof("net.lv")];
+ char stringpool_str5866[sizeof("xn--rdal-poa.no")];
+ char stringpool_str5867[sizeof("xn--risr-ira.no")];
+ char stringpool_str5868[sizeof("yonaguni.okinawa.jp")];
+ char stringpool_str5869[sizeof("kouzushima.tokyo.jp")];
+ char stringpool_str5870[sizeof("is-not-certified.com")];
+ char stringpool_str5871[sizeof("ishigaki.okinawa.jp")];
+ char stringpool_str5872[sizeof("tatsuno.nagano.jp")];
+ char stringpool_str5873[sizeof("is-a-chef.com")];
+ char stringpool_str5874[sizeof("xn--hnefoss-q1a.no")];
+ char stringpool_str5875[sizeof("shizuoka.shizuoka.jp")];
+ char stringpool_str5876[sizeof("yamada.iwate.jp")];
+ char stringpool_str5877[sizeof("pvt.ge")];
+ char stringpool_str5878[sizeof("xn--stre-toten-zcb.no")];
+ char stringpool_str5879[sizeof("futsu.nagasaki.jp")];
+ char stringpool_str5880[sizeof("xn--mk0axi.hk")];
+ char stringpool_str5881[sizeof("xn--merker-kua.no")];
+ char stringpool_str5882[sizeof("accident-prevention.aero")];
+ char stringpool_str5883[sizeof("kihoku.ehime.jp")];
+ char stringpool_str5884[sizeof("itoigawa.niigata.jp")];
+ char stringpool_str5885[sizeof("xn--risa-5na.no")];
+ char stringpool_str5886[sizeof("pro.pr")];
+ char stringpool_str5887[sizeof("ide.kyoto.jp")];
+ char stringpool_str5888[sizeof("prochowice.pl")];
+ char stringpool_str5889[sizeof("plo.ps")];
+ char stringpool_str5890[sizeof("yamakita.kanagawa.jp")];
+ char stringpool_str5891[sizeof("masaki.ehime.jp")];
+ char stringpool_str5892[sizeof("naoshima.kagawa.jp")];
+ char stringpool_str5893[sizeof("xn--rennesy-v1a.no")];
+ char stringpool_str5894[sizeof("ulm.museum")];
+ char stringpool_str5895[sizeof("penza.ru")];
+ char stringpool_str5896[sizeof("ine.kyoto.jp")];
+ char stringpool_str5897[sizeof("xn--mgbaam7a8h")];
+ char stringpool_str5898[sizeof("usa.museum")];
+ char stringpool_str5899[sizeof("xn--muost-0qa.no")];
+ char stringpool_str5900[sizeof("mutsuzawa.chiba.jp")];
+ char stringpool_str5901[sizeof("porsanger.no")];
+ char stringpool_str5902[sizeof("xn--mjndalen-64a.no")];
+ char stringpool_str5903[sizeof("naturalsciences.museum")];
+ char stringpool_str5904[sizeof("xn--drbak-wua.no")];
+ char stringpool_str5905[sizeof("iwanuma.miyagi.jp")];
+ char stringpool_str5906[sizeof("xn--indery-fya.no")];
+ char stringpool_str5907[sizeof("xn--dnna-gra.no")];
+ char stringpool_str5908[sizeof("taishin.fukushima.jp")];
+ char stringpool_str5909[sizeof("matsuda.kanagawa.jp")];
+ char stringpool_str5910[sizeof("noboribetsu.hokkaido.jp")];
+ char stringpool_str5911[sizeof("yamagata.gifu.jp")];
+ char stringpool_str5912[sizeof("katsuura.chiba.jp")];
+ char stringpool_str5913[sizeof("yakage.okayama.jp")];
+ char stringpool_str5914[sizeof("pharmaciens.km")];
+ char stringpool_str5915[sizeof("is-a-chef.net")];
+ char stringpool_str5916[sizeof("plaza.museum")];
+ char stringpool_str5917[sizeof("xn--msy-ula0h.no")];
+ char stringpool_str5918[sizeof("pvt.k12.ma.us")];
+ char stringpool_str5919[sizeof("xn--hylandet-54a.no")];
+ char stringpool_str5920[sizeof("scrapping.cc")];
+ char stringpool_str5921[sizeof("xn--uc0atv.hk")];
+ char stringpool_str5922[sizeof("xn--rskog-uua.no")];
+ char stringpool_str5923[sizeof("urayasu.chiba.jp")];
+ char stringpool_str5924[sizeof("kaszuby.pl")];
+ char stringpool_str5925[sizeof("xn--rholt-mra.no")];
+ char stringpool_str5926[sizeof("xn--uc0atv.tw")];
+ char stringpool_str5927[sizeof("xn--mlatvuopmi-s4a.no")];
+ char stringpool_str5928[sizeof("xn--mgba3a4fra")];
+ char stringpool_str5929[sizeof("kaizuka.osaka.jp")];
+ char stringpool_str5930[sizeof("xn--mgba3a4f16a")];
+ char stringpool_str5931[sizeof("irc.pl")];
+ char stringpool_str5932[sizeof("xn--rst-0na.no")];
+ char stringpool_str5933[sizeof("watch-and-clock.museum")];
+ char stringpool_str5934[sizeof("xn--rde-ula.no")];
+ char stringpool_str5935[sizeof("xn--mgba3a4fra.ir")];
+ char stringpool_str5936[sizeof("xn--mgba3a4f16a.ir")];
+ char stringpool_str5937[sizeof("ostrowiec.pl")];
+ char stringpool_str5938[sizeof("xn--rros-gra.no")];
+ char stringpool_str5939[sizeof("xn--h2brj9c")];
+ char stringpool_str5940[sizeof("chizu.tottori.jp")];
+ char stringpool_str5941[sizeof("org.mx")];
+ char stringpool_str5942[sizeof("urasoe.okinawa.jp")];
+ char stringpool_str5943[sizeof("xn--mgb9awbf")];
+ char stringpool_str5944[sizeof("xn--mot-tla.no")];
+ char stringpool_str5945[sizeof("yamagata.yamagata.jp")];
+ char stringpool_str5946[sizeof("uscountryestate.museum")];
+ char stringpool_str5947[sizeof("uji.kyoto.jp")];
+ char stringpool_str5948[sizeof("xn--mgbayh7gpa")];
+ char stringpool_str5949[sizeof("portlligat.museum")];
+ char stringpool_str5950[sizeof("inashiki.ibaraki.jp")];
+ char stringpool_str5951[sizeof("oharu.aichi.jp")];
+ char stringpool_str5952[sizeof("maizuru.kyoto.jp")];
+ char stringpool_str5953[sizeof("isa-geek.com")];
+ char stringpool_str5954[sizeof("xn--nttery-byae.no")];
+ char stringpool_str5955[sizeof("xn--dyry-ira.no")];
+ char stringpool_str5956[sizeof("nyc.museum")];
+ char stringpool_str5957[sizeof("ichihara.chiba.jp")];
+ char stringpool_str5958[sizeof("xn--rlingen-mxa.no")];
+ char stringpool_str5959[sizeof("matsumae.hokkaido.jp")];
+ char stringpool_str5960[sizeof("pro.mv")];
+ char stringpool_str5961[sizeof("iamallama.com")];
+ char stringpool_str5962[sizeof("net.mx")];
+ char stringpool_str5963[sizeof("xn--kprw13d")];
+ char stringpool_str5964[sizeof("xn--kpry57d")];
+ char stringpool_str5965[sizeof("nagakute.aichi.jp")];
+ char stringpool_str5966[sizeof("s3-ap-northeast-1.amazonaws.com")];
+ char stringpool_str5967[sizeof("pubol.museum")];
+ char stringpool_str5968[sizeof("usculture.museum")];
+ char stringpool_str5969[sizeof("perugia.it")];
+ char stringpool_str5970[sizeof("xn--unjrga-rta.no")];
+ char stringpool_str5971[sizeof("xn--davvenjrga-y4a.no")];
+ char stringpool_str5972[sizeof("shizukuishi.iwate.jp")];
+ char stringpool_str5973[sizeof("s3-ap-southeast-1.amazonaws.com")];
+ char stringpool_str5974[sizeof("press.aero")];
+ char stringpool_str5975[sizeof("press.se")];
+ char stringpool_str5976[sizeof("wildlife.museum")];
+ char stringpool_str5977[sizeof("xn--hery-ira.xn--mre-og-romsdal-qqb.no")];
+ char stringpool_str5978[sizeof("xn--krehamn-dxa.no")];
+ char stringpool_str5979[sizeof("xn--hyanger-q1a.no")];
+ char stringpool_str5980[sizeof("xn--lury-ira.no")];
+ char stringpool_str5981[sizeof("xn--mli-tla.no")];
+ char stringpool_str5982[sizeof("posts-and-telecommunications.museum")];
+ char stringpool_str5983[sizeof("perso.tn")];
+ char stringpool_str5984[sizeof("iwakura.aichi.jp")];
+ char stringpool_str5985[sizeof("pippu.hokkaido.jp")];
+ char stringpool_str5986[sizeof("nakagyo.kyoto.jp")];
+ char stringpool_str5987[sizeof("xn--p1ai")];
+ char stringpool_str5988[sizeof("xn--klbu-woa.no")];
+ char stringpool_str5989[sizeof("xn--hbmer-xqa.no")];
+ char stringpool_str5990[sizeof("wloclawek.pl")];
+ char stringpool_str5991[sizeof("xn--linds-pra.no")];
+ char stringpool_str5992[sizeof("nakagawa.fukuoka.jp")];
+ char stringpool_str5993[sizeof("s3-ap-southeast-2.amazonaws.com")];
+ char stringpool_str5994[sizeof("xn--lesund-hua.no")];
+ char stringpool_str5995[sizeof("perso.sn")];
+ char stringpool_str5996[sizeof("xn--kfjord-iua.no")];
+ char stringpool_str5997[sizeof("xn--fpcrj9c3d")];
+ char stringpool_str5998[sizeof("perso.ht")];
+ char stringpool_str5999[sizeof("xn--krdsherad-m8a.no")];
+ char stringpool_str6000[sizeof("xn--clchc0ea0b2g2a9gcd")];
+ char stringpool_str6001[sizeof("xn--oppegrd-ixa.no")];
+ char stringpool_str6002[sizeof("xn--pgbs0dh")];
+ char stringpool_str6003[sizeof("ishikawa.fukushima.jp")];
+ char stringpool_str6004[sizeof("xn--hgebostad-g3a.no")];
+ char stringpool_str6005[sizeof("utashinai.hokkaido.jp")];
+ char stringpool_str6006[sizeof("xn--lrenskog-54a.no")];
+ char stringpool_str6007[sizeof("xn--lrdal-sra.no")];
+ char stringpool_str6008[sizeof("xn--54b7fta0cc")];
+ char stringpool_str6009[sizeof("pc.pl")];
+ char stringpool_str6010[sizeof("appspot.com")];
+ char stringpool_str6011[sizeof("iwafune.tochigi.jp")];
+ char stringpool_str6012[sizeof("yatsushiro.kumamoto.jp")];
+ char stringpool_str6013[sizeof("yotsukaido.chiba.jp")];
+ char stringpool_str6014[sizeof("xn--lten-gra.no")];
+ char stringpool_str6015[sizeof("is-a-cubicle-slave.com")];
+ char stringpool_str6016[sizeof("xn--karmy-yua.no")];
+ char stringpool_str6017[sizeof("xn--gls-elac.no")];
+ char stringpool_str6018[sizeof("ichikawa.hyogo.jp")];
+ char stringpool_str6019[sizeof("xn--l-1fa.no")];
+ char stringpool_str6020[sizeof("xn--krager-gya.no")];
+ char stringpool_str6021[sizeof("xn--koluokta-7ya57h.no")];
+ char stringpool_str6022[sizeof("xn--ostery-fya.no")];
+ char stringpool_str6023[sizeof("xn--mgbbh1a71e")];
+ char stringpool_str6024[sizeof("xn--uc0ay4a.hk")];
+ char stringpool_str6025[sizeof("xn--langevg-jxa.no")];
+ char stringpool_str6026[sizeof("hatsukaichi.hiroshima.jp")];
+ char stringpool_str6027[sizeof("katsuyama.fukui.jp")];
+ char stringpool_str6028[sizeof("ichikawamisato.yamanashi.jp")];
+ char stringpool_str6029[sizeof("xn--h1aegh.museum")];
+ char stringpool_str6030[sizeof("xn--gmqw5a.hk")];
+ char stringpool_str6031[sizeof("nisshin.aichi.jp")];
+ char stringpool_str6032[sizeof("xn--rdy-0nab.no")];
+ char stringpool_str6033[sizeof("xn--lhppi-xqa.no")];
+ char stringpool_str6034[sizeof("natuurwetenschappen.museum")];
+ char stringpool_str6035[sizeof("xn--givuotna-8ya.no")];
+ char stringpool_str6036[sizeof("xn--gjvik-wua.no")];
+ char stringpool_str6037[sizeof("yanagawa.fukuoka.jp")];
+ char stringpool_str6038[sizeof("xn--leagaviika-52b.no")];
+ char stringpool_str6039[sizeof("xn--krjohka-hwab49j.no")];
+ char stringpool_str6040[sizeof("xn--frde-gra.no")];
+ char stringpool_str6041[sizeof("xn--rsta-fra.no")];
+ char stringpool_str6042[sizeof("xn--gildeskl-g0a.no")];
+ char stringpool_str6043[sizeof("xn--mgbc0a9azcg")];
+ char stringpool_str6044[sizeof("xn--lcvr32d.hk")];
+ char stringpool_str6045[sizeof("xn--mgberp4a5d4ar")];
+ char stringpool_str6046[sizeof("la-spezia.it")];
+ char stringpool_str6047[sizeof("xn--mlselv-iua.no")];
+ char stringpool_str6048[sizeof("xn--moreke-jua.no")];
+ char stringpool_str6049[sizeof("paragliding.aero")];
+ char stringpool_str6050[sizeof("podzone.net")];
+ char stringpool_str6051[sizeof("yachiyo.chiba.jp")];
+ char stringpool_str6052[sizeof("xn--ldingen-q1a.no")];
+ char stringpool_str6053[sizeof("xn--mgbqly7cvafr")];
+ char stringpool_str6054[sizeof("xn--lns-qla.museum")];
+ char stringpool_str6055[sizeof("yatsuka.shimane.jp")];
+ char stringpool_str6056[sizeof("iwakuni.yamaguchi.jp")];
+ char stringpool_str6057[sizeof("xn--rmskog-bya.no")];
+ char stringpool_str6058[sizeof("xn--rhkkervju-01af.no")];
+ char stringpool_str6059[sizeof("xn--gmq050i.hk")];
+ char stringpool_str6060[sizeof("xn--ggaviika-8ya47h.no")];
+ char stringpool_str6061[sizeof("plc.ly")];
+ char stringpool_str6062[sizeof("inazawa.aichi.jp")];
+ char stringpool_str6063[sizeof("ichikawa.chiba.jp")];
+ char stringpool_str6064[sizeof("xn--fzc2c9e2c")];
+ char stringpool_str6065[sizeof("xn--mtta-vrjjat-k7af.no")];
+ char stringpool_str6066[sizeof("passenger-association.aero")];
+ char stringpool_str6067[sizeof("ureshino.mie.jp")];
+ char stringpool_str6068[sizeof("xn--mgberp4a5d4a87g")];
+ char stringpool_str6069[sizeof("xn--mosjen-eya.no")];
+ char stringpool_str6070[sizeof("podzone.org")];
+ char stringpool_str6071[sizeof("xn--correios-e-telecomunicaes-ghc29a.museum")];
+ char stringpool_str6072[sizeof("xn--ogbpf8fl")];
+ char stringpool_str6073[sizeof("xn--loabt-0qa.no")];
+ char stringpool_str6074[sizeof("matsudo.chiba.jp")];
+ char stringpool_str6075[sizeof("xn--lgbbat1ad8j")];
+ char stringpool_str6076[sizeof("pacific.museum")];
+ char stringpool_str6077[sizeof("xn--lgrd-poac.no")];
+ char stringpool_str6078[sizeof("xn--ksnes-uua.no")];
+ char stringpool_str6079[sizeof("xn--gecrj9c")];
+ char stringpool_str6080[sizeof("xn--ryken-vua.no")];
+ char stringpool_str6081[sizeof("intelligence.museum")];
+ char stringpool_str6082[sizeof("xn--hmmrfeasta-s4ac.no")];
+ char stringpool_str6083[sizeof("xn--kvfjord-nxa.no")];
+ char stringpool_str6084[sizeof("xn--ryrvik-bya.no")];
+ char stringpool_str6085[sizeof("xn--lt-liac.no")];
+ char stringpool_str6086[sizeof("newspaper.museum")];
+ char stringpool_str6087[sizeof("porsgrunn.no")];
+ char stringpool_str6088[sizeof("is-slick.com")];
+ char stringpool_str6089[sizeof("xn--laheadju-7ya.no")];
+ char stringpool_str6090[sizeof("ichikai.tochigi.jp")];
+ char stringpool_str6091[sizeof("xn--mgbtf8fl")];
+ char stringpool_str6092[sizeof("xn--kvnangen-k0a.no")];
+ char stringpool_str6093[sizeof("xn--kranghke-b0a.no")];
+ char stringpool_str6094[sizeof("yaizu.shizuoka.jp")];
+ char stringpool_str6095[sizeof("imizu.toyama.jp")];
+ char stringpool_str6096[sizeof("xn--mgbqly7c0a67fbc")];
+ char stringpool_str6097[sizeof("xn--porsgu-sta26f.no")];
+ char stringpool_str6098[sizeof("xn--mxtq1m.hk")];
+ char stringpool_str6099[sizeof("unazuki.toyama.jp")];
+ char stringpool_str6100[sizeof("xn--kvitsy-fya.no")];
+ char stringpool_str6101[sizeof("utazu.kagawa.jp")];
+ char stringpool_str6102[sizeof("uchiko.ehime.jp")];
+ };
+static const struct stringpool_t stringpool_contents =
+ {
+ "gu",
+ "eu",
+ "gd",
+ "gov",
+ "co",
+ "cv",
+ "cu",
+ "gm",
+ "cd",
+ "edu",
+ "so",
+ "sv",
+ "su",
+ "ao",
+ "sd",
+ "au",
+ "cm",
+ "ad",
+ "ro",
+ "ru",
+ "cz",
+ "sm",
+ "com",
+ "am",
+ "sz",
+ "gov.to",
+ "gov.tm",
+ "az",
+ "edu.to",
+ "edu.tm",
+ "gov.bo",
+ "gov.bm",
+ "cx",
+ "edu.bo",
+ "edu.bm",
+ "sx",
+ "gov.ae",
+ "ax",
+ "com.to",
+ "com.tm",
+ "com.bo",
+ "com.bm",
+ "gov.bz",
+ "edu.bi",
+ "gr",
+ "er",
+ "edu.bz",
+ "gov.az",
+ "cr",
+ "edu.az",
+ "com.bi",
+ "co.ma",
+ "go.it",
+ "sr",
+ "com.bz",
+ "ar",
+ "com.ai",
+ "co.it",
+ "com.az",
+ "so.it",
+ "sv.it",
+ "ao.it",
+ "av.it",
+ "km",
+ "gov.qa",
+ "com.ag",
+ "ro.it",
+ "kz",
+ "cz.it",
+ "edu.qa",
+ "gov.ba",
+ "gov.sd",
+ "edu.ba",
+ "rm.it",
+ "edu.sd",
+ "com.qa",
+ "gt",
+ "et",
+ "com.ba",
+ "gov.bs",
+ "com.so",
+ "com.sd",
+ "edu.bs",
+ "gov.as",
+ "co.ca",
+ "st",
+ "at",
+ "gov.sg",
+ "gr.it",
+ "com.bs",
+ "edu.sg",
+ "kr",
+ "cr.it",
+ "co.ua",
+ "cv.ua",
+ "sr.it",
+ "com.sg",
+ "ar.it",
+ "gs",
+ "es",
+ "rv.ua",
+ "gov.ac",
+ "sm.ua",
+ "gov.sa",
+ "gov.jo",
+ "edu.ac",
+ "gov.je",
+ "edu.sa",
+ "edu.jo",
+ "as",
+ "ge",
+ "ee",
+ "rs",
+ "com.ac",
+ "com.sa",
+ "como.it",
+ "com.jo",
+ "se",
+ "ae",
+ "gov.bb",
+ "re",
+ "gu.us",
+ "ct.it",
+ "edu.bb",
+ "co.us",
+ "at.it",
+ "cr.ua",
+ "sd.us",
+ "com.bb",
+ "kr.it",
+ "gov.sc",
+ "kv.ua",
+ "edu.sc",
+ "az.us",
+ "km.ua",
+ "com.sc",
+ "cs.it",
+ "mo",
+ "mv",
+ "mu",
+ "md",
+ "ss.it",
+ "ge.it",
+ "mm",
+ "mz",
+ "ke",
+ "ce.it",
+ "gov.sb",
+ "edu.sb",
+ "aero",
+ "re.it",
+ "mx",
+ "ar.us",
+ "com.sb",
+ "kr.ua",
+ "to",
+ "tv",
+ "gov.au",
+ "educ.ar",
+ "td",
+ "lv",
+ "lu",
+ "edu.au",
+ "tm",
+ "tz",
+ "mr",
+ "com.au",
+ "g.se",
+ "e.se",
+ "gov.co",
+ "gov.cm",
+ "gov.cd",
+ "rome.it",
+ "edu.co",
+ "c.se",
+ "mo.it",
+ "gov.tt",
+ "s.se",
+ "ct.us",
+ "a.se",
+ "edu.tt",
+ "gov.bt",
+ "com.co",
+ "r.se",
+ "edu.ci",
+ "edu.bt",
+ "com.tt",
+ "com.ci",
+ "com.bt",
+ "tr",
+ "lr",
+ "jo",
+ "fo",
+ "mt",
+ "to.it",
+ "tv.it",
+ "lo.it",
+ "lu.it",
+ "jm",
+ "fm",
+ "as.us",
+ "coop",
+ "ks.ua",
+ "gi",
+ "gov.tn",
+ "k.se",
+ "ci",
+ "gov.st",
+ "gov.br",
+ "si",
+ "edu.st",
+ "aero.mv",
+ "ai",
+ "edu.br",
+ "ms",
+ "tt",
+ "lt",
+ "com.tn",
+ "co.tt",
+ "edu.an",
+ "com.st",
+ "com.br",
+ "adv.br",
+ "gn",
+ "me",
+ "fr",
+ "com.an",
+ "tr.it",
+ "cn",
+ "adm.br",
+ "sn",
+ "lv.ua",
+ "an",
+ "mt.it",
+ "ks.us",
+ "mo.us",
+ "gouv.km",
+ "md.us",
+ "fm.it",
+ "ls",
+ "do",
+ "ki",
+ "ci.it",
+ "edu.sn",
+ "dm",
+ "srv.br",
+ "si.it",
+ "dz",
+ "ms.it",
+ "ri.it",
+ "com.sn",
+ "lt.it",
+ "asso.km",
+ "gov.tl",
+ "asti.it",
+ "lodi.it",
+ "en.it",
+ "me.it",
+ "fr.it",
+ "kn",
+ "cn.it",
+ "gov.al",
+ "an.it",
+ "edu.al",
+ "coop.mv",
+ "rn.it",
+ "tx.us",
+ "gouv.sn",
+ "coop.km",
+ "tj",
+ "ts.it",
+ "com.al",
+ "co.na",
+ "ato.br",
+ "med.sd",
+ "je",
+ "gl",
+ "go.tj",
+ "te.it",
+ "asso.gp",
+ "le.it",
+ "cl",
+ "co.tj",
+ "lt.ua",
+ "sl",
+ "gouv.ml",
+ "m.se",
+ "al",
+ "gov.cu",
+ "mt.us",
+ "edu.cu",
+ "gov.sl",
+ "cn.ua",
+ "edu.sl",
+ "com.cu",
+ "com.sl",
+ "med.sa",
+ "art.br",
+ "fj",
+ "t.se",
+ "bo",
+ "tromso.no",
+ "l.se",
+ "bd",
+ "ms.us",
+ "ri.us",
+ "te.ua",
+ "bm",
+ "ga",
+ "bz",
+ "fe.it",
+ "ca",
+ "me.us",
+ "cl.it",
+ "sa",
+ "gov.tj",
+ "al.it",
+ "tm.mc",
+ "edu.tj",
+ "de",
+ "com.tj",
+ "gouv.bj",
+ "art.sn",
+ "br",
+ "mn",
+ "tromsa.no",
+ "f.se",
+ "gov.cn",
+ "bo.it",
+ "li",
+ "edu.cn",
+ "coop.br",
+ "bz.it",
+ "sb",
+ "com.cn",
+ "ca.it",
+ "dj",
+ "aero.tt",
+ "sa.it",
+ "asso.bj",
+ "mi.it",
+ "tn",
+ "ra.it",
+ "g.bg",
+ "e.bg",
+ "6.bg",
+ "2.bg",
+ "gob.bo",
+ "1.bg",
+ "9.bg",
+ "bt",
+ "8.bg",
+ "c.bg",
+ "7.bg",
+ "5.bg",
+ "4.bg",
+ "3.bg",
+ "co.ls",
+ "s.bg",
+ "0.bg",
+ "a.bg",
+ "r.bg",
+ "vu",
+ "br.it",
+ "mn.it",
+ "cat",
+ "fi",
+ "tur.br",
+ "trd.br",
+ "li.it",
+ "al.us",
+ "gov.ee",
+ "bs",
+ "cb.it",
+ "edu.ee",
+ "d.se",
+ "gov.cl",
+ "com.ee",
+ "be",
+ "tn.it",
+ "ml",
+ "co.me",
+ "gov.eg",
+ "ruovat.no",
+ "bt.it",
+ "k.bg",
+ "edu.eg",
+ "mx.na",
+ "gp",
+ "cim.br",
+ "mus.br",
+ "com.eg",
+ "tv.na",
+ "vv.it",
+ "ga.us",
+ "coop.tt",
+ "ab.ca",
+ "ca.us",
+ "jor.br",
+ "fi.it",
+ "med.br",
+ "tl",
+ "mi.us",
+ "sb.ua",
+ "rnu.tn",
+ "bj",
+ "bs.it",
+ "de.us",
+ "gv.at",
+ "edu.es",
+ "co.at",
+ "asso.re",
+ "ma",
+ "kurgan.ru",
+ "com.es",
+ "co.mw",
+ "gh",
+ "mn.us",
+ "teo.br",
+ "ch",
+ "vr.it",
+ "coop.mw",
+ "sh",
+ "gov.ec",
+ "fot.br",
+ "co.uz",
+ "kp",
+ "edu.ec",
+ "crew.aero",
+ "asn.au",
+ "sp.it",
+ "ap.it",
+ "ck",
+ "com.ec",
+ "sk",
+ "eti.br",
+ "la",
+ "tn.us",
+ "b.se",
+ "ve",
+ "gov.ws",
+ "edu.ws",
+ "jus.br",
+ "vt.it",
+ "com.ws",
+ "co.lc",
+ "cnt.br",
+ "kh",
+ "ch.it",
+ "m.bg",
+ "riik.ee",
+ "mobi",
+ "vs.it",
+ "lb",
+ "ens.tn",
+ "ta.it",
+ "firm.in",
+ "andebu.no",
+ "bi",
+ "ve.it",
+ "rns.tn",
+ "roma.it",
+ "mb.it",
+ "t.bg",
+ "l.bg",
+ "biz",
+ "bmd.br",
+ "tel",
+ "sk.ca",
+ "bn",
+ "dn.ua",
+ "jeju.kr",
+ "ck.ua",
+ "conf.lv",
+ "fst.br",
+ "mp",
+ "dr.na",
+ "mb.ca",
+ "vt.us",
+ "ma.us",
+ "bi.it",
+ "biz.az",
+ "co.ve",
+ "j.bg",
+ "f.bg",
+ "gov.ng",
+ "edu.ng",
+ "kh.ua",
+ "komi.ru",
+ "bn.it",
+ "conf.au",
+ "com.ng",
+ "ak.us",
+ "co.in",
+ "fl.us",
+ "la.us",
+ "mh",
+ "ltd.co.im",
+ "vi",
+ "gd.cn",
+ "mk",
+ "com.na",
+ "gz.cn",
+ "ca.na",
+ "sd.cn",
+ "go.tz",
+ "th",
+ "vn",
+ "gx.cn",
+ "co.tz",
+ "gouv.rw",
+ "jp",
+ "mobi.gp",
+ "tp.it",
+ "sx.cn",
+ "tk",
+ "d.bg",
+ "lk",
+ "mil",
+ "fnd.br",
+ "med.ee",
+ "vi.it",
+ "biz.bb",
+ "mil.to",
+ "mil.tm",
+ "go.pw",
+ "emp.br",
+ "mil.bo",
+ "ed.pw",
+ "eun.eg",
+ "ba",
+ "co.pw",
+ "bl.it",
+ "mil.ae",
+ "mil.tz",
+ "flog.br",
+ "eidfjord.no",
+ "res.aero",
+ "mil.az",
+ "fk",
+ "mil.qa",
+ "mk.ua",
+ "sirdal.no",
+ "mil.ba",
+ "bb",
+ "taxi.br",
+ "gs.cn",
+ "ba.it",
+ "vn.ua",
+ "rnrt.tn",
+ "med.ec",
+ "b.bg",
+ "vi.us",
+ "biz.tt",
+ "fhv.se",
+ "mil.ac",
+ "biz.at",
+ "mil.jo",
+ "lerdal.no",
+ "dk",
+ "va",
+ "lom.no",
+ "tas.au",
+ "taxi.aero",
+ "esp.br",
+ "suldal.no",
+ "asso.mc",
+ "alvdal.no",
+ "frog.museum",
+ "skodje.no",
+ "mo.cn",
+ "gov.nc.tr",
+ "mat.br",
+ "asia",
+ "rollag.no",
+ "c.la",
+ "bio.br",
+ "antiques.museum",
+ "eu.int",
+ "mobi.tt",
+ "dp.ua",
+ "gov.nr",
+ "lel.br",
+ "edu.nr",
+ "va.it",
+ "com.nr",
+ "assisi.museum",
+ "vet.br",
+ "co.pn",
+ "far.br",
+ "eid.no",
+ "tv.tz",
+ "v.bg",
+ "gob.cl",
+ "surnadal.no",
+ "blog.br",
+ "bh",
+ "bari.it",
+ "mil.co",
+ "test.tj",
+ "vb.it",
+ "tmp.br",
+ "asmatart.museum",
+ "show.aero",
+ "trento.it",
+ "jx.cn",
+ "gob.es",
+ "lindas.no",
+ "kvafjord.no",
+ "fuel.aero",
+ "game.tw",
+ "snoasa.no",
+ "va.us",
+ "gob.ec",
+ "sn.cn",
+ "fin.tn",
+ "gw",
+ "assn.lk",
+ "g12.br",
+ "mil.st",
+ "mil.br",
+ "gotdns.com",
+ "cw",
+ "me.tz",
+ "co.ae",
+ "aw",
+ "rw",
+ "gol.no",
+ "jondal.no",
+ "tree.museum",
+ "vlog.br",
+ "gov.bf",
+ "k12.ak.us",
+ "gov.af",
+ "vdonsk.ru",
+ "edu.af",
+ "k12.az.us",
+ "evenes.no",
+ "com.af",
+ "biz.tj",
+ "rindal.no",
+ "fhsk.se",
+ "js.cn",
+ "kw",
+ "tj.cn",
+ "kobe.jp",
+ "k12.sd.us",
+ "stuttgart.museum",
+ "mil.al",
+ "k12.as.us",
+ "fie.ee",
+ "arpa",
+ "juif.museum",
+ "gov.ve",
+ "jevnaker.no",
+ "edu.ve",
+ "fet.no",
+ "lib.ak.us",
+ "com.ve",
+ "kiev.ua",
+ "fj.cn",
+ "se.net",
+ "lib.az.us",
+ "asso.nc",
+ "com.vi",
+ "military.museum",
+ "sshn.se",
+ "crafts.museum",
+ "firm.nf",
+ "time.museum",
+ "lviv.ua",
+ "kustanai.ru",
+ "gaular.no",
+ "lib.sd.us",
+ "rendalen.no",
+ "roma.museum",
+ "meldal.no",
+ "k12.sc.us",
+ "lardal.no",
+ "sel.no",
+ "verdal.no",
+ "trogstad.no",
+ "lib.as.us",
+ "sor-aurdal.no",
+ "mil.tj",
+ "enna.it",
+ "mw",
+ "gov.vc",
+ "hu",
+ "edu.vc",
+ "hm",
+ "voagat.no",
+ "solund.no",
+ "com.vc",
+ "ln.cn",
+ "mil.cn",
+ "mobi.na",
+ "tw",
+ "muenster.museum",
+ "k12.co.us",
+ "hr",
+ "lib.sc.us",
+ "grimstad.no",
+ "lodingen.no",
+ "silk.museum",
+ "jpn.com",
+ "chieti.it",
+ "mari.ru",
+ "torino.it",
+ "k12.ca.us",
+ "ht",
+ "bievat.no",
+ "co.rs",
+ "k12.tn.us",
+ "mil.cl",
+ "test.ru",
+ "k12.ar.us",
+ "bj.cn",
+ "aip.ee",
+ "mil.eg",
+ "stalbans.museum",
+ "fosnes.no",
+ "lib.co.us",
+ "modena.it",
+ "gy",
+ "murmansk.ru",
+ "cy",
+ "farm.museum",
+ "sy",
+ "svalbard.no",
+ "moma.museum",
+ "sh.cn",
+ "ah.cn",
+ "rimini.it",
+ "gov.bh",
+ "gouv.fr",
+ "edu.bh",
+ "jl.cn",
+ "com.bh",
+ "lib.ca.us",
+ "crimea.ua",
+ "fin.ec",
+ "masfjorden.no",
+ "mil.ec",
+ "estate.museum",
+ "santacruz.museum",
+ "lib.tn.us",
+ "tuva.ru",
+ "gov.vn",
+ "ky",
+ "k12.al.us",
+ "asso.fr",
+ "edu.vn",
+ "lib.ar.us",
+ "luster.no",
+ "com.vn",
+ "evenassi.no",
+ "gov.sh",
+ "edunet.tn",
+ "mandal.no",
+ "snaase.no",
+ "com.sh",
+ "bergen.no",
+ "caa.aero",
+ "lib.ee",
+ "baseball.museum",
+ "h.se",
+ "varese.it",
+ "k12.ec",
+ "mine.nu",
+ "moskenes.no",
+ "k12.ct.us",
+ "coal.museum",
+ "bw",
+ "lib.al.us",
+ "venice.it",
+ "strand.no",
+ "beauxarts.museum",
+ "marker.no",
+ "freiburg.museum",
+ "maritimo.museum",
+ "suedtirol.it",
+ "gb.net",
+ "auto.pl",
+ "bindal.no",
+ "finearts.museum",
+ "mil.no",
+ "my",
+ "cadaques.museum",
+ "savona.it",
+ "hn",
+ "film.museum",
+ "gq",
+ "bern.museum",
+ "grue.no",
+ "amursk.ru",
+ "ky.us",
+ "reklam.hu",
+ "aq",
+ "lib.ct.us",
+ "ly",
+ "aure.no",
+ "kirkenes.no",
+ "tank.museum",
+ "rubtsovsk.ru",
+ "kvalsund.no",
+ "marine.ru",
+ "baidar.no",
+ "co.ba",
+ "fuettertdasnetz.de",
+ "ski.no",
+ "aq.it",
+ "retina.ar",
+ "torino.museum",
+ "casino.hu",
+ "trentino.it",
+ "hi.us",
+ "k12.wi.us",
+ "biz.nr",
+ "gouv.ht",
+ "bonn.museum",
+ "baikal.ru",
+ "fhs.no",
+ "lunner.no",
+ "k12.wa.us",
+ "amot.no",
+ "dinosaur.museum",
+ "asso.ht",
+ "travel",
+ "servebbs.net",
+ "rs.ba",
+ "dep.no",
+ "mq",
+ "coop.ht",
+ "berkeley.museum",
+ "lib.wi.us",
+ "exhibition.museum",
+ "museet.museum",
+ "eastcoast.museum",
+ "h.bg",
+ "karate.museum",
+ "lib.wa.us",
+ "research.museum",
+ "fla.no",
+ "from.hr",
+ "by",
+ "verona.it",
+ "farmstead.museum",
+ "dali.museum",
+ "tula.ru",
+ "k12.nm.us",
+ "k12.ne.us",
+ "k12.nd.us",
+ "rost.no",
+ "chel.ru",
+ "exeter.museum",
+ "com.nf",
+ "firm.co",
+ "sendai.jp",
+ "latina.it",
+ "sund.no",
+ "co.rw",
+ "amli.no",
+ "bodo.no",
+ "gg",
+ "eg",
+ "hurdal.no",
+ "jp.net",
+ "cg",
+ "hk",
+ "sg",
+ "ag",
+ "frei.no",
+ "lib.nm.us",
+ "lib.ne.us",
+ "lib.nd.us",
+ "mill.museum",
+ "k12.nc.us",
+ "arq.br",
+ "etne.no",
+ "living.museum",
+ "travel.tt",
+ "co.je",
+ "kg",
+ "ralingen.no",
+ "kvam.no",
+ "ag.it",
+ "rg.it",
+ "birkenes.no",
+ "mie.jp",
+ "gouv.ci",
+ "servebbs.org",
+ "amur.ru",
+ "bale.museum",
+ "aseral.no",
+ "rade.no",
+ "lib.nc.us",
+ "asso.ci",
+ "genova.it",
+ "e12.ve",
+ "mil.ve",
+ "broker.aero",
+ "time.no",
+ "firm.ro",
+ "garden.museum",
+ "krym.ua",
+ "berg.no",
+ "mg",
+ "divtasvuodna.no",
+ "lund.no",
+ "fortworth.museum",
+ "firm.ht",
+ "meland.no",
+ "k12.vi",
+ "drobak.no",
+ "flakstad.no",
+ "tg",
+ "tw.cn",
+ "mil.vc",
+ "levanger.no",
+ "smolensk.ru",
+ "sumoto.kumamoto.jp",
+ "krokstadelva.no",
+ "center.museum",
+ "deatnu.no",
+ "vik.no",
+ "biz.vn",
+ "creation.museum",
+ "chuvashia.ru",
+ "bill.museum",
+ "fjaler.no",
+ "durham.museum",
+ "co.bw",
+ "mining.museum",
+ "he.cn",
+ "lg.ua",
+ "fg.it",
+ "b.br",
+ "educator.aero",
+ "agr.br",
+ "roan.no",
+ "suli.hu",
+ "vercelli.it",
+ "research.aero",
+ "stat.no",
+ "vantaa.museum",
+ "club.aero",
+ "kviteseid.no",
+ "k12.nj.us",
+ "gran.no",
+ "e164.arpa",
+ "aviation.museum",
+ "capebreton.museum",
+ "kunisaki.oita.jp",
+ "krodsherad.no",
+ "tver.ru",
+ "mil.sh",
+ "town.museum",
+ "tysfjord.no",
+ "tysvar.no",
+ "k12.vi.us",
+ "landes.museum",
+ "lavangen.no",
+ "khakassia.ru",
+ "royken.no",
+ "dallas.museum",
+ "bg",
+ "brunel.museum",
+ "stpetersburg.museum",
+ "avocat.fr",
+ "lib.nj.us",
+ "k12.va.us",
+ "mosreg.ru",
+ "shop.pl",
+ "trader.aero",
+ "hi.cn",
+ "verran.no",
+ "audnedaln.no",
+ "amsterdam.museum",
+ "hu.net",
+ "hn.cn",
+ "bg.it",
+ "shop.ht",
+ "arna.no",
+ "lib.vi.us",
+ "cq.cn",
+ "fitjar.no",
+ "tingvoll.no",
+ "fusa.no",
+ "hol.no",
+ "evje-og-hornnes.no",
+ "lib.va.us",
+ "vg",
+ "sola.no",
+ "sula.no",
+ "hemnes.no",
+ "balestrand.no",
+ "surgeonshall.museum",
+ "arao.kumamoto.jp",
+ "benevento.it",
+ "siljan.no",
+ "bahn.museum",
+ "hl.cn",
+ "fyresdal.no",
+ "horten.no",
+ "eng.br",
+ "cng.br",
+ "kyiv.ua",
+ "milano.it",
+ "eisenbahn.museum",
+ "salangen.no",
+ "filatelia.museum",
+ "k12.vt.us",
+ "alta.no",
+ "ha.cn",
+ "guernsey.museum",
+ "collection.museum",
+ "leg.br",
+ "cheltenham.museum",
+ "losangeles.museum",
+ "kurotaki.nara.jp",
+ "mypets.ws",
+ "muenchen.museum",
+ "kuji.iwate.jp",
+ "club.tw",
+ "contemporary.museum",
+ "catanzaro.it",
+ "hb.cn",
+ "tinn.no",
+ "loabat.no",
+ "slg.br",
+ "lib.vt.us",
+ "siracusa.it",
+ "lancashire.museum",
+ "gjerstad.no",
+ "jorpeland.no",
+ "kirovograd.ua",
+ "tyumen.ru",
+ "holtalen.no",
+ "hokksund.no",
+ "kosa.kumamoto.jp",
+ "k-uralsk.ru",
+ "shop.hu",
+ "hk.cn",
+ "avoues.fr",
+ "vang.no",
+ "tananger.no",
+ "toon.ehime.jp",
+ "lierne.no",
+ "tysnes.no",
+ "film.hu",
+ "sandnessjoen.no",
+ "sigdal.no",
+ "mesaverde.museum",
+ "vennesla.no",
+ "mo-i-rana.no",
+ "mail.pl",
+ "rana.no",
+ "tono.iwate.jp",
+ "lyngen.no",
+ "steiermark.museum",
+ "slupsk.pl",
+ "rennesoy.no",
+ "sweden.museum",
+ "komono.mie.jp",
+ "geelvinck.museum",
+ "bokn.no",
+ "viking.museum",
+ "ragusa.it",
+ "matta-varjjat.no",
+ "giehtavuoatna.no",
+ "bolt.hu",
+ "kazo.saitama.jp",
+ "gdansk.pl",
+ "coop.py",
+ "fuji.shizuoka.jp",
+ "astrakhan.ru",
+ "tana.no",
+ "tobe.ehime.jp",
+ "meeres.museum",
+ "hellas.museum",
+ "riodejaneiro.museum",
+ "kamijima.ehime.jp",
+ "leka.no",
+ "bahccavuotna.no",
+ "cahcesuolo.no",
+ "arai.shizuoka.jp",
+ "hvaler.no",
+ "dyndns.tv",
+ "vindafjord.no",
+ "kalisz.pl",
+ "education.museum",
+ "lillesand.no",
+ "rakkestad.no",
+ "sauherad.no",
+ "likescandy.com",
+ "habmer.no",
+ "kuki.saitama.jp",
+ "halden.no",
+ "contemporaryart.museum",
+ "sorreisa.no",
+ "andasuolo.no",
+ "altoadige.it",
+ "toda.saitama.jp",
+ "gyeonggi.kr",
+ "gose.nara.jp",
+ "dyndns.ws",
+ "misasa.tottori.jp",
+ "joso.ibaraki.jp",
+ "jogasz.hu",
+ "sells-it.net",
+ "broke-it.net",
+ "sandefjord.no",
+ "syzran.ru",
+ "agents.aero",
+ "hole.no",
+ "fundacio.museum",
+ "zm",
+ "k12.nh.us",
+ "gov.im",
+ "gov.ie",
+ "gildeskal.no",
+ "donostia.museum",
+ "com.io",
+ "vgs.no",
+ "ando.nara.jp",
+ "kumatori.osaka.jp",
+ "go.ci",
+ "soni.nara.jp",
+ "santabarbara.museum",
+ "ed.ci",
+ "gov.is",
+ "co.ci",
+ "edu.is",
+ "journalism.museum",
+ "lib.nh.us",
+ "com.is",
+ "divttasvuotna.no",
+ "kawasaki.jp",
+ "blogspot.mx",
+ "mito.ibaraki.jp",
+ "blogspot.ro",
+ "lier.no",
+ "blogspot.mr",
+ "bilbao.museum",
+ "dagestan.ru",
+ "kumano.mie.jp",
+ "blogspot.td",
+ "tone.ibaraki.jp",
+ "kvinnherad.no",
+ "kvinesdal.no",
+ "zt.ua",
+ "elblag.pl",
+ "marnardal.no",
+ "karasjok.no",
+ "agro.pl",
+ "blogspot.de",
+ "francaise.museum",
+ "z.se",
+ "gov.it",
+ "edu.it",
+ "saga.jp",
+ "kunitomi.miyazaki.jp",
+ "ama.aichi.jp",
+ "co.vi",
+ "suzu.ishikawa.jp",
+ "kongsberg.no",
+ "md.ci",
+ "troandin.no",
+ "journalist.aero",
+ "blogspot.re",
+ "saintlouis.museum",
+ "gov.ir",
+ "gov.in",
+ "edu.in",
+ "television.museum",
+ "shinto.gunma.jp",
+ "misawa.aomori.jp",
+ "limanowa.pl",
+ "rakpetroleum.om",
+ "minnesota.museum",
+ "haugesund.no",
+ "blogspot.se",
+ "gyeongnam.kr",
+ "futtsu.chiba.jp",
+ "kurobe.toyama.jp",
+ "dell-ogliastra.it",
+ "soka.saitama.jp",
+ "salvadordali.museum",
+ "blogspot.no",
+ "gotdns.org",
+ "tomobe.ibaraki.jp",
+ "botanical.museum",
+ "humanities.museum",
+ "lowicz.pl",
+ "tosu.saga.jp",
+ "jewish.museum",
+ "chernivtsi.ua",
+ "figueres.museum",
+ "stateofdelaware.museum",
+ "za",
+ "blogspot.it",
+ "barcelona.museum",
+ "kizu.kyoto.jp",
+ "hokkaido.jp",
+ "chuo.chiba.jp",
+ "dyndns-web.com",
+ "fujisato.akita.jp",
+ "mielec.pl",
+ "kamisato.saitama.jp",
+ "servegame.org",
+ "saskatchewan.museum",
+ "cranbrook.museum",
+ "blogspot.ie",
+ "gjovik.no",
+ "engine.aero",
+ "z.bg",
+ "franziskaner.museum",
+ "heimatunduhren.museum",
+ "goto.nagasaki.jp",
+ "sosa.chiba.jp",
+ "res.in",
+ "jewishart.museum",
+ "kamo.kyoto.jp",
+ "susono.shizuoka.jp",
+ "davvesiida.no",
+ "vlaanderen.museum",
+ "epilepsy.museum",
+ "engineer.aero",
+ "hjartdal.no",
+ "mielno.pl",
+ "settsu.osaka.jp",
+ "mediaphone.om",
+ "travel.pl",
+ "aremark.no",
+ "misato.saitama.jp",
+ "carrara-massa.it",
+ "mosvik.no",
+ "kraanghke.no",
+ "kami.kochi.jp",
+ "gyeongbuk.kr",
+ "hirosaki.aomori.jp",
+ "chernigov.ua",
+ "miho.ibaraki.jp",
+ "blogspot.in",
+ "fylkesbibl.no",
+ "zp.ua",
+ "fareast.ru",
+ "dvrdns.org",
+ "misato.shimane.jp",
+ "chuo.osaka.jp",
+ "blogspot.be",
+ "fuoisku.no",
+ "blogspot.hu",
+ "gen.in",
+ "randaberg.no",
+ "blogspot.dk",
+ "chonan.chiba.jp",
+ "biz.id",
+ "blogspot.bj",
+ "blogspot.pt",
+ "misato.akita.jp",
+ "shikatsu.aichi.jp",
+ "blogspot.kr",
+ "transport.museum",
+ "alto-adige.it",
+ "blogspot.nl",
+ "koenig.ru",
+ "gamvik.no",
+ "egersund.no",
+ "laspezia.it",
+ "chernihiv.ua",
+ "blogspot.jp",
+ "furubira.hokkaido.jp",
+ "livinghistory.museum",
+ "blogspot.sk",
+ "miyazaki.jp",
+ "saratov.ru",
+ "muko.kyoto.jp",
+ "seto.aichi.jp",
+ "mil.id",
+ "cesena-forli.it",
+ "karasjohka.no",
+ "shiojiri.nagano.jp",
+ "serveftp.net",
+ "takatori.nara.jp",
+ "kazuno.akita.jp",
+ "sennan.osaka.jp",
+ "egyptian.museum",
+ "kusu.oita.jp",
+ "minobu.yamanashi.jp",
+ "kamisu.ibaraki.jp",
+ "assabu.hokkaido.jp",
+ "carraramassa.it",
+ "fujisawa.iwate.jp",
+ "lenvik.no",
+ "trieste.it",
+ "zj.cn",
+ "medecin.km",
+ "kitaaiki.nagano.jp",
+ "gorizia.it",
+ "kuju.oita.jp",
+ "sakhalin.ru",
+ "toei.aichi.jp",
+ "caltanissetta.it",
+ "tosa.kochi.jp",
+ "fuso.aichi.jp",
+ "larvik.no",
+ "mizusawa.iwate.jp",
+ "kita.kyoto.jp",
+ "kumano.hiroshima.jp",
+ "donetsk.ua",
+ "ebetsu.hokkaido.jp",
+ "kita.osaka.jp",
+ "media.pl",
+ "shinjuku.tokyo.jp",
+ "shintomi.miyazaki.jp",
+ "lipetsk.ru",
+ "realestate.pl",
+ "kainan.tokushima.jp",
+ "serveftp.org",
+ "shonai.yamagata.jp",
+ "tako.chiba.jp",
+ "anjo.aichi.jp",
+ "mil.in",
+ "zw",
+ "mari-el.ru",
+ "k12.id.us",
+ "hino.tottori.jp",
+ "swidnica.pl",
+ "health.museum",
+ "dudinka.ru",
+ "augustow.pl",
+ "ringerike.no",
+ "aurland.no",
+ "k12.ia.us",
+ "kimino.wakayama.jp",
+ "rawa-maz.pl",
+ "gov.by",
+ "com.by",
+ "shoo.okayama.jp",
+ "lib.id.us",
+ "minato.osaka.jp",
+ "kimobetsu.hokkaido.jp",
+ "chuo.fukuoka.jp",
+ "muncie.museum",
+ "alaheadju.no",
+ "shimamoto.osaka.jp",
+ "misato.wakayama.jp",
+ "gov.sy",
+ "edu.sy",
+ "com.sy",
+ "lib.ia.us",
+ "hoyanger.no",
+ "gs.jan-mayen.no",
+ "sowa.ibaraki.jp",
+ "blogspot.tw",
+ "kawaba.gunma.jp",
+ "shintoku.hokkaido.jp",
+ "slattum.no",
+ "assassination.museum",
+ "tara.saga.jp",
+ "kijo.miyazaki.jp",
+ "koebenhavn.museum",
+ "services.aero",
+ "cosenza.it",
+ "koge.tottori.jp",
+ "sopot.pl",
+ "kuwana.mie.jp",
+ "soja.okayama.jp",
+ "sevastopol.ua",
+ "malvik.no",
+ "blogspot.hk",
+ "austin.museum",
+ "association.museum",
+ "kota.aichi.jp",
+ "koto.tokyo.jp",
+ "babia-gora.pl",
+ "vega.no",
+ "siemens.om",
+ "gmina.pl",
+ "sor-varanger.no",
+ "taku.saga.jp",
+ "k12.in.us",
+ "koto.shiga.jp",
+ "roros.no",
+ "budejju.no",
+ "lorenskog.no",
+ "toho.fukuoka.jp",
+ "soma.fukushima.jp",
+ "jeonbuk.kr",
+ "froland.no",
+ "sugito.saitama.jp",
+ "hagebostad.no",
+ "kumejima.okinawa.jp",
+ "mitane.akita.jp",
+ "biratori.hokkaido.jp",
+ "kainan.wakayama.jp",
+ "k12.il.us",
+ "shinjo.yamagata.jp",
+ "lib.in.us",
+ "gjemnes.no",
+ "copenhagen.museum",
+ "firenze.it",
+ "minano.saitama.jp",
+ "kakinoki.shimane.jp",
+ "za.net",
+ "lib.il.us",
+ "tozawa.yamagata.jp",
+ "stavern.no",
+ "chernovtsy.ua",
+ "celtic.museum",
+ "council.aero",
+ "furano.hokkaido.jp",
+ "greta.fr",
+ "kyonan.chiba.jp",
+ "minato.tokyo.jp",
+ "kira.aichi.jp",
+ "ringebu.no",
+ "essex.museum",
+ "tondabayashi.osaka.jp",
+ "tsumagoi.gunma.jp",
+ "qa",
+ "amagasaki.hyogo.jp",
+ "tanabe.wakayama.jp",
+ "media.museum",
+ "tanabe.kyoto.jp",
+ "media.hu",
+ "ashibetsu.hokkaido.jp",
+ "gs.va.no",
+ "co.bi",
+ "somna.no",
+ "shimane.jp",
+ "gs.oslo.no",
+ "q.bg",
+ "kagawa.jp",
+ "vaga.no",
+ "kamoenai.hokkaido.jp",
+ "association.aero",
+ "swiebodzin.pl",
+ "gamo.shiga.jp",
+ "kaho.fukuoka.jp",
+ "mikasa.hokkaido.jp",
+ "joyo.kyoto.jp",
+ "gangwon.kr",
+ "aejrie.no",
+ "toyo.kochi.jp",
+ "adult.ht",
+ "mima.tokushima.jp",
+ "toyotomi.hokkaido.jp",
+ "skanit.no",
+ "saitama.jp",
+ "sandiego.museum",
+ "from-la.net",
+ "smola.no",
+ "gs.nt.no",
+ "stranda.no",
+ "sarpsborg.no",
+ "furniture.museum",
+ "rauma.no",
+ "qld.au",
+ "date.fukushima.jp",
+ "katano.osaka.jp",
+ "rennebu.no",
+ "tas.edu.au",
+ "anan.tokushima.jp",
+ "turen.tn",
+ "music.museum",
+ "starnberg.museum",
+ "murata.miyagi.jp",
+ "sebastopol.ua",
+ "minowa.nagano.jp",
+ "verbania.it",
+ "bergamo.it",
+ "sakata.yamagata.jp",
+ "chuo.tokyo.jp",
+ "sumoto.hyogo.jp",
+ "gorge.museum",
+ "rikubetsu.hokkaido.jp",
+ "asnes.no",
+ "konin.pl",
+ "monticello.museum",
+ "sauda.no",
+ "semine.miyagi.jp",
+ "games.hu",
+ "lezajsk.pl",
+ "tolga.no",
+ "ravenna.it",
+ "chita.ru",
+ "jeonnam.kr",
+ "kunitachi.tokyo.jp",
+ "dominic.ua",
+ "svelvik.no",
+ "motobu.okinawa.jp",
+ "student.aero",
+ "vestvagoy.no",
+ "stadt.museum",
+ "tvedestrand.no",
+ "qsl.br",
+ "venezia.it",
+ "hokuto.hokkaido.jp",
+ "kuzbass.ru",
+ "selfip.com",
+ "vaapste.no",
+ "mukawa.hokkaido.jp",
+ "kvitsoy.no",
+ "campidanomedio.it",
+ "berlin.museum",
+ "hareid.no",
+ "health.vn",
+ "hokuto.yamanashi.jp",
+ "gs.aa.no",
+ "joetsu.niigata.jp",
+ "mil.by",
+ "hamatama.saga.jp",
+ "takasu.hokkaido.jp",
+ "granvin.no",
+ "blogspot.gr",
+ "sandnes.no",
+ "tsunan.niigata.jp",
+ "kiyose.tokyo.jp",
+ "kawajima.saitama.jp",
+ "alstahaug.no",
+ "mil.sy",
+ "takatsuki.osaka.jp",
+ "6bone.pl",
+ "skedsmo.no",
+ "shiraoka.saitama.jp",
+ "misato.miyagi.jp",
+ "american.museum",
+ "jelenia-gora.pl",
+ "barlettatraniandria.it",
+ "blogspot.sg",
+ "kokubunji.tokyo.jp",
+ "koga.ibaraki.jp",
+ "sherbrooke.museum",
+ "force.museum",
+ "selfip.net",
+ "lukow.pl",
+ "skoczow.pl",
+ "takasaki.gunma.jp",
+ "hasvik.no",
+ "tjome.no",
+ "lubin.pl",
+ "county.museum",
+ "enebakk.no",
+ "godo.gifu.jp",
+ "kita.tokyo.jp",
+ "catania.it",
+ "giske.no",
+ "vestnes.no",
+ "taketomi.okinawa.jp",
+ "forde.no",
+ "toyono.osaka.jp",
+ "for-the.biz",
+ "toyonaka.osaka.jp",
+ "mbone.pl",
+ "donna.no",
+ "texas.museum",
+ "kitaakita.akita.jp",
+ "hyllestad.no",
+ "volkenkunde.museum",
+ "komatsushima.tokushima.jp",
+ "shinagawa.tokyo.jp",
+ "takizawa.iwate.jp",
+ "kirov.ru",
+ "savannahga.museum",
+ "tranby.no",
+ "depot.museum",
+ "kure.hiroshima.jp",
+ "vestby.no",
+ "trana.no",
+ "kariwa.niigata.jp",
+ "frogn.no",
+ "kherson.ua",
+ "volda.no",
+ "loppa.no",
+ "from-co.net",
+ "dyndns.org",
+ "juedisches.museum",
+ "karlsoy.no",
+ "miyota.nagano.jp",
+ "suginami.tokyo.jp",
+ "seranishi.hiroshima.jp",
+ "ferrara.it",
+ "frana.no",
+ "kiyosato.hokkaido.jp",
+ "cincinnati.museum",
+ "gujo.gifu.jp",
+ "trainer.aero",
+ "loten.no",
+ "agematsu.nagano.jp",
+ "takazaki.miyazaki.jp",
+ "lebesby.no",
+ "mikawa.yamagata.jp",
+ "tainai.niigata.jp",
+ "chtr.k12.ma.us",
+ "groundhandling.aero",
+ "morotsuka.miyazaki.jp",
+ "tama.tokyo.jp",
+ "hazu.aichi.jp",
+ "kayabe.hokkaido.jp",
+ "johana.toyama.jp",
+ "hiji.oita.jp",
+ "spydeberg.no",
+ "kragero.no",
+ "mytis.ru",
+ "moareke.no",
+ "dolls.museum",
+ "kunneppu.hokkaido.jp",
+ "molde.no",
+ "gulen.no",
+ "koka.shiga.jp",
+ "hannan.osaka.jp",
+ "kurate.fukuoka.jp",
+ "2000.hu",
+ "kazan.ru",
+ "sasebo.nagasaki.jp",
+ "kuban.ru",
+ "qh.cn",
+ "americana.museum",
+ "state.museum",
+ "zlg.br",
+ "from-me.org",
+ "foggia.it",
+ "land-4-sale.us",
+ "madrid.museum",
+ "futaba.fukushima.jp",
+ "kawazu.shizuoka.jp",
+ "bjugn.no",
+ "grane.no",
+ "koeln.museum",
+ "shimamaki.hokkaido.jp",
+ "ginoza.okinawa.jp",
+ "saikai.nagasaki.jp",
+ "game-host.org",
+ "tokke.no",
+ "agriculture.museum",
+ "rivne.ua",
+ "kyotamba.kyoto.jp",
+ "gunma.jp",
+ "hapmir.no",
+ "from-ny.net",
+ "tomi.nagano.jp",
+ "salat.no",
+ "sano.tochigi.jp",
+ "barletta-trani-andria.it",
+ "aosta.it",
+ "kawanishi.nara.jp",
+ "genoa.it",
+ "togo.aichi.jp",
+ "tsugaru.aomori.jp",
+ "kokonoe.oita.jp",
+ "busan.kr",
+ "cesenaforli.it",
+ "kuroishi.aomori.jp",
+ "mugi.tokushima.jp",
+ "shinjo.nara.jp",
+ "shonai.fukuoka.jp",
+ "kadena.okinawa.jp",
+ "shiiba.miyazaki.jp",
+ "agrinet.tn",
+ "kyotango.kyoto.jp",
+ "bronnoy.no",
+ "fujinomiya.shizuoka.jp",
+ "space.museum",
+ "k12.wy.us",
+ "skole.museum",
+ "sera.hiroshima.jp",
+ "biei.hokkaido.jp",
+ "mombetsu.hokkaido.jp",
+ "khabarovsk.ru",
+ "bato.tochigi.jp",
+ "kiso.nagano.jp",
+ "klodzko.pl",
+ "trapani.it",
+ "esan.hokkaido.jp",
+ "hakuba.nagano.jp",
+ "better-than.tv",
+ "hita.oita.jp",
+ "mimata.miyazaki.jp",
+ "togane.chiba.jp",
+ "mibu.tochigi.jp",
+ "lib.wy.us",
+ "bandai.fukushima.jp",
+ "hikawa.shimane.jp",
+ "mino.gifu.jp",
+ "date.hokkaido.jp",
+ "shinjo.okayama.jp",
+ "saga.saga.jp",
+ "miyazaki.miyazaki.jp",
+ "seki.gifu.jp",
+ "lanbib.se",
+ "christiansburg.museum",
+ "skien.no",
+ "k12.ny.us",
+ "kani.gifu.jp",
+ "homeip.net",
+ "bo.telemark.no",
+ "tsubetsu.hokkaido.jp",
+ "sado.niigata.jp",
+ "delmenhorst.museum",
+ "store.st",
+ "vikna.no",
+ "toki.gifu.jp",
+ "simbirsk.ru",
+ "siena.it",
+ "kawasaki.miyagi.jp",
+ "hino.tokyo.jp",
+ "lib.ny.us",
+ "kamo.niigata.jp",
+ "maebashi.gunma.jp",
+ "tsaritsyn.ru",
+ "balat.no",
+ "rikuzentakata.iwate.jp",
+ "gokase.miyazaki.jp",
+ "aoki.nagano.jp",
+ "toyosato.shiga.jp",
+ "bryansk.ru",
+ "koga.fukuoka.jp",
+ "kaga.ishikawa.jp",
+ "skaun.no",
+ "mizunami.gifu.jp",
+ "lesja.no",
+ "sport.hu",
+ "sannan.hyogo.jp",
+ "kamiichi.toyama.jp",
+ "holmestrand.no",
+ "lakas.hu",
+ "maniwa.okayama.jp",
+ "makinohara.shizuoka.jp",
+ "om",
+ "agdenes.no",
+ "store.ro",
+ "baltimore.museum",
+ "gov.tw",
+ "edu.tw",
+ "aoste.it",
+ "com.tw",
+ "daejeon.kr",
+ "aukra.no",
+ "flora.no",
+ "com.aw",
+ "miyazu.kyoto.jp",
+ "leirvik.no",
+ "gniezno.pl",
+ "bronnoysund.no",
+ "shiga.jp",
+ "tokushima.jp",
+ "vladimir.ru",
+ "charter.aero",
+ "or.it",
+ "od.ua",
+ "tempio-olbia.it",
+ "turin.it",
+ "moka.tochigi.jp",
+ "ot.it",
+ "journal.aero",
+ "fukushima.jp",
+ "botanicalgarden.museum",
+ "taketa.oita.jp",
+ "akaiwa.okayama.jp",
+ "or.us",
+ "baths.museum",
+ "taranto.it",
+ "kumenan.okayama.jp",
+ "o.se",
+ "bible.museum",
+ "edu.cw",
+ "com.cw",
+ "fukusaki.hyogo.jp",
+ "cagliari.it",
+ "dovre.no",
+ "fujiidera.osaka.jp",
+ "kamiamakusa.kumamoto.jp",
+ "odo.br",
+ "barreau.bj",
+ "virginia.museum",
+ "saitama.saitama.jp",
+ "takatsuki.shiga.jp",
+ "soeda.fukuoka.jp",
+ "anan.nagano.jp",
+ "ec",
+ "akita.jp",
+ "cc",
+ "sc",
+ "ac",
+ "kawatana.nagasaki.jp",
+ "hemne.no",
+ "bozen.it",
+ "on.ca",
+ "meiwa.mie.jp",
+ "valle.no",
+ "tamano.okayama.jp",
+ "saku.nagano.jp",
+ "educational.museum",
+ "aioi.hyogo.jp",
+ "himeji.hyogo.jp",
+ "ac.ma",
+ "fedje.no",
+ "or.na",
+ "shibetsu.hokkaido.jp",
+ "rc.it",
+ "colonialwilliamsburg.museum",
+ "gc.ca",
+ "kamitonda.wakayama.jp",
+ "botany.museum",
+ "sc.us",
+ "o.bg",
+ "kawanehon.shizuoka.jp",
+ "kusatsu.shiga.jp",
+ "mc",
+ "surrey.museum",
+ "lewismiller.museum",
+ "shima.mie.jp",
+ "takata.fukuoka.jp",
+ "selje.no",
+ "himeshima.oita.jp",
+ "tc",
+ "lc",
+ "sumy.ua",
+ "saka.hiroshima.jp",
+ "lugansk.ua",
+ "magnitka.ru",
+ "mc.it",
+ "ehime.jp",
+ "aridagawa.wakayama.jp",
+ "lc.it",
+ "express.aero",
+ "eco.br",
+ "or.at",
+ "embetsu.hokkaido.jp",
+ "sobetsu.hokkaido.jp",
+ "takino.hyogo.jp",
+ "act.au",
+ "kanazawa.ishikawa.jp",
+ "ac.pa",
+ "fc.it",
+ "cody.museum",
+ "hirata.fukushima.jp",
+ "shimane.shimane.jp",
+ "britishcolumbia.museum",
+ "gdynia.pl",
+ "rec.co",
+ "genkai.saga.jp",
+ "etc.br",
+ "shikabe.hokkaido.jp",
+ "cc.na",
+ "aikawa.kanagawa.jp",
+ "ac.tj",
+ "oh.us",
+ "hikone.shiga.jp",
+ "ok.us",
+ "rec.br",
+ "hirono.fukushima.jp",
+ "hiroshima.jp",
+ "omsk.ru",
+ "dc.us",
+ "bryne.no",
+ "bushey.museum",
+ "lillehammer.no",
+ "kawanishi.hyogo.jp",
+ "tobetsu.hokkaido.jp",
+ "stor-elvdal.no",
+ "mil.tw",
+ "or.tz",
+ "bc.ca",
+ "sogne.no",
+ "shimabara.nagasaki.jp",
+ "royrvik.no",
+ "vc",
+ "or.pw",
+ "shibecha.hokkaido.jp",
+ "ac.me",
+ "hornindal.no",
+ "aibetsu.hokkaido.jp",
+ "hawaii.museum",
+ "vc.it",
+ "sydney.museum",
+ "ecn.br",
+ "kamiizumi.saitama.jp",
+ "valley.museum",
+ "suwa.nagano.jp",
+ "ac.at",
+ "ac.mw",
+ "erotika.hu",
+ "quebec.museum",
+ "kanmaki.nara.jp",
+ "tagajo.miyagi.jp",
+ "sch.ae",
+ "musashino.tokyo.jp",
+ "toya.hokkaido.jp",
+ "jgora.pl",
+ "shimada.shizuoka.jp",
+ "reggioemilia.it",
+ "sch.qa",
+ "hitra.no",
+ "kesennuma.miyagi.jp",
+ "erotica.hu",
+ "fujieda.shizuoka.jp",
+ "sch.sa",
+ "sch.jo",
+ "sch.je",
+ "honbetsu.hokkaido.jp",
+ "fujisawa.kanagawa.jp",
+ "ac.gn",
+ "chattanooga.museum",
+ "ac.in",
+ "hida.gifu.jp",
+ "rybnik.pl",
+ "sc.cn",
+ "kibichuo.okayama.jp",
+ "ac.cn",
+ "showa.fukushima.jp",
+ "sc.tz",
+ "ac.tz",
+ "himi.toyama.jp",
+ "shiranuka.hokkaido.jp",
+ "miki.hyogo.jp",
+ "sci.eg",
+ "for-better.biz",
+ "orkdal.no",
+ "kaita.hiroshima.jp",
+ "milan.it",
+ "selfip.org",
+ "gorlice.pl",
+ "buzen.fukuoka.jp",
+ "ac.vn",
+ "tranibarlettaandria.it",
+ "asaka.saitama.jp",
+ "store.nf",
+ "vic.au",
+ "tsuwano.shimane.jp",
+ "kusatsu.gunma.jp",
+ "orenburg.ru",
+ "takasago.hyogo.jp",
+ "ginowan.okinawa.jp",
+ "daiwa.hiroshima.jp",
+ "oita.jp",
+ "ac.ae",
+ "targi.pl",
+ "bearalvahki.no",
+ "bykle.no",
+ "shinshinotsu.hokkaido.jp",
+ "koshigaya.saitama.jp",
+ "davvenjarga.no",
+ "kagoshima.jp",
+ "kamaishi.iwate.jp",
+ "hara.nagano.jp",
+ "asaminami.hiroshima.jp",
+ "hakata.fukuoka.jp",
+ "kaminoyama.yamagata.jp",
+ "hanawa.fukushima.jp",
+ "hadano.kanagawa.jp",
+ "gliwice.pl",
+ "bygland.no",
+ "chiba.jp",
+ "tsubame.niigata.jp",
+ "home.dyndns.org",
+ "hiraizumi.iwate.jp",
+ "trani-andria-barletta.it",
+ "katashina.gunma.jp",
+ "oppdal.no",
+ "hakone.kanagawa.jp",
+ "tourism.tn",
+ "ac.rs",
+ "trani-barletta-andria.it",
+ "karatsu.saga.jp",
+ "oksnes.no",
+ "andriabarlettatrani.it",
+ "ancona.it",
+ "americanantiques.museum",
+ "altai.ru",
+ "orland.no",
+ "tokushima.tokushima.jp",
+ "miyashiro.saitama.jp",
+ "zarow.pl",
+ "seika.kyoto.jp",
+ "tsukigata.hokkaido.jp",
+ "treviso.it",
+ "tome.miyagi.jp",
+ "kurashiki.okayama.jp",
+ "sciences.museum",
+ "resistance.museum",
+ "taka.hyogo.jp",
+ "forlicesena.it",
+ "sayo.hyogo.jp",
+ "takaishi.osaka.jp",
+ "rygge.no",
+ "org",
+ "og.it",
+ "oslo.no",
+ "org.to",
+ "org.tm",
+ "ama.shimane.jp",
+ "org.bo",
+ "org.bm",
+ "org.ae",
+ "servebbs.com",
+ "org.bi",
+ "org.bz",
+ "org.ai",
+ "org.az",
+ "airline.aero",
+ "org.ag",
+ "gliding.aero",
+ "gf",
+ "vinnica.ua",
+ "muika.niigata.jp",
+ "cf",
+ "org.qa",
+ "af",
+ "org.ba",
+ "org.so",
+ "org.se",
+ "org.sd",
+ "trading.aero",
+ "org.bs",
+ "osen.no",
+ "org.sz",
+ "odda.no",
+ "org.sg",
+ "satte.saitama.jp",
+ "org.ac",
+ "org.sa",
+ "chigasaki.kanagawa.jp",
+ "org.jo",
+ "org.je",
+ "eniwa.hokkaido.jp",
+ "ryokami.saitama.jp",
+ "ome.tokyo.jp",
+ "org.bb",
+ "org.sc",
+ "online.museum",
+ "kami.miyagi.jp",
+ "andria-trani-barletta.it",
+ "aeroclub.aero",
+ "org.sb",
+ "musashimurayama.tokyo.jp",
+ "tateshina.nagano.jp",
+ "komatsu.ishikawa.jp",
+ "tohma.hokkaido.jp",
+ "org.au",
+ "shinichi.hiroshima.jp",
+ "org.co",
+ "org.tt",
+ "org.ci",
+ "org.bt",
+ "city.hu",
+ "shikaoi.hokkaido.jp",
+ "kamisunagawa.hokkaido.jp",
+ "chuo.yamanashi.jp",
+ "tf",
+ "org.tn",
+ "org.st",
+ "org.br",
+ "org.an",
+ "tamatsukuri.ibaraki.jp",
+ "zama.kanagawa.jp",
+ "ac.rw",
+ "vagan.no",
+ "toga.toyama.jp",
+ "org.sn",
+ "conference.aero",
+ "tourism.pl",
+ "org.al",
+ "fukushima.hokkaido.jp",
+ "certification.aero",
+ "british-library.uk",
+ "org.cu",
+ "rec.nf",
+ "org.sl",
+ "ftpaccess.cc",
+ "satosho.okayama.jp",
+ "tagawa.fukuoka.jp",
+ "org.tj",
+ "maibara.shiga.jp",
+ "org.cn",
+ "karuizawa.nagano.jp",
+ "bf",
+ "zentsuji.kagawa.jp",
+ "americanart.museum",
+ "kodaira.tokyo.jp",
+ "gosen.niigata.jp",
+ "orkanger.no",
+ "ota.tokyo.jp",
+ "tokashiki.okinawa.jp",
+ "org.ee",
+ "est-a-la-maison.com",
+ "oji.nara.jp",
+ "ac.be",
+ "org.eg",
+ "org.es",
+ "reggiocalabria.it",
+ "kannami.shizuoka.jp",
+ "est-a-la-masion.com",
+ "org.ec",
+ "org.ws",
+ "arita.saga.jp",
+ "qld.edu.au",
+ "operaunite.com",
+ "muroto.kochi.jp",
+ "kitanakagusuku.okinawa.jp",
+ "likes-pie.com",
+ "ami.ibaraki.jp",
+ "lapy.pl",
+ "gushikami.okinawa.jp",
+ "no",
+ "nu",
+ "beskidy.pl",
+ "org.ng",
+ "oirase.aomori.jp",
+ "nz",
+ "rebun.hokkaido.jp",
+ "meiwa.gunma.jp",
+ "nom.tm",
+ "org.na",
+ "shibata.niigata.jp",
+ "nom.ad",
+ "nr",
+ "no.it",
+ "nu.it",
+ "nom.ag",
+ "showa.gunma.jp",
+ "kikuchi.kumamoto.jp",
+ "nu.ca",
+ "hashikami.aomori.jp",
+ "ne",
+ "nv.us",
+ "nd.us",
+ "nm.us",
+ "konyvelo.hu",
+ "kashihara.nara.jp",
+ "buryatia.ru",
+ "nt.ca",
+ "shikama.miyagi.jp",
+ "gifu.jp",
+ "elb.amazonaws.com",
+ "koganei.tokyo.jp",
+ "ns.ca",
+ "arida.wakayama.jp",
+ "eu.com",
+ "ovre-eiker.no",
+ "net",
+ "ru.com",
+ "n.se",
+ "nom.co",
+ "net.to",
+ "net.tm",
+ "net.bo",
+ "net.bm",
+ "org.nr",
+ "net.ae",
+ "haga.tochigi.jp",
+ "gr.com",
+ "net.bz",
+ "net.ai",
+ "net.az",
+ "kyowa.hokkaido.jp",
+ "ar.com",
+ "net.ag",
+ "ne.us",
+ "os.hedmark.no",
+ "ni",
+ "net.qa",
+ "groks-the.info",
+ "net.ba",
+ "net.so",
+ "net.sd",
+ "toshima.tokyo.jp",
+ "kchr.ru",
+ "nom.br",
+ "maritime.museum",
+ "net.bs",
+ "kikonai.hokkaido.jp",
+ "net.sg",
+ "kr.com",
+ "nj.us",
+ "jerusalem.museum",
+ "net.ac",
+ "rieti.it",
+ "net.sa",
+ "net.jo",
+ "net.je",
+ "laquila.it",
+ "se.com",
+ "takashima.shiga.jp",
+ "shobara.hiroshima.jp",
+ "net.bb",
+ "terni.it",
+ "not.br",
+ "tempioolbia.it",
+ "net.sc",
+ "space-to-rent.com",
+ "oshu.iwate.jp",
+ "omi.nagano.jp",
+ "nl",
+ "org.af",
+ "kanna.gunma.jp",
+ "net.sb",
+ "ntr.br",
+ "net.au",
+ "shirataka.yamagata.jp",
+ "showa.yamanashi.jp",
+ "net.co",
+ "na",
+ "net.tt",
+ "net.ci",
+ "net.bt",
+ "org.ve",
+ "org.vi",
+ "nl.ca",
+ "co.no",
+ "na.it",
+ "net.tn",
+ "city.kawasaki.jp",
+ "net.st",
+ "net.br",
+ "adygeya.ru",
+ "hiranai.aomori.jp",
+ "net.an",
+ "sellsyourhome.org",
+ "n.bg",
+ "cn.com",
+ "asahikawa.hokkaido.jp",
+ "olkusz.pl",
+ "org.vc",
+ "name",
+ "turystyka.pl",
+ "codespot.com",
+ "ogliastra.it",
+ "np",
+ "nb.ca",
+ "net.al",
+ "st.no",
+ "corvette.museum",
+ "nom.es",
+ "net.cu",
+ "net.sl",
+ "name.mv",
+ "name.eg",
+ "fukui.jp",
+ "or.ci",
+ "sa.com",
+ "artcenter.museum",
+ "tadotsu.kagawa.jp",
+ "net.tj",
+ "de.com",
+ "aogashima.tokyo.jp",
+ "org.bh",
+ "br.com",
+ "abira.hokkaido.jp",
+ "gb.com",
+ "net.cn",
+ "tm.no",
+ "hitachiota.ibaraki.jp",
+ "mr.no",
+ "org.vn",
+ "akune.kagoshima.jp",
+ "org.sh",
+ "kicks-ass.org",
+ "taira.toyama.jp",
+ "gv.ao",
+ "dgca.aero",
+ "ed.ao",
+ "co.ao",
+ "tr.no",
+ "groks-this.info",
+ "name.mk",
+ "fm.no",
+ "nh.us",
+ "shiraoi.hokkaido.jp",
+ "net.eg",
+ "taiwa.miyagi.jp",
+ "obu.aichi.jp",
+ "nat.tn",
+ "kawanishi.yamagata.jp",
+ "statecouncil.om",
+ "nm.cn",
+ "eastafrica.museum",
+ "nx.cn",
+ "tsubata.ishikawa.jp",
+ "sor-odal.no",
+ "net.ec",
+ "recreation.aero",
+ "name.tt",
+ "ouda.nara.jp",
+ "ogi.saga.jp",
+ "hiratsuka.kanagawa.jp",
+ "aga.niigata.jp",
+ "net.ws",
+ "al.no",
+ "name.tj",
+ "rl.no",
+ "blogsite.org",
+ "bu.no",
+ "for-some.biz",
+ "tatebayashi.gunma.jp",
+ "hakusan.ishikawa.jp",
+ "aa.no",
+ "ne.tz",
+ "legnica.pl",
+ "ac.ci",
+ "blogspot.cv",
+ "chambagri.fr",
+ "blogspot.cz",
+ "net.ng",
+ "dyndns.biz",
+ "ne.pw",
+ "teshikaga.hokkaido.jp",
+ "sande.more-og-romsdal.no",
+ "higashiizu.shizuoka.jp",
+ "vladikavkaz.ru",
+ "ringsaker.no",
+ "taiki.mie.jp",
+ "kashima.kumamoto.jp",
+ "tomisato.chiba.jp",
+ "ainan.ehime.jp",
+ "dyndns-remote.com",
+ "ah.no",
+ "omantel.om",
+ "kamishihoro.hokkaido.jp",
+ "heritage.museum",
+ "bremanger.no",
+ "akita.akita.jp",
+ "name.na",
+ "leikanger.no",
+ "nnov.ru",
+ "satx.museum",
+ "net.nr",
+ "kashiba.nara.jp",
+ "kisosaki.mie.jp",
+ "miyoshi.saitama.jp",
+ "higashiyoshino.nara.jp",
+ "k12.ok.us",
+ "haibara.shizuoka.jp",
+ "blogspot.com",
+ "shirahama.wakayama.jp",
+ "stavanger.no",
+ "ketrzyn.pl",
+ "steinkjer.no",
+ "sciencesnaturelles.museum",
+ "zgora.pl",
+ "samnanger.no",
+ "lib.ok.us",
+ "hof.no",
+ "co.gy",
+ "mincom.tn",
+ "kanra.gunma.jp",
+ "net.af",
+ "monmouth.museum",
+ "higashinaruse.akita.jp",
+ "akkeshi.hokkaido.jp",
+ "blogspot.ca",
+ "net.ve",
+ "saves-the-whales.com",
+ "gobiernoelectronico.ar",
+ "koshimizu.hokkaido.jp",
+ "kautokeino.no",
+ "net.vi",
+ "nsw.au",
+ "va.no",
+ "higashiizumo.shimane.jp",
+ "shimizu.hokkaido.jp",
+ "sakae.nagano.jp",
+ "akagi.shimane.jp",
+ "nome.pt",
+ "ggf.br",
+ "omaezaki.shizuoka.jp",
+ "hu.com",
+ "kawachinagano.osaka.jp",
+ "database.museum",
+ "net.vc",
+ "medizinhistorisches.museum",
+ "k12.or.us",
+ "from-mn.com",
+ "from-mt.com",
+ "from-de.com",
+ "from-or.com",
+ "from-in.com",
+ "sch.id",
+ "gratangen.no",
+ "skiptvet.no",
+ "blogspot.com.es",
+ "ny.us",
+ "niepce.museum",
+ "naumburg.museum",
+ "haebaru.okinawa.jp",
+ "from-ar.com",
+ "nes.akershus.no",
+ "manx.museum",
+ "blogspot.ch",
+ "oyer.no",
+ "lib.or.us",
+ "kyotanabe.kyoto.jp",
+ "from-ct.com",
+ "nara.jp",
+ "from-il.com",
+ "tenei.fukushima.jp",
+ "shimizu.shizuoka.jp",
+ "kyowa.akita.jp",
+ "kvanangen.no",
+ "from-ok.com",
+ "bizen.okayama.jp",
+ "from-al.com",
+ "net.th",
+ "delaware.museum",
+ "net.bh",
+ "city.sapporo.jp",
+ "shiriuchi.hokkaido.jp",
+ "oyamazaki.kyoto.jp",
+ "tm.ro",
+ "volgograd.ru",
+ "chukotka.ru",
+ "from-ak.com",
+ "net.vn",
+ "from-ut.com",
+ "net.sh",
+ "london.museum",
+ "hizen.saga.jp",
+ "knowsitall.info",
+ "k12.wv.us",
+ "air-surveillance.aero",
+ "miyoshi.tokushima.jp",
+ "from-tx.com",
+ "from-tn.com",
+ "hyuga.miyazaki.jp",
+ "zagan.pl",
+ "toyota.aichi.jp",
+ "blogspot.com.au",
+ "sabae.fukui.jp",
+ "togitsu.nagasaki.jp",
+ "campobasso.it",
+ "laakesvuemie.no",
+ "kamitsue.oita.jp",
+ "shika.ishikawa.jp",
+ "suita.osaka.jp",
+ "from-nv.com",
+ "from-nm.com",
+ "lib.wv.us",
+ "from-ne.com",
+ "nittedal.no",
+ "blogspot.com.ar",
+ "sch.ir",
+ "hm.no",
+ "from-ma.com",
+ "fukuchiyama.kyoto.jp",
+ "from-ia.com",
+ "higashikurume.tokyo.jp",
+ "from-ga.com",
+ "from-nj.com",
+ "forgot.her.name",
+ "lindesnes.no",
+ "okuizumo.shimane.jp",
+ "saiki.oita.jp",
+ "asahi.mie.jp",
+ "blogspot.com.br",
+ "higashiyamato.tokyo.jp",
+ "higashisumiyoshi.osaka.jp",
+ "folkebibl.no",
+ "k12.nv.us",
+ "chocolate.museum",
+ "shiwa.iwate.jp",
+ "from-ca.com",
+ "boston.museum",
+ "sanda.hyogo.jp",
+ "flatanger.no",
+ "from-md.com",
+ "from-id.com",
+ "nes.buskerud.no",
+ "ng",
+ "elvendrell.museum",
+ "scienceandindustry.museum",
+ "name.my",
+ "lib.nv.us",
+ "name.pr",
+ "from-ms.com",
+ "kyuragi.saga.jp",
+ "from-ks.com",
+ "artgallery.museum",
+ "onna.okinawa.jp",
+ "from-sd.com",
+ "sciencehistory.museum",
+ "tv.bo",
+ "sakawa.kochi.jp",
+ "shibata.miyagi.jp",
+ "nore-og-uvdal.no",
+ "ebina.kanagawa.jp",
+ "szex.hu",
+ "act.edu.au",
+ "dyndns-ip.com",
+ "oshino.yamanashi.jp",
+ "atami.shizuoka.jp",
+ "otsu.shiga.jp",
+ "cc.mt.us",
+ "forgot.his.name",
+ "cc.ga.us",
+ "cc.ma.us",
+ "kiyosu.aichi.jp",
+ "name.hr",
+ "edogawa.tokyo.jp",
+ "cc.ia.us",
+ "mashike.hokkaido.jp",
+ "cc.ct.us",
+ "net.nf",
+ "from-oh.com",
+ "cc.ca.us",
+ "cc.ms.us",
+ "cc.ut.us",
+ "sumita.iwate.jp",
+ "cc.ks.us",
+ "oiso.kanagawa.jp",
+ "jefferson.museum",
+ "katowice.pl",
+ "funabashi.chiba.jp",
+ "cartoonart.museum",
+ "froya.no",
+ "qc.ca",
+ "from-dc.com",
+ "oita.oita.jp",
+ "from-nd.com",
+ "hl.no",
+ "name.jo",
+ "shunan.yamaguchi.jp",
+ "tenri.nara.jp",
+ "tabuse.yamaguchi.jp",
+ "shiki.saitama.jp",
+ "from-sc.com",
+ "or.bi",
+ "cc.wa.us",
+ "ha.no",
+ "kosai.shizuoka.jp",
+ "sakaiminato.tottori.jp",
+ "cc.vt.us",
+ "orsta.no",
+ "city.sendai.jp",
+ "koori.fukushima.jp",
+ "cc.va.us",
+ "ookuwa.nagano.jp",
+ "olawa.pl",
+ "cc.tx.us",
+ "komae.tokyo.jp",
+ "cc.pa.us",
+ "countryestate.museum",
+ "kushimoto.wakayama.jp",
+ "hayashima.okayama.jp",
+ "mishima.shizuoka.jp",
+ "gobo.wakayama.jp",
+ "from-nh.com",
+ "cc.dc.us",
+ "skedsmokorset.no",
+ "harvestcelebration.museum",
+ "author.aero",
+ "hammerfest.no",
+ "naples.it",
+ "kagoshima.kagoshima.jp",
+ "from-nc.com",
+ "tado.mie.jp",
+ "toyone.aichi.jp",
+ "scienceandhistory.museum",
+ "kawai.nara.jp",
+ "cc.nj.us",
+ "cc.la.us",
+ "hitachiomiya.ibaraki.jp",
+ "ballangen.no",
+ "koza.wakayama.jp",
+ "miyoshi.hiroshima.jp",
+ "org.im",
+ "kashima.ibaraki.jp",
+ "mashiki.kumamoto.jp",
+ "cc.me.us",
+ "arakawa.tokyo.jp",
+ "cc.nc.us",
+ "org.is",
+ "noheji.aomori.jp",
+ "notteroy.no",
+ "toyota.yamaguchi.jp",
+ "oumu.hokkaido.jp",
+ "higashimurayama.tokyo.jp",
+ "from-mo.com",
+ "miura.kanagawa.jp",
+ "dlugoleka.pl",
+ "cc.as.us",
+ "k12.oh.us",
+ "cc.de.us",
+ "mifune.kumamoto.jp",
+ "higashiosaka.osaka.jp",
+ "belgorod.ru",
+ "stryn.no",
+ "blogspot.fr",
+ "azumino.nagano.jp",
+ "ayabe.kyoto.jp",
+ "hitachi.ibaraki.jp",
+ "vic.edu.au",
+ "tonosho.kagawa.jp",
+ "from-fl.com",
+ "langevag.no",
+ "kiho.mie.jp",
+ "dyndns-at-home.com",
+ "betainabox.com",
+ "lib.oh.us",
+ "at-band-camp.net",
+ "anamizu.ishikawa.jp",
+ "cc.mn.us",
+ "bielawa.pl",
+ "org.ir",
+ "org.in",
+ "cc.in.us",
+ "sakae.chiba.jp",
+ "taiki.hokkaido.jp",
+ "opole.pl",
+ "sells-for-u.com",
+ "berlevag.no",
+ "kashima.saga.jp",
+ "from-ky.com",
+ "from-pr.com",
+ "dyndns.info",
+ "joboji.iwate.jp",
+ "kushiro.hokkaido.jp",
+ "vicenza.it",
+ "cc.ne.us",
+ "nebraska.museum",
+ "noda.iwate.jp",
+ "midatlantic.museum",
+ "olbiatempio.it",
+ "blogspot.fi",
+ "nord-odal.no",
+ "medecin.fr",
+ "numata.gunma.jp",
+ "amami.kagoshima.jp",
+ "okutama.tokyo.jp",
+ "kushima.miyazaki.jp",
+ "bungoono.oita.jp",
+ "higashiomi.shiga.jp",
+ "cc.tn.us",
+ "mecon.ar",
+ "ryugasaki.ibaraki.jp",
+ "toba.mie.jp",
+ "etajima.hiroshima.jp",
+ "michigan.museum",
+ "florence.it",
+ "higashiyodogawa.osaka.jp",
+ "taki.mie.jp",
+ "nannestad.no",
+ "ozu.ehime.jp",
+ "from-pa.com",
+ "gov.hk",
+ "medical.museum",
+ "edu.hk",
+ "com.hk",
+ "hammarfeasta.no",
+ "homebuilt.aero",
+ "selfip.biz",
+ "from-mi.com",
+ "cc.az.us",
+ "yt",
+ "kanonji.kagawa.jp",
+ "mediocampidano.it",
+ "ye",
+ "rokunohe.aomori.jp",
+ "schoenbrunn.museum",
+ "jamison.museum",
+ "mallorca.museum",
+ "tenkawa.nara.jp",
+ "io",
+ "id",
+ "im",
+ "marketplace.aero",
+ "chihayaakasaka.osaka.jp",
+ "higashihiroshima.hiroshima.jp",
+ "tamba.hyogo.jp",
+ "co.st",
+ "ir",
+ "y.se",
+ "edu.ht",
+ "im.it",
+ "dellogliastra.it",
+ "com.ht",
+ "kanan.osaka.jp",
+ "narita.chiba.jp",
+ "it",
+ "archaeological.museum",
+ "goshiki.hyogo.jp",
+ "dyn-o-saur.com",
+ "edu.hn",
+ "austevoll.no",
+ "go.th",
+ "is",
+ "tokoname.aichi.jp",
+ "com.hr",
+ "com.hn",
+ "co.th",
+ "ie",
+ "city.kitakyushu.jp",
+ "from-ri.com",
+ "omaha.museum",
+ "id.us",
+ "maintenance.aero",
+ "cc.wv.us",
+ "aizubange.fukushima.jp",
+ "is.it",
+ "bergbau.museum",
+ "fribourg.museum",
+ "sex.hu",
+ "akabira.hokkaido.jp",
+ "clinton.museum",
+ "asahi.toyama.jp",
+ "art.ht",
+ "nomi.ishikawa.jp",
+ "tsukiyono.gunma.jp",
+ "i.se",
+ "osaka.jp",
+ "lucca.it",
+ "vibovalentia.it",
+ "net.im",
+ "net.id",
+ "noto.ishikawa.jp",
+ "konan.shiga.jp",
+ "botanicgarden.museum",
+ "naruto.tokushima.jp",
+ "oga.akita.jp",
+ "nose.osaka.jp",
+ "in",
+ "net.is",
+ "cc.nv.us",
+ "arakawa.saitama.jp",
+ "coldwar.museum",
+ "birdart.museum",
+ "boleslawiec.pl",
+ "y.bg",
+ "nysa.pl",
+ "denmark.museum",
+ "asso.dz",
+ "rishiri.hokkaido.jp",
+ "med.ht",
+ "bibai.hokkaido.jp",
+ "nawras.om",
+ "tsuyama.okayama.jp",
+ "il",
+ "fineart.museum",
+ "int",
+ "s3-website-us-east-1.amazonaws.com",
+ "in.ua",
+ "hagi.yamaguchi.jp",
+ "int.bo",
+ "gangaviika.no",
+ "noda.chiba.jp",
+ "int.az",
+ "nord-fron.no",
+ "store.bb",
+ "in.us",
+ "ohtawara.tochigi.jp",
+ "magazine.aero",
+ "volyn.ua",
+ "idrett.no",
+ "vibo-valentia.it",
+ "frosta.no",
+ "org.sy",
+ "convent.museum",
+ "hamatonbetsu.hokkaido.jp",
+ "opoczno.pl",
+ "net.ir",
+ "net.in",
+ "i.bg",
+ "muosat.no",
+ "otoineppu.hokkaido.jp",
+ "il.us",
+ "dyndns-at-work.com",
+ "yk.ca",
+ "artanddesign.museum",
+ "kiwa.mie.jp",
+ "york.museum",
+ "ostre-toten.no",
+ "mi.th",
+ "co.sz",
+ "ia.us",
+ "ind.tn",
+ "rel.ht",
+ "ind.br",
+ "gob.hn",
+ "numata.hokkaido.jp",
+ "int.co",
+ "komforb.se",
+ "int.tt",
+ "int.ci",
+ "medio-campidano.it",
+ "nativeamerican.museum",
+ "higashimatsuyama.saitama.jp",
+ "koya.wakayama.jp",
+ "za.com",
+ "onga.fukuoka.jp",
+ "in.na",
+ "fukui.fukui.jp",
+ "media.aero",
+ "theater.museum",
+ "logistics.aero",
+ "hashimoto.wakayama.jp",
+ "achi.nagano.jp",
+ "jobs",
+ "ws",
+ "defense.tn",
+ "tm.se",
+ "sande.xn--mre-og-romsdal-qqb.no",
+ "imb.br",
+ "saotome.st",
+ "wv.us",
+ "arts.nf",
+ "s3-website-sa-east-1.amazonaws.com",
+ "crotone.it",
+ "lebtimnetz.de",
+ "nara.nara.jp",
+ "arts.museum",
+ "dyndns-home.com",
+ "hioki.kagoshima.jp",
+ "omuta.fukuoka.jp",
+ "narvik.no",
+ "int.tj",
+ "aguni.okinawa.jp",
+ "village.museum",
+ "nagasaki.jp",
+ "lecce.it",
+ "hoylandet.no",
+ "w.se",
+ "sibenik.museum",
+ "higashine.yamagata.jp",
+ "hitachinaka.ibaraki.jp",
+ "yn.cn",
+ "com.fr",
+ "mil.hn",
+ "sorfold.no",
+ "cremona.it",
+ "sagae.yamagata.jp",
+ "shari.hokkaido.jp",
+ "fujishiro.ibaraki.jp",
+ "axis.museum",
+ "bd.se",
+ "asahi.nagano.jp",
+ "mobi.tz",
+ "ternopil.ua",
+ "blogspot.co.nz",
+ "akrehamn.no",
+ "novosibirsk.ru",
+ "brussels.museum",
+ "kids.museum",
+ "numazu.shizuoka.jp",
+ "suisse.museum",
+ "stjohn.museum",
+ "kids.us",
+ "lib.hi.us",
+ "iron.museum",
+ "go.dyndns.org",
+ "jobs.tt",
+ "wi.us",
+ "toyoake.aichi.jp",
+ "s3-website-us-west-1.amazonaws.com",
+ "grosseto.it",
+ "shimoichi.nara.jp",
+ "dyndns-work.com",
+ "hirono.iwate.jp",
+ "finland.museum",
+ "intl.tn",
+ "shitara.aichi.jp",
+ "ws.na",
+ "shinshiro.aichi.jp",
+ "campidano-medio.it",
+ "farmers.museum",
+ "kalmykia.ru",
+ "iraq.museum",
+ "w.bg",
+ "artdeco.museum",
+ "doshi.yamanashi.jp",
+ "technology.museum",
+ "blogspot.co.at",
+ "aizuwakamatsu.fukushima.jp",
+ "motoyama.kochi.jp",
+ "city.nagoya.jp",
+ "wa.us",
+ "tateyama.toyama.jp",
+ "oi.kanagawa.jp",
+ "naka.ibaraki.jp",
+ "osakasayama.osaka.jp",
+ "tobishima.aichi.jp",
+ "motosu.gifu.jp",
+ "eidsberg.no",
+ "vinnytsia.ua",
+ "indian.museum",
+ "chicago.museum",
+ "from-hi.com",
+ "omitama.ibaraki.jp",
+ "blogspot.co.uk",
+ "santafe.museum",
+ "moroyama.saitama.jp",
+ "blogspot.co.il",
+ "kamchatka.ru",
+ "kutno.pl",
+ "nantan.kyoto.jp",
+ "sakai.fukui.jp",
+ "honai.ehime.jp",
+ "otama.fukushima.jp",
+ "higashi.okinawa.jp",
+ "handson.museum",
+ "nuernberg.museum",
+ "fh.se",
+ "arts.co",
+ "songfest.om",
+ "atlanta.museum",
+ "net.sy",
+ "ip6.arpa",
+ "web.co",
+ "higashikagura.hokkaido.jp",
+ "seihi.nagasaki.jp",
+ "glas.museum",
+ "horonobe.hokkaido.jp",
+ "tonsberg.no",
+ "okuma.fukushima.jp",
+ "grajewo.pl",
+ "higashiyama.kyoto.jp",
+ "niigata.jp",
+ "bialystok.pl",
+ "askvoll.no",
+ "neues.museum",
+ "here-for-more.info",
+ "sveio.no",
+ "kristiansund.no",
+ "corporation.museum",
+ "kai.yamanashi.jp",
+ "iq",
+ "dyndns-wiki.com",
+ "takinoue.hokkaido.jp",
+ "radio.br",
+ "arts.ro",
+ "lans.museum",
+ "imageandsound.museum",
+ "fudai.iwate.jp",
+ "shakotan.hokkaido.jp",
+ "rankoshi.hokkaido.jp",
+ "id.lv",
+ "traniandriabarletta.it",
+ "narviika.no",
+ "voronezh.ru",
+ "in.rs",
+ "ballooning.aero",
+ "versailles.museum",
+ "higashiagatsuma.gunma.jp",
+ "web.tj",
+ "i.ph",
+ "gateway.museum",
+ "notaires.km",
+ "selfip.info",
+ "ethnology.museum",
+ "onomichi.hiroshima.jp",
+ "nagano.jp",
+ "kaminokawa.tochigi.jp",
+ "flight.aero",
+ "moriyoshi.akita.jp",
+ "otake.hiroshima.jp",
+ "flesberg.no",
+ "moss.no",
+ "stathelle.no",
+ "takanabe.miyazaki.jp",
+ "moriya.ibaraki.jp",
+ "safety.aero",
+ "wiki.br",
+ "okawa.fukuoka.jp",
+ "dyndns-mail.com",
+ "astronomy.museum",
+ "fukuoka.jp",
+ "tsuga.tochigi.jp",
+ "gifu.gifu.jp",
+ "int.vn",
+ "niikappu.hokkaido.jp",
+ "shiroi.chiba.jp",
+ "mosjoen.no",
+ "nesna.no",
+ "balsan.it",
+ "naha.okinawa.jp",
+ "karasuyama.tochigi.jp",
+ "s3-website-us-west-2.amazonaws.com",
+ "airport.aero",
+ "gonohe.aomori.jp",
+ "eidsvoll.no",
+ "alabama.museum",
+ "kuriyama.hokkaido.jp",
+ "shirakawa.gifu.jp",
+ "shirakawa.fukushima.jp",
+ "karmoy.no",
+ "kepno.pl",
+ "omi.niigata.jp",
+ "historichouses.museum",
+ "miyawaka.fukuoka.jp",
+ "rovno.ua",
+ "uz",
+ "kristiansand.no",
+ "manchester.museum",
+ "gov.do",
+ "gov.dm",
+ "southcarolina.museum",
+ "edu.do",
+ "edu.dm",
+ "com.do",
+ "com.dm",
+ "com.de",
+ "gov.dz",
+ "edu.dz",
+ "nagasu.kumamoto.jp",
+ "cc.mi.us",
+ "com.dz",
+ "go.id",
+ "co.id",
+ "ud.it",
+ "dynathome.net",
+ "taiji.wakayama.jp",
+ "k12.fl.us",
+ "halloffame.museum",
+ "voss.no",
+ "kosei.shiga.jp",
+ "nord-aurdal.no",
+ "uz.ua",
+ "us",
+ "obanazawa.yamagata.jp",
+ "kishiwada.osaka.jp",
+ "historical.museum",
+ "art.do",
+ "s3-website-ap-southeast-1.amazonaws.com",
+ "lib.fl.us",
+ "art.dz",
+ "wy.us",
+ "kasai.hyogo.jp",
+ "shimotsuma.ibaraki.jp",
+ "wroc.pl",
+ "cc.wi.us",
+ "kumiyama.kyoto.jp",
+ "u.se",
+ "reggio-calabria.it",
+ "ut.us",
+ "indianmarket.museum",
+ "cc.vi.us",
+ "forli-cesena.it",
+ "railway.museum",
+ "nsw.edu.au",
+ "tamayu.shimane.jp",
+ "hekinan.aichi.jp",
+ "kashiwara.osaka.jp",
+ "sandoy.no",
+ "bomlo.no",
+ "fukaya.saitama.jp",
+ "illustration.museum",
+ "asahi.chiba.jp",
+ "video.hu",
+ "arkhangelsk.ru",
+ "higashimatsushima.miyagi.jp",
+ "bunkyo.tokyo.jp",
+ "starostwo.gov.pl",
+ "bo.nordland.no",
+ "is-a-nurse.com",
+ "s3-website-ap-northeast-1.amazonaws.com",
+ "sld.do",
+ "asahi.yamagata.jp",
+ "sor-fron.no",
+ "tranoy.no",
+ "ua",
+ "hobby-site.com",
+ "org.tw",
+ "web.ve",
+ "org.bw",
+ "isla.pr",
+ "happou.akita.jp",
+ "us.na",
+ "england.museum",
+ "andriatranibarletta.it",
+ "gob.do",
+ "u.bg",
+ "gallery.museum",
+ "yono.saitama.jp",
+ "vardo.no",
+ "shimokawa.hokkaido.jp",
+ "tarnobrzeg.pl",
+ "tomiya.miyagi.jp",
+ "nasu.tochigi.jp",
+ "yaroslavl.ru",
+ "finnoy.no",
+ "hemsedal.no",
+ "rodoy.no",
+ "dyndns-free.com",
+ "nago.okinawa.jp",
+ "uk",
+ "nakano.tokyo.jp",
+ "massacarrara.it",
+ "org.cw",
+ "sejny.pl",
+ "hadsel.no",
+ "hitoyoshi.kumamoto.jp",
+ "com.ro",
+ "com.re",
+ "go.kr",
+ "co.kr",
+ "seljord.no",
+ "co.ir",
+ "tynset.no",
+ "go.cr",
+ "ed.cr",
+ "gov.rs",
+ "co.cr",
+ "edu.rs",
+ "shinanomachi.nagano.jp",
+ "dnsdojo.net",
+ "is-a-llama.com",
+ "nagatoro.saitama.jp",
+ "yokkaichi.mie.jp",
+ "nakano.nagano.jp",
+ "naie.hokkaido.jp",
+ "annefrank.museum",
+ "cuneo.it",
+ "yazu.tottori.jp",
+ "es.kr",
+ "california.museum",
+ "averoy.no",
+ "luroy.no",
+ "fukuyama.hiroshima.jp",
+ "mil.do",
+ "sakai.osaka.jp",
+ "re.kr",
+ "nagiso.nagano.jp",
+ "gov.ru",
+ "ae.org",
+ "fermo.it",
+ "edu.ru",
+ "com.ru",
+ "farmequipment.museum",
+ "andoy.no",
+ "shimosuwa.nagano.jp",
+ "moriyama.shiga.jp",
+ "tm.fr",
+ "nakanojo.gunma.jp",
+ "hatoyama.saitama.jp",
+ "univ.sn",
+ "nakasatsunai.hokkaido.jp",
+ "nagi.okayama.jp",
+ "niki.hokkaido.jp",
+ "yamato.kumamoto.jp",
+ "rishirifuji.hokkaido.jp",
+ "kawai.iwate.jp",
+ "stv.ru",
+ "uvic.museum",
+ "city.yokohama.jp",
+ "s3-website-ap-southeast-2.amazonaws.com",
+ "tottori.jp",
+ "gov.mo",
+ "gov.me",
+ "ms.kr",
+ "edu.mo",
+ "edu.me",
+ "gov.mk",
+ "edu.mk",
+ "com.mo",
+ "miasta.pl",
+ "com.mk",
+ "gov.mg",
+ "edu.mg",
+ "radoy.no",
+ "com.mg",
+ "co.im",
+ "gov.ma",
+ "kms.ru",
+ "tom.ru",
+ "www.ck",
+ "nedre-eiker.no",
+ "web.nf",
+ "s3-website-us-gov-west-1.amazonaws.com",
+ "ozora.hokkaido.jp",
+ "morioka.iwate.jp",
+ "uri.arpa",
+ "k12.de.us",
+ "mitoyo.kagawa.jp",
+ "reggio-emilia.it",
+ "kounosu.saitama.jp",
+ "money.museum",
+ "seirou.niigata.jp",
+ "urn.arpa",
+ "osakikamijima.hiroshima.jp",
+ "cc.hi.us",
+ "kawanabe.kagoshima.jp",
+ "kouyama.kagoshima.jp",
+ "honefoss.no",
+ "gov.mu",
+ "rnd.ru",
+ "snz.ru",
+ "lib.de.us",
+ "com.mu",
+ "sa.cr",
+ "nirasaki.yamanashi.jp",
+ "k12.dc.us",
+ "uba.ar",
+ "otsuchi.iwate.jp",
+ "station.museum",
+ "kamioka.akita.jp",
+ "sapporo.jp",
+ "tm.km",
+ "misconfused.org",
+ "gov.iq",
+ "edu.iq",
+ "hakui.ishikawa.jp",
+ "gov.mr",
+ "gov.mn",
+ "com.iq",
+ "edu.mn",
+ "meloy.no",
+ "lib.dc.us",
+ "co.tm",
+ "fi.cr",
+ "ginan.gifu.jp",
+ "unbi.ba",
+ "archaeology.museum",
+ "kui.hiroshima.jp",
+ "yuki.ibaraki.jp",
+ "bahcavuotna.no",
+ "yokote.akita.jp",
+ "narusawa.yamanashi.jp",
+ "masoy.no",
+ "sa.edu.au",
+ "cc.ri.us",
+ "tochio.niigata.jp",
+ "gov.ml",
+ "edu.ml",
+ "askoy.no",
+ "floro.no",
+ "com.ml",
+ "takayama.nagano.jp",
+ "malatvuopmi.no",
+ "textile.museum",
+ "uy",
+ "teledata.mz",
+ "hachioji.tokyo.jp",
+ "net.tw",
+ "ivgu.no",
+ "chichibu.saitama.jp",
+ "higashikawa.hokkaido.jp",
+ "unsa.ba",
+ "massa-carrara.it",
+ "zhitomir.ua",
+ "tadaoka.osaka.jp",
+ "yorkshire.museum",
+ "toyotsu.fukuoka.jp",
+ "tottori.tottori.jp",
+ "nc",
+ "ina.nagano.jp",
+ "kameoka.kyoto.jp",
+ "khv.ru",
+ "indianapolis.museum",
+ "arboretum.museum",
+ "shimonita.gunma.jp",
+ "stjordal.no",
+ "memorial.museum",
+ "varoy.no",
+ "e-burg.ru",
+ "jar.ru",
+ "judaica.museum",
+ "bir.ru",
+ "net.cw",
+ "takayama.gunma.jp",
+ "my.id",
+ "orskog.no",
+ "kemerovo.ru",
+ "kunstunddesign.museum",
+ "zoological.museum",
+ "kunstsammlung.museum",
+ "tachikawa.tokyo.jp",
+ "shikokuchuo.ehime.jp",
+ "nc.us",
+ "isen.kagoshima.jp",
+ "int.is",
+ "kostroma.ru",
+ "yokoze.saitama.jp",
+ "chelyabinsk.ru",
+ "mil.ru",
+ "uk.net",
+ "soundandvision.museum",
+ "foundation.museum",
+ "fukushima.fukushima.jp",
+ "vrn.ru",
+ "shichinohe.aomori.jp",
+ "naka.hiroshima.jp",
+ "ogawa.saitama.jp",
+ "kitahata.saga.jp",
+ "malbork.pl",
+ "louvre.museum",
+ "sango.nara.jp",
+ "msk.ru",
+ "aerobatic.aero",
+ "flanders.museum",
+ "ind.in",
+ "k12.ri.us",
+ "tsk.ru",
+ "adachi.tokyo.jp",
+ "istmein.de",
+ "mizuho.tokyo.jp",
+ "obama.fukui.jp",
+ "shiroishi.saga.jp",
+ "obira.hokkaido.jp",
+ "kasuya.fukuoka.jp",
+ "mil.mg",
+ "kawahara.tottori.jp",
+ "matera.it",
+ "nogi.tochigi.jp",
+ "children.museum",
+ "modern.museum",
+ "lib.ri.us",
+ "chosei.chiba.jp",
+ "broadcast.museum",
+ "ug",
+ "niihama.ehime.jp",
+ "honjyo.akita.jp",
+ "irkutsk.ru",
+ "jolster.no",
+ "bungotakada.oita.jp",
+ "ashiya.hyogo.jp",
+ "luzern.museum",
+ "spb.ru",
+ "ashiya.fukuoka.jp",
+ "kanoya.kagoshima.jp",
+ "takko.aomori.jp",
+ "hiraya.nagano.jp",
+ "awaji.hyogo.jp",
+ "nagawa.nagano.jp",
+ "samara.ru",
+ "niigata.niigata.jp",
+ "otobe.hokkaido.jp",
+ "montreal.museum",
+ "cmw.ru",
+ "automotive.museum",
+ "aomori.jp",
+ "childrens.museum",
+ "canada.museum",
+ "asuke.aichi.jp",
+ "ingatlan.hu",
+ "building.museum",
+ "ol.no",
+ "british.museum",
+ "obama.nagasaki.jp",
+ "kasaoka.okayama.jp",
+ "mil.iq",
+ "brindisi.it",
+ "nagano.nagano.jp",
+ "nic.tr",
+ "russia.museum",
+ "nakanoto.ishikawa.jp",
+ "tomioka.gunma.jp",
+ "nic.ar",
+ "k12.mo.us",
+ "k12.me.us",
+ "k12.md.us",
+ "czest.pl",
+ "machida.tokyo.jp",
+ "k12.mi.us",
+ "hs.kr",
+ "australia.museum",
+ "naganohara.gunma.jp",
+ "omihachiman.shiga.jp",
+ "aircraft.aero",
+ "kongsvinger.no",
+ "nagasaki.nagasaki.jp",
+ "k12.ma.us",
+ "kudoyama.wakayama.jp",
+ "k12.ms.us",
+ "higashikagawa.kagawa.jp",
+ "yame.fukuoka.jp",
+ "lib.mo.us",
+ "lib.me.us",
+ "lib.md.us",
+ "kofu.yamanashi.jp",
+ "lib.mi.us",
+ "kyoto.jp",
+ "mochizuki.nagano.jp",
+ "tanohata.iwate.jp",
+ "chitose.hokkaido.jp",
+ "arteducation.museum",
+ "carrier.museum",
+ "dnsdojo.org",
+ "forsand.no",
+ "tosashimizu.kochi.jp",
+ "lib.ma.us",
+ "tjeldsund.no",
+ "dielddanuorri.no",
+ "lib.ms.us",
+ "yakutia.ru",
+ "miners.museum",
+ "yekaterinburg.ru",
+ "nic.tj",
+ "columbus.museum",
+ "kicks-ass.net",
+ "gets-it.net",
+ "tochigi.jp",
+ "utah.museum",
+ "teramo.it",
+ "iki.nagasaki.jp",
+ "zhytomyr.ua",
+ "badaddja.no",
+ "fujioka.gunma.jp",
+ "elburg.museum",
+ "k12.mt.us",
+ "am.br",
+ "miyoshi.aichi.jp",
+ "wakasa.tottori.jp",
+ "shimoda.shizuoka.jp",
+ "moscow.museum",
+ "city.kobe.jp",
+ "vestre-toten.no",
+ "hattfjelldal.no",
+ "dyroy.no",
+ "k12.mn.us",
+ "odate.akita.jp",
+ "nogata.fukuoka.jp",
+ "cinema.museum",
+ "lib.mt.us",
+ "federation.aero",
+ "yamanashi.jp",
+ "from-az.net",
+ "kochi.jp",
+ "brescia.it",
+ "warabi.saitama.jp",
+ "kobayashi.miyazaki.jp",
+ "oregon.museum",
+ "hachinohe.aomori.jp",
+ "omura.nagasaki.jp",
+ "lib.mn.us",
+ "rifu.miyagi.jp",
+ "beiarn.no",
+ "sannohe.aomori.jp",
+ "iveland.no",
+ "higashiura.aichi.jp",
+ "shimokitayama.nara.jp",
+ "uda.nara.jp",
+ "aomori.aomori.jp",
+ "semboku.akita.jp",
+ "stavropol.ru",
+ "czeladz.pl",
+ "web.id",
+ "tv.br",
+ "does-it.net",
+ "ullensaker.no",
+ "mansions.museum",
+ "namie.fukushima.jp",
+ "railroad.museum",
+ "romsa.no",
+ "chita.aichi.jp",
+ "ueno.gunma.jp",
+ "beardu.no",
+ "fm.br",
+ "bauern.museum",
+ "usarts.museum",
+ "touch.museum",
+ "storfjord.no",
+ "nakijin.okinawa.jp",
+ "takahama.aichi.jp",
+ "sakuho.nagano.jp",
+ "aichi.jp",
+ "florida.museum",
+ "gs.st.no",
+ "takaharu.miyazaki.jp",
+ "repbody.aero",
+ "co.mu",
+ "catering.aero",
+ "daisen.akita.jp",
+ "com.uz",
+ "com.ug",
+ "yonabaru.okinawa.jp",
+ "gov.ua",
+ "edu.ua",
+ "kg.kr",
+ "com.ua",
+ "fuossko.no",
+ "from-wv.com",
+ "odawara.kanagawa.jp",
+ "kanie.aichi.jp",
+ "is-a-candidate.org",
+ "beeldengeluid.museum",
+ "trust.museum",
+ "iizuna.nagano.jp",
+ "cloudfront.net",
+ "karpacz.pl",
+ "ochi.kochi.jp",
+ "gjerdrum.no",
+ "mod.uk",
+ "iwatsuki.saitama.jp",
+ "kin.okinawa.jp",
+ "alessandria.it",
+ "higashitsuno.kochi.jp",
+ "rzeszow.pl",
+ "kunst.museum",
+ "anthro.museum",
+ "yasu.shiga.jp",
+ "tatarstan.ru",
+ "rissa.no",
+ "hinohara.tokyo.jp",
+ "rochester.museum",
+ "yamatotakada.nara.jp",
+ "sciencecenter.museum",
+ "is-very-evil.org",
+ "ayagawa.kagawa.jp",
+ "hobby-site.org",
+ "erimo.hokkaido.jp",
+ "owani.aomori.jp",
+ "kotohira.kagawa.jp",
+ "from-wa.com",
+ "ilawa.pl",
+ "exchange.aero",
+ "kumamoto.jp",
+ "otaki.saitama.jp",
+ "museum",
+ "entertainment.aero",
+ "ogawa.nagano.jp",
+ "takehara.hiroshima.jp",
+ "gs.ah.no",
+ "konan.aichi.jp",
+ "s3-fips-us-gov-west-1.amazonaws.com",
+ "pm",
+ "sasayama.hyogo.jp",
+ "itoman.okinawa.jp",
+ "sciencecenters.museum",
+ "ohira.miyagi.jp",
+ "kitashiobara.fukushima.jp",
+ "izhevsk.ru",
+ "fed.us",
+ "kitayama.wakayama.jp",
+ "kouhoku.saga.jp",
+ "trondheim.no",
+ "pr",
+ "pro",
+ "iwanai.hokkaido.jp",
+ "po.it",
+ "pv.it",
+ "pu.it",
+ "pd.it",
+ "malselv.no",
+ "jet.uk",
+ "museum.no",
+ "pz.it",
+ "shiroishi.miyagi.jp",
+ "surgut.ru",
+ "hongo.hiroshima.jp",
+ "leirfjord.no",
+ "gov.sx",
+ "vestre-slidre.no",
+ "encyclopedic.museum",
+ "pt",
+ "pro.az",
+ "carbonia-iglesias.it",
+ "pr.it",
+ "brasil.museum",
+ "ps",
+ "workshop.museum",
+ "toyooka.hyogo.jp",
+ "usantiques.museum",
+ "pe",
+ "school.na",
+ "hyogo.jp",
+ "takaoka.toyama.jp",
+ "pt.it",
+ "museum.tt",
+ "nf",
+ "pe.it",
+ "ogawara.miyagi.jp",
+ "kamifurano.hokkaido.jp",
+ "pr.us",
+ "raisa.no",
+ "pe.ca",
+ "p.se",
+ "gov.cx",
+ "snasa.no",
+ "cbg.ru",
+ "gs.of.no",
+ "nf.ca",
+ "fortmissoula.museum",
+ "yoro.gifu.jp",
+ "school.museum",
+ "per.sg",
+ "pro.tt",
+ "stjordalshalsen.no",
+ "yawata.kyoto.jp",
+ "trysil.no",
+ "takahagi.ibaraki.jp",
+ "pn",
+ "galsa.no",
+ "iwaizumi.iwate.jp",
+ "takayama.gifu.jp",
+ "zushi.kanagawa.jp",
+ "olbia-tempio.it",
+ "frosinone.it",
+ "pro.br",
+ "pi.it",
+ "glass.museum",
+ "yuzawa.niigata.jp",
+ "usenet.pl",
+ "pn.it",
+ "kuchinotsu.nagasaki.jp",
+ "tarui.gifu.jp",
+ "museum.mv",
+ "izu.shizuoka.jp",
+ "post",
+ "pl",
+ "airguard.museum",
+ "midori.gunma.jp",
+ "sarufutsu.hokkaido.jp",
+ "tgory.pl",
+ "tabayama.yamanashi.jp",
+ "motorcycle.museum",
+ "harstad.no",
+ "pa",
+ "ito.shizuoka.jp",
+ "gs.vf.no",
+ "neat-url.com",
+ "oguni.kumamoto.jp",
+ "kuroiso.tochigi.jp",
+ "computer.museum",
+ "nonoichi.ishikawa.jp",
+ "pl.ua",
+ "okayama.jp",
+ "pa.it",
+ "fukudomi.saga.jp",
+ "handa.aichi.jp",
+ "leksvik.no",
+ "square.museum",
+ "p.bg",
+ "nyny.museum",
+ "sumida.tokyo.jp",
+ "austrheim.no",
+ "dni.us",
+ "wake.okayama.jp",
+ "lebork.pl",
+ "wakkanai.hokkaido.jp",
+ "engerdal.no",
+ "masuda.shimane.jp",
+ "heroy.nordland.no",
+ "pa.us",
+ "itabashi.tokyo.jp",
+ "game-server.cc",
+ "pub.sa",
+ "oyabe.toyama.jp",
+ "sa.au",
+ "ph",
+ "schlesisches.museum",
+ "fukuchi.fukuoka.jp",
+ "murayama.yamagata.jp",
+ "pk",
+ "yalta.ua",
+ "algard.no",
+ "priv.me",
+ "toride.ibaraki.jp",
+ "tsuno.miyazaki.jp",
+ "sf.no",
+ "sells-for-less.com",
+ "okinawa.jp",
+ "towada.aomori.jp",
+ "yamato.fukushima.jp",
+ "pro.ec",
+ "pp.ua",
+ "psi.br",
+ "priv.at",
+ "circus.museum",
+ "homeftp.net",
+ "sassari.it",
+ "funahashi.toyama.jp",
+ "is-a-linux-user.org",
+ "nishihara.kumamoto.jp",
+ "kaneyama.yamagata.jp",
+ "co.hu",
+ "izumiotsu.osaka.jp",
+ "ogata.akita.jp",
+ "shijonawate.osaka.jp",
+ "iitate.fukushima.jp",
+ "glogow.pl",
+ "aquarium.museum",
+ "og.ao",
+ "yuzhno-sakhalinsk.ru",
+ "sue.fukuoka.jp",
+ "etnedal.no",
+ "isernia.it",
+ "helsinki.museum",
+ "pro.na",
+ "yamashina.kyoto.jp",
+ "midori.chiba.jp",
+ "kinko.kagoshima.jp",
+ "yamato.kanagawa.jp",
+ "arendal.no",
+ "pri.ee",
+ "perm.ru",
+ "hjelmeland.no",
+ "columbia.museum",
+ "k12.ut.us",
+ "nishiarita.saga.jp",
+ "usui.fukuoka.jp",
+ "krakow.pl",
+ "urausu.hokkaido.jp",
+ "tm.hu",
+ "wakasa.fukui.jp",
+ "ath.cx",
+ "ushistory.museum",
+ "ooshika.nagano.jp",
+ "from-wy.com",
+ "gojome.akita.jp",
+ "izumisano.osaka.jp",
+ "chesapeakebay.museum",
+ "saito.miyazaki.jp",
+ "lib.ut.us",
+ "katori.chiba.jp",
+ "sunndal.no",
+ "upow.gov.pl",
+ "jaworzno.pl",
+ "higashichichibu.saitama.jp",
+ "ohda.shimane.jp",
+ "no.com",
+ "cultural.museum",
+ "wales.museum",
+ "pisa.it",
+ "ebino.miyazaki.jp",
+ "arezzo.it",
+ "software.aero",
+ "urbino-pesaro.it",
+ "pw",
+ "kutchan.hokkaido.jp",
+ "fujimi.saitama.jp",
+ "blogspot.cf",
+ "k12.tx.us",
+ "graz.museum",
+ "oe.yamagata.jp",
+ "ustka.pl",
+ "cambridge.museum",
+ "vf.no",
+ "midtre-gauldal.no",
+ "dyndns-server.com",
+ "oceanographique.museum",
+ "yamatokoriyama.nara.jp",
+ "hiroo.hokkaido.jp",
+ "lib.tx.us",
+ "otaki.nagano.jp",
+ "rockart.museum",
+ "eigersund.no",
+ "university.museum",
+ "nishimera.miyazaki.jp",
+ "from-wi.com",
+ "keisen.fukuoka.jp",
+ "house.museum",
+ "aizumi.tokushima.jp",
+ "yamanakako.yamanashi.jp",
+ "nanae.hokkaido.jp",
+ "sakado.saitama.jp",
+ "kaisei.kanagawa.jp",
+ "sokndal.no",
+ "kuzumaki.iwate.jp",
+ "ebiz.tw",
+ "portal.museum",
+ "yamazoe.nara.jp",
+ "coloradoplateau.museum",
+ "ullensvang.no",
+ "vladivostok.ru",
+ "za.org",
+ "hanno.saitama.jp",
+ "komoro.nagano.jp",
+ "idv.tw",
+ "myoko.niigata.jp",
+ "py",
+ "pilots.museum",
+ "washingtondc.museum",
+ "scientist.aero",
+ "folldal.no",
+ "kasahara.gifu.jp",
+ "bajddar.no",
+ "shimoji.okinawa.jp",
+ "nt.no",
+ "saltdal.no",
+ "github.io",
+ "prof.pr",
+ "linz.museum",
+ "hachijo.tokyo.jp",
+ "kumamoto.kumamoto.jp",
+ "atsuma.hokkaido.jp",
+ "warmia.pl",
+ "museum.mw",
+ "hofu.yamaguchi.jp",
+ "iida.nagano.jp",
+ "toyama.jp",
+ "name.vn",
+ "fujimino.saitama.jp",
+ "chino.nagano.jp",
+ "yomitan.okinawa.jp",
+ "pro.vn",
+ "sakahogi.gifu.jp",
+ "seiro.niigata.jp",
+ "leitungsen.de",
+ "or.th",
+ "port.fr",
+ "is-very-good.org",
+ "williamsburg.museum",
+ "kitahiroshima.hokkaido.jp",
+ "melhus.no",
+ "ostroleka.pl",
+ "elverum.no",
+ "inawashiro.fukushima.jp",
+ "sakura.chiba.jp",
+ "ashoro.hokkaido.jp",
+ "takahashi.okayama.jp",
+ "halsa.no",
+ "obihiro.hokkaido.jp",
+ "hasuda.saitama.jp",
+ "anpachi.gifu.jp",
+ "tajiri.osaka.jp",
+ "kadoma.osaka.jp",
+ "svizzera.museum",
+ "openair.museum",
+ "osaki.miyagi.jp",
+ "tomari.hokkaido.jp",
+ "labour.museum",
+ "yokawa.hyogo.jp",
+ "mobara.chiba.jp",
+ "yamanouchi.nagano.jp",
+ "mihama.mie.jp",
+ "saroma.hokkaido.jp",
+ "pp.az",
+ "brumunddal.no",
+ "s3-us-west-2.amazonaws.com",
+ "nozawaonsen.nagano.jp",
+ "nl.no",
+ "stordal.no",
+ "ikoma.nara.jp",
+ "gov.km",
+ "edu.km",
+ "gov.ki",
+ "com.km",
+ "gov.kz",
+ "edu.ki",
+ "edu.kz",
+ "gov.kg",
+ "com.ki",
+ "edu.kg",
+ "com.kz",
+ "com.kg",
+ "imakane.hokkaido.jp",
+ "krasnoyarsk.ru",
+ "is-a-bruinsfan.org",
+ "namdalseid.no",
+ "ac.th",
+ "gaivuotna.no",
+ "plants.museum",
+ "childrensgarden.museum",
+ "pg",
+ "eiheiji.fukui.jp",
+ "tokai.ibaraki.jp",
+ "blogdns.net",
+ "ogori.fukuoka.jp",
+ "okegawa.saitama.jp",
+ "salzburg.museum",
+ "stokke.no",
+ "sanjo.niigata.jp",
+ "kimitsu.chiba.jp",
+ "pg.it",
+ "southwest.museum",
+ "ina.saitama.jp",
+ "ass.km",
+ "tsu.mie.jp",
+ "priv.no",
+ "morimachi.shizuoka.jp",
+ "priv.pl",
+ "gov.kn",
+ "edu.kn",
+ "bruxelles.museum",
+ "honjo.saitama.jp",
+ "kitamoto.saitama.jp",
+ "drammen.no",
+ "per.nf",
+ "traeumtgerade.de",
+ "nic.im",
+ "nishinoshima.shimane.jp",
+ "environment.museum",
+ "padova.it",
+ "sa.gov.au",
+ "mishima.fukushima.jp",
+ "przeworsk.pl",
+ "luxembourg.museum",
+ "fujimi.nagano.jp",
+ "caserta.it",
+ "hamada.shimane.jp",
+ "workinggroup.aero",
+ "minamata.kumamoto.jp",
+ "uslivinghistory.museum",
+ "science.museum",
+ "iyo.ehime.jp",
+ "homeftp.org",
+ "mashiko.tochigi.jp",
+ "ogimi.okinawa.jp",
+ "kitami.hokkaido.jp",
+ "sondre-land.no",
+ "odessa.ua",
+ "bihoro.hokkaido.jp",
+ "sakai.ibaraki.jp",
+ "asahi.ibaraki.jp",
+ "iruma.saitama.jp",
+ "fredrikstad.no",
+ "minamiise.mie.jp",
+ "minami.tokushima.jp",
+ "mihara.hiroshima.jp",
+ "os.hordaland.no",
+ "lyngdal.no",
+ "takamori.kumamoto.jp",
+ "yabu.hyogo.jp",
+ "ham-radio-op.net",
+ "palace.museum",
+ "ac.se",
+ "wada.nagano.jp",
+ "nishikatsura.yamanashi.jp",
+ "gs.mr.no",
+ "nic.in",
+ "ac.sz",
+ "plantation.museum",
+ "kisofukushima.nagano.jp",
+ "publ.pt",
+ "hinode.tokyo.jp",
+ "saijo.ehime.jp",
+ "manno.kagawa.jp",
+ "gov.my",
+ "ryazan.ru",
+ "priv.hu",
+ "edu.my",
+ "national.museum",
+ "from-vt.com",
+ "com.my",
+ "mihama.chiba.jp",
+ "abiko.chiba.jp",
+ "radom.pl",
+ "kakuda.miyagi.jp",
+ "nt.ro",
+ "anthropology.museum",
+ "yashio.saitama.jp",
+ "palana.ru",
+ "tawaramoto.nara.jp",
+ "kazimierz-dolny.pl",
+ "control.aero",
+ "monzabrianza.it",
+ "miyama.mie.jp",
+ "yamanashi.yamanashi.jp",
+ "tendo.yamagata.jp",
+ "messina.it",
+ "biz.ki",
+ "yokosuka.kanagawa.jp",
+ "ibaraki.jp",
+ "nishihara.okinawa.jp",
+ "otari.nagano.jp",
+ "yoka.hyogo.jp",
+ "okayama.okayama.jp",
+ "agano.niigata.jp",
+ "dazaifu.fukuoka.jp",
+ "tonami.toyama.jp",
+ "sondrio.it",
+ "pordenone.it",
+ "stalowa-wola.pl",
+ "navuotna.no",
+ "aland.fi",
+ "otaki.chiba.jp",
+ "kuromatsunai.hokkaido.jp",
+ "gs.tr.no",
+ "histoire.museum",
+ "field.museum",
+ "asago.hyogo.jp",
+ "s3-us-west-1.amazonaws.com",
+ "aisai.aichi.jp",
+ "risor.no",
+ "geometre-expert.fr",
+ "sorum.no",
+ "mil.km",
+ "marumori.miyagi.jp",
+ "kasama.ibaraki.jp",
+ "gs.fm.no",
+ "mil.kz",
+ "sogndal.no",
+ "mil.kg",
+ "gov.lk",
+ "edu.lk",
+ "minami.kyoto.jp",
+ "shichikashuku.miyagi.jp",
+ "com.lk",
+ "nishiawakura.okayama.jp",
+ "on-the-web.tv",
+ "bl.uk",
+ "from-va.com",
+ "gov.la",
+ "edu.la",
+ "com.la",
+ "co.cl",
+ "yoshikawa.saitama.jp",
+ "bellevue.museum",
+ "gov.lc",
+ "edu.lc",
+ "fujiyoshida.yamanashi.jp",
+ "com.lc",
+ "modum.no",
+ "pila.pl",
+ "poznan.pl",
+ "gov.lb",
+ "edu.lb",
+ "oristano.it",
+ "karikatur.museum",
+ "com.lb",
+ "amber.museum",
+ "yugawa.fukushima.jp",
+ "ppg.br",
+ "yonezawa.yamagata.jp",
+ "fukumitsu.toyama.jp",
+ "minamiboso.chiba.jp",
+ "gov.lt",
+ "livorno.it",
+ "forum.hu",
+ "mil.kr",
+ "kariya.aichi.jp",
+ "gs.tm.no",
+ "stord.no",
+ "gov.lr",
+ "edu.lr",
+ "mulhouse.museum",
+ "iwate.jp",
+ "com.lr",
+ "naamesjevuemie.no",
+ "co.pl",
+ "shimodate.ibaraki.jp",
+ "takamori.nagano.jp",
+ "doomdns.org",
+ "ltd.lk",
+ "omiya.saitama.jp",
+ "architecture.museum",
+ "k12.ks.us",
+ "cci.fr",
+ "ulsan.kr",
+ "entomology.museum",
+ "uhren.museum",
+ "co.nl",
+ "mihama.wakayama.jp",
+ "nishi.fukuoka.jp",
+ "hikimi.shimane.jp",
+ "society.museum",
+ "hamura.tokyo.jp",
+ "hamamatsu.shizuoka.jp",
+ "tokai.aichi.jp",
+ "lib.ks.us",
+ "aarborte.no",
+ "oishida.yamagata.jp",
+ "computerhistory.museum",
+ "asker.no",
+ "cc.mo.us",
+ "tamamura.gunma.jp",
+ "veterinaire.km",
+ "getmyip.com",
+ "yoshioka.gunma.jp",
+ "uozu.toyama.jp",
+ "gov.kp",
+ "edu.kp",
+ "haboro.hokkaido.jp",
+ "cc.co.us",
+ "com.kp",
+ "oamishirasato.chiba.jp",
+ "monzaebrianza.it",
+ "tm.pl",
+ "noshiro.akita.jp",
+ "qc.com",
+ "miyada.nagano.jp",
+ "magadan.ru",
+ "so.gov.pl",
+ "sayama.saitama.jp",
+ "izena.okinawa.jp",
+ "abeno.osaka.jp",
+ "sr.gov.pl",
+ "viterbo.it",
+ "chijiwa.nagasaki.jp",
+ "doesntexist.org",
+ "org.hk",
+ "kommune.no",
+ "oregontrail.museum",
+ "kurume.fukuoka.jp",
+ "community.museum",
+ "mil.my",
+ "monza-brianza.it",
+ "kunimi.fukushima.jp",
+ "grp.lk",
+ "mihama.fukui.jp",
+ "askim.no",
+ "steam.museum",
+ "salerno.it",
+ "heroy.more-og-romsdal.no",
+ "nishikawa.yamagata.jp",
+ "ishinomaki.miyagi.jp",
+ "starachowice.pl",
+ "s3-us-gov-west-1.amazonaws.com",
+ "labor.museum",
+ "taito.tokyo.jp",
+ "yoita.niigata.jp",
+ "academy.museum",
+ "philately.museum",
+ "otago.museum",
+ "chiropractic.museum",
+ "is-very-bad.org",
+ "ueda.nagano.jp",
+ "org.hu",
+ "ako.hyogo.jp",
+ "bv.nl",
+ "kitadaito.okinawa.jp",
+ "salem.museum",
+ "leasing.aero",
+ "blogdns.org",
+ "org.ht",
+ "meguro.tokyo.jp",
+ "sortland.no",
+ "hashima.gifu.jp",
+ "org.hn",
+ "barum.no",
+ "okinoshima.shimane.jp",
+ "takahata.yamagata.jp",
+ "ritto.shiga.jp",
+ "shacknet.nu",
+ "powiat.pl",
+ "settlers.museum",
+ "tambov.ru",
+ "or.id",
+ "communication.museum",
+ "if.ua",
+ "daito.osaka.jp",
+ "mordovia.ru",
+ "tohnosho.chiba.jp",
+ "herad.no",
+ "kashiwazaki.niigata.jp",
+ "sayama.osaka.jp",
+ "togura.nagano.jp",
+ "grandrapids.museum",
+ "jinsekikogen.hiroshima.jp",
+ "ikeda.hokkaido.jp",
+ "higashi.fukuoka.jp",
+ "communications.museum",
+ "okinawa.okinawa.jp",
+ "yokaichiba.chiba.jp",
+ "discovery.museum",
+ "echizen.fukui.jp",
+ "udine.it",
+ "aya.miyazaki.jp",
+ "philadelphia.museum",
+ "toyama.toyama.jp",
+ "ambulance.museum",
+ "tateyama.chiba.jp",
+ "topology.museum",
+ "historisches.museum",
+ "is-a-student.com",
+ "kameyama.mie.jp",
+ "congresodelalengua3.ar",
+ "info",
+ "simple-url.com",
+ "gs.hm.no",
+ "uryu.hokkaido.jp",
+ "tra.kp",
+ "ranzan.saitama.jp",
+ "historisch.museum",
+ "rep.kp",
+ "k12.la.us",
+ "hanamaki.iwate.jp",
+ "info.mv",
+ "ac.id",
+ "shimofusa.chiba.jp",
+ "edu.pe",
+ "gov.pk",
+ "lib.la.us",
+ "edu.pk",
+ "com.pe",
+ "info.at",
+ "com.pk",
+ "gov.ge",
+ "edu.ge",
+ "bytom.pl",
+ "inabe.mie.jp",
+ "gov.gi",
+ "ojiya.niigata.jp",
+ "com.ge",
+ "edu.gi",
+ "co.gg",
+ "gov.gg",
+ "hurum.no",
+ "com.gi",
+ "nakhodka.ru",
+ "edu.pa",
+ "xxx",
+ "cechire.com",
+ "com.pa",
+ "gov.ps",
+ "edu.ps",
+ "com.ps",
+ "minamiizu.shizuoka.jp",
+ "ibaraki.osaka.jp",
+ "go.ug",
+ "suzuka.mie.jp",
+ "saigawa.fukuoka.jp",
+ "co.ug",
+ "nakatombetsu.hokkaido.jp",
+ "chiyoda.tokyo.jp",
+ "newhampshire.museum",
+ "moseushi.hokkaido.jp",
+ "gos.pk",
+ "honjo.akita.jp",
+ "homedns.org",
+ "tarama.okinawa.jp",
+ "is-a-caterer.com",
+ "ostroda.pl",
+ "mragowo.pl",
+ "inf.br",
+ "higashi.fukushima.jp",
+ "raholt.no",
+ "aso.kumamoto.jp",
+ "googlecode.com",
+ "info.tn",
+ "edu.uy",
+ "info.tt",
+ "com.uy",
+ "dnsdojo.com",
+ "muroran.hokkaido.jp",
+ "hachirogata.akita.jp",
+ "mod.gi",
+ "gov.pt",
+ "wf",
+ "edu.pt",
+ "com.pt",
+ "x.se",
+ "is-a-cpa.com",
+ "imabari.ehime.jp",
+ "edu.gt",
+ "is-a-knight.org",
+ "info.nr",
+ "com.gt",
+ "tm.mg",
+ "info.bb",
+ "gov.pr",
+ "gov.pn",
+ "gov.rw",
+ "edu.pr",
+ "inf.cu",
+ "edu.pn",
+ "edu.rw",
+ "castle.museum",
+ "com.pr",
+ "shirosato.ibaraki.jp",
+ "com.rw",
+ "gov.gr",
+ "gov.gn",
+ "edu.gr",
+ "edu.gn",
+ "abu.yamaguchi.jp",
+ "com.gr",
+ "com.gn",
+ "dyndns-pics.com",
+ "overhalla.no",
+ "gon.pk",
+ "cyber.museum",
+ "iwata.shizuoka.jp",
+ "ltd.gi",
+ "or.kr",
+ "avellino.it",
+ "gov.pl",
+ "net.hk",
+ "asakawa.fukushima.jp",
+ "edu.pl",
+ "aizumisato.fukushima.jp",
+ "info.au",
+ "monza-e-della-brianza.it",
+ "com.pl",
+ "pescara.it",
+ "med.pa",
+ "sakuragawa.ibaraki.jp",
+ "or.cr",
+ "chikusei.ibaraki.jp",
+ "is-an-actress.com",
+ "cc.ky.us",
+ "nakaniikawa.toyama.jp",
+ "is-a-guru.com",
+ "lomza.pl",
+ "cieszyn.pl",
+ "rovigo.it",
+ "info.sd",
+ "horology.museum",
+ "hirado.nagasaki.jp",
+ "atm.pl",
+ "biella.it",
+ "sld.pa",
+ "ikeda.nagano.jp",
+ "gsm.pl",
+ "sos.pl",
+ "est.pr",
+ "miasa.nagano.jp",
+ "art.pl",
+ "gob.pe",
+ "sex.pl",
+ "gob.pk",
+ "is-an-artist.com",
+ "miharu.fukushima.jp",
+ "info.na",
+ "x.bg",
+ "microlight.aero",
+ "aisho.shiga.jp",
+ "paroch.k12.ma.us",
+ "info.nf",
+ "s3-website-eu-west-1.amazonaws.com",
+ "gob.pa",
+ "net.ht",
+ "minami.fukuoka.jp",
+ "cc.wy.us",
+ "abo.pa",
+ "oguni.yamagata.jp",
+ "minamifurano.hokkaido.jp",
+ "gov.mw",
+ "pittsburgh.museum",
+ "info.la",
+ "edu.mw",
+ "gop.pk",
+ "iide.yamagata.jp",
+ "com.mw",
+ "net.hn",
+ "info.ec",
+ "co.ag",
+ "dyndns-blog.com",
+ "aid.pl",
+ "yuza.yamagata.jp",
+ "med.pl",
+ "gok.pk",
+ "hamar.no",
+ "sc.kr",
+ "ac.kr",
+ "gub.uy",
+ "ac.ir",
+ "kotoura.tottori.jp",
+ "ina.ibaraki.jp",
+ "ac.cr",
+ "iwamizawa.hokkaido.jp",
+ "seaport.museum",
+ "hamaroy.no",
+ "notaires.fr",
+ "cc.ny.us",
+ "skanland.no",
+ "freemasonry.museum",
+ "potenza.it",
+ "civilisation.museum",
+ "koryo.nara.jp",
+ "fam.pk",
+ "gob.gt",
+ "shirako.chiba.jp",
+ "biz.pk",
+ "embroidery.museum",
+ "minamiaiki.nagano.jp",
+ "bashkiria.ru",
+ "lecco.it",
+ "lincoln.museum",
+ "onagawa.miyagi.jp",
+ "amusement.aero",
+ "hirara.okinawa.jp",
+ "rec.ro",
+ "gda.pl",
+ "minamimaki.nagano.jp",
+ "chiryu.aichi.jp",
+ "yugawara.kanagawa.jp",
+ "shioya.tochigi.jp",
+ "merseine.nu",
+ "ikata.ehime.jp",
+ "vyatka.ru",
+ "aquila.it",
+ "xz.cn",
+ "thruhere.net",
+ "daegu.kr",
+ "mil.pe",
+ "mil.ge",
+ "rel.pl",
+ "ikeda.fukui.jp",
+ "agrar.hu",
+ "tokyo.jp",
+ "ono.fukui.jp",
+ "homeunix.net",
+ "philadelphiaarea.museum",
+ "ac.pr",
+ "uruma.okinawa.jp",
+ "geisei.kochi.jp",
+ "nagato.yamaguchi.jp",
+ "oyama.tochigi.jp",
+ "stockholm.museum",
+ "cherkasy.ua",
+ "dnsalias.net",
+ "name.az",
+ "gotemba.shizuoka.jp",
+ "niimi.okayama.jp",
+ "frankfurt.museum",
+ "nishiokoppe.hokkaido.jp",
+ "ac.im",
+ "xj.cn",
+ "biz.pr",
+ "vevelstad.no",
+ "stange.no",
+ "urawa.saitama.jp",
+ "koriyama.fukushima.jp",
+ "haram.no",
+ "ofunato.iwate.jp",
+ "mazury.pl",
+ "mil.uy",
+ "harima.hyogo.jp",
+ "shimotsuke.tochigi.jp",
+ "choyo.kumamoto.jp",
+ "setouchi.okayama.jp",
+ "biz.pl",
+ "mil.gt",
+ "it.ao",
+ "soo.kagoshima.jp",
+ "for-our.info",
+ "narashino.chiba.jp",
+ "mil.rw",
+ "is-an-entertainer.com",
+ "googleapis.com",
+ "public.museum",
+ "homeunix.org",
+ "edu.pf",
+ "okaya.nagano.jp",
+ "kawara.fukuoka.jp",
+ "com.pf",
+ "mamurogawa.yamagata.jp",
+ "k12.pa.us",
+ "tsukui.kanagawa.jp",
+ "mil.pl",
+ "info.co",
+ "dnsalias.org",
+ "k12.ga.us",
+ "name.qa",
+ "yoshida.saitama.jp",
+ "nichinan.tottori.jp",
+ "swinoujscie.pl",
+ "tamaki.mie.jp",
+ "nom.fr",
+ "tokamachi.niigata.jp",
+ "kaluga.ru",
+ "lib.pa.us",
+ "cargo.aero",
+ "lib.ga.us",
+ "ohira.tochigi.jp",
+ "sykkylven.no",
+ "paris.museum",
+ "belau.pw",
+ "info.pr",
+ "omasvuotna.no",
+ "biz.mw",
+ "bjerkreim.no",
+ "bardu.no",
+ "k12.gu.us",
+ "unnan.shimane.jp",
+ "edu.gp",
+ "kozaki.chiba.jp",
+ "artsandcrafts.museum",
+ "com.gp",
+ "ono.fukushima.jp",
+ "info.ro",
+ "info.pl",
+ "elk.pl",
+ "naustdal.no",
+ "bamble.no",
+ "nesset.no",
+ "birthplace.museum",
+ "k12.pr.us",
+ "ibaraki.ibaraki.jp",
+ "minamiyamashiro.kyoto.jp",
+ "info.pk",
+ "lib.gu.us",
+ "info.ht",
+ "asakuchi.okayama.jp",
+ "omotego.fukushima.jp",
+ "gov.ph",
+ "minamitane.kagoshima.jp",
+ "edu.ph",
+ "go.jp",
+ "hanamigawa.chiba.jp",
+ "ed.jp",
+ "com.ph",
+ "co.jp",
+ "gov.gh",
+ "edu.gh",
+ "ad.jp",
+ "kobierzyce.pl",
+ "annaka.gunma.jp",
+ "detroit.museum",
+ "lib.pr.us",
+ "com.gh",
+ "kamikoani.akita.jp",
+ "telekommunikation.museum",
+ "dontexist.org",
+ "yorii.saitama.jp",
+ "geology.museum",
+ "gr.jp",
+ "ohi.fukui.jp",
+ "higashishirakawa.gifu.jp",
+ "is-a-green.com",
+ "ikawa.akita.jp",
+ "embaixada.st",
+ "org.do",
+ "org.dm",
+ "amakusa.kumamoto.jp",
+ "design.museum",
+ "org.dz",
+ "kosaka.akita.jp",
+ "kembuchi.hokkaido.jp",
+ "ichiba.tokushima.jp",
+ "is-uberleet.com",
+ "szkola.pl",
+ "omachi.nagano.jp",
+ "info.ki",
+ "stuff-4-sale.us",
+ "podhale.pl",
+ "grozny.ru",
+ "sakurai.nara.jp",
+ "snillfjord.no",
+ "lahppi.no",
+ "tagami.niigata.jp",
+ "design.aero",
+ "civilaviation.aero",
+ "is-found.org",
+ "ukiha.fukuoka.jp",
+ "hasami.nagasaki.jp",
+ "endofinternet.net",
+ "jur.pro",
+ "miyama.fukuoka.jp",
+ "med.pro",
+ "zgorzelec.pl",
+ "itayanagi.aomori.jp",
+ "loyalist.museum",
+ "info.hu",
+ "kawaminami.miyazaki.jp",
+ "bolzano.it",
+ "minamiuonuma.niigata.jp",
+ "is-a-painter.com",
+ "kaneyama.fukushima.jp",
+ "is-into-anime.com",
+ "xn--j1amh",
+ "kamikawa.saitama.jp",
+ "xn--wgbl6a",
+ "minamiminowa.nagano.jp",
+ "hasama.oita.jp",
+ "okagaki.fukuoka.jp",
+ "fussa.tokyo.jp",
+ "tsurugashima.saitama.jp",
+ "kartuzy.pl",
+ "yoshino.nara.jp",
+ "nissedal.no",
+ "belluno.it",
+ "mazowsze.pl",
+ "aki.kochi.jp",
+ "ena.gifu.jp",
+ "nishio.aichi.jp",
+ "xn--nnx388a",
+ "xn--asky-ira.no",
+ "kagamino.okayama.jp",
+ "tsuchiura.ibaraki.jp",
+ "newjersey.museum",
+ "mantova.it",
+ "heguri.nara.jp",
+ "takamatsu.kagawa.jp",
+ "spjelkavik.no",
+ "aknoluokta.no",
+ "fujikawa.shizuoka.jp",
+ "yoshida.shizuoka.jp",
+ "xn--skjk-soa.no",
+ "nakatane.kagoshima.jp",
+ "gs.bu.no",
+ "missoula.museum",
+ "planetarium.museum",
+ "is-a-socialist.com",
+ "id.ly",
+ "kitakami.iwate.jp",
+ "nagoya.jp",
+ "shinonsen.hyogo.jp",
+ "minoh.osaka.jp",
+ "karumai.iwate.jp",
+ "is-leet.com",
+ "pskov.ru",
+ "is-a-rockstar.com",
+ "off.ai",
+ "meraker.no",
+ "cc.oh.us",
+ "andria-barletta-trani.it",
+ "oyodo.nara.jp",
+ "kasumigaura.ibaraki.jp",
+ "nationalheritage.museum",
+ "xn--trgstad-r1a.no",
+ "misaki.osaka.jp",
+ "iwade.wakayama.jp",
+ "pavia.it",
+ "chikushino.fukuoka.jp",
+ "org.ro",
+ "monza.it",
+ "dynalias.net",
+ "turek.pl",
+ "suzaka.nagano.jp",
+ "parma.it",
+ "macerata.it",
+ "mil.ph",
+ "padua.it",
+ "or.mu",
+ "mil.gh",
+ "tsukumi.oita.jp",
+ "homelinux.org",
+ "org.rs",
+ "kafjord.no",
+ "xn--brum-voa.no",
+ "bar.pro",
+ "chiyoda.gunma.jp",
+ "agrigento.it",
+ "gov.ky",
+ "tachiarai.fukuoka.jp",
+ "edu.ky",
+ "bedzin.pl",
+ "com.ky",
+ "kitakata.miyazaki.jp",
+ "xn--snes-poa.no",
+ "ikeda.osaka.jp",
+ "historicalsociety.museum",
+ "xn--andy-ira.no",
+ "owariasahi.aichi.jp",
+ "cpa.pro",
+ "org.ru",
+ "selbu.no",
+ "xn--slat-5na.no",
+ "ogawa.ibaraki.jp",
+ "kamikawa.hokkaido.jp",
+ "kawakami.nara.jp",
+ "oceanographic.museum",
+ "okawa.kochi.jp",
+ "cc.nh.us",
+ "kunohe.iwate.jp",
+ "cherkassy.ua",
+ "dynalias.org",
+ "odesa.ua",
+ "sande.vestfold.no",
+ "shibukawa.gunma.jp",
+ "cc.sc.us",
+ "yoshimi.saitama.jp",
+ "net.do",
+ "net.dm",
+ "chungbuk.kr",
+ "decorativearts.museum",
+ "chungnam.kr",
+ "net.dz",
+ "org.mo",
+ "org.me",
+ "org.mk",
+ "isehara.kanagawa.jp",
+ "minamisanriku.miyagi.jp",
+ "org.mg",
+ "afjord.no",
+ "xn--snase-nra.no",
+ "mitaka.tokyo.jp",
+ "yura.wakayama.jp",
+ "kahoku.yamagata.jp",
+ "org.ma",
+ "ac.mu",
+ "us.com",
+ "ibara.okayama.jp",
+ "xn--bievt-0qa.no",
+ "xn--stjrdal-s1a.no",
+ "fukuroi.shizuoka.jp",
+ "minakami.gunma.jp",
+ "sanok.pl",
+ "naklo.pl",
+ "onojo.fukuoka.jp",
+ "xn--bdddj-mrabd.no",
+ "ujitawara.kyoto.jp",
+ "xn--snsa-roa.no",
+ "xn--smla-hra.no",
+ "misugi.mie.jp",
+ "tychy.pl",
+ "karelia.ru",
+ "org.mu",
+ "toyohashi.aichi.jp",
+ "sanfrancisco.museum",
+ "minamioguni.kumamoto.jp",
+ "hisayama.fukuoka.jp",
+ "notodden.no",
+ "iwaki.fukushima.jp",
+ "yusui.kagoshima.jp",
+ "toyokawa.aichi.jp",
+ "klabu.no",
+ "inami.toyama.jp",
+ "izumi.kagoshima.jp",
+ "org.iq",
+ "nichinan.miyazaki.jp",
+ "brandywinevalley.museum",
+ "xn--seral-lra.no",
+ "org.mn",
+ "modalen.no",
+ "oz.au",
+ "s3-sa-east-1.amazonaws.com",
+ "nakai.kanagawa.jp",
+ "hidaka.saitama.jp",
+ "omanmobile.om",
+ "furukawa.miyagi.jp",
+ "kolobrzeg.pl",
+ "org.ml",
+ "clock.museum",
+ "zgrad.ru",
+ "writesthisblog.com",
+ "nesodden.no",
+ "teaches-yoga.com",
+ "tsukuba.ibaraki.jp",
+ "est-mon-blogueur.com",
+ "is-a-conservative.com",
+ "guovdageaidnu.no",
+ "khmelnitskiy.ua",
+ "xn--xkc2dl3a5ee0h",
+ "tsuruta.aomori.jp",
+ "bydgoszcz.pl",
+ "kanagawa.jp",
+ "rahkkeravju.no",
+ "trustee.museum",
+ "is-an-actor.com",
+ "flekkefjord.no",
+ "xn--vard-jra.no",
+ "cymru.museum",
+ "nom.ro",
+ "nom.re",
+ "nishi.osaka.jp",
+ "shingo.aomori.jp",
+ "stargard.pl",
+ "pc.it",
+ "siellak.no",
+ "zao.miyagi.jp",
+ "shinyoshitomi.fukuoka.jp",
+ "history.museum",
+ "is-a-bulls-fan.com",
+ "uk.com",
+ "zamami.okinawa.jp",
+ "xn--sknit-yqa.no",
+ "endofinternet.org",
+ "hirakata.osaka.jp",
+ "gs.svalbard.no",
+ "ne.kr",
+ "law.pro",
+ "seoul.kr",
+ "xn--troms-zua.no",
+ "giessen.museum",
+ "nov.ru",
+ "kiyama.saga.jp",
+ "science-fiction.museum",
+ "sch.uk",
+ "takikawa.hokkaido.jp",
+ "gov.ly",
+ "edu.ly",
+ "com.ly",
+ "ashikaga.tochigi.jp",
+ "oizumi.gunma.jp",
+ "seiyo.ehime.jp",
+ "oketo.hokkaido.jp",
+ "isleofman.museum",
+ "habikino.osaka.jp",
+ "kawakami.nagano.jp",
+ "nt.edu.au",
+ "songdalen.no",
+ "xn--trna-woa.no",
+ "of.no",
+ "sakaki.nagano.jp",
+ "sanuki.kagawa.jp",
+ "takahama.fukui.jp",
+ "gov.mv",
+ "edu.mv",
+ "burghof.museum",
+ "toyako.hokkaido.jp",
+ "com.mv",
+ "namsskogan.no",
+ "hidaka.hokkaido.jp",
+ "xn--vads-jra.no",
+ "brussel.museum",
+ "siedlce.pl",
+ "nom.mg",
+ "drangedal.no",
+ "iglesias-carbonia.it",
+ "joshkar-ola.ru",
+ "unzen.nagasaki.jp",
+ "kamikitayama.nara.jp",
+ "naroy.no",
+ "news.hu",
+ "tsuiki.fukuoka.jp",
+ "idv.hk",
+ "art.museum",
+ "ryuoh.shiga.jp",
+ "psc.br",
+ "oto.fukuoka.jp",
+ "net.ru",
+ "inatsuki.fukuoka.jp",
+ "murakami.niigata.jp",
+ "iwate.iwate.jp",
+ "nishiwaki.hyogo.jp",
+ "sakyo.kyoto.jp",
+ "yufu.oita.jp",
+ "is-a-personaltrainer.com",
+ "gs.ol.no",
+ "stuff-4-sale.org",
+ "hidaka.wakayama.jp",
+ "larsson.museum",
+ "k12.ky.us",
+ "oystre-slidre.no",
+ "tomakomai.hokkaido.jp",
+ "gyokuto.kumamoto.jp",
+ "kosuge.yamanashi.jp",
+ "incheon.kr",
+ "med.ly",
+ "usuki.oita.jp",
+ "net.mo",
+ "net.me",
+ "net.mk",
+ "lib.ky.us",
+ "xn--berlevg-jxa.no",
+ "is-a-techie.com",
+ "steigen.no",
+ "xn--sr-aurdal-l8a.no",
+ "ayase.kanagawa.jp",
+ "bristol.museum",
+ "and.museum",
+ "is-a-doctor.com",
+ "lg.jp",
+ "net.ma",
+ "air.museum",
+ "xn--zf0avx.hk",
+ "indiana.museum",
+ "xn--vrggt-xqad.no",
+ "xn--j6w193g",
+ "eng.pro",
+ "ddr.museum",
+ "mjondalen.no",
+ "yashiro.hyogo.jp",
+ "gamagori.aichi.jp",
+ "tokuyama.yamaguchi.jp",
+ "yao.osaka.jp",
+ "kahoku.ishikawa.jp",
+ "minamidaito.okinawa.jp",
+ "xn--55qx5d.cn",
+ "sukumo.kochi.jp",
+ "net.mu",
+ "is-a-geek.org",
+ "pyatigorsk.ru",
+ "tonaki.okinawa.jp",
+ "government.aero",
+ "kharkov.ua",
+ "kharkiv.ua",
+ "net.iq",
+ "gs.nl.no",
+ "rotorcraft.aero",
+ "miyagi.jp",
+ "bus.museum",
+ "daigo.ibaraki.jp",
+ "yahaba.iwate.jp",
+ "nkz.ru",
+ "akiruno.tokyo.jp",
+ "skjak.no",
+ "is-a-hard-worker.com",
+ "net.ml",
+ "mad.museum",
+ "chikuhoku.nagano.jp",
+ "plc.co.im",
+ "ac.ru",
+ "xn--s-1fa.no",
+ "usdecorativearts.museum",
+ "uy.com",
+ "xn--smna-gra.no",
+ "nsk.ru",
+ "gloppen.no",
+ "readmyblog.org",
+ "in.th",
+ "khmelnytskyi.ua",
+ "doomdns.com",
+ "ardal.no",
+ "misaki.okayama.jp",
+ "ichinomiya.chiba.jp",
+ "nagai.yamagata.jp",
+ "sekikawa.niigata.jp",
+ "izunokuni.shizuoka.jp",
+ "barrell-of-knowledge.info",
+ "novara.it",
+ "katagami.akita.jp",
+ "kitagata.saga.jp",
+ "can.museum",
+ "ulan-ude.ru",
+ "bando.ibaraki.jp",
+ "inami.wakayama.jp",
+ "biz.mv",
+ "jamal.ru",
+ "toyoura.hokkaido.jp",
+ "xn--bmlo-gra.no",
+ "nuoro.it",
+ "moriguchi.osaka.jp",
+ "oseto.nagasaki.jp",
+ "xn--b-5ga.nordland.no",
+ "horokanai.hokkaido.jp",
+ "aerodrome.aero",
+ "nalchik.ru",
+ "mil.mv",
+ "chikuma.nagano.jp",
+ "beppu.oita.jp",
+ "yamatsuri.fukushima.jp",
+ "ski.museum",
+ "kitagawa.kochi.jp",
+ "palmsprings.museum",
+ "for-more.biz",
+ "nishinomiya.hyogo.jp",
+ "xn--tn0ag.hk",
+ "tas.gov.au",
+ "org.uz",
+ "tokigawa.saitama.jp",
+ "oarai.ibaraki.jp",
+ "org.ug",
+ "oki.fukuoka.jp",
+ "doesntexist.com",
+ "nishitosa.kochi.jp",
+ "is-a-blogger.com",
+ "org.ua",
+ "minokamo.gifu.jp",
+ "vologda.ru",
+ "pulawy.pl",
+ "kadogawa.miyazaki.jp",
+ "mykolaiv.ua",
+ "nature.museum",
+ "badajoz.museum",
+ "usa.oita.jp",
+ "isumi.chiba.jp",
+ "tsuruoka.yamagata.jp",
+ "malopolska.pl",
+ "xn--55qx5d.hk",
+ "xn--btsfjord-9za.no",
+ "fjell.no",
+ "is-a-hunter.com",
+ "xn--srum-gra.no",
+ "cc.md.us",
+ "torahime.shiga.jp",
+ "ogano.saitama.jp",
+ "fujikawa.yamanashi.jp",
+ "cc.id.us",
+ "varggat.no",
+ "minamiawaji.hyogo.jp",
+ "mihara.kochi.jp",
+ "basel.museum",
+ "shimogo.fukushima.jp",
+ "casadelamoneda.museum",
+ "nasushiobara.tochigi.jp",
+ "xn--sandy-yua.no",
+ "webhop.net",
+ "blogdns.com",
+ "xn--zf0ao64a.tw",
+ "gov.py",
+ "edu.py",
+ "office-on-the.net",
+ "barrel-of-knowledge.info",
+ "com.py",
+ "nikaho.akita.jp",
+ "isteingeek.de",
+ "com.gy",
+ "airtraffic.aero",
+ "shell.museum",
+ "xn--bjddar-pta.no",
+ "urbinopesaro.it",
+ "xn--vgan-qoa.no",
+ "is-a-lawyer.com",
+ "oygarden.no",
+ "ivano-frankivsk.ua",
+ "kunigami.okinawa.jp",
+ "gs.hl.no",
+ "kagamiishi.fukushima.jp",
+ "imari.saga.jp",
+ "kamikawa.hyogo.jp",
+ "kitaura.miyazaki.jp",
+ "omachi.saga.jp",
+ "zoology.museum",
+ "family.museum",
+ "ogaki.gifu.jp",
+ "works.aero",
+ "hotel.tz",
+ "western.museum",
+ "xn--sr-odal-q1a.no",
+ "cc.nd.us",
+ "pf",
+ "kikugawa.shizuoka.jp",
+ "hotel.lk",
+ "bologna.it",
+ "tsuno.kochi.jp",
+ "culturalcenter.museum",
+ "environmentalconservation.museum",
+ "nango.fukushima.jp",
+ "xn--trany-yua.no",
+ "kitagawa.miyazaki.jp",
+ "gs.rl.no",
+ "nishikata.tochigi.jp",
+ "shingu.wakayama.jp",
+ "izumi.osaka.jp",
+ "hotel.hu",
+ "xn--bhcavuotna-s4a.no",
+ "ouchi.saga.jp",
+ "xn--sandnessjen-ogb.no",
+ "xn--io0a7i.cn",
+ "bjarkoy.no",
+ "uwajima.ehime.jp",
+ "xn--vgsy-qoa0j.no",
+ "kakegawa.shizuoka.jp",
+ "yoshinogari.saga.jp",
+ "kinokawa.wakayama.jp",
+ "oshima.tokyo.jp",
+ "carboniaiglesias.it",
+ "sunagawa.hokkaido.jp",
+ "nagaokakyo.kyoto.jp",
+ "minami-alps.yamanashi.jp",
+ "grong.no",
+ "tahara.aichi.jp",
+ "sagamihara.kanagawa.jp",
+ "north.museum",
+ "lavagis.no",
+ "yawatahama.ehime.jp",
+ "nanmoku.gunma.jp",
+ "ohkura.yamagata.jp",
+ "inagawa.hyogo.jp",
+ "cc.or.us",
+ "kumagaya.saitama.jp",
+ "kawakita.ishikawa.jp",
+ "xn--ciqpn.hk",
+ "inzai.chiba.jp",
+ "dreamhosters.com",
+ "uzhgorod.ua",
+ "mihama.aichi.jp",
+ "yaotsu.gifu.jp",
+ "xn--xkc2al3hye2a",
+ "itami.hyogo.jp",
+ "hobol.no",
+ "miyake.nara.jp",
+ "iki.fi",
+ "sklep.pl",
+ "tochigi.tochigi.jp",
+ "osteroy.no",
+ "is-a-patsfan.org",
+ "yokohama.jp",
+ "net.uz",
+ "wakayama.jp",
+ "net.ua",
+ "xn--bearalvhki-y4a.no",
+ "shiogama.miyagi.jp",
+ "furudono.fukushima.jp",
+ "spy.museum",
+ "kurogi.fukuoka.jp",
+ "mil.py",
+ "cc.pr.us",
+ "parachuting.aero",
+ "kanzaki.saga.jp",
+ "nobeoka.miyazaki.jp",
+ "nacion.ar",
+ "setagaya.tokyo.jp",
+ "skjervoy.no",
+ "klepp.no",
+ "atsugi.kanagawa.jp",
+ "air-traffic-control.aero",
+ "inagi.tokyo.jp",
+ "nsn.us",
+ "xn--io0a7i.hk",
+ "nls.uk",
+ "miyako.fukuoka.jp",
+ "nel.uk",
+ "fukagawa.hokkaido.jp",
+ "samukawa.kanagawa.jp",
+ "championship.aero",
+ "vefsn.no",
+ "tydal.no",
+ "aogaki.hyogo.jp",
+ "xn--sr-fron-q1a.no",
+ "ariake.saga.jp",
+ "kanegasaki.iwate.jp",
+ "kamimine.saga.jp",
+ "takagi.nagano.jp",
+ "cc.ar.us",
+ "assedic.fr",
+ "is-very-sweet.org",
+ "nt.au",
+ "soc.lk",
+ "fuefuki.yamanashi.jp",
+ "kitakyushu.jp",
+ "cc.nm.us",
+ "ono.hyogo.jp",
+ "nanporo.hokkaido.jp",
+ "sakura.tochigi.jp",
+ "kanuma.tochigi.jp",
+ "xn--bod-2na.no",
+ "of.by",
+ "miyakonojo.miyazaki.jp",
+ "mizumaki.fukuoka.jp",
+ "nesseby.no",
+ "nesoddtangen.no",
+ "is-a-photographer.com",
+ "is-a-player.com",
+ "tv.sd",
+ "principe.st",
+ "kasuga.hyogo.jp",
+ "ushuaia.museum",
+ "koshu.yamanashi.jp",
+ "kasuga.fukuoka.jp",
+ "dnepropetrovsk.ua",
+ "nanto.toyama.jp",
+ "skydiving.aero",
+ "ikeda.gifu.jp",
+ "yaita.tochigi.jp",
+ "izumozaki.niigata.jp",
+ "isahaya.nagasaki.jp",
+ "norddal.no",
+ "timekeeping.museum",
+ "ostrowwlkp.pl",
+ "xn--bhccavuotna-k7a.no",
+ "vadso.no",
+ "nuremberg.museum",
+ "kamakura.kanagawa.jp",
+ "worse-than.tv",
+ "leangaviika.no",
+ "uchinomi.kagawa.jp",
+ "dnipropetrovsk.ua",
+ "kagami.kochi.jp",
+ "kochi.kochi.jp",
+ "suwalki.pl",
+ "kopervik.no",
+ "iheya.okinawa.jp",
+ "iris.arpa",
+ "togakushi.nagano.jp",
+ "freight.aero",
+ "kamigori.hyogo.jp",
+ "is-lost.org",
+ "jessheim.no",
+ "tomigusuku.okinawa.jp",
+ "sanagochi.tokushima.jp",
+ "nishiaizu.fukushima.jp",
+ "buyshouses.net",
+ "tozsde.hu",
+ "tsuru.yamanashi.jp",
+ "jan-mayen.no",
+ "minamiashigara.kanagawa.jp",
+ "hatogaya.saitama.jp",
+ "nordreisa.no",
+ "museumcenter.museum",
+ "xn--czrw28b.tw",
+ "rhcloud.com",
+ "xn--vg-yiab.no",
+ "civilwar.museum",
+ "sch.lk",
+ "nachikatsuura.wakayama.jp",
+ "szczecin.pl",
+ "kawaguchi.saitama.jp",
+ "yusuhara.kochi.jp",
+ "id.ir",
+ "gwangju.kr",
+ "scotland.museum",
+ "myphotos.cc",
+ "yurihonjo.akita.jp",
+ "org.km",
+ "maryland.museum",
+ "org.ki",
+ "org.kz",
+ "org.kg",
+ "nemuro.hokkaido.jp",
+ "nakadomari.aomori.jp",
+ "takanezawa.tochigi.jp",
+ "urakawa.hokkaido.jp",
+ "kitagata.gifu.jp",
+ "xn--stjrdalshalsen-sqb.no",
+ "shingu.hyogo.jp",
+ "xn--aurskog-hland-jnb.no",
+ "dyndns-office.com",
+ "ozu.kumamoto.jp",
+ "shingu.fukuoka.jp",
+ "nabari.mie.jp",
+ "pb.ao",
+ "tsurugi.ishikawa.jp",
+ "xn--vler-qoa.hedmark.no",
+ "nagaoka.niigata.jp",
+ "or.ug",
+ "org.kn",
+ "tsushima.aichi.jp",
+ "xn--vestvgy-ixa6o.no",
+ "uchinada.ishikawa.jp",
+ "castres.museum",
+ "hokuryu.hokkaido.jp",
+ "chofu.tokyo.jp",
+ "hikari.yamaguchi.jp",
+ "kiyokawa.kanagawa.jp",
+ "yoichi.hokkaido.jp",
+ "nowaruda.pl",
+ "izumo.shimane.jp",
+ "webhop.org",
+ "choshi.chiba.jp",
+ "its.me",
+ "izumizaki.fukushima.jp",
+ "int.ru",
+ "web.do",
+ "dontexist.com",
+ "kasamatsu.gifu.jp",
+ "naturalhistory.museum",
+ "nakayama.yamagata.jp",
+ "utsunomiya.tochigi.jp",
+ "missile.museum",
+ "mansion.museum",
+ "sc.ug",
+ "ac.ug",
+ "other.nf",
+ "osoyro.no",
+ "nakatsugawa.gifu.jp",
+ "cc.gu.us",
+ "org.my",
+ "kozagawa.wakayama.jp",
+ "warszawa.pl",
+ "naturbruksgymn.se",
+ "sec.ps",
+ "obuse.nagano.jp",
+ "olecko.pl",
+ "hichiso.gifu.jp",
+ "nerima.tokyo.jp",
+ "nt.gov.au",
+ "nanjo.okinawa.jp",
+ "veterinaire.fr",
+ "xn--wcvs22d.hk",
+ "is-a-democrat.com",
+ "shimonoseki.yamaguchi.jp",
+ "nom.km",
+ "naturalhistorymuseum.museum",
+ "esashi.hokkaido.jp",
+ "ac.ng",
+ "union.aero",
+ "ogose.saitama.jp",
+ "hakodate.hokkaido.jp",
+ "org.lk",
+ "brand.se",
+ "org.la",
+ "nawrastelecom.om",
+ "nordre-land.no",
+ "vegarshei.no",
+ "org.ls",
+ "kakogawa.hyogo.jp",
+ "org.lc",
+ "org.lb",
+ "net.ki",
+ "net.kz",
+ "net.kg",
+ "xn--nry-yla5g.no",
+ "shinkamigoto.nagasaki.jp",
+ "vossevangen.no",
+ "iz.hr",
+ "taishi.osaka.jp",
+ "iwama.ibaraki.jp",
+ "shibuya.tokyo.jp",
+ "chikuzen.fukuoka.jp",
+ "org.lr",
+ "sch.gg",
+ "akishima.tokyo.jp",
+ "xn--tysvr-vra.no",
+ "wa.edu.au",
+ "susaki.kochi.jp",
+ "skierva.no",
+ "xn--bjarky-fya.no",
+ "homeunix.com",
+ "net.kn",
+ "hayakawa.yamanashi.jp",
+ "dnsalias.com",
+ "wakuya.miyagi.jp",
+ "yamanobe.yamagata.jp",
+ "org.kp",
+ "homelinux.com",
+ "is-an-accountant.com",
+ "nagahama.shiga.jp",
+ "abashiri.hokkaido.jp",
+ "is-into-cars.com",
+ "kawagoe.saitama.jp",
+ "kudamatsu.yamaguchi.jp",
+ "batsfjord.no",
+ "omanpost.om",
+ "xn--45brj9c",
+ "natori.miyagi.jp",
+ "is-a-teacher.com",
+ "costume.museum",
+ "is-a-anarchist.com",
+ "podlasie.pl",
+ "xn--s9brj9c",
+ "is-by.us",
+ "xn--vry-yla5g.no",
+ "komaki.aichi.jp",
+ "vagsoy.no",
+ "coastaldefence.museum",
+ "onjuku.chiba.jp",
+ "ibestad.no",
+ "nishiizu.shizuoka.jp",
+ "otsuki.yamanashi.jp",
+ "kujukuri.chiba.jp",
+ "or.jp",
+ "is-a-musician.com",
+ "net.my",
+ "hirogawa.wakayama.jp",
+ "nakamura.kochi.jp",
+ "gov.lv",
+ "youth.museum",
+ "edu.lv",
+ "is-an-anarchist.com",
+ "com.lv",
+ "funagata.yamagata.jp",
+ "kawamata.fukushima.jp",
+ "balsfjord.no",
+ "ujiie.tochigi.jp",
+ "is-into-cartoons.com",
+ "nayoro.hokkaido.jp",
+ "nishigo.fukushima.jp",
+ "is-a-landscaper.com",
+ "tajimi.gifu.jp",
+ "net.lk",
+ "net.la",
+ "is-certified.com",
+ "oppegard.no",
+ "qld.gov.au",
+ "ac.jp",
+ "shishikui.tokushima.jp",
+ "net.lc",
+ "tsushima.nagasaki.jp",
+ "culture.museum",
+ "windmill.museum",
+ "sakegawa.yamagata.jp",
+ "dontexist.net",
+ "net.lb",
+ "xn--wgbh1c",
+ "yasuoka.nagano.jp",
+ "itano.tokushima.jp",
+ "whaling.museum",
+ "asn.lv",
+ "us.org",
+ "org.pe",
+ "otofuke.hokkaido.jp",
+ "org.pk",
+ "net.lr",
+ "udm.ru",
+ "org.ge",
+ "org.gi",
+ "nishinoomote.kagoshima.jp",
+ "org.gg",
+ "org.pa",
+ "org.ps",
+ "nanao.ishikawa.jp",
+ "ambulance.aero",
+ "virtuel.museum",
+ "marylhurst.museum",
+ "virtual.museum",
+ "bialowieza.pl",
+ "org.uy",
+ "oguchi.aichi.jp",
+ "utsira.no",
+ "org.pt",
+ "aca.pro",
+ "org.gt",
+ "org.pr",
+ "org.pn",
+ "hembygdsforbund.museum",
+ "pro.ht",
+ "org.gr",
+ "org.gn",
+ "promocion.ar",
+ "nagara.chiba.jp",
+ "is-a-liberal.com",
+ "settlement.museum",
+ "org.pl",
+ "aeroport.fr",
+ "id.au",
+ "elasticbeanstalk.com",
+ "chirurgiens-dentistes.fr",
+ "isa.us",
+ "mil.lv",
+ "udono.mie.jp",
+ "namerikawa.toyama.jp",
+ "gs.sf.no",
+ "dynalias.com",
+ "xn--blt-elab.no",
+ "bieszczady.pl",
+ "xn--slt-elab.no",
+ "est-le-patron.com",
+ "hidaka.kochi.jp",
+ "kumakogen.ehime.jp",
+ "org.mw",
+ "pol.ht",
+ "baghdad.museum",
+ "is-a-geek.com",
+ "newmexico.museum",
+ "xn--bidr-5nac.no",
+ "kashiwa.chiba.jp",
+ "homelinux.net",
+ "www.ro",
+ "cc.ok.us",
+ "huissier-justice.fr",
+ "isshiki.aichi.jp",
+ "info.vn",
+ "info.ve",
+ "nom.pe",
+ "presse.ci",
+ "phoenix.museum",
+ "gjesdal.no",
+ "nom.pa",
+ "is-a-soxfan.org",
+ "norfolk.museum",
+ "porsangu.no",
+ "nordkapp.no",
+ "nieruchomosci.pl",
+ "kawagoe.mie.jp",
+ "valer.ostfold.no",
+ "presse.km",
+ "napoli.it",
+ "taishi.hyogo.jp",
+ "consulado.st",
+ "gausdal.no",
+ "net.pe",
+ "ne.ug",
+ "net.pk",
+ "kakamigahara.gifu.jp",
+ "net.ge",
+ "oxford.museum",
+ "net.gg",
+ "net.pa",
+ "net.ps",
+ "undersea.museum",
+ "iglesiascarbonia.it",
+ "akashi.hyogo.jp",
+ "nom.pl",
+ "org.pf",
+ "prd.fr",
+ "net.uy",
+ "net.pt",
+ "pilot.aero",
+ "net.gt",
+ "schokoladen.museum",
+ "presse.ml",
+ "net.pr",
+ "net.pn",
+ "net.rw",
+ "net.gr",
+ "net.gn",
+ "ora.gunma.jp",
+ "cc.ak.us",
+ "poltava.ua",
+ "nishiazai.shiga.jp",
+ "pp.se",
+ "ota.gunma.jp",
+ "net.pl",
+ "org.gp",
+ "pistoia.it",
+ "xn--sr-varanger-ggb.no",
+ "valer.hedmark.no",
+ "kasukabe.saitama.jp",
+ "org.ph",
+ "is-a-republican.com",
+ "wa.au",
+ "org.gh",
+ "project.museum",
+ "marburg.museum",
+ "ngo.lk",
+ "yamada.toyama.jp",
+ "wakayama.wakayama.jp",
+ "is-a-libertarian.com",
+ "issmarterthanyou.com",
+ "is-a-nascarfan.com",
+ "oskol.ru",
+ "parliament.uk",
+ "localhistory.museum",
+ "net.mw",
+ "cc.fl.us",
+ "namikata.ehime.jp",
+ "inuyama.aichi.jp",
+ "gemological.museum",
+ "xn--brnnysund-m8ac.no",
+ "cc.il.us",
+ "nankoku.kochi.jp",
+ "museumvereniging.museum",
+ "xn--node",
+ "is-a-therapist.com",
+ "scrapper-site.net",
+ "nakama.fukuoka.jp",
+ "sodegaura.chiba.jp",
+ "yanaizu.fukushima.jp",
+ "namsos.no",
+ "hanggliding.aero",
+ "tcm.museum",
+ "munakata.fukuoka.jp",
+ "yakumo.shimane.jp",
+ "act.gov.au",
+ "consultant.aero",
+ "bifuka.hokkaido.jp",
+ "xn--nmesjevuemie-tcba.no",
+ "xn--sndre-land-0cb.no",
+ "is-a-designer.com",
+ "ise.mie.jp",
+ "xn--vegrshei-c0a.no",
+ "imperia.it",
+ "nikolaev.ua",
+ "is-a-geek.net",
+ "vaksdal.no",
+ "newport.museum",
+ "cc.al.us",
+ "okoppe.hokkaido.jp",
+ "xn--sknland-fxa.no",
+ "fuchu.hiroshima.jp",
+ "ichinomiya.aichi.jp",
+ "kitakata.fukushima.jp",
+ "xn--comunicaes-v6a2o.museum",
+ "xn--jlster-bya.no",
+ "sch.ly",
+ "kamogawa.chiba.jp",
+ "oryol.ru",
+ "udmurtia.ru",
+ "is-gone.com",
+ "yakumo.hokkaido.jp",
+ "net.gp",
+ "ne.jp",
+ "equipment.aero",
+ "iijima.nagano.jp",
+ "ogasawara.tokyo.jp",
+ "preservation.museum",
+ "org.ky",
+ "suifu.ibaraki.jp",
+ "nagareyama.chiba.jp",
+ "is-an-engineer.com",
+ "judygarland.museum",
+ "net.ph",
+ "tsuruga.fukui.jp",
+ "is-with-theband.com",
+ "schweiz.museum",
+ "omigawa.chiba.jp",
+ "navigation.aero",
+ "presse.fr",
+ "paleo.museum",
+ "hamburg.museum",
+ "xn--brnny-wuac.no",
+ "xn--tjme-hra.no",
+ "kiryu.gunma.jp",
+ "pol.dz",
+ "piacenza.it",
+ "vic.gov.au",
+ "nyuzen.toyama.jp",
+ "xn--o3cw4h",
+ "interactive.museum",
+ "in-addr.arpa",
+ "motegi.tochigi.jp",
+ "nakamichi.yamanashi.jp",
+ "tamakawa.fukushima.jp",
+ "hirokawa.fukuoka.jp",
+ "is-a-celticsfan.org",
+ "xn--9dbhblg6di.museum",
+ "kawaue.gifu.jp",
+ "fauske.no",
+ "usgarden.museum",
+ "xn--srfold-bya.no",
+ "matsusaka.mie.jp",
+ "inderoy.no",
+ "wassamu.hokkaido.jp",
+ "norilsk.ru",
+ "ichinohe.iwate.jp",
+ "is-into-games.com",
+ "tarumizu.kagoshima.jp",
+ "wa.gov.au",
+ "boldlygoingnowhere.org",
+ "webhop.biz",
+ "xn--skjervy-v1a.no",
+ "pruszkow.pl",
+ "tomika.gifu.jp",
+ "ngo.pl",
+ "hanyu.saitama.jp",
+ "pe.kr",
+ "oshima.yamaguchi.jp",
+ "shiso.hyogo.jp",
+ "olsztyn.pl",
+ "wolomin.pl",
+ "uenohara.yamanashi.jp",
+ "is-very-nice.org",
+ "yamada.fukuoka.jp",
+ "miyako.iwate.jp",
+ "xn--jrpeland-54a.no",
+ "is-a-financialadvisor.com",
+ "umi.fukuoka.jp",
+ "ptz.ru",
+ "int.lk",
+ "utazas.hu",
+ "fuchu.toyama.jp",
+ "org.ly",
+ "edu.mx",
+ "xn--skierv-uta.no",
+ "int.la",
+ "com.mx",
+ "ikusaka.nagano.jp",
+ "yawara.ibaraki.jp",
+ "matsue.shimane.jp",
+ "prd.mg",
+ "tokorozawa.saitama.jp",
+ "org.mv",
+ "yamamoto.miyagi.jp",
+ "net.ky",
+ "chikugo.fukuoka.jp",
+ "nyc.mn",
+ "otaru.hokkaido.jp",
+ "prato.it",
+ "is-a-bookkeeper.com",
+ "lajolla.museum",
+ "shisui.chiba.jp",
+ "walbrzych.pl",
+ "ninohe.iwate.jp",
+ "ichinoseki.iwate.jp",
+ "emergency.aero",
+ "alaska.museum",
+ "modelling.aero",
+ "xn--sgne-gra.no",
+ "consulting.aero",
+ "ninomiya.kanagawa.jp",
+ "nikko.tochigi.jp",
+ "wielun.pl",
+ "nic.uk",
+ "yuu.yamaguchi.jp",
+ "komagane.nagano.jp",
+ "isesaki.gunma.jp",
+ "gob.mx",
+ "chikujo.fukuoka.jp",
+ "yaese.okinawa.jp",
+ "is-a-chef.org",
+ "watari.miyagi.jp",
+ "mitake.gifu.jp",
+ "xn--b-5ga.telemark.no",
+ "matsubara.osaka.jp",
+ "kamagaya.chiba.jp",
+ "sasaguri.fukuoka.jp",
+ "katsuragi.nara.jp",
+ "sologne.museum",
+ "nosegawa.nara.jp",
+ "wajima.ishikawa.jp",
+ "kommunalforbund.se",
+ "trolley.museum",
+ "yachimata.chiba.jp",
+ "pomorskie.pl",
+ "sosnowiec.pl",
+ "matsuzaki.shizuoka.jp",
+ "info.az",
+ "xn--tnsberg-q1a.no",
+ "web.lk",
+ "xn--srreisa-q1a.no",
+ "samegawa.fukushima.jp",
+ "ngo.ph",
+ "pasadena.museum",
+ "iiyama.nagano.jp",
+ "midsund.no",
+ "fetsund.no",
+ "aurskog-holand.no",
+ "sekigahara.gifu.jp",
+ "net.ly",
+ "jfk.museum",
+ "info.tz",
+ "alesund.no",
+ "ishikawa.jp",
+ "zaporizhzhia.ua",
+ "net.mv",
+ "civilization.museum",
+ "yuasa.wakayama.jp",
+ "photography.museum",
+ "lucerne.museum",
+ "farsund.no",
+ "org.py",
+ "unjarga.no",
+ "group.aero",
+ "zachpomor.pl",
+ "mitou.yamaguchi.jp",
+ "xn--od0alg.cn",
+ "matsubushi.saitama.jp",
+ "watchandclock.museum",
+ "accident-investigation.aero",
+ "xn--ygbi2ammx",
+ "wallonie.museum",
+ "wegrow.pl",
+ "ind.gt",
+ "int.pt",
+ "xn--yfro4i67o",
+ "ibigawa.gifu.jp",
+ "xn--vre-eiker-k8a.no",
+ "namegawa.saitama.jp",
+ "parti.se",
+ "endoftheinternet.org",
+ "int.rw",
+ "xn--aroport-bya.ci",
+ "xn--ystre-slidre-ujb.no",
+ "niiza.saitama.jp",
+ "chikuho.fukuoka.jp",
+ "webhop.info",
+ "nanbu.tottori.jp",
+ "xn--ygarden-p1a.no",
+ "sukagawa.fukushima.jp",
+ "xn--osyro-wua.no",
+ "press.ma",
+ "georgia.museum",
+ "uonuma.niigata.jp",
+ "press.museum",
+ "tanagura.fukushima.jp",
+ "int.mw",
+ "yokoshibahikari.chiba.jp",
+ "pesaro-urbino.it",
+ "mitsue.nara.jp",
+ "naval.museum",
+ "nahari.kochi.jp",
+ "xn--avery-yua.no",
+ "xn--od0alg.hk",
+ "katsushika.tokyo.jp",
+ "otsuki.kochi.jp",
+ "jewelry.museum",
+ "nrw.museum",
+ "takarazuka.hyogo.jp",
+ "matsuura.nagasaki.jp",
+ "romskog.no",
+ "ino.kochi.jp",
+ "panama.museum",
+ "web.pk",
+ "um.gov.pl",
+ "namegata.ibaraki.jp",
+ "isa-hockeynut.com",
+ "xn--eveni-0qa01ga.no",
+ "marugame.kagawa.jp",
+ "is-saved.org",
+ "experts-comptables.fr",
+ "paderborn.museum",
+ "net.py",
+ "net.gy",
+ "komvux.se",
+ "zakopane.pl",
+ "torsken.no",
+ "isa-geek.net",
+ "matsukawa.nagano.jp",
+ "eidskog.no",
+ "ontario.museum",
+ "fujikawaguchiko.yamanashi.jp",
+ "makurazaki.kagoshima.jp",
+ "isa.kagoshima.jp",
+ "isa-geek.org",
+ "ascolipiceno.it",
+ "xn--hobl-ira.no",
+ "notogawa.shiga.jp",
+ "kasugai.aichi.jp",
+ "fuchu.tokyo.jp",
+ "mutsu.aomori.jp",
+ "matsumoto.nagano.jp",
+ "nanyo.yamagata.jp",
+ "umaji.kochi.jp",
+ "nakagawa.tokushima.jp",
+ "konskowola.pl",
+ "kursk.ru",
+ "ivanovo.ru",
+ "presidio.museum",
+ "ishikari.hokkaido.jp",
+ "yachiyo.ibaraki.jp",
+ "tomsk.ru",
+ "nakagawa.nagano.jp",
+ "westfalen.museum",
+ "gotsu.shimane.jp",
+ "uto.kumamoto.jp",
+ "wajiki.tokushima.jp",
+ "neyagawa.osaka.jp",
+ "yamagata.jp",
+ "pesarourbino.it",
+ "xn--nvuotna-hwa.no",
+ "matsushige.tokushima.jp",
+ "production.aero",
+ "ing.pa",
+ "polkowice.pl",
+ "xn--90a3ac",
+ "nakagawa.hokkaido.jp",
+ "uw.gov.pl",
+ "ube.yamaguchi.jp",
+ "wazuka.kyoto.jp",
+ "wodzislaw.pl",
+ "yasuda.kochi.jp",
+ "xn--vler-qoa.xn--stfold-9xa.no",
+ "mitsuke.niigata.jp",
+ "niyodogawa.kochi.jp",
+ "pharmacy.museum",
+ "waw.pl",
+ "zaporizhzhe.ua",
+ "xn--yer-zna.no",
+ "ascoli-piceno.it",
+ "yasaka.nagano.jp",
+ "xn--holtlen-hxa.no",
+ "xn--fl-zia.no",
+ "yonago.tottori.jp",
+ "naturhistorisches.museum",
+ "pp.ru",
+ "in-the-band.net",
+ "xn--fiqs8s",
+ "slask.pl",
+ "xn--hpmir-xqa.no",
+ "pomorze.pl",
+ "szczytno.pl",
+ "wanouchi.gifu.jp",
+ "inf.mk",
+ "matsumoto.kagoshima.jp",
+ "historyofscience.museum",
+ "yosemite.museum",
+ "minamiechizen.fukui.jp",
+ "uki.kumamoto.jp",
+ "lutsk.ua",
+ "yamaga.kumamoto.jp",
+ "matsuno.ehime.jp",
+ "prd.km",
+ "xn--fiqz9s",
+ "monzaedellabrianza.it",
+ "xn--hery-ira.nordland.no",
+ "shizuoka.jp",
+ "pisz.pl",
+ "satsumasendai.kagoshima.jp",
+ "matsushima.miyagi.jp",
+ "yahiko.niigata.jp",
+ "ug.gov.pl",
+ "yasugi.shimane.jp",
+ "itako.ibaraki.jp",
+ "xn--3e0b707e",
+ "s3-eu-west-1.amazonaws.com",
+ "ishikawa.okinawa.jp",
+ "iizuka.fukuoka.jp",
+ "ikaruga.nara.jp",
+ "insurance.aero",
+ "yukuhashi.fukuoka.jp",
+ "matsuyama.ehime.jp",
+ "itakura.gunma.jp",
+ "katsuragi.wakayama.jp",
+ "yamal.ru",
+ "vanylven.no",
+ "yabuki.fukushima.jp",
+ "nanbu.yamanashi.jp",
+ "xn--h-2fa.no",
+ "int.mv",
+ "yatomi.aichi.jp",
+ "wroclaw.pl",
+ "xn--flor-jra.no",
+ "wlocl.pl",
+ "xn--mgb2ddes",
+ "xn--frna-woa.no",
+ "org.lv",
+ "yamaguchi.jp",
+ "nationalfirearms.museum",
+ "per.la",
+ "chippubetsu.hokkaido.jp",
+ "xn--od0aq3b.hk",
+ "s3.amazonaws.com",
+ "xn--fjord-lra.no",
+ "kisarazu.chiba.jp",
+ "palermo.it",
+ "tatsuno.hyogo.jp",
+ "nakagusuku.okinawa.jp",
+ "ushiku.ibaraki.jp",
+ "watarai.mie.jp",
+ "xn--hcesuolo-7ya35b.no",
+ "cc.sd.us",
+ "yamagata.nagano.jp",
+ "war.museum",
+ "national-library-scotland.uk",
+ "po.gov.pl",
+ "uchihara.ibaraki.jp",
+ "newyork.museum",
+ "otoyo.kochi.jp",
+ "portland.museum",
+ "xn--rland-uua.no",
+ "okazaki.aichi.jp",
+ "xn--frya-hra.no",
+ "pa.gov.pl",
+ "xn--mely-ira.no",
+ "pharmacien.fr",
+ "xn--finny-yua.no",
+ "xn--rady-ira.no",
+ "ulvik.no",
+ "yamagata.ibaraki.jp",
+ "net.lv",
+ "xn--rdal-poa.no",
+ "xn--risr-ira.no",
+ "yonaguni.okinawa.jp",
+ "kouzushima.tokyo.jp",
+ "is-not-certified.com",
+ "ishigaki.okinawa.jp",
+ "tatsuno.nagano.jp",
+ "is-a-chef.com",
+ "xn--hnefoss-q1a.no",
+ "shizuoka.shizuoka.jp",
+ "yamada.iwate.jp",
+ "pvt.ge",
+ "xn--stre-toten-zcb.no",
+ "futsu.nagasaki.jp",
+ "xn--mk0axi.hk",
+ "xn--merker-kua.no",
+ "accident-prevention.aero",
+ "kihoku.ehime.jp",
+ "itoigawa.niigata.jp",
+ "xn--risa-5na.no",
+ "pro.pr",
+ "ide.kyoto.jp",
+ "prochowice.pl",
+ "plo.ps",
+ "yamakita.kanagawa.jp",
+ "masaki.ehime.jp",
+ "naoshima.kagawa.jp",
+ "xn--rennesy-v1a.no",
+ "ulm.museum",
+ "penza.ru",
+ "ine.kyoto.jp",
+ "xn--mgbaam7a8h",
+ "usa.museum",
+ "xn--muost-0qa.no",
+ "mutsuzawa.chiba.jp",
+ "porsanger.no",
+ "xn--mjndalen-64a.no",
+ "naturalsciences.museum",
+ "xn--drbak-wua.no",
+ "iwanuma.miyagi.jp",
+ "xn--indery-fya.no",
+ "xn--dnna-gra.no",
+ "taishin.fukushima.jp",
+ "matsuda.kanagawa.jp",
+ "noboribetsu.hokkaido.jp",
+ "yamagata.gifu.jp",
+ "katsuura.chiba.jp",
+ "yakage.okayama.jp",
+ "pharmaciens.km",
+ "is-a-chef.net",
+ "plaza.museum",
+ "xn--msy-ula0h.no",
+ "pvt.k12.ma.us",
+ "xn--hylandet-54a.no",
+ "scrapping.cc",
+ "xn--uc0atv.hk",
+ "xn--rskog-uua.no",
+ "urayasu.chiba.jp",
+ "kaszuby.pl",
+ "xn--rholt-mra.no",
+ "xn--uc0atv.tw",
+ "xn--mlatvuopmi-s4a.no",
+ "xn--mgba3a4fra",
+ "kaizuka.osaka.jp",
+ "xn--mgba3a4f16a",
+ "irc.pl",
+ "xn--rst-0na.no",
+ "watch-and-clock.museum",
+ "xn--rde-ula.no",
+ "xn--mgba3a4fra.ir",
+ "xn--mgba3a4f16a.ir",
+ "ostrowiec.pl",
+ "xn--rros-gra.no",
+ "xn--h2brj9c",
+ "chizu.tottori.jp",
+ "org.mx",
+ "urasoe.okinawa.jp",
+ "xn--mgb9awbf",
+ "xn--mot-tla.no",
+ "yamagata.yamagata.jp",
+ "uscountryestate.museum",
+ "uji.kyoto.jp",
+ "xn--mgbayh7gpa",
+ "portlligat.museum",
+ "inashiki.ibaraki.jp",
+ "oharu.aichi.jp",
+ "maizuru.kyoto.jp",
+ "isa-geek.com",
+ "xn--nttery-byae.no",
+ "xn--dyry-ira.no",
+ "nyc.museum",
+ "ichihara.chiba.jp",
+ "xn--rlingen-mxa.no",
+ "matsumae.hokkaido.jp",
+ "pro.mv",
+ "iamallama.com",
+ "net.mx",
+ "xn--kprw13d",
+ "xn--kpry57d",
+ "nagakute.aichi.jp",
+ "s3-ap-northeast-1.amazonaws.com",
+ "pubol.museum",
+ "usculture.museum",
+ "perugia.it",
+ "xn--unjrga-rta.no",
+ "xn--davvenjrga-y4a.no",
+ "shizukuishi.iwate.jp",
+ "s3-ap-southeast-1.amazonaws.com",
+ "press.aero",
+ "press.se",
+ "wildlife.museum",
+ "xn--hery-ira.xn--mre-og-romsdal-qqb.no",
+ "xn--krehamn-dxa.no",
+ "xn--hyanger-q1a.no",
+ "xn--lury-ira.no",
+ "xn--mli-tla.no",
+ "posts-and-telecommunications.museum",
+ "perso.tn",
+ "iwakura.aichi.jp",
+ "pippu.hokkaido.jp",
+ "nakagyo.kyoto.jp",
+ "xn--p1ai",
+ "xn--klbu-woa.no",
+ "xn--hbmer-xqa.no",
+ "wloclawek.pl",
+ "xn--linds-pra.no",
+ "nakagawa.fukuoka.jp",
+ "s3-ap-southeast-2.amazonaws.com",
+ "xn--lesund-hua.no",
+ "perso.sn",
+ "xn--kfjord-iua.no",
+ "xn--fpcrj9c3d",
+ "perso.ht",
+ "xn--krdsherad-m8a.no",
+ "xn--clchc0ea0b2g2a9gcd",
+ "xn--oppegrd-ixa.no",
+ "xn--pgbs0dh",
+ "ishikawa.fukushima.jp",
+ "xn--hgebostad-g3a.no",
+ "utashinai.hokkaido.jp",
+ "xn--lrenskog-54a.no",
+ "xn--lrdal-sra.no",
+ "xn--54b7fta0cc",
+ "pc.pl",
+ "appspot.com",
+ "iwafune.tochigi.jp",
+ "yatsushiro.kumamoto.jp",
+ "yotsukaido.chiba.jp",
+ "xn--lten-gra.no",
+ "is-a-cubicle-slave.com",
+ "xn--karmy-yua.no",
+ "xn--gls-elac.no",
+ "ichikawa.hyogo.jp",
+ "xn--l-1fa.no",
+ "xn--krager-gya.no",
+ "xn--koluokta-7ya57h.no",
+ "xn--ostery-fya.no",
+ "xn--mgbbh1a71e",
+ "xn--uc0ay4a.hk",
+ "xn--langevg-jxa.no",
+ "hatsukaichi.hiroshima.jp",
+ "katsuyama.fukui.jp",
+ "ichikawamisato.yamanashi.jp",
+ "xn--h1aegh.museum",
+ "xn--gmqw5a.hk",
+ "nisshin.aichi.jp",
+ "xn--rdy-0nab.no",
+ "xn--lhppi-xqa.no",
+ "natuurwetenschappen.museum",
+ "xn--givuotna-8ya.no",
+ "xn--gjvik-wua.no",
+ "yanagawa.fukuoka.jp",
+ "xn--leagaviika-52b.no",
+ "xn--krjohka-hwab49j.no",
+ "xn--frde-gra.no",
+ "xn--rsta-fra.no",
+ "xn--gildeskl-g0a.no",
+ "xn--mgbc0a9azcg",
+ "xn--lcvr32d.hk",
+ "xn--mgberp4a5d4ar",
+ "la-spezia.it",
+ "xn--mlselv-iua.no",
+ "xn--moreke-jua.no",
+ "paragliding.aero",
+ "podzone.net",
+ "yachiyo.chiba.jp",
+ "xn--ldingen-q1a.no",
+ "xn--mgbqly7cvafr",
+ "xn--lns-qla.museum",
+ "yatsuka.shimane.jp",
+ "iwakuni.yamaguchi.jp",
+ "xn--rmskog-bya.no",
+ "xn--rhkkervju-01af.no",
+ "xn--gmq050i.hk",
+ "xn--ggaviika-8ya47h.no",
+ "plc.ly",
+ "inazawa.aichi.jp",
+ "ichikawa.chiba.jp",
+ "xn--fzc2c9e2c",
+ "xn--mtta-vrjjat-k7af.no",
+ "passenger-association.aero",
+ "ureshino.mie.jp",
+ "xn--mgberp4a5d4a87g",
+ "xn--mosjen-eya.no",
+ "podzone.org",
+ "xn--correios-e-telecomunicaes-ghc29a.museum",
+ "xn--ogbpf8fl",
+ "xn--loabt-0qa.no",
+ "matsudo.chiba.jp",
+ "xn--lgbbat1ad8j",
+ "pacific.museum",
+ "xn--lgrd-poac.no",
+ "xn--ksnes-uua.no",
+ "xn--gecrj9c",
+ "xn--ryken-vua.no",
+ "intelligence.museum",
+ "xn--hmmrfeasta-s4ac.no",
+ "xn--kvfjord-nxa.no",
+ "xn--ryrvik-bya.no",
+ "xn--lt-liac.no",
+ "newspaper.museum",
+ "porsgrunn.no",
+ "is-slick.com",
+ "xn--laheadju-7ya.no",
+ "ichikai.tochigi.jp",
+ "xn--mgbtf8fl",
+ "xn--kvnangen-k0a.no",
+ "xn--kranghke-b0a.no",
+ "yaizu.shizuoka.jp",
+ "imizu.toyama.jp",
+ "xn--mgbqly7c0a67fbc",
+ "xn--porsgu-sta26f.no",
+ "xn--mxtq1m.hk",
+ "unazuki.toyama.jp",
+ "xn--kvitsy-fya.no",
+ "utazu.kagawa.jp",
+ "uchiko.ehime.jp"
+ };
+#define stringpool ((const char *) &stringpool_contents)
+const struct DomainRule *
+Perfect_Hash::FindDomain (register const char *str, register unsigned int len)
+{
+ static const struct DomainRule wordlist[] =
+ {
+#line 1877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str0, 2},
+#line 1351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1, 0},
+#line 1610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2, 0},
+#line 1715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3, 0},
+#line 802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4, 0},
+#line 1033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5, 0},
+#line 1028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6, 0},
+#line 1656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str7, 0},
+#line 694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str8, 0},
+#line 1171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str9, 0},
+#line 4961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str10, 0},
+#line 5094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str11, 2},
+#line 5062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str12, 0},
+#line 208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str13, 0},
+#line 4752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str14, 0},
+#line 328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str15, 0},
+#line 794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str16, 0},
+#line 76 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str17, 0},
+#line 4558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str18, 0},
+#line 4580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str19, 0},
+#line 1040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str20, 0},
+#line 4950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str21, 0},
+#line 862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str22, 0},
+#line 165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str23, 0},
+#line 5110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str24, 0},
+#line 1819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str25, 0},
+#line 1817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str26, 0},
+#line 356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str27, 0},
+#line 1267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str28, 0},
+#line 1266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str29, 0},
+#line 1728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str30, 0},
+#line 1727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str31, 0},
+#line 1036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str32, 0},
+#line 1183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str33, 0},
+#line 1182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str34, 0},
+#line 5104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str35, 0},
+#line 1717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str36, 0},
+#line 350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str37, 0},
+#line 968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str38, 0},
+#line 966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str39, 0},
+#line 877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str40, 0},
+#line 876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str41, 0},
+#line 1733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str42, 0},
+#line 1181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str43, 0},
+#line 1830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str44, 0},
+#line 1328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str45, 2},
+#line 1187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str46, 0},
+#line 1722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str47, 0},
+#line 1015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str48, 0},
+#line 1177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str49, 0},
+#line 875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str50, 0},
+#line 827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str51, 0},
+#line 1664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str52, 0},
+#line 5011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str53, 0},
+#line 882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str54, 0},
+#line 223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str55, 2},
+#line 866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str56, 0},
+#line 821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str57, 0},
+#line 871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str58, 0},
+#line 4963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str59, 0},
+#line 5095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str60, 0},
+#line 209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str61, 0},
+#line 342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str62, 0},
+#line 2786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str63, 0},
+#line 1801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str64, 0},
+#line 865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str65, 0},
+#line 4559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str66, 0},
+#line 2945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str67, 0},
+#line 1041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str68, 0},
+#line 1252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str69, 0},
+#line 1723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str70, 0},
+#line 1808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str71, 0},
+#line 1178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str72, 0},
+#line 4552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str73, 0},
+#line 1259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str74, 0},
+#line 949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str75, 0},
+#line 1876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str76, 0},
+#line 1344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str77, 2},
+#line 872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str78, 0},
+#line 1730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str79, 0},
+#line 962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str80, 0},
+#line 957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str81, 0},
+#line 1185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str82, 0},
+#line 1720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str83, 0},
+#line 810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str84, 4},
+#line 5017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str85, 0},
+#line 318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str86, 0},
+#line 1809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str87, 0},
+#line 1832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str88, 0},
+#line 879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str89, 0},
+#line 1260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str90, 0},
+#line 2855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str91, 0},
+#line 1016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str92, 0},
+#line 846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str93, 0},
+#line 1034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str94, 0},
+#line 5013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str95, 0},
+#line 958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str96, 0},
+#line 225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str97, 0},
+#line 1852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str98, 0},
+#line 1332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str99, 0},
+#line 4585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str100, 0},
+#line 1716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str101, 0},
+#line 4951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str102, 0},
+#line 1805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str103, 0},
+#line 1762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str104, 0},
+#line 1172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str105, 0},
+#line 1761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str106, 0},
+#line 1256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str107, 0},
+#line 1214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str108, 0},
+#line 264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str109, 0},
+#line 1615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str110, 0},
+#line 1281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str111, 0},
+#line 4578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str112, 0},
+#line 863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str113, 0},
+#line 954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str114, 0},
+#line 983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str115, 0},
+#line 912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str116, 0},
+#line 4755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str117, 0},
+#line 83 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str118, 0},
+#line 1724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str119, 0},
+#line 4501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str120, 0},
+#line 1878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str121, 0},
+#line 1026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str122, 0},
+#line 1179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str123, 0},
+#line 848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str124, 0},
+#line 320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str125, 0},
+#line 1017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str126, 0},
+#line 4754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str127, 0},
+#line 873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str128, 0},
+#line 2857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str129, 0},
+#line 1807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str130, 0},
+#line 2924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str131, 0},
+#line 1258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str132, 0},
+#line 357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str133, 0},
+#line 2787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str134, 0},
+#line 956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str135, 0},
+#line 1025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str136, 0},
+#line 3419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str137, 0},
+#line 3518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str138, 0},
+#line 3487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str139, 0},
+#line 3201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str140, 0},
+#line 5015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str141, 0},
+#line 1616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str142, 0},
+#line 3415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str143, 2},
+#line 3529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str144, 2},
+#line 2706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str145, 2},
+#line 695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str146, 0},
+#line 1806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str147, 0},
+#line 1257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str148, 0},
+#line 86 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str149, 0},
+#line 4502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str150, 0},
+#line 3520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str151, 0},
+#line 226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str152, 0},
+#line 955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str153, 0},
+#line 2858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str154, 0},
+#line 5273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str155, 0},
+#line 5438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str156, 0},
+#line 1721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str157, 0},
+#line 1276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str158, 1},
+#line 5218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str159, 0},
+#line 3114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str160, 0},
+#line 3100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str161, 0},
+#line 1176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str162, 0},
+#line 5259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str163, 0},
+#line 5457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str164, 0},
+#line 3476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str165, 0},
+#line 869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str166, 0},
+#line 1587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str167, 0},
+#line 1152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str168, 0},
+#line 1738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str169, 0},
+#line 1736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str170, 0},
+#line 1734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str171, 0},
+#line 4568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str172, 0},
+#line 1190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str173, 0},
+#line 600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str174, 0},
+#line 3422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str175, 0},
+#line 1820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str176, 0},
+#line 4595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str177, 0},
+#line 1027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str178, 0},
+#line 27 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str179, 0},
+#line 1268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str180, 0},
+#line 1731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str181, 0},
+#line 885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str182, 0},
+#line 4478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str183, 0},
+#line 1188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str184, 0},
+#line 1186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str185, 0},
+#line 969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str186, 0},
+#line 883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str187, 0},
+#line 880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str188, 0},
+#line 5365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str189, 2},
+#line 3092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str190, 0},
+#line 2465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str191, 0},
+#line 1434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str192, 0},
+#line 3484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str193, 2},
+#line 5274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str194, 0},
+#line 5441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str195, 0},
+#line 3076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str196, 0},
+#line 3101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str197, 0},
+#line 2464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str198, 2},
+#line 1429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str199, 0},
+#line 265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str200, 0},
+#line 997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str201, 0},
+#line 2868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str202, 0},
+#line 1633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str203, 0},
+#line 1818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str204, 0},
+#line 2495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str205, 0},
+#line 766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str206, 0},
+#line 1812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str207, 0},
+#line 1729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str208, 0},
+#line 4910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str209, 0},
+#line 1263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str210, 0},
+#line 87 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str211, 0},
+#line 111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str212, 0},
+#line 1184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str213, 0},
+#line 3479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str214, 0},
+#line 5430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str215, 0},
+#line 3094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str216, 0},
+#line 967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str217, 0},
+#line 844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str218, 0},
+#line 1175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str219, 0},
+#line 963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str220, 0},
+#line 878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str221, 0},
+#line 81 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str222, 0},
+#line 1658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str223, 0},
+#line 3204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str224, 0},
+#line 1456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str225, 0},
+#line 868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str226, 0},
+#line 5366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str227, 0},
+#line 796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str228, 0},
+#line 79 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str229, 0},
+#line 4954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str230, 0},
+#line 3115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str231, 0},
+#line 186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str232, 0},
+#line 3485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str233, 0},
+#line 2869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str234, 0},
+#line 3423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str235, 0},
+#line 1711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str236, 0},
+#line 3203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str237, 0},
+#line 1431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str238, 0},
+#line 3093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str239, 0},
+#line 1099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str240, 0},
+#line 2725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str241, 0},
+#line 767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str242, 0},
+#line 1262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str243, 0},
+#line 1088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str244, 0},
+#line 5014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str245, 0},
+#line 4911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str246, 0},
+#line 1149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str247, 0},
+#line 3480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str248, 0},
+#line 4532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str249, 0},
+#line 961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str250, 0},
+#line 3095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str251, 0},
+#line 308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str252, 0},
+#line 1816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str253, 0},
+#line 314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str254, 0},
+#line 3079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str255, 0},
+#line 1306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str256, 0},
+#line 3205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str257, 0},
+#line 1457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str258, 0},
+#line 2789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str259, 0},
+#line 798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str260, 0},
+#line 1719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str261, 0},
+#line 187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str262, 0},
+#line 1174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str263, 0},
+#line 1001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str264, 0},
+#line 4553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str265, 0},
+#line 5449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str266, 0},
+#line 1714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str267, 0},
+#line 1000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str268, 0},
+#line 5253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str269, 0},
+#line 5400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str270, 0},
+#line 867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str271, 0},
+#line 831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str272, 0},
+#line 325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str273, 0},
+#line 3218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str274, 0},
+#line 2447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str275, 0},
+#line 1649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str276, 0},
+#line 1669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str277, 0},
+#line 5219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str278, 0},
+#line 306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str279, 0},
+#line 2976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str280, 0},
+#line 787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str281, 0},
+#line 842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str282, 0},
+#line 3096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str283, 0},
+#line 4943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str284, 0},
+#line 1712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str285, 0},
+#line 3121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str286, 0},
+#line 148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str287, 0},
+#line 1739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str288, 0},
+#line 3486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str289, 0},
+#line 1191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str290, 0},
+#line 1811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str291, 0},
+#line 799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str292, 0},
+#line 1261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str293, 0},
+#line 886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str294, 0},
+#line 960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str295, 0},
+#line 3217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str296, 0},
+#line 247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str297, 0},
+#line 1411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str298, 2},
+#line 5116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str299, 0},
+#line 528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str300, 0},
+#line 5395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str301, 0},
+#line 2947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str302, 0},
+#line 404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str303, 2},
+#line 3482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str304, 0},
+#line 4533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str305, 0},
+#line 5220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str306, 0},
+#line 524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str307, 0},
+#line 1589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str308, 0},
+#line 596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str309, 0},
+#line 1376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str310, 0},
+#line 601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str311, 0},
+#line 3207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str312, 0},
+#line 788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str313, 0},
+#line 4615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str314, 0},
+#line 1815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str315, 0},
+#line 149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str316, 0},
+#line 5263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str317, 0},
+#line 1265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str318, 0},
+#line 1063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str319, 0},
+#line 965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str320, 0},
+#line 1707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str321, 0},
+#line 253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str322, 0},
+#line 547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str323, 0},
+#line 3416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str324, 0},
+#line 5394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str325, 0},
+#line 1364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str326, 0},
+#line 1737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str327, 0},
+#line 529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str328, 0},
+#line 3001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str329, 0},
+#line 1189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str330, 0},
+#line 998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str331, 0},
+#line 597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str332, 0},
+#line 4713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str333, 0},
+#line 884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str334, 0},
+#line 602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str335, 0},
+#line 1085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str336, 0},
+#line 88 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str337, 0},
+#line 4621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str338, 0},
+#line 302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str339, 0},
+#line 3245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str340, 0},
+#line 5270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str341, 0},
+#line 4479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str342, 0},
+#line 1586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str343, 0},
+#line 1151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str344, 0},
+#line 21 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str345, 0},
+#line 16 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str346, 0},
+#line 1672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str347, 0},
+#line 15 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str348, 0},
+#line 25 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str349, 0},
+#line 574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str350, 0},
+#line 24 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str351, 0},
+#line 598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str352, 0},
+#line 23 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str353, 0},
+#line 20 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str354, 0},
+#line 19 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str355, 0},
+#line 18 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str356, 0},
+#line 826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str357, 0},
+#line 4594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str358, 0},
+#line 14 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str359, 0},
+#line 26 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str360, 0},
+#line 4477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str361, 0},
+#line 5651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str362, 0},
+#line 549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str363, 0},
+#line 3417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str364, 0},
+#line 630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str365, 0},
+#line 1389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str366, 0},
+#line 5432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str367, 0},
+#line 5385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str368, 0},
+#line 3002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str369, 0},
+#line 151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str370, 0},
+#line 1745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str371, 0},
+#line 572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str372, 0},
+#line 634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str373, 0},
+#line 1197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str374, 0},
+#line 1045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str375, 0},
+#line 1735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str376, 0},
+#line 893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str377, 0},
+#line 406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str378, 0},
+#line 5271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str379, 0},
+#line 3414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str380, 0},
+#line 828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str381, 0},
+#line 1746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str382, 0},
+#line 4583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str383, 0},
+#line 575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str384, 0},
+#line 2494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str385, 0},
+#line 1198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str386, 0},
+#line 3521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str387, 0},
+#line 1828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str388, 0},
+#line 769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str389, 0},
+#line 3504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str390, 0},
+#line 894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str391, 0},
+#line 5442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str392, 0},
+#line 5652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str393, 0},
+#line 1590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str394, 0},
+#line 1004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str395, 0},
+#line 30 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str396, 0},
+#line 604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str397, 0},
+#line 2474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str398, 0},
+#line 1391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str399, 0},
+#line 3209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str400, 0},
+#line 5258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str401, 0},
+#line 3247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str402, 0},
+#line 4714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str403, 0},
+#line 4557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str404, 0},
+#line 472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str405, 0},
+#line 573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str406, 0},
+#line 1065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str407, 0},
+#line 1887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str408, 0},
+#line 1199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str409, 0},
+#line 806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str410, 0},
+#line 311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str411, 0},
+#line 3122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str412, 0},
+#line 2902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str413, 0},
+#line 895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str414, 0},
+#line 830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str415, 0},
+#line 1632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str416, 0},
+#line 3418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str417, 0},
+#line 5233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str418, 0},
+#line 704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str419, 0},
+#line 5646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str420, 0},
+#line 1002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str421, 0},
+#line 4808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str422, 0},
+#line 1744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str423, 0},
+#line 1454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str424, 0},
+#line 849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str425, 0},
+#line 2854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str426, 0},
+#line 1196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str427, 0},
+#line 1022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str428, 0},
+#line 292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str429, 0},
+#line 5002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str430, 0},
+#line 217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str431, 0},
+#line 785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str432, 2},
+#line 892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str433, 0},
+#line 4924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str434, 0},
+#line 1348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str435, 0},
+#line 2948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str436, 0},
+#line 5272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str437, 0},
+#line 361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str438, 0},
+#line 5583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str439, 0},
+#line 1826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str440, 0},
+#line 1275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str441, 0},
+#line 2491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str442, 0},
+#line 5649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str443, 0},
+#line 979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str444, 0},
+#line 825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str445, 0},
+#line 801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str446, 0},
+#line 2715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str447, 2},
+#line 705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str448, 0},
+#line 3120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str449, 0},
+#line 4536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str450, 0},
+#line 3426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str451, 0},
+#line 5648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str452, 0},
+#line 2973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str453, 0},
+#line 1321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str454, 0},
+#line 5117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str455, 0},
+#line 1407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str456, 0},
+#line 194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str457, 0},
+#line 434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str458, 0},
+#line 5584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str459, 0},
+#line 4556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str460, 0},
+#line 4566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str461, 0},
+#line 3197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str462, 0},
+#line 5115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str463, 0},
+#line 2946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str464, 0},
+#line 456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str465, 0},
+#line 525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str466, 0},
+#line 5223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str467, 0},
+#line 4925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str468, 0},
+#line 526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str469, 2},
+#line 1089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str470, 0},
+#line 2449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str471, 0},
+#line 786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str472, 0},
+#line 987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str473, 0},
+#line 1527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str474, 0},
+#line 3474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str475, 0},
+#line 1116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str476, 0},
+#line 3196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str477, 0},
+#line 5650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str478, 0},
+#line 3123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str479, 0},
+#line 435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str480, 0},
+#line 458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str481, 0},
+#line 850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str482, 0},
+#line 2441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str483, 0},
+#line 1363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str484, 0},
+#line 1791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str485, 0},
+#line 1239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str486, 0},
+#line 2716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str487, 0},
+#line 2815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str488, 0},
+#line 527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str489, 0},
+#line 986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str490, 0},
+#line 937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str491, 0},
+#line 133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str492, 0},
+#line 819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str493, 0},
+#line 1416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str494, 0},
+#line 2950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str495, 0},
+#line 3244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str496, 0},
+#line 3097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str497, 0},
+#line 5609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str498, 0},
+#line 1611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str499, 0},
+#line 3412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str500, 0},
+#line 935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str501, 0},
+#line 1896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str502, 0},
+#line 603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str503, 0},
+#line 4753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str504, 0},
+#line 1670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str505, 0},
+#line 5245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str506, 0},
+#line 5635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str507, 0},
+#line 1890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str508, 0},
+#line 845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str509, 0},
+#line 1713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str510, 0},
+#line 2482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str511, 0},
+#line 3427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str512, 0},
+#line 5364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str513, 0},
+#line 5105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str514, 0},
+#line 5257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str515, 0},
+#line 1044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str516, 0},
+#line 3074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str517, 0},
+#line 3274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str518, 0},
+#line 1433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str519, 0},
+#line 3211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str520, 0},
+#line 5610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str521, 0},
+#line 459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str522, 0},
+#line 3318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str523, 0},
+#line 3317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str524, 0},
+#line 1667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str525, 0},
+#line 1305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str526, 0},
+#line 3280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str527, 0},
+#line 1169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str528, 0},
+#line 1354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str529, 0},
+#line 362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str530, 0},
+#line 836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str531, 0},
+#line 477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str532, 0},
+#line 3276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str533, 0},
+#line 3320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str534, 0},
+#line 1424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str535, 0},
+#line 1287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str536, 0},
+#line 4524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str537, 0},
+#line 3278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str538, 0},
+#line 1415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str539, 2},
+#line 3310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str540, 0},
+#line 3413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str541, 0},
+#line 4923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str542, 0},
+#line 3279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str543, 0},
+#line 402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str544, 0},
+#line 5215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str545, 0},
+#line 1856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str546, 0},
+#line 363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str547, 0},
+#line 5636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str548, 0},
+#line 4555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str549, 0},
+#line 3210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str550, 0},
+#line 359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str551, 0},
+#line 5611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str552, 0},
+#line 469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str553, 0},
+#line 1388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str554, 0},
+#line 3275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str555, 0},
+#line 457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str556, 4},
+#line 3296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str557, 0},
+#line 2994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str558, 0},
+#line 1086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str559, 0},
+#line 5558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str560, 0},
+#line 3082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str561, 0},
+#line 5203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str562, 0},
+#line 5214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str563, 0},
+#line 1336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str564, 0},
+#line 5073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str565, 0},
+#line 309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str566, 0},
+#line 164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str567, 0},
+#line 1468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str568, 0},
+#line 4940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str569, 0},
+#line 3421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str570, 0},
+#line 1790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str571, 0},
+#line 3175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str572, 0},
+#line 286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str573, 0},
+#line 4565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str574, 0},
+#line 599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str575, 4},
+#line 450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str576, 0},
+#line 207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str577, 0},
+#line 1353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str578, 0},
+#line 3429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str579, 0},
+#line 1115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str580, 0},
+#line 1792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str581, 0},
+#line 2992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str582, 0},
+#line 1240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str583, 0},
+#line 5559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str584, 0},
+#line 938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str585, 0},
+#line 300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str586, 0},
+#line 5602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str587, 0},
+#line 835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str588, 0},
+#line 1367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str589, 0},
+#line 1286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str590, 0},
+#line 5444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str591, 0},
+#line 5557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str592, 0},
+#line 1673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str593, 0},
+#line 5085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str594, 0},
+#line 479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str595, 0},
+#line 433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str596, 0},
+#line 388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str597, 0},
+#line 3285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str598, 0},
+#line 5239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str599, 0},
+#line 5579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str600, 0},
+#line 5269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str601, 0},
+#line 291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str602, 0},
+#line 4905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str603, 0},
+#line 5388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str604, 0},
+#line 2492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str605, 0},
+#line 1676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str606, 0},
+#line 3067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str607, 0},
+#line 2925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str608, 0},
+#line 1534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str609, 0},
+#line 1597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str610, 0},
+#line 4959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str611, 0},
+#line 5561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str612, 0},
+#line 1675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str613, 0},
+#line 4955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str614, 0},
+#line 1399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str615, 0},
+#line 1888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str616, 0},
+#line 301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str617, 0},
+#line 1588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str618, 0},
+#line 3314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str619, 0},
+#line 3281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str620, 0},
+#line 1702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str621, 4},
+#line 1035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str622, 0},
+#line 3206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str623, 0},
+#line 803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str624, 0},
+#line 348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str625, 0},
+#line 4586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str626, 0},
+#line 1689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str627, 0},
+#line 2473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str628, 0},
+#line 5386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str629, 0},
+#line 5634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str630, 0},
+#line 1725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str631, 0},
+#line 2496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str632, 0},
+#line 1718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str633, 0},
+#line 5582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str634, 0},
+#line 1173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str635, 0},
+#line 2500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str636, 0},
+#line 1356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str637, 0},
+#line 864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str638, 0},
+#line 468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str639, 0},
+#line 4540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str640, 0},
+#line 1387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str641, 0},
+#line 2485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str642, 0},
+#line 2933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str643, 2},
+#line 5254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str644, 0},
+#line 2792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str645, 2},
+#line 2540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str646, 0},
+#line 5060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str647, 0},
+#line 3277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str648, 0},
+#line 2499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str649, 0},
+#line 1392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str650, 0},
+#line 245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str651, 0},
+#line 2489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str652, 0},
+#line 1824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str653, 0},
+#line 2456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str654, 0},
+#line 1273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str655, 0},
+#line 1382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str656, 0},
+#line 3003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str657, 0},
+#line 976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str658, 0},
+#line 2731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str659, 0},
+#line 1412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str660, 0},
+#line 4757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str661, 4},
+#line 3007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str662, 0},
+#line 310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str663, 0},
+#line 977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str664, 0},
+#line 3326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str665, 0},
+#line 5016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str666, 0},
+#line 1018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str667, 0},
+#line 1408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str668, 0},
+#line 5248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str669, 0},
+#line 3116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str670, 0},
+#line 2917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str671, 0},
+#line 1605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str672, 0},
+#line 3048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str673, 0},
+#line 4519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str674, 0},
+#line 4567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str675, 0},
+#line 3235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str676, 0},
+#line 2539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str677, 0},
+#line 2965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str678, 0},
+#line 4770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str679, 0},
+#line 5593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str680, 0},
+#line 5392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str681, 0},
+#line 3006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str682, 0},
+#line 4987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str683, 0},
+#line 3316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str684, 0},
+#line 1320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str685, 0},
+#line 3519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str686, 0},
+#line 1823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str687, 0},
+#line 2123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str688, 0},
+#line 1272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str689, 0},
+#line 2072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str690, 0},
+#line 5637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str691, 0},
+#line 4977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str692, 0},
+#line 975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str693, 0},
+#line 3075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str694, 0},
+#line 3284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str695, 0},
+#line 3428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str696, 0},
+#line 5447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str697, 0},
+#line 3489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str698, 0},
+#line 2502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str699, 0},
+#line 2120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str700, 0},
+#line 3047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str701, 0},
+#line 1842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str702, 0},
+#line 3080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str703, 0},
+#line 4919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str704, 0},
+#line 2484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str705, 4},
+#line 723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str706, 0},
+#line 3155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str707, 0},
+#line 5330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str708, 0},
+#line 2501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str709, 0},
+#line 2122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str710, 0},
+#line 444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str711, 0},
+#line 837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str712, 0},
+#line 2541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str713, 0},
+#line 3283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str714, 0},
+#line 5238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str715, 0},
+#line 2498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str716, 0},
+#line 473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str717, 0},
+#line 118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str718, 0},
+#line 3288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str719, 0},
+#line 5020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str720, 0},
+#line 1453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str721, 0},
+#line 3009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str722, 0},
+#line 3436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str723, 0},
+#line 1891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str724, 0},
+#line 3501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str725, 0},
+#line 1037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str726, 2},
+#line 1369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str727, 0},
+#line 5106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str728, 0},
+#line 5096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str729, 0},
+#line 3441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str730, 0},
+#line 4809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str731, 0},
+#line 109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str732, 0},
+#line 4539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str733, 0},
+#line 1726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str734, 0},
+#line 1709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str735, 0},
+#line 1180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str736, 0},
+#line 2463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str737, 0},
+#line 874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str738, 0},
+#line 3008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str739, 0},
+#line 1023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str740, 0},
+#line 1398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str741, 0},
+#line 3167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str742, 0},
+#line 3287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str743, 0},
+#line 1343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str744, 0},
+#line 4687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str745, 0},
+#line 3049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str746, 0},
+#line 5437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str747, 0},
+#line 1825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str748, 0},
+#line 2934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str749, 0},
+#line 2497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str750, 0},
+#line 305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str751, 0},
+#line 1274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str752, 0},
+#line 3005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str753, 0},
+#line 3110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str754, 0},
+#line 978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str755, 0},
+#line 1355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str756, 0},
+#line 1810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str757, 0},
+#line 1280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str758, 0},
+#line 3146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str759, 0},
+#line 4956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str760, 0},
+#line 959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str761, 0},
+#line 422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str762, 0},
+#line 605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str763, 0},
+#line 3013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str764, 0},
+#line 395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str765, 0},
+#line 1898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str766, 0},
+#line 5576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str767, 0},
+#line 2506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str768, 0},
+#line 3356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str769, 4},
+#line 3465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str770, 0},
+#line 2503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str771, 0},
+#line 852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str772, 0},
+#line 590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str773, 0},
+#line 3004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str774, 0},
+#line 5589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str775, 0},
+#line 5054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str776, 0},
+#line 409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str777, 0},
+#line 3159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str778, 0},
+#line 1465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str779, 0},
+#line 3158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str780, 0},
+#line 5064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str781, 0},
+#line 1608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str782, 4},
+#line 340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str783, 0},
+#line 449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str784, 0},
+#line 1401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str785, 0},
+#line 3305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str786, 0},
+#line 3522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str787, 0},
+#line 606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str788, 0},
+#line 4709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str789, 0},
+#line 2074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str790, 0},
+#line 1397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str791, 0},
+#line 1829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str792, 0},
+#line 426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str793, 0},
+#line 1851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str794, 0},
+#line 184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str795, 0},
+#line 2935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str796, 0},
+#line 4516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str797, 0},
+#line 219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str798, 0},
+#line 3010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str799, 0},
+#line 3117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str800, 0},
+#line 332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str801, 0},
+#line 2745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str802, 0},
+#line 5194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str803, 0},
+#line 4582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str804, 0},
+#line 2926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str805, 0},
+#line 3156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str806, 0},
+#line 371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str807, 0},
+#line 807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str808, 0},
+#line 1535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str809, 4},
+#line 4932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str810, 0},
+#line 220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str811, 0},
+#line 4529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str812, 1},
+#line 5331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str813, 0},
+#line 627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str814, 0},
+#line 5387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str815, 0},
+#line 1987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str816, 0},
+#line 2549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str817, 0},
+#line 464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str818, 0},
+#line 1710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str819, 0},
+#line 540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str820, 0},
+#line 372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str821, 0},
+#line 1386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str822, 0},
+#line 3108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str823, 0},
+#line 2548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str824, 0},
+#line 181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str825, 0},
+#line 1081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str826, 0},
+#line 307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str827, 0},
+#line 5382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str828, 0},
+#line 4791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str829, 4},
+#line 4579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str830, 0},
+#line 1074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str831, 0},
+#line 3475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str832, 0},
+#line 999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str833, 0},
+#line 423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str834, 0},
+#line 3056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str835, 0},
+#line 1360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str836, 0},
+#line 3507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str837, 0},
+#line 1156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str838, 0},
+#line 1897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str839, 0},
+#line 2632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str840, 0},
+#line 3055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str841, 0},
+#line 4527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str842, 0},
+#line 1417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str843, 0},
+#line 1523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str844, 0},
+#line 591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str845, 0},
+#line 5594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str846, 0},
+#line 1372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str847, 0},
+#line 1053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str848, 0},
+#line 5431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str849, 0},
+#line 2530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str850, 0},
+#line 2527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str851, 0},
+#line 2526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str852, 0},
+#line 4572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str853, 0},
+#line 710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str854, 0},
+#line 1359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str855, 0},
+#line 936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str856, 0},
+#line 1405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str857, 0},
+#line 4785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str858, 2},
+#line 2969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str859, 0},
+#line 5081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str860, 0},
+#line 838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str861, 0},
+#line 180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str862, 0},
+#line 532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str863, 0},
+#line 1630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str864, 0},
+#line 1282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str865, 0},
+#line 2128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str866, 0},
+#line 2483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str867, 4},
+#line 703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str868, 0},
+#line 2068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str869, 0},
+#line 4807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str870, 0},
+#line 95 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str871, 0},
+#line 1464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str872, 0},
+#line 3038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str873, 0},
+#line 3035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str874, 0},
+#line 3034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str875, 0},
+#line 3327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str876, 0},
+#line 2525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str877, 0},
+#line 246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str878, 0},
+#line 1349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str879, 0},
+#line 3071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str880, 0},
+#line 5384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str881, 0},
+#line 822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str882, 0},
+#line 2713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str883, 0},
+#line 4492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str884, 0},
+#line 2927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str885, 0},
+#line 96 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str886, 0},
+#line 4530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str887, 0},
+#line 454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str888, 0},
+#line 3258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str889, 0},
+#line 1708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str890, 0},
+#line 4792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str891, 4},
+#line 183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str892, 0},
+#line 375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str893, 0},
+#line 280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str894, 0},
+#line 4480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str895, 0},
+#line 3033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str896, 0},
+#line 303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str897, 0},
+#line 1623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str898, 0},
+#line 1153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str899, 0},
+#line 3323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str900, 0},
+#line 562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str901, 0},
+#line 5249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str902, 0},
+#line 1409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str903, 0},
+#line 1603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str904, 0},
+#line 2867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str905, 0},
+#line 419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str906, 0},
+#line 3243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str907, 0},
+#line 1083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str908, 0},
+#line 3107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str909, 0},
+#line 1451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str910, 0},
+#line 1406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str911, 0},
+#line 3234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str912, 0},
+#line 2545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str913, 0},
+#line 1120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str914, 0},
+#line 1418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str915, 0},
+#line 5243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str916, 0},
+#line 5448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str917, 0},
+#line 3322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str918, 0},
+#line 2996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str919, 0},
+#line 4953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str920, 0},
+#line 5078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str921, 0},
+#line 2866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str922, 0},
+#line 698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str923, 0},
+#line 1066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str924, 0},
+#line 5619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str925, 0},
+#line 470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str926, 0},
+#line 1020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str927, 0},
+#line 765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str928, 0},
+#line 448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str929, 0},
+#line 1413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str930, 0},
+#line 1122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str931, 0},
+#line 809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str932, 0},
+#line 3358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str933, 0},
+#line 1969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str934, 0},
+#line 3000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str935, 0},
+#line 1384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str936, 0},
+#line 360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str937, 0},
+#line 1279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str938, 0},
+#line 102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str939, 0},
+#line 4560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str940, 0},
+#line 5074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str941, 0},
+#line 5592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str942, 0},
+#line 4526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str943, 0},
+#line 5027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str944, 0},
+#line 5573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str945, 0},
+#line 792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str946, 0},
+#line 2931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str947, 0},
+#line 2529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str948, 0},
+#line 1835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str949, 0},
+#line 1154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str950, 0},
+#line 345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str951, 0},
+#line 617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str952, 0},
+#line 2891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str953, 0},
+#line 2865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str954, 0},
+#line 5446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str955, 0},
+#line 3313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str956, 0},
+#line 5343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str957, 0},
+#line 5453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str958, 0},
+#line 5455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str959, 0},
+#line 2546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str960, 0},
+#line 2960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str961, 0},
+#line 2971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str962, 0},
+#line 2718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str963, 0},
+#line 4576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str964, 0},
+#line 1054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str965, 0},
+#line 431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str966, 0},
+#line 566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str967, 0},
+#line 5053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str968, 0},
+#line 346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str969, 0},
+#line 3037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str970, 0},
+#line 2544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str971, 0},
+#line 3466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str972, 0},
+#line 4904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str973, 0},
+#line 5369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str974, 0},
+#line 1986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str975, 0},
+#line 5595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str976, 0},
+#line 329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str977, 0},
+#line 182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str978, 0},
+#line 2125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str979, 4},
+#line 2075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str980, 0},
+#line 432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str981, 0},
+#line 4902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str982, 0},
+#line 244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str983, 0},
+#line 3053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str984, 0},
+#line 1014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str985, 0},
+#line 1410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str986, 0},
+#line 5251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str987, 0},
+#line 1578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str988, 0},
+#line 2086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str989, 0},
+#line 1357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str990, 0},
+#line 3052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str991, 0},
+#line 5607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str992, 0},
+#line 4975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str993, 0},
+#line 5072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str994, 0},
+#line 1979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str995, 0},
+#line 376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str996, 0},
+#line 5083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str997, 0},
+#line 230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str998, 0},
+#line 417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str999, 0},
+#line 4918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1000, 0},
+#line 370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1001, 0},
+#line 2070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1002, 0},
+#line 1585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1003, 0},
+#line 2113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1004, 0},
+#line 1313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1005, 0},
+#line 800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1006, 0},
+#line 2936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1007, 0},
+#line 3325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1008, 0},
+#line 1293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1009, 0},
+#line 4658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1010, 0},
+#line 1395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1011, 0},
+#line 2547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1012, 0},
+#line 160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1013, 0},
+#line 1899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1014, 0},
+#line 1880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1015, 0},
+#line 857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1016, 0},
+#line 2984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1017, 0},
+#line 711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1018, 0},
+#line 3087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1019, 0},
+#line 2909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1020, 0},
+#line 3526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1021, 4},
+#line 3488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1022, 0},
+#line 2875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1023, 0},
+#line 793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1024, 0},
+#line 993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1025, 0},
+#line 632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1026, 0},
+#line 1968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1027, 0},
+#line 5252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1028, 0},
+#line 3077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1029, 0},
+#line 4948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1030, 0},
+#line 3054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1031, 0},
+#line 4922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1032, 0},
+#line 2958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1033, 0},
+#line 1646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1034, 0},
+#line 2475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1035, 0},
+#line 2747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1036, 0},
+#line 5456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1037, 0},
+#line 2089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1038, 0},
+#line 2082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1039, 0},
+#line 2832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1040, 0},
+#line 2493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1041, 0},
+#line 4903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1042, 0},
+#line 2069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1043, 0},
+#line 347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1044, 0},
+#line 5572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1045, 0},
+#line 5193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1046, 0},
+#line 5326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1047, 0},
+#line 3060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1048, 0},
+#line 5454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1049, 0},
+#line 1396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1050, 0},
+#line 4677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1051, 0},
+#line 4917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1052, 0},
+#line 3241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1053, 0},
+#line 5590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1054, 0},
+#line 3420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1055, 0},
+#line 3133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1056, 0},
+#line 4493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1057, 0},
+#line 5323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1058, 0},
+#line 3119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1059, 0},
+#line 5037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1060, 0},
+#line 4949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1061, 0},
+#line 4521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1062, 0},
+#line 5100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1063, 0},
+#line 2818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1064, 0},
+#line 1617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1065, 0},
+#line 533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1066, 0},
+#line 5620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1067, 0},
+#line 4484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1068, 0},
+#line 3193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1069, 0},
+#line 1634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1070, 0},
+#line 537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1071, 0},
+#line 2703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1072, 0},
+#line 1613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1073, 0},
+#line 1003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1074, 0},
+#line 1536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1075, 0},
+#line 315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1076, 0},
+#line 5189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1077, 0},
+#line 5276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1078, 0},
+#line 3230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1079, 0},
+#line 1975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1080, 0},
+#line 4544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1081, 0},
+#line 2593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1082, 0},
+#line 2990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1083, 0},
+#line 369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1084, 0},
+#line 608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1085, 0},
+#line 227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1086, 0},
+#line 2130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1087, 0},
+#line 1146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1088, 4},
+#line 5623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1089, 0},
+#line 2577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1090, 0},
+#line 1277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1091, 0},
+#line 3064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1092, 0},
+#line 4490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1093, 0},
+#line 4706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1094, 0},
+#line 3062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1095, 4},
+#line 1902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1096, 0},
+#line 2878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1097, 0},
+#line 1921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1098, 0},
+#line 994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1099, 0},
+#line 4992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1100, 0},
+#line 193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1101, 0},
+#line 163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1102, 0},
+#line 5282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1103, 0},
+#line 1893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1104, 0},
+#line 1699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1105, 0},
+#line 1147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1106, 4},
+#line 3367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1107, 0},
+#line 2477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1108, 0},
+#line 2470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1109, 0},
+#line 4781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1110, 4},
+#line 561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1111, 4},
+#line 4674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1112, 0},
+#line 5109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1113, 0},
+#line 101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1114, 0},
+#line 2087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1115, 0},
+#line 1570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1116, 0},
+#line 6110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1117, 2},
+#line 2528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1118, 0},
+#line 1755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1119, 0},
+#line 1754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1120, 0},
+#line 1638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1121, 0},
+#line 1107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1122, 0},
+#line 909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1123, 0},
+#line 5608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1124, 0},
+#line 195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1125, 0},
+#line 2885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1126, 0},
+#line 1660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1127, 0},
+#line 4984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1128, 0},
+#line 4686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1129, 0},
+#line 1166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1130, 0},
+#line 1759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1131, 0},
+#line 811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1132, 0},
+#line 1212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1133, 0},
+#line 2479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1134, 0},
+#line 3036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1135, 0},
+#line 911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1136, 0},
+#line 1084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1137, 0},
+#line 2695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1138, 2},
+#line 513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1139, 4},
+#line 3383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1140, 0},
+#line 518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1141, 4},
+#line 3059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1142, 0},
+#line 512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1143, 4},
+#line 447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1144, 0},
+#line 1048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1145, 0},
+#line 2884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1146, 0},
+#line 522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1147, 4},
+#line 5322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1148, 0},
+#line 2930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1149, 0},
+#line 2929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1150, 0},
+#line 6114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1151, 0},
+#line 1296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1152, 0},
+#line 3161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1153, 0},
+#line 2630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1154, 0},
+#line 107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1155, 0},
+#line 500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1156, 4},
+#line 1459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1157, 0},
+#line 6088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1158, 0},
+#line 1760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1159, 0},
+#line 1213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1160, 0},
+#line 4625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1161, 0},
+#line 2893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1162, 0},
+#line 167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1163, 0},
+#line 851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1164, 0},
+#line 5092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1165, 0},
+#line 2823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1166, 0},
+#line 3202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1167, 0},
+#line 5391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1168, 0},
+#line 2480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1169, 0},
+#line 517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1170, 4},
+#line 4633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1171, 0},
+#line 1758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1172, 0},
+#line 1756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1173, 0},
+#line 1210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1174, 0},
+#line 5226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1175, 0},
+#line 4870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1176, 0},
+#line 3373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1177, 0},
+#line 3065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1178, 0},
+#line 4491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1179, 1},
+#line 3359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1180, 0},
+#line 1963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1181, 0},
+#line 519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1182, 4},
+#line 1894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1183, 0},
+#line 1583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1184, 0},
+#line 2904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1185, 0},
+#line 1070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1186, 0},
+#line 4973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1187, 0},
+#line 4663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1188, 0},
+#line 515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1189, 4},
+#line 1703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1190, 4},
+#line 5317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1191, 0},
+#line 542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1192, 0},
+#line 2127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1193, 0},
+#line 3090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1194, 0},
+#line 5336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1195, 0},
+#line 2458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1196, 0},
+#line 717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1197, 0},
+#line 1394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1198, 0},
+#line 5030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1199, 0},
+#line 6089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1200, 2},
+#line 509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1201, 4},
+#line 386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1202, 0},
+#line 2782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1203, 0},
+#line 2081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1204, 0},
+#line 760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1205, 0},
+#line 1140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1206, 4},
+#line 1547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1207, 0},
+#line 3259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1208, 0},
+#line 2603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1209, 0},
+#line 4795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1210, 4},
+#line 4699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1211, 0},
+#line 1019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1212, 0},
+#line 507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1213, 4},
+#line 1648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1214, 0},
+#line 1316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1215, 0},
+#line 6087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1216, 0},
+#line 1461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1217, 0},
+#line 1973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1218, 0},
+#line 1705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1219, 0},
+#line 4996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1220, 0},
+#line 4525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1221, 0},
+#line 2459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1222, 0},
+#line 2609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1223, 0},
+#line 5088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1224, 0},
+#line 1059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1225, 0},
+#line 5630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1226, 0},
+#line 1326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1227, 0},
+#line 1317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1228, 0},
+#line 2066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1229, 0},
+#line 3260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1230, 0},
+#line 4802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1231, 0},
+#line 3225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1232, 1},
+#line 5383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1233, 0},
+#line 236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1234, 0},
+#line 3370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1235, 0},
+#line 621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1236, 0},
+#line 3468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1237, 0},
+#line 2859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1238, 0},
+#line 2586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1239, 0},
+#line 1892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1240, 0},
+#line 2047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1241, 0},
+#line 715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1242, 0},
+#line 3270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1243, 0},
+#line 508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1244, 4},
+#line 1584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1245, 0},
+#line 6113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1246, 0},
+#line 1368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1247, 0},
+#line 1123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1248, 4},
+#line 3371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1249, 0},
+#line 762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1250, 0},
+#line 484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1251, 4},
+#line 1571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1252, 0},
+#line 506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1253, 4},
+#line 1620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1254, 0},
+#line 4494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1255, 0},
+#line 501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1256, 4},
+#line 751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1257, 0},
+#line 460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1258, 0},
+#line 485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1259, 4},
+#line 516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1260, 4},
+#line 3368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1261, 0},
+#line 4830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1262, 0},
+#line 511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1263, 4},
+#line 5380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1264, 0},
+#line 162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1265, 0},
+#line 514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1266, 4},
+#line 2799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1267, 0},
+#line 1600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1268, 0},
+#line 1283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1269, 0},
+#line 2968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1270, 0},
+#line 716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1271, 0},
+#line 510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1272, 4},
+#line 1575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1273, 0},
+#line 3072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1274, 0},
+#line 521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1275, 4},
+#line 3399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1276, 0},
+#line 4692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1277, 0},
+#line 3493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1278, 0},
+#line 4798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1279, 0},
+#line 3293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1280, 0},
+#line 700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1281, 0},
+#line 2629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1282, 0},
+#line 4875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1283, 0},
+#line 4793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1284, 4},
+#line 5162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1285, 0},
+#line 2704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1286, 0},
+#line 4786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1287, 0},
+#line 1284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1288, 0},
+#line 2918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1289, 0},
+#line 3361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1290, 0},
+#line 2605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1291, 0},
+#line 297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1292, 0},
+#line 622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1293, 0},
+#line 1548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1294, 0},
+#line 2993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1295, 0},
+#line 5390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1296, 0},
+#line 6108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1297, 0},
+#line 3220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1298, 0},
+#line 2757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1299, 0},
+#line 1696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1300, 0},
+#line 2876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1301, 0},
+#line 4650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1302, 0},
+#line 5283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1303, 0},
+#line 610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1304, 0},
+#line 5333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1305, 0},
+#line 1579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1306, 0},
+#line 2967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1307, 0},
+#line 3410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1308, 0},
+#line 2754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1309, 0},
+#line 2883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1310, 0},
+#line 1105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1311, 0},
+#line 1157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1312, 0},
+#line 2755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1313, 0},
+#line 3224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1314, 0},
+#line 4865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1315, 0},
+#line 4872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1316, 0},
+#line 3070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1317, 0},
+#line 4505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1318, 0},
+#line 2567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1319, 0},
+#line 4794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1320, 4},
+#line 4900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1321, 0},
+#line 5178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1322, 0},
+#line 201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1323, 0},
+#line 3294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1324, 0},
+#line 6116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1325, 2},
+#line 3154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1326, 0},
+#line 2511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1327, 0},
+#line 2030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1328, 0},
+#line 5101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1329, 0},
+#line 1970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1330, 0},
+#line 1121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1331, 0},
+#line 330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1332, 0},
+#line 4542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1333, 0},
+#line 333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1334, 0},
+#line 2510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1335, 0},
+#line 2738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1336, 0},
+#line 4499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1337, 0},
+#line 1732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1338, 0},
+#line 881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1339, 0},
+#line 4901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1340, 0},
+#line 3019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1341, 0},
+#line 3353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1342, 0},
+#line 2740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1343, 0},
+#line 761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1344, 0},
+#line 3496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1345, 0},
+#line 153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1346, 0},
+#line 4837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1347, 0},
+#line 3372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1348, 0},
+#line 1814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1349, 0},
+#line 1264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1350, 0},
+#line 964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1351, 0},
+#line 3018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1352, 0},
+#line 2118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1353, 0},
+#line 1860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1354, 0},
+#line 5001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1355, 0},
+#line 523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1356, 4},
+#line 2675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1357, 0},
+#line 4871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1358, 0},
+#line 4945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1359, 0},
+#line 298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1360, 0},
+#line 5196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1361, 0},
+#line 2734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1362, 0},
+#line 2797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1363, 0},
+#line 4796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1364, 0},
+#line 1008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1365, 0},
+#line 2804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1366, 0},
+#line 4986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1367, 0},
+#line 2921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1368, 0},
+#line 4972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1369, 0},
+#line 4803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1370, 0},
+#line 3143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1371, 0},
+#line 505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1372, 4},
+#line 336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1373, 0},
+#line 313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1374, 0},
+#line 2841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1375, 0},
+#line 2843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1376, 0},
+#line 364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1377, 0},
+#line 5586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1378, 0},
+#line 4915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1379, 1},
+#line 1657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1380, 0},
+#line 4990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1381, 0},
+#line 5179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1382, 0},
+#line 2513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1383, 0},
+#line 2842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1384, 0},
+#line 4571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1385, 0},
+#line 577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1386, 0},
+#line 3086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1387, 0},
+#line 5292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1388, 0},
+#line 4978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1389, 0},
+#line 2451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1390, 0},
+#line 1470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1391, 0},
+#line 5066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1392, 0},
+#line 1912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1393, 0},
+#line 2886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1394, 0},
+#line 3382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1395, 0},
+#line 452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1396, 0},
+#line 2568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1397, 0},
+#line 2512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1398, 0},
+#line 4864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1399, 0},
+#line 3021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1400, 0},
+#line 1644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1401, 0},
+#line 1005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1402, 0},
+#line 1404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1403, 0},
+#line 3352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1404, 0},
+#line 2574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1405, 0},
+#line 6091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1406, 4},
+#line 3020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1407, 0},
+#line 5362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1408, 0},
+#line 5034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1409, 0},
+#line 718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1410, 0},
+#line 697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1411, 0},
+#line 1010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1412, 0},
+#line 1573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1413, 0},
+#line 1841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1414, 0},
+#line 2937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1415, 0},
+#line 3354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1416, 0},
+#line 2744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1417, 0},
+#line 4541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1418, 0},
+#line 1337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1419, 0},
+#line 5321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1420, 0},
+#line 5416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1421, 0},
+#line 4468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1422, 0},
+#line 169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1423, 0},
+#line 5191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1424, 0},
+#line 3223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1425, 0},
+#line 5190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1426, 0},
+#line 3222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1427, 0},
+#line 281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1428, 0},
+#line 1873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1429, 0},
+#line 808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1430, 0},
+#line 4979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1431, 0},
+#line 4838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1432, 0},
+#line 1866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1433, 0},
+#line 4467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1434, 0},
+#line 2560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1435, 0},
+#line 5564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1436, 0},
+#line 2611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1437, 0},
+#line 312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1438, 0},
+#line 5102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1439, 0},
+#line 1599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1440, 0},
+#line 2563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1441, 0},
+#line 3271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1442, 0},
+#line 2481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1443, 0},
+#line 1602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1444, 0},
+#line 85 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1445, 0},
+#line 5348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1446, 0},
+#line 80 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1447, 0},
+#line 3328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1448, 0},
+#line 5359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1449, 0},
+#line 4926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1450, 0},
+#line 4634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1451, 0},
+#line 4675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1452, 0},
+#line 1489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1453, 4},
+#line 4952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1454, 0},
+#line 1863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1455, 0},
+#line 5055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1456, 0},
+#line 4694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1457, 0},
+#line 1574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1458, 0},
+#line 4497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1459, 0},
+#line 4472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1460, 0},
+#line 1056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1461, 0},
+#line 2665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1462, 0},
+#line 4520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1463, 0},
+#line 5204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1464, 0},
+#line 190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1465, 0},
+#line 5434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1466, 0},
+#line 3515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1467, 0},
+#line 5025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1468, 0},
+#line 3499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1469, 0},
+#line 4759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1470, 0},
+#line 3364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1471, 0},
+#line 5591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1472, 0},
+#line 420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1473, 0},
+#line 4647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1474, 0},
+#line 763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1475, 0},
+#line 5077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1476, 0},
+#line 1695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1477, 0},
+#line 4537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1478, 0},
+#line 294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1479, 0},
+#line 2825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1480, 0},
+#line 3445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1481, 0},
+#line 4705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1482, 0},
+#line 4784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1483, 0},
+#line 1598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1484, 0},
+#line 2998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1485, 0},
+#line 5306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1486, 0},
+#line 4498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1487, 0},
+#line 744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1488, 0},
+#line 2452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1489, 0},
+#line 2892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1490, 0},
+#line 1104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1491, 0},
+#line 5098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1492, 0},
+#line 3470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1493, 0},
+#line 5057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1494, 0},
+#line 5601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1495, 0},
+#line 5019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1496, 0},
+#line 5445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1497, 0},
+#line 4475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1498, 0},
+#line 5588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1499, 0},
+#line 2084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1500, 0},
+#line 2922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1501, 0},
+#line 4773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1502, 4},
+#line 5562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1503, 0},
+#line 3492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1504, 0},
+#line 2932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1505, 0},
+#line 613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1506, 0},
+#line 425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1507, 0},
+#line 1948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1508, 0},
+#line 1971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1509, 0},
+#line 2085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1510, 0},
+#line 1853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1511, 0},
+#line 2469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1512, 0},
+#line 3282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1513, 0},
+#line 1929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1514, 0},
+#line 5160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1515, 0},
+#line 1838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1516, 0},
+#line 504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1517, 4},
+#line 4676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1518, 0},
+#line 5417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1519, 0},
+#line 2780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1520, 0},
+#line 2683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1521, 0},
+#line 159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1522, 0},
+#line 3315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1523, 0},
+#line 5163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1524, 0},
+#line 22 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1525, 0},
+#line 4929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1526, 0},
+#line 4883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1527, 0},
+#line 3369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1528, 0},
+#line 175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1529, 0},
+#line 2450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1530, 0},
+#line 390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1531, 0},
+#line 520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1532, 4},
+#line 2807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1533, 0},
+#line 2802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1534, 0},
+#line 4814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1535, 0},
+#line 1443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1536, 0},
+#line 4775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1537, 4},
+#line 3106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1538, 0},
+#line 4939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1539, 0},
+#line 5158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1540, 0},
+#line 1958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1541, 0},
+#line 5256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1542, 0},
+#line 3102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1543, 0},
+#line 1012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1544, 0},
+#line 1312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1545, 0},
+#line 1685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1546, 0},
+#line 2756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1547, 0},
+#line 631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1548, 0},
+#line 1642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1549, 0},
+#line 5598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1550, 0},
+#line 5171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1551, 0},
+#line 1444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1552, 0},
+#line 5354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1553, 0},
+#line 1442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1554, 4},
+#line 5352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1555, 0},
+#line 3198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1556, 0},
+#line 1106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1557, 0},
+#line 5240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1558, 0},
+#line 2758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1559, 0},
+#line 2131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1560, 0},
+#line 5640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1561, 0},
+#line 2813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1562, 0},
+#line 4855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1563, 0},
+#line 5176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1564, 0},
+#line 2746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1565, 0},
+#line 4707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1566, 0},
+#line 5374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1567, 0},
+#line 1075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1568, 0},
+#line 2901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1569, 0},
+#line 5597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1570, 0},
+#line 5373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1571, 0},
+#line 2636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1572, 0},
+#line 1469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1573, 0},
+#line 2721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1574, 0},
+#line 5638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1575, 0},
+#line 3085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1576, 0},
+#line 1476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1577, 4},
+#line 1145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1578, 4},
+#line 2488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1579, 0},
+#line 2638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1580, 0},
+#line 3406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1581, 0},
+#line 5065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1582, 0},
+#line 4789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1583, 0},
+#line 1381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1584, 0},
+#line 1458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1585, 0},
+#line 2779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1586, 0},
+#line 770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1587, 0},
+#line 1881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1588, 0},
+#line 5372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1589, 0},
+#line 3088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1590, 0},
+#line 100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1591, 0},
+#line 5168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1592, 0},
+#line 2979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1593, 0},
+#line 3272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1594, 0},
+#line 5132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1595, 0},
+#line 756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1596, 0},
+#line 1847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1597, 0},
+#line 3460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1598, 0},
+#line 5180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1599, 0},
+#line 1967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1600, 0},
+#line 2700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1601, 0},
+#line 2471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1602, 0},
+#line 2021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1603, 0},
+#line 5009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1604, 0},
+#line 2860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1605, 0},
+#line 3528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1606, 0},
+#line 3424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1607, 0},
+#line 1103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1608, 0},
+#line 2894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1609, 0},
+#line 3440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1610, 0},
+#line 1882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1611, 0},
+#line 2805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1612, 0},
+#line 1941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1613, 0},
+#line 2900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1614, 0},
+#line 17 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1615, 0},
+#line 2701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1616, 0},
+#line 4698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1617, 0},
+#line 2870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1618, 0},
+#line 4471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1619, 0},
+#line 176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1620, 0},
+#line 5028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1621, 0},
+#line 6109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1622, 0},
+#line 1492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1623, 4},
+#line 1435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1624, 0},
+#line 2959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1625, 4},
+#line 3127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1626, 0},
+#line 1581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1627, 0},
+#line 2699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1628, 0},
+#line 476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1629, 0},
+#line 1837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1630, 0},
+#line 2798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1631, 0},
+#line 4836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1632, 0},
+#line 1641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1633, 0},
+#line 4631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1634, 0},
+#line 1595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1635, 4},
+#line 5299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1636, 0},
+#line 104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1637, 0},
+#line 4550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1638, 0},
+#line 2938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1639, 0},
+#line 1883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1640, 0},
+#line 1944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1641, 0},
+#line 1505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1642, 4},
+#line 5311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1643, 0},
+#line 4659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1644, 0},
+#line 4684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1645, 0},
+#line 389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1646, 0},
+#line 215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1647, 0},
+#line 2692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1648, 0},
+#line 1622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1649, 0},
+#line 5288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1650, 0},
+#line 5409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1651, 0},
+#line 2806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1652, 0},
+#line 585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1653, 0},
+#line 701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1654, 0},
+#line 2906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1655, 0},
+#line 3490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1656, 0},
+#line 4862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1657, 0},
+#line 4899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1658, 0},
+#line 2552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1659, 0},
+#line 4824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1660, 0},
+#line 106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1661, 0},
+#line 2940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1662, 0},
+#line 563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1663, 0},
+#line 1545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1664, 0},
+#line 5004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1665, 0},
+#line 2551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1666, 0},
+#line 4941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1667, 0},
+#line 4788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1668, 0},
+#line 440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1669, 0},
+#line 3442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1670, 0},
+#line 2717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1671, 0},
+#line 399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1672, 0},
+#line 2751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1673, 0},
+#line 2785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1674, 0},
+#line 5381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1675, 0},
+#line 1334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1676, 0},
+#line 1918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1677, 0},
+#line 429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1678, 4},
+#line 2058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1679, 0},
+#line 3329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1680, 0},
+#line 5286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1681, 0},
+#line 3250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1682, 0},
+#line 3058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1683, 0},
+#line 383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1684, 0},
+#line 2023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1685, 0},
+#line 3360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1686, 0},
+#line 1057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1687, 0},
+#line 4863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1688, 0},
+#line 4626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1689, 0},
+#line 3400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1690, 0},
+#line 4767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1691, 0},
+#line 2957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1692, 0},
+#line 755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1693, 0},
+#line 4933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1694, 0},
+#line 2532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1695, 0},
+#line 2619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1696, 0},
+#line 2095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1697, 4},
+#line 531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1698, 0},
+#line 5406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1699, 0},
+#line 4623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1700, 0},
+#line 1072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1701, 0},
+#line 5051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1702, 0},
+#line 5621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1703, 0},
+#line 5297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1704, 0},
+#line 4920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1705, 0},
+#line 4916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1706, 0},
+#line 2696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1707, 0},
+#line 2029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1708, 0},
+#line 3040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1709, 0},
+#line 2610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1710, 0},
+#line 3128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1711, 0},
+#line 5401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1712, 0},
+#line 374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1713, 0},
+#line 4538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1714, 0},
+#line 1688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1715, 0},
+#line 212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1716, 0},
+#line 5356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1717, 0},
+#line 570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1718, 0},
+#line 2801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1719, 0},
+#line 2556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1720, 0},
+#line 4928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1721, 0},
+#line 3409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1722, 0},
+#line 2995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1723, 0},
+#line 5007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1724, 0},
+#line 4682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1725, 0},
+#line 2591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1726, 0},
+#line 2088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1727, 0},
+#line 2956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1728, 0},
+#line 3147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1729, 0},
+#line 3136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1730, 0},
+#line 4042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1731, 2},
+#line 99 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1732, 0},
+#line 5050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1733, 0},
+#line 381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1734, 0},
+#line 1821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1735, 0},
+#line 1269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1736, 0},
+#line 216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1737, 0},
+#line 970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1738, 0},
+#line 1047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1739, 0},
+#line 331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1740, 0},
+#line 1425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1741, 0},
+#line 870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1742, 0},
+#line 3401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1743, 0},
+#line 2988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1744, 0},
+#line 1659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1745, 0},
+#line 564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1746, 0},
+#line 4823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1747, 0},
+#line 5302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1748, 0},
+#line 5632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1749, 0},
+#line 708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1750, 0},
+#line 4088 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1751, 0},
+#line 3967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1752, 0},
+#line 5227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1753, 0},
+#line 5435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1754, 0},
+#line 3439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1755, 0},
+#line 4255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1756, 0},
+#line 2478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1757, 0},
+#line 1565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1758, 0},
+#line 543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1759, 0},
+#line 5170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1760, 0},
+#line 136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1761, 0},
+#line 4097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1762, 0},
+#line 398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1763, 0},
+#line 5198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1764, 0},
+#line 2887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1765, 0},
+#line 3954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1766, 0},
+#line 439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1767, 0},
+#line 1192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1768, 0},
+#line 887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1769, 0},
+#line 1562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1770, 0},
+#line 607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1771, 0},
+#line 1114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1772, 0},
+#line 1538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1773, 0},
+#line 2588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1774, 0},
+#line 3973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1775, 0},
+#line 391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1776, 0},
+#line 5626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1777, 0},
+#line 4635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1778, 0},
+#line 5164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1779, 0},
+#line 4968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1780, 0},
+#line 189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1781, 0},
+#line 1161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1782, 0},
+#line 142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1783, 0},
+#line 636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1784, 0},
+#line 4715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1785, 0},
+#line 37 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1786, 0},
+#line 2697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1787, 0},
+#line 1978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1788, 0},
+#line 546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1789, 0},
+#line 4063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1790, 0},
+#line 3233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1791, 0},
+#line 5570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1792, 0},
+#line 5184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1793, 0},
+#line 4651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1794, 0},
+#line 1278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1795, 0},
+#line 117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1796, 0},
+#line 2026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1797, 0},
+#line 51 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1798, 0},
+#line 1379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1799, 0},
+#line 4092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1800, 0},
+#line 4818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1801, 0},
+#line 4500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1802, 0},
+#line 858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1803, 0},
+#line 1609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1804, 0},
+#line 2607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1805, 0},
+#line 545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1806, 0},
+#line 4720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1807, 0},
+#line 3953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1808, 0},
+#line 2690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1809, 0},
+#line 2913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1810, 0},
+#line 3199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1811, 0},
+#line 5086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1812, 0},
+#line 2997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1813, 0},
+#line 4833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1814, 0},
+#line 5161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1815, 0},
+#line 4777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1816, 0},
+#line 2027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1817, 0},
+#line 5216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1818, 0},
+#line 2974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1819, 0},
+#line 5079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1820, 0},
+#line 4637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1821, 0},
+#line 3105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1822, 0},
+#line 3131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1823, 0},
+#line 3200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1824, 0},
+#line 1285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1825, 0},
+#line 241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1826, 0},
+#line 2975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1827, 0},
+#line 1362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1828, 0},
+#line 1164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1829, 0},
+#line 4083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1830, 0},
+#line 1302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1831, 0},
+#line 4964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1832, 0},
+#line 5174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1833, 0},
+#line 73 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1834, 0},
+#line 2615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1835, 0},
+#line 56 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1836, 0},
+#line 1375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1837, 0},
+#line 855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1838, 0},
+#line 2039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1839, 0},
+#line 4839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1840, 0},
+#line 559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1841, 0},
+#line 1614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1842, 0},
+#line 4508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1843, 0},
+#line 1621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1844, 0},
+#line 1346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1845, 0},
+#line 4827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1846, 0},
+#line 666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1847, 0},
+#line 115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1848, 0},
+#line 64 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1849, 0},
+#line 3999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1850, 0},
+#line 2025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1851, 0},
+#line 4016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1852, 0},
+#line 4507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1853, 0},
+#line 2044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1854, 0},
+#line 2048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1855, 0},
+#line 4059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1856, 0},
+#line 1061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1857, 0},
+#line 571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1858, 0},
+#line 586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1859, 0},
+#line 3063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1860, 0},
+#line 2691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1861, 0},
+#line 5277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1862, 0},
+#line 5045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1863, 0},
+#line 3319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1864, 0},
+#line 4095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1865, 0},
+#line 403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1866, 0},
+#line 4971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1867, 0},
+#line 4834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1868, 0},
+#line 4577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1869, 0},
+#line 5580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1870, 0},
+#line 4093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1871, 0},
+#line 4817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1872, 0},
+#line 52 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1873, 0},
+#line 2109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1874, 0},
+#line 112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1875, 0},
+#line 1964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1876, 0},
+#line 5581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1877, 0},
+#line 5107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1878, 0},
+#line 1163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1879, 0},
+#line 2592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1880, 0},
+#line 5571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1881, 0},
+#line 5089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1882, 0},
+#line 39 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1883, 0},
+#line 54 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1884, 0},
+#line 1331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1885, 0},
+#line 4476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1886, 0},
+#line 2621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1887, 0},
+#line 5125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1888, 0},
+#line 4721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1889, 0},
+#line 3506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1890, 0},
+#line 5344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1891, 0},
+#line 2461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1892, 0},
+#line 4835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1893, 0},
+#line 4515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1894, 0},
+#line 4729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1895, 0},
+#line 2064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1896, 0},
+#line 2711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1897, 0},
+#line 1330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1898, 0},
+#line 1537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1899, 0},
+#line 4730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1900, 0},
+#line 4726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1901, 0},
+#line 4725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1902, 0},
+#line 2103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1903, 0},
+#line 1549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1904, 0},
+#line 44 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1905, 0},
+#line 709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1906, 0},
+#line 47 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1907, 0},
+#line 1989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1908, 0},
+#line 4588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1909, 0},
+#line 4716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1910, 0},
+#line 2726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1911, 0},
+#line 42 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1912, 0},
+#line 4906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1913, 0},
+#line 4718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1914, 0},
+#line 65 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1915, 0},
+#line 2028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1916, 0},
+#line 4881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1917, 0},
+#line 3273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1918, 0},
+#line 4738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1919, 0},
+#line 1438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1920, 4},
+#line 4229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1921, 0},
+#line 2570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1922, 0},
+#line 3324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1923, 0},
+#line 4776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1924, 4},
+#line 1697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1925, 0},
+#line 588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1926, 0},
+#line 67 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1927, 0},
+#line 5378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1928, 0},
+#line 274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1929, 0},
+#line 5049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1930, 0},
+#line 5614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1931, 0},
+#line 5428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1932, 0},
+#line 2912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1933, 0},
+#line 4101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1934, 0},
+#line 5157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1935, 0},
+#line 1640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1936, 0},
+#line 1052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1937, 0},
+#line 4011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1938, 0},
+#line 38 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1939, 0},
+#line 5199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1940, 0},
+#line 407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1941, 0},
+#line 594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1942, 0},
+#line 4868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1943, 0},
+#line 2836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1944, 0},
+#line 1058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1945, 0},
+#line 2561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1946, 0},
+#line 2581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1947, 0},
+#line 1946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1948, 0},
+#line 277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1949, 0},
+#line 1915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1950, 0},
+#line 2601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1951, 0},
+#line 1937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1952, 0},
+#line 1908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1953, 0},
+#line 1653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1954, 0},
+#line 593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1955, 0},
+#line 720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1956, 0},
+#line 5404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1957, 0},
+#line 2090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1958, 4},
+#line 2035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1959, 0},
+#line 5375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1960, 0},
+#line 2666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1961, 0},
+#line 4081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1962, 0},
+#line 1917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1963, 0},
+#line 5341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1964, 0},
+#line 58 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1965, 0},
+#line 5376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1966, 0},
+#line 2633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1967, 0},
+#line 4031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1968, 0},
+#line 199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1969, 0},
+#line 191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1970, 0},
+#line 177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1971, 0},
+#line 161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1972, 0},
+#line 4230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1973, 0},
+#line 5303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1974, 0},
+#line 3397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1975, 0},
+#line 6101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1976, 0},
+#line 4762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1977, 0},
+#line 5411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1978, 0},
+#line 5389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1979, 0},
+#line 5310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1980, 0},
+#line 2899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1981, 0},
+#line 4746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1982, 0},
+#line 4528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1983, 0},
+#line 5141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1984, 0},
+#line 1448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1985, 0},
+#line 4712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1986, 0},
+#line 5149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1987, 0},
+#line 4589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1988, 0},
+#line 4102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1989, 0},
+#line 3981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1990, 0},
+#line 4247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1991, 0},
+#line 4215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1992, 0},
+#line 4213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1993, 0},
+#line 168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1994, 0},
+#line 4117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1995, 0},
+#line 4116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1996, 0},
+#line 4104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1997, 0},
+#line 4790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1998, 4},
+#line 4115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str1999, 0},
+#line 4122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2000, 0},
+#line 4107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2001, 0},
+#line 4111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2002, 0},
+#line 124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2003, 0},
+#line 4106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2004, 0},
+#line 1652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2005, 0},
+#line 1629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2006, 0},
+#line 5624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2007, 0},
+#line 3491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2008, 0},
+#line 702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2009, 0},
+#line 4195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2010, 0},
+#line 93 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2011, 0},
+#line 4112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2012, 0},
+#line 4208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2013, 0},
+#line 4203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2014, 0},
+#line 4202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2015, 0},
+#line 5370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2016, 0},
+#line 4119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2017, 0},
+#line 4240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2018, 0},
+#line 4211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2019, 0},
+#line 3970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2020, 0},
+#line 4204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2021, 0},
+#line 4703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2022, 0},
+#line 4103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2023, 0},
+#line 4199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2024, 0},
+#line 724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2025, 0},
+#line 4153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2026, 0},
+#line 4152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2027, 0},
+#line 1319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2028, 0},
+#line 4590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2029, 0},
+#line 4051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2030, 0},
+#line 4113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2031, 0},
+#line 4201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2032, 0},
+#line 4067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2033, 0},
+#line 2587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2034, 0},
+#line 198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2035, 0},
+#line 90 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2036, 0},
+#line 4200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2037, 0},
+#line 3505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2038, 0},
+#line 5208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2039, 0},
+#line 2812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2040, 0},
+#line 5290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2041, 0},
+#line 4110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2042, 0},
+#line 4861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2043, 0},
+#line 4125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2044, 0},
+#line 4216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2045, 0},
+#line 4123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2046, 0},
+#line 4120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2047, 0},
+#line 773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2048, 0},
+#line 4829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2049, 0},
+#line 2606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2050, 0},
+#line 764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2051, 0},
+#line 5242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2052, 0},
+#line 4214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2053, 0},
+#line 4209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2054, 0},
+#line 4118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2055, 0},
+#line 4109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2056, 0},
+#line 5185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2057, 0},
+#line 6096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2058, 0},
+#line 60 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2059, 0},
+#line 5565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2060, 0},
+#line 5284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2061, 0},
+#line 4207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2062, 0},
+#line 988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2063, 0},
+#line 5340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2064, 0},
+#line 4108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2065, 0},
+#line 1564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2066, 0},
+#line 699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2067, 0},
+#line 557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2068, 1},
+#line 4126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2069, 0},
+#line 4509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2070, 0},
+#line 4206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2071, 0},
+#line 1528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2072, 4},
+#line 4701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2073, 0},
+#line 5127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2074, 0},
+#line 4212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2075, 0},
+#line 3132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2076, 0},
+#line 4124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2077, 0},
+#line 2642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2078, 0},
+#line 430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2079, 0},
+#line 6102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2080, 0},
+#line 178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2081, 0},
+#line 2796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2082, 0},
+#line 1700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2083, 0},
+#line 4228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2084, 0},
+#line 4257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2085, 0},
+#line 5296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2086, 0},
+#line 4132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2087, 0},
+#line 1338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2088, 4},
+#line 4014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2089, 0},
+#line 40 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2090, 0},
+#line 4133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2091, 0},
+#line 4134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2092, 0},
+#line 4514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2093, 0},
+#line 2623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2094, 0},
+#line 1339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2095, 4},
+#line 4131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2096, 0},
+#line 4226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2097, 0},
+#line 242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2098, 0},
+#line 4473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2099, 0},
+#line 4078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2100, 4},
+#line 3503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2101, 0},
+#line 2772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2102, 0},
+#line 3061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2103, 4},
+#line 179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2104, 0},
+#line 2963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2105, 0},
+#line 1885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2106, 0},
+#line 3867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2107, 0},
+#line 3935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2108, 0},
+#line 427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2109, 0},
+#line 4182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2110, 0},
+#line 4008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2111, 0},
+#line 3952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2112, 2},
+#line 4506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2113, 0},
+#line 3232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2114, 0},
+#line 3890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2115, 0},
+#line 4181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2116, 0},
+#line 4816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2117, 0},
+#line 3877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2118, 0},
+#line 3921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2119, 0},
+#line 3869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2120, 0},
+#line 3937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2121, 0},
+#line 3878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2122, 0},
+#line 4907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2123, 0},
+#line 2736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2124, 0},
+#line 3936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2125, 0},
+#line 1954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2126, 0},
+#line 3663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2127, 0},
+#line 3944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2128, 0},
+#line 3662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2129, 0},
+#line 3865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2130, 0},
+#line 2827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2131, 0},
+#line 2650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2132, 0},
+#line 583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2133, 0},
+#line 3929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2134, 0},
+#line 4828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2135, 0},
+#line 1637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2136, 0},
+#line 1295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2137, 4},
+#line 2803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2138, 0},
+#line 3923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2139, 0},
+#line 240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2140, 0},
+#line 1352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2141, 4},
+#line 4280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2142, 0},
+#line 3683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2143, 0},
+#line 4581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2144, 4},
+#line 3531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2145, 0},
+#line 3880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2146, 0},
+#line 3782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2147, 0},
+#line 3780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2148, 0},
+#line 3697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2149, 0},
+#line 3696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2150, 0},
+#line 4183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2151, 0},
+#line 3685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2152, 0},
+#line 1911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2153, 0},
+#line 1831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2154, 4},
+#line 3701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2155, 0},
+#line 3688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2156, 0},
+#line 3692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2157, 0},
+#line 2943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2158, 0},
+#line 224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2159, 4},
+#line 3687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2160, 0},
+#line 3669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2161, 0},
+#line 4234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2162, 0},
+#line 3809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2163, 2},
+#line 3765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2164, 0},
+#line 1843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2165, 4},
+#line 3693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2166, 0},
+#line 3775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2167, 0},
+#line 3771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2168, 0},
+#line 5335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2169, 0},
+#line 2705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2170, 0},
+#line 3879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2171, 0},
+#line 3157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2172, 0},
+#line 3699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2173, 0},
+#line 2735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2174, 0},
+#line 3772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2175, 0},
+#line 2856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2176, 4},
+#line 3858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2177, 0},
+#line 2453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2178, 0},
+#line 3684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2179, 0},
+#line 4534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2180, 0},
+#line 3768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2181, 0},
+#line 3729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2182, 0},
+#line 3728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2183, 0},
+#line 2964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2184, 0},
+#line 4756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2185, 4},
+#line 5159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2186, 0},
+#line 4898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2187, 0},
+#line 3694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2188, 0},
+#line 5235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2189, 0},
+#line 3908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2190, 0},
+#line 5228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2191, 0},
+#line 3770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2192, 0},
+#line 5003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2193, 4},
+#line 4245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2194, 0},
+#line 4052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2195, 0},
+#line 3860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2196, 0},
+#line 4105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2197, 0},
+#line 2622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2198, 0},
+#line 3769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2199, 0},
+#line 3934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2200, 0},
+#line 3691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2201, 0},
+#line 4884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2202, 0},
+#line 4908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2203, 0},
+#line 3704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2204, 0},
+#line 3532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2205, 0},
+#line 3783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2206, 0},
+#line 3702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2207, 0},
+#line 3700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2208, 0},
+#line 4223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2209, 0},
+#line 4224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2210, 0},
+#line 3861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2211, 0},
+#line 833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2212, 4},
+#line 3533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2213, 0},
+#line 3781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2214, 0},
+#line 774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2215, 1},
+#line 3776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2216, 0},
+#line 3698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2217, 0},
+#line 82 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2218, 0},
+#line 2037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2219, 0},
+#line 3690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2220, 0},
+#line 4782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2221, 4},
+#line 3530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2222, 0},
+#line 797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2223, 4},
+#line 273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2224, 0},
+#line 4040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2225, 0},
+#line 4222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2226, 0},
+#line 3589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2227, 0},
+#line 5436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2228, 0},
+#line 854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2229, 4},
+#line 3993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2230, 0},
+#line 3920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2231, 2},
+#line 3659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2232, 0},
+#line 3689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2233, 0},
+#line 5018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2234, 0},
+#line 1007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2235, 0},
+#line 3881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2236, 0},
+#line 3705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2237, 0},
+#line 3774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2238, 0},
+#line 3595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2239, 0},
+#line 3591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2240, 0},
+#line 1558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2241, 0},
+#line 4085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2242, 0},
+#line 4617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2243, 4},
+#line 255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2244, 0},
+#line 5124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2245, 0},
+#line 3779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2246, 0},
+#line 1064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2247, 4},
+#line 211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2248, 0},
+#line 4114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2249, 0},
+#line 548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2250, 4},
+#line 34 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2251, 0},
+#line 1607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2252, 4},
+#line 3703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2253, 0},
+#line 5265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2254, 0},
+#line 2062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2255, 0},
+#line 3477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2256, 0},
+#line 4225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2257, 0},
+#line 147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2258, 0},
+#line 4205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2259, 0},
+#line 2728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2260, 4},
+#line 5133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2261, 0},
+#line 1886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2262, 0},
+#line 1079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2263, 0},
+#line 1165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2264, 0},
+#line 805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2265, 0},
+#line 5367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2266, 0},
+#line 1844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2267, 4},
+#line 3594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2268, 0},
+#line 1432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2269, 0},
+#line 3808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2270, 0},
+#line 4882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2271, 0},
+#line 3711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2272, 0},
+#line 5138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2273, 0},
+#line 3962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2274, 0},
+#line 3637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2275, 0},
+#line 2693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2276, 0},
+#line 5029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2277, 1},
+#line 3864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2278, 0},
+#line 1155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2279, 0},
+#line 3945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2280, 0},
+#line 5405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2281, 0},
+#line 4989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2282, 0},
+#line 3710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2283, 0},
+#line 4511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2284, 0},
+#line 3601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2285, 0},
+#line 4277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2286, 0},
+#line 3991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2287, 0},
+#line 2040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2288, 0},
+#line 97 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2289, 0},
+#line 3792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2290, 0},
+#line 150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2291, 0},
+#line 3600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2292, 0},
+#line 4551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2293, 0},
+#line 483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2294, 4},
+#line 576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2295, 0},
+#line 1441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2296, 4},
+#line 5207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2297, 0},
+#line 1920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2298, 0},
+#line 28 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2299, 0},
+#line 3667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2300, 0},
+#line 2985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2301, 0},
+#line 41 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2302, 0},
+#line 498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2303, 4},
+#line 706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2304, 0},
+#line 499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2305, 4},
+#line 3753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2306, 0},
+#line 1143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2307, 4},
+#line 3666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2308, 0},
+#line 5237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2309, 0},
+#line 4671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2310, 0},
+#line 2000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2311, 0},
+#line 5631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2312, 0},
+#line 4543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2313, 0},
+#line 5131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2314, 0},
+#line 2652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2315, 0},
+#line 5315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2316, 0},
+#line 116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2317, 0},
+#line 1138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2318, 4},
+#line 110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2319, 0},
+#line 4049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2320, 1},
+#line 2604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2321, 0},
+#line 1983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2322, 0},
+#line 553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2323, 0},
+#line 141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2324, 0},
+#line 3597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2325, 0},
+#line 2986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2326, 0},
+#line 3866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2327, 0},
+#line 4704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2328, 0},
+#line 3754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2329, 0},
+#line 2649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2330, 0},
+#line 2753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2331, 0},
+#line 3404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2332, 0},
+#line 2020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2333, 0},
+#line 2534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2334, 0},
+#line 1914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2335, 0},
+#line 493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2336, 4},
+#line 4877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2337, 0},
+#line 5033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2338, 0},
+#line 2712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2339, 0},
+#line 5039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2340, 0},
+#line 4747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2341, 0},
+#line 6103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2342, 0},
+#line 4667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2343, 0},
+#line 3042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2344, 0},
+#line 2079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2345, 0},
+#line 815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2346, 0},
+#line 3355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2347, 0},
+#line 2626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2348, 0},
+#line 3686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2349, 0},
+#line 3444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2350, 0},
+#line 2009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2351, 0},
+#line 143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2352, 0},
+#line 486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2353, 4},
+#line 3789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2354, 0},
+#line 4708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2355, 4},
+#line 1683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2356, 1},
+#line 2837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2357, 0},
+#line 2674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2358, 0},
+#line 3790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2359, 0},
+#line 3926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2360, 0},
+#line 5560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2361, 0},
+#line 2001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2362, 0},
+#line 4840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2363, 0},
+#line 4640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2364, 0},
+#line 135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2365, 0},
+#line 3891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2366, 0},
+#line 1631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2367, 0},
+#line 4045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2368, 0},
+#line 2124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2369, 4},
+#line 2676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2370, 0},
+#line 1055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2371, 0},
+#line 3788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2372, 0},
+#line 3229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2373, 0},
+#line 2535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2374, 0},
+#line 1494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2375, 4},
+#line 1497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2376, 4},
+#line 1479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2377, 4},
+#line 1508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2378, 4},
+#line 1486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2379, 4},
+#line 4723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2380, 0},
+#line 1839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2381, 0},
+#line 4935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2382, 0},
+#line 497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2383, 4},
+#line 3946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2384, 0},
+#line 3818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2385, 0},
+#line 3651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2386, 0},
+#line 1910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2387, 0},
+#line 1473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2388, 4},
+#line 3676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2389, 0},
+#line 3152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2390, 0},
+#line 488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2391, 4},
+#line 4287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2392, 0},
+#line 3043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2393, 0},
+#line 2939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2394, 0},
+#line 1477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2395, 4},
+#line 3626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2396, 0},
+#line 1485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2397, 4},
+#line 5230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2398, 0},
+#line 4841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2399, 0},
+#line 2942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2400, 0},
+#line 2928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2401, 0},
+#line 1507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2402, 4},
+#line 471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2403, 0},
+#line 1472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2404, 4},
+#line 3778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2405, 0},
+#line 1069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2406, 0},
+#line 3695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2407, 0},
+#line 778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2408, 1},
+#line 4885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2409, 0},
+#line 4286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2410, 0},
+#line 5267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2411, 0},
+#line 5639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2412, 0},
+#line 757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2413, 0},
+#line 1471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2414, 4},
+#line 3791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2415, 0},
+#line 1516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2416, 4},
+#line 3773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2417, 0},
+#line 3084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2418, 0},
+#line 2065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2419, 0},
+#line 2790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2420, 4},
+#line 2550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2421, 0},
+#line 119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2422, 0},
+#line 3405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2423, 0},
+#line 1515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2424, 4},
+#line 1514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2425, 4},
+#line 2133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2426, 0},
+#line 6094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2427, 0},
+#line 5357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2428, 0},
+#line 495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2429, 4},
+#line 4622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2430, 0},
+#line 5287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2431, 0},
+#line 614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2432, 0},
+#line 2951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2433, 0},
+#line 2608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2434, 0},
+#line 4826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2435, 0},
+#line 5069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2436, 0},
+#line 1504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2437, 4},
+#line 1503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2438, 4},
+#line 3057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2439, 0},
+#line 1500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2440, 4},
+#line 3856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2441, 0},
+#line 494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2442, 4},
+#line 4724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2443, 0},
+#line 2073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2444, 0},
+#line 1490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2445, 4},
+#line 1555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2446, 0},
+#line 1483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2447, 4},
+#line 2005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2448, 0},
+#line 1481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2449, 4},
+#line 1502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2450, 4},
+#line 1445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2451, 4},
+#line 3068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2452, 0},
+#line 4032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2453, 0},
+#line 4632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2454, 0},
+#line 269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2455, 0},
+#line 496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2456, 4},
+#line 2018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2457, 0},
+#line 2014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2458, 0},
+#line 1436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2459, 0},
+#line 2531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2460, 0},
+#line 749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2461, 0},
+#line 4894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2462, 0},
+#line 1475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2463, 4},
+#line 541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2464, 0},
+#line 4670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2465, 0},
+#line 1420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2466, 0},
+#line 1491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2467, 4},
+#line 1484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2468, 4},
+#line 3677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2469, 0},
+#line 3804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2470, 0},
+#line 1299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2471, 0},
+#line 4742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2472, 0},
+#line 3596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2473, 0},
+#line 3039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2474, 0},
+#line 3598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2475, 0},
+#line 1496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2476, 4},
+#line 2944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2477, 0},
+#line 1487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2478, 4},
+#line 258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2479, 0},
+#line 4068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2480, 0},
+#line 1513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2481, 4},
+#line 4745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2482, 0},
+#line 5439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2483, 0},
+#line 4648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2484, 0},
+#line 4815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2485, 0},
+#line 3901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2486, 0},
+#line 1158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2487, 0},
+#line 5113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2488, 0},
+#line 74 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2489, 0},
+#line 1134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2490, 4},
+#line 4244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2491, 0},
+#line 321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2492, 0},
+#line 4272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2493, 0},
+#line 665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2494, 0},
+#line 1446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2495, 4},
+#line 648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2496, 0},
+#line 658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2497, 0},
+#line 2781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2498, 0},
+#line 3592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2499, 0},
+#line 1170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2500, 0},
+#line 651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2501, 0},
+#line 3168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2502, 0},
+#line 644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2503, 0},
+#line 3752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2504, 0},
+#line 1506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2505, 4},
+#line 642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2506, 0},
+#line 664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2507, 0},
+#line 685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2508, 0},
+#line 5076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2509, 0},
+#line 655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2510, 0},
+#line 4010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2511, 0},
+#line 2448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2512, 0},
+#line 2668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2513, 0},
+#line 1567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2514, 0},
+#line 624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2515, 0},
+#line 1526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2516, 0},
+#line 4469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2517, 0},
+#line 1478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2518, 4},
+#line 4012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2519, 0},
+#line 1499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2520, 4},
+#line 2071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2521, 0},
+#line 3593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2522, 0},
+#line 4909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2523, 0},
+#line 5232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2524, 0},
+#line 5119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2525, 0},
+#line 4831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2526, 0},
+#line 1512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2527, 4},
+#line 4084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2528, 0},
+#line 689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2529, 0},
+#line 1900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2530, 0},
+#line 2833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2531, 0},
+#line 4645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2532, 0},
+#line 688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2533, 0},
+#line 4232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2534, 0},
+#line 779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2535, 1},
+#line 2828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2536, 0},
+#line 686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2537, 0},
+#line 4075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2538, 0},
+#line 4036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2539, 0},
+#line 684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2540, 0},
+#line 2809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2541, 0},
+#line 678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2542, 0},
+#line 1011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2543, 0},
+#line 2915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2544, 0},
+#line 1966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2545, 0},
+#line 3376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2546, 0},
+#line 1684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2547, 0},
+#line 1501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2548, 4},
+#line 645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2549, 0},
+#line 4930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2550, 0},
+#line 1951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2551, 0},
+#line 339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2552, 0},
+#line 1933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2553, 0},
+#line 3624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2554, 0},
+#line 2562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2555, 0},
+#line 1498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2556, 4},
+#line 5123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2557, 0},
+#line 5353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2558, 0},
+#line 4741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2559, 0},
+#line 2682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2560, 0},
+#line 671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2561, 0},
+#line 657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2562, 0},
+#line 2061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2563, 0},
+#line 377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2564, 0},
+#line 2851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2565, 0},
+#line 3403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2566, 0},
+#line 4147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2567, 0},
+#line 2651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2568, 0},
+#line 3169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2569, 0},
+#line 660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2570, 0},
+#line 229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2571, 0},
+#line 667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2572, 0},
+#line 4151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2573, 0},
+#line 3876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2574, 0},
+#line 3914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2575, 0},
+#line 5358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2576, 0},
+#line 4278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2577, 0},
+#line 2008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2578, 0},
+#line 1495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2579, 4},
+#line 3388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2580, 0},
+#line 1087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2581, 0},
+#line 640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2582, 0},
+#line 2533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2583, 0},
+#line 646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2584, 0},
+#line 3261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2585, 0},
+#line 2012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2586, 0},
+#line 414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2587, 0},
+#line 5056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2588, 0},
+#line 503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2589, 4},
+#line 358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2590, 0},
+#line 353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2591, 0},
+#line 2059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2592, 0},
+#line 5615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2593, 0},
+#line 5324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2594, 0},
+#line 1480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2595, 4},
+#line 2961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2596, 0},
+#line 2732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2597, 0},
+#line 1129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2598, 4},
+#line 428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2599, 4},
+#line 3041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2600, 0},
+#line 319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2601, 4},
+#line 188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2602, 0},
+#line 662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2603, 0},
+#line 441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2604, 0},
+#line 4150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2605, 0},
+#line 4148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2606, 0},
+#line 654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2607, 0},
+#line 4639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2608, 0},
+#line 5130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2609, 0},
+#line 4080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2610, 0},
+#line 4780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2611, 4},
+#line 424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2612, 0},
+#line 2653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2613, 0},
+#line 1488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2614, 4},
+#line 1510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2615, 4},
+#line 1144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2616, 4},
+#line 2466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2617, 0},
+#line 2916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2618, 0},
+#line 5617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2619, 0},
+#line 669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2620, 0},
+#line 3671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2621, 0},
+#line 3873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2622, 0},
+#line 3253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2623, 0},
+#line 4038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2624, 0},
+#line 502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2625, 4},
+#line 3896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2626, 0},
+#line 3219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2627, 0},
+#line 3939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2628, 0},
+#line 171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2629, 0},
+#line 4034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2630, 0},
+#line 2914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2631, 0},
+#line 579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2632, 0},
+#line 2011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2633, 0},
+#line 683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2634, 0},
+#line 3208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2635, 1},
+#line 4591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2636, 0},
+#line 5275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2637, 0},
+#line 1345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2638, 0},
+#line 3251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2639, 0},
+#line 1426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2640, 0},
+#line 2019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2641, 0},
+#line 5172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2642, 0},
+#line 3618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2643, 0},
+#line 4293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2644, 0},
+#line 1509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2645, 4},
+#line 1753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2646, 0},
+#line 3226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2647, 0},
+#line 1207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2648, 0},
+#line 905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2649, 0},
+#line 1932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2650, 0},
+#line 2091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2651, 0},
+#line 4772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2652, 4},
+#line 1493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2653, 4},
+#line 641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2654, 0},
+#line 6072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2655, 0},
+#line 2624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2656, 0},
+#line 3228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2657, 0},
+#line 6037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2658, 2},
+#line 4564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2659, 0},
+#line 4733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2660, 0},
+#line 2443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2661, 0},
+#line 3140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2662, 0},
+#line 5231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2663, 0},
+#line 2286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2664, 0},
+#line 2155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2665, 0},
+#line 2193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2666, 0},
+#line 3160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2667, 0},
+#line 725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2668, 0},
+#line 1999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2669, 0},
+#line 5187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2670, 0},
+#line 839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2671, 0},
+#line 2289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2672, 0},
+#line 5975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2673, 0},
+#line 1209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2674, 0},
+#line 2194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2675, 0},
+#line 1071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2676, 0},
+#line 908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2677, 0},
+#line 2614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2678, 0},
+#line 3629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2679, 0},
+#line 2398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2680, 0},
+#line 232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2681, 0},
+#line 1701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2682, 0},
+#line 1124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2683, 4},
+#line 1208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2684, 0},
+#line 335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2685, 0},
+#line 1668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2686, 0},
+#line 2296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2687, 0},
+#line 5300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2688, 0},
+#line 907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2689, 0},
+#line 906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2690, 0},
+#line 841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2691, 0},
+#line 2165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2692, 0},
+#line 775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2693, 1},
+#line 1511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2694, 4},
+#line 4046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2695, 0},
+#line 2160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2696, 0},
+#line 3134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2697, 0},
+#line 691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2698, 0},
+#line 129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2699, 0},
+#line 2372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2700, 0},
+#line 421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2701, 0},
+#line 1467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2702, 0},
+#line 4804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2703, 0},
+#line 134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2704, 0},
+#line 789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2705, 0},
+#line 271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2706, 0},
+#line 250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2707, 0},
+#line 3892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2708, 0},
+#line 5412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2709, 0},
+#line 2136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2710, 0},
+#line 4236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2711, 0},
+#line 3103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2712, 0},
+#line 5613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2713, 0},
+#line 3723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2714, 0},
+#line 3722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2715, 0},
+#line 3911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2716, 0},
+#line 2822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2717, 0},
+#line 544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2718, 0},
+#line 3632 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2719, 0},
+#line 3982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2720, 0},
+#line 3905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2721, 0},
+#line 2202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2722, 0},
+#line 3727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2723, 0},
+#line 673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2724, 0},
+#line 228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2725, 0},
+#line 856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2726, 0},
+#line 453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2727, 0},
+#line 535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2728, 0},
+#line 5974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2729, 0},
+#line 3950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2730, 0},
+#line 1073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2731, 0},
+#line 304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2732, 0},
+#line 4545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2733, 0},
+#line 3212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2734, 0},
+#line 438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2735, 0},
+#line 3656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2736, 1},
+#line 5429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2737, 0},
+#line 2189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2738, 2},
+#line 1400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2739, 0},
+#line 2265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2740, 0},
+#line 4610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2741, 4},
+#line 2208 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2742, 0},
+#line 1913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2743, 0},
+#line 2267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2744, 0},
+#line 1601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2745, 0},
+#line 3872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2746, 0},
+#line 2266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2747, 0},
+#line 3895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2748, 0},
+#line 5048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2749, 0},
+#line 2209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2750, 0},
+#line 4006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2751, 0},
+#line 3130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2752, 0},
+#line 5642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2753, 0},
+#line 2162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2754, 0},
+#line 5612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2755, 0},
+#line 1525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2756, 0},
+#line 4210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2757, 0},
+#line 996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2758, 0},
+#line 1930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2759, 0},
+#line 4079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2760, 0},
+#line 3726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2761, 0},
+#line 3724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2762, 0},
+#line 2134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2763, 0},
+#line 3497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2764, 0},
+#line 4270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2765, 0},
+#line 2190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2766, 0},
+#line 1130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2767, 4},
+#line 6039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2768, 0},
+#line 254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2769, 0},
+#line 2776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2770, 0},
+#line 6059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2771, 0},
+#line 4250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2772, 0},
+#line 3246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2773, 0},
+#line 840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2774, 0},
+#line 2137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2775, 0},
+#line 2226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2776, 0},
+#line 4517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2777, 0},
+#line 2223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2778, 0},
+#line 1678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2779, 0},
+#line 3940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2780, 0},
+#line 2269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2781, 0},
+#line 2814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2782, 0},
+#line 2279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2783, 0},
+#line 2268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2784, 0},
+#line 3227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2785, 0},
+#line 3642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2786, 0},
+#line 2007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2787, 0},
+#line 2850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2788, 0},
+#line 6090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2789, 4},
+#line 4065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2790, 0},
+#line 2205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2791, 0},
+#line 1557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2792, 0},
+#line 3221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2793, 0},
+#line 5246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2794, 0},
+#line 3081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2795, 0},
+#line 1956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2796, 0},
+#line 72 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2797, 0},
+#line 2467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2798, 0},
+#line 5720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2799, 0},
+#line 1068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2800, 0},
+#line 5268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2801, 0},
+#line 4673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2802, 0},
+#line 2199 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2803, 0},
+#line 4690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2804, 0},
+#line 5722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2805, 0},
+#line 261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2806, 0},
+#line 4609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2807, 4},
+#line 1024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2808, 0},
+#line 2981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2809, 4},
+#line 3627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2810, 0},
+#line 260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2811, 0},
+#line 1133 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2812, 4},
+#line 2033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2813, 0},
+#line 4061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2814, 0},
+#line 3634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2815, 0},
+#line 2278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2816, 0},
+#line 108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2817, 0},
+#line 5622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2818, 0},
+#line 3548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2819, 0},
+#line 2982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2820, 0},
+#line 2119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2821, 0},
+#line 5655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2822, 0},
+#line 4912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2823, 0},
+#line 2010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2824, 0},
+#line 2060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2825, 0},
+#line 6040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2826, 0},
+#line 896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2827, 0},
+#line 3292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2828, 0},
+#line 4991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2829, 0},
+#line 1021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2830, 0},
+#line 4627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2831, 0},
+#line 4812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2832, 0},
+#line 1550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2833, 0},
+#line 351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2834, 0},
+#line 405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2835, 0},
+#line 270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2836, 0},
+#line 3430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2837, 0},
+#line 5236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2838, 0},
+#line 491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2839, 4},
+#line 146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2840, 0},
+#line 3917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2841, 0},
+#line 568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2842, 0},
+#line 2729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2843, 0},
+#line 3941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2844, 0},
+#line 5068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2845, 0},
+#line 5040 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2846, 0},
+#line 2730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2847, 0},
+#line 3017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2848, 0},
+#line 2294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2849, 0},
+#line 1662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2850, 4},
+#line 2468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2851, 0},
+#line 5703 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2852, 0},
+#line 5349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2853, 0},
+#line 4612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2854, 4},
+#line 1846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2855, 0},
+#line 4846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2856, 0},
+#line 1142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2857, 4},
+#line 2045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2858, 0},
+#line 1402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2859, 0},
+#line 2283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2860, 0},
+#line 4893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2861, 0},
+#line 5721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2862, 0},
+#line 4869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2863, 0},
+#line 612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2864, 0},
+#line 1371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2865, 0},
+#line 2578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2866, 0},
+#line 2290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2867, 0},
+#line 5654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2868, 0},
+#line 256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2869, 0},
+#line 1113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2870, 0},
+#line 5222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2871, 0},
+#line 489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2872, 4},
+#line 132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2873, 0},
+#line 3473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2874, 0},
+#line 777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2875, 1},
+#line 5659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2876, 0},
+#line 5210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2877, 0},
+#line 4007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2878, 0},
+#line 3562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2879, 0},
+#line 4237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2880, 0},
+#line 5278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2881, 0},
+#line 3472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2882, 0},
+#line 1288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2883, 0},
+#line 5625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2884, 0},
+#line 2228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2885, 0},
+#line 721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2886, 0},
+#line 1482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2887, 4},
+#line 4056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2888, 0},
+#line 492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2889, 4},
+#line 4688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2890, 0},
+#line 3461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2891, 0},
+#line 490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2892, 4},
+#line 2583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2893, 0},
+#line 2920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2894, 0},
+#line 3620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2895, 0},
+#line 4642 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2896, 0},
+#line 2102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2897, 0},
+#line 4263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2898, 0},
+#line 1996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2899, 0},
+#line 1939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2900, 0},
+#line 3938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2901, 0},
+#line 1385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2902, 0},
+#line 259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2903, 0},
+#line 4983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2904, 1},
+#line 323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2905, 0},
+#line 3777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2906, 0},
+#line 2287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2907, 0},
+#line 5686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2908, 0},
+#line 2003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2909, 0},
+#line 4761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2910, 0},
+#line 1650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2911, 0},
+#line 2112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2912, 0},
+#line 5325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2913, 0},
+#line 4033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2914, 0},
+#line 1834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2915, 0},
+#line 2017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2916, 0},
+#line 3820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2917, 0},
+#line 437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2918, 0},
+#line 290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2919, 0},
+#line 3793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2920, 0},
+#line 1982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2921, 4},
+#line 5097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2922, 0},
+#line 2864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2923, 0},
+#line 1006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2924, 0},
+#line 2566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2925, 0},
+#line 2288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2926, 0},
+#line 1141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2927, 4},
+#line 5175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2928, 0},
+#line 4481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2929, 0},
+#line 262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2930, 0},
+#line 2962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2931, 0},
+#line 2196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2932, 0},
+#line 1532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2933, 0},
+#line 4811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2934, 0},
+#line 4495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2935, 0},
+#line 2158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2936, 0},
+#line 5377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2937, 0},
+#line 3633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2938, 0},
+#line 5643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2939, 0},
+#line 2206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2940, 0},
+#line 378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2941, 0},
+#line 5596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2942, 0},
+#line 1997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2943, 0},
+#line 5692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2944, 0},
+#line 2135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2945, 0},
+#line 1604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2946, 0},
+#line 3910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2947, 0},
+#line 4774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2948, 4},
+#line 1347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2949, 0},
+#line 4073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2950, 0},
+#line 3541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2951, 0},
+#line 2600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2952, 0},
+#line 1423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2953, 0},
+#line 3459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2954, 0},
+#line 4259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2955, 0},
+#line 1422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2956, 0},
+#line 3467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2957, 0},
+#line 5031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2958, 0},
+#line 5153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2959, 0},
+#line 3457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2960, 0},
+#line 4624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2961, 0},
+#line 5705 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2962, 0},
+#line 4018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2963, 0},
+#line 1135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2964, 4},
+#line 316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2965, 0},
+#line 1560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2966, 0},
+#line 5408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2967, 0},
+#line 1636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2968, 0},
+#line 2280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2969, 0},
+#line 3823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2970, 0},
+#line 4886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2971, 0},
+#line 3464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2972, 0},
+#line 3678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2973, 0},
+#line 379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2974, 0},
+#line 3558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2975, 0},
+#line 2631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2976, 0},
+#line 4613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2977, 4},
+#line 125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2978, 0},
+#line 1691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2979, 0},
+#line 1290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2980, 0},
+#line 152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2981, 0},
+#line 2903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2982, 0},
+#line 4879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2983, 0},
+#line 4878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2984, 0},
+#line 2639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2985, 0},
+#line 2710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2986, 0},
+#line 4053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2987, 0},
+#line 2053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2988, 0},
+#line 3398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2989, 0},
+#line 4575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2990, 0},
+#line 5554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2991, 0},
+#line 2863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2992, 0},
+#line 3145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2993, 0},
+#line 1742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2994, 0},
+#line 1741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2995, 0},
+#line 4999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2996, 0},
+#line 1194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2997, 0},
+#line 1193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2998, 0},
+#line 890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str2999, 0},
+#line 889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3000, 0},
+#line 888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3001, 4},
+#line 1743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3002, 0},
+#line 1195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3003, 0},
+#line 3550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3004, 0},
+#line 661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3005, 0},
+#line 891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3006, 0},
+#line 1663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3007, 0},
+#line 817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3008, 0},
+#line 5467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3009, 0},
+#line 1128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3010, 4},
+#line 5129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3011, 0},
+#line 2507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3012, 0},
+#line 1922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3013, 0},
+#line 5644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3014, 0},
+#line 2835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3015, 0},
+#line 3894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3016, 0},
+#line 5555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3017, 0},
+#line 5521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3018, 0},
+#line 3959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3019, 0},
+#line 2750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3020, 0},
+#line 2051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3021, 0},
+#line 248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3022, 0},
+#line 4606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3023, 4},
+#line 3014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3024, 0},
+#line 249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3025, 0},
+#line 5725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3026, 0},
+#line 2645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3027, 0},
+#line 4854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3028, 0},
+#line 5718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3029, 0},
+#line 690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3030, 0},
+#line 2888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3031, 0},
+#line 5459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3032, 0},
+#line 4512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3033, 0},
+#line 5541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3034, 0},
+#line 2231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3035, 0},
+#line 687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3036, 0},
+#line 1447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3037, 0},
+#line 4488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3038, 0},
+#line 3927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3039, 0},
+#line 5186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3040, 0},
+#line 1974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3041, 0},
+#line 2655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3042, 0},
+#line 4678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3043, 0},
+#line 539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3044, 0},
+#line 1553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3045, 0},
+#line 2192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3046, 0},
+#line 267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3047, 0},
+#line 5618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3048, 0},
+#line 243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3049, 0},
+#line 2006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3050, 0},
+#line 581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3051, 0},
+#line 5026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3052, 0},
+#line 530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3053, 0},
+#line 2331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3054, 4},
+#line 4605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3055, 4},
+#line 4946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3056, 0},
+#line 272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3057, 0},
+#line 4988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3058, 0},
+#line 5379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3059, 0},
+#line 5460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3060, 0},
+#line 2076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3061, 4},
+#line 4217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3062, 0},
+#line 5693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3063, 0},
+#line 4121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3064, 0},
+#line 2391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3065, 0},
+#line 1945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3066, 0},
+#line 5523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3067, 0},
+#line 1318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3068, 0},
+#line 200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3069, 0},
+#line 1674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3070, 0},
+#line 5458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3071, 0},
+#line 1592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3072, 0},
+#line 6057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3073, 0},
+#line 5575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3074, 0},
+#line 4848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3075, 0},
+#line 5200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3076, 0},
+#line 5316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3077, 0},
+#line 3635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3078, 0},
+#line 6022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3079, 0},
+#line 1403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3080, 0},
+#line 1980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3081, 0},
+#line 4563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3082, 0},
+#line 1132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3083, 4},
+#line 3556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3084, 0},
+#line 5482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3085, 2},
+#line 3576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3086, 0},
+#line 3173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3087, 0},
+#line 4127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3088, 0},
+#line 4766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3089, 0},
+#line 1909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3090, 0},
+#line 2063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3091, 0},
+#line 951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3092, 0},
+#line 950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3093, 0},
+#line 1666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3094, 0},
+#line 824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3095, 0},
+#line 4778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3096, 0},
+#line 820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3097, 0},
+#line 5452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3098, 0},
+#line 1661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3099, 0},
+#line 1167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3100, 0},
+#line 1802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3101, 0},
+#line 813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3102, 0},
+#line 1253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3103, 0},
+#line 4856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3104, 0},
+#line 1097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3105, 4},
+#line 2328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3106, 4},
+#line 3552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3107, 0},
+#line 6046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3108, 0},
+#line 3575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3109, 0},
+#line 3560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3110, 0},
+#line 203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3111, 0},
+#line 1032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3112, 0},
+#line 6036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3113, 0},
+#line 1333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3114, 0},
+#line 609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3115, 0},
+#line 344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3116, 0},
+#line 3109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3117, 0},
+#line 1566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3118, 0},
+#line 3286 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3119, 0},
+#line 4644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3120, 0},
+#line 4503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3121, 0},
+#line 3555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3122, 0},
+#line 1803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3123, 0},
+#line 84 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3124, 4},
+#line 1380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3125, 0},
+#line 1254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3126, 0},
+#line 952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3127, 0},
+#line 1370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3128, 0},
+#line 196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3129, 0},
+#line 4852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3130, 0},
+#line 3458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3131, 0},
+#line 5260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3132, 0},
+#line 3577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3133, 0},
+#line 1960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3134, 0},
+#line 5500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3135, 0},
+#line 3579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3136, 0},
+#line 3554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3137, 0},
+#line 3827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3138, 0},
+#line 6012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3139, 0},
+#line 4546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3140, 0},
+#line 2681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3141, 0},
+#line 5061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3142, 0},
+#line 5549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3143, 0},
+#line 780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3144, 1},
+#line 4607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3145, 4},
+#line 5337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3146, 0},
+#line 1784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3147, 0},
+#line 1779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3148, 0},
+#line 3481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3149, 0},
+#line 1234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3150, 0},
+#line 1229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3151, 0},
+#line 1781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3152, 0},
+#line 1231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3153, 0},
+#line 929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3154, 0},
+#line 3249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3155, 0},
+#line 927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3156, 0},
+#line 1780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3157, 0},
+#line 1230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3158, 0},
+#line 4483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3159, 0},
+#line 926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3160, 0},
+#line 818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3161, 0},
+#line 1778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3162, 0},
+#line 2788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3163, 0},
+#line 5307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3164, 0},
+#line 5723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3165, 1},
+#line 3672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3166, 0},
+#line 5690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3167, 0},
+#line 4611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3168, 4},
+#line 4292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3169, 0},
+#line 3456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3170, 0},
+#line 5517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3171, 0},
+#line 2505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3172, 0},
+#line 3385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3173, 0},
+#line 4513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3174, 0},
+#line 2847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3175, 0},
+#line 3443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3176, 0},
+#line 4764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3177, 0},
+#line 5518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3178, 0},
+#line 4239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3179, 0},
+#line 650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3180, 0},
+#line 2689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3181, 0},
+#line 2848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3182, 0},
+#line 2104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3183, 0},
+#line 1786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3184, 0},
+#line 4554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3185, 0},
+#line 4960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3186, 0},
+#line 3012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3187, 0},
+#line 930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3188, 0},
+#line 4618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3189, 0},
+#line 3832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3190, 0},
+#line 2504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3191, 0},
+#line 5461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3192, 1},
+#line 4273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3193, 0},
+#line 5032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3194, 0},
+#line 2602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3195, 0},
+#line 4691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3196, 2},
+#line 5262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3197, 0},
+#line 3374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3198, 4},
+#line 1757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3199, 0},
+#line 1211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3200, 0},
+#line 1919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3201, 0},
+#line 1785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3202, 0},
+#line 1783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3203, 0},
+#line 910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3204, 0},
+#line 1233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3205, 0},
+#line 3237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3206, 0},
+#line 3011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3207, 0},
+#line 843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3208, 0},
+#line 1390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3209, 0},
+#line 1639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3210, 0},
+#line 5497 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3211, 0},
+#line 233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3212, 0},
+#line 2874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3213, 0},
+#line 6077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3214, 0},
+#line 368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3215, 0},
+#line 6050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3216, 0},
+#line 3631 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3217, 0},
+#line 3171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3218, 0},
+#line 4619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3219, 0},
+#line 680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3220, 0},
+#line 5281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3221, 0},
+#line 1782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3222, 0},
+#line 1232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3223, 0},
+#line 289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3224, 0},
+#line 1428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3225, 0},
+#line 928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3226, 0},
+#line 5167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3227, 0},
+#line 3138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3228, 0},
+#line 5241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3229, 0},
+#line 5552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3230, 0},
+#line 5224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3231, 1},
+#line 1906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3232, 0},
+#line 3784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3233, 0},
+#line 2413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3234, 0},
+#line 722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3235, 0},
+#line 2004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3236, 0},
+#line 5504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3237, 0},
+#line 3172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3238, 0},
+#line 6106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3239, 0},
+#line 5122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3240, 0},
+#line 6060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3241, 0},
+#line 5360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3242, 0},
+#line 5338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3243, 0},
+#line 3660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3244, 0},
+#line 2211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3245, 0},
+#line 2584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3246, 0},
+#line 2724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3247, 0},
+#line 2230 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3248, 0},
+#line 231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3249, 0},
+#line 4850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3250, 0},
+#line 5041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3251, 0},
+#line 3238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3252, 0},
+#line 5578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3253, 0},
+#line 1150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3254, 0},
+#line 2445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3255, 0},
+#line 2486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3256, 0},
+#line 451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3257, 0},
+#line 3706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3258, 0},
+#line 5166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3259, 0},
+#line 3523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3260, 0},
+#line 4231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3261, 0},
+#line 2709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3262, 0},
+#line 2898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3263, 0},
+#line 6111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3264, 0},
+#line 2897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3265, 0},
+#line 5121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3266, 0},
+#line 4832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3267, 0},
+#line 3661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3268, 0},
+#line 2382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3269, 0},
+#line 2270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3270, 0},
+#line 2839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3271, 0},
+#line 6051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3272, 0},
+#line 712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3273, 0},
+#line 3311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3274, 0},
+#line 5484 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3275, 4},
+#line 4998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3276, 0},
+#line 1455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3277, 0},
+#line 1563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3278, 0},
+#line 5647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3279, 0},
+#line 4822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3280, 0},
+#line 3561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3281, 0},
+#line 3989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3282, 0},
+#line 2764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3283, 0},
+#line 3139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3284, 0},
+#line 3089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3285, 0},
+#line 4680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3286, 0},
+#line 3483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3287, 0},
+#line 89 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3288, 0},
+#line 1419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3289, 0},
+#line 2225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3290, 0},
+#line 2538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3291, 0},
+#line 5402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3292, 0},
+#line 78 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3293, 0},
+#line 2396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3294, 4},
+#line 3407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3295, 0},
+#line 3957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3296, 0},
+#line 4888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3297, 0},
+#line 3961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3298, 0},
+#line 2662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3299, 0},
+#line 3302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3300, 0},
+#line 2680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3301, 0},
+#line 3176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3302, 0},
+#line 3875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3303, 0},
+#line 735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3304, 0},
+#line 3437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3305, 0},
+#line 3046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3306, 0},
+#line 752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3307, 0},
+#line 560 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3308, 0},
+#line 5476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3309, 0},
+#line 3822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3310, 0},
+#line 2108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3311, 0},
+#line 2293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3312, 0},
+#line 2472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3313, 0},
+#line 580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3314, 0},
+#line 284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3315, 0},
+#line 3113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3316, 0},
+#line 5005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3317, 0},
+#line 283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3318, 0},
+#line 2625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3319, 0},
+#line 5177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3320, 0},
+#line 2041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3321, 0},
+#line 349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3322, 0},
+#line 3553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3323, 0},
+#line 4665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3324, 0},
+#line 3821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3325, 0},
+#line 4268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3326, 0},
+#line 3446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3327, 0},
+#line 795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3328, 0},
+#line 341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3329, 0},
+#line 214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3330, 0},
+#line 736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3331, 0},
+#line 616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3332, 0},
+#line 317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3333, 0},
+#line 2262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3334, 0},
+#line 578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3335, 0},
+#line 4035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3336, 0},
+#line 558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3337, 0},
+#line 3958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3338, 0},
+#line 2648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3339, 0},
+#line 3295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3340, 0},
+#line 555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3341, 0},
+#line 3542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3342, 0},
+#line 3814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3343, 1},
+#line 4584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3344, 0},
+#line 3578 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3345, 0},
+#line 5314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3346, 0},
+#line 3810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3347, 1},
+#line 2522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3348, 0},
+#line 2519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3349, 0},
+#line 2518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3350, 0},
+#line 1043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3351, 0},
+#line 3125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3352, 0},
+#line 2520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3353, 0},
+#line 2121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3354, 0},
+#line 337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3355, 0},
+#line 3543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3356, 0},
+#line 4055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3357, 0},
+#line 122 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3358, 0},
+#line 2824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3359, 0},
+#line 3549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3360, 0},
+#line 2517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3361, 0},
+#line 2873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3362, 0},
+#line 2523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3363, 0},
+#line 2002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3364, 0},
+#line 6017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3365, 0},
+#line 3030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3366, 0},
+#line 3027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3367, 0},
+#line 3026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3368, 0},
+#line 2800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3369, 0},
+#line 3028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3370, 0},
+#line 2941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3371, 0},
+#line 3431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3372, 0},
+#line 5195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3373, 0},
+#line 745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3374, 0},
+#line 257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3375, 0},
+#line 623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3376, 0},
+#line 1098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3377, 4},
+#line 1449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3378, 0},
+#line 5334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3379, 0},
+#line 3025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3380, 0},
+#line 5255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3381, 0},
+#line 1080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3382, 0},
+#line 3031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3383, 0},
+#line 5989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3384, 0},
+#line 3357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3385, 0},
+#line 6038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3386, 0},
+#line 3813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3387, 0},
+#line 861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3388, 0},
+#line 2727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3389, 4},
+#line 1628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3390, 4},
+#line 5279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3391, 0},
+#line 5542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3392, 0},
+#line 5234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3393, 0},
+#line 2186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3394, 0},
+#line 6107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3395, 0},
+#line 365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3396, 0},
+#line 1546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3397, 0},
+#line 1297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3398, 0},
+#line 2524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3399, 0},
+#line 166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3400, 0},
+#line 3402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3401, 0},
+#line 5664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3402, 0},
+#line 4842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3403, 0},
+#line 3462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3404, 0},
+#line 776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3405, 1},
+#line 5600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3406, 0},
+#line 1962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3407, 0},
+#line 1148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3408, 0},
+#line 2521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3409, 0},
+#line 3968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3410, 0},
+#line 3874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3411, 0},
+#line 771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3412, 0},
+#line 3032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3413, 0},
+#line 1378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3414, 0},
+#line 6005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3415, 0},
+#line 1474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3416, 4},
+#line 2794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3417, 0},
+#line 554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3418, 0},
+#line 5675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3419, 0},
+#line 2791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3420, 0},
+#line 4099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3421, 0},
+#line 1905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3422, 0},
+#line 4060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3423, 0},
+#line 3029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3424, 0},
+#line 4535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3425, 0},
+#line 412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3426, 0},
+#line 4683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3427, 0},
+#line 2412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3428, 0},
+#line 2016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3429, 0},
+#line 4849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3430, 0},
+#line 5468 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3431, 0},
+#line 213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3432, 0},
+#line 4783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3433, 0},
+#line 5035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3434, 0},
+#line 1042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3435, 0},
+#line 5688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3436, 0},
+#line 5440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3437, 0},
+#line 1100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3438, 4},
+#line 5488 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3439, 0},
+#line 3150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3440, 0},
+#line 3606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3441, 0},
+#line 4487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3442, 0},
+#line 4569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3443, 0},
+#line 743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3444, 0},
+#line 5474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3445, 0},
+#line 408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3446, 0},
+#line 1430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3447, 0},
+#line 401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3448, 0},
+#line 5528 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3449, 0},
+#line 5339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3450, 0},
+#line 5052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3451, 0},
+#line 3585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3452, 0},
+#line 5144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3453, 0},
+#line 4652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3454, 0},
+#line 113 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3455, 0},
+#line 1427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3456, 0},
+#line 1869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3457, 0},
+#line 5146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3458, 0},
+#line 4523 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3459, 0},
+#line 829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3460, 0},
+#line 633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3461, 0},
+#line 1050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3462, 0},
+#line 974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3463, 0},
+#line 972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3464, 0},
+#line 6053 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3465, 0},
+#line 1822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3466, 0},
+#line 1270 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3467, 0},
+#line 2714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3468, 0},
+#line 971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3469, 0},
+#line 1572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3470, 0},
+#line 1521 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3471, 4},
+#line 3969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3472, 0},
+#line 2620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3473, 0},
+#line 2302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3474, 4},
+#line 411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3475, 0},
+#line 5397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3476, 0},
+#line 2176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3477, 0},
+#line 791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3478, 4},
+#line 2640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3479, 0},
+#line 3966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3480, 0},
+#line 1645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3481, 0},
+#line 3433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3482, 1},
+#line 2427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3483, 0},
+#line 2741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3484, 0},
+#line 156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3485, 0},
+#line 2015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3486, 0},
+#line 4593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3487, 0},
+#line 2896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3488, 0},
+#line 205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3489, 0},
+#line 6026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3490, 0},
+#line 5206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3491, 0},
+#line 4548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3492, 0},
+#line 2032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3493, 0},
+#line 4561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3494, 0},
+#line 6014 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3495, 0},
+#line 4743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3496, 0},
+#line 2367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3497, 4},
+#line 354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3498, 0},
+#line 2077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3499, 4},
+#line 1329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3500, 0},
+#line 4281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3501, 0},
+#line 2844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3502, 0},
+#line 1519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3503, 4},
+#line 2191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3504, 0},
+#line 1358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3505, 0},
+#line 2881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3506, 0},
+#line 4262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3507, 0},
+#line 3508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3508, 0},
+#line 1322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3509, 0},
+#line 3988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3510, 0},
+#line 5169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3511, 0},
+#line 1854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3512, 0},
+#line 2821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3513, 0},
+#line 4600 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3514, 4},
+#line 4373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3515, 0},
+#line 4697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3516, 0},
+#line 2408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3517, 0},
+#line 4744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3518, 0},
+#line 4003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3519, 0},
+#line 2773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3520, 0},
+#line 2431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3521, 0},
+#line 1377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3522, 0},
+#line 2775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3523, 0},
+#line 2846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3524, 0},
+#line 5396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3525, 0},
+#line 4406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3526, 0},
+#line 4430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3527, 0},
+#line 2422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3528, 0},
+#line 4377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3529, 0},
+#line 4460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3530, 0},
+#line 4454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3531, 0},
+#line 4324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3532, 0},
+#line 3142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3533, 0},
+#line 2455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3534, 1},
+#line 3511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3535, 0},
+#line 4466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3536, 0},
+#line 4887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3537, 0},
+#line 5084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3538, 0},
+#line 2105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3539, 0},
+#line 2987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3540, 0},
+#line 1813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3541, 0},
+#line 5599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3542, 0},
+#line 1308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3543, 0},
+#line 4451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3544, 0},
+#line 4431 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3545, 0},
+#line 618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3546, 0},
+#line 4407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3547, 0},
+#line 552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3548, 0},
+#line 4447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3549, 0},
+#line 5715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3550, 0},
+#line 5355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3551, 0},
+#line 5527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3552, 0},
+#line 4325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3553, 0},
+#line 4736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3554, 0},
+#line 2132 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3555, 0},
+#line 5155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3556, 0},
+#line 4452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3557, 0},
+#line 3512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3558, 0},
+#line 3802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3559, 0},
+#line 4327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3560, 0},
+#line 3990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3561, 0},
+#line 2589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3562, 0},
+#line 4408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3563, 0},
+#line 4489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3564, 0},
+#line 4326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3565, 0},
+#line 4296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3566, 0},
+#line 1740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3567, 0},
+#line 4957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3568, 0},
+#line 635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3569, 0},
+#line 1864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3570, 0},
+#line 3803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3571, 0},
+#line 1450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3572, 0},
+#line 6061 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3573, 0},
+#line 4735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3574, 0},
+#line 4332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3575, 0},
+#line 4438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3576, 0},
+#line 5042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3577, 0},
+#line 6034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3578, 0},
+#line 5399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3579, 0},
+#line 5143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3580, 0},
+#line 4374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3581, 0},
+#line 1593 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3582, 0},
+#line 2416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3583, 0},
+#line 5165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3584, 0},
+#line 6115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3585, 0},
+#line 4037 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3586, 0},
+#line 1524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3587, 0},
+#line 4432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3588, 0},
+#line 4353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3589, 0},
+#line 1651 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3590, 0},
+#line 6085 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3591, 0},
+#line 5532 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3592, 0},
+#line 4375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3593, 0},
+#line 2871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3594, 0},
+#line 5201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3595, 0},
+#line 3509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3596, 0},
+#line 2432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3597, 0},
+#line 4396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3598, 0},
+#line 4364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3599, 0},
+#line 123 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3600, 0},
+#line 3255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3601, 0},
+#line 4695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3602, 0},
+#line 5244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3603, 0},
+#line 5118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3604, 0},
+#line 3471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3605, 0},
+#line 1950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3606, 0},
+#line 4297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3607, 0},
+#line 2406 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3608, 0},
+#line 1874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3609, 0},
+#line 3670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3610, 4},
+#line 3997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3611, 0},
+#line 2907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3612, 0},
+#line 984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3613, 0},
+#line 3893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3614, 0},
+#line 4365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3615, 0},
+#line 4021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3616, 0},
+#line 4299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3617, 0},
+#line 1556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3618, 0},
+#line 1938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3619, 0},
+#line 2991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3620, 0},
+#line 5010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3621, 0},
+#line 4295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3622, 0},
+#line 3949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3623, 0},
+#line 5075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3624, 0},
+#line 338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3625, 0},
+#line 1091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3626, 0},
+#line 5667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3627, 0},
+#line 2980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3628, 0},
+#line 5668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3629, 0},
+#line 1315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3630, 0},
+#line 3174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3631, 0},
+#line 1985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3632, 0},
+#line 4300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3633, 0},
+#line 2400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3634, 0},
+#line 1596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3635, 4},
+#line 4455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3636, 0},
+#line 4284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3637, 0},
+#line 4616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3638, 0},
+#line 4344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3639, 0},
+#line 4732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3640, 0},
+#line 1554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3641, 0},
+#line 3500 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3642, 0},
+#line 4363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3643, 0},
+#line 5990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3644, 0},
+#line 158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3645, 0},
+#line 4427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3646, 0},
+#line 5329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3647, 0},
+#line 5419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3648, 0},
+#line 4806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3649, 0},
+#line 4779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3650, 4},
+#line 4027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3651, 0},
+#line 5342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3652, 0},
+#line 6010 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3653, 0},
+#line 4433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3654, 0},
+#line 4404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3655, 0},
+#line 4449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3656, 0},
+#line 4425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3657, 4},
+#line 772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3658, 0},
+#line 2093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3659, 4},
+#line 4700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3660, 0},
+#line 1569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3661, 0},
+#line 2327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3662, 4},
+#line 3840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3663, 0},
+#line 2618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3664, 0},
+#line 816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3665, 0},
+#line 2435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3666, 0},
+#line 3986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3667, 0},
+#line 4825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3668, 0},
+#line 2173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3669, 0},
+#line 1654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3670, 0},
+#line 221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3671, 0},
+#line 3980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3672, 0},
+#line 6086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3673, 0},
+#line 5063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3674, 0},
+#line 1350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3675, 0},
+#line 2383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3676, 0},
+#line 1976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3677, 0},
+#line 4436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3678, 0},
+#line 6009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3679, 0},
+#line 3254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3680, 0},
+#line 2742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3681, 0},
+#line 6011 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3682, 0},
+#line 237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3683, 0},
+#line 4423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3684, 0},
+#line 4333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3685, 0},
+#line 2067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3686, 0},
+#line 860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3687, 0},
+#line 2543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3688, 0},
+#line 3836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3689, 0},
+#line 5539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3690, 0},
+#line 2861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3691, 0},
+#line 5511 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3692, 0},
+#line 5261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3693, 0},
+#line 5663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3694, 0},
+#line 322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3695, 4},
+#line 5535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3696, 0},
+#line 4076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3697, 0},
+#line 1522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3698, 4},
+#line 1686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3699, 0},
+#line 2436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3700, 0},
+#line 719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3701, 0},
+#line 4636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3702, 0},
+#line 3051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3703, 0},
+#line 2667 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3704, 0},
+#line 5082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3705, 0},
+#line 5508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3706, 0},
+#line 2446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3707, 0},
+#line 1998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3708, 0},
+#line 4001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3709, 0},
+#line 3868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3710, 4},
+#line 1029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3711, 0},
+#line 5671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3712, 0},
+#line 4359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3713, 0},
+#line 1159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3714, 0},
+#line 238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3715, 0},
+#line 4969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3716, 0},
+#line 5514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3717, 0},
+#line 4463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3718, 0},
+#line 2919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3719, 0},
+#line 1543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3720, 0},
+#line 487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3721, 4},
+#line 2542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3722, 0},
+#line 1840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3723, 0},
+#line 3974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3724, 0},
+#line 5538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3725, 0},
+#line 611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3726, 0},
+#line 5606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3727, 0},
+#line 3257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3728, 0},
+#line 1139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3729, 4},
+#line 3965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3730, 0},
+#line 6013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3731, 0},
+#line 2046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3732, 0},
+#line 3050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3733, 0},
+#line 4261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3734, 0},
+#line 4562 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3735, 0},
+#line 1291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3736, 0},
+#line 5501 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3737, 0},
+#line 3846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3738, 0},
+#line 1520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3739, 4},
+#line 2707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3740, 0},
+#line 2117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3741, 0},
+#line 130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3742, 0},
+#line 6004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3743, 0},
+#line 3610 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3744, 0},
+#line 4638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3745, 0},
+#line 2569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3746, 0},
+#line 4974 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3747, 0},
+#line 2923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3748, 0},
+#line 1160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3749, 0},
+#line 4393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3750, 0},
+#line 6016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3751, 0},
+#line 859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3752, 0},
+#line 5489 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3753, 0},
+#line 5633 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3754, 0},
+#line 6092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3755, 4},
+#line 1942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3756, 0},
+#line 2819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3757, 0},
+#line 2164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3758, 0},
+#line 3525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3759, 0},
+#line 4464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3760, 0},
+#line 4357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3761, 0},
+#line 5678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3762, 0},
+#line 4748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3763, 0},
+#line 1437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3764, 0},
+#line 2644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3765, 0},
+#line 373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3766, 0},
+#line 4847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3767, 0},
+#line 3932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3768, 0},
+#line 4662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3769, 0},
+#line 1643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3770, 4},
+#line 4442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3771, 0},
+#line 3069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3772, 0},
+#line 1904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3773, 0},
+#line 2882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3774, 0},
+#line 327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3775, 0},
+#line 5676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3776, 0},
+#line 3510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3777, 0},
+#line 2080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3778, 0},
+#line 2170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3779, 0},
+#line 5346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3780, 0},
+#line 3602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3781, 0},
+#line 1544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3782, 0},
+#line 738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3783, 0},
+#line 6052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3784, 0},
+#line 4439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3785, 0},
+#line 4641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3786, 0},
+#line 4763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3787, 0},
+#line 2989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3788, 4},
+#line 4094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3789, 0},
+#line 4392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3790, 0},
+#line 2368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3791, 4},
+#line 5707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3792, 0},
+#line 2765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3793, 0},
+#line 3236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3794, 0},
+#line 4252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3795, 0},
+#line 1300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3796, 0},
+#line 2220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3797, 0},
+#line 4653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3798, 0},
+#line 285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3799, 0},
+#line 5147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3800, 0},
+#line 1923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3801, 0},
+#line 3960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3802, 0},
+#line 1957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3803, 0},
+#line 204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3804, 0},
+#line 5140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3805, 0},
+#line 2554 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3806, 0},
+#line 5099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3807, 0},
+#line 4077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3808, 0},
+#line 4238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3809, 0},
+#line 5309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3810, 0},
+#line 2953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3811, 0},
+#line 6045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3812, 0},
+#line 3425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3813, 0},
+#line 6008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3814, 0},
+#line 3265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3815, 0},
+#line 4693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3816, 0},
+#line 4401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3817, 0},
+#line 565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3818, 0},
+#line 4604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3819, 4},
+#line 3919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3820, 0},
+#line 3862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3821, 0},
+#line 5047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3822, 0},
+#line 2187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3823, 0},
+#line 1765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3824, 0},
+#line 1217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3825, 0},
+#line 1764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3826, 0},
+#line 915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3827, 0},
+#line 1769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3828, 0},
+#line 1216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3829, 0},
+#line 1221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3830, 0},
+#line 1763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3831, 0},
+#line 914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3832, 0},
+#line 1215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3833, 0},
+#line 918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3834, 0},
+#line 913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3835, 0},
+#line 2197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3836, 0},
+#line 2862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3837, 0},
+#line 2300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3838, 4},
+#line 3588 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3839, 0},
+#line 63 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3840, 0},
+#line 1591 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3841, 0},
+#line 4368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3842, 0},
+#line 737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3843, 0},
+#line 4342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3844, 2},
+#line 1292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3845, 0},
+#line 5294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3846, 0},
+#line 481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3847, 4},
+#line 3994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3848, 0},
+#line 4024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3849, 0},
+#line 4664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3850, 0},
+#line 5044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3851, 0},
+#line 4681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3852, 0},
+#line 2739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3853, 0},
+#line 4343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3854, 0},
+#line 5000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3855, 0},
+#line 2212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3856, 0},
+#line 296 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3857, 0},
+#line 5403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3858, 0},
+#line 4428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3859, 0},
+#line 3455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3860, 0},
+#line 4429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3861, 0},
+#line 1766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3862, 0},
+#line 1218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3863, 0},
+#line 569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3864, 0},
+#line 2107 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3865, 0},
+#line 2771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3866, 0},
+#line 1117 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3867, 0},
+#line 4331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3868, 0},
+#line 5371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3869, 4},
+#line 3811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3870, 0},
+#line 3849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3871, 0},
+#line 1324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3872, 0},
+#line 4303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3873, 0},
+#line 4620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3874, 0},
+#line 3375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3875, 0},
+#line 4446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3876, 0},
+#line 3112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3877, 0},
+#line 1542 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3878, 0},
+#line 626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3879, 0},
+#line 1925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3880, 0},
+#line 5713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3881, 0},
+#line 3331 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3882, 0},
+#line 5537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3883, 0},
+#line 4740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3884, 0},
+#line 2428 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3885, 0},
+#line 2094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3886, 4},
+#line 3170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3887, 0},
+#line 3992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3888, 0},
+#line 2770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3889, 0},
+#line 4980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3890, 0},
+#line 3972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3891, 0},
+#line 446 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3892, 0},
+#line 4643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3893, 0},
+#line 268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3894, 0},
+#line 2295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3895, 0},
+#line 1462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3896, 0},
+#line 3343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3897, 0},
+#line 3335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3898, 0},
+#line 3267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3899, 0},
+#line 4235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3900, 0},
+#line 3118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3901, 0},
+#line 5151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3902, 0},
+#line 5976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3903, 0},
+#line 1924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3904, 4},
+#line 4305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3905, 0},
+#line 61 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3906, 0},
+#line 5660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3907, 0},
+#line 3844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3908, 0},
+#line 1861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3909, 0},
+#line 3812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3910, 0},
+#line 62 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3911, 0},
+#line 4367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3912, 0},
+#line 2752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3913, 0},
+#line 4456 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3914, 0},
+#line 2031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3915, 0},
+#line 4630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3916, 0},
+#line 3148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3917, 0},
+#line 1789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3918, 0},
+#line 4587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3919, 0},
+#line 4426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3920, 0},
+#line 1238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3921, 0},
+#line 3639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3922, 0},
+#line 1518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3923, 4},
+#line 934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3924, 0},
+#line 3263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3925, 0},
+#line 33 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3926, 0},
+#line 4482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3927, 0},
+#line 2576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3928, 0},
+#line 3933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3929, 0},
+#line 206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3930, 0},
+#line 6024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3931, 0},
+#line 4306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3932, 0},
+#line 5213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3933, 0},
+#line 2702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3934, 0},
+#line 995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3935, 0},
+#line 3450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3936, 0},
+#line 3396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3937, 0},
+#line 6006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3938, 0},
+#line 5229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3939, 0},
+#line 3242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3940, 0},
+#line 461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3941, 0},
+#line 6049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3942, 0},
+#line 2141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3943, 0},
+#line 3841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3944, 0},
+#line 4264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3945, 0},
+#line 6043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3946, 0},
+#line 4022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3947, 0},
+#line 98 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3948, 0},
+#line 1060 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3949, 0},
+#line 5320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3950, 0},
+#line 4981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3951, 0},
+#line 4388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3952, 0},
+#line 5021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3953, 0},
+#line 3655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3954, 0},
+#line 154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3955, 0},
+#line 4260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3956, 0},
+#line 2908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3957, 0},
+#line 1872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3958, 0},
+#line 2050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3959, 0},
+#line 1393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3960, 0},
+#line 266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3961, 0},
+#line 4603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3962, 4},
+#line 127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3963, 0},
+#line 4547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3964, 0},
+#line 1625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3965, 0},
+#line 4994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3966, 0},
+#line 3298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3967, 0},
+#line 3163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3968, 0},
+#line 2646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3969, 0},
+#line 1857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3970, 0},
+#line 3300 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3971, 0},
+#line 4970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3972, 0},
+#line 3297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3973, 0},
+#line 1773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3974, 0},
+#line 1225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3975, 0},
+#line 3334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3976, 0},
+#line 4821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3977, 0},
+#line 922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3978, 0},
+#line 3837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3979, 0},
+#line 4062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3980, 4},
+#line 478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3981, 1},
+#line 1517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3982, 4},
+#line 1770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3983, 0},
+#line 1222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3984, 0},
+#line 919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3985, 0},
+#line 812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3986, 0},
+#line 6065 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3987, 0},
+#line 415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3988, 0},
+#line 1772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3989, 0},
+#line 1224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3990, 0},
+#line 1551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3991, 0},
+#line 921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3992, 0},
+#line 3438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3993, 0},
+#line 4355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3994, 0},
+#line 4400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3995, 0},
+#line 1771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3996, 0},
+#line 1223 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3997, 0},
+#line 4227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3998, 0},
+#line 2635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str3999, 0},
+#line 920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4000, 0},
+#line 172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4001, 0},
+#line 6075 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4002, 0},
+#line 4405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4003, 0},
+#line 6056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4004, 0},
+#line 1559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4005, 0},
+#line 3339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4006, 0},
+#line 1775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4007, 0},
+#line 3073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4008, 0},
+#line 1452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4009, 0},
+#line 3299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4010, 0},
+#line 2637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4011, 0},
+#line 1871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4012, 0},
+#line 5046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4013, 0},
+#line 1774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4014, 0},
+#line 1226 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4015, 0},
+#line 3494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4016, 0},
+#line 2426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4017, 0},
+#line 923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4018, 0},
+#line 3534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4019, 0},
+#line 834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4020, 4},
+#line 4843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4021, 0},
+#line 5152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4022, 0},
+#line 1112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4023, 4},
+#line 3099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4024, 0},
+#line 4057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4025, 0},
+#line 234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4026, 0},
+#line 2514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4027, 0},
+#line 693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4028, 0},
+#line 5491 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4029, 0},
+#line 1323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4030, 0},
+#line 5478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4031, 0},
+#line 832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4032, 4},
+#line 3266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4033, 0},
+#line 3833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4034, 0},
+#line 2024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4035, 0},
+#line 4966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4036, 0},
+#line 1934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4037, 0},
+#line 1926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4038, 0},
+#line 5293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4039, 0},
+#line 3022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4040, 0},
+#line 29 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4041, 0},
+#line 4009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4042, 0},
+#line 985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4043, 0},
+#line 287 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4044, 0},
+#line 663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4045, 0},
+#line 5183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4046, 0},
+#line 5604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4047, 0},
+#line 1627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4048, 4},
+#line 6069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4049, 0},
+#line 5507 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4050, 0},
+#line 1767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4051, 0},
+#line 1219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4052, 0},
+#line 1903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4053, 0},
+#line 643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4054, 0},
+#line 916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4055, 0},
+#line 3955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4056, 0},
+#line 3451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4057, 0},
+#line 5266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4058, 0},
+#line 3907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4059, 0},
+#line 4470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4060, 4},
+#line 3389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4061, 0},
+#line 3129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4062, 0},
+#line 4962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4063, 0},
+#line 4711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4064, 0},
+#line 2430 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4065, 0},
+#line 32 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4066, 0},
+#line 5012 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4067, 0},
+#line 5629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4068, 0},
+#line 726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4069, 0},
+#line 1102 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4070, 4},
+#line 4143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4071, 0},
+#line 2817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4072, 0},
+#line 4100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4073, 0},
+#line 2911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4074, 0},
+#line 982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4075, 0},
+#line 3304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4076, 0},
+#line 3447 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4077, 0},
+#line 2890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4078, 0},
+#line 1850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4079, 0},
+#line 3264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4080, 0},
+#line 288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4081, 0},
+#line 5036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4082, 0},
+#line 4661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4083, 0},
+#line 1984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4084, 0},
+#line 3845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4085, 0},
+#line 2390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4086, 0},
+#line 5023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4087, 0},
+#line 4602 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4088, 4},
+#line 2952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4089, 0},
+#line 5137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4090, 0},
+#line 6042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4091, 0},
+#line 69 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4092, 0},
+#line 4350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4093, 0},
+#line 4258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4094, 0},
+#line 740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4095, 0},
+#line 2366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4096, 4},
+#line 5473 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4097, 0},
+#line 4146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4098, 0},
+#line 145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4099, 0},
+#line 589 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4100, 0},
+#line 2759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4101, 0},
+#line 4660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4102, 0},
+#line 2978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4103, 0},
+#line 482 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4104, 4},
+#line 4145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4105, 0},
+#line 3231 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4106, 0},
+#line 4993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4107, 0},
+#line 1955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4108, 0},
+#line 4144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4109, 0},
+#line 394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4110, 0},
+#line 4029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4111, 0},
+#line 5148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4112, 0},
+#line 4549 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4113, 0},
+#line 4810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4114, 4},
+#line 4399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4115, 0},
+#line 4801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4116, 0},
+#line 5188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4117, 0},
+#line 4087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4118, 0},
+#line 980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4119, 0},
+#line 2166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4120, 0},
+#line 1051 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4121, 0},
+#line 3453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4122, 0},
+#line 5291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4123, 0},
+#line 1981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4124, 0},
+#line 2656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4125, 0},
+#line 4710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4126, 0},
+#line 5289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4127, 0},
+#line 1836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4128, 0},
+#line 2462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4129, 0},
+#line 2182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4130, 0},
+#line 1994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4131, 0},
+#line 981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4132, 0},
+#line 4028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4133, 0},
+#line 6044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4134, 0},
+#line 1082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4135, 0},
+#line 1162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4136, 0},
+#line 5469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4137, 0},
+#line 352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4138, 0},
+#line 4348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4139, 0},
+#line 5347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4140, 0},
+#line 174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4141, 0},
+#line 5209 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4142, 0},
+#line 5327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4143, 0},
+#line 2055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4144, 0},
+#line 2341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4145, 4},
+#line 2585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4146, 0},
+#line 989 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4147, 1},
+#line 2236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4148, 0},
+#line 4921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4149, 4},
+#line 1859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4150, 0},
+#line 5520 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4151, 0},
+#line 5368 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4152, 0},
+#line 4496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4153, 0},
+#line 2054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4154, 0},
+#line 4522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4155, 0},
+#line 2516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4156, 0},
+#line 1935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4157, 0},
+#line 2247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4158, 0},
+#line 45 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4159, 0},
+#line 4844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4160, 0},
+#line 1242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4161, 0},
+#line 1794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4162, 0},
+#line 3024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4163, 0},
+#line 1245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4164, 0},
+#line 940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4165, 0},
+#line 2237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4166, 4},
+#line 943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4167, 0},
+#line 1747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4168, 0},
+#line 1200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4169, 0},
+#line 595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4170, 0},
+#line 2213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4171, 0},
+#line 1750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4172, 0},
+#line 4015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4173, 0},
+#line 897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4174, 0},
+#line 1202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4175, 0},
+#line 814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4176, 0},
+#line 1748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4177, 0},
+#line 2129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4178, 0},
+#line 899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4179, 0},
+#line 3584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4180, 0},
+#line 1241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4181, 0},
+#line 5972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4182, 0},
+#line 696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4183, 4},
+#line 939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4184, 0},
+#line 1798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4185, 0},
+#line 1249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4186, 0},
+#line 946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4187, 0},
+#line 3344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4188, 0},
+#line 2142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4189, 0},
+#line 1671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4190, 0},
+#line 5093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4191, 0},
+#line 4629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4192, 0},
+#line 847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4193, 0},
+#line 3581 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4194, 0},
+#line 747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4195, 0},
+#line 3794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4196, 0},
+#line 3463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4197, 0},
+#line 1698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4198, 0},
+#line 2106 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4199, 0},
+#line 2092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4200, 4},
+#line 5197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4201, 0},
+#line 2303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4202, 4},
+#line 4251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4203, 0},
+#line 3478 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4204, 0},
+#line 2233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4205, 0},
+#line 1995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4206, 0},
+#line 4486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4207, 0},
+#line 295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4208, 0},
+#line 1693 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4209, 4},
+#line 2256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4210, 0},
+#line 1271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4211, 0},
+#line 2257 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4212, 0},
+#line 973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4213, 0},
+#line 1096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4214, 4},
+#line 3502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4215, 0},
+#line 1907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4216, 0},
+#line 3432 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4217, 0},
+#line 1799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4218, 0},
+#line 5701 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4219, 0},
+#line 1250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4220, 0},
+#line 947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4221, 0},
+#line 5727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4222, 0},
+#line 2309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4223, 4},
+#line 2195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4224, 0},
+#line 1206 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4225, 0},
+#line 2322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4226, 4},
+#line 2250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4227, 0},
+#line 903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4228, 0},
+#line 5264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4229, 0},
+#line 2240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4230, 0},
+#line 1797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4231, 0},
+#line 1796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4232, 0},
+#line 1804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4233, 0},
+#line 1248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4234, 0},
+#line 2234 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4235, 0},
+#line 1247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4236, 0},
+#line 1255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4237, 0},
+#line 628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4238, 0},
+#line 945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4239, 0},
+#line 4889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4240, 0},
+#line 953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4241, 0},
+#line 1752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4242, 0},
+#line 1751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4243, 0},
+#line 1205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4244, 0},
+#line 1203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4245, 0},
+#line 36 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4246, 0},
+#line 902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4247, 0},
+#line 900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4248, 0},
+#line 1137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4249, 4},
+#line 4279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4250, 0},
+#line 1690 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4251, 0},
+#line 1038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4252, 0},
+#line 2424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4253, 0},
+#line 3098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4254, 0},
+#line 4090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4255, 0},
+#line 343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4256, 0},
+#line 1795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4257, 0},
+#line 3719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4258, 0},
+#line 275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4259, 0},
+#line 1246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4260, 0},
+#line 131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4261, 0},
+#line 2238 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4262, 0},
+#line 3448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4263, 0},
+#line 944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4264, 0},
+#line 4340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4265, 0},
+#line 3214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4266, 0},
+#line 4655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4267, 0},
+#line 4086 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4268, 0},
+#line 732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4269, 0},
+#line 2347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4270, 4},
+#line 656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4271, 0},
+#line 3574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4272, 0},
+#line 2319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4273, 4},
+#line 3083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4274, 0},
+#line 768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4275, 0},
+#line 4574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4276, 0},
+#line 2255 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4277, 0},
+#line 2111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4278, 0},
+#line 2034 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4279, 0},
+#line 324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4280, 0},
+#line 442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4281, 0},
+#line 4947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4282, 0},
+#line 2183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4283, 0},
+#line 1875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4284, 0},
+#line 4995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4285, 0},
+#line 1342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4286, 0},
+#line 3248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4287, 0},
+#line 252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4288, 0},
+#line 1681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4289, 0},
+#line 4805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4290, 0},
+#line 1682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4291, 0},
+#line 2349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4292, 4},
+#line 3269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4293, 0},
+#line 2248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4294, 0},
+#line 5726 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4295, 0},
+#line 3252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4296, 0},
+#line 128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4297, 0},
+#line 4316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4298, 0},
+#line 2249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4299, 0},
+#line 4608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4300, 4},
+#line 1680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4301, 0},
+#line 3721 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4302, 0},
+#line 3333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4303, 0},
+#line 692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4304, 0},
+#line 35 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4305, 0},
+#line 3998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4306, 0},
+#line 3342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4307, 0},
+#line 1788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4308, 0},
+#line 4362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4309, 0},
+#line 2246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4310, 0},
+#line 1236 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4311, 0},
+#line 1694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4312, 0},
+#line 2171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4313, 0},
+#line 932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4314, 0},
+#line 3720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4315, 0},
+#line 2242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4316, 0},
+#line 804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4317, 0},
+#line 1131 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4318, 4},
+#line 114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4319, 0},
+#line 6084 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4320, 0},
+#line 3215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4321, 0},
+#line 1687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4322, 0},
+#line 1927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4323, 0},
+#line 4717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4324, 0},
+#line 50 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4325, 0},
+#line 1879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4326, 0},
+#line 48 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4327, 0},
+#line 2845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4328, 0},
+#line 2210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4329, 0},
+#line 43 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4330, 0},
+#line 2421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4331, 0},
+#line 4758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4332, 0},
+#line 1928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4333, 0},
+#line 3909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4334, 0},
+#line 674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4335, 0},
+#line 4927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4336, 0},
+#line 1463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4337, 0},
+#line 4398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4338, 0},
+#line 782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4339, 0},
+#line 2831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4340, 0},
+#line 1365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4341, 0},
+#line 1677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4342, 0},
+#line 4880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4343, 0},
+#line 465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4344, 0},
+#line 1303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4345, 0},
+#line 3336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4346, 0},
+#line 397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4347, 0},
+#line 2983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4348, 0},
+#line 3066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4349, 0},
+#line 4064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4350, 0},
+#line 185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4351, 0},
+#line 2038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4352, 0},
+#line 4510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4353, 0},
+#line 1612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4354, 0},
+#line 3345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4355, 0},
+#line 742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4356, 0},
+#line 6076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4357, 0},
+#line 4876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4358, 0},
+#line 3240 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4359, 4},
+#line 2178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4360, 0},
+#line 5653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4361, 0},
+#line 222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4362, 0},
+#line 5973 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4363, 0},
+#line 5247 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4364, 4},
+#line 1046 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4365, 0},
+#line 3306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4366, 0},
+#line 3289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4367, 0},
+#line 4518 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4368, 0},
+#line 2180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4369, 0},
+#line 103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4370, 0},
+#line 5305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4371, 0},
+#line 4069 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4372, 0},
+#line 2100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4373, 4},
+#line 4349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4374, 0},
+#line 57 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4375, 0},
+#line 5519 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4376, 0},
+#line 1618 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4377, 0},
+#line 3551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4378, 0},
+#line 4285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4379, 0},
+#line 5043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4380, 0},
+#line 714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4381, 0},
+#line 1094 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4382, 4},
+#line 3590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4383, 0},
+#line 1704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4384, 0},
+#line 3824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4385, 0},
+#line 1460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4386, 0},
+#line 3851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4387, 0},
+#line 46 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4388, 0},
+#line 5728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4389, 0},
+#line 467 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4390, 0},
+#line 5605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4391, 0},
+#line 5022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4392, 0},
+#line 5512 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4393, 0},
+#line 2830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4394, 0},
+#line 1947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4395, 0},
+#line 3979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4396, 0},
+#line 3195 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4397, 0},
+#line 3321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4398, 0},
+#line 1949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4399, 0},
+#line 4853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4400, 0},
+#line 754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4401, 0},
+#line 4799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4402, 0},
+#line 466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4403, 0},
+#line 3291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4404, 0},
+#line 2399 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4405, 0},
+#line 4985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4406, 0},
+#line 1440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4407, 4},
+#line 3628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4408, 0},
+#line 3312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4409, 0},
+#line 2351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4410, 4},
+#line 1692 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4411, 4},
+#line 4457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4412, 0},
+#line 2101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4413, 4},
+#line 1243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4414, 0},
+#line 4020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4415, 0},
+#line 2694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4416, 0},
+#line 941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4417, 0},
+#line 3144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4418, 0},
+#line 2536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4419, 0},
+#line 5414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4420, 0},
+#line 3308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4421, 0},
+#line 2241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4422, 0},
+#line 1095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4423, 4},
+#line 2508 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4424, 0},
+#line 3599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4425, 0},
+#line 6063 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4426, 0},
+#line 3817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4427, 0},
+#line 5103 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4428, 0},
+#line 5182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4429, 0},
+#line 3882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4430, 0},
+#line 5295 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4431, 0},
+#line 2579 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4432, 0},
+#line 3044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4433, 0},
+#line 620 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4434, 0},
+#line 3015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4435, 0},
+#line 4004 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4436, 0},
+#line 5108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4437, 0},
+#line 4313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4438, 0},
+#line 413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4439, 0},
+#line 2253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4440, 0},
+#line 4050 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4441, 0},
+#line 463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4442, 0},
+#line 475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4443, 0},
+#line 387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4444, 0},
+#line 2509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4445, 0},
+#line 5503 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4446, 0},
+#line 1204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4447, 0},
+#line 2853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4448, 0},
+#line 263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4449, 0},
+#line 901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4450, 0},
+#line 4070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4451, 0},
+#line 2254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4452, 0},
+#line 2252 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4453, 0},
+#line 1298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4454, 0},
+#line 3652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4455, 0},
+#line 382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4456, 0},
+#line 3682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4457, 0},
+#line 455 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4458, 0},
+#line 2537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4459, 0},
+#line 2140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4460, 0},
+#line 3351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4461, 0},
+#line 2251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4462, 0},
+#line 3016 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4463, 0},
+#line 2243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4464, 0},
+#line 276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4465, 0},
+#line 4058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4466, 0},
+#line 1793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4467, 0},
+#line 3349 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4468, 0},
+#line 1244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4469, 0},
+#line 1665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4470, 0},
+#line 1936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4471, 0},
+#line 1168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4472, 0},
+#line 942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4473, 0},
+#line 823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4474, 0},
+#line 1749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4475, 0},
+#line 1201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4476, 0},
+#line 77 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4477, 0},
+#line 2793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4478, 0},
+#line 202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4479, 0},
+#line 1078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4480, 0},
+#line 3045 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4481, 0},
+#line 898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4482, 0},
+#line 2598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4483, 0},
+#line 5225 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4484, 0},
+#line 1110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4485, 4},
+#line 6058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4486, 0},
+#line 1624 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4487, 0},
+#line 1833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4488, 0},
+#line 4002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4489, 0},
+#line 2013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4490, 0},
+#line 2318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4491, 4},
+#line 2179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4492, 0},
+#line 1301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4493, 0},
+#line 4129 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4494, 0},
+#line 4128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4495, 0},
+#line 170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4496, 0},
+#line 1077 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4497, 0},
+#line 4130 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4498, 0},
+#line 2834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4499, 0},
+#line 2708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4500, 0},
+#line 2145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4501, 0},
+#line 2365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4502, 4},
+#line 5114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4503, 0},
+#line 4043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4504, 0},
+#line 2245 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4505, 0},
+#line 5059 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4506, 4},
+#line 4378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4507, 0},
+#line 1849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4508, 0},
+#line 4656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4509, 0},
+#line 4958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4510, 0},
+#line 2954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4511, 0},
+#line 5126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4512, 0},
+#line 1076 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4513, 0},
+#line 781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4514, 0},
+#line 2354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4515, 4},
+#line 5486 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4516, 0},
+#line 1953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4517, 0},
+#line 1309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4518, 4},
+#line 2490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4519, 0},
+#line 3395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4520, 0},
+#line 3216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4521, 0},
+#line 6104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4522, 0},
+#line 2405 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4523, 0},
+#line 3091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4524, 0},
+#line 2244 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4525, 0},
+#line 2688 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4526, 0},
+#line 538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4527, 0},
+#line 3350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4528, 0},
+#line 2332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4529, 4},
+#line 2617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4530, 0},
+#line 2356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4531, 4},
+#line 5806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4532, 0},
+#line 2596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4533, 0},
+#line 5962 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4534, 0},
+#line 3346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4535, 0},
+#line 1952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4536, 0},
+#line 4017 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4537, 0},
+#line 1580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4538, 0},
+#line 5422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4539, 0},
+#line 2641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4540, 0},
+#line 6067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4541, 0},
+#line 3854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4542, 0},
+#line 416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4543, 0},
+#line 3194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4544, 0},
+#line 138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4545, 0},
+#line 1307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4546, 0},
+#line 3850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4547, 0},
+#line 5873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4548, 0},
+#line 5738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4549, 0},
+#line 2559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4550, 0},
+#line 5407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4551, 0},
+#line 3795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4552, 0},
+#line 3151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4553, 0},
+#line 1972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4554, 0},
+#line 5150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4555, 0},
+#line 5006 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4556, 0},
+#line 144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4557, 0},
+#line 1539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4558, 0},
+#line 6064 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4559, 0},
+#line 5915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4560, 0},
+#line 3580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4561, 0},
+#line 1855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4562, 0},
+#line 3378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4563, 0},
+#line 4366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4564, 0},
+#line 2339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4565, 4},
+#line 2159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4566, 0},
+#line 2766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4567, 0},
+#line 3557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4568, 2},
+#line 4867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4569, 0},
+#line 3362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4570, 0},
+#line 2643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4571, 0},
+#line 2360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4572, 4},
+#line 4450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4573, 0},
+#line 2338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4574, 4},
+#line 3977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4575, 0},
+#line 3239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4576, 0},
+#line 675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4577, 0},
+#line 197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4578, 0},
+#line 4289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4579, 0},
+#line 2661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4580, 0},
+#line 3641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4581, 0},
+#line 5940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4582, 0},
+#line 3366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4583, 0},
+#line 2414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4584, 0},
+#line 4320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4585, 0},
+#line 733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4586, 0},
+#line 4196 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4587, 0},
+#line 3449 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4588, 0},
+#line 1126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4589, 4},
+#line 5433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4590, 0},
+#line 5091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4591, 0},
+#line 4315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4592, 0},
+#line 3124 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4593, 0},
+#line 3307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4594, 0},
+#line 4304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4595, 0},
+#line 4091 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4596, 0},
+#line 3290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4597, 0},
+#line 5415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4598, 0},
+#line 2098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4599, 4},
+#line 4197 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4600, 0},
+#line 2555 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4601, 0},
+#line 5757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4602, 0},
+#line 385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4603, 0},
+#line 746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4604, 0},
+#line 105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4605, 0},
+#line 1768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4606, 0},
+#line 5120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4607, 0},
+#line 1220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4608, 0},
+#line 410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4609, 0},
+#line 917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4610, 0},
+#line 2768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4611, 0},
+#line 5924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4612, 0},
+#line 2184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4613, 0},
+#line 2052 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4614, 0},
+#line 5736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4615, 0},
+#line 4282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4616, 0},
+#line 1013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4617, 0},
+#line 4198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4618, 0},
+#line 4771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4619, 0},
+#line 5918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4620, 0},
+#line 3987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4621, 0},
+#line 2594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4622, 0},
+#line 2685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4623, 0},
+#line 3964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4624, 0},
+#line 4019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4625, 0},
+#line 670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4626, 0},
+#line 2895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4627, 0},
+#line 713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4628, 0},
+#line 1127 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4629, 4},
+#line 3971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4630, 0},
+#line 4672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4631, 0},
+#line 4819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4632, 0},
+#line 681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4633, 0},
+#line 6066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4634, 0},
+#line 3708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4635, 0},
+#line 3707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4636, 0},
+#line 758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4637, 0},
+#line 1067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4638, 0},
+#line 759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4639, 0},
+#line 3709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4640, 0},
+#line 4175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4641, 0},
+#line 4170 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4642, 0},
+#line 4172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4643, 0},
+#line 2381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4644, 0},
+#line 3348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4645, 0},
+#line 4171 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4646, 0},
+#line 94 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4647, 0},
+#line 5922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4648, 0},
+#line 3380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4649, 0},
+#line 6079 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4650, 0},
+#line 2565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4651, 0},
+#line 4169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4652, 0},
+#line 53 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4653, 0},
+#line 5522 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4654, 4},
+#line 2139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4655, 0},
+#line 5749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4656, 0},
+#line 5933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4657, 0},
+#line 1561 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4658, 0},
+#line 3330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4659, 0},
+#line 4685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4660, 0},
+#line 3586 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4661, 0},
+#line 4072 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4662, 0},
+#line 5743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4663, 0},
+#line 5481 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4664, 0},
+#line 5925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4665, 0},
+#line 5920 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4666, 0},
+#line 3379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4667, 0},
+#line 5450 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4668, 0},
+#line 2634 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4669, 0},
+#line 4176 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4670, 0},
+#line 5350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4671, 0},
+#line 4679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4672, 0},
+#line 3347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4673, 0},
+#line 2049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4674, 0},
+#line 3912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4675, 0},
+#line 2417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4676, 0},
+#line 6082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4677, 0},
+#line 5351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4678, 0},
+#line 2783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4679, 0},
+#line 2216 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4680, 0},
+#line 2433 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4681, 0},
+#line 4149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4682, 0},
+#line 3816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4683, 0},
+#line 551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4684, 0},
+#line 5911 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4685, 0},
+#line 4174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4686, 0},
+#line 3434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4687, 0},
+#line 4291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4688, 0},
+#line 4601 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4689, 4},
+#line 3570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4690, 0},
+#line 1992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4691, 0},
+#line 4047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4692, 1},
+#line 1577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4693, 0},
+#line 2808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4694, 0},
+#line 4173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4695, 0},
+#line 790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4696, 0},
+#line 6105 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4697, 0},
+#line 5717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4698, 4},
+#line 3679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4699, 0},
+#line 5221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4700, 4},
+#line 5413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4701, 0},
+#line 1341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4702, 4},
+#line 2308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4703, 4},
+#line 1884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4704, 0},
+#line 2722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4705, 0},
+#line 5964 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4706, 0},
+#line 5425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4707, 0},
+#line 592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4708, 0},
+#line 2613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4709, 0},
+#line 4485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4710, 0},
+#line 5398 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4711, 0},
+#line 2346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4712, 4},
+#line 1421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4713, 0},
+#line 5949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4714, 0},
+#line 1039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4715, 0},
+#line 3889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4716, 0},
+#line 3888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4717, 0},
+#line 3834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4718, 0},
+#line 4857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4719, 0},
+#line 5024 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4720, 0},
+#line 4322 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4721, 0},
+#line 4914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4722, 0},
+#line 6098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4723, 0},
+#line 4873 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4724, 0},
+#line 2056 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4725, 0},
+#line 2301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4726, 4},
+#line 5483 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4727, 4},
+#line 6097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4728, 0},
+#line 5916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4729, 0},
+#line 1310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4730, 4},
+#line 2036 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4731, 0},
+#line 1870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4732, 0},
+#line 3665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4733, 0},
+#line 2972 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4734, 0},
+#line 4787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4735, 0},
+#line 5942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4736, 0},
+#line 1635 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4737, 0},
+#line 3915 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4738, 0},
+#line 2777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4739, 0},
+#line 4739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4740, 0},
+#line 4731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4741, 2},
+#line 5173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4742, 0},
+#line 1777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4743, 0},
+#line 1228 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4744, 0},
+#line 925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4745, 0},
+#line 282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4746, 0},
+#line 4013 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4747, 0},
+#line 4765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4748, 0},
+#line 4025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4749, 0},
+#line 2392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4750, 0},
+#line 1901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4751, 0},
+#line 2684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4752, 0},
+#line 3930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4753, 0},
+#line 4982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4754, 0},
+#line 5941 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4755, 0},
+#line 3976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4756, 0},
+#line 4646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4757, 0},
+#line 4689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4758, 0},
+#line 5145 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4759, 0},
+#line 1787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4760, 0},
+#line 1235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4761, 0},
+#line 582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4762, 0},
+#line 5345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4763, 0},
+#line 931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4764, 0},
+#line 3609 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4765, 0},
+#line 1990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4766, 0},
+#line 5948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4767, 0},
+#line 567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4768, 0},
+#line 4913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4769, 0},
+#line 3884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4770, 0},
+#line 1118 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4771, 0},
+#line 2167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4772, 0},
+#line 2476 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4773, 0},
+#line 5505 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4774, 0},
+#line 2597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4775, 0},
+#line 3630 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4776, 0},
+#line 3798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4777, 0},
+#line 5410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4778, 0},
+#line 2163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4779, 0},
+#line 251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4780, 0},
+#line 4592 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4781, 0},
+#line 4448 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4782, 0},
+#line 4267 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4783, 0},
+#line 3766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4784, 0},
+#line 2219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4785, 0},
+#line 3498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4786, 0},
+#line 2425 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4787, 0},
+#line 3853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4788, 0},
+#line 4657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4789, 0},
+#line 6074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4790, 0},
+#line 2334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4791, 4},
+#line 1865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4792, 0},
+#line 5058 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4793, 4},
+#line 1993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4794, 0},
+#line 2966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4795, 0},
+#line 2515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4796, 0},
+#line 4290 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4797, 0},
+#line 5308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4798, 0},
+#line 1895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4799, 0},
+#line 2840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4800, 0},
+#line 2222 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4801, 0},
+#line 3213 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4802, 0},
+#line 5540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4803, 0},
+#line 3746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4804, 0},
+#line 3743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4805, 0},
+#line 3744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4806, 0},
+#line 3023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4807, 0},
+#line 5745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4808, 0},
+#line 2343 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4809, 4},
+#line 5038 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4810, 0},
+#line 5926 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4811, 0},
+#line 355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4812, 0},
+#line 556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4813, 0},
+#line 192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4814, 0},
+#line 2313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4815, 4},
+#line 2999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4816, 0},
+#line 3742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4817, 0},
+#line 121 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4818, 0},
+#line 5971 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4819, 0},
+#line 2229 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4820, 0},
+#line 5958 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4821, 0},
+#line 5807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4822, 0},
+#line 1314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4823, 0},
+#line 1062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4824, 0},
+#line 3411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4825, 0},
+#line 6025 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4826, 0},
+#line 1594 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4827, 0},
+#line 5304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4828, 0},
+#line 6020 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4829, 0},
+#line 2564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4830, 0},
+#line 3340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4831, 0},
+#line 5732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4832, 0},
+#line 5071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4833, 0},
+#line 3747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4834, 0},
+#line 2317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4835, 4},
+#line 4465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4836, 0},
+#line 5319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4837, 0},
+#line 1827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4838, 0},
+#line 2720 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4839, 0},
+#line 2719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4840, 0},
+#line 3725 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4841, 0},
+#line 1862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4842, 0},
+#line 4573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4843, 0},
+#line 3390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4844, 0},
+#line 584 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4845, 0},
+#line 1049 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4846, 0},
+#line 5982 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4847, 0},
+#line 3859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4848, 0},
+#line 139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4849, 0},
+#line 4936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4850, 0},
+#line 2320 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4851, 4},
+#line 3745 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4852, 0},
+#line 3126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4853, 0},
+#line 729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4854, 0},
+#line 4370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4855, 0},
+#line 59 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4856, 0},
+#line 5907 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4857, 0},
+#line 5531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4858, 0},
+#line 5553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4859, 4},
+#line 5921 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4860, 0},
+#line 3924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4861, 0},
+#line 1655 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4862, 0},
+#line 4504 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4863, 4},
+#line 2207 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4864, 0},
+#line 2723 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4865, 0},
+#line 1111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4866, 4},
+#line 235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4867, 0},
+#line 3365 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4868, 0},
+#line 2153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4869, 0},
+#line 3539 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4870, 0},
+#line 4769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4871, 0},
+#line 2440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4872, 0},
+#line 393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4873, 4},
+#line 3916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4874, 0},
+#line 2664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4875, 0},
+#line 2761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4876, 0},
+#line 615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4877, 0},
+#line 5487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4878, 0},
+#line 384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4879, 0},
+#line 2217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4880, 0},
+#line 462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4881, 0},
+#line 2442 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4882, 0},
+#line 5361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4883, 0},
+#line 5753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4884, 0},
+#line 3942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4885, 0},
+#line 3454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4886, 0},
+#line 4241 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4887, 0},
+#line 5741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4888, 0},
+#line 2110 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4889, 0},
+#line 91 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4890, 0},
+#line 3587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4891, 0},
+#line 3303 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4892, 0},
+#line 731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4893, 0},
+#line 418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4894, 0},
+#line 6015 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4895, 0},
+#line 4931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4896, 0},
+#line 2762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4897, 0},
+#line 4309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4898, 0},
+#line 1439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4899, 4},
+#line 3847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4900, 0},
+#line 5937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4901, 0},
+#line 5205 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4902, 0},
+#line 4221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4903, 0},
+#line 5298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4904, 0},
+#line 3956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4905, 0},
+#line 4219 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4906, 0},
+#line 4026 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4907, 0},
+#line 1101 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4908, 4},
+#line 3852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4909, 0},
+#line 2298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4910, 4},
+#line 4218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4911, 0},
+#line 3363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4912, 0},
+#line 5641 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4913, 0},
+#line 4459 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4914, 0},
+#line 2553 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4915, 0},
+#line 3524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4916, 0},
+#line 3648 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4917, 0},
+#line 366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4918, 0},
+#line 5526 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4919, 0},
+#line 2397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4920, 0},
+#line 5424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4921, 0},
+#line 3141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4922, 0},
+#line 5733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4923, 0},
+#line 5758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4924, 0},
+#line 1414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4925, 0},
+#line 2321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4926, 4},
+#line 5932 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4927, 0},
+#line 659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4928, 0},
+#line 5328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4929, 0},
+#line 3984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4930, 0},
+#line 1540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4931, 0},
+#line 652 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4932, 0},
+#line 5577 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4933, 0},
+#line 3338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4934, 0},
+#line 3268 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4935, 0},
+#line 396 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4936, 0},
+#line 4845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4937, 0},
+#line 625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4938, 0},
+#line 3636 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4939, 0},
+#line 5910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4940, 0},
+#line 5696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4941, 4},
+#line 480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4942, 4},
+#line 5970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4943, 0},
+#line 1800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4944, 0},
+#line 1251 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4945, 0},
+#line 3978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4946, 4},
+#line 392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4947, 4},
+#line 948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4948, 0},
+#line 3826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4949, 0},
+#line 2395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4950, 4},
+#line 904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4951, 0},
+#line 126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4952, 0},
+#line 4813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4953, 0},
+#line 5751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4954, 0},
+#line 5515 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4955, 0},
+#line 5953 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4956, 0},
+#line 2324 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4957, 4},
+#line 4288 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4958, 0},
+#line 2410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4959, 0},
+#line 2889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4960, 0},
+#line 1858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4961, 0},
+#line 2558 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4962, 0},
+#line 2198 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4963, 0},
+#line 2595 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4964, 0},
+#line 2774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4965, 0},
+#line 4044 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4966, 0},
+#line 6112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4967, 0},
+#line 1366 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4968, 0},
+#line 3983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4969, 0},
+#line 5714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4970, 0},
+#line 2116 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4971, 0},
+#line 5699 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4972, 0},
+#line 5928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4973, 0},
+#line 668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4974, 0},
+#line 4341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4975, 0},
+#line 2737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4976, 0},
+#line 2115 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4977, 0},
+#line 536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4978, 0},
+#line 5418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4979, 0},
+#line 1030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4980, 0},
+#line 1325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4981, 0},
+#line 3614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4982, 0},
+#line 5939 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4983, 0},
+#line 2763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4984, 0},
+#line 1867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4985, 0},
+#line 3843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4986, 0},
+#line 4860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4987, 0},
+#line 2434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4988, 0},
+#line 2114 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4989, 0},
+#line 5746 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4990, 0},
+#line 4276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4991, 0},
+#line 5909 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4992, 0},
+#line 5804 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4993, 0},
+#line 474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4994, 0},
+#line 5551 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4995, 0},
+#line 5954 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4996, 0},
+#line 2573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4997, 0},
+#line 6068 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4998, 0},
+#line 2743 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str4999, 0},
+#line 4242 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5000, 0},
+#line 619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5001, 0},
+#line 5080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5002, 0},
+#line 3545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5003, 0},
+#line 3332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5004, 0},
+#line 1845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5005, 0},
+#line 5128 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5006, 0},
+#line 4628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5007, 0},
+#line 3904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5008, 0},
+#line 2970 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5009, 0},
+#line 6035 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5010, 0},
+#line 3617 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5011, 0},
+#line 4005 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5012, 0},
+#line 2214 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5013, 0},
+#line 677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5014, 0},
+#line 2879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5015, 0},
+#line 2686 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5016, 0},
+#line 5759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5017, 0},
+#line 2285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5018, 0},
+#line 1119 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5019, 4},
+#line 5556 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5020, 0},
+#line 3262 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5021, 0},
+#line 6021 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5022, 0},
+#line 5963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5023, 0},
+#line 2403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5024, 0},
+#line 2078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5025, 0},
+#line 3391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5026, 0},
+#line 2185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5027, 4},
+#line 4938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5028, 0},
+#line 5280 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5029, 0},
+#line 4249 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5030, 0},
+#line 2333 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5031, 4},
+#line 6047 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5032, 2},
+#line 3787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5033, 0},
+#line 5665 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5034, 0},
+#line 3785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5035, 0},
+#line 5744 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5036, 0},
+#line 4874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5037, 0},
+#line 1576 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5038, 0},
+#line 5008 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5039, 0},
+#line 2905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5040, 0},
+#line 3309 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5041, 0},
+#line 679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5042, 0},
+#line 4311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5043, 0},
+#line 2628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5044, 0},
+#line 3870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5045, 0},
+#line 3537 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5046, 1},
+#line 4797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5047, 0},
+#line 4937 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5048, 0},
+#line 2784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5049, 0},
+#line 326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5050, 0},
+#line 120 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5051, 0},
+#line 2215 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5052, 0},
+#line 3925 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5053, 0},
+#line 5805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5054, 0},
+#line 3863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5055, 1},
+#line 3392 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5056, 0},
+#line 3673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5057, 1},
+#line 1552 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5058, 0},
+#line 4668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5059, 0},
+#line 707 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5060, 0},
+#line 5585 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5061, 0},
+#line 5451 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5062, 0},
+#line 210 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5063, 0},
+#line 5927 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5064, 0},
+#line 239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5065, 0},
+#line 2616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5066, 0},
+#line 2599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5067, 0},
+#line 5142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5068, 0},
+#line 639 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5069, 0},
+#line 299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5070, 0},
+#line 2370 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5071, 4},
+#line 3928 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5072, 0},
+#line 4965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5073, 0},
+#line 1533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5074, 0},
+#line 2769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5075, 2},
+#line 672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5076, 0},
+#line 4071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5077, 0},
+#line 3619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5078, 0},
+#line 4654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5079, 0},
+#line 2627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5080, 0},
+#line 5754 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5081, 0},
+#line 3975 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5082, 0},
+#line 3394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5083, 0},
+#line 3408 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5084, 0},
+#line 3681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5085, 0},
+#line 3680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5086, 0},
+#line 2335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5087, 4},
+#line 2336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5088, 4},
+#line 5443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5089, 0},
+#line 4424 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5090, 0},
+#line 2658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5091, 0},
+#line 5536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5092, 0},
+#line 2838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5093, 0},
+#line 2657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5094, 0},
+#line 1090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5095, 0},
+#line 3621 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5096, 0},
+#line 4942 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5097, 0},
+#line 2181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5098, 0},
+#line 5984 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5099, 0},
+#line 2439 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5100, 0},
+#line 2379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5101, 0},
+#line 3897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5102, 0},
+#line 5250 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5103, 0},
+#line 4254 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5104, 0},
+#line 5747 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5105, 0},
+#line 5563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5106, 0},
+#line 3943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5107, 0},
+#line 2582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5108, 0},
+#line 5716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5109, 4},
+#line 2977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5110, 0},
+#line 5466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5111, 0},
+#line 1092 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5112, 0},
+#line 2557 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5113, 0},
+#line 2795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5114, 0},
+#line 5090 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5115, 0},
+#line 2829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5116, 0},
+#line 2169 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5117, 0},
+#line 2292 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5118, 0},
+#line 5285 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5119, 0},
+#line 1466 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5120, 0},
+#line 2590 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5121, 0},
+#line 2361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5122, 4},
+#line 2454 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5123, 0},
+#line 5312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5124, 0},
+#line 4669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5125, 0},
+#line 3835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5126, 0},
+#line 587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5127, 4},
+#line 5363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5128, 0},
+#line 5420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5129, 0},
+#line 2444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5130, 0},
+#line 3337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5131, 0},
+#line 1959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5132, 0},
+#line 3900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5133, 0},
+#line 3513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5134, 0},
+#line 5763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5135, 0},
+#line 4531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5136, 4},
+#line 5952 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5137, 0},
+#line 784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5138, 0},
+#line 4727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5139, 0},
+#line 3536 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5140, 0},
+#line 5111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5141, 0},
+#line 2679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5142, 0},
+#line 6081 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5143, 0},
+#line 2157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5144, 0},
+#line 1889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5145, 0},
+#line 4749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5146, 0},
+#line 3527 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5147, 4},
+#line 6080 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5148, 0},
+#line 4156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5149, 0},
+#line 3164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5150, 0},
+#line 4155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5151, 0},
+#line 4160 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5152, 0},
+#line 4154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5153, 0},
+#line 3674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5154, 0},
+#line 3563 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5155, 0},
+#line 5154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5156, 0},
+#line 5509 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5157, 0},
+#line 2760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5158, 0},
+#line 5934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5159, 0},
+#line 4859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5160, 0},
+#line 5739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5161, 0},
+#line 1136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5162, 4},
+#line 4294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5163, 0},
+#line 4858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5164, 0},
+#line 3535 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5165, 0},
+#line 4321 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5166, 0},
+#line 5423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5167, 0},
+#line 5955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5168, 0},
+#line 3544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5169, 0},
+#line 4096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5170, 0},
+#line 4157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5171, 0},
+#line 5426 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5172, 0},
+#line 5951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5173, 0},
+#line 5465 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5174, 0},
+#line 629 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5175, 0},
+#line 2083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5176, 0},
+#line 750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5177, 0},
+#line 2022 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5178, 0},
+#line 2778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5179, 0},
+#line 6041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5180, 0},
+#line 3918 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5181, 0},
+#line 2438 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5182, 0},
+#line 5697 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5183, 4},
+#line 753 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5184, 0},
+#line 2409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5185, 0},
+#line 2437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5186, 0},
+#line 2276 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5187, 0},
+#line 5687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5188, 0},
+#line 1108 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5189, 4},
+#line 2647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5190, 0},
+#line 3644 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5191, 0},
+#line 3583 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5192, 0},
+#line 5548 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5193, 0},
+#line 3377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5194, 0},
+#line 3149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5195, 0},
+#line 4719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5196, 0},
+#line 66 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5197, 0},
+#line 4266 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5198, 0},
+#line 4248 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5199, 0},
+#line 3582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5200, 0},
+#line 649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5201, 0},
+#line 4180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5202, 0},
+#line 2852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5203, 0},
+#line 5677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5204, 0},
+#line 3647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5205, 0},
+#line 4760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5206, 0},
+#line 3963 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5207, 0},
+#line 4039 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5208, 0},
+#line 1988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5209, 0},
+#line 3675 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5210, 0},
+#line 3931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5211, 0},
+#line 3615 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5212, 0},
+#line 5603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5213, 0},
+#line 5960 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5214, 0},
+#line 2311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5215, 4},
+#line 4851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5216, 0},
+#line 3883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5217, 0},
+#line 3645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5218, 0},
+#line 1335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5219, 0},
+#line 55 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5220, 0},
+#line 5499 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5221, 0},
+#line 3995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5222, 0},
+#line 1916 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5223, 0},
+#line 4164 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5224, 0},
+#line 550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5225, 0},
+#line 4161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5226, 0},
+#line 3657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5227, 1},
+#line 3899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5228, 0},
+#line 5587 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5229, 0},
+#line 4166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5230, 0},
+#line 2575 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5231, 0},
+#line 4163 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5232, 0},
+#line 4162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5233, 0},
+#line 3731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5234, 0},
+#line 3734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5235, 0},
+#line 3730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5236, 0},
+#line 5875 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5237, 0},
+#line 4866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5238, 0},
+#line 5645 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5239, 0},
+#line 2429 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5240, 0},
+#line 5135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5241, 0},
+#line 2420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5242, 0},
+#line 4820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5243, 0},
+#line 734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5244, 0},
+#line 4165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5245, 0},
+#line 4722 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5246, 0},
+#line 140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5247, 0},
+#line 5943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5248, 0},
+#line 5657 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5249, 0},
+#line 5087 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5250, 0},
+#line 4934 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5251, 0},
+#line 5750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5252, 0},
+#line 2099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5253, 4},
+#line 3732 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5254, 0},
+#line 1965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5255, 0},
+#line 1093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5256, 4},
+#line 5669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5257, 0},
+#line 6007 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5258, 0},
+#line 4158 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5259, 0},
+#line 2096 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5260, 4},
+#line 2345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5261, 4},
+#line 3538 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5262, 0},
+#line 31 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5263, 0},
+#line 2357 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5264, 4},
+#line 2678 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5265, 0},
+#line 2872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5266, 0},
+#line 400 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5267, 0},
+#line 4048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5268, 1},
+#line 5730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5269, 0},
+#line 3643 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5270, 0},
+#line 2342 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5271, 4},
+#line 1009 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5272, 0},
+#line 2297 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5273, 4},
+#line 4379 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5274, 0},
+#line 5908 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5275, 0},
+#line 2352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5276, 4},
+#line 5959 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5277, 0},
+#line 2811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5278, 0},
+#line 5566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5279, 0},
+#line 853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5280, 0},
+#line 4066 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5281, 0},
+#line 2143 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5282, 0},
+#line 3842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5283, 0},
+#line 4275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5284, 0},
+#line 2877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5285, 0},
+#line 4089 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5286, 0},
+#line 2329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5287, 4},
+#line 3751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5288, 0},
+#line 2042 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5289, 0},
+#line 3573 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5290, 0},
+#line 1776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5291, 0},
+#line 6071 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5292, 0},
+#line 1227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5293, 0},
+#line 2348 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5294, 4},
+#line 924 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5295, 0},
+#line 1568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5296, 0},
+#line 2687 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5297, 0},
+#line 380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5298, 0},
+#line 5480 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5299, 0},
+#line 2358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5300, 4},
+#line 3658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5301, 0},
+#line 3839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5302, 0},
+#line 2323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5303, 4},
+#line 5139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5304, 0},
+#line 3738 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5305, 0},
+#line 3735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5306, 0},
+#line 2353 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5307, 4},
+#line 4082 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5308, 0},
+#line 4474 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5309, 0},
+#line 49 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5310, 0},
+#line 4890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5311, 0},
+#line 3737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5312, 0},
+#line 5427 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5313, 0},
+#line 1031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5314, 0},
+#line 5708 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5315, 0},
+#line 4649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5316, 0},
+#line 1109 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5317, 4},
+#line 3736 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5318, 0},
+#line 5961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5319, 0},
+#line 6029 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5320, 0},
+#line 2404 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5321, 0},
+#line 5702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5322, 0},
+#line 293 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5323, 0},
+#line 5524 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5324, 4},
+#line 4185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5325, 0},
+#line 4269 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5326, 0},
+#line 4188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5327, 0},
+#line 3739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5328, 0},
+#line 5470 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5329, 0},
+#line 4135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5330, 0},
+#line 4138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5331, 0},
+#line 3848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5332, 0},
+#line 4136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5333, 0},
+#line 4184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5334, 0},
+#line 4192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5335, 0},
+#line 3611 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5336, 0},
+#line 173 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5337, 0},
+#line 5628 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5338, 0},
+#line 3165 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5339, 0},
+#line 5627 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5340, 0},
+#line 436 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5341, 0},
+#line 4220 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5342, 0},
+#line 3996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5343, 0},
+#line 5547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5344, 0},
+#line 4193 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5345, 0},
+#line 68 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5346, 0},
+#line 4142 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5347, 0},
+#line 4191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5348, 0},
+#line 4190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5349, 0},
+#line 1977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5350, 0},
+#line 4434 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5351, 0},
+#line 4141 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5352, 0},
+#line 4139 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5353, 0},
+#line 4444 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5354, 1},
+#line 3546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5355, 0},
+#line 2325 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5356, 4},
+#line 4800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5357, 0},
+#line 4189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5358, 0},
+#line 92 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5359, 0},
+#line 2156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5360, 0},
+#line 1294 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5361, 4},
+#line 741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5362, 0},
+#line 2378 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5363, 0},
+#line 3301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5364, 0},
+#line 5472 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5365, 0},
+#line 3605 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5366, 0},
+#line 1868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5367, 0},
+#line 1125 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5368, 4},
+#line 5752 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5369, 0},
+#line 443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5370, 0},
+#line 5919 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5371, 0},
+#line 1340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5372, 4},
+#line 1991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5373, 0},
+#line 2880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5374, 0},
+#line 4178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5375, 0},
+#line 4383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5376, 0},
+#line 367 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5377, 0},
+#line 2315 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5378, 4},
+#line 3796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5379, 0},
+#line 5748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5380, 0},
+#line 2654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5381, 0},
+#line 2097 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5382, 4},
+#line 5724 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5383, 0},
+#line 676 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5384, 0},
+#line 2126 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5385, 0},
+#line 2393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5386, 0},
+#line 2260 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5387, 0},
+#line 2259 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5388, 0},
+#line 3886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5389, 0},
+#line 4419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5390, 0},
+#line 4351 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5391, 0},
+#line 1647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5392, 0},
+#line 3885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5393, 0},
+#line 2340 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5394, 4},
+#line 3902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5395, 0},
+#line 4390 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5396, 0},
+#line 3898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5397, 0},
+#line 3819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5398, 0},
+#line 2677 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5399, 0},
+#line 5569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5400, 0},
+#line 4421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5401, 0},
+#line 3625 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5402, 0},
+#line 5134 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5403, 0},
+#line 990 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5404, 0},
+#line 1606 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5405, 0},
+#line 3756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5406, 0},
+#line 3668 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5407, 0},
+#line 3758 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5408, 0},
+#line 2572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5409, 0},
+#line 3712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5410, 0},
+#line 4283 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5411, 0},
+#line 3713 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5412, 0},
+#line 3755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5413, 0},
+#line 3762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5414, 0},
+#line 5498 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5415, 0},
+#line 2168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5416, 0},
+#line 137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5417, 0},
+#line 3887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5418, 0},
+#line 4186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5419, 0},
+#line 4410 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5420, 0},
+#line 3786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5421, 0},
+#line 3763 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5422, 0},
+#line 4356 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5423, 0},
+#line 3717 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5424, 0},
+#line 4734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5425, 0},
+#line 4422 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5426, 0},
+#line 3761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5427, 0},
+#line 3760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5428, 0},
+#line 3767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5429, 0},
+#line 3716 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5430, 0},
+#line 3714 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5431, 0},
+#line 4098 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5432, 0},
+#line 637 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5433, 0},
+#line 4385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5434, 0},
+#line 3838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5435, 0},
+#line 4403 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5436, 0},
+#line 4256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5437, 0},
+#line 3759 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5438, 0},
+#line 4140 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5439, 0},
+#line 4360 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5440, 0},
+#line 5929 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5441, 0},
+#line 5568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5442, 0},
+#line 2660 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5443, 0},
+#line 4187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5444, 0},
+#line 2337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5445, 4},
+#line 5656 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5446, 0},
+#line 4137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5447, 0},
+#line 4443 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5448, 0},
+#line 3153 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5449, 0},
+#line 3805 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5450, 0},
+#line 5993 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5451, 0},
+#line 5666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5452, 0},
+#line 2326 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5453, 4},
+#line 2394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5454, 4},
+#line 2330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5455, 4},
+#line 4246 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5456, 0},
+#line 4314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5457, 1},
+#line 3078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5458, 0},
+#line 3749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5459, 0},
+#line 647 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5460, 0},
+#line 3607 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5461, 0},
+#line 2284 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5462, 0},
+#line 1619 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5463, 0},
+#line 5756 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5464, 0},
+#line 653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5465, 0},
+#line 3616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5466, 0},
+#line 3514 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5467, 0},
+#line 5874 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5468, 0},
+#line 2344 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5469, 4},
+#line 4750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5470, 4},
+#line 3571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5471, 0},
+#line 4967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5472, 0},
+#line 6019 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5473, 0},
+#line 3608 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5474, 0},
+#line 1940 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5475, 0},
+#line 5217 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5476, 0},
+#line 3495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5477, 0},
+#line 5988 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5478, 0},
+#line 75 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5479, 0},
+#line 991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5480, 0},
+#line 445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5481, 0},
+#line 5872 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5482, 0},
+#line 5923 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5483, 0},
+#line 2312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5484, 4},
+#line 2380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5485, 0},
+#line 5950 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5486, 0},
+#line 2201 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5487, 0},
+#line 3829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5488, 0},
+#line 2316 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5489, 4},
+#line 5567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5490, 0},
+#line 3797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5491, 0},
+#line 638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5492, 0},
+#line 4030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5493, 0},
+#line 5917 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5494, 0},
+#line 1529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5495, 0},
+#line 2152 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5496, 0},
+#line 2767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5497, 0},
+#line 5761 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5498, 0},
+#line 5808 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5499, 0},
+#line 4728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5500, 0},
+#line 2612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5501, 0},
+#line 4233 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5502, 0},
+#line 5471 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5503, 0},
+#line 2355 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5504, 4},
+#line 5987 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5505, 0},
+#line 3715 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5506, 0},
+#line 3664 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5507, 0},
+#line 1327 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5508, 0},
+#line 2172 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5509, 0},
+#line 3985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5510, 0},
+#line 4413 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5511, 0},
+#line 4159 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5512, 0},
+#line 5067 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5513, 0},
+#line 3547 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5514, 0},
+#line 2350 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5515, 4},
+#line 2487 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5516, 0},
+#line 3757 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5517, 0},
+#line 5421 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5518, 0},
+#line 2371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5519, 4},
+#line 4737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5520, 0},
+#line 4054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5521, 0},
+#line 3654 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5522, 0},
+#line 4420 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5523, 0},
+#line 4307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5524, 0},
+#line 1931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5525, 0},
+#line 5755 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5526, 0},
+#line 5936 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5527, 0},
+#line 2748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5528, 0},
+#line 4382 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5529, 0},
+#line 4354 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5530, 0},
+#line 5616 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5531, 0},
+#line 3951 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5532, 0},
+#line 5878 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5533, 0},
+#line 2282 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5534, 0},
+#line 2203 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5535, 0},
+#line 3469 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5536, 0},
+#line 3572 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5537, 0},
+#line 5181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5538, 0},
+#line 2043 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5539, 0},
+#line 2304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5540, 4},
+#line 5735 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5541, 0},
+#line 2698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5542, 0},
+#line 1374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5543, 0},
+#line 5533 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5544, 0},
+#line 5930 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5545, 0},
+#line 3187 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5546, 0},
+#line 2227 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5547, 0},
+#line 5679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5548, 0},
+#line 3903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5549, 0},
+#line 2151 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5550, 0},
+#line 2359 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5551, 4},
+#line 5202 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5552, 0},
+#line 5658 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5553, 0},
+#line 534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5554, 4},
+#line 5694 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5555, 4},
+#line 5914 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5556, 0},
+#line 4445 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5557, 0},
+#line 5313 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5558, 0},
+#line 3807 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5559, 0},
+#line 1943 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5560, 0},
+#line 4328 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5561, 0},
+#line 4243 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5562, 0},
+#line 4891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5563, 0},
+#line 4041 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5564, 0},
+#line 5712 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5565, 0},
+#line 5475 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5566, 0},
+#line 2369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5567, 4},
+#line 5991 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5568, 0},
+#line 3393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5569, 0},
+#line 5809 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5570, 0},
+#line 2314 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5571, 4},
+#line 5495 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5572, 0},
+#line 4453 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5573, 0},
+#line 2272 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5574, 0},
+#line 5544 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5575, 0},
+#line 1531 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5576, 0},
+#line 4168 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5577, 0},
+#line 1237 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5578, 0},
+#line 5913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5579, 0},
+#line 2271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5580, 0},
+#line 933 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5581, 0},
+#line 2188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5582, 0},
+#line 6033 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5583, 0},
+#line 3181 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5584, 0},
+#line 4412 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5585, 0},
+#line 5301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5586, 0},
+#line 4177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5587, 0},
+#line 6003 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5588, 0},
+#line 3733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5589, 0},
+#line 727 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5590, 0},
+#line 3947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5591, 4},
+#line 4265 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5592, 0},
+#line 4409 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5593, 0},
+#line 2299 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5594, 4},
+#line 2955 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5595, 0},
+#line 4892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5596, 0},
+#line 5670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5597, 0},
+#line 3830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5598, 0},
+#line 2154 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5599, 0},
+#line 1304 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5600, 0},
+#line 155 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5601, 0},
+#line 3435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5602, 0},
+#line 5912 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5603, 0},
+#line 992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5604, 0},
+#line 3831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5605, 0},
+#line 3828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5606, 0},
+#line 5704 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5607, 0},
+#line 3815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5608, 1},
+#line 6083 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5609, 0},
+#line 2810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5610, 0},
+#line 2384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5611, 0},
+#line 1679 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5612, 0},
+#line 730 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5613, 0},
+#line 5981 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5614, 0},
+#line 2307 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5615, 4},
+#line 5681 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5616, 0},
+#line 3381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5617, 0},
+#line 5742 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5618, 0},
+#line 3177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5619, 0},
+#line 2580 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5620, 0},
+#line 4696 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5621, 0},
+#line 2669 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5622, 0},
+#line 4976 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5623, 0},
+#line 3906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5624, 0},
+#line 5662 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5625, 0},
+#line 2816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5626, 0},
+#line 5393 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5627, 0},
+#line 5978 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5628, 0},
+#line 4386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5629, 0},
+#line 4997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5630, 0},
+#line 3192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5631, 0},
+#line 2239 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5632, 0},
+#line 5938 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5633, 0},
+#line 5689 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5634, 0},
+#line 5931 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5635, 0},
+#line 4666 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5636, 0},
+#line 3806 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5637, 0},
+#line 4318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5638, 0},
+#line 2174 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5639, 0},
+#line 3256 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5640, 0},
+#line 1383 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5641, 0},
+#line 334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5642, 0},
+#line 4768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5643, 0},
+#line 3741 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5644, 0},
+#line 2460 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5645, 0},
+#line 2258 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5646, 0},
+#line 157 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5647, 0},
+#line 2388 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5648, 0},
+#line 6100 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5649, 0},
+#line 3748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5650, 0},
+#line 783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5651, 0},
+#line 6073 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5652, 0},
+#line 4352 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5653, 0},
+#line 3104 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5654, 0},
+#line 1373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5655, 0},
+#line 4194 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5656, 0},
+#line 5502 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5657, 0},
+#line 1848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5658, 0},
+#line 6093 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5659, 0},
+#line 3384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5660, 0},
+#line 5879 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5661, 0},
+#line 3178 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5662, 0},
+#line 5683 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5663, 0},
+#line 70 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5664, 0},
+#line 5968 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5665, 0},
+#line 5672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5666, 0},
+#line 5698 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5667, 0},
+#line 2224 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5668, 0},
+#line 2275 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5669, 0},
+#line 5966 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5670, 0},
+#line 2144 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5671, 0},
+#line 5957 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5672, 0},
+#line 3604 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5673, 0},
+#line 4317 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5674, 0},
+#line 1311 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5675, 4},
+#line 2277 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5676, 0},
+#line 5737 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5677, 0},
+#line 5969 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5678, 0},
+#line 3825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5679, 0},
+#line 728 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5680, 0},
+#line 5695 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5681, 4},
+#line 3612 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5682, 0},
+#line 5967 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5683, 0},
+#line 5070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5684, 0},
+#line 5885 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5685, 0},
+#line 4416 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5686, 0},
+#line 1626 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5687, 0},
+#line 5506 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5688, 0},
+#line 4417 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5689, 0},
+#line 5192 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5690, 0},
+#line 2274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5691, 0},
+#line 6048 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5692, 0},
+#line 4338 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5693, 0},
+#line 3386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5694, 0},
+#line 3653 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5695, 0},
+#line 3559 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5696, 0},
+#line 5740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5697, 0},
+#line 5880 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5698, 0},
+#line 2671 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5699, 0},
+#line 4274 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5700, 0},
+#line 2457 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5701, 0},
+#line 3922 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5702, 0},
+#line 5156 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5703, 0},
+#line 3190 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5704, 0},
+#line 4570 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5705, 0},
+#line 2263 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5706, 0},
+#line 4310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5707, 0},
+#line 5691 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5708, 0},
+#line 5493 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5709, 0},
+#line 3603 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5710, 0},
+#line 2376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5711, 4},
+#line 5768 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5712, 0},
+#line 3162 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5713, 0},
+#line 2363 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5714, 4},
+#line 1361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5715, 0},
+#line 4302 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5716, 0},
+#line 3764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5717, 0},
+#line 3718 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5718, 0},
+#line 2820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5719, 0},
+#line 6095 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5720, 0},
+#line 5332 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5721, 0},
+#line 2374 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5722, 4},
+#line 3182 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5723, 0},
+#line 1289 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5724, 0},
+#line 4074 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5725, 0},
+#line 1541 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5726, 0},
+#line 3137 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5727, 0},
+#line 2377 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5728, 0},
+#line 2375 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5729, 4},
+#line 279 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5730, 0},
+#line 5798 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5731, 0},
+#line 3913 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5732, 0},
+#line 2659 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5733, 0},
+#line 1530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5734, 0},
+#line 3516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5735, 0},
+#line 3185 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5736, 0},
+#line 3622 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5737, 0},
+#line 5494 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5738, 0},
+#line 3567 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5739, 0},
+#line 2826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5740, 0},
+#line 2910 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5741, 0},
+#line 2411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5742, 0},
+#line 4414 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5743, 0},
+#line 2386 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5744, 0},
+#line 5980 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5745, 0},
+#line 5318 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5746, 0},
+#line 3566 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5747, 0},
+#line 5700 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5748, 0},
+#line 1706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5749, 0},
+#line 5546 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5750, 0},
+#line 5661 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5751, 0},
+#line 3801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5752, 0},
+#line 5997 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5753, 0},
+#line 4339 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5754, 0},
+#line 5877 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5755, 0},
+#line 3188 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5756, 0},
+#line 4441 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5757, 0},
+#line 2261 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5758, 0},
+#line 4384 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5759, 0},
+#line 5734 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5760, 0},
+#line 3565 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5761, 0},
+#line 5550 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5762, 0},
+#line 5462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5763, 0},
+#line 5685 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5764, 0},
+#line 5711 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5765, 0},
+#line 6027 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5766, 0},
+#line 5956 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5767, 0},
+#line 3387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5768, 0},
+#line 3857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5769, 0},
+#line 4347 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5770, 0},
+#line 5684 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5771, 0},
+#line 6099 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5772, 0},
+#line 5965 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5773, 0},
+#line 278 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5774, 0},
+#line 6023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5775, 0},
+#line 5799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5776, 0},
+#line 5773 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5777, 0},
+#line 6054 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5778, 0},
+#line 3649 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5779, 0},
+#line 4402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5780, 0},
+#line 2204 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5781, 4},
+#line 5770 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5782, 0},
+#line 4944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5783, 0},
+#line 5800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5784, 0},
+#line 4387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5785, 0},
+#line 5112 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5786, 0},
+#line 5673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5787, 0},
+#line 2235 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5788, 0},
+#line 3184 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5789, 0},
+#line 2057 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5790, 0},
+#line 6062 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5791, 0},
+#line 3341 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5792, 0},
+#line 5485 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5793, 0},
+#line 3111 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5794, 0},
+#line 5994 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5795, 0},
+#line 3186 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5796, 0},
+#line 4411 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5797, 0},
+#line 5771 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5798, 0},
+#line 3452 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5799, 0},
+#line 5793 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5800, 0},
+#line 4896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5801, 0},
+#line 4361 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5802, 0},
+#line 4702 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5803, 0},
+#line 3189 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5804, 0},
+#line 5983 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5805, 0},
+#line 5477 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5806, 0},
+#line 6028 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5807, 0},
+#line 2401 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5808, 0},
+#line 5729 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5809, 0},
+#line 4599 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5810, 4},
+#line 2389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5811, 0},
+#line 2175 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5812, 0},
+#line 2177 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5813, 0},
+#line 2264 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5814, 0},
+#line 6078 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5815, 0},
+#line 3191 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5816, 0},
+#line 2402 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5817, 0},
+#line 2670 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5818, 0},
+#line 6002 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5819, 0},
+#line 5574 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5820, 0},
+#line 5977 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5821, 0},
+#line 3613 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5822, 0},
+#line 5788 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5823, 0},
+#line 2273 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5824, 0},
+#line 6030 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5825, 0},
+#line 5719 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5826, 0},
+#line 5774 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5827, 0},
+#line 5709 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5828, 0},
+#line 5845 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5829, 0},
+#line 5777 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5830, 0},
+#line 4167 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5831, 0},
+#line 6000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5832, 0},
+#line 3640 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5833, 0},
+#line 4330 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5834, 0},
+#line 739 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5835, 0},
+#line 5881 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5836, 0},
+#line 4614 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5837, 4},
+#line 5772 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5838, 0},
+#line 2749 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5839, 0},
+#line 4308 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5840, 0},
+#line 5211 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5841, 0},
+#line 3568 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5842, 0},
+#line 5534 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5843, 0},
+#line 5680 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5844, 0},
+#line 5792 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5845, 0},
+#line 682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5846, 0},
+#line 5998 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5847, 0},
+#line 5674 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5848, 0},
+#line 3638 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5849, 1},
+#line 4376 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5850, 0},
+#line 5463 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5851, 0},
+#line 3800 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5852, 0},
+#line 4271 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5853, 0},
+#line 4394 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5854, 0},
+#line 5898 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5855, 0},
+#line 4023 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5856, 0},
+#line 5778 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5857, 0},
+#line 4298 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5858, 0},
+#line 5843 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5859, 0},
+#line 4345 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5860, 0},
+#line 5769 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5861, 0},
+#line 5889 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5862, 0},
+#line 5492 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5863, 0},
+#line 5996 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5864, 0},
+#line 3740 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5865, 0},
+#line 5890 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5866, 0},
+#line 5897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5867, 0},
+#line 6055 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5868, 0},
+#line 2849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5869, 0},
+#line 2362 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5870, 4},
+#line 2385 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5871, 0},
+#line 5212 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5872, 0},
+#line 2305 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5873, 4},
+#line 5797 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5874, 0},
+#line 4897 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5875, 0},
+#line 5992 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5876, 0},
+#line 4461 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5877, 0},
+#line 5935 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5878, 0},
+#line 1582 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5879, 0},
+#line 5861 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5880, 0},
+#line 5844 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5881, 0},
+#line 71 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5882, 0},
+#line 2733 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5883, 0},
+#line 2407 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5884, 0},
+#line 5896 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5885, 0},
+#line 4437 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5886, 0},
+#line 2161 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5887, 0},
+#line 4440 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5888, 0},
+#line 4372 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5889, 0},
+#line 6001 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5890, 0},
+#line 3166 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5891, 0},
+#line 3623 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5892, 0},
+#line 5893 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5893, 0},
+#line 5490 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5894, 0},
+#line 4329 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5895, 0},
+#line 2232 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5896, 0},
+#line 5851 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5897, 0},
+#line 5525 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5898, 0},
+#line 5870 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5899, 0},
+#line 3517 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5900, 0},
+#line 4389 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5901, 0},
+#line 5860 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5902, 0},
+#line 3646 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5903, 0},
+#line 5766 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5904, 0},
+#line 2423 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5905, 0},
+#line 5803 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5906, 0},
+#line 5765 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5907, 0},
+#line 5136 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5908, 0},
+#line 3179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5909, 0},
+#line 3871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5910, 0},
+#line 5995 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5911, 0},
+#line 2672 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5912, 0},
+#line 5986 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5913, 0},
+#line 4346 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5914, 0},
+#line 2306 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5915, 4},
+#line 4369 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5916, 0},
+#line 5868 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5917, 0},
+#line 4462 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5918, 0},
+#line 5802 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5919, 0},
+#line 4751 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5920, 4},
+#line 5944 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5921, 0},
+#line 5902 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5922, 0},
+#line 5513 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5923, 0},
+#line 2663 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5924, 0},
+#line 5895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5925, 0},
+#line 5945 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5926, 0},
+#line 5862 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5927, 0},
+#line 5849 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5928, 0},
+#line 2571 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5929, 0},
+#line 5847 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5930, 0},
+#line 2291 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5931, 0},
+#line 5903 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5932, 0},
+#line 5682 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5933, 0},
+#line 5891 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5934, 0},
+#line 5850 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5935, 0},
+#line 5848 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5936, 0},
+#line 4253 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5937, 0},
+#line 5901 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5938, 0},
+#line 5790 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5939, 0},
+#line 748 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5940, 0},
+#line 4179 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5941, 0},
+#line 5510 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5942, 0},
+#line 5846 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5943, 0},
+#line 5867 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5944, 0},
+#line 5999 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5945, 0},
+#line 5529 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5946, 0},
+#line 5479 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5947, 0},
+#line 5852 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5948, 0},
+#line 4395 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5949, 0},
+#line 2218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5950, 0},
+#line 4000 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5951, 0},
+#line 3135 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5952, 0},
+#line 2373 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5953, 4},
+#line 5876 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5954, 0},
+#line 5767 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5955, 0},
+#line 3948 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5956, 0},
+#line 2146 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5957, 0},
+#line 5899 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5958, 0},
+#line 3183 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5959, 0},
+#line 4435 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5960, 0},
+#line 2138 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5961, 4},
+#line 3750 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5962, 0},
+#line 5814 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5963, 0},
+#line 5815 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5964, 0},
+#line 3540 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5965, 0},
+#line 4596 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5966, 4},
+#line 4458 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5967, 0},
+#line 5530 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5968, 0},
+#line 4337 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5969, 0},
+#line 5947 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5970, 0},
+#line 5764 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5971, 0},
+#line 4895 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5972, 0},
+#line 4597 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5973, 4},
+#line 4415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5974, 0},
+#line 4418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5975, 0},
+#line 5706 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5976, 0},
+#line 5794 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5977, 0},
+#line 5819 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5978, 0},
+#line 5801 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5979, 0},
+#line 5842 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5980, 0},
+#line 5863 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5981, 0},
+#line 4397 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5982, 0},
+#line 4336 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5983, 0},
+#line 2419 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5984, 0},
+#line 4358 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5985, 0},
+#line 3569 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5986, 0},
+#line 5886 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5987, 0},
+#line 5812 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5988, 0},
+#line 5791 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5989, 0},
+#line 5710 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5990, 0},
+#line 5835 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5991, 0},
+#line 3564 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5992, 0},
+#line 4598 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5993, 4},
+#line 5831 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5994, 0},
+#line 4335 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5995, 0},
+#line 5811 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5996, 0},
+#line 5775 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5997, 0},
+#line 4334 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5998, 0},
+#line 5818 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str5999, 0},
+#line 5760 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6000, 0},
+#line 5883 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6001, 0},
+#line 5887 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6002, 0},
+#line 2387 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6003, 0},
+#line 5795 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6004, 0},
+#line 5543 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6005, 0},
+#line 5839 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6006, 0},
+#line 5838 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6007, 0},
+#line 5731 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6008, 0},
+#line 4323 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6009, 0},
+#line 218 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6010, 4},
+#line 2415 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6011, 0},
+#line 6032 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6012, 0},
+#line 6070 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6013, 0},
+#line 5841 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6014, 0},
+#line 2310 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6015, 4},
+#line 5810 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6016, 0},
+#line 5785 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6017, 0},
+#line 2149 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6018, 0},
+#line 5825 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6019, 0},
+#line 5816 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6020, 0},
+#line 5813 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6021, 0},
+#line 5884 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6022, 0},
+#line 5853 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6023, 0},
+#line 5946 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6024, 0},
+#line 5827 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6025, 0},
+#line 1961 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6026, 0},
+#line 2673 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6027, 0},
+#line 2150 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6028, 0},
+#line 5789 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6029, 0},
+#line 5787 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6030, 0},
+#line 3855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6031, 0},
+#line 5892 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6032, 0},
+#line 5834 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6033, 0},
+#line 3650 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6034, 0},
+#line 5783 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6035, 0},
+#line 5784 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6036, 0},
+#line 6018 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6037, 0},
+#line 5830 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6038, 0},
+#line 5820 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6039, 0},
+#line 5776 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6040, 0},
+#line 5904 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6041, 0},
+#line 5782 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6042, 0},
+#line 5854 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6043, 0},
+#line 5828 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6044, 0},
+#line 5856 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6045, 0},
+#line 2949 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6046, 0},
+#line 5864 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6047, 0},
+#line 5865 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6048, 0},
+#line 4312 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6049, 0},
+#line 4380 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6050, 4},
+#line 5979 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6051, 0},
+#line 5829 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6052, 0},
+#line 5858 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6053, 0},
+#line 5836 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6054, 0},
+#line 6031 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6055, 0},
+#line 2418 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6056, 0},
+#line 5900 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6057, 0},
+#line 5894 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6058, 0},
+#line 5786 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6059, 0},
+#line 5781 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6060, 0},
+#line 4371 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6061, 0},
+#line 2221 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6062, 0},
+#line 2148 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6063, 0},
+#line 5779 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6064, 0},
+#line 5869 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6065, 0},
+#line 4319 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6066, 0},
+#line 5516 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6067, 0},
+#line 5855 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6068, 0},
+#line 5866 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6069, 0},
+#line 4381 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6070, 4},
+#line 5762 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6071, 0},
+#line 5882 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6072, 0},
+#line 5837 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6073, 0},
+#line 3180 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6074, 0},
+#line 5832 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6075, 0},
+#line 4301 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6076, 0},
+#line 5833 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6077, 0},
+#line 5821 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6078, 0},
+#line 5780 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6079, 0},
+#line 5905 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6080, 0},
+#line 2281 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6081, 0},
+#line 5796 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6082, 0},
+#line 5822 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6083, 0},
+#line 5906 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6084, 0},
+#line 5840 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6085, 0},
+#line 3799 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6086, 0},
+#line 4391 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6087, 0},
+#line 2364 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6088, 4},
+#line 5826 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6089, 0},
+#line 2147 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6090, 0},
+#line 5859 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6091, 0},
+#line 5824 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6092, 0},
+#line 5817 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6093, 0},
+#line 5985 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6094, 0},
+#line 2200 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6095, 0},
+#line 5857 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6096, 0},
+#line 5888 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6097, 0},
+#line 5871 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6098, 0},
+#line 5496 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6099, 0},
+#line 5823 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6100, 0},
+#line 5545 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6101, 0},
+#line 5464 "effective_tld_names.gperf"
+ {(int)(long)&((struct stringpool_t *)0)->stringpool_str6102, 0}
+ };
+
+ static const short lookup[] =
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 0,
+ 1, 2, -1, -1, -1, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, -1, 19, 20,
+ 21, 22, -1, 23, -1, 24, 25, 26,
+ 27, -1, -1, -1, 28, 29, 30, 31,
+ 32, -1, -1, -1, 33, 34, 35, -1,
+ 36, 37, 38, 39, -1, -1, -1, -1,
+ -1, -1, 40, 41, -1, 42, 43, -1,
+ -1, 44, 45, 46, -1, 47, -1, -1,
+ -1, -1, 48, 49, 50, -1, 51, 52,
+ 53, 54, -1, 55, 56, -1, -1, -1,
+ 57, 58, -1, -1, -1, -1, 59, 60,
+ -1, 61, 62, 63, 64, 65, 66, -1,
+ 67, 68, 69, 70, -1, -1, -1, -1,
+ 71, 72, 73, -1, -1, -1, 74, -1,
+ 75, -1, -1, -1, -1, 76, 77, 78,
+ 79, 80, -1, -1, 81, -1, 82, -1,
+ 83, -1, 84, -1, 85, -1, -1, 86,
+ 87, -1, -1, 88, 89, -1, 90, -1,
+ -1, -1, -1, 91, 92, -1, -1, 93,
+ 94, -1, 95, -1, 96, 97, -1, -1,
+ -1, -1, -1, 98, 99, -1, 100, 101,
+ -1, 102, 103, -1, 104, 105, 106, -1,
+ 107, -1, 108, -1, -1, 109, -1, 110,
+ 111, -1, 112, 113, -1, -1, 114, 115,
+ 116, -1, -1, -1, -1, -1, 117, -1,
+ -1, 118, -1, -1, -1, 119, 120, 121,
+ 122, -1, -1, 123, -1, -1, 124, -1,
+ -1, 125, -1, 126, -1, -1, -1, -1,
+ 127, 128, -1, -1, -1, 129, 130, -1,
+ -1, 131, -1, -1, 132, -1, -1, -1,
+ 133, -1, -1, -1, 134, -1, -1, -1,
+ -1, -1, 135, -1, -1, -1, 136, 137,
+ 138, 139, -1, 140, 141, -1, -1, -1,
+ -1, 142, -1, 143, -1, -1, -1, -1,
+ 144, 145, 146, -1, 147, -1, -1, -1,
+ -1, -1, 148, 149, -1, -1, -1, -1,
+ 150, 151, -1, 152, -1, -1, -1, -1,
+ 153, -1, -1, -1, 154, -1, 155, 156,
+ 157, 158, 159, 160, 161, -1, 162, -1,
+ -1, -1, 163, -1, -1, -1, -1, 164,
+ -1, -1, -1, 165, 166, -1, 167, 168,
+ 169, 170, -1, 171, -1, 172, 173, 174,
+ -1, 175, -1, 176, -1, 177, 178, -1,
+ 179, 180, -1, 181, 182, 183, -1, -1,
+ 184, 185, -1, -1, -1, -1, -1, 186,
+ -1, -1, -1, -1, -1, -1, 187, 188,
+ -1, -1, 189, -1, -1, -1, 190, -1,
+ -1, -1, -1, -1, 191, -1, 192, 193,
+ 194, 195, -1, -1, 196, -1, 197, -1,
+ 198, -1, 199, -1, -1, 200, -1, -1,
+ -1, -1, -1, -1, 201, -1, 202, -1,
+ 203, -1, -1, -1, -1, -1, -1, 204,
+ 205, 206, 207, -1, -1, 208, -1, 209,
+ 210, 211, 212, 213, -1, 214, 215, -1,
+ -1, -1, 216, 217, 218, 219, 220, -1,
+ -1, 221, -1, 222, 223, -1, -1, -1,
+ -1, 224, 225, 226, 227, 228, 229, -1,
+ -1, -1, -1, 230, 231, -1, 232, -1,
+ -1, 233, -1, -1, -1, 234, -1, 235,
+ -1, 236, -1, 237, 238, -1, -1, -1,
+ 239, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 240, -1, -1, -1, -1, -1,
+ -1, -1, 241, 242, 243, -1, 244, -1,
+ 245, 246, -1, 247, -1, -1, -1, 248,
+ -1, 249, 250, -1, 251, -1, -1, -1,
+ -1, 252, -1, 253, 254, 255, -1, 256,
+ -1, -1, -1, 257, 258, -1, 259, 260,
+ -1, -1, -1, 261, -1, -1, -1, -1,
+ 262, 263, 264, -1, -1, 265, -1, -1,
+ 266, 267, -1, -1, 268, 269, 270, 271,
+ -1, -1, 272, -1, -1, -1, 273, 274,
+ -1, -1, -1, -1, -1, -1, 275, 276,
+ -1, 277, 278, 279, -1, -1, 280, -1,
+ 281, -1, 282, 283, -1, -1, 284, 285,
+ 286, 287, 288, -1, -1, -1, -1, 289,
+ 290, -1, 291, -1, -1, -1, 292, -1,
+ 293, -1, -1, -1, -1, -1, 294, -1,
+ -1, -1, -1, -1, -1, -1, 295, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 296, 297, -1, -1, 298, -1, 299,
+ 300, -1, 301, 302, 303, 304, -1, 305,
+ -1, 306, -1, -1, 307, 308, -1, -1,
+ -1, 309, 310, -1, -1, -1, 311, -1,
+ -1, 312, 313, -1, 314, -1, -1, -1,
+ -1, 315, -1, 316, 317, -1, -1, 318,
+ -1, -1, -1, -1, 319, -1, -1, -1,
+ -1, -1, -1, -1, -1, 320, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 321, -1, 322, 323, -1, 324, 325,
+ -1, -1, -1, -1, -1, -1, -1, 326,
+ -1, 327, 328, -1, -1, 329, -1, 330,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 331, -1, 332, 333, 334, -1, -1,
+ 335, 336, -1, -1, -1, 337, 338, -1,
+ -1, 339, -1, -1, 340, 341, 342, -1,
+ -1, 343, 344, 345, 346, 347, 348, 349,
+ 350, 351, 352, 353, 354, 355, 356, 357,
+ 358, 359, -1, 360, -1, -1, -1, -1,
+ 361, -1, -1, 362, -1, -1, 363, -1,
+ 364, -1, -1, -1, -1, 365, -1, -1,
+ -1, 366, -1, 367, -1, 368, -1, 369,
+ -1, 370, -1, -1, -1, 371, 372, -1,
+ 373, -1, -1, 374, -1, -1, -1, -1,
+ -1, -1, -1, 375, -1, 376, -1, -1,
+ -1, 377, 378, -1, -1, -1, -1, 379,
+ -1, 380, -1, 381, -1, -1, 382, -1,
+ 383, -1, 384, 385, 386, 387, -1, 388,
+ -1, -1, -1, -1, 389, -1, 390, -1,
+ -1, -1, 391, 392, 393, 394, 395, -1,
+ -1, -1, -1, 396, -1, -1, 397, 398,
+ -1, -1, -1, 399, 400, -1, -1, -1,
+ 401, -1, 402, -1, -1, 403, 404, 405,
+ 406, -1, -1, -1, 407, -1, -1, -1,
+ 408, -1, -1, 409, -1, -1, -1, -1,
+ 410, 411, -1, -1, -1, -1, -1, 412,
+ 413, 414, 415, -1, 416, -1, 417, 418,
+ -1, -1, -1, -1, -1, 419, -1, 420,
+ -1, -1, 421, 422, 423, 424, -1, 425,
+ -1, 426, 427, -1, -1, 428, 429, -1,
+ 430, -1, -1, 431, 432, -1, -1, -1,
+ 433, -1, 434, -1, -1, -1, -1, -1,
+ 435, -1, 436, -1, -1, 437, -1, -1,
+ -1, 438, -1, 439, 440, -1, -1, -1,
+ -1, -1, 441, 442, -1, -1, -1, -1,
+ -1, -1, -1, 443, -1, -1, -1, -1,
+ 444, -1, 445, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 446, -1, 447, 448,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 449, -1, -1, -1, -1,
+ -1, -1, -1, -1, 450, -1, -1, 451,
+ -1, 452, 453, -1, 454, -1, -1, -1,
+ 455, -1, -1, 456, -1, 457, -1, -1,
+ -1, -1, -1, 458, -1, 459, -1, -1,
+ -1, -1, 460, -1, -1, -1, -1, 461,
+ -1, 462, 463, -1, -1, -1, 464, -1,
+ -1, -1, -1, 465, -1, -1, -1, 466,
+ -1, 467, -1, -1, -1, -1, 468, 469,
+ -1, -1, 470, 471, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 472, -1, 473,
+ -1, -1, -1, 474, -1, -1, -1, -1,
+ -1, 475, 476, 477, -1, -1, -1, -1,
+ -1, 478, -1, -1, -1, -1, -1, 479,
+ -1, -1, -1, -1, -1, 480, -1, -1,
+ -1, -1, -1, 481, -1, -1, -1, 482,
+ 483, -1, 484, -1, -1, -1, -1, -1,
+ 485, -1, -1, -1, -1, -1, 486, 487,
+ -1, -1, -1, 488, -1, -1, -1, -1,
+ -1, 489, -1, 490, 491, 492, -1, 493,
+ 494, -1, 495, -1, -1, -1, 496, -1,
+ -1, -1, -1, 497, -1, -1, -1, -1,
+ -1, -1, -1, -1, 498, -1, -1, -1,
+ -1, -1, -1, -1, 499, 500, 501, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 502, 503, 504, -1, -1, -1, -1,
+ -1, -1, -1, 505, -1, 506, -1, -1,
+ 507, -1, 508, -1, 509, 510, 511, -1,
+ -1, 512, 513, -1, -1, -1, -1, -1,
+ -1, 514, -1, -1, 515, -1, 516, -1,
+ 517, -1, -1, 518, -1, -1, -1, -1,
+ 519, -1, -1, -1, 520, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 521, -1,
+ -1, -1, -1, 522, 523, 524, -1, -1,
+ -1, 525, -1, 526, 527, -1, 528, 529,
+ 530, -1, 531, -1, 532, -1, 533, -1,
+ -1, -1, -1, 534, 535, -1, -1, -1,
+ 536, -1, -1, -1, -1, -1, -1, 537,
+ -1, -1, -1, 538, -1, -1, -1, -1,
+ -1, -1, -1, -1, 539, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 540, -1, -1, -1, 541, 542,
+ -1, 543, -1, -1, -1, -1, -1, -1,
+ 544, -1, -1, -1, -1, 545, 546, -1,
+ -1, -1, 547, -1, -1, -1, -1, -1,
+ -1, 548, -1, -1, -1, -1, 549, -1,
+ -1, -1, -1, 550, -1, -1, -1, -1,
+ -1, -1, -1, -1, 551, -1, -1, -1,
+ -1, -1, -1, -1, 552, -1, -1, -1,
+ -1, 553, -1, -1, -1, -1, -1, 554,
+ -1, -1, -1, -1, -1, 555, -1, -1,
+ -1, 556, 557, -1, 558, -1, -1, -1,
+ 559, 560, -1, -1, -1, 561, 562, -1,
+ -1, -1, 563, -1, -1, 564, -1, -1,
+ -1, -1, -1, -1, 565, 566, 567, 568,
+ -1, 569, 570, -1, 571, -1, 572, -1,
+ 573, -1, -1, -1, 574, 575, -1, -1,
+ -1, -1, 576, -1, -1, -1, -1, -1,
+ -1, -1, 577, -1, 578, -1, -1, -1,
+ -1, 579, -1, -1, -1, 580, -1, -1,
+ -1, -1, -1, -1, 581, 582, -1, -1,
+ -1, -1, 583, -1, -1, -1, -1, -1,
+ -1, -1, -1, 584, -1, -1, -1, -1,
+ 585, 586, 587, -1, -1, 588, -1, -1,
+ -1, 589, -1, -1, 590, 591, -1, -1,
+ -1, -1, -1, -1, -1, 592, -1, -1,
+ -1, -1, -1, -1, 593, -1, -1, -1,
+ 594, -1, -1, -1, -1, -1, 595, 596,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 597, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 598, -1,
+ 599, -1, -1, 600, -1, -1, -1, -1,
+ -1, -1, -1, 601, -1, -1, 602, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 603, -1, -1, -1,
+ 604, 605, -1, -1, 606, -1, -1, -1,
+ -1, -1, -1, -1, -1, 607, -1, -1,
+ -1, -1, -1, 608, 609, -1, -1, -1,
+ -1, 610, 611, -1, -1, -1, -1, -1,
+ -1, 612, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 613, -1, -1, 614, -1,
+ -1, 615, -1, -1, 616, 617, -1, 618,
+ 619, -1, -1, 620, 621, 622, -1, 623,
+ -1, -1, -1, -1, -1, 624, 625, -1,
+ -1, -1, -1, 626, -1, -1, 627, 628,
+ 629, -1, -1, -1, -1, -1, -1, 630,
+ -1, -1, -1, -1, -1, -1, 631, -1,
+ -1, -1, 632, -1, -1, -1, 633, -1,
+ -1, -1, 634, -1, 635, -1, -1, -1,
+ 636, -1, -1, -1, -1, 637, -1, -1,
+ -1, -1, 638, -1, -1, -1, -1, 639,
+ -1, -1, 640, -1, -1, 641, -1, -1,
+ -1, -1, -1, -1, -1, 642, 643, -1,
+ 644, -1, -1, -1, -1, -1, -1, 645,
+ -1, -1, -1, 646, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 647,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 648, -1, 649, -1, -1, -1, -1,
+ -1, -1, 650, -1, -1, 651, -1, 652,
+ -1, -1, -1, 653, -1, -1, 654, -1,
+ -1, 655, -1, -1, -1, -1, -1, 656,
+ -1, -1, -1, -1, -1, 657, -1, 658,
+ 659, -1, -1, -1, -1, -1, -1, -1,
+ 660, -1, 661, 662, -1, 663, -1, 664,
+ 665, -1, -1, -1, -1, -1, 666, -1,
+ -1, 667, -1, -1, -1, -1, -1, -1,
+ 668, 669, 670, -1, -1, -1, -1, 671,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 672, -1, -1, -1, -1, 673, -1,
+ -1, -1, -1, -1, 674, -1, 675, 676,
+ -1, 677, 678, -1, -1, -1, -1, 679,
+ -1, -1, -1, 680, 681, -1, 682, -1,
+ -1, -1, -1, -1, -1, 683, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 684,
+ -1, -1, -1, 685, -1, -1, 686, -1,
+ -1, -1, 687, -1, -1, 688, -1, -1,
+ 689, -1, -1, -1, -1, -1, -1, 690,
+ 691, -1, 692, -1, -1, -1, 693, -1,
+ -1, -1, -1, -1, 694, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 695,
+ 696, -1, -1, -1, -1, 697, -1, -1,
+ 698, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 699, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 700,
+ -1, -1, -1, -1, 701, -1, -1, -1,
+ -1, -1, -1, -1, -1, 702, 703, -1,
+ -1, -1, -1, -1, -1, -1, 704, -1,
+ 705, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 706, 707, -1, -1, -1, -1,
+ -1, -1, -1, 708, -1, -1, -1, -1,
+ 709, -1, -1, 710, -1, -1, -1, 711,
+ -1, -1, 712, -1, -1, -1, -1, -1,
+ -1, -1, 713, 714, -1, -1, -1, -1,
+ -1, -1, 715, -1, -1, -1, -1, -1,
+ 716, -1, 717, 718, 719, -1, -1, 720,
+ 721, -1, -1, -1, -1, -1, 722, 723,
+ -1, -1, 724, -1, -1, -1, -1, -1,
+ -1, 725, -1, 726, -1, -1, 727, -1,
+ -1, 728, -1, -1, -1, -1, -1, 729,
+ -1, 730, -1, -1, -1, -1, 731, -1,
+ -1, 732, -1, -1, -1, -1, 733, -1,
+ -1, -1, -1, -1, 734, -1, -1, 735,
+ -1, -1, 736, -1, -1, -1, -1, -1,
+ -1, 737, -1, -1, -1, -1, -1, -1,
+ 738, -1, -1, 739, -1, -1, -1, -1,
+ -1, 740, -1, -1, -1, -1, 741, -1,
+ 742, -1, 743, 744, 745, 746, -1, -1,
+ -1, 747, 748, -1, 749, -1, 750, 751,
+ 752, -1, -1, 753, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 754, 755, -1,
+ 756, -1, -1, 757, 758, -1, -1, -1,
+ -1, -1, 759, -1, -1, -1, -1, 760,
+ -1, -1, -1, -1, -1, -1, -1, 761,
+ -1, -1, -1, -1, -1, -1, 762, -1,
+ -1, 763, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 764, -1, -1, -1,
+ -1, -1, 765, -1, 766, -1, 767, -1,
+ 768, -1, -1, -1, -1, -1, -1, -1,
+ -1, 769, -1, -1, -1, -1, -1, -1,
+ 770, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 771, -1, -1, -1, 772, -1, -1, 773,
+ -1, 774, -1, -1, -1, -1, -1, -1,
+ 775, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 776, -1, -1,
+ -1, -1, 777, -1, -1, -1, -1, -1,
+ -1, 778, -1, 779, -1, -1, 780, -1,
+ -1, -1, 781, -1, -1, -1, -1, -1,
+ -1, 782, 783, -1, -1, -1, 784, -1,
+ -1, -1, 785, 786, 787, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 788, -1,
+ -1, 789, 790, -1, -1, -1, -1, 791,
+ -1, -1, -1, -1, -1, 792, 793, -1,
+ -1, -1, -1, -1, -1, -1, 794, -1,
+ -1, -1, -1, 795, 796, 797, -1, 798,
+ -1, -1, -1, 799, -1, -1, -1, 800,
+ 801, 802, -1, -1, -1, -1, -1, -1,
+ -1, -1, 803, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 804, -1,
+ -1, -1, -1, -1, -1, -1, 805, -1,
+ -1, -1, -1, -1, -1, -1, -1, 806,
+ -1, 807, -1, -1, -1, -1, 808, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 809, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 810, -1, -1, -1,
+ -1, 811, -1, -1, -1, -1, -1, -1,
+ -1, 812, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 813, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 814, -1, -1, -1, -1,
+ -1, 815, -1, -1, -1, -1, 816, 817,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 818, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 819, -1, -1, 820,
+ -1, -1, 821, -1, -1, -1, -1, -1,
+ -1, -1, 822, -1, 823, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 824, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 825, 826, -1, -1,
+ -1, -1, -1, -1, 827, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 828, -1, -1, 829, -1, -1, -1,
+ -1, -1, 830, -1, -1, 831, -1, 832,
+ -1, -1, -1, -1, -1, -1, -1, 833,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 834, 835, 836, -1, -1, -1, -1,
+ -1, -1, -1, -1, 837, -1, 838, -1,
+ -1, -1, -1, -1, -1, -1, -1, 839,
+ -1, -1, 840, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 841, -1, 842,
+ -1, -1, -1, -1, -1, -1, -1, 843,
+ -1, -1, -1, -1, 844, -1, -1, -1,
+ -1, -1, -1, -1, -1, 845, -1, -1,
+ -1, -1, -1, 846, -1, 847, -1, -1,
+ -1, -1, 848, -1, -1, -1, -1, -1,
+ -1, -1, 849, -1, -1, -1, -1, -1,
+ -1, 850, 851, 852, -1, -1, -1, -1,
+ -1, -1, -1, 853, -1, -1, -1, 854,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 855,
+ -1, 856, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 857, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 858, -1,
+ 859, -1, -1, -1, -1, -1, 860, 861,
+ -1, -1, 862, -1, -1, -1, -1, 863,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 864, 865, 866, -1, -1,
+ -1, -1, 867, -1, 868, -1, -1, -1,
+ -1, 869, 870, -1, -1, 871, -1, -1,
+ -1, 872, -1, -1, 873, 874, 875, -1,
+ -1, -1, 876, -1, -1, -1, -1, -1,
+ -1, 877, 878, -1, -1, -1, -1, -1,
+ 879, -1, -1, -1, -1, 880, -1, -1,
+ -1, -1, -1, 881, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 882, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 883, 884, -1,
+ -1, -1, -1, 885, -1, -1, -1, 886,
+ -1, -1, -1, -1, 887, -1, -1, -1,
+ -1, -1, 888, -1, -1, -1, -1, -1,
+ 889, -1, -1, -1, -1, -1, -1, 890,
+ -1, -1, -1, -1, 891, -1, -1, -1,
+ 892, -1, -1, -1, -1, -1, 893, -1,
+ -1, -1, 894, -1, -1, -1, -1, -1,
+ -1, -1, -1, 895, 896, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 897,
+ -1, -1, -1, -1, -1, -1, 898, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 899, -1,
+ -1, 900, -1, -1, -1, -1, 901, -1,
+ 902, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 903, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 904, -1, -1, -1, -1, -1, -1, -1,
+ 905, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 906, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 907, -1, -1,
+ -1, -1, -1, -1, -1, -1, 908, -1,
+ -1, -1, -1, -1, 909, 910, 911, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 912, -1, -1, -1, -1, -1, -1, 913,
+ 914, -1, 915, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 916, -1, -1, -1,
+ 917, -1, -1, -1, -1, -1, -1, -1,
+ 918, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 919, -1, -1, -1, -1,
+ -1, -1, -1, 920, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 921,
+ -1, -1, -1, -1, -1, -1, 922, -1,
+ -1, -1, -1, -1, 923, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 924, -1, -1, -1,
+ -1, 925, -1, -1, -1, -1, -1, -1,
+ 926, -1, 927, 928, -1, -1, -1, -1,
+ -1, -1, -1, 929, 930, -1, -1, -1,
+ -1, -1, -1, 931, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 932, -1, -1, -1, -1, -1,
+ 933, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 934, 935, -1, -1, -1, -1, 936, -1,
+ 937, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 938, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 939, -1, -1, 940, -1, -1, -1, 941,
+ 942, -1, -1, 943, -1, 944, 945, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 946, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 947, -1, -1, 948, -1, -1, -1, -1,
+ 949, -1, -1, -1, -1, -1, 950, -1,
+ -1, 951, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 952, -1,
+ -1, -1, 953, 954, -1, -1, -1, -1,
+ -1, -1, -1, -1, 955, -1, -1, -1,
+ -1, 956, -1, -1, -1, 957, -1, -1,
+ -1, -1, -1, -1, 958, 959, 960, -1,
+ -1, 961, -1, 962, -1, -1, -1, 963,
+ -1, 964, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 965, -1, -1, 966, 967,
+ -1, -1, -1, 968, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 969, -1, 970, -1,
+ -1, 971, -1, 972, 973, -1, -1, -1,
+ -1, 974, -1, -1, -1, -1, -1, -1,
+ -1, 975, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 976, -1,
+ 977, -1, -1, -1, -1, -1, -1, -1,
+ 978, 979, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 980, -1, -1,
+ 981, -1, -1, -1, 982, -1, -1, 983,
+ -1, 984, -1, -1, -1, -1, -1, -1,
+ -1, 985, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 986, -1,
+ 987, -1, -1, 988, -1, -1, -1, -1,
+ -1, -1, -1, -1, 989, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 990, -1, 991, -1, -1, 992,
+ -1, -1, -1, 993, -1, 994, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 995, -1, -1,
+ -1, 996, -1, 997, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 998, 999, -1,
+ 1000, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1001,
+ 1002, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1003, -1, -1, -1, 1004, -1, -1, -1,
+ -1, -1, 1005, -1, -1, -1, -1, -1,
+ -1, -1, 1006, 1007, -1, 1008, -1, -1,
+ -1, 1009, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1010, -1,
+ -1, -1, -1, -1, -1, 1011, -1, -1,
+ -1, 1012, -1, -1, -1, -1, 1013, -1,
+ -1, -1, -1, -1, -1, -1, 1014, 1015,
+ 1016, 1017, -1, -1, -1, 1018, 1019, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1020, -1,
+ -1, 1021, -1, -1, -1, -1, 1022, -1,
+ 1023, -1, 1024, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1025, 1026, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1027, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1028,
+ 1029, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1030, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1031, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1032,
+ 1033, -1, -1, -1, 1034, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1035, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1036, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1037, -1, -1, -1,
+ -1, -1, -1, -1, 1038, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1039, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1040, -1, -1, -1, -1, -1, 1041,
+ 1042, -1, -1, -1, 1043, -1, -1, -1,
+ -1, -1, -1, 1044, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1045, -1, -1, -1,
+ -1, -1, -1, 1046, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1047, 1048, -1,
+ -1, -1, -1, -1, -1, 1049, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1050, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1051, -1, -1, -1, -1, -1, -1,
+ -1, 1052, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1053, -1, -1, 1054, -1, -1,
+ -1, 1055, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1056, -1, -1,
+ 1057, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1058, -1, -1, 1059, -1,
+ -1, -1, 1060, 1061, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1062, -1, -1, -1, 1063, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1064, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1065, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1066, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1067, -1, -1, -1, -1, -1, 1068,
+ -1, -1, 1069, -1, -1, -1, -1, -1,
+ -1, -1, 1070, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1071, -1, -1,
+ -1, 1072, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1073, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1074, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1075, -1, -1, -1, -1, -1, -1,
+ -1, 1076, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1077, -1, -1, -1, -1, -1,
+ -1, 1078, 1079, -1, -1, -1, 1080, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1081, -1, -1, -1, -1, -1, 1082, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1083,
+ -1, -1, -1, -1, -1, -1, 1084, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1085,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1086, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1087, -1, -1, -1, -1,
+ -1, 1088, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1089, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1090, 1091, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1092, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1093,
+ -1, -1, -1, -1, -1, -1, 1094, -1,
+ -1, -1, -1, -1, -1, 1095, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1096, -1, 1097, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1098, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1099, -1, 1100, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1101, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1102, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1103, -1, -1,
+ 1104, -1, -1, -1, -1, -1, -1, 1105,
+ -1, -1, -1, -1, -1, -1, 1106, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1107, -1, -1, -1, -1, 1108, -1, -1,
+ 1109, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1110, 1111,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1112, -1, -1, -1, -1,
+ -1, 1113, 1114, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1115, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1116, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1117,
+ -1, -1, -1, -1, -1, -1, 1118, -1,
+ -1, -1, -1, -1, -1, -1, 1119, 1120,
+ 1121, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1122, 1123, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1124, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1125, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1126, -1, -1, -1,
+ 1127, 1128, 1129, -1, -1, 1130, -1, 1131,
+ -1, 1132, -1, -1, -1, 1133, -1, -1,
+ -1, -1, 1134, -1, -1, -1, -1, -1,
+ -1, 1135, -1, 1136, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1137,
+ -1, -1, -1, 1138, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1139, -1, -1, -1, -1, 1140, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1141, 1142,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1143, -1, -1, -1, -1, -1,
+ 1144, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1145, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1146, -1, 1147, -1,
+ 1148, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1149, -1, 1150, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1151, -1, -1, -1,
+ 1152, -1, -1, 1153, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1154, -1, 1155, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1156, -1, -1, -1, -1, -1, 1157, -1,
+ -1, -1, -1, -1, 1158, -1, -1, -1,
+ -1, -1, 1159, -1, -1, -1, -1, -1,
+ 1160, -1, -1, -1, 1161, -1, -1, -1,
+ -1, -1, -1, 1162, -1, -1, -1, 1163,
+ -1, 1164, 1165, -1, -1, -1, 1166, -1,
+ -1, -1, -1, -1, -1, -1, 1167, -1,
+ -1, -1, -1, -1, -1, -1, 1168, 1169,
+ 1170, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1171, 1172, -1, 1173, -1,
+ -1, -1, -1, -1, 1174, -1, -1, -1,
+ -1, 1175, -1, -1, -1, -1, -1, 1176,
+ 1177, -1, -1, -1, -1, -1, -1, 1178,
+ -1, -1, -1, -1, -1, 1179, -1, -1,
+ -1, -1, -1, -1, 1180, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1181, -1,
+ -1, -1, 1182, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1183,
+ -1, -1, -1, -1, -1, -1, 1184, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1185, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1186, -1, -1,
+ -1, -1, -1, -1, -1, 1187, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1188, -1, -1, -1,
+ -1, 1189, -1, -1, -1, -1, -1, -1,
+ 1190, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1191,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1192, -1, -1, 1193, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1194,
+ -1, -1, -1, 1195, -1, -1, -1, 1196,
+ 1197, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1198, -1, -1, 1199, -1,
+ -1, -1, -1, 1200, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1201, -1, -1,
+ 1202, -1, -1, -1, -1, -1, 1203, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1204, 1205, -1, -1, 1206, -1,
+ -1, -1, -1, -1, -1, 1207, -1, 1208,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1209, -1, 1210, -1, 1211,
+ -1, -1, -1, 1212, -1, -1, -1, 1213,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1214, -1, 1215, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1216,
+ 1217, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1218, -1, -1, 1219, -1, -1, -1, -1,
+ 1220, -1, -1, -1, 1221, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1222, 1223, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1224, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1225, -1, -1, -1, 1226, -1, 1227, 1228,
+ -1, 1229, -1, -1, -1, -1, -1, -1,
+ 1230, -1, -1, -1, 1231, -1, -1, 1232,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1233, 1234, -1, -1, -1,
+ 1235, -1, -1, -1, -1, -1, -1, 1236,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1237, -1, -1, -1, 1238, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1239, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1240, 1241, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1242, -1, 1243, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1244, -1, -1, -1,
+ -1, -1, -1, -1, 1245, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1246, -1,
+ -1, -1, -1, -1, -1, 1247, -1, -1,
+ -1, -1, 1248, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1249, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1250, -1, -1, 1251, -1, -1, -1,
+ 1252, -1, -1, 1253, -1, -1, 1254, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1255, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1256, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1257, -1, -1, -1, -1, 1258, -1,
+ -1, 1259, -1, -1, 1260, -1, -1, -1,
+ -1, -1, -1, -1, 1261, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1262, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1263, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1264,
+ -1, 1265, -1, 1266, -1, -1, -1, -1,
+ 1267, -1, -1, -1, -1, -1, 1268, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1269,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1270, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1271, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1272, 1273, -1, -1, -1,
+ 1274, -1, -1, -1, -1, -1, 1275, -1,
+ -1, -1, -1, -1, -1, 1276, -1, -1,
+ -1, -1, -1, 1277, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1278, -1, 1279, -1, -1, 1280, -1,
+ -1, -1, -1, -1, 1281, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1282, -1,
+ -1, -1, -1, 1283, -1, -1, 1284, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1285, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1286, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1287, -1, -1, -1, -1, -1, -1,
+ -1, 1288, -1, -1, 1289, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1290, -1, -1,
+ -1, 1291, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1292, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1293, -1, -1, -1,
+ -1, -1, 1294, -1, -1, -1, -1, -1,
+ 1295, -1, -1, 1296, -1, 1297, -1, 1298,
+ 1299, -1, -1, -1, -1, -1, 1300, -1,
+ -1, -1, -1, -1, -1, 1301, -1, -1,
+ -1, -1, -1, -1, -1, 1302, -1, 1303,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1304, -1, -1, -1, -1, -1, 1305, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1306, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1307, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1308, 1309, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1310, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1311, -1,
+ -1, -1, -1, 1312, -1, -1, 1313, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1314, -1, -1, -1,
+ -1, 1315, -1, 1316, -1, -1, -1, -1,
+ 1317, -1, 1318, -1, 1319, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1320, -1,
+ -1, -1, -1, 1321, -1, -1, -1, -1,
+ 1322, -1, 1323, -1, 1324, -1, -1, -1,
+ -1, -1, 1325, -1, 1326, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1327, -1, -1, -1, 1328,
+ 1329, -1, -1, -1, -1, -1, -1, -1,
+ 1330, -1, -1, -1, -1, -1, 1331, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1332, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1333, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1334, 1335, -1, -1,
+ -1, -1, 1336, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1337, -1, -1, -1,
+ -1, -1, 1338, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1339, -1,
+ -1, -1, -1, 1340, -1, -1, 1341, -1,
+ -1, -1, 1342, 1343, -1, -1, 1344, -1,
+ 1345, -1, -1, -1, -1, 1346, 1347, -1,
+ -1, -1, -1, -1, -1, 1348, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1349, -1, -1, -1, -1, -1, 1350,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1351, -1, -1,
+ 1352, -1, -1, -1, -1, -1, 1353, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1354,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1355, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1356, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1357, -1, -1, 1358, -1, -1,
+ -1, -1, 1359, -1, -1, -1, 1360, -1,
+ -1, -1, 1361, -1, -1, -1, -1, -1,
+ -1, 1362, -1, 1363, -1, -1, -1, -1,
+ -1, 1364, -1, 1365, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1366,
+ -1, -1, -1, 1367, 1368, 1369, 1370, -1,
+ -1, -1, -1, -1, -1, -1, 1371, 1372,
+ 1373, -1, -1, -1, -1, 1374, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1375, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1376, -1, -1, -1,
+ -1, 1377, -1, -1, -1, -1, 1378, -1,
+ 1379, -1, -1, -1, -1, 1380, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1381, -1, 1382, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1383, 1384, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1385, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1386, 1387, -1,
+ -1, -1, -1, -1, 1388, -1, -1, -1,
+ -1, 1389, -1, -1, -1, -1, -1, 1390,
+ -1, 1391, -1, -1, 1392, -1, -1, -1,
+ 1393, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1394, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1395,
+ -1, -1, -1, -1, -1, 1396, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1397, -1, -1, 1398, -1, -1,
+ -1, -1, 1399, -1, 1400, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1401, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1402, -1, -1, 1403, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1404, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1405, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1406,
+ 1407, -1, -1, -1, 1408, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1409,
+ 1410, -1, -1, 1411, -1, -1, -1, -1,
+ -1, 1412, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1413, -1, -1, -1, 1414, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1415, -1, -1, 1416, -1, 1417, -1, -1,
+ -1, 1418, -1, 1419, -1, -1, -1, -1,
+ -1, -1, -1, 1420, 1421, -1, -1, -1,
+ -1, 1422, -1, -1, 1423, -1, -1, -1,
+ -1, -1, -1, -1, 1424, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1425, -1,
+ -1, -1, -1, 1426, -1, -1, -1, -1,
+ -1, 1427, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1428, -1, -1, 1429, -1,
+ -1, -1, -1, -1, -1, 1430, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1431, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1432,
+ -1, -1, -1, -1, -1, 1433, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1434, -1, -1,
+ -1, 1435, -1, -1, 1436, -1, -1, 1437,
+ -1, 1438, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1439, -1, -1,
+ -1, -1, -1, -1, 1440, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1441, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1442, 1443, -1, -1, -1, 1444,
+ -1, -1, -1, -1, -1, -1, -1, 1445,
+ -1, -1, -1, 1446, -1, -1, -1, -1,
+ -1, -1, 1447, -1, -1, -1, -1, 1448,
+ 1449, -1, -1, 1450, -1, 1451, -1, 1452,
+ -1, 1453, -1, -1, 1454, -1, 1455, -1,
+ -1, 1456, 1457, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1458,
+ 1459, 1460, -1, -1, -1, -1, -1, -1,
+ -1, 1461, -1, -1, -1, -1, -1, 1462,
+ -1, -1, -1, 1463, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1464, -1, -1, -1,
+ -1, 1465, 1466, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1467, -1, -1, -1, 1468, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1469, -1, -1,
+ -1, -1, -1, -1, -1, 1470, -1, -1,
+ 1471, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1472, 1473, -1, -1,
+ -1, -1, -1, 1474, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1475, -1, -1, -1,
+ -1, -1, -1, 1476, -1, -1, 1477, -1,
+ -1, -1, -1, 1478, -1, 1479, -1, -1,
+ -1, -1, -1, -1, 1480, -1, -1, -1,
+ 1481, -1, -1, -1, -1, -1, 1482, -1,
+ -1, -1, -1, -1, -1, -1, 1483, -1,
+ -1, -1, 1484, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1485, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1486, -1, -1, 1487, 1488,
+ -1, -1, -1, -1, -1, 1489, -1, -1,
+ 1490, -1, -1, -1, 1491, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1492, -1,
+ -1, 1493, -1, 1494, -1, -1, -1, 1495,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1496, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1497, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1498, -1, -1, -1, -1, 1499, 1500, 1501,
+ -1, -1, -1, -1, -1, -1, -1, 1502,
+ -1, -1, -1, 1503, -1, -1, 1504, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1505,
+ 1506, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1507, -1, -1, -1, -1,
+ -1, -1, 1508, -1, 1509, -1, -1, -1,
+ -1, -1, -1, -1, 1510, -1, -1, -1,
+ -1, -1, -1, -1, 1511, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1512, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1513, -1, -1, -1, -1, -1, -1, 1514,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1515, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1516, -1, -1,
+ -1, -1, -1, -1, 1517, -1, 1518, -1,
+ -1, -1, -1, -1, -1, -1, 1519, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1520, -1, 1521, -1, -1, -1, 1522, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1523,
+ -1, -1, -1, -1, -1, -1, -1, 1524,
+ -1, -1, -1, -1, -1, 1525, -1, -1,
+ -1, -1, -1, 1526, -1, -1, -1, -1,
+ -1, 1527, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1528, -1, -1, -1, -1,
+ 1529, -1, -1, -1, 1530, -1, -1, -1,
+ -1, -1, 1531, -1, -1, -1, 1532, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1533,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1534, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1535, -1, -1, -1,
+ -1, -1, 1536, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1537, -1, 1538, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1539, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1540, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1541, -1,
+ -1, -1, -1, -1, 1542, -1, -1, -1,
+ -1, -1, 1543, 1544, -1, -1, -1, -1,
+ 1545, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1546, -1,
+ -1, 1547, 1548, -1, -1, -1, 1549, -1,
+ -1, -1, -1, -1, 1550, -1, -1, 1551,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1552,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1553, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1554, 1555, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1556, -1, 1557,
+ -1, -1, 1558, -1, 1559, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1560, -1, -1, -1, -1, 1561, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1562, 1563,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1564, -1, 1565, -1, -1, 1566, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1567, -1, 1568, -1, -1,
+ 1569, -1, -1, -1, 1570, -1, -1, -1,
+ 1571, 1572, -1, -1, 1573, -1, -1, -1,
+ -1, -1, 1574, 1575, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1576, -1,
+ -1, 1577, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1578, 1579, 1580, -1, -1, -1, -1,
+ -1, -1, 1581, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1582, -1, -1, -1, 1583,
+ -1, 1584, -1, -1, -1, -1, -1, -1,
+ 1585, 1586, 1587, 1588, -1, -1, -1, 1589,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1590, 1591, -1, -1, -1,
+ 1592, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1593, -1, -1, -1, 1594, -1, -1,
+ -1, -1, 1595, -1, -1, -1, -1, -1,
+ -1, -1, 1596, -1, -1, -1, 1597, -1,
+ -1, -1, 1598, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1599, 1600, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1601, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1602,
+ -1, -1, -1, 1603, -1, -1, 1604, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1605, -1, -1, -1, -1, 1606, -1,
+ 1607, -1, -1, -1, 1608, -1, 1609, -1,
+ -1, -1, -1, -1, -1, -1, 1610, -1,
+ -1, -1, 1611, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1612, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1613, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1614, -1, -1, -1, 1615, -1, -1, -1,
+ -1, -1, 1616, -1, -1, -1, -1, 1617,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1618, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1619, 1620, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1621, -1,
+ -1, -1, 1622, -1, -1, -1, -1, 1623,
+ -1, 1624, -1, -1, -1, 1625, -1, -1,
+ -1, -1, -1, -1, -1, 1626, -1, -1,
+ -1, -1, 1627, -1, -1, -1, -1, -1,
+ -1, -1, 1628, 1629, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1630, -1, -1, -1,
+ -1, 1631, -1, -1, -1, -1, -1, -1,
+ 1632, -1, -1, -1, 1633, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1634,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1635, -1, -1,
+ -1, -1, -1, -1, 1636, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1637, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1638, -1, -1, -1,
+ 1639, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1640, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1641, -1, -1, 1642,
+ -1, 1643, 1644, 1645, -1, -1, -1, -1,
+ -1, 1646, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1647, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1648, -1, 1649,
+ -1, 1650, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1651, -1, -1, -1, -1, -1, 1652,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1653, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1654, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1655, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1656, -1, -1, -1, -1, -1, -1, 1657,
+ 1658, -1, -1, -1, -1, -1, -1, 1659,
+ -1, -1, -1, -1, -1, 1660, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1661, -1,
+ -1, -1, -1, 1662, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1663, -1,
+ -1, -1, -1, -1, -1, 1664, 1665, -1,
+ -1, 1666, -1, -1, -1, -1, -1, -1,
+ 1667, -1, -1, -1, -1, -1, -1, 1668,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1669, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1670, -1, 1671, -1,
+ 1672, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1673, 1674, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1675, 1676, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1677, -1, -1,
+ -1, -1, -1, 1678, 1679, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1680, 1681, -1,
+ -1, -1, -1, 1682, 1683, -1, -1, -1,
+ -1, -1, 1684, -1, -1, 1685, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1686, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1687, -1, -1, -1,
+ 1688, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1689, 1690, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1691, -1, -1, -1, -1,
+ -1, -1, 1692, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1693, -1, -1, -1, 1694, -1,
+ -1, -1, -1, -1, 1695, 1696, 1697, -1,
+ 1698, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1699,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1700,
+ -1, 1701, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1702, -1, -1, -1, -1, -1, -1,
+ 1703, -1, -1, 1704, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1705, -1, 1706, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1707, -1, -1, -1, -1, -1,
+ -1, -1, 1708, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1709,
+ -1, -1, 1710, -1, -1, -1, -1, -1,
+ -1, 1711, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1712, -1, 1713, -1,
+ -1, -1, -1, 1714, -1, -1, 1715, -1,
+ -1, -1, -1, -1, 1716, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1717, -1, -1, -1,
+ -1, -1, -1, 1718, -1, -1, 1719, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1720, -1, -1, 1721, -1, -1, -1,
+ -1, -1, -1, -1, 1722, -1, -1, -1,
+ -1, 1723, -1, -1, -1, 1724, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1725, -1, 1726, -1, -1, -1, -1,
+ -1, -1, -1, 1727, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1728, -1, -1, -1, -1,
+ -1, 1729, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1730, -1, -1, -1, 1731,
+ -1, -1, -1, -1, -1, -1, 1732, -1,
+ 1733, -1, -1, -1, -1, -1, 1734, -1,
+ -1, -1, -1, -1, 1735, -1, -1, -1,
+ -1, -1, 1736, -1, -1, -1, -1, -1,
+ -1, 1737, -1, -1, -1, -1, -1, -1,
+ 1738, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1739, -1, 1740, 1741,
+ 1742, -1, -1, -1, -1, 1743, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1744, -1, -1, -1, -1, 1745, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1746,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1747, -1, -1, -1, -1, -1,
+ 1748, -1, -1, -1, -1, -1, 1749, -1,
+ -1, -1, -1, -1, -1, -1, 1750, -1,
+ -1, 1751, -1, -1, -1, -1, -1, -1,
+ 1752, 1753, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1754, 1755,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1756, -1, -1,
+ 1757, -1, 1758, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1759, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1760, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1761, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1762,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1763, -1, -1,
+ -1, -1, -1, -1, -1, 1764, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1765, -1,
+ -1, -1, -1, -1, 1766, -1, -1, -1,
+ -1, 1767, -1, -1, 1768, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1769, -1, -1, 1770, 1771, 1772,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1773, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1774,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1775, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1776, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1777, -1, -1, -1,
+ -1, 1778, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1779, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1780, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1781,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1782, -1,
+ -1, 1783, -1, -1, -1, -1, 1784, -1,
+ -1, -1, -1, -1, 1785, -1, -1, 1786,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1787, -1, -1, 1788, -1, -1, -1,
+ -1, 1789, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1790, 1791,
+ -1, -1, -1, -1, 1792, -1, -1, -1,
+ -1, -1, -1, 1793, -1, -1, -1, -1,
+ 1794, -1, -1, -1, -1, -1, -1, 1795,
+ -1, 1796, -1, 1797, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1798,
+ 1799, -1, -1, 1800, -1, -1, -1, 1801,
+ -1, -1, -1, -1, -1, -1, 1802, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1803, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1804, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1805, -1, -1, -1, -1,
+ 1806, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1807, -1, -1, 1808,
+ -1, -1, -1, -1, -1, -1, 1809, -1,
+ -1, 1810, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1811,
+ -1, -1, -1, 1812, -1, -1, -1, -1,
+ 1813, -1, -1, -1, -1, -1, -1, -1,
+ 1814, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1815, -1, 1816, -1, 1817, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1818, -1,
+ -1, -1, 1819, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1820,
+ -1, -1, -1, -1, -1, -1, -1, 1821,
+ 1822, -1, 1823, -1, -1, -1, -1, -1,
+ -1, 1824, -1, -1, -1, -1, -1, -1,
+ 1825, -1, -1, -1, -1, -1, -1, 1826,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1827, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1828, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1829, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1830, -1, -1, -1, -1, 1831, -1,
+ 1832, -1, -1, -1, -1, -1, 1833, -1,
+ -1, 1834, -1, -1, -1, -1, -1, 1835,
+ -1, -1, -1, -1, -1, -1, -1, 1836,
+ 1837, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1838, 1839, -1, -1, -1,
+ -1, -1, -1, -1, 1840, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1841, -1, -1, -1,
+ -1, 1842, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1843, -1, 1844, -1, -1, 1845, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1846, -1, 1847, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1848, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1849, -1, -1, -1, -1,
+ -1, -1, 1850, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1851, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1852, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1853, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1854, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1855, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1856, -1, -1, -1,
+ -1, -1, 1857, -1, 1858, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1859, -1, -1, 1860, 1861,
+ -1, -1, 1862, -1, -1, -1, -1, -1,
+ -1, -1, 1863, -1, -1, -1, -1, -1,
+ -1, -1, 1864, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1865, -1, -1,
+ -1, -1, -1, -1, 1866, 1867, -1, 1868,
+ -1, -1, -1, 1869, -1, -1, -1, -1,
+ -1, 1870, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1871, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1872, -1, 1873, -1, -1, -1,
+ 1874, -1, -1, -1, -1, -1, 1875, -1,
+ 1876, -1, -1, 1877, -1, 1878, -1, -1,
+ -1, -1, -1, -1, 1879, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1880, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1881, -1, -1, 1882, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1883, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1884, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1885,
+ -1, -1, -1, -1, -1, -1, 1886, -1,
+ -1, -1, -1, -1, -1, -1, 1887, -1,
+ -1, -1, -1, -1, -1, 1888, -1, -1,
+ -1, -1, -1, -1, -1, 1889, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1890, -1, -1, -1, 1891,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1892, -1, 1893, 1894,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1895, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1896, -1, -1,
+ -1, -1, 1897, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1898,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1899, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1900,
+ -1, 1901, -1, 1902, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1903, -1, -1, 1904, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1905,
+ 1906, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1907, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1908, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1909, -1, -1, -1, -1, -1, -1, 1910,
+ 1911, -1, 1912, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1913,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1914, -1, -1, 1915, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1916, -1, -1, 1917, -1, -1, -1, 1918,
+ -1, -1, -1, -1, -1, -1, -1, 1919,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1920, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1921, -1, -1, -1, -1, -1, -1,
+ 1922, -1, -1, -1, -1, -1, 1923, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1924, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1925, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1926, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1927, -1, -1, -1, 1928, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1929,
+ -1, -1, -1, -1, 1930, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1931, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1932, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1933, -1, -1, -1, -1, -1, 1934, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1935, 1936, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1937, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1938, -1, -1, -1, -1, -1, 1939, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1940,
+ -1, -1, 1941, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1942, -1, -1, -1,
+ -1, -1, 1943, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 1944, -1, -1,
+ -1, 1945, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1946, -1, -1, -1, 1947, -1, 1948, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1949, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1950,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1951, -1, -1, -1,
+ 1952, -1, -1, -1, -1, 1953, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1954, -1,
+ -1, -1, 1955, 1956, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1957, -1, -1, 1958, -1, -1, -1, 1959,
+ -1, -1, -1, 1960, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1961, -1, -1, 1962, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1963,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1964, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1965, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 1966, -1, -1, 1967, -1, -1, -1, -1,
+ -1, -1, -1, 1968, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1969, -1, -1, -1, -1,
+ -1, -1, 1970, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1971, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 1972, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1973, -1,
+ -1, -1, -1, 1974, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1975,
+ 1976, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 1977, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1978,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1979, -1,
+ 1980, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1981, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1982, -1,
+ -1, 1983, -1, 1984, -1, -1, -1, -1,
+ -1, -1, -1, 1985, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1986, -1,
+ -1, -1, -1, -1, -1, 1987, -1, 1988,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 1989, -1, -1, -1,
+ -1, -1, -1, 1990, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 1991, -1,
+ -1, -1, -1, -1, -1, 1992, 1993, 1994,
+ -1, -1, -1, -1, -1, 1995, 1996, -1,
+ -1, -1, -1, -1, -1, -1, -1, 1997,
+ 1998, -1, -1, -1, -1, -1, -1, 1999,
+ -1, -1, -1, -1, 2000, -1, -1, 2001,
+ -1, -1, -1, -1, 2002, 2003, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2004, 2005, -1, -1, -1, -1, 2006, 2007,
+ -1, -1, -1, -1, -1, 2008, -1, 2009,
+ -1, -1, -1, 2010, -1, -1, -1, -1,
+ 2011, -1, 2012, -1, 2013, -1, 2014, 2015,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2016, -1, -1, -1, -1, -1, 2017,
+ -1, -1, 2018, 2019, -1, -1, -1, -1,
+ 2020, -1, -1, -1, -1, -1, -1, 2021,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2022, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2023, -1,
+ -1, 2024, 2025, 2026, -1, 2027, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2028, -1, -1, 2029, -1, -1, 2030, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2031, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2032, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2033, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2034, -1, -1, 2035, 2036,
+ -1, -1, -1, 2037, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2038, 2039, 2040,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2041, -1, -1, 2042,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2043, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2044,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2045, -1, -1, -1, -1, -1,
+ -1, 2046, 2047, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2048, -1, 2049, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2050, -1, -1, -1, -1, 2051, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2052,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2053, -1,
+ -1, 2054, -1, -1, 2055, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2056, -1,
+ -1, -1, 2057, -1, -1, 2058, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2059, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2060, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2061, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2062, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2063, -1, -1, 2064,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2065, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2066, -1, -1, -1,
+ -1, 2067, -1, 2068, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2069, -1, -1, -1, 2070, -1, -1,
+ -1, 2071, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2072, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2073, -1, 2074, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2075, -1, -1, -1, -1, -1, 2076, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2077, -1, 2078, -1, -1, -1, -1, -1,
+ -1, 2079, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2080, -1, -1, -1, -1, -1,
+ 2081, -1, -1, -1, -1, -1, -1, -1,
+ 2082, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2083, 2084, -1, -1, -1, -1,
+ 2085, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2086, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2087, -1, -1, -1,
+ -1, 2088, -1, 2089, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2090, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2091, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2092, -1, -1, -1,
+ -1, -1, -1, 2093, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2094, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2095,
+ -1, -1, -1, 2096, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2097, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2098, -1, -1, -1, 2099,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2100, -1,
+ -1, -1, -1, -1, -1, -1, 2101, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2102, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2103, -1, -1, 2104, -1, -1,
+ -1, -1, 2105, -1, -1, -1, -1, -1,
+ -1, -1, 2106, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2107, -1, 2108, -1, -1, 2109,
+ -1, -1, -1, -1, -1, -1, -1, 2110,
+ -1, -1, 2111, 2112, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2113, -1, -1,
+ -1, 2114, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2115,
+ -1, 2116, -1, 2117, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2118, -1, -1, -1, -1, 2119, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2120, -1, 2121, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2122, -1, -1, -1, -1, -1, -1,
+ 2123, 2124, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2125, 2126, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2127, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2128, -1, -1, 2129, -1,
+ -1, -1, -1, -1, -1, -1, 2130, -1,
+ -1, -1, -1, -1, 2131, -1, 2132, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2133,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2134, -1,
+ -1, -1, 2135, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2136, 2137, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2138, -1, 2139, -1, -1, 2140,
+ -1, -1, -1, -1, 2141, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2142, -1, -1,
+ -1, -1, -1, 2143, -1, -1, -1, -1,
+ -1, -1, 2144, 2145, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2146, -1, -1, -1, 2147, 2148, -1, -1,
+ -1, -1, -1, -1, 2149, 2150, -1, -1,
+ -1, -1, -1, 2151, -1, -1, 2152, -1,
+ -1, -1, -1, 2153, -1, 2154, -1, -1,
+ -1, -1, -1, 2155, -1, -1, 2156, -1,
+ -1, -1, -1, 2157, 2158, -1, -1, 2159,
+ -1, -1, -1, -1, -1, -1, -1, 2160,
+ -1, -1, -1, -1, 2161, -1, -1, -1,
+ 2162, -1, -1, -1, -1, 2163, -1, -1,
+ -1, -1, 2164, -1, -1, -1, -1, -1,
+ 2165, 2166, -1, 2167, -1, -1, 2168, -1,
+ 2169, 2170, -1, -1, -1, 2171, -1, -1,
+ -1, -1, -1, -1, -1, 2172, 2173, -1,
+ -1, -1, 2174, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2175, 2176,
+ -1, 2177, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2178, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2179, -1, 2180,
+ 2181, -1, 2182, -1, 2183, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2184, -1, 2185, -1, 2186, -1, -1, -1,
+ 2187, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2188, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2189, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2190, -1, 2191, 2192, -1, -1, 2193,
+ -1, -1, -1, -1, -1, -1, -1, 2194,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2195, 2196, 2197, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2198, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2199, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2200, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2201, -1,
+ -1, 2202, -1, -1, -1, -1, -1, 2203,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2204, -1,
+ -1, -1, 2205, -1, -1, -1, -1, -1,
+ -1, 2206, -1, -1, -1, -1, -1, -1,
+ 2207, 2208, 2209, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2210, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2211, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2212, 2213, 2214, -1, 2215,
+ 2216, -1, -1, 2217, -1, -1, 2218, 2219,
+ -1, -1, -1, -1, -1, 2220, 2221, -1,
+ -1, -1, -1, -1, -1, -1, 2222, -1,
+ -1, 2223, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2224, -1, -1, 2225, -1, -1,
+ -1, 2226, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2227, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2228, -1, -1, -1, -1, 2229,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2230, -1, 2231, -1, 2232, -1,
+ -1, 2233, -1, -1, -1, -1, -1, -1,
+ -1, 2234, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2235, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2236, -1, -1,
+ 2237, -1, -1, -1, -1, -1, -1, -1,
+ 2238, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2239, 2240, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2241, 2242, -1, -1, -1, -1, -1,
+ 2243, 2244, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2245, -1, 2246,
+ 2247, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2248, -1, -1, 2249, -1, -1, -1, -1,
+ 2250, -1, -1, -1, -1, -1, 2251, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2252, -1, -1, -1, -1, -1, 2253,
+ -1, -1, -1, -1, -1, -1, -1, 2254,
+ -1, -1, -1, 2255, -1, -1, -1, -1,
+ 2256, 2257, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2258,
+ -1, -1, 2259, -1, -1, -1, 2260, 2261,
+ -1, 2262, 2263, -1, -1, 2264, -1, -1,
+ -1, 2265, -1, -1, -1, -1, -1, 2266,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2267, -1, -1, -1, -1, -1, -1,
+ 2268, -1, -1, -1, -1, -1, -1, 2269,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2270, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2271, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2272, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2273, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2274, -1, 2275, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2276, -1, 2277,
+ -1, 2278, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2279, -1, -1, 2280,
+ -1, -1, 2281, -1, -1, -1, -1, -1,
+ -1, -1, 2282, -1, -1, -1, -1, -1,
+ -1, -1, 2283, -1, -1, -1, 2284, -1,
+ -1, -1, -1, -1, -1, -1, 2285, 2286,
+ -1, 2287, -1, 2288, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2289, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2290, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2291, -1,
+ -1, -1, 2292, 2293, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2294, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2295,
+ 2296, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2297, 2298, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2299, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2300, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2301, -1, 2302, 2303, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2304, 2305, -1, -1, 2306, -1,
+ -1, 2307, -1, -1, -1, -1, -1, -1,
+ 2308, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2309, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2310, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2311, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2312,
+ -1, -1, -1, -1, -1, -1, -1, 2313,
+ -1, -1, -1, 2314, -1, -1, -1, 2315,
+ -1, 2316, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2317, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2318, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2319, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2320, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2321,
+ -1, -1, -1, -1, 2322, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2323, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2324, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2325, -1, 2326,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2327, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2328, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2329, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2330, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2331, -1, -1, -1,
+ -1, -1, -1, -1, 2332, -1, 2333, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2334, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2335, -1, -1, -1, -1, -1,
+ 2336, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2337, 2338,
+ -1, -1, 2339, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2340, -1, 2341, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2342, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2343, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2344, -1,
+ -1, -1, -1, 2345, 2346, -1, -1, -1,
+ -1, 2347, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2348,
+ -1, -1, -1, -1, 2349, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2350,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2351, -1, -1, -1, -1, -1, 2352, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2353, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2354, 2355, -1, -1, -1, -1, -1,
+ 2356, 2357, 2358, -1, -1, -1, -1, -1,
+ -1, 2359, -1, -1, -1, -1, -1, -1,
+ -1, 2360, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2361, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2362, -1, -1, 2363, 2364,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2365, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2366,
+ -1, -1, -1, 2367, 2368, -1, -1, -1,
+ -1, 2369, -1, -1, -1, -1, 2370, 2371,
+ 2372, -1, 2373, -1, -1, -1, -1, -1,
+ -1, 2374, -1, -1, -1, -1, 2375, -1,
+ -1, -1, -1, -1, -1, 2376, -1, -1,
+ -1, -1, -1, 2377, -1, 2378, -1, 2379,
+ -1, -1, -1, -1, -1, 2380, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2381, -1, -1, -1, -1, -1, -1,
+ 2382, -1, 2383, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2384,
+ -1, -1, -1, -1, -1, -1, 2385, -1,
+ -1, 2386, -1, -1, -1, -1, -1, -1,
+ -1, 2387, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2388, -1, -1, -1, -1, 2389,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2390, -1,
+ -1, 2391, 2392, -1, 2393, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2394,
+ -1, -1, -1, -1, -1, 2395, -1, -1,
+ 2396, -1, -1, -1, -1, -1, 2397, -1,
+ -1, 2398, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2399, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2400, 2401, -1,
+ -1, -1, 2402, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2403, -1, -1,
+ -1, 2404, 2405, -1, -1, -1, 2406, -1,
+ -1, -1, 2407, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2408, -1, -1, -1,
+ -1, 2409, -1, -1, -1, -1, 2410, -1,
+ 2411, -1, 2412, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2413, -1, -1, -1, 2414,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2415, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2416, -1,
+ -1, 2417, -1, -1, 2418, 2419, -1, -1,
+ 2420, -1, 2421, -1, -1, -1, -1, 2422,
+ -1, -1, -1, -1, -1, -1, 2423, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2424, -1, 2425, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2426,
+ -1, -1, -1, -1, -1, -1, -1, 2427,
+ -1, -1, 2428, -1, 2429, -1, 2430, -1,
+ 2431, 2432, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2433, -1, 2434, -1, -1, -1, -1,
+ -1, -1, 2435, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2436, 2437, -1, 2438,
+ -1, -1, -1, -1, -1, 2439, -1, -1,
+ -1, 2440, 2441, -1, -1, -1, 2442, -1,
+ -1, 2443, -1, -1, 2444, -1, -1, -1,
+ 2445, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2446, -1,
+ -1, 2447, -1, -1, -1, 2448, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2449, -1, -1, -1, -1, -1,
+ -1, 2450, -1, -1, -1, -1, -1, -1,
+ 2451, -1, 2452, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2453, 2454, 2455, 2456, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2457, 2458, -1, -1, -1, -1, -1,
+ 2459, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2460, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2461, -1, -1,
+ -1, -1, -1, 2462, -1, -1, -1, -1,
+ 2463, 2464, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2465, -1, -1, 2466, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2467,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2468, -1, -1, -1, -1, 2469, -1, -1,
+ 2470, -1, -1, 2471, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2472, -1, 2473,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2474, -1, -1, -1, -1, -1, -1, -1,
+ 2475, -1, -1, -1, -1, -1, -1, 2476,
+ -1, -1, -1, -1, -1, -1, 2477, 2478,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2479, -1, 2480,
+ 2481, -1, -1, -1, -1, 2482, -1, -1,
+ 2483, -1, -1, -1, -1, -1, 2484, -1,
+ -1, -1, -1, -1, -1, 2485, -1, -1,
+ -1, -1, -1, 2486, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2487, -1, -1, -1, -1,
+ 2488, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2489,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2490, 2491, -1, 2492, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2493, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2494, -1,
+ -1, -1, -1, 2495, -1, -1, -1, 2496,
+ -1, -1, -1, -1, -1, -1, 2497, -1,
+ -1, -1, 2498, -1, -1, -1, -1, -1,
+ 2499, -1, -1, -1, -1, -1, 2500, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2501, -1, -1, -1, -1, -1, 2502, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2503, 2504, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2505, -1,
+ -1, -1, 2506, -1, -1, 2507, -1, 2508,
+ -1, 2509, -1, -1, -1, -1, 2510, -1,
+ 2511, -1, -1, -1, -1, -1, -1, 2512,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2513, -1, -1, 2514, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2515, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2516, -1, -1, -1, 2517, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2518, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2519, -1, -1, 2520,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2521, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2522, -1, -1, -1, 2523,
+ -1, -1, -1, -1, -1, -1, -1, 2524,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2525, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2526,
+ -1, -1, -1, -1, 2527, -1, 2528, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2529,
+ 2530, -1, -1, -1, -1, 2531, -1, -1,
+ 2532, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2533, -1, -1, 2534, 2535, -1,
+ -1, -1, -1, -1, -1, -1, 2536, -1,
+ -1, -1, 2537, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2538, -1, 2539, -1, -1, 2540, 2541,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2542, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2543,
+ -1, -1, 2544, -1, -1, -1, -1, 2545,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2546, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2547, -1, -1, 2548,
+ -1, -1, -1, -1, -1, -1, 2549, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2550, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2551, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2552, -1, -1,
+ 2553, -1, -1, -1, -1, 2554, -1, -1,
+ -1, -1, -1, -1, -1, 2555, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2556, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2557, -1, -1, -1, -1, 2558, -1,
+ -1, -1, -1, 2559, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2560, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2561, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2562, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2563, -1, -1, -1, -1, 2564, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2565, -1, -1, -1, -1, -1, 2566,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2567, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2568, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2569, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2570, -1, -1, -1, 2571,
+ -1, -1, -1, -1, 2572, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2573, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2574, -1,
+ -1, -1, -1, -1, 2575, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2576, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2577, -1, -1, -1,
+ -1, -1, 2578, -1, 2579, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2580,
+ -1, 2581, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2582,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2583, 2584, 2585, -1, -1, -1, -1, -1,
+ -1, -1, 2586, -1, -1, 2587, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2588, 2589,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2590,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2591, -1, -1, 2592, 2593, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2594, -1,
+ -1, -1, -1, -1, 2595, -1, 2596, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2597, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2598, -1, -1, -1, -1, 2599,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2600, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2601, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2602, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2603, -1, 2604,
+ -1, -1, -1, 2605, -1, 2606, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2607,
+ -1, -1, -1, -1, -1, 2608, -1, -1,
+ 2609, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2610, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2611, 2612, -1, -1, 2613,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2614, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2615, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2616, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2617, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2618, -1, 2619, -1, -1, -1, 2620,
+ -1, -1, -1, -1, 2621, -1, 2622, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2623, -1, -1, -1, 2624, -1,
+ -1, -1, -1, -1, -1, -1, 2625, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2626, -1, -1,
+ -1, -1, 2627, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2628, -1, -1, -1,
+ -1, 2629, -1, -1, -1, -1, -1, 2630,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2631,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2632, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2633, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2634, -1, -1, -1, -1,
+ 2635, -1, -1, -1, 2636, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2637,
+ -1, 2638, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2639, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2640, -1,
+ -1, -1, -1, -1, 2641, 2642, -1, -1,
+ -1, 2643, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2644, -1, -1, -1, -1, 2645, -1, -1,
+ -1, -1, -1, -1, -1, 2646, -1, -1,
+ 2647, -1, -1, 2648, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2649, -1, -1, -1, -1, -1, -1,
+ 2650, -1, -1, -1, -1, -1, -1, -1,
+ 2651, -1, -1, 2652, -1, 2653, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2654, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2655, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2656, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2657, -1, -1, 2658, -1, -1, -1,
+ -1, -1, -1, 2659, -1, 2660, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2661, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2662, -1, -1, -1,
+ 2663, -1, -1, -1, 2664, -1, -1, -1,
+ 2665, -1, -1, -1, -1, -1, -1, -1,
+ 2666, 2667, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2668,
+ -1, -1, 2669, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2670, 2671, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2672, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2673, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2674,
+ -1, -1, 2675, -1, -1, -1, 2676, -1,
+ -1, -1, -1, -1, -1, 2677, -1, -1,
+ -1, -1, -1, -1, 2678, -1, -1, -1,
+ -1, -1, -1, -1, 2679, -1, -1, -1,
+ -1, -1, -1, -1, 2680, -1, -1, -1,
+ -1, 2681, -1, -1, -1, -1, -1, -1,
+ 2682, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2683, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2684, -1, 2685, -1, -1,
+ -1, 2686, 2687, -1, -1, 2688, -1, 2689,
+ -1, 2690, 2691, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2692, -1,
+ -1, -1, 2693, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2694, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2695, -1, -1,
+ 2696, -1, -1, -1, -1, 2697, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2698, -1, -1, 2699, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2700, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2701, 2702, -1, -1, -1, -1,
+ 2703, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2704, -1, -1, -1,
+ -1, -1, 2705, -1, 2706, -1, 2707, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2708, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2709, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2710, -1, -1,
+ -1, -1, -1, -1, -1, 2711, -1, -1,
+ -1, 2712, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2713,
+ -1, -1, -1, -1, 2714, -1, 2715, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2716, 2717, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2718, -1, -1, -1,
+ -1, -1, -1, -1, 2719, -1, -1, -1,
+ -1, -1, 2720, -1, -1, -1, -1, -1,
+ -1, -1, 2721, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2722, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2723, -1, -1,
+ -1, -1, -1, -1, -1, 2724, -1, -1,
+ -1, -1, -1, -1, -1, 2725, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2726, -1, -1, -1, 2727, -1,
+ -1, -1, -1, 2728, -1, -1, 2729, -1,
+ 2730, -1, 2731, -1, -1, -1, -1, 2732,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2733, -1, -1, -1, -1, -1, -1, -1,
+ 2734, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2735, -1, -1, -1, 2736,
+ -1, -1, -1, 2737, -1, -1, 2738, -1,
+ -1, 2739, 2740, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2741,
+ -1, -1, -1, -1, 2742, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2743, -1,
+ -1, -1, -1, 2744, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2745, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2746, -1, -1, -1,
+ -1, -1, 2747, 2748, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2749, -1, -1, -1,
+ -1, -1, -1, 2750, 2751, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2752, -1, -1, -1, -1, -1,
+ -1, -1, 2753, -1, -1, -1, -1, -1,
+ -1, -1, 2754, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2755, -1, -1, 2756, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2757, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2758, -1, -1, -1, -1, -1, 2759,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2760, -1, 2761, -1, 2762, -1, -1, -1,
+ 2763, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2764, -1, -1,
+ -1, -1, -1, 2765, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2766, -1,
+ -1, -1, -1, -1, -1, -1, 2767, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2768, -1, 2769, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2770, 2771, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2772, -1, 2773, -1,
+ -1, -1, -1, -1, 2774, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2775, -1, -1, -1,
+ 2776, -1, -1, 2777, -1, -1, 2778, -1,
+ -1, -1, -1, -1, 2779, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2780, -1, 2781, -1, 2782,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2783, -1, -1, -1, -1, -1, -1, 2784,
+ -1, -1, -1, -1, -1, 2785, -1, -1,
+ -1, -1, 2786, -1, -1, -1, 2787, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2788, -1, 2789,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2790, -1, -1, -1, -1, -1, 2791,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2792, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2793, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2794, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2795, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2796, 2797, -1, -1,
+ 2798, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2799, -1,
+ -1, -1, -1, -1, 2800, 2801, -1, -1,
+ -1, -1, -1, -1, 2802, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2803,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2804, -1, -1, -1, -1, -1, -1,
+ -1, 2805, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2806, -1, -1, -1,
+ 2807, -1, -1, -1, 2808, -1, -1, -1,
+ 2809, -1, -1, -1, 2810, 2811, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2812,
+ -1, -1, -1, -1, -1, 2813, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2814, -1,
+ -1, -1, -1, 2815, -1, -1, 2816, -1,
+ -1, -1, -1, -1, 2817, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2818, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2819, 2820, 2821,
+ -1, 2822, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2823, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2824, -1, 2825, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2826, 2827, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2828, 2829, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2830,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2831, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2832, -1,
+ -1, -1, 2833, -1, -1, -1, 2834, 2835,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2836, -1, -1, -1, -1, -1, 2837,
+ -1, -1, -1, -1, -1, -1, 2838, -1,
+ -1, -1, -1, -1, -1, 2839, 2840, 2841,
+ 2842, -1, -1, -1, -1, -1, -1, -1,
+ 2843, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2844, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2845, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2846, -1, -1,
+ -1, -1, -1, 2847, 2848, -1, -1, -1,
+ 2849, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2850, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2851, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2852, 2853, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2854, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2855, -1,
+ -1, 2856, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2857, -1, -1, -1, -1, -1,
+ -1, 2858, -1, -1, -1, -1, -1, 2859,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2860, -1,
+ -1, -1, 2861, -1, -1, -1, -1, -1,
+ -1, -1, 2862, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2863, -1,
+ -1, 2864, 2865, -1, -1, -1, 2866, -1,
+ -1, -1, -1, 2867, 2868, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2869, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2870, -1, -1, -1, 2871, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2872, -1, -1,
+ 2873, -1, -1, -1, -1, -1, -1, -1,
+ 2874, 2875, -1, -1, -1, -1, -1, -1,
+ 2876, -1, -1, -1, -1, -1, -1, 2877,
+ -1, -1, -1, -1, -1, -1, 2878, -1,
+ -1, -1, -1, -1, -1, 2879, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2880,
+ -1, -1, -1, -1, -1, 2881, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2882, -1, -1, -1, -1, 2883, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2884, 2885, -1, -1,
+ 2886, -1, 2887, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2888,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2889, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2890,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2891,
+ -1, -1, 2892, -1, -1, -1, -1, -1,
+ -1, 2893, -1, 2894, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2895, -1, -1, -1, 2896, -1,
+ -1, -1, -1, 2897, -1, -1, 2898, -1,
+ -1, -1, -1, -1, 2899, -1, -1, -1,
+ -1, -1, 2900, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2901, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2902, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2903, -1, -1, -1,
+ -1, -1, -1, 2904, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2905, -1, -1, 2906,
+ 2907, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2908, -1, 2909, -1,
+ -1, 2910, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2911, 2912, -1, -1, 2913, 2914, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2915,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2916, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2917, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2918, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2919,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2920, -1, -1, -1,
+ -1, -1, -1, -1, 2921, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2922, -1, 2923, -1,
+ 2924, -1, -1, -1, -1, 2925, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2926, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2927, -1, 2928, 2929,
+ -1, -1, 2930, 2931, -1, -1, 2932, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2933, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2934, -1, -1, 2935, 2936, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2937, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2938, -1, -1, -1, -1, 2939, 2940,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2941, -1, -1,
+ -1, -1, -1, -1, -1, 2942, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2943, 2944, -1, -1,
+ 2945, -1, -1, -1, -1, 2946, -1, -1,
+ -1, 2947, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2948, -1, -1,
+ -1, 2949, -1, -1, 2950, -1, -1, -1,
+ -1, -1, 2951, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2952, 2953, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 2954,
+ -1, 2955, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2956, -1, -1, -1, 2957, -1,
+ -1, 2958, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2959, -1, -1, -1, -1, -1,
+ -1, -1, 2960, -1, -1, -1, 2961, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2962, -1,
+ -1, 2963, 2964, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 2965, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2966, -1, 2967, -1, -1, -1, -1, 2968,
+ -1, -1, -1, -1, -1, -1, -1, 2969,
+ -1, -1, -1, -1, -1, -1, 2970, -1,
+ -1, -1, -1, -1, -1, 2971, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2972, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2973, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2974, -1, -1, -1, -1, -1, 2975,
+ -1, 2976, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2977, -1, -1, -1, 2978,
+ -1, 2979, 2980, -1, -1, -1, -1, 2981,
+ -1, -1, -1, -1, -1, -1, -1, 2982,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2983, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 2984, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2985, -1, -1, -1, 2986, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 2987, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 2988, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 2989, 2990, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 2991, -1, -1, -1, 2992, -1, 2993, -1,
+ -1, 2994, 2995, -1, -1, 2996, -1, 2997,
+ 2998, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 2999, 3000, 3001,
+ 3002, -1, -1, -1, -1, -1, 3003, -1,
+ 3004, -1, -1, -1, -1, 3005, -1, -1,
+ -1, -1, -1, -1, 3006, -1, -1, -1,
+ -1, -1, 3007, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3008, -1, 3009, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3010, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3011, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3012, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3013,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3014, -1, 3015, 3016, -1, -1, -1, -1,
+ -1, 3017, -1, -1, -1, 3018, -1, 3019,
+ -1, -1, -1, -1, -1, -1, -1, 3020,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3021, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3022, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3023, 3024, -1, -1,
+ -1, -1, -1, -1, -1, 3025, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3026, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3027,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3028, -1, -1, -1,
+ -1, 3029, -1, -1, -1, -1, 3030, -1,
+ -1, 3031, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3032, -1, -1, -1, -1, 3033, -1, 3034,
+ -1, -1, -1, -1, -1, -1, -1, 3035,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3036, -1, -1, -1, -1, -1, -1,
+ -1, 3037, -1, -1, -1, -1, -1, -1,
+ -1, 3038, -1, -1, -1, -1, -1, 3039,
+ -1, 3040, -1, -1, -1, -1, 3041, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3042, -1, -1, -1, -1, -1, -1, 3043,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3044, -1, -1, -1, -1, -1, -1,
+ 3045, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3046, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3047,
+ -1, -1, -1, 3048, -1, 3049, -1, -1,
+ -1, 3050, -1, -1, -1, -1, -1, 3051,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3052, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3053, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3054, -1, -1, -1, -1, 3055,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3056, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3057, -1,
+ -1, 3058, -1, -1, 3059, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3060,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3061, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3062, -1, -1, -1, 3063,
+ -1, -1, -1, 3064, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3065, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3066, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3067, 3068, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3069, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3070, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3071, 3072, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3073, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3074, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3075, -1,
+ -1, -1, 3076, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3077,
+ -1, 3078, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3079, -1, -1, -1, -1,
+ -1, -1, 3080, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3081, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3082,
+ -1, -1, -1, -1, 3083, -1, -1, -1,
+ -1, -1, -1, -1, 3084, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3085, -1, -1,
+ -1, 3086, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3087, -1, -1,
+ -1, -1, -1, -1, -1, 3088, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3089, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3090, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3091,
+ -1, -1, -1, -1, 3092, -1, 3093, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3094, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3095, -1, -1, -1, -1, -1, -1,
+ -1, 3096, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3097, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3098, -1, -1, -1, -1,
+ -1, -1, -1, 3099, -1, -1, -1, -1,
+ 3100, -1, 3101, -1, 3102, -1, -1, -1,
+ 3103, -1, -1, -1, -1, -1, -1, 3104,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3105, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3106, -1, -1, -1, -1, 3107, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3108, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3109,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3110,
+ 3111, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3112, -1, -1, -1, -1, 3113, 3114,
+ -1, -1, -1, -1, 3115, 3116, -1, 3117,
+ -1, -1, -1, -1, 3118, -1, -1, 3119,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3120, 3121, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3122,
+ -1, -1, 3123, -1, 3124, -1, 3125, -1,
+ 3126, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3127, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3128, -1, -1,
+ -1, 3129, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3130, -1, 3131, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3132, -1,
+ -1, -1, -1, -1, 3133, -1, -1, 3134,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3135, -1, -1, -1, -1,
+ -1, -1, 3136, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3137, -1, -1, -1, -1, 3138,
+ -1, -1, -1, -1, -1, 3139, -1, -1,
+ -1, -1, 3140, 3141, -1, -1, -1, -1,
+ -1, 3142, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3143, -1, -1, -1, -1,
+ -1, 3144, -1, -1, -1, -1, -1, -1,
+ -1, 3145, -1, -1, -1, -1, -1, -1,
+ 3146, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3147, -1, 3148,
+ 3149, -1, -1, 3150, -1, 3151, 3152, -1,
+ -1, -1, -1, -1, 3153, -1, -1, -1,
+ -1, 3154, -1, -1, -1, -1, -1, -1,
+ 3155, -1, 3156, -1, -1, -1, -1, -1,
+ 3157, -1, -1, -1, -1, -1, 3158, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3159,
+ -1, -1, -1, -1, 3160, -1, -1, 3161,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3162, -1, -1, 3163, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3164, -1,
+ -1, 3165, -1, -1, -1, -1, -1, 3166,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3167, 3168, -1, -1, -1, -1, -1,
+ 3169, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3170, -1, 3171, 3172, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3173, -1, 3174, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3175, -1, -1, -1,
+ -1, -1, 3176, -1, 3177, -1, -1, -1,
+ -1, 3178, 3179, -1, 3180, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3181, 3182, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3183, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3184,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3185, -1, -1, -1, -1,
+ 3186, 3187, -1, 3188, -1, -1, -1, -1,
+ -1, -1, 3189, -1, 3190, 3191, 3192, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3193, 3194, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3195, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3196, 3197, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3198, -1, 3199, -1, -1, -1,
+ -1, -1, 3200, -1, 3201, -1, -1, -1,
+ -1, -1, -1, -1, 3202, -1, 3203, -1,
+ 3204, -1, -1, -1, 3205, -1, -1, -1,
+ -1, -1, 3206, -1, -1, -1, -1, -1,
+ 3207, -1, -1, 3208, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3209,
+ -1, 3210, -1, -1, -1, 3211, -1, -1,
+ -1, 3212, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3213, -1, -1, -1, -1, 3214,
+ -1, -1, -1, 3215, 3216, -1, -1, -1,
+ -1, -1, -1, 3217, 3218, -1, -1, -1,
+ -1, 3219, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3220, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3221,
+ -1, -1, 3222, -1, -1, -1, -1, -1,
+ 3223, -1, 3224, -1, -1, -1, -1, -1,
+ 3225, -1, -1, -1, -1, -1, 3226, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3227,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3228, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3229, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3230, -1, -1, -1,
+ 3231, -1, -1, -1, 3232, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3233, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3234,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3235, -1, -1, -1,
+ -1, -1, 3236, 3237, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3238, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3239, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3240, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3241, -1, -1, -1, -1,
+ -1, -1, 3242, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3243, -1,
+ -1, -1, 3244, -1, 3245, -1, -1, -1,
+ 3246, -1, -1, 3247, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3248,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3249, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3250, -1, -1, 3251,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3252, 3253, -1, 3254, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3255, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3256, -1, 3257, 3258, -1, -1, 3259,
+ -1, 3260, -1, 3261, -1, -1, -1, -1,
+ 3262, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3263, -1,
+ -1, 3264, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3265, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3266,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3267, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3268, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3269, -1, 3270, -1, 3271, -1,
+ 3272, 3273, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3274, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3275, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3276, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3277, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3278, -1, -1,
+ 3279, -1, -1, -1, -1, -1, -1, 3280,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3281, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3282, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3283, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3284, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3285,
+ -1, 3286, -1, -1, -1, -1, -1, 3287,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3288, 3289, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3290,
+ -1, -1, -1, -1, -1, 3291, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3292, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3293,
+ -1, -1, -1, 3294, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3295, -1, -1, -1, 3296, -1,
+ -1, -1, -1, 3297, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3298, -1, -1, -1, -1, 3299,
+ -1, -1, -1, -1, -1, -1, 3300, -1,
+ -1, -1, -1, -1, 3301, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3302, -1,
+ -1, -1, 3303, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3304, -1, -1, -1, -1,
+ -1, 3305, -1, -1, -1, -1, -1, -1,
+ 3306, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3307, -1, -1, 3308, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3309, -1, -1,
+ -1, -1, -1, 3310, -1, -1, 3311, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3312, 3313, -1, -1, 3314, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3315, -1, 3316, -1, -1, -1, -1,
+ -1, -1, 3317, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3318,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3319, -1, -1, -1, -1,
+ 3320, -1, -1, -1, 3321, -1, -1, -1,
+ -1, -1, -1, 3322, -1, -1, -1, -1,
+ -1, 3323, 3324, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3325, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3326,
+ -1, -1, 3327, -1, -1, 3328, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3329, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3330, -1, -1, -1, -1, 3331, -1, -1,
+ -1, -1, 3332, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3333, -1, -1, -1, 3334, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3335,
+ -1, -1, 3336, -1, -1, -1, -1, -1,
+ -1, -1, 3337, -1, -1, 3338, -1, -1,
+ 3339, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3340, -1, -1, 3341, -1, 3342,
+ -1, -1, -1, -1, 3343, -1, -1, -1,
+ -1, 3344, -1, -1, -1, 3345, -1, -1,
+ -1, -1, 3346, -1, 3347, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3348, -1, 3349, 3350, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3351, 3352, -1, -1,
+ -1, -1, 3353, -1, 3354, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3355, -1, -1, -1, -1, -1,
+ -1, -1, 3356, 3357, -1, -1, -1, -1,
+ -1, 3358, -1, -1, -1, 3359, 3360, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3361, -1, -1,
+ -1, 3362, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3363, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3364, -1, 3365, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3366, -1, 3367, 3368, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3369,
+ -1, -1, -1, -1, -1, 3370, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3371, -1,
+ 3372, -1, -1, -1, -1, 3373, -1, -1,
+ -1, -1, -1, 3374, -1, -1, 3375, -1,
+ -1, -1, -1, 3376, 3377, 3378, -1, 3379,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3380, -1, -1, -1, -1, -1, -1, 3381,
+ -1, -1, -1, -1, 3382, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3383, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3384,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3385, -1, -1, -1, -1, 3386, -1, -1,
+ 3387, -1, -1, -1, -1, -1, 3388, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3389, -1, -1, -1, 3390, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3391, -1, -1, -1, -1,
+ -1, -1, 3392, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3393, -1, -1,
+ -1, 3394, -1, -1, -1, 3395, -1, -1,
+ 3396, -1, -1, -1, -1, -1, -1, 3397,
+ -1, 3398, -1, -1, -1, 3399, -1, -1,
+ -1, -1, -1, -1, -1, 3400, -1, -1,
+ 3401, -1, -1, -1, -1, -1, 3402, 3403,
+ -1, -1, -1, 3404, -1, -1, -1, -1,
+ -1, -1, 3405, -1, -1, -1, -1, -1,
+ -1, -1, 3406, -1, -1, -1, 3407, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3408, -1, -1, -1, -1, -1,
+ -1, 3409, 3410, -1, -1, -1, 3411, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3412,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3413, -1, -1, -1, 3414, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3415, 3416, -1, -1, -1,
+ 3417, 3418, -1, -1, -1, -1, 3419, -1,
+ -1, -1, 3420, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3421,
+ -1, -1, 3422, -1, -1, -1, -1, -1,
+ 3423, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3424, -1, -1, 3425,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3426, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3427, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3428, -1,
+ -1, -1, -1, -1, 3429, -1, 3430, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3431, -1, -1, 3432, -1, 3433, -1,
+ -1, -1, -1, -1, -1, 3434, -1, -1,
+ -1, -1, -1, 3435, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3436, -1, -1, -1,
+ -1, 3437, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3438, -1, -1, 3439, 3440,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3441, -1, -1, 3442, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3443, -1, 3444, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3445, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3446, -1, -1,
+ -1, -1, -1, -1, 3447, -1, -1, -1,
+ 3448, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3449,
+ -1, 3450, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3451, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3452, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3453, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3454,
+ -1, -1, -1, 3455, 3456, -1, -1, -1,
+ 3457, -1, -1, 3458, -1, -1, -1, -1,
+ -1, -1, 3459, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3460, -1, -1, -1,
+ -1, 3461, -1, 3462, -1, -1, -1, 3463,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3464, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3465, -1, -1, -1,
+ -1, 3466, -1, -1, -1, -1, -1, 3467,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3468, -1, -1, -1, -1, 3469, -1, 3470,
+ -1, -1, 3471, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3472, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3473, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3474, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3475, 3476, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3477, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3478, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3479, -1, -1, 3480, -1, -1, -1,
+ 3481, -1, 3482, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3483,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3484, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3485, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3486, 3487, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3488, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3489, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3490, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3491, -1, -1, -1, -1, 3492, -1, -1,
+ -1, 3493, -1, -1, -1, -1, 3494, -1,
+ -1, -1, -1, 3495, -1, -1, -1, -1,
+ -1, 3496, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3497, -1, -1,
+ -1, -1, -1, 3498, -1, 3499, -1, -1,
+ -1, -1, -1, -1, -1, 3500, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3501,
+ -1, -1, -1, -1, 3502, 3503, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3504, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3505, -1, 3506, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3507, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3508,
+ -1, -1, -1, 3509, -1, -1, -1, -1,
+ 3510, 3511, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3512, 3513, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3514, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3515, -1, -1, 3516, -1, -1,
+ -1, -1, -1, -1, -1, 3517, 3518, -1,
+ -1, -1, -1, 3519, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3520,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3521, -1, -1, -1, 3522, -1, -1, -1,
+ 3523, -1, -1, -1, -1, -1, 3524, 3525,
+ -1, -1, 3526, -1, -1, -1, -1, -1,
+ -1, 3527, -1, -1, -1, 3528, -1, -1,
+ 3529, 3530, 3531, -1, 3532, -1, -1, -1,
+ -1, -1, -1, 3533, -1, 3534, -1, 3535,
+ -1, 3536, -1, -1, -1, -1, 3537, 3538,
+ -1, -1, -1, -1, 3539, 3540, 3541, -1,
+ -1, -1, -1, -1, -1, -1, 3542, -1,
+ -1, -1, 3543, -1, -1, -1, 3544, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3545, -1, -1, -1, -1, 3546, -1,
+ -1, -1, -1, -1, 3547, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3548, -1, -1, 3549, -1, -1, -1,
+ -1, -1, -1, -1, 3550, -1, 3551, -1,
+ -1, -1, -1, 3552, -1, -1, -1, -1,
+ 3553, -1, 3554, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3555, 3556, -1,
+ 3557, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3558, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3559, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3560, 3561, -1, 3562, -1, -1,
+ -1, -1, 3563, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3564,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3565, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3566,
+ -1, 3567, -1, 3568, -1, -1, -1, -1,
+ 3569, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3570, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3571,
+ -1, -1, -1, -1, 3572, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3573, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3574, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3575, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3576,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3577, -1, 3578, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3579, -1, -1, -1, -1, 3580,
+ -1, -1, -1, -1, -1, 3581, -1, 3582,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3583, -1, -1,
+ -1, -1, -1, -1, -1, 3584, -1, 3585,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3586, -1, -1, 3587, -1,
+ -1, 3588, -1, 3589, -1, -1, -1, -1,
+ -1, -1, -1, 3590, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3591, -1, -1, -1, -1,
+ 3592, -1, -1, -1, -1, -1, -1, 3593,
+ -1, -1, 3594, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3595, -1, -1, -1, 3596,
+ -1, -1, -1, 3597, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3598, -1, -1, -1, -1, -1, -1, -1,
+ 3599, -1, -1, -1, -1, -1, -1, -1,
+ 3600, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3601,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3602, 3603, 3604, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3605, -1, -1, -1, -1, 3606, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3607, -1,
+ 3608, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3609, -1, 3610, -1, -1, -1,
+ -1, -1, -1, 3611, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3612, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3613, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3614, -1, -1, -1,
+ -1, 3615, -1, -1, -1, -1, 3616, -1,
+ 3617, -1, 3618, -1, 3619, 3620, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3621,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3622, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3623,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3624, 3625, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3626, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3627, -1,
+ -1, -1, 3628, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3629, 3630,
+ -1, -1, -1, -1, 3631, -1, -1, -1,
+ 3632, -1, -1, -1, -1, -1, 3633, 3634,
+ -1, -1, -1, -1, -1, -1, -1, 3635,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3636, -1, 3637, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3638, 3639, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3640, -1, -1, -1, -1, -1,
+ 3641, -1, -1, 3642, 3643, 3644, -1, 3645,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3646, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3647, -1, -1, 3648,
+ -1, -1, 3649, -1, 3650, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3651, -1, -1,
+ -1, -1, -1, -1, 3652, -1, -1, -1,
+ -1, -1, -1, 3653, -1, -1, -1, -1,
+ 3654, 3655, -1, -1, -1, -1, 3656, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3657, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3658, 3659, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3660, 3661, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3662, 3663, -1, -1, -1,
+ -1, -1, 3664, 3665, -1, -1, 3666, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3667,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3668,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3669, -1,
+ -1, -1, 3670, -1, -1, 3671, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3672, 3673, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3674, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3675,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3676, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3677, 3678, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3679, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3680, 3681, -1, -1, 3682, -1, -1,
+ -1, -1, -1, 3683, 3684, -1, -1, -1,
+ -1, -1, 3685, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3686, 3687, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3688, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3689, -1, -1, -1,
+ 3690, -1, -1, -1, -1, -1, 3691, -1,
+ -1, 3692, -1, -1, -1, -1, -1, 3693,
+ -1, -1, -1, -1, 3694, -1, -1, -1,
+ -1, 3695, -1, 3696, -1, -1, -1, -1,
+ -1, 3697, -1, -1, -1, -1, -1, 3698,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3699,
+ -1, 3700, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3701, -1, 3702, -1, -1, -1, -1, 3703,
+ -1, -1, 3704, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3705, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3706, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3707, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3708, -1,
+ 3709, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3710, 3711,
+ 3712, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3713, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3714, -1, -1, 3715, -1, -1,
+ -1, -1, -1, -1, -1, 3716, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3717, -1, -1, -1, -1, 3718, -1, 3719,
+ 3720, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3721, -1, -1, -1, -1,
+ -1, -1, 3722, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3723, -1, -1,
+ -1, -1, 3724, 3725, -1, -1, -1, -1,
+ 3726, -1, -1, -1, -1, -1, -1, 3727,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3728, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3729,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3730, -1, -1, -1, -1, 3731,
+ -1, -1, 3732, -1, -1, 3733, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3734, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3735, -1,
+ -1, -1, -1, -1, 3736, -1, -1, -1,
+ -1, 3737, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3738, -1, -1, -1, -1,
+ -1, -1, 3739, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3740,
+ -1, -1, -1, -1, -1, -1, -1, 3741,
+ -1, -1, 3742, -1, -1, -1, -1, 3743,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3744,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3745, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3746, -1, -1, -1,
+ 3747, 3748, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3749, -1, -1, -1, -1, -1, -1,
+ 3750, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3751, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3752,
+ 3753, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3754,
+ -1, -1, -1, -1, -1, -1, 3755, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3756, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3757, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3758,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3759, -1,
+ -1, -1, -1, 3760, -1, -1, -1, -1,
+ 3761, -1, -1, 3762, -1, -1, 3763, -1,
+ -1, -1, -1, -1, 3764, -1, 3765, -1,
+ 3766, -1, -1, 3767, -1, -1, -1, 3768,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3769, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3770, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3771, -1,
+ 3772, -1, -1, -1, -1, 3773, -1, -1,
+ -1, 3774, -1, -1, -1, -1, 3775, -1,
+ -1, -1, -1, 3776, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3777, -1, -1,
+ 3778, -1, 3779, -1, -1, -1, -1, -1,
+ -1, -1, 3780, -1, -1, -1, -1, 3781,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3782, -1, -1,
+ -1, -1, -1, 3783, -1, -1, -1, 3784,
+ -1, -1, -1, -1, -1, -1, 3785, -1,
+ -1, -1, -1, -1, 3786, -1, -1, 3787,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3788, -1, -1, 3789, 3790, -1, -1, -1,
+ -1, -1, -1, 3791, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3792,
+ -1, 3793, -1, 3794, -1, -1, -1, -1,
+ 3795, 3796, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3797, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3798, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3799, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3800, -1, -1, 3801, -1, -1,
+ 3802, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3803, 3804, 3805, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3806, -1,
+ -1, -1, -1, -1, -1, -1, 3807, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3808, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3809, -1, 3810, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3811, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3812, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3813,
+ -1, -1, -1, -1, 3814, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3815, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3816, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3817, -1, -1, -1, -1, -1,
+ -1, -1, 3818, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3819, -1, -1, 3820, -1,
+ -1, 3821, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3822, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3823, -1,
+ -1, -1, -1, 3824, -1, -1, -1, -1,
+ -1, 3825, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3826, -1, -1, 3827,
+ -1, 3828, 3829, -1, -1, -1, -1, 3830,
+ -1, -1, -1, -1, -1, 3831, -1, -1,
+ 3832, -1, -1, 3833, -1, 3834, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3835, -1, -1, 3836, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3837, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3838, -1, -1, -1, -1,
+ -1, -1, 3839, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3840, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3841, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3842, -1, -1, -1, -1, -1, -1,
+ -1, 3843, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3844, -1, -1, -1,
+ -1, -1, 3845, 3846, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3847, -1, -1, -1,
+ -1, -1, -1, -1, 3848, 3849, -1, -1,
+ -1, -1, -1, 3850, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3851, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3852, -1, 3853, -1, -1, 3854, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3855, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3856, -1, -1, -1,
+ 3857, -1, -1, -1, -1, -1, 3858, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3859, -1, -1, 3860,
+ -1, -1, 3861, 3862, -1, -1, -1, -1,
+ -1, 3863, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3864, -1, -1, -1, -1, -1,
+ -1, 3865, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3866, -1, -1,
+ -1, -1, 3867, 3868, 3869, 3870, -1, 3871,
+ -1, -1, 3872, -1, -1, -1, -1, -1,
+ -1, -1, 3873, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3874, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3875,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3876, -1, -1, -1,
+ 3877, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3878, -1, -1, -1, 3879, -1, -1, 3880,
+ -1, -1, -1, 3881, -1, -1, -1, -1,
+ 3882, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3883, -1, 3884, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3885, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3886, -1,
+ -1, -1, -1, 3887, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3888, -1, -1,
+ -1, 3889, -1, -1, -1, -1, -1, -1,
+ -1, 3890, -1, -1, -1, 3891, -1, -1,
+ 3892, 3893, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3894, -1, 3895, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3896, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3897, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3898, -1, -1, -1, -1, -1, 3899,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3900, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3901, -1, -1, -1, -1, 3902, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3903, -1, -1, -1, 3904, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3905, 3906, 3907, 3908, -1, -1, -1,
+ -1, -1, -1, -1, 3909, 3910, -1, -1,
+ -1, -1, -1, -1, -1, 3911, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3912, -1, 3913, -1, -1, -1, -1,
+ -1, -1, -1, 3914, -1, -1, -1, -1,
+ -1, -1, 3915, -1, -1, 3916, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3917, -1, -1, -1,
+ -1, 3918, -1, 3919, -1, -1, 3920, 3921,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3922, 3923, -1, -1, -1, 3924, 3925, -1,
+ 3926, -1, -1, -1, 3927, -1, -1, 3928,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3929, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3930, -1, -1, 3931, -1, -1, 3932,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3933, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3934, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3935, -1,
+ -1, -1, -1, -1, -1, 3936, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3937, -1, -1, -1, -1,
+ 3938, 3939, -1, -1, -1, -1, 3940, -1,
+ -1, -1, 3941, -1, -1, -1, -1, -1,
+ 3942, -1, -1, -1, -1, -1, 3943, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3944, -1, -1, -1, 3945, -1, 3946,
+ -1, -1, -1, -1, -1, -1, 3947, -1,
+ -1, -1, 3948, -1, -1, -1, -1, -1,
+ -1, 3949, -1, -1, -1, -1, 3950, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 3951, -1, 3952, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3953,
+ -1, -1, -1, 3954, -1, 3955, -1, 3956,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3957, -1, -1,
+ -1, -1, 3958, -1, -1, -1, -1, -1,
+ -1, -1, 3959, -1, -1, -1, -1, 3960,
+ 3961, -1, 3962, -1, -1, 3963, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3964, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3965, -1, -1,
+ -1, -1, 3966, -1, -1, -1, -1, -1,
+ -1, 3967, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3968, -1, 3969, -1,
+ -1, -1, -1, 3970, -1, -1, -1, 3971,
+ 3972, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 3973, -1, -1, 3974, -1,
+ -1, -1, -1, -1, 3975, -1, 3976, -1,
+ -1, -1, -1, 3977, -1, -1, -1, -1,
+ -1, -1, 3978, -1, 3979, -1, 3980, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3981,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 3982, -1, -1, -1,
+ -1, -1, 3983, -1, -1, -1, -1, -1,
+ 3984, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3985, -1,
+ -1, 3986, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3987,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 3988, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3989, -1,
+ -1, -1, -1, -1, 3990, -1, -1, -1,
+ -1, -1, -1, -1, -1, 3991, -1, -1,
+ -1, -1, 3992, -1, -1, 3993, -1, -1,
+ -1, -1, -1, -1, -1, -1, 3994, -1,
+ -1, -1, -1, -1, -1, -1, -1, 3995,
+ -1, -1, -1, -1, 3996, -1, -1, -1,
+ -1, -1, 3997, -1, 3998, -1, -1, -1,
+ -1, -1, 3999, -1, -1, -1, -1, -1,
+ 4000, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4001, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4002, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4003, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4004, -1,
+ -1, -1, -1, -1, 4005, -1, -1, -1,
+ 4006, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4007, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4008, -1,
+ -1, -1, -1, -1, 4009, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4010,
+ -1, 4011, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4012,
+ -1, -1, -1, -1, -1, -1, 4013, -1,
+ -1, -1, -1, -1, 4014, -1, -1, -1,
+ -1, -1, 4015, 4016, -1, -1, -1, -1,
+ -1, 4017, -1, -1, -1, -1, -1, -1,
+ 4018, 4019, -1, -1, -1, 4020, -1, 4021,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4022, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4023, -1, 4024, -1,
+ 4025, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4026, -1, -1, -1, -1, 4027,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4028, -1, -1, -1,
+ -1, -1, -1, 4029, -1, -1, -1, -1,
+ -1, -1, -1, 4030, -1, -1, -1, -1,
+ 4031, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4032, 4033, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4034, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4035, -1, 4036,
+ -1, -1, -1, -1, -1, -1, -1, 4037,
+ -1, 4038, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4039, 4040, -1, 4041, -1, -1, 4042,
+ -1, -1, -1, -1, 4043, -1, -1, 4044,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4045,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4046, -1, -1, -1, -1,
+ -1, -1, 4047, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4048, -1,
+ -1, 4049, -1, -1, -1, 4050, -1, -1,
+ 4051, -1, -1, -1, -1, -1, 4052, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4053, -1, 4054, 4055, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4056, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4057, 4058, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4059, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4060, -1, 4061,
+ -1, 4062, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4063, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4064, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4065, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4066, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4067,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4068, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4069, -1, -1, -1, -1, -1, -1,
+ 4070, -1, -1, -1, 4071, -1, -1, -1,
+ 4072, -1, -1, -1, -1, -1, 4073, -1,
+ -1, -1, -1, -1, -1, -1, 4074, 4075,
+ -1, -1, -1, -1, -1, -1, -1, 4076,
+ -1, -1, -1, -1, -1, -1, 4077, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4078, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4079, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4080,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4081, -1,
+ -1, -1, -1, -1, -1, -1, 4082, -1,
+ -1, -1, -1, -1, -1, -1, 4083, -1,
+ -1, 4084, -1, 4085, -1, -1, -1, -1,
+ -1, -1, -1, 4086, -1, -1, -1, -1,
+ -1, -1, 4087, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4088, -1, -1, -1,
+ -1, -1, -1, 4089, -1, 4090, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4091, -1, -1, -1, -1,
+ -1, -1, 4092, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4093, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4094, -1, -1, -1,
+ -1, 4095, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4096, -1, -1, 4097, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4098, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4099, 4100, -1, -1, 4101,
+ -1, -1, -1, -1, 4102, -1, -1, -1,
+ -1, -1, -1, 4103, -1, 4104, -1, -1,
+ 4105, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4106, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4107, -1, -1,
+ -1, -1, 4108, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4109, -1, 4110, -1,
+ -1, -1, -1, -1, 4111, -1, -1, -1,
+ -1, 4112, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4113, -1, -1, -1,
+ -1, -1, -1, -1, 4114, -1, -1, 4115,
+ -1, 4116, -1, -1, -1, -1, -1, -1,
+ -1, 4117, -1, -1, 4118, 4119, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4120, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4121, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4122, 4123,
+ 4124, -1, -1, -1, -1, -1, -1, 4125,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4126, -1,
+ -1, -1, 4127, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4128, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4129,
+ -1, 4130, -1, -1, 4131, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4132, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4133, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4134, -1, -1, 4135, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4136, -1, -1, -1, -1, -1,
+ 4137, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4138, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4139, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4140,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4141, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4142, -1, 4143, -1, -1, -1, -1,
+ 4144, -1, -1, 4145, -1, -1, -1, -1,
+ -1, -1, 4146, -1, -1, -1, -1, -1,
+ 4147, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4148, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4149, -1, -1, -1, -1, -1, -1, -1,
+ 4150, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4151, 4152, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4153, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4154, -1, -1, -1, -1,
+ -1, -1, 4155, -1, -1, 4156, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4157, -1, -1, -1, -1, -1,
+ 4158, -1, -1, -1, 4159, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4160, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4161, 4162, -1, -1, -1, -1,
+ 4163, 4164, -1, -1, -1, -1, -1, -1,
+ 4165, -1, -1, -1, 4166, -1, -1, 4167,
+ 4168, -1, -1, -1, -1, -1, 4169, -1,
+ -1, 4170, 4171, -1, -1, -1, -1, -1,
+ 4172, -1, 4173, -1, 4174, -1, 4175, 4176,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4177, -1, 4178, 4179, -1, -1, -1,
+ 4180, -1, -1, -1, -1, 4181, -1, 4182,
+ -1, -1, -1, -1, -1, -1, -1, 4183,
+ -1, -1, -1, 4184, 4185, -1, -1, -1,
+ -1, -1, 4186, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4187, -1, 4188, -1, -1, 4189, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4190, 4191,
+ -1, -1, 4192, -1, -1, -1, -1, 4193,
+ -1, 4194, -1, -1, -1, -1, 4195, -1,
+ -1, 4196, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4197, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4198, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4199, 4200, -1, -1, -1, -1, -1, -1,
+ 4201, -1, -1, -1, -1, 4202, -1, -1,
+ -1, -1, -1, -1, 4203, -1, -1, -1,
+ -1, 4204, -1, 4205, -1, -1, -1, -1,
+ 4206, -1, -1, -1, -1, -1, -1, -1,
+ 4207, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4208, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4209, -1, 4210, 4211, -1,
+ -1, -1, -1, -1, 4212, -1, -1, -1,
+ -1, -1, -1, -1, 4213, -1, 4214, -1,
+ -1, -1, -1, 4215, -1, -1, 4216, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4217, -1, 4218,
+ -1, 4219, -1, -1, -1, 4220, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4221, -1, 4222, -1, -1,
+ -1, -1, -1, 4223, -1, -1, -1, 4224,
+ -1, 4225, -1, -1, -1, -1, -1, 4226,
+ -1, 4227, -1, -1, -1, -1, -1, 4228,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4229, -1, -1, 4230, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4231, -1, 4232, -1, -1, 4233, 4234,
+ 4235, 4236, -1, -1, 4237, -1, 4238, -1,
+ -1, -1, -1, -1, -1, 4239, -1, -1,
+ -1, 4240, 4241, -1, -1, 4242, -1, 4243,
+ -1, -1, -1, 4244, -1, 4245, -1, 4246,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4247, -1, 4248, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4249,
+ -1, -1, -1, 4250, -1, -1, -1, -1,
+ -1, 4251, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4252, -1, -1, -1,
+ 4253, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4254, -1, 4255, -1, 4256, -1, -1, 4257,
+ -1, -1, -1, 4258, 4259, 4260, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4261, -1,
+ 4262, -1, 4263, 4264, -1, -1, -1, -1,
+ -1, -1, 4265, -1, -1, -1, 4266, -1,
+ -1, -1, 4267, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4268, -1, -1,
+ -1, -1, -1, 4269, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4270, -1,
+ -1, -1, -1, -1, 4271, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4272, -1, 4273, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4274, -1, 4275, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4276,
+ -1, -1, -1, 4277, -1, -1, -1, -1,
+ -1, -1, 4278, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4279, -1, -1, -1,
+ -1, -1, -1, -1, 4280, -1, -1, -1,
+ -1, -1, -1, 4281, -1, -1, -1, -1,
+ -1, -1, -1, 4282, -1, -1, -1, -1,
+ 4283, -1, -1, -1, -1, -1, -1, -1,
+ 4284, -1, -1, 4285, -1, -1, -1, 4286,
+ -1, -1, -1, -1, -1, -1, 4287, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4288, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4289, -1, -1, -1, -1,
+ -1, 4290, 4291, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4292, -1, 4293, 4294, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4295, -1, -1, -1, -1, -1, 4296, -1,
+ -1, -1, -1, -1, -1, -1, 4297, -1,
+ -1, -1, 4298, -1, -1, -1, -1, 4299,
+ -1, -1, 4300, -1, -1, -1, 4301, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4302,
+ -1, -1, -1, 4303, 4304, -1, -1, -1,
+ 4305, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4306, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4307, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4308, -1, -1, 4309, 4310,
+ -1, 4311, -1, -1, 4312, -1, -1, -1,
+ -1, 4313, -1, -1, -1, -1, -1, 4314,
+ -1, -1, -1, 4315, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4316,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4317, -1, -1, -1, -1, -1, 4318, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4319,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4320, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4321, -1,
+ 4322, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4323, -1, 4324,
+ -1, -1, 4325, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4326, -1, 4327, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4328, 4329, -1, -1, 4330, -1, 4331,
+ 4332, -1, 4333, -1, 4334, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4335,
+ -1, -1, -1, -1, -1, 4336, -1, -1,
+ -1, -1, -1, 4337, -1, 4338, -1, 4339,
+ -1, -1, -1, -1, -1, 4340, -1, 4341,
+ -1, -1, 4342, -1, -1, -1, -1, -1,
+ 4343, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4344, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4345, -1, 4346, -1, -1, -1, -1, -1,
+ 4347, -1, 4348, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4349, -1, 4350, -1, -1, -1, -1,
+ 4351, -1, -1, 4352, -1, -1, -1, -1,
+ 4353, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4354, -1, -1, -1, -1, -1,
+ -1, -1, 4355, -1, -1, -1, 4356, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4357,
+ 4358, -1, -1, -1, -1, -1, 4359, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4360, -1, -1, -1, -1, -1, 4361, 4362,
+ 4363, -1, -1, -1, -1, -1, -1, 4364,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4365, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4366, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4367, -1,
+ -1, 4368, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4369, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4370,
+ -1, -1, -1, -1, -1, 4371, -1, -1,
+ 4372, -1, -1, -1, -1, -1, 4373, -1,
+ 4374, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4375, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4376,
+ 4377, -1, -1, -1, -1, -1, -1, 4378,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4379, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4380, -1, -1, 4381, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4382, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4383, -1, -1, -1,
+ 4384, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4385, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4386, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4387, -1, -1,
+ 4388, -1, -1, -1, -1, -1, 4389, 4390,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4391, 4392, -1, 4393, -1, -1,
+ -1, 4394, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4395, -1, -1, -1, -1, 4396,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4397, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4398, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4399, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4400, -1, -1, -1, -1, -1, -1,
+ 4401, -1, -1, -1, 4402, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4403, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4404, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4405,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4406, 4407, -1, -1, -1, -1,
+ -1, -1, -1, 4408, 4409, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4410, -1, -1, -1, -1,
+ -1, 4411, -1, -1, -1, 4412, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4413, -1,
+ 4414, -1, -1, -1, 4415, -1, 4416, -1,
+ -1, -1, -1, -1, -1, -1, 4417, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4418, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4419, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4420, 4421, -1, 4422,
+ 4423, -1, -1, -1, -1, -1, 4424, -1,
+ -1, -1, -1, 4425, 4426, -1, -1, 4427,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4428, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4429, 4430, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4431, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4432, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4433, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4434, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4435, -1, -1, -1, 4436, -1, -1,
+ -1, -1, -1, 4437, -1, -1, -1, -1,
+ -1, -1, 4438, -1, -1, -1, -1, 4439,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4440, -1,
+ -1, 4441, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4442, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4443, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4444, 4445, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4446, 4447, -1, -1, -1, -1, 4448,
+ -1, 4449, -1, -1, -1, -1, -1, -1,
+ 4450, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4451, -1, -1, 4452, -1, -1,
+ -1, -1, -1, -1, -1, 4453, -1, 4454,
+ -1, -1, -1, -1, 4455, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4456, -1,
+ -1, -1, -1, 4457, -1, 4458, -1, -1,
+ -1, -1, -1, -1, 4459, 4460, -1, -1,
+ -1, -1, 4461, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4462, -1, -1, 4463, -1,
+ -1, -1, -1, -1, -1, 4464, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4465, -1, -1, -1,
+ -1, 4466, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4467, -1, -1, -1, 4468, -1, 4469, -1,
+ -1, -1, -1, -1, -1, -1, 4470, -1,
+ -1, 4471, -1, 4472, 4473, -1, -1, 4474,
+ -1, -1, -1, -1, 4475, -1, -1, -1,
+ -1, -1, 4476, -1, 4477, -1, -1, 4478,
+ -1, -1, -1, -1, -1, 4479, 4480, 4481,
+ 4482, -1, -1, -1, 4483, -1, -1, 4484,
+ 4485, -1, -1, -1, -1, -1, -1, -1,
+ 4486, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4487, -1, -1, -1,
+ -1, -1, 4488, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4489, 4490, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4491, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4492, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4493, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4494, 4495, -1, -1, -1, -1, -1, 4496,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4497, -1, -1, -1, -1, -1, -1, 4498,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4499, -1, -1, -1, -1, -1,
+ -1, 4500, -1, -1, -1, -1, -1, 4501,
+ -1, 4502, 4503, -1, -1, -1, -1, -1,
+ -1, 4504, -1, -1, -1, -1, -1, -1,
+ 4505, -1, -1, -1, -1, -1, 4506, -1,
+ -1, 4507, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4508, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4509, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4510, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4511, -1, 4512, -1, -1, -1, -1, -1,
+ -1, -1, 4513, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4514, -1, -1, 4515, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4516, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4517, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4518, -1, -1, -1,
+ -1, -1, -1, 4519, 4520, -1, 4521, 4522,
+ -1, -1, -1, 4523, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4524, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4525, -1, -1, -1, -1, 4526, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4527, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4528, -1, -1,
+ 4529, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4530, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4531,
+ 4532, -1, -1, -1, -1, -1, 4533, -1,
+ -1, -1, -1, -1, -1, 4534, -1, -1,
+ -1, -1, -1, 4535, -1, -1, -1, 4536,
+ -1, -1, -1, -1, 4537, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4538, -1, -1, -1, 4539, -1, -1,
+ -1, -1, 4540, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4541, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4542, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4543, -1, -1, -1, -1, -1,
+ -1, -1, 4544, -1, -1, -1, 4545, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4546, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4547, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4548, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4549, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4550, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4551, 4552, -1, -1, -1, -1,
+ -1, -1, -1, 4553, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4554,
+ -1, -1, -1, -1, -1, -1, 4555, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4556,
+ -1, -1, -1, -1, -1, 4557, -1, -1,
+ -1, -1, -1, -1, -1, 4558, 4559, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4560, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4561,
+ 4562, -1, -1, -1, 4563, -1, -1, 4564,
+ -1, -1, -1, -1, -1, -1, 4565, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4566,
+ -1, -1, -1, 4567, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4568, 4569, -1, -1, -1,
+ -1, -1, -1, -1, 4570, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4571, 4572, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4573, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4574, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4575,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4576, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4577, -1, -1, 4578,
+ -1, -1, -1, 4579, -1, -1, -1, -1,
+ -1, -1, 4580, -1, -1, -1, -1, -1,
+ -1, -1, 4581, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4582,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4583,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4584, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4585, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4586, -1, -1, 4587,
+ -1, -1, -1, -1, -1, -1, -1, 4588,
+ -1, -1, -1, -1, -1, -1, -1, 4589,
+ -1, -1, -1, -1, -1, 4590, -1, -1,
+ 4591, -1, -1, 4592, -1, -1, -1, -1,
+ -1, -1, 4593, -1, -1, -1, 4594, -1,
+ -1, -1, -1, -1, 4595, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4596, -1, -1,
+ -1, -1, 4597, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4598, -1, 4599,
+ -1, 4600, -1, -1, -1, -1, -1, -1,
+ -1, 4601, -1, 4602, -1, 4603, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4604, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4605, 4606, -1,
+ -1, -1, -1, 4607, 4608, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4609, 4610, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4611, 4612, 4613,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4614, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4615, -1, -1, -1, -1,
+ -1, -1, -1, 4616, -1, -1, -1, -1,
+ -1, 4617, -1, -1, -1, -1, -1, -1,
+ -1, 4618, -1, -1, -1, -1, -1, -1,
+ 4619, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4620,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4621, -1, 4622, -1, -1, -1, -1, -1,
+ -1, 4623, -1, -1, -1, -1, -1, -1,
+ 4624, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4625, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4626, -1, -1, -1, -1, -1, -1, -1,
+ 4627, -1, -1, -1, -1, -1, 4628, 4629,
+ -1, -1, 4630, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4631, -1, -1, -1, 4632, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4633, 4634,
+ -1, -1, -1, -1, -1, -1, -1, 4635,
+ 4636, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4637, 4638, -1, -1, -1,
+ -1, 4639, -1, -1, -1, -1, 4640, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4641, -1, 4642, -1,
+ -1, -1, -1, -1, -1, 4643, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4644,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4645, -1, -1, -1, -1, -1, -1, 4646,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4647, -1, -1,
+ -1, 4648, -1, -1, -1, 4649, -1, -1,
+ -1, 4650, -1, -1, -1, -1, 4651, -1,
+ -1, 4652, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4653, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4654, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4655, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4656, -1, 4657, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4658, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4659, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4660, -1, -1, -1,
+ 4661, -1, -1, -1, -1, -1, -1, 4662,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4663, -1, -1, -1, 4664, 4665, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4666, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4667, -1, -1, 4668, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4669, -1, -1, -1, -1, -1, 4670, 4671,
+ -1, 4672, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4673, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4674, -1,
+ -1, -1, -1, -1, 4675, -1, 4676, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4677, -1, -1, -1, -1, 4678, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4679, -1, 4680, -1, 4681, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4682, -1, -1, -1, 4683,
+ -1, -1, -1, -1, 4684, -1, -1, -1,
+ -1, -1, -1, 4685, -1, 4686, -1, -1,
+ -1, 4687, -1, -1, 4688, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4689, -1, -1, -1, 4690,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4691, -1, -1, -1, -1,
+ -1, 4692, -1, -1, -1, -1, -1, 4693,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4694, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4695, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4696,
+ -1, -1, -1, 4697, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4698, -1, -1, -1, 4699, -1, 4700, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4701, -1, -1, -1, -1, 4702,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4703, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4704, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4705, 4706, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4707, -1, -1, -1, -1, -1, 4708, 4709,
+ -1, -1, -1, 4710, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4711, -1, -1, -1, -1, -1,
+ -1, 4712, -1, -1, 4713, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4714, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4715, -1, -1, -1, -1, -1, -1,
+ 4716, -1, 4717, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4718, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4719,
+ -1, -1, -1, -1, -1, -1, 4720, -1,
+ 4721, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4722,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4723, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4724, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4725, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4726, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4727, 4728, -1, -1, -1, -1, -1,
+ 4729, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4730, -1, -1,
+ -1, -1, 4731, -1, 4732, -1, -1, 4733,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4734, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4735, -1, -1, 4736, -1, -1, -1,
+ -1, -1, -1, -1, 4737, -1, -1, 4738,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4739, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4740, -1, -1, -1, -1, -1,
+ -1, -1, 4741, 4742, -1, -1, -1, -1,
+ -1, 4743, -1, -1, -1, -1, -1, 4744,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4745, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4746, 4747, 4748,
+ -1, 4749, -1, -1, -1, 4750, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4751, -1, -1, -1, 4752,
+ -1, -1, -1, -1, -1, -1, -1, 4753,
+ -1, -1, -1, -1, -1, 4754, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4755,
+ -1, 4756, -1, -1, -1, -1, -1, -1,
+ 4757, -1, -1, -1, 4758, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4759, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4760, -1, -1, -1, -1, -1,
+ 4761, -1, -1, -1, -1, -1, 4762, -1,
+ -1, -1, -1, -1, -1, 4763, 4764, -1,
+ 4765, 4766, -1, -1, -1, 4767, -1, 4768,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4769, -1, -1, -1, -1, -1,
+ 4770, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4771, -1, -1, 4772, -1, -1, 4773,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4774, -1, -1, -1, -1, 4775, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4776, 4777, -1,
+ -1, -1, -1, -1, -1, 4778, -1, -1,
+ 4779, 4780, -1, -1, -1, -1, -1, 4781,
+ -1, -1, -1, 4782, -1, 4783, -1, -1,
+ 4784, -1, -1, 4785, -1, -1, -1, 4786,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4787, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4788, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4789,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4790, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4791, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4792, -1, -1, -1, -1, -1, 4793,
+ 4794, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4795, -1,
+ -1, 4796, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4797, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4798, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4799, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4800, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4801, -1, -1,
+ 4802, -1, -1, -1, 4803, -1, -1, -1,
+ -1, -1, -1, 4804, -1, 4805, -1, -1,
+ -1, -1, -1, -1, 4806, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4807, -1, -1, -1,
+ -1, -1, 4808, -1, 4809, 4810, 4811, -1,
+ -1, -1, -1, -1, -1, 4812, -1, 4813,
+ 4814, -1, -1, -1, -1, 4815, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4816, -1, -1, -1, -1, -1, -1,
+ 4817, -1, -1, -1, 4818, -1, 4819, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4820,
+ -1, 4821, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4822, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4823, -1, -1, -1,
+ 4824, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4825, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4826, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4827, -1, -1,
+ -1, -1, -1, -1, 4828, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4829, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4830, -1, -1, 4831, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4832, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4833, -1, -1,
+ -1, -1, -1, -1, -1, 4834, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4835, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4836, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4837, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4838, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4839, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4840, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4841, -1, -1, 4842, -1, -1,
+ -1, -1, -1, -1, 4843, -1, -1, -1,
+ -1, -1, -1, -1, 4844, -1, 4845, -1,
+ -1, -1, -1, -1, -1, 4846, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4847, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4848, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4849, -1, -1,
+ -1, 4850, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4851, -1, -1, -1, -1, -1,
+ 4852, 4853, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4854, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4855, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4856, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4857, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4858, -1, -1, -1,
+ 4859, -1, -1, 4860, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4861, -1, -1, -1, -1, -1,
+ 4862, -1, 4863, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4864,
+ 4865, -1, -1, -1, -1, -1, 4866, -1,
+ -1, -1, 4867, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4868, -1, -1, -1, -1, -1, -1, 4869,
+ -1, -1, -1, -1, 4870, 4871, 4872, -1,
+ 4873, -1, -1, -1, 4874, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4875, -1, -1,
+ 4876, -1, -1, 4877, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4878, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4879, 4880, -1, -1,
+ 4881, -1, 4882, -1, -1, -1, -1, -1,
+ -1, 4883, -1, -1, -1, 4884, -1, -1,
+ -1, -1, -1, 4885, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4886, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4887, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4888, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4889, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4890, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4891,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4892, 4893, -1, -1, -1, -1, -1, 4894,
+ -1, 4895, -1, 4896, -1, -1, 4897, 4898,
+ -1, -1, -1, -1, -1, -1, -1, 4899,
+ -1, 4900, -1, 4901, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4902, -1, 4903, 4904, -1, -1, 4905, -1,
+ -1, -1, -1, -1, -1, -1, 4906, -1,
+ -1, -1, -1, -1, -1, -1, 4907, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4908, -1, -1, -1, -1, 4909,
+ -1, -1, -1, -1, -1, -1, 4910, -1,
+ 4911, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4912, 4913, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4914,
+ 4915, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4916, -1, 4917, -1, 4918, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4919, -1, -1, -1, -1, -1, 4920,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4921,
+ -1, -1, -1, -1, 4922, -1, -1, -1,
+ -1, 4923, -1, -1, -1, -1, 4924, -1,
+ -1, -1, 4925, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4926, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4927, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4928, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4929, -1, -1, -1, -1, 4930,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4931, 4932, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4933, -1, -1,
+ -1, -1, -1, -1, 4934, -1, -1, -1,
+ -1, -1, -1, 4935, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4936, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4937, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4938, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4939, -1, -1, -1, -1, -1,
+ -1, 4940, 4941, -1, -1, -1, -1, 4942,
+ -1, 4943, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4944, -1,
+ -1, -1, -1, -1, 4945, 4946, -1, -1,
+ -1, -1, 4947, -1, -1, -1, -1, -1,
+ -1, -1, 4948, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4949, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4950, -1,
+ -1, -1, -1, -1, -1, -1, 4951, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4952, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4953, -1, -1,
+ -1, 4954, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4955, -1, -1, -1, -1,
+ -1, 4956, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4957, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4958, -1, -1, 4959, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4960, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4961, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4962, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4963, -1, -1, -1, -1,
+ -1, -1, -1, 4964, -1, -1, -1, -1,
+ -1, -1, 4965, -1, 4966, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4967, -1, 4968, -1, -1, -1, -1, -1,
+ -1, -1, 4969, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4970, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4971, -1, 4972, -1, -1,
+ -1, -1, -1, -1, -1, 4973, -1, -1,
+ -1, -1, -1, -1, -1, 4974, -1, 4975,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4976, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4977, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4978, 4979, -1, -1,
+ 4980, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4981, -1, -1, 4982, -1,
+ -1, -1, -1, -1, 4983, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4984, -1, -1, -1, -1, 4985, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4986, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4987,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 4988, -1, -1, -1, -1,
+ -1, -1, -1, -1, 4989, -1, -1, 4990,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 4991, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 4992, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 4993,
+ -1, -1, -1, -1, -1, -1, -1, 4994,
+ -1, -1, -1, -1, -1, -1, 4995, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 4996, -1,
+ -1, -1, -1, -1, -1, 4997, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 4998, -1, 4999, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5000, -1,
+ -1, -1, -1, 5001, -1, -1, -1, -1,
+ 5002, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5003, -1, -1, -1,
+ 5004, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5005,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5006, -1, -1, -1, -1, 5007, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5008, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5009, -1, -1, 5010, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5011, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5012, 5013, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5014, 5015, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5016, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5017, -1, -1, -1,
+ -1, 5018, -1, -1, 5019, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5020, -1, -1, -1,
+ -1, 5021, -1, -1, -1, -1, -1, -1,
+ -1, 5022, -1, -1, -1, 5023, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5024, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5025, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5026, -1,
+ -1, -1, -1, 5027, -1, 5028, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5029,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5030, -1, -1, -1, -1, -1,
+ -1, 5031, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5032,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5033, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5034, 5035,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5036, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5037, -1,
+ -1, -1, -1, 5038, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5039, -1, -1, -1, -1, 5040, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5041, -1, -1, -1,
+ 5042, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5043, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5044,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5045, -1, -1,
+ -1, -1, -1, 5046, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5047, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5048, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5049, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5050, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5051, -1, -1,
+ 5052, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5053,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5054, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5055, -1, -1, 5056, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5057, -1, -1,
+ -1, 5058, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5059, -1, -1, -1, -1,
+ -1, -1, -1, 5060, -1, -1, -1, -1,
+ -1, -1, -1, 5061, -1, -1, -1, -1,
+ -1, -1, 5062, -1, -1, -1, 5063, -1,
+ -1, -1, -1, -1, -1, -1, 5064, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5065,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5066, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5067,
+ -1, -1, -1, -1, -1, -1, 5068, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5069, -1, -1, -1, 5070, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5071, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5072, -1, 5073, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5074, -1, -1, -1, -1, -1,
+ 5075, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5076, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5077, -1, -1, -1, -1, -1, 5078, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5079, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5080, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5081, -1, -1, -1, -1,
+ -1, 5082, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5083, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5084, -1, 5085, -1, -1, -1, -1,
+ -1, -1, -1, 5086, -1, -1, -1, -1,
+ -1, 5087, -1, -1, 5088, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5089, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5090,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5091, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5092, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5093, -1, -1,
+ -1, -1, 5094, -1, 5095, -1, -1, -1,
+ -1, 5096, 5097, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5098, -1, -1, 5099, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5100, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5101, -1, 5102, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5103, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5104, 5105, -1, -1, 5106, -1,
+ -1, -1, -1, -1, -1, 5107, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5108, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5109, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5110, 5111, 5112, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5113, -1, -1, -1, -1, -1, 5114,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5115, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5116, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5117, -1, -1, -1,
+ 5118, -1, -1, -1, -1, -1, -1, 5119,
+ -1, -1, -1, -1, -1, 5120, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5121, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5122, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5123, -1, -1, -1, -1, -1, 5124,
+ -1, -1, -1, 5125, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5126, -1, -1, 5127,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5128, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5129, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5130, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5131, -1, -1,
+ -1, -1, -1, -1, -1, 5132, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5133, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5134, -1, -1, -1, -1, -1, -1,
+ -1, 5135, -1, -1, -1, -1, -1, -1,
+ 5136, -1, 5137, -1, -1, 5138, -1, -1,
+ -1, -1, -1, 5139, -1, 5140, -1, -1,
+ 5141, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5142, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5143, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5144, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5145, -1, -1, -1,
+ -1, -1, -1, -1, 5146, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5147, 5148, -1, -1, -1,
+ -1, -1, 5149, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5150, 5151, -1, -1, -1, -1,
+ 5152, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5153, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5154,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5155,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5156, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5157, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5158, -1, -1, -1,
+ 5159, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5160, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5161, -1, -1,
+ 5162, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5163, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5164, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5165, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5166, -1, -1, -1, -1, -1, -1,
+ 5167, -1, 5168, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5169, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5170, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5171, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5172, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5173, -1, -1, -1,
+ 5174, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5175, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5176, -1, -1, -1, -1, -1,
+ -1, -1, 5177, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5178,
+ -1, 5179, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5180, 5181, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5182, 5183, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5184, 5185, -1, -1, -1, 5186,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5187,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5188, -1, -1,
+ -1, -1, 5189, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5190, -1,
+ -1, -1, -1, -1, 5191, -1, 5192, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5193, -1, -1,
+ 5194, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5195, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5196, -1, -1,
+ 5197, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5198, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5199, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5200, -1, -1, 5201, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5202, -1, -1, -1, -1, -1, -1, 5203,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5204, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5205,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5206, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5207, -1,
+ -1, -1, 5208, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5209, -1, 5210, -1,
+ -1, 5211, -1, -1, -1, -1, -1, -1,
+ -1, 5212, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5213, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5214, -1, 5215, -1, -1, -1, -1, -1,
+ 5216, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5217, -1, -1, -1, -1,
+ -1, -1, 5218, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5219, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5220, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5221, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5222, 5223, -1,
+ -1, -1, -1, -1, -1, 5224, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5225, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5226, -1, 5227, 5228, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5229, -1,
+ -1, -1, -1, -1, -1, -1, 5230, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5231,
+ -1, -1, -1, -1, -1, 5232, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5233, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5234, -1, -1, -1, -1, 5235,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5236, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5237, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5238, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5239,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5240, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5241, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5242, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5243, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5244, -1,
+ -1, -1, -1, 5245, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5246, -1,
+ 5247, -1, -1, -1, -1, -1, -1, 5248,
+ -1, -1, -1, -1, -1, 5249, -1, -1,
+ -1, -1, -1, -1, -1, 5250, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5251, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5252, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5253, -1, -1, -1, -1, -1,
+ -1, 5254, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5255, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5256, -1, -1, -1,
+ -1, -1, 5257, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5258,
+ -1, -1, -1, -1, -1, -1, -1, 5259,
+ -1, 5260, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5261, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5262, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5263, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5264, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5265, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5266, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5267,
+ 5268, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5269, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5270,
+ -1, 5271, -1, -1, -1, -1, -1, 5272,
+ -1, -1, -1, -1, -1, 5273, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5274, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5275, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5276, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5277, -1, -1, 5278, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5279, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5280, -1, 5281,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5282, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5283, -1, -1,
+ -1, -1, 5284, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5285, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5286, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5287, -1, -1, -1, -1, 5288,
+ -1, -1, -1, -1, -1, -1, -1, 5289,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5290, 5291, -1, -1, -1, -1, 5292,
+ 5293, -1, -1, -1, 5294, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5295, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5296, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5297, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5298, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5299, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5300, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5301, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5302, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5303, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5304, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5305, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5306, -1, 5307, -1, -1, -1, -1, 5308,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5309, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5310, -1, -1, -1, -1, -1, -1, -1,
+ 5311, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5312, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5313, -1, 5314, -1, -1, -1, -1,
+ 5315, -1, -1, -1, -1, -1, -1, 5316,
+ -1, -1, -1, -1, 5317, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5318, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5319, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5320, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5321, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5322, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5323, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5324, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5325, -1, -1, 5326, -1,
+ -1, -1, 5327, -1, -1, -1, -1, -1,
+ -1, -1, 5328, 5329, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5330,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5331,
+ -1, 5332, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5333, -1, -1, -1, -1, -1, 5334, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5335, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5336,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5337, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5338, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5339,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5340, -1, -1, -1, 5341,
+ -1, -1, -1, -1, -1, -1, -1, 5342,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5343, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5344,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5345, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5346, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5347, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5348, -1, 5349, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5350,
+ -1, -1, -1, -1, -1, 5351, -1, -1,
+ -1, -1, -1, -1, 5352, -1, 5353, -1,
+ -1, -1, -1, 5354, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5355, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5356, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5357, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5358, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5359, -1, -1, -1, -1, -1,
+ 5360, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5361, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5362, -1, -1, -1,
+ -1, -1, 5363, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5364, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5365, -1, -1, -1, -1, -1,
+ 5366, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5367, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5368, -1, 5369, -1, -1,
+ -1, -1, -1, -1, 5370, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5371, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5372,
+ -1, -1, -1, -1, -1, -1, 5373, -1,
+ -1, 5374, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5375, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5376, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5377, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5378, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5379, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5380,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5381, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5382, -1, -1, -1, -1,
+ 5383, -1, 5384, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5385, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5386, -1, 5387, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5388, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5389, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5390, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5391,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5392, 5393,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5394,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5395, -1,
+ -1, -1, 5396, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5397,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5398, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5399, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5400, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5401, -1, -1, 5402, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5403, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5404, -1, -1, -1, 5405,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5406, -1, -1, 5407, -1, -1,
+ -1, 5408, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5409, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5410, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5411, 5412,
+ -1, -1, -1, -1, -1, 5413, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5414, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5415, -1, -1,
+ -1, -1, 5416, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5417, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5418,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5419, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5420, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5421, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5422, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5423, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5424, -1, 5425, -1, 5426, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5427,
+ -1, 5428, -1, -1, 5429, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5430, -1, 5431, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5432,
+ -1, -1, -1, -1, 5433, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5434, -1, -1, -1, -1,
+ -1, 5435, -1, 5436, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5437, -1, 5438, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5439, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5440, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5441, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5442,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5443,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5444,
+ -1, -1, -1, 5445, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5446, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5447, -1, -1, -1, 5448,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5449, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5450, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5451, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5452, -1, 5453, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5454, -1, -1,
+ -1, -1, -1, -1, 5455, -1, -1, 5456,
+ 5457, -1, -1, 5458, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5459, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5460,
+ 5461, -1, 5462, -1, -1, -1, -1, -1,
+ -1, -1, 5463, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5464,
+ -1, -1, -1, -1, -1, -1, -1, 5465,
+ -1, -1, -1, -1, -1, -1, -1, 5466,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5467, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5468, -1, -1, -1,
+ -1, -1, 5469, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5470, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5471, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5472, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5473, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5474, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5475, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5476, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5477,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5478, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5479, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5480, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5481, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5482, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5483, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5484, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5485, 5486, -1, 5487,
+ -1, -1, -1, -1, -1, 5488, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5489, 5490,
+ -1, -1, -1, -1, -1, 5491, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5492,
+ -1, -1, -1, 5493, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5494, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5495, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5496, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5497, -1, -1,
+ 5498, -1, -1, -1, -1, -1, -1, -1,
+ 5499, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5500, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5501,
+ -1, -1, -1, -1, 5502, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5503, -1,
+ 5504, -1, -1, -1, 5505, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5506, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5507, 5508, -1,
+ -1, -1, 5509, 5510, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5511, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5512, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5513,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5514, 5515, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5516, -1, -1, 5517, 5518,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5519, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5520, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5521, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5522, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5523, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5524, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5525, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5526, -1, -1, -1, -1,
+ -1, 5527, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5528, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5529, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5530, -1, -1,
+ 5531, -1, -1, -1, -1, 5532, -1, 5533,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5534,
+ -1, -1, -1, -1, 5535, -1, -1, -1,
+ -1, 5536, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5537, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5538, -1, 5539,
+ 5540, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5541, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5542, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5543, -1, -1,
+ -1, 5544, -1, -1, -1, -1, -1, -1,
+ 5545, 5546, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5547, -1, -1, -1, -1, -1, -1, 5548,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5549, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5550, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5551, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5552, -1, -1, -1, -1, -1, 5553,
+ -1, -1, -1, 5554, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5555, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5556, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5557, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5558, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5559, -1, -1, -1, -1, 5560, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5561, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5562, -1, -1, 5563, 5564,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5565, 5566, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5567, 5568,
+ -1, -1, -1, -1, -1, 5569, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5570, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5571, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5572, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5573, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5574, -1, -1, -1, 5575,
+ -1, -1, -1, -1, -1, -1, 5576, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5577, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5578, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5579, -1, 5580,
+ -1, -1, 5581, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5582, 5583, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5584, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5585, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5586,
+ -1, 5587, 5588, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5589, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5590, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5591,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5592, -1, -1,
+ -1, -1, 5593, -1, -1, 5594, -1, -1,
+ -1, -1, 5595, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5596, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5597, -1, -1, 5598, -1, -1, 5599,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5600, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5601, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5602, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5603, -1,
+ -1, -1, -1, -1, -1, 5604, -1, -1,
+ -1, -1, -1, 5605, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5606, 5607, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5608, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5609, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5610, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5611, 5612, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5613, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5614, -1, -1, -1, -1, -1, -1,
+ -1, 5615, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5616,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5617, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5618,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5619, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5620, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5621, 5622, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5623, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5624, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5625, -1, 5626, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5627, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5628, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5629, 5630, -1, -1, -1,
+ -1, -1, -1, -1, 5631, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5632, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5633,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5634, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5635, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5636, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5637, -1, -1, -1, -1, 5638,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5639, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5640,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5641, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5642, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5643, 5644,
+ -1, 5645, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5646, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5647, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5648, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5649, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5650, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5651, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5652, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5653, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5654, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5655, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5656, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5657, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5658, 5659, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5660, -1, -1,
+ -1, 5661, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5662, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5663, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5664, -1, -1,
+ -1, -1, -1, -1, 5665, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5666, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5667,
+ -1, -1, -1, -1, 5668, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5669, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5670, -1, -1, -1, -1, -1,
+ 5671, -1, -1, -1, 5672, -1, -1, -1,
+ -1, -1, -1, 5673, -1, -1, -1, -1,
+ -1, 5674, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5675,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5676, 5677, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5678, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5679, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5680, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5681, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5682,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5683, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5684, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5685, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5686, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5687, -1, -1, 5688, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5689, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5690, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5691, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5692, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5693, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5694,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5695, -1, -1, -1, -1,
+ 5696, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5697, -1, -1, -1, -1, -1, 5698,
+ 5699, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5700, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5701, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5702, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5703,
+ -1, 5704, -1, -1, 5705, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5706, -1, -1, -1, -1, 5707, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5708,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5709, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5710, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5711, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5712, -1, -1,
+ 5713, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5714,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5715, -1, -1, -1, 5716, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5717, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5718, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5719, -1, -1, -1, -1, -1, -1,
+ -1, 5720, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5721, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5722, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5723, -1, -1, -1,
+ -1, 5724, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5725, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5726, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5727, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5728, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5729, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5730, -1, -1, -1, -1, -1,
+ 5731, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5732, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5733,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5734, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5735,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5736, -1, 5737, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5738, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5739, 5740, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5741, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5742,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5743, -1, -1, -1, -1, -1, -1,
+ 5744, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5745, -1,
+ -1, -1, -1, -1, -1, 5746, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5747, -1, 5748, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5749, -1, -1, -1,
+ -1, -1, 5750, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5751, 5752, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5753,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5754, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5755, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5756,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5757, -1, 5758, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5759, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5760, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5761, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5762, -1, 5763,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5764, -1, -1,
+ -1, -1, 5765, -1, -1, -1, -1, -1,
+ -1, -1, 5766, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5767, -1, -1, -1, -1, -1,
+ 5768, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5769,
+ 5770, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5771, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5772, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5773, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5774, -1, -1, -1, -1, -1, -1,
+ -1, 5775, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5776, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5777,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5778, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5779, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5780, -1, -1, -1, -1,
+ -1, -1, -1, 5781, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5782, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5783, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5784, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5785, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5786,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5787, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5788, -1, -1, -1,
+ -1, 5789, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5790, -1, 5791, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5792, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5793, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5794, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5795, 5796, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5797, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5798, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5799, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5800, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5801, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5802, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5803, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5804, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5805, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5806, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5807, -1, -1, 5808, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5809, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5810, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5811, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5812, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5813, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5814, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5815, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5816,
+ -1, -1, -1, -1, -1, -1, 5817, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5818, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5819, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5820, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5821, -1, -1, -1,
+ -1, -1, 5822, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5823,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5824,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5825, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5826, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5827, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5828, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5829, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5830, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5831, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5832, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5833, -1, -1,
+ 5834, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5835, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5836, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5837, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5838,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5839,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5840, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5841, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5842, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5843,
+ 5844, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5845, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5846,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5847, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5848, -1, 5849, 5850, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5851, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5852, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5853, -1, -1, -1, -1,
+ -1, -1, 5854, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5855, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5856, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5857, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5858, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5859, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5860, -1, -1,
+ -1, 5861, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5862, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5863, -1, 5864, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5865, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5866, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5867, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5868,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5869, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5870, -1, -1, -1, 5871,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5872, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5873, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5874, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5875, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5876, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5877, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5878, 5879, -1,
+ 5880, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5881, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5882,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5883, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5884, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5885, 5886, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5887, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5888, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5889, 5890,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5891, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5892, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5893, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5894, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5895, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5896, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5897, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5898, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5899, -1, -1, -1, 5900,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5901,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5902,
+ -1, 5903, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5904, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5905, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5906, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5907,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5908, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5909, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5910, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5911, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5912, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5913, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5914, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5915, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5916,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5917, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5918, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5919, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5920, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5921, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5922, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5923, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5924, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5925, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5926, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5927, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5928, -1, -1, -1, 5929,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5930, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5931, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5932, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5933, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5934, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5935, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5936, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5937, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5938, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5939, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5940, -1,
+ -1, -1, -1, -1, -1, 5941, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5942, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5943, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5944,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5945, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5946, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5947, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5948, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5949, -1, -1,
+ -1, -1, -1, -1, -1, 5950, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5951, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5952, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5953, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5954, -1, -1, -1, -1, -1,
+ 5955, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5956, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5957, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5958, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5959, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5960, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5961, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5962, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5963, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5964, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5965, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5966, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5967, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5968, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5969, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5970, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5971, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5972, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5973, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5974, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5975, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5976,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5977, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5978, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5979, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5980, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5981, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 5982,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5983, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5984, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5985, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5986, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5987, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5988, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5989, -1, -1, -1, -1, -1, 5990,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5991, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 5992, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 5993, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 5994, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5995, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5996, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 5997, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 5998, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 5999, -1, -1,
+ -1, -1, -1, -1, -1, 6000, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6001, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6002, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6003, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6004, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6005, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6006,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6007, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6008, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6009, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6010, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6011, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6012, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6013, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6014, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6015, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6016, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6017, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6018, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6019, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6020, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6021,
+ -1, -1, -1, -1, -1, -1, 6022, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6023, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6024, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6025,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6026, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6027, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6028, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6029, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6030, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6031, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6032, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6033,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6034, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6035, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6036,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6037, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6038, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6039, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6040, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6041, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6042, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6043, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6044, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6045, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6046, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6047, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6048,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6049, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6050, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6051, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6052, -1, -1, -1, -1, -1, -1,
+ 6053, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6054, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6055, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6056, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6057, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6058, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6059, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6060, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6061, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6062, -1, -1, -1, -1, 6063, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6064, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6065, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6066, -1, -1, -1, -1, -1, -1,
+ -1, 6067, -1, -1, -1, -1, 6068, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6069, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6070, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6071, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6072,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6073, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6074, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6075, -1,
+ -1, -1, -1, -1, -1, -1, 6076, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6077, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6078, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6079, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6080, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6081, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6082, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6083, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6084, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6085, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6086, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6087, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6088, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 6089, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6090, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6091,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6092, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6093, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6094, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6095, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 6096, -1, -1, -1, -1,
+ 6097, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 6098, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 6099, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 6100,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 6101, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, 6102
+ };
+
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= 0)
+ {
+ register int index = lookup[key];
+
+ if (index >= 0)
+ {
+ register const char *s = wordlist[index].name_offset + stringpool;
+
+ if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0')
+ return &wordlist[index];
+ }
+ }
+ }
+ return 0;
+}
+#line 6117 "effective_tld_names.gperf"
+
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names.dat b/chromium/net/base/registry_controlled_domains/effective_tld_names.dat
new file mode 100644
index 00000000000..4a39bf94935
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names.dat
@@ -0,0 +1,7010 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Chromium note: this is based on Mozilla's file:
+// http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1
+
+
+// ===BEGIN ICANN DOMAINS===
+
+// ac : http://en.wikipedia.org/wiki/.ac
+ac
+com.ac
+edu.ac
+gov.ac
+net.ac
+mil.ac
+org.ac
+
+// ad : http://en.wikipedia.org/wiki/.ad
+ad
+nom.ad
+
+// ae : http://en.wikipedia.org/wiki/.ae
+// see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php
+ae
+co.ae
+net.ae
+org.ae
+sch.ae
+ac.ae
+gov.ae
+mil.ae
+
+// aero : see http://www.information.aero/index.php?id=66
+aero
+accident-investigation.aero
+accident-prevention.aero
+aerobatic.aero
+aeroclub.aero
+aerodrome.aero
+agents.aero
+aircraft.aero
+airline.aero
+airport.aero
+air-surveillance.aero
+airtraffic.aero
+air-traffic-control.aero
+ambulance.aero
+amusement.aero
+association.aero
+author.aero
+ballooning.aero
+broker.aero
+caa.aero
+cargo.aero
+catering.aero
+certification.aero
+championship.aero
+charter.aero
+civilaviation.aero
+club.aero
+conference.aero
+consultant.aero
+consulting.aero
+control.aero
+council.aero
+crew.aero
+design.aero
+dgca.aero
+educator.aero
+emergency.aero
+engine.aero
+engineer.aero
+entertainment.aero
+equipment.aero
+exchange.aero
+express.aero
+federation.aero
+flight.aero
+freight.aero
+fuel.aero
+gliding.aero
+government.aero
+groundhandling.aero
+group.aero
+hanggliding.aero
+homebuilt.aero
+insurance.aero
+journal.aero
+journalist.aero
+leasing.aero
+logistics.aero
+magazine.aero
+maintenance.aero
+marketplace.aero
+media.aero
+microlight.aero
+modelling.aero
+navigation.aero
+parachuting.aero
+paragliding.aero
+passenger-association.aero
+pilot.aero
+press.aero
+production.aero
+recreation.aero
+repbody.aero
+res.aero
+research.aero
+rotorcraft.aero
+safety.aero
+scientist.aero
+services.aero
+show.aero
+skydiving.aero
+software.aero
+student.aero
+taxi.aero
+trader.aero
+trading.aero
+trainer.aero
+union.aero
+workinggroup.aero
+works.aero
+
+// af : http://www.nic.af/help.jsp
+af
+gov.af
+com.af
+org.af
+net.af
+edu.af
+
+// ag : http://www.nic.ag/prices.htm
+ag
+com.ag
+org.ag
+net.ag
+co.ag
+nom.ag
+
+// ai : http://nic.com.ai/
+ai
+off.ai
+com.ai
+net.ai
+org.ai
+
+// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31
+al
+com.al
+edu.al
+gov.al
+mil.al
+net.al
+org.al
+
+// am : http://en.wikipedia.org/wiki/.am
+am
+
+// an : http://www.una.an/an_domreg/default.asp
+an
+com.an
+net.an
+org.an
+edu.an
+
+// ao : http://en.wikipedia.org/wiki/.ao
+// http://www.dns.ao/REGISTR.DOC
+ao
+ed.ao
+gv.ao
+og.ao
+co.ao
+pb.ao
+it.ao
+
+// aq : http://en.wikipedia.org/wiki/.aq
+aq
+
+// ar : http://en.wikipedia.org/wiki/.ar
+*.ar
+!congresodelalengua3.ar
+!educ.ar
+!gobiernoelectronico.ar
+!mecon.ar
+!nacion.ar
+!nic.ar
+!promocion.ar
+!retina.ar
+!uba.ar
+
+// arpa : http://en.wikipedia.org/wiki/.arpa
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+e164.arpa
+in-addr.arpa
+ip6.arpa
+iris.arpa
+uri.arpa
+urn.arpa
+
+// as : http://en.wikipedia.org/wiki/.as
+as
+gov.as
+
+// asia : http://en.wikipedia.org/wiki/.asia
+asia
+
+// at : http://en.wikipedia.org/wiki/.at
+// Confirmed by registry <it@nic.at> 2008-06-17
+at
+ac.at
+co.at
+gv.at
+or.at
+
+// au : http://en.wikipedia.org/wiki/.au
+// http://www.auda.org.au/
+// 2LDs
+com.au
+net.au
+org.au
+edu.au
+gov.au
+asn.au
+id.au
+// Historic 2LDs (closed to new registration, but sites still exist)
+info.au
+conf.au
+oz.au
+// CGDNs - http://www.cgdn.org.au/
+act.au
+nsw.au
+nt.au
+qld.au
+sa.au
+tas.au
+vic.au
+wa.au
+// 3LDs
+act.edu.au
+nsw.edu.au
+nt.edu.au
+qld.edu.au
+sa.edu.au
+tas.edu.au
+vic.edu.au
+wa.edu.au
+act.gov.au
+// Removed at request of Shae.Donelan@services.nsw.gov.au, 2010-03-04
+// nsw.gov.au
+nt.gov.au
+qld.gov.au
+sa.gov.au
+tas.gov.au
+vic.gov.au
+wa.gov.au
+
+// aw : http://en.wikipedia.org/wiki/.aw
+aw
+com.aw
+
+// ax : http://en.wikipedia.org/wiki/.ax
+ax
+
+// az : http://en.wikipedia.org/wiki/.az
+az
+com.az
+net.az
+int.az
+gov.az
+org.az
+edu.az
+info.az
+pp.az
+mil.az
+name.az
+pro.az
+biz.az
+
+// ba : http://en.wikipedia.org/wiki/.ba
+ba
+org.ba
+net.ba
+edu.ba
+gov.ba
+mil.ba
+unsa.ba
+unbi.ba
+co.ba
+com.ba
+rs.ba
+
+// bb : http://en.wikipedia.org/wiki/.bb
+bb
+biz.bb
+com.bb
+edu.bb
+gov.bb
+info.bb
+net.bb
+org.bb
+store.bb
+
+// bd : http://en.wikipedia.org/wiki/.bd
+*.bd
+
+// be : http://en.wikipedia.org/wiki/.be
+// Confirmed by registry <tech@dns.be> 2008-06-08
+be
+ac.be
+
+// bf : http://en.wikipedia.org/wiki/.bf
+bf
+gov.bf
+
+// bg : http://en.wikipedia.org/wiki/.bg
+// https://www.register.bg/user/static/rules/en/index.html
+bg
+a.bg
+b.bg
+c.bg
+d.bg
+e.bg
+f.bg
+g.bg
+h.bg
+i.bg
+j.bg
+k.bg
+l.bg
+m.bg
+n.bg
+o.bg
+p.bg
+q.bg
+r.bg
+s.bg
+t.bg
+u.bg
+v.bg
+w.bg
+x.bg
+y.bg
+z.bg
+0.bg
+1.bg
+2.bg
+3.bg
+4.bg
+5.bg
+6.bg
+7.bg
+8.bg
+9.bg
+
+// bh : http://en.wikipedia.org/wiki/.bh
+bh
+com.bh
+edu.bh
+net.bh
+org.bh
+gov.bh
+
+// bi : http://en.wikipedia.org/wiki/.bi
+// http://whois.nic.bi/
+bi
+co.bi
+com.bi
+edu.bi
+or.bi
+org.bi
+
+// biz : http://en.wikipedia.org/wiki/.biz
+biz
+
+// bj : http://en.wikipedia.org/wiki/.bj
+bj
+asso.bj
+barreau.bj
+gouv.bj
+
+// bm : http://www.bermudanic.bm/dnr-text.txt
+bm
+com.bm
+edu.bm
+gov.bm
+net.bm
+org.bm
+
+// bn : http://en.wikipedia.org/wiki/.bn
+*.bn
+
+// bo : http://www.nic.bo/
+bo
+com.bo
+edu.bo
+gov.bo
+gob.bo
+int.bo
+org.bo
+net.bo
+mil.bo
+tv.bo
+
+// br : http://registro.br/dominio/dpn.html
+// Updated by registry <fneves@registro.br> 2011-03-01
+br
+adm.br
+adv.br
+agr.br
+am.br
+arq.br
+art.br
+ato.br
+b.br
+bio.br
+blog.br
+bmd.br
+cim.br
+cng.br
+cnt.br
+com.br
+coop.br
+ecn.br
+eco.br
+edu.br
+emp.br
+eng.br
+esp.br
+etc.br
+eti.br
+far.br
+flog.br
+fm.br
+fnd.br
+fot.br
+fst.br
+g12.br
+ggf.br
+gov.br
+imb.br
+ind.br
+inf.br
+jor.br
+jus.br
+leg.br
+lel.br
+mat.br
+med.br
+mil.br
+mus.br
+net.br
+nom.br
+not.br
+ntr.br
+odo.br
+org.br
+ppg.br
+pro.br
+psc.br
+psi.br
+qsl.br
+radio.br
+rec.br
+slg.br
+srv.br
+taxi.br
+teo.br
+tmp.br
+trd.br
+tur.br
+tv.br
+vet.br
+vlog.br
+wiki.br
+zlg.br
+
+// bs : http://www.nic.bs/rules.html
+bs
+com.bs
+net.bs
+org.bs
+edu.bs
+gov.bs
+
+// bt : http://en.wikipedia.org/wiki/.bt
+bt
+com.bt
+edu.bt
+gov.bt
+net.bt
+org.bt
+
+// bv : No registrations at this time.
+// Submitted by registry <jarle@uninett.no> 2006-06-16
+
+// bw : http://en.wikipedia.org/wiki/.bw
+// http://www.gobin.info/domainname/bw.doc
+// list of other 2nd level tlds ?
+bw
+co.bw
+org.bw
+
+// by : http://en.wikipedia.org/wiki/.by
+// http://tld.by/rules_2006_en.html
+// list of other 2nd level tlds ?
+by
+gov.by
+mil.by
+// Official information does not indicate that com.by is a reserved
+// second-level domain, but it's being used as one (see www.google.com.by and
+// www.yahoo.com.by, for example), so we list it here for safety's sake.
+com.by
+
+// http://hoster.by/
+of.by
+
+// bz : http://en.wikipedia.org/wiki/.bz
+// http://www.belizenic.bz/
+bz
+com.bz
+net.bz
+org.bz
+edu.bz
+gov.bz
+
+// ca : http://en.wikipedia.org/wiki/.ca
+ca
+// ca geographical names
+ab.ca
+bc.ca
+mb.ca
+nb.ca
+nf.ca
+nl.ca
+ns.ca
+nt.ca
+nu.ca
+on.ca
+pe.ca
+qc.ca
+sk.ca
+yk.ca
+// gc.ca: http://en.wikipedia.org/wiki/.gc.ca
+// see also: http://registry.gc.ca/en/SubdomainFAQ
+gc.ca
+
+// cat : http://en.wikipedia.org/wiki/.cat
+cat
+
+// cc : http://en.wikipedia.org/wiki/.cc
+cc
+
+// cd : http://en.wikipedia.org/wiki/.cd
+// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1
+cd
+gov.cd
+
+// cf : http://en.wikipedia.org/wiki/.cf
+cf
+
+// cg : http://en.wikipedia.org/wiki/.cg
+cg
+
+// ch : http://en.wikipedia.org/wiki/.ch
+ch
+
+// ci : http://en.wikipedia.org/wiki/.ci
+// http://www.nic.ci/index.php?page=charte
+ci
+org.ci
+or.ci
+com.ci
+co.ci
+edu.ci
+ed.ci
+ac.ci
+net.ci
+go.ci
+asso.ci
+aéroport.ci
+int.ci
+presse.ci
+md.ci
+gouv.ci
+
+// ck : http://en.wikipedia.org/wiki/.ck
+*.ck
+!www.ck
+
+// cl : http://en.wikipedia.org/wiki/.cl
+cl
+gov.cl
+gob.cl
+co.cl
+mil.cl
+
+// cm : http://en.wikipedia.org/wiki/.cm
+cm
+gov.cm
+
+// cn : http://en.wikipedia.org/wiki/.cn
+// Submitted by registry <tanyaling@cnnic.cn> 2008-06-11
+cn
+ac.cn
+com.cn
+edu.cn
+gov.cn
+net.cn
+org.cn
+mil.cn
+å…¬å¸.cn
+网络.cn
+網絡.cn
+// cn geographic names
+ah.cn
+bj.cn
+cq.cn
+fj.cn
+gd.cn
+gs.cn
+gz.cn
+gx.cn
+ha.cn
+hb.cn
+he.cn
+hi.cn
+hl.cn
+hn.cn
+jl.cn
+js.cn
+jx.cn
+ln.cn
+nm.cn
+nx.cn
+qh.cn
+sc.cn
+sd.cn
+sh.cn
+sn.cn
+sx.cn
+tj.cn
+xj.cn
+xz.cn
+yn.cn
+zj.cn
+hk.cn
+mo.cn
+tw.cn
+
+// co : http://en.wikipedia.org/wiki/.co
+// Submitted by registry <tecnico@uniandes.edu.co> 2008-06-11
+co
+arts.co
+com.co
+edu.co
+firm.co
+gov.co
+info.co
+int.co
+mil.co
+net.co
+nom.co
+org.co
+rec.co
+web.co
+
+// com : http://en.wikipedia.org/wiki/.com
+com
+
+// coop : http://en.wikipedia.org/wiki/.coop
+coop
+
+// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do
+cr
+ac.cr
+co.cr
+ed.cr
+fi.cr
+go.cr
+or.cr
+sa.cr
+
+// cu : http://en.wikipedia.org/wiki/.cu
+cu
+com.cu
+edu.cu
+org.cu
+net.cu
+gov.cu
+inf.cu
+
+// cv : http://en.wikipedia.org/wiki/.cv
+cv
+
+// cw : http://www.una.cw/cw_registry/
+// Confirmed by registry <registry@una.net> 2013-03-26
+cw
+com.cw
+edu.cw
+net.cw
+org.cw
+
+// cx : http://en.wikipedia.org/wiki/.cx
+// list of other 2nd level tlds ?
+cx
+gov.cx
+
+// cy : http://en.wikipedia.org/wiki/.cy
+*.cy
+
+// cz : http://en.wikipedia.org/wiki/.cz
+cz
+
+// de : http://en.wikipedia.org/wiki/.de
+// Confirmed by registry <ops@denic.de> (with technical
+// reservations) 2008-07-01
+de
+
+// dj : http://en.wikipedia.org/wiki/.dj
+dj
+
+// dk : http://en.wikipedia.org/wiki/.dk
+// Confirmed by registry <robert@dk-hostmaster.dk> 2008-06-17
+dk
+
+// dm : http://en.wikipedia.org/wiki/.dm
+dm
+com.dm
+net.dm
+org.dm
+edu.dm
+gov.dm
+
+// do : http://en.wikipedia.org/wiki/.do
+do
+art.do
+com.do
+edu.do
+gob.do
+gov.do
+mil.do
+net.do
+org.do
+sld.do
+web.do
+
+// dz : http://en.wikipedia.org/wiki/.dz
+dz
+com.dz
+org.dz
+net.dz
+gov.dz
+edu.dz
+asso.dz
+pol.dz
+art.dz
+
+// ec : http://www.nic.ec/reg/paso1.asp
+// Submitted by registry <vabboud@nic.ec> 2008-07-04
+ec
+com.ec
+info.ec
+net.ec
+fin.ec
+k12.ec
+med.ec
+pro.ec
+org.ec
+edu.ec
+gov.ec
+gob.ec
+mil.ec
+
+// edu : http://en.wikipedia.org/wiki/.edu
+edu
+
+// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B
+ee
+edu.ee
+gov.ee
+riik.ee
+lib.ee
+med.ee
+com.ee
+pri.ee
+aip.ee
+org.ee
+fie.ee
+
+// eg : http://en.wikipedia.org/wiki/.eg
+eg
+com.eg
+edu.eg
+eun.eg
+gov.eg
+mil.eg
+name.eg
+net.eg
+org.eg
+sci.eg
+
+// er : http://en.wikipedia.org/wiki/.er
+*.er
+
+// es : https://www.nic.es/site_ingles/ingles/dominios/index.html
+es
+com.es
+nom.es
+org.es
+gob.es
+edu.es
+
+// et : http://en.wikipedia.org/wiki/.et
+*.et
+
+// eu : http://en.wikipedia.org/wiki/.eu
+eu
+
+// fi : http://en.wikipedia.org/wiki/.fi
+fi
+// aland.fi : http://en.wikipedia.org/wiki/.ax
+// This domain is being phased out in favor of .ax. As there are still many
+// domains under aland.fi, we still keep it on the list until aland.fi is
+// completely removed.
+// TODO: Check for updates (expected to be phased out around Q1/2009)
+aland.fi
+
+// fj : http://en.wikipedia.org/wiki/.fj
+*.fj
+
+// fk : http://en.wikipedia.org/wiki/.fk
+*.fk
+
+// fm : http://en.wikipedia.org/wiki/.fm
+fm
+
+// fo : http://en.wikipedia.org/wiki/.fo
+fo
+
+// fr : http://www.afnic.fr/
+// domaines descriptifs : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-descriptifs
+fr
+com.fr
+asso.fr
+nom.fr
+prd.fr
+presse.fr
+tm.fr
+// domaines sectoriels : http://www.afnic.fr/obtenir/chartes/nommage-fr/annexe-sectoriels
+aeroport.fr
+assedic.fr
+avocat.fr
+avoues.fr
+cci.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+geometre-expert.fr
+gouv.fr
+greta.fr
+huissier-justice.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.fr
+
+// ga : http://en.wikipedia.org/wiki/.ga
+ga
+
+// gb : This registry is effectively dormant
+// Submitted by registry <Damien.Shaw@ja.net> 2008-06-12
+
+// gd : http://en.wikipedia.org/wiki/.gd
+gd
+
+// ge : http://www.nic.net.ge/policy_en.pdf
+ge
+com.ge
+edu.ge
+gov.ge
+org.ge
+mil.ge
+net.ge
+pvt.ge
+
+// gf : http://en.wikipedia.org/wiki/.gf
+gf
+
+// gg : http://www.channelisles.net/applic/avextn.shtml
+gg
+co.gg
+org.gg
+net.gg
+sch.gg
+gov.gg
+
+// gh : http://en.wikipedia.org/wiki/.gh
+// see also: http://www.nic.gh/reg_now.php
+// Although domains directly at second level are not possible at the moment,
+// they have been possible for some time and may come back.
+gh
+com.gh
+edu.gh
+gov.gh
+org.gh
+mil.gh
+
+// gi : http://www.nic.gi/rules.html
+gi
+com.gi
+ltd.gi
+gov.gi
+mod.gi
+edu.gi
+org.gi
+
+// gl : http://en.wikipedia.org/wiki/.gl
+// http://nic.gl
+gl
+
+// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm
+gm
+
+// gn : http://psg.com/dns/gn/gn.txt
+// Submitted by registry <randy@psg.com> 2008-06-17
+ac.gn
+com.gn
+edu.gn
+gov.gn
+org.gn
+net.gn
+
+// gov : http://en.wikipedia.org/wiki/.gov
+gov
+
+// gp : http://www.nic.gp/index.php?lang=en
+gp
+com.gp
+net.gp
+mobi.gp
+edu.gp
+org.gp
+asso.gp
+
+// gq : http://en.wikipedia.org/wiki/.gq
+gq
+
+// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html
+// Submitted by registry <segred@ics.forth.gr> 2008-06-09
+gr
+com.gr
+edu.gr
+net.gr
+org.gr
+gov.gr
+
+// gs : http://en.wikipedia.org/wiki/.gs
+gs
+
+// gt : http://www.gt/politicas_de_registro.html
+gt
+com.gt
+edu.gt
+gob.gt
+ind.gt
+mil.gt
+net.gt
+org.gt
+
+// gu : http://gadao.gov.gu/registration.txt
+*.gu
+
+// gw : http://en.wikipedia.org/wiki/.gw
+gw
+
+// gy : http://en.wikipedia.org/wiki/.gy
+// http://registry.gy/
+gy
+co.gy
+com.gy
+net.gy
+
+// hk : https://www.hkdnr.hk
+// Submitted by registry <hk.tech@hkirc.hk> 2008-06-11
+hk
+com.hk
+edu.hk
+gov.hk
+idv.hk
+net.hk
+org.hk
+å…¬å¸.hk
+教育.hk
+敎育.hk
+政府.hk
+個人.hk
+个人.hk
+箇人.hk
+網络.hk
+网络.hk
+组織.hk
+網絡.hk
+网絡.hk
+组织.hk
+組織.hk
+組织.hk
+
+// hm : http://en.wikipedia.org/wiki/.hm
+hm
+
+// hn : http://www.nic.hn/politicas/ps02,,05.html
+hn
+com.hn
+edu.hn
+org.hn
+net.hn
+mil.hn
+gob.hn
+
+// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf
+hr
+iz.hr
+from.hr
+name.hr
+com.hr
+
+// ht : http://www.nic.ht/info/charte.cfm
+ht
+com.ht
+shop.ht
+firm.ht
+info.ht
+adult.ht
+net.ht
+pro.ht
+org.ht
+med.ht
+art.ht
+coop.ht
+pol.ht
+asso.ht
+edu.ht
+rel.ht
+gouv.ht
+perso.ht
+
+// hu : http://www.domain.hu/domain/English/sld.html
+// Confirmed by registry <pasztor@iszt.hu> 2008-06-12
+hu
+co.hu
+info.hu
+org.hu
+priv.hu
+sport.hu
+tm.hu
+2000.hu
+agrar.hu
+bolt.hu
+casino.hu
+city.hu
+erotica.hu
+erotika.hu
+film.hu
+forum.hu
+games.hu
+hotel.hu
+ingatlan.hu
+jogasz.hu
+konyvelo.hu
+lakas.hu
+media.hu
+news.hu
+reklam.hu
+sex.hu
+shop.hu
+suli.hu
+szex.hu
+tozsde.hu
+utazas.hu
+video.hu
+
+// id : https://register.pandi.or.id/
+id
+ac.id
+biz.id
+co.id
+go.id
+mil.id
+my.id
+net.id
+or.id
+sch.id
+web.id
+
+// ie : http://en.wikipedia.org/wiki/.ie
+ie
+gov.ie
+
+// il : http://en.wikipedia.org/wiki/.il
+*.il
+
+// im : https://www.nic.im/pdfs/imfaqs.pdf
+im
+co.im
+ltd.co.im
+plc.co.im
+net.im
+gov.im
+org.im
+nic.im
+ac.im
+
+// in : http://en.wikipedia.org/wiki/.in
+// see also: http://www.inregistry.in/policies/
+// Please note, that nic.in is not an offical eTLD, but used by most
+// government institutions.
+in
+co.in
+firm.in
+net.in
+org.in
+gen.in
+ind.in
+nic.in
+ac.in
+edu.in
+res.in
+gov.in
+mil.in
+
+// info : http://en.wikipedia.org/wiki/.info
+info
+
+// int : http://en.wikipedia.org/wiki/.int
+// Confirmed by registry <iana-questions@icann.org> 2008-06-18
+int
+eu.int
+
+// io : http://www.nic.io/rules.html
+// list of other 2nd level tlds ?
+io
+com.io
+
+// iq : http://www.cmc.iq/english/iq/iqregister1.htm
+iq
+gov.iq
+edu.iq
+mil.iq
+com.iq
+org.iq
+net.iq
+
+// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules
+// Also see http://www.nic.ir/Internationalized_Domain_Names
+// Two <iran>.ir entries added at request of <tech-team@nic.ir>, 2010-04-16
+ir
+ac.ir
+co.ir
+gov.ir
+id.ir
+net.ir
+org.ir
+sch.ir
+// xn--mgba3a4f16a.ir (<iran>.ir, Persian YEH)
+ایران.ir
+// xn--mgba3a4fra.ir (<iran>.ir, Arabic YEH)
+ايران.ir
+
+// is : http://www.isnic.is/domain/rules.php
+// Confirmed by registry <marius@isgate.is> 2008-12-06
+is
+net.is
+com.is
+edu.is
+gov.is
+org.is
+int.is
+
+// it : http://en.wikipedia.org/wiki/.it
+it
+gov.it
+edu.it
+// list of reserved geo-names :
+// http://www.nic.it/documenti/regolamenti-e-linee-guida/regolamento-assegnazione-versione-6.0.pdf
+// (There is also a list of reserved geo-names corresponding to Italian
+// municipalities : http://www.nic.it/documenti/appendice-c.pdf , but it is
+// not included here.)
+agrigento.it
+ag.it
+alessandria.it
+al.it
+ancona.it
+an.it
+aosta.it
+aoste.it
+ao.it
+arezzo.it
+ar.it
+ascoli-piceno.it
+ascolipiceno.it
+ap.it
+asti.it
+at.it
+avellino.it
+av.it
+bari.it
+ba.it
+andria-barletta-trani.it
+andriabarlettatrani.it
+trani-barletta-andria.it
+tranibarlettaandria.it
+barletta-trani-andria.it
+barlettatraniandria.it
+andria-trani-barletta.it
+andriatranibarletta.it
+trani-andria-barletta.it
+traniandriabarletta.it
+bt.it
+belluno.it
+bl.it
+benevento.it
+bn.it
+bergamo.it
+bg.it
+biella.it
+bi.it
+bologna.it
+bo.it
+bolzano.it
+bozen.it
+balsan.it
+alto-adige.it
+altoadige.it
+suedtirol.it
+bz.it
+brescia.it
+bs.it
+brindisi.it
+br.it
+cagliari.it
+ca.it
+caltanissetta.it
+cl.it
+campobasso.it
+cb.it
+carboniaiglesias.it
+carbonia-iglesias.it
+iglesias-carbonia.it
+iglesiascarbonia.it
+ci.it
+caserta.it
+ce.it
+catania.it
+ct.it
+catanzaro.it
+cz.it
+chieti.it
+ch.it
+como.it
+co.it
+cosenza.it
+cs.it
+cremona.it
+cr.it
+crotone.it
+kr.it
+cuneo.it
+cn.it
+dell-ogliastra.it
+dellogliastra.it
+ogliastra.it
+og.it
+enna.it
+en.it
+ferrara.it
+fe.it
+fermo.it
+fm.it
+firenze.it
+florence.it
+fi.it
+foggia.it
+fg.it
+forli-cesena.it
+forlicesena.it
+cesena-forli.it
+cesenaforli.it
+fc.it
+frosinone.it
+fr.it
+genova.it
+genoa.it
+ge.it
+gorizia.it
+go.it
+grosseto.it
+gr.it
+imperia.it
+im.it
+isernia.it
+is.it
+laquila.it
+aquila.it
+aq.it
+la-spezia.it
+laspezia.it
+sp.it
+latina.it
+lt.it
+lecce.it
+le.it
+lecco.it
+lc.it
+livorno.it
+li.it
+lodi.it
+lo.it
+lucca.it
+lu.it
+macerata.it
+mc.it
+mantova.it
+mn.it
+massa-carrara.it
+massacarrara.it
+carrara-massa.it
+carraramassa.it
+ms.it
+matera.it
+mt.it
+medio-campidano.it
+mediocampidano.it
+campidano-medio.it
+campidanomedio.it
+vs.it
+messina.it
+me.it
+milano.it
+milan.it
+mi.it
+modena.it
+mo.it
+monza.it
+monza-brianza.it
+monzabrianza.it
+monzaebrianza.it
+monzaedellabrianza.it
+monza-e-della-brianza.it
+mb.it
+napoli.it
+naples.it
+na.it
+novara.it
+no.it
+nuoro.it
+nu.it
+oristano.it
+or.it
+padova.it
+padua.it
+pd.it
+palermo.it
+pa.it
+parma.it
+pr.it
+pavia.it
+pv.it
+perugia.it
+pg.it
+pescara.it
+pe.it
+pesaro-urbino.it
+pesarourbino.it
+urbino-pesaro.it
+urbinopesaro.it
+pu.it
+piacenza.it
+pc.it
+pisa.it
+pi.it
+pistoia.it
+pt.it
+pordenone.it
+pn.it
+potenza.it
+pz.it
+prato.it
+po.it
+ragusa.it
+rg.it
+ravenna.it
+ra.it
+reggio-calabria.it
+reggiocalabria.it
+rc.it
+reggio-emilia.it
+reggioemilia.it
+re.it
+rieti.it
+ri.it
+rimini.it
+rn.it
+roma.it
+rome.it
+rm.it
+rovigo.it
+ro.it
+salerno.it
+sa.it
+sassari.it
+ss.it
+savona.it
+sv.it
+siena.it
+si.it
+siracusa.it
+sr.it
+sondrio.it
+so.it
+taranto.it
+ta.it
+tempio-olbia.it
+tempioolbia.it
+olbia-tempio.it
+olbiatempio.it
+ot.it
+teramo.it
+te.it
+terni.it
+tr.it
+torino.it
+turin.it
+to.it
+trapani.it
+tp.it
+trento.it
+trentino.it
+tn.it
+treviso.it
+tv.it
+trieste.it
+ts.it
+udine.it
+ud.it
+varese.it
+va.it
+venezia.it
+venice.it
+ve.it
+verbania.it
+vb.it
+vercelli.it
+vc.it
+verona.it
+vr.it
+vibo-valentia.it
+vibovalentia.it
+vv.it
+vicenza.it
+vi.it
+viterbo.it
+vt.it
+
+// je : http://www.channelisles.net/applic/avextn.shtml
+je
+co.je
+org.je
+net.je
+sch.je
+gov.je
+
+// jm : http://www.com.jm/register.html
+*.jm
+
+// jo : http://www.dns.jo/Registration_policy.aspx
+jo
+com.jo
+org.jo
+net.jo
+edu.jo
+sch.jo
+gov.jo
+mil.jo
+name.jo
+
+// jobs : http://en.wikipedia.org/wiki/.jobs
+jobs
+
+// jp : http://en.wikipedia.org/wiki/.jp
+// http://jprs.co.jp/en/jpdomain.html
+// Updated by registry <info@jprs.jp> 2012-05-28
+jp
+// jp organizational type names
+ac.jp
+ad.jp
+co.jp
+ed.jp
+go.jp
+gr.jp
+lg.jp
+ne.jp
+or.jp
+// jp preficture type names
+aichi.jp
+akita.jp
+aomori.jp
+chiba.jp
+ehime.jp
+fukui.jp
+fukuoka.jp
+fukushima.jp
+gifu.jp
+gunma.jp
+hiroshima.jp
+hokkaido.jp
+hyogo.jp
+ibaraki.jp
+ishikawa.jp
+iwate.jp
+kagawa.jp
+kagoshima.jp
+kanagawa.jp
+kochi.jp
+kumamoto.jp
+kyoto.jp
+mie.jp
+miyagi.jp
+miyazaki.jp
+nagano.jp
+nagasaki.jp
+nara.jp
+niigata.jp
+oita.jp
+okayama.jp
+okinawa.jp
+osaka.jp
+saga.jp
+saitama.jp
+shiga.jp
+shimane.jp
+shizuoka.jp
+tochigi.jp
+tokushima.jp
+tokyo.jp
+tottori.jp
+toyama.jp
+wakayama.jp
+yamagata.jp
+yamaguchi.jp
+yamanashi.jp
+// jp geographic type names
+// http://jprs.jp/doc/rule/saisoku-1.html
+*.kawasaki.jp
+*.kitakyushu.jp
+*.kobe.jp
+*.nagoya.jp
+*.sapporo.jp
+*.sendai.jp
+*.yokohama.jp
+!city.kawasaki.jp
+!city.kitakyushu.jp
+!city.kobe.jp
+!city.nagoya.jp
+!city.sapporo.jp
+!city.sendai.jp
+!city.yokohama.jp
+// 4th level registration
+aisai.aichi.jp
+ama.aichi.jp
+anjo.aichi.jp
+asuke.aichi.jp
+chiryu.aichi.jp
+chita.aichi.jp
+fuso.aichi.jp
+gamagori.aichi.jp
+handa.aichi.jp
+hazu.aichi.jp
+hekinan.aichi.jp
+higashiura.aichi.jp
+ichinomiya.aichi.jp
+inazawa.aichi.jp
+inuyama.aichi.jp
+isshiki.aichi.jp
+iwakura.aichi.jp
+kanie.aichi.jp
+kariya.aichi.jp
+kasugai.aichi.jp
+kira.aichi.jp
+kiyosu.aichi.jp
+komaki.aichi.jp
+konan.aichi.jp
+kota.aichi.jp
+mihama.aichi.jp
+miyoshi.aichi.jp
+nagakute.aichi.jp
+nishio.aichi.jp
+nisshin.aichi.jp
+obu.aichi.jp
+oguchi.aichi.jp
+oharu.aichi.jp
+okazaki.aichi.jp
+owariasahi.aichi.jp
+seto.aichi.jp
+shikatsu.aichi.jp
+shinshiro.aichi.jp
+shitara.aichi.jp
+tahara.aichi.jp
+takahama.aichi.jp
+tobishima.aichi.jp
+toei.aichi.jp
+togo.aichi.jp
+tokai.aichi.jp
+tokoname.aichi.jp
+toyoake.aichi.jp
+toyohashi.aichi.jp
+toyokawa.aichi.jp
+toyone.aichi.jp
+toyota.aichi.jp
+tsushima.aichi.jp
+yatomi.aichi.jp
+akita.akita.jp
+daisen.akita.jp
+fujisato.akita.jp
+gojome.akita.jp
+hachirogata.akita.jp
+happou.akita.jp
+higashinaruse.akita.jp
+honjo.akita.jp
+honjyo.akita.jp
+ikawa.akita.jp
+kamikoani.akita.jp
+kamioka.akita.jp
+katagami.akita.jp
+kazuno.akita.jp
+kitaakita.akita.jp
+kosaka.akita.jp
+kyowa.akita.jp
+misato.akita.jp
+mitane.akita.jp
+moriyoshi.akita.jp
+nikaho.akita.jp
+noshiro.akita.jp
+odate.akita.jp
+oga.akita.jp
+ogata.akita.jp
+semboku.akita.jp
+yokote.akita.jp
+yurihonjo.akita.jp
+aomori.aomori.jp
+gonohe.aomori.jp
+hachinohe.aomori.jp
+hashikami.aomori.jp
+hiranai.aomori.jp
+hirosaki.aomori.jp
+itayanagi.aomori.jp
+kuroishi.aomori.jp
+misawa.aomori.jp
+mutsu.aomori.jp
+nakadomari.aomori.jp
+noheji.aomori.jp
+oirase.aomori.jp
+owani.aomori.jp
+rokunohe.aomori.jp
+sannohe.aomori.jp
+shichinohe.aomori.jp
+shingo.aomori.jp
+takko.aomori.jp
+towada.aomori.jp
+tsugaru.aomori.jp
+tsuruta.aomori.jp
+abiko.chiba.jp
+asahi.chiba.jp
+chonan.chiba.jp
+chosei.chiba.jp
+choshi.chiba.jp
+chuo.chiba.jp
+funabashi.chiba.jp
+futtsu.chiba.jp
+hanamigawa.chiba.jp
+ichihara.chiba.jp
+ichikawa.chiba.jp
+ichinomiya.chiba.jp
+inzai.chiba.jp
+isumi.chiba.jp
+kamagaya.chiba.jp
+kamogawa.chiba.jp
+kashiwa.chiba.jp
+katori.chiba.jp
+katsuura.chiba.jp
+kimitsu.chiba.jp
+kisarazu.chiba.jp
+kozaki.chiba.jp
+kujukuri.chiba.jp
+kyonan.chiba.jp
+matsudo.chiba.jp
+midori.chiba.jp
+mihama.chiba.jp
+minamiboso.chiba.jp
+mobara.chiba.jp
+mutsuzawa.chiba.jp
+nagara.chiba.jp
+nagareyama.chiba.jp
+narashino.chiba.jp
+narita.chiba.jp
+noda.chiba.jp
+oamishirasato.chiba.jp
+omigawa.chiba.jp
+onjuku.chiba.jp
+otaki.chiba.jp
+sakae.chiba.jp
+sakura.chiba.jp
+shimofusa.chiba.jp
+shirako.chiba.jp
+shiroi.chiba.jp
+shisui.chiba.jp
+sodegaura.chiba.jp
+sosa.chiba.jp
+tako.chiba.jp
+tateyama.chiba.jp
+togane.chiba.jp
+tohnosho.chiba.jp
+tomisato.chiba.jp
+urayasu.chiba.jp
+yachimata.chiba.jp
+yachiyo.chiba.jp
+yokaichiba.chiba.jp
+yokoshibahikari.chiba.jp
+yotsukaido.chiba.jp
+ainan.ehime.jp
+honai.ehime.jp
+ikata.ehime.jp
+imabari.ehime.jp
+iyo.ehime.jp
+kamijima.ehime.jp
+kihoku.ehime.jp
+kumakogen.ehime.jp
+masaki.ehime.jp
+matsuno.ehime.jp
+matsuyama.ehime.jp
+namikata.ehime.jp
+niihama.ehime.jp
+ozu.ehime.jp
+saijo.ehime.jp
+seiyo.ehime.jp
+shikokuchuo.ehime.jp
+tobe.ehime.jp
+toon.ehime.jp
+uchiko.ehime.jp
+uwajima.ehime.jp
+yawatahama.ehime.jp
+echizen.fukui.jp
+eiheiji.fukui.jp
+fukui.fukui.jp
+ikeda.fukui.jp
+katsuyama.fukui.jp
+mihama.fukui.jp
+minamiechizen.fukui.jp
+obama.fukui.jp
+ohi.fukui.jp
+ono.fukui.jp
+sabae.fukui.jp
+sakai.fukui.jp
+takahama.fukui.jp
+tsuruga.fukui.jp
+wakasa.fukui.jp
+ashiya.fukuoka.jp
+buzen.fukuoka.jp
+chikugo.fukuoka.jp
+chikuho.fukuoka.jp
+chikujo.fukuoka.jp
+chikushino.fukuoka.jp
+chikuzen.fukuoka.jp
+chuo.fukuoka.jp
+dazaifu.fukuoka.jp
+fukuchi.fukuoka.jp
+hakata.fukuoka.jp
+higashi.fukuoka.jp
+hirokawa.fukuoka.jp
+hisayama.fukuoka.jp
+iizuka.fukuoka.jp
+inatsuki.fukuoka.jp
+kaho.fukuoka.jp
+kasuga.fukuoka.jp
+kasuya.fukuoka.jp
+kawara.fukuoka.jp
+keisen.fukuoka.jp
+koga.fukuoka.jp
+kurate.fukuoka.jp
+kurogi.fukuoka.jp
+kurume.fukuoka.jp
+minami.fukuoka.jp
+miyako.fukuoka.jp
+miyama.fukuoka.jp
+miyawaka.fukuoka.jp
+mizumaki.fukuoka.jp
+munakata.fukuoka.jp
+nakagawa.fukuoka.jp
+nakama.fukuoka.jp
+nishi.fukuoka.jp
+nogata.fukuoka.jp
+ogori.fukuoka.jp
+okagaki.fukuoka.jp
+okawa.fukuoka.jp
+oki.fukuoka.jp
+omuta.fukuoka.jp
+onga.fukuoka.jp
+onojo.fukuoka.jp
+oto.fukuoka.jp
+saigawa.fukuoka.jp
+sasaguri.fukuoka.jp
+shingu.fukuoka.jp
+shinyoshitomi.fukuoka.jp
+shonai.fukuoka.jp
+soeda.fukuoka.jp
+sue.fukuoka.jp
+tachiarai.fukuoka.jp
+tagawa.fukuoka.jp
+takata.fukuoka.jp
+toho.fukuoka.jp
+toyotsu.fukuoka.jp
+tsuiki.fukuoka.jp
+ukiha.fukuoka.jp
+umi.fukuoka.jp
+usui.fukuoka.jp
+yamada.fukuoka.jp
+yame.fukuoka.jp
+yanagawa.fukuoka.jp
+yukuhashi.fukuoka.jp
+aizubange.fukushima.jp
+aizumisato.fukushima.jp
+aizuwakamatsu.fukushima.jp
+asakawa.fukushima.jp
+bandai.fukushima.jp
+date.fukushima.jp
+fukushima.fukushima.jp
+furudono.fukushima.jp
+futaba.fukushima.jp
+hanawa.fukushima.jp
+higashi.fukushima.jp
+hirata.fukushima.jp
+hirono.fukushima.jp
+iitate.fukushima.jp
+inawashiro.fukushima.jp
+ishikawa.fukushima.jp
+iwaki.fukushima.jp
+izumizaki.fukushima.jp
+kagamiishi.fukushima.jp
+kaneyama.fukushima.jp
+kawamata.fukushima.jp
+kitakata.fukushima.jp
+kitashiobara.fukushima.jp
+koori.fukushima.jp
+koriyama.fukushima.jp
+kunimi.fukushima.jp
+miharu.fukushima.jp
+mishima.fukushima.jp
+namie.fukushima.jp
+nango.fukushima.jp
+nishiaizu.fukushima.jp
+nishigo.fukushima.jp
+okuma.fukushima.jp
+omotego.fukushima.jp
+ono.fukushima.jp
+otama.fukushima.jp
+samegawa.fukushima.jp
+shimogo.fukushima.jp
+shirakawa.fukushima.jp
+showa.fukushima.jp
+soma.fukushima.jp
+sukagawa.fukushima.jp
+taishin.fukushima.jp
+tamakawa.fukushima.jp
+tanagura.fukushima.jp
+tenei.fukushima.jp
+yabuki.fukushima.jp
+yamato.fukushima.jp
+yamatsuri.fukushima.jp
+yanaizu.fukushima.jp
+yugawa.fukushima.jp
+anpachi.gifu.jp
+ena.gifu.jp
+gifu.gifu.jp
+ginan.gifu.jp
+godo.gifu.jp
+gujo.gifu.jp
+hashima.gifu.jp
+hichiso.gifu.jp
+hida.gifu.jp
+higashishirakawa.gifu.jp
+ibigawa.gifu.jp
+ikeda.gifu.jp
+kakamigahara.gifu.jp
+kani.gifu.jp
+kasahara.gifu.jp
+kasamatsu.gifu.jp
+kawaue.gifu.jp
+kitagata.gifu.jp
+mino.gifu.jp
+minokamo.gifu.jp
+mitake.gifu.jp
+mizunami.gifu.jp
+motosu.gifu.jp
+nakatsugawa.gifu.jp
+ogaki.gifu.jp
+sakahogi.gifu.jp
+seki.gifu.jp
+sekigahara.gifu.jp
+shirakawa.gifu.jp
+tajimi.gifu.jp
+takayama.gifu.jp
+tarui.gifu.jp
+toki.gifu.jp
+tomika.gifu.jp
+wanouchi.gifu.jp
+yamagata.gifu.jp
+yaotsu.gifu.jp
+yoro.gifu.jp
+annaka.gunma.jp
+chiyoda.gunma.jp
+fujioka.gunma.jp
+higashiagatsuma.gunma.jp
+isesaki.gunma.jp
+itakura.gunma.jp
+kanna.gunma.jp
+kanra.gunma.jp
+katashina.gunma.jp
+kawaba.gunma.jp
+kiryu.gunma.jp
+kusatsu.gunma.jp
+maebashi.gunma.jp
+meiwa.gunma.jp
+midori.gunma.jp
+minakami.gunma.jp
+naganohara.gunma.jp
+nakanojo.gunma.jp
+nanmoku.gunma.jp
+numata.gunma.jp
+oizumi.gunma.jp
+ora.gunma.jp
+ota.gunma.jp
+shibukawa.gunma.jp
+shimonita.gunma.jp
+shinto.gunma.jp
+showa.gunma.jp
+takasaki.gunma.jp
+takayama.gunma.jp
+tamamura.gunma.jp
+tatebayashi.gunma.jp
+tomioka.gunma.jp
+tsukiyono.gunma.jp
+tsumagoi.gunma.jp
+ueno.gunma.jp
+yoshioka.gunma.jp
+asaminami.hiroshima.jp
+daiwa.hiroshima.jp
+etajima.hiroshima.jp
+fuchu.hiroshima.jp
+fukuyama.hiroshima.jp
+hatsukaichi.hiroshima.jp
+higashihiroshima.hiroshima.jp
+hongo.hiroshima.jp
+jinsekikogen.hiroshima.jp
+kaita.hiroshima.jp
+kui.hiroshima.jp
+kumano.hiroshima.jp
+kure.hiroshima.jp
+mihara.hiroshima.jp
+miyoshi.hiroshima.jp
+naka.hiroshima.jp
+onomichi.hiroshima.jp
+osakikamijima.hiroshima.jp
+otake.hiroshima.jp
+saka.hiroshima.jp
+sera.hiroshima.jp
+seranishi.hiroshima.jp
+shinichi.hiroshima.jp
+shobara.hiroshima.jp
+takehara.hiroshima.jp
+abashiri.hokkaido.jp
+abira.hokkaido.jp
+aibetsu.hokkaido.jp
+akabira.hokkaido.jp
+akkeshi.hokkaido.jp
+asahikawa.hokkaido.jp
+ashibetsu.hokkaido.jp
+ashoro.hokkaido.jp
+assabu.hokkaido.jp
+atsuma.hokkaido.jp
+bibai.hokkaido.jp
+biei.hokkaido.jp
+bifuka.hokkaido.jp
+bihoro.hokkaido.jp
+biratori.hokkaido.jp
+chippubetsu.hokkaido.jp
+chitose.hokkaido.jp
+date.hokkaido.jp
+ebetsu.hokkaido.jp
+embetsu.hokkaido.jp
+eniwa.hokkaido.jp
+erimo.hokkaido.jp
+esan.hokkaido.jp
+esashi.hokkaido.jp
+fukagawa.hokkaido.jp
+fukushima.hokkaido.jp
+furano.hokkaido.jp
+furubira.hokkaido.jp
+haboro.hokkaido.jp
+hakodate.hokkaido.jp
+hamatonbetsu.hokkaido.jp
+hidaka.hokkaido.jp
+higashikagura.hokkaido.jp
+higashikawa.hokkaido.jp
+hiroo.hokkaido.jp
+hokuryu.hokkaido.jp
+hokuto.hokkaido.jp
+honbetsu.hokkaido.jp
+horokanai.hokkaido.jp
+horonobe.hokkaido.jp
+ikeda.hokkaido.jp
+imakane.hokkaido.jp
+ishikari.hokkaido.jp
+iwamizawa.hokkaido.jp
+iwanai.hokkaido.jp
+kamifurano.hokkaido.jp
+kamikawa.hokkaido.jp
+kamishihoro.hokkaido.jp
+kamisunagawa.hokkaido.jp
+kamoenai.hokkaido.jp
+kayabe.hokkaido.jp
+kembuchi.hokkaido.jp
+kikonai.hokkaido.jp
+kimobetsu.hokkaido.jp
+kitahiroshima.hokkaido.jp
+kitami.hokkaido.jp
+kiyosato.hokkaido.jp
+koshimizu.hokkaido.jp
+kunneppu.hokkaido.jp
+kuriyama.hokkaido.jp
+kuromatsunai.hokkaido.jp
+kushiro.hokkaido.jp
+kutchan.hokkaido.jp
+kyowa.hokkaido.jp
+mashike.hokkaido.jp
+matsumae.hokkaido.jp
+mikasa.hokkaido.jp
+minamifurano.hokkaido.jp
+mombetsu.hokkaido.jp
+moseushi.hokkaido.jp
+mukawa.hokkaido.jp
+muroran.hokkaido.jp
+naie.hokkaido.jp
+nakagawa.hokkaido.jp
+nakasatsunai.hokkaido.jp
+nakatombetsu.hokkaido.jp
+nanae.hokkaido.jp
+nanporo.hokkaido.jp
+nayoro.hokkaido.jp
+nemuro.hokkaido.jp
+niikappu.hokkaido.jp
+niki.hokkaido.jp
+nishiokoppe.hokkaido.jp
+noboribetsu.hokkaido.jp
+numata.hokkaido.jp
+obihiro.hokkaido.jp
+obira.hokkaido.jp
+oketo.hokkaido.jp
+okoppe.hokkaido.jp
+otaru.hokkaido.jp
+otobe.hokkaido.jp
+otofuke.hokkaido.jp
+otoineppu.hokkaido.jp
+oumu.hokkaido.jp
+ozora.hokkaido.jp
+pippu.hokkaido.jp
+rankoshi.hokkaido.jp
+rebun.hokkaido.jp
+rikubetsu.hokkaido.jp
+rishiri.hokkaido.jp
+rishirifuji.hokkaido.jp
+saroma.hokkaido.jp
+sarufutsu.hokkaido.jp
+shakotan.hokkaido.jp
+shari.hokkaido.jp
+shibecha.hokkaido.jp
+shibetsu.hokkaido.jp
+shikabe.hokkaido.jp
+shikaoi.hokkaido.jp
+shimamaki.hokkaido.jp
+shimizu.hokkaido.jp
+shimokawa.hokkaido.jp
+shinshinotsu.hokkaido.jp
+shintoku.hokkaido.jp
+shiranuka.hokkaido.jp
+shiraoi.hokkaido.jp
+shiriuchi.hokkaido.jp
+sobetsu.hokkaido.jp
+sunagawa.hokkaido.jp
+taiki.hokkaido.jp
+takasu.hokkaido.jp
+takikawa.hokkaido.jp
+takinoue.hokkaido.jp
+teshikaga.hokkaido.jp
+tobetsu.hokkaido.jp
+tohma.hokkaido.jp
+tomakomai.hokkaido.jp
+tomari.hokkaido.jp
+toya.hokkaido.jp
+toyako.hokkaido.jp
+toyotomi.hokkaido.jp
+toyoura.hokkaido.jp
+tsubetsu.hokkaido.jp
+tsukigata.hokkaido.jp
+urakawa.hokkaido.jp
+urausu.hokkaido.jp
+uryu.hokkaido.jp
+utashinai.hokkaido.jp
+wakkanai.hokkaido.jp
+wassamu.hokkaido.jp
+yakumo.hokkaido.jp
+yoichi.hokkaido.jp
+aioi.hyogo.jp
+akashi.hyogo.jp
+ako.hyogo.jp
+amagasaki.hyogo.jp
+aogaki.hyogo.jp
+asago.hyogo.jp
+ashiya.hyogo.jp
+awaji.hyogo.jp
+fukusaki.hyogo.jp
+goshiki.hyogo.jp
+harima.hyogo.jp
+himeji.hyogo.jp
+ichikawa.hyogo.jp
+inagawa.hyogo.jp
+itami.hyogo.jp
+kakogawa.hyogo.jp
+kamigori.hyogo.jp
+kamikawa.hyogo.jp
+kasai.hyogo.jp
+kasuga.hyogo.jp
+kawanishi.hyogo.jp
+miki.hyogo.jp
+minamiawaji.hyogo.jp
+nishinomiya.hyogo.jp
+nishiwaki.hyogo.jp
+ono.hyogo.jp
+sanda.hyogo.jp
+sannan.hyogo.jp
+sasayama.hyogo.jp
+sayo.hyogo.jp
+shingu.hyogo.jp
+shinonsen.hyogo.jp
+shiso.hyogo.jp
+sumoto.hyogo.jp
+taishi.hyogo.jp
+taka.hyogo.jp
+takarazuka.hyogo.jp
+takasago.hyogo.jp
+takino.hyogo.jp
+tamba.hyogo.jp
+tatsuno.hyogo.jp
+toyooka.hyogo.jp
+yabu.hyogo.jp
+yashiro.hyogo.jp
+yoka.hyogo.jp
+yokawa.hyogo.jp
+ami.ibaraki.jp
+asahi.ibaraki.jp
+bando.ibaraki.jp
+chikusei.ibaraki.jp
+daigo.ibaraki.jp
+fujishiro.ibaraki.jp
+hitachi.ibaraki.jp
+hitachinaka.ibaraki.jp
+hitachiomiya.ibaraki.jp
+hitachiota.ibaraki.jp
+ibaraki.ibaraki.jp
+ina.ibaraki.jp
+inashiki.ibaraki.jp
+itako.ibaraki.jp
+iwama.ibaraki.jp
+joso.ibaraki.jp
+kamisu.ibaraki.jp
+kasama.ibaraki.jp
+kashima.ibaraki.jp
+kasumigaura.ibaraki.jp
+koga.ibaraki.jp
+miho.ibaraki.jp
+mito.ibaraki.jp
+moriya.ibaraki.jp
+naka.ibaraki.jp
+namegata.ibaraki.jp
+oarai.ibaraki.jp
+ogawa.ibaraki.jp
+omitama.ibaraki.jp
+ryugasaki.ibaraki.jp
+sakai.ibaraki.jp
+sakuragawa.ibaraki.jp
+shimodate.ibaraki.jp
+shimotsuma.ibaraki.jp
+shirosato.ibaraki.jp
+sowa.ibaraki.jp
+suifu.ibaraki.jp
+takahagi.ibaraki.jp
+tamatsukuri.ibaraki.jp
+tokai.ibaraki.jp
+tomobe.ibaraki.jp
+tone.ibaraki.jp
+toride.ibaraki.jp
+tsuchiura.ibaraki.jp
+tsukuba.ibaraki.jp
+uchihara.ibaraki.jp
+ushiku.ibaraki.jp
+yachiyo.ibaraki.jp
+yamagata.ibaraki.jp
+yawara.ibaraki.jp
+yuki.ibaraki.jp
+anamizu.ishikawa.jp
+hakui.ishikawa.jp
+hakusan.ishikawa.jp
+kaga.ishikawa.jp
+kahoku.ishikawa.jp
+kanazawa.ishikawa.jp
+kawakita.ishikawa.jp
+komatsu.ishikawa.jp
+nakanoto.ishikawa.jp
+nanao.ishikawa.jp
+nomi.ishikawa.jp
+nonoichi.ishikawa.jp
+noto.ishikawa.jp
+shika.ishikawa.jp
+suzu.ishikawa.jp
+tsubata.ishikawa.jp
+tsurugi.ishikawa.jp
+uchinada.ishikawa.jp
+wajima.ishikawa.jp
+fudai.iwate.jp
+fujisawa.iwate.jp
+hanamaki.iwate.jp
+hiraizumi.iwate.jp
+hirono.iwate.jp
+ichinohe.iwate.jp
+ichinoseki.iwate.jp
+iwaizumi.iwate.jp
+iwate.iwate.jp
+joboji.iwate.jp
+kamaishi.iwate.jp
+kanegasaki.iwate.jp
+karumai.iwate.jp
+kawai.iwate.jp
+kitakami.iwate.jp
+kuji.iwate.jp
+kunohe.iwate.jp
+kuzumaki.iwate.jp
+miyako.iwate.jp
+mizusawa.iwate.jp
+morioka.iwate.jp
+ninohe.iwate.jp
+noda.iwate.jp
+ofunato.iwate.jp
+oshu.iwate.jp
+otsuchi.iwate.jp
+rikuzentakata.iwate.jp
+shiwa.iwate.jp
+shizukuishi.iwate.jp
+sumita.iwate.jp
+takizawa.iwate.jp
+tanohata.iwate.jp
+tono.iwate.jp
+yahaba.iwate.jp
+yamada.iwate.jp
+ayagawa.kagawa.jp
+higashikagawa.kagawa.jp
+kanonji.kagawa.jp
+kotohira.kagawa.jp
+manno.kagawa.jp
+marugame.kagawa.jp
+mitoyo.kagawa.jp
+naoshima.kagawa.jp
+sanuki.kagawa.jp
+tadotsu.kagawa.jp
+takamatsu.kagawa.jp
+tonosho.kagawa.jp
+uchinomi.kagawa.jp
+utazu.kagawa.jp
+zentsuji.kagawa.jp
+akune.kagoshima.jp
+amami.kagoshima.jp
+hioki.kagoshima.jp
+isa.kagoshima.jp
+isen.kagoshima.jp
+izumi.kagoshima.jp
+kagoshima.kagoshima.jp
+kanoya.kagoshima.jp
+kawanabe.kagoshima.jp
+kinko.kagoshima.jp
+kouyama.kagoshima.jp
+makurazaki.kagoshima.jp
+matsumoto.kagoshima.jp
+minamitane.kagoshima.jp
+nakatane.kagoshima.jp
+nishinoomote.kagoshima.jp
+satsumasendai.kagoshima.jp
+soo.kagoshima.jp
+tarumizu.kagoshima.jp
+yusui.kagoshima.jp
+aikawa.kanagawa.jp
+atsugi.kanagawa.jp
+ayase.kanagawa.jp
+chigasaki.kanagawa.jp
+ebina.kanagawa.jp
+fujisawa.kanagawa.jp
+hadano.kanagawa.jp
+hakone.kanagawa.jp
+hiratsuka.kanagawa.jp
+isehara.kanagawa.jp
+kaisei.kanagawa.jp
+kamakura.kanagawa.jp
+kiyokawa.kanagawa.jp
+matsuda.kanagawa.jp
+minamiashigara.kanagawa.jp
+miura.kanagawa.jp
+nakai.kanagawa.jp
+ninomiya.kanagawa.jp
+odawara.kanagawa.jp
+oi.kanagawa.jp
+oiso.kanagawa.jp
+sagamihara.kanagawa.jp
+samukawa.kanagawa.jp
+tsukui.kanagawa.jp
+yamakita.kanagawa.jp
+yamato.kanagawa.jp
+yokosuka.kanagawa.jp
+yugawara.kanagawa.jp
+zama.kanagawa.jp
+zushi.kanagawa.jp
+aki.kochi.jp
+geisei.kochi.jp
+hidaka.kochi.jp
+higashitsuno.kochi.jp
+ino.kochi.jp
+kagami.kochi.jp
+kami.kochi.jp
+kitagawa.kochi.jp
+kochi.kochi.jp
+mihara.kochi.jp
+motoyama.kochi.jp
+muroto.kochi.jp
+nahari.kochi.jp
+nakamura.kochi.jp
+nankoku.kochi.jp
+nishitosa.kochi.jp
+niyodogawa.kochi.jp
+ochi.kochi.jp
+okawa.kochi.jp
+otoyo.kochi.jp
+otsuki.kochi.jp
+sakawa.kochi.jp
+sukumo.kochi.jp
+susaki.kochi.jp
+tosa.kochi.jp
+tosashimizu.kochi.jp
+toyo.kochi.jp
+tsuno.kochi.jp
+umaji.kochi.jp
+yasuda.kochi.jp
+yusuhara.kochi.jp
+amakusa.kumamoto.jp
+arao.kumamoto.jp
+aso.kumamoto.jp
+choyo.kumamoto.jp
+gyokuto.kumamoto.jp
+hitoyoshi.kumamoto.jp
+kamiamakusa.kumamoto.jp
+kashima.kumamoto.jp
+kikuchi.kumamoto.jp
+kosa.kumamoto.jp
+kumamoto.kumamoto.jp
+mashiki.kumamoto.jp
+mifune.kumamoto.jp
+minamata.kumamoto.jp
+minamioguni.kumamoto.jp
+nagasu.kumamoto.jp
+nishihara.kumamoto.jp
+oguni.kumamoto.jp
+ozu.kumamoto.jp
+sumoto.kumamoto.jp
+takamori.kumamoto.jp
+uki.kumamoto.jp
+uto.kumamoto.jp
+yamaga.kumamoto.jp
+yamato.kumamoto.jp
+yatsushiro.kumamoto.jp
+ayabe.kyoto.jp
+fukuchiyama.kyoto.jp
+higashiyama.kyoto.jp
+ide.kyoto.jp
+ine.kyoto.jp
+joyo.kyoto.jp
+kameoka.kyoto.jp
+kamo.kyoto.jp
+kita.kyoto.jp
+kizu.kyoto.jp
+kumiyama.kyoto.jp
+kyotamba.kyoto.jp
+kyotanabe.kyoto.jp
+kyotango.kyoto.jp
+maizuru.kyoto.jp
+minami.kyoto.jp
+minamiyamashiro.kyoto.jp
+miyazu.kyoto.jp
+muko.kyoto.jp
+nagaokakyo.kyoto.jp
+nakagyo.kyoto.jp
+nantan.kyoto.jp
+oyamazaki.kyoto.jp
+sakyo.kyoto.jp
+seika.kyoto.jp
+tanabe.kyoto.jp
+uji.kyoto.jp
+ujitawara.kyoto.jp
+wazuka.kyoto.jp
+yamashina.kyoto.jp
+yawata.kyoto.jp
+asahi.mie.jp
+inabe.mie.jp
+ise.mie.jp
+kameyama.mie.jp
+kawagoe.mie.jp
+kiho.mie.jp
+kisosaki.mie.jp
+kiwa.mie.jp
+komono.mie.jp
+kumano.mie.jp
+kuwana.mie.jp
+matsusaka.mie.jp
+meiwa.mie.jp
+mihama.mie.jp
+minamiise.mie.jp
+misugi.mie.jp
+miyama.mie.jp
+nabari.mie.jp
+shima.mie.jp
+suzuka.mie.jp
+tado.mie.jp
+taiki.mie.jp
+taki.mie.jp
+tamaki.mie.jp
+toba.mie.jp
+tsu.mie.jp
+udono.mie.jp
+ureshino.mie.jp
+watarai.mie.jp
+yokkaichi.mie.jp
+furukawa.miyagi.jp
+higashimatsushima.miyagi.jp
+ishinomaki.miyagi.jp
+iwanuma.miyagi.jp
+kakuda.miyagi.jp
+kami.miyagi.jp
+kawasaki.miyagi.jp
+kesennuma.miyagi.jp
+marumori.miyagi.jp
+matsushima.miyagi.jp
+minamisanriku.miyagi.jp
+misato.miyagi.jp
+murata.miyagi.jp
+natori.miyagi.jp
+ogawara.miyagi.jp
+ohira.miyagi.jp
+onagawa.miyagi.jp
+osaki.miyagi.jp
+rifu.miyagi.jp
+semine.miyagi.jp
+shibata.miyagi.jp
+shichikashuku.miyagi.jp
+shikama.miyagi.jp
+shiogama.miyagi.jp
+shiroishi.miyagi.jp
+tagajo.miyagi.jp
+taiwa.miyagi.jp
+tome.miyagi.jp
+tomiya.miyagi.jp
+wakuya.miyagi.jp
+watari.miyagi.jp
+yamamoto.miyagi.jp
+zao.miyagi.jp
+aya.miyazaki.jp
+ebino.miyazaki.jp
+gokase.miyazaki.jp
+hyuga.miyazaki.jp
+kadogawa.miyazaki.jp
+kawaminami.miyazaki.jp
+kijo.miyazaki.jp
+kitagawa.miyazaki.jp
+kitakata.miyazaki.jp
+kitaura.miyazaki.jp
+kobayashi.miyazaki.jp
+kunitomi.miyazaki.jp
+kushima.miyazaki.jp
+mimata.miyazaki.jp
+miyakonojo.miyazaki.jp
+miyazaki.miyazaki.jp
+morotsuka.miyazaki.jp
+nichinan.miyazaki.jp
+nishimera.miyazaki.jp
+nobeoka.miyazaki.jp
+saito.miyazaki.jp
+shiiba.miyazaki.jp
+shintomi.miyazaki.jp
+takaharu.miyazaki.jp
+takanabe.miyazaki.jp
+takazaki.miyazaki.jp
+tsuno.miyazaki.jp
+achi.nagano.jp
+agematsu.nagano.jp
+anan.nagano.jp
+aoki.nagano.jp
+asahi.nagano.jp
+azumino.nagano.jp
+chikuhoku.nagano.jp
+chikuma.nagano.jp
+chino.nagano.jp
+fujimi.nagano.jp
+hakuba.nagano.jp
+hara.nagano.jp
+hiraya.nagano.jp
+iida.nagano.jp
+iijima.nagano.jp
+iiyama.nagano.jp
+iizuna.nagano.jp
+ikeda.nagano.jp
+ikusaka.nagano.jp
+ina.nagano.jp
+karuizawa.nagano.jp
+kawakami.nagano.jp
+kiso.nagano.jp
+kisofukushima.nagano.jp
+kitaaiki.nagano.jp
+komagane.nagano.jp
+komoro.nagano.jp
+matsukawa.nagano.jp
+matsumoto.nagano.jp
+miasa.nagano.jp
+minamiaiki.nagano.jp
+minamimaki.nagano.jp
+minamiminowa.nagano.jp
+minowa.nagano.jp
+miyada.nagano.jp
+miyota.nagano.jp
+mochizuki.nagano.jp
+nagano.nagano.jp
+nagawa.nagano.jp
+nagiso.nagano.jp
+nakagawa.nagano.jp
+nakano.nagano.jp
+nozawaonsen.nagano.jp
+obuse.nagano.jp
+ogawa.nagano.jp
+okaya.nagano.jp
+omachi.nagano.jp
+omi.nagano.jp
+ookuwa.nagano.jp
+ooshika.nagano.jp
+otaki.nagano.jp
+otari.nagano.jp
+sakae.nagano.jp
+sakaki.nagano.jp
+saku.nagano.jp
+sakuho.nagano.jp
+shimosuwa.nagano.jp
+shinanomachi.nagano.jp
+shiojiri.nagano.jp
+suwa.nagano.jp
+suzaka.nagano.jp
+takagi.nagano.jp
+takamori.nagano.jp
+takayama.nagano.jp
+tateshina.nagano.jp
+tatsuno.nagano.jp
+togakushi.nagano.jp
+togura.nagano.jp
+tomi.nagano.jp
+ueda.nagano.jp
+wada.nagano.jp
+yamagata.nagano.jp
+yamanouchi.nagano.jp
+yasaka.nagano.jp
+yasuoka.nagano.jp
+chijiwa.nagasaki.jp
+futsu.nagasaki.jp
+goto.nagasaki.jp
+hasami.nagasaki.jp
+hirado.nagasaki.jp
+iki.nagasaki.jp
+isahaya.nagasaki.jp
+kawatana.nagasaki.jp
+kuchinotsu.nagasaki.jp
+matsuura.nagasaki.jp
+nagasaki.nagasaki.jp
+obama.nagasaki.jp
+omura.nagasaki.jp
+oseto.nagasaki.jp
+saikai.nagasaki.jp
+sasebo.nagasaki.jp
+seihi.nagasaki.jp
+shimabara.nagasaki.jp
+shinkamigoto.nagasaki.jp
+togitsu.nagasaki.jp
+tsushima.nagasaki.jp
+unzen.nagasaki.jp
+ando.nara.jp
+gose.nara.jp
+heguri.nara.jp
+higashiyoshino.nara.jp
+ikaruga.nara.jp
+ikoma.nara.jp
+kamikitayama.nara.jp
+kanmaki.nara.jp
+kashiba.nara.jp
+kashihara.nara.jp
+katsuragi.nara.jp
+kawai.nara.jp
+kawakami.nara.jp
+kawanishi.nara.jp
+koryo.nara.jp
+kurotaki.nara.jp
+mitsue.nara.jp
+miyake.nara.jp
+nara.nara.jp
+nosegawa.nara.jp
+oji.nara.jp
+ouda.nara.jp
+oyodo.nara.jp
+sakurai.nara.jp
+sango.nara.jp
+shimoichi.nara.jp
+shimokitayama.nara.jp
+shinjo.nara.jp
+soni.nara.jp
+takatori.nara.jp
+tawaramoto.nara.jp
+tenkawa.nara.jp
+tenri.nara.jp
+uda.nara.jp
+yamatokoriyama.nara.jp
+yamatotakada.nara.jp
+yamazoe.nara.jp
+yoshino.nara.jp
+aga.niigata.jp
+agano.niigata.jp
+gosen.niigata.jp
+itoigawa.niigata.jp
+izumozaki.niigata.jp
+joetsu.niigata.jp
+kamo.niigata.jp
+kariwa.niigata.jp
+kashiwazaki.niigata.jp
+minamiuonuma.niigata.jp
+mitsuke.niigata.jp
+muika.niigata.jp
+murakami.niigata.jp
+myoko.niigata.jp
+nagaoka.niigata.jp
+niigata.niigata.jp
+ojiya.niigata.jp
+omi.niigata.jp
+sado.niigata.jp
+sanjo.niigata.jp
+seiro.niigata.jp
+seirou.niigata.jp
+sekikawa.niigata.jp
+shibata.niigata.jp
+tagami.niigata.jp
+tainai.niigata.jp
+tochio.niigata.jp
+tokamachi.niigata.jp
+tsubame.niigata.jp
+tsunan.niigata.jp
+uonuma.niigata.jp
+yahiko.niigata.jp
+yoita.niigata.jp
+yuzawa.niigata.jp
+beppu.oita.jp
+bungoono.oita.jp
+bungotakada.oita.jp
+hasama.oita.jp
+hiji.oita.jp
+himeshima.oita.jp
+hita.oita.jp
+kamitsue.oita.jp
+kokonoe.oita.jp
+kuju.oita.jp
+kunisaki.oita.jp
+kusu.oita.jp
+oita.oita.jp
+saiki.oita.jp
+taketa.oita.jp
+tsukumi.oita.jp
+usa.oita.jp
+usuki.oita.jp
+yufu.oita.jp
+akaiwa.okayama.jp
+asakuchi.okayama.jp
+bizen.okayama.jp
+hayashima.okayama.jp
+ibara.okayama.jp
+kagamino.okayama.jp
+kasaoka.okayama.jp
+kibichuo.okayama.jp
+kumenan.okayama.jp
+kurashiki.okayama.jp
+maniwa.okayama.jp
+misaki.okayama.jp
+nagi.okayama.jp
+niimi.okayama.jp
+nishiawakura.okayama.jp
+okayama.okayama.jp
+satosho.okayama.jp
+setouchi.okayama.jp
+shinjo.okayama.jp
+shoo.okayama.jp
+soja.okayama.jp
+takahashi.okayama.jp
+tamano.okayama.jp
+tsuyama.okayama.jp
+wake.okayama.jp
+yakage.okayama.jp
+aguni.okinawa.jp
+ginowan.okinawa.jp
+ginoza.okinawa.jp
+gushikami.okinawa.jp
+haebaru.okinawa.jp
+higashi.okinawa.jp
+hirara.okinawa.jp
+iheya.okinawa.jp
+ishigaki.okinawa.jp
+ishikawa.okinawa.jp
+itoman.okinawa.jp
+izena.okinawa.jp
+kadena.okinawa.jp
+kin.okinawa.jp
+kitadaito.okinawa.jp
+kitanakagusuku.okinawa.jp
+kumejima.okinawa.jp
+kunigami.okinawa.jp
+minamidaito.okinawa.jp
+motobu.okinawa.jp
+nago.okinawa.jp
+naha.okinawa.jp
+nakagusuku.okinawa.jp
+nakijin.okinawa.jp
+nanjo.okinawa.jp
+nishihara.okinawa.jp
+ogimi.okinawa.jp
+okinawa.okinawa.jp
+onna.okinawa.jp
+shimoji.okinawa.jp
+taketomi.okinawa.jp
+tarama.okinawa.jp
+tokashiki.okinawa.jp
+tomigusuku.okinawa.jp
+tonaki.okinawa.jp
+urasoe.okinawa.jp
+uruma.okinawa.jp
+yaese.okinawa.jp
+yomitan.okinawa.jp
+yonabaru.okinawa.jp
+yonaguni.okinawa.jp
+zamami.okinawa.jp
+abeno.osaka.jp
+chihayaakasaka.osaka.jp
+chuo.osaka.jp
+daito.osaka.jp
+fujiidera.osaka.jp
+habikino.osaka.jp
+hannan.osaka.jp
+higashiosaka.osaka.jp
+higashisumiyoshi.osaka.jp
+higashiyodogawa.osaka.jp
+hirakata.osaka.jp
+ibaraki.osaka.jp
+ikeda.osaka.jp
+izumi.osaka.jp
+izumiotsu.osaka.jp
+izumisano.osaka.jp
+kadoma.osaka.jp
+kaizuka.osaka.jp
+kanan.osaka.jp
+kashiwara.osaka.jp
+katano.osaka.jp
+kawachinagano.osaka.jp
+kishiwada.osaka.jp
+kita.osaka.jp
+kumatori.osaka.jp
+matsubara.osaka.jp
+minato.osaka.jp
+minoh.osaka.jp
+misaki.osaka.jp
+moriguchi.osaka.jp
+neyagawa.osaka.jp
+nishi.osaka.jp
+nose.osaka.jp
+osakasayama.osaka.jp
+sakai.osaka.jp
+sayama.osaka.jp
+sennan.osaka.jp
+settsu.osaka.jp
+shijonawate.osaka.jp
+shimamoto.osaka.jp
+suita.osaka.jp
+tadaoka.osaka.jp
+taishi.osaka.jp
+tajiri.osaka.jp
+takaishi.osaka.jp
+takatsuki.osaka.jp
+tondabayashi.osaka.jp
+toyonaka.osaka.jp
+toyono.osaka.jp
+yao.osaka.jp
+ariake.saga.jp
+arita.saga.jp
+fukudomi.saga.jp
+genkai.saga.jp
+hamatama.saga.jp
+hizen.saga.jp
+imari.saga.jp
+kamimine.saga.jp
+kanzaki.saga.jp
+karatsu.saga.jp
+kashima.saga.jp
+kitagata.saga.jp
+kitahata.saga.jp
+kiyama.saga.jp
+kouhoku.saga.jp
+kyuragi.saga.jp
+nishiarita.saga.jp
+ogi.saga.jp
+omachi.saga.jp
+ouchi.saga.jp
+saga.saga.jp
+shiroishi.saga.jp
+taku.saga.jp
+tara.saga.jp
+tosu.saga.jp
+yoshinogari.saga.jp
+arakawa.saitama.jp
+asaka.saitama.jp
+chichibu.saitama.jp
+fujimi.saitama.jp
+fujimino.saitama.jp
+fukaya.saitama.jp
+hanno.saitama.jp
+hanyu.saitama.jp
+hasuda.saitama.jp
+hatogaya.saitama.jp
+hatoyama.saitama.jp
+hidaka.saitama.jp
+higashichichibu.saitama.jp
+higashimatsuyama.saitama.jp
+honjo.saitama.jp
+ina.saitama.jp
+iruma.saitama.jp
+iwatsuki.saitama.jp
+kamiizumi.saitama.jp
+kamikawa.saitama.jp
+kamisato.saitama.jp
+kasukabe.saitama.jp
+kawagoe.saitama.jp
+kawaguchi.saitama.jp
+kawajima.saitama.jp
+kazo.saitama.jp
+kitamoto.saitama.jp
+koshigaya.saitama.jp
+kounosu.saitama.jp
+kuki.saitama.jp
+kumagaya.saitama.jp
+matsubushi.saitama.jp
+minano.saitama.jp
+misato.saitama.jp
+miyashiro.saitama.jp
+miyoshi.saitama.jp
+moroyama.saitama.jp
+nagatoro.saitama.jp
+namegawa.saitama.jp
+niiza.saitama.jp
+ogano.saitama.jp
+ogawa.saitama.jp
+ogose.saitama.jp
+okegawa.saitama.jp
+omiya.saitama.jp
+otaki.saitama.jp
+ranzan.saitama.jp
+ryokami.saitama.jp
+saitama.saitama.jp
+sakado.saitama.jp
+satte.saitama.jp
+sayama.saitama.jp
+shiki.saitama.jp
+shiraoka.saitama.jp
+soka.saitama.jp
+sugito.saitama.jp
+toda.saitama.jp
+tokigawa.saitama.jp
+tokorozawa.saitama.jp
+tsurugashima.saitama.jp
+urawa.saitama.jp
+warabi.saitama.jp
+yashio.saitama.jp
+yokoze.saitama.jp
+yono.saitama.jp
+yorii.saitama.jp
+yoshida.saitama.jp
+yoshikawa.saitama.jp
+yoshimi.saitama.jp
+aisho.shiga.jp
+gamo.shiga.jp
+higashiomi.shiga.jp
+hikone.shiga.jp
+koka.shiga.jp
+konan.shiga.jp
+kosei.shiga.jp
+koto.shiga.jp
+kusatsu.shiga.jp
+maibara.shiga.jp
+moriyama.shiga.jp
+nagahama.shiga.jp
+nishiazai.shiga.jp
+notogawa.shiga.jp
+omihachiman.shiga.jp
+otsu.shiga.jp
+ritto.shiga.jp
+ryuoh.shiga.jp
+takashima.shiga.jp
+takatsuki.shiga.jp
+torahime.shiga.jp
+toyosato.shiga.jp
+yasu.shiga.jp
+akagi.shimane.jp
+ama.shimane.jp
+gotsu.shimane.jp
+hamada.shimane.jp
+higashiizumo.shimane.jp
+hikawa.shimane.jp
+hikimi.shimane.jp
+izumo.shimane.jp
+kakinoki.shimane.jp
+masuda.shimane.jp
+matsue.shimane.jp
+misato.shimane.jp
+nishinoshima.shimane.jp
+ohda.shimane.jp
+okinoshima.shimane.jp
+okuizumo.shimane.jp
+shimane.shimane.jp
+tamayu.shimane.jp
+tsuwano.shimane.jp
+unnan.shimane.jp
+yakumo.shimane.jp
+yasugi.shimane.jp
+yatsuka.shimane.jp
+arai.shizuoka.jp
+atami.shizuoka.jp
+fuji.shizuoka.jp
+fujieda.shizuoka.jp
+fujikawa.shizuoka.jp
+fujinomiya.shizuoka.jp
+fukuroi.shizuoka.jp
+gotemba.shizuoka.jp
+haibara.shizuoka.jp
+hamamatsu.shizuoka.jp
+higashiizu.shizuoka.jp
+ito.shizuoka.jp
+iwata.shizuoka.jp
+izu.shizuoka.jp
+izunokuni.shizuoka.jp
+kakegawa.shizuoka.jp
+kannami.shizuoka.jp
+kawanehon.shizuoka.jp
+kawazu.shizuoka.jp
+kikugawa.shizuoka.jp
+kosai.shizuoka.jp
+makinohara.shizuoka.jp
+matsuzaki.shizuoka.jp
+minamiizu.shizuoka.jp
+mishima.shizuoka.jp
+morimachi.shizuoka.jp
+nishiizu.shizuoka.jp
+numazu.shizuoka.jp
+omaezaki.shizuoka.jp
+shimada.shizuoka.jp
+shimizu.shizuoka.jp
+shimoda.shizuoka.jp
+shizuoka.shizuoka.jp
+susono.shizuoka.jp
+yaizu.shizuoka.jp
+yoshida.shizuoka.jp
+ashikaga.tochigi.jp
+bato.tochigi.jp
+haga.tochigi.jp
+ichikai.tochigi.jp
+iwafune.tochigi.jp
+kaminokawa.tochigi.jp
+kanuma.tochigi.jp
+karasuyama.tochigi.jp
+kuroiso.tochigi.jp
+mashiko.tochigi.jp
+mibu.tochigi.jp
+moka.tochigi.jp
+motegi.tochigi.jp
+nasu.tochigi.jp
+nasushiobara.tochigi.jp
+nikko.tochigi.jp
+nishikata.tochigi.jp
+nogi.tochigi.jp
+ohira.tochigi.jp
+ohtawara.tochigi.jp
+oyama.tochigi.jp
+sakura.tochigi.jp
+sano.tochigi.jp
+shimotsuke.tochigi.jp
+shioya.tochigi.jp
+takanezawa.tochigi.jp
+tochigi.tochigi.jp
+tsuga.tochigi.jp
+ujiie.tochigi.jp
+utsunomiya.tochigi.jp
+yaita.tochigi.jp
+aizumi.tokushima.jp
+anan.tokushima.jp
+ichiba.tokushima.jp
+itano.tokushima.jp
+kainan.tokushima.jp
+komatsushima.tokushima.jp
+matsushige.tokushima.jp
+mima.tokushima.jp
+minami.tokushima.jp
+miyoshi.tokushima.jp
+mugi.tokushima.jp
+nakagawa.tokushima.jp
+naruto.tokushima.jp
+sanagochi.tokushima.jp
+shishikui.tokushima.jp
+tokushima.tokushima.jp
+wajiki.tokushima.jp
+adachi.tokyo.jp
+akiruno.tokyo.jp
+akishima.tokyo.jp
+aogashima.tokyo.jp
+arakawa.tokyo.jp
+bunkyo.tokyo.jp
+chiyoda.tokyo.jp
+chofu.tokyo.jp
+chuo.tokyo.jp
+edogawa.tokyo.jp
+fuchu.tokyo.jp
+fussa.tokyo.jp
+hachijo.tokyo.jp
+hachioji.tokyo.jp
+hamura.tokyo.jp
+higashikurume.tokyo.jp
+higashimurayama.tokyo.jp
+higashiyamato.tokyo.jp
+hino.tokyo.jp
+hinode.tokyo.jp
+hinohara.tokyo.jp
+inagi.tokyo.jp
+itabashi.tokyo.jp
+katsushika.tokyo.jp
+kita.tokyo.jp
+kiyose.tokyo.jp
+kodaira.tokyo.jp
+koganei.tokyo.jp
+kokubunji.tokyo.jp
+komae.tokyo.jp
+koto.tokyo.jp
+kouzushima.tokyo.jp
+kunitachi.tokyo.jp
+machida.tokyo.jp
+meguro.tokyo.jp
+minato.tokyo.jp
+mitaka.tokyo.jp
+mizuho.tokyo.jp
+musashimurayama.tokyo.jp
+musashino.tokyo.jp
+nakano.tokyo.jp
+nerima.tokyo.jp
+ogasawara.tokyo.jp
+okutama.tokyo.jp
+ome.tokyo.jp
+oshima.tokyo.jp
+ota.tokyo.jp
+setagaya.tokyo.jp
+shibuya.tokyo.jp
+shinagawa.tokyo.jp
+shinjuku.tokyo.jp
+suginami.tokyo.jp
+sumida.tokyo.jp
+tachikawa.tokyo.jp
+taito.tokyo.jp
+tama.tokyo.jp
+toshima.tokyo.jp
+chizu.tottori.jp
+hino.tottori.jp
+kawahara.tottori.jp
+koge.tottori.jp
+kotoura.tottori.jp
+misasa.tottori.jp
+nanbu.tottori.jp
+nichinan.tottori.jp
+sakaiminato.tottori.jp
+tottori.tottori.jp
+wakasa.tottori.jp
+yazu.tottori.jp
+yonago.tottori.jp
+asahi.toyama.jp
+fuchu.toyama.jp
+fukumitsu.toyama.jp
+funahashi.toyama.jp
+himi.toyama.jp
+imizu.toyama.jp
+inami.toyama.jp
+johana.toyama.jp
+kamiichi.toyama.jp
+kurobe.toyama.jp
+nakaniikawa.toyama.jp
+namerikawa.toyama.jp
+nanto.toyama.jp
+nyuzen.toyama.jp
+oyabe.toyama.jp
+taira.toyama.jp
+takaoka.toyama.jp
+tateyama.toyama.jp
+toga.toyama.jp
+tonami.toyama.jp
+toyama.toyama.jp
+unazuki.toyama.jp
+uozu.toyama.jp
+yamada.toyama.jp
+arida.wakayama.jp
+aridagawa.wakayama.jp
+gobo.wakayama.jp
+hashimoto.wakayama.jp
+hidaka.wakayama.jp
+hirogawa.wakayama.jp
+inami.wakayama.jp
+iwade.wakayama.jp
+kainan.wakayama.jp
+kamitonda.wakayama.jp
+katsuragi.wakayama.jp
+kimino.wakayama.jp
+kinokawa.wakayama.jp
+kitayama.wakayama.jp
+koya.wakayama.jp
+koza.wakayama.jp
+kozagawa.wakayama.jp
+kudoyama.wakayama.jp
+kushimoto.wakayama.jp
+mihama.wakayama.jp
+misato.wakayama.jp
+nachikatsuura.wakayama.jp
+shingu.wakayama.jp
+shirahama.wakayama.jp
+taiji.wakayama.jp
+tanabe.wakayama.jp
+wakayama.wakayama.jp
+yuasa.wakayama.jp
+yura.wakayama.jp
+asahi.yamagata.jp
+funagata.yamagata.jp
+higashine.yamagata.jp
+iide.yamagata.jp
+kahoku.yamagata.jp
+kaminoyama.yamagata.jp
+kaneyama.yamagata.jp
+kawanishi.yamagata.jp
+mamurogawa.yamagata.jp
+mikawa.yamagata.jp
+murayama.yamagata.jp
+nagai.yamagata.jp
+nakayama.yamagata.jp
+nanyo.yamagata.jp
+nishikawa.yamagata.jp
+obanazawa.yamagata.jp
+oe.yamagata.jp
+oguni.yamagata.jp
+ohkura.yamagata.jp
+oishida.yamagata.jp
+sagae.yamagata.jp
+sakata.yamagata.jp
+sakegawa.yamagata.jp
+shinjo.yamagata.jp
+shirataka.yamagata.jp
+shonai.yamagata.jp
+takahata.yamagata.jp
+tendo.yamagata.jp
+tozawa.yamagata.jp
+tsuruoka.yamagata.jp
+yamagata.yamagata.jp
+yamanobe.yamagata.jp
+yonezawa.yamagata.jp
+yuza.yamagata.jp
+abu.yamaguchi.jp
+hagi.yamaguchi.jp
+hikari.yamaguchi.jp
+hofu.yamaguchi.jp
+iwakuni.yamaguchi.jp
+kudamatsu.yamaguchi.jp
+mitou.yamaguchi.jp
+nagato.yamaguchi.jp
+oshima.yamaguchi.jp
+shimonoseki.yamaguchi.jp
+shunan.yamaguchi.jp
+tabuse.yamaguchi.jp
+tokuyama.yamaguchi.jp
+toyota.yamaguchi.jp
+ube.yamaguchi.jp
+yuu.yamaguchi.jp
+chuo.yamanashi.jp
+doshi.yamanashi.jp
+fuefuki.yamanashi.jp
+fujikawa.yamanashi.jp
+fujikawaguchiko.yamanashi.jp
+fujiyoshida.yamanashi.jp
+hayakawa.yamanashi.jp
+hokuto.yamanashi.jp
+ichikawamisato.yamanashi.jp
+kai.yamanashi.jp
+kofu.yamanashi.jp
+koshu.yamanashi.jp
+kosuge.yamanashi.jp
+minami-alps.yamanashi.jp
+minobu.yamanashi.jp
+nakamichi.yamanashi.jp
+nanbu.yamanashi.jp
+narusawa.yamanashi.jp
+nirasaki.yamanashi.jp
+nishikatsura.yamanashi.jp
+oshino.yamanashi.jp
+otsuki.yamanashi.jp
+showa.yamanashi.jp
+tabayama.yamanashi.jp
+tsuru.yamanashi.jp
+uenohara.yamanashi.jp
+yamanakako.yamanashi.jp
+yamanashi.yamanashi.jp
+
+// ke : http://www.kenic.or.ke/index.php?option=com_content&task=view&id=117&Itemid=145
+*.ke
+
+// kg : http://www.domain.kg/dmn_n.html
+kg
+org.kg
+net.kg
+com.kg
+edu.kg
+gov.kg
+mil.kg
+
+// kh : http://www.mptc.gov.kh/dns_registration.htm
+*.kh
+
+// ki : http://www.ki/dns/index.html
+ki
+edu.ki
+biz.ki
+net.ki
+org.ki
+gov.ki
+info.ki
+com.ki
+
+// km : http://en.wikipedia.org/wiki/.km
+// http://www.domaine.km/documents/charte.doc
+km
+org.km
+nom.km
+gov.km
+prd.km
+tm.km
+edu.km
+mil.km
+ass.km
+com.km
+// These are only mentioned as proposed suggestions at domaine.km, but
+// http://en.wikipedia.org/wiki/.km says they're available for registration:
+coop.km
+asso.km
+presse.km
+medecin.km
+notaires.km
+pharmaciens.km
+veterinaire.km
+gouv.km
+
+// kn : http://en.wikipedia.org/wiki/.kn
+// http://www.dot.kn/domainRules.html
+kn
+net.kn
+org.kn
+edu.kn
+gov.kn
+
+// kp : http://www.kcce.kp/en_index.php
+com.kp
+edu.kp
+gov.kp
+org.kp
+rep.kp
+tra.kp
+
+// kr : http://en.wikipedia.org/wiki/.kr
+// see also: http://domain.nida.or.kr/eng/registration.jsp
+kr
+ac.kr
+co.kr
+es.kr
+go.kr
+hs.kr
+kg.kr
+mil.kr
+ms.kr
+ne.kr
+or.kr
+pe.kr
+re.kr
+sc.kr
+// kr geographical names
+busan.kr
+chungbuk.kr
+chungnam.kr
+daegu.kr
+daejeon.kr
+gangwon.kr
+gwangju.kr
+gyeongbuk.kr
+gyeonggi.kr
+gyeongnam.kr
+incheon.kr
+jeju.kr
+jeonbuk.kr
+jeonnam.kr
+seoul.kr
+ulsan.kr
+
+// kw : http://en.wikipedia.org/wiki/.kw
+*.kw
+
+// ky : http://www.icta.ky/da_ky_reg_dom.php
+// Confirmed by registry <kysupport@perimeterusa.com> 2008-06-17
+ky
+edu.ky
+gov.ky
+com.ky
+org.ky
+net.ky
+
+// kz : http://en.wikipedia.org/wiki/.kz
+// see also: http://www.nic.kz/rules/index.jsp
+kz
+org.kz
+edu.kz
+net.kz
+gov.kz
+mil.kz
+com.kz
+
+// la : http://en.wikipedia.org/wiki/.la
+// Submitted by registry <gavin.brown@nic.la> 2008-06-10
+la
+int.la
+net.la
+info.la
+edu.la
+gov.la
+per.la
+com.la
+org.la
+
+// lb : http://en.wikipedia.org/wiki/.lb
+// Submitted by registry <randy@psg.com> 2008-06-17
+com.lb
+edu.lb
+gov.lb
+net.lb
+org.lb
+
+// lc : http://en.wikipedia.org/wiki/.lc
+// see also: http://www.nic.lc/rules.htm
+lc
+com.lc
+net.lc
+co.lc
+org.lc
+edu.lc
+gov.lc
+
+// li : http://en.wikipedia.org/wiki/.li
+li
+
+// lk : http://www.nic.lk/seclevpr.html
+lk
+gov.lk
+sch.lk
+net.lk
+int.lk
+com.lk
+org.lk
+edu.lk
+ngo.lk
+soc.lk
+web.lk
+ltd.lk
+assn.lk
+grp.lk
+hotel.lk
+
+// lr : http://psg.com/dns/lr/lr.txt
+// Submitted by registry <randy@psg.com> 2008-06-17
+com.lr
+edu.lr
+gov.lr
+org.lr
+net.lr
+
+// ls : http://en.wikipedia.org/wiki/.ls
+ls
+co.ls
+org.ls
+
+// lt : http://en.wikipedia.org/wiki/.lt
+lt
+// gov.lt : http://www.gov.lt/index_en.php
+gov.lt
+
+// lu : http://www.dns.lu/en/
+lu
+
+// lv : http://www.nic.lv/DNS/En/generic.php
+lv
+com.lv
+edu.lv
+gov.lv
+org.lv
+mil.lv
+id.lv
+net.lv
+asn.lv
+conf.lv
+
+// ly : http://www.nic.ly/regulations.php
+ly
+com.ly
+net.ly
+gov.ly
+plc.ly
+edu.ly
+sch.ly
+med.ly
+org.ly
+id.ly
+
+// ma : http://en.wikipedia.org/wiki/.ma
+// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf
+ma
+co.ma
+net.ma
+gov.ma
+org.ma
+ac.ma
+press.ma
+
+// mc : http://www.nic.mc/
+mc
+tm.mc
+asso.mc
+
+// md : http://en.wikipedia.org/wiki/.md
+md
+
+// me : http://en.wikipedia.org/wiki/.me
+me
+co.me
+net.me
+org.me
+edu.me
+ac.me
+gov.me
+its.me
+priv.me
+
+// mg : http://www.nic.mg/tarif.htm
+mg
+org.mg
+nom.mg
+gov.mg
+prd.mg
+tm.mg
+edu.mg
+mil.mg
+com.mg
+
+// mh : http://en.wikipedia.org/wiki/.mh
+mh
+
+// mil : http://en.wikipedia.org/wiki/.mil
+mil
+
+// mk : http://en.wikipedia.org/wiki/.mk
+// see also: http://dns.marnet.net.mk/postapka.php
+mk
+com.mk
+org.mk
+net.mk
+edu.mk
+gov.mk
+inf.mk
+name.mk
+
+// ml : http://www.gobin.info/domainname/ml-template.doc
+// see also: http://en.wikipedia.org/wiki/.ml
+ml
+com.ml
+edu.ml
+gouv.ml
+gov.ml
+net.ml
+org.ml
+presse.ml
+
+// mm : http://en.wikipedia.org/wiki/.mm
+*.mm
+
+// mn : http://en.wikipedia.org/wiki/.mn
+mn
+gov.mn
+edu.mn
+org.mn
+
+// mo : http://www.monic.net.mo/
+mo
+com.mo
+net.mo
+org.mo
+edu.mo
+gov.mo
+
+// mobi : http://en.wikipedia.org/wiki/.mobi
+mobi
+
+// mp : http://www.dot.mp/
+// Confirmed by registry <dcamacho@saipan.com> 2008-06-17
+mp
+
+// mq : http://en.wikipedia.org/wiki/.mq
+mq
+
+// mr : http://en.wikipedia.org/wiki/.mr
+mr
+gov.mr
+
+// ms : http://en.wikipedia.org/wiki/.ms
+ms
+
+// mt : https://www.nic.org.mt/dotmt/
+*.mt
+
+// mu : http://en.wikipedia.org/wiki/.mu
+mu
+com.mu
+net.mu
+org.mu
+gov.mu
+ac.mu
+co.mu
+or.mu
+
+// museum : http://about.museum/naming/
+// http://index.museum/
+museum
+academy.museum
+agriculture.museum
+air.museum
+airguard.museum
+alabama.museum
+alaska.museum
+amber.museum
+ambulance.museum
+american.museum
+americana.museum
+americanantiques.museum
+americanart.museum
+amsterdam.museum
+and.museum
+annefrank.museum
+anthro.museum
+anthropology.museum
+antiques.museum
+aquarium.museum
+arboretum.museum
+archaeological.museum
+archaeology.museum
+architecture.museum
+art.museum
+artanddesign.museum
+artcenter.museum
+artdeco.museum
+arteducation.museum
+artgallery.museum
+arts.museum
+artsandcrafts.museum
+asmatart.museum
+assassination.museum
+assisi.museum
+association.museum
+astronomy.museum
+atlanta.museum
+austin.museum
+australia.museum
+automotive.museum
+aviation.museum
+axis.museum
+badajoz.museum
+baghdad.museum
+bahn.museum
+bale.museum
+baltimore.museum
+barcelona.museum
+baseball.museum
+basel.museum
+baths.museum
+bauern.museum
+beauxarts.museum
+beeldengeluid.museum
+bellevue.museum
+bergbau.museum
+berkeley.museum
+berlin.museum
+bern.museum
+bible.museum
+bilbao.museum
+bill.museum
+birdart.museum
+birthplace.museum
+bonn.museum
+boston.museum
+botanical.museum
+botanicalgarden.museum
+botanicgarden.museum
+botany.museum
+brandywinevalley.museum
+brasil.museum
+bristol.museum
+british.museum
+britishcolumbia.museum
+broadcast.museum
+brunel.museum
+brussel.museum
+brussels.museum
+bruxelles.museum
+building.museum
+burghof.museum
+bus.museum
+bushey.museum
+cadaques.museum
+california.museum
+cambridge.museum
+can.museum
+canada.museum
+capebreton.museum
+carrier.museum
+cartoonart.museum
+casadelamoneda.museum
+castle.museum
+castres.museum
+celtic.museum
+center.museum
+chattanooga.museum
+cheltenham.museum
+chesapeakebay.museum
+chicago.museum
+children.museum
+childrens.museum
+childrensgarden.museum
+chiropractic.museum
+chocolate.museum
+christiansburg.museum
+cincinnati.museum
+cinema.museum
+circus.museum
+civilisation.museum
+civilization.museum
+civilwar.museum
+clinton.museum
+clock.museum
+coal.museum
+coastaldefence.museum
+cody.museum
+coldwar.museum
+collection.museum
+colonialwilliamsburg.museum
+coloradoplateau.museum
+columbia.museum
+columbus.museum
+communication.museum
+communications.museum
+community.museum
+computer.museum
+computerhistory.museum
+comunicações.museum
+contemporary.museum
+contemporaryart.museum
+convent.museum
+copenhagen.museum
+corporation.museum
+correios-e-telecomunicações.museum
+corvette.museum
+costume.museum
+countryestate.museum
+county.museum
+crafts.museum
+cranbrook.museum
+creation.museum
+cultural.museum
+culturalcenter.museum
+culture.museum
+cyber.museum
+cymru.museum
+dali.museum
+dallas.museum
+database.museum
+ddr.museum
+decorativearts.museum
+delaware.museum
+delmenhorst.museum
+denmark.museum
+depot.museum
+design.museum
+detroit.museum
+dinosaur.museum
+discovery.museum
+dolls.museum
+donostia.museum
+durham.museum
+eastafrica.museum
+eastcoast.museum
+education.museum
+educational.museum
+egyptian.museum
+eisenbahn.museum
+elburg.museum
+elvendrell.museum
+embroidery.museum
+encyclopedic.museum
+england.museum
+entomology.museum
+environment.museum
+environmentalconservation.museum
+epilepsy.museum
+essex.museum
+estate.museum
+ethnology.museum
+exeter.museum
+exhibition.museum
+family.museum
+farm.museum
+farmequipment.museum
+farmers.museum
+farmstead.museum
+field.museum
+figueres.museum
+filatelia.museum
+film.museum
+fineart.museum
+finearts.museum
+finland.museum
+flanders.museum
+florida.museum
+force.museum
+fortmissoula.museum
+fortworth.museum
+foundation.museum
+francaise.museum
+frankfurt.museum
+franziskaner.museum
+freemasonry.museum
+freiburg.museum
+fribourg.museum
+frog.museum
+fundacio.museum
+furniture.museum
+gallery.museum
+garden.museum
+gateway.museum
+geelvinck.museum
+gemological.museum
+geology.museum
+georgia.museum
+giessen.museum
+glas.museum
+glass.museum
+gorge.museum
+grandrapids.museum
+graz.museum
+guernsey.museum
+halloffame.museum
+hamburg.museum
+handson.museum
+harvestcelebration.museum
+hawaii.museum
+health.museum
+heimatunduhren.museum
+hellas.museum
+helsinki.museum
+hembygdsforbund.museum
+heritage.museum
+histoire.museum
+historical.museum
+historicalsociety.museum
+historichouses.museum
+historisch.museum
+historisches.museum
+history.museum
+historyofscience.museum
+horology.museum
+house.museum
+humanities.museum
+illustration.museum
+imageandsound.museum
+indian.museum
+indiana.museum
+indianapolis.museum
+indianmarket.museum
+intelligence.museum
+interactive.museum
+iraq.museum
+iron.museum
+isleofman.museum
+jamison.museum
+jefferson.museum
+jerusalem.museum
+jewelry.museum
+jewish.museum
+jewishart.museum
+jfk.museum
+journalism.museum
+judaica.museum
+judygarland.museum
+juedisches.museum
+juif.museum
+karate.museum
+karikatur.museum
+kids.museum
+koebenhavn.museum
+koeln.museum
+kunst.museum
+kunstsammlung.museum
+kunstunddesign.museum
+labor.museum
+labour.museum
+lajolla.museum
+lancashire.museum
+landes.museum
+lans.museum
+läns.museum
+larsson.museum
+lewismiller.museum
+lincoln.museum
+linz.museum
+living.museum
+livinghistory.museum
+localhistory.museum
+london.museum
+losangeles.museum
+louvre.museum
+loyalist.museum
+lucerne.museum
+luxembourg.museum
+luzern.museum
+mad.museum
+madrid.museum
+mallorca.museum
+manchester.museum
+mansion.museum
+mansions.museum
+manx.museum
+marburg.museum
+maritime.museum
+maritimo.museum
+maryland.museum
+marylhurst.museum
+media.museum
+medical.museum
+medizinhistorisches.museum
+meeres.museum
+memorial.museum
+mesaverde.museum
+michigan.museum
+midatlantic.museum
+military.museum
+mill.museum
+miners.museum
+mining.museum
+minnesota.museum
+missile.museum
+missoula.museum
+modern.museum
+moma.museum
+money.museum
+monmouth.museum
+monticello.museum
+montreal.museum
+moscow.museum
+motorcycle.museum
+muenchen.museum
+muenster.museum
+mulhouse.museum
+muncie.museum
+museet.museum
+museumcenter.museum
+museumvereniging.museum
+music.museum
+national.museum
+nationalfirearms.museum
+nationalheritage.museum
+nativeamerican.museum
+naturalhistory.museum
+naturalhistorymuseum.museum
+naturalsciences.museum
+nature.museum
+naturhistorisches.museum
+natuurwetenschappen.museum
+naumburg.museum
+naval.museum
+nebraska.museum
+neues.museum
+newhampshire.museum
+newjersey.museum
+newmexico.museum
+newport.museum
+newspaper.museum
+newyork.museum
+niepce.museum
+norfolk.museum
+north.museum
+nrw.museum
+nuernberg.museum
+nuremberg.museum
+nyc.museum
+nyny.museum
+oceanographic.museum
+oceanographique.museum
+omaha.museum
+online.museum
+ontario.museum
+openair.museum
+oregon.museum
+oregontrail.museum
+otago.museum
+oxford.museum
+pacific.museum
+paderborn.museum
+palace.museum
+paleo.museum
+palmsprings.museum
+panama.museum
+paris.museum
+pasadena.museum
+pharmacy.museum
+philadelphia.museum
+philadelphiaarea.museum
+philately.museum
+phoenix.museum
+photography.museum
+pilots.museum
+pittsburgh.museum
+planetarium.museum
+plantation.museum
+plants.museum
+plaza.museum
+portal.museum
+portland.museum
+portlligat.museum
+posts-and-telecommunications.museum
+preservation.museum
+presidio.museum
+press.museum
+project.museum
+public.museum
+pubol.museum
+quebec.museum
+railroad.museum
+railway.museum
+research.museum
+resistance.museum
+riodejaneiro.museum
+rochester.museum
+rockart.museum
+roma.museum
+russia.museum
+saintlouis.museum
+salem.museum
+salvadordali.museum
+salzburg.museum
+sandiego.museum
+sanfrancisco.museum
+santabarbara.museum
+santacruz.museum
+santafe.museum
+saskatchewan.museum
+satx.museum
+savannahga.museum
+schlesisches.museum
+schoenbrunn.museum
+schokoladen.museum
+school.museum
+schweiz.museum
+science.museum
+scienceandhistory.museum
+scienceandindustry.museum
+sciencecenter.museum
+sciencecenters.museum
+science-fiction.museum
+sciencehistory.museum
+sciences.museum
+sciencesnaturelles.museum
+scotland.museum
+seaport.museum
+settlement.museum
+settlers.museum
+shell.museum
+sherbrooke.museum
+sibenik.museum
+silk.museum
+ski.museum
+skole.museum
+society.museum
+sologne.museum
+soundandvision.museum
+southcarolina.museum
+southwest.museum
+space.museum
+spy.museum
+square.museum
+stadt.museum
+stalbans.museum
+starnberg.museum
+state.museum
+stateofdelaware.museum
+station.museum
+steam.museum
+steiermark.museum
+stjohn.museum
+stockholm.museum
+stpetersburg.museum
+stuttgart.museum
+suisse.museum
+surgeonshall.museum
+surrey.museum
+svizzera.museum
+sweden.museum
+sydney.museum
+tank.museum
+tcm.museum
+technology.museum
+telekommunikation.museum
+television.museum
+texas.museum
+textile.museum
+theater.museum
+time.museum
+timekeeping.museum
+topology.museum
+torino.museum
+touch.museum
+town.museum
+transport.museum
+tree.museum
+trolley.museum
+trust.museum
+trustee.museum
+uhren.museum
+ulm.museum
+undersea.museum
+university.museum
+usa.museum
+usantiques.museum
+usarts.museum
+uscountryestate.museum
+usculture.museum
+usdecorativearts.museum
+usgarden.museum
+ushistory.museum
+ushuaia.museum
+uslivinghistory.museum
+utah.museum
+uvic.museum
+valley.museum
+vantaa.museum
+versailles.museum
+viking.museum
+village.museum
+virginia.museum
+virtual.museum
+virtuel.museum
+vlaanderen.museum
+volkenkunde.museum
+wales.museum
+wallonie.museum
+war.museum
+washingtondc.museum
+watchandclock.museum
+watch-and-clock.museum
+western.museum
+westfalen.museum
+whaling.museum
+wildlife.museum
+williamsburg.museum
+windmill.museum
+workshop.museum
+york.museum
+yorkshire.museum
+yosemite.museum
+youth.museum
+zoological.museum
+zoology.museum
+ירושלי×.museum
+иком.museum
+
+// mv : http://en.wikipedia.org/wiki/.mv
+// "mv" included because, contra Wikipedia, google.mv exists.
+mv
+aero.mv
+biz.mv
+com.mv
+coop.mv
+edu.mv
+gov.mv
+info.mv
+int.mv
+mil.mv
+museum.mv
+name.mv
+net.mv
+org.mv
+pro.mv
+
+// mw : http://www.registrar.mw/
+mw
+ac.mw
+biz.mw
+co.mw
+com.mw
+coop.mw
+edu.mw
+gov.mw
+int.mw
+museum.mw
+net.mw
+org.mw
+
+// mx : http://www.nic.mx/
+// Submitted by registry <farias@nic.mx> 2008-06-19
+mx
+com.mx
+org.mx
+gob.mx
+edu.mx
+net.mx
+
+// my : http://www.mynic.net.my/
+my
+com.my
+net.my
+org.my
+gov.my
+edu.my
+mil.my
+name.my
+
+// mz : http://www.gobin.info/domainname/mz-template.doc
+*.mz
+!teledata.mz
+
+// na : http://www.na-nic.com.na/
+// http://www.info.na/domain/
+na
+info.na
+pro.na
+name.na
+school.na
+or.na
+dr.na
+us.na
+mx.na
+ca.na
+in.na
+cc.na
+tv.na
+ws.na
+mobi.na
+co.na
+com.na
+org.na
+
+// name : has 2nd-level tlds, but there's no list of them
+name
+
+// nc : http://www.cctld.nc/
+nc
+asso.nc
+
+// ne : http://en.wikipedia.org/wiki/.ne
+ne
+
+// net : http://en.wikipedia.org/wiki/.net
+net
+
+// nf : http://en.wikipedia.org/wiki/.nf
+nf
+com.nf
+net.nf
+per.nf
+rec.nf
+web.nf
+arts.nf
+firm.nf
+info.nf
+other.nf
+store.nf
+
+// ng : http://psg.com/dns/ng/
+// Submitted by registry <randy@psg.com> 2008-06-17
+ac.ng
+com.ng
+edu.ng
+gov.ng
+net.ng
+org.ng
+
+// ni : http://www.nic.ni/dominios.htm
+*.ni
+
+// nl : http://www.domain-registry.nl/ace.php/c,728,122,,,,Home.html
+// Confirmed by registry <Antoin.Verschuren@sidn.nl> (with technical
+// reservations) 2008-06-08
+nl
+
+// BV.nl will be a registry for dutch BV's (besloten vennootschap)
+bv.nl
+
+// no : http://www.norid.no/regelverk/index.en.html
+// The Norwegian registry has declined to notify us of updates. The web pages
+// referenced below are the official source of the data. There is also an
+// announce mailing list:
+// https://postlister.uninett.no/sympa/info/norid-diskusjon
+no
+// Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html
+fhs.no
+vgs.no
+fylkesbibl.no
+folkebibl.no
+museum.no
+idrett.no
+priv.no
+// Non-Norid generic domains : http://www.norid.no/regelverk/vedlegg-d.en.html
+mil.no
+stat.no
+dep.no
+kommune.no
+herad.no
+// no geographical names : http://www.norid.no/regelverk/vedlegg-b.en.html
+// counties
+aa.no
+ah.no
+bu.no
+fm.no
+hl.no
+hm.no
+jan-mayen.no
+mr.no
+nl.no
+nt.no
+of.no
+ol.no
+oslo.no
+rl.no
+sf.no
+st.no
+svalbard.no
+tm.no
+tr.no
+va.no
+vf.no
+// primary and lower secondary schools per county
+gs.aa.no
+gs.ah.no
+gs.bu.no
+gs.fm.no
+gs.hl.no
+gs.hm.no
+gs.jan-mayen.no
+gs.mr.no
+gs.nl.no
+gs.nt.no
+gs.of.no
+gs.ol.no
+gs.oslo.no
+gs.rl.no
+gs.sf.no
+gs.st.no
+gs.svalbard.no
+gs.tm.no
+gs.tr.no
+gs.va.no
+gs.vf.no
+// cities
+akrehamn.no
+Ã¥krehamn.no
+algard.no
+ålgård.no
+arna.no
+brumunddal.no
+bryne.no
+bronnoysund.no
+brønnøysund.no
+drobak.no
+drøbak.no
+egersund.no
+fetsund.no
+floro.no
+florø.no
+fredrikstad.no
+hokksund.no
+honefoss.no
+hønefoss.no
+jessheim.no
+jorpeland.no
+jørpeland.no
+kirkenes.no
+kopervik.no
+krokstadelva.no
+langevag.no
+langevåg.no
+leirvik.no
+mjondalen.no
+mjøndalen.no
+mo-i-rana.no
+mosjoen.no
+mosjøen.no
+nesoddtangen.no
+orkanger.no
+osoyro.no
+osøyro.no
+raholt.no
+råholt.no
+sandnessjoen.no
+sandnessjøen.no
+skedsmokorset.no
+slattum.no
+spjelkavik.no
+stathelle.no
+stavern.no
+stjordalshalsen.no
+stjørdalshalsen.no
+tananger.no
+tranby.no
+vossevangen.no
+// communities
+afjord.no
+Ã¥fjord.no
+agdenes.no
+al.no
+Ã¥l.no
+alesund.no
+Ã¥lesund.no
+alstahaug.no
+alta.no
+áltá.no
+alaheadju.no
+álaheadju.no
+alvdal.no
+amli.no
+Ã¥mli.no
+amot.no
+Ã¥mot.no
+andebu.no
+andoy.no
+andøy.no
+andasuolo.no
+ardal.no
+Ã¥rdal.no
+aremark.no
+arendal.no
+Ã¥s.no
+aseral.no
+Ã¥seral.no
+asker.no
+askim.no
+askvoll.no
+askoy.no
+askøy.no
+asnes.no
+Ã¥snes.no
+audnedaln.no
+aukra.no
+aure.no
+aurland.no
+aurskog-holand.no
+aurskog-høland.no
+austevoll.no
+austrheim.no
+averoy.no
+averøy.no
+balestrand.no
+ballangen.no
+balat.no
+bálát.no
+balsfjord.no
+bahccavuotna.no
+báhccavuotna.no
+bamble.no
+bardu.no
+beardu.no
+beiarn.no
+bajddar.no
+bájddar.no
+baidar.no
+báidár.no
+berg.no
+bergen.no
+berlevag.no
+berlevåg.no
+bearalvahki.no
+bearalváhki.no
+bindal.no
+birkenes.no
+bjarkoy.no
+bjarkøy.no
+bjerkreim.no
+bjugn.no
+bodo.no
+bodø.no
+badaddja.no
+bådåddjå.no
+budejju.no
+bokn.no
+bremanger.no
+bronnoy.no
+brønnøy.no
+bygland.no
+bykle.no
+barum.no
+bærum.no
+bo.telemark.no
+bø.telemark.no
+bo.nordland.no
+bø.nordland.no
+bievat.no
+bievát.no
+bomlo.no
+bømlo.no
+batsfjord.no
+båtsfjord.no
+bahcavuotna.no
+báhcavuotna.no
+dovre.no
+drammen.no
+drangedal.no
+dyroy.no
+dyrøy.no
+donna.no
+dønna.no
+eid.no
+eidfjord.no
+eidsberg.no
+eidskog.no
+eidsvoll.no
+eigersund.no
+elverum.no
+enebakk.no
+engerdal.no
+etne.no
+etnedal.no
+evenes.no
+evenassi.no
+evenášši.no
+evje-og-hornnes.no
+farsund.no
+fauske.no
+fuossko.no
+fuoisku.no
+fedje.no
+fet.no
+finnoy.no
+finnøy.no
+fitjar.no
+fjaler.no
+fjell.no
+flakstad.no
+flatanger.no
+flekkefjord.no
+flesberg.no
+flora.no
+fla.no
+flå.no
+folldal.no
+forsand.no
+fosnes.no
+frei.no
+frogn.no
+froland.no
+frosta.no
+frana.no
+fræna.no
+froya.no
+frøya.no
+fusa.no
+fyresdal.no
+forde.no
+førde.no
+gamvik.no
+gangaviika.no
+gáŋgaviika.no
+gaular.no
+gausdal.no
+gildeskal.no
+gildeskål.no
+giske.no
+gjemnes.no
+gjerdrum.no
+gjerstad.no
+gjesdal.no
+gjovik.no
+gjøvik.no
+gloppen.no
+gol.no
+gran.no
+grane.no
+granvin.no
+gratangen.no
+grimstad.no
+grong.no
+kraanghke.no
+kråanghke.no
+grue.no
+gulen.no
+hadsel.no
+halden.no
+halsa.no
+hamar.no
+hamaroy.no
+habmer.no
+hábmer.no
+hapmir.no
+hápmir.no
+hammerfest.no
+hammarfeasta.no
+hámmárfeasta.no
+haram.no
+hareid.no
+harstad.no
+hasvik.no
+aknoluokta.no
+ákŋoluokta.no
+hattfjelldal.no
+aarborte.no
+haugesund.no
+hemne.no
+hemnes.no
+hemsedal.no
+heroy.more-og-romsdal.no
+herøy.møre-og-romsdal.no
+heroy.nordland.no
+herøy.nordland.no
+hitra.no
+hjartdal.no
+hjelmeland.no
+hobol.no
+hobøl.no
+hof.no
+hol.no
+hole.no
+holmestrand.no
+holtalen.no
+holtålen.no
+hornindal.no
+horten.no
+hurdal.no
+hurum.no
+hvaler.no
+hyllestad.no
+hagebostad.no
+hægebostad.no
+hoyanger.no
+høyanger.no
+hoylandet.no
+høylandet.no
+ha.no
+hå.no
+ibestad.no
+inderoy.no
+inderøy.no
+iveland.no
+jevnaker.no
+jondal.no
+jolster.no
+jølster.no
+karasjok.no
+karasjohka.no
+kárášjohka.no
+karlsoy.no
+galsa.no
+gálsá.no
+karmoy.no
+karmøy.no
+kautokeino.no
+guovdageaidnu.no
+klepp.no
+klabu.no
+klæbu.no
+kongsberg.no
+kongsvinger.no
+kragero.no
+kragerø.no
+kristiansand.no
+kristiansund.no
+krodsherad.no
+krødsherad.no
+kvalsund.no
+rahkkeravju.no
+ráhkkerávju.no
+kvam.no
+kvinesdal.no
+kvinnherad.no
+kviteseid.no
+kvitsoy.no
+kvitsøy.no
+kvafjord.no
+kvæfjord.no
+giehtavuoatna.no
+kvanangen.no
+kvænangen.no
+navuotna.no
+návuotna.no
+kafjord.no
+kåfjord.no
+gaivuotna.no
+gáivuotna.no
+larvik.no
+lavangen.no
+lavagis.no
+loabat.no
+loabát.no
+lebesby.no
+davvesiida.no
+leikanger.no
+leirfjord.no
+leka.no
+leksvik.no
+lenvik.no
+leangaviika.no
+leaŋgaviika.no
+lesja.no
+levanger.no
+lier.no
+lierne.no
+lillehammer.no
+lillesand.no
+lindesnes.no
+lindas.no
+lindås.no
+lom.no
+loppa.no
+lahppi.no
+láhppi.no
+lund.no
+lunner.no
+luroy.no
+lurøy.no
+luster.no
+lyngdal.no
+lyngen.no
+ivgu.no
+lardal.no
+lerdal.no
+lærdal.no
+lodingen.no
+lødingen.no
+lorenskog.no
+lørenskog.no
+loten.no
+løten.no
+malvik.no
+masoy.no
+måsøy.no
+muosat.no
+muosát.no
+mandal.no
+marker.no
+marnardal.no
+masfjorden.no
+meland.no
+meldal.no
+melhus.no
+meloy.no
+meløy.no
+meraker.no
+meråker.no
+moareke.no
+moåreke.no
+midsund.no
+midtre-gauldal.no
+modalen.no
+modum.no
+molde.no
+moskenes.no
+moss.no
+mosvik.no
+malselv.no
+målselv.no
+malatvuopmi.no
+málatvuopmi.no
+namdalseid.no
+aejrie.no
+namsos.no
+namsskogan.no
+naamesjevuemie.no
+nååmesjevuemie.no
+laakesvuemie.no
+nannestad.no
+narvik.no
+narviika.no
+naustdal.no
+nedre-eiker.no
+nes.akershus.no
+nes.buskerud.no
+nesna.no
+nesodden.no
+nesseby.no
+unjarga.no
+unjárga.no
+nesset.no
+nissedal.no
+nittedal.no
+nord-aurdal.no
+nord-fron.no
+nord-odal.no
+norddal.no
+nordkapp.no
+davvenjarga.no
+davvenjárga.no
+nordre-land.no
+nordreisa.no
+raisa.no
+ráisa.no
+nore-og-uvdal.no
+notodden.no
+naroy.no
+nærøy.no
+notteroy.no
+nøtterøy.no
+odda.no
+oksnes.no
+øksnes.no
+oppdal.no
+oppegard.no
+oppegård.no
+orkdal.no
+orland.no
+ørland.no
+orskog.no
+ørskog.no
+orsta.no
+ørsta.no
+os.hedmark.no
+os.hordaland.no
+osen.no
+osteroy.no
+osterøy.no
+ostre-toten.no
+østre-toten.no
+overhalla.no
+ovre-eiker.no
+øvre-eiker.no
+oyer.no
+øyer.no
+oygarden.no
+øygarden.no
+oystre-slidre.no
+øystre-slidre.no
+porsanger.no
+porsangu.no
+porsáŋgu.no
+porsgrunn.no
+radoy.no
+radøy.no
+rakkestad.no
+rana.no
+ruovat.no
+randaberg.no
+rauma.no
+rendalen.no
+rennebu.no
+rennesoy.no
+rennesøy.no
+rindal.no
+ringebu.no
+ringerike.no
+ringsaker.no
+rissa.no
+risor.no
+risør.no
+roan.no
+rollag.no
+rygge.no
+ralingen.no
+rælingen.no
+rodoy.no
+rødøy.no
+romskog.no
+rømskog.no
+roros.no
+røros.no
+rost.no
+røst.no
+royken.no
+røyken.no
+royrvik.no
+røyrvik.no
+rade.no
+råde.no
+salangen.no
+siellak.no
+saltdal.no
+salat.no
+sálát.no
+sálat.no
+samnanger.no
+sande.more-og-romsdal.no
+sande.møre-og-romsdal.no
+sande.vestfold.no
+sandefjord.no
+sandnes.no
+sandoy.no
+sandøy.no
+sarpsborg.no
+sauda.no
+sauherad.no
+sel.no
+selbu.no
+selje.no
+seljord.no
+sigdal.no
+siljan.no
+sirdal.no
+skaun.no
+skedsmo.no
+ski.no
+skien.no
+skiptvet.no
+skjervoy.no
+skjervøy.no
+skierva.no
+skiervá.no
+skjak.no
+skjåk.no
+skodje.no
+skanland.no
+skånland.no
+skanit.no
+skánit.no
+smola.no
+smøla.no
+snillfjord.no
+snasa.no
+snåsa.no
+snoasa.no
+snaase.no
+snåase.no
+sogndal.no
+sokndal.no
+sola.no
+solund.no
+songdalen.no
+sortland.no
+spydeberg.no
+stange.no
+stavanger.no
+steigen.no
+steinkjer.no
+stjordal.no
+stjørdal.no
+stokke.no
+stor-elvdal.no
+stord.no
+stordal.no
+storfjord.no
+omasvuotna.no
+strand.no
+stranda.no
+stryn.no
+sula.no
+suldal.no
+sund.no
+sunndal.no
+surnadal.no
+sveio.no
+svelvik.no
+sykkylven.no
+sogne.no
+søgne.no
+somna.no
+sømna.no
+sondre-land.no
+søndre-land.no
+sor-aurdal.no
+sør-aurdal.no
+sor-fron.no
+sør-fron.no
+sor-odal.no
+sør-odal.no
+sor-varanger.no
+sør-varanger.no
+matta-varjjat.no
+mátta-várjjat.no
+sorfold.no
+sørfold.no
+sorreisa.no
+sørreisa.no
+sorum.no
+sørum.no
+tana.no
+deatnu.no
+time.no
+tingvoll.no
+tinn.no
+tjeldsund.no
+dielddanuorri.no
+tjome.no
+tjøme.no
+tokke.no
+tolga.no
+torsken.no
+tranoy.no
+tranøy.no
+tromso.no
+tromsø.no
+tromsa.no
+romsa.no
+trondheim.no
+troandin.no
+trysil.no
+trana.no
+træna.no
+trogstad.no
+trøgstad.no
+tvedestrand.no
+tydal.no
+tynset.no
+tysfjord.no
+divtasvuodna.no
+divttasvuotna.no
+tysnes.no
+tysvar.no
+tysvær.no
+tonsberg.no
+tønsberg.no
+ullensaker.no
+ullensvang.no
+ulvik.no
+utsira.no
+vadso.no
+vadsø.no
+cahcesuolo.no
+Äáhcesuolo.no
+vaksdal.no
+valle.no
+vang.no
+vanylven.no
+vardo.no
+vardø.no
+varggat.no
+várggát.no
+vefsn.no
+vaapste.no
+vega.no
+vegarshei.no
+vegårshei.no
+vennesla.no
+verdal.no
+verran.no
+vestby.no
+vestnes.no
+vestre-slidre.no
+vestre-toten.no
+vestvagoy.no
+vestvågøy.no
+vevelstad.no
+vik.no
+vikna.no
+vindafjord.no
+volda.no
+voss.no
+varoy.no
+værøy.no
+vagan.no
+vågan.no
+voagat.no
+vagsoy.no
+vågsøy.no
+vaga.no
+vågå.no
+valer.ostfold.no
+våler.østfold.no
+valer.hedmark.no
+våler.hedmark.no
+
+// np : http://www.mos.com.np/register.html
+*.np
+
+// nr : http://cenpac.net.nr/dns/index.html
+// Confirmed by registry <technician@cenpac.net.nr> 2008-06-17
+nr
+biz.nr
+info.nr
+gov.nr
+edu.nr
+org.nr
+net.nr
+com.nr
+
+// nu : http://en.wikipedia.org/wiki/.nu
+nu
+
+// nz : http://en.wikipedia.org/wiki/.nz
+*.nz
+
+// om : http://en.wikipedia.org/wiki/.om
+*.om
+!mediaphone.om
+!nawrastelecom.om
+!nawras.om
+!omanmobile.om
+!omanpost.om
+!omantel.om
+!rakpetroleum.om
+!siemens.om
+!songfest.om
+!statecouncil.om
+
+// org : http://en.wikipedia.org/wiki/.org
+org
+
+// pa : http://www.nic.pa/
+// Some additional second level "domains" resolve directly as hostnames, such as
+// pannet.pa, so we add a rule for "pa".
+pa
+ac.pa
+gob.pa
+com.pa
+org.pa
+sld.pa
+edu.pa
+net.pa
+ing.pa
+abo.pa
+med.pa
+nom.pa
+
+// pe : https://www.nic.pe/InformeFinalComision.pdf
+pe
+edu.pe
+gob.pe
+nom.pe
+mil.pe
+org.pe
+com.pe
+net.pe
+
+// pf : http://www.gobin.info/domainname/formulaire-pf.pdf
+pf
+com.pf
+org.pf
+edu.pf
+
+// pg : http://en.wikipedia.org/wiki/.pg
+*.pg
+
+// ph : http://www.domains.ph/FAQ2.asp
+// Submitted by registry <jed@email.com.ph> 2008-06-13
+ph
+com.ph
+net.ph
+org.ph
+gov.ph
+edu.ph
+ngo.ph
+mil.ph
+i.ph
+
+// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK
+pk
+com.pk
+net.pk
+edu.pk
+org.pk
+fam.pk
+biz.pk
+web.pk
+gov.pk
+gob.pk
+gok.pk
+gon.pk
+gop.pk
+gos.pk
+info.pk
+
+// pl : http://www.dns.pl/english/
+pl
+// NASK functional domains (nask.pl / dns.pl) : http://www.dns.pl/english/dns-funk.html
+aid.pl
+agro.pl
+atm.pl
+auto.pl
+biz.pl
+com.pl
+edu.pl
+gmina.pl
+gsm.pl
+info.pl
+mail.pl
+miasta.pl
+media.pl
+mil.pl
+net.pl
+nieruchomosci.pl
+nom.pl
+org.pl
+pc.pl
+powiat.pl
+priv.pl
+realestate.pl
+rel.pl
+sex.pl
+shop.pl
+sklep.pl
+sos.pl
+szkola.pl
+targi.pl
+tm.pl
+tourism.pl
+travel.pl
+turystyka.pl
+// ICM functional domains (icm.edu.pl)
+6bone.pl
+art.pl
+mbone.pl
+// Government domains (administred by ippt.gov.pl)
+gov.pl
+uw.gov.pl
+um.gov.pl
+ug.gov.pl
+upow.gov.pl
+starostwo.gov.pl
+so.gov.pl
+sr.gov.pl
+po.gov.pl
+pa.gov.pl
+// other functional domains
+ngo.pl
+irc.pl
+usenet.pl
+// NASK geographical domains : http://www.dns.pl/english/dns-regiony.html
+augustow.pl
+babia-gora.pl
+bedzin.pl
+beskidy.pl
+bialowieza.pl
+bialystok.pl
+bielawa.pl
+bieszczady.pl
+boleslawiec.pl
+bydgoszcz.pl
+bytom.pl
+cieszyn.pl
+czeladz.pl
+czest.pl
+dlugoleka.pl
+elblag.pl
+elk.pl
+glogow.pl
+gniezno.pl
+gorlice.pl
+grajewo.pl
+ilawa.pl
+jaworzno.pl
+jelenia-gora.pl
+jgora.pl
+kalisz.pl
+kazimierz-dolny.pl
+karpacz.pl
+kartuzy.pl
+kaszuby.pl
+katowice.pl
+kepno.pl
+ketrzyn.pl
+klodzko.pl
+kobierzyce.pl
+kolobrzeg.pl
+konin.pl
+konskowola.pl
+kutno.pl
+lapy.pl
+lebork.pl
+legnica.pl
+lezajsk.pl
+limanowa.pl
+lomza.pl
+lowicz.pl
+lubin.pl
+lukow.pl
+malbork.pl
+malopolska.pl
+mazowsze.pl
+mazury.pl
+mielec.pl
+mielno.pl
+mragowo.pl
+naklo.pl
+nowaruda.pl
+nysa.pl
+olawa.pl
+olecko.pl
+olkusz.pl
+olsztyn.pl
+opoczno.pl
+opole.pl
+ostroda.pl
+ostroleka.pl
+ostrowiec.pl
+ostrowwlkp.pl
+pila.pl
+pisz.pl
+podhale.pl
+podlasie.pl
+polkowice.pl
+pomorze.pl
+pomorskie.pl
+prochowice.pl
+pruszkow.pl
+przeworsk.pl
+pulawy.pl
+radom.pl
+rawa-maz.pl
+rybnik.pl
+rzeszow.pl
+sanok.pl
+sejny.pl
+siedlce.pl
+slask.pl
+slupsk.pl
+sosnowiec.pl
+stalowa-wola.pl
+skoczow.pl
+starachowice.pl
+stargard.pl
+suwalki.pl
+swidnica.pl
+swiebodzin.pl
+swinoujscie.pl
+szczecin.pl
+szczytno.pl
+tarnobrzeg.pl
+tgory.pl
+turek.pl
+tychy.pl
+ustka.pl
+walbrzych.pl
+warmia.pl
+warszawa.pl
+waw.pl
+wegrow.pl
+wielun.pl
+wlocl.pl
+wloclawek.pl
+wodzislaw.pl
+wolomin.pl
+wroclaw.pl
+zachpomor.pl
+zagan.pl
+zarow.pl
+zgora.pl
+zgorzelec.pl
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+// other geographical domains
+gliwice.pl
+krakow.pl
+poznan.pl
+wroc.pl
+zakopane.pl
+
+// pm : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+pm
+
+// pn : http://www.government.pn/PnRegistry/policies.htm
+pn
+gov.pn
+co.pn
+org.pn
+edu.pn
+net.pn
+
+// post : http://en.wikipedia.org/wiki/.post
+post
+
+// pr : http://www.nic.pr/index.asp?f=1
+pr
+com.pr
+net.pr
+org.pr
+gov.pr
+edu.pr
+isla.pr
+pro.pr
+biz.pr
+info.pr
+name.pr
+// these aren't mentioned on nic.pr, but on http://en.wikipedia.org/wiki/.pr
+est.pr
+prof.pr
+ac.pr
+
+// pro : http://www.nic.pro/support_faq.htm
+pro
+aca.pro
+bar.pro
+cpa.pro
+jur.pro
+law.pro
+med.pro
+eng.pro
+
+// ps : http://en.wikipedia.org/wiki/.ps
+// http://www.nic.ps/registration/policy.html#reg
+ps
+edu.ps
+gov.ps
+sec.ps
+plo.ps
+com.ps
+org.ps
+net.ps
+
+// pt : http://online.dns.pt/dns/start_dns
+pt
+net.pt
+gov.pt
+org.pt
+edu.pt
+int.pt
+publ.pt
+com.pt
+nome.pt
+
+// pw : http://en.wikipedia.org/wiki/.pw
+pw
+co.pw
+ne.pw
+or.pw
+ed.pw
+go.pw
+belau.pw
+
+// py : http://www.nic.py/pautas.html#seccion_9
+// Confirmed by registry 2012-10-03
+py
+com.py
+coop.py
+edu.py
+gov.py
+mil.py
+net.py
+org.py
+
+// qa : http://domains.qa/en/
+qa
+com.qa
+edu.qa
+gov.qa
+mil.qa
+name.qa
+net.qa
+org.qa
+sch.qa
+
+// re : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs
+re
+com.re
+asso.re
+nom.re
+
+// ro : http://www.rotld.ro/
+ro
+com.ro
+org.ro
+tm.ro
+nt.ro
+nom.ro
+info.ro
+rec.ro
+arts.ro
+firm.ro
+store.ro
+www.ro
+
+// rs : http://en.wikipedia.org/wiki/.rs
+rs
+co.rs
+org.rs
+edu.rs
+ac.rs
+gov.rs
+in.rs
+
+// ru : http://www.cctld.ru/ru/docs/aktiv_8.php
+// Industry domains
+ru
+ac.ru
+com.ru
+edu.ru
+int.ru
+net.ru
+org.ru
+pp.ru
+// Geographical domains
+adygeya.ru
+altai.ru
+amur.ru
+arkhangelsk.ru
+astrakhan.ru
+bashkiria.ru
+belgorod.ru
+bir.ru
+bryansk.ru
+buryatia.ru
+cbg.ru
+chel.ru
+chelyabinsk.ru
+chita.ru
+chukotka.ru
+chuvashia.ru
+dagestan.ru
+dudinka.ru
+e-burg.ru
+grozny.ru
+irkutsk.ru
+ivanovo.ru
+izhevsk.ru
+jar.ru
+joshkar-ola.ru
+kalmykia.ru
+kaluga.ru
+kamchatka.ru
+karelia.ru
+kazan.ru
+kchr.ru
+kemerovo.ru
+khabarovsk.ru
+khakassia.ru
+khv.ru
+kirov.ru
+koenig.ru
+komi.ru
+kostroma.ru
+krasnoyarsk.ru
+kuban.ru
+kurgan.ru
+kursk.ru
+lipetsk.ru
+magadan.ru
+mari.ru
+mari-el.ru
+marine.ru
+mordovia.ru
+mosreg.ru
+msk.ru
+murmansk.ru
+nalchik.ru
+nnov.ru
+nov.ru
+novosibirsk.ru
+nsk.ru
+omsk.ru
+orenburg.ru
+oryol.ru
+palana.ru
+penza.ru
+perm.ru
+pskov.ru
+ptz.ru
+rnd.ru
+ryazan.ru
+sakhalin.ru
+samara.ru
+saratov.ru
+simbirsk.ru
+smolensk.ru
+spb.ru
+stavropol.ru
+stv.ru
+surgut.ru
+tambov.ru
+tatarstan.ru
+tom.ru
+tomsk.ru
+tsaritsyn.ru
+tsk.ru
+tula.ru
+tuva.ru
+tver.ru
+tyumen.ru
+udm.ru
+udmurtia.ru
+ulan-ude.ru
+vladikavkaz.ru
+vladimir.ru
+vladivostok.ru
+volgograd.ru
+vologda.ru
+voronezh.ru
+vrn.ru
+vyatka.ru
+yakutia.ru
+yamal.ru
+yaroslavl.ru
+yekaterinburg.ru
+yuzhno-sakhalinsk.ru
+// More geographical domains
+amursk.ru
+baikal.ru
+cmw.ru
+fareast.ru
+jamal.ru
+kms.ru
+k-uralsk.ru
+kustanai.ru
+kuzbass.ru
+magnitka.ru
+mytis.ru
+nakhodka.ru
+nkz.ru
+norilsk.ru
+oskol.ru
+pyatigorsk.ru
+rubtsovsk.ru
+snz.ru
+syzran.ru
+vdonsk.ru
+zgrad.ru
+// State domains
+gov.ru
+mil.ru
+// Technical domains
+test.ru
+
+// rw : http://www.nic.rw/cgi-bin/policy.pl
+rw
+gov.rw
+net.rw
+edu.rw
+ac.rw
+com.rw
+co.rw
+int.rw
+mil.rw
+gouv.rw
+
+// sa : http://www.nic.net.sa/
+sa
+com.sa
+net.sa
+org.sa
+gov.sa
+med.sa
+pub.sa
+edu.sa
+sch.sa
+
+// sb : http://www.sbnic.net.sb/
+// Submitted by registry <lee.humphries@telekom.com.sb> 2008-06-08
+sb
+com.sb
+edu.sb
+gov.sb
+net.sb
+org.sb
+
+// sc : http://www.nic.sc/
+sc
+com.sc
+gov.sc
+net.sc
+org.sc
+edu.sc
+
+// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm
+// Submitted by registry <admin@isoc.sd> 2008-06-17
+sd
+com.sd
+net.sd
+org.sd
+edu.sd
+med.sd
+tv.sd
+gov.sd
+info.sd
+
+// se : http://en.wikipedia.org/wiki/.se
+// Submitted by registry <Patrik.Wallstrom@iis.se> 2008-06-24
+se
+a.se
+ac.se
+b.se
+bd.se
+brand.se
+c.se
+d.se
+e.se
+f.se
+fh.se
+fhsk.se
+fhv.se
+g.se
+h.se
+i.se
+k.se
+komforb.se
+kommunalforbund.se
+komvux.se
+l.se
+lanbib.se
+m.se
+n.se
+naturbruksgymn.se
+o.se
+org.se
+p.se
+parti.se
+pp.se
+press.se
+r.se
+s.se
+sshn.se
+t.se
+tm.se
+u.se
+w.se
+x.se
+y.se
+z.se
+
+// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines
+sg
+com.sg
+net.sg
+org.sg
+gov.sg
+edu.sg
+per.sg
+
+// sh : http://www.nic.sh/registrar.html
+sh
+com.sh
+net.sh
+gov.sh
+org.sh
+mil.sh
+
+// si : http://en.wikipedia.org/wiki/.si
+si
+
+// sj : No registrations at this time.
+// Submitted by registry <jarle@uninett.no> 2008-06-16
+
+// sk : http://en.wikipedia.org/wiki/.sk
+// list of 2nd level domains ?
+sk
+
+// sl : http://www.nic.sl
+// Submitted by registry <adam@neoip.com> 2008-06-12
+sl
+com.sl
+net.sl
+edu.sl
+gov.sl
+org.sl
+
+// sm : http://en.wikipedia.org/wiki/.sm
+sm
+
+// sn : http://en.wikipedia.org/wiki/.sn
+sn
+art.sn
+com.sn
+edu.sn
+gouv.sn
+org.sn
+perso.sn
+univ.sn
+
+// so : http://www.soregistry.com/
+so
+com.so
+net.so
+org.so
+
+// sr : http://en.wikipedia.org/wiki/.sr
+sr
+
+// st : http://www.nic.st/html/policyrules/
+st
+co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+gov.st
+mil.st
+net.st
+org.st
+principe.st
+saotome.st
+store.st
+
+// su : http://en.wikipedia.org/wiki/.su
+su
+
+// sv : http://www.svnet.org.sv/svpolicy.html
+*.sv
+
+// sx : http://en.wikipedia.org/wiki/.sx
+// Confirmed by registry <jcvignes@openregistry.com> 2012-05-31
+sx
+gov.sx
+
+// sy : http://en.wikipedia.org/wiki/.sy
+// see also: http://www.gobin.info/domainname/sy.doc
+sy
+edu.sy
+gov.sy
+net.sy
+mil.sy
+com.sy
+org.sy
+
+// sz : http://en.wikipedia.org/wiki/.sz
+// http://www.sispa.org.sz/
+sz
+co.sz
+ac.sz
+org.sz
+
+// tc : http://en.wikipedia.org/wiki/.tc
+tc
+
+// td : http://en.wikipedia.org/wiki/.td
+td
+
+// tel: http://en.wikipedia.org/wiki/.tel
+// http://www.telnic.org/
+tel
+
+// tf : http://en.wikipedia.org/wiki/.tf
+tf
+
+// tg : http://en.wikipedia.org/wiki/.tg
+// http://www.nic.tg/
+tg
+
+// th : http://en.wikipedia.org/wiki/.th
+// Submitted by registry <krit@thains.co.th> 2008-06-17
+th
+ac.th
+co.th
+go.th
+in.th
+mi.th
+net.th
+or.th
+
+// tj : http://www.nic.tj/policy.html
+tj
+ac.tj
+biz.tj
+co.tj
+com.tj
+edu.tj
+go.tj
+gov.tj
+int.tj
+mil.tj
+name.tj
+net.tj
+nic.tj
+org.tj
+test.tj
+web.tj
+
+// tk : http://en.wikipedia.org/wiki/.tk
+tk
+
+// tl : http://en.wikipedia.org/wiki/.tl
+tl
+gov.tl
+
+// tm : http://www.nic.tm/local.html
+tm
+com.tm
+co.tm
+org.tm
+net.tm
+nom.tm
+gov.tm
+mil.tm
+edu.tm
+
+// tn : http://en.wikipedia.org/wiki/.tn
+// http://whois.ati.tn/
+tn
+com.tn
+ens.tn
+fin.tn
+gov.tn
+ind.tn
+intl.tn
+nat.tn
+net.tn
+org.tn
+info.tn
+perso.tn
+tourism.tn
+edunet.tn
+rnrt.tn
+rns.tn
+rnu.tn
+mincom.tn
+agrinet.tn
+defense.tn
+turen.tn
+
+// to : http://en.wikipedia.org/wiki/.to
+// Submitted by registry <egullich@colo.to> 2008-06-17
+to
+com.to
+gov.to
+net.to
+org.to
+edu.to
+mil.to
+
+// tr : http://en.wikipedia.org/wiki/.tr
+*.tr
+!nic.tr
+// Used by government in the TRNC
+// http://en.wikipedia.org/wiki/.nc.tr
+gov.nc.tr
+
+// travel : http://en.wikipedia.org/wiki/.travel
+travel
+
+// tt : http://www.nic.tt/
+tt
+co.tt
+com.tt
+org.tt
+net.tt
+biz.tt
+info.tt
+pro.tt
+int.tt
+coop.tt
+jobs.tt
+mobi.tt
+travel.tt
+museum.tt
+aero.tt
+name.tt
+gov.tt
+edu.tt
+
+// tv : http://en.wikipedia.org/wiki/.tv
+// Not listing any 2LDs as reserved since none seem to exist in practice,
+// Wikipedia notwithstanding.
+tv
+
+// tw : http://en.wikipedia.org/wiki/.tw
+tw
+edu.tw
+gov.tw
+mil.tw
+com.tw
+net.tw
+org.tw
+idv.tw
+game.tw
+ebiz.tw
+club.tw
+網路.tw
+組織.tw
+商業.tw
+
+// tz : http://www.tznic.or.tz/index.php/domains
+// Confirmed by registry <manager@tznic.or.tz> 2013-01-22
+ac.tz
+co.tz
+go.tz
+hotel.tz
+info.tz
+me.tz
+mil.tz
+mobi.tz
+ne.tz
+or.tz
+sc.tz
+tv.tz
+
+// ua : https://hostmaster.ua/policy/?ua
+// Submitted by registry <dk@cctld.ua> 2012-04-27
+ua
+// ua 2LD
+com.ua
+edu.ua
+gov.ua
+in.ua
+net.ua
+org.ua
+// ua geographic names
+// https://hostmaster.ua/2ld/
+cherkassy.ua
+cherkasy.ua
+chernigov.ua
+chernihiv.ua
+chernivtsi.ua
+chernovtsy.ua
+ck.ua
+cn.ua
+cr.ua
+crimea.ua
+cv.ua
+dn.ua
+dnepropetrovsk.ua
+dnipropetrovsk.ua
+dominic.ua
+donetsk.ua
+dp.ua
+if.ua
+ivano-frankivsk.ua
+kh.ua
+kharkiv.ua
+kharkov.ua
+kherson.ua
+khmelnitskiy.ua
+khmelnytskyi.ua
+kiev.ua
+kirovograd.ua
+km.ua
+kr.ua
+krym.ua
+ks.ua
+kv.ua
+kyiv.ua
+lg.ua
+lt.ua
+lugansk.ua
+lutsk.ua
+lv.ua
+lviv.ua
+mk.ua
+mykolaiv.ua
+nikolaev.ua
+od.ua
+odesa.ua
+odessa.ua
+pl.ua
+poltava.ua
+rivne.ua
+rovno.ua
+rv.ua
+sb.ua
+sebastopol.ua
+sevastopol.ua
+sm.ua
+sumy.ua
+te.ua
+ternopil.ua
+uz.ua
+uzhgorod.ua
+vinnica.ua
+vinnytsia.ua
+vn.ua
+volyn.ua
+yalta.ua
+zaporizhzhe.ua
+zaporizhzhia.ua
+zhitomir.ua
+zhytomyr.ua
+zp.ua
+zt.ua
+
+// Private registries in .ua
+co.ua
+pp.ua
+
+// ug : https://www.registry.co.ug/
+ug
+co.ug
+or.ug
+ac.ug
+sc.ug
+go.ug
+ne.ug
+com.ug
+org.ug
+
+// uk : http://en.wikipedia.org/wiki/.uk
+// Submitted by registry <noc@nominet.org.uk> 2012-10-02
+// and tweaked by us pending further consultation.
+*.uk
+*.sch.uk
+!bl.uk
+!british-library.uk
+!jet.uk
+!mod.uk
+!national-library-scotland.uk
+!nel.uk
+!nic.uk
+!nls.uk
+!parliament.uk
+
+// us : http://en.wikipedia.org/wiki/.us
+us
+dni.us
+fed.us
+isa.us
+kids.us
+nsn.us
+// us geographic names
+ak.us
+al.us
+ar.us
+as.us
+az.us
+ca.us
+co.us
+ct.us
+dc.us
+de.us
+fl.us
+ga.us
+gu.us
+hi.us
+ia.us
+id.us
+il.us
+in.us
+ks.us
+ky.us
+la.us
+ma.us
+md.us
+me.us
+mi.us
+mn.us
+mo.us
+ms.us
+mt.us
+nc.us
+nd.us
+ne.us
+nh.us
+nj.us
+nm.us
+nv.us
+ny.us
+oh.us
+ok.us
+or.us
+pa.us
+pr.us
+ri.us
+sc.us
+sd.us
+tn.us
+tx.us
+ut.us
+vi.us
+vt.us
+va.us
+wa.us
+wi.us
+wv.us
+wy.us
+// The registrar notes several more specific domains available in each state,
+// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat
+// haphazard; in some states these domains resolve as addresses, while in others
+// only subdomains are available, or even nothing at all. We include the
+// most common ones where it's clear that different sites are different
+// entities.
+k12.ak.us
+k12.al.us
+k12.ar.us
+k12.as.us
+k12.az.us
+k12.ca.us
+k12.co.us
+k12.ct.us
+k12.dc.us
+k12.de.us
+k12.fl.us
+k12.ga.us
+k12.gu.us
+// k12.hi.us Hawaii has a state-wide DOE login: bug 614565
+k12.ia.us
+k12.id.us
+k12.il.us
+k12.in.us
+k12.ks.us
+k12.ky.us
+k12.la.us
+k12.ma.us
+k12.md.us
+k12.me.us
+k12.mi.us
+k12.mn.us
+k12.mo.us
+k12.ms.us
+k12.mt.us
+k12.nc.us
+k12.nd.us
+k12.ne.us
+k12.nh.us
+k12.nj.us
+k12.nm.us
+k12.nv.us
+k12.ny.us
+k12.oh.us
+k12.ok.us
+k12.or.us
+k12.pa.us
+k12.pr.us
+k12.ri.us
+k12.sc.us
+k12.sd.us
+k12.tn.us
+k12.tx.us
+k12.ut.us
+k12.vi.us
+k12.vt.us
+k12.va.us
+k12.wa.us
+k12.wi.us
+k12.wv.us
+k12.wy.us
+
+cc.ak.us
+cc.al.us
+cc.ar.us
+cc.as.us
+cc.az.us
+cc.ca.us
+cc.co.us
+cc.ct.us
+cc.dc.us
+cc.de.us
+cc.fl.us
+cc.ga.us
+cc.gu.us
+cc.hi.us
+cc.ia.us
+cc.id.us
+cc.il.us
+cc.in.us
+cc.ks.us
+cc.ky.us
+cc.la.us
+cc.ma.us
+cc.md.us
+cc.me.us
+cc.mi.us
+cc.mn.us
+cc.mo.us
+cc.ms.us
+cc.mt.us
+cc.nc.us
+cc.nd.us
+cc.ne.us
+cc.nh.us
+cc.nj.us
+cc.nm.us
+cc.nv.us
+cc.ny.us
+cc.oh.us
+cc.ok.us
+cc.or.us
+cc.pa.us
+cc.pr.us
+cc.ri.us
+cc.sc.us
+cc.sd.us
+cc.tn.us
+cc.tx.us
+cc.ut.us
+cc.vi.us
+cc.vt.us
+cc.va.us
+cc.wa.us
+cc.wi.us
+cc.wv.us
+cc.wy.us
+
+lib.ak.us
+lib.al.us
+lib.ar.us
+lib.as.us
+lib.az.us
+lib.ca.us
+lib.co.us
+lib.ct.us
+lib.dc.us
+lib.de.us
+lib.fl.us
+lib.ga.us
+lib.gu.us
+lib.hi.us
+lib.ia.us
+lib.id.us
+lib.il.us
+lib.in.us
+lib.ks.us
+lib.ky.us
+lib.la.us
+lib.ma.us
+lib.md.us
+lib.me.us
+lib.mi.us
+lib.mn.us
+lib.mo.us
+lib.ms.us
+lib.mt.us
+lib.nc.us
+lib.nd.us
+lib.ne.us
+lib.nh.us
+lib.nj.us
+lib.nm.us
+lib.nv.us
+lib.ny.us
+lib.oh.us
+lib.ok.us
+lib.or.us
+lib.pa.us
+lib.pr.us
+lib.ri.us
+lib.sc.us
+lib.sd.us
+lib.tn.us
+lib.tx.us
+lib.ut.us
+lib.vi.us
+lib.vt.us
+lib.va.us
+lib.wa.us
+lib.wi.us
+lib.wv.us
+lib.wy.us
+
+// k12.ma.us contains school districts in Massachusetts. The 4LDs are
+// managed indepedently except for private (PVT), charter (CHTR) and
+// parochial (PAROCH) schools. Those are delegated dorectly to the
+// 5LD operators. <k12-ma-hostmaster _ at _ rsuc.gweep.net>
+pvt.k12.ma.us
+chtr.k12.ma.us
+paroch.k12.ma.us
+
+// uy : http://www.nic.org.uy/
+uy
+com.uy
+edu.uy
+gub.uy
+mil.uy
+net.uy
+org.uy
+
+// uz : http://www.reg.uz/
+uz
+co.uz
+com.uz
+net.uz
+org.uz
+
+// va : http://en.wikipedia.org/wiki/.va
+va
+
+// vc : http://en.wikipedia.org/wiki/.vc
+// Submitted by registry <kshah@ca.afilias.info> 2008-06-13
+vc
+com.vc
+net.vc
+org.vc
+gov.vc
+mil.vc
+edu.vc
+
+// ve : https://registro.nic.ve/
+// Confirmed by registry 2012-10-04
+ve
+co.ve
+com.ve
+e12.ve
+edu.ve
+gov.ve
+info.ve
+mil.ve
+net.ve
+org.ve
+web.ve
+
+// vg : http://en.wikipedia.org/wiki/.vg
+vg
+
+// vi : http://www.nic.vi/newdomainform.htm
+// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other
+// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they
+// are available for registration (which they do not seem to be).
+vi
+co.vi
+com.vi
+k12.vi
+net.vi
+org.vi
+
+// vn : https://www.dot.vn/vnnic/vnnic/domainregistration.jsp
+vn
+com.vn
+net.vn
+org.vn
+edu.vn
+gov.vn
+int.vn
+ac.vn
+biz.vn
+info.vn
+name.vn
+pro.vn
+health.vn
+
+// vu : http://en.wikipedia.org/wiki/.vu
+// list of 2nd level tlds ?
+vu
+
+// wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+wf
+
+// ws : http://en.wikipedia.org/wiki/.ws
+// http://samoanic.ws/index.dhtml
+ws
+com.ws
+net.ws
+org.ws
+gov.ws
+edu.ws
+
+// yt : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.pdf
+yt
+
+// IDN ccTLDs
+// Please sort by ISO 3166 ccTLD, then punicode string
+// when submitting patches and follow this format:
+// <Punicode> ("<english word>" <language>) : <ISO 3166 ccTLD>
+// [optional sponsoring org]
+// <URL>
+
+// xn--mgbaam7a8h ("Emerat" Arabic) : AE
+// http://nic.ae/english/arabicdomain/rules.jsp
+امارات
+
+// xn--54b7fta0cc ("Bangla" Bangla) : BD
+বাংলা
+
+// xn--fiqs8s ("China" Chinese-Han-Simplified <.Zhonggou>) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中国
+
+// xn--fiqz9s ("China" Chinese-Han-Traditional <.Zhonggou>) : CN
+// CNNIC
+// http://cnnic.cn/html/Dir/2005/10/11/3218.htm
+中國
+
+// xn--lgbbat1ad8j ("Algeria / Al Jazair" Arabic) : DZ
+الجزائر
+
+// xn--wgbh1c ("Egypt" Arabic .masr) : EG
+// http://www.dotmasr.eg/
+مصر
+
+// xn--node ("ge" Georgian (Mkhedruli)) : GE
+გე
+
+// xn--j6w193g ("Hong Kong" Chinese-Han) : HK
+// https://www2.hkirc.hk/register/rules.jsp
+香港
+
+// xn--h2brj9c ("Bharat" Devanagari) : IN
+// India
+भारत
+
+// xn--mgbbh1a71e ("Bharat" Arabic) : IN
+// India
+بھارت
+
+// xn--fpcrj9c3d ("Bharat" Telugu) : IN
+// India
+భారతà±
+
+// xn--gecrj9c ("Bharat" Gujarati) : IN
+// India
+ભારત
+
+// xn--s9brj9c ("Bharat" Gurmukhi) : IN
+// India
+ਭਾਰਤ
+
+// xn--45brj9c ("Bharat" Bengali) : IN
+// India
+ভারত
+
+// xn--xkc2dl3a5ee0h ("India" Tamil) : IN
+// India
+இநà¯à®¤à®¿à®¯à®¾
+
+// xn--mgba3a4f16a ("Iran" Persian) : IR
+ایران
+
+// xn--mgba3a4fra ("Iran" Arabic) : IR
+ايران
+
+// xn--mgbayh7gpa ("al-Ordon" Arabic) : JO
+// National Information Technology Center (NITC)
+// Royal Scientific Society, Al-Jubeiha
+الاردن
+
+// xn--3e0b707e ("Republic of Korea" Hangul) : KR
+한국
+
+// xn--fzc2c9e2c ("Lanka" Sinhalese-Sinhala) : LK
+// http://nic.lk
+ලංකà·
+
+// xn--xkc2al3hye2a ("Ilangai" Tamil) : LK
+// http://nic.lk
+இலஙà¯à®•à¯ˆ
+
+// xn--mgbc0a9azcg ("Morocco / al-Maghrib" Arabic) : MA
+المغرب
+
+// xn--mgb9awbf ("Oman" Arabic) : OM
+عمان
+
+// xn--ygbi2ammx ("Falasteen" Arabic) : PS
+// The Palestinian National Internet Naming Authority (PNINA)
+// http://www.pnina.ps
+Ùلسطين
+
+// xn--90a3ac ("srb" Cyrillic) : RS
+Ñрб
+
+// xn--p1ai ("rf" Russian-Cyrillic) : RU
+// http://www.cctld.ru/en/docs/rulesrf.php
+рф
+
+// xn--wgbl6a ("Qatar" Arabic) : QA
+// http://www.ict.gov.qa/
+قطر
+
+// xn--mgberp4a5d4ar ("AlSaudiah" Arabic) : SA
+// http://www.nic.net.sa/
+السعودية
+
+// xn--mgberp4a5d4a87g ("AlSaudiah" Arabic) variant : SA
+السعودیة
+
+// xn--mgbqly7c0a67fbc ("AlSaudiah" Arabic) variant : SA
+السعودیۃ
+
+// xn--mgbqly7cvafr ("AlSaudiah" Arabic) variant : SA
+السعوديه
+
+// xn--ogbpf8fl ("Syria" Arabic) : SY
+سورية
+
+// xn--mgbtf8fl ("Syria" Arabic) variant : SY
+سوريا
+
+// xn--yfro4i67o Singapore ("Singapore" Chinese-Han) : SG
+新加å¡
+
+// xn--clchc0ea0b2g2a9gcd ("Singapore" Tamil) : SG
+சிஙà¯à®•à®ªà¯à®ªà¯‚à®°à¯
+
+// xn--o3cw4h ("Thai" Thai) : TH
+// http://www.thnic.co.th
+ไทย
+
+// xn--pgbs0dh ("Tunis") : TN
+// http://nic.tn
+تونس
+
+// xn--kpry57d ("Taiwan" Chinese-Han-Traditional) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+å°ç£
+
+// xn--kprw13d ("Taiwan" Chinese-Han-Simplified) : TW
+// http://www.twnic.net/english/dn/dn_07a.htm
+å°æ¹¾
+
+// xn--nnx388a ("Taiwan") variant : TW
+臺ç£
+
+// xn--j1amh ("ukr" Cyrillic) : UA
+укр
+
+// xn--mgb2ddes ("AlYemen" Arabic) : YE
+اليمن
+
+// xxx : http://icmregistry.com
+xxx
+
+// ye : http://www.y.net.ye/services/domain_name.htm
+*.ye
+
+// za : http://www.zadna.org.za/slds.html
+*.za
+
+// zm : http://en.wikipedia.org/wiki/.zm
+*.zm
+
+// zw : http://en.wikipedia.org/wiki/.zw
+*.zw
+
+// ===END ICANN DOMAINS===
+// ===BEGIN PRIVATE DOMAINS===
+
+// Amazon CloudFront : https://aws.amazon.com/cloudfront/
+// Requested by Donavan Miller <donavanm@amazon.com> 2013-03-22
+cloudfront.net
+
+// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/
+// Requested by Adam Stein <astein@amazon.com> 2013-04-02
+elasticbeanstalk.com
+
+// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/
+// Requested by Scott Vidmar <svidmar@amazon.com> 2013-03-27
+elb.amazonaws.com
+
+// Amazon S3 : https://aws.amazon.com/s3/
+// Requested by Courtney Eckhardt <coec@amazon.com> 2013-03-22
+s3.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-eu-west-1.amazonaws.com
+s3-ap-southeast-1.amazonaws.com
+s3-ap-southeast-2.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-fips-us-gov-west-1.amazonaws.com
+s3-website-us-east-1.amazonaws.com
+s3-website-us-west-2.amazonaws.com
+s3-website-us-west-1.amazonaws.com
+s3-website-eu-west-1.amazonaws.com
+s3-website-ap-southeast-1.amazonaws.com
+s3-website-ap-southeast-2.amazonaws.com
+s3-website-ap-northeast-1.amazonaws.com
+s3-website-sa-east-1.amazonaws.com
+s3-website-us-gov-west-1.amazonaws.com
+
+// BetaInABox
+// Requested by adrian@betainabox.com 2012-09-13
+betainabox.com
+
+// CentralNic : http://www.centralnic.com/names/domains
+// Requested by registry <gavin.brown@centralnic.com> 2012-09-27
+ae.org
+ar.com
+br.com
+cn.com
+com.de
+de.com
+eu.com
+gb.com
+gb.net
+gr.com
+hu.com
+hu.net
+jp.net
+jpn.com
+kr.com
+no.com
+qc.com
+ru.com
+sa.com
+se.com
+se.net
+uk.com
+uk.net
+us.com
+us.org
+uy.com
+za.com
+
+// c.la : http://www.c.la/
+c.la
+
+// co.ca : http://registry.co.ca/
+co.ca
+
+// CoDNS B.V.
+co.nl
+co.no
+
+// DreamHost : http://www.dreamhost.com/
+// Requested by Andrew Farmer <andrew.farmer@dreamhost.com> 2012-10-02
+dreamhosters.com
+
+// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/
+dyndns-at-home.com
+dyndns-at-work.com
+dyndns-blog.com
+dyndns-free.com
+dyndns-home.com
+dyndns-ip.com
+dyndns-mail.com
+dyndns-office.com
+dyndns-pics.com
+dyndns-remote.com
+dyndns-server.com
+dyndns-web.com
+dyndns-wiki.com
+dyndns-work.com
+dyndns.biz
+dyndns.info
+dyndns.org
+dyndns.tv
+at-band-camp.net
+ath.cx
+barrel-of-knowledge.info
+barrell-of-knowledge.info
+better-than.tv
+blogdns.com
+blogdns.net
+blogdns.org
+blogsite.org
+boldlygoingnowhere.org
+broke-it.net
+buyshouses.net
+cechire.com
+dnsalias.com
+dnsalias.net
+dnsalias.org
+dnsdojo.com
+dnsdojo.net
+dnsdojo.org
+does-it.net
+doesntexist.com
+doesntexist.org
+dontexist.com
+dontexist.net
+dontexist.org
+doomdns.com
+doomdns.org
+dvrdns.org
+dyn-o-saur.com
+dynalias.com
+dynalias.net
+dynalias.org
+dynathome.net
+dyndns.ws
+endofinternet.net
+endofinternet.org
+endoftheinternet.org
+est-a-la-maison.com
+est-a-la-masion.com
+est-le-patron.com
+est-mon-blogueur.com
+for-better.biz
+for-more.biz
+for-our.info
+for-some.biz
+for-the.biz
+forgot.her.name
+forgot.his.name
+from-ak.com
+from-al.com
+from-ar.com
+from-az.net
+from-ca.com
+from-co.net
+from-ct.com
+from-dc.com
+from-de.com
+from-fl.com
+from-ga.com
+from-hi.com
+from-ia.com
+from-id.com
+from-il.com
+from-in.com
+from-ks.com
+from-ky.com
+from-la.net
+from-ma.com
+from-md.com
+from-me.org
+from-mi.com
+from-mn.com
+from-mo.com
+from-ms.com
+from-mt.com
+from-nc.com
+from-nd.com
+from-ne.com
+from-nh.com
+from-nj.com
+from-nm.com
+from-nv.com
+from-ny.net
+from-oh.com
+from-ok.com
+from-or.com
+from-pa.com
+from-pr.com
+from-ri.com
+from-sc.com
+from-sd.com
+from-tn.com
+from-tx.com
+from-ut.com
+from-va.com
+from-vt.com
+from-wa.com
+from-wi.com
+from-wv.com
+from-wy.com
+ftpaccess.cc
+fuettertdasnetz.de
+game-host.org
+game-server.cc
+getmyip.com
+gets-it.net
+go.dyndns.org
+gotdns.com
+gotdns.org
+groks-the.info
+groks-this.info
+ham-radio-op.net
+here-for-more.info
+hobby-site.com
+hobby-site.org
+home.dyndns.org
+homedns.org
+homeftp.net
+homeftp.org
+homeip.net
+homelinux.com
+homelinux.net
+homelinux.org
+homeunix.com
+homeunix.net
+homeunix.org
+iamallama.com
+in-the-band.net
+is-a-anarchist.com
+is-a-blogger.com
+is-a-bookkeeper.com
+is-a-bruinsfan.org
+is-a-bulls-fan.com
+is-a-candidate.org
+is-a-caterer.com
+is-a-celticsfan.org
+is-a-chef.com
+is-a-chef.net
+is-a-chef.org
+is-a-conservative.com
+is-a-cpa.com
+is-a-cubicle-slave.com
+is-a-democrat.com
+is-a-designer.com
+is-a-doctor.com
+is-a-financialadvisor.com
+is-a-geek.com
+is-a-geek.net
+is-a-geek.org
+is-a-green.com
+is-a-guru.com
+is-a-hard-worker.com
+is-a-hunter.com
+is-a-knight.org
+is-a-landscaper.com
+is-a-lawyer.com
+is-a-liberal.com
+is-a-libertarian.com
+is-a-linux-user.org
+is-a-llama.com
+is-a-musician.com
+is-a-nascarfan.com
+is-a-nurse.com
+is-a-painter.com
+is-a-patsfan.org
+is-a-personaltrainer.com
+is-a-photographer.com
+is-a-player.com
+is-a-republican.com
+is-a-rockstar.com
+is-a-socialist.com
+is-a-soxfan.org
+is-a-student.com
+is-a-teacher.com
+is-a-techie.com
+is-a-therapist.com
+is-an-accountant.com
+is-an-actor.com
+is-an-actress.com
+is-an-anarchist.com
+is-an-artist.com
+is-an-engineer.com
+is-an-entertainer.com
+is-by.us
+is-certified.com
+is-found.org
+is-gone.com
+is-into-anime.com
+is-into-cars.com
+is-into-cartoons.com
+is-into-games.com
+is-leet.com
+is-lost.org
+is-not-certified.com
+is-saved.org
+is-slick.com
+is-uberleet.com
+is-very-bad.org
+is-very-evil.org
+is-very-good.org
+is-very-nice.org
+is-very-sweet.org
+is-with-theband.com
+isa-geek.com
+isa-geek.net
+isa-geek.org
+isa-hockeynut.com
+issmarterthanyou.com
+isteingeek.de
+istmein.de
+kicks-ass.net
+kicks-ass.org
+knowsitall.info
+land-4-sale.us
+lebtimnetz.de
+leitungsen.de
+likes-pie.com
+likescandy.com
+merseine.nu
+mine.nu
+misconfused.org
+mypets.ws
+myphotos.cc
+neat-url.com
+office-on-the.net
+on-the-web.tv
+podzone.net
+podzone.org
+readmyblog.org
+saves-the-whales.com
+scrapper-site.net
+scrapping.cc
+selfip.biz
+selfip.com
+selfip.info
+selfip.net
+selfip.org
+sells-for-less.com
+sells-for-u.com
+sells-it.net
+sellsyourhome.org
+servebbs.com
+servebbs.net
+servebbs.org
+serveftp.net
+serveftp.org
+servegame.org
+shacknet.nu
+simple-url.com
+space-to-rent.com
+stuff-4-sale.org
+stuff-4-sale.us
+teaches-yoga.com
+thruhere.net
+traeumtgerade.de
+webhop.biz
+webhop.info
+webhop.net
+webhop.org
+worse-than.tv
+writesthisblog.com
+
+// GitHub, Inc.
+// Requested by Ben Toews <btoews@github.com> 2013-04-18
+github.io
+
+// Google, Inc.
+// Requested by Eduardo Vela <evn@google.com> 2012-10-24
+appspot.com
+blogspot.be
+blogspot.bj
+blogspot.ca
+blogspot.cf
+blogspot.ch
+blogspot.co.at
+blogspot.co.il
+blogspot.co.nz
+blogspot.co.uk
+blogspot.com
+blogspot.com.ar
+blogspot.com.au
+blogspot.com.br
+blogspot.com.es
+blogspot.cv
+blogspot.cz
+blogspot.de
+blogspot.dk
+blogspot.fi
+blogspot.fr
+blogspot.gr
+blogspot.hk
+blogspot.hu
+blogspot.ie
+blogspot.in
+blogspot.it
+blogspot.jp
+blogspot.kr
+blogspot.mr
+blogspot.mx
+blogspot.nl
+blogspot.no
+blogspot.pt
+blogspot.re
+blogspot.ro
+blogspot.se
+blogspot.sg
+blogspot.sk
+blogspot.td
+blogspot.tw
+codespot.com
+googleapis.com
+googlecode.com
+
+// iki.fi
+// Requested by Hannu Aronsson <haa@iki.fi> 2009-11-05
+iki.fi
+
+// info.at : http://www.info.at/
+biz.at
+info.at
+
+// Michau Enterprises Limited : http://www.co.pl/
+co.pl
+
+// NYC.mn : http://www.information.nyc.mn
+// Requested by Matthew Brown <mattbrown@nyc.mn> 2013-03-11
+nyc.mn
+
+// Opera Software, A.S.A.
+// Requested by Yngve Pettersen <yngve@opera.com> 2009-11-26
+operaunite.com
+
+// Red Hat, Inc. OpenShift : https://openshift.redhat.com/
+// Requested by Tim Kramer <tkramer@rhcloud.com> 2012-10-24
+rhcloud.com
+
+// priv.at : http://www.nic.priv.at/
+// Requested by registry <lendl@nic.at> 2008-06-09
+priv.at
+
+// ZaNiC : http://www.za.net/
+// Requested by registry <hostmaster@nic.za.net> 2009-10-03
+za.net
+za.org
+
+// ===END PRIVATE DOMAINS===
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names.gperf b/chromium/net/base/registry_controlled_domains/effective_tld_names.gperf
new file mode 100644
index 00000000000..7412aba1736
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names.gperf
@@ -0,0 +1,6117 @@
+%{
+// 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.
+
+// This file is generated by net/tools/tld_cleanup/.
+// DO NOT MANUALLY EDIT!
+%}
+struct DomainRule {
+ int name_offset;
+ int type; // flags: 1: exception, 2: wildcard, 4: private
+};
+%%
+0.bg, 0
+1.bg, 0
+2.bg, 0
+2000.hu, 0
+3.bg, 0
+4.bg, 0
+5.bg, 0
+6.bg, 0
+6bone.pl, 0
+7.bg, 0
+8.bg, 0
+9.bg, 0
+a.bg, 0
+a.se, 0
+aa.no, 0
+aarborte.no, 0
+ab.ca, 0
+abashiri.hokkaido.jp, 0
+abeno.osaka.jp, 0
+abiko.chiba.jp, 0
+abira.hokkaido.jp, 0
+abo.pa, 0
+abu.yamaguchi.jp, 0
+ac, 0
+ac.ae, 0
+ac.at, 0
+ac.be, 0
+ac.ci, 0
+ac.cn, 0
+ac.cr, 0
+ac.gn, 0
+ac.id, 0
+ac.im, 0
+ac.in, 0
+ac.ir, 0
+ac.jp, 0
+ac.kr, 0
+ac.ma, 0
+ac.me, 0
+ac.mu, 0
+ac.mw, 0
+ac.ng, 0
+ac.pa, 0
+ac.pr, 0
+ac.rs, 0
+ac.ru, 0
+ac.rw, 0
+ac.se, 0
+ac.sz, 0
+ac.th, 0
+ac.tj, 0
+ac.tz, 0
+ac.ug, 0
+ac.vn, 0
+aca.pro, 0
+academy.museum, 0
+accident-investigation.aero, 0
+accident-prevention.aero, 0
+achi.nagano.jp, 0
+act.au, 0
+act.edu.au, 0
+act.gov.au, 0
+ad, 0
+ad.jp, 0
+adachi.tokyo.jp, 0
+adm.br, 0
+adult.ht, 0
+adv.br, 0
+adygeya.ru, 0
+ae, 0
+ae.org, 4
+aejrie.no, 0
+aero, 0
+aero.mv, 0
+aero.tt, 0
+aerobatic.aero, 0
+aeroclub.aero, 0
+aerodrome.aero, 0
+aeroport.fr, 0
+af, 0
+afjord.no, 0
+ag, 0
+ag.it, 0
+aga.niigata.jp, 0
+agano.niigata.jp, 0
+agdenes.no, 0
+agematsu.nagano.jp, 0
+agents.aero, 0
+agr.br, 0
+agrar.hu, 0
+agriculture.museum, 0
+agrigento.it, 0
+agrinet.tn, 0
+agro.pl, 0
+aguni.okinawa.jp, 0
+ah.cn, 0
+ah.no, 0
+ai, 0
+aibetsu.hokkaido.jp, 0
+aichi.jp, 0
+aid.pl, 0
+aikawa.kanagawa.jp, 0
+ainan.ehime.jp, 0
+aioi.hyogo.jp, 0
+aip.ee, 0
+air-surveillance.aero, 0
+air-traffic-control.aero, 0
+air.museum, 0
+aircraft.aero, 0
+airguard.museum, 0
+airline.aero, 0
+airport.aero, 0
+airtraffic.aero, 0
+aisai.aichi.jp, 0
+aisho.shiga.jp, 0
+aizubange.fukushima.jp, 0
+aizumi.tokushima.jp, 0
+aizumisato.fukushima.jp, 0
+aizuwakamatsu.fukushima.jp, 0
+ak.us, 0
+akabira.hokkaido.jp, 0
+akagi.shimane.jp, 0
+akaiwa.okayama.jp, 0
+akashi.hyogo.jp, 0
+aki.kochi.jp, 0
+akiruno.tokyo.jp, 0
+akishima.tokyo.jp, 0
+akita.akita.jp, 0
+akita.jp, 0
+akkeshi.hokkaido.jp, 0
+aknoluokta.no, 0
+ako.hyogo.jp, 0
+akrehamn.no, 0
+akune.kagoshima.jp, 0
+al, 0
+al.it, 0
+al.no, 0
+al.us, 0
+alabama.museum, 0
+alaheadju.no, 0
+aland.fi, 0
+alaska.museum, 0
+alessandria.it, 0
+alesund.no, 0
+algard.no, 0
+alstahaug.no, 0
+alta.no, 0
+altai.ru, 0
+alto-adige.it, 0
+altoadige.it, 0
+alvdal.no, 0
+am, 0
+am.br, 0
+ama.aichi.jp, 0
+ama.shimane.jp, 0
+amagasaki.hyogo.jp, 0
+amakusa.kumamoto.jp, 0
+amami.kagoshima.jp, 0
+amber.museum, 0
+ambulance.aero, 0
+ambulance.museum, 0
+american.museum, 0
+americana.museum, 0
+americanantiques.museum, 0
+americanart.museum, 0
+ami.ibaraki.jp, 0
+amli.no, 0
+amot.no, 0
+amsterdam.museum, 0
+amur.ru, 0
+amursk.ru, 0
+amusement.aero, 0
+an, 0
+an.it, 0
+anamizu.ishikawa.jp, 0
+anan.nagano.jp, 0
+anan.tokushima.jp, 0
+ancona.it, 0
+and.museum, 0
+andasuolo.no, 0
+andebu.no, 0
+ando.nara.jp, 0
+andoy.no, 0
+andria-barletta-trani.it, 0
+andria-trani-barletta.it, 0
+andriabarlettatrani.it, 0
+andriatranibarletta.it, 0
+anjo.aichi.jp, 0
+annaka.gunma.jp, 0
+annefrank.museum, 0
+anpachi.gifu.jp, 0
+anthro.museum, 0
+anthropology.museum, 0
+antiques.museum, 0
+ao, 0
+ao.it, 0
+aogaki.hyogo.jp, 0
+aogashima.tokyo.jp, 0
+aoki.nagano.jp, 0
+aomori.aomori.jp, 0
+aomori.jp, 0
+aosta.it, 0
+aoste.it, 0
+ap.it, 0
+appspot.com, 4
+aq, 0
+aq.it, 0
+aquarium.museum, 0
+aquila.it, 0
+ar, 2
+ar.com, 4
+ar.it, 0
+ar.us, 0
+arai.shizuoka.jp, 0
+arakawa.saitama.jp, 0
+arakawa.tokyo.jp, 0
+arao.kumamoto.jp, 0
+arboretum.museum, 0
+archaeological.museum, 0
+archaeology.museum, 0
+architecture.museum, 0
+ardal.no, 0
+aremark.no, 0
+arendal.no, 0
+arezzo.it, 0
+ariake.saga.jp, 0
+arida.wakayama.jp, 0
+aridagawa.wakayama.jp, 0
+arita.saga.jp, 0
+arkhangelsk.ru, 0
+arna.no, 0
+arpa, 0
+arq.br, 0
+art.br, 0
+art.do, 0
+art.dz, 0
+art.ht, 0
+art.museum, 0
+art.pl, 0
+art.sn, 0
+artanddesign.museum, 0
+artcenter.museum, 0
+artdeco.museum, 0
+arteducation.museum, 0
+artgallery.museum, 0
+arts.co, 0
+arts.museum, 0
+arts.nf, 0
+arts.ro, 0
+artsandcrafts.museum, 0
+as, 0
+as.us, 0
+asago.hyogo.jp, 0
+asahi.chiba.jp, 0
+asahi.ibaraki.jp, 0
+asahi.mie.jp, 0
+asahi.nagano.jp, 0
+asahi.toyama.jp, 0
+asahi.yamagata.jp, 0
+asahikawa.hokkaido.jp, 0
+asaka.saitama.jp, 0
+asakawa.fukushima.jp, 0
+asakuchi.okayama.jp, 0
+asaminami.hiroshima.jp, 0
+ascoli-piceno.it, 0
+ascolipiceno.it, 0
+aseral.no, 0
+ashibetsu.hokkaido.jp, 0
+ashikaga.tochigi.jp, 0
+ashiya.fukuoka.jp, 0
+ashiya.hyogo.jp, 0
+ashoro.hokkaido.jp, 0
+asia, 0
+asker.no, 0
+askim.no, 0
+askoy.no, 0
+askvoll.no, 0
+asmatart.museum, 0
+asn.au, 0
+asn.lv, 0
+asnes.no, 0
+aso.kumamoto.jp, 0
+ass.km, 0
+assabu.hokkaido.jp, 0
+assassination.museum, 0
+assedic.fr, 0
+assisi.museum, 0
+assn.lk, 0
+asso.bj, 0
+asso.ci, 0
+asso.dz, 0
+asso.fr, 0
+asso.gp, 0
+asso.ht, 0
+asso.km, 0
+asso.mc, 0
+asso.nc, 0
+asso.re, 0
+association.aero, 0
+association.museum, 0
+asti.it, 0
+astrakhan.ru, 0
+astronomy.museum, 0
+asuke.aichi.jp, 0
+at, 0
+at-band-camp.net, 4
+at.it, 0
+atami.shizuoka.jp, 0
+ath.cx, 4
+atlanta.museum, 0
+atm.pl, 0
+ato.br, 0
+atsugi.kanagawa.jp, 0
+atsuma.hokkaido.jp, 0
+au, 0
+audnedaln.no, 0
+augustow.pl, 0
+aukra.no, 0
+aure.no, 0
+aurland.no, 0
+aurskog-holand.no, 0
+austevoll.no, 0
+austin.museum, 0
+australia.museum, 0
+austrheim.no, 0
+author.aero, 0
+auto.pl, 0
+automotive.museum, 0
+av.it, 0
+avellino.it, 0
+averoy.no, 0
+aviation.museum, 0
+avocat.fr, 0
+avoues.fr, 0
+aw, 0
+awaji.hyogo.jp, 0
+ax, 0
+axis.museum, 0
+aya.miyazaki.jp, 0
+ayabe.kyoto.jp, 0
+ayagawa.kagawa.jp, 0
+ayase.kanagawa.jp, 0
+az, 0
+az.us, 0
+azumino.nagano.jp, 0
+b.bg, 0
+b.br, 0
+b.se, 0
+ba, 0
+ba.it, 0
+babia-gora.pl, 0
+badaddja.no, 0
+badajoz.museum, 0
+baghdad.museum, 0
+bahcavuotna.no, 0
+bahccavuotna.no, 0
+bahn.museum, 0
+baidar.no, 0
+baikal.ru, 0
+bajddar.no, 0
+balat.no, 0
+bale.museum, 0
+balestrand.no, 0
+ballangen.no, 0
+ballooning.aero, 0
+balsan.it, 0
+balsfjord.no, 0
+baltimore.museum, 0
+bamble.no, 0
+bandai.fukushima.jp, 0
+bando.ibaraki.jp, 0
+bar.pro, 0
+barcelona.museum, 0
+bardu.no, 0
+bari.it, 0
+barletta-trani-andria.it, 0
+barlettatraniandria.it, 0
+barreau.bj, 0
+barrel-of-knowledge.info, 4
+barrell-of-knowledge.info, 4
+barum.no, 0
+baseball.museum, 0
+basel.museum, 0
+bashkiria.ru, 0
+baths.museum, 0
+bato.tochigi.jp, 0
+batsfjord.no, 0
+bauern.museum, 0
+bb, 0
+bc.ca, 0
+bd, 2
+bd.se, 0
+be, 0
+bearalvahki.no, 0
+beardu.no, 0
+beauxarts.museum, 0
+bedzin.pl, 0
+beeldengeluid.museum, 0
+beiarn.no, 0
+belau.pw, 0
+belgorod.ru, 0
+bellevue.museum, 0
+belluno.it, 0
+benevento.it, 0
+beppu.oita.jp, 0
+berg.no, 0
+bergamo.it, 0
+bergbau.museum, 0
+bergen.no, 0
+berkeley.museum, 0
+berlevag.no, 0
+berlin.museum, 0
+bern.museum, 0
+beskidy.pl, 0
+betainabox.com, 4
+better-than.tv, 4
+bf, 0
+bg, 0
+bg.it, 0
+bh, 0
+bi, 0
+bi.it, 0
+bialowieza.pl, 0
+bialystok.pl, 0
+bibai.hokkaido.jp, 0
+bible.museum, 0
+biei.hokkaido.jp, 0
+bielawa.pl, 0
+biella.it, 0
+bieszczady.pl, 0
+bievat.no, 0
+bifuka.hokkaido.jp, 0
+bihoro.hokkaido.jp, 0
+bilbao.museum, 0
+bill.museum, 0
+bindal.no, 0
+bio.br, 0
+bir.ru, 0
+biratori.hokkaido.jp, 0
+birdart.museum, 0
+birkenes.no, 0
+birthplace.museum, 0
+biz, 0
+biz.at, 4
+biz.az, 0
+biz.bb, 0
+biz.id, 0
+biz.ki, 0
+biz.mv, 0
+biz.mw, 0
+biz.nr, 0
+biz.pk, 0
+biz.pl, 0
+biz.pr, 0
+biz.tj, 0
+biz.tt, 0
+biz.vn, 0
+bizen.okayama.jp, 0
+bj, 0
+bj.cn, 0
+bjarkoy.no, 0
+bjerkreim.no, 0
+bjugn.no, 0
+bl.it, 0
+bl.uk, 1
+blog.br, 0
+blogdns.com, 4
+blogdns.net, 4
+blogdns.org, 4
+blogsite.org, 4
+blogspot.be, 4
+blogspot.bj, 4
+blogspot.ca, 4
+blogspot.cf, 4
+blogspot.ch, 4
+blogspot.co.at, 4
+blogspot.co.il, 4
+blogspot.co.nz, 4
+blogspot.co.uk, 4
+blogspot.com, 4
+blogspot.com.ar, 4
+blogspot.com.au, 4
+blogspot.com.br, 4
+blogspot.com.es, 4
+blogspot.cv, 4
+blogspot.cz, 4
+blogspot.de, 4
+blogspot.dk, 4
+blogspot.fi, 4
+blogspot.fr, 4
+blogspot.gr, 4
+blogspot.hk, 4
+blogspot.hu, 4
+blogspot.ie, 4
+blogspot.in, 4
+blogspot.it, 4
+blogspot.jp, 4
+blogspot.kr, 4
+blogspot.mr, 4
+blogspot.mx, 4
+blogspot.nl, 4
+blogspot.no, 4
+blogspot.pt, 4
+blogspot.re, 4
+blogspot.ro, 4
+blogspot.se, 4
+blogspot.sg, 4
+blogspot.sk, 4
+blogspot.td, 4
+blogspot.tw, 4
+bm, 0
+bmd.br, 0
+bn, 2
+bn.it, 0
+bo, 0
+bo.it, 0
+bo.nordland.no, 0
+bo.telemark.no, 0
+bodo.no, 0
+bokn.no, 0
+boldlygoingnowhere.org, 4
+boleslawiec.pl, 0
+bologna.it, 0
+bolt.hu, 0
+bolzano.it, 0
+bomlo.no, 0
+bonn.museum, 0
+boston.museum, 0
+botanical.museum, 0
+botanicalgarden.museum, 0
+botanicgarden.museum, 0
+botany.museum, 0
+bozen.it, 0
+br, 0
+br.com, 4
+br.it, 0
+brand.se, 0
+brandywinevalley.museum, 0
+brasil.museum, 0
+bremanger.no, 0
+brescia.it, 0
+brindisi.it, 0
+bristol.museum, 0
+british-library.uk, 1
+british.museum, 0
+britishcolumbia.museum, 0
+broadcast.museum, 0
+broke-it.net, 4
+broker.aero, 0
+bronnoy.no, 0
+bronnoysund.no, 0
+brumunddal.no, 0
+brunel.museum, 0
+brussel.museum, 0
+brussels.museum, 0
+bruxelles.museum, 0
+bryansk.ru, 0
+bryne.no, 0
+bs, 0
+bs.it, 0
+bt, 0
+bt.it, 0
+bu.no, 0
+budejju.no, 0
+building.museum, 0
+bungoono.oita.jp, 0
+bungotakada.oita.jp, 0
+bunkyo.tokyo.jp, 0
+burghof.museum, 0
+buryatia.ru, 0
+bus.museum, 0
+busan.kr, 0
+bushey.museum, 0
+buyshouses.net, 4
+buzen.fukuoka.jp, 0
+bv.nl, 0
+bw, 0
+by, 0
+bydgoszcz.pl, 0
+bygland.no, 0
+bykle.no, 0
+bytom.pl, 0
+bz, 0
+bz.it, 0
+c.bg, 0
+c.la, 4
+c.se, 0
+ca, 0
+ca.it, 0
+ca.na, 0
+ca.us, 0
+caa.aero, 0
+cadaques.museum, 0
+cagliari.it, 0
+cahcesuolo.no, 0
+california.museum, 0
+caltanissetta.it, 0
+cambridge.museum, 0
+campidano-medio.it, 0
+campidanomedio.it, 0
+campobasso.it, 0
+can.museum, 0
+canada.museum, 0
+capebreton.museum, 0
+carbonia-iglesias.it, 0
+carboniaiglesias.it, 0
+cargo.aero, 0
+carrara-massa.it, 0
+carraramassa.it, 0
+carrier.museum, 0
+cartoonart.museum, 0
+casadelamoneda.museum, 0
+caserta.it, 0
+casino.hu, 0
+castle.museum, 0
+castres.museum, 0
+cat, 0
+catania.it, 0
+catanzaro.it, 0
+catering.aero, 0
+cb.it, 0
+cbg.ru, 0
+cc, 0
+cc.ak.us, 0
+cc.al.us, 0
+cc.ar.us, 0
+cc.as.us, 0
+cc.az.us, 0
+cc.ca.us, 0
+cc.co.us, 0
+cc.ct.us, 0
+cc.dc.us, 0
+cc.de.us, 0
+cc.fl.us, 0
+cc.ga.us, 0
+cc.gu.us, 0
+cc.hi.us, 0
+cc.ia.us, 0
+cc.id.us, 0
+cc.il.us, 0
+cc.in.us, 0
+cc.ks.us, 0
+cc.ky.us, 0
+cc.la.us, 0
+cc.ma.us, 0
+cc.md.us, 0
+cc.me.us, 0
+cc.mi.us, 0
+cc.mn.us, 0
+cc.mo.us, 0
+cc.ms.us, 0
+cc.mt.us, 0
+cc.na, 0
+cc.nc.us, 0
+cc.nd.us, 0
+cc.ne.us, 0
+cc.nh.us, 0
+cc.nj.us, 0
+cc.nm.us, 0
+cc.nv.us, 0
+cc.ny.us, 0
+cc.oh.us, 0
+cc.ok.us, 0
+cc.or.us, 0
+cc.pa.us, 0
+cc.pr.us, 0
+cc.ri.us, 0
+cc.sc.us, 0
+cc.sd.us, 0
+cc.tn.us, 0
+cc.tx.us, 0
+cc.ut.us, 0
+cc.va.us, 0
+cc.vi.us, 0
+cc.vt.us, 0
+cc.wa.us, 0
+cc.wi.us, 0
+cc.wv.us, 0
+cc.wy.us, 0
+cci.fr, 0
+cd, 0
+ce.it, 0
+cechire.com, 4
+celtic.museum, 0
+center.museum, 0
+certification.aero, 0
+cesena-forli.it, 0
+cesenaforli.it, 0
+cf, 0
+cg, 0
+ch, 0
+ch.it, 0
+chambagri.fr, 0
+championship.aero, 0
+charter.aero, 0
+chattanooga.museum, 0
+chel.ru, 0
+cheltenham.museum, 0
+chelyabinsk.ru, 0
+cherkassy.ua, 0
+cherkasy.ua, 0
+chernigov.ua, 0
+chernihiv.ua, 0
+chernivtsi.ua, 0
+chernovtsy.ua, 0
+chesapeakebay.museum, 0
+chiba.jp, 0
+chicago.museum, 0
+chichibu.saitama.jp, 0
+chieti.it, 0
+chigasaki.kanagawa.jp, 0
+chihayaakasaka.osaka.jp, 0
+chijiwa.nagasaki.jp, 0
+chikugo.fukuoka.jp, 0
+chikuho.fukuoka.jp, 0
+chikuhoku.nagano.jp, 0
+chikujo.fukuoka.jp, 0
+chikuma.nagano.jp, 0
+chikusei.ibaraki.jp, 0
+chikushino.fukuoka.jp, 0
+chikuzen.fukuoka.jp, 0
+children.museum, 0
+childrens.museum, 0
+childrensgarden.museum, 0
+chino.nagano.jp, 0
+chippubetsu.hokkaido.jp, 0
+chiropractic.museum, 0
+chirurgiens-dentistes.fr, 0
+chiryu.aichi.jp, 0
+chita.aichi.jp, 0
+chita.ru, 0
+chitose.hokkaido.jp, 0
+chiyoda.gunma.jp, 0
+chiyoda.tokyo.jp, 0
+chizu.tottori.jp, 0
+chocolate.museum, 0
+chofu.tokyo.jp, 0
+chonan.chiba.jp, 0
+chosei.chiba.jp, 0
+choshi.chiba.jp, 0
+choyo.kumamoto.jp, 0
+christiansburg.museum, 0
+chtr.k12.ma.us, 0
+chukotka.ru, 0
+chungbuk.kr, 0
+chungnam.kr, 0
+chuo.chiba.jp, 0
+chuo.fukuoka.jp, 0
+chuo.osaka.jp, 0
+chuo.tokyo.jp, 0
+chuo.yamanashi.jp, 0
+chuvashia.ru, 0
+ci, 0
+ci.it, 0
+cieszyn.pl, 0
+cim.br, 0
+cincinnati.museum, 0
+cinema.museum, 0
+circus.museum, 0
+city.hu, 0
+city.kawasaki.jp, 1
+city.kitakyushu.jp, 1
+city.kobe.jp, 1
+city.nagoya.jp, 1
+city.sapporo.jp, 1
+city.sendai.jp, 1
+city.yokohama.jp, 1
+civilaviation.aero, 0
+civilisation.museum, 0
+civilization.museum, 0
+civilwar.museum, 0
+ck, 2
+ck.ua, 0
+cl, 0
+cl.it, 0
+clinton.museum, 0
+clock.museum, 0
+cloudfront.net, 4
+club.aero, 0
+club.tw, 0
+cm, 0
+cmw.ru, 0
+cn, 0
+cn.com, 4
+cn.it, 0
+cn.ua, 0
+cng.br, 0
+cnt.br, 0
+co, 0
+co.ae, 0
+co.ag, 0
+co.ao, 0
+co.at, 0
+co.ba, 0
+co.bi, 0
+co.bw, 0
+co.ca, 4
+co.ci, 0
+co.cl, 0
+co.cr, 0
+co.gg, 0
+co.gy, 0
+co.hu, 0
+co.id, 0
+co.im, 0
+co.in, 0
+co.ir, 0
+co.it, 0
+co.je, 0
+co.jp, 0
+co.kr, 0
+co.lc, 0
+co.ls, 0
+co.ma, 0
+co.me, 0
+co.mu, 0
+co.mw, 0
+co.na, 0
+co.nl, 4
+co.no, 4
+co.pl, 4
+co.pn, 0
+co.pw, 0
+co.rs, 0
+co.rw, 0
+co.st, 0
+co.sz, 0
+co.th, 0
+co.tj, 0
+co.tm, 0
+co.tt, 0
+co.tz, 0
+co.ua, 0
+co.ug, 0
+co.us, 0
+co.uz, 0
+co.ve, 0
+co.vi, 0
+coal.museum, 0
+coastaldefence.museum, 0
+codespot.com, 4
+cody.museum, 0
+coldwar.museum, 0
+collection.museum, 0
+colonialwilliamsburg.museum, 0
+coloradoplateau.museum, 0
+columbia.museum, 0
+columbus.museum, 0
+com, 0
+com.ac, 0
+com.af, 0
+com.ag, 0
+com.ai, 0
+com.al, 0
+com.an, 0
+com.au, 0
+com.aw, 0
+com.az, 0
+com.ba, 0
+com.bb, 0
+com.bh, 0
+com.bi, 0
+com.bm, 0
+com.bo, 0
+com.br, 0
+com.bs, 0
+com.bt, 0
+com.by, 0
+com.bz, 0
+com.ci, 0
+com.cn, 0
+com.co, 0
+com.cu, 0
+com.cw, 0
+com.de, 4
+com.dm, 0
+com.do, 0
+com.dz, 0
+com.ec, 0
+com.ee, 0
+com.eg, 0
+com.es, 0
+com.fr, 0
+com.ge, 0
+com.gh, 0
+com.gi, 0
+com.gn, 0
+com.gp, 0
+com.gr, 0
+com.gt, 0
+com.gy, 0
+com.hk, 0
+com.hn, 0
+com.hr, 0
+com.ht, 0
+com.io, 0
+com.iq, 0
+com.is, 0
+com.jo, 0
+com.kg, 0
+com.ki, 0
+com.km, 0
+com.kp, 0
+com.ky, 0
+com.kz, 0
+com.la, 0
+com.lb, 0
+com.lc, 0
+com.lk, 0
+com.lr, 0
+com.lv, 0
+com.ly, 0
+com.mg, 0
+com.mk, 0
+com.ml, 0
+com.mo, 0
+com.mu, 0
+com.mv, 0
+com.mw, 0
+com.mx, 0
+com.my, 0
+com.na, 0
+com.nf, 0
+com.ng, 0
+com.nr, 0
+com.pa, 0
+com.pe, 0
+com.pf, 0
+com.ph, 0
+com.pk, 0
+com.pl, 0
+com.pr, 0
+com.ps, 0
+com.pt, 0
+com.py, 0
+com.qa, 0
+com.re, 0
+com.ro, 0
+com.ru, 0
+com.rw, 0
+com.sa, 0
+com.sb, 0
+com.sc, 0
+com.sd, 0
+com.sg, 0
+com.sh, 0
+com.sl, 0
+com.sn, 0
+com.so, 0
+com.st, 0
+com.sy, 0
+com.tj, 0
+com.tm, 0
+com.tn, 0
+com.to, 0
+com.tt, 0
+com.tw, 0
+com.ua, 0
+com.ug, 0
+com.uy, 0
+com.uz, 0
+com.vc, 0
+com.ve, 0
+com.vi, 0
+com.vn, 0
+com.ws, 0
+communication.museum, 0
+communications.museum, 0
+community.museum, 0
+como.it, 0
+computer.museum, 0
+computerhistory.museum, 0
+conf.au, 0
+conf.lv, 0
+conference.aero, 0
+congresodelalengua3.ar, 1
+consulado.st, 0
+consultant.aero, 0
+consulting.aero, 0
+contemporary.museum, 0
+contemporaryart.museum, 0
+control.aero, 0
+convent.museum, 0
+coop, 0
+coop.br, 0
+coop.ht, 0
+coop.km, 0
+coop.mv, 0
+coop.mw, 0
+coop.py, 0
+coop.tt, 0
+copenhagen.museum, 0
+corporation.museum, 0
+corvette.museum, 0
+cosenza.it, 0
+costume.museum, 0
+council.aero, 0
+countryestate.museum, 0
+county.museum, 0
+cpa.pro, 0
+cq.cn, 0
+cr, 0
+cr.it, 0
+cr.ua, 0
+crafts.museum, 0
+cranbrook.museum, 0
+creation.museum, 0
+cremona.it, 0
+crew.aero, 0
+crimea.ua, 0
+crotone.it, 0
+cs.it, 0
+ct.it, 0
+ct.us, 0
+cu, 0
+cultural.museum, 0
+culturalcenter.museum, 0
+culture.museum, 0
+cuneo.it, 0
+cv, 0
+cv.ua, 0
+cw, 0
+cx, 0
+cy, 2
+cyber.museum, 0
+cymru.museum, 0
+cz, 0
+cz.it, 0
+czeladz.pl, 0
+czest.pl, 0
+d.bg, 0
+d.se, 0
+daegu.kr, 0
+daejeon.kr, 0
+dagestan.ru, 0
+daigo.ibaraki.jp, 0
+daisen.akita.jp, 0
+daito.osaka.jp, 0
+daiwa.hiroshima.jp, 0
+dali.museum, 0
+dallas.museum, 0
+database.museum, 0
+date.fukushima.jp, 0
+date.hokkaido.jp, 0
+davvenjarga.no, 0
+davvesiida.no, 0
+dazaifu.fukuoka.jp, 0
+dc.us, 0
+ddr.museum, 0
+de, 0
+de.com, 4
+de.us, 0
+deatnu.no, 0
+decorativearts.museum, 0
+defense.tn, 0
+delaware.museum, 0
+dell-ogliastra.it, 0
+dellogliastra.it, 0
+delmenhorst.museum, 0
+denmark.museum, 0
+dep.no, 0
+depot.museum, 0
+design.aero, 0
+design.museum, 0
+detroit.museum, 0
+dgca.aero, 0
+dielddanuorri.no, 0
+dinosaur.museum, 0
+discovery.museum, 0
+divtasvuodna.no, 0
+divttasvuotna.no, 0
+dj, 0
+dk, 0
+dlugoleka.pl, 0
+dm, 0
+dn.ua, 0
+dnepropetrovsk.ua, 0
+dni.us, 0
+dnipropetrovsk.ua, 0
+dnsalias.com, 4
+dnsalias.net, 4
+dnsalias.org, 4
+dnsdojo.com, 4
+dnsdojo.net, 4
+dnsdojo.org, 4
+do, 0
+does-it.net, 4
+doesntexist.com, 4
+doesntexist.org, 4
+dolls.museum, 0
+dominic.ua, 0
+donetsk.ua, 0
+donna.no, 0
+donostia.museum, 0
+dontexist.com, 4
+dontexist.net, 4
+dontexist.org, 4
+doomdns.com, 4
+doomdns.org, 4
+doshi.yamanashi.jp, 0
+dovre.no, 0
+dp.ua, 0
+dr.na, 0
+drammen.no, 0
+drangedal.no, 0
+dreamhosters.com, 4
+drobak.no, 0
+dudinka.ru, 0
+durham.museum, 0
+dvrdns.org, 4
+dyn-o-saur.com, 4
+dynalias.com, 4
+dynalias.net, 4
+dynalias.org, 4
+dynathome.net, 4
+dyndns-at-home.com, 4
+dyndns-at-work.com, 4
+dyndns-blog.com, 4
+dyndns-free.com, 4
+dyndns-home.com, 4
+dyndns-ip.com, 4
+dyndns-mail.com, 4
+dyndns-office.com, 4
+dyndns-pics.com, 4
+dyndns-remote.com, 4
+dyndns-server.com, 4
+dyndns-web.com, 4
+dyndns-wiki.com, 4
+dyndns-work.com, 4
+dyndns.biz, 4
+dyndns.info, 4
+dyndns.org, 4
+dyndns.tv, 4
+dyndns.ws, 4
+dyroy.no, 0
+dz, 0
+e-burg.ru, 0
+e.bg, 0
+e.se, 0
+e12.ve, 0
+e164.arpa, 0
+eastafrica.museum, 0
+eastcoast.museum, 0
+ebetsu.hokkaido.jp, 0
+ebina.kanagawa.jp, 0
+ebino.miyazaki.jp, 0
+ebiz.tw, 0
+ec, 0
+echizen.fukui.jp, 0
+ecn.br, 0
+eco.br, 0
+ed.ao, 0
+ed.ci, 0
+ed.cr, 0
+ed.jp, 0
+ed.pw, 0
+edogawa.tokyo.jp, 0
+edu, 0
+edu.ac, 0
+edu.af, 0
+edu.al, 0
+edu.an, 0
+edu.au, 0
+edu.az, 0
+edu.ba, 0
+edu.bb, 0
+edu.bh, 0
+edu.bi, 0
+edu.bm, 0
+edu.bo, 0
+edu.br, 0
+edu.bs, 0
+edu.bt, 0
+edu.bz, 0
+edu.ci, 0
+edu.cn, 0
+edu.co, 0
+edu.cu, 0
+edu.cw, 0
+edu.dm, 0
+edu.do, 0
+edu.dz, 0
+edu.ec, 0
+edu.ee, 0
+edu.eg, 0
+edu.es, 0
+edu.ge, 0
+edu.gh, 0
+edu.gi, 0
+edu.gn, 0
+edu.gp, 0
+edu.gr, 0
+edu.gt, 0
+edu.hk, 0
+edu.hn, 0
+edu.ht, 0
+edu.in, 0
+edu.iq, 0
+edu.is, 0
+edu.it, 0
+edu.jo, 0
+edu.kg, 0
+edu.ki, 0
+edu.km, 0
+edu.kn, 0
+edu.kp, 0
+edu.ky, 0
+edu.kz, 0
+edu.la, 0
+edu.lb, 0
+edu.lc, 0
+edu.lk, 0
+edu.lr, 0
+edu.lv, 0
+edu.ly, 0
+edu.me, 0
+edu.mg, 0
+edu.mk, 0
+edu.ml, 0
+edu.mn, 0
+edu.mo, 0
+edu.mv, 0
+edu.mw, 0
+edu.mx, 0
+edu.my, 0
+edu.ng, 0
+edu.nr, 0
+edu.pa, 0
+edu.pe, 0
+edu.pf, 0
+edu.ph, 0
+edu.pk, 0
+edu.pl, 0
+edu.pn, 0
+edu.pr, 0
+edu.ps, 0
+edu.pt, 0
+edu.py, 0
+edu.qa, 0
+edu.rs, 0
+edu.ru, 0
+edu.rw, 0
+edu.sa, 0
+edu.sb, 0
+edu.sc, 0
+edu.sd, 0
+edu.sg, 0
+edu.sl, 0
+edu.sn, 0
+edu.st, 0
+edu.sy, 0
+edu.tj, 0
+edu.tm, 0
+edu.to, 0
+edu.tt, 0
+edu.tw, 0
+edu.ua, 0
+edu.uy, 0
+edu.vc, 0
+edu.ve, 0
+edu.vn, 0
+edu.ws, 0
+educ.ar, 1
+education.museum, 0
+educational.museum, 0
+educator.aero, 0
+edunet.tn, 0
+ee, 0
+eg, 0
+egersund.no, 0
+egyptian.museum, 0
+ehime.jp, 0
+eid.no, 0
+eidfjord.no, 0
+eidsberg.no, 0
+eidskog.no, 0
+eidsvoll.no, 0
+eigersund.no, 0
+eiheiji.fukui.jp, 0
+eisenbahn.museum, 0
+elasticbeanstalk.com, 4
+elb.amazonaws.com, 4
+elblag.pl, 0
+elburg.museum, 0
+elk.pl, 0
+elvendrell.museum, 0
+elverum.no, 0
+embaixada.st, 0
+embetsu.hokkaido.jp, 0
+embroidery.museum, 0
+emergency.aero, 0
+emp.br, 0
+en.it, 0
+ena.gifu.jp, 0
+encyclopedic.museum, 0
+endofinternet.net, 4
+endofinternet.org, 4
+endoftheinternet.org, 4
+enebakk.no, 0
+eng.br, 0
+eng.pro, 0
+engerdal.no, 0
+engine.aero, 0
+engineer.aero, 0
+england.museum, 0
+eniwa.hokkaido.jp, 0
+enna.it, 0
+ens.tn, 0
+entertainment.aero, 0
+entomology.museum, 0
+environment.museum, 0
+environmentalconservation.museum, 0
+epilepsy.museum, 0
+equipment.aero, 0
+er, 2
+erimo.hokkaido.jp, 0
+erotica.hu, 0
+erotika.hu, 0
+es, 0
+es.kr, 0
+esan.hokkaido.jp, 0
+esashi.hokkaido.jp, 0
+esp.br, 0
+essex.museum, 0
+est-a-la-maison.com, 4
+est-a-la-masion.com, 4
+est-le-patron.com, 4
+est-mon-blogueur.com, 4
+est.pr, 0
+estate.museum, 0
+et, 2
+etajima.hiroshima.jp, 0
+etc.br, 0
+ethnology.museum, 0
+eti.br, 0
+etne.no, 0
+etnedal.no, 0
+eu, 0
+eu.com, 4
+eu.int, 0
+eun.eg, 0
+evenassi.no, 0
+evenes.no, 0
+evje-og-hornnes.no, 0
+exchange.aero, 0
+exeter.museum, 0
+exhibition.museum, 0
+experts-comptables.fr, 0
+express.aero, 0
+f.bg, 0
+f.se, 0
+fam.pk, 0
+family.museum, 0
+far.br, 0
+fareast.ru, 0
+farm.museum, 0
+farmequipment.museum, 0
+farmers.museum, 0
+farmstead.museum, 0
+farsund.no, 0
+fauske.no, 0
+fc.it, 0
+fe.it, 0
+fed.us, 0
+federation.aero, 0
+fedje.no, 0
+fermo.it, 0
+ferrara.it, 0
+fet.no, 0
+fetsund.no, 0
+fg.it, 0
+fh.se, 0
+fhs.no, 0
+fhsk.se, 0
+fhv.se, 0
+fi, 0
+fi.cr, 0
+fi.it, 0
+fie.ee, 0
+field.museum, 0
+figueres.museum, 0
+filatelia.museum, 0
+film.hu, 0
+film.museum, 0
+fin.ec, 0
+fin.tn, 0
+fineart.museum, 0
+finearts.museum, 0
+finland.museum, 0
+finnoy.no, 0
+firenze.it, 0
+firm.co, 0
+firm.ht, 0
+firm.in, 0
+firm.nf, 0
+firm.ro, 0
+fitjar.no, 0
+fj, 2
+fj.cn, 0
+fjaler.no, 0
+fjell.no, 0
+fk, 2
+fl.us, 0
+fla.no, 0
+flakstad.no, 0
+flanders.museum, 0
+flatanger.no, 0
+flekkefjord.no, 0
+flesberg.no, 0
+flight.aero, 0
+flog.br, 0
+flora.no, 0
+florence.it, 0
+florida.museum, 0
+floro.no, 0
+fm, 0
+fm.br, 0
+fm.it, 0
+fm.no, 0
+fnd.br, 0
+fo, 0
+foggia.it, 0
+folkebibl.no, 0
+folldal.no, 0
+for-better.biz, 4
+for-more.biz, 4
+for-our.info, 4
+for-some.biz, 4
+for-the.biz, 4
+force.museum, 0
+forde.no, 0
+forgot.her.name, 4
+forgot.his.name, 4
+forli-cesena.it, 0
+forlicesena.it, 0
+forsand.no, 0
+fortmissoula.museum, 0
+fortworth.museum, 0
+forum.hu, 0
+fosnes.no, 0
+fot.br, 0
+foundation.museum, 0
+fr, 0
+fr.it, 0
+frana.no, 0
+francaise.museum, 0
+frankfurt.museum, 0
+franziskaner.museum, 0
+fredrikstad.no, 0
+freemasonry.museum, 0
+frei.no, 0
+freiburg.museum, 0
+freight.aero, 0
+fribourg.museum, 0
+frog.museum, 0
+frogn.no, 0
+froland.no, 0
+from-ak.com, 4
+from-al.com, 4
+from-ar.com, 4
+from-az.net, 4
+from-ca.com, 4
+from-co.net, 4
+from-ct.com, 4
+from-dc.com, 4
+from-de.com, 4
+from-fl.com, 4
+from-ga.com, 4
+from-hi.com, 4
+from-ia.com, 4
+from-id.com, 4
+from-il.com, 4
+from-in.com, 4
+from-ks.com, 4
+from-ky.com, 4
+from-la.net, 4
+from-ma.com, 4
+from-md.com, 4
+from-me.org, 4
+from-mi.com, 4
+from-mn.com, 4
+from-mo.com, 4
+from-ms.com, 4
+from-mt.com, 4
+from-nc.com, 4
+from-nd.com, 4
+from-ne.com, 4
+from-nh.com, 4
+from-nj.com, 4
+from-nm.com, 4
+from-nv.com, 4
+from-ny.net, 4
+from-oh.com, 4
+from-ok.com, 4
+from-or.com, 4
+from-pa.com, 4
+from-pr.com, 4
+from-ri.com, 4
+from-sc.com, 4
+from-sd.com, 4
+from-tn.com, 4
+from-tx.com, 4
+from-ut.com, 4
+from-va.com, 4
+from-vt.com, 4
+from-wa.com, 4
+from-wi.com, 4
+from-wv.com, 4
+from-wy.com, 4
+from.hr, 0
+frosinone.it, 0
+frosta.no, 0
+froya.no, 0
+fst.br, 0
+ftpaccess.cc, 4
+fuchu.hiroshima.jp, 0
+fuchu.tokyo.jp, 0
+fuchu.toyama.jp, 0
+fudai.iwate.jp, 0
+fuefuki.yamanashi.jp, 0
+fuel.aero, 0
+fuettertdasnetz.de, 4
+fuji.shizuoka.jp, 0
+fujieda.shizuoka.jp, 0
+fujiidera.osaka.jp, 0
+fujikawa.shizuoka.jp, 0
+fujikawa.yamanashi.jp, 0
+fujikawaguchiko.yamanashi.jp, 0
+fujimi.nagano.jp, 0
+fujimi.saitama.jp, 0
+fujimino.saitama.jp, 0
+fujinomiya.shizuoka.jp, 0
+fujioka.gunma.jp, 0
+fujisato.akita.jp, 0
+fujisawa.iwate.jp, 0
+fujisawa.kanagawa.jp, 0
+fujishiro.ibaraki.jp, 0
+fujiyoshida.yamanashi.jp, 0
+fukagawa.hokkaido.jp, 0
+fukaya.saitama.jp, 0
+fukuchi.fukuoka.jp, 0
+fukuchiyama.kyoto.jp, 0
+fukudomi.saga.jp, 0
+fukui.fukui.jp, 0
+fukui.jp, 0
+fukumitsu.toyama.jp, 0
+fukuoka.jp, 0
+fukuroi.shizuoka.jp, 0
+fukusaki.hyogo.jp, 0
+fukushima.fukushima.jp, 0
+fukushima.hokkaido.jp, 0
+fukushima.jp, 0
+fukuyama.hiroshima.jp, 0
+funabashi.chiba.jp, 0
+funagata.yamagata.jp, 0
+funahashi.toyama.jp, 0
+fundacio.museum, 0
+fuoisku.no, 0
+fuossko.no, 0
+furano.hokkaido.jp, 0
+furniture.museum, 0
+furubira.hokkaido.jp, 0
+furudono.fukushima.jp, 0
+furukawa.miyagi.jp, 0
+fusa.no, 0
+fuso.aichi.jp, 0
+fussa.tokyo.jp, 0
+futaba.fukushima.jp, 0
+futsu.nagasaki.jp, 0
+futtsu.chiba.jp, 0
+fylkesbibl.no, 0
+fyresdal.no, 0
+g.bg, 0
+g.se, 0
+g12.br, 0
+ga, 0
+ga.us, 0
+gaivuotna.no, 0
+gallery.museum, 0
+galsa.no, 0
+gamagori.aichi.jp, 0
+game-host.org, 4
+game-server.cc, 4
+game.tw, 0
+games.hu, 0
+gamo.shiga.jp, 0
+gamvik.no, 0
+gangaviika.no, 0
+gangwon.kr, 0
+garden.museum, 0
+gateway.museum, 0
+gaular.no, 0
+gausdal.no, 0
+gb.com, 4
+gb.net, 4
+gc.ca, 0
+gd, 0
+gd.cn, 0
+gda.pl, 0
+gdansk.pl, 0
+gdynia.pl, 0
+ge, 0
+ge.it, 0
+geelvinck.museum, 0
+geisei.kochi.jp, 0
+gemological.museum, 0
+gen.in, 0
+genkai.saga.jp, 0
+genoa.it, 0
+genova.it, 0
+geology.museum, 0
+geometre-expert.fr, 0
+georgia.museum, 0
+getmyip.com, 4
+gets-it.net, 4
+gf, 0
+gg, 0
+ggf.br, 0
+gh, 0
+gi, 0
+giehtavuoatna.no, 0
+giessen.museum, 0
+gifu.gifu.jp, 0
+gifu.jp, 0
+gildeskal.no, 0
+ginan.gifu.jp, 0
+ginowan.okinawa.jp, 0
+ginoza.okinawa.jp, 0
+giske.no, 0
+github.io, 4
+gjemnes.no, 0
+gjerdrum.no, 0
+gjerstad.no, 0
+gjesdal.no, 0
+gjovik.no, 0
+gl, 0
+glas.museum, 0
+glass.museum, 0
+gliding.aero, 0
+gliwice.pl, 0
+glogow.pl, 0
+gloppen.no, 0
+gm, 0
+gmina.pl, 0
+gn, 0
+gniezno.pl, 0
+go.ci, 0
+go.cr, 0
+go.dyndns.org, 4
+go.id, 0
+go.it, 0
+go.jp, 0
+go.kr, 0
+go.pw, 0
+go.th, 0
+go.tj, 0
+go.tz, 0
+go.ug, 0
+gob.bo, 0
+gob.cl, 0
+gob.do, 0
+gob.ec, 0
+gob.es, 0
+gob.gt, 0
+gob.hn, 0
+gob.mx, 0
+gob.pa, 0
+gob.pe, 0
+gob.pk, 0
+gobiernoelectronico.ar, 1
+gobo.wakayama.jp, 0
+godo.gifu.jp, 0
+gojome.akita.jp, 0
+gok.pk, 0
+gokase.miyazaki.jp, 0
+gol.no, 0
+gon.pk, 0
+gonohe.aomori.jp, 0
+googleapis.com, 4
+googlecode.com, 4
+gop.pk, 0
+gorge.museum, 0
+gorizia.it, 0
+gorlice.pl, 0
+gos.pk, 0
+gose.nara.jp, 0
+gosen.niigata.jp, 0
+goshiki.hyogo.jp, 0
+gotdns.com, 4
+gotdns.org, 4
+gotemba.shizuoka.jp, 0
+goto.nagasaki.jp, 0
+gotsu.shimane.jp, 0
+gouv.bj, 0
+gouv.ci, 0
+gouv.fr, 0
+gouv.ht, 0
+gouv.km, 0
+gouv.ml, 0
+gouv.rw, 0
+gouv.sn, 0
+gov, 0
+gov.ac, 0
+gov.ae, 0
+gov.af, 0
+gov.al, 0
+gov.as, 0
+gov.au, 0
+gov.az, 0
+gov.ba, 0
+gov.bb, 0
+gov.bf, 0
+gov.bh, 0
+gov.bm, 0
+gov.bo, 0
+gov.br, 0
+gov.bs, 0
+gov.bt, 0
+gov.by, 0
+gov.bz, 0
+gov.cd, 0
+gov.cl, 0
+gov.cm, 0
+gov.cn, 0
+gov.co, 0
+gov.cu, 0
+gov.cx, 0
+gov.dm, 0
+gov.do, 0
+gov.dz, 0
+gov.ec, 0
+gov.ee, 0
+gov.eg, 0
+gov.ge, 0
+gov.gg, 0
+gov.gh, 0
+gov.gi, 0
+gov.gn, 0
+gov.gr, 0
+gov.hk, 0
+gov.ie, 0
+gov.im, 0
+gov.in, 0
+gov.iq, 0
+gov.ir, 0
+gov.is, 0
+gov.it, 0
+gov.je, 0
+gov.jo, 0
+gov.kg, 0
+gov.ki, 0
+gov.km, 0
+gov.kn, 0
+gov.kp, 0
+gov.ky, 0
+gov.kz, 0
+gov.la, 0
+gov.lb, 0
+gov.lc, 0
+gov.lk, 0
+gov.lr, 0
+gov.lt, 0
+gov.lv, 0
+gov.ly, 0
+gov.ma, 0
+gov.me, 0
+gov.mg, 0
+gov.mk, 0
+gov.ml, 0
+gov.mn, 0
+gov.mo, 0
+gov.mr, 0
+gov.mu, 0
+gov.mv, 0
+gov.mw, 0
+gov.my, 0
+gov.nc.tr, 0
+gov.ng, 0
+gov.nr, 0
+gov.ph, 0
+gov.pk, 0
+gov.pl, 0
+gov.pn, 0
+gov.pr, 0
+gov.ps, 0
+gov.pt, 0
+gov.py, 0
+gov.qa, 0
+gov.rs, 0
+gov.ru, 0
+gov.rw, 0
+gov.sa, 0
+gov.sb, 0
+gov.sc, 0
+gov.sd, 0
+gov.sg, 0
+gov.sh, 0
+gov.sl, 0
+gov.st, 0
+gov.sx, 0
+gov.sy, 0
+gov.tj, 0
+gov.tl, 0
+gov.tm, 0
+gov.tn, 0
+gov.to, 0
+gov.tt, 0
+gov.tw, 0
+gov.ua, 0
+gov.vc, 0
+gov.ve, 0
+gov.vn, 0
+gov.ws, 0
+government.aero, 0
+gp, 0
+gq, 0
+gr, 0
+gr.com, 4
+gr.it, 0
+gr.jp, 0
+grajewo.pl, 0
+gran.no, 0
+grandrapids.museum, 0
+grane.no, 0
+granvin.no, 0
+gratangen.no, 0
+graz.museum, 0
+greta.fr, 0
+grimstad.no, 0
+groks-the.info, 4
+groks-this.info, 4
+grong.no, 0
+grosseto.it, 0
+groundhandling.aero, 0
+group.aero, 0
+grozny.ru, 0
+grp.lk, 0
+grue.no, 0
+gs, 0
+gs.aa.no, 0
+gs.ah.no, 0
+gs.bu.no, 0
+gs.cn, 0
+gs.fm.no, 0
+gs.hl.no, 0
+gs.hm.no, 0
+gs.jan-mayen.no, 0
+gs.mr.no, 0
+gs.nl.no, 0
+gs.nt.no, 0
+gs.of.no, 0
+gs.ol.no, 0
+gs.oslo.no, 0
+gs.rl.no, 0
+gs.sf.no, 0
+gs.st.no, 0
+gs.svalbard.no, 0
+gs.tm.no, 0
+gs.tr.no, 0
+gs.va.no, 0
+gs.vf.no, 0
+gsm.pl, 0
+gt, 0
+gu, 2
+gu.us, 0
+gub.uy, 0
+guernsey.museum, 0
+gujo.gifu.jp, 0
+gulen.no, 0
+gunma.jp, 0
+guovdageaidnu.no, 0
+gushikami.okinawa.jp, 0
+gv.ao, 0
+gv.at, 0
+gw, 0
+gwangju.kr, 0
+gx.cn, 0
+gy, 0
+gyeongbuk.kr, 0
+gyeonggi.kr, 0
+gyeongnam.kr, 0
+gyokuto.kumamoto.jp, 0
+gz.cn, 0
+h.bg, 0
+h.se, 0
+ha.cn, 0
+ha.no, 0
+habikino.osaka.jp, 0
+habmer.no, 0
+haboro.hokkaido.jp, 0
+hachijo.tokyo.jp, 0
+hachinohe.aomori.jp, 0
+hachioji.tokyo.jp, 0
+hachirogata.akita.jp, 0
+hadano.kanagawa.jp, 0
+hadsel.no, 0
+haebaru.okinawa.jp, 0
+haga.tochigi.jp, 0
+hagebostad.no, 0
+hagi.yamaguchi.jp, 0
+haibara.shizuoka.jp, 0
+hakata.fukuoka.jp, 0
+hakodate.hokkaido.jp, 0
+hakone.kanagawa.jp, 0
+hakuba.nagano.jp, 0
+hakui.ishikawa.jp, 0
+hakusan.ishikawa.jp, 0
+halden.no, 0
+halloffame.museum, 0
+halsa.no, 0
+ham-radio-op.net, 4
+hamada.shimane.jp, 0
+hamamatsu.shizuoka.jp, 0
+hamar.no, 0
+hamaroy.no, 0
+hamatama.saga.jp, 0
+hamatonbetsu.hokkaido.jp, 0
+hamburg.museum, 0
+hammarfeasta.no, 0
+hammerfest.no, 0
+hamura.tokyo.jp, 0
+hanamaki.iwate.jp, 0
+hanamigawa.chiba.jp, 0
+hanawa.fukushima.jp, 0
+handa.aichi.jp, 0
+handson.museum, 0
+hanggliding.aero, 0
+hannan.osaka.jp, 0
+hanno.saitama.jp, 0
+hanyu.saitama.jp, 0
+hapmir.no, 0
+happou.akita.jp, 0
+hara.nagano.jp, 0
+haram.no, 0
+hareid.no, 0
+harima.hyogo.jp, 0
+harstad.no, 0
+harvestcelebration.museum, 0
+hasama.oita.jp, 0
+hasami.nagasaki.jp, 0
+hashikami.aomori.jp, 0
+hashima.gifu.jp, 0
+hashimoto.wakayama.jp, 0
+hasuda.saitama.jp, 0
+hasvik.no, 0
+hatogaya.saitama.jp, 0
+hatoyama.saitama.jp, 0
+hatsukaichi.hiroshima.jp, 0
+hattfjelldal.no, 0
+haugesund.no, 0
+hawaii.museum, 0
+hayakawa.yamanashi.jp, 0
+hayashima.okayama.jp, 0
+hazu.aichi.jp, 0
+hb.cn, 0
+he.cn, 0
+health.museum, 0
+health.vn, 0
+heguri.nara.jp, 0
+heimatunduhren.museum, 0
+hekinan.aichi.jp, 0
+hellas.museum, 0
+helsinki.museum, 0
+hembygdsforbund.museum, 0
+hemne.no, 0
+hemnes.no, 0
+hemsedal.no, 0
+herad.no, 0
+here-for-more.info, 4
+heritage.museum, 0
+heroy.more-og-romsdal.no, 0
+heroy.nordland.no, 0
+hi.cn, 0
+hi.us, 0
+hichiso.gifu.jp, 0
+hida.gifu.jp, 0
+hidaka.hokkaido.jp, 0
+hidaka.kochi.jp, 0
+hidaka.saitama.jp, 0
+hidaka.wakayama.jp, 0
+higashi.fukuoka.jp, 0
+higashi.fukushima.jp, 0
+higashi.okinawa.jp, 0
+higashiagatsuma.gunma.jp, 0
+higashichichibu.saitama.jp, 0
+higashihiroshima.hiroshima.jp, 0
+higashiizu.shizuoka.jp, 0
+higashiizumo.shimane.jp, 0
+higashikagawa.kagawa.jp, 0
+higashikagura.hokkaido.jp, 0
+higashikawa.hokkaido.jp, 0
+higashikurume.tokyo.jp, 0
+higashimatsushima.miyagi.jp, 0
+higashimatsuyama.saitama.jp, 0
+higashimurayama.tokyo.jp, 0
+higashinaruse.akita.jp, 0
+higashine.yamagata.jp, 0
+higashiomi.shiga.jp, 0
+higashiosaka.osaka.jp, 0
+higashishirakawa.gifu.jp, 0
+higashisumiyoshi.osaka.jp, 0
+higashitsuno.kochi.jp, 0
+higashiura.aichi.jp, 0
+higashiyama.kyoto.jp, 0
+higashiyamato.tokyo.jp, 0
+higashiyodogawa.osaka.jp, 0
+higashiyoshino.nara.jp, 0
+hiji.oita.jp, 0
+hikari.yamaguchi.jp, 0
+hikawa.shimane.jp, 0
+hikimi.shimane.jp, 0
+hikone.shiga.jp, 0
+himeji.hyogo.jp, 0
+himeshima.oita.jp, 0
+himi.toyama.jp, 0
+hino.tokyo.jp, 0
+hino.tottori.jp, 0
+hinode.tokyo.jp, 0
+hinohara.tokyo.jp, 0
+hioki.kagoshima.jp, 0
+hirado.nagasaki.jp, 0
+hiraizumi.iwate.jp, 0
+hirakata.osaka.jp, 0
+hiranai.aomori.jp, 0
+hirara.okinawa.jp, 0
+hirata.fukushima.jp, 0
+hiratsuka.kanagawa.jp, 0
+hiraya.nagano.jp, 0
+hirogawa.wakayama.jp, 0
+hirokawa.fukuoka.jp, 0
+hirono.fukushima.jp, 0
+hirono.iwate.jp, 0
+hiroo.hokkaido.jp, 0
+hirosaki.aomori.jp, 0
+hiroshima.jp, 0
+hisayama.fukuoka.jp, 0
+histoire.museum, 0
+historical.museum, 0
+historicalsociety.museum, 0
+historichouses.museum, 0
+historisch.museum, 0
+historisches.museum, 0
+history.museum, 0
+historyofscience.museum, 0
+hita.oita.jp, 0
+hitachi.ibaraki.jp, 0
+hitachinaka.ibaraki.jp, 0
+hitachiomiya.ibaraki.jp, 0
+hitachiota.ibaraki.jp, 0
+hitoyoshi.kumamoto.jp, 0
+hitra.no, 0
+hizen.saga.jp, 0
+hjartdal.no, 0
+hjelmeland.no, 0
+hk, 0
+hk.cn, 0
+hl.cn, 0
+hl.no, 0
+hm, 0
+hm.no, 0
+hn, 0
+hn.cn, 0
+hobby-site.com, 4
+hobby-site.org, 4
+hobol.no, 0
+hof.no, 0
+hofu.yamaguchi.jp, 0
+hokkaido.jp, 0
+hokksund.no, 0
+hokuryu.hokkaido.jp, 0
+hokuto.hokkaido.jp, 0
+hokuto.yamanashi.jp, 0
+hol.no, 0
+hole.no, 0
+holmestrand.no, 0
+holtalen.no, 0
+home.dyndns.org, 4
+homebuilt.aero, 0
+homedns.org, 4
+homeftp.net, 4
+homeftp.org, 4
+homeip.net, 4
+homelinux.com, 4
+homelinux.net, 4
+homelinux.org, 4
+homeunix.com, 4
+homeunix.net, 4
+homeunix.org, 4
+honai.ehime.jp, 0
+honbetsu.hokkaido.jp, 0
+honefoss.no, 0
+hongo.hiroshima.jp, 0
+honjo.akita.jp, 0
+honjo.saitama.jp, 0
+honjyo.akita.jp, 0
+hornindal.no, 0
+horokanai.hokkaido.jp, 0
+horology.museum, 0
+horonobe.hokkaido.jp, 0
+horten.no, 0
+hotel.hu, 0
+hotel.lk, 0
+hotel.tz, 0
+house.museum, 0
+hoyanger.no, 0
+hoylandet.no, 0
+hr, 0
+hs.kr, 0
+ht, 0
+hu, 0
+hu.com, 4
+hu.net, 4
+huissier-justice.fr, 0
+humanities.museum, 0
+hurdal.no, 0
+hurum.no, 0
+hvaler.no, 0
+hyllestad.no, 0
+hyogo.jp, 0
+hyuga.miyazaki.jp, 0
+i.bg, 0
+i.ph, 0
+i.se, 0
+ia.us, 0
+iamallama.com, 4
+ibara.okayama.jp, 0
+ibaraki.ibaraki.jp, 0
+ibaraki.jp, 0
+ibaraki.osaka.jp, 0
+ibestad.no, 0
+ibigawa.gifu.jp, 0
+ichiba.tokushima.jp, 0
+ichihara.chiba.jp, 0
+ichikai.tochigi.jp, 0
+ichikawa.chiba.jp, 0
+ichikawa.hyogo.jp, 0
+ichikawamisato.yamanashi.jp, 0
+ichinohe.iwate.jp, 0
+ichinomiya.aichi.jp, 0
+ichinomiya.chiba.jp, 0
+ichinoseki.iwate.jp, 0
+id, 0
+id.au, 0
+id.ir, 0
+id.lv, 0
+id.ly, 0
+id.us, 0
+ide.kyoto.jp, 0
+idrett.no, 0
+idv.hk, 0
+idv.tw, 0
+ie, 0
+if.ua, 0
+iglesias-carbonia.it, 0
+iglesiascarbonia.it, 0
+iheya.okinawa.jp, 0
+iida.nagano.jp, 0
+iide.yamagata.jp, 0
+iijima.nagano.jp, 0
+iitate.fukushima.jp, 0
+iiyama.nagano.jp, 0
+iizuka.fukuoka.jp, 0
+iizuna.nagano.jp, 0
+ikaruga.nara.jp, 0
+ikata.ehime.jp, 0
+ikawa.akita.jp, 0
+ikeda.fukui.jp, 0
+ikeda.gifu.jp, 0
+ikeda.hokkaido.jp, 0
+ikeda.nagano.jp, 0
+ikeda.osaka.jp, 0
+iki.fi, 4
+iki.nagasaki.jp, 0
+ikoma.nara.jp, 0
+ikusaka.nagano.jp, 0
+il, 2
+il.us, 0
+ilawa.pl, 0
+illustration.museum, 0
+im, 0
+im.it, 0
+imabari.ehime.jp, 0
+imageandsound.museum, 0
+imakane.hokkaido.jp, 0
+imari.saga.jp, 0
+imb.br, 0
+imizu.toyama.jp, 0
+imperia.it, 0
+in, 0
+in-addr.arpa, 0
+in-the-band.net, 4
+in.na, 0
+in.rs, 0
+in.th, 0
+in.ua, 0
+in.us, 0
+ina.ibaraki.jp, 0
+ina.nagano.jp, 0
+ina.saitama.jp, 0
+inabe.mie.jp, 0
+inagawa.hyogo.jp, 0
+inagi.tokyo.jp, 0
+inami.toyama.jp, 0
+inami.wakayama.jp, 0
+inashiki.ibaraki.jp, 0
+inatsuki.fukuoka.jp, 0
+inawashiro.fukushima.jp, 0
+inazawa.aichi.jp, 0
+incheon.kr, 0
+ind.br, 0
+ind.gt, 0
+ind.in, 0
+ind.tn, 0
+inderoy.no, 0
+indian.museum, 0
+indiana.museum, 0
+indianapolis.museum, 0
+indianmarket.museum, 0
+ine.kyoto.jp, 0
+inf.br, 0
+inf.cu, 0
+inf.mk, 0
+info, 0
+info.at, 4
+info.au, 0
+info.az, 0
+info.bb, 0
+info.co, 0
+info.ec, 0
+info.ht, 0
+info.hu, 0
+info.ki, 0
+info.la, 0
+info.mv, 0
+info.na, 0
+info.nf, 0
+info.nr, 0
+info.pk, 0
+info.pl, 0
+info.pr, 0
+info.ro, 0
+info.sd, 0
+info.tn, 0
+info.tt, 0
+info.tz, 0
+info.ve, 0
+info.vn, 0
+ing.pa, 0
+ingatlan.hu, 0
+ino.kochi.jp, 0
+insurance.aero, 0
+int, 0
+int.az, 0
+int.bo, 0
+int.ci, 0
+int.co, 0
+int.is, 0
+int.la, 0
+int.lk, 0
+int.mv, 0
+int.mw, 0
+int.pt, 0
+int.ru, 0
+int.rw, 0
+int.tj, 0
+int.tt, 0
+int.vn, 0
+intelligence.museum, 0
+interactive.museum, 0
+intl.tn, 0
+inuyama.aichi.jp, 0
+inzai.chiba.jp, 0
+io, 0
+ip6.arpa, 0
+iq, 0
+ir, 0
+iraq.museum, 0
+irc.pl, 0
+iris.arpa, 0
+irkutsk.ru, 0
+iron.museum, 0
+iruma.saitama.jp, 0
+is, 0
+is-a-anarchist.com, 4
+is-a-blogger.com, 4
+is-a-bookkeeper.com, 4
+is-a-bruinsfan.org, 4
+is-a-bulls-fan.com, 4
+is-a-candidate.org, 4
+is-a-caterer.com, 4
+is-a-celticsfan.org, 4
+is-a-chef.com, 4
+is-a-chef.net, 4
+is-a-chef.org, 4
+is-a-conservative.com, 4
+is-a-cpa.com, 4
+is-a-cubicle-slave.com, 4
+is-a-democrat.com, 4
+is-a-designer.com, 4
+is-a-doctor.com, 4
+is-a-financialadvisor.com, 4
+is-a-geek.com, 4
+is-a-geek.net, 4
+is-a-geek.org, 4
+is-a-green.com, 4
+is-a-guru.com, 4
+is-a-hard-worker.com, 4
+is-a-hunter.com, 4
+is-a-knight.org, 4
+is-a-landscaper.com, 4
+is-a-lawyer.com, 4
+is-a-liberal.com, 4
+is-a-libertarian.com, 4
+is-a-linux-user.org, 4
+is-a-llama.com, 4
+is-a-musician.com, 4
+is-a-nascarfan.com, 4
+is-a-nurse.com, 4
+is-a-painter.com, 4
+is-a-patsfan.org, 4
+is-a-personaltrainer.com, 4
+is-a-photographer.com, 4
+is-a-player.com, 4
+is-a-republican.com, 4
+is-a-rockstar.com, 4
+is-a-socialist.com, 4
+is-a-soxfan.org, 4
+is-a-student.com, 4
+is-a-teacher.com, 4
+is-a-techie.com, 4
+is-a-therapist.com, 4
+is-an-accountant.com, 4
+is-an-actor.com, 4
+is-an-actress.com, 4
+is-an-anarchist.com, 4
+is-an-artist.com, 4
+is-an-engineer.com, 4
+is-an-entertainer.com, 4
+is-by.us, 4
+is-certified.com, 4
+is-found.org, 4
+is-gone.com, 4
+is-into-anime.com, 4
+is-into-cars.com, 4
+is-into-cartoons.com, 4
+is-into-games.com, 4
+is-leet.com, 4
+is-lost.org, 4
+is-not-certified.com, 4
+is-saved.org, 4
+is-slick.com, 4
+is-uberleet.com, 4
+is-very-bad.org, 4
+is-very-evil.org, 4
+is-very-good.org, 4
+is-very-nice.org, 4
+is-very-sweet.org, 4
+is-with-theband.com, 4
+is.it, 0
+isa-geek.com, 4
+isa-geek.net, 4
+isa-geek.org, 4
+isa-hockeynut.com, 4
+isa.kagoshima.jp, 0
+isa.us, 0
+isahaya.nagasaki.jp, 0
+ise.mie.jp, 0
+isehara.kanagawa.jp, 0
+isen.kagoshima.jp, 0
+isernia.it, 0
+isesaki.gunma.jp, 0
+ishigaki.okinawa.jp, 0
+ishikari.hokkaido.jp, 0
+ishikawa.fukushima.jp, 0
+ishikawa.jp, 0
+ishikawa.okinawa.jp, 0
+ishinomaki.miyagi.jp, 0
+isla.pr, 0
+isleofman.museum, 0
+isshiki.aichi.jp, 0
+issmarterthanyou.com, 4
+isteingeek.de, 4
+istmein.de, 4
+isumi.chiba.jp, 0
+it, 0
+it.ao, 0
+itabashi.tokyo.jp, 0
+itako.ibaraki.jp, 0
+itakura.gunma.jp, 0
+itami.hyogo.jp, 0
+itano.tokushima.jp, 0
+itayanagi.aomori.jp, 0
+ito.shizuoka.jp, 0
+itoigawa.niigata.jp, 0
+itoman.okinawa.jp, 0
+its.me, 0
+ivano-frankivsk.ua, 0
+ivanovo.ru, 0
+iveland.no, 0
+ivgu.no, 0
+iwade.wakayama.jp, 0
+iwafune.tochigi.jp, 0
+iwaizumi.iwate.jp, 0
+iwaki.fukushima.jp, 0
+iwakuni.yamaguchi.jp, 0
+iwakura.aichi.jp, 0
+iwama.ibaraki.jp, 0
+iwamizawa.hokkaido.jp, 0
+iwanai.hokkaido.jp, 0
+iwanuma.miyagi.jp, 0
+iwata.shizuoka.jp, 0
+iwate.iwate.jp, 0
+iwate.jp, 0
+iwatsuki.saitama.jp, 0
+iyo.ehime.jp, 0
+iz.hr, 0
+izena.okinawa.jp, 0
+izhevsk.ru, 0
+izu.shizuoka.jp, 0
+izumi.kagoshima.jp, 0
+izumi.osaka.jp, 0
+izumiotsu.osaka.jp, 0
+izumisano.osaka.jp, 0
+izumizaki.fukushima.jp, 0
+izumo.shimane.jp, 0
+izumozaki.niigata.jp, 0
+izunokuni.shizuoka.jp, 0
+j.bg, 0
+jamal.ru, 0
+jamison.museum, 0
+jan-mayen.no, 0
+jar.ru, 0
+jaworzno.pl, 0
+je, 0
+jefferson.museum, 0
+jeju.kr, 0
+jelenia-gora.pl, 0
+jeonbuk.kr, 0
+jeonnam.kr, 0
+jerusalem.museum, 0
+jessheim.no, 0
+jet.uk, 1
+jevnaker.no, 0
+jewelry.museum, 0
+jewish.museum, 0
+jewishart.museum, 0
+jfk.museum, 0
+jgora.pl, 0
+jinsekikogen.hiroshima.jp, 0
+jl.cn, 0
+jm, 2
+jo, 0
+joboji.iwate.jp, 0
+jobs, 0
+jobs.tt, 0
+joetsu.niigata.jp, 0
+jogasz.hu, 0
+johana.toyama.jp, 0
+jolster.no, 0
+jondal.no, 0
+jor.br, 0
+jorpeland.no, 0
+joshkar-ola.ru, 0
+joso.ibaraki.jp, 0
+journal.aero, 0
+journalism.museum, 0
+journalist.aero, 0
+joyo.kyoto.jp, 0
+jp, 0
+jp.net, 4
+jpn.com, 4
+js.cn, 0
+judaica.museum, 0
+judygarland.museum, 0
+juedisches.museum, 0
+juif.museum, 0
+jur.pro, 0
+jus.br, 0
+jx.cn, 0
+k-uralsk.ru, 0
+k.bg, 0
+k.se, 0
+k12.ak.us, 0
+k12.al.us, 0
+k12.ar.us, 0
+k12.as.us, 0
+k12.az.us, 0
+k12.ca.us, 0
+k12.co.us, 0
+k12.ct.us, 0
+k12.dc.us, 0
+k12.de.us, 0
+k12.ec, 0
+k12.fl.us, 0
+k12.ga.us, 0
+k12.gu.us, 0
+k12.ia.us, 0
+k12.id.us, 0
+k12.il.us, 0
+k12.in.us, 0
+k12.ks.us, 0
+k12.ky.us, 0
+k12.la.us, 0
+k12.ma.us, 0
+k12.md.us, 0
+k12.me.us, 0
+k12.mi.us, 0
+k12.mn.us, 0
+k12.mo.us, 0
+k12.ms.us, 0
+k12.mt.us, 0
+k12.nc.us, 0
+k12.nd.us, 0
+k12.ne.us, 0
+k12.nh.us, 0
+k12.nj.us, 0
+k12.nm.us, 0
+k12.nv.us, 0
+k12.ny.us, 0
+k12.oh.us, 0
+k12.ok.us, 0
+k12.or.us, 0
+k12.pa.us, 0
+k12.pr.us, 0
+k12.ri.us, 0
+k12.sc.us, 0
+k12.sd.us, 0
+k12.tn.us, 0
+k12.tx.us, 0
+k12.ut.us, 0
+k12.va.us, 0
+k12.vi, 0
+k12.vi.us, 0
+k12.vt.us, 0
+k12.wa.us, 0
+k12.wi.us, 0
+k12.wv.us, 0
+k12.wy.us, 0
+kadena.okinawa.jp, 0
+kadogawa.miyazaki.jp, 0
+kadoma.osaka.jp, 0
+kafjord.no, 0
+kaga.ishikawa.jp, 0
+kagami.kochi.jp, 0
+kagamiishi.fukushima.jp, 0
+kagamino.okayama.jp, 0
+kagawa.jp, 0
+kagoshima.jp, 0
+kagoshima.kagoshima.jp, 0
+kaho.fukuoka.jp, 0
+kahoku.ishikawa.jp, 0
+kahoku.yamagata.jp, 0
+kai.yamanashi.jp, 0
+kainan.tokushima.jp, 0
+kainan.wakayama.jp, 0
+kaisei.kanagawa.jp, 0
+kaita.hiroshima.jp, 0
+kaizuka.osaka.jp, 0
+kakamigahara.gifu.jp, 0
+kakegawa.shizuoka.jp, 0
+kakinoki.shimane.jp, 0
+kakogawa.hyogo.jp, 0
+kakuda.miyagi.jp, 0
+kalisz.pl, 0
+kalmykia.ru, 0
+kaluga.ru, 0
+kamagaya.chiba.jp, 0
+kamaishi.iwate.jp, 0
+kamakura.kanagawa.jp, 0
+kamchatka.ru, 0
+kameoka.kyoto.jp, 0
+kameyama.mie.jp, 0
+kami.kochi.jp, 0
+kami.miyagi.jp, 0
+kamiamakusa.kumamoto.jp, 0
+kamifurano.hokkaido.jp, 0
+kamigori.hyogo.jp, 0
+kamiichi.toyama.jp, 0
+kamiizumi.saitama.jp, 0
+kamijima.ehime.jp, 0
+kamikawa.hokkaido.jp, 0
+kamikawa.hyogo.jp, 0
+kamikawa.saitama.jp, 0
+kamikitayama.nara.jp, 0
+kamikoani.akita.jp, 0
+kamimine.saga.jp, 0
+kaminokawa.tochigi.jp, 0
+kaminoyama.yamagata.jp, 0
+kamioka.akita.jp, 0
+kamisato.saitama.jp, 0
+kamishihoro.hokkaido.jp, 0
+kamisu.ibaraki.jp, 0
+kamisunagawa.hokkaido.jp, 0
+kamitonda.wakayama.jp, 0
+kamitsue.oita.jp, 0
+kamo.kyoto.jp, 0
+kamo.niigata.jp, 0
+kamoenai.hokkaido.jp, 0
+kamogawa.chiba.jp, 0
+kanagawa.jp, 0
+kanan.osaka.jp, 0
+kanazawa.ishikawa.jp, 0
+kanegasaki.iwate.jp, 0
+kaneyama.fukushima.jp, 0
+kaneyama.yamagata.jp, 0
+kani.gifu.jp, 0
+kanie.aichi.jp, 0
+kanmaki.nara.jp, 0
+kanna.gunma.jp, 0
+kannami.shizuoka.jp, 0
+kanonji.kagawa.jp, 0
+kanoya.kagoshima.jp, 0
+kanra.gunma.jp, 0
+kanuma.tochigi.jp, 0
+kanzaki.saga.jp, 0
+karasjohka.no, 0
+karasjok.no, 0
+karasuyama.tochigi.jp, 0
+karate.museum, 0
+karatsu.saga.jp, 0
+karelia.ru, 0
+karikatur.museum, 0
+kariwa.niigata.jp, 0
+kariya.aichi.jp, 0
+karlsoy.no, 0
+karmoy.no, 0
+karpacz.pl, 0
+kartuzy.pl, 0
+karuizawa.nagano.jp, 0
+karumai.iwate.jp, 0
+kasahara.gifu.jp, 0
+kasai.hyogo.jp, 0
+kasama.ibaraki.jp, 0
+kasamatsu.gifu.jp, 0
+kasaoka.okayama.jp, 0
+kashiba.nara.jp, 0
+kashihara.nara.jp, 0
+kashima.ibaraki.jp, 0
+kashima.kumamoto.jp, 0
+kashima.saga.jp, 0
+kashiwa.chiba.jp, 0
+kashiwara.osaka.jp, 0
+kashiwazaki.niigata.jp, 0
+kasuga.fukuoka.jp, 0
+kasuga.hyogo.jp, 0
+kasugai.aichi.jp, 0
+kasukabe.saitama.jp, 0
+kasumigaura.ibaraki.jp, 0
+kasuya.fukuoka.jp, 0
+kaszuby.pl, 0
+katagami.akita.jp, 0
+katano.osaka.jp, 0
+katashina.gunma.jp, 0
+katori.chiba.jp, 0
+katowice.pl, 0
+katsuragi.nara.jp, 0
+katsuragi.wakayama.jp, 0
+katsushika.tokyo.jp, 0
+katsuura.chiba.jp, 0
+katsuyama.fukui.jp, 0
+kautokeino.no, 0
+kawaba.gunma.jp, 0
+kawachinagano.osaka.jp, 0
+kawagoe.mie.jp, 0
+kawagoe.saitama.jp, 0
+kawaguchi.saitama.jp, 0
+kawahara.tottori.jp, 0
+kawai.iwate.jp, 0
+kawai.nara.jp, 0
+kawajima.saitama.jp, 0
+kawakami.nagano.jp, 0
+kawakami.nara.jp, 0
+kawakita.ishikawa.jp, 0
+kawamata.fukushima.jp, 0
+kawaminami.miyazaki.jp, 0
+kawanabe.kagoshima.jp, 0
+kawanehon.shizuoka.jp, 0
+kawanishi.hyogo.jp, 0
+kawanishi.nara.jp, 0
+kawanishi.yamagata.jp, 0
+kawara.fukuoka.jp, 0
+kawasaki.jp, 2
+kawasaki.miyagi.jp, 0
+kawatana.nagasaki.jp, 0
+kawaue.gifu.jp, 0
+kawazu.shizuoka.jp, 0
+kayabe.hokkaido.jp, 0
+kazan.ru, 0
+kazimierz-dolny.pl, 0
+kazo.saitama.jp, 0
+kazuno.akita.jp, 0
+kchr.ru, 0
+ke, 2
+keisen.fukuoka.jp, 0
+kembuchi.hokkaido.jp, 0
+kemerovo.ru, 0
+kepno.pl, 0
+kesennuma.miyagi.jp, 0
+ketrzyn.pl, 0
+kg, 0
+kg.kr, 0
+kh, 2
+kh.ua, 0
+khabarovsk.ru, 0
+khakassia.ru, 0
+kharkiv.ua, 0
+kharkov.ua, 0
+kherson.ua, 0
+khmelnitskiy.ua, 0
+khmelnytskyi.ua, 0
+khv.ru, 0
+ki, 0
+kibichuo.okayama.jp, 0
+kicks-ass.net, 4
+kicks-ass.org, 4
+kids.museum, 0
+kids.us, 0
+kiev.ua, 0
+kiho.mie.jp, 0
+kihoku.ehime.jp, 0
+kijo.miyazaki.jp, 0
+kikonai.hokkaido.jp, 0
+kikuchi.kumamoto.jp, 0
+kikugawa.shizuoka.jp, 0
+kimino.wakayama.jp, 0
+kimitsu.chiba.jp, 0
+kimobetsu.hokkaido.jp, 0
+kin.okinawa.jp, 0
+kinko.kagoshima.jp, 0
+kinokawa.wakayama.jp, 0
+kira.aichi.jp, 0
+kirkenes.no, 0
+kirov.ru, 0
+kirovograd.ua, 0
+kiryu.gunma.jp, 0
+kisarazu.chiba.jp, 0
+kishiwada.osaka.jp, 0
+kiso.nagano.jp, 0
+kisofukushima.nagano.jp, 0
+kisosaki.mie.jp, 0
+kita.kyoto.jp, 0
+kita.osaka.jp, 0
+kita.tokyo.jp, 0
+kitaaiki.nagano.jp, 0
+kitaakita.akita.jp, 0
+kitadaito.okinawa.jp, 0
+kitagata.gifu.jp, 0
+kitagata.saga.jp, 0
+kitagawa.kochi.jp, 0
+kitagawa.miyazaki.jp, 0
+kitahata.saga.jp, 0
+kitahiroshima.hokkaido.jp, 0
+kitakami.iwate.jp, 0
+kitakata.fukushima.jp, 0
+kitakata.miyazaki.jp, 0
+kitakyushu.jp, 2
+kitami.hokkaido.jp, 0
+kitamoto.saitama.jp, 0
+kitanakagusuku.okinawa.jp, 0
+kitashiobara.fukushima.jp, 0
+kitaura.miyazaki.jp, 0
+kitayama.wakayama.jp, 0
+kiwa.mie.jp, 0
+kiyama.saga.jp, 0
+kiyokawa.kanagawa.jp, 0
+kiyosato.hokkaido.jp, 0
+kiyose.tokyo.jp, 0
+kiyosu.aichi.jp, 0
+kizu.kyoto.jp, 0
+klabu.no, 0
+klepp.no, 0
+klodzko.pl, 0
+km, 0
+km.ua, 0
+kms.ru, 0
+kn, 0
+knowsitall.info, 4
+kobayashi.miyazaki.jp, 0
+kobe.jp, 2
+kobierzyce.pl, 0
+kochi.jp, 0
+kochi.kochi.jp, 0
+kodaira.tokyo.jp, 0
+koebenhavn.museum, 0
+koeln.museum, 0
+koenig.ru, 0
+kofu.yamanashi.jp, 0
+koga.fukuoka.jp, 0
+koga.ibaraki.jp, 0
+koganei.tokyo.jp, 0
+koge.tottori.jp, 0
+koka.shiga.jp, 0
+kokonoe.oita.jp, 0
+kokubunji.tokyo.jp, 0
+kolobrzeg.pl, 0
+komae.tokyo.jp, 0
+komagane.nagano.jp, 0
+komaki.aichi.jp, 0
+komatsu.ishikawa.jp, 0
+komatsushima.tokushima.jp, 0
+komforb.se, 0
+komi.ru, 0
+kommunalforbund.se, 0
+kommune.no, 0
+komono.mie.jp, 0
+komoro.nagano.jp, 0
+komvux.se, 0
+konan.aichi.jp, 0
+konan.shiga.jp, 0
+kongsberg.no, 0
+kongsvinger.no, 0
+konin.pl, 0
+konskowola.pl, 0
+konyvelo.hu, 0
+koori.fukushima.jp, 0
+kopervik.no, 0
+koriyama.fukushima.jp, 0
+koryo.nara.jp, 0
+kosa.kumamoto.jp, 0
+kosai.shizuoka.jp, 0
+kosaka.akita.jp, 0
+kosei.shiga.jp, 0
+koshigaya.saitama.jp, 0
+koshimizu.hokkaido.jp, 0
+koshu.yamanashi.jp, 0
+kostroma.ru, 0
+kosuge.yamanashi.jp, 0
+kota.aichi.jp, 0
+koto.shiga.jp, 0
+koto.tokyo.jp, 0
+kotohira.kagawa.jp, 0
+kotoura.tottori.jp, 0
+kouhoku.saga.jp, 0
+kounosu.saitama.jp, 0
+kouyama.kagoshima.jp, 0
+kouzushima.tokyo.jp, 0
+koya.wakayama.jp, 0
+koza.wakayama.jp, 0
+kozagawa.wakayama.jp, 0
+kozaki.chiba.jp, 0
+kp, 0
+kr, 0
+kr.com, 4
+kr.it, 0
+kr.ua, 0
+kraanghke.no, 0
+kragero.no, 0
+krakow.pl, 0
+krasnoyarsk.ru, 0
+kristiansand.no, 0
+kristiansund.no, 0
+krodsherad.no, 0
+krokstadelva.no, 0
+krym.ua, 0
+ks.ua, 0
+ks.us, 0
+kuban.ru, 0
+kuchinotsu.nagasaki.jp, 0
+kudamatsu.yamaguchi.jp, 0
+kudoyama.wakayama.jp, 0
+kui.hiroshima.jp, 0
+kuji.iwate.jp, 0
+kuju.oita.jp, 0
+kujukuri.chiba.jp, 0
+kuki.saitama.jp, 0
+kumagaya.saitama.jp, 0
+kumakogen.ehime.jp, 0
+kumamoto.jp, 0
+kumamoto.kumamoto.jp, 0
+kumano.hiroshima.jp, 0
+kumano.mie.jp, 0
+kumatori.osaka.jp, 0
+kumejima.okinawa.jp, 0
+kumenan.okayama.jp, 0
+kumiyama.kyoto.jp, 0
+kunigami.okinawa.jp, 0
+kunimi.fukushima.jp, 0
+kunisaki.oita.jp, 0
+kunitachi.tokyo.jp, 0
+kunitomi.miyazaki.jp, 0
+kunneppu.hokkaido.jp, 0
+kunohe.iwate.jp, 0
+kunst.museum, 0
+kunstsammlung.museum, 0
+kunstunddesign.museum, 0
+kurashiki.okayama.jp, 0
+kurate.fukuoka.jp, 0
+kure.hiroshima.jp, 0
+kurgan.ru, 0
+kuriyama.hokkaido.jp, 0
+kurobe.toyama.jp, 0
+kurogi.fukuoka.jp, 0
+kuroishi.aomori.jp, 0
+kuroiso.tochigi.jp, 0
+kuromatsunai.hokkaido.jp, 0
+kurotaki.nara.jp, 0
+kursk.ru, 0
+kurume.fukuoka.jp, 0
+kusatsu.gunma.jp, 0
+kusatsu.shiga.jp, 0
+kushima.miyazaki.jp, 0
+kushimoto.wakayama.jp, 0
+kushiro.hokkaido.jp, 0
+kustanai.ru, 0
+kusu.oita.jp, 0
+kutchan.hokkaido.jp, 0
+kutno.pl, 0
+kuwana.mie.jp, 0
+kuzbass.ru, 0
+kuzumaki.iwate.jp, 0
+kv.ua, 0
+kvafjord.no, 0
+kvalsund.no, 0
+kvam.no, 0
+kvanangen.no, 0
+kvinesdal.no, 0
+kvinnherad.no, 0
+kviteseid.no, 0
+kvitsoy.no, 0
+kw, 2
+ky, 0
+ky.us, 0
+kyiv.ua, 0
+kyonan.chiba.jp, 0
+kyotamba.kyoto.jp, 0
+kyotanabe.kyoto.jp, 0
+kyotango.kyoto.jp, 0
+kyoto.jp, 0
+kyowa.akita.jp, 0
+kyowa.hokkaido.jp, 0
+kyuragi.saga.jp, 0
+kz, 0
+l.bg, 0
+l.se, 0
+la, 0
+la-spezia.it, 0
+la.us, 0
+laakesvuemie.no, 0
+labor.museum, 0
+labour.museum, 0
+lahppi.no, 0
+lajolla.museum, 0
+lakas.hu, 0
+lanbib.se, 0
+lancashire.museum, 0
+land-4-sale.us, 4
+landes.museum, 0
+langevag.no, 0
+lans.museum, 0
+lapy.pl, 0
+laquila.it, 0
+lardal.no, 0
+larsson.museum, 0
+larvik.no, 0
+laspezia.it, 0
+latina.it, 0
+lavagis.no, 0
+lavangen.no, 0
+law.pro, 0
+lb, 0
+lc, 0
+lc.it, 0
+le.it, 0
+leangaviika.no, 0
+leasing.aero, 0
+lebesby.no, 0
+lebork.pl, 0
+lebtimnetz.de, 4
+lecce.it, 0
+lecco.it, 0
+leg.br, 0
+legnica.pl, 0
+leikanger.no, 0
+leirfjord.no, 0
+leirvik.no, 0
+leitungsen.de, 4
+leka.no, 0
+leksvik.no, 0
+lel.br, 0
+lenvik.no, 0
+lerdal.no, 0
+lesja.no, 0
+levanger.no, 0
+lewismiller.museum, 0
+lezajsk.pl, 0
+lg.jp, 0
+lg.ua, 0
+li, 0
+li.it, 0
+lib.ak.us, 0
+lib.al.us, 0
+lib.ar.us, 0
+lib.as.us, 0
+lib.az.us, 0
+lib.ca.us, 0
+lib.co.us, 0
+lib.ct.us, 0
+lib.dc.us, 0
+lib.de.us, 0
+lib.ee, 0
+lib.fl.us, 0
+lib.ga.us, 0
+lib.gu.us, 0
+lib.hi.us, 0
+lib.ia.us, 0
+lib.id.us, 0
+lib.il.us, 0
+lib.in.us, 0
+lib.ks.us, 0
+lib.ky.us, 0
+lib.la.us, 0
+lib.ma.us, 0
+lib.md.us, 0
+lib.me.us, 0
+lib.mi.us, 0
+lib.mn.us, 0
+lib.mo.us, 0
+lib.ms.us, 0
+lib.mt.us, 0
+lib.nc.us, 0
+lib.nd.us, 0
+lib.ne.us, 0
+lib.nh.us, 0
+lib.nj.us, 0
+lib.nm.us, 0
+lib.nv.us, 0
+lib.ny.us, 0
+lib.oh.us, 0
+lib.ok.us, 0
+lib.or.us, 0
+lib.pa.us, 0
+lib.pr.us, 0
+lib.ri.us, 0
+lib.sc.us, 0
+lib.sd.us, 0
+lib.tn.us, 0
+lib.tx.us, 0
+lib.ut.us, 0
+lib.va.us, 0
+lib.vi.us, 0
+lib.vt.us, 0
+lib.wa.us, 0
+lib.wi.us, 0
+lib.wv.us, 0
+lib.wy.us, 0
+lier.no, 0
+lierne.no, 0
+likes-pie.com, 4
+likescandy.com, 4
+lillehammer.no, 0
+lillesand.no, 0
+limanowa.pl, 0
+lincoln.museum, 0
+lindas.no, 0
+lindesnes.no, 0
+linz.museum, 0
+lipetsk.ru, 0
+living.museum, 0
+livinghistory.museum, 0
+livorno.it, 0
+lk, 0
+ln.cn, 0
+lo.it, 0
+loabat.no, 0
+localhistory.museum, 0
+lodi.it, 0
+lodingen.no, 0
+logistics.aero, 0
+lom.no, 0
+lomza.pl, 0
+london.museum, 0
+loppa.no, 0
+lorenskog.no, 0
+losangeles.museum, 0
+loten.no, 0
+louvre.museum, 0
+lowicz.pl, 0
+loyalist.museum, 0
+lr, 0
+ls, 0
+lt, 0
+lt.it, 0
+lt.ua, 0
+ltd.co.im, 0
+ltd.gi, 0
+ltd.lk, 0
+lu, 0
+lu.it, 0
+lubin.pl, 0
+lucca.it, 0
+lucerne.museum, 0
+lugansk.ua, 0
+lukow.pl, 0
+lund.no, 0
+lunner.no, 0
+luroy.no, 0
+luster.no, 0
+lutsk.ua, 0
+luxembourg.museum, 0
+luzern.museum, 0
+lv, 0
+lv.ua, 0
+lviv.ua, 0
+ly, 0
+lyngdal.no, 0
+lyngen.no, 0
+m.bg, 0
+m.se, 0
+ma, 0
+ma.us, 0
+macerata.it, 0
+machida.tokyo.jp, 0
+mad.museum, 0
+madrid.museum, 0
+maebashi.gunma.jp, 0
+magadan.ru, 0
+magazine.aero, 0
+magnitka.ru, 0
+maibara.shiga.jp, 0
+mail.pl, 0
+maintenance.aero, 0
+maizuru.kyoto.jp, 0
+makinohara.shizuoka.jp, 0
+makurazaki.kagoshima.jp, 0
+malatvuopmi.no, 0
+malbork.pl, 0
+mallorca.museum, 0
+malopolska.pl, 0
+malselv.no, 0
+malvik.no, 0
+mamurogawa.yamagata.jp, 0
+manchester.museum, 0
+mandal.no, 0
+maniwa.okayama.jp, 0
+manno.kagawa.jp, 0
+mansion.museum, 0
+mansions.museum, 0
+mantova.it, 0
+manx.museum, 0
+marburg.museum, 0
+mari-el.ru, 0
+mari.ru, 0
+marine.ru, 0
+maritime.museum, 0
+maritimo.museum, 0
+marker.no, 0
+marketplace.aero, 0
+marnardal.no, 0
+marugame.kagawa.jp, 0
+marumori.miyagi.jp, 0
+maryland.museum, 0
+marylhurst.museum, 0
+masaki.ehime.jp, 0
+masfjorden.no, 0
+mashike.hokkaido.jp, 0
+mashiki.kumamoto.jp, 0
+mashiko.tochigi.jp, 0
+masoy.no, 0
+massa-carrara.it, 0
+massacarrara.it, 0
+masuda.shimane.jp, 0
+mat.br, 0
+matera.it, 0
+matsubara.osaka.jp, 0
+matsubushi.saitama.jp, 0
+matsuda.kanagawa.jp, 0
+matsudo.chiba.jp, 0
+matsue.shimane.jp, 0
+matsukawa.nagano.jp, 0
+matsumae.hokkaido.jp, 0
+matsumoto.kagoshima.jp, 0
+matsumoto.nagano.jp, 0
+matsuno.ehime.jp, 0
+matsusaka.mie.jp, 0
+matsushige.tokushima.jp, 0
+matsushima.miyagi.jp, 0
+matsuura.nagasaki.jp, 0
+matsuyama.ehime.jp, 0
+matsuzaki.shizuoka.jp, 0
+matta-varjjat.no, 0
+mazowsze.pl, 0
+mazury.pl, 0
+mb.ca, 0
+mb.it, 0
+mbone.pl, 0
+mc, 0
+mc.it, 0
+md, 0
+md.ci, 0
+md.us, 0
+me, 0
+me.it, 0
+me.tz, 0
+me.us, 0
+mecon.ar, 1
+med.br, 0
+med.ec, 0
+med.ee, 0
+med.ht, 0
+med.ly, 0
+med.pa, 0
+med.pl, 0
+med.pro, 0
+med.sa, 0
+med.sd, 0
+medecin.fr, 0
+medecin.km, 0
+media.aero, 0
+media.hu, 0
+media.museum, 0
+media.pl, 0
+mediaphone.om, 1
+medical.museum, 0
+medio-campidano.it, 0
+mediocampidano.it, 0
+medizinhistorisches.museum, 0
+meeres.museum, 0
+meguro.tokyo.jp, 0
+meiwa.gunma.jp, 0
+meiwa.mie.jp, 0
+meland.no, 0
+meldal.no, 0
+melhus.no, 0
+meloy.no, 0
+memorial.museum, 0
+meraker.no, 0
+merseine.nu, 4
+mesaverde.museum, 0
+messina.it, 0
+mg, 0
+mh, 0
+mi.it, 0
+mi.th, 0
+mi.us, 0
+miasa.nagano.jp, 0
+miasta.pl, 0
+mibu.tochigi.jp, 0
+michigan.museum, 0
+microlight.aero, 0
+midatlantic.museum, 0
+midori.chiba.jp, 0
+midori.gunma.jp, 0
+midsund.no, 0
+midtre-gauldal.no, 0
+mie.jp, 0
+mielec.pl, 0
+mielno.pl, 0
+mifune.kumamoto.jp, 0
+mihama.aichi.jp, 0
+mihama.chiba.jp, 0
+mihama.fukui.jp, 0
+mihama.mie.jp, 0
+mihama.wakayama.jp, 0
+mihara.hiroshima.jp, 0
+mihara.kochi.jp, 0
+miharu.fukushima.jp, 0
+miho.ibaraki.jp, 0
+mikasa.hokkaido.jp, 0
+mikawa.yamagata.jp, 0
+miki.hyogo.jp, 0
+mil, 0
+mil.ac, 0
+mil.ae, 0
+mil.al, 0
+mil.az, 0
+mil.ba, 0
+mil.bo, 0
+mil.br, 0
+mil.by, 0
+mil.cl, 0
+mil.cn, 0
+mil.co, 0
+mil.do, 0
+mil.ec, 0
+mil.eg, 0
+mil.ge, 0
+mil.gh, 0
+mil.gt, 0
+mil.hn, 0
+mil.id, 0
+mil.in, 0
+mil.iq, 0
+mil.jo, 0
+mil.kg, 0
+mil.km, 0
+mil.kr, 0
+mil.kz, 0
+mil.lv, 0
+mil.mg, 0
+mil.mv, 0
+mil.my, 0
+mil.no, 0
+mil.pe, 0
+mil.ph, 0
+mil.pl, 0
+mil.py, 0
+mil.qa, 0
+mil.ru, 0
+mil.rw, 0
+mil.sh, 0
+mil.st, 0
+mil.sy, 0
+mil.tj, 0
+mil.tm, 0
+mil.to, 0
+mil.tw, 0
+mil.tz, 0
+mil.uy, 0
+mil.vc, 0
+mil.ve, 0
+milan.it, 0
+milano.it, 0
+military.museum, 0
+mill.museum, 0
+mima.tokushima.jp, 0
+mimata.miyazaki.jp, 0
+minakami.gunma.jp, 0
+minamata.kumamoto.jp, 0
+minami-alps.yamanashi.jp, 0
+minami.fukuoka.jp, 0
+minami.kyoto.jp, 0
+minami.tokushima.jp, 0
+minamiaiki.nagano.jp, 0
+minamiashigara.kanagawa.jp, 0
+minamiawaji.hyogo.jp, 0
+minamiboso.chiba.jp, 0
+minamidaito.okinawa.jp, 0
+minamiechizen.fukui.jp, 0
+minamifurano.hokkaido.jp, 0
+minamiise.mie.jp, 0
+minamiizu.shizuoka.jp, 0
+minamimaki.nagano.jp, 0
+minamiminowa.nagano.jp, 0
+minamioguni.kumamoto.jp, 0
+minamisanriku.miyagi.jp, 0
+minamitane.kagoshima.jp, 0
+minamiuonuma.niigata.jp, 0
+minamiyamashiro.kyoto.jp, 0
+minano.saitama.jp, 0
+minato.osaka.jp, 0
+minato.tokyo.jp, 0
+mincom.tn, 0
+mine.nu, 4
+miners.museum, 0
+mining.museum, 0
+minnesota.museum, 0
+mino.gifu.jp, 0
+minobu.yamanashi.jp, 0
+minoh.osaka.jp, 0
+minokamo.gifu.jp, 0
+minowa.nagano.jp, 0
+misaki.okayama.jp, 0
+misaki.osaka.jp, 0
+misasa.tottori.jp, 0
+misato.akita.jp, 0
+misato.miyagi.jp, 0
+misato.saitama.jp, 0
+misato.shimane.jp, 0
+misato.wakayama.jp, 0
+misawa.aomori.jp, 0
+misconfused.org, 4
+mishima.fukushima.jp, 0
+mishima.shizuoka.jp, 0
+missile.museum, 0
+missoula.museum, 0
+misugi.mie.jp, 0
+mitaka.tokyo.jp, 0
+mitake.gifu.jp, 0
+mitane.akita.jp, 0
+mito.ibaraki.jp, 0
+mitou.yamaguchi.jp, 0
+mitoyo.kagawa.jp, 0
+mitsue.nara.jp, 0
+mitsuke.niigata.jp, 0
+miura.kanagawa.jp, 0
+miyada.nagano.jp, 0
+miyagi.jp, 0
+miyake.nara.jp, 0
+miyako.fukuoka.jp, 0
+miyako.iwate.jp, 0
+miyakonojo.miyazaki.jp, 0
+miyama.fukuoka.jp, 0
+miyama.mie.jp, 0
+miyashiro.saitama.jp, 0
+miyawaka.fukuoka.jp, 0
+miyazaki.jp, 0
+miyazaki.miyazaki.jp, 0
+miyazu.kyoto.jp, 0
+miyoshi.aichi.jp, 0
+miyoshi.hiroshima.jp, 0
+miyoshi.saitama.jp, 0
+miyoshi.tokushima.jp, 0
+miyota.nagano.jp, 0
+mizuho.tokyo.jp, 0
+mizumaki.fukuoka.jp, 0
+mizunami.gifu.jp, 0
+mizusawa.iwate.jp, 0
+mjondalen.no, 0
+mk, 0
+mk.ua, 0
+ml, 0
+mm, 2
+mn, 0
+mn.it, 0
+mn.us, 0
+mo, 0
+mo-i-rana.no, 0
+mo.cn, 0
+mo.it, 0
+mo.us, 0
+moareke.no, 0
+mobara.chiba.jp, 0
+mobi, 0
+mobi.gp, 0
+mobi.na, 0
+mobi.tt, 0
+mobi.tz, 0
+mochizuki.nagano.jp, 0
+mod.gi, 0
+mod.uk, 1
+modalen.no, 0
+modelling.aero, 0
+modena.it, 0
+modern.museum, 0
+modum.no, 0
+moka.tochigi.jp, 0
+molde.no, 0
+moma.museum, 0
+mombetsu.hokkaido.jp, 0
+money.museum, 0
+monmouth.museum, 0
+monticello.museum, 0
+montreal.museum, 0
+monza-brianza.it, 0
+monza-e-della-brianza.it, 0
+monza.it, 0
+monzabrianza.it, 0
+monzaebrianza.it, 0
+monzaedellabrianza.it, 0
+mordovia.ru, 0
+moriguchi.osaka.jp, 0
+morimachi.shizuoka.jp, 0
+morioka.iwate.jp, 0
+moriya.ibaraki.jp, 0
+moriyama.shiga.jp, 0
+moriyoshi.akita.jp, 0
+morotsuka.miyazaki.jp, 0
+moroyama.saitama.jp, 0
+moscow.museum, 0
+moseushi.hokkaido.jp, 0
+mosjoen.no, 0
+moskenes.no, 0
+mosreg.ru, 0
+moss.no, 0
+mosvik.no, 0
+motegi.tochigi.jp, 0
+motobu.okinawa.jp, 0
+motorcycle.museum, 0
+motosu.gifu.jp, 0
+motoyama.kochi.jp, 0
+mp, 0
+mq, 0
+mr, 0
+mr.no, 0
+mragowo.pl, 0
+ms, 0
+ms.it, 0
+ms.kr, 0
+ms.us, 0
+msk.ru, 0
+mt, 2
+mt.it, 0
+mt.us, 0
+mu, 0
+muenchen.museum, 0
+muenster.museum, 0
+mugi.tokushima.jp, 0
+muika.niigata.jp, 0
+mukawa.hokkaido.jp, 0
+muko.kyoto.jp, 0
+mulhouse.museum, 0
+munakata.fukuoka.jp, 0
+muncie.museum, 0
+muosat.no, 0
+murakami.niigata.jp, 0
+murata.miyagi.jp, 0
+murayama.yamagata.jp, 0
+murmansk.ru, 0
+muroran.hokkaido.jp, 0
+muroto.kochi.jp, 0
+mus.br, 0
+musashimurayama.tokyo.jp, 0
+musashino.tokyo.jp, 0
+museet.museum, 0
+museum, 0
+museum.mv, 0
+museum.mw, 0
+museum.no, 0
+museum.tt, 0
+museumcenter.museum, 0
+museumvereniging.museum, 0
+music.museum, 0
+mutsu.aomori.jp, 0
+mutsuzawa.chiba.jp, 0
+mv, 0
+mw, 0
+mx, 0
+mx.na, 0
+my, 0
+my.id, 0
+mykolaiv.ua, 0
+myoko.niigata.jp, 0
+mypets.ws, 4
+myphotos.cc, 4
+mytis.ru, 0
+mz, 2
+n.bg, 0
+n.se, 0
+na, 0
+na.it, 0
+naamesjevuemie.no, 0
+nabari.mie.jp, 0
+nachikatsuura.wakayama.jp, 0
+nacion.ar, 1
+nagahama.shiga.jp, 0
+nagai.yamagata.jp, 0
+nagakute.aichi.jp, 0
+nagano.jp, 0
+nagano.nagano.jp, 0
+naganohara.gunma.jp, 0
+nagaoka.niigata.jp, 0
+nagaokakyo.kyoto.jp, 0
+nagara.chiba.jp, 0
+nagareyama.chiba.jp, 0
+nagasaki.jp, 0
+nagasaki.nagasaki.jp, 0
+nagasu.kumamoto.jp, 0
+nagato.yamaguchi.jp, 0
+nagatoro.saitama.jp, 0
+nagawa.nagano.jp, 0
+nagi.okayama.jp, 0
+nagiso.nagano.jp, 0
+nago.okinawa.jp, 0
+nagoya.jp, 2
+naha.okinawa.jp, 0
+nahari.kochi.jp, 0
+naie.hokkaido.jp, 0
+naka.hiroshima.jp, 0
+naka.ibaraki.jp, 0
+nakadomari.aomori.jp, 0
+nakagawa.fukuoka.jp, 0
+nakagawa.hokkaido.jp, 0
+nakagawa.nagano.jp, 0
+nakagawa.tokushima.jp, 0
+nakagusuku.okinawa.jp, 0
+nakagyo.kyoto.jp, 0
+nakai.kanagawa.jp, 0
+nakama.fukuoka.jp, 0
+nakamichi.yamanashi.jp, 0
+nakamura.kochi.jp, 0
+nakaniikawa.toyama.jp, 0
+nakano.nagano.jp, 0
+nakano.tokyo.jp, 0
+nakanojo.gunma.jp, 0
+nakanoto.ishikawa.jp, 0
+nakasatsunai.hokkaido.jp, 0
+nakatane.kagoshima.jp, 0
+nakatombetsu.hokkaido.jp, 0
+nakatsugawa.gifu.jp, 0
+nakayama.yamagata.jp, 0
+nakhodka.ru, 0
+nakijin.okinawa.jp, 0
+naklo.pl, 0
+nalchik.ru, 0
+namdalseid.no, 0
+name, 0
+name.az, 0
+name.eg, 0
+name.hr, 0
+name.jo, 0
+name.mk, 0
+name.mv, 0
+name.my, 0
+name.na, 0
+name.pr, 0
+name.qa, 0
+name.tj, 0
+name.tt, 0
+name.vn, 0
+namegata.ibaraki.jp, 0
+namegawa.saitama.jp, 0
+namerikawa.toyama.jp, 0
+namie.fukushima.jp, 0
+namikata.ehime.jp, 0
+namsos.no, 0
+namsskogan.no, 0
+nanae.hokkaido.jp, 0
+nanao.ishikawa.jp, 0
+nanbu.tottori.jp, 0
+nanbu.yamanashi.jp, 0
+nango.fukushima.jp, 0
+nanjo.okinawa.jp, 0
+nankoku.kochi.jp, 0
+nanmoku.gunma.jp, 0
+nannestad.no, 0
+nanporo.hokkaido.jp, 0
+nantan.kyoto.jp, 0
+nanto.toyama.jp, 0
+nanyo.yamagata.jp, 0
+naoshima.kagawa.jp, 0
+naples.it, 0
+napoli.it, 0
+nara.jp, 0
+nara.nara.jp, 0
+narashino.chiba.jp, 0
+narita.chiba.jp, 0
+naroy.no, 0
+narusawa.yamanashi.jp, 0
+naruto.tokushima.jp, 0
+narviika.no, 0
+narvik.no, 0
+nasu.tochigi.jp, 0
+nasushiobara.tochigi.jp, 0
+nat.tn, 0
+national-library-scotland.uk, 1
+national.museum, 0
+nationalfirearms.museum, 0
+nationalheritage.museum, 0
+nativeamerican.museum, 0
+natori.miyagi.jp, 0
+naturalhistory.museum, 0
+naturalhistorymuseum.museum, 0
+naturalsciences.museum, 0
+naturbruksgymn.se, 0
+nature.museum, 0
+naturhistorisches.museum, 0
+natuurwetenschappen.museum, 0
+naumburg.museum, 0
+naustdal.no, 0
+naval.museum, 0
+navigation.aero, 0
+navuotna.no, 0
+nawras.om, 1
+nawrastelecom.om, 1
+nayoro.hokkaido.jp, 0
+nb.ca, 0
+nc, 0
+nc.us, 0
+nd.us, 0
+ne, 0
+ne.jp, 0
+ne.kr, 0
+ne.pw, 0
+ne.tz, 0
+ne.ug, 0
+ne.us, 0
+neat-url.com, 4
+nebraska.museum, 0
+nedre-eiker.no, 0
+nel.uk, 1
+nemuro.hokkaido.jp, 0
+nerima.tokyo.jp, 0
+nes.akershus.no, 0
+nes.buskerud.no, 0
+nesna.no, 0
+nesodden.no, 0
+nesoddtangen.no, 0
+nesseby.no, 0
+nesset.no, 0
+net, 0
+net.ac, 0
+net.ae, 0
+net.af, 0
+net.ag, 0
+net.ai, 0
+net.al, 0
+net.an, 0
+net.au, 0
+net.az, 0
+net.ba, 0
+net.bb, 0
+net.bh, 0
+net.bm, 0
+net.bo, 0
+net.br, 0
+net.bs, 0
+net.bt, 0
+net.bz, 0
+net.ci, 0
+net.cn, 0
+net.co, 0
+net.cu, 0
+net.cw, 0
+net.dm, 0
+net.do, 0
+net.dz, 0
+net.ec, 0
+net.eg, 0
+net.ge, 0
+net.gg, 0
+net.gn, 0
+net.gp, 0
+net.gr, 0
+net.gt, 0
+net.gy, 0
+net.hk, 0
+net.hn, 0
+net.ht, 0
+net.id, 0
+net.im, 0
+net.in, 0
+net.iq, 0
+net.ir, 0
+net.is, 0
+net.je, 0
+net.jo, 0
+net.kg, 0
+net.ki, 0
+net.kn, 0
+net.ky, 0
+net.kz, 0
+net.la, 0
+net.lb, 0
+net.lc, 0
+net.lk, 0
+net.lr, 0
+net.lv, 0
+net.ly, 0
+net.ma, 0
+net.me, 0
+net.mk, 0
+net.ml, 0
+net.mo, 0
+net.mu, 0
+net.mv, 0
+net.mw, 0
+net.mx, 0
+net.my, 0
+net.nf, 0
+net.ng, 0
+net.nr, 0
+net.pa, 0
+net.pe, 0
+net.ph, 0
+net.pk, 0
+net.pl, 0
+net.pn, 0
+net.pr, 0
+net.ps, 0
+net.pt, 0
+net.py, 0
+net.qa, 0
+net.ru, 0
+net.rw, 0
+net.sa, 0
+net.sb, 0
+net.sc, 0
+net.sd, 0
+net.sg, 0
+net.sh, 0
+net.sl, 0
+net.so, 0
+net.st, 0
+net.sy, 0
+net.th, 0
+net.tj, 0
+net.tm, 0
+net.tn, 0
+net.to, 0
+net.tt, 0
+net.tw, 0
+net.ua, 0
+net.uy, 0
+net.uz, 0
+net.vc, 0
+net.ve, 0
+net.vi, 0
+net.vn, 0
+net.ws, 0
+neues.museum, 0
+newhampshire.museum, 0
+newjersey.museum, 0
+newmexico.museum, 0
+newport.museum, 0
+news.hu, 0
+newspaper.museum, 0
+newyork.museum, 0
+neyagawa.osaka.jp, 0
+nf, 0
+nf.ca, 0
+ng, 0
+ngo.lk, 0
+ngo.ph, 0
+ngo.pl, 0
+nh.us, 0
+ni, 2
+nic.ar, 1
+nic.im, 0
+nic.in, 0
+nic.tj, 0
+nic.tr, 1
+nic.uk, 1
+nichinan.miyazaki.jp, 0
+nichinan.tottori.jp, 0
+niepce.museum, 0
+nieruchomosci.pl, 0
+niigata.jp, 0
+niigata.niigata.jp, 0
+niihama.ehime.jp, 0
+niikappu.hokkaido.jp, 0
+niimi.okayama.jp, 0
+niiza.saitama.jp, 0
+nikaho.akita.jp, 0
+niki.hokkaido.jp, 0
+nikko.tochigi.jp, 0
+nikolaev.ua, 0
+ninohe.iwate.jp, 0
+ninomiya.kanagawa.jp, 0
+nirasaki.yamanashi.jp, 0
+nishi.fukuoka.jp, 0
+nishi.osaka.jp, 0
+nishiaizu.fukushima.jp, 0
+nishiarita.saga.jp, 0
+nishiawakura.okayama.jp, 0
+nishiazai.shiga.jp, 0
+nishigo.fukushima.jp, 0
+nishihara.kumamoto.jp, 0
+nishihara.okinawa.jp, 0
+nishiizu.shizuoka.jp, 0
+nishikata.tochigi.jp, 0
+nishikatsura.yamanashi.jp, 0
+nishikawa.yamagata.jp, 0
+nishimera.miyazaki.jp, 0
+nishinomiya.hyogo.jp, 0
+nishinoomote.kagoshima.jp, 0
+nishinoshima.shimane.jp, 0
+nishio.aichi.jp, 0
+nishiokoppe.hokkaido.jp, 0
+nishitosa.kochi.jp, 0
+nishiwaki.hyogo.jp, 0
+nissedal.no, 0
+nisshin.aichi.jp, 0
+nittedal.no, 0
+niyodogawa.kochi.jp, 0
+nj.us, 0
+nkz.ru, 0
+nl, 0
+nl.ca, 0
+nl.no, 0
+nls.uk, 1
+nm.cn, 0
+nm.us, 0
+nnov.ru, 0
+no, 0
+no.com, 4
+no.it, 0
+nobeoka.miyazaki.jp, 0
+noboribetsu.hokkaido.jp, 0
+noda.chiba.jp, 0
+noda.iwate.jp, 0
+nogata.fukuoka.jp, 0
+nogi.tochigi.jp, 0
+noheji.aomori.jp, 0
+nom.ad, 0
+nom.ag, 0
+nom.br, 0
+nom.co, 0
+nom.es, 0
+nom.fr, 0
+nom.km, 0
+nom.mg, 0
+nom.pa, 0
+nom.pe, 0
+nom.pl, 0
+nom.re, 0
+nom.ro, 0
+nom.tm, 0
+nome.pt, 0
+nomi.ishikawa.jp, 0
+nonoichi.ishikawa.jp, 0
+nord-aurdal.no, 0
+nord-fron.no, 0
+nord-odal.no, 0
+norddal.no, 0
+nordkapp.no, 0
+nordre-land.no, 0
+nordreisa.no, 0
+nore-og-uvdal.no, 0
+norfolk.museum, 0
+norilsk.ru, 0
+north.museum, 0
+nose.osaka.jp, 0
+nosegawa.nara.jp, 0
+noshiro.akita.jp, 0
+not.br, 0
+notaires.fr, 0
+notaires.km, 0
+noto.ishikawa.jp, 0
+notodden.no, 0
+notogawa.shiga.jp, 0
+notteroy.no, 0
+nov.ru, 0
+novara.it, 0
+novosibirsk.ru, 0
+nowaruda.pl, 0
+nozawaonsen.nagano.jp, 0
+np, 2
+nr, 0
+nrw.museum, 0
+ns.ca, 0
+nsk.ru, 0
+nsn.us, 0
+nsw.au, 0
+nsw.edu.au, 0
+nt.au, 0
+nt.ca, 0
+nt.edu.au, 0
+nt.gov.au, 0
+nt.no, 0
+nt.ro, 0
+ntr.br, 0
+nu, 0
+nu.ca, 0
+nu.it, 0
+nuernberg.museum, 0
+numata.gunma.jp, 0
+numata.hokkaido.jp, 0
+numazu.shizuoka.jp, 0
+nuoro.it, 0
+nuremberg.museum, 0
+nv.us, 0
+nx.cn, 0
+ny.us, 0
+nyc.mn, 4
+nyc.museum, 0
+nyny.museum, 0
+nysa.pl, 0
+nyuzen.toyama.jp, 0
+nz, 2
+o.bg, 0
+o.se, 0
+oamishirasato.chiba.jp, 0
+oarai.ibaraki.jp, 0
+obama.fukui.jp, 0
+obama.nagasaki.jp, 0
+obanazawa.yamagata.jp, 0
+obihiro.hokkaido.jp, 0
+obira.hokkaido.jp, 0
+obu.aichi.jp, 0
+obuse.nagano.jp, 0
+oceanographic.museum, 0
+oceanographique.museum, 0
+ochi.kochi.jp, 0
+od.ua, 0
+odate.akita.jp, 0
+odawara.kanagawa.jp, 0
+odda.no, 0
+odesa.ua, 0
+odessa.ua, 0
+odo.br, 0
+oe.yamagata.jp, 0
+of.by, 0
+of.no, 0
+off.ai, 0
+office-on-the.net, 4
+ofunato.iwate.jp, 0
+og.ao, 0
+og.it, 0
+oga.akita.jp, 0
+ogaki.gifu.jp, 0
+ogano.saitama.jp, 0
+ogasawara.tokyo.jp, 0
+ogata.akita.jp, 0
+ogawa.ibaraki.jp, 0
+ogawa.nagano.jp, 0
+ogawa.saitama.jp, 0
+ogawara.miyagi.jp, 0
+ogi.saga.jp, 0
+ogimi.okinawa.jp, 0
+ogliastra.it, 0
+ogori.fukuoka.jp, 0
+ogose.saitama.jp, 0
+oguchi.aichi.jp, 0
+oguni.kumamoto.jp, 0
+oguni.yamagata.jp, 0
+oh.us, 0
+oharu.aichi.jp, 0
+ohda.shimane.jp, 0
+ohi.fukui.jp, 0
+ohira.miyagi.jp, 0
+ohira.tochigi.jp, 0
+ohkura.yamagata.jp, 0
+ohtawara.tochigi.jp, 0
+oi.kanagawa.jp, 0
+oirase.aomori.jp, 0
+oishida.yamagata.jp, 0
+oiso.kanagawa.jp, 0
+oita.jp, 0
+oita.oita.jp, 0
+oizumi.gunma.jp, 0
+oji.nara.jp, 0
+ojiya.niigata.jp, 0
+ok.us, 0
+okagaki.fukuoka.jp, 0
+okawa.fukuoka.jp, 0
+okawa.kochi.jp, 0
+okaya.nagano.jp, 0
+okayama.jp, 0
+okayama.okayama.jp, 0
+okazaki.aichi.jp, 0
+okegawa.saitama.jp, 0
+oketo.hokkaido.jp, 0
+oki.fukuoka.jp, 0
+okinawa.jp, 0
+okinawa.okinawa.jp, 0
+okinoshima.shimane.jp, 0
+okoppe.hokkaido.jp, 0
+oksnes.no, 0
+okuizumo.shimane.jp, 0
+okuma.fukushima.jp, 0
+okutama.tokyo.jp, 0
+ol.no, 0
+olawa.pl, 0
+olbia-tempio.it, 0
+olbiatempio.it, 0
+olecko.pl, 0
+olkusz.pl, 0
+olsztyn.pl, 0
+om, 2
+omachi.nagano.jp, 0
+omachi.saga.jp, 0
+omaezaki.shizuoka.jp, 0
+omaha.museum, 0
+omanmobile.om, 1
+omanpost.om, 1
+omantel.om, 1
+omasvuotna.no, 0
+ome.tokyo.jp, 0
+omi.nagano.jp, 0
+omi.niigata.jp, 0
+omigawa.chiba.jp, 0
+omihachiman.shiga.jp, 0
+omitama.ibaraki.jp, 0
+omiya.saitama.jp, 0
+omotego.fukushima.jp, 0
+omsk.ru, 0
+omura.nagasaki.jp, 0
+omuta.fukuoka.jp, 0
+on-the-web.tv, 4
+on.ca, 0
+onagawa.miyagi.jp, 0
+onga.fukuoka.jp, 0
+onjuku.chiba.jp, 0
+online.museum, 0
+onna.okinawa.jp, 0
+ono.fukui.jp, 0
+ono.fukushima.jp, 0
+ono.hyogo.jp, 0
+onojo.fukuoka.jp, 0
+onomichi.hiroshima.jp, 0
+ontario.museum, 0
+ookuwa.nagano.jp, 0
+ooshika.nagano.jp, 0
+openair.museum, 0
+operaunite.com, 4
+opoczno.pl, 0
+opole.pl, 0
+oppdal.no, 0
+oppegard.no, 0
+or.at, 0
+or.bi, 0
+or.ci, 0
+or.cr, 0
+or.id, 0
+or.it, 0
+or.jp, 0
+or.kr, 0
+or.mu, 0
+or.na, 0
+or.pw, 0
+or.th, 0
+or.tz, 0
+or.ug, 0
+or.us, 0
+ora.gunma.jp, 0
+oregon.museum, 0
+oregontrail.museum, 0
+orenburg.ru, 0
+org, 0
+org.ac, 0
+org.ae, 0
+org.af, 0
+org.ag, 0
+org.ai, 0
+org.al, 0
+org.an, 0
+org.au, 0
+org.az, 0
+org.ba, 0
+org.bb, 0
+org.bh, 0
+org.bi, 0
+org.bm, 0
+org.bo, 0
+org.br, 0
+org.bs, 0
+org.bt, 0
+org.bw, 0
+org.bz, 0
+org.ci, 0
+org.cn, 0
+org.co, 0
+org.cu, 0
+org.cw, 0
+org.dm, 0
+org.do, 0
+org.dz, 0
+org.ec, 0
+org.ee, 0
+org.eg, 0
+org.es, 0
+org.ge, 0
+org.gg, 0
+org.gh, 0
+org.gi, 0
+org.gn, 0
+org.gp, 0
+org.gr, 0
+org.gt, 0
+org.hk, 0
+org.hn, 0
+org.ht, 0
+org.hu, 0
+org.im, 0
+org.in, 0
+org.iq, 0
+org.ir, 0
+org.is, 0
+org.je, 0
+org.jo, 0
+org.kg, 0
+org.ki, 0
+org.km, 0
+org.kn, 0
+org.kp, 0
+org.ky, 0
+org.kz, 0
+org.la, 0
+org.lb, 0
+org.lc, 0
+org.lk, 0
+org.lr, 0
+org.ls, 0
+org.lv, 0
+org.ly, 0
+org.ma, 0
+org.me, 0
+org.mg, 0
+org.mk, 0
+org.ml, 0
+org.mn, 0
+org.mo, 0
+org.mu, 0
+org.mv, 0
+org.mw, 0
+org.mx, 0
+org.my, 0
+org.na, 0
+org.ng, 0
+org.nr, 0
+org.pa, 0
+org.pe, 0
+org.pf, 0
+org.ph, 0
+org.pk, 0
+org.pl, 0
+org.pn, 0
+org.pr, 0
+org.ps, 0
+org.pt, 0
+org.py, 0
+org.qa, 0
+org.ro, 0
+org.rs, 0
+org.ru, 0
+org.sa, 0
+org.sb, 0
+org.sc, 0
+org.sd, 0
+org.se, 0
+org.sg, 0
+org.sh, 0
+org.sl, 0
+org.sn, 0
+org.so, 0
+org.st, 0
+org.sy, 0
+org.sz, 0
+org.tj, 0
+org.tm, 0
+org.tn, 0
+org.to, 0
+org.tt, 0
+org.tw, 0
+org.ua, 0
+org.ug, 0
+org.uy, 0
+org.uz, 0
+org.vc, 0
+org.ve, 0
+org.vi, 0
+org.vn, 0
+org.ws, 0
+oristano.it, 0
+orkanger.no, 0
+orkdal.no, 0
+orland.no, 0
+orskog.no, 0
+orsta.no, 0
+oryol.ru, 0
+os.hedmark.no, 0
+os.hordaland.no, 0
+osaka.jp, 0
+osakasayama.osaka.jp, 0
+osaki.miyagi.jp, 0
+osakikamijima.hiroshima.jp, 0
+osen.no, 0
+oseto.nagasaki.jp, 0
+oshima.tokyo.jp, 0
+oshima.yamaguchi.jp, 0
+oshino.yamanashi.jp, 0
+oshu.iwate.jp, 0
+oskol.ru, 0
+oslo.no, 0
+osoyro.no, 0
+osteroy.no, 0
+ostre-toten.no, 0
+ostroda.pl, 0
+ostroleka.pl, 0
+ostrowiec.pl, 0
+ostrowwlkp.pl, 0
+ot.it, 0
+ota.gunma.jp, 0
+ota.tokyo.jp, 0
+otago.museum, 0
+otake.hiroshima.jp, 0
+otaki.chiba.jp, 0
+otaki.nagano.jp, 0
+otaki.saitama.jp, 0
+otama.fukushima.jp, 0
+otari.nagano.jp, 0
+otaru.hokkaido.jp, 0
+other.nf, 0
+oto.fukuoka.jp, 0
+otobe.hokkaido.jp, 0
+otofuke.hokkaido.jp, 0
+otoineppu.hokkaido.jp, 0
+otoyo.kochi.jp, 0
+otsu.shiga.jp, 0
+otsuchi.iwate.jp, 0
+otsuki.kochi.jp, 0
+otsuki.yamanashi.jp, 0
+ouchi.saga.jp, 0
+ouda.nara.jp, 0
+oumu.hokkaido.jp, 0
+overhalla.no, 0
+ovre-eiker.no, 0
+owani.aomori.jp, 0
+owariasahi.aichi.jp, 0
+oxford.museum, 0
+oyabe.toyama.jp, 0
+oyama.tochigi.jp, 0
+oyamazaki.kyoto.jp, 0
+oyer.no, 0
+oygarden.no, 0
+oyodo.nara.jp, 0
+oystre-slidre.no, 0
+oz.au, 0
+ozora.hokkaido.jp, 0
+ozu.ehime.jp, 0
+ozu.kumamoto.jp, 0
+p.bg, 0
+p.se, 0
+pa, 0
+pa.gov.pl, 0
+pa.it, 0
+pa.us, 0
+pacific.museum, 0
+paderborn.museum, 0
+padova.it, 0
+padua.it, 0
+palace.museum, 0
+palana.ru, 0
+paleo.museum, 0
+palermo.it, 0
+palmsprings.museum, 0
+panama.museum, 0
+parachuting.aero, 0
+paragliding.aero, 0
+paris.museum, 0
+parliament.uk, 1
+parma.it, 0
+paroch.k12.ma.us, 0
+parti.se, 0
+pasadena.museum, 0
+passenger-association.aero, 0
+pavia.it, 0
+pb.ao, 0
+pc.it, 0
+pc.pl, 0
+pd.it, 0
+pe, 0
+pe.ca, 0
+pe.it, 0
+pe.kr, 0
+penza.ru, 0
+per.la, 0
+per.nf, 0
+per.sg, 0
+perm.ru, 0
+perso.ht, 0
+perso.sn, 0
+perso.tn, 0
+perugia.it, 0
+pesaro-urbino.it, 0
+pesarourbino.it, 0
+pescara.it, 0
+pf, 0
+pg, 2
+pg.it, 0
+ph, 0
+pharmacien.fr, 0
+pharmaciens.km, 0
+pharmacy.museum, 0
+philadelphia.museum, 0
+philadelphiaarea.museum, 0
+philately.museum, 0
+phoenix.museum, 0
+photography.museum, 0
+pi.it, 0
+piacenza.it, 0
+pila.pl, 0
+pilot.aero, 0
+pilots.museum, 0
+pippu.hokkaido.jp, 0
+pisa.it, 0
+pistoia.it, 0
+pisz.pl, 0
+pittsburgh.museum, 0
+pk, 0
+pl, 0
+pl.ua, 0
+planetarium.museum, 0
+plantation.museum, 0
+plants.museum, 0
+plaza.museum, 0
+plc.co.im, 0
+plc.ly, 0
+plo.ps, 0
+pm, 0
+pn, 0
+pn.it, 0
+po.gov.pl, 0
+po.it, 0
+podhale.pl, 0
+podlasie.pl, 0
+podzone.net, 4
+podzone.org, 4
+pol.dz, 0
+pol.ht, 0
+polkowice.pl, 0
+poltava.ua, 0
+pomorskie.pl, 0
+pomorze.pl, 0
+pordenone.it, 0
+porsanger.no, 0
+porsangu.no, 0
+porsgrunn.no, 0
+port.fr, 0
+portal.museum, 0
+portland.museum, 0
+portlligat.museum, 0
+post, 0
+posts-and-telecommunications.museum, 0
+potenza.it, 0
+powiat.pl, 0
+poznan.pl, 0
+pp.az, 0
+pp.ru, 0
+pp.se, 0
+pp.ua, 0
+ppg.br, 0
+pr, 0
+pr.it, 0
+pr.us, 0
+prato.it, 0
+prd.fr, 0
+prd.km, 0
+prd.mg, 0
+preservation.museum, 0
+presidio.museum, 0
+press.aero, 0
+press.ma, 0
+press.museum, 0
+press.se, 0
+presse.ci, 0
+presse.fr, 0
+presse.km, 0
+presse.ml, 0
+pri.ee, 0
+principe.st, 0
+priv.at, 4
+priv.hu, 0
+priv.me, 0
+priv.no, 0
+priv.pl, 0
+pro, 0
+pro.az, 0
+pro.br, 0
+pro.ec, 0
+pro.ht, 0
+pro.mv, 0
+pro.na, 0
+pro.pr, 0
+pro.tt, 0
+pro.vn, 0
+prochowice.pl, 0
+production.aero, 0
+prof.pr, 0
+project.museum, 0
+promocion.ar, 1
+pruszkow.pl, 0
+przeworsk.pl, 0
+ps, 0
+psc.br, 0
+psi.br, 0
+pskov.ru, 0
+pt, 0
+pt.it, 0
+ptz.ru, 0
+pu.it, 0
+pub.sa, 0
+publ.pt, 0
+public.museum, 0
+pubol.museum, 0
+pulawy.pl, 0
+pv.it, 0
+pvt.ge, 0
+pvt.k12.ma.us, 0
+pw, 0
+py, 0
+pyatigorsk.ru, 0
+pz.it, 0
+q.bg, 0
+qa, 0
+qc.ca, 0
+qc.com, 4
+qh.cn, 0
+qld.au, 0
+qld.edu.au, 0
+qld.gov.au, 0
+qsl.br, 0
+quebec.museum, 0
+r.bg, 0
+r.se, 0
+ra.it, 0
+rade.no, 0
+radio.br, 0
+radom.pl, 0
+radoy.no, 0
+ragusa.it, 0
+rahkkeravju.no, 0
+raholt.no, 0
+railroad.museum, 0
+railway.museum, 0
+raisa.no, 0
+rakkestad.no, 0
+rakpetroleum.om, 1
+ralingen.no, 0
+rana.no, 0
+randaberg.no, 0
+rankoshi.hokkaido.jp, 0
+ranzan.saitama.jp, 0
+rauma.no, 0
+ravenna.it, 0
+rawa-maz.pl, 0
+rc.it, 0
+re, 0
+re.it, 0
+re.kr, 0
+readmyblog.org, 4
+realestate.pl, 0
+rebun.hokkaido.jp, 0
+rec.br, 0
+rec.co, 0
+rec.nf, 0
+rec.ro, 0
+recreation.aero, 0
+reggio-calabria.it, 0
+reggio-emilia.it, 0
+reggiocalabria.it, 0
+reggioemilia.it, 0
+reklam.hu, 0
+rel.ht, 0
+rel.pl, 0
+rendalen.no, 0
+rennebu.no, 0
+rennesoy.no, 0
+rep.kp, 0
+repbody.aero, 0
+res.aero, 0
+res.in, 0
+research.aero, 0
+research.museum, 0
+resistance.museum, 0
+retina.ar, 1
+rg.it, 0
+rhcloud.com, 4
+ri.it, 0
+ri.us, 0
+rieti.it, 0
+rifu.miyagi.jp, 0
+riik.ee, 0
+rikubetsu.hokkaido.jp, 0
+rikuzentakata.iwate.jp, 0
+rimini.it, 0
+rindal.no, 0
+ringebu.no, 0
+ringerike.no, 0
+ringsaker.no, 0
+riodejaneiro.museum, 0
+rishiri.hokkaido.jp, 0
+rishirifuji.hokkaido.jp, 0
+risor.no, 0
+rissa.no, 0
+ritto.shiga.jp, 0
+rivne.ua, 0
+rl.no, 0
+rm.it, 0
+rn.it, 0
+rnd.ru, 0
+rnrt.tn, 0
+rns.tn, 0
+rnu.tn, 0
+ro, 0
+ro.it, 0
+roan.no, 0
+rochester.museum, 0
+rockart.museum, 0
+rodoy.no, 0
+rokunohe.aomori.jp, 0
+rollag.no, 0
+roma.it, 0
+roma.museum, 0
+rome.it, 0
+romsa.no, 0
+romskog.no, 0
+roros.no, 0
+rost.no, 0
+rotorcraft.aero, 0
+rovigo.it, 0
+rovno.ua, 0
+royken.no, 0
+royrvik.no, 0
+rs, 0
+rs.ba, 0
+ru, 0
+ru.com, 4
+rubtsovsk.ru, 0
+ruovat.no, 0
+russia.museum, 0
+rv.ua, 0
+rw, 0
+ryazan.ru, 0
+rybnik.pl, 0
+rygge.no, 0
+ryokami.saitama.jp, 0
+ryugasaki.ibaraki.jp, 0
+ryuoh.shiga.jp, 0
+rzeszow.pl, 0
+s.bg, 0
+s.se, 0
+s3-ap-northeast-1.amazonaws.com, 4
+s3-ap-southeast-1.amazonaws.com, 4
+s3-ap-southeast-2.amazonaws.com, 4
+s3-eu-west-1.amazonaws.com, 4
+s3-fips-us-gov-west-1.amazonaws.com, 4
+s3-sa-east-1.amazonaws.com, 4
+s3-us-gov-west-1.amazonaws.com, 4
+s3-us-west-1.amazonaws.com, 4
+s3-us-west-2.amazonaws.com, 4
+s3-website-ap-northeast-1.amazonaws.com, 4
+s3-website-ap-southeast-1.amazonaws.com, 4
+s3-website-ap-southeast-2.amazonaws.com, 4
+s3-website-eu-west-1.amazonaws.com, 4
+s3-website-sa-east-1.amazonaws.com, 4
+s3-website-us-east-1.amazonaws.com, 4
+s3-website-us-gov-west-1.amazonaws.com, 4
+s3-website-us-west-1.amazonaws.com, 4
+s3-website-us-west-2.amazonaws.com, 4
+s3.amazonaws.com, 4
+sa, 0
+sa.au, 0
+sa.com, 4
+sa.cr, 0
+sa.edu.au, 0
+sa.gov.au, 0
+sa.it, 0
+sabae.fukui.jp, 0
+sado.niigata.jp, 0
+safety.aero, 0
+saga.jp, 0
+saga.saga.jp, 0
+sagae.yamagata.jp, 0
+sagamihara.kanagawa.jp, 0
+saigawa.fukuoka.jp, 0
+saijo.ehime.jp, 0
+saikai.nagasaki.jp, 0
+saiki.oita.jp, 0
+saintlouis.museum, 0
+saitama.jp, 0
+saitama.saitama.jp, 0
+saito.miyazaki.jp, 0
+saka.hiroshima.jp, 0
+sakado.saitama.jp, 0
+sakae.chiba.jp, 0
+sakae.nagano.jp, 0
+sakahogi.gifu.jp, 0
+sakai.fukui.jp, 0
+sakai.ibaraki.jp, 0
+sakai.osaka.jp, 0
+sakaiminato.tottori.jp, 0
+sakaki.nagano.jp, 0
+sakata.yamagata.jp, 0
+sakawa.kochi.jp, 0
+sakegawa.yamagata.jp, 0
+sakhalin.ru, 0
+saku.nagano.jp, 0
+sakuho.nagano.jp, 0
+sakura.chiba.jp, 0
+sakura.tochigi.jp, 0
+sakuragawa.ibaraki.jp, 0
+sakurai.nara.jp, 0
+sakyo.kyoto.jp, 0
+salangen.no, 0
+salat.no, 0
+salem.museum, 0
+salerno.it, 0
+saltdal.no, 0
+salvadordali.museum, 0
+salzburg.museum, 0
+samara.ru, 0
+samegawa.fukushima.jp, 0
+samnanger.no, 0
+samukawa.kanagawa.jp, 0
+sanagochi.tokushima.jp, 0
+sanda.hyogo.jp, 0
+sande.more-og-romsdal.no, 0
+sande.vestfold.no, 0
+sande.xn--mre-og-romsdal-qqb.no, 0
+sandefjord.no, 0
+sandiego.museum, 0
+sandnes.no, 0
+sandnessjoen.no, 0
+sandoy.no, 0
+sanfrancisco.museum, 0
+sango.nara.jp, 0
+sanjo.niigata.jp, 0
+sannan.hyogo.jp, 0
+sannohe.aomori.jp, 0
+sano.tochigi.jp, 0
+sanok.pl, 0
+santabarbara.museum, 0
+santacruz.museum, 0
+santafe.museum, 0
+sanuki.kagawa.jp, 0
+saotome.st, 0
+sapporo.jp, 2
+saratov.ru, 0
+saroma.hokkaido.jp, 0
+sarpsborg.no, 0
+sarufutsu.hokkaido.jp, 0
+sasaguri.fukuoka.jp, 0
+sasayama.hyogo.jp, 0
+sasebo.nagasaki.jp, 0
+saskatchewan.museum, 0
+sassari.it, 0
+satosho.okayama.jp, 0
+satsumasendai.kagoshima.jp, 0
+satte.saitama.jp, 0
+satx.museum, 0
+sauda.no, 0
+sauherad.no, 0
+savannahga.museum, 0
+saves-the-whales.com, 4
+savona.it, 0
+sayama.osaka.jp, 0
+sayama.saitama.jp, 0
+sayo.hyogo.jp, 0
+sb, 0
+sb.ua, 0
+sc, 0
+sc.cn, 0
+sc.kr, 0
+sc.tz, 0
+sc.ug, 0
+sc.us, 0
+sch.ae, 0
+sch.gg, 0
+sch.id, 0
+sch.ir, 0
+sch.je, 0
+sch.jo, 0
+sch.lk, 0
+sch.ly, 0
+sch.qa, 0
+sch.sa, 0
+sch.uk, 2
+schlesisches.museum, 0
+schoenbrunn.museum, 0
+schokoladen.museum, 0
+school.museum, 0
+school.na, 0
+schweiz.museum, 0
+sci.eg, 0
+science-fiction.museum, 0
+science.museum, 0
+scienceandhistory.museum, 0
+scienceandindustry.museum, 0
+sciencecenter.museum, 0
+sciencecenters.museum, 0
+sciencehistory.museum, 0
+sciences.museum, 0
+sciencesnaturelles.museum, 0
+scientist.aero, 0
+scotland.museum, 0
+scrapper-site.net, 4
+scrapping.cc, 4
+sd, 0
+sd.cn, 0
+sd.us, 0
+se, 0
+se.com, 4
+se.net, 4
+seaport.museum, 0
+sebastopol.ua, 0
+sec.ps, 0
+seihi.nagasaki.jp, 0
+seika.kyoto.jp, 0
+seiro.niigata.jp, 0
+seirou.niigata.jp, 0
+seiyo.ehime.jp, 0
+sejny.pl, 0
+seki.gifu.jp, 0
+sekigahara.gifu.jp, 0
+sekikawa.niigata.jp, 0
+sel.no, 0
+selbu.no, 0
+selfip.biz, 4
+selfip.com, 4
+selfip.info, 4
+selfip.net, 4
+selfip.org, 4
+selje.no, 0
+seljord.no, 0
+sells-for-less.com, 4
+sells-for-u.com, 4
+sells-it.net, 4
+sellsyourhome.org, 4
+semboku.akita.jp, 0
+semine.miyagi.jp, 0
+sendai.jp, 2
+sennan.osaka.jp, 0
+seoul.kr, 0
+sera.hiroshima.jp, 0
+seranishi.hiroshima.jp, 0
+servebbs.com, 4
+servebbs.net, 4
+servebbs.org, 4
+serveftp.net, 4
+serveftp.org, 4
+servegame.org, 4
+services.aero, 0
+setagaya.tokyo.jp, 0
+seto.aichi.jp, 0
+setouchi.okayama.jp, 0
+settlement.museum, 0
+settlers.museum, 0
+settsu.osaka.jp, 0
+sevastopol.ua, 0
+sex.hu, 0
+sex.pl, 0
+sf.no, 0
+sg, 0
+sh, 0
+sh.cn, 0
+shacknet.nu, 4
+shakotan.hokkaido.jp, 0
+shari.hokkaido.jp, 0
+shell.museum, 0
+sherbrooke.museum, 0
+shibata.miyagi.jp, 0
+shibata.niigata.jp, 0
+shibecha.hokkaido.jp, 0
+shibetsu.hokkaido.jp, 0
+shibukawa.gunma.jp, 0
+shibuya.tokyo.jp, 0
+shichikashuku.miyagi.jp, 0
+shichinohe.aomori.jp, 0
+shiga.jp, 0
+shiiba.miyazaki.jp, 0
+shijonawate.osaka.jp, 0
+shika.ishikawa.jp, 0
+shikabe.hokkaido.jp, 0
+shikama.miyagi.jp, 0
+shikaoi.hokkaido.jp, 0
+shikatsu.aichi.jp, 0
+shiki.saitama.jp, 0
+shikokuchuo.ehime.jp, 0
+shima.mie.jp, 0
+shimabara.nagasaki.jp, 0
+shimada.shizuoka.jp, 0
+shimamaki.hokkaido.jp, 0
+shimamoto.osaka.jp, 0
+shimane.jp, 0
+shimane.shimane.jp, 0
+shimizu.hokkaido.jp, 0
+shimizu.shizuoka.jp, 0
+shimoda.shizuoka.jp, 0
+shimodate.ibaraki.jp, 0
+shimofusa.chiba.jp, 0
+shimogo.fukushima.jp, 0
+shimoichi.nara.jp, 0
+shimoji.okinawa.jp, 0
+shimokawa.hokkaido.jp, 0
+shimokitayama.nara.jp, 0
+shimonita.gunma.jp, 0
+shimonoseki.yamaguchi.jp, 0
+shimosuwa.nagano.jp, 0
+shimotsuke.tochigi.jp, 0
+shimotsuma.ibaraki.jp, 0
+shinagawa.tokyo.jp, 0
+shinanomachi.nagano.jp, 0
+shingo.aomori.jp, 0
+shingu.fukuoka.jp, 0
+shingu.hyogo.jp, 0
+shingu.wakayama.jp, 0
+shinichi.hiroshima.jp, 0
+shinjo.nara.jp, 0
+shinjo.okayama.jp, 0
+shinjo.yamagata.jp, 0
+shinjuku.tokyo.jp, 0
+shinkamigoto.nagasaki.jp, 0
+shinonsen.hyogo.jp, 0
+shinshinotsu.hokkaido.jp, 0
+shinshiro.aichi.jp, 0
+shinto.gunma.jp, 0
+shintoku.hokkaido.jp, 0
+shintomi.miyazaki.jp, 0
+shinyoshitomi.fukuoka.jp, 0
+shiogama.miyagi.jp, 0
+shiojiri.nagano.jp, 0
+shioya.tochigi.jp, 0
+shirahama.wakayama.jp, 0
+shirakawa.fukushima.jp, 0
+shirakawa.gifu.jp, 0
+shirako.chiba.jp, 0
+shiranuka.hokkaido.jp, 0
+shiraoi.hokkaido.jp, 0
+shiraoka.saitama.jp, 0
+shirataka.yamagata.jp, 0
+shiriuchi.hokkaido.jp, 0
+shiroi.chiba.jp, 0
+shiroishi.miyagi.jp, 0
+shiroishi.saga.jp, 0
+shirosato.ibaraki.jp, 0
+shishikui.tokushima.jp, 0
+shiso.hyogo.jp, 0
+shisui.chiba.jp, 0
+shitara.aichi.jp, 0
+shiwa.iwate.jp, 0
+shizukuishi.iwate.jp, 0
+shizuoka.jp, 0
+shizuoka.shizuoka.jp, 0
+shobara.hiroshima.jp, 0
+shonai.fukuoka.jp, 0
+shonai.yamagata.jp, 0
+shoo.okayama.jp, 0
+shop.ht, 0
+shop.hu, 0
+shop.pl, 0
+show.aero, 0
+showa.fukushima.jp, 0
+showa.gunma.jp, 0
+showa.yamanashi.jp, 0
+shunan.yamaguchi.jp, 0
+si, 0
+si.it, 0
+sibenik.museum, 0
+siedlce.pl, 0
+siellak.no, 0
+siemens.om, 1
+siena.it, 0
+sigdal.no, 0
+siljan.no, 0
+silk.museum, 0
+simbirsk.ru, 0
+simple-url.com, 4
+siracusa.it, 0
+sirdal.no, 0
+sk, 0
+sk.ca, 0
+skanit.no, 0
+skanland.no, 0
+skaun.no, 0
+skedsmo.no, 0
+skedsmokorset.no, 0
+ski.museum, 0
+ski.no, 0
+skien.no, 0
+skierva.no, 0
+skiptvet.no, 0
+skjak.no, 0
+skjervoy.no, 0
+sklep.pl, 0
+skoczow.pl, 0
+skodje.no, 0
+skole.museum, 0
+skydiving.aero, 0
+sl, 0
+slask.pl, 0
+slattum.no, 0
+sld.do, 0
+sld.pa, 0
+slg.br, 0
+slupsk.pl, 0
+sm, 0
+sm.ua, 0
+smola.no, 0
+smolensk.ru, 0
+sn, 0
+sn.cn, 0
+snaase.no, 0
+snasa.no, 0
+snillfjord.no, 0
+snoasa.no, 0
+snz.ru, 0
+so, 0
+so.gov.pl, 0
+so.it, 0
+sobetsu.hokkaido.jp, 0
+soc.lk, 0
+society.museum, 0
+sodegaura.chiba.jp, 0
+soeda.fukuoka.jp, 0
+software.aero, 0
+sogndal.no, 0
+sogne.no, 0
+soja.okayama.jp, 0
+soka.saitama.jp, 0
+sokndal.no, 0
+sola.no, 0
+sologne.museum, 0
+solund.no, 0
+soma.fukushima.jp, 0
+somna.no, 0
+sondre-land.no, 0
+sondrio.it, 0
+songdalen.no, 0
+songfest.om, 1
+soni.nara.jp, 0
+soo.kagoshima.jp, 0
+sopot.pl, 0
+sor-aurdal.no, 0
+sor-fron.no, 0
+sor-odal.no, 0
+sor-varanger.no, 0
+sorfold.no, 0
+sorreisa.no, 0
+sortland.no, 0
+sorum.no, 0
+sos.pl, 0
+sosa.chiba.jp, 0
+sosnowiec.pl, 0
+soundandvision.museum, 0
+southcarolina.museum, 0
+southwest.museum, 0
+sowa.ibaraki.jp, 0
+sp.it, 0
+space-to-rent.com, 4
+space.museum, 0
+spb.ru, 0
+spjelkavik.no, 0
+sport.hu, 0
+spy.museum, 0
+spydeberg.no, 0
+square.museum, 0
+sr, 0
+sr.gov.pl, 0
+sr.it, 0
+srv.br, 0
+ss.it, 0
+sshn.se, 0
+st, 0
+st.no, 0
+stadt.museum, 0
+stalbans.museum, 0
+stalowa-wola.pl, 0
+stange.no, 0
+starachowice.pl, 0
+stargard.pl, 0
+starnberg.museum, 0
+starostwo.gov.pl, 0
+stat.no, 0
+state.museum, 0
+statecouncil.om, 1
+stateofdelaware.museum, 0
+stathelle.no, 0
+station.museum, 0
+stavanger.no, 0
+stavern.no, 0
+stavropol.ru, 0
+steam.museum, 0
+steiermark.museum, 0
+steigen.no, 0
+steinkjer.no, 0
+stjohn.museum, 0
+stjordal.no, 0
+stjordalshalsen.no, 0
+stockholm.museum, 0
+stokke.no, 0
+stor-elvdal.no, 0
+stord.no, 0
+stordal.no, 0
+store.bb, 0
+store.nf, 0
+store.ro, 0
+store.st, 0
+storfjord.no, 0
+stpetersburg.museum, 0
+strand.no, 0
+stranda.no, 0
+stryn.no, 0
+student.aero, 0
+stuff-4-sale.org, 4
+stuff-4-sale.us, 4
+stuttgart.museum, 0
+stv.ru, 0
+su, 0
+sue.fukuoka.jp, 0
+suedtirol.it, 0
+suginami.tokyo.jp, 0
+sugito.saitama.jp, 0
+suifu.ibaraki.jp, 0
+suisse.museum, 0
+suita.osaka.jp, 0
+sukagawa.fukushima.jp, 0
+sukumo.kochi.jp, 0
+sula.no, 0
+suldal.no, 0
+suli.hu, 0
+sumida.tokyo.jp, 0
+sumita.iwate.jp, 0
+sumoto.hyogo.jp, 0
+sumoto.kumamoto.jp, 0
+sumy.ua, 0
+sunagawa.hokkaido.jp, 0
+sund.no, 0
+sunndal.no, 0
+surgeonshall.museum, 0
+surgut.ru, 0
+surnadal.no, 0
+surrey.museum, 0
+susaki.kochi.jp, 0
+susono.shizuoka.jp, 0
+suwa.nagano.jp, 0
+suwalki.pl, 0
+suzaka.nagano.jp, 0
+suzu.ishikawa.jp, 0
+suzuka.mie.jp, 0
+sv, 2
+sv.it, 0
+svalbard.no, 0
+sveio.no, 0
+svelvik.no, 0
+svizzera.museum, 0
+sweden.museum, 0
+swidnica.pl, 0
+swiebodzin.pl, 0
+swinoujscie.pl, 0
+sx, 0
+sx.cn, 0
+sy, 0
+sydney.museum, 0
+sykkylven.no, 0
+syzran.ru, 0
+sz, 0
+szczecin.pl, 0
+szczytno.pl, 0
+szex.hu, 0
+szkola.pl, 0
+t.bg, 0
+t.se, 0
+ta.it, 0
+tabayama.yamanashi.jp, 0
+tabuse.yamaguchi.jp, 0
+tachiarai.fukuoka.jp, 0
+tachikawa.tokyo.jp, 0
+tadaoka.osaka.jp, 0
+tado.mie.jp, 0
+tadotsu.kagawa.jp, 0
+tagajo.miyagi.jp, 0
+tagami.niigata.jp, 0
+tagawa.fukuoka.jp, 0
+tahara.aichi.jp, 0
+taiji.wakayama.jp, 0
+taiki.hokkaido.jp, 0
+taiki.mie.jp, 0
+tainai.niigata.jp, 0
+taira.toyama.jp, 0
+taishi.hyogo.jp, 0
+taishi.osaka.jp, 0
+taishin.fukushima.jp, 0
+taito.tokyo.jp, 0
+taiwa.miyagi.jp, 0
+tajimi.gifu.jp, 0
+tajiri.osaka.jp, 0
+taka.hyogo.jp, 0
+takagi.nagano.jp, 0
+takahagi.ibaraki.jp, 0
+takahama.aichi.jp, 0
+takahama.fukui.jp, 0
+takaharu.miyazaki.jp, 0
+takahashi.okayama.jp, 0
+takahata.yamagata.jp, 0
+takaishi.osaka.jp, 0
+takamatsu.kagawa.jp, 0
+takamori.kumamoto.jp, 0
+takamori.nagano.jp, 0
+takanabe.miyazaki.jp, 0
+takanezawa.tochigi.jp, 0
+takaoka.toyama.jp, 0
+takarazuka.hyogo.jp, 0
+takasago.hyogo.jp, 0
+takasaki.gunma.jp, 0
+takashima.shiga.jp, 0
+takasu.hokkaido.jp, 0
+takata.fukuoka.jp, 0
+takatori.nara.jp, 0
+takatsuki.osaka.jp, 0
+takatsuki.shiga.jp, 0
+takayama.gifu.jp, 0
+takayama.gunma.jp, 0
+takayama.nagano.jp, 0
+takazaki.miyazaki.jp, 0
+takehara.hiroshima.jp, 0
+taketa.oita.jp, 0
+taketomi.okinawa.jp, 0
+taki.mie.jp, 0
+takikawa.hokkaido.jp, 0
+takino.hyogo.jp, 0
+takinoue.hokkaido.jp, 0
+takizawa.iwate.jp, 0
+takko.aomori.jp, 0
+tako.chiba.jp, 0
+taku.saga.jp, 0
+tama.tokyo.jp, 0
+tamakawa.fukushima.jp, 0
+tamaki.mie.jp, 0
+tamamura.gunma.jp, 0
+tamano.okayama.jp, 0
+tamatsukuri.ibaraki.jp, 0
+tamayu.shimane.jp, 0
+tamba.hyogo.jp, 0
+tambov.ru, 0
+tana.no, 0
+tanabe.kyoto.jp, 0
+tanabe.wakayama.jp, 0
+tanagura.fukushima.jp, 0
+tananger.no, 0
+tank.museum, 0
+tanohata.iwate.jp, 0
+tara.saga.jp, 0
+tarama.okinawa.jp, 0
+taranto.it, 0
+targi.pl, 0
+tarnobrzeg.pl, 0
+tarui.gifu.jp, 0
+tarumizu.kagoshima.jp, 0
+tas.au, 0
+tas.edu.au, 0
+tas.gov.au, 0
+tatarstan.ru, 0
+tatebayashi.gunma.jp, 0
+tateshina.nagano.jp, 0
+tateyama.chiba.jp, 0
+tateyama.toyama.jp, 0
+tatsuno.hyogo.jp, 0
+tatsuno.nagano.jp, 0
+tawaramoto.nara.jp, 0
+taxi.aero, 0
+taxi.br, 0
+tc, 0
+tcm.museum, 0
+td, 0
+te.it, 0
+te.ua, 0
+teaches-yoga.com, 4
+technology.museum, 0
+tel, 0
+teledata.mz, 1
+telekommunikation.museum, 0
+television.museum, 0
+tempio-olbia.it, 0
+tempioolbia.it, 0
+tendo.yamagata.jp, 0
+tenei.fukushima.jp, 0
+tenkawa.nara.jp, 0
+tenri.nara.jp, 0
+teo.br, 0
+teramo.it, 0
+terni.it, 0
+ternopil.ua, 0
+teshikaga.hokkaido.jp, 0
+test.ru, 0
+test.tj, 0
+texas.museum, 0
+textile.museum, 0
+tf, 0
+tg, 0
+tgory.pl, 0
+th, 0
+theater.museum, 0
+thruhere.net, 4
+time.museum, 0
+time.no, 0
+timekeeping.museum, 0
+tingvoll.no, 0
+tinn.no, 0
+tj, 0
+tj.cn, 0
+tjeldsund.no, 0
+tjome.no, 0
+tk, 0
+tl, 0
+tm, 0
+tm.fr, 0
+tm.hu, 0
+tm.km, 0
+tm.mc, 0
+tm.mg, 0
+tm.no, 0
+tm.pl, 0
+tm.ro, 0
+tm.se, 0
+tmp.br, 0
+tn, 0
+tn.it, 0
+tn.us, 0
+to, 0
+to.it, 0
+toba.mie.jp, 0
+tobe.ehime.jp, 0
+tobetsu.hokkaido.jp, 0
+tobishima.aichi.jp, 0
+tochigi.jp, 0
+tochigi.tochigi.jp, 0
+tochio.niigata.jp, 0
+toda.saitama.jp, 0
+toei.aichi.jp, 0
+toga.toyama.jp, 0
+togakushi.nagano.jp, 0
+togane.chiba.jp, 0
+togitsu.nagasaki.jp, 0
+togo.aichi.jp, 0
+togura.nagano.jp, 0
+tohma.hokkaido.jp, 0
+tohnosho.chiba.jp, 0
+toho.fukuoka.jp, 0
+tokai.aichi.jp, 0
+tokai.ibaraki.jp, 0
+tokamachi.niigata.jp, 0
+tokashiki.okinawa.jp, 0
+toki.gifu.jp, 0
+tokigawa.saitama.jp, 0
+tokke.no, 0
+tokoname.aichi.jp, 0
+tokorozawa.saitama.jp, 0
+tokushima.jp, 0
+tokushima.tokushima.jp, 0
+tokuyama.yamaguchi.jp, 0
+tokyo.jp, 0
+tolga.no, 0
+tom.ru, 0
+tomakomai.hokkaido.jp, 0
+tomari.hokkaido.jp, 0
+tome.miyagi.jp, 0
+tomi.nagano.jp, 0
+tomigusuku.okinawa.jp, 0
+tomika.gifu.jp, 0
+tomioka.gunma.jp, 0
+tomisato.chiba.jp, 0
+tomiya.miyagi.jp, 0
+tomobe.ibaraki.jp, 0
+tomsk.ru, 0
+tonaki.okinawa.jp, 0
+tonami.toyama.jp, 0
+tondabayashi.osaka.jp, 0
+tone.ibaraki.jp, 0
+tono.iwate.jp, 0
+tonosho.kagawa.jp, 0
+tonsberg.no, 0
+toon.ehime.jp, 0
+topology.museum, 0
+torahime.shiga.jp, 0
+toride.ibaraki.jp, 0
+torino.it, 0
+torino.museum, 0
+torsken.no, 0
+tosa.kochi.jp, 0
+tosashimizu.kochi.jp, 0
+toshima.tokyo.jp, 0
+tosu.saga.jp, 0
+tottori.jp, 0
+tottori.tottori.jp, 0
+touch.museum, 0
+tourism.pl, 0
+tourism.tn, 0
+towada.aomori.jp, 0
+town.museum, 0
+toya.hokkaido.jp, 0
+toyako.hokkaido.jp, 0
+toyama.jp, 0
+toyama.toyama.jp, 0
+toyo.kochi.jp, 0
+toyoake.aichi.jp, 0
+toyohashi.aichi.jp, 0
+toyokawa.aichi.jp, 0
+toyonaka.osaka.jp, 0
+toyone.aichi.jp, 0
+toyono.osaka.jp, 0
+toyooka.hyogo.jp, 0
+toyosato.shiga.jp, 0
+toyota.aichi.jp, 0
+toyota.yamaguchi.jp, 0
+toyotomi.hokkaido.jp, 0
+toyotsu.fukuoka.jp, 0
+toyoura.hokkaido.jp, 0
+tozawa.yamagata.jp, 0
+tozsde.hu, 0
+tp.it, 0
+tr, 2
+tr.it, 0
+tr.no, 0
+tra.kp, 0
+trader.aero, 0
+trading.aero, 0
+traeumtgerade.de, 4
+trainer.aero, 0
+trana.no, 0
+tranby.no, 0
+trani-andria-barletta.it, 0
+trani-barletta-andria.it, 0
+traniandriabarletta.it, 0
+tranibarlettaandria.it, 0
+tranoy.no, 0
+transport.museum, 0
+trapani.it, 0
+travel, 0
+travel.pl, 0
+travel.tt, 0
+trd.br, 0
+tree.museum, 0
+trentino.it, 0
+trento.it, 0
+treviso.it, 0
+trieste.it, 0
+troandin.no, 0
+trogstad.no, 0
+trolley.museum, 0
+tromsa.no, 0
+tromso.no, 0
+trondheim.no, 0
+trust.museum, 0
+trustee.museum, 0
+trysil.no, 0
+ts.it, 0
+tsaritsyn.ru, 0
+tsk.ru, 0
+tsu.mie.jp, 0
+tsubame.niigata.jp, 0
+tsubata.ishikawa.jp, 0
+tsubetsu.hokkaido.jp, 0
+tsuchiura.ibaraki.jp, 0
+tsuga.tochigi.jp, 0
+tsugaru.aomori.jp, 0
+tsuiki.fukuoka.jp, 0
+tsukigata.hokkaido.jp, 0
+tsukiyono.gunma.jp, 0
+tsukuba.ibaraki.jp, 0
+tsukui.kanagawa.jp, 0
+tsukumi.oita.jp, 0
+tsumagoi.gunma.jp, 0
+tsunan.niigata.jp, 0
+tsuno.kochi.jp, 0
+tsuno.miyazaki.jp, 0
+tsuru.yamanashi.jp, 0
+tsuruga.fukui.jp, 0
+tsurugashima.saitama.jp, 0
+tsurugi.ishikawa.jp, 0
+tsuruoka.yamagata.jp, 0
+tsuruta.aomori.jp, 0
+tsushima.aichi.jp, 0
+tsushima.nagasaki.jp, 0
+tsuwano.shimane.jp, 0
+tsuyama.okayama.jp, 0
+tt, 0
+tula.ru, 0
+tur.br, 0
+turek.pl, 0
+turen.tn, 0
+turin.it, 0
+turystyka.pl, 0
+tuva.ru, 0
+tv, 0
+tv.bo, 0
+tv.br, 0
+tv.it, 0
+tv.na, 0
+tv.sd, 0
+tv.tz, 0
+tvedestrand.no, 0
+tver.ru, 0
+tw, 0
+tw.cn, 0
+tx.us, 0
+tychy.pl, 0
+tydal.no, 0
+tynset.no, 0
+tysfjord.no, 0
+tysnes.no, 0
+tysvar.no, 0
+tyumen.ru, 0
+tz, 0
+u.bg, 0
+u.se, 0
+ua, 0
+uba.ar, 1
+ube.yamaguchi.jp, 0
+uchihara.ibaraki.jp, 0
+uchiko.ehime.jp, 0
+uchinada.ishikawa.jp, 0
+uchinomi.kagawa.jp, 0
+ud.it, 0
+uda.nara.jp, 0
+udine.it, 0
+udm.ru, 0
+udmurtia.ru, 0
+udono.mie.jp, 0
+ueda.nagano.jp, 0
+ueno.gunma.jp, 0
+uenohara.yamanashi.jp, 0
+ug, 0
+ug.gov.pl, 0
+uhren.museum, 0
+uji.kyoto.jp, 0
+ujiie.tochigi.jp, 0
+ujitawara.kyoto.jp, 0
+uk, 2
+uk.com, 4
+uk.net, 4
+uki.kumamoto.jp, 0
+ukiha.fukuoka.jp, 0
+ulan-ude.ru, 0
+ullensaker.no, 0
+ullensvang.no, 0
+ulm.museum, 0
+ulsan.kr, 0
+ulvik.no, 0
+um.gov.pl, 0
+umaji.kochi.jp, 0
+umi.fukuoka.jp, 0
+unazuki.toyama.jp, 0
+unbi.ba, 0
+undersea.museum, 0
+union.aero, 0
+univ.sn, 0
+university.museum, 0
+unjarga.no, 0
+unnan.shimane.jp, 0
+unsa.ba, 0
+unzen.nagasaki.jp, 0
+uonuma.niigata.jp, 0
+uozu.toyama.jp, 0
+upow.gov.pl, 0
+urakawa.hokkaido.jp, 0
+urasoe.okinawa.jp, 0
+urausu.hokkaido.jp, 0
+urawa.saitama.jp, 0
+urayasu.chiba.jp, 0
+urbino-pesaro.it, 0
+urbinopesaro.it, 0
+ureshino.mie.jp, 0
+uri.arpa, 0
+urn.arpa, 0
+uruma.okinawa.jp, 0
+uryu.hokkaido.jp, 0
+us, 0
+us.com, 4
+us.na, 0
+us.org, 4
+usa.museum, 0
+usa.oita.jp, 0
+usantiques.museum, 0
+usarts.museum, 0
+uscountryestate.museum, 0
+usculture.museum, 0
+usdecorativearts.museum, 0
+usenet.pl, 0
+usgarden.museum, 0
+ushiku.ibaraki.jp, 0
+ushistory.museum, 0
+ushuaia.museum, 0
+uslivinghistory.museum, 0
+ustka.pl, 0
+usui.fukuoka.jp, 0
+usuki.oita.jp, 0
+ut.us, 0
+utah.museum, 0
+utashinai.hokkaido.jp, 0
+utazas.hu, 0
+utazu.kagawa.jp, 0
+uto.kumamoto.jp, 0
+utsira.no, 0
+utsunomiya.tochigi.jp, 0
+uvic.museum, 0
+uw.gov.pl, 0
+uwajima.ehime.jp, 0
+uy, 0
+uy.com, 4
+uz, 0
+uz.ua, 0
+uzhgorod.ua, 0
+v.bg, 0
+va, 0
+va.it, 0
+va.no, 0
+va.us, 0
+vaapste.no, 0
+vadso.no, 0
+vaga.no, 0
+vagan.no, 0
+vagsoy.no, 0
+vaksdal.no, 0
+valer.hedmark.no, 0
+valer.ostfold.no, 0
+valle.no, 0
+valley.museum, 0
+vang.no, 0
+vantaa.museum, 0
+vanylven.no, 0
+vardo.no, 0
+varese.it, 0
+varggat.no, 0
+varoy.no, 0
+vb.it, 0
+vc, 0
+vc.it, 0
+vdonsk.ru, 0
+ve, 0
+ve.it, 0
+vefsn.no, 0
+vega.no, 0
+vegarshei.no, 0
+venezia.it, 0
+venice.it, 0
+vennesla.no, 0
+verbania.it, 0
+vercelli.it, 0
+verdal.no, 0
+verona.it, 0
+verran.no, 0
+versailles.museum, 0
+vestby.no, 0
+vestnes.no, 0
+vestre-slidre.no, 0
+vestre-toten.no, 0
+vestvagoy.no, 0
+vet.br, 0
+veterinaire.fr, 0
+veterinaire.km, 0
+vevelstad.no, 0
+vf.no, 0
+vg, 0
+vgs.no, 0
+vi, 0
+vi.it, 0
+vi.us, 0
+vibo-valentia.it, 0
+vibovalentia.it, 0
+vic.au, 0
+vic.edu.au, 0
+vic.gov.au, 0
+vicenza.it, 0
+video.hu, 0
+vik.no, 0
+viking.museum, 0
+vikna.no, 0
+village.museum, 0
+vindafjord.no, 0
+vinnica.ua, 0
+vinnytsia.ua, 0
+virginia.museum, 0
+virtual.museum, 0
+virtuel.museum, 0
+viterbo.it, 0
+vlaanderen.museum, 0
+vladikavkaz.ru, 0
+vladimir.ru, 0
+vladivostok.ru, 0
+vlog.br, 0
+vn, 0
+vn.ua, 0
+voagat.no, 0
+volda.no, 0
+volgograd.ru, 0
+volkenkunde.museum, 0
+vologda.ru, 0
+volyn.ua, 0
+voronezh.ru, 0
+voss.no, 0
+vossevangen.no, 0
+vr.it, 0
+vrn.ru, 0
+vs.it, 0
+vt.it, 0
+vt.us, 0
+vu, 0
+vv.it, 0
+vyatka.ru, 0
+w.bg, 0
+w.se, 0
+wa.au, 0
+wa.edu.au, 0
+wa.gov.au, 0
+wa.us, 0
+wada.nagano.jp, 0
+wajiki.tokushima.jp, 0
+wajima.ishikawa.jp, 0
+wakasa.fukui.jp, 0
+wakasa.tottori.jp, 0
+wakayama.jp, 0
+wakayama.wakayama.jp, 0
+wake.okayama.jp, 0
+wakkanai.hokkaido.jp, 0
+wakuya.miyagi.jp, 0
+walbrzych.pl, 0
+wales.museum, 0
+wallonie.museum, 0
+wanouchi.gifu.jp, 0
+war.museum, 0
+warabi.saitama.jp, 0
+warmia.pl, 0
+warszawa.pl, 0
+washingtondc.museum, 0
+wassamu.hokkaido.jp, 0
+watarai.mie.jp, 0
+watari.miyagi.jp, 0
+watch-and-clock.museum, 0
+watchandclock.museum, 0
+waw.pl, 0
+wazuka.kyoto.jp, 0
+web.co, 0
+web.do, 0
+web.id, 0
+web.lk, 0
+web.nf, 0
+web.pk, 0
+web.tj, 0
+web.ve, 0
+webhop.biz, 4
+webhop.info, 4
+webhop.net, 4
+webhop.org, 4
+wegrow.pl, 0
+western.museum, 0
+westfalen.museum, 0
+wf, 0
+whaling.museum, 0
+wi.us, 0
+wielun.pl, 0
+wiki.br, 0
+wildlife.museum, 0
+williamsburg.museum, 0
+windmill.museum, 0
+wlocl.pl, 0
+wloclawek.pl, 0
+wodzislaw.pl, 0
+wolomin.pl, 0
+workinggroup.aero, 0
+works.aero, 0
+workshop.museum, 0
+worse-than.tv, 4
+writesthisblog.com, 4
+wroc.pl, 0
+wroclaw.pl, 0
+ws, 0
+ws.na, 0
+wv.us, 0
+www.ck, 1
+www.ro, 0
+wy.us, 0
+x.bg, 0
+x.se, 0
+xj.cn, 0
+xn--3e0b707e, 0
+xn--45brj9c, 0
+xn--54b7fta0cc, 0
+xn--55qx5d.cn, 0
+xn--55qx5d.hk, 0
+xn--90a3ac, 0
+xn--9dbhblg6di.museum, 0
+xn--andy-ira.no, 0
+xn--aroport-bya.ci, 0
+xn--asky-ira.no, 0
+xn--aurskog-hland-jnb.no, 0
+xn--avery-yua.no, 0
+xn--b-5ga.nordland.no, 0
+xn--b-5ga.telemark.no, 0
+xn--bdddj-mrabd.no, 0
+xn--bearalvhki-y4a.no, 0
+xn--berlevg-jxa.no, 0
+xn--bhcavuotna-s4a.no, 0
+xn--bhccavuotna-k7a.no, 0
+xn--bidr-5nac.no, 0
+xn--bievt-0qa.no, 0
+xn--bjarky-fya.no, 0
+xn--bjddar-pta.no, 0
+xn--blt-elab.no, 0
+xn--bmlo-gra.no, 0
+xn--bod-2na.no, 0
+xn--brnny-wuac.no, 0
+xn--brnnysund-m8ac.no, 0
+xn--brum-voa.no, 0
+xn--btsfjord-9za.no, 0
+xn--ciqpn.hk, 0
+xn--clchc0ea0b2g2a9gcd, 0
+xn--comunicaes-v6a2o.museum, 0
+xn--correios-e-telecomunicaes-ghc29a.museum, 0
+xn--czrw28b.tw, 0
+xn--davvenjrga-y4a.no, 0
+xn--dnna-gra.no, 0
+xn--drbak-wua.no, 0
+xn--dyry-ira.no, 0
+xn--eveni-0qa01ga.no, 0
+xn--finny-yua.no, 0
+xn--fiqs8s, 0
+xn--fiqz9s, 0
+xn--fjord-lra.no, 0
+xn--fl-zia.no, 0
+xn--flor-jra.no, 0
+xn--fpcrj9c3d, 0
+xn--frde-gra.no, 0
+xn--frna-woa.no, 0
+xn--frya-hra.no, 0
+xn--fzc2c9e2c, 0
+xn--gecrj9c, 0
+xn--ggaviika-8ya47h.no, 0
+xn--gildeskl-g0a.no, 0
+xn--givuotna-8ya.no, 0
+xn--gjvik-wua.no, 0
+xn--gls-elac.no, 0
+xn--gmq050i.hk, 0
+xn--gmqw5a.hk, 0
+xn--h-2fa.no, 0
+xn--h1aegh.museum, 0
+xn--h2brj9c, 0
+xn--hbmer-xqa.no, 0
+xn--hcesuolo-7ya35b.no, 0
+xn--hery-ira.nordland.no, 0
+xn--hery-ira.xn--mre-og-romsdal-qqb.no, 0
+xn--hgebostad-g3a.no, 0
+xn--hmmrfeasta-s4ac.no, 0
+xn--hnefoss-q1a.no, 0
+xn--hobl-ira.no, 0
+xn--holtlen-hxa.no, 0
+xn--hpmir-xqa.no, 0
+xn--hyanger-q1a.no, 0
+xn--hylandet-54a.no, 0
+xn--indery-fya.no, 0
+xn--io0a7i.cn, 0
+xn--io0a7i.hk, 0
+xn--j1amh, 0
+xn--j6w193g, 0
+xn--jlster-bya.no, 0
+xn--jrpeland-54a.no, 0
+xn--karmy-yua.no, 0
+xn--kfjord-iua.no, 0
+xn--klbu-woa.no, 0
+xn--koluokta-7ya57h.no, 0
+xn--kprw13d, 0
+xn--kpry57d, 0
+xn--krager-gya.no, 0
+xn--kranghke-b0a.no, 0
+xn--krdsherad-m8a.no, 0
+xn--krehamn-dxa.no, 0
+xn--krjohka-hwab49j.no, 0
+xn--ksnes-uua.no, 0
+xn--kvfjord-nxa.no, 0
+xn--kvitsy-fya.no, 0
+xn--kvnangen-k0a.no, 0
+xn--l-1fa.no, 0
+xn--laheadju-7ya.no, 0
+xn--langevg-jxa.no, 0
+xn--lcvr32d.hk, 0
+xn--ldingen-q1a.no, 0
+xn--leagaviika-52b.no, 0
+xn--lesund-hua.no, 0
+xn--lgbbat1ad8j, 0
+xn--lgrd-poac.no, 0
+xn--lhppi-xqa.no, 0
+xn--linds-pra.no, 0
+xn--lns-qla.museum, 0
+xn--loabt-0qa.no, 0
+xn--lrdal-sra.no, 0
+xn--lrenskog-54a.no, 0
+xn--lt-liac.no, 0
+xn--lten-gra.no, 0
+xn--lury-ira.no, 0
+xn--mely-ira.no, 0
+xn--merker-kua.no, 0
+xn--mgb2ddes, 0
+xn--mgb9awbf, 0
+xn--mgba3a4f16a, 0
+xn--mgba3a4f16a.ir, 0
+xn--mgba3a4fra, 0
+xn--mgba3a4fra.ir, 0
+xn--mgbaam7a8h, 0
+xn--mgbayh7gpa, 0
+xn--mgbbh1a71e, 0
+xn--mgbc0a9azcg, 0
+xn--mgberp4a5d4a87g, 0
+xn--mgberp4a5d4ar, 0
+xn--mgbqly7c0a67fbc, 0
+xn--mgbqly7cvafr, 0
+xn--mgbtf8fl, 0
+xn--mjndalen-64a.no, 0
+xn--mk0axi.hk, 0
+xn--mlatvuopmi-s4a.no, 0
+xn--mli-tla.no, 0
+xn--mlselv-iua.no, 0
+xn--moreke-jua.no, 0
+xn--mosjen-eya.no, 0
+xn--mot-tla.no, 0
+xn--msy-ula0h.no, 0
+xn--mtta-vrjjat-k7af.no, 0
+xn--muost-0qa.no, 0
+xn--mxtq1m.hk, 0
+xn--nmesjevuemie-tcba.no, 0
+xn--nnx388a, 0
+xn--node, 0
+xn--nry-yla5g.no, 0
+xn--nttery-byae.no, 0
+xn--nvuotna-hwa.no, 0
+xn--o3cw4h, 0
+xn--od0alg.cn, 0
+xn--od0alg.hk, 0
+xn--od0aq3b.hk, 0
+xn--ogbpf8fl, 0
+xn--oppegrd-ixa.no, 0
+xn--ostery-fya.no, 0
+xn--osyro-wua.no, 0
+xn--p1ai, 0
+xn--pgbs0dh, 0
+xn--porsgu-sta26f.no, 0
+xn--rady-ira.no, 0
+xn--rdal-poa.no, 0
+xn--rde-ula.no, 0
+xn--rdy-0nab.no, 0
+xn--rennesy-v1a.no, 0
+xn--rhkkervju-01af.no, 0
+xn--rholt-mra.no, 0
+xn--risa-5na.no, 0
+xn--risr-ira.no, 0
+xn--rland-uua.no, 0
+xn--rlingen-mxa.no, 0
+xn--rmskog-bya.no, 0
+xn--rros-gra.no, 0
+xn--rskog-uua.no, 0
+xn--rst-0na.no, 0
+xn--rsta-fra.no, 0
+xn--ryken-vua.no, 0
+xn--ryrvik-bya.no, 0
+xn--s-1fa.no, 0
+xn--s9brj9c, 0
+xn--sandnessjen-ogb.no, 0
+xn--sandy-yua.no, 0
+xn--seral-lra.no, 0
+xn--sgne-gra.no, 0
+xn--skierv-uta.no, 0
+xn--skjervy-v1a.no, 0
+xn--skjk-soa.no, 0
+xn--sknit-yqa.no, 0
+xn--sknland-fxa.no, 0
+xn--slat-5na.no, 0
+xn--slt-elab.no, 0
+xn--smla-hra.no, 0
+xn--smna-gra.no, 0
+xn--snase-nra.no, 0
+xn--sndre-land-0cb.no, 0
+xn--snes-poa.no, 0
+xn--snsa-roa.no, 0
+xn--sr-aurdal-l8a.no, 0
+xn--sr-fron-q1a.no, 0
+xn--sr-odal-q1a.no, 0
+xn--sr-varanger-ggb.no, 0
+xn--srfold-bya.no, 0
+xn--srreisa-q1a.no, 0
+xn--srum-gra.no, 0
+xn--stjrdal-s1a.no, 0
+xn--stjrdalshalsen-sqb.no, 0
+xn--stre-toten-zcb.no, 0
+xn--tjme-hra.no, 0
+xn--tn0ag.hk, 0
+xn--tnsberg-q1a.no, 0
+xn--trany-yua.no, 0
+xn--trgstad-r1a.no, 0
+xn--trna-woa.no, 0
+xn--troms-zua.no, 0
+xn--tysvr-vra.no, 0
+xn--uc0atv.hk, 0
+xn--uc0atv.tw, 0
+xn--uc0ay4a.hk, 0
+xn--unjrga-rta.no, 0
+xn--vads-jra.no, 0
+xn--vard-jra.no, 0
+xn--vegrshei-c0a.no, 0
+xn--vestvgy-ixa6o.no, 0
+xn--vg-yiab.no, 0
+xn--vgan-qoa.no, 0
+xn--vgsy-qoa0j.no, 0
+xn--vler-qoa.hedmark.no, 0
+xn--vler-qoa.xn--stfold-9xa.no, 0
+xn--vre-eiker-k8a.no, 0
+xn--vrggt-xqad.no, 0
+xn--vry-yla5g.no, 0
+xn--wcvs22d.hk, 0
+xn--wgbh1c, 0
+xn--wgbl6a, 0
+xn--xkc2al3hye2a, 0
+xn--xkc2dl3a5ee0h, 0
+xn--yer-zna.no, 0
+xn--yfro4i67o, 0
+xn--ygarden-p1a.no, 0
+xn--ygbi2ammx, 0
+xn--ystre-slidre-ujb.no, 0
+xn--zf0ao64a.tw, 0
+xn--zf0avx.hk, 0
+xxx, 0
+xz.cn, 0
+y.bg, 0
+y.se, 0
+yabu.hyogo.jp, 0
+yabuki.fukushima.jp, 0
+yachimata.chiba.jp, 0
+yachiyo.chiba.jp, 0
+yachiyo.ibaraki.jp, 0
+yaese.okinawa.jp, 0
+yahaba.iwate.jp, 0
+yahiko.niigata.jp, 0
+yaita.tochigi.jp, 0
+yaizu.shizuoka.jp, 0
+yakage.okayama.jp, 0
+yakumo.hokkaido.jp, 0
+yakumo.shimane.jp, 0
+yakutia.ru, 0
+yalta.ua, 0
+yamada.fukuoka.jp, 0
+yamada.iwate.jp, 0
+yamada.toyama.jp, 0
+yamaga.kumamoto.jp, 0
+yamagata.gifu.jp, 0
+yamagata.ibaraki.jp, 0
+yamagata.jp, 0
+yamagata.nagano.jp, 0
+yamagata.yamagata.jp, 0
+yamaguchi.jp, 0
+yamakita.kanagawa.jp, 0
+yamal.ru, 0
+yamamoto.miyagi.jp, 0
+yamanakako.yamanashi.jp, 0
+yamanashi.jp, 0
+yamanashi.yamanashi.jp, 0
+yamanobe.yamagata.jp, 0
+yamanouchi.nagano.jp, 0
+yamashina.kyoto.jp, 0
+yamato.fukushima.jp, 0
+yamato.kanagawa.jp, 0
+yamato.kumamoto.jp, 0
+yamatokoriyama.nara.jp, 0
+yamatotakada.nara.jp, 0
+yamatsuri.fukushima.jp, 0
+yamazoe.nara.jp, 0
+yame.fukuoka.jp, 0
+yanagawa.fukuoka.jp, 0
+yanaizu.fukushima.jp, 0
+yao.osaka.jp, 0
+yaotsu.gifu.jp, 0
+yaroslavl.ru, 0
+yasaka.nagano.jp, 0
+yashio.saitama.jp, 0
+yashiro.hyogo.jp, 0
+yasu.shiga.jp, 0
+yasuda.kochi.jp, 0
+yasugi.shimane.jp, 0
+yasuoka.nagano.jp, 0
+yatomi.aichi.jp, 0
+yatsuka.shimane.jp, 0
+yatsushiro.kumamoto.jp, 0
+yawara.ibaraki.jp, 0
+yawata.kyoto.jp, 0
+yawatahama.ehime.jp, 0
+yazu.tottori.jp, 0
+ye, 2
+yekaterinburg.ru, 0
+yk.ca, 0
+yn.cn, 0
+yoichi.hokkaido.jp, 0
+yoita.niigata.jp, 0
+yoka.hyogo.jp, 0
+yokaichiba.chiba.jp, 0
+yokawa.hyogo.jp, 0
+yokkaichi.mie.jp, 0
+yokohama.jp, 2
+yokoshibahikari.chiba.jp, 0
+yokosuka.kanagawa.jp, 0
+yokote.akita.jp, 0
+yokoze.saitama.jp, 0
+yomitan.okinawa.jp, 0
+yonabaru.okinawa.jp, 0
+yonago.tottori.jp, 0
+yonaguni.okinawa.jp, 0
+yonezawa.yamagata.jp, 0
+yono.saitama.jp, 0
+yorii.saitama.jp, 0
+york.museum, 0
+yorkshire.museum, 0
+yoro.gifu.jp, 0
+yosemite.museum, 0
+yoshida.saitama.jp, 0
+yoshida.shizuoka.jp, 0
+yoshikawa.saitama.jp, 0
+yoshimi.saitama.jp, 0
+yoshino.nara.jp, 0
+yoshinogari.saga.jp, 0
+yoshioka.gunma.jp, 0
+yotsukaido.chiba.jp, 0
+youth.museum, 0
+yt, 0
+yuasa.wakayama.jp, 0
+yufu.oita.jp, 0
+yugawa.fukushima.jp, 0
+yugawara.kanagawa.jp, 0
+yuki.ibaraki.jp, 0
+yukuhashi.fukuoka.jp, 0
+yura.wakayama.jp, 0
+yurihonjo.akita.jp, 0
+yusuhara.kochi.jp, 0
+yusui.kagoshima.jp, 0
+yuu.yamaguchi.jp, 0
+yuza.yamagata.jp, 0
+yuzawa.niigata.jp, 0
+yuzhno-sakhalinsk.ru, 0
+z.bg, 0
+z.se, 0
+za, 2
+za.com, 4
+za.net, 4
+za.org, 4
+zachpomor.pl, 0
+zagan.pl, 0
+zakopane.pl, 0
+zama.kanagawa.jp, 0
+zamami.okinawa.jp, 0
+zao.miyagi.jp, 0
+zaporizhzhe.ua, 0
+zaporizhzhia.ua, 0
+zarow.pl, 0
+zentsuji.kagawa.jp, 0
+zgora.pl, 0
+zgorzelec.pl, 0
+zgrad.ru, 0
+zhitomir.ua, 0
+zhytomyr.ua, 0
+zj.cn, 0
+zlg.br, 0
+zm, 2
+zoological.museum, 0
+zoology.museum, 0
+zp.ua, 0
+zt.ua, 0
+zushi.kanagawa.jp, 0
+zw, 2
+%%
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.cc b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.cc
new file mode 100644
index 00000000000..b1c5d26c506
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.cc
@@ -0,0 +1,218 @@
+/* C++ code produced by gperf version 3.0.3 */
+/* Command-line: gperf -a -L C++ -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test1 -P -K name_offset -Q stringpool1 -D effective_tld_names_unittest1.gperf */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
+#endif
+
+#line 1 "effective_tld_names_unittest1.gperf"
+
+// Copyright (c) 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.
+// Test file used by registry_controlled_domain_unittest.
+// We edit this file manually, then run
+// gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test1 -P -K name_offset -Q stringpool1 -D effective_tld_names_unittest1.gperf > effective_tld_names_unittest1.cc
+// to generate the perfect hashmap.
+#line 10 "effective_tld_names_unittest1.gperf"
+struct DomainRule {
+ int name_offset;
+ int type; // 1: exception, 2: wildcard, 4: private
+};
+
+#define TOTAL_KEYWORDS 11
+#define MIN_WORD_LENGTH 1
+#define MAX_WORD_LENGTH 11
+#define MIN_HASH_VALUE 1
+#define MAX_HASH_VALUE 17
+/* maximum key range = 17, duplicates = 0 */
+
+class Perfect_Hash_Test1
+{
+private:
+ static inline unsigned int hash (const char *str, unsigned int len);
+public:
+ static const struct DomainRule *FindDomain (const char *str, unsigned int len);
+};
+
+inline unsigned int
+Perfect_Hash_Test1::hash (register const char *str, register unsigned int len)
+{
+ static const unsigned char asso_values[] =
+ {
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 0, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 0, 0, 0,
+ 18, 5, 0, 18, 18, 0, 0, 18, 18, 0,
+ 5, 0, 0, 18, 0, 18, 5, 18, 0, 18,
+ 18, 18, 0, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[10]];
+ /*FALLTHROUGH*/
+ case 10:
+ hval += asso_values[(unsigned char)str[9]];
+ /*FALLTHROUGH*/
+ case 9:
+ hval += asso_values[(unsigned char)str[8]];
+ /*FALLTHROUGH*/
+ case 8:
+ hval += asso_values[(unsigned char)str[7]];
+ /*FALLTHROUGH*/
+ case 7:
+ hval += asso_values[(unsigned char)str[6]];
+ /*FALLTHROUGH*/
+ case 6:
+ hval += asso_values[(unsigned char)str[5]];
+ /*FALLTHROUGH*/
+ case 5:
+ hval += asso_values[(unsigned char)str[4]];
+ /*FALLTHROUGH*/
+ case 4:
+ hval += asso_values[(unsigned char)str[3]];
+ /*FALLTHROUGH*/
+ case 3:
+ hval += asso_values[(unsigned char)str[2]];
+ /*FALLTHROUGH*/
+ case 2:
+ hval += asso_values[(unsigned char)str[1]];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]];
+ break;
+ }
+ return hval;
+}
+
+struct stringpool1_t
+ {
+ char stringpool1_str0[sizeof("c")];
+ char stringpool1_str1[sizeof("jp")];
+ char stringpool1_str2[sizeof("b.c")];
+ char stringpool1_str3[sizeof("ac.jp")];
+ char stringpool1_str4[sizeof("bar.jp")];
+ char stringpool1_str5[sizeof("no")];
+ char stringpool1_str6[sizeof("baz.bar.jp")];
+ char stringpool1_str7[sizeof("bar.baz.com")];
+ char stringpool1_str8[sizeof("priv.no")];
+ char stringpool1_str9[sizeof("pref.bar.jp")];
+ char stringpool1_str10[sizeof("private")];
+ };
+static const struct stringpool1_t stringpool1_contents =
+ {
+ "c",
+ "jp",
+ "b.c",
+ "ac.jp",
+ "bar.jp",
+ "no",
+ "baz.bar.jp",
+ "bar.baz.com",
+ "priv.no",
+ "pref.bar.jp",
+ "private"
+ };
+#define stringpool1 ((const char *) &stringpool1_contents)
+const struct DomainRule *
+Perfect_Hash_Test1::FindDomain (register const char *str, register unsigned int len)
+{
+ static const struct DomainRule wordlist[] =
+ {
+#line 21 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str0, 2},
+#line 15 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str1, 0},
+#line 22 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str2, 1},
+#line 16 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str3, 0},
+#line 17 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str4, 2},
+#line 23 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str5, 0},
+#line 18 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str6, 2},
+#line 20 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str7, 0},
+#line 24 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str8, 4},
+#line 19 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str9, 1},
+#line 25 "effective_tld_names_unittest1.gperf"
+ {(int)(long)&((struct stringpool1_t *)0)->stringpool1_str10, 4}
+ };
+
+ static const signed char lookup[] =
+ {
+ -1, 0, 1, 2, -1, 3, 4, 5, -1, -1, 6, 7, 8, -1,
+ -1, -1, 9, 10
+ };
+
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= 0)
+ {
+ register int index = lookup[key];
+
+ if (index >= 0)
+ {
+ register const char *s = wordlist[index].name_offset + stringpool1;
+
+ if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0')
+ return &wordlist[index];
+ }
+ }
+ }
+ return 0;
+}
+#line 26 "effective_tld_names_unittest1.gperf"
+
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf
new file mode 100644
index 00000000000..7f192702b67
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf
@@ -0,0 +1,26 @@
+%{
+// Copyright (c) 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.
+// Test file used by registry_controlled_domain_unittest.
+// We edit this file manually, then run
+// gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test1 -P -K name_offset -Q stringpool1 -D effective_tld_names_unittest1.gperf > effective_tld_names_unittest1.cc
+// to generate the perfect hashmap.
+%}
+struct DomainRule {
+ int name_offset;
+ int type; // 1: exception, 2: wildcard, 4: private
+};
+%%
+jp, 0
+ac.jp, 0
+bar.jp, 2
+baz.bar.jp, 2
+pref.bar.jp, 1
+bar.baz.com, 0
+c, 2
+b.c, 1
+no, 0
+priv.no, 4
+private, 4
+%%
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.cc b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.cc
new file mode 100644
index 00000000000..a57151e0c23
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.cc
@@ -0,0 +1,161 @@
+/* C++ code produced by gperf version 3.0.3 */
+/* Command-line: gperf -a -L C++ -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test2 -P -K name_offset -Q stringpool2 -D -T effective_tld_names_unittest2.gperf */
+
+#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+ && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+ && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+ && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+ && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+ && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+ && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+ && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+ && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+ && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+ && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+ && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+ && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+ && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+ && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+ && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+ && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+ && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+ && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+ && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+ && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+ && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+ && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
+/* The character set is not based on ISO-646. */
+#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
+#endif
+
+#line 1 "effective_tld_names_unittest2.gperf"
+
+// Copyright (c) 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.
+// Test file used by registry_controlled_domain_unittest.
+// We edit this file manually, then run
+// gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test2 -P -K name_offset -Q stringpool2 -D -T effective_tld_names_unittest2.gperf > effective_tld_names_unittest2.cc
+// to generate the perfect hashmap.
+
+#define TOTAL_KEYWORDS 2
+#define MIN_WORD_LENGTH 2
+#define MAX_WORD_LENGTH 6
+#define MIN_HASH_VALUE 2
+#define MAX_HASH_VALUE 6
+/* maximum key range = 5, duplicates = 0 */
+
+class Perfect_Hash_Test2
+{
+private:
+ static inline unsigned int hash (const char *str, unsigned int len);
+public:
+ static const struct DomainRule *FindDomain (const char *str, unsigned int len);
+};
+
+inline unsigned int
+Perfect_Hash_Test2::hash (register const char *str, register unsigned int len)
+{
+ static const unsigned char asso_values[] =
+ {
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 0, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 0, 0, 7,
+ 7, 7, 7, 7, 7, 7, 0, 7, 7, 7,
+ 7, 7, 0, 7, 0, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7
+ };
+ register int hval = len;
+
+ switch (hval)
+ {
+ default:
+ hval += asso_values[(unsigned char)str[5]];
+ /*FALLTHROUGH*/
+ case 5:
+ hval += asso_values[(unsigned char)str[4]];
+ /*FALLTHROUGH*/
+ case 4:
+ hval += asso_values[(unsigned char)str[3]];
+ /*FALLTHROUGH*/
+ case 3:
+ hval += asso_values[(unsigned char)str[2]];
+ /*FALLTHROUGH*/
+ case 2:
+ hval += asso_values[(unsigned char)str[1]];
+ /*FALLTHROUGH*/
+ case 1:
+ hval += asso_values[(unsigned char)str[0]];
+ break;
+ }
+ return hval;
+}
+
+struct stringpool2_t
+ {
+ char stringpool2_str0[sizeof("jp")];
+ char stringpool2_str1[sizeof("bar.jp")];
+ };
+static const struct stringpool2_t stringpool2_contents =
+ {
+ "jp",
+ "bar.jp"
+ };
+#define stringpool2 ((const char *) &stringpool2_contents)
+const struct DomainRule *
+Perfect_Hash_Test2::FindDomain (register const char *str, register unsigned int len)
+{
+ static const struct DomainRule wordlist[] =
+ {
+#line 15 "effective_tld_names_unittest2.gperf"
+ {(int)(long)&((struct stringpool2_t *)0)->stringpool2_str0, 0},
+#line 16 "effective_tld_names_unittest2.gperf"
+ {(int)(long)&((struct stringpool2_t *)0)->stringpool2_str1, 0}
+ };
+
+ static const signed char lookup[] =
+ {
+ -1, -1, 0, -1, -1, -1, 1
+ };
+
+ if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
+ {
+ register int key = hash (str, len);
+
+ if (key <= MAX_HASH_VALUE && key >= 0)
+ {
+ register int index = lookup[key];
+
+ if (index >= 0)
+ {
+ register const char *s = wordlist[index].name_offset + stringpool2;
+
+ if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0')
+ return &wordlist[index];
+ }
+ }
+ }
+ return 0;
+}
+#line 17 "effective_tld_names_unittest2.gperf"
+
diff --git a/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf
new file mode 100644
index 00000000000..03c2e2a909f
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf
@@ -0,0 +1,17 @@
+%{
+// Copyright (c) 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.
+// Test file used by registry_controlled_domain_unittest.
+// We edit this file manually, then run
+// gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -ZPerfect_Hash_Test2 -P -K name_offset -Q stringpool2 -D -T effective_tld_names_unittest2.gperf > effective_tld_names_unittest2.cc
+// to generate the perfect hashmap.
+%}
+struct DomainRule {
+ int name_offset;
+ int type; // 1: exception, 2: wildcard, 4: private
+};
+%%
+jp, 0
+bar.jp, 0
+%%
diff --git a/chromium/net/base/registry_controlled_domains/registry_controlled_domain.cc b/chromium/net/base/registry_controlled_domains/registry_controlled_domain.cc
new file mode 100644
index 00000000000..56d5ed9d6a1
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/registry_controlled_domain.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 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.
+
+// NB: Modelled after Mozilla's code (originally written by Pamela Greene,
+// later modified by others), but almost entirely rewritten for Chrome.
+// (netwerk/dns/src/nsEffectiveTLDService.cpp)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Effective-TLD Service
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Pamela Greene <pamg.bugs@gmail.com> (original author)
+ * Daniel Witte <dwitte@stanford.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_module.h"
+#include "net/base/net_util.h"
+#include "url/gurl.h"
+#include "url/url_parse.h"
+
+#include "effective_tld_names.cc"
+
+namespace net {
+namespace registry_controlled_domains {
+
+namespace {
+
+const int kExceptionRule = 1;
+const int kWildcardRule = 2;
+const int kPrivateRule = 4;
+
+const FindDomainPtr kDefaultFindDomainFunction = Perfect_Hash::FindDomain;
+
+// 'stringpool' is defined as a macro by the gperf-generated
+// "effective_tld_names.cc". Provide a real constant value for it instead.
+const char* const kDefaultStringPool = stringpool;
+#undef stringpool
+
+FindDomainPtr g_find_domain_function = kDefaultFindDomainFunction;
+const char* g_stringpool = kDefaultStringPool;
+
+size_t GetRegistryLengthImpl(
+ const std::string& host,
+ UnknownRegistryFilter unknown_filter,
+ PrivateRegistryFilter private_filter) {
+ DCHECK(!host.empty());
+
+ // Skip leading dots.
+ const size_t host_check_begin = host.find_first_not_of('.');
+ if (host_check_begin == std::string::npos)
+ return 0; // Host is only dots.
+
+ // A single trailing dot isn't relevant in this determination, but does need
+ // to be included in the final returned length.
+ size_t host_check_len = host.length();
+ if (host[host_check_len - 1] == '.') {
+ --host_check_len;
+ DCHECK(host_check_len > 0); // If this weren't true, the host would be ".",
+ // and we'd have already returned above.
+ if (host[host_check_len - 1] == '.')
+ return 0; // Multiple trailing dots.
+ }
+
+ // Walk up the domain tree, most specific to least specific,
+ // looking for matches at each level.
+ size_t prev_start = std::string::npos;
+ size_t curr_start = host_check_begin;
+ size_t next_dot = host.find('.', curr_start);
+ if (next_dot >= host_check_len) // Catches std::string::npos as well.
+ return 0; // This can't have a registry + domain.
+ while (1) {
+ const char* domain_str = host.data() + curr_start;
+ int domain_length = host_check_len - curr_start;
+ const DomainRule* rule = g_find_domain_function(domain_str, domain_length);
+
+ // We need to compare the string after finding a match because the
+ // no-collisions of perfect hashing only refers to items in the set. Since
+ // we're searching for arbitrary domains, there could be collisions.
+ // Furthermore, if the apparent match is a private registry and we're not
+ // including those, it can't be an actual match.
+ if (rule) {
+ bool do_check = !(rule->type & kPrivateRule) ||
+ private_filter == INCLUDE_PRIVATE_REGISTRIES;
+ if (do_check && base::strncasecmp(domain_str,
+ g_stringpool + rule->name_offset,
+ domain_length) == 0) {
+ // Exception rules override wildcard rules when the domain is an exact
+ // match, but wildcards take precedence when there's a subdomain.
+ if (rule->type & kWildcardRule && (prev_start != std::string::npos)) {
+ // If prev_start == host_check_begin, then the host is the registry
+ // itself, so return 0.
+ return (prev_start == host_check_begin) ?
+ 0 : (host.length() - prev_start);
+ }
+
+ if (rule->type & kExceptionRule) {
+ if (next_dot == std::string::npos) {
+ // If we get here, we had an exception rule with no dots (e.g.
+ // "!foo"). This would only be valid if we had a corresponding
+ // wildcard rule, which would have to be "*". But we explicitly
+ // disallow that case, so this kind of rule is invalid.
+ NOTREACHED() << "Invalid exception rule";
+ return 0;
+ }
+ return host.length() - next_dot - 1;
+ }
+
+ // If curr_start == host_check_begin, then the host is the registry
+ // itself, so return 0.
+ return (curr_start == host_check_begin) ?
+ 0 : (host.length() - curr_start);
+ }
+ }
+
+ if (next_dot >= host_check_len) // Catches std::string::npos as well.
+ break;
+
+ prev_start = curr_start;
+ curr_start = next_dot + 1;
+ next_dot = host.find('.', curr_start);
+ }
+
+ // No rule found in the registry. curr_start now points to the first
+ // character of the last subcomponent of the host, so if we allow unknown
+ // registries, return the length of this subcomponent.
+ return unknown_filter == INCLUDE_UNKNOWN_REGISTRIES ?
+ (host.length() - curr_start) : 0;
+}
+
+std::string GetDomainAndRegistryImpl(
+ const std::string& host, PrivateRegistryFilter private_filter) {
+ DCHECK(!host.empty());
+
+ // Find the length of the registry for this host.
+ const size_t registry_length =
+ GetRegistryLengthImpl(host, INCLUDE_UNKNOWN_REGISTRIES, private_filter);
+ if ((registry_length == std::string::npos) || (registry_length == 0))
+ return std::string(); // No registry.
+ // The "2" in this next line is 1 for the dot, plus a 1-char minimum preceding
+ // subcomponent length.
+ DCHECK(host.length() >= 2);
+ if (registry_length > (host.length() - 2)) {
+ NOTREACHED() <<
+ "Host does not have at least one subcomponent before registry!";
+ return std::string();
+ }
+
+ // Move past the dot preceding the registry, and search for the next previous
+ // dot. Return the host from after that dot, or the whole host when there is
+ // no dot.
+ const size_t dot = host.rfind('.', host.length() - registry_length - 2);
+ if (dot == std::string::npos)
+ return host;
+ return host.substr(dot + 1);
+}
+
+} // namespace
+
+std::string GetDomainAndRegistry(
+ const GURL& gurl,
+ PrivateRegistryFilter filter) {
+ const url_parse::Component host =
+ gurl.parsed_for_possibly_invalid_spec().host;
+ if ((host.len <= 0) || gurl.HostIsIPAddress())
+ return std::string();
+ return GetDomainAndRegistryImpl(std::string(
+ gurl.possibly_invalid_spec().data() + host.begin, host.len), filter);
+}
+
+std::string GetDomainAndRegistry(
+ const std::string& host,
+ PrivateRegistryFilter filter) {
+ url_canon::CanonHostInfo host_info;
+ const std::string canon_host(CanonicalizeHost(host, &host_info));
+ if (canon_host.empty() || host_info.IsIPAddress())
+ return std::string();
+ return GetDomainAndRegistryImpl(canon_host, filter);
+}
+
+bool SameDomainOrHost(
+ const GURL& gurl1,
+ const GURL& gurl2,
+ PrivateRegistryFilter filter) {
+ // See if both URLs have a known domain + registry, and those values are the
+ // same.
+ const std::string domain1(GetDomainAndRegistry(gurl1, filter));
+ const std::string domain2(GetDomainAndRegistry(gurl2, filter));
+ if (!domain1.empty() || !domain2.empty())
+ return domain1 == domain2;
+
+ // No domains. See if the hosts are identical.
+ const url_parse::Component host1 =
+ gurl1.parsed_for_possibly_invalid_spec().host;
+ const url_parse::Component host2 =
+ gurl2.parsed_for_possibly_invalid_spec().host;
+ if ((host1.len <= 0) || (host1.len != host2.len))
+ return false;
+ return !strncmp(gurl1.possibly_invalid_spec().data() + host1.begin,
+ gurl2.possibly_invalid_spec().data() + host2.begin,
+ host1.len);
+}
+
+size_t GetRegistryLength(
+ const GURL& gurl,
+ UnknownRegistryFilter unknown_filter,
+ PrivateRegistryFilter private_filter) {
+ const url_parse::Component host =
+ gurl.parsed_for_possibly_invalid_spec().host;
+ if (host.len <= 0)
+ return std::string::npos;
+ if (gurl.HostIsIPAddress())
+ return 0;
+ return GetRegistryLengthImpl(
+ std::string(gurl.possibly_invalid_spec().data() + host.begin, host.len),
+ unknown_filter,
+ private_filter);
+}
+
+size_t GetRegistryLength(
+ const std::string& host,
+ UnknownRegistryFilter unknown_filter,
+ PrivateRegistryFilter private_filter) {
+ url_canon::CanonHostInfo host_info;
+ const std::string canon_host(CanonicalizeHost(host, &host_info));
+ if (canon_host.empty())
+ return std::string::npos;
+ if (host_info.IsIPAddress())
+ return 0;
+ return GetRegistryLengthImpl(canon_host, unknown_filter, private_filter);
+}
+
+void SetFindDomainFunctionAndStringPoolForTesting(FindDomainPtr function,
+ const char* stringpool) {
+ g_find_domain_function = function ? function : kDefaultFindDomainFunction;
+ g_stringpool = stringpool ? stringpool : kDefaultStringPool;
+}
+
+} // namespace registry_controlled_domains
+} // namespace net
diff --git a/chromium/net/base/registry_controlled_domains/registry_controlled_domain.h b/chromium/net/base/registry_controlled_domains/registry_controlled_domain.h
new file mode 100644
index 00000000000..b30f55ea6c2
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/registry_controlled_domain.h
@@ -0,0 +1,237 @@
+// Copyright (c) 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.
+
+// NB: Modelled after Mozilla's code (originally written by Pamela Greene,
+// later modified by others), but almost entirely rewritten for Chrome.
+// (netwerk/dns/src/nsEffectiveTLDService.h)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla TLD Service
+ *
+ * The Initial Developer of the Original Code is
+ * Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Pamela Greene <pamg.bugs@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ (Documentation based on the Mozilla documentation currently at
+ http://wiki.mozilla.org/Gecko:Effective_TLD_Service, written by the same
+ author.)
+
+ The RegistryControlledDomainService examines the hostname of a GURL passed to
+ it and determines the longest portion that is controlled by a registrar.
+ Although technically the top-level domain (TLD) for a hostname is the last
+ dot-portion of the name (such as .com or .org), many domains (such as co.uk)
+ function as though they were TLDs, allocating any number of more specific,
+ essentially unrelated names beneath them. For example, .uk is a TLD, but
+ nobody is allowed to register a domain directly under .uk; the "effective"
+ TLDs are ac.uk, co.uk, and so on. We wouldn't want to allow any site in
+ *.co.uk to set a cookie for the entire co.uk domain, so it's important to be
+ able to identify which higher-level domains function as effective TLDs and
+ which can be registered.
+
+ The service obtains its information about effective TLDs from a text resource
+ that must be in the following format:
+
+ * It should use plain ASCII.
+ * It should contain one domain rule per line, terminated with \n, with nothing
+ else on the line. (The last rule in the file may omit the ending \n.)
+ * Rules should have been normalized using the same canonicalization that GURL
+ applies. For ASCII, that means they're not case-sensitive, among other
+ things; other normalizations are applied for other characters.
+ * Each rule should list the entire TLD-like domain name, with any subdomain
+ portions separated by dots (.) as usual.
+ * Rules should neither begin nor end with a dot.
+ * If a hostname matches more than one rule, the most specific rule (that is,
+ the one with more dot-levels) will be used.
+ * Other than in the case of wildcards (see below), rules do not implicitly
+ include their subcomponents. For example, "bar.baz.uk" does not imply
+ "baz.uk", and if "bar.baz.uk" is the only rule in the list, "foo.bar.baz.uk"
+ will match, but "baz.uk" and "qux.baz.uk" won't.
+ * The wildcard character '*' will match any valid sequence of characters.
+ * Wildcards may only appear as the entire most specific level of a rule. That
+ is, a wildcard must come at the beginning of a line and must be followed by
+ a dot. (You may not use a wildcard as the entire rule.)
+ * A wildcard rule implies a rule for the entire non-wildcard portion. For
+ example, the rule "*.foo.bar" implies the rule "foo.bar" (but not the rule
+ "bar"). This is typically important in the case of exceptions (see below).
+ * The exception character '!' before a rule marks an exception to a wildcard
+ rule. If your rules are "*.tokyo.jp" and "!pref.tokyo.jp", then
+ "a.b.tokyo.jp" has an effective TLD of "b.tokyo.jp", but "a.pref.tokyo.jp"
+ has an effective TLD of "tokyo.jp" (the exception prevents the wildcard
+ match, and we thus fall through to matching on the implied "tokyo.jp" rule
+ from the wildcard).
+ * If you use an exception rule without a corresponding wildcard rule, the
+ behavior is undefined.
+
+ Firefox has a very similar service, and it's their data file we use to
+ construct our resource. However, the data expected by this implementation
+ differs from the Mozilla file in several important ways:
+ (1) We require that all single-level TLDs (com, edu, etc.) be explicitly
+ listed. As of this writing, Mozilla's file includes the single-level
+ TLDs too, but that might change.
+ (2) Our data is expected be in pure ASCII: all UTF-8 or otherwise encoded
+ items must already have been normalized.
+ (3) We do not allow comments, rule notes, blank lines, or line endings other
+ than LF.
+ Rules are also expected to be syntactically valid.
+
+ The utility application tld_cleanup.exe converts a Mozilla-style file into a
+ Chrome one, making sure that single-level TLDs are explicitly listed, using
+ GURL to normalize rules, and validating the rules.
+*/
+
+#ifndef NET_BASE_REGISTRY_CONTROLLED_DOMAINS_REGISTRY_CONTROLLED_DOMAIN_H_
+#define NET_BASE_REGISTRY_CONTROLLED_DOMAINS_REGISTRY_CONTROLLED_DOMAIN_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+struct DomainRule;
+
+namespace net {
+namespace registry_controlled_domains {
+
+// This enum is a required parameter to all public methods declared for this
+// service. The Public Suffix List (http://publicsuffix.org/) this service
+// uses as a data source splits all effective-TLDs into two groups. The main
+// group describes registries that are acknowledged by ICANN. The second group
+// contains a list of private additions for domains that enable external users
+// to create subdomains, such as appspot.com.
+// The RegistryFilter enum lets you choose whether you want to include the
+// private additions in your lookup.
+// See this for example use cases:
+// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
+enum NET_EXPORT PrivateRegistryFilter {
+ EXCLUDE_PRIVATE_REGISTRIES = 0,
+ INCLUDE_PRIVATE_REGISTRIES
+};
+
+// This enum is a required parameter to the GetRegistryLength functions
+// declared for this service. Whenever there is no matching rule in the
+// effective-TLD data (or in the default data, if the resource failed to
+// load), the result will be dependent on which enum value was passed in.
+// If EXCLUDE_UNKNOWN_REGISTRIES was passed in, the resulting registry length
+// will be 0. If INCLUDE_UNKNOWN_REGISTRIES was passed in, the resulting
+// registry length will be the length of the last subcomponent (eg. 3 for
+// foobar.baz).
+enum NET_EXPORT UnknownRegistryFilter {
+ EXCLUDE_UNKNOWN_REGISTRIES = 0,
+ INCLUDE_UNKNOWN_REGISTRIES
+};
+
+// Returns the registered, organization-identifying host and all its registry
+// information, but no subdomains, from the given GURL. Returns an empty
+// string if the GURL is invalid, has no host (e.g. a file: URL), has multiple
+// trailing dots, is an IP address, has only one subcomponent (i.e. no dots
+// other than leading/trailing ones), or is itself a recognized registry
+// identifier. If no matching rule is found in the effective-TLD data (or in
+// the default data, if the resource failed to load), the last subcomponent of
+// the host is assumed to be the registry.
+//
+// Examples:
+// http://www.google.com/file.html -> "google.com" (com)
+// http://..google.com/file.html -> "google.com" (com)
+// http://google.com./file.html -> "google.com." (com)
+// http://a.b.co.uk/file.html -> "b.co.uk" (co.uk)
+// file:///C:/bar.html -> "" (no host)
+// http://foo.com../file.html -> "" (multiple trailing dots)
+// http://192.168.0.1/file.html -> "" (IP address)
+// http://bar/file.html -> "" (no subcomponents)
+// http://co.uk/file.html -> "" (host is a registry)
+// http://foo.bar/file.html -> "foo.bar" (no rule; assume bar)
+NET_EXPORT std::string GetDomainAndRegistry(const GURL& gurl,
+ PrivateRegistryFilter filter);
+
+// Like the GURL version, but takes a host (which is canonicalized internally)
+// instead of a full GURL.
+NET_EXPORT std::string GetDomainAndRegistry(const std::string& host,
+ PrivateRegistryFilter filter);
+
+// This convenience function returns true if the two GURLs both have hosts
+// and one of the following is true:
+// * They each have a known domain and registry, and it is the same for both
+// URLs. Note that this means the trailing dot, if any, must match too.
+// * They don't have known domains/registries, but the hosts are identical.
+// Effectively, callers can use this function to check whether the input URLs
+// represent hosts "on the same site".
+NET_EXPORT bool SameDomainOrHost(const GURL& gurl1, const GURL& gurl2,
+ PrivateRegistryFilter filter);
+
+// Finds the length in bytes of the registrar portion of the host in the
+// given GURL. Returns std::string::npos if the GURL is invalid or has no
+// host (e.g. a file: URL). Returns 0 if the GURL has multiple trailing dots,
+// is an IP address, has no subcomponents, or is itself a recognized registry
+// identifier. The result is also dependent on the UnknownRegistryFilter.
+// If no matching rule is found in the effective-TLD data (or in
+// the default data, if the resource failed to load), returns 0 if
+// |unknown_filter| is EXCLUDE_UNKNOWN_REGISTRIES, or the length of the last
+// subcomponent if |unknown_filter| is INCLUDE_UNKNOWN_REGISTRIES.
+//
+// Examples:
+// http://www.google.com/file.html -> 3 (com)
+// http://..google.com/file.html -> 3 (com)
+// http://google.com./file.html -> 4 (com)
+// http://a.b.co.uk/file.html -> 5 (co.uk)
+// file:///C:/bar.html -> std::string::npos (no host)
+// http://foo.com../file.html -> 0 (multiple trailing
+// dots)
+// http://192.168.0.1/file.html -> 0 (IP address)
+// http://bar/file.html -> 0 (no subcomponents)
+// http://co.uk/file.html -> 0 (host is a registry)
+// http://foo.bar/file.html -> 0 or 3, depending (no rule; assume
+// bar)
+NET_EXPORT size_t GetRegistryLength(const GURL& gurl,
+ UnknownRegistryFilter unknown_filter,
+ PrivateRegistryFilter private_filter);
+
+// Like the GURL version, but takes a host (which is canonicalized internally)
+// instead of a full GURL.
+NET_EXPORT size_t GetRegistryLength(const std::string& host,
+ UnknownRegistryFilter unknown_filter,
+ PrivateRegistryFilter private_filter);
+
+typedef const struct DomainRule* (*FindDomainPtr)(const char *, unsigned int);
+
+// Used for unit tests, so that a different perfect hash map from the full
+// list is used. Set to NULL to use the Default function.
+NET_EXPORT_PRIVATE void SetFindDomainFunctionAndStringPoolForTesting(
+ FindDomainPtr fn, const char* stringpool);
+
+} // namespace registry_controlled_domains
+} // namespace net
+
+#endif // NET_BASE_REGISTRY_CONTROLLED_DOMAINS_REGISTRY_CONTROLLED_DOMAIN_H_
diff --git a/chromium/net/base/registry_controlled_domains/registry_controlled_domain_unittest.cc b/chromium/net/base/registry_controlled_domains/registry_controlled_domain_unittest.cc
new file mode 100644
index 00000000000..d1d96f9caa4
--- /dev/null
+++ b/chromium/net/base/registry_controlled_domains/registry_controlled_domain_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (c) 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.
+
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+#include "effective_tld_names_unittest1.cc"
+static const char* const Perfect_Hash_Test1_stringpool = stringpool1;
+#undef TOTAL_KEYWORDS
+#undef MIN_WORD_LENGTH
+#undef MAX_WORD_LENGTH
+#undef MIN_HASH_VALUE
+#undef MAX_HASH_VALUE
+#include "effective_tld_names_unittest2.cc"
+static const char* const Perfect_Hash_Test2_stringpool = stringpool2;
+
+namespace net {
+namespace registry_controlled_domains {
+namespace {
+
+std::string GetDomainFromURL(const std::string& url) {
+ return GetDomainAndRegistry(GURL(url), EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+std::string GetDomainFromHost(const std::string& host) {
+ return GetDomainAndRegistry(host, EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+size_t GetRegistryLengthFromURL(
+ const std::string& url,
+ UnknownRegistryFilter unknown_filter) {
+ return GetRegistryLength(GURL(url),
+ unknown_filter,
+ EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+size_t GetRegistryLengthFromURLIncludingPrivate(
+ const std::string& url,
+ UnknownRegistryFilter unknown_filter) {
+ return GetRegistryLength(GURL(url),
+ unknown_filter,
+ INCLUDE_PRIVATE_REGISTRIES);
+}
+
+size_t GetRegistryLengthFromHost(
+ const std::string& host,
+ UnknownRegistryFilter unknown_filter) {
+ return GetRegistryLength(host, unknown_filter, EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+bool CompareDomains(const std::string& url1, const std::string& url2) {
+ GURL g1 = GURL(url1);
+ GURL g2 = GURL(url2);
+ return SameDomainOrHost(g1, g2, EXCLUDE_PRIVATE_REGISTRIES);
+}
+
+} // namespace
+
+class RegistryControlledDomainTest : public testing::Test {
+ protected:
+ void UseDomainData(FindDomainPtr function, const char* const stringpool) {
+ SetFindDomainFunctionAndStringPoolForTesting(function, stringpool);
+ }
+
+ virtual void TearDown() {
+ SetFindDomainFunctionAndStringPoolForTesting(NULL, NULL);
+ }
+};
+
+TEST_F(RegistryControlledDomainTest, TestGetDomainAndRegistry) {
+ UseDomainData(Perfect_Hash_Test1::FindDomain, Perfect_Hash_Test1_stringpool);
+
+ // Test GURL version of GetDomainAndRegistry().
+ EXPECT_EQ("baz.jp", GetDomainFromURL("http://a.baz.jp/file.html")); // 1
+ EXPECT_EQ("baz.jp.", GetDomainFromURL("http://a.baz.jp./file.html")); // 1
+ EXPECT_EQ("", GetDomainFromURL("http://ac.jp")); // 2
+ EXPECT_EQ("", GetDomainFromURL("http://a.bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromURL("http://bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromURL("http://baz.bar.jp")); // 3 4
+ EXPECT_EQ("a.b.baz.bar.jp", GetDomainFromURL("http://a.b.baz.bar.jp"));
+ // 4
+ EXPECT_EQ("pref.bar.jp", GetDomainFromURL("http://baz.pref.bar.jp")); // 5
+ EXPECT_EQ("b.bar.baz.com.", GetDomainFromURL("http://a.b.bar.baz.com."));
+ // 6
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://a.d.c")); // 7
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://.a.d.c")); // 7
+ EXPECT_EQ("a.d.c", GetDomainFromURL("http://..a.d.c")); // 7
+ EXPECT_EQ("b.c", GetDomainFromURL("http://a.b.c")); // 7 8
+ EXPECT_EQ("baz.com", GetDomainFromURL("http://baz.com")); // none
+ EXPECT_EQ("baz.com.", GetDomainFromURL("http://baz.com.")); // none
+
+ EXPECT_EQ("", GetDomainFromURL(std::string()));
+ EXPECT_EQ("", GetDomainFromURL("http://"));
+ EXPECT_EQ("", GetDomainFromURL("file:///C:/file.html"));
+ EXPECT_EQ("", GetDomainFromURL("http://foo.com.."));
+ EXPECT_EQ("", GetDomainFromURL("http://..."));
+ EXPECT_EQ("", GetDomainFromURL("http://192.168.0.1"));
+ EXPECT_EQ("", GetDomainFromURL("http://localhost"));
+ EXPECT_EQ("", GetDomainFromURL("http://localhost."));
+ EXPECT_EQ("", GetDomainFromURL("http:////Comment"));
+
+ // Test std::string version of GetDomainAndRegistry(). Uses the same
+ // underpinnings as the GURL version, so this is really more of a check of
+ // CanonicalizeHost().
+ EXPECT_EQ("baz.jp", GetDomainFromHost("a.baz.jp")); // 1
+ EXPECT_EQ("baz.jp.", GetDomainFromHost("a.baz.jp.")); // 1
+ EXPECT_EQ("", GetDomainFromHost("ac.jp")); // 2
+ EXPECT_EQ("", GetDomainFromHost("a.bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromHost("bar.jp")); // 3
+ EXPECT_EQ("", GetDomainFromHost("baz.bar.jp")); // 3 4
+ EXPECT_EQ("a.b.baz.bar.jp", GetDomainFromHost("a.b.baz.bar.jp")); // 3 4
+ EXPECT_EQ("pref.bar.jp", GetDomainFromHost("baz.pref.bar.jp")); // 5
+ EXPECT_EQ("b.bar.baz.com.", GetDomainFromHost("a.b.bar.baz.com.")); // 6
+ EXPECT_EQ("a.d.c", GetDomainFromHost("a.d.c")); // 7
+ EXPECT_EQ("a.d.c", GetDomainFromHost(".a.d.c")); // 7
+ EXPECT_EQ("a.d.c", GetDomainFromHost("..a.d.c")); // 7
+ EXPECT_EQ("b.c", GetDomainFromHost("a.b.c")); // 7 8
+ EXPECT_EQ("baz.com", GetDomainFromHost("baz.com")); // none
+ EXPECT_EQ("baz.com.", GetDomainFromHost("baz.com.")); // none
+
+ EXPECT_EQ("", GetDomainFromHost(std::string()));
+ EXPECT_EQ("", GetDomainFromHost("foo.com.."));
+ EXPECT_EQ("", GetDomainFromHost("..."));
+ EXPECT_EQ("", GetDomainFromHost("192.168.0.1"));
+ EXPECT_EQ("", GetDomainFromHost("localhost."));
+ EXPECT_EQ("", GetDomainFromHost(".localhost."));
+}
+
+TEST_F(RegistryControlledDomainTest, TestGetRegistryLength) {
+ UseDomainData(Perfect_Hash_Test1::FindDomain, Perfect_Hash_Test1_stringpool);
+
+ // Test GURL version of GetRegistryLength().
+ EXPECT_EQ(2U, GetRegistryLengthFromURL("http://a.baz.jp/file.html",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 1
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://a.baz.jp./file.html",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 1
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://ac.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 2
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://a.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://baz.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3 4
+ EXPECT_EQ(12U, GetRegistryLengthFromURL("http://a.b.baz.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 4
+ EXPECT_EQ(6U, GetRegistryLengthFromURL("http://baz.pref.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 5
+ EXPECT_EQ(11U, GetRegistryLengthFromURL("http://a.b.bar.baz.com",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 6
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://.a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://..a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(1U, GetRegistryLengthFromURL("http://a.b.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7 8
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://baz.com",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://baz.com.",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://baz.com",
+ INCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(4U, GetRegistryLengthFromURL("http://baz.com.",
+ INCLUDE_UNKNOWN_REGISTRIES)); // none
+
+ EXPECT_EQ(std::string::npos,
+ GetRegistryLengthFromURL(std::string(), EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(std::string::npos,
+ GetRegistryLengthFromURL("http://", EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(std::string::npos,
+ GetRegistryLengthFromURL("file:///C:/file.html",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://foo.com..",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://...",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://192.168.0.1",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://localhost",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://localhost",
+ INCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://localhost.",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://localhost.",
+ INCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http:////Comment",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+
+ // Test std::string version of GetRegistryLength(). Uses the same
+ // underpinnings as the GURL version, so this is really more of a check of
+ // CanonicalizeHost().
+ EXPECT_EQ(2U, GetRegistryLengthFromHost("a.baz.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 1
+ EXPECT_EQ(3U, GetRegistryLengthFromHost("a.baz.jp.",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 1
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("ac.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 2
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("a.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("baz.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 3 4
+ EXPECT_EQ(12U, GetRegistryLengthFromHost("a.b.baz.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 4
+ EXPECT_EQ(6U, GetRegistryLengthFromHost("baz.pref.bar.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 5
+ EXPECT_EQ(11U, GetRegistryLengthFromHost("a.b.bar.baz.com",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 6
+ EXPECT_EQ(3U, GetRegistryLengthFromHost("a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(3U, GetRegistryLengthFromHost(".a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(3U, GetRegistryLengthFromHost("..a.d.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7
+ EXPECT_EQ(1U, GetRegistryLengthFromHost("a.b.c",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // 7 8
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("baz.com",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("baz.com.",
+ EXCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(3U, GetRegistryLengthFromHost("baz.com",
+ INCLUDE_UNKNOWN_REGISTRIES)); // none
+ EXPECT_EQ(4U, GetRegistryLengthFromHost("baz.com.",
+ INCLUDE_UNKNOWN_REGISTRIES)); // none
+
+ EXPECT_EQ(std::string::npos,
+ GetRegistryLengthFromHost(std::string(), EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("foo.com..",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("..",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("192.168.0.1",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("localhost",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("localhost",
+ INCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("localhost.",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromHost("localhost.",
+ INCLUDE_UNKNOWN_REGISTRIES));
+}
+
+TEST_F(RegistryControlledDomainTest, TestSameDomainOrHost) {
+ UseDomainData(Perfect_Hash_Test2::FindDomain, Perfect_Hash_Test2_stringpool);
+
+ EXPECT_TRUE(CompareDomains("http://a.b.bar.jp/file.html",
+ "http://a.b.bar.jp/file.html")); // b.bar.jp
+ EXPECT_TRUE(CompareDomains("http://a.b.bar.jp/file.html",
+ "http://b.b.bar.jp/file.html")); // b.bar.jp
+ EXPECT_FALSE(CompareDomains("http://a.foo.jp/file.html", // foo.jp
+ "http://a.not.jp/file.html")); // not.jp
+ EXPECT_FALSE(CompareDomains("http://a.foo.jp/file.html", // foo.jp
+ "http://a.foo.jp./file.html")); // foo.jp.
+ EXPECT_FALSE(CompareDomains("http://a.com/file.html", // a.com
+ "http://b.com/file.html")); // b.com
+ EXPECT_TRUE(CompareDomains("http://a.x.com/file.html",
+ "http://b.x.com/file.html")); // x.com
+ EXPECT_TRUE(CompareDomains("http://a.x.com/file.html",
+ "http://.x.com/file.html")); // x.com
+ EXPECT_TRUE(CompareDomains("http://a.x.com/file.html",
+ "http://..b.x.com/file.html")); // x.com
+ EXPECT_TRUE(CompareDomains("http://intranet/file.html",
+ "http://intranet/file.html")); // intranet
+ EXPECT_TRUE(CompareDomains("http://127.0.0.1/file.html",
+ "http://127.0.0.1/file.html")); // 127.0.0.1
+ EXPECT_FALSE(CompareDomains("http://192.168.0.1/file.html", // 192.168.0.1
+ "http://127.0.0.1/file.html")); // 127.0.0.1
+ EXPECT_FALSE(CompareDomains("file:///C:/file.html",
+ "file:///C:/file.html")); // no host
+}
+
+TEST_F(RegistryControlledDomainTest, TestDefaultData) {
+ // Note that no data is set: we're using the default rules.
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://google.com",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://stanford.edu",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://ustreas.gov",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://icann.net",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://ferretcentral.org",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://nowhere.foo",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(3U, GetRegistryLengthFromURL("http://nowhere.foo",
+ INCLUDE_UNKNOWN_REGISTRIES));
+}
+
+TEST_F(RegistryControlledDomainTest, TestPrivateRegistryHandling) {
+ UseDomainData(Perfect_Hash_Test1::FindDomain, Perfect_Hash_Test1_stringpool);
+
+ // Testing the same dataset for INCLUDE_PRIVATE_REGISTRIES and
+ // EXCLUDE_PRIVATE_REGISTRIES arguments.
+ // For the domain data used for this test, the private registries are
+ // 'priv.no' and 'private'.
+
+ // Non-private registries.
+ EXPECT_EQ(2U, GetRegistryLengthFromURL("http://priv.no",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(2U, GetRegistryLengthFromURL("http://foo.priv.no",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(2U, GetRegistryLengthFromURL("http://foo.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(2U, GetRegistryLengthFromURL("http://www.foo.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://private",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://foo.private",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U, GetRegistryLengthFromURL("http://private",
+ INCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(7U, GetRegistryLengthFromURL("http://foo.private",
+ INCLUDE_UNKNOWN_REGISTRIES));
+
+ // Private registries.
+ EXPECT_EQ(0U,
+ GetRegistryLengthFromURLIncludingPrivate("http://priv.no",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(7U,
+ GetRegistryLengthFromURLIncludingPrivate("http://foo.priv.no",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(2U,
+ GetRegistryLengthFromURLIncludingPrivate("http://foo.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(2U,
+ GetRegistryLengthFromURLIncludingPrivate("http://www.foo.jp",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U,
+ GetRegistryLengthFromURLIncludingPrivate("http://private",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(7U,
+ GetRegistryLengthFromURLIncludingPrivate("http://foo.private",
+ EXCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(0U,
+ GetRegistryLengthFromURLIncludingPrivate("http://private",
+ INCLUDE_UNKNOWN_REGISTRIES));
+ EXPECT_EQ(7U,
+ GetRegistryLengthFromURLIncludingPrivate("http://foo.private",
+ INCLUDE_UNKNOWN_REGISTRIES));
+}
+
+
+} // namespace registry_controlled_domains
+} // namespace net
diff --git a/chromium/net/base/request_priority.h b/chromium/net/base/request_priority.h
new file mode 100644
index 00000000000..6efd3ab37b1
--- /dev/null
+++ b/chromium/net/base/request_priority.h
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_REQUEST_PRIORITY_H__
+#define NET_BASE_REQUEST_PRIORITY_H__
+
+namespace net {
+
+// Prioritization used in various parts of the networking code such
+// as connection prioritization and resource loading prioritization.
+enum RequestPriority {
+ IDLE = 0,
+ MINIMUM_PRIORITY = IDLE,
+ LOWEST,
+ DEFAULT_PRIORITY = LOWEST,
+ LOW,
+ MEDIUM,
+ HIGHEST,
+ NUM_PRIORITIES,
+};
+
+} // namespace net
+
+#endif // NET_BASE_REQUEST_PRIORITY_H__
diff --git a/chromium/net/base/sdch_filter.cc b/chromium/net/base/sdch_filter.cc
new file mode 100644
index 00000000000..89891b66f49
--- /dev/null
+++ b/chromium/net/base/sdch_filter.cc
@@ -0,0 +1,388 @@
+// Copyright (c) 2011 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/base/sdch_filter.h"
+
+#include <limits.h>
+#include <ctype.h>
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "net/base/sdch_manager.h"
+
+#include "sdch/open-vcdiff/src/google/vcdecoder.h"
+
+namespace net {
+
+SdchFilter::SdchFilter(const FilterContext& filter_context)
+ : filter_context_(filter_context),
+ decoding_status_(DECODING_UNINITIALIZED),
+ dictionary_hash_(),
+ dictionary_hash_is_plausible_(false),
+ dictionary_(NULL),
+ dest_buffer_excess_(),
+ dest_buffer_excess_index_(0),
+ source_bytes_(0),
+ output_bytes_(0),
+ possible_pass_through_(false) {
+ bool success = filter_context.GetMimeType(&mime_type_);
+ DCHECK(success);
+ success = filter_context.GetURL(&url_);
+ DCHECK(success);
+}
+
+SdchFilter::~SdchFilter() {
+ // All code here is for gathering stats, and can be removed when SDCH is
+ // considered stable.
+
+ static int filter_use_count = 0;
+ ++filter_use_count;
+ if (META_REFRESH_RECOVERY == decoding_status_) {
+ UMA_HISTOGRAM_COUNTS("Sdch3.FilterUseBeforeDisabling", filter_use_count);
+ }
+
+ if (vcdiff_streaming_decoder_.get()) {
+ if (!vcdiff_streaming_decoder_->FinishDecoding()) {
+ decoding_status_ = DECODING_ERROR;
+ SdchManager::SdchErrorRecovery(SdchManager::INCOMPLETE_SDCH_CONTENT);
+ // Make it possible for the user to hit reload, and get non-sdch content.
+ // Note this will "wear off" quickly enough, and is just meant to assure
+ // in some rare case that the user is not stuck.
+ SdchManager::BlacklistDomain(url_);
+ UMA_HISTOGRAM_COUNTS("Sdch3.PartialBytesIn",
+ static_cast<int>(filter_context_.GetByteReadCount()));
+ UMA_HISTOGRAM_COUNTS("Sdch3.PartialVcdiffIn", source_bytes_);
+ UMA_HISTOGRAM_COUNTS("Sdch3.PartialVcdiffOut", output_bytes_);
+ }
+ }
+
+ if (!dest_buffer_excess_.empty()) {
+ // Filter chaining error, or premature teardown.
+ SdchManager::SdchErrorRecovery(SdchManager::UNFLUSHED_CONTENT);
+ UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBytesIn",
+ static_cast<int>(filter_context_.GetByteReadCount()));
+ UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedBufferSize",
+ dest_buffer_excess_.size());
+ UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedVcdiffIn", source_bytes_);
+ UMA_HISTOGRAM_COUNTS("Sdch3.UnflushedVcdiffOut", output_bytes_);
+ }
+
+ if (filter_context_.IsCachedContent()) {
+ // Not a real error, but it is useful to have this tally.
+ // TODO(jar): Remove this stat after SDCH stability is validated.
+ SdchManager::SdchErrorRecovery(SdchManager::CACHE_DECODED);
+ return; // We don't need timing stats, and we aready got ratios.
+ }
+
+ switch (decoding_status_) {
+ case DECODING_IN_PROGRESS: {
+ if (output_bytes_)
+ UMA_HISTOGRAM_PERCENTAGE("Sdch3.Network_Decode_Ratio_a",
+ static_cast<int>(
+ (filter_context_.GetByteReadCount() * 100) / output_bytes_));
+ UMA_HISTOGRAM_COUNTS("Sdch3.Network_Decode_Bytes_VcdiffOut_a",
+ output_bytes_);
+ filter_context_.RecordPacketStats(FilterContext::SDCH_DECODE);
+
+ // Allow latency experiments to proceed.
+ SdchManager::Global()->SetAllowLatencyExperiment(url_, true);
+ return;
+ }
+ case PASS_THROUGH: {
+ filter_context_.RecordPacketStats(FilterContext::SDCH_PASSTHROUGH);
+ return;
+ }
+ case DECODING_UNINITIALIZED: {
+ SdchManager::SdchErrorRecovery(SdchManager::UNINITIALIZED);
+ return;
+ }
+ case WAITING_FOR_DICTIONARY_SELECTION: {
+ SdchManager::SdchErrorRecovery(SdchManager::PRIOR_TO_DICTIONARY);
+ return;
+ }
+ case DECODING_ERROR: {
+ SdchManager::SdchErrorRecovery(SdchManager::DECODE_ERROR);
+ return;
+ }
+ case META_REFRESH_RECOVERY: {
+ // Already accounted for when set.
+ return;
+ }
+ } // end of switch.
+}
+
+bool SdchFilter::InitDecoding(Filter::FilterType filter_type) {
+ if (decoding_status_ != DECODING_UNINITIALIZED)
+ return false;
+
+ // Handle case where sdch filter is guessed, but not required.
+ if (FILTER_TYPE_SDCH_POSSIBLE == filter_type)
+ possible_pass_through_ = true;
+
+ // Initialize decoder only after we have a dictionary in hand.
+ decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION;
+ return true;
+}
+
+#ifndef NDEBUG
+static const char* kDecompressionErrorHtml =
+ "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>"
+ "<div style=\"position:fixed;top:0;left:0;width:100%;border-width:thin;"
+ "border-color:black;border-style:solid;text-align:left;font-family:arial;"
+ "font-size:10pt;foreground-color:black;background-color:white\">"
+ "An error occurred. This page will be reloaded shortly. "
+ "Or press the \"reload\" button now to reload it immediately."
+ "</div>";
+#else
+static const char* kDecompressionErrorHtml =
+ "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>";
+#endif
+
+Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
+ int* dest_len) {
+ int available_space = *dest_len;
+ *dest_len = 0; // Nothing output yet.
+
+ if (!dest_buffer || available_space <= 0)
+ return FILTER_ERROR;
+
+ if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) {
+ FilterStatus status = InitializeDictionary();
+ if (FILTER_NEED_MORE_DATA == status)
+ return FILTER_NEED_MORE_DATA;
+ if (FILTER_ERROR == status) {
+ DCHECK_EQ(DECODING_ERROR, decoding_status_);
+ DCHECK_EQ(0u, dest_buffer_excess_index_);
+ DCHECK(dest_buffer_excess_.empty());
+ // This is where we try very hard to do error recovery, and make this
+ // protocol robust in the face of proxies that do many different things.
+ // If we decide that things are looking very bad (too hard to recover),
+ // we may even issue a "meta-refresh" to reload the page without an SDCH
+ // advertisement (so that we are sure we're not hurting anything).
+ //
+ // Watch out for an error page inserted by the proxy as part of a 40x
+ // error response. When we see such content molestation, we certainly
+ // need to fall into the meta-refresh case.
+ if (filter_context_.GetResponseCode() == 404) {
+ // We could be more generous, but for now, only a "NOT FOUND" code will
+ // cause a pass through. All other bad codes will fall into a
+ // meta-refresh.
+ SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_404_CODE);
+ decoding_status_ = PASS_THROUGH;
+ } else if (filter_context_.GetResponseCode() != 200) {
+ // We need to meta-refresh, with SDCH disabled.
+ } else if (filter_context_.IsCachedContent()
+ && !dictionary_hash_is_plausible_) {
+ // We must have hit the back button, and gotten content that was fetched
+ // before we *really* advertised SDCH and a dictionary.
+ SdchManager::SdchErrorRecovery(SdchManager::PASS_THROUGH_OLD_CACHED);
+ decoding_status_ = PASS_THROUGH;
+ } else if (possible_pass_through_) {
+ // This is the potentially most graceful response. There really was no
+ // error. We were just overly cautious when we added a TENTATIVE_SDCH.
+ // We added the sdch coding tag, and it should not have been added.
+ // This can happen in server experiments, where the server decides
+ // not to use sdch, even though there is a dictionary. To be
+ // conservative, we locally added the tentative sdch (fearing that a
+ // proxy stripped it!) and we must now recant (pass through).
+ SdchManager::SdchErrorRecovery(SdchManager::DISCARD_TENTATIVE_SDCH);
+ // However.... just to be sure we don't get burned by proxies that
+ // re-compress with gzip or other system, we can sniff to see if this
+ // is compressed data etc. For now, we do nothing, which gets us into
+ // the meta-refresh result.
+ // TODO(jar): Improve robustness by sniffing for valid text that we can
+ // actual use re: decoding_status_ = PASS_THROUGH;
+ } else if (dictionary_hash_is_plausible_) {
+ // We need a meta-refresh since we don't have the dictionary.
+ // The common cause is a restart of the browser, where we try to render
+ // cached content that was saved when we had a dictionary.
+ } else if (filter_context_.IsSdchResponse()) {
+ // This is a very corrupt SDCH request response. We can't decode it.
+ // We'll use a meta-refresh, and get content without asking for SDCH.
+ // This will also progressively disable SDCH for this domain.
+ } else {
+ // One of the first 9 bytes precluded consideration as a hash.
+ // This can't be an SDCH payload, even though the server said it was.
+ // This is a major error, as the server or proxy tagged this SDCH even
+ // though it is not!
+ // Meta-refresh won't help, as we didn't advertise an SDCH dictionary!!
+ // Worse yet, meta-refresh could lead to an infinite refresh loop.
+ SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH);
+ decoding_status_ = PASS_THROUGH;
+ // ... but further back-off on advertising SDCH support.
+ SdchManager::BlacklistDomain(url_);
+ }
+
+ if (decoding_status_ == PASS_THROUGH) {
+ dest_buffer_excess_ = dictionary_hash_; // Send what we scanned.
+ } else {
+ // This is where we try to do the expensive meta-refresh.
+ if (std::string::npos == mime_type_.find("text/html")) {
+ // Since we can't do a meta-refresh (along with an exponential
+ // backoff), we'll just make sure this NEVER happens again.
+ SdchManager::BlacklistDomainForever(url_);
+ if (filter_context_.IsCachedContent())
+ SdchManager::SdchErrorRecovery(
+ SdchManager::CACHED_META_REFRESH_UNSUPPORTED);
+ else
+ SdchManager::SdchErrorRecovery(
+ SdchManager::META_REFRESH_UNSUPPORTED);
+ return FILTER_ERROR;
+ }
+ // HTML content means we can issue a meta-refresh, and get the content
+ // again, perhaps without SDCH (to be safe).
+ if (filter_context_.IsCachedContent()) {
+ // Cached content is probably a startup tab, so we'll just get fresh
+ // content and try again, without disabling sdch.
+ SdchManager::SdchErrorRecovery(
+ SdchManager::META_REFRESH_CACHED_RECOVERY);
+ } else {
+ // Since it wasn't in the cache, we definately need at least some
+ // period of blacklisting to get the correct content.
+ SdchManager::BlacklistDomain(url_);
+ SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY);
+ }
+ decoding_status_ = META_REFRESH_RECOVERY;
+ // Issue a meta redirect with SDCH disabled.
+ dest_buffer_excess_ = kDecompressionErrorHtml;
+ }
+ } else {
+ DCHECK_EQ(DECODING_IN_PROGRESS, decoding_status_);
+ }
+ }
+
+ int amount = OutputBufferExcess(dest_buffer, available_space);
+ *dest_len += amount;
+ dest_buffer += amount;
+ available_space -= amount;
+ DCHECK_GE(available_space, 0);
+
+ if (available_space <= 0)
+ return FILTER_OK;
+ DCHECK(dest_buffer_excess_.empty());
+ DCHECK_EQ(0u, dest_buffer_excess_index_);
+
+ if (decoding_status_ != DECODING_IN_PROGRESS) {
+ if (META_REFRESH_RECOVERY == decoding_status_) {
+ // Absorb all input data. We've already output page reload HTML.
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return FILTER_NEED_MORE_DATA;
+ }
+ if (PASS_THROUGH == decoding_status_) {
+ // We must pass in available_space, but it will be changed to bytes_used.
+ FilterStatus result = CopyOut(dest_buffer, &available_space);
+ // Accumulate the returned count of bytes_used (a.k.a., available_space).
+ *dest_len += available_space;
+ return result;
+ }
+ DCHECK(false);
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+
+ if (!next_stream_data_ || stream_data_len_ <= 0)
+ return FILTER_NEED_MORE_DATA;
+
+ bool ret = vcdiff_streaming_decoder_->DecodeChunk(
+ next_stream_data_, stream_data_len_, &dest_buffer_excess_);
+ // Assume all data was used in decoding.
+ next_stream_data_ = NULL;
+ source_bytes_ += stream_data_len_;
+ stream_data_len_ = 0;
+ output_bytes_ += dest_buffer_excess_.size();
+ if (!ret) {
+ vcdiff_streaming_decoder_.reset(NULL); // Don't call it again.
+ decoding_status_ = DECODING_ERROR;
+ SdchManager::SdchErrorRecovery(SdchManager::DECODE_BODY_ERROR);
+ return FILTER_ERROR;
+ }
+
+ amount = OutputBufferExcess(dest_buffer, available_space);
+ *dest_len += amount;
+ dest_buffer += amount;
+ available_space -= amount;
+ if (0 == available_space && !dest_buffer_excess_.empty())
+ return FILTER_OK;
+ return FILTER_NEED_MORE_DATA;
+}
+
+Filter::FilterStatus SdchFilter::InitializeDictionary() {
+ const size_t kServerIdLength = 9; // Dictionary hash plus null from server.
+ size_t bytes_needed = kServerIdLength - dictionary_hash_.size();
+ DCHECK_GT(bytes_needed, 0u);
+ if (!next_stream_data_)
+ return FILTER_NEED_MORE_DATA;
+ if (static_cast<size_t>(stream_data_len_) < bytes_needed) {
+ dictionary_hash_.append(next_stream_data_, stream_data_len_);
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return FILTER_NEED_MORE_DATA;
+ }
+ dictionary_hash_.append(next_stream_data_, bytes_needed);
+ DCHECK(kServerIdLength == dictionary_hash_.size());
+ stream_data_len_ -= bytes_needed;
+ DCHECK_LE(0, stream_data_len_);
+ if (stream_data_len_ > 0)
+ next_stream_data_ += bytes_needed;
+ else
+ next_stream_data_ = NULL;
+
+ DCHECK(!dictionary_.get());
+ dictionary_hash_is_plausible_ = true; // Assume plausible, but check.
+
+ SdchManager::Dictionary* dictionary = NULL;
+ if ('\0' == dictionary_hash_[kServerIdLength - 1])
+ SdchManager::Global()->GetVcdiffDictionary(std::string(dictionary_hash_, 0,
+ kServerIdLength - 1),
+ url_, &dictionary);
+ else
+ dictionary_hash_is_plausible_ = false;
+
+ if (!dictionary) {
+ DCHECK(dictionary_hash_.size() == kServerIdLength);
+ // Since dictionary was not found, check to see if hash was even plausible.
+ for (size_t i = 0; i < kServerIdLength - 1; ++i) {
+ char base64_char = dictionary_hash_[i];
+ if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) {
+ dictionary_hash_is_plausible_ = false;
+ break;
+ }
+ }
+ if (dictionary_hash_is_plausible_)
+ SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND);
+ else
+ SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED);
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+ dictionary_ = dictionary;
+ vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder);
+ vcdiff_streaming_decoder_->SetAllowVcdTarget(false);
+ vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(),
+ dictionary_->text().size());
+ decoding_status_ = DECODING_IN_PROGRESS;
+ return FILTER_OK;
+}
+
+int SdchFilter::OutputBufferExcess(char* const dest_buffer,
+ size_t available_space) {
+ if (dest_buffer_excess_.empty())
+ return 0;
+ DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_);
+ size_t amount = std::min(available_space,
+ dest_buffer_excess_.size() - dest_buffer_excess_index_);
+ memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_,
+ amount);
+ dest_buffer_excess_index_ += amount;
+ if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) {
+ DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_);
+ dest_buffer_excess_.clear();
+ dest_buffer_excess_index_ = 0;
+ }
+ return amount;
+}
+
+} // namespace net
diff --git a/chromium/net/base/sdch_filter.h b/chromium/net/base/sdch_filter.h
new file mode 100644
index 00000000000..9f7cf9ae9c8
--- /dev/null
+++ b/chromium/net/base/sdch_filter.h
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 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.
+
+// SdchFilter applies open_vcdiff content decoding to a datastream.
+// This decoding uses a pre-cached dictionary of text fragments to decode
+// (expand) the stream back to its original contents.
+//
+// This SdchFilter internally uses open_vcdiff/vcdec library to do decoding.
+//
+// SdchFilter is also a subclass of Filter. See the latter's header file
+// filter.h for sample usage.
+
+#ifndef NET_BASE_SDCH_FILTER_H_
+#define NET_BASE_SDCH_FILTER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/filter.h"
+#include "net/base/net_export.h"
+#include "net/base/sdch_manager.h"
+
+namespace open_vcdiff {
+class VCDiffStreamingDecoder;
+}
+
+namespace net {
+
+class NET_EXPORT_PRIVATE SdchFilter : public Filter {
+ public:
+ virtual ~SdchFilter();
+
+ // Initializes filter decoding mode and internal control blocks.
+ bool InitDecoding(Filter::FilterType filter_type);
+
+ // Decode the pre-filter data and writes the output into |dest_buffer|
+ // The function returns FilterStatus. See filter.h for its description.
+ //
+ // Upon entry, *dest_len is the total size (in number of chars) of the
+ // destination buffer. Upon exit, *dest_len is the actual number of chars
+ // written into the destination buffer.
+ virtual FilterStatus ReadFilteredData(char* dest_buffer,
+ int* dest_len) OVERRIDE;
+
+ private:
+ // Internal status. Once we enter an error state, we stop processing data.
+ enum DecodingStatus {
+ DECODING_UNINITIALIZED,
+ WAITING_FOR_DICTIONARY_SELECTION,
+ DECODING_IN_PROGRESS,
+ DECODING_ERROR,
+ META_REFRESH_RECOVERY, // Decoding error being handled by a meta-refresh.
+ PASS_THROUGH, // Non-sdch content being passed without alteration.
+ };
+
+ // Only to be instantiated by Filter::Factory.
+ explicit SdchFilter(const FilterContext& filter_context);
+ friend class Filter;
+
+ // Identify the suggested dictionary, and initialize underlying decompressor.
+ Filter::FilterStatus InitializeDictionary();
+
+ // Move data that was internally buffered (after decompression) to the
+ // specified dest_buffer.
+ int OutputBufferExcess(char* const dest_buffer, size_t available_space);
+
+ // Context data from the owner of this filter.
+ const FilterContext& filter_context_;
+
+ // Tracks the status of decoding.
+ // This variable is initialized by InitDecoding and updated only by
+ // ReadFilteredData.
+ DecodingStatus decoding_status_;
+
+ // The underlying decoder that processes data.
+ // This data structure is initialized by InitDecoding and updated in
+ // ReadFilteredData.
+ scoped_ptr<open_vcdiff::VCDiffStreamingDecoder> vcdiff_streaming_decoder_;
+
+ // In case we need to assemble the hash piecemeal, we have a place to store
+ // a part of the hash until we "get all 8 bytes plus a null."
+ std::string dictionary_hash_;
+
+ // After assembling an entire dictionary hash (the first 9 bytes of the
+ // sdch payload, we check to see if it is plausible, meaning it has a null
+ // termination, and has 8 characters that are possible in a net-safe base64
+ // encoding. If the hash is not plausible, then the payload is probably not
+ // an SDCH encoded bundle, and various error recovery strategies can be
+ // attempted.
+ bool dictionary_hash_is_plausible_;
+
+ // We hold an in-memory copy of the dictionary during the entire decoding, as
+ // it is used directly by the VC-DIFF decoding system.
+ // That char* data is part of the dictionary_ we hold a reference to.
+ scoped_refptr<SdchManager::Dictionary> dictionary_;
+
+ // The decoder may demand a larger output buffer than the target of
+ // ReadFilteredData so we buffer the excess output between calls.
+ std::string dest_buffer_excess_;
+ // To avoid moving strings around too much, we save the index into
+ // dest_buffer_excess_ that has the next byte to output.
+ size_t dest_buffer_excess_index_;
+
+ // To get stats on activities, we keep track of source and target bytes.
+ // Visit about:histograms/Sdch to see histogram data.
+ size_t source_bytes_;
+ size_t output_bytes_;
+
+ // Error recovery in content type may add an sdch filter type, in which case
+ // we should gracefully perform pass through if the format is incorrect, or
+ // an applicable dictionary can't be found.
+ bool possible_pass_through_;
+
+ // The URL that is currently being filtered.
+ // This is used to restrict use of a dictionary to a specific URL or path.
+ GURL url_;
+
+ // To facilitate error recovery, allow filter to know if content is text/html
+ // by checking within this mime type (we may do a meta-refresh via html).
+ std::string mime_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(SdchFilter);
+};
+
+} // namespace net
+
+#endif // NET_BASE_SDCH_FILTER_H_
diff --git a/chromium/net/base/sdch_filter_unittest.cc b/chromium/net/base/sdch_filter_unittest.cc
new file mode 100644
index 00000000000..1cc70cb58be
--- /dev/null
+++ b/chromium/net/base/sdch_filter_unittest.cc
@@ -0,0 +1,1393 @@
+// Copyright (c) 2011 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 <limits.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/filter.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mock_filter_context.h"
+#include "net/base/sdch_filter.h"
+#include "net/url_request/url_request_http_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+namespace net {
+
+//------------------------------------------------------------------------------
+// Provide sample data and compression results with a sample VCDIFF dictionary.
+// Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary.
+static const char kTestVcdiffDictionary[] = "DictionaryFor"
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
+// Pre-compression test data. Note that we pad with a lot of highly gzip
+// compressible content to help to exercise the chaining pipeline. That is why
+// there are a PILE of zeros at the start and end.
+// This will ensure that gzip compressed data can be fed to the chain in one
+// gulp, but (with careful selection of intermediate buffers) that it takes
+// several sdch buffers worth of data to satisfy the sdch filter. See detailed
+// CHECK() calls in FilterChaining test for specifics.
+static const char kTestData[] = "0000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000TestData "
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000\n";
+
+// Note SDCH compressed data will include a reference to the SDCH dictionary.
+static const char kSdchCompressedTestData[] =
+ "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001"
+ "00000000000000000000000000000000000000000000000000000000000000000000000000"
+ "TestData 00000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000\n\001S\023\077\001r\r";
+
+//------------------------------------------------------------------------------
+
+class SdchFilterTest : public testing::Test {
+ protected:
+ SdchFilterTest()
+ : test_vcdiff_dictionary_(kTestVcdiffDictionary,
+ sizeof(kTestVcdiffDictionary) - 1),
+ vcdiff_compressed_data_(kSdchCompressedTestData,
+ sizeof(kSdchCompressedTestData) - 1),
+ expanded_(kTestData, sizeof(kTestData) - 1),
+ sdch_manager_(new SdchManager) {
+ }
+
+ std::string NewSdchCompressedData(const std::string dictionary);
+
+ const std::string test_vcdiff_dictionary_;
+ const std::string vcdiff_compressed_data_;
+ const std::string expanded_; // Desired final, decompressed data.
+
+ scoped_ptr<SdchManager> sdch_manager_; // A singleton database.
+};
+
+std::string SdchFilterTest::NewSdchCompressedData(
+ const std::string dictionary) {
+ std::string client_hash;
+ std::string server_hash;
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+
+ // Build compressed data that refers to our dictionary.
+ std::string compressed(server_hash);
+ compressed.append("\0", 1);
+ compressed.append(vcdiff_compressed_data_);
+ return compressed;
+}
+
+//------------------------------------------------------------------------------
+
+
+TEST_F(SdchFilterTest, Hashing) {
+ std::string client_hash, server_hash;
+ std::string dictionary("test contents");
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+
+ EXPECT_EQ(client_hash, "lMQBjS3P");
+ EXPECT_EQ(server_hash, "MyciMVll");
+}
+
+
+//------------------------------------------------------------------------------
+// Provide a generic helper function for trying to filter data.
+// This function repeatedly calls the filter to process data, until the entire
+// source is consumed. The return value from the filter is appended to output.
+// This allows us to vary input and output block sizes in order to test for edge
+// effects (boundary effects?) during the filtering process.
+// This function provides data to the filter in blocks of no-more-than the
+// specified input_block_length. It allows the filter to fill no more than
+// output_buffer_length in any one call to proccess (a.k.a., Read) data, and
+// concatenates all these little output blocks into the singular output string.
+static bool FilterTestData(const std::string& source,
+ size_t input_block_length,
+ const size_t output_buffer_length,
+ Filter* filter, std::string* output) {
+ CHECK_GT(input_block_length, 0u);
+ Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA);
+ size_t source_index = 0;
+ scoped_ptr<char[]> output_buffer(new char[output_buffer_length]);
+ size_t input_amount = std::min(input_block_length,
+ static_cast<size_t>(filter->stream_buffer_size()));
+
+ do {
+ int copy_amount = std::min(input_amount, source.size() - source_index);
+ if (copy_amount > 0 && status == Filter::FILTER_NEED_MORE_DATA) {
+ memcpy(filter->stream_buffer()->data(), source.data() + source_index,
+ copy_amount);
+ filter->FlushStreamBuffer(copy_amount);
+ source_index += copy_amount;
+ }
+ int buffer_length = output_buffer_length;
+ status = filter->ReadData(output_buffer.get(), &buffer_length);
+ output->append(output_buffer.get(), buffer_length);
+ if (status == Filter::FILTER_ERROR)
+ return false;
+ // Callers assume that FILTER_OK with no output buffer means FILTER_DONE.
+ if (Filter::FILTER_OK == status && 0 == buffer_length)
+ return true;
+ if (copy_amount == 0 && buffer_length == 0)
+ return true;
+ } while (1);
+}
+//------------------------------------------------------------------------------
+static std::string NewSdchDictionary(const std::string& domain) {
+ std::string dictionary;
+ if (!domain.empty()) {
+ dictionary.append("Domain: ");
+ dictionary.append(domain);
+ dictionary.append("\n");
+ }
+ dictionary.append("\n");
+ dictionary.append(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1);
+ return dictionary;
+}
+
+//------------------------------------------------------------------------------
+
+TEST_F(SdchFilterTest, EmptyInputOk) {
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+
+ // With no input data, try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
+}
+
+TEST_F(SdchFilterTest, PassThroughWhenTentative) {
+ std::vector<Filter::FilterType> filter_types;
+ // Selective a tentative filter (which can fall back to pass through).
+ filter_types.push_back(Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ // Response code needs to be 200 to allow a pass through.
+ filter_context.SetResponseCode(200);
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ // Supply enough data to force a pass-through mode..
+ std::string non_gzip_content("not GZIPed data");
+
+ char* input_buffer = filter->stream_buffer()->data();
+ int input_buffer_size = filter->stream_buffer_size();
+
+ EXPECT_LT(static_cast<int>(non_gzip_content.size()),
+ input_buffer_size);
+ memcpy(input_buffer, non_gzip_content.data(),
+ non_gzip_content.size());
+ filter->FlushStreamBuffer(non_gzip_content.size());
+
+ // Try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(non_gzip_content.size(),
+ static_cast<size_t>(output_bytes_or_buffer_size));
+ ASSERT_GT(sizeof(output_buffer),
+ static_cast<size_t>(output_bytes_or_buffer_size));
+ output_buffer[output_bytes_or_buffer_size] = '\0';
+ EXPECT_TRUE(non_gzip_content == output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
+}
+
+TEST_F(SdchFilterTest, RefreshBadReturnCode) {
+ std::vector<Filter::FilterType> filter_types;
+ // Selective a tentative filter (which can fall back to pass through).
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ // Response code needs to be 200 to allow a pass through.
+ filter_context.SetResponseCode(403);
+ // Meta refresh will only appear for html content
+ filter_context.SetMimeType("text/html");
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ // Supply enough data to force a pass-through mode, which means we have
+ // provided more than 9 characters that can't be a dictionary hash.
+ std::string non_sdch_content("This is not SDCH");
+
+ char* input_buffer = filter->stream_buffer()->data();
+ int input_buffer_size = filter->stream_buffer_size();
+
+ EXPECT_LT(static_cast<int>(non_sdch_content.size()),
+ input_buffer_size);
+ memcpy(input_buffer, non_sdch_content.data(),
+ non_sdch_content.size());
+ filter->FlushStreamBuffer(non_sdch_content.size());
+
+ // Try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ // We should have read a long and complicated meta-refresh request.
+ EXPECT_TRUE(sizeof(output_buffer) == output_bytes_or_buffer_size);
+ // Check at least the prefix of the return.
+ EXPECT_EQ(0, strncmp(output_buffer,
+ "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
+ sizeof(output_buffer)));
+ EXPECT_EQ(Filter::FILTER_OK, status);
+}
+
+TEST_F(SdchFilterTest, ErrorOnBadReturnCode) {
+ std::vector<Filter::FilterType> filter_types;
+ // Selective a tentative filter (which can fall back to pass through).
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ // Response code needs to be 200 to allow a pass through.
+ filter_context.SetResponseCode(403);
+ // Meta refresh will only appear for html content, so set to something else
+ // to induce an error (we can't meta refresh).
+ filter_context.SetMimeType("anything");
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ // Supply enough data to force a pass-through mode, which means we have
+ // provided more than 9 characters that can't be a dictionary hash.
+ std::string non_sdch_content("This is not SDCH");
+
+ char* input_buffer = filter->stream_buffer()->data();
+ int input_buffer_size = filter->stream_buffer_size();
+
+ EXPECT_LT(static_cast<int>(non_sdch_content.size()),
+ input_buffer_size);
+ memcpy(input_buffer, non_sdch_content.data(),
+ non_sdch_content.size());
+ filter->FlushStreamBuffer(non_sdch_content.size());
+
+ // Try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_ERROR, status);
+}
+
+TEST_F(SdchFilterTest, ErrorOnBadReturnCodeWithHtml) {
+ std::vector<Filter::FilterType> filter_types;
+ // Selective a tentative filter (which can fall back to pass through).
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ // Response code needs to be 200 to allow a pass through.
+ filter_context.SetResponseCode(403);
+ // Meta refresh will only appear for html content
+ filter_context.SetMimeType("text/html");
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ // Supply enough data to force a pass-through mode, which means we have
+ // provided more than 9 characters that can't be a dictionary hash.
+ std::string non_sdch_content("This is not SDCH");
+
+ char* input_buffer = filter->stream_buffer()->data();
+ int input_buffer_size = filter->stream_buffer_size();
+
+ EXPECT_LT(static_cast<int>(non_sdch_content.size()),
+ input_buffer_size);
+ memcpy(input_buffer, non_sdch_content.data(),
+ non_sdch_content.size());
+ filter->FlushStreamBuffer(non_sdch_content.size());
+
+ // Try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ // We should have read a long and complicated meta-refresh request.
+ EXPECT_EQ(sizeof(output_buffer),
+ static_cast<size_t>(output_bytes_or_buffer_size));
+ // Check at least the prefix of the return.
+ EXPECT_EQ(0, strncmp(output_buffer,
+ "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
+ sizeof(output_buffer)));
+ EXPECT_EQ(Filter::FILTER_OK, status);
+}
+
+TEST_F(SdchFilterTest, BasicBadDictionary) {
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+ char output_buffer[20];
+ MockFilterContext filter_context;
+ std::string url_string("http://ignore.com");
+ filter_context.SetURL(GURL(url_string));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ // Supply bogus data (which doesn't yet specify a full dictionary hash).
+ // Dictionary hash is 8 characters followed by a null.
+ std::string dictionary_hash_prefix("123");
+
+ char* input_buffer = filter->stream_buffer()->data();
+ int input_buffer_size = filter->stream_buffer_size();
+
+ EXPECT_LT(static_cast<int>(dictionary_hash_prefix.size()),
+ input_buffer_size);
+ memcpy(input_buffer, dictionary_hash_prefix.data(),
+ dictionary_hash_prefix.size());
+ filter->FlushStreamBuffer(dictionary_hash_prefix.size());
+
+ // With less than a dictionary specifier, try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
+
+ // Provide enough data to complete *a* hash, but it is bogus, and not in our
+ // list of dictionaries, so the filter should error out immediately.
+ std::string dictionary_hash_postfix("4abcd\0", 6);
+
+ CHECK_LT(dictionary_hash_postfix.size(),
+ static_cast<size_t>(input_buffer_size));
+ memcpy(input_buffer, dictionary_hash_postfix.data(),
+ dictionary_hash_postfix.size());
+ filter->FlushStreamBuffer(dictionary_hash_postfix.size());
+
+ // With a non-existant dictionary specifier, try to read output.
+ output_bytes_or_buffer_size = sizeof(output_buffer);
+ status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_ERROR, status);
+
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+}
+
+TEST_F(SdchFilterTest, DictionaryAddOnce) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ // Check we can't add it twice.
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ const std::string kSampleDomain2 = "sdchtest2.com";
+
+ // Construct a second SDCH dictionary from a VCDIFF dictionary.
+ std::string dictionary2(NewSdchDictionary(kSampleDomain2));
+
+ std::string url_string2 = "http://" + kSampleDomain2;
+ GURL url2(url_string2);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary2, url2));
+}
+
+TEST_F(SdchFilterTest, BasicDictionary) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(url);
+
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Decode with really small buffers (size 1) to check for edge effects.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, NoDecodeHttps) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("https://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+// Current failsafe TODO/hack refuses to decode any content that doesn't use
+// http as the scheme (see use of DICTIONARY_SELECTED_FOR_NON_HTTP).
+// The following tests this blockage. Note that blacklisting results, so we
+// we need separate tests for each of these.
+TEST_F(SdchFilterTest, NoDecodeFtp) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("ftp://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+TEST_F(SdchFilterTest, NoDecodeFileColon) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("file://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+TEST_F(SdchFilterTest, NoDecodeAboutColon) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("about://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+TEST_F(SdchFilterTest, NoDecodeJavaScript) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("javascript://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+TEST_F(SdchFilterTest, CanStillDecodeHttp) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL("http://" + kSampleDomain));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ const size_t feed_block_size(100);
+ const size_t output_block_size(100);
+ std::string output;
+
+ EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+}
+
+TEST_F(SdchFilterTest, CrossDomainDictionaryUse) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ // Decode with content arriving from the "wrong" domain.
+ // This tests SdchManager::CanSet().
+ MockFilterContext filter_context;
+ GURL wrong_domain_url("http://www.wrongdomain.com");
+ filter_context.SetURL(wrong_domain_url);
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+ EXPECT_EQ(output.size(), 0u); // No output written.
+
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(wrong_domain_url));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(wrong_domain_url));
+}
+
+TEST_F(SdchFilterTest, DictionaryPathValidation) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ // Create a dictionary with a path restriction, by prefixing dictionary.
+ const std::string path("/special_path/bin");
+ std::string dictionary_with_path("Path: " + path + "\n");
+ dictionary_with_path.append(dictionary);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_with_path, url));
+
+ std::string compressed_for_path(NewSdchCompressedData(dictionary_with_path));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ // Test decode the path data, arriving from a valid path.
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL(url_string + path));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+
+ EXPECT_TRUE(FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Test decode the path data, arriving from a invalid path.
+ filter_context.SetURL(GURL(url_string));
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ EXPECT_FALSE(FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output.size(), 0u); // No output written.
+
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+}
+
+TEST_F(SdchFilterTest, DictionaryPortValidation) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+
+ // Create a dictionary with a port restriction, by prefixing old dictionary.
+ const std::string port("502");
+ std::string dictionary_with_port("Port: " + port + "\n");
+ dictionary_with_port.append("Port: 80\n"); // Add default port.
+ dictionary_with_port.append(dictionary);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_with_port,
+ GURL(url_string + ":" + port)));
+
+ std::string compressed_for_port(NewSdchCompressedData(dictionary_with_port));
+
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ // Test decode the port data, arriving from a valid port.
+ MockFilterContext filter_context;
+ filter_context.SetURL(GURL(url_string + ":" + port));
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Test decode the port data, arriving from a valid (default) port.
+ filter_context.SetURL(GURL(url_string)); // Default port.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Test decode the port data, arriving from a invalid port.
+ filter_context.SetURL(GURL(url_string + ":" + port + "1"));
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ EXPECT_FALSE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output.size(), 0u); // No output written.
+
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+}
+
+//------------------------------------------------------------------------------
+// Helper function to perform gzip compression of data.
+
+static std::string gzip_compress(const std::string &input) {
+ z_stream zlib_stream;
+ memset(&zlib_stream, 0, sizeof(zlib_stream));
+ int code;
+
+ // Initialize zlib
+ code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -MAX_WBITS,
+ 8, // DEF_MEM_LEVEL
+ Z_DEFAULT_STRATEGY);
+
+ CHECK_EQ(Z_OK, code);
+
+ // Fill in zlib control block
+ zlib_stream.next_in = bit_cast<Bytef*>(input.data());
+ zlib_stream.avail_in = input.size();
+
+ // Assume we can compress into similar buffer (add 100 bytes to be sure).
+ size_t gzip_compressed_length = zlib_stream.avail_in + 100;
+ scoped_ptr<char[]> gzip_compressed(new char[gzip_compressed_length]);
+ zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get());
+ zlib_stream.avail_out = gzip_compressed_length;
+
+ // The GZIP header (see RFC 1952):
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |ID1|ID2|CM |FLG| MTIME |XFL|OS |
+ // +---+---+---+---+---+---+---+---+---+---+
+ // ID1 \037
+ // ID2 \213
+ // CM \010 (compression method == DEFLATE)
+ // FLG \000 (special flags that we do not support)
+ // MTIME Unix format modification time (0 means not available)
+ // XFL 2-4? DEFLATE flags
+ // OS ???? Operating system indicator (255 means unknown)
+ //
+ // Header value we generate:
+ const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000',
+ '\000', '\000', '\000', '\002', '\377' };
+ CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader));
+ memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader));
+ zlib_stream.next_out += sizeof(kGZipHeader);
+ zlib_stream.avail_out -= sizeof(kGZipHeader);
+
+ // Do deflate
+ code = deflate(&zlib_stream, Z_FINISH);
+ gzip_compressed_length -= zlib_stream.avail_out;
+ std::string compressed(gzip_compressed.get(), gzip_compressed_length);
+ deflateEnd(&zlib_stream);
+ return compressed;
+}
+
+//------------------------------------------------------------------------------
+
+class SdchFilterChainingTest {
+ public:
+ static Filter* Factory(const std::vector<Filter::FilterType>& types,
+ const FilterContext& context, int size) {
+ return Filter::FactoryForTests(types, context, size);
+ }
+};
+
+// Test that filters can be cascaded (chained) so that the output of one filter
+// is processed by the next one. This is most critical for SDCH, which is
+// routinely followed by gzip (during encoding). The filter we'll test for will
+// do the gzip decoding first, and then decode the SDCH content.
+TEST_F(SdchFilterTest, FilterChaining) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
+
+ // Construct a chained filter.
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+ filter_types.push_back(Filter::FILTER_TYPE_GZIP);
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ const size_t kLargeInputBufferSize(1000); // Used internally in filters.
+ CHECK_GT(kLargeInputBufferSize, gzip_compressed_sdch.size());
+ CHECK_GT(kLargeInputBufferSize, sdch_compressed.size());
+ CHECK_GT(kLargeInputBufferSize, expanded_.size());
+ MockFilterContext filter_context;
+ filter_context.SetURL(url);
+ scoped_ptr<Filter> filter(
+ SdchFilterChainingTest::Factory(filter_types, filter_context,
+ kLargeInputBufferSize));
+ EXPECT_EQ(static_cast<int>(kLargeInputBufferSize),
+ filter->stream_buffer_size());
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ // Make chain process all data.
+ size_t feed_block_size = kLargeInputBufferSize;
+ size_t output_block_size = kLargeInputBufferSize;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a mid-sized internal buffer size.
+ const size_t kMidSizedInputBufferSize(100);
+ // Buffer should be big enough to swallow whole gzip content.
+ CHECK_GT(kMidSizedInputBufferSize, gzip_compressed_sdch.size());
+ // Buffer should be small enough that entire SDCH content can't fit.
+ // We'll go even further, and force the chain to flush the buffer between the
+ // two filters more than once (that is why we multiply by 2).
+ CHECK_LT(kMidSizedInputBufferSize * 2, sdch_compressed.size());
+ filter_context.SetURL(url);
+ filter.reset(
+ SdchFilterChainingTest::Factory(filter_types, filter_context,
+ kMidSizedInputBufferSize));
+ EXPECT_EQ(static_cast<int>(kMidSizedInputBufferSize),
+ filter->stream_buffer_size());
+
+ feed_block_size = kMidSizedInputBufferSize;
+ output_block_size = kMidSizedInputBufferSize;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny input and output buffer to cover edge effects.
+ filter.reset(SdchFilterChainingTest::Factory(filter_types, filter_context,
+ kLargeInputBufferSize));
+ EXPECT_EQ(static_cast<int>(kLargeInputBufferSize),
+ filter->stream_buffer_size());
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, DefaultGzipIfSdch) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
+
+ // Only claim to have sdch content, but really use the gzipped sdch content.
+ // System should automatically add the missing (optional) gzip.
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_SDCH);
+
+ MockFilterContext filter_context;
+ filter_context.SetMimeType("anything/mime");
+ filter_context.SetSdchResponse(true);
+ Filter::FixupEncodingTypes(filter_context, &filter_types);
+ ASSERT_EQ(filter_types.size(), 2u);
+ EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH);
+ EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ filter_context.SetURL(url);
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, AcceptGzipSdchIfGzip) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
+
+ // Some proxies strip the content encoding statement down to a mere gzip, but
+ // pass through the original content (with full sdch,gzip encoding).
+ // Only claim to have gzip content, but really use the gzipped sdch content.
+ // System should automatically add the missing (optional) sdch.
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_GZIP);
+
+ MockFilterContext filter_context;
+ filter_context.SetMimeType("anything/mime");
+ filter_context.SetSdchResponse(true);
+ Filter::FixupEncodingTypes(filter_context, &filter_types);
+ ASSERT_EQ(filter_types.size(), 3u);
+ EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
+ EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP);
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ filter_context.SetURL(url);
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, DefaultSdchGzipIfEmpty) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
+
+ // Only claim to have non-encoded content, but really use the gzipped sdch
+ // content.
+ // System should automatically add the missing (optional) sdch,gzip.
+ std::vector<Filter::FilterType> filter_types;
+
+ MockFilterContext filter_context;
+ filter_context.SetMimeType("anything/mime");
+ filter_context.SetSdchResponse(true);
+ Filter::FixupEncodingTypes(filter_context, &filter_types);
+ ASSERT_EQ(filter_types.size(), 2u);
+ EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ filter_context.SetURL(url);
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, AcceptGzipGzipSdchIfGzip) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Vodaphone (UK) Mobile Broadband provides double gzipped sdch with a content
+ // encoding of merely gzip (apparently, only listing the extra level of
+ // wrapper compression they added, but discarding the actual content encoding.
+ // Use Gzip to double compress the sdch sdch_compressed data.
+ std::string double_gzip_compressed_sdch = gzip_compress(gzip_compress(
+ sdch_compressed));
+
+ // Only claim to have gzip content, but really use the double gzipped sdch
+ // content.
+ // System should automatically add the missing (optional) sdch, gzip decoders.
+ std::vector<Filter::FilterType> filter_types;
+ filter_types.push_back(Filter::FILTER_TYPE_GZIP);
+
+ MockFilterContext filter_context;
+ filter_context.SetMimeType("anything/mime");
+ filter_context.SetSdchResponse(true);
+ Filter::FixupEncodingTypes(filter_context, &filter_types);
+ ASSERT_EQ(filter_types.size(), 3u);
+ EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
+ EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
+ EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP);
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ filter_context.SetURL(url);
+ scoped_ptr<Filter> filter(Filter::Factory(filter_types, filter_context));
+
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filter_types, filter_context));
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, DomainSupported) {
+ GURL google_url("http://www.google.com");
+
+ net::SdchManager::EnableSdchSupport(false);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(google_url));
+ net::SdchManager::EnableSdchSupport(true);
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(google_url));
+}
+
+TEST_F(SdchFilterTest, DomainBlacklisting) {
+ GURL test_url("http://www.test.com");
+ GURL google_url("http://www.google.com");
+
+ SdchManager::BlacklistDomain(test_url);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(google_url));
+
+ SdchManager::BlacklistDomain(google_url);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(google_url));
+}
+
+TEST_F(SdchFilterTest, DomainBlacklistingCaseSensitivity) {
+ GURL test_url("http://www.TesT.com");
+ GURL test2_url("http://www.tEst.com");
+
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(test2_url));
+ SdchManager::BlacklistDomain(test_url);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(test2_url));
+}
+
+TEST_F(SdchFilterTest, BlacklistingReset) {
+ GURL gurl("http://mytest.DoMain.com");
+ std::string domain(gurl.host());
+
+ SdchManager::ClearBlacklistings();
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), 0);
+ EXPECT_EQ(SdchManager::BlacklistDomainExponential(domain), 0);
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(gurl));
+}
+
+TEST_F(SdchFilterTest, BlacklistingSingleBlacklist) {
+ GURL gurl("http://mytest.DoMain.com");
+ std::string domain(gurl.host());
+ SdchManager::ClearBlacklistings();
+
+ SdchManager::Global()->BlacklistDomain(gurl);
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), 1);
+ EXPECT_EQ(SdchManager::BlacklistDomainExponential(domain), 1);
+
+ // Check that any domain lookup reduces the blacklist counter.
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(gurl));
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), 0);
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(gurl));
+}
+
+TEST_F(SdchFilterTest, BlacklistingExponential) {
+ GURL gurl("http://mytest.DoMain.com");
+ std::string domain(gurl.host());
+ SdchManager::ClearBlacklistings();
+
+ int exponential = 1;
+ for (int i = 1; i < 100; ++i) {
+ SdchManager::Global()->BlacklistDomain(gurl);
+ EXPECT_EQ(SdchManager::BlacklistDomainExponential(domain), exponential);
+
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), exponential);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(gurl));
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), exponential - 1);
+
+ // Simulate a large number of domain checks (which eventually remove the
+ // blacklisting).
+ SdchManager::ClearDomainBlacklisting(domain);
+ EXPECT_EQ(SdchManager::BlackListDomainCount(domain), 0);
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(gurl));
+
+ // Predict what exponential backoff will be.
+ exponential = 1 + 2 * exponential;
+ if (exponential < 0)
+ exponential = INT_MAX; // We don't wrap.
+ }
+}
+
+TEST_F(SdchFilterTest, CanSetExactMatchDictionary) {
+ std::string dictionary_domain("x.y.z.google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Perfect match should work.
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://" + dictionary_domain)));
+}
+
+TEST_F(SdchFilterTest, FailToSetDomainMismatchDictionary) {
+ std::string dictionary_domain("x.y.z.google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Fail the "domain match" requirement.
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://y.z.google.com")));
+}
+
+TEST_F(SdchFilterTest, FailToSetDotHostPrefixDomainDictionary) {
+ std::string dictionary_domain("x.y.z.google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Fail the HD with D being the domain and H having a dot requirement.
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://w.x.y.z.google.com")));
+}
+
+TEST_F(SdchFilterTest, FailToSetRepeatPrefixWithDotDictionary) {
+ // Make sure that a prefix that matches the domain postfix won't confuse
+ // the validation checks.
+ std::string dictionary_domain("www.google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Fail the HD with D being the domain and H having a dot requirement.
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://www.google.com.www.google.com")));
+}
+
+TEST_F(SdchFilterTest, CanSetLeadingDotDomainDictionary) {
+ // Make sure that a prefix that matches the domain postfix won't confuse
+ // the validation checks.
+ std::string dictionary_domain(".google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Verify that a leading dot in the domain is acceptable, as long as the host
+ // name does not contain any dots preceding the matched domain name.
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://www.google.com")));
+}
+
+// Make sure the order of the tests is not helping us or confusing things.
+// See test CanSetExactMatchDictionary above for first try.
+TEST_F(SdchFilterTest, CanStillSetExactMatchDictionary) {
+ std::string dictionary_domain("x.y.z.google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ // Perfect match should *STILL* work.
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://" + dictionary_domain)));
+}
+
+// Make sure the DOS protection precludes the addition of too many dictionaries.
+TEST_F(SdchFilterTest, TooManyDictionaries) {
+ std::string dictionary_domain(".google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ size_t count = 0;
+ while (count <= SdchManager::kMaxDictionaryCount + 1) {
+ if (!sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://www.google.com")))
+ break;
+
+ dictionary_text += " "; // Create dictionary with different SHA signature.
+ ++count;
+ }
+ EXPECT_EQ(SdchManager::kMaxDictionaryCount, count);
+}
+
+TEST_F(SdchFilterTest, DictionaryNotTooLarge) {
+ std::string dictionary_domain(".google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ dictionary_text.append(
+ SdchManager::kMaxDictionarySize - dictionary_text.size(), ' ');
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://" + dictionary_domain)));
+}
+
+TEST_F(SdchFilterTest, DictionaryTooLarge) {
+ std::string dictionary_domain(".google.com");
+ std::string dictionary_text(NewSdchDictionary(dictionary_domain));
+
+ dictionary_text.append(
+ SdchManager::kMaxDictionarySize + 1 - dictionary_text.size(), ' ');
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary_text,
+ GURL("http://" + dictionary_domain)));
+}
+
+TEST_F(SdchFilterTest, PathMatch) {
+ bool (*PathMatch)(const std::string& path, const std::string& restriction) =
+ SdchManager::Dictionary::PathMatch;
+ // Perfect match is supported.
+ EXPECT_TRUE(PathMatch("/search", "/search"));
+ EXPECT_TRUE(PathMatch("/search/", "/search/"));
+
+ // Prefix only works if last character of restriction is a slash, or first
+ // character in path after a match is a slash. Validate each case separately.
+
+ // Rely on the slash in the path (not at the end of the restriction).
+ EXPECT_TRUE(PathMatch("/search/something", "/search"));
+ EXPECT_TRUE(PathMatch("/search/s", "/search"));
+ EXPECT_TRUE(PathMatch("/search/other", "/search"));
+ EXPECT_TRUE(PathMatch("/search/something", "/search"));
+
+ // Rely on the slash at the end of the restriction.
+ EXPECT_TRUE(PathMatch("/search/something", "/search/"));
+ EXPECT_TRUE(PathMatch("/search/s", "/search/"));
+ EXPECT_TRUE(PathMatch("/search/other", "/search/"));
+ EXPECT_TRUE(PathMatch("/search/something", "/search/"));
+
+ // Make sure less that sufficient prefix match is false.
+ EXPECT_FALSE(PathMatch("/sear", "/search"));
+ EXPECT_FALSE(PathMatch("/", "/search"));
+ EXPECT_FALSE(PathMatch(std::string(), "/search"));
+
+ // Add examples with several levels of direcories in the restriction.
+ EXPECT_FALSE(PathMatch("/search/something", "search/s"));
+ EXPECT_FALSE(PathMatch("/search/", "/search/s"));
+
+ // Make sure adding characters to path will also fail.
+ EXPECT_FALSE(PathMatch("/searching", "/search/"));
+ EXPECT_FALSE(PathMatch("/searching", "/search"));
+
+ // Make sure we're case sensitive.
+ EXPECT_FALSE(PathMatch("/ABC", "/abc"));
+ EXPECT_FALSE(PathMatch("/abc", "/ABC"));
+}
+
+// The following are only applicable while we have a latency test in the code,
+// and can be removed when that functionality is stripped.
+TEST_F(SdchFilterTest, LatencyTestControls) {
+ GURL url("http://www.google.com");
+ GURL url2("http://www.google2.com");
+
+ // First make sure we default to false.
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url));
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url2));
+
+ // That we can set each to true.
+ sdch_manager_->SetAllowLatencyExperiment(url, true);
+ EXPECT_TRUE(sdch_manager_->AllowLatencyExperiment(url));
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url2));
+
+ sdch_manager_->SetAllowLatencyExperiment(url2, true);
+ EXPECT_TRUE(sdch_manager_->AllowLatencyExperiment(url));
+ EXPECT_TRUE(sdch_manager_->AllowLatencyExperiment(url2));
+
+ // And can reset them to false.
+ sdch_manager_->SetAllowLatencyExperiment(url, false);
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url));
+ EXPECT_TRUE(sdch_manager_->AllowLatencyExperiment(url2));
+
+ sdch_manager_->SetAllowLatencyExperiment(url2, false);
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url));
+ EXPECT_FALSE(sdch_manager_->AllowLatencyExperiment(url2));
+}
+
+} // namespace net
diff --git a/chromium/net/base/sdch_manager.cc b/chromium/net/base/sdch_manager.cc
new file mode 100644
index 00000000000..17883b11613
--- /dev/null
+++ b/chromium/net/base/sdch_manager.cc
@@ -0,0 +1,566 @@
+// Copyright (c) 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.
+
+#include "net/base/sdch_manager.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "crypto/sha2.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/url_request/url_request_http_job.h"
+
+namespace net {
+
+//------------------------------------------------------------------------------
+// static
+const size_t SdchManager::kMaxDictionarySize = 1000000;
+
+// static
+const size_t SdchManager::kMaxDictionaryCount = 20;
+
+// static
+SdchManager* SdchManager::global_ = NULL;
+
+// static
+bool SdchManager::g_sdch_enabled_ = true;
+
+//------------------------------------------------------------------------------
+SdchManager::Dictionary::Dictionary(const std::string& dictionary_text,
+ size_t offset,
+ const std::string& client_hash,
+ const GURL& gurl,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration,
+ const std::set<int>& ports)
+ : text_(dictionary_text, offset),
+ client_hash_(client_hash),
+ url_(gurl),
+ domain_(domain),
+ path_(path),
+ expiration_(expiration),
+ ports_(ports) {
+}
+
+SdchManager::Dictionary::~Dictionary() {
+}
+
+bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(target_url))
+ return false;
+ /* The specific rules of when a dictionary should be advertised in an
+ Avail-Dictionary header are modeled after the rules for cookie scoping. The
+ terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A
+ dictionary may be advertised in the Avail-Dictionaries header exactly when
+ all of the following are true:
+ 1. The server's effective host name domain-matches the Domain attribute of
+ the dictionary.
+ 2. If the dictionary has a Port attribute, the request port is one of the
+ ports listed in the Port attribute.
+ 3. The request URI path-matches the path header of the dictionary.
+ 4. The request is not an HTTPS request.
+ */
+ if (!DomainMatch(target_url, domain_))
+ return false;
+ if (!ports_.empty() && 0 == ports_.count(target_url.EffectiveIntPort()))
+ return false;
+ if (path_.size() && !PathMatch(target_url.path(), path_))
+ return false;
+ if (target_url.SchemeIsSecure())
+ return false;
+ if (base::Time::Now() > expiration_)
+ return false;
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// Security functions restricting loads and use of dictionaries.
+
+// static
+bool SdchManager::Dictionary::CanSet(const std::string& domain,
+ const std::string& path,
+ const std::set<int>& ports,
+ const GURL& dictionary_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url))
+ return false;
+ /*
+ A dictionary is invalid and must not be stored if any of the following are
+ true:
+ 1. The dictionary has no Domain attribute.
+ 2. The effective host name that derives from the referer URL host name does
+ not domain-match the Domain attribute.
+ 3. The Domain attribute is a top level domain.
+ 4. The referer URL host is a host domain name (not IP address) and has the
+ form HD, where D is the value of the Domain attribute, and H is a string
+ that contains one or more dots.
+ 5. If the dictionary has a Port attribute and the referer URL's port was not
+ in the list.
+ */
+
+ // TODO(jar): Redirects in dictionary fetches might plausibly be problematic,
+ // and hence the conservative approach is to not allow any redirects (if there
+ // were any... then don't allow the dictionary to be set).
+
+ if (domain.empty()) {
+ SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER);
+ return false; // Domain is required.
+ }
+ if (registry_controlled_domains::GetDomainAndRegistry(
+ domain,
+ registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES).empty()) {
+ SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN);
+ return false; // domain was a TLD.
+ }
+ if (!Dictionary::DomainMatch(dictionary_url, domain)) {
+ SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL);
+ return false;
+ }
+
+ std::string referrer_url_host = dictionary_url.host();
+ size_t postfix_domain_index = referrer_url_host.rfind(domain);
+ // See if it is indeed a postfix, or just an internal string.
+ if (referrer_url_host.size() == postfix_domain_index + domain.size()) {
+ // It is a postfix... so check to see if there's a dot in the prefix.
+ size_t end_of_host_index = referrer_url_host.find_first_of('.');
+ if (referrer_url_host.npos != end_of_host_index &&
+ end_of_host_index < postfix_domain_index) {
+ SdchErrorRecovery(DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX);
+ return false;
+ }
+ }
+
+ if (!ports.empty()
+ && 0 == ports.count(dictionary_url.EffectiveIntPort())) {
+ SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL);
+ return false;
+ }
+ return true;
+}
+
+// static
+bool SdchManager::Dictionary::CanUse(const GURL& referring_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(referring_url))
+ return false;
+ /*
+ 1. The request URL's host name domain-matches the Domain attribute of the
+ dictionary.
+ 2. If the dictionary has a Port attribute, the request port is one of the
+ ports listed in the Port attribute.
+ 3. The request URL path-matches the path attribute of the dictionary.
+ 4. The request is not an HTTPS request.
+*/
+ if (!DomainMatch(referring_url, domain_)) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN);
+ return false;
+ }
+ if (!ports_.empty()
+ && 0 == ports_.count(referring_url.EffectiveIntPort())) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST);
+ return false;
+ }
+ if (path_.size() && !PathMatch(referring_url.path(), path_)) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH);
+ return false;
+ }
+ if (referring_url.SchemeIsSecure()) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
+ return false;
+ }
+
+ // TODO(jar): Remove overly restrictive failsafe test (added per security
+ // review) when we have a need to be more general.
+ if (!referring_url.SchemeIs("http")) {
+ SdchErrorRecovery(ATTEMPT_TO_DECODE_NON_HTTP_DATA);
+ return false;
+ }
+
+ return true;
+}
+
+bool SdchManager::Dictionary::PathMatch(const std::string& path,
+ const std::string& restriction) {
+ /* Must be either:
+ 1. P2 is equal to P1
+ 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the
+ character following P2 in P1 is "/".
+ */
+ if (path == restriction)
+ return true;
+ size_t prefix_length = restriction.size();
+ if (prefix_length > path.size())
+ return false; // Can't be a prefix.
+ if (0 != path.compare(0, prefix_length, restriction))
+ return false;
+ return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/';
+}
+
+// static
+bool SdchManager::Dictionary::DomainMatch(const GURL& gurl,
+ const std::string& restriction) {
+ // TODO(jar): This is not precisely a domain match definition.
+ return gurl.DomainIs(restriction.data(), restriction.size());
+}
+
+//------------------------------------------------------------------------------
+SdchManager::SdchManager() {
+ DCHECK(!global_);
+ DCHECK(CalledOnValidThread());
+ global_ = this;
+}
+
+SdchManager::~SdchManager() {
+ DCHECK_EQ(this, global_);
+ DCHECK(CalledOnValidThread());
+ while (!dictionaries_.empty()) {
+ DictionaryMap::iterator it = dictionaries_.begin();
+ it->second->Release();
+ dictionaries_.erase(it->first);
+ }
+ global_ = NULL;
+}
+
+// static
+void SdchManager::Shutdown() {
+ EnableSdchSupport(false);
+ if (!global_ )
+ return;
+ global_->set_sdch_fetcher(NULL);
+}
+
+// static
+SdchManager* SdchManager::Global() {
+ return global_;
+}
+
+// static
+void SdchManager::SdchErrorRecovery(ProblemCodes problem) {
+ UMA_HISTOGRAM_ENUMERATION("Sdch3.ProblemCodes_4", problem, MAX_PROBLEM_CODE);
+}
+
+void SdchManager::set_sdch_fetcher(SdchFetcher* fetcher) {
+ DCHECK(CalledOnValidThread());
+ fetcher_.reset(fetcher);
+}
+
+// static
+void SdchManager::EnableSdchSupport(bool enabled) {
+ g_sdch_enabled_ = enabled;
+}
+
+// static
+void SdchManager::BlacklistDomain(const GURL& url) {
+ if (!global_ )
+ return;
+ global_->SetAllowLatencyExperiment(url, false);
+
+ std::string domain(StringToLowerASCII(url.host()));
+ int count = global_->blacklisted_domains_[domain];
+ if (count > 0)
+ return; // Domain is already blacklisted.
+
+ count = 1 + 2 * global_->exponential_blacklist_count[domain];
+ if (count > 0)
+ global_->exponential_blacklist_count[domain] = count;
+ else
+ count = INT_MAX;
+
+ global_->blacklisted_domains_[domain] = count;
+}
+
+// static
+void SdchManager::BlacklistDomainForever(const GURL& url) {
+ if (!global_ )
+ return;
+ global_->SetAllowLatencyExperiment(url, false);
+
+ std::string domain(StringToLowerASCII(url.host()));
+ global_->exponential_blacklist_count[domain] = INT_MAX;
+ global_->blacklisted_domains_[domain] = INT_MAX;
+}
+
+// static
+void SdchManager::ClearBlacklistings() {
+ Global()->blacklisted_domains_.clear();
+ Global()->exponential_blacklist_count.clear();
+}
+
+// static
+void SdchManager::ClearDomainBlacklisting(const std::string& domain) {
+ Global()->blacklisted_domains_.erase(StringToLowerASCII(domain));
+}
+
+// static
+int SdchManager::BlackListDomainCount(const std::string& domain) {
+ if (Global()->blacklisted_domains_.end() ==
+ Global()->blacklisted_domains_.find(domain))
+ return 0;
+ return Global()->blacklisted_domains_[StringToLowerASCII(domain)];
+}
+
+// static
+int SdchManager::BlacklistDomainExponential(const std::string& domain) {
+ if (Global()->exponential_blacklist_count.end() ==
+ Global()->exponential_blacklist_count.find(domain))
+ return 0;
+ return Global()->exponential_blacklist_count[StringToLowerASCII(domain)];
+}
+
+bool SdchManager::IsInSupportedDomain(const GURL& url) {
+ DCHECK(CalledOnValidThread());
+ if (!g_sdch_enabled_ )
+ return false;
+
+ if (blacklisted_domains_.empty())
+ return true;
+
+ std::string domain(StringToLowerASCII(url.host()));
+ DomainCounter::iterator it = blacklisted_domains_.find(domain);
+ if (blacklisted_domains_.end() == it)
+ return true;
+
+ int count = it->second - 1;
+ if (count > 0)
+ blacklisted_domains_[domain] = count;
+ else
+ blacklisted_domains_.erase(domain);
+ SdchErrorRecovery(DOMAIN_BLACKLIST_INCLUDES_TARGET);
+ return false;
+}
+
+void SdchManager::FetchDictionary(const GURL& request_url,
+ const GURL& dictionary_url) {
+ DCHECK(CalledOnValidThread());
+ if (SdchManager::Global()->CanFetchDictionary(request_url, dictionary_url) &&
+ fetcher_.get())
+ fetcher_->Schedule(dictionary_url);
+}
+
+bool SdchManager::CanFetchDictionary(const GURL& referring_url,
+ const GURL& dictionary_url) const {
+ DCHECK(CalledOnValidThread());
+ /* The user agent may retrieve a dictionary from the dictionary URL if all of
+ the following are true:
+ 1 The dictionary URL host name matches the referrer URL host name
+ 2 The dictionary URL host name domain matches the parent domain of the
+ referrer URL host name
+ 3 The parent domain of the referrer URL host name is not a top level
+ domain
+ 4 The dictionary URL is not an HTTPS URL.
+ */
+ // Item (1) above implies item (2). Spec should be updated.
+ // I take "host name match" to be "is identical to"
+ if (referring_url.host() != dictionary_url.host()) {
+ SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST);
+ return false;
+ }
+ if (referring_url.SchemeIs("https")) {
+ SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL);
+ return false;
+ }
+
+ // TODO(jar): Remove this failsafe conservative hack which is more restrictive
+ // than current SDCH spec when needed, and justified by security audit.
+ if (!referring_url.SchemeIs("http")) {
+ SdchErrorRecovery(DICTIONARY_SELECTED_FROM_NON_HTTP);
+ return false;
+ }
+
+ return true;
+}
+
+bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
+ const GURL& dictionary_url) {
+ DCHECK(CalledOnValidThread());
+ std::string client_hash;
+ std::string server_hash;
+ GenerateHash(dictionary_text, &client_hash, &server_hash);
+ if (dictionaries_.find(server_hash) != dictionaries_.end()) {
+ SdchErrorRecovery(DICTIONARY_ALREADY_LOADED);
+ return false; // Already loaded.
+ }
+
+ std::string domain, path;
+ std::set<int> ports;
+ base::Time expiration(base::Time::Now() + base::TimeDelta::FromDays(30));
+
+ if (dictionary_text.empty()) {
+ SdchErrorRecovery(DICTIONARY_HAS_NO_TEXT);
+ return false; // Missing header.
+ }
+
+ size_t header_end = dictionary_text.find("\n\n");
+ if (std::string::npos == header_end) {
+ SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER);
+ return false; // Missing header.
+ }
+ size_t line_start = 0; // Start of line being parsed.
+ while (1) {
+ size_t line_end = dictionary_text.find('\n', line_start);
+ DCHECK(std::string::npos != line_end);
+ DCHECK_LE(line_end, header_end);
+
+ size_t colon_index = dictionary_text.find(':', line_start);
+ if (std::string::npos == colon_index) {
+ SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON);
+ return false; // Illegal line missing a colon.
+ }
+
+ if (colon_index > line_end)
+ break;
+
+ size_t value_start = dictionary_text.find_first_not_of(" \t",
+ colon_index + 1);
+ if (std::string::npos != value_start) {
+ if (value_start >= line_end)
+ break;
+ std::string name(dictionary_text, line_start, colon_index - line_start);
+ std::string value(dictionary_text, value_start, line_end - value_start);
+ name = StringToLowerASCII(name);
+ if (name == "domain") {
+ domain = value;
+ } else if (name == "path") {
+ path = value;
+ } else if (name == "format-version") {
+ if (value != "1.0")
+ return false;
+ } else if (name == "max-age") {
+ int64 seconds;
+ base::StringToInt64(value, &seconds);
+ expiration = base::Time::Now() + base::TimeDelta::FromSeconds(seconds);
+ } else if (name == "port") {
+ int port;
+ base::StringToInt(value, &port);
+ if (port >= 0)
+ ports.insert(port);
+ }
+ }
+
+ if (line_end >= header_end)
+ break;
+ line_start = line_end + 1;
+ }
+
+ if (!Dictionary::CanSet(domain, path, ports, dictionary_url))
+ return false;
+
+ // TODO(jar): Remove these hacks to preclude a DOS attack involving piles of
+ // useless dictionaries. We should probably have a cache eviction plan,
+ // instead of just blocking additions. For now, with the spec in flux, it
+ // is probably not worth doing eviction handling.
+ if (kMaxDictionarySize < dictionary_text.size()) {
+ SdchErrorRecovery(DICTIONARY_IS_TOO_LARGE);
+ return false;
+ }
+ if (kMaxDictionaryCount <= dictionaries_.size()) {
+ SdchErrorRecovery(DICTIONARY_COUNT_EXCEEDED);
+ return false;
+ }
+
+ UMA_HISTOGRAM_COUNTS("Sdch3.Dictionary size loaded", dictionary_text.size());
+ DVLOG(1) << "Loaded dictionary with client hash " << client_hash
+ << " and server hash " << server_hash;
+ Dictionary* dictionary =
+ new Dictionary(dictionary_text, header_end + 2, client_hash,
+ dictionary_url, domain, path, expiration, ports);
+ dictionary->AddRef();
+ dictionaries_[server_hash] = dictionary;
+ return true;
+}
+
+void SdchManager::GetVcdiffDictionary(const std::string& server_hash,
+ const GURL& referring_url, Dictionary** dictionary) {
+ DCHECK(CalledOnValidThread());
+ *dictionary = NULL;
+ DictionaryMap::iterator it = dictionaries_.find(server_hash);
+ if (it == dictionaries_.end()) {
+ return;
+ }
+ Dictionary* matching_dictionary = it->second;
+ if (!matching_dictionary->CanUse(referring_url))
+ return;
+ *dictionary = matching_dictionary;
+}
+
+// TODO(jar): If we have evictions from the dictionaries_, then we need to
+// change this interface to return a list of reference counted Dictionary
+// instances that can be used if/when a server specifies one.
+void SdchManager::GetAvailDictionaryList(const GURL& target_url,
+ std::string* list) {
+ DCHECK(CalledOnValidThread());
+ int count = 0;
+ for (DictionaryMap::iterator it = dictionaries_.begin();
+ it != dictionaries_.end(); ++it) {
+ if (!it->second->CanAdvertise(target_url))
+ continue;
+ ++count;
+ if (!list->empty())
+ list->append(",");
+ list->append(it->second->client_hash());
+ }
+ // Watch to see if we have corrupt or numerous dictionaries.
+ if (count > 0)
+ UMA_HISTOGRAM_COUNTS("Sdch3.Advertisement_Count", count);
+}
+
+// static
+void SdchManager::GenerateHash(const std::string& dictionary_text,
+ std::string* client_hash, std::string* server_hash) {
+ char binary_hash[32];
+ crypto::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash));
+
+ std::string first_48_bits(&binary_hash[0], 6);
+ std::string second_48_bits(&binary_hash[6], 6);
+ UrlSafeBase64Encode(first_48_bits, client_hash);
+ UrlSafeBase64Encode(second_48_bits, server_hash);
+
+ DCHECK_EQ(server_hash->length(), 8u);
+ DCHECK_EQ(client_hash->length(), 8u);
+}
+
+//------------------------------------------------------------------------------
+// Methods for supporting latency experiments.
+
+bool SdchManager::AllowLatencyExperiment(const GURL& url) const {
+ DCHECK(CalledOnValidThread());
+ return allow_latency_experiment_.end() !=
+ allow_latency_experiment_.find(url.host());
+}
+
+void SdchManager::SetAllowLatencyExperiment(const GURL& url, bool enable) {
+ DCHECK(CalledOnValidThread());
+ if (enable) {
+ allow_latency_experiment_.insert(url.host());
+ return;
+ }
+ ExperimentSet::iterator it = allow_latency_experiment_.find(url.host());
+ if (allow_latency_experiment_.end() == it)
+ return; // It was already erased, or never allowed.
+ SdchErrorRecovery(LATENCY_TEST_DISALLOWED);
+ allow_latency_experiment_.erase(it);
+}
+
+// static
+void SdchManager::UrlSafeBase64Encode(const std::string& input,
+ std::string* output) {
+ // Since this is only done during a dictionary load, and hashes are only 8
+ // characters, we just do the simple fixup, rather than rewriting the encoder.
+ base::Base64Encode(input, output);
+ for (size_t i = 0; i < output->size(); ++i) {
+ switch (output->data()[i]) {
+ case '+':
+ (*output)[i] = '-';
+ continue;
+ case '/':
+ (*output)[i] = '_';
+ continue;
+ default:
+ continue;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/sdch_manager.h b/chromium/net/base/sdch_manager.h
new file mode 100644
index 00000000000..4f45966dd5b
--- /dev/null
+++ b/chromium/net/base/sdch_manager.h
@@ -0,0 +1,372 @@
+// Copyright (c) 2011 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.
+
+// Provides global database of differential decompression dictionaries for the
+// SDCH filter (processes sdch enconded content).
+
+// Exactly one instance of SdchManager is built, and all references are made
+// into that collection.
+//
+// The SdchManager maintains a collection of memory resident dictionaries. It
+// can find a dictionary (based on a server specification of a hash), store a
+// dictionary, and make judgements about what URLs can use, set, etc. a
+// dictionary.
+
+// These dictionaries are acquired over the net, and include a header
+// (containing metadata) as well as a VCDIFF dictionary (for use by a VCDIFF
+// module) to decompress data.
+
+#ifndef NET_BASE_SDCH_MANAGER_H_
+#define NET_BASE_SDCH_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+//------------------------------------------------------------------------------
+// Create a public interface to help us load SDCH dictionaries.
+// The SdchManager class allows registration to support this interface.
+// A browser may register a fetcher that is used by the dictionary managers to
+// get data from a specified URL. This allows us to use very high level browser
+// functionality in this base (when the functionaity can be provided).
+class SdchFetcher {
+ public:
+ SdchFetcher() {}
+ virtual ~SdchFetcher() {}
+
+ // The Schedule() method is called when there is a need to get a dictionary
+ // from a server. The callee is responsible for getting that dictionary_text,
+ // and then calling back to AddSdchDictionary() to the SdchManager instance.
+ virtual void Schedule(const GURL& dictionary_url) = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SdchFetcher);
+};
+
+//------------------------------------------------------------------------------
+
+class NET_EXPORT SdchManager : public NON_EXPORTED_BASE(base::NonThreadSafe) {
+ public:
+ // A list of errors that appeared and were either resolved, or used to turn
+ // off sdch encoding.
+ enum ProblemCodes {
+ MIN_PROBLEM_CODE,
+
+ // Content-encoding correction problems.
+ ADDED_CONTENT_ENCODING = 1,
+ FIXED_CONTENT_ENCODING = 2,
+ FIXED_CONTENT_ENCODINGS = 3,
+
+ // Content decoding errors.
+ DECODE_HEADER_ERROR = 4,
+ DECODE_BODY_ERROR = 5,
+
+ // More content-encoding correction problems.
+ OPTIONAL_GUNZIP_ENCODING_ADDED = 6,
+
+ // Content encoding correction when we're not even tagged as HTML!?!
+ BINARY_ADDED_CONTENT_ENCODING = 7,
+ BINARY_FIXED_CONTENT_ENCODING = 8,
+ BINARY_FIXED_CONTENT_ENCODINGS = 9,
+
+ // Dictionary selection for use problems.
+ DICTIONARY_FOUND_HAS_WRONG_DOMAIN = 10,
+ DICTIONARY_FOUND_HAS_WRONG_PORT_LIST = 11,
+ DICTIONARY_FOUND_HAS_WRONG_PATH = 12,
+ DICTIONARY_FOUND_HAS_WRONG_SCHEME = 13,
+ DICTIONARY_HASH_NOT_FOUND = 14,
+ DICTIONARY_HASH_MALFORMED = 15,
+
+ // Dictionary saving problems.
+ DICTIONARY_HAS_NO_HEADER = 20,
+ DICTIONARY_HEADER_LINE_MISSING_COLON = 21,
+ DICTIONARY_MISSING_DOMAIN_SPECIFIER = 22,
+ DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN = 23,
+ DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL = 24,
+ DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL = 25,
+ DICTIONARY_HAS_NO_TEXT = 26,
+ DICTIONARY_REFERER_URL_HAS_DOT_IN_PREFIX = 27,
+
+ // Dictionary loading problems.
+ DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST = 30,
+ DICTIONARY_SELECTED_FOR_SSL = 31,
+ DICTIONARY_ALREADY_LOADED = 32,
+ DICTIONARY_SELECTED_FROM_NON_HTTP = 33,
+ DICTIONARY_IS_TOO_LARGE= 34,
+ DICTIONARY_COUNT_EXCEEDED = 35,
+ DICTIONARY_ALREADY_SCHEDULED_TO_DOWNLOAD = 36,
+ DICTIONARY_ALREADY_TRIED_TO_DOWNLOAD = 37,
+
+ // Failsafe hack.
+ ATTEMPT_TO_DECODE_NON_HTTP_DATA = 40,
+
+
+ // Content-Encoding problems detected, with no action taken.
+ MULTIENCODING_FOR_NON_SDCH_REQUEST = 50,
+ SDCH_CONTENT_ENCODE_FOR_NON_SDCH_REQUEST = 51,
+
+ // Dictionary manager issues.
+ DOMAIN_BLACKLIST_INCLUDES_TARGET = 61,
+
+ // Problematic decode recovery methods.
+ META_REFRESH_RECOVERY = 70, // Dictionary not found.
+ // defunct = 71, // Almost the same as META_REFRESH_UNSUPPORTED.
+ // defunct = 72, // Almost the same as CACHED_META_REFRESH_UNSUPPORTED.
+ // defunct = 73, // PASSING_THROUGH_NON_SDCH plus DISCARD_TENTATIVE_SDCH.
+ META_REFRESH_UNSUPPORTED = 74, // Unrecoverable error.
+ CACHED_META_REFRESH_UNSUPPORTED = 75, // As above, but pulled from cache.
+ PASSING_THROUGH_NON_SDCH = 76, // Tagged sdch but missing dictionary-hash.
+ INCOMPLETE_SDCH_CONTENT = 77, // Last window was not completely decoded.
+ PASS_THROUGH_404_CODE = 78, // URL not found message passing through.
+
+ // This next report is very common, and not really an error scenario, but
+ // it exercises the error recovery logic.
+ PASS_THROUGH_OLD_CACHED = 79, // Back button got pre-SDCH cached content.
+
+ // Common decoded recovery methods.
+ META_REFRESH_CACHED_RECOVERY = 80, // Probably startup tab loading.
+ DISCARD_TENTATIVE_SDCH = 81, // Server decided not to use sdch.
+
+ // Non SDCH problems, only accounted for to make stat counting complete
+ // (i.e., be able to be sure all dictionary advertisements are accounted
+ // for).
+
+ UNFLUSHED_CONTENT = 90, // Possible error in filter chaining.
+ // defunct = 91, // MISSING_TIME_STATS (Should never happen.)
+ CACHE_DECODED = 92, // No timing stats recorded.
+ // defunct = 93, // OVER_10_MINUTES (No timing stats recorded.)
+ UNINITIALIZED = 94, // Filter never even got initialized.
+ PRIOR_TO_DICTIONARY = 95, // We hadn't even parsed a dictionary selector.
+ DECODE_ERROR = 96, // Something went wrong during decode.
+
+ // Problem during the latency test.
+ LATENCY_TEST_DISALLOWED = 100, // SDCH now failing, but it worked before!
+
+ MAX_PROBLEM_CODE // Used to bound histogram.
+ };
+
+ // Use the following static limits to block DOS attacks until we implement
+ // a cached dictionary evicition strategy.
+ static const size_t kMaxDictionarySize;
+ static const size_t kMaxDictionaryCount;
+
+ // There is one instance of |Dictionary| for each memory-cached SDCH
+ // dictionary.
+ class NET_EXPORT_PRIVATE Dictionary : public base::RefCounted<Dictionary> {
+ public:
+ // Sdch filters can get our text to use in decoding compressed data.
+ const std::string& text() const { return text_; }
+
+ private:
+ friend class base::RefCounted<Dictionary>;
+ friend class SdchManager; // Only manager can construct an instance.
+ FRIEND_TEST_ALL_PREFIXES(SdchFilterTest, PathMatch);
+
+ // Construct a vc-diff usable dictionary from the dictionary_text starting
+ // at the given offset. The supplied client_hash should be used to
+ // advertise the dictionary's availability relative to the suppplied URL.
+ Dictionary(const std::string& dictionary_text,
+ size_t offset,
+ const std::string& client_hash,
+ const GURL& url,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration,
+ const std::set<int>& ports);
+ ~Dictionary();
+
+ const GURL& url() const { return url_; }
+ const std::string& client_hash() const { return client_hash_; }
+
+ // Security method to check if we can advertise this dictionary for use
+ // if the |target_url| returns SDCH compressed data.
+ bool CanAdvertise(const GURL& target_url);
+
+ // Security methods to check if we can establish a new dictionary with the
+ // given data, that arrived in response to get of dictionary_url.
+ static bool CanSet(const std::string& domain, const std::string& path,
+ const std::set<int>& ports, const GURL& dictionary_url);
+
+ // Security method to check if we can use a dictionary to decompress a
+ // target that arrived with a reference to this dictionary.
+ bool CanUse(const GURL& referring_url);
+
+ // Compare paths to see if they "match" for dictionary use.
+ static bool PathMatch(const std::string& path,
+ const std::string& restriction);
+
+ // Compare domains to see if the "match" for dictionary use.
+ static bool DomainMatch(const GURL& url, const std::string& restriction);
+
+
+ // The actual text of the dictionary.
+ std::string text_;
+
+ // Part of the hash of text_ that the client uses to advertise the fact that
+ // it has a specific dictionary pre-cached.
+ std::string client_hash_;
+
+ // The GURL that arrived with the text_ in a URL request to specify where
+ // this dictionary may be used.
+ const GURL url_;
+
+ // Metadate "headers" in before dictionary text contained the following:
+ // Each dictionary payload consists of several headers, followed by the text
+ // of the dictionary. The following are the known headers.
+ const std::string domain_;
+ const std::string path_;
+ const base::Time expiration_; // Implied by max-age.
+ const std::set<int> ports_;
+
+ DISALLOW_COPY_AND_ASSIGN(Dictionary);
+ };
+
+ SdchManager();
+ ~SdchManager();
+
+ // Discontinue fetching of dictionaries, as we're now shutting down.
+ static void Shutdown();
+
+ // Provide access to the single instance of this class.
+ static SdchManager* Global();
+
+ // Record stats on various errors.
+ static void SdchErrorRecovery(ProblemCodes problem);
+
+ // Register a fetcher that this class can use to obtain dictionaries.
+ void set_sdch_fetcher(SdchFetcher* fetcher);
+
+ // Enables or disables SDCH compression.
+ static void EnableSdchSupport(bool enabled);
+
+ static bool sdch_enabled() { return g_sdch_enabled_; }
+
+ // Briefly prevent further advertising of SDCH on this domain (if SDCH is
+ // enabled). After enough calls to IsInSupportedDomain() the blacklisting
+ // will be removed. Additional blacklists take exponentially more calls
+ // to IsInSupportedDomain() before the blacklisting is undone.
+ // Used when filter errors are found from a given domain, but it is plausible
+ // that the cause is temporary (such as application startup, where cached
+ // entries are used, but a dictionary is not yet loaded).
+ static void BlacklistDomain(const GURL& url);
+
+ // Used when SEVERE filter errors are found from a given domain, to prevent
+ // further use of SDCH on that domain.
+ static void BlacklistDomainForever(const GURL& url);
+
+ // Unit test only, this function resets enabling of sdch, and clears the
+ // blacklist.
+ static void ClearBlacklistings();
+
+ // Unit test only, this function resets the blacklisting count for a domain.
+ static void ClearDomainBlacklisting(const std::string& domain);
+
+ // Unit test only: indicate how many more times a domain will be blacklisted.
+ static int BlackListDomainCount(const std::string& domain);
+
+ // Unit test only: Indicate what current blacklist increment is for a domain.
+ static int BlacklistDomainExponential(const std::string& domain);
+
+ // Check to see if SDCH is enabled (globally), and the given URL is in a
+ // supported domain (i.e., not blacklisted, and either the specific supported
+ // domain, or all domains were assumed supported). If it is blacklist, reduce
+ // by 1 the number of times it will be reported as blacklisted.
+ bool IsInSupportedDomain(const GURL& url);
+
+ // Schedule the URL fetching to load a dictionary. This will always return
+ // before the dictionary is actually loaded and added.
+ // After the implied task does completes, the dictionary will have been
+ // cached in memory.
+ void FetchDictionary(const GURL& request_url, const GURL& dictionary_url);
+
+ // Security test function used before initiating a FetchDictionary.
+ // Return true if fetch is legal.
+ bool CanFetchDictionary(const GURL& referring_url,
+ const GURL& dictionary_url) const;
+
+ // Add an SDCH dictionary to our list of availible dictionaries. This addition
+ // will fail (return false) if addition is illegal (data in the dictionary is
+ // not acceptable from the dictionary_url; dictionary already added, etc.).
+ bool AddSdchDictionary(const std::string& dictionary_text,
+ const GURL& dictionary_url);
+
+ // Find the vcdiff dictionary (the body of the sdch dictionary that appears
+ // after the meta-data headers like Domain:...) with the given |server_hash|
+ // to use to decompreses data that arrived as SDCH encoded content. Check to
+ // be sure the returned |dictionary| can be used for decoding content supplied
+ // in response to a request for |referring_url|.
+ // Caller is responsible for AddRef()ing the dictionary, and Release()ing it
+ // when done.
+ // Return null in |dictionary| if there is no matching legal dictionary.
+ void GetVcdiffDictionary(const std::string& server_hash,
+ const GURL& referring_url,
+ Dictionary** dictionary);
+
+ // Get list of available (pre-cached) dictionaries that we have already loaded
+ // into memory. The list is a comma separated list of (client) hashes per
+ // the SDCH spec.
+ void GetAvailDictionaryList(const GURL& target_url, std::string* list);
+
+ // Construct the pair of hashes for client and server to identify an SDCH
+ // dictionary. This is only made public to facilitate unit testing, but is
+ // otherwise private
+ static void GenerateHash(const std::string& dictionary_text,
+ std::string* client_hash, std::string* server_hash);
+
+ // For Latency testing only, we need to know if we've succeeded in doing a
+ // round trip before starting our comparative tests. If ever we encounter
+ // problems with SDCH, we opt-out of the test unless/until we perform a
+ // complete SDCH decoding.
+ bool AllowLatencyExperiment(const GURL& url) const;
+
+ void SetAllowLatencyExperiment(const GURL& url, bool enable);
+
+ private:
+ typedef std::map<std::string, int> DomainCounter;
+ typedef std::set<std::string> ExperimentSet;
+
+ // A map of dictionaries info indexed by the hash that the server provides.
+ typedef std::map<std::string, Dictionary*> DictionaryMap;
+
+ // The one global instance of that holds all the data.
+ static SdchManager* global_;
+
+ // Support SDCH compression, by advertising in headers.
+ static bool g_sdch_enabled_;
+
+ // A simple implementation of a RFC 3548 "URL safe" base64 encoder.
+ static void UrlSafeBase64Encode(const std::string& input,
+ std::string* output);
+ DictionaryMap dictionaries_;
+
+ // An instance that can fetch a dictionary given a URL.
+ scoped_ptr<SdchFetcher> fetcher_;
+
+ // List domains where decode failures have required disabling sdch, along with
+ // count of how many additonal uses should be blacklisted.
+ DomainCounter blacklisted_domains_;
+
+ // Support exponential backoff in number of domain accesses before
+ // blacklisting expires.
+ DomainCounter exponential_blacklist_count;
+
+ // List of hostnames for which a latency experiment is allowed (because a
+ // round trip test has recently passed).
+ ExperimentSet allow_latency_experiment_;
+
+ DISALLOW_COPY_AND_ASSIGN(SdchManager);
+};
+
+} // namespace net
+
+#endif // NET_BASE_SDCH_MANAGER_H_
diff --git a/chromium/net/base/static_cookie_policy.cc b/chromium/net/base/static_cookie_policy.cc
new file mode 100644
index 00000000000..c1acb669736
--- /dev/null
+++ b/chromium/net/base/static_cookie_policy.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 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.
+
+#include "net/base/static_cookie_policy.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+
+namespace net {
+
+int StaticCookiePolicy::CanGetCookies(
+ const GURL& url,
+ const GURL& first_party_for_cookies) const {
+ switch (type_) {
+ case StaticCookiePolicy::ALLOW_ALL_COOKIES:
+ case StaticCookiePolicy::BLOCK_SETTING_THIRD_PARTY_COOKIES:
+ return OK;
+ case StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES:
+ if (first_party_for_cookies.is_empty())
+ return OK; // Empty first-party URL indicates a first-party request.
+ return registry_controlled_domains::SameDomainOrHost(
+ url,
+ first_party_for_cookies,
+ registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES) ?
+ OK : ERR_ACCESS_DENIED;
+ case StaticCookiePolicy::BLOCK_ALL_COOKIES:
+ return ERR_ACCESS_DENIED;
+ default:
+ NOTREACHED();
+ return ERR_ACCESS_DENIED;
+ }
+}
+
+int StaticCookiePolicy::CanSetCookie(
+ const GURL& url,
+ const GURL& first_party_for_cookies) const {
+ switch (type_) {
+ case StaticCookiePolicy::ALLOW_ALL_COOKIES:
+ return OK;
+ case StaticCookiePolicy::BLOCK_SETTING_THIRD_PARTY_COOKIES:
+ case StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES:
+ if (first_party_for_cookies.is_empty())
+ return OK; // Empty first-party URL indicates a first-party request.
+ return registry_controlled_domains::SameDomainOrHost(
+ url,
+ first_party_for_cookies,
+ registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES) ?
+ OK : ERR_ACCESS_DENIED;
+ case StaticCookiePolicy::BLOCK_ALL_COOKIES:
+ return ERR_ACCESS_DENIED;
+ default:
+ NOTREACHED();
+ return ERR_ACCESS_DENIED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/base/static_cookie_policy.h b/chromium/net/base/static_cookie_policy.h
new file mode 100644
index 00000000000..eaf2f97fc36
--- /dev/null
+++ b/chromium/net/base/static_cookie_policy.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_BASE_STATIC_COOKIE_POLICY_H_
+#define NET_BASE_STATIC_COOKIE_POLICY_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// The StaticCookiePolicy class implements a static cookie policy that supports
+// three modes: allow all, deny all, or block third-party cookies.
+class NET_EXPORT StaticCookiePolicy {
+ public:
+ // Do not change the order of these types as they are persisted in
+ // preferences.
+ enum Type {
+ // Do not perform any cookie blocking.
+ ALLOW_ALL_COOKIES = 0,
+ // Prevent only third-party cookies from being set.
+ BLOCK_SETTING_THIRD_PARTY_COOKIES,
+ // Block all cookies (third-party or not) from begin set or read.
+ BLOCK_ALL_COOKIES,
+ // Prevent only third-party cookies from being set or read.
+ BLOCK_ALL_THIRD_PARTY_COOKIES
+ };
+
+ StaticCookiePolicy()
+ : type_(StaticCookiePolicy::ALLOW_ALL_COOKIES) {
+ }
+
+ explicit StaticCookiePolicy(Type type)
+ : type_(type) {
+ }
+
+ // Sets the current policy to enforce. This should be called when the user's
+ // preferences change.
+ void set_type(Type type) { type_ = type; }
+ Type type() const { return type_; }
+
+ // Consults the user's third-party cookie blocking preferences to determine
+ // whether the URL's cookies can be read.
+ int CanGetCookies(const GURL& url, const GURL& first_party_for_cookies) const;
+
+ // Consults the user's third-party cookie blocking preferences to determine
+ // whether the URL's cookies can be set.
+ int CanSetCookie(const GURL& url,
+ const GURL& first_party_for_cookies) const;
+
+ private:
+ Type type_;
+
+ DISALLOW_COPY_AND_ASSIGN(StaticCookiePolicy);
+};
+
+} // namespace net
+
+#endif // NET_BASE_STATIC_COOKIE_POLICY_H_
diff --git a/chromium/net/base/static_cookie_policy_unittest.cc b/chromium/net/base/static_cookie_policy_unittest.cc
new file mode 100644
index 00000000000..eb1d40b816b
--- /dev/null
+++ b/chromium/net/base/static_cookie_policy_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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/base/net_errors.h"
+#include "net/base/static_cookie_policy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class StaticCookiePolicyTest : public testing::Test {
+ public:
+ StaticCookiePolicyTest()
+ : url_google_("http://www.google.izzle"),
+ url_google_secure_("https://www.google.izzle"),
+ url_google_mail_("http://mail.google.izzle"),
+ url_google_analytics_("http://www.googleanalytics.izzle") {
+ }
+ void SetPolicyType(StaticCookiePolicy::Type type) {
+ policy_.set_type(type);
+ }
+ int CanGetCookies(const GURL& url, const GURL& first_party) {
+ return policy_.CanGetCookies(url, first_party);
+ }
+ int CanSetCookie(const GURL& url, const GURL& first_party) {
+ return policy_.CanSetCookie(url, first_party);
+ }
+ protected:
+ StaticCookiePolicy policy_;
+ GURL url_google_;
+ GURL url_google_secure_;
+ GURL url_google_mail_;
+ GURL url_google_analytics_;
+};
+
+TEST_F(StaticCookiePolicyTest, DefaultPolicyTest) {
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, GURL()));
+
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(StaticCookiePolicyTest, AllowAllCookiesTest) {
+ SetPolicyType(StaticCookiePolicy::ALLOW_ALL_COOKIES);
+
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, GURL()));
+
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(StaticCookiePolicyTest, BlockSettingThirdPartyCookiesTest) {
+ SetPolicyType(StaticCookiePolicy::BLOCK_SETTING_THIRD_PARTY_COOKIES);
+
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, GURL()));
+
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(StaticCookiePolicyTest, BlockAllThirdPartyCookiesTest) {
+ SetPolicyType(StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES);
+
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_NE(OK, CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanGetCookies(url_google_, GURL()));
+
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_EQ(OK, CanSetCookie(url_google_, GURL()));
+}
+
+TEST_F(StaticCookiePolicyTest, BlockAllCookiesTest) {
+ SetPolicyType(StaticCookiePolicy::BLOCK_ALL_COOKIES);
+
+ EXPECT_NE(OK, CanGetCookies(url_google_, url_google_));
+ EXPECT_NE(OK, CanGetCookies(url_google_, url_google_secure_));
+ EXPECT_NE(OK, CanGetCookies(url_google_, url_google_mail_));
+ EXPECT_NE(OK, CanGetCookies(url_google_, url_google_analytics_));
+ EXPECT_NE(OK, CanGetCookies(url_google_, GURL()));
+
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_secure_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_mail_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, url_google_analytics_));
+ EXPECT_NE(OK, CanSetCookie(url_google_, GURL()));
+}
+
+} // namespace net
diff --git a/chromium/net/base/sys_addrinfo.h b/chromium/net/base/sys_addrinfo.h
new file mode 100644
index 00000000000..62a6317e887
--- /dev/null
+++ b/chromium/net/base/sys_addrinfo.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2009 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.
+
+// This is a convenience header to pull in the platform-specific headers
+// that define at least:
+//
+// struct addrinfo
+// struct sockaddr*
+// getaddrinfo()
+// freeaddrinfo()
+// AI_*
+//
+// Prefer including this file instead of directly writing the #if / #else,
+// since it avoids duplicating the platform-specific selections.
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <ws2tcpip.h>
+#elif defined(OS_POSIX)
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#endif
diff --git a/chromium/net/base/test_completion_callback.cc b/chromium/net/base/test_completion_callback.cc
new file mode 100644
index 00000000000..4fcff74faac
--- /dev/null
+++ b/chromium/net/base/test_completion_callback.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 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.
+
+#include "net/base/test_completion_callback.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+
+namespace net {
+
+namespace internal {
+
+void TestCompletionCallbackBaseInternal::DidSetResult() {
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+}
+
+void TestCompletionCallbackBaseInternal::WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // Auto-reset for next callback.
+}
+
+TestCompletionCallbackBaseInternal::TestCompletionCallbackBaseInternal()
+ : have_result_(false),
+ waiting_for_result_(false) {
+}
+
+} // namespace internal
+
+TestCompletionCallback::TestCompletionCallback()
+ : callback_(base::Bind(&TestCompletionCallback::SetResult,
+ base::Unretained(this))) {
+}
+
+TestCompletionCallback::~TestCompletionCallback() {
+}
+
+TestInt64CompletionCallback::TestInt64CompletionCallback()
+ : callback_(base::Bind(&TestInt64CompletionCallback::SetResult,
+ base::Unretained(this))) {
+}
+
+TestInt64CompletionCallback::~TestInt64CompletionCallback() {
+}
+
+ReleaseBufferCompletionCallback::ReleaseBufferCompletionCallback(
+ IOBuffer* buffer) : buffer_(buffer) {
+}
+
+ReleaseBufferCompletionCallback::~ReleaseBufferCompletionCallback() {
+}
+
+void ReleaseBufferCompletionCallback::SetResult(int result) {
+ if (!buffer_->HasOneRef())
+ result = net::ERR_FAILED;
+ TestCompletionCallback::SetResult(result);
+}
+
+} // namespace net
diff --git a/chromium/net/base/test_completion_callback.h b/chromium/net/base/test_completion_callback.h
new file mode 100644
index 00000000000..4a0afe13359
--- /dev/null
+++ b/chromium/net/base/test_completion_callback.h
@@ -0,0 +1,128 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_TEST_COMPLETION_CALLBACK_H_
+#define NET_BASE_TEST_COMPLETION_CALLBACK_H_
+
+#include "base/compiler_specific.h"
+#include "base/tuple.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+
+//-----------------------------------------------------------------------------
+// completion callback helper
+
+// A helper class for completion callbacks, designed to make it easy to run
+// tests involving asynchronous operations. Just call WaitForResult to wait
+// for the asynchronous operation to complete.
+//
+// NOTE: Since this runs a message loop to wait for the completion callback,
+// there could be other side-effects resulting from WaitForResult. For this
+// reason, this class is probably not ideal for a general application.
+//
+
+namespace net {
+
+class IOBuffer;
+
+namespace internal {
+
+class TestCompletionCallbackBaseInternal {
+ public:
+ bool have_result() const { return have_result_; }
+
+ protected:
+ TestCompletionCallbackBaseInternal();
+ void DidSetResult();
+ void WaitForResult();
+
+ bool have_result_;
+ bool waiting_for_result_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallbackBaseInternal);
+};
+
+template <typename R>
+class TestCompletionCallbackTemplate
+ : public TestCompletionCallbackBaseInternal {
+ public:
+ virtual ~TestCompletionCallbackTemplate() {}
+
+ R WaitForResult() {
+ TestCompletionCallbackBaseInternal::WaitForResult();
+ return result_;
+ }
+
+ R GetResult(R result) {
+ if (net::ERR_IO_PENDING != result)
+ return result;
+ return WaitForResult();
+ }
+
+ protected:
+ // Override this method to gain control as the callback is running.
+ virtual void SetResult(R result) {
+ result_ = result;
+ DidSetResult();
+ }
+
+ TestCompletionCallbackTemplate() : result_(R()) {}
+ R result_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallbackTemplate);
+};
+
+} // namespace internal
+
+// Base class overridden by custom implementations of TestCompletionCallback.
+typedef internal::TestCompletionCallbackTemplate<int>
+ TestCompletionCallbackBase;
+
+typedef internal::TestCompletionCallbackTemplate<int64>
+ TestInt64CompletionCallbackBase;
+
+class TestCompletionCallback : public TestCompletionCallbackBase {
+ public:
+ TestCompletionCallback();
+ virtual ~TestCompletionCallback();
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ const CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCompletionCallback);
+};
+
+class TestInt64CompletionCallback : public TestInt64CompletionCallbackBase {
+ public:
+ TestInt64CompletionCallback();
+ virtual ~TestInt64CompletionCallback();
+
+ const Int64CompletionCallback& callback() const { return callback_; }
+
+ private:
+ const Int64CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestInt64CompletionCallback);
+};
+
+// Makes sure that the buffer is not referenced when the callback runs.
+class ReleaseBufferCompletionCallback: public TestCompletionCallback {
+ public:
+ explicit ReleaseBufferCompletionCallback(IOBuffer* buffer);
+ virtual ~ReleaseBufferCompletionCallback();
+
+ private:
+ virtual void SetResult(int result) OVERRIDE;
+
+ IOBuffer* buffer_;
+ DISALLOW_COPY_AND_ASSIGN(ReleaseBufferCompletionCallback);
+};
+
+} // namespace net
+
+#endif // NET_BASE_TEST_COMPLETION_CALLBACK_H_
diff --git a/chromium/net/base/test_completion_callback_unittest.cc b/chromium/net/base/test_completion_callback_unittest.cc
new file mode 100644
index 00000000000..704273ebc7f
--- /dev/null
+++ b/chromium/net/base/test_completion_callback_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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.
+
+// Illustrates how to use worker threads that issue completion callbacks
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/completion_callback.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+typedef PlatformTest TestCompletionCallbackTest;
+
+const int kMagicResult = 8888;
+
+// ExampleEmployer is a toy version of HostResolver
+// TODO: restore damage done in extracting example from real code
+// (e.g. bring back real destructor, bring back comments)
+class ExampleEmployer {
+ public:
+ ExampleEmployer();
+ ~ExampleEmployer();
+
+ // Do some imaginary work on a worker thread;
+ // when done, worker posts callback on the original thread.
+ // Returns true on success
+ bool DoSomething(const net::CompletionCallback& callback);
+
+ private:
+ class ExampleWorker;
+ friend class ExampleWorker;
+ scoped_refptr<ExampleWorker> request_;
+ DISALLOW_COPY_AND_ASSIGN(ExampleEmployer);
+};
+
+// Helper class; this is how ExampleEmployer puts work on a different thread
+class ExampleEmployer::ExampleWorker
+ : public base::RefCountedThreadSafe<ExampleWorker> {
+ public:
+ ExampleWorker(ExampleEmployer* employer,
+ const net::CompletionCallback& callback)
+ : employer_(employer),
+ callback_(callback),
+ origin_loop_(base::MessageLoop::current()) {}
+ void DoWork();
+ void DoCallback();
+ private:
+ friend class base::RefCountedThreadSafe<ExampleWorker>;
+
+ ~ExampleWorker() {}
+
+ // Only used on the origin thread (where DoSomething was called).
+ ExampleEmployer* employer_;
+ net::CompletionCallback callback_;
+ // Used to post ourselves onto the origin thread.
+ base::Lock origin_loop_lock_;
+ base::MessageLoop* origin_loop_;
+};
+
+void ExampleEmployer::ExampleWorker::DoWork() {
+ // Running on the worker thread
+ // In a real worker thread, some work would be done here.
+ // Pretend it is, and send the completion callback.
+
+ // The origin loop could go away while we are trying to post to it, so we
+ // need to call its PostTask method inside a lock. See ~ExampleEmployer.
+ {
+ base::AutoLock locked(origin_loop_lock_);
+ if (origin_loop_)
+ origin_loop_->PostTask(FROM_HERE,
+ base::Bind(&ExampleWorker::DoCallback, this));
+ }
+}
+
+void ExampleEmployer::ExampleWorker::DoCallback() {
+ // Running on the origin thread.
+
+ // Drop the employer_'s reference to us. Do this before running the
+ // callback since the callback might result in the employer being
+ // destroyed.
+ employer_->request_ = NULL;
+
+ callback_.Run(kMagicResult);
+}
+
+ExampleEmployer::ExampleEmployer() {
+}
+
+ExampleEmployer::~ExampleEmployer() {
+}
+
+bool ExampleEmployer::DoSomething(const net::CompletionCallback& callback) {
+ DCHECK(!request_.get()) << "already in use";
+
+ request_ = new ExampleWorker(this, callback);
+
+ // Dispatch to worker thread...
+ if (!base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&ExampleWorker::DoWork, request_.get()),
+ true)) {
+ NOTREACHED();
+ request_ = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+TEST_F(TestCompletionCallbackTest, Simple) {
+ ExampleEmployer boss;
+ net::TestCompletionCallback callback;
+ bool queued = boss.DoSomething(callback.callback());
+ EXPECT_EQ(queued, true);
+ int result = callback.WaitForResult();
+ EXPECT_EQ(result, kMagicResult);
+}
+
+// TODO: test deleting ExampleEmployer while work outstanding
diff --git a/chromium/net/base/test_data_directory.cc b/chromium/net/base/test_data_directory.cc
new file mode 100644
index 00000000000..45fe638d428
--- /dev/null
+++ b/chromium/net/base/test_data_directory.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 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.
+
+#include "net/base/test_data_directory.h"
+
+#include "base/base_paths.h"
+#include "base/path_service.h"
+
+namespace net {
+
+namespace {
+const base::FilePath::CharType kCertificateRelativePath[] =
+ FILE_PATH_LITERAL("net/data/ssl/certificates");
+} // namespace
+
+base::FilePath GetTestCertsDirectory() {
+ base::FilePath src_root;
+ PathService::Get(base::DIR_SOURCE_ROOT, &src_root);
+ return src_root.Append(kCertificateRelativePath);
+}
+
+base::FilePath GetTestClientCertsDirectory() {
+#if defined(OS_ANDROID)
+ return base::FilePath(kCertificateRelativePath);
+#else
+ return GetTestCertsDirectory();
+#endif
+}
+
+base::FilePath GetWebSocketTestDataDirectory() {
+ base::FilePath data_dir(FILE_PATH_LITERAL("net/data/websocket"));
+ return data_dir;
+}
+
+} // namespace net
diff --git a/chromium/net/base/test_data_directory.h b/chromium/net/base/test_data_directory.h
new file mode 100644
index 00000000000..9f1a84d369a
--- /dev/null
+++ b/chromium/net/base/test_data_directory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_TEST_DATA_DIRECTORY_H_
+#define NET_BASE_TEST_DATA_DIRECTORY_H_
+
+#include "base/files/file_path.h"
+
+namespace net {
+
+// Returns the FilePath object representing the absolute path in the source
+// tree that contains certificates for testing.
+base::FilePath GetTestCertsDirectory();
+
+// Returns the base::FilePath object representing the path to client
+// certificate files to be used in the |client_authorities| list
+// of a net::SSLConfig object. For all other uses, use
+// GetTestCertsDirectory() instead.
+base::FilePath GetTestClientCertsDirectory();
+
+// Returns the base::FilePath object representing the relative path containing
+// resource files for testing WebSocket. Typically the FilePath will be used as
+// document root argument for net::SpawnedTestServer with TYPE_WS or TYPE_WSS.
+base::FilePath GetWebSocketTestDataDirectory();
+
+} // namespace net
+
+#endif // NET_BASE_TEST_DATA_DIRECTORY_H_
diff --git a/chromium/net/base/test_data_stream.cc b/chromium/net/base/test_data_stream.cc
new file mode 100644
index 00000000000..66728043011
--- /dev/null
+++ b/chromium/net/base/test_data_stream.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 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/base/test_data_stream.h"
+
+namespace net {
+
+TestDataStream::TestDataStream() {
+ Reset();
+}
+
+// Fill |buffer| with |length| bytes of data from the stream.
+void TestDataStream::GetBytes(char* buffer, int length) {
+ while (length) {
+ AdvanceIndex();
+ int bytes_to_copy = std::min(length, bytes_remaining_);
+ memcpy(buffer, buffer_ptr_, bytes_to_copy);
+ buffer += bytes_to_copy;
+ Consume(bytes_to_copy);
+ length -= bytes_to_copy;
+ }
+}
+
+bool TestDataStream::VerifyBytes(const char *buffer, int length) {
+ while (length) {
+ AdvanceIndex();
+ int bytes_to_compare = std::min(length, bytes_remaining_);
+ if (memcmp(buffer, buffer_ptr_, bytes_to_compare))
+ return false;
+ Consume(bytes_to_compare);
+ length -= bytes_to_compare;
+ buffer += bytes_to_compare;
+ }
+ return true;
+}
+
+void TestDataStream::Reset() {
+ index_ = 0;
+ bytes_remaining_ = 0;
+ buffer_ptr_ = buffer_;
+}
+
+// If there is no data spilled over from the previous index, advance the
+// index and fill the buffer.
+void TestDataStream::AdvanceIndex() {
+ if (bytes_remaining_ == 0) {
+ // Convert it to ascii, but don't bother to reverse it.
+ // (e.g. 12345 becomes "54321")
+ int val = index_++;
+ do {
+ buffer_[bytes_remaining_++] = (val % 10) + '0';
+ } while ((val /= 10) > 0);
+ buffer_[bytes_remaining_++] = '.';
+ }
+}
+
+// Consume data from the spill buffer.
+void TestDataStream::Consume(int bytes) {
+ bytes_remaining_ -= bytes;
+ if (bytes_remaining_)
+ buffer_ptr_ += bytes;
+ else
+ buffer_ptr_ = buffer_;
+}
+
+} // namespace net
+
diff --git a/chromium/net/base/test_data_stream.h b/chromium/net/base/test_data_stream.h
new file mode 100644
index 00000000000..464220e8982
--- /dev/null
+++ b/chromium/net/base/test_data_stream.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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_BASE_TEST_DATA_STREAM_H_
+#define NET_BASE_TEST_DATA_STREAM_H_
+
+#include <string.h> // for memcpy().
+#include <algorithm>
+#include "net/base/net_export.h"
+
+// This is a class for generating an infinite stream of data which can be
+// verified independently to be the correct stream of data.
+
+namespace net {
+
+class NET_EXPORT TestDataStream {
+ public:
+ TestDataStream();
+
+ // Fill |buffer| with |length| bytes of data from the stream.
+ void GetBytes(char* buffer, int length);
+
+ // Verify that |buffer| contains the expected next |length| bytes from the
+ // stream. Returns true if correct, false otherwise.
+ bool VerifyBytes(const char *buffer, int length);
+
+ // Resets all the data.
+ void Reset();
+
+ private:
+ // If there is no data spilled over from the previous index, advance the
+ // index and fill the buffer.
+ void AdvanceIndex();
+
+ // Consume data from the spill buffer.
+ void Consume(int bytes);
+
+ int index_;
+ int bytes_remaining_;
+ char buffer_[16];
+ char* buffer_ptr_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_TEST_DATA_STREAM_H_
diff --git a/chromium/net/base/upload_bytes_element_reader.cc b/chromium/net/base/upload_bytes_element_reader.cc
new file mode 100644
index 00000000000..6927d8ea7c2
--- /dev/null
+++ b/chromium/net/base/upload_bytes_element_reader.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_bytes_element_reader.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+UploadBytesElementReader::UploadBytesElementReader(const char* bytes,
+ uint64 length)
+ : bytes_(bytes),
+ length_(length),
+ offset_(0) {
+}
+
+UploadBytesElementReader::~UploadBytesElementReader() {
+}
+
+const UploadBytesElementReader*
+UploadBytesElementReader::AsBytesReader() const {
+ return this;
+}
+
+int UploadBytesElementReader::Init(const CompletionCallback& callback) {
+ offset_ = 0;
+ return OK;
+}
+
+uint64 UploadBytesElementReader::GetContentLength() const {
+ return length_;
+}
+
+uint64 UploadBytesElementReader::BytesRemaining() const {
+ return length_ - offset_;
+}
+
+bool UploadBytesElementReader::IsInMemory() const {
+ return true;
+}
+
+int UploadBytesElementReader::Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) {
+ DCHECK_LT(0, buf_length);
+
+ const size_t num_bytes_to_read =
+ std::min(BytesRemaining(), static_cast<uint64>(buf_length));
+
+ // Check if we have anything to copy first, because we are getting
+ // the address of an element in |bytes_| and that will throw an
+ // exception if |bytes_| is an empty vector.
+ if (num_bytes_to_read > 0)
+ memcpy(buf->data(), bytes_ + offset_, num_bytes_to_read);
+
+ offset_ += num_bytes_to_read;
+ return num_bytes_to_read;
+}
+
+
+UploadOwnedBytesElementReader::UploadOwnedBytesElementReader(
+ std::vector<char>* data)
+ : UploadBytesElementReader(vector_as_array(data), data->size()) {
+ data_.swap(*data);
+}
+
+UploadOwnedBytesElementReader::~UploadOwnedBytesElementReader() {}
+
+UploadOwnedBytesElementReader*
+UploadOwnedBytesElementReader::CreateWithString(const std::string& string) {
+ std::vector<char> data(string.begin(), string.end());
+ return new UploadOwnedBytesElementReader(&data);
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_bytes_element_reader.h b/chromium/net/base/upload_bytes_element_reader.h
new file mode 100644
index 00000000000..9c7daf3a657
--- /dev/null
+++ b/chromium/net/base/upload_bytes_element_reader.h
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_BYTES_ELEMENT_READER_H_
+#define NET_BASE_UPLOAD_BYTES_ELEMENT_READER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/upload_element_reader.h"
+
+namespace net {
+
+// An UploadElementReader implementation for bytes.
+// |data| should outlive this class because this class does not take the
+// ownership of the data.
+class NET_EXPORT UploadBytesElementReader : public UploadElementReader {
+ public:
+ UploadBytesElementReader(const char* bytes, uint64 length);
+ virtual ~UploadBytesElementReader();
+
+ const char* bytes() const { return bytes_; }
+ uint64 length() const { return length_; }
+
+ // UploadElementReader overrides:
+ virtual const UploadBytesElementReader* AsBytesReader() const OVERRIDE;
+ virtual int Init(const CompletionCallback& callback) OVERRIDE;
+ virtual uint64 GetContentLength() const OVERRIDE;
+ virtual uint64 BytesRemaining() const OVERRIDE;
+ virtual bool IsInMemory() const OVERRIDE;
+ virtual int Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ const char* const bytes_;
+ const uint64 length_;
+ uint64 offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadBytesElementReader);
+};
+
+// A subclass of UplodBytesElementReader which owns the data given as a vector.
+class NET_EXPORT UploadOwnedBytesElementReader
+ : public UploadBytesElementReader {
+ public:
+ // |data| is cleared by this ctor.
+ explicit UploadOwnedBytesElementReader(std::vector<char>* data);
+ virtual ~UploadOwnedBytesElementReader();
+
+ // Creates UploadOwnedBytesElementReader with a string.
+ static UploadOwnedBytesElementReader* CreateWithString(
+ const std::string& string);
+
+ private:
+ std::vector<char> data_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadOwnedBytesElementReader);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_BYTES_ELEMENT_READER_H_
diff --git a/chromium/net/base/upload_bytes_element_reader_unittest.cc b/chromium/net/base/upload_bytes_element_reader_unittest.cc
new file mode 100644
index 00000000000..1aad55ee5ce
--- /dev/null
+++ b/chromium/net/base/upload_bytes_element_reader_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_bytes_element_reader.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class UploadBytesElementReaderTest : public PlatformTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ const char kData[] = "123abc";
+ bytes_.assign(kData, kData + arraysize(kData));
+ reader_.reset(new UploadBytesElementReader(&bytes_[0], bytes_.size()));
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+ EXPECT_TRUE(reader_->IsInMemory());
+ }
+
+ std::vector<char> bytes_;
+ scoped_ptr<UploadElementReader> reader_;
+};
+
+TEST_F(UploadBytesElementReaderTest, ReadPartially) {
+ const size_t kHalfSize = bytes_.size() / 2;
+ std::vector<char> buf(kHalfSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(bytes_.size() - buf.size(), reader_->BytesRemaining());
+ bytes_.resize(kHalfSize); // Resize to compare.
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadBytesElementReaderTest, ReadAll) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+ // Try to read again.
+ EXPECT_EQ(
+ 0, reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+}
+
+TEST_F(UploadBytesElementReaderTest, ReadTooMuch) {
+ const size_t kTooLargeSize = bytes_.size() * 2;
+ std::vector<char> buf(kTooLargeSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(bytes_.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ buf.resize(bytes_.size()); // Resize to compare.
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadBytesElementReaderTest, MultipleInit) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+
+ // Read all.
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+
+ // Call Init() again to reset the state.
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+
+ // Read again.
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_data.cc b/chromium/net/base/upload_data.cc
new file mode 100644
index 00000000000..48cb2020f98
--- /dev/null
+++ b/chromium/net/base/upload_data.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_data.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+UploadData::UploadData()
+ : identifier_(0),
+ is_chunked_(false),
+ last_chunk_appended_(false) {
+}
+
+void UploadData::AppendBytes(const char* bytes, int bytes_len) {
+ DCHECK(!is_chunked_);
+ if (bytes_len > 0) {
+ elements_.push_back(new UploadElement());
+ elements_.back()->SetToBytes(bytes, bytes_len);
+ }
+}
+
+void UploadData::AppendFileRange(const base::FilePath& file_path,
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ DCHECK(!is_chunked_);
+ elements_.push_back(new UploadElement());
+ elements_.back()->SetToFilePathRange(file_path, offset, length,
+ expected_modification_time);
+}
+
+UploadData::~UploadData() {
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_data.h b/chromium/net/base/upload_data.h
new file mode 100644
index 00000000000..b782ab4e4de
--- /dev/null
+++ b/chromium/net/base/upload_data.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_DATA_H_
+#define NET_BASE_UPLOAD_DATA_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/supports_user_data.h"
+#include "net/base/net_export.h"
+#include "net/base/upload_element.h"
+
+namespace base {
+class FilePath;
+class Time;
+} // namespace base
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// A very concrete class representing the data to be uploaded as part of a
+// URLRequest.
+//
+// Until there is a more abstract class for this, this one derives from
+// SupportsUserData to allow users to stash random data by
+// key and ensure its destruction when UploadData is finally deleted.
+class NET_EXPORT UploadData
+ : public base::RefCounted<UploadData>,
+ public base::SupportsUserData {
+ public:
+ UploadData();
+
+ void AppendBytes(const char* bytes, int bytes_len);
+
+ void AppendFileRange(const base::FilePath& file_path,
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+
+ // Initializes the object to send chunks of upload data over time rather
+ // than all at once. Chunked data may only contain bytes, not files.
+ void set_is_chunked(bool set) { is_chunked_ = set; }
+ bool is_chunked() const { return is_chunked_; }
+
+ // set_last_chunk_appended() is only used for serialization.
+ void set_last_chunk_appended(bool set) { last_chunk_appended_ = set; }
+ bool last_chunk_appended() const { return last_chunk_appended_; }
+
+ const ScopedVector<UploadElement>& elements() const {
+ return elements_;
+ }
+
+ ScopedVector<UploadElement>* elements_mutable() {
+ return &elements_;
+ }
+
+ void swap_elements(ScopedVector<UploadElement>* elements) {
+ elements_.swap(*elements);
+ }
+
+ // Identifies a particular upload instance, which is used by the cache to
+ // formulate a cache key. This value should be unique across browser
+ // sessions. A value of 0 is used to indicate an unspecified identifier.
+ void set_identifier(int64 id) { identifier_ = id; }
+ int64 identifier() const { return identifier_; }
+
+ private:
+ friend class base::RefCounted<UploadData>;
+
+ virtual ~UploadData();
+
+ ScopedVector<UploadElement> elements_;
+ int64 identifier_;
+ bool is_chunked_;
+ bool last_chunk_appended_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadData);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_DATA_H_
diff --git a/chromium/net/base/upload_data_stream.cc b/chromium/net/base/upload_data_stream.cc
new file mode 100644
index 00000000000..03946da6b1b
--- /dev/null
+++ b/chromium/net/base/upload_data_stream.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_data_stream.h"
+
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_element_reader.h"
+
+namespace net {
+
+UploadDataStream::UploadDataStream(
+ ScopedVector<UploadElementReader>* element_readers,
+ int64 identifier)
+ : element_index_(0),
+ total_size_(0),
+ current_position_(0),
+ identifier_(identifier),
+ is_chunked_(false),
+ last_chunk_appended_(false),
+ read_failed_(false),
+ initialized_successfully_(false),
+ weak_ptr_factory_(this) {
+ element_readers_.swap(*element_readers);
+}
+
+UploadDataStream::UploadDataStream(Chunked /*chunked*/, int64 identifier)
+ : element_index_(0),
+ total_size_(0),
+ current_position_(0),
+ identifier_(identifier),
+ is_chunked_(true),
+ last_chunk_appended_(false),
+ read_failed_(false),
+ initialized_successfully_(false),
+ weak_ptr_factory_(this) {
+}
+
+UploadDataStream::~UploadDataStream() {
+}
+
+UploadDataStream* UploadDataStream::CreateWithReader(
+ scoped_ptr<UploadElementReader> reader,
+ int64 identifier) {
+ ScopedVector<UploadElementReader> readers;
+ readers.push_back(reader.release());
+ return new UploadDataStream(&readers, identifier);
+}
+
+int UploadDataStream::Init(const CompletionCallback& callback) {
+ Reset();
+ return InitInternal(0, callback);
+}
+
+int UploadDataStream::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(initialized_successfully_);
+ DCHECK_GT(buf_len, 0);
+ return ReadInternal(new DrainableIOBuffer(buf, buf_len), callback);
+}
+
+bool UploadDataStream::IsEOF() const {
+ DCHECK(initialized_successfully_);
+ if (!is_chunked_)
+ return current_position_ == total_size_;
+
+ // If the upload data is chunked, check if the last chunk is appended and all
+ // elements are consumed.
+ return element_index_ == element_readers_.size() && last_chunk_appended_;
+}
+
+bool UploadDataStream::IsInMemory() const {
+ // Chunks are in memory, but UploadData does not have all the chunks at
+ // once. Chunks are provided progressively with AppendChunk() as chunks
+ // are ready. Check is_chunked_ here, rather than relying on the loop
+ // below, as there is a case that is_chunked_ is set to true, but the
+ // first chunk is not yet delivered.
+ if (is_chunked_)
+ return false;
+
+ for (size_t i = 0; i < element_readers_.size(); ++i) {
+ if (!element_readers_[i]->IsInMemory())
+ return false;
+ }
+ return true;
+}
+
+void UploadDataStream::AppendChunk(const char* bytes,
+ int bytes_len,
+ bool is_last_chunk) {
+ DCHECK(is_chunked_);
+ DCHECK(!last_chunk_appended_);
+ last_chunk_appended_ = is_last_chunk;
+
+ // Initialize a reader for the newly appended chunk. We leave |total_size_| at
+ // zero, since for chunked uploads, we may not know the total size.
+ std::vector<char> data(bytes, bytes + bytes_len);
+ UploadElementReader* reader = new UploadOwnedBytesElementReader(&data);
+ const int rv = reader->Init(net::CompletionCallback());
+ DCHECK_EQ(OK, rv);
+ element_readers_.push_back(reader);
+
+ // Resume pending read.
+ if (!pending_chunked_read_callback_.is_null()) {
+ base::Closure callback = pending_chunked_read_callback_;
+ pending_chunked_read_callback_.Reset();
+ callback.Run();
+ }
+}
+
+void UploadDataStream::Reset() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ pending_chunked_read_callback_.Reset();
+ initialized_successfully_ = false;
+ read_failed_ = false;
+ current_position_ = 0;
+ total_size_ = 0;
+ element_index_ = 0;
+}
+
+int UploadDataStream::InitInternal(int start_index,
+ const CompletionCallback& callback) {
+ DCHECK(!initialized_successfully_);
+
+ // Call Init() for all elements.
+ for (size_t i = start_index; i < element_readers_.size(); ++i) {
+ UploadElementReader* reader = element_readers_[i];
+ // When new_result is ERR_IO_PENDING, InitInternal() will be called
+ // with start_index == i + 1 when reader->Init() finishes.
+ const int result = reader->Init(
+ base::Bind(&UploadDataStream::ResumePendingInit,
+ weak_ptr_factory_.GetWeakPtr(),
+ i + 1,
+ callback));
+ if (result != OK) {
+ DCHECK(result != ERR_IO_PENDING || !callback.is_null());
+ return result;
+ }
+ }
+
+ // Finalize initialization.
+ if (!is_chunked_) {
+ uint64 total_size = 0;
+ for (size_t i = 0; i < element_readers_.size(); ++i) {
+ UploadElementReader* reader = element_readers_[i];
+ total_size += reader->GetContentLength();
+ }
+ total_size_ = total_size;
+ }
+ initialized_successfully_ = true;
+ return OK;
+}
+
+void UploadDataStream::ResumePendingInit(int start_index,
+ const CompletionCallback& callback,
+ int previous_result) {
+ DCHECK(!initialized_successfully_);
+ DCHECK(!callback.is_null());
+ DCHECK_NE(ERR_IO_PENDING, previous_result);
+
+ // Check the last result.
+ if (previous_result != OK) {
+ callback.Run(previous_result);
+ return;
+ }
+
+ const int result = InitInternal(start_index, callback);
+ if (result != ERR_IO_PENDING)
+ callback.Run(result);
+}
+
+int UploadDataStream::ReadInternal(scoped_refptr<DrainableIOBuffer> buf,
+ const CompletionCallback& callback) {
+ DCHECK(initialized_successfully_);
+
+ while (!read_failed_ && element_index_ < element_readers_.size()) {
+ UploadElementReader* reader = element_readers_[element_index_];
+
+ if (reader->BytesRemaining() == 0) {
+ ++element_index_;
+ continue;
+ }
+
+ if (buf->BytesRemaining() == 0)
+ break;
+
+ int result = reader->Read(
+ buf.get(),
+ buf->BytesRemaining(),
+ base::Bind(base::IgnoreResult(&UploadDataStream::ResumePendingRead),
+ weak_ptr_factory_.GetWeakPtr(),
+ buf,
+ callback));
+ if (result == ERR_IO_PENDING) {
+ DCHECK(!callback.is_null());
+ return ERR_IO_PENDING;
+ }
+ ProcessReadResult(buf, result);
+ }
+
+ if (read_failed_) {
+ // Chunked transfers may only contain byte readers, so cannot have read
+ // failures.
+ DCHECK(!is_chunked_);
+
+ // If an error occured during read operation, then pad with zero.
+ // Otherwise the server will hang waiting for the rest of the data.
+ const int num_bytes_to_fill =
+ std::min(static_cast<uint64>(buf->BytesRemaining()),
+ size() - position() - buf->BytesConsumed());
+ DCHECK_LE(0, num_bytes_to_fill);
+ memset(buf->data(), 0, num_bytes_to_fill);
+ buf->DidConsume(num_bytes_to_fill);
+ }
+
+ const int bytes_copied = buf->BytesConsumed();
+ current_position_ += bytes_copied;
+ DCHECK(is_chunked_ || total_size_ >= current_position_);
+
+ if (is_chunked_ && !IsEOF() && bytes_copied == 0) {
+ DCHECK(!callback.is_null());
+ DCHECK(pending_chunked_read_callback_.is_null());
+ pending_chunked_read_callback_ =
+ base::Bind(&UploadDataStream::ResumePendingRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ buf,
+ callback,
+ OK);
+ return ERR_IO_PENDING;
+ }
+
+ // Returning 0 is allowed only when IsEOF() == true.
+ DCHECK(bytes_copied != 0 || IsEOF());
+ return bytes_copied;
+}
+
+void UploadDataStream::ResumePendingRead(scoped_refptr<DrainableIOBuffer> buf,
+ const CompletionCallback& callback,
+ int previous_result) {
+ DCHECK(!callback.is_null());
+
+ ProcessReadResult(buf, previous_result);
+
+ const int result = ReadInternal(buf, callback);
+ if (result != ERR_IO_PENDING)
+ callback.Run(result);
+}
+
+void UploadDataStream::ProcessReadResult(scoped_refptr<DrainableIOBuffer> buf,
+ int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(!read_failed_);
+
+ if (result >= 0)
+ buf->DidConsume(result);
+ else
+ read_failed_ = true;
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_data_stream.h b/chromium/net/base/upload_data_stream.h
new file mode 100644
index 00000000000..c8f2cc2a34f
--- /dev/null
+++ b/chromium/net/base/upload_data_stream.h
@@ -0,0 +1,159 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_DATA_STREAM_H_
+#define NET_BASE_UPLOAD_DATA_STREAM_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class DrainableIOBuffer;
+class IOBuffer;
+class UploadElementReader;
+
+// A class to read all elements from an UploadData object.
+class NET_EXPORT UploadDataStream {
+ public:
+ // An enum used to construct chunked data stream.
+ enum Chunked { CHUNKED };
+
+ // Constructs a non-chunked data stream.
+ // |element_readers| is cleared by this ctor.
+ UploadDataStream(ScopedVector<UploadElementReader>* element_readers,
+ int64 identifier);
+
+ // Constructs a chunked data stream.
+ UploadDataStream(Chunked chunked, int64 identifier);
+
+ ~UploadDataStream();
+
+ // Creates UploadDataStream with a reader.
+ static UploadDataStream* CreateWithReader(
+ scoped_ptr<UploadElementReader> reader,
+ int64 identifier);
+
+ // Initializes the stream. This function must be called before calling any
+ // other method. It is not valid to call any method (other than the
+ // destructor) if Init() returns a failure. This method can be called multiple
+ // times. Calling this method after a Init() success results in resetting the
+ // state.
+ //
+ // Does the initialization synchronously and returns the result if possible,
+ // otherwise returns ERR_IO_PENDING and runs the callback with the result.
+ //
+ // Returns OK on success. Returns ERR_UPLOAD_FILE_CHANGED if the expected
+ // file modification time is set (usually not set, but set for sliced
+ // files) and the target file is changed.
+ int Init(const CompletionCallback& callback);
+
+ // When possible, reads up to |buf_len| bytes synchronously from the upload
+ // data stream to |buf| and returns the number of bytes read; otherwise,
+ // returns ERR_IO_PENDING and calls |callback| with the number of bytes read.
+ // Partial reads are allowed. Zero is returned on a call to Read when there
+ // are no remaining bytes in the stream, and IsEof() will return true
+ // hereafter.
+ //
+ // If there's less data to read than we initially observed (i.e. the actual
+ // upload data is smaller than size()), zeros are padded to ensure that
+ // size() bytes can be read, which can happen for TYPE_FILE payloads.
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Identifies a particular upload instance, which is used by the cache to
+ // formulate a cache key. This value should be unique across browser
+ // sessions. A value of 0 is used to indicate an unspecified identifier.
+ int64 identifier() const { return identifier_; }
+
+ // Returns the total size of the data stream and the current position.
+ // size() is not to be used to determine whether the stream has ended
+ // because it is possible for the stream to end before its size is reached,
+ // for example, if the file is truncated. When the data is chunked, size()
+ // always returns zero.
+ uint64 size() const { return total_size_; }
+ uint64 position() const { return current_position_; }
+
+ bool is_chunked() const { return is_chunked_; }
+ bool last_chunk_appended() const { return last_chunk_appended_; }
+
+ const ScopedVector<UploadElementReader>& element_readers() const {
+ return element_readers_;
+ }
+
+ // Returns true if all data has been consumed from this upload data
+ // stream.
+ bool IsEOF() const;
+
+ // Returns true if the upload data in the stream is entirely in memory.
+ bool IsInMemory() const;
+
+ // Adds the given chunk of bytes to be sent with chunked transfer encoding.
+ void AppendChunk(const char* bytes, int bytes_len, bool is_last_chunk);
+
+ private:
+ // Resets this instance to the uninitialized state.
+ void Reset();
+
+ // Runs Init() for all element readers.
+ // This method is used to implement Init().
+ int InitInternal(int start_index, const CompletionCallback& callback);
+
+ // Resumes initialization and runs callback with the result when necessary.
+ void ResumePendingInit(int start_index,
+ const CompletionCallback& callback,
+ int previous_result);
+
+ // Reads data from the element readers.
+ // This method is used to implement Read().
+ int ReadInternal(scoped_refptr<DrainableIOBuffer> buf,
+ const CompletionCallback& callback);
+
+ // Resumes pending read and calls callback with the result when necessary.
+ void ResumePendingRead(scoped_refptr<DrainableIOBuffer> buf,
+ const CompletionCallback& callback,
+ int previous_result);
+
+ // Processes result of UploadElementReader::Read(). If |result| indicates
+ // success, updates |buf|'s offset. Otherwise, sets |read_failed_| to true.
+ void ProcessReadResult(scoped_refptr<DrainableIOBuffer> buf,
+ int result);
+
+ ScopedVector<UploadElementReader> element_readers_;
+
+ // Index of the current upload element (i.e. the element currently being
+ // read). The index is used as a cursor to iterate over elements in
+ // |upload_data_|.
+ size_t element_index_;
+
+ // Size and current read position within the upload data stream.
+ // |total_size_| is set to zero when the data is chunked.
+ uint64 total_size_;
+ uint64 current_position_;
+
+ const int64 identifier_;
+
+ const bool is_chunked_;
+ bool last_chunk_appended_;
+
+ // True if an error occcured during read operation.
+ bool read_failed_;
+
+ // True if the initialization was successful.
+ bool initialized_successfully_;
+
+ // Callback to resume reading chunked data.
+ base::Closure pending_chunked_read_callback_;
+
+ base::WeakPtrFactory<UploadDataStream> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadDataStream);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_DATA_STREAM_H_
diff --git a/chromium/net/base/upload_data_stream_unittest.cc b/chromium/net/base/upload_data_stream_unittest.cc
new file mode 100644
index 00000000000..42b71b9cbea
--- /dev/null
+++ b/chromium/net/base/upload_data_stream_unittest.cc
@@ -0,0 +1,814 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_data_stream.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_file_element_reader.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::_;
+
+namespace net {
+
+namespace {
+
+const char kTestData[] = "0123456789";
+const size_t kTestDataSize = arraysize(kTestData) - 1;
+const size_t kTestBufferSize = 1 << 14; // 16KB.
+
+// Reads data from the upload data stream, and returns the data as string.
+std::string ReadFromUploadDataStream(UploadDataStream* stream) {
+ std::string data_read;
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream->IsEOF()) {
+ TestCompletionCallback callback;
+ const int result =
+ stream->Read(buf.get(), kTestBufferSize, callback.callback());
+ const int bytes_read =
+ result != ERR_IO_PENDING ? result : callback.WaitForResult();
+ data_read.append(buf->data(), bytes_read);
+ }
+ return data_read;
+}
+
+// A mock class of UploadElementReader.
+class MockUploadElementReader : public UploadElementReader {
+ public:
+ MockUploadElementReader(int content_length, bool is_in_memory)
+ : content_length_(content_length),
+ bytes_remaining_(content_length),
+ is_in_memory_(is_in_memory),
+ init_result_(OK),
+ read_result_(OK) {}
+
+ virtual ~MockUploadElementReader() {}
+
+ // UploadElementReader overrides.
+ MOCK_METHOD1(Init, int(const CompletionCallback& callback));
+ virtual uint64 GetContentLength() const OVERRIDE { return content_length_; }
+ virtual uint64 BytesRemaining() const OVERRIDE { return bytes_remaining_; }
+ virtual bool IsInMemory() const OVERRIDE { return is_in_memory_; }
+ MOCK_METHOD3(Read, int(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback));
+
+ // Sets expectation to return the specified result from Init() asynchronously.
+ void SetAsyncInitExpectation(int result) {
+ init_result_ = result;
+ EXPECT_CALL(*this, Init(_))
+ .WillOnce(DoAll(Invoke(this, &MockUploadElementReader::OnInit),
+ Return(ERR_IO_PENDING)));
+ }
+
+ // Sets expectation to return the specified result from Read().
+ void SetReadExpectation(int result) {
+ read_result_ = result;
+ EXPECT_CALL(*this, Read(_, _, _))
+ .WillOnce(Invoke(this, &MockUploadElementReader::OnRead));
+ }
+
+ private:
+ void OnInit(const CompletionCallback& callback) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback, init_result_));
+ }
+
+ int OnRead(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) {
+ if (read_result_ > 0)
+ bytes_remaining_ = std::max(0, bytes_remaining_ - read_result_);
+ if (IsInMemory()) {
+ return read_result_;
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, read_result_));
+ return ERR_IO_PENDING;
+ }
+ }
+
+ int content_length_;
+ int bytes_remaining_;
+ bool is_in_memory_;
+
+ // Result value returned from Init().
+ int init_result_;
+
+ // Result value returned from Read().
+ int read_result_;
+};
+
+} // namespace
+
+class UploadDataStreamTest : public PlatformTest {
+ public:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ void FileChangedHelper(const base::FilePath& file_path,
+ const base::Time& time,
+ bool error_expected);
+
+ base::ScopedTempDir temp_dir_;
+ ScopedVector<UploadElementReader> element_readers_;
+};
+
+TEST_F(UploadDataStreamTest, EmptyUploadData) {
+ UploadDataStream stream(&element_readers_, 0);
+ ASSERT_EQ(OK, stream.Init(CompletionCallback()));
+ EXPECT_TRUE(stream.IsInMemory());
+ EXPECT_EQ(0U, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, ConsumeAllBytes) {
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ UploadDataStream stream(&element_readers_, 0);
+ ASSERT_EQ(OK, stream.Init(CompletionCallback()));
+ EXPECT_TRUE(stream.IsInMemory());
+ EXPECT_EQ(kTestDataSize, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream.IsEOF()) {
+ int bytes_read =
+ stream.Read(buf.get(), kTestBufferSize, CompletionCallback());
+ ASSERT_LE(0, bytes_read); // Not an error.
+ }
+ EXPECT_EQ(kTestDataSize, stream.position());
+ ASSERT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, File) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+
+ TestCompletionCallback init_callback;
+ UploadDataStream stream(&element_readers_, 0);
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ ASSERT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_FALSE(stream.IsInMemory());
+ EXPECT_EQ(kTestDataSize, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream.IsEOF()) {
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(
+ ERR_IO_PENDING,
+ stream.Read(buf.get(), kTestBufferSize, read_callback.callback()));
+ ASSERT_LE(0, read_callback.WaitForResult()); // Not an error.
+ }
+ EXPECT_EQ(kTestDataSize, stream.position());
+ ASSERT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, FileSmallerThanLength) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+ const uint64 kFakeSize = kTestDataSize*2;
+
+ UploadFileElementReader::ScopedOverridingContentLengthForTests
+ overriding_content_length(kFakeSize);
+
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+
+ TestCompletionCallback init_callback;
+ UploadDataStream stream(&element_readers_, 0);
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ ASSERT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_FALSE(stream.IsInMemory());
+ EXPECT_EQ(kFakeSize, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ uint64 read_counter = 0;
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream.IsEOF()) {
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(
+ ERR_IO_PENDING,
+ stream.Read(buf.get(), kTestBufferSize, read_callback.callback()));
+ int bytes_read = read_callback.WaitForResult();
+ ASSERT_LE(0, bytes_read); // Not an error.
+ read_counter += bytes_read;
+ EXPECT_EQ(read_counter, stream.position());
+ }
+ // UpdateDataStream will pad out the file with 0 bytes so that the HTTP
+ // transaction doesn't hang. Therefore we expected the full size.
+ EXPECT_EQ(kFakeSize, read_counter);
+ EXPECT_EQ(read_counter, stream.position());
+}
+
+TEST_F(UploadDataStreamTest, ReadErrorSync) {
+ // This element cannot be read.
+ MockUploadElementReader* reader =
+ new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ reader->SetReadExpectation(ERR_FAILED);
+ element_readers_.push_back(reader);
+
+ // This element is ignored because of the error from the previous reader.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ ASSERT_EQ(OK, stream.Init(CompletionCallback()));
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+
+ // Prepare a buffer filled with non-zero data.
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ std::fill_n(buf->data(), kTestBufferSize, -1);
+
+ // Read() results in success even when the reader returns error.
+ EXPECT_EQ(static_cast<int>(kTestDataSize * 2),
+ stream.Read(buf.get(), kTestBufferSize, CompletionCallback()));
+ EXPECT_EQ(kTestDataSize * 2, stream.position());
+ EXPECT_TRUE(stream.IsEOF());
+
+ // The buffer is filled with zero.
+ EXPECT_EQ(static_cast<int>(kTestDataSize*2),
+ std::count(buf->data(), buf->data() + kTestBufferSize, 0));
+}
+
+TEST_F(UploadDataStreamTest, ReadErrorAsync) {
+ // This element cannot be read.
+ MockUploadElementReader* reader =
+ new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ reader->SetReadExpectation(ERR_FAILED);
+ element_readers_.push_back(reader);
+
+ // This element is ignored because of the error from the previous reader.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ TestCompletionCallback init_callback;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+
+ // Prepare a buffer filled with non-zero data.
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ std::fill_n(buf->data(), kTestBufferSize, -1);
+
+ // Read() results in success even when the reader returns error.
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(ERR_IO_PENDING,
+ stream.Read(buf.get(), kTestBufferSize, read_callback.callback()));
+ EXPECT_EQ(static_cast<int>(kTestDataSize * 2), read_callback.WaitForResult());
+ EXPECT_EQ(kTestDataSize*2, stream.position());
+ EXPECT_TRUE(stream.IsEOF());
+
+ // The buffer is filled with zero.
+ EXPECT_EQ(static_cast<int>(kTestDataSize*2),
+ std::count(buf->data(), buf->data() + kTestBufferSize, 0));
+}
+
+TEST_F(UploadDataStreamTest, FileAndBytes) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ const uint64 kFileRangeOffset = 1;
+ const uint64 kFileRangeLength = 4;
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ kFileRangeOffset,
+ kFileRangeLength,
+ base::Time()));
+
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+
+ const uint64 kStreamSize = kTestDataSize + kFileRangeLength;
+ TestCompletionCallback init_callback;
+ UploadDataStream stream(&element_readers_, 0);
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ ASSERT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_FALSE(stream.IsInMemory());
+ EXPECT_EQ(kStreamSize, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream.IsEOF()) {
+ TestCompletionCallback read_callback;
+ const int result =
+ stream.Read(buf.get(), kTestBufferSize, read_callback.callback());
+ const int bytes_read =
+ result != ERR_IO_PENDING ? result : read_callback.WaitForResult();
+ ASSERT_LE(0, bytes_read); // Not an error.
+ }
+ EXPECT_EQ(kStreamSize, stream.position());
+ ASSERT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, Chunk) {
+ const uint64 kStreamSize = kTestDataSize*2;
+ UploadDataStream stream(UploadDataStream::CHUNKED, 0);
+ stream.AppendChunk(kTestData, kTestDataSize, false);
+ stream.AppendChunk(kTestData, kTestDataSize, true);
+
+ ASSERT_EQ(OK, stream.Init(CompletionCallback()));
+ EXPECT_FALSE(stream.IsInMemory());
+ EXPECT_EQ(0U, stream.size()); // Content-Length is 0 for chunked data.
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+ while (!stream.IsEOF()) {
+ int bytes_read =
+ stream.Read(buf.get(), kTestBufferSize, CompletionCallback());
+ ASSERT_LE(0, bytes_read); // Not an error.
+ }
+ EXPECT_EQ(kStreamSize, stream.position());
+ ASSERT_TRUE(stream.IsEOF());
+}
+
+// Init() with on-memory and not-on-memory readers.
+TEST_F(UploadDataStreamTest, InitAsync) {
+ // Create UploadDataStream with mock readers.
+ MockUploadElementReader* reader = NULL;
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ element_readers_.push_back(reader);
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(callback.callback()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Init() of a reader fails asynchronously.
+TEST_F(UploadDataStreamTest, InitAsyncFailureAsync) {
+ // Create UploadDataStream with a mock reader.
+ MockUploadElementReader* reader = NULL;
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(ERR_FAILED);
+ element_readers_.push_back(reader);
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(callback.callback()));
+ EXPECT_EQ(ERR_FAILED, callback.WaitForResult());
+}
+
+// Init() of a reader fails synchronously.
+TEST_F(UploadDataStreamTest, InitAsyncFailureSync) {
+ // Create UploadDataStream with mock readers.
+ MockUploadElementReader* reader = NULL;
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(ERR_FAILED));
+ element_readers_.push_back(reader);
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(callback.callback()));
+ EXPECT_EQ(ERR_FAILED, callback.WaitForResult());
+}
+
+// Read with a buffer whose size is same as the data.
+TEST_F(UploadDataStreamTest, ReadAsyncWithExactSizeBuffer) {
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ UploadDataStream stream(&element_readers_, 0);
+
+ ASSERT_EQ(OK, stream.Init(CompletionCallback()));
+ EXPECT_TRUE(stream.IsInMemory());
+ EXPECT_EQ(kTestDataSize, stream.size());
+ EXPECT_EQ(0U, stream.position());
+ EXPECT_FALSE(stream.IsEOF());
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestDataSize);
+ int bytes_read = stream.Read(buf.get(), kTestDataSize, CompletionCallback());
+ ASSERT_EQ(static_cast<int>(kTestDataSize), bytes_read); // Not an error.
+ EXPECT_EQ(kTestDataSize, stream.position());
+ ASSERT_TRUE(stream.IsEOF());
+}
+
+// Async Read() with on-memory and not-on-memory readers.
+TEST_F(UploadDataStreamTest, ReadAsync) {
+ // Create UploadDataStream with mock readers.
+ MockUploadElementReader* reader = NULL;
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ reader->SetReadExpectation(kTestDataSize);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ reader->SetReadExpectation(kTestDataSize);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, true);
+ EXPECT_CALL(*reader, Init(_)).WillOnce(Return(OK));
+ reader->SetReadExpectation(kTestDataSize);
+ element_readers_.push_back(reader);
+
+ reader = new MockUploadElementReader(kTestDataSize, false);
+ reader->SetAsyncInitExpectation(OK);
+ reader->SetReadExpectation(kTestDataSize);
+ element_readers_.push_back(reader);
+
+ UploadDataStream stream(&element_readers_, 0);
+
+ // Run Init().
+ TestCompletionCallback init_callback;
+ EXPECT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ scoped_refptr<IOBuffer> buf = new IOBuffer(kTestBufferSize);
+
+ // Consume the first element.
+ TestCompletionCallback read_callback1;
+ EXPECT_EQ(static_cast<int>(kTestDataSize),
+ stream.Read(buf.get(), kTestDataSize, read_callback1.callback()));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(read_callback1.have_result());
+
+ // Consume the second element.
+ TestCompletionCallback read_callback2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ stream.Read(buf.get(), kTestDataSize, read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(kTestDataSize), read_callback2.WaitForResult());
+
+ // Consume the third and the fourth elements.
+ TestCompletionCallback read_callback3;
+ ASSERT_EQ(
+ ERR_IO_PENDING,
+ stream.Read(buf.get(), kTestDataSize * 2, read_callback3.callback()));
+ EXPECT_EQ(static_cast<int>(kTestDataSize * 2),
+ read_callback3.WaitForResult());
+}
+
+void UploadDataStreamTest::FileChangedHelper(const base::FilePath& file_path,
+ const base::Time& time,
+ bool error_expected) {
+ // Don't use element_readers_ here, as this function is called twice, and
+ // reusing element_readers_ is wrong.
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(new UploadFileElementReader(
+ base::MessageLoopProxy::current().get(), file_path, 1, 2, time));
+
+ TestCompletionCallback init_callback;
+ UploadDataStream stream(&element_readers, 0);
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback.callback()));
+ int error_code = init_callback.WaitForResult();
+ if (error_expected)
+ ASSERT_EQ(ERR_UPLOAD_FILE_CHANGED, error_code);
+ else
+ ASSERT_EQ(OK, error_code);
+}
+
+TEST_F(UploadDataStreamTest, FileChanged) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ base::PlatformFileInfo file_info;
+ ASSERT_TRUE(file_util::GetFileInfo(temp_file_path, &file_info));
+
+ // Test file not changed.
+ FileChangedHelper(temp_file_path, file_info.last_modified, false);
+
+ // Test file changed.
+ FileChangedHelper(temp_file_path,
+ file_info.last_modified - base::TimeDelta::FromSeconds(1),
+ true);
+}
+
+TEST_F(UploadDataStreamTest, MultipleInit) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ // Prepare data.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream stream(&element_readers_, 0);
+
+ std::string expected_data(kTestData, kTestData + kTestDataSize);
+ expected_data += expected_data;
+
+ // Call Init().
+ TestCompletionCallback init_callback1;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback1.callback()));
+ ASSERT_EQ(OK, init_callback1.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read.
+ EXPECT_EQ(expected_data, ReadFromUploadDataStream(&stream));
+ EXPECT_TRUE(stream.IsEOF());
+
+ // Call Init() again to reset.
+ TestCompletionCallback init_callback2;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback2.callback()));
+ ASSERT_EQ(OK, init_callback2.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read again.
+ EXPECT_EQ(expected_data, ReadFromUploadDataStream(&stream));
+ EXPECT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, MultipleInitAsync) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+ TestCompletionCallback test_callback;
+
+ // Prepare data.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream stream(&element_readers_, 0);
+
+ std::string expected_data(kTestData, kTestData + kTestDataSize);
+ expected_data += expected_data;
+
+ // Call Init().
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(test_callback.callback()));
+ EXPECT_EQ(OK, test_callback.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read.
+ EXPECT_EQ(expected_data, ReadFromUploadDataStream(&stream));
+ EXPECT_TRUE(stream.IsEOF());
+
+ // Call Init() again to reset.
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(test_callback.callback()));
+ EXPECT_EQ(OK, test_callback.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read again.
+ EXPECT_EQ(expected_data, ReadFromUploadDataStream(&stream));
+ EXPECT_TRUE(stream.IsEOF());
+}
+
+TEST_F(UploadDataStreamTest, InitToReset) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ // Prepare data.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream stream(&element_readers_, 0);
+
+ std::vector<char> expected_data(kTestData, kTestData + kTestDataSize);
+ expected_data.insert(expected_data.end(), expected_data.begin(),
+ expected_data.begin() + kTestDataSize);
+
+ // Call Init().
+ TestCompletionCallback init_callback1;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback1.callback()));
+ EXPECT_EQ(OK, init_callback1.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read some.
+ TestCompletionCallback read_callback1;
+ std::vector<char> buf(kTestDataSize + kTestDataSize/2);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ ERR_IO_PENDING,
+ stream.Read(wrapped_buffer.get(), buf.size(), read_callback1.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback1.WaitForResult());
+ EXPECT_EQ(buf.size(), stream.position());
+
+ // Call Init to reset the state.
+ TestCompletionCallback init_callback2;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback2.callback()));
+ EXPECT_EQ(OK, init_callback2.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read.
+ TestCompletionCallback read_callback2;
+ std::vector<char> buf2(kTestDataSize*2);
+ scoped_refptr<IOBuffer> wrapped_buffer2 = new WrappedIOBuffer(&buf2[0]);
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream.Read(
+ wrapped_buffer2.get(), buf2.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf2.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(expected_data, buf2);
+}
+
+TEST_F(UploadDataStreamTest, InitDuringAsyncInit) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ // Prepare data.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream stream(&element_readers_, 0);
+
+ std::vector<char> expected_data(kTestData, kTestData + kTestDataSize);
+ expected_data.insert(expected_data.end(), expected_data.begin(),
+ expected_data.begin() + kTestDataSize);
+
+ // Start Init.
+ TestCompletionCallback init_callback1;
+ EXPECT_EQ(ERR_IO_PENDING, stream.Init(init_callback1.callback()));
+
+ // Call Init again to cancel the previous init.
+ TestCompletionCallback init_callback2;
+ EXPECT_EQ(ERR_IO_PENDING, stream.Init(init_callback2.callback()));
+ EXPECT_EQ(OK, init_callback2.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read.
+ TestCompletionCallback read_callback2;
+ std::vector<char> buf2(kTestDataSize*2);
+ scoped_refptr<IOBuffer> wrapped_buffer2 = new WrappedIOBuffer(&buf2[0]);
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream.Read(
+ wrapped_buffer2.get(), buf2.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf2.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(expected_data, buf2);
+ EXPECT_TRUE(stream.IsEOF());
+
+ // Make sure callbacks are not called for cancelled operations.
+ EXPECT_FALSE(init_callback1.have_result());
+}
+
+TEST_F(UploadDataStreamTest, InitDuringAsyncRead) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path));
+ ASSERT_EQ(static_cast<int>(kTestDataSize),
+ file_util::WriteFile(temp_file_path, kTestData, kTestDataSize));
+
+ // Prepare data.
+ element_readers_.push_back(new UploadBytesElementReader(
+ kTestData, kTestDataSize));
+ element_readers_.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream stream(&element_readers_, 0);
+
+ std::vector<char> expected_data(kTestData, kTestData + kTestDataSize);
+ expected_data.insert(expected_data.end(), expected_data.begin(),
+ expected_data.begin() + kTestDataSize);
+
+ // Call Init().
+ TestCompletionCallback init_callback1;
+ ASSERT_EQ(ERR_IO_PENDING, stream.Init(init_callback1.callback()));
+ EXPECT_EQ(OK, init_callback1.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Start reading.
+ TestCompletionCallback read_callback1;
+ std::vector<char> buf(kTestDataSize*2);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ ERR_IO_PENDING,
+ stream.Read(wrapped_buffer.get(), buf.size(), read_callback1.callback()));
+
+ // Call Init to cancel the previous read.
+ TestCompletionCallback init_callback2;
+ EXPECT_EQ(ERR_IO_PENDING, stream.Init(init_callback2.callback()));
+ EXPECT_EQ(OK, init_callback2.WaitForResult());
+ EXPECT_FALSE(stream.IsEOF());
+ EXPECT_EQ(kTestDataSize*2, stream.size());
+
+ // Read.
+ TestCompletionCallback read_callback2;
+ std::vector<char> buf2(kTestDataSize*2);
+ scoped_refptr<IOBuffer> wrapped_buffer2 = new WrappedIOBuffer(&buf2[0]);
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream.Read(
+ wrapped_buffer2.get(), buf2.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf2.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(expected_data, buf2);
+ EXPECT_TRUE(stream.IsEOF());
+
+ // Make sure callbacks are not called for cancelled operations.
+ EXPECT_FALSE(read_callback1.have_result());
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_element.cc b/chromium/net/base/upload_element.cc
new file mode 100644
index 00000000000..6b6438aec26
--- /dev/null
+++ b/chromium/net/base/upload_element.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_element.h"
+
+#include <algorithm>
+
+#include "net/base/file_stream.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+UploadElement::UploadElement()
+ : type_(TYPE_BYTES),
+ bytes_start_(NULL),
+ bytes_length_(0),
+ file_range_offset_(0),
+ file_range_length_(kuint64max) {
+}
+
+UploadElement::~UploadElement() {
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_element.h b/chromium/net/base/upload_element.h
new file mode 100644
index 00000000000..34f601464d9
--- /dev/null
+++ b/chromium/net/base/upload_element.h
@@ -0,0 +1,110 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_ELEMENT_H_
+#define NET_BASE_UPLOAD_ELEMENT_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A class representing an element contained by UploadData.
+class NET_EXPORT UploadElement {
+ public:
+ enum Type {
+ TYPE_BYTES,
+ TYPE_FILE,
+ };
+
+ UploadElement();
+ ~UploadElement();
+
+ Type type() const { return type_; }
+
+ const char* bytes() const { return bytes_start_ ? bytes_start_ : &buf_[0]; }
+ uint64 bytes_length() const { return buf_.size() + bytes_length_; }
+ const base::FilePath& file_path() const { return file_path_; }
+ uint64 file_range_offset() const { return file_range_offset_; }
+ uint64 file_range_length() const { return file_range_length_; }
+ // If NULL time is returned, we do not do the check.
+ const base::Time& expected_file_modification_time() const {
+ return expected_file_modification_time_;
+ }
+
+ void SetToBytes(const char* bytes, int bytes_len) {
+ type_ = TYPE_BYTES;
+ buf_.assign(bytes, bytes + bytes_len);
+ }
+
+ // This does not copy the given data and the caller should make sure
+ // the data is secured somewhere else (e.g. by attaching the data
+ // using SetUserData).
+ void SetToSharedBytes(const char* bytes, int bytes_len) {
+ type_ = TYPE_BYTES;
+ bytes_start_ = bytes;
+ bytes_length_ = bytes_len;
+ }
+
+ void SetToFilePath(const base::FilePath& path) {
+ SetToFilePathRange(path, 0, kuint64max, base::Time());
+ }
+
+ // If expected_modification_time is NULL, we do not check for the file
+ // change. Also note that the granularity for comparison is time_t, not
+ // the full precision.
+ void SetToFilePathRange(const base::FilePath& path,
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ type_ = TYPE_FILE;
+ file_path_ = path;
+ file_range_offset_ = offset;
+ file_range_length_ = length;
+ expected_file_modification_time_ = expected_modification_time;
+ }
+
+ private:
+ Type type_;
+ std::vector<char> buf_;
+ const char* bytes_start_;
+ uint64 bytes_length_;
+ base::FilePath file_path_;
+ uint64 file_range_offset_;
+ uint64 file_range_length_;
+ base::Time expected_file_modification_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadElement);
+};
+
+#if defined(UNIT_TEST)
+inline bool operator==(const UploadElement& a,
+ const UploadElement& b) {
+ if (a.type() != b.type())
+ return false;
+ if (a.type() == UploadElement::TYPE_BYTES)
+ return a.bytes_length() == b.bytes_length() &&
+ memcmp(a.bytes(), b.bytes(), b.bytes_length()) == 0;
+ if (a.type() == UploadElement::TYPE_FILE) {
+ return a.file_path() == b.file_path() &&
+ a.file_range_offset() == b.file_range_offset() &&
+ a.file_range_length() == b.file_range_length() &&
+ a.expected_file_modification_time() ==
+ b.expected_file_modification_time();
+ }
+ return false;
+}
+
+inline bool operator!=(const UploadElement& a,
+ const UploadElement& b) {
+ return !(a == b);
+}
+#endif // defined(UNIT_TEST)
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_ELEMENT_H_
diff --git a/chromium/net/base/upload_element_reader.cc b/chromium/net/base/upload_element_reader.cc
new file mode 100644
index 00000000000..f44931c1eca
--- /dev/null
+++ b/chromium/net/base/upload_element_reader.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_element_reader.h"
+
+namespace net {
+
+const UploadBytesElementReader* UploadElementReader::AsBytesReader() const {
+ return NULL;
+}
+
+const UploadFileElementReader* UploadElementReader::AsFileReader() const {
+ return NULL;
+}
+
+bool UploadElementReader::IsInMemory() const {
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_element_reader.h b/chromium/net/base/upload_element_reader.h
new file mode 100644
index 00000000000..d71f57cdb64
--- /dev/null
+++ b/chromium/net/base/upload_element_reader.h
@@ -0,0 +1,62 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_ELEMENT_READER_H_
+#define NET_BASE_UPLOAD_ELEMENT_READER_H_
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IOBuffer;
+class UploadBytesElementReader;
+class UploadFileElementReader;
+
+// An interface to read an upload data element.
+class NET_EXPORT UploadElementReader {
+ public:
+ UploadElementReader() {}
+ virtual ~UploadElementReader() {}
+
+ // Returns this instance's pointer as UploadBytesElementReader when possible,
+ // otherwise returns NULL.
+ virtual const UploadBytesElementReader* AsBytesReader() const;
+
+ // Returns this instance's pointer as UploadFileElementReader when possible,
+ // otherwise returns NULL.
+ virtual const UploadFileElementReader* AsFileReader() const;
+
+ // Initializes the instance synchronously when possible, otherwise does
+ // initialization aynschronously, returns ERR_IO_PENDING and runs callback.
+ // Calling this method again after a Init() success results in resetting the
+ // state.
+ virtual int Init(const CompletionCallback& callback) = 0;
+
+ // Returns the byte-length of the element. For files that do not exist, 0
+ // is returned. This is done for consistency with Mozilla.
+ virtual uint64 GetContentLength() const = 0;
+
+ // Returns the number of bytes remaining to read.
+ virtual uint64 BytesRemaining() const = 0;
+
+ // Returns true if the upload element is entirely in memory.
+ // The default implementation returns false.
+ virtual bool IsInMemory() const;
+
+ // Reads up to |buf_length| bytes synchronously and returns the number of
+ // bytes read or error code when possible, otherwise, returns ERR_IO_PENDING
+ // and runs |callback| with the result. |buf_length| must be greater than 0.
+ virtual int Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UploadElementReader);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_ELEMENT_READER_H_
diff --git a/chromium/net/base/upload_file_element_reader.cc b/chromium/net/base/upload_file_element_reader.cc
new file mode 100644
index 00000000000..d1f2a12ac8c
--- /dev/null
+++ b/chromium/net/base/upload_file_element_reader.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_file_element_reader.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/location.h"
+#include "base/task_runner_util.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+// In tests, this value is used to override the return value of
+// UploadFileElementReader::GetContentLength() when set to non-zero.
+uint64 overriding_content_length = 0;
+
+// This function is used to implement Init().
+template<typename FileStreamDeleter>
+int InitInternal(const base::FilePath& path,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time,
+ scoped_ptr<FileStream, FileStreamDeleter>* out_file_stream,
+ uint64* out_content_length) {
+ scoped_ptr<FileStream> file_stream(new FileStream(NULL));
+ int64 rv = file_stream->OpenSync(
+ path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ);
+ if (rv != OK) {
+ // If the file can't be opened, we'll just upload an empty file.
+ DLOG(WARNING) << "Failed to open \"" << path.value()
+ << "\" for reading: " << rv;
+ file_stream.reset();
+ } else if (range_offset) {
+ rv = file_stream->SeekSync(FROM_BEGIN, range_offset);
+ if (rv < 0) {
+ DLOG(WARNING) << "Failed to seek \"" << path.value()
+ << "\" to offset: " << range_offset << " (" << rv << ")";
+ file_stream.reset();
+ }
+ }
+
+ int64 length = 0;
+ if (file_stream.get() &&
+ file_util::GetFileSize(path, &length) &&
+ range_offset < static_cast<uint64>(length)) {
+ // Compensate for the offset.
+ length = std::min(length - range_offset, range_length);
+ }
+ *out_content_length = length;
+ out_file_stream->reset(file_stream.release());
+
+ // If the underlying file has been changed and the expected file modification
+ // time is set, treat it as error. Note that the expected modification time
+ // from WebKit is based on time_t precision. So we have to convert both to
+ // time_t to compare. This check is used for sliced files.
+ if (!expected_modification_time.is_null()) {
+ base::PlatformFileInfo info;
+ if (file_util::GetFileInfo(path, &info) &&
+ expected_modification_time.ToTimeT() != info.last_modified.ToTimeT()) {
+ return ERR_UPLOAD_FILE_CHANGED;
+ }
+ }
+
+ return OK;
+}
+
+// This function is used to implement Read().
+int ReadInternal(scoped_refptr<IOBuffer> buf,
+ int buf_length,
+ uint64 bytes_remaining,
+ FileStream* file_stream) {
+ DCHECK_LT(0, buf_length);
+
+ const uint64 num_bytes_to_read =
+ std::min(bytes_remaining, static_cast<uint64>(buf_length));
+
+ int result = 0;
+ if (num_bytes_to_read > 0) {
+ DCHECK(file_stream); // file_stream is non-null if content_length_ > 0.
+ result = file_stream->ReadSync(buf->data(), num_bytes_to_read);
+ if (result == 0) // Reached end-of-file earlier than expected.
+ result = ERR_UPLOAD_FILE_CHANGED;
+ }
+ return result;
+}
+
+} // namespace
+
+UploadFileElementReader::FileStreamDeleter::FileStreamDeleter(
+ base::TaskRunner* task_runner) : task_runner_(task_runner) {
+ DCHECK(task_runner_.get());
+}
+
+UploadFileElementReader::FileStreamDeleter::~FileStreamDeleter() {}
+
+void UploadFileElementReader::FileStreamDeleter::operator() (
+ FileStream* file_stream) const {
+ if (file_stream) {
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&base::DeletePointer<FileStream>,
+ file_stream));
+ }
+}
+
+UploadFileElementReader::UploadFileElementReader(
+ base::TaskRunner* task_runner,
+ const base::FilePath& path,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time)
+ : task_runner_(task_runner),
+ path_(path),
+ range_offset_(range_offset),
+ range_length_(range_length),
+ expected_modification_time_(expected_modification_time),
+ file_stream_(NULL, FileStreamDeleter(task_runner_.get())),
+ content_length_(0),
+ bytes_remaining_(0),
+ weak_ptr_factory_(this) {
+ DCHECK(task_runner_.get());
+}
+
+UploadFileElementReader::~UploadFileElementReader() {
+}
+
+const UploadFileElementReader* UploadFileElementReader::AsFileReader() const {
+ return this;
+}
+
+int UploadFileElementReader::Init(const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ Reset();
+
+ ScopedFileStreamPtr* file_stream =
+ new ScopedFileStreamPtr(NULL, FileStreamDeleter(task_runner_.get()));
+ uint64* content_length = new uint64;
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&InitInternal<FileStreamDeleter>,
+ path_,
+ range_offset_,
+ range_length_,
+ expected_modification_time_,
+ file_stream,
+ content_length),
+ base::Bind(&UploadFileElementReader::OnInitCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(file_stream),
+ base::Owned(content_length),
+ callback));
+ DCHECK(posted);
+ return ERR_IO_PENDING;
+}
+
+uint64 UploadFileElementReader::GetContentLength() const {
+ if (overriding_content_length)
+ return overriding_content_length;
+ return content_length_;
+}
+
+uint64 UploadFileElementReader::BytesRemaining() const {
+ return bytes_remaining_;
+}
+
+int UploadFileElementReader::Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (BytesRemaining() == 0)
+ return 0;
+
+ // Save the value of file_stream_.get() before base::Passed() invalidates it.
+ FileStream* file_stream_ptr = file_stream_.get();
+ // Pass the ownership of file_stream_ to the worker pool to safely perform
+ // operation even when |this| is destructed before the read completes.
+ const bool posted = base::PostTaskAndReplyWithResult(
+ task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ReadInternal,
+ scoped_refptr<IOBuffer>(buf),
+ buf_length,
+ BytesRemaining(),
+ file_stream_ptr),
+ base::Bind(&UploadFileElementReader::OnReadCompleted,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&file_stream_),
+ callback));
+ DCHECK(posted);
+ return ERR_IO_PENDING;
+}
+
+void UploadFileElementReader::Reset() {
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ bytes_remaining_ = 0;
+ content_length_ = 0;
+ file_stream_.reset();
+}
+
+void UploadFileElementReader::OnInitCompleted(
+ ScopedFileStreamPtr* file_stream,
+ uint64* content_length,
+ const CompletionCallback& callback,
+ int result) {
+ file_stream_.swap(*file_stream);
+ content_length_ = *content_length;
+ bytes_remaining_ = GetContentLength();
+ if (!callback.is_null())
+ callback.Run(result);
+}
+
+void UploadFileElementReader::OnReadCompleted(
+ ScopedFileStreamPtr file_stream,
+ const CompletionCallback& callback,
+ int result) {
+ file_stream_.swap(file_stream);
+ if (result > 0) {
+ DCHECK_GE(bytes_remaining_, static_cast<uint64>(result));
+ bytes_remaining_ -= result;
+ }
+ if (!callback.is_null())
+ callback.Run(result);
+}
+
+UploadFileElementReader::ScopedOverridingContentLengthForTests::
+ScopedOverridingContentLengthForTests(uint64 value) {
+ overriding_content_length = value;
+}
+
+UploadFileElementReader::ScopedOverridingContentLengthForTests::
+~ScopedOverridingContentLengthForTests() {
+ overriding_content_length = 0;
+}
+
+UploadFileElementReaderSync::UploadFileElementReaderSync(
+ const base::FilePath& path,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time)
+ : path_(path),
+ range_offset_(range_offset),
+ range_length_(range_length),
+ expected_modification_time_(expected_modification_time),
+ content_length_(0),
+ bytes_remaining_(0) {
+}
+
+UploadFileElementReaderSync::~UploadFileElementReaderSync() {
+}
+
+int UploadFileElementReaderSync::Init(const CompletionCallback& callback) {
+ bytes_remaining_ = 0;
+ content_length_ = 0;
+ file_stream_.reset();
+
+ const int result = InitInternal(path_, range_offset_, range_length_,
+ expected_modification_time_,
+ &file_stream_, &content_length_);
+ bytes_remaining_ = GetContentLength();
+ return result;
+}
+
+uint64 UploadFileElementReaderSync::GetContentLength() const {
+ return content_length_;
+}
+
+uint64 UploadFileElementReaderSync::BytesRemaining() const {
+ return bytes_remaining_;
+}
+
+int UploadFileElementReaderSync::Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) {
+ const int result = ReadInternal(buf, buf_length, BytesRemaining(),
+ file_stream_.get());
+ if (result > 0) {
+ DCHECK_GE(bytes_remaining_, static_cast<uint64>(result));
+ bytes_remaining_ -= result;
+ }
+ return result;
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_file_element_reader.h b/chromium/net/base/upload_file_element_reader.h
new file mode 100644
index 00000000000..a805c7a74ee
--- /dev/null
+++ b/chromium/net/base/upload_file_element_reader.h
@@ -0,0 +1,142 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_FILE_ELEMENT_READER_H_
+#define NET_BASE_UPLOAD_FILE_ELEMENT_READER_H_
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/upload_element_reader.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+
+class FileStream;
+
+// An UploadElementReader implementation for file.
+class NET_EXPORT UploadFileElementReader : public UploadElementReader {
+ public:
+ // |task_runner| is used to perform file operations. It must not be NULL.
+ UploadFileElementReader(base::TaskRunner* task_runner,
+ const base::FilePath& path,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time);
+ virtual ~UploadFileElementReader();
+
+ const base::FilePath& path() const { return path_; }
+ uint64 range_offset() const { return range_offset_; }
+ uint64 range_length() const { return range_length_; }
+ const base::Time& expected_modification_time() const {
+ return expected_modification_time_;
+ }
+
+ // UploadElementReader overrides:
+ virtual const UploadFileElementReader* AsFileReader() const OVERRIDE;
+ virtual int Init(const CompletionCallback& callback) OVERRIDE;
+ virtual uint64 GetContentLength() const OVERRIDE;
+ virtual uint64 BytesRemaining() const OVERRIDE;
+ virtual int Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ // Deletes FileStream with |task_runner| to avoid blocking the IO thread.
+ // This class is used as a template argument of scoped_ptr.
+ class FileStreamDeleter {
+ public:
+ explicit FileStreamDeleter(base::TaskRunner* task_runner);
+ ~FileStreamDeleter();
+ void operator() (FileStream* file_stream) const;
+
+ private:
+ scoped_refptr<base::TaskRunner> task_runner_;
+ };
+
+ typedef scoped_ptr<FileStream, FileStreamDeleter> ScopedFileStreamPtr;
+
+ FRIEND_TEST_ALL_PREFIXES(UploadDataStreamTest, FileSmallerThanLength);
+ FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionTest,
+ UploadFileSmallerThanLength);
+ FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy2Test,
+ UploadFileSmallerThanLength);
+ FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionSpdy3Test,
+ UploadFileSmallerThanLength);
+
+ // Resets this instance to the uninitialized state.
+ void Reset();
+
+ // This method is used to implement Init().
+ void OnInitCompleted(ScopedFileStreamPtr* file_stream,
+ uint64* content_length,
+ const CompletionCallback& callback,
+ int result);
+
+ // This method is used to implement Read().
+ void OnReadCompleted(ScopedFileStreamPtr file_stream,
+ const CompletionCallback& callback,
+ int result);
+
+ // Sets an value to override the result for GetContentLength().
+ // Used for tests.
+ struct NET_EXPORT_PRIVATE ScopedOverridingContentLengthForTests {
+ ScopedOverridingContentLengthForTests(uint64 value);
+ ~ScopedOverridingContentLengthForTests();
+ };
+
+ scoped_refptr<base::TaskRunner> task_runner_;
+ const base::FilePath path_;
+ const uint64 range_offset_;
+ const uint64 range_length_;
+ const base::Time expected_modification_time_;
+ ScopedFileStreamPtr file_stream_;
+ uint64 content_length_;
+ uint64 bytes_remaining_;
+ base::WeakPtrFactory<UploadFileElementReader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadFileElementReader);
+};
+
+// An UploadElementReader implementation for file which performs file operation
+// synchronously.
+// Use this class only if the thread is IO allowed.
+class NET_EXPORT UploadFileElementReaderSync : public UploadElementReader {
+ public:
+ UploadFileElementReaderSync(const base::FilePath& path,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time);
+ virtual ~UploadFileElementReaderSync();
+
+ // UploadElementReader overrides:
+ virtual int Init(const CompletionCallback& callback) OVERRIDE;
+ virtual uint64 GetContentLength() const OVERRIDE;
+ virtual uint64 BytesRemaining() const OVERRIDE;
+ virtual int Read(IOBuffer* buf,
+ int buf_length,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ const base::FilePath path_;
+ const uint64 range_offset_;
+ const uint64 range_length_;
+ const base::Time expected_modification_time_;
+ scoped_ptr<FileStream> file_stream_;
+ uint64 content_length_;
+ uint64 bytes_remaining_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadFileElementReaderSync);
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_FILE_ELEMENT_READER_H_
diff --git a/chromium/net/base/upload_file_element_reader_unittest.cc b/chromium/net/base/upload_file_element_reader_unittest.cc
new file mode 100644
index 00000000000..8224f773046
--- /dev/null
+++ b/chromium/net/base/upload_file_element_reader_unittest.cc
@@ -0,0 +1,367 @@
+// Copyright (c) 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.
+
+#include "net/base/upload_file_element_reader.h"
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class UploadFileElementReaderTest : public PlatformTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ // Some tests (*.ReadPartially) rely on bytes_.size() being even.
+ const char kData[] = "123456789abcdefghi";
+ bytes_.assign(kData, kData + arraysize(kData) - 1);
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path_));
+ ASSERT_EQ(
+ static_cast<int>(bytes_.size()),
+ file_util::WriteFile(temp_file_path_, &bytes_[0], bytes_.size()));
+
+ reader_.reset(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path_,
+ 0,
+ kuint64max,
+ base::Time()));
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING, reader_->Init(callback.callback()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+ EXPECT_FALSE(reader_->IsInMemory());
+ }
+
+ std::vector<char> bytes_;
+ scoped_ptr<UploadElementReader> reader_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath temp_file_path_;
+};
+
+TEST_F(UploadFileElementReaderTest, ReadPartially) {
+ const size_t kHalfSize = bytes_.size() / 2;
+ ASSERT_EQ(bytes_.size(), kHalfSize * 2);
+ std::vector<char> buf(kHalfSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ TestCompletionCallback read_callback1;
+ ASSERT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback1.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback1.WaitForResult());
+ EXPECT_EQ(bytes_.size() - buf.size(), reader_->BytesRemaining());
+ EXPECT_EQ(std::vector<char>(bytes_.begin(), bytes_.begin() + kHalfSize), buf);
+
+ TestCompletionCallback read_callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(std::vector<char>(bytes_.begin() + kHalfSize, bytes_.end()), buf);
+}
+
+TEST_F(UploadFileElementReaderTest, ReadAll) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+ // Try to read again.
+ EXPECT_EQ(0,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback.callback()));
+}
+
+TEST_F(UploadFileElementReaderTest, ReadTooMuch) {
+ const size_t kTooLargeSize = bytes_.size() * 2;
+ std::vector<char> buf(kTooLargeSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback.callback()));
+ EXPECT_EQ(static_cast<int>(bytes_.size()), read_callback.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ buf.resize(bytes_.size()); // Resize to compare.
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadFileElementReaderTest, MultipleInit) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+
+ // Read all.
+ TestCompletionCallback read_callback1;
+ ASSERT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback1.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback1.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+
+ // Call Init() again to reset the state.
+ TestCompletionCallback init_callback;
+ ASSERT_EQ(ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+
+ // Read again.
+ TestCompletionCallback read_callback2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadFileElementReaderTest, InitDuringAsyncOperation) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+
+ // Start reading all.
+ TestCompletionCallback read_callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer.get(), buf.size(), read_callback1.callback()));
+
+ // Call Init to cancel the previous read.
+ TestCompletionCallback init_callback1;
+ EXPECT_EQ(ERR_IO_PENDING, reader_->Init(init_callback1.callback()));
+
+ // Call Init again to cancel the previous init.
+ TestCompletionCallback init_callback2;
+ EXPECT_EQ(ERR_IO_PENDING, reader_->Init(init_callback2.callback()));
+ EXPECT_EQ(OK, init_callback2.WaitForResult());
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+
+ // Read half.
+ std::vector<char> buf2(bytes_.size() / 2);
+ scoped_refptr<IOBuffer> wrapped_buffer2 = new WrappedIOBuffer(&buf2[0]);
+ TestCompletionCallback read_callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ reader_->Read(
+ wrapped_buffer2.get(), buf2.size(), read_callback2.callback()));
+ EXPECT_EQ(static_cast<int>(buf2.size()), read_callback2.WaitForResult());
+ EXPECT_EQ(bytes_.size() - buf2.size(), reader_->BytesRemaining());
+ EXPECT_EQ(std::vector<char>(bytes_.begin(), bytes_.begin() + buf2.size()),
+ buf2);
+
+ // Make sure callbacks are not called for cancelled operations.
+ EXPECT_FALSE(read_callback1.have_result());
+ EXPECT_FALSE(init_callback1.have_result());
+}
+
+TEST_F(UploadFileElementReaderTest, Range) {
+ const uint64 kOffset = 2;
+ const uint64 kLength = bytes_.size() - kOffset * 3;
+ reader_.reset(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path_,
+ kOffset,
+ kLength,
+ base::Time()));
+ TestCompletionCallback init_callback;
+ ASSERT_EQ(ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_EQ(kLength, reader_->GetContentLength());
+ EXPECT_EQ(kLength, reader_->BytesRemaining());
+ std::vector<char> buf(kLength);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ TestCompletionCallback read_callback;
+ ASSERT_EQ(
+ ERR_IO_PENDING,
+ reader_->Read(wrapped_buffer.get(), kLength, read_callback.callback()));
+ EXPECT_EQ(static_cast<int>(kLength), read_callback.WaitForResult());
+ const std::vector<char> expected(bytes_.begin() + kOffset,
+ bytes_.begin() + kOffset + kLength);
+ EXPECT_EQ(expected, buf);
+}
+
+TEST_F(UploadFileElementReaderTest, FileChanged) {
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(temp_file_path_, &info));
+
+ // Expect one second before the actual modification time to simulate change.
+ const base::Time expected_modification_time =
+ info.last_modified - base::TimeDelta::FromSeconds(1);
+ reader_.reset(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path_,
+ 0,
+ kuint64max,
+ expected_modification_time));
+ TestCompletionCallback init_callback;
+ ASSERT_EQ(ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(ERR_UPLOAD_FILE_CHANGED, init_callback.WaitForResult());
+}
+
+TEST_F(UploadFileElementReaderTest, WrongPath) {
+ const base::FilePath wrong_path(FILE_PATH_LITERAL("wrong_path"));
+ reader_.reset(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ wrong_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ TestCompletionCallback init_callback;
+ ASSERT_EQ(ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+ EXPECT_EQ(0U, reader_->GetContentLength());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+}
+
+
+class UploadFileElementReaderSyncTest : public PlatformTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ // Some tests (*.ReadPartially) rely on bytes_.size() being even.
+ const char kData[] = "123456789abcdefghi";
+ bytes_.assign(kData, kData + arraysize(kData) - 1);
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(),
+ &temp_file_path_));
+ ASSERT_EQ(
+ static_cast<int>(bytes_.size()),
+ file_util::WriteFile(temp_file_path_, &bytes_[0], bytes_.size()));
+
+ reader_.reset(new UploadFileElementReaderSync(
+ temp_file_path_, 0, kuint64max, base::Time()));
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+ EXPECT_FALSE(reader_->IsInMemory());
+ }
+
+ std::vector<char> bytes_;
+ scoped_ptr<UploadElementReader> reader_;
+ base::ScopedTempDir temp_dir_;
+ base::FilePath temp_file_path_;
+};
+
+TEST_F(UploadFileElementReaderSyncTest, ReadPartially) {
+ const size_t kHalfSize = bytes_.size() / 2;
+ ASSERT_EQ(bytes_.size(), kHalfSize * 2);
+ std::vector<char> buf(kHalfSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(bytes_.size() - buf.size(), reader_->BytesRemaining());
+ EXPECT_EQ(std::vector<char>(bytes_.begin(), bytes_.begin() + kHalfSize), buf);
+
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(std::vector<char>(bytes_.begin() + kHalfSize, bytes_.end()), buf);
+}
+
+TEST_F(UploadFileElementReaderSyncTest, ReadAll) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+ // Try to read again.
+ EXPECT_EQ(
+ 0, reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+}
+
+TEST_F(UploadFileElementReaderSyncTest, ReadTooMuch) {
+ const size_t kTooLargeSize = bytes_.size() * 2;
+ std::vector<char> buf(kTooLargeSize);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(
+ static_cast<int>(bytes_.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ buf.resize(bytes_.size()); // Resize to compare.
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadFileElementReaderSyncTest, MultipleInit) {
+ std::vector<char> buf(bytes_.size());
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+
+ // Read all.
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+
+ // Call Init() again to reset the state.
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(bytes_.size(), reader_->GetContentLength());
+ EXPECT_EQ(bytes_.size(), reader_->BytesRemaining());
+
+ // Read again.
+ EXPECT_EQ(
+ static_cast<int>(buf.size()),
+ reader_->Read(wrapped_buffer.get(), buf.size(), CompletionCallback()));
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_EQ(bytes_, buf);
+}
+
+TEST_F(UploadFileElementReaderSyncTest, Range) {
+ const uint64 kOffset = 2;
+ const uint64 kLength = bytes_.size() - kOffset * 3;
+ reader_.reset(new UploadFileElementReaderSync(
+ temp_file_path_, kOffset, kLength, base::Time()));
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(kLength, reader_->GetContentLength());
+ EXPECT_EQ(kLength, reader_->BytesRemaining());
+ std::vector<char> buf(kLength);
+ scoped_refptr<IOBuffer> wrapped_buffer = new WrappedIOBuffer(&buf[0]);
+ EXPECT_EQ(static_cast<int>(kLength),
+ reader_->Read(wrapped_buffer.get(), kLength, CompletionCallback()));
+ const std::vector<char> expected(bytes_.begin() + kOffset,
+ bytes_.begin() + kOffset + kLength);
+ EXPECT_EQ(expected, buf);
+}
+
+TEST_F(UploadFileElementReaderSyncTest, FileChanged) {
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(temp_file_path_, &info));
+
+ // Expect one second before the actual modification time to simulate change.
+ const base::Time expected_modification_time =
+ info.last_modified - base::TimeDelta::FromSeconds(1);
+ reader_.reset(new UploadFileElementReaderSync(
+ temp_file_path_, 0, kuint64max, expected_modification_time));
+ EXPECT_EQ(ERR_UPLOAD_FILE_CHANGED, reader_->Init(CompletionCallback()));
+}
+
+TEST_F(UploadFileElementReaderSyncTest, WrongPath) {
+ const base::FilePath wrong_path(FILE_PATH_LITERAL("wrong_path"));
+ reader_.reset(new UploadFileElementReaderSync(
+ wrong_path, 0, kuint64max, base::Time()));
+ ASSERT_EQ(OK, reader_->Init(CompletionCallback()));
+ EXPECT_EQ(0U, reader_->GetContentLength());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+}
+
+} // namespace net
diff --git a/chromium/net/base/upload_progress.h b/chromium/net/base/upload_progress.h
new file mode 100644
index 00000000000..0620009085d
--- /dev/null
+++ b/chromium/net/base/upload_progress.h
@@ -0,0 +1,28 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_UPLOAD_PROGRESS_H_
+#define NET_BASE_UPLOAD_PROGRESS_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+class UploadProgress {
+ public:
+ UploadProgress() : size_(0), position_(0) {}
+ UploadProgress(uint64 position, uint64 size)
+ : size_(size), position_(position) {}
+
+ uint64 size() const { return size_; }
+ uint64 position() const { return position_; }
+
+ private:
+ uint64 size_;
+ uint64 position_;
+};
+
+} // namespace net
+
+#endif // NET_BASE_UPLOAD_PROGRESS_H_
diff --git a/chromium/net/base/url_util.cc b/chromium/net/base/url_util.cc
new file mode 100644
index 00000000000..c18fd179872
--- /dev/null
+++ b/chromium/net/base/url_util.cc
@@ -0,0 +1,100 @@
+// Copyright 2013 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/base/url_util.h"
+
+#include "base/strings/string_piece.h"
+#include "net/base/escape.h"
+#include "url/gurl.h"
+
+namespace net {
+
+GURL AppendQueryParameter(const GURL& url,
+ const std::string& name,
+ const std::string& value) {
+ std::string query(url.query());
+
+ if (!query.empty())
+ query += "&";
+
+ query += (EscapeQueryParamValue(name, true) + "=" +
+ EscapeQueryParamValue(value, true));
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query);
+ return url.ReplaceComponents(replacements);
+}
+
+GURL AppendOrReplaceQueryParameter(const GURL& url,
+ const std::string& name,
+ const std::string& value) {
+ bool replaced = false;
+ std::string param_name = EscapeQueryParamValue(name, true);
+ std::string param_value = EscapeQueryParamValue(value, true);
+
+ const std::string input = url.query();
+ url_parse::Component cursor(0, input.size());
+ std::string output;
+ url_parse::Component key_range, value_range;
+ while (url_parse::ExtractQueryKeyValue(
+ input.data(), &cursor, &key_range, &value_range)) {
+ const base::StringPiece key(
+ input.data() + key_range.begin, key_range.len);
+ const base::StringPiece value(
+ input.data() + value_range.begin, value_range.len);
+ std::string key_value_pair;
+ // Check |replaced| as only the first pair should be replaced.
+ if (!replaced && key == param_name) {
+ replaced = true;
+ key_value_pair = (param_name + "=" + param_value);
+ } else {
+ key_value_pair.assign(input.data(),
+ key_range.begin,
+ value_range.end() - key_range.begin);
+ }
+ if (!output.empty())
+ output += "&";
+
+ output += key_value_pair;
+ }
+ if (!replaced) {
+ if (!output.empty())
+ output += "&";
+
+ output += (param_name + "=" + param_value);
+ }
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(output);
+ return url.ReplaceComponents(replacements);
+}
+
+bool GetValueForKeyInQuery(const GURL& url,
+ const std::string& search_key,
+ std::string* out_value) {
+ if (!url.is_valid())
+ return false;
+
+ url_parse::Component query = url.parsed_for_possibly_invalid_spec().query;
+ url_parse::Component key, value;
+ while (url_parse::ExtractQueryKeyValue(
+ url.spec().c_str(), &query, &key, &value)) {
+ if (key.is_nonempty()) {
+ std::string key_string = url.spec().substr(key.begin, key.len);
+ if (key_string == search_key) {
+ if (value.is_nonempty()) {
+ *out_value = UnescapeURLComponent(
+ url.spec().substr(value.begin, value.len),
+ UnescapeRule::SPACES |
+ UnescapeRule::URL_SPECIAL_CHARS |
+ UnescapeRule::REPLACE_PLUS_WITH_SPACE);
+ } else {
+ *out_value = "";
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/base/url_util.h b/chromium/net/base/url_util.h
new file mode 100644
index 00000000000..5d5e58bc617
--- /dev/null
+++ b/chromium/net/base/url_util.h
@@ -0,0 +1,56 @@
+// Copyright 2013 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_BASE_URL_UTIL_H_
+#define NET_BASE_URL_UTIL_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// Returns a new GURL by appending the given query parameter name and the
+// value. Unsafe characters in the name and the value are escaped like
+// %XX%XX. The original query component is preserved if it's present.
+//
+// Examples:
+//
+// AppendQueryParameter(GURL("http://example.com"), "name", "value").spec()
+// => "http://example.com?name=value"
+// AppendQueryParameter(GURL("http://example.com?x=y"), "name", "value").spec()
+// => "http://example.com?x=y&name=value"
+NET_EXPORT GURL AppendQueryParameter(const GURL& url,
+ const std::string& name,
+ const std::string& value);
+
+// Returns a new GURL by appending or replacing the given query parameter name
+// and the value. If |name| appears more than once, only the first name-value
+// pair is replaced. Unsafe characters in the name and the value are escaped
+// like %XX%XX. The original query component is preserved if it's present.
+//
+// Examples:
+//
+// AppendOrReplaceQueryParameter(
+// GURL("http://example.com"), "name", "new").spec()
+// => "http://example.com?name=value"
+// AppendOrReplaceQueryParameter(
+// GURL("http://example.com?x=y&name=old"), "name", "new").spec()
+// => "http://example.com?x=y&name=new"
+NET_EXPORT GURL AppendOrReplaceQueryParameter(const GURL& url,
+ const std::string& name,
+ const std::string& value);
+
+// Looks for |search_key| in the query portion of |url|. Returns true if the
+// key is found and sets |out_value| to the unescaped value for the key.
+// Returns false if the key is not found.
+NET_EXPORT bool GetValueForKeyInQuery(const GURL& url,
+ const std::string& search_key,
+ std::string* out_value);
+
+} // namespace net
+
+#endif // NET_BASE_URL_UTIL_H_
diff --git a/chromium/net/base/url_util_unittest.cc b/chromium/net/base/url_util_unittest.cc
new file mode 100644
index 00000000000..83ac258650b
--- /dev/null
+++ b/chromium/net/base/url_util_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 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/base/url_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace {
+
+TEST(UrlUtilTest, AppendQueryParameter) {
+ // Appending a name-value pair to a URL without a query component.
+ EXPECT_EQ("http://example.com/path?name=value",
+ AppendQueryParameter(GURL("http://example.com/path"),
+ "name", "value").spec());
+
+ // Appending a name-value pair to a URL with a query component.
+ // The original component should be preserved, and the new pair should be
+ // appended with '&'.
+ EXPECT_EQ("http://example.com/path?existing=one&name=value",
+ AppendQueryParameter(GURL("http://example.com/path?existing=one"),
+ "name", "value").spec());
+
+ // Appending a name-value pair with unsafe characters included. The
+ // unsafe characters should be escaped.
+ EXPECT_EQ("http://example.com/path?existing=one&na+me=v.alue%3D",
+ AppendQueryParameter(GURL("http://example.com/path?existing=one"),
+ "na me", "v.alue=").spec());
+
+}
+
+TEST(UrlUtilTest, AppendOrReplaceQueryParameter) {
+ // Appending a name-value pair to a URL without a query component.
+ EXPECT_EQ("http://example.com/path?name=value",
+ AppendOrReplaceQueryParameter(GURL("http://example.com/path"),
+ "name", "value").spec());
+
+ // Appending a name-value pair to a URL with a query component.
+ // The original component should be preserved, and the new pair should be
+ // appended with '&'.
+ EXPECT_EQ("http://example.com/path?existing=one&name=value",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?existing=one"),
+ "name", "value").spec());
+
+ // Appending a name-value pair with unsafe characters included. The
+ // unsafe characters should be escaped.
+ EXPECT_EQ("http://example.com/path?existing=one&na+me=v.alue%3D",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?existing=one"),
+ "na me", "v.alue=").spec());
+
+ // Replace value of an existing paramater.
+ EXPECT_EQ("http://example.com/path?existing=one&name=new",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?existing=one&name=old"),
+ "name", "new").spec());
+
+ // Replace a name-value pair with unsafe characters included. The
+ // unsafe characters should be escaped.
+ EXPECT_EQ("http://example.com/path?na+me=n.ew%3D&existing=one",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?na+me=old&existing=one"),
+ "na me", "n.ew=").spec());
+
+ // Replace the value of first parameter with this name only.
+ EXPECT_EQ("http://example.com/path?name=new&existing=one&name=old",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?name=old&existing=one&name=old"),
+ "name", "new").spec());
+
+ // Preserve the content of the original params regarless of our failure to
+ // interpret them correctly.
+ EXPECT_EQ("http://example.com/path?bar&name=new&left=&"
+ "=right&=&&name=again",
+ AppendOrReplaceQueryParameter(
+ GURL("http://example.com/path?bar&name=old&left=&"
+ "=right&=&&name=again"),
+ "name", "new").spec());
+}
+
+TEST(UrlUtilTest, GetValueForKeyInQuery) {
+ GURL url("http://example.com/path?name=value&boolParam&"
+ "url=http://test.com/q?n1%3Dv1%26n2");
+ std::string value;
+
+ // False when getting a non-existent query param.
+ EXPECT_FALSE(GetValueForKeyInQuery(url, "non-exist", &value));
+
+ // True when query param exist.
+ EXPECT_TRUE(GetValueForKeyInQuery(url, "name", &value));
+ EXPECT_EQ("value", value);
+
+ EXPECT_TRUE(GetValueForKeyInQuery(url, "boolParam", &value));
+ EXPECT_EQ("", value);
+
+ EXPECT_TRUE(GetValueForKeyInQuery(url, "url", &value));
+ EXPECT_EQ("http://test.com/q?n1=v1&n2", value);
+}
+
+TEST(UrlUtilTest, GetValueForKeyInQueryInvalidURL) {
+ GURL url("http://%01/?test");
+ std::string value;
+
+ // Always false when parsing an invalid URL.
+ EXPECT_FALSE(GetValueForKeyInQuery(url, "test", &value));
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/base/winsock_init.cc b/chromium/net/base/winsock_init.cc
new file mode 100644
index 00000000000..e7601859e78
--- /dev/null
+++ b/chromium/net/base/winsock_init.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 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 <winsock2.h>
+
+#include "net/base/winsock_init.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+namespace {
+
+class WinsockInitSingleton {
+ public:
+ WinsockInitSingleton() {
+ WORD winsock_ver = MAKEWORD(2, 2);
+ WSAData wsa_data;
+ bool did_init = (WSAStartup(winsock_ver, &wsa_data) == 0);
+ if (did_init) {
+ DCHECK(wsa_data.wVersion == winsock_ver);
+
+ // The first time WSAGetLastError is called, the delay load helper will
+ // resolve the address with GetProcAddress and fixup the import. If a
+ // third party application hooks system functions without correctly
+ // restoring the error code, it is possible that the error code will be
+ // overwritten during delay load resolution. The result of the first
+ // call may be incorrect, so make sure the function is bound and future
+ // results will be correct.
+ WSAGetLastError();
+ }
+ }
+
+ ~WinsockInitSingleton() {
+ // Don't call WSACleanup() since the worker pool threads can continue to
+ // call getaddrinfo() after Winsock has shutdown, which can lead to crashes.
+ }
+};
+
+static base::LazyInstance<WinsockInitSingleton> g_winsock_init_singleton =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace net {
+
+void EnsureWinsockInit() {
+ g_winsock_init_singleton.Get();
+}
+
+} // namespace net
diff --git a/chromium/net/base/winsock_init.h b/chromium/net/base/winsock_init.h
new file mode 100644
index 00000000000..c33de9dbfbe
--- /dev/null
+++ b/chromium/net/base/winsock_init.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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.
+
+// Winsock initialization must happen before any Winsock calls are made. The
+// EnsureWinsockInit method will make sure that WSAStartup has been called.
+
+#ifndef NET_BASE_WINSOCK_INIT_H_
+#define NET_BASE_WINSOCK_INIT_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Make sure that Winsock is initialized, calling WSAStartup if needed.
+NET_EXPORT void EnsureWinsockInit();
+
+} // namespace net
+
+#endif // NET_BASE_WINSOCK_INIT_H_
diff --git a/chromium/net/base/winsock_util.cc b/chromium/net/base/winsock_util.cc
new file mode 100644
index 00000000000..5e5c312d385
--- /dev/null
+++ b/chromium/net/base/winsock_util.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 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/base/winsock_util.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+// Prevent the compiler from optimizing away the arguments so they appear
+// nicely on the stack in crash dumps.
+#pragma warning(push)
+#pragma warning (disable: 4748)
+#pragma optimize( "", off )
+
+// Pass the important values as function arguments so that they are available
+// in crash dumps.
+void CheckEventWait(WSAEVENT hEvent, DWORD wait_rv, DWORD expected) {
+ if (wait_rv != expected) {
+ DWORD err = ERROR_SUCCESS;
+ if (wait_rv == WAIT_FAILED)
+ err = GetLastError();
+ CHECK(false); // Crash.
+ }
+}
+
+#pragma optimize( "", on )
+#pragma warning(pop)
+
+net::PlatformSocketFactory* g_socket_factory = NULL;
+
+} // namespace
+
+void AssertEventNotSignaled(WSAEVENT hEvent) {
+ DWORD wait_rv = WaitForSingleObject(hEvent, 0);
+ CheckEventWait(hEvent, wait_rv, WAIT_TIMEOUT);
+}
+
+bool ResetEventIfSignaled(WSAEVENT hEvent) {
+ // TODO(wtc): Remove the CHECKs after enough testing.
+ DWORD wait_rv = WaitForSingleObject(hEvent, 0);
+ if (wait_rv == WAIT_TIMEOUT)
+ return false; // The event object is not signaled.
+ CheckEventWait(hEvent, wait_rv, WAIT_OBJECT_0);
+ BOOL ok = WSAResetEvent(hEvent);
+ CHECK(ok);
+ return true;
+}
+
+void PlatformSocketFactory::SetInstance(PlatformSocketFactory* factory) {
+ g_socket_factory = factory;
+}
+
+SOCKET CreatePlatformSocket(int family, int type, int protocol) {
+ if (g_socket_factory)
+ return g_socket_factory->CreateSocket(family, type, protocol);
+ else
+ return ::WSASocket(family, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED);
+}
+
+} // namespace net
diff --git a/chromium/net/base/winsock_util.h b/chromium/net/base/winsock_util.h
new file mode 100644
index 00000000000..06ac448a817
--- /dev/null
+++ b/chromium/net/base/winsock_util.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 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_BASE_WINSOCK_UTIL_H_
+#define NET_BASE_WINSOCK_UTIL_H_
+
+#include <winsock2.h>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Assert that the (manual-reset) event object is not signaled.
+void AssertEventNotSignaled(WSAEVENT hEvent);
+
+// If the (manual-reset) event object is signaled, resets it and returns true.
+// Otherwise, does nothing and returns false. Called after a Winsock function
+// succeeds synchronously
+//
+// Our testing shows that except in rare cases (when running inside QEMU),
+// the event object is already signaled at this point, so we call this method
+// to avoid a context switch in common cases. This is just a performance
+// optimization. The code still works if this function simply returns false.
+bool ResetEventIfSignaled(WSAEVENT hEvent);
+
+// Interface to create Windows Socket.
+// Usually such factories are used for testing purposes, which is not true in
+// this case. This interface is used to substitute WSASocket to make possible
+// execution of some network code in sandbox.
+class NET_EXPORT PlatformSocketFactory {
+ public:
+ PlatformSocketFactory() {}
+ virtual ~PlatformSocketFactory() {}
+
+ // Creates Windows socket. See WSASocket documentation of parameters.
+ virtual SOCKET CreateSocket(int family, int type, int protocol) = 0;
+
+ // Replace WSASocket with given factory. The factory will be used by
+ // CreatePlatformSocket.
+ static void SetInstance(PlatformSocketFactory* factory);
+};
+
+// Creates Windows Socket. See WSASocket documentation of parameters.
+SOCKET CreatePlatformSocket(int family, int type, int protocol);
+
+} // namespace net
+
+#endif // NET_BASE_WINSOCK_UTIL_H_
diff --git a/chromium/net/base/zap.cc b/chromium/net/base/zap.cc
new file mode 100644
index 00000000000..331d696557f
--- /dev/null
+++ b/chromium/net/base/zap.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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/base/zap.h"
+
+namespace net {
+
+void ZapBuf(void* buf, size_t buf_len) {
+ memset(buf, 0x0, buf_len);
+}
+
+void ZapString(std::string* s) {
+ if (!s->empty())
+ ZapBuf(&(*s)[0], s->length() * sizeof(char));
+}
+
+void ZapString(base::string16* s) {
+ if (!s->empty())
+ ZapBuf(&(*s)[0], s->length() * sizeof(base::char16));
+}
+
+} // net
diff --git a/chromium/net/base/zap.h b/chromium/net/base/zap.h
new file mode 100644
index 00000000000..5be113add3f
--- /dev/null
+++ b/chromium/net/base/zap.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 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_BASE_ZAP_H_
+#define NET_BASE_ZAP_H_
+
+#include <string>
+#include "base/strings/string16.h"
+
+namespace net {
+
+// Zap functions are used to clear sensitive data in RAM to minimize the
+// time that people can access them once they are written to disk.
+
+// Overwrite a buffer with 0's.
+void ZapBuf(void* buf, size_t buf_len);
+
+// Overwrite a string's internal buffer with 0's.
+void ZapString(std::string* s);
+
+// Overwrite a base::string16's internal buffer with 0's.
+void ZapString(base::string16* s);
+
+} // net
+
+#endif // NET_BASE_ZAP_H_
diff --git a/chromium/net/cert/asn1_util.cc b/chromium/net/cert/asn1_util.cc
new file mode 100644
index 00000000000..6dcff52ee65
--- /dev/null
+++ b/chromium/net/cert/asn1_util.cc
@@ -0,0 +1,331 @@
+// Copyright (c) 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.
+
+#include "net/cert/asn1_util.h"
+
+namespace net {
+
+namespace asn1 {
+
+bool ParseElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out,
+ unsigned *out_header_len) {
+ const uint8* data = reinterpret_cast<const uint8*>(in->data());
+
+ // We don't support kAny and kOptional at the same time.
+ if ((tag_value & kAny) && (tag_value & kOptional))
+ return false;
+
+ if (in->empty() && (tag_value & kOptional)) {
+ if (out_header_len)
+ *out_header_len = 0;
+ if (out)
+ *out = base::StringPiece();
+ return true;
+ }
+
+ if (in->size() < 2)
+ return false;
+
+ if (tag_value != kAny &&
+ static_cast<unsigned char>(data[0]) != (tag_value & 0xff)) {
+ if (tag_value & kOptional) {
+ if (out_header_len)
+ *out_header_len = 0;
+ if (out)
+ *out = base::StringPiece();
+ return true;
+ }
+ return false;
+ }
+
+ size_t len = 0;
+ if ((data[1] & 0x80) == 0) {
+ // short form length
+ if (out_header_len)
+ *out_header_len = 2;
+ len = static_cast<size_t>(data[1]) + 2;
+ } else {
+ // long form length
+ const unsigned num_bytes = data[1] & 0x7f;
+ if (num_bytes == 0 || num_bytes > 2)
+ return false;
+ if (in->size() < 2 + num_bytes)
+ return false;
+ len = data[2];
+ if (num_bytes == 2) {
+ if (len == 0) {
+ // the length encoding must be minimal.
+ return false;
+ }
+ len <<= 8;
+ len += data[3];
+ }
+ if (len < 128) {
+ // the length should have been encoded in short form. This distinguishes
+ // DER from BER encoding.
+ return false;
+ }
+ if (out_header_len)
+ *out_header_len = 2 + num_bytes;
+ len += 2 + num_bytes;
+ }
+
+ if (in->size() < len)
+ return false;
+ if (out)
+ *out = base::StringPiece(in->data(), len);
+ in->remove_prefix(len);
+ return true;
+}
+
+bool GetElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out) {
+ unsigned header_len;
+ if (!ParseElement(in, tag_value, out, &header_len))
+ return false;
+ if (out)
+ out->remove_prefix(header_len);
+ return true;
+}
+
+// SeekToSPKI changes |cert| so that it points to a suffix of the
+// TBSCertificate where the suffix begins at the start of the ASN.1
+// SubjectPublicKeyInfo value.
+static bool SeekToSPKI(base::StringPiece* cert) {
+ // From RFC 5280, section 4.1
+ // Certificate ::= SEQUENCE {
+ // tbsCertificate TBSCertificate,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signatureValue BIT STRING }
+
+ // TBSCertificate ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+
+ base::StringPiece certificate;
+ if (!GetElement(cert, kSEQUENCE, &certificate))
+ return false;
+
+ // We don't allow junk after the certificate.
+ if (!cert->empty())
+ return false;
+
+ base::StringPiece tbs_certificate;
+ if (!GetElement(&certificate, kSEQUENCE, &tbs_certificate))
+ return false;
+
+ if (!GetElement(&tbs_certificate,
+ kOptional | kConstructed | kContextSpecific | 0,
+ NULL)) {
+ return false;
+ }
+
+ // serialNumber
+ if (!GetElement(&tbs_certificate, kINTEGER, NULL))
+ return false;
+ // signature
+ if (!GetElement(&tbs_certificate, kSEQUENCE, NULL))
+ return false;
+ // issuer
+ if (!GetElement(&tbs_certificate, kSEQUENCE, NULL))
+ return false;
+ // validity
+ if (!GetElement(&tbs_certificate, kSEQUENCE, NULL))
+ return false;
+ // subject
+ if (!GetElement(&tbs_certificate, kSEQUENCE, NULL))
+ return false;
+ *cert = tbs_certificate;
+ return true;
+}
+
+bool ExtractSPKIFromDERCert(base::StringPiece cert,
+ base::StringPiece* spki_out) {
+ if (!SeekToSPKI(&cert))
+ return false;
+ if (!ParseElement(&cert, kSEQUENCE, spki_out, NULL))
+ return false;
+ return true;
+}
+
+bool ExtractSubjectPublicKeyFromSPKI(base::StringPiece spki,
+ base::StringPiece* spk_out) {
+ // From RFC 5280, Section 4.1
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING }
+ //
+ // AlgorithmIdentifier ::= SEQUENCE {
+ // algorithm OBJECT IDENTIFIER,
+ // parameters ANY DEFINED BY algorithm OPTIONAL }
+
+ // Step into SubjectPublicKeyInfo sequence.
+ base::StringPiece spki_contents;
+ if (!asn1::GetElement(&spki, asn1::kSEQUENCE, &spki_contents))
+ return false;
+
+ // Step over algorithm field (a SEQUENCE).
+ base::StringPiece algorithm;
+ if (!asn1::GetElement(&spki_contents, asn1::kSEQUENCE, &algorithm))
+ return false;
+
+ // Extract the subjectPublicKey field.
+ if (!asn1::GetElement(&spki_contents, asn1::kBITSTRING, spk_out))
+ return false;
+ return true;
+}
+
+
+bool ExtractCRLURLsFromDERCert(base::StringPiece cert,
+ std::vector<base::StringPiece>* urls_out) {
+ urls_out->clear();
+ std::vector<base::StringPiece> tmp_urls_out;
+
+ if (!SeekToSPKI(&cert))
+ return false;
+
+ // From RFC 5280, section 4.1
+ // TBSCertificate ::= SEQUENCE {
+ // ...
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // extensions [3] EXPLICIT Extensions OPTIONAL
+
+ // subjectPublicKeyInfo
+ if (!GetElement(&cert, kSEQUENCE, NULL))
+ return false;
+ // issuerUniqueID
+ if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 1, NULL))
+ return false;
+ // subjectUniqueID
+ if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 2, NULL))
+ return false;
+
+ base::StringPiece extensions_seq;
+ if (!GetElement(&cert, kOptional | kConstructed | kContextSpecific | 3,
+ &extensions_seq)) {
+ return false;
+ }
+
+ if (extensions_seq.empty())
+ return true;
+
+ // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ // Extension ::= SEQUENCE {
+ // extnID OBJECT IDENTIFIER,
+ // critical BOOLEAN DEFAULT FALSE,
+ // extnValue OCTET STRING
+
+ // |extensions_seq| was EXPLICITly tagged, so we still need to remove the
+ // ASN.1 SEQUENCE header.
+ base::StringPiece extensions;
+ if (!GetElement(&extensions_seq, kSEQUENCE, &extensions))
+ return false;
+
+ while (extensions.size() > 0) {
+ base::StringPiece extension;
+ if (!GetElement(&extensions, kSEQUENCE, &extension))
+ return false;
+
+ base::StringPiece oid;
+ if (!GetElement(&extension, kOID, &oid))
+ return false;
+
+ // kCRLDistributionPointsOID is the DER encoding of the OID for the X.509
+ // CRL Distribution Points extension.
+ static const uint8 kCRLDistributionPointsOID[] = {0x55, 0x1d, 0x1f};
+
+ if (oid.size() != sizeof(kCRLDistributionPointsOID) ||
+ memcmp(oid.data(), kCRLDistributionPointsOID, oid.size()) != 0) {
+ continue;
+ }
+
+ // critical
+ GetElement(&extension, kBOOLEAN, NULL);
+
+ // extnValue
+ base::StringPiece extension_value;
+ if (!GetElement(&extension, kOCTETSTRING, &extension_value))
+ return false;
+
+ // RFC 5280, section 4.2.1.13.
+ //
+ // CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
+ //
+ // DistributionPoint ::= SEQUENCE {
+ // distributionPoint [0] DistributionPointName OPTIONAL,
+ // reasons [1] ReasonFlags OPTIONAL,
+ // cRLIssuer [2] GeneralNames OPTIONAL }
+
+ base::StringPiece distribution_points;
+ if (!GetElement(&extension_value, kSEQUENCE, &distribution_points))
+ return false;
+
+ while (distribution_points.size() > 0) {
+ base::StringPiece distrib_point;
+ if (!GetElement(&distribution_points, kSEQUENCE, &distrib_point))
+ return false;
+
+ base::StringPiece name;
+ if (!GetElement(&distrib_point, kContextSpecific | kConstructed | 0,
+ &name)) {
+ // If it doesn't contain a name then we skip it.
+ continue;
+ }
+
+ if (GetElement(&distrib_point, kContextSpecific | 1, NULL)) {
+ // If it contains a subset of reasons then we skip it. We aren't
+ // interested in subsets of CRLs and the RFC states that there MUST be
+ // a CRL that covers all reasons.
+ continue;
+ }
+
+ if (GetElement(&distrib_point,
+ kContextSpecific | kConstructed | 2, NULL)) {
+ // If it contains a alternative issuer, then we skip it.
+ continue;
+ }
+
+ // DistributionPointName ::= CHOICE {
+ // fullName [0] GeneralNames,
+ // nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
+ base::StringPiece general_names;
+ if (!GetElement(&name,
+ kContextSpecific | kConstructed | 0, &general_names)) {
+ continue;
+ }
+
+ // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ // GeneralName ::= CHOICE {
+ // ...
+ // uniformResourceIdentifier [6] IA5String,
+ // ...
+ while (general_names.size() > 0) {
+ base::StringPiece url;
+ if (GetElement(&general_names, kContextSpecific | 6, &url)) {
+ tmp_urls_out.push_back(url);
+ } else {
+ if (!GetElement(&general_names, kAny, NULL))
+ return false;
+ }
+ }
+ }
+ }
+
+ urls_out->swap(tmp_urls_out);
+ return true;
+}
+
+} // namespace asn1
+
+} // namespace net
diff --git a/chromium/net/cert/asn1_util.h b/chromium/net/cert/asn1_util.h
new file mode 100644
index 00000000000..ed379b3f122
--- /dev/null
+++ b/chromium/net/cert/asn1_util.h
@@ -0,0 +1,93 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_ASN1_UTIL_H_
+#define NET_CERT_ASN1_UTIL_H_
+
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+namespace asn1 {
+
+// These are the DER encodings of the tag byte for ASN.1 objects.
+static const unsigned kBOOLEAN = 0x01;
+static const unsigned kINTEGER = 0x02;
+static const unsigned kBITSTRING = 0x03;
+static const unsigned kOCTETSTRING = 0x04;
+static const unsigned kOID = 0x06;
+static const unsigned kSEQUENCE = 0x30;
+
+// These are flags that can be ORed with the above tag numbers.
+static const unsigned kContextSpecific = 0x80;
+static const unsigned kConstructed = 0x20;
+
+// kAny matches any tag value;
+static const unsigned kAny = 0x10000;
+// kOptional denotes an optional element.
+static const unsigned kOptional = 0x20000;
+
+// ParseElement parses a DER encoded ASN1 element from |in|, requiring that
+// it have the given |tag_value|. It returns true on success. The following
+// limitations are imposed:
+// 1) tag numbers > 31 are not permitted.
+// 2) lengths > 65535 are not permitted.
+// On successful return:
+// |in| is advanced over the element
+// |out| contains the element, including the tag and length bytes.
+// |out_header_len| contains the length of the tag and length bytes in |out|.
+//
+// If |tag_value & kOptional| is true then *out_header_len can be zero after a
+// true return value if the element was not found.
+bool ParseElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out,
+ unsigned *out_header_len);
+
+// GetElement performs the same actions as ParseElement, except that the header
+// bytes are not included in the output.
+//
+// If |tag_value & kOptional| is true then this function cannot distinguish
+// between a missing optional element and an empty one.
+bool GetElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out);
+
+// ExtractSPKIFromDERCert parses the DER encoded certificate in |cert| and
+// extracts the bytes of the SubjectPublicKeyInfo. On successful return,
+// |spki_out| is set to contain the SPKI, pointing into |cert|.
+NET_EXPORT_PRIVATE bool ExtractSPKIFromDERCert(base::StringPiece cert,
+ base::StringPiece* spki_out);
+
+// ExtractSubjectPublicKeyFromSPKI parses the DER encoded SubjectPublicKeyInfo
+// in |spki| and extracts the bytes of the SubjectPublicKey. On successful
+// return, |spk_out| is set to contain the public key, pointing into |spki|.
+NET_EXPORT_PRIVATE bool ExtractSubjectPublicKeyFromSPKI(
+ base::StringPiece spki,
+ base::StringPiece* spk_out);
+
+// ExtractCRLURLsFromDERCert parses the DER encoded certificate in |cert| and
+// extracts the URL of each CRL. On successful return, the elements of
+// |urls_out| point into |cert|.
+//
+// CRLs that only cover a subset of the reasons are omitted as the spec
+// requires that at least one CRL be included that covers all reasons.
+//
+// CRLs that use an alternative issuer are also omitted.
+//
+// The nested set of GeneralNames is flattened into a single list because
+// having several CRLs with one location is equivalent to having one CRL with
+// several locations as far as a CRL filter is concerned.
+NET_EXPORT_PRIVATE bool ExtractCRLURLsFromDERCert(
+ base::StringPiece cert,
+ std::vector<base::StringPiece>* urls_out);
+
+} // namespace asn1
+
+} // namespace net
+
+#endif // NET_CERT_ASN1_UTIL_H_
diff --git a/chromium/net/cert/cert_database.cc b/chromium/net/cert/cert_database.cc
new file mode 100644
index 00000000000..db54172d070
--- /dev/null
+++ b/chromium/net/cert/cert_database.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include "base/memory/singleton.h"
+#include "base/observer_list_threadsafe.h"
+
+namespace net {
+
+// static
+CertDatabase* CertDatabase::GetInstance() {
+ return Singleton<CertDatabase>::get();
+}
+
+void CertDatabase::AddObserver(Observer* observer) {
+ observer_list_->AddObserver(observer);
+}
+
+void CertDatabase::RemoveObserver(Observer* observer) {
+ observer_list_->RemoveObserver(observer);
+}
+
+void CertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) {
+ observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert));
+}
+
+void CertDatabase::NotifyObserversOfCertRemoved(const X509Certificate* cert) {
+ observer_list_->Notify(&Observer::OnCertRemoved, make_scoped_refptr(cert));
+}
+
+void CertDatabase::NotifyObserversOfCertTrustChanged(
+ const X509Certificate* cert) {
+ observer_list_->Notify(
+ &Observer::OnCertTrustChanged, make_scoped_refptr(cert));
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database.h b/chromium/net/cert/cert_database.h
new file mode 100644
index 00000000000..c4ead812f2f
--- /dev/null
+++ b/chromium/net/cert/cert_database.h
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_DATABASE_H_
+#define NET_CERT_CERT_DATABASE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_certificate.h"
+
+template <typename T> struct DefaultSingletonTraits;
+template <class ObserverType> class ObserverListThreadSafe;
+
+namespace net {
+
+// This class provides cross-platform functions to verify and add user
+// certificates, and to observe changes to the underlying certificate stores.
+
+// TODO(gauravsh): This class could be augmented with methods
+// for all operations that manipulate the underlying system
+// certificate store.
+
+class NET_EXPORT CertDatabase {
+ public:
+ // A CertDatabase::Observer will be notified on certificate database changes.
+ // The change could be either a new user certificate is added or trust on
+ // a certificate is changed. Observers can register themselves
+ // via CertDatabase::AddObserver, and can un-register with
+ // CertDatabase::RemoveObserver.
+ class NET_EXPORT Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Will be called when a new certificate is added.
+ virtual void OnCertAdded(const X509Certificate* cert) {}
+
+ // Will be called when a certificate is removed.
+ virtual void OnCertRemoved(const X509Certificate* cert) {}
+
+ // Will be called when a certificate's trust is changed.
+ virtual void OnCertTrustChanged(const X509Certificate* cert) {}
+
+ protected:
+ Observer() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ // Returns the CertDatabase singleton.
+ static CertDatabase* GetInstance();
+
+ // Check whether this is a valid user cert that we have the private key for.
+ // Returns OK or a network error code such as ERR_CERT_CONTAINS_ERRORS.
+ int CheckUserCert(X509Certificate* cert);
+
+ // Store user (client) certificate. Assumes CheckUserCert has already passed.
+ // Returns OK, or ERR_ADD_USER_CERT_FAILED if there was a problem saving to
+ // the platform cert database, or possibly other network error codes.
+ int AddUserCert(X509Certificate* cert);
+
+ // Registers |observer| to receive notifications of certificate changes. The
+ // thread on which this is called is the thread on which |observer| will be
+ // called back with notifications.
+ void AddObserver(Observer* observer);
+
+ // Unregisters |observer| from receiving notifications. This must be called
+ // on the same thread on which AddObserver() was called.
+ void RemoveObserver(Observer* observer);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Configures the current message loop to observe and forward events from
+ // Keychain services. The MessageLoop must have an associated CFRunLoop,
+ // which means that this must be called from a MessageLoop of TYPE_UI.
+ void SetMessageLoopForKeychainEvents();
+#endif
+
+ private:
+ friend struct DefaultSingletonTraits<CertDatabase>;
+
+ CertDatabase();
+ ~CertDatabase();
+
+ // Broadcasts notifications to all registered observers.
+ void NotifyObserversOfCertAdded(const X509Certificate* cert);
+ void NotifyObserversOfCertRemoved(const X509Certificate* cert);
+ void NotifyObserversOfCertTrustChanged(const X509Certificate* cert);
+
+ const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_;
+
+#if defined(USE_NSS) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ class Notifier;
+ friend class Notifier;
+ scoped_ptr<Notifier> notifier_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(CertDatabase);
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_DATABASE_H_
diff --git a/chromium/net/cert/cert_database_android.cc b/chromium/net/cert/cert_database_android.cc
new file mode 100644
index 00000000000..9755805026b
--- /dev/null
+++ b/chromium/net/cert/cert_database_android.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include "base/logging.h"
+#include "base/observer_list_threadsafe.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+}
+
+CertDatabase::~CertDatabase() {}
+
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ // NOTE: This method shall never be called on Android.
+ //
+ // On other platforms, it is only used by the SSLAddCertHandler class
+ // to handle veritication and installation of downloaded certificates.
+ //
+ // On Android, the certificate data is passed directly to the system's
+ // CertInstaller activity, which handles verification, naming,
+ // installation and UI (for success/failure).
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ // This method is only used by the content SSLAddCertHandler which is
+ // never used on Android.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database_ios.cc b/chromium/net/cert/cert_database_ios.cc
new file mode 100644
index 00000000000..f96f22ae2be
--- /dev/null
+++ b/chromium/net/cert/cert_database_ios.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include "base/logging.h"
+#include "base/observer_list_threadsafe.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {}
+
+CertDatabase::~CertDatabase() {}
+
+int CertDatabase::CheckUserCert(X509Certificate* cert_obj) {
+ // iOS doesn't handle user certificates.
+ NOTREACHED();
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert_obj) {
+ // iOS doesn't handle user certificates.
+ NOTREACHED();
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database_mac.cc b/chromium/net/cert/cert_database_mac.cc
new file mode 100644
index 00000000000..76701963fd5
--- /dev/null
+++ b/chromium/net/cert/cert_database_mac.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include <Security/Security.h>
+
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/process/process_handle.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "crypto/mac_security_services_lock.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+// Helper that observes events from the Keychain and forwards them to the
+// given CertDatabase.
+class CertDatabase::Notifier {
+ public:
+ // Creates a new Notifier that will forward Keychain events to |cert_db|.
+ // |message_loop| must refer to a thread with an associated CFRunLoop - a
+ // TYPE_UI thread. Events will be dispatched from this message loop.
+ Notifier(CertDatabase* cert_db, base::MessageLoop* message_loop)
+ : cert_db_(cert_db),
+ registered_(false),
+ called_shutdown_(false) {
+ // Ensure an associated CFRunLoop.
+ DCHECK(message_loop->IsType(base::MessageLoop::TYPE_UI));
+ task_runner_ = message_loop->message_loop_proxy();
+ task_runner_->PostTask(FROM_HERE,
+ base::Bind(&Notifier::Init,
+ base::Unretained(this)));
+ }
+
+ // Should be called from the |task_runner_|'s thread. Use Shutdown()
+ // to shutdown on arbitrary threads.
+ ~Notifier() {
+ DCHECK(called_shutdown_);
+ // Only unregister from the same thread where registration was performed.
+ if (registered_ && task_runner_->RunsTasksOnCurrentThread())
+ SecKeychainRemoveCallback(&Notifier::KeychainCallback);
+ }
+
+ void Shutdown() {
+ called_shutdown_ = true;
+ if (!task_runner_->DeleteSoon(FROM_HERE, this)) {
+ // If the task runner is no longer running, it's safe to just delete
+ // the object, since no further events will or can be delivered by
+ // Keychain Services.
+ delete this;
+ }
+ }
+
+ private:
+ void Init() {
+ SecKeychainEventMask event_mask =
+ kSecKeychainListChangedMask | kSecTrustSettingsChangedEventMask;
+ OSStatus status = SecKeychainAddCallback(&Notifier::KeychainCallback,
+ event_mask, this);
+ if (status == noErr)
+ registered_ = true;
+ }
+
+ // SecKeychainCallback function that receives notifications from securityd
+ // and forwards them to the |cert_db_|.
+ static OSStatus KeychainCallback(SecKeychainEvent keychain_event,
+ SecKeychainCallbackInfo* info,
+ void* context);
+
+ CertDatabase* const cert_db_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+ bool registered_;
+ bool called_shutdown_;
+};
+
+// static
+OSStatus CertDatabase::Notifier::KeychainCallback(
+ SecKeychainEvent keychain_event,
+ SecKeychainCallbackInfo* info,
+ void* context) {
+ Notifier* that = reinterpret_cast<Notifier*>(context);
+
+ if (info->version > SEC_KEYCHAIN_SETTINGS_VERS1) {
+ NOTREACHED();
+ return errSecWrongSecVersion;
+ }
+
+ if (info->pid == base::GetCurrentProcId()) {
+ // Ignore events generated by the current process, as the assumption is
+ // that they have already been handled. This may miss events that
+ // originated as a result of spawning native dialogs that allow the user
+ // to modify Keychain settings. However, err on the side of missing
+ // events rather than sending too many events.
+ return errSecSuccess;
+ }
+
+ switch (keychain_event) {
+ case kSecKeychainListChangedEvent:
+ case kSecTrustSettingsChangedEvent:
+ that->cert_db_->NotifyObserversOfCertTrustChanged(NULL);
+ break;
+ }
+
+ return errSecSuccess;
+}
+
+void CertDatabase::SetMessageLoopForKeychainEvents() {
+ // Shutdown will take care to delete the notifier on the right thread.
+ if (notifier_.get())
+ notifier_.release()->Shutdown();
+
+ notifier_.reset(new Notifier(this, base::MessageLoopForUI::current()));
+}
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+}
+
+CertDatabase::~CertDatabase() {
+ // Shutdown will take care to delete the notifier on the right thread.
+ if (notifier_.get())
+ notifier_.release()->Shutdown();
+}
+
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ if (!cert)
+ return ERR_CERT_INVALID;
+ if (cert->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // Verify the Keychain already has the corresponding private key:
+ SecIdentityRef identity = NULL;
+ OSStatus err = SecIdentityCreateWithCertificate(NULL, cert->os_cert_handle(),
+ &identity);
+ if (err == errSecItemNotFound)
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ if (err != noErr || !identity) {
+ // TODO(snej): Map the error code more intelligently.
+ return ERR_CERT_INVALID;
+ }
+
+ CFRelease(identity);
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ OSStatus err;
+ {
+ base::AutoLock locked(crypto::GetMacSecurityServicesLock());
+ err = SecCertificateAddToKeychain(cert->os_cert_handle(), NULL);
+ }
+ switch (err) {
+ case noErr:
+ CertDatabase::NotifyObserversOfCertAdded(cert);
+ // Fall through.
+ case errSecDuplicateItem:
+ return OK;
+ default:
+ OSSTATUS_LOG(ERROR, err) << "CertDatabase failed to add cert to keychain";
+ // TODO(snej): Map the error code more intelligently.
+ return ERR_ADD_USER_CERT_FAILED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database_nss.cc b/chromium/net/cert/cert_database_nss.cc
new file mode 100644
index 00000000000..5fa272134a2
--- /dev/null
+++ b/chromium/net/cert/cert_database_nss.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include <cert.h>
+#include <pk11pub.h>
+#include <secmod.h>
+
+#include "base/logging.h"
+#include "base/observer_list_threadsafe.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/net_errors.h"
+#include "net/cert/nss_cert_database.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util_nss.h"
+
+namespace net {
+
+// Helper that observes events from the NSSCertDatabase and forwards them to
+// the given CertDatabase.
+class CertDatabase::Notifier : public NSSCertDatabase::Observer {
+ public:
+ explicit Notifier(CertDatabase* cert_db) : cert_db_(cert_db) {
+ NSSCertDatabase::GetInstance()->AddObserver(this);
+ }
+
+ virtual ~Notifier() {
+ NSSCertDatabase::GetInstance()->RemoveObserver(this);
+ }
+
+ // NSSCertDatabase::Observer implementation:
+ virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE {
+ cert_db_->NotifyObserversOfCertAdded(cert);
+ }
+
+ virtual void OnCertRemoved(const X509Certificate* cert) OVERRIDE {
+ cert_db_->NotifyObserversOfCertRemoved(cert);
+ }
+
+ virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE {
+ cert_db_->NotifyObserversOfCertTrustChanged(cert);
+ }
+
+ private:
+ CertDatabase* cert_db_;
+
+ DISALLOW_COPY_AND_ASSIGN(Notifier);
+};
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+ // Observe NSSCertDatabase events and forward them to observers of
+ // CertDatabase. This also makes sure that NSS has been initialized.
+ notifier_.reset(new Notifier(this));
+}
+
+CertDatabase::~CertDatabase() {}
+
+int CertDatabase::CheckUserCert(X509Certificate* cert_obj) {
+ if (!cert_obj)
+ return ERR_CERT_INVALID;
+ if (cert_obj->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // Check if the private key corresponding to the certificate exist
+ // We shouldn't accept any random client certificate sent by a CA.
+
+ // Note: The NSS source documentation wrongly suggests that this
+ // also imports the certificate if the private key exists. This
+ // doesn't seem to be the case.
+
+ CERTCertificate* cert = cert_obj->os_cert_handle();
+ PK11SlotInfo* slot = PK11_KeyForCertExists(cert, NULL, NULL);
+ if (!slot)
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ PK11_FreeSlot(slot);
+
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert_obj) {
+ CERTCertificate* cert = cert_obj->os_cert_handle();
+ CK_OBJECT_HANDLE key;
+ crypto::ScopedPK11Slot slot(PK11_KeyForCertExists(cert, &key, NULL));
+ if (!slot.get())
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ std::string nickname = x509_util::GetUniqueNicknameForSlot(
+ cert_obj->GetDefaultNickname(USER_CERT),
+ &cert->derSubject,
+ slot.get());
+
+ SECStatus rv;
+ {
+ crypto::AutoNSSWriteLock lock;
+ rv = PK11_ImportCert(slot.get(), cert, key, nickname.c_str(), PR_FALSE);
+ }
+
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "Couldn't import user certificate. " << PORT_GetError();
+ return ERR_ADD_USER_CERT_FAILED;
+ }
+
+ NotifyObserversOfCertAdded(cert_obj);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database_openssl.cc b/chromium/net/cert/cert_database_openssl.cc
new file mode 100644
index 00000000000..23b64cc256f
--- /dev/null
+++ b/chromium/net/cert/cert_database_openssl.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_database.h"
+
+#include <openssl/x509.h>
+
+#include "base/logging.h"
+#include "base/observer_list_threadsafe.h"
+#include "crypto/openssl_util.h"
+#include "net/base/crypto_module.h"
+#include "net/base/net_errors.h"
+#include "net/base/openssl_private_key_store.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+}
+
+CertDatabase::~CertDatabase() {}
+
+// This method is used to check a client certificate before trying to
+// install it on the system, which will happen later by calling
+// AddUserCert() below.
+//
+// On the Linux/OpenSSL build, there is simply no system keystore, but
+// OpenSSLPrivateKeyStore() implements a small in-memory store for
+// (public/private) key pairs generated through keygen.
+//
+// Try to check for a private key in the in-memory store to check
+// for the case when the browser is trying to install a server-generated
+// certificate from a <keygen> exchange.
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ if (!cert)
+ return ERR_CERT_INVALID;
+ if (cert->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // X509_PUBKEY_get() transfers ownership, not X509_get_X509_PUBKEY()
+ crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> public_key(
+ X509_PUBKEY_get(X509_get_X509_PUBKEY(cert->os_cert_handle())));
+
+ if (!OpenSSLPrivateKeyStore::HasPrivateKey(public_key.get()))
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ // There is no certificate store on the Linux/OpenSSL build.
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_database_win.cc b/chromium/net/cert/cert_database_win.cc
new file mode 100644
index 00000000000..9bf378cc350
--- /dev/null
+++ b/chromium/net/cert/cert_database_win.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2010 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/cert/cert_database.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32.lib")
+
+#include "base/observer_list_threadsafe.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+CertDatabase::CertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+}
+
+CertDatabase::~CertDatabase() {}
+
+int CertDatabase::CheckUserCert(X509Certificate* cert) {
+ if (!cert)
+ return ERR_CERT_INVALID;
+ if (cert->HasExpired())
+ return ERR_CERT_DATE_INVALID;
+
+ // TODO(rsleevi): Should CRYPT_FIND_SILENT_KEYSET_FLAG be specified? A UI
+ // may be shown here / this call may block.
+ if (!CryptFindCertificateKeyProvInfo(cert->os_cert_handle(), 0, NULL))
+ return ERR_NO_PRIVATE_KEY_FOR_CERT;
+
+ return OK;
+}
+
+int CertDatabase::AddUserCert(X509Certificate* cert) {
+ // TODO(rsleevi): Would it be more appropriate to have the CertDatabase take
+ // construction parameters (Keychain filepath on Mac OS X, PKCS #11 slot on
+ // NSS, and Store Type / Path) here? For now, certs will be stashed into the
+ // user's personal store, which will not automatically mark them as trusted,
+ // but will allow them to be used for client auth.
+ HCERTSTORE cert_db = CertOpenSystemStore(NULL, L"MY");
+ if (!cert_db)
+ return ERR_ADD_USER_CERT_FAILED;
+
+ BOOL added = CertAddCertificateContextToStore(cert_db,
+ cert->os_cert_handle(),
+ CERT_STORE_ADD_USE_EXISTING,
+ NULL);
+
+ CertCloseStore(cert_db, 0);
+
+ if (!added)
+ return ERR_ADD_USER_CERT_FAILED;
+
+ NotifyObserversOfCertAdded(cert);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_status_flags.cc b/chromium/net/cert/cert_status_flags.cc
new file mode 100644
index 00000000000..8cb736c5485
--- /dev/null
+++ b/chromium/net/cert/cert_status_flags.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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/cert/cert_status_flags.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+bool IsCertStatusMinorError(CertStatus cert_status) {
+ static const CertStatus kMinorErrors =
+ CERT_STATUS_UNABLE_TO_CHECK_REVOCATION |
+ CERT_STATUS_NO_REVOCATION_MECHANISM;
+ cert_status &= CERT_STATUS_ALL_ERRORS;
+ return cert_status != 0 && (cert_status & ~kMinorErrors) == 0;
+}
+
+CertStatus MapNetErrorToCertStatus(int error) {
+ switch (error) {
+ case ERR_CERT_COMMON_NAME_INVALID:
+ return CERT_STATUS_COMMON_NAME_INVALID;
+ case ERR_CERT_DATE_INVALID:
+ return CERT_STATUS_DATE_INVALID;
+ case ERR_CERT_AUTHORITY_INVALID:
+ return CERT_STATUS_AUTHORITY_INVALID;
+ case ERR_CERT_NO_REVOCATION_MECHANISM:
+ return CERT_STATUS_NO_REVOCATION_MECHANISM;
+ case ERR_CERT_UNABLE_TO_CHECK_REVOCATION:
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+ case ERR_CERT_REVOKED:
+ return CERT_STATUS_REVOKED;
+ // We added the ERR_CERT_CONTAINS_ERRORS error code when we were using
+ // WinInet, but we never figured out how it differs from ERR_CERT_INVALID.
+ // We should not use ERR_CERT_CONTAINS_ERRORS in new code.
+ case ERR_CERT_CONTAINS_ERRORS:
+ NOTREACHED();
+ // Falls through.
+ case ERR_CERT_INVALID:
+ return CERT_STATUS_INVALID;
+ case ERR_CERT_WEAK_SIGNATURE_ALGORITHM:
+ return CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
+ case ERR_CERT_WEAK_KEY:
+ return CERT_STATUS_WEAK_KEY;
+ default:
+ return 0;
+ }
+}
+
+int MapCertStatusToNetError(CertStatus cert_status) {
+ // A certificate may have multiple errors. We report the most
+ // serious error.
+
+ // Unrecoverable errors
+ if (cert_status & CERT_STATUS_REVOKED)
+ return ERR_CERT_REVOKED;
+ if (cert_status & CERT_STATUS_INVALID)
+ return ERR_CERT_INVALID;
+
+ // Recoverable errors
+ if (cert_status & CERT_STATUS_AUTHORITY_INVALID)
+ return ERR_CERT_AUTHORITY_INVALID;
+ if (cert_status & CERT_STATUS_COMMON_NAME_INVALID)
+ return ERR_CERT_COMMON_NAME_INVALID;
+ if (cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM)
+ return ERR_CERT_WEAK_SIGNATURE_ALGORITHM;
+ if (cert_status & CERT_STATUS_WEAK_KEY)
+ return ERR_CERT_WEAK_KEY;
+ if (cert_status & CERT_STATUS_DATE_INVALID)
+ return ERR_CERT_DATE_INVALID;
+
+ // Unknown status. Give it the benefit of the doubt.
+ if (cert_status & CERT_STATUS_UNABLE_TO_CHECK_REVOCATION)
+ return ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ if (cert_status & CERT_STATUS_NO_REVOCATION_MECHANISM)
+ return ERR_CERT_NO_REVOCATION_MECHANISM;
+
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_status_flags.h b/chromium/net/cert/cert_status_flags.h
new file mode 100644
index 00000000000..8431032f679
--- /dev/null
+++ b/chromium/net/cert/cert_status_flags.h
@@ -0,0 +1,61 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_STATUS_FLAGS_H_
+#define NET_CERT_CERT_STATUS_FLAGS_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Bitmask of status flags of a certificate, representing any errors, as well as
+// other non-error status information such as whether the certificate is EV.
+typedef uint32 CertStatus;
+
+// The possible status bits for CertStatus.
+// NOTE: Because these names have appeared in bug reports, we preserve them as
+// MACRO_STYLE for continuity, instead of renaming them to kConstantStyle as
+// befits most static consts.
+// Bits 0 to 15 are for errors.
+static const CertStatus CERT_STATUS_ALL_ERRORS = 0xFFFF;
+static const CertStatus CERT_STATUS_COMMON_NAME_INVALID = 1 << 0;
+static const CertStatus CERT_STATUS_DATE_INVALID = 1 << 1;
+static const CertStatus CERT_STATUS_AUTHORITY_INVALID = 1 << 2;
+// 1 << 3 is reserved for ERR_CERT_CONTAINS_ERRORS (not useful with WinHTTP).
+static const CertStatus CERT_STATUS_NO_REVOCATION_MECHANISM = 1 << 4;
+static const CertStatus CERT_STATUS_UNABLE_TO_CHECK_REVOCATION = 1 << 5;
+static const CertStatus CERT_STATUS_REVOKED = 1 << 6;
+static const CertStatus CERT_STATUS_INVALID = 1 << 7;
+static const CertStatus CERT_STATUS_WEAK_SIGNATURE_ALGORITHM = 1 << 8;
+// 1 << 9 was used for CERT_STATUS_NOT_IN_DNS
+static const CertStatus CERT_STATUS_NON_UNIQUE_NAME = 1 << 10;
+static const CertStatus CERT_STATUS_WEAK_KEY = 1 << 11;
+
+// Bits 16 to 31 are for non-error statuses.
+static const CertStatus CERT_STATUS_IS_EV = 1 << 16;
+static const CertStatus CERT_STATUS_REV_CHECKING_ENABLED = 1 << 17;
+// bit 18 was CERT_STATUS_IS_DNSSEC.
+
+// Returns true if the specified cert status has an error set.
+static inline bool IsCertStatusError(CertStatus status) {
+ return (CERT_STATUS_ALL_ERRORS & status) != 0;
+}
+
+// IsCertStatusMinorError returns true iff |cert_status| indicates a condition
+// that should typically be ignored by automated requests. (i.e. a revocation
+// check failure.)
+NET_EXPORT bool IsCertStatusMinorError(CertStatus cert_status);
+
+// Maps a network error code to the equivalent certificate status flag. If
+// the error code is not a certificate error, it is mapped to 0.
+NET_EXPORT CertStatus MapNetErrorToCertStatus(int error);
+
+// Maps the most serious certificate error in the certificate status flags
+// to the equivalent network error code.
+NET_EXPORT int MapCertStatusToNetError(CertStatus cert_status);
+
+} // namespace net
+
+#endif // NET_CERT_CERT_STATUS_FLAGS_H_
diff --git a/chromium/net/cert/cert_trust_anchor_provider.h b/chromium/net/cert/cert_trust_anchor_provider.h
new file mode 100644
index 00000000000..1712b2d0211
--- /dev/null
+++ b/chromium/net/cert/cert_trust_anchor_provider.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 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_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_
+#define NET_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+
+// Interface to retrieve the current list of additional trust anchors.
+// This is used by CertVerifier to get a list of anchors to trust in addition to
+// the anchors known to the CertVerifier.
+class NET_EXPORT CertTrustAnchorProvider {
+ public:
+ virtual ~CertTrustAnchorProvider() {}
+
+ // Returns a list of certificates to be used as trust anchors during
+ // certificate validation, in addition to (eg: the union of) any pre-existing
+ // or pre-configured trust anchors.
+ virtual const CertificateList& GetAdditionalTrustAnchors() = 0;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_TRUST_ANCHOR_PROVIDER_H_
diff --git a/chromium/net/cert/cert_type.h b/chromium/net/cert/cert_type.h
new file mode 100644
index 00000000000..cb212274b12
--- /dev/null
+++ b/chromium/net/cert/cert_type.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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_CERT_CERT_TYPE_H_
+#define NET_CERT_CERT_TYPE_H_
+
+namespace net {
+
+// Constants to classify the type of a certificate.
+// This is only used in the context of CertDatabase, but is defined outside to
+// avoid an awkwardly long type name.
+// The type is a combination of intrinsic properties, such as the presense of an
+// Certificate Authority Basic Constraint, and assigned trust values. For
+// example, a cert with no basic constraints or trust would be classified as
+// UNKNOWN_CERT. If that cert is then trusted with SetCertTrust(cert,
+// SERVER_CERT, TRUSTED_SSL), it would become a SERVER_CERT.
+enum CertType {
+ UNKNOWN_CERT,
+ CA_CERT,
+ USER_CERT,
+ SERVER_CERT,
+ NUM_CERT_TYPES
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_TYPE_H_
diff --git a/chromium/net/cert/cert_verifier.cc b/chromium/net/cert/cert_verifier.cc
new file mode 100644
index 00000000000..e4acec4f288
--- /dev/null
+++ b/chromium/net/cert/cert_verifier.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verifier.h"
+
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/multi_threaded_cert_verifier.h"
+
+namespace net {
+
+CertVerifier* CertVerifier::CreateDefault() {
+ return new MultiThreadedCertVerifier(CertVerifyProc::CreateDefault());
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verifier.h b/chromium/net/cert/cert_verifier.h
new file mode 100644
index 00000000000..743c8350b27
--- /dev/null
+++ b/chromium/net/cert/cert_verifier.h
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFIER_H_
+#define NET_CERT_CERT_VERIFIER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class BoundNetLog;
+class CertVerifyResult;
+class CRLSet;
+class X509Certificate;
+
+// CertVerifier represents a service for verifying certificates.
+//
+// CertVerifiers can handle multiple requests at a time. A simpler alternative
+// for consumers that only have 1 outstanding request at a time is to create a
+// SingleRequestCertVerifier wrapper around CertVerifier (which will
+// automatically cancel the single request when it goes out of scope).
+class NET_EXPORT CertVerifier {
+ public:
+ // Opaque pointer type used to cancel outstanding requests.
+ typedef void* RequestHandle;
+
+ enum VerifyFlags {
+ // If set, enables online revocation checking via CRLs and OCSP for the
+ // certificate chain.
+ VERIFY_REV_CHECKING_ENABLED = 1 << 0,
+
+ // If set, and the certificate being verified may be an EV certificate,
+ // attempt to verify the certificate according to the EV processing
+ // guidelines. In order to successfully verify a certificate as EV,
+ // either an online or offline revocation check must be successfully
+ // completed. To ensure it's possible to complete a revocation check,
+ // callers should also specify either VERIFY_REV_CHECKING_ENABLED or
+ // VERIFY_REV_CHECKING_ENABLED_EV_ONLY (to enable online checks), and
+ // VERIFY_CERT_IO_ENABLED (to enable network fetches for online checks).
+ VERIFY_EV_CERT = 1 << 1,
+
+ // If set, permits NSS to use the network when verifying certificates,
+ // such as to fetch missing intermediates or to check OCSP or CRLs.
+ // TODO(rsleevi): http://crbug.com/143300 - Define this flag for all
+ // verification engines with well-defined semantics, rather than being
+ // NSS only.
+ VERIFY_CERT_IO_ENABLED = 1 << 2,
+
+ // If set, enables online revocation checking via CRLs or OCSP when the
+ // chain is not covered by a fresh CRLSet, but only for certificates which
+ // may be EV, and only when VERIFY_EV_CERT is also set.
+ VERIFY_REV_CHECKING_ENABLED_EV_ONLY = 1 << 3,
+
+ // If set, this is equivalent to VERIFY_REV_CHECKING_ENABLED, in that it
+ // enables online revocation checking via CRLs or OCSP, but only
+ // for certificates issued by non-public trust anchors. Failure to check
+ // revocation is treated as a hard failure.
+ // Note: If VERIFY_CERT_IO_ENABLE is not also supplied, certificates
+ // that chain to local trust anchors will likely fail - for example, due to
+ // lacking fresh cached revocation issue (Windows) or because OCSP stapling
+ // can only provide information for the leaf, and not for any
+ // intermediates.
+ VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS = 1 << 4,
+ };
+
+ // When the verifier is destroyed, all certificate verification requests are
+ // canceled, and their completion callbacks will not be called.
+ virtual ~CertVerifier() {}
+
+ // Verifies the given certificate against the given hostname as an SSL server.
+ // Returns OK if successful or an error code upon failure.
+ //
+ // The |*verify_result| structure, including the |verify_result->cert_status|
+ // bitmask, is always filled out regardless of the return value. If the
+ // certificate has multiple errors, the corresponding status flags are set in
+ // |verify_result->cert_status|, and the error code for the most serious
+ // error is returned.
+ //
+ // |flags| is bitwise OR'd of VerifyFlags.
+ // If VERIFY_REV_CHECKING_ENABLED is set in |flags|, certificate revocation
+ // checking is performed.
+ //
+ // If VERIFY_EV_CERT is set in |flags| too, EV certificate verification is
+ // performed. If |flags| is VERIFY_EV_CERT (that is,
+ // VERIFY_REV_CHECKING_ENABLED is not set), EV certificate verification will
+ // not be performed.
+ //
+ // |crl_set| points to an optional CRLSet structure which can be used to
+ // avoid revocation checks over the network.
+ //
+ // |callback| must not be null. ERR_IO_PENDING is returned if the operation
+ // could not be completed synchronously, in which case the result code will
+ // be passed to the callback when available.
+ //
+ // |*out_req| will be filled with a handle to the async request.
+ // This handle is not valid after the request has completed.
+ //
+ // TODO(rsleevi): Move CRLSet* out of the CertVerifier signature.
+ virtual int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) = 0;
+
+ // Cancels the specified request. |req| is the handle returned by Verify().
+ // After a request is canceled, its completion callback will not be called.
+ virtual void CancelRequest(RequestHandle req) = 0;
+
+ // Creates a CertVerifier implementation that verifies certificates using
+ // the preferred underlying cryptographic libraries.
+ static CertVerifier* CreateDefault();
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFIER_H_
diff --git a/chromium/net/cert/cert_verify_proc.cc b/chromium/net/cert/cert_verify_proc.cc
new file mode 100644
index 00000000000..ec1ef682b47
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc.h"
+
+#include "base/metrics/histogram.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/x509_certificate.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include "net/cert/cert_verify_proc_nss.h"
+#elif defined(USE_OPENSSL) && !defined(OS_ANDROID)
+#include "net/cert/cert_verify_proc_openssl.h"
+#elif defined(OS_ANDROID)
+#include "net/cert/cert_verify_proc_android.h"
+#elif defined(OS_MACOSX)
+#include "net/cert/cert_verify_proc_mac.h"
+#elif defined(OS_WIN)
+#include "net/cert/cert_verify_proc_win.h"
+#else
+#error Implement certificate verification.
+#endif
+
+
+namespace net {
+
+namespace {
+
+// Constants used to build histogram names
+const char kLeafCert[] = "Leaf";
+const char kIntermediateCert[] = "Intermediate";
+const char kRootCert[] = "Root";
+// Matches the order of X509Certificate::PublicKeyType
+const char* const kCertTypeStrings[] = {
+ "Unknown",
+ "RSA",
+ "DSA",
+ "ECDSA",
+ "DH",
+ "ECDH"
+};
+// Histogram buckets for RSA/DSA/DH key sizes.
+const int kRsaDsaKeySizes[] = {512, 768, 1024, 1536, 2048, 3072, 4096, 8192,
+ 16384};
+// Histogram buckets for ECDSA/ECDH key sizes. The list is based upon the FIPS
+// 186-4 approved curves.
+const int kEccKeySizes[] = {163, 192, 224, 233, 256, 283, 384, 409, 521, 571};
+
+const char* CertTypeToString(int cert_type) {
+ if (cert_type < 0 ||
+ static_cast<size_t>(cert_type) >= arraysize(kCertTypeStrings)) {
+ return "Unsupported";
+ }
+ return kCertTypeStrings[cert_type];
+}
+
+void RecordPublicKeyHistogram(const char* chain_position,
+ bool baseline_keysize_applies,
+ size_t size_bits,
+ X509Certificate::PublicKeyType cert_type) {
+ std::string histogram_name =
+ base::StringPrintf("CertificateType2.%s.%s.%s",
+ baseline_keysize_applies ? "BR" : "NonBR",
+ chain_position,
+ CertTypeToString(cert_type));
+ // Do not use UMA_HISTOGRAM_... macros here, as it caches the Histogram
+ // instance and thus only works if |histogram_name| is constant.
+ base::HistogramBase* counter = NULL;
+
+ // Histogram buckets are contingent upon the underlying algorithm being used.
+ if (cert_type == X509Certificate::kPublicKeyTypeECDH ||
+ cert_type == X509Certificate::kPublicKeyTypeECDSA) {
+ // Typical key sizes match SECP/FIPS 186-3 recommendations for prime and
+ // binary curves - which range from 163 bits to 571 bits.
+ counter = base::CustomHistogram::FactoryGet(
+ histogram_name,
+ base::CustomHistogram::ArrayToCustomRanges(kEccKeySizes,
+ arraysize(kEccKeySizes)),
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ } else {
+ // Key sizes < 1024 bits should cause errors, while key sizes > 16K are not
+ // uniformly supported by the underlying cryptographic libraries.
+ counter = base::CustomHistogram::FactoryGet(
+ histogram_name,
+ base::CustomHistogram::ArrayToCustomRanges(kRsaDsaKeySizes,
+ arraysize(kRsaDsaKeySizes)),
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ }
+ counter->Add(size_bits);
+}
+
+// Returns true if |type| is |kPublicKeyTypeRSA| or |kPublicKeyTypeDSA|, and
+// if |size_bits| is < 1024. Note that this means there may be false
+// negatives: keys for other algorithms and which are weak will pass this
+// test.
+bool IsWeakKey(X509Certificate::PublicKeyType type, size_t size_bits) {
+ switch (type) {
+ case X509Certificate::kPublicKeyTypeRSA:
+ case X509Certificate::kPublicKeyTypeDSA:
+ return size_bits < 1024;
+ default:
+ return false;
+ }
+}
+
+// Returns true if |cert| contains a known-weak key. Additionally, histograms
+// the observed keys for future tightening of the definition of what
+// constitutes a weak key.
+bool ExaminePublicKeys(const scoped_refptr<X509Certificate>& cert,
+ bool should_histogram) {
+ // The effective date of the CA/Browser Forum's Baseline Requirements -
+ // 2012-07-01 00:00:00 UTC.
+ const base::Time kBaselineEffectiveDate =
+ base::Time::FromInternalValue(GG_INT64_C(12985574400000000));
+ // The effective date of the key size requirements from Appendix A, v1.1.5
+ // 2014-01-01 00:00:00 UTC.
+ const base::Time kBaselineKeysizeEffectiveDate =
+ base::Time::FromInternalValue(GG_INT64_C(13033008000000000));
+
+ size_t size_bits = 0;
+ X509Certificate::PublicKeyType type = X509Certificate::kPublicKeyTypeUnknown;
+ bool weak_key = false;
+ bool baseline_keysize_applies =
+ cert->valid_start() >= kBaselineEffectiveDate &&
+ cert->valid_expiry() >= kBaselineKeysizeEffectiveDate;
+
+ X509Certificate::GetPublicKeyInfo(cert->os_cert_handle(), &size_bits, &type);
+ if (should_histogram) {
+ RecordPublicKeyHistogram(kLeafCert, baseline_keysize_applies, size_bits,
+ type);
+ }
+ if (IsWeakKey(type, size_bits))
+ weak_key = true;
+
+ const X509Certificate::OSCertHandles& intermediates =
+ cert->GetIntermediateCertificates();
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ X509Certificate::GetPublicKeyInfo(intermediates[i], &size_bits, &type);
+ if (should_histogram) {
+ RecordPublicKeyHistogram(
+ (i < intermediates.size() - 1) ? kIntermediateCert : kRootCert,
+ baseline_keysize_applies,
+ size_bits,
+ type);
+ }
+ if (!weak_key && IsWeakKey(type, size_bits))
+ weak_key = true;
+ }
+
+ return weak_key;
+}
+
+} // namespace
+
+// static
+CertVerifyProc* CertVerifyProc::CreateDefault() {
+#if defined(USE_NSS) || defined(OS_IOS)
+ return new CertVerifyProcNSS();
+#elif defined(USE_OPENSSL) && !defined(OS_ANDROID)
+ return new CertVerifyProcOpenSSL();
+#elif defined(OS_ANDROID)
+ return new CertVerifyProcAndroid();
+#elif defined(OS_MACOSX)
+ return new CertVerifyProcMac();
+#elif defined(OS_WIN)
+ return new CertVerifyProcWin();
+#else
+ return NULL;
+#endif
+}
+
+CertVerifyProc::CertVerifyProc() {}
+
+CertVerifyProc::~CertVerifyProc() {}
+
+int CertVerifyProc::Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ verify_result->Reset();
+ verify_result->verified_cert = cert;
+
+ if (IsBlacklisted(cert)) {
+ verify_result->cert_status |= CERT_STATUS_REVOKED;
+ return ERR_CERT_REVOKED;
+ }
+
+ // We do online revocation checking for EV certificates that aren't covered
+ // by a fresh CRLSet.
+ // TODO(rsleevi): http://crbug.com/142974 - Allow preferences to fully
+ // disable revocation checking.
+ if (flags & CertVerifier::VERIFY_EV_CERT)
+ flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY;
+
+ int rv = VerifyInternal(cert, hostname, flags, crl_set,
+ additional_trust_anchors, verify_result);
+
+ // This check is done after VerifyInternal so that VerifyInternal can fill
+ // in the list of public key hashes.
+ if (IsPublicKeyBlacklisted(verify_result->public_key_hashes)) {
+ verify_result->cert_status |= CERT_STATUS_REVOKED;
+ rv = MapCertStatusToNetError(verify_result->cert_status);
+ }
+
+ // Check for weak keys in the entire verified chain.
+ bool weak_key = ExaminePublicKeys(verify_result->verified_cert,
+ verify_result->is_issued_by_known_root);
+
+ if (weak_key) {
+ verify_result->cert_status |= CERT_STATUS_WEAK_KEY;
+ // Avoid replacing a more serious error, such as an OS/library failure,
+ // by ensuring that if verification failed, it failed with a certificate
+ // error.
+ if (rv == OK || IsCertificateError(rv))
+ rv = MapCertStatusToNetError(verify_result->cert_status);
+ }
+
+ // Treat certificates signed using broken signature algorithms as invalid.
+ if (verify_result->has_md2 || verify_result->has_md4) {
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ rv = MapCertStatusToNetError(verify_result->cert_status);
+ }
+
+ // Flag certificates using weak signature algorithms.
+ if (verify_result->has_md5) {
+ verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
+ // Avoid replacing a more serious error, such as an OS/library failure,
+ // by ensuring that if verification failed, it failed with a certificate
+ // error.
+ if (rv == OK || IsCertificateError(rv))
+ rv = MapCertStatusToNetError(verify_result->cert_status);
+ }
+
+ // Flag certificates from publicly-trusted CAs that are issued to intranet
+ // hosts. While the CA/Browser Forum Baseline Requirements (v1.1) permit
+ // these to be issued until 1 November 2015, they represent a real risk for
+ // the deployment of gTLDs and are being phased out ahead of the hard
+ // deadline.
+ // TODO(rsleevi): http://crbug.com/119212 - Also match internal IP address
+ // ranges.
+ if (verify_result->is_issued_by_known_root && IsHostnameNonUnique(hostname)) {
+ verify_result->cert_status |= CERT_STATUS_NON_UNIQUE_NAME;
+ }
+
+ return rv;
+}
+
+// static
+bool CertVerifyProc::IsBlacklisted(X509Certificate* cert) {
+ static const unsigned kComodoSerialBytes = 16;
+ static const uint8 kComodoSerials[][kComodoSerialBytes] = {
+ // Not a real certificate. For testing only.
+ {0x07,0x7a,0x59,0xbc,0xd5,0x34,0x59,0x60,0x1c,0xa6,0x90,0x72,0x67,0xa6,0xdd,0x1c},
+
+ // The next nine certificates all expire on Fri Mar 14 23:59:59 2014.
+ // Some serial numbers actually have a leading 0x00 byte required to
+ // encode a positive integer in DER if the most significant bit is 0.
+ // We omit the leading 0x00 bytes to make all serial numbers 16 bytes.
+
+ // Subject: CN=mail.google.com
+ // subjectAltName dNSName: mail.google.com, www.mail.google.com
+ {0x04,0x7e,0xcb,0xe9,0xfc,0xa5,0x5f,0x7b,0xd0,0x9e,0xae,0x36,0xe1,0x0c,0xae,0x1e},
+ // Subject: CN=global trustee
+ // subjectAltName dNSName: global trustee
+ // Note: not a CA certificate.
+ {0xd8,0xf3,0x5f,0x4e,0xb7,0x87,0x2b,0x2d,0xab,0x06,0x92,0xe3,0x15,0x38,0x2f,0xb0},
+ // Subject: CN=login.live.com
+ // subjectAltName dNSName: login.live.com, www.login.live.com
+ {0xb0,0xb7,0x13,0x3e,0xd0,0x96,0xf9,0xb5,0x6f,0xae,0x91,0xc8,0x74,0xbd,0x3a,0xc0},
+ // Subject: CN=addons.mozilla.org
+ // subjectAltName dNSName: addons.mozilla.org, www.addons.mozilla.org
+ {0x92,0x39,0xd5,0x34,0x8f,0x40,0xd1,0x69,0x5a,0x74,0x54,0x70,0xe1,0xf2,0x3f,0x43},
+ // Subject: CN=login.skype.com
+ // subjectAltName dNSName: login.skype.com, www.login.skype.com
+ {0xe9,0x02,0x8b,0x95,0x78,0xe4,0x15,0xdc,0x1a,0x71,0x0a,0x2b,0x88,0x15,0x44,0x47},
+ // Subject: CN=login.yahoo.com
+ // subjectAltName dNSName: login.yahoo.com, www.login.yahoo.com
+ {0xd7,0x55,0x8f,0xda,0xf5,0xf1,0x10,0x5b,0xb2,0x13,0x28,0x2b,0x70,0x77,0x29,0xa3},
+ // Subject: CN=www.google.com
+ // subjectAltName dNSName: www.google.com, google.com
+ {0xf5,0xc8,0x6a,0xf3,0x61,0x62,0xf1,0x3a,0x64,0xf5,0x4f,0x6d,0xc9,0x58,0x7c,0x06},
+ // Subject: CN=login.yahoo.com
+ // subjectAltName dNSName: login.yahoo.com
+ {0x39,0x2a,0x43,0x4f,0x0e,0x07,0xdf,0x1f,0x8a,0xa3,0x05,0xde,0x34,0xe0,0xc2,0x29},
+ // Subject: CN=login.yahoo.com
+ // subjectAltName dNSName: login.yahoo.com
+ {0x3e,0x75,0xce,0xd4,0x6b,0x69,0x30,0x21,0x21,0x88,0x30,0xae,0x86,0xa8,0x2a,0x71},
+ };
+
+ const std::string& serial_number = cert->serial_number();
+ if (!serial_number.empty() && (serial_number[0] & 0x80) != 0) {
+ // This is a negative serial number, which isn't technically allowed but
+ // which probably happens. In order to avoid confusing a negative serial
+ // number with a positive one once the leading zeros have been removed, we
+ // disregard it.
+ return false;
+ }
+
+ base::StringPiece serial(serial_number);
+ // Remove leading zeros.
+ while (serial.size() > 1 && serial[0] == 0)
+ serial.remove_prefix(1);
+
+ if (serial.size() == kComodoSerialBytes) {
+ for (unsigned i = 0; i < arraysize(kComodoSerials); i++) {
+ if (memcmp(kComodoSerials[i], serial.data(), kComodoSerialBytes) == 0) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SSLCertBlacklisted", i,
+ arraysize(kComodoSerials) + 1);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// static
+// NOTE: This implementation assumes and enforces that the hashes are SHA1.
+bool CertVerifyProc::IsPublicKeyBlacklisted(
+ const HashValueVector& public_key_hashes) {
+ static const unsigned kNumHashes = 10;
+ static const uint8 kHashes[kNumHashes][base::kSHA1Length] = {
+ // Subject: CN=DigiNotar Root CA
+ // Issuer: CN=Entrust.net x2 and self-signed
+ {0x41, 0x0f, 0x36, 0x36, 0x32, 0x58, 0xf3, 0x0b, 0x34, 0x7d,
+ 0x12, 0xce, 0x48, 0x63, 0xe4, 0x33, 0x43, 0x78, 0x06, 0xa8},
+ // Subject: CN=DigiNotar Cyber CA
+ // Issuer: CN=GTE CyberTrust Global Root
+ {0xc4, 0xf9, 0x66, 0x37, 0x16, 0xcd, 0x5e, 0x71, 0xd6, 0x95,
+ 0x0b, 0x5f, 0x33, 0xce, 0x04, 0x1c, 0x95, 0xb4, 0x35, 0xd1},
+ // Subject: CN=DigiNotar Services 1024 CA
+ // Issuer: CN=Entrust.net
+ {0xe2, 0x3b, 0x8d, 0x10, 0x5f, 0x87, 0x71, 0x0a, 0x68, 0xd9,
+ 0x24, 0x80, 0x50, 0xeb, 0xef, 0xc6, 0x27, 0xbe, 0x4c, 0xa6},
+ // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
+ // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
+ {0x7b, 0x2e, 0x16, 0xbc, 0x39, 0xbc, 0xd7, 0x2b, 0x45, 0x6e,
+ 0x9f, 0x05, 0x5d, 0x1d, 0xe6, 0x15, 0xb7, 0x49, 0x45, 0xdb},
+ // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
+ // Issuer: CN=Staat der Nederlanden Overheid CA
+ {0xe8, 0xf9, 0x12, 0x00, 0xc6, 0x5c, 0xee, 0x16, 0xe0, 0x39,
+ 0xb9, 0xf8, 0x83, 0x84, 0x16, 0x61, 0x63, 0x5f, 0x81, 0xc5},
+ // Subject: O=Digicert Sdn. Bhd.
+ // Issuer: CN=GTE CyberTrust Global Root
+ // Expires: Jul 17 15:16:54 2012 GMT
+ {0x01, 0x29, 0xbc, 0xd5, 0xb4, 0x48, 0xae, 0x8d, 0x24, 0x96,
+ 0xd1, 0xc3, 0xe1, 0x97, 0x23, 0x91, 0x90, 0x88, 0xe1, 0x52},
+ // Subject: O=Digicert Sdn. Bhd.
+ // Issuer: CN=Entrust.net Certification Authority (2048)
+ // Expires: Jul 16 17:53:37 2015 GMT
+ {0xd3, 0x3c, 0x5b, 0x41, 0xe4, 0x5c, 0xc4, 0xb3, 0xbe, 0x9a,
+ 0xd6, 0x95, 0x2c, 0x4e, 0xcc, 0x25, 0x28, 0x03, 0x29, 0x81},
+ // Issuer: CN=Trustwave Organization Issuing CA, Level 2
+ // Covers two certificates, the latter of which expires Apr 15 21:09:30
+ // 2021 GMT.
+ {0xe1, 0x2d, 0x89, 0xf5, 0x6d, 0x22, 0x76, 0xf8, 0x30, 0xe6,
+ 0xce, 0xaf, 0xa6, 0x6c, 0x72, 0x5c, 0x0b, 0x41, 0xa9, 0x32},
+ // Cyberoam CA certificate. Private key leaked, but this certificate would
+ // only have been installed by Cyberoam customers. The certificate expires
+ // in 2036, but we can probably remove in a couple of years (2014).
+ {0xd9, 0xf5, 0xc6, 0xce, 0x57, 0xff, 0xaa, 0x39, 0xcc, 0x7e,
+ 0xd1, 0x72, 0xbd, 0x53, 0xe0, 0xd3, 0x07, 0x83, 0x4b, 0xd1},
+ // Win32/Sirefef.gen!C generates fake certifciates with this public key.
+ {0xa4, 0xf5, 0x6e, 0x9e, 0x1d, 0x9a, 0x3b, 0x7b, 0x1a, 0xc3,
+ 0x31, 0xcf, 0x64, 0xfc, 0x76, 0x2c, 0xd0, 0x51, 0xfb, 0xa4},
+ };
+
+ for (unsigned i = 0; i < kNumHashes; i++) {
+ for (HashValueVector::const_iterator j = public_key_hashes.begin();
+ j != public_key_hashes.end(); ++j) {
+ if (j->tag == HASH_VALUE_SHA1 &&
+ memcmp(j->data(), kHashes[i], base::kSHA1Length) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc.h b/chromium/net/cert/cert_verify_proc.h
new file mode 100644
index 00000000000..518adbc122a
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc.h
@@ -0,0 +1,98 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_H_
+#define NET_CERT_CERT_VERIFY_PROC_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace net {
+
+class CertVerifyResult;
+class CRLSet;
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+
+// Class to perform certificate path building and verification for various
+// certificate uses. All methods of this class must be thread-safe, as they
+// may be called from various non-joinable worker threads.
+class NET_EXPORT CertVerifyProc
+ : public base::RefCountedThreadSafe<CertVerifyProc> {
+ public:
+ // Creates and returns the default CertVerifyProc.
+ static CertVerifyProc* CreateDefault();
+
+ // Verifies the certificate against the given hostname as an SSL server
+ // certificate. Returns OK if successful or an error code upon failure.
+ //
+ // The |*verify_result| structure, including the |verify_result->cert_status|
+ // bitmask, is always filled out regardless of the return value. If the
+ // certificate has multiple errors, the corresponding status flags are set in
+ // |verify_result->cert_status|, and the error code for the most serious
+ // error is returned.
+ //
+ // |flags| is bitwise OR'd of VerifyFlags:
+ //
+ // If VERIFY_REV_CHECKING_ENABLED is set in |flags|, online certificate
+ // revocation checking is performed (i.e. OCSP and downloading CRLs). CRLSet
+ // based revocation checking is always enabled, regardless of this flag, if
+ // |crl_set| is given.
+ //
+ // If VERIFY_EV_CERT is set in |flags| too, EV certificate verification is
+ // performed.
+ //
+ // |crl_set| points to an optional CRLSet structure which can be used to
+ // avoid revocation checks over the network.
+ //
+ // |additional_trust_anchors| lists certificates that can be trusted when
+ // building a certificate chain, in addition to the anchors known to the
+ // implementation.
+ int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result);
+
+ // Returns true if the implementation supports passing additional trust
+ // anchors to the Verify() call. The |additional_trust_anchors| parameter
+ // passed to Verify() is ignored when this returns false.
+ virtual bool SupportsAdditionalTrustAnchors() const = 0;
+
+ protected:
+ CertVerifyProc();
+ virtual ~CertVerifyProc();
+
+ private:
+ friend class base::RefCountedThreadSafe<CertVerifyProc>;
+ FRIEND_TEST_ALL_PREFIXES(CertVerifyProcTest, DigiNotarCerts);
+
+ // Performs the actual verification using the desired underlying
+ // cryptographic library.
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) = 0;
+
+ // Returns true if |cert| is explicitly blacklisted.
+ static bool IsBlacklisted(X509Certificate* cert);
+
+ // IsPublicKeyBlacklisted returns true iff one of |public_key_hashes| (which
+ // are hashes of SubjectPublicKeyInfo structures) is explicitly blocked.
+ static bool IsPublicKeyBlacklisted(const HashValueVector& public_key_hashes);
+
+ DISALLOW_COPY_AND_ASSIGN(CertVerifyProc);
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_H_
diff --git a/chromium/net/cert/cert_verify_proc_android.cc b/chromium/net/cert/cert_verify_proc_android.cc
new file mode 100644
index 00000000000..9a8acf76fa9
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_android.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc_android.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/android/cert_verify_result_android.h"
+#include "net/android/network_library.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+// Returns true if the certificate verification call was successful (regardless
+// of its result), i.e. if |verify_result| was set. Otherwise returns false.
+bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes,
+ CertVerifyResult* verify_result) {
+ // TODO(joth): Fetch the authentication type from SSL rather than hardcode.
+ android::CertVerifyResultAndroid android_result =
+ android::VerifyX509CertChain(cert_bytes, "RSA");
+ switch (android_result) {
+ case android::VERIFY_FAILED:
+ return false;
+ case android::VERIFY_OK:
+ break;
+ case android::VERIFY_NO_TRUSTED_ROOT:
+ verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+ break;
+ case android::VERIFY_EXPIRED:
+ case android::VERIFY_NOT_YET_VALID:
+ verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
+ break;
+ case android::VERIFY_UNABLE_TO_PARSE:
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ break;
+ case android::VERIFY_INCORRECT_KEY_USAGE:
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ break;
+ default:
+ NOTREACHED();
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ break;
+ }
+ return true;
+}
+
+bool GetChainDEREncodedBytes(X509Certificate* cert,
+ std::vector<std::string>* chain_bytes) {
+ X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle();
+ X509Certificate::OSCertHandles cert_handles =
+ cert->GetIntermediateCertificates();
+
+ // Make sure the peer's own cert is the first in the chain, if it's not
+ // already there.
+ if (cert_handles.empty() || cert_handles[0] != cert_handle)
+ cert_handles.insert(cert_handles.begin(), cert_handle);
+
+ chain_bytes->reserve(cert_handles.size());
+ for (X509Certificate::OSCertHandles::const_iterator it =
+ cert_handles.begin(); it != cert_handles.end(); ++it) {
+ std::string cert_bytes;
+ if(!X509Certificate::GetDEREncoded(*it, &cert_bytes))
+ return false;
+ chain_bytes->push_back(cert_bytes);
+ }
+ return true;
+}
+
+} // namespace
+
+CertVerifyProcAndroid::CertVerifyProcAndroid() {}
+
+CertVerifyProcAndroid::~CertVerifyProcAndroid() {}
+
+bool CertVerifyProcAndroid::SupportsAdditionalTrustAnchors() const {
+ return false;
+}
+
+int CertVerifyProcAndroid::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ if (!cert->VerifyNameMatch(hostname))
+ verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+
+ std::vector<std::string> cert_bytes;
+ if (!GetChainDEREncodedBytes(cert, &cert_bytes))
+ return ERR_CERT_INVALID;
+ if (!VerifyFromAndroidTrustManager(cert_bytes, verify_result)) {
+ NOTREACHED();
+ return ERR_FAILED;
+ }
+ if (IsCertStatusError(verify_result->cert_status))
+ return MapCertStatusToNetError(verify_result->cert_status);
+
+ // TODO(ppi): Implement missing functionality: yielding the constructed trust
+ // chain, public key hashes of its certificates and |is_issued_by_known_root|
+ // flag. All of the above require specific support from the platform, missing
+ // in the Java APIs. See also: http://crbug.com/116838
+
+ // Until the required support is available in the platform, we don't know if
+ // the trust root at the end of the chain was standard or user-added, so we
+ // mark all correctly verified certificates as issued by a known root.
+ verify_result->is_issued_by_known_root = true;
+
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_android.h b/chromium/net/cert/cert_verify_proc_android.h
new file mode 100644
index 00000000000..ca8746b4a96
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_android.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_ANDROID_H_
+#define NET_CERT_CERT_VERIFY_PROC_ANDROID_H_
+
+#include "net/cert/cert_verify_proc.h"
+
+namespace net {
+
+// Performs certificate verification on Android by calling the platform
+// TrustManager through JNI.
+class CertVerifyProcAndroid : public CertVerifyProc {
+ public:
+ CertVerifyProcAndroid();
+
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE;
+
+ protected:
+ virtual ~CertVerifyProcAndroid();
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_ANDROID_H_
diff --git a/chromium/net/cert/cert_verify_proc_mac.cc b/chromium/net/cert/cert_verify_proc_mac.cc
new file mode 100644
index 00000000000..4efdaacffed
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_mac.cc
@@ -0,0 +1,714 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc_mac.h"
+
+#include <CommonCrypto/CommonDigest.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/sha1.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "crypto/mac_security_services_lock.h"
+#include "crypto/nss_util.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_certificate_known_roots_mac.h"
+#include "net/cert/x509_util_mac.h"
+
+// From 10.7.2 libsecurity_keychain-55035/lib/SecTrustPriv.h, for use with
+// SecTrustCopyExtendedResult.
+#ifndef kSecEVOrganizationName
+#define kSecEVOrganizationName CFSTR("Organization")
+#endif
+
+using base::ScopedCFTypeRef;
+
+namespace net {
+
+namespace {
+
+typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef,
+ CFDictionaryRef*);
+
+int NetErrorFromOSStatus(OSStatus status) {
+ switch (status) {
+ case noErr:
+ return OK;
+ case errSecNotAvailable:
+ case errSecNoCertificateModule:
+ case errSecNoPolicyModule:
+ return ERR_NOT_IMPLEMENTED;
+ case errSecAuthFailed:
+ return ERR_ACCESS_DENIED;
+ default: {
+ OSSTATUS_LOG(ERROR, status) << "Unknown error mapped to ERR_FAILED";
+ return ERR_FAILED;
+ }
+ }
+}
+
+CertStatus CertStatusFromOSStatus(OSStatus status) {
+ switch (status) {
+ case noErr:
+ return 0;
+
+ case CSSMERR_TP_INVALID_ANCHOR_CERT:
+ case CSSMERR_TP_NOT_TRUSTED:
+ case CSSMERR_TP_INVALID_CERT_AUTHORITY:
+ return CERT_STATUS_AUTHORITY_INVALID;
+
+ case CSSMERR_TP_CERT_EXPIRED:
+ case CSSMERR_TP_CERT_NOT_VALID_YET:
+ // "Expired" and "not yet valid" collapse into a single status.
+ return CERT_STATUS_DATE_INVALID;
+
+ case CSSMERR_TP_CERT_REVOKED:
+ case CSSMERR_TP_CERT_SUSPENDED:
+ return CERT_STATUS_REVOKED;
+
+ case CSSMERR_APPLETP_HOSTNAME_MISMATCH:
+ return CERT_STATUS_COMMON_NAME_INVALID;
+
+ case CSSMERR_APPLETP_CRL_NOT_FOUND:
+ case CSSMERR_APPLETP_OCSP_UNAVAILABLE:
+ case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK:
+ return CERT_STATUS_NO_REVOCATION_MECHANISM;
+
+ case CSSMERR_APPLETP_CRL_EXPIRED:
+ case CSSMERR_APPLETP_CRL_NOT_VALID_YET:
+ case CSSMERR_APPLETP_CRL_SERVER_DOWN:
+ case CSSMERR_APPLETP_CRL_NOT_TRUSTED:
+ case CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT:
+ case CSSMERR_APPLETP_CRL_POLICY_FAIL:
+ case CSSMERR_APPLETP_OCSP_BAD_RESPONSE:
+ case CSSMERR_APPLETP_OCSP_BAD_REQUEST:
+ case CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED:
+ case CSSMERR_APPLETP_NETWORK_FAILURE:
+ case CSSMERR_APPLETP_OCSP_NOT_TRUSTED:
+ case CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT:
+ case CSSMERR_APPLETP_OCSP_SIG_ERROR:
+ case CSSMERR_APPLETP_OCSP_NO_SIGNER:
+ case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ:
+ case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR:
+ case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER:
+ case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED:
+ case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED:
+ case CSSMERR_APPLETP_OCSP_NONCE_MISMATCH:
+ // We asked for a revocation check, but didn't get it.
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+
+ case CSSMERR_APPLETP_SSL_BAD_EXT_KEY_USE:
+ // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE?
+ return CERT_STATUS_INVALID;
+
+ case CSSMERR_APPLETP_CRL_BAD_URI:
+ case CSSMERR_APPLETP_IDP_FAIL:
+ return CERT_STATUS_INVALID;
+
+ case CSSMERR_CSP_UNSUPPORTED_KEY_SIZE:
+ // Mapping UNSUPPORTED_KEY_SIZE to CERT_STATUS_WEAK_KEY is not strictly
+ // accurate, as the error may have been returned due to a key size
+ // that exceeded the maximum supported. However, within
+ // CertVerifyProcMac::VerifyInternal(), this code should only be
+ // encountered as a certificate status code, and only when the key size
+ // is smaller than the minimum required (1024 bits).
+ return CERT_STATUS_WEAK_KEY;
+
+ default: {
+ // Failure was due to something Chromium doesn't define a
+ // specific status for (such as basic constraints violation, or
+ // unknown critical extension)
+ OSSTATUS_LOG(WARNING, status)
+ << "Unknown error mapped to CERT_STATUS_INVALID";
+ return CERT_STATUS_INVALID;
+ }
+ }
+}
+
+// Creates a series of SecPolicyRefs to be added to a SecTrustRef used to
+// validate a certificate for an SSL server. |hostname| contains the name of
+// the SSL server that the certificate should be verified against. |flags| is
+// a bitwise-OR of VerifyFlags that can further alter how trust is validated,
+// such as how revocation is checked. If successful, returns noErr, and
+// stores the resultant array of SecPolicyRefs in |policies|.
+OSStatus CreateTrustPolicies(const std::string& hostname,
+ int flags,
+ ScopedCFTypeRef<CFArrayRef>* policies) {
+ ScopedCFTypeRef<CFMutableArrayRef> local_policies(
+ CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
+ if (!local_policies)
+ return memFullErr;
+
+ SecPolicyRef ssl_policy;
+ OSStatus status = x509_util::CreateSSLServerPolicy(hostname, &ssl_policy);
+ if (status)
+ return status;
+ CFArrayAppendValue(local_policies, ssl_policy);
+ CFRelease(ssl_policy);
+
+ // Explicitly add revocation policies, in order to override system
+ // revocation checking policies and instead respect the application-level
+ // revocation preference.
+ status = x509_util::CreateRevocationPolicies(
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED),
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY),
+ local_policies);
+ if (status)
+ return status;
+
+ policies->reset(local_policies.release());
+ return noErr;
+}
+
+// Saves some information about the certificate chain |cert_chain| in
+// |*verify_result|. The caller MUST initialize |*verify_result| before
+// calling this function.
+void GetCertChainInfo(CFArrayRef cert_chain,
+ CSSM_TP_APPLE_EVIDENCE_INFO* chain_info,
+ CertVerifyResult* verify_result) {
+ SecCertificateRef verified_cert = NULL;
+ std::vector<SecCertificateRef> verified_chain;
+ for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) {
+ SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
+ if (i == 0) {
+ verified_cert = chain_cert;
+ } else {
+ verified_chain.push_back(chain_cert);
+ }
+
+ if ((chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) ||
+ (chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_ROOT)) {
+ // The current certificate is either in the user's trusted store or is
+ // a root (self-signed) certificate. Ignore the signature algorithm for
+ // these certificates, as it is meaningless for security. We allow
+ // self-signed certificates (i == 0 & IS_ROOT), since we accept that
+ // any security assertions by such a cert are inherently meaningless.
+ continue;
+ }
+
+ x509_util::CSSMCachedCertificate cached_cert;
+ OSStatus status = cached_cert.Init(chain_cert);
+ if (status)
+ continue;
+ x509_util::CSSMFieldValue signature_field;
+ status = cached_cert.GetField(&CSSMOID_X509V1SignatureAlgorithm,
+ &signature_field);
+ if (status || !signature_field.field())
+ continue;
+ // Match the behaviour of OS X system tools and defensively check that
+ // sizes are appropriate. This would indicate a critical failure of the
+ // OS X certificate library, but based on history, it is best to play it
+ // safe.
+ const CSSM_X509_ALGORITHM_IDENTIFIER* sig_algorithm =
+ signature_field.GetAs<CSSM_X509_ALGORITHM_IDENTIFIER>();
+ if (!sig_algorithm)
+ continue;
+
+ const CSSM_OID* alg_oid = &sig_algorithm->algorithm;
+ if (CSSMOIDEqual(alg_oid, &CSSMOID_MD2WithRSA)) {
+ verify_result->has_md2 = true;
+ } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD4WithRSA)) {
+ verify_result->has_md4 = true;
+ } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD5WithRSA)) {
+ verify_result->has_md5 = true;
+ }
+ }
+ if (!verified_cert)
+ return;
+
+ verify_result->verified_cert =
+ X509Certificate::CreateFromHandle(verified_cert, verified_chain);
+}
+
+void AppendPublicKeyHashes(CFArrayRef chain,
+ HashValueVector* hashes) {
+ const CFIndex n = CFArrayGetCount(chain);
+ for (CFIndex i = 0; i < n; i++) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, i)));
+
+ CSSM_DATA cert_data;
+ OSStatus err = SecCertificateGetData(cert, &cert_data);
+ DCHECK_EQ(err, noErr);
+ base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data),
+ cert_data.Length);
+ base::StringPiece spki_bytes;
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes))
+ continue;
+
+ HashValue sha1(HASH_VALUE_SHA1);
+ CC_SHA1(spki_bytes.data(), spki_bytes.size(), sha1.data());
+ hashes->push_back(sha1);
+
+ HashValue sha256(HASH_VALUE_SHA256);
+ CC_SHA256(spki_bytes.data(), spki_bytes.size(), sha256.data());
+ hashes->push_back(sha256);
+ }
+}
+
+bool CheckRevocationWithCRLSet(CFArrayRef chain, CRLSet* crl_set) {
+ if (CFArrayGetCount(chain) == 0)
+ return true;
+
+ // We iterate from the root certificate down to the leaf, keeping track of
+ // the issuer's SPKI at each step.
+ std::string issuer_spki_hash;
+ for (CFIndex i = CFArrayGetCount(chain) - 1; i >= 0; i--) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, i)));
+
+ CSSM_DATA cert_data;
+ OSStatus err = SecCertificateGetData(cert, &cert_data);
+ if (err != noErr) {
+ NOTREACHED();
+ continue;
+ }
+ base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data),
+ cert_data.Length);
+ base::StringPiece spki;
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) {
+ NOTREACHED();
+ continue;
+ }
+
+ const std::string spki_hash = crypto::SHA256HashString(spki);
+ x509_util::CSSMCachedCertificate cached_cert;
+ if (cached_cert.Init(cert) != CSSM_OK) {
+ NOTREACHED();
+ continue;
+ }
+ x509_util::CSSMFieldValue serial_number;
+ err = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, &serial_number);
+ if (err || !serial_number.field()) {
+ NOTREACHED();
+ continue;
+ }
+
+ base::StringPiece serial(
+ reinterpret_cast<const char*>(serial_number.field()->Data),
+ serial_number.field()->Length);
+
+ CRLSet::Result result = crl_set->CheckSPKI(spki_hash);
+
+ if (result != CRLSet::REVOKED && !issuer_spki_hash.empty())
+ result = crl_set->CheckSerial(serial, issuer_spki_hash);
+
+ issuer_spki_hash = spki_hash;
+
+ switch (result) {
+ case CRLSet::REVOKED:
+ return false;
+ case CRLSet::UNKNOWN:
+ case CRLSet::GOOD:
+ continue;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA
+// that we recognise as a standard root.
+// static
+bool IsIssuedByKnownRoot(CFArrayRef chain) {
+ int n = CFArrayGetCount(chain);
+ if (n < 1)
+ return false;
+ SecCertificateRef root_ref = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(chain, n - 1)));
+ SHA1HashValue hash = X509Certificate::CalculateFingerprint(root_ref);
+ return IsSHA1HashInSortedArray(
+ hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes));
+}
+
+// Builds and evaluates a SecTrustRef for the certificate chain contained
+// in |cert_array|, using the verification policies in |trust_policies|. On
+// success, returns OK, and updates |trust_ref|, |trust_result|,
+// |verified_chain|, and |chain_info| with the verification results. On
+// failure, no output parameters are modified.
+//
+// Note: An OK return does not mean that |cert_array| is trusted, merely that
+// verification was performed successfully.
+//
+// This function should only be called while the Mac Security Services lock is
+// held.
+int BuildAndEvaluateSecTrustRef(CFArrayRef cert_array,
+ CFArrayRef trust_policies,
+ int flags,
+ ScopedCFTypeRef<SecTrustRef>* trust_ref,
+ SecTrustResultType* trust_result,
+ ScopedCFTypeRef<CFArrayRef>* verified_chain,
+ CSSM_TP_APPLE_EVIDENCE_INFO** chain_info) {
+ SecTrustRef tmp_trust = NULL;
+ OSStatus status = SecTrustCreateWithCertificates(cert_array, trust_policies,
+ &tmp_trust);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ ScopedCFTypeRef<SecTrustRef> scoped_tmp_trust(tmp_trust);
+
+ if (TestRootCerts::HasInstance()) {
+ status = TestRootCerts::GetInstance()->FixupSecTrustRef(tmp_trust);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ }
+
+ CSSM_APPLE_TP_ACTION_DATA tp_action_data;
+ memset(&tp_action_data, 0, sizeof(tp_action_data));
+ tp_action_data.Version = CSSM_APPLE_TP_ACTION_VERSION;
+ // Allow CSSM to download any missing intermediate certificates if an
+ // authorityInfoAccess extension or issuerAltName extension is present.
+ tp_action_data.ActionFlags = CSSM_TP_ACTION_FETCH_CERT_FROM_NET |
+ CSSM_TP_ACTION_TRUST_SETTINGS;
+
+ // Note: For EV certificates, the Apple TP will handle setting these flags
+ // as part of EV evaluation.
+ if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED) {
+ // Require a positive result from an OCSP responder or a CRL (or both)
+ // for every certificate in the chain. The Apple TP automatically
+ // excludes the self-signed root from this requirement. If a certificate
+ // is missing both a crlDistributionPoints extension and an
+ // authorityInfoAccess extension with an OCSP responder URL, then we
+ // will get a kSecTrustResultRecoverableTrustFailure back from
+ // SecTrustEvaluate(), with a
+ // CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK error code. In that case,
+ // we'll set our own result to include
+ // CERT_STATUS_NO_REVOCATION_MECHANISM. If one or both extensions are
+ // present, and a check fails (server unavailable, OCSP retry later,
+ // signature mismatch), then we'll set our own result to include
+ // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION.
+ tp_action_data.ActionFlags |= CSSM_TP_ACTION_REQUIRE_REV_PER_CERT;
+
+ // Note, even if revocation checking is disabled, SecTrustEvaluate() will
+ // modify the OCSP options so as to attempt OCSP checking if it believes a
+ // certificate may chain to an EV root. However, because network fetches
+ // are disabled in CreateTrustPolicies() when revocation checking is
+ // disabled, these will only go against the local cache.
+ }
+
+ CFDataRef action_data_ref =
+ CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+ reinterpret_cast<UInt8*>(&tp_action_data),
+ sizeof(tp_action_data), kCFAllocatorNull);
+ if (!action_data_ref)
+ return ERR_OUT_OF_MEMORY;
+ ScopedCFTypeRef<CFDataRef> scoped_action_data_ref(action_data_ref);
+ status = SecTrustSetParameters(tmp_trust, CSSM_TP_ACTION_DEFAULT,
+ action_data_ref);
+ if (status)
+ return NetErrorFromOSStatus(status);
+
+ // Verify the certificate. A non-zero result from SecTrustGetResult()
+ // indicates that some fatal error occurred and the chain couldn't be
+ // processed, not that the chain contains no errors. We need to examine the
+ // output of SecTrustGetResult() to determine that.
+ SecTrustResultType tmp_trust_result;
+ status = SecTrustEvaluate(tmp_trust, &tmp_trust_result);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ CFArrayRef tmp_verified_chain = NULL;
+ CSSM_TP_APPLE_EVIDENCE_INFO* tmp_chain_info;
+ status = SecTrustGetResult(tmp_trust, &tmp_trust_result, &tmp_verified_chain,
+ &tmp_chain_info);
+ if (status)
+ return NetErrorFromOSStatus(status);
+
+ trust_ref->swap(scoped_tmp_trust);
+ *trust_result = tmp_trust_result;
+ verified_chain->reset(tmp_verified_chain);
+ *chain_info = tmp_chain_info;
+
+ return OK;
+}
+
+// OS X ships with both "GTE CyberTrust Global Root" and "Baltimore CyberTrust
+// Root" as part of its trusted root store. However, a cross-certified version
+// of the "Baltimore CyberTrust Root" exists that chains to "GTE CyberTrust
+// Global Root". When OS X/Security.framework attempts to evaluate such a
+// certificate chain, it disregards the "Baltimore CyberTrust Root" that exists
+// within Keychain and instead attempts to terminate the chain in the "GTE
+// CyberTrust Global Root". However, the GTE root is scheduled to be removed in
+// a future OS X update (for sunsetting purposes), and once removed, such
+// chains will fail validation, even though a trust anchor still exists.
+//
+// Rather than over-generalizing a solution that may mask a number of TLS
+// misconfigurations, attempt to specifically match the affected
+// cross-certified certificate and remove it from certificate chain processing.
+bool IsBadBaltimoreGTECertificate(SecCertificateRef cert) {
+ // Matches the GTE-signed Baltimore CyberTrust Root
+ // https://cacert.omniroot.com/Baltimore-to-GTE-04-12.pem
+ static const SHA1HashValue kBadBaltimoreHashNew =
+ { { 0x4D, 0x34, 0xEA, 0x92, 0x76, 0x4B, 0x3A, 0x31, 0x49, 0x11,
+ 0x99, 0x52, 0xF4, 0x19, 0x30, 0xCA, 0x11, 0x34, 0x83, 0x61 } };
+ // Matches the legacy GTE-signed Baltimore CyberTrust Root
+ // https://cacert.omniroot.com/gte-2-2025.pem
+ static const SHA1HashValue kBadBaltimoreHashOld =
+ { { 0x54, 0xD8, 0xCB, 0x49, 0x1F, 0xA1, 0x6D, 0xF8, 0x87, 0xDC,
+ 0x94, 0xA9, 0x34, 0xCC, 0x83, 0x6B, 0xDA, 0xA8, 0xA3, 0x69 } };
+
+ SHA1HashValue fingerprint = X509Certificate::CalculateFingerprint(cert);
+
+ return fingerprint.Equals(kBadBaltimoreHashNew) ||
+ fingerprint.Equals(kBadBaltimoreHashOld);
+}
+
+// Attempts to re-verify |cert_array| after adjusting the inputs to work around
+// known issues in OS X. To be used if BuildAndEvaluateSecTrustRef fails to
+// return a positive result for verification.
+//
+// This function should only be called while the Mac Security Services lock is
+// held.
+void RetrySecTrustEvaluateWithAdjustedChain(
+ CFArrayRef cert_array,
+ CFArrayRef trust_policies,
+ int flags,
+ ScopedCFTypeRef<SecTrustRef>* trust_ref,
+ SecTrustResultType* trust_result,
+ ScopedCFTypeRef<CFArrayRef>* verified_chain,
+ CSSM_TP_APPLE_EVIDENCE_INFO** chain_info) {
+ CFIndex count = CFArrayGetCount(*verified_chain);
+ CFIndex slice_point = 0;
+
+ for (CFIndex i = 1; i < count; ++i) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(*verified_chain, i)));
+ if (cert == NULL)
+ return; // Strange times; can't fix things up.
+
+ if (IsBadBaltimoreGTECertificate(cert)) {
+ slice_point = i;
+ break;
+ }
+ }
+ if (slice_point == 0)
+ return; // Nothing to do.
+
+ ScopedCFTypeRef<CFMutableArrayRef> adjusted_cert_array(
+ CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
+ // Note: This excludes the certificate at |slice_point|.
+ CFArrayAppendArray(adjusted_cert_array, cert_array,
+ CFRangeMake(0, slice_point));
+
+ // Ignore the result; failure will preserve the old verification results.
+ BuildAndEvaluateSecTrustRef(
+ adjusted_cert_array, trust_policies, flags, trust_ref, trust_result,
+ verified_chain, chain_info);
+}
+
+} // namespace
+
+CertVerifyProcMac::CertVerifyProcMac() {}
+
+CertVerifyProcMac::~CertVerifyProcMac() {}
+
+bool CertVerifyProcMac::SupportsAdditionalTrustAnchors() const {
+ return false;
+}
+
+int CertVerifyProcMac::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ ScopedCFTypeRef<CFArrayRef> trust_policies;
+ OSStatus status = CreateTrustPolicies(hostname, flags, &trust_policies);
+ if (status)
+ return NetErrorFromOSStatus(status);
+
+ // Create and configure a SecTrustRef, which takes our certificate(s)
+ // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an
+ // array of certificates, the first of which is the certificate we're
+ // verifying, and the subsequent (optional) certificates are used for
+ // chain building.
+ ScopedCFTypeRef<CFArrayRef> cert_array(cert->CreateOSCertChainForCert());
+
+ // Serialize all calls that may use the Keychain, to work around various
+ // issues in OS X 10.6+ with multi-threaded access to Security.framework.
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+
+ ScopedCFTypeRef<SecTrustRef> trust_ref;
+ SecTrustResultType trust_result = kSecTrustResultDeny;
+ ScopedCFTypeRef<CFArrayRef> completed_chain;
+ CSSM_TP_APPLE_EVIDENCE_INFO* chain_info = NULL;
+
+ int rv = BuildAndEvaluateSecTrustRef(
+ cert_array, trust_policies, flags, &trust_ref, &trust_result,
+ &completed_chain, &chain_info);
+ if (rv != OK)
+ return rv;
+ if (trust_result != kSecTrustResultUnspecified &&
+ trust_result != kSecTrustResultProceed) {
+ RetrySecTrustEvaluateWithAdjustedChain(
+ cert_array, trust_policies, flags, &trust_ref, &trust_result,
+ &completed_chain, &chain_info);
+ }
+
+ if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED)
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+
+ if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set))
+ verify_result->cert_status |= CERT_STATUS_REVOKED;
+
+ GetCertChainInfo(completed_chain, chain_info, verify_result);
+
+ // As of Security Update 2012-002/OS X 10.7.4, when an RSA key < 1024 bits
+ // is encountered, CSSM returns CSSMERR_TP_VERIFY_ACTION_FAILED and adds
+ // CSSMERR_CSP_UNSUPPORTED_KEY_SIZE as a certificate status. Avoid mapping
+ // the CSSMERR_TP_VERIFY_ACTION_FAILED to CERT_STATUS_INVALID if the only
+ // error was due to an unsupported key size.
+ bool policy_failed = false;
+ bool weak_key = false;
+
+ // Evaluate the results
+ OSStatus cssm_result;
+ switch (trust_result) {
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ // Certificate chain is valid and trusted ("unspecified" indicates that
+ // the user has not explicitly set a trust setting)
+ break;
+
+ // According to SecTrust.h, kSecTrustResultConfirm isn't returned on 10.5+,
+ // and it is marked deprecated in the 10.9 SDK.
+ case kSecTrustResultDeny:
+ // Certificate chain is explicitly untrusted.
+ verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+ break;
+
+ case kSecTrustResultRecoverableTrustFailure:
+ // Certificate chain has a failure that can be overridden by the user.
+ status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ if (cssm_result == CSSMERR_TP_VERIFY_ACTION_FAILED) {
+ policy_failed = true;
+ } else {
+ verify_result->cert_status |= CertStatusFromOSStatus(cssm_result);
+ }
+ // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO
+ // structure which can catch multiple errors from each certificate.
+ for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain);
+ index < chain_count; ++index) {
+ if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED ||
+ chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET)
+ verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
+ if (!IsCertStatusError(verify_result->cert_status) &&
+ chain_info[index].NumStatusCodes == 0) {
+ LOG(WARNING) << "chain_info[" << index << "].NumStatusCodes is 0"
+ ", chain_info[" << index << "].StatusBits is "
+ << chain_info[index].StatusBits;
+ }
+ for (uint32 status_code_index = 0;
+ status_code_index < chain_info[index].NumStatusCodes;
+ ++status_code_index) {
+ CertStatus mapped_status = CertStatusFromOSStatus(
+ chain_info[index].StatusCodes[status_code_index]);
+ if (mapped_status == CERT_STATUS_WEAK_KEY)
+ weak_key = true;
+ verify_result->cert_status |= mapped_status;
+ }
+ }
+ if (policy_failed && !weak_key) {
+ // If CSSMERR_TP_VERIFY_ACTION_FAILED wasn't returned due to a weak
+ // key, map it back to an appropriate error code.
+ verify_result->cert_status |= CertStatusFromOSStatus(cssm_result);
+ }
+ if (!IsCertStatusError(verify_result->cert_status)) {
+ LOG(ERROR) << "cssm_result=" << cssm_result;
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ NOTREACHED();
+ }
+ break;
+
+ default:
+ status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
+ if (status)
+ return NetErrorFromOSStatus(status);
+ verify_result->cert_status |= CertStatusFromOSStatus(cssm_result);
+ if (!IsCertStatusError(verify_result->cert_status)) {
+ LOG(WARNING) << "trust_result=" << trust_result;
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ }
+ break;
+ }
+
+ // Perform hostname verification independent of SecTrustEvaluate. In order to
+ // do so, mask off any reported name errors first.
+ verify_result->cert_status &= ~CERT_STATUS_COMMON_NAME_INVALID;
+ if (!cert->VerifyNameMatch(hostname))
+ verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+
+ // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be
+ // compatible with Windows, which in turn implements this behavior to be
+ // compatible with WinHTTP, which doesn't report this error (bug 3004).
+ verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM;
+
+ AppendPublicKeyHashes(completed_chain, &verify_result->public_key_hashes);
+ verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(completed_chain);
+
+ if (IsCertStatusError(verify_result->cert_status))
+ return MapCertStatusToNetError(verify_result->cert_status);
+
+ if (flags & CertVerifier::VERIFY_EV_CERT) {
+ // Determine the certificate's EV status using SecTrustCopyExtendedResult(),
+ // which is an internal/private API function added in OS X 10.5.7.
+ // Note: "ExtendedResult" means extended validation results.
+ CFBundleRef bundle =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
+ if (bundle) {
+ SecTrustCopyExtendedResultFuncPtr copy_extended_result =
+ reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>(
+ CFBundleGetFunctionPointerForName(bundle,
+ CFSTR("SecTrustCopyExtendedResult")));
+ if (copy_extended_result) {
+ CFDictionaryRef ev_dict_temp = NULL;
+ status = copy_extended_result(trust_ref, &ev_dict_temp);
+ ScopedCFTypeRef<CFDictionaryRef> ev_dict(ev_dict_temp);
+ ev_dict_temp = NULL;
+ if (status == noErr && ev_dict) {
+ // In 10.7.3, SecTrustCopyExtendedResult returns noErr and populates
+ // ev_dict even for non-EV certificates, but only EV certificates
+ // will cause ev_dict to contain kSecEVOrganizationName. In previous
+ // releases, SecTrustCopyExtendedResult would only return noErr and
+ // populate ev_dict for EV certificates, but would always include
+ // kSecEVOrganizationName in that case, so checking for this key is
+ // appropriate for all known versions of SecTrustCopyExtendedResult.
+ // The actual organization name is unneeded here and can be accessed
+ // through other means. All that matters here is the OS' conception
+ // of whether or not the certificate is EV.
+ if (CFDictionaryContainsKey(ev_dict,
+ kSecEVOrganizationName)) {
+ verify_result->cert_status |= CERT_STATUS_IS_EV;
+ if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY)
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ }
+ }
+ }
+ }
+ }
+
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_mac.h b/chromium/net/cert/cert_verify_proc_mac.h
new file mode 100644
index 00000000000..cb557671f64
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_mac.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_MAC_H_
+#define NET_CERT_CERT_VERIFY_PROC_MAC_H_
+
+#include "net/cert/cert_verify_proc.h"
+
+namespace net {
+
+// Performs certificate path construction and validation using OS X's
+// Security.framework.
+class CertVerifyProcMac : public CertVerifyProc {
+ public:
+ CertVerifyProcMac();
+
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE;
+
+ protected:
+ virtual ~CertVerifyProcMac();
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_MAC_H_
diff --git a/chromium/net/cert/cert_verify_proc_nss.cc b/chromium/net/cert/cert_verify_proc_nss.cc
new file mode 100644
index 00000000000..f63297e83c5
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_nss.cc
@@ -0,0 +1,892 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc_nss.h"
+
+#include <string>
+#include <vector>
+
+#include <cert.h>
+#include <nss.h>
+#include <prerror.h>
+#include <secerr.h>
+#include <sechash.h>
+#include <sslerr.h>
+
+#include "base/logging.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util_nss.h"
+
+#if defined(OS_IOS)
+#include <CommonCrypto/CommonDigest.h>
+#include "net/cert/x509_util_ios.h"
+#endif // defined(OS_IOS)
+
+#define NSS_VERSION_NUM (NSS_VMAJOR * 10000 + NSS_VMINOR * 100 + NSS_VPATCH)
+#if NSS_VERSION_NUM < 31305
+// Added in NSS 3.13.5.
+#define SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED -8016
+#endif
+
+#if NSS_VERSION_NUM < 31402
+// Added in NSS 3.14.2.
+#define cert_pi_useOnlyTrustAnchors static_cast<CERTValParamInType>(14)
+#endif
+
+namespace net {
+
+namespace {
+
+typedef scoped_ptr_malloc<
+ CERTCertificatePolicies,
+ crypto::NSSDestroyer<CERTCertificatePolicies,
+ CERT_DestroyCertificatePoliciesExtension> >
+ ScopedCERTCertificatePolicies;
+
+typedef scoped_ptr_malloc<
+ CERTCertList,
+ crypto::NSSDestroyer<CERTCertList, CERT_DestroyCertList> >
+ ScopedCERTCertList;
+
+// ScopedCERTValOutParam manages destruction of values in the CERTValOutParam
+// array that cvout points to. cvout must be initialized as passed to
+// CERT_PKIXVerifyCert, so that the array must be terminated with
+// cert_po_end type.
+// When it goes out of scope, it destroys values of cert_po_trustAnchor
+// and cert_po_certList types, but doesn't release the array itself.
+class ScopedCERTValOutParam {
+ public:
+ explicit ScopedCERTValOutParam(CERTValOutParam* cvout) : cvout_(cvout) {}
+
+ ~ScopedCERTValOutParam() {
+ Clear();
+ }
+
+ // Free the internal resources, but do not release the array itself.
+ void Clear() {
+ if (cvout_ == NULL)
+ return;
+ for (CERTValOutParam *p = cvout_; p->type != cert_po_end; p++) {
+ switch (p->type) {
+ case cert_po_trustAnchor:
+ if (p->value.pointer.cert) {
+ CERT_DestroyCertificate(p->value.pointer.cert);
+ p->value.pointer.cert = NULL;
+ }
+ break;
+ case cert_po_certList:
+ if (p->value.pointer.chain) {
+ CERT_DestroyCertList(p->value.pointer.chain);
+ p->value.pointer.chain = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private:
+ CERTValOutParam* cvout_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedCERTValOutParam);
+};
+
+// Map PORT_GetError() return values to our network error codes.
+int MapSecurityError(int err) {
+ switch (err) {
+ case PR_DIRECTORY_LOOKUP_ERROR: // DNS lookup error.
+ return ERR_NAME_NOT_RESOLVED;
+ case SEC_ERROR_INVALID_ARGS:
+ return ERR_INVALID_ARGUMENT;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ return ERR_CERT_COMMON_NAME_INVALID;
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ return ERR_CERT_DATE_INVALID;
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_CA_CERT_INVALID:
+ return ERR_CERT_AUTHORITY_INVALID;
+ // TODO(port): map ERR_CERT_NO_REVOCATION_MECHANISM.
+ case SEC_ERROR_OCSP_BAD_HTTP_RESPONSE:
+ case SEC_ERROR_OCSP_SERVER_ERROR:
+ return ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ case SEC_ERROR_REVOKED_CERTIFICATE:
+ case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked.
+ return ERR_CERT_REVOKED;
+ case SEC_ERROR_BAD_DER:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_CERT_NOT_VALID:
+ // TODO(port): add an ERR_CERT_WRONG_USAGE error code.
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_INADEQUATE_KEY_USAGE: // Key usage.
+ case SEC_ERROR_INADEQUATE_CERT_TYPE: // Extended key usage and whether
+ // the certificate is a CA.
+ case SEC_ERROR_POLICY_VALIDATION_FAILED:
+ case SEC_ERROR_CERT_NOT_IN_NAME_SPACE:
+ case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ return ERR_CERT_INVALID;
+ case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+ return ERR_CERT_WEAK_SIGNATURE_ALGORITHM;
+ default:
+ LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+}
+
+// Map PORT_GetError() return values to our cert status flags.
+CertStatus MapCertErrorToCertStatus(int err) {
+ int net_error = MapSecurityError(err);
+ return MapNetErrorToCertStatus(net_error);
+}
+
+// Saves some information about the certificate chain cert_list in
+// *verify_result. The caller MUST initialize *verify_result before calling
+// this function.
+// Note that cert_list[0] is the end entity certificate.
+void GetCertChainInfo(CERTCertList* cert_list,
+ CERTCertificate* root_cert,
+ CertVerifyResult* verify_result) {
+ DCHECK(cert_list);
+
+ CERTCertificate* verified_cert = NULL;
+ std::vector<CERTCertificate*> verified_chain;
+ int i = 0;
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node), ++i) {
+ if (i == 0) {
+ verified_cert = node->cert;
+ } else {
+ // Because of an NSS bug, CERT_PKIXVerifyCert may chain a self-signed
+ // certificate of a root CA to another certificate of the same root CA
+ // key. Detect that error and ignore the root CA certificate.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=721288.
+ if (node->cert->isRoot) {
+ // NOTE: isRoot doesn't mean the certificate is a trust anchor. It
+ // means the certificate is self-signed. Here we assume isRoot only
+ // implies the certificate is self-issued.
+ CERTCertListNode* next_node = CERT_LIST_NEXT(node);
+ CERTCertificate* next_cert;
+ if (!CERT_LIST_END(next_node, cert_list)) {
+ next_cert = next_node->cert;
+ } else {
+ next_cert = root_cert;
+ }
+ // Test that |node->cert| is actually a self-signed certificate
+ // whose key is equal to |next_cert|, and not a self-issued
+ // certificate signed by another key of the same CA.
+ if (next_cert && SECITEM_ItemsAreEqual(&node->cert->derPublicKey,
+ &next_cert->derPublicKey)) {
+ continue;
+ }
+ }
+ verified_chain.push_back(node->cert);
+ }
+
+ SECAlgorithmID& signature = node->cert->signature;
+ SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm);
+ switch (oid_tag) {
+ case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION:
+ verify_result->has_md5 = true;
+ break;
+ case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION:
+ verify_result->has_md2 = true;
+ break;
+ case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION:
+ verify_result->has_md4 = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (root_cert)
+ verified_chain.push_back(root_cert);
+#if defined(OS_IOS)
+ verify_result->verified_cert =
+ x509_util_ios::CreateCertFromNSSHandles(verified_cert, verified_chain);
+#else
+ verify_result->verified_cert =
+ X509Certificate::CreateFromHandle(verified_cert, verified_chain);
+#endif // defined(OS_IOS)
+}
+
+// IsKnownRoot returns true if the given certificate is one that we believe
+// is a standard (as opposed to user-installed) root.
+bool IsKnownRoot(CERTCertificate* root) {
+ if (!root || !root->slot)
+ return false;
+
+ // This magic name is taken from
+ // http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/security/nss/lib/ckfw/builtins/constants.c&rev=1.13&mark=86,89#79
+ return 0 == strcmp(PK11_GetSlotName(root->slot),
+ "NSS Builtin Objects");
+}
+
+// Returns true if the given certificate is one of the additional trust anchors.
+bool IsAdditionalTrustAnchor(CERTCertList* additional_trust_anchors,
+ CERTCertificate* root) {
+ if (!additional_trust_anchors || !root)
+ return false;
+ for (CERTCertListNode* node = CERT_LIST_HEAD(additional_trust_anchors);
+ !CERT_LIST_END(node, additional_trust_anchors);
+ node = CERT_LIST_NEXT(node)) {
+ if (CERT_CompareCerts(node->cert, root))
+ return true;
+ }
+ return false;
+}
+
+enum CRLSetResult {
+ kCRLSetOk,
+ kCRLSetRevoked,
+ kCRLSetUnknown,
+};
+
+// CheckRevocationWithCRLSet attempts to check each element of |cert_list|
+// against |crl_set|. It returns:
+// kCRLSetRevoked: if any element of the chain is known to have been revoked.
+// kCRLSetUnknown: if there is no fresh information about some element in
+// the chain.
+// kCRLSetOk: if every element in the chain is covered by a fresh CRLSet and
+// is unrevoked.
+CRLSetResult CheckRevocationWithCRLSet(CERTCertList* cert_list,
+ CERTCertificate* root,
+ CRLSet* crl_set) {
+ std::vector<CERTCertificate*> certs;
+
+ if (cert_list) {
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ certs.push_back(node->cert);
+ }
+ }
+ if (root)
+ certs.push_back(root);
+
+ bool covered = true;
+
+ // We iterate from the root certificate down to the leaf, keeping track of
+ // the issuer's SPKI at each step.
+ std::string issuer_spki_hash;
+ for (std::vector<CERTCertificate*>::reverse_iterator i = certs.rbegin();
+ i != certs.rend(); ++i) {
+ CERTCertificate* cert = *i;
+
+ base::StringPiece der(reinterpret_cast<char*>(cert->derCert.data),
+ cert->derCert.len);
+
+ base::StringPiece spki;
+ if (!asn1::ExtractSPKIFromDERCert(der, &spki)) {
+ NOTREACHED();
+ covered = false;
+ continue;
+ }
+ const std::string spki_hash = crypto::SHA256HashString(spki);
+
+ base::StringPiece serial_number = base::StringPiece(
+ reinterpret_cast<char*>(cert->serialNumber.data),
+ cert->serialNumber.len);
+
+ CRLSet::Result result = crl_set->CheckSPKI(spki_hash);
+
+ if (result != CRLSet::REVOKED && !issuer_spki_hash.empty())
+ result = crl_set->CheckSerial(serial_number, issuer_spki_hash);
+
+ issuer_spki_hash = spki_hash;
+
+ switch (result) {
+ case CRLSet::REVOKED:
+ return kCRLSetRevoked;
+ case CRLSet::UNKNOWN:
+ covered = false;
+ continue;
+ case CRLSet::GOOD:
+ continue;
+ default:
+ NOTREACHED();
+ covered = false;
+ continue;
+ }
+ }
+
+ if (!covered || crl_set->IsExpired())
+ return kCRLSetUnknown;
+ return kCRLSetOk;
+}
+
+// Forward declarations.
+SECStatus RetryPKIXVerifyCertWithWorkarounds(
+ CERTCertificate* cert_handle, int num_policy_oids,
+ bool cert_io_enabled, std::vector<CERTValInParam>* cvin,
+ CERTValOutParam* cvout);
+SECOidTag GetFirstCertPolicy(CERTCertificate* cert_handle);
+
+// Call CERT_PKIXVerifyCert for the cert_handle.
+// Verification results are stored in an array of CERTValOutParam.
+// If |hard_fail| is true, and no policy_oids are supplied (eg: EV is NOT being
+// checked), then the failure to obtain valid CRL/OCSP information for all
+// certificates that contain CRL/OCSP URLs will cause the certificate to be
+// treated as if it was revoked. Since failures may be caused by transient
+// network failures or by malicious attackers, in general, hard_fail should be
+// false.
+// If policy_oids is not NULL and num_policy_oids is positive, policies
+// are also checked.
+// additional_trust_anchors is an optional list of certificates that can be
+// trusted as anchors when building a certificate chain.
+// Caller must initialize cvout before calling this function.
+SECStatus PKIXVerifyCert(CERTCertificate* cert_handle,
+ bool check_revocation,
+ bool hard_fail,
+ bool cert_io_enabled,
+ const SECOidTag* policy_oids,
+ int num_policy_oids,
+ CERTCertList* additional_trust_anchors,
+ CERTValOutParam* cvout) {
+ bool use_crl = check_revocation;
+ bool use_ocsp = check_revocation;
+
+ PRUint64 revocation_method_flags =
+ CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD |
+ CERT_REV_M_ALLOW_NETWORK_FETCHING |
+ CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE |
+ CERT_REV_M_IGNORE_MISSING_FRESH_INFO |
+ CERT_REV_M_STOP_TESTING_ON_FRESH_INFO;
+ PRUint64 revocation_method_independent_flags =
+ CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST;
+ if (check_revocation && policy_oids && num_policy_oids > 0) {
+ // EV verification requires revocation checking. Consider the certificate
+ // revoked if we don't have revocation info.
+ // TODO(wtc): Add a bool parameter to expressly specify we're doing EV
+ // verification or we want strict revocation flags.
+ revocation_method_flags |= CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE;
+ revocation_method_independent_flags |=
+ CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE;
+ } else if (check_revocation && hard_fail) {
+ revocation_method_flags |= CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO;
+ revocation_method_independent_flags |=
+ CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE;
+ } else {
+ revocation_method_flags |= CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE;
+ revocation_method_independent_flags |=
+ CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT;
+ }
+ PRUint64 method_flags[2];
+ method_flags[cert_revocation_method_crl] = revocation_method_flags;
+ method_flags[cert_revocation_method_ocsp] = revocation_method_flags;
+
+ if (use_crl) {
+ method_flags[cert_revocation_method_crl] |=
+ CERT_REV_M_TEST_USING_THIS_METHOD;
+ }
+ if (use_ocsp) {
+ method_flags[cert_revocation_method_ocsp] |=
+ CERT_REV_M_TEST_USING_THIS_METHOD;
+ }
+
+ CERTRevocationMethodIndex preferred_revocation_methods[1];
+ if (use_ocsp) {
+ preferred_revocation_methods[0] = cert_revocation_method_ocsp;
+ } else {
+ preferred_revocation_methods[0] = cert_revocation_method_crl;
+ }
+
+ CERTRevocationFlags revocation_flags;
+ revocation_flags.leafTests.number_of_defined_methods =
+ arraysize(method_flags);
+ revocation_flags.leafTests.cert_rev_flags_per_method = method_flags;
+ revocation_flags.leafTests.number_of_preferred_methods =
+ arraysize(preferred_revocation_methods);
+ revocation_flags.leafTests.preferred_methods = preferred_revocation_methods;
+ revocation_flags.leafTests.cert_rev_method_independent_flags =
+ revocation_method_independent_flags;
+
+ revocation_flags.chainTests.number_of_defined_methods =
+ arraysize(method_flags);
+ revocation_flags.chainTests.cert_rev_flags_per_method = method_flags;
+ revocation_flags.chainTests.number_of_preferred_methods =
+ arraysize(preferred_revocation_methods);
+ revocation_flags.chainTests.preferred_methods = preferred_revocation_methods;
+ revocation_flags.chainTests.cert_rev_method_independent_flags =
+ revocation_method_independent_flags;
+
+
+ std::vector<CERTValInParam> cvin;
+ cvin.reserve(7);
+ CERTValInParam in_param;
+ in_param.type = cert_pi_revocationFlags;
+ in_param.value.pointer.revocation = &revocation_flags;
+ cvin.push_back(in_param);
+ if (policy_oids && num_policy_oids > 0) {
+ in_param.type = cert_pi_policyOID;
+ in_param.value.arraySize = num_policy_oids;
+ in_param.value.array.oids = policy_oids;
+ cvin.push_back(in_param);
+ }
+ if (additional_trust_anchors) {
+ in_param.type = cert_pi_trustAnchors;
+ in_param.value.pointer.chain = additional_trust_anchors;
+ cvin.push_back(in_param);
+ in_param.type = cert_pi_useOnlyTrustAnchors;
+ in_param.value.scalar.b = PR_FALSE;
+ cvin.push_back(in_param);
+ }
+ in_param.type = cert_pi_end;
+ cvin.push_back(in_param);
+
+ SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
+ &cvin[0], cvout, NULL);
+ if (rv != SECSuccess) {
+ rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids,
+ cert_io_enabled, &cvin, cvout);
+ }
+ return rv;
+}
+
+// PKIXVerifyCert calls this function to work around some bugs in
+// CERT_PKIXVerifyCert. All the arguments of this function are either the
+// arguments or local variables of PKIXVerifyCert.
+SECStatus RetryPKIXVerifyCertWithWorkarounds(
+ CERTCertificate* cert_handle, int num_policy_oids,
+ bool cert_io_enabled, std::vector<CERTValInParam>* cvin,
+ CERTValOutParam* cvout) {
+ // We call this function when the first CERT_PKIXVerifyCert call in
+ // PKIXVerifyCert failed, so we initialize |rv| to SECFailure.
+ SECStatus rv = SECFailure;
+ int nss_error = PORT_GetError();
+ CERTValInParam in_param;
+
+ // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate
+ // CA certificate, so we retry with cert_pi_useAIACertFetch.
+ // cert_pi_useAIACertFetch has several bugs in its error handling and
+ // error reporting (NSS bug 528743), so we don't use it by default.
+ // Note: When building a certificate chain, CERT_PKIXVerifyCert may
+ // incorrectly pick a CA certificate with the same subject name as the
+ // missing intermediate CA certificate, and fail with the
+ // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with
+ // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE.
+ if (cert_io_enabled &&
+ (nss_error == SEC_ERROR_UNKNOWN_ISSUER ||
+ nss_error == SEC_ERROR_BAD_SIGNATURE)) {
+ DCHECK_EQ(cvin->back().type, cert_pi_end);
+ cvin->pop_back();
+ in_param.type = cert_pi_useAIACertFetch;
+ in_param.value.scalar.b = PR_TRUE;
+ cvin->push_back(in_param);
+ in_param.type = cert_pi_end;
+ cvin->push_back(in_param);
+ rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
+ &(*cvin)[0], cvout, NULL);
+ if (rv == SECSuccess)
+ return rv;
+ int new_nss_error = PORT_GetError();
+ if (new_nss_error == SEC_ERROR_INVALID_ARGS ||
+ new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE ||
+ new_nss_error == SEC_ERROR_BAD_INFO_ACCESS_LOCATION ||
+ new_nss_error == SEC_ERROR_BAD_HTTP_RESPONSE ||
+ new_nss_error == SEC_ERROR_BAD_LDAP_RESPONSE ||
+ !IS_SEC_ERROR(new_nss_error)) {
+ // Use the original error code because of cert_pi_useAIACertFetch's
+ // bad error reporting.
+ PORT_SetError(nss_error);
+ return rv;
+ }
+ nss_error = new_nss_error;
+ }
+
+ // If an intermediate CA certificate has requireExplicitPolicy in its
+ // policyConstraints extension, CERT_PKIXVerifyCert fails with
+ // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any
+ // certificate policy (NSS bug 552775). So we retry with the certificate
+ // policy found in the server certificate.
+ if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED &&
+ num_policy_oids == 0) {
+ SECOidTag policy = GetFirstCertPolicy(cert_handle);
+ if (policy != SEC_OID_UNKNOWN) {
+ DCHECK_EQ(cvin->back().type, cert_pi_end);
+ cvin->pop_back();
+ in_param.type = cert_pi_policyOID;
+ in_param.value.arraySize = 1;
+ in_param.value.array.oids = &policy;
+ cvin->push_back(in_param);
+ in_param.type = cert_pi_end;
+ cvin->push_back(in_param);
+ rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer,
+ &(*cvin)[0], cvout, NULL);
+ if (rv != SECSuccess) {
+ // Use the original error code.
+ PORT_SetError(nss_error);
+ }
+ }
+ }
+
+ return rv;
+}
+
+// Decodes the certificatePolicies extension of the certificate. Returns
+// NULL if the certificate doesn't have the extension or the extension can't
+// be decoded. The returned value must be freed with a
+// CERT_DestroyCertificatePoliciesExtension call.
+CERTCertificatePolicies* DecodeCertPolicies(
+ CERTCertificate* cert_handle) {
+ SECItem policy_ext;
+ SECStatus rv = CERT_FindCertExtension(cert_handle,
+ SEC_OID_X509_CERTIFICATE_POLICIES,
+ &policy_ext);
+ if (rv != SECSuccess)
+ return NULL;
+ CERTCertificatePolicies* policies =
+ CERT_DecodeCertificatePoliciesExtension(&policy_ext);
+ SECITEM_FreeItem(&policy_ext, PR_FALSE);
+ return policies;
+}
+
+// Returns the OID tag for the first certificate policy in the certificate's
+// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate
+// has no certificate policy.
+SECOidTag GetFirstCertPolicy(CERTCertificate* cert_handle) {
+ ScopedCERTCertificatePolicies policies(DecodeCertPolicies(cert_handle));
+ if (!policies.get())
+ return SEC_OID_UNKNOWN;
+
+ CERTPolicyInfo* policy_info = policies->policyInfos[0];
+ if (!policy_info)
+ return SEC_OID_UNKNOWN;
+ if (policy_info->oid != SEC_OID_UNKNOWN)
+ return policy_info->oid;
+
+ // The certificate policy is unknown to NSS. We need to create a dynamic
+ // OID tag for the policy.
+ SECOidData od;
+ od.oid.len = policy_info->policyID.len;
+ od.oid.data = policy_info->policyID.data;
+ od.offset = SEC_OID_UNKNOWN;
+ // NSS doesn't allow us to pass an empty description, so I use a hardcoded,
+ // default description here. The description doesn't need to be unique for
+ // each OID.
+ od.desc = "a certificate policy";
+ od.mechanism = CKM_INVALID_MECHANISM;
+ od.supportedExtension = INVALID_CERT_EXTENSION;
+ return SECOID_AddEntry(&od);
+}
+
+HashValue CertPublicKeyHashSHA1(CERTCertificate* cert) {
+ HashValue hash(HASH_VALUE_SHA1);
+#if defined(OS_IOS)
+ CC_SHA1(cert->derPublicKey.data, cert->derPublicKey.len, hash.data());
+#else
+ SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, hash.data(),
+ cert->derPublicKey.data, cert->derPublicKey.len);
+ DCHECK_EQ(SECSuccess, rv);
+#endif
+ return hash;
+}
+
+HashValue CertPublicKeyHashSHA256(CERTCertificate* cert) {
+ HashValue hash(HASH_VALUE_SHA256);
+#if defined(OS_IOS)
+ CC_SHA256(cert->derPublicKey.data, cert->derPublicKey.len, hash.data());
+#else
+ SECStatus rv = HASH_HashBuf(HASH_AlgSHA256, hash.data(),
+ cert->derPublicKey.data, cert->derPublicKey.len);
+ DCHECK_EQ(rv, SECSuccess);
+#endif
+ return hash;
+}
+
+void AppendPublicKeyHashes(CERTCertList* cert_list,
+ CERTCertificate* root_cert,
+ HashValueVector* hashes) {
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ hashes->push_back(CertPublicKeyHashSHA1(node->cert));
+ hashes->push_back(CertPublicKeyHashSHA256(node->cert));
+ }
+ if (root_cert) {
+ hashes->push_back(CertPublicKeyHashSHA1(root_cert));
+ hashes->push_back(CertPublicKeyHashSHA256(root_cert));
+ }
+}
+
+// Returns true if |cert_handle| contains a policy OID that is an EV policy
+// OID according to |metadata|, storing the resulting policy OID in
+// |*ev_policy_oid|. A true return is not sufficient to establish that a
+// certificate is EV, but a false return is sufficient to establish the
+// certificate cannot be EV.
+bool IsEVCandidate(EVRootCAMetadata* metadata,
+ CERTCertificate* cert_handle,
+ SECOidTag* ev_policy_oid) {
+ DCHECK(cert_handle);
+ ScopedCERTCertificatePolicies policies(DecodeCertPolicies(cert_handle));
+ if (!policies.get())
+ return false;
+
+ CERTPolicyInfo** policy_infos = policies->policyInfos;
+ while (*policy_infos != NULL) {
+ CERTPolicyInfo* policy_info = *policy_infos++;
+ // If the Policy OID is unknown, that implicitly means it has not been
+ // registered as an EV policy.
+ if (policy_info->oid == SEC_OID_UNKNOWN)
+ continue;
+ if (metadata->IsEVPolicyOID(policy_info->oid)) {
+ *ev_policy_oid = policy_info->oid;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Studied Mozilla's code (esp. security/manager/ssl/src/nsIdentityChecking.cpp
+// and nsNSSCertHelper.cpp) to learn how to verify EV certificate.
+// TODO(wtc): A possible optimization is that we get the trust anchor from
+// the first PKIXVerifyCert call. We look up the EV policy for the trust
+// anchor. If the trust anchor has no EV policy, we know the cert isn't EV.
+// Otherwise, we pass just that EV policy (as opposed to all the EV policies)
+// to the second PKIXVerifyCert call.
+bool VerifyEV(CERTCertificate* cert_handle,
+ int flags,
+ CRLSet* crl_set,
+ bool rev_checking_enabled,
+ EVRootCAMetadata* metadata,
+ SECOidTag ev_policy_oid,
+ CERTCertList* additional_trust_anchors) {
+ CERTValOutParam cvout[3];
+ int cvout_index = 0;
+ cvout[cvout_index].type = cert_po_certList;
+ cvout[cvout_index].value.pointer.chain = NULL;
+ int cvout_cert_list_index = cvout_index;
+ cvout_index++;
+ cvout[cvout_index].type = cert_po_trustAnchor;
+ cvout[cvout_index].value.pointer.cert = NULL;
+ int cvout_trust_anchor_index = cvout_index;
+ cvout_index++;
+ cvout[cvout_index].type = cert_po_end;
+ ScopedCERTValOutParam scoped_cvout(cvout);
+
+ SECStatus status = PKIXVerifyCert(
+ cert_handle,
+ rev_checking_enabled,
+ true, /* hard fail is implied in EV. */
+ flags & CertVerifier::VERIFY_CERT_IO_ENABLED,
+ &ev_policy_oid,
+ 1,
+ additional_trust_anchors,
+ cvout);
+ if (status != SECSuccess)
+ return false;
+
+ CERTCertificate* root_ca =
+ cvout[cvout_trust_anchor_index].value.pointer.cert;
+ if (root_ca == NULL)
+ return false;
+
+ // This second PKIXVerifyCert call could have found a different certification
+ // path and one or more of the certificates on this new path, that weren't on
+ // the old path, might have been revoked.
+ if (crl_set) {
+ CRLSetResult crl_set_result = CheckRevocationWithCRLSet(
+ cvout[cvout_cert_list_index].value.pointer.chain,
+ cvout[cvout_trust_anchor_index].value.pointer.cert,
+ crl_set);
+ if (crl_set_result == kCRLSetRevoked)
+ return false;
+ }
+
+#if defined(OS_IOS)
+ SHA1HashValue fingerprint = x509_util_ios::CalculateFingerprintNSS(root_ca);
+#else
+ SHA1HashValue fingerprint =
+ X509Certificate::CalculateFingerprint(root_ca);
+#endif
+ return metadata->HasEVPolicyOID(fingerprint, ev_policy_oid);
+}
+
+CERTCertList* CertificateListToCERTCertList(const CertificateList& list) {
+ CERTCertList* result = CERT_NewCertList();
+ for (size_t i = 0; i < list.size(); ++i) {
+#if defined(OS_IOS)
+ // X509Certificate::os_cert_handle() on iOS is a SecCertificateRef; convert
+ // it to an NSS CERTCertificate.
+ CERTCertificate* cert = x509_util_ios::CreateNSSCertHandleFromOSHandle(
+ list[i]->os_cert_handle());
+#else
+ CERTCertificate* cert = list[i]->os_cert_handle();
+#endif
+ CERT_AddCertToListTail(result, CERT_DupCertificate(cert));
+ }
+ return result;
+}
+
+} // namespace
+
+CertVerifyProcNSS::CertVerifyProcNSS() {}
+
+CertVerifyProcNSS::~CertVerifyProcNSS() {}
+
+bool CertVerifyProcNSS::SupportsAdditionalTrustAnchors() const {
+ // This requires APIs introduced in 3.14.2.
+ return NSS_VersionCheck("3.14.2");
+}
+
+int CertVerifyProcNSS::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+#if defined(OS_IOS)
+ // For iOS, the entire chain must be loaded into NSS's in-memory certificate
+ // store.
+ x509_util_ios::NSSCertChain scoped_chain(cert);
+ CERTCertificate* cert_handle = scoped_chain.cert_handle();
+#else
+ CERTCertificate* cert_handle = cert->os_cert_handle();
+#endif // defined(OS_IOS)
+
+ // Make sure that the hostname matches with the common name of the cert.
+ SECStatus status = CERT_VerifyCertName(cert_handle, hostname.c_str());
+ if (status != SECSuccess)
+ verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+
+ // Make sure that the cert is valid now.
+ SECCertTimeValidity validity = CERT_CheckCertValidTimes(
+ cert_handle, PR_Now(), PR_TRUE);
+ if (validity != secCertTimeValid)
+ verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
+
+ CERTValOutParam cvout[3];
+ int cvout_index = 0;
+ cvout[cvout_index].type = cert_po_certList;
+ cvout[cvout_index].value.pointer.chain = NULL;
+ int cvout_cert_list_index = cvout_index;
+ cvout_index++;
+ cvout[cvout_index].type = cert_po_trustAnchor;
+ cvout[cvout_index].value.pointer.cert = NULL;
+ int cvout_trust_anchor_index = cvout_index;
+ cvout_index++;
+ cvout[cvout_index].type = cert_po_end;
+ ScopedCERTValOutParam scoped_cvout(cvout);
+
+ EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance();
+ SECOidTag ev_policy_oid = SEC_OID_UNKNOWN;
+ bool is_ev_candidate =
+ (flags & CertVerifier::VERIFY_EV_CERT) &&
+ IsEVCandidate(metadata, cert_handle, &ev_policy_oid);
+ bool cert_io_enabled = flags & CertVerifier::VERIFY_CERT_IO_ENABLED;
+ bool check_revocation =
+ cert_io_enabled &&
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED);
+ if (check_revocation)
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+
+ ScopedCERTCertList trust_anchors;
+ if (SupportsAdditionalTrustAnchors() && !additional_trust_anchors.empty()) {
+ trust_anchors.reset(
+ CertificateListToCERTCertList(additional_trust_anchors));
+ }
+
+ status = PKIXVerifyCert(cert_handle, check_revocation, false,
+ cert_io_enabled, NULL, 0, trust_anchors.get(),
+ cvout);
+
+ if (status == SECSuccess &&
+ (flags & CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS) &&
+ !IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert)) {
+ // TODO(rsleevi): Optimize this by supplying the constructed chain to
+ // libpkix via cvin. Omitting for now, due to lack of coverage in upstream
+ // NSS tests for that feature.
+ scoped_cvout.Clear();
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ status = PKIXVerifyCert(cert_handle, true, true,
+ cert_io_enabled, NULL, 0, trust_anchors.get(),
+ cvout);
+ }
+
+ if (status == SECSuccess) {
+ AppendPublicKeyHashes(cvout[cvout_cert_list_index].value.pointer.chain,
+ cvout[cvout_trust_anchor_index].value.pointer.cert,
+ &verify_result->public_key_hashes);
+
+ verify_result->is_issued_by_known_root =
+ IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert);
+ verify_result->is_issued_by_additional_trust_anchor =
+ IsAdditionalTrustAnchor(
+ trust_anchors.get(),
+ cvout[cvout_trust_anchor_index].value.pointer.cert);
+
+ GetCertChainInfo(cvout[cvout_cert_list_index].value.pointer.chain,
+ cvout[cvout_trust_anchor_index].value.pointer.cert,
+ verify_result);
+ }
+
+ CRLSetResult crl_set_result = kCRLSetUnknown;
+ if (crl_set) {
+ crl_set_result = CheckRevocationWithCRLSet(
+ cvout[cvout_cert_list_index].value.pointer.chain,
+ cvout[cvout_trust_anchor_index].value.pointer.cert,
+ crl_set);
+ if (crl_set_result == kCRLSetRevoked) {
+ PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE);
+ status = SECFailure;
+ }
+ }
+
+ if (status != SECSuccess) {
+ int err = PORT_GetError();
+ LOG(ERROR) << "CERT_PKIXVerifyCert for " << hostname
+ << " failed err=" << err;
+ // CERT_PKIXVerifyCert rerports the wrong error code for
+ // expired certificates (NSS bug 491174)
+ if (err == SEC_ERROR_CERT_NOT_VALID &&
+ (verify_result->cert_status & CERT_STATUS_DATE_INVALID))
+ err = SEC_ERROR_EXPIRED_CERTIFICATE;
+ CertStatus cert_status = MapCertErrorToCertStatus(err);
+ if (cert_status) {
+ verify_result->cert_status |= cert_status;
+ return MapCertStatusToNetError(verify_result->cert_status);
+ }
+ // |err| is not a certificate error.
+ return MapSecurityError(err);
+ }
+
+ if (IsCertStatusError(verify_result->cert_status))
+ return MapCertStatusToNetError(verify_result->cert_status);
+
+ if ((flags & CertVerifier::VERIFY_EV_CERT) && is_ev_candidate) {
+ check_revocation |=
+ crl_set_result != kCRLSetOk &&
+ cert_io_enabled &&
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY);
+ if (check_revocation)
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+
+ if (VerifyEV(cert_handle, flags, crl_set, check_revocation, metadata,
+ ev_policy_oid, trust_anchors.get())) {
+ verify_result->cert_status |= CERT_STATUS_IS_EV;
+ }
+ }
+
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_nss.h b/chromium/net/cert/cert_verify_proc_nss.h
new file mode 100644
index 00000000000..f8bb853544e
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_nss.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_NSS_H_
+#define NET_CERT_CERT_VERIFY_PROC_NSS_H_
+
+#include "net/base/net_export.h"
+#include "net/cert/cert_verify_proc.h"
+
+namespace net {
+
+// Performs certificate path construction and validation using NSS's libpkix.
+class NET_EXPORT_PRIVATE CertVerifyProcNSS : public CertVerifyProc {
+ public:
+ CertVerifyProcNSS();
+
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE;
+
+ protected:
+ virtual ~CertVerifyProcNSS();
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_NSS_H_
diff --git a/chromium/net/cert/cert_verify_proc_openssl.cc b/chromium/net/cert/cert_verify_proc_openssl.cc
new file mode 100644
index 00000000000..a67f194662d
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_openssl.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc_openssl.h"
+
+#include <openssl/x509v3.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "crypto/openssl_util.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+// Maps X509_STORE_CTX_get_error() return values to our cert status flags.
+CertStatus MapCertErrorToCertStatus(int err) {
+ switch (err) {
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ return CERT_STATUS_COMMON_NAME_INVALID;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_CRL_NOT_YET_VALID:
+ case X509_V_ERR_CRL_HAS_EXPIRED:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+ return CERT_STATUS_DATE_INVALID;
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ case X509_V_ERR_UNABLE_TO_GET_CRL:
+ case X509_V_ERR_INVALID_CA:
+ case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
+ case X509_V_ERR_INVALID_NON_CA:
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ return CERT_STATUS_AUTHORITY_INVALID;
+#if 0
+// TODO(bulach): what should we map to these status?
+ return CERT_STATUS_NO_REVOCATION_MECHANISM;
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+#endif
+ case X509_V_ERR_CERT_REVOKED:
+ return CERT_STATUS_REVOKED;
+ // All these status are mapped to CERT_STATUS_INVALID.
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+ case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+ case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+ case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+ case X509_V_ERR_OUT_OF_MEM:
+ case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+ case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+ case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+ case X509_V_ERR_INVALID_PURPOSE:
+ case X509_V_ERR_CERT_UNTRUSTED:
+ case X509_V_ERR_CERT_REJECTED:
+ case X509_V_ERR_AKID_SKID_MISMATCH:
+ case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+ case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN:
+ case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION:
+ case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED:
+ case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE:
+ case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED:
+ case X509_V_ERR_INVALID_EXTENSION:
+ case X509_V_ERR_INVALID_POLICY_EXTENSION:
+ case X509_V_ERR_NO_EXPLICIT_POLICY:
+ case X509_V_ERR_UNNESTED_RESOURCE:
+ case X509_V_ERR_APPLICATION_VERIFICATION:
+ return CERT_STATUS_INVALID;
+ default:
+ NOTREACHED() << "Invalid X509 err " << err;
+ return CERT_STATUS_INVALID;
+ }
+}
+
+// sk_X509_free is a function-style macro, so can't be used as a template
+// param directly.
+void sk_X509_free_fn(STACK_OF(X509)* st) {
+ sk_X509_free(st);
+}
+
+void GetCertChainInfo(X509_STORE_CTX* store_ctx,
+ CertVerifyResult* verify_result) {
+ STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx);
+ X509* verified_cert = NULL;
+ std::vector<X509*> verified_chain;
+ for (int i = 0; i < sk_X509_num(chain); ++i) {
+ X509* cert = sk_X509_value(chain, i);
+ if (i == 0) {
+ verified_cert = cert;
+ } else {
+ verified_chain.push_back(cert);
+ }
+
+ // Only check the algorithm status for certificates that are not in the
+ // trust store.
+ if (i < store_ctx->last_untrusted) {
+ int sig_alg = OBJ_obj2nid(cert->sig_alg->algorithm);
+ if (sig_alg == NID_md2WithRSAEncryption) {
+ verify_result->has_md2 = true;
+ } else if (sig_alg == NID_md4WithRSAEncryption) {
+ verify_result->has_md4 = true;
+ } else if (sig_alg == NID_md5WithRSAEncryption) {
+ verify_result->has_md5 = true;
+ }
+ }
+ }
+
+ if (verified_cert) {
+ verify_result->verified_cert =
+ X509Certificate::CreateFromHandle(verified_cert, verified_chain);
+ }
+}
+
+void AppendPublicKeyHashes(X509_STORE_CTX* store_ctx,
+ HashValueVector* hashes) {
+ STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx);
+ for (int i = 0; i < sk_X509_num(chain); ++i) {
+ X509* cert = sk_X509_value(chain, i);
+
+ std::string der_data;
+ if (!X509Certificate::GetDEREncoded(cert, &der_data))
+ continue;
+
+ base::StringPiece der_bytes(der_data);
+ base::StringPiece spki_bytes;
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes))
+ continue;
+
+ HashValue sha1(HASH_VALUE_SHA1);
+ base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()),
+ spki_bytes.size(), sha1.data());
+ hashes->push_back(sha1);
+
+ HashValue sha256(HASH_VALUE_SHA256);
+ crypto::SHA256HashString(spki_bytes, sha256.data(), crypto::kSHA256Length);
+ hashes->push_back(sha256);
+ }
+}
+
+} // namespace
+
+CertVerifyProcOpenSSL::CertVerifyProcOpenSSL() {}
+
+CertVerifyProcOpenSSL::~CertVerifyProcOpenSSL() {}
+
+bool CertVerifyProcOpenSSL::SupportsAdditionalTrustAnchors() const {
+ return false;
+}
+
+int CertVerifyProcOpenSSL::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ crypto::EnsureOpenSSLInit();
+
+ if (!cert->VerifyNameMatch(hostname))
+ verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+
+ crypto::ScopedOpenSSL<X509_STORE_CTX, X509_STORE_CTX_free> ctx(
+ X509_STORE_CTX_new());
+
+ crypto::ScopedOpenSSL<STACK_OF(X509), sk_X509_free_fn> intermediates(
+ sk_X509_new_null());
+ if (!intermediates.get())
+ return ERR_OUT_OF_MEMORY;
+
+ const X509Certificate::OSCertHandles& os_intermediates =
+ cert->GetIntermediateCertificates();
+ for (X509Certificate::OSCertHandles::const_iterator it =
+ os_intermediates.begin(); it != os_intermediates.end(); ++it) {
+ if (!sk_X509_push(intermediates.get(), *it))
+ return ERR_OUT_OF_MEMORY;
+ }
+ if (X509_STORE_CTX_init(ctx.get(), X509Certificate::cert_store(),
+ cert->os_cert_handle(), intermediates.get()) != 1) {
+ NOTREACHED();
+ return ERR_FAILED;
+ }
+
+ if (X509_verify_cert(ctx.get()) != 1) {
+ int x509_error = X509_STORE_CTX_get_error(ctx.get());
+ CertStatus cert_status = MapCertErrorToCertStatus(x509_error);
+ LOG(ERROR) << "X509 Verification error "
+ << X509_verify_cert_error_string(x509_error)
+ << " : " << x509_error
+ << " : " << X509_STORE_CTX_get_error_depth(ctx.get())
+ << " : " << cert_status;
+ verify_result->cert_status |= cert_status;
+ }
+
+ GetCertChainInfo(ctx.get(), verify_result);
+ AppendPublicKeyHashes(ctx.get(), &verify_result->public_key_hashes);
+ if (IsCertStatusError(verify_result->cert_status))
+ return MapCertStatusToNetError(verify_result->cert_status);
+
+ // Currently we only ues OpenSSL's default root CA paths, so treat all
+ // correctly verified certs as being from a known root.
+ // TODO(joth): if the motivations described in
+ // http://src.chromium.org/viewvc/chrome?view=rev&revision=80778 become an
+ // issue on OpenSSL builds, we will need to embed a hardcoded list of well
+ // known root CAs, as per the _mac and _win versions.
+ verify_result->is_issued_by_known_root = true;
+
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_openssl.h b/chromium/net/cert/cert_verify_proc_openssl.h
new file mode 100644
index 00000000000..d0d25746bf9
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_openssl.h
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_
+#define NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_
+
+#include "net/cert/cert_verify_proc.h"
+
+namespace net {
+
+// Performs certificate path construction and validation using OpenSSL.
+class CertVerifyProcOpenSSL : public CertVerifyProc {
+ public:
+ CertVerifyProcOpenSSL();
+
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE;
+
+ protected:
+ virtual ~CertVerifyProcOpenSSL();
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_OPENSSL_H_
diff --git a/chromium/net/cert/cert_verify_proc_unittest.cc b/chromium/net/cert/cert_verify_proc_unittest.cc
new file mode 100644
index 00000000000..a53d10a0845
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_unittest.cc
@@ -0,0 +1,1359 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc.h"
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_certificate_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+#include "base/mac/mac_util.h"
+#endif
+
+using base::HexEncode;
+
+namespace net {
+
+namespace {
+
+// A certificate for www.paypal.com with a NULL byte in the common name.
+// From http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/70363
+unsigned char paypal_null_fingerprint[] = {
+ 0x4c, 0x88, 0x9e, 0x28, 0xd7, 0x7a, 0x44, 0x1e, 0x13, 0xf2, 0x6a, 0xba,
+ 0x1f, 0xe8, 0x1b, 0xd6, 0xab, 0x7b, 0xe8, 0xd7
+};
+
+// Mock CertVerifyProc that will set |verify_result->is_issued_by_known_root|
+// for all certificates that are Verified.
+class WellKnownCaCertVerifyProc : public CertVerifyProc {
+ public:
+ // Initialize a CertVerifyProc that will set
+ // |verify_result->is_issued_by_known_root| to |is_well_known|.
+ explicit WellKnownCaCertVerifyProc(bool is_well_known)
+ : is_well_known_(is_well_known) {}
+
+ // CertVerifyProc implementation:
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE { return false; }
+
+ protected:
+ virtual ~WellKnownCaCertVerifyProc() {}
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+
+ const bool is_well_known_;
+
+ DISALLOW_COPY_AND_ASSIGN(WellKnownCaCertVerifyProc);
+};
+
+int WellKnownCaCertVerifyProc::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ verify_result->is_issued_by_known_root = is_well_known_;
+ return OK;
+}
+
+} // namespace
+
+class CertVerifyProcTest : public testing::Test {
+ public:
+ CertVerifyProcTest()
+ : verify_proc_(CertVerifyProc::CreateDefault()) {
+ }
+ virtual ~CertVerifyProcTest() {}
+
+ protected:
+ bool SupportsAdditionalTrustAnchors() {
+ return verify_proc_->SupportsAdditionalTrustAnchors();
+ }
+
+ int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ return verify_proc_->Verify(cert, hostname, flags, crl_set,
+ additional_trust_anchors, verify_result);
+ }
+
+ const CertificateList empty_cert_list_;
+ scoped_refptr<CertVerifyProc> verify_proc_;
+};
+
+TEST_F(CertVerifyProcTest, WithoutRevocationChecking) {
+ // Check that verification without revocation checking works.
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(),
+ "googlenew.chain.pem",
+ X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> google_full_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ EXPECT_EQ(OK,
+ Verify(google_full_chain.get(),
+ "www.google.com",
+ 0 /* flags */,
+ NULL,
+ empty_cert_list_,
+ &verify_result));
+}
+
+#if defined(OS_ANDROID) || defined(USE_OPENSSL)
+// TODO(jnd): http://crbug.com/117478 - EV verification is not yet supported.
+#define MAYBE_EVVerification DISABLED_EVVerification
+#else
+#define MAYBE_EVVerification EVVerification
+#endif
+TEST_F(CertVerifyProcTest, MAYBE_EVVerification) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(),
+ "comodo.chain.pem",
+ X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+ ASSERT_EQ(3U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+ intermediates.push_back(certs[2]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> comodo_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+
+ scoped_refptr<CRLSet> crl_set(CRLSet::ForTesting(false, NULL, ""));
+ CertVerifyResult verify_result;
+ int flags = CertVerifier::VERIFY_EV_CERT;
+ int error = Verify(comodo_chain.get(),
+ "comodo.com",
+ flags,
+ crl_set.get(),
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
+}
+
+TEST_F(CertVerifyProcTest, PaypalNullCertParsing) {
+ scoped_refptr<X509Certificate> paypal_null_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(paypal_null_der),
+ sizeof(paypal_null_der)));
+
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), paypal_null_cert);
+
+ const SHA1HashValue& fingerprint =
+ paypal_null_cert->fingerprint();
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(paypal_null_fingerprint[i], fingerprint.data[i]);
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = Verify(paypal_null_cert.get(),
+ "www.paypal.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+#if defined(USE_NSS) || defined(OS_IOS) || defined(OS_ANDROID)
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+#else
+ // TOOD(bulach): investigate why macosx and win aren't returning
+ // ERR_CERT_INVALID or ERR_CERT_COMMON_NAME_INVALID.
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+#endif
+ // Either the system crypto library should correctly report a certificate
+ // name mismatch, or our certificate blacklist should cause us to report an
+ // invalid certificate.
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_IOS)
+ EXPECT_TRUE(verify_result.cert_status &
+ (CERT_STATUS_COMMON_NAME_INVALID | CERT_STATUS_INVALID));
+#endif
+}
+
+// A regression test for http://crbug.com/31497.
+#if defined(OS_ANDROID)
+// Disabled on Android, as the Android verification libraries require an
+// explicit policy to be specified, even when anyPolicy is permitted.
+#define MAYBE_IntermediateCARequireExplicitPolicy \
+ DISABLED_IntermediateCARequireExplicitPolicy
+#else
+#define MAYBE_IntermediateCARequireExplicitPolicy \
+ IntermediateCARequireExplicitPolicy
+#endif
+TEST_F(CertVerifyProcTest, MAYBE_IntermediateCARequireExplicitPolicy) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "explicit-policy-chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> cert =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ ASSERT_TRUE(cert.get());
+
+ ScopedTestRoot scoped_root(certs[2].get());
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = Verify(cert.get(),
+ "policy_test.example",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0u, verify_result.cert_status);
+}
+
+
+// Test for bug 58437.
+// This certificate will expire on 2011-12-21. The test will still
+// pass if error == ERR_CERT_DATE_INVALID.
+// This test is DISABLED because it appears that we cannot do
+// certificate revocation checking when running all of the net unit tests.
+// This test passes when run individually, but when run with all of the net
+// unit tests, the call to PKIXVerifyCert returns the NSS error -8180, which is
+// SEC_ERROR_REVOKED_CERTIFICATE. This indicates a lack of revocation
+// status, i.e. that the revocation check is failing for some reason.
+TEST_F(CertVerifyProcTest, DISABLED_GlobalSignR3EVTest) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "2029_globalsign_com_cert.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ scoped_refptr<X509Certificate> intermediate_cert =
+ ImportCertFromFile(certs_dir, "globalsign_ev_sha256_ca_cert.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ int flags = CertVerifier::VERIFY_REV_CHECKING_ENABLED |
+ CertVerifier::VERIFY_EV_CERT;
+ int error = Verify(cert_chain.get(),
+ "2029.globalsign.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ if (error == OK)
+ EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV);
+ else
+ EXPECT_EQ(ERR_CERT_DATE_INVALID, error);
+}
+
+// Test that verifying an ECDSA certificate doesn't crash on XP. (See
+// crbug.com/144466).
+TEST_F(CertVerifyProcTest, ECDSA_RSA) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> cert =
+ ImportCertFromFile(certs_dir,
+ "prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem");
+
+ CertVerifyResult verify_result;
+ Verify(cert.get(), "127.0.0.1", 0, NULL, empty_cert_list_, &verify_result);
+
+ // We don't check verify_result because the certificate is signed by an
+ // unknown CA and will be considered invalid on XP because of the ECDSA
+ // public key.
+}
+
+// Currently, only RSA and DSA keys are checked for weakness, and our example
+// weak size is 768. These could change in the future.
+//
+// Note that this means there may be false negatives: keys for other
+// algorithms and which are weak will pass this test.
+static bool IsWeakKeyType(const std::string& key_type) {
+ size_t pos = key_type.find("-");
+ std::string size = key_type.substr(0, pos);
+ std::string type = key_type.substr(pos + 1);
+
+ if (type == "rsa" || type == "dsa")
+ return size == "768";
+
+ return false;
+}
+
+TEST_F(CertVerifyProcTest, RejectWeakKeys) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ typedef std::vector<std::string> Strings;
+ Strings key_types;
+
+ // generate-weak-test-chains.sh currently has:
+ // key_types="768-rsa 1024-rsa 2048-rsa prime256v1-ecdsa"
+ // We must use the same key types here. The filenames generated look like:
+ // 2048-rsa-ee-by-768-rsa-intermediate.pem
+ key_types.push_back("768-rsa");
+ key_types.push_back("1024-rsa");
+ key_types.push_back("2048-rsa");
+
+ bool use_ecdsa = true;
+#if defined(OS_WIN)
+ use_ecdsa = base::win::GetVersion() > base::win::VERSION_XP;
+#endif
+
+ if (use_ecdsa)
+ key_types.push_back("prime256v1-ecdsa");
+
+ // Add the root that signed the intermediates for this test.
+ scoped_refptr<X509Certificate> root_cert =
+ ImportCertFromFile(certs_dir, "2048-rsa-root.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ ScopedTestRoot scoped_root(root_cert.get());
+
+ // Now test each chain.
+ for (Strings::const_iterator ee_type = key_types.begin();
+ ee_type != key_types.end(); ++ee_type) {
+ for (Strings::const_iterator signer_type = key_types.begin();
+ signer_type != key_types.end(); ++signer_type) {
+ std::string basename = *ee_type + "-ee-by-" + *signer_type +
+ "-intermediate.pem";
+ SCOPED_TRACE(basename);
+ scoped_refptr<X509Certificate> ee_cert =
+ ImportCertFromFile(certs_dir, basename);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert);
+
+ basename = *signer_type + "-intermediate.pem";
+ scoped_refptr<X509Certificate> intermediate =
+ ImportCertFromFile(certs_dir, basename);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ int error = Verify(cert_chain.get(),
+ "127.0.0.1",
+ 0,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+
+ if (IsWeakKeyType(*ee_type) || IsWeakKeyType(*signer_type)) {
+ EXPECT_NE(OK, error);
+ EXPECT_EQ(CERT_STATUS_WEAK_KEY,
+ verify_result.cert_status & CERT_STATUS_WEAK_KEY);
+ EXPECT_NE(CERT_STATUS_INVALID,
+ verify_result.cert_status & CERT_STATUS_INVALID);
+ } else {
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status & CERT_STATUS_WEAK_KEY);
+ }
+ }
+ }
+}
+
+// Regression test for http://crbug.com/108514.
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Disabled on OS X - Security.framework doesn't ignore superflous certificates
+// provided by servers. See CertVerifyProcTest.CybertrustGTERoot for further
+// details.
+#define MAYBE_ExtraneousMD5RootCert DISABLED_ExtraneousMD5RootCert
+#elif defined(USE_OPENSSL) || defined(OS_ANDROID)
+// Disabled for OpenSSL / Android - Android and OpenSSL do not attempt to find
+// a minimal certificate chain, thus prefer the MD5 root over the SHA-1 root.
+#define MAYBE_ExtraneousMD5RootCert DISABLED_ExtraneousMD5RootCert
+#else
+#define MAYBE_ExtraneousMD5RootCert ExtraneousMD5RootCert
+#endif
+TEST_F(CertVerifyProcTest, MAYBE_ExtraneousMD5RootCert) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "cross-signed-leaf.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert.get());
+
+ scoped_refptr<X509Certificate> extra_cert =
+ ImportCertFromFile(certs_dir, "cross-signed-root-md5.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), extra_cert.get());
+
+ scoped_refptr<X509Certificate> root_cert =
+ ImportCertFromFile(certs_dir, "cross-signed-root-sha1.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert.get());
+
+ ScopedTestRoot scoped_root(root_cert.get());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(extra_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ int flags = 0;
+ int error = Verify(cert_chain.get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+
+ // The extra MD5 root should be discarded
+ ASSERT_TRUE(verify_result.verified_cert.get());
+ ASSERT_EQ(1u,
+ verify_result.verified_cert->GetIntermediateCertificates().size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(
+ verify_result.verified_cert->GetIntermediateCertificates().front(),
+ root_cert->os_cert_handle()));
+
+ EXPECT_FALSE(verify_result.has_md5);
+}
+
+// Test for bug 94673.
+TEST_F(CertVerifyProcTest, GoogleDigiNotarTest) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "google_diginotar.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ scoped_refptr<X509Certificate> intermediate_cert =
+ ImportCertFromFile(certs_dir, "diginotar_public_ca_2025.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ int flags = CertVerifier::VERIFY_REV_CHECKING_ENABLED;
+ int error = Verify(cert_chain.get(),
+ "mail.google.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_NE(OK, error);
+
+ // Now turn off revocation checking. Certificate verification should still
+ // fail.
+ flags = 0;
+ error = Verify(cert_chain.get(),
+ "mail.google.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_NE(OK, error);
+}
+
+TEST_F(CertVerifyProcTest, DigiNotarCerts) {
+ static const char* const kDigiNotarFilenames[] = {
+ "diginotar_root_ca.pem",
+ "diginotar_cyber_ca.pem",
+ "diginotar_services_1024_ca.pem",
+ "diginotar_pkioverheid.pem",
+ "diginotar_pkioverheid_g2.pem",
+ NULL,
+ };
+
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ for (size_t i = 0; kDigiNotarFilenames[i]; i++) {
+ scoped_refptr<X509Certificate> diginotar_cert =
+ ImportCertFromFile(certs_dir, kDigiNotarFilenames[i]);
+ std::string der_bytes;
+ ASSERT_TRUE(X509Certificate::GetDEREncoded(
+ diginotar_cert->os_cert_handle(), &der_bytes));
+
+ base::StringPiece spki;
+ ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_bytes, &spki));
+
+ std::string spki_sha1 = base::SHA1HashString(spki.as_string());
+
+ HashValueVector public_keys;
+ HashValue hash(HASH_VALUE_SHA1);
+ ASSERT_EQ(hash.size(), spki_sha1.size());
+ memcpy(hash.data(), spki_sha1.data(), spki_sha1.size());
+ public_keys.push_back(hash);
+
+ EXPECT_TRUE(CertVerifyProc::IsPublicKeyBlacklisted(public_keys)) <<
+ "Public key not blocked for " << kDigiNotarFilenames[i];
+ }
+}
+
+// The certse.pem certificate has been revoked. crbug.com/259723.
+TEST_F(CertVerifyProcTest, TestKnownRoot) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "satveda.pem", X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(2U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ // This will blow up, May 24th, 2019. Sorry! Please disable and file a bug
+ // against agl. See also PublicKeyHashes.
+ int error = Verify(cert_chain.get(),
+ "satveda.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+ EXPECT_TRUE(verify_result.is_issued_by_known_root);
+}
+
+// The certse.pem certificate has been revoked. crbug.com/259723.
+TEST_F(CertVerifyProcTest, PublicKeyHashes) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "satveda.pem", X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(2U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ int flags = 0;
+ CertVerifyResult verify_result;
+
+ // This will blow up, May 24th, 2019. Sorry! Please disable and file a bug
+ // against agl. See also TestKnownRoot.
+ int error = Verify(cert_chain.get(),
+ "satveda.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+ ASSERT_LE(2U, verify_result.public_key_hashes.size());
+
+ HashValueVector sha1_hashes;
+ for (size_t i = 0; i < verify_result.public_key_hashes.size(); ++i) {
+ if (verify_result.public_key_hashes[i].tag != HASH_VALUE_SHA1)
+ continue;
+ sha1_hashes.push_back(verify_result.public_key_hashes[i]);
+ }
+ ASSERT_LE(2u, sha1_hashes.size());
+
+ for (size_t i = 0; i < 2; ++i) {
+ EXPECT_EQ(HexEncode(kSatvedaSPKIs[i], base::kSHA1Length),
+ HexEncode(sha1_hashes[i].data(), base::kSHA1Length));
+ }
+
+ HashValueVector sha256_hashes;
+ for (size_t i = 0; i < verify_result.public_key_hashes.size(); ++i) {
+ if (verify_result.public_key_hashes[i].tag != HASH_VALUE_SHA256)
+ continue;
+ sha256_hashes.push_back(verify_result.public_key_hashes[i]);
+ }
+ ASSERT_LE(2u, sha256_hashes.size());
+
+ for (size_t i = 0; i < 2; ++i) {
+ EXPECT_EQ(HexEncode(kSatvedaSPKIsSHA256[i], crypto::kSHA256Length),
+ HexEncode(sha256_hashes[i].data(), crypto::kSHA256Length));
+ }
+}
+
+// A regression test for http://crbug.com/70293.
+// The Key Usage extension in this RSA SSL server certificate does not have
+// the keyEncipherment bit.
+TEST_F(CertVerifyProcTest, InvalidKeyUsage) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "invalid_key_usage_cert.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = Verify(server_cert.get(),
+ "jira.aquameta.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+#if defined(USE_OPENSSL) && !defined(OS_ANDROID)
+ // This certificate has two errors: "invalid key usage" and "untrusted CA".
+ // However, OpenSSL returns only one (the latter), and we can't detect
+ // the other errors.
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+#else
+ EXPECT_EQ(ERR_CERT_INVALID, error);
+ EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID);
+#endif
+ // TODO(wtc): fix http://crbug.com/75520 to get all the certificate errors
+ // from NSS.
+#if !defined(USE_NSS) && !defined(OS_IOS) && !defined(OS_ANDROID)
+ // The certificate is issued by an unknown CA.
+ EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
+#endif
+}
+
+// Basic test for returning the chain in CertVerifyResult. Note that the
+// returned chain may just be a reflection of the originally supplied chain;
+// that is, if any errors occur, the default chain returned is an exact copy
+// of the certificate to be verified. The remaining VerifyReturn* tests are
+// used to ensure that the actual, verified chain is being returned by
+// Verify().
+TEST_F(CertVerifyProcTest, VerifyReturnChainBasic) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "x509_verify_results.chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+ intermediates.push_back(certs[2]->os_cert_handle());
+
+ ScopedTestRoot scoped_root(certs[2].get());
+
+ scoped_refptr<X509Certificate> google_full_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain);
+ ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size());
+
+ CertVerifyResult verify_result;
+ EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+ int error = Verify(google_full_chain.get(),
+ "127.0.0.1",
+ 0,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+
+ EXPECT_NE(google_full_chain, verify_result.verified_cert);
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(
+ google_full_chain->os_cert_handle(),
+ verify_result.verified_cert->os_cert_handle()));
+ const X509Certificate::OSCertHandles& return_intermediates =
+ verify_result.verified_cert->GetIntermediateCertificates();
+ ASSERT_EQ(2U, return_intermediates.size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0],
+ certs[1]->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1],
+ certs[2]->os_cert_handle()));
+}
+
+// Test that certificates issued for 'intranet' names (that is, containing no
+// known public registry controlled domain information) issued by well-known
+// CAs are flagged appropriately, while certificates that are issued by
+// internal CAs are not flagged.
+TEST_F(CertVerifyProcTest, IntranetHostsRejected) {
+ CertificateList cert_list = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "ok_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+
+ CertVerifyResult verify_result;
+ int error = 0;
+
+ // Intranet names for public CAs should be flagged:
+ verify_proc_ = new WellKnownCaCertVerifyProc(true);
+ error =
+ Verify(cert.get(), "intranet", 0, NULL, empty_cert_list_, &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME);
+
+ // However, if the CA is not well known, these should not be flagged:
+ verify_proc_ = new WellKnownCaCertVerifyProc(false);
+ error =
+ Verify(cert.get(), "intranet", 0, NULL, empty_cert_list_, &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME);
+}
+
+// Test that the certificate returned in CertVerifyResult is able to reorder
+// certificates that are not ordered from end-entity to root. While this is
+// a protocol violation if sent during a TLS handshake, if multiple sources
+// of intermediate certificates are combined, it's possible that order may
+// not be maintained.
+TEST_F(CertVerifyProcTest, VerifyReturnChainProperlyOrdered) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "x509_verify_results.chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3U, certs.size());
+
+ // Construct the chain out of order.
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[2]->os_cert_handle());
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ ScopedTestRoot scoped_root(certs[2].get());
+
+ scoped_refptr<X509Certificate> google_full_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain);
+ ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size());
+
+ CertVerifyResult verify_result;
+ EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+ int error = Verify(google_full_chain.get(),
+ "127.0.0.1",
+ 0,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+
+ EXPECT_NE(google_full_chain, verify_result.verified_cert);
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(
+ google_full_chain->os_cert_handle(),
+ verify_result.verified_cert->os_cert_handle()));
+ const X509Certificate::OSCertHandles& return_intermediates =
+ verify_result.verified_cert->GetIntermediateCertificates();
+ ASSERT_EQ(2U, return_intermediates.size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0],
+ certs[1]->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1],
+ certs[2]->os_cert_handle()));
+}
+
+// Test that Verify() filters out certificates which are not related to
+// or part of the certificate chain being verified.
+TEST_F(CertVerifyProcTest, VerifyReturnChainFiltersUnrelatedCerts) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, "x509_verify_results.chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3U, certs.size());
+ ScopedTestRoot scoped_root(certs[2].get());
+
+ scoped_refptr<X509Certificate> unrelated_certificate =
+ ImportCertFromFile(certs_dir, "duplicate_cn_1.pem");
+ scoped_refptr<X509Certificate> unrelated_certificate2 =
+ ImportCertFromFile(certs_dir, "aia-cert.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_certificate);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_certificate2);
+
+ // Interject unrelated certificates into the list of intermediates.
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(unrelated_certificate->os_cert_handle());
+ intermediates.push_back(certs[1]->os_cert_handle());
+ intermediates.push_back(unrelated_certificate2->os_cert_handle());
+ intermediates.push_back(certs[2]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> google_full_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain);
+ ASSERT_EQ(4U, google_full_chain->GetIntermediateCertificates().size());
+
+ CertVerifyResult verify_result;
+ EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+ int error = Verify(google_full_chain.get(),
+ "127.0.0.1",
+ 0,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert);
+
+ EXPECT_NE(google_full_chain, verify_result.verified_cert);
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(
+ google_full_chain->os_cert_handle(),
+ verify_result.verified_cert->os_cert_handle()));
+ const X509Certificate::OSCertHandles& return_intermediates =
+ verify_result.verified_cert->GetIntermediateCertificates();
+ ASSERT_EQ(2U, return_intermediates.size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0],
+ certs[1]->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1],
+ certs[2]->os_cert_handle()));
+}
+
+TEST_F(CertVerifyProcTest, AdditionalTrustAnchors) {
+ if (!SupportsAdditionalTrustAnchors()) {
+ LOG(INFO) << "Skipping this test in this platform.";
+ return;
+ }
+
+ // |ca_cert| is the issuer of |cert|.
+ CertificateList ca_cert_list = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_cert_list.size());
+ scoped_refptr<X509Certificate> ca_cert(ca_cert_list[0]);
+
+ CertificateList cert_list = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "ok_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+
+ // Verification of |cert| fails when |ca_cert| is not in the trust anchors
+ // list.
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = Verify(
+ cert.get(), "127.0.0.1", flags, NULL, empty_cert_list_, &verify_result);
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status);
+ EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor);
+
+ // Now add the |ca_cert| to the |trust_anchors|, and verification should pass.
+ CertificateList trust_anchors;
+ trust_anchors.push_back(ca_cert);
+ error = Verify(
+ cert.get(), "127.0.0.1", flags, NULL, trust_anchors, &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+ EXPECT_TRUE(verify_result.is_issued_by_additional_trust_anchor);
+
+ // Clearing the |trust_anchors| makes verification fail again (the cache
+ // should be skipped).
+ error = Verify(
+ cert.get(), "127.0.0.1", flags, NULL, empty_cert_list_, &verify_result);
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status);
+ EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor);
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Tests that, on OS X, issues with a cross-certified Baltimore CyberTrust
+// Root can be successfully worked around once Apple completes removing the
+// older GTE CyberTrust Root from its trusted root store.
+//
+// The issue is caused by servers supplying the cross-certified intermediate
+// (necessary for certain mobile platforms), which OS X does not recognize
+// as already existing within its trust store.
+TEST_F(CertVerifyProcTest, CybertrustGTERoot) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(),
+ "cybertrust_omniroot_chain.pem",
+ X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+ ASSERT_EQ(2U, certs.size());
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> cybertrust_basic =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+ ASSERT_TRUE(cybertrust_basic.get());
+
+ scoped_refptr<X509Certificate> baltimore_root =
+ ImportCertFromFile(GetTestCertsDirectory(),
+ "cybertrust_baltimore_root.pem");
+ ASSERT_TRUE(baltimore_root.get());
+
+ ScopedTestRoot scoped_root(baltimore_root.get());
+
+ // Ensure that ONLY the Baltimore CyberTrust Root is trusted. This
+ // simulates Keychain removing support for the GTE CyberTrust Root.
+ TestRootCerts::GetInstance()->SetAllowSystemTrust(false);
+ base::ScopedClosureRunner reset_system_trust(
+ base::Bind(&TestRootCerts::SetAllowSystemTrust,
+ base::Unretained(TestRootCerts::GetInstance()),
+ true));
+
+ // First, make sure a simple certificate chain from
+ // EE -> Public SureServer SV -> Baltimore CyberTrust
+ // works. Only the first two certificates are included in the chain.
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = Verify(cybertrust_basic.get(),
+ "cacert.omniroot.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Attempt to verify with the first known cross-certified intermediate
+ // provided.
+ scoped_refptr<X509Certificate> baltimore_intermediate_1 =
+ ImportCertFromFile(GetTestCertsDirectory(),
+ "cybertrust_baltimore_cross_certified_1.pem");
+ ASSERT_TRUE(baltimore_intermediate_1.get());
+
+ X509Certificate::OSCertHandles intermediate_chain_1 =
+ cybertrust_basic->GetIntermediateCertificates();
+ intermediate_chain_1.push_back(baltimore_intermediate_1->os_cert_handle());
+
+ scoped_refptr<X509Certificate> baltimore_chain_1 =
+ X509Certificate::CreateFromHandle(cybertrust_basic->os_cert_handle(),
+ intermediate_chain_1);
+ error = Verify(baltimore_chain_1.get(),
+ "cacert.omniroot.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Attempt to verify with the second known cross-certified intermediate
+ // provided.
+ scoped_refptr<X509Certificate> baltimore_intermediate_2 =
+ ImportCertFromFile(GetTestCertsDirectory(),
+ "cybertrust_baltimore_cross_certified_2.pem");
+ ASSERT_TRUE(baltimore_intermediate_2.get());
+
+ X509Certificate::OSCertHandles intermediate_chain_2 =
+ cybertrust_basic->GetIntermediateCertificates();
+ intermediate_chain_2.push_back(baltimore_intermediate_2->os_cert_handle());
+
+ scoped_refptr<X509Certificate> baltimore_chain_2 =
+ X509Certificate::CreateFromHandle(cybertrust_basic->os_cert_handle(),
+ intermediate_chain_2);
+ error = Verify(baltimore_chain_2.get(),
+ "cacert.omniroot.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Attempt to verify when both a cross-certified intermediate AND
+ // the legacy GTE root are provided.
+ scoped_refptr<X509Certificate> cybertrust_root =
+ ImportCertFromFile(GetTestCertsDirectory(),
+ "cybertrust_gte_root.pem");
+ ASSERT_TRUE(cybertrust_root.get());
+
+ intermediate_chain_2.push_back(cybertrust_root->os_cert_handle());
+ scoped_refptr<X509Certificate> baltimore_chain_with_root =
+ X509Certificate::CreateFromHandle(cybertrust_basic->os_cert_handle(),
+ intermediate_chain_2);
+ error = Verify(baltimore_chain_with_root.get(),
+ "cacert.omniroot.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+}
+#endif
+
+#if defined(USE_NSS) || defined(OS_IOS) || defined(OS_WIN) || defined(OS_MACOSX)
+static const uint8 kCRLSetThawteSPKIBlocked[] = {
+ 0x8e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x36, 0x58, 0x36, 0x4d, 0x78, 0x52, 0x37,
+ 0x58, 0x70, 0x4d, 0x51, 0x4b, 0x78, 0x49, 0x41, 0x39, 0x50, 0x6a, 0x36, 0x37,
+ 0x36, 0x38, 0x76, 0x74, 0x55, 0x6b, 0x6b, 0x7a, 0x48, 0x79, 0x7a, 0x41, 0x6f,
+ 0x6d, 0x6f, 0x4f, 0x68, 0x4b, 0x55, 0x6e, 0x7a, 0x73, 0x55, 0x3d, 0x22, 0x5d,
+ 0x7d,
+};
+
+static const uint8 kCRLSetThawteSerialBlocked[] = {
+ 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xb1, 0x12, 0x41, 0x42, 0xa5, 0xa1,
+ 0xa5, 0xa2, 0x88, 0x19, 0xc7, 0x35, 0x34, 0x0e, 0xff, 0x8c, 0x9e, 0x2f, 0x81,
+ 0x68, 0xfe, 0xe3, 0xba, 0x18, 0x7f, 0x25, 0x3b, 0xc1, 0xa3, 0x92, 0xd7, 0xe2,
+ // Note that this is actually blocking two serial numbers because on XP and
+ // Vista, CryptoAPI finds a different Thawte certificate.
+ 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x30, 0x00, 0x00, 0x02,
+ 0x04, 0x30, 0x00, 0x00, 0x06,
+};
+
+static const uint8 kCRLSetGoogleSerialBlocked[] = {
+ 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xe9, 0x7e, 0x8c, 0xc5, 0x1e, 0xd7,
+ 0xa4, 0xc4, 0x0a, 0xc4, 0x80, 0x3d, 0x3e, 0x3e, 0xbb, 0xeb, 0xcb, 0xed, 0x52,
+ 0x49, 0x33, 0x1f, 0x2c, 0xc0, 0xa2, 0x6a, 0x0e, 0x84, 0xa5, 0x27, 0xce, 0xc5,
+ 0x01, 0x00, 0x00, 0x00, 0x10, 0x4f, 0x9d, 0x96, 0xd9, 0x66, 0xb0, 0x99, 0x2b,
+ 0x54, 0xc2, 0x95, 0x7c, 0xb4, 0x15, 0x7d, 0x4d,
+};
+
+// Test that CRLSets are effective in making a certificate appear to be
+// revoked.
+TEST_F(CertVerifyProcTest, CRLSet) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(),
+ "googlenew.chain.pem",
+ X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(certs[1]->os_cert_handle());
+
+ scoped_refptr<X509Certificate> google_full_chain =
+ X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(),
+ intermediates);
+
+ CertVerifyResult verify_result;
+ int error = Verify(google_full_chain.get(),
+ "www.google.com",
+ 0,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+
+ // First test blocking by SPKI.
+ base::StringPiece crl_set_bytes(
+ reinterpret_cast<const char*>(kCRLSetThawteSPKIBlocked),
+ sizeof(kCRLSetThawteSPKIBlocked));
+ scoped_refptr<CRLSet> crl_set;
+ ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
+
+ error = Verify(google_full_chain.get(),
+ "www.google.com",
+ 0,
+ crl_set.get(),
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+
+ // Second, test revocation by serial number of a cert directly under the
+ // root.
+ crl_set_bytes = base::StringPiece(
+ reinterpret_cast<const char*>(kCRLSetThawteSerialBlocked),
+ sizeof(kCRLSetThawteSerialBlocked));
+ ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
+
+ error = Verify(google_full_chain.get(),
+ "www.google.com",
+ 0,
+ crl_set.get(),
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+
+ // Lastly, test revocation by serial number of a certificate not under the
+ // root.
+ crl_set_bytes = base::StringPiece(
+ reinterpret_cast<const char*>(kCRLSetGoogleSerialBlocked),
+ sizeof(kCRLSetGoogleSerialBlocked));
+ ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set));
+
+ error = Verify(google_full_chain.get(),
+ "www.google.com",
+ 0,
+ crl_set.get(),
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+}
+#endif
+
+struct WeakDigestTestData {
+ const char* root_cert_filename;
+ const char* intermediate_cert_filename;
+ const char* ee_cert_filename;
+ bool expected_has_md5;
+ bool expected_has_md4;
+ bool expected_has_md2;
+};
+
+// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how
+// to output the parameter that was passed. Without this, it will simply
+// attempt to print out the first twenty bytes of the object, which depending
+// on platform and alignment, may result in an invalid read.
+void PrintTo(const WeakDigestTestData& data, std::ostream* os) {
+ *os << "root: "
+ << (data.root_cert_filename ? data.root_cert_filename : "none")
+ << "; intermediate: " << data.intermediate_cert_filename
+ << "; end-entity: " << data.ee_cert_filename;
+}
+
+class CertVerifyProcWeakDigestTest
+ : public CertVerifyProcTest,
+ public testing::WithParamInterface<WeakDigestTestData> {
+ public:
+ CertVerifyProcWeakDigestTest() {}
+ virtual ~CertVerifyProcWeakDigestTest() {}
+};
+
+TEST_P(CertVerifyProcWeakDigestTest, Verify) {
+ WeakDigestTestData data = GetParam();
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ ScopedTestRoot test_root;
+ if (data.root_cert_filename) {
+ scoped_refptr<X509Certificate> root_cert =
+ ImportCertFromFile(certs_dir, data.root_cert_filename);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ test_root.Reset(root_cert.get());
+ }
+
+ scoped_refptr<X509Certificate> intermediate_cert =
+ ImportCertFromFile(certs_dir, data.intermediate_cert_filename);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert);
+ scoped_refptr<X509Certificate> ee_cert =
+ ImportCertFromFile(certs_dir, data.ee_cert_filename);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_cert);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate_cert->os_cert_handle());
+
+ scoped_refptr<X509Certificate> ee_chain =
+ X509Certificate::CreateFromHandle(ee_cert->os_cert_handle(),
+ intermediates);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), ee_chain);
+
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int rv = Verify(ee_chain.get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(data.expected_has_md5, verify_result.has_md5);
+ EXPECT_EQ(data.expected_has_md4, verify_result.has_md4);
+ EXPECT_EQ(data.expected_has_md2, verify_result.has_md2);
+ EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor);
+
+ // Ensure that MD4 and MD2 are tagged as invalid.
+ if (data.expected_has_md4 || data.expected_has_md2) {
+ EXPECT_EQ(CERT_STATUS_INVALID,
+ verify_result.cert_status & CERT_STATUS_INVALID);
+ }
+
+ // Ensure that MD5 is flagged as weak.
+ if (data.expected_has_md5) {
+ EXPECT_EQ(
+ CERT_STATUS_WEAK_SIGNATURE_ALGORITHM,
+ verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM);
+ }
+
+ // If a root cert is present, then check that the chain was rejected if any
+ // weak algorithms are present. This is only checked when a root cert is
+ // present because the error reported for incomplete chains with weak
+ // algorithms depends on which implementation was used to validate (NSS,
+ // OpenSSL, CryptoAPI, Security.framework) and upon which weak algorithm
+ // present (MD2, MD4, MD5).
+ if (data.root_cert_filename) {
+ if (data.expected_has_md4 || data.expected_has_md2) {
+ EXPECT_EQ(ERR_CERT_INVALID, rv);
+ } else if (data.expected_has_md5) {
+ EXPECT_EQ(ERR_CERT_WEAK_SIGNATURE_ALGORITHM, rv);
+ } else {
+ EXPECT_EQ(OK, rv);
+ }
+ }
+}
+
+// Unlike TEST/TEST_F, which are macros that expand to further macros,
+// INSTANTIATE_TEST_CASE_P is a macro that expands directly to code that
+// stringizes the arguments. As a result, macros passed as parameters (such as
+// prefix or test_case_name) will not be expanded by the preprocessor. To work
+// around this, indirect the macro for INSTANTIATE_TEST_CASE_P, so that the
+// pre-processor will expand macros such as MAYBE_test_name before
+// instantiating the test.
+#define WRAPPED_INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \
+ INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator)
+
+// The signature algorithm of the root CA should not matter.
+const WeakDigestTestData kVerifyRootCATestData[] = {
+ { "weak_digest_md5_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_sha1_ee.pem", false, false, false },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { "weak_digest_md4_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_sha1_ee.pem", false, false, false },
+#endif
+ { "weak_digest_md2_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_sha1_ee.pem", false, false, false },
+};
+INSTANTIATE_TEST_CASE_P(VerifyRoot, CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyRootCATestData));
+
+// The signature algorithm of intermediates should be properly detected.
+const WeakDigestTestData kVerifyIntermediateCATestData[] = {
+ { "weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem",
+ "weak_digest_sha1_ee.pem", true, false, false },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { "weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem",
+ "weak_digest_sha1_ee.pem", false, true, false },
+#endif
+ { "weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem",
+ "weak_digest_sha1_ee.pem", false, false, true },
+};
+// Disabled on NSS - MD4 is not supported, and MD2 and MD5 are disabled.
+#if defined(USE_NSS) || defined(OS_IOS)
+#define MAYBE_VerifyIntermediate DISABLED_VerifyIntermediate
+#else
+#define MAYBE_VerifyIntermediate VerifyIntermediate
+#endif
+WRAPPED_INSTANTIATE_TEST_CASE_P(
+ MAYBE_VerifyIntermediate,
+ CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyIntermediateCATestData));
+
+// The signature algorithm of end-entity should be properly detected.
+const WeakDigestTestData kVerifyEndEntityTestData[] = {
+ { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_md5_ee.pem", true, false, false },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_md4_ee.pem", false, true, false },
+#endif
+ { "weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem",
+ "weak_digest_md2_ee.pem", false, false, true },
+};
+// Disabled on NSS - NSS caches chains/signatures in such a way that cannot
+// be cleared until NSS is cleanly shutdown, which is not presently supported
+// in Chromium.
+#if defined(USE_NSS) || defined(OS_IOS)
+#define MAYBE_VerifyEndEntity DISABLED_VerifyEndEntity
+#else
+#define MAYBE_VerifyEndEntity VerifyEndEntity
+#endif
+WRAPPED_INSTANTIATE_TEST_CASE_P(MAYBE_VerifyEndEntity,
+ CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyEndEntityTestData));
+
+// Incomplete chains should still report the status of the intermediate.
+const WeakDigestTestData kVerifyIncompleteIntermediateTestData[] = {
+ { NULL, "weak_digest_md5_intermediate.pem", "weak_digest_sha1_ee.pem",
+ true, false, false },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { NULL, "weak_digest_md4_intermediate.pem", "weak_digest_sha1_ee.pem",
+ false, true, false },
+#endif
+ { NULL, "weak_digest_md2_intermediate.pem", "weak_digest_sha1_ee.pem",
+ false, false, true },
+};
+// Disabled on NSS - libpkix does not return constructed chains on error,
+// preventing us from detecting/inspecting the verified chain.
+#if defined(USE_NSS) || defined(OS_IOS)
+#define MAYBE_VerifyIncompleteIntermediate \
+ DISABLED_VerifyIncompleteIntermediate
+#else
+#define MAYBE_VerifyIncompleteIntermediate VerifyIncompleteIntermediate
+#endif
+WRAPPED_INSTANTIATE_TEST_CASE_P(
+ MAYBE_VerifyIncompleteIntermediate,
+ CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyIncompleteIntermediateTestData));
+
+// Incomplete chains should still report the status of the end-entity.
+const WeakDigestTestData kVerifyIncompleteEETestData[] = {
+ { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md5_ee.pem",
+ true, false, false },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md4_ee.pem",
+ false, true, false },
+#endif
+ { NULL, "weak_digest_sha1_intermediate.pem", "weak_digest_md2_ee.pem",
+ false, false, true },
+};
+// Disabled on NSS - libpkix does not return constructed chains on error,
+// preventing us from detecting/inspecting the verified chain.
+#if defined(USE_NSS) || defined(OS_IOS)
+#define MAYBE_VerifyIncompleteEndEntity DISABLED_VerifyIncompleteEndEntity
+#else
+#define MAYBE_VerifyIncompleteEndEntity VerifyIncompleteEndEntity
+#endif
+WRAPPED_INSTANTIATE_TEST_CASE_P(
+ MAYBE_VerifyIncompleteEndEntity,
+ CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyIncompleteEETestData));
+
+// Differing algorithms between the intermediate and the EE should still be
+// reported.
+const WeakDigestTestData kVerifyMixedTestData[] = {
+ { "weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem",
+ "weak_digest_md2_ee.pem", true, false, true },
+ { "weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem",
+ "weak_digest_md5_ee.pem", true, false, true },
+#if defined(USE_OPENSSL) || defined(OS_WIN)
+ // MD4 is not supported by OS X / NSS
+ { "weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem",
+ "weak_digest_md2_ee.pem", false, true, true },
+#endif
+};
+// NSS does not support MD4 and does not enable MD2 by default, making all
+// permutations invalid.
+#if defined(USE_NSS) || defined(OS_IOS)
+#define MAYBE_VerifyMixed DISABLED_VerifyMixed
+#else
+#define MAYBE_VerifyMixed VerifyMixed
+#endif
+WRAPPED_INSTANTIATE_TEST_CASE_P(
+ MAYBE_VerifyMixed,
+ CertVerifyProcWeakDigestTest,
+ testing::ValuesIn(kVerifyMixedTestData));
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_win.cc b/chromium/net/cert/cert_verify_proc_win.cc
new file mode 100644
index 00000000000..7e94246af96
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_win.cc
@@ -0,0 +1,827 @@
+// Copyright (c) 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.
+
+#include "net/cert/cert_verify_proc_win.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/sha1.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "crypto/capi_util.h"
+#include "crypto/scoped_capi_types.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_certificate_known_roots_win.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+#if !defined(CERT_TRUST_HAS_WEAK_SIGNATURE)
+// This was introduced in Windows 8 / Windows Server 2012, but retroactively
+// ported as far back as Windows XP via system update.
+#define CERT_TRUST_HAS_WEAK_SIGNATURE 0x00100000
+#endif
+
+namespace net {
+
+namespace {
+
+struct FreeChainEngineFunctor {
+ void operator()(HCERTCHAINENGINE engine) const {
+ if (engine)
+ CertFreeCertificateChainEngine(engine);
+ }
+};
+
+struct FreeCertChainContextFunctor {
+ void operator()(PCCERT_CHAIN_CONTEXT chain_context) const {
+ if (chain_context)
+ CertFreeCertificateChain(chain_context);
+ }
+};
+
+struct FreeCertContextFunctor {
+ void operator()(PCCERT_CONTEXT context) const {
+ if (context)
+ CertFreeCertificateContext(context);
+ }
+};
+
+typedef crypto::ScopedCAPIHandle<HCERTCHAINENGINE, FreeChainEngineFunctor>
+ ScopedHCERTCHAINENGINE;
+
+typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT,
+ FreeCertChainContextFunctor>
+ ScopedPCCERT_CHAIN_CONTEXT;
+
+typedef scoped_ptr_malloc<const CERT_CONTEXT,
+ FreeCertContextFunctor> ScopedPCCERT_CONTEXT;
+
+//-----------------------------------------------------------------------------
+
+int MapSecurityError(SECURITY_STATUS err) {
+ // There are numerous security error codes, but these are the ones we thus
+ // far find interesting.
+ switch (err) {
+ case SEC_E_WRONG_PRINCIPAL: // Schannel
+ case CERT_E_CN_NO_MATCH: // CryptoAPI
+ return ERR_CERT_COMMON_NAME_INVALID;
+ case SEC_E_UNTRUSTED_ROOT: // Schannel
+ case CERT_E_UNTRUSTEDROOT: // CryptoAPI
+ return ERR_CERT_AUTHORITY_INVALID;
+ case SEC_E_CERT_EXPIRED: // Schannel
+ case CERT_E_EXPIRED: // CryptoAPI
+ return ERR_CERT_DATE_INVALID;
+ case CRYPT_E_NO_REVOCATION_CHECK:
+ return ERR_CERT_NO_REVOCATION_MECHANISM;
+ case CRYPT_E_REVOCATION_OFFLINE:
+ return ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ case CRYPT_E_REVOKED: // Schannel and CryptoAPI
+ return ERR_CERT_REVOKED;
+ case SEC_E_CERT_UNKNOWN:
+ case CERT_E_ROLE:
+ return ERR_CERT_INVALID;
+ case CERT_E_WRONG_USAGE:
+ // TODO(wtc): Should we add ERR_CERT_WRONG_USAGE?
+ return ERR_CERT_INVALID;
+ // We received an unexpected_message or illegal_parameter alert message
+ // from the server.
+ case SEC_E_ILLEGAL_MESSAGE:
+ return ERR_SSL_PROTOCOL_ERROR;
+ case SEC_E_ALGORITHM_MISMATCH:
+ return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
+ case SEC_E_INVALID_HANDLE:
+ return ERR_UNEXPECTED;
+ case SEC_E_OK:
+ return OK;
+ default:
+ LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+}
+
+// Map the errors in the chain_context->TrustStatus.dwErrorStatus returned by
+// CertGetCertificateChain to our certificate status flags.
+int MapCertChainErrorStatusToCertStatus(DWORD error_status) {
+ CertStatus cert_status = 0;
+
+ // We don't include CERT_TRUST_IS_NOT_TIME_NESTED because it's obsolete and
+ // we wouldn't consider it an error anyway
+ const DWORD kDateInvalidErrors = CERT_TRUST_IS_NOT_TIME_VALID |
+ CERT_TRUST_CTL_IS_NOT_TIME_VALID;
+ if (error_status & kDateInvalidErrors)
+ cert_status |= CERT_STATUS_DATE_INVALID;
+
+ const DWORD kAuthorityInvalidErrors = CERT_TRUST_IS_UNTRUSTED_ROOT |
+ CERT_TRUST_IS_EXPLICIT_DISTRUST |
+ CERT_TRUST_IS_PARTIAL_CHAIN;
+ if (error_status & kAuthorityInvalidErrors)
+ cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+
+ if ((error_status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) &&
+ !(error_status & CERT_TRUST_IS_OFFLINE_REVOCATION))
+ cert_status |= CERT_STATUS_NO_REVOCATION_MECHANISM;
+
+ if (error_status & CERT_TRUST_IS_OFFLINE_REVOCATION)
+ cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+
+ if (error_status & CERT_TRUST_IS_REVOKED)
+ cert_status |= CERT_STATUS_REVOKED;
+
+ const DWORD kWrongUsageErrors = CERT_TRUST_IS_NOT_VALID_FOR_USAGE |
+ CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE;
+ if (error_status & kWrongUsageErrors) {
+ // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE?
+ cert_status |= CERT_STATUS_INVALID;
+ }
+
+ if (error_status & CERT_TRUST_IS_NOT_SIGNATURE_VALID) {
+ // Check for a signature that does not meet the OS criteria for strong
+ // signatures.
+ // Note: These checks may be more restrictive than the current weak key
+ // criteria implemented within CertVerifier, such as excluding SHA-1 or
+ // excluding RSA keys < 2048 bits. However, if the user has configured
+ // these more stringent checks, respect that configuration and err on the
+ // more restrictive criteria.
+ if (error_status & CERT_TRUST_HAS_WEAK_SIGNATURE) {
+ cert_status |= CERT_STATUS_WEAK_KEY;
+ } else {
+ cert_status |= CERT_STATUS_INVALID;
+ }
+ }
+
+ // The rest of the errors.
+ const DWORD kCertInvalidErrors =
+ CERT_TRUST_IS_CYCLIC |
+ CERT_TRUST_INVALID_EXTENSION |
+ CERT_TRUST_INVALID_POLICY_CONSTRAINTS |
+ CERT_TRUST_INVALID_BASIC_CONSTRAINTS |
+ CERT_TRUST_INVALID_NAME_CONSTRAINTS |
+ CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID |
+ CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT |
+ CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT |
+ CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT |
+ CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT |
+ CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY |
+ CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT;
+ if (error_status & kCertInvalidErrors)
+ cert_status |= CERT_STATUS_INVALID;
+
+ return cert_status;
+}
+
+// Returns true if any common name in the certificate's Subject field contains
+// a NULL character.
+bool CertSubjectCommonNameHasNull(PCCERT_CONTEXT cert) {
+ CRYPT_DECODE_PARA decode_para;
+ decode_para.cbSize = sizeof(decode_para);
+ decode_para.pfnAlloc = crypto::CryptAlloc;
+ decode_para.pfnFree = crypto::CryptFree;
+ CERT_NAME_INFO* name_info = NULL;
+ DWORD name_info_size = 0;
+ BOOL rv;
+ rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ X509_NAME,
+ cert->pCertInfo->Subject.pbData,
+ cert->pCertInfo->Subject.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &name_info,
+ &name_info_size);
+ if (rv) {
+ scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info);
+
+ // The Subject field may have multiple common names. According to the
+ // "PKI Layer Cake" paper, CryptoAPI uses every common name in the
+ // Subject field, so we inspect every common name.
+ //
+ // From RFC 5280:
+ // X520CommonName ::= CHOICE {
+ // teletexString TeletexString (SIZE (1..ub-common-name)),
+ // printableString PrintableString (SIZE (1..ub-common-name)),
+ // universalString UniversalString (SIZE (1..ub-common-name)),
+ // utf8String UTF8String (SIZE (1..ub-common-name)),
+ // bmpString BMPString (SIZE (1..ub-common-name)) }
+ //
+ // We also check IA5String and VisibleString.
+ for (DWORD i = 0; i < name_info->cRDN; ++i) {
+ PCERT_RDN rdn = &name_info->rgRDN[i];
+ for (DWORD j = 0; j < rdn->cRDNAttr; ++j) {
+ PCERT_RDN_ATTR rdn_attr = &rdn->rgRDNAttr[j];
+ if (strcmp(rdn_attr->pszObjId, szOID_COMMON_NAME) == 0) {
+ switch (rdn_attr->dwValueType) {
+ // After the CryptoAPI ASN.1 security vulnerabilities described in
+ // http://www.microsoft.com/technet/security/Bulletin/MS09-056.mspx
+ // were patched, we get CERT_RDN_ENCODED_BLOB for a common name
+ // that contains a NULL character.
+ case CERT_RDN_ENCODED_BLOB:
+ break;
+ // Array of 8-bit characters.
+ case CERT_RDN_PRINTABLE_STRING:
+ case CERT_RDN_TELETEX_STRING:
+ case CERT_RDN_IA5_STRING:
+ case CERT_RDN_VISIBLE_STRING:
+ for (DWORD k = 0; k < rdn_attr->Value.cbData; ++k) {
+ if (rdn_attr->Value.pbData[k] == '\0')
+ return true;
+ }
+ break;
+ // Array of 16-bit characters.
+ case CERT_RDN_BMP_STRING:
+ case CERT_RDN_UTF8_STRING: {
+ DWORD num_wchars = rdn_attr->Value.cbData / 2;
+ wchar_t* common_name =
+ reinterpret_cast<wchar_t*>(rdn_attr->Value.pbData);
+ for (DWORD k = 0; k < num_wchars; ++k) {
+ if (common_name[k] == L'\0')
+ return true;
+ }
+ break;
+ }
+ // Array of ints (32-bit).
+ case CERT_RDN_UNIVERSAL_STRING: {
+ DWORD num_ints = rdn_attr->Value.cbData / 4;
+ int* common_name =
+ reinterpret_cast<int*>(rdn_attr->Value.pbData);
+ for (DWORD k = 0; k < num_ints; ++k) {
+ if (common_name[k] == 0)
+ return true;
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA
+// which we recognise as a standard root.
+// static
+bool IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context) {
+ PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0];
+ int num_elements = first_chain->cElement;
+ if (num_elements < 1)
+ return false;
+ PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement;
+ PCCERT_CONTEXT cert = element[num_elements - 1]->pCertContext;
+
+ SHA1HashValue hash = X509Certificate::CalculateFingerprint(cert);
+ return IsSHA1HashInSortedArray(
+ hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes));
+}
+
+// Saves some information about the certificate chain |chain_context| in
+// |*verify_result|. The caller MUST initialize |*verify_result| before
+// calling this function.
+void GetCertChainInfo(PCCERT_CHAIN_CONTEXT chain_context,
+ CertVerifyResult* verify_result) {
+ if (chain_context->cChain == 0)
+ return;
+
+ PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0];
+ int num_elements = first_chain->cElement;
+ PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement;
+
+ PCCERT_CONTEXT verified_cert = NULL;
+ std::vector<PCCERT_CONTEXT> verified_chain;
+
+ bool has_root_ca = num_elements > 1 &&
+ !(chain_context->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_PARTIAL_CHAIN);
+
+ // Each chain starts with the end entity certificate (i = 0) and ends with
+ // either the root CA certificate or the last available intermediate. If a
+ // root CA certificate is present, do not inspect the signature algorithm of
+ // the root CA certificate because the signature on the trust anchor is not
+ // important.
+ if (has_root_ca) {
+ // If a full chain was constructed, regardless of whether it was trusted,
+ // don't inspect the root's signature algorithm.
+ num_elements -= 1;
+ }
+
+ for (int i = 0; i < num_elements; ++i) {
+ PCCERT_CONTEXT cert = element[i]->pCertContext;
+ if (i == 0) {
+ verified_cert = cert;
+ } else {
+ verified_chain.push_back(cert);
+ }
+
+ const char* algorithm = cert->pCertInfo->SignatureAlgorithm.pszObjId;
+ if (strcmp(algorithm, szOID_RSA_MD5RSA) == 0) {
+ // md5WithRSAEncryption: 1.2.840.113549.1.1.4
+ verify_result->has_md5 = true;
+ } else if (strcmp(algorithm, szOID_RSA_MD2RSA) == 0) {
+ // md2WithRSAEncryption: 1.2.840.113549.1.1.2
+ verify_result->has_md2 = true;
+ } else if (strcmp(algorithm, szOID_RSA_MD4RSA) == 0) {
+ // md4WithRSAEncryption: 1.2.840.113549.1.1.3
+ verify_result->has_md4 = true;
+ }
+ }
+
+ if (verified_cert) {
+ // Add the root certificate, if present, as it was not added above.
+ if (has_root_ca)
+ verified_chain.push_back(element[num_elements]->pCertContext);
+ verify_result->verified_cert =
+ X509Certificate::CreateFromHandle(verified_cert, verified_chain);
+ }
+}
+
+// Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO
+// structure and stores it in *output.
+void GetCertPoliciesInfo(PCCERT_CONTEXT cert,
+ scoped_ptr_malloc<CERT_POLICIES_INFO>* output) {
+ PCERT_EXTENSION extension = CertFindExtension(szOID_CERT_POLICIES,
+ cert->pCertInfo->cExtension,
+ cert->pCertInfo->rgExtension);
+ if (!extension)
+ return;
+
+ CRYPT_DECODE_PARA decode_para;
+ decode_para.cbSize = sizeof(decode_para);
+ decode_para.pfnAlloc = crypto::CryptAlloc;
+ decode_para.pfnFree = crypto::CryptFree;
+ CERT_POLICIES_INFO* policies_info = NULL;
+ DWORD policies_info_size = 0;
+ BOOL rv;
+ rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ szOID_CERT_POLICIES,
+ extension->Value.pbData,
+ extension->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &policies_info,
+ &policies_info_size);
+ if (rv)
+ output->reset(policies_info);
+}
+
+enum CRLSetResult {
+ kCRLSetOk,
+ kCRLSetUnknown,
+ kCRLSetRevoked,
+};
+
+// CheckRevocationWithCRLSet attempts to check each element of |chain|
+// against |crl_set|. It returns:
+// kCRLSetRevoked: if any element of the chain is known to have been revoked.
+// kCRLSetUnknown: if there is no fresh information about some element in
+// the chain.
+// kCRLSetOk: if every element in the chain is covered by a fresh CRLSet and
+// is unrevoked.
+CRLSetResult CheckRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain,
+ CRLSet* crl_set) {
+ if (chain->cChain == 0)
+ return kCRLSetOk;
+
+ const PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0];
+ const PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement;
+
+ const int num_elements = first_chain->cElement;
+ if (num_elements == 0)
+ return kCRLSetOk;
+
+ bool covered = true;
+
+ // We iterate from the root certificate down to the leaf, keeping track of
+ // the issuer's SPKI at each step.
+ std::string issuer_spki_hash;
+ for (int i = num_elements - 1; i >= 0; i--) {
+ PCCERT_CONTEXT cert = element[i]->pCertContext;
+
+ base::StringPiece der_bytes(
+ reinterpret_cast<const char*>(cert->pbCertEncoded),
+ cert->cbCertEncoded);
+
+ base::StringPiece spki;
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) {
+ NOTREACHED();
+ covered = false;
+ continue;
+ }
+
+ const std::string spki_hash = crypto::SHA256HashString(spki);
+
+ const CRYPT_INTEGER_BLOB* serial_blob = &cert->pCertInfo->SerialNumber;
+ scoped_ptr<uint8[]> serial_bytes(new uint8[serial_blob->cbData]);
+ // The bytes of the serial number are stored little-endian.
+ for (unsigned j = 0; j < serial_blob->cbData; j++)
+ serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1];
+ base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()),
+ serial_blob->cbData);
+
+ CRLSet::Result result = crl_set->CheckSPKI(spki_hash);
+
+ if (result != CRLSet::REVOKED && !issuer_spki_hash.empty())
+ result = crl_set->CheckSerial(serial, issuer_spki_hash);
+
+ issuer_spki_hash = spki_hash;
+
+ switch (result) {
+ case CRLSet::REVOKED:
+ return kCRLSetRevoked;
+ case CRLSet::UNKNOWN:
+ covered = false;
+ continue;
+ case CRLSet::GOOD:
+ continue;
+ default:
+ NOTREACHED();
+ covered = false;
+ continue;
+ }
+ }
+
+ if (!covered || crl_set->IsExpired())
+ return kCRLSetUnknown;
+ return kCRLSetOk;
+}
+
+void AppendPublicKeyHashes(PCCERT_CHAIN_CONTEXT chain,
+ HashValueVector* hashes) {
+ if (chain->cChain == 0)
+ return;
+
+ PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0];
+ PCERT_CHAIN_ELEMENT* const element = first_chain->rgpElement;
+
+ const DWORD num_elements = first_chain->cElement;
+ for (DWORD i = 0; i < num_elements; i++) {
+ PCCERT_CONTEXT cert = element[i]->pCertContext;
+
+ base::StringPiece der_bytes(
+ reinterpret_cast<const char*>(cert->pbCertEncoded),
+ cert->cbCertEncoded);
+ base::StringPiece spki_bytes;
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes))
+ continue;
+
+ HashValue sha1(HASH_VALUE_SHA1);
+ base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()),
+ spki_bytes.size(), sha1.data());
+ hashes->push_back(sha1);
+
+ HashValue sha256(HASH_VALUE_SHA256);
+ crypto::SHA256HashString(spki_bytes, sha256.data(), crypto::kSHA256Length);
+ hashes->push_back(sha256);
+ }
+}
+
+// Returns true if the certificate is an extended-validation certificate.
+//
+// This function checks the certificatePolicies extensions of the
+// certificates in the certificate chain according to Section 7 (pp. 11-12)
+// of the EV Certificate Guidelines Version 1.0 at
+// http://cabforum.org/EV_Certificate_Guidelines.pdf.
+bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context,
+ bool rev_checking_enabled,
+ const char* policy_oid) {
+ DCHECK_NE(static_cast<DWORD>(0), chain_context->cChain);
+ // If the cert doesn't match any of the policies, the
+ // CERT_TRUST_IS_NOT_VALID_FOR_USAGE bit (0x10) in
+ // chain_context->TrustStatus.dwErrorStatus is set.
+ DWORD error_status = chain_context->TrustStatus.dwErrorStatus;
+
+ if (!rev_checking_enabled) {
+ // If online revocation checking is disabled then we will have still
+ // requested that the revocation cache be checked. However, that will often
+ // cause the following two error bits to be set. These error bits mean that
+ // the local OCSP/CRL is stale or missing entries for these certificates.
+ // Since they are expected, we mask them away.
+ error_status &= ~(CERT_TRUST_IS_OFFLINE_REVOCATION |
+ CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
+ }
+ if (!chain_context->cChain || error_status != CERT_TRUST_NO_ERROR)
+ return false;
+
+ // Check the end certificate simple chain (chain_context->rgpChain[0]).
+ // If the end certificate's certificatePolicies extension contains the
+ // EV policy OID of the root CA, return true.
+ PCERT_CHAIN_ELEMENT* element = chain_context->rgpChain[0]->rgpElement;
+ int num_elements = chain_context->rgpChain[0]->cElement;
+ if (num_elements < 2)
+ return false;
+
+ // Look up the EV policy OID of the root CA.
+ PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext;
+ SHA1HashValue fingerprint =
+ X509Certificate::CalculateFingerprint(root_cert);
+ EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance();
+ return metadata->HasEVPolicyOID(fingerprint, policy_oid);
+}
+
+} // namespace
+
+CertVerifyProcWin::CertVerifyProcWin() {}
+
+CertVerifyProcWin::~CertVerifyProcWin() {}
+
+bool CertVerifyProcWin::SupportsAdditionalTrustAnchors() const {
+ return false;
+}
+
+int CertVerifyProcWin::VerifyInternal(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) {
+ PCCERT_CONTEXT cert_handle = cert->os_cert_handle();
+ if (!cert_handle)
+ return ERR_UNEXPECTED;
+
+ // Build and validate certificate chain.
+ CERT_CHAIN_PARA chain_para;
+ memset(&chain_para, 0, sizeof(chain_para));
+ chain_para.cbSize = sizeof(chain_para);
+ // ExtendedKeyUsage.
+ // We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE
+ // today because some certificate chains need them. IE also requests these
+ // two usages.
+ static const LPSTR usage[] = {
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+ };
+ chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage);
+ chain_para.RequestedUsage.Usage.rgpszUsageIdentifier =
+ const_cast<LPSTR*>(usage);
+
+ // Get the certificatePolicies extension of the certificate.
+ scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info;
+ LPSTR ev_policy_oid = NULL;
+ if (flags & CertVerifier::VERIFY_EV_CERT) {
+ GetCertPoliciesInfo(cert_handle, &policies_info);
+ if (policies_info.get()) {
+ EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance();
+ for (DWORD i = 0; i < policies_info->cPolicyInfo; ++i) {
+ LPSTR policy_oid = policies_info->rgPolicyInfo[i].pszPolicyIdentifier;
+ if (metadata->IsEVPolicyOID(policy_oid)) {
+ ev_policy_oid = policy_oid;
+ chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_AND;
+ chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 1;
+ chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier =
+ &ev_policy_oid;
+ break;
+ }
+ }
+ }
+ }
+
+ // We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains.
+ DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT |
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
+ bool rev_checking_enabled =
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED);
+
+ if (rev_checking_enabled) {
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ } else {
+ chain_flags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
+ }
+
+ // For non-test scenarios, use the default HCERTCHAINENGINE, NULL, which
+ // corresponds to HCCE_CURRENT_USER and is is initialized as needed by
+ // crypt32. However, when testing, it is necessary to create a new
+ // HCERTCHAINENGINE and use that instead. This is because each
+ // HCERTCHAINENGINE maintains a cache of information about certificates
+ // encountered, and each test run may modify the trust status of a
+ // certificate.
+ ScopedHCERTCHAINENGINE chain_engine(NULL);
+ if (TestRootCerts::HasInstance())
+ chain_engine.reset(TestRootCerts::GetInstance()->GetChainEngine());
+
+ ScopedPCCERT_CONTEXT cert_list(cert->CreateOSCertChainForCert());
+ PCCERT_CHAIN_CONTEXT chain_context;
+ // IE passes a non-NULL pTime argument that specifies the current system
+ // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the
+ // chain_flags argument.
+ if (!CertGetCertificateChain(
+ chain_engine,
+ cert_list.get(),
+ NULL, // current system time
+ cert_list->hCertStore,
+ &chain_para,
+ chain_flags,
+ NULL, // reserved
+ &chain_context)) {
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ return MapSecurityError(GetLastError());
+ }
+
+ CRLSetResult crl_set_result = kCRLSetUnknown;
+ if (crl_set)
+ crl_set_result = CheckRevocationWithCRLSet(chain_context, crl_set);
+
+ if (crl_set_result == kCRLSetRevoked) {
+ verify_result->cert_status |= CERT_STATUS_REVOKED;
+ } else if (crl_set_result == kCRLSetUnknown &&
+ (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED_EV_ONLY) &&
+ !rev_checking_enabled &&
+ ev_policy_oid != NULL) {
+ // We don't have fresh information about this chain from the CRLSet and
+ // it's probably an EV certificate. Retry with online revocation checking.
+ rev_checking_enabled = true;
+ chain_flags &= ~CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+
+ if (!CertGetCertificateChain(
+ chain_engine,
+ cert_list.get(),
+ NULL, // current system time
+ cert_list->hCertStore,
+ &chain_para,
+ chain_flags,
+ NULL, // reserved
+ &chain_context)) {
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ return MapSecurityError(GetLastError());
+ }
+ }
+
+ if (chain_context->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_NOT_VALID_FOR_USAGE) {
+ ev_policy_oid = NULL;
+ chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 0;
+ chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = NULL;
+ CertFreeCertificateChain(chain_context);
+ if (!CertGetCertificateChain(
+ chain_engine,
+ cert_list.get(),
+ NULL, // current system time
+ cert_list->hCertStore,
+ &chain_para,
+ chain_flags,
+ NULL, // reserved
+ &chain_context)) {
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ return MapSecurityError(GetLastError());
+ }
+ }
+
+ CertVerifyResult temp_verify_result = *verify_result;
+ GetCertChainInfo(chain_context, verify_result);
+ if (!verify_result->is_issued_by_known_root &&
+ (flags & CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS)) {
+ *verify_result = temp_verify_result;
+
+ rev_checking_enabled = true;
+ verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ chain_flags &= ~CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY;
+
+ CertFreeCertificateChain(chain_context);
+ if (!CertGetCertificateChain(
+ chain_engine,
+ cert_list.get(),
+ NULL, // current system time
+ cert_list->hCertStore,
+ &chain_para,
+ chain_flags,
+ NULL, // reserved
+ &chain_context)) {
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+ return MapSecurityError(GetLastError());
+ }
+ GetCertChainInfo(chain_context, verify_result);
+
+ if (chain_context->TrustStatus.dwErrorStatus &
+ CERT_TRUST_IS_OFFLINE_REVOCATION) {
+ verify_result->cert_status |= CERT_STATUS_REVOKED;
+ }
+ }
+
+ ScopedPCCERT_CHAIN_CONTEXT scoped_chain_context(chain_context);
+
+ verify_result->cert_status |= MapCertChainErrorStatusToCertStatus(
+ chain_context->TrustStatus.dwErrorStatus);
+
+ // Flag certificates that have a Subject common name with a NULL character.
+ if (CertSubjectCommonNameHasNull(cert_handle))
+ verify_result->cert_status |= CERT_STATUS_INVALID;
+
+ std::wstring wstr_hostname = ASCIIToWide(hostname);
+
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para;
+ memset(&extra_policy_para, 0, sizeof(extra_policy_para));
+ extra_policy_para.cbSize = sizeof(extra_policy_para);
+ extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
+ extra_policy_para.fdwChecks = 0;
+ extra_policy_para.pwszServerName =
+ const_cast<wchar_t*>(wstr_hostname.c_str());
+
+ CERT_CHAIN_POLICY_PARA policy_para;
+ memset(&policy_para, 0, sizeof(policy_para));
+ policy_para.cbSize = sizeof(policy_para);
+ policy_para.dwFlags = 0;
+ policy_para.pvExtraPolicyPara = &extra_policy_para;
+
+ CERT_CHAIN_POLICY_STATUS policy_status;
+ memset(&policy_status, 0, sizeof(policy_status));
+ policy_status.cbSize = sizeof(policy_status);
+
+ if (!CertVerifyCertificateChainPolicy(
+ CERT_CHAIN_POLICY_SSL,
+ chain_context,
+ &policy_para,
+ &policy_status)) {
+ return MapSecurityError(GetLastError());
+ }
+
+ if (policy_status.dwError) {
+ verify_result->cert_status |= MapNetErrorToCertStatus(
+ MapSecurityError(policy_status.dwError));
+
+ // CertVerifyCertificateChainPolicy reports only one error (in
+ // policy_status.dwError) if the certificate has multiple errors.
+ // CertGetCertificateChain doesn't report certificate name mismatch, so
+ // CertVerifyCertificateChainPolicy is the only function that can report
+ // certificate name mismatch.
+ //
+ // To prevent a potential certificate name mismatch from being hidden by
+ // some other certificate error, if we get any other certificate error,
+ // we call CertVerifyCertificateChainPolicy again, ignoring all other
+ // certificate errors. Both extra_policy_para.fdwChecks and
+ // policy_para.dwFlags allow us to ignore certificate errors, so we set
+ // them both.
+ if (policy_status.dwError != CERT_E_CN_NO_MATCH) {
+ const DWORD extra_ignore_flags =
+ 0x00000080 | // SECURITY_FLAG_IGNORE_REVOCATION
+ 0x00000100 | // SECURITY_FLAG_IGNORE_UNKNOWN_CA
+ 0x00002000 | // SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
+ 0x00000200; // SECURITY_FLAG_IGNORE_WRONG_USAGE
+ extra_policy_para.fdwChecks = extra_ignore_flags;
+ const DWORD ignore_flags =
+ CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS |
+ CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG |
+ CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS |
+ CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG |
+ CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_NOT_SUPPORTED_CRITICAL_EXT_FLAG |
+ CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG;
+ policy_para.dwFlags = ignore_flags;
+ if (!CertVerifyCertificateChainPolicy(
+ CERT_CHAIN_POLICY_SSL,
+ chain_context,
+ &policy_para,
+ &policy_status)) {
+ return MapSecurityError(GetLastError());
+ }
+ if (policy_status.dwError) {
+ verify_result->cert_status |= MapNetErrorToCertStatus(
+ MapSecurityError(policy_status.dwError));
+ }
+ }
+ }
+
+ // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be
+ // compatible with WinHTTP, which doesn't report this error (bug 3004).
+ verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM;
+
+ if (!rev_checking_enabled) {
+ // If we didn't do online revocation checking then Windows will report
+ // CERT_UNABLE_TO_CHECK_REVOCATION unless it had cached OCSP or CRL
+ // information for every certificate. We only want to put up revoked
+ // statuses from the offline checks so we squash this error.
+ verify_result->cert_status &= ~CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+ }
+
+ AppendPublicKeyHashes(chain_context, &verify_result->public_key_hashes);
+ verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(chain_context);
+
+ if (IsCertStatusError(verify_result->cert_status))
+ return MapCertStatusToNetError(verify_result->cert_status);
+
+ if (ev_policy_oid &&
+ CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) {
+ verify_result->cert_status |= CERT_STATUS_IS_EV;
+ }
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_proc_win.h b/chromium/net/cert/cert_verify_proc_win.h
new file mode 100644
index 00000000000..147f47ab5a5
--- /dev/null
+++ b/chromium/net/cert/cert_verify_proc_win.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CERT_VERIFY_PROC_WIN_H_
+#define NET_CERT_CERT_VERIFY_PROC_WIN_H_
+
+#include "net/cert/cert_verify_proc.h"
+
+namespace net {
+
+// Performs certificate path construction and validation using Windows'
+// CryptoAPI.
+class CertVerifyProcWin : public CertVerifyProc {
+ public:
+ CertVerifyProcWin();
+
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE;
+
+ protected:
+ virtual ~CertVerifyProcWin();
+
+ private:
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_PROC_WIN_H_
diff --git a/chromium/net/cert/cert_verify_result.cc b/chromium/net/cert/cert_verify_result.cc
new file mode 100644
index 00000000000..68e76a77f7a
--- /dev/null
+++ b/chromium/net/cert/cert_verify_result.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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/cert/cert_verify_result.h"
+
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+CertVerifyResult::CertVerifyResult() {
+ Reset();
+}
+
+CertVerifyResult::~CertVerifyResult() {
+}
+
+void CertVerifyResult::Reset() {
+ verified_cert = NULL;
+ cert_status = 0;
+ has_md5 = false;
+ has_md2 = false;
+ has_md4 = false;
+ is_issued_by_known_root = false;
+ is_issued_by_additional_trust_anchor = false;
+
+ public_key_hashes.clear();
+}
+
+} // namespace net
diff --git a/chromium/net/cert/cert_verify_result.h b/chromium/net/cert/cert_verify_result.h
new file mode 100644
index 00000000000..a00c03e4a24
--- /dev/null
+++ b/chromium/net/cert/cert_verify_result.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 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_CERT_CERT_VERIFY_RESULT_H_
+#define NET_CERT_CERT_VERIFY_RESULT_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace net {
+
+class X509Certificate;
+
+// The result of certificate verification.
+class NET_EXPORT CertVerifyResult {
+ public:
+ CertVerifyResult();
+ ~CertVerifyResult();
+
+ void Reset();
+
+ // Copies from |other| to |this|.
+ void CopyFrom(const CertVerifyResult& other) {
+ *this = other;
+ }
+
+ // The certificate and chain that was constructed during verification.
+ // Note that the though the verified certificate will match the originally
+ // supplied certificate, the intermediate certificates stored within may
+ // be substantially different. In the event of a verification failure, this
+ // will contain the chain as supplied by the server. This may be NULL if
+ // running within the sandbox.
+ scoped_refptr<X509Certificate> verified_cert;
+
+ // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h. Note that
+ // these status flags apply to the certificate chain returned in
+ // |verified_cert|, rather than the originally supplied certificate
+ // chain.
+ CertStatus cert_status;
+
+ // Properties of the certificate chain.
+ bool has_md5;
+ bool has_md2;
+ bool has_md4;
+
+ // If the certificate was successfully verified then this contains the
+ // hashes, in several hash algorithms, of the SubjectPublicKeyInfos of the
+ // chain.
+ HashValueVector public_key_hashes;
+
+ // is_issued_by_known_root is true if we recognise the root CA as a standard
+ // root. If it isn't then it's probably the case that this certificate was
+ // generated by a MITM proxy whose root has been installed locally. This is
+ // meaningless if the certificate was not trusted.
+ bool is_issued_by_known_root;
+
+ // is_issued_by_additional_trust_anchor is true if the root CA used for this
+ // verification came from the list of additional trust anchors.
+ bool is_issued_by_additional_trust_anchor;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CERT_VERIFY_RESULT_H_
diff --git a/chromium/net/cert/crl_set.cc b/chromium/net/cert/crl_set.cc
new file mode 100644
index 00000000000..de0651666d8
--- /dev/null
+++ b/chromium/net/cert/crl_set.cc
@@ -0,0 +1,611 @@
+// Copyright (c) 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.
+
+#include "base/base64.h"
+#include "base/format_macros.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "crypto/sha2.h"
+#include "net/cert/crl_set.h"
+#include "third_party/zlib/zlib.h"
+
+namespace net {
+
+// Decompress zlib decompressed |in| into |out|. |out_len| is the number of
+// bytes at |out| and must be exactly equal to the size of the decompressed
+// data.
+static bool DecompressZlib(uint8* out, int out_len, base::StringPiece in) {
+ z_stream z;
+ memset(&z, 0, sizeof(z));
+
+ z.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(in.data()));
+ z.avail_in = in.size();
+ z.next_out = reinterpret_cast<Bytef*>(out);
+ z.avail_out = out_len;
+
+ if (inflateInit(&z) != Z_OK)
+ return false;
+ bool ret = false;
+ int r = inflate(&z, Z_FINISH);
+ if (r != Z_STREAM_END)
+ goto err;
+ if (z.avail_in || z.avail_out)
+ goto err;
+ ret = true;
+
+ err:
+ inflateEnd(&z);
+ return ret;
+}
+
+CRLSet::CRLSet()
+ : sequence_(0),
+ not_after_(0) {
+}
+
+CRLSet::~CRLSet() {
+}
+
+// CRLSet format:
+//
+// uint16le header_len
+// byte[header_len] header_bytes
+// repeated {
+// byte[32] parent_spki_sha256
+// uint32le num_serials
+// [num_serials] {
+// uint8 serial_length;
+// byte[serial_length] serial;
+// }
+//
+// header_bytes consists of a JSON dictionary with the following keys:
+// Version (int): currently 0
+// ContentType (string): "CRLSet" or "CRLSetDelta" (magic value)
+// DeltaFrom (int32): if this is a delta update (see below), then this
+// contains the sequence number of the base CRLSet.
+// Sequence (int32): the monotonic sequence number of this CRL set.
+//
+// A delta CRLSet is similar to a CRLSet:
+//
+// struct CompressedChanges {
+// uint32le uncompressed_size
+// uint32le compressed_size
+// byte[compressed_size] zlib_data
+// }
+//
+// uint16le header_len
+// byte[header_len] header_bytes
+// CompressedChanges crl_changes
+// [crl_changes.uncompressed_size] {
+// switch (crl_changes[i]) {
+// case 0:
+// // CRL is the same
+// case 1:
+// // New CRL inserted
+// // See CRL structure from the non-delta format
+// case 2:
+// // CRL deleted
+// case 3:
+// // CRL changed
+// CompressedChanges serials_changes
+// [serials_changes.uncompressed_size] {
+// switch (serials_changes[i]) {
+// case 0:
+// // the serial is the same
+// case 1:
+// // serial inserted
+// uint8 serial_length
+// byte[serial_length] serial
+// case 2:
+// // serial deleted
+// }
+// }
+// }
+// }
+//
+// A delta CRLSet applies to a specific CRL set as given in the
+// header's "DeltaFrom" value. The delta describes the changes to each CRL
+// in turn with a zlib compressed array of options: either the CRL is the same,
+// a new CRL is inserted, the CRL is deleted or the CRL is updated. In the case
+// of an update, the serials in the CRL are considered in the same fashion
+// except there is no delta update of a serial number: they are either
+// inserted, deleted or left the same.
+
+// ReadHeader reads the header (including length prefix) from |data| and
+// updates |data| to remove the header on return. Caller takes ownership of the
+// returned pointer.
+static base::DictionaryValue* ReadHeader(base::StringPiece* data) {
+ if (data->size() < 2)
+ return NULL;
+ uint16 header_len;
+ memcpy(&header_len, data->data(), 2); // assumes little-endian.
+ data->remove_prefix(2);
+
+ if (data->size() < header_len)
+ return NULL;
+
+ const base::StringPiece header_bytes(data->data(), header_len);
+ data->remove_prefix(header_len);
+
+ scoped_ptr<base::Value> header(base::JSONReader::Read(
+ header_bytes, base::JSON_ALLOW_TRAILING_COMMAS));
+ if (header.get() == NULL)
+ return NULL;
+
+ if (!header->IsType(base::Value::TYPE_DICTIONARY))
+ return NULL;
+ return reinterpret_cast<base::DictionaryValue*>(header.release());
+}
+
+// kCurrentFileVersion is the version of the CRLSet file format that we
+// currently implement.
+static const int kCurrentFileVersion = 0;
+
+static bool ReadCRL(base::StringPiece* data, std::string* out_parent_spki_hash,
+ std::vector<std::string>* out_serials) {
+ if (data->size() < crypto::kSHA256Length)
+ return false;
+ *out_parent_spki_hash = std::string(data->data(), crypto::kSHA256Length);
+ data->remove_prefix(crypto::kSHA256Length);
+
+ if (data->size() < sizeof(uint32))
+ return false;
+ uint32 num_serials;
+ memcpy(&num_serials, data->data(), sizeof(uint32)); // assumes little endian
+ data->remove_prefix(sizeof(uint32));
+
+ for (uint32 i = 0; i < num_serials; ++i) {
+ uint8 serial_length;
+ if (data->size() < sizeof(uint8))
+ return false;
+ memcpy(&serial_length, data->data(), sizeof(uint8));
+ data->remove_prefix(sizeof(uint8));
+
+ if (data->size() < serial_length)
+ return false;
+ std::string serial(data->data(), serial_length);
+ data->remove_prefix(serial_length);
+ out_serials->push_back(serial);
+ }
+
+ return true;
+}
+
+bool CRLSet::CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict) {
+ base::ListValue* blocked_spkis_list = NULL;
+ if (!header_dict->GetList("BlockedSPKIs", &blocked_spkis_list)) {
+ // BlockedSPKIs is optional, so it's fine if we don't find it.
+ return true;
+ }
+
+ blocked_spkis_.clear();
+
+ for (size_t i = 0; i < blocked_spkis_list->GetSize(); ++i) {
+ std::string spki_sha256_base64, spki_sha256;
+ if (!blocked_spkis_list->GetString(i, &spki_sha256_base64))
+ return false;
+ if (!base::Base64Decode(spki_sha256_base64, &spki_sha256))
+ return false;
+ blocked_spkis_.push_back(spki_sha256);
+ }
+
+ return true;
+}
+
+// static
+bool CRLSet::Parse(base::StringPiece data, scoped_refptr<CRLSet>* out_crl_set) {
+ // Other parts of Chrome assume that we're little endian, so we don't lose
+ // anything by doing this.
+#if defined(__BYTE_ORDER)
+ // Linux check
+ COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian);
+#elif defined(__BIG_ENDIAN__)
+ // Mac check
+ #error assumes little endian
+#endif
+
+ scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
+ if (!header_dict.get())
+ return false;
+
+ std::string contents;
+ if (!header_dict->GetString("ContentType", &contents))
+ return false;
+ if (contents != "CRLSet")
+ return false;
+
+ int version;
+ if (!header_dict->GetInteger("Version", &version) ||
+ version != kCurrentFileVersion) {
+ return false;
+ }
+
+ int sequence;
+ if (!header_dict->GetInteger("Sequence", &sequence))
+ return false;
+
+ double not_after;
+ if (!header_dict->GetDouble("NotAfter", &not_after)) {
+ // NotAfter is optional for now.
+ not_after = 0;
+ }
+ if (not_after < 0)
+ return false;
+
+ scoped_refptr<CRLSet> crl_set(new CRLSet);
+ crl_set->sequence_ = static_cast<uint32>(sequence);
+ crl_set->not_after_ = static_cast<uint64>(not_after);
+
+ for (size_t crl_index = 0; !data.empty(); crl_index++) {
+ std::string parent_spki_sha256;
+ std::vector<std::string> serials;
+ if (!ReadCRL(&data, &parent_spki_sha256, &serials))
+ return false;
+
+ crl_set->crls_.push_back(std::make_pair(parent_spki_sha256, serials));
+ crl_set->crls_index_by_issuer_[parent_spki_sha256] = crl_index;
+ }
+
+ if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get()))
+ return false;
+
+ *out_crl_set = crl_set;
+ return true;
+}
+
+// kMaxUncompressedChangesLength is the largest changes array that we'll
+// accept. This bounds the number of CRLs in the CRLSet as well as the number
+// of serial numbers in a given CRL.
+static const unsigned kMaxUncompressedChangesLength = 1024 * 1024;
+
+static bool ReadChanges(base::StringPiece* data,
+ std::vector<uint8>* out_changes) {
+ uint32 uncompressed_size, compressed_size;
+ if (data->size() < 2 * sizeof(uint32))
+ return false;
+ // assumes little endian.
+ memcpy(&uncompressed_size, data->data(), sizeof(uint32));
+ data->remove_prefix(4);
+ memcpy(&compressed_size, data->data(), sizeof(uint32));
+ data->remove_prefix(4);
+
+ if (uncompressed_size > kMaxUncompressedChangesLength)
+ return false;
+ if (data->size() < compressed_size)
+ return false;
+
+ out_changes->clear();
+ if (uncompressed_size == 0)
+ return true;
+
+ out_changes->resize(uncompressed_size);
+ base::StringPiece compressed(data->data(), compressed_size);
+ data->remove_prefix(compressed_size);
+ return DecompressZlib(&(*out_changes)[0], uncompressed_size, compressed);
+}
+
+// These are the range coder symbols used in delta updates.
+enum {
+ SYMBOL_SAME = 0,
+ SYMBOL_INSERT = 1,
+ SYMBOL_DELETE = 2,
+ SYMBOL_CHANGED = 3,
+};
+
+bool ReadDeltaCRL(base::StringPiece* data,
+ const std::vector<std::string>& old_serials,
+ std::vector<std::string>* out_serials) {
+ std::vector<uint8> changes;
+ if (!ReadChanges(data, &changes))
+ return false;
+
+ size_t i = 0;
+ for (std::vector<uint8>::const_iterator k = changes.begin();
+ k != changes.end(); ++k) {
+ if (*k == SYMBOL_SAME) {
+ if (i >= old_serials.size())
+ return false;
+ out_serials->push_back(old_serials[i]);
+ i++;
+ } else if (*k == SYMBOL_INSERT) {
+ uint8 serial_length;
+ if (data->size() < sizeof(uint8))
+ return false;
+ memcpy(&serial_length, data->data(), sizeof(uint8));
+ data->remove_prefix(sizeof(uint8));
+
+ if (data->size() < serial_length)
+ return false;
+ const std::string serial(data->data(), serial_length);
+ data->remove_prefix(serial_length);
+
+ out_serials->push_back(serial);
+ } else if (*k == SYMBOL_DELETE) {
+ if (i >= old_serials.size())
+ return false;
+ i++;
+ } else {
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ if (i != old_serials.size())
+ return false;
+ return true;
+}
+
+bool CRLSet::ApplyDelta(const base::StringPiece& in_data,
+ scoped_refptr<CRLSet>* out_crl_set) {
+ base::StringPiece data(in_data);
+ scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
+ if (!header_dict.get())
+ return false;
+
+ std::string contents;
+ if (!header_dict->GetString("ContentType", &contents))
+ return false;
+ if (contents != "CRLSetDelta")
+ return false;
+
+ int version;
+ if (!header_dict->GetInteger("Version", &version) ||
+ version != kCurrentFileVersion) {
+ return false;
+ }
+
+ int sequence, delta_from;
+ if (!header_dict->GetInteger("Sequence", &sequence) ||
+ !header_dict->GetInteger("DeltaFrom", &delta_from) ||
+ delta_from < 0 ||
+ static_cast<uint32>(delta_from) != sequence_) {
+ return false;
+ }
+
+ double not_after;
+ if (!header_dict->GetDouble("NotAfter", &not_after)) {
+ // NotAfter is optional for now.
+ not_after = 0;
+ }
+ if (not_after < 0)
+ return false;
+
+ scoped_refptr<CRLSet> crl_set(new CRLSet);
+ crl_set->sequence_ = static_cast<uint32>(sequence);
+ crl_set->not_after_ = static_cast<uint64>(not_after);
+
+ if (!crl_set->CopyBlockedSPKIsFromHeader(header_dict.get()))
+ return false;
+
+ std::vector<uint8> crl_changes;
+
+ if (!ReadChanges(&data, &crl_changes))
+ return false;
+
+ size_t i = 0, j = 0;
+ for (std::vector<uint8>::const_iterator k = crl_changes.begin();
+ k != crl_changes.end(); ++k) {
+ if (*k == SYMBOL_SAME) {
+ if (i >= crls_.size())
+ return false;
+ crl_set->crls_.push_back(crls_[i]);
+ crl_set->crls_index_by_issuer_[crls_[i].first] = j;
+ i++;
+ j++;
+ } else if (*k == SYMBOL_INSERT) {
+ std::string parent_spki_hash;
+ std::vector<std::string> serials;
+ if (!ReadCRL(&data, &parent_spki_hash, &serials))
+ return false;
+ crl_set->crls_.push_back(std::make_pair(parent_spki_hash, serials));
+ crl_set->crls_index_by_issuer_[parent_spki_hash] = j;
+ j++;
+ } else if (*k == SYMBOL_DELETE) {
+ if (i >= crls_.size())
+ return false;
+ i++;
+ } else if (*k == SYMBOL_CHANGED) {
+ if (i >= crls_.size())
+ return false;
+ std::vector<std::string> serials;
+ if (!ReadDeltaCRL(&data, crls_[i].second, &serials))
+ return false;
+ crl_set->crls_.push_back(std::make_pair(crls_[i].first, serials));
+ crl_set->crls_index_by_issuer_[crls_[i].first] = j;
+ i++;
+ j++;
+ } else {
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ if (!data.empty())
+ return false;
+ if (i != crls_.size())
+ return false;
+
+ *out_crl_set = crl_set;
+ return true;
+}
+
+// static
+bool CRLSet::GetIsDeltaUpdate(const base::StringPiece& in_data,
+ bool* is_delta) {
+ base::StringPiece data(in_data);
+ scoped_ptr<base::DictionaryValue> header_dict(ReadHeader(&data));
+ if (!header_dict.get())
+ return false;
+
+ std::string contents;
+ if (!header_dict->GetString("ContentType", &contents))
+ return false;
+
+ if (contents == "CRLSet") {
+ *is_delta = false;
+ } else if (contents == "CRLSetDelta") {
+ *is_delta = true;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+std::string CRLSet::Serialize() const {
+ std::string header = base::StringPrintf(
+ "{"
+ "\"Version\":0,"
+ "\"ContentType\":\"CRLSet\","
+ "\"Sequence\":%u,"
+ "\"DeltaFrom\":0,"
+ "\"NumParents\":%u,"
+ "\"BlockedSPKIs\":[",
+ static_cast<unsigned>(sequence_),
+ static_cast<unsigned>(crls_.size()));
+
+ for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin();
+ i != blocked_spkis_.end(); ++i) {
+ std::string spki_hash_base64;
+ base::Base64Encode(*i, &spki_hash_base64);
+
+ if (i != blocked_spkis_.begin())
+ header += ",";
+ header += "\"" + spki_hash_base64 + "\"";
+ }
+ header += "]";
+ if (not_after_ != 0)
+ header += base::StringPrintf(",\"NotAfter\":%" PRIu64, not_after_);
+ header += "}";
+
+ size_t len = 2 /* header len */ + header.size();
+
+ for (CRLList::const_iterator i = crls_.begin(); i != crls_.end(); ++i) {
+ len += i->first.size() + 4 /* num serials */;
+ for (std::vector<std::string>::const_iterator j = i->second.begin();
+ j != i->second.end(); ++j) {
+ len += 1 /* serial length */ + j->size();
+ }
+ }
+
+ std::string ret;
+ char* out = WriteInto(&ret, len + 1 /* to include final NUL */);
+ size_t off = 0;
+ out[off++] = header.size();
+ out[off++] = header.size() >> 8;
+ memcpy(out + off, header.data(), header.size());
+ off += header.size();
+
+ for (CRLList::const_iterator i = crls_.begin(); i != crls_.end(); ++i) {
+ memcpy(out + off, i->first.data(), i->first.size());
+ off += i->first.size();
+ const uint32 num_serials = i->second.size();
+ memcpy(out + off, &num_serials, sizeof(num_serials));
+ off += sizeof(num_serials);
+
+ for (std::vector<std::string>::const_iterator j = i->second.begin();
+ j != i->second.end(); ++j) {
+ out[off++] = j->size();
+ memcpy(out + off, j->data(), j->size());
+ off += j->size();
+ }
+ }
+
+ CHECK_EQ(off, len);
+ return ret;
+}
+
+CRLSet::Result CRLSet::CheckSPKI(const base::StringPiece& spki_hash) const {
+ for (std::vector<std::string>::const_iterator i = blocked_spkis_.begin();
+ i != blocked_spkis_.end(); ++i) {
+ if (spki_hash.size() == i->size() &&
+ memcmp(spki_hash.data(), i->data(), i->size()) == 0) {
+ return REVOKED;
+ }
+ }
+
+ return GOOD;
+}
+
+CRLSet::Result CRLSet::CheckSerial(
+ const base::StringPiece& serial_number,
+ const base::StringPiece& issuer_spki_hash) const {
+ base::StringPiece serial(serial_number);
+
+ if (!serial.empty() && (serial[0] & 0x80) != 0) {
+ // This serial number is negative but the process which generates CRL sets
+ // will reject any certificates with negative serial numbers as invalid.
+ return UNKNOWN;
+ }
+
+ // Remove any leading zero bytes.
+ while (serial.size() > 1 && serial[0] == 0x00)
+ serial.remove_prefix(1);
+
+ std::map<std::string, size_t>::const_iterator i =
+ crls_index_by_issuer_.find(issuer_spki_hash.as_string());
+ if (i == crls_index_by_issuer_.end())
+ return UNKNOWN;
+ const std::vector<std::string>& serials = crls_[i->second].second;
+
+ for (std::vector<std::string>::const_iterator i = serials.begin();
+ i != serials.end(); ++i) {
+ if (base::StringPiece(*i) == serial)
+ return REVOKED;
+ }
+
+ return GOOD;
+}
+
+bool CRLSet::IsExpired() const {
+ if (not_after_ == 0)
+ return false;
+
+ uint64 now = base::Time::Now().ToTimeT();
+ return now > not_after_;
+}
+
+uint32 CRLSet::sequence() const {
+ return sequence_;
+}
+
+const CRLSet::CRLList& CRLSet::crls() const {
+ return crls_;
+}
+
+// static
+CRLSet* CRLSet::EmptyCRLSetForTesting() {
+ return ForTesting(false, NULL, "");
+}
+
+CRLSet* CRLSet::ExpiredCRLSetForTesting() {
+ return ForTesting(true, NULL, "");
+}
+
+// static
+CRLSet* CRLSet::ForTesting(bool is_expired,
+ const SHA256HashValue* issuer_spki,
+ const std::string& serial_number) {
+ CRLSet* crl_set = new CRLSet;
+ if (is_expired)
+ crl_set->not_after_ = 1;
+ if (issuer_spki != NULL) {
+ const std::string spki(reinterpret_cast<const char*>(issuer_spki->data),
+ sizeof(issuer_spki->data));
+ crl_set->crls_.push_back(make_pair(spki, std::vector<std::string>()));
+ crl_set->crls_index_by_issuer_[spki] = 0;
+ }
+
+ if (!serial_number.empty())
+ crl_set->crls_[0].second.push_back(serial_number);
+
+ return crl_set;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/crl_set.h b/chromium/net/cert/crl_set.h
new file mode 100644
index 00000000000..348005f097b
--- /dev/null
+++ b/chromium/net/cert/crl_set.h
@@ -0,0 +1,128 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_CRL_SET_H_
+#define NET_CERT_CRL_SET_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace net {
+
+// A CRLSet is a structure that lists the serial numbers of revoked
+// certificates from a number of issuers where issuers are identified by the
+// SHA256 of their SubjectPublicKeyInfo.
+class NET_EXPORT CRLSet : public base::RefCountedThreadSafe<CRLSet> {
+ public:
+ enum Result {
+ REVOKED, // the certificate should be rejected.
+ UNKNOWN, // the CRL for the certificate is not included in the set.
+ GOOD, // the certificate is not listed.
+ };
+
+ // Parse parses the bytes in |data| and, on success, puts a new CRLSet in
+ // |out_crl_set| and returns true.
+ static bool Parse(base::StringPiece data,
+ scoped_refptr<CRLSet>* out_crl_set);
+
+ // CheckSPKI checks whether the given SPKI has been listed as blocked.
+ // spki_hash: the SHA256 of the SubjectPublicKeyInfo of the certificate.
+ Result CheckSPKI(const base::StringPiece& spki_hash) const;
+
+ // CheckSerial returns the information contained in the set for a given
+ // certificate:
+ // serial_number: the serial number of the certificate
+ // issuer_spki_hash: the SHA256 of the SubjectPublicKeyInfo of the CRL
+ // signer
+ Result CheckSerial(
+ const base::StringPiece& serial_number,
+ const base::StringPiece& issuer_spki_hash) const;
+
+ // IsExpired returns true iff the current time is past the NotAfter time
+ // specified in the CRLSet.
+ bool IsExpired() const;
+
+ // ApplyDelta returns a new CRLSet in |out_crl_set| that is the result of
+ // updating the current CRL set with the delta information in |delta_bytes|.
+ bool ApplyDelta(const base::StringPiece& delta_bytes,
+ scoped_refptr<CRLSet>* out_crl_set);
+
+ // GetIsDeltaUpdate extracts the header from |bytes|, sets *is_delta to
+ // whether |bytes| is a delta CRL set or not and returns true. In the event
+ // of a parse error, it returns false.
+ static bool GetIsDeltaUpdate(const base::StringPiece& bytes, bool *is_delta);
+
+ // Serialize returns a string of bytes suitable for passing to Parse. Parsing
+ // and serializing a CRLSet is a lossless operation - the resulting bytes
+ // will be equal.
+ std::string Serialize() const;
+
+ // sequence returns the sequence number of this CRL set. CRL sets generated
+ // by the same source are given strictly monotonically increasing sequence
+ // numbers.
+ uint32 sequence() const;
+
+ // CRLList contains a list of (issuer SPKI hash, revoked serial numbers)
+ // pairs.
+ typedef std::vector< std::pair<std::string, std::vector<std::string> > >
+ CRLList;
+
+ // crls returns the internal state of this CRLSet. It should only be used in
+ // testing.
+ const CRLList& crls() const;
+
+ // EmptyCRLSetForTesting returns a valid, but empty, CRLSet for unit tests.
+ static CRLSet* EmptyCRLSetForTesting();
+
+ // ExpiredCRLSetForTesting returns a expired, empty CRLSet for unit tests.
+ static CRLSet* ExpiredCRLSetForTesting();
+
+ // ForTesting returns a CRLSet for testing. If |is_expired| is true, calling
+ // IsExpired on the result will return true. If |issuer_spki| is not NULL,
+ // the CRLSet will cover certificates issued by that SPKI. If |serial_number|
+ // is not emtpy, then that big-endian serial number will be considered to
+ // have been revoked by |issuer_spki|.
+ static CRLSet* ForTesting(bool is_expired,
+ const SHA256HashValue* issuer_spki,
+ const std::string& serial_number);
+
+ private:
+ CRLSet();
+ ~CRLSet();
+
+ friend class base::RefCountedThreadSafe<CRLSet>;
+
+ // CopyBlockedSPKIsFromHeader sets |blocked_spkis_| to the list of values
+ // from "BlockedSPKIs" in |header_dict|.
+ bool CopyBlockedSPKIsFromHeader(base::DictionaryValue* header_dict);
+
+ uint32 sequence_;
+ CRLList crls_;
+ // not_after_ contains the time, in UNIX epoch seconds, after which the
+ // CRLSet should be considered stale, or 0 if no such time was given.
+ uint64 not_after_;
+ // crls_index_by_issuer_ maps from issuer SPKI hashes to the index in |crls_|
+ // where the information for that issuer can be found. We have both |crls_|
+ // and |crls_index_by_issuer_| because, when applying a delta update, we need
+ // to identify a CRL by index.
+ std::map<std::string, size_t> crls_index_by_issuer_;
+ // blocked_spkis_ contains the SHA256 hashes of SPKIs which are to be blocked
+ // no matter where in a certificate chain they might appear.
+ std::vector<std::string> blocked_spkis_;
+};
+
+} // namespace net
+
+#endif // NET_CERT_CRL_SET_H_
diff --git a/chromium/net/cert/crl_set_unittest.cc b/chromium/net/cert/crl_set_unittest.cc
new file mode 100644
index 00000000000..88eb65467c0
--- /dev/null
+++ b/chromium/net/cert/crl_set_unittest.cc
@@ -0,0 +1,326 @@
+// Copyright (c) 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.
+
+#include "net/cert/crl_set.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// These data blocks were generated using a lot of code that is still in
+// development. For now, if you need to update them, you have to contact agl.
+static const uint8 kGIACRLSet[] = {
+ 0x60, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x31, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x7d, 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae,
+ 0x57, 0xfe, 0x02, 0x0c, 0xb2, 0xb7, 0x4f, 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7,
+ 0x08, 0xaf, 0xc9, 0xe7, 0x32, 0xac, 0xe2, 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05,
+ 0x0d, 0x00, 0x00, 0x00, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00,
+ 0x23, 0xb0, 0x0a, 0x10, 0x0e, 0x37, 0x06, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb1,
+ 0x0a, 0x16, 0x25, 0x42, 0x54, 0x00, 0x03, 0x00, 0x00, 0x14, 0x51, 0x0a, 0x16,
+ 0x69, 0xd1, 0xd7, 0x00, 0x03, 0x00, 0x00, 0x14, 0x52, 0x0a, 0x16, 0x70, 0x8c,
+ 0x22, 0x00, 0x03, 0x00, 0x00, 0x14, 0x53, 0x0a, 0x16, 0x71, 0x31, 0x2c, 0x00,
+ 0x03, 0x00, 0x00, 0x14, 0x54, 0x0a, 0x16, 0x7d, 0x75, 0x9d, 0x00, 0x03, 0x00,
+ 0x00, 0x14, 0x55, 0x0a, 0x1f, 0xee, 0xf9, 0x49, 0x00, 0x03, 0x00, 0x00, 0x23,
+ 0xae, 0x0a, 0x1f, 0xfc, 0xd1, 0x89, 0x00, 0x03, 0x00, 0x00, 0x23, 0xaf, 0x0a,
+ 0x61, 0xdd, 0xc7, 0x48, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0e, 0x0a, 0x61, 0xe6,
+ 0x12, 0x64, 0x00, 0x03, 0x00, 0x00, 0x18, 0x0f, 0x0a, 0x61, 0xe9, 0x46, 0x56,
+ 0x00, 0x03, 0x00, 0x00, 0x18, 0x10, 0x0a, 0x64, 0x63, 0x49, 0xd2, 0x00, 0x03,
+ 0x00, 0x00, 0x1d, 0x77,
+};
+
+static const uint8 kNoopDeltaCRL[] = {
+ 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c,
+ 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65,
+ 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x33, 0x37, 0x39,
+ 0x33, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73,
+ 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74,
+ 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d,
+ 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53,
+ 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
+ 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69,
+ 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+ 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61,
+ 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
+ 0x22, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62,
+ 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0x00, 0x01,
+};
+
+static const uint8 kAddCRLDelta[] = {
+ 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c,
+ 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65,
+ 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x35, 0x39, 0x34,
+ 0x38, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73,
+ 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74,
+ 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d,
+ 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x32, 0x2c, 0x22, 0x53,
+ 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
+ 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69,
+ 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+ 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61,
+ 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
+ 0x22, 0x7d, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62,
+ 0x60, 0x04, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0x00, 0x02, 0xe4, 0x2f,
+ 0x24, 0xbd, 0x4d, 0x37, 0xf4, 0xaa, 0x2e, 0x56, 0xb9, 0x79, 0xd8, 0x3d, 0x1e,
+ 0x65, 0x21, 0x9f, 0xe0, 0xe9, 0xe3, 0xa3, 0x82, 0xa1, 0xb3, 0xcb, 0x66, 0xc9,
+ 0x39, 0x55, 0xde, 0x75, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x01,
+ 0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x2f,
+ 0x01, 0x30, 0x01, 0x31, 0x01, 0x32,
+};
+
+static const uint8 kRemoveCRLDelta[] = {
+ 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c,
+ 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65,
+ 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x36, 0x31, 0x31,
+ 0x36, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73,
+ 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74,
+ 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d,
+ 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53,
+ 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
+ 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69,
+ 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+ 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61,
+ 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
+ 0x22, 0x7d, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62,
+ 0x60, 0x02, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x04, 0x00, 0x03,
+};
+
+static const uint8 kUpdateSerialsDelta[] = {
+ 0xc3, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x44, 0x65, 0x6c,
+ 0x74, 0x61, 0x22, 0x2c, 0x22, 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65,
+ 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x65, 0x78, 0x74, 0x55, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x22, 0x3a, 0x31, 0x33, 0x31, 0x31, 0x31, 0x32, 0x36, 0x34, 0x36,
+ 0x31, 0x2c, 0x22, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x73,
+ 0x22, 0x3a, 0x34, 0x33, 0x32, 0x30, 0x30, 0x2c, 0x22, 0x44, 0x65, 0x6c, 0x74,
+ 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x4e, 0x75, 0x6d,
+ 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x53,
+ 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b,
+ 0x65, 0x79, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x53, 0x69, 0x67, 0x6e, 0x69,
+ 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72,
+ 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61,
+ 0x64, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3a, 0x22,
+ 0x22, 0x7d, 0x01, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62,
+ 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x04, 0x00, 0x04, 0x2d, 0x00, 0x00,
+ 0x00, 0x15, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x62, 0x80, 0x00, 0x46, 0x2c, 0x00,
+ 0x45, 0x14, 0xac, 0x08, 0x10, 0x00, 0x00, 0xff, 0xff, 0x02, 0xe1, 0x00, 0x21,
+ 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10,
+ 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f,
+ 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00,
+ 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00,
+ 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23,
+ 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a,
+ 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d,
+ 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30,
+ 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03,
+ 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00,
+ 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0,
+ 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10,
+ 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f,
+ 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00,
+ 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00,
+ 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23,
+ 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a,
+ 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d,
+ 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30,
+ 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03,
+ 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00,
+ 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0,
+ 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10,
+ 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f,
+ 0x30, 0x00, 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00,
+ 0x03, 0x00, 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00,
+ 0x00, 0x23, 0xb0, 0x0a, 0x10, 0x0d, 0x7f, 0x30, 0x00, 0x03, 0x00, 0x00, 0x23,
+ 0xb0,
+};
+
+static const uint8 kBlockedSPKICRLSet[] = {
+ 0x8e, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x30, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x34, 0x37, 0x44, 0x45, 0x51, 0x70, 0x6a,
+ 0x38, 0x48, 0x42, 0x53, 0x61, 0x2b, 0x2f, 0x54, 0x49, 0x6d, 0x57, 0x2b, 0x35,
+ 0x4a, 0x43, 0x65, 0x75, 0x51, 0x65, 0x52, 0x6b, 0x6d, 0x35, 0x4e, 0x4d, 0x70,
+ 0x4a, 0x57, 0x5a, 0x47, 0x33, 0x68, 0x53, 0x75, 0x46, 0x55, 0x3d, 0x22, 0x5d,
+ 0x7d,
+};
+
+static const uint8 kExpiredCRLSet[] = {
+ 0x6d, 0x00, 0x7b, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
+ 0x65, 0x22, 0x3a, 0x22, 0x43, 0x52, 0x4c, 0x53, 0x65, 0x74, 0x22, 0x2c, 0x22,
+ 0x53, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x31, 0x2c, 0x22,
+ 0x44, 0x65, 0x6c, 0x74, 0x61, 0x46, 0x72, 0x6f, 0x6d, 0x22, 0x3a, 0x30, 0x2c,
+ 0x22, 0x4e, 0x75, 0x6d, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a,
+ 0x30, 0x2c, 0x22, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x53, 0x50, 0x4b,
+ 0x49, 0x73, 0x22, 0x3a, 0x5b, 0x5d, 0x2c, 0x22, 0x4e, 0x6f, 0x74, 0x41, 0x66,
+ 0x74, 0x65, 0x72, 0x22, 0x3a, 0x31, 0x7d,
+};
+
+// kGIASPKISHA256 is the SHA256 digest the Google Internet Authority's
+// SubjectPublicKeyInfo.
+static const uint8 kGIASPKISHA256[32] = {
+ 0xb6, 0xb9, 0x54, 0x32, 0xab, 0xae, 0x57, 0xfe, 0x02, 0x0c, 0xb2, 0xb7, 0x4f,
+ 0x4f, 0x9f, 0x91, 0x73, 0xc8, 0xc7, 0x08, 0xaf, 0xc9, 0xe7, 0x32, 0xac, 0xe2,
+ 0x32, 0x79, 0x04, 0x7c, 0x6d, 0x05,
+};
+
+TEST(CRLSetTest, Parse) {
+ base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet),
+ sizeof(kGIACRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ const net::CRLSet::CRLList& crls = set->crls();
+ ASSERT_EQ(1u, crls.size());
+ const std::vector<std::string>& serials = crls[0].second;
+ static const unsigned kExpectedNumSerials = 13;
+ ASSERT_EQ(kExpectedNumSerials, serials.size());
+ EXPECT_EQ(std::string("\x10\x0D\x7F\x30\x00\x03\x00\x00\x23\xB0", 10),
+ serials[0]);
+ EXPECT_EQ(std::string("\x64\x63\x49\xD2\x00\x03\x00\x00\x1D\x77", 10),
+ serials[kExpectedNumSerials - 1]);
+
+ const std::string gia_spki_hash(
+ reinterpret_cast<const char*>(kGIASPKISHA256),
+ sizeof(kGIASPKISHA256));
+ EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSerial(
+ std::string("\x16\x7D\x75\x9D\x00\x03\x00\x00\x14\x55", 10),
+ gia_spki_hash));
+ EXPECT_EQ(net::CRLSet::GOOD, set->CheckSerial(
+ std::string("\x47\x54\x3E\x79\x00\x03\x00\x00\x14\xF5", 10),
+ gia_spki_hash));
+
+ EXPECT_FALSE(set->IsExpired());
+}
+
+TEST(CRLSetTest, NoOpDeltaUpdate) {
+ base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet),
+ sizeof(kGIACRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ scoped_refptr<net::CRLSet> delta_set;
+ base::StringPiece delta(reinterpret_cast<const char*>(kNoopDeltaCRL),
+ sizeof(kNoopDeltaCRL));
+ EXPECT_TRUE(set->ApplyDelta(delta, &delta_set));
+ ASSERT_TRUE(delta_set.get() != NULL);
+
+ std::string out = delta_set->Serialize();
+ EXPECT_EQ(s.as_string(), out);
+}
+
+TEST(CRLSetTest, AddCRLDelta) {
+ base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet),
+ sizeof(kGIACRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ scoped_refptr<net::CRLSet> delta_set;
+ base::StringPiece delta(reinterpret_cast<const char*>(kAddCRLDelta),
+ sizeof(kAddCRLDelta));
+ EXPECT_TRUE(set->ApplyDelta(delta, &delta_set));
+ ASSERT_TRUE(delta_set.get() != NULL);
+
+ const net::CRLSet::CRLList& crls = delta_set->crls();
+ ASSERT_EQ(2u, crls.size());
+ const std::vector<std::string>& serials = crls[1].second;
+ ASSERT_EQ(12u, serials.size());
+ EXPECT_EQ(std::string("\x02", 1), serials[0]);
+ EXPECT_EQ(std::string("\x03", 1), serials[1]);
+ EXPECT_EQ(std::string("\x04", 1), serials[2]);
+}
+
+TEST(CRLSetTest, AddRemoveCRLDelta) {
+ base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet),
+ sizeof(kGIACRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ scoped_refptr<net::CRLSet> delta_set;
+ base::StringPiece delta(reinterpret_cast<const char*>(kAddCRLDelta),
+ sizeof(kAddCRLDelta));
+ EXPECT_TRUE(set->ApplyDelta(delta, &delta_set));
+ ASSERT_TRUE(delta_set.get() != NULL);
+
+ scoped_refptr<net::CRLSet> delta2_set;
+ base::StringPiece delta2(reinterpret_cast<const char*>(kRemoveCRLDelta),
+ sizeof(kRemoveCRLDelta));
+ EXPECT_TRUE(delta_set->ApplyDelta(delta2, &delta2_set));
+ ASSERT_TRUE(delta2_set.get() != NULL);
+
+ const net::CRLSet::CRLList& crls = delta2_set->crls();
+ ASSERT_EQ(1u, crls.size());
+ const std::vector<std::string>& serials = crls[0].second;
+ ASSERT_EQ(13u, serials.size());
+}
+
+TEST(CRLSetTest, UpdateSerialsDelta) {
+ base::StringPiece s(reinterpret_cast<const char*>(kGIACRLSet),
+ sizeof(kGIACRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ scoped_refptr<net::CRLSet> delta_set;
+ base::StringPiece delta(reinterpret_cast<const char*>(kUpdateSerialsDelta),
+ sizeof(kUpdateSerialsDelta));
+ EXPECT_TRUE(set->ApplyDelta(delta, &delta_set));
+ ASSERT_TRUE(delta_set.get() != NULL);
+
+ const net::CRLSet::CRLList& crls = delta_set->crls();
+ ASSERT_EQ(1u, crls.size());
+ const std::vector<std::string>& serials = crls[0].second;
+ EXPECT_EQ(45u, serials.size());
+}
+
+TEST(CRLSetTest, BlockedSPKIs) {
+ base::StringPiece s(reinterpret_cast<const char*>(kBlockedSPKICRLSet),
+ sizeof(kBlockedSPKICRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ const uint8 spki_hash[] = {
+ 227, 176, 196, 66, 152, 252, 28, 20, 154, 251, 244, 200, 153, 111, 185, 36,
+ 39, 174, 65, 228, 100, 155, 147, 76, 164, 149, 153, 27, 120, 82, 184, 85,
+ 0,
+ };
+
+ EXPECT_EQ(net::CRLSet::GOOD, set->CheckSPKI(""));
+ EXPECT_EQ(net::CRLSet::REVOKED, set->CheckSPKI(
+ reinterpret_cast<const char*>(spki_hash)));
+}
+
+TEST(CRLSetTest, Expired) {
+ // This CRLSet has an expiry value set to one second past midnight, 1st Jan,
+ // 1970.
+ base::StringPiece s(reinterpret_cast<const char*>(kExpiredCRLSet),
+ sizeof(kExpiredCRLSet));
+ scoped_refptr<net::CRLSet> set;
+ EXPECT_TRUE(net::CRLSet::Parse(s, &set));
+ ASSERT_TRUE(set.get() != NULL);
+
+ EXPECT_TRUE(set->IsExpired());
+}
diff --git a/chromium/net/cert/ev_root_ca_metadata.cc b/chromium/net/cert/ev_root_ca_metadata.cc
new file mode 100644
index 00000000000..9e611a3093d
--- /dev/null
+++ b/chromium/net/cert/ev_root_ca_metadata.cc
@@ -0,0 +1,571 @@
+// Copyright (c) 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.
+
+#include "net/cert/ev_root_ca_metadata.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <cert.h>
+#include <pkcs11n.h>
+#include <secerr.h>
+#include <secoid.h>
+#elif defined(OS_WIN)
+#include <stdlib.h>
+#endif
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#if defined(USE_NSS) || defined(OS_IOS)
+#include "crypto/nss_util.h"
+#endif
+
+namespace net {
+
+// Raw metadata.
+struct EVMetadata {
+ // kMaxOIDsPerCA is the number of OIDs that we can support per root CA. At
+ // least one CA has different EV policies for businuss vs government
+ // entities and, in the case of cross-signing, we might need to list another
+ // CA's policy OID under the cross-signing root.
+ static const size_t kMaxOIDsPerCA = 2;
+ // This is the maximum length of an OID string (including the trailing NUL).
+ static const size_t kMaxOIDLength = 32;
+
+ // The SHA-1 fingerprint of the root CA certificate, used as a unique
+ // identifier for a root CA certificate.
+ SHA1HashValue fingerprint;
+
+ // The EV policy OIDs of the root CA.
+ const char policy_oids[kMaxOIDsPerCA][kMaxOIDLength];
+};
+
+static const EVMetadata ev_root_ca_metadata[] = {
+ // AC Camerfirma S.A. Chambers of Commerce Root - 2008
+ // https://www.camerfirma.com
+ { { { 0x78, 0x6a, 0x74, 0xac, 0x76, 0xab, 0x14, 0x7f, 0x9c, 0x6a,
+ 0x30, 0x50, 0xba, 0x9e, 0xa8, 0x7e, 0xfe, 0x9a, 0xce, 0x3c } },
+ { // AC Camerfirma uses the last two arcs to track how the private key is
+ // managed - the effective verification policy is the same.
+ "1.3.6.1.4.1.17326.10.14.2.1.2",
+ "1.3.6.1.4.1.17326.10.14.2.2.2", },
+ },
+ // AC Camerfirma S.A. Global Chambersign Root - 2008
+ // https://server2.camerfirma.com:8082
+ { { { 0x4a, 0xbd, 0xee, 0xec, 0x95, 0x0d, 0x35, 0x9c, 0x89, 0xae,
+ 0xc7, 0x52, 0xa1, 0x2c, 0x5b, 0x29, 0xf6, 0xd6, 0xaa, 0x0c } },
+ { // AC Camerfirma uses the last two arcs to track how the private key is
+ // managed - the effective verification policy is the same.
+ "1.3.6.1.4.1.17326.10.8.12.1.2",
+ "1.3.6.1.4.1.17326.10.8.12.2.2", },
+ },
+ // AddTrust External CA Root
+ // https://addtrustexternalcaroot-ev.comodoca.com
+ { { { 0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78,
+ 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68 } },
+ {
+ "1.3.6.1.4.1.6449.1.2.1.5.1",
+ // This is the Network Solutions EV OID. However, this root
+ // cross-certifies NetSol and so we need it here too.
+ "1.3.6.1.4.1.782.1.2.1.8.1",
+ },
+ },
+ // AffirmTrust Commercial
+ // https://commercial.affirmtrust.com/
+ { { { 0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57,
+ 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7 } },
+ {"1.3.6.1.4.1.34697.2.1", ""},
+ },
+ // AffirmTrust Networking
+ // https://networking.affirmtrust.com:4431
+ { { { 0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66,
+ 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f } },
+ {"1.3.6.1.4.1.34697.2.2", ""},
+ },
+ // AffirmTrust Premium
+ // https://premium.affirmtrust.com:4432/
+ { { { 0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6,
+ 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27 } },
+ {"1.3.6.1.4.1.34697.2.3", ""},
+ },
+ // AffirmTrust Premium ECC
+ // https://premiumecc.affirmtrust.com:4433/
+ { { { 0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01,
+ 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb } },
+ {"1.3.6.1.4.1.34697.2.4", ""},
+ },
+ // CertPlus Class 2 Primary CA (KEYNECTIS)
+ // https://www.keynectis.com/
+ { { { 0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79,
+ 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb } },
+ {"1.3.6.1.4.1.22234.2.5.2.3.1", ""},
+ },
+ // Certum Trusted Network CA
+ // https://juice.certum.pl/
+ { { { 0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f,
+ 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e } },
+ {"1.2.616.1.113527.2.5.1.1", ""},
+ },
+ // COMODO Certification Authority
+ // https://secure.comodo.com/
+ { { { 0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5,
+ 0xa6, 0x0c, 0xba, 0x6a, 0xbe, 0xd1, 0xf7, 0xbd, 0xef, 0x7b } },
+ {"1.3.6.1.4.1.6449.1.2.1.5.1", ""},
+ },
+ // COMODO Certification Authority (reissued certificate with NotBefore of Jan
+ // 1 00:00:00 2011 GMT)
+ // https://secure.comodo.com/
+ { { { 0xee, 0x86, 0x93, 0x87, 0xff, 0xfd, 0x83, 0x49, 0xab, 0x5a,
+ 0xd1, 0x43, 0x22, 0x58, 0x87, 0x89, 0xa4, 0x57, 0xb0, 0x12 } },
+ {"1.3.6.1.4.1.6449.1.2.1.5.1", ""},
+ },
+ // COMODO ECC Certification Authority
+ // https://comodoecccertificationauthority-ev.comodoca.com/
+ { { { 0x9f, 0x74, 0x4e, 0x9f, 0x2b, 0x4d, 0xba, 0xec, 0x0f, 0x31,
+ 0x2c, 0x50, 0xb6, 0x56, 0x3b, 0x8e, 0x2d, 0x93, 0xc3, 0x11 } },
+ {"1.3.6.1.4.1.6449.1.2.1.5.1", ""},
+ },
+ // Cybertrust Global Root
+ // https://evup.cybertrust.ne.jp/ctj-ev-upgrader/evseal.gif
+ { { { 0x5f, 0x43, 0xe5, 0xb1, 0xbf, 0xf8, 0x78, 0x8c, 0xac, 0x1c,
+ 0xc7, 0xca, 0x4a, 0x9a, 0xc6, 0x22, 0x2b, 0xcc, 0x34, 0xc6 } },
+ {"1.3.6.1.4.1.6334.1.100.1", ""},
+ },
+ // DigiCert High Assurance EV Root CA
+ // https://www.digicert.com
+ { { { 0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c,
+ 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25 } },
+ {"2.16.840.1.114412.2.1", ""},
+ },
+ // D-TRUST Root Class 3 CA 2 EV 2009
+ // https://certdemo-ev-valid.ssl.d-trust.net/
+ { { { 0x96, 0xc9, 0x1b, 0x0b, 0x95, 0xb4, 0x10, 0x98, 0x42, 0xfa,
+ 0xd0, 0xd8, 0x22, 0x79, 0xfe, 0x60, 0xfa, 0xb9, 0x16, 0x83 } },
+ {"1.3.6.1.4.1.4788.2.202.1", ""},
+ },
+ // Entrust.net Secure Server Certification Authority
+ // https://www.entrust.net/
+ { { { 0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b,
+ 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39 } },
+ {"2.16.840.1.114028.10.1.2", ""},
+ },
+ // Entrust Root Certification Authority
+ // https://www.entrust.net/
+ { { { 0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda,
+ 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9 } },
+ {"2.16.840.1.114028.10.1.2", ""},
+ },
+ // Equifax Secure Certificate Authority (GeoTrust)
+ // https://www.geotrust.com/
+ { { { 0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74,
+ 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a } },
+ {"1.3.6.1.4.1.14370.1.6", ""},
+ },
+ // GeoTrust Primary Certification Authority
+ // https://www.geotrust.com/
+ { { { 0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54,
+ 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96 } },
+ {"1.3.6.1.4.1.14370.1.6", ""},
+ },
+ // GeoTrust Primary Certification Authority - G2
+ { { { 0x8d, 0x17, 0x84, 0xd5, 0x37, 0xf3, 0x03, 0x7d, 0xec, 0x70,
+ 0xfe, 0x57, 0x8b, 0x51, 0x9a, 0x99, 0xe6, 0x10, 0xd7, 0xb0 } },
+ {"1.3.6.1.4.1.14370.1.6", ""},
+ },
+ // GeoTrust Primary Certification Authority - G3
+ { { { 0x03, 0x9e, 0xed, 0xb8, 0x0b, 0xe7, 0xa0, 0x3c, 0x69, 0x53,
+ 0x89, 0x3b, 0x20, 0xd2, 0xd9, 0x32, 0x3a, 0x4c, 0x2a, 0xfd } },
+ {"1.3.6.1.4.1.14370.1.6", ""},
+ },
+ // GlobalSign Root CA - R2
+ // https://www.globalsign.com/
+ { { { 0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04,
+ 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe } },
+ {"1.3.6.1.4.1.4146.1.1", ""},
+ },
+ // GlobalSign Root CA
+ { { { 0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8,
+ 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c } },
+ {"1.3.6.1.4.1.4146.1.1", ""},
+ },
+ // GlobalSign Root CA - R3
+ // https://2029.globalsign.com/
+ { { { 0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45,
+ 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad } },
+ {"1.3.6.1.4.1.4146.1.1", ""},
+ },
+ // Go Daddy Class 2 Certification Authority
+ // https://www.godaddy.com/
+ { { { 0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26,
+ 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4 } },
+ {"2.16.840.1.114413.1.7.23.3", ""},
+ },
+ // Go Daddy Root Certificate Authority - G2
+ // https://valid.gdig2.catest.godaddy.com/
+ { { { 0x47, 0xbe, 0xab, 0xc9, 0x22, 0xea, 0xe8, 0x0e, 0x78, 0x78,
+ 0x34, 0x62, 0xa7, 0x9f, 0x45, 0xc2, 0x54, 0xfd, 0xe6, 0x8b } },
+ {"2.16.840.1.114413.1.7.23.3", ""},
+ },
+ // GTE CyberTrust Global Root
+ // https://www.cybertrust.ne.jp/
+ { { { 0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34,
+ 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74 } },
+ {"1.3.6.1.4.1.6334.1.100.1", ""},
+ },
+ // Izenpe.com - SHA256 root
+ // The first OID is for businesses and the second for government entities.
+ // These are the test sites, respectively:
+ // https://servicios.izenpe.com
+ // https://servicios1.izenpe.com
+ { { { 0x2f, 0x78, 0x3d, 0x25, 0x52, 0x18, 0xa7, 0x4a, 0x65, 0x39,
+ 0x71, 0xb5, 0x2c, 0xa2, 0x9c, 0x45, 0x15, 0x6f, 0xe9, 0x19} },
+ {"1.3.6.1.4.1.14777.6.1.1", "1.3.6.1.4.1.14777.6.1.2"},
+ },
+ // Izenpe.com - SHA1 root
+ // Windows XP finds this, SHA1, root instead. The policy OIDs are the same as
+ // for the SHA256 root, above.
+ { { { 0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a,
+ 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd} },
+ {"1.3.6.1.4.1.14777.6.1.1", "1.3.6.1.4.1.14777.6.1.2"},
+ },
+ // Network Solutions Certificate Authority
+ // https://www.networksolutions.com/website-packages/index.jsp
+ { { { 0x74, 0xf8, 0xa3, 0xc3, 0xef, 0xe7, 0xb3, 0x90, 0x06, 0x4b,
+ 0x83, 0x90, 0x3c, 0x21, 0x64, 0x60, 0x20, 0xe5, 0xdf, 0xce } },
+ {"1.3.6.1.4.1.782.1.2.1.8.1", ""},
+ },
+ // Network Solutions Certificate Authority (reissued certificate with
+ // NotBefore of Jan 1 00:00:00 2011 GMT).
+ // https://www.networksolutions.com/website-packages/index.jsp
+ { { { 0x71, 0x89, 0x9a, 0x67, 0xbf, 0x33, 0xaf, 0x31, 0xbe, 0xfd,
+ 0xc0, 0x71, 0xf8, 0xf7, 0x33, 0xb1, 0x83, 0x85, 0x63, 0x32 } },
+ {"1.3.6.1.4.1.782.1.2.1.8.1", ""},
+ },
+ // QuoVadis Root CA 2
+ // https://www.quovadis.bm/
+ { { { 0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2,
+ 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7 } },
+ {"1.3.6.1.4.1.8024.0.2.100.1.2", ""},
+ },
+ // SecureTrust CA, SecureTrust Corporation
+ // https://www.securetrust.com
+ // https://www.trustwave.com/
+ { { { 0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96,
+ 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11 } },
+ {"2.16.840.1.114404.1.1.2.4.1", ""},
+ },
+ // Secure Global CA, SecureTrust Corporation
+ { { { 0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86,
+ 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b } },
+ {"2.16.840.1.114404.1.1.2.4.1", ""},
+ },
+ // Security Communication RootCA1
+ // https://www.secomtrust.net/contact/form.html
+ { { { 0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e,
+ 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7 } },
+ {"1.2.392.200091.100.721.1", ""},
+ },
+ // Security Communication EV RootCA1
+ // https://www.secomtrust.net/contact/form.html
+ { { { 0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae,
+ 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d } },
+ {"1.2.392.200091.100.721.1", ""},
+ },
+ // StartCom Certification Authority
+ // https://www.startssl.com/
+ { { { 0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6,
+ 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f } },
+ {"1.3.6.1.4.1.23223.1.1.1", ""},
+ },
+ // Starfield Class 2 Certification Authority
+ // https://www.starfieldtech.com/
+ { { { 0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03,
+ 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a } },
+ {"2.16.840.1.114414.1.7.23.3", ""},
+ },
+ // Starfield Root Certificate Authority - G2
+ // https://valid.sfig2.catest.starfieldtech.com/
+ { { { 0xb5, 0x1c, 0x06, 0x7c, 0xee, 0x2b, 0x0c, 0x3d, 0xf8, 0x55,
+ 0xab, 0x2d, 0x92, 0xf4, 0xfe, 0x39, 0xd4, 0xe7, 0x0f, 0x0e } },
+ {"2.16.840.1.114414.1.7.23.3", ""},
+ },
+ // Starfield Services Root Certificate Authority - G2
+ // https://valid.sfsg2.catest.starfieldtech.com/
+ { { { 0x92, 0x5a, 0x8f, 0x8d, 0x2c, 0x6d, 0x04, 0xe0, 0x66, 0x5f,
+ 0x59, 0x6a, 0xff, 0x22, 0xd8, 0x63, 0xe8, 0x25, 0x6f, 0x3f } },
+ {"2.16.840.1.114414.1.7.24.3", ""},
+ },
+ // SwissSign Gold CA - G2
+ // https://testevg2.swisssign.net/
+ { { { 0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4,
+ 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61 } },
+ {"2.16.756.1.89.1.2.1.1", ""},
+ },
+ // Thawte Premium Server CA
+ // https://www.thawte.com/
+ { { { 0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d,
+ 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a } },
+ {"2.16.840.1.113733.1.7.48.1", ""},
+ },
+ // thawte Primary Root CA
+ // https://www.thawte.com/
+ { { { 0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5,
+ 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81 } },
+ {"2.16.840.1.113733.1.7.48.1", ""},
+ },
+ // thawte Primary Root CA - G2
+ { { { 0xaa, 0xdb, 0xbc, 0x22, 0x23, 0x8f, 0xc4, 0x01, 0xa1, 0x27,
+ 0xbb, 0x38, 0xdd, 0xf4, 0x1d, 0xdb, 0x08, 0x9e, 0xf0, 0x12 } },
+ {"2.16.840.1.113733.1.7.48.1", ""},
+ },
+ // thawte Primary Root CA - G3
+ { { { 0xf1, 0x8b, 0x53, 0x8d, 0x1b, 0xe9, 0x03, 0xb6, 0xa6, 0xf0,
+ 0x56, 0x43, 0x5b, 0x17, 0x15, 0x89, 0xca, 0xf3, 0x6b, 0xf2 } },
+ {"2.16.840.1.113733.1.7.48.1", ""},
+ },
+ // TWCA Root Certification Authority
+ // https://evssldemo.twca.com.tw/index.html
+ { { { 0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97,
+ 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48 } },
+ {"1.3.6.1.4.1.40869.1.1.22.3", ""},
+ },
+ // T-TeleSec GlobalRoot Class 3
+ // http://www.telesec.de/ / https://root-class3.test.telesec.de/
+ { { { 0x55, 0xa6, 0x72, 0x3e, 0xcb, 0xf2, 0xec, 0xcd, 0xc3, 0x23,
+ 0x74, 0x70, 0x19, 0x9d, 0x2a, 0xbe, 0x11, 0xe3, 0x81, 0xd1 } },
+ {"1.3.6.1.4.1.7879.13.24.1", "" },
+ },
+ // UTN - DATACorp SGC
+ { { { 0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd,
+ 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4 } },
+ {"1.3.6.1.4.1.6449.1.2.1.5.1", ""},
+ },
+ // UTN-USERFirst-Hardware
+ { { { 0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87,
+ 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7 } },
+ {
+ "1.3.6.1.4.1.6449.1.2.1.5.1",
+ // This is the Network Solutions EV OID. However, this root
+ // cross-certifies NetSol and so we need it here too.
+ "1.3.6.1.4.1.782.1.2.1.8.1",
+ },
+ },
+ // ValiCert Class 2 Policy Validation Authority
+ { { { 0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1,
+ 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6 } },
+ {"2.16.840.1.114413.1.7.23.3", "2.16.840.1.114414.1.7.23.3"},
+ },
+ // VeriSign Class 3 Public Primary Certification Authority
+ // https://www.verisign.com/
+ { { { 0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45,
+ 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2 } },
+ {"2.16.840.1.113733.1.7.23.6", ""},
+ },
+ // VeriSign Class 3 Public Primary Certification Authority - G4
+ { { { 0x22, 0xD5, 0xD8, 0xDF, 0x8F, 0x02, 0x31, 0xD1, 0x8D, 0xF7,
+ 0x9D, 0xB7, 0xCF, 0x8A, 0x2D, 0x64, 0xC9, 0x3F, 0x6C, 0x3A } },
+ {"2.16.840.1.113733.1.7.23.6", ""},
+ },
+ // VeriSign Class 3 Public Primary Certification Authority - G5
+ // https://www.verisign.com/
+ { { { 0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58,
+ 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5 } },
+ {"2.16.840.1.113733.1.7.23.6", ""},
+ },
+ // VeriSign Universal Root Certification Authority
+ { { { 0x36, 0x79, 0xca, 0x35, 0x66, 0x87, 0x72, 0x30, 0x4d, 0x30,
+ 0xa5, 0xfb, 0x87, 0x3b, 0x0f, 0xa7, 0x7b, 0xb7, 0x0d, 0x54 } },
+ {"2.16.840.1.113733.1.7.23.6", ""},
+ },
+ // Wells Fargo WellsSecure Public Root Certificate Authority
+ // https://nerys.wellsfargo.com/test.html
+ { { { 0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e,
+ 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee } },
+ {"2.16.840.1.114171.500.9", ""},
+ },
+ // XRamp Global Certification Authority
+ { { { 0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04,
+ 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6 } },
+ {"2.16.840.1.114404.1.1.2.4.1", ""},
+ }
+};
+
+static base::LazyInstance<EVRootCAMetadata>::Leaky
+ g_ev_root_ca_metadata = LAZY_INSTANCE_INITIALIZER;
+
+// static
+EVRootCAMetadata* EVRootCAMetadata::GetInstance() {
+ return g_ev_root_ca_metadata.Pointer();
+}
+
+#if defined(USE_NSS) || defined(OS_IOS)
+bool EVRootCAMetadata::IsEVPolicyOID(PolicyOID policy_oid) const {
+ return policy_oids_.find(policy_oid) != policy_oids_.end();
+}
+
+bool EVRootCAMetadata::HasEVPolicyOID(
+ const SHA1HashValue& fingerprint,
+ PolicyOID policy_oid) const {
+ PolicyOIDMap::const_iterator iter = ev_policy_.find(fingerprint);
+ if (iter == ev_policy_.end())
+ return false;
+ for (std::vector<PolicyOID>::const_iterator
+ j = iter->second.begin(); j != iter->second.end(); ++j) {
+ if (*j == policy_oid)
+ return true;
+ }
+ return false;
+}
+
+bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint,
+ const char* policy) {
+ if (ev_policy_.find(fingerprint) != ev_policy_.end())
+ return false;
+
+ PolicyOID oid;
+ if (!RegisterOID(policy, &oid))
+ return false;
+
+ ev_policy_[fingerprint].push_back(oid);
+ policy_oids_.insert(oid);
+
+ return true;
+}
+
+bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) {
+ PolicyOIDMap::iterator it = ev_policy_.find(fingerprint);
+ if (it == ev_policy_.end())
+ return false;
+ PolicyOID oid = it->second[0];
+ ev_policy_.erase(it);
+ policy_oids_.erase(oid);
+ return true;
+}
+
+// static
+bool EVRootCAMetadata::RegisterOID(const char* policy,
+ PolicyOID* out) {
+ PRUint8 buf[64];
+ SECItem oid_item;
+ oid_item.data = buf;
+ oid_item.len = sizeof(buf);
+ SECStatus status = SEC_StringToOID(NULL, &oid_item, policy, 0);
+ if (status != SECSuccess)
+ return false;
+
+ // Register the OID.
+ SECOidData od;
+ od.oid.len = oid_item.len;
+ od.oid.data = oid_item.data;
+ od.offset = SEC_OID_UNKNOWN;
+ od.desc = policy;
+ od.mechanism = CKM_INVALID_MECHANISM;
+ od.supportedExtension = INVALID_CERT_EXTENSION;
+ *out = SECOID_AddEntry(&od);
+ return *out != SEC_OID_UNKNOWN;
+}
+
+#elif defined(OS_WIN)
+
+bool EVRootCAMetadata::IsEVPolicyOID(PolicyOID policy_oid) const {
+ for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) {
+ for (size_t j = 0; j < arraysize(ev_root_ca_metadata[i].policy_oids); j++) {
+ if (ev_root_ca_metadata[i].policy_oids[j][0] == '\0')
+ break;
+ if (strcmp(policy_oid, ev_root_ca_metadata[i].policy_oids[j]) == 0)
+ return true;
+ }
+ }
+
+ for (ExtraEVCAMap::const_iterator i = extra_cas_.begin();
+ i != extra_cas_.end(); i++) {
+ if (i->second == policy_oid)
+ return true;
+ }
+
+ return false;
+}
+
+bool EVRootCAMetadata::HasEVPolicyOID(const SHA1HashValue& fingerprint,
+ PolicyOID policy_oid) const {
+ for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) {
+ if (!fingerprint.Equals(ev_root_ca_metadata[i].fingerprint))
+ continue;
+ for (size_t j = 0; j < arraysize(ev_root_ca_metadata[i].policy_oids); j++) {
+ if (ev_root_ca_metadata[i].policy_oids[j][0] == '\0')
+ break;
+ if (strcmp(policy_oid, ev_root_ca_metadata[i].policy_oids[j]) == 0)
+ return true;
+ }
+ return false;
+ }
+
+ ExtraEVCAMap::const_iterator it = extra_cas_.find(fingerprint);
+ return it != extra_cas_.end() && it->second == policy_oid;
+}
+
+bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint,
+ const char* policy) {
+ for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) {
+ if (fingerprint.Equals(ev_root_ca_metadata[i].fingerprint))
+ return false;
+ }
+
+ if (extra_cas_.find(fingerprint) != extra_cas_.end())
+ return false;
+
+ extra_cas_[fingerprint] = policy;
+ return true;
+}
+
+bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) {
+ ExtraEVCAMap::iterator it = extra_cas_.find(fingerprint);
+ if (it == extra_cas_.end())
+ return false;
+ extra_cas_.erase(it);
+ return true;
+}
+
+#else
+
+// These are just stub functions for platforms where we don't use this EV
+// metadata.
+
+bool EVRootCAMetadata::AddEVCA(const SHA1HashValue& fingerprint,
+ const char* policy) {
+ return true;
+}
+
+bool EVRootCAMetadata::RemoveEVCA(const SHA1HashValue& fingerprint) {
+ return true;
+}
+
+#endif
+
+EVRootCAMetadata::EVRootCAMetadata() {
+ // Constructs the object from the raw metadata in ev_root_ca_metadata.
+#if defined(USE_NSS) || defined(OS_IOS)
+ crypto::EnsureNSSInit();
+
+ for (size_t i = 0; i < arraysize(ev_root_ca_metadata); i++) {
+ const EVMetadata& metadata = ev_root_ca_metadata[i];
+ for (size_t j = 0; j < arraysize(metadata.policy_oids); j++) {
+ if (metadata.policy_oids[j][0] == '\0')
+ break;
+ const char* policy_oid = metadata.policy_oids[j];
+
+ PolicyOID policy;
+ if (!RegisterOID(policy_oid, &policy)) {
+ LOG(ERROR) << "Failed to register OID: " << policy_oid;
+ continue;
+ }
+
+ ev_policy_[metadata.fingerprint].push_back(policy);
+ policy_oids_.insert(policy);
+ }
+ }
+#endif
+}
+
+EVRootCAMetadata::~EVRootCAMetadata() { }
+
+} // namespace net
diff --git a/chromium/net/cert/ev_root_ca_metadata.h b/chromium/net/cert/ev_root_ca_metadata.h
new file mode 100644
index 00000000000..aad78484cd1
--- /dev/null
+++ b/chromium/net/cert/ev_root_ca_metadata.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_EV_ROOT_CA_METADATA_H_
+#define NET_CERT_EV_ROOT_CA_METADATA_H_
+
+#include "build/build_config.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <secoidt.h>
+#endif
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/cert/x509_certificate.h"
+
+namespace base {
+template <typename T>
+struct DefaultLazyInstanceTraits;
+} // namespace base
+
+namespace net {
+
+// A singleton. This class stores the meta data of the root CAs that issue
+// extended-validation (EV) certificates.
+class NET_EXPORT_PRIVATE EVRootCAMetadata {
+ public:
+#if defined(USE_NSS) || defined(OS_IOS)
+ typedef SECOidTag PolicyOID;
+#elif defined(OS_WIN)
+ typedef const char* PolicyOID;
+#endif
+
+ static EVRootCAMetadata* GetInstance();
+
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_IOS)
+ // Returns true if policy_oid is an EV policy OID of some root CA.
+ bool IsEVPolicyOID(PolicyOID policy_oid) const;
+
+ // Returns true if the root CA with the given certificate fingerprint has
+ // the EV policy OID policy_oid.
+ bool HasEVPolicyOID(const SHA1HashValue& fingerprint,
+ PolicyOID policy_oid) const;
+#endif
+
+ // AddEVCA adds an EV CA to the list of known EV CAs with the given policy.
+ // |policy| is expressed as a string of dotted numbers. It returns true on
+ // success.
+ bool AddEVCA(const SHA1HashValue& fingerprint, const char* policy);
+
+ // RemoveEVCA removes an EV CA that was previously added by AddEVCA. It
+ // returns true on success.
+ bool RemoveEVCA(const SHA1HashValue& fingerprint);
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<EVRootCAMetadata>;
+
+ EVRootCAMetadata();
+ ~EVRootCAMetadata();
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ typedef std::map<SHA1HashValue, std::vector<PolicyOID>,
+ SHA1HashValueLessThan> PolicyOIDMap;
+
+ // RegisterOID registers |policy|, a policy OID in dotted string form, and
+ // writes the memoized form to |*out|. It returns true on success.
+ static bool RegisterOID(const char* policy, PolicyOID* out);
+
+ PolicyOIDMap ev_policy_;
+ std::set<PolicyOID> policy_oids_;
+#elif defined(OS_WIN)
+ typedef std::map<SHA1HashValue, std::string,
+ SHA1HashValueLessThan> ExtraEVCAMap;
+
+ // extra_cas_ contains any EV CA metadata that was added at runtime.
+ ExtraEVCAMap extra_cas_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(EVRootCAMetadata);
+};
+
+} // namespace net
+
+#endif // NET_CERT_EV_ROOT_CA_METADATA_H_
diff --git a/chromium/net/cert/ev_root_ca_metadata_unittest.cc b/chromium/net/cert/ev_root_ca_metadata_unittest.cc
new file mode 100644
index 00000000000..2c845dbfcff
--- /dev/null
+++ b/chromium/net/cert/ev_root_ca_metadata_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 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.
+
+#include "net/cert/ev_root_ca_metadata.h"
+
+#include "net/cert/x509_cert_types.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS)
+#include "crypto/scoped_nss_types.h"
+#endif
+
+namespace net {
+
+namespace {
+
+static const char kVerisignPolicy[] = "2.16.840.1.113733.1.7.23.6";
+static const char kThawtePolicy[] = "2.16.840.1.113733.1.7.48.1";
+static const char kFakePolicy[] = "2.16.840.1.42";
+static const SHA1HashValue kVerisignFingerprint =
+ { { 0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45,
+ 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2 } };
+static const SHA1HashValue kFakeFingerprint =
+ { { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 } };
+
+#if defined(USE_NSS) || defined(OS_WIN)
+class EVOidData {
+ public:
+ EVOidData();
+ bool Init();
+
+ EVRootCAMetadata::PolicyOID verisign_policy;
+ EVRootCAMetadata::PolicyOID thawte_policy;
+ EVRootCAMetadata::PolicyOID fake_policy;
+};
+
+#endif // defined(USE_NSS) || defined(OS_WIN)
+
+#if defined(USE_NSS)
+
+SECOidTag RegisterOID(PLArenaPool* arena, const char* oid_string) {
+ SECOidData oid_data;
+ memset(&oid_data, 0, sizeof(oid_data));
+ oid_data.offset = SEC_OID_UNKNOWN;
+ oid_data.desc = oid_string;
+ oid_data.mechanism = CKM_INVALID_MECHANISM;
+ oid_data.supportedExtension = INVALID_CERT_EXTENSION;
+
+ SECStatus rv = SEC_StringToOID(arena, &oid_data.oid, oid_string, 0);
+ if (rv != SECSuccess)
+ return SEC_OID_UNKNOWN;
+
+ return SECOID_AddEntry(&oid_data);
+}
+
+EVOidData::EVOidData()
+ : verisign_policy(SEC_OID_UNKNOWN),
+ thawte_policy(SEC_OID_UNKNOWN),
+ fake_policy(SEC_OID_UNKNOWN) {
+}
+
+bool EVOidData::Init() {
+ crypto::ScopedPLArenaPool pool(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!pool.get())
+ return false;
+
+ verisign_policy = RegisterOID(pool.get(), kVerisignPolicy);
+ thawte_policy = RegisterOID(pool.get(), kThawtePolicy);
+ fake_policy = RegisterOID(pool.get(), kFakePolicy);
+
+ return verisign_policy != SEC_OID_UNKNOWN &&
+ thawte_policy != SEC_OID_UNKNOWN &&
+ fake_policy != SEC_OID_UNKNOWN;
+}
+
+#elif defined(OS_WIN)
+
+EVOidData::EVOidData()
+ : verisign_policy(kVerisignPolicy),
+ thawte_policy(kThawtePolicy),
+ fake_policy(kFakePolicy) {
+}
+
+bool EVOidData::Init() {
+ return true;
+}
+
+#endif
+
+#if defined(USE_NSS) || defined(OS_WIN)
+
+class EVRootCAMetadataTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(ev_oid_data.Init());
+ }
+
+ EVOidData ev_oid_data;
+};
+
+TEST_F(EVRootCAMetadataTest, Basic) {
+ EVRootCAMetadata* ev_metadata(EVRootCAMetadata::GetInstance());
+
+ EXPECT_TRUE(ev_metadata->IsEVPolicyOID(ev_oid_data.verisign_policy));
+ EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy));
+ EXPECT_TRUE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint,
+ ev_oid_data.verisign_policy));
+ EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint,
+ ev_oid_data.verisign_policy));
+ EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint,
+ ev_oid_data.fake_policy));
+ EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kVerisignFingerprint,
+ ev_oid_data.thawte_policy));
+}
+
+TEST_F(EVRootCAMetadataTest, AddRemove) {
+ EVRootCAMetadata* ev_metadata(EVRootCAMetadata::GetInstance());
+
+ EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy));
+ EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint,
+ ev_oid_data.fake_policy));
+
+ {
+ ScopedTestEVPolicy test_ev_policy(ev_metadata, kFakeFingerprint,
+ kFakePolicy);
+
+ EXPECT_TRUE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy));
+ EXPECT_TRUE(ev_metadata->HasEVPolicyOID(kFakeFingerprint,
+ ev_oid_data.fake_policy));
+ }
+
+ EXPECT_FALSE(ev_metadata->IsEVPolicyOID(ev_oid_data.fake_policy));
+ EXPECT_FALSE(ev_metadata->HasEVPolicyOID(kFakeFingerprint,
+ ev_oid_data.fake_policy));
+}
+
+#endif // defined(USE_NSS) || defined(OS_WIN)
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/cert/jwk_serializer.h b/chromium/net/cert/jwk_serializer.h
new file mode 100644
index 00000000000..7a12a366246
--- /dev/null
+++ b/chromium/net/cert/jwk_serializer.h
@@ -0,0 +1,30 @@
+// Copyright 2013 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_CERT_JWK_SERIALIZER_H_
+#define NET_CERT_JWK_SERIALIZER_H_
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace net {
+
+namespace JwkSerializer {
+
+// Converts a subject public key info from DER to JWK.
+// See http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-13 for
+// the output format.
+NET_EXPORT_PRIVATE bool ConvertSpkiFromDerToJwk(
+ const base::StringPiece& spki_der,
+ base::DictionaryValue* public_key_jwk);
+
+} // namespace JwkSerializer
+
+} // namespace net
+
+#endif // NET_CERT_JWK_SERIALIZER_H_
diff --git a/chromium/net/cert/jwk_serializer_nss.cc b/chromium/net/cert/jwk_serializer_nss.cc
new file mode 100644
index 00000000000..d8445805f8e
--- /dev/null
+++ b/chromium/net/cert/jwk_serializer_nss.cc
@@ -0,0 +1,118 @@
+// Copyright 2013 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/cert/jwk_serializer.h"
+
+#include <cert.h>
+#include <keyhi.h>
+#include <nss.h>
+
+#include "base/base64.h"
+#include "base/values.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+
+namespace net {
+
+namespace JwkSerializer {
+
+namespace {
+
+bool ConvertEcPrime256v1PublicKeyInfoToJwk(
+ CERTSubjectPublicKeyInfo* spki,
+ base::DictionaryValue* public_key_jwk) {
+ static const int kUncompressedEncodingType = 4;
+ static const int kPrime256v1PublicKeyLength = 64;
+ // The public key value is encoded as 0x04 + 64 bytes of public key.
+ // NSS gives the length as the bit length.
+ if (spki->subjectPublicKey.len != (kPrime256v1PublicKeyLength + 1) * 8 ||
+ spki->subjectPublicKey.data[0] != kUncompressedEncodingType)
+ return false;
+
+ public_key_jwk->SetString("alg", "EC");
+ public_key_jwk->SetString("crv", "P-256");
+
+ base::StringPiece x(
+ reinterpret_cast<char*>(spki->subjectPublicKey.data + 1),
+ kPrime256v1PublicKeyLength / 2);
+ std::string x_b64;
+ base::Base64Encode(x, &x_b64);
+ public_key_jwk->SetString("x", x_b64);
+
+ base::StringPiece y(
+ reinterpret_cast<char*>(spki->subjectPublicKey.data + 1 +
+ kPrime256v1PublicKeyLength / 2),
+ kPrime256v1PublicKeyLength / 2);
+ std::string y_b64;
+ base::Base64Encode(y, &y_b64);
+ public_key_jwk->SetString("y", y_b64);
+ return true;
+}
+
+bool ConvertEcPublicKeyInfoToJwk(
+ CERTSubjectPublicKeyInfo* spki,
+ base::DictionaryValue* public_key_jwk) {
+ // 1.2.840.10045.3.1.7
+ // (iso.member-body.us.ansi-x9-62.ellipticCurve.primeCurve.prime256v1)
+ // (This includes the DER-encoded type (OID) and length: parameters can be
+ // anything, so the DER type isn't implied, and NSS includes it.)
+ static const unsigned char kPrime256v1[] = {
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+ };
+ if (spki->algorithm.parameters.len == sizeof(kPrime256v1) &&
+ !memcmp(spki->algorithm.parameters.data, kPrime256v1,
+ sizeof(kPrime256v1))) {
+ return ConvertEcPrime256v1PublicKeyInfoToJwk(spki, public_key_jwk);
+ }
+ // TODO(juanlang): other curves
+ return false;
+}
+
+typedef scoped_ptr_malloc<
+ CERTSubjectPublicKeyInfo,
+ crypto::NSSDestroyer<CERTSubjectPublicKeyInfo,
+ SECKEY_DestroySubjectPublicKeyInfo> >
+ ScopedCERTSubjectPublicKeyInfo;
+
+} // namespace
+
+bool ConvertSpkiFromDerToJwk(
+ const base::StringPiece& spki_der,
+ base::DictionaryValue* public_key_jwk) {
+ public_key_jwk->Clear();
+
+ crypto::EnsureNSSInit();
+
+ if (!NSS_IsInitialized())
+ return false;
+
+ SECItem sec_item;
+ sec_item.data = const_cast<unsigned char*>(
+ reinterpret_cast<const unsigned char*>(spki_der.data()));
+ sec_item.len = spki_der.size();
+ ScopedCERTSubjectPublicKeyInfo spki(
+ SECKEY_DecodeDERSubjectPublicKeyInfo(&sec_item));
+ if (!spki)
+ return false;
+
+ // 1.2.840.10045.2
+ // (iso.member-body.us.ansi-x9-62.id-ecPublicKey)
+ // (This omits the ASN.1 encoding of the type (OID) and length: the fact that
+ // this is an OID is already clear, and NSS omits it here.)
+ static const unsigned char kIdEcPublicKey[] = {
+ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01
+ };
+ bool rv = false;
+ if (spki->algorithm.algorithm.len == sizeof(kIdEcPublicKey) &&
+ !memcmp(spki->algorithm.algorithm.data, kIdEcPublicKey,
+ sizeof(kIdEcPublicKey))) {
+ rv = ConvertEcPublicKeyInfoToJwk(spki.get(), public_key_jwk);
+ }
+ // TODO(juanlang): other algorithms
+ return rv;
+}
+
+} // namespace JwkSerializer
+
+} // namespace net
diff --git a/chromium/net/cert/jwk_serializer_openssl.cc b/chromium/net/cert/jwk_serializer_openssl.cc
new file mode 100644
index 00000000000..b11e9696d6f
--- /dev/null
+++ b/chromium/net/cert/jwk_serializer_openssl.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 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/cert/jwk_serializer.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+namespace JwkSerializer {
+
+bool ConvertSpkiFromDerToJwk(
+ const base::StringPiece& spki_der,
+ base::DictionaryValue* public_key_jwk) {
+ // TODO(juanlang): implement
+ NOTIMPLEMENTED();
+ return false;
+}
+
+} // namespace JwkSerializer
+
+} // namespace net
diff --git a/chromium/net/cert/jwk_serializer_unittest.cc b/chromium/net/cert/jwk_serializer_unittest.cc
new file mode 100644
index 00000000000..8059e73a3a6
--- /dev/null
+++ b/chromium/net/cert/jwk_serializer_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2013 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/cert/jwk_serializer.h"
+
+#include "base/base64.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// This is the ASN.1 prefix for a P-256 public key. Specifically it's:
+// SEQUENCE
+// SEQUENCE
+// OID id-ecPublicKey
+// OID prime256v1
+// BIT STRING, length 66, 0 trailing bits: 0x04
+//
+// The 0x04 in the BIT STRING is the prefix for an uncompressed, X9.62
+// public key. Following that are the two field elements as 32-byte,
+// big-endian numbers, as required by the Channel ID.
+static const unsigned char kP256SpkiPrefix[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+ 0x42, 0x00, 0x04
+};
+static const unsigned int kEcCoordinateSize = 32U;
+
+// This is a valid P-256 public key.
+static const unsigned char kSpkiEc[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+ 0x42, 0x00, 0x04,
+ 0x29, 0x5d, 0x6e, 0xfe, 0x33, 0x77, 0x26, 0xea,
+ 0x5b, 0xa4, 0xe6, 0x1b, 0x34, 0x6e, 0x7b, 0xa0,
+ 0xa3, 0x8f, 0x33, 0x49, 0xa0, 0x9c, 0xae, 0x98,
+ 0xbd, 0x46, 0x0d, 0xf6, 0xd4, 0x5a, 0xdc, 0x8a,
+ 0x1f, 0x8a, 0xb2, 0x20, 0x51, 0xb7, 0xd2, 0x87,
+ 0x0d, 0x53, 0x7e, 0x5d, 0x94, 0xa3, 0xe0, 0x34,
+ 0x16, 0xa1, 0xcc, 0x10, 0x48, 0xcd, 0x70, 0x9c,
+ 0x05, 0xd3, 0xd2, 0xca, 0xdf, 0x44, 0x2f, 0xf4
+};
+
+// This is a P-256 public key with 0 X and Y values.
+static const unsigned char kSpkiEcWithZeroXY[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+ 0x42, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+#if !defined(USE_OPENSSL)
+
+TEST(JwkSerializerNSSTest, ConvertSpkiFromDerToJwkEc) {
+ base::StringPiece spki;
+ base::DictionaryValue public_key_jwk;
+
+ EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk));
+ EXPECT_TRUE(public_key_jwk.empty());
+
+ // Test the result of a "normal" point on this curve.
+ spki.set(reinterpret_cast<const char*>(kSpkiEc), sizeof(kSpkiEc));
+ EXPECT_TRUE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk));
+
+ std::string string_value;
+ EXPECT_TRUE(public_key_jwk.GetString("alg", &string_value));
+ EXPECT_STREQ("EC", string_value.c_str());
+ EXPECT_TRUE(public_key_jwk.GetString("crv", &string_value));
+ EXPECT_STREQ("P-256", string_value.c_str());
+
+ EXPECT_TRUE(public_key_jwk.GetString("x", &string_value));
+ std::string decoded_coordinate;
+ EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate));
+ EXPECT_EQ(kEcCoordinateSize, decoded_coordinate.size());
+ EXPECT_EQ(0,
+ memcmp(decoded_coordinate.data(),
+ kSpkiEc + sizeof(kP256SpkiPrefix),
+ kEcCoordinateSize));
+
+ EXPECT_TRUE(public_key_jwk.GetString("y", &string_value));
+ EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate));
+ EXPECT_EQ(kEcCoordinateSize, decoded_coordinate.size());
+ EXPECT_EQ(0,
+ memcmp(decoded_coordinate.data(),
+ kSpkiEc + sizeof(kP256SpkiPrefix) + kEcCoordinateSize,
+ kEcCoordinateSize));
+
+ // Test the result of a corner case: leading 0s in the x, y coordinates are
+ // not trimmed, but the point is fixed-length encoded.
+ spki.set(reinterpret_cast<const char*>(kSpkiEcWithZeroXY),
+ sizeof(kSpkiEcWithZeroXY));
+ EXPECT_TRUE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk));
+
+ EXPECT_TRUE(public_key_jwk.GetString("alg", &string_value));
+ EXPECT_STREQ("EC", string_value.c_str());
+ EXPECT_TRUE(public_key_jwk.GetString("crv", &string_value));
+ EXPECT_STREQ("P-256", string_value.c_str());
+
+ EXPECT_TRUE(public_key_jwk.GetString("x", &string_value));
+ EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate));
+ EXPECT_EQ(kEcCoordinateSize, decoded_coordinate.size());
+ EXPECT_EQ(0,
+ memcmp(decoded_coordinate.data(),
+ kSpkiEcWithZeroXY + sizeof(kP256SpkiPrefix),
+ kEcCoordinateSize));
+
+ EXPECT_TRUE(public_key_jwk.GetString("y", &string_value));
+ EXPECT_TRUE(base::Base64Decode(string_value, &decoded_coordinate));
+ EXPECT_EQ(kEcCoordinateSize, decoded_coordinate.size());
+ EXPECT_EQ(0,
+ memcmp(decoded_coordinate.data(),
+ kSpkiEcWithZeroXY + sizeof(kP256SpkiPrefix) + kEcCoordinateSize,
+ kEcCoordinateSize));
+}
+
+#else
+
+// For OpenSSL, JwkSerializer::ConvertSpkiFromDerToJwk() is not yet implemented
+// and should return false. This unit test ensures that a stub implementation
+// is present.
+TEST(JwkSerializerOpenSSLTest, ConvertSpkiFromDerToJwkNotImplemented) {
+ base::StringPiece spki;
+ base::DictionaryValue public_key_jwk;
+
+ // The empty SPKI is trivially non-convertible...
+ EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk));
+ EXPECT_TRUE(public_key_jwk.empty());
+ // but even a valid SPKI is non-convertible via the stub OpenSSL
+ // implementation.
+ spki.set(reinterpret_cast<const char*>(kSpkiEc), sizeof(kSpkiEc));
+ EXPECT_FALSE(JwkSerializer::ConvertSpkiFromDerToJwk(spki, &public_key_jwk));
+ EXPECT_TRUE(public_key_jwk.empty());
+}
+
+#endif // !defined(USE_OPENSSL)
+
+} // namespace net
diff --git a/chromium/net/cert/mock_cert_verifier.cc b/chromium/net/cert/mock_cert_verifier.cc
new file mode 100644
index 00000000000..a30e3d54c86
--- /dev/null
+++ b/chromium/net/cert/mock_cert_verifier.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 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.
+
+#include "net/cert/mock_cert_verifier.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+MockCertVerifier::MockCertVerifier() : default_result_(ERR_CERT_INVALID) {}
+
+MockCertVerifier::~MockCertVerifier() {}
+
+int MockCertVerifier::Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ RuleList::const_iterator it;
+ for (it = rules_.begin(); it != rules_.end(); ++it) {
+ // Check just the server cert. Intermediates will be ignored.
+ if (!it->cert->Equals(cert))
+ continue;
+ if (!MatchPattern(hostname, it->hostname))
+ continue;
+ *verify_result = it->result;
+ return it->rv;
+ }
+
+ // Fall through to the default.
+ verify_result->verified_cert = cert;
+ verify_result->cert_status = MapNetErrorToCertStatus(default_result_);
+ return default_result_;
+}
+
+void MockCertVerifier::CancelRequest(RequestHandle req) {
+ NOTIMPLEMENTED();
+}
+
+void MockCertVerifier::AddResultForCert(X509Certificate* cert,
+ const CertVerifyResult& verify_result,
+ int rv) {
+ AddResultForCertAndHost(cert, "*", verify_result, rv);
+}
+
+void MockCertVerifier::AddResultForCertAndHost(
+ X509Certificate* cert,
+ const std::string& host_pattern,
+ const CertVerifyResult& verify_result,
+ int rv) {
+ Rule rule(cert, host_pattern, verify_result, rv);
+ rules_.push_back(rule);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/mock_cert_verifier.h b/chromium/net/cert/mock_cert_verifier.h
new file mode 100644
index 00000000000..704c66b2b80
--- /dev/null
+++ b/chromium/net/cert/mock_cert_verifier.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_MOCK_CERT_VERIFIER_H_
+#define NET_CERT_MOCK_CERT_VERIFIER_H_
+
+#include <list>
+
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+
+namespace net {
+
+class MockCertVerifier : public CertVerifier {
+ public:
+ // Creates a new MockCertVerifier. By default, any call to Verify() will
+ // result in the cert status being flagged as CERT_STATUS_INVALID and return
+ // an ERR_CERT_INVALID network error code. This behaviour can be overridden
+ // by calling set_default_result() to change the default return value for
+ // Verify() or by calling one of the AddResult*() methods to specifically
+ // handle a certificate or certificate and host.
+ MockCertVerifier();
+
+ virtual ~MockCertVerifier();
+
+ // CertVerifier implementation
+ virtual int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle req) OVERRIDE;
+
+ // Sets the default return value for Verify() for certificates/hosts that do
+ // not have explicit results added via the AddResult*() methods.
+ void set_default_result(int default_result) {
+ default_result_ = default_result;
+ }
+
+ // Adds a rule that will cause any call to Verify() for |cert| to return rv,
+ // copying |verify_result| into the verified result.
+ // Note: Only the primary certificate of |cert| is checked. Any intermediate
+ // certificates will be ignored.
+ void AddResultForCert(X509Certificate* cert,
+ const CertVerifyResult& verify_result,
+ int rv);
+
+ // Same as AddResultForCert(), but further restricts it to only return for
+ // hostnames that match |host_pattern|.
+ void AddResultForCertAndHost(X509Certificate* cert,
+ const std::string& host_pattern,
+ const CertVerifyResult& verify_result,
+ int rv);
+
+ private:
+ struct Rule {
+ Rule(X509Certificate* cert,
+ const std::string& hostname,
+ const CertVerifyResult& result,
+ int rv)
+ : cert(cert),
+ hostname(hostname),
+ result(result),
+ rv(rv) {
+ DCHECK(cert);
+ DCHECK(result.verified_cert.get());
+ }
+
+ scoped_refptr<X509Certificate> cert;
+ std::string hostname;
+ CertVerifyResult result;
+ int rv;
+ };
+
+ typedef std::list<Rule> RuleList;
+
+ int default_result_;
+ RuleList rules_;
+};
+
+} // namespace net
+
+#endif // NET_CERT_MOCK_CERT_VERIFIER_H_
diff --git a/chromium/net/cert/multi_threaded_cert_verifier.cc b/chromium/net/cert/multi_threaded_cert_verifier.cc
new file mode 100644
index 00000000000..821cec1220b
--- /dev/null
+++ b/chromium/net/cert/multi_threaded_cert_verifier.cc
@@ -0,0 +1,566 @@
+// Copyright (c) 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.
+
+#include "net/cert/multi_threaded_cert_verifier.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/worker_pool.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cert/cert_trust_anchor_provider.h"
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_certificate_net_log_param.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <private/pprthred.h> // PR_DetachThread
+#endif
+
+namespace net {
+
+////////////////////////////////////////////////////////////////////////////
+
+// Life of a request:
+//
+// MultiThreadedCertVerifier CertVerifierJob CertVerifierWorker Request
+// | (origin loop) (worker loop)
+// |
+// Verify()
+// |---->-------------------------------------<creates>
+// |
+// |---->-------------------<creates>
+// |
+// |---->-------------------------------------------------------<creates>
+// |
+// |---->---------------------------------------Start
+// | |
+// | PostTask
+// |
+// | <starts verifying>
+// |---->-------------------AddRequest |
+// |
+// |
+// |
+// Finish
+// |
+// PostTask
+//
+// |
+// DoReply
+// |----<-----------------------------------------|
+// HandleResult
+// |
+// |---->------------------HandleResult
+// |
+// |------>---------------------------Post
+//
+//
+//
+// On a cache hit, MultiThreadedCertVerifier::Verify() returns synchronously
+// without posting a task to a worker thread.
+
+namespace {
+
+// The default value of max_cache_entries_.
+const unsigned kMaxCacheEntries = 256;
+
+// The number of seconds for which we'll cache a cache entry.
+const unsigned kTTLSecs = 1800; // 30 minutes.
+
+} // namespace
+
+MultiThreadedCertVerifier::CachedResult::CachedResult() : error(ERR_FAILED) {}
+
+MultiThreadedCertVerifier::CachedResult::~CachedResult() {}
+
+MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
+ const base::Time& now)
+ : verification_time(now),
+ expiration_time(now) {
+}
+
+MultiThreadedCertVerifier::CacheValidityPeriod::CacheValidityPeriod(
+ const base::Time& now,
+ const base::Time& expiration)
+ : verification_time(now),
+ expiration_time(expiration) {
+}
+
+bool MultiThreadedCertVerifier::CacheExpirationFunctor::operator()(
+ const CacheValidityPeriod& now,
+ const CacheValidityPeriod& expiration) const {
+ // Ensure this functor is being used for expiration only, and not strict
+ // weak ordering/sorting. |now| should only ever contain a single
+ // base::Time.
+ // Note: DCHECK_EQ is not used due to operator<< overloading requirements.
+ DCHECK(now.verification_time == now.expiration_time);
+
+ // |now| contains only a single time (verification_time), while |expiration|
+ // contains the validity range - both when the certificate was verified and
+ // when the verification result should expire.
+ //
+ // If the user receives a "not yet valid" message, and adjusts their clock
+ // foward to the correct time, this will (typically) cause
+ // now.verification_time to advance past expiration.expiration_time, thus
+ // treating the cached result as an expired entry and re-verifying.
+ // If the user receives a "expired" message, and adjusts their clock
+ // backwards to the correct time, this will cause now.verification_time to
+ // be less than expiration_verification_time, thus treating the cached
+ // result as an expired entry and re-verifying.
+ // If the user receives either of those messages, and does not adjust their
+ // clock, then the result will be (typically) be cached until the expiration
+ // TTL.
+ //
+ // This algorithm is only problematic if the user consistently keeps
+ // adjusting their clock backwards in increments smaller than the expiration
+ // TTL, in which case, cached elements continue to be added. However,
+ // because the cache has a fixed upper bound, if no entries are expired, a
+ // 'random' entry will be, thus keeping the memory constraints bounded over
+ // time.
+ return now.verification_time >= expiration.verification_time &&
+ now.verification_time < expiration.expiration_time;
+};
+
+
+// Represents the output and result callback of a request.
+class CertVerifierRequest {
+ public:
+ CertVerifierRequest(const CompletionCallback& callback,
+ CertVerifyResult* verify_result,
+ const BoundNetLog& net_log)
+ : callback_(callback),
+ verify_result_(verify_result),
+ net_log_(net_log) {
+ net_log_.BeginEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ }
+
+ ~CertVerifierRequest() {
+ }
+
+ // Ensures that the result callback will never be made.
+ void Cancel() {
+ callback_.Reset();
+ verify_result_ = NULL;
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ }
+
+ // Copies the contents of |verify_result| to the caller's
+ // CertVerifyResult and calls the callback.
+ void Post(const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ if (!callback_.is_null()) {
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_REQUEST);
+ *verify_result_ = verify_result.result;
+ callback_.Run(verify_result.error);
+ }
+ delete this;
+ }
+
+ bool canceled() const { return callback_.is_null(); }
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ private:
+ CompletionCallback callback_;
+ CertVerifyResult* verify_result_;
+ const BoundNetLog net_log_;
+};
+
+
+// CertVerifierWorker runs on a worker thread and takes care of the blocking
+// process of performing the certificate verification. Deletes itself
+// eventually if Start() succeeds.
+class CertVerifierWorker {
+ public:
+ CertVerifierWorker(CertVerifyProc* verify_proc,
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ MultiThreadedCertVerifier* cert_verifier)
+ : verify_proc_(verify_proc),
+ cert_(cert),
+ hostname_(hostname),
+ flags_(flags),
+ crl_set_(crl_set),
+ additional_trust_anchors_(additional_trust_anchors),
+ origin_loop_(base::MessageLoop::current()),
+ cert_verifier_(cert_verifier),
+ canceled_(false),
+ error_(ERR_FAILED) {
+ }
+
+ // Returns the certificate being verified. May only be called /before/
+ // Start() is called.
+ X509Certificate* certificate() const { return cert_.get(); }
+
+ bool Start() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+
+ return base::WorkerPool::PostTask(
+ FROM_HERE, base::Bind(&CertVerifierWorker::Run, base::Unretained(this)),
+ true /* task is slow */);
+ }
+
+ // Cancel is called from the origin loop when the MultiThreadedCertVerifier is
+ // getting deleted.
+ void Cancel() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+ base::AutoLock locked(lock_);
+ canceled_ = true;
+ }
+
+ private:
+ void Run() {
+ // Runs on a worker thread.
+ error_ = verify_proc_->Verify(cert_.get(),
+ hostname_,
+ flags_,
+ crl_set_.get(),
+ additional_trust_anchors_,
+ &verify_result_);
+#if defined(USE_NSS) || defined(OS_IOS)
+ // Detach the thread from NSPR.
+ // Calling NSS functions attaches the thread to NSPR, which stores
+ // the NSPR thread ID in thread-specific data.
+ // The threads in our thread pool terminate after we have called
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
+ // segfaults on shutdown when the threads' thread-specific data
+ // destructors run.
+ PR_DetachThread();
+#endif
+ Finish();
+ }
+
+ // DoReply runs on the origin thread.
+ void DoReply() {
+ DCHECK_EQ(base::MessageLoop::current(), origin_loop_);
+ {
+ // We lock here because the worker thread could still be in Finished,
+ // after the PostTask, but before unlocking |lock_|. If we do not lock in
+ // this case, we will end up deleting a locked Lock, which can lead to
+ // memory leaks or worse errors.
+ base::AutoLock locked(lock_);
+ if (!canceled_) {
+ cert_verifier_->HandleResult(cert_.get(),
+ hostname_,
+ flags_,
+ additional_trust_anchors_,
+ error_,
+ verify_result_);
+ }
+ }
+ delete this;
+ }
+
+ void Finish() {
+ // Runs on the worker thread.
+ // We assume that the origin loop outlives the MultiThreadedCertVerifier. If
+ // the MultiThreadedCertVerifier is deleted, it will call Cancel on us. If
+ // it does so before the Acquire, we'll delete ourselves and return. If it's
+ // trying to do so concurrently, then it'll block on the lock and we'll call
+ // PostTask while the MultiThreadedCertVerifier (and therefore the
+ // MessageLoop) is still alive.
+ // If it does so after this function, we assume that the MessageLoop will
+ // process pending tasks. In which case we'll notice the |canceled_| flag
+ // in DoReply.
+
+ bool canceled;
+ {
+ base::AutoLock locked(lock_);
+ canceled = canceled_;
+ if (!canceled) {
+ origin_loop_->PostTask(
+ FROM_HERE, base::Bind(
+ &CertVerifierWorker::DoReply, base::Unretained(this)));
+ }
+ }
+
+ if (canceled)
+ delete this;
+ }
+
+ scoped_refptr<CertVerifyProc> verify_proc_;
+ scoped_refptr<X509Certificate> cert_;
+ const std::string hostname_;
+ const int flags_;
+ scoped_refptr<CRLSet> crl_set_;
+ const CertificateList additional_trust_anchors_;
+ base::MessageLoop* const origin_loop_;
+ MultiThreadedCertVerifier* const cert_verifier_;
+
+ // lock_ protects canceled_.
+ base::Lock lock_;
+
+ // If canceled_ is true,
+ // * origin_loop_ cannot be accessed by the worker thread,
+ // * cert_verifier_ cannot be accessed by any thread.
+ bool canceled_;
+
+ int error_;
+ CertVerifyResult verify_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertVerifierWorker);
+};
+
+// A CertVerifierJob is a one-to-one counterpart of a CertVerifierWorker. It
+// lives only on the CertVerifier's origin message loop.
+class CertVerifierJob {
+ public:
+ CertVerifierJob(CertVerifierWorker* worker,
+ const BoundNetLog& net_log)
+ : start_time_(base::TimeTicks::Now()),
+ worker_(worker),
+ net_log_(net_log) {
+ net_log_.BeginEvent(
+ NetLog::TYPE_CERT_VERIFIER_JOB,
+ base::Bind(&NetLogX509CertificateCallback,
+ base::Unretained(worker_->certificate())));
+ }
+
+ ~CertVerifierJob() {
+ if (worker_) {
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
+ worker_->Cancel();
+ DeleteAllCanceled();
+ }
+ }
+
+ void AddRequest(CertVerifierRequest* request) {
+ request->net_log().AddEvent(
+ NetLog::TYPE_CERT_VERIFIER_REQUEST_BOUND_TO_JOB,
+ net_log_.source().ToEventParametersCallback());
+
+ requests_.push_back(request);
+ }
+
+ void HandleResult(
+ const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ worker_ = NULL;
+ net_log_.EndEvent(NetLog::TYPE_CERT_VERIFIER_JOB);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.CertVerifier_Job_Latency",
+ base::TimeTicks::Now() - start_time_,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ PostAll(verify_result);
+ }
+
+ private:
+ void PostAll(const MultiThreadedCertVerifier::CachedResult& verify_result) {
+ std::vector<CertVerifierRequest*> requests;
+ requests_.swap(requests);
+
+ for (std::vector<CertVerifierRequest*>::iterator
+ i = requests.begin(); i != requests.end(); i++) {
+ (*i)->Post(verify_result);
+ // Post() causes the CertVerifierRequest to delete itself.
+ }
+ }
+
+ void DeleteAllCanceled() {
+ for (std::vector<CertVerifierRequest*>::iterator
+ i = requests_.begin(); i != requests_.end(); i++) {
+ if ((*i)->canceled()) {
+ delete *i;
+ } else {
+ LOG(DFATAL) << "CertVerifierRequest leaked!";
+ }
+ }
+ }
+
+ const base::TimeTicks start_time_;
+ std::vector<CertVerifierRequest*> requests_;
+ CertVerifierWorker* worker_;
+ const BoundNetLog net_log_;
+};
+
+MultiThreadedCertVerifier::MultiThreadedCertVerifier(
+ CertVerifyProc* verify_proc)
+ : cache_(kMaxCacheEntries),
+ requests_(0),
+ cache_hits_(0),
+ inflight_joins_(0),
+ verify_proc_(verify_proc),
+ trust_anchor_provider_(NULL) {
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+MultiThreadedCertVerifier::~MultiThreadedCertVerifier() {
+ STLDeleteValues(&inflight_);
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+void MultiThreadedCertVerifier::SetCertTrustAnchorProvider(
+ CertTrustAnchorProvider* trust_anchor_provider) {
+ DCHECK(CalledOnValidThread());
+ trust_anchor_provider_ = trust_anchor_provider;
+}
+
+int MultiThreadedCertVerifier::Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+
+ if (callback.is_null() || !verify_result || hostname.empty()) {
+ *out_req = NULL;
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ requests_++;
+
+ const CertificateList empty_cert_list;
+ const CertificateList& additional_trust_anchors =
+ trust_anchor_provider_ ?
+ trust_anchor_provider_->GetAdditionalTrustAnchors() : empty_cert_list;
+
+ const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
+ hostname, flags, additional_trust_anchors);
+ const CertVerifierCache::value_type* cached_entry =
+ cache_.Get(key, CacheValidityPeriod(base::Time::Now()));
+ if (cached_entry) {
+ ++cache_hits_;
+ *out_req = NULL;
+ *verify_result = cached_entry->result;
+ return cached_entry->error;
+ }
+
+ // No cache hit. See if an identical request is currently in flight.
+ CertVerifierJob* job;
+ std::map<RequestParams, CertVerifierJob*>::const_iterator j;
+ j = inflight_.find(key);
+ if (j != inflight_.end()) {
+ // An identical request is in flight already. We'll just attach our
+ // callback.
+ inflight_joins_++;
+ job = j->second;
+ } else {
+ // Need to make a new request.
+ CertVerifierWorker* worker =
+ new CertVerifierWorker(verify_proc_.get(),
+ cert,
+ hostname,
+ flags,
+ crl_set,
+ additional_trust_anchors,
+ this);
+ job = new CertVerifierJob(
+ worker,
+ BoundNetLog::Make(net_log.net_log(), NetLog::SOURCE_CERT_VERIFIER_JOB));
+ if (!worker->Start()) {
+ delete job;
+ delete worker;
+ *out_req = NULL;
+ // TODO(wtc): log to the NetLog.
+ LOG(ERROR) << "CertVerifierWorker couldn't be started.";
+ return ERR_INSUFFICIENT_RESOURCES; // Just a guess.
+ }
+ inflight_.insert(std::make_pair(key, job));
+ }
+
+ CertVerifierRequest* request =
+ new CertVerifierRequest(callback, verify_result, net_log);
+ job->AddRequest(request);
+ *out_req = request;
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedCertVerifier::CancelRequest(RequestHandle req) {
+ DCHECK(CalledOnValidThread());
+ CertVerifierRequest* request = reinterpret_cast<CertVerifierRequest*>(req);
+ request->Cancel();
+}
+
+MultiThreadedCertVerifier::RequestParams::RequestParams(
+ const SHA1HashValue& cert_fingerprint_arg,
+ const SHA1HashValue& ca_fingerprint_arg,
+ const std::string& hostname_arg,
+ int flags_arg,
+ const CertificateList& additional_trust_anchors)
+ : hostname(hostname_arg),
+ flags(flags_arg) {
+ hash_values.reserve(2 + additional_trust_anchors.size());
+ hash_values.push_back(cert_fingerprint_arg);
+ hash_values.push_back(ca_fingerprint_arg);
+ for (size_t i = 0; i < additional_trust_anchors.size(); ++i)
+ hash_values.push_back(additional_trust_anchors[i]->fingerprint());
+}
+
+MultiThreadedCertVerifier::RequestParams::~RequestParams() {}
+
+bool MultiThreadedCertVerifier::RequestParams::operator<(
+ const RequestParams& other) const {
+ // |flags| is compared before |cert_fingerprint|, |ca_fingerprint|, and
+ // |hostname| under assumption that integer comparisons are faster than
+ // memory and string comparisons.
+ if (flags != other.flags)
+ return flags < other.flags;
+ if (hostname != other.hostname)
+ return hostname < other.hostname;
+ return std::lexicographical_compare(
+ hash_values.begin(), hash_values.end(),
+ other.hash_values.begin(), other.hash_values.end(),
+ net::SHA1HashValueLessThan());
+}
+
+// HandleResult is called by CertVerifierWorker on the origin message loop.
+// It deletes CertVerifierJob.
+void MultiThreadedCertVerifier::HandleResult(
+ X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ const CertificateList& additional_trust_anchors,
+ int error,
+ const CertVerifyResult& verify_result) {
+ DCHECK(CalledOnValidThread());
+
+ const RequestParams key(cert->fingerprint(), cert->ca_fingerprint(),
+ hostname, flags, additional_trust_anchors);
+
+ CachedResult cached_result;
+ cached_result.error = error;
+ cached_result.result = verify_result;
+ base::Time now = base::Time::Now();
+ cache_.Put(
+ key, cached_result, CacheValidityPeriod(now),
+ CacheValidityPeriod(now, now + base::TimeDelta::FromSeconds(kTTLSecs)));
+
+ std::map<RequestParams, CertVerifierJob*>::iterator j;
+ j = inflight_.find(key);
+ if (j == inflight_.end()) {
+ NOTREACHED();
+ return;
+ }
+ CertVerifierJob* job = j->second;
+ inflight_.erase(j);
+
+ job->HandleResult(cached_result);
+ delete job;
+}
+
+void MultiThreadedCertVerifier::OnCertTrustChanged(
+ const X509Certificate* cert) {
+ DCHECK(CalledOnValidThread());
+
+ ClearCache();
+}
+
+} // namespace net
diff --git a/chromium/net/cert/multi_threaded_cert_verifier.h b/chromium/net/cert/multi_threaded_cert_verifier.h
new file mode 100644
index 00000000000..bc9cd4f6ce6
--- /dev/null
+++ b/chromium/net/cert/multi_threaded_cert_verifier.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_
+#define NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/expiring_cache.h"
+#include "net/base/hash_value.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_database.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace net {
+
+class CertTrustAnchorProvider;
+class CertVerifierJob;
+class CertVerifierRequest;
+class CertVerifierWorker;
+class CertVerifyProc;
+
+// MultiThreadedCertVerifier is a CertVerifier implementation that runs
+// synchronous CertVerifier implementations on worker threads.
+class NET_EXPORT_PRIVATE MultiThreadedCertVerifier
+ : public CertVerifier,
+ NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public CertDatabase::Observer {
+ public:
+ explicit MultiThreadedCertVerifier(CertVerifyProc* verify_proc);
+
+ // When the verifier is destroyed, all certificate verifications requests are
+ // canceled, and their completion callbacks will not be called.
+ virtual ~MultiThreadedCertVerifier();
+
+ // Configures a source of additional certificates that should be treated as
+ // trust anchors during verification, provided that the underlying
+ // CertVerifyProc supports additional trust beyond the default implementation.
+ // The CertTrustAnchorProvider will only be accessed on the same
+ // thread that Verify() is called on; that is, it will not be
+ // accessed from worker threads.
+ // It must outlive the MultiThreadedCertVerifier.
+ void SetCertTrustAnchorProvider(
+ CertTrustAnchorProvider* trust_anchor_provider);
+
+ // CertVerifier implementation
+ virtual int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ CertVerifier::RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(CertVerifier::RequestHandle req) OVERRIDE;
+
+ private:
+ friend class CertVerifierWorker; // Calls HandleResult.
+ friend class CertVerifierRequest;
+ friend class CertVerifierJob;
+ friend class MultiThreadedCertVerifierTest;
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, CacheHit);
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, DifferentCACerts);
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, InflightJoin);
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest, CancelRequest);
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest,
+ RequestParamsComparators);
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCertVerifierTest,
+ CertTrustAnchorProvider);
+
+ // Input parameters of a certificate verification request.
+ struct NET_EXPORT_PRIVATE RequestParams {
+ RequestParams(const SHA1HashValue& cert_fingerprint_arg,
+ const SHA1HashValue& ca_fingerprint_arg,
+ const std::string& hostname_arg,
+ int flags_arg,
+ const CertificateList& additional_trust_anchors);
+ ~RequestParams();
+
+ bool operator<(const RequestParams& other) const;
+
+ std::string hostname;
+ int flags;
+ std::vector<SHA1HashValue> hash_values;
+ };
+
+ // CachedResult contains the result of a certificate verification.
+ struct CachedResult {
+ CachedResult();
+ ~CachedResult();
+
+ int error; // The return value of CertVerifier::Verify.
+ CertVerifyResult result; // The output of CertVerifier::Verify.
+ };
+
+ // Rather than having a single validity point along a monotonically increasing
+ // timeline, certificate verification is based on falling within a range of
+ // the certificate's NotBefore and NotAfter and based on what the current
+ // system clock says (which may advance forwards or backwards as users correct
+ // clock skew). CacheValidityPeriod and CacheExpirationFunctor are helpers to
+ // ensure that expiration is measured both by the 'general' case (now + cache
+ // TTL) and by whether or not significant enough clock skew was introduced
+ // since the last verification.
+ struct CacheValidityPeriod {
+ explicit CacheValidityPeriod(const base::Time& now);
+ CacheValidityPeriod(const base::Time& now, const base::Time& expiration);
+
+ base::Time verification_time;
+ base::Time expiration_time;
+ };
+
+ struct CacheExpirationFunctor {
+ // Returns true iff |now| is within the validity period of |expiration|.
+ bool operator()(const CacheValidityPeriod& now,
+ const CacheValidityPeriod& expiration) const;
+ };
+
+ typedef ExpiringCache<RequestParams, CachedResult, CacheValidityPeriod,
+ CacheExpirationFunctor> CertVerifierCache;
+
+ void HandleResult(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ const CertificateList& additional_trust_anchors,
+ int error,
+ const CertVerifyResult& verify_result);
+
+ // CertDatabase::Observer methods:
+ virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE;
+
+ // For unit testing.
+ void ClearCache() { cache_.Clear(); }
+ size_t GetCacheSize() const { return cache_.size(); }
+ uint64 cache_hits() const { return cache_hits_; }
+ uint64 requests() const { return requests_; }
+ uint64 inflight_joins() const { return inflight_joins_; }
+
+ // cache_ maps from a request to a cached result.
+ CertVerifierCache cache_;
+
+ // inflight_ maps from a request to an active verification which is taking
+ // place.
+ std::map<RequestParams, CertVerifierJob*> inflight_;
+
+ uint64 requests_;
+ uint64 cache_hits_;
+ uint64 inflight_joins_;
+
+ scoped_refptr<CertVerifyProc> verify_proc_;
+
+ CertTrustAnchorProvider* trust_anchor_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiThreadedCertVerifier);
+};
+
+} // namespace net
+
+#endif // NET_CERT_MULTI_THREADED_CERT_VERIFIER_H_
diff --git a/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc b/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc
new file mode 100644
index 00000000000..17d23d34995
--- /dev/null
+++ b/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc
@@ -0,0 +1,479 @@
+// Copyright (c) 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.
+
+#include "net/cert/multi_threaded_cert_verifier.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_trust_anchor_provider.h"
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Mock;
+using testing::ReturnRef;
+
+namespace net {
+
+namespace {
+
+void FailTest(int /* result */) {
+ FAIL();
+}
+
+class MockCertVerifyProc : public CertVerifyProc {
+ public:
+ MockCertVerifyProc() {}
+
+ private:
+ virtual ~MockCertVerifyProc() {}
+
+ // CertVerifyProc implementation
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE {
+ return false;
+ }
+
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ const CertificateList& additional_trust_anchors,
+ CertVerifyResult* verify_result) OVERRIDE {
+ verify_result->Reset();
+ verify_result->verified_cert = cert;
+ verify_result->cert_status = CERT_STATUS_COMMON_NAME_INVALID;
+ return ERR_CERT_COMMON_NAME_INVALID;
+ }
+};
+
+class MockCertTrustAnchorProvider : public CertTrustAnchorProvider {
+ public:
+ MockCertTrustAnchorProvider() {}
+ virtual ~MockCertTrustAnchorProvider() {}
+
+ MOCK_METHOD0(GetAdditionalTrustAnchors, const CertificateList&());
+};
+
+} // namespace
+
+class MultiThreadedCertVerifierTest : public ::testing::Test {
+ public:
+ MultiThreadedCertVerifierTest() : verifier_(new MockCertVerifyProc()) {}
+ virtual ~MultiThreadedCertVerifierTest() {}
+
+ protected:
+ MultiThreadedCertVerifier verifier_;
+};
+
+TEST_F(MultiThreadedCertVerifierTest, CacheHit) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "ok_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_EQ(1u, verifier_.requests());
+ ASSERT_EQ(0u, verifier_.cache_hits());
+ ASSERT_EQ(0u, verifier_.inflight_joins());
+ ASSERT_EQ(1u, verifier_.GetCacheSize());
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ // Synchronous completion.
+ ASSERT_NE(ERR_IO_PENDING, error);
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_TRUE(request_handle == NULL);
+ ASSERT_EQ(2u, verifier_.requests());
+ ASSERT_EQ(1u, verifier_.cache_hits());
+ ASSERT_EQ(0u, verifier_.inflight_joins());
+ ASSERT_EQ(1u, verifier_.GetCacheSize());
+}
+
+// Tests the same server certificate with different intermediate CA
+// certificates. These should be treated as different certificate chains even
+// though the two X509Certificate objects contain the same server certificate.
+TEST_F(MultiThreadedCertVerifierTest, DifferentCACerts) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "salesforce_com_test.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ scoped_refptr<X509Certificate> intermediate_cert1 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1);
+
+ scoped_refptr<X509Certificate> intermediate_cert2 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate_cert1->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain1 =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ intermediates.clear();
+ intermediates.push_back(intermediate_cert2->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain2 =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(cert_chain1.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_EQ(1u, verifier_.requests());
+ ASSERT_EQ(0u, verifier_.cache_hits());
+ ASSERT_EQ(0u, verifier_.inflight_joins());
+ ASSERT_EQ(1u, verifier_.GetCacheSize());
+
+ error = verifier_.Verify(cert_chain2.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_EQ(2u, verifier_.requests());
+ ASSERT_EQ(0u, verifier_.cache_hits());
+ ASSERT_EQ(0u, verifier_.inflight_joins());
+ ASSERT_EQ(2u, verifier_.GetCacheSize());
+}
+
+// Tests an inflight join.
+TEST_F(MultiThreadedCertVerifierTest, InflightJoin) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "ok_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+ CertVerifyResult verify_result2;
+ TestCompletionCallback callback2;
+ CertVerifier::RequestHandle request_handle2;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result2,
+ callback2.callback(),
+ &request_handle2,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2 != NULL);
+ error = callback.WaitForResult();
+ EXPECT_TRUE(IsCertificateError(error));
+ error = callback2.WaitForResult();
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_EQ(2u, verifier_.requests());
+ ASSERT_EQ(0u, verifier_.cache_hits());
+ ASSERT_EQ(1u, verifier_.inflight_joins());
+}
+
+// Tests that the callback of a canceled request is never made.
+TEST_F(MultiThreadedCertVerifierTest, CancelRequest) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "ok_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ int error;
+ CertVerifyResult verify_result;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ base::Bind(&FailTest),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ ASSERT_TRUE(request_handle != NULL);
+ verifier_.CancelRequest(request_handle);
+
+ // Issue a few more requests to the worker pool and wait for their
+ // completion, so that the task of the canceled request (which runs on a
+ // worker thread) is likely to complete by the end of this test.
+ TestCompletionCallback callback;
+ for (int i = 0; i < 5; ++i) {
+ error = verifier_.Verify(test_cert.get(),
+ "www2.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ verifier_.ClearCache();
+ }
+}
+
+// Tests that a canceled request is not leaked.
+TEST_F(MultiThreadedCertVerifierTest, CancelRequestThenQuit) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "ok_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ verifier_.CancelRequest(request_handle);
+ // Destroy |verifier| by going out of scope.
+}
+
+TEST_F(MultiThreadedCertVerifierTest, RequestParamsComparators) {
+ SHA1HashValue a_key;
+ memset(a_key.data, 'a', sizeof(a_key.data));
+
+ SHA1HashValue z_key;
+ memset(z_key.data, 'z', sizeof(z_key.data));
+
+ const CertificateList empty_list;
+ CertificateList test_list;
+ test_list.push_back(
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
+
+ struct {
+ // Keys to test
+ MultiThreadedCertVerifier::RequestParams key1;
+ MultiThreadedCertVerifier::RequestParams key2;
+
+ // Expectation:
+ // -1 means key1 is less than key2
+ // 0 means key1 equals key2
+ // 1 means key1 is greater than key2
+ int expected_result;
+ } tests[] = {
+ { // Test for basic equivalence.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ 0,
+ },
+ { // Test that different certificates but with the same CA and for
+ // the same host are different validation keys.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(z_key, a_key, "www.example.test",
+ 0, test_list),
+ -1,
+ },
+ { // Test that the same EE certificate for the same host, but with
+ // different chains are different validation keys.
+ MultiThreadedCertVerifier::RequestParams(a_key, z_key, "www.example.test",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ 1,
+ },
+ { // The same certificate, with the same chain, but for different
+ // hosts are different validation keys.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key,
+ "www1.example.test", 0,
+ test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key,
+ "www2.example.test", 0,
+ test_list),
+ -1,
+ },
+ { // The same certificate, chain, and host, but with different flags
+ // are different validation keys.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ CertVerifier::VERIFY_EV_CERT,
+ test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ 1,
+ },
+ { // Different additional_trust_anchors.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, empty_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ -1,
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i));
+
+ const MultiThreadedCertVerifier::RequestParams& key1 = tests[i].key1;
+ const MultiThreadedCertVerifier::RequestParams& key2 = tests[i].key2;
+
+ switch (tests[i].expected_result) {
+ case -1:
+ EXPECT_TRUE(key1 < key2);
+ EXPECT_FALSE(key2 < key1);
+ break;
+ case 0:
+ EXPECT_FALSE(key1 < key2);
+ EXPECT_FALSE(key2 < key1);
+ break;
+ case 1:
+ EXPECT_FALSE(key1 < key2);
+ EXPECT_TRUE(key2 < key1);
+ break;
+ default:
+ FAIL() << "Invalid expectation. Can be only -1, 0, 1";
+ }
+ }
+}
+
+TEST_F(MultiThreadedCertVerifierTest, CertTrustAnchorProvider) {
+ MockCertTrustAnchorProvider trust_provider;
+ verifier_.SetCertTrustAnchorProvider(&trust_provider);
+
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
+ ASSERT_TRUE(test_cert.get());
+
+ const CertificateList empty_cert_list;
+ CertificateList cert_list;
+ cert_list.push_back(test_cert);
+
+ // Check that Verify() asks the |trust_provider| for the current list of
+ // additional trust anchors.
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+ EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors())
+ .WillOnce(ReturnRef(empty_cert_list));
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+ ASSERT_EQ(1u, verifier_.requests());
+ ASSERT_EQ(0u, verifier_.cache_hits());
+
+ // The next Verify() uses the cached result.
+ EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors())
+ .WillOnce(ReturnRef(empty_cert_list));
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+ EXPECT_FALSE(request_handle);
+ ASSERT_EQ(2u, verifier_.requests());
+ ASSERT_EQ(1u, verifier_.cache_hits());
+
+ // Another Verify() for the same certificate but with a different list of
+ // trust anchors will not reuse the cache.
+ EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors())
+ .WillOnce(ReturnRef(cert_list));
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+ ASSERT_EQ(3u, verifier_.requests());
+ ASSERT_EQ(1u, verifier_.cache_hits());
+}
+
+} // namespace net
diff --git a/chromium/net/cert/nss_cert_database.cc b/chromium/net/cert/nss_cert_database.cc
new file mode 100644
index 00000000000..8e9ef4e6f01
--- /dev/null
+++ b/chromium/net/cert/nss_cert_database.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 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.
+
+#include "net/cert/nss_cert_database.h"
+
+#include <cert.h>
+#include <certdb.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <secmod.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list_threadsafe.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "net/base/crypto_module.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_database.h"
+#include "net/cert/x509_certificate.h"
+#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
+#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
+
+// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use
+// the new name of the macro.
+#if !defined(CERTDB_TERMINAL_RECORD)
+#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
+#endif
+
+// PSM = Mozilla's Personal Security Manager.
+namespace psm = mozilla_security_manager;
+
+namespace net {
+
+NSSCertDatabase::ImportCertFailure::ImportCertFailure(
+ const scoped_refptr<X509Certificate>& cert,
+ int err)
+ : certificate(cert), net_error(err) {}
+
+NSSCertDatabase::ImportCertFailure::~ImportCertFailure() {}
+
+// static
+NSSCertDatabase* NSSCertDatabase::GetInstance() {
+ return Singleton<NSSCertDatabase,
+ LeakySingletonTraits<NSSCertDatabase> >::get();
+}
+
+NSSCertDatabase::NSSCertDatabase()
+ : observer_list_(new ObserverListThreadSafe<Observer>) {
+ crypto::EnsureNSSInit();
+ psm::EnsurePKCS12Init();
+}
+
+NSSCertDatabase::~NSSCertDatabase() {}
+
+void NSSCertDatabase::ListCerts(CertificateList* certs) {
+ certs->clear();
+
+ CERTCertList* cert_list = PK11_ListCerts(PK11CertListUnique, NULL);
+ CERTCertListNode* node;
+ for (node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ certs->push_back(X509Certificate::CreateFromHandle(
+ node->cert, X509Certificate::OSCertHandles()));
+ }
+ CERT_DestroyCertList(cert_list);
+}
+
+CryptoModule* NSSCertDatabase::GetPublicModule() const {
+ CryptoModule* module =
+ CryptoModule::CreateFromHandle(crypto::GetPublicNSSKeySlot());
+ // The module is already referenced when returned from
+ // GetPublicNSSKeySlot, so we need to deref it once.
+ PK11_FreeSlot(module->os_module_handle());
+
+ return module;
+}
+
+CryptoModule* NSSCertDatabase::GetPrivateModule() const {
+ CryptoModule* module =
+ CryptoModule::CreateFromHandle(crypto::GetPrivateNSSKeySlot());
+ // The module is already referenced when returned from
+ // GetPrivateNSSKeySlot, so we need to deref it once.
+ PK11_FreeSlot(module->os_module_handle());
+
+ return module;
+}
+
+void NSSCertDatabase::ListModules(CryptoModuleList* modules,
+ bool need_rw) const {
+ modules->clear();
+
+ PK11SlotList* slot_list = NULL;
+ // The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc.
+ slot_list = PK11_GetAllTokens(CKM_INVALID_MECHANISM,
+ need_rw ? PR_TRUE : PR_FALSE, // needRW
+ PR_TRUE, // loadCerts (unused)
+ NULL); // wincx
+ if (!slot_list) {
+ LOG(ERROR) << "PK11_GetAllTokens failed: " << PORT_GetError();
+ return;
+ }
+
+ PK11SlotListElement* slot_element = PK11_GetFirstSafe(slot_list);
+ while (slot_element) {
+ modules->push_back(CryptoModule::CreateFromHandle(slot_element->slot));
+ slot_element = PK11_GetNextSafe(slot_list, slot_element,
+ PR_FALSE); // restart
+ }
+
+ PK11_FreeSlotList(slot_list);
+}
+
+int NSSCertDatabase::ImportFromPKCS12(
+ CryptoModule* module,
+ const std::string& data,
+ const base::string16& password,
+ bool is_extractable,
+ net::CertificateList* imported_certs) {
+ int result = psm::nsPKCS12Blob_Import(module->os_module_handle(),
+ data.data(), data.size(),
+ password,
+ is_extractable,
+ imported_certs);
+ if (result == net::OK)
+ NotifyObserversOfCertAdded(NULL);
+
+ return result;
+}
+
+int NSSCertDatabase::ExportToPKCS12(
+ const CertificateList& certs,
+ const base::string16& password,
+ std::string* output) const {
+ return psm::nsPKCS12Blob_Export(output, certs, password);
+}
+
+X509Certificate* NSSCertDatabase::FindRootInList(
+ const CertificateList& certificates) const {
+ DCHECK_GT(certificates.size(), 0U);
+
+ if (certificates.size() == 1)
+ return certificates[0].get();
+
+ X509Certificate* cert0 = certificates[0].get();
+ X509Certificate* cert1 = certificates[1].get();
+ X509Certificate* certn_2 = certificates[certificates.size() - 2].get();
+ X509Certificate* certn_1 = certificates[certificates.size() - 1].get();
+
+ if (CERT_CompareName(&cert1->os_cert_handle()->issuer,
+ &cert0->os_cert_handle()->subject) == SECEqual)
+ return cert0;
+ if (CERT_CompareName(&certn_2->os_cert_handle()->issuer,
+ &certn_1->os_cert_handle()->subject) == SECEqual)
+ return certn_1;
+
+ VLOG(1) << "certificate list is not a hierarchy";
+ return cert0;
+}
+
+bool NSSCertDatabase::ImportCACerts(const CertificateList& certificates,
+ TrustBits trust_bits,
+ ImportCertFailureList* not_imported) {
+ X509Certificate* root = FindRootInList(certificates);
+ bool success = psm::ImportCACerts(certificates, root, trust_bits,
+ not_imported);
+ if (success)
+ NotifyObserversOfCertTrustChanged(NULL);
+
+ return success;
+}
+
+bool NSSCertDatabase::ImportServerCert(const CertificateList& certificates,
+ TrustBits trust_bits,
+ ImportCertFailureList* not_imported) {
+ return psm::ImportServerCert(certificates, trust_bits, not_imported);
+}
+
+NSSCertDatabase::TrustBits NSSCertDatabase::GetCertTrust(
+ const X509Certificate* cert,
+ CertType type) const {
+ CERTCertTrust trust;
+ SECStatus srv = CERT_GetCertTrust(cert->os_cert_handle(), &trust);
+ if (srv != SECSuccess) {
+ LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError();
+ return TRUST_DEFAULT;
+ }
+ // We define our own more "friendly" TrustBits, which means we aren't able to
+ // round-trip all possible NSS trust flag combinations. We try to map them in
+ // a sensible way.
+ switch (type) {
+ case CA_CERT: {
+ const unsigned kTrustedCA = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
+ const unsigned kCAFlags = kTrustedCA | CERTDB_TERMINAL_RECORD;
+
+ TrustBits trust_bits = TRUST_DEFAULT;
+ if ((trust.sslFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
+ trust_bits |= DISTRUSTED_SSL;
+ else if (trust.sslFlags & kTrustedCA)
+ trust_bits |= TRUSTED_SSL;
+
+ if ((trust.emailFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
+ trust_bits |= DISTRUSTED_EMAIL;
+ else if (trust.emailFlags & kTrustedCA)
+ trust_bits |= TRUSTED_EMAIL;
+
+ if ((trust.objectSigningFlags & kCAFlags) == CERTDB_TERMINAL_RECORD)
+ trust_bits |= DISTRUSTED_OBJ_SIGN;
+ else if (trust.objectSigningFlags & kTrustedCA)
+ trust_bits |= TRUSTED_OBJ_SIGN;
+
+ return trust_bits;
+ }
+ case SERVER_CERT:
+ if (trust.sslFlags & CERTDB_TERMINAL_RECORD) {
+ if (trust.sslFlags & CERTDB_TRUSTED)
+ return TRUSTED_SSL;
+ return DISTRUSTED_SSL;
+ }
+ return TRUST_DEFAULT;
+ default:
+ return TRUST_DEFAULT;
+ }
+}
+
+bool NSSCertDatabase::IsUntrusted(const X509Certificate* cert) const {
+ CERTCertTrust nsstrust;
+ SECStatus rv = CERT_GetCertTrust(cert->os_cert_handle(), &nsstrust);
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError();
+ return false;
+ }
+
+ // The CERTCertTrust structure contains three trust records:
+ // sslFlags, emailFlags, and objectSigningFlags. The three
+ // trust records are independent of each other.
+ //
+ // If the CERTDB_TERMINAL_RECORD bit in a trust record is set,
+ // then that trust record is a terminal record. A terminal
+ // record is used for explicit trust and distrust of an
+ // end-entity or intermediate CA cert.
+ //
+ // In a terminal record, if neither CERTDB_TRUSTED_CA nor
+ // CERTDB_TRUSTED is set, then the terminal record means
+ // explicit distrust. On the other hand, if the terminal
+ // record has either CERTDB_TRUSTED_CA or CERTDB_TRUSTED bit
+ // set, then the terminal record means explicit trust.
+ //
+ // For a root CA, the trust record does not have
+ // the CERTDB_TERMINAL_RECORD bit set.
+
+ static const unsigned int kTrusted = CERTDB_TRUSTED_CA | CERTDB_TRUSTED;
+ if ((nsstrust.sslFlags & CERTDB_TERMINAL_RECORD) != 0 &&
+ (nsstrust.sslFlags & kTrusted) == 0) {
+ return true;
+ }
+ if ((nsstrust.emailFlags & CERTDB_TERMINAL_RECORD) != 0 &&
+ (nsstrust.emailFlags & kTrusted) == 0) {
+ return true;
+ }
+ if ((nsstrust.objectSigningFlags & CERTDB_TERMINAL_RECORD) != 0 &&
+ (nsstrust.objectSigningFlags & kTrusted) == 0) {
+ return true;
+ }
+
+ // Self-signed certificates that don't have any trust bits set are untrusted.
+ // Other certificates that don't have any trust bits set may still be trusted
+ // if they chain up to a trust anchor.
+ if (CERT_CompareName(&cert->os_cert_handle()->issuer,
+ &cert->os_cert_handle()->subject) == SECEqual) {
+ return (nsstrust.sslFlags & kTrusted) == 0 &&
+ (nsstrust.emailFlags & kTrusted) == 0 &&
+ (nsstrust.objectSigningFlags & kTrusted) == 0;
+ }
+
+ return false;
+}
+
+bool NSSCertDatabase::SetCertTrust(const X509Certificate* cert,
+ CertType type,
+ TrustBits trust_bits) {
+ bool success = psm::SetCertTrust(cert, type, trust_bits);
+ if (success)
+ NotifyObserversOfCertTrustChanged(cert);
+
+ return success;
+}
+
+bool NSSCertDatabase::DeleteCertAndKey(const X509Certificate* cert) {
+ // For some reason, PK11_DeleteTokenCertAndKey only calls
+ // SEC_DeletePermCertificate if the private key is found. So, we check
+ // whether a private key exists before deciding which function to call to
+ // delete the cert.
+ SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert(cert->os_cert_handle(),
+ NULL);
+ if (privKey) {
+ SECKEY_DestroyPrivateKey(privKey);
+ if (PK11_DeleteTokenCertAndKey(cert->os_cert_handle(), NULL)) {
+ LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError();
+ return false;
+ }
+ } else {
+ if (SEC_DeletePermCertificate(cert->os_cert_handle())) {
+ LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError();
+ return false;
+ }
+ }
+
+ NotifyObserversOfCertRemoved(cert);
+
+ return true;
+}
+
+bool NSSCertDatabase::IsReadOnly(const X509Certificate* cert) const {
+ PK11SlotInfo* slot = cert->os_cert_handle()->slot;
+ return slot && PK11_IsReadOnly(slot);
+}
+
+void NSSCertDatabase::AddObserver(Observer* observer) {
+ observer_list_->AddObserver(observer);
+}
+
+void NSSCertDatabase::RemoveObserver(Observer* observer) {
+ observer_list_->RemoveObserver(observer);
+}
+
+void NSSCertDatabase::NotifyObserversOfCertAdded(const X509Certificate* cert) {
+ observer_list_->Notify(&Observer::OnCertAdded, make_scoped_refptr(cert));
+}
+
+void NSSCertDatabase::NotifyObserversOfCertRemoved(
+ const X509Certificate* cert) {
+ observer_list_->Notify(&Observer::OnCertRemoved, make_scoped_refptr(cert));
+}
+
+void NSSCertDatabase::NotifyObserversOfCertTrustChanged(
+ const X509Certificate* cert) {
+ observer_list_->Notify(
+ &Observer::OnCertTrustChanged, make_scoped_refptr(cert));
+}
+
+} // namespace net
diff --git a/chromium/net/cert/nss_cert_database.h b/chromium/net/cert/nss_cert_database.h
new file mode 100644
index 00000000000..9db1b75d973
--- /dev/null
+++ b/chromium/net/cert/nss_cert_database.h
@@ -0,0 +1,208 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_NSS_CERT_DATABASE_H_
+#define NET_CERT_NSS_CERT_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_type.h"
+#include "net/cert/x509_certificate.h"
+
+template <typename T> struct DefaultSingletonTraits;
+template <class ObserverType> class ObserverListThreadSafe;
+
+namespace net {
+
+class CryptoModule;
+typedef std::vector<scoped_refptr<CryptoModule> > CryptoModuleList;
+
+// Provides functions to manipulate the NSS certificate stores.
+class NET_EXPORT NSSCertDatabase {
+ public:
+
+ class NET_EXPORT Observer {
+ public:
+ virtual ~Observer() {}
+
+ // Will be called when a new certificate is added.
+ // Called with |cert| == NULL after importing a list of certificates
+ // in ImportFromPKCS12().
+ virtual void OnCertAdded(const X509Certificate* cert) {}
+
+ // Will be called when a certificate is removed.
+ virtual void OnCertRemoved(const X509Certificate* cert) {}
+
+ // Will be called when a certificate's trust is changed.
+ // Called with |cert| == NULL after importing a list of certificates
+ // in ImportCACerts().
+ virtual void OnCertTrustChanged(const X509Certificate* cert) {}
+
+ protected:
+ Observer() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ // Stores per-certificate error codes for import failures.
+ struct NET_EXPORT ImportCertFailure {
+ public:
+ ImportCertFailure(const scoped_refptr<X509Certificate>& cert, int err);
+ ~ImportCertFailure();
+
+ scoped_refptr<X509Certificate> certificate;
+ int net_error;
+ };
+ typedef std::vector<ImportCertFailure> ImportCertFailureList;
+
+ // Constants that define which usages a certificate is trusted for.
+ // They are used in combination with CertType to specify trust for each type
+ // of certificate.
+ // For a CA_CERT, they specify that the CA is trusted for issuing server and
+ // client certs of each type.
+ // For SERVER_CERT, only TRUSTED_SSL makes sense, and specifies the cert is
+ // trusted as a server.
+ // For EMAIL_CERT, only TRUSTED_EMAIL makes sense, and specifies the cert is
+ // trusted for email.
+ // DISTRUSTED_* specifies that the cert should not be trusted for the given
+ // usage, regardless of whether it would otherwise inherit trust from the
+ // issuer chain.
+ // Use TRUST_DEFAULT to inherit trust as normal.
+ // NOTE: The actual constants are defined using an enum instead of static
+ // consts due to compilation/linkage constraints with template functions.
+ typedef uint32 TrustBits;
+ enum {
+ TRUST_DEFAULT = 0,
+ TRUSTED_SSL = 1 << 0,
+ TRUSTED_EMAIL = 1 << 1,
+ TRUSTED_OBJ_SIGN = 1 << 2,
+ DISTRUSTED_SSL = 1 << 3,
+ DISTRUSTED_EMAIL = 1 << 4,
+ DISTRUSTED_OBJ_SIGN = 1 << 5,
+ };
+
+ static NSSCertDatabase* GetInstance();
+
+ // Get a list of unique certificates in the certificate database (one
+ // instance of all certificates).
+ void ListCerts(CertificateList* certs);
+
+ // Get the default module for public key data.
+ // The returned pointer must be stored in a scoped_refptr<CryptoModule>.
+ CryptoModule* GetPublicModule() const;
+
+ // Get the default module for private key or mixed private/public key data.
+ // The returned pointer must be stored in a scoped_refptr<CryptoModule>.
+ CryptoModule* GetPrivateModule() const;
+
+ // Get all modules.
+ // If |need_rw| is true, only writable modules will be returned.
+ void ListModules(CryptoModuleList* modules, bool need_rw) const;
+
+ // Import certificates and private keys from PKCS #12 blob into the module.
+ // If |is_extractable| is false, mark the private key as being unextractable
+ // from the module.
+ // Returns OK or a network error code such as ERR_PKCS12_IMPORT_BAD_PASSWORD
+ // or ERR_PKCS12_IMPORT_ERROR. |imported_certs|, if non-NULL, returns a list
+ // of certs that were imported.
+ int ImportFromPKCS12(CryptoModule* module,
+ const std::string& data,
+ const base::string16& password,
+ bool is_extractable,
+ CertificateList* imported_certs);
+
+ // Export the given certificates and private keys into a PKCS #12 blob,
+ // storing into |output|.
+ // Returns the number of certificates successfully exported.
+ int ExportToPKCS12(const CertificateList& certs,
+ const base::string16& password,
+ std::string* output) const;
+
+ // Uses similar logic to nsNSSCertificateDB::handleCACertDownload to find the
+ // root. Assumes the list is an ordered hierarchy with the root being either
+ // the first or last element.
+ // TODO(mattm): improve this to handle any order.
+ X509Certificate* FindRootInList(const CertificateList& certificates) const;
+
+ // Import CA certificates.
+ // Tries to import all the certificates given. The root will be trusted
+ // according to |trust_bits|. Any certificates that could not be imported
+ // will be listed in |not_imported|.
+ // Returns false if there is an internal error, otherwise true is returned and
+ // |not_imported| should be checked for any certificates that were not
+ // imported.
+ bool ImportCACerts(const CertificateList& certificates,
+ TrustBits trust_bits,
+ ImportCertFailureList* not_imported);
+
+ // Import server certificate. The first cert should be the server cert. Any
+ // additional certs should be intermediate/CA certs and will be imported but
+ // not given any trust.
+ // Any certificates that could not be imported will be listed in
+ // |not_imported|.
+ // |trust_bits| can be set to explicitly trust or distrust the certificate, or
+ // use TRUST_DEFAULT to inherit trust as normal.
+ // Returns false if there is an internal error, otherwise true is returned and
+ // |not_imported| should be checked for any certificates that were not
+ // imported.
+ bool ImportServerCert(const CertificateList& certificates,
+ TrustBits trust_bits,
+ ImportCertFailureList* not_imported);
+
+ // Get trust bits for certificate.
+ TrustBits GetCertTrust(const X509Certificate* cert, CertType type) const;
+
+ // IsUntrusted returns true if |cert| is specifically untrusted. These
+ // certificates are stored in the database for the specific purpose of
+ // rejecting them.
+ bool IsUntrusted(const X509Certificate* cert) const;
+
+ // Set trust values for certificate.
+ // Returns true on success or false on failure.
+ bool SetCertTrust(const X509Certificate* cert,
+ CertType type,
+ TrustBits trust_bits);
+
+ // Delete certificate and associated private key (if one exists).
+ // |cert| is still valid when this function returns. Returns true on
+ // success.
+ bool DeleteCertAndKey(const X509Certificate* cert);
+
+ // Check whether cert is stored in a readonly slot.
+ bool IsReadOnly(const X509Certificate* cert) const;
+
+ // Registers |observer| to receive notifications of certificate changes. The
+ // thread on which this is called is the thread on which |observer| will be
+ // called back with notifications.
+ void AddObserver(Observer* observer);
+
+ // Unregisters |observer| from receiving notifications. This must be called
+ // on the same thread on which AddObserver() was called.
+ void RemoveObserver(Observer* observer);
+
+ private:
+ friend struct DefaultSingletonTraits<NSSCertDatabase>;
+
+ NSSCertDatabase();
+ ~NSSCertDatabase();
+
+ // Broadcasts notifications to all registered observers.
+ void NotifyObserversOfCertAdded(const X509Certificate* cert);
+ void NotifyObserversOfCertRemoved(const X509Certificate* cert);
+ void NotifyObserversOfCertTrustChanged(const X509Certificate* cert);
+
+ const scoped_refptr<ObserverListThreadSafe<Observer> > observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(NSSCertDatabase);
+};
+
+} // namespace net
+
+#endif // NET_CERT_NSS_CERT_DATABASE_H_
diff --git a/chromium/net/cert/nss_cert_database_unittest.cc b/chromium/net/cert/nss_cert_database_unittest.cc
new file mode 100644
index 00000000000..2e712bb54ad
--- /dev/null
+++ b/chromium/net/cert/nss_cert_database_unittest.cc
@@ -0,0 +1,1042 @@
+// Copyright (c) 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.
+
+#include <cert.h>
+#include <certdb.h>
+#include <pk11pub.h>
+
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/crypto_module.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_proc_nss.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/nss_cert_database.h"
+#include "net/cert/x509_certificate.h"
+#include "net/test/cert_test_util.h"
+#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use
+// the new name of the macro.
+#if !defined(CERTDB_TERMINAL_RECORD)
+#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
+#endif
+
+namespace net {
+
+class CertDatabaseNSSTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_TRUE(test_nssdb_.is_open());
+ cert_db_ = NSSCertDatabase::GetInstance();
+ slot_ = cert_db_->GetPublicModule();
+
+ // Test db should be empty at start of test.
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+ }
+
+ virtual void TearDown() {
+ // Don't try to cleanup if the setup failed.
+ ASSERT_TRUE(slot_->os_module_handle());
+
+ EXPECT_TRUE(CleanupSlotContents());
+
+ // Run the message loop to process any observer callbacks (e.g. for the
+ // ClientSocketFactory singleton) so that the scoped ref ptrs created in
+ // NSSCertDatabase::NotifyObservers* get released.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+ }
+
+ protected:
+ static std::string ReadTestFile(const std::string& name) {
+ std::string result;
+ base::FilePath cert_path = GetTestCertsDirectory().AppendASCII(name);
+ EXPECT_TRUE(file_util::ReadFileToString(cert_path, &result));
+ return result;
+ }
+
+ static bool ReadCertIntoList(const std::string& name,
+ CertificateList* certs) {
+ scoped_refptr<X509Certificate> cert(
+ ImportCertFromFile(GetTestCertsDirectory(), name));
+ if (!cert.get())
+ return false;
+
+ certs->push_back(cert);
+ return true;
+ }
+
+ static CertificateList ListCertsInSlot(PK11SlotInfo* slot) {
+ CertificateList result;
+ CERTCertList* cert_list = PK11_ListCertsInSlot(slot);
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ result.push_back(X509Certificate::CreateFromHandle(
+ node->cert, X509Certificate::OSCertHandles()));
+ }
+ CERT_DestroyCertList(cert_list);
+
+ // Sort the result so that test comparisons can be deterministic.
+ std::sort(result.begin(), result.end(), X509Certificate::LessThan());
+ return result;
+ }
+
+ scoped_refptr<CryptoModule> slot_;
+ NSSCertDatabase* cert_db_;
+ const CertificateList empty_cert_list_;
+
+ private:
+ bool CleanupSlotContents() {
+ bool ok = true;
+ CertificateList certs = ListCertsInSlot(slot_->os_module_handle());
+ CERTCertTrust default_trust = {0};
+ for (size_t i = 0; i < certs.size(); ++i) {
+ // Reset cert trust values to defaults before deleting. Otherwise NSS
+ // somehow seems to remember the trust which can break following tests.
+ SECStatus srv = CERT_ChangeCertTrust(
+ CERT_GetDefaultCertDB(), certs[i]->os_cert_handle(), &default_trust);
+ if (srv != SECSuccess)
+ ok = false;
+
+ if (!cert_db_->DeleteCertAndKey(certs[i].get()))
+ ok = false;
+ }
+ return ok;
+ }
+
+ crypto::ScopedTestNSSDB test_nssdb_;
+};
+
+TEST_F(CertDatabaseNSSTest, ListCerts) {
+ // This test isn't terribly useful, though it will at least let valgrind test
+ // for leaks.
+ CertificateList certs;
+ cert_db_->ListCerts(&certs);
+ // The test DB is empty, but let's assume there will always be something in
+ // the other slots.
+ EXPECT_LT(0U, certs.size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12WrongPassword) {
+ std::string pkcs12_data = ReadTestFile("client.p12");
+
+ EXPECT_EQ(ERR_PKCS12_IMPORT_BAD_PASSWORD,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ base::string16(),
+ true, // is_extractable
+ NULL));
+
+ // Test db should still be empty.
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12AsExtractableAndExportAgain) {
+ std::string pkcs12_data = ReadTestFile("client.p12");
+
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ true, // is_extractable
+ NULL));
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+
+ EXPECT_EQ("testusercert",
+ cert->subject().common_name);
+
+ // TODO(mattm): move export test to separate test case?
+ std::string exported_data;
+ EXPECT_EQ(1, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"),
+ &exported_data));
+ ASSERT_LT(0U, exported_data.size());
+ // TODO(mattm): further verification of exported data?
+}
+
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12Twice) {
+ std::string pkcs12_data = ReadTestFile("client.p12");
+
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ true, // is_extractable
+ NULL));
+ EXPECT_EQ(1U, ListCertsInSlot(slot_->os_module_handle()).size());
+
+ // NSS has a SEC_ERROR_PKCS12_DUPLICATE_DATA error, but it doesn't look like
+ // it's ever used. This test verifies that.
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ true, // is_extractable
+ NULL));
+ EXPECT_EQ(1U, ListCertsInSlot(slot_->os_module_handle()).size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12AsUnextractableAndExportAgain) {
+ std::string pkcs12_data = ReadTestFile("client.p12");
+
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ false, // is_extractable
+ NULL));
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+
+ EXPECT_EQ("testusercert",
+ cert->subject().common_name);
+
+ std::string exported_data;
+ EXPECT_EQ(0, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"),
+ &exported_data));
+}
+
+// Importing a PKCS#12 file with a certificate but no corresponding
+// private key should not mark an existing private key as unextractable.
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12OnlyMarkIncludedKey) {
+ std::string pkcs12_data = ReadTestFile("client.p12");
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ true, // is_extractable
+ NULL));
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+
+ // Now import a PKCS#12 file with just a certificate but no private key.
+ pkcs12_data = ReadTestFile("client-nokey.p12");
+ EXPECT_EQ(OK,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ ASCIIToUTF16("12345"),
+ false, // is_extractable
+ NULL));
+
+ cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+
+ // Make sure the imported private key is still extractable.
+ std::string exported_data;
+ EXPECT_EQ(1, cert_db_->ExportToPKCS12(cert_list, ASCIIToUTF16("exportpw"),
+ &exported_data));
+ ASSERT_LT(0U, exported_data.size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportFromPKCS12InvalidFile) {
+ std::string pkcs12_data = "Foobarbaz";
+
+ EXPECT_EQ(ERR_PKCS12_IMPORT_INVALID_FILE,
+ cert_db_->ImportFromPKCS12(slot_.get(),
+ pkcs12_data,
+ base::string16(),
+ true, // is_extractable
+ NULL));
+
+ // Test db should still be empty.
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACert_SSLTrust) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+ EXPECT_FALSE(certs[0]->os_cert_handle()->isperm);
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+ EXPECT_EQ("Test Root CA", cert->subject().common_name);
+
+ EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL,
+ cert_db_->GetCertTrust(cert.get(), CA_CERT));
+
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA |
+ CERTDB_TRUSTED_CLIENT_CA),
+ cert->os_cert_handle()->trust->sslFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->emailFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->objectSigningFlags);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACert_EmailTrust) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+ EXPECT_FALSE(certs[0]->os_cert_handle()->isperm);
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_EMAIL,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+ EXPECT_EQ("Test Root CA", cert->subject().common_name);
+
+ EXPECT_EQ(NSSCertDatabase::TRUSTED_EMAIL,
+ cert_db_->GetCertTrust(cert.get(), CA_CERT));
+
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->sslFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA |
+ CERTDB_TRUSTED_CLIENT_CA),
+ cert->os_cert_handle()->trust->emailFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->objectSigningFlags);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACert_ObjSignTrust) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+ EXPECT_FALSE(certs[0]->os_cert_handle()->isperm);
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_OBJ_SIGN,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> cert(cert_list[0]);
+ EXPECT_EQ("Test Root CA", cert->subject().common_name);
+
+ EXPECT_EQ(NSSCertDatabase::TRUSTED_OBJ_SIGN,
+ cert_db_->GetCertTrust(cert.get(), CA_CERT));
+
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->sslFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ cert->os_cert_handle()->trust->emailFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA |
+ CERTDB_TRUSTED_CLIENT_CA),
+ cert->os_cert_handle()->trust->objectSigningFlags);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCA_NotCACert) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "ok_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+ EXPECT_FALSE(certs[0]->os_cert_handle()->isperm);
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+ ASSERT_EQ(1U, failed.size());
+ // Note: this compares pointers directly. It's okay in this case because
+ // ImportCACerts returns the same pointers that were passed in. In the
+ // general case IsSameOSCert should be used.
+ EXPECT_EQ(certs[0], failed[0].certificate);
+ EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[0].net_error);
+
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACertHierarchy) {
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("www_us_army_mil_cert.der", &certs));
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ // Have to specify email trust for the cert verification of the child cert to
+ // work (see
+ // http://mxr.mozilla.org/mozilla/source/security/nss/lib/certhigh/certvfy.c#752
+ // "XXX This choice of trustType seems arbitrary.")
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL,
+ &failed));
+
+ ASSERT_EQ(2U, failed.size());
+ EXPECT_EQ("DOD CA-17", failed[0].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[0].net_error); // The certificate expired.
+ EXPECT_EQ("www.us.army.mil", failed[1].certificate->subject().common_name);
+ EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[1].net_error);
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyDupeRoot) {
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs));
+
+ // First import just the root.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name);
+
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("www_us_army_mil_cert.der", &certs));
+
+ // Now import with the other certs in the list too. Even though the root is
+ // already present, we should still import the rest.
+ failed.clear();
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL,
+ &failed));
+
+ ASSERT_EQ(3U, failed.size());
+ EXPECT_EQ("DoD Root CA 2", failed[0].certificate->subject().common_name);
+ EXPECT_EQ(ERR_IMPORT_CERT_ALREADY_EXISTS, failed[0].net_error);
+ EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[1].net_error); // The certificate expired.
+ EXPECT_EQ("www.us.army.mil", failed[2].certificate->subject().common_name);
+ EXPECT_EQ(ERR_IMPORT_CA_CERT_NOT_CA, failed[2].net_error);
+
+ cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyUntrusted) {
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs));
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(certs, NSSCertDatabase::TRUST_DEFAULT,
+ &failed));
+
+ ASSERT_EQ(1U, failed.size());
+ EXPECT_EQ("DOD CA-17", failed[0].certificate->subject().common_name);
+ // TODO(mattm): should check for net error equivalent of
+ // SEC_ERROR_UNTRUSTED_ISSUER
+ EXPECT_EQ(ERR_FAILED, failed[0].net_error);
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACertHierarchyTree) {
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("dod_root_ca_2_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_13_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs));
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL,
+ &failed));
+
+ EXPECT_EQ(2U, failed.size());
+ EXPECT_EQ("DOD CA-13", failed[0].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[0].net_error); // The certificate expired.
+ EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[1].net_error); // The certificate expired.
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("DoD Root CA 2", cert_list[0]->subject().common_name);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCACertNotHierarchy) {
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_13_cert.der", &certs));
+ ASSERT_TRUE(ReadCertIntoList("dod_ca_17_cert.der", &certs));
+
+ // Import it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ certs, NSSCertDatabase::TRUSTED_SSL | NSSCertDatabase::TRUSTED_EMAIL |
+ NSSCertDatabase::TRUSTED_OBJ_SIGN, &failed));
+
+ ASSERT_EQ(2U, failed.size());
+ // TODO(mattm): should check for net error equivalent of
+ // SEC_ERROR_UNKNOWN_ISSUER
+ EXPECT_EQ("DOD CA-13", failed[0].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[0].net_error);
+ EXPECT_EQ("DOD CA-17", failed[1].certificate->subject().common_name);
+ EXPECT_EQ(ERR_FAILED, failed[1].net_error);
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ EXPECT_EQ("Test Root CA", cert_list[0]->subject().common_name);
+}
+
+// http://crbug.com/108009 - Disabled, as google.chain.pem is an expired
+// certificate.
+TEST_F(CertDatabaseNSSTest, DISABLED_ImportServerCert) {
+ // Need to import intermediate cert for the verify of google cert, otherwise
+ // it will try to fetch it automatically with cert_pi_useAIACertFetch, which
+ // will cause OCSPCreateSession on the main thread, which is not allowed.
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "google.chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(2U, certs.size());
+
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(2U, cert_list.size());
+ scoped_refptr<X509Certificate> goog_cert(cert_list[0]);
+ scoped_refptr<X509Certificate> thawte_cert(cert_list[1]);
+ EXPECT_EQ("www.google.com", goog_cert->subject().common_name);
+ EXPECT_EQ("Thawte SGC CA", thawte_cert->subject().common_name);
+
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(goog_cert.get(), SERVER_CERT));
+
+ EXPECT_EQ(0U, goog_cert->os_cert_handle()->trust->sslFlags);
+
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(goog_cert.get(),
+ "www.google.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportServerCert_SelfSigned) {
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("punycodetest.der", &certs));
+
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> puny_cert(cert_list[0]);
+
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(puny_cert.get(), SERVER_CERT));
+ EXPECT_EQ(0U, puny_cert->os_cert_handle()->trust->sslFlags);
+
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(puny_cert.get(),
+ "xn--wgv71a119e.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportServerCert_SelfSigned_Trusted) {
+ // When using CERT_PKIXVerifyCert (which we do), server trust only works from
+ // 3.13.4 onwards. See https://bugzilla.mozilla.org/show_bug.cgi?id=647364.
+ if (!NSS_VersionCheck("3.13.4")) {
+ LOG(INFO) << "test skipped on NSS < 3.13.4";
+ return;
+ }
+
+ CertificateList certs;
+ ASSERT_TRUE(ReadCertIntoList("punycodetest.der", &certs));
+
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList cert_list = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, cert_list.size());
+ scoped_refptr<X509Certificate> puny_cert(cert_list[0]);
+
+ EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL,
+ cert_db_->GetCertTrust(puny_cert.get(), SERVER_CERT));
+ EXPECT_EQ(unsigned(CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD),
+ puny_cert->os_cert_handle()->trust->sslFlags);
+
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(puny_cert.get(),
+ "xn--wgv71a119e.com",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCaAndServerCert) {
+ CertificateList ca_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_certs.size());
+
+ // Import CA cert and trust it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "ok_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(certs, NSSCertDatabase::TRUST_DEFAULT,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ // Server cert should verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, ImportCaAndServerCert_DistrustServer) {
+ // Explicit distrust only works starting in NSS 3.13.
+ if (!NSS_VersionCheck("3.13")) {
+ LOG(INFO) << "test skipped on NSS < 3.13";
+ return;
+ }
+
+ CertificateList ca_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "root_ca_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_certs.size());
+
+ // Import CA cert and trust it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "ok_cert.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert without inheriting trust from issuer (explicit
+ // distrust).
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::DISTRUSTED_SSL, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::DISTRUSTED_SSL,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ EXPECT_EQ(unsigned(CERTDB_TERMINAL_RECORD),
+ certs[0]->os_cert_handle()->trust->sslFlags);
+
+ // Server cert should fail to verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+ EXPECT_EQ(CERT_STATUS_REVOKED, verify_result.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, TrustIntermediateCa) {
+ CertificateList ca_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-root.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_certs.size());
+
+ // Import Root CA cert and distrust it.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::DISTRUSTED_SSL,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList intermediate_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, intermediate_certs.size());
+
+ // Import Intermediate CA cert and trust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs,
+ NSSCertDatabase::TRUSTED_SSL, &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ // Server cert should verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Explicit distrust only works starting in NSS 3.13.
+ if (!NSS_VersionCheck("3.13")) {
+ LOG(INFO) << "test partially skipped on NSS < 3.13";
+ return;
+ }
+
+ // Trust the root cert and distrust the intermediate.
+ EXPECT_TRUE(cert_db_->SetCertTrust(
+ ca_certs[0].get(), CA_CERT, NSSCertDatabase::TRUSTED_SSL));
+ EXPECT_TRUE(cert_db_->SetCertTrust(
+ intermediate_certs[0].get(), CA_CERT, NSSCertDatabase::DISTRUSTED_SSL));
+ EXPECT_EQ(
+ unsigned(CERTDB_VALID_CA | CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA),
+ ca_certs[0]->os_cert_handle()->trust->sslFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ ca_certs[0]->os_cert_handle()->trust->emailFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ ca_certs[0]->os_cert_handle()->trust->objectSigningFlags);
+ EXPECT_EQ(unsigned(CERTDB_TERMINAL_RECORD),
+ intermediate_certs[0]->os_cert_handle()->trust->sslFlags);
+ EXPECT_EQ(unsigned(CERTDB_VALID_CA),
+ intermediate_certs[0]->os_cert_handle()->trust->emailFlags);
+ EXPECT_EQ(
+ unsigned(CERTDB_VALID_CA),
+ intermediate_certs[0]->os_cert_handle()->trust->objectSigningFlags);
+
+ // Server cert should fail to verify.
+ CertVerifyResult verify_result2;
+ error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result2);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+ EXPECT_EQ(CERT_STATUS_REVOKED, verify_result2.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, TrustIntermediateCa2) {
+ if (NSS_VersionCheck("3.14.2") && !NSS_VersionCheck("3.15")) {
+ // See http://bugzil.la/863947 for details.
+ LOG(INFO) << "Skipping test for NSS 3.14.2 - NSS 3.15";
+ return;
+ }
+
+ NSSCertDatabase::ImportCertFailureList failed;
+
+ CertificateList intermediate_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, intermediate_certs.size());
+
+ // Import Intermediate CA cert and trust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs,
+ NSSCertDatabase::TRUSTED_SSL, &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ // Server cert should verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Without explicit trust of the intermediate, verification should fail.
+ EXPECT_TRUE(cert_db_->SetCertTrust(
+ intermediate_certs[0].get(), CA_CERT, NSSCertDatabase::TRUST_DEFAULT));
+
+ // Server cert should fail to verify.
+ CertVerifyResult verify_result2;
+ error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result2);
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result2.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, TrustIntermediateCa3) {
+ if (NSS_VersionCheck("3.14.2") && !NSS_VersionCheck("3.15")) {
+ // See http://bugzil.la/863947 for details.
+ LOG(INFO) << "Skipping test for NSS 3.14.2 - NSS 3.15";
+ return;
+ }
+
+ NSSCertDatabase::ImportCertFailureList failed;
+
+ CertificateList ca_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-root.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_certs.size());
+
+ // Import Root CA cert and default trust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUST_DEFAULT,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList intermediate_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, intermediate_certs.size());
+
+ // Import Intermediate CA cert and trust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(intermediate_certs,
+ NSSCertDatabase::TRUSTED_SSL, &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ // Server cert should verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result.cert_status);
+
+ // Without explicit trust of the intermediate, verification should fail.
+ EXPECT_TRUE(cert_db_->SetCertTrust(
+ intermediate_certs[0].get(), CA_CERT, NSSCertDatabase::TRUST_DEFAULT));
+
+ // Server cert should fail to verify.
+ CertVerifyResult verify_result2;
+ error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result2);
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, error);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result2.cert_status);
+}
+
+TEST_F(CertDatabaseNSSTest, TrustIntermediateCa4) {
+ // Explicit distrust only works starting in NSS 3.13.
+ if (!NSS_VersionCheck("3.13")) {
+ LOG(INFO) << "test skipped on NSS < 3.13";
+ return;
+ }
+
+ NSSCertDatabase::ImportCertFailureList failed;
+
+ CertificateList ca_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-root.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, ca_certs.size());
+
+ // Import Root CA cert and trust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(ca_certs, NSSCertDatabase::TRUSTED_SSL,
+ &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList intermediate_certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, intermediate_certs.size());
+
+ // Import Intermediate CA cert and distrust it.
+ EXPECT_TRUE(cert_db_->ImportCACerts(
+ intermediate_certs, NSSCertDatabase::DISTRUSTED_SSL, &failed));
+ EXPECT_EQ(0U, failed.size());
+
+ CertificateList certs = CreateCertificateListFromFile(
+ GetTestCertsDirectory(), "2048-rsa-ee-by-2048-rsa-intermediate.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ // Server cert should not verify.
+ scoped_refptr<CertVerifyProc> verify_proc(new CertVerifyProcNSS());
+ int flags = 0;
+ CertVerifyResult verify_result;
+ int error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result);
+ EXPECT_EQ(ERR_CERT_REVOKED, error);
+ EXPECT_EQ(CERT_STATUS_REVOKED, verify_result.cert_status);
+
+ // Without explicit distrust of the intermediate, verification should succeed.
+ EXPECT_TRUE(cert_db_->SetCertTrust(
+ intermediate_certs[0].get(), CA_CERT, NSSCertDatabase::TRUST_DEFAULT));
+
+ // Server cert should verify.
+ CertVerifyResult verify_result2;
+ error = verify_proc->Verify(certs[0].get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ empty_cert_list_,
+ &verify_result2);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0U, verify_result2.cert_status);
+}
+
+// Importing two certificates with the same issuer and subject common name,
+// but overall distinct subject names, should succeed and generate a unique
+// nickname for the second certificate.
+TEST_F(CertDatabaseNSSTest, ImportDuplicateCommonName) {
+ CertificateList certs =
+ CreateCertificateListFromFile(GetTestCertsDirectory(),
+ "duplicate_cn_1.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs.size());
+
+ EXPECT_EQ(0U, ListCertsInSlot(slot_->os_module_handle()).size());
+
+ // Import server cert with default trust.
+ NSSCertDatabase::ImportCertFailureList failed;
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs[0].get(), SERVER_CERT));
+
+ CertificateList new_certs = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(1U, new_certs.size());
+
+ // Now attempt to import a different certificate with the same common name.
+ CertificateList certs2 =
+ CreateCertificateListFromFile(GetTestCertsDirectory(),
+ "duplicate_cn_2.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(1U, certs2.size());
+
+ // Import server cert with default trust.
+ EXPECT_TRUE(cert_db_->ImportServerCert(
+ certs2, NSSCertDatabase::TRUST_DEFAULT, &failed));
+ EXPECT_EQ(0U, failed.size());
+ EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
+ cert_db_->GetCertTrust(certs2[0].get(), SERVER_CERT));
+
+ new_certs = ListCertsInSlot(slot_->os_module_handle());
+ ASSERT_EQ(2U, new_certs.size());
+ EXPECT_STRNE(new_certs[0]->os_cert_handle()->nickname,
+ new_certs[1]->os_cert_handle()->nickname);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/pem_tokenizer.cc b/chromium/net/cert/pem_tokenizer.cc
new file mode 100644
index 00000000000..d9c152875c6
--- /dev/null
+++ b/chromium/net/cert/pem_tokenizer.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2010 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/cert/pem_tokenizer.h"
+
+#include "base/base64.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+const char kPEMSearchBlock[] = "-----BEGIN ";
+const char kPEMBeginBlock[] = "-----BEGIN %s-----";
+const char kPEMEndBlock[] = "-----END %s-----";
+
+} // namespace
+
+namespace net {
+
+using base::StringPiece;
+
+struct PEMTokenizer::PEMType {
+ std::string type;
+ std::string header;
+ std::string footer;
+};
+
+PEMTokenizer::PEMTokenizer(
+ const StringPiece& str,
+ const std::vector<std::string>& allowed_block_types) {
+ Init(str, allowed_block_types);
+}
+
+PEMTokenizer::~PEMTokenizer() {
+}
+
+bool PEMTokenizer::GetNext() {
+ while (pos_ != StringPiece::npos) {
+ // Scan for the beginning of the next PEM encoded block.
+ pos_ = str_.find(kPEMSearchBlock, pos_);
+ if (pos_ == StringPiece::npos)
+ return false; // No more PEM blocks
+
+ std::vector<PEMType>::const_iterator it;
+ // Check to see if it is of an acceptable block type.
+ for (it = block_types_.begin(); it != block_types_.end(); ++it) {
+ if (!str_.substr(pos_).starts_with(it->header))
+ continue;
+
+ // Look for a footer matching the header. If none is found, then all
+ // data following this point is invalid and should not be parsed.
+ StringPiece::size_type footer_pos = str_.find(it->footer, pos_);
+ if (footer_pos == StringPiece::npos) {
+ pos_ = StringPiece::npos;
+ return false;
+ }
+
+ // Chop off the header and footer and parse the data in between.
+ StringPiece::size_type data_begin = pos_ + it->header.size();
+ pos_ = footer_pos + it->footer.size();
+ block_type_ = it->type;
+
+ StringPiece encoded = str_.substr(data_begin,
+ footer_pos - data_begin);
+ if (!base::Base64Decode(CollapseWhitespaceASCII(encoded.as_string(),
+ true), &data_)) {
+ // The most likely cause for a decode failure is a datatype that
+ // includes PEM headers, which are not supported.
+ break;
+ }
+
+ return true;
+ }
+
+ // If the block did not match any acceptable type, move past it and
+ // continue the search. Otherwise, |pos_| has been updated to the most
+ // appropriate search position to continue searching from and should not
+ // be adjusted.
+ if (it == block_types_.end())
+ pos_ += sizeof(kPEMSearchBlock);
+ }
+
+ return false;
+}
+
+void PEMTokenizer::Init(
+ const StringPiece& str,
+ const std::vector<std::string>& allowed_block_types) {
+ str_ = str;
+ pos_ = 0;
+
+ // Construct PEM header/footer strings for all the accepted types, to
+ // reduce parsing later.
+ for (std::vector<std::string>::const_iterator it =
+ allowed_block_types.begin(); it != allowed_block_types.end(); ++it) {
+ PEMType allowed_type;
+ allowed_type.type = *it;
+ allowed_type.header = base::StringPrintf(kPEMBeginBlock, it->c_str());
+ allowed_type.footer = base::StringPrintf(kPEMEndBlock, it->c_str());
+ block_types_.push_back(allowed_type);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cert/pem_tokenizer.h b/chromium/net/cert/pem_tokenizer.h
new file mode 100644
index 00000000000..bb41c446db7
--- /dev/null
+++ b/chromium/net/cert/pem_tokenizer.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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_CERT_PEM_TOKENIZER_H_
+#define NET_CERT_PEM_TOKENIZER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// PEMTokenizer is a utility class for the parsing of data encapsulated
+// using RFC 1421, Privacy Enhancement for Internet Electronic Mail. It
+// does not implement the full specification, most notably it does not
+// support the Encapsulated Header Portion described in Section 4.4.
+class NET_EXPORT_PRIVATE PEMTokenizer {
+ public:
+ // Create a new PEMTokenizer that iterates through |str| searching for
+ // instances of PEM encoded blocks that are of the |allowed_block_types|.
+ // |str| must remain valid for the duration of the PEMTokenizer.
+ PEMTokenizer(const base::StringPiece& str,
+ const std::vector<std::string>& allowed_block_types);
+ ~PEMTokenizer();
+
+ // Attempts to decode the next PEM block in the string. Returns false if no
+ // PEM blocks can be decoded. The decoded PEM block will be available via
+ // data().
+ bool GetNext();
+
+ // Returns the PEM block type (eg: CERTIFICATE) of the last successfully
+ // decoded PEM block.
+ // GetNext() must have returned true before calling this method.
+ const std::string& block_type() const { return block_type_; }
+
+ // Returns the raw, Base64-decoded data of the last successfully decoded
+ // PEM block.
+ // GetNext() must have returned true before calling this method.
+ const std::string& data() const { return data_; }
+
+ private:
+ void Init(const base::StringPiece& str,
+ const std::vector<std::string>& allowed_block_types);
+
+ // A simple cache of the allowed PEM header and footer for a given PEM
+ // block type, so that it is only computed once.
+ struct PEMType;
+
+ // The string to search, which must remain valid for as long as this class
+ // is around.
+ base::StringPiece str_;
+
+ // The current position within |str_| that searching should begin from,
+ // or StringPiece::npos if iteration is complete
+ base::StringPiece::size_type pos_;
+
+ // The type of data that was encoded, as indicated in the PEM
+ // Pre-Encapsulation Boundary (eg: CERTIFICATE, PKCS7, or
+ // PRIVACY-ENHANCED MESSAGE).
+ std::string block_type_;
+
+ // The types of PEM blocks that are allowed. PEM blocks that are not of
+ // one of these types will be skipped.
+ std::vector<PEMType> block_types_;
+
+ // The raw (Base64-decoded) data of the last successfully decoded block.
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(PEMTokenizer);
+};
+
+} // namespace net
+
+#endif // NET_CERT_PEM_TOKENIZER_H_
diff --git a/chromium/net/cert/pem_tokenizer_unittest.cc b/chromium/net/cert/pem_tokenizer_unittest.cc
new file mode 100644
index 00000000000..d5334db8999
--- /dev/null
+++ b/chromium/net/cert/pem_tokenizer_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2010 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/cert/pem_tokenizer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(PEMTokenizerTest, BasicParsing) {
+ const char data[] =
+ "-----BEGIN EXPECTED-BLOCK-----\n"
+ "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\n"
+ "-----END EXPECTED-BLOCK-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+ EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, CarriageReturnLineFeeds) {
+ const char data[] =
+ "-----BEGIN EXPECTED-BLOCK-----\r\n"
+ "TWF0Y2hlc0FjY2VwdGVkQmxvY2tUeXBl\r\n"
+ "-----END EXPECTED-BLOCK-----\r\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("EXPECTED-BLOCK", tokenizer.block_type());
+ EXPECT_EQ("MatchesAcceptedBlockType", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NoAcceptedBlockTypes) {
+ const char data[] =
+ "-----BEGIN UNEXPECTED-BLOCK-----\n"
+ "SWdub3Jlc1JlamVjdGVkQmxvY2tUeXBl\n"
+ "-----END UNEXPECTED-BLOCK-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("EXPECTED-BLOCK");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MultipleAcceptedBlockTypes) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-TWO-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-TWO-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+ accepted_types.push_back("BLOCK-TWO");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-TWO", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, MissingFooter) {
+ const char data[] =
+ "-----BEGIN MISSING-FOOTER-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END MISSING-FOOTER-----\n"
+ "-----BEGIN MISSING-FOOTER-----\n"
+ "RW5jb2RlZERhdGFUd28=\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("MISSING-FOOTER");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("MISSING-FOOTER", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataOne", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, NestedEncoding) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----BEGIN BLOCK-TWO-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-TWO-----\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFUaHJlZQ==\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataThree", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, EmptyAcceptedTypes) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+TEST(PEMTokenizerTest, BlockWithHeader) {
+ const char data[] =
+ "-----BEGIN BLOCK-ONE-----\n"
+ "Header-One: Data data data\n"
+ "Header-Two: \n"
+ " continuation\n"
+ "Header-Three: Mix-And,Match\n"
+ "\n"
+ "RW5jb2RlZERhdGFPbmU=\n"
+ "-----END BLOCK-ONE-----\n"
+ "-----BEGIN BLOCK-ONE-----\n"
+ "RW5jb2RlZERhdGFUd28=\n"
+ "-----END BLOCK-ONE-----\n";
+ base::StringPiece string_piece(data);
+ std::vector<std::string> accepted_types;
+ accepted_types.push_back("BLOCK-ONE");
+
+ PEMTokenizer tokenizer(string_piece, accepted_types);
+ EXPECT_TRUE(tokenizer.GetNext());
+
+ EXPECT_EQ("BLOCK-ONE", tokenizer.block_type());
+ EXPECT_EQ("EncodedDataTwo", tokenizer.data());
+
+ EXPECT_FALSE(tokenizer.GetNext());
+}
+
+} // namespace net
diff --git a/chromium/net/cert/single_request_cert_verifier.cc b/chromium/net/cert/single_request_cert_verifier.cc
new file mode 100644
index 00000000000..909af07d848
--- /dev/null
+++ b/chromium/net/cert/single_request_cert_verifier.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 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.
+
+#include "net/cert/single_request_cert_verifier.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+SingleRequestCertVerifier::SingleRequestCertVerifier(
+ CertVerifier* cert_verifier)
+ : cert_verifier_(cert_verifier),
+ cur_request_(NULL) {
+ DCHECK(cert_verifier_ != NULL);
+}
+
+SingleRequestCertVerifier::~SingleRequestCertVerifier() {
+ if (cur_request_) {
+ cert_verifier_->CancelRequest(cur_request_);
+ cur_request_ = NULL;
+ }
+}
+
+int SingleRequestCertVerifier::Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ // Should not be already in use.
+ DCHECK(!cur_request_ && cur_request_callback_.is_null());
+
+ CertVerifier::RequestHandle request = NULL;
+
+ // We need to be notified of completion before |callback| is called, so that
+ // we can clear out |cur_request_*|.
+ int rv = cert_verifier_->Verify(
+ cert, hostname, flags, crl_set, verify_result,
+ base::Bind(&SingleRequestCertVerifier::OnVerifyCompletion,
+ base::Unretained(this)),
+ &request, net_log);
+
+ if (rv == ERR_IO_PENDING) {
+ // Cleared in OnVerifyCompletion().
+ cur_request_ = request;
+ cur_request_callback_ = callback;
+ }
+
+ return rv;
+}
+
+void SingleRequestCertVerifier::OnVerifyCompletion(int result) {
+ DCHECK(cur_request_ && !cur_request_callback_.is_null());
+
+ CompletionCallback callback = cur_request_callback_;
+
+ // Clear the outstanding request information.
+ cur_request_ = NULL;
+ cur_request_callback_.Reset();
+
+ // Call the user's original callback.
+ callback.Run(result);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/single_request_cert_verifier.h b/chromium/net/cert/single_request_cert_verifier.h
new file mode 100644
index 00000000000..6b192819c62
--- /dev/null
+++ b/chromium/net/cert/single_request_cert_verifier.h
@@ -0,0 +1,52 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_
+#define NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_
+
+#include "net/cert/cert_verifier.h"
+
+namespace net {
+
+// This class represents the task of verifying a certificate. It wraps
+// CertVerifier to verify only a single certificate at a time and cancels this
+// request when going out of scope.
+class SingleRequestCertVerifier {
+ public:
+ // |cert_verifier| must remain valid for the lifetime of |this|.
+ explicit SingleRequestCertVerifier(CertVerifier* cert_verifier);
+
+ // If a completion callback is pending when the verifier is destroyed, the
+ // certificate verification is canceled, and the completion callback will
+ // not be called.
+ ~SingleRequestCertVerifier();
+
+ // Verifies the given certificate, filling out the |verify_result| object
+ // upon success. See CertVerifier::Verify() for details.
+ int Verify(X509Certificate* cert,
+ const std::string& hostname,
+ int flags,
+ CRLSet* crl_set,
+ CertVerifyResult* verify_result,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log);
+
+ private:
+ // Callback for when the request to |cert_verifier_| completes, so we
+ // dispatch to the user's callback.
+ void OnVerifyCompletion(int result);
+
+ // The actual certificate verifier that will handle the request.
+ CertVerifier* const cert_verifier_;
+
+ // The current request (if any).
+ CertVerifier::RequestHandle cur_request_;
+ CompletionCallback cur_request_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SingleRequestCertVerifier);
+};
+
+} // namespace net
+
+#endif // NET_CERT_SINGLE_REQUEST_CERT_VERIFIER_H_
diff --git a/chromium/net/cert/test_root_certs.cc b/chromium/net/cert/test_root_certs.cc
new file mode 100644
index 00000000000..3219f1d84c1
--- /dev/null
+++ b/chromium/net/cert/test_root_certs.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 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.
+
+#include "net/cert/test_root_certs.h"
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+bool g_has_instance = false;
+
+base::LazyInstance<TestRootCerts>::Leaky
+ g_test_root_certs = LAZY_INSTANCE_INITIALIZER;
+
+CertificateList LoadCertificates(const base::FilePath& filename) {
+ std::string raw_cert;
+ if (!file_util::ReadFileToString(filename, &raw_cert)) {
+ LOG(ERROR) << "Can't load certificate " << filename.value();
+ return CertificateList();
+ }
+
+ return X509Certificate::CreateCertificateListFromBytes(
+ raw_cert.data(), raw_cert.length(), X509Certificate::FORMAT_AUTO);
+}
+
+} // namespace
+
+// static
+TestRootCerts* TestRootCerts::GetInstance() {
+ return g_test_root_certs.Pointer();
+}
+
+bool TestRootCerts::HasInstance() {
+ return g_has_instance;
+}
+
+bool TestRootCerts::AddFromFile(const base::FilePath& file) {
+ CertificateList root_certs = LoadCertificates(file);
+ if (root_certs.empty() || root_certs.size() > 1)
+ return false;
+
+ return Add(root_certs.front().get());
+}
+
+TestRootCerts::TestRootCerts() {
+ Init();
+ g_has_instance = true;
+}
+
+ScopedTestRoot::ScopedTestRoot() {}
+
+ScopedTestRoot::ScopedTestRoot(X509Certificate* cert) {
+ Reset(cert);
+}
+
+ScopedTestRoot::~ScopedTestRoot() {
+ Reset(NULL);
+}
+
+void ScopedTestRoot::Reset(X509Certificate* cert) {
+ if (cert_.get())
+ TestRootCerts::GetInstance()->Clear();
+ if (cert)
+ TestRootCerts::GetInstance()->Add(cert);
+ cert_ = cert;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs.h b/chromium/net/cert/test_root_certs.h
new file mode 100644
index 00000000000..22c635f9dbb
--- /dev/null
+++ b/chromium/net/cert/test_root_certs.h
@@ -0,0 +1,134 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_TEST_ROOT_CERTS_H_
+#define NET_CERT_TEST_ROOT_CERTS_H_
+
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <list>
+#elif defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#elif defined(OS_MACOSX)
+#include <CoreFoundation/CFArray.h>
+#include <Security/SecTrust.h>
+#include "base/mac/scoped_cftyperef.h"
+#endif
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+
+class X509Certificate;
+
+// TestRootCerts is a helper class for unit tests that is used to
+// artificially mark a certificate as trusted, independent of the local
+// machine configuration.
+class NET_EXPORT_PRIVATE TestRootCerts {
+ public:
+ // Obtains the Singleton instance to the trusted certificates.
+ static TestRootCerts* GetInstance();
+
+ // Returns true if an instance exists, without forcing an initialization.
+ static bool HasInstance();
+
+ // Marks |certificate| as trusted for X509Certificate::Verify(). Returns
+ // false if the certificate could not be marked trusted.
+ bool Add(X509Certificate* certificate);
+
+ // Reads a single certificate from |file| and marks it as trusted. Returns
+ // false if an error is encountered, such as being unable to read |file|
+ // or more than one certificate existing in |file|.
+ bool AddFromFile(const base::FilePath& file);
+
+ // Clears the trusted status of any certificates that were previously
+ // marked trusted via Add().
+ void Clear();
+
+ // Returns true if there are no certificates that have been marked trusted.
+ bool IsEmpty() const;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ CFArrayRef temporary_roots() const { return temporary_roots_; }
+
+ // Modifies the root certificates of |trust_ref| to include the
+ // certificates stored in |temporary_roots_|. If IsEmpty() is true, this
+ // does not modify |trust_ref|.
+ OSStatus FixupSecTrustRef(SecTrustRef trust_ref) const;
+
+ // Configures whether or not the default/system root store should also
+ // be trusted. By default, this is true, indicating that the TestRootCerts
+ // are used in addition to OS trust store.
+ void SetAllowSystemTrust(bool allow_system_trust);
+
+#elif defined(OS_WIN)
+ HCERTSTORE temporary_roots() const { return temporary_roots_; }
+
+ // Returns an HCERTCHAINENGINE suitable to be used for certificate
+ // validation routines, or NULL to indicate that the default system chain
+ // engine is appropriate. The caller is responsible for freeing the
+ // returned HCERTCHAINENGINE.
+ HCERTCHAINENGINE GetChainEngine() const;
+#endif
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<TestRootCerts>;
+
+ TestRootCerts();
+ ~TestRootCerts();
+
+ // Performs platform-dependent initialization.
+ void Init();
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ // It is necessary to maintain a cache of the original certificate trust
+ // settings, in order to restore them when Clear() is called.
+ class TrustEntry;
+ std::list<TrustEntry*> trust_cache_;
+#elif defined(OS_WIN)
+ HCERTSTORE temporary_roots_;
+#elif defined(OS_MACOSX)
+ base::ScopedCFTypeRef<CFMutableArrayRef> temporary_roots_;
+ bool allow_system_trust_;
+#endif
+
+#if defined(OS_WIN) || defined(USE_OPENSSL)
+ // True if there are no temporarily trusted root certificates.
+ bool empty_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(TestRootCerts);
+};
+
+// Scoped helper for unittests to handle safely managing trusted roots.
+class NET_EXPORT_PRIVATE ScopedTestRoot {
+ public:
+ ScopedTestRoot();
+ // Creates a ScopedTestRoot that will adds|cert| to the TestRootCerts store.
+ explicit ScopedTestRoot(X509Certificate* cert);
+ ~ScopedTestRoot();
+
+ // Assigns |cert| to be the new test root cert. If |cert| is NULL, undoes
+ // any work the ScopedTestRoot may have previously done.
+ // If |cert_| contains a certificate (due to a prior call to Reset or due to
+ // a cert being passed at construction), the existing TestRootCerts store is
+ // cleared.
+ void Reset(X509Certificate* cert);
+
+ private:
+ scoped_refptr<X509Certificate> cert_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTestRoot);
+};
+
+} // namespace net
+
+#endif // NET_CERT_TEST_ROOT_CERTS_H_
diff --git a/chromium/net/cert/test_root_certs_android.cc b/chromium/net/cert/test_root_certs_android.cc
new file mode 100644
index 00000000000..73588270e95
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_android.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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/cert/test_root_certs.h"
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "net/android/network_library.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+bool TestRootCerts::Add(X509Certificate* certificate) {
+ std::string cert_bytes;
+ if (!X509Certificate::GetDEREncoded(certificate->os_cert_handle(),
+ &cert_bytes))
+ return false;
+ android::AddTestRootCertificate(
+ reinterpret_cast<const uint8*>(cert_bytes.data()), cert_bytes.size());
+ empty_ = false;
+ return true;
+}
+
+void TestRootCerts::Clear() {
+ if (empty_)
+ return;
+
+ android::ClearTestRootCertificates();
+ empty_ = true;
+}
+
+bool TestRootCerts::IsEmpty() const {
+ return empty_;
+}
+
+TestRootCerts::~TestRootCerts() {}
+
+void TestRootCerts::Init() {
+ empty_ = true;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs_mac.cc b/chromium/net/cert/test_root_certs_mac.cc
new file mode 100644
index 00000000000..87824d4c9ed
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_mac.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 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.
+
+#include "net/cert/test_root_certs.h"
+
+#include <Security/Security.h>
+
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+typedef OSStatus (*SecTrustSetAnchorCertificatesOnlyFuncPtr)(SecTrustRef,
+ Boolean);
+
+Boolean OurSecCertificateEqual(const void* value1, const void* value2) {
+ if (CFGetTypeID(value1) != SecCertificateGetTypeID() ||
+ CFGetTypeID(value2) != SecCertificateGetTypeID())
+ return CFEqual(value1, value2);
+ return X509Certificate::IsSameOSCert(
+ reinterpret_cast<SecCertificateRef>(const_cast<void*>(value1)),
+ reinterpret_cast<SecCertificateRef>(const_cast<void*>(value2)));
+}
+
+const void* RetainWrapper(CFAllocatorRef unused, const void* value) {
+ return CFRetain(value);
+}
+
+void ReleaseWrapper(CFAllocatorRef unused, const void* value) {
+ CFRelease(value);
+}
+
+// CFEqual prior to 10.6 only performed pointer checks on SecCertificateRefs,
+// rather than checking if they were the same (logical) certificate, so a
+// custom structure is used for the array callbacks.
+const CFArrayCallBacks kCertArrayCallbacks = {
+ 0, // version
+ RetainWrapper,
+ ReleaseWrapper,
+ CFCopyDescription,
+ OurSecCertificateEqual,
+};
+
+} // namespace
+
+bool TestRootCerts::Add(X509Certificate* certificate) {
+ if (CFArrayContainsValue(temporary_roots_,
+ CFRangeMake(0, CFArrayGetCount(temporary_roots_)),
+ certificate->os_cert_handle()))
+ return true;
+ CFArrayAppendValue(temporary_roots_, certificate->os_cert_handle());
+ return true;
+}
+
+void TestRootCerts::Clear() {
+ CFArrayRemoveAllValues(temporary_roots_);
+}
+
+bool TestRootCerts::IsEmpty() const {
+ return CFArrayGetCount(temporary_roots_) == 0;
+}
+
+OSStatus TestRootCerts::FixupSecTrustRef(SecTrustRef trust_ref) const {
+ if (IsEmpty())
+ return noErr;
+
+ // Despite SecTrustSetAnchorCertificatesOnly existing in OS X 10.6, and
+ // being documented as available, it is not actually implemented. On 10.7+,
+ // however, it always works.
+ if (base::mac::IsOSLionOrLater()) {
+ OSStatus status = SecTrustSetAnchorCertificates(trust_ref,
+ temporary_roots_);
+ if (status)
+ return status;
+ return SecTrustSetAnchorCertificatesOnly(trust_ref, !allow_system_trust_);
+ }
+
+ if (!allow_system_trust_) {
+ // Avoid any copying if system roots are not to be trusted. This acts as
+ // an exclusive list on 10.6, replacing the built-ins.
+ return SecTrustSetAnchorCertificates(trust_ref, temporary_roots_);
+ }
+
+ // Otherwise, both system trust and temporary_roots_ must be trusted.
+ // Emulate the functionality of SecTrustSetAnchorCertificatesOnly by
+ // creating a copy of the system roots and merging with temporary_roots_.
+ CFArrayRef system_roots = NULL;
+ OSStatus status = SecTrustCopyAnchorCertificates(&system_roots);
+ if (status)
+ return status;
+
+ base::ScopedCFTypeRef<CFArrayRef> scoped_system_roots(system_roots);
+ base::ScopedCFTypeRef<CFMutableArrayRef> scoped_roots(
+ CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, scoped_system_roots));
+ CFArrayAppendArray(scoped_roots, temporary_roots_,
+ CFRangeMake(0, CFArrayGetCount(temporary_roots_)));
+ return SecTrustSetAnchorCertificates(trust_ref, scoped_roots);
+}
+
+void TestRootCerts::SetAllowSystemTrust(bool allow_system_trust) {
+ allow_system_trust_ = allow_system_trust;
+}
+
+TestRootCerts::~TestRootCerts() {}
+
+void TestRootCerts::Init() {
+ temporary_roots_.reset(CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCertArrayCallbacks));
+ allow_system_trust_ = true;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs_nss.cc b/chromium/net/cert/test_root_certs_nss.cc
new file mode 100644
index 00000000000..3a2f88a7968
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_nss.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2011 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/cert/test_root_certs.h"
+
+#include <cert.h>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "crypto/nss_util.h"
+#include "net/cert/x509_certificate.h"
+
+#if defined(OS_IOS)
+#include "net/cert/x509_util_ios.h"
+#endif
+
+namespace net {
+
+// TrustEntry is used to store the original CERTCertificate and CERTCertTrust
+// for a certificate whose trust status has been changed by the
+// TestRootCerts.
+class TestRootCerts::TrustEntry {
+ public:
+ // Creates a new TrustEntry by incrementing the reference to |certificate|
+ // and copying |trust|.
+ TrustEntry(CERTCertificate* certificate, const CERTCertTrust& trust);
+ ~TrustEntry();
+
+ CERTCertificate* certificate() const { return certificate_; }
+ const CERTCertTrust& trust() const { return trust_; }
+
+ private:
+ // The temporary root certificate.
+ CERTCertificate* certificate_;
+
+ // The original trust settings, before |certificate_| was manipulated to
+ // be a temporarily trusted root.
+ CERTCertTrust trust_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrustEntry);
+};
+
+TestRootCerts::TrustEntry::TrustEntry(CERTCertificate* certificate,
+ const CERTCertTrust& trust)
+ : certificate_(CERT_DupCertificate(certificate)),
+ trust_(trust) {
+}
+
+TestRootCerts::TrustEntry::~TrustEntry() {
+ CERT_DestroyCertificate(certificate_);
+}
+
+bool TestRootCerts::Add(X509Certificate* certificate) {
+#if defined(OS_IOS)
+ x509_util_ios::NSSCertificate nss_certificate(certificate->os_cert_handle());
+ CERTCertificate* cert_handle = nss_certificate.cert_handle();
+#else
+ CERTCertificate* cert_handle = certificate->os_cert_handle();
+#endif
+ // Preserve the original trust bits so that they can be restored when
+ // the certificate is removed.
+ CERTCertTrust original_trust;
+ SECStatus rv = CERT_GetCertTrust(cert_handle, &original_trust);
+ if (rv != SECSuccess) {
+ // CERT_GetCertTrust will fail if the certificate does not have any
+ // particular trust settings associated with it, and attempts to use
+ // |original_trust| later to restore the original trust settings will not
+ // cause the trust settings to be revoked. If the certificate has no
+ // particular trust settings associated with it, mark the certificate as
+ // a valid CA certificate with no specific trust.
+ rv = CERT_DecodeTrustString(&original_trust, "c,c,c");
+ }
+
+ // Change the trust bits to unconditionally trust this certificate.
+ CERTCertTrust new_trust;
+ rv = CERT_DecodeTrustString(&new_trust, "TCu,Cu,Tu");
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "Cannot decode certificate trust string.";
+ return false;
+ }
+
+ rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert_handle, &new_trust);
+ if (rv != SECSuccess) {
+ LOG(ERROR) << "Cannot change certificate trust.";
+ return false;
+ }
+
+ trust_cache_.push_back(new TrustEntry(cert_handle, original_trust));
+ return true;
+}
+
+void TestRootCerts::Clear() {
+ // Restore the certificate trusts to what they were originally, before
+ // Add() was called. Work from the rear first, since if a certificate was
+ // added twice, the second entry's original trust status will be that of
+ // the first entry, while the first entry contains the desired resultant
+ // status.
+ for (std::list<TrustEntry*>::reverse_iterator it = trust_cache_.rbegin();
+ it != trust_cache_.rend(); ++it) {
+ CERTCertTrust original_trust = (*it)->trust();
+ SECStatus rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(),
+ (*it)->certificate(),
+ &original_trust);
+ // DCHECK(), rather than LOG(), as a failure to restore the original
+ // trust can cause flake or hard-to-trace errors in any unit tests that
+ // occur after Clear() has been called.
+ DCHECK_EQ(SECSuccess, rv) << "Cannot restore certificate trust.";
+ }
+ STLDeleteElements(&trust_cache_);
+}
+
+bool TestRootCerts::IsEmpty() const {
+ return trust_cache_.empty();
+}
+
+TestRootCerts::~TestRootCerts() {
+ Clear();
+}
+
+void TestRootCerts::Init() {
+ crypto::EnsureNSSInit();
+}
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs_openssl.cc b/chromium/net/cert/test_root_certs_openssl.cc
new file mode 100644
index 00000000000..3d5cf3d72d1
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_openssl.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 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/cert/test_root_certs.h"
+
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "crypto/openssl_util.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+bool TestRootCerts::Add(X509Certificate* certificate) {
+ if (!X509_STORE_add_cert(X509Certificate::cert_store(),
+ certificate->os_cert_handle())) {
+ unsigned long error_code = ERR_peek_error();
+ if (ERR_GET_LIB(error_code) != ERR_LIB_X509 ||
+ ERR_GET_REASON(error_code) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
+ crypto::ClearOpenSSLERRStack(FROM_HERE);
+ return false;
+ }
+ ERR_clear_error();
+ }
+
+ empty_ = false;
+ return true;
+}
+
+void TestRootCerts::Clear() {
+ if (empty_)
+ return;
+
+ X509Certificate::ResetCertStore();
+ empty_ = true;
+}
+
+bool TestRootCerts::IsEmpty() const {
+ return empty_;
+}
+
+TestRootCerts::~TestRootCerts() {}
+
+void TestRootCerts::Init() {
+ empty_ = true;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs_unittest.cc b/chromium/net/cert/test_root_certs_unittest.cc
new file mode 100644
index 00000000000..74c3551862b
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright 2013 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 "base/files/file_path.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include <nss.h>
+#endif
+
+namespace net {
+
+namespace {
+
+// The local test root certificate.
+const char kRootCertificateFile[] = "root_ca_cert.pem";
+// A certificate issued by the local test root for 127.0.0.1.
+const char kGoodCertificateFile[] = "ok_cert.pem";
+
+} // namespace
+
+// Test basic functionality when adding from an existing X509Certificate.
+TEST(TestRootCertsTest, AddFromPointer) {
+ scoped_refptr<X509Certificate> root_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), kRootCertificateFile);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert.get());
+
+ TestRootCerts* test_roots = TestRootCerts::GetInstance();
+ ASSERT_NE(static_cast<TestRootCerts*>(NULL), test_roots);
+ EXPECT_TRUE(test_roots->IsEmpty());
+
+ EXPECT_TRUE(test_roots->Add(root_cert.get()));
+ EXPECT_FALSE(test_roots->IsEmpty());
+
+ test_roots->Clear();
+ EXPECT_TRUE(test_roots->IsEmpty());
+}
+
+// Test basic functionality when adding directly from a file, which should
+// behave the same as when adding from an existing certificate.
+TEST(TestRootCertsTest, AddFromFile) {
+ TestRootCerts* test_roots = TestRootCerts::GetInstance();
+ ASSERT_NE(static_cast<TestRootCerts*>(NULL), test_roots);
+ EXPECT_TRUE(test_roots->IsEmpty());
+
+ base::FilePath cert_path =
+ GetTestCertsDirectory().AppendASCII(kRootCertificateFile);
+ EXPECT_TRUE(test_roots->AddFromFile(cert_path));
+ EXPECT_FALSE(test_roots->IsEmpty());
+
+ test_roots->Clear();
+ EXPECT_TRUE(test_roots->IsEmpty());
+}
+
+// Test that TestRootCerts actually adds the appropriate trust status flags
+// when requested, and that the trusted status is cleared once the root is
+// removed the TestRootCerts. This test acts as a canary/sanity check for
+// the results of the rest of net_unittests, ensuring that the trust status
+// is properly being set and cleared.
+TEST(TestRootCertsTest, OverrideTrust) {
+#if defined(USE_NSS) || defined(OS_IOS)
+ if (NSS_VersionCheck("3.14.2") && !NSS_VersionCheck("3.15")) {
+ // See http://bugzil.la/863947 for details
+ LOG(INFO) << "Skipping test for NSS 3.14.2 - NSS 3.15";
+ return;
+ }
+#endif
+
+ TestRootCerts* test_roots = TestRootCerts::GetInstance();
+ ASSERT_NE(static_cast<TestRootCerts*>(NULL), test_roots);
+ EXPECT_TRUE(test_roots->IsEmpty());
+
+ scoped_refptr<X509Certificate> test_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), kGoodCertificateFile);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get());
+
+ // Test that the good certificate fails verification, because the root
+ // certificate should not yet be trusted.
+ int flags = 0;
+ CertVerifyResult bad_verify_result;
+ scoped_refptr<CertVerifyProc> verify_proc(CertVerifyProc::CreateDefault());
+ int bad_status = verify_proc->Verify(test_cert.get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ CertificateList(),
+ &bad_verify_result);
+ EXPECT_NE(OK, bad_status);
+ EXPECT_NE(0u, bad_verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
+
+ // Add the root certificate and mark it as trusted.
+ EXPECT_TRUE(test_roots->AddFromFile(
+ GetTestCertsDirectory().AppendASCII(kRootCertificateFile)));
+ EXPECT_FALSE(test_roots->IsEmpty());
+
+ // Test that the certificate verification now succeeds, because the
+ // TestRootCerts is successfully imbuing trust.
+ CertVerifyResult good_verify_result;
+ int good_status = verify_proc->Verify(test_cert.get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ CertificateList(),
+ &good_verify_result);
+ EXPECT_EQ(OK, good_status);
+ EXPECT_EQ(0u, good_verify_result.cert_status);
+
+ test_roots->Clear();
+ EXPECT_TRUE(test_roots->IsEmpty());
+
+ // Ensure that when the TestRootCerts is cleared, the trust settings
+ // revert to their original state, and don't linger. If trust status
+ // lingers, it will likely break other tests in net_unittests.
+ CertVerifyResult restored_verify_result;
+ int restored_status = verify_proc->Verify(test_cert.get(),
+ "127.0.0.1",
+ flags,
+ NULL,
+ CertificateList(),
+ &restored_verify_result);
+ EXPECT_NE(OK, restored_status);
+ EXPECT_NE(0u,
+ restored_verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID);
+ EXPECT_EQ(bad_status, restored_status);
+ EXPECT_EQ(bad_verify_result.cert_status, restored_verify_result.cert_status);
+}
+
+// TODO(rsleevi): Add tests for revocation checking via CRLs, ensuring that
+// TestRootCerts properly injects itself into the validation process. See
+// http://crbug.com/63958
+
+} // namespace net
diff --git a/chromium/net/cert/test_root_certs_win.cc b/chromium/net/cert/test_root_certs_win.cc
new file mode 100644
index 00000000000..90a21d65889
--- /dev/null
+++ b/chromium/net/cert/test_root_certs_win.cc
@@ -0,0 +1,213 @@
+// Copyright (c) 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.
+
+#include "net/cert/test_root_certs.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+
+#include "base/basictypes.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+// Provides a CertDllOpenStoreProv callback provider function, to be called
+// by CertOpenStore when the CERT_STORE_PROV_SYSTEM_W store is opened. See
+// http://msdn.microsoft.com/en-us/library/aa376043(VS.85).aspx.
+BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider,
+ DWORD encoding,
+ HCRYPTPROV crypt_provider,
+ DWORD flags,
+ const void* extra,
+ HCERTSTORE memory_store,
+ PCERT_STORE_PROV_INFO store_info);
+
+// CryptoAPIInjector is used to inject a store provider function for system
+// certificate stores before the one provided internally by Crypt32.dll.
+// Once injected, there is no way to remove, so every call to open a system
+// store will be redirected to the injected function.
+struct CryptoAPIInjector {
+ // The previous default function for opening system stores. For most
+ // configurations, this should point to Crypt32's internal
+ // I_CertDllOpenSystemStoreProvW function.
+ PFN_CERT_DLL_OPEN_STORE_PROV_FUNC original_function;
+
+ // The handle that CryptoAPI uses to ensure the DLL implementing
+ // |original_function| remains loaded in memory.
+ HCRYPTOIDFUNCADDR original_handle;
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<CryptoAPIInjector>;
+
+ CryptoAPIInjector()
+ : original_function(NULL),
+ original_handle(NULL) {
+ HCRYPTOIDFUNCSET registered_functions =
+ CryptInitOIDFunctionSet(CRYPT_OID_OPEN_STORE_PROV_FUNC, 0);
+
+ // Preserve the original handler function in |original_function|. If other
+ // functions are overridden, they will also need to be preserved.
+ BOOL ok = CryptGetOIDFunctionAddress(
+ registered_functions, 0, CERT_STORE_PROV_SYSTEM_W, 0,
+ reinterpret_cast<void**>(&original_function), &original_handle);
+ DCHECK(ok);
+
+ // For now, intercept only the numeric form of the system store
+ // function, CERT_STORE_PROV_SYSTEM_W (0x0A), which is what Crypt32
+ // functionality uses exclusively. Depending on the machine that tests
+ // are being run on, it may prove necessary to also intercept
+ // sz_CERT_STORE_PROV_SYSTEM_[A/W] and CERT_STORE_PROV_SYSTEM_A, based
+ // on whether or not any third-party CryptoAPI modules have been
+ // installed.
+ const CRYPT_OID_FUNC_ENTRY kFunctionToIntercept =
+ { CERT_STORE_PROV_SYSTEM_W, &InterceptedOpenStoreW };
+
+ // Inject kFunctionToIntercept at the front of the linked list that
+ // crypt32 uses when CertOpenStore is called, replacing the existing
+ // registered function.
+ ok = CryptInstallOIDFunctionAddress(NULL, 0,
+ CRYPT_OID_OPEN_STORE_PROV_FUNC, 1,
+ &kFunctionToIntercept,
+ CRYPT_INSTALL_OID_FUNC_BEFORE_FLAG);
+ DCHECK(ok);
+ }
+
+ // This is never called, because this object is intentionally leaked.
+ // Certificate verification happens on a non-joinable worker thread, which
+ // may still be running when ~AtExitManager is called, so the LazyInstance
+ // must be leaky.
+ ~CryptoAPIInjector() {
+ original_function = NULL;
+ CryptFreeOIDFunctionAddress(original_handle, NULL);
+ }
+};
+
+base::LazyInstance<CryptoAPIInjector>::Leaky
+ g_capi_injector = LAZY_INSTANCE_INITIALIZER;
+
+BOOL WINAPI InterceptedOpenStoreW(LPCSTR store_provider,
+ DWORD encoding,
+ HCRYPTPROV crypt_provider,
+ DWORD flags,
+ const void* store_name,
+ HCERTSTORE memory_store,
+ PCERT_STORE_PROV_INFO store_info) {
+ // If the high word is all zeroes, then |store_provider| is a numeric ID.
+ // Otherwise, it's a pointer to a null-terminated ASCII string. See the
+ // documentation for CryptGetOIDFunctionAddress for more information.
+ uint32 store_as_uint = reinterpret_cast<uint32>(store_provider);
+ if (store_as_uint > 0xFFFF || store_provider != CERT_STORE_PROV_SYSTEM_W ||
+ !g_capi_injector.Get().original_function)
+ return FALSE;
+
+ BOOL ok = g_capi_injector.Get().original_function(store_provider, encoding,
+ crypt_provider, flags,
+ store_name, memory_store,
+ store_info);
+ // Only the Root store should have certificates injected. If
+ // CERT_SYSTEM_STORE_RELOCATE_FLAG is set, then |store_name| points to a
+ // CERT_SYSTEM_STORE_RELOCATE_PARA structure, rather than a
+ // NULL-terminated wide string, so check before making a string
+ // comparison.
+ if (!ok || TestRootCerts::GetInstance()->IsEmpty() ||
+ (flags & CERT_SYSTEM_STORE_RELOCATE_FLAG) ||
+ lstrcmpiW(reinterpret_cast<LPCWSTR>(store_name), L"root"))
+ return ok;
+
+ // The result of CertOpenStore with CERT_STORE_PROV_SYSTEM_W is documented
+ // to be a collection store, and that appears to hold for |memory_store|.
+ // Attempting to add an individual certificate to |memory_store| causes
+ // the request to be forwarded to the first physical store in the
+ // collection that accepts modifications, which will cause a secure
+ // confirmation dialog to be displayed, confirming the user wishes to
+ // trust the certificate. However, appending a store to the collection
+ // will merely modify the temporary collection store, and will not persist
+ // any changes to the underlying physical store. When the |memory_store| is
+ // searched to see if a certificate is in the Root store, all the
+ // underlying stores in the collection will be searched, and any certificate
+ // in temporary_roots() will be found and seen as trusted.
+ return CertAddStoreToCollection(
+ memory_store, TestRootCerts::GetInstance()->temporary_roots(), 0, 0);
+}
+
+} // namespace
+
+bool TestRootCerts::Add(X509Certificate* certificate) {
+ // Ensure that the default CryptoAPI functionality has been intercepted.
+ // If a test certificate is never added, then no interception should
+ // happen.
+ g_capi_injector.Get();
+
+ BOOL ok = CertAddCertificateContextToStore(
+ temporary_roots_, certificate->os_cert_handle(),
+ CERT_STORE_ADD_NEW, NULL);
+ if (!ok) {
+ // If the certificate is already added, return successfully.
+ return GetLastError() == CRYPT_E_EXISTS;
+ }
+
+ empty_ = false;
+ return true;
+}
+
+void TestRootCerts::Clear() {
+ empty_ = true;
+
+ PCCERT_CONTEXT prev_cert = NULL;
+ while (prev_cert = CertEnumCertificatesInStore(temporary_roots_, NULL))
+ CertDeleteCertificateFromStore(prev_cert);
+}
+
+bool TestRootCerts::IsEmpty() const {
+ return empty_;
+}
+
+HCERTCHAINENGINE TestRootCerts::GetChainEngine() const {
+ if (IsEmpty())
+ return NULL; // Default chain engine will suffice.
+
+ // Windows versions before 7 don't accept the struct size for later versions.
+ // We report the size of the old struct since we don't need the new members.
+ static const DWORD kSizeofCertChainEngineConfig =
+ SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(
+ CERT_CHAIN_ENGINE_CONFIG, CycleDetectionModulus);
+
+ // Each HCERTCHAINENGINE caches both the configured system stores and
+ // information about each chain that has been built. In order to ensure
+ // that changes to |temporary_roots_| are properly propagated and that the
+ // various caches are flushed, when at least one certificate is added,
+ // return a new chain engine for every call. Each chain engine creation
+ // should re-open the root store, ensuring the most recent changes are
+ // visible.
+ CERT_CHAIN_ENGINE_CONFIG engine_config = {
+ kSizeofCertChainEngineConfig
+ };
+ engine_config.dwFlags =
+ CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE |
+ CERT_CHAIN_ENABLE_SHARE_STORE;
+ HCERTCHAINENGINE chain_engine = NULL;
+ BOOL ok = CertCreateCertificateChainEngine(&engine_config, &chain_engine);
+ DCHECK(ok);
+ return chain_engine;
+}
+
+TestRootCerts::~TestRootCerts() {
+ CertCloseStore(temporary_roots_, 0);
+}
+
+void TestRootCerts::Init() {
+ empty_ = true;
+ temporary_roots_ = CertOpenStore(
+ CERT_STORE_PROV_MEMORY, 0, NULL,
+ CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL);
+ DCHECK(temporary_roots_);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_cert_types.cc b/chromium/net/cert/x509_cert_types.cc
new file mode 100644
index 00000000000..cfa09923f60
--- /dev/null
+++ b/chromium/net/cert/x509_cert_types.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_cert_types.h"
+
+#include <cstdlib>
+#include <cstring>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+// Helper for ParseCertificateDate. |*field| must contain at least
+// |field_len| characters. |*field| will be advanced by |field_len| on exit.
+// |*ok| is set to false if there is an error in parsing the number, but left
+// untouched otherwise. Returns the parsed integer.
+int ParseIntAndAdvance(const char** field, size_t field_len, bool* ok) {
+ int result = 0;
+ *ok &= base::StringToInt(base::StringPiece(*field, field_len), &result);
+ *field += field_len;
+ return result;
+}
+
+}
+
+CertPrincipal::CertPrincipal() {
+}
+
+CertPrincipal::CertPrincipal(const std::string& name) : common_name(name) {}
+
+CertPrincipal::~CertPrincipal() {
+}
+
+std::string CertPrincipal::GetDisplayName() const {
+ if (!common_name.empty())
+ return common_name;
+ if (!organization_names.empty())
+ return organization_names[0];
+ if (!organization_unit_names.empty())
+ return organization_unit_names[0];
+
+ return std::string();
+}
+
+CertPolicy::CertPolicy() {
+}
+
+CertPolicy::~CertPolicy() {
+}
+
+// For a denial, we consider a given |cert| to be a match to a saved denied
+// cert if the |error| intersects with the saved error status. For an
+// allowance, we consider a given |cert| to be a match to a saved allowed
+// cert if the |error| is an exact match to or subset of the errors in the
+// saved CertStatus.
+CertPolicy::Judgment CertPolicy::Check(
+ X509Certificate* cert, CertStatus error) const {
+ // It shouldn't matter which set we check first, but we check denied first
+ // in case something strange has happened.
+ bool denied = false;
+ std::map<SHA1HashValue, CertStatus, SHA1HashValueLessThan>::const_iterator
+ denied_iter = denied_.find(cert->fingerprint());
+ if ((denied_iter != denied_.end()) && (denied_iter->second & error))
+ denied = true;
+
+ std::map<SHA1HashValue, CertStatus, SHA1HashValueLessThan>::const_iterator
+ allowed_iter = allowed_.find(cert->fingerprint());
+ if ((allowed_iter != allowed_.end()) &&
+ (allowed_iter->second & error) &&
+ !(~(allowed_iter->second & error) ^ ~error)) {
+ DCHECK(!denied);
+ return ALLOWED;
+ }
+
+ if (denied)
+ return DENIED;
+ return UNKNOWN; // We don't have a policy for this cert.
+}
+
+void CertPolicy::Allow(X509Certificate* cert, CertStatus error) {
+ // Put the cert in the allowed set and (maybe) remove it from the denied set.
+ denied_.erase(cert->fingerprint());
+ // If this same cert had already been saved with a different error status,
+ // this will replace it with the new error status.
+ allowed_[cert->fingerprint()] = error;
+}
+
+void CertPolicy::Deny(X509Certificate* cert, CertStatus error) {
+ // Put the cert in the denied set and (maybe) remove it from the allowed set.
+ std::map<SHA1HashValue, CertStatus, SHA1HashValueLessThan>::const_iterator
+ allowed_iter = allowed_.find(cert->fingerprint());
+ if ((allowed_iter != allowed_.end()) && (allowed_iter->second & error))
+ allowed_.erase(cert->fingerprint());
+ denied_[cert->fingerprint()] |= error;
+}
+
+bool CertPolicy::HasAllowedCert() const {
+ return !allowed_.empty();
+}
+
+bool CertPolicy::HasDeniedCert() const {
+ return !denied_.empty();
+}
+
+bool ParseCertificateDate(const base::StringPiece& raw_date,
+ CertDateFormat format,
+ base::Time* time) {
+ size_t year_length = format == CERT_DATE_FORMAT_UTC_TIME ? 2 : 4;
+
+ if (raw_date.length() < 11 + year_length)
+ return false;
+
+ const char* field = raw_date.data();
+ bool valid = true;
+ base::Time::Exploded exploded = {0};
+
+ exploded.year = ParseIntAndAdvance(&field, year_length, &valid);
+ exploded.month = ParseIntAndAdvance(&field, 2, &valid);
+ exploded.day_of_month = ParseIntAndAdvance(&field, 2, &valid);
+ exploded.hour = ParseIntAndAdvance(&field, 2, &valid);
+ exploded.minute = ParseIntAndAdvance(&field, 2, &valid);
+ exploded.second = ParseIntAndAdvance(&field, 2, &valid);
+ if (valid && year_length == 2)
+ exploded.year += exploded.year < 50 ? 2000 : 1900;
+
+ valid &= exploded.HasValidValues();
+
+ if (!valid)
+ return false;
+
+ *time = base::Time::FromUTCExploded(exploded);
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_cert_types.h b/chromium/net/cert/x509_cert_types.h
new file mode 100644
index 00000000000..f74c82eab7b
--- /dev/null
+++ b/chromium/net/cert/x509_cert_types.h
@@ -0,0 +1,144 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_X509_CERT_TYPES_H_
+#define NET_CERT_X509_CERT_TYPES_H_
+
+#include <string.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "net/base/hash_value.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_status_flags.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+#include <Security/x509defs.h>
+#endif
+
+namespace base {
+class Time;
+} // namespace base
+
+namespace net {
+
+class X509Certificate;
+
+// CertPrincipal represents the issuer or subject field of an X.509 certificate.
+struct NET_EXPORT CertPrincipal {
+ CertPrincipal();
+ explicit CertPrincipal(const std::string& name);
+ ~CertPrincipal();
+
+#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN)
+ // Parses a BER-format DistinguishedName.
+ bool ParseDistinguishedName(const void* ber_name_data, size_t length);
+#endif
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Compare this CertPrincipal with |against|, returning true if they're
+ // equal enough to be a possible match. This should NOT be used for any
+ // security relevant decisions.
+ // TODO(rsleevi): Remove once Mac client auth uses NSS for name comparison.
+ bool Matches(const CertPrincipal& against) const;
+#endif
+
+ // Returns a name that can be used to represent the issuer. It tries in this
+ // order: CN, O and OU and returns the first non-empty one found.
+ std::string GetDisplayName() const;
+
+ // The different attributes for a principal, stored in UTF-8. They may be "".
+ // Note that some of them can have several values.
+
+ std::string common_name;
+ std::string locality_name;
+ std::string state_or_province_name;
+ std::string country_name;
+
+ std::vector<std::string> street_addresses;
+ std::vector<std::string> organization_names;
+ std::vector<std::string> organization_unit_names;
+ std::vector<std::string> domain_components;
+};
+
+// This class is useful for maintaining policies about which certificates are
+// permitted or forbidden for a particular purpose.
+class NET_EXPORT CertPolicy {
+ public:
+ // The judgments this policy can reach.
+ enum Judgment {
+ // We don't have policy information for this certificate.
+ UNKNOWN,
+
+ // This certificate is allowed.
+ ALLOWED,
+
+ // This certificate is denied.
+ DENIED,
+ };
+
+ CertPolicy();
+ ~CertPolicy();
+
+ // Returns the judgment this policy makes about this certificate.
+ // For a certificate to be allowed, it must not have any *additional* errors
+ // from when it was allowed. For a certificate to be denied, it need only
+ // match *any* of the errors that caused it to be denied. We check denial
+ // first, before checking whether it's been allowed.
+ Judgment Check(X509Certificate* cert, CertStatus error) const;
+
+ // Causes the policy to allow this certificate for a given |error|.
+ void Allow(X509Certificate* cert, CertStatus error);
+
+ // Causes the policy to deny this certificate for a given |error|.
+ void Deny(X509Certificate* cert, CertStatus error);
+
+ // Returns true if this policy has allowed at least one certificate.
+ bool HasAllowedCert() const;
+
+ // Returns true if this policy has denied at least one certificate.
+ bool HasDeniedCert() const;
+
+ private:
+ // The set of fingerprints of allowed certificates.
+ std::map<SHA1HashValue, CertStatus, SHA1HashValueLessThan> allowed_;
+
+ // The set of fingerprints of denied certificates.
+ std::map<SHA1HashValue, CertStatus, SHA1HashValueLessThan> denied_;
+};
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Compares two OIDs by value.
+inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) {
+ return oid1->Length == oid2->Length &&
+ (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0);
+}
+#endif
+
+// A list of ASN.1 date/time formats that ParseCertificateDate() supports,
+// encoded in the canonical forms specified in RFC 2459/3280/5280.
+enum CertDateFormat {
+ // UTCTime: Format is YYMMDDHHMMSSZ
+ CERT_DATE_FORMAT_UTC_TIME,
+
+ // GeneralizedTime: Format is YYYYMMDDHHMMSSZ
+ CERT_DATE_FORMAT_GENERALIZED_TIME,
+};
+
+// Attempts to parse |raw_date|, an ASN.1 date/time string encoded as
+// |format|, and writes the result into |*time|. If an invalid date is
+// specified, or if parsing fails, returns false, and |*time| will not be
+// updated.
+NET_EXPORT_PRIVATE bool ParseCertificateDate(const base::StringPiece& raw_date,
+ CertDateFormat format,
+ base::Time* time);
+} // namespace net
+
+#endif // NET_CERT_X509_CERT_TYPES_H_
diff --git a/chromium/net/cert/x509_cert_types_mac.cc b/chromium/net/cert/x509_cert_types_mac.cc
new file mode 100644
index 00000000000..6439c7f3e77
--- /dev/null
+++ b/chromium/net/cert/x509_cert_types_mac.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_cert_types.h"
+
+#include <CoreServices/CoreServices.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/Security.h>
+
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace net {
+
+namespace {
+
+// The BER encoding of 0.9.2342.19200300.100.1.25.
+// On 10.6 and later this is available as CSSMOID_DomainComponent, which is an
+// external symbol from Security.framework. However, it appears that Apple's
+// implementation improperly encoded this on 10.6+, and even still is
+// unavailable on 10.5, so simply including the raw BER here.
+//
+// Note: CSSM is allowed to store CSSM_OIDs in any arbitrary format desired,
+// as long as the symbols are properly exposed. The fact that Apple's
+// implementation stores it in BER is an internal implementation detail
+// observed by studying libsecurity_cssm.
+const uint8 kDomainComponentData[] = {
+ 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19
+};
+
+const CSSM_OID kDomainComponentOID = {
+ arraysize(kDomainComponentData),
+ const_cast<uint8*>(kDomainComponentData)
+};
+
+const CSSM_OID* kOIDs[] = {
+ &CSSMOID_CommonName,
+ &CSSMOID_LocalityName,
+ &CSSMOID_StateProvinceName,
+ &CSSMOID_CountryName,
+ &CSSMOID_StreetAddress,
+ &CSSMOID_OrganizationName,
+ &CSSMOID_OrganizationalUnitName,
+ &kDomainComponentOID,
+};
+
+// The following structs and templates work with Apple's very arcane and under-
+// documented SecAsn1Parser API, which is apparently the same as NSS's ASN.1
+// decoder:
+// http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn1.html
+
+// These are used to parse the contents of a raw
+// BER DistinguishedName structure.
+
+const SecAsn1Template kStringValueTemplate[] = {
+ { SEC_ASN1_CHOICE, offsetof(CSSM_X509_TYPE_VALUE_PAIR, valueType), },
+ { SEC_ASN1_PRINTABLE_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_PRINTABLE_STRING },
+ { SEC_ASN1_IA5_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_IA5_STRING },
+ { SEC_ASN1_T61_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_T61_STRING },
+ { SEC_ASN1_UTF8_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_PKIX_UTF8_STRING },
+ { SEC_ASN1_BMP_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_PKIX_BMP_STRING },
+ { SEC_ASN1_UNIVERSAL_STRING,
+ offsetof(CSSM_X509_TYPE_VALUE_PAIR, value), 0,
+ BER_TAG_PKIX_UNIVERSAL_STRING },
+ { 0, }
+};
+
+const SecAsn1Template kKeyValuePairTemplate[] = {
+ { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CSSM_X509_TYPE_VALUE_PAIR) },
+ { SEC_ASN1_OBJECT_ID, offsetof(CSSM_X509_TYPE_VALUE_PAIR, type), },
+ { SEC_ASN1_INLINE, 0, &kStringValueTemplate, },
+ { 0, }
+};
+
+struct KeyValuePairs {
+ CSSM_X509_TYPE_VALUE_PAIR* pairs;
+};
+
+const SecAsn1Template kKeyValuePairSetTemplate[] = {
+ { SEC_ASN1_SET_OF, offsetof(KeyValuePairs, pairs),
+ kKeyValuePairTemplate, sizeof(KeyValuePairs) }
+};
+
+struct X509Name {
+ KeyValuePairs** pairs_list;
+};
+
+const SecAsn1Template kNameTemplate[] = {
+ { SEC_ASN1_SEQUENCE_OF, offsetof(X509Name, pairs_list),
+ kKeyValuePairSetTemplate, sizeof(X509Name) }
+};
+
+// Converts raw CSSM_DATA to a std::string. (Char encoding is unaltered.)
+std::string DataToString(CSSM_DATA data) {
+ return std::string(
+ reinterpret_cast<std::string::value_type*>(data.Data),
+ data.Length);
+}
+
+// Converts raw CSSM_DATA in ISO-8859-1 to a std::string in UTF-8.
+std::string Latin1DataToUTF8String(CSSM_DATA data) {
+ base::string16 utf16;
+ if (!CodepageToUTF16(DataToString(data), base::kCodepageLatin1,
+ base::OnStringConversionError::FAIL, &utf16))
+ return "";
+ return UTF16ToUTF8(utf16);
+}
+
+// Converts big-endian UTF-16 to UTF-8 in a std::string.
+// Note: The byte-order flipping is done in place on the input buffer!
+bool UTF16BigEndianToUTF8(base::char16* chars, size_t length,
+ std::string* out_string) {
+ for (size_t i = 0; i < length; i++)
+ chars[i] = EndianU16_BtoN(chars[i]);
+ return UTF16ToUTF8(chars, length, out_string);
+}
+
+// Converts big-endian UTF-32 to UTF-8 in a std::string.
+// Note: The byte-order flipping is done in place on the input buffer!
+bool UTF32BigEndianToUTF8(char32* chars, size_t length,
+ std::string* out_string) {
+ for (size_t i = 0; i < length; ++i)
+ chars[i] = EndianS32_BtoN(chars[i]);
+#if defined(WCHAR_T_IS_UTF32)
+ return WideToUTF8(reinterpret_cast<const wchar_t*>(chars),
+ length, out_string);
+#else
+#error This code doesn't handle 16-bit wchar_t.
+#endif
+}
+
+// Adds a type+value pair to the appropriate vector from a C array.
+// The array is keyed by the matching OIDs from kOIDS[].
+void AddTypeValuePair(const CSSM_OID type,
+ const std::string& value,
+ std::vector<std::string>* values[]) {
+ for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
+ if (CSSMOIDEqual(&type, kOIDs[oid])) {
+ values[oid]->push_back(value);
+ break;
+ }
+ }
+}
+
+// Stores the first string of the vector, if any, to *single_value.
+void SetSingle(const std::vector<std::string>& values,
+ std::string* single_value) {
+ // We don't expect to have more than one CN, L, S, and C.
+ LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values";
+ if (!values.empty())
+ *single_value = values[0];
+}
+
+bool match(const std::string& str, const std::string& against) {
+ // TODO(snej): Use the full matching rules specified in RFC 5280 sec. 7.1
+ // including trimming and case-folding: <http://www.ietf.org/rfc/rfc5280.txt>.
+ return against == str;
+}
+
+bool match(const std::vector<std::string>& rdn1,
+ const std::vector<std::string>& rdn2) {
+ // "Two relative distinguished names RDN1 and RDN2 match if they have the
+ // same number of naming attributes and for each naming attribute in RDN1
+ // there is a matching naming attribute in RDN2." --RFC 5280 sec. 7.1.
+ if (rdn1.size() != rdn2.size())
+ return false;
+ for (unsigned i1 = 0; i1 < rdn1.size(); ++i1) {
+ unsigned i2;
+ for (i2 = 0; i2 < rdn2.size(); ++i2) {
+ if (match(rdn1[i1], rdn2[i2]))
+ break;
+ }
+ if (i2 == rdn2.size())
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data,
+ size_t length) {
+ DCHECK(ber_name_data);
+
+ // First parse the BER |name_data| into the above structs.
+ SecAsn1CoderRef coder = NULL;
+ SecAsn1CoderCreate(&coder);
+ DCHECK(coder);
+ X509Name* name = NULL;
+ OSStatus err = SecAsn1Decode(coder, ber_name_data, length, kNameTemplate,
+ &name);
+ if (err) {
+ OSSTATUS_LOG(ERROR, err) << "SecAsn1Decode";
+ SecAsn1CoderRelease(coder);
+ return false;
+ }
+
+ // Now scan the structs and add the values to my string vectors.
+ // I don't store multiple common/locality/state/country names, so use
+ // temporary vectors for those.
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names;
+ std::vector<std::string>* values[] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &this->street_addresses,
+ &this->organization_names,
+ &this->organization_unit_names,
+ &this->domain_components
+ };
+ DCHECK(arraysize(kOIDs) == arraysize(values));
+
+ for (int rdn = 0; name[rdn].pairs_list; ++rdn) {
+ CSSM_X509_TYPE_VALUE_PAIR* pair;
+ for (int pair_index = 0;
+ NULL != (pair = name[rdn].pairs_list[0][pair_index].pairs);
+ ++pair_index) {
+ switch (pair->valueType) {
+ case BER_TAG_IA5_STRING: // ASCII (that means 7-bit!)
+ case BER_TAG_PRINTABLE_STRING: // a subset of ASCII
+ case BER_TAG_PKIX_UTF8_STRING: // UTF-8
+ AddTypeValuePair(pair->type, DataToString(pair->value), values);
+ break;
+ case BER_TAG_T61_STRING: // T61, pretend it's Latin-1
+ AddTypeValuePair(pair->type,
+ Latin1DataToUTF8String(pair->value),
+ values);
+ break;
+ case BER_TAG_PKIX_BMP_STRING: { // UTF-16, big-endian
+ std::string value;
+ UTF16BigEndianToUTF8(
+ reinterpret_cast<base::char16*>(pair->value.Data),
+ pair->value.Length / sizeof(base::char16),
+ &value);
+ AddTypeValuePair(pair->type, value, values);
+ break;
+ }
+ case BER_TAG_PKIX_UNIVERSAL_STRING: { // UTF-32, big-endian
+ std::string value;
+ UTF32BigEndianToUTF8(reinterpret_cast<char32*>(pair->value.Data),
+ pair->value.Length / sizeof(char32),
+ &value);
+ AddTypeValuePair(pair->type, value, values);
+ break;
+ }
+ default:
+ DCHECK_EQ(pair->valueType, BER_TAG_UNKNOWN);
+ // We don't know what data type this is, but we'll store it as a blob.
+ // Displaying the string may not work, but at least it can be compared
+ // byte-for-byte by a Matches() call.
+ AddTypeValuePair(pair->type, DataToString(pair->value), values);
+ break;
+ }
+ }
+ }
+
+ SetSingle(common_names, &this->common_name);
+ SetSingle(locality_names, &this->locality_name);
+ SetSingle(state_names, &this->state_or_province_name);
+ SetSingle(country_names, &this->country_name);
+
+ // Releasing |coder| frees all the memory pointed to via |name|.
+ SecAsn1CoderRelease(coder);
+ return true;
+}
+
+bool CertPrincipal::Matches(const CertPrincipal& against) const {
+ return match(common_name, against.common_name) &&
+ match(locality_name, against.locality_name) &&
+ match(state_or_province_name, against.state_or_province_name) &&
+ match(country_name, against.country_name) &&
+ match(street_addresses, against.street_addresses) &&
+ match(organization_names, against.organization_names) &&
+ match(organization_unit_names, against.organization_unit_names) &&
+ match(domain_components, against.domain_components);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_cert_types_unittest.cc b/chromium/net/cert/x509_cert_types_unittest.cc
new file mode 100644
index 00000000000..38fd3e95266
--- /dev/null
+++ b/chromium/net/cert/x509_cert_types_unittest.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2010 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/cert/x509_cert_types.h"
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "net/test/test_certificate_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+TEST(X509TypesTest, Matching) {
+ CertPrincipal spamco;
+ spamco.common_name = "SpamCo Dept. Of Certificization";
+ spamco.country_name = "EB";
+ spamco.organization_names.push_back("SpamCo Holding Company, LLC");
+ spamco.organization_names.push_back("SpamCo Evil Masterminds");
+ spamco.organization_unit_names.push_back("Class Z Obfuscation Authority");
+ ASSERT_TRUE(spamco.Matches(spamco));
+
+ CertPrincipal bogus;
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus = spamco;
+ EXPECT_TRUE(bogus.Matches(spamco));
+ EXPECT_TRUE(spamco.Matches(bogus));
+
+ bogus.organization_names.erase(bogus.organization_names.begin(),
+ bogus.organization_names.end());
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus.organization_names.push_back("SpamCo Holding Company, LLC");
+ bogus.organization_names.push_back("SpamCo Evil Masterminds");
+ EXPECT_TRUE(bogus.Matches(spamco));
+ EXPECT_TRUE(spamco.Matches(bogus));
+
+ bogus.locality_name = "Elbosdorf";
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+
+ bogus.locality_name = "";
+ bogus.organization_unit_names.push_back("Q Division");
+ EXPECT_FALSE(bogus.Matches(spamco));
+ EXPECT_FALSE(spamco.Matches(bogus));
+}
+#endif
+
+#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN)
+TEST(X509TypesTest, ParseDNVerisign) {
+ CertPrincipal verisign;
+ EXPECT_TRUE(verisign.ParseDistinguishedName(VerisignDN, sizeof(VerisignDN)));
+ EXPECT_EQ("", verisign.common_name);
+ EXPECT_EQ("US", verisign.country_name);
+ ASSERT_EQ(1U, verisign.organization_names.size());
+ EXPECT_EQ("VeriSign, Inc.", verisign.organization_names[0]);
+ ASSERT_EQ(1U, verisign.organization_unit_names.size());
+ EXPECT_EQ("Class 1 Public Primary Certification Authority",
+ verisign.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNStartcom) {
+ CertPrincipal startcom;
+ EXPECT_TRUE(startcom.ParseDistinguishedName(StartComDN, sizeof(StartComDN)));
+ EXPECT_EQ("StartCom Certification Authority", startcom.common_name);
+ EXPECT_EQ("IL", startcom.country_name);
+ ASSERT_EQ(1U, startcom.organization_names.size());
+ EXPECT_EQ("StartCom Ltd.", startcom.organization_names[0]);
+ ASSERT_EQ(1U, startcom.organization_unit_names.size());
+ EXPECT_EQ("Secure Digital Certificate Signing",
+ startcom.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNUserTrust) {
+ CertPrincipal usertrust;
+ EXPECT_TRUE(usertrust.ParseDistinguishedName(UserTrustDN,
+ sizeof(UserTrustDN)));
+ EXPECT_EQ("UTN-USERFirst-Client Authentication and Email",
+ usertrust.common_name);
+ EXPECT_EQ("US", usertrust.country_name);
+ EXPECT_EQ("UT", usertrust.state_or_province_name);
+ EXPECT_EQ("Salt Lake City", usertrust.locality_name);
+ ASSERT_EQ(1U, usertrust.organization_names.size());
+ EXPECT_EQ("The USERTRUST Network", usertrust.organization_names[0]);
+ ASSERT_EQ(1U, usertrust.organization_unit_names.size());
+ EXPECT_EQ("http://www.usertrust.com",
+ usertrust.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNTurkTrust) {
+ // Note: This tests parsing UTF8STRINGs.
+ CertPrincipal turktrust;
+ EXPECT_TRUE(turktrust.ParseDistinguishedName(TurkTrustDN,
+ sizeof(TurkTrustDN)));
+ EXPECT_EQ("TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı",
+ turktrust.common_name);
+ EXPECT_EQ("TR", turktrust.country_name);
+ EXPECT_EQ("Ankara", turktrust.locality_name);
+ ASSERT_EQ(1U, turktrust.organization_names.size());
+ EXPECT_EQ("TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005",
+ turktrust.organization_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNATrust) {
+ // Note: This tests parsing 16-bit BMPSTRINGs.
+ CertPrincipal atrust;
+ EXPECT_TRUE(atrust.ParseDistinguishedName(ATrustQual01DN,
+ sizeof(ATrustQual01DN)));
+ EXPECT_EQ("A-Trust-Qual-01",
+ atrust.common_name);
+ EXPECT_EQ("AT", atrust.country_name);
+ ASSERT_EQ(1U, atrust.organization_names.size());
+ EXPECT_EQ("A-Trust Ges. für Sicherheitssysteme im elektr. Datenverkehr GmbH",
+ atrust.organization_names[0]);
+ ASSERT_EQ(1U, atrust.organization_unit_names.size());
+ EXPECT_EQ("A-Trust-Qual-01",
+ atrust.organization_unit_names[0]);
+}
+
+TEST(X509TypesTest, ParseDNEntrust) {
+ // Note: This tests parsing T61STRINGs and fields with multiple values.
+ CertPrincipal entrust;
+ EXPECT_TRUE(entrust.ParseDistinguishedName(EntrustDN,
+ sizeof(EntrustDN)));
+ EXPECT_EQ("Entrust.net Certification Authority (2048)",
+ entrust.common_name);
+ EXPECT_EQ("", entrust.country_name);
+ ASSERT_EQ(1U, entrust.organization_names.size());
+ EXPECT_EQ("Entrust.net",
+ entrust.organization_names[0]);
+ ASSERT_EQ(2U, entrust.organization_unit_names.size());
+ EXPECT_EQ("www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)",
+ entrust.organization_unit_names[0]);
+ EXPECT_EQ("(c) 1999 Entrust.net Limited",
+ entrust.organization_unit_names[1]);
+}
+#endif
+
+const struct CertDateTestData {
+ CertDateFormat format;
+ const char* date_string;
+ bool is_valid;
+ base::Time::Exploded expected_result;
+} kCertDateTimeData[] = {
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "120101000000Z",
+ true,
+ { 2012, 1, 0, 1, 0, 0, 0 } },
+ { CERT_DATE_FORMAT_GENERALIZED_TIME,
+ "20120101000000Z",
+ true,
+ { 2012, 1, 0, 1, 0, 0, 0 } },
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "490101000000Z",
+ true,
+ { 2049, 1, 0, 1, 0, 0, 0 } },
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "500101000000Z",
+ true,
+ { 1950, 1, 0, 1, 0, 0, 0 } },
+ { CERT_DATE_FORMAT_GENERALIZED_TIME,
+ "19500101000000Z",
+ true,
+ { 1950, 1, 0, 1, 0, 0, 0 } },
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "AB0101000000Z",
+ false,
+ { 0 } },
+ { CERT_DATE_FORMAT_GENERALIZED_TIME,
+ "19AB0101000000Z",
+ false,
+ { 0 } },
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "",
+ false,
+ { 0 } },
+ { CERT_DATE_FORMAT_UTC_TIME,
+ "A",
+ false,
+ { 0 } },
+ { CERT_DATE_FORMAT_GENERALIZED_TIME,
+ "20121301000000Z",
+ false,
+ { 0 } },
+ { CERT_DATE_FORMAT_GENERALIZED_TIME,
+ "20120101123000Z",
+ true,
+ { 2012, 1, 0, 1, 12, 30, 0 } },
+};
+
+// GTest pretty printer.
+void PrintTo(const CertDateTestData& data, std::ostream* os) {
+ *os << " format: " << data.format
+ << "; date string: " << base::StringPiece(data.date_string)
+ << "; valid: " << data.is_valid
+ << "; expected date: "
+ << (data.is_valid ?
+ base::Time::FromUTCExploded(data.expected_result)
+ .ToInternalValue() :
+ 0U);
+}
+
+class X509CertTypesDateTest : public testing::TestWithParam<CertDateTestData> {
+ public:
+ virtual ~X509CertTypesDateTest() {}
+ virtual void SetUp() {
+ test_data_ = GetParam();
+ }
+
+ protected:
+ CertDateTestData test_data_;
+};
+
+TEST_P(X509CertTypesDateTest, Parse) {
+ base::Time parsed_date;
+ bool parsed = ParseCertificateDate(
+ test_data_.date_string, test_data_.format, &parsed_date);
+ EXPECT_EQ(test_data_.is_valid, parsed);
+ if (!test_data_.is_valid)
+ return;
+ // Convert the expected value to a base::Time(). This ensures that systems
+ // systems that only support 32-bit times will pass the tests, by ensuring at
+ // least that the times have the same truncating behaviour.
+ // Note: Compared as internal values so that mismatches can be cleanly
+ // printed by GTest (eg: without PrintTo overrides).
+ EXPECT_EQ(base::Time::FromUTCExploded(test_data_.expected_result)
+ .ToInternalValue(),
+ parsed_date.ToInternalValue());
+}
+INSTANTIATE_TEST_CASE_P(,
+ X509CertTypesDateTest,
+ testing::ValuesIn(kCertDateTimeData));
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/cert/x509_cert_types_win.cc b/chromium/net/cert/x509_cert_types_win.cc
new file mode 100644
index 00000000000..d99362cb921
--- /dev/null
+++ b/chromium/net/cert/x509_cert_types_win.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_cert_types.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "crypto/capi_util.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+namespace net {
+
+namespace {
+
+// A list of OIDs to decode. Any OID not on this list will be ignored for
+// purposes of parsing.
+const char* kOIDs[] = {
+ szOID_COMMON_NAME,
+ szOID_LOCALITY_NAME,
+ szOID_STATE_OR_PROVINCE_NAME,
+ szOID_COUNTRY_NAME,
+ szOID_STREET_ADDRESS,
+ szOID_ORGANIZATION_NAME,
+ szOID_ORGANIZATIONAL_UNIT_NAME,
+ szOID_DOMAIN_COMPONENT
+};
+
+// Converts the value for |attribute| to an UTF-8 string, storing the result
+// in |value|. Returns false if the string cannot be converted.
+bool GetAttributeValue(PCERT_RDN_ATTR attribute,
+ std::string* value) {
+ DWORD chars_needed = CertRDNValueToStrW(attribute->dwValueType,
+ &attribute->Value, NULL, 0);
+ if (chars_needed == 0)
+ return false;
+ if (chars_needed == 1) {
+ // The value is actually an empty string (chars_needed includes a single
+ // char for a NULL value). Don't bother converting - just clear the
+ // string.
+ value->clear();
+ return true;
+ }
+ std::wstring wide_name;
+ DWORD chars_written = CertRDNValueToStrW(
+ attribute->dwValueType, &attribute->Value,
+ WriteInto(&wide_name, chars_needed), chars_needed);
+ if (chars_written <= 1)
+ return false;
+ wide_name.resize(chars_written - 1);
+ *value = WideToUTF8(wide_name);
+ return true;
+}
+
+// Adds a type+value pair to the appropriate vector from a C array.
+// The array is keyed by the matching OIDs from kOIDS[].
+bool AddTypeValuePair(PCERT_RDN_ATTR attribute,
+ std::vector<std::string>* values[]) {
+ for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
+ if (strcmp(attribute->pszObjId, kOIDs[oid]) == 0) {
+ std::string value;
+ if (!GetAttributeValue(attribute, &value))
+ return false;
+ values[oid]->push_back(value);
+ break;
+ }
+ }
+ return true;
+}
+
+// Stores the first string of the vector, if any, to *single_value.
+void SetSingle(const std::vector<std::string>& values,
+ std::string* single_value) {
+ // We don't expect to have more than one CN, L, S, and C.
+ LOG_IF(WARNING, values.size() > 1) << "Didn't expect multiple values";
+ if (!values.empty())
+ *single_value = values[0];
+}
+
+} // namespace
+
+bool CertPrincipal::ParseDistinguishedName(const void* ber_name_data,
+ size_t length) {
+ DCHECK(ber_name_data);
+
+ CRYPT_DECODE_PARA decode_para;
+ decode_para.cbSize = sizeof(decode_para);
+ decode_para.pfnAlloc = crypto::CryptAlloc;
+ decode_para.pfnFree = crypto::CryptFree;
+ CERT_NAME_INFO* name_info = NULL;
+ DWORD name_info_size = 0;
+ BOOL rv;
+ rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ X509_NAME,
+ reinterpret_cast<const BYTE*>(ber_name_data),
+ length,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &name_info, &name_info_size);
+ if (!rv)
+ return false;
+ scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info);
+
+ std::vector<std::string> common_names, locality_names, state_names,
+ country_names;
+
+ std::vector<std::string>* values[] = {
+ &common_names, &locality_names,
+ &state_names, &country_names,
+ &this->street_addresses,
+ &this->organization_names,
+ &this->organization_unit_names,
+ &this->domain_components
+ };
+ DCHECK(arraysize(kOIDs) == arraysize(values));
+
+ for (DWORD cur_rdn = 0; cur_rdn < name_info->cRDN; ++cur_rdn) {
+ PCERT_RDN rdn = &name_info->rgRDN[cur_rdn];
+ for (DWORD cur_ava = 0; cur_ava < rdn->cRDNAttr; ++cur_ava) {
+ PCERT_RDN_ATTR ava = &rdn->rgRDNAttr[cur_ava];
+ if (!AddTypeValuePair(ava, values))
+ return false;
+ }
+ }
+
+ SetSingle(common_names, &this->common_name);
+ SetSingle(locality_names, &this->locality_name);
+ SetSingle(state_names, &this->state_or_province_name);
+ SetSingle(country_names, &this->country_name);
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate.cc b/chromium/net/cert/x509_certificate.cc
new file mode 100644
index 00000000000..36e806ebaba
--- /dev/null
+++ b/chromium/net/cert/x509_certificate.cc
@@ -0,0 +1,734 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <stdlib.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cert/pem_tokenizer.h"
+#include "url/url_canon.h"
+
+namespace net {
+
+namespace {
+
+// Indicates the order to use when trying to decode binary data, which is
+// based on (speculation) as to what will be most common -> least common
+const X509Certificate::Format kFormatDecodePriority[] = {
+ X509Certificate::FORMAT_SINGLE_CERTIFICATE,
+ X509Certificate::FORMAT_PKCS7
+};
+
+// The PEM block header used for DER certificates
+const char kCertificateHeader[] = "CERTIFICATE";
+// The PEM block header used for PKCS#7 data
+const char kPKCS7Header[] = "PKCS7";
+
+#if !defined(USE_NSS)
+// A thread-safe cache for OS certificate handles.
+//
+// Within each of the supported underlying crypto libraries, a certificate
+// handle is represented as a ref-counted object that contains the parsed
+// data for the certificate. In addition, the underlying OS handle may also
+// contain a copy of the original ASN.1 DER used to constructed the handle.
+//
+// In order to reduce the memory usage when multiple SSL connections exist,
+// with each connection storing the server's identity certificate plus any
+// intermediates supplied, the certificate handles are cached. Any two
+// X509Certificates that were created from the same ASN.1 DER data,
+// regardless of where that data came from, will share the same underlying
+// OS certificate handle.
+class X509CertificateCache {
+ public:
+ // Performs a compare-and-swap like operation. If an OS certificate handle
+ // for the same certificate data as |*cert_handle| already exists in the
+ // cache, the original |*cert_handle| will be freed and |cert_handle|
+ // will be updated to point to a duplicated reference to the existing cached
+ // certificate, with the caller taking ownership of this duplicated handle.
+ // If an equivalent OS certificate handle is not found, a duplicated
+ // reference to |*cert_handle| will be added to the cache. In either case,
+ // upon return, the caller fully owns |*cert_handle| and is responsible for
+ // calling FreeOSCertHandle(), after first calling Remove().
+ void InsertOrUpdate(X509Certificate::OSCertHandle* cert_handle);
+
+ // Decrements the cache reference count for |cert_handle|, a handle that was
+ // previously obtained by calling InsertOrUpdate(). If this is the last
+ // cached reference held, this will remove the handle from the cache. The
+ // caller retains ownership of |cert_handle| and remains responsible for
+ // calling FreeOSCertHandle() to release the underlying OS certificate
+ void Remove(X509Certificate::OSCertHandle cert_handle);
+
+ private:
+ // A single entry in the cache. Certificates will be keyed by their SHA1
+ // fingerprints, but will not be considered equivalent unless the entire
+ // certificate data matches.
+ struct Entry {
+ Entry() : cert_handle(NULL), ref_count(0) {}
+
+ X509Certificate::OSCertHandle cert_handle;
+
+ // Increased by each call to InsertOrUpdate(), and balanced by each call
+ // to Remove(). When it equals 0, all references created by
+ // InsertOrUpdate() have been released, so the cache entry will be removed
+ // the cached OS certificate handle will be freed.
+ int ref_count;
+ };
+ typedef std::map<SHA1HashValue, Entry, SHA1HashValueLessThan> CertMap;
+
+ // Obtain an instance of X509CertificateCache via a LazyInstance.
+ X509CertificateCache() {}
+ ~X509CertificateCache() {}
+ friend struct base::DefaultLazyInstanceTraits<X509CertificateCache>;
+
+ // You must acquire this lock before using any private data of this object
+ // You must not block while holding this lock.
+ base::Lock lock_;
+
+ // The certificate cache. You must acquire |lock_| before using |cache_|.
+ CertMap cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(X509CertificateCache);
+};
+
+base::LazyInstance<X509CertificateCache>::Leaky
+ g_x509_certificate_cache = LAZY_INSTANCE_INITIALIZER;
+
+void X509CertificateCache::InsertOrUpdate(
+ X509Certificate::OSCertHandle* cert_handle) {
+ DCHECK(cert_handle);
+ SHA1HashValue fingerprint =
+ X509Certificate::CalculateFingerprint(*cert_handle);
+
+ X509Certificate::OSCertHandle old_handle = NULL;
+ {
+ base::AutoLock lock(lock_);
+ CertMap::iterator pos = cache_.find(fingerprint);
+ if (pos == cache_.end()) {
+ // A cached entry was not found, so initialize a new entry. The entry
+ // assumes ownership of the current |*cert_handle|.
+ Entry cache_entry;
+ cache_entry.cert_handle = *cert_handle;
+ cache_entry.ref_count = 0;
+ CertMap::value_type cache_value(fingerprint, cache_entry);
+ pos = cache_.insert(cache_value).first;
+ } else {
+ bool is_same_cert =
+ X509Certificate::IsSameOSCert(*cert_handle, pos->second.cert_handle);
+ if (!is_same_cert) {
+ // Two certificates don't match, due to a SHA1 hash collision. Given
+ // the low probability, the simplest solution is to not cache the
+ // certificate, which should not affect performance too negatively.
+ return;
+ }
+ // A cached entry was found and will be used instead of the caller's
+ // handle. Ensure the caller's original handle will be freed, since
+ // ownership is assumed.
+ old_handle = *cert_handle;
+ }
+ // Whether an existing cached handle or a new handle, increment the
+ // cache's reference count and return a handle that the caller can own.
+ ++pos->second.ref_count;
+ *cert_handle = X509Certificate::DupOSCertHandle(pos->second.cert_handle);
+ }
+ // If the caller's handle was replaced with a cached handle, free the
+ // original handle now. This is done outside of the lock because
+ // |old_handle| may be the only handle for this particular certificate, so
+ // freeing it may be complex or resource-intensive and does not need to
+ // be guarded by the lock.
+ if (old_handle) {
+ X509Certificate::FreeOSCertHandle(old_handle);
+ DHISTOGRAM_COUNTS("X509CertificateReuseCount", 1);
+ }
+}
+
+void X509CertificateCache::Remove(X509Certificate::OSCertHandle cert_handle) {
+ SHA1HashValue fingerprint =
+ X509Certificate::CalculateFingerprint(cert_handle);
+ base::AutoLock lock(lock_);
+
+ CertMap::iterator pos = cache_.find(fingerprint);
+ if (pos == cache_.end())
+ return; // A hash collision where the winning cert was already freed.
+
+ bool is_same_cert = X509Certificate::IsSameOSCert(cert_handle,
+ pos->second.cert_handle);
+ if (!is_same_cert)
+ return; // A hash collision where the winning cert is still around.
+
+ if (--pos->second.ref_count == 0) {
+ // The last reference to |cert_handle| has been removed, so release the
+ // Entry's OS handle and remove the Entry. The caller still holds a
+ // reference to |cert_handle| and is responsible for freeing it.
+ X509Certificate::FreeOSCertHandle(pos->second.cert_handle);
+ cache_.erase(pos);
+ }
+}
+#endif // !defined(USE_NSS)
+
+// See X509CertificateCache::InsertOrUpdate. NSS has a built-in cache, so there
+// is no point in wrapping another cache around it.
+void InsertOrUpdateCache(X509Certificate::OSCertHandle* cert_handle) {
+#if !defined(USE_NSS)
+ g_x509_certificate_cache.Pointer()->InsertOrUpdate(cert_handle);
+#endif
+}
+
+// See X509CertificateCache::Remove.
+void RemoveFromCache(X509Certificate::OSCertHandle cert_handle) {
+#if !defined(USE_NSS)
+ g_x509_certificate_cache.Pointer()->Remove(cert_handle);
+#endif
+}
+
+// Utility to split |src| on the first occurrence of |c|, if any. |right| will
+// either be empty if |c| was not found, or will contain the remainder of the
+// string including the split character itself.
+void SplitOnChar(const base::StringPiece& src,
+ char c,
+ base::StringPiece* left,
+ base::StringPiece* right) {
+ size_t pos = src.find(c);
+ if (pos == base::StringPiece::npos) {
+ *left = src;
+ right->clear();
+ } else {
+ *left = src.substr(0, pos);
+ *right = src.substr(pos);
+ }
+}
+
+} // namespace
+
+bool X509Certificate::LessThan::operator()(
+ const scoped_refptr<X509Certificate>& lhs,
+ const scoped_refptr<X509Certificate>& rhs) const {
+ if (lhs.get() == rhs.get())
+ return false;
+
+ int rv = memcmp(lhs->fingerprint_.data, rhs->fingerprint_.data,
+ sizeof(lhs->fingerprint_.data));
+ if (rv != 0)
+ return rv < 0;
+
+ rv = memcmp(lhs->ca_fingerprint_.data, rhs->ca_fingerprint_.data,
+ sizeof(lhs->ca_fingerprint_.data));
+ return rv < 0;
+}
+
+X509Certificate::X509Certificate(const std::string& subject,
+ const std::string& issuer,
+ base::Time start_date,
+ base::Time expiration_date)
+ : subject_(subject),
+ issuer_(issuer),
+ valid_start_(start_date),
+ valid_expiry_(expiration_date),
+ cert_handle_(NULL) {
+ memset(fingerprint_.data, 0, sizeof(fingerprint_.data));
+ memset(ca_fingerprint_.data, 0, sizeof(ca_fingerprint_.data));
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromHandle(
+ OSCertHandle cert_handle,
+ const OSCertHandles& intermediates) {
+ DCHECK(cert_handle);
+ return new X509Certificate(cert_handle, intermediates);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromDERCertChain(
+ const std::vector<base::StringPiece>& der_certs) {
+ if (der_certs.empty())
+ return NULL;
+
+ X509Certificate::OSCertHandles intermediate_ca_certs;
+ for (size_t i = 1; i < der_certs.size(); i++) {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(
+ const_cast<char*>(der_certs[i].data()), der_certs[i].size());
+ if (!handle)
+ break;
+ intermediate_ca_certs.push_back(handle);
+ }
+
+ OSCertHandle handle = NULL;
+ // Return NULL if we failed to parse any of the certs.
+ if (der_certs.size() - 1 == intermediate_ca_certs.size()) {
+ handle = CreateOSCertHandleFromBytes(
+ const_cast<char*>(der_certs[0].data()), der_certs[0].size());
+ }
+
+ X509Certificate* cert = NULL;
+ if (handle) {
+ cert = CreateFromHandle(handle, intermediate_ca_certs);
+ FreeOSCertHandle(handle);
+ }
+
+ for (size_t i = 0; i < intermediate_ca_certs.size(); i++)
+ FreeOSCertHandle(intermediate_ca_certs[i]);
+
+ return cert;
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromBytes(const char* data,
+ int length) {
+ OSCertHandle cert_handle = CreateOSCertHandleFromBytes(data, length);
+ if (!cert_handle)
+ return NULL;
+
+ X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles());
+ FreeOSCertHandle(cert_handle);
+ return cert;
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromPickle(const Pickle& pickle,
+ PickleIterator* pickle_iter,
+ PickleType type) {
+ if (type == PICKLETYPE_CERTIFICATE_CHAIN_V3) {
+ int chain_length = 0;
+ if (!pickle_iter->ReadLength(&chain_length))
+ return NULL;
+
+ std::vector<base::StringPiece> cert_chain;
+ const char* data = NULL;
+ int data_length = 0;
+ for (int i = 0; i < chain_length; ++i) {
+ if (!pickle_iter->ReadData(&data, &data_length))
+ return NULL;
+ cert_chain.push_back(base::StringPiece(data, data_length));
+ }
+ return CreateFromDERCertChain(cert_chain);
+ }
+
+ // Legacy / Migration code. This should eventually be removed once
+ // sufficient time has passed that all pickles serialized prior to
+ // PICKLETYPE_CERTIFICATE_CHAIN_V3 have been removed.
+ OSCertHandle cert_handle = ReadOSCertHandleFromPickle(pickle_iter);
+ if (!cert_handle)
+ return NULL;
+
+ OSCertHandles intermediates;
+ uint32 num_intermediates = 0;
+ if (type != PICKLETYPE_SINGLE_CERTIFICATE) {
+ if (!pickle_iter->ReadUInt32(&num_intermediates)) {
+ FreeOSCertHandle(cert_handle);
+ return NULL;
+ }
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && defined(__x86_64__)
+ // On 64-bit Linux (and any other 64-bit platforms), the intermediate count
+ // might really be a 64-bit field since we used to use Pickle::WriteSize(),
+ // which writes either 32 or 64 bits depending on the architecture. Since
+ // x86-64 is little-endian, if that happens, the next 32 bits will be all
+ // zeroes (the high bits) and the 32 bits we already read above are the
+ // correct value (we assume there are never more than 2^32 - 1 intermediate
+ // certificates in a chain; in practice, more than a dozen or so is
+ // basically unheard of). Since it's invalid for a certificate to start with
+ // 32 bits of zeroes, we check for that here and skip it if we find it. We
+ // save a copy of the pickle iterator to restore in case we don't get 32
+ // bits of zeroes. Now we always write 32 bits, so after a while, these old
+ // cached pickles will all get replaced.
+ // TODO(mdm): remove this compatibility code in April 2013 or so.
+ PickleIterator saved_iter = *pickle_iter;
+ uint32 zero_check = 0;
+ if (!pickle_iter->ReadUInt32(&zero_check)) {
+ // This may not be an error. If there are no intermediates, and we're
+ // reading an old 32-bit pickle, and there's nothing else after this in
+ // the pickle, we should report success. Note that it is technically
+ // possible for us to skip over zeroes that should have occurred after
+ // an empty certificate list; to avoid this going forward, only do this
+ // backward-compatibility stuff for PICKLETYPE_CERTIFICATE_CHAIN_V1
+ // which comes from the pickle version number in http_response_info.cc.
+ if (num_intermediates) {
+ FreeOSCertHandle(cert_handle);
+ return NULL;
+ }
+ }
+ if (zero_check)
+ *pickle_iter = saved_iter;
+#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && defined(__x86_64__)
+
+ for (uint32 i = 0; i < num_intermediates; ++i) {
+ OSCertHandle intermediate = ReadOSCertHandleFromPickle(pickle_iter);
+ if (!intermediate)
+ break;
+ intermediates.push_back(intermediate);
+ }
+ }
+
+ X509Certificate* cert = NULL;
+ if (intermediates.size() == num_intermediates)
+ cert = CreateFromHandle(cert_handle, intermediates);
+ FreeOSCertHandle(cert_handle);
+ for (size_t i = 0; i < intermediates.size(); ++i)
+ FreeOSCertHandle(intermediates[i]);
+
+ return cert;
+}
+
+// static
+CertificateList X509Certificate::CreateCertificateListFromBytes(
+ const char* data, int length, int format) {
+ OSCertHandles certificates;
+
+ // Check to see if it is in a PEM-encoded form. This check is performed
+ // first, as both OS X and NSS will both try to convert if they detect
+ // PEM encoding, except they don't do it consistently between the two.
+ base::StringPiece data_string(data, length);
+ std::vector<std::string> pem_headers;
+
+ // To maintain compatibility with NSS/Firefox, CERTIFICATE is a universally
+ // valid PEM block header for any format.
+ pem_headers.push_back(kCertificateHeader);
+ if (format & FORMAT_PKCS7)
+ pem_headers.push_back(kPKCS7Header);
+
+ PEMTokenizer pem_tok(data_string, pem_headers);
+ while (pem_tok.GetNext()) {
+ std::string decoded(pem_tok.data());
+
+ OSCertHandle handle = NULL;
+ if (format & FORMAT_PEM_CERT_SEQUENCE)
+ handle = CreateOSCertHandleFromBytes(decoded.c_str(), decoded.size());
+ if (handle != NULL) {
+ // Parsed a DER encoded certificate. All PEM blocks that follow must
+ // also be DER encoded certificates wrapped inside of PEM blocks.
+ format = FORMAT_PEM_CERT_SEQUENCE;
+ certificates.push_back(handle);
+ continue;
+ }
+
+ // If the first block failed to parse as a DER certificate, and
+ // formats other than PEM are acceptable, check to see if the decoded
+ // data is one of the accepted formats.
+ if (format & ~FORMAT_PEM_CERT_SEQUENCE) {
+ for (size_t i = 0; certificates.empty() &&
+ i < arraysize(kFormatDecodePriority); ++i) {
+ if (format & kFormatDecodePriority[i]) {
+ certificates = CreateOSCertHandlesFromBytes(decoded.c_str(),
+ decoded.size(), kFormatDecodePriority[i]);
+ }
+ }
+ }
+
+ // Stop parsing after the first block for any format but a sequence of
+ // PEM-encoded DER certificates. The case of FORMAT_PEM_CERT_SEQUENCE
+ // is handled above, and continues processing until a certificate fails
+ // to parse.
+ break;
+ }
+
+ // Try each of the formats, in order of parse preference, to see if |data|
+ // contains the binary representation of a Format, if it failed to parse
+ // as a PEM certificate/chain.
+ for (size_t i = 0; certificates.empty() &&
+ i < arraysize(kFormatDecodePriority); ++i) {
+ if (format & kFormatDecodePriority[i])
+ certificates = CreateOSCertHandlesFromBytes(data, length,
+ kFormatDecodePriority[i]);
+ }
+
+ CertificateList results;
+ // No certificates parsed.
+ if (certificates.empty())
+ return results;
+
+ for (OSCertHandles::iterator it = certificates.begin();
+ it != certificates.end(); ++it) {
+ X509Certificate* result = CreateFromHandle(*it, OSCertHandles());
+ results.push_back(scoped_refptr<X509Certificate>(result));
+ FreeOSCertHandle(*it);
+ }
+
+ return results;
+}
+
+void X509Certificate::Persist(Pickle* pickle) {
+ DCHECK(cert_handle_);
+ // This would be an absolutely insane number of intermediates.
+ if (intermediate_ca_certs_.size() > static_cast<size_t>(INT_MAX) - 1) {
+ NOTREACHED();
+ return;
+ }
+ if (!pickle->WriteInt(
+ static_cast<int>(intermediate_ca_certs_.size() + 1)) ||
+ !WriteOSCertHandleToPickle(cert_handle_, pickle)) {
+ NOTREACHED();
+ return;
+ }
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) {
+ if (!WriteOSCertHandleToPickle(intermediate_ca_certs_[i], pickle)) {
+ NOTREACHED();
+ return;
+ }
+ }
+}
+
+void X509Certificate::GetDNSNames(std::vector<std::string>* dns_names) const {
+ GetSubjectAltName(dns_names, NULL);
+ if (dns_names->empty())
+ dns_names->push_back(subject_.common_name);
+}
+
+bool X509Certificate::HasExpired() const {
+ return base::Time::Now() > valid_expiry();
+}
+
+bool X509Certificate::Equals(const X509Certificate* other) const {
+ return IsSameOSCert(cert_handle_, other->cert_handle_);
+}
+
+// static
+bool X509Certificate::VerifyHostname(
+ const std::string& hostname,
+ const std::string& cert_common_name,
+ const std::vector<std::string>& cert_san_dns_names,
+ const std::vector<std::string>& cert_san_ip_addrs) {
+ DCHECK(!hostname.empty());
+ // Perform name verification following http://tools.ietf.org/html/rfc6125.
+ // The terminology used in this method is as per that RFC:-
+ // Reference identifier == the host the local user/agent is intending to
+ // access, i.e. the thing displayed in the URL bar.
+ // Presented identifier(s) == name(s) the server knows itself as, in its cert.
+
+ // CanonicalizeHost requires surrounding brackets to parse an IPv6 address.
+ const std::string host_or_ip = hostname.find(':') != std::string::npos ?
+ "[" + hostname + "]" : hostname;
+ url_canon::CanonHostInfo host_info;
+ std::string reference_name = CanonicalizeHost(host_or_ip, &host_info);
+ // CanonicalizeHost does not normalize absolute vs relative DNS names. If
+ // the input name was absolute (included trailing .), normalize it as if it
+ // was relative.
+ if (!reference_name.empty() && *reference_name.rbegin() == '.')
+ reference_name.resize(reference_name.size() - 1);
+ if (reference_name.empty())
+ return false;
+
+ // Allow fallback to Common name matching?
+ const bool common_name_fallback = cert_san_dns_names.empty() &&
+ cert_san_ip_addrs.empty();
+
+ // Fully handle all cases where |hostname| contains an IP address.
+ if (host_info.IsIPAddress()) {
+ if (common_name_fallback &&
+ host_info.family == url_canon::CanonHostInfo::IPV4) {
+ // Fallback to Common name matching. As this is deprecated and only
+ // supported for compatibility refuse it for IPv6 addresses.
+ return reference_name == cert_common_name;
+ }
+ base::StringPiece ip_addr_string(
+ reinterpret_cast<const char*>(host_info.address),
+ host_info.AddressLength());
+ return std::find(cert_san_ip_addrs.begin(), cert_san_ip_addrs.end(),
+ ip_addr_string) != cert_san_ip_addrs.end();
+ }
+
+ // |reference_domain| is the remainder of |host| after the leading host
+ // component is stripped off, but includes the leading dot e.g.
+ // "www.f.com" -> ".f.com".
+ // If there is no meaningful domain part to |host| (e.g. it contains no dots)
+ // then |reference_domain| will be empty.
+ base::StringPiece reference_host, reference_domain;
+ SplitOnChar(reference_name, '.', &reference_host, &reference_domain);
+ bool allow_wildcards = false;
+ if (!reference_domain.empty()) {
+ DCHECK(reference_domain.starts_with("."));
+
+ // Do not allow wildcards for public/ICANN registry controlled domains -
+ // that is, prevent *.com or *.co.uk as valid presented names, but do not
+ // prevent *.appspot.com (a private registry controlled domain).
+ // In addition, unknown top-level domains (such as 'intranet' domains or
+ // new TLDs/gTLDs not yet added to the registry controlled domain dataset)
+ // are also implicitly prevented.
+ // Because |reference_domain| must contain at least one name component that
+ // is not registry controlled, this ensures that all reference domains
+ // contain at least three domain components when using wildcards.
+ size_t registry_length =
+ registry_controlled_domains::GetRegistryLength(
+ reference_name,
+ registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
+ registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+
+ // Because |reference_name| was already canonicalized, the following
+ // should never happen.
+ CHECK_NE(std::string::npos, registry_length);
+
+ // Account for the leading dot in |reference_domain|.
+ bool is_registry_controlled =
+ registry_length != 0 &&
+ registry_length == (reference_domain.size() - 1);
+
+ // Additionally, do not attempt wildcard matching for purely numeric
+ // hostnames.
+ allow_wildcards =
+ !is_registry_controlled &&
+ reference_name.find_first_not_of("0123456789.") != std::string::npos;
+ }
+
+ // Now step through the DNS names doing wild card comparison (if necessary)
+ // on each against the reference name. If subjectAltName is empty, then
+ // fallback to use the common name instead.
+ std::vector<std::string> common_name_as_vector;
+ const std::vector<std::string>* presented_names = &cert_san_dns_names;
+ if (common_name_fallback) {
+ // Note: there's a small possibility cert_common_name is an international
+ // domain name in non-standard encoding (e.g. UTF8String or BMPString
+ // instead of A-label). As common name fallback is deprecated we're not
+ // doing anything specific to deal with this.
+ common_name_as_vector.push_back(cert_common_name);
+ presented_names = &common_name_as_vector;
+ }
+ for (std::vector<std::string>::const_iterator it =
+ presented_names->begin();
+ it != presented_names->end(); ++it) {
+ // Catch badly corrupt cert names up front.
+ if (it->empty() || it->find('\0') != std::string::npos) {
+ DVLOG(1) << "Bad name in cert: " << *it;
+ continue;
+ }
+ std::string presented_name(StringToLowerASCII(*it));
+
+ // Remove trailing dot, if any.
+ if (*presented_name.rbegin() == '.')
+ presented_name.resize(presented_name.length() - 1);
+
+ // The hostname must be at least as long as the cert name it is matching,
+ // as we require the wildcard (if present) to match at least one character.
+ if (presented_name.length() > reference_name.length())
+ continue;
+
+ base::StringPiece presented_host, presented_domain;
+ SplitOnChar(presented_name, '.', &presented_host, &presented_domain);
+
+ if (presented_domain != reference_domain)
+ continue;
+
+ base::StringPiece pattern_begin, pattern_end;
+ SplitOnChar(presented_host, '*', &pattern_begin, &pattern_end);
+
+ if (pattern_end.empty()) { // No '*' in the presented_host
+ if (presented_host == reference_host)
+ return true;
+ continue;
+ }
+ pattern_end.remove_prefix(1); // move past the *
+
+ if (!allow_wildcards)
+ continue;
+
+ // * must not match a substring of an IDN A label; just a whole fragment.
+ if (reference_host.starts_with("xn--") &&
+ !(pattern_begin.empty() && pattern_end.empty()))
+ continue;
+
+ if (reference_host.starts_with(pattern_begin) &&
+ reference_host.ends_with(pattern_end))
+ return true;
+ }
+ return false;
+}
+
+bool X509Certificate::VerifyNameMatch(const std::string& hostname) const {
+ std::vector<std::string> dns_names, ip_addrs;
+ GetSubjectAltName(&dns_names, &ip_addrs);
+ return VerifyHostname(hostname, subject_.common_name, dns_names, ip_addrs);
+}
+
+// static
+bool X509Certificate::GetPEMEncodedFromDER(const std::string& der_encoded,
+ std::string* pem_encoded) {
+ if (der_encoded.empty())
+ return false;
+ std::string b64_encoded;
+ if (!base::Base64Encode(der_encoded, &b64_encoded) || b64_encoded.empty())
+ return false;
+ *pem_encoded = "-----BEGIN CERTIFICATE-----\n";
+
+ // Divide the Base-64 encoded data into 64-character chunks, as per
+ // 4.3.2.4 of RFC 1421.
+ static const size_t kChunkSize = 64;
+ size_t chunks = (b64_encoded.size() + (kChunkSize - 1)) / kChunkSize;
+ for (size_t i = 0, chunk_offset = 0; i < chunks;
+ ++i, chunk_offset += kChunkSize) {
+ pem_encoded->append(b64_encoded, chunk_offset, kChunkSize);
+ pem_encoded->append("\n");
+ }
+ pem_encoded->append("-----END CERTIFICATE-----\n");
+ return true;
+}
+
+// static
+bool X509Certificate::GetPEMEncoded(OSCertHandle cert_handle,
+ std::string* pem_encoded) {
+ std::string der_encoded;
+ if (!GetDEREncoded(cert_handle, &der_encoded))
+ return false;
+ return GetPEMEncodedFromDER(der_encoded, pem_encoded);
+}
+
+bool X509Certificate::GetPEMEncodedChain(
+ std::vector<std::string>* pem_encoded) const {
+ std::vector<std::string> encoded_chain;
+ std::string pem_data;
+ if (!GetPEMEncoded(os_cert_handle(), &pem_data))
+ return false;
+ encoded_chain.push_back(pem_data);
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) {
+ if (!GetPEMEncoded(intermediate_ca_certs_[i], &pem_data))
+ return false;
+ encoded_chain.push_back(pem_data);
+ }
+ pem_encoded->swap(encoded_chain);
+ return true;
+}
+
+X509Certificate::X509Certificate(OSCertHandle cert_handle,
+ const OSCertHandles& intermediates)
+ : cert_handle_(DupOSCertHandle(cert_handle)) {
+ InsertOrUpdateCache(&cert_handle_);
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ // Duplicate the incoming certificate, as the caller retains ownership
+ // of |intermediates|.
+ OSCertHandle intermediate = DupOSCertHandle(intermediates[i]);
+ // Update the cache, which will assume ownership of the duplicated
+ // handle and return a suitable equivalent, potentially from the cache.
+ InsertOrUpdateCache(&intermediate);
+ intermediate_ca_certs_.push_back(intermediate);
+ }
+ // Platform-specific initialization.
+ Initialize();
+}
+
+X509Certificate::~X509Certificate() {
+ if (cert_handle_) {
+ RemoveFromCache(cert_handle_);
+ FreeOSCertHandle(cert_handle_);
+ }
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) {
+ RemoveFromCache(intermediate_ca_certs_[i]);
+ FreeOSCertHandle(intermediate_ca_certs_[i]);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate.h b/chromium/net/cert/x509_certificate.h
new file mode 100644
index 00000000000..ef552431729
--- /dev/null
+++ b/chromium/net/cert/x509_certificate.h
@@ -0,0 +1,487 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_X509_CERTIFICATE_H_
+#define NET_CERT_X509_CERTIFICATE_H_
+
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_type.h"
+#include "net/cert/x509_cert_types.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#elif defined(OS_MACOSX)
+#include <CoreFoundation/CFArray.h>
+#include <Security/SecBase.h>
+
+#elif defined(USE_OPENSSL)
+// Forward declaration; real one in <x509.h>
+typedef struct x509_st X509;
+typedef struct x509_store_st X509_STORE;
+#elif defined(USE_NSS)
+// Forward declaration; real one in <cert.h>
+struct CERTCertificateStr;
+#endif
+
+class Pickle;
+class PickleIterator;
+
+namespace net {
+
+class CRLSet;
+class CertVerifyResult;
+
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+
+// X509Certificate represents a X.509 certificate, which is comprised a
+// particular identity or end-entity certificate, such as an SSL server
+// identity or an SSL client certificate, and zero or more intermediate
+// certificates that may be used to build a path to a root certificate.
+class NET_EXPORT X509Certificate
+ : public base::RefCountedThreadSafe<X509Certificate> {
+ public:
+ // An OSCertHandle is a handle to a certificate object in the underlying
+ // crypto library. We assume that OSCertHandle is a pointer type on all
+ // platforms and that NULL represents an invalid OSCertHandle.
+#if defined(OS_WIN)
+ typedef PCCERT_CONTEXT OSCertHandle;
+#elif defined(OS_MACOSX)
+ typedef SecCertificateRef OSCertHandle;
+#elif defined(USE_OPENSSL)
+ typedef X509* OSCertHandle;
+#elif defined(USE_NSS)
+ typedef struct CERTCertificateStr* OSCertHandle;
+#else
+ // TODO(ericroman): not implemented
+ typedef void* OSCertHandle;
+#endif
+
+ typedef std::vector<OSCertHandle> OSCertHandles;
+
+ enum PublicKeyType {
+ kPublicKeyTypeUnknown,
+ kPublicKeyTypeRSA,
+ kPublicKeyTypeDSA,
+ kPublicKeyTypeECDSA,
+ kPublicKeyTypeDH,
+ kPublicKeyTypeECDH
+ };
+
+ // Predicate functor used in maps when X509Certificate is used as the key.
+ class NET_EXPORT LessThan {
+ public:
+ bool operator()(const scoped_refptr<X509Certificate>& lhs,
+ const scoped_refptr<X509Certificate>& rhs) const;
+ };
+
+ enum Format {
+ // The data contains a single DER-encoded certificate, or a PEM-encoded
+ // DER certificate with the PEM encoding block name of "CERTIFICATE".
+ // Any subsequent blocks will be ignored.
+ FORMAT_SINGLE_CERTIFICATE = 1 << 0,
+
+ // The data contains a sequence of one or more PEM-encoded, DER
+ // certificates, with the PEM encoding block name of "CERTIFICATE".
+ // All PEM blocks will be parsed, until the first error is encountered.
+ FORMAT_PEM_CERT_SEQUENCE = 1 << 1,
+
+ // The data contains a PKCS#7 SignedData structure, whose certificates
+ // member is to be used to initialize the certificate and intermediates.
+ // The data may further be encoded using PEM, specifying block names of
+ // either "PKCS7" or "CERTIFICATE".
+ FORMAT_PKCS7 = 1 << 2,
+
+ // Automatically detect the format.
+ FORMAT_AUTO = FORMAT_SINGLE_CERTIFICATE | FORMAT_PEM_CERT_SEQUENCE |
+ FORMAT_PKCS7,
+ };
+
+ // PickleType is intended for deserializing certificates that were pickled
+ // by previous releases as part of a net::HttpResponseInfo.
+ // When serializing certificates to a new Pickle,
+ // PICKLETYPE_CERTIFICATE_CHAIN_V3 is always used.
+ enum PickleType {
+ // When reading a certificate from a Pickle, the Pickle only contains a
+ // single certificate.
+ PICKLETYPE_SINGLE_CERTIFICATE,
+
+ // When reading a certificate from a Pickle, the Pickle contains the
+ // the certificate plus any certificates that were stored in
+ // |intermediate_ca_certificates_| at the time it was serialized.
+ // The count of certificates is stored as a size_t, which is either 32
+ // or 64 bits.
+ PICKLETYPE_CERTIFICATE_CHAIN_V2,
+
+ // The Pickle contains the certificate and any certificates that were
+ // stored in |intermediate_ca_certs_| at the time it was serialized.
+ // The format is [int count], [data - this certificate],
+ // [data - intermediate1], ... [data - intermediateN].
+ // All certificates are stored in DER form.
+ PICKLETYPE_CERTIFICATE_CHAIN_V3,
+ };
+
+ // Creates a X509Certificate from the ground up. Used by tests that simulate
+ // SSL connections.
+ X509Certificate(const std::string& subject, const std::string& issuer,
+ base::Time start_date, base::Time expiration_date);
+
+ // Create an X509Certificate from a handle to the certificate object in the
+ // underlying crypto library. The returned pointer must be stored in a
+ // scoped_refptr<X509Certificate>.
+ static X509Certificate* CreateFromHandle(OSCertHandle cert_handle,
+ const OSCertHandles& intermediates);
+
+ // Create an X509Certificate from a chain of DER encoded certificates. The
+ // first certificate in the chain is the end-entity certificate to which a
+ // handle is returned. The other certificates in the chain are intermediate
+ // certificates. The returned pointer must be stored in a
+ // scoped_refptr<X509Certificate>.
+ static X509Certificate* CreateFromDERCertChain(
+ const std::vector<base::StringPiece>& der_certs);
+
+ // Create an X509Certificate from the DER-encoded representation.
+ // Returns NULL on failure.
+ //
+ // The returned pointer must be stored in a scoped_refptr<X509Certificate>.
+ static X509Certificate* CreateFromBytes(const char* data, int length);
+
+#if defined(USE_NSS)
+ // Create an X509Certificate from the DER-encoded representation.
+ // |nickname| can be NULL if an auto-generated nickname is desired.
+ // Returns NULL on failure. The returned pointer must be stored in a
+ // scoped_refptr<X509Certificate>.
+ //
+ // This function differs from CreateFromBytes in that it takes a
+ // nickname that will be used when the certificate is imported into PKCS#11.
+ static X509Certificate* CreateFromBytesWithNickname(const char* data,
+ int length,
+ const char* nickname);
+
+ // The default nickname of the certificate, based on the certificate type
+ // passed in. If this object was created using CreateFromBytesWithNickname,
+ // then this will return the nickname specified upon creation.
+ std::string GetDefaultNickname(CertType type) const;
+#endif
+
+ // Create an X509Certificate from the representation stored in the given
+ // pickle. The data for this object is found relative to the given
+ // pickle_iter, which should be passed to the pickle's various Read* methods.
+ // Returns NULL on failure.
+ //
+ // The returned pointer must be stored in a scoped_refptr<X509Certificate>.
+ static X509Certificate* CreateFromPickle(const Pickle& pickle,
+ PickleIterator* pickle_iter,
+ PickleType type);
+
+ // Parses all of the certificates possible from |data|. |format| is a
+ // bit-wise OR of Format, indicating the possible formats the
+ // certificates may have been serialized as. If an error occurs, an empty
+ // collection will be returned.
+ static CertificateList CreateCertificateListFromBytes(const char* data,
+ int length,
+ int format);
+
+ // Appends a representation of this object to the given pickle.
+ void Persist(Pickle* pickle);
+
+ // The serial number, DER encoded, possibly including a leading 00 byte.
+ const std::string& serial_number() const { return serial_number_; }
+
+ // The subject of the certificate. For HTTPS server certificates, this
+ // represents the web server. The common name of the subject should match
+ // the host name of the web server.
+ const CertPrincipal& subject() const { return subject_; }
+
+ // The issuer of the certificate.
+ const CertPrincipal& issuer() const { return issuer_; }
+
+ // Time period during which the certificate is valid. More precisely, this
+ // certificate is invalid before the |valid_start| date and invalid after
+ // the |valid_expiry| date.
+ // If we were unable to parse either date from the certificate (or if the cert
+ // lacks either date), the date will be null (i.e., is_null() will be true).
+ const base::Time& valid_start() const { return valid_start_; }
+ const base::Time& valid_expiry() const { return valid_expiry_; }
+
+ // The fingerprint of this certificate.
+ const SHA1HashValue& fingerprint() const { return fingerprint_; }
+
+ // The fingerprint of the intermediate CA certificates.
+ const SHA1HashValue& ca_fingerprint() const {
+ return ca_fingerprint_;
+ }
+
+ // Gets the DNS names in the certificate. Pursuant to RFC 2818, Section 3.1
+ // Server Identity, if the certificate has a subjectAltName extension of
+ // type dNSName, this method gets the DNS names in that extension.
+ // Otherwise, it gets the common name in the subject field.
+ void GetDNSNames(std::vector<std::string>* dns_names) const;
+
+ // Gets the subjectAltName extension field from the certificate, if any.
+ // For future extension; currently this only returns those name types that
+ // are required for HTTP certificate name verification - see VerifyHostname.
+ // Unrequired parameters may be passed as NULL.
+ void GetSubjectAltName(std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const;
+
+ // Convenience method that returns whether this certificate has expired as of
+ // now.
+ bool HasExpired() const;
+
+ // Returns true if this object and |other| represent the same certificate.
+ bool Equals(const X509Certificate* other) const;
+
+ // Returns intermediate certificates added via AddIntermediateCertificate().
+ // Ownership follows the "get" rule: it is the caller's responsibility to
+ // retain the elements of the result.
+ const OSCertHandles& GetIntermediateCertificates() const {
+ return intermediate_ca_certs_;
+ }
+
+#if defined(OS_MACOSX)
+ // Does this certificate's usage allow SSL client authentication?
+ bool SupportsSSLClientAuth() const;
+
+ // Returns a new CFArrayRef containing this certificate and its intermediate
+ // certificates in the form expected by Security.framework and Keychain
+ // Services, or NULL on failure.
+ // The first item in the array will be this certificate, followed by its
+ // intermediates, if any.
+ CFArrayRef CreateOSCertChainForCert() const;
+#endif
+
+ // Do any of the given issuer names appear in this cert's chain of trust?
+ // |valid_issuers| is a list of DER-encoded X.509 DistinguishedNames.
+ bool IsIssuedByEncoded(const std::vector<std::string>& valid_issuers);
+
+#if defined(OS_WIN)
+ // Returns a new PCCERT_CONTEXT containing this certificate and its
+ // intermediate certificates, or NULL on failure. The returned
+ // PCCERT_CONTEXT *MUST NOT* be stored in an X509Certificate, as this will
+ // cause os_cert_handle() to return incorrect results. This function is only
+ // necessary if the CERT_CONTEXT.hCertStore member will be accessed or
+ // enumerated, which is generally true for any CryptoAPI functions involving
+ // certificate chains, including validation or certificate display.
+ //
+ // Remarks:
+ // Depending on the CryptoAPI function, Windows may need to access the
+ // HCERTSTORE that the passed-in PCCERT_CONTEXT belongs to, such as to
+ // locate additional intermediates. However, all certificate handles are added
+ // to a NULL HCERTSTORE, allowing the system to manage the resources. As a
+ // result, intermediates for |cert_handle_| cannot be located simply via
+ // |cert_handle_->hCertStore|, as it refers to a magic value indicating
+ // "only this certificate".
+ //
+ // To avoid this problems, a new in-memory HCERTSTORE is created containing
+ // just this certificate and its intermediates. The handle to the version of
+ // the current certificate in the new HCERTSTORE is then returned, with the
+ // PCCERT_CONTEXT's HCERTSTORE set to be automatically freed when the returned
+ // certificate handle is freed.
+ //
+ // This function is only needed when the HCERTSTORE of the os_cert_handle()
+ // will be accessed, which is generally only during certificate validation
+ // or display. While the returned PCCERT_CONTEXT and its HCERTSTORE can
+ // safely be used on multiple threads if no further modifications happen, it
+ // is generally preferable for each thread that needs such a context to
+ // obtain its own, rather than risk thread-safety issues by sharing.
+ //
+ // Because of how X509Certificate caching is implemented, attempting to
+ // create an X509Certificate from the returned PCCERT_CONTEXT may result in
+ // the original handle (and thus the originall HCERTSTORE) being returned by
+ // os_cert_handle(). For this reason, the returned PCCERT_CONTEXT *MUST NOT*
+ // be stored in an X509Certificate.
+ PCCERT_CONTEXT CreateOSCertChainForCert() const;
+#endif
+
+#if defined(USE_OPENSSL)
+ // Returns a handle to a global, in-memory certificate store. We
+ // use it for test code, e.g. importing the test server's certificate.
+ static X509_STORE* cert_store();
+#endif
+
+ // Verifies that |hostname| matches this certificate.
+ // Does not verify that the certificate is valid, only that the certificate
+ // matches this host.
+ // Returns true if it matches.
+ bool VerifyNameMatch(const std::string& hostname) const;
+
+ // Obtains the DER encoded certificate data for |cert_handle|. On success,
+ // returns true and writes the DER encoded certificate to |*der_encoded|.
+ static bool GetDEREncoded(OSCertHandle cert_handle,
+ std::string* der_encoded);
+
+ // Returns the PEM encoded data from a DER encoded certificate. If the return
+ // value is true, then the PEM encoded certificate is written to
+ // |pem_encoded|.
+ static bool GetPEMEncodedFromDER(const std::string& der_encoded,
+ std::string* pem_encoded);
+
+ // Returns the PEM encoded data from an OSCertHandle. If the return value is
+ // true, then the PEM encoded certificate is written to |pem_encoded|.
+ static bool GetPEMEncoded(OSCertHandle cert_handle,
+ std::string* pem_encoded);
+
+ // Encodes the entire certificate chain (this certificate and any
+ // intermediate certificates stored in |intermediate_ca_certs_|) as a series
+ // of PEM encoded strings. Returns true if all certificates were encoded,
+ // storig the result in |*pem_encoded|, with this certificate stored as
+ // the first element.
+ bool GetPEMEncodedChain(std::vector<std::string>* pem_encoded) const;
+
+ // Sets |*size_bits| to be the length of the public key in bits, and sets
+ // |*type| to one of the |PublicKeyType| values. In case of
+ // |kPublicKeyTypeUnknown|, |*size_bits| will be set to 0.
+ static void GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type);
+
+ // Returns the OSCertHandle of this object. Because of caching, this may
+ // differ from the OSCertHandle originally supplied during initialization.
+ // Note: On Windows, CryptoAPI may return unexpected results if this handle
+ // is used across multiple threads. For more details, see
+ // CreateOSCertChainForCert().
+ OSCertHandle os_cert_handle() const { return cert_handle_; }
+
+ // Returns true if two OSCertHandles refer to identical certificates.
+ static bool IsSameOSCert(OSCertHandle a, OSCertHandle b);
+
+ // Creates an OS certificate handle from the DER-encoded representation.
+ // Returns NULL on failure.
+ static OSCertHandle CreateOSCertHandleFromBytes(const char* data,
+ int length);
+
+#if defined(USE_NSS)
+ // Creates an OS certificate handle from the DER-encoded representation.
+ // Returns NULL on failure. Sets the default nickname if |nickname| is
+ // non-NULL.
+ static OSCertHandle CreateOSCertHandleFromBytesWithNickname(
+ const char* data,
+ int length,
+ const char* nickname);
+#endif
+
+ // Creates all possible OS certificate handles from |data| encoded in a
+ // specific |format|. Returns an empty collection on failure.
+ static OSCertHandles CreateOSCertHandlesFromBytes(
+ const char* data,
+ int length,
+ Format format);
+
+ // Duplicates (or adds a reference to) an OS certificate handle.
+ static OSCertHandle DupOSCertHandle(OSCertHandle cert_handle);
+
+ // Frees (or releases a reference to) an OS certificate handle.
+ static void FreeOSCertHandle(OSCertHandle cert_handle);
+
+ // Calculates the SHA-1 fingerprint of the certificate. Returns an empty
+ // (all zero) fingerprint on failure.
+ static SHA1HashValue CalculateFingerprint(OSCertHandle cert_handle);
+
+ // Calculates the SHA-1 fingerprint of the intermediate CA certificates.
+ // Returns an empty (all zero) fingerprint on failure.
+ static SHA1HashValue CalculateCAFingerprint(
+ const OSCertHandles& intermediates);
+
+ private:
+ friend class base::RefCountedThreadSafe<X509Certificate>;
+ friend class TestRootCerts; // For unit tests
+
+ FRIEND_TEST_ALL_PREFIXES(X509CertificateNameVerifyTest, VerifyHostname);
+ FRIEND_TEST_ALL_PREFIXES(X509CertificateTest, SerialNumbers);
+
+ // Construct an X509Certificate from a handle to the certificate object
+ // in the underlying crypto library.
+ X509Certificate(OSCertHandle cert_handle,
+ const OSCertHandles& intermediates);
+
+ ~X509Certificate();
+
+ // Common object initialization code. Called by the constructors only.
+ void Initialize();
+
+#if defined(USE_OPENSSL)
+ // Resets the store returned by cert_store() to default state. Used by
+ // TestRootCerts to undo modifications.
+ static void ResetCertStore();
+#endif
+
+ // Verifies that |hostname| matches one of the certificate names or IP
+ // addresses supplied, based on TLS name matching rules - specifically,
+ // following http://tools.ietf.org/html/rfc6125.
+ // |cert_common_name| is the Subject CN, e.g. from X509Certificate::subject().
+ // The members of |cert_san_dns_names| and |cert_san_ipaddrs| must be filled
+ // from the dNSName and iPAddress components of the subject alternative name
+ // extension, if present. Note these IP addresses are NOT ascii-encoded:
+ // they must be 4 or 16 bytes of network-ordered data, for IPv4 and IPv6
+ // addresses, respectively.
+ static bool VerifyHostname(const std::string& hostname,
+ const std::string& cert_common_name,
+ const std::vector<std::string>& cert_san_dns_names,
+ const std::vector<std::string>& cert_san_ip_addrs);
+
+ // Reads a single certificate from |pickle_iter| and returns a
+ // platform-specific certificate handle. The format of the certificate
+ // stored in |pickle_iter| is not guaranteed to be the same across different
+ // underlying cryptographic libraries, nor acceptable to CreateFromBytes().
+ // Returns an invalid handle, NULL, on failure.
+ // NOTE: This should not be used for any new code. It is provided for
+ // migration purposes and should eventually be removed.
+ static OSCertHandle ReadOSCertHandleFromPickle(PickleIterator* pickle_iter);
+
+ // Writes a single certificate to |pickle| in DER form. Returns false on
+ // failure.
+ static bool WriteOSCertHandleToPickle(OSCertHandle handle, Pickle* pickle);
+
+ // The subject of the certificate.
+ CertPrincipal subject_;
+
+ // The issuer of the certificate.
+ CertPrincipal issuer_;
+
+ // This certificate is not valid before |valid_start_|
+ base::Time valid_start_;
+
+ // This certificate is not valid after |valid_expiry_|
+ base::Time valid_expiry_;
+
+ // The fingerprint of this certificate.
+ SHA1HashValue fingerprint_;
+
+ // The fingerprint of the intermediate CA certificates.
+ SHA1HashValue ca_fingerprint_;
+
+ // The serial number of this certificate, DER encoded.
+ std::string serial_number_;
+
+ // A handle to the certificate object in the underlying crypto library.
+ OSCertHandle cert_handle_;
+
+ // Untrusted intermediate certificates associated with this certificate
+ // that may be needed for chain building.
+ OSCertHandles intermediate_ca_certs_;
+
+#if defined(USE_NSS)
+ // This stores any default nickname that has been set on the certificate
+ // at creation time with CreateFromBytesWithNickname.
+ // If this is empty, then GetDefaultNickname will return a generated name
+ // based on the type of the certificate.
+ std::string default_nickname_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(X509Certificate);
+};
+
+} // namespace net
+
+#endif // NET_CERT_X509_CERTIFICATE_H_
diff --git a/chromium/net/cert/x509_certificate_ios.cc b/chromium/net/cert/x509_certificate_ios.cc
new file mode 100644
index 00000000000..48f833f2ec7
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_ios.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <CommonCrypto/CommonDigest.h>
+#include <Security/Security.h>
+
+#include <cert.h>
+#include <cryptohi.h>
+#include <keyhi.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <prerror.h>
+#include <prtime.h>
+#include <prtypes.h>
+#include <secder.h>
+#include <secerr.h>
+#include <sslerr.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/time/time.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/cert/x509_util_ios.h"
+#include "net/cert/x509_util_nss.h"
+
+using base::ScopedCFTypeRef;
+
+namespace net {
+namespace {
+// Returns true if a given |cert_handle| is actually a valid X.509 certificate
+// handle.
+//
+// SecCertificateCreateFromData() does not always force the immediate parsing of
+// the certificate, and as such, may return a SecCertificateRef for an
+// invalid/unparsable certificate. Force parsing to occur to ensure that the
+// SecCertificateRef is correct. On later versions where
+// SecCertificateCreateFromData() immediately parses, rather than lazily, this
+// call is cheap, as the subject is cached.
+bool IsValidOSCertHandle(SecCertificateRef cert_handle) {
+ ScopedCFTypeRef<CFStringRef> sanity_check(
+ SecCertificateCopySubjectSummary(cert_handle));
+ return sanity_check != NULL;
+}
+} // namespace
+
+void X509Certificate::Initialize() {
+ x509_util_ios::NSSCertificate nss_cert(cert_handle_);
+ CERTCertificate* cert_handle = nss_cert.cert_handle();
+ if (cert_handle) {
+ x509_util::ParsePrincipal(&cert_handle->subject, &subject_);
+ x509_util::ParsePrincipal(&cert_handle->issuer, &issuer_);
+ x509_util::ParseDate(&cert_handle->validity.notBefore, &valid_start_);
+ x509_util::ParseDate(&cert_handle->validity.notAfter, &valid_expiry_);
+ serial_number_ = x509_util::ParseSerialNumber(cert_handle);
+ }
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+ ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_);
+}
+
+bool X509Certificate::IsIssuedByEncoded(
+ const std::vector<std::string>& valid_issuers) {
+ x509_util_ios::NSSCertChain nss_chain(this);
+ // Convert to scoped CERTName* list.
+ std::vector<CERTName*> issuers;
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!x509_util::GetIssuersFromEncodedList(valid_issuers,
+ arena.get(),
+ &issuers)) {
+ return false;
+ }
+ return x509_util::IsCertificateIssuedBy(
+ nss_chain.cert_chain(), issuers);
+}
+
+void X509Certificate::GetSubjectAltName(
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const {
+ x509_util_ios::NSSCertificate nss_cert(cert_handle_);
+ CERTCertificate* cert_handle = nss_cert.cert_handle();
+ if (!cert_handle) {
+ if (dns_names)
+ dns_names->clear();
+ if (ip_addrs)
+ ip_addrs->clear();
+ return;
+ }
+ x509_util::GetSubjectAltName(cert_handle, dns_names, ip_addrs);
+}
+
+// static
+bool X509Certificate::GetDEREncoded(OSCertHandle cert_handle,
+ std::string* encoded) {
+ ScopedCFTypeRef<CFDataRef> der_data(SecCertificateCopyData(cert_handle));
+ if (!der_data)
+ return false;
+ encoded->assign(reinterpret_cast<const char*>(CFDataGetBytePtr(der_data)),
+ CFDataGetLength(der_data));
+ return true;
+}
+
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+ if (CFEqual(a, b))
+ return true;
+ ScopedCFTypeRef<CFDataRef> a_data(SecCertificateCopyData(a));
+ ScopedCFTypeRef<CFDataRef> b_data(SecCertificateCopyData(b));
+ return a_data && b_data &&
+ CFDataGetLength(a_data) == CFDataGetLength(b_data) &&
+ memcmp(CFDataGetBytePtr(a_data), CFDataGetBytePtr(b_data),
+ CFDataGetLength(a_data)) == 0;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
+ const char* data, int length) {
+ ScopedCFTypeRef<CFDataRef> cert_data(CFDataCreateWithBytesNoCopy(
+ kCFAllocatorDefault, reinterpret_cast<const UInt8 *>(data), length,
+ kCFAllocatorNull));
+ if (!cert_data)
+ return NULL;
+ OSCertHandle cert_handle = SecCertificateCreateWithData(NULL, cert_data);
+ if (!cert_handle)
+ return NULL;
+ if (!IsValidOSCertHandle(cert_handle)) {
+ CFRelease(cert_handle);
+ return NULL;
+ }
+ return cert_handle;
+}
+
+// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data,
+ int length,
+ Format format) {
+ return x509_util::CreateOSCertHandlesFromBytes(data, length, format);
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle handle) {
+ if (!handle)
+ return NULL;
+ return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle)));
+}
+
+// static
+void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
+ CFRelease(cert_handle);
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateFingerprint(
+ OSCertHandle cert) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert));
+ if (!cert_data)
+ return sha1;
+ DCHECK(CFDataGetBytePtr(cert_data));
+ DCHECK_NE(0, CFDataGetLength(cert_data));
+ CC_SHA1(CFDataGetBytePtr(cert_data), CFDataGetLength(cert_data), sha1.data);
+
+ return sha1;
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateCAFingerprint(
+ const OSCertHandles& intermediates) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so
+ // we don't check their return values.
+ CC_SHA1_CTX sha1_ctx;
+ CC_SHA1_Init(&sha1_ctx);
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ ScopedCFTypeRef<CFDataRef>
+ cert_data(SecCertificateCopyData(intermediates[i]));
+ if (!cert_data)
+ return sha1;
+ CC_SHA1_Update(&sha1_ctx,
+ CFDataGetBytePtr(cert_data),
+ CFDataGetLength(cert_data));
+ }
+ CC_SHA1_Final(sha1.data, &sha1_ctx);
+ return sha1;
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) {
+ return x509_util::ReadOSCertHandleFromPickle(pickle_iter);
+}
+
+// static
+bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle,
+ Pickle* pickle) {
+ ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle));
+ if (!cert_data)
+ return false;
+
+ return pickle->WriteData(
+ reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)),
+ CFDataGetLength(cert_data));
+}
+
+// static
+void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type) {
+ x509_util_ios::NSSCertificate nss_cert(cert_handle);
+ x509_util::GetPublicKeyInfo(nss_cert.cert_handle(), size_bits, type);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_known_roots_mac.h b/chromium/net/cert/x509_certificate_known_roots_mac.h
new file mode 100644
index 00000000000..c4375f0f61c
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_known_roots_mac.h
@@ -0,0 +1,433 @@
+// Copyright (c) 2011 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_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_
+#define NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_
+
+// This is the set of Apple trusted roots from OS X 10.8.3, revision 55137.5
+// and available at:
+// http://opensource.apple.com/source/securityd/securityd-55137.5/
+//
+// Note that these *are not* trust anchors for Chromium. They are only used to
+// distinguish `real' root CAs from roots that were user-installed.
+static uint8 kKnownRootCertSHA1Hashes[][20] = {
+ {0x01, 0x68, 0x97, 0xe1, 0xa0, 0xb8, 0xf2, 0xc3, 0xb1, 0x34,
+ 0x66, 0x5c, 0x20, 0xa7, 0x27, 0xb7, 0xa1, 0x58, 0xe2, 0x8f},
+ {0x02, 0x72, 0x68, 0x29, 0x3e, 0x5f, 0x5d, 0x17, 0xaa, 0xa4,
+ 0xb3, 0xc3, 0xe6, 0x36, 0x1e, 0x1f, 0x92, 0x57, 0x5e, 0xaa},
+ {0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78,
+ 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68},
+ {0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87,
+ 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7},
+ {0x05, 0x63, 0xb8, 0x63, 0x0d, 0x62, 0xd7, 0x5a, 0xbb, 0xc8,
+ 0xab, 0x1e, 0x4b, 0xdf, 0xb5, 0xa8, 0x99, 0xb2, 0x4d, 0x43},
+ {0x06, 0x08, 0x3f, 0x59, 0x3f, 0x15, 0xa1, 0x04, 0xa0, 0x69,
+ 0xa4, 0x6b, 0xa9, 0x03, 0xd0, 0x06, 0xb7, 0x97, 0x09, 0x91},
+ {0x07, 0x47, 0x22, 0x01, 0x99, 0xce, 0x74, 0xb9, 0x7c, 0xb0,
+ 0x3d, 0x79, 0xb2, 0x64, 0xa2, 0xc8, 0x55, 0xe9, 0x33, 0xff},
+ {0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f,
+ 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e},
+ {0x08, 0x64, 0x18, 0xe9, 0x06, 0xce, 0xe8, 0x9c, 0x23, 0x53,
+ 0xb6, 0xe2, 0x7f, 0xbd, 0x9e, 0x74, 0x39, 0xf7, 0x63, 0x16},
+ {0x0b, 0x77, 0xbe, 0xbb, 0xcb, 0x7a, 0xa2, 0x47, 0x05, 0xde,
+ 0xcc, 0x0f, 0xbd, 0x6a, 0x02, 0xfc, 0x7a, 0xbd, 0x9b, 0x52},
+ {0x0b, 0x97, 0x2c, 0x9e, 0xa6, 0xe7, 0xcc, 0x58, 0xd9, 0x3b,
+ 0x20, 0xbf, 0x71, 0xec, 0x41, 0x2e, 0x72, 0x09, 0xfa, 0xbf},
+ {0x0c, 0x08, 0xb8, 0x4a, 0x1f, 0x26, 0xf5, 0x05, 0x49, 0xda,
+ 0xab, 0x36, 0x33, 0x08, 0xd3, 0x56, 0x52, 0x68, 0xf1, 0x60},
+ {0x10, 0x1d, 0xfa, 0x3f, 0xd5, 0x0b, 0xcb, 0xbb, 0x9b, 0xb5,
+ 0x60, 0x0c, 0x19, 0x55, 0xa4, 0x1a, 0xf4, 0x73, 0x3a, 0x04},
+ {0x10, 0xf1, 0x93, 0xf3, 0x40, 0xac, 0x91, 0xd6, 0xde, 0x5f,
+ 0x1e, 0xdc, 0x00, 0x62, 0x47, 0xc4, 0xf2, 0x5d, 0x96, 0x71},
+ {0x13, 0x2d, 0x0d, 0x45, 0x53, 0x4b, 0x69, 0x97, 0xcd, 0xb2,
+ 0xd5, 0xc3, 0x39, 0xe2, 0x55, 0x76, 0x60, 0x9b, 0x5c, 0xc6},
+ {0x17, 0xc0, 0xc5, 0x9a, 0xb5, 0xd8, 0xd5, 0x85, 0x20, 0x43,
+ 0xe8, 0xec, 0x69, 0x2c, 0x40, 0x9d, 0x80, 0x62, 0xaa, 0x53},
+ {0x1b, 0x4b, 0x39, 0x61, 0x26, 0x27, 0x6b, 0x64, 0x91, 0xa2,
+ 0x68, 0x6d, 0xd7, 0x02, 0x43, 0x21, 0x2d, 0x1f, 0x1d, 0x96},
+ {0x1f, 0x49, 0x14, 0xf7, 0xd8, 0x74, 0x95, 0x1d, 0xdd, 0xae,
+ 0x02, 0xc0, 0xbe, 0xfd, 0x3a, 0x2d, 0x82, 0x75, 0x51, 0x85},
+ {0x20, 0x42, 0x85, 0xdc, 0xf7, 0xeb, 0x76, 0x41, 0x95, 0x57,
+ 0x8e, 0x13, 0x6b, 0xd4, 0xb7, 0xd1, 0xe9, 0x8e, 0x46, 0xa5},
+ {0x20, 0x99, 0x00, 0xb6, 0x3d, 0x95, 0x57, 0x28, 0x14, 0x0c,
+ 0xd1, 0x36, 0x22, 0xd8, 0xc6, 0x87, 0xa4, 0xeb, 0x00, 0x85},
+ {0x20, 0xce, 0xb1, 0xf0, 0xf5, 0x1c, 0x0e, 0x19, 0xa9, 0xf3,
+ 0x8d, 0xb1, 0xaa, 0x8e, 0x03, 0x8c, 0xaa, 0x7a, 0xc7, 0x01},
+ {0x21, 0xfc, 0xbd, 0x8e, 0x7f, 0x6c, 0xaf, 0x05, 0x1b, 0xd1,
+ 0xb3, 0x43, 0xec, 0xa8, 0xe7, 0x61, 0x47, 0xf2, 0x0f, 0x8a},
+ {0x23, 0xe5, 0x94, 0x94, 0x51, 0x95, 0xf2, 0x41, 0x48, 0x03,
+ 0xb4, 0xd5, 0x64, 0xd2, 0xa3, 0xa3, 0xf5, 0xd8, 0x8b, 0x8c},
+ {0x25, 0x01, 0x90, 0x19, 0xcf, 0xfb, 0xd9, 0x99, 0x1c, 0xb7,
+ 0x68, 0x25, 0x74, 0x8d, 0x94, 0x5f, 0x30, 0x93, 0x95, 0x42},
+ {0x25, 0x3f, 0x77, 0x5b, 0x0e, 0x77, 0x97, 0xab, 0x64, 0x5f,
+ 0x15, 0x91, 0x55, 0x97, 0xc3, 0x9e, 0x26, 0x36, 0x31, 0xd1},
+ {0x27, 0x0c, 0x50, 0x0c, 0xc6, 0xc8, 0x6e, 0xcb, 0x19, 0x80,
+ 0xbc, 0x13, 0x05, 0x43, 0x9e, 0xd2, 0x82, 0x48, 0x0b, 0xe3},
+ {0x27, 0x3e, 0xe1, 0x24, 0x57, 0xfd, 0xc4, 0xf9, 0x0c, 0x55,
+ 0xe8, 0x2b, 0x56, 0x16, 0x7f, 0x62, 0xf5, 0x32, 0xe5, 0x47},
+ {0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26,
+ 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4},
+ {0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66,
+ 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f},
+ {0x29, 0x64, 0xb6, 0x86, 0x13, 0x5b, 0x5d, 0xfd, 0xdd, 0x32,
+ 0x53, 0xa8, 0x9b, 0xbc, 0x24, 0xd7, 0x4b, 0x08, 0xc6, 0x4d},
+ {0x2a, 0xb6, 0x28, 0x48, 0x5e, 0x78, 0xfb, 0xf3, 0xad, 0x9e,
+ 0x79, 0x10, 0xdd, 0x6b, 0xdf, 0x99, 0x72, 0x2c, 0x96, 0xe5},
+ {0x2a, 0xc8, 0xd5, 0x8b, 0x57, 0xce, 0xbf, 0x2f, 0x49, 0xaf,
+ 0xf2, 0xfc, 0x76, 0x8f, 0x51, 0x14, 0x62, 0x90, 0x7a, 0x41},
+ {0x2b, 0x84, 0xbf, 0xbb, 0x34, 0xee, 0x2e, 0xf9, 0x49, 0xfe,
+ 0x1c, 0xbe, 0x30, 0xaa, 0x02, 0x64, 0x16, 0xeb, 0x22, 0x16},
+ {0x2c, 0xeb, 0x05, 0x34, 0xad, 0x59, 0x27, 0x18, 0x0d, 0x34,
+ 0x8c, 0x5f, 0x0e, 0x05, 0x6d, 0x38, 0x2b, 0x50, 0x82, 0x87},
+ {0x2d, 0xc7, 0xab, 0xf6, 0x7e, 0x9e, 0x63, 0x9a, 0x22, 0xdb,
+ 0xdf, 0x4e, 0xee, 0x9b, 0xc5, 0x2a, 0x48, 0xa2, 0xcc, 0x2d},
+ {0x2d, 0xff, 0x63, 0x36, 0xe3, 0x3a, 0x48, 0x29, 0xaa, 0x00,
+ 0x9f, 0x01, 0xa1, 0x80, 0x1e, 0xe7, 0xeb, 0xa5, 0x82, 0xbb},
+ {0x2e, 0xf6, 0x4b, 0xba, 0x77, 0xdd, 0x37, 0x79, 0xe9, 0x1f,
+ 0xbd, 0x5a, 0x4e, 0xee, 0x63, 0x3c, 0x8a, 0x36, 0xa5, 0xb1},
+ {0x2f, 0x17, 0x3f, 0x7d, 0xe9, 0x96, 0x67, 0xaf, 0xa5, 0x7a,
+ 0xf8, 0x0a, 0xa2, 0xd1, 0xb1, 0x2f, 0xac, 0x83, 0x03, 0x38},
+ {0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a,
+ 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd},
+ {0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1,
+ 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6},
+ {0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54,
+ 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96},
+ {0x33, 0x9b, 0x6b, 0x14, 0x50, 0x24, 0x9b, 0x55, 0x7a, 0x01,
+ 0x87, 0x72, 0x84, 0xd9, 0xe0, 0x2f, 0xc3, 0xd2, 0xd8, 0xe9},
+ {0x36, 0x7d, 0x4b, 0x3b, 0x4f, 0xcb, 0xbc, 0x0b, 0x76, 0x7b,
+ 0x2e, 0xc0, 0xcd, 0xb2, 0xa3, 0x6e, 0xab, 0x71, 0xa4, 0xeb},
+ {0x36, 0x86, 0x35, 0x63, 0xfd, 0x51, 0x28, 0xc7, 0xbe, 0xa6,
+ 0xf0, 0x05, 0xcf, 0xe9, 0xb4, 0x36, 0x68, 0x08, 0x6c, 0xce},
+ {0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e,
+ 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7},
+ {0x37, 0xf7, 0x6d, 0xe6, 0x07, 0x7c, 0x90, 0xc5, 0xb1, 0x3e,
+ 0x93, 0x1a, 0xb7, 0x41, 0x10, 0xb4, 0xf2, 0xe4, 0x9a, 0x27},
+ {0x39, 0x21, 0xc1, 0x15, 0xc1, 0x5d, 0x0e, 0xca, 0x5c, 0xcb,
+ 0x5b, 0xc4, 0xf0, 0x7d, 0x21, 0xd8, 0x05, 0x0b, 0x56, 0x6a},
+ {0x39, 0x4f, 0xf6, 0x85, 0x0b, 0x06, 0xbe, 0x52, 0xe5, 0x18,
+ 0x56, 0xcc, 0x10, 0xe1, 0x80, 0xe8, 0x82, 0xb3, 0x85, 0xcc},
+ {0x3a, 0x32, 0xef, 0x7b, 0x9a, 0xb8, 0x36, 0xf8, 0x37, 0x18,
+ 0x1a, 0x4c, 0xef, 0xa3, 0x55, 0xc6, 0x46, 0x67, 0xac, 0xbf},
+ {0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86,
+ 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b},
+ {0x3b, 0x16, 0x6c, 0x3b, 0x7d, 0xc4, 0xb7, 0x51, 0xc9, 0xfe,
+ 0x2a, 0xfa, 0xb9, 0x13, 0x56, 0x41, 0xe3, 0x88, 0xe1, 0x86},
+ {0x3b, 0xc0, 0x38, 0x0b, 0x33, 0xc3, 0xf6, 0xa6, 0x0c, 0x86,
+ 0x15, 0x22, 0x93, 0xd9, 0xdf, 0xf5, 0x4b, 0x81, 0xc0, 0x04},
+ {0x3b, 0xc4, 0x9f, 0x48, 0xf8, 0xf3, 0x73, 0xa0, 0x9c, 0x1e,
+ 0xbd, 0xf8, 0x5b, 0xb1, 0xc3, 0x65, 0xc7, 0xd8, 0x11, 0xb3},
+ {0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6,
+ 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f},
+ {0x3f, 0x01, 0x8e, 0x6f, 0xe3, 0x36, 0x09, 0x6a, 0x79, 0x1b,
+ 0x81, 0xe7, 0x69, 0xbe, 0x70, 0x1a, 0xaf, 0x21, 0xe3, 0x07},
+ {0x40, 0x54, 0xda, 0x6f, 0x1c, 0x3f, 0x40, 0x74, 0xac, 0xed,
+ 0x0f, 0xec, 0xcd, 0xdb, 0x79, 0xd1, 0x53, 0xfb, 0x90, 0x1d},
+ {0x40, 0x9d, 0x4b, 0xd9, 0x17, 0xb5, 0x5c, 0x27, 0xb6, 0x9b,
+ 0x64, 0xcb, 0x98, 0x22, 0x44, 0x0d, 0xcd, 0x09, 0xb8, 0x89},
+ {0x40, 0xaa, 0x38, 0x73, 0x1b, 0xd1, 0x89, 0xf9, 0xcd, 0xb5,
+ 0xb9, 0xdc, 0x35, 0xe2, 0x13, 0x6f, 0x38, 0x77, 0x7a, 0xf4},
+ {0x40, 0xe7, 0x8c, 0x1d, 0x52, 0x3d, 0x1c, 0xd9, 0x95, 0x4f,
+ 0xac, 0x1a, 0x1a, 0xb3, 0xbd, 0x3c, 0xba, 0xa1, 0x5b, 0xfc},
+ {0x42, 0xf8, 0x18, 0xe8, 0x33, 0x06, 0x3b, 0xf5, 0x16, 0xc6,
+ 0x61, 0x8c, 0x1e, 0x60, 0xfd, 0x0f, 0x35, 0xc4, 0x76, 0x21},
+ {0x43, 0xd9, 0xbc, 0xb5, 0x68, 0xe0, 0x39, 0xd0, 0x73, 0xa7,
+ 0x4a, 0x71, 0xd8, 0x51, 0x1f, 0x74, 0x76, 0x08, 0x9c, 0xc3},
+ {0x43, 0xf9, 0xb1, 0x10, 0xd5, 0xba, 0xfd, 0x48, 0x22, 0x52,
+ 0x31, 0xb0, 0xd0, 0x08, 0x2b, 0x37, 0x2f, 0xef, 0x9a, 0x54},
+ {0x47, 0xbe, 0xab, 0xc9, 0x22, 0xea, 0xe8, 0x0e, 0x78, 0x78,
+ 0x34, 0x62, 0xa7, 0x9f, 0x45, 0xc2, 0x54, 0xfd, 0xe6, 0x8b},
+ {0x4a, 0x3f, 0x8d, 0x6b, 0xdc, 0x0e, 0x1e, 0xcf, 0xcd, 0x72,
+ 0xe3, 0x77, 0xde, 0xf2, 0xd7, 0xff, 0x92, 0xc1, 0x9b, 0xc7},
+ {0x4a, 0x65, 0xd5, 0xf4, 0x1d, 0xef, 0x39, 0xb8, 0xb8, 0x90,
+ 0x4a, 0x4a, 0xd3, 0x64, 0x81, 0x33, 0xcf, 0xc7, 0xa1, 0xd1},
+ {0x4a, 0xd4, 0x4d, 0x4d, 0x81, 0x2e, 0x42, 0x23, 0x2f, 0xe0,
+ 0x38, 0x76, 0x4c, 0x7b, 0x0c, 0xeb, 0x46, 0x6e, 0xef, 0x96},
+ {0x4c, 0xab, 0x31, 0xa1, 0x28, 0x34, 0x02, 0x52, 0xbc, 0xb4,
+ 0x67, 0xd6, 0x2a, 0x99, 0x63, 0x1b, 0x21, 0x77, 0x20, 0x50},
+ {0x4d, 0x23, 0x78, 0xec, 0x91, 0x95, 0x39, 0xb5, 0x00, 0x7f,
+ 0x75, 0x8f, 0x03, 0x3b, 0x21, 0x1e, 0xc5, 0x4d, 0x8b, 0xcf},
+ {0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58,
+ 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5},
+ {0x4f, 0x99, 0xaa, 0x93, 0xfb, 0x2b, 0xd1, 0x37, 0x26, 0xa1,
+ 0x99, 0x4a, 0xce, 0x7f, 0xf0, 0x05, 0xf2, 0x93, 0x5d, 0x1e},
+ {0x50, 0x30, 0x06, 0x09, 0x1d, 0x97, 0xd4, 0xf5, 0xae, 0x39,
+ 0xf7, 0xcb, 0xe7, 0x92, 0x7d, 0x7d, 0x65, 0x2d, 0x34, 0x31},
+ {0x51, 0xa4, 0x4c, 0x28, 0xf3, 0x13, 0xe3, 0xf9, 0xcb, 0x5e,
+ 0x7c, 0x0a, 0x1e, 0x0e, 0x0d, 0xd2, 0x84, 0x37, 0x58, 0xae},
+ {0x51, 0xc3, 0x24, 0x7d, 0x60, 0xf3, 0x56, 0xc7, 0xca, 0x3b,
+ 0xaf, 0x4c, 0x3f, 0x42, 0x9d, 0xac, 0x93, 0xee, 0x7b, 0x74},
+ {0x51, 0xcc, 0xa0, 0x71, 0x0a, 0xf7, 0x73, 0x3d, 0x34, 0xac,
+ 0xdc, 0x19, 0x45, 0x09, 0x9f, 0x43, 0x5c, 0x7f, 0xc5, 0x9f},
+ {0x56, 0x4b, 0x6f, 0x8c, 0x56, 0x38, 0xdc, 0x05, 0x5b, 0xba,
+ 0x2b, 0xa1, 0x39, 0x0f, 0x7e, 0x31, 0x95, 0x4a, 0x55, 0x50},
+ {0x56, 0xe0, 0xfa, 0xc0, 0x3b, 0x8f, 0x18, 0x23, 0x55, 0x18,
+ 0xe5, 0xd3, 0x11, 0xca, 0xe8, 0xc2, 0x43, 0x31, 0xab, 0x66},
+ {0x57, 0xf0, 0x3d, 0xce, 0xfb, 0x45, 0x69, 0x4c, 0x1c, 0x25,
+ 0xe6, 0xee, 0xa0, 0x2c, 0x43, 0xd7, 0x52, 0x38, 0xd3, 0xc4},
+ {0x58, 0x0f, 0x80, 0x47, 0x92, 0xab, 0xc6, 0x3b, 0xbb, 0x80,
+ 0x15, 0x4d, 0x4d, 0xfd, 0xdd, 0x8b, 0x2e, 0xf2, 0x67, 0x4e},
+ {0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd,
+ 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4},
+ {0x59, 0x22, 0xa1, 0xe1, 0x5a, 0xea, 0x16, 0x35, 0x21, 0xf8,
+ 0x98, 0x39, 0x6a, 0x46, 0x46, 0xb0, 0x44, 0x1b, 0x0f, 0xa9},
+ {0x59, 0xaf, 0x82, 0x79, 0x91, 0x86, 0xc7, 0xb4, 0x75, 0x07,
+ 0xcb, 0xcf, 0x03, 0x57, 0x46, 0xeb, 0x04, 0xdd, 0xb7, 0x16},
+ {0x5d, 0x98, 0x9c, 0xdb, 0x15, 0x96, 0x11, 0x36, 0x51, 0x65,
+ 0x64, 0x1b, 0x56, 0x0f, 0xdb, 0xea, 0x2a, 0xc2, 0x3e, 0xf1},
+ {0x5d, 0xe8, 0x3e, 0xe8, 0x2a, 0xc5, 0x09, 0x0a, 0xea, 0x9d,
+ 0x6a, 0xc4, 0xe7, 0xa6, 0xe2, 0x13, 0xf9, 0x46, 0xe1, 0x79},
+ {0x5f, 0x3a, 0xfc, 0x0a, 0x8b, 0x64, 0xf6, 0x86, 0x67, 0x34,
+ 0x74, 0xdf, 0x7e, 0xa9, 0xa2, 0xfe, 0xf9, 0xfa, 0x7a, 0x51},
+ {0x5f, 0x3b, 0x8c, 0xf2, 0xf8, 0x10, 0xb3, 0x7d, 0x78, 0xb4,
+ 0xce, 0xec, 0x19, 0x19, 0xc3, 0x73, 0x34, 0xb9, 0xc7, 0x74},
+ {0x5f, 0x4e, 0x1f, 0xcf, 0x31, 0xb7, 0x91, 0x3b, 0x85, 0x0b,
+ 0x54, 0xf6, 0xe5, 0xff, 0x50, 0x1a, 0x2b, 0x6f, 0xc6, 0xcf},
+ {0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c,
+ 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25},
+ {0x61, 0x1e, 0x5b, 0x66, 0x2c, 0x59, 0x3a, 0x08, 0xff, 0x58,
+ 0xd1, 0x4a, 0xe2, 0x24, 0x52, 0xd1, 0x98, 0xdf, 0x6c, 0x60},
+ {0x61, 0x57, 0x3a, 0x11, 0xdf, 0x0e, 0xd8, 0x7e, 0xd5, 0x92,
+ 0x65, 0x22, 0xea, 0xd0, 0x56, 0xd7, 0x44, 0xb3, 0x23, 0x71},
+ {0x61, 0xef, 0x43, 0xd7, 0x7f, 0xca, 0xd4, 0x61, 0x51, 0xbc,
+ 0x98, 0xe0, 0xc3, 0x59, 0x12, 0xaf, 0x9f, 0xeb, 0x63, 0x11},
+ {0x62, 0x52, 0xdc, 0x40, 0xf7, 0x11, 0x43, 0xa2, 0x2f, 0xde,
+ 0x9e, 0xf7, 0x34, 0x8e, 0x06, 0x42, 0x51, 0xb1, 0x81, 0x18},
+ {0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d,
+ 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a},
+ {0x66, 0x31, 0xbf, 0x9e, 0xf7, 0x4f, 0x9e, 0xb6, 0xc9, 0xd5,
+ 0xa6, 0x0c, 0xba, 0x6a, 0xbe, 0xd1, 0xf7, 0xbd, 0xef, 0x7b},
+ {0x67, 0x65, 0x0d, 0xf1, 0x7e, 0x8e, 0x7e, 0x5b, 0x82, 0x40,
+ 0xa4, 0xf4, 0x56, 0x4b, 0xcf, 0xe2, 0x3d, 0x69, 0xc6, 0xf0},
+ {0x67, 0x82, 0xaa, 0xe0, 0xed, 0xee, 0xe2, 0x1a, 0x58, 0x39,
+ 0xd3, 0xc0, 0xcd, 0x14, 0x68, 0x0a, 0x4f, 0x60, 0x14, 0x2a},
+ {0x67, 0x9a, 0x4f, 0x81, 0xfc, 0x70, 0x5d, 0xde, 0xc4, 0x19,
+ 0x77, 0x8d, 0xd2, 0xeb, 0xd8, 0x75, 0xf4, 0xc2, 0x42, 0xc6},
+ {0x69, 0xbd, 0x8c, 0xf4, 0x9c, 0xd3, 0x00, 0xfb, 0x59, 0x2e,
+ 0x17, 0x93, 0xca, 0x55, 0x6a, 0xf3, 0xec, 0xaa, 0x35, 0xfb},
+ {0x6a, 0x84, 0xfe, 0x62, 0x7e, 0xcc, 0x49, 0xa1, 0xbe, 0x02,
+ 0xe9, 0x18, 0xfa, 0xc9, 0xe1, 0xf7, 0x32, 0x80, 0x3a, 0x62},
+ {0x6b, 0x2f, 0x34, 0xad, 0x89, 0x58, 0xbe, 0x62, 0xfd, 0xb0,
+ 0x6b, 0x5c, 0xce, 0xbb, 0x9d, 0xd9, 0x4f, 0x4e, 0x39, 0xf3},
+ {0x6b, 0x81, 0x44, 0x6a, 0x5c, 0xdd, 0xf4, 0x74, 0xa0, 0xf8,
+ 0x00, 0xff, 0xbe, 0x69, 0xfd, 0x0d, 0xb6, 0x28, 0x75, 0x16},
+ {0x6e, 0x3a, 0x55, 0xa4, 0x19, 0x0c, 0x19, 0x5c, 0x93, 0x84,
+ 0x3c, 0xc0, 0xdb, 0x72, 0x2e, 0x31, 0x30, 0x61, 0xf0, 0xb1},
+ {0x70, 0x17, 0x9b, 0x86, 0x8c, 0x00, 0xa4, 0xfa, 0x60, 0x91,
+ 0x52, 0x22, 0x3f, 0x9f, 0x3e, 0x32, 0xbd, 0xe0, 0x05, 0x62},
+ {0x70, 0x43, 0x2d, 0xe4, 0x3c, 0x9d, 0x1e, 0x79, 0xc1, 0x13,
+ 0x25, 0x20, 0xbc, 0x58, 0x43, 0xf7, 0xbb, 0x7d, 0x92, 0x95},
+ {0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79,
+ 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb},
+ {0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45,
+ 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2},
+ {0x74, 0x54, 0x53, 0x5c, 0x24, 0xa3, 0xa7, 0x58, 0x20, 0x7e,
+ 0x3e, 0x3e, 0xd3, 0x24, 0xf8, 0x16, 0xfb, 0x21, 0x16, 0x49},
+ {0x74, 0xa2, 0x66, 0xf0, 0x95, 0xa9, 0xa4, 0xeb, 0x95, 0x22,
+ 0x19, 0xd6, 0x05, 0xda, 0x93, 0x63, 0xf5, 0x14, 0xfa, 0xf9},
+ {0x74, 0xf8, 0xa3, 0xc3, 0xef, 0xe7, 0xb3, 0x90, 0x06, 0x4b,
+ 0x83, 0x90, 0x3c, 0x21, 0x64, 0x60, 0x20, 0xe5, 0xdf, 0xce},
+ {0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04,
+ 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe},
+ {0x79, 0x98, 0xa3, 0x08, 0xe1, 0x4d, 0x65, 0x85, 0xe6, 0xc2,
+ 0x1e, 0x15, 0x3a, 0x71, 0x9f, 0xba, 0x5a, 0xd3, 0x4a, 0xd9},
+ {0x7e, 0x78, 0x4a, 0x10, 0x1c, 0x82, 0x65, 0xcc, 0x2d, 0xe1,
+ 0xf1, 0x6d, 0x47, 0xb4, 0x40, 0xca, 0xd9, 0x0a, 0x19, 0x45},
+ {0x7f, 0x8a, 0xb0, 0xcf, 0xd0, 0x51, 0x87, 0x6a, 0x66, 0xf3,
+ 0x36, 0x0f, 0x47, 0xc8, 0x8d, 0x8c, 0xd3, 0x35, 0xfc, 0x74},
+ {0x80, 0x1d, 0x62, 0xd0, 0x7b, 0x44, 0x9d, 0x5c, 0x5c, 0x03,
+ 0x5c, 0x98, 0xea, 0x61, 0xfa, 0x44, 0x3c, 0x2a, 0x58, 0xfe},
+ {0x80, 0x25, 0xef, 0xf4, 0x6e, 0x70, 0xc8, 0xd4, 0x72, 0x24,
+ 0x65, 0x84, 0xfe, 0x40, 0x3b, 0x8a, 0x8d, 0x6a, 0xdb, 0xf5},
+ {0x82, 0x50, 0xbe, 0xd5, 0xa2, 0x14, 0x43, 0x3a, 0x66, 0x37,
+ 0x7c, 0xbc, 0x10, 0xef, 0x83, 0xf6, 0x69, 0xda, 0x3a, 0x67},
+ {0x82, 0x68, 0x99, 0x3e, 0xda, 0xeb, 0xb1, 0xe4, 0xfb, 0x77,
+ 0x91, 0x0f, 0x12, 0xcb, 0xd6, 0xc6, 0x70, 0xf0, 0x7c, 0xea},
+ {0x85, 0x37, 0x1c, 0xa6, 0xe5, 0x50, 0x14, 0x3d, 0xce, 0x28,
+ 0x03, 0x47, 0x1b, 0xde, 0x3a, 0x09, 0xe8, 0xf8, 0x77, 0x0f},
+ {0x85, 0xa4, 0x08, 0xc0, 0x9c, 0x19, 0x3e, 0x5d, 0x51, 0x58,
+ 0x7d, 0xcd, 0xd6, 0x13, 0x30, 0xfd, 0x8c, 0xde, 0x37, 0xbf},
+ {0x85, 0xb5, 0xff, 0x67, 0x9b, 0x0c, 0x79, 0x96, 0x1f, 0xc8,
+ 0x6e, 0x44, 0x22, 0x00, 0x46, 0x13, 0xdb, 0x17, 0x92, 0x84},
+ {0x86, 0xe8, 0x17, 0xc8, 0x1a, 0x5c, 0xa6, 0x72, 0xfe, 0x00,
+ 0x0f, 0x36, 0xf8, 0x78, 0xc1, 0x95, 0x18, 0xd6, 0xf8, 0x44},
+ {0x87, 0x81, 0xc2, 0x5a, 0x96, 0xbd, 0xc2, 0xfb, 0x4c, 0x65,
+ 0x06, 0x4f, 0xf9, 0x39, 0x0b, 0x26, 0x04, 0x8a, 0x0e, 0x01},
+ {0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96,
+ 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11},
+ {0x87, 0x9f, 0x4b, 0xee, 0x05, 0xdf, 0x98, 0x58, 0x3b, 0xe3,
+ 0x60, 0xd6, 0x33, 0xe7, 0x0d, 0x3f, 0xfe, 0x98, 0x71, 0xaf},
+ {0x8b, 0xaf, 0x4c, 0x9b, 0x1d, 0xf0, 0x2a, 0x92, 0xf7, 0xda,
+ 0x12, 0x8e, 0xb9, 0x1b, 0xac, 0xf4, 0x98, 0x60, 0x4b, 0x6f},
+ {0x8c, 0x94, 0x1b, 0x34, 0xea, 0x1e, 0xa6, 0xed, 0x9a, 0xe2,
+ 0xbc, 0x54, 0xcf, 0x68, 0x72, 0x52, 0xb4, 0xc9, 0xb5, 0x61},
+ {0x8c, 0x96, 0xba, 0xeb, 0xdd, 0x2b, 0x07, 0x07, 0x48, 0xee,
+ 0x30, 0x32, 0x66, 0xa0, 0xf3, 0x98, 0x6e, 0x7c, 0xae, 0x58},
+ {0x8c, 0xc4, 0x30, 0x7b, 0xc6, 0x07, 0x55, 0xe7, 0xb2, 0x2d,
+ 0xd9, 0xf7, 0xfe, 0xa2, 0x45, 0x93, 0x6c, 0x7c, 0xf2, 0x88},
+ {0x8e, 0x5b, 0xd5, 0x0d, 0x6a, 0xe6, 0x86, 0xd6, 0x52, 0x52,
+ 0xf8, 0x43, 0xa9, 0xd4, 0xb9, 0x6d, 0x19, 0x77, 0x30, 0xab},
+ {0x90, 0x5f, 0x94, 0x2f, 0xd9, 0xf2, 0x8f, 0x67, 0x9b, 0x37,
+ 0x81, 0x80, 0xfd, 0x4f, 0x84, 0x63, 0x47, 0xf6, 0x45, 0xc1},
+ {0x90, 0xae, 0xa2, 0x69, 0x85, 0xff, 0x14, 0x80, 0x4c, 0x43,
+ 0x49, 0x52, 0xec, 0xe9, 0x60, 0x84, 0x77, 0xaf, 0x55, 0x6f},
+ {0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5,
+ 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81},
+ {0x92, 0x5a, 0x8f, 0x8d, 0x2c, 0x6d, 0x04, 0xe0, 0x66, 0x5f,
+ 0x59, 0x6a, 0xff, 0x22, 0xd8, 0x63, 0xe8, 0x25, 0x6f, 0x3f},
+ {0x93, 0xe6, 0xab, 0x22, 0x03, 0x03, 0xb5, 0x23, 0x28, 0xdc,
+ 0xda, 0x56, 0x9e, 0xba, 0xe4, 0xd1, 0xd1, 0xcc, 0xfb, 0x65},
+ {0x94, 0x32, 0xbd, 0x9a, 0xec, 0x1d, 0x75, 0xd1, 0x70, 0x5c,
+ 0x54, 0x3a, 0xa3, 0x4c, 0x4a, 0xf6, 0xa5, 0x26, 0xc1, 0x3d},
+ {0x96, 0x56, 0xcd, 0x7b, 0x57, 0x96, 0x98, 0x95, 0xd0, 0xe1,
+ 0x41, 0x46, 0x68, 0x06, 0xfb, 0xb8, 0xc6, 0x11, 0x06, 0x87},
+ {0x96, 0x83, 0x38, 0xf1, 0x13, 0xe3, 0x6a, 0x7b, 0xab, 0xdd,
+ 0x08, 0xf7, 0x77, 0x63, 0x91, 0xa6, 0x87, 0x36, 0x58, 0x2e},
+ {0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34,
+ 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74},
+ {0x98, 0x45, 0xa4, 0x31, 0xd5, 0x19, 0x59, 0xca, 0xf2, 0x25,
+ 0x32, 0x2b, 0x4a, 0x4f, 0xe9, 0xf2, 0x23, 0xce, 0x6d, 0x15},
+ {0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b,
+ 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39},
+ {0x9b, 0xaa, 0xe5, 0x9f, 0x56, 0xee, 0x21, 0xcb, 0x43, 0x5a,
+ 0xbe, 0x25, 0x93, 0xdf, 0xa7, 0xf0, 0x40, 0xd1, 0x1d, 0xcb},
+ {0x9f, 0xad, 0x91, 0xa6, 0xce, 0x6a, 0xc6, 0xc5, 0x00, 0x47,
+ 0xc4, 0x4e, 0xc9, 0xd4, 0xa5, 0x0d, 0x92, 0xd8, 0x49, 0x79},
+ {0xa0, 0xa1, 0xab, 0x90, 0xc9, 0xfc, 0x84, 0x7b, 0x3b, 0x12,
+ 0x61, 0xe8, 0x97, 0x7d, 0x5f, 0xd3, 0x22, 0x61, 0xd3, 0xcc},
+ {0xa1, 0xdb, 0x63, 0x93, 0x91, 0x6f, 0x17, 0xe4, 0x18, 0x55,
+ 0x09, 0x40, 0x04, 0x15, 0xc7, 0x02, 0x40, 0xb0, 0xae, 0x6b},
+ {0xa2, 0x48, 0x41, 0xab, 0xd6, 0xa0, 0xca, 0x5c, 0xcd, 0x2a,
+ 0xa3, 0xb1, 0x90, 0x70, 0x1e, 0xd6, 0x4b, 0x39, 0xfe, 0x53},
+ {0xa3, 0xf1, 0x33, 0x3f, 0xe2, 0x42, 0xbf, 0xcf, 0xc5, 0xd1,
+ 0x4e, 0x8f, 0x39, 0x42, 0x98, 0x40, 0x68, 0x10, 0xd1, 0xa0},
+ {0xa6, 0x76, 0xdb, 0xf1, 0x92, 0x48, 0xf5, 0x2c, 0x57, 0x53,
+ 0xd0, 0xda, 0xc1, 0x4c, 0x53, 0xc4, 0x74, 0xa4, 0x83, 0x5e},
+ {0xa6, 0x9a, 0x91, 0xfd, 0x05, 0x7f, 0x13, 0x6a, 0x42, 0x63,
+ 0x0b, 0xb1, 0x76, 0x0d, 0x2d, 0x51, 0x12, 0x0c, 0x16, 0x50},
+ {0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7,
+ 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36},
+ {0xaa, 0xdb, 0xbc, 0x22, 0x23, 0x8f, 0xc4, 0x01, 0xa1, 0x27,
+ 0xbb, 0x38, 0xdd, 0xf4, 0x1d, 0xdb, 0x08, 0x9e, 0xf0, 0x12},
+ {0xac, 0xed, 0x5f, 0x65, 0x53, 0xfd, 0x25, 0xce, 0x01, 0x5f,
+ 0x1f, 0x7a, 0x48, 0x3b, 0x6a, 0x74, 0x9f, 0x61, 0x78, 0xc6},
+ {0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03,
+ 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a},
+ {0xae, 0x50, 0x83, 0xed, 0x7c, 0xf4, 0x5c, 0xbc, 0x8f, 0x61,
+ 0xc6, 0x21, 0xfe, 0x68, 0x5d, 0x79, 0x42, 0x21, 0x15, 0x6e},
+ {0xae, 0xc5, 0xfb, 0x3f, 0xc8, 0xe1, 0xbf, 0xc4, 0xe5, 0x4f,
+ 0x03, 0x07, 0x5a, 0x9a, 0xe8, 0x00, 0xb7, 0xf7, 0xb6, 0xfa},
+ {0xb1, 0x2e, 0x13, 0x63, 0x45, 0x86, 0xa4, 0x6f, 0x1a, 0xb2,
+ 0x60, 0x68, 0x37, 0x58, 0x2d, 0xc4, 0xac, 0xfd, 0x94, 0x97},
+ {0xb1, 0x72, 0xb1, 0xa5, 0x6d, 0x95, 0xf9, 0x1f, 0xe5, 0x02,
+ 0x87, 0xe1, 0x4d, 0x37, 0xea, 0x6a, 0x44, 0x63, 0x76, 0x8a},
+ {0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8,
+ 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c},
+ {0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda,
+ 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9},
+ {0xb3, 0xea, 0xc4, 0x47, 0x76, 0xc9, 0xc8, 0x1c, 0xea, 0xf2,
+ 0x9d, 0x95, 0xb6, 0xcc, 0xa0, 0x08, 0x1b, 0x67, 0xec, 0x9d},
+ {0xb4, 0x35, 0xd4, 0xe1, 0x11, 0x9d, 0x1c, 0x66, 0x90, 0xa7,
+ 0x49, 0xeb, 0xb3, 0x94, 0xbd, 0x63, 0x7b, 0xa7, 0x82, 0xb7},
+ {0xb4, 0x57, 0x12, 0x1e, 0x63, 0x45, 0xff, 0x93, 0x5d, 0x6b,
+ 0x1c, 0xa2, 0xdd, 0xf4, 0x52, 0x3c, 0xc6, 0xd0, 0xef, 0x6b},
+ {0xb5, 0x1c, 0x06, 0x7c, 0xee, 0x2b, 0x0c, 0x3d, 0xf8, 0x55,
+ 0xab, 0x2d, 0x92, 0xf4, 0xfe, 0x39, 0xd4, 0xe7, 0x0f, 0x0e},
+ {0xb6, 0xca, 0x21, 0x5b, 0x83, 0x6c, 0x35, 0x10, 0x1d, 0xaf,
+ 0x74, 0x63, 0x90, 0x0a, 0x93, 0x68, 0x80, 0x76, 0x7a, 0xa6},
+ {0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04,
+ 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6},
+ {0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01,
+ 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb},
+ {0xb8, 0x6e, 0x79, 0x16, 0x20, 0xf7, 0x59, 0xf1, 0x7b, 0x8d,
+ 0x25, 0xe3, 0x8c, 0xa8, 0xbe, 0x32, 0xe7, 0xd5, 0xea, 0xc2},
+ {0xc0, 0x60, 0xed, 0x44, 0xcb, 0xd8, 0x81, 0xbd, 0x0e, 0xf8,
+ 0x6c, 0x0b, 0xa2, 0x87, 0xdd, 0xcf, 0x81, 0x67, 0x47, 0x8c},
+ {0xc8, 0xec, 0x8c, 0x87, 0x92, 0x69, 0xcb, 0x4b, 0xab, 0x39,
+ 0xe9, 0x8d, 0x7e, 0x57, 0x67, 0xf3, 0x14, 0x95, 0x73, 0x9d},
+ {0xca, 0x39, 0xd8, 0xea, 0x48, 0x22, 0x13, 0x7f, 0x33, 0x8d,
+ 0xca, 0x79, 0x56, 0x6e, 0xdd, 0xf0, 0x54, 0x7e, 0xce, 0xa7},
+ {0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2,
+ 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7},
+ {0xcb, 0x44, 0xa0, 0x97, 0x85, 0x7c, 0x45, 0xfa, 0x18, 0x7e,
+ 0xd9, 0x52, 0x08, 0x6c, 0xb9, 0x84, 0x1f, 0x2d, 0x51, 0xb5},
+ {0xcb, 0x65, 0x82, 0x64, 0xea, 0x8c, 0xda, 0x18, 0x6e, 0x17,
+ 0x52, 0xfb, 0x52, 0xc3, 0x97, 0x36, 0x7e, 0xa3, 0x87, 0xbe},
+ {0xcb, 0xa1, 0xc5, 0xf8, 0xb0, 0xe3, 0x5e, 0xb8, 0xb9, 0x45,
+ 0x12, 0xd3, 0xf9, 0x34, 0xa2, 0xe9, 0x06, 0x10, 0xd3, 0x36},
+ {0xcc, 0xab, 0x0e, 0xa0, 0x4c, 0x23, 0x01, 0xd6, 0x69, 0x7b,
+ 0xdd, 0x37, 0x9f, 0xcd, 0x12, 0xeb, 0x24, 0xe3, 0x94, 0x9d},
+ {0xce, 0x6a, 0x64, 0xa3, 0x09, 0xe4, 0x2f, 0xbb, 0xd9, 0x85,
+ 0x1c, 0x45, 0x3e, 0x64, 0x09, 0xea, 0xe8, 0x7d, 0x60, 0xf1},
+ {0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97,
+ 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48},
+ {0xd1, 0xeb, 0x23, 0xa4, 0x6d, 0x17, 0xd6, 0x8f, 0xd9, 0x25,
+ 0x64, 0xc2, 0xf1, 0xf1, 0x60, 0x17, 0x64, 0xd8, 0xe3, 0x49},
+ {0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74,
+ 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a},
+ {0xd3, 0xc0, 0x63, 0xf2, 0x19, 0xed, 0x07, 0x3e, 0x34, 0xad,
+ 0x5d, 0x75, 0x0b, 0x32, 0x76, 0x29, 0xff, 0xd5, 0x9a, 0xf2},
+ {0xd4, 0x37, 0x19, 0xb5, 0x1b, 0x57, 0xca, 0x4b, 0xb8, 0x74,
+ 0x16, 0x7d, 0x47, 0x95, 0x23, 0x1d, 0x34, 0x34, 0xfd, 0xa8},
+ {0xd4, 0xde, 0x20, 0xd0, 0x5e, 0x66, 0xfc, 0x53, 0xfe, 0x1a,
+ 0x50, 0x88, 0x2c, 0x78, 0xdb, 0x28, 0x52, 0xca, 0xe4, 0x74},
+ {0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45,
+ 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad},
+ {0xd6, 0xda, 0xa8, 0x20, 0x8d, 0x09, 0xd2, 0x15, 0x4d, 0x24,
+ 0xb5, 0x2f, 0xcb, 0x34, 0x6e, 0xb2, 0x58, 0xb2, 0x8a, 0x58},
+ {0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6,
+ 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27},
+ {0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4,
+ 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61},
+ {0xda, 0x40, 0x18, 0x8b, 0x91, 0x89, 0xa3, 0xed, 0xee, 0xae,
+ 0xda, 0x97, 0xfe, 0x2f, 0x9d, 0xf5, 0xb7, 0xd1, 0x8a, 0x41},
+ {0xda, 0xc9, 0x02, 0x4f, 0x54, 0xd8, 0xf6, 0xdf, 0x94, 0x93,
+ 0x5f, 0xb1, 0x73, 0x26, 0x38, 0xca, 0x6a, 0xd7, 0x7c, 0x13},
+ {0xde, 0x28, 0xf4, 0xa4, 0xff, 0xe5, 0xb9, 0x2f, 0xa3, 0xc5,
+ 0x03, 0xd1, 0xa3, 0x49, 0xa7, 0xf9, 0x96, 0x2a, 0x82, 0x12},
+ {0xde, 0x3f, 0x40, 0xbd, 0x50, 0x93, 0xd3, 0x9b, 0x6c, 0x60,
+ 0xf6, 0xda, 0xbc, 0x07, 0x62, 0x01, 0x00, 0x89, 0x76, 0xc9},
+ {0xde, 0x99, 0x0c, 0xed, 0x99, 0xe0, 0x43, 0x1f, 0x60, 0xed,
+ 0xc3, 0x93, 0x7e, 0x7c, 0xd5, 0xbf, 0x0e, 0xd9, 0xe5, 0xfa},
+ {0xdf, 0xdf, 0xac, 0x89, 0x47, 0xbd, 0xf7, 0x52, 0x64, 0xa9,
+ 0x23, 0x3a, 0xc1, 0x0e, 0xe3, 0xd1, 0x28, 0x33, 0xda, 0xcc},
+ {0xe0, 0x5f, 0x7c, 0x22, 0x59, 0x8c, 0x12, 0x56, 0xa7, 0xb9,
+ 0x4d, 0x92, 0xd3, 0xd1, 0x94, 0x50, 0x8c, 0x8c, 0xba, 0x71},
+ {0xe0, 0xab, 0x05, 0x94, 0x20, 0x72, 0x54, 0x93, 0x05, 0x60,
+ 0x62, 0x02, 0x36, 0x70, 0xf7, 0xcd, 0x2e, 0xfc, 0x66, 0x66},
+ {0xe1, 0x2d, 0xfb, 0x4b, 0x41, 0xd7, 0xd9, 0xc3, 0x2b, 0x30,
+ 0x51, 0x4b, 0xac, 0x1d, 0x81, 0xd8, 0x38, 0x5e, 0x2d, 0x46},
+ {0xe1, 0x9f, 0xe3, 0x0e, 0x8b, 0x84, 0x60, 0x9e, 0x80, 0x9b,
+ 0x17, 0x0d, 0x72, 0xa8, 0xc5, 0xba, 0x6e, 0x14, 0x09, 0xbd},
+ {0xe3, 0x92, 0x51, 0x2f, 0x0a, 0xcf, 0xf5, 0x05, 0xdf, 0xf6,
+ 0xde, 0x06, 0x7f, 0x75, 0x37, 0xe1, 0x65, 0xea, 0x57, 0x4b},
+ {0xe5, 0xdf, 0x74, 0x3c, 0xb6, 0x01, 0xc4, 0x9b, 0x98, 0x43,
+ 0xdc, 0xab, 0x8c, 0xe8, 0x6a, 0x81, 0x10, 0x9f, 0xe4, 0x8e},
+ {0xe6, 0x18, 0x83, 0xae, 0x84, 0xca, 0xc1, 0xc1, 0xcd, 0x52,
+ 0xad, 0xe8, 0xe9, 0x25, 0x2b, 0x45, 0xa6, 0x4f, 0xb7, 0xe2},
+ {0xe6, 0x19, 0xd2, 0x5b, 0x38, 0x0b, 0x7b, 0x13, 0xfd, 0xa3,
+ 0x3e, 0x8a, 0x58, 0xcd, 0x82, 0xd8, 0xa8, 0x8e, 0x05, 0x15},
+ {0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e,
+ 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee},
+ {0xee, 0xef, 0xaa, 0x0b, 0xcd, 0x11, 0xaf, 0x5c, 0x02, 0xfa,
+ 0x96, 0x20, 0x6a, 0xc5, 0xc6, 0x2b, 0xa7, 0x24, 0xd6, 0x0a},
+ {0xf1, 0x7f, 0x6f, 0xb6, 0x31, 0xdc, 0x99, 0xe3, 0xa3, 0xc8,
+ 0x7f, 0xfe, 0x1c, 0xf1, 0x81, 0x10, 0x88, 0xd9, 0x60, 0x33},
+ {0xf7, 0x4d, 0xac, 0xb2, 0x14, 0x14, 0xdc, 0xba, 0xab, 0x0b,
+ 0x94, 0x7c, 0x8a, 0x25, 0x7c, 0x32, 0x5c, 0xa8, 0x85, 0x50},
+ {0xf8, 0xa5, 0x4e, 0x03, 0xaa, 0xdc, 0x56, 0x92, 0xb8, 0x50,
+ 0x49, 0x6a, 0x4c, 0x46, 0x30, 0xff, 0xea, 0xa2, 0x9d, 0x83},
+ {0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57,
+ 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7},
+ {0xfa, 0xa7, 0xd9, 0xfb, 0x31, 0xb7, 0x46, 0xf2, 0x00, 0xa8,
+ 0x5e, 0x65, 0x79, 0x76, 0x13, 0xd8, 0x16, 0xe0, 0x63, 0xb5},
+ {0xfc, 0x21, 0x9a, 0x76, 0x11, 0x2f, 0x76, 0xc1, 0xc5, 0x08,
+ 0x83, 0x3c, 0x9a, 0x2f, 0xa2, 0xba, 0x84, 0xac, 0x08, 0x7a},
+ {0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae,
+ 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d},
+ {0xff, 0xad, 0x0e, 0x26, 0xf0, 0x5b, 0xbc, 0xd8, 0x06, 0x3c,
+ 0xce, 0x1d, 0xfa, 0x60, 0x24, 0x5e, 0x14, 0x3d, 0x53, 0x80},
+};
+
+#endif // NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_MAC_H_
diff --git a/chromium/net/cert/x509_certificate_known_roots_win.h b/chromium/net/cert/x509_certificate_known_roots_win.h
new file mode 100644
index 00000000000..53b14c0bc91
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_known_roots_win.h
@@ -0,0 +1,726 @@
+// Copyright (c) 2011 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_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_
+#define NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_
+
+// This is the set of 318 Microsoft trusted roots from 29th April 2013.
+// Extracted from
+// http://www.download.windowsupdate.com/msdownload/update/v3/
+// static/trustedr/en/authrootstl.cab
+// (sha1sum: b643e7b6591d58c175b41ce5967d9ec6eb552e4d)
+// (thisUpdate time: 2013-03-21 19:46:44)
+//
+// Note that these *are not* trust anchors for Chromium. They are only used to
+// distinguish `real' root CAs from roots that were user-installed.
+static uint8 kKnownRootCertSHA1Hashes[][20] = {
+ {0x00, 0x48, 0xf8, 0xd3, 0x7b, 0x15, 0x3f, 0x6e, 0xa2, 0x79,
+ 0x8c, 0x32, 0x3e, 0xf4, 0xf3, 0x18, 0xa5, 0x62, 0x4a, 0x9e},
+ {0x00, 0xea, 0x52, 0x2c, 0x8a, 0x9c, 0x06, 0xaa, 0x3e, 0xcc,
+ 0xe0, 0xb4, 0xfa, 0x6c, 0xdc, 0x21, 0xd9, 0x2e, 0x80, 0x99},
+ {0x01, 0x68, 0x97, 0xe1, 0xa0, 0xb8, 0xf2, 0xc3, 0xb1, 0x34,
+ 0x66, 0x5c, 0x20, 0xa7, 0x27, 0xb7, 0xa1, 0x58, 0xe2, 0x8f},
+ {0x02, 0x72, 0x68, 0x29, 0x3e, 0x5f, 0x5d, 0x17, 0xaa, 0xa4,
+ 0xb3, 0xc3, 0xe6, 0x36, 0x1e, 0x1f, 0x92, 0x57, 0x5e, 0xaa},
+ {0x02, 0xfa, 0xf3, 0xe2, 0x91, 0x43, 0x54, 0x68, 0x60, 0x78,
+ 0x57, 0x69, 0x4d, 0xf5, 0xe4, 0x5b, 0x68, 0x85, 0x18, 0x68},
+ {0x03, 0x9e, 0xed, 0xb8, 0x0b, 0xe7, 0xa0, 0x3c, 0x69, 0x53,
+ 0x89, 0x3b, 0x20, 0xd2, 0xd9, 0x32, 0x3a, 0x4c, 0x2a, 0xfd},
+ {0x04, 0x09, 0x56, 0x5b, 0x77, 0xda, 0x58, 0x2e, 0x64, 0x95,
+ 0xac, 0x00, 0x60, 0xa7, 0x23, 0x54, 0xe6, 0x4b, 0x01, 0x92},
+ {0x04, 0x46, 0xc8, 0xbb, 0x9a, 0x69, 0x83, 0xc9, 0x5c, 0x8a,
+ 0x2e, 0x54, 0x64, 0x68, 0x7c, 0x11, 0x15, 0xaa, 0xb7, 0x4a},
+ {0x04, 0x56, 0xf2, 0x3d, 0x1e, 0x9c, 0x43, 0xae, 0xcb, 0x0d,
+ 0x80, 0x7f, 0x1c, 0x06, 0x47, 0x55, 0x1a, 0x05, 0xf4, 0x56},
+ {0x04, 0x83, 0xed, 0x33, 0x99, 0xac, 0x36, 0x08, 0x05, 0x87,
+ 0x22, 0xed, 0xbc, 0x5e, 0x46, 0x00, 0xe3, 0xbe, 0xf9, 0xd7},
+ {0x04, 0x98, 0x11, 0x05, 0x6a, 0xfe, 0x9f, 0xd0, 0xf5, 0xbe,
+ 0x01, 0x68, 0x5a, 0xac, 0xe6, 0xa5, 0xd1, 0xc4, 0x45, 0x4c},
+ {0x05, 0x60, 0xa2, 0xc7, 0x38, 0xff, 0x98, 0xd1, 0x17, 0x2a,
+ 0x94, 0xfe, 0x45, 0xfb, 0x8a, 0x47, 0xd6, 0x65, 0x37, 0x1e},
+ {0x05, 0x63, 0xb8, 0x63, 0x0d, 0x62, 0xd7, 0x5a, 0xbb, 0xc8,
+ 0xab, 0x1e, 0x4b, 0xdf, 0xb5, 0xa8, 0x99, 0xb2, 0x4d, 0x43},
+ {0x06, 0x08, 0x3f, 0x59, 0x3f, 0x15, 0xa1, 0x04, 0xa0, 0x69,
+ 0xa4, 0x6b, 0xa9, 0x03, 0xd0, 0x06, 0xb7, 0x97, 0x09, 0x91},
+ {0x06, 0x14, 0x31, 0x51, 0xe0, 0x2b, 0x45, 0xdd, 0xba, 0xdd,
+ 0x5d, 0x8e, 0x56, 0x53, 0x0d, 0xaa, 0xe3, 0x28, 0xcf, 0x90},
+ {0x07, 0x47, 0x22, 0x01, 0x99, 0xce, 0x74, 0xb9, 0x7c, 0xb0,
+ 0x3d, 0x79, 0xb2, 0x64, 0xa2, 0xc8, 0x55, 0xe9, 0x33, 0xff},
+ {0x07, 0xe0, 0x32, 0xe0, 0x20, 0xb7, 0x2c, 0x3f, 0x19, 0x2f,
+ 0x06, 0x28, 0xa2, 0x59, 0x3a, 0x19, 0xa7, 0x0f, 0x06, 0x9e},
+ {0x08, 0x64, 0x18, 0xe9, 0x06, 0xce, 0xe8, 0x9c, 0x23, 0x53,
+ 0xb6, 0xe2, 0x7f, 0xbd, 0x9e, 0x74, 0x39, 0xf7, 0x63, 0x16},
+ {0x0b, 0x71, 0x99, 0xa1, 0xc7, 0xf3, 0xad, 0xdf, 0x7b, 0xa7,
+ 0xea, 0xb8, 0xeb, 0x57, 0x4a, 0xe8, 0x0d, 0x60, 0xdd, 0xde},
+ {0x0b, 0x77, 0xbe, 0xbb, 0xcb, 0x7a, 0xa2, 0x47, 0x05, 0xde,
+ 0xcc, 0x0f, 0xbd, 0x6a, 0x02, 0xfc, 0x7a, 0xbd, 0x9b, 0x52},
+ {0x0b, 0x97, 0x2c, 0x9e, 0xa6, 0xe7, 0xcc, 0x58, 0xd9, 0x3b,
+ 0x20, 0xbf, 0x71, 0xec, 0x41, 0x2e, 0x72, 0x09, 0xfa, 0xbf},
+ {0x0c, 0x62, 0x8f, 0x5c, 0x55, 0x70, 0xb1, 0xc9, 0x57, 0xfa,
+ 0xfd, 0x38, 0x3f, 0xb0, 0x3d, 0x7b, 0x7d, 0xd7, 0xb9, 0xc6},
+ {0x0c, 0xfd, 0x83, 0xdb, 0xae, 0x44, 0xb9, 0xa0, 0xc8, 0xf6,
+ 0x76, 0xf3, 0xb5, 0x70, 0x65, 0x0b, 0x94, 0xb6, 0x9d, 0xbf},
+ {0x10, 0x1d, 0xfa, 0x3f, 0xd5, 0x0b, 0xcb, 0xbb, 0x9b, 0xb5,
+ 0x60, 0x0c, 0x19, 0x55, 0xa4, 0x1a, 0xf4, 0x73, 0x3a, 0x04},
+ {0x11, 0xc5, 0xb5, 0xf7, 0x55, 0x52, 0xb0, 0x11, 0x66, 0x9c,
+ 0x2e, 0x97, 0x17, 0xde, 0x6d, 0x9b, 0xff, 0x5f, 0xa8, 0x10},
+ {0x11, 0xe1, 0x9b, 0xbc, 0x74, 0x7b, 0x1a, 0xed, 0x0d, 0xb8,
+ 0x33, 0xc9, 0x4c, 0xac, 0x6c, 0x3f, 0x85, 0xbd, 0xeb, 0xdb},
+ {0x13, 0x2d, 0x0d, 0x45, 0x53, 0x4b, 0x69, 0x97, 0xcd, 0xb2,
+ 0xd5, 0xc3, 0x39, 0xe2, 0x55, 0x76, 0x60, 0x9b, 0x5c, 0xc6},
+ {0x15, 0x03, 0x32, 0xa5, 0x8d, 0xc5, 0x91, 0xfc, 0x42, 0xd4,
+ 0xc8, 0x73, 0xff, 0x9f, 0x1f, 0x0f, 0x81, 0xd5, 0x97, 0xc9},
+ {0x16, 0xd8, 0x66, 0x35, 0xaf, 0x13, 0x41, 0xcd, 0x34, 0x79,
+ 0x94, 0x45, 0xeb, 0x60, 0x3e, 0x27, 0x37, 0x02, 0x96, 0x5d},
+ {0x18, 0xf7, 0xc1, 0xfc, 0xc3, 0x09, 0x02, 0x03, 0xfd, 0x5b,
+ 0xaa, 0x2f, 0x86, 0x1a, 0x75, 0x49, 0x76, 0xc8, 0xdd, 0x25},
+ {0x1a, 0xc9, 0x2f, 0x09, 0xea, 0x89, 0xe2, 0x8b, 0x12, 0x6d,
+ 0xfa, 0xc5, 0x1e, 0x3a, 0xf7, 0xea, 0x90, 0x95, 0xa3, 0xee},
+ {0x1b, 0x4b, 0x39, 0x61, 0x26, 0x27, 0x6b, 0x64, 0x91, 0xa2,
+ 0x68, 0x6d, 0xd7, 0x02, 0x43, 0x21, 0x2d, 0x1f, 0x1d, 0x96},
+ {0x1f, 0x49, 0x14, 0xf7, 0xd8, 0x74, 0x95, 0x1d, 0xdd, 0xae,
+ 0x02, 0xc0, 0xbe, 0xfd, 0x3a, 0x2d, 0x82, 0x75, 0x51, 0x85},
+ {0x20, 0x42, 0x85, 0xdc, 0xf7, 0xeb, 0x76, 0x41, 0x95, 0x57,
+ 0x8e, 0x13, 0x6b, 0xd4, 0xb7, 0xd1, 0xe9, 0x8e, 0x46, 0xa5},
+ {0x20, 0x99, 0x00, 0xb6, 0x3d, 0x95, 0x57, 0x28, 0x14, 0x0c,
+ 0xd1, 0x36, 0x22, 0xd8, 0xc6, 0x87, 0xa4, 0xeb, 0x00, 0x85},
+ {0x20, 0xcb, 0x59, 0x4f, 0xb4, 0xed, 0xd8, 0x95, 0x76, 0x3f,
+ 0xd5, 0x25, 0x4e, 0x95, 0x9a, 0x66, 0x74, 0xc6, 0xee, 0xb2},
+ {0x21, 0x11, 0x65, 0xca, 0x37, 0x9f, 0xbb, 0x5e, 0xd8, 0x01,
+ 0xe3, 0x1c, 0x43, 0x0a, 0x62, 0xaa, 0xc1, 0x09, 0xbc, 0xb4},
+ {0x21, 0x6b, 0x2a, 0x29, 0xe6, 0x2a, 0x00, 0xce, 0x82, 0x01,
+ 0x46, 0xd8, 0x24, 0x41, 0x41, 0xb9, 0x25, 0x11, 0xb2, 0x79},
+ {0x21, 0xfc, 0xbd, 0x8e, 0x7f, 0x6c, 0xaf, 0x05, 0x1b, 0xd1,
+ 0xb3, 0x43, 0xec, 0xa8, 0xe7, 0x61, 0x47, 0xf2, 0x0f, 0x8a},
+ {0x22, 0xd5, 0xd8, 0xdf, 0x8f, 0x02, 0x31, 0xd1, 0x8d, 0xf7,
+ 0x9d, 0xb7, 0xcf, 0x8a, 0x2d, 0x64, 0xc9, 0x3f, 0x6c, 0x3a},
+ {0x23, 0x88, 0xc9, 0xd3, 0x71, 0xcc, 0x9e, 0x96, 0x3d, 0xff,
+ 0x7d, 0x3c, 0xa7, 0xce, 0xfc, 0xd6, 0x25, 0xec, 0x19, 0x0d},
+ {0x23, 0xe5, 0x94, 0x94, 0x51, 0x95, 0xf2, 0x41, 0x48, 0x03,
+ 0xb4, 0xd5, 0x64, 0xd2, 0xa3, 0xa3, 0xf5, 0xd8, 0x8b, 0x8c},
+ {0x23, 0xe8, 0x33, 0x23, 0x3e, 0x7d, 0x0c, 0xc9, 0x2b, 0x7c,
+ 0x42, 0x79, 0xac, 0x19, 0xc2, 0xf4, 0x74, 0xd6, 0x04, 0xca},
+ {0x24, 0x5c, 0x97, 0xdf, 0x75, 0x14, 0xe7, 0xcf, 0x2d, 0xf8,
+ 0xbe, 0x72, 0xae, 0x95, 0x7b, 0x9e, 0x04, 0x74, 0x1e, 0x85},
+ {0x24, 0xa4, 0x0a, 0x1f, 0x57, 0x36, 0x43, 0xa6, 0x7f, 0x0a,
+ 0x4b, 0x07, 0x49, 0xf6, 0xa2, 0x2b, 0xf2, 0x8a, 0xbb, 0x6b},
+ {0x24, 0xba, 0x6d, 0x6c, 0x8a, 0x5b, 0x58, 0x37, 0xa4, 0x8d,
+ 0xb5, 0xfa, 0xe9, 0x19, 0xea, 0x67, 0x5c, 0x94, 0xd2, 0x17},
+ {0x25, 0x01, 0x90, 0x19, 0xcf, 0xfb, 0xd9, 0x99, 0x1c, 0xb7,
+ 0x68, 0x25, 0x74, 0x8d, 0x94, 0x5f, 0x30, 0x93, 0x95, 0x42},
+ {0x25, 0x3f, 0x77, 0x5b, 0x0e, 0x77, 0x97, 0xab, 0x64, 0x5f,
+ 0x15, 0x91, 0x55, 0x97, 0xc3, 0x9e, 0x26, 0x36, 0x31, 0xd1},
+ {0x27, 0x3e, 0xe1, 0x24, 0x57, 0xfd, 0xc4, 0xf9, 0x0c, 0x55,
+ 0xe8, 0x2b, 0x56, 0x16, 0x7f, 0x62, 0xf5, 0x32, 0xe5, 0x47},
+ {0x27, 0x96, 0xba, 0xe6, 0x3f, 0x18, 0x01, 0xe2, 0x77, 0x26,
+ 0x1b, 0xa0, 0xd7, 0x77, 0x70, 0x02, 0x8f, 0x20, 0xee, 0xe4},
+ {0x28, 0x90, 0x3a, 0x63, 0x5b, 0x52, 0x80, 0xfa, 0xe6, 0x77,
+ 0x4c, 0x0b, 0x6d, 0xa7, 0xd6, 0xba, 0xa6, 0x4a, 0xf2, 0xe8},
+ {0x29, 0x36, 0x21, 0x02, 0x8b, 0x20, 0xed, 0x02, 0xf5, 0x66,
+ 0xc5, 0x32, 0xd1, 0xd6, 0xed, 0x90, 0x9f, 0x45, 0x00, 0x2f},
+ {0x29, 0x64, 0xb6, 0x86, 0x13, 0x5b, 0x5d, 0xfd, 0xdd, 0x32,
+ 0x53, 0xa8, 0x9b, 0xbc, 0x24, 0xd7, 0x4b, 0x08, 0xc6, 0x4d},
+ {0x2a, 0xc8, 0xd5, 0x8b, 0x57, 0xce, 0xbf, 0x2f, 0x49, 0xaf,
+ 0xf2, 0xfc, 0x76, 0x8f, 0x51, 0x14, 0x62, 0x90, 0x7a, 0x41},
+ {0x2b, 0x8f, 0x1b, 0x57, 0x33, 0x0d, 0xbb, 0xa2, 0xd0, 0x7a,
+ 0x6c, 0x51, 0xf7, 0x0e, 0xe9, 0x0d, 0xda, 0xb9, 0xad, 0x8e},
+ {0x2b, 0xb1, 0xf5, 0x3e, 0x55, 0x0c, 0x1d, 0xc5, 0xf1, 0xd4,
+ 0xe6, 0xb7, 0x6a, 0x46, 0x4b, 0x55, 0x06, 0x02, 0xac, 0x21},
+ {0x2e, 0x14, 0xda, 0xec, 0x28, 0xf0, 0xfa, 0x1e, 0x8e, 0x38,
+ 0x9a, 0x4e, 0xab, 0xeb, 0x26, 0xc0, 0x0a, 0xd3, 0x83, 0xc3},
+ {0x30, 0x70, 0xf8, 0x83, 0x3e, 0x4a, 0xa6, 0x80, 0x3e, 0x09,
+ 0xa6, 0x46, 0xae, 0x3f, 0x7d, 0x8a, 0xe1, 0xfd, 0x16, 0x54},
+ {0x30, 0x77, 0x9e, 0x93, 0x15, 0x02, 0x2e, 0x94, 0x85, 0x6a,
+ 0x3f, 0xf8, 0xbc, 0xf8, 0x15, 0xb0, 0x82, 0xf9, 0xae, 0xfd},
+ {0x31, 0x7a, 0x2a, 0xd0, 0x7f, 0x2b, 0x33, 0x5e, 0xf5, 0xa1,
+ 0xc3, 0x4e, 0x4b, 0x57, 0xe8, 0xb7, 0xd8, 0xf1, 0xfc, 0xa6},
+ {0x31, 0xe2, 0xc5, 0x2c, 0xe1, 0x08, 0x9b, 0xef, 0xfd, 0xda,
+ 0xdb, 0x26, 0xdd, 0x7c, 0x78, 0x2e, 0xbc, 0x40, 0x37, 0xbd},
+ {0x32, 0x3c, 0x11, 0x8e, 0x1b, 0xf7, 0xb8, 0xb6, 0x52, 0x54,
+ 0xe2, 0xe2, 0x10, 0x0d, 0xd6, 0x02, 0x90, 0x37, 0xf0, 0x96},
+ {0x33, 0x9b, 0x6b, 0x14, 0x50, 0x24, 0x9b, 0x55, 0x7a, 0x01,
+ 0x87, 0x72, 0x84, 0xd9, 0xe0, 0x2f, 0xc3, 0xd2, 0xd8, 0xe9},
+ {0x34, 0x2c, 0xd9, 0xd3, 0x06, 0x2d, 0xa4, 0x8c, 0x34, 0x69,
+ 0x65, 0x29, 0x7f, 0x08, 0x1e, 0xbc, 0x2e, 0xf6, 0x8f, 0xdc},
+ {0x34, 0xd4, 0x99, 0x42, 0x6f, 0x9f, 0xc2, 0xbb, 0x27, 0xb0,
+ 0x75, 0xba, 0xb6, 0x82, 0xaa, 0xe5, 0xef, 0xfc, 0xba, 0x74},
+ {0x36, 0x79, 0xca, 0x35, 0x66, 0x87, 0x72, 0x30, 0x4d, 0x30,
+ 0xa5, 0xfb, 0x87, 0x3b, 0x0f, 0xa7, 0x7b, 0xb7, 0x0d, 0x54},
+ {0x36, 0x86, 0x35, 0x63, 0xfd, 0x51, 0x28, 0xc7, 0xbe, 0xa6,
+ 0xf0, 0x05, 0xcf, 0xe9, 0xb4, 0x36, 0x68, 0x08, 0x6c, 0xce},
+ {0x36, 0xb1, 0x2b, 0x49, 0xf9, 0x81, 0x9e, 0xd7, 0x4c, 0x9e,
+ 0xbc, 0x38, 0x0f, 0xc6, 0x56, 0x8f, 0x5d, 0xac, 0xb2, 0xf7},
+ {0x37, 0x9a, 0x19, 0x7b, 0x41, 0x85, 0x45, 0x35, 0x0c, 0xa6,
+ 0x03, 0x69, 0xf3, 0x3c, 0x2e, 0xaf, 0x47, 0x4f, 0x20, 0x79},
+ {0x37, 0xf7, 0x6d, 0xe6, 0x07, 0x7c, 0x90, 0xc5, 0xb1, 0x3e,
+ 0x93, 0x1a, 0xb7, 0x41, 0x10, 0xb4, 0xf2, 0xe4, 0x9a, 0x27},
+ {0x39, 0x13, 0x85, 0x3e, 0x45, 0xc4, 0x39, 0xa2, 0xda, 0x71,
+ 0x8c, 0xdf, 0xb6, 0xf3, 0xe0, 0x33, 0xe0, 0x4f, 0xee, 0x71},
+ {0x39, 0x21, 0xc1, 0x15, 0xc1, 0x5d, 0x0e, 0xca, 0x5c, 0xcb,
+ 0x5b, 0xc4, 0xf0, 0x7d, 0x21, 0xd8, 0x05, 0x0b, 0x56, 0x6a},
+ {0x39, 0x41, 0x0b, 0xc2, 0x30, 0x37, 0x48, 0x06, 0x60, 0x69,
+ 0xa7, 0x2a, 0x66, 0x4d, 0xe4, 0xc7, 0x43, 0x48, 0x12, 0x96},
+ {0x39, 0x4f, 0xf6, 0x85, 0x0b, 0x06, 0xbe, 0x52, 0xe5, 0x18,
+ 0x56, 0xcc, 0x10, 0xe1, 0x80, 0xe8, 0x82, 0xb3, 0x85, 0xcc},
+ {0x39, 0x8e, 0xbe, 0x9c, 0x0f, 0x46, 0xc0, 0x79, 0xc3, 0xc7,
+ 0xaf, 0xe0, 0x7a, 0x2f, 0xdd, 0x9f, 0xae, 0x5f, 0x8a, 0x5c},
+ {0x3a, 0x44, 0x73, 0x5a, 0xe5, 0x81, 0x90, 0x1f, 0x24, 0x86,
+ 0x61, 0x46, 0x1e, 0x3b, 0x9c, 0xc4, 0x5f, 0xf5, 0x3a, 0x1b},
+ {0x3b, 0x1e, 0xfd, 0x3a, 0x66, 0xea, 0x28, 0xb1, 0x66, 0x97,
+ 0x39, 0x47, 0x03, 0xa7, 0x2c, 0xa3, 0x40, 0xa0, 0x5b, 0xd5},
+ {0x3b, 0xc0, 0x38, 0x0b, 0x33, 0xc3, 0xf6, 0xa6, 0x0c, 0x86,
+ 0x15, 0x22, 0x93, 0xd9, 0xdf, 0xf5, 0x4b, 0x81, 0xc0, 0x04},
+ {0x3b, 0xc4, 0x9f, 0x48, 0xf8, 0xf3, 0x73, 0xa0, 0x9c, 0x1e,
+ 0xbd, 0xf8, 0x5b, 0xb1, 0xc3, 0x65, 0xc7, 0xd8, 0x11, 0xb3},
+ {0x3c, 0x71, 0xd7, 0x0e, 0x35, 0xa5, 0xda, 0xa8, 0xb2, 0xe3,
+ 0x81, 0x2d, 0xc3, 0x67, 0x74, 0x17, 0xf5, 0x99, 0x0d, 0xf3},
+ {0x3e, 0x2b, 0xf7, 0xf2, 0x03, 0x1b, 0x96, 0xf3, 0x8c, 0xe6,
+ 0xc4, 0xd8, 0xa8, 0x5d, 0x3e, 0x2d, 0x58, 0x47, 0x6a, 0x0f},
+ {0x3e, 0x42, 0xa1, 0x87, 0x06, 0xbd, 0x0c, 0x9c, 0xcf, 0x59,
+ 0x47, 0x50, 0xd2, 0xe4, 0xd6, 0xab, 0x00, 0x48, 0xfd, 0xc4},
+ {0x3e, 0x84, 0xd3, 0xbc, 0xc5, 0x44, 0xc0, 0xf6, 0xfa, 0x19,
+ 0x43, 0x5c, 0x85, 0x1f, 0x3f, 0x2f, 0xcb, 0xa8, 0xe8, 0x14},
+ {0x3f, 0x85, 0xf2, 0xbb, 0x4a, 0x62, 0xb0, 0xb5, 0x8b, 0xe1,
+ 0x61, 0x4a, 0xbb, 0x0d, 0x46, 0x31, 0xb4, 0xbe, 0xf8, 0xba},
+ {0x40, 0x54, 0xda, 0x6f, 0x1c, 0x3f, 0x40, 0x74, 0xac, 0xed,
+ 0x0f, 0xec, 0xcd, 0xdb, 0x79, 0xd1, 0x53, 0xfb, 0x90, 0x1d},
+ {0x40, 0x9d, 0x4b, 0xd9, 0x17, 0xb5, 0x5c, 0x27, 0xb6, 0x9b,
+ 0x64, 0xcb, 0x98, 0x22, 0x44, 0x0d, 0xcd, 0x09, 0xb8, 0x89},
+ {0x40, 0xe7, 0x8c, 0x1d, 0x52, 0x3d, 0x1c, 0xd9, 0x95, 0x4f,
+ 0xac, 0x1a, 0x1a, 0xb3, 0xbd, 0x3c, 0xba, 0xa1, 0x5b, 0xfc},
+ {0x42, 0xef, 0xdd, 0xe6, 0xbf, 0xf3, 0x5e, 0xd0, 0xba, 0xe6,
+ 0xac, 0xdd, 0x20, 0x4c, 0x50, 0xae, 0x86, 0xc4, 0xf4, 0xfa},
+ {0x43, 0x13, 0xbb, 0x96, 0xf1, 0xd5, 0x86, 0x9b, 0xc1, 0x4e,
+ 0x6a, 0x92, 0xf6, 0xcf, 0xf6, 0x34, 0x69, 0x87, 0x82, 0x37},
+ {0x43, 0x94, 0xce, 0x31, 0x26, 0xff, 0x1a, 0x22, 0x4c, 0xdd,
+ 0x4d, 0xee, 0xb4, 0xf4, 0xec, 0x1d, 0xa3, 0x68, 0xef, 0x6a},
+ {0x43, 0xf9, 0xb1, 0x10, 0xd5, 0xba, 0xfd, 0x48, 0x22, 0x52,
+ 0x31, 0xb0, 0xd0, 0x08, 0x2b, 0x37, 0x2f, 0xef, 0x9a, 0x54},
+ {0x44, 0x63, 0xc5, 0x31, 0xd7, 0xcc, 0xc1, 0x00, 0x67, 0x94,
+ 0x61, 0x2b, 0xb6, 0x56, 0xd3, 0xbf, 0x82, 0x57, 0x84, 0x6f},
+ {0x47, 0xbe, 0xab, 0xc9, 0x22, 0xea, 0xe8, 0x0e, 0x78, 0x78,
+ 0x34, 0x62, 0xa7, 0x9f, 0x45, 0xc2, 0x54, 0xfd, 0xe6, 0x8b},
+ {0x49, 0x0a, 0x75, 0x74, 0xde, 0x87, 0x0a, 0x47, 0xfe, 0x58,
+ 0xee, 0xf6, 0xc7, 0x6b, 0xeb, 0xc6, 0x0b, 0x12, 0x40, 0x99},
+ {0x4a, 0x05, 0x8f, 0xdf, 0xd7, 0x61, 0xdb, 0x21, 0xb0, 0xc2,
+ 0xee, 0x48, 0x57, 0x9b, 0xe2, 0x7f, 0x42, 0xa4, 0xda, 0x1c},
+ {0x4a, 0x3f, 0x8d, 0x6b, 0xdc, 0x0e, 0x1e, 0xcf, 0xcd, 0x72,
+ 0xe3, 0x77, 0xde, 0xf2, 0xd7, 0xff, 0x92, 0xc1, 0x9b, 0xc7},
+ {0x4a, 0xbd, 0xee, 0xec, 0x95, 0x0d, 0x35, 0x9c, 0x89, 0xae,
+ 0xc7, 0x52, 0xa1, 0x2c, 0x5b, 0x29, 0xf6, 0xd6, 0xaa, 0x0c},
+ {0x4e, 0xb6, 0xd5, 0x78, 0x49, 0x9b, 0x1c, 0xcf, 0x5f, 0x58,
+ 0x1e, 0xad, 0x56, 0xbe, 0x3d, 0x9b, 0x67, 0x44, 0xa5, 0xe5},
+ {0x4e, 0xfc, 0xed, 0x9c, 0x6b, 0xdd, 0x0c, 0x98, 0x5c, 0xa3,
+ 0xc7, 0xd2, 0x53, 0x06, 0x3c, 0x5b, 0xe6, 0xfc, 0x62, 0x0c},
+ {0x4f, 0x55, 0x5c, 0xe2, 0x0d, 0xcd, 0x33, 0x64, 0xe0, 0xdc,
+ 0x7c, 0x41, 0xef, 0xdd, 0x40, 0xf5, 0x03, 0x56, 0xc1, 0x22},
+ {0x4f, 0x65, 0x56, 0x63, 0x36, 0xdb, 0x65, 0x98, 0x58, 0x1d,
+ 0x58, 0x4a, 0x59, 0x6c, 0x87, 0x93, 0x4d, 0x5f, 0x2a, 0xb4},
+ {0x4f, 0x99, 0xaa, 0x93, 0xfb, 0x2b, 0xd1, 0x37, 0x26, 0xa1,
+ 0x99, 0x4a, 0xce, 0x7f, 0xf0, 0x05, 0xf2, 0x93, 0x5d, 0x1e},
+ {0x50, 0x30, 0x06, 0x09, 0x1d, 0x97, 0xd4, 0xf5, 0xae, 0x39,
+ 0xf7, 0xcb, 0xe7, 0x92, 0x7d, 0x7d, 0x65, 0x2d, 0x34, 0x31},
+ {0x51, 0xa4, 0x4c, 0x28, 0xf3, 0x13, 0xe3, 0xf9, 0xcb, 0x5e,
+ 0x7c, 0x0a, 0x1e, 0x0e, 0x0d, 0xd2, 0x84, 0x37, 0x58, 0xae},
+ {0x52, 0x41, 0x2b, 0xd6, 0x7b, 0x5a, 0x6c, 0x69, 0x52, 0x82,
+ 0x38, 0x60, 0x26, 0xf0, 0xb0, 0x53, 0xdd, 0x40, 0x0e, 0xfc},
+ {0x54, 0xf9, 0xc1, 0x63, 0x75, 0x9f, 0x19, 0x04, 0x51, 0x21,
+ 0xa3, 0x19, 0xf6, 0x4c, 0x2d, 0x05, 0x55, 0xb7, 0xe0, 0x73},
+ {0x55, 0xa6, 0x72, 0x3e, 0xcb, 0xf2, 0xec, 0xcd, 0xc3, 0x23,
+ 0x74, 0x70, 0x19, 0x9d, 0x2a, 0xbe, 0x11, 0xe3, 0x81, 0xd1},
+ {0x55, 0xc8, 0x6f, 0x74, 0x14, 0xac, 0x8b, 0xdd, 0x68, 0x14,
+ 0xf4, 0xd8, 0x6a, 0xf1, 0x5f, 0x37, 0x10, 0xe1, 0x04, 0xd0},
+ {0x56, 0xe0, 0xfa, 0xc0, 0x3b, 0x8f, 0x18, 0x23, 0x55, 0x18,
+ 0xe5, 0xd3, 0x11, 0xca, 0xe8, 0xc2, 0x43, 0x31, 0xab, 0x66},
+ {0x58, 0x11, 0x9f, 0x0e, 0x12, 0x82, 0x87, 0xea, 0x50, 0xfd,
+ 0xd9, 0x87, 0x45, 0x6f, 0x4f, 0x78, 0xdc, 0xfa, 0xd6, 0xd4},
+ {0x58, 0x5f, 0x78, 0x75, 0xbe, 0xe7, 0x43, 0x3e, 0xb0, 0x79,
+ 0xea, 0xab, 0x7d, 0x05, 0xbb, 0x0f, 0x7a, 0xf2, 0xbc, 0xcc},
+ {0x58, 0xe8, 0xab, 0xb0, 0x36, 0x15, 0x33, 0xfb, 0x80, 0xf7,
+ 0x9b, 0x1b, 0x6d, 0x29, 0xd3, 0xff, 0x8d, 0x5f, 0x00, 0xf0},
+ {0x59, 0x22, 0xa1, 0xe1, 0x5a, 0xea, 0x16, 0x35, 0x21, 0xf8,
+ 0x98, 0x39, 0x6a, 0x46, 0x46, 0xb0, 0x44, 0x1b, 0x0f, 0xa9},
+ {0x59, 0xaf, 0x82, 0x79, 0x91, 0x86, 0xc7, 0xb4, 0x75, 0x07,
+ 0xcb, 0xcf, 0x03, 0x57, 0x46, 0xeb, 0x04, 0xdd, 0xb7, 0x16},
+ {0x5a, 0x4d, 0x0e, 0x8b, 0x5f, 0xdc, 0xfd, 0xf6, 0x4e, 0x72,
+ 0x99, 0xa3, 0x6c, 0x06, 0x0d, 0xb2, 0x22, 0xca, 0x78, 0xe4},
+ {0x5a, 0x5a, 0x4d, 0xaf, 0x78, 0x61, 0x26, 0x7c, 0x4b, 0x1f,
+ 0x1e, 0x67, 0x58, 0x6b, 0xae, 0x6e, 0xd4, 0xfe, 0xb9, 0x3f},
+ {0x5d, 0x00, 0x38, 0x60, 0xf0, 0x02, 0xed, 0x82, 0x9d, 0xea,
+ 0xa4, 0x18, 0x68, 0xf7, 0x88, 0x18, 0x6d, 0x62, 0x12, 0x7f},
+ {0x5d, 0x98, 0x9c, 0xdb, 0x15, 0x96, 0x11, 0x36, 0x51, 0x65,
+ 0x64, 0x1b, 0x56, 0x0f, 0xdb, 0xea, 0x2a, 0xc2, 0x3e, 0xf1},
+ {0x5f, 0x3a, 0xfc, 0x0a, 0x8b, 0x64, 0xf6, 0x86, 0x67, 0x34,
+ 0x74, 0xdf, 0x7e, 0xa9, 0xa2, 0xfe, 0xf9, 0xfa, 0x7a, 0x51},
+ {0x5f, 0x3b, 0x8c, 0xf2, 0xf8, 0x10, 0xb3, 0x7d, 0x78, 0xb4,
+ 0xce, 0xec, 0x19, 0x19, 0xc3, 0x73, 0x34, 0xb9, 0xc7, 0x74},
+ {0x5f, 0x43, 0xe5, 0xb1, 0xbf, 0xf8, 0x78, 0x8c, 0xac, 0x1c,
+ 0xc7, 0xca, 0x4a, 0x9a, 0xc6, 0x22, 0x2b, 0xcc, 0x34, 0xc6},
+ {0x5f, 0x4e, 0x1f, 0xcf, 0x31, 0xb7, 0x91, 0x3b, 0x85, 0x0b,
+ 0x54, 0xf6, 0xe5, 0xff, 0x50, 0x1a, 0x2b, 0x6f, 0xc6, 0xcf},
+ {0x5f, 0xb7, 0xee, 0x06, 0x33, 0xe2, 0x59, 0xdb, 0xad, 0x0c,
+ 0x4c, 0x9a, 0xe6, 0xd3, 0x8f, 0x1a, 0x61, 0xc7, 0xdc, 0x25},
+ {0x60, 0xd6, 0x89, 0x74, 0xb5, 0xc2, 0x65, 0x9e, 0x8a, 0x0f,
+ 0xc1, 0x88, 0x7c, 0x88, 0xd2, 0x46, 0x69, 0x1b, 0x18, 0x2c},
+ {0x61, 0x57, 0x3a, 0x11, 0xdf, 0x0e, 0xd8, 0x7e, 0xd5, 0x92,
+ 0x65, 0x22, 0xea, 0xd0, 0x56, 0xd7, 0x44, 0xb3, 0x23, 0x71},
+ {0x61, 0xef, 0x43, 0xd7, 0x7f, 0xca, 0xd4, 0x61, 0x51, 0xbc,
+ 0x98, 0xe0, 0xc3, 0x59, 0x12, 0xaf, 0x9f, 0xeb, 0x63, 0x11},
+ {0x62, 0x52, 0xdc, 0x40, 0xf7, 0x11, 0x43, 0xa2, 0x2f, 0xde,
+ 0x9e, 0xf7, 0x34, 0x8e, 0x06, 0x42, 0x51, 0xb1, 0x81, 0x18},
+ {0x62, 0x7f, 0x8d, 0x78, 0x27, 0x65, 0x63, 0x99, 0xd2, 0x7d,
+ 0x7f, 0x90, 0x44, 0xc9, 0xfe, 0xb3, 0xf3, 0x3e, 0xfa, 0x9a},
+ {0x64, 0x90, 0x2a, 0xd7, 0x27, 0x7a, 0xf3, 0xe3, 0x2c, 0xd8,
+ 0xcc, 0x1d, 0xc7, 0x9d, 0xe1, 0xfd, 0x7f, 0x80, 0x69, 0xea},
+ {0x67, 0x24, 0x89, 0x80, 0xde, 0x77, 0x5d, 0x2c, 0x9b, 0x04,
+ 0xe4, 0x03, 0x07, 0x94, 0x0b, 0xad, 0xb3, 0x51, 0xf3, 0x95},
+ {0x67, 0x65, 0x0d, 0xf1, 0x7e, 0x8e, 0x7e, 0x5b, 0x82, 0x40,
+ 0xa4, 0xf4, 0x56, 0x4b, 0xcf, 0xe2, 0x3d, 0x69, 0xc6, 0xf0},
+ {0x67, 0x82, 0xaa, 0xe0, 0xed, 0xee, 0xe2, 0x1a, 0x58, 0x39,
+ 0xd3, 0xc0, 0xcd, 0x14, 0x68, 0x0a, 0x4f, 0x60, 0x14, 0x2a},
+ {0x67, 0x9a, 0x4f, 0x81, 0xfc, 0x70, 0x5d, 0xde, 0xc4, 0x19,
+ 0x77, 0x8d, 0xd2, 0xeb, 0xd8, 0x75, 0xf4, 0xc2, 0x42, 0xc6},
+ {0x67, 0xeb, 0x33, 0x7b, 0x68, 0x4c, 0xeb, 0x0e, 0xc2, 0xb0,
+ 0x76, 0x0a, 0xb4, 0x88, 0x27, 0x8c, 0xdd, 0x95, 0x97, 0xdd},
+ {0x68, 0x8b, 0x6e, 0xb8, 0x07, 0xe8, 0xed, 0xa5, 0xc7, 0xb1,
+ 0x7c, 0x43, 0x93, 0xd0, 0x79, 0x5f, 0x0f, 0xae, 0x15, 0x5f},
+ {0x68, 0xed, 0x18, 0xb3, 0x09, 0xcd, 0x52, 0x91, 0xc0, 0xd3,
+ 0x35, 0x7c, 0x1d, 0x11, 0x41, 0xbf, 0x88, 0x38, 0x66, 0xb1},
+ {0x69, 0xbd, 0x8c, 0xf4, 0x9c, 0xd3, 0x00, 0xfb, 0x59, 0x2e,
+ 0x17, 0x93, 0xca, 0x55, 0x6a, 0xf3, 0xec, 0xaa, 0x35, 0xfb},
+ {0x6a, 0x17, 0x45, 0x70, 0xa9, 0x16, 0xfb, 0xe8, 0x44, 0x53,
+ 0xee, 0xd3, 0xd0, 0x70, 0xa1, 0xd8, 0xda, 0x44, 0x28, 0x29},
+ {0x6a, 0x6f, 0x2a, 0x8b, 0x6e, 0x26, 0x15, 0x08, 0x8d, 0xf5,
+ 0x9c, 0xd2, 0x4c, 0x40, 0x24, 0x18, 0xae, 0x42, 0xa3, 0xf1},
+ {0x6a, 0xd2, 0x3b, 0x9d, 0xc4, 0x8e, 0x37, 0x5f, 0x85, 0x9a,
+ 0xd9, 0xca, 0xb5, 0x85, 0x32, 0x5c, 0x23, 0x89, 0x40, 0x71},
+ {0x6b, 0x2f, 0x34, 0xad, 0x89, 0x58, 0xbe, 0x62, 0xfd, 0xb0,
+ 0x6b, 0x5c, 0xce, 0xbb, 0x9d, 0xd9, 0x4f, 0x4e, 0x39, 0xf3},
+ {0x6b, 0x81, 0x44, 0x6a, 0x5c, 0xdd, 0xf4, 0x74, 0xa0, 0xf8,
+ 0x00, 0xff, 0xbe, 0x69, 0xfd, 0x0d, 0xb6, 0x28, 0x75, 0x16},
+ {0x6e, 0x3a, 0x55, 0xa4, 0x19, 0x0c, 0x19, 0x5c, 0x93, 0x84,
+ 0x3c, 0xc0, 0xdb, 0x72, 0x2e, 0x31, 0x30, 0x61, 0xf0, 0xb1},
+ {0x6f, 0x62, 0xde, 0xb8, 0x6c, 0x85, 0x58, 0x5a, 0xe4, 0x2e,
+ 0x47, 0x8d, 0xb4, 0xd7, 0x6d, 0xb3, 0x67, 0x58, 0x5a, 0xe6},
+ {0x70, 0x17, 0x9b, 0x86, 0x8c, 0x00, 0xa4, 0xfa, 0x60, 0x91,
+ 0x52, 0x22, 0x3f, 0x9f, 0x3e, 0x32, 0xbd, 0xe0, 0x05, 0x62},
+ {0x70, 0x30, 0xaa, 0xbf, 0x84, 0x32, 0xa8, 0x00, 0x66, 0x6c,
+ 0xcc, 0xc4, 0x2a, 0x88, 0x7e, 0x42, 0xb7, 0x55, 0x3e, 0x2b},
+ {0x70, 0x5d, 0x2b, 0x45, 0x65, 0xc7, 0x04, 0x7a, 0x54, 0x06,
+ 0x94, 0xa7, 0x9a, 0xf7, 0xab, 0xb8, 0x42, 0xbd, 0xc1, 0x61},
+ {0x71, 0x89, 0x9a, 0x67, 0xbf, 0x33, 0xaf, 0x31, 0xbe, 0xfd,
+ 0xc0, 0x71, 0xf8, 0xf7, 0x33, 0xb1, 0x83, 0x85, 0x63, 0x32},
+ {0x72, 0x0f, 0xc1, 0x5d, 0xdc, 0x27, 0xd4, 0x56, 0xd0, 0x98,
+ 0xfa, 0xbf, 0x3c, 0xdd, 0x78, 0xd3, 0x1e, 0xf5, 0xa8, 0xda},
+ {0x74, 0x20, 0x74, 0x41, 0x72, 0x9c, 0xdd, 0x92, 0xec, 0x79,
+ 0x31, 0xd8, 0x23, 0x10, 0x8d, 0xc2, 0x81, 0x92, 0xe2, 0xbb},
+ {0x74, 0x2c, 0x31, 0x92, 0xe6, 0x07, 0xe4, 0x24, 0xeb, 0x45,
+ 0x49, 0x54, 0x2b, 0xe1, 0xbb, 0xc5, 0x3e, 0x61, 0x74, 0xe2},
+ {0x74, 0x2c, 0xdf, 0x15, 0x94, 0x04, 0x9c, 0xbf, 0x17, 0xa2,
+ 0x04, 0x6c, 0xc6, 0x39, 0xbb, 0x38, 0x88, 0xe0, 0x2e, 0x33},
+ {0x75, 0x02, 0x51, 0xb2, 0xc6, 0x32, 0x53, 0x6f, 0x9d, 0x91,
+ 0x72, 0x79, 0x54, 0x3c, 0x13, 0x7c, 0xd7, 0x21, 0xc6, 0xe0},
+ {0x75, 0xe0, 0xab, 0xb6, 0x13, 0x85, 0x12, 0x27, 0x1c, 0x04,
+ 0xf8, 0x5f, 0xdd, 0xde, 0x38, 0xe4, 0xb7, 0x24, 0x2e, 0xfe},
+ {0x76, 0x12, 0xed, 0x9e, 0x49, 0xb3, 0x65, 0xb4, 0xda, 0xd3,
+ 0x12, 0x0c, 0x01, 0xe6, 0x03, 0x74, 0x8d, 0xae, 0x8c, 0xf0},
+ {0x76, 0x39, 0xc7, 0x18, 0x47, 0xe1, 0x51, 0xb5, 0xc7, 0xea,
+ 0x01, 0xc7, 0x58, 0xfb, 0xf1, 0x2a, 0xba, 0x29, 0x8f, 0x7a},
+ {0x76, 0xb7, 0x60, 0x96, 0xdd, 0x14, 0x56, 0x29, 0xac, 0x75,
+ 0x85, 0xd3, 0x70, 0x63, 0xc1, 0xbc, 0x47, 0x86, 0x1c, 0x8b},
+ {0x77, 0x47, 0x4f, 0xc6, 0x30, 0xe4, 0x0f, 0x4c, 0x47, 0x64,
+ 0x3f, 0x84, 0xba, 0xb8, 0xc6, 0x95, 0x4a, 0x8a, 0x41, 0xec},
+ {0x78, 0x6a, 0x74, 0xac, 0x76, 0xab, 0x14, 0x7f, 0x9c, 0x6a,
+ 0x30, 0x50, 0xba, 0x9e, 0xa8, 0x7e, 0xfe, 0x9a, 0xce, 0x3c},
+ {0x78, 0xe9, 0xdd, 0x06, 0x50, 0x62, 0x4d, 0xb9, 0xcb, 0x36,
+ 0xb5, 0x07, 0x67, 0xf2, 0x09, 0xb8, 0x43, 0xbe, 0x15, 0xb3},
+ {0x79, 0x98, 0xa3, 0x08, 0xe1, 0x4d, 0x65, 0x85, 0xe6, 0xc2,
+ 0x1e, 0x15, 0x3a, 0x71, 0x9f, 0xba, 0x5a, 0xd3, 0x4a, 0xd9},
+ {0x7a, 0x74, 0x41, 0x0f, 0xb0, 0xcd, 0x5c, 0x97, 0x2a, 0x36,
+ 0x4b, 0x71, 0xbf, 0x03, 0x1d, 0x88, 0xa6, 0x51, 0x0e, 0x9e},
+ {0x7a, 0xc5, 0xff, 0xf8, 0xdc, 0xbc, 0x55, 0x83, 0x17, 0x68,
+ 0x77, 0x07, 0x3b, 0xf7, 0x51, 0x73, 0x5e, 0x9b, 0xd3, 0x58},
+ {0x7e, 0x20, 0x69, 0x39, 0xcc, 0x5f, 0xa8, 0x83, 0x63, 0x5f,
+ 0x64, 0xc7, 0x50, 0xeb, 0xf5, 0xfd, 0xa9, 0xae, 0xe6, 0x53},
+ {0x7e, 0x78, 0x4a, 0x10, 0x1c, 0x82, 0x65, 0xcc, 0x2d, 0xe1,
+ 0xf1, 0x6d, 0x47, 0xb4, 0x40, 0xca, 0xd9, 0x0a, 0x19, 0x45},
+ {0x7e, 0xb1, 0xa0, 0x42, 0x9b, 0xe5, 0xf4, 0x28, 0xac, 0x2b,
+ 0x93, 0x97, 0x1d, 0x7c, 0x84, 0x48, 0xa5, 0x36, 0x07, 0x0c},
+ {0x7f, 0x88, 0xcd, 0x72, 0x23, 0xf3, 0xc8, 0x13, 0x81, 0x8c,
+ 0x99, 0x46, 0x14, 0xa8, 0x9c, 0x99, 0xfa, 0x3b, 0x52, 0x47},
+ {0x7f, 0x8a, 0x77, 0x83, 0x6b, 0xdc, 0x6d, 0x06, 0x8f, 0x8b,
+ 0x07, 0x37, 0xfc, 0xc5, 0x72, 0x54, 0x13, 0x06, 0x8c, 0xa4},
+ {0x7f, 0x8a, 0xb0, 0xcf, 0xd0, 0x51, 0x87, 0x6a, 0x66, 0xf3,
+ 0x36, 0x0f, 0x47, 0xc8, 0x8d, 0x8c, 0xd3, 0x35, 0xfc, 0x74},
+ {0x7f, 0xb9, 0xe2, 0xc9, 0x95, 0xc9, 0x7a, 0x93, 0x9f, 0x9e,
+ 0x81, 0xa0, 0x7a, 0xea, 0x9b, 0x4d, 0x70, 0x46, 0x34, 0x96},
+ {0x7f, 0xbb, 0x6a, 0xcd, 0x7e, 0x0a, 0xb4, 0x38, 0xda, 0xaf,
+ 0x6f, 0xd5, 0x02, 0x10, 0xd0, 0x07, 0xc6, 0xc0, 0x82, 0x9c},
+ {0x80, 0x25, 0xef, 0xf4, 0x6e, 0x70, 0xc8, 0xd4, 0x72, 0x24,
+ 0x65, 0x84, 0xfe, 0x40, 0x3b, 0x8a, 0x8d, 0x6a, 0xdb, 0xf5},
+ {0x80, 0xbf, 0x3d, 0xe9, 0xa4, 0x1d, 0x76, 0x8d, 0x19, 0x4b,
+ 0x29, 0x3c, 0x85, 0x63, 0x2c, 0xdb, 0xc8, 0xea, 0x8c, 0xf7},
+ {0x81, 0x96, 0x8b, 0x3a, 0xef, 0x1c, 0xdc, 0x70, 0xf5, 0xfa,
+ 0x32, 0x69, 0xc2, 0x92, 0xa3, 0x63, 0x5b, 0xd1, 0x23, 0xd3},
+ {0x82, 0x50, 0xbe, 0xd5, 0xa2, 0x14, 0x43, 0x3a, 0x66, 0x37,
+ 0x7c, 0xbc, 0x10, 0xef, 0x83, 0xf6, 0x69, 0xda, 0x3a, 0x67},
+ {0x83, 0x8e, 0x30, 0xf7, 0x7f, 0xdd, 0x14, 0xaa, 0x38, 0x5e,
+ 0xd1, 0x45, 0x00, 0x9c, 0x0e, 0x22, 0x36, 0x49, 0x4f, 0xaa},
+ {0x85, 0x37, 0x1c, 0xa6, 0xe5, 0x50, 0x14, 0x3d, 0xce, 0x28,
+ 0x03, 0x47, 0x1b, 0xde, 0x3a, 0x09, 0xe8, 0xf8, 0x77, 0x0f},
+ {0x85, 0xa4, 0x08, 0xc0, 0x9c, 0x19, 0x3e, 0x5d, 0x51, 0x58,
+ 0x7d, 0xcd, 0xd6, 0x13, 0x30, 0xfd, 0x8c, 0xde, 0x37, 0xbf},
+ {0x85, 0xb5, 0xff, 0x67, 0x9b, 0x0c, 0x79, 0x96, 0x1f, 0xc8,
+ 0x6e, 0x44, 0x22, 0x00, 0x46, 0x13, 0xdb, 0x17, 0x92, 0x84},
+ {0x87, 0x81, 0xc2, 0x5a, 0x96, 0xbd, 0xc2, 0xfb, 0x4c, 0x65,
+ 0x06, 0x4f, 0xf9, 0x39, 0x0b, 0x26, 0x04, 0x8a, 0x0e, 0x01},
+ {0x87, 0x82, 0xc6, 0xc3, 0x04, 0x35, 0x3b, 0xcf, 0xd2, 0x96,
+ 0x92, 0xd2, 0x59, 0x3e, 0x7d, 0x44, 0xd9, 0x34, 0xff, 0x11},
+ {0x87, 0x9f, 0x4b, 0xee, 0x05, 0xdf, 0x98, 0x58, 0x3b, 0xe3,
+ 0x60, 0xd6, 0x33, 0xe7, 0x0d, 0x3f, 0xfe, 0x98, 0x71, 0xaf},
+ {0x89, 0xc3, 0x2e, 0x6b, 0x52, 0x4e, 0x4d, 0x65, 0x38, 0x8b,
+ 0x9e, 0xce, 0xdc, 0x63, 0x71, 0x34, 0xed, 0x41, 0x93, 0xa3},
+ {0x89, 0xdf, 0x74, 0xfe, 0x5c, 0xf4, 0x0f, 0x4a, 0x80, 0xf9,
+ 0xe3, 0x37, 0x7d, 0x54, 0xda, 0x91, 0xe1, 0x01, 0x31, 0x8e},
+ {0x8b, 0x1a, 0x11, 0x06, 0xb8, 0xe2, 0x6b, 0x23, 0x29, 0x80,
+ 0xfd, 0x65, 0x2e, 0x61, 0x81, 0x37, 0x64, 0x41, 0xfd, 0x11},
+ {0x8b, 0xaf, 0x4c, 0x9b, 0x1d, 0xf0, 0x2a, 0x92, 0xf7, 0xda,
+ 0x12, 0x8e, 0xb9, 0x1b, 0xac, 0xf4, 0x98, 0x60, 0x4b, 0x6f},
+ {0x8c, 0x96, 0xba, 0xeb, 0xdd, 0x2b, 0x07, 0x07, 0x48, 0xee,
+ 0x30, 0x32, 0x66, 0xa0, 0xf3, 0x98, 0x6e, 0x7c, 0xae, 0x58},
+ {0x8c, 0xc4, 0x30, 0x7b, 0xc6, 0x07, 0x55, 0xe7, 0xb2, 0x2d,
+ 0xd9, 0xf7, 0xfe, 0xa2, 0x45, 0x93, 0x6c, 0x7c, 0xf2, 0x88},
+ {0x8c, 0xf4, 0x27, 0xfd, 0x79, 0x0c, 0x3a, 0xd1, 0x66, 0x06,
+ 0x8d, 0xe8, 0x1e, 0x57, 0xef, 0xbb, 0x93, 0x22, 0x72, 0xd4},
+ {0x8d, 0x08, 0xfc, 0x43, 0xc0, 0x77, 0x0c, 0xa8, 0x4f, 0x4d,
+ 0xcc, 0xb2, 0xd4, 0x1a, 0x5d, 0x95, 0x6d, 0x78, 0x6d, 0xc4},
+ {0x8d, 0x17, 0x84, 0xd5, 0x37, 0xf3, 0x03, 0x7d, 0xec, 0x70,
+ 0xfe, 0x57, 0x8b, 0x51, 0x9a, 0x99, 0xe6, 0x10, 0xd7, 0xb0},
+ {0x8e, 0x10, 0x32, 0xe9, 0x24, 0x59, 0x44, 0xf8, 0x47, 0x91,
+ 0x98, 0x3e, 0xc9, 0xe8, 0x29, 0xcb, 0x10, 0x59, 0xb4, 0xd3},
+ {0x8e, 0xb0, 0x3f, 0xc3, 0xcf, 0x7b, 0xb2, 0x92, 0x86, 0x62,
+ 0x68, 0xb7, 0x51, 0x22, 0x3d, 0xb5, 0x10, 0x34, 0x05, 0xcb},
+ {0x8e, 0xfd, 0xca, 0xbc, 0x93, 0xe6, 0x1e, 0x92, 0x5d, 0x4d,
+ 0x1d, 0xed, 0x18, 0x1a, 0x43, 0x20, 0xa4, 0x67, 0xa1, 0x39},
+ {0x8f, 0x43, 0x28, 0x8a, 0xd2, 0x72, 0xf3, 0x10, 0x3b, 0x6f,
+ 0xb1, 0x42, 0x84, 0x85, 0xea, 0x30, 0x14, 0xc0, 0xbc, 0xfe},
+ {0x90, 0x5f, 0x94, 0x2f, 0xd9, 0xf2, 0x8f, 0x67, 0x9b, 0x37,
+ 0x81, 0x80, 0xfd, 0x4f, 0x84, 0x63, 0x47, 0xf6, 0x45, 0xc1},
+ {0x90, 0x78, 0xc5, 0xa2, 0x8f, 0x9a, 0x43, 0x25, 0xc2, 0xa7,
+ 0xc7, 0x38, 0x13, 0xcd, 0xfe, 0x13, 0xc2, 0x0f, 0x93, 0x4e},
+ {0x90, 0xae, 0xa2, 0x69, 0x85, 0xff, 0x14, 0x80, 0x4c, 0x43,
+ 0x49, 0x52, 0xec, 0xe9, 0x60, 0x84, 0x77, 0xaf, 0x55, 0x6f},
+ {0x90, 0xde, 0xce, 0x77, 0xf8, 0xc8, 0x25, 0x34, 0x0e, 0x62,
+ 0xeb, 0xd6, 0x35, 0xe1, 0xbe, 0x20, 0xcf, 0x73, 0x27, 0xdd},
+ {0x90, 0xde, 0xde, 0x9e, 0x4c, 0x4e, 0x9f, 0x6f, 0xd8, 0x86,
+ 0x17, 0x57, 0x9d, 0xd3, 0x91, 0xbc, 0x65, 0xa6, 0x89, 0x64},
+ {0x91, 0x21, 0x98, 0xee, 0xf2, 0x3d, 0xca, 0xc4, 0x09, 0x39,
+ 0x31, 0x2f, 0xee, 0x97, 0xdd, 0x56, 0x0b, 0xae, 0x49, 0xb1},
+ {0x91, 0x58, 0xc5, 0xef, 0x98, 0x73, 0x01, 0xa8, 0x90, 0x3c,
+ 0xfd, 0xab, 0x03, 0xd7, 0x2d, 0xa1, 0xd8, 0x89, 0x09, 0xc9},
+ {0x91, 0xc6, 0xd6, 0xee, 0x3e, 0x8a, 0xc8, 0x63, 0x84, 0xe5,
+ 0x48, 0xc2, 0x99, 0x29, 0x5c, 0x75, 0x6c, 0x81, 0x7b, 0x81},
+ {0x92, 0x5a, 0x8f, 0x8d, 0x2c, 0x6d, 0x04, 0xe0, 0x66, 0x5f,
+ 0x59, 0x6a, 0xff, 0x22, 0xd8, 0x63, 0xe8, 0x25, 0x6f, 0x3f},
+ {0x93, 0xe6, 0xab, 0x22, 0x03, 0x03, 0xb5, 0x23, 0x28, 0xdc,
+ 0xda, 0x56, 0x9e, 0xba, 0xe4, 0xd1, 0xd1, 0xcc, 0xfb, 0x65},
+ {0x93, 0xf7, 0xf4, 0x8b, 0x12, 0x61, 0x94, 0x3f, 0x6a, 0x78,
+ 0x21, 0x0c, 0x52, 0xe6, 0x26, 0xdf, 0xbf, 0xbb, 0xe2, 0x60},
+ {0x96, 0x56, 0xcd, 0x7b, 0x57, 0x96, 0x98, 0x95, 0xd0, 0xe1,
+ 0x41, 0x46, 0x68, 0x06, 0xfb, 0xb8, 0xc6, 0x11, 0x06, 0x87},
+ {0x96, 0x83, 0x38, 0xf1, 0x13, 0xe3, 0x6a, 0x7b, 0xab, 0xdd,
+ 0x08, 0xf7, 0x77, 0x63, 0x91, 0xa6, 0x87, 0x36, 0x58, 0x2e},
+ {0x96, 0x97, 0x4c, 0xd6, 0xb6, 0x63, 0xa7, 0x18, 0x45, 0x26,
+ 0xb1, 0xd6, 0x48, 0xad, 0x81, 0x5c, 0xf5, 0x1e, 0x80, 0x1a},
+ {0x96, 0xc9, 0x1b, 0x0b, 0x95, 0xb4, 0x10, 0x98, 0x42, 0xfa,
+ 0xd0, 0xd8, 0x22, 0x79, 0xfe, 0x60, 0xfa, 0xb9, 0x16, 0x83},
+ {0x97, 0x1d, 0x34, 0x86, 0xfc, 0x1e, 0x8e, 0x63, 0x15, 0xf7,
+ 0xc6, 0xf2, 0xe1, 0x29, 0x67, 0xc7, 0x24, 0x34, 0x22, 0x14},
+ {0x97, 0x22, 0x6a, 0xae, 0x4a, 0x7a, 0x64, 0xa5, 0x9b, 0xd1,
+ 0x67, 0x87, 0xf2, 0x7f, 0x84, 0x1c, 0x0a, 0x00, 0x1f, 0xd0},
+ {0x97, 0x81, 0x79, 0x50, 0xd8, 0x1c, 0x96, 0x70, 0xcc, 0x34,
+ 0xd8, 0x09, 0xcf, 0x79, 0x44, 0x31, 0x36, 0x7e, 0xf4, 0x74},
+ {0x97, 0xe2, 0xe9, 0x96, 0x36, 0xa5, 0x47, 0x55, 0x4f, 0x83,
+ 0x8f, 0xba, 0x38, 0xb8, 0x2e, 0x74, 0xf8, 0x9a, 0x83, 0x0a},
+ {0x99, 0xa6, 0x9b, 0xe6, 0x1a, 0xfe, 0x88, 0x6b, 0x4d, 0x2b,
+ 0x82, 0x00, 0x7c, 0xb8, 0x54, 0xfc, 0x31, 0x7e, 0x15, 0x39},
+ {0x9b, 0xaa, 0xe5, 0x9f, 0x56, 0xee, 0x21, 0xcb, 0x43, 0x5a,
+ 0xbe, 0x25, 0x93, 0xdf, 0xa7, 0xf0, 0x40, 0xd1, 0x1d, 0xcb},
+ {0x9c, 0x61, 0x5c, 0x4d, 0x4d, 0x85, 0x10, 0x3a, 0x53, 0x26,
+ 0xc2, 0x4d, 0xba, 0xea, 0xe4, 0xa2, 0xd2, 0xd5, 0xcc, 0x97},
+ {0x9e, 0xd1, 0x80, 0x28, 0xfb, 0x1e, 0x8a, 0x97, 0x01, 0x48,
+ 0x0a, 0x78, 0x90, 0xa5, 0x9a, 0xcd, 0x73, 0xdf, 0xf8, 0x71},
+ {0x9f, 0x74, 0x4e, 0x9f, 0x2b, 0x4d, 0xba, 0xec, 0x0f, 0x31,
+ 0x2c, 0x50, 0xb6, 0x56, 0x3b, 0x8e, 0x2d, 0x93, 0xc3, 0x11},
+ {0x9f, 0xad, 0x91, 0xa6, 0xce, 0x6a, 0xc6, 0xc5, 0x00, 0x47,
+ 0xc4, 0x4e, 0xc9, 0xd4, 0xa5, 0x0d, 0x92, 0xd8, 0x49, 0x79},
+ {0x9f, 0xc7, 0x96, 0xe8, 0xf8, 0x52, 0x4f, 0x86, 0x3a, 0xe1,
+ 0x49, 0x6d, 0x38, 0x12, 0x42, 0x10, 0x5f, 0x1b, 0x78, 0xf5},
+ {0xa0, 0x73, 0xe5, 0xc5, 0xbd, 0x43, 0x61, 0x0d, 0x86, 0x4c,
+ 0x21, 0x13, 0x0a, 0x85, 0x58, 0x57, 0xcc, 0x9c, 0xea, 0x46},
+ {0xa0, 0xa1, 0xab, 0x90, 0xc9, 0xfc, 0x84, 0x7b, 0x3b, 0x12,
+ 0x61, 0xe8, 0x97, 0x7d, 0x5f, 0xd3, 0x22, 0x61, 0xd3, 0xcc},
+ {0xa0, 0xf8, 0xdb, 0x3f, 0x0b, 0xf4, 0x17, 0x69, 0x3b, 0x28,
+ 0x2e, 0xb7, 0x4a, 0x6a, 0xd8, 0x6d, 0xf9, 0xd4, 0x48, 0xa3},
+ {0xa1, 0x58, 0x51, 0x87, 0x15, 0x65, 0x86, 0xce, 0xf9, 0xc4,
+ 0x54, 0xe2, 0x2a, 0xb1, 0x5c, 0x58, 0x74, 0x56, 0x07, 0xb4},
+ {0xa1, 0xdb, 0x63, 0x93, 0x91, 0x6f, 0x17, 0xe4, 0x18, 0x55,
+ 0x09, 0x40, 0x04, 0x15, 0xc7, 0x02, 0x40, 0xb0, 0xae, 0x6b},
+ {0xa1, 0xe7, 0xc6, 0x00, 0xaa, 0x41, 0x70, 0xe5, 0xb7, 0x4b,
+ 0xc9, 0x4f, 0x9b, 0x97, 0x03, 0xed, 0xc2, 0x61, 0xb4, 0xb9},
+ {0xa3, 0x99, 0xf7, 0x6f, 0x0c, 0xbf, 0x4c, 0x9d, 0xa5, 0x5e,
+ 0x4a, 0xc2, 0x4e, 0x89, 0x60, 0x98, 0x4b, 0x29, 0x05, 0xb6},
+ {0xa3, 0xe3, 0x1e, 0x20, 0xb2, 0xe4, 0x6a, 0x32, 0x85, 0x20,
+ 0x47, 0x2d, 0x0c, 0xde, 0x95, 0x23, 0xe7, 0x26, 0x0c, 0x6d},
+ {0xa4, 0x34, 0x89, 0x15, 0x9a, 0x52, 0x0f, 0x0d, 0x93, 0xd0,
+ 0x32, 0xcc, 0xaf, 0x37, 0xe7, 0xfe, 0x20, 0xa8, 0xb4, 0x19},
+ {0xa5, 0x9c, 0x9b, 0x10, 0xec, 0x73, 0x57, 0x51, 0x5a, 0xbb,
+ 0x66, 0x0c, 0x4d, 0x94, 0xf7, 0x3b, 0x9e, 0x6e, 0x92, 0x72},
+ {0xa5, 0xec, 0x73, 0xd4, 0x8c, 0x34, 0xfc, 0xbe, 0xf1, 0x00,
+ 0x5a, 0xeb, 0x85, 0x84, 0x35, 0x24, 0xbb, 0xfa, 0xb7, 0x27},
+ {0xa6, 0x9a, 0x91, 0xfd, 0x05, 0x7f, 0x13, 0x6a, 0x42, 0x63,
+ 0x0b, 0xb1, 0x76, 0x0d, 0x2d, 0x51, 0x12, 0x0c, 0x16, 0x50},
+ {0xa7, 0xf8, 0x39, 0x0b, 0xa5, 0x77, 0x05, 0x09, 0x6f, 0xd3,
+ 0x69, 0x41, 0xd4, 0x2e, 0x71, 0x98, 0xc6, 0xd4, 0xd9, 0xd5},
+ {0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7,
+ 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36},
+ {0xa9, 0x62, 0x8f, 0x4b, 0x98, 0xa9, 0x1b, 0x48, 0x35, 0xba,
+ 0xd2, 0xc1, 0x46, 0x32, 0x86, 0xbb, 0x66, 0x64, 0x6a, 0x8c},
+ {0xa9, 0x82, 0x2e, 0x6c, 0x69, 0x33, 0xc6, 0x3c, 0x14, 0x8c,
+ 0x2d, 0xca, 0xa4, 0x4a, 0x5c, 0xf1, 0xaa, 0xd2, 0xc4, 0x2e},
+ {0xa9, 0xe9, 0x78, 0x08, 0x14, 0x37, 0x58, 0x88, 0xf2, 0x05,
+ 0x19, 0xb0, 0x6d, 0x2b, 0x0d, 0x2b, 0x60, 0x16, 0x90, 0x7d},
+ {0xaa, 0xdb, 0xbc, 0x22, 0x23, 0x8f, 0xc4, 0x01, 0xa1, 0x27,
+ 0xbb, 0x38, 0xdd, 0xf4, 0x1d, 0xdb, 0x08, 0x9e, 0xf0, 0x12},
+ {0xab, 0x16, 0xdd, 0x14, 0x4e, 0xcd, 0xc0, 0xfc, 0x4b, 0xaa,
+ 0xb6, 0x2e, 0xcf, 0x04, 0x08, 0x89, 0x6f, 0xde, 0x52, 0xb7},
+ {0xab, 0x48, 0xf3, 0x33, 0xdb, 0x04, 0xab, 0xb9, 0xc0, 0x72,
+ 0xda, 0x5b, 0x0c, 0xc1, 0xd0, 0x57, 0xf0, 0x36, 0x9b, 0x46},
+ {0xab, 0x9d, 0x58, 0xc0, 0x3f, 0x54, 0xb1, 0xda, 0xe3, 0xf7,
+ 0xc2, 0xd4, 0xc6, 0xc1, 0xec, 0x36, 0x94, 0x55, 0x9c, 0x37},
+ {0xac, 0xed, 0x5f, 0x65, 0x53, 0xfd, 0x25, 0xce, 0x01, 0x5f,
+ 0x1f, 0x7a, 0x48, 0x3b, 0x6a, 0x74, 0x9f, 0x61, 0x78, 0xc6},
+ {0xad, 0x7e, 0x1c, 0x28, 0xb0, 0x64, 0xef, 0x8f, 0x60, 0x03,
+ 0x40, 0x20, 0x14, 0xc3, 0xd0, 0xe3, 0x37, 0x0e, 0xb5, 0x8a},
+ {0xae, 0x3b, 0x31, 0xbf, 0x8f, 0xd8, 0x91, 0x07, 0x9c, 0xf1,
+ 0xdf, 0x34, 0xcb, 0xce, 0x6e, 0x70, 0xd3, 0x7f, 0xb5, 0xb0},
+ {0xae, 0x50, 0x83, 0xed, 0x7c, 0xf4, 0x5c, 0xbc, 0x8f, 0x61,
+ 0xc6, 0x21, 0xfe, 0x68, 0x5d, 0x79, 0x42, 0x21, 0x15, 0x6e},
+ {0xae, 0xc5, 0xfb, 0x3f, 0xc8, 0xe1, 0xbf, 0xc4, 0xe5, 0x4f,
+ 0x03, 0x07, 0x5a, 0x9a, 0xe8, 0x00, 0xb7, 0xf7, 0xb6, 0xfa},
+ {0xaf, 0xe5, 0xd2, 0x44, 0xa8, 0xd1, 0x19, 0x42, 0x30, 0xff,
+ 0x47, 0x9f, 0xe2, 0xf8, 0x97, 0xbb, 0xcd, 0x7a, 0x8c, 0xb4},
+ {0xb1, 0x2e, 0x13, 0x63, 0x45, 0x86, 0xa4, 0x6f, 0x1a, 0xb2,
+ 0x60, 0x68, 0x37, 0x58, 0x2d, 0xc4, 0xac, 0xfd, 0x94, 0x97},
+ {0xb1, 0x72, 0xb1, 0xa5, 0x6d, 0x95, 0xf9, 0x1f, 0xe5, 0x02,
+ 0x87, 0xe1, 0x4d, 0x37, 0xea, 0x6a, 0x44, 0x63, 0x76, 0x8a},
+ {0xb1, 0x9d, 0xd0, 0x96, 0xdc, 0xd4, 0xe3, 0xe0, 0xfd, 0x67,
+ 0x68, 0x85, 0x50, 0x5a, 0x67, 0x2c, 0x43, 0x8d, 0x4e, 0x9c},
+ {0xb1, 0xb2, 0x36, 0x4f, 0xd4, 0xd4, 0xf5, 0x2e, 0x89, 0xb2,
+ 0xd0, 0xfa, 0xf3, 0x3e, 0x4d, 0x62, 0xbd, 0x96, 0x99, 0x21},
+ {0xb1, 0xbc, 0x96, 0x8b, 0xd4, 0xf4, 0x9d, 0x62, 0x2a, 0xa8,
+ 0x9a, 0x81, 0xf2, 0x15, 0x01, 0x52, 0xa4, 0x1d, 0x82, 0x9c},
+ {0xb1, 0xea, 0xc3, 0xe5, 0xb8, 0x24, 0x76, 0xe9, 0xd5, 0x0b,
+ 0x1e, 0xc6, 0x7d, 0x2c, 0xc1, 0x1e, 0x12, 0xe0, 0xb4, 0x91},
+ {0xb3, 0x1e, 0xb1, 0xb7, 0x40, 0xe3, 0x6c, 0x84, 0x02, 0xda,
+ 0xdc, 0x37, 0xd4, 0x4d, 0xf5, 0xd4, 0x67, 0x49, 0x52, 0xf9},
+ {0xb3, 0x8f, 0xec, 0xec, 0x0b, 0x14, 0x8a, 0xa6, 0x86, 0xc3,
+ 0xd0, 0x0f, 0x01, 0xec, 0xc8, 0x84, 0x8e, 0x80, 0x85, 0xeb},
+ {0xb3, 0xea, 0xc4, 0x47, 0x76, 0xc9, 0xc8, 0x1c, 0xea, 0xf2,
+ 0x9d, 0x95, 0xb6, 0xcc, 0xa0, 0x08, 0x1b, 0x67, 0xec, 0x9d},
+ {0xb4, 0x35, 0xd4, 0xe1, 0x11, 0x9d, 0x1c, 0x66, 0x90, 0xa7,
+ 0x49, 0xeb, 0xb3, 0x94, 0xbd, 0x63, 0x7b, 0xa7, 0x82, 0xb7},
+ {0xb5, 0x1c, 0x06, 0x7c, 0xee, 0x2b, 0x0c, 0x3d, 0xf8, 0x55,
+ 0xab, 0x2d, 0x92, 0xf4, 0xfe, 0x39, 0xd4, 0xe7, 0x0f, 0x0e},
+ {0xb7, 0x2f, 0xff, 0x92, 0xd2, 0xce, 0x43, 0xde, 0x0a, 0x8d,
+ 0x4c, 0x54, 0x8c, 0x50, 0x37, 0x26, 0xa8, 0x1e, 0x2b, 0x93},
+ {0xb8, 0x01, 0x86, 0xd1, 0xeb, 0x9c, 0x86, 0xa5, 0x41, 0x04,
+ 0xcf, 0x30, 0x54, 0xf3, 0x4c, 0x52, 0xb7, 0xe5, 0x58, 0xc6},
+ {0xb8, 0x23, 0x6b, 0x00, 0x2f, 0x1d, 0x16, 0x86, 0x53, 0x01,
+ 0x55, 0x6c, 0x11, 0xa4, 0x37, 0xca, 0xeb, 0xff, 0xc3, 0xbb},
+ {0xb8, 0x65, 0x13, 0x0b, 0xed, 0xca, 0x38, 0xd2, 0x7f, 0x69,
+ 0x92, 0x94, 0x20, 0x77, 0x0b, 0xed, 0x86, 0xef, 0xbc, 0x10},
+ {0xb9, 0xcd, 0x0c, 0xf6, 0x98, 0x35, 0xea, 0xbf, 0x3f, 0x13,
+ 0x7f, 0x20, 0x49, 0xe4, 0xc9, 0x24, 0x87, 0x84, 0x77, 0xdb},
+ {0xbc, 0x7b, 0x3c, 0x6f, 0xef, 0x26, 0xb9, 0xf7, 0xab, 0x10,
+ 0xd7, 0xa1, 0xf6, 0xb6, 0x7c, 0x5e, 0xd2, 0xa1, 0x2d, 0x3d},
+ {0xbc, 0x92, 0x19, 0xdd, 0xc9, 0x8e, 0x14, 0xbf, 0x1a, 0x78,
+ 0x1f, 0x6e, 0x28, 0x0b, 0x04, 0xc2, 0x7f, 0x90, 0x27, 0x12},
+ {0xbe, 0x36, 0xa4, 0x56, 0x2f, 0xb2, 0xee, 0x05, 0xdb, 0xb3,
+ 0xd3, 0x23, 0x23, 0xad, 0xf4, 0x45, 0x08, 0x4e, 0xd6, 0x56},
+ {0xbe, 0xb5, 0xa9, 0x95, 0x74, 0x6b, 0x9e, 0xdf, 0x73, 0x8b,
+ 0x56, 0xe6, 0xdf, 0x43, 0x7a, 0x77, 0xbe, 0x10, 0x6b, 0x81},
+ {0xbe, 0xd5, 0x25, 0xd1, 0xac, 0x63, 0xa7, 0xfc, 0x6a, 0x66,
+ 0x0b, 0xa7, 0xa8, 0x95, 0x81, 0x8d, 0x5e, 0x8d, 0xd5, 0x64},
+ {0xc0, 0x9a, 0xb0, 0xc8, 0xad, 0x71, 0x14, 0x71, 0x4e, 0xd5,
+ 0xe2, 0x1a, 0x5a, 0x27, 0x6a, 0xdc, 0xd5, 0xe7, 0xef, 0xcb},
+ {0xc0, 0xdb, 0x57, 0x81, 0x57, 0xe9, 0xee, 0x82, 0xb5, 0x91,
+ 0x7d, 0xf0, 0xdd, 0x6d, 0x82, 0xee, 0x90, 0x39, 0xc4, 0xe2},
+ {0xc1, 0x82, 0x11, 0x32, 0x8a, 0x92, 0xb3, 0xb2, 0x38, 0x09,
+ 0xb9, 0xb5, 0xe2, 0x74, 0x0a, 0x07, 0xfb, 0x12, 0xeb, 0x5e},
+ {0xc4, 0x67, 0x4d, 0xdc, 0x6c, 0xe2, 0x96, 0x7f, 0xf9, 0xc9,
+ 0x2e, 0x07, 0x2e, 0xf8, 0xe8, 0xa7, 0xfb, 0xd6, 0xa1, 0x31},
+ {0xc7, 0x30, 0x26, 0xe3, 0x25, 0xfe, 0x21, 0x91, 0x6b, 0x55,
+ 0xc4, 0xb5, 0x3a, 0x56, 0xb1, 0x3d, 0xca, 0xf3, 0xd6, 0x25},
+ {0xc7, 0xf7, 0xcb, 0xe2, 0x02, 0x36, 0x66, 0xf9, 0x86, 0x02,
+ 0x5d, 0x4a, 0x3e, 0x31, 0x3f, 0x29, 0xeb, 0x0c, 0x5b, 0x38},
+ {0xc8, 0xec, 0x8c, 0x87, 0x92, 0x69, 0xcb, 0x4b, 0xab, 0x39,
+ 0xe9, 0x8d, 0x7e, 0x57, 0x67, 0xf3, 0x14, 0x95, 0x73, 0x9d},
+ {0xc9, 0x32, 0x1d, 0xe6, 0xb5, 0xa8, 0x26, 0x66, 0xcf, 0x69,
+ 0x71, 0xa1, 0x8a, 0x56, 0xf2, 0xd3, 0xa8, 0x67, 0x56, 0x02},
+ {0xc9, 0x3c, 0x34, 0xea, 0x90, 0xd9, 0x13, 0x0c, 0x0f, 0x03,
+ 0x00, 0x4b, 0x98, 0xbd, 0x8b, 0x35, 0x70, 0x91, 0x56, 0x11},
+ {0xc9, 0xa8, 0xb9, 0xe7, 0x55, 0x80, 0x5e, 0x58, 0xe3, 0x53,
+ 0x77, 0xa7, 0x25, 0xeb, 0xaf, 0xc3, 0x7b, 0x27, 0xcc, 0xd7},
+ {0xca, 0x3a, 0xfb, 0xcf, 0x12, 0x40, 0x36, 0x4b, 0x44, 0xb2,
+ 0x16, 0x20, 0x88, 0x80, 0x48, 0x39, 0x19, 0x93, 0x7c, 0xf7},
+ {0xca, 0xbb, 0x51, 0x67, 0x24, 0x00, 0x58, 0x8e, 0x64, 0x19,
+ 0xf1, 0xd4, 0x08, 0x78, 0xd0, 0x40, 0x3a, 0xa2, 0x02, 0x64},
+ {0xcb, 0x44, 0xa0, 0x97, 0x85, 0x7c, 0x45, 0xfa, 0x18, 0x7e,
+ 0xd9, 0x52, 0x08, 0x6c, 0xb9, 0x84, 0x1f, 0x2d, 0x51, 0xb5},
+ {0xcb, 0x65, 0x82, 0x64, 0xea, 0x8c, 0xda, 0x18, 0x6e, 0x17,
+ 0x52, 0xfb, 0x52, 0xc3, 0x97, 0x36, 0x7e, 0xa3, 0x87, 0xbe},
+ {0xcb, 0xa1, 0xc5, 0xf8, 0xb0, 0xe3, 0x5e, 0xb8, 0xb9, 0x45,
+ 0x12, 0xd3, 0xf9, 0x34, 0xa2, 0xe9, 0x06, 0x10, 0xd3, 0x36},
+ {0xcc, 0x7e, 0xa2, 0x92, 0xaf, 0x87, 0x15, 0xd7, 0x4c, 0xa4,
+ 0xb4, 0x15, 0xf3, 0x20, 0x15, 0x4b, 0x24, 0xf5, 0x65, 0xfd},
+ {0xcd, 0xd4, 0xee, 0xae, 0x60, 0x00, 0xac, 0x7f, 0x40, 0xc3,
+ 0x80, 0x2c, 0x17, 0x1e, 0x30, 0x14, 0x80, 0x30, 0xc0, 0x72},
+ {0xce, 0x6a, 0x64, 0xa3, 0x09, 0xe4, 0x2f, 0xbb, 0xd9, 0x85,
+ 0x1c, 0x45, 0x3e, 0x64, 0x09, 0xea, 0xe8, 0x7d, 0x60, 0xf1},
+ {0xce, 0xa9, 0x89, 0x0d, 0x85, 0xd8, 0x07, 0x53, 0xa6, 0x26,
+ 0x28, 0x6c, 0xda, 0xd7, 0x8c, 0xb5, 0x66, 0xd7, 0x0c, 0xf2},
+ {0xcf, 0x9e, 0x87, 0x6d, 0xd3, 0xeb, 0xfc, 0x42, 0x26, 0x97,
+ 0xa3, 0xb5, 0xa3, 0x7a, 0xa0, 0x76, 0xa9, 0x06, 0x23, 0x48},
+ {0xcf, 0xde, 0xfe, 0x10, 0x2f, 0xda, 0x05, 0xbb, 0xe4, 0xc7,
+ 0x8d, 0x2e, 0x44, 0x23, 0x58, 0x90, 0x05, 0xb2, 0x57, 0x1d},
+ {0xcf, 0xe4, 0x31, 0x3d, 0xba, 0x05, 0xb8, 0xa7, 0xc3, 0x00,
+ 0x63, 0x99, 0x5a, 0x9e, 0xb7, 0xc2, 0x47, 0xad, 0x8f, 0xd5},
+ {0xcf, 0xf3, 0x60, 0xf5, 0x24, 0xcb, 0x20, 0xf1, 0xfe, 0xad,
+ 0x89, 0x00, 0x6f, 0x7f, 0x58, 0x6a, 0x28, 0x5b, 0x2d, 0x5b},
+ {0xcf, 0xf8, 0x10, 0xfb, 0x2c, 0x4f, 0xfc, 0x01, 0x56, 0xbf,
+ 0xe1, 0xe1, 0xfa, 0xbc, 0xb4, 0x18, 0xc6, 0x8d, 0x31, 0xc5},
+ {0xd1, 0xcb, 0xca, 0x5d, 0xb2, 0xd5, 0x2a, 0x7f, 0x69, 0x3b,
+ 0x67, 0x4d, 0xe5, 0xf0, 0x5a, 0x1d, 0x0c, 0x95, 0x7d, 0xf0},
+ {0xd1, 0xeb, 0x23, 0xa4, 0x6d, 0x17, 0xd6, 0x8f, 0xd9, 0x25,
+ 0x64, 0xc2, 0xf1, 0xf1, 0x60, 0x17, 0x64, 0xd8, 0xe3, 0x49},
+ {0xd2, 0x32, 0x09, 0xad, 0x23, 0xd3, 0x14, 0x23, 0x21, 0x74,
+ 0xe4, 0x0d, 0x7f, 0x9d, 0x62, 0x13, 0x97, 0x86, 0x63, 0x3a},
+ {0xd2, 0x44, 0x1a, 0xa8, 0xc2, 0x03, 0xae, 0xca, 0xa9, 0x6e,
+ 0x50, 0x1f, 0x12, 0x4d, 0x52, 0xb6, 0x8f, 0xe4, 0xc3, 0x75},
+ {0xd2, 0x9f, 0x6c, 0x98, 0xbe, 0xfc, 0x6d, 0x98, 0x65, 0x21,
+ 0x54, 0x3e, 0xe8, 0xbe, 0x56, 0xce, 0xbc, 0x28, 0x8c, 0xf3},
+ {0xd2, 0xed, 0xf8, 0x8b, 0x41, 0xb6, 0xfe, 0x01, 0x46, 0x1d,
+ 0x6e, 0x28, 0x34, 0xec, 0x7c, 0x8f, 0x6c, 0x77, 0x72, 0x1e},
+ {0xd3, 0xc0, 0x63, 0xf2, 0x19, 0xed, 0x07, 0x3e, 0x34, 0xad,
+ 0x5d, 0x75, 0x0b, 0x32, 0x76, 0x29, 0xff, 0xd5, 0x9a, 0xf2},
+ {0xd4, 0xde, 0x20, 0xd0, 0x5e, 0x66, 0xfc, 0x53, 0xfe, 0x1a,
+ 0x50, 0x88, 0x2c, 0x78, 0xdb, 0x28, 0x52, 0xca, 0xe4, 0x74},
+ {0xd6, 0x9b, 0x56, 0x11, 0x48, 0xf0, 0x1c, 0x77, 0xc5, 0x45,
+ 0x78, 0xc1, 0x09, 0x26, 0xdf, 0x5b, 0x85, 0x69, 0x76, 0xad},
+ {0xd6, 0xbf, 0x79, 0x94, 0xf4, 0x2b, 0xe5, 0xfa, 0x29, 0xda,
+ 0x0b, 0xd7, 0x58, 0x7b, 0x59, 0x1f, 0x47, 0xa4, 0x4f, 0x22},
+ {0xd6, 0xda, 0xa8, 0x20, 0x8d, 0x09, 0xd2, 0x15, 0x4d, 0x24,
+ 0xb5, 0x2f, 0xcb, 0x34, 0x6e, 0xb2, 0x58, 0xb2, 0x8a, 0x58},
+ {0xd8, 0xa6, 0x33, 0x2c, 0xe0, 0x03, 0x6f, 0xb1, 0x85, 0xf6,
+ 0x63, 0x4f, 0x7d, 0x6a, 0x06, 0x65, 0x26, 0x32, 0x28, 0x27},
+ {0xd8, 0xc5, 0x38, 0x8a, 0xb7, 0x30, 0x1b, 0x1b, 0x6e, 0xd4,
+ 0x7a, 0xe6, 0x45, 0x25, 0x3a, 0x6f, 0x9f, 0x1a, 0x27, 0x61},
+ {0xd9, 0x04, 0x08, 0x0a, 0x49, 0x29, 0xc8, 0x38, 0xe9, 0xf1,
+ 0x85, 0xec, 0xf7, 0xa2, 0x2d, 0xef, 0x99, 0x34, 0x24, 0x07},
+ {0xda, 0x40, 0x18, 0x8b, 0x91, 0x89, 0xa3, 0xed, 0xee, 0xae,
+ 0xda, 0x97, 0xfe, 0x2f, 0x9d, 0xf5, 0xb7, 0xd1, 0x8a, 0x41},
+ {0xda, 0x8b, 0x65, 0x67, 0xef, 0x3f, 0x6e, 0x1e, 0xa2, 0x6a,
+ 0xb1, 0x46, 0xe3, 0x6c, 0xcb, 0x57, 0x28, 0x04, 0x18, 0x46},
+ {0xda, 0xc9, 0x02, 0x4f, 0x54, 0xd8, 0xf6, 0xdf, 0x94, 0x93,
+ 0x5f, 0xb1, 0x73, 0x26, 0x38, 0xca, 0x6a, 0xd7, 0x7c, 0x13},
+ {0xda, 0xfa, 0xf7, 0xfa, 0x66, 0x84, 0xec, 0x06, 0x8f, 0x14,
+ 0x50, 0xbd, 0xc7, 0xc2, 0x81, 0xa5, 0xbc, 0xa9, 0x64, 0x57},
+ {0xdb, 0xac, 0x3c, 0x7a, 0xa4, 0x25, 0x4d, 0xa1, 0xaa, 0x5c,
+ 0xaa, 0xd6, 0x84, 0x68, 0xcb, 0x88, 0xee, 0xdd, 0xee, 0xa8},
+ {0xdd, 0x83, 0xc5, 0x19, 0xd4, 0x34, 0x81, 0xfa, 0xd4, 0xc2,
+ 0x2c, 0x03, 0xd7, 0x02, 0xfe, 0x9f, 0x3b, 0x22, 0xf5, 0x17},
+ {0xdd, 0xe1, 0xd2, 0xa9, 0x01, 0x80, 0x2e, 0x1d, 0x87, 0x5e,
+ 0x84, 0xb3, 0x80, 0x7e, 0x4b, 0xb1, 0xfd, 0x99, 0x41, 0x34},
+ {0xde, 0x28, 0xf4, 0xa4, 0xff, 0xe5, 0xb9, 0x2f, 0xa3, 0xc5,
+ 0x03, 0xd1, 0xa3, 0x49, 0xa7, 0xf9, 0x96, 0x2a, 0x82, 0x12},
+ {0xde, 0x3f, 0x40, 0xbd, 0x50, 0x93, 0xd3, 0x9b, 0x6c, 0x60,
+ 0xf6, 0xda, 0xbc, 0x07, 0x62, 0x01, 0x00, 0x89, 0x76, 0xc9},
+ {0xde, 0x99, 0x0c, 0xed, 0x99, 0xe0, 0x43, 0x1f, 0x60, 0xed,
+ 0xc3, 0x93, 0x7e, 0x7c, 0xd5, 0xbf, 0x0e, 0xd9, 0xe5, 0xfa},
+ {0xdf, 0x64, 0x6d, 0xcb, 0x7b, 0x0f, 0xd3, 0xa9, 0x6a, 0xee,
+ 0x88, 0xc6, 0x4e, 0x2d, 0x67, 0x67, 0x11, 0xff, 0x9d, 0x5f},
+ {0xe0, 0x92, 0x5e, 0x18, 0xc7, 0x76, 0x5e, 0x22, 0xda, 0xbd,
+ 0x94, 0x27, 0x52, 0x9d, 0xa6, 0xaf, 0x4e, 0x06, 0x64, 0x28},
+ {0xe0, 0xab, 0x05, 0x94, 0x20, 0x72, 0x54, 0x93, 0x05, 0x60,
+ 0x62, 0x02, 0x36, 0x70, 0xf7, 0xcd, 0x2e, 0xfc, 0x66, 0x66},
+ {0xe0, 0xb4, 0x32, 0x2e, 0xb2, 0xf6, 0xa5, 0x68, 0xb6, 0x54,
+ 0x53, 0x84, 0x48, 0x18, 0x4a, 0x50, 0x36, 0x87, 0x43, 0x84},
+ {0xe1, 0x2d, 0xfb, 0x4b, 0x41, 0xd7, 0xd9, 0xc3, 0x2b, 0x30,
+ 0x51, 0x4b, 0xac, 0x1d, 0x81, 0xd8, 0x38, 0x5e, 0x2d, 0x46},
+ {0xe1, 0xa4, 0x5b, 0x14, 0x1a, 0x21, 0xda, 0x1a, 0x79, 0xf4,
+ 0x1a, 0x42, 0xa9, 0x61, 0xd6, 0x69, 0xcd, 0x06, 0x34, 0xc1},
+ {0xe3, 0x92, 0x51, 0x2f, 0x0a, 0xcf, 0xf5, 0x05, 0xdf, 0xf6,
+ 0xde, 0x06, 0x7f, 0x75, 0x37, 0xe1, 0x65, 0xea, 0x57, 0x4b},
+ {0xe3, 0xd7, 0x36, 0x06, 0x99, 0x6c, 0xdf, 0xef, 0x61, 0xfa,
+ 0x04, 0xc3, 0x35, 0xe9, 0x8e, 0xa9, 0x61, 0x04, 0x26, 0x4a},
+ {0xe5, 0xdf, 0x74, 0x3c, 0xb6, 0x01, 0xc4, 0x9b, 0x98, 0x43,
+ 0xdc, 0xab, 0x8c, 0xe8, 0x6a, 0x81, 0x10, 0x9f, 0xe4, 0x8e},
+ {0xe6, 0x19, 0xd2, 0x5b, 0x38, 0x0b, 0x7b, 0x13, 0xfd, 0xa3,
+ 0x3e, 0x8a, 0x58, 0xcd, 0x82, 0xd8, 0xa8, 0x8e, 0x05, 0x15},
+ {0xe6, 0x21, 0xf3, 0x35, 0x43, 0x79, 0x05, 0x9a, 0x4b, 0x68,
+ 0x30, 0x9d, 0x8a, 0x2f, 0x74, 0x22, 0x15, 0x87, 0xec, 0x79},
+ {0xe7, 0x07, 0x15, 0xf6, 0xf7, 0x28, 0x36, 0x5b, 0x51, 0x90,
+ 0xe2, 0x71, 0xde, 0xe4, 0xc6, 0x5e, 0xbe, 0xea, 0xca, 0xf3},
+ {0xe7, 0xb4, 0xf6, 0x9d, 0x61, 0xec, 0x90, 0x69, 0xdb, 0x7e,
+ 0x90, 0xa7, 0x40, 0x1a, 0x3c, 0xf4, 0x7d, 0x4f, 0xe8, 0xee},
+ {0xea, 0xbd, 0xa2, 0x40, 0x44, 0x0a, 0xbb, 0xd6, 0x94, 0x93,
+ 0x0a, 0x01, 0xd0, 0x97, 0x64, 0xc6, 0xc2, 0xd7, 0x79, 0x66},
+ {0xec, 0x0c, 0x37, 0x16, 0xea, 0x9e, 0xdf, 0xad, 0xd3, 0x5d,
+ 0xfb, 0xd5, 0x56, 0x08, 0xe6, 0x0a, 0x05, 0xd3, 0xcb, 0xf3},
+ {0xec, 0x93, 0xde, 0x08, 0x3c, 0x93, 0xd9, 0x33, 0xa9, 0x86,
+ 0xb3, 0xd5, 0xcd, 0xe2, 0x5a, 0xcb, 0x2f, 0xee, 0xcf, 0x8e},
+ {0xed, 0x8d, 0xc8, 0x38, 0x6c, 0x48, 0x86, 0xae, 0xee, 0x07,
+ 0x91, 0x58, 0xaa, 0xc3, 0xbf, 0xe6, 0x58, 0xe3, 0x94, 0xb4},
+ {0xed, 0xb3, 0xcb, 0x5f, 0xb4, 0x19, 0xa1, 0x85, 0x06, 0x62,
+ 0x67, 0xe5, 0x79, 0x15, 0x54, 0xe1, 0xe2, 0x8b, 0x63, 0x99},
+ {0xee, 0x29, 0xd6, 0xea, 0x98, 0xe6, 0x32, 0xc6, 0xe5, 0x27,
+ 0xe0, 0x90, 0x6f, 0x02, 0x80, 0x68, 0x8b, 0xdf, 0x44, 0xdc},
+ {0xee, 0x86, 0x93, 0x87, 0xff, 0xfd, 0x83, 0x49, 0xab, 0x5a,
+ 0xd1, 0x43, 0x22, 0x58, 0x87, 0x89, 0xa4, 0x57, 0xb0, 0x12},
+ {0xf1, 0x38, 0xa3, 0x30, 0xa4, 0xea, 0x98, 0x6b, 0xeb, 0x52,
+ 0x0b, 0xb1, 0x10, 0x35, 0x87, 0x6e, 0xfb, 0x9d, 0x7f, 0x1c},
+ {0xf1, 0x7f, 0x6f, 0xb6, 0x31, 0xdc, 0x99, 0xe3, 0xa3, 0xc8,
+ 0x7f, 0xfe, 0x1c, 0xf1, 0x81, 0x10, 0x88, 0xd9, 0x60, 0x33},
+ {0xf1, 0x8b, 0x53, 0x8d, 0x1b, 0xe9, 0x03, 0xb6, 0xa6, 0xf0,
+ 0x56, 0x43, 0x5b, 0x17, 0x15, 0x89, 0xca, 0xf3, 0x6b, 0xf2},
+ {0xf3, 0x73, 0xb3, 0x87, 0x06, 0x5a, 0x28, 0x84, 0x8a, 0xf2,
+ 0xf3, 0x4a, 0xce, 0x19, 0x2b, 0xdd, 0xc7, 0x8e, 0x9c, 0xac},
+ {0xf4, 0x40, 0x95, 0xc2, 0x38, 0xac, 0x73, 0xfc, 0x4f, 0x77,
+ 0xbf, 0x8f, 0x98, 0xdf, 0x70, 0xf8, 0xf0, 0x91, 0xbc, 0x52},
+ {0xf4, 0x8b, 0x11, 0xbf, 0xde, 0xab, 0xbe, 0x94, 0x54, 0x20,
+ 0x71, 0xe6, 0x41, 0xde, 0x6b, 0xbe, 0x88, 0x2b, 0x40, 0xb9},
+ {0xf5, 0xc2, 0x7c, 0xf5, 0xff, 0xf3, 0x02, 0x9a, 0xcf, 0x1a,
+ 0x1a, 0x4b, 0xec, 0x7e, 0xe1, 0x96, 0x4c, 0x77, 0xd7, 0x84},
+ {0xf9, 0xb5, 0xb6, 0x32, 0x45, 0x5f, 0x9c, 0xbe, 0xec, 0x57,
+ 0x5f, 0x80, 0xdc, 0xe9, 0x6e, 0x2c, 0xc7, 0xb2, 0x78, 0xb7},
+ {0xf9, 0xcd, 0x0e, 0x2c, 0xda, 0x76, 0x24, 0xc1, 0x8f, 0xbd,
+ 0xf0, 0xf0, 0xab, 0xb6, 0x45, 0xb8, 0xf7, 0xfe, 0xd5, 0x7a},
+ {0xf9, 0xdd, 0x19, 0x26, 0x6b, 0x20, 0x43, 0xf1, 0xfe, 0x4b,
+ 0x3d, 0xcb, 0x01, 0x90, 0xaf, 0xf1, 0x1f, 0x31, 0xa6, 0x9d},
+ {0xfa, 0x08, 0x82, 0x59, 0x5f, 0x9c, 0xa6, 0xa1, 0x1e, 0xcc,
+ 0xbe, 0xaf, 0x65, 0xc7, 0x64, 0xc0, 0xcc, 0xc3, 0x11, 0xd0},
+ {0xfa, 0xa7, 0xd9, 0xfb, 0x31, 0xb7, 0x46, 0xf2, 0x00, 0xa8,
+ 0x5e, 0x65, 0x79, 0x76, 0x13, 0xd8, 0x16, 0xe0, 0x63, 0xb5},
+ {0xfa, 0xaa, 0x27, 0xb8, 0xca, 0xf5, 0xfd, 0xf5, 0xcd, 0xa9,
+ 0x8a, 0xc3, 0x37, 0x85, 0x72, 0xe0, 0x4c, 0xe8, 0xf2, 0xe0},
+ {0xfa, 0xb7, 0xee, 0x36, 0x97, 0x26, 0x62, 0xfb, 0x2d, 0xb0,
+ 0x2a, 0xf6, 0xbf, 0x03, 0xfd, 0xe8, 0x7c, 0x4b, 0x2f, 0x9b},
+ {0xfd, 0x1e, 0xd1, 0xe2, 0x02, 0x1b, 0x0b, 0x9f, 0x73, 0xe8,
+ 0xeb, 0x75, 0xce, 0x23, 0x43, 0x6b, 0xbc, 0xc7, 0x46, 0xeb},
+ {0xfe, 0xb8, 0xc4, 0x32, 0xdc, 0xf9, 0x76, 0x9a, 0xce, 0xae,
+ 0x3d, 0xd8, 0x90, 0x8f, 0xfd, 0x28, 0x86, 0x65, 0x64, 0x7d},
+};
+
+#endif // NET_CERT_X509_CERTIFICATE_KNOWN_ROOTS_WIN_H_
diff --git a/chromium/net/cert/x509_certificate_mac.cc b/chromium/net/cert/x509_certificate_mac.cc
new file mode 100644
index 00000000000..2f8ce438afd
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_mac.cc
@@ -0,0 +1,611 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <CommonCrypto/CommonDigest.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+
+#include <cert.h>
+
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/memory/singleton.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "crypto/cssm_init.h"
+#include "crypto/mac_security_services_lock.h"
+#include "crypto/nss_util.h"
+#include "net/cert/x509_util_mac.h"
+
+using base::ScopedCFTypeRef;
+using base::Time;
+
+namespace net {
+
+namespace {
+
+void GetCertDistinguishedName(
+ const x509_util::CSSMCachedCertificate& cached_cert,
+ const CSSM_OID* oid,
+ CertPrincipal* result) {
+ x509_util::CSSMFieldValue distinguished_name;
+ OSStatus status = cached_cert.GetField(oid, &distinguished_name);
+ if (status || !distinguished_name.field())
+ return;
+ result->ParseDistinguishedName(distinguished_name.field()->Data,
+ distinguished_name.field()->Length);
+}
+
+bool IsCertIssuerInEncodedList(X509Certificate::OSCertHandle cert_handle,
+ const std::vector<std::string>& issuers) {
+ x509_util::CSSMCachedCertificate cached_cert;
+ if (cached_cert.Init(cert_handle) != CSSM_OK)
+ return false;
+
+ x509_util::CSSMFieldValue distinguished_name;
+ OSStatus status = cached_cert.GetField(&CSSMOID_X509V1IssuerNameStd,
+ &distinguished_name);
+ if (status || !distinguished_name.field())
+ return false;
+
+ base::StringPiece name_piece(
+ reinterpret_cast<const char*>(distinguished_name.field()->Data),
+ static_cast<size_t>(distinguished_name.field()->Length));
+
+ for (std::vector<std::string>::const_iterator it = issuers.begin();
+ it != issuers.end(); ++it) {
+ base::StringPiece issuer_piece(*it);
+ if (name_piece == issuer_piece)
+ return true;
+ }
+
+ return false;
+}
+
+void GetCertDateForOID(const x509_util::CSSMCachedCertificate& cached_cert,
+ const CSSM_OID* oid,
+ Time* result) {
+ *result = Time::Time();
+
+ x509_util::CSSMFieldValue field;
+ OSStatus status = cached_cert.GetField(oid, &field);
+ if (status)
+ return;
+
+ const CSSM_X509_TIME* x509_time = field.GetAs<CSSM_X509_TIME>();
+ if (x509_time->timeType != BER_TAG_UTC_TIME &&
+ x509_time->timeType != BER_TAG_GENERALIZED_TIME) {
+ LOG(ERROR) << "Unsupported date/time format "
+ << x509_time->timeType;
+ return;
+ }
+
+ base::StringPiece time_string(
+ reinterpret_cast<const char*>(x509_time->time.Data),
+ x509_time->time.Length);
+ CertDateFormat format = x509_time->timeType == BER_TAG_UTC_TIME ?
+ CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME;
+ if (!ParseCertificateDate(time_string, format, result))
+ LOG(ERROR) << "Invalid certificate date/time " << time_string;
+}
+
+std::string GetCertSerialNumber(
+ const x509_util::CSSMCachedCertificate& cached_cert) {
+ x509_util::CSSMFieldValue serial_number;
+ OSStatus status = cached_cert.GetField(&CSSMOID_X509V1SerialNumber,
+ &serial_number);
+ if (status || !serial_number.field())
+ return std::string();
+
+ return std::string(
+ reinterpret_cast<const char*>(serial_number.field()->Data),
+ serial_number.field()->Length);
+}
+
+// Returns true if |purpose| is listed as allowed in |usage|. This
+// function also considers the "Any" purpose. If the attribute is
+// present and empty, we return false.
+bool ExtendedKeyUsageAllows(const CE_ExtendedKeyUsage* usage,
+ const CSSM_OID* purpose) {
+ for (unsigned p = 0; p < usage->numPurposes; ++p) {
+ if (CSSMOIDEqual(&usage->purposes[p], purpose))
+ return true;
+ if (CSSMOIDEqual(&usage->purposes[p], &CSSMOID_ExtendedKeyUsageAny))
+ return true;
+ }
+ return false;
+}
+
+// Test that a given |cert_handle| is actually a valid X.509 certificate, and
+// return true if it is.
+//
+// On OS X, SecCertificateCreateFromData() does not return any errors if
+// called with invalid data, as long as data is present. The actual decoding
+// of the certificate does not happen until an API that requires a CSSM
+// handle is called. While SecCertificateGetCLHandle is the most likely
+// candidate, as it performs the parsing, it does not check whether the
+// parsing was actually successful. Instead, SecCertificateGetSubject is
+// used (supported since 10.3), as a means to check that the certificate
+// parsed as a valid X.509 certificate.
+bool IsValidOSCertHandle(SecCertificateRef cert_handle) {
+ const CSSM_X509_NAME* sanity_check = NULL;
+ OSStatus status = SecCertificateGetSubject(cert_handle, &sanity_check);
+ return status == noErr && sanity_check;
+}
+
+// Parses |data| of length |length|, attempting to decode it as the specified
+// |format|. If |data| is in the specified format, any certificates contained
+// within are stored into |output|.
+void AddCertificatesFromBytes(const char* data, size_t length,
+ SecExternalFormat format,
+ X509Certificate::OSCertHandles* output) {
+ SecExternalFormat input_format = format;
+ ScopedCFTypeRef<CFDataRef> local_data(CFDataCreateWithBytesNoCopy(
+ kCFAllocatorDefault, reinterpret_cast<const UInt8*>(data), length,
+ kCFAllocatorNull));
+
+ CFArrayRef items = NULL;
+ OSStatus status;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ status = SecKeychainItemImport(local_data, NULL, &input_format,
+ NULL, 0, NULL, NULL, &items);
+ }
+
+ if (status) {
+ OSSTATUS_DLOG(WARNING, status)
+ << "Unable to import items from data of length " << length;
+ return;
+ }
+
+ ScopedCFTypeRef<CFArrayRef> scoped_items(items);
+ CFTypeID cert_type_id = SecCertificateGetTypeID();
+
+ for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) {
+ SecKeychainItemRef item = reinterpret_cast<SecKeychainItemRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(items, i)));
+
+ // While inputFormat implies only certificates will be imported, if/when
+ // other formats (eg: PKCS#12) are supported, this may also include
+ // private keys or other items types, so filter appropriately.
+ if (CFGetTypeID(item) == cert_type_id) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(item);
+ // OS X ignores |input_format| if it detects that |local_data| is PEM
+ // encoded, attempting to decode data based on internal rules for PEM
+ // block headers. If a PKCS#7 blob is encoded with a PEM block of
+ // CERTIFICATE, OS X 10.5 will return a single, invalid certificate
+ // based on the decoded data. If this happens, the certificate should
+ // not be included in |output|. Because |output| is empty,
+ // CreateCertificateListfromBytes will use PEMTokenizer to decode the
+ // data. When called again with the decoded data, OS X will honor
+ // |input_format|, causing decode to succeed. On OS X 10.6, the data
+ // is properly decoded as a PKCS#7, whether PEM or not, which avoids
+ // the need to fallback to internal decoding.
+ if (IsValidOSCertHandle(cert)) {
+ CFRetain(cert);
+ output->push_back(cert);
+ }
+ }
+ }
+}
+
+struct CSSMOIDString {
+ const CSSM_OID* oid_;
+ std::string string_;
+};
+
+typedef std::vector<CSSMOIDString> CSSMOIDStringVector;
+
+bool CERTNameToCSSMOIDVector(CERTName* name, CSSMOIDStringVector* out_values) {
+ struct OIDCSSMMap {
+ SECOidTag sec_OID_;
+ const CSSM_OID* cssm_OID_;
+ };
+
+ const OIDCSSMMap kOIDs[] = {
+ { SEC_OID_AVA_COMMON_NAME, &CSSMOID_CommonName },
+ { SEC_OID_AVA_COUNTRY_NAME, &CSSMOID_CountryName },
+ { SEC_OID_AVA_LOCALITY, &CSSMOID_LocalityName },
+ { SEC_OID_AVA_STATE_OR_PROVINCE, &CSSMOID_StateProvinceName },
+ { SEC_OID_AVA_STREET_ADDRESS, &CSSMOID_StreetAddress },
+ { SEC_OID_AVA_ORGANIZATION_NAME, &CSSMOID_OrganizationName },
+ { SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, &CSSMOID_OrganizationalUnitName },
+ { SEC_OID_AVA_DN_QUALIFIER, &CSSMOID_DNQualifier },
+ { SEC_OID_RFC1274_UID, &CSSMOID_UniqueIdentifier },
+ { SEC_OID_PKCS9_EMAIL_ADDRESS, &CSSMOID_EmailAddress },
+ };
+
+ CERTRDN** rdns = name->rdns;
+ for (size_t rdn = 0; rdns[rdn]; ++rdn) {
+ CERTAVA** avas = rdns[rdn]->avas;
+ for (size_t pair = 0; avas[pair] != 0; ++pair) {
+ SECOidTag tag = CERT_GetAVATag(avas[pair]);
+ if (tag == SEC_OID_UNKNOWN) {
+ return false;
+ }
+ CSSMOIDString oidString;
+ bool found_oid = false;
+ for (size_t oid = 0; oid < ARRAYSIZE_UNSAFE(kOIDs); ++oid) {
+ if (kOIDs[oid].sec_OID_ == tag) {
+ SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value);
+ if (!decode_item)
+ return false;
+
+ // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote.
+ std::string value(reinterpret_cast<char*>(decode_item->data),
+ decode_item->len);
+ oidString.oid_ = kOIDs[oid].cssm_OID_;
+ oidString.string_ = value;
+ out_values->push_back(oidString);
+ SECITEM_FreeItem(decode_item, PR_TRUE);
+ found_oid = true;
+ break;
+ }
+ }
+ if (!found_oid) {
+ DLOG(ERROR) << "Unrecognized OID: " << tag;
+ }
+ }
+ }
+ return true;
+}
+
+class ScopedCertName {
+ public:
+ explicit ScopedCertName(CERTName* name) : name_(name) { }
+ ~ScopedCertName() {
+ if (name_) CERT_DestroyName(name_);
+ }
+ operator CERTName*() { return name_; }
+
+ private:
+ CERTName* name_;
+};
+
+class ScopedEncodedCertResults {
+ public:
+ explicit ScopedEncodedCertResults(CSSM_TP_RESULT_SET* results)
+ : results_(results) { }
+ ~ScopedEncodedCertResults() {
+ if (results_) {
+ CSSM_ENCODED_CERT* encCert =
+ reinterpret_cast<CSSM_ENCODED_CERT*>(results_->Results);
+ for (uint32 i = 0; i < results_->NumberOfResults; i++) {
+ crypto::CSSMFree(encCert[i].CertBlob.Data);
+ }
+ }
+ crypto::CSSMFree(results_->Results);
+ crypto::CSSMFree(results_);
+ }
+
+ private:
+ CSSM_TP_RESULT_SET* results_;
+};
+
+} // namespace
+
+void X509Certificate::Initialize() {
+ x509_util::CSSMCachedCertificate cached_cert;
+ if (cached_cert.Init(cert_handle_) == CSSM_OK) {
+ GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1SubjectNameStd,
+ &subject_);
+ GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1IssuerNameStd,
+ &issuer_);
+ GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotBefore,
+ &valid_start_);
+ GetCertDateForOID(cached_cert, &CSSMOID_X509V1ValidityNotAfter,
+ &valid_expiry_);
+ serial_number_ = GetCertSerialNumber(cached_cert);
+ }
+
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+ ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_);
+}
+
+bool X509Certificate::IsIssuedByEncoded(
+ const std::vector<std::string>& valid_issuers) {
+ if (IsCertIssuerInEncodedList(cert_handle_, valid_issuers))
+ return true;
+
+ for (OSCertHandles::iterator it = intermediate_ca_certs_.begin();
+ it != intermediate_ca_certs_.end(); ++it) {
+ if (IsCertIssuerInEncodedList(*it, valid_issuers))
+ return true;
+ }
+ return false;
+}
+
+void X509Certificate::GetSubjectAltName(
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const {
+ if (dns_names)
+ dns_names->clear();
+ if (ip_addrs)
+ ip_addrs->clear();
+
+ x509_util::CSSMCachedCertificate cached_cert;
+ OSStatus status = cached_cert.Init(cert_handle_);
+ if (status)
+ return;
+ x509_util::CSSMFieldValue subject_alt_name;
+ status = cached_cert.GetField(&CSSMOID_SubjectAltName, &subject_alt_name);
+ if (status || !subject_alt_name.field())
+ return;
+ const CSSM_X509_EXTENSION* cssm_ext =
+ subject_alt_name.GetAs<CSSM_X509_EXTENSION>();
+ if (!cssm_ext || !cssm_ext->value.parsedValue)
+ return;
+ const CE_GeneralNames* alt_name =
+ reinterpret_cast<const CE_GeneralNames*>(cssm_ext->value.parsedValue);
+
+ for (size_t name = 0; name < alt_name->numNames; ++name) {
+ const CE_GeneralName& name_struct = alt_name->generalName[name];
+ const CSSM_DATA& name_data = name_struct.name;
+ // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs
+ // respectively, both of which can be byte copied from
+ // CSSM_DATA::data into the appropriate output vector.
+ if (dns_names && name_struct.nameType == GNT_DNSName) {
+ dns_names->push_back(std::string(
+ reinterpret_cast<const char*>(name_data.Data),
+ name_data.Length));
+ } else if (ip_addrs && name_struct.nameType == GNT_IPAddress) {
+ ip_addrs->push_back(std::string(
+ reinterpret_cast<const char*>(name_data.Data),
+ name_data.Length));
+ }
+ }
+}
+
+// static
+bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle,
+ std::string* encoded) {
+ CSSM_DATA der_data;
+ if (SecCertificateGetData(cert_handle, &der_data) != noErr)
+ return false;
+ encoded->assign(reinterpret_cast<char*>(der_data.Data),
+ der_data.Length);
+ return true;
+}
+
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+ if (CFEqual(a, b))
+ return true;
+ CSSM_DATA a_data, b_data;
+ return SecCertificateGetData(a, &a_data) == noErr &&
+ SecCertificateGetData(b, &b_data) == noErr &&
+ a_data.Length == b_data.Length &&
+ memcmp(a_data.Data, b_data.Data, a_data.Length) == 0;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
+ const char* data, int length) {
+ CSSM_DATA cert_data;
+ cert_data.Data = const_cast<uint8*>(reinterpret_cast<const uint8*>(data));
+ cert_data.Length = length;
+
+ OSCertHandle cert_handle = NULL;
+ OSStatus status = SecCertificateCreateFromData(&cert_data,
+ CSSM_CERT_X_509v3,
+ CSSM_CERT_ENCODING_DER,
+ &cert_handle);
+ if (status != noErr)
+ return NULL;
+ if (!IsValidOSCertHandle(cert_handle)) {
+ CFRelease(cert_handle);
+ return NULL;
+ }
+ return cert_handle;
+}
+
+// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+
+ switch (format) {
+ case FORMAT_SINGLE_CERTIFICATE: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7:
+ AddCertificatesFromBytes(data, length, kSecFormatPKCS7, &results);
+ break;
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle handle) {
+ if (!handle)
+ return NULL;
+ return reinterpret_cast<OSCertHandle>(const_cast<void*>(CFRetain(handle)));
+}
+
+// static
+void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
+ CFRelease(cert_handle);
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateFingerprint(
+ OSCertHandle cert) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ CSSM_DATA cert_data;
+ OSStatus status = SecCertificateGetData(cert, &cert_data);
+ if (status)
+ return sha1;
+
+ DCHECK(cert_data.Data);
+ DCHECK_NE(cert_data.Length, 0U);
+
+ CC_SHA1(cert_data.Data, cert_data.Length, sha1.data);
+
+ return sha1;
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateCAFingerprint(
+ const OSCertHandles& intermediates) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ // The CC_SHA(3cc) man page says all CC_SHA1_xxx routines return 1, so
+ // we don't check their return values.
+ CC_SHA1_CTX sha1_ctx;
+ CC_SHA1_Init(&sha1_ctx);
+ CSSM_DATA cert_data;
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ OSStatus status = SecCertificateGetData(intermediates[i], &cert_data);
+ if (status)
+ return sha1;
+ CC_SHA1_Update(&sha1_ctx, cert_data.Data, cert_data.Length);
+ }
+ CC_SHA1_Final(sha1.data, &sha1_ctx);
+
+ return sha1;
+}
+
+bool X509Certificate::SupportsSSLClientAuth() const {
+ x509_util::CSSMCachedCertificate cached_cert;
+ OSStatus status = cached_cert.Init(cert_handle_);
+ if (status)
+ return false;
+
+ // RFC5280 says to take the intersection of the two extensions.
+ //
+ // Our underlying crypto libraries don't expose
+ // ClientCertificateType, so for now we will not support fixed
+ // Diffie-Hellman mechanisms. For rsa_sign, we need the
+ // digitalSignature bit.
+ //
+ // In particular, if a key has the nonRepudiation bit and not the
+ // digitalSignature one, we will not offer it to the user.
+ x509_util::CSSMFieldValue key_usage;
+ status = cached_cert.GetField(&CSSMOID_KeyUsage, &key_usage);
+ if (status == CSSM_OK && key_usage.field()) {
+ const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>();
+ const CE_KeyUsage* key_usage_value =
+ reinterpret_cast<const CE_KeyUsage*>(ext->value.parsedValue);
+ if (!((*key_usage_value) & CE_KU_DigitalSignature))
+ return false;
+ }
+
+ status = cached_cert.GetField(&CSSMOID_ExtendedKeyUsage, &key_usage);
+ if (status == CSSM_OK && key_usage.field()) {
+ const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>();
+ const CE_ExtendedKeyUsage* ext_key_usage =
+ reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue);
+ if (!ExtendedKeyUsageAllows(ext_key_usage, &CSSMOID_ClientAuth))
+ return false;
+ }
+ return true;
+}
+
+CFArrayRef X509Certificate::CreateOSCertChainForCert() const {
+ CFMutableArrayRef cert_list =
+ CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeArrayCallBacks);
+ if (!cert_list)
+ return NULL;
+
+ CFArrayAppendValue(cert_list, os_cert_handle());
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i)
+ CFArrayAppendValue(cert_list, intermediate_ca_certs_[i]);
+
+ return cert_list;
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle_iter->ReadData(&data, &length))
+ return NULL;
+
+ return CreateOSCertHandleFromBytes(data, length);
+}
+
+// static
+bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle,
+ Pickle* pickle) {
+ CSSM_DATA cert_data;
+ OSStatus status = SecCertificateGetData(cert_handle, &cert_data);
+ if (status)
+ return false;
+
+ return pickle->WriteData(reinterpret_cast<char*>(cert_data.Data),
+ cert_data.Length);
+}
+
+// static
+void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type) {
+ // Since we might fail, set the output parameters to default values first.
+ *type = kPublicKeyTypeUnknown;
+ *size_bits = 0;
+
+ SecKeyRef key;
+ OSStatus status = SecCertificateCopyPublicKey(cert_handle, &key);
+ if (status) {
+ NOTREACHED() << "SecCertificateCopyPublicKey failed: " << status;
+ return;
+ }
+ ScopedCFTypeRef<SecKeyRef> scoped_key(key);
+
+ const CSSM_KEY* cssm_key;
+ status = SecKeyGetCSSMKey(key, &cssm_key);
+ if (status) {
+ NOTREACHED() << "SecKeyGetCSSMKey failed: " << status;
+ return;
+ }
+
+ *size_bits = cssm_key->KeyHeader.LogicalKeySizeInBits;
+
+ switch (cssm_key->KeyHeader.AlgorithmId) {
+ case CSSM_ALGID_RSA:
+ *type = kPublicKeyTypeRSA;
+ break;
+ case CSSM_ALGID_DSA:
+ *type = kPublicKeyTypeDSA;
+ break;
+ case CSSM_ALGID_ECDSA:
+ *type = kPublicKeyTypeECDSA;
+ break;
+ case CSSM_ALGID_DH:
+ *type = kPublicKeyTypeDH;
+ break;
+ default:
+ *type = kPublicKeyTypeUnknown;
+ *size_bits = 0;
+ break;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_net_log_param.cc b/chromium/net/cert/x509_certificate_net_log_param.cc
new file mode 100644
index 00000000000..3c52b96dc07
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_net_log_param.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate_net_log_param.h"
+
+#include <string>
+#include <vector>
+
+#include "base/values.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+base::Value* NetLogX509CertificateCallback(const X509Certificate* certificate,
+ NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* certs = new base::ListValue();
+ std::vector<std::string> encoded_chain;
+ certificate->GetPEMEncodedChain(&encoded_chain);
+ for (size_t i = 0; i < encoded_chain.size(); ++i)
+ certs->Append(new base::StringValue(encoded_chain[i]));
+ dict->Set("certificates", certs);
+ return dict;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_net_log_param.h b/chromium/net/cert/x509_certificate_net_log_param.h
new file mode 100644
index 00000000000..36ae8f5ecdf
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_net_log_param.h
@@ -0,0 +1,21 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_X509_CERT_NET_LOG_PARAM_H_
+#define NET_BASE_X509_CERT_NET_LOG_PARAM_H_
+
+#include "net/base/net_log.h"
+
+namespace net {
+
+class X509Certificate;
+
+// Creates NetLog parameter to describe an X509Certificate.
+base::Value* NetLogX509CertificateCallback(
+ const X509Certificate* certificate,
+ NetLog::LogLevel log_level);
+
+} // namespace net
+
+#endif // NET_BASE_X509_CERT_NET_LOG_PARAM_H_
diff --git a/chromium/net/cert/x509_certificate_nss.cc b/chromium/net/cert/x509_certificate_nss.cc
new file mode 100644
index 00000000000..ca256504817
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_nss.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <cert.h>
+#include <cryptohi.h>
+#include <keyhi.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <prtime.h>
+#include <seccomon.h>
+#include <secder.h>
+#include <sechash.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/cert/x509_util_nss.h"
+
+namespace net {
+
+void X509Certificate::Initialize() {
+ x509_util::ParsePrincipal(&cert_handle_->subject, &subject_);
+ x509_util::ParsePrincipal(&cert_handle_->issuer, &issuer_);
+
+ x509_util::ParseDate(&cert_handle_->validity.notBefore, &valid_start_);
+ x509_util::ParseDate(&cert_handle_->validity.notAfter, &valid_expiry_);
+
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+ ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_);
+
+ serial_number_ = x509_util::ParseSerialNumber(cert_handle_);
+}
+
+// static
+X509Certificate* X509Certificate::CreateFromBytesWithNickname(
+ const char* data,
+ int length,
+ const char* nickname) {
+ OSCertHandle cert_handle = CreateOSCertHandleFromBytesWithNickname(data,
+ length,
+ nickname);
+ if (!cert_handle)
+ return NULL;
+
+ X509Certificate* cert = CreateFromHandle(cert_handle, OSCertHandles());
+ FreeOSCertHandle(cert_handle);
+
+ if (nickname)
+ cert->default_nickname_ = nickname;
+
+ return cert;
+}
+
+std::string X509Certificate::GetDefaultNickname(CertType type) const {
+ if (!default_nickname_.empty())
+ return default_nickname_;
+
+ std::string result;
+ if (type == USER_CERT && cert_handle_->slot) {
+ // Find the private key for this certificate and see if it has a
+ // nickname. If there is a private key, and it has a nickname, then
+ // return that nickname.
+ SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
+ cert_handle_->slot,
+ cert_handle_,
+ NULL); // wincx
+ if (private_key) {
+ char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key);
+ if (private_key_nickname) {
+ result = private_key_nickname;
+ PORT_Free(private_key_nickname);
+ SECKEY_DestroyPrivateKey(private_key);
+ return result;
+ }
+ SECKEY_DestroyPrivateKey(private_key);
+ }
+ }
+
+ switch (type) {
+ case CA_CERT: {
+ char* nickname = CERT_MakeCANickname(cert_handle_);
+ result = nickname;
+ PORT_Free(nickname);
+ break;
+ }
+ case USER_CERT: {
+ std::string subject_name = subject_.GetDisplayName();
+ if (subject_name.empty()) {
+ const char* email = CERT_GetFirstEmailAddress(cert_handle_);
+ if (email)
+ subject_name = email;
+ }
+ // TODO(gspencer): Internationalize this. It's wrong to assume English
+ // here.
+ result = base::StringPrintf("%s's %s ID", subject_name.c_str(),
+ issuer_.GetDisplayName().c_str());
+ break;
+ }
+ case SERVER_CERT:
+ result = subject_.GetDisplayName();
+ break;
+ case UNKNOWN_CERT:
+ default:
+ break;
+ }
+ return result;
+}
+
+void X509Certificate::GetSubjectAltName(
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const {
+ x509_util::GetSubjectAltName(cert_handle_, dns_names, ip_addrs);
+}
+
+bool X509Certificate::IsIssuedByEncoded(
+ const std::vector<std::string>& valid_issuers) {
+ // Get certificate chain as scoped list of CERTCertificate objects.
+ std::vector<CERTCertificate*> cert_chain;
+ cert_chain.push_back(cert_handle_);
+ for (size_t n = 0; n < intermediate_ca_certs_.size(); ++n) {
+ cert_chain.push_back(intermediate_ca_certs_[n]);
+ }
+ // Convert encoded issuers to scoped CERTName* list.
+ std::vector<CERTName*> issuers;
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!x509_util::GetIssuersFromEncodedList(valid_issuers,
+ arena.get(),
+ &issuers)) {
+ return false;
+ }
+ return x509_util::IsCertificateIssuedBy(cert_chain, issuers);
+}
+
+// static
+bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle,
+ std::string* encoded) {
+ if (!cert_handle->derCert.len)
+ return false;
+ encoded->assign(reinterpret_cast<char*>(cert_handle->derCert.data),
+ cert_handle->derCert.len);
+ return true;
+}
+
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+ return a->derCert.len == b->derCert.len &&
+ memcmp(a->derCert.data, b->derCert.data, a->derCert.len) == 0;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
+ const char* data, int length) {
+ return CreateOSCertHandleFromBytesWithNickname(data, length, NULL);
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::CreateOSCertHandleFromBytesWithNickname(
+ const char* data,
+ int length,
+ const char* nickname) {
+ if (length < 0)
+ return NULL;
+
+ crypto::EnsureNSSInit();
+
+ if (!NSS_IsInitialized())
+ return NULL;
+
+ SECItem der_cert;
+ der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
+ der_cert.len = length;
+ der_cert.type = siDERCertBuffer;
+
+ // Parse into a certificate structure.
+ return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert,
+ const_cast<char*>(nickname),
+ PR_FALSE, PR_TRUE);
+}
+
+// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data,
+ int length,
+ Format format) {
+ return x509_util::CreateOSCertHandlesFromBytes(data, length, format);
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle cert_handle) {
+ return CERT_DupCertificate(cert_handle);
+}
+
+// static
+void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
+ CERT_DestroyCertificate(cert_handle);
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateFingerprint(
+ OSCertHandle cert) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ DCHECK(NULL != cert->derCert.data);
+ DCHECK_NE(0U, cert->derCert.len);
+
+ SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, sha1.data,
+ cert->derCert.data, cert->derCert.len);
+ DCHECK_EQ(SECSuccess, rv);
+
+ return sha1;
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateCAFingerprint(
+ const OSCertHandles& intermediates) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ HASHContext* sha1_ctx = HASH_Create(HASH_AlgSHA1);
+ if (!sha1_ctx)
+ return sha1;
+ HASH_Begin(sha1_ctx);
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ CERTCertificate* ca_cert = intermediates[i];
+ HASH_Update(sha1_ctx, ca_cert->derCert.data, ca_cert->derCert.len);
+ }
+ unsigned int result_len;
+ HASH_End(sha1_ctx, sha1.data, &result_len, HASH_ResultLenContext(sha1_ctx));
+ HASH_Destroy(sha1_ctx);
+
+ return sha1;
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) {
+ return x509_util::ReadOSCertHandleFromPickle(pickle_iter);
+}
+
+// static
+bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle,
+ Pickle* pickle) {
+ return pickle->WriteData(
+ reinterpret_cast<const char*>(cert_handle->derCert.data),
+ cert_handle->derCert.len);
+}
+
+// static
+void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type) {
+ x509_util::GetPublicKeyInfo(cert_handle, size_bits, type);
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_openssl.cc b/chromium/net/cert/x509_certificate_openssl.cc
new file mode 100644
index 00000000000..bdf2bf20538
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_openssl.cc
@@ -0,0 +1,520 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <openssl/asn1.h>
+#include <openssl/crypto.h>
+#include <openssl/obj_mac.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs7.h>
+#include <openssl/sha.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include "base/memory/singleton.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "crypto/openssl_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/cert/x509_util_openssl.h"
+
+#if defined(OS_ANDROID)
+#include "base/logging.h"
+#include "net/android/network_library.h"
+#endif
+
+namespace net {
+
+namespace {
+
+void CreateOSCertHandlesFromPKCS7Bytes(
+ const char* data, int length,
+ X509Certificate::OSCertHandles* handles) {
+ crypto::EnsureOpenSSLInit();
+ const unsigned char* der_data = reinterpret_cast<const unsigned char*>(data);
+ crypto::ScopedOpenSSL<PKCS7, PKCS7_free> pkcs7_cert(
+ d2i_PKCS7(NULL, &der_data, length));
+ if (!pkcs7_cert.get())
+ return;
+
+ STACK_OF(X509)* certs = NULL;
+ int nid = OBJ_obj2nid(pkcs7_cert.get()->type);
+ if (nid == NID_pkcs7_signed) {
+ certs = pkcs7_cert.get()->d.sign->cert;
+ } else if (nid == NID_pkcs7_signedAndEnveloped) {
+ certs = pkcs7_cert.get()->d.signed_and_enveloped->cert;
+ }
+
+ if (certs) {
+ for (int i = 0; i < sk_X509_num(certs); ++i) {
+ X509* x509_cert =
+ X509Certificate::DupOSCertHandle(sk_X509_value(certs, i));
+ handles->push_back(x509_cert);
+ }
+ }
+}
+
+void ParsePrincipalValues(X509_NAME* name,
+ int nid,
+ std::vector<std::string>* fields) {
+ for (int index = -1;
+ (index = X509_NAME_get_index_by_NID(name, nid, index)) != -1;) {
+ std::string field;
+ if (!x509_util::ParsePrincipalValueByIndex(name, index, &field))
+ break;
+ fields->push_back(field);
+ }
+}
+
+void ParsePrincipal(X509Certificate::OSCertHandle cert,
+ X509_NAME* x509_name,
+ CertPrincipal* principal) {
+ if (!x509_name)
+ return;
+
+ ParsePrincipalValues(x509_name, NID_streetAddress,
+ &principal->street_addresses);
+ ParsePrincipalValues(x509_name, NID_organizationName,
+ &principal->organization_names);
+ ParsePrincipalValues(x509_name, NID_organizationalUnitName,
+ &principal->organization_unit_names);
+ ParsePrincipalValues(x509_name, NID_domainComponent,
+ &principal->domain_components);
+
+ x509_util::ParsePrincipalValueByNID(x509_name, NID_commonName,
+ &principal->common_name);
+ x509_util::ParsePrincipalValueByNID(x509_name, NID_localityName,
+ &principal->locality_name);
+ x509_util::ParsePrincipalValueByNID(x509_name, NID_stateOrProvinceName,
+ &principal->state_or_province_name);
+ x509_util::ParsePrincipalValueByNID(x509_name, NID_countryName,
+ &principal->country_name);
+}
+
+void ParseSubjectAltName(X509Certificate::OSCertHandle cert,
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addresses) {
+ DCHECK(dns_names || ip_addresses);
+ int index = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
+ X509_EXTENSION* alt_name_ext = X509_get_ext(cert, index);
+ if (!alt_name_ext)
+ return;
+
+ crypto::ScopedOpenSSL<GENERAL_NAMES, GENERAL_NAMES_free> alt_names(
+ reinterpret_cast<GENERAL_NAMES*>(X509V3_EXT_d2i(alt_name_ext)));
+ if (!alt_names.get())
+ return;
+
+ for (int i = 0; i < sk_GENERAL_NAME_num(alt_names.get()); ++i) {
+ const GENERAL_NAME* name = sk_GENERAL_NAME_value(alt_names.get(), i);
+ if (name->type == GEN_DNS && dns_names) {
+ const unsigned char* dns_name = ASN1_STRING_data(name->d.dNSName);
+ if (!dns_name)
+ continue;
+ int dns_name_len = ASN1_STRING_length(name->d.dNSName);
+ dns_names->push_back(
+ std::string(reinterpret_cast<const char*>(dns_name), dns_name_len));
+ } else if (name->type == GEN_IPADD && ip_addresses) {
+ const unsigned char* ip_addr = name->d.iPAddress->data;
+ if (!ip_addr)
+ continue;
+ int ip_addr_len = name->d.iPAddress->length;
+ if (ip_addr_len != static_cast<int>(kIPv4AddressSize) &&
+ ip_addr_len != static_cast<int>(kIPv6AddressSize)) {
+ // http://www.ietf.org/rfc/rfc3280.txt requires subjectAltName iPAddress
+ // to have 4 or 16 bytes, whereas in a name constraint it includes a
+ // net mask hence 8 or 32 bytes. Logging to help diagnose any mixup.
+ LOG(WARNING) << "Bad sized IP Address in cert: " << ip_addr_len;
+ continue;
+ }
+ ip_addresses->push_back(
+ std::string(reinterpret_cast<const char*>(ip_addr), ip_addr_len));
+ }
+ }
+}
+
+struct DERCache {
+ unsigned char* data;
+ int data_length;
+};
+
+void DERCache_free(void* parent, void* ptr, CRYPTO_EX_DATA* ad, int idx,
+ long argl, void* argp) {
+ DERCache* der_cache = static_cast<DERCache*>(ptr);
+ if (!der_cache)
+ return;
+ if (der_cache->data)
+ OPENSSL_free(der_cache->data);
+ OPENSSL_free(der_cache);
+}
+
+class X509InitSingleton {
+ public:
+ static X509InitSingleton* GetInstance() {
+ // We allow the X509 store to leak, because it is used from a non-joinable
+ // worker that is not stopped on shutdown, hence may still be using
+ // OpenSSL library after the AtExit runner has completed.
+ return Singleton<X509InitSingleton,
+ LeakySingletonTraits<X509InitSingleton> >::get();
+ }
+ int der_cache_ex_index() const { return der_cache_ex_index_; }
+ X509_STORE* store() const { return store_.get(); }
+
+ void ResetCertStore() {
+ store_.reset(X509_STORE_new());
+ DCHECK(store_.get());
+ X509_STORE_set_default_paths(store_.get());
+ // TODO(joth): Enable CRL (see X509_STORE_set_flags(X509_V_FLAG_CRL_CHECK)).
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<X509InitSingleton>;
+ X509InitSingleton() {
+ crypto::EnsureOpenSSLInit();
+ der_cache_ex_index_ = X509_get_ex_new_index(0, 0, 0, 0, DERCache_free);
+ DCHECK_NE(der_cache_ex_index_, -1);
+ ResetCertStore();
+ }
+
+ int der_cache_ex_index_;
+ crypto::ScopedOpenSSL<X509_STORE, X509_STORE_free> store_;
+
+ DISALLOW_COPY_AND_ASSIGN(X509InitSingleton);
+};
+
+// Takes ownership of |data| (which must have been allocated by OpenSSL).
+DERCache* SetDERCache(X509Certificate::OSCertHandle cert,
+ int x509_der_cache_index,
+ unsigned char* data,
+ int data_length) {
+ DERCache* internal_cache = static_cast<DERCache*>(
+ OPENSSL_malloc(sizeof(*internal_cache)));
+ if (!internal_cache) {
+ // We took ownership of |data|, so we must free if we can't add it to
+ // |cert|.
+ OPENSSL_free(data);
+ return NULL;
+ }
+
+ internal_cache->data = data;
+ internal_cache->data_length = data_length;
+ X509_set_ex_data(cert, x509_der_cache_index, internal_cache);
+ return internal_cache;
+}
+
+// Returns true if |der_cache| points to valid data, false otherwise.
+// (note: the DER-encoded data in |der_cache| is owned by |cert|, callers should
+// not free it).
+bool GetDERAndCacheIfNeeded(X509Certificate::OSCertHandle cert,
+ DERCache* der_cache) {
+ int x509_der_cache_index =
+ X509InitSingleton::GetInstance()->der_cache_ex_index();
+
+ // Re-encoding the DER data via i2d_X509 is an expensive operation, but it's
+ // necessary for comparing two certificates. We re-encode at most once per
+ // certificate and cache the data within the X509 cert using X509_set_ex_data.
+ DERCache* internal_cache = static_cast<DERCache*>(
+ X509_get_ex_data(cert, x509_der_cache_index));
+ if (!internal_cache) {
+ unsigned char* data = NULL;
+ int data_length = i2d_X509(cert, &data);
+ if (data_length <= 0 || !data)
+ return false;
+ internal_cache = SetDERCache(cert, x509_der_cache_index, data, data_length);
+ if (!internal_cache)
+ return false;
+ }
+ *der_cache = *internal_cache;
+ return true;
+}
+
+// Used to free a list of X509_NAMEs and the objects it points to.
+void sk_X509_NAME_free_all(STACK_OF(X509_NAME)* sk) {
+ sk_X509_NAME_pop_free(sk, X509_NAME_free);
+}
+
+} // namespace
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle cert_handle) {
+ DCHECK(cert_handle);
+ // Using X509_dup causes the entire certificate to be reparsed. This
+ // conversion, besides being non-trivial, drops any associated
+ // application-specific data set by X509_set_ex_data. Using CRYPTO_add
+ // just bumps up the ref-count for the cert, without causing any allocations
+ // or deallocations.
+ CRYPTO_add(&cert_handle->references, 1, CRYPTO_LOCK_X509);
+ return cert_handle;
+}
+
+// static
+void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
+ // Decrement the ref-count for the cert and, if all references are gone,
+ // free the memory and any application-specific data associated with the
+ // certificate.
+ X509_free(cert_handle);
+}
+
+void X509Certificate::Initialize() {
+ crypto::EnsureOpenSSLInit();
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+ ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_);
+
+ ASN1_INTEGER* serial_num = X509_get_serialNumber(cert_handle_);
+ if (serial_num) {
+ // ASN1_INTEGERS represent the decoded number, in a format internal to
+ // OpenSSL. Most notably, this may have leading zeroes stripped off for
+ // numbers whose first byte is >= 0x80. Thus, it is necessary to
+ // re-encoded the integer back into DER, which is what the interface
+ // of X509Certificate exposes, to ensure callers get the proper (DER)
+ // value.
+ int bytes_required = i2c_ASN1_INTEGER(serial_num, NULL);
+ unsigned char* buffer = reinterpret_cast<unsigned char*>(
+ WriteInto(&serial_number_, bytes_required + 1));
+ int bytes_written = i2c_ASN1_INTEGER(serial_num, &buffer);
+ DCHECK_EQ(static_cast<size_t>(bytes_written), serial_number_.size());
+ }
+
+ ParsePrincipal(cert_handle_, X509_get_subject_name(cert_handle_), &subject_);
+ ParsePrincipal(cert_handle_, X509_get_issuer_name(cert_handle_), &issuer_);
+ x509_util::ParseDate(X509_get_notBefore(cert_handle_), &valid_start_);
+ x509_util::ParseDate(X509_get_notAfter(cert_handle_), &valid_expiry_);
+}
+
+// static
+void X509Certificate::ResetCertStore() {
+ X509InitSingleton::GetInstance()->ResetCertStore();
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateFingerprint(OSCertHandle cert) {
+ SHA1HashValue sha1;
+ unsigned int sha1_size = static_cast<unsigned int>(sizeof(sha1.data));
+ int ret = X509_digest(cert, EVP_sha1(), sha1.data, &sha1_size);
+ CHECK(ret);
+ CHECK_EQ(sha1_size, sizeof(sha1.data));
+ return sha1;
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateCAFingerprint(
+ const OSCertHandles& intermediates) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ SHA_CTX sha1_ctx;
+ SHA1_Init(&sha1_ctx);
+ DERCache der_cache;
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ if (!GetDERAndCacheIfNeeded(intermediates[i], &der_cache))
+ return sha1;
+ SHA1_Update(&sha1_ctx, der_cache.data, der_cache.data_length);
+ }
+ SHA1_Final(sha1.data, &sha1_ctx);
+
+ return sha1;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
+ const char* data, int length) {
+ if (length < 0)
+ return NULL;
+ crypto::EnsureOpenSSLInit();
+ const unsigned char* d2i_data =
+ reinterpret_cast<const unsigned char*>(data);
+ // Don't cache this data via SetDERCache as this wire format may be not be
+ // identical from the i2d_X509 roundtrip.
+ X509* cert = d2i_X509(NULL, &d2i_data, length);
+ return cert;
+}
+
+// static
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+ if (length < 0)
+ return results;
+
+ switch (format) {
+ case FORMAT_SINGLE_CERTIFICATE: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7: {
+ CreateOSCertHandlesFromPKCS7Bytes(data, length, &results);
+ break;
+ }
+ default: {
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+ }
+
+ return results;
+}
+
+void X509Certificate::GetSubjectAltName(
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const {
+ if (dns_names)
+ dns_names->clear();
+ if (ip_addrs)
+ ip_addrs->clear();
+
+ ParseSubjectAltName(cert_handle_, dns_names, ip_addrs);
+}
+
+// static
+X509_STORE* X509Certificate::cert_store() {
+ return X509InitSingleton::GetInstance()->store();
+}
+
+// static
+bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle,
+ std::string* encoded) {
+ DERCache der_cache;
+ if (!GetDERAndCacheIfNeeded(cert_handle, &der_cache))
+ return false;
+ encoded->assign(reinterpret_cast<const char*>(der_cache.data),
+ der_cache.data_length);
+ return true;
+}
+
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+
+ // X509_cmp only checks the fingerprint, but we want to compare the whole
+ // DER data. Encoding it from OSCertHandle is an expensive operation, so we
+ // cache the DER (if not already cached via X509_set_ex_data).
+ DERCache der_cache_a, der_cache_b;
+
+ return GetDERAndCacheIfNeeded(a, &der_cache_a) &&
+ GetDERAndCacheIfNeeded(b, &der_cache_b) &&
+ der_cache_a.data_length == der_cache_b.data_length &&
+ memcmp(der_cache_a.data, der_cache_b.data, der_cache_a.data_length) == 0;
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle_iter->ReadData(&data, &length))
+ return NULL;
+
+ return CreateOSCertHandleFromBytes(data, length);
+}
+
+// static
+bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle,
+ Pickle* pickle) {
+ DERCache der_cache;
+ if (!GetDERAndCacheIfNeeded(cert_handle, &der_cache))
+ return false;
+
+ return pickle->WriteData(
+ reinterpret_cast<const char*>(der_cache.data),
+ der_cache.data_length);
+}
+
+// static
+void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type) {
+ *type = kPublicKeyTypeUnknown;
+ *size_bits = 0;
+
+ crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> scoped_key(
+ X509_get_pubkey(cert_handle));
+ if (!scoped_key.get())
+ return;
+
+ CHECK(scoped_key.get());
+ EVP_PKEY* key = scoped_key.get();
+
+ switch (key->type) {
+ case EVP_PKEY_RSA:
+ *type = kPublicKeyTypeRSA;
+ *size_bits = EVP_PKEY_size(key) * 8;
+ break;
+ case EVP_PKEY_DSA:
+ *type = kPublicKeyTypeDSA;
+ *size_bits = EVP_PKEY_size(key) * 8;
+ break;
+ case EVP_PKEY_EC:
+ *type = kPublicKeyTypeECDSA;
+ *size_bits = EVP_PKEY_size(key);
+ break;
+ case EVP_PKEY_DH:
+ *type = kPublicKeyTypeDH;
+ *size_bits = EVP_PKEY_size(key) * 8;
+ break;
+ }
+}
+
+bool X509Certificate::IsIssuedByEncoded(
+ const std::vector<std::string>& valid_issuers) {
+ if (valid_issuers.empty())
+ return false;
+
+ // Convert to a temporary list of X509_NAME objects.
+ // It will own the objects it points to.
+ crypto::ScopedOpenSSL<STACK_OF(X509_NAME), sk_X509_NAME_free_all>
+ issuer_names(sk_X509_NAME_new_null());
+ if (!issuer_names.get())
+ return false;
+
+ for (std::vector<std::string>::const_iterator it = valid_issuers.begin();
+ it != valid_issuers.end(); ++it) {
+ const unsigned char* p =
+ reinterpret_cast<const unsigned char*>(it->data());
+ long len = static_cast<long>(it->length());
+ X509_NAME* ca_name = d2i_X509_NAME(NULL, &p, len);
+ if (ca_name == NULL)
+ return false;
+ sk_X509_NAME_push(issuer_names.get(), ca_name);
+ }
+
+ // Create a temporary list of X509_NAME objects corresponding
+ // to the certificate chain. It doesn't own the object it points to.
+ std::vector<X509_NAME*> cert_names;
+ X509_NAME* issuer = X509_get_issuer_name(cert_handle_);
+ if (issuer == NULL)
+ return false;
+
+ cert_names.push_back(issuer);
+ for (OSCertHandles::iterator it = intermediate_ca_certs_.begin();
+ it != intermediate_ca_certs_.end(); ++it) {
+ issuer = X509_get_issuer_name(*it);
+ if (issuer == NULL)
+ return false;
+ cert_names.push_back(issuer);
+ }
+
+ // and 'cert_names'.
+ for (size_t n = 0; n < cert_names.size(); ++n) {
+ for (int m = 0; m < sk_X509_NAME_num(issuer_names.get()); ++m) {
+ X509_NAME* issuer = sk_X509_NAME_value(issuer_names.get(), m);
+ if (X509_NAME_cmp(issuer, cert_names[n]) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_unittest.cc b/chromium/net/cert/x509_certificate_unittest.cc
new file mode 100644
index 00000000000..75ba827a18a
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_unittest.cc
@@ -0,0 +1,1147 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/asn1_util.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_certificate_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS)
+#include <cert.h>
+#endif
+
+using base::HexEncode;
+using base::Time;
+
+namespace net {
+
+// Certificates for test data. They're obtained with:
+//
+// $ openssl s_client -connect [host]:443 -showcerts > /tmp/host.pem < /dev/null
+// $ openssl x509 -inform PEM -outform DER < /tmp/host.pem > /tmp/host.der
+//
+// For fingerprint
+// $ openssl x509 -inform DER -fingerprint -noout < /tmp/host.der
+
+// For valid_start, valid_expiry
+// $ openssl x509 -inform DER -text -noout < /tmp/host.der |
+// grep -A 2 Validity
+// $ date +%s -d '<date str>'
+
+// Google's cert.
+uint8 google_fingerprint[] = {
+ 0xab, 0xbe, 0x5e, 0xb4, 0x93, 0x88, 0x4e, 0xe4, 0x60, 0xc6, 0xef, 0xf8,
+ 0xea, 0xd4, 0xb1, 0x55, 0x4b, 0xc9, 0x59, 0x3c
+};
+
+// webkit.org's cert.
+uint8 webkit_fingerprint[] = {
+ 0xa1, 0x4a, 0x94, 0x46, 0x22, 0x8e, 0x70, 0x66, 0x2b, 0x94, 0xf9, 0xf8,
+ 0x57, 0x83, 0x2d, 0xa2, 0xff, 0xbc, 0x84, 0xc2
+};
+
+// thawte.com's cert (it's EV-licious!).
+uint8 thawte_fingerprint[] = {
+ 0x85, 0x04, 0x2d, 0xfd, 0x2b, 0x0e, 0xc6, 0xc8, 0xaf, 0x2d, 0x77, 0xd6,
+ 0xa1, 0x3a, 0x64, 0x04, 0x27, 0x90, 0x97, 0x37
+};
+
+// A certificate for https://www.unosoft.hu/, whose AIA extension contains
+// an LDAP URL without a host name.
+uint8 unosoft_hu_fingerprint[] = {
+ 0x32, 0xff, 0xe3, 0xbe, 0x2c, 0x3b, 0xc7, 0xca, 0xbf, 0x2d, 0x64, 0xbd,
+ 0x25, 0x66, 0xf2, 0xec, 0x8b, 0x0f, 0xbf, 0xd8
+};
+
+// The fingerprint of the Google certificate used in the parsing tests,
+// which is newer than the one included in the x509_certificate_data.h
+uint8 google_parse_fingerprint[] = {
+ 0x40, 0x50, 0x62, 0xe5, 0xbe, 0xfd, 0xe4, 0xaf, 0x97, 0xe9, 0x38, 0x2a,
+ 0xf1, 0x6c, 0xc8, 0x7c, 0x8f, 0xb7, 0xc4, 0xe2
+};
+
+// The fingerprint for the Thawte SGC certificate
+uint8 thawte_parse_fingerprint[] = {
+ 0xec, 0x07, 0x10, 0x03, 0xd8, 0xf5, 0xa3, 0x7f, 0x42, 0xc4, 0x55, 0x7f,
+ 0x65, 0x6a, 0xae, 0x86, 0x65, 0xfa, 0x4b, 0x02
+};
+
+// Dec 18 00:00:00 2009 GMT
+const double kGoogleParseValidFrom = 1261094400;
+// Dec 18 23:59:59 2011 GMT
+const double kGoogleParseValidTo = 1324252799;
+
+struct CertificateFormatTestData {
+ const char* file_name;
+ X509Certificate::Format format;
+ uint8* chain_fingerprints[3];
+};
+
+const CertificateFormatTestData FormatTestData[] = {
+ // DER Parsing - single certificate, DER encoded
+ { "google.single.der", X509Certificate::FORMAT_SINGLE_CERTIFICATE,
+ { google_parse_fingerprint,
+ NULL, } },
+ // DER parsing - single certificate, PEM encoded
+ { "google.single.pem", X509Certificate::FORMAT_SINGLE_CERTIFICATE,
+ { google_parse_fingerprint,
+ NULL, } },
+ // PEM parsing - single certificate, PEM encoded with a PEB of
+ // "CERTIFICATE"
+ { "google.single.pem", X509Certificate::FORMAT_PEM_CERT_SEQUENCE,
+ { google_parse_fingerprint,
+ NULL, } },
+ // PEM parsing - sequence of certificates, PEM encoded with a PEB of
+ // "CERTIFICATE"
+ { "google.chain.pem", X509Certificate::FORMAT_PEM_CERT_SEQUENCE,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, DER
+ // encoding
+ { "google.binary.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM
+ // encoded with a PEM PEB of "CERTIFICATE"
+ { "google.pem_cert.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // PKCS#7 parsing - "degenerate" SignedData collection of certificates, PEM
+ // encoded with a PEM PEB of "PKCS7"
+ { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_PKCS7,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ // All of the above, this time using auto-detection
+ { "google.single.der", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ NULL, } },
+ { "google.single.pem", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ NULL, } },
+ { "google.chain.pem", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.binary.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.pem_cert.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+ { "google.pem_pkcs7.p7b", X509Certificate::FORMAT_AUTO,
+ { google_parse_fingerprint,
+ thawte_parse_fingerprint,
+ NULL, } },
+};
+
+void CheckGoogleCert(const scoped_refptr<X509Certificate>& google_cert,
+ uint8* expected_fingerprint,
+ double valid_from, double valid_to) {
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert);
+
+ const CertPrincipal& subject = google_cert->subject();
+ EXPECT_EQ("www.google.com", subject.common_name);
+ EXPECT_EQ("Mountain View", subject.locality_name);
+ EXPECT_EQ("California", subject.state_or_province_name);
+ EXPECT_EQ("US", subject.country_name);
+ EXPECT_EQ(0U, subject.street_addresses.size());
+ ASSERT_EQ(1U, subject.organization_names.size());
+ EXPECT_EQ("Google Inc", subject.organization_names[0]);
+ EXPECT_EQ(0U, subject.organization_unit_names.size());
+ EXPECT_EQ(0U, subject.domain_components.size());
+
+ const CertPrincipal& issuer = google_cert->issuer();
+ EXPECT_EQ("Thawte SGC CA", issuer.common_name);
+ EXPECT_EQ("", issuer.locality_name);
+ EXPECT_EQ("", issuer.state_or_province_name);
+ EXPECT_EQ("ZA", issuer.country_name);
+ EXPECT_EQ(0U, issuer.street_addresses.size());
+ ASSERT_EQ(1U, issuer.organization_names.size());
+ EXPECT_EQ("Thawte Consulting (Pty) Ltd.", issuer.organization_names[0]);
+ EXPECT_EQ(0U, issuer.organization_unit_names.size());
+ EXPECT_EQ(0U, issuer.domain_components.size());
+
+ // Use DoubleT because its epoch is the same on all platforms
+ const Time& valid_start = google_cert->valid_start();
+ EXPECT_EQ(valid_from, valid_start.ToDoubleT());
+
+ const Time& valid_expiry = google_cert->valid_expiry();
+ EXPECT_EQ(valid_to, valid_expiry.ToDoubleT());
+
+ const SHA1HashValue& fingerprint = google_cert->fingerprint();
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(expected_fingerprint[i], fingerprint.data[i]);
+
+ std::vector<std::string> dns_names;
+ google_cert->GetDNSNames(&dns_names);
+ ASSERT_EQ(1U, dns_names.size());
+ EXPECT_EQ("www.google.com", dns_names[0]);
+}
+
+TEST(X509CertificateTest, GoogleCertParsing) {
+ scoped_refptr<X509Certificate> google_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der)));
+
+ CheckGoogleCert(google_cert, google_fingerprint,
+ 1238192407, // Mar 27 22:20:07 2009 GMT
+ 1269728407); // Mar 27 22:20:07 2010 GMT
+}
+
+TEST(X509CertificateTest, WebkitCertParsing) {
+ scoped_refptr<X509Certificate> webkit_cert(X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
+
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), webkit_cert);
+
+ const CertPrincipal& subject = webkit_cert->subject();
+ EXPECT_EQ("Cupertino", subject.locality_name);
+ EXPECT_EQ("California", subject.state_or_province_name);
+ EXPECT_EQ("US", subject.country_name);
+ EXPECT_EQ(0U, subject.street_addresses.size());
+ ASSERT_EQ(1U, subject.organization_names.size());
+ EXPECT_EQ("Apple Inc.", subject.organization_names[0]);
+ ASSERT_EQ(1U, subject.organization_unit_names.size());
+ EXPECT_EQ("Mac OS Forge", subject.organization_unit_names[0]);
+ EXPECT_EQ(0U, subject.domain_components.size());
+
+ const CertPrincipal& issuer = webkit_cert->issuer();
+ EXPECT_EQ("Go Daddy Secure Certification Authority", issuer.common_name);
+ EXPECT_EQ("Scottsdale", issuer.locality_name);
+ EXPECT_EQ("Arizona", issuer.state_or_province_name);
+ EXPECT_EQ("US", issuer.country_name);
+ EXPECT_EQ(0U, issuer.street_addresses.size());
+ ASSERT_EQ(1U, issuer.organization_names.size());
+ EXPECT_EQ("GoDaddy.com, Inc.", issuer.organization_names[0]);
+ ASSERT_EQ(1U, issuer.organization_unit_names.size());
+ EXPECT_EQ("http://certificates.godaddy.com/repository",
+ issuer.organization_unit_names[0]);
+ EXPECT_EQ(0U, issuer.domain_components.size());
+
+ // Use DoubleT because its epoch is the same on all platforms
+ const Time& valid_start = webkit_cert->valid_start();
+ EXPECT_EQ(1205883319, valid_start.ToDoubleT()); // Mar 18 23:35:19 2008 GMT
+
+ const Time& valid_expiry = webkit_cert->valid_expiry();
+ EXPECT_EQ(1300491319, valid_expiry.ToDoubleT()); // Mar 18 23:35:19 2011 GMT
+
+ const SHA1HashValue& fingerprint = webkit_cert->fingerprint();
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(webkit_fingerprint[i], fingerprint.data[i]);
+
+ std::vector<std::string> dns_names;
+ webkit_cert->GetDNSNames(&dns_names);
+ ASSERT_EQ(2U, dns_names.size());
+ EXPECT_EQ("*.webkit.org", dns_names[0]);
+ EXPECT_EQ("webkit.org", dns_names[1]);
+
+ // Test that the wildcard cert matches properly.
+ EXPECT_TRUE(webkit_cert->VerifyNameMatch("www.webkit.org"));
+ EXPECT_TRUE(webkit_cert->VerifyNameMatch("foo.webkit.org"));
+ EXPECT_TRUE(webkit_cert->VerifyNameMatch("webkit.org"));
+ EXPECT_FALSE(webkit_cert->VerifyNameMatch("www.webkit.com"));
+ EXPECT_FALSE(webkit_cert->VerifyNameMatch("www.foo.webkit.com"));
+}
+
+TEST(X509CertificateTest, ThawteCertParsing) {
+ scoped_refptr<X509Certificate> thawte_cert(X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der)));
+
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), thawte_cert);
+
+ const CertPrincipal& subject = thawte_cert->subject();
+ EXPECT_EQ("www.thawte.com", subject.common_name);
+ EXPECT_EQ("Mountain View", subject.locality_name);
+ EXPECT_EQ("California", subject.state_or_province_name);
+ EXPECT_EQ("US", subject.country_name);
+ EXPECT_EQ(0U, subject.street_addresses.size());
+ ASSERT_EQ(1U, subject.organization_names.size());
+ EXPECT_EQ("Thawte Inc", subject.organization_names[0]);
+ EXPECT_EQ(0U, subject.organization_unit_names.size());
+ EXPECT_EQ(0U, subject.domain_components.size());
+
+ const CertPrincipal& issuer = thawte_cert->issuer();
+ EXPECT_EQ("thawte Extended Validation SSL CA", issuer.common_name);
+ EXPECT_EQ("", issuer.locality_name);
+ EXPECT_EQ("", issuer.state_or_province_name);
+ EXPECT_EQ("US", issuer.country_name);
+ EXPECT_EQ(0U, issuer.street_addresses.size());
+ ASSERT_EQ(1U, issuer.organization_names.size());
+ EXPECT_EQ("thawte, Inc.", issuer.organization_names[0]);
+ ASSERT_EQ(1U, issuer.organization_unit_names.size());
+ EXPECT_EQ("Terms of use at https://www.thawte.com/cps (c)06",
+ issuer.organization_unit_names[0]);
+ EXPECT_EQ(0U, issuer.domain_components.size());
+
+ // Use DoubleT because its epoch is the same on all platforms
+ const Time& valid_start = thawte_cert->valid_start();
+ EXPECT_EQ(1227052800, valid_start.ToDoubleT()); // Nov 19 00:00:00 2008 GMT
+
+ const Time& valid_expiry = thawte_cert->valid_expiry();
+ EXPECT_EQ(1263772799, valid_expiry.ToDoubleT()); // Jan 17 23:59:59 2010 GMT
+
+ const SHA1HashValue& fingerprint = thawte_cert->fingerprint();
+ for (size_t i = 0; i < 20; ++i)
+ EXPECT_EQ(thawte_fingerprint[i], fingerprint.data[i]);
+
+ std::vector<std::string> dns_names;
+ thawte_cert->GetDNSNames(&dns_names);
+ ASSERT_EQ(1U, dns_names.size());
+ EXPECT_EQ("www.thawte.com", dns_names[0]);
+}
+
+// Test that all desired AttributeAndValue pairs can be extracted when only
+// a single RelativeDistinguishedName is present. "Normally" there is only
+// one AVA per RDN, but some CAs place all AVAs within a single RDN.
+// This is a regression test for http://crbug.com/101009
+TEST(X509CertificateTest, MultivalueRDN) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> multivalue_rdn_cert =
+ ImportCertFromFile(certs_dir, "multivalue_rdn.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), multivalue_rdn_cert);
+
+ const CertPrincipal& subject = multivalue_rdn_cert->subject();
+ EXPECT_EQ("Multivalue RDN Test", subject.common_name);
+ EXPECT_EQ("", subject.locality_name);
+ EXPECT_EQ("", subject.state_or_province_name);
+ EXPECT_EQ("US", subject.country_name);
+ EXPECT_EQ(0U, subject.street_addresses.size());
+ ASSERT_EQ(1U, subject.organization_names.size());
+ EXPECT_EQ("Chromium", subject.organization_names[0]);
+ ASSERT_EQ(1U, subject.organization_unit_names.size());
+ EXPECT_EQ("Chromium net_unittests", subject.organization_unit_names[0]);
+ ASSERT_EQ(1U, subject.domain_components.size());
+ EXPECT_EQ("Chromium", subject.domain_components[0]);
+}
+
+// Test that characters which would normally be escaped in the string form,
+// such as '=' or '"', are not escaped when parsed as individual components.
+// This is a regression test for http://crbug.com/102839
+TEST(X509CertificateTest, UnescapedSpecialCharacters) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> unescaped_cert =
+ ImportCertFromFile(certs_dir, "unescaped.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), unescaped_cert);
+
+ const CertPrincipal& subject = unescaped_cert->subject();
+ EXPECT_EQ("127.0.0.1", subject.common_name);
+ EXPECT_EQ("Mountain View", subject.locality_name);
+ EXPECT_EQ("California", subject.state_or_province_name);
+ EXPECT_EQ("US", subject.country_name);
+ ASSERT_EQ(1U, subject.street_addresses.size());
+ EXPECT_EQ("1600 Amphitheatre Parkway", subject.street_addresses[0]);
+ ASSERT_EQ(1U, subject.organization_names.size());
+ EXPECT_EQ("Chromium = \"net_unittests\"", subject.organization_names[0]);
+ ASSERT_EQ(2U, subject.organization_unit_names.size());
+ EXPECT_EQ("net_unittests", subject.organization_unit_names[0]);
+ EXPECT_EQ("Chromium", subject.organization_unit_names[1]);
+ EXPECT_EQ(0U, subject.domain_components.size());
+}
+
+TEST(X509CertificateTest, SerialNumbers) {
+ scoped_refptr<X509Certificate> google_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der)));
+
+ static const uint8 google_serial[16] = {
+ 0x01,0x2a,0x39,0x76,0x0d,0x3f,0x4f,0xc9,
+ 0x0b,0xe7,0xbd,0x2b,0xcf,0x95,0x2e,0x7a,
+ };
+
+ ASSERT_EQ(sizeof(google_serial), google_cert->serial_number().size());
+ EXPECT_TRUE(memcmp(google_cert->serial_number().data(), google_serial,
+ sizeof(google_serial)) == 0);
+
+ // We also want to check a serial number where the first byte is >= 0x80 in
+ // case the underlying library tries to pad it.
+ scoped_refptr<X509Certificate> paypal_null_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(paypal_null_der),
+ sizeof(paypal_null_der)));
+
+ static const uint8 paypal_null_serial[3] = {0x00, 0xf0, 0x9b};
+ ASSERT_EQ(sizeof(paypal_null_serial),
+ paypal_null_cert->serial_number().size());
+ EXPECT_TRUE(memcmp(paypal_null_cert->serial_number().data(),
+ paypal_null_serial, sizeof(paypal_null_serial)) == 0);
+}
+
+TEST(X509CertificateTest, CAFingerprints) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> server_cert =
+ ImportCertFromFile(certs_dir, "salesforce_com_test.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ scoped_refptr<X509Certificate> intermediate_cert1 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1);
+
+ scoped_refptr<X509Certificate> intermediate_cert2 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2);
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(intermediate_cert1->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain1 =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ intermediates.clear();
+ intermediates.push_back(intermediate_cert2->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain2 =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ // No intermediate CA certicates.
+ intermediates.clear();
+ scoped_refptr<X509Certificate> cert_chain3 =
+ X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
+ intermediates);
+
+ static const uint8 cert_chain1_ca_fingerprint[20] = {
+ 0xc2, 0xf0, 0x08, 0x7d, 0x01, 0xe6, 0x86, 0x05, 0x3a, 0x4d,
+ 0x63, 0x3e, 0x7e, 0x70, 0xd4, 0xef, 0x65, 0xc2, 0xcc, 0x4f
+ };
+ static const uint8 cert_chain2_ca_fingerprint[20] = {
+ 0xd5, 0x59, 0xa5, 0x86, 0x66, 0x9b, 0x08, 0xf4, 0x6a, 0x30,
+ 0xa1, 0x33, 0xf8, 0xa9, 0xed, 0x3d, 0x03, 0x8e, 0x2e, 0xa8
+ };
+ // The SHA-1 hash of nothing.
+ static const uint8 cert_chain3_ca_fingerprint[20] = {
+ 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55,
+ 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09
+ };
+ EXPECT_TRUE(memcmp(cert_chain1->ca_fingerprint().data,
+ cert_chain1_ca_fingerprint, 20) == 0);
+ EXPECT_TRUE(memcmp(cert_chain2->ca_fingerprint().data,
+ cert_chain2_ca_fingerprint, 20) == 0);
+ EXPECT_TRUE(memcmp(cert_chain3->ca_fingerprint().data,
+ cert_chain3_ca_fingerprint, 20) == 0);
+}
+
+TEST(X509CertificateTest, ParseSubjectAltNames) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> san_cert =
+ ImportCertFromFile(certs_dir, "subjectAltName_sanity_check.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), san_cert);
+
+ std::vector<std::string> dns_names;
+ std::vector<std::string> ip_addresses;
+ san_cert->GetSubjectAltName(&dns_names, &ip_addresses);
+
+ // Ensure that DNS names are correctly parsed.
+ ASSERT_EQ(1U, dns_names.size());
+ EXPECT_EQ("test.example", dns_names[0]);
+
+ // Ensure that both IPv4 and IPv6 addresses are correctly parsed.
+ ASSERT_EQ(2U, ip_addresses.size());
+
+ static const uint8 kIPv4Address[] = {
+ 0x7F, 0x00, 0x00, 0x02
+ };
+ ASSERT_EQ(arraysize(kIPv4Address), ip_addresses[0].size());
+ EXPECT_EQ(0, memcmp(ip_addresses[0].data(), kIPv4Address,
+ arraysize(kIPv4Address)));
+
+ static const uint8 kIPv6Address[] = {
+ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+ ASSERT_EQ(arraysize(kIPv6Address), ip_addresses[1].size());
+ EXPECT_EQ(0, memcmp(ip_addresses[1].data(), kIPv6Address,
+ arraysize(kIPv6Address)));
+
+ // Ensure the subjectAltName dirName has not influenced the handling of
+ // the subject commonName.
+ EXPECT_EQ("127.0.0.1", san_cert->subject().common_name);
+}
+
+TEST(X509CertificateTest, ExtractSPKIFromDERCert) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> cert =
+ ImportCertFromFile(certs_dir, "nist.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), cert);
+
+ std::string derBytes;
+ EXPECT_TRUE(X509Certificate::GetDEREncoded(cert->os_cert_handle(),
+ &derBytes));
+
+ base::StringPiece spkiBytes;
+ EXPECT_TRUE(asn1::ExtractSPKIFromDERCert(derBytes, &spkiBytes));
+
+ uint8 hash[base::kSHA1Length];
+ base::SHA1HashBytes(reinterpret_cast<const uint8*>(spkiBytes.data()),
+ spkiBytes.size(), hash);
+
+ EXPECT_EQ(0, memcmp(hash, kNistSPKIHash, sizeof(hash)));
+}
+
+TEST(X509CertificateTest, ExtractCRLURLsFromDERCert) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> cert =
+ ImportCertFromFile(certs_dir, "nist.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), cert);
+
+ std::string derBytes;
+ EXPECT_TRUE(X509Certificate::GetDEREncoded(cert->os_cert_handle(),
+ &derBytes));
+
+ std::vector<base::StringPiece> crl_urls;
+ EXPECT_TRUE(asn1::ExtractCRLURLsFromDERCert(derBytes, &crl_urls));
+
+ EXPECT_EQ(1u, crl_urls.size());
+ if (crl_urls.size() > 0) {
+ EXPECT_EQ("http://SVRSecure-G3-crl.verisign.com/SVRSecureG3.crl",
+ crl_urls[0].as_string());
+ }
+}
+
+// Tests X509CertificateCache via X509Certificate::CreateFromHandle. We
+// call X509Certificate::CreateFromHandle several times and observe whether
+// it returns a cached or new OSCertHandle.
+TEST(X509CertificateTest, Cache) {
+ X509Certificate::OSCertHandle google_cert_handle;
+ X509Certificate::OSCertHandle thawte_cert_handle;
+
+ // Add a single certificate to the certificate cache.
+ google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ scoped_refptr<X509Certificate> cert1(X509Certificate::CreateFromHandle(
+ google_cert_handle, X509Certificate::OSCertHandles()));
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
+
+ // Add the same certificate, but as a new handle.
+ google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ scoped_refptr<X509Certificate> cert2(X509Certificate::CreateFromHandle(
+ google_cert_handle, X509Certificate::OSCertHandles()));
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
+
+ // A new X509Certificate should be returned.
+ EXPECT_NE(cert1.get(), cert2.get());
+ // But both instances should share the underlying OS certificate handle.
+ EXPECT_EQ(cert1->os_cert_handle(), cert2->os_cert_handle());
+ EXPECT_EQ(0u, cert1->GetIntermediateCertificates().size());
+ EXPECT_EQ(0u, cert2->GetIntermediateCertificates().size());
+
+ // Add the same certificate, but this time with an intermediate. This
+ // should result in the intermediate being cached. Note that this is not
+ // a legitimate chain, but is suitable for testing.
+ google_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ thawte_cert_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der));
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(thawte_cert_handle);
+ scoped_refptr<X509Certificate> cert3(X509Certificate::CreateFromHandle(
+ google_cert_handle, intermediates));
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
+ X509Certificate::FreeOSCertHandle(thawte_cert_handle);
+
+ // Test that the new certificate, even with intermediates, results in the
+ // same underlying handle being used.
+ EXPECT_EQ(cert1->os_cert_handle(), cert3->os_cert_handle());
+ // Though they use the same OS handle, the intermediates should be different.
+ EXPECT_NE(cert1->GetIntermediateCertificates().size(),
+ cert3->GetIntermediateCertificates().size());
+}
+
+TEST(X509CertificateTest, Pickle) {
+ X509Certificate::OSCertHandle google_cert_handle =
+ X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ X509Certificate::OSCertHandle thawte_cert_handle =
+ X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der));
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(thawte_cert_handle);
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ google_cert_handle, intermediates);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), cert.get());
+
+ X509Certificate::FreeOSCertHandle(google_cert_handle);
+ X509Certificate::FreeOSCertHandle(thawte_cert_handle);
+
+ Pickle pickle;
+ cert->Persist(&pickle);
+
+ PickleIterator iter(pickle);
+ scoped_refptr<X509Certificate> cert_from_pickle =
+ X509Certificate::CreateFromPickle(
+ pickle, &iter, X509Certificate::PICKLETYPE_CERTIFICATE_CHAIN_V3);
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), cert_from_pickle);
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(
+ cert->os_cert_handle(), cert_from_pickle->os_cert_handle()));
+ const X509Certificate::OSCertHandles& cert_intermediates =
+ cert->GetIntermediateCertificates();
+ const X509Certificate::OSCertHandles& pickle_intermediates =
+ cert_from_pickle->GetIntermediateCertificates();
+ ASSERT_EQ(cert_intermediates.size(), pickle_intermediates.size());
+ for (size_t i = 0; i < cert_intermediates.size(); ++i) {
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(cert_intermediates[i],
+ pickle_intermediates[i]));
+ }
+}
+
+TEST(X509CertificateTest, Policy) {
+ scoped_refptr<X509Certificate> google_cert(X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der)));
+
+ scoped_refptr<X509Certificate> webkit_cert(X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
+
+ CertPolicy policy;
+
+ // To begin with, everything should be unknown.
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_FALSE(policy.HasAllowedCert());
+ EXPECT_FALSE(policy.HasDeniedCert());
+
+ // Test adding one certificate with one error.
+ policy.Allow(google_cert.get(), CERT_STATUS_DATE_INVALID);
+ EXPECT_EQ(CertPolicy::ALLOWED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(),
+ CERT_STATUS_DATE_INVALID | CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_TRUE(policy.HasAllowedCert());
+ EXPECT_FALSE(policy.HasDeniedCert());
+
+ // Test saving the same certificate with a new error.
+ policy.Allow(google_cert.get(), CERT_STATUS_AUTHORITY_INVALID);
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::ALLOWED,
+ policy.Check(google_cert.get(), CERT_STATUS_AUTHORITY_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_TRUE(policy.HasAllowedCert());
+ EXPECT_FALSE(policy.HasDeniedCert());
+
+ // Test adding one certificate with two errors.
+ policy.Allow(google_cert.get(),
+ CERT_STATUS_DATE_INVALID | CERT_STATUS_AUTHORITY_INVALID);
+ EXPECT_EQ(CertPolicy::ALLOWED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::ALLOWED,
+ policy.Check(google_cert.get(), CERT_STATUS_AUTHORITY_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_TRUE(policy.HasAllowedCert());
+ EXPECT_FALSE(policy.HasDeniedCert());
+
+ // Test removing a certificate that was previously allowed.
+ policy.Deny(google_cert.get(), CERT_STATUS_DATE_INVALID);
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_FALSE(policy.HasAllowedCert());
+ EXPECT_TRUE(policy.HasDeniedCert());
+
+ // Test removing a certificate that was previously unknown.
+ policy.Deny(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID);
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_FALSE(policy.HasAllowedCert());
+ EXPECT_TRUE(policy.HasDeniedCert());
+
+ // Test saving a certificate that was previously denied.
+ policy.Allow(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID);
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::ALLOWED,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_TRUE(policy.HasAllowedCert());
+ EXPECT_TRUE(policy.HasDeniedCert());
+
+ // Test denying an overlapping certificate.
+ policy.Allow(google_cert.get(),
+ CERT_STATUS_COMMON_NAME_INVALID | CERT_STATUS_DATE_INVALID);
+ policy.Deny(google_cert.get(), CERT_STATUS_DATE_INVALID);
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(google_cert.get(), CERT_STATUS_DATE_INVALID));
+ EXPECT_EQ(CertPolicy::UNKNOWN,
+ policy.Check(google_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(google_cert.get(),
+ CERT_STATUS_COMMON_NAME_INVALID | CERT_STATUS_DATE_INVALID));
+
+ // Test denying an overlapping certificate (other direction).
+ policy.Allow(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID);
+ policy.Deny(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID);
+ policy.Deny(webkit_cert.get(), CERT_STATUS_DATE_INVALID);
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(webkit_cert.get(), CERT_STATUS_COMMON_NAME_INVALID));
+ EXPECT_EQ(CertPolicy::DENIED,
+ policy.Check(webkit_cert.get(), CERT_STATUS_DATE_INVALID));
+}
+
+TEST(X509CertificateTest, IntermediateCertificates) {
+ scoped_refptr<X509Certificate> webkit_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
+
+ scoped_refptr<X509Certificate> thawte_cert(
+ X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(thawte_der), sizeof(thawte_der)));
+
+ X509Certificate::OSCertHandle google_handle;
+ // Create object with no intermediates:
+ google_handle = X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(google_der), sizeof(google_der));
+ X509Certificate::OSCertHandles intermediates1;
+ scoped_refptr<X509Certificate> cert1;
+ cert1 = X509Certificate::CreateFromHandle(google_handle, intermediates1);
+ EXPECT_EQ(0u, cert1->GetIntermediateCertificates().size());
+
+ // Create object with 2 intermediates:
+ X509Certificate::OSCertHandles intermediates2;
+ intermediates2.push_back(webkit_cert->os_cert_handle());
+ intermediates2.push_back(thawte_cert->os_cert_handle());
+ scoped_refptr<X509Certificate> cert2;
+ cert2 = X509Certificate::CreateFromHandle(google_handle, intermediates2);
+
+ // Verify it has all the intermediates:
+ const X509Certificate::OSCertHandles& cert2_intermediates =
+ cert2->GetIntermediateCertificates();
+ ASSERT_EQ(2u, cert2_intermediates.size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(cert2_intermediates[0],
+ webkit_cert->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(cert2_intermediates[1],
+ thawte_cert->os_cert_handle()));
+
+ // Cleanup
+ X509Certificate::FreeOSCertHandle(google_handle);
+}
+
+TEST(X509CertificateTest, IsIssuedByEncoded) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ // Test a client certificate from MIT.
+ scoped_refptr<X509Certificate> mit_davidben_cert(
+ ImportCertFromFile(certs_dir, "mit.davidben.der"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), mit_davidben_cert);
+
+ std::string mit_issuer(reinterpret_cast<const char*>(MITDN),
+ sizeof(MITDN));
+
+ // Test a certificate from Google, issued by Thawte
+ scoped_refptr<X509Certificate> google_cert(
+ ImportCertFromFile(certs_dir, "google.single.der"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), google_cert);
+
+ std::string thawte_issuer(reinterpret_cast<const char*>(ThawteDN),
+ sizeof(ThawteDN));
+
+ // Check that the David Ben certificate is issued by MIT, but not
+ // by Thawte.
+ std::vector<std::string> issuers;
+ issuers.clear();
+ issuers.push_back(mit_issuer);
+ EXPECT_TRUE(mit_davidben_cert->IsIssuedByEncoded(issuers));
+ EXPECT_FALSE(google_cert->IsIssuedByEncoded(issuers));
+
+ // Check that the Google certificate is issued by Thawte and not
+ // by MIT.
+ issuers.clear();
+ issuers.push_back(thawte_issuer);
+ EXPECT_FALSE(mit_davidben_cert->IsIssuedByEncoded(issuers));
+ EXPECT_TRUE(google_cert->IsIssuedByEncoded(issuers));
+
+ // Check that they both pass when given a list of the two issuers.
+ issuers.clear();
+ issuers.push_back(mit_issuer);
+ issuers.push_back(thawte_issuer);
+ EXPECT_TRUE(mit_davidben_cert->IsIssuedByEncoded(issuers));
+ EXPECT_TRUE(google_cert->IsIssuedByEncoded(issuers));
+}
+
+TEST(X509CertificateTest, IsIssuedByEncodedWithIntermediates) {
+ static const unsigned char kPolicyRootDN[] = {
+ 0x30, 0x1e, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x54, 0x65, 0x73, 0x74,
+ 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41
+ };
+ static const unsigned char kPolicyIntermediateDN[] = {
+ 0x30, 0x26, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x1b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x54, 0x65, 0x73, 0x74,
+ 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74,
+ 0x65, 0x20, 0x43, 0x41
+ };
+
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ CertificateList policy_chain = CreateCertificateListFromFile(
+ certs_dir, "explicit-policy-chain.pem", X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3u, policy_chain.size());
+
+ // The intermediate CA certificate's policyConstraints extension has a
+ // requireExplicitPolicy field with SkipCerts=0.
+ std::string policy_intermediate_dn(
+ reinterpret_cast<const char*>(kPolicyIntermediateDN),
+ sizeof(kPolicyIntermediateDN));
+ std::string policy_root_dn(reinterpret_cast<const char*>(kPolicyRootDN),
+ sizeof(kPolicyRootDN));
+
+ X509Certificate::OSCertHandles intermediates;
+ intermediates.push_back(policy_chain[1]->os_cert_handle());
+ scoped_refptr<X509Certificate> cert_chain =
+ X509Certificate::CreateFromHandle(policy_chain[0]->os_cert_handle(),
+ intermediates);
+
+ std::vector<std::string> issuers;
+
+ // Check that the chain is issued by the intermediate.
+ issuers.clear();
+ issuers.push_back(policy_intermediate_dn);
+ EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers));
+
+ // Check that the chain is also issued by the root.
+ issuers.clear();
+ issuers.push_back(policy_root_dn);
+ EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers));
+
+ // Check that the chain is issued by either the intermediate or the root.
+ issuers.clear();
+ issuers.push_back(policy_intermediate_dn);
+ issuers.push_back(policy_root_dn);
+ EXPECT_TRUE(cert_chain->IsIssuedByEncoded(issuers));
+
+ // Check that an empty issuers list returns false.
+ issuers.clear();
+ EXPECT_FALSE(cert_chain->IsIssuedByEncoded(issuers));
+
+ // Check that the chain is not issued by Verisign
+ std::string mit_issuer(reinterpret_cast<const char*>(VerisignDN),
+ sizeof(VerisignDN));
+ issuers.clear();
+ issuers.push_back(mit_issuer);
+ EXPECT_FALSE(cert_chain->IsIssuedByEncoded(issuers));
+}
+
+#if defined(USE_NSS)
+TEST(X509CertificateTest, GetDefaultNickname) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "no_subject_common_name_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ std::string nickname = test_cert->GetDefaultNickname(USER_CERT);
+ EXPECT_EQ("wtc@google.com's COMODO Client Authentication and "
+ "Secure Email CA ID", nickname);
+}
+#endif
+
+class X509CertificateParseTest
+ : public testing::TestWithParam<CertificateFormatTestData> {
+ public:
+ virtual ~X509CertificateParseTest() {}
+ virtual void SetUp() {
+ test_data_ = GetParam();
+ }
+ virtual void TearDown() {}
+
+ protected:
+ CertificateFormatTestData test_data_;
+};
+
+TEST_P(X509CertificateParseTest, CanParseFormat) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ CertificateList certs = CreateCertificateListFromFile(
+ certs_dir, test_data_.file_name, test_data_.format);
+ ASSERT_FALSE(certs.empty());
+ ASSERT_LE(certs.size(), arraysize(test_data_.chain_fingerprints));
+ CheckGoogleCert(certs.front(), google_parse_fingerprint,
+ kGoogleParseValidFrom, kGoogleParseValidTo);
+
+ size_t i;
+ for (i = 0; i < arraysize(test_data_.chain_fingerprints); ++i) {
+ if (test_data_.chain_fingerprints[i] == NULL) {
+ // No more test certificates expected - make sure no more were
+ // returned before marking this test a success.
+ EXPECT_EQ(i, certs.size());
+ break;
+ }
+
+ // A cert is expected - make sure that one was parsed.
+ ASSERT_LT(i, certs.size());
+
+ // Compare the parsed certificate with the expected certificate, by
+ // comparing fingerprints.
+ const X509Certificate* cert = certs[i].get();
+ const SHA1HashValue& actual_fingerprint = cert->fingerprint();
+ uint8* expected_fingerprint = test_data_.chain_fingerprints[i];
+
+ for (size_t j = 0; j < 20; ++j)
+ EXPECT_EQ(expected_fingerprint[j], actual_fingerprint.data[j]);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(, X509CertificateParseTest,
+ testing::ValuesIn(FormatTestData));
+
+struct CertificateNameVerifyTestData {
+ // true iff we expect hostname to match an entry in cert_names.
+ bool expected;
+ // The hostname to match.
+ const char* hostname;
+ // Common name, may be used if |dns_names| or |ip_addrs| are empty.
+ const char* common_name;
+ // Comma separated list of certificate names to match against. Any occurrence
+ // of '#' will be replaced with a null character before processing.
+ const char* dns_names;
+ // Comma separated list of certificate IP Addresses to match against. Each
+ // address is x prefixed 16 byte hex code for v6 or dotted-decimals for v4.
+ const char* ip_addrs;
+};
+
+// GTest 'magic' pretty-printer, so that if/when a test fails, it knows how
+// to output the parameter that was passed. Without this, it will simply
+// attempt to print out the first twenty bytes of the object, which depending
+// on platform and alignment, may result in an invalid read.
+void PrintTo(const CertificateNameVerifyTestData& data, std::ostream* os) {
+ ASSERT_TRUE(data.hostname && data.common_name);
+ // Using StringPiece to allow for optional fields being NULL.
+ *os << " expected: " << data.expected
+ << "; hostname: " << data.hostname
+ << "; common_name: " << data.common_name
+ << "; dns_names: " << base::StringPiece(data.dns_names)
+ << "; ip_addrs: " << base::StringPiece(data.ip_addrs);
+}
+
+const CertificateNameVerifyTestData kNameVerifyTestData[] = {
+ { true, "foo.com", "foo.com" },
+ { true, "f", "f" },
+ { false, "h", "i" },
+ { true, "bar.foo.com", "*.foo.com" },
+ { true, "www.test.fr", "common.name",
+ "*.test.com,*.test.co.uk,*.test.de,*.test.fr" },
+ { true, "wwW.tESt.fr", "common.name",
+ ",*.*,*.test.de,*.test.FR,www" },
+ { false, "f.uk", ".uk" },
+ { false, "w.bar.foo.com", "?.bar.foo.com" },
+ { false, "www.foo.com", "(www|ftp).foo.com" },
+ { false, "www.foo.com", "www.foo.com#" }, // # = null char.
+ { false, "www.foo.com", "", "www.foo.com#*.foo.com,#,#" },
+ { false, "www.house.example", "ww.house.example" },
+ { false, "test.org", "", "www.test.org,*.test.org,*.org" },
+ { false, "w.bar.foo.com", "w*.bar.foo.com" },
+ { false, "www.bar.foo.com", "ww*ww.bar.foo.com" },
+ { false, "wwww.bar.foo.com", "ww*ww.bar.foo.com" },
+ { true, "wwww.bar.foo.com", "w*w.bar.foo.com" },
+ { false, "wwww.bar.foo.com", "w*w.bar.foo.c0m" },
+ { true, "WALLY.bar.foo.com", "wa*.bar.foo.com" },
+ { true, "wally.bar.foo.com", "*Ly.bar.foo.com" },
+ { true, "ww%57.foo.com", "", "www.foo.com" },
+ { true, "www&.foo.com", "www%26.foo.com" },
+ // Common name must not be used if subject alternative name was provided.
+ { false, "www.test.co.jp", "www.test.co.jp",
+ "*.test.de,*.jp,www.test.co.uk,www.*.co.jp" },
+ { false, "www.bar.foo.com", "www.bar.foo.com",
+ "*.foo.com,*.*.foo.com,*.*.bar.foo.com,*..bar.foo.com," },
+ { false, "www.bath.org", "www.bath.org", "", "20.30.40.50" },
+ { false, "66.77.88.99", "www.bath.org", "www.bath.org" },
+ // IDN tests
+ { true, "xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br" },
+ { true, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br" },
+ { false, "xn--poema-9qae5a.com.br", "", "*.xn--poema-9qae5a.com.br,"
+ "xn--poema-*.com.br,"
+ "xn--*-9qae5a.com.br,"
+ "*--poema-9qae5a.com.br" },
+ // The following are adapted from the examples quoted from
+ // http://tools.ietf.org/html/rfc6125#section-6.4.3
+ // (e.g., *.example.com would match foo.example.com but
+ // not bar.foo.example.com or example.com).
+ { true, "foo.example.com", "*.example.com" },
+ { false, "bar.foo.example.com", "*.example.com" },
+ { false, "example.com", "*.example.com" },
+ // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would
+ // be taken to match baz1.example.net and foobaz.example.net and
+ // buzz.example.net, respectively
+ { true, "baz1.example.net", "baz*.example.net" },
+ { true, "foobaz.example.net", "*baz.example.net" },
+ { true, "buzz.example.net", "b*z.example.net" },
+ // Wildcards should not be valid for public registry controlled domains,
+ // and unknown/unrecognized domains, at least three domain components must
+ // be present.
+ { true, "www.test.example", "*.test.example" },
+ { true, "test.example.co.uk", "*.example.co.uk" },
+ { false, "test.example", "*.exmaple" },
+ { false, "example.co.uk", "*.co.uk" },
+ { false, "foo.com", "*.com" },
+ { false, "foo.us", "*.us" },
+ { false, "foo", "*" },
+ // IDN variants of wildcards and registry controlled domains.
+ { true, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br" },
+ { true, "test.example.xn--mgbaam7a8h", "*.example.xn--mgbaam7a8h" },
+ { false, "xn--poema-9qae5a.com.br", "*.com.br" },
+ { false, "example.xn--mgbaam7a8h", "*.xn--mgbaam7a8h" },
+ // Wildcards should be permissible for 'private' registry controlled
+ // domains.
+ { true, "www.appspot.com", "*.appspot.com" },
+ { true, "foo.s3.amazonaws.com", "*.s3.amazonaws.com" },
+ // Multiple wildcards are not valid.
+ { false, "foo.example.com", "*.*.com" },
+ { false, "foo.bar.example.com", "*.bar.*.com" },
+ // Absolute vs relative DNS name tests. Although not explicitly specified
+ // in RFC 6125, absolute reference names (those ending in a .) should
+ // match either absolute or relative presented names.
+ { true, "foo.com", "foo.com." },
+ { true, "foo.com.", "foo.com" },
+ { true, "foo.com.", "foo.com." },
+ { true, "f", "f." },
+ { true, "f.", "f" },
+ { true, "f.", "f." },
+ { true, "www-3.bar.foo.com", "*.bar.foo.com." },
+ { true, "www-3.bar.foo.com.", "*.bar.foo.com" },
+ { true, "www-3.bar.foo.com.", "*.bar.foo.com." },
+ { false, ".", "." },
+ { false, "example.com", "*.com." },
+ { false, "example.com.", "*.com" },
+ { false, "example.com.", "*.com." },
+ { false, "foo.", "*." },
+ { false, "foo", "*." },
+ { false, "foo.co.uk", "*.co.uk." },
+ { false, "foo.co.uk.", "*.co.uk." },
+ // IP addresses in common name; IPv4 only.
+ { true, "127.0.0.1", "127.0.0.1" },
+ { true, "192.168.1.1", "192.168.1.1" },
+ { true, "676768", "0.10.83.160" },
+ { true, "1.2.3", "1.2.0.3" },
+ { false, "192.169.1.1", "192.168.1.1" },
+ { false, "12.19.1.1", "12.19.1.1/255.255.255.0" },
+ { false, "FEDC:ba98:7654:3210:FEDC:BA98:7654:3210",
+ "FEDC:BA98:7654:3210:FEDC:ba98:7654:3210" },
+ { false, "1111:2222:3333:4444:5555:6666:7777:8888",
+ "1111:2222:3333:4444:5555:6666:7777:8888" },
+ { false, "::192.9.5.5", "[::192.9.5.5]" },
+ // No wildcard matching in valid IP addresses
+ { false, "::192.9.5.5", "*.9.5.5" },
+ { false, "2010:836B:4179::836B:4179", "*:836B:4179::836B:4179" },
+ { false, "192.168.1.11", "*.168.1.11" },
+ { false, "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", "*.]" },
+ // IP addresses in subject alternative name (common name ignored)
+ { true, "10.1.2.3", "", "", "10.1.2.3" },
+ { true, "14.15", "", "", "14.0.0.15" },
+ { false, "10.1.2.7", "10.1.2.7", "", "10.1.2.6,10.1.2.8" },
+ { false, "10.1.2.8", "10.20.2.8", "foo" },
+ { true, "::4.5.6.7", "", "", "x00000000000000000000000004050607" },
+ { false, "::6.7.8.9", "::6.7.8.9", "::6.7.8.9",
+ "x00000000000000000000000006070808,x0000000000000000000000000607080a,"
+ "xff000000000000000000000006070809,6.7.8.9" },
+ { true, "FE80::200:f8ff:fe21:67cf", "no.common.name", "",
+ "x00000000000000000000000006070808,xfe800000000000000200f8fffe2167cf,"
+ "xff0000000000000000000000060708ff,10.0.0.1" },
+ // Numeric only hostnames (none of these are considered valid IP addresses).
+ { false, "12345.6", "12345.6" },
+ { false, "121.2.3.512", "", "1*1.2.3.512,*1.2.3.512,1*.2.3.512,*.2.3.512",
+ "121.2.3.0"},
+ { false, "1.2.3.4.5.6", "*.2.3.4.5.6" },
+ { true, "1.2.3.4.5", "", "1.2.3.4.5" },
+ // Invalid host names.
+ { false, "junk)(£)$*!@~#", "junk)(£)$*!@~#" },
+ { false, "www.*.com", "www.*.com" },
+ { false, "w$w.f.com", "w$w.f.com" },
+ { false, "nocolonallowed:example", "", "nocolonallowed:example" },
+ { false, "www-1.[::FFFF:129.144.52.38]", "*.[::FFFF:129.144.52.38]" },
+ { false, "[::4.5.6.9]", "", "", "x00000000000000000000000004050609" },
+};
+
+class X509CertificateNameVerifyTest
+ : public testing::TestWithParam<CertificateNameVerifyTestData> {
+};
+
+TEST_P(X509CertificateNameVerifyTest, VerifyHostname) {
+ CertificateNameVerifyTestData test_data = GetParam();
+
+ std::string common_name(test_data.common_name);
+ ASSERT_EQ(std::string::npos, common_name.find(','));
+ std::replace(common_name.begin(), common_name.end(), '#', '\0');
+
+ std::vector<std::string> dns_names, ip_addressses;
+ if (test_data.dns_names) {
+ // Build up the certificate DNS names list.
+ std::string dns_name_line(test_data.dns_names);
+ std::replace(dns_name_line.begin(), dns_name_line.end(), '#', '\0');
+ base::SplitString(dns_name_line, ',', &dns_names);
+ }
+
+ if (test_data.ip_addrs) {
+ // Build up the certificate IP address list.
+ std::string ip_addrs_line(test_data.ip_addrs);
+ std::vector<std::string> ip_addressses_ascii;
+ base::SplitString(ip_addrs_line, ',', &ip_addressses_ascii);
+ for (size_t i = 0; i < ip_addressses_ascii.size(); ++i) {
+ std::string& addr_ascii = ip_addressses_ascii[i];
+ ASSERT_NE(0U, addr_ascii.length());
+ if (addr_ascii[0] == 'x') { // Hex encoded address
+ addr_ascii.erase(0, 1);
+ std::vector<uint8> bytes;
+ EXPECT_TRUE(base::HexStringToBytes(addr_ascii, &bytes))
+ << "Could not parse hex address " << addr_ascii << " i = " << i;
+ ip_addressses.push_back(std::string(reinterpret_cast<char*>(&bytes[0]),
+ bytes.size()));
+ ASSERT_EQ(16U, ip_addressses.back().size()) << i;
+ } else { // Decimal groups
+ std::vector<std::string> decimals_ascii;
+ base::SplitString(addr_ascii, '.', &decimals_ascii);
+ EXPECT_EQ(4U, decimals_ascii.size()) << i;
+ std::string addr_bytes;
+ for (size_t j = 0; j < decimals_ascii.size(); ++j) {
+ int decimal_value;
+ EXPECT_TRUE(base::StringToInt(decimals_ascii[j], &decimal_value));
+ EXPECT_GE(decimal_value, 0);
+ EXPECT_LE(decimal_value, 255);
+ addr_bytes.push_back(static_cast<char>(decimal_value));
+ }
+ ip_addressses.push_back(addr_bytes);
+ ASSERT_EQ(4U, ip_addressses.back().size()) << i;
+ }
+ }
+ }
+
+ EXPECT_EQ(test_data.expected, X509Certificate::VerifyHostname(
+ test_data.hostname, common_name, dns_names, ip_addressses));
+}
+
+INSTANTIATE_TEST_CASE_P(, X509CertificateNameVerifyTest,
+ testing::ValuesIn(kNameVerifyTestData));
+
+} // namespace net
diff --git a/chromium/net/cert/x509_certificate_win.cc b/chromium/net/cert/x509_certificate_win.cc
new file mode 100644
index 00000000000..f0c9e502e46
--- /dev/null
+++ b/chromium/net/cert/x509_certificate_win.cc
@@ -0,0 +1,453 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_certificate.h"
+
+#include <blapi.h> // Implement CalculateChainFingerprint() with NSS.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "crypto/capi_util.h"
+#include "crypto/scoped_capi_types.h"
+#include "net/base/net_errors.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+typedef crypto::ScopedCAPIHandle<
+ HCERTSTORE,
+ crypto::CAPIDestroyerWithFlags<HCERTSTORE,
+ CertCloseStore, 0> > ScopedHCERTSTORE;
+
+void ExplodedTimeToSystemTime(const base::Time::Exploded& exploded,
+ SYSTEMTIME* system_time) {
+ system_time->wYear = exploded.year;
+ system_time->wMonth = exploded.month;
+ system_time->wDayOfWeek = exploded.day_of_week;
+ system_time->wDay = exploded.day_of_month;
+ system_time->wHour = exploded.hour;
+ system_time->wMinute = exploded.minute;
+ system_time->wSecond = exploded.second;
+ system_time->wMilliseconds = exploded.millisecond;
+}
+
+//-----------------------------------------------------------------------------
+
+// Decodes the cert's subjectAltName extension into a CERT_ALT_NAME_INFO
+// structure and stores it in *output.
+void GetCertSubjectAltName(PCCERT_CONTEXT cert,
+ scoped_ptr_malloc<CERT_ALT_NAME_INFO>* output) {
+ PCERT_EXTENSION extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
+ cert->pCertInfo->cExtension,
+ cert->pCertInfo->rgExtension);
+ if (!extension)
+ return;
+
+ CRYPT_DECODE_PARA decode_para;
+ decode_para.cbSize = sizeof(decode_para);
+ decode_para.pfnAlloc = crypto::CryptAlloc;
+ decode_para.pfnFree = crypto::CryptFree;
+ CERT_ALT_NAME_INFO* alt_name_info = NULL;
+ DWORD alt_name_info_size = 0;
+ BOOL rv;
+ rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ szOID_SUBJECT_ALT_NAME2,
+ extension->Value.pbData,
+ extension->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &alt_name_info,
+ &alt_name_info_size);
+ if (rv)
+ output->reset(alt_name_info);
+}
+
+void AddCertsFromStore(HCERTSTORE store,
+ X509Certificate::OSCertHandles* results) {
+ PCCERT_CONTEXT cert = NULL;
+
+ while ((cert = CertEnumCertificatesInStore(store, cert)) != NULL) {
+ PCCERT_CONTEXT to_add = NULL;
+ if (CertAddCertificateContextToStore(
+ NULL, // The cert won't be persisted in any cert store. This breaks
+ // any association the context currently has to |store|, which
+ // allows us, the caller, to safely close |store| without
+ // releasing the cert handles.
+ cert,
+ CERT_STORE_ADD_USE_EXISTING,
+ &to_add) && to_add != NULL) {
+ // When processing stores generated from PKCS#7/PKCS#12 files, it
+ // appears that the order returned is the inverse of the order that it
+ // appeared in the file.
+ // TODO(rsleevi): Ensure this order is consistent across all Win
+ // versions
+ results->insert(results->begin(), to_add);
+ }
+ }
+}
+
+X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) {
+ X509Certificate::OSCertHandles results;
+ CERT_BLOB data_blob;
+ data_blob.cbData = length;
+ data_blob.pbData = reinterpret_cast<BYTE*>(const_cast<char*>(data));
+
+ HCERTSTORE out_store = NULL;
+
+ DWORD expected_types = CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED |
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED |
+ CERT_QUERY_CONTENT_FLAG_PKCS7_UNSIGNED;
+
+ if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &data_blob, expected_types,
+ CERT_QUERY_FORMAT_FLAG_BINARY, 0, NULL, NULL, NULL,
+ &out_store, NULL, NULL) || out_store == NULL) {
+ return results;
+ }
+
+ AddCertsFromStore(out_store, &results);
+ CertCloseStore(out_store, CERT_CLOSE_STORE_CHECK_FLAG);
+
+ return results;
+}
+
+// Given a CERT_NAME_BLOB, returns true if it appears in a given list,
+// formatted as a vector of strings holding DER-encoded X.509
+// DistinguishedName entries.
+bool IsCertNameBlobInIssuerList(
+ CERT_NAME_BLOB* name_blob,
+ const std::vector<std::string>& issuer_names) {
+ for (std::vector<std::string>::const_iterator it = issuer_names.begin();
+ it != issuer_names.end(); ++it) {
+ CERT_NAME_BLOB issuer_blob;
+ issuer_blob.pbData =
+ reinterpret_cast<BYTE*>(const_cast<char*>(it->data()));
+ issuer_blob.cbData = static_cast<DWORD>(it->length());
+
+ BOOL rb = CertCompareCertificateName(
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &issuer_blob, name_blob);
+ if (rb)
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+void X509Certificate::Initialize() {
+ DCHECK(cert_handle_);
+ subject_.ParseDistinguishedName(cert_handle_->pCertInfo->Subject.pbData,
+ cert_handle_->pCertInfo->Subject.cbData);
+ issuer_.ParseDistinguishedName(cert_handle_->pCertInfo->Issuer.pbData,
+ cert_handle_->pCertInfo->Issuer.cbData);
+
+ valid_start_ = Time::FromFileTime(cert_handle_->pCertInfo->NotBefore);
+ valid_expiry_ = Time::FromFileTime(cert_handle_->pCertInfo->NotAfter);
+
+ fingerprint_ = CalculateFingerprint(cert_handle_);
+ ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_);
+
+ const CRYPT_INTEGER_BLOB* serial = &cert_handle_->pCertInfo->SerialNumber;
+ scoped_ptr<uint8[]> serial_bytes(new uint8[serial->cbData]);
+ for (unsigned i = 0; i < serial->cbData; i++)
+ serial_bytes[i] = serial->pbData[serial->cbData - i - 1];
+ serial_number_ = std::string(
+ reinterpret_cast<char*>(serial_bytes.get()), serial->cbData);
+}
+
+void X509Certificate::GetSubjectAltName(
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) const {
+ if (dns_names)
+ dns_names->clear();
+ if (ip_addrs)
+ ip_addrs->clear();
+
+ if (!cert_handle_)
+ return;
+
+ scoped_ptr_malloc<CERT_ALT_NAME_INFO> alt_name_info;
+ GetCertSubjectAltName(cert_handle_, &alt_name_info);
+ CERT_ALT_NAME_INFO* alt_name = alt_name_info.get();
+ if (alt_name) {
+ int num_entries = alt_name->cAltEntry;
+ for (int i = 0; i < num_entries; i++) {
+ // dNSName is an ASN.1 IA5String representing a string of ASCII
+ // characters, so we can use WideToASCII here.
+ const CERT_ALT_NAME_ENTRY& entry = alt_name->rgAltEntry[i];
+
+ if (dns_names && entry.dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) {
+ dns_names->push_back(WideToASCII(entry.pwszDNSName));
+ } else if (ip_addrs &&
+ entry.dwAltNameChoice == CERT_ALT_NAME_IP_ADDRESS) {
+ ip_addrs->push_back(std::string(
+ reinterpret_cast<const char*>(entry.IPAddress.pbData),
+ entry.IPAddress.cbData));
+ }
+ }
+ }
+}
+
+PCCERT_CONTEXT X509Certificate::CreateOSCertChainForCert() const {
+ // Create an in-memory certificate store to hold this certificate and
+ // any intermediate certificates in |intermediate_ca_certs_|. The store
+ // will be referenced in the returned PCCERT_CONTEXT, and will not be freed
+ // until the PCCERT_CONTEXT is freed.
+ ScopedHCERTSTORE store(CertOpenStore(
+ CERT_STORE_PROV_MEMORY, 0, NULL,
+ CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL));
+ if (!store.get())
+ return NULL;
+
+ // NOTE: This preserves all of the properties of |os_cert_handle()| except
+ // for CERT_KEY_PROV_HANDLE_PROP_ID and CERT_KEY_CONTEXT_PROP_ID - the two
+ // properties that hold access to already-opened private keys. If a handle
+ // has already been unlocked (eg: PIN prompt), then the first time that the
+ // identity is used for client auth, it may prompt the user again.
+ PCCERT_CONTEXT primary_cert;
+ BOOL ok = CertAddCertificateContextToStore(store.get(), os_cert_handle(),
+ CERT_STORE_ADD_ALWAYS,
+ &primary_cert);
+ if (!ok || !primary_cert)
+ return NULL;
+
+ for (size_t i = 0; i < intermediate_ca_certs_.size(); ++i) {
+ CertAddCertificateContextToStore(store.get(), intermediate_ca_certs_[i],
+ CERT_STORE_ADD_ALWAYS, NULL);
+ }
+
+ // Note: |store| is explicitly not released, as the call to CertCloseStore()
+ // when |store| goes out of scope will not actually free the store. Instead,
+ // the store will be freed when |primary_cert| is freed.
+ return primary_cert;
+}
+
+// static
+bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle,
+ std::string* encoded) {
+ if (!cert_handle->pbCertEncoded || !cert_handle->cbCertEncoded)
+ return false;
+ encoded->assign(reinterpret_cast<char*>(cert_handle->pbCertEncoded),
+ cert_handle->cbCertEncoded);
+ return true;
+}
+
+// static
+bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a,
+ X509Certificate::OSCertHandle b) {
+ DCHECK(a && b);
+ if (a == b)
+ return true;
+ return a->cbCertEncoded == b->cbCertEncoded &&
+ memcmp(a->pbCertEncoded, b->pbCertEncoded, a->cbCertEncoded) == 0;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
+ const char* data, int length) {
+ OSCertHandle cert_handle = NULL;
+ if (!CertAddEncodedCertificateToStore(
+ NULL, X509_ASN_ENCODING, reinterpret_cast<const BYTE*>(data),
+ length, CERT_STORE_ADD_USE_EXISTING, &cert_handle))
+ return NULL;
+
+ return cert_handle;
+}
+
+X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes(
+ const char* data, int length, Format format) {
+ OSCertHandles results;
+ switch (format) {
+ case FORMAT_SINGLE_CERTIFICATE: {
+ OSCertHandle handle = CreateOSCertHandleFromBytes(data, length);
+ if (handle != NULL)
+ results.push_back(handle);
+ break;
+ }
+ case FORMAT_PKCS7:
+ results = ParsePKCS7(data, length);
+ break;
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
+}
+
+// static
+X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
+ OSCertHandle cert_handle) {
+ return CertDuplicateCertificateContext(cert_handle);
+}
+
+// static
+void X509Certificate::FreeOSCertHandle(OSCertHandle cert_handle) {
+ CertFreeCertificateContext(cert_handle);
+}
+
+// static
+SHA1HashValue X509Certificate::CalculateFingerprint(
+ OSCertHandle cert) {
+ DCHECK(NULL != cert->pbCertEncoded);
+ DCHECK_NE(static_cast<DWORD>(0), cert->cbCertEncoded);
+
+ BOOL rv;
+ SHA1HashValue sha1;
+ DWORD sha1_size = sizeof(sha1.data);
+ rv = CryptHashCertificate(NULL, CALG_SHA1, 0, cert->pbCertEncoded,
+ cert->cbCertEncoded, sha1.data, &sha1_size);
+ DCHECK(rv && sha1_size == sizeof(sha1.data));
+ if (!rv)
+ memset(sha1.data, 0, sizeof(sha1.data));
+ return sha1;
+}
+
+// TODO(wtc): This function is implemented with NSS low-level hash
+// functions to ensure it is fast. Reimplement this function with
+// CryptoAPI. May need to cache the HCRYPTPROV to reduce the overhead.
+// static
+SHA1HashValue X509Certificate::CalculateCAFingerprint(
+ const OSCertHandles& intermediates) {
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+
+ SHA1Context* sha1_ctx = SHA1_NewContext();
+ if (!sha1_ctx)
+ return sha1;
+ SHA1_Begin(sha1_ctx);
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ PCCERT_CONTEXT ca_cert = intermediates[i];
+ SHA1_Update(sha1_ctx, ca_cert->pbCertEncoded, ca_cert->cbCertEncoded);
+ }
+ unsigned int result_len;
+ SHA1_End(sha1_ctx, sha1.data, &result_len, SHA1_LENGTH);
+ SHA1_DestroyContext(sha1_ctx, PR_TRUE);
+
+ return sha1;
+}
+
+// static
+X509Certificate::OSCertHandle
+X509Certificate::ReadOSCertHandleFromPickle(PickleIterator* pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle_iter->ReadData(&data, &length))
+ return NULL;
+
+ // Legacy serialized certificates were serialized with extended attributes,
+ // rather than as DER only. As a result, these serialized certificates are
+ // not portable across platforms and may have side-effects on Windows due
+ // to extended attributes being serialized/deserialized -
+ // http://crbug.com/118706. To avoid deserializing these attributes, write
+ // the deserialized cert into a temporary cert store and then create a new
+ // cert from the DER - that is, without attributes.
+ ScopedHCERTSTORE store(
+ CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL));
+ if (!store.get())
+ return NULL;
+
+ OSCertHandle cert_handle = NULL;
+ if (!CertAddSerializedElementToStore(
+ store.get(), reinterpret_cast<const BYTE*>(data), length,
+ CERT_STORE_ADD_NEW, 0, CERT_STORE_CERTIFICATE_CONTEXT_FLAG,
+ NULL, reinterpret_cast<const void **>(&cert_handle))) {
+ return NULL;
+ }
+
+ std::string encoded;
+ bool ok = GetDEREncoded(cert_handle, &encoded);
+ FreeOSCertHandle(cert_handle);
+ cert_handle = NULL;
+
+ if (ok)
+ cert_handle = CreateOSCertHandleFromBytes(encoded.data(), encoded.size());
+ return cert_handle;
+}
+
+// static
+bool X509Certificate::WriteOSCertHandleToPickle(OSCertHandle cert_handle,
+ Pickle* pickle) {
+ return pickle->WriteData(
+ reinterpret_cast<char*>(cert_handle->pbCertEncoded),
+ cert_handle->cbCertEncoded);
+}
+
+// static
+void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle,
+ size_t* size_bits,
+ PublicKeyType* type) {
+ *type = kPublicKeyTypeUnknown;
+ *size_bits = 0;
+
+ PCCRYPT_OID_INFO oid_info = CryptFindOIDInfo(
+ CRYPT_OID_INFO_OID_KEY,
+ cert_handle->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId,
+ CRYPT_PUBKEY_ALG_OID_GROUP_ID);
+ if (!oid_info)
+ return;
+
+ CHECK_EQ(oid_info->dwGroupId,
+ static_cast<DWORD>(CRYPT_PUBKEY_ALG_OID_GROUP_ID));
+
+ *size_bits = CertGetPublicKeyLength(
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ &cert_handle->pCertInfo->SubjectPublicKeyInfo);
+
+ if (IS_SPECIAL_OID_INFO_ALGID(oid_info->Algid)) {
+ // For an EC public key, oid_info->Algid is CALG_OID_INFO_PARAMETERS
+ // (0xFFFFFFFE). Need to handle it as a special case.
+ if (strcmp(oid_info->pszOID, szOID_ECC_PUBLIC_KEY) == 0) {
+ *type = kPublicKeyTypeECDSA;
+ } else {
+ NOTREACHED();
+ }
+ return;
+ }
+ switch (oid_info->Algid) {
+ case CALG_RSA_SIGN:
+ case CALG_RSA_KEYX:
+ *type = kPublicKeyTypeRSA;
+ break;
+ case CALG_DSS_SIGN:
+ *type = kPublicKeyTypeDSA;
+ break;
+ case CALG_ECDSA:
+ *type = kPublicKeyTypeECDSA;
+ break;
+ case CALG_ECDH:
+ *type = kPublicKeyTypeECDH;
+ break;
+ }
+}
+
+bool X509Certificate::IsIssuedByEncoded(
+ const std::vector<std::string>& valid_issuers) {
+
+ // If the certificate's issuer in the list?
+ if (IsCertNameBlobInIssuerList(&cert_handle_->pCertInfo->Issuer,
+ valid_issuers)) {
+ return true;
+ }
+ // Otherwise, is any of the intermediate CA subjects in the list?
+ for (OSCertHandles::iterator it = intermediate_ca_certs_.begin();
+ it != intermediate_ca_certs_.end(); ++it) {
+ if (IsCertNameBlobInIssuerList(&(*it)->pCertInfo->Issuer,
+ valid_issuers)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util.cc b/chromium/net/cert/x509_util.cc
new file mode 100644
index 00000000000..8beb5572b87
--- /dev/null
+++ b/chromium/net/cert/x509_util.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util.h"
+
+#include "base/time/time.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace x509_util {
+
+ClientCertSorter::ClientCertSorter() : now_(base::Time::Now()) {}
+
+bool ClientCertSorter::operator()(
+ const scoped_refptr<X509Certificate>& a,
+ const scoped_refptr<X509Certificate>& b) const {
+ // Certificates that are null are sorted last.
+ if (!a.get() || !b.get())
+ return a.get() && !b.get();
+
+ // Certificates that are expired/not-yet-valid are sorted last.
+ bool a_is_valid = now_ >= a->valid_start() && now_ <= a->valid_expiry();
+ bool b_is_valid = now_ >= b->valid_start() && now_ <= b->valid_expiry();
+ if (a_is_valid != b_is_valid)
+ return a_is_valid && !b_is_valid;
+
+ // Certificates with longer expirations appear as higher priority (less
+ // than) certificates with shorter expirations.
+ if (a->valid_expiry() != b->valid_expiry())
+ return a->valid_expiry() > b->valid_expiry();
+
+ // If the expiration dates are equivalent, certificates that were issued
+ // more recently should be prioritized over older certificates.
+ if (a->valid_start() != b->valid_start())
+ return a->valid_start() > b->valid_start();
+
+ // Otherwise, prefer client certificates with shorter chains.
+ const X509Certificate::OSCertHandles& a_intermediates =
+ a->GetIntermediateCertificates();
+ const X509Certificate::OSCertHandles& b_intermediates =
+ b->GetIntermediateCertificates();
+ return a_intermediates.size() < b_intermediates.size();
+}
+
+} // namespace x509_util
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util.h b/chromium/net/cert/x509_util.h
new file mode 100644
index 00000000000..8a6bae2c95f
--- /dev/null
+++ b/chromium/net/cert/x509_util.h
@@ -0,0 +1,99 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_X509_UTIL_H_
+#define NET_CERT_X509_UTIL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace crypto {
+class ECPrivateKey;
+class RSAPrivateKey;
+}
+
+namespace net {
+
+class X509Certificate;
+
+namespace x509_util {
+
+// Returns true if the times can be used to create an X.509 certificate.
+// Certificates can accept dates from Jan 1st, 1 to Dec 31, 9999. A bug in NSS
+// limited the range to 1950-9999
+// (https://bugzilla.mozilla.org/show_bug.cgi?id=786531). This function will
+// return whether it is supported by the currently used crypto library.
+NET_EXPORT_PRIVATE bool IsSupportedValidityRange(base::Time not_valid_before,
+ base::Time not_valid_after);
+
+// Creates a server bound certificate containing the public key in |key|.
+// Domain, serial number and validity period are given as
+// parameters. The certificate is signed by the private key in |key|.
+// The hashing algorithm for the signature is SHA-1.
+//
+// See Internet Draft draft-balfanz-tls-obc-00 for more details:
+// http://tools.ietf.org/html/draft-balfanz-tls-obc-00
+NET_EXPORT_PRIVATE bool CreateDomainBoundCertEC(
+ crypto::ECPrivateKey* key,
+ const std::string& domain,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_cert);
+
+// Create a self-signed certificate containing the public key in |key|.
+// Subject, serial number and validity period are given as parameters.
+// The certificate is signed by the private key in |key|. The hashing
+// algorithm for the signature is SHA-1.
+//
+// |subject| is a distinguished name defined in RFC4514.
+//
+// An example:
+// CN=Michael Wong,O=FooBar Corporation,DC=foobar,DC=com
+//
+// SECURITY WARNING
+//
+// Using self-signed certificates has the following security risks:
+// 1. Encryption without authentication and thus vulnerable to
+// man-in-the-middle attacks.
+// 2. Self-signed certificates cannot be revoked.
+//
+// Use this certificate only after the above risks are acknowledged.
+NET_EXPORT bool CreateSelfSignedCert(crypto::RSAPrivateKey* key,
+ const std::string& subject,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_cert);
+
+// Comparator for use in STL algorithms that will sort client certificates by
+// order of preference.
+// Returns true if |a| is more preferable than |b|, allowing it to be used
+// with any algorithm that compares according to strict weak ordering.
+//
+// Criteria include:
+// - Prefer certificates that have a longer validity period (later
+// expiration dates)
+// - If equal, prefer certificates that were issued more recently
+// - If equal, prefer shorter chains (if available)
+class NET_EXPORT_PRIVATE ClientCertSorter {
+ public:
+ ClientCertSorter();
+
+ bool operator()(
+ const scoped_refptr<X509Certificate>& a,
+ const scoped_refptr<X509Certificate>& b) const;
+
+ private:
+ base::Time now_;
+};
+
+} // namespace x509_util
+
+} // namespace net
+
+#endif // NET_CERT_X509_UTIL_H_
diff --git a/chromium/net/cert/x509_util_ios.cc b/chromium/net/cert/x509_util_ios.cc
new file mode 100644
index 00000000000..736c26e0438
--- /dev/null
+++ b/chromium/net/cert/x509_util_ios.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util_ios.h"
+
+#include <cert.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <nss.h>
+#include <prtypes.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "crypto/nss_util.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util_nss.h"
+
+using base::ScopedCFTypeRef;
+
+namespace net {
+namespace x509_util_ios {
+
+namespace {
+
+// Creates an NSS certificate handle from |data|, which is |length| bytes in
+// size.
+CERTCertificate* CreateNSSCertHandleFromBytes(const char* data,
+ int length) {
+ if (length < 0)
+ return NULL;
+
+ crypto::EnsureNSSInit();
+
+ if (!NSS_IsInitialized())
+ return NULL;
+
+ SECItem der_cert;
+ der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
+ der_cert.len = length;
+ der_cert.type = siDERCertBuffer;
+
+ // Parse into a certificate structure.
+ return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL,
+ PR_FALSE, PR_TRUE);
+}
+
+} // namespace
+
+CERTCertificate* CreateNSSCertHandleFromOSHandle(
+ SecCertificateRef cert_handle) {
+ ScopedCFTypeRef<CFDataRef> cert_data(SecCertificateCopyData(cert_handle));
+ return CreateNSSCertHandleFromBytes(
+ reinterpret_cast<const char*>(CFDataGetBytePtr(cert_data)),
+ CFDataGetLength(cert_data));
+}
+
+SecCertificateRef CreateOSCertHandleFromNSSHandle(
+ CERTCertificate* nss_cert_handle) {
+ return X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<const char*>(nss_cert_handle->derCert.data),
+ nss_cert_handle->derCert.len);
+}
+
+X509Certificate* CreateCertFromNSSHandles(
+ CERTCertificate* cert_handle,
+ const std::vector<CERTCertificate*>& intermediates) {
+ ScopedCFTypeRef<SecCertificateRef> os_server_cert(
+ CreateOSCertHandleFromNSSHandle(cert_handle));
+ if (!os_server_cert)
+ return NULL;
+ std::vector<SecCertificateRef> os_intermediates;
+ for (size_t i = 0; i < intermediates.size(); ++i) {
+ SecCertificateRef intermediate =
+ CreateOSCertHandleFromNSSHandle(intermediates[i]);
+ if (!intermediate)
+ break;
+ os_intermediates.push_back(intermediate);
+ }
+
+ X509Certificate* cert = NULL;
+ if (intermediates.size() == os_intermediates.size()) {
+ cert = X509Certificate::CreateFromHandle(os_server_cert,
+ os_intermediates);
+ }
+
+ for (size_t i = 0; i < os_intermediates.size(); ++i)
+ CFRelease(os_intermediates[i]);
+ return cert;
+}
+
+SHA1HashValue CalculateFingerprintNSS(CERTCertificate* cert) {
+ DCHECK(cert->derCert.data);
+ DCHECK_NE(0U, cert->derCert.len);
+ SHA1HashValue sha1;
+ memset(sha1.data, 0, sizeof(sha1.data));
+ CC_SHA1(cert->derCert.data, cert->derCert.len, sha1.data);
+ return sha1;
+}
+
+// NSSCertificate implementation.
+
+NSSCertificate::NSSCertificate(SecCertificateRef cert_handle) {
+ nss_cert_handle_ = CreateNSSCertHandleFromOSHandle(cert_handle);
+ DLOG_IF(INFO, cert_handle && !nss_cert_handle_)
+ << "Could not convert SecCertificateRef to CERTCertificate*";
+}
+
+NSSCertificate::~NSSCertificate() {
+ CERT_DestroyCertificate(nss_cert_handle_);
+}
+
+CERTCertificate* NSSCertificate::cert_handle() const {
+ return nss_cert_handle_;
+}
+
+// NSSCertChain implementation
+
+NSSCertChain::NSSCertChain(X509Certificate* certificate) {
+ DCHECK(certificate);
+ certs_.push_back(CreateNSSCertHandleFromOSHandle(
+ certificate->os_cert_handle()));
+ const X509Certificate::OSCertHandles& cert_intermediates =
+ certificate->GetIntermediateCertificates();
+ for (size_t i = 0; i < cert_intermediates.size(); ++i)
+ certs_.push_back(CreateNSSCertHandleFromOSHandle(cert_intermediates[i]));
+}
+
+NSSCertChain::~NSSCertChain() {
+ for (size_t i = 0; i < certs_.size(); ++i)
+ CERT_DestroyCertificate(certs_[i]);
+}
+
+CERTCertificate* NSSCertChain::cert_handle() const {
+ return certs_.empty() ? NULL : certs_.front();
+}
+
+const std::vector<CERTCertificate*>& NSSCertChain::cert_chain() const {
+ return certs_;
+}
+
+} // namespace x509_util_ios
+} // namespace net
diff --git a/chromium/net/cert/x509_util_ios.h b/chromium/net/cert/x509_util_ios.h
new file mode 100644
index 00000000000..5a8a57601ca
--- /dev/null
+++ b/chromium/net/cert/x509_util_ios.h
@@ -0,0 +1,72 @@
+// Copyright (c) 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.
+
+// This file contains functions for iOS to glue NSS and Security.framework
+// together.
+
+#ifndef NET_CERT_X509_UTIL_IOS_H_
+#define NET_CERT_X509_UTIL_IOS_H_
+
+#include <Security/Security.h>
+#include <vector>
+
+#include "net/cert/x509_cert_types.h"
+
+// Forward declaration; real one in <cert.h>
+typedef struct CERTCertificateStr CERTCertificate;
+
+namespace net {
+
+class X509Certificate;
+
+namespace x509_util_ios {
+
+// Converts a Security.framework certificate handle (SecCertificateRef) into
+// an NSS certificate handle (CERTCertificate*).
+CERTCertificate* CreateNSSCertHandleFromOSHandle(SecCertificateRef cert_handle);
+
+// Converts an NSS certificate handle (CERTCertificate*) into a
+// Security.framework handle (SecCertificateRef)
+SecCertificateRef CreateOSCertHandleFromNSSHandle(
+ CERTCertificate* nss_cert_handle);
+
+// Create a new X509Certificate from the specified NSS server cert and
+// intermediates. This is functionally equivalent to
+// X509Certificate::CreateFromHandle(), except it supports receiving
+// NSS CERTCertificate*s rather than iOS SecCertificateRefs.
+X509Certificate* CreateCertFromNSSHandles(
+ CERTCertificate* cert_handle,
+ const std::vector<CERTCertificate*>& intermediates);
+
+SHA1HashValue CalculateFingerprintNSS(CERTCertificate* cert);
+
+// This is a wrapper class around the native NSS certificate handle.
+// The constructor copies the certificate data from |cert_handle| and
+// uses the NSS library to parse it.
+class NSSCertificate {
+ public:
+ explicit NSSCertificate(SecCertificateRef cert_handle);
+ ~NSSCertificate();
+ CERTCertificate* cert_handle() const;
+ private:
+ CERTCertificate* nss_cert_handle_;
+};
+
+// A wrapper class that loads a certificate and all of its intermediates into
+// NSS. This is necessary for libpkix path building to be able to locate
+// needed intermediates.
+class NSSCertChain {
+ public:
+ explicit NSSCertChain(X509Certificate* certificate);
+ ~NSSCertChain();
+ CERTCertificate* cert_handle() const;
+ const std::vector<CERTCertificate*>& cert_chain() const;
+ private:
+ std::vector<CERTCertificate*> certs_;
+};
+
+} // namespace x509_util_ios
+} // namespace net
+
+#endif // NET_CERT_X509_UTIL_IOS_H_
diff --git a/chromium/net/cert/x509_util_mac.cc b/chromium/net/cert/x509_util_mac.cc
new file mode 100644
index 00000000000..c9aa37bc63f
--- /dev/null
+++ b/chromium/net/cert/x509_util_mac.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util_mac.h"
+
+#include "base/logging.h"
+#include "third_party/apple_apsl/cssmapplePriv.h"
+
+namespace net {
+
+namespace x509_util {
+
+namespace {
+
+// Creates a SecPolicyRef for the given OID, with optional value.
+OSStatus CreatePolicy(const CSSM_OID* policy_oid,
+ void* option_data,
+ size_t option_length,
+ SecPolicyRef* policy) {
+ SecPolicySearchRef search;
+ OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_oid, NULL,
+ &search);
+ if (err)
+ return err;
+ err = SecPolicySearchCopyNext(search, policy);
+ CFRelease(search);
+ if (err)
+ return err;
+
+ if (option_data) {
+ CSSM_DATA options_data = {
+ option_length,
+ reinterpret_cast<uint8_t*>(option_data)
+ };
+ err = SecPolicySetValue(*policy, &options_data);
+ if (err) {
+ CFRelease(*policy);
+ return err;
+ }
+ }
+ return noErr;
+}
+
+} // namespace
+
+
+OSStatus CreateSSLClientPolicy(SecPolicyRef* policy) {
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options;
+ memset(&tp_ssl_options, 0, sizeof(tp_ssl_options));
+ tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION;
+ tp_ssl_options.Flags |= CSSM_APPLE_TP_SSL_CLIENT;
+
+ return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options,
+ sizeof(tp_ssl_options), policy);
+}
+
+OSStatus CreateSSLServerPolicy(const std::string& hostname,
+ SecPolicyRef* policy) {
+ CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options;
+ memset(&tp_ssl_options, 0, sizeof(tp_ssl_options));
+ tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION;
+ if (!hostname.empty()) {
+ tp_ssl_options.ServerName = hostname.data();
+ tp_ssl_options.ServerNameLen = hostname.size();
+ }
+
+ return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options,
+ sizeof(tp_ssl_options), policy);
+}
+
+OSStatus CreateBasicX509Policy(SecPolicyRef* policy) {
+ return CreatePolicy(&CSSMOID_APPLE_X509_BASIC, NULL, 0, policy);
+}
+
+OSStatus CreateRevocationPolicies(bool enable_revocation_checking,
+ bool enable_ev_checking,
+ CFMutableArrayRef policies) {
+ OSStatus status = noErr;
+
+ // In order to bypass the system revocation checking settings, the
+ // SecTrustRef must have at least one revocation policy associated with it.
+ // Since it is not known prior to verification whether the Apple TP will
+ // consider a certificate as an EV candidate, the default policy used is a
+ // CRL policy, since it does not communicate over the network.
+ // If the TP believes the leaf is an EV cert, it will explicitly add an
+ // OCSP policy to perform the online checking, and if it doesn't believe
+ // that the leaf is EV, then the default CRL policy will effectively no-op.
+ // This behaviour is used to implement EV-only revocation checking.
+ if (enable_ev_checking || enable_revocation_checking) {
+ CSSM_APPLE_TP_CRL_OPTIONS tp_crl_options;
+ memset(&tp_crl_options, 0, sizeof(tp_crl_options));
+ tp_crl_options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
+ // Only allow network CRL fetches if the caller explicitly requests
+ // online revocation checking. Note that, as of OS X 10.7.2, the system
+ // will set force this flag on according to system policies, so
+ // online revocation checks cannot be completely disabled.
+ if (enable_revocation_checking)
+ tp_crl_options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET;
+
+ SecPolicyRef crl_policy;
+ status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_CRL, &tp_crl_options,
+ sizeof(tp_crl_options), &crl_policy);
+ if (status)
+ return status;
+ CFArrayAppendValue(policies, crl_policy);
+ CFRelease(crl_policy);
+ }
+
+ // If revocation checking is explicitly enabled, then add an OCSP policy
+ // and allow network access. If both revocation checking and EV checking
+ // are disabled, then the added OCSP policy will be prevented from
+ // accessing the network. This is done because the TP will force an OCSP
+ // policy to be present when it believes the certificate is EV. If network
+ // fetching was not explicitly disabled, then it would be as if
+ // enable_ev_checking was always set to true.
+ if (enable_revocation_checking || !enable_ev_checking) {
+ CSSM_APPLE_TP_OCSP_OPTIONS tp_ocsp_options;
+ memset(&tp_ocsp_options, 0, sizeof(tp_ocsp_options));
+ tp_ocsp_options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
+
+ if (enable_revocation_checking) {
+ // The default for the OCSP policy is to fetch responses via the network,
+ // unlike the CRL policy default. The policy is further modified to
+ // prefer OCSP over CRLs, if both are specified on the certificate. This
+ // is because an OCSP response is both sufficient and typically
+ // significantly smaller than the CRL counterpart.
+ tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
+ } else {
+ // Effectively disable OCSP checking by making it impossible to get an
+ // OCSP response. Even if the Apple TP forces OCSP, no checking will
+ // be able to succeed. If this happens, the Apple TP will report an error
+ // that OCSP was unavailable, but this will be handled and suppressed in
+ // X509Certificate::Verify().
+ tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_DISABLE_NET |
+ CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE;
+ }
+
+ SecPolicyRef ocsp_policy;
+ status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_OCSP, &tp_ocsp_options,
+ sizeof(tp_ocsp_options), &ocsp_policy);
+ if (status)
+ return status;
+ CFArrayAppendValue(policies, ocsp_policy);
+ CFRelease(ocsp_policy);
+ }
+
+ return status;
+}
+
+CSSMFieldValue::CSSMFieldValue()
+ : cl_handle_(CSSM_INVALID_HANDLE),
+ oid_(NULL),
+ field_(NULL) {
+}
+CSSMFieldValue::CSSMFieldValue(CSSM_CL_HANDLE cl_handle,
+ const CSSM_OID* oid,
+ CSSM_DATA_PTR field)
+ : cl_handle_(cl_handle),
+ oid_(const_cast<CSSM_OID_PTR>(oid)),
+ field_(field) {
+}
+
+CSSMFieldValue::~CSSMFieldValue() {
+ Reset(CSSM_INVALID_HANDLE, NULL, NULL);
+}
+
+void CSSMFieldValue::Reset(CSSM_CL_HANDLE cl_handle,
+ CSSM_OID_PTR oid,
+ CSSM_DATA_PTR field) {
+ if (cl_handle_ && oid_ && field_)
+ CSSM_CL_FreeFieldValue(cl_handle_, oid_, field_);
+ cl_handle_ = cl_handle;
+ oid_ = oid;
+ field_ = field;
+}
+
+CSSMCachedCertificate::CSSMCachedCertificate()
+ : cl_handle_(CSSM_INVALID_HANDLE),
+ cached_cert_handle_(CSSM_INVALID_HANDLE) {
+}
+CSSMCachedCertificate::~CSSMCachedCertificate() {
+ if (cl_handle_ && cached_cert_handle_)
+ CSSM_CL_CertAbortCache(cl_handle_, cached_cert_handle_);
+}
+
+OSStatus CSSMCachedCertificate::Init(SecCertificateRef os_cert_handle) {
+ DCHECK(!cl_handle_ && !cached_cert_handle_);
+ DCHECK(os_cert_handle);
+ CSSM_DATA cert_data;
+ OSStatus status = SecCertificateGetData(os_cert_handle, &cert_data);
+ if (status)
+ return status;
+ status = SecCertificateGetCLHandle(os_cert_handle, &cl_handle_);
+ if (status) {
+ DCHECK(!cl_handle_);
+ return status;
+ }
+
+ status = CSSM_CL_CertCache(cl_handle_, &cert_data, &cached_cert_handle_);
+ if (status)
+ DCHECK(!cached_cert_handle_);
+ return status;
+}
+
+OSStatus CSSMCachedCertificate::GetField(const CSSM_OID* field_oid,
+ CSSMFieldValue* field) const {
+ DCHECK(cl_handle_);
+ DCHECK(cached_cert_handle_);
+
+ CSSM_OID_PTR oid = const_cast<CSSM_OID_PTR>(field_oid);
+ CSSM_DATA_PTR field_ptr = NULL;
+ CSSM_HANDLE results_handle = CSSM_INVALID_HANDLE;
+ uint32 field_value_count = 0;
+ CSSM_RETURN status = CSSM_CL_CertGetFirstCachedFieldValue(
+ cl_handle_, cached_cert_handle_, oid, &results_handle,
+ &field_value_count, &field_ptr);
+ if (status)
+ return status;
+
+ // Note: |field_value_count| may be > 1, indicating that more than one
+ // value is present. This may happen with extensions, but for current
+ // usages, only the first value is returned.
+ CSSM_CL_CertAbortQuery(cl_handle_, results_handle);
+ field->Reset(cl_handle_, oid, field_ptr);
+ return CSSM_OK;
+}
+
+} // namespace x509_util
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util_mac.h b/chromium/net/cert/x509_util_mac.h
new file mode 100644
index 00000000000..caf7a281ce9
--- /dev/null
+++ b/chromium/net/cert/x509_util_mac.h
@@ -0,0 +1,139 @@
+// Copyright (c) 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.
+
+#ifndef NET_CERT_X509_UTIL_MAC_H_
+#define NET_CERT_X509_UTIL_MAC_H_
+
+#include <CoreFoundation/CFArray.h>
+#include <Security/Security.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+namespace x509_util {
+
+// Creates a security policy for certificates used as client certificates
+// in SSL.
+// If a policy is successfully created, it will be stored in
+// |*policy| and ownership transferred to the caller.
+OSStatus NET_EXPORT CreateSSLClientPolicy(SecPolicyRef* policy);
+
+// Create an SSL server policy. While certificate name validation will be
+// performed by SecTrustEvaluate(), it has the following limitations:
+// - Doesn't support IP addresses in dotted-quad literals (127.0.0.1)
+// - Doesn't support IPv6 addresses
+// - Doesn't support the iPAddress subjectAltName
+// Providing the hostname is necessary in order to locate certain user or
+// system trust preferences, such as those created by Safari. Preferences
+// created by Keychain Access do not share this requirement.
+// On success, stores the resultant policy in |*policy| and returns noErr.
+OSStatus NET_EXPORT CreateSSLServerPolicy(const std::string& hostname,
+ SecPolicyRef* policy);
+
+// Creates a security policy for basic X.509 validation. If the policy is
+// successfully created, it will be stored in |*policy| and ownership
+// transferred to the caller.
+OSStatus NET_EXPORT CreateBasicX509Policy(SecPolicyRef* policy);
+
+// Creates security policies to control revocation checking (OCSP and CRL).
+// If |enable_revocation_checking| is true, revocation checking will be
+// explicitly enabled.
+// If |enable_revocation_checking| is false, but |enable_ev_checking| is
+// true, then the system policies for EV checking (which include checking
+// for an online OCSP response) will be permitted. However, if the OS
+// does not believe the certificate is EV, no revocation checking will be
+// performed.
+// If both are false, then the policies returned will be explicitly
+// prohibited from accessing the network or the local cache, regardless of
+// system settings.
+// If the policies are successfully created, they will be appended to
+// |policies|.
+OSStatus NET_EXPORT CreateRevocationPolicies(bool enable_revocation_checking,
+ bool enable_ev_checking,
+ CFMutableArrayRef policies);
+
+// Wrapper for a CSSM_DATA_PTR that was obtained via one of the CSSM field
+// accessors (such as CSSM_CL_CertGet[First/Next]Value or
+// CSSM_CL_CertGet[First/Next]CachedValue).
+class CSSMFieldValue {
+ public:
+ CSSMFieldValue();
+ CSSMFieldValue(CSSM_CL_HANDLE cl_handle,
+ const CSSM_OID* oid,
+ CSSM_DATA_PTR field);
+ ~CSSMFieldValue();
+
+ CSSM_OID_PTR oid() const { return oid_; }
+ CSSM_DATA_PTR field() const { return field_; }
+
+ // Returns the field as if it was an arbitrary type - most commonly, by
+ // interpreting the field as a specific CSSM/CDSA parsed type, such as
+ // CSSM_X509_SUBJECT_PUBLIC_KEY_INFO or CSSM_X509_ALGORITHM_IDENTIFIER.
+ // An added check is applied to ensure that the current field is large
+ // enough to actually contain the requested type.
+ template <typename T> const T* GetAs() const {
+ if (!field_ || field_->Length < sizeof(T))
+ return NULL;
+ return reinterpret_cast<const T*>(field_->Data);
+ }
+
+ void Reset(CSSM_CL_HANDLE cl_handle,
+ CSSM_OID_PTR oid,
+ CSSM_DATA_PTR field);
+
+ private:
+ CSSM_CL_HANDLE cl_handle_;
+ CSSM_OID_PTR oid_;
+ CSSM_DATA_PTR field_;
+
+ DISALLOW_COPY_AND_ASSIGN(CSSMFieldValue);
+};
+
+// CSSMCachedCertificate is a container class that is used to wrap the
+// CSSM_CL_CertCache APIs and provide safe and efficient access to
+// certificate fields in their CSSM form.
+//
+// To provide efficient access to certificate/CRL fields, CSSM provides an
+// API/SPI to "cache" a certificate/CRL. The exact meaning of a cached
+// certificate is not defined by CSSM, but is documented to generally be some
+// intermediate or parsed form of the certificate. In the case of Apple's
+// CSSM CL implementation, the intermediate form is the parsed certificate
+// stored in an internal format (which happens to be NSS). By caching the
+// certificate, callers that wish to access multiple fields (such as subject,
+// issuer, and validity dates) do not need to repeatedly parse the entire
+// certificate, nor are they forced to convert all fields from their NSS types
+// to their CSSM equivalents. This latter point is especially helpful when
+// running on OS X 10.5, as it will fail to convert some fields that reference
+// unsupported algorithms, such as ECC.
+class CSSMCachedCertificate {
+ public:
+ CSSMCachedCertificate();
+ ~CSSMCachedCertificate();
+
+ // Initializes the CSSMCachedCertificate by caching the specified
+ // |os_cert_handle|. On success, returns noErr.
+ // Note: Once initialized, the cached certificate should only be accessed
+ // from a single thread.
+ OSStatus Init(SecCertificateRef os_cert_handle);
+
+ // Fetches the first value for the field associated with |field_oid|.
+ // If |field_oid| is a valid OID and is present in the current certificate,
+ // returns CSSM_OK and stores the first value in |field|. If additional
+ // values are associated with |field_oid|, they are ignored.
+ OSStatus GetField(const CSSM_OID* field_oid, CSSMFieldValue* field) const;
+
+ private:
+ CSSM_CL_HANDLE cl_handle_;
+ CSSM_HANDLE cached_cert_handle_;
+};
+
+} // namespace x509_util
+
+} // namespace net
+
+#endif // NET_CERT_X509_UTIL_MAC_H_
diff --git a/chromium/net/cert/x509_util_nss.cc b/chromium/net/cert/x509_util_nss.cc
new file mode 100644
index 00000000000..f8fbd6feda9
--- /dev/null
+++ b/chromium/net/cert/x509_util_nss.cc
@@ -0,0 +1,624 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_nss.h"
+
+#include <cert.h> // Must be included before certdb.h
+#include <certdb.h>
+#include <cryptohi.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <prerror.h>
+#include <secder.h>
+#include <secmod.h>
+#include <secport.h>
+
+#include "base/debug/leak_annotations.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/pickle.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/scoped_nss_types.h"
+#include "crypto/third_party/nss/chromium-nss.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+class DomainBoundCertOIDWrapper {
+ public:
+ static DomainBoundCertOIDWrapper* GetInstance() {
+ // Instantiated as a leaky singleton to allow the singleton to be
+ // constructed on a worker thead that is not joined when a process
+ // shuts down.
+ return Singleton<DomainBoundCertOIDWrapper,
+ LeakySingletonTraits<DomainBoundCertOIDWrapper> >::get();
+ }
+
+ SECOidTag domain_bound_cert_oid_tag() const {
+ return domain_bound_cert_oid_tag_;
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<DomainBoundCertOIDWrapper>;
+
+ DomainBoundCertOIDWrapper();
+
+ SECOidTag domain_bound_cert_oid_tag_;
+
+ DISALLOW_COPY_AND_ASSIGN(DomainBoundCertOIDWrapper);
+};
+
+DomainBoundCertOIDWrapper::DomainBoundCertOIDWrapper()
+ : domain_bound_cert_oid_tag_(SEC_OID_UNKNOWN) {
+ // 1.3.6.1.4.1.11129.2.1.6
+ // (iso.org.dod.internet.private.enterprises.google.googleSecurity.
+ // certificateExtensions.originBoundCertificate)
+ static const uint8 kObCertOID[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x06
+ };
+ SECOidData oid_data;
+ memset(&oid_data, 0, sizeof(oid_data));
+ oid_data.oid.data = const_cast<uint8*>(kObCertOID);
+ oid_data.oid.len = sizeof(kObCertOID);
+ oid_data.offset = SEC_OID_UNKNOWN;
+ oid_data.desc = "Origin Bound Certificate";
+ oid_data.mechanism = CKM_INVALID_MECHANISM;
+ oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION;
+ domain_bound_cert_oid_tag_ = SECOID_AddEntry(&oid_data);
+ if (domain_bound_cert_oid_tag_ == SEC_OID_UNKNOWN)
+ LOG(ERROR) << "OB_CERT OID tag creation failed";
+}
+
+// Creates a Certificate object that may be passed to the SignCertificate
+// method to generate an X509 certificate.
+// Returns NULL if an error is encountered in the certificate creation
+// process.
+// Caller responsible for freeing returned certificate object.
+CERTCertificate* CreateCertificate(
+ SECKEYPublicKey* public_key,
+ const std::string& subject,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after) {
+ // Create info about public key.
+ CERTSubjectPublicKeyInfo* spki =
+ SECKEY_CreateSubjectPublicKeyInfo(public_key);
+ if (!spki)
+ return NULL;
+
+ // Create the certificate request.
+ CERTName* subject_name =
+ CERT_AsciiToName(const_cast<char*>(subject.c_str()));
+ CERTCertificateRequest* cert_request =
+ CERT_CreateCertificateRequest(subject_name, spki, NULL);
+ SECKEY_DestroySubjectPublicKeyInfo(spki);
+
+ if (!cert_request) {
+ PRErrorCode prerr = PR_GetError();
+ LOG(ERROR) << "Failed to create certificate request: " << prerr;
+ CERT_DestroyName(subject_name);
+ return NULL;
+ }
+
+ CERTValidity* validity = CERT_CreateValidity(
+ crypto::BaseTimeToPRTime(not_valid_before),
+ crypto::BaseTimeToPRTime(not_valid_after));
+ if (!validity) {
+ PRErrorCode prerr = PR_GetError();
+ LOG(ERROR) << "Failed to create certificate validity object: " << prerr;
+ CERT_DestroyName(subject_name);
+ CERT_DestroyCertificateRequest(cert_request);
+ return NULL;
+ }
+ CERTCertificate* cert = CERT_CreateCertificate(serial_number, subject_name,
+ validity, cert_request);
+ if (!cert) {
+ PRErrorCode prerr = PR_GetError();
+ LOG(ERROR) << "Failed to create certificate: " << prerr;
+ }
+
+ // Cleanup for resources used to generate the cert.
+ CERT_DestroyName(subject_name);
+ CERT_DestroyValidity(validity);
+ CERT_DestroyCertificateRequest(cert_request);
+
+ return cert;
+}
+
+// Signs a certificate object, with |key| generating a new X509Certificate
+// and destroying the passed certificate object (even when NULL is returned).
+// The logic of this method references SignCert() in NSS utility certutil:
+// http://mxr.mozilla.org/security/ident?i=SignCert.
+// Returns true on success or false if an error is encountered in the
+// certificate signing process.
+bool SignCertificate(
+ CERTCertificate* cert,
+ SECKEYPrivateKey* key) {
+ // |arena| is used to encode the cert.
+ PLArenaPool* arena = cert->arena;
+ SECOidTag algo_id = SEC_GetSignatureAlgorithmOidTag(key->keyType,
+ SEC_OID_SHA1);
+ if (algo_id == SEC_OID_UNKNOWN)
+ return false;
+
+ SECStatus rv = SECOID_SetAlgorithmID(arena, &cert->signature, algo_id, 0);
+ if (rv != SECSuccess)
+ return false;
+
+ // Generate a cert of version 3.
+ *(cert->version.data) = 2;
+ cert->version.len = 1;
+
+ SECItem der = { siBuffer, NULL, 0 };
+
+ // Use ASN1 DER to encode the cert.
+ void* encode_result = SEC_ASN1EncodeItem(
+ NULL, &der, cert, SEC_ASN1_GET(CERT_CertificateTemplate));
+ if (!encode_result)
+ return false;
+
+ // Allocate space to contain the signed cert.
+ SECItem result = { siBuffer, NULL, 0 };
+
+ // Sign the ASN1 encoded cert and save it to |result|.
+ rv = DerSignData(arena, &result, &der, key, algo_id);
+ PORT_Free(der.data);
+ if (rv != SECSuccess) {
+ DLOG(ERROR) << "DerSignData: " << PORT_GetError();
+ return false;
+ }
+
+ // Save the signed result to the cert.
+ cert->derCert = result;
+
+ return true;
+}
+
+#if defined(USE_NSS) || defined(OS_IOS)
+// Callback for CERT_DecodeCertPackage(), used in
+// CreateOSCertHandlesFromBytes().
+SECStatus PR_CALLBACK CollectCertsCallback(void* arg,
+ SECItem** certs,
+ int num_certs) {
+ X509Certificate::OSCertHandles* results =
+ reinterpret_cast<X509Certificate::OSCertHandles*>(arg);
+
+ for (int i = 0; i < num_certs; ++i) {
+ X509Certificate::OSCertHandle handle =
+ X509Certificate::CreateOSCertHandleFromBytes(
+ reinterpret_cast<char*>(certs[i]->data), certs[i]->len);
+ if (handle)
+ results->push_back(handle);
+ }
+
+ return SECSuccess;
+}
+
+typedef scoped_ptr_malloc<
+ CERTName,
+ crypto::NSSDestroyer<CERTName, CERT_DestroyName> > ScopedCERTName;
+
+// Create a new CERTName object from its encoded representation.
+// |arena| is the allocation pool to use.
+// |data| points to a DER-encoded X.509 DistinguishedName.
+// Return a new CERTName pointer on success, or NULL.
+CERTName* CreateCertNameFromEncoded(PLArenaPool* arena,
+ const base::StringPiece& data) {
+ if (!arena)
+ return NULL;
+
+ ScopedCERTName name(PORT_ArenaZNew(arena, CERTName));
+ if (!name.get())
+ return NULL;
+
+ SECItem item;
+ item.len = static_cast<unsigned int>(data.length());
+ item.data = reinterpret_cast<unsigned char*>(
+ const_cast<char*>(data.data()));
+
+ SECStatus rv = SEC_ASN1DecodeItem(
+ arena, name.get(), SEC_ASN1_GET(CERT_NameTemplate), &item);
+ if (rv != SECSuccess)
+ return NULL;
+
+ return name.release();
+}
+
+#endif // defined(USE_NSS) || defined(OS_IOS)
+
+} // namespace
+
+namespace x509_util {
+
+bool CreateSelfSignedCert(crypto::RSAPrivateKey* key,
+ const std::string& subject,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_cert) {
+ DCHECK(key);
+ CERTCertificate* cert = CreateCertificate(key->public_key(),
+ subject,
+ serial_number,
+ not_valid_before,
+ not_valid_after);
+ if (!cert)
+ return false;
+
+ if (!SignCertificate(cert, key->key())) {
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ der_cert->assign(reinterpret_cast<char*>(cert->derCert.data),
+ cert->derCert.len);
+ CERT_DestroyCertificate(cert);
+ return true;
+}
+
+bool IsSupportedValidityRange(base::Time not_valid_before,
+ base::Time not_valid_after) {
+ CERTValidity* validity = CERT_CreateValidity(
+ crypto::BaseTimeToPRTime(not_valid_before),
+ crypto::BaseTimeToPRTime(not_valid_after));
+
+ if (!validity)
+ return false;
+
+ CERT_DestroyValidity(validity);
+ return true;
+}
+
+bool CreateDomainBoundCertEC(crypto::ECPrivateKey* key,
+ const std::string& domain,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_cert) {
+ DCHECK(key);
+
+ CERTCertificate* cert = CreateCertificate(key->public_key(),
+ "CN=anonymous.invalid",
+ serial_number,
+ not_valid_before,
+ not_valid_after);
+
+ if (!cert)
+ return false;
+
+ // Create opaque handle used to add extensions later.
+ void* cert_handle;
+ if ((cert_handle = CERT_StartCertExtensions(cert)) == NULL) {
+ LOG(ERROR) << "Unable to get opaque handle for adding extensions";
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ // Create SECItem for IA5String encoding.
+ SECItem domain_string_item = {
+ siAsciiString,
+ (unsigned char*)domain.data(),
+ static_cast<unsigned>(domain.size())
+ };
+
+ // IA5Encode and arena allocate SECItem
+ SECItem* asn1_domain_string = SEC_ASN1EncodeItem(
+ cert->arena, NULL, &domain_string_item,
+ SEC_ASN1_GET(SEC_IA5StringTemplate));
+ if (asn1_domain_string == NULL) {
+ LOG(ERROR) << "Unable to get ASN1 encoding for domain in domain_bound_cert"
+ " extension";
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ // Add the extension to the opaque handle
+ if (CERT_AddExtension(
+ cert_handle,
+ DomainBoundCertOIDWrapper::GetInstance()->domain_bound_cert_oid_tag(),
+ asn1_domain_string,
+ PR_TRUE,
+ PR_TRUE) != SECSuccess){
+ LOG(ERROR) << "Unable to add domain bound cert extension to opaque handle";
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ // Copy extension into x509 cert
+ if (CERT_FinishExtensions(cert_handle) != SECSuccess){
+ LOG(ERROR) << "Unable to copy extension to X509 cert";
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ if (!SignCertificate(cert, key->key())) {
+ CERT_DestroyCertificate(cert);
+ return false;
+ }
+
+ DCHECK(cert->derCert.len);
+ // XXX copied from X509Certificate::GetDEREncoded
+ der_cert->clear();
+ der_cert->append(reinterpret_cast<char*>(cert->derCert.data),
+ cert->derCert.len);
+ CERT_DestroyCertificate(cert);
+ return true;
+}
+
+#if defined(USE_NSS) || defined(OS_IOS)
+void ParsePrincipal(CERTName* name, CertPrincipal* principal) {
+// Starting in NSS 3.15, CERTGetNameFunc takes a const CERTName* argument.
+#if NSS_VMINOR >= 15
+ typedef char* (*CERTGetNameFunc)(const CERTName* name);
+#else
+ typedef char* (*CERTGetNameFunc)(CERTName* name);
+#endif
+
+ // TODO(jcampan): add business_category and serial_number.
+ // TODO(wtc): NSS has the CERT_GetOrgName, CERT_GetOrgUnitName, and
+ // CERT_GetDomainComponentName functions, but they return only the most
+ // general (the first) RDN. NSS doesn't have a function for the street
+ // address.
+ static const SECOidTag kOIDs[] = {
+ SEC_OID_AVA_STREET_ADDRESS,
+ SEC_OID_AVA_ORGANIZATION_NAME,
+ SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME,
+ SEC_OID_AVA_DC };
+
+ std::vector<std::string>* values[] = {
+ &principal->street_addresses,
+ &principal->organization_names,
+ &principal->organization_unit_names,
+ &principal->domain_components };
+ DCHECK_EQ(arraysize(kOIDs), arraysize(values));
+
+ CERTRDN** rdns = name->rdns;
+ for (size_t rdn = 0; rdns[rdn]; ++rdn) {
+ CERTAVA** avas = rdns[rdn]->avas;
+ for (size_t pair = 0; avas[pair] != 0; ++pair) {
+ SECOidTag tag = CERT_GetAVATag(avas[pair]);
+ for (size_t oid = 0; oid < arraysize(kOIDs); ++oid) {
+ if (kOIDs[oid] == tag) {
+ SECItem* decode_item = CERT_DecodeAVAValue(&avas[pair]->value);
+ if (!decode_item)
+ break;
+ // TODO(wtc): Pass decode_item to CERT_RFC1485_EscapeAndQuote.
+ std::string value(reinterpret_cast<char*>(decode_item->data),
+ decode_item->len);
+ values[oid]->push_back(value);
+ SECITEM_FreeItem(decode_item, PR_TRUE);
+ break;
+ }
+ }
+ }
+ }
+
+ // Get CN, L, S, and C.
+ CERTGetNameFunc get_name_funcs[4] = {
+ CERT_GetCommonName, CERT_GetLocalityName,
+ CERT_GetStateName, CERT_GetCountryName };
+ std::string* single_values[4] = {
+ &principal->common_name, &principal->locality_name,
+ &principal->state_or_province_name, &principal->country_name };
+ for (size_t i = 0; i < arraysize(get_name_funcs); ++i) {
+ char* value = get_name_funcs[i](name);
+ if (value) {
+ single_values[i]->assign(value);
+ PORT_Free(value);
+ }
+ }
+}
+
+void ParseDate(const SECItem* der_date, base::Time* result) {
+ PRTime prtime;
+ SECStatus rv = DER_DecodeTimeChoice(&prtime, der_date);
+ DCHECK_EQ(SECSuccess, rv);
+ *result = crypto::PRTimeToBaseTime(prtime);
+}
+
+std::string ParseSerialNumber(const CERTCertificate* certificate) {
+ return std::string(reinterpret_cast<char*>(certificate->serialNumber.data),
+ certificate->serialNumber.len);
+}
+
+void GetSubjectAltName(CERTCertificate* cert_handle,
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs) {
+ if (dns_names)
+ dns_names->clear();
+ if (ip_addrs)
+ ip_addrs->clear();
+
+ SECItem alt_name;
+ SECStatus rv = CERT_FindCertExtension(cert_handle,
+ SEC_OID_X509_SUBJECT_ALT_NAME,
+ &alt_name);
+ if (rv != SECSuccess)
+ return;
+
+ PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ DCHECK(arena != NULL);
+
+ CERTGeneralName* alt_name_list;
+ alt_name_list = CERT_DecodeAltNameExtension(arena, &alt_name);
+ SECITEM_FreeItem(&alt_name, PR_FALSE);
+
+ CERTGeneralName* name = alt_name_list;
+ while (name) {
+ // DNSName and IPAddress are encoded as IA5String and OCTET STRINGs
+ // respectively, both of which can be byte copied from
+ // SECItemType::data into the appropriate output vector.
+ if (dns_names && name->type == certDNSName) {
+ dns_names->push_back(std::string(
+ reinterpret_cast<char*>(name->name.other.data),
+ name->name.other.len));
+ } else if (ip_addrs && name->type == certIPAddress) {
+ ip_addrs->push_back(std::string(
+ reinterpret_cast<char*>(name->name.other.data),
+ name->name.other.len));
+ }
+ name = CERT_GetNextGeneralName(name);
+ if (name == alt_name_list)
+ break;
+ }
+ PORT_FreeArena(arena, PR_FALSE);
+}
+
+X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes(
+ const char* data,
+ int length,
+ X509Certificate::Format format) {
+ X509Certificate::OSCertHandles results;
+ if (length < 0)
+ return results;
+
+ crypto::EnsureNSSInit();
+
+ if (!NSS_IsInitialized())
+ return results;
+
+ switch (format) {
+ case X509Certificate::FORMAT_SINGLE_CERTIFICATE: {
+ X509Certificate::OSCertHandle handle =
+ X509Certificate::CreateOSCertHandleFromBytes(data, length);
+ if (handle)
+ results.push_back(handle);
+ break;
+ }
+ case X509Certificate::FORMAT_PKCS7: {
+ // Make a copy since CERT_DecodeCertPackage may modify it
+ std::vector<char> data_copy(data, data + length);
+
+ SECStatus result = CERT_DecodeCertPackage(&data_copy[0],
+ length, CollectCertsCallback, &results);
+ if (result != SECSuccess)
+ results.clear();
+ break;
+ }
+ default:
+ NOTREACHED() << "Certificate format " << format << " unimplemented";
+ break;
+ }
+
+ return results;
+}
+
+X509Certificate::OSCertHandle ReadOSCertHandleFromPickle(
+ PickleIterator* pickle_iter) {
+ const char* data;
+ int length;
+ if (!pickle_iter->ReadData(&data, &length))
+ return NULL;
+
+ return X509Certificate::CreateOSCertHandleFromBytes(data, length);
+}
+
+void GetPublicKeyInfo(CERTCertificate* handle,
+ size_t* size_bits,
+ X509Certificate::PublicKeyType* type) {
+ // Since we might fail, set the output parameters to default values first.
+ *type = X509Certificate::kPublicKeyTypeUnknown;
+ *size_bits = 0;
+
+ crypto::ScopedSECKEYPublicKey key(CERT_ExtractPublicKey(handle));
+ if (!key.get())
+ return;
+
+ *size_bits = SECKEY_PublicKeyStrengthInBits(key.get());
+
+ switch (key->keyType) {
+ case rsaKey:
+ *type = X509Certificate::kPublicKeyTypeRSA;
+ break;
+ case dsaKey:
+ *type = X509Certificate::kPublicKeyTypeDSA;
+ break;
+ case dhKey:
+ *type = X509Certificate::kPublicKeyTypeDH;
+ break;
+ case ecKey:
+ *type = X509Certificate::kPublicKeyTypeECDSA;
+ break;
+ default:
+ *type = X509Certificate::kPublicKeyTypeUnknown;
+ *size_bits = 0;
+ break;
+ }
+}
+
+bool GetIssuersFromEncodedList(
+ const std::vector<std::string>& encoded_issuers,
+ PLArenaPool* arena,
+ std::vector<CERTName*>* out) {
+ std::vector<CERTName*> result;
+ for (size_t n = 0; n < encoded_issuers.size(); ++n) {
+ CERTName* name = CreateCertNameFromEncoded(arena, encoded_issuers[n]);
+ if (name != NULL)
+ result.push_back(name);
+ }
+
+ if (result.size() == encoded_issuers.size()) {
+ out->swap(result);
+ return true;
+ }
+
+ for (size_t n = 0; n < result.size(); ++n)
+ CERT_DestroyName(result[n]);
+ return false;
+}
+
+
+bool IsCertificateIssuedBy(const std::vector<CERTCertificate*>& cert_chain,
+ const std::vector<CERTName*>& valid_issuers) {
+ for (size_t n = 0; n < cert_chain.size(); ++n) {
+ CERTName* cert_issuer = &cert_chain[n]->issuer;
+ for (size_t i = 0; i < valid_issuers.size(); ++i) {
+ if (CERT_CompareName(valid_issuers[i], cert_issuer) == SECEqual)
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string GetUniqueNicknameForSlot(const std::string& nickname,
+ const SECItem* subject,
+ PK11SlotInfo* slot) {
+ int index = 2;
+ std::string new_name = nickname;
+ std::string temp_nickname = new_name;
+ std::string token_name;
+
+ if (!slot)
+ return new_name;
+
+ if (!PK11_IsInternalKeySlot(slot)) {
+ token_name.assign(PK11_GetTokenName(slot));
+ token_name.append(":");
+
+ temp_nickname = token_name + new_name;
+ }
+
+ while (SEC_CertNicknameConflict(temp_nickname.c_str(),
+ const_cast<SECItem*>(subject),
+ CERT_GetDefaultCertDB())) {
+ base::SStringPrintf(&new_name, "%s #%d", nickname.c_str(), index++);
+ temp_nickname = token_name + new_name;
+ }
+
+ return new_name;
+}
+
+#endif // defined(USE_NSS) || defined(OS_IOS)
+
+} // namespace x509_util
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util_nss.h b/chromium/net/cert/x509_util_nss.h
new file mode 100644
index 00000000000..877dc4856d2
--- /dev/null
+++ b/chromium/net/cert/x509_util_nss.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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_CERT_X509_UTIL_NSS_H_
+#define NET_CERT_X509_UTIL_NSS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_certificate.h"
+
+class PickleIterator;
+
+typedef struct CERTCertificateStr CERTCertificate;
+typedef struct CERTNameStr CERTName;
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef struct PLArenaPool PLArenaPool;
+typedef struct SECItemStr SECItem;
+
+namespace net {
+
+namespace x509_util {
+
+#if defined(USE_NSS) || defined(OS_IOS)
+// Parses the Principal attribute from |name| and outputs the result in
+// |principal|.
+void ParsePrincipal(CERTName* name,
+ CertPrincipal* principal);
+
+// Parses the date from |der_date| and outputs the result in |result|.
+void ParseDate(const SECItem* der_date, base::Time* result);
+
+// Parses the serial number from |certificate|.
+std::string ParseSerialNumber(const CERTCertificate* certificate);
+
+// Gets the subjectAltName extension field from the certificate, if any.
+void GetSubjectAltName(CERTCertificate* cert_handle,
+ std::vector<std::string>* dns_names,
+ std::vector<std::string>* ip_addrs);
+
+// Creates all possible OS certificate handles from |data| encoded in a specific
+// |format|. Returns an empty collection on failure.
+X509Certificate::OSCertHandles CreateOSCertHandlesFromBytes(
+ const char* data,
+ int length,
+ X509Certificate::Format format);
+
+// Reads a single certificate from |pickle_iter| and returns a platform-specific
+// certificate handle. Returns an invalid handle, NULL, on failure.
+X509Certificate::OSCertHandle ReadOSCertHandleFromPickle(
+ PickleIterator* pickle_iter);
+
+// Sets |*size_bits| to be the length of the public key in bits, and sets
+// |*type| to one of the |PublicKeyType| values. In case of
+// |kPublicKeyTypeUnknown|, |*size_bits| will be set to 0.
+void GetPublicKeyInfo(CERTCertificate* handle,
+ size_t* size_bits,
+ X509Certificate::PublicKeyType* type);
+
+// Create a list of CERTName objects from a list of DER-encoded X.509
+// DistinguishedName items. All objects are created in a given arena.
+// |encoded_issuers| is the list of encoded DNs.
+// |arena| is the arena used for all allocations.
+// |out| will receive the result list on success.
+// Return true on success. On failure, the caller must free the
+// intermediate CERTName objects pushed to |out|.
+bool GetIssuersFromEncodedList(
+ const std::vector<std::string>& issuers,
+ PLArenaPool* arena,
+ std::vector<CERTName*>* out);
+
+// Returns true iff a certificate is issued by any of the issuers listed
+// by name in |valid_issuers|.
+// |cert_chain| is the certificate's chain.
+// |valid_issuers| is a list of strings, where each string contains
+// a DER-encoded X.509 Distinguished Name.
+bool IsCertificateIssuedBy(const std::vector<CERTCertificate*>& cert_chain,
+ const std::vector<CERTName*>& valid_issuers);
+
+// Generates a unique nickname for |slot|, returning |nickname| if it is
+// already unique.
+//
+// Note: The nickname returned will NOT include the token name, thus the
+// token name must be prepended if calling an NSS function that expects
+// <token>:<nickname>.
+// TODO(gspencer): Internationalize this: it's wrong to hard-code English.
+std::string GetUniqueNicknameForSlot(const std::string& nickname,
+ const SECItem* subject,
+ PK11SlotInfo* slot);
+#endif // defined(USE_NSS) || defined(OS_IOS)
+
+} // namespace x509_util
+
+} // namespace net
+
+#endif // NET_CERT_X509_UTIL_NSS_H_
diff --git a/chromium/net/cert/x509_util_nss_unittest.cc b/chromium/net/cert/x509_util_nss_unittest.cc
new file mode 100644
index 00000000000..968cc147ec5
--- /dev/null
+++ b/chromium/net/cert/x509_util_nss_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_nss.h"
+
+#include <cert.h>
+#include <secoid.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/scoped_nss_types.h"
+#include "crypto/signature_verifier.h"
+#include "net/cert/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+CERTCertificate* CreateNSSCertHandleFromBytes(const char* data, size_t length) {
+ SECItem der_cert;
+ der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
+ der_cert.len = length;
+ der_cert.type = siDERCertBuffer;
+
+ // Parse into a certificate structure.
+ return CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der_cert, NULL,
+ PR_FALSE, PR_TRUE);
+}
+
+#if !defined(OS_WIN) && !defined(OS_MACOSX)
+void VerifyCertificateSignature(const std::string& der_cert,
+ const std::vector<uint8>& der_spki) {
+ crypto::ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+
+ CERTSignedData sd;
+ memset(&sd, 0, sizeof(sd));
+
+ SECItem der_cert_item = {
+ siDERCertBuffer,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(der_cert.data())),
+ static_cast<unsigned int>(der_cert.size())
+ };
+ SECStatus rv = SEC_ASN1DecodeItem(arena.get(), &sd,
+ SEC_ASN1_GET(CERT_SignedDataTemplate),
+ &der_cert_item);
+ ASSERT_EQ(SECSuccess, rv);
+
+ // The CERTSignedData.signatureAlgorithm is decoded, but SignatureVerifier
+ // wants the DER encoded form, so re-encode it again.
+ SECItem* signature_algorithm = SEC_ASN1EncodeItem(
+ arena.get(),
+ NULL,
+ &sd.signatureAlgorithm,
+ SEC_ASN1_GET(SECOID_AlgorithmIDTemplate));
+ ASSERT_TRUE(signature_algorithm);
+
+ crypto::SignatureVerifier verifier;
+ bool ok = verifier.VerifyInit(
+ signature_algorithm->data,
+ signature_algorithm->len,
+ sd.signature.data,
+ sd.signature.len / 8, // Signature is a BIT STRING, convert to bytes.
+ &der_spki[0],
+ der_spki.size());
+
+ ASSERT_TRUE(ok);
+ verifier.VerifyUpdate(sd.data.data,
+ sd.data.len);
+
+ ok = verifier.VerifyFinal();
+ EXPECT_TRUE(ok);
+}
+#endif // !defined(OS_WIN) && !defined(OS_MACOSX)
+
+void VerifyDomainBoundCert(const std::string& domain,
+ const std::string& der_cert) {
+ // Origin Bound Cert OID.
+ static const char oid_string[] = "1.3.6.1.4.1.11129.2.1.6";
+
+ // Create object neccessary for extension lookup call.
+ SECItem extension_object = {
+ siAsciiString,
+ (unsigned char*)domain.data(),
+ static_cast<unsigned int>(domain.size())
+ };
+
+ // IA5Encode and arena allocate SECItem.
+ PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ SECItem* expected = SEC_ASN1EncodeItem(arena,
+ NULL,
+ &extension_object,
+ SEC_ASN1_GET(SEC_IA5StringTemplate));
+
+ ASSERT_NE(static_cast<SECItem*>(NULL), expected);
+
+ // Create OID SECItem.
+ SECItem ob_cert_oid = { siDEROID, NULL, 0 };
+ SECStatus ok = SEC_StringToOID(arena, &ob_cert_oid,
+ oid_string, 0);
+
+ ASSERT_EQ(SECSuccess, ok);
+
+ SECOidTag ob_cert_oid_tag = SECOID_FindOIDTag(&ob_cert_oid);
+
+ ASSERT_NE(SEC_OID_UNKNOWN, ob_cert_oid_tag);
+
+ // This test is run on Mac and Win where X509Certificate::os_cert_handle isn't
+ // an NSS type, so we have to manually create a NSS certificate object so we
+ // can use CERT_FindCertExtension. We also check the subject and validity
+ // times using NSS since X509Certificate will fail with EC certs on OSX 10.5
+ // (http://crbug.com/101231).
+ CERTCertificate* nss_cert = CreateNSSCertHandleFromBytes(
+ der_cert.data(), der_cert.size());
+
+ char* common_name = CERT_GetCommonName(&nss_cert->subject);
+ ASSERT_TRUE(common_name);
+ EXPECT_STREQ("anonymous.invalid", common_name);
+ PORT_Free(common_name);
+ EXPECT_EQ(SECSuccess, CERT_CertTimesValid(nss_cert));
+
+ // Lookup Origin Bound Cert extension in generated cert.
+ SECItem actual = { siBuffer, NULL, 0 };
+ ok = CERT_FindCertExtension(nss_cert,
+ ob_cert_oid_tag,
+ &actual);
+ CERT_DestroyCertificate(nss_cert);
+ ASSERT_EQ(SECSuccess, ok);
+
+ // Compare expected and actual extension values.
+ PRBool result = SECITEM_ItemsAreEqual(expected, &actual);
+ ASSERT_TRUE(result);
+
+ // Do Cleanup.
+ SECITEM_FreeItem(&actual, PR_FALSE);
+ PORT_FreeArena(arena, PR_FALSE);
+}
+
+} // namespace
+
+// This test creates a domain-bound cert from an EC private key and
+// then verifies the content of the certificate.
+TEST(X509UtilNSSTest, CreateDomainBoundCertEC) {
+ // Create a sample ASCII weborigin.
+ std::string domain = "weborigin.com";
+ base::Time now = base::Time::Now();
+
+ scoped_ptr<crypto::ECPrivateKey> private_key(
+ crypto::ECPrivateKey::Create());
+ std::string der_cert;
+ ASSERT_TRUE(x509_util::CreateDomainBoundCertEC(
+ private_key.get(),
+ domain, 1,
+ now,
+ now + base::TimeDelta::FromDays(1),
+ &der_cert));
+
+ VerifyDomainBoundCert(domain, der_cert);
+
+#if !defined(OS_WIN) && !defined(OS_MACOSX)
+ // signature_verifier_win and signature_verifier_mac can't handle EC certs.
+ std::vector<uint8> spki;
+ ASSERT_TRUE(private_key->ExportPublicKey(&spki));
+ VerifyCertificateSignature(der_cert, spki);
+#endif
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util_openssl.cc b/chromium/net/cert/x509_util_openssl.cc
new file mode 100644
index 00000000000..e4dec992575
--- /dev/null
+++ b/chromium/net/cert/x509_util_openssl.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_openssl.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace net {
+
+namespace x509_util {
+
+bool IsSupportedValidityRange(base::Time not_valid_before,
+ base::Time not_valid_after) {
+ if (not_valid_before > not_valid_after)
+ return false;
+
+ // The validity field of a certificate can only encode years 1-9999.
+
+ // Compute the base::Time values corresponding to Jan 1st,0001 and
+ // Jan 1st, 10000 respectively. Done by using the pre-computed numbers
+ // of days between these dates and the Unix epoch, i.e. Jan 1st, 1970,
+ // using the following Python script:
+ //
+ // from datetime import date as D
+ // print (D(1970,1,1)-D(1,1,1)) # -> 719162 days
+ // print (D(9999,12,31)-D(1970,1,1)) # -> 2932896 days
+ //
+ // Note: This ignores leap seconds, but should be enough in practice.
+ //
+ const int64 kDaysFromYear0001ToUnixEpoch = 719162;
+ const int64 kDaysFromUnixEpochToYear10000 = 2932896 + 1;
+ const base::Time kEpoch = base::Time::UnixEpoch();
+ const base::Time kYear0001 = kEpoch -
+ base::TimeDelta::FromDays(kDaysFromYear0001ToUnixEpoch);
+ const base::Time kYear10000 = kEpoch +
+ base::TimeDelta::FromDays(kDaysFromUnixEpochToYear10000);
+
+ if (not_valid_before < kYear0001 || not_valid_before >= kYear10000 ||
+ not_valid_after < kYear0001 || not_valid_after >= kYear10000)
+ return false;
+
+ return true;
+}
+
+bool CreateDomainBoundCertEC(
+ crypto::ECPrivateKey* key,
+ const std::string& domain,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_cert) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool CreateSelfSignedCert(crypto::RSAPrivateKey* key,
+ const std::string& common_name,
+ uint32 serial_number,
+ base::Time not_valid_before,
+ base::Time not_valid_after,
+ std::string* der_encoded) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool ParsePrincipalKeyAndValueByIndex(X509_NAME* name,
+ int index,
+ std::string* key,
+ std::string* value) {
+ X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, index);
+ if (!entry)
+ return false;
+
+ if (key) {
+ ASN1_OBJECT* object = X509_NAME_ENTRY_get_object(entry);
+ key->assign(OBJ_nid2sn(OBJ_obj2nid(object)));
+ }
+
+ ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
+ if (!data)
+ return false;
+
+ unsigned char* buf = NULL;
+ int len = ASN1_STRING_to_UTF8(&buf, data);
+ if (len <= 0)
+ return false;
+
+ value->assign(reinterpret_cast<const char*>(buf), len);
+ OPENSSL_free(buf);
+ return true;
+}
+
+bool ParsePrincipalValueByIndex(X509_NAME* name,
+ int index,
+ std::string* value) {
+ return ParsePrincipalKeyAndValueByIndex(name, index, NULL, value);
+}
+
+bool ParsePrincipalValueByNID(X509_NAME* name, int nid, std::string* value) {
+ int index = X509_NAME_get_index_by_NID(name, nid, -1);
+ if (index < 0)
+ return false;
+
+ return ParsePrincipalValueByIndex(name, index, value);
+}
+
+bool ParseDate(ASN1_TIME* x509_time, base::Time* time) {
+ if (!x509_time ||
+ (x509_time->type != V_ASN1_UTCTIME &&
+ x509_time->type != V_ASN1_GENERALIZEDTIME))
+ return false;
+
+ base::StringPiece str_date(reinterpret_cast<const char*>(x509_time->data),
+ x509_time->length);
+
+ CertDateFormat format = x509_time->type == V_ASN1_UTCTIME ?
+ CERT_DATE_FORMAT_UTC_TIME : CERT_DATE_FORMAT_GENERALIZED_TIME;
+ return ParseCertificateDate(str_date, format, time);
+}
+
+} // namespace x509_util
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util_openssl.h b/chromium/net/cert/x509_util_openssl.h
new file mode 100644
index 00000000000..ec4538003f3
--- /dev/null
+++ b/chromium/net/cert/x509_util_openssl.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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_CERT_X509_UTIL_OPENSSL_H_
+#define NET_CERT_X509_UTIL_OPENSSL_H_
+
+#include <openssl/asn1.h>
+#include <openssl/x509v3.h>
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+} // namespace base
+
+namespace net {
+
+// A collection of helper functions to fetch data from OpenSSL X509 certificates
+// into more convenient std / base datatypes.
+namespace x509_util {
+
+bool NET_EXPORT ParsePrincipalKeyAndValueByIndex(X509_NAME* name,
+ int index,
+ std::string* key,
+ std::string* value);
+
+bool NET_EXPORT ParsePrincipalValueByIndex(X509_NAME* name,
+ int index,
+ std::string* value);
+
+bool NET_EXPORT ParsePrincipalValueByNID(X509_NAME* name,
+ int nid,
+ std::string* value);
+
+bool NET_EXPORT ParseDate(ASN1_TIME* x509_time, base::Time* time);
+
+} // namespace x509_util
+
+} // namespace net
+
+#endif // NET_CERT_X509_UTIL_OPENSSL_H_
diff --git a/chromium/net/cert/x509_util_openssl_unittest.cc b/chromium/net/cert/x509_util_openssl_unittest.cc
new file mode 100644
index 00000000000..f237602ecbd
--- /dev/null
+++ b/chromium/net/cert/x509_util_openssl_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ec_private_key.h"
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_openssl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(X509UtilOpenSSLTest, IsSupportedValidityRange) {
+ base::Time now = base::Time::Now();
+ EXPECT_TRUE(x509_util::IsSupportedValidityRange(now, now));
+ EXPECT_FALSE(x509_util::IsSupportedValidityRange(
+ now, now - base::TimeDelta::FromSeconds(1)));
+
+ // See x509_util_openssl.cc to see how these were computed.
+ const int64 kDaysFromYear0001ToUnixEpoch = 719162;
+ const int64 kDaysFromUnixEpochToYear10000 = 2932896 + 1;
+
+ // When computing too_old / too_late, add one day to account for
+ // possible leap seconds.
+ base::Time too_old = base::Time::UnixEpoch() -
+ base::TimeDelta::FromDays(kDaysFromYear0001ToUnixEpoch + 1);
+
+ base::Time too_late = base::Time::UnixEpoch() +
+ base::TimeDelta::FromDays(kDaysFromUnixEpochToYear10000 + 1);
+
+ EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_old, too_old));
+ EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_old, now));
+
+ EXPECT_FALSE(x509_util::IsSupportedValidityRange(now, too_late));
+ EXPECT_FALSE(x509_util::IsSupportedValidityRange(too_late, too_late));
+}
+
+// For OpenSSL, x509_util::CreateDomainBoundCertEC() is not yet implemented
+// and should return false. This unit test ensures that a stub implementation
+// is present.
+TEST(X509UtilOpenSSLTest, CreateDomainBoundCertNotImplemented) {
+ std::string domain = "weborigin.com";
+ base::Time now = base::Time::Now();
+ scoped_ptr<crypto::ECPrivateKey> private_key(
+ crypto::ECPrivateKey::Create());
+ std::string der_cert;
+ EXPECT_FALSE(x509_util::CreateDomainBoundCertEC(
+ private_key.get(),
+ domain, 1,
+ now,
+ now + base::TimeDelta::FromDays(1),
+ &der_cert));
+ EXPECT_TRUE(der_cert.empty());
+
+}
+
+} // namespace net
diff --git a/chromium/net/cert/x509_util_unittest.cc b/chromium/net/cert/x509_util_unittest.cc
new file mode 100644
index 00000000000..cc13d96f1bf
--- /dev/null
+++ b/chromium/net/cert/x509_util_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 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.
+
+#include "net/cert/x509_util.h"
+
+#include <algorithm>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "crypto/rsa_private_key.h"
+#include "net/cert/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace x509_util {
+
+TEST(X509UtilTest, SortClientCertificates) {
+ CertificateList certs;
+
+ const base::Time now = base::Time::Now();
+ const base::TimeDelta five_days = base::TimeDelta::FromDays(5);
+
+ certs.push_back(scoped_refptr<X509Certificate>(NULL));
+ certs.push_back(new X509Certificate(
+ "expired", "expired",
+ base::Time::UnixEpoch(), base::Time::UnixEpoch()));
+ certs.push_back(new X509Certificate(
+ "not yet valid", "not yet valid",
+ base::Time::Max(), base::Time::Max()));
+ certs.push_back(new X509Certificate(
+ "older cert", "older cert",
+ now - five_days, now + five_days));
+ certs.push_back(scoped_refptr<X509Certificate>(NULL));
+ certs.push_back(new X509Certificate(
+ "newer cert", "newer cert",
+ now - base::TimeDelta::FromDays(3), now + five_days));
+
+ std::sort(certs.begin(), certs.end(), ClientCertSorter());
+
+ ASSERT_TRUE(certs[0].get());
+ EXPECT_EQ("newer cert", certs[0]->subject().common_name);
+ ASSERT_TRUE(certs[1].get());
+ EXPECT_EQ("older cert", certs[1]->subject().common_name);
+ ASSERT_TRUE(certs[2].get());
+ EXPECT_EQ("not yet valid", certs[2]->subject().common_name);
+ ASSERT_TRUE(certs[3].get());
+ EXPECT_EQ("expired", certs[3]->subject().common_name);
+ ASSERT_FALSE(certs[4].get());
+ ASSERT_FALSE(certs[5].get());
+}
+
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX)
+// This test creates a self-signed cert from a private key and then verify the
+// content of the certificate.
+TEST(X509UtilTest, CreateSelfSigned) {
+ scoped_ptr<crypto::RSAPrivateKey> private_key(
+ crypto::RSAPrivateKey::Create(1024));
+
+ ASSERT_TRUE(private_key.get());
+
+ std::string der_cert;
+ ASSERT_TRUE(x509_util::CreateSelfSignedCert(
+ private_key.get(),
+ "CN=subject",
+ 1,
+ base::Time::Now(),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ &der_cert));
+
+ scoped_refptr<X509Certificate> cert(X509Certificate::CreateFromBytes(
+ der_cert.data(), der_cert.size()));
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ("subject", cert->subject().GetDisplayName());
+ EXPECT_FALSE(cert->HasExpired());
+
+ cert = NULL;
+
+ const uint8 private_key_info[] = {
+ 0x30, 0x82, 0x02, 0x78, 0x02, 0x01, 0x00, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82,
+ 0x02, 0x62, 0x30, 0x82, 0x02, 0x5e, 0x02, 0x01,
+ 0x00, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b,
+ 0x20, 0xdc, 0x7c, 0x9b, 0x0c, 0xdc, 0x51, 0x61,
+ 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08,
+ 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64,
+ 0x85, 0x7b, 0x0c, 0x04, 0x13, 0x3f, 0x8d, 0xf4,
+ 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a,
+ 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f,
+ 0x9e, 0x36, 0x74, 0x30, 0xda, 0x8a, 0x31, 0x4f,
+ 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17,
+ 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5,
+ 0xb2, 0x65, 0x7a, 0x89, 0x4e, 0xb6, 0x47, 0xff,
+ 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85,
+ 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9,
+ 0x96, 0xd0, 0xd6, 0x14, 0x6f, 0x13, 0x8d, 0xc5,
+ 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18,
+ 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93,
+ 0xe7, 0x1f, 0x0f, 0xe6, 0x0f, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0x02, 0x81, 0x80, 0x03, 0x61, 0x89,
+ 0x37, 0xcb, 0xf2, 0x98, 0xa0, 0xce, 0xb4, 0xcb,
+ 0x16, 0x13, 0xf0, 0xe6, 0xaf, 0x5c, 0xc5, 0xa7,
+ 0x69, 0x71, 0xca, 0xba, 0x8d, 0xe0, 0x4d, 0xdd,
+ 0xed, 0xb8, 0x48, 0x8b, 0x16, 0x93, 0x36, 0x95,
+ 0xc2, 0x91, 0x40, 0x65, 0x17, 0xbd, 0x7f, 0xd6,
+ 0xad, 0x9e, 0x30, 0x28, 0x46, 0xe4, 0x3e, 0xcc,
+ 0x43, 0x78, 0xf9, 0xfe, 0x1f, 0x33, 0x23, 0x1e,
+ 0x31, 0x12, 0x9d, 0x3c, 0xa7, 0x08, 0x82, 0x7b,
+ 0x7d, 0x25, 0x4e, 0x5e, 0x19, 0xa8, 0x9b, 0xed,
+ 0x86, 0xb2, 0xcb, 0x3c, 0xfe, 0x4e, 0xa1, 0xfa,
+ 0x62, 0x87, 0x3a, 0x17, 0xf7, 0x60, 0xec, 0x38,
+ 0x29, 0xe8, 0x4f, 0x34, 0x9f, 0x76, 0x9d, 0xee,
+ 0xa3, 0xf6, 0x85, 0x6b, 0x84, 0x43, 0xc9, 0x1e,
+ 0x01, 0xff, 0xfd, 0xd0, 0x29, 0x4c, 0xfa, 0x8e,
+ 0x57, 0x0c, 0xc0, 0x71, 0xa5, 0xbb, 0x88, 0x46,
+ 0x29, 0x5c, 0xc0, 0x4f, 0x01, 0x02, 0x41, 0x00,
+ 0xf5, 0x83, 0xa4, 0x64, 0x4a, 0xf2, 0xdd, 0x8c,
+ 0x2c, 0xed, 0xa8, 0xd5, 0x60, 0x5a, 0xe4, 0xc7,
+ 0xcc, 0x61, 0xcd, 0x38, 0x42, 0x20, 0xd3, 0x82,
+ 0x18, 0xf2, 0x35, 0x00, 0x72, 0x2d, 0xf7, 0x89,
+ 0x80, 0x67, 0xb5, 0x93, 0x05, 0x5f, 0xdd, 0x42,
+ 0xba, 0x16, 0x1a, 0xea, 0x15, 0xc6, 0xf0, 0xb8,
+ 0x8c, 0xbc, 0xbf, 0x54, 0x9e, 0xf1, 0xc1, 0xb2,
+ 0xb3, 0x8b, 0xb6, 0x26, 0x02, 0x30, 0xc4, 0x81,
+ 0x02, 0x41, 0x00, 0xc0, 0x60, 0x62, 0x80, 0xe1,
+ 0x22, 0x78, 0xf6, 0x9d, 0x83, 0x18, 0xeb, 0x72,
+ 0x45, 0xd7, 0xc8, 0x01, 0x7f, 0xa9, 0xca, 0x8f,
+ 0x7d, 0xd6, 0xb8, 0x31, 0x2b, 0x84, 0x7f, 0x62,
+ 0xd9, 0xa9, 0x22, 0x17, 0x7d, 0x06, 0x35, 0x6c,
+ 0xf3, 0xc1, 0x94, 0x17, 0x85, 0x5a, 0xaf, 0x9c,
+ 0x5c, 0x09, 0x3c, 0xcf, 0x2f, 0x44, 0x9d, 0xb6,
+ 0x52, 0x68, 0x5f, 0xf9, 0x59, 0xc8, 0x84, 0x2b,
+ 0x39, 0x22, 0x8f, 0x02, 0x41, 0x00, 0xb2, 0x04,
+ 0xe2, 0x0e, 0x56, 0xca, 0x03, 0x1a, 0xc0, 0xf9,
+ 0x12, 0x92, 0xa5, 0x6b, 0x42, 0xb8, 0x1c, 0xda,
+ 0x4d, 0x93, 0x9d, 0x5f, 0x6f, 0xfd, 0xc5, 0x58,
+ 0xda, 0x55, 0x98, 0x74, 0xfc, 0x28, 0x17, 0x93,
+ 0x1b, 0x75, 0x9f, 0x50, 0x03, 0x7f, 0x7e, 0xae,
+ 0xc8, 0x95, 0x33, 0x75, 0x2c, 0xd6, 0xa4, 0x35,
+ 0xb8, 0x06, 0x03, 0xba, 0x08, 0x59, 0x2b, 0x17,
+ 0x02, 0xdc, 0x4c, 0x7a, 0x50, 0x01, 0x02, 0x41,
+ 0x00, 0x9d, 0xdb, 0x39, 0x59, 0x09, 0xe4, 0x30,
+ 0xa0, 0x24, 0xf5, 0xdb, 0x2f, 0xf0, 0x2f, 0xf1,
+ 0x75, 0x74, 0x0d, 0x5e, 0xb5, 0x11, 0x73, 0xb0,
+ 0x0a, 0xaa, 0x86, 0x4c, 0x0d, 0xff, 0x7e, 0x1d,
+ 0xb4, 0x14, 0xd4, 0x09, 0x91, 0x33, 0x5a, 0xfd,
+ 0xa0, 0x58, 0x80, 0x9b, 0xbe, 0x78, 0x2e, 0x69,
+ 0x82, 0x15, 0x7c, 0x72, 0xf0, 0x7b, 0x18, 0x39,
+ 0xff, 0x6e, 0xeb, 0xc6, 0x86, 0xf5, 0xb4, 0xc7,
+ 0x6f, 0x02, 0x41, 0x00, 0x8d, 0x1a, 0x37, 0x0f,
+ 0x76, 0xc4, 0x82, 0xfa, 0x5c, 0xc3, 0x79, 0x35,
+ 0x3e, 0x70, 0x8a, 0xbf, 0x27, 0x49, 0xb0, 0x99,
+ 0x63, 0xcb, 0x77, 0x5f, 0xa8, 0x82, 0x65, 0xf6,
+ 0x03, 0x52, 0x51, 0xf1, 0xae, 0x2e, 0x05, 0xb3,
+ 0xc6, 0xa4, 0x92, 0xd1, 0xce, 0x6c, 0x72, 0xfb,
+ 0x21, 0xb3, 0x02, 0x87, 0xe4, 0xfd, 0x61, 0xca,
+ 0x00, 0x42, 0x19, 0xf0, 0xda, 0x5a, 0x53, 0xe3,
+ 0xb1, 0xc5, 0x15, 0xf3
+ };
+
+ std::vector<uint8> input;
+ input.resize(sizeof(private_key_info));
+ memcpy(&input.front(), private_key_info, sizeof(private_key_info));
+
+ private_key.reset(crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input));
+ ASSERT_TRUE(private_key.get());
+
+ ASSERT_TRUE(x509_util::CreateSelfSignedCert(
+ private_key.get(),
+ "CN=subject",
+ 1,
+ base::Time::Now(),
+ base::Time::Now() + base::TimeDelta::FromDays(1),
+ &der_cert));
+
+ cert = X509Certificate::CreateFromBytes(der_cert.data(), der_cert.size());
+ ASSERT_TRUE(cert.get());
+
+ EXPECT_EQ("subject", cert->subject().GetDisplayName());
+ EXPECT_FALSE(cert->HasExpired());
+}
+#endif
+
+} // namespace x509_util
+
+} // namespace net
diff --git a/chromium/net/cookies/OWNERS b/chromium/net/cookies/OWNERS
new file mode 100644
index 00000000000..82e44d8afb0
--- /dev/null
+++ b/chromium/net/cookies/OWNERS
@@ -0,0 +1 @@
+erikwright@chromium.org
diff --git a/chromium/net/cookies/canonical_cookie.cc b/chromium/net/cookies/canonical_cookie.cc
new file mode 100644
index 00000000000..15b77c7add5
--- /dev/null
+++ b/chromium/net/cookies/canonical_cookie.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 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.
+
+// Portions of this code based on Mozilla:
+// (netwerk/cookie/src/nsCookieService.cpp)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2003
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Witte (dwitte@stanford.edu)
+ * Michiel van Leeuwen (mvl@exedo.nl)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/cookies/canonical_cookie.h"
+
+#include "base/basictypes.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace net {
+
+namespace {
+
+const int kVlogSetCookies = 7;
+
+// Determine the cookie domain to use for setting the specified cookie.
+bool GetCookieDomain(const GURL& url,
+ const ParsedCookie& pc,
+ std::string* result) {
+ std::string domain_string;
+ if (pc.HasDomain())
+ domain_string = pc.Domain();
+ return cookie_util::GetCookieDomainWithString(url, domain_string, result);
+}
+
+std::string CanonPathWithString(const GURL& url,
+ const std::string& path_string) {
+ // The RFC says the path should be a prefix of the current URL path.
+ // However, Mozilla allows you to set any path for compatibility with
+ // broken websites. We unfortunately will mimic this behavior. We try
+ // to be generous and accept cookies with an invalid path attribute, and
+ // default the path to something reasonable.
+
+ // The path was supplied in the cookie, we'll take it.
+ if (!path_string.empty() && path_string[0] == '/')
+ return path_string;
+
+ // The path was not supplied in the cookie or invalid, we will default
+ // to the current URL path.
+ // """Defaults to the path of the request URL that generated the
+ // Set-Cookie response, up to, but not including, the
+ // right-most /."""
+ // How would this work for a cookie on /? We will include it then.
+ const std::string& url_path = url.path();
+
+ size_t idx = url_path.find_last_of('/');
+
+ // The cookie path was invalid or a single '/'.
+ if (idx == 0 || idx == std::string::npos)
+ return std::string("/");
+
+ // Return up to the rightmost '/'.
+ return url_path.substr(0, idx);
+}
+
+} // namespace
+
+CanonicalCookie::CanonicalCookie()
+ : secure_(false),
+ httponly_(false) {
+}
+
+CanonicalCookie::CanonicalCookie(
+ const GURL& url, const std::string& name, const std::string& value,
+ const std::string& domain, const std::string& path,
+ const base::Time& creation, const base::Time& expiration,
+ const base::Time& last_access, bool secure, bool httponly,
+ CookiePriority priority)
+ : source_(GetCookieSourceFromURL(url)),
+ name_(name),
+ value_(value),
+ domain_(domain),
+ path_(path),
+ creation_date_(creation),
+ expiry_date_(expiration),
+ last_access_date_(last_access),
+ secure_(secure),
+ httponly_(httponly),
+ priority_(priority) {
+}
+
+CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc)
+ : source_(GetCookieSourceFromURL(url)),
+ name_(pc.Name()),
+ value_(pc.Value()),
+ path_(CanonPath(url, pc)),
+ creation_date_(Time::Now()),
+ last_access_date_(Time()),
+ secure_(pc.IsSecure()),
+ httponly_(pc.IsHttpOnly()),
+ priority_(pc.Priority()) {
+ if (pc.HasExpires())
+ expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_);
+
+ // Do the best we can with the domain.
+ std::string cookie_domain;
+ std::string domain_string;
+ if (pc.HasDomain()) {
+ domain_string = pc.Domain();
+ }
+ bool result
+ = cookie_util::GetCookieDomainWithString(url, domain_string,
+ &cookie_domain);
+ // Caller is responsible for passing in good arguments.
+ DCHECK(result);
+ domain_ = cookie_domain;
+}
+
+CanonicalCookie::~CanonicalCookie() {
+}
+
+std::string CanonicalCookie::GetCookieSourceFromURL(const GURL& url) {
+ if (url.SchemeIsFile())
+ return url.spec();
+
+ url_canon::Replacements<char> replacements;
+ replacements.ClearPort();
+ if (url.SchemeIsSecure())
+ replacements.SetScheme("http", url_parse::Component(0, 4));
+
+ return url.GetOrigin().ReplaceComponents(replacements).spec();
+}
+
+// static
+std::string CanonicalCookie::CanonPath(const GURL& url,
+ const ParsedCookie& pc) {
+ std::string path_string;
+ if (pc.HasPath())
+ path_string = pc.Path();
+ return CanonPathWithString(url, path_string);
+}
+
+// static
+Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc,
+ const Time& current,
+ const Time& server_time) {
+ // First, try the Max-Age attribute.
+ uint64 max_age = 0;
+ if (pc.HasMaxAge() &&
+#ifdef COMPILER_MSVC
+ sscanf_s(
+#else
+ sscanf(
+#endif
+ pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) {
+ return current + TimeDelta::FromSeconds(max_age);
+ }
+
+ // Try the Expires attribute.
+ if (pc.HasExpires() && !pc.Expires().empty()) {
+ // Adjust for clock skew between server and host.
+ base::Time parsed_expiry = cookie_util::ParseCookieTime(pc.Expires());
+ if (!parsed_expiry.is_null())
+ return parsed_expiry + (current - server_time);
+ }
+
+ // Invalid or no expiration, persistent cookie.
+ return Time();
+}
+
+// static
+CanonicalCookie* CanonicalCookie::Create(const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& creation_time,
+ const CookieOptions& options) {
+ ParsedCookie parsed_cookie(cookie_line);
+
+ if (!parsed_cookie.IsValid()) {
+ VLOG(kVlogSetCookies) << "WARNING: Couldn't parse cookie";
+ return NULL;
+ }
+
+ if (options.exclude_httponly() && parsed_cookie.IsHttpOnly()) {
+ VLOG(kVlogSetCookies) << "Create() is not creating a httponly cookie";
+ return NULL;
+ }
+
+ std::string cookie_domain;
+ if (!GetCookieDomain(url, parsed_cookie, &cookie_domain)) {
+ return NULL;
+ }
+
+ std::string cookie_path = CanonicalCookie::CanonPath(url, parsed_cookie);
+ Time server_time(creation_time);
+ if (options.has_server_time())
+ server_time = options.server_time();
+
+ Time cookie_expires = CanonicalCookie::CanonExpiration(parsed_cookie,
+ creation_time,
+ server_time);
+
+ return new CanonicalCookie(url, parsed_cookie.Name(), parsed_cookie.Value(),
+ cookie_domain, cookie_path, creation_time,
+ cookie_expires, creation_time,
+ parsed_cookie.IsSecure(),
+ parsed_cookie.IsHttpOnly(),
+ parsed_cookie.Priority());
+}
+
+CanonicalCookie* CanonicalCookie::Create(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& creation,
+ const base::Time& expiration,
+ bool secure,
+ bool http_only,
+ CookiePriority priority) {
+ // Expect valid attribute tokens and values, as defined by the ParsedCookie
+ // logic, otherwise don't create the cookie.
+ std::string parsed_name = ParsedCookie::ParseTokenString(name);
+ if (parsed_name != name)
+ return NULL;
+ std::string parsed_value = ParsedCookie::ParseValueString(value);
+ if (parsed_value != value)
+ return NULL;
+
+ std::string parsed_domain = ParsedCookie::ParseValueString(domain);
+ if (parsed_domain != domain)
+ return NULL;
+ std::string cookie_domain;
+ if (!cookie_util::GetCookieDomainWithString(url, parsed_domain,
+ &cookie_domain)) {
+ return NULL;
+ }
+
+ std::string parsed_path = ParsedCookie::ParseValueString(path);
+ if (parsed_path != path)
+ return NULL;
+
+ std::string cookie_path = CanonPathWithString(url, parsed_path);
+ // Expect that the path was either not specified (empty), or is valid.
+ if (!parsed_path.empty() && cookie_path != parsed_path)
+ return NULL;
+ // Canonicalize path again to make sure it escapes characters as needed.
+ url_parse::Component path_component(0, cookie_path.length());
+ url_canon::RawCanonOutputT<char> canon_path;
+ url_parse::Component canon_path_component;
+ url_canon::CanonicalizePath(cookie_path.data(), path_component,
+ &canon_path, &canon_path_component);
+ cookie_path = std::string(canon_path.data() + canon_path_component.begin,
+ canon_path_component.len);
+
+ return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain,
+ cookie_path, creation, expiration, creation,
+ secure, http_only, priority);
+}
+
+bool CanonicalCookie::IsOnPath(const std::string& url_path) const {
+
+ // A zero length would be unsafe for our trailing '/' checks, and
+ // would also make no sense for our prefix match. The code that
+ // creates a CanonicalCookie should make sure the path is never zero length,
+ // but we double check anyway.
+ if (path_.empty())
+ return false;
+
+ // The Mozilla code broke this into three cases, based on if the cookie path
+ // was longer, the same length, or shorter than the length of the url path.
+ // I think the approach below is simpler.
+
+ // Make sure the cookie path is a prefix of the url path. If the
+ // url path is shorter than the cookie path, then the cookie path
+ // can't be a prefix.
+ if (url_path.find(path_) != 0)
+ return false;
+
+ // Now we know that url_path is >= cookie_path, and that cookie_path
+ // is a prefix of url_path. If they are the are the same length then
+ // they are identical, otherwise we need an additional check:
+
+ // In order to avoid in correctly matching a cookie path of /blah
+ // with a request path of '/blahblah/', we need to make sure that either
+ // the cookie path ends in a trailing '/', or that we prefix up to a '/'
+ // in the url path. Since we know that the url path length is greater
+ // than the cookie path length, it's safe to index one byte past.
+ if (path_.length() != url_path.length() &&
+ path_[path_.length() - 1] != '/' &&
+ url_path[path_.length()] != '/')
+ return false;
+
+ return true;
+}
+
+bool CanonicalCookie::IsDomainMatch(const std::string& host) const {
+ // Can domain match in two ways; as a domain cookie (where the cookie
+ // domain begins with ".") or as a host cookie (where it doesn't).
+
+ // Some consumers of the CookieMonster expect to set cookies on
+ // URLs like http://.strange.url. To retrieve cookies in this instance,
+ // we allow matching as a host cookie even when the domain_ starts with
+ // a period.
+ if (host == domain_)
+ return true;
+
+ // Domain cookie must have an initial ".". To match, it must be
+ // equal to url's host with initial period removed, or a suffix of
+ // it.
+
+ // Arguably this should only apply to "http" or "https" cookies, but
+ // extension cookie tests currently use the funtionality, and if we
+ // ever decide to implement that it should be done by preventing
+ // such cookies from being set.
+ if (domain_.empty() || domain_[0] != '.')
+ return false;
+
+ // The host with a "." prefixed.
+ if (domain_.compare(1, std::string::npos, host) == 0)
+ return true;
+
+ // A pure suffix of the host (ok since we know the domain already
+ // starts with a ".")
+ return (host.length() > domain_.length() &&
+ host.compare(host.length() - domain_.length(),
+ domain_.length(), domain_) == 0);
+}
+
+bool CanonicalCookie::IncludeForRequestURL(const GURL& url,
+ const CookieOptions& options) const {
+ // Filter out HttpOnly cookies, per options.
+ if (options.exclude_httponly() && IsHttpOnly())
+ return false;
+ // Secure cookies should not be included in requests for URLs with an
+ // insecure scheme.
+ if (IsSecure() && !url.SchemeIsSecure())
+ return false;
+ // Don't include cookies for requests that don't apply to the cookie domain.
+ if (!IsDomainMatch(url.host()))
+ return false;
+ // Don't include cookies for requests with a url path that does not path
+ // match the cookie-path.
+ if (!IsOnPath(url.path()))
+ return false;
+
+ return true;
+}
+
+std::string CanonicalCookie::DebugString() const {
+ return base::StringPrintf(
+ "name: %s value: %s domain: %s path: %s creation: %"
+ PRId64,
+ name_.c_str(), value_.c_str(),
+ domain_.c_str(), path_.c_str(),
+ static_cast<int64>(creation_date_.ToTimeT()));
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/canonical_cookie.h b/chromium/net/cookies/canonical_cookie.h
new file mode 100644
index 00000000000..a78eece4198
--- /dev/null
+++ b/chromium/net/cookies/canonical_cookie.h
@@ -0,0 +1,162 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_CANONICAL_COOKIE_H_
+#define NET_COOKIES_CANONICAL_COOKIE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_options.h"
+
+class GURL;
+
+namespace net {
+
+class ParsedCookie;
+
+class NET_EXPORT CanonicalCookie {
+ public:
+ // These constructors do no validation or canonicalization of their inputs;
+ // the resulting CanonicalCookies should not be relied on to be canonical
+ // unless the caller has done appropriate validation and canonicalization
+ // themselves.
+ CanonicalCookie();
+ CanonicalCookie(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& creation,
+ const base::Time& expiration,
+ const base::Time& last_access,
+ bool secure,
+ bool httponly,
+ CookiePriority priority);
+
+ // This constructor does canonicalization but not validation.
+ // The result of this constructor should not be relied on in contexts
+ // in which pre-validation of the ParsedCookie has not been done.
+ CanonicalCookie(const GURL& url, const ParsedCookie& pc);
+
+ ~CanonicalCookie();
+
+ // Supports the default copy constructor.
+
+ // Creates a new |CanonicalCookie| from the |cookie_line| and the
+ // |creation_time|. Canonicalizes and validates inputs. May return NULL if
+ // an attribut value is invalid.
+ static CanonicalCookie* Create(const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& creation_time,
+ const CookieOptions& options);
+
+ // Creates a canonical cookie from unparsed attribute values.
+ // Canonicalizes and validates inputs. May return NULL if an attribute
+ // value is invalid.
+ static CanonicalCookie* Create(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& creation,
+ const base::Time& expiration,
+ bool secure,
+ bool http_only,
+ CookiePriority priority);
+
+ const std::string& Source() const { return source_; }
+ const std::string& Name() const { return name_; }
+ const std::string& Value() const { return value_; }
+ const std::string& Domain() const { return domain_; }
+ const std::string& Path() const { return path_; }
+ const base::Time& CreationDate() const { return creation_date_; }
+ const base::Time& LastAccessDate() const { return last_access_date_; }
+ bool IsPersistent() const { return !expiry_date_.is_null(); }
+ const base::Time& ExpiryDate() const { return expiry_date_; }
+ bool IsSecure() const { return secure_; }
+ bool IsHttpOnly() const { return httponly_; }
+ CookiePriority Priority() const { return priority_; }
+ bool IsDomainCookie() const {
+ return !domain_.empty() && domain_[0] == '.'; }
+ bool IsHostCookie() const { return !IsDomainCookie(); }
+
+ bool IsExpired(const base::Time& current) const {
+ return !expiry_date_.is_null() && current >= expiry_date_;
+ }
+
+ // Are the cookies considered equivalent in the eyes of RFC 2965.
+ // The RFC says that name must match (case-sensitive), domain must
+ // match (case insensitive), and path must match (case sensitive).
+ // For the case insensitive domain compare, we rely on the domain
+ // having been canonicalized (in
+ // GetCookieDomainWithString->CanonicalizeHost).
+ bool IsEquivalent(const CanonicalCookie& ecc) const {
+ // It seems like it would make sense to take secure and httponly into
+ // account, but the RFC doesn't specify this.
+ // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost().
+ return (name_ == ecc.Name() && domain_ == ecc.Domain()
+ && path_ == ecc.Path());
+ }
+
+ void SetLastAccessDate(const base::Time& date) {
+ last_access_date_ = date;
+ }
+
+ // Returns true if the given |url_path| path-matches the cookie-path as
+ // described in section 5.1.4 in RFC 6265.
+ bool IsOnPath(const std::string& url_path) const;
+
+ // Returns true if the cookie domain matches the given |host| as described in
+ // section 5.1.3 of RFC 6265.
+ bool IsDomainMatch(const std::string& host) const;
+
+ // Returns true if the cookie should be included for the given request |url|.
+ // HTTP only cookies can be filter by using appropriate cookie |options|.
+ // PLEASE NOTE that this method does not check whether a cookie is expired or
+ // not!
+ bool IncludeForRequestURL(const GURL& url,
+ const CookieOptions& options) const;
+
+ std::string DebugString() const;
+
+ // Returns the cookie source when cookies are set for |url|. This function
+ // is public for unit test purposes only.
+ static std::string GetCookieSourceFromURL(const GURL& url);
+ static std::string CanonPath(const GURL& url, const ParsedCookie& pc);
+ static base::Time CanonExpiration(const ParsedCookie& pc,
+ const base::Time& current,
+ const base::Time& server_time);
+
+ private:
+ // The source member of a canonical cookie is the origin of the URL that tried
+ // to set this cookie, minus the port number if any. This field is not
+ // persistent though; its only used in the in-tab cookies dialog to show the
+ // user the source URL. This is used for both allowed and blocked cookies.
+ // When a CanonicalCookie is constructed from the backing store (common case)
+ // this field will be null. CanonicalCookie consumers should not rely on
+ // this field unless they guarantee that the creator of those
+ // CanonicalCookies properly initialized the field.
+ std::string source_;
+ std::string name_;
+ std::string value_;
+ std::string domain_;
+ std::string path_;
+ base::Time creation_date_;
+ base::Time expiry_date_;
+ base::Time last_access_date_;
+ bool secure_;
+ bool httponly_;
+ CookiePriority priority_;
+};
+
+typedef std::vector<CanonicalCookie> CookieList;
+
+} // namespace net
+
+#endif // NET_COOKIES_CANONICAL_COOKIE_H_
diff --git a/chromium/net/cookies/canonical_cookie_unittest.cc b/chromium/net/cookies/canonical_cookie_unittest.cc
new file mode 100644
index 00000000000..26e4e1249f1
--- /dev/null
+++ b/chromium/net/cookies/canonical_cookie_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright (c) 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.
+
+#include "net/cookies/canonical_cookie.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_options.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+TEST(CanonicalCookieTest, GetCookieSourceFromURL) {
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com/")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com/test")));
+ EXPECT_EQ("file:///tmp/test.html",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("file:///tmp/test.html")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com:1234/")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("https://example.com/")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://user:pwd@example.com/")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com/test?foo")));
+ EXPECT_EQ("http://example.com/",
+ CanonicalCookie::GetCookieSourceFromURL(
+ GURL("http://example.com/test#foo")));
+}
+
+TEST(CanonicalCookieTest, Constructor) {
+ GURL url("http://www.example.com/test");
+ base::Time current_time = base::Time::Now();
+
+ CanonicalCookie cookie(url, "A", "2", "www.example.com", "/test",
+ current_time, base::Time(), current_time, false, false,
+ COOKIE_PRIORITY_DEFAULT);
+ EXPECT_EQ(url.GetOrigin().spec(), cookie.Source());
+ EXPECT_EQ("A", cookie.Name());
+ EXPECT_EQ("2", cookie.Value());
+ EXPECT_EQ("www.example.com", cookie.Domain());
+ EXPECT_EQ("/test", cookie.Path());
+ EXPECT_FALSE(cookie.IsSecure());
+
+ CanonicalCookie cookie2(url,
+ "A",
+ "2",
+ std::string(),
+ std::string(),
+ current_time,
+ base::Time(),
+ current_time,
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT);
+ EXPECT_EQ(url.GetOrigin().spec(), cookie.Source());
+ EXPECT_EQ("A", cookie2.Name());
+ EXPECT_EQ("2", cookie2.Value());
+ EXPECT_EQ("", cookie2.Domain());
+ EXPECT_EQ("", cookie2.Path());
+ EXPECT_FALSE(cookie2.IsSecure());
+
+}
+
+TEST(CanonicalCookieTest, Create) {
+ // Test creating cookies from a cookie string.
+ GURL url("http://www.example.com/test/foo.html");
+ base::Time creation_time = base::Time::Now();
+ CookieOptions options;
+
+ scoped_ptr<CanonicalCookie> cookie(
+ CanonicalCookie::Create(url, "A=2", creation_time, options));
+ EXPECT_EQ(url.GetOrigin().spec(), cookie->Source());
+ EXPECT_EQ("A", cookie->Name());
+ EXPECT_EQ("2", cookie->Value());
+ EXPECT_EQ("www.example.com", cookie->Domain());
+ EXPECT_EQ("/test", cookie->Path());
+ EXPECT_FALSE(cookie->IsSecure());
+
+ GURL url2("http://www.foo.com");
+ cookie.reset(CanonicalCookie::Create(url2, "B=1", creation_time, options));
+ EXPECT_EQ(url2.GetOrigin().spec(), cookie->Source());
+ EXPECT_EQ("B", cookie->Name());
+ EXPECT_EQ("1", cookie->Value());
+ EXPECT_EQ("www.foo.com", cookie->Domain());
+ EXPECT_EQ("/", cookie->Path());
+ EXPECT_FALSE(cookie->IsSecure());
+
+ // Test creating secure cookies. RFC 6265 allows insecure urls to set secure
+ // cookies.
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; Secure", creation_time, options));
+ EXPECT_TRUE(cookie.get());
+ EXPECT_TRUE(cookie->IsSecure());
+
+ // Test creating http only cookies.
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; HttpOnly", creation_time, options));
+ EXPECT_FALSE(cookie.get());
+ CookieOptions httponly_options;
+ httponly_options.set_include_httponly();
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; HttpOnly", creation_time,
+ httponly_options));
+ EXPECT_TRUE(cookie->IsHttpOnly());
+
+ // Test the creating cookies using specific parameter instead of a cookie
+ // string.
+ cookie.reset(CanonicalCookie::Create(
+ url, "A", "2", "www.example.com", "/test", creation_time, base::Time(),
+ false, false, COOKIE_PRIORITY_DEFAULT));
+ EXPECT_EQ(url.GetOrigin().spec(), cookie->Source());
+ EXPECT_EQ("A", cookie->Name());
+ EXPECT_EQ("2", cookie->Value());
+ EXPECT_EQ(".www.example.com", cookie->Domain());
+ EXPECT_EQ("/test", cookie->Path());
+ EXPECT_FALSE(cookie->IsSecure());
+
+ cookie.reset(CanonicalCookie::Create(
+ url, "A", "2", ".www.example.com", "/test", creation_time, base::Time(),
+ false, false, COOKIE_PRIORITY_DEFAULT));
+ EXPECT_EQ(url.GetOrigin().spec(), cookie->Source());
+ EXPECT_EQ("A", cookie->Name());
+ EXPECT_EQ("2", cookie->Value());
+ EXPECT_EQ(".www.example.com", cookie->Domain());
+ EXPECT_EQ("/test", cookie->Path());
+ EXPECT_FALSE(cookie->IsSecure());
+}
+
+TEST(CanonicalCookieTest, EmptyExpiry) {
+ GURL url("http://www7.ipdl.inpit.go.jp/Tokujitu/tjkta.ipdl?N0000=108");
+ base::Time creation_time = base::Time::Now();
+ CookieOptions options;
+
+ std::string cookie_line =
+ "ACSTM=20130308043820420042; path=/; domain=ipdl.inpit.go.jp; Expires=";
+ scoped_ptr<CanonicalCookie> cookie(CanonicalCookie::Create(
+ url, cookie_line, creation_time, options));
+ EXPECT_TRUE(cookie.get());
+ EXPECT_FALSE(cookie->IsPersistent());
+ EXPECT_FALSE(cookie->IsExpired(creation_time));
+ EXPECT_EQ(base::Time(), cookie->ExpiryDate());
+
+ // With a stale server time
+ options.set_server_time(creation_time - base::TimeDelta::FromHours(1));
+ cookie.reset(CanonicalCookie::Create(
+ url, cookie_line, creation_time, options));
+ EXPECT_TRUE(cookie.get());
+ EXPECT_FALSE(cookie->IsPersistent());
+ EXPECT_FALSE(cookie->IsExpired(creation_time));
+ EXPECT_EQ(base::Time(), cookie->ExpiryDate());
+
+ // With a future server time
+ options.set_server_time(creation_time + base::TimeDelta::FromHours(1));
+ cookie.reset(CanonicalCookie::Create(
+ url, cookie_line, creation_time, options));
+ EXPECT_TRUE(cookie.get());
+ EXPECT_FALSE(cookie->IsPersistent());
+ EXPECT_FALSE(cookie->IsExpired(creation_time));
+ EXPECT_EQ(base::Time(), cookie->ExpiryDate());
+}
+
+TEST(CanonicalCookieTest, IsEquivalent) {
+ GURL url("http://www.example.com/");
+ std::string cookie_name = "A";
+ std::string cookie_value = "2EDA-EF";
+ std::string cookie_domain = ".www.example.com";
+ std::string cookie_path = "/";
+ base::Time creation_time = base::Time::Now();
+ base::Time last_access_time = creation_time;
+ base::Time expiration_time = creation_time + base::TimeDelta::FromDays(2);
+ bool secure(false);
+ bool httponly(false);
+
+ // Test that a cookie is equivalent to itself.
+ scoped_ptr<CanonicalCookie> cookie(
+ new CanonicalCookie(url, cookie_name, cookie_value, cookie_domain,
+ cookie_path, creation_time, expiration_time,
+ last_access_time, secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_TRUE(cookie->IsEquivalent(*cookie));
+
+ // Test that two identical cookies are equivalent.
+ scoped_ptr<CanonicalCookie> other_cookie(
+ new CanonicalCookie(url, cookie_name, cookie_value, cookie_domain,
+ cookie_path, creation_time, expiration_time,
+ last_access_time, secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+
+ // Tests that use different variations of attribute values that
+ // DON'T affect cookie equivalence.
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, "2", cookie_domain,
+ cookie_path, creation_time,
+ expiration_time, last_access_time,
+ secure, httponly,
+ COOKIE_PRIORITY_HIGH));
+ EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+
+ base::Time other_creation_time =
+ creation_time + base::TimeDelta::FromMinutes(2);
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, "2", cookie_domain,
+ cookie_path, other_creation_time,
+ expiration_time, last_access_time,
+ secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, cookie_name,
+ cookie_domain, cookie_path,
+ creation_time, expiration_time,
+ last_access_time, true, httponly,
+ COOKIE_PRIORITY_LOW));
+ EXPECT_TRUE(cookie->IsEquivalent(*other_cookie));
+
+ // Tests that use different variations of attribute values that
+ // DO affect cookie equivalence.
+ other_cookie.reset(new CanonicalCookie(url, "B", cookie_value, cookie_domain,
+ cookie_path, creation_time,
+ expiration_time, last_access_time,
+ secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
+
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, cookie_value,
+ "www.example.com", cookie_path,
+ creation_time, expiration_time,
+ last_access_time, secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_TRUE(cookie->IsDomainCookie());
+ EXPECT_FALSE(other_cookie->IsDomainCookie());
+ EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
+
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, cookie_value,
+ ".example.com", cookie_path,
+ creation_time, expiration_time,
+ last_access_time, secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
+
+ other_cookie.reset(new CanonicalCookie(url, cookie_name, cookie_value,
+ cookie_domain, "/test/0",
+ creation_time, expiration_time,
+ last_access_time, secure, httponly,
+ COOKIE_PRIORITY_MEDIUM));
+ EXPECT_FALSE(cookie->IsEquivalent(*other_cookie));
+}
+
+TEST(CanonicalCookieTest, IsDomainMatch) {
+ GURL url("http://www.example.com/test/foo.html");
+ base::Time creation_time = base::Time::Now();
+ CookieOptions options;
+
+ scoped_ptr<CanonicalCookie> cookie(
+ CanonicalCookie::Create(url, "A=2", creation_time, options));
+ EXPECT_TRUE(cookie->IsHostCookie());
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("foo.www.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("www0.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("example.com"));
+
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; Domain=www.example.com", creation_time,
+ options));
+ EXPECT_TRUE(cookie->IsDomainCookie());
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_TRUE(cookie->IsDomainMatch("foo.www.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("www0.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("example.com"));
+
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; Domain=.www.example.com",
+ creation_time, options));
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_TRUE(cookie->IsDomainMatch("www.example.com"));
+ EXPECT_TRUE(cookie->IsDomainMatch("foo.www.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("www0.example.com"));
+ EXPECT_FALSE(cookie->IsDomainMatch("example.com"));
+}
+
+TEST(CanonicalCookieTest, IsOnPath) {
+ base::Time creation_time = base::Time::Now();
+ CookieOptions options;
+
+ scoped_ptr<CanonicalCookie> cookie(
+ CanonicalCookie::Create(GURL("http://www.example.com"),
+ "A=2", creation_time, options));
+ EXPECT_TRUE(cookie->IsOnPath("/"));
+ EXPECT_TRUE(cookie->IsOnPath("/test"));
+ EXPECT_TRUE(cookie->IsOnPath("/test/bar.html"));
+
+ // Test the empty string edge case.
+ EXPECT_FALSE(cookie->IsOnPath(std::string()));
+
+ cookie.reset(
+ CanonicalCookie::Create(GURL("http://www.example.com/test/foo.html"),
+ "A=2", creation_time, options));
+ EXPECT_FALSE(cookie->IsOnPath("/"));
+ EXPECT_TRUE(cookie->IsOnPath("/test"));
+ EXPECT_TRUE(cookie->IsOnPath("/test/bar.html"));
+ EXPECT_TRUE(cookie->IsOnPath("/test/sample/bar.html"));
+}
+
+TEST(CanonicalCookieTest, IncludeForRequestURL) {
+ GURL url("http://www.example.com");
+ base::Time creation_time = base::Time::Now();
+ CookieOptions options;
+
+ scoped_ptr<CanonicalCookie> cookie(
+ CanonicalCookie::Create(url, "A=2", creation_time, options));
+ EXPECT_TRUE(cookie->IncludeForRequestURL(url, options));
+ EXPECT_TRUE(cookie->IncludeForRequestURL(
+ GURL("http://www.example.com/foo/bar"), options));
+ EXPECT_TRUE(cookie->IncludeForRequestURL(
+ GURL("https://www.example.com/foo/bar"), options));
+ EXPECT_FALSE(cookie->IncludeForRequestURL(GURL("https://sub.example.com"),
+ options));
+ EXPECT_FALSE(cookie->IncludeForRequestURL(GURL("https://sub.www.example.com"),
+ options));
+
+ // Test that cookie with a cookie path that does not match the url path are
+ // not included.
+ cookie.reset(CanonicalCookie::Create(url, "A=2; Path=/foo/bar", creation_time,
+ options));
+ EXPECT_FALSE(cookie->IncludeForRequestURL(url, options));
+ EXPECT_TRUE(cookie->IncludeForRequestURL(
+ GURL("http://www.example.com/foo/bar/index.html"), options));
+
+ // Test that a secure cookie is not included for a non secure URL.
+ GURL secure_url("https://www.example.com");
+ cookie.reset(CanonicalCookie::Create(secure_url, "A=2; Secure", creation_time,
+ options));
+ EXPECT_TRUE(cookie->IsSecure());
+ EXPECT_TRUE(cookie->IncludeForRequestURL(secure_url, options));
+ EXPECT_FALSE(cookie->IncludeForRequestURL(url, options));
+
+ // Test that http only cookies are only included if the include httponly flag
+ // is set on the cookie options.
+ options.set_include_httponly();
+ cookie.reset(
+ CanonicalCookie::Create(url, "A=2; HttpOnly", creation_time, options));
+ EXPECT_TRUE(cookie->IsHttpOnly());
+ EXPECT_TRUE(cookie->IncludeForRequestURL(url, options));
+ options.set_exclude_httponly();
+ EXPECT_FALSE(cookie->IncludeForRequestURL(url, options));
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_constants.cc b/chromium/net/cookies/cookie_constants.cc
new file mode 100644
index 00000000000..1771c20e4bd
--- /dev/null
+++ b/chromium/net/cookies/cookie_constants.cc
@@ -0,0 +1,46 @@
+// Copyright 2013 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/cookies/cookie_constants.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+
+namespace net {
+
+namespace {
+const char kPriorityLow[] = "low";
+const char kPriorityMedium[] = "medium";
+const char kPriorityHigh[] = "high";
+} // namespace
+
+NET_EXPORT const std::string CookiePriorityToString(CookiePriority priority) {
+ switch(priority) {
+ case COOKIE_PRIORITY_HIGH:
+ return kPriorityHigh;
+ case COOKIE_PRIORITY_MEDIUM:
+ return kPriorityMedium;
+ case COOKIE_PRIORITY_LOW:
+ return kPriorityLow;
+ default:
+ NOTREACHED();
+ }
+ return std::string();
+}
+
+NET_EXPORT CookiePriority StringToCookiePriority(const std::string& priority) {
+ std::string priority_comp(priority);
+ StringToLowerASCII(&priority_comp);
+
+ if (priority_comp == kPriorityHigh)
+ return COOKIE_PRIORITY_HIGH;
+ if (priority_comp == kPriorityMedium)
+ return COOKIE_PRIORITY_MEDIUM;
+ if (priority_comp == kPriorityLow)
+ return COOKIE_PRIORITY_LOW;
+
+ return COOKIE_PRIORITY_DEFAULT;
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_constants.h b/chromium/net/cookies/cookie_constants.h
new file mode 100644
index 00000000000..7e27e14657d
--- /dev/null
+++ b/chromium/net/cookies/cookie_constants.h
@@ -0,0 +1,30 @@
+// Copyright 2013 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_COOKIES_COOKIE_CONSTANTS_H_
+#define NET_COOKIES_COOKIE_CONSTANTS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+enum CookiePriority {
+ COOKIE_PRIORITY_LOW = 0,
+ COOKIE_PRIORITY_MEDIUM = 1,
+ COOKIE_PRIORITY_HIGH = 2,
+ COOKIE_PRIORITY_DEFAULT = COOKIE_PRIORITY_MEDIUM
+};
+
+// Returns the Set-Cookie header priority token corresponding to |priority|.
+NET_EXPORT const std::string CookiePriorityToString(CookiePriority priority);
+
+// Converts the Set-Cookie header priority token |priority| to a CookiePriority.
+// Defaults to COOKIE_PRIORITY_DEFAULT for empty or unrecognized strings.
+NET_EXPORT CookiePriority StringToCookiePriority(const std::string& priority);
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_CONSTANTS_H_
diff --git a/chromium/net/cookies/cookie_constants_unittest.cc b/chromium/net/cookies/cookie_constants_unittest.cc
new file mode 100644
index 00000000000..f14f11e1279
--- /dev/null
+++ b/chromium/net/cookies/cookie_constants_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright 2013 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 "base/basictypes.h"
+#include "net/cookies/cookie_constants.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(CookieConstantsTest, TestCookiePriority) {
+ // Basic cases.
+ EXPECT_EQ("low", CookiePriorityToString(COOKIE_PRIORITY_LOW));
+ EXPECT_EQ("medium", CookiePriorityToString(COOKIE_PRIORITY_MEDIUM));
+ EXPECT_EQ("high", CookiePriorityToString(COOKIE_PRIORITY_HIGH));
+
+ EXPECT_EQ(COOKIE_PRIORITY_LOW, StringToCookiePriority("low"));
+ EXPECT_EQ(COOKIE_PRIORITY_MEDIUM, StringToCookiePriority("medium"));
+ EXPECT_EQ(COOKIE_PRIORITY_HIGH, StringToCookiePriority("high"));
+
+ // Case Insensitivity of StringToCookiePriority().
+ EXPECT_EQ(COOKIE_PRIORITY_LOW, StringToCookiePriority("LOW"));
+ EXPECT_EQ(COOKIE_PRIORITY_MEDIUM, StringToCookiePriority("Medium"));
+ EXPECT_EQ(COOKIE_PRIORITY_HIGH, StringToCookiePriority("hiGH"));
+
+ // Value of default priority.
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, COOKIE_PRIORITY_MEDIUM);
+
+ // Numeric values.
+ EXPECT_LT(COOKIE_PRIORITY_LOW, COOKIE_PRIORITY_MEDIUM);
+ EXPECT_LT(COOKIE_PRIORITY_MEDIUM, COOKIE_PRIORITY_HIGH);
+
+ // Unrecognized tokens are interpreted as COOKIE_PRIORITY_DEFAULT.
+ const char* bad_tokens[] = {"", "lo", "lowerest", "high ", " high", "0"};
+ for (size_t i = 0; i < arraysize(bad_tokens); ++i) {
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, StringToCookiePriority(bad_tokens[i]));
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_monster.cc b/chromium/net/cookies/cookie_monster.cc
new file mode 100644
index 00000000000..f24637735fd
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster.cc
@@ -0,0 +1,2223 @@
+// Copyright (c) 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.
+
+// Portions of this code based on Mozilla:
+// (netwerk/cookie/src/nsCookieService.cpp)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2003
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Witte (dwitte@stanford.edu)
+ * Michiel van Leeuwen (mvl@exedo.nl)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/cookies/cookie_monster.h"
+
+#include <algorithm>
+#include <functional>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
+#include "url/gurl.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+// In steady state, most cookie requests can be satisfied by the in memory
+// cookie monster store. However, if a request comes in during the initial
+// cookie load, it must be delayed until that load completes. That is done by
+// queueing it on CookieMonster::tasks_pending_ and running it when notification
+// of cookie load completion is received via CookieMonster::OnLoaded. This
+// callback is passed to the persistent store from CookieMonster::InitStore(),
+// which is called on the first operation invoked on the CookieMonster.
+//
+// On the browser critical paths (e.g. for loading initial web pages in a
+// session restore) it may take too long to wait for the full load. If a cookie
+// request is for a specific URL, DoCookieTaskForURL is called, which triggers a
+// priority load if the key is not loaded yet by calling PersistentCookieStore
+// :: LoadCookiesForKey. The request is queued in
+// CookieMonster::tasks_pending_for_key_ and executed upon receiving
+// notification of key load completion via CookieMonster::OnKeyLoaded(). If
+// multiple requests for the same eTLD+1 are received before key load
+// completion, only the first request calls
+// PersistentCookieStore::LoadCookiesForKey, all subsequent requests are queued
+// in CookieMonster::tasks_pending_for_key_ and executed upon receiving
+// notification of key load completion triggered by the first request for the
+// same eTLD+1.
+
+static const int kMinutesInTenYears = 10 * 365 * 24 * 60;
+
+namespace net {
+
+// See comments at declaration of these variables in cookie_monster.h
+// for details.
+const size_t CookieMonster::kDomainMaxCookies = 180;
+const size_t CookieMonster::kDomainPurgeCookies = 30;
+const size_t CookieMonster::kMaxCookies = 3300;
+const size_t CookieMonster::kPurgeCookies = 300;
+
+const size_t CookieMonster::kDomainCookiesQuotaLow = 30;
+const size_t CookieMonster::kDomainCookiesQuotaMedium = 50;
+const size_t CookieMonster::kDomainCookiesQuotaHigh =
+ CookieMonster::kDomainMaxCookies - CookieMonster::kDomainPurgeCookies
+ - CookieMonster::kDomainCookiesQuotaLow
+ - CookieMonster::kDomainCookiesQuotaMedium;
+
+const int CookieMonster::kSafeFromGlobalPurgeDays = 30;
+
+namespace {
+
+typedef std::vector<CanonicalCookie*> CanonicalCookieVector;
+
+// Default minimum delay after updating a cookie's LastAccessDate before we
+// will update it again.
+const int kDefaultAccessUpdateThresholdSeconds = 60;
+
+// Comparator to sort cookies from highest creation date to lowest
+// creation date.
+struct OrderByCreationTimeDesc {
+ bool operator()(const CookieMonster::CookieMap::iterator& a,
+ const CookieMonster::CookieMap::iterator& b) const {
+ return a->second->CreationDate() > b->second->CreationDate();
+ }
+};
+
+// Constants for use in VLOG
+const int kVlogPerCookieMonster = 1;
+const int kVlogPeriodic = 3;
+const int kVlogGarbageCollection = 5;
+const int kVlogSetCookies = 7;
+const int kVlogGetCookies = 9;
+
+// Mozilla sorts on the path length (longest first), and then it
+// sorts by creation time (oldest first).
+// The RFC says the sort order for the domain attribute is undefined.
+bool CookieSorter(CanonicalCookie* cc1, CanonicalCookie* cc2) {
+ if (cc1->Path().length() == cc2->Path().length())
+ return cc1->CreationDate() < cc2->CreationDate();
+ return cc1->Path().length() > cc2->Path().length();
+}
+
+bool LRACookieSorter(const CookieMonster::CookieMap::iterator& it1,
+ const CookieMonster::CookieMap::iterator& it2) {
+ // Cookies accessed less recently should be deleted first.
+ if (it1->second->LastAccessDate() != it2->second->LastAccessDate())
+ return it1->second->LastAccessDate() < it2->second->LastAccessDate();
+
+ // In rare cases we might have two cookies with identical last access times.
+ // To preserve the stability of the sort, in these cases prefer to delete
+ // older cookies over newer ones. CreationDate() is guaranteed to be unique.
+ return it1->second->CreationDate() < it2->second->CreationDate();
+}
+
+// Our strategy to find duplicates is:
+// (1) Build a map from (cookiename, cookiepath) to
+// {list of cookies with this signature, sorted by creation time}.
+// (2) For each list with more than 1 entry, keep the cookie having the
+// most recent creation time, and delete the others.
+//
+// Two cookies are considered equivalent if they have the same domain,
+// name, and path.
+struct CookieSignature {
+ public:
+ CookieSignature(const std::string& name,
+ const std::string& domain,
+ const std::string& path)
+ : name(name), domain(domain), path(path) {
+ }
+
+ // To be a key for a map this class needs to be assignable, copyable,
+ // and have an operator<. The default assignment operator
+ // and copy constructor are exactly what we want.
+
+ bool operator<(const CookieSignature& cs) const {
+ // Name compare dominates, then domain, then path.
+ int diff = name.compare(cs.name);
+ if (diff != 0)
+ return diff < 0;
+
+ diff = domain.compare(cs.domain);
+ if (diff != 0)
+ return diff < 0;
+
+ return path.compare(cs.path) < 0;
+ }
+
+ std::string name;
+ std::string domain;
+ std::string path;
+};
+
+// Determine the cookie domain to use for setting the specified cookie.
+bool GetCookieDomain(const GURL& url,
+ const ParsedCookie& pc,
+ std::string* result) {
+ std::string domain_string;
+ if (pc.HasDomain())
+ domain_string = pc.Domain();
+ return cookie_util::GetCookieDomainWithString(url, domain_string, result);
+}
+
+// For a CookieItVector iterator range [|it_begin|, |it_end|),
+// sorts the first |num_sort| + 1 elements by LastAccessDate().
+// The + 1 element exists so for any interval of length <= |num_sort| starting
+// from |cookies_its_begin|, a LastAccessDate() bound can be found.
+void SortLeastRecentlyAccessed(
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end,
+ size_t num_sort) {
+ DCHECK_LT(static_cast<int>(num_sort), it_end - it_begin);
+ std::partial_sort(it_begin, it_begin + num_sort + 1, it_end, LRACookieSorter);
+}
+
+// Predicate to support PartitionCookieByPriority().
+struct CookiePriorityEqualsTo
+ : std::unary_function<const CookieMonster::CookieMap::iterator, bool> {
+ CookiePriorityEqualsTo(CookiePriority priority)
+ : priority_(priority) {}
+
+ bool operator()(const CookieMonster::CookieMap::iterator it) const {
+ return it->second->Priority() == priority_;
+ }
+
+ const CookiePriority priority_;
+};
+
+// For a CookieItVector iterator range [|it_begin|, |it_end|),
+// moves all cookies with a given |priority| to the beginning of the list.
+// Returns: An iterator in [it_begin, it_end) to the first element with
+// priority != |priority|, or |it_end| if all have priority == |priority|.
+CookieMonster::CookieItVector::iterator PartitionCookieByPriority(
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end,
+ CookiePriority priority) {
+ return std::partition(it_begin, it_end, CookiePriorityEqualsTo(priority));
+}
+
+bool LowerBoundAccessDateComparator(
+ const CookieMonster::CookieMap::iterator it, const Time& access_date) {
+ return it->second->LastAccessDate() < access_date;
+}
+
+// For a CookieItVector iterator range [|it_begin|, |it_end|)
+// from a CookieItVector sorted by LastAccessDate(), returns the
+// first iterator with access date >= |access_date|, or cookie_its_end if this
+// holds for all.
+CookieMonster::CookieItVector::iterator LowerBoundAccessDate(
+ const CookieMonster::CookieItVector::iterator its_begin,
+ const CookieMonster::CookieItVector::iterator its_end,
+ const Time& access_date) {
+ return std::lower_bound(its_begin, its_end, access_date,
+ LowerBoundAccessDateComparator);
+}
+
+// Mapping between DeletionCause and Delegate::ChangeCause; the mapping also
+// provides a boolean that specifies whether or not an OnCookieChanged
+// notification ought to be generated.
+typedef struct ChangeCausePair_struct {
+ CookieMonster::Delegate::ChangeCause cause;
+ bool notify;
+} ChangeCausePair;
+ChangeCausePair ChangeCauseMapping[] = {
+ // DELETE_COOKIE_EXPLICIT
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT, true },
+ // DELETE_COOKIE_OVERWRITE
+ { CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE, true },
+ // DELETE_COOKIE_EXPIRED
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED, true },
+ // DELETE_COOKIE_EVICTED
+ { CookieMonster::Delegate::CHANGE_COOKIE_EVICTED, true },
+ // DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT, false },
+ // DELETE_COOKIE_DONT_RECORD
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT, false },
+ // DELETE_COOKIE_EVICTED_DOMAIN
+ { CookieMonster::Delegate::CHANGE_COOKIE_EVICTED, true },
+ // DELETE_COOKIE_EVICTED_GLOBAL
+ { CookieMonster::Delegate::CHANGE_COOKIE_EVICTED, true },
+ // DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE
+ { CookieMonster::Delegate::CHANGE_COOKIE_EVICTED, true },
+ // DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE
+ { CookieMonster::Delegate::CHANGE_COOKIE_EVICTED, true },
+ // DELETE_COOKIE_EXPIRED_OVERWRITE
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE, true },
+ // DELETE_COOKIE_LAST_ENTRY
+ { CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT, false }
+};
+
+std::string BuildCookieLine(const CanonicalCookieVector& cookies) {
+ std::string cookie_line;
+ for (CanonicalCookieVector::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ if (it != cookies.begin())
+ cookie_line += "; ";
+ // In Mozilla if you set a cookie like AAAA, it will have an empty token
+ // and a value of AAAA. When it sends the cookie back, it will send AAAA,
+ // so we need to avoid sending =AAAA for a blank token value.
+ if (!(*it)->Name().empty())
+ cookie_line += (*it)->Name() + "=";
+ cookie_line += (*it)->Value();
+ }
+ return cookie_line;
+}
+
+} // namespace
+
+// static
+bool CookieMonster::default_enable_file_scheme_ = false;
+
+CookieMonster::CookieMonster(PersistentCookieStore* store, Delegate* delegate)
+ : initialized_(false),
+ loaded_(false),
+ store_(store),
+ last_access_threshold_(
+ TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds)),
+ delegate_(delegate),
+ last_statistic_record_time_(Time::Now()),
+ keep_expired_cookies_(false),
+ persist_session_cookies_(false),
+ priority_aware_garbage_collection_(false) {
+ InitializeHistograms();
+ SetDefaultCookieableSchemes();
+}
+
+CookieMonster::CookieMonster(PersistentCookieStore* store,
+ Delegate* delegate,
+ int last_access_threshold_milliseconds)
+ : initialized_(false),
+ loaded_(false),
+ store_(store),
+ last_access_threshold_(base::TimeDelta::FromMilliseconds(
+ last_access_threshold_milliseconds)),
+ delegate_(delegate),
+ last_statistic_record_time_(base::Time::Now()),
+ keep_expired_cookies_(false),
+ persist_session_cookies_(false),
+ priority_aware_garbage_collection_(false) {
+ InitializeHistograms();
+ SetDefaultCookieableSchemes();
+}
+
+
+// Task classes for queueing the coming request.
+
+class CookieMonster::CookieMonsterTask
+ : public base::RefCountedThreadSafe<CookieMonsterTask> {
+ public:
+ // Runs the task and invokes the client callback on the thread that
+ // originally constructed the task.
+ virtual void Run() = 0;
+
+ protected:
+ explicit CookieMonsterTask(CookieMonster* cookie_monster);
+ virtual ~CookieMonsterTask();
+
+ // Invokes the callback immediately, if the current thread is the one
+ // that originated the task, or queues the callback for execution on the
+ // appropriate thread. Maintains a reference to this CookieMonsterTask
+ // instance until the callback completes.
+ void InvokeCallback(base::Closure callback);
+
+ CookieMonster* cookie_monster() {
+ return cookie_monster_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<CookieMonsterTask>;
+
+ CookieMonster* cookie_monster_;
+ scoped_refptr<base::MessageLoopProxy> thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieMonsterTask);
+};
+
+CookieMonster::CookieMonsterTask::CookieMonsterTask(
+ CookieMonster* cookie_monster)
+ : cookie_monster_(cookie_monster),
+ thread_(base::MessageLoopProxy::current()) {
+}
+
+CookieMonster::CookieMonsterTask::~CookieMonsterTask() {}
+
+// Unfortunately, one cannot re-bind a Callback with parameters into a closure.
+// Therefore, the closure passed to InvokeCallback is a clumsy binding of
+// Callback::Run on a wrapped Callback instance. Since Callback is not
+// reference counted, we bind to an instance that is a member of the
+// CookieMonsterTask subclass. Then, we cannot simply post the callback to a
+// message loop because the underlying instance may be destroyed (along with the
+// CookieMonsterTask instance) in the interim. Therefore, we post a callback
+// bound to the CookieMonsterTask, which *is* reference counted (thus preventing
+// destruction of the original callback), and which invokes the closure (which
+// invokes the original callback with the returned data).
+void CookieMonster::CookieMonsterTask::InvokeCallback(base::Closure callback) {
+ if (thread_->BelongsToCurrentThread()) {
+ callback.Run();
+ } else {
+ thread_->PostTask(FROM_HERE, base::Bind(
+ &CookieMonster::CookieMonsterTask::InvokeCallback, this, callback));
+ }
+}
+
+// Task class for SetCookieWithDetails call.
+class CookieMonster::SetCookieWithDetailsTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ SetCookieWithDetailsTask(CookieMonster* cookie_monster,
+ const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority,
+ const CookieMonster::SetCookiesCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ name_(name),
+ value_(value),
+ domain_(domain),
+ path_(path),
+ expiration_time_(expiration_time),
+ secure_(secure),
+ http_only_(http_only),
+ priority_(priority),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~SetCookieWithDetailsTask() {}
+
+ private:
+ GURL url_;
+ std::string name_;
+ std::string value_;
+ std::string domain_;
+ std::string path_;
+ base::Time expiration_time_;
+ bool secure_;
+ bool http_only_;
+ CookiePriority priority_;
+ CookieMonster::SetCookiesCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SetCookieWithDetailsTask);
+};
+
+void CookieMonster::SetCookieWithDetailsTask::Run() {
+ bool success = this->cookie_monster()->
+ SetCookieWithDetails(url_, name_, value_, domain_, path_,
+ expiration_time_, secure_, http_only_, priority_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::SetCookiesCallback::Run,
+ base::Unretained(&callback_), success));
+ }
+}
+
+// Task class for GetAllCookies call.
+class CookieMonster::GetAllCookiesTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ GetAllCookiesTask(CookieMonster* cookie_monster,
+ const CookieMonster::GetCookieListCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~GetAllCookiesTask() {}
+
+ private:
+ CookieMonster::GetCookieListCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetAllCookiesTask);
+};
+
+void CookieMonster::GetAllCookiesTask::Run() {
+ if (!callback_.is_null()) {
+ CookieList cookies = this->cookie_monster()->GetAllCookies();
+ this->InvokeCallback(base::Bind(&CookieMonster::GetCookieListCallback::Run,
+ base::Unretained(&callback_), cookies));
+ }
+}
+
+// Task class for GetAllCookiesForURLWithOptions call.
+class CookieMonster::GetAllCookiesForURLWithOptionsTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ GetAllCookiesForURLWithOptionsTask(
+ CookieMonster* cookie_monster,
+ const GURL& url,
+ const CookieOptions& options,
+ const CookieMonster::GetCookieListCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ options_(options),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~GetAllCookiesForURLWithOptionsTask() {}
+
+ private:
+ GURL url_;
+ CookieOptions options_;
+ CookieMonster::GetCookieListCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetAllCookiesForURLWithOptionsTask);
+};
+
+void CookieMonster::GetAllCookiesForURLWithOptionsTask::Run() {
+ if (!callback_.is_null()) {
+ CookieList cookies = this->cookie_monster()->
+ GetAllCookiesForURLWithOptions(url_, options_);
+ this->InvokeCallback(base::Bind(&CookieMonster::GetCookieListCallback::Run,
+ base::Unretained(&callback_), cookies));
+ }
+}
+
+// Task class for DeleteAll call.
+class CookieMonster::DeleteAllTask : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteAllTask(CookieMonster* cookie_monster,
+ const CookieMonster::DeleteCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteAllTask() {}
+
+ private:
+ CookieMonster::DeleteCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteAllTask);
+};
+
+void CookieMonster::DeleteAllTask::Run() {
+ int num_deleted = this->cookie_monster()->DeleteAll(true);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCallback::Run,
+ base::Unretained(&callback_), num_deleted));
+ }
+}
+
+// Task class for DeleteAllCreatedBetween call.
+class CookieMonster::DeleteAllCreatedBetweenTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteAllCreatedBetweenTask(CookieMonster* cookie_monster,
+ const Time& delete_begin,
+ const Time& delete_end,
+ const CookieMonster::DeleteCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ delete_begin_(delete_begin),
+ delete_end_(delete_end),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteAllCreatedBetweenTask() {}
+
+ private:
+ Time delete_begin_;
+ Time delete_end_;
+ CookieMonster::DeleteCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteAllCreatedBetweenTask);
+};
+
+void CookieMonster::DeleteAllCreatedBetweenTask::Run() {
+ int num_deleted = this->cookie_monster()->
+ DeleteAllCreatedBetween(delete_begin_, delete_end_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCallback::Run,
+ base::Unretained(&callback_), num_deleted));
+ }
+}
+
+// Task class for DeleteAllForHost call.
+class CookieMonster::DeleteAllForHostTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteAllForHostTask(CookieMonster* cookie_monster,
+ const GURL& url,
+ const CookieMonster::DeleteCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteAllForHostTask() {}
+
+ private:
+ GURL url_;
+ CookieMonster::DeleteCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteAllForHostTask);
+};
+
+void CookieMonster::DeleteAllForHostTask::Run() {
+ int num_deleted = this->cookie_monster()->DeleteAllForHost(url_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCallback::Run,
+ base::Unretained(&callback_), num_deleted));
+ }
+}
+
+// Task class for DeleteAllCreatedBetweenForHost call.
+class CookieMonster::DeleteAllCreatedBetweenForHostTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteAllCreatedBetweenForHostTask(
+ CookieMonster* cookie_monster,
+ Time delete_begin,
+ Time delete_end,
+ const GURL& url,
+ const CookieMonster::DeleteCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ delete_begin_(delete_begin),
+ delete_end_(delete_end),
+ url_(url),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteAllCreatedBetweenForHostTask() {}
+
+ private:
+ Time delete_begin_;
+ Time delete_end_;
+ GURL url_;
+ CookieMonster::DeleteCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteAllCreatedBetweenForHostTask);
+};
+
+void CookieMonster::DeleteAllCreatedBetweenForHostTask::Run() {
+ int num_deleted = this->cookie_monster()->DeleteAllCreatedBetweenForHost(
+ delete_begin_, delete_end_, url_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCallback::Run,
+ base::Unretained(&callback_), num_deleted));
+ }
+}
+
+// Task class for DeleteCanonicalCookie call.
+class CookieMonster::DeleteCanonicalCookieTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteCanonicalCookieTask(CookieMonster* cookie_monster,
+ const CanonicalCookie& cookie,
+ const CookieMonster::DeleteCookieCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ cookie_(cookie),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteCanonicalCookieTask() {}
+
+ private:
+ CanonicalCookie cookie_;
+ CookieMonster::DeleteCookieCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteCanonicalCookieTask);
+};
+
+void CookieMonster::DeleteCanonicalCookieTask::Run() {
+ bool result = this->cookie_monster()->DeleteCanonicalCookie(cookie_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCookieCallback::Run,
+ base::Unretained(&callback_), result));
+ }
+}
+
+// Task class for SetCookieWithOptions call.
+class CookieMonster::SetCookieWithOptionsTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ SetCookieWithOptionsTask(CookieMonster* cookie_monster,
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const CookieMonster::SetCookiesCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ cookie_line_(cookie_line),
+ options_(options),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~SetCookieWithOptionsTask() {}
+
+ private:
+ GURL url_;
+ std::string cookie_line_;
+ CookieOptions options_;
+ CookieMonster::SetCookiesCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SetCookieWithOptionsTask);
+};
+
+void CookieMonster::SetCookieWithOptionsTask::Run() {
+ bool result = this->cookie_monster()->
+ SetCookieWithOptions(url_, cookie_line_, options_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::SetCookiesCallback::Run,
+ base::Unretained(&callback_), result));
+ }
+}
+
+// Task class for GetCookiesWithOptions call.
+class CookieMonster::GetCookiesWithOptionsTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ GetCookiesWithOptionsTask(CookieMonster* cookie_monster,
+ const GURL& url,
+ const CookieOptions& options,
+ const CookieMonster::GetCookiesCallback& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ options_(options),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~GetCookiesWithOptionsTask() {}
+
+ private:
+ GURL url_;
+ CookieOptions options_;
+ CookieMonster::GetCookiesCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetCookiesWithOptionsTask);
+};
+
+void CookieMonster::GetCookiesWithOptionsTask::Run() {
+ std::string cookie = this->cookie_monster()->
+ GetCookiesWithOptions(url_, options_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::GetCookiesCallback::Run,
+ base::Unretained(&callback_), cookie));
+ }
+}
+
+// Task class for DeleteCookie call.
+class CookieMonster::DeleteCookieTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteCookieTask(CookieMonster* cookie_monster,
+ const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback)
+ : CookieMonsterTask(cookie_monster),
+ url_(url),
+ cookie_name_(cookie_name),
+ callback_(callback) { }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteCookieTask() {}
+
+ private:
+ GURL url_;
+ std::string cookie_name_;
+ base::Closure callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteCookieTask);
+};
+
+void CookieMonster::DeleteCookieTask::Run() {
+ this->cookie_monster()->DeleteCookie(url_, cookie_name_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(callback_);
+ }
+}
+
+// Task class for DeleteSessionCookies call.
+class CookieMonster::DeleteSessionCookiesTask
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ DeleteSessionCookiesTask(CookieMonster* cookie_monster,
+ const CookieMonster::DeleteCallback& callback)
+ : CookieMonsterTask(cookie_monster), callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~DeleteSessionCookiesTask() {}
+
+ private:
+ CookieMonster::DeleteCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteSessionCookiesTask);
+};
+
+void CookieMonster::DeleteSessionCookiesTask::Run() {
+ int num_deleted = this->cookie_monster()->DeleteSessionCookies();
+ if (!callback_.is_null()) {
+ this->InvokeCallback(base::Bind(&CookieMonster::DeleteCallback::Run,
+ base::Unretained(&callback_), num_deleted));
+ }
+}
+
+// Task class for HasCookiesForETLDP1Task call.
+class CookieMonster::HasCookiesForETLDP1Task
+ : public CookieMonster::CookieMonsterTask {
+ public:
+ HasCookiesForETLDP1Task(
+ CookieMonster* cookie_monster,
+ const std::string& etldp1,
+ const CookieMonster::HasCookiesForETLDP1Callback& callback)
+ : CookieMonsterTask(cookie_monster),
+ etldp1_(etldp1),
+ callback_(callback) {
+ }
+
+ // CookieMonster::CookieMonsterTask:
+ virtual void Run() OVERRIDE;
+
+ protected:
+ virtual ~HasCookiesForETLDP1Task() {}
+
+ private:
+ std::string etldp1_;
+ CookieMonster::HasCookiesForETLDP1Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(HasCookiesForETLDP1Task);
+};
+
+void CookieMonster::HasCookiesForETLDP1Task::Run() {
+ bool result = this->cookie_monster()->HasCookiesForETLDP1(etldp1_);
+ if (!callback_.is_null()) {
+ this->InvokeCallback(
+ base::Bind(&CookieMonster::HasCookiesForETLDP1Callback::Run,
+ base::Unretained(&callback_), result));
+ }
+}
+
+// Asynchronous CookieMonster API
+
+void CookieMonster::SetCookieWithDetailsAsync(
+ const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority,
+ const SetCookiesCallback& callback) {
+ scoped_refptr<SetCookieWithDetailsTask> task =
+ new SetCookieWithDetailsTask(this, url, name, value, domain, path,
+ expiration_time, secure, http_only, priority,
+ callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::GetAllCookiesAsync(const GetCookieListCallback& callback) {
+ scoped_refptr<GetAllCookiesTask> task =
+ new GetAllCookiesTask(this, callback);
+
+ DoCookieTask(task);
+}
+
+
+void CookieMonster::GetAllCookiesForURLWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookieListCallback& callback) {
+ scoped_refptr<GetAllCookiesForURLWithOptionsTask> task =
+ new GetAllCookiesForURLWithOptionsTask(this, url, options, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::GetAllCookiesForURLAsync(
+ const GURL& url, const GetCookieListCallback& callback) {
+ CookieOptions options;
+ options.set_include_httponly();
+ scoped_refptr<GetAllCookiesForURLWithOptionsTask> task =
+ new GetAllCookiesForURLWithOptionsTask(this, url, options, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::HasCookiesForETLDP1Async(
+ const std::string& etldp1,
+ const HasCookiesForETLDP1Callback& callback) {
+ scoped_refptr<HasCookiesForETLDP1Task> task =
+ new HasCookiesForETLDP1Task(this, etldp1, callback);
+
+ DoCookieTaskForURL(task, GURL("http://" + etldp1));
+}
+
+void CookieMonster::DeleteAllAsync(const DeleteCallback& callback) {
+ scoped_refptr<DeleteAllTask> task =
+ new DeleteAllTask(this, callback);
+
+ DoCookieTask(task);
+}
+
+void CookieMonster::DeleteAllCreatedBetweenAsync(
+ const Time& delete_begin, const Time& delete_end,
+ const DeleteCallback& callback) {
+ scoped_refptr<DeleteAllCreatedBetweenTask> task =
+ new DeleteAllCreatedBetweenTask(this, delete_begin, delete_end,
+ callback);
+
+ DoCookieTask(task);
+}
+
+void CookieMonster::DeleteAllCreatedBetweenForHostAsync(
+ const Time delete_begin,
+ const Time delete_end,
+ const GURL& url,
+ const DeleteCallback& callback) {
+ scoped_refptr<DeleteAllCreatedBetweenForHostTask> task =
+ new DeleteAllCreatedBetweenForHostTask(
+ this, delete_begin, delete_end, url, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::DeleteAllForHostAsync(
+ const GURL& url, const DeleteCallback& callback) {
+ scoped_refptr<DeleteAllForHostTask> task =
+ new DeleteAllForHostTask(this, url, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::DeleteCanonicalCookieAsync(
+ const CanonicalCookie& cookie,
+ const DeleteCookieCallback& callback) {
+ scoped_refptr<DeleteCanonicalCookieTask> task =
+ new DeleteCanonicalCookieTask(this, cookie, callback);
+
+ DoCookieTask(task);
+}
+
+void CookieMonster::SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const SetCookiesCallback& callback) {
+ scoped_refptr<SetCookieWithOptionsTask> task =
+ new SetCookieWithOptionsTask(this, url, cookie_line, options, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookiesCallback& callback) {
+ scoped_refptr<GetCookiesWithOptionsTask> task =
+ new GetCookiesWithOptionsTask(this, url, options, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) {
+ scoped_refptr<DeleteCookieTask> task =
+ new DeleteCookieTask(this, url, cookie_name, callback);
+
+ DoCookieTaskForURL(task, url);
+}
+
+void CookieMonster::DeleteSessionCookiesAsync(
+ const CookieStore::DeleteCallback& callback) {
+ scoped_refptr<DeleteSessionCookiesTask> task =
+ new DeleteSessionCookiesTask(this, callback);
+
+ DoCookieTask(task);
+}
+
+void CookieMonster::DoCookieTask(
+ const scoped_refptr<CookieMonsterTask>& task_item) {
+ {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+ if (!loaded_) {
+ tasks_pending_.push(task_item);
+ return;
+ }
+ }
+
+ task_item->Run();
+}
+
+void CookieMonster::DoCookieTaskForURL(
+ const scoped_refptr<CookieMonsterTask>& task_item,
+ const GURL& url) {
+ {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+ // If cookies for the requested domain key (eTLD+1) have been loaded from DB
+ // then run the task, otherwise load from DB.
+ if (!loaded_) {
+ // Checks if the domain key has been loaded.
+ std::string key(cookie_util::GetEffectiveDomain(url.scheme(),
+ url.host()));
+ if (keys_loaded_.find(key) == keys_loaded_.end()) {
+ std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > >
+ ::iterator it = tasks_pending_for_key_.find(key);
+ if (it == tasks_pending_for_key_.end()) {
+ store_->LoadCookiesForKey(key,
+ base::Bind(&CookieMonster::OnKeyLoaded, this, key));
+ it = tasks_pending_for_key_.insert(std::make_pair(key,
+ std::deque<scoped_refptr<CookieMonsterTask> >())).first;
+ }
+ it->second.push_back(task_item);
+ return;
+ }
+ }
+ }
+ task_item->Run();
+}
+
+bool CookieMonster::SetCookieWithDetails(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority) {
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url))
+ return false;
+
+ Time creation_time = CurrentTime();
+ last_time_seen_ = creation_time;
+
+ scoped_ptr<CanonicalCookie> cc;
+ cc.reset(CanonicalCookie::Create(url, name, value, domain, path,
+ creation_time, expiration_time,
+ secure, http_only, priority));
+
+ if (!cc.get())
+ return false;
+
+ CookieOptions options;
+ options.set_include_httponly();
+ return SetCanonicalCookie(&cc, creation_time, options);
+}
+
+bool CookieMonster::InitializeFrom(const CookieList& list) {
+ base::AutoLock autolock(lock_);
+ InitIfNecessary();
+ for (net::CookieList::const_iterator iter = list.begin();
+ iter != list.end(); ++iter) {
+ scoped_ptr<CanonicalCookie> cookie(new CanonicalCookie(*iter));
+ net::CookieOptions options;
+ options.set_include_httponly();
+ if (!SetCanonicalCookie(&cookie, cookie->CreationDate(), options))
+ return false;
+ }
+ return true;
+}
+
+CookieList CookieMonster::GetAllCookies() {
+ base::AutoLock autolock(lock_);
+
+ // This function is being called to scrape the cookie list for management UI
+ // or similar. We shouldn't show expired cookies in this list since it will
+ // just be confusing to users, and this function is called rarely enough (and
+ // is already slow enough) that it's OK to take the time to garbage collect
+ // the expired cookies now.
+ //
+ // Note that this does not prune cookies to be below our limits (if we've
+ // exceeded them) the way that calling GarbageCollect() would.
+ GarbageCollectExpired(Time::Now(),
+ CookieMapItPair(cookies_.begin(), cookies_.end()),
+ NULL);
+
+ // Copy the CanonicalCookie pointers from the map so that we can use the same
+ // sorter as elsewhere, then copy the result out.
+ std::vector<CanonicalCookie*> cookie_ptrs;
+ cookie_ptrs.reserve(cookies_.size());
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end(); ++it)
+ cookie_ptrs.push_back(it->second);
+ std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter);
+
+ CookieList cookie_list;
+ cookie_list.reserve(cookie_ptrs.size());
+ for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin();
+ it != cookie_ptrs.end(); ++it)
+ cookie_list.push_back(**it);
+
+ return cookie_list;
+}
+
+CookieList CookieMonster::GetAllCookiesForURLWithOptions(
+ const GURL& url,
+ const CookieOptions& options) {
+ base::AutoLock autolock(lock_);
+
+ std::vector<CanonicalCookie*> cookie_ptrs;
+ FindCookiesForHostAndDomain(url, options, false, &cookie_ptrs);
+ std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter);
+
+ CookieList cookies;
+ for (std::vector<CanonicalCookie*>::const_iterator it = cookie_ptrs.begin();
+ it != cookie_ptrs.end(); it++)
+ cookies.push_back(**it);
+
+ return cookies;
+}
+
+CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) {
+ CookieOptions options;
+ options.set_include_httponly();
+
+ return GetAllCookiesForURLWithOptions(url, options);
+}
+
+int CookieMonster::DeleteAll(bool sync_to_store) {
+ base::AutoLock autolock(lock_);
+
+ int num_deleted = 0;
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
+ CookieMap::iterator curit = it;
+ ++it;
+ InternalDeleteCookie(curit, sync_to_store,
+ sync_to_store ? DELETE_COOKIE_EXPLICIT :
+ DELETE_COOKIE_DONT_RECORD /* Destruction. */);
+ ++num_deleted;
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAllCreatedBetween(const Time& delete_begin,
+ const Time& delete_end) {
+ base::AutoLock autolock(lock_);
+
+ int num_deleted = 0;
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
+ CookieMap::iterator curit = it;
+ CanonicalCookie* cc = curit->second;
+ ++it;
+
+ if (cc->CreationDate() >= delete_begin &&
+ (delete_end.is_null() || cc->CreationDate() < delete_end)) {
+ InternalDeleteCookie(curit,
+ true, /*sync_to_store*/
+ DELETE_COOKIE_EXPLICIT);
+ ++num_deleted;
+ }
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAllCreatedBetweenForHost(const Time delete_begin,
+ const Time delete_end,
+ const GURL& url) {
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url))
+ return 0;
+
+ const std::string host(url.host());
+
+ // We store host cookies in the store by their canonical host name;
+ // domain cookies are stored with a leading ".". So this is a pretty
+ // simple lookup and per-cookie delete.
+ int num_deleted = 0;
+ for (CookieMapItPair its = cookies_.equal_range(GetKey(host));
+ its.first != its.second;) {
+ CookieMap::iterator curit = its.first;
+ ++its.first;
+
+ const CanonicalCookie* const cc = curit->second;
+
+ // Delete only on a match as a host cookie.
+ if (cc->IsHostCookie() && cc->IsDomainMatch(host) &&
+ cc->CreationDate() >= delete_begin &&
+ // The assumption that null |delete_end| is equivalent to
+ // Time::Max() is confusing.
+ (delete_end.is_null() || cc->CreationDate() < delete_end)) {
+ num_deleted++;
+
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
+ }
+ }
+ return num_deleted;
+}
+
+int CookieMonster::DeleteAllForHost(const GURL& url) {
+ return DeleteAllCreatedBetweenForHost(Time(), Time::Max(), url);
+}
+
+
+bool CookieMonster::DeleteCanonicalCookie(const CanonicalCookie& cookie) {
+ base::AutoLock autolock(lock_);
+
+ for (CookieMapItPair its = cookies_.equal_range(GetKey(cookie.Domain()));
+ its.first != its.second; ++its.first) {
+ // The creation date acts as our unique index...
+ if (its.first->second->CreationDate() == cookie.CreationDate()) {
+ InternalDeleteCookie(its.first, true, DELETE_COOKIE_EXPLICIT);
+ return true;
+ }
+ }
+ return false;
+}
+
+void CookieMonster::SetCookieableSchemes(const char* schemes[],
+ size_t num_schemes) {
+ base::AutoLock autolock(lock_);
+
+ // Cookieable Schemes must be set before first use of function.
+ DCHECK(!initialized_);
+
+ cookieable_schemes_.clear();
+ cookieable_schemes_.insert(cookieable_schemes_.end(),
+ schemes, schemes + num_schemes);
+}
+
+void CookieMonster::SetEnableFileScheme(bool accept) {
+ // This assumes "file" is always at the end of the array. See the comment
+ // above kDefaultCookieableSchemes.
+ int num_schemes = accept ? kDefaultCookieableSchemesCount :
+ kDefaultCookieableSchemesCount - 1;
+ SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes);
+}
+
+void CookieMonster::SetKeepExpiredCookies() {
+ keep_expired_cookies_ = true;
+}
+
+// static
+void CookieMonster::EnableFileScheme() {
+ default_enable_file_scheme_ = true;
+}
+
+void CookieMonster::FlushStore(const base::Closure& callback) {
+ base::AutoLock autolock(lock_);
+ if (initialized_ && store_.get())
+ store_->Flush(callback);
+ else if (!callback.is_null())
+ base::MessageLoop::current()->PostTask(FROM_HERE, callback);
+}
+
+bool CookieMonster::SetCookieWithOptions(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options) {
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url)) {
+ return false;
+ }
+
+ return SetCookieWithCreationTimeAndOptions(url, cookie_line, Time(), options);
+}
+
+std::string CookieMonster::GetCookiesWithOptions(const GURL& url,
+ const CookieOptions& options) {
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url))
+ return std::string();
+
+ TimeTicks start_time(TimeTicks::Now());
+
+ std::vector<CanonicalCookie*> cookies;
+ FindCookiesForHostAndDomain(url, options, true, &cookies);
+ std::sort(cookies.begin(), cookies.end(), CookieSorter);
+
+ std::string cookie_line = BuildCookieLine(cookies);
+
+ histogram_time_get_->AddTime(TimeTicks::Now() - start_time);
+
+ VLOG(kVlogGetCookies) << "GetCookies() result: " << cookie_line;
+
+ return cookie_line;
+}
+
+void CookieMonster::DeleteCookie(const GURL& url,
+ const std::string& cookie_name) {
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url))
+ return;
+
+ CookieOptions options;
+ options.set_include_httponly();
+ // Get the cookies for this host and its domain(s).
+ std::vector<CanonicalCookie*> cookies;
+ FindCookiesForHostAndDomain(url, options, true, &cookies);
+ std::set<CanonicalCookie*> matching_cookies;
+
+ for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ if ((*it)->Name() != cookie_name)
+ continue;
+ if (url.path().find((*it)->Path()))
+ continue;
+ matching_cookies.insert(*it);
+ }
+
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
+ CookieMap::iterator curit = it;
+ ++it;
+ if (matching_cookies.find(curit->second) != matching_cookies.end()) {
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPLICIT);
+ }
+ }
+}
+
+int CookieMonster::DeleteSessionCookies() {
+ base::AutoLock autolock(lock_);
+
+ int num_deleted = 0;
+ for (CookieMap::iterator it = cookies_.begin(); it != cookies_.end();) {
+ CookieMap::iterator curit = it;
+ CanonicalCookie* cc = curit->second;
+ ++it;
+
+ if (!cc->IsPersistent()) {
+ InternalDeleteCookie(curit,
+ true, /*sync_to_store*/
+ DELETE_COOKIE_EXPIRED);
+ ++num_deleted;
+ }
+ }
+
+ return num_deleted;
+}
+
+bool CookieMonster::HasCookiesForETLDP1(const std::string& etldp1) {
+ base::AutoLock autolock(lock_);
+
+ const std::string key(GetKey(etldp1));
+
+ CookieMapItPair its = cookies_.equal_range(key);
+ return its.first != its.second;
+}
+
+CookieMonster* CookieMonster::GetCookieMonster() {
+ return this;
+}
+
+// This function must be called before the CookieMonster is used.
+void CookieMonster::SetPersistSessionCookies(bool persist_session_cookies) {
+ DCHECK(!initialized_);
+ persist_session_cookies_ = persist_session_cookies;
+}
+
+// This function must be called before the CookieMonster is used.
+void CookieMonster::SetPriorityAwareGarbageCollection(
+ bool priority_aware_garbage_collection) {
+ DCHECK(!initialized_);
+ priority_aware_garbage_collection_ = priority_aware_garbage_collection;
+}
+
+void CookieMonster::SetForceKeepSessionState() {
+ if (store_.get()) {
+ store_->SetForceKeepSessionState();
+ }
+}
+
+CookieMonster::~CookieMonster() {
+ DeleteAll(false);
+}
+
+bool CookieMonster::SetCookieWithCreationTime(const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& creation_time) {
+ DCHECK(!store_.get()) << "This method is only to be used by unit-tests.";
+ base::AutoLock autolock(lock_);
+
+ if (!HasCookieableScheme(url)) {
+ return false;
+ }
+
+ InitIfNecessary();
+ return SetCookieWithCreationTimeAndOptions(url, cookie_line, creation_time,
+ CookieOptions());
+}
+
+void CookieMonster::InitStore() {
+ DCHECK(store_.get()) << "Store must exist to initialize";
+
+ // We bind in the current time so that we can report the wall-clock time for
+ // loading cookies.
+ store_->Load(base::Bind(&CookieMonster::OnLoaded, this, TimeTicks::Now()));
+}
+
+void CookieMonster::OnLoaded(TimeTicks beginning_time,
+ const std::vector<CanonicalCookie*>& cookies) {
+ StoreLoadedCookies(cookies);
+ histogram_time_blocked_on_load_->AddTime(TimeTicks::Now() - beginning_time);
+
+ // Invoke the task queue of cookie request.
+ InvokeQueue();
+}
+
+void CookieMonster::OnKeyLoaded(const std::string& key,
+ const std::vector<CanonicalCookie*>& cookies) {
+ // This function does its own separate locking.
+ StoreLoadedCookies(cookies);
+
+ std::deque<scoped_refptr<CookieMonsterTask> > tasks_pending_for_key;
+ {
+ base::AutoLock autolock(lock_);
+ keys_loaded_.insert(key);
+ std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > >
+ ::iterator it = tasks_pending_for_key_.find(key);
+ if (it == tasks_pending_for_key_.end())
+ return;
+ it->second.swap(tasks_pending_for_key);
+ tasks_pending_for_key_.erase(it);
+ }
+
+ while (!tasks_pending_for_key.empty()) {
+ scoped_refptr<CookieMonsterTask> task = tasks_pending_for_key.front();
+ task->Run();
+ tasks_pending_for_key.pop_front();
+ }
+}
+
+void CookieMonster::StoreLoadedCookies(
+ const std::vector<CanonicalCookie*>& cookies) {
+ // Initialize the store and sync in any saved persistent cookies. We don't
+ // care if it's expired, insert it so it can be garbage collected, removed,
+ // and sync'd.
+ base::AutoLock autolock(lock_);
+
+ for (std::vector<CanonicalCookie*>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ int64 cookie_creation_time = (*it)->CreationDate().ToInternalValue();
+
+ if (creation_times_.insert(cookie_creation_time).second) {
+ InternalInsertCookie(GetKey((*it)->Domain()), *it, false);
+ const Time cookie_access_time((*it)->LastAccessDate());
+ if (earliest_access_time_.is_null() ||
+ cookie_access_time < earliest_access_time_)
+ earliest_access_time_ = cookie_access_time;
+ } else {
+ LOG(ERROR) << base::StringPrintf("Found cookies with duplicate creation "
+ "times in backing store: "
+ "{name='%s', domain='%s', path='%s'}",
+ (*it)->Name().c_str(),
+ (*it)->Domain().c_str(),
+ (*it)->Path().c_str());
+ // We've been given ownership of the cookie and are throwing it
+ // away; reclaim the space.
+ delete (*it);
+ }
+ }
+
+ // After importing cookies from the PersistentCookieStore, verify that
+ // none of our other constraints are violated.
+ // In particular, the backing store might have given us duplicate cookies.
+
+ // This method could be called multiple times due to priority loading, thus
+ // cookies loaded in previous runs will be validated again, but this is OK
+ // since they are expected to be much fewer than total DB.
+ EnsureCookiesMapIsValid();
+}
+
+void CookieMonster::InvokeQueue() {
+ while (true) {
+ scoped_refptr<CookieMonsterTask> request_task;
+ {
+ base::AutoLock autolock(lock_);
+ if (tasks_pending_.empty()) {
+ loaded_ = true;
+ creation_times_.clear();
+ keys_loaded_.clear();
+ break;
+ }
+ request_task = tasks_pending_.front();
+ tasks_pending_.pop();
+ }
+ request_task->Run();
+ }
+}
+
+void CookieMonster::EnsureCookiesMapIsValid() {
+ lock_.AssertAcquired();
+
+ int num_duplicates_trimmed = 0;
+
+ // Iterate through all the of the cookies, grouped by host.
+ CookieMap::iterator prev_range_end = cookies_.begin();
+ while (prev_range_end != cookies_.end()) {
+ CookieMap::iterator cur_range_begin = prev_range_end;
+ const std::string key = cur_range_begin->first; // Keep a copy.
+ CookieMap::iterator cur_range_end = cookies_.upper_bound(key);
+ prev_range_end = cur_range_end;
+
+ // Ensure no equivalent cookies for this host.
+ num_duplicates_trimmed +=
+ TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end);
+ }
+
+ // Record how many duplicates were found in the database.
+ // See InitializeHistograms() for details.
+ histogram_cookie_deletion_cause_->Add(num_duplicates_trimmed);
+}
+
+int CookieMonster::TrimDuplicateCookiesForKey(
+ const std::string& key,
+ CookieMap::iterator begin,
+ CookieMap::iterator end) {
+ lock_.AssertAcquired();
+
+ // Set of cookies ordered by creation time.
+ typedef std::set<CookieMap::iterator, OrderByCreationTimeDesc> CookieSet;
+
+ // Helper map we populate to find the duplicates.
+ typedef std::map<CookieSignature, CookieSet> EquivalenceMap;
+ EquivalenceMap equivalent_cookies;
+
+ // The number of duplicate cookies that have been found.
+ int num_duplicates = 0;
+
+ // Iterate through all of the cookies in our range, and insert them into
+ // the equivalence map.
+ for (CookieMap::iterator it = begin; it != end; ++it) {
+ DCHECK_EQ(key, it->first);
+ CanonicalCookie* cookie = it->second;
+
+ CookieSignature signature(cookie->Name(), cookie->Domain(),
+ cookie->Path());
+ CookieSet& set = equivalent_cookies[signature];
+
+ // We found a duplicate!
+ if (!set.empty())
+ num_duplicates++;
+
+ // We save the iterator into |cookies_| rather than the actual cookie
+ // pointer, since we may need to delete it later.
+ bool insert_success = set.insert(it).second;
+ DCHECK(insert_success) <<
+ "Duplicate creation times found in duplicate cookie name scan.";
+ }
+
+ // If there were no duplicates, we are done!
+ if (num_duplicates == 0)
+ return 0;
+
+ // Make sure we find everything below that we did above.
+ int num_duplicates_found = 0;
+
+ // Otherwise, delete all the duplicate cookies, both from our in-memory store
+ // and from the backing store.
+ for (EquivalenceMap::iterator it = equivalent_cookies.begin();
+ it != equivalent_cookies.end();
+ ++it) {
+ const CookieSignature& signature = it->first;
+ CookieSet& dupes = it->second;
+
+ if (dupes.size() <= 1)
+ continue; // This cookiename/path has no duplicates.
+ num_duplicates_found += dupes.size() - 1;
+
+ // Since |dups| is sorted by creation time (descending), the first cookie
+ // is the most recent one, so we will keep it. The rest are duplicates.
+ dupes.erase(dupes.begin());
+
+ LOG(ERROR) << base::StringPrintf(
+ "Found %d duplicate cookies for host='%s', "
+ "with {name='%s', domain='%s', path='%s'}",
+ static_cast<int>(dupes.size()),
+ key.c_str(),
+ signature.name.c_str(),
+ signature.domain.c_str(),
+ signature.path.c_str());
+
+ // Remove all the cookies identified by |dupes|. It is valid to delete our
+ // list of iterators one at a time, since |cookies_| is a multimap (they
+ // don't invalidate existing iterators following deletion).
+ for (CookieSet::iterator dupes_it = dupes.begin();
+ dupes_it != dupes.end();
+ ++dupes_it) {
+ InternalDeleteCookie(*dupes_it, true,
+ DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE);
+ }
+ }
+ DCHECK_EQ(num_duplicates, num_duplicates_found);
+
+ return num_duplicates;
+}
+
+// Note: file must be the last scheme.
+const char* CookieMonster::kDefaultCookieableSchemes[] =
+ { "http", "https", "file" };
+const int CookieMonster::kDefaultCookieableSchemesCount =
+ arraysize(CookieMonster::kDefaultCookieableSchemes);
+
+void CookieMonster::SetDefaultCookieableSchemes() {
+ int num_schemes = default_enable_file_scheme_ ?
+ kDefaultCookieableSchemesCount : kDefaultCookieableSchemesCount - 1;
+ SetCookieableSchemes(kDefaultCookieableSchemes, num_schemes);
+}
+
+void CookieMonster::FindCookiesForHostAndDomain(
+ const GURL& url,
+ const CookieOptions& options,
+ bool update_access_time,
+ std::vector<CanonicalCookie*>* cookies) {
+ lock_.AssertAcquired();
+
+ const Time current_time(CurrentTime());
+
+ // Probe to save statistics relatively frequently. We do it here rather
+ // than in the set path as many websites won't set cookies, and we
+ // want to collect statistics whenever the browser's being used.
+ RecordPeriodicStats(current_time);
+
+ // Can just dispatch to FindCookiesForKey
+ const std::string key(GetKey(url.host()));
+ FindCookiesForKey(key, url, options, current_time,
+ update_access_time, cookies);
+}
+
+void CookieMonster::FindCookiesForKey(const std::string& key,
+ const GURL& url,
+ const CookieOptions& options,
+ const Time& current,
+ bool update_access_time,
+ std::vector<CanonicalCookie*>* cookies) {
+ lock_.AssertAcquired();
+
+ for (CookieMapItPair its = cookies_.equal_range(key);
+ its.first != its.second; ) {
+ CookieMap::iterator curit = its.first;
+ CanonicalCookie* cc = curit->second;
+ ++its.first;
+
+ // If the cookie is expired, delete it.
+ if (cc->IsExpired(current) && !keep_expired_cookies_) {
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
+ continue;
+ }
+
+ // Filter out cookies that should not be included for a request to the
+ // given |url|. HTTP only cookies are filtered depending on the passed
+ // cookie |options|.
+ if (!cc->IncludeForRequestURL(url, options))
+ continue;
+
+ // Add this cookie to the set of matching cookies. Update the access
+ // time if we've been requested to do so.
+ if (update_access_time) {
+ InternalUpdateCookieAccessTime(cc, current);
+ }
+ cookies->push_back(cc);
+ }
+}
+
+bool CookieMonster::DeleteAnyEquivalentCookie(const std::string& key,
+ const CanonicalCookie& ecc,
+ bool skip_httponly,
+ bool already_expired) {
+ lock_.AssertAcquired();
+
+ bool found_equivalent_cookie = false;
+ bool skipped_httponly = false;
+ for (CookieMapItPair its = cookies_.equal_range(key);
+ its.first != its.second; ) {
+ CookieMap::iterator curit = its.first;
+ CanonicalCookie* cc = curit->second;
+ ++its.first;
+
+ if (ecc.IsEquivalent(*cc)) {
+ // We should never have more than one equivalent cookie, since they should
+ // overwrite each other.
+ CHECK(!found_equivalent_cookie) <<
+ "Duplicate equivalent cookies found, cookie store is corrupted.";
+ if (skip_httponly && cc->IsHttpOnly()) {
+ skipped_httponly = true;
+ } else {
+ InternalDeleteCookie(curit, true, already_expired ?
+ DELETE_COOKIE_EXPIRED_OVERWRITE : DELETE_COOKIE_OVERWRITE);
+ }
+ found_equivalent_cookie = true;
+ }
+ }
+ return skipped_httponly;
+}
+
+void CookieMonster::InternalInsertCookie(const std::string& key,
+ CanonicalCookie* cc,
+ bool sync_to_store) {
+ lock_.AssertAcquired();
+
+ if ((cc->IsPersistent() || persist_session_cookies_) && store_.get() &&
+ sync_to_store)
+ store_->AddCookie(*cc);
+ cookies_.insert(CookieMap::value_type(key, cc));
+ if (delegate_.get()) {
+ delegate_->OnCookieChanged(
+ *cc, false, CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT);
+ }
+}
+
+bool CookieMonster::SetCookieWithCreationTimeAndOptions(
+ const GURL& url,
+ const std::string& cookie_line,
+ const Time& creation_time_or_null,
+ const CookieOptions& options) {
+ lock_.AssertAcquired();
+
+ VLOG(kVlogSetCookies) << "SetCookie() line: " << cookie_line;
+
+ Time creation_time = creation_time_or_null;
+ if (creation_time.is_null()) {
+ creation_time = CurrentTime();
+ last_time_seen_ = creation_time;
+ }
+
+ scoped_ptr<CanonicalCookie> cc(
+ CanonicalCookie::Create(url, cookie_line, creation_time, options));
+
+ if (!cc.get()) {
+ VLOG(kVlogSetCookies) << "WARNING: Failed to allocate CanonicalCookie";
+ return false;
+ }
+ return SetCanonicalCookie(&cc, creation_time, options);
+}
+
+bool CookieMonster::SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc,
+ const Time& creation_time,
+ const CookieOptions& options) {
+ const std::string key(GetKey((*cc)->Domain()));
+ bool already_expired = (*cc)->IsExpired(creation_time);
+ if (DeleteAnyEquivalentCookie(key, **cc, options.exclude_httponly(),
+ already_expired)) {
+ VLOG(kVlogSetCookies) << "SetCookie() not clobbering httponly cookie";
+ return false;
+ }
+
+ VLOG(kVlogSetCookies) << "SetCookie() key: " << key << " cc: "
+ << (*cc)->DebugString();
+
+ // Realize that we might be setting an expired cookie, and the only point
+ // was to delete the cookie which we've already done.
+ if (!already_expired || keep_expired_cookies_) {
+ // See InitializeHistograms() for details.
+ if ((*cc)->IsPersistent()) {
+ histogram_expiration_duration_minutes_->Add(
+ ((*cc)->ExpiryDate() - creation_time).InMinutes());
+ }
+
+ InternalInsertCookie(key, cc->release(), true);
+ } else {
+ VLOG(kVlogSetCookies) << "SetCookie() not storing already expired cookie.";
+ }
+
+ // We assume that hopefully setting a cookie will be less common than
+ // querying a cookie. Since setting a cookie can put us over our limits,
+ // make sure that we garbage collect... We can also make the assumption that
+ // if a cookie was set, in the common case it will be used soon after,
+ // and we will purge the expired cookies in GetCookies().
+ GarbageCollect(creation_time, key);
+
+ return true;
+}
+
+void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc,
+ const Time& current) {
+ lock_.AssertAcquired();
+
+ // Based off the Mozilla code. When a cookie has been accessed recently,
+ // don't bother updating its access time again. This reduces the number of
+ // updates we do during pageload, which in turn reduces the chance our storage
+ // backend will hit its batch thresholds and be forced to update.
+ if ((current - cc->LastAccessDate()) < last_access_threshold_)
+ return;
+
+ // See InitializeHistograms() for details.
+ histogram_between_access_interval_minutes_->Add(
+ (current - cc->LastAccessDate()).InMinutes());
+
+ cc->SetLastAccessDate(current);
+ if ((cc->IsPersistent() || persist_session_cookies_) && store_.get())
+ store_->UpdateCookieAccessTime(*cc);
+}
+
+void CookieMonster::InternalDeleteCookie(CookieMap::iterator it,
+ bool sync_to_store,
+ DeletionCause deletion_cause) {
+ lock_.AssertAcquired();
+
+ // Ideally, this would be asserted up where we define ChangeCauseMapping,
+ // but DeletionCause's visibility (or lack thereof) forces us to make
+ // this check here.
+ COMPILE_ASSERT(arraysize(ChangeCauseMapping) == DELETE_COOKIE_LAST_ENTRY + 1,
+ ChangeCauseMapping_size_not_eq_DeletionCause_enum_size);
+
+ // See InitializeHistograms() for details.
+ if (deletion_cause != DELETE_COOKIE_DONT_RECORD)
+ histogram_cookie_deletion_cause_->Add(deletion_cause);
+
+ CanonicalCookie* cc = it->second;
+ VLOG(kVlogSetCookies) << "InternalDeleteCookie() cc: " << cc->DebugString();
+
+ if ((cc->IsPersistent() || persist_session_cookies_) && store_.get() &&
+ sync_to_store)
+ store_->DeleteCookie(*cc);
+ if (delegate_.get()) {
+ ChangeCausePair mapping = ChangeCauseMapping[deletion_cause];
+
+ if (mapping.notify)
+ delegate_->OnCookieChanged(*cc, true, mapping.cause);
+ }
+ cookies_.erase(it);
+ delete cc;
+}
+
+// Domain expiry behavior is unchanged by key/expiry scheme (the
+// meaning of the key is different, but that's not visible to this routine).
+int CookieMonster::GarbageCollect(const Time& current,
+ const std::string& key) {
+ lock_.AssertAcquired();
+
+ int num_deleted = 0;
+ Time safe_date(
+ Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays));
+
+ // Collect garbage for this key, minding cookie priorities.
+ if (cookies_.count(key) > kDomainMaxCookies) {
+ VLOG(kVlogGarbageCollection) << "GarbageCollect() key: " << key;
+
+ CookieItVector cookie_its;
+ num_deleted += GarbageCollectExpired(
+ current, cookies_.equal_range(key), &cookie_its);
+ if (cookie_its.size() > kDomainMaxCookies) {
+ VLOG(kVlogGarbageCollection) << "Deep Garbage Collect domain.";
+ size_t purge_goal =
+ cookie_its.size() - (kDomainMaxCookies - kDomainPurgeCookies);
+ DCHECK(purge_goal > kDomainPurgeCookies);
+
+ // Boundary iterators into |cookie_its| for different priorities.
+ CookieItVector::iterator it_bdd[4];
+ // Intialize |it_bdd| while sorting |cookie_its| by priorities.
+ // Schematic: [MLLHMHHLMM] => [LLL|MMMM|HHH], with 4 boundaries.
+ it_bdd[0] = cookie_its.begin();
+ it_bdd[3] = cookie_its.end();
+ it_bdd[1] = PartitionCookieByPriority(it_bdd[0], it_bdd[3],
+ COOKIE_PRIORITY_LOW);
+ it_bdd[2] = PartitionCookieByPriority(it_bdd[1], it_bdd[3],
+ COOKIE_PRIORITY_MEDIUM);
+ size_t quota[3] = {
+ kDomainCookiesQuotaLow,
+ kDomainCookiesQuotaMedium,
+ kDomainCookiesQuotaHigh
+ };
+
+ // Purge domain cookies in 3 rounds.
+ // Round 1: consider low-priority cookies only: evict least-recently
+ // accessed, while protecting quota[0] of these from deletion.
+ // Round 2: consider {low, medium}-priority cookies, evict least-recently
+ // accessed, while protecting quota[0] + quota[1].
+ // Round 3: consider all cookies, evict least-recently accessed.
+ size_t accumulated_quota = 0;
+ CookieItVector::iterator it_purge_begin = it_bdd[0];
+ for (int i = 0; i < 3 && purge_goal > 0; ++i) {
+ accumulated_quota += quota[i];
+
+ // If we are not using priority, only do Round 3. This reproduces the
+ // old way of indiscriminately purging least-recently accessed cookies.
+ if (!priority_aware_garbage_collection_ && i < 2)
+ continue;
+
+ size_t num_considered = it_bdd[i + 1] - it_purge_begin;
+ if (num_considered <= accumulated_quota)
+ continue;
+
+ // Number of cookies that will be purged in this round.
+ size_t round_goal =
+ std::min(purge_goal, num_considered - accumulated_quota);
+ purge_goal -= round_goal;
+
+ SortLeastRecentlyAccessed(it_purge_begin, it_bdd[i + 1], round_goal);
+ // Cookies accessed on or after |safe_date| would have been safe from
+ // global purge, and we want to keep track of this.
+ CookieItVector::iterator it_purge_end = it_purge_begin + round_goal;
+ CookieItVector::iterator it_purge_middle =
+ LowerBoundAccessDate(it_purge_begin, it_purge_end, safe_date);
+ // Delete cookies accessed before |safe_date|.
+ num_deleted += GarbageCollectDeleteRange(
+ current,
+ DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE,
+ it_purge_begin,
+ it_purge_middle);
+ // Delete cookies accessed on or after |safe_date|.
+ num_deleted += GarbageCollectDeleteRange(
+ current,
+ DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE,
+ it_purge_middle,
+ it_purge_end);
+ it_purge_begin = it_purge_end;
+ }
+ DCHECK_EQ(0U, purge_goal);
+ }
+ }
+
+ // Collect garbage for everything. With firefox style we want to preserve
+ // cookies accessed in kSafeFromGlobalPurgeDays, otherwise evict.
+ if (cookies_.size() > kMaxCookies &&
+ earliest_access_time_ < safe_date) {
+ VLOG(kVlogGarbageCollection) << "GarbageCollect() everything";
+ CookieItVector cookie_its;
+ num_deleted += GarbageCollectExpired(
+ current, CookieMapItPair(cookies_.begin(), cookies_.end()),
+ &cookie_its);
+ if (cookie_its.size() > kMaxCookies) {
+ VLOG(kVlogGarbageCollection) << "Deep Garbage Collect everything.";
+ size_t purge_goal = cookie_its.size() - (kMaxCookies - kPurgeCookies);
+ DCHECK(purge_goal > kPurgeCookies);
+ // Sorts up to *and including* |cookie_its[purge_goal]|, so
+ // |earliest_access_time| will be properly assigned even if
+ // |global_purge_it| == |cookie_its.begin() + purge_goal|.
+ SortLeastRecentlyAccessed(cookie_its.begin(), cookie_its.end(),
+ purge_goal);
+ // Find boundary to cookies older than safe_date.
+ CookieItVector::iterator global_purge_it =
+ LowerBoundAccessDate(cookie_its.begin(),
+ cookie_its.begin() + purge_goal,
+ safe_date);
+ // Only delete the old cookies.
+ num_deleted += GarbageCollectDeleteRange(
+ current,
+ DELETE_COOKIE_EVICTED_GLOBAL,
+ cookie_its.begin(),
+ global_purge_it);
+ // Set access day to the oldest cookie that wasn't deleted.
+ earliest_access_time_ = (*global_purge_it)->second->LastAccessDate();
+ }
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::GarbageCollectExpired(
+ const Time& current,
+ const CookieMapItPair& itpair,
+ CookieItVector* cookie_its) {
+ if (keep_expired_cookies_)
+ return 0;
+
+ lock_.AssertAcquired();
+
+ int num_deleted = 0;
+ for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) {
+ CookieMap::iterator curit = it;
+ ++it;
+
+ if (curit->second->IsExpired(current)) {
+ InternalDeleteCookie(curit, true, DELETE_COOKIE_EXPIRED);
+ ++num_deleted;
+ } else if (cookie_its) {
+ cookie_its->push_back(curit);
+ }
+ }
+
+ return num_deleted;
+}
+
+int CookieMonster::GarbageCollectDeleteRange(
+ const Time& current,
+ DeletionCause cause,
+ CookieMonster::CookieItVector::iterator it_begin,
+ CookieMonster::CookieItVector::iterator it_end) {
+ for (CookieItVector::iterator it = it_begin; it != it_end; it++) {
+ histogram_evicted_last_access_minutes_->Add(
+ (current - (*it)->second->LastAccessDate()).InMinutes());
+ InternalDeleteCookie((*it), true, cause);
+ }
+ return it_end - it_begin;
+}
+
+// A wrapper around registry_controlled_domains::GetDomainAndRegistry
+// to make clear we're creating a key for our local map. Here and
+// in FindCookiesForHostAndDomain() are the only two places where
+// we need to conditionalize based on key type.
+//
+// Note that this key algorithm explicitly ignores the scheme. This is
+// because when we're entering cookies into the map from the backing store,
+// we in general won't have the scheme at that point.
+// In practical terms, this means that file cookies will be stored
+// in the map either by an empty string or by UNC name (and will be
+// limited by kMaxCookiesPerHost), and extension cookies will be stored
+// based on the single extension id, as the extension id won't have the
+// form of a DNS host and hence GetKey() will return it unchanged.
+//
+// Arguably the right thing to do here is to make the key
+// algorithm dependent on the scheme, and make sure that the scheme is
+// available everywhere the key must be obtained (specfically at backing
+// store load time). This would require either changing the backing store
+// database schema to include the scheme (far more trouble than it's worth), or
+// separating out file cookies into their own CookieMonster instance and
+// thus restricting each scheme to a single cookie monster (which might
+// be worth it, but is still too much trouble to solve what is currently a
+// non-problem).
+std::string CookieMonster::GetKey(const std::string& domain) const {
+ std::string effective_domain(
+ registry_controlled_domains::GetDomainAndRegistry(
+ domain, registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES));
+ if (effective_domain.empty())
+ effective_domain = domain;
+
+ if (!effective_domain.empty() && effective_domain[0] == '.')
+ return effective_domain.substr(1);
+ return effective_domain;
+}
+
+bool CookieMonster::IsCookieableScheme(const std::string& scheme) {
+ base::AutoLock autolock(lock_);
+
+ return std::find(cookieable_schemes_.begin(), cookieable_schemes_.end(),
+ scheme) != cookieable_schemes_.end();
+}
+
+bool CookieMonster::HasCookieableScheme(const GURL& url) {
+ lock_.AssertAcquired();
+
+ // Make sure the request is on a cookie-able url scheme.
+ for (size_t i = 0; i < cookieable_schemes_.size(); ++i) {
+ // We matched a scheme.
+ if (url.SchemeIs(cookieable_schemes_[i].c_str())) {
+ // We've matched a supported scheme.
+ return true;
+ }
+ }
+
+ // The scheme didn't match any in our whitelist.
+ VLOG(kVlogPerCookieMonster) << "WARNING: Unsupported cookie scheme: "
+ << url.scheme();
+ return false;
+}
+
+// Test to see if stats should be recorded, and record them if so.
+// The goal here is to get sampling for the average browser-hour of
+// activity. We won't take samples when the web isn't being surfed,
+// and when the web is being surfed, we'll take samples about every
+// kRecordStatisticsIntervalSeconds.
+// last_statistic_record_time_ is initialized to Now() rather than null
+// in the constructor so that we won't take statistics right after
+// startup, to avoid bias from browsers that are started but not used.
+void CookieMonster::RecordPeriodicStats(const base::Time& current_time) {
+ const base::TimeDelta kRecordStatisticsIntervalTime(
+ base::TimeDelta::FromSeconds(kRecordStatisticsIntervalSeconds));
+
+ // If we've taken statistics recently, return.
+ if (current_time - last_statistic_record_time_ <=
+ kRecordStatisticsIntervalTime) {
+ return;
+ }
+
+ // See InitializeHistograms() for details.
+ histogram_count_->Add(cookies_.size());
+
+ // More detailed statistics on cookie counts at different granularities.
+ TimeTicks beginning_of_time(TimeTicks::Now());
+
+ for (CookieMap::const_iterator it_key = cookies_.begin();
+ it_key != cookies_.end(); ) {
+ const std::string& key(it_key->first);
+
+ int key_count = 0;
+ typedef std::map<std::string, unsigned int> DomainMap;
+ DomainMap domain_map;
+ CookieMapItPair its_cookies = cookies_.equal_range(key);
+ while (its_cookies.first != its_cookies.second) {
+ key_count++;
+ const std::string& cookie_domain(its_cookies.first->second->Domain());
+ domain_map[cookie_domain]++;
+
+ its_cookies.first++;
+ }
+ histogram_etldp1_count_->Add(key_count);
+ histogram_domain_per_etldp1_count_->Add(domain_map.size());
+ for (DomainMap::const_iterator domain_map_it = domain_map.begin();
+ domain_map_it != domain_map.end(); domain_map_it++)
+ histogram_domain_count_->Add(domain_map_it->second);
+
+ it_key = its_cookies.second;
+ }
+
+ VLOG(kVlogPeriodic)
+ << "Time for recording cookie stats (us): "
+ << (TimeTicks::Now() - beginning_of_time).InMicroseconds();
+
+ last_statistic_record_time_ = current_time;
+}
+
+// Initialize all histogram counter variables used in this class.
+//
+// Normal histogram usage involves using the macros defined in
+// histogram.h, which automatically takes care of declaring these
+// variables (as statics), initializing them, and accumulating into
+// them, all from a single entry point. Unfortunately, that solution
+// doesn't work for the CookieMonster, as it's vulnerable to races between
+// separate threads executing the same functions and hence initializing the
+// same static variables. There isn't a race danger in the histogram
+// accumulation calls; they are written to be resilient to simultaneous
+// calls from multiple threads.
+//
+// The solution taken here is to have per-CookieMonster instance
+// variables that are constructed during CookieMonster construction.
+// Note that these variables refer to the same underlying histogram,
+// so we still race (but safely) with other CookieMonster instances
+// for accumulation.
+//
+// To do this we've expanded out the individual histogram macros calls,
+// with declarations of the variables in the class decl, initialization here
+// (done from the class constructor) and direct calls to the accumulation
+// methods where needed. The specific histogram macro calls on which the
+// initialization is based are included in comments below.
+void CookieMonster::InitializeHistograms() {
+ // From UMA_HISTOGRAM_CUSTOM_COUNTS
+ histogram_expiration_duration_minutes_ = base::Histogram::FactoryGet(
+ "Cookie.ExpirationDurationMinutes",
+ 1, kMinutesInTenYears, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_between_access_interval_minutes_ = base::Histogram::FactoryGet(
+ "Cookie.BetweenAccessIntervalMinutes",
+ 1, kMinutesInTenYears, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_evicted_last_access_minutes_ = base::Histogram::FactoryGet(
+ "Cookie.EvictedLastAccessMinutes",
+ 1, kMinutesInTenYears, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_count_ = base::Histogram::FactoryGet(
+ "Cookie.Count", 1, 4000, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_domain_count_ = base::Histogram::FactoryGet(
+ "Cookie.DomainCount", 1, 4000, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_etldp1_count_ = base::Histogram::FactoryGet(
+ "Cookie.Etldp1Count", 1, 4000, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_domain_per_etldp1_count_ = base::Histogram::FactoryGet(
+ "Cookie.DomainPerEtldp1Count", 1, 4000, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+
+ // From UMA_HISTOGRAM_COUNTS_10000 & UMA_HISTOGRAM_CUSTOM_COUNTS
+ histogram_number_duplicate_db_cookies_ = base::Histogram::FactoryGet(
+ "Net.NumDuplicateCookiesInDb", 1, 10000, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+
+ // From UMA_HISTOGRAM_ENUMERATION
+ histogram_cookie_deletion_cause_ = base::LinearHistogram::FactoryGet(
+ "Cookie.DeletionCause", 1,
+ DELETE_COOKIE_LAST_ENTRY - 1, DELETE_COOKIE_LAST_ENTRY,
+ base::Histogram::kUmaTargetedHistogramFlag);
+
+ // From UMA_HISTOGRAM_{CUSTOM_,}TIMES
+ histogram_time_get_ = base::Histogram::FactoryTimeGet("Cookie.TimeGet",
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50, base::Histogram::kUmaTargetedHistogramFlag);
+ histogram_time_blocked_on_load_ = base::Histogram::FactoryTimeGet(
+ "Cookie.TimeBlockedOnLoad",
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1),
+ 50, base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+
+// The system resolution is not high enough, so we can have multiple
+// set cookies that result in the same system time. When this happens, we
+// increment by one Time unit. Let's hope computers don't get too fast.
+Time CookieMonster::CurrentTime() {
+ return std::max(Time::Now(),
+ Time::FromInternalValue(last_time_seen_.ToInternalValue() + 1));
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_monster.h b/chromium/net/cookies/cookie_monster.h
new file mode 100644
index 00000000000..eaf89d33810
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster.h
@@ -0,0 +1,764 @@
+// Copyright (c) 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.
+
+// Brought to you by the letter D and the number 2.
+
+#ifndef NET_COOKIES_COOKIE_MONSTER_H_
+#define NET_COOKIES_COOKIE_MONSTER_H_
+
+#include <deque>
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_store.h"
+
+class GURL;
+
+namespace base {
+class Histogram;
+class HistogramBase;
+class TimeTicks;
+} // namespace base
+
+namespace net {
+
+class ParsedCookie;
+
+// The cookie monster is the system for storing and retrieving cookies. It has
+// an in-memory list of all cookies, and synchronizes non-session cookies to an
+// optional permanent storage that implements the PersistentCookieStore
+// interface.
+//
+// This class IS thread-safe. Normally, it is only used on the I/O thread, but
+// is also accessed directly through Automation for UI testing.
+//
+// All cookie tasks are handled asynchronously. Tasks may be deferred if
+// all affected cookies are not yet loaded from the backing store. Otherwise,
+// the callback may be invoked immediately (prior to return of the asynchronous
+// function).
+//
+// A cookie task is either pending loading of the entire cookie store, or
+// loading of cookies for a specfic domain key(eTLD+1). In the former case, the
+// cookie task will be queued in tasks_pending_ while PersistentCookieStore
+// chain loads the cookie store on DB thread. In the latter case, the cookie
+// task will be queued in tasks_pending_for_key_ while PermanentCookieStore
+// loads cookies for the specified domain key(eTLD+1) on DB thread.
+//
+// Callbacks are guaranteed to be invoked on the calling thread.
+//
+// TODO(deanm) Implement CookieMonster, the cookie database.
+// - Verify that our domain enforcement and non-dotted handling is correct
+class NET_EXPORT CookieMonster : public CookieStore {
+ public:
+ class Delegate;
+ class PersistentCookieStore;
+
+ // Terminology:
+ // * The 'top level domain' (TLD) of an internet domain name is
+ // the terminal "." free substring (e.g. "com" for google.com
+ // or world.std.com).
+ // * The 'effective top level domain' (eTLD) is the longest
+ // "." initiated terminal substring of an internet domain name
+ // that is controlled by a general domain registrar.
+ // (e.g. "co.uk" for news.bbc.co.uk).
+ // * The 'effective top level domain plus one' (eTLD+1) is the
+ // shortest "." delimited terminal substring of an internet
+ // domain name that is not controlled by a general domain
+ // registrar (e.g. "bbc.co.uk" for news.bbc.co.uk, or
+ // "google.com" for news.google.com). The general assumption
+ // is that all hosts and domains under an eTLD+1 share some
+ // administrative control.
+
+ // CookieMap is the central data structure of the CookieMonster. It
+ // is a map whose values are pointers to CanonicalCookie data
+ // structures (the data structures are owned by the CookieMonster
+ // and must be destroyed when removed from the map). The key is based on the
+ // effective domain of the cookies. If the domain of the cookie has an
+ // eTLD+1, that is the key for the map. If the domain of the cookie does not
+ // have an eTLD+1, the key of the map is the host the cookie applies to (it is
+ // not legal to have domain cookies without an eTLD+1). This rule
+ // excludes cookies for, e.g, ".com", ".co.uk", or ".internalnetwork".
+ // This behavior is the same as the behavior in Firefox v 3.6.10.
+
+ // NOTE(deanm):
+ // I benchmarked hash_multimap vs multimap. We're going to be query-heavy
+ // so it would seem like hashing would help. However they were very
+ // close, with multimap being a tiny bit faster. I think this is because
+ // our map is at max around 1000 entries, and the additional complexity
+ // for the hashing might not overcome the O(log(1000)) for querying
+ // a multimap. Also, multimap is standard, another reason to use it.
+ // TODO(rdsmith): This benchmark should be re-done now that we're allowing
+ // subtantially more entries in the map.
+ typedef std::multimap<std::string, CanonicalCookie*> CookieMap;
+ typedef std::pair<CookieMap::iterator, CookieMap::iterator> CookieMapItPair;
+ typedef std::vector<CookieMap::iterator> CookieItVector;
+
+ // Cookie garbage collection thresholds. Based off of the Mozilla defaults.
+ // When the number of cookies gets to k{Domain,}MaxCookies
+ // purge down to k{Domain,}MaxCookies - k{Domain,}PurgeCookies.
+ // It might seem scary to have a high purge value, but really it's not.
+ // You just make sure that you increase the max to cover the increase
+ // in purge, and we would have been purging the same number of cookies.
+ // We're just going through the garbage collection process less often.
+ // Note that the DOMAIN values are per eTLD+1; see comment for the
+ // CookieMap typedef. So, e.g., the maximum number of cookies allowed for
+ // google.com and all of its subdomains will be 150-180.
+ //
+ // Any cookies accessed more recently than kSafeFromGlobalPurgeDays will not
+ // be evicted by global garbage collection, even if we have more than
+ // kMaxCookies. This does not affect domain garbage collection.
+ static const size_t kDomainMaxCookies;
+ static const size_t kDomainPurgeCookies;
+ static const size_t kMaxCookies;
+ static const size_t kPurgeCookies;
+
+ // Quota for cookies with {low, medium, high} priorities within a domain.
+ static const size_t kDomainCookiesQuotaLow;
+ static const size_t kDomainCookiesQuotaMedium;
+ static const size_t kDomainCookiesQuotaHigh;
+
+ // The store passed in should not have had Init() called on it yet. This
+ // class will take care of initializing it. The backing store is NOT owned by
+ // this class, but it must remain valid for the duration of the cookie
+ // monster's existence. If |store| is NULL, then no backing store will be
+ // updated. If |delegate| is non-NULL, it will be notified on
+ // creation/deletion of cookies.
+ CookieMonster(PersistentCookieStore* store, Delegate* delegate);
+
+ // Only used during unit testing.
+ CookieMonster(PersistentCookieStore* store,
+ Delegate* delegate,
+ int last_access_threshold_milliseconds);
+
+ // Helper function that adds all cookies from |list| into this instance.
+ bool InitializeFrom(const CookieList& list);
+
+ typedef base::Callback<void(const CookieList& cookies)> GetCookieListCallback;
+ typedef base::Callback<void(bool success)> DeleteCookieCallback;
+ typedef base::Callback<void(bool cookies_exist)> HasCookiesForETLDP1Callback;
+
+ // Sets a cookie given explicit user-provided cookie attributes. The cookie
+ // name, value, domain, etc. are each provided as separate strings. This
+ // function expects each attribute to be well-formed. It will check for
+ // disallowed characters (e.g. the ';' character is disallowed within the
+ // cookie value attribute) and will return false without setting the cookie
+ // if such characters are found.
+ void SetCookieWithDetailsAsync(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority,
+ const SetCookiesCallback& callback);
+
+
+ // Returns all the cookies, for use in management UI, etc. This does not mark
+ // the cookies as having been accessed.
+ // The returned cookies are ordered by longest path, then by earliest
+ // creation date.
+ void GetAllCookiesAsync(const GetCookieListCallback& callback);
+
+ // Returns all the cookies, for use in management UI, etc. Filters results
+ // using given url scheme, host / domain and path and options. This does not
+ // mark the cookies as having been accessed.
+ // The returned cookies are ordered by longest path, then earliest
+ // creation date.
+ void GetAllCookiesForURLWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookieListCallback& callback);
+
+ // Invokes GetAllCookiesForURLWithOptions with options set to include HTTP
+ // only cookies.
+ void GetAllCookiesForURLAsync(const GURL& url,
+ const GetCookieListCallback& callback);
+
+ // Deletes all of the cookies.
+ void DeleteAllAsync(const DeleteCallback& callback);
+
+ // Deletes all cookies that match the host of the given URL
+ // regardless of path. This includes all http_only and secure cookies,
+ // but does not include any domain cookies that may apply to this host.
+ // Returns the number of cookies deleted.
+ void DeleteAllForHostAsync(const GURL& url,
+ const DeleteCallback& callback);
+
+ // Same as DeleteAllForHostAsync, except it deletes cookies between
+ // [|delete_begin|, |delete_end|).
+ // Returns the number of cookies deleted.
+ void DeleteAllCreatedBetweenForHostAsync(const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url,
+ const DeleteCallback& callback);
+
+ // Deletes one specific cookie.
+ void DeleteCanonicalCookieAsync(const CanonicalCookie& cookie,
+ const DeleteCookieCallback& callback);
+
+ // Checks whether for a given ETLD+1, there currently exist any cookies.
+ void HasCookiesForETLDP1Async(const std::string& etldp1,
+ const HasCookiesForETLDP1Callback& callback);
+
+ // Resets the list of cookieable schemes to the supplied schemes.
+ // If this this method is called, it must be called before first use of
+ // the instance (i.e. as part of the instance initialization process).
+ void SetCookieableSchemes(const char* schemes[], size_t num_schemes);
+
+ // Resets the list of cookieable schemes to kDefaultCookieableSchemes with or
+ // without 'file' being included.
+ void SetEnableFileScheme(bool accept);
+
+ // Instructs the cookie monster to not delete expired cookies. This is used
+ // in cases where the cookie monster is used as a data structure to keep
+ // arbitrary cookies.
+ void SetKeepExpiredCookies();
+
+ // Protects session cookies from deletion on shutdown.
+ void SetForceKeepSessionState();
+
+ // There are some unknowns about how to correctly handle file:// cookies,
+ // and our implementation for this is not robust enough. This allows you
+ // to enable support, but it should only be used for testing. Bug 1157243.
+ // Must be called before creating a CookieMonster instance.
+ static void EnableFileScheme();
+
+ // Flush the backing store (if any) to disk and post the given callback when
+ // done.
+ // WARNING: THE CALLBACK WILL RUN ON A RANDOM THREAD. IT MUST BE THREAD SAFE.
+ // It may be posted to the current thread, or it may run on the thread that
+ // actually does the flushing. Your Task should generally post a notification
+ // to the thread you actually want to be notified on.
+ void FlushStore(const base::Closure& callback);
+
+ // CookieStore implementation.
+
+ // Sets the cookies specified by |cookie_list| returned from |url|
+ // with options |options| in effect.
+ virtual void SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const SetCookiesCallback& callback) OVERRIDE;
+
+ // Gets all cookies that apply to |url| given |options|.
+ // The returned cookies are ordered by longest path, then earliest
+ // creation date.
+ virtual void GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookiesCallback& callback) OVERRIDE;
+
+ // Deletes all cookies with that might apply to |url| that has |cookie_name|.
+ virtual void DeleteCookieAsync(
+ const GURL& url, const std::string& cookie_name,
+ const base::Closure& callback) OVERRIDE;
+
+ // Deletes all of the cookies that have a creation_date greater than or equal
+ // to |delete_begin| and less than |delete_end|
+ // Returns the number of cookies that have been deleted.
+ virtual void DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) OVERRIDE;
+
+ virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE;
+
+ virtual CookieMonster* GetCookieMonster() OVERRIDE;
+
+ // Enables writing session cookies into the cookie database. If this this
+ // method is called, it must be called before first use of the instance
+ // (i.e. as part of the instance initialization process).
+ void SetPersistSessionCookies(bool persist_session_cookies);
+
+ // Enables the new garbage collection algorithm where domain cookie eviction
+ // uses cookie priorities to decide which cookies to purge and which to keep.
+ void SetPriorityAwareGarbageCollection(
+ bool priority_aware_garbage_collection);
+
+ // Debugging method to perform various validation checks on the map.
+ // Currently just checking that there are no null CanonicalCookie pointers
+ // in the map.
+ // Argument |arg| is to allow retaining of arbitrary data if the CHECKs
+ // in the function trip. TODO(rdsmith):Remove hack.
+ void ValidateMap(int arg);
+
+ // Determines if the scheme of the URL is a scheme that cookies will be
+ // stored for.
+ bool IsCookieableScheme(const std::string& scheme);
+
+ // The default list of schemes the cookie monster can handle.
+ static const char* kDefaultCookieableSchemes[];
+ static const int kDefaultCookieableSchemesCount;
+
+ private:
+ // For queueing the cookie monster calls.
+ class CookieMonsterTask;
+ class DeleteAllCreatedBetweenTask;
+ class DeleteAllCreatedBetweenForHostTask;
+ class DeleteAllForHostTask;
+ class DeleteAllTask;
+ class DeleteCookieTask;
+ class DeleteCanonicalCookieTask;
+ class GetAllCookiesForURLWithOptionsTask;
+ class GetAllCookiesTask;
+ class GetCookiesWithOptionsTask;
+ class SetCookieWithDetailsTask;
+ class SetCookieWithOptionsTask;
+ class DeleteSessionCookiesTask;
+ class HasCookiesForETLDP1Task;
+
+ // Testing support.
+ // For SetCookieWithCreationTime.
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest,
+ TestCookieDeleteAllCreatedBetweenTimestamps);
+ // For SetCookieWithCreationTime.
+ FRIEND_TEST_ALL_PREFIXES(MultiThreadedCookieMonsterTest,
+ ThreadCheckDeleteAllCreatedBetweenForHost);
+
+ // For gargage collection constants.
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestHostGarbageCollection);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestTotalGarbageCollection);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, GarbageCollectionTriggers);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestGCTimes);
+
+ // For validation of key values.
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestDomainTree);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestImport);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, GetKey);
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, TestGetKey);
+
+ // For FindCookiesForKey.
+ FRIEND_TEST_ALL_PREFIXES(CookieMonsterTest, ShortLivedSessionCookies);
+
+ // Internal reasons for deletion, used to populate informative histograms
+ // and to provide a public cause for onCookieChange notifications.
+ //
+ // If you add or remove causes from this list, please be sure to also update
+ // the Delegate::ChangeCause mapping inside ChangeCauseMapping. Moreover,
+ // these are used as array indexes, so avoid reordering to keep the
+ // histogram buckets consistent. New items (if necessary) should be added
+ // at the end of the list, just before DELETE_COOKIE_LAST_ENTRY.
+ enum DeletionCause {
+ DELETE_COOKIE_EXPLICIT = 0,
+ DELETE_COOKIE_OVERWRITE,
+ DELETE_COOKIE_EXPIRED,
+ DELETE_COOKIE_EVICTED,
+ DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE,
+ DELETE_COOKIE_DONT_RECORD, // e.g. For final cleanup after flush to store.
+ DELETE_COOKIE_EVICTED_DOMAIN,
+ DELETE_COOKIE_EVICTED_GLOBAL,
+
+ // Cookies evicted during domain level garbage collection that
+ // were accessed longer ago than kSafeFromGlobalPurgeDays
+ DELETE_COOKIE_EVICTED_DOMAIN_PRE_SAFE,
+
+ // Cookies evicted during domain level garbage collection that
+ // were accessed more recently than kSafeFromGlobalPurgeDays
+ // (and thus would have been preserved by global garbage collection).
+ DELETE_COOKIE_EVICTED_DOMAIN_POST_SAFE,
+
+ // A common idiom is to remove a cookie by overwriting it with an
+ // already-expired expiration date. This captures that case.
+ DELETE_COOKIE_EXPIRED_OVERWRITE,
+
+ DELETE_COOKIE_LAST_ENTRY
+ };
+
+ // The number of days since last access that cookies will not be subject
+ // to global garbage collection.
+ static const int kSafeFromGlobalPurgeDays;
+
+ // Record statistics every kRecordStatisticsIntervalSeconds of uptime.
+ static const int kRecordStatisticsIntervalSeconds = 10 * 60;
+
+ virtual ~CookieMonster();
+
+ // The following are synchronous calls to which the asynchronous methods
+ // delegate either immediately (if the store is loaded) or through a deferred
+ // task (if the store is not yet loaded).
+ bool SetCookieWithDetails(const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority);
+
+ CookieList GetAllCookies();
+
+ CookieList GetAllCookiesForURLWithOptions(const GURL& url,
+ const CookieOptions& options);
+
+ CookieList GetAllCookiesForURL(const GURL& url);
+
+ int DeleteAll(bool sync_to_store);
+
+ int DeleteAllCreatedBetween(const base::Time& delete_begin,
+ const base::Time& delete_end);
+
+ int DeleteAllForHost(const GURL& url);
+ int DeleteAllCreatedBetweenForHost(const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url);
+
+ bool DeleteCanonicalCookie(const CanonicalCookie& cookie);
+
+ bool SetCookieWithOptions(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options);
+
+ std::string GetCookiesWithOptions(const GURL& url,
+ const CookieOptions& options);
+
+ void DeleteCookie(const GURL& url, const std::string& cookie_name);
+
+ bool SetCookieWithCreationTime(const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& creation_time);
+
+ int DeleteSessionCookies();
+
+ bool HasCookiesForETLDP1(const std::string& etldp1);
+
+ // Called by all non-static functions to ensure that the cookies store has
+ // been initialized. This is not done during creating so it doesn't block
+ // the window showing.
+ // Note: this method should always be called with lock_ held.
+ void InitIfNecessary() {
+ if (!initialized_) {
+ if (store_.get()) {
+ InitStore();
+ } else {
+ loaded_ = true;
+ }
+ initialized_ = true;
+ }
+ }
+
+ // Initializes the backing store and reads existing cookies from it.
+ // Should only be called by InitIfNecessary().
+ void InitStore();
+
+ // Stores cookies loaded from the backing store and invokes any deferred
+ // calls. |beginning_time| should be the moment PersistentCookieStore::Load
+ // was invoked and is used for reporting histogram_time_blocked_on_load_.
+ // See PersistentCookieStore::Load for details on the contents of cookies.
+ void OnLoaded(base::TimeTicks beginning_time,
+ const std::vector<CanonicalCookie*>& cookies);
+
+ // Stores cookies loaded from the backing store and invokes the deferred
+ // task(s) pending loading of cookies associated with the domain key
+ // (eTLD+1). Called when all cookies for the domain key(eTLD+1) have been
+ // loaded from DB. See PersistentCookieStore::Load for details on the contents
+ // of cookies.
+ void OnKeyLoaded(
+ const std::string& key,
+ const std::vector<CanonicalCookie*>& cookies);
+
+ // Stores the loaded cookies.
+ void StoreLoadedCookies(const std::vector<CanonicalCookie*>& cookies);
+
+ // Invokes deferred calls.
+ void InvokeQueue();
+
+ // Checks that |cookies_| matches our invariants, and tries to repair any
+ // inconsistencies. (In other words, it does not have duplicate cookies).
+ void EnsureCookiesMapIsValid();
+
+ // Checks for any duplicate cookies for CookieMap key |key| which lie between
+ // |begin| and |end|. If any are found, all but the most recent are deleted.
+ // Returns the number of duplicate cookies that were deleted.
+ int TrimDuplicateCookiesForKey(const std::string& key,
+ CookieMap::iterator begin,
+ CookieMap::iterator end);
+
+ void SetDefaultCookieableSchemes();
+
+ void FindCookiesForHostAndDomain(const GURL& url,
+ const CookieOptions& options,
+ bool update_access_time,
+ std::vector<CanonicalCookie*>* cookies);
+
+ void FindCookiesForKey(const std::string& key,
+ const GURL& url,
+ const CookieOptions& options,
+ const base::Time& current,
+ bool update_access_time,
+ std::vector<CanonicalCookie*>* cookies);
+
+ // Delete any cookies that are equivalent to |ecc| (same path, domain, etc).
+ // If |skip_httponly| is true, httponly cookies will not be deleted. The
+ // return value with be true if |skip_httponly| skipped an httponly cookie.
+ // |key| is the key to find the cookie in cookies_; see the comment before
+ // the CookieMap typedef for details.
+ // NOTE: There should never be more than a single matching equivalent cookie.
+ bool DeleteAnyEquivalentCookie(const std::string& key,
+ const CanonicalCookie& ecc,
+ bool skip_httponly,
+ bool already_expired);
+
+ // Takes ownership of *cc.
+ void InternalInsertCookie(const std::string& key,
+ CanonicalCookie* cc,
+ bool sync_to_store);
+
+ // Helper function that sets cookies with more control.
+ // Not exposed as we don't want callers to have the ability
+ // to specify (potentially duplicate) creation times.
+ bool SetCookieWithCreationTimeAndOptions(const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& creation_time,
+ const CookieOptions& options);
+
+ // Helper function that sets a canonical cookie, deleting equivalents and
+ // performing garbage collection.
+ bool SetCanonicalCookie(scoped_ptr<CanonicalCookie>* cc,
+ const base::Time& creation_time,
+ const CookieOptions& options);
+
+ void InternalUpdateCookieAccessTime(CanonicalCookie* cc,
+ const base::Time& current_time);
+
+ // |deletion_cause| argument is used for collecting statistics and choosing
+ // the correct Delegate::ChangeCause for OnCookieChanged notifications.
+ void InternalDeleteCookie(CookieMap::iterator it, bool sync_to_store,
+ DeletionCause deletion_cause);
+
+ // If the number of cookies for CookieMap key |key|, or globally, are
+ // over the preset maximums above, garbage collect, first for the host and
+ // then globally. See comments above garbage collection threshold
+ // constants for details.
+ //
+ // Returns the number of cookies deleted (useful for debugging).
+ int GarbageCollect(const base::Time& current, const std::string& key);
+
+ // Helper for GarbageCollect(); can be called directly as well. Deletes
+ // all expired cookies in |itpair|. If |cookie_its| is non-NULL, it is
+ // populated with all the non-expired cookies from |itpair|.
+ //
+ // Returns the number of cookies deleted.
+ int GarbageCollectExpired(const base::Time& current,
+ const CookieMapItPair& itpair,
+ std::vector<CookieMap::iterator>* cookie_its);
+
+ // Helper for GarbageCollect(). Deletes all cookies in the range specified by
+ // [|it_begin|, |it_end|). Returns the number of cookies deleted.
+ int GarbageCollectDeleteRange(const base::Time& current,
+ DeletionCause cause,
+ CookieItVector::iterator cookie_its_begin,
+ CookieItVector::iterator cookie_its_end);
+
+ // Find the key (for lookup in cookies_) based on the given domain.
+ // See comment on keys before the CookieMap typedef.
+ std::string GetKey(const std::string& domain) const;
+
+ bool HasCookieableScheme(const GURL& url);
+
+ // Statistics support
+
+ // This function should be called repeatedly, and will record
+ // statistics if a sufficient time period has passed.
+ void RecordPeriodicStats(const base::Time& current_time);
+
+ // Initialize the above variables; should only be called from
+ // the constructor.
+ void InitializeHistograms();
+
+ // The resolution of our time isn't enough, so we do something
+ // ugly and increment when we've seen the same time twice.
+ base::Time CurrentTime();
+
+ // Runs the task if, or defers the task until, the full cookie database is
+ // loaded.
+ void DoCookieTask(const scoped_refptr<CookieMonsterTask>& task_item);
+
+ // Runs the task if, or defers the task until, the cookies for the given URL
+ // are loaded.
+ void DoCookieTaskForURL(const scoped_refptr<CookieMonsterTask>& task_item,
+ const GURL& url);
+
+ // Histogram variables; see CookieMonster::InitializeHistograms() in
+ // cookie_monster.cc for details.
+ base::HistogramBase* histogram_expiration_duration_minutes_;
+ base::HistogramBase* histogram_between_access_interval_minutes_;
+ base::HistogramBase* histogram_evicted_last_access_minutes_;
+ base::HistogramBase* histogram_count_;
+ base::HistogramBase* histogram_domain_count_;
+ base::HistogramBase* histogram_etldp1_count_;
+ base::HistogramBase* histogram_domain_per_etldp1_count_;
+ base::HistogramBase* histogram_number_duplicate_db_cookies_;
+ base::HistogramBase* histogram_cookie_deletion_cause_;
+ base::HistogramBase* histogram_time_get_;
+ base::HistogramBase* histogram_time_mac_;
+ base::HistogramBase* histogram_time_blocked_on_load_;
+
+ CookieMap cookies_;
+
+ // Indicates whether the cookie store has been initialized. This happens
+ // lazily in InitStoreIfNecessary().
+ bool initialized_;
+
+ // Indicates whether loading from the backend store is completed and
+ // calls may be immediately processed.
+ bool loaded_;
+
+ // List of domain keys that have been loaded from the DB.
+ std::set<std::string> keys_loaded_;
+
+ // Map of domain keys to their associated task queues. These tasks are blocked
+ // until all cookies for the associated domain key eTLD+1 are loaded from the
+ // backend store.
+ std::map<std::string, std::deque<scoped_refptr<CookieMonsterTask> > >
+ tasks_pending_for_key_;
+
+ // Queues tasks that are blocked until all cookies are loaded from the backend
+ // store.
+ std::queue<scoped_refptr<CookieMonsterTask> > tasks_pending_;
+
+ scoped_refptr<PersistentCookieStore> store_;
+
+ base::Time last_time_seen_;
+
+ // Minimum delay after updating a cookie's LastAccessDate before we will
+ // update it again.
+ const base::TimeDelta last_access_threshold_;
+
+ // Approximate date of access time of least recently accessed cookie
+ // in |cookies_|. Note that this is not guaranteed to be accurate, only a)
+ // to be before or equal to the actual time, and b) to be accurate
+ // immediately after a garbage collection that scans through all the cookies.
+ // This value is used to determine whether global garbage collection might
+ // find cookies to purge.
+ // Note: The default Time() constructor will create a value that compares
+ // earlier than any other time value, which is wanted. Thus this
+ // value is not initialized.
+ base::Time earliest_access_time_;
+
+ // During loading, holds the set of all loaded cookie creation times. Used to
+ // avoid ever letting cookies with duplicate creation times into the store;
+ // that way we don't have to worry about what sections of code are safe
+ // to call while it's in that state.
+ std::set<int64> creation_times_;
+
+ std::vector<std::string> cookieable_schemes_;
+
+ scoped_refptr<Delegate> delegate_;
+
+ // Lock for thread-safety
+ base::Lock lock_;
+
+ base::Time last_statistic_record_time_;
+
+ bool keep_expired_cookies_;
+ bool persist_session_cookies_;
+ bool priority_aware_garbage_collection_;
+
+ // Static setting for whether or not file scheme cookies are allows when
+ // a new CookieMonster is created, or the accepted schemes on a CookieMonster
+ // instance are reset back to defaults.
+ static bool default_enable_file_scheme_;
+
+ DISALLOW_COPY_AND_ASSIGN(CookieMonster);
+};
+
+class NET_EXPORT CookieMonster::Delegate
+ : public base::RefCountedThreadSafe<CookieMonster::Delegate> {
+ public:
+ // The publicly relevant reasons a cookie might be changed.
+ enum ChangeCause {
+ // The cookie was changed directly by a consumer's action.
+ CHANGE_COOKIE_EXPLICIT,
+ // The cookie was automatically removed due to an insert operation that
+ // overwrote it.
+ CHANGE_COOKIE_OVERWRITE,
+ // The cookie was automatically removed as it expired.
+ CHANGE_COOKIE_EXPIRED,
+ // The cookie was automatically evicted during garbage collection.
+ CHANGE_COOKIE_EVICTED,
+ // The cookie was overwritten with an already-expired expiration date.
+ CHANGE_COOKIE_EXPIRED_OVERWRITE
+ };
+
+ // Will be called when a cookie is added or removed. The function is passed
+ // the respective |cookie| which was added to or removed from the cookies.
+ // If |removed| is true, the cookie was deleted, and |cause| will be set
+ // to the reason for its removal. If |removed| is false, the cookie was
+ // added, and |cause| will be set to CHANGE_COOKIE_EXPLICIT.
+ //
+ // As a special case, note that updating a cookie's properties is implemented
+ // as a two step process: the cookie to be updated is first removed entirely,
+ // generating a notification with cause CHANGE_COOKIE_OVERWRITE. Afterwards,
+ // a new cookie is written with the updated values, generating a notification
+ // with cause CHANGE_COOKIE_EXPLICIT.
+ virtual void OnCookieChanged(const CanonicalCookie& cookie,
+ bool removed,
+ ChangeCause cause) = 0;
+ protected:
+ friend class base::RefCountedThreadSafe<CookieMonster::Delegate>;
+ virtual ~Delegate() {}
+};
+
+typedef base::RefCountedThreadSafe<CookieMonster::PersistentCookieStore>
+ RefcountedPersistentCookieStore;
+
+class NET_EXPORT CookieMonster::PersistentCookieStore
+ : public RefcountedPersistentCookieStore {
+ public:
+ typedef base::Callback<void(const std::vector<CanonicalCookie*>&)>
+ LoadedCallback;
+
+ // Initializes the store and retrieves the existing cookies. This will be
+ // called only once at startup. The callback will return all the cookies
+ // that are not yet returned to CookieMonster by previous priority loads.
+ virtual void Load(const LoadedCallback& loaded_callback) = 0;
+
+ // Does a priority load of all cookies for the domain key (eTLD+1). The
+ // callback will return all the cookies that are not yet returned by previous
+ // loads, which includes cookies for the requested domain key if they are not
+ // already returned, plus all cookies that are chain-loaded and not yet
+ // returned to CookieMonster.
+ virtual void LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& loaded_callback) = 0;
+
+ virtual void AddCookie(const CanonicalCookie& cc) = 0;
+ virtual void UpdateCookieAccessTime(const CanonicalCookie& cc) = 0;
+ virtual void DeleteCookie(const CanonicalCookie& cc) = 0;
+
+ // Instructs the store to not discard session only cookies on shutdown.
+ virtual void SetForceKeepSessionState() = 0;
+
+ // Flushes the store and posts |callback| when complete.
+ virtual void Flush(const base::Closure& callback) = 0;
+
+ protected:
+ PersistentCookieStore() {}
+ virtual ~PersistentCookieStore() {}
+
+ private:
+ friend class base::RefCountedThreadSafe<PersistentCookieStore>;
+ DISALLOW_COPY_AND_ASSIGN(PersistentCookieStore);
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_MONSTER_H_
diff --git a/chromium/net/cookies/cookie_monster_perftest.cc b/chromium/net/cookies/cookie_monster_perftest.cc
new file mode 100644
index 00000000000..c70516f70eb
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster_perftest.cc
@@ -0,0 +1,385 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/perftimer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_monster_store_test.h"
+#include "net/cookies/parsed_cookie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+const int kNumCookies = 20000;
+const char kCookieLine[] = "A = \"b=;\\\"\" ;secure;;;";
+const char kGoogleURL[] = "http://www.google.izzle";
+
+int CountInString(const std::string& str, char c) {
+ return std::count(str.begin(), str.end(), c);
+}
+
+class CookieMonsterTest : public testing::Test {
+ public:
+ CookieMonsterTest() : message_loop_(new base::MessageLoopForIO()) {}
+
+ private:
+ scoped_ptr<base::MessageLoop> message_loop_;
+};
+
+class BaseCallback {
+ public:
+ BaseCallback() : has_run_(false) {}
+
+ protected:
+ void WaitForCallback() {
+ // Note that the performance tests currently all operate on a loaded cookie
+ // store (or, more precisely, one that has no backing persistent store).
+ // Therefore, callbacks will actually always complete synchronously. If the
+ // tests get more advanced we need to add other means of signaling
+ // completion.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(has_run_);
+ has_run_ = false;
+ }
+
+ void Run() {
+ has_run_ = true;
+ }
+
+ bool has_run_;
+};
+
+class SetCookieCallback : public BaseCallback {
+ public:
+ void SetCookie(
+ CookieMonster* cm, const GURL& gurl, const std::string& cookie) {
+ cm->SetCookieWithOptionsAsync(gurl, cookie, options_, base::Bind(
+ &SetCookieCallback::Run, base::Unretained(this)));
+ WaitForCallback();
+ }
+ private:
+ void Run(bool success) {
+ EXPECT_TRUE(success);
+ BaseCallback::Run();
+ }
+ net::CookieOptions options_;
+};
+
+class GetCookiesCallback : public BaseCallback {
+ public:
+ const std::string& GetCookies(CookieMonster* cm, const GURL& gurl) {
+ cm->GetCookiesWithOptionsAsync(gurl, options_, base::Bind(
+ &GetCookiesCallback::Run, base::Unretained(this)));
+ WaitForCallback();
+ return cookies_;
+ }
+
+ private:
+ void Run(const std::string& cookies) {
+ cookies_ = cookies;
+ BaseCallback::Run();
+ }
+ std::string cookies_;
+ net::CookieOptions options_;
+};
+
+} // namespace
+
+TEST(ParsedCookieTest, TestParseCookies) {
+ std::string cookie(kCookieLine);
+ PerfTimeLogger timer("Parsed_cookie_parse_cookies");
+ for (int i = 0; i < kNumCookies; ++i) {
+ ParsedCookie pc(cookie);
+ EXPECT_TRUE(pc.IsValid());
+ }
+ timer.Done();
+}
+
+TEST(ParsedCookieTest, TestParseBigCookies) {
+ std::string cookie(3800, 'z');
+ cookie += kCookieLine;
+ PerfTimeLogger timer("Parsed_cookie_parse_big_cookies");
+ for (int i = 0; i < kNumCookies; ++i) {
+ ParsedCookie pc(cookie);
+ EXPECT_TRUE(pc.IsValid());
+ }
+ timer.Done();
+}
+
+TEST_F(CookieMonsterTest, TestAddCookiesOnSingleHost) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ std::vector<std::string> cookies;
+ for (int i = 0; i < kNumCookies; i++) {
+ cookies.push_back(base::StringPrintf("a%03d=b", i));
+ }
+
+ SetCookieCallback setCookieCallback;
+
+ // Add a bunch of cookies on a single host
+ PerfTimeLogger timer("Cookie_monster_add_single_host");
+
+ for (std::vector<std::string>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ setCookieCallback.SetCookie(cm.get(), GURL(kGoogleURL), *it);
+ }
+ timer.Done();
+
+ GetCookiesCallback getCookiesCallback;
+
+ PerfTimeLogger timer2("Cookie_monster_query_single_host");
+ for (std::vector<std::string>::const_iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ getCookiesCallback.GetCookies(cm.get(), GURL(kGoogleURL));
+ }
+ timer2.Done();
+
+ PerfTimeLogger timer3("Cookie_monster_deleteall_single_host");
+ cm->DeleteAllAsync(CookieMonster::DeleteCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ timer3.Done();
+}
+
+TEST_F(CookieMonsterTest, TestAddCookieOnManyHosts) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ std::string cookie(kCookieLine);
+ std::vector<GURL> gurls; // just wanna have ffffuunnn
+ for (int i = 0; i < kNumCookies; ++i) {
+ gurls.push_back(GURL(base::StringPrintf("https://a%04d.izzle", i)));
+ }
+
+ SetCookieCallback setCookieCallback;
+
+ // Add a cookie on a bunch of host
+ PerfTimeLogger timer("Cookie_monster_add_many_hosts");
+ for (std::vector<GURL>::const_iterator it = gurls.begin();
+ it != gurls.end(); ++it) {
+ setCookieCallback.SetCookie(cm.get(), *it, cookie);
+ }
+ timer.Done();
+
+ GetCookiesCallback getCookiesCallback;
+
+ PerfTimeLogger timer2("Cookie_monster_query_many_hosts");
+ for (std::vector<GURL>::const_iterator it = gurls.begin();
+ it != gurls.end(); ++it) {
+ getCookiesCallback.GetCookies(cm.get(), *it);
+ }
+ timer2.Done();
+
+ PerfTimeLogger timer3("Cookie_monster_deleteall_many_hosts");
+ cm->DeleteAllAsync(CookieMonster::DeleteCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ timer3.Done();
+}
+
+TEST_F(CookieMonsterTest, TestDomainTree) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ GetCookiesCallback getCookiesCallback;
+ SetCookieCallback setCookieCallback;
+ const char* domain_cookie_format_tree = "a=b; domain=%s";
+ const std::string domain_base("top.com");
+
+ std::vector<std::string> domain_list;
+
+ // Create a balanced binary tree of domains on which the cookie is set.
+ domain_list.push_back(domain_base);
+ for (int i1 = 0; i1 < 2; i1++) {
+ std::string domain_base_1((i1 ? "a." : "b.") + domain_base);
+ EXPECT_EQ("top.com", cm->GetKey(domain_base_1));
+ domain_list.push_back(domain_base_1);
+ for (int i2 = 0; i2 < 2; i2++) {
+ std::string domain_base_2((i2 ? "a." : "b.") + domain_base_1);
+ EXPECT_EQ("top.com", cm->GetKey(domain_base_2));
+ domain_list.push_back(domain_base_2);
+ for (int i3 = 0; i3 < 2; i3++) {
+ std::string domain_base_3((i3 ? "a." : "b.") + domain_base_2);
+ EXPECT_EQ("top.com", cm->GetKey(domain_base_3));
+ domain_list.push_back(domain_base_3);
+ for (int i4 = 0; i4 < 2; i4++) {
+ std::string domain_base_4((i4 ? "a." : "b.") + domain_base_3);
+ EXPECT_EQ("top.com", cm->GetKey(domain_base_4));
+ domain_list.push_back(domain_base_4);
+ }
+ }
+ }
+ }
+
+
+ EXPECT_EQ(31u, domain_list.size());
+ for (std::vector<std::string>::const_iterator it = domain_list.begin();
+ it != domain_list.end(); it++) {
+ GURL gurl("https://" + *it + "/");
+ const std::string cookie = base::StringPrintf(domain_cookie_format_tree,
+ it->c_str());
+ setCookieCallback.SetCookie(cm.get(), gurl, cookie);
+ }
+ EXPECT_EQ(31u, cm->GetAllCookies().size());
+
+ GURL probe_gurl("https://b.a.b.a.top.com/");
+ std::string cookie_line = getCookiesCallback.GetCookies(cm.get(), probe_gurl);
+ EXPECT_EQ(5, CountInString(cookie_line, '='))
+ << "Cookie line: " << cookie_line;
+ PerfTimeLogger timer("Cookie_monster_query_domain_tree");
+ for (int i = 0; i < kNumCookies; i++) {
+ getCookiesCallback.GetCookies(cm.get(), probe_gurl);
+ }
+ timer.Done();
+}
+
+TEST_F(CookieMonsterTest, TestDomainLine) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ SetCookieCallback setCookieCallback;
+ GetCookiesCallback getCookiesCallback;
+ std::vector<std::string> domain_list;
+ GURL probe_gurl("https://b.a.b.a.top.com/");
+ std::string cookie_line;
+
+ // Create a line of 32 domain cookies such that all cookies stored
+ // by effective TLD+1 will apply to probe GURL.
+ // (TLD + 1 is the level above .com/org/net/etc, e.g. "top.com"
+ // or "google.com". "Effective" is added to include sites like
+ // bbc.co.uk, where the effetive TLD+1 is more than one level
+ // below the top level.)
+ domain_list.push_back("a.top.com");
+ domain_list.push_back("b.a.top.com");
+ domain_list.push_back("a.b.a.top.com");
+ domain_list.push_back("b.a.b.a.top.com");
+ EXPECT_EQ(4u, domain_list.size());
+
+ const char* domain_cookie_format_line = "a%03d=b; domain=%s";
+ for (int i = 0; i < 8; i++) {
+ for (std::vector<std::string>::const_iterator it = domain_list.begin();
+ it != domain_list.end(); it++) {
+ GURL gurl("https://" + *it + "/");
+ const std::string cookie = base::StringPrintf(domain_cookie_format_line,
+ i, it->c_str());
+ setCookieCallback.SetCookie(cm.get(), gurl, cookie);
+ }
+ }
+
+ cookie_line = getCookiesCallback.GetCookies(cm.get(), probe_gurl);
+ EXPECT_EQ(32, CountInString(cookie_line, '='));
+ PerfTimeLogger timer2("Cookie_monster_query_domain_line");
+ for (int i = 0; i < kNumCookies; i++) {
+ getCookiesCallback.GetCookies(cm.get(), probe_gurl);
+ }
+ timer2.Done();
+}
+
+TEST_F(CookieMonsterTest, TestImport) {
+ scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore);
+ std::vector<CanonicalCookie*> initial_cookies;
+ GetCookiesCallback getCookiesCallback;
+
+ // We want to setup a fairly large backing store, with 300 domains of 50
+ // cookies each. Creation times must be unique.
+ int64 time_tick(base::Time::Now().ToInternalValue());
+
+ for (int domain_num = 0; domain_num < 300; domain_num++) {
+ std::string domain_name(base::StringPrintf(".Domain_%d.com", domain_num));
+ std::string gurl("www" + domain_name);
+ for (int cookie_num = 0; cookie_num < 50; cookie_num++) {
+ std::string cookie_line(base::StringPrintf("Cookie_%d=1; Path=/",
+ cookie_num));
+ AddCookieToList(gurl, cookie_line,
+ base::Time::FromInternalValue(time_tick++),
+ &initial_cookies);
+ }
+ }
+
+ store->SetLoadExpectation(true, initial_cookies);
+
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+
+ // Import will happen on first access.
+ GURL gurl("www.google.com");
+ CookieOptions options;
+ PerfTimeLogger timer("Cookie_monster_import_from_store");
+ getCookiesCallback.GetCookies(cm.get(), gurl);
+ timer.Done();
+
+ // Just confirm keys were set as expected.
+ EXPECT_EQ("domain_1.com", cm->GetKey("www.Domain_1.com"));
+}
+
+TEST_F(CookieMonsterTest, TestGetKey) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ PerfTimeLogger timer("Cookie_monster_get_key");
+ for (int i = 0; i < kNumCookies; i++)
+ cm->GetKey("www.google.com");
+ timer.Done();
+}
+
+// This test is probing for whether garbage collection happens when it
+// shouldn't. This will not in general be visible functionally, since
+// if GC runs twice in a row without any change to the store, the second
+// GC run will not do anything the first one didn't. That's why this is
+// a performance test. The test should be considered to pass if all the
+// times reported are approximately the same--this indicates that no GC
+// happened repeatedly for any case.
+TEST_F(CookieMonsterTest, TestGCTimes) {
+ SetCookieCallback setCookieCallback;
+
+ const struct TestCase {
+ const char* name;
+ size_t num_cookies;
+ size_t num_old_cookies;
+ } test_cases[] = {
+ {
+ // A whole lot of recent cookies; gc shouldn't happen.
+ "all_recent",
+ CookieMonster::kMaxCookies * 2,
+ 0,
+ }, {
+ // Some old cookies, but still overflowing max.
+ "mostly_recent",
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies / 2,
+ }, {
+ // Old cookies enough to bring us right down to our purge line.
+ "balanced",
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies + CookieMonster::kPurgeCookies + 1,
+ }, {
+ "mostly_old",
+ // Old cookies enough to bring below our purge line (which we
+ // shouldn't do).
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies * 3 / 4,
+ }, {
+ "less_than_gc_thresh",
+ // Few enough cookies that gc shouldn't happen at all.
+ CookieMonster::kMaxCookies - 5,
+ 0,
+ },
+ };
+ for (int ci = 0; ci < static_cast<int>(ARRAYSIZE_UNSAFE(test_cases)); ++ci) {
+ const TestCase& test_case(test_cases[ci]);
+ scoped_refptr<CookieMonster> cm(
+ CreateMonsterFromStoreForGC(
+ test_case.num_cookies, test_case.num_old_cookies,
+ CookieMonster::kSafeFromGlobalPurgeDays * 2));
+
+ GURL gurl("http://google.com");
+ std::string cookie_line("z=3");
+ // Trigger the Garbage collection we're allowed.
+ setCookieCallback.SetCookie(cm.get(), gurl, cookie_line);
+
+ PerfTimeLogger timer((std::string("GC_") + test_case.name).c_str());
+ for (int i = 0; i < kNumCookies; i++)
+ setCookieCallback.SetCookie(cm.get(), gurl, cookie_line);
+ timer.Done();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_monster_store_test.cc b/chromium/net/cookies/cookie_monster_store_test.cc
new file mode 100644
index 00000000000..350a0159b90
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster_store_test.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_monster_store_test.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+LoadedCallbackTask::LoadedCallbackTask(LoadedCallback loaded_callback,
+ std::vector<CanonicalCookie*> cookies)
+ : loaded_callback_(loaded_callback),
+ cookies_(cookies) {
+}
+
+LoadedCallbackTask::~LoadedCallbackTask() {}
+
+MockPersistentCookieStore::MockPersistentCookieStore()
+ : load_return_value_(true),
+ loaded_(false) {
+}
+
+void MockPersistentCookieStore::SetLoadExpectation(
+ bool return_value,
+ const std::vector<CanonicalCookie*>& result) {
+ load_return_value_ = return_value;
+ load_result_ = result;
+}
+
+void MockPersistentCookieStore::Load(const LoadedCallback& loaded_callback) {
+ std::vector<CanonicalCookie*> out_cookies;
+ if (load_return_value_) {
+ out_cookies = load_result_;
+ loaded_ = true;
+ }
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LoadedCallbackTask::Run,
+ new LoadedCallbackTask(loaded_callback, out_cookies)));
+}
+
+void MockPersistentCookieStore::LoadCookiesForKey(
+ const std::string& key,
+ const LoadedCallback& loaded_callback) {
+ if (!loaded_) {
+ Load(loaded_callback);
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LoadedCallbackTask::Run,
+ new LoadedCallbackTask(loaded_callback,
+ std::vector<CanonicalCookie*>())));
+ }
+}
+
+void MockPersistentCookieStore::AddCookie(const CanonicalCookie& cookie) {
+ commands_.push_back(
+ CookieStoreCommand(CookieStoreCommand::ADD, cookie));
+}
+
+void MockPersistentCookieStore::UpdateCookieAccessTime(
+ const CanonicalCookie& cookie) {
+ commands_.push_back(CookieStoreCommand(
+ CookieStoreCommand::UPDATE_ACCESS_TIME, cookie));
+}
+
+void MockPersistentCookieStore::DeleteCookie(const CanonicalCookie& cookie) {
+ commands_.push_back(
+ CookieStoreCommand(CookieStoreCommand::REMOVE, cookie));
+}
+
+void MockPersistentCookieStore::Flush(const base::Closure& callback) {
+ if (!callback.is_null())
+ base::MessageLoop::current()->PostTask(FROM_HERE, callback);
+}
+
+void MockPersistentCookieStore::SetForceKeepSessionState() {
+}
+
+MockPersistentCookieStore::~MockPersistentCookieStore() {}
+
+MockCookieMonsterDelegate::MockCookieMonsterDelegate() {}
+
+void MockCookieMonsterDelegate::OnCookieChanged(
+ const CanonicalCookie& cookie,
+ bool removed,
+ CookieMonster::Delegate::ChangeCause cause) {
+ CookieNotification notification(cookie, removed);
+ changes_.push_back(notification);
+}
+
+MockCookieMonsterDelegate::~MockCookieMonsterDelegate() {}
+
+CanonicalCookie BuildCanonicalCookie(const std::string& key,
+ const std::string& cookie_line,
+ const base::Time& creation_time) {
+
+ // Parse the cookie line.
+ ParsedCookie pc(cookie_line);
+ EXPECT_TRUE(pc.IsValid());
+
+ // This helper is simplistic in interpreting a parsed cookie, in order to
+ // avoid duplicated CookieMonster's CanonPath() and CanonExpiration()
+ // functions. Would be nice to export them, and re-use here.
+ EXPECT_FALSE(pc.HasMaxAge());
+ EXPECT_TRUE(pc.HasPath());
+ base::Time cookie_expires = pc.HasExpires() ?
+ cookie_util::ParseCookieTime(pc.Expires()) : base::Time();
+ std::string cookie_path = pc.Path();
+
+ return CanonicalCookie(
+ GURL(), pc.Name(), pc.Value(), key, cookie_path,
+ creation_time, cookie_expires, creation_time,
+ pc.IsSecure(), pc.IsHttpOnly(), pc.Priority());
+}
+
+void AddCookieToList(
+ const std::string& key,
+ const std::string& cookie_line,
+ const base::Time& creation_time,
+ std::vector<CanonicalCookie*>* out_list) {
+ scoped_ptr<CanonicalCookie> cookie(
+ new CanonicalCookie(
+ BuildCanonicalCookie(key, cookie_line, creation_time)));
+
+ out_list->push_back(cookie.release());
+}
+
+MockSimplePersistentCookieStore::MockSimplePersistentCookieStore()
+ : loaded_(false) {
+}
+
+void MockSimplePersistentCookieStore::Load(
+ const LoadedCallback& loaded_callback) {
+ std::vector<CanonicalCookie*> out_cookies;
+
+ for (CanonicalCookieMap::const_iterator it = cookies_.begin();
+ it != cookies_.end(); it++)
+ out_cookies.push_back(new CanonicalCookie(it->second));
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LoadedCallbackTask::Run,
+ new LoadedCallbackTask(loaded_callback, out_cookies)));
+ loaded_ = true;
+}
+
+void MockSimplePersistentCookieStore::LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& loaded_callback) {
+ if (!loaded_) {
+ Load(loaded_callback);
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LoadedCallbackTask::Run,
+ new LoadedCallbackTask(loaded_callback,
+ std::vector<CanonicalCookie*>())));
+ }
+}
+
+void MockSimplePersistentCookieStore::AddCookie(const CanonicalCookie& cookie) {
+ int64 creation_time = cookie.CreationDate().ToInternalValue();
+ EXPECT_TRUE(cookies_.find(creation_time) == cookies_.end());
+ cookies_[creation_time] = cookie;
+}
+
+void MockSimplePersistentCookieStore::UpdateCookieAccessTime(
+ const CanonicalCookie& cookie) {
+ int64 creation_time = cookie.CreationDate().ToInternalValue();
+ ASSERT_TRUE(cookies_.find(creation_time) != cookies_.end());
+ cookies_[creation_time].SetLastAccessDate(base::Time::Now());
+}
+
+void MockSimplePersistentCookieStore::DeleteCookie(
+ const CanonicalCookie& cookie) {
+ int64 creation_time = cookie.CreationDate().ToInternalValue();
+ CanonicalCookieMap::iterator it = cookies_.find(creation_time);
+ ASSERT_TRUE(it != cookies_.end());
+ cookies_.erase(it);
+}
+
+void MockSimplePersistentCookieStore::Flush(const base::Closure& callback) {
+ if (!callback.is_null())
+ base::MessageLoop::current()->PostTask(FROM_HERE, callback);
+}
+
+void MockSimplePersistentCookieStore::SetForceKeepSessionState() {
+}
+
+CookieMonster* CreateMonsterFromStoreForGC(
+ int num_cookies,
+ int num_old_cookies,
+ int days_old) {
+ base::Time current(base::Time::Now());
+ base::Time past_creation(base::Time::Now() - base::TimeDelta::FromDays(1000));
+ scoped_refptr<MockSimplePersistentCookieStore> store(
+ new MockSimplePersistentCookieStore);
+ // Must expire to be persistent
+ for (int i = 0; i < num_cookies; i++) {
+ base::Time creation_time =
+ past_creation + base::TimeDelta::FromMicroseconds(i);
+ base::Time expiration_time = current + base::TimeDelta::FromDays(30);
+ base::Time last_access_time =
+ (i < num_old_cookies) ? current - base::TimeDelta::FromDays(days_old) :
+ current;
+
+ CanonicalCookie cc(
+ GURL(), "a", "1", base::StringPrintf("h%05d.izzle", i), "/path",
+ creation_time, expiration_time, last_access_time, false, false,
+ COOKIE_PRIORITY_DEFAULT);
+ store->AddCookie(cc);
+ }
+
+ return new CookieMonster(store.get(), NULL);
+}
+
+MockSimplePersistentCookieStore::~MockSimplePersistentCookieStore() {}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_monster_store_test.h b/chromium/net/cookies/cookie_monster_store_test.h
new file mode 100644
index 00000000000..d7da52104be
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster_store_test.h
@@ -0,0 +1,205 @@
+// Copyright (c) 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.
+
+// This file contains test infrastructure for multiple files
+// (current cookie_monster_unittest.cc and cookie_monster_perftest.cc)
+// that need to test out CookieMonster interactions with the backing store.
+// It should only be included by test code.
+
+#ifndef NET_COOKIES_COOKIE_MONSTER_STORE_TEST_H_
+#define NET_COOKIES_COOKIE_MONSTER_STORE_TEST_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_monster.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+// Wrapper class for posting a loaded callback. Since the Callback class is not
+// reference counted, we cannot post a callback to the message loop directly,
+// instead we post a LoadedCallbackTask.
+class LoadedCallbackTask
+ : public base::RefCountedThreadSafe<LoadedCallbackTask> {
+ public:
+ typedef CookieMonster::PersistentCookieStore::LoadedCallback LoadedCallback;
+
+ LoadedCallbackTask(LoadedCallback loaded_callback,
+ std::vector<CanonicalCookie*> cookies);
+
+ void Run() {
+ loaded_callback_.Run(cookies_);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<LoadedCallbackTask>;
+ ~LoadedCallbackTask();
+
+ LoadedCallback loaded_callback_;
+ std::vector<CanonicalCookie*> cookies_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoadedCallbackTask);
+}; // Wrapper class LoadedCallbackTask
+
+// Describes a call to one of the 3 functions of PersistentCookieStore.
+struct CookieStoreCommand {
+ enum Type {
+ ADD,
+ UPDATE_ACCESS_TIME,
+ REMOVE,
+ };
+
+ CookieStoreCommand(Type type, const CanonicalCookie& cookie)
+ : type(type),
+ cookie(cookie) {}
+
+ Type type;
+ CanonicalCookie cookie;
+};
+
+// Implementation of PersistentCookieStore that captures the
+// received commands and saves them to a list.
+// The result of calls to Load() can be configured using SetLoadExpectation().
+class MockPersistentCookieStore
+ : public CookieMonster::PersistentCookieStore {
+ public:
+ typedef std::vector<CookieStoreCommand> CommandList;
+
+ MockPersistentCookieStore();
+
+ void SetLoadExpectation(
+ bool return_value,
+ const std::vector<CanonicalCookie*>& result);
+
+ const CommandList& commands() const {
+ return commands_;
+ }
+
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
+
+ virtual void LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& loaded_callback) OVERRIDE;
+
+ virtual void AddCookie(const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void UpdateCookieAccessTime(
+ const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void DeleteCookie(
+ const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void Flush(const base::Closure& callback) OVERRIDE;
+
+ virtual void SetForceKeepSessionState() OVERRIDE;
+
+ protected:
+ virtual ~MockPersistentCookieStore();
+
+ private:
+ CommandList commands_;
+
+ // Deferred result to use when Load() is called.
+ bool load_return_value_;
+ std::vector<CanonicalCookie*> load_result_;
+ // Indicates if the store has been fully loaded to avoid returning duplicate
+ // cookies.
+ bool loaded_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPersistentCookieStore);
+};
+
+// Mock for CookieMonster::Delegate
+class MockCookieMonsterDelegate : public CookieMonster::Delegate {
+ public:
+ typedef std::pair<CanonicalCookie, bool>
+ CookieNotification;
+
+ MockCookieMonsterDelegate();
+
+ const std::vector<CookieNotification>& changes() const { return changes_; }
+
+ void reset() { changes_.clear(); }
+
+ virtual void OnCookieChanged(
+ const CanonicalCookie& cookie,
+ bool removed,
+ CookieMonster::Delegate::ChangeCause cause) OVERRIDE;
+
+ private:
+ virtual ~MockCookieMonsterDelegate();
+
+ std::vector<CookieNotification> changes_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockCookieMonsterDelegate);
+};
+
+// Helper to build a single CanonicalCookie.
+CanonicalCookie BuildCanonicalCookie(const std::string& key,
+ const std::string& cookie_line,
+ const base::Time& creation_time);
+
+// Helper to build a list of CanonicalCookie*s.
+void AddCookieToList(
+ const std::string& key,
+ const std::string& cookie_line,
+ const base::Time& creation_time,
+ std::vector<CanonicalCookie*>* out_list);
+
+// Just act like a backing database. Keep cookie information from
+// Add/Update/Delete and regurgitate it when Load is called.
+class MockSimplePersistentCookieStore
+ : public CookieMonster::PersistentCookieStore {
+ public:
+ MockSimplePersistentCookieStore();
+
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
+
+ virtual void LoadCookiesForKey(const std::string& key,
+ const LoadedCallback& loaded_callback) OVERRIDE;
+
+ virtual void AddCookie(const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void UpdateCookieAccessTime(const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void DeleteCookie(const CanonicalCookie& cookie) OVERRIDE;
+
+ virtual void Flush(const base::Closure& callback) OVERRIDE;
+
+ virtual void SetForceKeepSessionState() OVERRIDE;
+
+ protected:
+ virtual ~MockSimplePersistentCookieStore();
+
+ private:
+ typedef std::map<int64, CanonicalCookie> CanonicalCookieMap;
+
+ CanonicalCookieMap cookies_;
+
+ // Indicates if the store has been fully loaded to avoid return duplicate
+ // cookies in subsequent load requests
+ bool loaded_;
+};
+
+// Helper function for creating a CookieMonster backed by a
+// MockSimplePersistentCookieStore for garbage collection testing.
+//
+// Fill the store through import with |num_cookies| cookies, |num_old_cookies|
+// with access time Now()-days_old, the rest with access time Now().
+// Do two SetCookies(). Return whether each of the two SetCookies() took
+// longer than |gc_perf_micros| to complete, and how many cookie were
+// left in the store afterwards.
+CookieMonster* CreateMonsterFromStoreForGC(
+ int num_cookies,
+ int num_old_cookies,
+ int days_old);
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_MONSTER_STORE_TEST_H_
diff --git a/chromium/net/cookies/cookie_monster_unittest.cc b/chromium/net/cookies/cookie_monster_unittest.cc
new file mode 100644
index 00000000000..d1ce04f3885
--- /dev/null
+++ b/chromium/net/cookies/cookie_monster_unittest.cc
@@ -0,0 +1,2688 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_store_unittest.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread.h"
+#include "base/time/time.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_monster_store_test.h" // For CookieStore mock
+#include "net/cookies/cookie_util.h"
+#include "net/cookies/parsed_cookie.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+using base::Time;
+using base::TimeDelta;
+
+namespace {
+
+// TODO(erikwright): Replace the pre-existing MockPersistentCookieStore (and
+// brethren) with this one, and remove the 'New' prefix.
+class NewMockPersistentCookieStore
+ : public CookieMonster::PersistentCookieStore {
+ public:
+ MOCK_METHOD1(Load, void(const LoadedCallback& loaded_callback));
+ MOCK_METHOD2(LoadCookiesForKey, void(const std::string& key,
+ const LoadedCallback& loaded_callback));
+ MOCK_METHOD1(AddCookie, void(const CanonicalCookie& cc));
+ MOCK_METHOD1(UpdateCookieAccessTime, void(const CanonicalCookie& cc));
+ MOCK_METHOD1(DeleteCookie, void(const CanonicalCookie& cc));
+ MOCK_METHOD1(Flush, void(const base::Closure& callback));
+ MOCK_METHOD0(SetForceKeepSessionState, void());
+
+ private:
+ virtual ~NewMockPersistentCookieStore() {}
+};
+
+const char* kTopLevelDomainPlus1 = "http://www.harvard.edu";
+const char* kTopLevelDomainPlus2 = "http://www.math.harvard.edu";
+const char* kTopLevelDomainPlus2Secure = "https://www.math.harvard.edu";
+const char* kTopLevelDomainPlus3 =
+ "http://www.bourbaki.math.harvard.edu";
+const char* kOtherDomain = "http://www.mit.edu";
+const char kUrlGoogleSpecific[] = "http://www.gmail.google.izzle";
+
+class GetCookieListCallback : public CookieCallback {
+ public:
+ GetCookieListCallback() {}
+ explicit GetCookieListCallback(Thread* run_in_thread)
+ : CookieCallback(run_in_thread) {}
+
+ void Run(const CookieList& cookies) {
+ cookies_ = cookies;
+ CallbackEpilogue();
+ }
+
+ const CookieList& cookies() { return cookies_; }
+
+ private:
+ CookieList cookies_;
+};
+
+struct CookieMonsterTestTraits {
+ static scoped_refptr<CookieStore> Create() {
+ return new CookieMonster(NULL, NULL);
+ }
+
+ static const bool is_cookie_monster = true;
+ static const bool supports_http_only = true;
+ static const bool supports_cookies_with_info = true;
+ static const bool supports_non_dotted_domains = true;
+ static const bool supports_trailing_dots = true;
+ static const bool filters_schemes = true;
+ static const bool has_path_prefix_bug = false;
+ static const int creation_time_granularity_in_ms = 0;
+};
+
+INSTANTIATE_TYPED_TEST_CASE_P(CookieMonster,
+ CookieStoreTest,
+ CookieMonsterTestTraits);
+
+INSTANTIATE_TYPED_TEST_CASE_P(CookieMonster,
+ MultiThreadedCookieStoreTest,
+ CookieMonsterTestTraits);
+
+class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> {
+ protected:
+
+ CookieList GetAllCookies(CookieMonster* cm) {
+ DCHECK(cm);
+ GetCookieListCallback callback;
+ cm->GetAllCookiesAsync(
+ base::Bind(&GetCookieListCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.cookies();
+ }
+
+ CookieList GetAllCookiesForURL(CookieMonster* cm,
+ const GURL& url) {
+ DCHECK(cm);
+ GetCookieListCallback callback;
+ cm->GetAllCookiesForURLAsync(
+ url, base::Bind(&GetCookieListCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.cookies();
+ }
+
+ CookieList GetAllCookiesForURLWithOptions(CookieMonster* cm,
+ const GURL& url,
+ const CookieOptions& options) {
+ DCHECK(cm);
+ GetCookieListCallback callback;
+ cm->GetAllCookiesForURLWithOptionsAsync(
+ url, options, base::Bind(&GetCookieListCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.cookies();
+ }
+
+ bool SetCookieWithDetails(CookieMonster* cm,
+ const GURL& url,
+ const std::string& name,
+ const std::string& value,
+ const std::string& domain,
+ const std::string& path,
+ const base::Time& expiration_time,
+ bool secure,
+ bool http_only,
+ CookiePriority priority) {
+ DCHECK(cm);
+ BoolResultCookieCallback callback;
+ cm->SetCookieWithDetailsAsync(
+ url, name, value, domain, path, expiration_time, secure, http_only,
+ priority,
+ base::Bind(&BoolResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ int DeleteAll(CookieMonster*cm) {
+ DCHECK(cm);
+ IntResultCookieCallback callback;
+ cm->DeleteAllAsync(
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ int DeleteAllCreatedBetween(CookieMonster*cm,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(cm);
+ IntResultCookieCallback callback;
+ cm->DeleteAllCreatedBetweenAsync(
+ delete_begin, delete_end,
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ int DeleteAllCreatedBetweenForHost(CookieMonster* cm,
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url) {
+ DCHECK(cm);
+ IntResultCookieCallback callback;
+ cm->DeleteAllCreatedBetweenForHostAsync(
+ delete_begin, delete_end, url,
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ int DeleteAllForHost(CookieMonster* cm,
+ const GURL& url) {
+ DCHECK(cm);
+ IntResultCookieCallback callback;
+ cm->DeleteAllForHostAsync(
+ url, base::Bind(&IntResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ bool DeleteCanonicalCookie(CookieMonster* cm, const CanonicalCookie& cookie) {
+ DCHECK(cm);
+ BoolResultCookieCallback callback;
+ cm->DeleteCanonicalCookieAsync(
+ cookie,
+ base::Bind(&BoolResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ // Helper for DeleteAllForHost test; repopulates CM with same layout
+ // each time.
+ void PopulateCmForDeleteAllForHost(scoped_refptr<CookieMonster> cm) {
+ GURL url_top_level_domain_plus_1(kTopLevelDomainPlus1);
+ GURL url_top_level_domain_plus_2(kTopLevelDomainPlus2);
+ GURL url_top_level_domain_plus_2_secure(kTopLevelDomainPlus2Secure);
+ GURL url_top_level_domain_plus_3(kTopLevelDomainPlus3);
+ GURL url_other(kOtherDomain);
+
+ DeleteAll(cm.get());
+
+ // Static population for probe:
+ // * Three levels of domain cookie (.b.a, .c.b.a, .d.c.b.a)
+ // * Three levels of host cookie (w.b.a, w.c.b.a, w.d.c.b.a)
+ // * http_only cookie (w.c.b.a)
+ // * Two secure cookies (.c.b.a, w.c.b.a)
+ // * Two domain path cookies (.c.b.a/dir1, .c.b.a/dir1/dir2)
+ // * Two host path cookies (w.c.b.a/dir1, w.c.b.a/dir1/dir2)
+
+ // Domain cookies
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_1,
+ "dom_1",
+ "X",
+ ".harvard.edu",
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "dom_2",
+ "X",
+ ".math.harvard.edu",
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_3,
+ "dom_3",
+ "X",
+ ".bourbaki.math.harvard.edu",
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Host cookies
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_1,
+ "host_1",
+ "X",
+ std::string(),
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "host_2",
+ "X",
+ std::string(),
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_3,
+ "host_3",
+ "X",
+ std::string(),
+ "/",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Http_only cookie
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "httpo_check",
+ "X",
+ std::string(),
+ "/",
+ base::Time(),
+ false,
+ true,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Secure cookies
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2_secure,
+ "sec_dom",
+ "X",
+ ".math.harvard.edu",
+ "/",
+ base::Time(),
+ true,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2_secure,
+ "sec_host",
+ "X",
+ std::string(),
+ "/",
+ base::Time(),
+ true,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Domain path cookies
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "dom_path_1",
+ "X",
+ ".math.harvard.edu",
+ "/dir1",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "dom_path_2",
+ "X",
+ ".math.harvard.edu",
+ "/dir1/dir2",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Host path cookies
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "host_path_1",
+ "X",
+ std::string(),
+ "/dir1",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(this->SetCookieWithDetails(cm.get(),
+ url_top_level_domain_plus_2,
+ "host_path_2",
+ "X",
+ std::string(),
+ "/dir1/dir2",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ EXPECT_EQ(13U, this->GetAllCookies(cm.get()).size());
+ }
+
+ Time GetFirstCookieAccessDate(CookieMonster* cm) {
+ const CookieList all_cookies(this->GetAllCookies(cm));
+ return all_cookies.front().LastAccessDate();
+ }
+
+ bool FindAndDeleteCookie(CookieMonster* cm,
+ const std::string& domain,
+ const std::string& name) {
+ CookieList cookies = this->GetAllCookies(cm);
+ for (CookieList::iterator it = cookies.begin();
+ it != cookies.end(); ++it)
+ if (it->Domain() == domain && it->Name() == name)
+ return this->DeleteCanonicalCookie(cm, *it);
+ return false;
+ }
+
+ int CountInString(const std::string& str, char c) {
+ return std::count(str.begin(), str.end(), c);
+ }
+
+ void TestHostGarbageCollectHelper() {
+ int domain_max_cookies = CookieMonster::kDomainMaxCookies;
+ int domain_purge_cookies = CookieMonster::kDomainPurgeCookies;
+ const int more_than_enough_cookies =
+ (domain_max_cookies + domain_purge_cookies) * 2;
+ // Add a bunch of cookies on a single host, should purge them.
+ {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ for (int i = 0; i < more_than_enough_cookies; ++i) {
+ std::string cookie = base::StringPrintf("a%03d=b", i);
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, cookie));
+ std::string cookies = this->GetCookies(cm.get(), url_google_);
+ // Make sure we find it in the cookies.
+ EXPECT_NE(cookies.find(cookie), std::string::npos);
+ // Count the number of cookies.
+ EXPECT_LE(CountInString(cookies, '='), domain_max_cookies);
+ }
+ }
+
+ // Add a bunch of cookies on multiple hosts within a single eTLD.
+ // Should keep at least kDomainMaxCookies - kDomainPurgeCookies
+ // between them. We shouldn't go above kDomainMaxCookies for both together.
+ GURL url_google_specific(kUrlGoogleSpecific);
+ {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ for (int i = 0; i < more_than_enough_cookies; ++i) {
+ std::string cookie_general = base::StringPrintf("a%03d=b", i);
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, cookie_general));
+ std::string cookie_specific = base::StringPrintf("c%03d=b", i);
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_specific, cookie_specific));
+ std::string cookies_general = this->GetCookies(cm.get(), url_google_);
+ EXPECT_NE(cookies_general.find(cookie_general), std::string::npos);
+ std::string cookies_specific =
+ this->GetCookies(cm.get(), url_google_specific);
+ EXPECT_NE(cookies_specific.find(cookie_specific), std::string::npos);
+ EXPECT_LE((CountInString(cookies_general, '=') +
+ CountInString(cookies_specific, '=')),
+ domain_max_cookies);
+ }
+ // After all this, there should be at least
+ // kDomainMaxCookies - kDomainPurgeCookies for both URLs.
+ std::string cookies_general = this->GetCookies(cm.get(), url_google_);
+ std::string cookies_specific =
+ this->GetCookies(cm.get(), url_google_specific);
+ int total_cookies = (CountInString(cookies_general, '=') +
+ CountInString(cookies_specific, '='));
+ EXPECT_GE(total_cookies, domain_max_cookies - domain_purge_cookies);
+ EXPECT_LE(total_cookies, domain_max_cookies);
+ }
+ }
+
+ CookiePriority CharToPriority(char ch) {
+ switch (ch) {
+ case 'L':
+ return COOKIE_PRIORITY_LOW;
+ case 'M':
+ return COOKIE_PRIORITY_MEDIUM;
+ case 'H':
+ return COOKIE_PRIORITY_HIGH;
+ }
+ NOTREACHED();
+ return COOKIE_PRIORITY_DEFAULT;
+ }
+
+ // Instantiates a CookieMonster, adds multiple cookies (to url_google_) with
+ // priorities specified by |coded_priority_str|, and tests priority-aware
+ // domain cookie eviction.
+ // |coded_priority_str| specifies a run-length-encoded string of priorities.
+ // Example: "2M 3L M 4H" means "MMLLLMHHHH", and speicifies sequential (i.e.,
+ // from least- to most-recently accessed) insertion of 2 medium-priority
+ // cookies, 3 low-priority cookies, 1 medium-priority cookie, and 4
+ // high-priority cookies.
+ // Within each priority, only the least-accessed cookies should be evicted.
+ // Thus, to describe expected suriving cookies, it suffices to specify the
+ // expected population of surviving cookies per priority, i.e.,
+ // |expected_low_count|, |expected_medium_count|, and |expected_high_count|.
+ void TestPriorityCookieCase(CookieMonster* cm,
+ const std::string& coded_priority_str,
+ size_t expected_low_count,
+ size_t expected_medium_count,
+ size_t expected_high_count) {
+ DeleteAll(cm);
+ int next_cookie_id = 0;
+ std::vector<CookiePriority> priority_list;
+ std::vector<int> id_list[3]; // Indexed by CookiePriority.
+
+ // Parse |coded_priority_str| and add cookies.
+ std::vector<std::string> priority_tok_list;
+ base::SplitString(coded_priority_str, ' ', &priority_tok_list);
+ for (std::vector<std::string>::iterator it = priority_tok_list.begin();
+ it != priority_tok_list.end(); ++it) {
+ size_t len = it->length();
+ DCHECK_NE(len, 0U);
+ // Take last character as priority.
+ CookiePriority priority = CharToPriority((*it)[len - 1]);
+ std::string priority_str = CookiePriorityToString(priority);
+ // The rest of the string (possibly empty) specifies repetition.
+ int rep = 1;
+ if (!it->empty()) {
+ bool result = base::StringToInt(
+ base::StringPiece(it->begin(), it->end() - 1), &rep);
+ DCHECK(result);
+ }
+ for (; rep > 0; --rep, ++next_cookie_id) {
+ std::string cookie = base::StringPrintf(
+ "a%d=b;priority=%s", next_cookie_id, priority_str.c_str());
+ EXPECT_TRUE(SetCookie(cm, url_google_, cookie));
+ priority_list.push_back(priority);
+ id_list[priority].push_back(next_cookie_id);
+ }
+ }
+
+ int num_cookies = static_cast<int>(priority_list.size());
+ std::vector<int> surviving_id_list[3]; // Indexed by CookiePriority.
+
+ // Parse the list of cookies
+ std::string cookie_str = this->GetCookies(cm, url_google_);
+ std::vector<std::string> cookie_tok_list;
+ base::SplitString(cookie_str, ';', &cookie_tok_list);
+ for (std::vector<std::string>::iterator it = cookie_tok_list.begin();
+ it != cookie_tok_list.end(); ++it) {
+ // Assuming *it is "a#=b", so extract and parse "#" portion.
+ int id = -1;
+ bool result = base::StringToInt(
+ base::StringPiece(it->begin() + 1, it->end() - 2), &id);
+ DCHECK(result);
+ DCHECK_GE(id, 0);
+ DCHECK_LT(id, num_cookies);
+ surviving_id_list[priority_list[id]].push_back(id);
+ }
+
+ // Validate each priority.
+ size_t expected_count[3] = {
+ expected_low_count, expected_medium_count, expected_high_count
+ };
+ for (int i = 0; i < 3; ++i) {
+ DCHECK_LE(surviving_id_list[i].size(), id_list[i].size());
+ EXPECT_EQ(expected_count[i], surviving_id_list[i].size());
+ // Verify that the remaining cookies are the most recent among those
+ // with the same priorities.
+ if (expected_count[i] == surviving_id_list[i].size()) {
+ std::sort(surviving_id_list[i].begin(), surviving_id_list[i].end());
+ EXPECT_TRUE(std::equal(surviving_id_list[i].begin(),
+ surviving_id_list[i].end(),
+ id_list[i].end() - expected_count[i]));
+ }
+ }
+ }
+
+ void TestPriorityAwareGarbageCollectHelper() {
+ // Hard-coding limits in the test, but use DCHECK_EQ to enforce constraint.
+ DCHECK_EQ(180U, CookieMonster::kDomainMaxCookies);
+ DCHECK_EQ(150U, CookieMonster::kDomainMaxCookies -
+ CookieMonster::kDomainPurgeCookies);
+ DCHECK_EQ(30U, CookieMonster::kDomainCookiesQuotaLow);
+ DCHECK_EQ(50U, CookieMonster::kDomainCookiesQuotaMedium);
+ DCHECK_EQ(70U, CookieMonster::kDomainCookiesQuotaHigh);
+
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ cm->SetPriorityAwareGarbageCollection(true);
+
+ // Each test case adds 181 cookies, so 31 cookies are evicted.
+ // Cookie same priority, repeated for each priority.
+ TestPriorityCookieCase(cm.get(), "181L", 150U, 0U, 0U);
+ TestPriorityCookieCase(cm.get(), "181M", 0U, 150U, 0U);
+ TestPriorityCookieCase(cm.get(), "181H", 0U, 0U, 150U);
+
+ // Pairwise scenarios.
+ // Round 1 => none; round2 => 31M; round 3 => none.
+ TestPriorityCookieCase(cm.get(), "10H 171M", 0U, 140U, 10U);
+ // Round 1 => 10L; round2 => 21M; round 3 => none.
+ TestPriorityCookieCase(cm.get(), "141M 40L", 30U, 120U, 0U);
+ // Round 1 => none; round2 => none; round 3 => 31H.
+ TestPriorityCookieCase(cm.get(), "101H 80M", 0U, 80U, 70U);
+
+ // For {low, medium} priorities right on quota, different orders.
+ // Round 1 => 1L; round 2 => none, round3 => 30L.
+ TestPriorityCookieCase(cm.get(), "31L 50M 100H", 0U, 50U, 100U);
+ // Round 1 => none; round 2 => 1M, round3 => 30M.
+ TestPriorityCookieCase(cm.get(), "51M 100H 30L", 30U, 20U, 100U);
+ // Round 1 => none; round 2 => none; round3 => 31H.
+ TestPriorityCookieCase(cm.get(), "101H 50M 30L", 30U, 50U, 70U);
+
+ // Round 1 => 10L; round 2 => 10M; round3 => 11H.
+ TestPriorityCookieCase(cm.get(), "81H 60M 40L", 30U, 50U, 70U);
+
+ // More complex scenarios.
+ // Round 1 => 10L; round 2 => 10M; round 3 => 11H.
+ TestPriorityCookieCase(cm.get(), "21H 60M 40L 60H", 30U, 50U, 70U);
+ // Round 1 => 10L; round 2 => 11M, 10L; round 3 => none.
+ TestPriorityCookieCase(
+ cm.get(), "11H 10M 20L 110M 20L 10H", 20U, 109U, 21U);
+ // Round 1 => none; round 2 => none; round 3 => 11L, 10M, 10H.
+ TestPriorityCookieCase(cm.get(), "11L 10M 140H 10M 10L", 10U, 10U, 130U);
+ // Round 1 => none; round 2 => 1M; round 3 => 10L, 10M, 10H.
+ TestPriorityCookieCase(cm.get(), "11M 10H 10L 60M 90H", 0U, 60U, 90U);
+ // Round 1 => none; round 2 => 10L, 21M; round 3 => none.
+ TestPriorityCookieCase(cm.get(), "11M 10H 10L 90M 60H", 0U, 80U, 70U);
+ }
+
+ // Function for creating a CM with a number of cookies in it,
+ // no store (and hence no ability to affect access time).
+ CookieMonster* CreateMonsterForGC(int num_cookies) {
+ CookieMonster* cm(new CookieMonster(NULL, NULL));
+ for (int i = 0; i < num_cookies; i++) {
+ SetCookie(cm, GURL(base::StringPrintf("http://h%05d.izzle", i)), "a=1");
+ }
+ return cm;
+ }
+};
+
+// TODO(erikwright): Replace the other callbacks and synchronous helper methods
+// in this test suite with these Mocks.
+template<typename T, typename C> class MockCookieCallback {
+ public:
+ C AsCallback() {
+ return base::Bind(&T::Invoke, base::Unretained(static_cast<T*>(this)));
+ }
+};
+
+class MockGetCookiesCallback
+ : public MockCookieCallback<MockGetCookiesCallback,
+ CookieStore::GetCookiesCallback> {
+ public:
+ MOCK_METHOD1(Invoke, void(const std::string& cookies));
+};
+
+class MockSetCookiesCallback
+ : public MockCookieCallback<MockSetCookiesCallback,
+ CookieStore::SetCookiesCallback> {
+ public:
+ MOCK_METHOD1(Invoke, void(bool success));
+};
+
+class MockClosure
+ : public MockCookieCallback<MockClosure, base::Closure> {
+ public:
+ MOCK_METHOD0(Invoke, void(void));
+};
+
+class MockGetCookieListCallback
+ : public MockCookieCallback<MockGetCookieListCallback,
+ CookieMonster::GetCookieListCallback> {
+ public:
+ MOCK_METHOD1(Invoke, void(const CookieList& cookies));
+};
+
+class MockDeleteCallback
+ : public MockCookieCallback<MockDeleteCallback,
+ CookieMonster::DeleteCallback> {
+ public:
+ MOCK_METHOD1(Invoke, void(int num_deleted));
+};
+
+class MockDeleteCookieCallback
+ : public MockCookieCallback<MockDeleteCookieCallback,
+ CookieMonster::DeleteCookieCallback> {
+ public:
+ MOCK_METHOD1(Invoke, void(bool success));
+};
+
+struct CookiesInputInfo {
+ const GURL url;
+ const std::string name;
+ const std::string value;
+ const std::string domain;
+ const std::string path;
+ const base::Time expiration_time;
+ bool secure;
+ bool http_only;
+ CookiePriority priority;
+};
+
+ACTION(QuitCurrentMessageLoop) {
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+}
+
+// TODO(erikwright): When the synchronous helpers 'GetCookies' etc. are removed,
+// rename these, removing the 'Action' suffix.
+ACTION_P4(DeleteCookieAction, cookie_monster, url, name, callback) {
+ cookie_monster->DeleteCookieAsync(url, name, callback->AsCallback());
+}
+ACTION_P3(GetCookiesAction, cookie_monster, url, callback) {
+ cookie_monster->GetCookiesWithOptionsAsync(
+ url, CookieOptions(), callback->AsCallback());
+}
+ACTION_P4(SetCookieAction, cookie_monster, url, cookie_line, callback) {
+ cookie_monster->SetCookieWithOptionsAsync(
+ url, cookie_line, CookieOptions(), callback->AsCallback());
+}
+ACTION_P4(DeleteAllCreatedBetweenAction,
+ cookie_monster, delete_begin, delete_end, callback) {
+ cookie_monster->DeleteAllCreatedBetweenAsync(
+ delete_begin, delete_end, callback->AsCallback());
+}
+ACTION_P3(SetCookieWithDetailsAction, cookie_monster, cc, callback) {
+ cookie_monster->SetCookieWithDetailsAsync(
+ cc.url, cc.name, cc.value, cc.domain, cc.path, cc.expiration_time,
+ cc.secure, cc.http_only, cc.priority,
+ callback->AsCallback());
+}
+
+ACTION_P2(GetAllCookiesAction, cookie_monster, callback) {
+ cookie_monster->GetAllCookiesAsync(callback->AsCallback());
+}
+
+ACTION_P3(DeleteAllForHostAction, cookie_monster, url, callback) {
+ cookie_monster->DeleteAllForHostAsync(url, callback->AsCallback());
+}
+
+ACTION_P3(DeleteCanonicalCookieAction, cookie_monster, cookie, callback) {
+ cookie_monster->DeleteCanonicalCookieAsync(cookie, callback->AsCallback());
+}
+
+ACTION_P2(DeleteAllAction, cookie_monster, callback) {
+ cookie_monster->DeleteAllAsync(callback->AsCallback());
+}
+
+ACTION_P3(GetAllCookiesForUrlWithOptionsAction, cookie_monster, url, callback) {
+ cookie_monster->GetAllCookiesForURLWithOptionsAsync(
+ url, CookieOptions(), callback->AsCallback());
+}
+
+ACTION_P3(GetAllCookiesForUrlAction, cookie_monster, url, callback) {
+ cookie_monster->GetAllCookiesForURLAsync(url, callback->AsCallback());
+}
+
+ACTION_P(PushCallbackAction, callback_vector) {
+ callback_vector->push(arg1);
+}
+
+ACTION_P2(DeleteSessionCookiesAction, cookie_monster, callback) {
+ cookie_monster->DeleteSessionCookiesAsync(callback->AsCallback());
+}
+
+} // namespace
+
+// This test suite verifies the task deferral behaviour of the CookieMonster.
+// Specifically, for each asynchronous method, verify that:
+// 1. invoking it on an uninitialized cookie store causes the store to begin
+// chain-loading its backing data or loading data for a specific domain key
+// (eTLD+1).
+// 2. The initial invocation does not complete until the loading completes.
+// 3. Invocations after the loading has completed complete immediately.
+class DeferredCookieTaskTest : public CookieMonsterTest {
+ protected:
+ DeferredCookieTaskTest() {
+ persistent_store_ = new NewMockPersistentCookieStore();
+ cookie_monster_ = new CookieMonster(persistent_store_.get(), NULL);
+ }
+
+ // Defines a cookie to be returned from PersistentCookieStore::Load
+ void DeclareLoadedCookie(const std::string& key,
+ const std::string& cookie_line,
+ const base::Time& creation_time) {
+ AddCookieToList(key, cookie_line, creation_time, &loaded_cookies_);
+ }
+
+ // Runs the message loop, waiting until PersistentCookieStore::Load is called.
+ // Call CompleteLoadingAndWait to cause the load to complete.
+ void WaitForLoadCall() {
+ RunFor(kTimeout);
+
+ // Verify that PeristentStore::Load was called.
+ testing::Mock::VerifyAndClear(persistent_store_.get());
+ }
+
+ // Invokes the PersistentCookieStore::LoadCookiesForKey completion callbacks
+ // and PersistentCookieStore::Load completion callback and waits
+ // until the message loop is quit.
+ void CompleteLoadingAndWait() {
+ while (!loaded_for_key_callbacks_.empty()) {
+ loaded_for_key_callbacks_.front().Run(loaded_cookies_);
+ loaded_cookies_.clear();
+ loaded_for_key_callbacks_.pop();
+ }
+
+ loaded_callback_.Run(loaded_cookies_);
+ RunFor(kTimeout);
+ }
+
+ // Performs the provided action, expecting it to cause a call to
+ // PersistentCookieStore::Load. Call WaitForLoadCall to verify the load call
+ // is received.
+ void BeginWith(testing::Action<void(void)> action) {
+ EXPECT_CALL(*this, Begin()).WillOnce(action);
+ ExpectLoadCall();
+ Begin();
+ }
+
+ void BeginWithForDomainKey(std::string key,
+ testing::Action<void(void)> action) {
+ EXPECT_CALL(*this, Begin()).WillOnce(action);
+ ExpectLoadCall();
+ ExpectLoadForKeyCall(key, false);
+ Begin();
+ }
+
+ // Declares an expectation that PersistentCookieStore::Load will be called,
+ // saving the provided callback and sending a quit to the message loop.
+ void ExpectLoadCall() {
+ EXPECT_CALL(*persistent_store_.get(), Load(testing::_))
+ .WillOnce(testing::DoAll(testing::SaveArg<0>(&loaded_callback_),
+ QuitCurrentMessageLoop()));
+ }
+
+ // Declares an expectation that PersistentCookieStore::LoadCookiesForKey
+ // will be called, saving the provided callback and sending a quit to the
+ // message loop.
+ void ExpectLoadForKeyCall(std::string key, bool quit_queue) {
+ if (quit_queue)
+ EXPECT_CALL(*persistent_store_.get(), LoadCookiesForKey(key, testing::_))
+ .WillOnce(
+ testing::DoAll(PushCallbackAction(&loaded_for_key_callbacks_),
+ QuitCurrentMessageLoop()));
+ else
+ EXPECT_CALL(*persistent_store_.get(), LoadCookiesForKey(key, testing::_))
+ .WillOnce(PushCallbackAction(&loaded_for_key_callbacks_));
+ }
+
+ // Invokes the initial action.
+ MOCK_METHOD0(Begin, void(void));
+
+ // Returns the CookieMonster instance under test.
+ CookieMonster& cookie_monster() { return *cookie_monster_.get(); }
+
+ private:
+ // Declares that mock expectations in this test suite are strictly ordered.
+ testing::InSequence in_sequence_;
+ // Holds cookies to be returned from PersistentCookieStore::Load or
+ // PersistentCookieStore::LoadCookiesForKey.
+ std::vector<CanonicalCookie*> loaded_cookies_;
+ // Stores the callback passed from the CookieMonster to the
+ // PersistentCookieStore::Load
+ CookieMonster::PersistentCookieStore::LoadedCallback loaded_callback_;
+ // Stores the callback passed from the CookieMonster to the
+ // PersistentCookieStore::LoadCookiesForKey
+ std::queue<CookieMonster::PersistentCookieStore::LoadedCallback>
+ loaded_for_key_callbacks_;
+
+ // Stores the CookieMonster under test.
+ scoped_refptr<CookieMonster> cookie_monster_;
+ // Stores the mock PersistentCookieStore.
+ scoped_refptr<NewMockPersistentCookieStore> persistent_store_;
+};
+
+TEST_F(DeferredCookieTaskTest, DeferredGetCookies) {
+ DeclareLoadedCookie("www.google.izzle",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3));
+
+ MockGetCookiesCallback get_cookies_callback;
+
+ BeginWithForDomainKey("google.izzle", GetCookiesAction(
+ &cookie_monster(), url_google_, &get_cookies_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(get_cookies_callback, Invoke("X=1")).WillOnce(
+ GetCookiesAction(&cookie_monster(), url_google_, &get_cookies_callback));
+ EXPECT_CALL(get_cookies_callback, Invoke("X=1")).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredSetCookie) {
+ MockSetCookiesCallback set_cookies_callback;
+
+ BeginWithForDomainKey("google.izzle", SetCookieAction(
+ &cookie_monster(), url_google_, "A=B", &set_cookies_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(set_cookies_callback, Invoke(true)).WillOnce(
+ SetCookieAction(
+ &cookie_monster(), url_google_, "X=Y", &set_cookies_callback));
+ EXPECT_CALL(set_cookies_callback, Invoke(true)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteCookie) {
+ MockClosure delete_cookie_callback;
+
+ BeginWithForDomainKey("google.izzle", DeleteCookieAction(
+ &cookie_monster(), url_google_, "A", &delete_cookie_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_cookie_callback, Invoke()).WillOnce(
+ DeleteCookieAction(
+ &cookie_monster(), url_google_, "X", &delete_cookie_callback));
+ EXPECT_CALL(delete_cookie_callback, Invoke()).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredSetCookieWithDetails) {
+ MockSetCookiesCallback set_cookies_callback;
+
+ CookiesInputInfo cookie_info = {
+ url_google_foo_, "A", "B", std::string(), "/foo",
+ base::Time(), false, false, COOKIE_PRIORITY_DEFAULT
+ };
+ BeginWithForDomainKey("google.izzle", SetCookieWithDetailsAction(
+ &cookie_monster(), cookie_info, &set_cookies_callback));
+
+ WaitForLoadCall();
+
+ CookiesInputInfo cookie_info_exp = {
+ url_google_foo_, "A", "B", std::string(), "/foo",
+ base::Time(), false, false, COOKIE_PRIORITY_DEFAULT
+ };
+ EXPECT_CALL(set_cookies_callback, Invoke(true)).WillOnce(
+ SetCookieWithDetailsAction(
+ &cookie_monster(), cookie_info_exp, &set_cookies_callback));
+ EXPECT_CALL(set_cookies_callback, Invoke(true)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredGetAllCookies) {
+ DeclareLoadedCookie("www.google.izzle",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3));
+
+ MockGetCookieListCallback get_cookie_list_callback;
+
+ BeginWith(GetAllCookiesAction(
+ &cookie_monster(), &get_cookie_list_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ GetAllCookiesAction(&cookie_monster(), &get_cookie_list_callback));
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) {
+ DeclareLoadedCookie("www.google.izzle",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3));
+
+ MockGetCookieListCallback get_cookie_list_callback;
+
+ BeginWithForDomainKey("google.izzle", GetAllCookiesForUrlAction(
+ &cookie_monster(), url_google_, &get_cookie_list_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ GetAllCookiesForUrlAction(
+ &cookie_monster(), url_google_, &get_cookie_list_callback));
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) {
+ DeclareLoadedCookie("www.google.izzle",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3));
+
+ MockGetCookieListCallback get_cookie_list_callback;
+
+ BeginWithForDomainKey("google.izzle", GetAllCookiesForUrlWithOptionsAction(
+ &cookie_monster(), url_google_, &get_cookie_list_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ GetAllCookiesForUrlWithOptionsAction(
+ &cookie_monster(), url_google_, &get_cookie_list_callback));
+ EXPECT_CALL(get_cookie_list_callback, Invoke(testing::_)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteAllCookies) {
+ MockDeleteCallback delete_callback;
+
+ BeginWith(DeleteAllAction(
+ &cookie_monster(), &delete_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ DeleteAllAction(&cookie_monster(), &delete_callback));
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteAllCreatedBetweenCookies) {
+ MockDeleteCallback delete_callback;
+
+ BeginWith(DeleteAllCreatedBetweenAction(
+ &cookie_monster(), base::Time(), base::Time::Now(), &delete_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ DeleteAllCreatedBetweenAction(
+ &cookie_monster(), base::Time(), base::Time::Now(),
+ &delete_callback));
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteAllForHostCookies) {
+ MockDeleteCallback delete_callback;
+
+ BeginWithForDomainKey("google.izzle", DeleteAllForHostAction(
+ &cookie_monster(), url_google_, &delete_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ DeleteAllForHostAction(
+ &cookie_monster(), url_google_, &delete_callback));
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteCanonicalCookie) {
+ std::vector<CanonicalCookie*> cookies;
+ CanonicalCookie cookie = BuildCanonicalCookie(
+ "www.google.com", "X=1; path=/", base::Time::Now());
+
+ MockDeleteCookieCallback delete_cookie_callback;
+
+ BeginWith(DeleteCanonicalCookieAction(
+ &cookie_monster(), cookie, &delete_cookie_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_cookie_callback, Invoke(false)).WillOnce(
+ DeleteCanonicalCookieAction(
+ &cookie_monster(), cookie, &delete_cookie_callback));
+ EXPECT_CALL(delete_cookie_callback, Invoke(false)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(DeferredCookieTaskTest, DeferredDeleteSessionCookies) {
+ MockDeleteCallback delete_callback;
+
+ BeginWith(DeleteSessionCookiesAction(
+ &cookie_monster(), &delete_callback));
+
+ WaitForLoadCall();
+
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ DeleteSessionCookiesAction(&cookie_monster(), &delete_callback));
+ EXPECT_CALL(delete_callback, Invoke(false)).WillOnce(
+ QuitCurrentMessageLoop());
+
+ CompleteLoadingAndWait();
+}
+
+// Verify that a series of queued tasks are executed in order upon loading of
+// the backing store and that new tasks received while the queued tasks are
+// being dispatched go to the end of the queue.
+TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) {
+ DeclareLoadedCookie("www.google.izzle",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3));
+
+ MockGetCookiesCallback get_cookies_callback;
+ MockSetCookiesCallback set_cookies_callback;
+ MockClosure delete_cookie_callback;
+ MockGetCookiesCallback get_cookies_callback_deferred;
+
+ EXPECT_CALL(*this, Begin()).WillOnce(testing::DoAll(
+ GetCookiesAction(
+ &cookie_monster(), url_google_, &get_cookies_callback),
+ SetCookieAction(
+ &cookie_monster(), url_google_, "A=B", &set_cookies_callback),
+ DeleteCookieAction(
+ &cookie_monster(), url_google_, "A", &delete_cookie_callback)));
+ ExpectLoadCall();
+ ExpectLoadForKeyCall("google.izzle", false);
+ Begin();
+
+ WaitForLoadCall();
+ EXPECT_CALL(get_cookies_callback, Invoke("X=1")).WillOnce(
+ GetCookiesAction(
+ &cookie_monster(), url_google_, &get_cookies_callback_deferred));
+ EXPECT_CALL(get_cookies_callback_deferred, Invoke("X=1")).WillOnce(
+ QuitCurrentMessageLoop());
+ EXPECT_CALL(set_cookies_callback, Invoke(true));
+ EXPECT_CALL(delete_cookie_callback, Invoke());
+
+ CompleteLoadingAndWait();
+}
+
+TEST_F(CookieMonsterTest, TestCookieDeleteAll) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+ CookieOptions options;
+ options.set_include_httponly();
+
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, kValidCookieLine));
+ EXPECT_EQ("A=B", GetCookies(cm.get(), url_google_));
+
+ EXPECT_TRUE(
+ SetCookieWithOptions(cm.get(), url_google_, "C=D; httponly", options));
+ EXPECT_EQ("A=B; C=D", GetCookiesWithOptions(cm.get(), url_google_, options));
+
+ EXPECT_EQ(2, DeleteAll(cm.get()));
+ EXPECT_EQ("", GetCookiesWithOptions(cm.get(), url_google_, options));
+ EXPECT_EQ(0u, store->commands().size());
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(SetCookie(
+ cm.get(),
+ url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+
+ EXPECT_EQ(1, DeleteAll(cm.get())); // sync_to_store = true.
+ ASSERT_EQ(2u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+
+ EXPECT_EQ("", GetCookiesWithOptions(cm.get(), url_google_, options));
+}
+
+TEST_F(CookieMonsterTest, TestCookieDeleteAllCreatedBetweenTimestamps) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ Time now = Time::Now();
+
+ // Nothing has been added so nothing should be deleted.
+ EXPECT_EQ(
+ 0,
+ DeleteAllCreatedBetween(cm.get(), now - TimeDelta::FromDays(99), Time()));
+
+ // Create 3 cookies with creation date of today, yesterday and the day before.
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "T-0=Now", now));
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "T-1=Yesterday",
+ now - TimeDelta::FromDays(1)));
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "T-2=DayBefore",
+ now - TimeDelta::FromDays(2)));
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "T-3=ThreeDays",
+ now - TimeDelta::FromDays(3)));
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "T-7=LastWeek",
+ now - TimeDelta::FromDays(7)));
+
+ // Try to delete threedays and the daybefore.
+ EXPECT_EQ(2,
+ DeleteAllCreatedBetween(cm.get(),
+ now - TimeDelta::FromDays(3),
+ now - TimeDelta::FromDays(1)));
+
+ // Try to delete yesterday, also make sure that delete_end is not
+ // inclusive.
+ EXPECT_EQ(
+ 1, DeleteAllCreatedBetween(cm.get(), now - TimeDelta::FromDays(2), now));
+
+ // Make sure the delete_begin is inclusive.
+ EXPECT_EQ(
+ 1, DeleteAllCreatedBetween(cm.get(), now - TimeDelta::FromDays(7), now));
+
+ // Delete the last (now) item.
+ EXPECT_EQ(1, DeleteAllCreatedBetween(cm.get(), Time(), Time()));
+
+ // Really make sure everything is gone.
+ EXPECT_EQ(0, DeleteAll(cm.get()));
+}
+
+static const int kAccessDelayMs = kLastAccessThresholdMilliseconds + 20;
+
+TEST_F(CookieMonsterTest, TestLastAccess) {
+ scoped_refptr<CookieMonster> cm(
+ new CookieMonster(NULL, NULL, kLastAccessThresholdMilliseconds));
+
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ const Time last_access_date(GetFirstCookieAccessDate(cm.get()));
+
+ // Reading the cookie again immediately shouldn't update the access date,
+ // since we're inside the threshold.
+ EXPECT_EQ("A=B", GetCookies(cm.get(), url_google_));
+ EXPECT_TRUE(last_access_date == GetFirstCookieAccessDate(cm.get()));
+
+ // Reading after a short wait should update the access date.
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMilliseconds(kAccessDelayMs));
+ EXPECT_EQ("A=B", GetCookies(cm.get(), url_google_));
+ EXPECT_FALSE(last_access_date == GetFirstCookieAccessDate(cm.get()));
+}
+
+TEST_F(CookieMonsterTest, TestHostGarbageCollection) {
+ TestHostGarbageCollectHelper();
+}
+
+TEST_F(CookieMonsterTest, TestPriorityAwareGarbageCollection) {
+ TestPriorityAwareGarbageCollectHelper();
+}
+
+TEST_F(CookieMonsterTest, TestDeleteSingleCookie) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "C=D"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "E=F"));
+ EXPECT_EQ("A=B; C=D; E=F", GetCookies(cm.get(), url_google_));
+
+ EXPECT_TRUE(FindAndDeleteCookie(cm.get(), url_google_.host(), "C"));
+ EXPECT_EQ("A=B; E=F", GetCookies(cm.get(), url_google_));
+
+ EXPECT_FALSE(FindAndDeleteCookie(cm.get(), "random.host", "E"));
+ EXPECT_EQ("A=B; E=F", GetCookies(cm.get(), url_google_));
+}
+
+TEST_F(CookieMonsterTest, SetCookieableSchemes) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ scoped_refptr<CookieMonster> cm_foo(new CookieMonster(NULL, NULL));
+
+ // Only cm_foo should allow foo:// cookies.
+ const char* kSchemes[] = {"foo"};
+ cm_foo->SetCookieableSchemes(kSchemes, 1);
+
+ GURL foo_url("foo://host/path");
+ GURL http_url("http://host/path");
+
+ EXPECT_TRUE(SetCookie(cm.get(), http_url, "x=1"));
+ EXPECT_FALSE(SetCookie(cm.get(), foo_url, "x=1"));
+ EXPECT_TRUE(SetCookie(cm_foo.get(), foo_url, "x=1"));
+ EXPECT_FALSE(SetCookie(cm_foo.get(), http_url, "x=1"));
+}
+
+TEST_F(CookieMonsterTest, GetAllCookiesForURL) {
+ scoped_refptr<CookieMonster> cm(
+ new CookieMonster(NULL, NULL, kLastAccessThresholdMilliseconds));
+
+ // Create an httponly cookie.
+ CookieOptions options;
+ options.set_include_httponly();
+
+ EXPECT_TRUE(
+ SetCookieWithOptions(cm.get(), url_google_, "A=B; httponly", options));
+ EXPECT_TRUE(SetCookieWithOptions(
+ cm.get(), url_google_, "C=D; domain=.google.izzle", options));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(),
+ url_google_secure_,
+ "E=F; domain=.google.izzle; secure",
+ options));
+
+ const Time last_access_date(GetFirstCookieAccessDate(cm.get()));
+
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMilliseconds(kAccessDelayMs));
+
+ // Check cookies for url.
+ CookieList cookies = GetAllCookiesForURL(cm.get(), url_google_);
+ CookieList::iterator it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("C", it->Name());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ // Check cookies for url excluding http-only cookies.
+ cookies =
+ GetAllCookiesForURLWithOptions(cm.get(), url_google_, CookieOptions());
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("C", it->Name());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ // Test secure cookies.
+ cookies = GetAllCookiesForURL(cm.get(), url_google_secure_);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("C", it->Name());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("E", it->Name());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ // Reading after a short wait should not update the access date.
+ EXPECT_TRUE(last_access_date == GetFirstCookieAccessDate(cm.get()));
+}
+
+TEST_F(CookieMonsterTest, GetAllCookiesForURLPathMatching) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+
+ EXPECT_TRUE(SetCookieWithOptions(
+ cm.get(), url_google_foo_, "A=B; path=/foo;", options));
+ EXPECT_TRUE(SetCookieWithOptions(
+ cm.get(), url_google_bar_, "C=D; path=/bar;", options));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "E=F;", options));
+
+ CookieList cookies = GetAllCookiesForURL(cm.get(), url_google_foo_);
+ CookieList::iterator it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("A", it->Name());
+ EXPECT_EQ("/foo", it->Path());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("/", it->Path());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = GetAllCookiesForURL(cm.get(), url_google_bar_);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("C", it->Name());
+ EXPECT_EQ("/bar", it->Path());
+
+ ASSERT_TRUE(++it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("/", it->Path());
+
+ ASSERT_TRUE(++it == cookies.end());
+}
+
+TEST_F(CookieMonsterTest, DeleteCookieByName) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=A1; path=/"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=A2; path=/foo"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=A3; path=/bar"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "B=B1; path=/"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "B=B2; path=/foo"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "B=B3; path=/bar"));
+
+ DeleteCookie(cm.get(), GURL(std::string(kUrlGoogle) + "/foo/bar"), "A");
+
+ CookieList cookies = GetAllCookies(cm.get());
+ size_t expected_size = 4;
+ EXPECT_EQ(expected_size, cookies.size());
+ for (CookieList::iterator it = cookies.begin();
+ it != cookies.end(); ++it) {
+ EXPECT_NE("A1", it->Value());
+ EXPECT_NE("A2", it->Value());
+ }
+}
+
+TEST_F(CookieMonsterTest, InitializeFromCookieMonster) {
+ scoped_refptr<CookieMonster> cm_1(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+
+ EXPECT_TRUE(SetCookieWithOptions(cm_1.get(), url_google_foo_,
+ "A1=B; path=/foo;",
+ options));
+ EXPECT_TRUE(SetCookieWithOptions(cm_1.get(), url_google_bar_,
+ "A2=D; path=/bar;",
+ options));
+ EXPECT_TRUE(SetCookieWithOptions(cm_1.get(), url_google_,
+ "A3=F;",
+ options));
+
+ CookieList cookies_1 = GetAllCookies(cm_1.get());
+ scoped_refptr<CookieMonster> cm_2(new CookieMonster(NULL, NULL));
+ ASSERT_TRUE(cm_2->InitializeFrom(cookies_1));
+ CookieList cookies_2 = GetAllCookies(cm_2.get());
+
+ size_t expected_size = 3;
+ EXPECT_EQ(expected_size, cookies_2.size());
+
+ CookieList::iterator it = cookies_2.begin();
+
+ ASSERT_TRUE(it != cookies_2.end());
+ EXPECT_EQ("A1", it->Name());
+ EXPECT_EQ("/foo", it->Path());
+
+ ASSERT_TRUE(++it != cookies_2.end());
+ EXPECT_EQ("A2", it->Name());
+ EXPECT_EQ("/bar", it->Path());
+
+ ASSERT_TRUE(++it != cookies_2.end());
+ EXPECT_EQ("A3", it->Name());
+ EXPECT_EQ("/", it->Path());
+}
+
+// Tests importing from a persistent cookie store that contains duplicate
+// equivalent cookies. This situation should be handled by removing the
+// duplicate cookie (both from the in-memory cache, and from the backing store).
+//
+// This is a regression test for: http://crbug.com/17855.
+TEST_F(CookieMonsterTest, DontImportDuplicateCookies) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+
+ // We will fill some initial cookies into the PersistentCookieStore,
+ // to simulate a database with 4 duplicates. Note that we need to
+ // be careful not to have any duplicate creation times at all (as it's a
+ // violation of a CookieMonster invariant) even if Time::Now() doesn't
+ // move between calls.
+ std::vector<CanonicalCookie*> initial_cookies;
+
+ // Insert 4 cookies with name "X" on path "/", with varying creation
+ // dates. We expect only the most recent one to be preserved following
+ // the import.
+
+ AddCookieToList("www.google.com",
+ "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(3),
+ &initial_cookies);
+
+ AddCookieToList("www.google.com",
+ "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(1),
+ &initial_cookies);
+
+ // ===> This one is the WINNER (biggest creation time). <====
+ AddCookieToList("www.google.com",
+ "X=3; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(4),
+ &initial_cookies);
+
+ AddCookieToList("www.google.com",
+ "X=4; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now(),
+ &initial_cookies);
+
+ // Insert 2 cookies with name "X" on path "/2", with varying creation
+ // dates. We expect only the most recent one to be preserved the import.
+
+ // ===> This one is the WINNER (biggest creation time). <====
+ AddCookieToList("www.google.com",
+ "X=a1; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(9),
+ &initial_cookies);
+
+ AddCookieToList("www.google.com",
+ "X=a2; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(2),
+ &initial_cookies);
+
+ // Insert 1 cookie with name "Y" on path "/".
+ AddCookieToList("www.google.com",
+ "Y=a; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT",
+ Time::Now() + TimeDelta::FromDays(10),
+ &initial_cookies);
+
+ // Inject our initial cookies into the mock PersistentCookieStore.
+ store->SetLoadExpectation(true, initial_cookies);
+
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+
+ // Verify that duplicates were not imported for path "/".
+ // (If this had failed, GetCookies() would have also returned X=1, X=2, X=4).
+ EXPECT_EQ("X=3; Y=a", GetCookies(cm.get(), GURL("http://www.google.com/")));
+
+ // Verify that same-named cookie on a different path ("/x2") didn't get
+ // messed up.
+ EXPECT_EQ("X=a1; X=3; Y=a",
+ GetCookies(cm.get(), GURL("http://www.google.com/2/x")));
+
+ // Verify that the PersistentCookieStore was told to kill its 4 duplicates.
+ ASSERT_EQ(4u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[0].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[2].type);
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
+}
+
+// Tests importing from a persistent cookie store that contains cookies
+// with duplicate creation times. This situation should be handled by
+// dropping the cookies before insertion/visibility to user.
+//
+// This is a regression test for: http://crbug.com/43188.
+TEST_F(CookieMonsterTest, DontImportDuplicateCreationTimes) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+
+ Time now(Time::Now());
+ Time earlier(now - TimeDelta::FromDays(1));
+
+ // Insert 8 cookies, four with the current time as creation times, and
+ // four with the earlier time as creation times. We should only get
+ // two cookies remaining, but which two (other than that there should
+ // be one from each set) will be random.
+ std::vector<CanonicalCookie*> initial_cookies;
+ AddCookieToList("www.google.com", "X=1; path=/", now, &initial_cookies);
+ AddCookieToList("www.google.com", "X=2; path=/", now, &initial_cookies);
+ AddCookieToList("www.google.com", "X=3; path=/", now, &initial_cookies);
+ AddCookieToList("www.google.com", "X=4; path=/", now, &initial_cookies);
+
+ AddCookieToList("www.google.com", "Y=1; path=/", earlier, &initial_cookies);
+ AddCookieToList("www.google.com", "Y=2; path=/", earlier, &initial_cookies);
+ AddCookieToList("www.google.com", "Y=3; path=/", earlier, &initial_cookies);
+ AddCookieToList("www.google.com", "Y=4; path=/", earlier, &initial_cookies);
+
+ // Inject our initial cookies into the mock PersistentCookieStore.
+ store->SetLoadExpectation(true, initial_cookies);
+
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+
+ CookieList list(GetAllCookies(cm.get()));
+ EXPECT_EQ(2U, list.size());
+ // Confirm that we have one of each.
+ std::string name1(list[0].Name());
+ std::string name2(list[1].Name());
+ EXPECT_TRUE(name1 == "X" || name2 == "X");
+ EXPECT_TRUE(name1 == "Y" || name2 == "Y");
+ EXPECT_NE(name1, name2);
+}
+
+TEST_F(CookieMonsterTest, Delegate) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<MockCookieMonsterDelegate> delegate(
+ new MockCookieMonsterDelegate);
+ scoped_refptr<CookieMonster> cm(
+ new CookieMonster(store.get(), delegate.get()));
+
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "C=D"));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "E=F"));
+ EXPECT_EQ("A=B; C=D; E=F", GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(3u, delegate->changes().size());
+ EXPECT_FALSE(delegate->changes()[0].second);
+ EXPECT_EQ(url_google_.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ("A", delegate->changes()[0].first.Name());
+ EXPECT_EQ("B", delegate->changes()[0].first.Value());
+ EXPECT_EQ(url_google_.host(), delegate->changes()[1].first.Domain());
+ EXPECT_FALSE(delegate->changes()[1].second);
+ EXPECT_EQ("C", delegate->changes()[1].first.Name());
+ EXPECT_EQ("D", delegate->changes()[1].first.Value());
+ EXPECT_EQ(url_google_.host(), delegate->changes()[2].first.Domain());
+ EXPECT_FALSE(delegate->changes()[2].second);
+ EXPECT_EQ("E", delegate->changes()[2].first.Name());
+ EXPECT_EQ("F", delegate->changes()[2].first.Value());
+ delegate->reset();
+
+ EXPECT_TRUE(FindAndDeleteCookie(cm.get(), url_google_.host(), "C"));
+ EXPECT_EQ("A=B; E=F", GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(1u, delegate->changes().size());
+ EXPECT_EQ(url_google_.host(), delegate->changes()[0].first.Domain());
+ EXPECT_TRUE(delegate->changes()[0].second);
+ EXPECT_EQ("C", delegate->changes()[0].first.Name());
+ EXPECT_EQ("D", delegate->changes()[0].first.Value());
+ delegate->reset();
+
+ EXPECT_FALSE(FindAndDeleteCookie(cm.get(), "random.host", "E"));
+ EXPECT_EQ("A=B; E=F", GetCookies(cm.get(), url_google_));
+ EXPECT_EQ(0u, delegate->changes().size());
+
+ // Insert a cookie "a" for path "/path1"
+ EXPECT_TRUE(SetCookie(cm.get(),
+ url_google_,
+ "a=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+ ASSERT_EQ(1u, delegate->changes().size());
+ EXPECT_FALSE(delegate->changes()[0].second);
+ EXPECT_EQ(url_google_.host(), delegate->changes()[0].first.Domain());
+ EXPECT_EQ("a", delegate->changes()[0].first.Name());
+ EXPECT_EQ("val1", delegate->changes()[0].first.Value());
+ delegate->reset();
+
+ // Insert a cookie "a" for path "/path1", that is httponly. This should
+ // overwrite the non-http-only version.
+ CookieOptions allow_httponly;
+ allow_httponly.set_include_httponly();
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(),
+ url_google_,
+ "a=val2; path=/path1; httponly; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT",
+ allow_httponly));
+ ASSERT_EQ(3u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
+ ASSERT_EQ(2u, delegate->changes().size());
+ EXPECT_EQ(url_google_.host(), delegate->changes()[0].first.Domain());
+ EXPECT_TRUE(delegate->changes()[0].second);
+ EXPECT_EQ("a", delegate->changes()[0].first.Name());
+ EXPECT_EQ("val1", delegate->changes()[0].first.Value());
+ EXPECT_EQ(url_google_.host(), delegate->changes()[1].first.Domain());
+ EXPECT_FALSE(delegate->changes()[1].second);
+ EXPECT_EQ("a", delegate->changes()[1].first.Name());
+ EXPECT_EQ("val2", delegate->changes()[1].first.Value());
+ delegate->reset();
+}
+
+TEST_F(CookieMonsterTest, SetCookieWithDetails) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+
+ EXPECT_TRUE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A",
+ "B",
+ std::string(),
+ "/foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(SetCookieWithDetails(cm.get(),
+ url_google_bar_,
+ "C",
+ "D",
+ "google.izzle",
+ "/bar",
+ base::Time(),
+ false,
+ true,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_TRUE(SetCookieWithDetails(cm.get(),
+ url_google_,
+ "E",
+ "F",
+ std::string(),
+ std::string(),
+ base::Time(),
+ true,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ // Test that malformed attributes fail to set the cookie.
+ EXPECT_FALSE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ " A",
+ "B",
+ std::string(),
+ "/foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_FALSE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A;",
+ "B",
+ std::string(),
+ "/foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_FALSE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A=",
+ "B",
+ std::string(),
+ "/foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_FALSE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A",
+ "B",
+ "google.ozzzzzzle",
+ "foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ EXPECT_FALSE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A=",
+ "B",
+ std::string(),
+ "foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ CookieList cookies = GetAllCookiesForURL(cm.get(), url_google_foo_);
+ CookieList::iterator it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("A", it->Name());
+ EXPECT_EQ("B", it->Value());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("/foo", it->Path());
+ EXPECT_FALSE(it->IsPersistent());
+ EXPECT_FALSE(it->IsSecure());
+ EXPECT_FALSE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = GetAllCookiesForURL(cm.get(), url_google_bar_);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("C", it->Name());
+ EXPECT_EQ("D", it->Value());
+ EXPECT_EQ(".google.izzle", it->Domain());
+ EXPECT_EQ("/bar", it->Path());
+ EXPECT_FALSE(it->IsSecure());
+ EXPECT_TRUE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+
+ cookies = GetAllCookiesForURL(cm.get(), url_google_secure_);
+ it = cookies.begin();
+
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("E", it->Name());
+ EXPECT_EQ("F", it->Value());
+ EXPECT_EQ("/", it->Path());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_TRUE(it->IsSecure());
+ EXPECT_FALSE(it->IsHttpOnly());
+
+ ASSERT_TRUE(++it == cookies.end());
+}
+
+TEST_F(CookieMonsterTest, DeleteAllForHost) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+
+ // Test probes:
+ // * Non-secure URL, mid-level (http://w.c.b.a)
+ // * Secure URL, mid-level (https://w.c.b.a)
+ // * URL with path, mid-level (https:/w.c.b.a/dir1/xx)
+ // All three tests should nuke only the midlevel host cookie,
+ // the http_only cookie, the host secure cookie, and the two host
+ // path cookies. http_only, secure, and paths are ignored by
+ // this call, and domain cookies arent touched.
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; host_2=X; sec_dom=X; sec_host=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; host_path_2=X; dom_path_1=X; host_path_1=X; "
+ "dom_1=X; dom_2=X; host_2=X; sec_dom=X; sec_host=X",
+ GetCookies(cm.get(),
+ GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ EXPECT_EQ(5, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2)));
+ EXPECT_EQ(8U, GetAllCookies(cm.get()).size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(),
+ GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ(5, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ(8U, GetAllCookies(cm.get()).size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(),
+ GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+
+ PopulateCmForDeleteAllForHost(cm);
+ EXPECT_EQ(5,
+ DeleteAllForHost(
+ cm.get(),
+ GURL(kTopLevelDomainPlus2Secure + std::string("/dir1/xxx"))));
+ EXPECT_EQ(8U, GetAllCookies(cm.get()).size());
+
+ EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus3)));
+ EXPECT_EQ("dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure)));
+ EXPECT_EQ("dom_1=X; host_1=X",
+ GetCookies(cm.get(), GURL(kTopLevelDomainPlus1)));
+ EXPECT_EQ("dom_path_2=X; dom_path_1=X; dom_1=X; dom_2=X; sec_dom=X",
+ GetCookies(cm.get(),
+ GURL(kTopLevelDomainPlus2Secure +
+ std::string("/dir1/dir2/xxx"))));
+}
+
+TEST_F(CookieMonsterTest, UniqueCreationTime) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+
+ // Add in three cookies through every public interface to the
+ // CookieMonster and confirm that none of them have duplicate
+ // creation times.
+
+ // SetCookieWithCreationTime and SetCookieWithCreationTimeAndOptions
+ // are not included as they aren't going to be public for very much
+ // longer.
+
+ // SetCookie, SetCookieWithOptions, SetCookieWithDetails
+
+ SetCookie(cm.get(), url_google_, "SetCookie1=A");
+ SetCookie(cm.get(), url_google_, "SetCookie2=A");
+ SetCookie(cm.get(), url_google_, "SetCookie3=A");
+
+ SetCookieWithOptions(
+ cm.get(), url_google_, "setCookieWithOptions1=A", options);
+ SetCookieWithOptions(
+ cm.get(), url_google_, "setCookieWithOptions2=A", options);
+ SetCookieWithOptions(
+ cm.get(), url_google_, "setCookieWithOptions3=A", options);
+
+ SetCookieWithDetails(cm.get(),
+ url_google_,
+ "setCookieWithDetails1",
+ "A",
+ ".google.com",
+ "/",
+ Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT);
+ SetCookieWithDetails(cm.get(),
+ url_google_,
+ "setCookieWithDetails2",
+ "A",
+ ".google.com",
+ "/",
+ Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT);
+ SetCookieWithDetails(cm.get(),
+ url_google_,
+ "setCookieWithDetails3",
+ "A",
+ ".google.com",
+ "/",
+ Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT);
+
+ // Now we check
+ CookieList cookie_list(GetAllCookies(cm.get()));
+ typedef std::map<int64, CanonicalCookie> TimeCookieMap;
+ TimeCookieMap check_map;
+ for (CookieList::const_iterator it = cookie_list.begin();
+ it != cookie_list.end(); it++) {
+ const int64 creation_date = it->CreationDate().ToInternalValue();
+ TimeCookieMap::const_iterator
+ existing_cookie_it(check_map.find(creation_date));
+ EXPECT_TRUE(existing_cookie_it == check_map.end())
+ << "Cookie " << it->Name() << " has same creation date ("
+ << it->CreationDate().ToInternalValue()
+ << ") as previously entered cookie "
+ << existing_cookie_it->second.Name();
+
+ if (existing_cookie_it == check_map.end()) {
+ check_map.insert(TimeCookieMap::value_type(
+ it->CreationDate().ToInternalValue(), *it));
+ }
+ }
+}
+
+// Mainly a test of GetEffectiveDomain, or more specifically, of the
+// expected behavior of GetEffectiveDomain within the CookieMonster.
+TEST_F(CookieMonsterTest, GetKey) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+
+ // This test is really only interesting if GetKey() actually does something.
+ EXPECT_EQ("google.com", cm->GetKey("www.google.com"));
+ EXPECT_EQ("google.izzie", cm->GetKey("www.google.izzie"));
+ EXPECT_EQ("google.izzie", cm->GetKey(".google.izzie"));
+ EXPECT_EQ("bbc.co.uk", cm->GetKey("bbc.co.uk"));
+ EXPECT_EQ("bbc.co.uk", cm->GetKey("a.b.c.d.bbc.co.uk"));
+ EXPECT_EQ("apple.com", cm->GetKey("a.b.c.d.apple.com"));
+ EXPECT_EQ("apple.izzie", cm->GetKey("a.b.c.d.apple.izzie"));
+
+ // Cases where the effective domain is null, so we use the host
+ // as the key.
+ EXPECT_EQ("co.uk", cm->GetKey("co.uk"));
+ const std::string extension_name("iehocdgbbocmkdidlbnnfbmbinnahbae");
+ EXPECT_EQ(extension_name, cm->GetKey(extension_name));
+ EXPECT_EQ("com", cm->GetKey("com"));
+ EXPECT_EQ("hostalias", cm->GetKey("hostalias"));
+ EXPECT_EQ("localhost", cm->GetKey("localhost"));
+}
+
+// Test that cookies transfer from/to the backing store correctly.
+TEST_F(CookieMonsterTest, BackingStoreCommunication) {
+ // Store details for cookies transforming through the backing store interface.
+
+ base::Time current(base::Time::Now());
+ scoped_refptr<MockSimplePersistentCookieStore> store(
+ new MockSimplePersistentCookieStore);
+ base::Time new_access_time;
+ base::Time expires(base::Time::Now() + base::TimeDelta::FromSeconds(100));
+
+ const CookiesInputInfo input_info[] = {
+ {GURL("http://a.b.google.com"), "a", "1", "", "/path/to/cookie", expires,
+ false, false, COOKIE_PRIORITY_DEFAULT},
+ {GURL("https://www.google.com"), "b", "2", ".google.com",
+ "/path/from/cookie", expires + TimeDelta::FromSeconds(10),
+ true, true, COOKIE_PRIORITY_DEFAULT},
+ {GURL("https://google.com"), "c", "3", "", "/another/path/to/cookie",
+ base::Time::Now() + base::TimeDelta::FromSeconds(100),
+ true, false, COOKIE_PRIORITY_DEFAULT}
+ };
+ const int INPUT_DELETE = 1;
+
+ // Create new cookies and flush them to the store.
+ {
+ scoped_refptr<CookieMonster> cmout(new CookieMonster(store.get(), NULL));
+ for (const CookiesInputInfo* p = input_info;
+ p < &input_info[ARRAYSIZE_UNSAFE(input_info)];
+ p++) {
+ EXPECT_TRUE(SetCookieWithDetails(cmout.get(),
+ p->url,
+ p->name,
+ p->value,
+ p->domain,
+ p->path,
+ p->expiration_time,
+ p->secure,
+ p->http_only,
+ p->priority));
+ }
+ GURL del_url(input_info[INPUT_DELETE].url.Resolve(
+ input_info[INPUT_DELETE].path).spec());
+ DeleteCookie(cmout.get(), del_url, input_info[INPUT_DELETE].name);
+ }
+
+ // Create a new cookie monster and make sure that everything is correct
+ {
+ scoped_refptr<CookieMonster> cmin(new CookieMonster(store.get(), NULL));
+ CookieList cookies(GetAllCookies(cmin.get()));
+ ASSERT_EQ(2u, cookies.size());
+ // Ordering is path length, then creation time. So second cookie
+ // will come first, and we need to swap them.
+ std::swap(cookies[0], cookies[1]);
+ for (int output_index = 0; output_index < 2; output_index++) {
+ int input_index = output_index * 2;
+ const CookiesInputInfo* input = &input_info[input_index];
+ const CanonicalCookie* output = &cookies[output_index];
+
+ EXPECT_EQ(input->name, output->Name());
+ EXPECT_EQ(input->value, output->Value());
+ EXPECT_EQ(input->url.host(), output->Domain());
+ EXPECT_EQ(input->path, output->Path());
+ EXPECT_LE(current.ToInternalValue(),
+ output->CreationDate().ToInternalValue());
+ EXPECT_EQ(input->secure, output->IsSecure());
+ EXPECT_EQ(input->http_only, output->IsHttpOnly());
+ EXPECT_TRUE(output->IsPersistent());
+ EXPECT_EQ(input->expiration_time.ToInternalValue(),
+ output->ExpiryDate().ToInternalValue());
+ }
+ }
+}
+
+TEST_F(CookieMonsterTest, CookieListOrdering) {
+ // Put a random set of cookies into a monster and make sure
+ // they're returned in the right order.
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ EXPECT_TRUE(
+ SetCookie(cm.get(), GURL("http://d.c.b.a.google.com/aa/x.html"), "c=1"));
+ EXPECT_TRUE(SetCookie(cm.get(),
+ GURL("http://b.a.google.com/aa/bb/cc/x.html"),
+ "d=1; domain=b.a.google.com"));
+ EXPECT_TRUE(SetCookie(cm.get(),
+ GURL("http://b.a.google.com/aa/bb/cc/x.html"),
+ "a=4; domain=b.a.google.com"));
+ EXPECT_TRUE(SetCookie(cm.get(),
+ GURL("http://c.b.a.google.com/aa/bb/cc/x.html"),
+ "e=1; domain=c.b.a.google.com"));
+ EXPECT_TRUE(SetCookie(
+ cm.get(), GURL("http://d.c.b.a.google.com/aa/bb/x.html"), "b=1"));
+ EXPECT_TRUE(SetCookie(
+ cm.get(), GURL("http://news.bbc.co.uk/midpath/x.html"), "g=10"));
+ {
+ unsigned int i = 0;
+ CookieList cookies(GetAllCookiesForURL(
+ cm.get(), GURL("http://d.c.b.a.google.com/aa/bb/cc/dd")));
+ ASSERT_EQ(5u, cookies.size());
+ EXPECT_EQ("d", cookies[i++].Name());
+ EXPECT_EQ("a", cookies[i++].Name());
+ EXPECT_EQ("e", cookies[i++].Name());
+ EXPECT_EQ("b", cookies[i++].Name());
+ EXPECT_EQ("c", cookies[i++].Name());
+ }
+
+ {
+ unsigned int i = 0;
+ CookieList cookies(GetAllCookies(cm.get()));
+ ASSERT_EQ(6u, cookies.size());
+ EXPECT_EQ("d", cookies[i++].Name());
+ EXPECT_EQ("a", cookies[i++].Name());
+ EXPECT_EQ("e", cookies[i++].Name());
+ EXPECT_EQ("g", cookies[i++].Name());
+ EXPECT_EQ("b", cookies[i++].Name());
+ EXPECT_EQ("c", cookies[i++].Name());
+ }
+}
+
+// This test and CookieMonstertest.TestGCTimes (in cookie_monster_perftest.cc)
+// are somewhat complementary twins. This test is probing for whether
+// garbage collection always happens when it should (i.e. that we actually
+// get rid of cookies when we should). The perftest is probing for
+// whether garbage collection happens when it shouldn't. See comments
+// before that test for more details.
+
+// Disabled on Windows, see crbug.com/126095
+#if defined(OS_WIN)
+#define MAYBE_GarbageCollectionTriggers DISABLED_GarbageCollectionTriggers
+#else
+#define MAYBE_GarbageCollectionTriggers GarbageCollectionTriggers
+#endif
+
+TEST_F(CookieMonsterTest, MAYBE_GarbageCollectionTriggers) {
+ // First we check to make sure that a whole lot of recent cookies
+ // doesn't get rid of anything after garbage collection is checked for.
+ {
+ scoped_refptr<CookieMonster> cm(
+ CreateMonsterForGC(CookieMonster::kMaxCookies * 2));
+ EXPECT_EQ(CookieMonster::kMaxCookies * 2, GetAllCookies(cm.get()).size());
+ SetCookie(cm.get(), GURL("http://newdomain.com"), "b=2");
+ EXPECT_EQ(CookieMonster::kMaxCookies * 2 + 1,
+ GetAllCookies(cm.get()).size());
+ }
+
+ // Now we explore a series of relationships between cookie last access
+ // time and size of store to make sure we only get rid of cookies when
+ // we really should.
+ const struct TestCase {
+ size_t num_cookies;
+ size_t num_old_cookies;
+ size_t expected_initial_cookies;
+ // Indexed by ExpiryAndKeyScheme
+ size_t expected_cookies_after_set;
+ } test_cases[] = {
+ {
+ // A whole lot of recent cookies; gc shouldn't happen.
+ CookieMonster::kMaxCookies * 2,
+ 0,
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies * 2 + 1
+ }, {
+ // Some old cookies, but still overflowing max.
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies / 2,
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies * 2 - CookieMonster::kMaxCookies / 2 + 1
+ }, {
+ // Old cookies enough to bring us right down to our purge line.
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies + CookieMonster::kPurgeCookies + 1,
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies - CookieMonster::kPurgeCookies
+ }, {
+ // Old cookies enough to bring below our purge line (which we
+ // shouldn't do).
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies * 3 / 2,
+ CookieMonster::kMaxCookies * 2,
+ CookieMonster::kMaxCookies - CookieMonster::kPurgeCookies
+ }
+ };
+
+ for (int ci = 0; ci < static_cast<int>(ARRAYSIZE_UNSAFE(test_cases)); ++ci) {
+ const TestCase *test_case = &test_cases[ci];
+ scoped_refptr<CookieMonster> cm(
+ CreateMonsterFromStoreForGC(
+ test_case->num_cookies, test_case->num_old_cookies,
+ CookieMonster::kSafeFromGlobalPurgeDays * 2));
+ EXPECT_EQ(test_case->expected_initial_cookies,
+ GetAllCookies(cm.get()).size()) << "For test case " << ci;
+ // Will trigger GC
+ SetCookie(cm.get(), GURL("http://newdomain.com"), "b=2");
+ EXPECT_EQ(test_case->expected_cookies_after_set,
+ GetAllCookies(cm.get()).size()) << "For test case " << ci;
+ }
+}
+
+// This test checks that keep expired cookies flag is working.
+TEST_F(CookieMonsterTest, KeepExpiredCookies) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ cm->SetKeepExpiredCookies();
+ CookieOptions options;
+
+ // Set a persistent cookie.
+ ASSERT_TRUE(SetCookieWithOptions(
+ cm.get(),
+ url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT",
+ options));
+
+ // Get the canonical cookie.
+ CookieList cookie_list = GetAllCookies(cm.get());
+ ASSERT_EQ(1U, cookie_list.size());
+
+ // Use a past expiry date to delete the cookie.
+ ASSERT_TRUE(SetCookieWithOptions(
+ cm.get(),
+ url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-1977 22:50:13 GMT",
+ options));
+
+ // Check that the cookie with the past expiry date is still there.
+ // GetAllCookies() also triggers garbage collection.
+ cookie_list = GetAllCookies(cm.get());
+ ASSERT_EQ(1U, cookie_list.size());
+ ASSERT_TRUE(cookie_list[0].IsExpired(Time::Now()));
+}
+
+namespace {
+
+// Mock PersistentCookieStore that keeps track of the number of Flush() calls.
+class FlushablePersistentStore : public CookieMonster::PersistentCookieStore {
+ public:
+ FlushablePersistentStore() : flush_count_(0) {}
+
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE {
+ std::vector<CanonicalCookie*> out_cookies;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&net::LoadedCallbackTask::Run,
+ new net::LoadedCallbackTask(loaded_callback, out_cookies)));
+ }
+
+ virtual void LoadCookiesForKey(
+ const std::string& key,
+ const LoadedCallback& loaded_callback) OVERRIDE {
+ Load(loaded_callback);
+ }
+
+ virtual void AddCookie(const CanonicalCookie&) OVERRIDE {}
+ virtual void UpdateCookieAccessTime(const CanonicalCookie&) OVERRIDE {}
+ virtual void DeleteCookie(const CanonicalCookie&) OVERRIDE {}
+ virtual void SetForceKeepSessionState() OVERRIDE {}
+
+ virtual void Flush(const base::Closure& callback) OVERRIDE {
+ ++flush_count_;
+ if (!callback.is_null())
+ callback.Run();
+ }
+
+ int flush_count() {
+ return flush_count_;
+ }
+
+ private:
+ virtual ~FlushablePersistentStore() {}
+
+ volatile int flush_count_;
+};
+
+// Counts the number of times Callback() has been run.
+class CallbackCounter : public base::RefCountedThreadSafe<CallbackCounter> {
+ public:
+ CallbackCounter() : callback_count_(0) {}
+
+ void Callback() {
+ ++callback_count_;
+ }
+
+ int callback_count() {
+ return callback_count_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<CallbackCounter>;
+ ~CallbackCounter() {}
+
+ volatile int callback_count_;
+};
+
+} // namespace
+
+// Test that FlushStore() is forwarded to the store and callbacks are posted.
+TEST_F(CookieMonsterTest, FlushStore) {
+ scoped_refptr<CallbackCounter> counter(new CallbackCounter());
+ scoped_refptr<FlushablePersistentStore> store(new FlushablePersistentStore());
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+
+ ASSERT_EQ(0, store->flush_count());
+ ASSERT_EQ(0, counter->callback_count());
+
+ // Before initialization, FlushStore() should just run the callback.
+ cm->FlushStore(base::Bind(&CallbackCounter::Callback, counter.get()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(0, store->flush_count());
+ ASSERT_EQ(1, counter->callback_count());
+
+ // NULL callback is safe.
+ cm->FlushStore(base::Closure());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(0, store->flush_count());
+ ASSERT_EQ(1, counter->callback_count());
+
+ // After initialization, FlushStore() should delegate to the store.
+ GetAllCookies(cm.get()); // Force init.
+ cm->FlushStore(base::Bind(&CallbackCounter::Callback, counter.get()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(1, store->flush_count());
+ ASSERT_EQ(2, counter->callback_count());
+
+ // NULL callback is still safe.
+ cm->FlushStore(base::Closure());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(2, store->flush_count());
+ ASSERT_EQ(2, counter->callback_count());
+
+ // If there's no backing store, FlushStore() is always a safe no-op.
+ cm = new CookieMonster(NULL, NULL);
+ GetAllCookies(cm.get()); // Force init.
+ cm->FlushStore(base::Closure());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(2, counter->callback_count());
+
+ cm->FlushStore(base::Bind(&CallbackCounter::Callback, counter.get()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(3, counter->callback_count());
+}
+
+TEST_F(CookieMonsterTest, HistogramCheck) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ // Should match call in InitializeHistograms, but doesn't really matter
+ // since the histogram should have been initialized by the CM construction
+ // above.
+ base::HistogramBase* expired_histogram =
+ base::Histogram::FactoryGet(
+ "Cookie.ExpirationDurationMinutes", 1, 10 * 365 * 24 * 60, 50,
+ base::Histogram::kUmaTargetedHistogramFlag);
+
+ scoped_ptr<base::HistogramSamples> samples1(
+ expired_histogram->SnapshotSamples());
+ ASSERT_TRUE(
+ SetCookieWithDetails(cm.get(),
+ GURL("http://fake.a.url"),
+ "a",
+ "b",
+ "a.url",
+ "/",
+ base::Time::Now() + base::TimeDelta::FromMinutes(59),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+
+ scoped_ptr<base::HistogramSamples> samples2(
+ expired_histogram->SnapshotSamples());
+ EXPECT_EQ(samples1->TotalCount() + 1, samples2->TotalCount());
+
+ // kValidCookieLine creates a session cookie.
+ ASSERT_TRUE(SetCookie(cm.get(), url_google_, kValidCookieLine));
+
+ scoped_ptr<base::HistogramSamples> samples3(
+ expired_histogram->SnapshotSamples());
+ EXPECT_EQ(samples2->TotalCount(), samples3->TotalCount());
+}
+
+namespace {
+
+class MultiThreadedCookieMonsterTest : public CookieMonsterTest {
+ public:
+ MultiThreadedCookieMonsterTest() : other_thread_("CMTthread") {}
+
+ // Helper methods for calling the asynchronous CookieMonster methods
+ // from a different thread.
+
+ void GetAllCookiesTask(CookieMonster* cm,
+ GetCookieListCallback* callback) {
+ cm->GetAllCookiesAsync(
+ base::Bind(&GetCookieListCallback::Run, base::Unretained(callback)));
+ }
+
+ void GetAllCookiesForURLTask(CookieMonster* cm,
+ const GURL& url,
+ GetCookieListCallback* callback) {
+ cm->GetAllCookiesForURLAsync(
+ url,
+ base::Bind(&GetCookieListCallback::Run, base::Unretained(callback)));
+ }
+
+ void GetAllCookiesForURLWithOptionsTask(CookieMonster* cm,
+ const GURL& url,
+ const CookieOptions& options,
+ GetCookieListCallback* callback) {
+ cm->GetAllCookiesForURLWithOptionsAsync(
+ url, options,
+ base::Bind(&GetCookieListCallback::Run, base::Unretained(callback)));
+ }
+
+ void SetCookieWithDetailsTask(CookieMonster* cm, const GURL& url,
+ BoolResultCookieCallback* callback) {
+ // Define the parameters here instead of in the calling fucntion.
+ // The maximum number of parameters for Bind function is 6.
+ std::string name = "A";
+ std::string value = "B";
+ std::string domain = std::string();
+ std::string path = "/foo";
+ base::Time expiration_time = base::Time();
+ bool secure = false;
+ bool http_only = false;
+ CookiePriority priority = COOKIE_PRIORITY_DEFAULT;
+ cm->SetCookieWithDetailsAsync(
+ url, name, value, domain, path, expiration_time, secure, http_only,
+ priority,
+ base::Bind(&BoolResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ void DeleteAllCreatedBetweenTask(CookieMonster* cm,
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ IntResultCookieCallback* callback) {
+ cm->DeleteAllCreatedBetweenAsync(
+ delete_begin, delete_end,
+ base::Bind(&IntResultCookieCallback::Run,
+ base::Unretained(callback)));
+ }
+
+ void DeleteAllForHostTask(CookieMonster* cm,
+ const GURL& url,
+ IntResultCookieCallback* callback) {
+ cm->DeleteAllForHostAsync(
+ url,
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ void DeleteAllCreatedBetweenForHostTask(CookieMonster* cm,
+ const base::Time delete_begin,
+ const base::Time delete_end,
+ const GURL& url,
+ IntResultCookieCallback* callback) {
+ cm->DeleteAllCreatedBetweenForHostAsync(
+ delete_begin, delete_end, url,
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ void DeleteCanonicalCookieTask(CookieMonster* cm,
+ const CanonicalCookie& cookie,
+ BoolResultCookieCallback* callback) {
+ cm->DeleteCanonicalCookieAsync(
+ cookie,
+ base::Bind(&BoolResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ protected:
+ void RunOnOtherThread(const base::Closure& task) {
+ other_thread_.Start();
+ other_thread_.message_loop()->PostTask(FROM_HERE, task);
+ RunFor(kTimeout);
+ other_thread_.Stop();
+ }
+
+ Thread other_thread_;
+};
+
+} // namespace
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckGetAllCookies) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ CookieList cookies = GetAllCookies(cm.get());
+ CookieList::const_iterator it = cookies.begin();
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == cookies.end());
+ GetCookieListCallback callback(&other_thread_);
+ base::Closure task =
+ base::Bind(&net::MultiThreadedCookieMonsterTest::GetAllCookiesTask,
+ base::Unretained(this),
+ cm, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ it = callback.cookies().begin();
+ ASSERT_TRUE(it != callback.cookies().end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == callback.cookies().end());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckGetAllCookiesForURL) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ CookieList cookies = GetAllCookiesForURL(cm.get(), url_google_);
+ CookieList::const_iterator it = cookies.begin();
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == cookies.end());
+ GetCookieListCallback callback(&other_thread_);
+ base::Closure task =
+ base::Bind(&net::MultiThreadedCookieMonsterTest::GetAllCookiesForURLTask,
+ base::Unretained(this),
+ cm, url_google_, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ it = callback.cookies().begin();
+ ASSERT_TRUE(it != callback.cookies().end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == callback.cookies().end());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckGetAllCookiesForURLWithOpt) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ CookieOptions options;
+ CookieList cookies =
+ GetAllCookiesForURLWithOptions(cm.get(), url_google_, options);
+ CookieList::const_iterator it = cookies.begin();
+ ASSERT_TRUE(it != cookies.end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == cookies.end());
+ GetCookieListCallback callback(&other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::GetAllCookiesForURLWithOptionsTask,
+ base::Unretained(this),
+ cm, url_google_, options, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ it = callback.cookies().begin();
+ ASSERT_TRUE(it != callback.cookies().end());
+ EXPECT_EQ("www.google.izzle", it->Domain());
+ EXPECT_EQ("A", it->Name());
+ ASSERT_TRUE(++it == callback.cookies().end());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckSetCookieWithDetails) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ EXPECT_TRUE(SetCookieWithDetails(cm.get(),
+ url_google_foo_,
+ "A",
+ "B",
+ std::string(),
+ "/foo",
+ base::Time(),
+ false,
+ false,
+ COOKIE_PRIORITY_DEFAULT));
+ BoolResultCookieCallback callback(&other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::SetCookieWithDetailsTask,
+ base::Unretained(this),
+ cm, url_google_foo_, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_TRUE(callback.result());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckDeleteAllCreatedBetween) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+ Time now = Time::Now();
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ EXPECT_EQ(
+ 1,
+ DeleteAllCreatedBetween(cm.get(), now - TimeDelta::FromDays(99), Time()));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ IntResultCookieCallback callback(&other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::DeleteAllCreatedBetweenTask,
+ base::Unretained(this),
+ cm, now - TimeDelta::FromDays(99),
+ Time(), &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ(1, callback.result());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckDeleteAllForHost) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ EXPECT_EQ(1, DeleteAllForHost(cm.get(), url_google_));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ IntResultCookieCallback callback(&other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::DeleteAllForHostTask,
+ base::Unretained(this),
+ cm, url_google_, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ(1, callback.result());
+}
+
+TEST_F(MultiThreadedCookieMonsterTest,
+ ThreadCheckDeleteAllCreatedBetweenForHost) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ GURL url_not_google("http://www.notgoogle.com");
+
+ CookieOptions options;
+ Time now = Time::Now();
+ // ago1 < ago2 < ago3 < now.
+ Time ago1 = now - TimeDelta::FromDays(101);
+ Time ago2 = now - TimeDelta::FromDays(100);
+ Time ago3 = now - TimeDelta::FromDays(99);
+
+ // These 3 cookies match the first deletion.
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "C=D", options));
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "Y=Z", options));
+
+ // This cookie does not match host.
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_not_google, "E=F", options));
+
+ // This cookie does not match time range: [ago3, inf], for first deletion, but
+ // matches for the second deletion.
+ EXPECT_TRUE(cm->SetCookieWithCreationTime(url_google_, "G=H", ago2));
+
+ // 1. First set of deletions.
+ EXPECT_EQ(
+ 3, // Deletes A=B, C=D, Y=Z
+ DeleteAllCreatedBetweenForHost(
+ cm.get(), ago3, Time::Max(), url_google_));
+
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ IntResultCookieCallback callback(&other_thread_);
+
+ // 2. Second set of deletions.
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::DeleteAllCreatedBetweenForHostTask,
+ base::Unretained(this),
+ cm, ago1, Time(), url_google_,
+ &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ(2, callback.result()); // Deletes A=B, G=H.
+}
+
+TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckDeleteCanonicalCookie) {
+ scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL));
+ CookieOptions options;
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ CookieList cookies = GetAllCookies(cm.get());
+ CookieList::iterator it = cookies.begin();
+ EXPECT_TRUE(DeleteCanonicalCookie(cm.get(), *it));
+
+ EXPECT_TRUE(SetCookieWithOptions(cm.get(), url_google_, "A=B", options));
+ BoolResultCookieCallback callback(&other_thread_);
+ cookies = GetAllCookies(cm.get());
+ it = cookies.begin();
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieMonsterTest::DeleteCanonicalCookieTask,
+ base::Unretained(this),
+ cm, *it, &callback);
+ RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_TRUE(callback.result());
+}
+
+TEST_F(CookieMonsterTest, InvalidExpiryTime) {
+ std::string cookie_line =
+ std::string(kValidCookieLine) + "; expires=Blarg arg arg";
+ scoped_ptr<CanonicalCookie> cookie(
+ CanonicalCookie::Create(url_google_, cookie_line, Time::Now(),
+ CookieOptions()));
+ ASSERT_FALSE(cookie->IsPersistent());
+}
+
+// Test that CookieMonster writes session cookies into the underlying
+// CookieStore if the "persist session cookies" option is on.
+TEST_F(CookieMonsterTest, PersistSessionCookies) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+ cm->SetPersistSessionCookies(true);
+
+ // All cookies set with SetCookie are session cookies.
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B"));
+ EXPECT_EQ("A=B", GetCookies(cm.get(), url_google_));
+
+ // The cookie was written to the backing store.
+ EXPECT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+ EXPECT_EQ("A", store->commands()[0].cookie.Name());
+ EXPECT_EQ("B", store->commands()[0].cookie.Value());
+
+ // Modify the cookie.
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=C"));
+ EXPECT_EQ("A=C", GetCookies(cm.get(), url_google_));
+ EXPECT_EQ(3u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+ EXPECT_EQ("A", store->commands()[1].cookie.Name());
+ EXPECT_EQ("B", store->commands()[1].cookie.Value());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
+ EXPECT_EQ("A", store->commands()[2].cookie.Name());
+ EXPECT_EQ("C", store->commands()[2].cookie.Value());
+
+ // Delete the cookie.
+ DeleteCookie(cm.get(), url_google_, "A");
+ EXPECT_EQ("", GetCookies(cm.get(), url_google_));
+ EXPECT_EQ(4u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
+ EXPECT_EQ("A", store->commands()[3].cookie.Name());
+ EXPECT_EQ("C", store->commands()[3].cookie.Value());
+}
+
+// Test the commands sent to the persistent cookie store.
+TEST_F(CookieMonsterTest, PersisentCookieStorageTest) {
+ scoped_refptr<MockPersistentCookieStore> store(
+ new MockPersistentCookieStore);
+ scoped_refptr<CookieMonster> cm(new CookieMonster(store.get(), NULL));
+
+ // Add a cookie.
+ EXPECT_TRUE(SetCookie(
+ cm.get(), url_google_, "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B", GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(1u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[0].type);
+ // Remove it.
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "A=B; max-age=0"));
+ this->MatchCookieLines(std::string(), GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(2u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type);
+
+ // Add a cookie.
+ EXPECT_TRUE(SetCookie(
+ cm.get(), url_google_, "A=B; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B", GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(3u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[2].type);
+ // Overwrite it.
+ EXPECT_TRUE(SetCookie(
+ cm.get(), url_google_, "A=Foo; expires=Mon, 18-Apr-22 22:50:14 GMT"));
+ this->MatchCookieLines("A=Foo", GetCookies(cm.get(), url_google_));
+ ASSERT_EQ(5u, store->commands().size());
+ EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type);
+ EXPECT_EQ(CookieStoreCommand::ADD, store->commands()[4].type);
+
+ // Create some non-persistent cookies and check that they don't go to the
+ // persistent storage.
+ EXPECT_TRUE(SetCookie(cm.get(), url_google_, "B=Bar"));
+ this->MatchCookieLines("A=Foo; B=Bar", GetCookies(cm.get(), url_google_));
+ EXPECT_EQ(5u, store->commands().size());
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_options.h b/chromium/net/cookies/cookie_options.h
new file mode 100644
index 00000000000..ed5e2ef3a47
--- /dev/null
+++ b/chromium/net/cookies/cookie_options.h
@@ -0,0 +1,42 @@
+// Copyright (c) 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.
+
+// Brought to you by number 42.
+
+#ifndef NET_COOKIES_COOKIE_OPTIONS_H_
+#define NET_COOKIES_COOKIE_OPTIONS_H_
+
+namespace net {
+
+class CookieOptions {
+ public:
+ // Default is to exclude httponly, which means:
+ // - reading operations will not return httponly cookies.
+ // - writing operations will not write httponly cookies.
+ CookieOptions()
+ : exclude_httponly_(true),
+ server_time_() {
+ }
+
+ void set_exclude_httponly() { exclude_httponly_ = true; }
+ void set_include_httponly() { exclude_httponly_ = false; }
+ bool exclude_httponly() const { return exclude_httponly_; }
+
+ // |server_time| indicates what the server sending us the Cookie thought the
+ // current time was when the cookie was produced. This is used to adjust for
+ // clock skew between server and host.
+ void set_server_time(const base::Time& server_time) {
+ server_time_ = server_time;
+ }
+ bool has_server_time() const { return !server_time_.is_null(); }
+ base::Time server_time() const { return server_time_; }
+
+ private:
+ bool exclude_httponly_;
+ base::Time server_time_;
+};
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_OPTIONS_H_
+
diff --git a/chromium/net/cookies/cookie_store.cc b/chromium/net/cookies/cookie_store.cc
new file mode 100644
index 00000000000..e1b2e091001
--- /dev/null
+++ b/chromium/net/cookies/cookie_store.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_store.h"
+
+#include "net/cookies/cookie_options.h"
+
+namespace net {
+
+CookieStore::CookieStore() {}
+
+CookieStore::~CookieStore() {}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_store.h b/chromium/net/cookies/cookie_store.h
new file mode 100644
index 00000000000..af996378893
--- /dev/null
+++ b/chromium/net/cookies/cookie_store.h
@@ -0,0 +1,84 @@
+// Copyright (c) 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.
+
+// Brought to you by number 42.
+
+#ifndef NET_COOKIES_COOKIE_STORE_H_
+#define NET_COOKIES_COOKIE_STORE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cookies/cookie_options.h"
+
+class GURL;
+
+namespace net {
+
+class CookieMonster;
+
+// An interface for storing and retrieving cookies. Implementations need to
+// be thread safe as its methods can be accessed from IO as well as UI threads.
+class NET_EXPORT CookieStore : public base::RefCountedThreadSafe<CookieStore> {
+ public:
+ // Callback definitions.
+ typedef base::Callback<void(const std::string& cookie)>
+ GetCookiesCallback;
+ typedef base::Callback<void(bool success)> SetCookiesCallback;
+ typedef base::Callback<void(int num_deleted)> DeleteCallback;
+
+
+ // Sets a single cookie. Expects a cookie line, like "a=1; domain=b.com".
+ //
+ // Fails either if the cookie is invalid or if this is a non-HTTPONLY cookie
+ // and it would overwrite an existing HTTPONLY cookie.
+ // Returns true if the cookie is successfully set.
+ virtual void SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const SetCookiesCallback& callback) = 0;
+
+ // TODO(???): what if the total size of all the cookies >4k, can we have a
+ // header that big or do we need multiple Cookie: headers?
+ // Note: Some sites, such as Facebook, occasionally use Cookie headers >4k.
+ //
+ // Simple interface, gets a cookie string "a=b; c=d" for the given URL.
+ // Use options to access httponly cookies.
+ virtual void GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookiesCallback& callback) = 0;
+
+ // Deletes the passed in cookie for the specified URL.
+ virtual void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) = 0;
+
+ // Deletes all of the cookies that have a creation_date greater than or equal
+ // to |delete_begin| and less than |delete_end|
+ // Returns the number of cookies that have been deleted.
+ virtual void DeleteAllCreatedBetweenAsync(const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) = 0;
+
+ virtual void DeleteSessionCookiesAsync(const DeleteCallback&) = 0;
+
+ // Returns the underlying CookieMonster.
+ virtual CookieMonster* GetCookieMonster() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<CookieStore>;
+ CookieStore();
+ virtual ~CookieStore();
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_STORE_H_
diff --git a/chromium/net/cookies/cookie_store_test_callbacks.cc b/chromium/net/cookies/cookie_store_test_callbacks.cc
new file mode 100644
index 00000000000..8d09f9ea516
--- /dev/null
+++ b/chromium/net/cookies/cookie_store_test_callbacks.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_store_test_callbacks.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+CookieCallback::CookieCallback(base::Thread* run_in_thread)
+ : did_run_(false),
+ run_in_thread_(run_in_thread),
+ run_in_loop_(NULL),
+ parent_loop_(base::MessageLoop::current()),
+ loop_to_quit_(base::MessageLoop::current()) {}
+
+CookieCallback::CookieCallback()
+ : did_run_(false),
+ run_in_thread_(NULL),
+ run_in_loop_(base::MessageLoop::current()),
+ parent_loop_(NULL),
+ loop_to_quit_(base::MessageLoop::current()) {}
+
+void CookieCallback::CallbackEpilogue() {
+ base::MessageLoop* expected_loop = NULL;
+ if (run_in_thread_) {
+ DCHECK(!run_in_loop_);
+ expected_loop = run_in_thread_->message_loop();
+ } else if (run_in_loop_) {
+ expected_loop = run_in_loop_;
+ }
+ ASSERT_TRUE(expected_loop != NULL);
+
+ did_run_ = true;
+ EXPECT_EQ(expected_loop, base::MessageLoop::current());
+ loop_to_quit_->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+}
+
+BoolResultCookieCallback::BoolResultCookieCallback() : result_(false) {}
+BoolResultCookieCallback::BoolResultCookieCallback(base::Thread* run_in_thread)
+ : CookieCallback(run_in_thread),
+ result_(false) {}
+
+StringResultCookieCallback::StringResultCookieCallback() {}
+StringResultCookieCallback::StringResultCookieCallback(
+ base::Thread* run_in_thread)
+ : CookieCallback(run_in_thread) {}
+
+IntResultCookieCallback::IntResultCookieCallback() : result_(0) {}
+IntResultCookieCallback::IntResultCookieCallback(base::Thread* run_in_thread)
+ : CookieCallback(run_in_thread),
+ result_(0) {}
+
+NoResultCookieCallback::NoResultCookieCallback() {}
+NoResultCookieCallback::NoResultCookieCallback(base::Thread* run_in_thread)
+ : CookieCallback(run_in_thread) {}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_store_test_callbacks.h b/chromium/net/cookies/cookie_store_test_callbacks.h
new file mode 100644
index 00000000000..c8308e1bc39
--- /dev/null
+++ b/chromium/net/cookies/cookie_store_test_callbacks.h
@@ -0,0 +1,111 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_COOKIE_STORE_TEST_CALLBACKS_H_
+#define NET_COOKIES_COOKIE_STORE_TEST_CALLBACKS_H_
+
+#include <string>
+#include <vector>
+
+#include "net/cookies/cookie_store.h"
+
+namespace base {
+class MessageLoop;
+class Thread;
+}
+
+namespace net {
+
+// Defines common behaviour for the callbacks from GetCookies, SetCookies, etc.
+// Asserts that the current thread is the expected invocation thread, sends a
+// quit to the thread in which it was constructed.
+class CookieCallback {
+ public:
+ // Indicates whether the callback has been called.
+ bool did_run() { return did_run_; }
+
+ protected:
+ // Constructs a callback that expects to be called in the given thread and
+ // will, upon execution, send a QUIT to the constructing thread.
+ explicit CookieCallback(base::Thread* run_in_thread);
+
+ // Constructs a callback that expects to be called in current thread and will
+ // send a QUIT to the constructing thread.
+ CookieCallback();
+
+ // Tests whether the current thread was the caller's thread.
+ // Sends a QUIT to the constructing thread.
+ void CallbackEpilogue();
+
+ private:
+ bool did_run_;
+ base::Thread* run_in_thread_;
+ base::MessageLoop* run_in_loop_;
+ base::MessageLoop* parent_loop_;
+ base::MessageLoop* loop_to_quit_;
+};
+
+// Callback implementations for the asynchronous CookieStore methods.
+
+class BoolResultCookieCallback : public CookieCallback {
+ public:
+ BoolResultCookieCallback();
+ explicit BoolResultCookieCallback(base::Thread* run_in_thread);
+
+ void Run(bool result) {
+ result_ = result;
+ CallbackEpilogue();
+ }
+
+ bool result() { return result_; }
+
+ private:
+ bool result_;
+};
+
+class StringResultCookieCallback : public CookieCallback {
+ public:
+ StringResultCookieCallback();
+ explicit StringResultCookieCallback(base::Thread* run_in_thread);
+
+ void Run(const std::string& result) {
+ result_ = result;
+ CallbackEpilogue();
+ }
+
+ const std::string& result() { return result_; }
+
+ private:
+ std::string result_;
+};
+
+class IntResultCookieCallback : public CookieCallback {
+ public:
+ IntResultCookieCallback();
+ explicit IntResultCookieCallback(base::Thread* run_in_thread);
+
+ void Run(int result) {
+ result_ = result;
+ CallbackEpilogue();
+ }
+
+ int result() { return result_; }
+
+ private:
+ int result_;
+};
+
+class NoResultCookieCallback : public CookieCallback {
+ public:
+ NoResultCookieCallback();
+ explicit NoResultCookieCallback(base::Thread* run_in_thread);
+
+ void Run() {
+ CallbackEpilogue();
+ }
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_STORE_TEST_CALLBACKS_H_
diff --git a/chromium/net/cookies/cookie_store_test_helpers.cc b/chromium/net/cookies/cookie_store_test_helpers.cc
new file mode 100644
index 00000000000..22992f63ec5
--- /dev/null
+++ b/chromium/net/cookies/cookie_store_test_helpers.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_store_test_helpers.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+
+namespace net {
+
+const int kDelayedTime = 0;
+
+DelayedCookieMonster::DelayedCookieMonster()
+ : cookie_monster_(new CookieMonster(NULL, NULL)),
+ did_run_(false),
+ result_(false) {
+}
+
+DelayedCookieMonster::~DelayedCookieMonster() {
+}
+
+void DelayedCookieMonster::SetCookiesInternalCallback(bool result) {
+ result_ = result;
+ did_run_ = true;
+}
+
+void DelayedCookieMonster::GetCookiesWithOptionsInternalCallback(
+ const std::string& cookie) {
+ cookie_ = cookie;
+ did_run_ = true;
+}
+
+void DelayedCookieMonster::SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const CookieMonster::SetCookiesCallback& callback) {
+ did_run_ = false;
+ cookie_monster_->SetCookieWithOptionsAsync(
+ url, cookie_line, options,
+ base::Bind(&DelayedCookieMonster::SetCookiesInternalCallback,
+ base::Unretained(this)));
+ DCHECK_EQ(did_run_, true);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DelayedCookieMonster::InvokeSetCookiesCallback,
+ base::Unretained(this),
+ callback),
+ base::TimeDelta::FromMilliseconds(kDelayedTime));
+}
+
+void DelayedCookieMonster::GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const CookieMonster::GetCookiesCallback& callback) {
+ did_run_ = false;
+ cookie_monster_->GetCookiesWithOptionsAsync(
+ url, options,
+ base::Bind(&DelayedCookieMonster::GetCookiesWithOptionsInternalCallback,
+ base::Unretained(this)));
+ DCHECK_EQ(did_run_, true);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DelayedCookieMonster::InvokeGetCookieStringCallback,
+ base::Unretained(this),
+ callback),
+ base::TimeDelta::FromMilliseconds(kDelayedTime));
+}
+
+void DelayedCookieMonster::InvokeSetCookiesCallback(
+ const CookieMonster::SetCookiesCallback& callback) {
+ if (!callback.is_null())
+ callback.Run(result_);
+}
+
+void DelayedCookieMonster::InvokeGetCookieStringCallback(
+ const CookieMonster::GetCookiesCallback& callback) {
+ if (!callback.is_null())
+ callback.Run(cookie_);
+}
+
+bool DelayedCookieMonster::SetCookieWithOptions(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options) {
+ ADD_FAILURE();
+ return false;
+}
+
+std::string DelayedCookieMonster::GetCookiesWithOptions(
+ const GURL& url,
+ const CookieOptions& options) {
+ ADD_FAILURE();
+ return std::string();
+}
+
+void DelayedCookieMonster::DeleteCookie(const GURL& url,
+ const std::string& cookie_name) {
+ ADD_FAILURE();
+}
+
+void DelayedCookieMonster::DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) {
+ ADD_FAILURE();
+}
+
+void DelayedCookieMonster::DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) {
+ ADD_FAILURE();
+}
+
+void DelayedCookieMonster::DeleteSessionCookiesAsync(const DeleteCallback&) {
+ ADD_FAILURE();
+}
+
+CookieMonster* DelayedCookieMonster::GetCookieMonster() {
+ return cookie_monster_.get();
+}
+
+} // namespace net
diff --git a/chromium/net/cookies/cookie_store_test_helpers.h b/chromium/net/cookies/cookie_store_test_helpers.h
new file mode 100644
index 00000000000..86b572afdd9
--- /dev/null
+++ b/chromium/net/cookies/cookie_store_test_helpers.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_COOKIE_STORE_TEST_HELPERS_H_
+#define NET_COOKIES_COOKIE_STORE_TEST_HELPERS_H_
+
+#include "net/cookies/cookie_monster.h"
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class DelayedCookieMonster : public CookieStore {
+ public:
+ DelayedCookieMonster();
+
+ // Call the asynchronous CookieMonster function, expect it to immediately
+ // invoke the internal callback.
+ // Post a delayed task to invoke the original callback with the results.
+
+ virtual void SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const CookieMonster::SetCookiesCallback& callback) OVERRIDE;
+
+ virtual void GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const CookieMonster::GetCookiesCallback& callback) OVERRIDE;
+
+ virtual bool SetCookieWithOptions(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options);
+
+ virtual std::string GetCookiesWithOptions(const GURL& url,
+ const CookieOptions& options);
+
+ virtual void DeleteCookie(const GURL& url,
+ const std::string& cookie_name);
+
+ virtual void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) OVERRIDE;
+
+ virtual void DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) OVERRIDE;
+
+ virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE;
+
+ virtual CookieMonster* GetCookieMonster() OVERRIDE;
+
+ private:
+
+ // Be called immediately from CookieMonster.
+
+ void SetCookiesInternalCallback(bool result);
+
+ void GetCookiesWithOptionsInternalCallback(const std::string& cookie);
+
+ // Invoke the original callbacks.
+
+ void InvokeSetCookiesCallback(
+ const CookieMonster::SetCookiesCallback& callback);
+
+ void InvokeGetCookieStringCallback(
+ const CookieMonster::GetCookiesCallback& callback);
+
+ friend class base::RefCountedThreadSafe<DelayedCookieMonster>;
+ virtual ~DelayedCookieMonster();
+
+ scoped_refptr<CookieMonster> cookie_monster_;
+
+ bool did_run_;
+ bool result_;
+ std::string cookie_;
+ std::string cookie_line_;
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_STORE_TEST_HELPERS_H_
diff --git a/chromium/net/cookies/cookie_store_unittest.h b/chromium/net/cookies/cookie_store_unittest.h
new file mode 100644
index 00000000000..90999ecdc31
--- /dev/null
+++ b/chromium/net/cookies/cookie_store_unittest.h
@@ -0,0 +1,1160 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_COOKIE_STORE_UNITTEST_H_
+#define NET_COOKIES_COOKIE_STORE_UNITTEST_H_
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/threading/thread.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
+#include "net/cookies/cookie_store_test_callbacks.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// This file declares unittest templates that can be used to test common
+// behavior of any CookieStore implementation.
+// See cookie_monster_unittest.cc for an example of an implementation.
+
+namespace net {
+
+using base::Thread;
+
+const int kTimeout = 1000;
+
+const char kUrlFtp[] = "ftp://ftp.google.izzle/";
+const char kUrlGoogle[] = "http://www.google.izzle";
+const char kUrlGoogleFoo[] = "http://www.google.izzle/foo";
+const char kUrlGoogleBar[] = "http://www.google.izzle/bar";
+const char kUrlGoogleSecure[] = "https://www.google.izzle";
+const char kValidCookieLine[] = "A=B; path=/";
+const char kValidDomainCookieLine[] = "A=B; path=/; domain=google.izzle";
+
+// The CookieStoreTestTraits must have the following members:
+// struct CookieStoreTestTraits {
+// // Factory function.
+// static scoped_refptr<CookieStore> Create();
+//
+// // The cookie store is a CookieMonster. Only used to test
+// // GetCookieMonster().
+// static const bool is_cookie_monster;
+//
+// // The cookie store supports cookies with the exclude_httponly() option.
+// static const bool supports_http_only;
+//
+// // The cookie store is able to make the difference between the ".com"
+// // and the "com" domains.
+// static const bool supports_non_dotted_domains;
+//
+// // The cookie store handles the domains with trailing dots (such as "com.")
+// // correctly.
+// static const bool supports_trailing_dots;
+//
+// // The cookie store rejects cookies for invalid schemes such as ftp.
+// static const bool filters_schemes;
+//
+// // The cookie store has a bug happening when a path is a substring of
+// // another.
+// static const bool has_path_prefix_bug;
+//
+// // Time to wait between two cookie insertions to ensure that cookies have
+// // different creation times.
+// static const int creation_time_granularity_in_ms;
+// };
+
+template <class CookieStoreTestTraits>
+class CookieStoreTest : public testing::Test {
+ protected:
+ CookieStoreTest()
+ : url_google_(kUrlGoogle),
+ url_google_secure_(kUrlGoogleSecure),
+ url_google_foo_(kUrlGoogleFoo),
+ url_google_bar_(kUrlGoogleBar) {
+ // This test may be used outside of the net test suite, and thus may not
+ // have a message loop.
+ if (!base::MessageLoop::current())
+ message_loop_.reset(new base::MessageLoop);
+ weak_factory_.reset(new base::WeakPtrFactory<base::MessageLoop>(
+ base::MessageLoop::current()));
+ }
+
+ // Helper methods for the asynchronous Cookie Store API that call the
+ // asynchronous method and then pump the loop until the callback is invoked,
+ // finally returning the value.
+
+ std::string GetCookies(CookieStore* cs, const GURL& url) {
+ DCHECK(cs);
+ CookieOptions options;
+ if (!CookieStoreTestTraits::supports_http_only)
+ options.set_include_httponly();
+ StringResultCookieCallback callback;
+ cs->GetCookiesWithOptionsAsync(
+ url, options,
+ base::Bind(&StringResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ std::string GetCookiesWithOptions(CookieStore* cs,
+ const GURL& url,
+ const CookieOptions& options) {
+ DCHECK(cs);
+ StringResultCookieCallback callback;
+ cs->GetCookiesWithOptionsAsync(
+ url, options, base::Bind(&StringResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ bool SetCookieWithOptions(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options) {
+ DCHECK(cs);
+ BoolResultCookieCallback callback;
+ cs->SetCookieWithOptionsAsync(
+ url, cookie_line, options,
+ base::Bind(&BoolResultCookieCallback::Run,
+ base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ bool SetCookieWithServerTime(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_line,
+ const base::Time& server_time) {
+ CookieOptions options;
+ if (!CookieStoreTestTraits::supports_http_only)
+ options.set_include_httponly();
+ options.set_server_time(server_time);
+ return SetCookieWithOptions(cs, url, cookie_line, options);
+ }
+
+ bool SetCookie(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_line) {
+ CookieOptions options;
+ if (!CookieStoreTestTraits::supports_http_only)
+ options.set_include_httponly();
+ return SetCookieWithOptions(cs, url, cookie_line, options);
+ }
+
+ void DeleteCookie(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_name) {
+ DCHECK(cs);
+ NoResultCookieCallback callback;
+ cs->DeleteCookieAsync(
+ url, cookie_name,
+ base::Bind(&NoResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ }
+
+ int DeleteCreatedBetween(CookieStore* cs,
+ const base::Time& delete_begin,
+ const base::Time& delete_end) {
+ DCHECK(cs);
+ IntResultCookieCallback callback;
+ cs->DeleteAllCreatedBetweenAsync(
+ delete_begin, delete_end,
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ int DeleteSessionCookies(CookieStore* cs) {
+ DCHECK(cs);
+ IntResultCookieCallback callback;
+ cs->DeleteSessionCookiesAsync(
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(&callback)));
+ RunFor(kTimeout);
+ EXPECT_TRUE(callback.did_run());
+ return callback.result();
+ }
+
+ void RunFor(int ms) {
+ // Runs the test thread message loop for up to |ms| milliseconds.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&base::MessageLoop::Quit, weak_factory_->GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(ms));
+ base::MessageLoop::current()->Run();
+ weak_factory_->InvalidateWeakPtrs();
+ }
+
+ scoped_refptr<CookieStore> GetCookieStore() {
+ return CookieStoreTestTraits::Create();
+ }
+
+ // Compares two cookie lines.
+ void MatchCookieLines(const std::string& line1, const std::string& line2) {
+ EXPECT_EQ(TokenizeCookieLine(line1), TokenizeCookieLine(line2));
+ }
+
+ // Check the cookie line by polling until equality or a timeout is reached.
+ void MatchCookieLineWithTimeout(CookieStore* cs,
+ const GURL& url,
+ const std::string& line) {
+ std::string cookies = GetCookies(cs, url);
+ bool matched = (TokenizeCookieLine(line) == TokenizeCookieLine(cookies));
+ base::Time polling_end_date = base::Time::Now() +
+ base::TimeDelta::FromMilliseconds(
+ CookieStoreTestTraits::creation_time_granularity_in_ms);
+
+ while (!matched && base::Time::Now() <= polling_end_date) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ cookies = GetCookies(cs, url);
+ matched = (TokenizeCookieLine(line) == TokenizeCookieLine(cookies));
+ }
+
+ EXPECT_TRUE(matched) << "\"" << cookies
+ << "\" does not match \"" << line << "\"";
+ }
+
+ GURL url_google_;
+ GURL url_google_secure_;
+ GURL url_google_foo_;
+ GURL url_google_bar_;
+
+ scoped_ptr<base::WeakPtrFactory<base::MessageLoop> > weak_factory_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+
+ private:
+ // Returns a set of strings of type "name=value". Fails in case of duplicate.
+ std::set<std::string> TokenizeCookieLine(const std::string& line) {
+ std::set<std::string> tokens;
+ base::StringTokenizer tokenizer(line, " ;");
+ while (tokenizer.GetNext())
+ EXPECT_TRUE(tokens.insert(tokenizer.token()).second);
+ return tokens;
+ }
+};
+
+TYPED_TEST_CASE_P(CookieStoreTest);
+
+TYPED_TEST_P(CookieStoreTest, TypeTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_EQ(cs->GetCookieMonster(),
+ (TypeParam::is_cookie_monster) ?
+ static_cast<CookieMonster*>(cs.get()) : NULL);
+}
+
+TYPED_TEST_P(CookieStoreTest, DomainTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "A=B"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), this->url_google_, "C=D; domain=.google.izzle"));
+ this->MatchCookieLines("A=B; C=D",
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Verify that A=B was set as a host cookie rather than a domain
+ // cookie -- should not be accessible from a sub sub-domain.
+ this->MatchCookieLines(
+ "C=D", this->GetCookies(cs.get(), GURL("http://foo.www.google.izzle")));
+
+ // Test and make sure we find domain cookies on the same domain.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), this->url_google_, "E=F; domain=.www.google.izzle"));
+ this->MatchCookieLines("A=B; C=D; E=F",
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Test setting a domain= that doesn't start w/ a dot, should
+ // treat it as a domain cookie, as if there was a pre-pended dot.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), this->url_google_, "G=H; domain=www.google.izzle"));
+ this->MatchCookieLines("A=B; C=D; E=F; G=H",
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Test domain enforcement, should fail on a sub-domain or something too deep.
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), this->url_google_, "I=J; domain=.izzle"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL("http://a.izzle")));
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), this->url_google_, "K=L; domain=.bla.www.google.izzle"));
+ this->MatchCookieLines(
+ "C=D; E=F; G=H",
+ this->GetCookies(cs.get(), GURL("http://bla.www.google.izzle")));
+ this->MatchCookieLines("A=B; C=D; E=F; G=H",
+ this->GetCookies(cs.get(), this->url_google_));
+}
+
+// FireFox recognizes domains containing trailing periods as valid.
+// IE and Safari do not. Assert the expected policy here.
+TYPED_TEST_P(CookieStoreTest, DomainWithTrailingDotTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), this->url_google_, "a=1; domain=.www.google.com."));
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), this->url_google_, "b=2; domain=.www.google.com.."));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+}
+
+// Test that cookies can bet set on higher level domains.
+// http://b/issue?id=896491
+TYPED_TEST_P(CookieStoreTest, ValidSubdomainTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url_abcd("http://a.b.c.d.com");
+ GURL url_bcd("http://b.c.d.com");
+ GURL url_cd("http://c.d.com");
+ GURL url_d("http://d.com");
+
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_abcd, "a=1; domain=.a.b.c.d.com"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_abcd, "b=2; domain=.b.c.d.com"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_abcd, "c=3; domain=.c.d.com"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_abcd, "d=4; domain=.d.com"));
+
+ this->MatchCookieLines("a=1; b=2; c=3; d=4",
+ this->GetCookies(cs.get(), url_abcd));
+ this->MatchCookieLines("b=2; c=3; d=4", this->GetCookies(cs.get(), url_bcd));
+ this->MatchCookieLines("c=3; d=4", this->GetCookies(cs.get(), url_cd));
+ this->MatchCookieLines("d=4", this->GetCookies(cs.get(), url_d));
+
+ // Check that the same cookie can exist on different sub-domains.
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_bcd, "X=bcd; domain=.b.c.d.com"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_bcd, "X=cd; domain=.c.d.com"));
+ this->MatchCookieLines("b=2; c=3; d=4; X=bcd; X=cd",
+ this->GetCookies(cs.get(), url_bcd));
+ this->MatchCookieLines("c=3; d=4; X=cd", this->GetCookies(cs.get(), url_cd));
+}
+
+// Test that setting a cookie which specifies an invalid domain has
+// no side-effect. An invalid domain in this context is one which does
+// not match the originating domain.
+// http://b/issue?id=896472
+TYPED_TEST_P(CookieStoreTest, InvalidDomainTest) {
+ {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url_foobar("http://foo.bar.com");
+
+ // More specific sub-domain than allowed.
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "a=1; domain=.yo.foo.bar.com"));
+
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_foobar, "b=2; domain=.foo.com"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "c=3; domain=.bar.foo.com"));
+
+ // Different TLD, but the rest is a substring.
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "d=4; domain=.foo.bar.com.net"));
+
+ // A substring that isn't really a parent domain.
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_foobar, "e=5; domain=ar.com"));
+
+ // Completely invalid domains:
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_foobar, "f=6; domain=."));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_foobar, "g=7; domain=/"));
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), url_foobar, "h=8; domain=http://foo.bar.com"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "i=9; domain=..foo.bar.com"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "j=10; domain=..bar.com"));
+
+ // Make sure there isn't something quirky in the domain canonicalization
+ // that supports full URL semantics.
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), url_foobar, "k=11; domain=.foo.bar.com?blah"));
+ EXPECT_FALSE(this->SetCookie(
+ cs.get(), url_foobar, "l=12; domain=.foo.bar.com/blah"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "m=13; domain=.foo.bar.com:80"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "n=14; domain=.foo.bar.com:"));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foobar, "o=15; domain=.foo.bar.com#sup"));
+
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), url_foobar));
+ }
+
+ {
+ // Make sure the cookie code hasn't gotten its subdomain string handling
+ // reversed, missed a suffix check, etc. It's important here that the two
+ // hosts below have the same domain + registry.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url_foocom("http://foo.com.com");
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_foocom, "a=1; domain=.foo.com.com.com"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), url_foocom));
+ }
+}
+
+// Test the behavior of omitting dot prefix from domain, should
+// function the same as FireFox.
+// http://b/issue?id=889898
+TYPED_TEST_P(CookieStoreTest, DomainWithoutLeadingDotTest) {
+ { // The omission of dot results in setting a domain cookie.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url_hosted("http://manage.hosted.filefront.com");
+ GURL url_filefront("http://www.filefront.com");
+ EXPECT_TRUE(
+ this->SetCookie(cs.get(), url_hosted, "sawAd=1; domain=filefront.com"));
+ this->MatchCookieLines("sawAd=1", this->GetCookies(cs.get(), url_hosted));
+ this->MatchCookieLines("sawAd=1",
+ this->GetCookies(cs.get(), url_filefront));
+ }
+
+ { // Even when the domains match exactly, don't consider it host cookie.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://www.google.com");
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1; domain=www.google.com"));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+ this->MatchCookieLines(
+ "a=1", this->GetCookies(cs.get(), GURL("http://sub.www.google.com")));
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(), GURL("http://something-else.com")));
+ }
+}
+
+// Test that the domain specified in cookie string is treated case-insensitive
+// http://b/issue?id=896475.
+TYPED_TEST_P(CookieStoreTest, CaseInsensitiveDomainTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://www.google.com");
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1; domain=.GOOGLE.COM"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "b=2; domain=.wWw.gOOgLE.coM"));
+ this->MatchCookieLines("a=1; b=2", this->GetCookies(cs.get(), url));
+}
+
+TYPED_TEST_P(CookieStoreTest, TestIpAddress) {
+ GURL url_ip("http://1.2.3.4/weee");
+ {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_ip, kValidCookieLine));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), url_ip));
+ }
+
+ { // IP addresses should not be able to set domain cookies.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_ip, "b=2; domain=.1.2.3.4"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_ip, "c=3; domain=.3.4"));
+ this->MatchCookieLines(std::string(), this->GetCookies(cs.get(), url_ip));
+ // It should be allowed to set a cookie if domain= matches the IP address
+ // exactly. This matches IE/Firefox, even though it seems a bit wrong.
+ EXPECT_FALSE(this->SetCookie(cs.get(), url_ip, "b=2; domain=1.2.3.3"));
+ this->MatchCookieLines(std::string(), this->GetCookies(cs.get(), url_ip));
+ EXPECT_TRUE(this->SetCookie(cs.get(), url_ip, "b=2; domain=1.2.3.4"));
+ this->MatchCookieLines("b=2", this->GetCookies(cs.get(), url_ip));
+ }
+}
+
+// Test host cookies, and setting of cookies on TLD.
+TYPED_TEST_P(CookieStoreTest, TestNonDottedAndTLD) {
+ {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://com/");
+ // Allow setting on "com", (but only as a host cookie).
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "b=2; domain=.com"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "c=3; domain=com"));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+ // Make sure it doesn't show up for a normal .com, it should be a host
+ // not a domain cookie.
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(), GURL("http://hopefully-no-cookies.com/")));
+ if (TypeParam::supports_non_dotted_domains) {
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL("http://.com/")));
+ }
+ }
+
+ {
+ // http://com. should be treated the same as http://com.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://com./index.html");
+ if (TypeParam::supports_trailing_dots) {
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1"));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(),
+ GURL("http://hopefully-no-cookies.com./")));
+ } else {
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "a=1"));
+ }
+ }
+
+ { // Should not be able to set host cookie from a subdomain.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://a.b");
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "a=1; domain=.b"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "b=2; domain=b"));
+ this->MatchCookieLines(std::string(), this->GetCookies(cs.get(), url));
+ }
+
+ { // Same test as above, but explicitly on a known TLD (com).
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://google.com");
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "a=1; domain=.com"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "b=2; domain=com"));
+ this->MatchCookieLines(std::string(), this->GetCookies(cs.get(), url));
+ }
+
+ { // Make sure can't set cookie on TLD which is dotted.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://google.co.uk");
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "a=1; domain=.co.uk"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "b=2; domain=.uk"));
+ this->MatchCookieLines(std::string(), this->GetCookies(cs.get(), url));
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(), GURL("http://something-else.co.uk")));
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(), GURL("http://something-else.uk")));
+ }
+
+ { // Intranet URLs should only be able to set host cookies.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://b");
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "b=2; domain=.b"));
+ EXPECT_FALSE(this->SetCookie(cs.get(), url, "c=3; domain=b"));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+ }
+}
+
+// Test reading/writing cookies when the domain ends with a period,
+// as in "www.google.com."
+TYPED_TEST_P(CookieStoreTest, TestHostEndsWithDot) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ GURL url("http://www.google.com");
+ GURL url_with_dot("http://www.google.com.");
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "a=1"));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+
+ if (TypeParam::supports_trailing_dots) {
+ // Do not share cookie space with the dot version of domain.
+ // Note: this is not what FireFox does, but it _is_ what IE+Safari do.
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url, "b=2; domain=.www.google.com."));
+ this->MatchCookieLines("a=1", this->GetCookies(cs.get(), url));
+
+ EXPECT_TRUE(
+ this->SetCookie(cs.get(), url_with_dot, "b=2; domain=.google.com."));
+ this->MatchCookieLines("b=2", this->GetCookies(cs.get(), url_with_dot));
+ } else {
+ EXPECT_TRUE(this->SetCookie(cs.get(), url, "b=2; domain=.www.google.com."));
+ EXPECT_FALSE(
+ this->SetCookie(cs.get(), url_with_dot, "b=2; domain=.google.com."));
+ }
+
+ // Make sure there weren't any side effects.
+ this->MatchCookieLines(
+ std::string(),
+ this->GetCookies(cs.get(), GURL("http://hopefully-no-cookies.com/")));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL("http://.com/")));
+}
+
+TYPED_TEST_P(CookieStoreTest, InvalidScheme) {
+ if (!TypeParam::filters_schemes)
+ return;
+
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_FALSE(this->SetCookie(cs.get(), GURL(kUrlFtp), kValidCookieLine));
+}
+
+TYPED_TEST_P(CookieStoreTest, InvalidScheme_Read) {
+ if (!TypeParam::filters_schemes)
+ return;
+
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_TRUE(
+ this->SetCookie(cs.get(), GURL(kUrlGoogle), kValidDomainCookieLine));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL(kUrlFtp)));
+}
+
+TYPED_TEST_P(CookieStoreTest, PathTest) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ std::string url("http://www.google.izzle");
+ EXPECT_TRUE(this->SetCookie(cs.get(), GURL(url), "A=B; path=/wee"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), GURL(url + "/wee")));
+ this->MatchCookieLines("A=B",
+ this->GetCookies(cs.get(), GURL(url + "/wee/")));
+ this->MatchCookieLines("A=B",
+ this->GetCookies(cs.get(), GURL(url + "/wee/war")));
+ this->MatchCookieLines(
+ "A=B", this->GetCookies(cs.get(), GURL(url + "/wee/war/more/more")));
+ if (!TypeParam::has_path_prefix_bug)
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL(url + "/weehee")));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), GURL(url + "/")));
+
+ // If we add a 0 length path, it should default to /
+ EXPECT_TRUE(this->SetCookie(cs.get(), GURL(url), "A=C; path="));
+ this->MatchCookieLines("A=B; A=C",
+ this->GetCookies(cs.get(), GURL(url + "/wee")));
+ this->MatchCookieLines("A=C", this->GetCookies(cs.get(), GURL(url + "/")));
+}
+
+TYPED_TEST_P(CookieStoreTest, EmptyExpires) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ if (!TypeParam::supports_http_only)
+ options.set_include_httponly();
+ GURL url("http://www7.ipdl.inpit.go.jp/Tokujitu/tjkta.ipdl?N0000=108");
+ std::string set_cookie_line =
+ "ACSTM=20130308043820420042; path=/; domain=ipdl.inpit.go.jp; Expires=";
+ std::string cookie_line = "ACSTM=20130308043820420042";
+
+ this->SetCookieWithOptions(cs.get(), url, set_cookie_line, options);
+ this->MatchCookieLines(cookie_line,
+ this->GetCookiesWithOptions(cs.get(), url, options));
+
+ options.set_server_time(base::Time::Now() - base::TimeDelta::FromHours(1));
+ this->SetCookieWithOptions(cs.get(), url, set_cookie_line, options);
+ this->MatchCookieLines(cookie_line,
+ this->GetCookiesWithOptions(cs.get(), url, options));
+
+ options.set_server_time(base::Time::Now() + base::TimeDelta::FromHours(1));
+ this->SetCookieWithOptions(cs.get(), url, set_cookie_line, options);
+ this->MatchCookieLines(cookie_line,
+ this->GetCookiesWithOptions(cs.get(), url, options));
+}
+
+TYPED_TEST_P(CookieStoreTest, HttpOnlyTest) {
+ if (!TypeParam::supports_http_only)
+ return;
+
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ options.set_include_httponly();
+
+ // Create a httponly cookie.
+ EXPECT_TRUE(this->SetCookieWithOptions(
+ cs.get(), this->url_google_, "A=B; httponly", options));
+
+ // Check httponly read protection.
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines(
+ "A=B", this->GetCookiesWithOptions(cs.get(), this->url_google_, options));
+
+ // Check httponly overwrite protection.
+ EXPECT_FALSE(this->SetCookie(cs.get(), this->url_google_, "A=C"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines(
+ "A=B", this->GetCookiesWithOptions(cs.get(), this->url_google_, options));
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=C", options));
+ this->MatchCookieLines("A=C", this->GetCookies(cs.get(), this->url_google_));
+
+ // Check httponly create protection.
+ EXPECT_FALSE(this->SetCookie(cs.get(), this->url_google_, "B=A; httponly"));
+ this->MatchCookieLines(
+ "A=C", this->GetCookiesWithOptions(cs.get(), this->url_google_, options));
+ EXPECT_TRUE(this->SetCookieWithOptions(
+ cs.get(), this->url_google_, "B=A; httponly", options));
+ this->MatchCookieLines(
+ "A=C; B=A",
+ this->GetCookiesWithOptions(cs.get(), this->url_google_, options));
+ this->MatchCookieLines("A=C", this->GetCookies(cs.get(), this->url_google_));
+}
+
+TYPED_TEST_P(CookieStoreTest, TestCookieDeletion) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+
+ // Create a session cookie.
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, kValidCookieLine));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Delete it via Max-Age.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; max-age=0"));
+ this->MatchCookieLineWithTimeout(cs.get(), this->url_google_, std::string());
+
+ // Create a session cookie.
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, kValidCookieLine));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Delete it via Expires.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-1977 22:50:13 GMT"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Delete it via Max-Age.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; max-age=0"));
+ this->MatchCookieLineWithTimeout(cs.get(), this->url_google_, std::string());
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Delete it via Expires.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) +
+ "; expires=Mon, 18-Apr-1977 22:50:13 GMT"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Check that it is not deleted with significant enough clock skew.
+ base::Time server_time;
+ EXPECT_TRUE(base::Time::FromString("Sun, 17-Apr-1977 22:50:13 GMT",
+ &server_time));
+ EXPECT_TRUE(this->SetCookieWithServerTime(
+ cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-1977 22:50:13 GMT",
+ server_time));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+
+ // Create a persistent cookie.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) + "; expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ // Delete it via Expires, with a unix epoch of 0.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ std::string(kValidCookieLine) +
+ "; expires=Thu, 1-Jan-1970 00:00:00 GMT"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+}
+
+TYPED_TEST_P(CookieStoreTest, TestDeleteAllCreatedBetween) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ const base::Time last_month = base::Time::Now() -
+ base::TimeDelta::FromDays(30);
+ const base::Time last_minute = base::Time::Now() -
+ base::TimeDelta::FromMinutes(1);
+ const base::Time next_minute = base::Time::Now() +
+ base::TimeDelta::FromMinutes(1);
+ const base::Time next_month = base::Time::Now() +
+ base::TimeDelta::FromDays(30);
+
+ // Add a cookie.
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "A=B"));
+ // Check that the cookie is in the store.
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+
+ // Remove cookies in empty intervals.
+ EXPECT_EQ(0, this->DeleteCreatedBetween(cs.get(), last_month, last_minute));
+ EXPECT_EQ(0, this->DeleteCreatedBetween(cs.get(), next_minute, next_month));
+ // Check that the cookie is still there.
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+
+ // Remove the cookie with an interval defined by two dates.
+ EXPECT_EQ(1, this->DeleteCreatedBetween(cs.get(), last_minute, next_minute));
+ // Check that the cookie disappeared.
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+
+ // Add another cookie.
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "C=D"));
+ // Check that the cookie is in the store.
+ this->MatchCookieLines("C=D", this->GetCookies(cs.get(), this->url_google_));
+
+ // Remove the cookie with a null ending time.
+ EXPECT_EQ(1, this->DeleteCreatedBetween(cs.get(), last_minute, base::Time()));
+ // Check that the cookie disappeared.
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+}
+
+TYPED_TEST_P(CookieStoreTest, TestSecure) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "A=B"));
+ this->MatchCookieLines("A=B",
+ this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines(
+ "A=B", this->GetCookies(cs.get(), this->url_google_secure_));
+
+ EXPECT_TRUE(
+ this->SetCookie(cs.get(), this->url_google_secure_, "A=B; secure"));
+ // The secure should overwrite the non-secure.
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines("A=B",
+ this->GetCookies(cs.get(), this->url_google_secure_));
+
+ EXPECT_TRUE(
+ this->SetCookie(cs.get(), this->url_google_secure_, "D=E; secure"));
+ this->MatchCookieLines(std::string(),
+ this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines("A=B; D=E",
+ this->GetCookies(cs.get(), this->url_google_secure_));
+
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_secure_, "A=B"));
+ // The non-secure should overwrite the secure.
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ this->MatchCookieLines("D=E; A=B",
+ this->GetCookies(cs.get(), this->url_google_secure_));
+}
+
+static const int kLastAccessThresholdMilliseconds = 200;
+
+// Formerly NetUtilTest.CookieTest back when we used wininet's cookie handling.
+TYPED_TEST_P(CookieStoreTest, NetUtilCookieTest) {
+ const GURL test_url("http://mojo.jojo.google.izzle/");
+
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+
+ EXPECT_TRUE(this->SetCookie(cs.get(), test_url, "foo=bar"));
+ std::string value = this->GetCookies(cs.get(), test_url);
+ this->MatchCookieLines("foo=bar", value);
+
+ // test that we can retrieve all cookies:
+ EXPECT_TRUE(this->SetCookie(cs.get(), test_url, "x=1"));
+ EXPECT_TRUE(this->SetCookie(cs.get(), test_url, "y=2"));
+
+ std::string result = this->GetCookies(cs.get(), test_url);
+ EXPECT_FALSE(result.empty());
+ EXPECT_NE(result.find("x=1"), std::string::npos) << result;
+ EXPECT_NE(result.find("y=2"), std::string::npos) << result;
+}
+
+TYPED_TEST_P(CookieStoreTest, OverwritePersistentCookie) {
+ GURL url_google("http://www.google.com/");
+ GURL url_chromium("http://chromium.org");
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+
+ // Insert a cookie "a" for path "/path1"
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ url_google,
+ "a=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+
+ // Insert a cookie "b" for path "/path1"
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ url_google,
+ "b=val1; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+
+ // Insert a cookie "b" for path "/path1", that is httponly. This should
+ // overwrite the non-http-only version.
+ CookieOptions allow_httponly;
+ allow_httponly.set_include_httponly();
+ EXPECT_TRUE(this->SetCookieWithOptions(cs.get(),
+ url_google,
+ "b=val2; path=/path1; httponly; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT",
+ allow_httponly));
+
+ // Insert a cookie "a" for path "/path1". This should overwrite.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ url_google,
+ "a=val33; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+
+ // Insert a cookie "a" for path "/path2". This should NOT overwrite
+ // cookie "a", since the path is different.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ url_google,
+ "a=val9; path=/path2; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+
+ // Insert a cookie "a" for path "/path1", but this time for "chromium.org".
+ // Although the name and path match, the hostnames do not, so shouldn't
+ // overwrite.
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ url_chromium,
+ "a=val99; path=/path1; "
+ "expires=Mon, 18-Apr-22 22:50:14 GMT"));
+
+ if (TypeParam::supports_http_only) {
+ this->MatchCookieLines(
+ "a=val33",
+ this->GetCookies(cs.get(), GURL("http://www.google.com/path1")));
+ } else {
+ this->MatchCookieLines(
+ "a=val33; b=val2",
+ this->GetCookies(cs.get(), GURL("http://www.google.com/path1")));
+ }
+ this->MatchCookieLines(
+ "a=val9",
+ this->GetCookies(cs.get(), GURL("http://www.google.com/path2")));
+ this->MatchCookieLines(
+ "a=val99", this->GetCookies(cs.get(), GURL("http://chromium.org/path1")));
+}
+
+TYPED_TEST_P(CookieStoreTest, CookieOrdering) {
+ // Put a random set of cookies into a store and make sure they're returned in
+ // the right order.
+ // Cookies should be sorted by path length and creation time, as per RFC6265.
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), GURL("http://d.c.b.a.google.com/aa/x.html"), "c=1"));
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ GURL("http://b.a.google.com/aa/bb/cc/x.html"),
+ "d=1; domain=b.a.google.com"));
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ TypeParam::creation_time_granularity_in_ms));
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ GURL("http://b.a.google.com/aa/bb/cc/x.html"),
+ "a=4; domain=b.a.google.com"));
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ TypeParam::creation_time_granularity_in_ms));
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ GURL("http://c.b.a.google.com/aa/bb/cc/x.html"),
+ "e=1; domain=c.b.a.google.com"));
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), GURL("http://d.c.b.a.google.com/aa/bb/x.html"), "b=1"));
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), GURL("http://news.bbc.co.uk/midpath/x.html"), "g=10"));
+ EXPECT_EQ("d=1; a=4; e=1; b=1; c=1",
+ this->GetCookies(cs.get(),
+ GURL("http://d.c.b.a.google.com/aa/bb/cc/dd")));
+}
+
+TYPED_TEST_P(CookieStoreTest, DeleteSessionCookie) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ // Create a session cookie and a persistent cookie.
+ EXPECT_TRUE(this->SetCookie(
+ cs.get(), this->url_google_, std::string(kValidCookieLine)));
+ EXPECT_TRUE(this->SetCookie(cs.get(),
+ this->url_google_,
+ "C=D; path=/; domain=google.izzle;"
+ "expires=Mon, 18-Apr-22 22:50:13 GMT"));
+ this->MatchCookieLines("A=B; C=D",
+ this->GetCookies(cs.get(), this->url_google_));
+ // Delete the session cookie.
+ this->DeleteSessionCookies(cs.get());
+ // Check that the session cookie has been deleted but not the persistent one.
+ EXPECT_EQ("C=D", this->GetCookies(cs.get(), this->url_google_));
+}
+
+REGISTER_TYPED_TEST_CASE_P(CookieStoreTest,
+ TypeTest,
+ DomainTest,
+ DomainWithTrailingDotTest,
+ ValidSubdomainTest,
+ InvalidDomainTest,
+ DomainWithoutLeadingDotTest,
+ CaseInsensitiveDomainTest,
+ TestIpAddress,
+ TestNonDottedAndTLD,
+ TestHostEndsWithDot,
+ InvalidScheme,
+ InvalidScheme_Read,
+ PathTest,
+ EmptyExpires,
+ HttpOnlyTest,
+ TestCookieDeletion,
+ TestDeleteAllCreatedBetween,
+ TestSecure,
+ NetUtilCookieTest,
+ OverwritePersistentCookie,
+ CookieOrdering,
+ DeleteSessionCookie);
+
+template<class CookieStoreTestTraits>
+class MultiThreadedCookieStoreTest :
+ public CookieStoreTest<CookieStoreTestTraits> {
+ public:
+ MultiThreadedCookieStoreTest() : other_thread_("CMTthread") {}
+
+ // Helper methods for calling the asynchronous CookieStore methods
+ // from a different thread.
+
+ void GetCookiesTask(CookieStore* cs,
+ const GURL& url,
+ StringResultCookieCallback* callback) {
+ CookieOptions options;
+ if (!CookieStoreTestTraits::supports_http_only)
+ options.set_include_httponly();
+ cs->GetCookiesWithOptionsAsync(
+ url, options,
+ base::Bind(&StringResultCookieCallback::Run,
+ base::Unretained(callback)));
+ }
+
+ void GetCookiesWithOptionsTask(CookieStore* cs,
+ const GURL& url,
+ const CookieOptions& options,
+ StringResultCookieCallback* callback) {
+ cs->GetCookiesWithOptionsAsync(
+ url, options,
+ base::Bind(&StringResultCookieCallback::Run,
+ base::Unretained(callback)));
+ }
+
+ void SetCookieWithOptionsTask(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ BoolResultCookieCallback* callback) {
+ cs->SetCookieWithOptionsAsync(
+ url, cookie_line, options,
+ base::Bind(&BoolResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ void DeleteCookieTask(CookieStore* cs,
+ const GURL& url,
+ const std::string& cookie_name,
+ NoResultCookieCallback* callback) {
+ cs->DeleteCookieAsync(
+ url, cookie_name,
+ base::Bind(&NoResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ void DeleteSessionCookiesTask(CookieStore* cs,
+ IntResultCookieCallback* callback) {
+ cs->DeleteSessionCookiesAsync(
+ base::Bind(&IntResultCookieCallback::Run, base::Unretained(callback)));
+ }
+
+ protected:
+ void RunOnOtherThread(const base::Closure& task) {
+ other_thread_.Start();
+ other_thread_.message_loop()->PostTask(FROM_HERE, task);
+ CookieStoreTest<CookieStoreTestTraits>::RunFor(kTimeout);
+ other_thread_.Stop();
+ }
+
+ Thread other_thread_;
+};
+
+TYPED_TEST_CASE_P(MultiThreadedCookieStoreTest);
+
+// TODO(ycxiao): Eventually, we will need to create a separate thread, create
+// the cookie store on that thread (or at least its store, i.e., the DB
+// thread).
+TYPED_TEST_P(MultiThreadedCookieStoreTest, ThreadCheckGetCookies) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "A=B"));
+ this->MatchCookieLines("A=B", this->GetCookies(cs.get(), this->url_google_));
+ StringResultCookieCallback callback(&this->other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieStoreTest<TypeParam>::GetCookiesTask,
+ base::Unretained(this),
+ cs, this->url_google_, &callback);
+ this->RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("A=B", callback.result());
+}
+
+TYPED_TEST_P(MultiThreadedCookieStoreTest, ThreadCheckGetCookiesWithOptions) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ if (!TypeParam::supports_http_only)
+ options.set_include_httponly();
+ EXPECT_TRUE(this->SetCookie(cs.get(), this->url_google_, "A=B"));
+ this->MatchCookieLines(
+ "A=B", this->GetCookiesWithOptions(cs.get(), this->url_google_, options));
+ StringResultCookieCallback callback(&this->other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieStoreTest<TypeParam>::GetCookiesWithOptionsTask,
+ base::Unretained(this),
+ cs, this->url_google_, options, &callback);
+ this->RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ("A=B", callback.result());
+}
+
+TYPED_TEST_P(MultiThreadedCookieStoreTest, ThreadCheckSetCookieWithOptions) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ if (!TypeParam::supports_http_only)
+ options.set_include_httponly();
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=B", options));
+ BoolResultCookieCallback callback(&this->other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieStoreTest<TypeParam>::SetCookieWithOptionsTask,
+ base::Unretained(this),
+ cs, this->url_google_, "A=B", options, &callback);
+ this->RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_TRUE(callback.result());
+}
+
+TYPED_TEST_P(MultiThreadedCookieStoreTest, ThreadCheckDeleteCookie) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ if (!TypeParam::supports_http_only)
+ options.set_include_httponly();
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=B", options));
+ this->DeleteCookie(cs.get(), this->url_google_, "A");
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=B", options));
+ NoResultCookieCallback callback(&this->other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieStoreTest<TypeParam>::DeleteCookieTask,
+ base::Unretained(this),
+ cs, this->url_google_, "A", &callback);
+ this->RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+}
+
+TYPED_TEST_P(MultiThreadedCookieStoreTest, ThreadCheckDeleteSessionCookies) {
+ scoped_refptr<CookieStore> cs(this->GetCookieStore());
+ CookieOptions options;
+ if (!TypeParam::supports_http_only)
+ options.set_include_httponly();
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=B", options));
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(),
+ this->url_google_,
+ "B=C; expires=Mon, 18-Apr-22 22:50:13 GMT",
+ options));
+ EXPECT_EQ(1, this->DeleteSessionCookies(cs.get()));
+ EXPECT_EQ(0, this->DeleteSessionCookies(cs.get()));
+ EXPECT_TRUE(
+ this->SetCookieWithOptions(cs.get(), this->url_google_, "A=B", options));
+ IntResultCookieCallback callback(&this->other_thread_);
+ base::Closure task = base::Bind(
+ &net::MultiThreadedCookieStoreTest<TypeParam>::DeleteSessionCookiesTask,
+ base::Unretained(this),
+ cs, &callback);
+ this->RunOnOtherThread(task);
+ EXPECT_TRUE(callback.did_run());
+ EXPECT_EQ(1, callback.result());
+}
+
+REGISTER_TYPED_TEST_CASE_P(MultiThreadedCookieStoreTest,
+ ThreadCheckGetCookies,
+ ThreadCheckGetCookiesWithOptions,
+ ThreadCheckSetCookieWithOptions,
+ ThreadCheckDeleteCookie,
+ ThreadCheckDeleteSessionCookies);
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_STORE_UNITTEST_H_
diff --git a/chromium/net/cookies/cookie_util.cc b/chromium/net/cookies/cookie_util.cc
new file mode 100644
index 00000000000..a4200f5c1d6
--- /dev/null
+++ b/chromium/net/cookies/cookie_util.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 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.
+
+#include "net/cookies/cookie_util.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "base/logging.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "build/build_config.h"
+#include "net/base/net_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace cookie_util {
+
+bool DomainIsHostOnly(const std::string& domain_string) {
+ return (domain_string.empty() || domain_string[0] != '.');
+}
+
+std::string GetEffectiveDomain(const std::string& scheme,
+ const std::string& host) {
+ if (scheme == "http" || scheme == "https") {
+ return registry_controlled_domains::GetDomainAndRegistry(
+ host, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+ }
+
+ if (!DomainIsHostOnly(host))
+ return host.substr(1);
+ return host;
+}
+
+bool GetCookieDomainWithString(const GURL& url,
+ const std::string& domain_string,
+ std::string* result) {
+ const std::string url_host(url.host());
+
+ // If no domain was specified in the domain string, default to a host cookie.
+ // We match IE/Firefox in allowing a domain=IPADDR if it matches the url
+ // ip address hostname exactly. It should be treated as a host cookie.
+ if (domain_string.empty() ||
+ (url.HostIsIPAddress() && url_host == domain_string)) {
+ *result = url_host;
+ DCHECK(DomainIsHostOnly(*result));
+ return true;
+ }
+
+ // Get the normalized domain specified in cookie line.
+ url_canon::CanonHostInfo ignored;
+ std::string cookie_domain(CanonicalizeHost(domain_string, &ignored));
+ if (cookie_domain.empty())
+ return false;
+ if (cookie_domain[0] != '.')
+ cookie_domain = "." + cookie_domain;
+
+ // Ensure |url| and |cookie_domain| have the same domain+registry.
+ const std::string url_scheme(url.scheme());
+ const std::string url_domain_and_registry(
+ GetEffectiveDomain(url_scheme, url_host));
+ if (url_domain_and_registry.empty())
+ return false; // IP addresses/intranet hosts can't set domain cookies.
+ const std::string cookie_domain_and_registry(
+ GetEffectiveDomain(url_scheme, cookie_domain));
+ if (url_domain_and_registry != cookie_domain_and_registry)
+ return false; // Can't set a cookie on a different domain + registry.
+
+ // Ensure |url_host| is |cookie_domain| or one of its subdomains. Given that
+ // we know the domain+registry are the same from the above checks, this is
+ // basically a simple string suffix check.
+ const bool is_suffix = (url_host.length() < cookie_domain.length()) ?
+ (cookie_domain != ("." + url_host)) :
+ (url_host.compare(url_host.length() - cookie_domain.length(),
+ cookie_domain.length(), cookie_domain) != 0);
+ if (is_suffix)
+ return false;
+
+ *result = cookie_domain;
+ return true;
+}
+
+// Parse a cookie expiration time. We try to be lenient, but we need to
+// assume some order to distinguish the fields. The basic rules:
+// - The month name must be present and prefix the first 3 letters of the
+// full month name (jan for January, jun for June).
+// - If the year is <= 2 digits, it must occur after the day of month.
+// - The time must be of the format hh:mm:ss.
+// An average cookie expiration will look something like this:
+// Sat, 15-Apr-17 21:01:22 GMT
+base::Time ParseCookieTime(const std::string& time_string) {
+ static const char* kMonths[] = { "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec" };
+ static const int kMonthsLen = arraysize(kMonths);
+ // We want to be pretty liberal, and support most non-ascii and non-digit
+ // characters as a delimiter. We can't treat : as a delimiter, because it
+ // is the delimiter for hh:mm:ss, and we want to keep this field together.
+ // We make sure to include - and +, since they could prefix numbers.
+ // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes
+ // will be preserved, and we will get them here. So we make sure to include
+ // quote characters, and also \ for anything that was internally escaped.
+ static const char* kDelimiters = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
+
+ base::Time::Exploded exploded = {0};
+
+ base::StringTokenizer tokenizer(time_string, kDelimiters);
+
+ bool found_day_of_month = false;
+ bool found_month = false;
+ bool found_time = false;
+ bool found_year = false;
+
+ while (tokenizer.GetNext()) {
+ const std::string token = tokenizer.token();
+ DCHECK(!token.empty());
+ bool numerical = IsAsciiDigit(token[0]);
+
+ // String field
+ if (!numerical) {
+ if (!found_month) {
+ for (int i = 0; i < kMonthsLen; ++i) {
+ // Match prefix, so we could match January, etc
+ if (base::strncasecmp(token.c_str(), kMonths[i], 3) == 0) {
+ exploded.month = i + 1;
+ found_month = true;
+ break;
+ }
+ }
+ } else {
+ // If we've gotten here, it means we've already found and parsed our
+ // month, and we have another string, which we would expect to be the
+ // the time zone name. According to the RFC and my experiments with
+ // how sites format their expirations, we don't have much of a reason
+ // to support timezones. We don't want to ever barf on user input,
+ // but this DCHECK should pass for well-formed data.
+ // DCHECK(token == "GMT");
+ }
+ // Numeric field w/ a colon
+ } else if (token.find(':') != std::string::npos) {
+ if (!found_time &&
+#ifdef COMPILER_MSVC
+ sscanf_s(
+#else
+ sscanf(
+#endif
+ token.c_str(), "%2u:%2u:%2u", &exploded.hour,
+ &exploded.minute, &exploded.second) == 3) {
+ found_time = true;
+ } else {
+ // We should only ever encounter one time-like thing. If we're here,
+ // it means we've found a second, which shouldn't happen. We keep
+ // the first. This check should be ok for well-formed input:
+ // NOTREACHED();
+ }
+ // Numeric field
+ } else {
+ // Overflow with atoi() is unspecified, so we enforce a max length.
+ if (!found_day_of_month && token.length() <= 2) {
+ exploded.day_of_month = atoi(token.c_str());
+ found_day_of_month = true;
+ } else if (!found_year && token.length() <= 5) {
+ exploded.year = atoi(token.c_str());
+ found_year = true;
+ } else {
+ // If we're here, it means we've either found an extra numeric field,
+ // or a numeric field which was too long. For well-formed input, the
+ // following check would be reasonable:
+ // NOTREACHED();
+ }
+ }
+ }
+
+ if (!found_day_of_month || !found_month || !found_time || !found_year) {
+ // We didn't find all of the fields we need. For well-formed input, the
+ // following check would be reasonable:
+ // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
+ return base::Time();
+ }
+
+ // Normalize the year to expand abbreviated years to the full year.
+ if (exploded.year >= 69 && exploded.year <= 99)
+ exploded.year += 1900;
+ if (exploded.year >= 0 && exploded.year <= 68)
+ exploded.year += 2000;
+
+ // If our values are within their correct ranges, we got our time.
+ if (exploded.day_of_month >= 1 && exploded.day_of_month <= 31 &&
+ exploded.month >= 1 && exploded.month <= 12 &&
+ exploded.year >= 1601 && exploded.year <= 30827 &&
+ exploded.hour <= 23 && exploded.minute <= 59 && exploded.second <= 59) {
+ return base::Time::FromUTCExploded(exploded);
+ }
+
+ // One of our values was out of expected range. For well-formed input,
+ // the following check would be reasonable:
+ // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
+
+ return base::Time();
+}
+
+GURL CookieOriginToURL(const std::string& domain, bool is_https) {
+ if (domain.empty())
+ return GURL();
+
+ const std::string scheme = is_https ? "https" : "http";
+ const std::string host = domain[0] == '.' ? domain.substr(1) : domain;
+ return GURL(scheme + "://" + host);
+}
+
+} // namespace cookie_utils
+} // namespace net
+
diff --git a/chromium/net/cookies/cookie_util.h b/chromium/net/cookies/cookie_util.h
new file mode 100644
index 00000000000..aaaf27d41aa
--- /dev/null
+++ b/chromium/net/cookies/cookie_util.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_COOKIE_UTIL_H_
+#define NET_COOKIES_COOKIE_UTIL_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+namespace cookie_util {
+
+// Returns the effective TLD+1 for a given host. This only makes sense for http
+// and https schemes. For other schemes, the host will be returned unchanged
+// (minus any leading period).
+NET_EXPORT std::string GetEffectiveDomain(const std::string& scheme,
+ const std::string& host);
+
+// Determine the actual cookie domain based on the domain string passed
+// (if any) and the URL from which the cookie came.
+// On success returns true, and sets cookie_domain to either a
+// -host cookie domain (ex: "google.com")
+// -domain cookie domain (ex: ".google.com")
+NET_EXPORT bool GetCookieDomainWithString(const GURL& url,
+ const std::string& domain_string,
+ std::string* result);
+
+// Returns true if a domain string represents a host-only cookie,
+// i.e. it doesn't begin with a leading '.' character.
+NET_EXPORT bool DomainIsHostOnly(const std::string& domain_string);
+
+// Parses the string with the cookie time (very forgivingly).
+NET_EXPORT base::Time ParseCookieTime(const std::string& time_string);
+
+// Convenience for converting a cookie origin (domain and https pair) to a URL.
+NET_EXPORT GURL CookieOriginToURL(const std::string& domain, bool is_https);
+
+} // namspace cookie_util
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_UTIL_H_
diff --git a/chromium/net/cookies/cookie_util_unittest.cc b/chromium/net/cookies/cookie_util_unittest.cc
new file mode 100644
index 00000000000..f297afdd076
--- /dev/null
+++ b/chromium/net/cookies/cookie_util_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "net/cookies/cookie_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CookieUtilTest, TestDomainIsHostOnly) {
+ const struct {
+ const char* str;
+ const bool is_host_only;
+ } tests[] = {
+ { "", true },
+ { "www.google.com", true },
+ { ".google.com", false }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ EXPECT_EQ(tests[i].is_host_only,
+ net::cookie_util::DomainIsHostOnly(tests[i].str));
+ }
+}
+
+TEST(CookieUtilTest, TestCookieDateParsing) {
+ const struct {
+ const char* str;
+ const bool valid;
+ const time_t epoch;
+ } tests[] = {
+ { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 },
+ { "Thu, 19-Apr-2007 16:00:00 GMT", true, 1176998400 },
+ { "Wed, 25 Apr 2007 21:02:13 GMT", true, 1177534933 },
+ { "Thu, 19/Apr\\2007 16:00:00 GMT", true, 1176998400 },
+ { "Fri, 1 Jan 2010 01:01:50 GMT", true, 1262307710 },
+ { "Wednesday, 1-Jan-2003 00:00:00 GMT", true, 1041379200 },
+ { ", 1-Jan-2003 00:00:00 GMT", true, 1041379200 },
+ { " 1-Jan-2003 00:00:00 GMT", true, 1041379200 },
+ { "1-Jan-2003 00:00:00 GMT", true, 1041379200 },
+ { "Wed,18-Apr-07 22:50:12 GMT", true, 1176936612 },
+ { "WillyWonka , 18-Apr-07 22:50:12 GMT", true, 1176936612 },
+ { "WillyWonka , 18-Apr-07 22:50:12", true, 1176936612 },
+ { "WillyWonka , 18-apr-07 22:50:12", true, 1176936612 },
+ { "Mon, 18-Apr-1977 22:50:13 GMT", true, 230251813 },
+ { "Mon, 18-Apr-77 22:50:13 GMT", true, 230251813 },
+ // If the cookie came in with the expiration quoted (which in terms of
+ // the RFC you shouldn't do), we will get string quoted. Bug 1261605.
+ { "\"Sat, 15-Apr-17\\\"21:01:22\\\"GMT\"", true, 1492290082 },
+ // Test with full month names and partial names.
+ { "Partyday, 18- April-07 22:50:12", true, 1176936612 },
+ { "Partyday, 18 - Apri-07 22:50:12", true, 1176936612 },
+ { "Wednes, 1-Januar-2003 00:00:00 GMT", true, 1041379200 },
+ // Test that we always take GMT even with other time zones or bogus
+ // values. The RFC says everything should be GMT, and in the worst case
+ // we are 24 hours off because of zone issues.
+ { "Sat, 15-Apr-17 21:01:22", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 GMT-2", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 GMT BLAH", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 GMT-0400", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 GMT-0400 (EDT)",true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 DST", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 -0400", true, 1492290082 },
+ { "Sat, 15-Apr-17 21:01:22 (hello there)", true, 1492290082 },
+ // Test that if we encounter multiple : fields, that we take the first
+ // that correctly parses.
+ { "Sat, 15-Apr-17 21:01:22 11:22:33", true, 1492290082 },
+ { "Sat, 15-Apr-17 ::00 21:01:22", true, 1492290082 },
+ { "Sat, 15-Apr-17 boink:z 21:01:22", true, 1492290082 },
+ // We take the first, which in this case is invalid.
+ { "Sat, 15-Apr-17 91:22:33 21:01:22", false, 0 },
+ // amazon.com formats their cookie expiration like this.
+ { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 },
+ // Test that hh:mm:ss can occur anywhere.
+ { "22:50:12 Thu Apr 18 2007 GMT", true, 1176936612 },
+ { "Thu 22:50:12 Apr 18 2007 GMT", true, 1176936612 },
+ { "Thu Apr 22:50:12 18 2007 GMT", true, 1176936612 },
+ { "Thu Apr 18 22:50:12 2007 GMT", true, 1176936612 },
+ { "Thu Apr 18 2007 22:50:12 GMT", true, 1176936612 },
+ { "Thu Apr 18 2007 GMT 22:50:12", true, 1176936612 },
+ // Test that the day and year can be anywhere if they are unambigious.
+ { "Sat, 15-Apr-17 21:01:22 GMT", true, 1492290082 },
+ { "15-Sat, Apr-17 21:01:22 GMT", true, 1492290082 },
+ { "15-Sat, Apr 21:01:22 GMT 17", true, 1492290082 },
+ { "15-Sat, Apr 21:01:22 GMT 2017", true, 1492290082 },
+ { "15 Apr 21:01:22 2017", true, 1492290082 },
+ { "15 17 Apr 21:01:22", true, 1492290082 },
+ { "Apr 15 17 21:01:22", true, 1492290082 },
+ { "Apr 15 21:01:22 17", true, 1492290082 },
+ { "2017 April 15 21:01:22", true, 1492290082 },
+ { "15 April 2017 21:01:22", true, 1492290082 },
+ // Some invalid dates
+ { "98 April 17 21:01:22", false, 0 },
+ { "Thu, 012-Aug-2008 20:49:07 GMT", false, 0 },
+ { "Thu, 12-Aug-31841 20:49:07 GMT", false, 0 },
+ { "Thu, 12-Aug-9999999999 20:49:07 GMT", false, 0 },
+ { "Thu, 999999999999-Aug-2007 20:49:07 GMT", false, 0 },
+ { "Thu, 12-Aug-2007 20:61:99999999999 GMT", false, 0 },
+ { "IAintNoDateFool", false, 0 },
+ };
+
+ base::Time parsed_time;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ parsed_time = net::cookie_util::ParseCookieTime(tests[i].str);
+ if (!tests[i].valid) {
+ EXPECT_FALSE(!parsed_time.is_null()) << tests[i].str;
+ continue;
+ }
+ EXPECT_TRUE(!parsed_time.is_null()) << tests[i].str;
+ EXPECT_EQ(tests[i].epoch, parsed_time.ToTimeT()) << tests[i].str;
+ }
+}
diff --git a/chromium/net/cookies/parsed_cookie.cc b/chromium/net/cookies/parsed_cookie.cc
new file mode 100644
index 00000000000..125d3d998b9
--- /dev/null
+++ b/chromium/net/cookies/parsed_cookie.cc
@@ -0,0 +1,517 @@
+// Copyright (c) 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.
+
+// Portions of this code based on Mozilla:
+// (netwerk/cookie/src/nsCookieService.cpp)
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2003
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Daniel Witte (dwitte@stanford.edu)
+ * Michiel van Leeuwen (mvl@exedo.nl)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/cookies/parsed_cookie.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+
+// TODO(jww): We are collecting several UMA statistics in this file, and they
+// relate to http://crbug.com/238041. We are measuring stats related to control
+// characters in cookies because, currently, we allow control characters in a
+// variety of scenarios where various RFCs theoretically disallow them. These
+// control characters have the potential to cause problems with certain web
+// servers that reject HTTP requests that contain cookies with control
+// characters. We are measuring whether disallowing such cookies would have a
+// notable impact on our users. We want to collect these stats through 1 stable
+// release, so these UMA stats should remain at least through the M29
+// branch-point.
+
+namespace {
+
+const char kPathTokenName[] = "path";
+const char kDomainTokenName[] = "domain";
+const char kExpiresTokenName[] = "expires";
+const char kMaxAgeTokenName[] = "max-age";
+const char kSecureTokenName[] = "secure";
+const char kHttpOnlyTokenName[] = "httponly";
+const char kPriorityTokenName[] = "priority";
+
+const char kTerminator[] = "\n\r\0";
+const int kTerminatorLen = sizeof(kTerminator) - 1;
+const char kWhitespace[] = " \t";
+const char kValueSeparator[] = ";";
+const char kTokenSeparator[] = ";=";
+
+// Returns true if |c| occurs in |chars|
+// TODO(erikwright): maybe make this take an iterator, could check for end also?
+inline bool CharIsA(const char c, const char* chars) {
+ return strchr(chars, c) != NULL;
+}
+// Seek the iterator to the first occurrence of a character in |chars|.
+// Returns true if it hit the end, false otherwise.
+inline bool SeekTo(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ const char* chars) {
+ for (; *it != end && !CharIsA(**it, chars); ++(*it)) {}
+ return *it == end;
+}
+// Seek the iterator to the first occurrence of a character not in |chars|.
+// Returns true if it hit the end, false otherwise.
+inline bool SeekPast(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ const char* chars) {
+ for (; *it != end && CharIsA(**it, chars); ++(*it)) {}
+ return *it == end;
+}
+inline bool SeekBackPast(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ const char* chars) {
+ for (; *it != end && CharIsA(**it, chars); --(*it)) {}
+ return *it == end;
+}
+
+// Validate whether |value| is a valid token according to [RFC2616],
+// Section 2.2.
+bool IsValidToken(const std::string& value) {
+ if (value.empty())
+ return false;
+
+ // Check that |value| has no separators.
+ std::string separators = "()<>@,;:\\\"/[]?={} \t";
+ if (value.find_first_of(separators) != std::string::npos)
+ return false;
+
+ // Check that |value| has no CTLs.
+ for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) {
+ if ((*i >= 0 && *i <= 31) || *i >= 127)
+ return false;
+ }
+
+ return true;
+}
+
+// Validate value, which may be according to RFC 6265
+// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
+// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
+// ; US-ASCII characters excluding CTLs,
+// ; whitespace DQUOTE, comma, semicolon,
+// ; and backslash
+bool IsValidCookieValue(const std::string& value) {
+ // Number of characters to skip in validation at beginning and end of string.
+ size_t skip = 0;
+ if (value.size() >= 2 && *value.begin() == '"' && *(value.end()-1) == '"')
+ skip = 1;
+ for (std::string::const_iterator i = value.begin() + skip;
+ i != value.end() - skip; ++i) {
+ bool valid_octet =
+ (*i == 0x21 ||
+ (*i >= 0x23 && *i <= 0x2B) ||
+ (*i >= 0x2D && *i <= 0x3A) ||
+ (*i >= 0x3C && *i <= 0x5B) ||
+ (*i >= 0x5D && *i <= 0x7E));
+ if (!valid_octet)
+ return false;
+ }
+ return true;
+}
+
+bool IsValidCookieAttributeValue(const std::string& value) {
+ // The greatest common denominator of cookie attribute values is
+ // <any CHAR except CTLs or ";"> according to RFC 6265.
+ for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) {
+ if ((*i >= 0 && *i <= 31) || *i == ';')
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+ParsedCookie::ParsedCookie(const std::string& cookie_line)
+ : path_index_(0),
+ domain_index_(0),
+ expires_index_(0),
+ maxage_index_(0),
+ secure_index_(0),
+ httponly_index_(0),
+ priority_index_(0) {
+
+ if (cookie_line.size() > kMaxCookieSize) {
+ VLOG(1) << "Not parsing cookie, too large: " << cookie_line.size();
+ return;
+ }
+
+ ParseTokenValuePairs(cookie_line);
+ if (!pairs_.empty())
+ SetupAttributes();
+}
+
+ParsedCookie::~ParsedCookie() {
+}
+
+bool ParsedCookie::IsValid() const {
+ return !pairs_.empty();
+}
+
+CookiePriority ParsedCookie::Priority() const {
+ return (priority_index_ == 0) ? COOKIE_PRIORITY_DEFAULT :
+ StringToCookiePriority(pairs_[priority_index_].second);
+}
+
+bool ParsedCookie::SetName(const std::string& name) {
+ bool valid_token = IsValidToken(name);
+ UMA_HISTOGRAM_BOOLEAN("Cookie.SetNameVaildity", valid_token);
+ if (!valid_token)
+ return false;
+ if (pairs_.empty())
+ pairs_.push_back(std::make_pair("", ""));
+ pairs_[0].first = name;
+ return true;
+}
+
+bool ParsedCookie::SetValue(const std::string& value) {
+ bool valid_cookie_value = IsValidCookieValue(value);
+ UMA_HISTOGRAM_BOOLEAN("Cookie.SetValueCookieValueValidity",
+ valid_cookie_value);
+ if (!valid_cookie_value)
+ return false;
+ if (pairs_.empty())
+ pairs_.push_back(std::make_pair("", ""));
+ pairs_[0].second = value;
+ return true;
+}
+
+bool ParsedCookie::SetPath(const std::string& path) {
+ return SetString(&path_index_, kPathTokenName, path);
+}
+
+bool ParsedCookie::SetDomain(const std::string& domain) {
+ return SetString(&domain_index_, kDomainTokenName, domain);
+}
+
+bool ParsedCookie::SetExpires(const std::string& expires) {
+ return SetString(&expires_index_, kExpiresTokenName, expires);
+}
+
+bool ParsedCookie::SetMaxAge(const std::string& maxage) {
+ return SetString(&maxage_index_, kMaxAgeTokenName, maxage);
+}
+
+bool ParsedCookie::SetIsSecure(bool is_secure) {
+ return SetBool(&secure_index_, kSecureTokenName, is_secure);
+}
+
+bool ParsedCookie::SetIsHttpOnly(bool is_http_only) {
+ return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only);
+}
+
+bool ParsedCookie::SetPriority(const std::string& priority) {
+ return SetString(&priority_index_, kPriorityTokenName, priority);
+}
+
+std::string ParsedCookie::ToCookieLine() const {
+ std::string out;
+ for (PairList::const_iterator it = pairs_.begin();
+ it != pairs_.end(); ++it) {
+ if (!out.empty())
+ out.append("; ");
+ out.append(it->first);
+ if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName) {
+ out.append("=");
+ out.append(it->second);
+ }
+ }
+ return out;
+}
+
+std::string::const_iterator ParsedCookie::FindFirstTerminator(
+ const std::string& s) {
+ std::string::const_iterator end = s.end();
+ size_t term_pos =
+ s.find_first_of(std::string(kTerminator, kTerminatorLen));
+ if (term_pos != std::string::npos) {
+ // We found a character we should treat as an end of string.
+ end = s.begin() + term_pos;
+ }
+ return end;
+}
+
+bool ParsedCookie::ParseToken(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end) {
+ DCHECK(it && token_start && token_end);
+ std::string::const_iterator token_real_end;
+
+ // Seek past any whitespace before the "token" (the name).
+ // token_start should point at the first character in the token
+ if (SeekPast(it, end, kWhitespace))
+ return false; // No token, whitespace or empty.
+ *token_start = *it;
+
+ // Seek over the token, to the token separator.
+ // token_real_end should point at the token separator, i.e. '='.
+ // If it == end after the seek, we probably have a token-value.
+ SeekTo(it, end, kTokenSeparator);
+ token_real_end = *it;
+
+ // Ignore any whitespace between the token and the token separator.
+ // token_end should point after the last interesting token character,
+ // pointing at either whitespace, or at '=' (and equal to token_real_end).
+ if (*it != *token_start) { // We could have an empty token name.
+ --(*it); // Go back before the token separator.
+ // Skip over any whitespace to the first non-whitespace character.
+ SeekBackPast(it, *token_start, kWhitespace);
+ // Point after it.
+ ++(*it);
+ }
+ *token_end = *it;
+
+ // Seek us back to the end of the token.
+ *it = token_real_end;
+ return true;
+}
+
+void ParsedCookie::ParseValue(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end) {
+ DCHECK(it && value_start && value_end);
+
+ // Seek past any whitespace that might in-between the token and value.
+ SeekPast(it, end, kWhitespace);
+ // value_start should point at the first character of the value.
+ *value_start = *it;
+
+ // Just look for ';' to terminate ('=' allowed).
+ // We can hit the end, maybe they didn't terminate.
+ SeekTo(it, end, kValueSeparator);
+
+ // Will be pointed at the ; seperator or the end.
+ *value_end = *it;
+
+ // Ignore any unwanted whitespace after the value.
+ if (*value_end != *value_start) { // Could have an empty value
+ --(*value_end);
+ SeekBackPast(value_end, *value_start, kWhitespace);
+ ++(*value_end);
+ }
+}
+
+std::string ParsedCookie::ParseTokenString(const std::string& token) {
+ std::string::const_iterator it = token.begin();
+ std::string::const_iterator end = FindFirstTerminator(token);
+
+ std::string::const_iterator token_start, token_end;
+ if (ParseToken(&it, end, &token_start, &token_end))
+ return std::string(token_start, token_end);
+ return std::string();
+}
+
+std::string ParsedCookie::ParseValueString(const std::string& value) {
+ std::string::const_iterator it = value.begin();
+ std::string::const_iterator end = FindFirstTerminator(value);
+
+ std::string::const_iterator value_start, value_end;
+ ParseValue(&it, end, &value_start, &value_end);
+ return std::string(value_start, value_end);
+}
+
+// Parse all token/value pairs and populate pairs_.
+void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line) {
+ enum ParsedCookieStatus {
+ PARSED_COOKIE_STATUS_NOTHING = 0x0,
+ PARSED_COOKIE_STATUS_CONTROL_CHAR = 0x1,
+ PARSED_COOKIE_STATUS_INVALID = 0x2,
+ PARSED_COOKIE_STATUS_BOTH =
+ PARSED_COOKIE_STATUS_CONTROL_CHAR | PARSED_COOKIE_STATUS_INVALID
+ };
+ int parsed_cookie_status = PARSED_COOKIE_STATUS_NOTHING;
+
+ pairs_.clear();
+
+ // Ok, here we go. We should be expecting to be starting somewhere
+ // before the cookie line, not including any header name...
+ std::string::const_iterator start = cookie_line.begin();
+ std::string::const_iterator it = start;
+
+ // TODO(erikwright): Make sure we're stripping \r\n in the network code.
+ // Then we can log any unexpected terminators.
+ std::string::const_iterator end = FindFirstTerminator(cookie_line);
+
+ for (int pair_num = 0; pair_num < kMaxPairs && it != end; ++pair_num) {
+ TokenValuePair pair;
+
+ std::string::const_iterator token_start, token_end;
+ if (!ParseToken(&it, end, &token_start, &token_end))
+ break;
+
+ if (it == end || *it != '=') {
+ // We have a token-value, we didn't have any token name.
+ if (pair_num == 0) {
+ // For the first time around, we want to treat single values
+ // as a value with an empty name. (Mozilla bug 169091).
+ // IE seems to also have this behavior, ex "AAA", and "AAA=10" will
+ // set 2 different cookies, and setting "BBB" will then replace "AAA".
+ pair.first = "";
+ // Rewind to the beginning of what we thought was the token name,
+ // and let it get parsed as a value.
+ it = token_start;
+ } else {
+ // Any not-first attribute we want to treat a value as a
+ // name with an empty value... This is so something like
+ // "secure;" will get parsed as a Token name, and not a value.
+ pair.first = std::string(token_start, token_end);
+ }
+ } else {
+ // We have a TOKEN=VALUE.
+ pair.first = std::string(token_start, token_end);
+ ++it; // Skip past the '='.
+ }
+
+ // OK, now try to parse a value.
+ std::string::const_iterator value_start, value_end;
+ ParseValue(&it, end, &value_start, &value_end);
+ // OK, we're finished with a Token/Value.
+ pair.second = std::string(value_start, value_end);
+
+ if (!IsValidCookieAttributeValue(pair.second))
+ parsed_cookie_status |= PARSED_COOKIE_STATUS_CONTROL_CHAR;
+ if (!IsValidToken(pair.second))
+ parsed_cookie_status |= PARSED_COOKIE_STATUS_INVALID;
+
+ // From RFC2109: "Attributes (names) (attr) are case-insensitive."
+ if (pair_num != 0)
+ StringToLowerASCII(&pair.first);
+ pairs_.push_back(pair);
+
+ // We've processed a token/value pair, we're either at the end of
+ // the string or a ValueSeparator like ';', which we want to skip.
+ if (it != end)
+ ++it;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Cookie.ParsedCookieStatus", parsed_cookie_status,
+ PARSED_COOKIE_STATUS_BOTH + 1);
+}
+
+void ParsedCookie::SetupAttributes() {
+ // We skip over the first token/value, the user supplied one.
+ for (size_t i = 1; i < pairs_.size(); ++i) {
+ if (pairs_[i].first == kPathTokenName) {
+ path_index_ = i;
+ } else if (pairs_[i].first == kDomainTokenName) {
+ domain_index_ = i;
+ } else if (pairs_[i].first == kExpiresTokenName) {
+ expires_index_ = i;
+ } else if (pairs_[i].first == kMaxAgeTokenName) {
+ maxage_index_ = i;
+ } else if (pairs_[i].first == kSecureTokenName) {
+ secure_index_ = i;
+ } else if (pairs_[i].first == kHttpOnlyTokenName) {
+ httponly_index_ = i;
+ } else if (pairs_[i].first == kPriorityTokenName) {
+ priority_index_ = i;
+ } else {
+ /* some attribute we don't know or don't care about. */
+ }
+ }
+}
+
+bool ParsedCookie::SetString(size_t* index,
+ const std::string& key,
+ const std::string& value) {
+ if (value.empty()) {
+ ClearAttributePair(*index);
+ return true;
+ } else {
+ return SetAttributePair(index, key, value);
+ }
+}
+
+bool ParsedCookie::SetBool(size_t* index,
+ const std::string& key,
+ bool value) {
+ if (!value) {
+ ClearAttributePair(*index);
+ return true;
+ } else {
+ return SetAttributePair(index, key, std::string());
+ }
+}
+
+bool ParsedCookie::SetAttributePair(size_t* index,
+ const std::string& key,
+ const std::string& value) {
+ bool valid_attribute_pair = IsValidToken(key) &&
+ IsValidCookieAttributeValue(value);
+ UMA_HISTOGRAM_BOOLEAN("Cookie.SetAttributePairCharsValidity",
+ valid_attribute_pair);
+ if (!valid_attribute_pair)
+ return false;
+ if (!IsValid())
+ return false;
+ if (*index) {
+ pairs_[*index].second = value;
+ } else {
+ pairs_.push_back(std::make_pair(key, value));
+ *index = pairs_.size() - 1;
+ }
+ return true;
+}
+
+void ParsedCookie::ClearAttributePair(size_t index) {
+ // The first pair (name/value of cookie at pairs_[0]) cannot be cleared.
+ // Cookie attributes that don't have a value at the moment, are represented
+ // with an index being equal to 0.
+ if (index == 0)
+ return;
+
+ size_t* indexes[] = { &path_index_, &domain_index_, &expires_index_,
+ &maxage_index_, &secure_index_, &httponly_index_,
+ &priority_index_ };
+ for (size_t i = 0; i < arraysize(indexes); ++i) {
+ if (*indexes[i] == index)
+ *indexes[i] = 0;
+ else if (*indexes[i] > index)
+ --*indexes[i];
+ }
+ pairs_.erase(pairs_.begin() + index);
+}
+
+} // namespace
diff --git a/chromium/net/cookies/parsed_cookie.h b/chromium/net/cookies/parsed_cookie.h
new file mode 100644
index 00000000000..93b2c23853b
--- /dev/null
+++ b/chromium/net/cookies/parsed_cookie.h
@@ -0,0 +1,147 @@
+// Copyright (c) 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.
+
+#ifndef NET_COOKIES_PARSED_COOKIE_H_
+#define NET_COOKIES_PARSED_COOKIE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/cookies/cookie_constants.h"
+
+namespace net {
+
+class NET_EXPORT ParsedCookie {
+ public:
+ typedef std::pair<std::string, std::string> TokenValuePair;
+ typedef std::vector<TokenValuePair> PairList;
+
+ // The maximum length of a cookie string we will try to parse
+ static const size_t kMaxCookieSize = 4096;
+ // The maximum number of Token/Value pairs. Shouldn't have more than 8.
+ static const int kMaxPairs = 16;
+
+ // Construct from a cookie string like "BLAH=1; path=/; domain=.google.com"
+ ParsedCookie(const std::string& cookie_line);
+ ~ParsedCookie();
+
+ // You should not call any other methods except for SetName/SetValue on the
+ // class if !IsValid.
+ bool IsValid() const;
+
+ const std::string& Name() const { return pairs_[0].first; }
+ const std::string& Token() const { return Name(); }
+ const std::string& Value() const { return pairs_[0].second; }
+
+ bool HasPath() const { return path_index_ != 0; }
+ const std::string& Path() const { return pairs_[path_index_].second; }
+ bool HasDomain() const { return domain_index_ != 0; }
+ const std::string& Domain() const { return pairs_[domain_index_].second; }
+ bool HasExpires() const { return expires_index_ != 0; }
+ const std::string& Expires() const { return pairs_[expires_index_].second; }
+ bool HasMaxAge() const { return maxage_index_ != 0; }
+ const std::string& MaxAge() const { return pairs_[maxage_index_].second; }
+ bool IsSecure() const { return secure_index_ != 0; }
+ bool IsHttpOnly() const { return httponly_index_ != 0; }
+ CookiePriority Priority() const;
+
+ // Returns the number of attributes, for example, returning 2 for:
+ // "BLAH=hah; path=/; domain=.google.com"
+ size_t NumberOfAttributes() const { return pairs_.size() - 1; }
+
+ // These functions set the respective properties of the cookie. If the
+ // parameters are empty, the respective properties are cleared.
+ // The functions return false in case an error occurred.
+ // The cookie needs to be assigned a name/value before setting the other
+ // attributes.
+ bool SetName(const std::string& name);
+ bool SetValue(const std::string& value);
+ bool SetPath(const std::string& path);
+ bool SetDomain(const std::string& domain);
+ bool SetExpires(const std::string& expires);
+ bool SetMaxAge(const std::string& maxage);
+ bool SetIsSecure(bool is_secure);
+ bool SetIsHttpOnly(bool is_http_only);
+ bool SetPriority(const std::string& priority);
+
+ // Returns the cookie description as it appears in a HTML response header.
+ std::string ToCookieLine() const;
+
+ // Returns an iterator pointing to the first terminator character found in
+ // the given string.
+ static std::string::const_iterator FindFirstTerminator(const std::string& s);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments token_start and token_end to the start and end
+ // positions of a cookie attribute token name parsed from the segment, and
+ // updates the segment iterator to point to the next segment to be parsed.
+ // If no token is found, the function returns false.
+ static bool ParseToken(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* token_start,
+ std::string::const_iterator* token_end);
+
+ // Given iterators pointing to the beginning and end of a string segment,
+ // returns as output arguments value_start and value_end to the start and end
+ // positions of a cookie attribute value parsed from the segment, and updates
+ // the segment iterator to point to the next segment to be parsed.
+ static void ParseValue(std::string::const_iterator* it,
+ const std::string::const_iterator& end,
+ std::string::const_iterator* value_start,
+ std::string::const_iterator* value_end);
+
+ // Same as the above functions, except the input is assumed to contain the
+ // desired token/value and nothing else.
+ static std::string ParseTokenString(const std::string& token);
+ static std::string ParseValueString(const std::string& value);
+
+ private:
+ void ParseTokenValuePairs(const std::string& cookie_line);
+ void SetupAttributes();
+
+ // Sets a key/value pair for a cookie. |index| has to point to one of the
+ // |*_index_| fields in ParsedCookie and is updated to the position where
+ // the key/value pair is set in |pairs_|. Accordingly, |key| has to correspond
+ // to the token matching |index|. If |value| contains invalid characters, the
+ // cookie parameter is not changed and the function returns false.
+ // If |value| is empty/false the key/value pair is removed.
+ bool SetString(size_t* index,
+ const std::string& key,
+ const std::string& value);
+ bool SetBool(size_t* index,
+ const std::string& key,
+ bool value);
+
+ // Helper function for SetString and SetBool handling the case that the
+ // key/value pair shall not be removed.
+ bool SetAttributePair(size_t* index,
+ const std::string& key,
+ const std::string& value);
+
+ // Removes the key/value pair from a cookie that is identified by |index|.
+ // |index| refers to a position in |pairs_|.
+ void ClearAttributePair(size_t index);
+
+ PairList pairs_;
+ bool is_valid_;
+ // These will default to 0, but that should never be valid since the
+ // 0th index is the user supplied token/value, not an attribute.
+ // We're really never going to have more than like 8 attributes, so we
+ // could fit these into 3 bits each if we're worried about size...
+ size_t path_index_;
+ size_t domain_index_;
+ size_t expires_index_;
+ size_t maxage_index_;
+ size_t secure_index_;
+ size_t httponly_index_;
+ size_t priority_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(ParsedCookie);
+};
+
+} // namespace net
+
+#endif // NET_COOKIES_COOKIE_MONSTER_H_
diff --git a/chromium/net/cookies/parsed_cookie_unittest.cc b/chromium/net/cookies/parsed_cookie_unittest.cc
new file mode 100644
index 00000000000..ad4aba65d79
--- /dev/null
+++ b/chromium/net/cookies/parsed_cookie_unittest.cc
@@ -0,0 +1,425 @@
+// Copyright (c) 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.
+
+#include <string>
+
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/parsed_cookie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(ParsedCookieTest, TestBasic) {
+ ParsedCookie pc("a=b");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_EQ("a", pc.Name());
+ EXPECT_EQ("b", pc.Value());
+}
+
+TEST(ParsedCookieTest, TestQuoted) {
+ // These are some quoting cases which the major browsers all
+ // handle differently. I've tested Internet Explorer 6, Opera 9.6,
+ // Firefox 3, and Safari Windows 3.2.1. We originally tried to match
+ // Firefox closely, however we now match Internet Explorer and Safari.
+ const char* values[] = {
+ // Trailing whitespace after a quoted value. The whitespace after
+ // the quote is stripped in all browsers.
+ "\"zzz \" ", "\"zzz \"",
+ // Handling a quoted value with a ';', like FOO="zz;pp" ;
+ // IE and Safari: "zz;
+ // Firefox and Opera: "zz;pp"
+ "\"zz;pp\" ;", "\"zz",
+ // Handling a value with multiple quoted parts, like FOO="zzz " "ppp" ;
+ // IE and Safari: "zzz " "ppp";
+ // Firefox: "zzz ";
+ // Opera: <rejects cookie>
+ "\"zzz \" \"ppp\" ", "\"zzz \" \"ppp\"",
+ // A quote in a value that didn't start quoted. like FOO=A"B ;
+ // IE, Safari, and Firefox: A"B;
+ // Opera: <rejects cookie>
+ "A\"B", "A\"B",
+ };
+
+ for (size_t i = 0; i < arraysize(values); i += 2) {
+ std::string input(values[i]);
+ std::string expected(values[i + 1]);
+
+ ParsedCookie pc("aBc=" + input + " ; path=\"/\" ; httponly ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("aBc", pc.Name());
+ EXPECT_EQ(expected, pc.Value());
+
+ // If a path was quoted, the path attribute keeps the quotes. This will
+ // make the cookie effectively useless, but path parameters aren't supposed
+ // to be quoted. Bug 1261605.
+ EXPECT_EQ("\"/\"", pc.Path());
+ }
+}
+
+TEST(ParsedCookieTest, TestNameless) {
+ ParsedCookie pc("BLAHHH; path=/; secure;");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("BLAHHH", pc.Value());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+}
+
+TEST(ParsedCookieTest, TestAttributeCase) {
+ ParsedCookie pc("BLAHHH; Path=/; sECuRe; httpONLY; pRIoRitY=hIgH");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("BLAHHH", pc.Value());
+ EXPECT_EQ(COOKIE_PRIORITY_HIGH, pc.Priority());
+ EXPECT_EQ(4U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TestDoubleQuotedNameless) {
+ ParsedCookie pc("\"BLA\\\"HHH\"; path=/; secure;");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("\"BLA\\\"HHH\"", pc.Value());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, QuoteOffTheEnd) {
+ ParsedCookie pc("a=\"B");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("a", pc.Name());
+ EXPECT_EQ("\"B", pc.Value());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(0U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MissingName) {
+ ParsedCookie pc("=ABC");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("", pc.Name());
+ EXPECT_EQ("ABC", pc.Value());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(0U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, MissingValue) {
+ ParsedCookie pc("ABC=; path = /wee");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ABC", pc.Name());
+ EXPECT_EQ("", pc.Value());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/wee", pc.Path());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(1U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, Whitespace) {
+ ParsedCookie pc(" A = BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("A", pc.Name());
+ EXPECT_EQ("BC", pc.Value());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ // We parse anything between ; as attributes, so we end up with two
+ // attributes with an empty string name and value.
+ EXPECT_EQ(4U, pc.NumberOfAttributes());
+}
+TEST(ParsedCookieTest, MultipleEquals) {
+ ParsedCookie pc(" A=== BC ;secure;;; httponly");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("A", pc.Name());
+ EXPECT_EQ("== BC", pc.Value());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(4U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, QuotedTrailingWhitespace) {
+ ParsedCookie pc("ANCUUID=\"zohNumRKgI0oxyhSsV3Z7D\" ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ANCUUID", pc.Name());
+ // Stripping whitespace after the quotes matches all other major browsers.
+ EXPECT_EQ("\"zohNumRKgI0oxyhSsV3Z7D\"", pc.Value());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TrailingWhitespace) {
+ ParsedCookie pc("ANCUUID=zohNumRKgI0oxyhSsV3Z7D ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; ");
+ EXPECT_TRUE(pc.IsValid());
+ EXPECT_EQ("ANCUUID", pc.Name());
+ EXPECT_EQ("zohNumRKgI0oxyhSsV3Z7D", pc.Value());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_EQ("/", pc.Path());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+ EXPECT_EQ(2U, pc.NumberOfAttributes());
+}
+
+TEST(ParsedCookieTest, TooManyPairs) {
+ std::string blankpairs;
+ blankpairs.resize(ParsedCookie::kMaxPairs - 1, ';');
+
+ ParsedCookie pc1(blankpairs + "secure");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_TRUE(pc1.IsSecure());
+
+ ParsedCookie pc2(blankpairs + ";secure");
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_FALSE(pc2.IsSecure());
+}
+
+// TODO(erikwright): some better test cases for invalid cookies.
+TEST(ParsedCookieTest, InvalidWhitespace) {
+ ParsedCookie pc(" ");
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidTooLong) {
+ std::string maxstr;
+ maxstr.resize(ParsedCookie::kMaxCookieSize, 'a');
+
+ ParsedCookie pc1(maxstr);
+ EXPECT_TRUE(pc1.IsValid());
+
+ ParsedCookie pc2(maxstr + "A");
+ EXPECT_FALSE(pc2.IsValid());
+}
+
+TEST(ParsedCookieTest, InvalidEmpty) {
+ ParsedCookie pc((std::string()));
+ EXPECT_FALSE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, EmbeddedTerminator) {
+ ParsedCookie pc1("AAA=BB\0ZYX");
+ ParsedCookie pc2("AAA=BB\rZYX");
+ ParsedCookie pc3("AAA=BB\nZYX");
+ EXPECT_TRUE(pc1.IsValid());
+ EXPECT_EQ("AAA", pc1.Name());
+ EXPECT_EQ("BB", pc1.Value());
+ EXPECT_TRUE(pc2.IsValid());
+ EXPECT_EQ("AAA", pc2.Name());
+ EXPECT_EQ("BB", pc2.Value());
+ EXPECT_TRUE(pc3.IsValid());
+ EXPECT_EQ("AAA", pc3.Name());
+ EXPECT_EQ("BB", pc3.Value());
+}
+
+TEST(ParsedCookieTest, ParseTokensAndValues) {
+ EXPECT_EQ("hello",
+ ParsedCookie::ParseTokenString("hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ ParsedCookie::ParseTokenString("fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ ParsedCookie::ParseTokenString("hello world\tgood\rbye"));
+ EXPECT_EQ("A",
+ ParsedCookie::ParseTokenString("A=B=C;D=E"));
+ EXPECT_EQ("hello",
+ ParsedCookie::ParseValueString("hello\nworld"));
+ EXPECT_EQ("fs!!@",
+ ParsedCookie::ParseValueString("fs!!@;helloworld"));
+ EXPECT_EQ("hello world\tgood",
+ ParsedCookie::ParseValueString("hello world\tgood\rbye"));
+ EXPECT_EQ("A=B=C",
+ ParsedCookie::ParseValueString("A=B=C;D=E"));
+}
+
+TEST(ParsedCookieTest, SerializeCookieLine) {
+ const char input[] = "ANCUUID=zohNumRKgI0oxyhSsV3Z7D ; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT ; "
+ "path=/ ; priority=low ; ";
+ const char output[] = "ANCUUID=zohNumRKgI0oxyhSsV3Z7D; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; "
+ "path=/; priority=low";
+ ParsedCookie pc(input);
+ EXPECT_EQ(output, pc.ToCookieLine());
+}
+
+
+TEST(ParsedCookieTest, SetNameAndValue) {
+ ParsedCookie empty((std::string()));
+ EXPECT_FALSE(empty.IsValid());
+ EXPECT_FALSE(empty.SetDomain("foobar.com"));
+ EXPECT_TRUE(empty.SetName("name"));
+ EXPECT_TRUE(empty.SetValue("value"));
+ EXPECT_EQ("name=value", empty.ToCookieLine());
+ EXPECT_TRUE(empty.IsValid());
+
+ // We don't test
+ // ParsedCookie invalid("@foo=bar");
+ // EXPECT_FALSE(invalid.IsValid());
+ // here because we are slightly more tolerant to invalid cookie names and
+ // values that are set by webservers. We only enforce a correct name and
+ // value if set via SetName() and SetValue().
+
+ ParsedCookie pc("name=value");
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set invalid name / value.
+ EXPECT_FALSE(pc.SetName("@foobar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetName(std::string()));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetValue("foo bar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_FALSE(pc.SetValue("\"foobar"));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set valid name / value
+ EXPECT_TRUE(pc.SetName("test"));
+ EXPECT_EQ("test=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_TRUE(pc.SetValue("\"foobar\""));
+ EXPECT_EQ("test=\"foobar\"", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_TRUE(pc.SetValue(std::string()));
+ EXPECT_EQ("test=", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+}
+
+TEST(ParsedCookieTest, SetAttributes) {
+ ParsedCookie pc("name=value");
+ EXPECT_TRUE(pc.IsValid());
+
+ // Clear an unset attribute.
+ EXPECT_TRUE(pc.SetDomain(std::string()));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set a string containing an invalid character
+ EXPECT_FALSE(pc.SetDomain("foo;bar"));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_TRUE(pc.IsValid());
+
+ // Set all other attributes and check that they are appended in order.
+ EXPECT_TRUE(pc.SetDomain("domain.com"));
+ EXPECT_TRUE(pc.SetPath("/"));
+ EXPECT_TRUE(pc.SetExpires("Sun, 18-Apr-2027 21:06:29 GMT"));
+ EXPECT_TRUE(pc.SetMaxAge("12345"));
+ EXPECT_TRUE(pc.SetIsSecure(true));
+ EXPECT_TRUE(pc.SetIsHttpOnly(true));
+ EXPECT_TRUE(pc.SetIsHttpOnly(true));
+ EXPECT_TRUE(pc.SetPriority("HIGH"));
+ EXPECT_EQ("name=value; domain=domain.com; path=/; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; "
+ "httponly; priority=HIGH",
+ pc.ToCookieLine());
+ EXPECT_TRUE(pc.HasDomain());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.HasMaxAge());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ(COOKIE_PRIORITY_HIGH, pc.Priority());
+
+ // Clear one attribute from the middle.
+ EXPECT_TRUE(pc.SetPath("/foo"));
+ EXPECT_TRUE(pc.HasDomain());
+ EXPECT_TRUE(pc.HasPath());
+ EXPECT_TRUE(pc.HasExpires());
+ EXPECT_TRUE(pc.IsSecure());
+ EXPECT_TRUE(pc.IsHttpOnly());
+ EXPECT_EQ("name=value; domain=domain.com; path=/foo; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; "
+ "httponly; priority=HIGH",
+ pc.ToCookieLine());
+
+ // Set priority to medium.
+ EXPECT_TRUE(pc.SetPriority("medium"));
+ EXPECT_EQ("name=value; domain=domain.com; path=/foo; "
+ "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; "
+ "httponly; priority=medium",
+ pc.ToCookieLine());
+
+ // Clear the rest and change the name and value.
+ EXPECT_TRUE(pc.SetDomain(std::string()));
+ EXPECT_TRUE(pc.SetPath(std::string()));
+ EXPECT_TRUE(pc.SetExpires(std::string()));
+ EXPECT_TRUE(pc.SetMaxAge(std::string()));
+ EXPECT_TRUE(pc.SetIsSecure(false));
+ EXPECT_TRUE(pc.SetIsHttpOnly(false));
+ EXPECT_TRUE(pc.SetName("name2"));
+ EXPECT_TRUE(pc.SetValue("value2"));
+ EXPECT_TRUE(pc.SetPriority(std::string()));
+ EXPECT_FALSE(pc.HasDomain());
+ EXPECT_FALSE(pc.HasPath());
+ EXPECT_FALSE(pc.HasExpires());
+ EXPECT_FALSE(pc.HasMaxAge());
+ EXPECT_FALSE(pc.IsSecure());
+ EXPECT_FALSE(pc.IsHttpOnly());
+ EXPECT_EQ("name2=value2", pc.ToCookieLine());
+}
+
+TEST(ParsedCookieTest, SetPriority) {
+ ParsedCookie pc("name=value");
+ EXPECT_TRUE(pc.IsValid());
+
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+
+ // Test each priority, expect case-insensitive compare.
+ EXPECT_TRUE(pc.SetPriority("high"));
+ EXPECT_EQ("name=value; priority=high", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_HIGH, pc.Priority());
+
+ EXPECT_TRUE(pc.SetPriority("mEDium"));
+ EXPECT_EQ("name=value; priority=mEDium", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_MEDIUM, pc.Priority());
+
+ EXPECT_TRUE(pc.SetPriority("LOW"));
+ EXPECT_EQ("name=value; priority=LOW", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_LOW, pc.Priority());
+
+ // Interpret invalid priority values as COOKIE_PRIORITY_DEFAULT.
+ EXPECT_TRUE(pc.SetPriority("Blah"));
+ EXPECT_EQ("name=value; priority=Blah", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+
+ EXPECT_TRUE(pc.SetPriority("lowerest"));
+ EXPECT_EQ("name=value; priority=lowerest", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+
+ EXPECT_TRUE(pc.SetPriority(""));
+ EXPECT_EQ("name=value", pc.ToCookieLine());
+ EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
+}
+
+} // namespace net
diff --git a/chromium/net/data/cache_tests/bad_entry/contents.txt b/chromium/net/data/cache_tests/bad_entry/contents.txt
new file mode 100644
index 00000000000..cffac69b978
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/contents.txt
@@ -0,0 +1,66 @@
+Index header:
+num_entries: 2
+num_bytes: 27
+this_id: 1
+table_len: 64k
+
+head: 0x90000001
+tail: 0x90000000
+
+Address: 0xa0010002
+Address: 0xa0010003
+
+-------------------------------
+
+entry:
+Address: 0xa0010002
+hash: 0x687d1422
+next: 0
+rankings_node: 0x90000000
+key_len: 13
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "the first key"
+
+rankings:
+Address: 0x90000000
+next: 0x90000000
+prev: 0x90000001
+contents: 0xa0010002
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010003
+hash: 0x63909ecb
+next: 0
+rankings_node: 0x00000000 <---- Wrong
+key_len: 14
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "some other key"
+
+rankings:
+Address: 0x90000001
+next: 0x90000000
+prev: 0x90000001
+contents: 0xa0010003
+dirty: 0
+pointer: 0
+
+================================
+
+Generated with:
+
+disk_cache::Entry *entry;
+ASSERT_TRUE(cache_->CreateEntry("the first key", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("some other key", &entry)); <---- Edit value*
+entry->Close();
+
+* Edit the value with the debugger before it is saved to disk. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/bad_entry/data_0 b/chromium/net/data/cache_tests/bad_entry/data_0
new file mode 100644
index 00000000000..f7cb1292d78
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_entry/data_1 b/chromium/net/data/cache_tests/bad_entry/data_1
new file mode 100644
index 00000000000..ccf28422b71
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_entry/data_2 b/chromium/net/data/cache_tests/bad_entry/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_entry/data_3 b/chromium/net/data/cache_tests/bad_entry/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_entry/index b/chromium/net/data/cache_tests/bad_entry/index
new file mode 100644
index 00000000000..609cb00b996
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_entry/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings/contents.txt b/chromium/net/data/cache_tests/bad_rankings/contents.txt
new file mode 100644
index 00000000000..a949728dc22
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/contents.txt
@@ -0,0 +1,66 @@
+Index header:
+num_entries: 2
+num_bytes: 27
+this_id: 1
+table_len: 64k
+
+head: 0x90000001
+tail: 0x90000000
+
+Address: 0xa0010002
+Address: 0xa0010003
+
+-------------------------------
+
+entry:
+Address: 0xa0010002
+hash: 0x687d1422
+next: 0
+rankings_node: 0x90000000
+key_len: 13
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "the first key"
+
+rankings:
+Address: 0x90000000
+next: 0x90000000
+prev: 0 <------ wrong
+contents: 0xa0010002
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010003
+hash: 0x63909ecb
+next: 0
+rankings_node: 0x90000001
+key_len: 14
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "some other key"
+
+rankings:
+Address: 0x90000001
+next: 0x90000000
+prev: 0x90000001
+contents: 0xa0010003
+dirty: 0
+pointer: 0
+
+================================
+
+Generated with:
+
+disk_cache::Entry *entry;
+ASSERT_TRUE(cache_->CreateEntry("the first key", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("some other key", &entry)); <---- Edit value*
+entry->Close();
+
+* Edit the value with the debugger before it is saved to disk. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/bad_rankings/data_0 b/chromium/net/data/cache_tests/bad_rankings/data_0
new file mode 100644
index 00000000000..40f60768398
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings/data_1 b/chromium/net/data/cache_tests/bad_rankings/data_1
new file mode 100644
index 00000000000..c38c4120b74
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings/data_2 b/chromium/net/data/cache_tests/bad_rankings/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings/data_3 b/chromium/net/data/cache_tests/bad_rankings/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings/index b/chromium/net/data/cache_tests/bad_rankings/index
new file mode 100644
index 00000000000..609cb00b996
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings2/contents.txt b/chromium/net/data/cache_tests/bad_rankings2/contents.txt
new file mode 100644
index 00000000000..79cc3e925d8
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/contents.txt
@@ -0,0 +1,66 @@
+Index header:
+num_entries: 2
+num_bytes: 27
+this_id: 2
+table_len: 128k
+
+head: 0x90000001
+tail: 0x90000000
+
+Address: 0xa0010002
+Address: 0xa0010003
+
+-------------------------------
+
+entry:
+Address: 0xa0010002
+hash: 0x687d1422
+next: 0
+rankings_node: 0x90000000
+key_len: 13
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "the first key"
+
+rankings:
+Address: 0x90000000
+next: 0x90000000
+prev: 0 <------ wrong
+contents: 0xa0010002
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010003
+hash: 0x63909ecb
+next: 0
+rankings_node: 0x90000001
+key_len: 14
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "some other key"
+
+rankings:
+Address: 0x90000001
+next: 0x90000000
+prev: 0x90000001
+contents: 0xa0010003
+dirty: 0
+pointer: 0
+
+================================
+
+Generated with:
+
+disk_cache::Entry *entry;
+ASSERT_TRUE(cache_->CreateEntry("the first key", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("some other key", &entry)); <---- Edit value*
+entry->Close();
+
+* Edit the value with the debugger before it is saved to disk. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/bad_rankings2/data_0 b/chromium/net/data/cache_tests/bad_rankings2/data_0
new file mode 100644
index 00000000000..5b322157a93
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings2/data_1 b/chromium/net/data/cache_tests/bad_rankings2/data_1
new file mode 100644
index 00000000000..7241f132351
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings2/data_2 b/chromium/net/data/cache_tests/bad_rankings2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings2/data_3 b/chromium/net/data/cache_tests/bad_rankings2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings2/index b/chromium/net/data/cache_tests/bad_rankings2/index
new file mode 100644
index 00000000000..602dfbadb76
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings3/contents.txt b/chromium/net/data/cache_tests/bad_rankings3/contents.txt
new file mode 100644
index 00000000000..d85287b555d
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/contents.txt
@@ -0,0 +1,92 @@
+Index file:
+version: 2.1
+entries: 3
+current id: 1
+last crash: 1
+head 0: 0x90000002
+tail 0: 0x90000002
+size 0: 0x1
+head 1: 0x90000001 <----
+tail 1: 0x90000000
+size 1: 0x2
+transaction: 0x0
+-------------------------
+
+Entry at 0xa0010002
+hash: 0x687d1422
+next entry: 0x0
+rankings: 0x90000000
+key length: 13
+key: "the first key"
+key addr: 0x0
+reuse count: 1
+----------
+
+Rankings at 0x90000000
+next: 0x90000000
+prev: 0x90000001
+entry: 0xa0010002
+dirty: 0
+----------
+
+Entry at 0xa0010003
+hash: 0x4a70620e
+next entry: 0x0
+rankings: 0x90000001
+key length: 14
+key: "the second key"
+key addr: 0x0
+reuse count: 0 <---- list 0
+----------
+
+Rankings at 0x90000001
+next: 0x90000000
+prev: 0x90000001 <----- head
+entry: 0xa0010003
+dirty: 1 <----- This was actually inserted on list 1
+----------
+
+Entry at 0xa0010004
+hash: 0x63909ecb
+next entry: 0x0
+rankings: 0x90000002
+key length: 14
+key: "some other key"
+key addr: 0x0
+reuse count: 0
+----------
+
+Rankings at 0x90000002
+next: 0x90000002
+prev: 0x90000002
+entry: 0xa0010004
+dirty: 0
+----------
+
+================================
+
+Generated with:
+
+ SetNewEviction();
+ SetMaxSize(20 * 1024 * 1024);
+ InitCache();
+ const char* kName1 = "the first key";
+ const char* kName2 = "the second key";
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(kName1, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(kName2, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("some other key", &entry));
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(kName1, &entry));
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(kName2, &entry));
+
+Set a breakpoint on that last line, and when the entry is moved from one list to another, crash the process:
+
+ rankings_->Remove(entry->rankings(), Rankings::NO_USE);
+ rankings_->Insert(entry->rankings(), false, Rankings::LOW_USE);
+ entry->entry()->Store(); <---- crash here
diff --git a/chromium/net/data/cache_tests/bad_rankings3/data_0 b/chromium/net/data/cache_tests/bad_rankings3/data_0
new file mode 100644
index 00000000000..ce35b75d6af
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings3/data_1 b/chromium/net/data/cache_tests/bad_rankings3/data_1
new file mode 100644
index 00000000000..5585567f57c
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings3/data_2 b/chromium/net/data/cache_tests/bad_rankings3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings3/data_3 b/chromium/net/data/cache_tests/bad_rankings3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/bad_rankings3/index b/chromium/net/data/cache_tests/bad_rankings3/index
new file mode 100644
index 00000000000..8bb75853563
--- /dev/null
+++ b/chromium/net/data/cache_tests/bad_rankings3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry3/contents.txt b/chromium/net/data/cache_tests/dirty_entry3/contents.txt
new file mode 100644
index 00000000000..b2aa37aabc5
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/contents.txt
@@ -0,0 +1,183 @@
+Index file:
+magic: c103cac3
+version: 2.0
+entries: 3 <---- there is an extra entry on the lru.
+total bytes: 40
+last file number: 0
+current id: 4
+table length: 65536
+last crash: 0
+experiment: 0
+head 0: 0x90000002
+tail 0: 0x90000000
+size 0: 0x0
+head 1: 0x0
+tail 1: 0x0
+size 1: 0x0
+head 2: 0x0
+tail 2: 0x0
+size 2: 0x0
+head 3: 0x0
+tail 3: 0x0
+size 3: 0x0
+head 4: 0x0
+tail 4: 0x0
+size 4: 0x0
+transaction: 0x0
+operation: 0
+operation list: 0
+-------------------------
+
+Block file: data_0
+magic: c104cac3
+version: 2.0
+file id: 0
+next file id: 0
+entry size: 36
+current entries: 3
+max entries: 1024
+updating: 0
+empty sz 1: 1
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 255
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_1
+magic: c104cac3
+version: 2.0
+file id: 1
+next file id: 0
+entry size: 256
+current entries: 4
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 1
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_2
+magic: c104cac3
+version: 2.0
+file id: 2
+next file id: 0
+entry size: 1024
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_3
+magic: c104cac3
+version: 2.0
+file id: 3
+next file id: 0
+entry size: 4096
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Entry at 0xa0010003
+hash: 0xb16af282
+next entry: 0xa0010004
+rankings: 0x90000001
+key length: 14
+key: "The Second key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000001
+next: 0x90000000
+prev: 0x90000002
+entry: 0xa0010003
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010004
+hash: 0xc24ac438
+next entry: 0x0
+rankings: 0x90000002
+key length: 13
+key: "The first key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000002
+next: 0x90000001
+prev: 0x90000002
+entry: 0xa0010004
+dirty: 0
+pointer: 0x0
+
+================================
+
+Generated with: (see steps on the bug 69135)
+
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ InitCache();
+
+ std::string key1("The first key");
+ std::string key2("The Second key");
+ disk_cache::Entry* entry;
+
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry)); <--- 1st crash.
+
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry)); <--- 2nd crash.
+
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ entry->Close(); \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/dirty_entry3/data_0 b/chromium/net/data/cache_tests/dirty_entry3/data_0
new file mode 100644
index 00000000000..77dd0ae3242
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry3/data_1 b/chromium/net/data/cache_tests/dirty_entry3/data_1
new file mode 100644
index 00000000000..baeff3cc964
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry3/data_2 b/chromium/net/data/cache_tests/dirty_entry3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry3/data_3 b/chromium/net/data/cache_tests/dirty_entry3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry3/index b/chromium/net/data/cache_tests/dirty_entry3/index
new file mode 100644
index 00000000000..d387abe0b84
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry4/contents.txt b/chromium/net/data/cache_tests/dirty_entry4/contents.txt
new file mode 100644
index 00000000000..a1c8f0de92e
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/contents.txt
@@ -0,0 +1,183 @@
+Index file:
+magic: c103cac3
+version: 2.1
+entries: 3
+total bytes: 40
+last file number: 0
+current id: 4
+table length: 65536
+last crash: 0
+experiment: 0
+head 0: 0x90000002
+tail 0: 0x90000001
+size 0: 0x2
+head 1: 0x0
+tail 1: 0x0
+size 1: 0x0
+head 2: 0x0
+tail 2: 0x0
+size 2: 0x0
+head 3: 0x0
+tail 3: 0x0
+size 3: 0x0
+head 4: 0x90000000
+tail 4: 0x90000000
+size 4: 0x1
+transaction: 0x0
+operation: 0
+operation list: 0
+-------------------------
+
+Block file: data_0
+magic: c104cac3
+version: 2.0
+file id: 0
+next file id: 0
+entry size: 36
+current entries: 3
+max entries: 1024
+updating: 0
+empty sz 1: 1
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 255
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_1
+magic: c104cac3
+version: 2.0
+file id: 1
+next file id: 0
+entry size: 256
+current entries: 4
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 1
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_2
+magic: c104cac3
+version: 2.0
+file id: 2
+next file id: 0
+entry size: 1024
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_3
+magic: c104cac3
+version: 2.0
+file id: 3
+next file id: 0
+entry size: 4096
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Entry at 0xa0010003
+hash: 0xb16af282
+next entry: 0xa0010004
+rankings: 0x90000001
+key length: 14
+key: "The Second key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000001
+next: 0x90000001
+prev: 0x90000002
+entry: 0xa0010003
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010004
+hash: 0xc24ac438
+next entry: 0x0
+rankings: 0x90000002
+key length: 13
+key: "The first key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000002
+next: 0x90000001
+prev: 0x90000002
+entry: 0xa0010004
+dirty: 0
+pointer: 0x0
+
+================================
+
+Generated with: (see steps on the bug 69135)
+
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ SetNewEviction();
+ InitCache();
+
+ std::string key1("The first key");
+ std::string key2("The Second key");
+ disk_cache::Entry* entry;
+
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
+ DoomAllEntries(); <--- First crash. Fix key2 dirty flag.
+
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry)); <--- Second crash
+
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ entry->Close(); \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/dirty_entry4/data_0 b/chromium/net/data/cache_tests/dirty_entry4/data_0
new file mode 100644
index 00000000000..702f5c7d383
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry4/data_1 b/chromium/net/data/cache_tests/dirty_entry4/data_1
new file mode 100644
index 00000000000..35dacdfbc09
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry4/data_2 b/chromium/net/data/cache_tests/dirty_entry4/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry4/data_3 b/chromium/net/data/cache_tests/dirty_entry4/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry4/index b/chromium/net/data/cache_tests/dirty_entry4/index
new file mode 100644
index 00000000000..e3e9f57f38d
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry4/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry5/contents.txt b/chromium/net/data/cache_tests/dirty_entry5/contents.txt
new file mode 100644
index 00000000000..afa5c031ba6
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/contents.txt
@@ -0,0 +1,166 @@
+Index file:
+magic: c103cac3
+version: 2.0
+entries: 2 <---- there is an extra entry on the lru.
+total bytes: 47
+last file number: 0
+current id: 3
+table length: 65536
+last crash: 1
+experiment: 0
+head 0: 0x90000000
+tail 0: 0x90000001
+size 0: 0x0
+head 1: 0x0
+tail 1: 0x0
+size 1: 0x0
+head 2: 0x0
+tail 2: 0x0
+size 2: 0x0
+head 3: 0x0
+tail 3: 0x0
+size 3: 0x0
+head 4: 0x0
+tail 4: 0x0
+size 4: 0x0
+transaction: 0x0
+operation: 0
+operation list: 0
+-------------------------
+
+Block file: data_0
+magic: c104cac3
+version: 2.0
+file id: 0
+next file id: 0
+entry size: 36
+current entries: 2
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 1
+empty sz 3: 0
+empty sz 4: 255
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_1
+magic: c104cac3
+version: 2.0
+file id: 1
+next file id: 0
+entry size: 256
+current entries: 4
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 1
+empty sz 3: 0
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_2
+magic: c104cac3
+version: 2.0
+file id: 2
+next file id: 0
+entry size: 1024
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_3
+magic: c104cac3
+version: 2.0
+file id: 3
+next file id: 0
+entry size: 4096
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Entry at 0xa0010002
+hash: 0xc24ac438
+next entry: 0x0
+rankings: 0x90000000
+key length: 13
+key: "The first key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 20
+data addr 0: 0xa0010005
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000000
+next: 0x90000001
+prev: 0x90000000
+entry: 0xa0010002
+dirty: 0
+pointer: 0x0
+
+================================
+
+Generated with: (see steps on the bug 69135)
+
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ InitCache();
+
+ std::string key1("The first key");
+ std::string key2("The Second key");
+ std::string key3("The third key");
+ disk_cache::Entry* entry;
+
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(key3, &entry));
+ entry->Close();
+
+ const int kSize = 20;
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry));
+ scoped_refptr<net::IOBuffer> buf = new net::IOBuffer(kSize);
+ memset(buf->data(), 0, kSize);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buf, kSize, false));
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry)); <--- 1st crash.
+ ASSERT_NE(net::OK, OpenEntry(key2, &entry)); <--- 2nd crash. *
+
+ ASSERT_EQ(net::OK, DoomEntry(key3));
+
+(*) and trick the code into deleting the dirty flag. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/dirty_entry5/data_0 b/chromium/net/data/cache_tests/dirty_entry5/data_0
new file mode 100644
index 00000000000..5231b26962f
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry5/data_1 b/chromium/net/data/cache_tests/dirty_entry5/data_1
new file mode 100644
index 00000000000..0cd565452b3
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry5/data_2 b/chromium/net/data/cache_tests/dirty_entry5/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry5/data_3 b/chromium/net/data/cache_tests/dirty_entry5/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/dirty_entry5/index b/chromium/net/data/cache_tests/dirty_entry5/index
new file mode 100644
index 00000000000..70110bcda73
--- /dev/null
+++ b/chromium/net/data/cache_tests/dirty_entry5/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty1/data_0 b/chromium/net/data/cache_tests/insert_empty1/data_0
new file mode 100644
index 00000000000..1f84482c905
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty1/data_1 b/chromium/net/data/cache_tests/insert_empty1/data_1
new file mode 100644
index 00000000000..dfa550de0f3
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty1/data_2 b/chromium/net/data/cache_tests/insert_empty1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty1/data_3 b/chromium/net/data/cache_tests/insert_empty1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty1/index b/chromium/net/data/cache_tests/insert_empty1/index
new file mode 100644
index 00000000000..82123b13a4b
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty2/data_0 b/chromium/net/data/cache_tests/insert_empty2/data_0
new file mode 100644
index 00000000000..c0926e55e65
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty2/data_1 b/chromium/net/data/cache_tests/insert_empty2/data_1
new file mode 100644
index 00000000000..55f236ca27d
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty2/data_2 b/chromium/net/data/cache_tests/insert_empty2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty2/data_3 b/chromium/net/data/cache_tests/insert_empty2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty2/index b/chromium/net/data/cache_tests/insert_empty2/index
new file mode 100644
index 00000000000..9d66715e61c
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty3/data_0 b/chromium/net/data/cache_tests/insert_empty3/data_0
new file mode 100644
index 00000000000..f5bd25920d3
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty3/data_1 b/chromium/net/data/cache_tests/insert_empty3/data_1
new file mode 100644
index 00000000000..59ebebf45c8
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty3/data_2 b/chromium/net/data/cache_tests/insert_empty3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty3/data_3 b/chromium/net/data/cache_tests/insert_empty3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_empty3/index b/chromium/net/data/cache_tests/insert_empty3/index
new file mode 100644
index 00000000000..7e48e9762dd
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_empty3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load1/data_0 b/chromium/net/data/cache_tests/insert_load1/data_0
new file mode 100644
index 00000000000..dbef64508e4
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load1/data_1 b/chromium/net/data/cache_tests/insert_load1/data_1
new file mode 100644
index 00000000000..204205a33af
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load1/data_2 b/chromium/net/data/cache_tests/insert_load1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load1/data_3 b/chromium/net/data/cache_tests/insert_load1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load1/index b/chromium/net/data/cache_tests/insert_load1/index
new file mode 100644
index 00000000000..e97b7edc3c7
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load2/data_0 b/chromium/net/data/cache_tests/insert_load2/data_0
new file mode 100644
index 00000000000..1c8bbe87eb2
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load2/data_1 b/chromium/net/data/cache_tests/insert_load2/data_1
new file mode 100644
index 00000000000..e5324e71dc8
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load2/data_2 b/chromium/net/data/cache_tests/insert_load2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load2/data_3 b/chromium/net/data/cache_tests/insert_load2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_load2/index b/chromium/net/data/cache_tests/insert_load2/index
new file mode 100644
index 00000000000..9e8f94480d4
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_load2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one1/data_0 b/chromium/net/data/cache_tests/insert_one1/data_0
new file mode 100644
index 00000000000..ef77b3c25ba
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one1/data_1 b/chromium/net/data/cache_tests/insert_one1/data_1
new file mode 100644
index 00000000000..b2b7c2fbb79
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one1/data_2 b/chromium/net/data/cache_tests/insert_one1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one1/data_3 b/chromium/net/data/cache_tests/insert_one1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one1/index b/chromium/net/data/cache_tests/insert_one1/index
new file mode 100644
index 00000000000..40c8999faf7
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one2/data_0 b/chromium/net/data/cache_tests/insert_one2/data_0
new file mode 100644
index 00000000000..151bd4e389d
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one2/data_1 b/chromium/net/data/cache_tests/insert_one2/data_1
new file mode 100644
index 00000000000..bc909065197
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one2/data_2 b/chromium/net/data/cache_tests/insert_one2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one2/data_3 b/chromium/net/data/cache_tests/insert_one2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one2/index b/chromium/net/data/cache_tests/insert_one2/index
new file mode 100644
index 00000000000..542a525802e
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one3/data_0 b/chromium/net/data/cache_tests/insert_one3/data_0
new file mode 100644
index 00000000000..515496d1c10
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one3/data_1 b/chromium/net/data/cache_tests/insert_one3/data_1
new file mode 100644
index 00000000000..27f2a7bd468
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one3/data_2 b/chromium/net/data/cache_tests/insert_one3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one3/data_3 b/chromium/net/data/cache_tests/insert_one3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/insert_one3/index b/chromium/net/data/cache_tests/insert_one3/index
new file mode 100644
index 00000000000..2c0258de207
--- /dev/null
+++ b/chromium/net/data/cache_tests/insert_one3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop/contents.txt b/chromium/net/data/cache_tests/list_loop/contents.txt
new file mode 100644
index 00000000000..fb58b14fe61
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/contents.txt
@@ -0,0 +1,228 @@
+Index header:
+num_entries: 8
+num_bytes: 52
+this_id: 1
+table_len: 64k
+
+head: 0x90000004
+tail: 0x90000000
+
+Address: 0xa0010007
+Address: 0xa0010003
+Address: 0xa001000b
+Address: 0xa001000a
+Address: 0xa0010009
+Address: 0xa0010006
+Address: 0xa0010005
+Address: 0xa0010002
+
+
+-------------------------------
+
+entry:
+Address: 0xa0010007
+hash: 0xcb30d119
+next: 0
+rankings_node: 0x90000004
+key_len: 5
+long_key: 0
+data_size[0]: 4
+data_addr[0]: 0xa0010008
+key: "fifth"
+
+rankings:
+Address: 0x90000004
+next: 0x90000001
+prev: 0x90000004
+contents: 0xa0010007
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010003
+hash: 0x090fbce3
+next: 0
+rankings_node: 0x90000001
+key_len: 6
+long_key: 0
+data_size[0]: 4
+data_addr[0]: 0xa0010004
+key: "second"
+
+rankings:
+Address: 0x90000001
+next: 0x90000007
+prev: 0x90000004
+contents: 0xa0010003
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa001000b
+hash: 0xad80b702
+next: 0
+rankings_node: 0x90000007
+key_len: 5
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "eight"
+
+rankings:
+Address: 0x90000007
+next: 0x90000006
+prev: 0x90000001
+contents: 0xa001000b
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa001000a
+hash: 0xfdae1d2a
+next: 0
+rankings_node: 0x90000006
+key_len: 7
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "seventh"
+
+rankings:
+Address: 0x90000006
+next: 0x90000005
+prev: 0x90000007
+contents: 0xa001000a
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010009
+hash: 0x2129e026
+next: 0
+rankings_node: 0x90000005
+key_len: 5
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "sixth"
+
+rankings:
+Address: 0x90000005
+next: 0x90000003
+prev: 0x90000006
+contents: 0xa0010009
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010006
+hash: 0x3d9011cc
+next: 0
+rankings_node: 0x90000003
+key_len: 6
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "fourth"
+
+rankings:
+Address: 0x90000003
+next: 0x90000002
+prev: 0x90000005
+contents: 0xa0010006
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010005
+hash: 0x8f04b77c
+next: 0
+rankings_node: 0x90000002
+key_len: 5
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "third"
+
+rankings:
+Address: 0x90000002
+next: 0x90000001 <--------- wrong
+prev: 0x90000003
+contents: 0xa0010005
+dirty: 0
+pointer: 0
+
+-------------------------------
+
+entry:
+Address: 0xa0010002
+hash: 0x0138974a
+next: 0
+rankings_node: 0x90000000
+key_len: 5
+long_key: 0
+data_size: 0's
+data_addr: 0's
+key: "first"
+
+rankings:
+Address: 0x90000000
+next: 0x90000000
+prev: 0x90000002
+contents: 0xa0010002
+dirty: 0
+pointer: 0
+
+==============================
+
+Generated with:
+
+disk_cache::Entry *entry;
+ASSERT_TRUE(cache_->CreateEntry("first", &entry));
+entry->Close();
+
+char buffer[] = "abcd";
+ASSERT_TRUE(cache_->CreateEntry("second", &entry));
+entry->WriteData(0, 0, buffer, 4, NULL, false);
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("third", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("fourth", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("fifth", &entry));
+entry->WriteData(0, 0, buffer, 4, NULL, false);
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("sixth", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("seventh", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->CreateEntry("eight", &entry));
+entry->Close();
+
+ASSERT_TRUE(cache_->OpenEntry("second", &entry));
+entry->ReadData(0, 0, buffer, 4, NULL); <--- fix the values*
+entry->Close();
+
+ASSERT_TRUE(cache_->OpenEntry("fifth", &entry));
+entry->ReadData(0, 0, buffer, 4, NULL);
+entry->Close();
+
+* break on Rankings::Remove() and edit the values before they are saved. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/list_loop/data_0 b/chromium/net/data/cache_tests/list_loop/data_0
new file mode 100644
index 00000000000..6bf05330264
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop/data_1 b/chromium/net/data/cache_tests/list_loop/data_1
new file mode 100644
index 00000000000..32244ab5852
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop/data_2 b/chromium/net/data/cache_tests/list_loop/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop/data_3 b/chromium/net/data/cache_tests/list_loop/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop/index b/chromium/net/data/cache_tests/list_loop/index
new file mode 100644
index 00000000000..32acaf83084
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop2/contents.txt b/chromium/net/data/cache_tests/list_loop2/contents.txt
new file mode 100644
index 00000000000..da7d1dcdffd
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/contents.txt
@@ -0,0 +1,145 @@
+Index file:
+magic: c103cac3
+version: 2.0
+entries: 2
+total bytes: 27
+last file number: 0
+current id: 5
+table length: 65536
+last crash: 1
+experiment: 0
+head 0: 0x90000002
+tail 0: 0x90000001
+size 0: 0x2
+head 1: 0x0
+tail 1: 0x0
+size 1: 0x0
+head 2: 0x0
+tail 2: 0x0
+size 2: 0x0
+head 3: 0x0
+tail 3: 0x0
+size 3: 0x0
+head 4: 0x0
+tail 4: 0x0
+size 4: 0x0
+transaction: 0x0
+operation: 0
+operation list: 0
+-------------------------
+
+Block file: data_0
+magic: c104cac3
+version: 2.0
+file id: 0
+next file id: 0
+entry size: 36
+current entries: 2
+max entries: 1024
+updating: 0
+empty sz 1: 1
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 255
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_1
+magic: c104cac3
+version: 2.0
+file id: 1
+next file id: 0
+entry size: 256
+current entries: 3
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 1
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_2
+magic: c104cac3
+version: 2.0
+file id: 2
+next file id: 0
+entry size: 1024
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_3
+magic: c104cac3
+version: 2.0
+file id: 3
+next file id: 0
+entry size: 4096
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Entry at 0xa0010003
+hash: 0xb16af282
+next entry: 0xa0010003
+rankings: 0x90000001
+key length: 14
+key: "The Second key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000001
+next: 0x90000001
+prev: 0x90000002
+entry: 0xa0010003
+dirty: 0
+pointer: 0x0
+
+================================
+
+Generated with: (see steps on the bug 69135)
+
+ ASSERT_TRUE(CopyTestCache("dirty_entry4"));
+ SetNewEviction();
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ TrimDeletedListForTest(false); <-- Without the code that fixes the loop. \ No newline at end of file
diff --git a/chromium/net/data/cache_tests/list_loop2/data_0 b/chromium/net/data/cache_tests/list_loop2/data_0
new file mode 100644
index 00000000000..90c58da8cf9
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop2/data_1 b/chromium/net/data/cache_tests/list_loop2/data_1
new file mode 100644
index 00000000000..5579786d56d
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop2/data_2 b/chromium/net/data/cache_tests/list_loop2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop2/data_3 b/chromium/net/data/cache_tests/list_loop2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop2/index b/chromium/net/data/cache_tests/list_loop2/index
new file mode 100644
index 00000000000..b5b1dd6349d
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop3/contents.txt b/chromium/net/data/cache_tests/list_loop3/contents.txt
new file mode 100644
index 00000000000..f359067a3b6
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/contents.txt
@@ -0,0 +1,265 @@
+Index file:
+magic: c103cac3
+version: 2.0
+entries: 5
+total bytes: 66
+last file number: 0
+current id: 1
+table length: 65536
+last crash: 1
+experiment: 0
+head 0: 0x90000004
+tail 0: 0x90000000
+size 0: 0x0
+head 1: 0x0
+tail 1: 0x0
+size 1: 0x0
+head 2: 0x0
+tail 2: 0x0
+size 2: 0x0
+head 3: 0x0
+tail 3: 0x0
+size 3: 0x0
+head 4: 0x0
+tail 4: 0x0
+size 4: 0x0
+transaction: 0x0
+operation: 0
+operation list: 0
+-------------------------
+
+Block file: data_0
+magic: c104cac3
+version: 2.0
+file id: 0
+next file id: 0
+entry size: 36
+current entries: 5
+max entries: 1024
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 1
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_1
+magic: c104cac3
+version: 2.0
+file id: 1
+next file id: 0
+entry size: 256
+current entries: 6
+max entries: 1024
+updating: 0
+empty sz 1: 1
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 254
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_2
+magic: c104cac3
+version: 2.0
+file id: 2
+next file id: 0
+entry size: 1024
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Block file: data_3
+magic: c104cac3
+version: 2.0
+file id: 3
+next file id: 0
+entry size: 4096
+current entries: 0
+max entries: 0
+updating: 0
+empty sz 1: 0
+empty sz 2: 0
+empty sz 3: 0
+empty sz 4: 0
+user 0: 0x0
+user 1: 0x0
+user 2: 0x0
+user 3: 0x0
+-------------------------
+
+Entry at 0xa0010002
+hash: 0xc24ac438
+next entry: 0xa0010003
+rankings: 0x90000000
+key length: 13
+key: "The first key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000000
+next: 0x90000000
+prev: 0x90000001
+entry: 0xa0010002
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010003
+hash: 0xb16af282
+next entry: 0xa0010004
+rankings: 0x90000001
+key length: 14
+key: "The Second key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000001
+next: 0x90000000
+prev: 0x90000002
+entry: 0xa0010003
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010004
+hash: 0x90efd732
+next entry: 0xa0010005
+rankings: 0x90000002
+key length: 13
+key: "The third key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000002
+next: 0x90000001
+prev: 0x90000003
+entry: 0xa0010004
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010005
+hash: 0x147cc398
+next entry: 0xa0010006
+rankings: 0x90000003
+key length: 13
+key: "The Fouth key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000003
+next: 0x90000002
+prev: 0x90000004
+entry: 0xa0010005
+dirty: 0
+pointer: 0x0
+----------
+
+Entry at 0xa0010006
+hash: 0x9d910c3c
+next entry: 0xa0010002
+rankings: 0x90000004
+key length: 13
+key: "The fifth key"
+key addr: 0x0
+reuse count: 0
+refetch count: 0
+state: 0
+data size 0: 0
+data addr 0: 0x0
+data size 1: 0
+data addr 1: 0x0
+data size 2: 0
+data addr 2: 0x0
+data size 3: 0
+data addr 3: 0x0
+----------
+
+Rankings at 0x90000004
+next: 0x90000003
+prev: 0x90000004
+entry: 0xa0010006
+dirty: 1
+pointer: 0x0
+
+================================
+
+Generated with:
+
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ InitCache();
+
+ disk_cache::Entry* entry;
+
+ ASSERT_EQ(net::OK, CreateEntry("The first key", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("The Second key", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("The third key", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("The Fouth key", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("The fifth key", &entry)); <---- Loop it to the first one.
+ entry->Close();
diff --git a/chromium/net/data/cache_tests/list_loop3/data_0 b/chromium/net/data/cache_tests/list_loop3/data_0
new file mode 100644
index 00000000000..d6056bf1b5c
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop3/data_1 b/chromium/net/data/cache_tests/list_loop3/data_1
new file mode 100644
index 00000000000..d41e0fac4fe
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop3/data_2 b/chromium/net/data/cache_tests/list_loop3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop3/data_3 b/chromium/net/data/cache_tests/list_loop3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/list_loop3/index b/chromium/net/data/cache_tests/list_loop3/index
new file mode 100644
index 00000000000..5dcb65417ec
--- /dev/null
+++ b/chromium/net/data/cache_tests/list_loop3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head1/data_0 b/chromium/net/data/cache_tests/remove_head1/data_0
new file mode 100644
index 00000000000..238580b9598
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head1/data_1 b/chromium/net/data/cache_tests/remove_head1/data_1
new file mode 100644
index 00000000000..38f9960e883
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head1/data_2 b/chromium/net/data/cache_tests/remove_head1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head1/data_3 b/chromium/net/data/cache_tests/remove_head1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head1/index b/chromium/net/data/cache_tests/remove_head1/index
new file mode 100644
index 00000000000..595aad3f359
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head2/data_0 b/chromium/net/data/cache_tests/remove_head2/data_0
new file mode 100644
index 00000000000..29eab4a2678
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head2/data_1 b/chromium/net/data/cache_tests/remove_head2/data_1
new file mode 100644
index 00000000000..d5749efdbce
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head2/data_2 b/chromium/net/data/cache_tests/remove_head2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head2/data_3 b/chromium/net/data/cache_tests/remove_head2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head2/index b/chromium/net/data/cache_tests/remove_head2/index
new file mode 100644
index 00000000000..12244a5dcad
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head3/data_0 b/chromium/net/data/cache_tests/remove_head3/data_0
new file mode 100644
index 00000000000..44fca6fa986
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head3/data_1 b/chromium/net/data/cache_tests/remove_head3/data_1
new file mode 100644
index 00000000000..40c5f1d0d40
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head3/data_2 b/chromium/net/data/cache_tests/remove_head3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head3/data_3 b/chromium/net/data/cache_tests/remove_head3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head3/index b/chromium/net/data/cache_tests/remove_head3/index
new file mode 100644
index 00000000000..dc2d12d6069
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head4/data_0 b/chromium/net/data/cache_tests/remove_head4/data_0
new file mode 100644
index 00000000000..fad4d685481
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head4/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head4/data_1 b/chromium/net/data/cache_tests/remove_head4/data_1
new file mode 100644
index 00000000000..7c6780a99f7
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head4/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head4/data_2 b/chromium/net/data/cache_tests/remove_head4/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head4/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head4/data_3 b/chromium/net/data/cache_tests/remove_head4/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head4/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_head4/index b/chromium/net/data/cache_tests/remove_head4/index
new file mode 100644
index 00000000000..65f598b82aa
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_head4/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load1/data_0 b/chromium/net/data/cache_tests/remove_load1/data_0
new file mode 100644
index 00000000000..1fddb010736
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load1/data_1 b/chromium/net/data/cache_tests/remove_load1/data_1
new file mode 100644
index 00000000000..a12a44f6a30
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load1/data_2 b/chromium/net/data/cache_tests/remove_load1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load1/data_3 b/chromium/net/data/cache_tests/remove_load1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load1/index b/chromium/net/data/cache_tests/remove_load1/index
new file mode 100644
index 00000000000..52189b29ea8
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load2/data_0 b/chromium/net/data/cache_tests/remove_load2/data_0
new file mode 100644
index 00000000000..c35dca76e0c
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load2/data_1 b/chromium/net/data/cache_tests/remove_load2/data_1
new file mode 100644
index 00000000000..31f5de89faf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load2/data_2 b/chromium/net/data/cache_tests/remove_load2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load2/data_3 b/chromium/net/data/cache_tests/remove_load2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load2/index b/chromium/net/data/cache_tests/remove_load2/index
new file mode 100644
index 00000000000..f3152eb85f1
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load3/data_0 b/chromium/net/data/cache_tests/remove_load3/data_0
new file mode 100644
index 00000000000..fb7e63428f7
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load3/data_1 b/chromium/net/data/cache_tests/remove_load3/data_1
new file mode 100644
index 00000000000..b132cd62ca9
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load3/data_2 b/chromium/net/data/cache_tests/remove_load3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load3/data_3 b/chromium/net/data/cache_tests/remove_load3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_load3/index b/chromium/net/data/cache_tests/remove_load3/index
new file mode 100644
index 00000000000..05dd18afbc3
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_load3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one1/data_0 b/chromium/net/data/cache_tests/remove_one1/data_0
new file mode 100644
index 00000000000..44dd98e6069
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one1/data_1 b/chromium/net/data/cache_tests/remove_one1/data_1
new file mode 100644
index 00000000000..6076ea6c59f
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one1/data_2 b/chromium/net/data/cache_tests/remove_one1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one1/data_3 b/chromium/net/data/cache_tests/remove_one1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one1/index b/chromium/net/data/cache_tests/remove_one1/index
new file mode 100644
index 00000000000..6e6d10d78d0
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one2/data_0 b/chromium/net/data/cache_tests/remove_one2/data_0
new file mode 100644
index 00000000000..dd64f1e1a48
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one2/data_1 b/chromium/net/data/cache_tests/remove_one2/data_1
new file mode 100644
index 00000000000..671cfaa4860
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one2/data_2 b/chromium/net/data/cache_tests/remove_one2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one2/data_3 b/chromium/net/data/cache_tests/remove_one2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one2/index b/chromium/net/data/cache_tests/remove_one2/index
new file mode 100644
index 00000000000..cd1146cc1a5
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one3/data_0 b/chromium/net/data/cache_tests/remove_one3/data_0
new file mode 100644
index 00000000000..b9efe8a3296
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one3/data_1 b/chromium/net/data/cache_tests/remove_one3/data_1
new file mode 100644
index 00000000000..5802509f531
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one3/data_2 b/chromium/net/data/cache_tests/remove_one3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one3/data_3 b/chromium/net/data/cache_tests/remove_one3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one3/index b/chromium/net/data/cache_tests/remove_one3/index
new file mode 100644
index 00000000000..48a195038ac
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one4/data_0 b/chromium/net/data/cache_tests/remove_one4/data_0
new file mode 100644
index 00000000000..e1f538227ce
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one4/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one4/data_1 b/chromium/net/data/cache_tests/remove_one4/data_1
new file mode 100644
index 00000000000..4985ffbe101
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one4/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one4/data_2 b/chromium/net/data/cache_tests/remove_one4/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one4/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one4/data_3 b/chromium/net/data/cache_tests/remove_one4/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one4/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_one4/index b/chromium/net/data/cache_tests/remove_one4/index
new file mode 100644
index 00000000000..c0dcd59c230
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_one4/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail1/data_0 b/chromium/net/data/cache_tests/remove_tail1/data_0
new file mode 100644
index 00000000000..490168d2887
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail1/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail1/data_1 b/chromium/net/data/cache_tests/remove_tail1/data_1
new file mode 100644
index 00000000000..b8b8418356b
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail1/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail1/data_2 b/chromium/net/data/cache_tests/remove_tail1/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail1/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail1/data_3 b/chromium/net/data/cache_tests/remove_tail1/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail1/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail1/index b/chromium/net/data/cache_tests/remove_tail1/index
new file mode 100644
index 00000000000..0fe2a8d46f2
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail1/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail2/data_0 b/chromium/net/data/cache_tests/remove_tail2/data_0
new file mode 100644
index 00000000000..ca0dbd129e8
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail2/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail2/data_1 b/chromium/net/data/cache_tests/remove_tail2/data_1
new file mode 100644
index 00000000000..cb2b2e6915b
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail2/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail2/data_2 b/chromium/net/data/cache_tests/remove_tail2/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail2/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail2/data_3 b/chromium/net/data/cache_tests/remove_tail2/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail2/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail2/index b/chromium/net/data/cache_tests/remove_tail2/index
new file mode 100644
index 00000000000..e1851289c49
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail2/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail3/data_0 b/chromium/net/data/cache_tests/remove_tail3/data_0
new file mode 100644
index 00000000000..80efaab44f3
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail3/data_0
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail3/data_1 b/chromium/net/data/cache_tests/remove_tail3/data_1
new file mode 100644
index 00000000000..ad9086c459a
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail3/data_1
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail3/data_2 b/chromium/net/data/cache_tests/remove_tail3/data_2
new file mode 100644
index 00000000000..c7e2eb9adcf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail3/data_2
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail3/data_3 b/chromium/net/data/cache_tests/remove_tail3/data_3
new file mode 100644
index 00000000000..5eec97358cf
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail3/data_3
Binary files differ
diff --git a/chromium/net/data/cache_tests/remove_tail3/index b/chromium/net/data/cache_tests/remove_tail3/index
new file mode 100644
index 00000000000..7c534b02e21
--- /dev/null
+++ b/chromium/net/data/cache_tests/remove_tail3/index
Binary files differ
diff --git a/chromium/net/data/cache_tests/wrong_version/index b/chromium/net/data/cache_tests/wrong_version/index
new file mode 100644
index 00000000000..64c1c3cd107
--- /dev/null
+++ b/chromium/net/data/cache_tests/wrong_version/index
Binary files differ
diff --git a/chromium/net/data/filter_unittests/google.txt b/chromium/net/data/filter_unittests/google.txt
new file mode 100644
index 00000000000..b06e0fecbc0
--- /dev/null
+++ b/chromium/net/data/filter_unittests/google.txt
@@ -0,0 +1,19 @@
+Company Overview
+
+Google's mission is to organize the world's information and make it universally accessible and useful.
+
+As a first step to fulfilling that mission, Google's founders Larry Page and Sergey Brin developed a new approach to online search that took root in a Stanford University dorm room and quickly spread to information seekers around the globe. Google is now widely recognized as the world's largest search engine -- an easy-to-use free service that usually returns relevant results in a fraction of a second.
+
+When you visit www.google.com or one of the dozens of other Google domains, you'll be able to find information in many different languages; check stock quotes, maps, and news headlines; lookup phonebook listings for every city in the United States; search billions of images and peruse the world's largest archive of Usenet messages -- more than 1 billion posts dating back to 1981.
+
+We also provide ways to access all this information without making a special trip to the Google homepage. The Google Toolbar enables you to conduct a Google search from anywhere on the web. And for those times when you're away from your PC altogether, Google can be used from a number of wireless platforms including WAP and i-mode phones.
+
+Google's utility and ease of use have made it one of the world's best known brands almost entirely through word of mouth from satisfied users. As a business, Google generates revenue by providing advertisers with the opportunity to deliver measurable, cost-effective online advertising that is relevant to the information displayed on any given page. This makes the advertising useful to you as well as to the advertiser placing it. We believe you should know when someone has paid to put a message in front of you, so we always distinguish ads from the search results or other content on a page. We don't sell placement in the search results themselves, or allow people to pay for a higher ranking there.
+
+Thousands of advertisers use our Google AdWords program to promote their products and services on the web with targeted advertising, and we believe AdWords is the largest program of its kind. In addition, thousands of web site managers take advantage of our Google AdSense program to deliver ads relevant to the content on their sites, improving their ability to generate revenue and enhancing the experience for their users.
+
+To learn more about Google, click on the link at the left for the area that most interests you. Or type what you want to find into our search box and hit enter. Once you do, you'll be on your way to understanding why others say, "Google is the closest thing the Web has to an ultimate answer machine."
+
+What's a Google?
+
+"Googol" is the mathematical term for a 1 followed by 100 zeros. The term was coined by Milton Sirotta, nephew of American mathematician Edward Kasner, and was popularized in the book, "Mathematics and the Imagination" by Kasner and James Newman. Google's play on the term reflects the company's mission to organize the immense amount of information available on the web.
diff --git a/chromium/net/data/ftp/dir-listing-ls-1 b/chromium/net/data/ftp/dir-listing-ls-1
new file mode 100644
index 00000000000..0c09e7ddc95
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-1
@@ -0,0 +1,6 @@
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 .
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 ..
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 .message
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README
+-rw-r--r-- 1 ftp ftp 560 Sep 28 2007 index.html
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-1-utf8 b/chromium/net/data/ftp/dir-listing-ls-1-utf8
new file mode 100644
index 00000000000..8f33030eac9
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-1-utf8
@@ -0,0 +1,6 @@
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 .
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 ..
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 .message
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 READMEï¼
+-rw-r--r-- 1 ftp ftp 560 Sep 28 2007 ã“ã‚“ã«ã¡ã¯.html
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-1-utf8.expected b/chromium/net/data/ftp/dir-listing-ls-1-utf8.expected
new file mode 100644
index 00000000000..17db110e44b
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-1-utf8.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+1994
+5
+15
+18
+11
+
+d
+..
+-1
+1994
+5
+15
+18
+11
+
+-
+.message
+528
+2007
+11
+1
+0
+0
+
+-
+READMEï¼
+528
+2007
+11
+1
+0
+0
+
+-
+ã“ã‚“ã«ã¡ã¯.html
+560
+2007
+9
+28
+0
+0
+
+d
+pub
+-1
+2008
+8
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-1.expected b/chromium/net/data/ftp/dir-listing-ls-1.expected
new file mode 100644
index 00000000000..7998960e214
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-1.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+1994
+5
+15
+18
+11
+
+d
+..
+-1
+1994
+5
+15
+18
+11
+
+-
+.message
+528
+2007
+11
+1
+0
+0
+
+-
+README
+528
+2007
+11
+1
+0
+0
+
+-
+index.html
+560
+2007
+9
+28
+0
+0
+
+d
+pub
+-1
+2008
+8
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-10 b/chromium/net/data/ftp/dir-listing-ls-10
new file mode 100644
index 00000000000..deff68fc292
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-10
@@ -0,0 +1,10 @@
+Gesamt 4352
+---------- 1 1 1 0 Okt 25 1999 .notar
+lrwxrwxrwx 1 1 1 7 Okt 23 2007 bin -> usr/bin
+d--x--x--x 1 2 2 512 Apr 23 2002 dev
+d--x--x--x 1 2 2 512 Apr 1 2004 etc
+drwx------ 1 7 root 1536 Aug 14 13:49 lost+found
+drwxr-xr-x 1 3 1 512 Mär 10 2003 private
+drwxrwsr-x 1 25 1260 1024 Aug 10 2006 pub
+-rw------- 1 1 1 2211496 Okt 23 2007 restoresymtable
+d--x--x--x 1 6 2 512 Apr 23 2002 usr
diff --git a/chromium/net/data/ftp/dir-listing-ls-10.expected b/chromium/net/data/ftp/dir-listing-ls-10.expected
new file mode 100644
index 00000000000..73f16bfb327
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-10.expected
@@ -0,0 +1,80 @@
+-
+.notar
+0
+1999
+10
+25
+0
+0
+
+l
+bin
+-1
+2007
+10
+23
+0
+0
+
+d
+dev
+-1
+2002
+4
+23
+0
+0
+
+d
+etc
+-1
+2004
+4
+1
+0
+0
+
+d
+lost+found
+-1
+1994
+8
+14
+13
+49
+
+d
+private
+-1
+2003
+3
+10
+0
+0
+
+d
+pub
+-1
+2006
+8
+10
+0
+0
+
+-
+restoresymtable
+2211496
+2007
+10
+23
+0
+0
+
+d
+usr
+-1
+2002
+4
+23
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-11 b/chromium/net/data/ftp/dir-listing-ls-11
new file mode 100644
index 00000000000..f81fda61f38
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-11
@@ -0,0 +1,8 @@
+total 14
+drwxr-xr-x 2 other 512 Feb 25 2009 beid
+lrwxrwxrwx 1 bin 9 May 1 2007 bin -> ./usr/bin
+d--x--x--x 2 sys 512 May 1 2007 dev
+d--x--x--x 5 sys 512 May 1 2007 etc
+drwxr-xr-x 2 sys 512 Mar 27 2009 pub
+drwxr-xr-x 3 other 512 Apr 11 2007 tigerd1
+d--x--x--x 6 sys 512 May 1 2007 usr
diff --git a/chromium/net/data/ftp/dir-listing-ls-11.expected b/chromium/net/data/ftp/dir-listing-ls-11.expected
new file mode 100644
index 00000000000..813bdba0d6e
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-11.expected
@@ -0,0 +1,62 @@
+d
+beid
+-1
+2009
+2
+25
+0
+0
+
+l
+bin
+-1
+2007
+5
+1
+0
+0
+
+d
+dev
+-1
+2007
+5
+1
+0
+0
+
+d
+etc
+-1
+2007
+5
+1
+0
+0
+
+d
+pub
+-1
+2009
+3
+27
+0
+0
+
+d
+tigerd1
+-1
+2007
+4
+11
+0
+0
+
+d
+usr
+-1
+2007
+5
+1
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-12 b/chromium/net/data/ftp/dir-listing-ls-12
new file mode 100644
index 00000000000..7eee0bd1f24
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-12
@@ -0,0 +1,8 @@
+total 14
+lrwxrwxrwx 1 other 7 Sep 1 2005 bin -> usr/bin
+dr-xr-xr-x 2 other 512 Aug 9 2004 dev
+dr-xr-xr-x 2 other 512 Sep 28 2006 etc
+drwxr-xr-x 2 other 512 Sep 28 2006 msgs
+drwxr-xr-x 53 other 1024 Jun 30 09:52 pub
+dr-xr-xr-x 5 other 512 Aug 9 2004 usr
+drwxr-xr-x 2 other 512 Aug 9 2004 var
diff --git a/chromium/net/data/ftp/dir-listing-ls-12.expected b/chromium/net/data/ftp/dir-listing-ls-12.expected
new file mode 100644
index 00000000000..c68e415ee9a
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-12.expected
@@ -0,0 +1,62 @@
+l
+bin
+-1
+2005
+9
+1
+0
+0
+
+d
+dev
+-1
+2004
+8
+9
+0
+0
+
+d
+etc
+-1
+2006
+9
+28
+0
+0
+
+d
+msgs
+-1
+2006
+9
+28
+0
+0
+
+d
+pub
+-1
+1994
+6
+30
+9
+52
+
+d
+usr
+-1
+2004
+8
+9
+0
+0
+
+d
+var
+-1
+2004
+8
+9
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-13 b/chromium/net/data/ftp/dir-listing-ls-13
new file mode 100644
index 00000000000..b2b7bff5a24
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-13
@@ -0,0 +1,3 @@
+-r--r--r-- 1 ftp -A--- 93064 Dec 28 2007 test.jpg
+dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels
+-r--r--r-- 1 ftp -A--- 13274 Mar 1 2006 UpTime.exe
diff --git a/chromium/net/data/ftp/dir-listing-ls-13.expected b/chromium/net/data/ftp/dir-listing-ls-13.expected
new file mode 100644
index 00000000000..30b0b706698
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-13.expected
@@ -0,0 +1,26 @@
+-
+test.jpg
+93064
+2007
+12
+28
+0
+0
+
+d
+kernels
+-1
+1993
+11
+17
+17
+8
+
+-
+UpTime.exe
+13274
+2006
+3
+1
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-14 b/chromium/net/data/ftp/dir-listing-ls-14
new file mode 100644
index 00000000000..84edee4ef3b
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-14
@@ -0,0 +1,3 @@
+-rwxr-xr-x 0 214 214 Jun 30 2005 !readme
+drwxr-xr-x folder 0 Jul 17 2006 online
+-rwxr-xr-x 332 7 339 Feb 5 2004 welcome.txt
diff --git a/chromium/net/data/ftp/dir-listing-ls-14.expected b/chromium/net/data/ftp/dir-listing-ls-14.expected
new file mode 100644
index 00000000000..86aca302b18
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-14.expected
@@ -0,0 +1,26 @@
+-
+!readme
+214
+2005
+6
+30
+0
+0
+
+d
+online
+-1
+2006
+7
+17
+0
+0
+
+-
+welcome.txt
+339
+2004
+2
+5
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-15 b/chromium/net/data/ftp/dir-listing-ls-15
new file mode 100644
index 00000000000..ee0de6ff0b1
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-15
@@ -0,0 +1,5 @@
+total 30
+---------- 1 root other 0 Mar 26 2004 .notar
+d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming
+-r--r--r-- 2 ftp 989 7196 Aug 22 2007 incoming.README
+drwxr-xr-x+ 16 root sys 512 Sep 25 09:56 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-15.expected b/chromium/net/data/ftp/dir-listing-ls-15.expected
new file mode 100644
index 00000000000..99576fb40be
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-15.expected
@@ -0,0 +1,35 @@
+-
+.notar
+0
+2004
+3
+26
+0
+0
+
+d
+incoming
+-1
+1993
+12
+8
+15
+54
+
+-
+incoming.README
+7196
+2007
+8
+22
+0
+0
+
+d
+pub
+-1
+1994
+9
+25
+9
+56
diff --git a/chromium/net/data/ftp/dir-listing-ls-16 b/chromium/net/data/ftp/dir-listing-ls-16
new file mode 100644
index 00000000000..4c2f783c054
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-16
@@ -0,0 +1,7 @@
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 documentales
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 dosier
+dr-xr-xr-x 1 owner group 0 Dec 1 2008 promos
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 Sueños_futbol
+dr-xr-xr-x 1 owner group 0 Nov 2 15:53 test
+dr-xr-xr-x 1 owner group 0 Nov 25 10:04 tmp
+-r-xr-xr-x 1 owner group 125 Oct 11 2007 Gastronomía.txt
diff --git a/chromium/net/data/ftp/dir-listing-ls-16.expected b/chromium/net/data/ftp/dir-listing-ls-16.expected
new file mode 100644
index 00000000000..18e553c9107
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-16.expected
@@ -0,0 +1,62 @@
+d
+documentales
+-1
+2008
+11
+28
+0
+0
+
+d
+dosier
+-1
+2008
+11
+28
+0
+0
+
+d
+promos
+-1
+2008
+12
+1
+0
+0
+
+d
+Sueños_futbol
+-1
+2008
+11
+28
+0
+0
+
+d
+test
+-1
+1994
+11
+2
+15
+53
+
+d
+tmp
+-1
+1993
+11
+25
+10
+4
+
+-
+Gastronomía.txt
+125
+2007
+10
+11
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-17 b/chromium/net/data/ftp/dir-listing-ls-17
new file mode 100644
index 00000000000..c07fbf3e34f
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-17
@@ -0,0 +1 @@
+ftpd-BSD: .: Permission denied
diff --git a/chromium/net/data/ftp/dir-listing-ls-17.expected b/chromium/net/data/ftp/dir-listing-ls-17.expected
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-17.expected
diff --git a/chromium/net/data/ftp/dir-listing-ls-18 b/chromium/net/data/ftp/dir-listing-ls-18
new file mode 100644
index 00000000000..a5040745775
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-18
@@ -0,0 +1,3 @@
+-rw-r--r-- 1 ftpadmin ftpadmin125435904 Apr 9 2008 .pureftpd-upload.47fcbb3c.849.191b.cd40d08a
+-rw-r--r-- 1 ftpadmin ftpadmin153198592 Nov 20 2008 .pureftpd-upload.4925a00d.849.668d.9ea5b3ed
+-rw-r--r-- 1 ftpadmin ftpadmin 8760 Nov 24 2008 .pureftpd-upload.492b0a03.849.12d.bf5d2bc6
diff --git a/chromium/net/data/ftp/dir-listing-ls-18.expected b/chromium/net/data/ftp/dir-listing-ls-18.expected
new file mode 100644
index 00000000000..1a1fe365d91
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-18.expected
@@ -0,0 +1,26 @@
+-
+.pureftpd-upload.47fcbb3c.849.191b.cd40d08a
+0
+2008
+4
+9
+0
+0
+
+-
+.pureftpd-upload.4925a00d.849.668d.9ea5b3ed
+0
+2008
+11
+20
+0
+0
+
+-
+.pureftpd-upload.492b0a03.849.12d.bf5d2bc6
+8760
+2008
+11
+24
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-19 b/chromium/net/data/ftp/dir-listing-ls-19
new file mode 100644
index 00000000000..c7c66fe679f
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-19
@@ -0,0 +1,2 @@
+drwxr-xr-x 2 0 0 4096 Mar 18 2007
+-rw-r--r-- 1 48 0 4327486 Jun 16 2006 junorelease.zip
diff --git a/chromium/net/data/ftp/dir-listing-ls-19.expected b/chromium/net/data/ftp/dir-listing-ls-19.expected
new file mode 100644
index 00000000000..1d057a91787
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-19.expected
@@ -0,0 +1,8 @@
+-
+junorelease.zip
+4327486
+2006
+6
+16
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-2 b/chromium/net/data/ftp/dir-listing-ls-2
new file mode 100644
index 00000000000..5e2c6e2e078
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-2
@@ -0,0 +1,7 @@
+drwxr-xr-x 3 0 0 4096 Sep 18 2008 .
+drwxr-xr-x 3 0 0 4096 Sep 18 2008 ..
+lrwxrwxrwx 1 0 509 1 Nov 08 2007 ftp -> .
+lrwxrwxrwx 1 0 0 3 Oct 12 2007 mirror -> pub
+lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub
+lrwxrwxrwx 1 0 0 27 Oct 16 2007 site -> vol/1/.CLUSTER/var_ftp/site
+drwxr-xr-x 7 0 0 4096 Jul 02 2008 vol
diff --git a/chromium/net/data/ftp/dir-listing-ls-2.expected b/chromium/net/data/ftp/dir-listing-ls-2.expected
new file mode 100644
index 00000000000..403a61cb07e
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-2.expected
@@ -0,0 +1,62 @@
+d
+.
+-1
+2008
+9
+18
+0
+0
+
+d
+..
+-1
+2008
+9
+18
+0
+0
+
+l
+ftp
+-1
+2007
+11
+8
+0
+0
+
+l
+mirror
+-1
+2007
+10
+12
+0
+0
+
+l
+pub
+-1
+2008
+9
+18
+0
+0
+
+l
+site
+-1
+2007
+10
+16
+0
+0
+
+d
+vol
+-1
+2008
+7
+2
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-20 b/chromium/net/data/ftp/dir-listing-ls-20
new file mode 100644
index 00000000000..18d5bb27e51
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-20
@@ -0,0 +1,18 @@
+drwxrwxr-x 17 ftp ftp 4096 Nov 01 16:27 .
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
+drwxrwxrwx 5 ftp ftp 4096 May 26 15:51 2012_-_2012-(2009)-[1080p]_[BD]
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:24 _READ_ME.txt
+drwxrwxrwx 5 ftp ftp 4096 Nov 01 16:27 Áåç_ëèöà_-_Face_Off-(1997)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jan 22 2010 Ââåðõ_-_Up-(2009)-[1080p]_[BD]
+drwxrwxrwx 5 ftp ftp 4096 May 27 18:12 Âñïîìíèòü_âñå_-_Total_Recall-(1990)-[1080p]_[BD]
+drwxrwxrwx 3 ftp ftp 4096 May 21 14:28 Çàêîíîïîñëóøíûé_ãðàæäàíèí_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Law_Abiding_Citizen_[Unrated_Edition]-(2009)-[1080p]_[BD_Remux]
+drwxrwxrwx 5 ftp ftp 4096 Jan 21 2010 Ìîíñòðî_-_Cloverfield-(2008)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 May 26 13:43 Ïàíäîðóì_-_Pandorum-(2009)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 May 27 14:18 Ïîñëåäíèé_ñàìóðàé_-_The_Last_Samurai-(2003)-[1080p]_[BD]
+drwxrwxrwx 6 ftp ftp 4096 Jan 27 2010 Ðàéîí_9_-_District_9-(2009)-[1080p]_[BD]
+drwxrwxrwx 7 ftp ftp 4096 Jan 01 2010 Ðîêêè_Àíòîëîãèÿ_-_Rocky_The_Undisputed_Collection-(1976_1979_1982_1985_1990)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 May 31 12:57 Ñóððîãàòû_-_Surrogates-(2009)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jan 28 2010 Òðîéíîé_Ôîðñàæ-Òîêèéñêèé_Äðèôò_-_The_Fast_and_the_Furious-Tokyo_Drift-(2006)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jan 08 2010 Ôîðñàæ_-_The_Fast_and_the_Furious-(2001)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jan 06 2010 Ôîðñàæ_2_-_2_Fast_2_Furious-(2003)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jun 08 15:26 Ôîðñàæ_4_-_Fast_&_Furious-(2009)-[1080p]_[BD]
diff --git a/chromium/net/data/ftp/dir-listing-ls-20.expected b/chromium/net/data/ftp/dir-listing-ls-20.expected
new file mode 100644
index 00000000000..9c636b9307d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-20.expected
@@ -0,0 +1,161 @@
+d
+.
+-1
+1994
+11
+1
+16
+27
+
+d
+..
+-1
+2010
+4
+3
+0
+0
+
+d
+2012_-_2012-(2009)-[1080p]_[BD]
+-1
+1994
+5
+26
+15
+51
+
+-
+_READ_ME.txt
+4931
+1994
+6
+8
+15
+24
+
+d
+Ãåç_ëèöà_-_Face_Off-(1997)-[1080p]_[BD]
+-1
+1994
+11
+1
+16
+27
+
+d
+Ââåðõ_-_Up-(2009)-[1080p]_[BD]
+-1
+2010
+1
+22
+0
+0
+
+d
+Âñïîìíèòü_âñå_-_Total_Recall-(1990)-[1080p]_[BD]
+-1
+1994
+5
+27
+18
+12
+
+d
+Çàêîíîïîñëóøíûé_ãðàæäàíèí_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Law_Abiding_Citizen_[Unrated_Edition]-(2009)-[1080p]_[BD_Remux]
+-1
+1994
+5
+21
+14
+28
+
+d
+Ìîíñòðî_-_Cloverfield-(2008)-[1080p]_[BD]
+-1
+2010
+1
+21
+0
+0
+
+d
+Ãàíäîðóì_-_Pandorum-(2009)-[1080p]_[BD]
+-1
+1994
+5
+26
+13
+43
+
+d
+Ãîñëåäíèé_ñàìóðàé_-_The_Last_Samurai-(2003)-[1080p]_[BD]
+-1
+1994
+5
+27
+14
+18
+
+d
+Ãàéîí_9_-_District_9-(2009)-[1080p]_[BD]
+-1
+2010
+1
+27
+0
+0
+
+d
+Ãîêêè_Àíòîëîãèÿ_-_Rocky_The_Undisputed_Collection-(1976_1979_1982_1985_1990)-[1080p]_[BD]
+-1
+2010
+1
+1
+0
+0
+
+d
+Ñóððîãàòû_-_Surrogates-(2009)-[1080p]_[BD]
+-1
+1994
+5
+31
+12
+57
+
+d
+Òðîéíîé_Ôîðñàæ-Òîêèéñêèé_Äðèôò_-_The_Fast_and_the_Furious-Tokyo_Drift-(2006)-[1080p]_[BD]
+-1
+2010
+1
+28
+0
+0
+
+d
+Ôîðñàæ_-_The_Fast_and_the_Furious-(2001)-[1080p]_[BD]
+-1
+2010
+1
+8
+0
+0
+
+d
+Ôîðñàæ_2_-_2_Fast_2_Furious-(2003)-[1080p]_[BD]
+-1
+2010
+1
+6
+0
+0
+
+d
+Ôîðñàæ_4_-_Fast_&_Furious-(2009)-[1080p]_[BD]
+-1
+1994
+6
+8
+15
+26
diff --git a/chromium/net/data/ftp/dir-listing-ls-21 b/chromium/net/data/ftp/dir-listing-ls-21
new file mode 100644
index 00000000000..1246efd95e0
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-21
@@ -0,0 +1,27 @@
+drwxrwxr-x 26 ftp ftp 4096 Jul 15 2009 .
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:24 _READ_ME.txt
+drwxr-xr-x 5 ftp ftp 4096 Apr 27 2009 Àâàëîí_-_Avalon-(2001)-[1080p]_[BD_Remux]
+drwxrwxrwx 5 ftp ftp 4096 Jun 15 2009 Áðþñ_âñåìîãóùèé_-_Bruce_Almighty-(2003)-[1080p]_[BD]
+drwxr-xr-x 4 ftp ftp 4096 Apr 15 2009 ÂÀËË-È_-_WALL-E-(2008)-[1080p]_[BD]
+drwxr-xr-x 4 ftp ftp 4096 Apr 28 2009 Äæåéìñ_Áîíä_007-Êâàíò_ìèëîñåðäèÿ_-_James_Bond_007-Quantum_of_Solace-(2008)-[1080p]_[BD]
+drwxr-xr-x 4 ftp ftp 4096 Apr 15 2009 Êîñìîñ-Òåððèòîðèÿ_cìåðòè_-_Dead_Space-Downfall-(2008)-[1080p]_[BD]
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 Ìàäàãàñêàð_1_-_Madagascar_1-(2005)-[1080p]_[BD]
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 Ìàäàãàñêàð_2_-_Madagascar-Escape_2_Africa-(2008)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jun 13 2009 Ìàòðèöà-Ïåðåçàãðóçêà_-_The_Matrix-Reloaded-(2003)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jun 14 2009 Ìàòðèöà-Ðåâîëþöèÿ_-_The_Matrix-Revolutions-(2003)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jun 12 2009 Ìàòðèöà_-_The_Matrix-(1999)-[1080p]_[BD]
+drwxrwxrwx 4 ftp ftp 4096 Jul 02 2009 Îáèòåëü_çëà_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD]
+drwxr-xr-x 3 ftp ftp 4096 May 01 2009 Îñòðîâ_-_The_Island-(2005)-[1080p]_[BD]
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 Ïåðåâîç÷èê_3_-_Transporter_3-(2008)-[1080p]_[BD]
+drwxrwxr-x 5 ftp ftp 4096 May 02 2009 Ïèðàòû_Êàðèáñêîãî_ìîðÿ-Íà_êðàþ_Ñâåòà_-_Pirates_of_the_Caribbean-At_World's_End-(2007)-[1080p]_[BD]
+drwxrwxr-x 5 ftp ftp 4096 May 03 2009 Ïèðàòû_Êàðèáñêîãî_ìîðÿ-Ïðîêëÿòèå_×åðíîé_Æåì÷óæèíû_-_Pirates_of_the_Caribbean-The_Curse_of_the_Black_Pearl-(2003)-[1080p]_[BD]
+drwxrwxr-x 5 ftp ftp 4096 May 02 2009 Ïèðàòû_Êàðèáñêîãî_ìîðÿ-Ñóíäóê_ìåðòâåöà_-_Pirates_of_the_Caribbean-Dead_Man's_Chest-(2006)-[1080p]_[BD]
+drwxrwxr-x 3 ftp ftp 4096 May 01 2009 Ïðèçðà÷íûé_ãîíùèê_-_Ghost_Rider-(2007)-[1080p]_[BD]
+drwxr-xr-x 5 ftp ftp 4096 Apr 29 2009 Ïðèíöåññà-íåâåñòà_-_The_Princess_Bride-(1987)-[1080p]_[BD]
+drwxrwxrwx 5 ftp ftp 4096 Jun 08 2009 Ñåêñ_è_101_ñìåðòü_-_Sex_and_Death_101-(2007)-[1080p]_[BD]
+drwxr-xr-x 4 ftp ftp 4096 May 01 2009 Òðàíñôîðìåðû-Áîíóñ_äèñê_-_Transformers-Bonus_Disk-(2007)-[1080p]_[BD]
+drwxr-xr-x 4 ftp ftp 4096 Apr 30 2009 Òðàíñôîðìåðû_-_Transformers-(2007)-[1080p]_[BD]
+drwxrwxrwx 6 ftp ftp 4096 Jun 07 2009 Òðèíàäöàòûé_ýòàæ_-_The_Thirteenth_Floor-(1999)-[1080p]_[BD]
+drwxrwxr-x 3 ftp ftp 4096 May 04 2009 Óëè÷íûé_áîåö_-_Street_Fighter-(1994)-[1080p]_[BD_Remux]
+drwxr-xr-x 5 ftp ftp 4096 Mar 15 2009 ×åãî_õîòÿò_æåíùèíû_-_What_Woman_Want-(2000)-[1080p]_[BD]
diff --git a/chromium/net/data/ftp/dir-listing-ls-21.expected b/chromium/net/data/ftp/dir-listing-ls-21.expected
new file mode 100644
index 00000000000..0111be16235
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-21.expected
@@ -0,0 +1,242 @@
+d
+.
+-1
+2009
+7
+15
+0
+0
+
+d
+..
+-1
+2010
+4
+3
+0
+0
+
+-
+_READ_ME.txt
+4931
+1994
+6
+8
+15
+24
+
+d
+Àâàëîí_-_Avalon-(2001)-[1080p]_[BD_Remux]
+-1
+2009
+4
+27
+0
+0
+
+d
+Ãðþñ_âñåìîãóùèé_-_Bruce_Almighty-(2003)-[1080p]_[BD]
+-1
+2009
+6
+15
+0
+0
+
+d
+ÂÀËË-È_-_WALL-E-(2008)-[1080p]_[BD]
+-1
+2009
+4
+15
+0
+0
+
+d
+Äæåéìñ_Ãîíä_007-Êâàíò_ìèëîñåðäèÿ_-_James_Bond_007-Quantum_of_Solace-(2008)-[1080p]_[BD]
+-1
+2009
+4
+28
+0
+0
+
+d
+Êîñìîñ-Òåððèòîðèÿ_cìåðòè_-_Dead_Space-Downfall-(2008)-[1080p]_[BD]
+-1
+2009
+4
+15
+0
+0
+
+d
+Ìàäàãàñêàð_1_-_Madagascar_1-(2005)-[1080p]_[BD]
+-1
+2009
+7
+3
+0
+0
+
+d
+Ìàäàãàñêàð_2_-_Madagascar-Escape_2_Africa-(2008)-[1080p]_[BD]
+-1
+2009
+7
+3
+0
+0
+
+d
+Ìàòðèöà-Ãåðåçàãðóçêà_-_The_Matrix-Reloaded-(2003)-[1080p]_[BD]
+-1
+2009
+6
+13
+0
+0
+
+d
+Ìàòðèöà-Ãåâîëþöèÿ_-_The_Matrix-Revolutions-(2003)-[1080p]_[BD]
+-1
+2009
+6
+14
+0
+0
+
+d
+Ìàòðèöà_-_The_Matrix-(1999)-[1080p]_[BD]
+-1
+2009
+6
+12
+0
+0
+
+d
+Îáèòåëü_çëà_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD]
+-1
+2009
+7
+2
+0
+0
+
+d
+Îñòðîâ_-_The_Island-(2005)-[1080p]_[BD]
+-1
+2009
+5
+1
+0
+0
+
+d
+Ãåðåâîç÷èê_3_-_Transporter_3-(2008)-[1080p]_[BD]
+-1
+2009
+7
+3
+0
+0
+
+d
+Ãèðàòû_Êàðèáñêîãî_ìîðÿ-Ãà_êðàþ_Ñâåòà_-_Pirates_of_the_Caribbean-At_World's_End-(2007)-[1080p]_[BD]
+-1
+2009
+5
+2
+0
+0
+
+d
+Ãèðàòû_Êàðèáñêîãî_ìîðÿ-Ãðîêëÿòèå_×åðíîé_Æåì÷óæèíû_-_Pirates_of_the_Caribbean-The_Curse_of_the_Black_Pearl-(2003)-[1080p]_[BD]
+-1
+2009
+5
+3
+0
+0
+
+d
+Ãèðàòû_Êàðèáñêîãî_ìîðÿ-Ñóíäóê_ìåðòâåöà_-_Pirates_of_the_Caribbean-Dead_Man's_Chest-(2006)-[1080p]_[BD]
+-1
+2009
+5
+2
+0
+0
+
+d
+Ãðèçðà÷íûé_ãîíùèê_-_Ghost_Rider-(2007)-[1080p]_[BD]
+-1
+2009
+5
+1
+0
+0
+
+d
+Ãðèíöåññà-íåâåñòà_-_The_Princess_Bride-(1987)-[1080p]_[BD]
+-1
+2009
+4
+29
+0
+0
+
+d
+Ñåêñ_è_101_ñìåðòü_-_Sex_and_Death_101-(2007)-[1080p]_[BD]
+-1
+2009
+6
+8
+0
+0
+
+d
+Òðàíñôîðìåðû-Ãîíóñ_äèñê_-_Transformers-Bonus_Disk-(2007)-[1080p]_[BD]
+-1
+2009
+5
+1
+0
+0
+
+d
+Òðàíñôîðìåðû_-_Transformers-(2007)-[1080p]_[BD]
+-1
+2009
+4
+30
+0
+0
+
+d
+Òðèíàäöàòûé_ýòàæ_-_The_Thirteenth_Floor-(1999)-[1080p]_[BD]
+-1
+2009
+6
+7
+0
+0
+
+d
+Óëè÷íûé_áîåö_-_Street_Fighter-(1994)-[1080p]_[BD_Remux]
+-1
+2009
+5
+4
+0
+0
+
+d
+×åãî_õîòÿò_æåíùèíû_-_What_Woman_Want-(2000)-[1080p]_[BD]
+-1
+2009
+3
+15
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-22 b/chromium/net/data/ftp/dir-listing-ls-22
new file mode 100644
index 00000000000..df4414125de
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-22
@@ -0,0 +1,32 @@
+drwxrwxr-x 5 ftp ftp 12288 Oct 20 17:04 .
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:23 _READ_ME.txt
+drwxrwxrwx 4 ftp ftp 4096 May 31 16:26 Àâàòàð_-_Avatar-(2009)-[1080p]_[BD]
+-rwxrwxr-x 1 ftp ftp 17577705968 Mar 08 2009 Àìåðèêàíñêèé_ïèðîã_1_[Ðàñøèðåííàÿ_âåðñèÿ]_-_American_Pie_1_[Unrated_Edition]-(1999)-[1080p]_[BD_remux].ts
+-rwxrwxr-x 1 ftp ftp 15512934868 Mar 16 2009 Áîëüøîé_êóø_-_Snatch-(2000)-[1080i]_[HDTV].ts
+drwxrwxrwx 2 ftp ftp 4096 Jun 03 19:07 Áîëüøîé_êóø_-_Snatch-(2000)-[1080p]_[BD_Remux]
+-rwxrwxr-x 1 ftp ftp 8900589105 Mar 24 2009 Âîéíà_ìèðîâ_-_War_of_the_Worlds-(2005)-[720p]_[HDTV].mkv
+-rwxrwxr-x 1 ftp ftp 27728321654 Mar 09 2009 Ãàíãñòåð_-_American_Gangster-(2007)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 31731782861 Mar 09 2009 Ãàíãñòåð_[Ðàñøèðåííàÿ_âåðñèÿ]_-_American_Gangster_[Unrated_Edition]-(2007)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 5009104014 Mar 24 2009 Äîðîæíîå_ïðèêëþ÷åíèå_-_Road_Trip-(2000)-[720p]_[HDTV_Rip].mkv
+-rwxrwxr-x 1 ftp ftp 21410583980 Mar 11 2009 Çâ¸çäíûå_âîéíû-Ýïèçîä_2-Àòàêà_êëîíîâ_-_Star_Wars-Episode_2-Attack_of_the_Clones-(2002)-[1080i]_[HDTV].ts
+-rwxrwxr-x 1 ftp ftp 19858181688 Mar 11 2009 Çâ¸çäíûå_âîéíû-Ýïèçîä_3-Ìåñòü_Ñèòõîâ_-_Star_Wars-Episode_3-Revenge_of_the_Sith-(2005)-[1080i]_[HDTV].ts
+-rwxrwxr-x 1 ftp ftp 29026065728 Mar 16 2009 Çâ¸çäíûé_äåñàíò_-_Starship_Troopers-(1997)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 22169179449 Mar 16 2009 Çåðêàëà_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Mirrors_[Unrated_Edition]-(2008)-[1080p]_[BD_remux].mkv
+drwxrwxrwx 4 ftp ftp 4096 Jun 15 14:56 Íèíäçÿ-óáèéöà_-_Ninja_Assassin-(2009)-[1080p]_[BD]
+-rwxrwxr-x 1 ftp ftp 19717173247 Mar 11 2009 Îáèòåëü_çëà_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 18660904388 Mar 11 2009 Ïàòîëîãèÿ_-_Pathology-(2008)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 16476154520 Mar 05 2009 Ïèëà_1_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_I_[Director's_Cut]-(2004)-[1080p]_[HDDVD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 19917510515 Mar 05 2009 Ïèëà_2_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_II_[Director's_Cut]-(2005)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 18085592265 Mar 05 2009 Ïèëà_3_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_III_[Director's_Cut]-(2006)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 3473582701 Mar 05 2009 Ïèëà_4_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].flac
+-rwxrwxr-x 1 ftp ftp 15263958421 Mar 05 2009 Ïèëà_4_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 19944605507 Mar 16 2009 Ïèëà_5_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_V_[Director's_Cut]-(2008)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 3024333064 Mar 24 2009 Ïèíãâèíû_èç_Ìàäàãàñêàðà-Îïåðàöèÿ_Ñ_Íîâûì_Ãîäîì!_-_The_Madagascar_Penguins_in_A_Christmas_Caper-(2005)-[1080p]_[BD_remux].ts
+-rwxrwxr-x 1 ftp ftp 125961 Mar 05 2009 Ïëîõîé_Ñàíòà_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].srt
+-rwxrwxr-x 1 ftp ftp 19908695408 Mar 05 2009 Ïëîõîé_Ñàíòà_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].ts
+-rwxrwxr-x 1 ftp ftp 23185439267 Mar 11 2009 Ïîáåã_èç_Øîóøåíêà_-_The_Shawshank_Redemption-(1994)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 19567287274 Mar 16 2009 Òóïîé_è_åùå_òóïåå_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Dumb_and_Dumber_[Unrated_Edition]-(1994)-[1080p]_[BD_remux].mkv
+-rwxrwxr-x 1 ftp ftp 14773061093 Mar 16 2009 Óðàãàí_-_The_Hurricane-(1999)-[1080p]_[HDDVD_Rip].mkv
+-rwxrwxr-x 1 ftp ftp 22411268500 Mar 11 2009 Õîñòåë_2_[Ðåæèññ¸ðñêàÿ_âåðñèÿ]_-_Hostel_2_[Director's_Cut]-(2007)-[1080p]_[BD_remux].ts
+-rwxrwxr-x 1 ftp ftp 23712519861 Mar 11 2009 ×óæîé_ïðîòèâ_Õèùíèêà_[Ðàñøèðåííàÿ_âåðñèÿ]_-_Alien_vs_Predator_[Unrated_Edition]-(2004)-[1080p]_[BD_remux].mkv
diff --git a/chromium/net/data/ftp/dir-listing-ls-22.expected b/chromium/net/data/ftp/dir-listing-ls-22.expected
new file mode 100644
index 00000000000..c5a02b7850f
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-22.expected
@@ -0,0 +1,287 @@
+d
+.
+-1
+1994
+10
+20
+17
+4
+
+d
+..
+-1
+2010
+4
+3
+0
+0
+
+-
+_READ_ME.txt
+4931
+1994
+6
+8
+15
+23
+
+d
+Àâàòàð_-_Avatar-(2009)-[1080p]_[BD]
+-1
+1994
+5
+31
+16
+26
+
+-
+Àìåðèêàíñêèé_ïèðîã_1_[Ãàñøèðåííàÿ_âåðñèÿ]_-_American_Pie_1_[Unrated_Edition]-(1999)-[1080p]_[BD_remux].ts
+17577705968
+2009
+3
+8
+0
+0
+
+-
+Ãîëüøîé_êóø_-_Snatch-(2000)-[1080i]_[HDTV].ts
+15512934868
+2009
+3
+16
+0
+0
+
+d
+Ãîëüøîé_êóø_-_Snatch-(2000)-[1080p]_[BD_Remux]
+-1
+1994
+6
+3
+19
+7
+
+-
+Âîéíà_ìèðîâ_-_War_of_the_Worlds-(2005)-[720p]_[HDTV].mkv
+8900589105
+2009
+3
+24
+0
+0
+
+-
+Ãàíãñòåð_-_American_Gangster-(2007)-[1080p]_[BD_remux].mkv
+27728321654
+2009
+3
+9
+0
+0
+
+-
+Ãàíãñòåð_[Ãàñøèðåííàÿ_âåðñèÿ]_-_American_Gangster_[Unrated_Edition]-(2007)-[1080p]_[BD_remux].mkv
+31731782861
+2009
+3
+9
+0
+0
+
+-
+Äîðîæíîå_ïðèêëþ÷åíèå_-_Road_Trip-(2000)-[720p]_[HDTV_Rip].mkv
+5009104014
+2009
+3
+24
+0
+0
+
+-
+Çâ¸çäíûå_âîéíû-Ãïèçîä_2-Àòàêà_êëîíîâ_-_Star_Wars-Episode_2-Attack_of_the_Clones-(2002)-[1080i]_[HDTV].ts
+21410583980
+2009
+3
+11
+0
+0
+
+-
+Çâ¸çäíûå_âîéíû-Ãïèçîä_3-Ìåñòü_Ñèòõîâ_-_Star_Wars-Episode_3-Revenge_of_the_Sith-(2005)-[1080i]_[HDTV].ts
+19858181688
+2009
+3
+11
+0
+0
+
+-
+Çâ¸çäíûé_äåñàíò_-_Starship_Troopers-(1997)-[1080p]_[BD_remux].mkv
+29026065728
+2009
+3
+16
+0
+0
+
+-
+Çåðêàëà_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Mirrors_[Unrated_Edition]-(2008)-[1080p]_[BD_remux].mkv
+22169179449
+2009
+3
+16
+0
+0
+
+d
+Ãèíäçÿ-óáèéöà_-_Ninja_Assassin-(2009)-[1080p]_[BD]
+-1
+1994
+6
+15
+14
+56
+
+-
+Îáèòåëü_çëà_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD_remux].mkv
+19717173247
+2009
+3
+11
+0
+0
+
+-
+Ãàòîëîãèÿ_-_Pathology-(2008)-[1080p]_[BD_remux].mkv
+18660904388
+2009
+3
+11
+0
+0
+
+-
+Ãèëà_1_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_I_[Director's_Cut]-(2004)-[1080p]_[HDDVD_remux].mkv
+16476154520
+2009
+3
+5
+0
+0
+
+-
+Ãèëà_2_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_II_[Director's_Cut]-(2005)-[1080p]_[BD_remux].mkv
+19917510515
+2009
+3
+5
+0
+0
+
+-
+Ãèëà_3_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_III_[Director's_Cut]-(2006)-[1080p]_[BD_remux].mkv
+18085592265
+2009
+3
+5
+0
+0
+
+-
+Ãèëà_4_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].flac
+3473582701
+2009
+3
+5
+0
+0
+
+-
+Ãèëà_4_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].mkv
+15263958421
+2009
+3
+5
+0
+0
+
+-
+Ãèëà_5_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Saw_V_[Director's_Cut]-(2008)-[1080p]_[BD_remux].mkv
+19944605507
+2009
+3
+16
+0
+0
+
+-
+Ãèíãâèíû_èç_Ìàäàãàñêàðà-Îïåðàöèÿ_Ñ_Ãîâûì_Ãîäîì!_-_The_Madagascar_Penguins_in_A_Christmas_Caper-(2005)-[1080p]_[BD_remux].ts
+3024333064
+2009
+3
+24
+0
+0
+
+-
+Ãëîõîé_Ñàíòà_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].srt
+125961
+2009
+3
+5
+0
+0
+
+-
+Ãëîõîé_Ñàíòà_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].ts
+19908695408
+2009
+3
+5
+0
+0
+
+-
+Ãîáåã_èç_Øîóøåíêà_-_The_Shawshank_Redemption-(1994)-[1080p]_[BD_remux].mkv
+23185439267
+2009
+3
+11
+0
+0
+
+-
+Òóïîé_è_åùå_òóïåå_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Dumb_and_Dumber_[Unrated_Edition]-(1994)-[1080p]_[BD_remux].mkv
+19567287274
+2009
+3
+16
+0
+0
+
+-
+Óðàãàí_-_The_Hurricane-(1999)-[1080p]_[HDDVD_Rip].mkv
+14773061093
+2009
+3
+16
+0
+0
+
+-
+Õîñòåë_2_[Ãåæèññ¸ðñêàÿ_âåðñèÿ]_-_Hostel_2_[Director's_Cut]-(2007)-[1080p]_[BD_remux].ts
+22411268500
+2009
+3
+11
+0
+0
+
+-
+×óæîé_ïðîòèâ_Õèùíèêà_[Ãàñøèðåííàÿ_âåðñèÿ]_-_Alien_vs_Predator_[Unrated_Edition]-(2004)-[1080p]_[BD_remux].mkv
+23712519861
+2009
+3
+11
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-23 b/chromium/net/data/ftp/dir-listing-ls-23
new file mode 100644
index 00000000000..2b8c49483d0
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-23
@@ -0,0 +1,2 @@
+total 0
+ftpd: .: Permission denied
diff --git a/chromium/net/data/ftp/dir-listing-ls-23.expected b/chromium/net/data/ftp/dir-listing-ls-23.expected
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-23.expected
diff --git a/chromium/net/data/ftp/dir-listing-ls-24 b/chromium/net/data/ftp/dir-listing-ls-24
new file mode 100644
index 00000000000..acb09a8bc1d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-24
@@ -0,0 +1,2 @@
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 note_empty_line_below
+
diff --git a/chromium/net/data/ftp/dir-listing-ls-24.expected b/chromium/net/data/ftp/dir-listing-ls-24.expected
new file mode 100644
index 00000000000..bdfd3091873
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-24.expected
@@ -0,0 +1,8 @@
+d
+note_empty_line_below
+-1
+2008
+8
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-25 b/chromium/net/data/ftp/dir-listing-ls-25
new file mode 100644
index 00000000000..47e0487b3cf
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-25
@@ -0,0 +1,6 @@
+drwxr-xr-x 3 ftp ftp 4096 15 апр 18:11 .
+drwxr-xr-x 3 ftp ftp 4096 15 июл 18:11 ..
+-rw-r--r-- 1 ftp ftp 528 01 май 2007 .message
+-rw-r--r-- 1 ftp ftp 528 01 Ð½Ð¾Ñ 2007 README
+-rw-r--r-- 1 ftp ftp 560 28 Ñен 2007 index.html
+drwxr-xr-x 33 ftp ftp 4096 12 фев 2008 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-25.expected b/chromium/net/data/ftp/dir-listing-ls-25.expected
new file mode 100644
index 00000000000..8b1b2db85f2
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-25.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+1994
+4
+15
+18
+11
+
+d
+..
+-1
+1994
+7
+15
+18
+11
+
+-
+.message
+528
+2007
+5
+1
+0
+0
+
+-
+README
+528
+2007
+11
+1
+0
+0
+
+-
+index.html
+560
+2007
+9
+28
+0
+0
+
+d
+pub
+-1
+2008
+2
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-26 b/chromium/net/data/ftp/dir-listing-ls-26
new file mode 100644
index 00000000000..45d7e8a8d22
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-26
@@ -0,0 +1,6 @@
+drwxr-xr-x 3 ftp ftp 4096 15 ÁÐÒ 18:11 .
+drwxr-xr-x 3 ftp ftp 4096 15 ÉÀÌ 18:11 ..
+-rw-r--r-- 1 ftp ftp 528 01 ÍÁÊ 2007 .message
+-rw-r--r-- 1 ftp ftp 528 01 ÎÏÑ 2007 README
+-rw-r--r-- 1 ftp ftp 560 28 ÓÅÎ 2007 index.html
+drwxr-xr-x 33 ftp ftp 4096 12 ÆÅ× 2008 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-26.expected b/chromium/net/data/ftp/dir-listing-ls-26.expected
new file mode 100644
index 00000000000..8b1b2db85f2
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-26.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+1994
+4
+15
+18
+11
+
+d
+..
+-1
+1994
+7
+15
+18
+11
+
+-
+.message
+528
+2007
+5
+1
+0
+0
+
+-
+README
+528
+2007
+11
+1
+0
+0
+
+-
+index.html
+560
+2007
+9
+28
+0
+0
+
+d
+pub
+-1
+2008
+2
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-27 b/chromium/net/data/ftp/dir-listing-ls-27
new file mode 100644
index 00000000000..3c0a3040e17
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-27
@@ -0,0 +1,6 @@
+drwxr-xr-x 3 ftp ftp 4096 15 àïð 18:11 .
+drwxr-xr-x 3 ftp ftp 4096 15 èþë 18:11 ..
+-rw-r--r-- 1 ftp ftp 528 01 ìàé 2007 .message
+-rw-r--r-- 1 ftp ftp 528 01 íîÿ 2007 README
+-rw-r--r-- 1 ftp ftp 560 28 ñåí 2007 index.html
+drwxr-xr-x 33 ftp ftp 4096 12 ôåâ 2008 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-27.expected b/chromium/net/data/ftp/dir-listing-ls-27.expected
new file mode 100644
index 00000000000..8b1b2db85f2
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-27.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+1994
+4
+15
+18
+11
+
+d
+..
+-1
+1994
+7
+15
+18
+11
+
+-
+.message
+528
+2007
+5
+1
+0
+0
+
+-
+README
+528
+2007
+11
+1
+0
+0
+
+-
+index.html
+560
+2007
+9
+28
+0
+0
+
+d
+pub
+-1
+2008
+2
+12
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-28 b/chromium/net/data/ftp/dir-listing-ls-28
new file mode 100644
index 00000000000..85b387f5702
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-28
@@ -0,0 +1,5 @@
+drwxr-x 2 10 4096 Dec 10 14:32 pollq
+drwxr-x 4 0 4096 Jul 28 01:44 etc
+drwxrwx 2 10 4096 Jul 28 02:41 tmp
+drwxr-x 2 10 4096 Jul 28 02:00 status
+drwxr-x 2 0 4096 Jul 27 23:21 bin
diff --git a/chromium/net/data/ftp/dir-listing-ls-28.expected b/chromium/net/data/ftp/dir-listing-ls-28.expected
new file mode 100644
index 00000000000..ca5fb9c0dd7
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-28.expected
@@ -0,0 +1,44 @@
+d
+pollq
+-1
+1993
+12
+10
+14
+32
+
+d
+etc
+-1
+1994
+7
+28
+1
+44
+
+d
+tmp
+-1
+1994
+7
+28
+2
+41
+
+d
+status
+-1
+1994
+7
+28
+2
+0
+
+d
+bin
+-1
+1994
+7
+27
+23
+21
diff --git a/chromium/net/data/ftp/dir-listing-ls-29 b/chromium/net/data/ftp/dir-listing-ls-29
new file mode 100644
index 00000000000..6c82909f195
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-29
@@ -0,0 +1,9 @@
+.:
+total 1204984
+drwxrwxrwx 2 sunle sunle 2048 Apr 6 00:36 .
+drwxrwxrwx 9406 sunle sunle 245760 Apr 10 18:00 ..
+-rw-rw---- 1 ftp sunle 110583757 Apr 5 13:43 GRBRATLIN64_4_77_ia64_4.77_S0@874222@03717.rpm.Z
+-rw-rw---- 1 ftp sunle 121591811 Apr 5 13:43 GRBRATLIN_4_77_ia32_4.77_S0@874222@20701.rpm.Z
+-rw-rw---- 1 ftp sunle 110965625 Apr 5 13:43 XENRATLIN64_4_77_ia64_4.77_S0@874222@17785.rpm.Z
+-rw-rw---- 1 ftp sunle 121986153 Apr 5 13:44 XENRATLIN_4_77_ia32_4.77_S0@874222@09635.rpm.Z
+-rw-rw---- 1 ftp sunle 151525255 Apr 5 13:44 XENRATS10_4_77_sun4_10_4.77_S0@874222@21358.dstream.Z
diff --git a/chromium/net/data/ftp/dir-listing-ls-29.expected b/chromium/net/data/ftp/dir-listing-ls-29.expected
new file mode 100644
index 00000000000..b480f093697
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-29.expected
@@ -0,0 +1,62 @@
+d
+.
+-1
+1994
+4
+6
+0
+36
+
+d
+..
+-1
+1994
+4
+10
+18
+0
+
+-
+GRBRATLIN64_4_77_ia64_4.77_S0@874222@03717.rpm.Z
+110583757
+1994
+4
+5
+13
+43
+
+-
+GRBRATLIN_4_77_ia32_4.77_S0@874222@20701.rpm.Z
+121591811
+1994
+4
+5
+13
+43
+
+-
+XENRATLIN64_4_77_ia64_4.77_S0@874222@17785.rpm.Z
+110965625
+1994
+4
+5
+13
+43
+
+-
+XENRATLIN_4_77_ia32_4.77_S0@874222@09635.rpm.Z
+121986153
+1994
+4
+5
+13
+44
+
+-
+XENRATS10_4_77_sun4_10_4.77_S0@874222@21358.dstream.Z
+151525255
+1994
+4
+5
+13
+44
diff --git a/chromium/net/data/ftp/dir-listing-ls-3 b/chromium/net/data/ftp/dir-listing-ls-3
new file mode 100644
index 00000000000..8720c06572b
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-3
@@ -0,0 +1,7 @@
+
+-r-xr-xr-x 1 anonymou 245 90 Mar 1 2003 .welcome
+drwxr-xr-x 1 system 1 512 Feb 26 2005 decus
+dr-xr-xr-x 1 system 1 3072 Dec 2 2006 gnv
+-r-xr-xr-x 1 anonymou 245 158208 Apr 10 2003 unzip.alpha_exe
+-r-xr-xr-x 1 anonymou 245 102400 Apr 10 2003 unzip.vax_exe
+dr-xr-xr-x 1 anonymou 245 1024 Mar 1 2003 vms-freeware
diff --git a/chromium/net/data/ftp/dir-listing-ls-3.expected b/chromium/net/data/ftp/dir-listing-ls-3.expected
new file mode 100644
index 00000000000..c2431a6d9b0
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-3.expected
@@ -0,0 +1,53 @@
+-
+.welcome
+90
+2003
+3
+1
+0
+0
+
+d
+decus
+-1
+2005
+2
+26
+0
+0
+
+d
+gnv
+-1
+2006
+12
+2
+0
+0
+
+-
+unzip.alpha_exe
+158208
+2003
+4
+10
+0
+0
+
+-
+unzip.vax_exe
+102400
+2003
+4
+10
+0
+0
+
+d
+vms-freeware
+-1
+2003
+3
+1
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-30 b/chromium/net/data/ftp/dir-listing-ls-30
new file mode 100644
index 00000000000..6c095ccd554
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-30
@@ -0,0 +1,2 @@
+total 395136991232
+drwx-wx-wx 10 ftpadmin ftpusers 256 May 27 2010 downloads
diff --git a/chromium/net/data/ftp/dir-listing-ls-30.expected b/chromium/net/data/ftp/dir-listing-ls-30.expected
new file mode 100644
index 00000000000..a94497aa4be
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-30.expected
@@ -0,0 +1,8 @@
+d
+downloads
+-1
+2010
+5
+27
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-31 b/chromium/net/data/ftp/dir-listing-ls-31
new file mode 100644
index 00000000000..237a54a690f
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-31
@@ -0,0 +1,15 @@
+drwxrwxrwx 6 1000 1000 4096 Jul 23 2011 Alfresco
+drwxrwxrwx 2 1000 1000 4096 Mar 18 2010 DIRECTUM
+-rwxrwxrwx 1 1000 1000 222510 Mar 29 2010 Featurelist 6.30.pdf
+drwxrwxrwx 2 1000 1000 4096 Jul 23 2011 NauDoc_v4.4
+-rwxrwxrwx 1 1000 1000 1564767 Apr 06 2010 RUS_v_01_00_МЕТОД СБОРРИ ДОКУМЕÐТИРОВÐÐИЯ
+ ТРЕБОВÐÐИЙ К ПОРТÐЛУ
+.pdf
+drwxrwxrwx 4 1000 1000 4096 Jul 22 2011 Videoconferencing
+drwxrwxrwx 3 1000 1000 4096 Apr 15 2010 Virtualization
+-rwxrwxrwx 1 1000 1000 111726333 Jan 10 2010 electr_docoborot_2010.flv
+-rwxrwxrwx 1 1000 1000 4224387 Mar 31 2010 millenniumbsa.pdf
+drwxrwxrwx 5 1000 1000 4096 Apr 16 2010 Ð‘Ð¸Ð·Ð½ÐµÑ ÐŸÐ»Ð°Ð½
+-rwxrwxrwx 1 1000 1000 138217 Apr 16 2010 Мониторинг в инфраÑтруктуре раÑпределенных приложений .NET.rar
+-rwxrwxrwx 1 1000 1000 4131 Feb 25 2010 О законе О перÑональных данных.txt
+-rwxrwxrwx 1 1000 1000 3627173 Feb 21 2010 Шеер Ð.Ð’. -- БизнеÑ-процеÑÑÑ‹. ОÑновные понÑтиÑ..djvu
diff --git a/chromium/net/data/ftp/dir-listing-ls-31.expected b/chromium/net/data/ftp/dir-listing-ls-31.expected
new file mode 100644
index 00000000000..3addbeefa76
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-31.expected
@@ -0,0 +1,118 @@
+d
+Alfresco
+-1
+2011
+7
+23
+0
+0
+
+d
+DIRECTUM
+-1
+2010
+3
+18
+0
+0
+
+-
+Featurelist 6.30.pdf
+222510
+2010
+3
+29
+0
+0
+
+d
+NauDoc_v4.4
+-1
+2011
+7
+23
+0
+0
+
+-
+RUS_v_01_00_МЕТОД СБОРРИ ДОКУМЕÐТИРОВÐÐИЯ
+ ТРЕБОВÐÐИЙ К ПОРТÐЛУ
+.pdf
+1564767
+2010
+4
+6
+0
+0
+
+d
+Videoconferencing
+-1
+2011
+7
+22
+0
+0
+
+d
+Virtualization
+-1
+2010
+4
+15
+0
+0
+
+-
+electr_docoborot_2010.flv
+111726333
+2010
+1
+10
+0
+0
+
+-
+millenniumbsa.pdf
+4224387
+2010
+3
+31
+0
+0
+
+d
+Ð‘Ð¸Ð·Ð½ÐµÑ ÐŸÐ»Ð°Ð½
+-1
+2010
+4
+16
+0
+0
+
+-
+Мониторинг в инфраÑтруктуре раÑпределенных приложений .NET.rar
+138217
+2010
+4
+16
+0
+0
+
+-
+О законе О перÑональных данных.txt
+4131
+2010
+2
+25
+0
+0
+
+-
+Шеер Ð.Ð’. -- БизнеÑ-процеÑÑÑ‹. ОÑновные понÑтиÑ..djvu
+3627173
+2010
+2
+21
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-4 b/chromium/net/data/ftp/dir-listing-ls-4
new file mode 100644
index 00000000000..078542f0976
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-4
@@ -0,0 +1,10 @@
+
+-rwx---r-x 1 archives 0 472 Jun 28 2004 .welcome
+drwxr-xr-x 1 archives 0 512 Mar 5 1998 contributed-software
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 customer_support
+drwxr-xr-x 1 archives 0 512 Dec 30 1998 docs
+drwxr-xr-x 1 archives 0 512 May 8 1998 faq
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 mail_archives
+drwxr-xr-x 1 archives 0 1024 Nov 11 1997 patches
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 tech-tips
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 white_papers
diff --git a/chromium/net/data/ftp/dir-listing-ls-4.expected b/chromium/net/data/ftp/dir-listing-ls-4.expected
new file mode 100644
index 00000000000..0a49e5ecd84
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-4.expected
@@ -0,0 +1,80 @@
+-
+.welcome
+472
+2004
+6
+28
+0
+0
+
+d
+contributed-software
+-1
+1998
+3
+5
+0
+0
+
+d
+customer_support
+-1
+1997
+11
+11
+0
+0
+
+d
+docs
+-1
+1998
+12
+30
+0
+0
+
+d
+faq
+-1
+1998
+5
+8
+0
+0
+
+d
+mail_archives
+-1
+1997
+11
+11
+0
+0
+
+d
+patches
+-1
+1997
+11
+11
+0
+0
+
+d
+tech-tips
+-1
+1997
+11
+11
+0
+0
+
+d
+white_papers
+-1
+1997
+11
+11
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-5 b/chromium/net/data/ftp/dir-listing-ls-5
new file mode 100644
index 00000000000..87c42665ffd
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-5
@@ -0,0 +1 @@
+drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub
diff --git a/chromium/net/data/ftp/dir-listing-ls-5.expected b/chromium/net/data/ftp/dir-listing-ls-5.expected
new file mode 100644
index 00000000000..1cf3e9ae3f4
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-5.expected
@@ -0,0 +1,8 @@
+d
+pub
+-1
+2007
+2
+20
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-6 b/chromium/net/data/ftp/dir-listing-ls-6
new file mode 100644
index 00000000000..5cc802c8822
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-6
@@ -0,0 +1,7 @@
+total 14
+dr-xr-xr-x 7 0 1 512 Jun 19 2006 .
+dr-xr-xr-x 7 0 1 512 Jun 19 2006 ..
+dr-xr-xr-x 2 0 1 512 Mar 24 2003 bin
+dr-xr-xr-x 2 0 1 512 Mar 24 2003 etc
+dr-xr-xr-x 12 0 0 512 Apr 7 2009 pub
+dr-xr-xr-x 3 0 1 512 Mar 24 2003 usr
diff --git a/chromium/net/data/ftp/dir-listing-ls-6.expected b/chromium/net/data/ftp/dir-listing-ls-6.expected
new file mode 100644
index 00000000000..be30bc6f1e2
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-6.expected
@@ -0,0 +1,53 @@
+d
+.
+-1
+2006
+6
+19
+0
+0
+
+d
+..
+-1
+2006
+6
+19
+0
+0
+
+d
+bin
+-1
+2003
+3
+24
+0
+0
+
+d
+etc
+-1
+2003
+3
+24
+0
+0
+
+d
+pub
+-1
+2009
+4
+7
+0
+0
+
+d
+usr
+-1
+2003
+3
+24
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-7 b/chromium/net/data/ftp/dir-listing-ls-7
new file mode 100644
index 00000000000..93dd8045ca3
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-7
@@ -0,0 +1,6 @@
+-rw-r--r-- 1 0 100 3108 Mar 07 2001 00readme.html
+drwxr-xr-x 3 1164 100 4096 Oct 19 13:45 OCU
+lrwxrwxrwx 1 203 1 10 Jun 15 2006 año2000 -> ./urte2000
+drwxr-xr-x 2 0 0 4096 Mar 07 2001 bin
+drwxr-xr-x 2 0 100 4096 Mar 07 2001 dev
+drwxr-xr-x 3 0 100 4096 Apr 20 2005 doc
diff --git a/chromium/net/data/ftp/dir-listing-ls-7.expected b/chromium/net/data/ftp/dir-listing-ls-7.expected
new file mode 100644
index 00000000000..1e0e5d36f38
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-7.expected
@@ -0,0 +1,53 @@
+-
+00readme.html
+3108
+2001
+3
+7
+0
+0
+
+d
+OCU
+-1
+1994
+10
+19
+13
+45
+
+l
+año2000
+-1
+2006
+6
+15
+0
+0
+
+d
+bin
+-1
+2001
+3
+7
+0
+0
+
+d
+dev
+-1
+2001
+3
+7
+0
+0
+
+d
+doc
+-1
+2005
+4
+20
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-ls-8 b/chromium/net/data/ftp/dir-listing-ls-8
new file mode 100644
index 00000000000..92811047f08
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-8
@@ -0,0 +1 @@
+total 0
diff --git a/chromium/net/data/ftp/dir-listing-ls-8.expected b/chromium/net/data/ftp/dir-listing-ls-8.expected
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-8.expected
diff --git a/chromium/net/data/ftp/dir-listing-ls-9 b/chromium/net/data/ftp/dir-listing-ls-9
new file mode 100644
index 00000000000..89df17cb8ef
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-9
@@ -0,0 +1,4 @@
+total 510528
+-rw-r--r-- 1 nobody nogroup 174680068 Jun 4 23:20 Akademia Teatralna spot.mpg
+-rw-r--r-- 1 nobody nogroup 3447432 May 18 2009 Foo - Instrukcja_Obsługi.pdf
+-rw-r--r-- 1 nobody nogroup 23197684 Jun 9 13:36 Zdjecia.zip
diff --git a/chromium/net/data/ftp/dir-listing-ls-9.expected b/chromium/net/data/ftp/dir-listing-ls-9.expected
new file mode 100644
index 00000000000..98fe53777c5
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-ls-9.expected
@@ -0,0 +1,26 @@
+-
+Akademia Teatralna spot.mpg
+174680068
+1994
+6
+4
+23
+20
+
+-
+Foo - Instrukcja_Obsługi.pdf
+3447432
+2009
+5
+18
+0
+0
+
+-
+Zdjecia.zip
+23197684
+1994
+6
+9
+13
+36
diff --git a/chromium/net/data/ftp/dir-listing-netware-1 b/chromium/net/data/ftp/dir-listing-netware-1
new file mode 100644
index 00000000000..02a51676992
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-1
@@ -0,0 +1,3 @@
+total 0
+d [RWCEAFMS] ftpadmin 512 Jun 25 2007 pandora
+d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub
diff --git a/chromium/net/data/ftp/dir-listing-netware-1.expected b/chromium/net/data/ftp/dir-listing-netware-1.expected
new file mode 100644
index 00000000000..3084dbfe1bc
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-1.expected
@@ -0,0 +1,17 @@
+d
+pandora
+-1
+2007
+6
+25
+0
+0
+
+d
+pub
+-1
+2004
+1
+29
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-netware-2 b/chromium/net/data/ftp/dir-listing-netware-2
new file mode 100644
index 00000000000..13af27b7292
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-2
@@ -0,0 +1,4 @@
+total 0
+- [RWCEAFMS] AK101850 1328 Dec 27 2007 rootcert.der
+d [RWCEAFMS] Admin 512 Nov 13 07:51 Driver
+d [RWCEAFMS] AK101850 512 Nov 16 15:40 temp
diff --git a/chromium/net/data/ftp/dir-listing-netware-2.expected b/chromium/net/data/ftp/dir-listing-netware-2.expected
new file mode 100644
index 00000000000..e958a1a329e
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-2.expected
@@ -0,0 +1,26 @@
+-
+rootcert.der
+1328
+2007
+12
+27
+0
+0
+
+d
+Driver
+-1
+1994
+11
+13
+7
+51
+
+d
+temp
+-1
+1993
+11
+16
+15
+40
diff --git a/chromium/net/data/ftp/dir-listing-netware-3 b/chromium/net/data/ftp/dir-listing-netware-3
new file mode 100644
index 00000000000..1d24bccd574
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-3
@@ -0,0 +1,22 @@
+total 0
+- [RWCEAFMS] bodi 1588 Oct 28 22:21 leltar.as
+- [RWCEAFMS] bodi 19440 Oct 28 22:21 leltarVizellatas.as
+- [RWCEAFMS] bodi 38795 Oct 28 22:21 leltarVizelvezetes.as
+- [RWCEAFMS] bodi 227 Jan 06 2010 Olvass_el.txt
+- [RWCEAFMS] dora 19300888 Jan 15 2011 vegleges.pdf
+d [RWCEAFMS] Fulop 512 Nov 10 2011 DHI
+d [RWCEAFMS] bodi 512 Aug 02 11:48 English
+d [RWCEAFMS] clement 512 Jun 24 2011 Floodrisk
+d [RWCEAFMS] bodi 512 Mar 10 14:53 HC_MIR_FD
+d [RWCEAFMS] bodi 512 Oct 08 15:59 HCWP614-11
+d [RWCEAFMS] knolmar 512 Mar 26 15:10 Mike
+d [RWCEAFMS] daniel 512 May 17 2010 NVP anyagok
+d [RWCEAFMS] bodi 512 Sep 15 2010 Oktatas
+d [RWCEAFMS] buzas 512 Oct 09 12:47 processing_modflow
+d [RWCEAFMS] peterbud 512 Mar 24 2010 Prospektushoz
+d [RWCEAFMS] bodi 512 Sep 05 2011 sewcad
+d [RWCEAFMS] bodi 512 May 09 08:50 szakmernok
+d [RWCEAFMS] daniel 512 Aug 03 18:53 tomi
+d [RWCEAFMS] bodi 512 Sep 20 21:10 Virtualis-GEP
+d [RWCEAFMS] clement 512 Sep 26 12:31 Vizrajzi evkonyvek
+d [RWCEAFMS] darabos 512 Jul 09 2011 VKKI-Tanulmanyok
diff --git a/chromium/net/data/ftp/dir-listing-netware-3.expected b/chromium/net/data/ftp/dir-listing-netware-3.expected
new file mode 100644
index 00000000000..44393e13564
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-netware-3.expected
@@ -0,0 +1,188 @@
+-
+leltar.as
+1588
+1994
+10
+28
+22
+21
+
+-
+leltarVizellatas.as
+19440
+1994
+10
+28
+22
+21
+
+-
+leltarVizelvezetes.as
+38795
+1994
+10
+28
+22
+21
+
+-
+Olvass_el.txt
+227
+2010
+1
+6
+0
+0
+
+-
+vegleges.pdf
+19300888
+2011
+1
+15
+0
+0
+
+d
+DHI
+-1
+2011
+11
+10
+0
+0
+
+d
+English
+-1
+1994
+8
+2
+11
+48
+
+d
+Floodrisk
+-1
+2011
+6
+24
+0
+0
+
+d
+HC_MIR_FD
+-1
+1994
+3
+10
+14
+53
+
+d
+HCWP614-11
+-1
+1994
+10
+8
+15
+59
+
+d
+Mike
+-1
+1994
+3
+26
+15
+10
+
+d
+NVP anyagok
+-1
+2010
+5
+17
+0
+0
+
+d
+Oktatas
+-1
+2010
+9
+15
+0
+0
+
+d
+processing_modflow
+-1
+1994
+10
+9
+12
+47
+
+d
+Prospektushoz
+-1
+2010
+3
+24
+0
+0
+
+d
+sewcad
+-1
+2011
+9
+5
+0
+0
+
+d
+szakmernok
+-1
+1994
+5
+9
+8
+50
+
+d
+tomi
+-1
+1994
+8
+3
+18
+53
+
+d
+Virtualis-GEP
+-1
+1994
+9
+20
+21
+10
+
+d
+Vizrajzi evkonyvek
+-1
+1994
+9
+26
+12
+31
+
+d
+VKKI-Tanulmanyok
+-1
+2011
+7
+9
+0
+0
diff --git a/chromium/net/data/ftp/dir-listing-os2-1 b/chromium/net/data/ftp/dir-listing-os2-1
new file mode 100644
index 00000000000..5d6003166a1
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-os2-1
@@ -0,0 +1,9 @@
+ 0 DIR 08-08-11 01:47 Archive
+ 0 DIR 01-13-11 10:18 BootCD
+ 0 DIR 10-29-10 23:20 BootUSB512
+ 65201 A 08-04-11 10:24 csort.exe
+ 0 DIR 08-04-11 16:07 misc
+ 0 DIR 06-24-11 01:18 moveton
+ 1031106 A 08-08-11 01:47 os2krnlSVN3370_unoff.zip
+ 603 A 01-08-11 14:18 SSE_TEST.EXE
+ 91018 A 04-07-11 18:26 TETRIS.ZIP
diff --git a/chromium/net/data/ftp/dir-listing-os2-1.expected b/chromium/net/data/ftp/dir-listing-os2-1.expected
new file mode 100644
index 00000000000..11248c65317
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-os2-1.expected
@@ -0,0 +1,80 @@
+d
+Archive
+-1
+2011
+8
+8
+1
+47
+
+d
+BootCD
+-1
+2011
+1
+13
+10
+18
+
+d
+BootUSB512
+-1
+2010
+10
+29
+23
+20
+
+-
+csort.exe
+65201
+2011
+8
+4
+10
+24
+
+d
+misc
+-1
+2011
+8
+4
+16
+7
+
+d
+moveton
+-1
+2011
+6
+24
+1
+18
+
+-
+os2krnlSVN3370_unoff.zip
+1031106
+2011
+8
+8
+1
+47
+
+-
+SSE_TEST.EXE
+603
+2011
+1
+8
+14
+18
+
+-
+TETRIS.ZIP
+91018
+2011
+4
+7
+18
+26
diff --git a/chromium/net/data/ftp/dir-listing-vms-1 b/chromium/net/data/ftp/dir-listing-vms-1
new file mode 100644
index 00000000000..ca7cdf1edff
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-1
@@ -0,0 +1,19 @@
+
+
+
+Directory ANONYMOUS_ROOT:[000000]
+
+.WELCOME;1 2 13-FEB-2002 23:32:40.47
+DECUS.DIR;1 1 9-MAY-2001 22:18:51.69
+INFORMATION.DIR;1 1 9-MAY-2001 22:23:42.78
+MADGOAT.DIR;1 2 9-MAY-2001 22:23:44.85
+MAIL_ARCHIVES.DIR;1
+ 1 13-DEC-2005 08:45:27.56
+MOZILLA.DIR;1 1 21-JUN-2001 14:57:51.38
+README.TXT;4 2 18-APR-2000 10:40:39.90
+SSH.DIR;1 1 22-JUN-2002 15:11:12.71
+SUPPORT.DIR;1 3 9-MAY-2001 22:29:45.02
+TCPWARE.DIR;1 1 9-MAY-2001 23:34:10.92
+VMS-FREEWARE.DIR;1 2 9-MAY-2001 23:58:31.39
+
+Total of 11 files, 17 blocks.
diff --git a/chromium/net/data/ftp/dir-listing-vms-1.expected b/chromium/net/data/ftp/dir-listing-vms-1.expected
new file mode 100644
index 00000000000..8e9dddd485b
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-1.expected
@@ -0,0 +1,98 @@
+-
+.welcome
+1024
+2002
+2
+13
+23
+32
+
+d
+decus
+-1
+2001
+5
+9
+22
+18
+
+d
+information
+-1
+2001
+5
+9
+22
+23
+
+d
+madgoat
+-1
+2001
+5
+9
+22
+23
+
+d
+mail_archives
+-1
+2005
+12
+13
+8
+45
+
+d
+mozilla
+-1
+2001
+6
+21
+14
+57
+
+-
+readme.txt
+1024
+2000
+4
+18
+10
+40
+
+d
+ssh
+-1
+2002
+6
+22
+15
+11
+
+d
+support
+-1
+2001
+5
+9
+22
+29
+
+d
+tcpware
+-1
+2001
+5
+9
+23
+34
+
+d
+vms-freeware
+-1
+2001
+5
+9
+23
+58
diff --git a/chromium/net/data/ftp/dir-listing-vms-2 b/chromium/net/data/ftp/dir-listing-vms-2
new file mode 100644
index 00000000000..7fc20a93f9c
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-2
@@ -0,0 +1,35 @@
+
+Directory SYS$SYSDEVICE:[ANONYMOUS]
+
+ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)
+BOINC.DIR;1 1/16 29-DEC-2005 21:33:21 [SYSTEM] (RWE,RWE,RE,RE)
+BZIP2.DIR;1 1/16 27-SEP-2005 19:45:39 [SYSTEM] (RWE,RWE,RE,RE)
+CDRTOOLS.DIR;1 3/16 10-MAR-2005 17:31:44 [SYSTEM] (RWE,RWE,RE,RE)
+DIFFUTILS.DIR;1 1/16 23-JUN-2007 23:04:21 [SYSTEM] (RWE,RWE,RE,RE)
+DTSS_NTP.DIR;1 2/16 25-SEP-2000 21:03:28 [SYSTEM] (RWE,RWE,RE,RE)
+FIXREC.DIR;1 1/16 20-DEC-2003 10:57:22 [SYSTEM] (RWE,RWE,RE,RE)
+GNUPG.DIR;1 1/16 9-AUG-2006 02:11:51 [SYSTEM] (RWE,RWE,RE,RE)
+GZIP.DIR;1 1/16 5-JUL-2006 21:59:45 [SYSTEM] (RWE,RWE,RE,RE)
+INFO-ZIP.DIR;1 15/16 20-SEP-2004 21:27:27 [SYSTEM] (RWE,RWE,RE,RE)
+INPUT.DIR;1 1/16 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] (RWE,RWE,RWE,RWE)
+KERMIT.DIR;1 1/16 25-FEB-2006 12:22:34 [SYSTEM] (RWE,RWE,RE,RE)
+LOGIN.COM;2 1/16 28-SEP-2006 09:20:32 [SYSTEM] (RWED,RWED,RE,RE)
+MISC.DIR;1 6/16 12-DEC-1999 17:31:56 [SYSTEM] (RWE,RWE,RE,RE)
+MMK.DIR;1 1/16 30-SEP-2009 08:06:26 [SYSTEM] (RWE,RWE,RE,RE)
+MOZ_TEST.DIR;1 1/16 8-APR-2008 17:12:53 [SYSTEM] (RWE,RWE,RE,RE)
+MPACK.DIR;1 1/16 21-AUG-2009 10:28:57 [SYSTEM] (RWE,RWE,RE,RE)
+MTOOLS.DIR;1 1/16 14-MAR-2006 15:05:01 [SYSTEM] (RWE,RWE,RE,RE)
+OPENSSL.DIR;1 1/16 12-JAN-2009 08:42:56 [SYSTEM] (RWE,RWE,RE,RE)
+PGP.DIR;1 1/16 19-SEP-1999 16:39:04 [SYSTEM] (RWE,RWE,RE,RE)
+PICS.DIR;1 No privilege for attempted operation
+QREADCD.DIR;1 1/16 29-SEP-2004 20:32:38 [SYSTEM] (RWE,RWE,RE,RE)
+RZSPINUP.DIR;1 1/16 24-JUL-2004 21:34:12 [SYSTEM] (RWE,RWE,RE,RE)
+TEST.DIR;1 1/16 5-NOV-2008 21:59:10 [SYSTEM] (RWE,RWE,RE,RE)
+VIM.DIR;1 1/16 30-APR-2005 16:32:56 [SYSTEM] (RWE,RWE,RE,RE)
+VMSTAR.DIR;1 1/16 7-JUN-2007 09:36:04 [SYSTEM] (RWE,RWE,RE,RE)
+WELCOME.TXT;3 1/16 12-MAR-2005 08:45:28 [SYSTEM] (RWED,RWED,RE,RE)
+WGET.DIR;1 3/16 17-AUG-1999 20:41:54 [SYSTEM] (RWE,RWE,RE,RE)
+WGET_TEST.DIR;1 1/16 13-JUN-2006 21:29:27 [SYSTEM] (RWE,RWE,RE,RE)
+WPUT.DIR;1 1/16 9-DEC-2004 20:16:46 [SYSTEM] (RWE,RWE,RE,RE)
+
+Total of 30 files, 53/464 blocks
diff --git a/chromium/net/data/ftp/dir-listing-vms-2.expected b/chromium/net/data/ftp/dir-listing-vms-2.expected
new file mode 100644
index 00000000000..92ea55ba4e7
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-2.expected
@@ -0,0 +1,260 @@
+-
+announce.txt
+512
+2005
+3
+12
+8
+44
+
+d
+boinc
+-1
+2005
+12
+29
+21
+33
+
+d
+bzip2
+-1
+2005
+9
+27
+19
+45
+
+d
+cdrtools
+-1
+2005
+3
+10
+17
+31
+
+d
+diffutils
+-1
+2007
+6
+23
+23
+4
+
+d
+dtss_ntp
+-1
+2000
+9
+25
+21
+3
+
+d
+fixrec
+-1
+2003
+12
+20
+10
+57
+
+d
+gnupg
+-1
+2006
+8
+9
+2
+11
+
+d
+gzip
+-1
+2006
+7
+5
+21
+59
+
+d
+info-zip
+-1
+2004
+9
+20
+21
+27
+
+d
+input
+-1
+1999
+3
+4
+22
+14
+
+d
+kermit
+-1
+2006
+2
+25
+12
+22
+
+-
+login.com
+512
+2006
+9
+28
+9
+20
+
+d
+misc
+-1
+1999
+12
+12
+17
+31
+
+d
+mmk
+-1
+2009
+9
+30
+8
+6
+
+d
+moz_test
+-1
+2008
+4
+8
+17
+12
+
+d
+mpack
+-1
+2009
+8
+21
+10
+28
+
+d
+mtools
+-1
+2006
+3
+14
+15
+5
+
+d
+openssl
+-1
+2009
+1
+12
+8
+42
+
+d
+pgp
+-1
+1999
+9
+19
+16
+39
+
+d
+qreadcd
+-1
+2004
+9
+29
+20
+32
+
+d
+rzspinup
+-1
+2004
+7
+24
+21
+34
+
+d
+test
+-1
+2008
+11
+5
+21
+59
+
+d
+vim
+-1
+2005
+4
+30
+16
+32
+
+d
+vmstar
+-1
+2007
+6
+7
+9
+36
+
+-
+welcome.txt
+512
+2005
+3
+12
+8
+45
+
+d
+wget
+-1
+1999
+8
+17
+20
+41
+
+d
+wget_test
+-1
+2006
+6
+13
+21
+29
+
+d
+wput
+-1
+2004
+12
+9
+20
+16
diff --git a/chromium/net/data/ftp/dir-listing-vms-3 b/chromium/net/data/ftp/dir-listing-vms-3
new file mode 100644
index 00000000000..3bda7ff5bd1
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-3
@@ -0,0 +1,3 @@
+
+
+Total of 0 blocks in 0 files in 0 directories.
diff --git a/chromium/net/data/ftp/dir-listing-vms-3.expected b/chromium/net/data/ftp/dir-listing-vms-3.expected
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-3.expected
diff --git a/chromium/net/data/ftp/dir-listing-vms-4 b/chromium/net/data/ftp/dir-listing-vms-4
new file mode 100644
index 00000000000..0954441c229
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-4
@@ -0,0 +1,15 @@
+
+EISNER$DRA3:[DECUSERVE_USER.JOHNDOE]
+
+$MAIN.TPU$JOURNAL;1 1 4-NOV-2009 05:59 [JOHNDOE] (RWED,RWED,,)
+.WELCOME;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
+EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
+FILE.;1 1 4-NOV-2009 08:59 [JOHNDOE] (RWED,RWED,,)
+FTP_SERVER.LOG;12 0 4-NOV-2009 09:12 [JOHNDOE] (RWED,RWED,,)
+LOGIN.COM;1 2 4-NOV-2009 05:58 [JOHNDOE] (RWED,RWED,,)
+NOTES$NOTEBOOK.NOTE;1
+ 36 4-NOV-2009 05:55 [DECUSERVE] (RWE,RWE,,)
+TEST.DIR;1 1 4-NOV-2009 08:15 [JOHNDOE] (RWE,RWE,,)
+
+
+Total of 43 blocks in 8 files.
diff --git a/chromium/net/data/ftp/dir-listing-vms-4.expected b/chromium/net/data/ftp/dir-listing-vms-4.expected
new file mode 100644
index 00000000000..a638df95ad1
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-4.expected
@@ -0,0 +1,71 @@
+-
+$main.tpu$journal
+512
+2009
+11
+4
+5
+59
+
+-
+.welcome
+512
+2009
+11
+4
+6
+2
+
+-
+example.txt
+512
+2009
+11
+4
+6
+2
+
+-
+file.
+512
+2009
+11
+4
+8
+59
+
+-
+ftp_server.log
+0
+2009
+11
+4
+9
+12
+
+-
+login.com
+1024
+2009
+11
+4
+5
+58
+
+-
+notes$notebook.note
+18432
+2009
+11
+4
+5
+55
+
+d
+test
+-1
+2009
+11
+4
+8
+15
diff --git a/chromium/net/data/ftp/dir-listing-vms-5 b/chromium/net/data/ftp/dir-listing-vms-5
new file mode 100644
index 00000000000..d769993de8e
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-5
@@ -0,0 +1,12 @@
+
+SYS:[ANON_LOGS]
+
+FTP_SERVER.LOG;1682 0 27-NOV-2009 14:35 [NET,ANONYMOUS] (RWED,RWED,,)
+FTP_SERVER_LOG.KEEP;11400
+ 2 19-DEC-1994 15:40 [NET,ANONYMOUS] (RWED,RWED,,)
+FTP_SERVER_LOG.SEARCH;1
+ 274 7-DEC-1993 15:54 [NET,ANONYMOUS] (RWED,RWED,,)
+TESTLOG.DAT;1 0 27-APR-1995 13:18 [NET,ANONYMOUS] (RWED,RWED,,)
+
+
+Total of 276 blocks in 4 files.
diff --git a/chromium/net/data/ftp/dir-listing-vms-5.expected b/chromium/net/data/ftp/dir-listing-vms-5.expected
new file mode 100644
index 00000000000..91594986c41
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-5.expected
@@ -0,0 +1,35 @@
+-
+ftp_server.log
+0
+2009
+11
+27
+14
+35
+
+-
+ftp_server_log.keep
+1024
+1994
+12
+19
+15
+40
+
+-
+ftp_server_log.search
+140288
+1993
+12
+7
+15
+54
+
+-
+testlog.dat
+0
+1995
+4
+27
+13
+18
diff --git a/chromium/net/data/ftp/dir-listing-vms-6 b/chromium/net/data/ftp/dir-listing-vms-6
new file mode 100644
index 00000000000..6143c82e14a
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-6
@@ -0,0 +1,13 @@
+
+ANONYMOUS:[000000]
+
+ANON_FTP_LOG.LOG;23
+ <%SYSTEM-F-NOPRIV, insufficient privilege or object protection violation>
+EK-SMCPR-UG-A01.PDF;1
+ <%SYSTEM-F-NOPRIV, insufficient privilege or object protection violation>
+INCOMING.DIR;1 1 16-FEB-2009 00:49 [ANONY,ANONYMOUS] (RWE,RWE,RE,RE)
+INPUT.DIR;1 1 25-JAN-2004 07:11 [ANONY,ANONYMOUS] (RWED,RWE,R,R)
+LOGIN.COM;1 8 25-NOV-2003 20:01 [ANONY,ANONYMOUS] (RWED,RE,RE,RE)
+PUB.DIR;1 2 25-JAN-2004 07:11 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+
+Total of 6 Files, 12 Blocks.
diff --git a/chromium/net/data/ftp/dir-listing-vms-6.expected b/chromium/net/data/ftp/dir-listing-vms-6.expected
new file mode 100644
index 00000000000..c3634c169af
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-6.expected
@@ -0,0 +1,35 @@
+d
+incoming
+-1
+2009
+2
+16
+0
+49
+
+d
+input
+-1
+2004
+1
+25
+7
+11
+
+-
+login.com
+4096
+2003
+11
+25
+20
+1
+
+d
+pub
+-1
+2004
+1
+25
+7
+11
diff --git a/chromium/net/data/ftp/dir-listing-vms-7 b/chromium/net/data/ftp/dir-listing-vms-7
new file mode 100644
index 00000000000..d9ad20ba97b
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-7
@@ -0,0 +1,4 @@
+
+ANONYMOUS:[INCOMING]
+
+*.*;0 <%RMS-E-FNF, file not found>
diff --git a/chromium/net/data/ftp/dir-listing-vms-7.expected b/chromium/net/data/ftp/dir-listing-vms-7.expected
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-7.expected
diff --git a/chromium/net/data/ftp/dir-listing-vms-8 b/chromium/net/data/ftp/dir-listing-vms-8
new file mode 100644
index 00000000000..d805ab50b72
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-8
@@ -0,0 +1,37 @@
+
+ANONYMOUS:[PUB]
+
+ALPHA0721.ISO-GZ;1 383594 14-MAY-2008 08:52 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
+AXP.DIR;1 1 25-JAN-2004 07:11 [SYSTEM] (RWE,RE,RE,RE)
+BENCHMARKS.DIR;1 1 10-JUN-2009 19:32 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+EULER.DIR;1 1 10-JUN-2009 20:43 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+GAMES.DIR;1 1 5-FEB-2009 18:43 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+HOUSE.DIR;1 1 27-MAR-2008 08:02 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+HTML.DIR;1 1 25-JAN-2004 07:12 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+I64.DIR;1 1 22-JUN-2008 20:07 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+ITANIUM.DIR;1 1 12-JAN-2008 06:56 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+J2VMS.DIR;1 1 17-NOV-2008 23:26 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+MISC.DIR;1 1 25-JAN-2004 07:12 [SYSTEM] (RWE,RE,RE,RE)
+OPENSOURCE.DIR;1 1 8-SEP-2009 19:29 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+PHONE.DIR;1 2 15-DEC-2008 21:03 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+PLIDOCS.DIR;1 1 25-JAN-2004 07:12 [SYSTEM] (RWE,RE,RE,RE)
+PLI_HTML_DOCS-BCK.ZIP;1
+ 2992 25-MAR-2009 18:27 [ANONY,ANONYMOUS] (RWED,RWED,RE,RE)
+PLI_HTML_DOCS.BCK;2
+ 9639 25-MAR-2009 18:23 [VMSUSER,TSNEDDON] (RE,RWED,RE,RE)
+RESET_BACKUP_SAVESET_ATTRIBUTES.COM;1
+ 3 14-JUN-2009 22:46 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
+RESET_BACKUP_SAVESET_ATTRIBUTES.TXT;1
+ 3 28-AUG-2001 07:54 [SYSTEM] (RE,RE,RE,RE)
+rexx.DIR;1 1 11-AUG-2009 18:06 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+SDLEXT.DIR;1 1 19-DEC-2008 08:35 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+TOOLS.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
+TRU64.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
+VAX.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
+VMS721.ISO;2 ****** 6-MAY-2008 09:29 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
+WINDOWS.DIR;1 1 26-AUG-2011 05:53 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+XMLRTL.DIR;1 1 3-JUN-2009 17:24 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
+_SITE.CSS;8 5 19-MAR-2009 23:44 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
+_VWCMS.CSS;5 14 19-MAR-2009 23:38 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
+
+Total of 28 Files, 1557541 Blocks.
diff --git a/chromium/net/data/ftp/dir-listing-vms-8.expected b/chromium/net/data/ftp/dir-listing-vms-8.expected
new file mode 100644
index 00000000000..17dd8b58275
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-vms-8.expected
@@ -0,0 +1,251 @@
+-
+alpha0721.iso-gz
+196400128
+2008
+5
+14
+8
+52
+
+d
+axp
+-1
+2004
+1
+25
+7
+11
+
+d
+benchmarks
+-1
+2009
+6
+10
+19
+32
+
+d
+euler
+-1
+2009
+6
+10
+20
+43
+
+d
+games
+-1
+2009
+2
+5
+18
+43
+
+d
+house
+-1
+2008
+3
+27
+8
+2
+
+d
+html
+-1
+2004
+1
+25
+7
+12
+
+d
+i64
+-1
+2008
+6
+22
+20
+7
+
+d
+itanium
+-1
+2008
+1
+12
+6
+56
+
+d
+j2vms
+-1
+2008
+11
+17
+23
+26
+
+d
+misc
+-1
+2004
+1
+25
+7
+12
+
+d
+opensource
+-1
+2009
+9
+8
+19
+29
+
+d
+phone
+-1
+2008
+12
+15
+21
+3
+
+d
+plidocs
+-1
+2004
+1
+25
+7
+12
+
+-
+pli_html_docs-bck.zip
+1531904
+2009
+3
+25
+18
+27
+
+-
+pli_html_docs.bck
+4935168
+2009
+3
+25
+18
+23
+
+-
+reset_backup_saveset_attributes.com
+1536
+2009
+6
+14
+22
+46
+
+-
+reset_backup_saveset_attributes.txt
+1536
+2001
+8
+28
+7
+54
+
+d
+rexx
+-1
+2009
+8
+11
+18
+6
+
+d
+sdlext
+-1
+2008
+12
+19
+8
+35
+
+d
+tools
+-1
+2004
+1
+25
+7
+13
+
+d
+tru64
+-1
+2004
+1
+25
+7
+13
+
+d
+vax
+-1
+2004
+1
+25
+7
+13
+
+-
+vms721.iso
+-1
+2008
+5
+6
+9
+29
+
+d
+windows
+-1
+2011
+8
+26
+5
+53
+
+d
+xmlrtl
+-1
+2009
+6
+3
+17
+24
+
+-
+_site.css
+2560
+2009
+3
+19
+23
+44
+
+-
+_vwcms.css
+7168
+2009
+3
+19
+23
+38
diff --git a/chromium/net/data/ftp/dir-listing-windows-1 b/chromium/net/data/ftp/dir-listing-windows-1
new file mode 100644
index 00000000000..a8ff16d73a4
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-windows-1
@@ -0,0 +1,2 @@
+11-02-09 05:32PM <DIR> NT
+01-06-09 02:42PM 458 Readme.txt
diff --git a/chromium/net/data/ftp/dir-listing-windows-1.expected b/chromium/net/data/ftp/dir-listing-windows-1.expected
new file mode 100644
index 00000000000..068c2969860
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-windows-1.expected
@@ -0,0 +1,17 @@
+d
+NT
+-1
+2009
+11
+2
+17
+32
+
+-
+Readme.txt
+458
+2009
+1
+6
+14
+42
diff --git a/chromium/net/data/ftp/dir-listing-windows-2 b/chromium/net/data/ftp/dir-listing-windows-2
new file mode 100644
index 00000000000..f62fea49959
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-windows-2
@@ -0,0 +1,16 @@
+05-18-09 11:07AM <DIR> beta
+01-06-09 04:25PM <DIR> cdrom
+01-06-09 02:38PM 129 checkdownload.html
+08-19-09 11:23AM <DIR> Digital_Media_Player
+12-29-08 10:27PM <DIR> LiveUpdate
+08-20-09 04:34PM <DIR> mb
+01-06-09 02:38PM 83933 Mb.eng
+11-04-09 03:42PM <DIR> misc
+06-12-09 04:20PM 462 Path.idx
+12-30-08 07:41AM <DIR> PDA
+01-06-09 02:38PM 2625 Platform.idx
+12-30-08 07:41AM <DIR> print
+01-06-09 02:42PM 458 Readme.txt
+10-28-09 02:27PM <DIR> server
+12-30-08 08:59AM <DIR> vga
+02-03-09 04:42PM 1951823 VH203_FR.pdf
diff --git a/chromium/net/data/ftp/dir-listing-windows-2.expected b/chromium/net/data/ftp/dir-listing-windows-2.expected
new file mode 100644
index 00000000000..be951fac16e
--- /dev/null
+++ b/chromium/net/data/ftp/dir-listing-windows-2.expected
@@ -0,0 +1,143 @@
+d
+beta
+-1
+2009
+5
+18
+11
+7
+
+d
+cdrom
+-1
+2009
+1
+6
+16
+25
+
+-
+checkdownload.html
+129
+2009
+1
+6
+14
+38
+
+d
+Digital_Media_Player
+-1
+2009
+8
+19
+11
+23
+
+d
+LiveUpdate
+-1
+2008
+12
+29
+22
+27
+
+d
+mb
+-1
+2009
+8
+20
+16
+34
+
+-
+Mb.eng
+83933
+2009
+1
+6
+14
+38
+
+d
+misc
+-1
+2009
+11
+4
+15
+42
+
+-
+Path.idx
+462
+2009
+6
+12
+16
+20
+
+d
+PDA
+-1
+2008
+12
+30
+7
+41
+
+-
+Platform.idx
+2625
+2009
+1
+6
+14
+38
+
+d
+print
+-1
+2008
+12
+30
+7
+41
+
+-
+Readme.txt
+458
+2009
+1
+6
+14
+42
+
+d
+server
+-1
+2009
+10
+28
+14
+27
+
+d
+vga
+-1
+2008
+12
+30
+8
+59
+
+-
+VH203_FR.pdf
+1951823
+2009
+2
+3
+16
+42
diff --git a/chromium/net/data/proxy_resolver_perftest/no-ads.pac b/chromium/net/data/proxy_resolver_perftest/no-ads.pac
new file mode 100644
index 00000000000..e55fa0ffd7e
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_perftest/no-ads.pac
@@ -0,0 +1,1362 @@
+//////////////////////////////////////////////////////////////////////////////
+//
+// John's No-ADS proxy auto configuration script
+// http://www.schooner.com/~loverso/no-ads/
+// loverso@schooner.com
+// Questions/help web forum at http://www.network54.com/Hide/Forum/223428
+//
+// Copyright 1996-2004, John LoVerso. All Rights Reserved.
+//
+// Permission is given to use and distribute this file, as long as this
+// copyright message and author notice are not removed.
+//
+// No responsibility is taken for any errors on inaccuracies inherent
+// either to the comments or the code of this program, but if reported
+// to me, then an attempt will be made to fix them.
+//
+// ("no monies exchanged" in Copyright clause removed 11/2001)
+//
+var noadsver = "$Id: no-ads.pac,v 5.70 2007/05/11 16:56:01 loverso Exp loverso $";
+
+// ****
+// **** If you do not use a proxy to access the Internet, then the following
+// **** line is already fine.
+// ****
+// **** If you use an a proxy to access the Internet, as required by your
+// **** ISP or firewall, then change the line below, replacing
+// **** "DIRECT" with "PROXY hostname:port", using the correct hostname:port
+// **** for your proxy server.
+// ****
+var normal = "DIRECT";
+
+// ***
+// *** If you are not using a blackhold proxy, then you can leave this
+// *** setting as is.
+// ***
+// *** Otherwise, update the next line with the correct hostname:port
+// *** of your blackhole proxy server. If you are using Larry Wang's
+// *** BHP for Windows, you need to change the "0.0.0.0" to "127.0.0.1"
+// ***
+var blackhole = "PROXY 0.0.0.0:3421";
+
+// ***
+// *** If you need a different proxy to access local/internal hosts vs.
+// *** the rest of the Internet, set 'localproxy' to that value. Otherwise,
+// *** 'localproxy' defaults to the same value as 'normal', so you do
+// *** not need to change anything in the normal case.
+// ***
+// *** Some typical cases:
+// *** - 'normal' might be one proxy, and 'localproxy' might be another
+// *** - 'normal' might be a proxy, and 'localproxy' might be "DIRECT"
+// ***
+// *** You will also need to change the LOCAL section below by adding
+// *** rules to match your local/internal hosts.
+// ***
+var localproxy = normal;
+
+// ***
+// *** 'bypass' is the preferred proxy setting for when no-ads is inactive.
+// *** Either use '= normal' or '= localproxy' (or perhaps just "DIRECT").
+// *** This only matters when you need to use a localproxy.
+// *** (You probably don't need to care about this)
+// ***
+var bypass = normal;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// This simple kludge uses a mechanism built into most browsers (IE, Netscape,
+// Mozilla, Firefox, and Opera) on most platforms to block connections to
+// banner ad servers.
+//
+// This mechanism uses the "proxy auto configuration" to blackhole requests
+// to load ad images without forcing all your traffic through an ad-blocking
+// proxy server. Of course, unlike ad-blocking proxy servers, this does not
+// otherwise not strip cookies.
+//
+// "Proxy auto configuration" invokes the JavaScript FindProxyForURL function
+// below each time your browser requests a URL. This works even if you have
+// JavaScript otherwise disabled in your browser! (Which you should!)
+//
+
+//
+// Send me your additions or comments. I'll credit you in the file.
+// (But I've removed all email addresses to stop spam harvesters).
+//
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// These are the basic steps needed to use "no-ads.pac".
+// Detailed instructions follow below!
+//
+// 1. Save this as a file (no-ads.pac) on your local disk
+// (or, add it to your home page, if you have one)
+// 2. Select a no-ads "blackhole".
+// 3. Configure your browser to use this file as its auto proxy configuration.
+// 4. Clear your browser's cache
+// (or else it may still show you ads it has saved on your disk).
+//
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 1. SAVE THIS FILE
+//
+// Copy this file to your local machine; use your home directory (UNIX)
+// or your Desktop or C:\ directory (Windows).
+//
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 2. SELECT A NO-ADS BLACKHOLE
+//
+// You can skip this section if you are using any version of Internet Explorer.
+// You can also skip this section for Netscape 7.1, Mozilla 1.4, or
+// Firefox 1.0 (or later), as they include PAC failover support (but do
+// read the note in section "2a" below).
+//
+//
+// The basic trick of no-ads is to match the site or URL of annoying web content
+// and tell your browser to use a proxy that will deny loading of that resource
+// (image, page, etc).
+//
+// A "black-hole" proxy server is one that always denies loading a web page.
+// ("send it off to a blackhole").
+//
+// When you initially get "no-ads.pac", it is using this as the blackhole:
+//
+// "PROXY 0.0.0.0:3421"
+//
+// This says to use the local host at a port which nothing should be listening
+// on. Thus, this is "a server that doesn't repond."
+//
+// This is a good default for all systems, and especially Windows.
+// However, if you are using the Blackhole Proxy Server on Windows,
+// be sure to change it to "PROXY 127.0.0.1:3421"
+//
+//
+// Some possibilities for the blackhole:
+//
+// a. A server that doesn't respond.
+//
+// *** This works for all versions of Internet Explorer.
+// *** This mostly works for Mozilla, Firefox, and Netscape.
+//
+// If you do nothing, then this is configured to direct annoying
+// content to the proxy running on your own host at port 3421.
+// Since you shouldn't have anything running on that port, that
+// connection will timeout and the annoying content will never be
+// loaded.
+//
+// Older versions of Netscape wait to connect to the proxy server
+// (usually it needs to load part of the image to layout the web
+// page), and then asks if you want to disable the proxy that
+// doesn't answer.
+//
+// Older versions of Mozilla will give an alert saying it couldn't
+// connect to the proxy server.
+//
+// Mozilla 1.4+, Firefox 1.0+ and Netscape 7.1 will only give
+// you this alert if the whole page being display is blocked,
+// rather than just an image on that page. Thus, I still
+// recommend a blackhole proxy even though it isn't needed.
+//
+// Opera will disable your auto proxy config if the proxy server
+// doesn't respond.
+//
+// IE doesn't care that the proxy server isn't responding. As
+// this avoids a connection for annoying content, it is fastest.
+//
+// b. A simple, blackhole server
+//
+// When needed, I run a simple "server" at port 3421 that denies
+// all requests. Some options you can use for this:
+//
+// - On Windows, you can try Larry Wang's black-hole proxy program:
+//
+// http://leisuresuit10.tripod.com/BlackHoleProxy/
+//
+// I can not vouch that his binaries are virus free, but he does
+// offer the source code.
+//
+// - I use this shell script on UNIX; it is invoked via inetd.
+// /usr/local/lib/noproxy:
+//
+// #!/bin/sh
+// read a
+// read b
+// echo HTTP/1.0 501 No Ads Accepted
+// echo ""
+// exit
+//
+// Add this line to inetd.conf ('kill -HUP' inetd afterwards):
+//
+// 3421 stream tcp nowait nobody /usr/local/lib/noproxy noproxy
+//
+// This simple script doesn't work on Linux because of the
+// (IMHO) broken way its TCP stack works. See the bottom of
+// http://www.schooner.com/~loverso/no-ads/ for a complete copy
+// of the `noproxy' shell script.
+//
+// If always exec'ing a shell was expensive on your computer
+// (it isn't on mine), then you could use a "wait"-style Perl
+// script that would accept() incoming connections.
+//
+// - Sean Burke has a black-hole proxy written in Perl script:
+//
+// http://www.speech.cs.cmu.edu/~sburke/pub/black_hole_http_server.pl
+// (This is a standalone server, not run from inetd).
+//
+// e. A trick: use an HTTP/1.0 non-proxy server
+//
+// An HTTP/1.0 non-proxy server will return a 501 error when
+// given a proxy request. Thus, just use the address of your
+// local intranet web server as your blackhole PROXY.
+// The downside of this is that it will probably also log an
+// error, which wastes a small amount of resources.
+//
+// ***
+// *** Be sure to update the "blackhole" variable above with a setting of
+// *** "PROXY hostname:port" that matches your blackhole server!!
+// ***
+//
+// ***
+// *** If you already use a proxy server to access the WWW,
+// *** change the "normal" variable above from "DIRECT" to
+// *** be "PROXY proxy:port" to match your proxy server.
+// ***
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 3. TO CONFIGURE YOUR BROWSER
+//
+// The Proxy Auto Configuration file can be either on the local disk or
+// accessed from a web server, with the following constraints:
+//
+// a. IE4 can only load the PAC from a web server (http:// URL)
+// b. Netscape, Mozilla, Firefox and IE (5 or later) can load the
+// PAC from anywhere.
+// c. Netscape, Mozilla, Firefox and (probably) Opera require the correct
+// MIME type when loading the PAC from a web server.
+//
+//
+// To set the Proxy Auto Configuration with Netscape, Mozilla, or Firefox:
+//
+// 1. Enable Proxy Auto Config:
+//
+// For Netsacpe/Mozilla:
+//
+// Open "Edit->Preferences"
+// Select "Advanced"
+// Select "Proxies"
+//
+// For Firefox (1.0):
+//
+// Open "Tools->Options"
+// Select "Coonection Settings" on the General tab:
+//
+// Select the "Auto proxy configuration URL" option.
+// Enter URL or path of where you've saved this file, such as:
+//
+// http://yourserver/no-ads.pac
+//
+// If you place this on your local disk, you should use a
+// file: URL such as:
+//
+// file:/home/loverso/no-ads.pac (UNIX)
+// file:///c:/windows/desktop/no-ads.pac (Windows)
+//
+// (file:/ and file:// will work in Mozilla, but file:/// is correct
+// required for Firefox)
+//
+// 2. If you are serving this from a web server, these browsers require
+// the correct MIME type on the file before using it. You must configure
+// your web server to provide a "application/x-ns-proxy-autoconfig"
+// MIME type.
+//
+// a. For Apache, name the file with a ".pac" extension and add this
+// line to the http.conf (or the .htaccess file in the same directory):
+//
+// AddType application/x-ns-proxy-autoconfig .pac
+//
+// b. For IIS (instructions from Kevin Roth)
+//
+// Open Internet Services Manager
+// Right click on the web site (or directory) you wish to change.
+// Choose Properties
+// Click the "HTTP Headers" tab
+// Click the "File Types" button in the "MIME Map" section
+// Click the "New Type..." button
+// Enter "pac" for "Associated Extension"
+// Enter "application/x-ns-proxy-autoconfig" for "Content Type (MIME)"
+// Click OK to close the Add type dialog, the MIME types dialog,
+// and the main properties dialog.
+//
+// (This is definately needed for NS, but not for IE)
+//
+//
+// To set the Proxy Auto Configuration with IE:
+//
+// 1. Enable Proxy Auto Config:
+//
+// Open "Tools->Internet Options"
+// Select "Connections" tab
+// Click "LAN Settings"
+// or Choose an entry from "Dial-up settings" and click "Settings"
+//
+// On the settings dialog, select "Use automatic configuration script"
+// Enter the URL of this file in Address field.
+//
+// http://yourserver/no-ads.pac
+// file:///c:/windows/desktop/no-ads.pac (Windows)
+//
+// You can only use a file: URL with IE5 (or later).
+// ("file:///" with with IE versions after 5.0 SP2)
+//
+// 2. Fix Security Settings (IMPORTANT):
+//
+// Select "Security" tab
+// Select "Local intranet"
+// Click "Sites" box
+// Unselect "include all sites that bypass the proxy server" option
+//
+// 3. Disable "Auto Proxy Caching" (IMPORTANT):
+// (thanks to Kevin Roth for alerting me of this!)
+//
+// IE contains a proxy result caching mechanism that will defeat the
+// ability to block servers that server both ad and non-ad content.
+// To prevent this, add the registry key described in this MS KB article:
+//
+// http://support.microsoft.com/?kbid=271361
+//
+// You can do so by downloading this file and clicking on it to load
+// it into the registry. This must be done on a per-user basis.
+// http://www.schooner.com/~loverso/no-ads/IE-no-auto-proxy-cache.reg
+//
+// IE doesn't currently check the MIME type of the PAC file.
+//
+// To see some notes from MS on PAC in IE, see
+// http://msdn.microsoft.com/library/periodic/period99/faq0599.htm
+// (they seem to have removed this URL)
+//
+//
+// To set the Proxy Auto Configuration with Opera 6 (6.04 on Windows tested):
+//
+// 1. Enable Proxy Auto Config:
+// Open the Preferences (Alt-P)
+// Select "Network"
+// Click the "Proxy servers" box
+// Select "Use automatic proxy configuration"
+// Enter the URL of this file as
+//
+// http://yourserver/no-ads.pac
+// file://c:/windows/desktop/no-ads.pac
+//
+// (file:/// might be needed; I've not tested Opera lately)
+//
+// 2. You must use a blackhole proxy for Opera (it will not work with an
+// address of a server that does not respond).
+//
+// 3. Be sure to clear the cache and exit/restart Opera.
+//
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 4. CLEAR YOUR BROWSER'S CACHE
+//
+// For Internet Explorer:
+//
+// Open "Tools->Internet Options"
+// Select "Delete Files" under "Temporary Internet Files"
+// Click "OK"
+//
+// For Mozilla/Netscape Navigator:
+//
+// Open "Edit->Preferences"
+// Select "Advanced"
+// Select "Proxies"
+// Click "Clear Disk Cache"
+// Click "Clear Memory Cache"
+//
+// For Firefox:
+//
+// Open "Tools->Options"
+// Select the "Privay" tab
+// Scroll down or go to the "Cache" section
+// Click "Clear"
+//
+// For Opera:
+//
+// Open "File->Preferences"
+// Select "History and cache"
+// Click "Empty now"
+//
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// To see the definition of this page's JavaScript contents, see
+//
+// http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/proxy-live.html
+//
+// Microsoft includes this in their KB article:
+//
+// http://support.microsoft.com/support/kb/articles/Q209/2/66.ASP
+//
+// Special PAC functions:
+// Hostname:
+// isPlainHostName(host)
+// dnsDomainIs(host, domain)
+// localHostOrDomainIs(host, hostdom)
+// isResolvable(host)
+// isInNet(host, pattern, mask)
+// Utility:
+// dnsResolve(host)
+// myIpAddress()
+// dnsDomainLevels(host)
+// URL:
+// shExpMatch(str, shexp)
+// Time:
+// weekdayRange(wd1, wd2, gmt)
+// dateRange(...)
+// timeRange(...)
+//
+// Other functions and methods that may work:
+// http://developer.netscape.com/docs/manuals/communicator/jsref/win1.htm
+// Note that "alert()" only works with Netscape4 and IE, and Mozilla 1.4+.
+//
+// NOTE:
+// isInNet() will resolve a hostname to an IP address, and cause
+// hangs on Mozilla/Firefox. Currently, these are stubbed out and replaced
+// with shExpMatch(host, "a.b.c.*"), which doesn't do the same thing,
+// but is sufficient for these purposes.
+//
+// Additional Mozilla/Firefox comments:
+//
+// All the above PAC functions are implemented in JavaScript,
+// and are added to the body of your PAC file when it is loaded.
+// See the "components/nsProxyAutoConfig.js" browser install
+// directory.
+//
+// - shExpMatch() is implemented as three pattern.replaces()
+// followed by a call to RegExp() (SLOW)
+// - isPlainHostname() just checks for lack of "." in the string
+// - dnsDomainIs() just matches strings exactly
+// - alert() is bound to this.proxyAlert(), which displays a message
+// in the JavaScript console window
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Regular Expressions
+//
+// Angus Turnbull pointed out the JavaScript 1.2 RE operators to me.
+// These should work in NS4 and IE4 (or later), but I have only tested on
+// Mozilla (1.3), IE5.5, and IE6. PLEASE TELL ME IF IT WORKS FOR YOU!
+//
+// A good introduction is at:
+// http://www.evolt.org/article/Regular_Expressions_in_JavaScript/17/36435/
+// Some references:
+// (old Netscape documentation is gone)
+// http://devedge.netscape.com/library/manuals/2000/javascript/1.5/reference/regexp.html
+// http://developer.netscape.com/docs/manuals/js/client/jsref/regexp.htm
+// http://www.webreference.com/js/column5/
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/js56jsobjRegExpression.asp
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/js56jsgrpRegExpSyntax.asp
+// Real-time evaluator:
+// http://www.cuneytyilmaz.com/prog/jrx/
+//
+// I'm slowly replacing multiple glob patterns with regexps.
+// By using RE literals of /.../ rather than the constructor 'new RegExp()',
+// the regexps should be compiled as no-ads.pac is loaded.
+//
+// Important notes:
+// - if using the constructor, \ needs to be quoted; thus "\\." is used
+// to match a literal '.'. In the RE literal form, I need to end up
+// quoting any / for a URL path.
+// - Avoid these for now; they are broken or not supported in "older"
+// browsers such as NS4 and IE4:
+// - look-aheads (?=pat)
+// - non-greedy ? - a ? that follows *,+,?, and {}; (s)? is NOT non-greedy
+//
+
+// matches several common URL paths for ad images:
+// such as: /banner/ /..._banner/ /banner_...
+// but matches several words and includes plurals
+var re_banner = /\/(.*_){0,1}(ad|adverts?|adimage|adframe|adserver|admentor|adview|banner|popup|popunder)(s)?[_.\/]/i;
+
+// matches host names staring with "ad" but not (admin|add|adsl)
+// or any hostname starting with "pop", "clicks", and "cash"
+// or any hostname containing "banner"
+// ^(ad(s)?.{0,4}\.|pop|click|cash|[^.]*banner|[^.]*adserv)
+// ^(ad(?!(min|sl|d\.))|pop|click|cash|[^.]*banner|[^.]*adserv)
+// ^(ad(?!(min|sl|d\.))|pop|click|cash|[^.]*banner|[^.]*adserv|.*\.ads\.)
+var re_adhost = /^(www\.)?(ad(?!(ult|obe.*|min|sl|d|olly.*))|tology|pop|click|cash|[^.]*banner|[^.]*adserv|.+\.ads?\.)/i;
+
+// neg:
+// admin.foobar.com
+// add.iahoo.com
+// adsl.allow.com
+// administration.all.net
+// pos:
+// fire.ads.ighoo.com
+// ads.foo.org
+// ad0121.aaaa.com
+// adserver.goo.biz
+// popup.foo.bar
+
+///////////////////////////////////////////////////////////////////////////////
+
+var isActive = 1;
+
+function FindProxyForURL(url, host)
+{
+ // debug
+ // alert("checking: url=" + url + ", host=" + host);
+
+ // Excellent kludge from Sean M. Burke:
+ // Enable or disable no-ads for the current browser session.
+ //
+ // To disable, visit this URL: http://no-ads.int/off
+ // To re-enable, visit this URL: http://no-ads.int/on
+ //
+ // (this will not work with Mozilla or Opera if the alert()s are present)
+ //
+ // This happens before lowercasing the URL, so make sure you use lowercase!
+ //
+ if (shExpMatch(host, "no-ads.int")) {
+ if (shExpMatch(url, "*/on*")) {
+ isActive = 1;
+ //alert("no-ads is enabled.\n" + url);
+ } else if (shExpMatch(url, "*/off*")) {
+ isActive = 0;
+ //alert("no-ads has been disabled.\n" + url);
+ } else if (shExpMatch(url, "*no-ads.int/")) {
+ alert("no-ads is "+(isActive ? "enabled" : "disabled")+".\n" + url);
+ } else {
+ alert("no-ads unknown option.\n" + url);
+ }
+
+ return blackhole;
+ }
+
+ if (!isActive) {
+ // alert("allowing (not active): return " + bypass);
+ return bypass;
+ }
+
+ // Suggestion from Quinten Martens
+ // Make everything lower case.
+ // WARNING: all shExpMatch rules following MUST be lowercase!
+ url = url.toLowerCase();
+ host = host.toLowerCase();
+
+ //
+ // Local/Internal rule
+ // matches to this rule get the 'local' proxy.
+ // Adding rules here enables the use of 'local'
+ //
+ if (0
+ // LOCAL
+ // add rules such as:
+ // || dnsDomainIs(host, "schooner.com")
+ // || isPlainHostName(host)
+ // or for a single host
+ // || (host == "some-local-host")
+ ) {
+ // alert("allowing (local): return " + localproxy);
+ return localproxy;
+ }
+
+ //
+ // Whitelist section from InvisiBill
+ //
+ // Add sites here that should never be matched for ads.
+ //
+ if (0
+ // WHITELIST
+ // To add whitelist domains, simple add a line such as:
+ // || dnsDomainIs(host, "schooner.com")
+ // or for a single host
+ // || (host == "some-host-name")
+
+ // Note: whitelisting schooner.com will defeat the "is-it-working"
+ // test page at http://www.schooner.com/~loverso/no-ads/ads/
+
+ // Apple.com "Switch" ads
+ || shExpMatch(url, "*.apple.com/switch/ads/*")
+
+ // SprintPCS
+ || dnsDomainIs(host, ".sprintpcs.com")
+
+ // Lego
+ || dnsDomainIs(host, ".lego.com")
+
+ // Dell login popups
+ || host == "ecomm.dell.com"
+
+ || host == "click2tab.mozdev.org"
+ || host == "addons.mozilla.org"
+
+ // Uncomment for metacrawler
+ // || (host == "clickit.go2net.com")
+
+ // Wunderground weather station banners
+ || shExpMatch(url, "*banners.wunderground.com/cgi-bin/banner/ban/wxbanner*")
+ || shExpMatch(url, "*banners.wunderground.com/weathersticker/*")
+ ) {
+ // alert("allowing (whitelist): return " + normal);
+ return normal;
+ }
+
+ // To add more sites, simply include them in the correct format.
+ //
+ // The sites below are ones I currently block. Tell me of others you add!
+
+ if (0
+ // BLOCK
+ // Block IE4/5 "favicon.ico" fetches
+ // (to avoid being tracked as having bookmarked the site)
+ || shExpMatch(url, "*/favicon.ico")
+
+ //////
+ //
+ // Global Section
+ // tries to match common names
+ //
+
+ // RE for common URL paths
+ || re_banner.test(url)
+
+ // RE for common adserver hostnames.
+ // The regexp matches all hostnames starting with "ad" that are not
+ // admin|add|adsl
+ // (replaces explicit shExpMatch's below)
+ || re_adhost.test(host)
+
+// || (re_adhost.test(host)
+// && !(
+// shExpMatch(host, "add*")
+// || shExpMatch(host, "admin*")
+// || shExpMatch(host, "adsl*")
+// )
+// )
+// // or any subdomain "ads"
+// || (dnsDomainLevels(host) > 2 && shExpMatch(host, "*.ads.*"))
+
+ //////
+ //
+ // banner/ad organizations
+ // Just delete the entire namespace
+ //
+
+ // doubleclick
+ || dnsDomainIs(host, ".doubleclick.com")
+ || dnsDomainIs(host, ".doubleclick.net")
+ || dnsDomainIs(host, ".rpts.net")
+ || dnsDomainIs(host, ".2mdn.net")
+ || dnsDomainIs(host, ".2mdn.com")
+
+ // these set cookies
+ || dnsDomainIs(host, ".globaltrack.com")
+ || dnsDomainIs(host, ".burstnet.com")
+ || dnsDomainIs(host, ".adbureau.net")
+ || dnsDomainIs(host, ".targetnet.com")
+ || dnsDomainIs(host, ".humanclick.com")
+ || dnsDomainIs(host, ".linkexchange.com")
+
+ || dnsDomainIs(host, ".fastclick.com")
+ || dnsDomainIs(host, ".fastclick.net")
+
+ // one whole class C full of ad servers (fastclick)
+ // XXX this might need the resolver
+// || isInNet(host, "205.180.85.0", "255.255.255.0")
+ || shExpMatch(host, "205.180.85.*")
+
+ // these use 1x1 images to track you
+ || dnsDomainIs(host, ".admonitor.com")
+ || dnsDomainIs(host, ".focalink.com")
+
+ || dnsDomainIs(host, ".websponsors.com")
+ || dnsDomainIs(host, ".advertising.com")
+ || dnsDomainIs(host, ".cybereps.com")
+ || dnsDomainIs(host, ".postmasterdirect.com")
+ || dnsDomainIs(host, ".mediaplex.com")
+ || dnsDomainIs(host, ".adtegrity.com")
+ || dnsDomainIs(host, ".bannerbank.ru")
+ || dnsDomainIs(host, ".bannerspace.com")
+ || dnsDomainIs(host, ".theadstop.com")
+ || dnsDomainIs(host, ".l90.com")
+ || dnsDomainIs(host, ".webconnect.net")
+ || dnsDomainIs(host, ".avenuea.com")
+ || dnsDomainIs(host, ".flycast.com")
+ || dnsDomainIs(host, ".engage.com")
+ || dnsDomainIs(host, ".imgis.com")
+ || dnsDomainIs(host, ".datais.com")
+ || dnsDomainIs(host, ".link4ads.com")
+ || dnsDomainIs(host, ".247media.com")
+ || dnsDomainIs(host, ".hightrafficads.com")
+ || dnsDomainIs(host, ".tribalfusion.com")
+ || dnsDomainIs(host, ".rightserve.net")
+ || dnsDomainIs(host, ".admaximize.com")
+ || dnsDomainIs(host, ".valueclick.com")
+ || dnsDomainIs(host, ".adlibris.se")
+ || dnsDomainIs(host, ".vibrantmedia.com")
+ || dnsDomainIs(host, ".coremetrics.com")
+ || dnsDomainIs(host, ".vx2.cc")
+ || dnsDomainIs(host, ".webpower.com")
+ || dnsDomainIs(host, ".everyone.net")
+ || dnsDomainIs(host, ".zedo.com")
+ || dnsDomainIs(host, ".bigbangmedia.com")
+ || dnsDomainIs(host, ".ad-annex.com")
+ || dnsDomainIs(host, ".iwdirect.com")
+ || dnsDomainIs(host, ".adlink.de")
+ || dnsDomainIs(host, ".bidclix.net")
+ || dnsDomainIs(host, ".webclients.net")
+ || dnsDomainIs(host, ".linkcounter.com")
+ || dnsDomainIs(host, ".sitetracker.com")
+ || dnsDomainIs(host, ".adtrix.com")
+ || dnsDomainIs(host, ".netshelter.net")
+ || dnsDomainIs(host, ".rn11.com")
+ // http://vpdc.ru4.com/content/images/66/011.gif
+ || dnsDomainIs(host, ".ru4.com")
+ // no '.' for rightmedia.net
+ || dnsDomainIs(host, "rightmedia.net")
+ || dnsDomainIs(host, ".casalemedia.com")
+ || dnsDomainIs(host, ".casalemedia.com")
+
+ // C-J
+ || dnsDomainIs(host, ".commission-junction.com")
+ || dnsDomainIs(host, ".qkimg.net")
+ // emjcd.com ... many others
+
+ // */adv/*
+ || dnsDomainIs(host, ".bluestreak.com")
+
+ // Virtumundo -- as annoying as they get
+ || dnsDomainIs(host, ".virtumundo.com")
+ || dnsDomainIs(host, ".treeloot.com")
+ || dnsDomainIs(host, ".memberprize.com")
+
+ // internetfuel and _some_ of the sites they redirect to
+ // (more internetfuel - from Sam G)
+ || dnsDomainIs(host, ".internetfuel.net")
+ || dnsDomainIs(host, ".internetfuel.com")
+ || dnsDomainIs(host, ".peoplecaster.com")
+ || dnsDomainIs(host, ".cupidsdatabase.com")
+ || dnsDomainIs(host, ".automotive-times.com")
+ || dnsDomainIs(host, ".healthy-lifetimes.com")
+ || dnsDomainIs(host, ".us-world-business.com")
+ || dnsDomainIs(host, ".internet-2-web.com")
+ || dnsDomainIs(host, ".my-job-careers.com")
+ || dnsDomainIs(host, ".freeonline.com")
+ || dnsDomainIs(host, ".exitfuel.com")
+ || dnsDomainIs(host, ".netbroadcaster.com")
+ || dnsDomainIs(host, ".spaceports.com")
+ || dnsDomainIs(host, ".mircx.com")
+ || dnsDomainIs(host, ".exitchat.com")
+ || dnsDomainIs(host, ".atdmt.com")
+ || dnsDomainIs(host, ".partner2profit.com")
+ || dnsDomainIs(host, ".centrport.net")
+ || dnsDomainIs(host, ".centrport.com")
+ || dnsDomainIs(host, ".rampidads.com")
+
+ //////
+ //
+ // banner servers
+ // (typically these set cookies or serve animated ads)
+ //
+
+ || dnsDomainIs(host, "commonwealth.riddler.com")
+ || dnsDomainIs(host, "banner.freeservers.com")
+ || dnsDomainIs(host, "usads.futurenet.com")
+ || dnsDomainIs(host, "banners.egroups.com")
+ || dnsDomainIs(host, "ngadclient.hearme.com")
+ || dnsDomainIs(host, "affiliates.allposters.com")
+ || dnsDomainIs(host, "adincl.go2net.com")
+ || dnsDomainIs(host, "webads.bizservers.com")
+ || dnsDomainIs(host, ".addserv.com")
+ || dnsDomainIs(host, ".falkag.net")
+ || (host == "promote.pair.com")
+
+ // marketwatch.com (flash ads), but CSS get loaded
+ || (dnsDomainIs(host, ".mktw.net")
+ && !shExpMatch(url, "*/css/*"))
+ || dnsDomainIs(host, ".cjt1.net")
+ || dnsDomainIs(host, ".bns1.net")
+
+ // "undergroundonline"
+ // comes from iframe with this url: http://mediamgr.ugo.com/html.ng/size=728x90&affiliate=megagames&channel=games&subchannel=pc&Network=affiliates&rating=g
+ || dnsDomainIs(host, "image.ugo.com")
+ || dnsDomainIs(host, "mediamgr.ugo.com")
+
+ // web ads and "cheap Long Distance"
+ || dnsDomainIs(host, "zonecms.com")
+ || dnsDomainIs(host, "zoneld.com")
+
+ // AOL
+ || dnsDomainIs(host, ".atwola.com")
+ || dnsDomainIs(host, "toolbar.aol.com")
+
+ // animated ads shown at techbargains
+ || (dnsDomainIs(host, ".overstock.com")
+ && shExpMatch(url, "*/linkshare/*"))
+ || (dnsDomainIs(host, ".supermediastore.com")
+ && shExpMatch(url, "*/lib/supermediastore/*"))
+ || (dnsDomainIs(host, ".shop4tech.com")
+ && shExpMatch(url, "*/assets/*"))
+ || (dnsDomainIs(host, ".softwareandstuff.com")
+ && shExpMatch(url, "*/media/*"))
+ || (dnsDomainIs(host, ".buy.com")
+ && shExpMatch(url, "*/affiliate/*"))
+
+ || (dnsDomainIs(host, "pdaphonehome.com")
+ && (shExpMatch(url, "*/pocketpcmagbest.gif")
+ || shExpMatch(url, "*/link-msmobiles.gif")))
+ || (dnsDomainIs(host, "ppc4you.com")
+ && shExpMatch(url, "*/ppc_top_sites.gif"))
+
+ // more animated ads... these really drive me crazy
+ || (dnsDomainIs(host, ".freewarepalm.com")
+ && shExpMatch(url, "*/sponsors/*"))
+
+ //////
+ //
+ // popups/unders
+ //
+
+ || dnsDomainIs(host, "remotead.cnet.com")
+ || dnsDomainIs(host, ".1st-dating.com")
+ || dnsDomainIs(host, ".mousebucks.com")
+ || dnsDomainIs(host, ".yourfreedvds.com")
+ || dnsDomainIs(host, ".popupsavings.com")
+ || dnsDomainIs(host, ".popupmoney.com")
+ || dnsDomainIs(host, ".popuptraffic.com")
+ || dnsDomainIs(host, ".popupnation.com")
+ || dnsDomainIs(host, ".infostart.com")
+ || dnsDomainIs(host, ".popupad.net")
+ || dnsDomainIs(host, ".usapromotravel.com")
+ || dnsDomainIs(host, ".goclick.com")
+ || dnsDomainIs(host, ".trafficwave.net")
+ || dnsDomainIs(host, ".popupad.net")
+ || dnsDomainIs(host, ".paypopup.com")
+
+ // Popups from ezboard
+ || dnsDomainIs(host, ".greenreaper.com")
+ || dnsDomainIs(host, ".spewey.com")
+ || dnsDomainIs(host, ".englishharbour.com")
+ || dnsDomainIs(host, ".casino-trade.com")
+ || dnsDomainIs(host, "got2goshop.com")
+ // more ezboard crud (from Miika Asunta)
+ || dnsDomainIs(host, ".addynamix.com")
+ || dnsDomainIs(host, ".trafficmp.com")
+ || dnsDomainIs(host, ".makingmoneyfromhome.net")
+ || dnsDomainIs(host, ".leadcart.com")
+
+ // http://www.power-mark.com/js/popunder.js
+ || dnsDomainIs(host, ".power-mark.com")
+
+ //////
+ //
+ // User tracking (worse than ads) && hit counting "services"
+ //
+
+ // "web trends live"
+ || dnsDomainIs(host, ".webtrendslive.com")
+ || dnsDomainIs(host, ".wtlive.com")
+
+ // 1x1 tracking images
+ // ** (but also used in some pay-for-clicks that I want to follow,
+ // ** so disabled for now. 9/2001)
+ // || dnsDomainIs(host, "service.bfast.com")
+
+ // one whole class C full of ad servers
+ // XXX this might need the resolver
+// || isInNet(host, "66.40.16.0", "255.255.255.0")
+ || shExpMatch(host, "66.40.16.*")
+
+ || dnsDomainIs(host, ".web-stat.com")
+ || dnsDomainIs(host, ".superstats.com")
+ || dnsDomainIs(host, ".allhits.ru")
+ || dnsDomainIs(host, ".list.ru")
+ || dnsDomainIs(host, ".counted.com")
+ || dnsDomainIs(host, ".rankyou.com")
+ || dnsDomainIs(host, ".clickcash.com")
+ || dnsDomainIs(host, ".clickbank.com")
+ || dnsDomainIs(host, ".paycounter.com")
+ || dnsDomainIs(host, ".cashcount.com")
+ || dnsDomainIs(host, ".clickedyclick.com")
+ || dnsDomainIs(host, ".clickxchange.com")
+ || dnsDomainIs(host, ".sitestats.com")
+ || dnsDomainIs(host, ".site-stats.com")
+ || dnsDomainIs(host, ".hitbox.com")
+ || dnsDomainIs(host, ".exitdirect.com")
+ || dnsDomainIs(host, ".realtracker.com")
+ || dnsDomainIs(host, ".etracking.com")
+ || dnsDomainIs(host, ".livestat.com")
+ || dnsDomainIs(host, ".spylog.com")
+ || dnsDomainIs(host, ".freestats.com")
+ || dnsDomainIs(host, ".addfreestats.com")
+ || dnsDomainIs(host, ".topclicks.net")
+ || dnsDomainIs(host, ".mystat.pl")
+ || dnsDomainIs(host, ".hitz4you.de")
+ || dnsDomainIs(host, ".hitslink.com")
+ || dnsDomainIs(host, ".thecounter.com")
+ || dnsDomainIs(host, ".roiservice.com")
+ || dnsDomainIs(host, ".overture.com")
+ || dnsDomainIs(host, ".xiti.com")
+ || dnsDomainIs(host, ".cj.com")
+ || dnsDomainIs(host, ".anrdoezrs.net")
+ || dnsDomainIs(host, ".hey.it")
+ || dnsDomainIs(host, ".ppctracking.net")
+ || dnsDomainIs(host, ".darkcounter.com")
+ || dnsDomainIs(host, ".2o7.com")
+ || dnsDomainIs(host, ".2o7.net")
+ || dnsDomainIs(host, ".gostats.com")
+ || dnsDomainIs(host, ".everstats.com")
+ || dnsDomainIs(host, ".onestat.com")
+ || dnsDomainIs(host, ".statcounter.com")
+ || dnsDomainIs(host, ".trafic.ro")
+ || dnsDomainIs(host, ".exitexchange.com")
+
+ // clickability, via CNN
+ || dnsDomainIs(host, ".clickability.com")
+ || dnsDomainIs(host, ".savethis.com")
+
+ //////
+ //
+ // Dead domain parking
+ //
+ || dnsDomainIs(host, ".netster.com")
+
+ //////
+ //
+ // Search engine "optimizers"
+ //
+ || dnsDomainIs(host, ".searchmarketing.com")
+
+ //////
+ //
+ // Spyware/worms
+ //
+
+ || dnsDomainIs(host, ".friendgreetings.com")
+ || dnsDomainIs(host, ".permissionedmedia.com")
+ || dnsDomainIs(host, ".searchbarcash.com")
+
+ //////
+ //
+ // "Surveys"
+ //
+
+ || dnsDomainIs(host, ".zoomerang.com")
+
+ //////
+ //
+ // "Casino" ads (scams)
+ //
+
+ || dnsDomainIs(host, ".aceshigh.com")
+ || dnsDomainIs(host, ".idealcasino.net")
+ || dnsDomainIs(host, ".casinobar.net")
+ || dnsDomainIs(host, ".casinoionair.com")
+
+ || (dnsDomainIs(host, ".go2net.com")
+ && shExpMatch(url, "*adclick*")
+ )
+
+ //////
+ //
+ // Spammers
+ //
+
+ || dnsDomainIs(host, ".licensed-collectibles.com")
+ || dnsDomainIs(host, ".webdesignprofessional.com")
+
+ //////
+ //
+ // Directed at extra annoying places
+ //
+
+ // Attempts to download ad-supported spyware without asking first
+ || dnsDomainIs(host, ".gator.com")
+
+ // ebay
+ || ((dnsDomainIs(host, "pics.ebay.com")
+ || dnsDomainIs(host, "pics.ebaystatic.com"))
+ && shExpMatch(url, "*/pics/mops/*/*[0-9]x[0-9]*")
+ )
+ || (dnsDomainIs(host, "ebayobjects.com")
+ && shExpMatch(url, "*search/keywords*")
+ )
+ || dnsDomainIs(host, "admarketplace.com")
+ || dnsDomainIs(host, "admarketplace.net")
+
+ // Bravenet & Ezboard
+ || (dnsDomainIs(host, ".ezboard.com")
+ && shExpMatch(url, "*/bravenet/*")
+ )
+ || (dnsDomainIs(host, ".bravenet.com")
+ && ( shExpMatch(host, "*counter*")
+ || shExpMatch(url, "*/jsbanner*")
+ || shExpMatch(url, "*/bravenet/*")
+ )
+ )
+
+ // GeoCities
+ // (checking "toto" from Prakash Persaud)
+ || (( dnsDomainIs(host,"geo.yahoo.com")
+ || dnsDomainIs(host,".geocities.com"))
+ && (
+ shExpMatch(url,"*/toto?s*")
+ || shExpMatch(url, "*geocities.com/js_source*")
+ || dnsDomainIs(host, "visit.geocities.com")
+ )
+ )
+
+ // Yahoo ads (direct and via Akamai)
+ // http://us.a1.yimg.com/us.yimg.com/a/...
+ || (dnsDomainIs(host,"yimg.com")
+ && ( shExpMatch(url,"*yimg.com/a/*")
+ || shExpMatch(url,"*yimg.com/*/adv/*")
+ )
+ )
+ // "eyewonder" ads at Yahoo
+ || dnsDomainIs(host,"qz3.net")
+ || dnsDomainIs(host,".eyewonder.com")
+
+ // background ad images
+ || dnsDomainIs(host,"buzzcity.com")
+
+ // FortuneCity - ads and tracking
+ || (dnsDomainIs(host,".fortunecity.com")
+ && ( shExpMatch(url,"*/js/adscript*")
+ || shExpMatch(url,"*/js/fctrack*")
+ )
+ )
+
+ // zdnet
+ // tracking webbugs:
+ // http://gserv.zdnet.com/clear/ns.gif?a000009999999999999+2093
+ || (dnsDomainIs(host, ".zdnet.com")
+ && ( dnsDomainIs(host, "ads3.zdnet.com")
+ || host == "gserv.zdnet.com"
+ || shExpMatch(url, "*/texis/cs/ad.html")
+ || shExpMatch(url, "*/adverts")
+ )
+ )
+
+ // cnet
+ // web bugs and ad redirections
+ // taken care of by hostname rules:
+ // http://adimg.com.com/...
+ // http://adlog.com.com/...
+ // http://dw.com.com/clear/c.gif
+ // http://dw.com.com/redir?astid=2&destUrl=http%3A%2F%2Fwww.buy ...
+ // http://mads.com.com/mac-ad?...
+ || (host == "dw.com.com" || host == "mads.com.com")
+ || (dnsDomainIs(host, ".com.com")
+ && ( host == "dw.com.com"
+ || host == "mads.com.com"
+ )
+ )
+
+ // nytimes
+ || (dnsDomainIs(host, ".nytimes.com")
+ && shExpMatch(url,"*/adx/*")
+ )
+
+ // pop-after
+ || dnsDomainIs(host, ".unicast.net")
+
+
+ // Be Free affiliate ads
+ || dnsDomainIs(host, ".reporting.net")
+ || dnsDomainIs(host, ".affliate.net")
+ || (dnsDomainIs(host, ".akamai.net")
+ && shExpMatch(url, "*.affiliate.net/*")
+ )
+
+ // Infospace.com popunder
+ // for "webmarket.com" & "shopping.dogpile.com" -- just say no!
+ || (dnsDomainIs(host, ".infospace.com")
+ && shExpMatch(url, "*/goshopping/*")
+ )
+ || dnsDomainIs(host, ".webmarket.com")
+ || dnsDomainIs(host, "shopping.dogpile.com")
+
+ // goto.com popunder for information.gopher.com
+ || dnsDomainIs(host, "information.gopher.com")
+
+ // About.com popunder and floating ad bar
+ || (dnsDomainIs(host, ".about.com")
+ && (0
+ || shExpMatch(url, "*/sprinks/*")
+ || shExpMatch(url, "*about.com/0/js/*")
+ || shExpMatch(url, "*about.com/f/p/*")
+ )
+ )
+
+ // Dell
+ || (dnsDomainIs(host, ".dell.com")
+ && shExpMatch(url, "*/images/affiliates/*")
+ )
+
+ // IFilm iframes
+ || (dnsDomainIs(host, ".ifilm.com")
+ && (shExpMatch(url, "*/partners/*")
+ || shExpMatch(url, "*/redirect*")
+ )
+ )
+
+ // tomshardware
+ // they are most annoying:
+ // - cookies on their background images to track you
+ // - looping shockwave ads
+ // this kills most of the crud
+// || isInNet(host, "216.92.21.0", "255.255.255.0")
+ || ((dnsDomainIs(host, ".tomshardware.com")
+ || shExpMatch(host, "216.92.21.*"))
+ && ( shExpMatch(url, "*/cgi-bin/banner*")
+ || shExpMatch(url, "*/cgi-bin/bd.m*")
+ || shExpMatch(url, "*/images/banner/*")
+ )
+ )
+
+ || shExpMatch(url, "*mapsonus.com/ad.images*")
+
+ // Slashdot: added these when I saw hidden 1x1 images with cookies
+ || dnsDomainIs(host, "adfu.blockstackers.com")
+ || (dnsDomainIs(host, "slashdot.org")
+ && (
+ shExpMatch(url, "*/slashdot/pc.gif*")
+ || shExpMatch(url, "*/pagecount.gif*")
+ || shExpMatch(url, "*/adlog.pl*")
+ )
+ )
+ || dnsDomainIs(host, "googlesyndication.com")
+ || dnsDomainIs(host, "google-analytics.com")
+
+ // it-aint-cool.com
+ || (dnsDomainIs(host, "aintitcool.com")
+ && (
+ shExpMatch(url, "*/newline/*")
+ || shExpMatch(url, "*/drillteammedia/*")
+ || shExpMatch(url, "*/foxsearchlight/*")
+ || shExpMatch(url, "*/media/aol*")
+ || shExpMatch(url, "*swf")
+ )
+ )
+
+ // Staples & CrossMediaServices
+ || (dnsDomainIs(host, ".staples.com")
+ && shExpMatch(url, "*/pixeltracker/*")
+ )
+ || dnsDomainIs(host, "pt.crossmediaservices.com")
+
+ // OfficeMax affiliate art (affArt->affart because of toLowerCase)
+ || (dnsDomainIs(host, ".officemax.com")
+ && shExpMatch(url, "*/affart/*")
+ )
+
+ // complicated JavaScript for directed ads!
+// 1/5/2004: allow /js/ as they now use it for graphs
+// || (dnsDomainIs(host, ".anandtech.com")
+// && (shExpMatch(url,"*/js/*")
+// || shExpMatch(url,"*/bnr_*")
+// )
+// )
+
+ // hardocp
+ // http://65.119.30.151/UploadFilesForNewegg/onlineads/newegg728hardocp.swf
+ || (host == "hera.hardocp.com")
+ || shExpMatch(url,"*/onlineads/*")
+
+ // complicated JavaScript for gliding ads!
+ || (dnsDomainIs(host, ".fatwallet.com")
+ && shExpMatch(url,"*/js/*")
+ )
+
+ // cnet ads
+ || dnsDomainIs(host, "promo.search.com")
+
+ // IMDB celeb photos
+ // (Photos/CMSIcons->photos/cmsicons because of toLowerCase)
+ || (dnsDomainIs(host, "imdb.com")
+ && ( shExpMatch(url, "*/photos/cmsicons/*")
+ || shExpMatch(url, "*/icons/*/celeb/*")
+ || shExpMatch(url, "*.swf")
+ )
+ )
+ // incredibly annoying IMDB shock/flash ads
+ || dnsDomainIs(host, "kliptracker.com")
+ || dnsDomainIs(host, "klipmart.com")
+
+ || host == "spinbox.techtracker.com"
+
+ // Amazon affiliate 'search'. retrieves a JS that writes new HTML
+ // that references one or more images "related to your search".
+ // (If there is a real use for rcm.amazon.com, let me know)
+ // http://rcm.amazon.com/e/cm?t=starlingtechnolo&amp;l=st1&amp;search=cynicism&amp;mode=books&amp;p=11&amp;o=1&amp;bg1=CEE7FF&amp;fc1=000000&amp;lc1=083194&amp;lt1=_blank
+ || host == "rcm.amazon.com"
+
+ //////
+ //
+ // "Other Scum And Villainry"
+ //
+
+ // Popup from "reserved" domains at register.com
+ // (I considered blocking all of register.com)
+ || (dnsDomainIs(host, ".register.com")
+ && (shExpMatch(url,"*.js")
+ || shExpMatch(host, "searchtheweb*")
+ || shExpMatch(host, "futuresite*")
+ )
+ )
+
+ || dnsDomainIs(host, ".oingo.com")
+ || dnsDomainIs(host, ".namingsolutions.com")
+
+ // "Data collection"
+ || dnsDomainIs(host, ".coremetrics.com")
+
+ // Sets your home page
+ || dnsDomainIs(host, ".firehunt.com")
+
+ // tracking
+ || dnsDomainIs(host, ".appliedsemantics.com")
+
+ // Scum who buy ad space from the above
+ // || dnsDomainIs(host, ".hartfordrents.com")
+ // || dnsDomainIs(host, ".chicagocomputerrentals.com")
+ // || dnsDomainIs(host, ".ccrsolutions.com")
+ // || dnsDomainIs(host, ".rushcomputer.com")
+ // || dnsDomainIs(host, ".localesimates.com")
+ // || dnsDomainIs(host, ".unitedvision.com")
+ // XXX this might need the resolver
+// || isInNet(host, "216.216.246.31", "255.255.255.255")
+ || (host == "216.216.246.31")
+
+ // avsforum ads
+// || isInNet(host, "216.66.21.35", "255.255.255.255")
+ || (host == "216.66.21.35")
+ || dnsDomainIs(host, ".avsads.com")
+
+ // bogus "search" sites at non-existent sites
+ || dnsDomainIs(host, ".search411.com")
+
+ // palmgear.com
+ || (dnsDomainIs(host, ".palmgear.com")
+ && ( shExpMatch(url, "*/adsales/*")
+ || shExpMatch(url, "*/emailblast*")
+ )
+ )
+
+ //////
+ //
+ // Contributed adult sites
+ //
+
+ || dnsDomainIs(host, ".porntrack.com")
+ || dnsDomainIs(host, ".sexe-portail.com")
+ || dnsDomainIs(host, ".sextracker.com")
+ || dnsDomainIs(host, ".sexspy.com")
+ || dnsDomainIs(host, ".offshoreclicks.com")
+ || dnsDomainIs(host, ".exxxit.com")
+ || dnsDomainIs(host, "private-dailer.biz")
+ || shExpMatch(url, "*retestrak.nl/misc/reet.gif")
+ || shExpMatch(url, "*dontstayin.com/*.swf")
+
+ // debug
+ // || (alertmatch("NOT:" + url) && 0)
+
+ ) {
+
+ // alert("blackholing: " + url);
+
+ // deny this request
+ return blackhole;
+
+ } else {
+ // debug
+ // alert("allowing: " + url);
+
+ // all other requests go direct and avoid any overhead
+ return normal;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// This line is just for testing; you can ignore it. But, if you are having
+// problems where you think this PAC file isn't being loaded, then change this
+// to read "if (1)" and the alert box should appear when the browser loads this
+// file.
+//
+// This works for IE4, IE5, IE5.5, IE6 and Netscape 2.x, 3.x, and 4.x.
+// (For IE6, tested on Win2K)
+// This does not work for Mozilla before 1.4 (and not for Netscape 6.x).
+// In Mozilla 1.4+ and Fireox, this will write to the JavaScript console.
+//
+if (0) {
+ alert("no-ads.pac: LOADED:\n" +
+ " version: "+noadsver+"\n" +
+ " normal: "+normal+"\n" +
+ " blackhole: "+blackhole+"\n" +
+ " localproxy: "+localproxy+"\n" +
+ " bypass: "+bypass+"\n"
+ // MSG
+ );
+}
+
+// The above should show you that this JavaScript is executed in an
+// unprotected global context. NEVER point at someone elses autoconfig file;
+// always load from your own copy!
+
+// an alert that returns true
+function alertmatch(str)
+{
+ // alert("match: "+str);
+ return 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Replacement function for dnsDomainIs(). This is to replace the
+// prefix problem, which a leading '.' used to be used for.
+//
+// dnsDomainIs("bar.com", "bar.com") => true
+// dnsDomainIs("www.bar.com", "bar.com") => true
+// dnsDomainIs("www.foobar.com", "bar.com") => true <<< incorrect
+//
+// isInDomain("bar.com", "bar.com") => true
+// isInDomain("www.bar.com", "bar.com") => true
+// isInDomain("www.foobar.com", "bar.com") => false <<< correct
+//
+function isInDomain(host, domain) {
+ if (host.length > domain.length) {
+ return (host.substring(host.length - domain.length - 1) == "."+domain);
+ }
+ return (host == domain);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tired of reading boring comments? Try reading today's comics:
+// http://www.schooner.com/~loverso/comics/
+//
+// or getting a quote from my collection:
+// http://www.schooner.com/~loverso/quote/
+//
+
+// eof
+ //intelliserv.net
+ //intellisrv.net
+ //rambler.ru
+ //rightmedia.net
+ //calloffate.com
+ //fairmeasures.com
+
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns.js
new file mode 100644
index 00000000000..fbfb74ec5db
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns.js
@@ -0,0 +1,31 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ alert('iteration: ' + g_iteration++);
+
+ var ips = [
+ myIpAddress(),
+ dnsResolve(''),
+ dnsResolveEx('host1'),
+ dnsResolve('host2'),
+ dnsResolve('host3'),
+ myIpAddress(),
+ dnsResolve('host3'),
+ dnsResolveEx('host1'),
+ myIpAddress(),
+ dnsResolve('host2'),
+ dnsResolveEx('host6'),
+ myIpAddressEx(),
+ dnsResolve('host1'),
+ ];
+
+ for (var i = 0; i < ips.length; ++i) {
+ // Stringize everything.
+ ips[i] = '' + ips[i];
+ }
+
+ var proxyHost = ips.join('-');
+ proxyHost = proxyHost.replace(/[^0-9a-zA-Z.-]/g, '_');
+
+ return "PROXY " + proxyHost + ":99";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns_during_init.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns_during_init.js
new file mode 100644
index 00000000000..55aef52e6f1
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/dns_during_init.js
@@ -0,0 +1,14 @@
+var g_ips = [
+ dnsResolve('host1'),
+ dnsResolve('host2')
+];
+
+alert('Watsup');
+alert('Watsup2');
+
+function FindProxyForURL(url, host) {
+ // Note that host1 and host2 should not resolve using the same cache as was
+ // used for g_ips!
+ var ips = g_ips.concat([dnsResolve('host1'), dnsResolve('host2')]);
+ return 'PROXY ' + ips.join('-') + ':99';
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/error.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/error.js
new file mode 100644
index 00000000000..83e534c28f2
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/error.js
@@ -0,0 +1,8 @@
+function FindProxyForURL(url, host) {
+ if (host == 'throw-an-error') {
+ alert('Prepare to DIE!');
+ var x = null;
+ return x.split('-'); // Throws exception.
+ }
+ return "PROXY i-approve-this-message:42";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects1.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects1.js
new file mode 100644
index 00000000000..251af9f7546
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects1.js
@@ -0,0 +1,14 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ var ips = [
+ dnsResolve('host1'),
+ dnsResolve('crazy' + g_iteration)
+ ];
+
+ alert('iteration: ' + g_iteration);
+
+ return 'PROXY ' + ips.join('-') + ':100';
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects2.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects2.js
new file mode 100644
index 00000000000..f5e5076a7f8
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects2.js
@@ -0,0 +1,18 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ var ips;
+
+ if (g_iteration < 3) {
+ ips = [
+ dnsResolve('host1'),
+ dnsResolve('host2')
+ ];
+ } else {
+ ips = [ dnsResolve('host' + g_iteration) ];
+ }
+
+ return 'PROXY ' + ips.join('-') + ':100';
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects3.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects3.js
new file mode 100644
index 00000000000..04211260c3b
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects3.js
@@ -0,0 +1,13 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ var results = [];
+ for (var i = 1; i <= g_iteration; ++i) {
+ results.push('' + dnsResolve('host' + i));
+ }
+
+ alert('iteration: ' + g_iteration);
+ return 'PROXY ' + results.join('-') + ':' + g_iteration;
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects4.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects4.js
new file mode 100644
index 00000000000..1bfbbfd88e5
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/global_sideffects4.js
@@ -0,0 +1,15 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ for (var i = 0; i < g_iteration; ++i) {
+ myIpAddress();
+ }
+
+ var result = '' + dnsResolve('host' + g_iteration);
+ result += g_iteration;
+
+ alert('iteration: ' + g_iteration);
+ return 'PROXY ' + result + ':34';
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple.js
new file mode 100644
index 00000000000..f742081f8dd
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple.js
@@ -0,0 +1,3 @@
+function FindProxyForURL(url, host) {
+ return "PROXY " + host + ":99";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple_dns.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple_dns.js
new file mode 100644
index 00000000000..24867b07a30
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/simple_dns.js
@@ -0,0 +1,8 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+ myIpAddress();
+ var ip = dnsResolve(host);
+ return "PROXY " + ip + ':' + g_iteration;
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/terminate.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/terminate.js
new file mode 100644
index 00000000000..88fe4cb2389
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/terminate.js
@@ -0,0 +1,20 @@
+// Copyright (c) 2013 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.
+
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ var ip1 = dnsResolve("host1");
+ var ip2 = dnsResolveEx("host2");
+
+ if (ip1 == "182.111.0.222" && ip2 == "111.33.44.55")
+ return "PROXY foopy:" + g_iteration;
+
+ // If the script didn't terminate when abandoned, then it will reach this and
+ // hang.
+ for (;;) {}
+ throw "not reached";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_alerts.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_alerts.js
new file mode 100644
index 00000000000..564e30ec638
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_alerts.js
@@ -0,0 +1,13 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ dnsResolve(host);
+
+ for (var i = 0; i < 50; i++) {
+ alert('Gee, all these alerts are silly!');
+ }
+
+ return "PROXY foo:" + g_iteration;
+}
diff --git a/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_empty_alerts.js b/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_empty_alerts.js
new file mode 100644
index 00000000000..6fe85d43dcd
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_tracing_unittest/too_many_empty_alerts.js
@@ -0,0 +1,13 @@
+var g_iteration = 0;
+
+function FindProxyForURL(url, host) {
+ g_iteration++;
+
+ dnsResolve(host);
+
+ for (var i = 0; i < 1000; i++) {
+ alert('');
+ }
+
+ return "PROXY foo:" + g_iteration;
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/binding_from_global.js b/chromium/net/data/proxy_resolver_v8_unittest/binding_from_global.js
new file mode 100644
index 00000000000..91bbcf2378f
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/binding_from_global.js
@@ -0,0 +1,8 @@
+// Calls a bindings outside of FindProxyForURL(). This causes the code to
+// get exercised during initialization.
+
+var x = myIpAddress();
+
+function FindProxyForURL(url, host) {
+ return "PROXY " + x + ":80";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/bindings.js b/chromium/net/data/proxy_resolver_v8_unittest/bindings.js
new file mode 100644
index 00000000000..7cf9f263465
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/bindings.js
@@ -0,0 +1,62 @@
+// Try calling the browser-side bound functions with varying (invalid)
+// inputs. There is no notion of "success" for this test, other than
+// verifying the correct C++ bindings were reached with expected values.
+
+function MyObject() {
+ this.x = "3";
+}
+
+MyObject.prototype.toString = function() {
+ throw "exception from calling toString()";
+}
+
+function expectEquals(expectation, actual) {
+ if (!(expectation === actual)) {
+ throw "FAIL: expected: " + expectation + ", actual: " + actual;
+ }
+}
+
+function FindProxyForURL(url, host) {
+ // Call dnsResolve with some wonky arguments.
+ // Those expected to fail (because we have passed a non-string parameter)
+ // will return |null|, whereas those that have called through to the C++
+ // bindings will return '127.0.0.1'.
+ expectEquals(null, dnsResolve());
+ expectEquals(null, dnsResolve(null));
+ expectEquals(null, dnsResolve(undefined));
+ expectEquals('127.0.0.1', dnsResolve(""));
+ expectEquals(null, dnsResolve({foo: 'bar'}));
+ expectEquals(null, dnsResolve(fn));
+ expectEquals(null, dnsResolve(['3']));
+ expectEquals('127.0.0.1', dnsResolve("arg1", "arg2", "arg3", "arg4"));
+
+ // Call alert with some wonky arguments.
+ alert();
+ alert(null);
+ alert(undefined);
+ alert({foo:'bar'});
+
+ // This should throw an exception when we toString() the argument
+ // to alert in the bindings.
+ try {
+ alert(new MyObject());
+ } catch (e) {
+ alert(e);
+ }
+
+ // Call myIpAddress() with wonky arguments
+ myIpAddress(null);
+ myIpAddress(null, null);
+
+ // Call myIpAddressEx() correctly (no arguments).
+ myIpAddressEx();
+
+ // Call dnsResolveEx() (note that isResolvableEx() implicity calls it.)
+ isResolvableEx("is_resolvable");
+ dnsResolveEx("foobar");
+
+ return "DIRECT";
+}
+
+function fn() {}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/direct.js b/chromium/net/data/proxy_resolver_v8_unittest/direct.js
new file mode 100644
index 00000000000..43a04da7c8c
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/direct.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return "DIRECT";
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/dns_fail.js b/chromium/net/data/proxy_resolver_v8_unittest/dns_fail.js
new file mode 100644
index 00000000000..c71bcc38304
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/dns_fail.js
@@ -0,0 +1,27 @@
+// This script should be run in an environment where all DNS resolution are
+// failing. It tests that functions return the expected values.
+//
+// Returns "PROXY success:80" on success.
+function FindProxyForURL(url, host) {
+ try {
+ expectEq("127.0.0.1", myIpAddress());
+ expectEq("", myIpAddressEx());
+
+ expectEq(null, dnsResolve("not-found"));
+ expectEq("", dnsResolveEx("not-found"));
+
+ expectEq(false, isResolvable("not-found"));
+ expectEq(false, isResolvableEx("not-found"));
+
+ return "PROXY success:80";
+ } catch(e) {
+ alert(e);
+ return "PROXY failed:80";
+ }
+}
+
+function expectEq(expected, actual) {
+ if (expected != actual)
+ throw "Expected " + expected + " but was " + actual;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/ends_with_comment.js b/chromium/net/data/proxy_resolver_v8_unittest/ends_with_comment.js
new file mode 100644
index 00000000000..bbfef8561d2
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/ends_with_comment.js
@@ -0,0 +1,8 @@
+function FindProxyForURL(url, host) {
+ return "PROXY success:80";
+}
+
+// We end the script with a comment (and no trailing newline).
+// This used to cause problems, because internally ProxyResolverV8
+// would append some functions to the script; the first line of
+// those extra functions was being considered part of the comment. \ No newline at end of file
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/ends_with_statement_no_semicolon.js b/chromium/net/data/proxy_resolver_v8_unittest/ends_with_statement_no_semicolon.js
new file mode 100644
index 00000000000..ea03714b3bc
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/ends_with_statement_no_semicolon.js
@@ -0,0 +1,3 @@
+// Ends with a statement, and no terminal newline.
+function FindProxyForURL(url, host) { return "PROXY success:" + x; }
+x = 3 \ No newline at end of file
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/international_domain_names.js b/chromium/net/data/proxy_resolver_v8_unittest/international_domain_names.js
new file mode 100644
index 00000000000..546af13d51d
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/international_domain_names.js
@@ -0,0 +1,16 @@
+// Try resolving hostnames containing non-ASCII characters.
+
+function FindProxyForURL(url, host) {
+ // This international hostname has a non-ASCII character. It is represented
+ // in punycode as 'xn--bcher-kva.ch'
+ var idn = 'B\u00fccher.ch';
+
+ // We disregard the actual return value -- all we care about is that on
+ // the C++ end the bindings were passed the punycode equivalent of this
+ // unicode hostname.
+ dnsResolve(idn);
+ dnsResolveEx(idn);
+
+ return "DIRECT";
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/missing_close_brace.js b/chromium/net/data/proxy_resolver_v8_unittest/missing_close_brace.js
new file mode 100644
index 00000000000..8018f8f595e
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/missing_close_brace.js
@@ -0,0 +1,6 @@
+// This PAC script is invalid, because there is a missing close brace
+// on the function FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+ return "DIRECT";
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/no_entrypoint.js b/chromium/net/data/proxy_resolver_v8_unittest/no_entrypoint.js
new file mode 100644
index 00000000000..8993059435c
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/no_entrypoint.js
@@ -0,0 +1,2 @@
+var x = "This is an invalid PAC script because it lacks a " +
+ "FindProxyForURL() function";
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js b/chromium/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js
new file mode 100644
index 00000000000..0c0a4a98150
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js
@@ -0,0 +1,366 @@
+// This should output "PROXY success:80" if all the tests pass.
+// Otherwise it will output "PROXY failure:<num-failures>".
+//
+// This aims to unit-test the PAC library functions, which are
+// exposed in the PAC's execution environment. (Namely, dnsDomainLevels,
+// timeRange, etc.)
+
+function FindProxyForURL(url, host) {
+ var numTestsFailed = 0;
+
+ // Run all the tests
+ for (var test in Tests) {
+ var t = new TestContext(test);
+
+ // Run the test.
+ Tests[test](t);
+
+ if (t.failed()) {
+ numTestsFailed++;
+ }
+ }
+
+ if (numTestsFailed == 0) {
+ return "PROXY success:80";
+ }
+ return "PROXY failure:" + numTestsFailed;
+}
+
+// --------------------------
+// Tests
+// --------------------------
+
+var Tests = {};
+
+Tests.testDnsDomainIs = function(t) {
+ t.expectTrue(dnsDomainIs("google.com", ".com"));
+ t.expectTrue(dnsDomainIs("google.co.uk", ".co.uk"));
+ t.expectFalse(dnsDomainIs("google.com", ".co.uk"));
+ t.expectFalse(dnsDomainIs("www.adobe.com", ".ad"));
+};
+
+Tests.testDnsDomainLevels = function(t) {
+ t.expectEquals(0, dnsDomainLevels("www"));
+ t.expectEquals(2, dnsDomainLevels("www.google.com"));
+ t.expectEquals(3, dnsDomainLevels("192.168.1.1"));
+};
+
+Tests.testIsInNet = function(t) {
+ t.expectTrue(
+ isInNet("192.89.132.25", "192.89.132.25", "255.255.255.255"));
+ t.expectFalse(
+ isInNet("193.89.132.25", "192.89.132.25", "255.255.255.255"));
+
+ t.expectTrue(isInNet("192.89.132.25", "192.89.0.0", "255.255.0.0"));
+ t.expectFalse(isInNet("193.89.132.25", "192.89.0.0", "255.255.0.0"));
+
+ t.expectFalse(
+ isInNet("192.89.132.a", "192.89.0.0", "255.255.0.0"));
+};
+
+Tests.testIsPlainHostName = function(t) {
+ t.expectTrue(isPlainHostName("google"));
+ t.expectFalse(isPlainHostName("google.com"));
+};
+
+Tests.testLocalHostOrDomainIs = function(t) {
+ t.expectTrue(localHostOrDomainIs("www.google.com", "www.google.com"));
+ t.expectTrue(localHostOrDomainIs("www", "www.google.com"));
+ t.expectFalse(localHostOrDomainIs("maps.google.com", "www.google.com"));
+};
+
+Tests.testShExpMatch = function(t) {
+ t.expectTrue(shExpMatch("foo.jpg", "*.jpg"));
+ t.expectTrue(shExpMatch("foo5.jpg", "*o?.jpg"));
+ t.expectFalse(shExpMatch("foo.jpg", ".jpg"));
+ t.expectFalse(shExpMatch("foo.jpg", "foo"));
+};
+
+Tests.testSortIpAddressList = function(t) {
+ t.expectEquals("::1;::2;::3", sortIpAddressList("::2;::3;::1"));
+ t.expectEquals(
+ "2001:4898:28:3:201:2ff:feea:fc14;fe80::5efe:157:9d3b:8b16;157.59.139.22",
+ sortIpAddressList("157.59.139.22;" +
+ "2001:4898:28:3:201:2ff:feea:fc14;" +
+ "fe80::5efe:157:9d3b:8b16"));
+
+ // Single IP address (v4 and v6).
+ t.expectEquals("127.0.0.1", sortIpAddressList("127.0.0.1"));
+ t.expectEquals("::1", sortIpAddressList("::1"))
+
+ // Verify that IPv6 address is not re-written (not reduced).
+ t.expectEquals("0:0::1;192.168.1.1", sortIpAddressList("192.168.1.1;0:0::1"));
+
+ // Input is already sorted.
+ t.expectEquals("::1;192.168.1.3", sortIpAddressList("::1;192.168.1.3"));
+
+ // Same-valued IP addresses (also tests stability).
+ t.expectEquals("0::1;::1;0:0::1", sortIpAddressList("0::1;::1;0:0::1"));
+
+ // Contains extra semi-colons.
+ t.expectEquals("127.0.0.1", sortIpAddressList(";127.0.0.1;"));
+
+ // Contains whitespace (spaces and tabs).
+ t.expectEquals("192.168.0.1;192.168.0.2",
+ sortIpAddressList("192.168.0.1; 192.168.0.2"));
+ t.expectEquals("127.0.0.0;127.0.0.1;127.0.0.2",
+ sortIpAddressList("127.0.0.1; 127.0.0.2; 127.0.0.0"));
+
+ // Empty lists.
+ t.expectFalse(sortIpAddressList(""));
+ t.expectFalse(sortIpAddressList(" "));
+ t.expectFalse(sortIpAddressList(";"));
+ t.expectFalse(sortIpAddressList(";;"));
+ t.expectFalse(sortIpAddressList(" ; ; "));
+
+ // Invalid IP addresses.
+ t.expectFalse(sortIpAddressList("256.0.0.1"));
+ t.expectFalse(sortIpAddressList("192.168.1.1;0:0:0:1;127.0.0.1"));
+
+ // Call sortIpAddressList() with wonky arguments.
+ t.expectEquals(null, sortIpAddressList());
+ t.expectEquals(null, sortIpAddressList(null));
+ t.expectEquals(null, sortIpAddressList(null, null));
+};
+
+Tests.testIsInNetEx = function(t) {
+ t.expectTrue(isInNetEx("198.95.249.79", "198.95.249.79/32"));
+ t.expectTrue(isInNetEx("198.95.115.10", "198.95.0.0/16"));
+ t.expectTrue(isInNetEx("198.95.1.1", "198.95.0.0/16"));
+ t.expectTrue(isInNetEx("198.95.1.1", "198.95.3.3/16"));
+ t.expectTrue(isInNetEx("0:0:0:0:0:0:7f00:1", "0:0:0:0:0:0:7f00:1/32"));
+ t.expectTrue(isInNetEx("3ffe:8311:ffff:abcd:1234:dead:beef:101",
+ "3ffe:8311:ffff::/48"));
+
+ // IPv4 and IPv6 mix.
+ t.expectFalse(isInNetEx("127.0.0.1", "0:0:0:0:0:0:7f00:1/16"));
+ t.expectFalse(isInNetEx("192.168.24.3", "fe80:0:0:0:0:0:c0a8:1803/32"));
+
+ t.expectFalse(isInNetEx("198.95.249.78", "198.95.249.79/32"));
+ t.expectFalse(isInNetEx("198.96.115.10", "198.95.0.0/16"));
+ t.expectFalse(isInNetEx("3fff:8311:ffff:abcd:1234:dead:beef:101",
+ "3ffe:8311:ffff::/48"));
+
+ // Call isInNetEx with wonky arguments.
+ t.expectEquals(null, isInNetEx());
+ t.expectEquals(null, isInNetEx(null));
+ t.expectEquals(null, isInNetEx(null, null));
+ t.expectEquals(null, isInNetEx(null, null, null));
+ t.expectEquals(null, isInNetEx("198.95.249.79"));
+
+ // Invalid IP address.
+ t.expectFalse(isInNetEx("256.0.0.1", "198.95.249.79"));
+ t.expectFalse(isInNetEx("127.0.0.1 ", "127.0.0.1/32")); // Extra space.
+
+ // Invalid prefix.
+ t.expectFalse(isInNetEx("198.95.115.10", "198.95.0.0/34"));
+ t.expectFalse(isInNetEx("127.0.0.1", "127.0.0.1")); // Missing '/' in prefix.
+};
+
+Tests.testWeekdayRange = function(t) {
+ // Test with local time.
+ MockDate.setCurrent("Tue Mar 03 2009");
+ t.expectEquals(true, weekdayRange("MON", "FRI"));
+ t.expectEquals(true, weekdayRange("TUE", "FRI"));
+ t.expectEquals(true, weekdayRange("TUE", "TUE"));
+ t.expectEquals(true, weekdayRange("TUE"));
+ t.expectEquals(false, weekdayRange("WED", "FRI"));
+ t.expectEquals(false, weekdayRange("SUN", "MON"));
+ t.expectEquals(false, weekdayRange("SAT"));
+ t.expectEquals(false, weekdayRange("FRI", "MON"));
+
+ // Test with GMT time.
+ MockDate.setCurrent("Tue Mar 03 2009 GMT");
+ t.expectEquals(true, weekdayRange("MON", "FRI", "GMT"));
+ t.expectEquals(true, weekdayRange("TUE", "FRI", "GMT"));
+ t.expectEquals(true, weekdayRange("TUE", "TUE", "GMT"));
+ t.expectEquals(true, weekdayRange("TUE", "GMT"));
+ t.expectEquals(false, weekdayRange("WED", "FRI", "GMT"));
+ t.expectEquals(false, weekdayRange("SUN", "MON", "GMT"));
+ t.expectEquals(false, weekdayRange("SAT", "GMT"));
+};
+
+Tests.testDateRange = function(t) {
+ // dateRange(day)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(3));
+ t.expectEquals(false, dateRange(1));
+
+ // dateRange(day, "GMT")
+ MockDate.setCurrent("Mar 03 2009 GMT");
+ t.expectEquals(true, dateRange(3, "GMT"));
+ t.expectEquals(false, dateRange(1, "GMT"));
+
+ // dateRange(day1, day2)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(1, 4));
+ t.expectEquals(false, dateRange(4, 20));
+
+ // dateRange(day, month)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(3, "MAR"));
+ MockDate.setCurrent("Mar 03 2014");
+ t.expectEquals(true, dateRange(3, "MAR"));
+ // TODO(eroman):
+ //t.expectEquals(false, dateRange(2, "MAR"));
+ //t.expectEquals(false, dateRange(3, "JAN"));
+
+ // dateRange(day, month, year)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(3, "MAR", 2009));
+ t.expectEquals(false, dateRange(4, "MAR", 2009));
+ t.expectEquals(false, dateRange(3, "FEB", 2009));
+ MockDate.setCurrent("Mar 03 2014");
+ t.expectEquals(false, dateRange(3, "MAR", 2009));
+
+ // dateRange(month1, month2)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange("JAN", "MAR"));
+ t.expectEquals(true, dateRange("MAR", "APR"));
+ t.expectEquals(false, dateRange("MAY", "SEP"));
+
+ // dateRange(day1, month1, day2, month2)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(1, "JAN", 3, "MAR"));
+ t.expectEquals(true, dateRange(3, "MAR", 4, "SEP"));
+ t.expectEquals(false, dateRange(4, "MAR", 4, "SEP"));
+
+ // dateRange(month1, year1, month2, year2)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange("FEB", 2009, "MAR", 2009));
+ MockDate.setCurrent("Apr 03 2009");
+ t.expectEquals(true, dateRange("FEB", 2009, "MAR", 2010));
+ t.expectEquals(false, dateRange("FEB", 2009, "MAR", 2009));
+
+ // dateRange(day1, month1, year1, day2, month2, year2)
+ MockDate.setCurrent("Mar 03 2009");
+ t.expectEquals(true, dateRange(1, "JAN", 2009, 3, "MAR", 2009));
+ t.expectEquals(true, dateRange(3, "MAR", 2009, 4, "SEP", 2009));
+ t.expectEquals(true, dateRange(3, "JAN", 2009, 4, "FEB", 2010));
+ t.expectEquals(false, dateRange(4, "MAR", 2009, 4, "SEP", 2009));
+};
+
+Tests.testTimeRange = function(t) {
+ // timeRange(hour)
+ MockDate.setCurrent("Mar 03, 2009 03:34:01");
+ t.expectEquals(true, timeRange(3));
+ t.expectEquals(false, timeRange(2));
+
+ // timeRange(hour1, hour2)
+ MockDate.setCurrent("Mar 03, 2009 03:34:01");
+ t.expectEquals(true, timeRange(2, 3));
+ t.expectEquals(true, timeRange(2, 4));
+ t.expectEquals(true, timeRange(3, 5));
+ t.expectEquals(false, timeRange(1, 2));
+ t.expectEquals(false, timeRange(11, 12));
+
+ // timeRange(hour1, min1, hour2, min2)
+ MockDate.setCurrent("Mar 03, 2009 03:34:01");
+ t.expectEquals(true, timeRange(1, 0, 3, 34));
+ t.expectEquals(true, timeRange(1, 0, 3, 35));
+ t.expectEquals(true, timeRange(3, 34, 5, 0));
+ t.expectEquals(false, timeRange(1, 0, 3, 0));
+ t.expectEquals(false, timeRange(11, 0, 16, 0));
+
+ // timeRange(hour1, min1, sec1, hour2, min2, sec2)
+ MockDate.setCurrent("Mar 03, 2009 03:34:14");
+ t.expectEquals(true, timeRange(1, 0, 0, 3, 34, 14));
+ t.expectEquals(false, timeRange(1, 0, 0, 3, 34, 0));
+ t.expectEquals(true, timeRange(1, 0, 0, 3, 35, 0));
+ t.expectEquals(true, timeRange(3, 34, 0, 5, 0, 0));
+ t.expectEquals(false, timeRange(1, 0, 0, 3, 0, 0));
+ t.expectEquals(false, timeRange(11, 0, 0, 16, 0, 0));
+};
+
+// --------------------------
+// TestContext
+// --------------------------
+
+// |name| is the name of the test being executed, it will be used when logging
+// errors.
+function TestContext(name) {
+ this.numFailures_ = 0;
+ this.name_ = name;
+};
+
+TestContext.prototype.failed = function() {
+ return this.numFailures_ != 0;
+};
+
+TestContext.prototype.expectEquals = function(expectation, actual) {
+ if (!(expectation === actual)) {
+ this.numFailures_++;
+ this.log("FAIL: expected: " + expectation + ", actual: " + actual);
+ }
+};
+
+TestContext.prototype.expectTrue = function(x) {
+ this.expectEquals(true, x);
+};
+
+TestContext.prototype.expectFalse = function(x) {
+ this.expectEquals(false, x);
+};
+
+TestContext.prototype.log = function(x) {
+ // Prefix with the test name that generated the log.
+ try {
+ alert(this.name_ + ": " + x);
+ } catch(e) {
+ // In case alert() is not defined.
+ }
+};
+
+// --------------------------
+// MockDate
+// --------------------------
+
+function MockDate() {
+ this.wrappedDate_ = new MockDate.super_(MockDate.currentDateString_);
+};
+
+// Setup the MockDate so it forwards methods to "this.wrappedDate_" (which is a
+// real Date object). We can't simply chain the prototypes since Date() doesn't
+// allow it.
+MockDate.init = function() {
+ MockDate.super_ = Date;
+
+ function createProxyMethod(methodName) {
+ return function() {
+ return this.wrappedDate_[methodName]
+ .apply(this.wrappedDate_, arguments);
+ }
+ };
+
+ for (i in MockDate.methodNames_) {
+ var methodName = MockDate.methodNames_[i];
+ // Don't define the closure directly in the loop body, since Javascript's
+ // crazy scoping rules mean |methodName| actually bleeds out of the loop!
+ MockDate.prototype[methodName] = createProxyMethod(methodName);
+ }
+
+ // Replace the native Date() with our mock.
+ Date = MockDate;
+};
+
+// Unfortunately Date()'s methods are non-enumerable, therefore list manually.
+MockDate.methodNames_ = [
+ "toString", "toDateString", "toTimeString", "toLocaleString",
+ "toLocaleDateString", "toLocaleTimeString", "valueOf", "getTime",
+ "getFullYear", "getUTCFullYear", "getMonth", "getUTCMonth",
+ "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours", "getUTCHours",
+ "getMinutes", "getUTCMinutes", "getSeconds", "getUTCSeconds",
+ "getMilliseconds", "getUTCMilliseconds", "getTimezoneOffset", "setTime",
+ "setMilliseconds", "setUTCMilliseconds", "setSeconds", "setUTCSeconds",
+ "setMinutes", "setUTCMinutes", "setHours", "setUTCHours", "setDate",
+ "setUTCDate", "setMonth", "setUTCMonth", "setFullYear", "setUTCFullYear",
+ "toGMTString", "toUTCString", "getYear", "setYear"
+];
+
+MockDate.setCurrent = function(currentDateString) {
+ MockDate.currentDateString_ = currentDateString;
+}
+
+// Bind the methods to proxy requests to the wrapped Date().
+MockDate.init();
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/passthrough.js b/chromium/net/data/proxy_resolver_v8_unittest/passthrough.js
new file mode 100644
index 00000000000..832ac6624b4
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/passthrough.js
@@ -0,0 +1,45 @@
+// Return a single-proxy result, which encodes ALL the arguments that were
+// passed to FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+ if (arguments.length != 2) {
+ throw "Wrong number of arguments passed to FindProxyForURL!";
+ return "FAIL";
+ }
+
+ return "PROXY " + makePseudoHost(url + "." + host);
+}
+
+// Form a string that kind-of resembles a host. We will replace any
+// non-alphanumeric character with a dot, then fix up the oddly placed dots.
+function makePseudoHost(str) {
+ var result = "";
+
+ for (var i = 0; i < str.length; ++i) {
+ var c = str.charAt(i);
+ if (!isValidPseudoHostChar(c)) {
+ c = '.'; // Replace unsupported characters with a dot.
+ }
+
+ // Take care not to place multiple adjacent dots,
+ // a dot at the beginning, or a dot at the end.
+ if (c == '.' &&
+ (result.length == 0 ||
+ i == str.length - 1 ||
+ result.charAt(result.length - 1) == '.')) {
+ continue;
+ }
+ result += c;
+ }
+ return result;
+}
+
+function isValidPseudoHostChar(c) {
+ if (c >= '0' && c <= '9')
+ return true;
+ if (c >= 'a' && c <= 'z')
+ return true;
+ if (c >= 'A' && c <= 'Z')
+ return true;
+ return false;
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/resolve_host.js b/chromium/net/data/proxy_resolver_v8_unittest/resolve_host.js
new file mode 100644
index 00000000000..0461de98628
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/resolve_host.js
@@ -0,0 +1,9 @@
+// Copyright (c) 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.
+
+// This script passes the URL's host to dnsResolveEx().
+function FindProxyForURL(url, host) {
+ dnsResolveEx(host);
+ return 'DIRECT';
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_empty_string.js b/chromium/net/data/proxy_resolver_v8_unittest/return_empty_string.js
new file mode 100644
index 00000000000..3342196a82c
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_empty_string.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return "";
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_function.js b/chromium/net/data/proxy_resolver_v8_unittest/return_function.js
new file mode 100644
index 00000000000..90055530f51
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_function.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return FindProxyForURL;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_integer.js b/chromium/net/data/proxy_resolver_v8_unittest/return_integer.js
new file mode 100644
index 00000000000..d86b2998c78
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_integer.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return 0;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_null.js b/chromium/net/data/proxy_resolver_v8_unittest/return_null.js
new file mode 100644
index 00000000000..6cf90c53a08
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_null.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return null;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_object.js b/chromium/net/data/proxy_resolver_v8_unittest/return_object.js
new file mode 100644
index 00000000000..3824f8a211d
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_object.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return {result: "PROXY foo"};
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_undefined.js b/chromium/net/data/proxy_resolver_v8_unittest/return_undefined.js
new file mode 100644
index 00000000000..0f0aa9891f9
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_undefined.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return undefined;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/return_unicode.js b/chromium/net/data/proxy_resolver_v8_unittest/return_unicode.js
new file mode 100644
index 00000000000..5ecdd1c2fec
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/return_unicode.js
@@ -0,0 +1,4 @@
+// U+200B is the codepoint for zero-width-space.
+function FindProxyForURL(url, host) {
+ return "PROXY foo.com\u200B";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/side_effects.js b/chromium/net/data/proxy_resolver_v8_unittest/side_effects.js
new file mode 100644
index 00000000000..39b3b2d6ca3
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/side_effects.js
@@ -0,0 +1,10 @@
+if (!gCounter) {
+ // We write it this way so if the script gets loaded twice,
+ // gCounter remains dirty.
+ var gCounter = 0;
+}
+
+function FindProxyForURL(url, host) {
+ return "PROXY sideffect_" + gCounter++;
+}
+
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/simple.js b/chromium/net/data/proxy_resolver_v8_unittest/simple.js
new file mode 100644
index 00000000000..c5dfa6d6f56
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/simple.js
@@ -0,0 +1,21 @@
+// PAC script which uses isInNet on both IP addresses and hosts, and calls
+// isResolvable().
+
+function FindProxyForURL(url, host) {
+ var my_ip = myIpAddress();
+
+ if (isInNet(my_ip, "172.16.0.0", "255.248.0.0")) {
+ return "PROXY a:80";
+ }
+
+ if (url.substring(0, 6) != "https:" &&
+ isInNet(host, "10.0.0.0", "255.0.0.0")) {
+ return "PROXY b:80";
+ }
+
+ if (dnsDomainIs(host, "foo.bar.baz.com") || !isResolvable(host)) {
+ return "PROXY c:100";
+ }
+
+ return "DIRECT";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/terminate.js b/chromium/net/data/proxy_resolver_v8_unittest/terminate.js
new file mode 100644
index 00000000000..08d92891907
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/terminate.js
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 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.
+
+function FindProxyForURL(url, host) {
+ if (host != 'hang')
+ return 'PROXY ' + host + ':88';
+
+ var ip = dnsResolve("host1");
+
+ // The following may or may not be executed, even if dnsResolve() terminates
+ // the script execution.
+ dnsResolveEx("host2");
+ dnsResolveEx("host3");
+ alert("hahaha");
+
+ // Hang!
+ for (;;) {}
+
+ // The following definitely won't be executed, since control should never
+ // make it past the preceding hang.
+ dnsResolve("host4");
+ dnsResolve("host5");
+ alert("uhm...");
+ throw "not reached";
+}
diff --git a/chromium/net/data/proxy_resolver_v8_unittest/unhandled_exception.js b/chromium/net/data/proxy_resolver_v8_unittest/unhandled_exception.js
new file mode 100644
index 00000000000..9cc28569e08
--- /dev/null
+++ b/chromium/net/data/proxy_resolver_v8_unittest/unhandled_exception.js
@@ -0,0 +1,5 @@
+function FindProxyForURL(url, host) {
+ // This will throw a runtime exception.
+ return "PROXY x" + undefined_variable;
+}
+
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/404.pac b/chromium/net/data/proxy_script_fetcher_unittest/404.pac
new file mode 100644
index 00000000000..15e6da3e273
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/404.pac
@@ -0,0 +1 @@
+-404.pac-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/404.pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/404.pac.mock-http-headers
new file mode 100644
index 00000000000..d8378a5aacb
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/404.pac.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 404 OK
+Content-Type: application/x-javascript-config
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/500.pac b/chromium/net/data/proxy_script_fetcher_unittest/500.pac
new file mode 100644
index 00000000000..fa4fa3e2b43
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/500.pac
@@ -0,0 +1 @@
+-500.pac-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/500.pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/500.pac.mock-http-headers
new file mode 100644
index 00000000000..48114a5e2d6
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/500.pac.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 500 OK
+Content-Type: application/x-javascript-config
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac b/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac
new file mode 100644
index 00000000000..f71b53af815
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac
@@ -0,0 +1 @@
+-cacheable_1hr.pac-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac.mock-http-headers
new file mode 100644
index 00000000000..5b64f73788c
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/cacheable_1hr.pac.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-javascript-config
+Cache-Control: public, max-age=3600
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac b/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac
new file mode 100644
index 00000000000..596643649a5
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac
@@ -0,0 +1 @@
+-downloadable.pac-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac.mock-http-headers
new file mode 100644
index 00000000000..7efc4adca15
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/downloadable.pac.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-javascript-config
+Content-Disposition: attachment; filename="download-pac.pac"
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac b/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac
new file mode 100644
index 00000000000..f44a604e69b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac
Binary files differ
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac.mock-http-headers
new file mode 100644
index 00000000000..b9a3312dce2
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/gzipped_pac.mock-http-headers
@@ -0,0 +1,3 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-ns-proxy-autoconfig
+Content-Encoding: gzip
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy b/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy
new file mode 100644
index 00000000000..762ca2a61a8
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy
@@ -0,0 +1 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy.mock-http-headers
new file mode 100644
index 00000000000..56019e4833b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/large-pac.nsproxy.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-ns-proxy-autoconfig
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.html b/chromium/net/data/proxy_script_fetcher_unittest/pac.html
new file mode 100644
index 00000000000..7f5a9931e52
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.html
@@ -0,0 +1 @@
+-pac.html-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.html.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/pac.html.mock-http-headers
new file mode 100644
index 00000000000..524e3d8cb9b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy b/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy
new file mode 100644
index 00000000000..7fe7da4fb9b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy
@@ -0,0 +1 @@
+-pac.nsproxy-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy.mock-http-headers
new file mode 100644
index 00000000000..56019e4833b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.nsproxy.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: application/x-ns-proxy-autoconfig
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.txt b/chromium/net/data/proxy_script_fetcher_unittest/pac.txt
new file mode 100644
index 00000000000..9950aa5ba66
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.txt
@@ -0,0 +1 @@
+-pac.txt-
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/pac.txt.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/pac.txt.mock-http-headers
new file mode 100644
index 00000000000..5c695b9136f
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/pac.txt.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: text/plain
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac b/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac
new file mode 100644
index 00000000000..892841f8d5f
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac
Binary files differ
diff --git a/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac.mock-http-headers b/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac.mock-http-headers
new file mode 100644
index 00000000000..1bd08ee771b
--- /dev/null
+++ b/chromium/net/data/proxy_script_fetcher_unittest/utf16be_pac.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=UTF16-BE
diff --git a/chromium/net/data/quic_in_memory_cache_data/quic-datatesturl.com/index.html b/chromium/net/data/quic_in_memory_cache_data/quic-datatesturl.com/index.html
new file mode 100644
index 00000000000..041f56d8870
--- /dev/null
+++ b/chromium/net/data/quic_in_memory_cache_data/quic-datatesturl.com/index.html
@@ -0,0 +1,14 @@
+HTTP/1.1 200 OK
+Date: Sat, 22 Jun 2013 01:21:01 GMT
+Server: Apache/2.2.22 (Ubuntu)
+Last-Modified: Fri, 21 Jun 2013 22:20:24 GMT
+ETag: "2a9d2-98-4dfb17827450a"
+Accept-Ranges: bytes
+Vary: Accept-Encoding
+Content-Encoding: None
+Keep-Alive: timeout=5, max=100
+Connection: Keep-Alive
+Content-Type: text/html
+X-Original-Url: http://quic.test.url/index.html
+
+This is a test page.
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-ee-by-1024-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-1024-rsa-intermediate.pem
new file mode 100644
index 00000000000..725bfce68dc
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-1024-rsa-intermediate.pem
@@ -0,0 +1,50 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=1024 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:de:52:f9:7c:4e:29:e3:35:24:63:74:83:70:5b:
+ 90:75:75:53:65:cb:74:a1:8e:b8:5a:bf:17:d0:0d:
+ a5:73:af:13:08:94:e4:ad:c3:d3:5c:bd:fd:b0:f7:
+ 0b:97:55:bd:7b:4d:87:54:d3:6b:0f:3f:1c:b9:fc:
+ 3b:65:20:29:4e:18:f0:77:fe:13:bb:b3:25:30:e4:
+ 3e:04:fa:49:4a:45:d0:80:a0:de:ba:1c:c9:be:ca:
+ 83:cc:94:18:f9:16:ab:3d:0f:43:28:b2:2c:d9:cf:
+ 43:17:17:48:9a:8e:6f:7a:53:46:4e:aa:ff:89:7b:
+ a8:0e:e4:04:41:b8:45:a5:4f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 99:ed:12:d1:1c:a1:a4:f6:88:30:23:40:69:af:14:ba:7b:e9:
+ 24:ef:01:a9:6f:b0:77:78:91:49:f7:84:17:84:ca:63:e1:71:
+ 8c:52:50:b7:20:06:fc:58:c9:20:d4:fa:78:89:5f:da:0b:2d:
+ 88:c9:41:ed:a6:52:c6:f8:dc:c9:1a:11:c7:ab:0a:ae:80:e4:
+ 7c:15:41:ac:64:63:3e:93:e5:ad:dc:de:e8:dc:d6:ca:0a:fd:
+ 01:01:d7:4d:4a:dd:c6:93:7c:52:ad:61:18:09:8c:29:ae:6b:
+ 42:d1:3a:98:d8:14:45:e4:d7:e1:b2:1e:12:92:99:65:6c:13:
+ 03:46
+-----BEGIN CERTIFICATE-----
+MIICDTCCAXYCAgDtMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTEwMjQgcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIy
+NDc1M1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3lL5fE4p4zUkY3SD
+cFuQdXVTZct0oY64Wr8X0A2lc68TCJTkrcPTXL39sPcLl1W9e02HVNNrDz8cufw7
+ZSApThjwd/4Tu7MlMOQ+BPpJSkXQgKDeuhzJvsqDzJQY+RarPQ9DKLIs2c9DFxdI
+mo5velNGTqr/iXuoDuQEQbhFpU8CAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATAN
+BgkqhkiG9w0BAQUFAAOBgQCZ7RLRHKGk9ogwI0BprxS6e+kk7wGpb7B3eJFJ94QX
+hMpj4XGMUlC3IAb8WMkg1Pp4iV/aCy2IyUHtplLG+NzJGhHHqwqugOR8FUGsZGM+
+k+Wt3N7o3NbKCv0BAddNSt3Gk3xSrWEYCYwprmtC0TqY2BRF5Nfhsh4SkpllbBMD
+Rg==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-ee-by-2048-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-2048-rsa-intermediate.pem
new file mode 100644
index 00000000000..69482c31305
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-2048-rsa-intermediate.pem
@@ -0,0 +1,59 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:f0:6b:dc:59:dc:ed:79:a3:68:4f:10:00:8b:31:
+ cd:aa:44:34:b9:3b:94:6c:4c:d1:05:3a:ba:bc:93:
+ f6:d6:89:00:ec:71:10:ed:9b:11:84:10:80:d4:51:
+ 30:fe:3e:17:bd:d7:e2:89:7b:22:80:ca:73:75:98:
+ e3:67:cd:fd:c4:c8:d1:7a:95:ae:f3:98:95:45:06:
+ 91:3c:6f:dc:37:e5:4d:29:9a:e1:99:9b:a3:6b:b5:
+ 74:be:1d:f8:97:92:27:1c:15:fb:5b:cb:e4:88:e7:
+ 10:ff:2f:8a:59:fd:59:53:76:0f:57:83:db:27:45:
+ 0a:00:b0:4f:3a:e9:4d:24:5f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 5f:a8:ec:ff:b6:09:b6:6e:42:82:c5:72:9e:ba:d5:d2:7b:36:
+ 06:fc:c3:8c:ce:ad:47:18:60:4c:ce:aa:08:fc:7d:85:df:e4:
+ d7:44:86:89:be:36:bc:04:c9:be:5e:af:85:f5:92:7c:ec:96:
+ 24:1a:11:c3:e7:8f:bc:79:51:c0:44:4e:4b:d3:d9:3a:6c:7c:
+ 2c:80:21:d3:22:db:2f:e8:04:94:0d:b0:13:32:ea:d8:26:bd:
+ e8:01:c0:ff:57:6a:2d:86:a1:f1:e0:a2:8b:39:73:7a:8b:e9:
+ 0b:ee:a1:e8:45:56:f7:45:fa:7f:a1:c4:e6:9a:32:c8:2e:03:
+ c2:57:f8:f9:be:c1:af:82:39:73:7a:39:e7:8c:c2:36:5c:47:
+ 5b:c7:20:e2:b1:5f:06:1e:34:c7:f3:e0:4f:a9:b5:34:79:83:
+ a0:b2:79:70:a2:b7:1c:a3:1b:7d:7c:e3:24:57:90:0e:3a:43:
+ b9:98:68:97:70:17:dc:31:c7:e1:be:2b:d4:a7:1f:97:8a:e4:
+ 57:58:2f:b3:c6:27:3b:1b:f4:f3:11:0e:4c:31:73:5e:22:c9:
+ e0:07:7b:0e:87:06:ba:f9:10:f5:bb:4d:5e:cf:14:ab:57:6b:
+ 76:8e:bd:66:2d:39:4b:8a:42:00:3e:0b:5e:dc:39:69:89:ca:
+ 5a:37:4f:bc
+-----BEGIN CERTIFICATE-----
+MIICjjCCAXYCAgDtMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTIwNDggcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIy
+NDc1M1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA8GvcWdzteaNoTxAA
+izHNqkQ0uTuUbEzRBTq6vJP21okA7HEQ7ZsRhBCA1FEw/j4XvdfiiXsigMpzdZjj
+Z839xMjRepWu85iVRQaRPG/cN+VNKZrhmZuja7V0vh34l5InHBX7W8vkiOcQ/y+K
+Wf1ZU3YPV4PbJ0UKALBPOulNJF8CAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATAN
+BgkqhkiG9w0BAQUFAAOCAQEAX6js/7YJtm5CgsVynrrV0ns2BvzDjM6tRxhgTM6q
+CPx9hd/k10SGib42vATJvl6vhfWSfOyWJBoRw+ePvHlRwEROS9PZOmx8LIAh0yLb
+L+gElA2wEzLq2Ca96AHA/1dqLYah8eCiizlzeovpC+6h6EVW90X6f6HE5poyyC4D
+wlf4+b7Br4I5c3o554zCNlxHW8cg4rFfBh40x/PgT6m1NHmDoLJ5cKK3HKMbfXzj
+JFeQDjpDuZhol3AX3DHH4b4r1Kcfl4rkV1gvs8YnOxv08xEOTDFzXiLJ4Ad7DocG
+uvkQ9btNXs8Uq1drdo69Zi05S4pCAD4LXtw5aYnKWjdPvA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-ee-by-768-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-768-rsa-intermediate.pem
new file mode 100644
index 00000000000..1b278f90e2e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-768-rsa-intermediate.pem
@@ -0,0 +1,47 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=768 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:b5:21:de:d6:d7:1b:b3:ba:1d:9b:cd:89:56:7f:
+ c4:82:cb:86:34:d0:27:3a:95:d9:57:bc:e8:74:e6:
+ cd:01:1a:8c:72:07:68:5b:ad:4e:2a:91:e0:50:e7:
+ 23:34:c7:c8:18:d9:7f:e9:f1:a8:09:2e:eb:e1:3f:
+ 26:5a:b5:9a:9d:50:82:fb:30:4a:b3:f4:d3:1d:3c:
+ 90:5b:67:dc:92:eb:70:78:4f:c4:62:b8:7b:93:3f:
+ 70:56:6d:18:4b:a5:63:03:d0:15:c6:94:f5:ed:3c:
+ 5b:0d:7e:0a:85:0b:26:2f:0d:c9:68:08:42:33:94:
+ a2:81:7d:b0:34:12:f2:d3:db
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ ac:96:12:2e:32:99:ba:13:11:ba:67:e3:8c:71:03:4e:ea:3f:
+ 03:d4:88:a1:eb:93:83:dd:53:02:3b:df:9f:f5:7c:f6:49:e8:
+ 71:c0:2a:2e:75:c1:53:9e:be:a1:0a:13:21:0c:e2:44:2f:a0:
+ 12:6e:ed:f5:60:43:2d:79:e8:6d:58:e6:c8:f1:b0:64:04:31:
+ 0e:f7:61:7c:a0:35:f1:d6:b3:67:b2:6c:e4:5c:6e:ec:02:43:
+ a5:7e:00:d8:bf:a7
+-----BEGIN CERTIFICATE-----
+MIIB6zCCAXUCAgDtMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNVBAMMHDc2OCByc2Eg
+VGVzdCBpbnRlcm1lZGlhdGUgQ0EwHhcNMTExMjEyMjI0NzUzWhcNMjExMjA5MjI0
+NzUzWjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE
+BwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3
+LjAuMC4xMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1Id7W1xuzuh2bzYlW
+f8SCy4Y00Cc6ldlXvOh05s0BGoxyB2hbrU4qkeBQ5yM0x8gY2X/p8agJLuvhPyZa
+tZqdUIL7MEqz9NMdPJBbZ9yS63B4T8RiuHuTP3BWbRhLpWMD0BXGlPXtPFsNfgqF
+CyYvDcloCEIzlKKBfbA0EvLT2wIDAQABoxMwETAPBgNVHREECDAGhwR/AAABMA0G
+CSqGSIb3DQEBBQUAA2EArJYSLjKZuhMRumfjjHEDTuo/A9SIoeuTg91TAjvfn/V8
+9knoccAqLnXBU56+oQoTIQziRC+gEm7t9WBDLXnobVjmyPGwZAQxDvdhfKA18daz
+Z7Js5Fxu7AJDpX4A2L+n
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-ee-by-prime256v1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..1032f821997
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
@@ -0,0 +1,44 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=prime256v1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:cc:ca:df:89:1f:e5:aa:3c:2e:d1:62:85:de:2a:
+ 4c:bd:68:89:d0:05:06:56:30:f5:6e:93:a4:4f:74:
+ c0:7e:14:5f:1b:dd:93:33:ea:b7:17:8d:63:3e:c4:
+ 2e:2c:b3:0c:62:37:0f:1b:90:16:8e:73:b5:90:3f:
+ c5:e2:08:9d:b6:8f:80:e0:95:3d:28:d3:8f:d7:b7:
+ 0e:8b:43:8c:95:29:a9:51:3b:6d:0d:35:ea:c1:ff:
+ d8:8b:47:71:7b:3b:4c:65:7f:a9:85:43:6e:43:1f:
+ 9d:9c:ff:15:d2:ac:29:db:5f:56:42:88:4a:68:0d:
+ 49:73:0a:fd:b2:94:58:95:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:45:02:20:07:1e:96:23:74:f5:77:05:b4:a9:fa:2f:8c:b3:
+ 3c:48:89:46:38:f0:0d:18:76:d2:9c:ee:cd:2d:8d:ce:7a:71:
+ 02:21:00:ff:b1:8b:0d:40:a4:ef:1b:9d:ba:d6:92:30:04:38:
+ 21:b1:ad:53:61:71:83:01:9a:3c:3e:ef:d4:53:59:c7:df
+-----BEGIN CERTIFICATE-----
+MIIB0zCCAXoCAgDtMAkGByqGSM49BAEwMDEuMCwGA1UEAwwlcHJpbWUyNTZ2MSBl
+Y2RzYSBUZXN0IGludGVybWVkaWF0ZSBDQTAeFw0xMTEyMTIyMjQ3NTNaFw0yMTEy
+MDkyMjQ3NTNaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQD
+DAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMzK34kf5ao8
+LtFihd4qTL1oidAFBlYw9W6TpE90wH4UXxvdkzPqtxeNYz7ELiyzDGI3DxuQFo5z
+tZA/xeIInbaPgOCVPSjTj9e3DotDjJUpqVE7bQ016sH/2ItHcXs7TGV/qYVDbkMf
+nZz/FdKsKdtfVkKISmgNSXMK/bKUWJX3AgMBAAGjEzARMA8GA1UdEQQIMAaHBH8A
+AAEwCQYHKoZIzj0EAQNIADBFAiAHHpYjdPV3BbSp+i+MszxIiUY48A0YdtKc7s0t
+jc56cQIhAP+xiw1ApO8bnbrWkjAEOCGxrVNhcYMBmjw+79RTWcff
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-ee-by-secp256k1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..2d47aced565
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
@@ -0,0 +1,44 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=secp256k1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 10 01:51:17 2011 GMT
+ Not After : Dec 9 01:51:17 2012 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:9b:1b:ad:af:0e:61:db:3f:dc:b7:91:5d:bf:1f:
+ 0a:70:6a:fa:89:b7:6e:fc:aa:ef:ce:9e:db:6c:4c:
+ 9a:2d:81:7b:59:96:20:eb:11:ef:e4:85:c6:ca:33:
+ 41:22:4a:20:86:9c:01:02:f9:63:13:9b:3b:1e:f5:
+ a9:3e:40:98:8e:78:1f:99:32:64:2f:4c:dc:ae:3a:
+ e7:cf:00:22:2f:77:f2:be:7b:64:9c:a0:92:27:b1:
+ 35:4d:44:de:7b:cd:75:4a:a7:9b:27:e0:3c:0b:13:
+ ee:57:5a:f7:c2:81:c0:b8:ea:0b:39:b5:6f:17:57:
+ 24:f0:c0:c5:4b:b3:0d:92:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:46:02:21:00:83:ef:77:11:e7:67:3c:53:20:88:b6:03:10:
+ e8:e5:9b:a1:12:48:3a:1e:a8:3b:31:fa:1b:56:95:28:d3:6e:
+ 6b:02:21:00:cd:e3:2c:6e:41:59:e2:6a:d4:ec:de:11:99:99:
+ e6:b7:7e:90:89:91:e5:35:d1:2c:c7:15:e7:46:94:ab:11:6f
+-----BEGIN CERTIFICATE-----
+MIIB0zCCAXkCAgDtMAkGByqGSM49BAEwLzEtMCsGA1UEAwwkc2VjcDI1NmsxIGVj
+ZHNhIFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMDAxNTExN1oXDTEyMTIw
+OTAxNTExN1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAU
+BgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMM
+CTEyNy4wLjAuMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmxutrw5h2z/c
+t5Fdvx8KcGr6ibdu/Krvzp7bbEyaLYF7WZYg6xHv5IXGyjNBIkoghpwBAvljE5s7
+HvWpPkCYjngfmTJkL0zcrjrnzwAiL3fyvntknKCSJ7E1TUTee811SqebJ+A8CxPu
+V1r3woHAuOoLObVvF1ck8MDFS7MNkm8CAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAA
+ATAJBgcqhkjOPQQBA0kAMEYCIQCD73cR52c8UyCItgMQ6OWboRJIOh6oOzH6G1aV
+KNNuawIhAM3jLG5BWeJq1OzeEZmZ5rd+kImR5TXRLMcV50aUqxFv
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/1024-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/1024-rsa-intermediate.pem
new file mode 100644
index 00000000000..54f02176cee
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/1024-rsa-intermediate.pem
@@ -0,0 +1,63 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: CN=1024 rsa Test intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:bf:d2:b4:44:b4:7a:03:64:91:ca:2d:cf:7b:22:
+ bd:02:f3:5d:b7:97:fa:72:91:cd:c8:9c:4f:e1:6a:
+ 52:08:67:3b:2c:06:63:89:c0:0a:cd:e1:80:56:88:
+ 27:93:12:32:d6:47:6e:e5:34:ac:9e:f4:17:f3:c8:
+ b1:29:65:3f:75:5a:01:c9:2a:63:48:d7:8a:13:a4:
+ 08:ae:8c:a4:95:e4:78:2d:35:ff:73:5e:49:d3:1d:
+ 14:f7:c7:7c:56:9c:ff:c9:f9:d4:bf:44:c7:4d:57:
+ 71:4d:7d:64:3d:7e:ec:9f:eb:69:50:7b:34:9c:8d:
+ 99:0a:4f:26:90:1e:b5:06:8d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 67:2E:75:A1:D2:3D:9D:36:78:59:C4:20:6E:8C:2F:AD:75:5C:16:8B
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 44:49:10:c4:22:4f:57:bb:c2:29:c7:77:21:48:a8:1d:ca:7a:
+ 86:c3:24:3d:7a:f6:05:3b:77:75:5f:54:6f:04:8c:c0:89:e9:
+ 17:4f:84:c7:23:33:fb:23:d8:f8:c5:46:e4:cc:e1:69:6b:4f:
+ b3:a9:1d:36:6c:92:c4:55:c7:73:bd:ff:5b:56:b5:80:f5:a7:
+ 2c:50:73:04:76:44:b0:ad:61:1e:bc:d0:78:88:77:74:94:1d:
+ 2d:1d:4f:3b:15:11:47:02:d6:a4:af:f4:8b:75:39:bf:bf:41:
+ 41:77:db:e1:7d:28:8f:da:66:88:0e:4a:53:81:30:e2:18:3f:
+ e6:09:8c:d3:98:00:50:ac:d7:24:08:bf:2d:2c:20:b2:74:06:
+ 2c:3e:9b:3c:e6:3c:72:08:b0:6d:49:a9:b9:26:67:17:d4:ed:
+ 95:48:71:72:5d:fe:8d:0a:0f:31:e6:bd:15:ec:e1:36:65:c1:
+ b0:00:45:ae:bc:d2:13:0d:ac:6e:4f:8c:f4:29:2c:b1:a0:cb:
+ aa:a4:ff:21:92:5b:8d:f7:23:9b:df:18:f6:cb:a4:4b:6c:56:
+ e6:bf:e1:74:2e:74:30:8a:d6:ab:16:c3:f2:b2:21:4b:04:a5:
+ dd:c5:c4:98:71:4d:ae:47:43:d6:af:8f:d3:4c:13:44:e0:3d:
+ d3:4f:68:6a
+-----BEGIN CERTIFICATE-----
+MIICgjCCAWqgAwIBAgICAO0wDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UEAwwVMjA0
+OCBSU0EgVGVzdCBSb290IENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIyNDc1
+M1owKDEmMCQGA1UEAwwdMTAyNCByc2EgVGVzdCBpbnRlcm1lZGlhdGUgQ0EwgZ8w
+DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL/StES0egNkkcotz3sivQLzXbeX+nKR
+zcicT+FqUghnOywGY4nACs3hgFaIJ5MSMtZHbuU0rJ70F/PIsSllP3VaAckqY0jX
+ihOkCK6MpJXkeC01/3NeSdMdFPfHfFac/8n51L9Ex01XcU19ZD1+7J/raVB7NJyN
+mQpPJpAetQaNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFGcu
+daHSPZ02eFnEIG6ML611XBaLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUF
+AAOCAQEAREkQxCJPV7vCKcd3IUioHcp6hsMkPXr2BTt3dV9UbwSMwInpF0+ExyMz
++yPY+MVG5MzhaWtPs6kdNmySxFXHc73/W1a1gPWnLFBzBHZEsK1hHrzQeIh3dJQd
+LR1POxURRwLWpK/0i3U5v79BQXfb4X0oj9pmiA5KU4Ew4hg/5gmM05gAUKzXJAi/
+LSwgsnQGLD6bPOY8cgiwbUmpuSZnF9TtlUhxcl3+jQoPMea9FezhNmXBsABFrrzS
+Ew2sbk+M9CkssaDLqqT/IZJbjfcjm98Y9sukS2xW5r/hdC50MIrWqxbD8rIhSwSl
+3cXEmHFNrkdD1q+P00wTROA9009oag==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2029_globalsign_com_cert.pem b/chromium/net/data/ssl/certificates/2029_globalsign_com_cert.pem
new file mode 100644
index 00000000000..cd0d9882388
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2029_globalsign_com_cert.pem
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFTjCCBDagAwIBAgILAQAAAAABJbCbWRgwDQYJKoZIhvcNAQELBQAwcDEmMCQG
+A1UECxMdRXh0ZW5kZWQgVmFsaWRhdGlvbiBTSEEyNTYgQ0ExEzARBgNVBAoTCkds
+b2JhbFNpZ24xMTAvBgNVBAMTKEdsb2JhbFNpZ24gRXh0ZW5kZWQgVmFsaWRhdGlv
+biBTSEEyNTYgQ0EwHhcNMDkxMjIxMDkwNTA4WhcNMTExMjIxMDkwNTA4WjCB4TEb
+MBkGA1UEDxMSVjEuMCwgQ2xhdXNlIDUuKGIpMRcwFQYDVQQFEw4wMTEwLTAxLTA0
+MDE4MTETMBEGCysGAQQBgjc8AgEDEwJKUDELMAkGA1UEBhMCSlAxDjAMBgNVBAgT
+BVRPS1lPMRAwDgYDVQQHEwdTaGlidXlhMRkwFwYDVQQJExAyMC0xIHNha3VyYWdh
+b2thMRIwEAYDVQQLEwlUZWNobmljYWwxGDAWBgNVBAoTD0dsb2JhbFNpZ24gSy5L
+LjEcMBoGA1UEAxMTMjAyOS5nbG9iYWxzaWduLmNvbTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAK1O+qRY8VTYBzAU+RkWEli4/0uTMtYJmR138O7iOrog
+3rRPfAZouTz9zZf8aKCp2S1VAHufuxzn1GhQRdlI6fnyDEjJoUpDo4mJbJFUghLt
+bwnFIsScSHdqaTKx0N99xRR9SJbEshfeIszBWTdLsUdBDzj64YiBg9qJMfPynPGC
+dbbf4crM/28jXaiaB/AJ8lv1AAstW8bps+y6ZHrZqG9i3llfetpe+4Dg9XqUQAD4
+0OBNIn8fD2CXk916wiN2JfnZjSOStTKSsy5zXlx5mgMFkSMtOkqbJDgbjcYmajA2
+YDnzlSbz9oYL4jv/NZ+A2aJzoqbP7tef88P/cN+/XXECAwEAAaOCAXUwggFxMB0G
+A1UdDgQWBBRZvNlp97Blu8g0xdLC7xd4pkceizAfBgNVHSMEGDAWgBSK/BQbPaNZ
+Z6U74XOSpmKRf+R4MDBUBggrBgEFBQcBAQRIMEYwRAYIKwYBBQUHMAKGOGh0dHA6
+Ly9zZWN1cmUuZ2xvYmFsc2lnbi5uZXQvY2FjZXJ0L1NIQTI1NmV4dGVuZHZhbDEu
+Y3J0MD8GA1UdHwQ4MDYwNKAyoDCGLmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQv
+U0hBMjU2RXh0ZW5kVmFsMS5jcmwwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBPAw
+HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEsGA1UdIAREMEIwQAYJKwYB
+BAGgMgEBMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuZ2xvYmFsc2lnbi5uZXQv
+cmVwb3NpdG9yeS8wEQYJYIZIAYb4QgEBBAQDAgbAMA0GCSqGSIb3DQEBCwUAA4IB
+AQBCfnYnxZkYxp3amwmrrCAwac5SYVK4bDwxKtAHa9NbOGV6jAXFNIknttAuwy9I
+w7h4E5HHnZeDVZpWQ1ncmMqkMPfYruThLGx8GOMThigkMHOZLPx+WmB4alDKj9Ra
+SOISmiIHMfw7RKPZuL3dttzr477DVukILeUjfKeQ9dlQwAyeMNH840jrOmyVl54R
+nfFnOL7O46NqJBrC0cNp5bdyzVJ77qZ29r7e3+uJ61OeCzovImyPrgGDy96rhxaB
+ZjdXGKH2nlFQ3SpsH5QMVr8kn8a1etoIGexJ8L7FdSu3ywvyjaIPOANI/Y0gTlax
+vIkF1Dc5EejvaEMyVwrVTbzJ
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-ee-by-1024-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-1024-rsa-intermediate.pem
new file mode 100644
index 00000000000..259786e9833
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-1024-rsa-intermediate.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=1024 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:ae:52:d9:c5:51:d2:0f:bf:ee:b1:7c:a0:4e:c7:
+ a3:b5:e5:bf:72:f1:53:d5:dc:9e:2e:0b:72:5e:06:
+ 77:71:ee:0d:be:93:9b:b2:77:d4:b6:f8:e2:f9:3c:
+ e1:0e:6a:7a:35:4f:74:df:f0:b9:f6:b1:85:dd:ef:
+ 13:0c:67:df:25:eb:ee:21:70:e5:39:e9:61:5f:ad:
+ e7:42:17:69:a4:dd:2b:47:99:33:71:63:3e:0d:6a:
+ 36:97:01:4b:a2:e8:32:41:a5:87:91:af:f8:3e:e8:
+ 8c:e2:f1:86:8f:0f:7b:98:30:56:55:75:8b:82:75:
+ a2:ef:26:da:5b:83:12:5f:ee:92:08:cc:a0:64:30:
+ 9d:56:30:9c:64:79:34:a0:09:a0:c5:e0:47:a1:89:
+ e7:d3:43:b7:b4:13:26:bc:a2:50:75:07:9f:98:67:
+ 22:ba:e9:00:da:96:ee:2c:2a:d9:b5:2f:7f:7f:70:
+ 3f:26:d7:45:28:eb:90:de:f7:37:89:c7:9d:a2:06:
+ 81:30:7f:c2:cf:8f:4c:3d:c4:43:48:6d:5d:2a:c4:
+ e6:c5:5a:b4:54:55:e0:87:65:67:24:f8:e1:af:c0:
+ 5e:e4:99:0d:55:c8:8a:d8:73:3a:9c:b3:3d:91:51:
+ b8:4e:16:d0:ca:36:92:01:4f:b9:a2:d6:da:39:8d:
+ 4e:71
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 00:05:e5:a4:4b:3d:15:1d:26:0f:42:2c:69:ce:b1:c4:fc:33:
+ 07:d9:50:66:25:57:2f:57:e9:7f:9e:dd:df:bf:74:2e:c1:49:
+ f5:d3:3c:17:0b:80:86:9f:11:a5:95:8d:88:f3:43:ce:68:04:
+ 96:4a:59:05:4a:3b:f8:df:f1:e1:e3:48:ae:ab:f5:a9:9d:ce:
+ 04:a5:9b:90:f5:9d:f4:c2:49:6b:e4:34:2b:91:85:2a:ae:c1:
+ 7b:b8:3d:6d:27:0e:ad:24:0a:33:31:dd:b9:cd:00:04:e7:8d:
+ 39:34:3a:3c:fc:4d:a8:2d:06:13:71:0e:03:29:31:c4:25:5f:
+ cd:0c
+-----BEGIN CERTIFICATE-----
+MIICkTCCAfoCAgDuMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTEwMjQgcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1NFoXDTIxMTIwOTIy
+NDc1NFowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5S2cVR0g+/
+7rF8oE7Ho7Xlv3LxU9Xcni4Lcl4Gd3HuDb6Tm7J31Lb44vk84Q5qejVPdN/wufax
+hd3vEwxn3yXr7iFw5TnpYV+t50IXaaTdK0eZM3FjPg1qNpcBS6LoMkGlh5Gv+D7o
+jOLxho8Pe5gwVlV1i4J1ou8m2luDEl/ukgjMoGQwnVYwnGR5NKAJoMXgR6GJ59ND
+t7QTJryiUHUHn5hnIrrpANqW7iwq2bUvf39wPybXRSjrkN73N4nHnaIGgTB/ws+P
+TD3EQ0htXSrE5sVatFRV4IdlZyT44a/AXuSZDVXIithzOpyzPZFRuE4W0Mo2kgFP
+uaLW2jmNTnECAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUF
+AAOBgQAABeWkSz0VHSYPQixpzrHE/DMH2VBmJVcvV+l/nt3fv3QuwUn10zwXC4CG
+nxGllY2I80POaASWSlkFSjv43/Hh40iuq/Wpnc4EpZuQ9Z30wklr5DQrkYUqrsF7
+uD1tJw6tJAozMd25zQAE5405NDo8/E2oLQYTcQ4DKTHEJV/NDA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-ee-by-2048-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-2048-rsa-intermediate.pem
new file mode 100644
index 00000000000..f23d6367198
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-2048-rsa-intermediate.pem
@@ -0,0 +1,71 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:9f:da:64:07:a7:c4:a4:31:eb:21:18:56:5f:8d:
+ bb:f1:ac:dd:2b:a1:45:e9:42:e1:a3:5c:6a:91:a4:
+ f1:78:ac:e9:c1:0a:c3:87:59:5a:51:f5:04:47:73:
+ 5b:f8:47:b5:ab:81:d5:30:7e:13:e7:67:be:fa:14:
+ 33:79:49:92:2a:fc:ec:61:3e:38:74:64:d5:e6:1b:
+ 53:51:5f:56:a4:c2:d3:57:13:0d:e1:c0:66:f8:49:
+ eb:bb:e5:8e:4c:dc:b1:38:57:84:05:f4:a8:6c:e2:
+ 51:1c:10:3f:d5:9d:ec:d5:db:b9:7d:0b:fc:b9:19:
+ 07:28:ed:63:98:33:d1:7b:eb:59:2e:9e:16:ff:2d:
+ aa:cf:8e:0e:2d:9c:5c:40:7c:6b:1a:9a:0a:5a:15:
+ 02:d2:f1:93:c6:89:79:dd:93:e8:0b:01:7f:95:f3:
+ 23:78:9c:69:77:ae:27:a9:67:4f:03:91:13:6f:01:
+ 7f:e9:8f:f8:d9:44:be:e4:c2:e7:b1:06:47:05:0e:
+ 13:98:48:a3:45:6f:ff:c5:17:1d:2c:cb:7f:c8:a2:
+ 5d:6b:53:e3:9b:45:81:5a:b6:43:49:1c:1f:07:b5:
+ 69:30:52:64:73:d7:3c:e7:48:df:12:db:a5:17:35:
+ 0d:45:44:0b:68:f4:52:8d:b1:5a:d7:b5:9a:ce:8d:
+ 8e:cf
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 08:ef:7a:ce:d2:0b:a8:1e:34:a2:d5:ee:b0:7a:b4:b4:fc:ba:
+ 76:3b:f4:92:21:42:80:85:49:1f:b1:dd:77:45:85:97:5d:22:
+ a8:c9:e3:d9:54:a2:df:57:ae:e9:a0:fb:1a:bd:69:9b:dd:df:
+ 58:8a:38:c8:59:92:9c:f3:31:d0:23:5c:cb:e8:7a:c2:8b:60:
+ 31:bd:9c:51:05:92:30:13:43:c9:f1:51:54:21:61:e5:3a:8d:
+ 4f:d2:2d:93:5c:dc:ed:51:30:6a:6f:30:0f:21:09:45:e2:34:
+ 03:c7:d0:78:83:d6:78:72:fc:6d:37:2f:e1:ca:25:df:18:79:
+ 99:c0:5e:b7:a3:ec:a6:b3:08:e6:9a:00:2c:a7:4d:01:20:d5:
+ f1:7f:62:fe:e2:33:5e:60:6a:87:0c:df:24:83:67:99:e1:dc:
+ eb:e3:59:12:15:1c:a8:dc:99:4c:fe:b6:99:37:a1:b0:90:b4:
+ c7:ff:0f:70:99:f9:94:c8:f1:fc:bf:45:fd:d5:98:1e:b6:4f:
+ 14:7e:1a:c6:f0:d2:26:1e:d7:d3:ed:45:23:6e:19:68:4c:2d:
+ cb:23:d6:9b:bd:47:eb:5b:64:14:c6:13:0a:5d:99:6d:d2:5f:
+ 17:dc:f2:d9:5b:f5:ad:2f:41:f0:3a:4c:ee:e1:86:d9:f3:57:
+ d9:0c:34:4c
+-----BEGIN CERTIFICATE-----
+MIIDEjCCAfoCAgDuMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTIwNDggcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1NFoXDTIxMTIwOTIy
+NDc1NFowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/aZAenxKQx
+6yEYVl+Nu/Gs3SuhRelC4aNcapGk8Xis6cEKw4dZWlH1BEdzW/hHtauB1TB+E+dn
+vvoUM3lJkir87GE+OHRk1eYbU1FfVqTC01cTDeHAZvhJ67vljkzcsThXhAX0qGzi
+URwQP9Wd7NXbuX0L/LkZByjtY5gz0XvrWS6eFv8tqs+ODi2cXEB8axqaCloVAtLx
+k8aJed2T6AsBf5XzI3icaXeuJ6lnTwORE28Bf+mP+NlEvuTC57EGRwUOE5hIo0Vv
+/8UXHSzLf8iiXWtT45tFgVq2Q0kcHwe1aTBSZHPXPOdI3xLbpRc1DUVEC2j0Uo2x
+Wte1ms6Njs8CAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUF
+AAOCAQEACO96ztILqB40otXusHq0tPy6djv0kiFCgIVJH7Hdd0WFl10iqMnj2VSi
+31eu6aD7Gr1pm93fWIo4yFmSnPMx0CNcy+h6wotgMb2cUQWSMBNDyfFRVCFh5TqN
+T9Itk1zc7VEwam8wDyEJReI0A8fQeIPWeHL8bTcv4col3xh5mcBet6PsprMI5poA
+LKdNASDV8X9i/uIzXmBqhwzfJINnmeHc6+NZEhUcqNyZTP62mTehsJC0x/8PcJn5
+lMjx/L9F/dWYHrZPFH4axvDSJh7X0+1FI24ZaEwtyyPWm71H61tkFMYTCl2ZbdJf
+F9zy2Vv1rS9B8DpM7uGG2fNX2Qw0TA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-ee-by-768-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-768-rsa-intermediate.pem
new file mode 100644
index 00000000000..29e5eb71ba0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-768-rsa-intermediate.pem
@@ -0,0 +1,59 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=768 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:f1:de:da:de:0d:99:88:05:1e:96:f3:3c:4d:11:
+ 1e:c6:b4:47:9a:ff:74:4c:ba:ae:2b:8d:30:e7:d5:
+ 15:e2:08:70:58:33:51:44:03:cb:c4:9c:67:dd:24:
+ 65:51:10:f8:a7:f2:8a:38:d7:82:99:2c:04:98:da:
+ 48:d6:2b:b7:dd:1f:af:7f:26:d1:19:67:10:36:54:
+ a0:fe:f2:7c:17:40:d9:18:2e:cf:82:ef:d6:c8:c5:
+ 5c:1f:ca:6c:34:f8:7f:2c:f1:a2:8e:5e:81:53:b6:
+ 54:98:b6:90:50:60:96:fd:82:20:fb:4c:25:5b:61:
+ 52:dc:af:55:6c:55:f6:46:b0:33:b6:65:4f:0a:ff:
+ 94:6c:c8:27:0a:bb:cd:44:f2:45:ff:a1:ba:56:c3:
+ d9:a6:ad:64:fc:ab:95:28:6e:a8:80:1b:66:37:44:
+ 71:1e:6f:1f:5c:32:bd:c6:d1:97:ce:08:1a:15:fc:
+ a8:20:e3:cd:2a:5f:b4:49:7f:aa:2f:36:f1:3e:45:
+ 33:80:63:38:a6:b2:c5:35:2a:5c:58:a2:19:e0:6a:
+ 43:50:77:0b:7a:a2:2e:29:5e:c0:7d:32:f4:94:bd:
+ 7d:40:b0:95:d2:35:a8:98:88:a2:68:4a:f2:c8:45:
+ ab:9b:04:24:7b:d3:47:7a:e0:5d:d9:b6:aa:da:4e:
+ 3e:7b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 7b:3d:57:0d:58:2c:23:2a:24:b8:35:ee:0a:05:fb:39:fe:77:
+ 4a:38:17:63:72:ea:1c:6f:f6:2e:a8:3c:11:c3:0f:fb:f5:e1:
+ e5:ce:64:7f:63:e5:99:e0:dc:39:90:a4:4c:ca:4f:ad:39:ba:
+ e6:eb:89:e9:78:43:50:6d:c6:99:6f:ea:50:76:83:32:da:50:
+ 7b:55:f0:a5:73:f8:69:25:9e:89:0d:1d:fd:b7:1e:19:96:40:
+ ae:ff:9b:cc:7b:16
+-----BEGIN CERTIFICATE-----
+MIICbzCCAfkCAgDuMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNVBAMMHDc2OCByc2Eg
+VGVzdCBpbnRlcm1lZGlhdGUgQ0EwHhcNMTExMjEyMjI0NzUzWhcNMjExMjA5MjI0
+NzUzWjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE
+BwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3
+LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8d7a3g2ZiAUe
+lvM8TREexrRHmv90TLquK40w59UV4ghwWDNRRAPLxJxn3SRlURD4p/KKONeCmSwE
+mNpI1iu33R+vfybRGWcQNlSg/vJ8F0DZGC7Pgu/WyMVcH8psNPh/LPGijl6BU7ZU
+mLaQUGCW/YIg+0wlW2FS3K9VbFX2RrAztmVPCv+UbMgnCrvNRPJF/6G6VsPZpq1k
+/KuVKG6ogBtmN0RxHm8fXDK9xtGXzggaFfyoIOPNKl+0SX+qLzbxPkUzgGM4prLF
+NSpcWKIZ4GpDUHcLeqIuKV7AfTL0lL19QLCV0jWomIiiaEryyEWrmwQke9NHeuBd
+2baq2k4+ewIDAQABoxMwETAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBBQUA
+A2EAez1XDVgsIyokuDXuCgX7Of53SjgXY3LqHG/2Lqg8EcMP+/Xh5c5kf2PlmeDc
+OZCkTMpPrTm65uuJ6XhDUG3GmW/qUHaDMtpQe1XwpXP4aSWeiQ0d/bceGZZArv+b
+zHsW
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-ee-by-prime256v1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..7b80a7c44dd
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
@@ -0,0 +1,56 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=prime256v1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:92:b8:c2:a0:68:77:27:d3:07:af:d4:3f:4a:d9:
+ 56:08:ec:12:e5:d1:70:0a:1f:c1:29:b9:20:2b:65:
+ 1a:de:b3:25:72:5c:3b:72:28:9e:11:d9:9f:f5:2a:
+ 8a:f5:1e:f2:d0:cd:fd:8b:ba:2f:22:19:8e:34:f8:
+ 8a:c5:df:f0:d7:c9:d9:cb:fc:18:94:9f:5d:8c:e7:
+ c3:33:f7:b5:18:dd:48:24:16:1c:56:19:92:cf:1e:
+ 1a:aa:45:2e:85:23:57:f1:b2:6c:04:e8:75:a2:92:
+ 15:c9:02:64:96:b0:86:14:26:ab:6f:c1:4d:db:07:
+ 7b:16:61:7e:d7:26:83:5f:0b:b2:5e:08:5b:63:30:
+ 38:bf:50:92:3c:cc:57:fd:05:91:4a:f4:99:1b:a3:
+ 4b:de:99:08:2f:e4:b6:4e:e2:74:ee:35:5e:8a:9d:
+ 32:4d:60:c6:89:cf:c8:34:1b:73:95:dc:58:75:fd:
+ fd:fb:83:08:78:09:fe:09:58:f3:c2:64:95:14:28:
+ bd:92:c4:a4:25:9d:4e:a9:07:4b:ca:7c:ec:2b:e3:
+ 8b:f1:2e:bd:64:d4:53:c7:4a:4e:0a:b3:65:df:08:
+ fe:37:20:db:87:8d:cc:67:62:4a:98:b2:ed:47:9a:
+ 77:26:da:b2:03:2f:59:5d:a2:41:35:78:b1:b2:85:
+ 4a:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:46:02:21:00:94:ad:24:93:85:3b:9f:cf:67:8c:cc:01:31:
+ ed:8c:23:ed:79:fa:3d:88:b0:57:e1:29:71:f2:d1:15:90:cc:
+ 73:02:21:00:a8:66:ce:74:c8:3f:e7:71:c2:08:46:54:5b:7b:
+ 0a:a3:76:1b:13:0b:9c:0e:8d:13:c0:01:6d:2b:34:93:86:7e
+-----BEGIN CERTIFICATE-----
+MIICWDCCAf4CAgDuMAkGByqGSM49BAEwMDEuMCwGA1UEAwwlcHJpbWUyNTZ2MSBl
+Y2RzYSBUZXN0IGludGVybWVkaWF0ZSBDQTAeFw0xMTEyMTIyMjQ3NTRaFw0yMTEy
+MDkyMjQ3NTRaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQD
+DAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCSuMKg
+aHcn0wev1D9K2VYI7BLl0XAKH8EpuSArZRresyVyXDtyKJ4R2Z/1Kor1HvLQzf2L
+ui8iGY40+IrF3/DXydnL/BiUn12M58Mz97UY3UgkFhxWGZLPHhqqRS6FI1fxsmwE
+6HWikhXJAmSWsIYUJqtvwU3bB3sWYX7XJoNfC7JeCFtjMDi/UJI8zFf9BZFK9Jkb
+o0vemQgv5LZO4nTuNV6KnTJNYMaJz8g0G3OV3Fh1/f37gwh4Cf4JWPPCZJUUKL2S
+xKQlnU6pB0vKfOwr44vxLr1k1FPHSk4Ks2XfCP43INuHjcxnYkqYsu1Hmncm2rID
+L1ldokE1eLGyhUoVAgMBAAGjEzARMA8GA1UdEQQIMAaHBH8AAAEwCQYHKoZIzj0E
+AQNJADBGAiEAlK0kk4U7n89njMwBMe2MI+15+j2IsFfhKXHy0RWQzHMCIQCoZs50
+yD/nccIIRlRbewqjdhsTC5wOjRPAAW0rNJOGfg==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-ee-by-secp256k1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..d23179db870
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
@@ -0,0 +1,56 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=secp256k1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 10 01:51:17 2011 GMT
+ Not After : Dec 9 01:51:17 2012 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:e7:10:f2:68:0c:18:a5:e5:dd:a8:4b:2f:6b:f5:
+ 71:f4:bf:dd:ef:39:69:04:38:3d:52:c5:e7:cc:b3:
+ eb:98:57:13:4e:3e:79:cf:80:4b:d7:9d:7e:88:f3:
+ a9:02:47:b8:d9:ec:8a:8c:34:20:aa:29:3b:a1:d6:
+ 45:23:b5:6d:36:56:3c:a4:64:13:ee:23:70:09:fa:
+ 75:83:c6:b7:be:b5:b3:3f:80:cb:ce:7b:18:1f:ac:
+ 7c:25:b6:58:bc:07:b7:35:77:2b:64:1e:ca:14:0b:
+ d0:bb:6c:6e:1d:2f:ee:10:90:a1:ce:a9:ab:88:0a:
+ 28:74:ae:ae:ca:fc:da:c3:3a:ba:39:de:c8:1b:46:
+ bf:93:98:a2:5b:ba:b2:a6:d8:bd:54:52:be:52:31:
+ fa:07:3a:6d:8f:42:c2:92:80:31:5c:ae:cb:15:f0:
+ 72:cf:f6:5c:b9:f2:6b:91:b0:03:48:08:ae:a6:8d:
+ e4:bd:a1:f6:05:38:1c:70:43:b6:7d:34:b5:c1:b9:
+ 0b:f7:ec:71:0c:a4:20:92:2b:0f:c0:41:80:16:84:
+ 64:98:6d:13:38:df:ce:82:98:8c:ac:97:56:10:6d:
+ f8:e1:d5:19:ed:b7:60:44:c8:9e:72:61:1f:16:3b:
+ 81:13:a8:c3:99:99:47:ba:81:68:af:2a:39:80:c5:
+ 88:1b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:45:02:21:00:b4:35:4d:5d:8b:9c:bd:ea:be:86:13:75:ab:
+ d0:af:cc:fb:39:85:20:0b:2d:a7:84:d7:ce:8f:44:54:b6:6a:
+ 7c:02:20:62:49:3e:32:da:e2:fe:bb:f3:db:8d:f6:78:de:0a:
+ 46:e3:93:87:1c:e7:b1:6f:81:9e:1d:b2:3b:5e:a2:2c:7b
+-----BEGIN CERTIFICATE-----
+MIICVjCCAf0CAgDuMAkGByqGSM49BAEwLzEtMCsGA1UEAwwkc2VjcDI1NmsxIGVj
+ZHNhIFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMDAxNTExN1oXDTEyMTIw
+OTAxNTExN1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAU
+BgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMM
+CTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOcQ8mgM
+GKXl3ahLL2v1cfS/3e85aQQ4PVLF58yz65hXE04+ec+AS9edfojzqQJHuNnsiow0
+IKopO6HWRSO1bTZWPKRkE+4jcAn6dYPGt761sz+Ay857GB+sfCW2WLwHtzV3K2Qe
+yhQL0Ltsbh0v7hCQoc6pq4gKKHSursr82sM6ujneyBtGv5OYolu6sqbYvVRSvlIx
++gc6bY9CwpKAMVyuyxXwcs/2XLnya5GwA0gIrqaN5L2h9gU4HHBDtn00tcG5C/fs
+cQykIJIrD8BBgBaEZJhtEzjfzoKYjKyXVhBt+OHVGe23YETInnJhHxY7gROow5mZ
+R7qBaK8qOYDFiBsCAwEAAaMTMBEwDwYDVR0RBAgwBocEfwAAATAJBgcqhkjOPQQB
+A0gAMEUCIQC0NU1di5y96r6GE3Wr0K/M+zmFIAstp4TXzo9EVLZqfAIgYkk+Mtri
+/rvz2432eN4KRuOThxznsW+Bnh2yO16iLHs=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/2048-rsa-intermediate.pem
new file mode 100644
index 00000000000..8a5bc3d8f6c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-intermediate.pem
@@ -0,0 +1,75 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 238 (0xee)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: CN=2048 rsa Test intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:ca:0a:cb:c7:e3:8d:05:87:73:4b:a2:fc:31:a3:
+ 87:21:2a:d6:0f:83:d0:3d:c2:d8:d2:77:62:28:05:
+ fe:06:ad:b9:c5:e9:97:87:b3:90:f2:e5:13:45:47:
+ b0:9e:8b:bd:1b:7f:f9:35:7f:25:f6:bc:5d:c1:dc:
+ 6c:42:49:56:70:69:d7:55:a0:75:e0:68:7a:c8:8d:
+ 51:07:15:66:3d:30:3f:20:bb:e6:20:df:d0:5b:f1:
+ 4b:fd:0a:3f:d7:88:01:20:4f:21:f7:84:61:36:e5:
+ ca:63:c5:e8:d8:50:a5:a0:43:f5:a7:4c:f7:89:9b:
+ 2b:40:0e:bf:78:6c:33:85:87:73:78:1a:bc:2f:e2:
+ a8:ae:81:1f:89:e7:a0:88:96:d5:b5:bf:9a:68:d2:
+ 44:42:fa:af:f5:d9:82:93:97:c7:df:a6:22:11:16:
+ 90:06:14:0f:fb:84:a1:75:ba:2a:2b:ba:5b:4f:cf:
+ f0:21:81:9f:66:a7:88:70:64:5c:27:96:b4:9a:e1:
+ 53:a3:e5:25:0b:60:fa:48:05:6b:b1:73:ee:94:f2:
+ 9b:be:8b:01:f6:14:0c:3c:a2:28:01:64:6c:81:86:
+ 25:ac:0d:e8:df:37:3c:2c:a9:42:a3:b9:42:19:95:
+ a9:2f:aa:35:0c:13:4f:e2:1a:b3:6e:dc:4d:c6:28:
+ 09:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 48:6A:51:0C:3A:1F:20:7F:BD:4C:6B:0A:11:1F:A4:16:84:FB:83:C5
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 3a:f6:e3:fc:14:01:a3:89:91:84:7b:cb:50:e5:64:0a:a4:40:
+ d6:ad:40:26:53:29:28:c3:17:3b:12:42:8a:e8:bf:78:4a:77:
+ d2:65:08:0e:50:75:c9:fd:9e:13:de:f0:95:44:35:4a:98:50:
+ f6:ce:24:6b:e1:8c:a4:56:04:3e:4c:c7:f2:a8:07:bb:94:64:
+ ec:63:cb:e9:0d:a0:96:d8:d0:25:d5:22:cc:c7:a3:79:31:23:
+ 24:8a:24:9c:0c:a2:6c:05:a9:49:80:07:21:4f:f4:84:0f:34:
+ 9e:64:15:ed:3b:b9:ae:fc:3f:d8:06:92:b7:01:56:99:1f:91:
+ a6:13:06:11:9e:5b:66:71:30:ca:d2:44:6d:6f:8b:98:75:57:
+ 62:01:3b:47:aa:3e:ac:ea:97:00:24:5e:95:44:39:c3:df:cd:
+ f3:61:22:74:3f:64:31:11:31:b7:6e:a6:36:90:ee:9e:07:ca:
+ b9:81:e4:c7:fe:a9:ed:16:4f:f2:bc:14:1e:ef:79:44:23:33:
+ 42:e3:ab:eb:71:5e:ef:14:43:f8:29:48:7a:b4:40:80:6e:b5:
+ c0:de:d4:db:42:01:0f:9f:88:43:72:7e:76:01:72:a3:25:c1:
+ 2f:47:59:7e:a0:c8:e0:06:98:e0:47:9b:e9:98:55:5f:92:3e:
+ 7d:41:79:6a
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe6gAwIBAgICAO4wDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UEAwwVMjA0
+OCBSU0EgVGVzdCBSb290IENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIyNDc1
+M1owKDEmMCQGA1UEAwwdMjA0OCByc2EgVGVzdCBpbnRlcm1lZGlhdGUgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKCsvH440Fh3NLovwxo4chKtYP
+g9A9wtjSd2IoBf4GrbnF6ZeHs5Dy5RNFR7Cei70bf/k1fyX2vF3B3GxCSVZwaddV
+oHXgaHrIjVEHFWY9MD8gu+Yg39Bb8Uv9Cj/XiAEgTyH3hGE25cpjxejYUKWgQ/Wn
+TPeJmytADr94bDOFh3N4Grwv4qiugR+J56CIltW1v5po0kRC+q/12YKTl8ffpiIR
+FpAGFA/7hKF1uiorultPz/AhgZ9mp4hwZFwnlrSa4VOj5SULYPpIBWuxc+6U8pu+
+iwH2FAw8oigBZGyBhiWsDejfNzwsqUKjuUIZlakvqjUME0/iGrNu3E3GKAlvAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEhqUQw6HyB/vUxrChEf
+pBaE+4PFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAOvbj/BQB
+o4mRhHvLUOVkCqRA1q1AJlMpKMMXOxJCiui/eEp30mUIDlB1yf2eE97wlUQ1SphQ
+9s4ka+GMpFYEPkzH8qgHu5Rk7GPL6Q2gltjQJdUizMejeTEjJIoknAyibAWpSYAH
+IU/0hA80nmQV7Tu5rvw/2AaStwFWmR+RphMGEZ5bZnEwytJEbW+LmHVXYgE7R6o+
+rOqXACRelUQ5w9/N82EidD9kMRExt26mNpDungfKuYHkx/6p7RZP8rwUHu95RCMz
+QuOr63Fe7xRD+ClIerRAgG61wN7U20IBD5+IQ3J+dgFyoyXBL0dZfqDI4AaY4Eeb
+6ZhVX5I+fUF5ag==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/2048-rsa-root.pem b/chromium/net/data/ssl/certificates/2048-rsa-root.pem
new file mode 100644
index 00000000000..ef7dde1f517
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/2048-rsa-root.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvDCCAaQCCQCTFvHGgBv5DTANBgkqhkiG9w0BAQUFADAgMR4wHAYDVQQDDBUy
+MDQ4IFJTQSBUZXN0IFJvb3QgQ0EwHhcNMTExMjEyMjI0NzQwWhcNMjExMjA5MjI0
+NzQwWjAgMR4wHAYDVQQDDBUyMDQ4IFJTQSBUZXN0IFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3qdvYWmdiR80bRDk+xLGgAsjeqFJJQbSx
+jyBmB9BP4vf14FZrwezRt83RDqE7PlbWZ7BAG0MZ2SsuY6ZTu20zeoqMGaFPLWA9
+nqSI+DIoc5cARpMDFvZJaA1ip9NQw9kZrgGBgCUkYQpQyQ/ZKP3eU7dlDT6szj8g
+sZh0aQqelIDLo+KGan32ICPorV6gjSOlklw5io6/h5rf6UpCmoON0WWkO2+rXsgW
+OcJ5lzoCRAk7uPvbe0MklJlaCGtnfDI0ftH+uB54odT/cGp+k9ok7o8qvM/nNDOq
+i+jWqwr4XzdroqN7DnqOj14Luq1SfuwTdVQohUiJBjDrA2ZZbd0jAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggEBAII1PYV5jW0uK4t9aNbAaK6M2L+WCRUQrc+xZqbMqkK0
+L7ZFOuRt0uDq/fp3dlF16KoIsMKHkpGqxF0ipbvhLpH2olW52dMQQ/D5Kb84DZ+g
+etjutHQWHon2xCGFPfnl1i04hiKg4XVuupGhu2rR4HFqerc0HXFHcnlDQ7BlQ1+I
+Ug8GcOwc2lXbnB8cfDCmOnpUNUd1D3bNEZUy+YhvqxYlKLgp9xc1mWfEyW/8Caqx
++XNntHZ7xEWBX+OJu3Sg7iBLUUGzckl6KJ6DxMam3Bi334zgKW/dlML8vJ7vnkG+
+mpvuz+9C6Dq5r2VVkLrAHjGLLPAiiXUvoXOEitKsTQs=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-ee-by-1024-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-ee-by-1024-rsa-intermediate.pem
new file mode 100644
index 00000000000..5556aa2a19e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-ee-by-1024-rsa-intermediate.pem
@@ -0,0 +1,47 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=1024 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:99:15:16:6e:0f:58:af:87:bc:b8:83:ae:3a:57:
+ 85:f9:9c:70:f8:b7:9b:e2:17:c6:5e:4b:c2:4b:e3:
+ 94:ba:c3:5d:85:91:2d:cb:73:6d:ee:9b:76:9f:b1:
+ ce:34:cc:9f:73:75:00:1f:d7:cf:66:e9:a4:cc:8b:
+ 93:2e:b6:15:15:16:c0:7f:eb:70:00:ed:9c:f1:19:
+ d3:8e:38:60:bb:39:68:38:68:5b:06:67:84:13:7b:
+ 5a:69:71:82:a7:90:99
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 98:23:c2:af:db:c3:76:a7:5d:c2:29:ae:03:be:30:6d:aa:c9:
+ ef:01:04:a8:9d:45:ad:45:4b:f1:7f:6e:bb:7d:ee:41:d6:a6:
+ da:65:e1:07:28:05:fd:35:ca:89:25:a4:c3:3a:49:8e:d5:2f:
+ fe:95:8a:26:a7:82:5b:ea:b7:c6:85:bf:3f:03:1c:d5:90:e4:
+ 40:95:12:3c:1a:8f:ef:1a:ef:f3:ac:4b:05:21:63:4a:d8:4f:
+ 5f:4a:9a:b2:6e:b2:8b:d5:3a:93:0a:c9:84:c0:3e:9a:ac:b7:
+ b3:a6:36:fb:fe:6a:9a:5a:10:fc:be:40:09:ac:2b:d6:93:30:
+ b3:50
+-----BEGIN CERTIFICATE-----
+MIIB6TCCAVICAgDsMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTEwMjQgcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIy
+NDc1M1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCZFRZuD1ivh7y4g646V4X5
+nHD4t5viF8ZeS8JL45S6w12FkS3Lc23um3afsc40zJ9zdQAf189m6aTMi5MuthUV
+FsB/63AA7ZzxGdOOOGC7OWg4aFsGZ4QTe1ppcYKnkJkCAwEAAaMTMBEwDwYDVR0R
+BAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOBgQCYI8Kv28N2p13CKa4DvjBtqsnv
+AQSonUWtRUvxf267fe5B1qbaZeEHKAX9NcqJJaTDOkmO1S/+lYomp4Jb6rfGhb8/
+AxzVkORAlRI8Go/vGu/zrEsFIWNK2E9fSpqybrKL1TqTCsmEwD6arLezpjb7/mqa
+WhD8vkAJrCvWkzCzUA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-ee-by-2048-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-ee-by-2048-rsa-intermediate.pem
new file mode 100644
index 00000000000..7385c612cc8
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-ee-by-2048-rsa-intermediate.pem
@@ -0,0 +1,56 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:d8:33:27:fc:e4:01:aa:68:42:d1:6d:26:ae:8f:
+ e5:9e:85:a7:af:98:86:9a:bc:ad:a9:c6:81:75:fb:
+ 2d:fc:ce:84:16:bc:02:d7:93:37:98:f1:c7:9e:b5:
+ 5a:ce:45:92:2d:0f:fd:79:07:16:36:ef:63:e2:7b:
+ 81:02:1f:ee:ea:2b:68:66:75:d0:51:29:ce:77:cd:
+ db:06:29:e2:83:41:86:90:98:3a:a3:21:b6:82:ad:
+ 0a:b7:c8:04:fd:b5:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 1c:93:4c:b9:cf:c2:3f:7e:b7:09:1c:d9:14:ca:36:f7:a6:93:
+ c7:99:09:77:ce:0e:a9:1b:ca:19:90:20:2f:3b:ca:62:5c:3d:
+ 28:75:b1:f4:be:99:3b:2d:d6:bf:df:8a:4d:81:7d:9c:a2:16:
+ 7d:93:73:a1:61:cb:13:63:88:94:20:fb:87:a4:4b:c8:d5:5b:
+ 77:07:7a:3a:d8:c8:fb:f1:76:2d:68:5b:7b:69:37:74:4a:96:
+ 32:39:5d:99:18:10:80:6a:ee:43:6d:72:74:69:85:1a:9f:ee:
+ 4d:f7:b0:cd:b9:0b:c3:1b:b0:76:3a:53:6f:9b:b4:f6:8c:af:
+ f7:b7:33:b2:d6:18:94:3b:ae:db:22:72:0b:d7:ea:d3:3d:6d:
+ db:50:78:e8:60:2f:04:aa:f3:68:23:43:fe:83:b7:be:39:54:
+ 0a:06:df:b9:0f:13:56:0b:ba:cf:dd:f0:ca:c1:d6:f6:a7:15:
+ e9:c8:20:fe:a0:46:86:2a:2b:26:cd:0b:9c:0c:0d:a3:84:3a:
+ bf:ae:60:65:88:2f:e5:6d:d6:e5:d7:e0:75:3d:00:73:65:ae:
+ dd:f1:2b:1d:ff:5c:9d:58:db:07:4b:c7:3a:78:06:6a:d1:73:
+ 30:d8:bf:cb:9b:1d:e3:fc:f8:42:49:08:4b:e8:f9:67:c6:fd:
+ f9:34:54:a1
+-----BEGIN CERTIFICATE-----
+MIICajCCAVICAgDsMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTIwNDggcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIy
+NDc1M1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQDYMyf85AGqaELRbSauj+We
+haevmIaavK2pxoF1+y38zoQWvALXkzeY8ceetVrORZItD/15BxY272Pie4ECH+7q
+K2hmddBRKc53zdsGKeKDQYaQmDqjIbaCrQq3yAT9tS8CAwEAAaMTMBEwDwYDVR0R
+BAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOCAQEAHJNMuc/CP363CRzZFMo296aT
+x5kJd84OqRvKGZAgLzvKYlw9KHWx9L6ZOy3Wv9+KTYF9nKIWfZNzoWHLE2OIlCD7
+h6RLyNVbdwd6OtjI+/F2LWhbe2k3dEqWMjldmRgQgGruQ21ydGmFGp/uTfewzbkL
+wxuwdjpTb5u09oyv97czstYYlDuu2yJyC9fq0z1t21B46GAvBKrzaCND/oO3vjlU
+CgbfuQ8TVgu6z93wysHW9qcV6cgg/qBGhiorJs0LnAwNo4Q6v65gZYgv5W3W5dfg
+dT0Ac2Wu3fErHf9cnVjbB0vHOngGatFzMNi/y5sd4/z4QkkIS+j5Z8b9+TRUoQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-ee-by-768-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-ee-by-768-rsa-intermediate.pem
new file mode 100644
index 00000000000..bec71b2b320
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-ee-by-768-rsa-intermediate.pem
@@ -0,0 +1,44 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=768 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:a2:72:21:2e:6e:fa:d0:4e:6c:13:b8:7c:c4:e4:
+ 7b:c8:e9:ab:d3:b8:ce:f5:9f:f6:c2:25:39:08:c4:
+ a0:c8:a9:30:43:d4:e3:fa:f1:23:57:9a:93:51:ec:
+ 00:7d:a1:85:22:2d:cf:75:b7:c4:60:f7:e0:e9:6d:
+ ac:45:e5:eb:15:a5:27:5c:f6:a9:3d:87:7b:82:dc:
+ 5a:39:65:03:8b:ee:32:55:f7:2a:52:fa:a8:07:5e:
+ 31:de:d7:02:74:bc:01
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 70:6a:2e:12:b8:3e:49:6a:f4:5b:c1:57:54:b8:fd:5b:0a:20:
+ d1:c7:71:35:2f:61:3a:64:25:9f:9f:f2:88:d0:10:f6:08:a5:
+ 0f:19:b1:ba:ee:a2:21:ff:da:ca:d1:1e:41:54:8a:e4:c2:4b:
+ 53:aa:dc:5f:46:aa:66:13:6f:3e:65:c5:f3:05:ea:a3:7c:fc:
+ e9:89:b3:9a:8d:c1:e9:98:61:33:1c:5e:64:e3:aa:e2:25:03:
+ fb:70:58:9b:81:99
+-----BEGIN CERTIFICATE-----
+MIIBxzCCAVECAgDsMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNVBAMMHDc2OCByc2Eg
+VGVzdCBpbnRlcm1lZGlhdGUgQ0EwHhcNMTExMjEyMjI0NzUzWhcNMjExMjA5MjI0
+NzUzWjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE
+BwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3
+LjAuMC4xMHwwDQYJKoZIhvcNAQEBBQADawAwaAJhAKJyIS5u+tBObBO4fMTke8jp
+q9O4zvWf9sIlOQjEoMipMEPU4/rxI1eak1HsAH2hhSItz3W3xGD34OltrEXl6xWl
+J1z2qT2He4LcWjllA4vuMlX3KlL6qAdeMd7XAnS8AQIDAQABoxMwETAPBgNVHREE
+CDAGhwR/AAABMA0GCSqGSIb3DQEBBQUAA2EAcGouErg+SWr0W8FXVLj9Wwog0cdx
+NS9hOmQln5/yiNAQ9gilDxmxuu6iIf/aytEeQVSK5MJLU6rcX0aqZhNvPmXF8wXq
+o3z86Ymzmo3B6ZhhMxxeZOOq4iUD+3BYm4GZ
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-ee-by-prime256v1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..c2937098259
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-ee-by-prime256v1-ecdsa-intermediate.pem
@@ -0,0 +1,42 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=prime256v1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:9f:be:cd:ca:eb:45:e5:2a:77:c6:d2:82:b8:22:
+ 9b:44:d5:d8:04:34:af:8e:35:ba:83:d2:fa:0d:64:
+ e4:1c:4e:1b:34:8b:db:a2:1b:67:36:fb:8d:ac:3b:
+ 52:71:aa:77:63:e7:a5:3b:a5:1c:ae:7a:7f:1e:8c:
+ 98:ac:e8:19:67:ca:a0:fc:fb:df:57:3c:6c:b1:85:
+ bb:7c:a6:15:df:13:82:34:e7:84:7b:75:c7:69:ed:
+ f3:8f:77:63:af:6f:29
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:45:02:20:76:9c:1b:cf:23:dd:36:94:7a:8e:76:80:b8:4a:
+ f1:c9:d0:d0:ca:5c:81:57:e3:cf:21:43:63:72:03:56:f8:5d:
+ 02:21:00:fe:01:ba:f2:5e:ad:ca:2f:56:2f:b3:6d:82:cd:72:
+ 9c:22:2d:0e:10:04:e0:55:e0:d3:c7:4b:a1:60:8d:2c:2b
+-----BEGIN CERTIFICATE-----
+MIIBrzCCAVYCAgDsMAkGByqGSM49BAEwMDEuMCwGA1UEAwwlcHJpbWUyNTZ2MSBl
+Y2RzYSBUZXN0IGludGVybWVkaWF0ZSBDQTAeFw0xMTEyMTIyMjQ3NTNaFw0yMTEy
+MDkyMjQ3NTNaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQD
+DAkxMjcuMC4wLjEwfDANBgkqhkiG9w0BAQEFAANrADBoAmEAn77NyutF5Sp3xtKC
+uCKbRNXYBDSvjjW6g9L6DWTkHE4bNIvbohtnNvuNrDtScap3Y+elO6Ucrnp/HoyY
+rOgZZ8qg/PvfVzxssYW7fKYV3xOCNOeEe3XHae3zj3djr28pAgMBAAGjEzARMA8G
+A1UdEQQIMAaHBH8AAAEwCQYHKoZIzj0EAQNIADBFAiB2nBvPI902lHqOdoC4SvHJ
+0NDKXIFX488hQ2NyA1b4XQIhAP4BuvJercovVi+zbYLNcpwiLQ4QBOBV4NPHS6Fg
+jSwr
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-ee-by-secp256k1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..5825106a1c7
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-ee-by-secp256k1-ecdsa-intermediate.pem
@@ -0,0 +1,42 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=secp256k1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 10 01:51:16 2011 GMT
+ Not After : Dec 9 01:51:16 2012 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:cb:5e:34:c8:77:3c:55:25:ee:1c:68:96:0c:2c:
+ 48:2b:ed:83:ca:91:12:37:ea:71:ff:bc:c8:de:16:
+ 03:0c:cf:b8:40:ff:3c:43:1f:10:ab:bf:d8:e4:8f:
+ c1:82:cf:66:7d:c0:aa:c6:e6:3d:74:65:2d:df:f3:
+ f7:e1:f0:c0:4a:f8:eb:b8:5d:63:ff:78:67:b7:c6:
+ 1b:24:33:6d:0f:9c:39:86:72:41:03:26:8f:e0:55:
+ 1d:1c:72:a5:38:15:8f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:44:02:20:72:f6:48:3b:5d:88:f4:fc:50:c8:74:21:a6:f2:
+ c4:f7:d0:40:69:a1:48:93:98:36:fe:36:16:ec:95:a6:28:12:
+ 02:20:48:e4:7e:32:a0:4b:c0:4d:08:5f:c8:63:f9:67:7f:2d:
+ dc:78:77:78:ec:0e:a2:ee:78:60:d9:07:7d:b3:0a:d3
+-----BEGIN CERTIFICATE-----
+MIIBrTCCAVUCAgDsMAkGByqGSM49BAEwLzEtMCsGA1UEAwwkc2VjcDI1NmsxIGVj
+ZHNhIFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMDAxNTExNloXDTEyMTIw
+OTAxNTExNlowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAU
+BgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMM
+CTEyNy4wLjAuMTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQDLXjTIdzxVJe4caJYM
+LEgr7YPKkRI36nH/vMjeFgMMz7hA/zxDHxCrv9jkj8GCz2Z9wKrG5j10ZS3f8/fh
+8MBK+Ou4XWP/eGe3xhskM20PnDmGckEDJo/gVR0ccqU4FY8CAwEAAaMTMBEwDwYD
+VR0RBAgwBocEfwAAATAJBgcqhkjOPQQBA0cAMEQCIHL2SDtdiPT8UMh0IabyxPfQ
+QGmhSJOYNv42FuyVpigSAiBI5H4yoEvATQhfyGP5Z38t3Hh3eOwOou54YNkHfbMK
+0w==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/768-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/768-rsa-intermediate.pem
new file mode 100644
index 00000000000..3ad84e5ae88
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/768-rsa-intermediate.pem
@@ -0,0 +1,60 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: CN=768 rsa Test intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (768 bit)
+ Modulus (768 bit):
+ 00:cc:6a:db:92:b7:bf:b3:fa:c0:b3:54:8e:3d:26:
+ 08:e6:a3:d0:ca:e2:75:86:25:27:0d:f4:67:e4:9f:
+ 90:0e:2f:4d:d4:15:af:c9:53:1c:44:f4:2e:90:6c:
+ 82:9b:b6:d2:59:0d:89:6a:f8:4a:c4:37:39:4f:c9:
+ 08:f1:c1:ed:e4:51:74:0c:b7:a6:2a:cf:ba:f5:47:
+ 96:6c:09:ac:d3:e4:3c:fe:ec:6f:63:60:ad:7d:ee:
+ 33:d1:cd:4f:15:f4:a7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ D5:89:AE:A8:B3:CF:6A:F1:0C:AF:E0:11:2F:C8:59:12:A6:31:E9:3E
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 1f:a3:25:77:24:2e:b9:92:5b:06:4c:fe:31:a8:fd:86:23:97:
+ 03:ab:8f:50:7f:44:0b:6a:77:ff:0d:c8:0e:d2:98:e4:26:9c:
+ cc:26:ed:5f:1f:9c:40:0e:9e:e5:8d:1d:7f:7c:cb:2e:64:fc:
+ 8a:81:a6:d9:9d:05:4d:a1:45:76:d2:78:2a:4e:d9:5e:8c:59:
+ b3:cd:be:3b:ae:09:8c:e2:a6:a0:0e:c5:28:93:b1:bb:c1:76:
+ c1:f3:d7:d7:22:e3:25:d4:1c:cc:ae:4a:6e:1b:ae:98:3b:c5:
+ bb:dc:92:87:8b:a3:92:91:b2:23:84:1f:7f:cb:f9:85:42:d2:
+ 85:ff:a7:90:69:e0:26:ae:3f:fd:cb:7f:a6:e9:b9:3f:7f:54:
+ 3f:c7:b3:98:15:e3:22:da:c3:e7:ab:d1:b8:00:62:a2:26:9f:
+ 59:7f:51:b5:c5:10:5a:0e:a4:be:bd:22:26:5b:fc:d6:2d:32:
+ 13:04:ae:28:32:ac:e5:10:7a:81:79:a9:84:ca:67:6c:74:31:
+ 64:07:4e:3e:4f:6f:c4:e9:90:7f:a8:f5:b4:f9:65:4d:35:fa:
+ ab:92:1b:2f:b5:49:c7:73:38:3a:c7:92:f0:16:bb:a5:55:b8:
+ d9:79:4f:98:4e:6e:2a:4d:88:a3:c4:23:5c:c4:3f:ee:f7:26:
+ 56:38:1e:11
+-----BEGIN CERTIFICATE-----
+MIICXTCCAUWgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UEAwwVMjA0
+OCBSU0EgVGVzdCBSb290IENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIyNDc1
+M1owJzElMCMGA1UEAwwcNzY4IHJzYSBUZXN0IGludGVybWVkaWF0ZSBDQTB8MA0G
+CSqGSIb3DQEBAQUAA2sAMGgCYQDMatuSt7+z+sCzVI49Jgjmo9DK4nWGJScN9Gfk
+n5AOL03UFa/JUxxE9C6QbIKbttJZDYlq+ErENzlPyQjxwe3kUXQMt6Yqz7r1R5Zs
+CazT5Dz+7G9jYK197jPRzU8V9KcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd
+BgNVHQ4EFgQU1YmuqLPPavEMr+ARL8hZEqYx6T4wDgYDVR0PAQH/BAQDAgEGMA0G
+CSqGSIb3DQEBBQUAA4IBAQAfoyV3JC65klsGTP4xqP2GI5cDq49Qf0QLanf/DcgO
+0pjkJpzMJu1fH5xADp7ljR1/fMsuZPyKgabZnQVNoUV20ngqTtlejFmzzb47rgmM
+4qagDsUok7G7wXbB89fXIuMl1BzMrkpuG66YO8W73JKHi6OSkbIjhB9/y/mFQtKF
+/6eQaeAmrj/9y3+m6bk/f1Q/x7OYFeMi2sPnq9G4AGKiJp9Zf1G1xRBaDqS+vSIm
+W/zWLTITBK4oMqzlEHqBeamEymdsdDFkB04+T2/E6ZB/qPW0+WVNNfqrkhsvtUnH
+czg6x5LwFrulVbjZeU+YTm4qTYijxCNcxD/u9yZWOB4R
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/README b/chromium/net/data/ssl/certificates/README
new file mode 100644
index 00000000000..01f6dfd69c8
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/README
@@ -0,0 +1,228 @@
+This directory contains various certificates for use with SSL-related
+unit tests.
+
+- google.binary.p7b
+- google.chain.pem
+- google.pem_cert.p7b
+- google.pem_pkcs7.p7b
+- google.pkcs7.p7b
+- google.single.der
+- google.single.pem
+- thawte.single.pem : Certificates for testing parsing of different formats.
+
+- googlenew.chain.pem : The refreshed Google certificate
+ (valid until Sept 30 2013).
+
+- mit.davidben.der : An expired MIT client certificate.
+
+- foaf.me.chromium-test-cert.der : A client certificate for a FOAF.ME identity
+ created for testing.
+
+- www_us_army_mil_cert.der
+- dod_ca_17_cert.der
+- dod_root_ca_2_cert.der :
+ A certificate chain used for testing certificate imports
+
+- unosoft_hu_cert : Certificate used by X509CertificateTest.UnoSoftCertParsing.
+
+- client.p12 : A PKCS #12 file containing a client certificate and a private
+ key created for testing. The password is "12345".
+
+- client-nokey.p12 : A PKCS #12 file containing a client certificate (the same
+ as the one in client.p12) but no private key. The password is "12345".
+
+- punycodetest.der : A test self-signed server certificate with punycode name.
+ The common name is "xn--wgv71a119e.com" (日本語.com)
+
+- unittest.selfsigned.der : A self-signed certificate generated using private
+ key in unittest.key.bin. The common name is "unittest".
+
+- unittest.key.bin : private key stored unencrypted.
+
+- unittest.originbound.der: A test origin-bound certificate for
+ https://www.google.com:443.
+- unittest.originbound.key.der: matching PrivateKeyInfo.
+
+- x509_verify_results.chain.pem : A simple certificate chain used to test that
+ the correctly ordered, filtered certificate chain is returned during
+ verification, regardless of the order in which the intermediate/root CA
+ certificates are provided.
+
+- google_diginotar.pem
+- diginotar_public_ca_2025.pem : A certificate chain for the regression test
+ of http://crbug.com/94673
+
+- test_mail_google_com.pem : A certificate signed by the test CA for
+ "mail.google.com". Because it is signed by that CA instead of the true CA
+ for that host, it will fail the
+ TransportSecurityState::IsChainOfPublicKeysPermitted test.
+
+- salesforce_com_test.pem
+- verisign_intermediate_ca_2011.pem
+- verisign_intermediate_ca_2016.pem : Certificates for testing two
+ X509Certificate objects that contain the same server certificate but
+ different intermediate CA certificates. The two intermediate CA
+ certificates actually represent the same intermediate CA but have
+ different validity periods.
+
+- multivalue_rdn.pem : A regression test for http://crbug.com/101009. A
+ certificate with all of the AttributeTypeAndValues stored within a single
+ RelativeDistinguishedName, rather than one AVA per RDN as normally seen.
+
+- unescaped.pem : Regression test for http://crbug.com/102839. Contains
+ characters such as '=' and '"' that would normally be escaped when
+ converting a subject/issuer name to their stringized form.
+
+- 2048-rsa-root.pem
+- {768-rsa,1024-rsa,2048-rsa,prime256v1-ecdsa}-intermediate.pem
+- {768-rsa,1024-rsa,2048-rsa,prime256v1-ecdsa}-ee-by-
+ {768-rsa,1024-rsa,2048-rsa,prime256v1-ecdsa}-intermediate.pem
+ These certficates are generated by
+ net/data/ssl/scripts/generate-weak-test-chains.sh and used in the
+ RejectWeakKeys test in net/base/x509_certificate_unittest.cc.
+
+- cross-signed-leaf.pem
+- cross-signed-root-md5.pem
+- cross-signed-root-sha1.pem
+ A certificate chain for regression testing http://crbug.com/108514,
+ generated via scripts/generate-cross-signed-certs.sh
+
+- redundant-validated-chain.pem
+- redundant-server-chain.pem
+- redundant-validated-chain-root.pem
+
+ Two chains, A -> B -> C -> D and A -> B -> C2 (C and C2 share the same
+ public key) to test that SSLInfo gets the reconstructed, re-ordered
+ chain instead of the chain as served. See
+ SSLClientSocketTest.VerifyReturnChainProperlyOrdered in
+ net/socket/ssl_client_socket_unittest.cc. These chains are valid until
+ 26 Feb 2022 and are generated by
+ net/data/ssl/scripts/generate-redundant-test-chains.sh.
+
+- comodo.chain.pem : A certificate chain for www.comodo.com which should be
+ recognised as EV. Expires Jun 21 2013.
+
+- ocsp-test-root.pem : A root certificate for the code in
+ net/tools/testserver/minica.py
+
+- spdy_pooling.pem : Used to test the handling of spdy IP connection pooling
+ Generated by using the command
+ "openssl req -x509 -days 3650 -sha1 -extensions req_spdy_pooling \
+ -config ../scripts/ee.cnf -newkey rsa:1024 -text \
+ -out spdy_pooling.pem"
+
+- subjectAltName_sanity_check.pem : Used to test the handling of various types
+ within the subjectAltName extension of a certificate. Generated by using
+ the command
+ "openssl req -x509 -days 3650 -sha1 -extensions req_san_sanity \
+ -config ../scripts/ee.cnf -newkey rsa:1024 -text \
+ -out subjectAltName_sanity_check.pem"
+
+- ndn.ca.crt: "New Dream Network Certificate Authority" root certificate.
+ This is an X.509 v1 certificate that omits the version field. Used to
+ test that the certificate version gets the default value v1.
+
+- websocket_cacert.pem : The testing root CA for testing WebSocket client
+ certificate authentication.
+ This file is used in SSLUITest.TestWSSClientCert.
+
+- websocket_client_cert.p12 : A PKCS #12 file containing a client certificate
+ and a private key created for WebSocket testing. The password is "".
+ This file is used in SSLUITest.TestWSSClientCert.
+
+- android-test-key-rsa.pem
+- android-test-key-dsa.pem
+- android-test-key-dsa-public.pem
+- android-test-key-ecdsa.pem
+- android-test-key-ecdsa-public.pem
+ This is a set of test RSA/DSA/ECDSA keys used by the Android-specific
+ unit test in net/android/keystore_unittest.c. They are used to verify
+ that the OpenSSL-specific wrapper for platform PrivateKey objects
+ works properly. See the generate-android-test-keys.sh script.
+
+- client_1.pem
+- client_1.key
+- client_1_ca.pem
+- client_2.pem
+- client_2.key
+- client_2_ca.pem
+ This is a set of files used to unit test SSL client certificate
+ authentication. These are generated by
+ net/data/ssl/scripts/generate-client-certificates.sh
+ - client_1_ca.pem and client_2_ca.pem are the certificates of
+ two distinct signing CAs.
+ - client_1.pem and client_1.key correspond to the certificate and
+ private key for a first certificate signed by client_1_ca.pem.
+ - client_2.pem and client_2.key correspond to the certificate and
+ private key for a second certificate signed by client_2_ca.pem.
+
+- eku-test-root.pem
+- non-crit-codeSigning-chain.pem
+- crit-codeSigning-chain.pem
+ Two code-signing certificates (eKU: codeSigning; eKU: critical,
+ codeSigning) which we use to test that clients are making sure that web
+ server certs are checked for correct eKU fields (when an eKU field is
+ present). Since codeSigning is not valid for web server auth, the checks
+ should fail.
+
+- duplicate_cn_1.p12
+- duplicate_cn_1.pem
+- duplicate_cn_2.p12
+- duplicate_cn_2.pem
+ Two certificates from the same issuer that share the same common name,
+ but have distinct subject names (namely, their O fields differ). NSS
+ requires that certificates have unique nicknames if they do not share the
+ same subject, and these certificates are used to test that the nickname
+ generation algorithm generates unique nicknames.
+ The .pem versions contain just the certs, while the .p12 versions contain
+ both the cert and a private key, since there are multiple ways to import
+ certificates into NSS.
+
+- aia-cert.pem
+- aia-intermediate.der
+- aia-root.pem
+ A certificate chain which we use to ensure AIA fetching works correctly
+ when using NSS to verify certificates (which uses our HTTP stack).
+ aia-cert.pem has a caIssuers that points to "aia-test.invalid" as the URL
+ containing the intermediate, which can be served via a URLRequestFilter.
+ aia-intermediate.der is stored in DER form for convenience, since that is
+ the form expected of certificates discovered via AIA.
+
+- cybertrust_gte_root.pem
+- cybertrust_baltimore_root.pem
+- cybertrust_omniroot_chain.pem
+- cybertrust_baltimore_cross_certified_1.pem
+- cybertrust_baltimore_cross_certified_2.pem
+ These certificates are reflect a portion of the CyberTrust (Verizon
+ Business) CA hierarchy. _gte_root.pem is a legacy 1024-bit root that is
+ still widely supported, while _baltimore_root.pem reflects the newer
+ 2048-bit root. For clients that only support the GTE root, two versions
+ of the Baltimore root were cross-signed by GTE, namely
+ _cross_certified_[1,2].pem. _omniroot_chain.pem contains a certificate
+ chain that was issued under the Baltimore root. Combined, these
+ certificates can be used to test real-world cross-signing; in practice,
+ they are used to test certain workarounds for OS X's chain building code.
+
+- no_subject_common_name_cert.pem: Used to test the function that generates a
+ NSS certificate nickname for a user certificate. This certificate's Subject
+ field doesn't have a common name.
+
+- expired_cert.pem
+- ok_cert.pem
+- root_ca_cert.pem
+ These certificates are the common certificates used by the Python test
+ server for simulating HTTPS connections. They are generated by running
+ the script net/data/ssl/scripts/generate-test-certs.sh.
+
+- quic_intermediate.crt
+- quic_test_ecc.example.com.crt
+- quic_test.example.com.crt
+- quic_root.crt
+ These certificates are used by the ProofVerifier's unit tests of QUIC.
+
+- explicit-policy-chain.pem
+ A test certificate chain with requireExplicitPolicy field set on the
+ intermediate, with SkipCerts=0. This is used for regression testing
+ http://crbug.com/31497. It is generated by running the script
+ net/data/ssl/scripts/generate-policy-certs.sh
+
diff --git a/chromium/net/data/ssl/certificates/aia-cert.pem b/chromium/net/data/ssl/certificates/aia-cert.pem
new file mode 100644
index 00000000000..90e74db65e9
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/aia-cert.pem
@@ -0,0 +1,76 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=AIA Test Intermediate CA
+ Validity
+ Not Before: May 15 22:24:35 2013 GMT
+ Not After : May 13 22:24:35 2023 GMT
+ Subject: CN=aia-host.invalid
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d4:c2:59:42:3c:b5:53:1b:38:9b:09:d7:31:2d:
+ ec:ce:ac:22:d0:19:72:e7:56:1e:91:97:93:3a:53:
+ 4a:76:8e:2c:d7:f3:e6:99:f2:a3:33:77:5b:df:8b:
+ 21:13:10:62:d5:11:4b:e8:8e:35:84:ba:41:04:47:
+ e9:e9:29:b3:e9:cf:73:b6:c6:4b:95:50:28:14:4a:
+ 10:10:da:25:3a:95:bf:f7:ee:df:96:32:82:e4:0d:
+ 21:76:1a:03:5e:3d:54:54:a1:79:67:80:7f:b7:2d:
+ 60:c7:e2:a5:47:5e:a4:e8:ca:6b:0e:63:9a:c1:02:
+ 65:e3:ca:7b:68:30:c3:50:e8:b7:70:ce:8e:85:ac:
+ bd:c8:0c:2a:a1:69:14:2c:a1:2b:bd:b2:09:0c:22:
+ 30:2a:e1:51:70:82:27:23:be:0f:cb:91:e3:44:9b:
+ ba:93:67:c3:2f:20:fc:b4:8f:30:0a:1d:14:8a:a7:
+ ce:cd:39:31:4b:49:55:e9:41:b6:f6:b1:84:7a:07:
+ a2:c9:0b:7b:95:bd:e7:02:5d:e0:6e:02:21:99:d7:
+ 65:99:5f:6a:c6:03:dc:1f:1e:81:cc:8b:c5:4a:d9:
+ 53:16:65:05:41:9e:b0:d1:0c:31:37:21:fa:ae:b7:
+ f9:36:db:1e:6b:43:c9:96:c7:f3:c2:1f:c0:ae:f6:
+ 4e:93
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Authority Information Access:
+ CA Issuers - URI:http://aia-test.invalid
+
+ Signature Algorithm: sha1WithRSAEncryption
+ bd:43:b3:d3:1f:8a:9d:a3:ef:a0:8a:ba:5a:c7:7d:fa:ea:26:
+ 97:d3:22:41:1f:c4:0e:65:80:d6:c9:39:e2:3c:13:0f:7c:58:
+ 0b:15:cb:f4:66:3b:7d:2c:7b:11:f2:8e:ca:f8:5b:d0:d3:fd:
+ e6:e3:67:5b:99:f0:0b:d2:cc:ea:59:6d:80:40:7d:3e:dc:01:
+ 51:64:5e:11:19:de:ad:fa:6d:25:5a:e0:bc:ea:23:c2:8b:5e:
+ e5:eb:d5:70:37:ad:3f:65:52:5c:c7:ad:e3:7c:bb:13:d9:6f:
+ 89:e4:a6:ba:d7:e1:96:c5:48:1a:53:66:15:f5:a1:42:c1:f2:
+ e5:7d:74:e3:c6:6a:91:0b:cd:1c:fe:94:dc:be:87:30:e7:c5:
+ d9:a5:87:fa:cf:ef:3f:a9:25:44:4d:ff:9d:ae:a3:08:b0:a7:
+ 52:f9:9a:26:2c:1a:ec:f6:c2:f7:aa:a0:fc:b3:d6:33:81:3b:
+ d4:79:ba:24:85:74:90:fd:76:09:a9:91:71:df:1d:e0:f6:c6:
+ ce:c6:69:a4:af:24:40:f0:5d:74:9a:35:4c:e6:fa:ef:76:0e:
+ ca:d2:dc:69:26:06:d7:78:95:aa:50:43:21:c8:f6:5f:75:e8:
+ 7e:7b:57:e1:e2:28:03:4e:11:46:c6:58:64:43:c7:da:1d:3b:
+ a9:e4:0f:d7
+-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwIzEhMB8GA1UEAwwYQUlB
+IFRlc3QgSW50ZXJtZWRpYXRlIENBMB4XDTEzMDUxNTIyMjQzNVoXDTIzMDUxMzIy
+MjQzNVowGzEZMBcGA1UEAwwQYWlhLWhvc3QuaW52YWxpZDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANTCWUI8tVMbOJsJ1zEt7M6sItAZcudWHpGXkzpT
+SnaOLNfz5pnyozN3W9+LIRMQYtURS+iONYS6QQRH6ekps+nPc7bGS5VQKBRKEBDa
+JTqVv/fu35YyguQNIXYaA149VFSheWeAf7ctYMfipUdepOjKaw5jmsECZePKe2gw
+w1Dot3DOjoWsvcgMKqFpFCyhK72yCQwiMCrhUXCCJyO+D8uR40SbupNnwy8g/LSP
+MAodFIqnzs05MUtJVelBtvaxhHoHoskLe5W95wJd4G4CIZnXZZlfasYD3B8egcyL
+xUrZUxZlBUGesNEMMTch+q63+TbbHmtDyZbH88IfwK72TpMCAwEAAaNkMGIwDAYD
+VR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwMwYIKwYB
+BQUHAQEEJzAlMCMGCCsGAQUFBzAChhdodHRwOi8vYWlhLXRlc3QuaW52YWxpZDAN
+BgkqhkiG9w0BAQUFAAOCAQEAvUOz0x+KnaPvoIq6Wsd9+uoml9MiQR/EDmWA1sk5
+4jwTD3xYCxXL9GY7fSx7EfKOyvhb0NP95uNnW5nwC9LM6lltgEB9PtwBUWReERne
+rfptJVrgvOojwote5evVcDetP2VSXMet43y7E9lvieSmutfhlsVIGlNmFfWhQsHy
+5X1048ZqkQvNHP6U3L6HMOfF2aWH+s/vP6klRE3/na6jCLCnUvmaJiwa7PbC96qg
+/LPWM4E71Hm6JIV0kP12CamRcd8d4PbGzsZppK8kQPBddJo1TOb673YOytLcaSYG
+13iVqlBDIcj2X3XofntX4eIoA04RRsZYZEPH2h07qeQP1w==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/aia-intermediate.der b/chromium/net/data/ssl/certificates/aia-intermediate.der
new file mode 100644
index 00000000000..d7dba149e52
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/aia-intermediate.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/aia-root.pem b/chromium/net/data/ssl/certificates/aia-root.pem
new file mode 100644
index 00000000000..c5fa02a8001
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/aia-root.pem
@@ -0,0 +1,72 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 10573340048066023812 (0x92bc0cc0f6631184)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=AIA Test Root CA
+ Validity
+ Not Before: May 15 22:24:35 2013 GMT
+ Not After : May 13 22:24:35 2023 GMT
+ Subject: CN=AIA Test Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ac:f7:c4:ab:87:d2:d3:e4:38:b7:22:a7:f5:ba:
+ 62:51:a2:bc:8c:6d:e9:15:ce:88:e4:94:26:38:b2:
+ 84:f6:aa:2b:44:af:c9:b0:6d:20:52:47:5d:c5:d4:
+ 57:54:39:37:77:e4:5a:95:83:eb:d9:ec:fd:20:b0:
+ 3d:50:b2:c8:3c:9f:09:74:fb:3a:59:da:8b:86:f9:
+ 98:87:85:b3:f7:04:bc:2c:f0:35:3d:93:2b:a9:d5:
+ 89:6d:18:1d:5f:29:bd:f5:69:7f:bc:91:34:08:96:
+ d4:a5:45:5c:dc:60:79:8a:55:42:fe:12:23:6a:2e:
+ 37:ce:7c:5f:d3:0f:7c:a4:6c:13:62:59:79:e8:24:
+ 10:e1:d7:2d:e6:20:bd:53:f3:bd:43:ea:2d:3f:7f:
+ 5f:05:9c:e7:e4:8d:9d:cd:0d:99:29:80:9e:22:c5:
+ d7:e7:55:cb:3c:09:25:31:61:cf:9b:96:e5:01:e5:
+ 4e:ce:39:c2:37:aa:8a:a2:c0:1c:20:38:7c:f0:90:
+ b8:16:83:ec:0a:0f:2b:8c:5a:69:db:46:ff:74:50:
+ cb:db:c6:1c:bc:05:bc:91:84:47:e0:ce:57:5e:cd:
+ 49:26:55:87:bb:45:4b:23:9d:39:ff:5b:d5:47:93:
+ 47:87:6d:78:7a:81:79:7d:66:55:6a:25:0b:3b:d5:
+ a5:05
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 79:19:7e:16:33:cc:d4:73:92:30:6d:66:17:31:5a:65:b7:63:
+ ec:07:f0:d5:6d:dc:0d:10:57:5b:97:a0:ae:ad:b9:43:ab:7a:
+ 07:21:ac:04:be:8b:21:32:20:ed:24:42:af:31:7c:24:43:83:
+ 47:44:a2:68:21:d6:6d:d2:a1:cb:b9:23:15:47:32:f6:6a:6f:
+ 65:09:21:41:aa:99:a2:a1:68:45:3a:b9:9c:fa:cd:3a:dd:bf:
+ eb:4e:82:74:75:18:99:c6:58:2a:5b:33:aa:bf:cb:8b:31:0c:
+ 36:ef:2e:2c:a7:b3:8e:98:ce:b4:86:85:06:9c:74:36:9e:30:
+ 25:4d:fb:e0:20:e1:eb:71:71:82:c3:5c:ed:f0:ed:1f:8d:21:
+ be:9c:6f:b0:38:29:32:3b:c1:06:ae:f9:46:f0:7f:7f:21:37:
+ 31:67:32:97:7d:77:3a:7a:99:80:88:9b:8d:a7:6d:a9:f0:ef:
+ 8b:5e:73:01:d5:2b:c0:82:db:7b:24:0b:7a:1b:f0:c7:8b:2b:
+ bf:b2:ef:b9:ff:d0:a4:5d:1b:46:ba:00:71:8f:9a:d2:a3:28:
+ e5:7d:2c:41:ce:6e:de:1f:9a:af:0b:f8:02:79:88:29:59:ee:
+ e2:e1:cc:80:4e:19:1b:00:84:95:26:b9:1c:a2:97:b4:bd:e0:
+ f5:b9:c4:04
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAcSgAwIBAgIJAJK8DMD2YxGEMA0GCSqGSIb3DQEBBQUAMBsxGTAXBgNV
+BAMMEEFJQSBUZXN0IFJvb3QgQ0EwHhcNMTMwNTE1MjIyNDM1WhcNMjMwNTEzMjIy
+NDM1WjAbMRkwFwYDVQQDDBBBSUEgVGVzdCBSb290IENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEArPfEq4fS0+Q4tyKn9bpiUaK8jG3pFc6I5JQmOLKE
+9qorRK/JsG0gUkddxdRXVDk3d+RalYPr2ez9ILA9ULLIPJ8JdPs6WdqLhvmYh4Wz
+9wS8LPA1PZMrqdWJbRgdXym99Wl/vJE0CJbUpUVc3GB5ilVC/hIjai43znxf0w98
+pGwTYll56CQQ4dct5iC9U/O9Q+otP39fBZzn5I2dzQ2ZKYCeIsXX51XLPAklMWHP
+m5blAeVOzjnCN6qKosAcIDh88JC4FoPsCg8rjFpp20b/dFDL28YcvAW8kYRH4M5X
+Xs1JJlWHu0VLI505/1vVR5NHh214eoF5fWZVaiULO9WlBQIDAQABoyMwITAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEA
+eRl+FjPM1HOSMG1mFzFaZbdj7Afw1W3cDRBXW5egrq25Q6t6ByGsBL6LITIg7SRC
+rzF8JEODR0SiaCHWbdKhy7kjFUcy9mpvZQkhQaqZoqFoRTq5nPrNOt2/606CdHUY
+mcZYKlszqr/LizEMNu8uLKezjpjOtIaFBpx0Np4wJU374CDh63FxgsNc7fDtH40h
+vpxvsDgpMjvBBq75RvB/fyE3MWcyl313OnqZgIibjadtqfDvi15zAdUrwILbeyQL
+ehvwx4srv7Lvuf/QpF0bRroAcY+a0qMo5X0sQc5u3h+arwv4AnmIKVnu4uHMgE4Z
+GwCElSa5HKKXtL3g9bnEBA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/android-test-key-dsa-public.pem b/chromium/net/data/ssl/certificates/android-test-key-dsa-public.pem
new file mode 100644
index 00000000000..f9968099d9c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/android-test-key-dsa-public.pem
@@ -0,0 +1,20 @@
+-----BEGIN PUBLIC KEY-----
+MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQDaJruwQmE1uMS2WnGl6L2UQy8iHmcs
+74gWjym/8HVVdLXsC3wGoXGj0vj3Zr7DpgzrXBAWj2JILVApJgbFIbEFLAHcv6Bd
+/3PjbRrva4PMQypul1o+j9IiFn2oemeVoCz77mz5aczfq7AoiMnMpdP501qcAOF2
+gDR61r1BO8QRV9gkbOLpZHX101NB9GW+PKR7C+SJBN5rVVX2YgtxPe/Km5jFgIuS
+P76eSvxGh1kUApFDhqEDh7Gck5Slt6lJDPj399MZC74XrrdbjrZbO/jt5TsLxRac
+SvI0waX+49+UnHYb3cjPynMkBgLDl7itMluTm3nS8IkgKKgYPiEAXlKbAiEAu0Ip
+TvTF7SfYMd5me26T40Fa2mAWYzWQLF10WvqheOcCggEAarH6R0LvhcWInCB1Yr4U
+FA3QjynYWfxZZ6g+Tf/NxPQmVj720SEm+QfiZnrtSReTctBHJPkcrzHVU53LNVPx
+CxvoVJUEdUhxWUV0o0PXBUfJZB3JdVXLGEu4MlXkwFh87+S3Y/+9yeMog3SaaqDA
+cISNUQSWd+q5oiAeFdYiaTjY+qD+UGBChL/w8QSrr0BRiHF1EskKI3bFmqQzDjD7
+9xm4wEGUiMvF+dHa50rjIzD+EyWvbkq3HonB7N7DDciVQ1V/qGmI6dvP5yf/Ml90
+YDL8QIrXWP2p81FlvfU3QE9tjI6Bo5cjTavRU7WORN0it9B3pmvyL++E1EGd6hBr
+qwOCAQUAAoIBAFR3VCzbhpt1iR/LpAlN2QIeQ4r2vkng6CJaxSQZBKBc1CjFHAXy
+4QIThVpagyH+QM4I5pPIxkcXpFwEPHzuZow8/4DMA6IlPJ6R8ih1IgWGqzASzXYv
+ZrlasBUjTS4no54L9PueAfZ2G0Rc9ePZ8snH9zsQVu5Rd2+zUkAUzR0jCT/zveeQ
+t9c/px8356C4beEUAFoiXbSNMDruVdFemEwC5oCIwMBamPUxf//Og74Wg0W/0DNW
+j8ffKNht51i+537vjBjVlLd/ETXARhAZg83jcqKKG0I4hECFZE5xBl0flU82pQAf
+eKk0vNRiuAXKgxNor+pnpXC3qqMkxuwQBls=
+-----END PUBLIC KEY-----
diff --git a/chromium/net/data/ssl/certificates/android-test-key-dsa.pem b/chromium/net/data/ssl/certificates/android-test-key-dsa.pem
new file mode 100644
index 00000000000..3e0ec612701
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/android-test-key-dsa.pem
@@ -0,0 +1,20 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIDVQIBAAKCAQEA2ia7sEJhNbjEtlpxpei9lEMvIh5nLO+IFo8pv/B1VXS17At8
+BqFxo9L492a+w6YM61wQFo9iSC1QKSYGxSGxBSwB3L+gXf9z420a72uDzEMqbpda
+Po/SIhZ9qHpnlaAs++5s+WnM36uwKIjJzKXT+dNanADhdoA0eta9QTvEEVfYJGzi
+6WR19dNTQfRlvjykewvkiQTea1VV9mILcT3vypuYxYCLkj++nkr8RodZFAKRQ4ah
+A4exnJOUpbepSQz49/fTGQu+F663W462Wzv47eU7C8UWnEryNMGl/uPflJx2G93I
+z8pzJAYCw5e4rTJbk5t50vCJICioGD4hAF5SmwIhALtCKU70xe0n2DHeZntuk+NB
+WtpgFmM1kCxddFr6oXjnAoIBAGqx+kdC74XFiJwgdWK+FBQN0I8p2Fn8WWeoPk3/
+zcT0JlY+9tEhJvkH4mZ67UkXk3LQRyT5HK8x1VOdyzVT8Qsb6FSVBHVIcVlFdKND
+1wVHyWQdyXVVyxhLuDJV5MBYfO/kt2P/vcnjKIN0mmqgwHCEjVEElnfquaIgHhXW
+Imk42Pqg/lBgQoS/8PEEq69AUYhxdRLJCiN2xZqkMw4w+/cZuMBBlIjLxfnR2udK
+4yMw/hMlr25Ktx6Jwezeww3IlUNVf6hpiOnbz+cn/zJfdGAy/ECK11j9qfNRZb31
+N0BPbYyOgaOXI02r0VO1jkTdIrfQd6Zr8i/vhNRBneoQa6sCggEAVHdULNuGm3WJ
+H8ukCU3ZAh5Diva+SeDoIlrFJBkEoFzUKMUcBfLhAhOFWlqDIf5Azgjmk8jGRxek
+XAQ8fO5mjDz/gMwDoiU8npHyKHUiBYarMBLNdi9muVqwFSNNLiejngv0+54B9nYb
+RFz149nyycf3OxBW7lF3b7NSQBTNHSMJP/O955C31z+nHzfnoLht4RQAWiJdtI0w
+Ou5V0V6YTALmgIjAwFqY9TF//86DvhaDRb/QM1aPx98o2G3nWL7nfu+MGNWUt38R
+NcBGEBmDzeNyooobQjiEQIVkTnEGXR+VTzalAB94qTS81GK4BcqDE2iv6melcLeq
+oyTG7BAGWwIgUF1cgjzMuvW665RmAumSL2vvkRuNm54i8cHSq53ZVq8=
+-----END DSA PRIVATE KEY-----
diff --git a/chromium/net/data/ssl/certificates/android-test-key-ecdsa-public.pem b/chromium/net/data/ssl/certificates/android-test-key-ecdsa-public.pem
new file mode 100644
index 00000000000..451262259ab
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/android-test-key-ecdsa-public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJH4uX5l3CYLPIQ7tXxBjtPZN7HVf
+l4uyPAs6VCPitxLjEcKq3w/wwnPAbhbXN7bnC6lq1Yro/5vlpa1RGB46yQ==
+-----END PUBLIC KEY-----
diff --git a/chromium/net/data/ssl/certificates/android-test-key-ecdsa.pem b/chromium/net/data/ssl/certificates/android-test-key-ecdsa.pem
new file mode 100644
index 00000000000..6f128bde2bd
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/android-test-key-ecdsa.pem
@@ -0,0 +1,8 @@
+-----BEGIN EC PARAMETERS-----
+BggqhkjOPQMBBw==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIFb6/5kje8LB6bKDjQbfr2d4wfvLjy+SNs7j4J1eEF+FoAoGCCqGSM49
+AwEHoUQDQgAEJH4uX5l3CYLPIQ7tXxBjtPZN7HVfl4uyPAs6VCPitxLjEcKq3w/w
+wnPAbhbXN7bnC6lq1Yro/5vlpa1RGB46yQ==
+-----END EC PRIVATE KEY-----
diff --git a/chromium/net/data/ssl/certificates/android-test-key-rsa.pem b/chromium/net/data/ssl/certificates/android-test-key-rsa.pem
new file mode 100644
index 00000000000..cd771fb6d88
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/android-test-key-rsa.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAtGeaaS0gTqtgxhVXHEvwq04nAj8G0VCC8QjJW1ULVA576lhy
+3IfdP3CxSLUYaJ4QNlXswDkbV8U8VPwresHqd1OTcCII774hLNFw7QtTmi1dM3rL
+kOHApeCUa32VzZSuzCyu9f2/T4HvEH367GDTIxk1NEoXKSuehjMUFiqmtJEZjHSK
+agq3p8NLTP/WpMm1WfEg5QRBQaQ82hOYN8CDaRSOW2GuHw+qZAtA7n033yX8cu3H
+khPT5INk04/byZVDMNK1hbf90y+0XeZRZAKZ9rklMnz3jPJvLLsVwSMexJnRbe/G
+cQyE9V1jHN9aGeBlm+9xvv3VjddNhaxhYLDYuQIDAQABAoIBAF5MCRoQzGJSkjL3
+1KCl0Ra5swoph5bBTrBOt3FV8qXtLDhCI0fCfJM8hG5MuoV0mWTNZQLU1sX6Ap8p
+cFCqK7RTqy1hnOozp4OVtkExOnHMZHsUJHOGjPwnd2z4J+VdYkC22n0aNXWJpTwp
+nY8QzUv7USQT1ide9W2QJV+wy5J1pQt4U/TH44FD6ceYXzEKCwHb1FXK4YOS0xgR
+mW3gKFdHAYwk6OTyJOTLoa8zRL+8w7cTx2uNbf5EXeSMPjVl6SsVqJxOoBxVhi6M
+Hj/pV4xs2sF2iqRlRZLpR5nOeJEwiELCymDgSzvSh0PZKAQzEvdYbH91vSEu6d+Q
+gSGgkAECgYEA1wepPWDJyYPXPfemqeNrq7uVMwO3EI9IwlI+ouZ1sPHDEcQ3dNGw
+5/QK7mgG++Wn+EfDkD1U26qGaGiWlCHTC/YlQ1tyrw3KqqV4QT+AIHJnr52PMUTu
+vpSUJEfLMEboa8nsdHRHvScqqhijgI5fY4RJa20EVAjA0DRB/BSyEuECgYEA1scO
+Q7s0jXWIfK/Y0z8WFfcHd1+xdIEueGqfI/Qx57Gsaen6mOKC6nESzq2GOiiXdX4X
+NzdqPhZ1TWt+8FtSjDx61xFYldVf/0mSMn0skUSshKkBxZoKT65mmH2aRn9Mh1W7
+PaIN/JCi20kaZqhbaNhPi/Dd6SK2CIJco9Dx2NkCgYEAjHYqrTdeWM5QeeAd9Hfk
+S4f7TBmvKZgPVTBYThzw4Cbs39wmxZ58Suh1g4pclYtND7gBHWWS2vMnXWiEhDsc
+G4IskTVZUtRVgOcaCLUsQwW4iVUIxoxa0A9KPfDP37dR96ctWFzkx8Cf9ACoPT/D
+O8ScGRpba3FUUizwtXPnZsECgYA7ltjPU/ZdtRlcNtG6sosnJvWsWiF7CIhjInnq
+2Mqr1PDYJfHAT0AxWZP1QdG2+yIimAxK5pYUidib1VJPz5aUkAco+ogQcjYDN19X
+oMEnwNz4pYd3Uqi/uMyATIDsRE9wUQn1LKwiweJdYufvSZCrAzD2y6pWD6pfrAOV
+89fV6QKBgQDADTt1uVUPm2XpAkRAgKT7hncHnMcVqXvQsrqbHlbC7SARTbX0lUga
+eO18L/k3h5XGXSQbVeVHdo5EZOEqQkiGcQbOgtRosMp/+6XtPMyvMMxWRhjIcyAJ
+5czOAcXm8iVv7m89w+QnK+zMsLkY0j0X70lDXq7Tui+VH9lXa4wjtw==
+-----END RSA PRIVATE KEY-----
diff --git a/chromium/net/data/ssl/certificates/client-nokey.p12 b/chromium/net/data/ssl/certificates/client-nokey.p12
new file mode 100644
index 00000000000..bd6bd7c80d0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client-nokey.p12
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/client.p12 b/chromium/net/data/ssl/certificates/client.p12
new file mode 100644
index 00000000000..ea1657a7834
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client.p12
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/client_1.key b/chromium/net/data/ssl/certificates/client_1.key
new file mode 100644
index 00000000000..156d9d592d1
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_1.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvA+oJRIT2OF09P5+DL+f4r5d94hEouZPj2eBQ2S3bb0SeeYO
+2AeuD/QfdgCchrBwVAK6qcUFLykZyUsGvGVAth0exUSR8NbShsP3TnOyknclyk/V
+nCumLPn49/0eHrzU2x6Zx3xSzb580+7HObhqiNAkJuoNu+3ALvtx310SVR02+HRV
+LjMaaMMm+od71oAgRRDNq3f0sjprLqF/fKkXwUdRs+Y9Z2iQ39g3dC0CEdLQZJb0
+BoUjBxRVUHqLMiYPF76aU+EDwOgn/waWdF0pDKsh9a4iPC8nuDu2xbpBWI7oX7pW
+lwg2J9qm8MwnEVL7m+WSqj3xYj6uu7gJUubl0wIDAQABAoIBAGXlAdDcI57OQaWA
+wmE77nBXfuhgj/fHW/IyPap7RpuR5xHfIcnRF3GTbvxrxmN/88zBEcxscKul1E/p
+c8PeBJrn2kU+KujYLIdSZIvASk/reLFOYknUqJwT8N7E6W30GEyFHwMkDGVnwZC+
+/nj6v9ZTGFNxW3GolwmduYwxjH5Kf7nVYUMsBcHnFUFHGYnZ1IE5GkufpTnF658/
+Qy6VSToV9AlziuP8ibsP7K5k6dX4m7WQXMdhv6FmzlByL7AfcBl2W2n24124gJq6
+vMd81gKiITfkvxXGUBqg7bJ0Cn1TKMibfvOk3TlitPV3U21N1ZYBv8ooPACgiBXg
+oRufAdkCgYEA9X5O0LbrLRmCGMNSjXQwU5mJaIySe5MM69XkhmpEJMaReK01JbsI
+/tNav3B68P9seQngDFZIJUXFbSDhGl/AmwtZ7dCNJOy+im7Rqyz5zOzdb5d8czTA
+Q3AbFMzg5iE3L/kOFDAce2qV9NNI43KQD/BeQFOdOQqKIWEUDkSe8kcCgYEAxBwb
+GV95LNjPtvxs9tsAljlVjWqNe6Y3ePNd+JcF5N//gXbubYxhx1RVrK8upr91jFmG
+Pv1IH11LtUGy3Df59X6foFkt1pdYPJDaZMcmPenNxRkQIZGgyCwtHfqYep4sZeQF
+eriemLsIrpEmbc/+J3WFMGlUNITcgkq1scNHyhUCgYEArmtOTiYYU1OzRirIZW4u
+w8brhNeTX68r2AeBNSsdPU/DnYzanPMVQhAigq/E+aNQi3LDt6A55Bl9WrqolJeB
+fecDvt6U2a5G9o4j882hscJ81cM4jZXmIEPvSckC5R6mWjRGl3tTUTB6WJchS1Bj
+IJ/0JxoBM7zURUD3AegpUhMCgYB4ojlhQEOP/Ma5b0mwCEOyJQ6lcRgbKcIR2tLB
+alUr10aa4wgDx0kWjqAtG4388OVkMmXMNY26DW/WzdUydhSCmSOkRXdW+75Bc3GE
+cKTrjPkQ9zvfCm/28oXGXTKSqt3wx3U/anXUyairiYo0Hq/eogIOJ5yuudPTKhXe
+hZuRiQKBgQDbxekKJp2kVS20UwKqO5Z0BYjsmAWNun/ooSDwHBUBUKwMZ9MG69aG
+b6OAFpO6wFhHjayd+XDJfKIS+sKHiSYqvFed+dpf0G9fGjpR8lyoh8XEopMSJQ8i
+rHJZMKTLUiWLdln80Q2BoH4qRB72At2XLSFfHP5D5E8h79z3eY8Dfg==
+-----END RSA PRIVATE KEY-----
diff --git a/chromium/net/data/ssl/certificates/client_1.pem b/chromium/net/data/ssl/certificates/client_1.pem
new file mode 100644
index 00000000000..9cc987b09a1
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_1.pem
@@ -0,0 +1,72 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=B CA
+ Validity
+ Not Before: Apr 22 21:58:52 2013 GMT
+ Not After : Apr 20 21:58:52 2023 GMT
+ Subject: CN=Client Cert A
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bc:0f:a8:25:12:13:d8:e1:74:f4:fe:7e:0c:bf:
+ 9f:e2:be:5d:f7:88:44:a2:e6:4f:8f:67:81:43:64:
+ b7:6d:bd:12:79:e6:0e:d8:07:ae:0f:f4:1f:76:00:
+ 9c:86:b0:70:54:02:ba:a9:c5:05:2f:29:19:c9:4b:
+ 06:bc:65:40:b6:1d:1e:c5:44:91:f0:d6:d2:86:c3:
+ f7:4e:73:b2:92:77:25:ca:4f:d5:9c:2b:a6:2c:f9:
+ f8:f7:fd:1e:1e:bc:d4:db:1e:99:c7:7c:52:cd:be:
+ 7c:d3:ee:c7:39:b8:6a:88:d0:24:26:ea:0d:bb:ed:
+ c0:2e:fb:71:df:5d:12:55:1d:36:f8:74:55:2e:33:
+ 1a:68:c3:26:fa:87:7b:d6:80:20:45:10:cd:ab:77:
+ f4:b2:3a:6b:2e:a1:7f:7c:a9:17:c1:47:51:b3:e6:
+ 3d:67:68:90:df:d8:37:74:2d:02:11:d2:d0:64:96:
+ f4:06:85:23:07:14:55:50:7a:8b:32:26:0f:17:be:
+ 9a:53:e1:03:c0:e8:27:ff:06:96:74:5d:29:0c:ab:
+ 21:f5:ae:22:3c:2f:27:b8:3b:b6:c5:ba:41:58:8e:
+ e8:5f:ba:56:97:08:36:27:da:a6:f0:cc:27:11:52:
+ fb:9b:e5:92:aa:3d:f1:62:3e:ae:bb:b8:09:52:e6:
+ e5:d3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ a5:ec:14:84:d5:27:71:38:e7:f1:5f:38:7f:96:0f:a7:5d:ad:
+ 9d:bf:5d:f9:eb:66:d5:61:ea:5b:d5:c9:3e:73:a1:62:8a:a5:
+ 25:60:8f:90:fb:9f:38:2d:1d:42:ec:e7:db:f5:34:fc:4a:57:
+ 19:eb:29:83:ae:a4:8f:94:2c:9d:c0:dd:df:6c:29:d8:c9:9f:
+ ec:07:e7:18:80:8e:3b:92:0a:f1:2e:e7:1f:0b:d5:b7:b9:d9:
+ a9:39:cf:46:a3:7a:ec:95:7f:4d:0a:99:ba:e8:ca:a9:4e:ea:
+ 48:b9:e3:21:ad:6e:20:8d:db:c5:7d:9d:94:69:f0:d0:8e:b3:
+ 32:39:67:42:7c:22:e4:25:a5:d3:51:0e:65:89:52:90:63:b4:
+ bf:c5:8e:2c:79:cc:c5:b7:e5:00:98:5b:f8:f6:01:b1:83:cb:
+ ee:a2:cb:ba:4f:c4:a6:8b:1f:fa:fa:4f:43:b7:e9:75:54:43:
+ 1b:e0:3d:d5:9b:15:6d:3d:c2:31:9f:42:10:be:9f:a1:67:0b:
+ f5:4b:ad:a4:8b:cb:ca:3f:be:ad:16:c8:6f:7a:42:33:71:39:
+ 22:a1:ee:7b:d0:3f:fb:1c:c6:bf:90:17:18:0d:0f:00:18:15:
+ 4f:2f:4f:7b:fb:26:05:05:e5:de:29:5e:ad:09:55:e6:d6:c5:
+ de:27:a6:6c
+-----BEGIN CERTIFICATE-----
+MIIC0jCCAbqgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwEQiBD
+QTAeFw0xMzA0MjIyMTU4NTJaFw0yMzA0MjAyMTU4NTJaMBgxFjAUBgNVBAMMDUNs
+aWVudCBDZXJ0IEEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8D6gl
+EhPY4XT0/n4Mv5/ivl33iESi5k+PZ4FDZLdtvRJ55g7YB64P9B92AJyGsHBUArqp
+xQUvKRnJSwa8ZUC2HR7FRJHw1tKGw/dOc7KSdyXKT9WcK6Ys+fj3/R4evNTbHpnH
+fFLNvnzT7sc5uGqI0CQm6g277cAu+3HfXRJVHTb4dFUuMxpowyb6h3vWgCBFEM2r
+d/SyOmsuoX98qRfBR1Gz5j1naJDf2Dd0LQIR0tBklvQGhSMHFFVQeosyJg8XvppT
+4QPA6Cf/BpZ0XSkMqyH1riI8Lye4O7bFukFYjuhfulaXCDYn2qbwzCcRUvub5ZKq
+PfFiPq67uAlS5uXTAgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYI
+KwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQCl7BSE1SdxOOfx
+Xzh/lg+nXa2dv13562bVYepb1ck+c6FiiqUlYI+Q+584LR1C7Ofb9TT8SlcZ6ymD
+rqSPlCydwN3fbCnYyZ/sB+cYgI47kgrxLucfC9W3udmpOc9Go3rslX9NCpm66Mqp
+TupIueMhrW4gjdvFfZ2UafDQjrMyOWdCfCLkJaXTUQ5liVKQY7S/xY4seczFt+UA
+mFv49gGxg8vuosu6T8Smix/6+k9Dt+l1VEMb4D3VmxVtPcIxn0IQvp+hZwv1S62k
+i8vKP76tFshvekIzcTkioe570D/7HMa/kBcYDQ8AGBVPL097+yYFBeXeKV6tCVXm
+1sXeJ6Zs
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/client_1_ca.pem b/chromium/net/data/ssl/certificates/client_1_ca.pem
new file mode 100644
index 00000000000..5426bf448ec
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_1_ca.pem
@@ -0,0 +1,71 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=C Root CA
+ Validity
+ Not Before: Apr 22 21:58:52 2013 GMT
+ Not After : Apr 20 21:58:52 2023 GMT
+ Subject: CN=B CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ca:2b:37:8c:21:be:73:36:40:67:43:2a:89:9c:
+ 3f:43:88:8b:34:8e:3e:34:cd:ee:32:5e:f2:a2:22:
+ f8:d9:7f:c6:96:b7:e1:52:7f:91:3e:81:4a:a9:35:
+ 63:03:d7:3c:38:c5:0d:b8:a6:b0:be:b0:c5:b8:b9:
+ 6c:34:fc:f2:9a:25:7c:37:cf:04:e6:c4:9b:00:5a:
+ b2:d3:9e:6c:85:97:92:0a:44:08:8d:32:2b:9b:50:
+ 9a:e4:bd:61:db:49:d7:40:6b:72:15:6a:a3:75:52:
+ 31:65:44:e0:bd:c1:bf:6e:b6:71:71:29:fd:98:67:
+ b9:62:62:d9:7b:a7:cb:4f:93:70:f9:1c:2c:83:42:
+ 2a:dc:4b:e8:2d:51:3c:ef:f0:4b:a3:2b:db:7f:6d:
+ 73:11:21:55:33:90:7c:94:29:2a:8c:3a:7b:22:b3:
+ 9e:30:16:d1:41:64:7c:4d:83:79:5f:8f:c4:ec:21:
+ e4:0c:14:95:1e:ec:d7:d0:f7:d7:44:f5:93:48:01:
+ bf:e9:99:06:7e:2e:d4:e9:87:88:3f:46:f1:7c:c3:
+ 07:5a:8b:b7:16:72:dc:35:d4:69:e3:33:68:45:79:
+ 1a:35:26:37:08:4e:12:57:02:34:24:45:ec:2f:19:
+ ab:d5:7a:b6:20:db:93:0a:0d:f4:77:1d:27:15:37:
+ 54:2b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 93:c0:c7:af:77:e2:52:21:1f:59:b4:d1:df:d7:43:cc:31:73:
+ 2b:76:3a:d9:ec:e0:ab:1d:e0:8d:7f:e4:16:2d:06:40:d1:c1:
+ 97:be:65:d0:e6:4b:c3:a3:6a:e9:0c:5b:86:f6:49:eb:e2:f0:
+ 07:95:ee:37:7f:10:df:ce:2f:5b:4e:70:24:5b:47:1f:f5:d8:
+ 8e:0a:7c:4d:54:e2:e6:a7:0a:15:c6:16:a4:0f:79:03:22:a3:
+ 76:23:6d:e6:1e:ce:81:84:39:ec:b8:f7:e8:0a:a2:1f:93:fa:
+ 60:92:df:35:c2:23:0d:5c:74:70:74:46:fc:b4:47:83:81:ee:
+ a6:c7:03:90:26:78:84:1f:3d:c4:39:16:a1:f0:aa:13:9a:be:
+ 6b:2d:ad:3f:5d:e2:57:45:60:6b:56:2a:e3:00:50:29:bb:41:
+ 87:ba:c8:21:82:dd:57:68:4f:cd:ea:11:2a:9a:93:c7:c3:af:
+ 2f:fb:0d:a5:40:59:2a:22:ac:df:98:3d:2a:ea:1e:c5:e8:03:
+ c6:0d:b4:2d:10:c6:a0:b5:e8:61:fd:b7:07:82:54:80:68:21:
+ 05:db:d4:d7:1f:5f:62:93:21:a2:cd:b8:08:f0:06:86:04:93:
+ aa:b2:a3:64:4c:2f:47:78:0e:b1:a2:1c:b1:50:72:f4:86:cc:
+ 07:2a:a6:cb
+-----BEGIN CERTIFICATE-----
+MIICwjCCAaqgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJQyBS
+b290IENBMB4XDTEzMDQyMjIxNTg1MloXDTIzMDQyMDIxNTg1MlowDzENMAsGA1UE
+AwwEQiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorN4whvnM2
+QGdDKomcP0OIizSOPjTN7jJe8qIi+Nl/xpa34VJ/kT6BSqk1YwPXPDjFDbimsL6w
+xbi5bDT88polfDfPBObEmwBastOebIWXkgpECI0yK5tQmuS9YdtJ10BrchVqo3VS
+MWVE4L3Bv262cXEp/ZhnuWJi2Xuny0+TcPkcLINCKtxL6C1RPO/wS6Mr239tcxEh
+VTOQfJQpKow6eyKznjAW0UFkfE2DeV+PxOwh5AwUlR7s19D310T1k0gBv+mZBn4u
+1OmHiD9G8XzDB1qLtxZy3DXUaeMzaEV5GjUmNwhOElcCNCRF7C8Zq9V6tiDbkwoN
+9HcdJxU3VCsCAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAJPAx6934lIhH1m00d/XQ8wxcyt2Otns4Ksd
+4I1/5BYtBkDRwZe+ZdDmS8OjaukMW4b2Sevi8AeV7jd/EN/OL1tOcCRbRx/12I4K
+fE1U4uanChXGFqQPeQMio3YjbeYezoGEOey49+gKoh+T+mCS3zXCIw1cdHB0Rvy0
+R4OB7qbHA5AmeIQfPcQ5FqHwqhOavmstrT9d4ldFYGtWKuMAUCm7QYe6yCGC3Vdo
+T83qESqak8fDry/7DaVAWSoirN+YPSrqHsXoA8YNtC0QxqC16GH9tweCVIBoIQXb
+1NcfX2KTIaLNuAjwBoYEk6qyo2RML0d4DrGiHLFQcvSGzAcqpss=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/client_2.key b/chromium/net/data/ssl/certificates/client_2.key
new file mode 100644
index 00000000000..966a4c14026
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_2.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvSAJxKBJi2mnSqYIkxVLZHUz/WDoigJuFU95CzOlT3uTtamU
+cBV3Wj+j8FFObvtOwaGqNhzEI/B1lL75NMYIHA5hRHLgIHMj2rDXAIrSN0/zQKae
+vAkY4lDqAGIWJipu6R7ImXedmDMCGR6JNBN5zF1KuRBBrlOKyGFMMGx//D6KvEnq
+BXdGERADto1szBljLYV/l9KSBijU9050gMOnykkapheB7e2OlZdzr8nvBCrexJpi
+5ffU3AJPty6oROMxuZvg/DvN6v/PWsRM+n8yHoxeg43bKGU5MnIVD4I86P6Q05Bh
+vzmr/apeHiYVccX0EHdqhUf56W2QqTul2Mab9QIDAQABAoIBAQCIAnFj6Y3aZ8n6
+gjsqY6cLgMo5zyaMkcDPLI81QhgBeDK681Cf5qAl1By26BIK+EokMHozXi6kVfqJ
+VWnszPnqC2FiE2chjwxa6tBEQJF7W9DpTqpbOgOeRmhyjBe3rM6EcjH7RC2e1hgN
+LounWtY95V2mh41krAnjny1mqbDFGoy5Wa4cJXdlJa7rm7yV3G871m9zP4tt3Aw+
+w2nX0+U0QtZN+LiFr5M2bNtfEukjd4z9mhpiVYHTG6M5HcxUw1gpCCNJ4oEHlV0T
+c21pZg2OFmkU+83QyeanWXWQtriPT5Jgs8H/pvEiCjE0sxOvgICB8L8NYJxGTQMQ
+UiGNeGNBAoGBANySFy1PW5hRHobY/p1QjnJvvBwGJRUI7FYLkXv+8ZVEScYs9NMH
+MRygkkzEvadnD19WJ2WoOsq2p8RFjjOtyozb3Q24/qpUr1qNKkx0xEjnPhlYd7sh
+iex9F4RgjNCFW2K/F1YcNMo8dm05PC0uaIKLz824DdTCnp8p/pGhEp5xAoGBANuA
+5tfnOqAbhrj1uaeI67tzJBtsAlPFgTpBNRPzt/vfXUoSlHq7vQ3VzGtaJVLykiv9
+Q3e2ShwuX4jcFl59u20WGlCFskjG/wbKhkZkEUqEqIhiEP+qx9BX8z6Sa0zMZxoc
+lkZfZSYDu9AXymVlEUfPuXYE1TzKa2fxkZiv8x/FAoGAde8Cx24z+jf9S3qAgNqO
+n29Qs+cxMpMH0mXzDspcn0PY8kYdTSv+PWE5eCSFhxlapc3p2LffX33UK+RIySb2
+MuRnyCuOtsH61D7ATAru3FAP6vtbYUnodfLYfSYmhGOZXi3wK1F/hFZZt8KvgzTa
+Glro7ASqGIVKzK1meLUXwHECgYEAqvH9VYGH367wQdVkq7vvUkG+ifiY62KyXIrx
+6kLxMx/mSEymA9t3xXGOzMActzegbM/FnlKB7uaSkkRMy3QB5lfDUJh+mz0W2dQZ
+tHI0ISOlGOm+sU3wZSpJjp57IAlD9krzIYUjgfKAbvRINKT8Sz/UALyM0NYXxZCV
+QiMtJb0CgYBV9tKcQOckZH90rk7DOcvW9tVy6bbEJcTO1/r2Sn15rjhDkm6X4pY8
+HM5hUHEhVlGgszj1wPaAKSQlhBawDvy4MJPmWKsjVKp6euz4xpYtF9cvZ2PF4B4y
+P4TkPZMgEMU3tyn1Dgc9w5X8m1R0WRaXJghrCQebREFSbb2pyMDSpA==
+-----END RSA PRIVATE KEY-----
diff --git a/chromium/net/data/ssl/certificates/client_2.pem b/chromium/net/data/ssl/certificates/client_2.pem
new file mode 100644
index 00000000000..c07753577af
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_2.pem
@@ -0,0 +1,72 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=E CA
+ Validity
+ Not Before: Apr 22 21:58:52 2013 GMT
+ Not After : Apr 20 21:58:52 2023 GMT
+ Subject: CN=Client Cert D
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bd:20:09:c4:a0:49:8b:69:a7:4a:a6:08:93:15:
+ 4b:64:75:33:fd:60:e8:8a:02:6e:15:4f:79:0b:33:
+ a5:4f:7b:93:b5:a9:94:70:15:77:5a:3f:a3:f0:51:
+ 4e:6e:fb:4e:c1:a1:aa:36:1c:c4:23:f0:75:94:be:
+ f9:34:c6:08:1c:0e:61:44:72:e0:20:73:23:da:b0:
+ d7:00:8a:d2:37:4f:f3:40:a6:9e:bc:09:18:e2:50:
+ ea:00:62:16:26:2a:6e:e9:1e:c8:99:77:9d:98:33:
+ 02:19:1e:89:34:13:79:cc:5d:4a:b9:10:41:ae:53:
+ 8a:c8:61:4c:30:6c:7f:fc:3e:8a:bc:49:ea:05:77:
+ 46:11:10:03:b6:8d:6c:cc:19:63:2d:85:7f:97:d2:
+ 92:06:28:d4:f7:4e:74:80:c3:a7:ca:49:1a:a6:17:
+ 81:ed:ed:8e:95:97:73:af:c9:ef:04:2a:de:c4:9a:
+ 62:e5:f7:d4:dc:02:4f:b7:2e:a8:44:e3:31:b9:9b:
+ e0:fc:3b:cd:ea:ff:cf:5a:c4:4c:fa:7f:32:1e:8c:
+ 5e:83:8d:db:28:65:39:32:72:15:0f:82:3c:e8:fe:
+ 90:d3:90:61:bf:39:ab:fd:aa:5e:1e:26:15:71:c5:
+ f4:10:77:6a:85:47:f9:e9:6d:90:a9:3b:a5:d8:c6:
+ 9b:f5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ 05:33:9d:86:a9:81:49:a9:5c:57:41:67:30:2a:b3:92:d5:96:
+ f4:fc:4f:9a:ad:2a:18:f9:66:7e:e8:3a:ac:ef:6c:42:53:60:
+ 1b:99:cc:aa:bd:78:ef:d7:d7:d1:52:04:3f:c6:d5:ea:ec:51:
+ d1:88:1f:ad:05:a7:16:12:2c:f9:7f:79:0f:10:70:de:a6:d1:
+ 62:93:68:57:5d:a0:bd:95:0f:ba:82:37:66:77:d6:48:1f:ab:
+ 10:aa:bd:1d:46:9c:23:d6:fa:2f:c2:3d:38:8e:84:7e:7a:62:
+ f5:6f:6d:c3:68:95:6f:4f:99:ec:2c:d6:6c:22:aa:a3:0a:d0:
+ 09:d8:0f:19:5f:75:5d:65:6e:31:76:f9:b9:43:6b:f6:fa:22:
+ 70:ff:c0:fa:03:f6:22:89:5c:69:9d:9b:fb:f8:a0:e8:76:66:
+ 64:32:db:51:23:fb:58:e0:67:68:24:15:58:81:78:c3:80:7e:
+ 79:d7:1e:5e:bf:9c:82:04:cf:c8:34:6a:c7:1e:75:92:0d:45:
+ d5:83:a3:5b:e7:3f:49:ed:7e:a0:f7:8b:6c:45:45:4d:f9:c0:
+ 1a:5c:17:50:93:35:87:1e:7e:12:dc:41:fc:6b:2c:f7:ac:97:
+ 6c:91:ba:47:22:91:99:36:45:74:14:f2:62:5b:e0:b1:59:ba:
+ 53:f4:34:1c
+-----BEGIN CERTIFICATE-----
+MIIC0jCCAbqgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwERSBD
+QTAeFw0xMzA0MjIyMTU4NTJaFw0yMzA0MjAyMTU4NTJaMBgxFjAUBgNVBAMMDUNs
+aWVudCBDZXJ0IEQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9IAnE
+oEmLaadKpgiTFUtkdTP9YOiKAm4VT3kLM6VPe5O1qZRwFXdaP6PwUU5u+07Boao2
+HMQj8HWUvvk0xggcDmFEcuAgcyPasNcAitI3T/NApp68CRjiUOoAYhYmKm7pHsiZ
+d52YMwIZHok0E3nMXUq5EEGuU4rIYUwwbH/8Poq8SeoFd0YREAO2jWzMGWMthX+X
+0pIGKNT3TnSAw6fKSRqmF4Ht7Y6Vl3Ovye8EKt7EmmLl99TcAk+3LqhE4zG5m+D8
+O83q/89axEz6fzIejF6DjdsoZTkychUPgjzo/pDTkGG/Oav9ql4eJhVxxfQQd2qF
+R/npbZCpO6XYxpv1AgMBAAGjLzAtMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYI
+KwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQAFM52GqYFJqVxX
+QWcwKrOS1Zb0/E+arSoY+WZ+6Dqs72xCU2AbmcyqvXjv19fRUgQ/xtXq7FHRiB+t
+BacWEiz5f3kPEHDeptFik2hXXaC9lQ+6gjdmd9ZIH6sQqr0dRpwj1vovwj04joR+
+emL1b23DaJVvT5nsLNZsIqqjCtAJ2A8ZX3VdZW4xdvm5Q2v2+iJw/8D6A/YiiVxp
+nZv7+KDodmZkMttRI/tY4GdoJBVYgXjDgH551x5ev5yCBM/INGrHHnWSDUXVg6Nb
+5z9J7X6g94tsRUVN+cAaXBdQkzWHHn4S3EH8ayz3rJdskbpHIpGZNkV0FPJiW+Cx
+WbpT9DQc
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/client_2_ca.pem b/chromium/net/data/ssl/certificates/client_2_ca.pem
new file mode 100644
index 00000000000..220a477d5c3
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/client_2_ca.pem
@@ -0,0 +1,71 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=C Root CA
+ Validity
+ Not Before: Apr 22 21:58:52 2013 GMT
+ Not After : Apr 20 21:58:52 2023 GMT
+ Subject: CN=E CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e6:0b:39:56:df:1c:f7:69:44:73:00:91:ad:ef:
+ 0a:31:1d:86:ca:67:b7:f5:bd:a8:34:d2:7d:af:3b:
+ 46:61:98:d6:b6:ea:97:60:d8:1a:91:87:1e:b3:89:
+ f6:eb:8f:05:f4:7b:0b:77:3a:4c:7e:04:14:ce:60:
+ 2f:f8:8c:c4:45:07:0b:86:ad:9e:97:58:20:90:4a:
+ 16:90:f3:6e:7d:4e:92:f9:2b:bf:62:99:d6:86:38:
+ 53:a4:33:79:df:7e:64:2b:8e:86:c0:c6:5a:87:73:
+ 69:f5:5f:89:d5:3b:d7:f4:e4:1e:78:15:38:89:ff:
+ 87:a9:7a:85:b9:4c:20:c6:44:7d:3a:d1:10:59:86:
+ 7e:0f:d4:0c:a7:48:f8:42:7e:61:0a:bf:2b:4f:03:
+ 3d:ac:f5:0f:01:5b:65:3b:fd:82:a1:8c:40:c7:8e:
+ 24:e4:75:17:92:1c:76:e8:6c:f6:44:de:ee:90:d9:
+ cd:40:7c:70:50:91:23:a0:f5:c5:3c:9b:7e:5f:0d:
+ 54:4d:b7:67:ce:1f:99:50:bf:da:a8:33:4f:6c:b3:
+ aa:4b:af:59:87:25:4a:8c:87:56:66:15:13:8a:58:
+ 5a:9f:0a:fa:0e:34:8a:7a:cc:ac:9e:0e:c9:53:22:
+ b9:60:ae:32:b9:bc:5a:51:53:f9:f5:91:83:9e:df:
+ 4b:77
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 46:32:41:a6:8c:f8:2e:56:59:6a:e6:c0:2e:85:1b:49:19:ba:
+ 63:ee:5d:44:c1:91:f3:4b:1a:2c:e9:fd:d7:5f:0e:24:63:6a:
+ ea:5f:37:fd:ad:db:54:ad:a8:2b:10:f7:7c:86:98:39:62:ad:
+ 50:bb:c4:f5:e1:4d:1f:12:30:83:cc:a9:cb:c7:5e:f9:d9:ee:
+ 95:8f:d2:5a:f6:24:45:09:f6:66:44:76:79:e4:49:08:3b:ca:
+ c7:09:7c:f6:26:07:9c:01:70:38:cd:57:3f:16:ad:af:82:42:
+ 4d:7e:e4:45:ed:0e:8d:83:a0:7a:56:8b:3f:21:52:db:6b:ca:
+ ab:bb:f0:17:10:e9:83:af:fd:4a:ea:32:61:ea:ec:fe:42:67:
+ fd:a2:2e:7a:3d:d7:9f:ff:f0:59:8b:a6:54:4d:77:f2:0c:4f:
+ c4:71:7e:8c:f0:3b:4b:72:6d:f7:28:35:0f:96:42:61:bf:28:
+ 44:a2:7c:86:43:65:aa:3d:c1:6d:cf:41:f3:23:d3:96:ea:d4:
+ e0:72:78:04:d9:ff:7e:7c:fc:bf:88:f9:e2:64:80:47:52:97:
+ 42:11:07:90:3c:31:35:c2:f9:83:88:e7:59:3f:f4:06:f4:b8:
+ 07:35:14:56:1f:73:b9:a5:c3:95:47:20:4d:e0:8e:41:bd:c5:
+ 7b:88:15:d6
+-----BEGIN CERTIFICATE-----
+MIICwjCCAaqgAwIBAgICAO0wDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJQyBS
+b290IENBMB4XDTEzMDQyMjIxNTg1MloXDTIzMDQyMDIxNTg1MlowDzENMAsGA1UE
+AwwERSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOYLOVbfHPdp
+RHMAka3vCjEdhspnt/W9qDTSfa87RmGY1rbql2DYGpGHHrOJ9uuPBfR7C3c6TH4E
+FM5gL/iMxEUHC4atnpdYIJBKFpDzbn1Okvkrv2KZ1oY4U6Qzed9+ZCuOhsDGWodz
+afVfidU71/TkHngVOIn/h6l6hblMIMZEfTrREFmGfg/UDKdI+EJ+YQq/K08DPaz1
+DwFbZTv9gqGMQMeOJOR1F5Icduhs9kTe7pDZzUB8cFCRI6D1xTybfl8NVE23Z84f
+mVC/2qgzT2yzqkuvWYclSoyHVmYVE4pYWp8K+g40inrMrJ4OyVMiuWCuMrm8WlFT
++fWRg57fS3cCAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAEYyQaaM+C5WWWrmwC6FG0kZumPuXUTBkfNL
+Gizp/ddfDiRjaupfN/2t21StqCsQ93yGmDlirVC7xPXhTR8SMIPMqcvHXvnZ7pWP
+0lr2JEUJ9mZEdnnkSQg7yscJfPYmB5wBcDjNVz8Wra+CQk1+5EXtDo2DoHpWiz8h
+Uttryqu78BcQ6YOv/UrqMmHq7P5CZ/2iLno915//8FmLplRNd/IMT8RxfozwO0ty
+bfcoNQ+WQmG/KESifIZDZao9wW3PQfMj05bq1OByeATZ/358/L+I+eJkgEdSl0IR
+B5A8MTXC+YOI51k/9Ab0uAc1FFYfc7mlw5VHIE3gjkG9xXuIFdY=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/comodo.chain.pem b/chromium/net/data/ssl/certificates/comodo.chain.pem
new file mode 100644
index 00000000000..88699ac0c08
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/comodo.chain.pem
@@ -0,0 +1,317 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ f7:8b:13:b9:46:fc:96:35:d8:ab:49:de:9d:21:48:21
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Extended Validation Secure Server CA
+ Validity
+ Not Before: May 29 00:00:00 2013 GMT
+ Not After : Jun 20 23:59:59 2015 GMT
+ Subject: serialNumber=-na/1.3.6.1.4.1.311.60.2.1.3=GB/businessCategory=Private Organization, C=GB/postalCode=M5 3EQ, ST=Greater Manchester, L=Salford,/street=Exchange Quay, Trafford Road/street=26 Office Village,/street=3rd Floor,, O=Comodo CA Ltd, OU=Comodo EV SGC SSL, OU=COMODO EV SGC SSL, CN=www.comodo.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a7:ad:ff:5f:d0:7d:1d:97:78:f7:da:f8:77:da:
+ 86:d2:11:ec:83:b1:4b:fd:8d:75:f2:44:9c:18:db:
+ d5:fa:ca:3e:17:8c:a3:23:36:e9:03:7d:04:6f:2f:
+ e2:c0:f8:74:23:b6:f6:47:a5:b0:91:b4:78:49:64:
+ 91:10:71:19:63:4c:9d:19:90:1d:1e:b8:92:cf:93:
+ 9f:5f:f6:53:db:68:b5:07:29:a2:82:92:3a:cd:c0:
+ a4:02:2a:87:c3:38:36:e5:86:6d:e6:d7:bd:73:97:
+ 64:b8:a7:1e:9d:a0:58:11:42:73:5e:7e:1e:5e:96:
+ a2:0d:1b:b9:56:56:bd:2a:ce:52:b7:b2:a2:e8:1b:
+ d7:75:f7:32:80:58:b4:04:96:ae:b7:be:f5:b5:7b:
+ df:46:94:28:f1:36:80:d1:f2:78:65:55:d8:6d:fa:
+ 8b:f6:1d:69:5a:a3:ba:40:41:f2:17:a0:5c:9b:81:
+ 6a:56:e4:02:7f:0c:63:40:8c:b2:26:d8:16:f3:55:
+ f8:a1:00:db:71:60:95:b3:7d:f6:b1:1c:a9:1d:76:
+ 90:41:72:1e:11:2a:83:16:98:e0:a3:61:69:a8:39:
+ 4a:a7:1e:14:de:b4:07:9f:59:53:e3:6f:88:61:2b:
+ 1d:22:c4:92:bf:ba:65:23:ee:e8:76:f3:32:40:b0:
+ f0:c7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:88:44:51:FF:50:2A:69:5E:2D:88:F4:21:BA:D9:0C:F2:CE:CB:EA:7C
+
+ X509v3 Subject Key Identifier:
+ B2:E0:EB:4A:E7:C6:1C:27:0A:54:4F:C4:00:AA:E1:6A:2C:14:91:C3
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.1.5.1
+ CPS: https://secure.comodo.com/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOExtendedValidationSecureServerCA.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/COMODOExtendedValidationSecureServerCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ X509v3 Subject Alternative Name:
+ DNS:www.comodo.com, DNS:comodo.com
+ Signature Algorithm: sha1WithRSAEncryption
+ a4:c4:55:e9:b6:8f:09:b4:34:ea:22:6c:32:71:97:fb:c4:93:
+ ce:d3:f3:28:f0:16:f6:3f:6e:6f:ff:3f:03:f6:d4:51:d5:a5:
+ 1a:4f:b1:91:e4:49:7e:e1:d0:5b:30:1c:e9:3b:36:5e:48:97:
+ 1a:5c:f5:2f:3a:b3:51:cb:b7:db:fa:f2:a2:e7:45:ab:1a:23:
+ 70:ce:15:8e:9c:2c:6e:21:c9:03:ad:94:92:f3:20:d3:dd:43:
+ 13:91:58:20:b9:a1:e1:83:86:bd:e8:46:e3:74:2a:c8:26:c2:
+ 3a:16:98:cd:d4:b1:82:75:4f:3e:02:87:56:5f:af:5e:b4:e1:
+ 2d:05:c2:f0:2b:bc:5a:1f:dc:3c:05:03:0a:c5:29:b5:1d:94:
+ 69:dd:af:d5:46:c3:69:bc:b8:1c:40:08:39:8f:76:29:5d:a2:
+ 4b:fa:1f:2f:68:a3:88:64:80:e8:7a:1d:74:2e:29:02:e2:57:
+ 94:57:dd:c8:1d:03:67:46:84:c1:2a:be:ef:05:f7:47:da:02:
+ 01:1e:10:f2:14:02:7f:66:d2:a9:a6:e4:28:59:fd:fc:ea:25:
+ f5:7f:eb:f6:7a:4e:ed:74:66:54:62:db:db:30:9b:cd:c4:c6:
+ 74:89:69:13:ed:32:8d:92:c7:a2:48:e4:2b:42:89:1f:31:6e:
+ f3:44:9f:f8
+-----BEGIN CERTIFICATE-----
+MIIGVDCCBTygAwIBAgIRAPeLE7lG/JY12KtJ3p0hSCEwDQYJKoZIhvcNAQEFBQAw
+gY4xCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTQwMgYD
+VQQDEytDT01PRE8gRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB
+MB4XDTEzMDUyOTAwMDAwMFoXDTE1MDYyMDIzNTk1OVowggFTMQwwCgYDVQQFEwMt
+bmExEzARBgsrBgEEAYI3PAIBAxMCR0IxHTAbBgNVBA8TFFByaXZhdGUgT3JnYW5p
+emF0aW9uMQswCQYDVQQGEwJHQjEPMA0GA1UEERMGTTUgM0VRMRswGQYDVQQIExJH
+cmVhdGVyIE1hbmNoZXN0ZXIxETAPBgNVBAcTCFNhbGZvcmQsMSYwJAYDVQQJEx1F
+eGNoYW5nZSBRdWF5LCAgVHJhZmZvcmQgUm9hZDEbMBkGA1UECRMSMjYgT2ZmaWNl
+IFZpbGxhZ2UsMRMwEQYDVQQJEwozcmQgRmxvb3IsMRYwFAYDVQQKEw1Db21vZG8g
+Q0EgTHRkMRowGAYDVQQLExFDb21vZG8gRVYgU0dDIFNTTDEaMBgGA1UECxMRQ09N
+T0RPIEVWIFNHQyBTU0wxFzAVBgNVBAMTDnd3dy5jb21vZG8uY29tMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp63/X9B9HZd499r4d9qG0hHsg7FL/Y11
+8kScGNvV+so+F4yjIzbpA30Eby/iwPh0I7b2R6WwkbR4SWSREHEZY0ydGZAdHriS
+z5OfX/ZT22i1BymigpI6zcCkAiqHwzg25YZt5te9c5dkuKcenaBYEUJzXn4eXpai
+DRu5Vla9Ks5St7Ki6BvXdfcygFi0BJaut771tXvfRpQo8TaA0fJ4ZVXYbfqL9h1p
+WqO6QEHyF6Bcm4FqVuQCfwxjQIyyJtgW81X4oQDbcWCVs332sRypHXaQQXIeESqD
+Fpjgo2FpqDlKpx4U3rQHn1lT42+IYSsdIsSSv7plI+7odvMyQLDwxwIDAQABo4IB
+4zCCAd8wHwYDVR0jBBgwFoAUiERR/1AqaV4tiPQhutkM8s7L6nwwHQYDVR0OBBYE
+FLLg60rnxhwnClRPxACq4WosFJHDMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8E
+AjAAMDQGA1UdJQQtMCsGCCsGAQUFBwMBBggrBgEFBQcDAgYKKwYBBAGCNwoDAwYJ
+YIZIAYb4QgQBMEYGA1UdIAQ/MD0wOwYMKwYBBAGyMQECAQUBMCswKQYIKwYBBQUH
+AgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5jb20vQ1BTMFMGA1UdHwRMMEowSKBG
+oESGQmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET0V4dGVuZGVkVmFsaWRh
+dGlvblNlY3VyZVNlcnZlckNBLmNybDCBhAYIKwYBBQUHAQEEeDB2ME4GCCsGAQUF
+BzAChkJodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9FeHRlbmRlZFZhbGlk
+YXRpb25TZWN1cmVTZXJ2ZXJDQS5jcnQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
+LmNvbW9kb2NhLmNvbTAlBgNVHREEHjAcgg53d3cuY29tb2RvLmNvbYIKY29tb2Rv
+LmNvbTANBgkqhkiG9w0BAQUFAAOCAQEApMRV6baPCbQ06iJsMnGX+8STztPzKPAW
+9j9ub/8/A/bUUdWlGk+xkeRJfuHQWzAc6Ts2XkiXGlz1LzqzUcu32/ryoudFqxoj
+cM4VjpwsbiHJA62UkvMg091DE5FYILmh4YOGvehG43QqyCbCOhaYzdSxgnVPPgKH
+Vl+vXrThLQXC8Cu8Wh/cPAUDCsUptR2Uad2v1UbDaby4HEAIOY92KV2iS/ofL2ij
+iGSA6HoddC4pAuJXlFfdyB0DZ0aEwSq+7wX3R9oCAR4Q8hQCf2bSqabkKFn9/Ool
+9X/r9npO7XRmVGLb2zCbzcTGdIlpE+0yjZLHokjkK0KJHzFu80Sf+A==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 11:a3:b4:d0:ec:8d:b7:7f:9d:a0:cd:5d:2d:51:2f:42
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Validity
+ Not Before: May 24 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Extended Validation Secure Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cc:4a:96:33:cd:25:8d:67:ee:28:96:37:87:46:
+ f0:f6:04:a2:84:7f:53:aa:96:e6:1f:b1:02:1c:6e:
+ ed:7d:21:d4:d7:3c:1e:a2:d8:69:2f:a8:b7:f5:a2:
+ ed:64:58:64:e1:44:65:36:49:41:20:01:8d:3b:13:
+ e2:08:f3:0c:f2:57:39:93:37:b7:1c:93:44:83:8e:
+ bf:2d:f1:a1:05:75:da:6e:ee:7b:6f:1b:ea:76:83:
+ 28:74:4a:1c:2b:d3:f5:c4:03:72:93:af:86:ce:09:
+ 8c:3c:75:d4:c9:0a:2f:72:f3:ad:bd:0e:30:3c:84:
+ a1:73:1f:03:25:14:a5:8f:c3:d6:f4:b5:e4:dd:86:
+ 7a:f5:19:ba:68:f2:85:54:a2:30:11:ca:d1:92:cb:
+ 3b:74:06:12:a0:37:ab:6a:d8:54:11:df:6c:9a:16:
+ 94:b9:b4:a7:65:c6:74:2d:31:f3:4d:52:e9:55:51:
+ 9f:cb:3e:a2:8d:76:98:70:d2:6f:a6:65:45:2f:1b:
+ 85:bb:5b:6d:f9:f2:c0:04:66:13:84:7a:9d:ce:27:
+ d8:f4:44:9e:bf:ac:be:99:db:6b:4f:db:58:21:b0:
+ 89:27:b4:8f:32:d6:4b:5e:72:91:5e:df:05:9d:d9:
+ 49:2f:f4:b6:6f:50:1f:75:cb:80:9d:e6:d3:e4:d1:
+ f2:d3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+ X509v3 Subject Key Identifier:
+ 88:44:51:FF:50:2A:69:5E:2D:88:F4:21:BA:D9:0C:F2:CE:CB:EA:7C
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://secure.comodo.com/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/COMODOAddTrustServerCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 9a:43:bf:af:a4:72:5e:cd:7d:6f:7f:f4:fc:3d:8c:bb:70:e6:
+ 1e:dd:04:fd:3f:dc:9d:9f:bf:89:76:9b:f2:86:31:fc:7f:b3:
+ ed:2a:91:53:2c:e2:aa:b0:e3:c8:2c:71:f7:15:8a:23:1c:f1:
+ 69:2e:81:fb:b1:bc:62:0b:ab:1a:54:1c:d9:22:5e:34:4c:a5:
+ f6:23:0f:5d:7a:3d:db:43:cd:69:7e:17:37:52:cd:53:a1:c2:
+ 11:d4:53:78:27:64:d5:89:41:4d:16:55:bb:90:cb:f0:d8:e4:
+ dd:dd:d3:09:64:48:28:ff:32:23:84:2f:8c:7b:55:2f:cf:29:
+ 88:37:34:78:0f:33:aa:ff:b7:f2:96:a4:9b:44:80:b5:be:6c:
+ 56:54:ab:a4:81:9e:25:18:28:54:3a:7f:2c:63:cf:59:20:8c:
+ 18:6b:38:2c:b4:dd:ed:e3:40:de:0c:36:25:57:9a:c0:d1:60:
+ 9e:5e:03:68:97:ae:1a:3b:ea:45:d7:51:99:49:ee:44:59:56:
+ 0b:5e:b1:8f:68:ea:8a:9e:ca:d2:c9:a0:03:7e:70:25:f4:32:
+ c9:4e:50:83:87:a2:34:48:3d:4f:35:77:fc:d8:88:ea:f6:7d:
+ 1e:ce:43:b6:d5:c2:6a:7e:38:66:63:4d:e7:ee:32:ef:0f:24:
+ e8:2a:67:fa
+-----BEGIN CERTIFICATE-----
+MIIFBjCCA+6gAwIBAgIQEaO00OyNt3+doM1dLVEvQjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMDA1MjQwMDAw
+MDBaFw0yMDA1MzAxMDQ4MzhaMIGOMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDE0MDIGA1UEAxMrQ09NT0RPIEV4dGVuZGVkIFZhbGlkYXRp
+b24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMxKljPNJY1n7iiWN4dG8PYEooR/U6qW5h+xAhxu7X0h1Nc8HqLYaS+ot/Wi
+7WRYZOFEZTZJQSABjTsT4gjzDPJXOZM3txyTRIOOvy3xoQV12m7ue28b6naDKHRK
+HCvT9cQDcpOvhs4JjDx11MkKL3Lzrb0OMDyEoXMfAyUUpY/D1vS15N2GevUZumjy
+hVSiMBHK0ZLLO3QGEqA3q2rYVBHfbJoWlLm0p2XGdC0x801S6VVRn8s+oo12mHDS
+b6ZlRS8bhbtbbfnywARmE4R6nc4n2PREnr+svpnba0/bWCGwiSe0jzLWS15ykV7f
+BZ3ZSS/0tm9QH3XLgJ3m0+TR8tMCAwEAAaOCAWkwggFlMB8GA1UdIwQYMBaAFAtY
+5YvGTBU3pECpMKkhvkc2Wlb/MB0GA1UdDgQWBBSIRFH/UCppXi2I9CG62Qzyzsvq
+fDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADA+BgNVHSAENzA1
+MDMGBFUdIAAwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNv
+bS9DUFMwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2NybC5jb21vZG9jYS5jb20v
+Q09NT0RPQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdAYIKwYBBQUHAQEEaDBm
+MD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9BZGRU
+cnVzdFNlcnZlckNBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2Rv
+Y2EuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQCaQ7+vpHJezX1vf/T8PYy7cOYe3QT9
+P9ydn7+JdpvyhjH8f7PtKpFTLOKqsOPILHH3FYojHPFpLoH7sbxiC6saVBzZIl40
+TKX2Iw9dej3bQ81pfhc3Us1TocIR1FN4J2TViUFNFlW7kMvw2OTd3dMJZEgo/zIj
+hC+Me1UvzymINzR4DzOq/7fylqSbRIC1vmxWVKukgZ4lGChUOn8sY89ZIIwYazgs
+tN3t40DeDDYlV5rA0WCeXgNol64aO+pF11GZSe5EWVYLXrGPaOqKnsrSyaADfnAl
+9DLJTlCDh6I0SD1PNXf82Ijq9n0ezkO21cJqfjhmY03n7jLvDyToKmf6
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 6f:25:dc:15:af:df:5e:a3:08:56:0c:3b:7a:4f:c7:f8
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: May 30 10:48:38 2000 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+ 98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+ e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+ b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+ 86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+ da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+ d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+ 7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+ 9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+ 30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+ 77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+ 02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+ c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+ 00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+ 0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+ 2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+ 47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+ 26:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 07:60:93:99:aa:ce:d0:d3:47:d0:37:33:de:3f:64:b7:e5:2e:
+ a3:25:0c:d5:33:1d:0d:8d:ab:f6:7e:46:7b:59:06:92:e3:82:
+ c4:e7:f5:f6:f3:d9:05:cf:49:34:2d:37:5f:f4:25:c7:f0:fb:
+ 6b:23:77:f1:f1:40:d7:4c:bb:49:45:31:dd:00:28:67:b7:29:
+ 4c:75:a8:1f:79:31:c9:36:37:0f:ca:35:4f:8c:f1:7e:de:fc:
+ 46:ab:bf:68:9b:70:23:30:2e:b7:c5:5c:7b:8a:fb:18:13:79:
+ 4b:92:42:8c:dc:2c:ab:6c:22:b7:28:53:b3:1a:4a:ce:1b:fb:
+ 28:0e:b7:3a:a4:da:0d:f7:40:32:4f:df:6f:bb:01:50:fc:87:
+ d3:76:d9:fc:fb:b6:84:03:ca:c9:36:18:f7:dd:6c:db:bb:ba:
+ 81:1c:a6:ad:fe:28:f9:cf:b9:a2:71:5d:19:05:ea:4a:46:dc:
+ 73:41:ef:89:94:42:b1:43:88:6f:35:17:af:1e:60:83:ac:7a:
+ 8c:10:7b:9f:c9:f6:83:6d:9e:fa:88:ee:3e:dd:ee:9e:b0:bf:
+ e0:6a:b9:d0:9f:07:b2:09:13:9a:f5:a4:e5:c8:5b:79:a7:47:
+ 35:33:68:e5:55:9e:aa:5b:cb:30:0b:9d:c7:0f:bf:68:44:81:
+ 97:8b:51:4a
+-----BEGIN CERTIFICATE-----
+MIIE8TCCA9mgAwIBAgIQbyXcFa/fXqMIVgw7ek/H+DANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMScwJQYD
+VQQDEx5DT01PRE8gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQQIuLcuORG/dRwRtUBJjTqb/B5opdO4f7u4jO
+DeMvPwaW8KIpUJmu2zuhV7B0UXHN7UKRTUH+qcjYaoZ3RLtZZpdQXrTULHBEz9o3
+lUJpPDDEcbNS8CFNodi6OXwcnqMknfKDFpiqFnxDmxVbt640kf7UYiYYRpo/68H5
+8ZBX66x6DYvbcjBqZtXgRqNw3GjZ/wRIiXfeten7Z21B6bw5vTLZYgLxsag9bjec
+4i/i06Imi8a4VUOI4SM+pdIkOWpHqwDUobOpJf4NP6cdutNRwQuk2qw471VQJAVl
+RpM0Ty2NrcbUIRnSjsoFYXEHc0flihkSvQRNzk6cpUisuyb3AgMBAAGjggF0MIIB
+cDAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUC1jl
+i8ZMFTekQKkwqSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYI
+KwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0
+cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUA
+A4IBAQAHYJOZqs7Q00fQNzPeP2S35S6jJQzVMx0Njav2fkZ7WQaS44LE5/X289kF
+z0k0LTdf9CXH8PtrI3fx8UDXTLtJRTHdAChntylMdagfeTHJNjcPyjVPjPF+3vxG
+q79om3AjMC63xVx7ivsYE3lLkkKM3CyrbCK3KFOzGkrOG/soDrc6pNoN90AyT99v
+uwFQ/IfTdtn8+7aEA8rJNhj33Wzbu7qBHKat/ij5z7micV0ZBepKRtxzQe+JlEKx
+Q4hvNRevHmCDrHqMEHufyfaDbZ76iO4+3e6esL/garnQnweyCROa9aTlyFt5p0c1
+M2jlVZ6qW8swC53HD79oRIGXi1FK
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/crit-codeSigning-chain.pem b/chromium/net/data/ssl/certificates/crit-codeSigning-chain.pem
new file mode 100644
index 00000000000..516f84037d9
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/crit-codeSigning-chain.pem
@@ -0,0 +1,105 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvzKDpx0x9sOsDZBKBUMVs4yo7pXQT1lsfXeJTNiVErcCeSF9
+Igu2jYI3+qn03TYCcWpcgwJ86DWSuloTCOi/+xD4DKxQErJzFcl3hq8Q00r9tCNg
+7hmOup2hNYXOaC+U1tqC/TID9Gc8neJLHGkh2tfgbqemkFq6ObtYlA+hXy3kK+Tc
+7g7r+FHHWpOgj7ihc82EX+y4VT6GQov2tQqZXYSOQOcPcEru0AhGY+wOgJ2Ge4m8
+UNQmLxbmQDfqBplyRsUDqbTAjIE2nXpg9DT/vRufNW4ccSik4q1Wf/H2jAzONuL9
+79bhpBJdFad1bxJE6SOLVW2WLJQlD3VR2aUiUQIDAQABAoIBAQC7k6tlwFbMaZva
+kryL/QzFYjI6H/CGaIzvat7g7XsTKUjb3ecOB7/JhP+2hBcfD2XGLzsjrMUua6DQ
+Ap66Ft6CSluQ01ybe0c9ELKbCBXRtbjBihQGhDxrCUqnX+bogAXd4/KuxOrsVHw7
+yU83SE1WjoQ5C3F2Hgylpw938v22iXPvc+V6QjCGY1Apw75I/M3rmBHfyfVyUUYT
+Pt1UOZmhWq/gt0S5ACWDkFAxN/Ld2fyUTQM4q1PV1qR73KmxJr8y+SMTMVtx4V8n
+MFSiUeNan6xQUuI3iK4UCvi8TAFf8sKqM9vDte8wSaiC6TkJJTl+rnJtsyItVs5n
+eropLKIBAoGBAO8X374KqWREiFt/BKsjhs1upuyko7AgwHhSpkxHKqDSsOUITkym
+88+Pav/C/oExMIqRCMbz8F2V72Tu15sowVk4aFe35U+HxnWjhjCi1tI3Ujm0SGl0
+HPmEryhx6TuIj4Z901VH3mL9C8zab08n0ANYxn4aaQEOe1SOhX+KvnnZAoGBAMy3
+nbUMlVyactn757teQ0GHLnBERLYKOp7pmjx+SIUYrnPCPE2A+8ZF6Ron+5UvxtSu
+Kx4MRsOHbzI/4m+erbNX+OKN/4thLbzlokfAOJnV6n7kHeKZPVZVY3PQqr4WbN2a
+EwsVJ6gyBwGTqtXXRY7rJNPInN7i8U1ffKrkEmk5AoGBAIinqphiW6MVkKJLHQ38
+BkZQolPLXkuCzL947dfXLUQyaCzf7HDfU6ckn9GDUBfjV407jDq6nn2+/s8/vDfQ
+uYIXPXw1fXlRb1s5la8iw4nvWK3mnyS22wC6l6qUQOxoBzClpi6uHyuQ2jfB+bDS
+XpArI3hb+/xAnLkdpKvbTzcxAoGANWl22UT2N1oIuz0RQf6fF5q4kAwPzVsv3kRe
+vIfKTgeZhJRZ/XK4vguBfRSPfGYhv13N3CIh2GQerAKlBrBk65T5V8rqsKfjMhTL
+2WKaofCBJShJb9TBfyP6Nb5svfnF36+SZmLXnPeogk3P1ck72cUaL7N40mJtyN0v
+/rpQ32kCgYAF9pEUVyEmIFyPJCDyPqD8c4ZisONKd/MnLs6tYEYwxSkJq6QUTZe6
+0LWDp1Ohcg5V5Upg0pQotAPmLTQ77XPlLl92ByFdbakH/p8Q73Lv0j/y6c1BYeSn
+JQm2/uLDW4Zrp+UzD7z6km2GjyeVDxfbpaEHsXRhMfJLZdrI1ZJvyA==
+-----END RSA PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Jan 23 23:51:05 2013 GMT
+ Not After : Jan 21 23:51:05 2023 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bf:32:83:a7:1d:31:f6:c3:ac:0d:90:4a:05:43:
+ 15:b3:8c:a8:ee:95:d0:4f:59:6c:7d:77:89:4c:d8:
+ 95:12:b7:02:79:21:7d:22:0b:b6:8d:82:37:fa:a9:
+ f4:dd:36:02:71:6a:5c:83:02:7c:e8:35:92:ba:5a:
+ 13:08:e8:bf:fb:10:f8:0c:ac:50:12:b2:73:15:c9:
+ 77:86:af:10:d3:4a:fd:b4:23:60:ee:19:8e:ba:9d:
+ a1:35:85:ce:68:2f:94:d6:da:82:fd:32:03:f4:67:
+ 3c:9d:e2:4b:1c:69:21:da:d7:e0:6e:a7:a6:90:5a:
+ ba:39:bb:58:94:0f:a1:5f:2d:e4:2b:e4:dc:ee:0e:
+ eb:f8:51:c7:5a:93:a0:8f:b8:a1:73:cd:84:5f:ec:
+ b8:55:3e:86:42:8b:f6:b5:0a:99:5d:84:8e:40:e7:
+ 0f:70:4a:ee:d0:08:46:63:ec:0e:80:9d:86:7b:89:
+ bc:50:d4:26:2f:16:e6:40:37:ea:06:99:72:46:c5:
+ 03:a9:b4:c0:8c:81:36:9d:7a:60:f4:34:ff:bd:1b:
+ 9f:35:6e:1c:71:28:a4:e2:ad:56:7f:f1:f6:8c:0c:
+ ce:36:e2:fd:ef:d6:e1:a4:12:5d:15:a7:75:6f:12:
+ 44:e9:23:8b:55:6d:96:2c:94:25:0f:75:51:d9:a5:
+ 22:51
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ A2:2A:EC:DD:31:B9:D2:D1:6A:21:8D:11:C1:2B:E5:1E:92:85:C4:06
+ X509v3 Extended Key Usage: critical
+ Code Signing
+ Signature Algorithm: sha1WithRSAEncryption
+ 6c:46:ce:18:06:16:77:8a:81:dc:aa:f9:b9:0d:af:ce:61:bf:
+ 3e:60:2c:c9:58:97:54:41:a9:14:44:4d:e7:b7:fa:3c:6f:09:
+ 92:d6:d2:5d:89:60:ef:ec:3d:62:f7:22:d9:df:e4:05:3b:23:
+ 2a:3c:f1:6f:f3:e9:9c:61:8f:b0:92:d2:67:81:aa:43:dc:37:
+ bc:27:e7:d8:59:ce:f9:cc:fd:b0:30:c3:8d:68:f0:bf:91:7c:
+ 3b:1a:7c:af:ef:23:1a:c2:1d:bc:be:c2:eb:06:9a:57:be:ed:
+ b6:9f:81:6f:86:35:22:68:2d:89:47:d6:80:ca:4a:91:05:6d:
+ 49:aa:88:9e:4b:3a:90:0b:72:4f:b4:af:44:5d:67:52:11:cf:
+ 6b:52:ac:db:48:23:aa:e0:53:f0:4c:49:98:62:70:d7:dc:2d:
+ c9:8d:5d:35:7c:e4:a9:50:d4:50:56:e1:d9:9a:7a:35:f7:11:
+ b6:98:f6:76:6d:33:7c:8b:1a:82:20:2c:ae:b5:93:f0:55:77:
+ da:f0:a2:86:e8:d4:4d:7f:e9:c0:e3:21:6e:bc:e9:af:9a:9a:
+ f3:cd:36:4f:db:6f:fe:de:1f:fa:20:de:ec:f6:63:d2:98:01:
+ f4:45:29:1f:88:a0:0b:5c:a8:4a:0e:87:c5:94:07:fb:60:79:
+ 13:a9:ce:1f
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjcCAgDtMA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNVBAMMFTIwNDggUlNB
+IFRlc3QgUm9vdCBDQTAeFw0xMzAxMjMyMzUxMDVaFw0yMzAxMjEyMzUxMDVaMGAx
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3Vu
+dGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/MoOnHTH2w6wNkEoFQxWz
+jKjuldBPWWx9d4lM2JUStwJ5IX0iC7aNgjf6qfTdNgJxalyDAnzoNZK6WhMI6L/7
+EPgMrFASsnMVyXeGrxDTSv20I2DuGY66naE1hc5oL5TW2oL9MgP0Zzyd4kscaSHa
+1+Bup6aQWro5u1iUD6FfLeQr5NzuDuv4Ucdak6CPuKFzzYRf7LhVPoZCi/a1Cpld
+hI5A5w9wSu7QCEZj7A6AnYZ7ibxQ1CYvFuZAN+oGmXJGxQOptMCMgTademD0NP+9
+G581bhxxKKTirVZ/8faMDM424v3v1uGkEl0Vp3VvEkTpI4tVbZYslCUPdVHZpSJR
+AgMBAAGjWDBWMA8GA1UdEQQIMAaHBH8AAAEwDAYDVR0TAQH/BAIwADAdBgNVHQ4E
+FgQUoirs3TG50tFqIY0RwSvlHpKFxAYwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwMw
+DQYJKoZIhvcNAQEFBQADggEBAGxGzhgGFneKgdyq+bkNr85hvz5gLMlYl1RBqRRE
+Tee3+jxvCZLW0l2JYO/sPWL3Itnf5AU7Iyo88W/z6Zxhj7CS0meBqkPcN7wn59hZ
+zvnM/bAww41o8L+RfDsafK/vIxrCHby+wusGmle+7bafgW+GNSJoLYlH1oDKSpEF
+bUmqiJ5LOpALck+0r0RdZ1IRz2tSrNtII6rgU/BMSZhicNfcLcmNXTV85KlQ1FBW
+4dmaejX3EbaY9nZtM3yLGoIgLK61k/BVd9rwoobo1E1/6cDjIW686a+amvPNNk/b
+b/7eH/og3uz2Y9KYAfRFKR+IoAtcqEoOh8WUB/tgeROpzh8=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cross-signed-leaf.pem b/chromium/net/data/ssl/certificates/cross-signed-leaf.pem
new file mode 100644
index 00000000000..563c0575eab
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cross-signed-leaf.pem
@@ -0,0 +1,82 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Dup-Hash Root CA
+ Validity
+ Not Before: Jul 1 22:15:21 2013 GMT
+ Not After : Jun 29 22:15:21 2023 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b7:96:78:e6:03:94:d8:22:d8:88:01:b2:00:93:
+ 86:a1:c0:d9:49:5f:23:40:93:f8:ec:3f:9e:b2:b3:
+ 9d:70:ea:6d:83:6c:05:fd:7c:04:be:61:0f:3b:12:
+ 54:cf:36:fd:0b:33:b9:e8:6f:ea:2a:fd:6c:9a:1f:
+ a9:8d:e9:1b:77:0f:7b:cd:14:1c:5c:b2:0f:8b:b7:
+ 97:14:7c:25:78:e0:26:3c:e0:e6:8a:f7:6f:25:5d:
+ 45:34:c3:fe:26:67:d7:69:8d:21:e9:b4:86:59:86:
+ cb:15:fa:10:dc:d3:30:57:da:0d:a5:c7:ee:16:f6:
+ 56:89:e3:51:7e:e6:2d:8d:9c:6f:3f:ca:57:3b:bc:
+ 1e:81:58:28:a1:ff:0f:d9:0b:44:a5:04:9d:a4:4f:
+ 68:5a:67:46:80:1b:df:24:40:49:ac:4c:85:70:f3:
+ 84:47:81:cf:8d:cc:4b:e2:b0:4b:1f:33:ed:c3:a0:
+ 7d:8c:d5:a0:4c:43:68:fb:2a:59:74:25:2b:ef:da:
+ 3c:db:75:6c:84:b4:1e:fa:f1:26:40:03:7c:f7:04:
+ cf:99:70:9a:49:10:f6:91:1c:30:2d:a7:33:5f:7e:
+ 92:4e:b0:91:4f:24:35:92:92:e3:77:99:db:6f:21:
+ 4d:82:b8:01:f8:d6:5e:cc:1a:20:1e:d9:3e:d7:1d:
+ fb:dd
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 80:2C:F6:3D:68:54:4C:34:20:8F:76:5B:06:97:BB:9E:40:8D:28:3E
+ X509v3 Authority Key Identifier:
+ keyid:96:2A:53:9C:77:4D:AC:DD:C3:62:E8:7C:3A:53:66:E6:B1:9E:D0:5F
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 78:fb:0b:4a:4f:9e:6d:76:b7:c0:27:0f:06:92:a9:c1:97:d9:
+ cd:15:5d:9b:18:93:2a:06:95:bc:48:e9:4e:02:ac:92:a5:c8:
+ 6a:71:1d:69:a2:8d:ae:bd:cf:5b:d9:c3:5e:cb:db:01:13:1b:
+ c7:13:fd:22:f6:ca:48:ca:98:de:93:54:48:15:38:1f:22:c8:
+ 2f:fb:71:73:2e:f5:d2:7e:48:91:6e:9a:c7:0b:51:96:d6:c1:
+ c0:2a:d4:3c:69:3e:dd:1d:7d:d4:b1:0f:d1:d3:41:ca:a8:22:
+ 8f:19:9d:a7:91:7b:25:26:4e:c6:e8:3b:9b:e7:cd:c4:f2:c5:
+ 63:c3:10:6f:93:03:b1:c6:e9:05:db:1e:cf:42:19:72:b2:b2:
+ cf:30:a0:99:70:6c:fb:4e:7f:ca:a5:3b:8b:83:72:10:77:04:
+ e4:96:a9:8e:70:2d:c0:54:71:5e:76:8f:4c:20:33:d6:78:f2:
+ d9:6a:2c:b0:7a:1e:82:08:3d:e8:59:87:e9:a0:3c:84:3d:b4:
+ 60:38:01:89:04:93:d0:3e:36:5d:57:aa:03:4f:ca:46:80:a0:
+ d2:2c:d0:59:18:b1:fb:66:84:39:4d:90:c5:20:d7:4b:03:11:
+ 77:b4:fd:24:58:d9:1f:dc:4d:34:f7:c9:54:59:3f:8e:1a:17:
+ e8:b1:65:a5
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UEAwwVVGVz
+dCBEdXAtSGFzaCBSb290IENBMB4XDTEzMDcwMTIyMTUyMVoXDTIzMDYyOTIyMTUy
+MVowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEyNy4w
+LjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALeWeOYDlNgi2IgB
+sgCThqHA2UlfI0CT+Ow/nrKznXDqbYNsBf18BL5hDzsSVM82/Qszuehv6ir9bJof
+qY3pG3cPe80UHFyyD4u3lxR8JXjgJjzg5or3byVdRTTD/iZn12mNIem0hlmGyxX6
+ENzTMFfaDaXH7hb2VonjUX7mLY2cbz/KVzu8HoFYKKH/D9kLRKUEnaRPaFpnRoAb
+3yRASaxMhXDzhEeBz43MS+KwSx8z7cOgfYzVoExDaPsqWXQlK+/aPNt1bIS0Hvrx
+JkADfPcEz5lwmkkQ9pEcMC2nM19+kk6wkU8kNZKS43eZ228hTYK4AfjWXswaIB7Z
+Ptcd+90CAwEAAaOBgDB+MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIAs9j1oVEw0
+II92WwaXu55AjSg+MB8GA1UdIwQYMBaAFJYqU5x3Tazdw2LofDpTZuaxntBfMB0G
+A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHREECDAGhwR/AAABMA0G
+CSqGSIb3DQEBBQUAA4IBAQB4+wtKT55tdrfAJw8GkqnBl9nNFV2bGJMqBpW8SOlO
+AqySpchqcR1poo2uvc9b2cNey9sBExvHE/0i9spIypjek1RIFTgfIsgv+3FzLvXS
+fkiRbprHC1GW1sHAKtQ8aT7dHX3UsQ/R00HKqCKPGZ2nkXslJk7G6Dub583E8sVj
+wxBvkwOxxukF2x7PQhlysrLPMKCZcGz7Tn/KpTuLg3IQdwTklqmOcC3AVHFedo9M
+IDPWePLZaiyweh6CCD3oWYfpoDyEPbRgOAGJBJPQPjZdV6oDT8pGgKDSLNBZGLH7
+ZoQ5TZDFINdLAxF3tP0kWNkf3E0098lUWT+OGhfosWWl
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cross-signed-root-md5.pem b/chromium/net/data/ssl/certificates/cross-signed-root-md5.pem
new file mode 100644
index 00000000000..24f2dab9f3c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cross-signed-root-md5.pem
@@ -0,0 +1,75 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 9681607970376803213 (0x865bfb2758e6638d)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: CN=Test Dup-Hash Root CA
+ Validity
+ Not Before: Jul 1 22:15:21 2013 GMT
+ Not After : Jun 29 22:15:21 2023 GMT
+ Subject: CN=Test Dup-Hash Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:de:f6:bf:8a:9b:10:4f:4c:c8:e7:9d:38:f8:ce:
+ fc:f9:02:89:38:05:be:2e:cc:bc:18:8d:cc:32:cb:
+ 65:06:21:b6:12:1a:af:fc:98:60:26:60:e7:60:57:
+ 68:24:17:d6:6c:f9:f1:0f:5b:a9:ae:55:a3:fc:84:
+ c7:b4:0d:e6:71:98:e5:a5:6a:3a:30:05:35:22:59:
+ 29:d3:27:4c:82:c4:1c:d6:2b:19:78:c7:2c:6e:75:
+ c0:bb:5f:3c:c0:9f:ed:0f:72:10:59:0d:cd:88:08:
+ 76:9b:e1:fc:1d:7d:d1:d0:d7:e1:76:d8:44:9c:c4:
+ 80:3e:d1:09:cb:67:07:81:ed:1a:fc:68:15:3a:11:
+ f8:f8:8e:02:8b:ec:e3:c5:e6:84:7f:99:79:cf:d8:
+ 9e:54:ea:3e:65:3c:ae:cb:4f:a5:4a:3b:32:65:00:
+ 92:45:e5:cd:2a:38:f3:18:b2:1d:62:8e:3c:a5:89:
+ 47:6a:0d:43:87:68:82:10:5d:e3:db:70:e5:60:9c:
+ 13:c4:87:54:3d:3c:7b:f1:b4:16:b6:4b:b9:23:74:
+ a4:a1:91:e2:61:29:63:37:b2:74:ec:49:a1:94:35:
+ 34:fd:c6:5c:55:54:14:14:eb:e1:12:22:28:0a:9e:
+ 10:b4:37:54:e6:50:93:85:87:0f:c1:34:ca:cb:76:
+ 12:8f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 96:2A:53:9C:77:4D:AC:DD:C3:62:E8:7C:3A:53:66:E6:B1:9E:D0:5F
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: md5WithRSAEncryption
+ 2d:0f:96:87:ae:0d:e6:47:c7:5b:b7:5c:d3:85:7a:b0:1b:e0:
+ 6e:43:1f:c7:8d:ab:1b:5a:5b:bf:3b:a4:de:4b:45:bd:f6:59:
+ 12:b2:56:7e:4c:ea:67:e9:bc:23:09:ba:e8:89:55:78:98:55:
+ b0:12:bf:92:3c:45:e7:ec:56:f7:88:1d:10:f3:68:1a:84:97:
+ 26:44:90:e5:19:09:77:94:08:2c:f1:27:1e:bb:09:6d:36:47:
+ de:e2:7d:b4:fb:d5:8d:11:d7:09:9e:22:16:3e:e9:67:de:2b:
+ e6:6b:25:f6:02:b1:9c:5f:27:62:fe:21:a1:58:8f:b0:68:7d:
+ 44:1a:b4:af:b1:1b:c3:5d:84:d0:a4:1c:57:4c:28:2d:64:4f:
+ c6:19:93:c2:96:7d:b0:1f:80:e5:08:a0:14:1f:03:0c:ab:90:
+ c5:c9:01:22:36:39:66:a9:38:9d:8f:89:34:da:60:85:6e:de:
+ 47:33:78:d3:52:ea:0b:8d:38:70:8a:6b:b1:72:18:8e:e2:01:
+ 28:df:23:4a:18:9c:65:7e:f9:42:04:84:62:67:40:31:16:9d:
+ 09:0c:d6:2c:b6:86:5d:d0:b4:08:af:ca:d3:4e:a2:2c:15:d7:
+ c8:48:5c:92:ee:33:84:2d:6e:5e:97:f3:f7:ee:1a:30:fc:83:
+ 50:60:80:7a
+-----BEGIN CERTIFICATE-----
+MIIDBTCCAe2gAwIBAgIJAIZb+ydY5mONMA0GCSqGSIb3DQEBBAUAMCAxHjAcBgNV
+BAMMFVRlc3QgRHVwLUhhc2ggUm9vdCBDQTAeFw0xMzA3MDEyMjE1MjFaFw0yMzA2
+MjkyMjE1MjFaMCAxHjAcBgNVBAMMFVRlc3QgRHVwLUhhc2ggUm9vdCBDQTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN72v4qbEE9MyOedOPjO/PkCiTgF
+vi7MvBiNzDLLZQYhthIar/yYYCZg52BXaCQX1mz58Q9bqa5Vo/yEx7QN5nGY5aVq
+OjAFNSJZKdMnTILEHNYrGXjHLG51wLtfPMCf7Q9yEFkNzYgIdpvh/B190dDX4XbY
+RJzEgD7RCctnB4HtGvxoFToR+PiOAovs48XmhH+Zec/YnlTqPmU8rstPpUo7MmUA
+kkXlzSo48xiyHWKOPKWJR2oNQ4doghBd49tw5WCcE8SHVD08e/G0FrZLuSN0pKGR
+4mEpYzeydOxJoZQ1NP3GXFVUFBTr4RIiKAqeELQ3VOZQk4WHD8E0yst2Eo8CAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUlipTnHdNrN3DYuh8OlNm
+5rGe0F8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBAUAA4IBAQAtD5aHrg3m
+R8dbt1zThXqwG+BuQx/HjasbWlu/O6TeS0W99lkSslZ+TOpn6bwjCbroiVV4mFWw
+Er+SPEXn7Fb3iB0Q82gahJcmRJDlGQl3lAgs8SceuwltNkfe4n20+9WNEdcJniIW
+Puln3ivmayX2ArGcXydi/iGhWI+waH1EGrSvsRvDXYTQpBxXTCgtZE/GGZPCln2w
+H4DlCKAUHwMMq5DFyQEiNjlmqTidj4k02mCFbt5HM3jTUuoLjThwimuxchiO4gEo
+3yNKGJxlfvlCBIRiZ0AxFp0JDNYstoZd0LQIr8rTTqIsFdfISFyS7jOELW5el/P3
+7how/INQYIB6
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cross-signed-root-sha1.pem b/chromium/net/data/ssl/certificates/cross-signed-root-sha1.pem
new file mode 100644
index 00000000000..71df04f4b66
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cross-signed-root-sha1.pem
@@ -0,0 +1,75 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 16026332507200838417 (0xde68f88110005b11)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Dup-Hash Root CA
+ Validity
+ Not Before: Jul 1 22:15:21 2013 GMT
+ Not After : Jun 29 22:15:21 2023 GMT
+ Subject: CN=Test Dup-Hash Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:de:f6:bf:8a:9b:10:4f:4c:c8:e7:9d:38:f8:ce:
+ fc:f9:02:89:38:05:be:2e:cc:bc:18:8d:cc:32:cb:
+ 65:06:21:b6:12:1a:af:fc:98:60:26:60:e7:60:57:
+ 68:24:17:d6:6c:f9:f1:0f:5b:a9:ae:55:a3:fc:84:
+ c7:b4:0d:e6:71:98:e5:a5:6a:3a:30:05:35:22:59:
+ 29:d3:27:4c:82:c4:1c:d6:2b:19:78:c7:2c:6e:75:
+ c0:bb:5f:3c:c0:9f:ed:0f:72:10:59:0d:cd:88:08:
+ 76:9b:e1:fc:1d:7d:d1:d0:d7:e1:76:d8:44:9c:c4:
+ 80:3e:d1:09:cb:67:07:81:ed:1a:fc:68:15:3a:11:
+ f8:f8:8e:02:8b:ec:e3:c5:e6:84:7f:99:79:cf:d8:
+ 9e:54:ea:3e:65:3c:ae:cb:4f:a5:4a:3b:32:65:00:
+ 92:45:e5:cd:2a:38:f3:18:b2:1d:62:8e:3c:a5:89:
+ 47:6a:0d:43:87:68:82:10:5d:e3:db:70:e5:60:9c:
+ 13:c4:87:54:3d:3c:7b:f1:b4:16:b6:4b:b9:23:74:
+ a4:a1:91:e2:61:29:63:37:b2:74:ec:49:a1:94:35:
+ 34:fd:c6:5c:55:54:14:14:eb:e1:12:22:28:0a:9e:
+ 10:b4:37:54:e6:50:93:85:87:0f:c1:34:ca:cb:76:
+ 12:8f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 96:2A:53:9C:77:4D:AC:DD:C3:62:E8:7C:3A:53:66:E6:B1:9E:D0:5F
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 82:0e:89:6b:05:a5:56:b1:0f:79:19:9f:1e:88:c9:7a:ba:fc:
+ 7e:9b:58:39:64:38:89:64:c6:59:c6:be:d1:c1:d7:41:bb:ac:
+ 68:22:61:df:27:0f:ee:00:90:8f:8a:27:ab:dc:46:6f:d3:f2:
+ 5e:1e:02:1f:8b:56:9f:83:6d:8d:a4:2a:2e:e3:f1:f1:91:ec:
+ c9:1c:f2:10:b7:7d:47:f4:1b:ff:75:c5:a9:74:5c:d7:f8:41:
+ c3:51:30:9d:bf:13:f6:24:82:70:15:83:2f:0d:e4:ce:8f:3a:
+ ff:92:d5:7d:6d:1a:66:6e:4a:15:4f:c3:c3:45:8d:12:09:c8:
+ 0c:58:db:6b:3c:7b:3f:65:49:ea:5d:72:c3:3d:9b:a5:5f:72:
+ 9f:72:d6:32:44:4f:cc:79:2a:c1:22:01:5e:0a:cf:2b:f7:03:
+ c6:bf:15:4b:d1:5a:ab:0f:c5:8f:11:6c:73:e9:a5:60:90:18:
+ ec:c2:dc:27:ce:f8:f1:95:d3:8e:df:29:c5:4a:dc:35:db:ed:
+ 71:ee:33:2e:71:69:6f:29:fd:40:b2:ff:66:19:81:c7:4d:c3:
+ e8:19:ad:d0:23:b3:7f:57:4e:95:15:52:dc:bf:65:b4:02:59:
+ dc:07:c4:e9:08:88:bf:c1:a4:1b:6e:79:3f:38:02:fd:11:93:
+ a8:f0:11:65
+-----BEGIN CERTIFICATE-----
+MIIDBTCCAe2gAwIBAgIJAN5o+IEQAFsRMA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNV
+BAMMFVRlc3QgRHVwLUhhc2ggUm9vdCBDQTAeFw0xMzA3MDEyMjE1MjFaFw0yMzA2
+MjkyMjE1MjFaMCAxHjAcBgNVBAMMFVRlc3QgRHVwLUhhc2ggUm9vdCBDQTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN72v4qbEE9MyOedOPjO/PkCiTgF
+vi7MvBiNzDLLZQYhthIar/yYYCZg52BXaCQX1mz58Q9bqa5Vo/yEx7QN5nGY5aVq
+OjAFNSJZKdMnTILEHNYrGXjHLG51wLtfPMCf7Q9yEFkNzYgIdpvh/B190dDX4XbY
+RJzEgD7RCctnB4HtGvxoFToR+PiOAovs48XmhH+Zec/YnlTqPmU8rstPpUo7MmUA
+kkXlzSo48xiyHWKOPKWJR2oNQ4doghBd49tw5WCcE8SHVD08e/G0FrZLuSN0pKGR
+4mEpYzeydOxJoZQ1NP3GXFVUFBTr4RIiKAqeELQ3VOZQk4WHD8E0yst2Eo8CAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUlipTnHdNrN3DYuh8OlNm
+5rGe0F8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCCDolrBaVW
+sQ95GZ8eiMl6uvx+m1g5ZDiJZMZZxr7RwddBu6xoImHfJw/uAJCPiier3EZv0/Je
+HgIfi1afg22NpCou4/HxkezJHPIQt31H9Bv/dcWpdFzX+EHDUTCdvxP2JIJwFYMv
+DeTOjzr/ktV9bRpmbkoVT8PDRY0SCcgMWNtrPHs/ZUnqXXLDPZulX3KfctYyRE/M
+eSrBIgFeCs8r9wPGvxVL0VqrD8WPEWxz6aVgkBjswtwnzvjxldOO3ynFStw12+1x
+7jMucWlvKf1Asv9mGYHHTcPoGa3QI7N/V06VFVLcv2W0AlncB8TpCIi/waQbbnk/
+OAL9EZOo8BFl
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_1.pem b/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_1.pem
new file mode 100644
index 00000000000..31bb088d416
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_1.pem
@@ -0,0 +1,82 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120033005 (0x7278eed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Apr 18 16:36:18 2012 GMT
+ Not After : Aug 13 16:35:17 2018 GMT
+ Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+ d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+ 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+ 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+ 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+ 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+ 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+ a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+ 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+ d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+ 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+ 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+ ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+ 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+ c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+ ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+ 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+ 1a:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://cybertrust.omniroot.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 93:1d:fe:8b:ae:46:ec:cb:a9:0f:ab:e5:ef:ca:b2:68:16:68:
+ d8:8f:fa:13:a9:af:b3:cb:2d:e7:4b:6e:8e:69:2a:c2:2b:10:
+ 0a:8d:f6:ae:73:b6:b9:fb:14:fd:5f:6d:b8:50:b6:c4:8a:d6:
+ 40:7e:d7:c3:cb:73:dc:c9:5d:5b:af:b0:41:b5:37:eb:ea:dc:
+ 20:91:c4:34:6a:f4:a1:f3:96:9d:37:86:97:e1:71:a4:dd:7d:
+ fa:44:84:94:ae:d7:09:04:22:76:0f:64:51:35:a9:24:0f:f9:
+ 0b:db:32:da:c2:fe:c1:b9:2a:5c:7a:27:13:ca:b1:48:3a:71:
+ d0:43
+-----BEGIN CERTIFICATE-----
+MIIEFTCCA36gAwIBAgIEByeO7TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEyMDQxODE2MzYxOFoXDTE4MDgxMzE2MzUxN1owWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAUcw
+ggFDMBIGA1UdEwEB/wQIMAYBAf8CAQMwSgYDVR0gBEMwQTA/BgRVHSAAMDcwNQYI
+KwYBBQUHAgEWKWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5MA4GA1UdDwEB/wQEAwIBBjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYT
+AlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJl
+clRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3Qg
+R2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVi
+bGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwDQYJKoZIhvcN
+AQEFBQADgYEAkx3+i65G7MupD6vl78qyaBZo2I/6E6mvs8st50tujmkqwisQCo32
+rnO2ufsU/V9tuFC2xIrWQH7Xw8tz3MldW6+wQbU36+rcIJHENGr0ofOWnTeGl+Fx
+pN19+kSElK7XCQQidg9kUTWpJA/5C9sy2sL+wbkqXHonE8qxSDpx0EM=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_2.pem b/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_2.pem
new file mode 100644
index 00000000000..8d3445bac4c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cybertrust_baltimore_cross_certified_2.pem
@@ -0,0 +1,85 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120024505 (0x7276db9)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Nov 30 16:35:21 2010 GMT
+ Not After : Aug 10 15:34:26 2018 GMT
+ Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+ d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+ 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+ 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+ 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+ 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+ 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+ a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+ 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+ d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+ 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+ 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+ ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+ 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+ c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+ ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+ 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+ 1a:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+ Signature Algorithm: sha1WithRSAEncryption
+ 16:b4:2c:c9:f1:5e:e1:a2:7b:9b:78:20:7a:4a:70:70:86:19:
+ 00:b7:05:2a:e8:c9:25:39:0f:c3:64:3c:75:09:d9:89:15:80:
+ 07:c2:8d:bc:29:a5:64:50:cf:71:75:47:23:bd:4d:d8:7f:77:
+ 9a:51:10:6e:4e:1f:20:3c:47:9c:43:74:7f:96:84:10:4c:13:
+ 43:be:f8:e0:72:2e:ff:bf:ae:3c:0a:03:60:82:4b:6f:f9:9a:
+ c5:1e:f6:af:90:3b:9f:61:3b:3e:de:9b:05:1a:c6:2c:3c:57:
+ 21:08:0f:54:fa:28:63:6c:e8:1b:9c:0f:cf:dd:30:44:13:b9:
+ 57:fe
+-----BEGIN CERTIFICATE-----
+MIIEODCCA6GgAwIBAgIEBydtuTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMTEzMDE2MzUyMVoXDTE4MDgxMDE1MzQyNlowWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAWow
+ggFmMBIGA1UdEwEB/wQIMAYBAf8CAQMwTgYDVR0gBEcwRTBDBgRVHSAAMDswOQYI
+KwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5LmNmbTAOBgNVHQ8BAf8EBAMCAQYwgYkGA1UdIwSBgTB/oXmkdzB1MQswCQYD
+VQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUg
+Q3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRy
+dXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vd3d3
+LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3JsMB0GA1Ud
+DgQWBBTlnVkwgkdYzKz6CFQ2hns6tQRN8DANBgkqhkiG9w0BAQUFAAOBgQAWtCzJ
+8V7honubeCB6SnBwhhkAtwUq6MklOQ/DZDx1CdmJFYAHwo28KaVkUM9xdUcjvU3Y
+f3eaURBuTh8gPEecQ3R/loQQTBNDvvjgci7/v648CgNggktv+ZrFHvavkDufYTs+
+3psFGsYsPFchCA9U+ihjbOgbnA/P3TBEE7lX/g==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cybertrust_baltimore_root.pem b/chromium/net/data/ssl/certificates/cybertrust_baltimore_root.pem
new file mode 100644
index 00000000000..43a6c0fc1f9
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cybertrust_baltimore_root.pem
@@ -0,0 +1,77 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 33554617 (0x20000b9)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: May 12 18:46:00 2000 GMT
+ Not After : May 12 23:59:00 2025 GMT
+ Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+ d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+ 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+ 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+ 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+ 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+ 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+ a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+ 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+ d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+ 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+ 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+ ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+ 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+ c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+ ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+ 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+ 1a:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:3
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 85:0c:5d:8e:e4:6f:51:68:42:05:a0:dd:bb:4f:27:25:84:03:
+ bd:f7:64:fd:2d:d7:30:e3:a4:10:17:eb:da:29:29:b6:79:3f:
+ 76:f6:19:13:23:b8:10:0a:f9:58:a4:d4:61:70:bd:04:61:6a:
+ 12:8a:17:d5:0a:bd:c5:bc:30:7c:d6:e9:0c:25:8d:86:40:4f:
+ ec:cc:a3:7e:38:c6:37:11:4f:ed:dd:68:31:8e:4c:d2:b3:01:
+ 74:ee:be:75:5e:07:48:1a:7f:70:ff:16:5c:84:c0:79:85:b8:
+ 05:fd:7f:be:65:11:a3:0f:c0:02:b4:f8:52:37:39:04:d5:a9:
+ 31:7a:18:bf:a0:2a:f4:12:99:f7:a3:45:82:e3:3c:5e:f5:9d:
+ 9e:b5:c8:9e:7c:2e:c8:a4:9e:4e:08:14:4b:6d:fd:70:6d:6b:
+ 1a:63:bd:64:e6:1f:b7:ce:f0:f2:9f:2e:bb:1b:b7:f2:50:88:
+ 73:92:c2:e2:e3:16:8d:9a:32:02:ab:8e:18:dd:e9:10:11:ee:
+ 7e:35:ab:90:af:3e:30:94:7a:d0:33:3d:a7:65:0f:f5:fc:8e:
+ 9e:62:cf:47:44:2c:01:5d:bb:1d:b5:32:d2:47:d2:38:2e:d0:
+ fe:81:dc:32:6a:1e:b5:ee:3c:d5:fc:e7:81:1d:19:c3:24:42:
+ ea:63:39:a9
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cybertrust_gte_root.pem b/chromium/net/data/ssl/certificates/cybertrust_gte_root.pem
new file mode 100644
index 00000000000..27fcceb27f8
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cybertrust_gte_root.pem
@@ -0,0 +1,48 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 421 (0x1a5)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Aug 13 00:29:00 1998 GMT
+ Not After : Aug 13 23:59:00 2018 GMT
+ Subject: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:95:0f:a0:b6:f0:50:9c:e8:7a:c7:88:cd:dd:17:
+ 0e:2e:b0:94:d0:1b:3d:0e:f6:94:c0:8a:94:c7:06:
+ c8:90:97:c8:b8:64:1a:7a:7e:6c:3c:53:e1:37:28:
+ 73:60:7f:b2:97:53:07:9f:53:f9:6d:58:94:d2:af:
+ 8d:6d:88:67:80:e6:ed:b2:95:cf:72:31:ca:a5:1c:
+ 72:ba:5c:02:e7:64:42:e7:f9:a9:2c:d6:3a:0d:ac:
+ 8d:42:aa:24:01:39:e6:9c:3f:01:85:57:0d:58:87:
+ 45:f8:d3:85:aa:93:69:26:85:70:48:80:3f:12:15:
+ c7:79:b4:1f:05:2f:3b:62:99
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: md5WithRSAEncryption
+ 6d:eb:1b:09:e9:5e:d9:51:db:67:22:61:a4:2a:3c:48:77:e3:
+ a0:7c:a6:de:73:a2:14:03:85:3d:fb:ab:0e:30:c5:83:16:33:
+ 81:13:08:9e:7b:34:4e:df:40:c8:74:d7:b9:7d:dc:f4:76:55:
+ 7d:9b:63:54:18:e9:f0:ea:f3:5c:b1:d9:8b:42:1e:b9:c0:95:
+ 4e:ba:fa:d5:e2:7c:f5:68:61:bf:8e:ec:05:97:5f:5b:b0:d7:
+ a3:85:34:c4:24:a7:0d:0f:95:93:ef:cb:94:d8:9e:1f:9d:5c:
+ 85:6d:c7:aa:ae:4f:1f:22:b5:cd:95:ad:ba:a7:cc:f9:ab:0b:
+ 7a:7f
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/cybertrust_omniroot_chain.pem b/chromium/net/data/ssl/certificates/cybertrust_omniroot_chain.pem
new file mode 100644
index 00000000000..af584a11846
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/cybertrust_omniroot_chain.pem
@@ -0,0 +1,48 @@
+-----BEGIN CERTIFICATE-----
+MIID/TCCAuWgAwIBAgIOAgAAAAABPSFwPhps5l0wDQYJKoZIhvcNAQEFBQAwRjEX
+MBUGA1UEChMOQ3liZXJ0cnVzdCBJbmMxKzApBgNVBAMTIkN5YmVydHJ1c3QgUHVi
+bGljIFN1cmVTZXJ2ZXIgU1YgQ0EwHhcNMTMwMjI4MTU0NjQxWhcNMTYwMjI5MTU0
+NjQxWjA9MQswCQYDVQQGEwJVUzEQMA4GA1UEChMHVmVyaXpvbjEcMBoGA1UEAxMT
+Y2FjZXJ0Lm9tbmlyb290LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAN0nfD4v5g24/aThycyY8BQVHq6Ehs6/5NsoNHuLCy2MRagK4sxD9qU0Ue8z
+HwaULEa4sWKEIMg765tkbiyEfo06SM1QXQDXb+z7QNZAdpZA3q8Caw7LPx4Z8GL9
+rDUaLEd5RJ33a2AiGJebbUQAo//PZrtksy3QAOZF7bXuUyhZrJwKUPrcG+bOK6CU
+oRBDKMaqBYG+pjxZ07Pp9saiQeIT2OM4hQRGO8oN7oCvYKAx9TBxGPT2G8cswJh2
+UrikRNZDnyAoosI+fWWIamVXDirJSlbi3TpHHwL/QtuXOlXhipb7gZLYyMaOIJMX
+ymB0W3pSkVvY0PEfanGxNHjf3/cCAwEAAaOB8TCB7jAfBgNVHSMEGDAWgBQEmGDf
+gBuWSV1lVi2lLAkkCuzcuTA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLm9t
+bmlyb290LmNvbS9QdWJsaWNTdXJlU2VydmVyU1YuY3JsMB0GA1UdDgQWBBQ4hF16
+ABr+XdLRPxE4TPPgGWqd+DAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIFoDAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEQYJYIZIAYb4QgEBBAQDAgbAMB4G
+A1UdEQQXMBWCE2NhY2VydC5vbW5pcm9vdC5jb20wDQYJKoZIhvcNAQEFBQADggEB
+ACfEzbzAnq0UqfKUvoVGTnhRLd8cwTP0lvZBn4DZMgEziU7YMN33OQCKZqVdzOzl
+6y78glpFpkkWBvnUaAKgS8GxuigZX6AefB8bVxbsW76DoE+BYG6zJYZkeoUgv6qh
+rjnX2S1q/+wfbuuq1OR7BGpqZ8uDmq2k+qePWj+Mgd3ZtLbBaGWn5XrxvWSLjDlU
+eVOmSHvTcXQyeJpF1Eqsjt+9CjFSnexfVuVjst+iRr87qM9L8/QbIqNyDV7nNTfE
+Z+5bk9WtxFzkkBhwprFLMjtxoJ+JEjhK5WmrQ6wVT/ggmIz0d9oDhEcVh95LBES0
+E4qr4DvFQ2FdPKYoFFPm5uM=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEGzCCAwOgAwIBAgIEByc3DDANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEwMDkwODE3MzUxNloX
+DTIwMDkwODE3MzQwOFowRjEXMBUGA1UEChMOQ3liZXJ0cnVzdCBJbmMxKzApBgNV
+BAMTIkN5YmVydHJ1c3QgUHVibGljIFN1cmVTZXJ2ZXIgU1YgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjupmNt+HNc4j5ud3e9AXzJfU/xVIeUVo/
+mv9NhLdQf/EQil1/ZFUcO6Pz/5d/HEvtb3/pVOyXKkIDZ3+5yGyil/hAkyTDJV6l
+ZouGvde5JiJu0maDs3jBfFh2EesWVUcy8Lk0EL2PJqIlaMEUK6Jz1mY9RIdcE39Y
+kWI9V39srkLoEn69ePHxrFw1YGhFvFNzhxEdxS76YDXakfna8lVsv8qiV1zIZLyp
+WxWg/BzzRC69BvJo2EAtu7NhJZKTJRx3RpC/0K+3g6A8h16lkaj/wTEbtkusEjQI
+1dvsiYdjBqdT+NX15masXoRlRsn0OiUPbMwPZriaVaFGbPyRI1+9AgMBAAGjgfww
+gfkwEgYDVR0TAQH/BAgwBgEB/wIBADBPBgNVHSAESDBGMEQGCSsGAQQBsT4BMjA3
+MDUGCCsGAQUFBwIBFilodHRwOi8vY3liZXJ0cnVzdC5vbW5pcm9vdC5jb20vcmVw
+b3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAU5Z1ZMIJHWMys+ghU
+NoZ7OrUETfAwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NkcDEucHVibGljLXRy
+dXN0LmNvbS9DUkwvT21uaXJvb3QyMDI1LmNybDAdBgNVHQ4EFgQUBJhg34Ablkld
+ZVYtpSwJJArs3LkwDQYJKoZIhvcNAQEFBQADggEBAF/fi88peXgr83z0gl954OGz
+KL0IdUHOjIjXDlW5ArUFeT67UjGzSx6x/tOiIUPSkdMW+mt55I5NGexMhmg0Urdv
+wr2ceL7wbz89np9JdMR8lxlFV6xv+lo+P9PW4yvcivjICg1rjD+UeDeYiGGR31kU
+DwnFY1T79Pavl+z8Y2RDprzM5OMf33Owbve1yCmbriVSuLRy4d6TSPEon35mPz+L
+VQ/4FgdxBddlnNcbPDTmRBY6vdhgk4ODDIiWZTNA32qs//6UUWG7iT/3rMTks0fi
+/aJqMoPifm/wEo6jZnZAl/sR4fdzH9qLHDFCi58RxUmlYO1IKwWEFasviixRcsA=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/diginotar_cyber_ca.pem b/chromium/net/data/ssl/certificates/diginotar_cyber_ca.pem
new file mode 100644
index 00000000000..0abbd7ce319
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_cyber_ca.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFWjCCBMOgAwIBAgIEBycQDTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA2MTAwNDEwNTQxMVoXDTExMTAwNDEwNTMxMVowYDELMAkG
+A1UEBhMCTkwxEjAQBgNVBAoTCURpZ2lOb3RhcjEbMBkGA1UEAxMSRGlnaU5vdGFy
+IEN5YmVyIENBMSAwHgYJKoZIhvcNAQkBFhFpbmZvQGRpZ2lub3Rhci5ubDCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANLOFQotqF6EZ639vu9Gx8i5z3P8
+9DS5+SxD52ATPXrjss87Z2yQrcC5P4RS8DVC3HTcKDu9UrSnrHJFF8bwieu0qiXy
+XUte0dmHutZ9fPXOMp8QM8WxSrtekTHC0OlBwpFkfglBO9uLCDdqqspS3rU5HsCI
+A6U/i5kTYUO1m4Kz7iBvz6FEouova0CfjytXraFTwoUiaZ2gP1HfC0GRDaXhqKpc
+SQhdvd5wQbEPyWNr0380dAIvNFp4dRxoeoFnivPaQPBgY/SSINcDpj2jHmfEhBtB
+pcmM5r3qSLYFFgizNxJa92E89zhvLpfgb1Y4VNMota0Ubi5LZLUnZbd1JQm2Bz2V
+VgIKgmCyc0XgMyZRdJq51FAc9k1bW1JSE1qmf6cO4ehBVGeYjIfVydNsy9NUkgYJ
+NEH3gW8/nsl8dVWw58Gzd+jDxAA1lUBwEEoF3iW7n1mlZLxHYL9g43aLE1Xd4XR6
+uc8kpmp/3mQiRFhogmoQ+T3lPhu5vfwi9GAEibtVbShV+t6OjRshFNc3izR7Tfay
+shDPM7F9HGKZSMsrbHaWVb8ZDR0fu2WqG46ZtcYokOWCLXhQIJr9eS8kf/CJKWn0
+fc1zvrPtTsHR7VJej/e4142HrbLZG1ES/1az4a80fVykeIgQnp0DxqWqoiRR90kU
+xbHuWUOV36toKDA/AgMBAAGjggGGMIIBgjASBgNVHRMBAf8ECDAGAQH/AgEBMFMG
+A1UdIARMMEowSAYJKwYBBAGxPgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly93d3cu
+cHVibGljLXRydXN0LmNvbS9DUFMvT21uaVJvb3QuaHRtbDAOBgNVHQ8BAf8EBAMC
+AQYwgaAGA1UdIwSBmDCBlYAUpgwdn2H/Bxe1vzhG20Mw1Y6wUgaheaR3MHUxCzAJ
+BgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdU
+RSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVy
+VHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93
+d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYD
+VR0OBBYEFKv5aN/PSjfXe0WMX3LeQETDZbvCMA0GCSqGSIb3DQEBBQUAA4GBAI9o
+a6VbB7pEZg4cqFwwezPkCiYE/O+eGjjWLqEf0JlHwnVkJP2eOyh2uSYoYZEMbSz4
+BJ98UAHV42mv7xXSRZskCSpmBU8lgcpdvqrBWSeuM46C9990sFWzjvjnN8huqlZE
+9r1TgSOWPbT6MopTZkQloiXGpjwljPDgKAYityZB
+-----END CERTIFICATE-----
+
diff --git a/chromium/net/data/ssl/certificates/diginotar_pkioverheid.pem b/chromium/net/data/ssl/certificates/diginotar_pkioverheid.pem
new file mode 100644
index 00000000000..b41fa3e5d22
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_pkioverheid.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEiDCCA3CgAwIBAgIEATFpsDANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJO
+TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSowKAYDVQQDEyFTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gT3ZlcmhlaWQgQ0EwHhcNMDcwNzA1MDg0MjA3WhcN
+MTUwNzI3MDgzOTQ2WjBfMQswCQYDVQQGEwJOTDEXMBUGA1UEChMORGlnaU5vdGFy
+IEIuVi4xNzA1BgNVBAMTLkRpZ2lOb3RhciBQS0lvdmVyaGVpZCBDQSBPdmVyaGVp
+ZCBlbiBCZWRyaWp2ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDc
+vdKnTmoKuzuiheF/AK2+tDBomAfNoHrElM9x+Yo35FPrV3bMi+Zs/u6HVcg+uwQ5
+AKeAeKxbT370vbhUuHE7BzFJOZNUfCA7eSuPu2GQfbGs5h+QLp1FAalkLU3DL7nn
+UNVOKlyrdnY3Rtd57EKZ96LspIlw3Dgrh6aqJOadkiQbvvb91C8ZF3rmMgeUVAVT
+Q+lsvK9Hy7zL/b07RBKB8WtLu+20z6slTxjSzAL8o0+1QjPLWc0J3NNQ/aB2jKx+
+ZopC9q0ckvO2+xRG603XLzDgbe5bNr5EdLcgBVeFTegAGaL2DOauocBC36esgl3H
+aLcY5olLmmv6znn58yynAgMBAAGjggFQMIIBTDBIBgNVHSAEQTA/MD0GBFUdIAAw
+NTAzBggrBgEFBQcCARYnaHR0cDovL3d3dy5kaWdpbm90YXIubmwvY3BzL3BraW92
+ZXJoZWlkMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMIGABgNVHSME
+eTB3gBQLhtYPd6NosftkCcOIblwEHFfpPaFZpFcwVTELMAkGA1UEBhMCTkwxHjAc
+BgNVBAoTFVN0YWF0IGRlciBOZWRlcmxhbmRlbjEmMCQGA1UEAxMdU3RhYXQgZGVy
+IE5lZGVybGFuZGVuIFJvb3QgQ0GCBACYmnkwPQYDVR0fBDYwNDAyoDCgLoYsaHR0
+cDovL2NybC5wa2lvdmVyaGVpZC5ubC9Eb21PdkxhdGVzdENSTC5jcmwwHQYDVR0O
+BBYEFEwIyY128ZjHPt881y91DbF2eZfMMA0GCSqGSIb3DQEBBQUAA4IBAQAMlIca
+v03jheLu19hjeQ5Q38aEW9K72fUxCho1l3TfFPoqDz7toOMI9tVOW6+mriXiRWsi
+D7dUKH6S3o0UbNEc5W50BJy37zRERd/Jgx0ZH8Apad+J1T/CsFNt5U4X5HNhIxMm
+cUP9TFnLw98iqiEr2b+VERqKpOKrp11Lbyn1UtHk0hWxi/7wA8+nfemZhzizDXMU
+5HIs4c71rQZIZPrTKbmi2Lv01QulQERDjqC/zlqlUkxk0xcxYczopIro5Ij76eUv
+BjMzm5RmZrGrUDqhCYF0U1onuabSJc/Tw6f/ltAv6uAejVLpGBwgCkegllYOQJBR
+RKwa/fHuhR/3Qlpl
+-----END CERTIFICATE-----
+
diff --git a/chromium/net/data/ssl/certificates/diginotar_pkioverheid_g2.pem b/chromium/net/data/ssl/certificates/diginotar_pkioverheid_g2.pem
new file mode 100644
index 00000000000..12570d21ae4
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_pkioverheid_g2.pem
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIGnDCCBISgAwIBAgIEATE0vzANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJO
+TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMTIwMAYDVQQDDClTdGFh
+dCBkZXIgTmVkZXJsYW5kZW4gT3JnYW5pc2F0aWUgQ0EgLSBHMjAeFw0xMDA1MTIw
+ODUxMzhaFw0yMDAzMjMwOTUwMDRaMFoxCzAJBgNVBAYTAk5MMRcwFQYDVQQKDA5E
+aWdpTm90YXIgQi5WLjEyMDAGA1UEAwwpRGlnaU5vdGFyIFBLSW92ZXJoZWlkIENB
+IE9yZ2FuaXNhdGllIC0gRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCxExkPJ+Zs1FWGS9DsiYpFkXisR71HK+T8RetPtCZzWzfTw3/2497Xo/gtaMUI
+PkuU1uSHJTZrhLUYdPMoWHMvm2rPvAQe9t7dr/xLqvXbZmIlASWC3vKXWhBu3V2p
+IrEEqSNzOvhxrR3PhETrR9Gvbch8KKvH8jd6dF9fxQIUiqNa4xtsAeNdjtlo1vQJ
+GzLckbUs9SDrjANtJkm4k8SFXdjSm69WaswFM8ygQp40VUSca6DUEtArVM23iQ3l
+9uvo+4UBM096a/GdcjOWDveyhKWlJ8Qn8VFzKXe6Z27+TNy04qGhgS85SY1DOBPO
+0KVcwoc6AGdlQiPxNlkKHaNRyLyjlCox3+M88p0aPASw77EKMBNzttfzo0wBdRSF
+eMDXijlYhVD6LubFvs+LP6+PNtQlCS3SD6xyk/K/i9RQs/kVUJuZ9RTZ+4uRozIm
+JqD43ztggYaDeVsr6xM9KTrBbd29no6H1kquNJcF7hSm9tw4fkrpJFQHPZdoN0Zr
+DceoIa8TVOQJavFNRgrJXfubT73e+7dUy7g4nKc5+2otwHuNq6WnV+xKkoozxeEg
+XHPYkJIrgNUPhhhpfDlPhIa890xb89W0yqDC8DciynlSH1PmqvOQsDvd8ij9rOvF
+BiSgydQvD1j9tZ7sD8+yWdCiBHo4aq5y+73wJWKUCacFCwIDAQABo4IBYTCCAV0w
+SAYDVR0gBEEwPzA9BgRVHSAAMDUwMwYIKwYBBQUHAgEWJ2h0dHA6Ly93d3cuZGln
+aW5vdGFyLm5sL2Nwcy9wa2lvdmVyaGVpZDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjCBhQYDVR0jBH4wfIAUORCLSZJc22ESIM1JnRqO2pxnQLmhXqRc
+MFoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4x
+KzApBgNVBAMMIlN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENBIC0gRzKCBACY
+lvQwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2NybC5wa2lvdmVyaGVpZC5ubC9E
+b21PcmdhbmlzYXRpZUxhdGVzdENSTC1HMi5jcmwwHQYDVR0OBBYEFLxdlDvZq3sD
+JXNhwtst7vyrj2WhMA0GCSqGSIb3DQEBCwUAA4ICAQCP/C1Mt9kt1R+978v0t2gX
+dZ1O1ffdnPEqJu2forYcA9VTs+wIzzTi48P0tRYvyMO+19NzqwA2+RpKftZj6V5G
+uqW2jhW3oyrYQx3vXcgfgYWzi/f/PPTZ9EYIP5y8HaDZqEzNJVJOCrEg9x/pQ9lU
+RoETmsBedGwqmDLq/He7DaWiMZgifnx859qkrey3LhoZcfhIUNpDjyyE3cFAJ+O1
+8BVOltT4XOOGKUYr1zsH6zh/yIZXl9PvKjPEF1DVZGlrK2tFXl0vF8paTs/D1zk8
+9TufRrmb5w5Jl53W1eMbD+qPAU6aE5RZCgIHSEsaYKt/T+0L2FUNaG9VnGllFULs
+wNzdbKzDFs4LHVabpMTE0i7gD+JEJytQaaTcYuiKISlCbMwAOpZ2m+9AwKRed4Qy
+bCYqOWauXeO5ubIsaB8empADOfCqs6TMSYsYNOk3yXspx4R8b0QVL+xhWQTJRcui
+1lKifH8pktZKxYtCqNT+6tjHhyMY5J16fXNAUpigrm7jBT8FD+Clxm1N7YM3iJzH
+89xCmmq21yFJNnfy7xhPxXDZnunetyuL9Lx+KN8NQMmFXK6dxTH/0FwOtah+8Okv
+uq+IruW10Vilr5xxpykBkINpN4IFuvwJwQhujHg7wzMCgD9EhQgd31VWCK0shS1d
+sQPhrqp0xaTzTro3mHuCuQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/diginotar_public_ca_2025.pem b/chromium/net/data/ssl/certificates/diginotar_public_ca_2025.pem
new file mode 100644
index 00000000000..c81461a59d4
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_public_ca_2025.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGAzCCA+ugAwIBAgIQHn16Uz1FMEGWQA9xSB9FBDANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdp
+Tm90YXIgUm9vdCBDQTEgMB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmww
+HhcNMDYwMjA2MTYwNzAyWhcNMjUwMzI4MTYwNzAyWjBmMQswCQYDVQQGEwJOTDES
+MBAGA1UEChMJRGlnaU5vdGFyMSEwHwYDVQQDExhEaWdpTm90YXIgUHVibGljIENB
+IDIwMjUxIDAeBgkqhkiG9w0BCQEWEWluZm9AZGlnaW5vdGFyLm5sMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/2eu/I5fMG8lbvPph3e8zfJpZQtg/72
+Yx29+ivtKehiF6A3n785XyoY6IT3vlCrhy1CbMOY3M0x1n4YQlv17B0XZ/DqHyBA
+SQvnDNbkM9j4NoSy/sRtGsP6PetIFFjrhE9whZuvuSUC1PY4PruEEJp8zOCx4+wU
+Zt9xvjy4Xra+bSia5rwccQ/R5FYTGKrYCthOy9C9ud5Fhd++rlVhgdA/78w+Cs2s
+xS4i0MAxG75P3/e/bATJKepbydHdDjkyz9o3RW/wdPUXhzEw4EwUjYg6XJrDzMad
+6aL9M/eaxDjgz6o48EaWRDrGptaE2uJRuErVz7oOO0p/wYKq/BU+/wIDAQABo4IB
+sjCCAa4wOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRwOi8vdmFsaWRh
+dGlvbi5kaWdpbm90YXIubmwwHwYDVR0jBBgwFoAUiGi/4I41xDs4a2L3KDuEgcgM
+100wEgYDVR0TAQH/BAgwBgEB/wIBADCBxgYDVR0gBIG+MIG7MIG4Bg5ghBABh2kB
+AQEBBQIGBDCBpTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpbm90YXIubmwv
+Y3BzMHoGCCsGAQUFBwICMG4abENvbmRpdGlvbnMsIGFzIG1lbnRpb25lZCBvbiBv
+dXIgd2Vic2l0ZSAod3d3LmRpZ2lub3Rhci5ubCksIGFyZSBhcHBsaWNhYmxlIHRv
+IGFsbCBvdXIgcHJvZHVjdHMgYW5kIHNlcnZpY2VzLjBDBgNVHR8EPDA6MDigNqA0
+hjJodHRwOi8vc2VydmljZS5kaWdpbm90YXIubmwvY3JsL3Jvb3QvbGF0ZXN0Q1JM
+LmNybDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFN8zwK+S/jf8ttgWFtDZsZHV
++m6lMA0GCSqGSIb3DQEBBQUAA4ICAQCfV1rmBd9QStEyQ40lT0tqby0/3ez0STuJ
+ESBQLQD56XYdb4VFSuqA6xTtiuSVHLoiv2xyISN9FvX3A5VtifkJ00JEaLQJiSsE
+wGDkYGl1DT7SsqtAVKdMAuCM+e0j0/RV3hZ6kcrM7/wFccHwM+/TiurR9lgZDzB4
+a7++A4XrYyKx9vc9ZwBEnD1nrAe7++gg9cuZgP7e+QL0FBHMjpw+gnCDjr2dzBZC
+4r+b8SOqlbPRPexBuNghlc7PfcPIyFis2LJXDRMWiAd3TcfdALwRsuKMR/T+cwyr
+asy69OEGHplLT57otQ524BDctDXNzlH9bHEh52QzqkWvIDqs42910IUy1nYNPIUG
+yYJV/T7H8Jb6vfMZWe47iUFvtNZCi8+b542gRUwdi+ca+hGviBC9Qr4Wv1pl7CBQ
+Hy1axTkHiQawUo/hgmoetCpftugl9yJTfvsBorUV1ZMxn9B1JLSGtWnbUsFRla7G
+fNa0IsUkzmmha8XCzvNu0d1PDGtcQyUqmDOE1Hx4cIBeuF8ipuIXkrVCr9zAZ4ZC
+hgz6aA1gDTW8whSRJqYEYEQ0pcMEFLyXE+Nz3O8NinO2AuxqKhjMk13203xA7lPY
+MnBQ0v7S3qqbp/pvPMiUhOz/VaYted6QmOY5EATBnFiLCuw87JXoAyp382eJ3WX1
+hOiR4IX9Tg==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/diginotar_root_ca.pem b/chromium/net/data/ssl/certificates/diginotar_root_ca.pem
new file mode 100644
index 00000000000..b972b4bc50c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_root_ca.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFijCCA3KgAwIBAgIQDHbanJEMTiye/hXQWJM8TDANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdp
+Tm90YXIgUm9vdCBDQTEgMB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmww
+HhcNMDcwNTE2MTcxOTM2WhcNMjUwMzMxMTgxOTIxWjBfMQswCQYDVQQGEwJOTDES
+MBAGA1UEChMJRGlnaU5vdGFyMRowGAYDVQQDExFEaWdpTm90YXIgUm9vdCBDQTEg
+MB4GCSqGSIb3DQEJARYRaW5mb0BkaWdpbm90YXIubmwwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCssFjBAL3YIQgLK5r+blYwBZ8bd5AQQVzDDYcRd46B
+8cp86Yxq7Th0Nbva3/m7wAk3tJZzgX0zGpg595NvlX89ubF1h7pRSOiLcD6VBMXY
+tsMW2YiwsYcdcNqGtA8Ui3rPENF0NqISe3eGSnnme98CEWilToauNFibJBN4ViIl
+HgGLS1Fx+4LMWZZpiFpoU8W5DQI3y0u8ZkqQfioLBQftFl9VkHXYRskbg+IIvvEj
+zJkd1ioPgyAVWCeCLvriIsJJsbkBgWqdbZ1Ad2h2TiEqbYRAhU52mXyC8/O3AlnU
+JgEbjt+tUwbRrhjd4rI6y9eIOI6sWym5GdOY+RgDz0iChmYLG2kPyes4iHomGgVM
+ktck1JbyrFIto0fVUvY//s6EBnCmqj6i8rZWNBhXouSBbefK8GrTx5FrAoNBfBXv
+a5pkXuPQPOWx63tdhvvL5ndJzaNl3Pe5nLjkC1+Tz8wwGjIczhxjlaX56uF0i57p
+K6kwe6AYHw4YC+VbqdPRbB4HZ4+RS6mKvNJmqpMBiLKR+jFc1abBUggJzQpjotMi
+puih2TkGl/VujQKQjBR7P4DNG5y6xFhyI6+2Vp/GekIzKQc/gsnmHwUNzUwoNovT
+yD4cxojvXu6JZOkd69qJfjKmadHdzIif0dDJZiHcBmfFlHqabWJMfczgZICynkeO
+owIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUiGi/4I41xDs4a2L3KDuEgcgM100wDQYJKoZIhvcNAQEFBQADggIBADsC
+jcs8MOhuoK3yc7NfniUTBAXT9uOLuwt5zlPe5JbF0a9zvNXD0EBVfEB/zRtfCdXy
+fJ9oHbtdzno5wozWmHvFg1Wo1X1AyuAe94leY12hE8JdiraKfADzI8PthV9xdvBo
+Y6pFITlIYXg23PFDk9Qlx/KAZeFTAnVR/Ho67zerhChXDNjU1JlWbOOi/lmEtDHo
+M/hklJRRl6s5xUvt2t2AC298KQ3EjopyDedTFLJgQT2EkTFoPSdE2+Xe9PpjRchM
+Ppj1P0G6Tss3DbpmmPHdy59c91Q2gmssvBNhl0L4eLvMyKKfyvBovWsdst+Nbwed
+2o5nx0ceyrm/KkKRt2NTZvFCo+H0Wk1Ya7XkpDOtXHAd3ODy63MUkZoDweoAZbwH
+/M8SESIsrqC9OuCiKthZ6SnTGDWkrBFfGbW1G/8iSlzGeuQX7yCpp/Q/rYqnmgQl
+nQ7KN+ZQ/YxCKQSa7LnPS3K94gg2ryMvYuXKAdNw23yCIywWMQzGNgeQerEfZ1jE
+O1hZibCMjFCz2IbLaKPECudpSyDOwR5WS5WpI2jYMNjD67BVUc3l/Su49bsRn1NU
+9jQZjHkJNsphFyUXC4KYcwx3dMPVDceoEkzHp1RxRy4sGn3J4ys7SN4nhKdjNrN9
+j6BkOSQNPXuHr2ZcdBtLc7LljPCGmbjlxd+Ewbfr
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/diginotar_services_1024_ca.pem b/chromium/net/data/ssl/certificates/diginotar_services_1024_ca.pem
new file mode 100644
index 00000000000..d32de7afdaa
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/diginotar_services_1024_ca.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIDzTCCAzagAwIBAgIERpwssDANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNzA3
+MjYxNTU5MDBaFw0xMzA4MjYxNjI5MDBaMGgxCzAJBgNVBAYTAk5MMRIwEAYDVQQK
+EwlEaWdpTm90YXIxIzAhBgNVBAMTGkRpZ2lOb3RhciBTZXJ2aWNlcyAxMDI0IENB
+MSAwHgYJKoZIhvcNAQkBFhFpbmZvQGRpZ2lub3Rhci5ubDCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEA2ptNXTz50eKLxsYIIMXZHkjsZlhneWIrQWP0iY1o2q+4
+lDaLGSSkoJPSmQ+yrS01Tc0vauH5mxkrvAQafi09UmTN8T5nD4ku6PJPrqYIoYX+
+oakJ5sarPkP8r3oDkdqmOaZh7phPGKjTs69mgumfvN1y+QYEvRLZGCTnq5NTi1kC
+AwEAAaOCASYwggEiMBIGA1UdEwEB/wQIMAYBAf8CAQAwJwYDVR0lBCAwHgYIKwYB
+BQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDBDARBgNVHSAECjAIMAYGBFUdIAAwMwYI
+KwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0Lm5l
+dDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1c3QubmV0L3NlcnZl
+cjEuY3JsMB0GA1UdDgQWBBT+3JRJDG/vXH/G8RKZTxZJrfuCZTALBgNVHQ8EBAMC
+AQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0BowGQYJKoZIhvZ9B0EA
+BAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEFBQADgYEAY3RqN6k/lpxmyFisCcnv
+9WWUf6MCxDgxvV0jh+zUVrLJsm7kBQb87PX6iHBZ1O7m3bV6oKNgLwIMq94SXa/w
+NUuqikeRGvWFLELHHe+VQ7NeuJWTpdrFKKqtci0xrZlrbP+MISevrZqRK8fdWMNu
+B8WfedLHjFW/TMcnXlEWKz4=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/dod_ca_13_cert.der b/chromium/net/data/ssl/certificates/dod_ca_13_cert.der
new file mode 100644
index 00000000000..bcf3ec2b24f
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/dod_ca_13_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/dod_ca_17_cert.der b/chromium/net/data/ssl/certificates/dod_ca_17_cert.der
new file mode 100644
index 00000000000..50e509eebda
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/dod_ca_17_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/dod_root_ca_2_cert.der b/chromium/net/data/ssl/certificates/dod_root_ca_2_cert.der
new file mode 100644
index 00000000000..30dcdb99970
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/dod_root_ca_2_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/duplicate_cn_1.p12 b/chromium/net/data/ssl/certificates/duplicate_cn_1.p12
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/duplicate_cn_1.p12
diff --git a/chromium/net/data/ssl/certificates/duplicate_cn_1.pem b/chromium/net/data/ssl/certificates/duplicate_cn_1.pem
new file mode 100644
index 00000000000..c581cb54ad3
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/duplicate_cn_1.pem
@@ -0,0 +1,78 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=B Root CA
+ Validity
+ Not Before: May 20 23:20:42 2013 GMT
+ Not After : May 18 23:20:42 2023 GMT
+ Subject: O=Foo, CN=Duplicate
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c8:3c:a1:5e:3c:f8:61:26:9f:62:ed:c1:a2:c7:
+ 7b:af:77:9a:c0:4f:c0:f7:39:96:08:b0:b6:19:b3:
+ 4b:58:7b:98:94:e1:24:c5:30:4b:da:bf:6b:67:e7:
+ bd:1b:e6:c8:36:18:79:53:f8:85:bc:c7:fa:fb:bd:
+ 10:7f:4d:90:e0:8d:c2:bc:63:75:36:8b:63:a5:87:
+ 34:36:06:4a:23:e4:8a:93:48:97:3b:e4:52:3e:9d:
+ 8e:d1:1b:bc:4e:8d:09:61:5c:42:70:d9:48:8d:45:
+ e7:f8:cb:bc:98:4f:50:8f:6b:91:99:b5:4a:df:eb:
+ 73:8b:b8:65:88:99:9e:70:3f:bf:3a:2d:f4:ff:97:
+ a8:4e:28:ee:9a:6d:0b:45:82:bc:c7:93:38:a5:d8:
+ f3:ab:ff:d2:85:9c:93:d0:95:5c:85:11:89:be:d1:
+ 90:70:db:c7:34:32:c2:e4:a4:cb:e4:96:87:a5:42:
+ d1:18:15:83:db:18:28:a6:08:95:3f:07:dc:73:b7:
+ 14:5f:75:68:f9:9e:c0:60:1a:97:01:72:39:48:98:
+ a0:15:54:d7:d9:87:9d:86:c8:ef:3f:d1:62:5e:d2:
+ 9e:f5:60:bb:49:bc:fc:68:16:df:0d:70:03:9c:af:
+ dc:0f:3c:7d:3f:0d:7c:54:7c:0f:16:5e:ad:03:d5:
+ 3c:d9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 14:0D:5C:0D:44:95:BA:5E:0F:25:3F:F6:F0:7C:A0:C3:B9:3A:BD:AF
+ X509v3 Authority Key Identifier:
+ keyid:42:00:DD:75:A7:80:94:A0:B0:8E:9F:0B:99:08:CF:A7:BE:B7:EA:A9
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ 6c:f9:75:5b:42:5a:58:59:38:39:eb:54:e8:dd:74:30:d4:62:
+ 79:87:90:c2:e6:2f:58:85:b6:2d:ff:5d:68:7a:1f:6d:21:71:
+ f5:3c:47:9c:0c:cb:3c:63:1c:87:dc:a8:da:09:98:55:26:84:
+ 55:0e:63:31:4c:ce:9d:22:e4:cd:b0:3a:9a:df:45:ab:7a:6b:
+ 17:36:b6:30:d3:d9:04:37:67:73:23:ae:78:d2:6f:2f:49:0d:
+ 02:cd:c9:a0:5d:b0:70:53:03:01:32:81:20:d5:28:07:f6:54:
+ 75:b7:89:9d:14:89:09:7c:db:a2:4f:23:a3:4e:0c:38:68:58:
+ b2:91:f1:34:55:69:ab:40:09:b3:72:b7:4b:58:ff:bd:42:62:
+ 0e:9e:76:ca:da:70:8b:ce:3e:e1:88:c5:3f:1f:1a:90:d1:e1:
+ ce:93:12:6c:04:b4:d1:fb:b7:80:cb:4c:e5:0b:93:35:31:27:
+ 75:cc:c8:dd:37:43:5f:33:6a:4c:7f:5a:45:1d:c2:6e:f4:c0:
+ c0:c5:30:c4:1a:76:1f:d6:ea:25:7c:ce:da:88:74:08:78:01:
+ cd:c6:9d:c7:f2:bd:43:65:a6:35:55:2d:14:1f:03:c7:b9:e7:
+ 5e:aa:ce:73:bc:67:9e:25:10:3c:1a:b9:f9:9e:06:3c:c3:82:
+ 98:0c:bc:e0
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJQiBS
+b290IENBMB4XDTEzMDUyMDIzMjA0MloXDTIzMDUxODIzMjA0MlowIjEMMAoGA1UE
+CgwDRm9vMRIwEAYDVQQDDAlEdXBsaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIPKFePPhhJp9i7cGix3uvd5rAT8D3OZYIsLYZs0tYe5iU4STF
+MEvav2tn570b5sg2GHlT+IW8x/r7vRB/TZDgjcK8Y3U2i2OlhzQ2Bkoj5IqTSJc7
+5FI+nY7RG7xOjQlhXEJw2UiNRef4y7yYT1CPa5GZtUrf63OLuGWImZ5wP786LfT/
+l6hOKO6abQtFgrzHkzil2POr/9KFnJPQlVyFEYm+0ZBw28c0MsLkpMvkloelQtEY
+FYPbGCimCJU/B9xztxRfdWj5nsBgGpcBcjlImKAVVNfZh52GyO8/0WJe0p71YLtJ
+vPxoFt8NcAOcr9wPPH0/DXxUfA8WXq0D1TzZAgMBAAGjbzBtMAwGA1UdEwEB/wQC
+MAAwHQYDVR0OBBYEFBQNXA1ElbpeDyU/9vB8oMO5Or2vMB8GA1UdIwQYMBaAFEIA
+3XWngJSgsI6fC5kIz6e+t+qpMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
+AjANBgkqhkiG9w0BAQUFAAOCAQEAbPl1W0JaWFk4OetU6N10MNRieYeQwuYvWIW2
+Lf9daHofbSFx9TxHnAzLPGMch9yo2gmYVSaEVQ5jMUzOnSLkzbA6mt9Fq3prFza2
+MNPZBDdncyOueNJvL0kNAs3JoF2wcFMDATKBINUoB/ZUdbeJnRSJCXzbok8jo04M
+OGhYspHxNFVpq0AJs3K3S1j/vUJiDp52ytpwi84+4YjFPx8akNHhzpMSbAS00fu3
+gMtM5QuTNTEndczI3TdDXzNqTH9aRR3CbvTAwMUwxBp2H9bqJXzO2oh0CHgBzcad
+x/K9Q2WmNVUtFB8Dx7nnXqrOc7xnniUQPBq5+Z4GPMOCmAy84A==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/duplicate_cn_2.p12 b/chromium/net/data/ssl/certificates/duplicate_cn_2.p12
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/duplicate_cn_2.p12
diff --git a/chromium/net/data/ssl/certificates/duplicate_cn_2.pem b/chromium/net/data/ssl/certificates/duplicate_cn_2.pem
new file mode 100644
index 00000000000..eb78e991dd6
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/duplicate_cn_2.pem
@@ -0,0 +1,78 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=B Root CA
+ Validity
+ Not Before: May 20 23:20:42 2013 GMT
+ Not After : May 18 23:20:42 2023 GMT
+ Subject: O=Bar, CN=Duplicate
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c8:3c:a1:5e:3c:f8:61:26:9f:62:ed:c1:a2:c7:
+ 7b:af:77:9a:c0:4f:c0:f7:39:96:08:b0:b6:19:b3:
+ 4b:58:7b:98:94:e1:24:c5:30:4b:da:bf:6b:67:e7:
+ bd:1b:e6:c8:36:18:79:53:f8:85:bc:c7:fa:fb:bd:
+ 10:7f:4d:90:e0:8d:c2:bc:63:75:36:8b:63:a5:87:
+ 34:36:06:4a:23:e4:8a:93:48:97:3b:e4:52:3e:9d:
+ 8e:d1:1b:bc:4e:8d:09:61:5c:42:70:d9:48:8d:45:
+ e7:f8:cb:bc:98:4f:50:8f:6b:91:99:b5:4a:df:eb:
+ 73:8b:b8:65:88:99:9e:70:3f:bf:3a:2d:f4:ff:97:
+ a8:4e:28:ee:9a:6d:0b:45:82:bc:c7:93:38:a5:d8:
+ f3:ab:ff:d2:85:9c:93:d0:95:5c:85:11:89:be:d1:
+ 90:70:db:c7:34:32:c2:e4:a4:cb:e4:96:87:a5:42:
+ d1:18:15:83:db:18:28:a6:08:95:3f:07:dc:73:b7:
+ 14:5f:75:68:f9:9e:c0:60:1a:97:01:72:39:48:98:
+ a0:15:54:d7:d9:87:9d:86:c8:ef:3f:d1:62:5e:d2:
+ 9e:f5:60:bb:49:bc:fc:68:16:df:0d:70:03:9c:af:
+ dc:0f:3c:7d:3f:0d:7c:54:7c:0f:16:5e:ad:03:d5:
+ 3c:d9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 14:0D:5C:0D:44:95:BA:5E:0F:25:3F:F6:F0:7C:A0:C3:B9:3A:BD:AF
+ X509v3 Authority Key Identifier:
+ keyid:42:00:DD:75:A7:80:94:A0:B0:8E:9F:0B:99:08:CF:A7:BE:B7:EA:A9
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ 13:cf:8c:72:24:c5:2f:20:d2:5d:a4:8c:ad:68:e3:27:6b:58:
+ 7f:9b:b5:c4:25:cb:b4:62:ff:4b:db:5a:57:13:b2:49:2c:3f:
+ 51:82:84:33:75:3a:4b:d7:14:d4:91:37:7c:f6:40:a3:e2:da:
+ 82:d3:cf:1a:2b:43:3b:76:18:37:63:42:5f:3a:02:a1:b9:23:
+ ff:ea:4c:ec:22:c9:b8:2a:9e:19:d0:01:cb:63:bf:a4:7e:97:
+ d9:8e:b9:a9:39:3f:09:44:ee:30:f5:fc:0c:0f:91:eb:ce:b7:
+ 72:eb:a0:c0:34:6e:69:7d:43:9c:2e:ba:d5:39:60:db:ad:d1:
+ 5c:f3:72:68:d4:73:c6:04:a6:a4:3a:15:21:87:85:31:20:0f:
+ ed:65:65:ec:66:57:1a:9b:bd:72:10:e3:55:5e:94:f4:51:cd:
+ 93:a2:2f:02:85:5d:65:af:5d:95:50:54:3b:dc:9a:33:04:e0:
+ cd:6c:a3:6d:a0:1a:84:c9:da:8a:db:cc:9f:90:b0:df:ee:ff:
+ 0c:bd:58:f0:98:6e:a3:21:d8:ab:e2:99:a1:44:92:ad:d3:f7:
+ 91:b2:72:9a:a6:75:4d:19:4a:6e:1f:64:27:a6:ce:39:79:ef:
+ 5d:db:f2:7b:0f:6e:52:96:22:27:b3:67:81:f5:13:8d:79:0a:
+ d5:9f:f1:af
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgICAO0wDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJQiBS
+b290IENBMB4XDTEzMDUyMDIzMjA0MloXDTIzMDUxODIzMjA0MlowIjEMMAoGA1UE
+CgwDQmFyMRIwEAYDVQQDDAlEdXBsaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIPKFePPhhJp9i7cGix3uvd5rAT8D3OZYIsLYZs0tYe5iU4STF
+MEvav2tn570b5sg2GHlT+IW8x/r7vRB/TZDgjcK8Y3U2i2OlhzQ2Bkoj5IqTSJc7
+5FI+nY7RG7xOjQlhXEJw2UiNRef4y7yYT1CPa5GZtUrf63OLuGWImZ5wP786LfT/
+l6hOKO6abQtFgrzHkzil2POr/9KFnJPQlVyFEYm+0ZBw28c0MsLkpMvkloelQtEY
+FYPbGCimCJU/B9xztxRfdWj5nsBgGpcBcjlImKAVVNfZh52GyO8/0WJe0p71YLtJ
+vPxoFt8NcAOcr9wPPH0/DXxUfA8WXq0D1TzZAgMBAAGjbzBtMAwGA1UdEwEB/wQC
+MAAwHQYDVR0OBBYEFBQNXA1ElbpeDyU/9vB8oMO5Or2vMB8GA1UdIwQYMBaAFEIA
+3XWngJSgsI6fC5kIz6e+t+qpMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
+AjANBgkqhkiG9w0BAQUFAAOCAQEAE8+MciTFLyDSXaSMrWjjJ2tYf5u1xCXLtGL/
+S9taVxOySSw/UYKEM3U6S9cU1JE3fPZAo+LagtPPGitDO3YYN2NCXzoCobkj/+pM
+7CLJuCqeGdABy2O/pH6X2Y65qTk/CUTuMPX8DA+R6863cuugwDRuaX1DnC661Tlg
+263RXPNyaNRzxgSmpDoVIYeFMSAP7WVl7GZXGpu9chDjVV6U9FHNk6IvAoVdZa9d
+lVBUO9yaMwTgzWyjbaAahMnaitvMn5Cw3+7/DL1Y8JhuoyHYq+KZoUSSrdP3kbJy
+mqZ1TRlKbh9kJ6bOOXnvXdvyew9uUpYiJ7NngfUTjXkK1Z/xrw==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/eku-test-root.pem b/chromium/net/data/ssl/certificates/eku-test-root.pem
new file mode 100644
index 00000000000..faeb997b753
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/eku-test-root.pem
@@ -0,0 +1,66 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 17699587613995562877 (0xf5a19128931bf77d)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Jan 23 23:51:05 2013 GMT
+ Not After : Jan 21 23:51:05 2023 GMT
+ Subject: CN=2048 RSA Test Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b3:86:f9:ec:39:f5:63:1b:16:17:c9:fa:04:1a:
+ 32:1b:d8:ff:01:7c:55:2f:90:66:da:be:8a:b6:1e:
+ 68:ad:08:60:dc:63:40:c3:9c:fc:17:e9:5b:1f:bf:
+ a0:c7:de:42:7d:d8:cf:7b:56:9e:c7:17:65:65:45:
+ c5:e0:b0:80:c9:8a:b2:a5:a3:6e:af:14:96:56:9a:
+ 12:b1:2b:d6:ef:e6:03:79:96:70:9b:6b:01:9e:42:
+ 66:1f:8d:4b:bb:c7:60:09:df:2f:cc:b6:92:32:58:
+ 91:2d:80:c5:0c:d5:e0:a7:48:1e:5a:3a:e0:68:34:
+ 67:c0:67:59:2f:0d:fd:53:4c:5e:15:2a:2c:c2:ba:
+ 8e:1e:61:86:88:cc:84:74:9f:05:ed:7b:10:c2:b5:
+ dc:57:8f:9f:cc:ad:a4:bd:a0:97:90:2a:a8:8e:fc:
+ e3:13:2d:ef:a6:6b:41:54:4a:60:d3:d6:18:8f:4e:
+ 4f:fb:db:42:c3:5f:14:56:cd:e8:29:d1:f5:9b:55:
+ 66:c1:d5:3c:c0:6b:04:1a:a8:51:89:36:c9:43:eb:
+ f6:5d:4f:74:3b:4d:ff:9c:9b:00:49:31:8c:bb:c3:
+ 29:49:9e:08:6a:2b:41:a0:75:95:44:2d:28:6e:eb:
+ f9:4f:6c:2f:db:23:37:ef:f2:2a:3b:5e:e4:e4:0a:
+ e8:8d
+ Exponent: 65537 (0x10001)
+ Signature Algorithm: sha1WithRSAEncryption
+ 49:b1:97:48:72:dc:fb:97:56:f8:e7:80:81:9e:be:4b:9a:6a:
+ d8:5e:b0:59:a1:45:65:f8:1d:ad:c3:de:41:c9:33:17:66:47:
+ 36:54:7f:3c:e8:84:a0:81:40:79:7a:4f:09:1c:88:11:e4:b7:
+ b9:b1:ef:43:7b:70:67:21:81:9c:87:50:b0:1f:f6:fa:28:a4:
+ 62:e5:7d:e4:33:1c:50:11:7d:61:60:e4:bb:95:ba:0d:95:e8:
+ a3:44:07:58:47:c8:e2:57:dc:a6:80:12:62:a4:a4:73:a1:9b:
+ fc:6b:da:4d:44:44:8c:fc:c0:03:b2:6a:41:90:cd:db:53:17:
+ 05:74:7d:dd:3a:88:c6:ec:5d:d4:80:37:22:7f:b0:d2:eb:db:
+ 6e:1d:d5:fd:d7:1d:ee:29:c3:11:85:94:07:0d:f6:8b:7f:c6:
+ 35:39:08:74:87:3c:35:28:18:7c:dc:71:6c:e7:6c:a6:34:77:
+ 27:e7:0a:a8:dc:cf:b7:73:3c:45:b0:26:c3:09:d6:f9:ce:70:
+ 5a:7c:eb:5e:a5:60:97:55:f2:e2:87:8c:96:00:03:a9:20:2a:
+ 4d:b3:10:96:26:1c:c8:dc:58:30:62:95:05:a5:45:d8:07:d2:
+ 0d:47:a9:e8:2e:d0:75:f1:36:c1:d1:ca:9a:2e:5d:75:f7:69:
+ 3e:00:ae:9d
+-----BEGIN CERTIFICATE-----
+MIICvDCCAaQCCQD1oZEokxv3fTANBgkqhkiG9w0BAQUFADAgMR4wHAYDVQQDDBUy
+MDQ4IFJTQSBUZXN0IFJvb3QgQ0EwHhcNMTMwMTIzMjM1MTA1WhcNMjMwMTIxMjM1
+MTA1WjAgMR4wHAYDVQQDDBUyMDQ4IFJTQSBUZXN0IFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzhvnsOfVjGxYXyfoEGjIb2P8BfFUvkGba
+voq2HmitCGDcY0DDnPwX6Vsfv6DH3kJ92M97Vp7HF2VlRcXgsIDJirKlo26vFJZW
+mhKxK9bv5gN5lnCbawGeQmYfjUu7x2AJ3y/MtpIyWJEtgMUM1eCnSB5aOuBoNGfA
+Z1kvDf1TTF4VKizCuo4eYYaIzIR0nwXtexDCtdxXj5/MraS9oJeQKqiO/OMTLe+m
+a0FUSmDT1hiPTk/720LDXxRWzegp0fWbVWbB1TzAawQaqFGJNslD6/ZdT3Q7Tf+c
+mwBJMYy7wylJnghqK0GgdZVELShu6/lPbC/bIzfv8io7XuTkCuiNAgMBAAEwDQYJ
+KoZIhvcNAQEFBQADggEBAEmxl0hy3PuXVvjngIGevkuaathesFmhRWX4Ha3D3kHJ
+MxdmRzZUfzzohKCBQHl6TwkciBHkt7mx70N7cGchgZyHULAf9voopGLlfeQzHFAR
+fWFg5LuVug2V6KNEB1hHyOJX3KaAEmKkpHOhm/xr2k1ERIz8wAOyakGQzdtTFwV0
+fd06iMbsXdSANyJ/sNLr224d1f3XHe4pwxGFlAcN9ot/xjU5CHSHPDUoGHzccWzn
+bKY0dyfnCqjcz7dzPEWwJsMJ1vnOcFp8616lYJdV8uKHjJYAA6kgKk2zEJYmHMjc
+WDBilQWlRdgH0g1Hqegu0HXxNsHRypouXXX3aT4Arp0=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/empty_subject_cert.der b/chromium/net/data/ssl/certificates/empty_subject_cert.der
new file mode 100644
index 00000000000..0a2283aa425
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/empty_subject_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/expired_cert.pem b/chromium/net/data/ssl/certificates/expired_cert.pem
new file mode 100644
index 00000000000..1fa3c10bf0e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/expired_cert.pem
@@ -0,0 +1,110 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcLvSySLaCEIdT
+Q+Ahzoi//Ai4d2IawA84yV42iHR/zlTeTKIifpVp1KH8VJ4smC6/KKtkfnLWljxp
+9dIjHMLrTf4nD6gtgMEJdmmz++py9X9cuS9TJ79k1V6BlnzU4R39pbl9KF5BOM2Q
+AvGoiODRr+B5BpERqImtfzQ3iQcgGoAvbclJNtlBl6r40F/VW4+mpPMjo1OJSYRd
+2z2ILq8Hi1eiW9AKd4UJ3IsdU9lwm9SfNQyhIi68KfzxaWohSD/wFqz83Y/1nxlB
+2KkJ4XStd2uMQ0ymxM4ocDl7/MiaM/VrJG2PM76Bl4Wv7GCngcw6MFvy18mtVwRG
+SxJJNx7TAgMBAAECggEAMfw+9NJpDHHwJsj78vDOPrday3Xewa/kRMICL2Me/3y5
+65V+IVKXXOU2uPAE0J7jzeXPswq5y1lj8bIquGnpHj4geoGlbl8Sc7F8Ia4I9Xwi
+I8yun7QgUSXXRJgDUtigVFrxFya47xmUISNp9RbN4wyIazFAcb/PMaH1Gr5y7ahq
+je2YV5wvG2YG18LJrSrAYmxEVUqECFv0B8fQS23pbjJlzJdtBVheRz/uWuAQ8jma
+I1tI2sT/NKikMhUwqdTWZSkuOEMKmB18K/rxFpKLQYx6vo8irwvk2pZHAHTV/WYO
+jlqkXBMviHAUHBoOVG8DeiVlBNnOY8+Ef94ngaqw4QKBgQD5vzh7UcvYwdQbsOUO
+NiQKBgt1FpE4nGglR3i0inxIYqkkmEDMthvzYEZPCVQsNHJPy92O5mjp6tNb1NcN
+T9faJ7USa3Cse+z7/dNgnu7BFfW3sahLzvaS4rMY5N0a3LA9aneUwRpj1JALXOnq
+CgN0zPea92AJyZkvq3d6gMnt2wKBgQDhsj6gOk7ojf3RL5htU0s/nFI1pzo1TiHj
+uHlQvQfnLPTZ9gWC66YUghOtfYCgSmZa7Zvmp4YP9wYE1CR34j3FltVhatOQG0yF
+gzf0htQ/r7z1A8QJcXNh4DNGZdjHnXDK0dFzRhJFlknrnAgZhN1tQlPrlObzp1kQ
+L0YhgfqwaQKBgQCPqZrQ/W14Y48i2q/4a9ZxWabPlMq2uOoDv8hEeoEhlndb0KU9
+3OfZKSFop6Iex5CWLFxnrkzdejJY2edXBf3RxROY4+rtnV/mWM12ABaxRXOSVoTM
+DuKx/ewuPsCdiiNrQMpG7InEsWqmzqzT7yyrzJgGMZSyLKCM1mKlOl5uDwKBgQCK
+kzWOjZ6LfTs9qFqPrZfDO8jiNoYv2oRGXKroHthuYZUJCtyBcQytBNzuJePHB0dn
+RwL1ESDoRoTEjDjD/v5shlsHkF7L2WcXhuThV7OUUuPTEHfaUHVGwDDbZYc/sPRj
+I4bVn01yun8ykcALYx/NiO8b83YGXlb57/zAcB0rGQKBgQC7P+qSkSwQsmjnQw1z
+1Or3v4eojlUHF3FXsGb13TAPKBCtnENlyIk51AnfC0ieBYsVOycVMW5RuNcTEF3C
+Sx/hBpkPgz4UgfFtBwgT+N84Cp0vAZ7YnP4tSu2kvm0Ehz92pUZZmqCNawp8vjjS
+zJB+cmCxvRYtVgKCr7Q8NVUsuA==
+-----END PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Root CA
+ Validity
+ Not Before: Jan 1 00:00:00 2006 GMT
+ Not After : Jan 1 00:00:00 2007 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:dc:2e:f4:b2:48:b6:82:10:87:53:43:e0:21:ce:
+ 88:bf:fc:08:b8:77:62:1a:c0:0f:38:c9:5e:36:88:
+ 74:7f:ce:54:de:4c:a2:22:7e:95:69:d4:a1:fc:54:
+ 9e:2c:98:2e:bf:28:ab:64:7e:72:d6:96:3c:69:f5:
+ d2:23:1c:c2:eb:4d:fe:27:0f:a8:2d:80:c1:09:76:
+ 69:b3:fb:ea:72:f5:7f:5c:b9:2f:53:27:bf:64:d5:
+ 5e:81:96:7c:d4:e1:1d:fd:a5:b9:7d:28:5e:41:38:
+ cd:90:02:f1:a8:88:e0:d1:af:e0:79:06:91:11:a8:
+ 89:ad:7f:34:37:89:07:20:1a:80:2f:6d:c9:49:36:
+ d9:41:97:aa:f8:d0:5f:d5:5b:8f:a6:a4:f3:23:a3:
+ 53:89:49:84:5d:db:3d:88:2e:af:07:8b:57:a2:5b:
+ d0:0a:77:85:09:dc:8b:1d:53:d9:70:9b:d4:9f:35:
+ 0c:a1:22:2e:bc:29:fc:f1:69:6a:21:48:3f:f0:16:
+ ac:fc:dd:8f:f5:9f:19:41:d8:a9:09:e1:74:ad:77:
+ 6b:8c:43:4c:a6:c4:ce:28:70:39:7b:fc:c8:9a:33:
+ f5:6b:24:6d:8f:33:be:81:97:85:af:ec:60:a7:81:
+ cc:3a:30:5b:f2:d7:c9:ad:57:04:46:4b:12:49:37:
+ 1e:d3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 9C:80:C5:A0:E6:0F:7F:D7:E2:6F:BE:D8:F1:13:F8:84:BD:D8:5B:B6
+ X509v3 Authority Key Identifier:
+ keyid:2B:88:93:E1:D2:54:50:F4:B8:A4:20:BD:B1:79:E6:0B:AA:EB:EC:1A
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 5d:0f:99:23:45:8b:a2:24:f7:b7:bb:f7:0a:e9:f9:52:5a:e8:
+ 82:72:ee:b5:86:0d:74:a0:cc:19:32:b0:f9:61:bf:36:26:a4:
+ 94:8c:59:29:68:1e:f4:75:3b:fe:df:8c:5a:d8:29:c0:9f:45:
+ c2:4b:47:1b:15:07:cc:4b:08:26:bb:41:44:49:0a:75:48:06:
+ 4a:e1:e1:51:f5:b6:be:5d:5a:d9:b4:f9:41:64:4d:a9:3e:97:
+ 6f:c0:1f:af:ca:dc:24:11:ec:e0:d0:be:a7:13:11:9f:76:72:
+ 16:95:53:e5:22:6f:44:89:e1:7a:c3:d7:39:7d:fc:5d:40:94:
+ 9a:aa:94:e5:f5:2b:4f:63:c1:34:fe:b7:f6:85:53:32:77:97:
+ 3c:74:11:67:cd:27:b5:e6:cb:8a:18:10:c0:f7:0a:e2:9f:5d:
+ 88:ff:82:54:43:41:dc:51:33:f1:c8:6d:09:c6:25:f0:53:3f:
+ 7e:04:ca:b4:07:f9:c4:9a:0d:ae:65:79:d7:42:e3:6b:1c:7f:
+ 59:67:19:f5:af:d0:b9:f3:73:04:bf:55:1d:ed:49:88:52:f4:
+ dc:3b:fb:a3:56:f8:29:fc:99:3e:6e:7f:9b:12:31:4b:24:c7:
+ a6:a2:95:2f:c3:51:02:66:8e:8e:dc:af:cb:10:e7:34:48:62:
+ 14:db:1d:47
+-----BEGIN CERTIFICATE-----
+MIIDdDCCAlygAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwFzEVMBMGA1UEAwwMVGVz
+dCBSb290IENBMB4XDTA2MDEwMTAwMDAwMFoXDTA3MDEwMTAwMDAwMFowYDELMAkG
+A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWlu
+IFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANwu9LJItoIQh1ND4CHOiL/8CLh3
+YhrADzjJXjaIdH/OVN5MoiJ+lWnUofxUniyYLr8oq2R+ctaWPGn10iMcwutN/icP
+qC2AwQl2abP76nL1f1y5L1Mnv2TVXoGWfNThHf2luX0oXkE4zZAC8aiI4NGv4HkG
+kRGoia1/NDeJByAagC9tyUk22UGXqvjQX9Vbj6ak8yOjU4lJhF3bPYgurweLV6Jb
+0Ap3hQncix1T2XCb1J81DKEiLrwp/PFpaiFIP/AWrPzdj/WfGUHYqQnhdK13a4xD
+TKbEzihwOXv8yJoz9WskbY8zvoGXha/sYKeBzDowW/LXya1XBEZLEkk3HtMCAwEA
+AaOBgDB+MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFJyAxaDmD3/X4m++2PET+IS9
+2Fu2MB8GA1UdIwQYMBaAFCuIk+HSVFD0uKQgvbF55guq6+waMB0GA1UdJQQWMBQG
+CCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEB
+BQUAA4IBAQBdD5kjRYuiJPe3u/cK6flSWuiCcu61hg10oMwZMrD5Yb82JqSUjFkp
+aB70dTv+34xa2CnAn0XCS0cbFQfMSwgmu0FESQp1SAZK4eFR9ba+XVrZtPlBZE2p
+PpdvwB+vytwkEezg0L6nExGfdnIWlVPlIm9EieF6w9c5ffxdQJSaqpTl9StPY8E0
+/rf2hVMyd5c8dBFnzSe15suKGBDA9wrin12I/4JUQ0HcUTPxyG0JxiXwUz9+BMq0
+B/nEmg2uZXnXQuNrHH9ZZxn1r9C583MEv1Ud7UmIUvTcO/ujVvgp/Jk+bn+bEjFL
+JMemopUvw1ECZo6O3K/LEOc0SGIU2x1H
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/explicit-policy-chain.pem b/chromium/net/data/ssl/certificates/explicit-policy-chain.pem
new file mode 100644
index 00000000000..df1a667b880
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/explicit-policy-chain.pem
@@ -0,0 +1,228 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Policy Test Intermediate CA
+ Validity
+ Not Before: Jun 28 18:46:07 2013 GMT
+ Not After : Jun 26 18:46:07 2023 GMT
+ Subject: CN=policy_test.example
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:be:a4:a1:34:5f:d7:70:e7:49:5a:75:2b:c5:8a:
+ 63:43:bb:27:80:83:34:ab:42:6d:21:13:a4:4e:a7:
+ 85:4c:81:53:a4:1d:c4:bd:1b:bc:af:68:fb:4b:84:
+ 38:26:6b:79:96:a2:92:ca:4f:e3:6d:8e:b3:f4:68:
+ f6:ea:db:57:5d:e1:7e:78:52:28:ec:91:6a:20:11:
+ f6:35:2a:0e:4e:dc:6f:df:85:3f:ff:18:31:f6:72:
+ 81:d8:0f:e0:10:5f:cf:40:21:23:16:06:8f:7b:65:
+ ba:8e:db:1b:b0:1f:38:1f:a2:d1:e6:06:46:89:30:
+ fb:04:87:74:8f:95:cc:ce:4a:05:20:d8:ae:04:58:
+ c5:8f:2f:0d:33:9d:50:0d:19:f7:e7:64:35:9a:74:
+ 0d:1c:b5:4b:05:b2:76:1f:17:ec:33:b3:a0:69:fd:
+ 52:2e:c0:f4:3b:83:0b:cd:99:ba:1e:35:b8:ac:57:
+ d7:c9:bb:9a:d8:a0:c6:1a:86:4f:21:b6:3c:07:fc:
+ 36:16:31:7d:88:ab:6e:6f:b1:39:49:62:32:dd:61:
+ 04:12:85:6a:58:1e:8c:c6:68:05:a6:c8:ee:ac:78:
+ 8b:c7:6c:3f:20:a9:d7:e3:9a:fd:e4:d6:47:fb:53:
+ 0c:02:4c:b6:63:24:fc:b1:be:0a:2b:90:be:fd:6e:
+ 9c:2d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Certificate Policies:
+ Policy: 1.2.3.4
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3e:02:2e:1d:b2:69:0e:5a:d1:14:c7:0f:2b:b3:4d:5a:88:7a:
+ 11:16:77:78:e7:d5:1f:45:08:87:e8:88:92:52:73:eb:cd:41:
+ 93:41:ee:3b:fb:08:77:5d:6c:46:58:79:df:2c:60:09:79:ac:
+ 3c:7b:df:be:20:7c:22:6a:eb:5b:f6:c3:f4:e7:27:83:03:14:
+ 89:32:7b:d7:67:bd:0d:25:12:3a:c9:2d:82:94:1d:36:44:5f:
+ bc:23:bb:9f:a0:0c:09:19:88:4e:6d:1f:ce:83:6a:1b:6b:f0:
+ 08:34:3d:6f:00:7f:b9:f8:57:90:c1:b8:7d:4d:a6:22:f2:1f:
+ 45:44:41:31:d7:c5:b8:05:1c:9c:7e:72:4e:49:6c:52:07:c9:
+ 6d:d8:eb:5f:c7:4a:30:49:f5:4d:bd:70:4e:18:15:c9:cd:ee:
+ 45:c0:94:77:fe:9d:fc:25:9b:e9:53:e3:2f:81:c7:cd:59:14:
+ cf:e2:45:ef:04:a2:a0:17:08:60:a3:8b:75:6b:2b:fd:c5:fd:
+ 92:20:53:7e:ba:28:25:87:cb:23:68:57:aa:2e:ae:22:7d:79:
+ b2:b2:66:01:00:a0:4a:7e:34:bb:b8:bc:be:c0:64:77:0c:dd:
+ 3a:1e:43:54:a4:1f:29:50:c4:2e:16:65:e9:aa:2e:ee:99:1f:
+ de:00:0c:7c
+-----BEGIN CERTIFICATE-----
+MIIDATCCAemgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwJjEkMCIGA1UEAwwbUG9s
+aWN5IFRlc3QgSW50ZXJtZWRpYXRlIENBMB4XDTEzMDYyODE4NDYwN1oXDTIzMDYy
+NjE4NDYwN1owHjEcMBoGA1UEAwwTcG9saWN5X3Rlc3QuZXhhbXBsZTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6koTRf13DnSVp1K8WKY0O7J4CDNKtC
+bSETpE6nhUyBU6QdxL0bvK9o+0uEOCZreZaikspP422Os/Ro9urbV13hfnhSKOyR
+aiAR9jUqDk7cb9+FP/8YMfZygdgP4BBfz0AhIxYGj3tluo7bG7AfOB+i0eYGRokw
++wSHdI+VzM5KBSDYrgRYxY8vDTOdUA0Z9+dkNZp0DRy1SwWydh8X7DOzoGn9Ui7A
+9DuDC82Zuh41uKxX18m7mtigxhqGTyG2PAf8NhYxfYirbm+xOUliMt1hBBKFalge
+jMZoBabI7qx4i8dsPyCp1+Oa/eTWR/tTDAJMtmMk/LG+CiuQvv1unC0CAwEAAaNB
+MD8wDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+EAYDVR0gBAkwBzAFBgMqAwQwDQYJKoZIhvcNAQEFBQADggEBAD4CLh2yaQ5a0RTH
+DyuzTVqIehEWd3jn1R9FCIfoiJJSc+vNQZNB7jv7CHddbEZYed8sYAl5rDx7374g
+fCJq61v2w/TnJ4MDFIkye9dnvQ0lEjrJLYKUHTZEX7wju5+gDAkZiE5tH86Dahtr
+8Ag0PW8Af7n4V5DBuH1NpiLyH0VEQTHXxbgFHJx+ck5JbFIHyW3Y61/HSjBJ9U29
+cE4YFcnN7kXAlHf+nfwlm+lT4y+Bx81ZFM/iRe8EoqAXCGCji3VrK/3F/ZIgU366
+KCWHyyNoV6ouriJ9ebKyZgEAoEp+NLu4vL7AZHcM3ToeQ1SkHylQxC4WZemqLu6Z
+H94ADHw=
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Policy Test Root CA
+ Validity
+ Not Before: Jun 28 18:46:07 2013 GMT
+ Not After : Jun 26 18:46:07 2023 GMT
+ Subject: CN=Policy Test Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:9b:f5:f1:b3:9a:f8:7b:5d:b6:b6:99:97:ee:a8:
+ b5:62:2c:7a:f5:2e:04:42:d7:b3:f5:0b:10:71:50:
+ a8:db:27:11:4c:e0:2f:40:fe:fb:08:54:df:53:86:
+ d2:fc:f9:8b:40:a4:f4:0c:b9:ee:27:f3:be:bb:b1:
+ e5:84:07:3c:d7:b0:75:bc:03:31:ae:e6:35:78:d5:
+ d5:b0:3e:dc:be:3e:da:62:3b:77:90:71:f7:fd:e9:
+ 0d:d9:0a:ed:6a:59:25:b6:50:5a:d9:bd:a2:86:f2:
+ c1:bb:ba:e3:54:b7:20:38:00:aa:f7:1c:57:e5:dd:
+ d0:d5:75:3b:70:84:13:50:08:c0:ec:95:24:c4:8d:
+ 65:68:be:11:68:35:50:d3:d4:96:57:6c:de:3b:7e:
+ fc:c5:dd:c0:f2:77:a4:82:22:14:18:3f:e6:4e:5b:
+ 1e:3c:28:69:99:d9:64:e3:70:30:cc:12:0b:ad:ee:
+ ee:8e:3a:4f:f0:a1:02:dd:b1:d1:d0:3d:2c:ca:52:
+ 04:ea:98:3f:bf:14:15:5e:68:c7:ae:c6:8a:3e:4e:
+ 07:4c:d4:b3:94:4a:3d:ea:86:e2:ae:35:7b:39:0a:
+ 86:c0:f8:79:c2:61:47:59:3a:79:82:e3:56:6b:47:
+ d9:ec:70:33:7a:11:82:71:a6:57:a3:ec:f7:e6:e6:
+ 28:5d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Policy Constraints:
+ Require Explicit Policy:0
+ X509v3 Certificate Policies:
+ Policy: 1.2.3.4
+ Policy: 1.2.3.4.5
+ Policy: 1.2.3.5
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 1c:0c:79:88:11:1d:40:f2:6e:f1:d9:b7:da:2c:e1:5d:49:28:
+ e7:f5:51:27:d6:46:cc:9b:93:4c:25:ff:98:8d:91:a2:a2:92:
+ 81:6f:28:a9:ef:25:ed:33:01:bd:ab:30:3c:a1:95:ff:88:f0:
+ b5:9d:06:e6:5c:9d:a0:bd:9e:e7:11:bc:ff:19:0f:81:76:42:
+ 30:c6:e7:2d:2b:37:f5:03:66:71:3e:49:8e:0f:2c:42:20:93:
+ 49:42:d8:fa:54:1f:4b:c9:36:ac:eb:fa:fe:e4:bc:31:54:30:
+ 5f:f6:ba:0b:f5:3b:84:79:9d:f2:a9:c2:ca:4c:1b:37:f7:f6:
+ 23:f9:64:56:1e:84:78:f2:57:0b:81:63:51:db:00:8b:eb:52:
+ 54:d5:c5:d3:b5:e9:0a:82:26:cd:42:7b:64:8a:50:f4:fd:d4:
+ 9f:38:d5:27:9c:55:13:6f:ce:65:06:27:8c:e8:08:5b:fc:f9:
+ 5d:b5:57:89:36:20:44:ac:bf:89:77:55:e7:65:38:13:86:93:
+ af:54:7e:f6:2a:24:6a:28:ed:da:23:96:6f:24:be:ec:30:95:
+ 80:f9:77:1f:57:48:6f:56:f9:ab:13:25:91:4a:0a:cc:ff:3b:
+ 6a:43:9d:30:67:6f:06:5a:5c:3f:4a:be:4e:bf:2d:69:35:bd:
+ 95:c2:99:5d
+-----BEGIN CERTIFICATE-----
+MIIDEjCCAfqgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwHjEcMBoGA1UEAwwTUG9s
+aWN5IFRlc3QgUm9vdCBDQTAeFw0xMzA2MjgxODQ2MDdaFw0yMzA2MjYxODQ2MDda
+MCYxJDAiBgNVBAMMG1BvbGljeSBUZXN0IEludGVybWVkaWF0ZSBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJv18bOa+HtdtraZl+6otWIsevUuBELX
+s/ULEHFQqNsnEUzgL0D++whU31OG0vz5i0Ck9Ay57ifzvrux5YQHPNewdbwDMa7m
+NXjV1bA+3L4+2mI7d5Bx9/3pDdkK7WpZJbZQWtm9oobywbu641S3IDgAqvccV+Xd
+0NV1O3CEE1AIwOyVJMSNZWi+EWg1UNPUllds3jt+/MXdwPJ3pIIiFBg/5k5bHjwo
+aZnZZONwMMwSC63u7o46T/ChAt2x0dA9LMpSBOqYP78UFV5ox67Gij5OB0zUs5RK
+PeqG4q41ezkKhsD4ecJhR1k6eYLjVmtH2exwM3oRgnGmV6Ps9+bmKF0CAwEAAaNS
+MFAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDAYDVR0kBAUwA4AB
+ADAfBgNVHSAEGDAWMAUGAyoDBDAGBgQqAwQFMAUGAyoDBTANBgkqhkiG9w0BAQUF
+AAOCAQEAHAx5iBEdQPJu8dm32izhXUko5/VRJ9ZGzJuTTCX/mI2RoqKSgW8oqe8l
+7TMBvaswPKGV/4jwtZ0G5lydoL2e5xG8/xkPgXZCMMbnLSs39QNmcT5Jjg8sQiCT
+SULY+lQfS8k2rOv6/uS8MVQwX/a6C/U7hHmd8qnCykwbN/f2I/lkVh6EePJXC4Fj
+UdsAi+tSVNXF07XpCoImzUJ7ZIpQ9P3UnzjVJ5xVE2/OZQYnjOgIW/z5XbVXiTYg
+RKy/iXdV52U4E4aTr1R+9iokaijt2iOWbyS+7DCVgPl3H1dIb1b5qxMlkUoKzP87
+akOdMGdvBlpcP0q+Tr8taTW9lcKZXQ==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 9822402887813377013 (0x88502f6298e32bf5)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Policy Test Root CA
+ Validity
+ Not Before: Jun 28 18:46:07 2013 GMT
+ Not After : Jun 26 18:46:07 2023 GMT
+ Subject: CN=Policy Test Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a5:39:54:40:12:d5:30:51:aa:fc:bb:85:24:5a:
+ 76:19:e5:f5:54:d0:6c:c2:99:18:45:03:89:da:29:
+ 17:41:53:ac:89:1c:1c:46:b5:b6:78:d8:66:88:d1:
+ bf:83:6b:73:fb:43:0a:10:66:44:43:6e:a2:d7:d5:
+ b4:90:48:9f:ff:12:4a:f1:ae:6e:fb:21:d5:6d:7d:
+ 46:e5:96:41:80:4a:32:ee:e0:84:1e:22:2c:91:6a:
+ 62:08:18:7a:6b:67:8e:cd:39:28:13:5d:7a:75:a8:
+ 91:66:f6:9e:75:7e:b0:12:c8:3a:ea:d1:f5:7d:fa:
+ c8:3c:04:15:d8:63:5a:9e:3b:ad:4c:3c:e7:c8:f8:
+ b3:8c:2c:d6:79:28:b1:25:6a:11:c1:6e:04:5f:bf:
+ 0e:67:74:b0:99:be:d6:b9:df:99:7d:1f:7f:57:f9:
+ c5:78:07:73:b3:34:e4:7d:08:7c:9f:81:db:31:3f:
+ 2a:03:4b:8d:3c:23:6a:87:91:f7:f7:f2:b0:d5:83:
+ dd:c2:b3:c2:43:d0:ee:f7:e9:09:0b:1f:b5:12:22:
+ 37:4f:d3:ad:5c:0e:ee:51:8e:da:b4:ae:26:f1:4c:
+ 9e:34:aa:1c:8b:d3:b7:7e:17:f9:cb:12:36:3f:0d:
+ d0:b1:29:9c:9d:07:91:9b:30:9d:17:c8:a4:69:1d:
+ 60:ab
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 33:12:f6:02:8e:73:45:09:80:8e:71:c1:52:fd:3c:48:0d:c8:
+ 5a:62:64:21:ab:5b:46:6c:cd:e4:55:06:83:05:df:d7:50:78:
+ 41:8a:7b:a3:39:29:81:c6:de:e1:8d:d4:d2:f6:4c:31:7b:59:
+ 90:d7:ad:af:69:9e:49:e5:ca:06:f2:bf:34:55:e9:d2:40:54:
+ 89:2f:b9:94:f2:a2:06:db:cf:ef:20:8d:0d:8f:3c:76:65:a4:
+ 13:1d:4e:ae:de:43:a3:ed:98:4f:c0:f9:59:99:22:ba:43:cd:
+ 6e:82:2a:28:69:b6:38:ec:c1:d8:bb:42:3b:44:85:c1:09:15:
+ 19:ab:12:92:d6:84:a8:e8:88:7e:1c:e6:d6:82:ff:57:68:1f:
+ c7:67:6f:42:15:9e:9b:56:3f:5e:36:27:e1:68:0b:d3:a0:24:
+ 7d:68:08:55:30:f1:a5:2d:e7:f0:03:82:aa:2c:03:fb:78:dc:
+ 81:54:db:38:8c:7e:f9:c4:7d:f1:50:1c:42:b9:6e:c0:91:9e:
+ b0:56:57:f0:0e:3d:d9:24:65:50:a1:57:a5:72:6e:8a:1f:7f:
+ fa:ab:91:33:d8:1d:9c:48:b3:60:57:b1:94:09:61:9e:a3:90:
+ b0:c2:17:13:7a:aa:b9:ad:99:dc:25:5e:d8:c8:e8:a6:88:bb:
+ fd:94:ee:4c
+-----BEGIN CERTIFICATE-----
+MIIC4jCCAcqgAwIBAgIJAIhQL2KY4yv1MA0GCSqGSIb3DQEBBQUAMB4xHDAaBgNV
+BAMME1BvbGljeSBUZXN0IFJvb3QgQ0EwHhcNMTMwNjI4MTg0NjA3WhcNMjMwNjI2
+MTg0NjA3WjAeMRwwGgYDVQQDDBNQb2xpY3kgVGVzdCBSb290IENBMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApTlUQBLVMFGq/LuFJFp2GeX1VNBswpkY
+RQOJ2ikXQVOsiRwcRrW2eNhmiNG/g2tz+0MKEGZEQ26i19W0kEif/xJK8a5u+yHV
+bX1G5ZZBgEoy7uCEHiIskWpiCBh6a2eOzTkoE116daiRZvaedX6wEsg66tH1ffrI
+PAQV2GNanjutTDznyPizjCzWeSixJWoRwW4EX78OZ3Swmb7Wud+ZfR9/V/nFeAdz
+szTkfQh8n4HbMT8qA0uNPCNqh5H39/Kw1YPdwrPCQ9Du9+kJCx+1EiI3T9OtXA7u
+UY7atK4m8UyeNKoci9O3fhf5yxI2Pw3QsSmcnQeRmzCdF8ikaR1gqwIDAQABoyMw
+ITAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUF
+AAOCAQEAMxL2Ao5zRQmAjnHBUv08SA3IWmJkIatbRmzN5FUGgwXf11B4QYp7ozkp
+gcbe4Y3U0vZMMXtZkNetr2meSeXKBvK/NFXp0kBUiS+5lPKiBtvP7yCNDY88dmWk
+Ex1Ort5Do+2YT8D5WZkiukPNboIqKGm2OOzB2LtCO0SFwQkVGasSktaEqOiIfhzm
+1oL/V2gfx2dvQhWem1Y/XjYn4WgL06AkfWgIVTDxpS3n8AOCqiwD+3jcgVTbOIx+
++cR98VAcQrluwJGesFZX8A492SRlUKFXpXJuih9/+quRM9gdnEizYFexlAlhnqOQ
+sMIXE3qqua2Z3CVe2Mjopoi7/ZTuTA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/foaf.me.chromium-test-cert.der b/chromium/net/data/ssl/certificates/foaf.me.chromium-test-cert.der
new file mode 100644
index 00000000000..da0afc624c6
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/foaf.me.chromium-test-cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/globalsign_ev_sha256_ca_cert.pem b/chromium/net/data/ssl/certificates/globalsign_ev_sha256_ca_cert.pem
new file mode 100644
index 00000000000..bfad0aac94b
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/globalsign_ev_sha256_ca_cert.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgILBAAAAAABJQcQRNUwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkxMTE4MTAwMDAwWhcNMTkwMzE4
+MTAwMDAwWjBwMSYwJAYDVQQLEx1FeHRlbmRlZCBWYWxpZGF0aW9uIFNIQTI1NiBD
+QTETMBEGA1UEChMKR2xvYmFsU2lnbjExMC8GA1UEAxMoR2xvYmFsU2lnbiBFeHRl
+bmRlZCBWYWxpZGF0aW9uIFNIQTI1NiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALBif8TfrtmndK/ILyYLrRoXAmHWc7OhUAhQOi9KnUuv0F7PPbG3
+IoqTWVHc/5BXLTrU8v7FdKBKEyevSd8QqPltoW/wzqhwFShy8PPpWT5UrcxVGHNP
+BE99xpAPWq6JOAKSQxp59F0rhXCKUsFO+Gl+K/zR1tpYR9Oy9GKyowpIDvbxevkS
+n8b/ZAbtdi4YL3M6qy680hD7XFEgMiOcjgHvIHBj21Ga01L6yYv0klQHDRVFhhca
+wcSsQ/ckrODI508EwvESGrk18bmkypnNxDoZJJoqtQUeS8UBwUDsZ9ES63LitzBF
+uTG/nz5A/WWOzN18Adq6piOm8QGcKfwatMcCAwEAAaOB+jCB9zAOBgNVHQ8BAf8E
+BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUivwUGz2jWWelO+Fz
+kqZikX/keDAwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYBBQUHAgEWJWh0dHA6
+Ly93d3cuZ2xvYmFsc2lnbi5uZXQvcmVwb3NpdG9yeS8wNgYDVR0fBC8wLTAroCmg
+J4YlaHR0cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LXIzLmNybDARBglghkgB
+hvhCAQEEBAMCAgQwHwYDVR0jBBgwFoAUj/BLf6guRSSuTVD6Y5qL3uLdG7wwDQYJ
+KoZIhvcNAQELBQADggEBAF2HVz/fP/IUd8jFBven7nszfYdjJJbzdpehLJqoLccQ
+EEHtGTy9xnqChzCkSgIoe705slJDt4wdXibIm5uV1CE0YidAEqLPeg4w1sU6y9eV
+L0E+IGJR7UF3FH0tBDbnyiTGEjviDxqi1VlSSbXvIoyhQHlsNvSeKdvTudDNmaSF
+lleJLy8SEXyJwieoACCibX27ZUk1vU4Hhvm2nY7AbP5ry6lzNuHozc+mMvuiwM/3
+Mc61UR+LBN8YxgpSHB1m1THyVG/Nz3b82rMe1XvSm1sYsULysrRLzSVc+89Up74A
+CIFMY+ZrQU0Yq+VrBBMPRtJ1Jc1BxkjaXf0O0c9CLFo=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/google.binary.p7b b/chromium/net/data/ssl/certificates/google.binary.p7b
new file mode 100644
index 00000000000..052e3885ef9
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.binary.p7b
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/google.chain.pem b/chromium/net/data/ssl/certificates/google.chain.pem
new file mode 100644
index 00000000000..e78af716195
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.chain.pem
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
+MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
+THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x
+MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
+MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
+FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN
+gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L
+05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM
+BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
+BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
+Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
+ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
+AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5
+u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6
+z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw
+MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
+d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx
+PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g
+5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo
+3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG
+A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX
+BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG
+AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF
+BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB
+BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc
+q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR
+bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/chromium/net/data/ssl/certificates/google.pem_cert.p7b b/chromium/net/data/ssl/certificates/google.pem_cert.p7b
new file mode 100644
index 00000000000..ba80fb0733d
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.pem_cert.p7b
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGeQYJKoZIhvcNAQcCoIIGajCCBmYCAQExADALBgkqhkiG9w0BBwGgggZMMIID
+ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw
+CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk
+LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy
+MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
+FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD
+VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj
+9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu
+uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV
+HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv
+bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC
+BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v
+b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j
+b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB
+gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON
+gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR
+UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXzCCAyMwggKMoAMCAQIC
+BDAAAAIwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZl
+cmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDUxMzAwMDAwMFoXDTE0MDUxMjIz
+NTk1OVowTDELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5n
+IChQdHkpIEx0ZC4xFjAUBgNVBAMTDVRoYXd0ZSBTR0MgQ0EwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANTTZ9CNFX+uzTH+fR2RoT8LcTyszMhk+2P8MksHlL1v
+gLov4QSTwDP8CTMj6Qt0K3HEA8bSzeIv9Qljzf9IpQC/4OfziLctMt6YNuYKrQB7
+xGRKO4R1A/Jwkn0OYvUhq2k2hDF1kPi/x2yIGwaVfMnlqN51oSx6aN/VyhyHWGAZ
+AgMBAAGjgf4wgfswEgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwEQYJ
+YIZIAYb4QgEBBAQDAgEGMCgGA1UdEQQhMB+kHTAbMRkwFwYDVQQDExBQcml2YXRl
+TGFiZWwzLTE1MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24u
+Y29tL3BjYTMuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDov
+L29jc3AudGhhd3RlLmNvbTA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG
+CWCGSAGG+EIEAQYKYIZIAYb4RQEIATANBgkqhkiG9w0BAQUFAAOBgQBVrGPq3qHd
+0pBfnwvOdr4TUY+T2QUryBt3S61pUKHu3tz92wfp6DmU3KtyeS8Gv6uBcMSo7epT
+NO3vHlPZBsdWK9Fc9NGKjrQrsTeQSAhCJcU+ist/628E0W3FdKL3onx7YDx3zQ7O
+SAJ/AS+2mzfgKio23NWF1qzlP1Rvlh4Fr6EAMQA=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/google.pem_pkcs7.p7b b/chromium/net/data/ssl/certificates/google.pem_pkcs7.p7b
new file mode 100644
index 00000000000..49e2eec66d6
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.pem_pkcs7.p7b
@@ -0,0 +1,37 @@
+-----BEGIN PKCS7-----
+MIIGeQYJKoZIhvcNAQcCoIIGajCCBmYCAQExADALBgkqhkiG9w0BBwGgggZMMIID
+ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw
+CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk
+LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy
+MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw
+FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD
+VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj
+9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu
+uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV
+HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv
+bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC
+BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v
+b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j
+b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB
+gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON
+gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR
+UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXzCCAyMwggKMoAMCAQIC
+BDAAAAIwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZl
+cmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDUxMzAwMDAwMFoXDTE0MDUxMjIz
+NTk1OVowTDELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5n
+IChQdHkpIEx0ZC4xFjAUBgNVBAMTDVRoYXd0ZSBTR0MgQ0EwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANTTZ9CNFX+uzTH+fR2RoT8LcTyszMhk+2P8MksHlL1v
+gLov4QSTwDP8CTMj6Qt0K3HEA8bSzeIv9Qljzf9IpQC/4OfziLctMt6YNuYKrQB7
+xGRKO4R1A/Jwkn0OYvUhq2k2hDF1kPi/x2yIGwaVfMnlqN51oSx6aN/VyhyHWGAZ
+AgMBAAGjgf4wgfswEgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwEQYJ
+YIZIAYb4QgEBBAQDAgEGMCgGA1UdEQQhMB+kHTAbMRkwFwYDVQQDExBQcml2YXRl
+TGFiZWwzLTE1MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24u
+Y29tL3BjYTMuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDov
+L29jc3AudGhhd3RlLmNvbTA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG
+CWCGSAGG+EIEAQYKYIZIAYb4RQEIATANBgkqhkiG9w0BAQUFAAOBgQBVrGPq3qHd
+0pBfnwvOdr4TUY+T2QUryBt3S61pUKHu3tz92wfp6DmU3KtyeS8Gv6uBcMSo7epT
+NO3vHlPZBsdWK9Fc9NGKjrQrsTeQSAhCJcU+ist/628E0W3FdKL3onx7YDx3zQ7O
+SAJ/AS+2mzfgKio23NWF1qzlP1Rvlh4Fr6EAMQA=
+-----END PKCS7-----
diff --git a/chromium/net/data/ssl/certificates/google.single.der b/chromium/net/data/ssl/certificates/google.single.der
new file mode 100644
index 00000000000..f73df1704f0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.single.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/google.single.pem b/chromium/net/data/ssl/certificates/google.single.pem
new file mode 100644
index 00000000000..a03adc4ca6b
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google.single.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM
+MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
+THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x
+MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
+MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
+FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN
+gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L
+05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM
+BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
+BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
+Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
+ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
+AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5
+u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6
+z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/chromium/net/data/ssl/certificates/google_diginotar.pem b/chromium/net/data/ssl/certificates/google_diginotar.pem
new file mode 100644
index 00000000000..12bbcae0826
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/google_diginotar.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFKDCCBBCgAwIBAgIQBeLmpM0J6lTWZbB1/iKiVjANBgkqhkiG9w0BAQUFADBm
+MQswCQYDVQQGEwJOTDESMBAGA1UEChMJRGlnaU5vdGFyMSEwHwYDVQQDExhEaWdp
+Tm90YXIgUHVibGljIENBIDIwMjUxIDAeBgkqhkiG9w0BCQEWEWluZm9AZGlnaW5v
+dGFyLm5sMB4XDTExMDcxMDE5MDYzMFoXDTEzMDcwOTE5MDYzMFowajELMAkGA1UE
+BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxFjAUBgNVBAcTDU1vdW50YWluIFZp
+ZXcxFzAVBgNVBAUTDlBLMDAwMjI5MjAwMDAyMRUwEwYDVQQDEwwqLmdvb2dsZS5j
+b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNbeKubCV0aCxhOiOS
+CSQ/w9HXTYuD5BLKuiqXNw3setdTymeJz2L8aWOHo3nicFNDVwWTgwWomGNr2J6Q
+7g1iINNSW0rR4E1l2szRkcnAY6c6i/Eke93nF4i2hDsnIBveolF5yjpuRm73uQQD
+ulHjA3BFRF/PTi0fw2/Yt+8ieoMuNcMWN6Eou5Gqt5YZkWv176ofeCbsBmMrP87x
+OhhtTDckCapk4VQZG2XrfzZcV6tdzCp5TI8uHdu17cdzXm1imZ8tyvzFeiCEOQN8
+vPNzB/fIr3CJQ5q4uM5aKT3DD5PeVzf4rfJKQNgCTWiIBc9XcWEUuszwAsnmg7e2
+EJRdAgMBAAGjggHMMIIByDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0
+dHA6Ly92YWxpZGF0aW9uLmRpZ2lub3Rhci5ubDAfBgNVHSMEGDAWgBTfM8Cvkv43
+/LbYFhbQ2bGR1fpupTAJBgNVHRMEAjAAMIHGBgNVHSAEgb4wgbswgbgGDmCEEAGH
+aQEBAQIEAQICMIGlMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2lub3Rhci5u
+bC9jcHMwegYIKwYBBQUHAgIwbhpsQ29uZGl0aW9ucywgYXMgbWVudGlvbmVkIG9u
+IG91ciB3ZWJzaXRlICh3d3cuZGlnaW5vdGFyLm5sKSwgYXJlIGFwcGxpY2FibGUg
+dG8gYWxsIG91ciBwcm9kdWN0cyBhbmQgc2VydmljZXMuMEkGA1UdHwRCMEAwPqA8
+oDqGOGh0dHA6Ly9zZXJ2aWNlLmRpZ2lub3Rhci5ubC9jcmwvcHVibGljMjAyNS9s
+YXRlc3RDUkwuY3JsMA4GA1UdDwEB/wQEAwIEsDAbBgNVHREEFDASgRBhZG1pbkBn
+b29nbGUuY29tMB0GA1UdDgQWBBQHSn0WJzIo0eMBMQUNsMqN6eF/7TANBgkqhkiG
+9w0BAQUFAAOCAQEAAs5dL7N9wzRJkI4Aq4lC5t8j5ZadqnqUcgYLADzSv4ExytNH
+UY2nH6iVTihC0UPSsILWraoeApdT7Rphz/8DLQEBRGdeKWAptNM3EbiXtQaZT2uB
+pidL8UoafX0kch3f71Y1scpBEjvu5ZZLnjg0A8AL0tnsereOVdDpU98bKqdbbrnM
+FRmBlSf7xdaNca6JJHeEpga4E9Ty683CmccrSGXdU2tTCuHEJww+iOAUtPIZcsum
+U7/eYeY1pMyGLyIjbNgRY7nDzRwvM/BsbL9eh4/mSQj/4nncqJd22sVQpCggQiVK
+baB2sVGcVNBkK55bT8gPqnx8JypubyUvayzZGg==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/googlenew.chain.pem b/chromium/net/data/ssl/certificates/googlenew.chain.pem
new file mode 100644
index 00000000000..5a3e1cf6ff3
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/googlenew.chain.pem
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAoqgAwIBAgIQT52W2WawmStUwpV8tBV9TTANBgkqhkiG9w0BAQUFADBM
+MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg
+THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0xMTEwMjYwMDAwMDBaFw0x
+MzA5MzAyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
+MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw
+FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA3rcmQ6aZhc04pxUJuc8PycNVjIjujI0oJyRLKl6g2Bb6YRhLz21ggNM1QDJy
+wI8S2OVOj7my9tkVXlqGMaO6hqpryNlxjMzNJxMenUJdOPanrO/6YvMYgdQkRn8B
+d3zGKokUmbuYOR2oGfs5AER9G5RqeC1prcB6LPrQ2iASmNMCAwEAAaOB5zCB5DAM
+BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF
+BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw
+Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0
+ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF
+AAOBgQAhrNWuyjSJWsKrUtKyNGadeqvu5nzVfsJcKLt0AMkQH0IT/GmKHiSgAgDp
+ulvKGQSy068Bsn5fFNum21K5mvMSf3yinDtvmX3qUA12IxL/92ZzKbeVCq3Yi7Le
+IOkKcGQRCMha8X2e7GmlpdWC1ycenlbN0nbVeSv3JUMcafC4+Q==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw
+MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
+d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx
+PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g
+5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo
+3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG
+A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX
+BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG
+AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF
+BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB
+BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc
+q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR
+bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/invalid_key_usage_cert.der b/chromium/net/data/ssl/certificates/invalid_key_usage_cert.der
new file mode 100644
index 00000000000..3a1be64f9ae
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/invalid_key_usage_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/mit.davidben.der b/chromium/net/data/ssl/certificates/mit.davidben.der
new file mode 100644
index 00000000000..4e26aa8825f
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/mit.davidben.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/multivalue_rdn.pem b/chromium/net/data/ssl/certificates/multivalue_rdn.pem
new file mode 100644
index 00000000000..6ffe16111f1
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/multivalue_rdn.pem
@@ -0,0 +1,59 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ ef:6c:2f:57:d9:fd:5a:0f
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Chromium, DC=Chromium, CN=Multivalue RDN Test, OU=Chromium net_unittests
+ Validity
+ Not Before: Dec 2 03:47:39 2011 GMT
+ Not After : Jan 1 03:47:39 2012 GMT
+ Subject: C=US, O=Chromium, DC=Chromium, CN=Multivalue RDN Test, OU=Chromium net_unittests
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:9d:23:10:ed:87:82:b0:eb:a4:fb:49:f5:db:c1:
+ 7b:4d:f0:ed:1b:f5:f8:f2:c1:b5:d2:84:de:eb:94:
+ 82:f0:de:8b:04:b7:e8:ed:86:22:41:99:56:54:71:
+ 33:8e:c1:69:6a:2b:f4:77:1e:24:70:81:5b:56:08:
+ 57:02:4c:bf:af:9a:a0:33:55:e2:00:6b:b3:cc:5c:
+ 3b:47:6e:dc:05:30:bd:0c:f9:51:c0:70:2b:3f:70:
+ a2:10:a3:b7:8b:3f:22:fa:ab:bd:c7:48:a5:ff:d3:
+ 7b:d0:b7:12:48:0b:bf:90:62:f1:8a:40:db:1d:1a:
+ 0c:f5:dd:92:2a:1c:b6:2c:6b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 8D:5E:71:FA:2A:73:BA:9F:8E:63:32:1C:5D:AB:87:D0:AB:47:AB:B7
+ X509v3 Authority Key Identifier:
+ keyid:8D:5E:71:FA:2A:73:BA:9F:8E:63:32:1C:5D:AB:87:D0:AB:47:AB:B7
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 18:ac:0d:d3:50:38:ee:7c:55:1c:e9:30:c0:57:5b:4f:cb:7d:
+ 14:59:18:de:92:20:e6:67:a8:a8:ed:da:01:33:5a:48:4c:e4:
+ 66:85:25:8d:6b:56:81:67:f3:af:e9:f8:12:5c:19:07:17:98:
+ bf:d7:0f:ba:b5:64:6b:ec:17:ca:0c:d6:ce:c3:b3:09:43:0a:
+ 04:8f:da:4b:c8:a3:45:ea:ef:ca:f8:7a:2e:91:a8:8c:f1:a7:
+ d4:7b:6d:9d:73:4b:9a:1c:be:04:b1:02:b3:b7:2a:e9:fd:19:
+ 86:f2:26:ac:45:a1:0f:9b:99:1a:53:b1:69:99:3e:6c:51:23:
+ 40:70
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIJAO9sL1fZ/VoPMA0GCSqGSIb3DQEBBQUAMHExbzAJBgNV
+BAYTAlVTMA8GA1UECgwIQ2hyb21pdW0wFgYKCZImiZPyLGQBGRYIQ2hyb21pdW0w
+GgYDVQQDDBNNdWx0aXZhbHVlIFJETiBUZXN0MB0GA1UECwwWQ2hyb21pdW0gbmV0
+X3VuaXR0ZXN0czAeFw0xMTEyMDIwMzQ3MzlaFw0xMjAxMDEwMzQ3MzlaMHExbzAJ
+BgNVBAYTAlVTMA8GA1UECgwIQ2hyb21pdW0wFgYKCZImiZPyLGQBGRYIQ2hyb21p
+dW0wGgYDVQQDDBNNdWx0aXZhbHVlIFJETiBUZXN0MB0GA1UECwwWQ2hyb21pdW0g
+bmV0X3VuaXR0ZXN0czCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAnSMQ7YeC
+sOuk+0n128F7TfDtG/X48sG10oTe65SC8N6LBLfo7YYiQZlWVHEzjsFpaiv0dx4k
+cIFbVghXAky/r5qgM1XiAGuzzFw7R27cBTC9DPlRwHArP3CiEKO3iz8i+qu9x0il
+/9N70LcSSAu/kGLxikDbHRoM9d2SKhy2LGsCAwEAAaNQME4wHQYDVR0OBBYEFI1e
+cfoqc7qfjmMyHF2rh9CrR6u3MB8GA1UdIwQYMBaAFI1ecfoqc7qfjmMyHF2rh9Cr
+R6u3MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAGKwN01A47nxVHOkw
+wFdbT8t9FFkY3pIg5meoqO3aATNaSEzkZoUljWtWgWfzr+n4ElwZBxeYv9cPurVk
+a+wXygzWzsOzCUMKBI/aS8ijRervyvh6LpGojPGn1HttnXNLmhy+BLECs7cq6f0Z
+hvImrEWhD5uZGlOxaZk+bFEjQHA=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/ndn.ca.crt b/chromium/net/data/ssl/certificates/ndn.ca.crt
new file mode 100644
index 00000000000..6da9fb204b7
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/ndn.ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGBjCCA+4CCQDbt8YGR683ojANBgkqhkiG9w0BAQUFADCBxDELMAkGA1UEBhMC
+VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMR8w
+HQYDVQQKExZOZXcgRHJlYW0gTmV0d29yaywgTExDMREwDwYDVQQLEwhTZWN1cml0
+eTEwMC4GA1UEAxMnTmV3IERyZWFtIE5ldHdvcmsgQ2VydGlmaWNhdGUgQXV0aG9y
+aXR5MSQwIgYJKoZIhvcNAQkBFhVzdXBwb3J0QGRyZWFtaG9zdC5jb20wHhcNMDYw
+ODIyMjExMjU4WhcNMTYwODE5MjExMjU4WjCBxDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMR8wHQYDVQQKExZO
+ZXcgRHJlYW0gTmV0d29yaywgTExDMREwDwYDVQQLEwhTZWN1cml0eTEwMC4GA1UE
+AxMnTmV3IERyZWFtIE5ldHdvcmsgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSQwIgYJ
+KoZIhvcNAQkBFhVzdXBwb3J0QGRyZWFtaG9zdC5jb20wggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQDHykL2vz70nbj827iARXxgBK41yXUy7oBcoG5TA+MD
+GjXVqV+szQWgQz6C6uPPp+O4cUVxnAIfXvCo7VjsYYsDcmCWAlV27ZRN3Ek7HvIv
+4rHaht0y/E/DgKOZiiy7kJMvqDZSuD9NxSlIOTDBS6aDyORNZKubWdg0l+Axk3Yn
+YhmVhPtqupO8HzQjR0s2wgwKdz3m96RqjJDBng0wLEv/iCGN0ogBBV1TePuKcodF
+TQivkjvVcITRVWIBKNfg0uDeM+cHIYGp44WM3gBX0W3AaG9JH/1tYWmgVrk/cmwK
+oMq3PKVr/Usp7nR3PTyn1rpcJjPZDSNOleO+KilI4vbwFgnydbm97eTf/BFy548j
+SPi9HINufNDSajXCRvyQ04CQVpAzH2mPVcykoL6++M2u2nNO5tLWa2ix96RNPrV/
+K00KbmIODPoqRVmoL7UvD4w2AM26l7Ol20cLqU8G4bZGe1DoKpF1CxwQlBzY6iJN
+PiEY+eAII54w7cnHdAp2mOKvJBVUzCROc7g4W0n3/JCHEaXDnfXlqevCckAaDbes
+AM7w9epOd7rUWpbSxOuKsgIyXvhM9VwxI7L5TdgULqKXq57jMAK8irsOalEt+5pJ
+8RGFDOgJsbR+eJ3En//11OJzgz1bdW0lSUD72hoNdLyJtP7dn8+FrIfGBw9mwWMG
+MwIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAVUa0xn7/bDpyxdqJrr3np5HKDKaL6
+CL4KNgBS1hKHLIqxwKGTO+nCm1sVgK9fIh7hF5sG7f5hc5ZPp9usOeqwewB5drYZ
+jPyk2e5WX0fjdB2pFPxhPS8vzNJRBtpPnUoiOuwXI4K12V7xj3SC/S4tqNfBM/p9
+sk15XfWiS8rMqDGg6oMFeM4BZ7YaZOXY/upqofg+MEbVdLBngwUPI/a1rR8OYUBr
+NhsSZpXwg22IwDE45M7wxgRvj0hojRAAxVS75ogitx040tf2Lp8DJgtSGvOHtuW9
+87MXd8Ev8xf/7d9EXw4ghwKvyWglrw0Bpoh9OP9DOwoRFIzdBz5aUmAx6PNIvZ0Y
+xQ+QRUxj+Kx2Xl6hpsk8URsfxKDHR2sZwcSqjD+JJGJee26An7btAst1/grAYw5X
+tEfcXyXiDBHXPY6t0NZOzfQUggwIU0fiWhvT43Skob6pCc0dUBeEhFK0dpyDE3AM
+4LwF/ECGfPn/UzzB/Lax4WaY2yGccBrikRfIkZARrJbvBCAxchH+HCeBV0B0BlOi
+2R0RxwH0gKBEHxhMaq5J/rTaE7V5wd8OsxzcxUgW+te6hc0bbuhPiYhtvQ0N7+oF
+z89JuuwocsTbNekeBwHB5wpsCPA29mYId8uvVl7LThvut9+szsWetmVua7P9t10h
+GX3cdJCMZUwypA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/nist.der b/chromium/net/data/ssl/certificates/nist.der
new file mode 100644
index 00000000000..8add89d6e9e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/nist.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/no_subject_common_name_cert.pem b/chromium/net/data/ssl/certificates/no_subject_common_name_cert.pem
new file mode 100644
index 00000000000..f9d582e84ca
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/no_subject_common_name_cert.pem
@@ -0,0 +1,109 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ f0:1e:87:f4:fe:b6:f0:10:dc:7e:65:da:f4:fb:8f:e3
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Client Authentication and Secure Email CA
+ Validity
+ Not Before: Apr 25 00:00:00 2012 GMT
+ Not After : Apr 25 23:59:59 2013 GMT
+ Subject: emailAddress=wtc@google.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:f6:50:cf:80:32:ee:05:1b:6a:07:c5:5f:71:bc:
+ 67:81:40:63:46:35:a4:23:6c:77:32:57:ec:c8:b0:
+ 0b:16:0e:5f:b7:42:6e:de:6f:de:84:9f:cf:81:73:
+ ee:30:a9:ac:47:8c:47:27:78:89:eb:d1:14:e9:a9:
+ fd:08:74:01:2c:dc:ea:4f:0a:30:82:cd:b5:02:65:
+ 54:42:9c:af:14:18:a7:56:c7:c4:00:7d:c0:f6:4d:
+ 97:58:80:57:f5:fc:2c:36:78:69:d6:e3:7f:05:05:
+ ff:08:26:1e:f5:d5:60:3f:15:5d:06:16:28:11:a5:
+ 5d:b5:d0:59:d1:27:ab:38:8e:6b:68:b6:3d:c3:cb:
+ f5:0d:42:35:a1:6e:f5:3f:73:12:33:9f:47:a5:43:
+ 2f:f3:dc:00:61:18:3f:4a:74:e4:d2:e8:37:e7:19:
+ 18:84:34:49:78:d2:b5:4d:90:65:4e:6f:a0:13:5b:
+ 2f:47:3e:0c:04:2f:fe:bd:9c:c1:d1:d1:f2:36:fe:
+ df:07:e6:ec:b8:48:c7:1f:24:f6:af:9a:35:2a:a8:
+ 12:db:a9:a2:50:cc:0c:28:a4:fc:66:f2:28:a8:c2:
+ d1:fb:ea:1e:58:c3:14:70:e0:18:a4:2a:04:54:8c:
+ d3:f3:09:2e:b7:76:a3:eb:07:7a:f4:e8:3a:e3:2e:
+ 6b:b7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:7A:13:4E:00:74:5B:C6:78:63:64:27:C1:2F:E2:A0:5B:BC:79:C5:7B
+
+ X509v3 Subject Key Identifier:
+ D7:D3:09:C6:62:34:E3:2A:2D:5D:F3:E9:B4:B0:DA:73:55:E9:75:28
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ E-mail Protection, 1.3.6.1.4.1.6449.1.3.5.2
+ Netscape Cert Type:
+ S/MIME
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.1.1.1
+ CPS: https://secure.comodo.net/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOClientAuthenticationandSecureEmailCA.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/COMODOClientAuthenticationandSecureEmailCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ X509v3 Subject Alternative Name:
+ email:wtc@google.com
+ Signature Algorithm: sha1WithRSAEncryption
+ 62:4c:6b:d9:de:0e:56:66:74:ac:b2:08:ef:d4:9f:f7:02:d7:
+ f0:80:7a:ad:91:19:d4:4e:1e:76:1d:34:fb:f9:c5:c3:5b:55:
+ 83:af:5f:da:12:43:bf:e2:d2:4a:3b:aa:91:d6:0e:07:4b:ab:
+ 57:c9:b8:d7:6b:cc:f9:33:85:d4:ea:40:06:09:30:15:8f:e9:
+ 1a:0f:9d:b7:a4:4b:b5:a1:f5:3a:90:71:93:ae:ab:e1:84:1c:
+ d8:d5:1c:2c:87:df:3a:5e:e8:5a:75:21:d8:45:41:5e:db:ba:
+ 9c:f3:50:c9:08:1f:e5:d5:e7:55:0a:25:cd:86:88:41:83:2f:
+ b3:ee:39:02:a6:34:52:fe:64:0a:72:6e:65:41:eb:f9:10:34:
+ 65:40:3d:93:3e:68:6a:ea:68:c5:cb:09:09:78:be:a7:1e:fa:
+ 6f:21:d8:0e:e3:8d:74:08:57:ef:17:f1:6d:50:66:a2:73:78:
+ 10:81:65:bf:96:e4:82:8d:46:7b:e8:a1:fa:6f:33:90:d8:4f:
+ ec:1f:fe:6f:4b:bf:b1:67:2d:cc:e3:90:ef:87:d3:af:ef:d7:
+ 3b:d7:14:56:b7:7a:1a:96:e4:8c:de:2b:a3:95:9d:a9:e5:31:
+ 7b:c9:2c:ec:1f:82:06:7c:80:fa:14:da:71:3b:d1:47:84:8f:
+ 01:e6:5a:8a
+-----BEGIN CERTIFICATE-----
+MIIFHjCCBAagAwIBAgIRAPAeh/T+tvAQ3H5l2vT7j+MwDQYJKoZIhvcNAQEFBQAw
+gZMxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTkwNwYD
+VQQDEzBDT01PRE8gQ2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBTZWN1cmUgRW1h
+aWwgQ0EwHhcNMTIwNDI1MDAwMDAwWhcNMTMwNDI1MjM1OTU5WjAfMR0wGwYJKoZI
+hvcNAQkBFg53dGNAZ29vZ2xlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAPZQz4Ay7gUbagfFX3G8Z4FAY0Y1pCNsdzJX7MiwCxYOX7dCbt5v3oSf
+z4Fz7jCprEeMRyd4ievRFOmp/Qh0ASzc6k8KMILNtQJlVEKcrxQYp1bHxAB9wPZN
+l1iAV/X8LDZ4adbjfwUF/wgmHvXVYD8VXQYWKBGlXbXQWdEnqziOa2i2PcPL9Q1C
+NaFu9T9zEjOfR6VDL/PcAGEYP0p05NLoN+cZGIQ0SXjStU2QZU5voBNbL0c+DAQv
+/r2cwdHR8jb+3wfm7LhIxx8k9q+aNSqoEtupolDMDCik/GbyKKjC0fvqHljDFHDg
+GKQqBFSM0/MJLrd2o+sHevToOuMua7cCAwEAAaOCAd4wggHaMB8GA1UdIwQYMBaA
+FHoTTgB0W8Z4Y2QnwS/ioFu8ecV7MB0GA1UdDgQWBBTX0wnGYjTjKi1d8+m0sNpz
+Vel1KDAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAgBgNVHSUEGTAXBggr
+BgEFBQcDBAYLKwYBBAGyMQEDBQIwEQYJYIZIAYb4QgEBBAQDAgUgMEYGA1UdIAQ/
+MD0wOwYMKwYBBAGyMQECAQEBMCswKQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJl
+LmNvbW9kby5uZXQvQ1BTMFcGA1UdHwRQME4wTKBKoEiGRmh0dHA6Ly9jcmwuY29t
+b2RvY2EuY29tL0NPTU9ET0NsaWVudEF1dGhlbnRpY2F0aW9uYW5kU2VjdXJlRW1h
+aWxDQS5jcmwwgYgGCCsGAQUFBwEBBHwwejBSBggrBgEFBQcwAoZGaHR0cDovL2Ny
+dC5jb21vZG9jYS5jb20vQ09NT0RPQ2xpZW50QXV0aGVudGljYXRpb25hbmRTZWN1
+cmVFbWFpbENBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu
+Y29tMBkGA1UdEQQSMBCBDnd0Y0Bnb29nbGUuY29tMA0GCSqGSIb3DQEBBQUAA4IB
+AQBiTGvZ3g5WZnSssgjv1J/3AtfwgHqtkRnUTh52HTT7+cXDW1WDr1/aEkO/4tJK
+O6qR1g4HS6tXybjXa8z5M4XU6kAGCTAVj+kaD523pEu1ofU6kHGTrqvhhBzY1Rws
+h986XuhadSHYRUFe27qc81DJCB/l1edVCiXNhohBgy+z7jkCpjRS/mQKcm5lQev5
+EDRlQD2TPmhq6mjFywkJeL6nHvpvIdgO4410CFfvF/FtUGaic3gQgWW/luSCjUZ7
+6KH6bzOQ2E/sH/5vS7+xZy3M45Dvh9Ov79c71xRWt3oaluSM3iujlZ2p5TF7ySzs
+H4IGfID6FNpxO9FHhI8B5lqK
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/non-crit-codeSigning-chain.pem b/chromium/net/data/ssl/certificates/non-crit-codeSigning-chain.pem
new file mode 100644
index 00000000000..d4565d14029
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/non-crit-codeSigning-chain.pem
@@ -0,0 +1,105 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpgIBAAKCAQEA9qqEx3tA2pheek7aVb/16FY4NyumZ0fhyXn60xmhUPkRESbR
+0JGUNfWEk1KJgGZHL/fkwRW4hCPQMmBzBdAFjCUR/V2QGUVu4dEcNFHrhGsdFiLb
+o2T+9a4mqmtYSRZFqHpBqBv8Uy0ec4Et/FzDACLK3CY7Ut+BDW+qcBLYEvXYx3oJ
+UGQW71yB7XuBgFjd5R3yq3pHXQXSrZboz2hGd3adYU6xRFiWAEgxOlMm4CmLgqXW
+3ywEW4XAmf93jDxjRPCiQqjUFFKu+O9gFIKjNq+apifuVZA5DXI8DmrJfxNQk4g1
+iJFZp6FzfyUr/eCV5iHjkjfVpWDr663yy4xAQwIDAQABAoIBAQDGovraSX8oiZtL
+k0Rv2W4F86jBhP00HsbMoNZEOVeXBXug/EnqU9GuwlBI8yaXp8Wb81zbwMoeX1jB
+uLnn7GULPq8GAdqQ/0ETyHmWCIJI2JlLwjZtll9bKSsqAdfOw4jE6+8DmcXQlXz4
+Nwb5OxotedczAfaz/hjG4S23ovUuDggSUtNXNxvXOyzk9FjN87vvl9HWLOLuKzDB
+LXYOm0UcEY833XNhnoV3V9GZPHKyZO7HsPLNbCRLwKgVOEqBVwWS+M+QMR8uxrEk
++GVto05gJ6fZ+PXzIVKMOFBST7fx6tBOEAFwmxbYovv7N653rzz/lXQ0SbIPRmcB
+VqBDCWOBAoGBAPu1nPTneCX4EdP7ml+RNreRcOce1/9VxOATp7msDghOfBGjwV01
+3VYzn7YVxBHxadb0kMiJ6rwEmLroiJsnp2zZpoK8K7Kzkr3Bv2E19CDNNKNo2iAp
+WbjTwtPKuj3dWEuLHfQ+FYRb6IkAWfif/u3DFGd5ntMLhhbNbjoRiudbAoGBAPre
+5dxxQMFcEw0YaEOlG/hxTh6wsMAd2P4u1vkjz841mNFP04MECJVWUIEdHiN9hCIU
+n07V8HE9t5GFEahO1+hIwq35vOQQ0hqGk3s2NvvHZjRtiQXgqX4uOULhyHo9LAyi
+bsJHC/+TYyImr/1kLdkdfdjfIwylnFM8wvQ/Bcc5AoGBAK0JO75MhZcsgy0EUSqT
+jFcDf+cSmBBq6O3V9T653eet9LK3rU0jo2YIFSo6qlBfcpAYDpQd46WfT1NmWcq3
+puw4b7R+IVg3BJC4aZUtXwdRqncPtKvZYVmjEdNBEWlICdPc7hmiuG4GNMRB3pgB
+fWH78sHzpNCWonZ4gOcgeinzAoGBAODeGzvVaC3p0knSruKzhLbVGSj5R/Vsy6xL
+X/ZB22sDL8+utfCiFO+HRvH0n6dAQsgppAKOUc+venO9y24J/g3/6kD3shDb4vhT
+2K6AkhhtS3eDF2dlUYgA9uK0b3bijjFlB9KszFxySFG8S9PdkBslDEDxAgG8ELur
+ozQ7qXpBAoGBAOwbkTFGJNCLyirSkyEdPIFDv7GmfKKUNFl5rbRopoQ4q/yjRNKw
+fsF7TE9/hqkanVfRCpKM78NtrKGPgTXHPq8kCtdL5yDIQzz/EVC30ar2HzZmnMgX
+2AXhOHYT8iXDHcad0HE58gRReb2pPzoxyLDXgEZftI6vbyojfi46NtC4
+-----END RSA PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Jan 23 23:51:05 2013 GMT
+ Not After : Jan 21 23:51:05 2023 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:f6:aa:84:c7:7b:40:da:98:5e:7a:4e:da:55:bf:
+ f5:e8:56:38:37:2b:a6:67:47:e1:c9:79:fa:d3:19:
+ a1:50:f9:11:11:26:d1:d0:91:94:35:f5:84:93:52:
+ 89:80:66:47:2f:f7:e4:c1:15:b8:84:23:d0:32:60:
+ 73:05:d0:05:8c:25:11:fd:5d:90:19:45:6e:e1:d1:
+ 1c:34:51:eb:84:6b:1d:16:22:db:a3:64:fe:f5:ae:
+ 26:aa:6b:58:49:16:45:a8:7a:41:a8:1b:fc:53:2d:
+ 1e:73:81:2d:fc:5c:c3:00:22:ca:dc:26:3b:52:df:
+ 81:0d:6f:aa:70:12:d8:12:f5:d8:c7:7a:09:50:64:
+ 16:ef:5c:81:ed:7b:81:80:58:dd:e5:1d:f2:ab:7a:
+ 47:5d:05:d2:ad:96:e8:cf:68:46:77:76:9d:61:4e:
+ b1:44:58:96:00:48:31:3a:53:26:e0:29:8b:82:a5:
+ d6:df:2c:04:5b:85:c0:99:ff:77:8c:3c:63:44:f0:
+ a2:42:a8:d4:14:52:ae:f8:ef:60:14:82:a3:36:af:
+ 9a:a6:27:ee:55:90:39:0d:72:3c:0e:6a:c9:7f:13:
+ 50:93:88:35:88:91:59:a7:a1:73:7f:25:2b:fd:e0:
+ 95:e6:21:e3:92:37:d5:a5:60:eb:eb:ad:f2:cb:8c:
+ 40:43
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ F1:CF:56:60:86:22:7B:5F:E3:00:7E:D8:73:A2:F2:C4:1D:A9:EB:43
+ X509v3 Extended Key Usage:
+ Code Signing
+ Signature Algorithm: sha1WithRSAEncryption
+ 93:70:2e:48:e9:19:55:22:e8:c4:18:9c:2a:cb:4f:3c:e5:88:
+ 08:a8:8e:8c:49:d5:3e:6a:bc:77:d7:8d:22:03:78:9a:1f:01:
+ 43:85:e6:ec:d3:ec:90:05:47:a3:23:e8:04:e4:6d:7a:ef:45:
+ af:b1:6c:17:7c:e1:3e:be:10:bd:c1:05:12:98:c7:3c:87:49:
+ 9e:79:30:ca:8a:44:95:ce:00:be:81:26:0e:84:83:ab:16:91:
+ f3:33:30:ef:09:50:3e:4b:7b:f8:5b:6c:90:47:f9:55:f6:4c:
+ cb:be:50:2d:f1:c1:96:c0:c2:4f:2c:ee:cb:8b:aa:d6:c2:dd:
+ 28:89:bc:b3:d1:dc:d1:ec:c0:fe:bf:b9:7b:0a:ab:aa:96:ce:
+ 08:89:d8:c7:50:5d:b0:16:ea:90:15:f0:20:d3:e6:bb:3b:18:
+ 87:12:71:b7:b8:56:eb:9c:85:be:82:7c:53:4b:88:22:08:59:
+ 63:ee:75:42:7c:10:69:e9:4c:fe:b0:77:52:81:17:f3:42:b5:
+ 60:e5:2a:1b:03:21:a0:df:bb:ab:21:46:6b:fb:78:76:ac:80:
+ ec:c3:9d:58:a7:27:af:8a:b0:60:a1:c7:8c:f9:e3:d6:f9:1b:
+ e8:44:67:3f:bc:43:e6:32:e1:d9:ca:29:78:20:8c:45:42:0c:
+ 76:39:c2:c0
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjQCAgDsMA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNVBAMMFTIwNDggUlNB
+IFRlc3QgUm9vdCBDQTAeFw0xMzAxMjMyMzUxMDVaFw0yMzAxMjEyMzUxMDVaMGAx
+CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3Vu
+dGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD2qoTHe0DamF56TtpVv/Xo
+Vjg3K6ZnR+HJefrTGaFQ+RERJtHQkZQ19YSTUomAZkcv9+TBFbiEI9AyYHMF0AWM
+JRH9XZAZRW7h0Rw0UeuEax0WItujZP71riaqa1hJFkWoekGoG/xTLR5zgS38XMMA
+IsrcJjtS34ENb6pwEtgS9djHeglQZBbvXIHte4GAWN3lHfKrekddBdKtlujPaEZ3
+dp1hTrFEWJYASDE6UybgKYuCpdbfLARbhcCZ/3eMPGNE8KJCqNQUUq7472AUgqM2
+r5qmJ+5VkDkNcjwOasl/E1CTiDWIkVmnoXN/JSv94JXmIeOSN9WlYOvrrfLLjEBD
+AgMBAAGjVTBTMA8GA1UdEQQIMAaHBH8AAAEwDAYDVR0TAQH/BAIwADAdBgNVHQ4E
+FgQU8c9WYIYie1/jAH7Yc6LyxB2p60MwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDQYJ
+KoZIhvcNAQEFBQADggEBAJNwLkjpGVUi6MQYnCrLTzzliAiojoxJ1T5qvHfXjSID
+eJofAUOF5uzT7JAFR6Mj6ATkbXrvRa+xbBd84T6+EL3BBRKYxzyHSZ55MMqKRJXO
+AL6BJg6Eg6sWkfMzMO8JUD5Le/hbbJBH+VX2TMu+UC3xwZbAwk8s7suLqtbC3SiJ
+vLPR3NHswP6/uXsKq6qWzgiJ2MdQXbAW6pAV8CDT5rs7GIcScbe4Vuuchb6CfFNL
+iCIIWWPudUJ8EGnpTP6wd1KBF/NCtWDlKhsDIaDfu6shRmv7eHasgOzDnVinJ6+K
+sGChx4z549b5G+hEZz+8Q+Yy4dnKKXggjEVCDHY5wsA=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/ocsp-test-root.pem b/chromium/net/data/ssl/certificates/ocsp-test-root.pem
new file mode 100644
index 00000000000..493fe54f9bc
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/ocsp-test-root.pem
@@ -0,0 +1,51 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Testing CA
+ Validity
+ Not Before: Jan 1 06:00:00 2010 GMT
+ Not After : Dec 1 06:00:00 2032 GMT
+ Subject: CN=Testing CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:a7:19:98:f2:93:0b:fe:73:d0:31:a8:7f:13:3d:
+ 2f:37:8e:ee:ee:d5:2a:77:e4:4d:0f:c9:ff:6f:07:
+ ff:32:cb:f3:da:99:9d:e4:ed:65:83:2a:fc:b0:80:
+ 7f:98:78:75:06:53:9d:25:8a:0c:e3:c2:c7:79:67:
+ 65:30:99:a9:03:4a:9b:11:5a:87:6c:39:a8:c4:e4:
+ ed:4a:cd:0c:64:09:59:46:fb:39:ee:eb:47:a0:70:
+ 4d:bb:01:8a:cf:48:c3:a1:c4:b8:95:fc:40:9f:b4:
+ a3:40:a9:86:b1:af:c4:55:19:ab:9e:ca:47:c3:01:
+ 85:c7:71:c6:4a:a5:ec:f0:7d
+ Exponent: 3 (0x3)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.11129.2.4.1
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 48:0c:c9:ab:8f:f2:cc:80:f1:1f:b3:3a:45:18:de:ab:c5:e0:
+ d7:d4:64:a0:c4:86:2e:fc:58:3a:d7:86:ba:02:4e:29:95:72:
+ 9f:20:5d:43:b2:41:4e:7c:a4:86:a1:df:b3:ab:7e:46:cb:af:
+ 41:7d:c2:2b:b4:d3:22:d3:67:3e:13:ef:b6:9f:5c:8a:0d:3c:
+ a7:58:eb:a9:21:d2:9b:6b:e5:b6:4f:d6:7c:22:a7:b3:18:82:
+ b2:16:7d:d6:5c:7d:c9:46:be:91:49:e8:d2:42:95:cd:f8:8a:
+ 91:50:e7:5b:2a:26:68:ef:e7:e7:c6:24:d1:3c:01:9d:6c:48:
+ a4:f5
+-----BEGIN CERTIFICATE-----
+MIIB0DCCATmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpUZXN0
+aW5nIENBMB4XDTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2MDAwMFowFTETMBEGA1UE
+AxMKVGVzdGluZyBDQTCBnTANBgkqhkiG9w0BAQEFAAOBiwAwgYcCgYEApxmY8pML
+/nPQMah/Ez0vN47u7tUqd+RND8n/bwf/Msvz2pmd5O1lgyr8sIB/mHh1BlOdJYoM
+48LHeWdlMJmpA0qbEVqHbDmoxOTtSs0MZAlZRvs57utHoHBNuwGKz0jDocS4lfxA
+n7SjQKmGsa/EVRmrnspHwwGFx3HGSqXs8H0CAQOjMjAwMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwGgYDVR0gAQEABBAwDjAMBgorBgEEAdZ5AgQBMA0GCSqGSIb3DQEBBQUA
+A4GBAEgMyauP8syA8R+zOkUY3qvF4NfUZKDEhi78WDrXhroCTimVcp8gXUOyQU58
+pIah37OrfkbLr0F9wiu00yLTZz4T77afXIoNPKdY66kh0ptr5bZP1nwip7MYgrIW
+fdZcfclGvpFJ6NJClc34ipFQ51sqJmjv5+fGJNE8AZ1sSKT1
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/ok_cert.pem b/chromium/net/data/ssl/certificates/ok_cert.pem
new file mode 100644
index 00000000000..69d10d2219c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/ok_cert.pem
@@ -0,0 +1,110 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCbE8ILYEITh9up
+ken6c61zNAibRnq70g7glLy6fHqQJsrIVzmj6b+4dHLYUA3mu13pqZyoK6YP1MPd
+afdh23IrW4vo6xtxCpqQk0rAEa+0cji7E+01JHEvO4Y1Q4NScGj628HDe+dJqiQD
+AZWRDgIodlKrRjD1XZT91U0UsuytDvYQVbEIosgMdEevW8FvqWAhDH6fkb4Q5OdW
+wUxGnX0+cHLINVMJvCoPq+fDQWy4A/2uRiFn/x9NBZEz2AwNta+y6e3pn8gZE9vh
+Feqe3Jv9toIZzsqQZ/fCWK6FKzfkxQNYc5vcSqHx96VuDhqrbnPFdpcQfRB08Yg4
+3TSioKY7AgMBAAECggEAOqImzPxRH2dPs3Z6+/n+y78RvfvQ7fDHNTyneu8TvCse
+os7v+TypA4nr3kOubd3L7Uv28lLGj9fHUpD9Ot+o9CHB7YfvMTdsJ1u5eJN3VoeV
+UY6AMoab0Nr1rG/hWCsuViL+yPWxBlYxFX3k2hps0HWkXiPE4RDIA41BfqEEAY4+
+6V0lvoBZAJbYncGg1BEDxH+erXIFmAu3PeCYEpb2VP7hQH8JITEWco+DmK5impoB
+e+BaEVLqFKUjU+EdvpE4WKB24K9lw35bfGhWd/cQwSaLIPezG1OK9M0JbpPoj5gg
+KBdwrS7EdOur64Ue774KPAFRYU8mEpnnQMKOnNUuSQKBgQDHfriFwjViBdRJGTZ1
+Wa/AgJ7rVU9yMhaUSifYbLQDrYRwagYNtA/NqTgRYl7BbHZpsYmglQoCAWJa+KK2
+xwJ/1uLsNG+I04lSQCsRr4/z23O6Vc2VzmMpru6Upa9mL8aNiUm2+7VJje/NnUW5
+OXiSxbYGchYbO7+sIo8UmSpm7wKBgQDHAFLoBZOeh1BGnD0YNH7WYJ4kH9IriFcB
+QqJL8yFjikbUU976yAyXNbMIVBMkCZoT/l7RvgwLufarKTzRHv88nCPwg8/sbQQT
+WP4TRcxS6zZj4y0VHN2i0KSldAYk1ohgLFXzAQzXVcMBzNNOdSSLtqRbPZo9Gd7U
+DYaHxRIVdQKBgQCFykaV7hk/FAm6vF35dZyYzanGyf/t5gmeid0PGFfh34zilzhY
+GFpA4yvm/MHvln4ThC14tHtxvNvphrYZPn4+ni6xmrjyWmvN7Zr00XkJYjPK06B8
+x11Zpyf6KOPo9EGEyn3Vahm6qqYYj1EjV5e1V0MsL3cD7J4vIz4x4ka9oQKBgQCN
+9tQuQ7Qw1rVU+ia3etO1Wc3XVYAYoDX5ZzDi37rFCSNIW+DppQceZCepXFkfT15E
+vyWjmWF8iBjJuCxzvxo0ges9rLsLHiZXKxhuZU/DI5t0nN9PfX07pn6ereuoIge+
+HELgjbI8eCkawqVIBleg+BW+JW9AAZGuU0vS1ar19QKBgQC3J57JCl6ZZzGKpFpU
+/9qYA0qFFRBIRddHlfelk7EBqg/6C6yEXAqNO+DcurdU6li+lEOKNSPAqii2MC+H
+XqCIdtbZcOX7pUSg5E8N883ruMjsaePTsvA5iEY5QvA8Mn47wpPikYEXQgFWoP+W
+UFlVhwe/E/ebjJZqyTiQaQcMPQ==
+-----END PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 237 (0xed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Root CA
+ Validity
+ Not Before: Jun 18 19:52:02 2013 GMT
+ Not After : Jun 16 19:52:02 2023 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:9b:13:c2:0b:60:42:13:87:db:a9:91:e9:fa:73:
+ ad:73:34:08:9b:46:7a:bb:d2:0e:e0:94:bc:ba:7c:
+ 7a:90:26:ca:c8:57:39:a3:e9:bf:b8:74:72:d8:50:
+ 0d:e6:bb:5d:e9:a9:9c:a8:2b:a6:0f:d4:c3:dd:69:
+ f7:61:db:72:2b:5b:8b:e8:eb:1b:71:0a:9a:90:93:
+ 4a:c0:11:af:b4:72:38:bb:13:ed:35:24:71:2f:3b:
+ 86:35:43:83:52:70:68:fa:db:c1:c3:7b:e7:49:aa:
+ 24:03:01:95:91:0e:02:28:76:52:ab:46:30:f5:5d:
+ 94:fd:d5:4d:14:b2:ec:ad:0e:f6:10:55:b1:08:a2:
+ c8:0c:74:47:af:5b:c1:6f:a9:60:21:0c:7e:9f:91:
+ be:10:e4:e7:56:c1:4c:46:9d:7d:3e:70:72:c8:35:
+ 53:09:bc:2a:0f:ab:e7:c3:41:6c:b8:03:fd:ae:46:
+ 21:67:ff:1f:4d:05:91:33:d8:0c:0d:b5:af:b2:e9:
+ ed:e9:9f:c8:19:13:db:e1:15:ea:9e:dc:9b:fd:b6:
+ 82:19:ce:ca:90:67:f7:c2:58:ae:85:2b:37:e4:c5:
+ 03:58:73:9b:dc:4a:a1:f1:f7:a5:6e:0e:1a:ab:6e:
+ 73:c5:76:97:10:7d:10:74:f1:88:38:dd:34:a2:a0:
+ a6:3b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 34:FF:09:84:DB:23:D0:1F:45:72:50:CE:79:28:D3:EF:FB:B1:46:07
+ X509v3 Authority Key Identifier:
+ keyid:2B:88:93:E1:D2:54:50:F4:B8:A4:20:BD:B1:79:E6:0B:AA:EB:EC:1A
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ a6:21:b1:53:7f:ec:a8:23:6f:76:d4:bd:0a:6a:67:a7:a8:9e:
+ 7d:08:38:62:cd:f4:34:d9:41:3a:02:a7:6d:31:7d:33:02:27:
+ ab:06:e6:01:c8:65:32:b5:f3:96:27:4c:5a:82:a5:84:a7:99:
+ 29:4a:b6:b9:57:41:75:a9:e2:a6:87:00:25:ff:5a:85:f6:68:
+ da:e1:5a:19:fb:91:5e:70:27:31:dd:9a:ac:20:9a:d6:27:1c:
+ 55:34:8c:f9:a4:97:ff:81:63:fb:b8:7d:71:d4:42:88:3c:10:
+ db:78:54:e3:42:b1:a4:83:81:b9:92:7a:f6:b2:f2:19:1c:b6:
+ 68:80:2b:14:5a:36:84:e0:67:ad:f9:e4:bc:a1:63:af:a1:13:
+ 13:95:3f:76:5e:2c:81:ed:7c:4a:38:04:bf:dc:03:b0:ca:8b:
+ d3:17:d8:fc:60:d9:83:31:9f:ef:be:a7:e3:05:4f:b4:3e:97:
+ 8a:6d:86:c5:69:ef:93:8c:1b:9e:6e:95:f7:1a:66:f7:1f:bf:
+ 5e:92:c4:ed:15:e1:2e:56:56:11:80:be:02:1d:96:fa:39:6a:
+ e0:dd:04:d7:98:e9:29:72:a5:60:f1:0e:14:5d:08:db:26:18:
+ 42:5b:f3:82:fb:79:83:48:1b:86:8d:9d:8b:5c:87:1a:23:ae:
+ bc:4c:13:46
+-----BEGIN CERTIFICATE-----
+MIIDdDCCAlygAwIBAgICAO0wDQYJKoZIhvcNAQEFBQAwFzEVMBMGA1UEAwwMVGVz
+dCBSb290IENBMB4XDTEzMDYxODE5NTIwMloXDTIzMDYxNjE5NTIwMlowYDELMAkG
+A1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWlu
+IFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJsTwgtgQhOH26mR6fpzrXM0CJtG
+ervSDuCUvLp8epAmyshXOaPpv7h0cthQDea7XempnKgrpg/Uw91p92Hbcitbi+jr
+G3EKmpCTSsARr7RyOLsT7TUkcS87hjVDg1JwaPrbwcN750mqJAMBlZEOAih2UqtG
+MPVdlP3VTRSy7K0O9hBVsQiiyAx0R69bwW+pYCEMfp+RvhDk51bBTEadfT5wcsg1
+Uwm8Kg+r58NBbLgD/a5GIWf/H00FkTPYDA21r7Lp7emfyBkT2+EV6p7cm/22ghnO
+ypBn98JYroUrN+TFA1hzm9xKofH3pW4OGqtuc8V2lxB9EHTxiDjdNKKgpjsCAwEA
+AaOBgDB+MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFDT/CYTbI9AfRXJQznko0+/7
+sUYHMB8GA1UdIwQYMBaAFCuIk+HSVFD0uKQgvbF55guq6+waMB0GA1UdJQQWMBQG
+CCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEB
+BQUAA4IBAQCmIbFTf+yoI2921L0KamenqJ59CDhizfQ02UE6AqdtMX0zAierBuYB
+yGUytfOWJ0xagqWEp5kpSra5V0F1qeKmhwAl/1qF9mja4VoZ+5FecCcx3ZqsIJrW
+JxxVNIz5pJf/gWP7uH1x1EKIPBDbeFTjQrGkg4G5knr2svIZHLZogCsUWjaE4Get
++eS8oWOvoRMTlT92XiyB7XxKOAS/3AOwyovTF9j8YNmDMZ/vvqfjBU+0PpeKbYbF
+ae+TjBuebpX3Gmb3H79eksTtFeEuVlYRgL4CHZb6OWrg3QTXmOkpcqVg8Q4UXQjb
+JhhCW/OC+3mDSBuGjZ2LXIcaI668TBNG
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem
new file mode 100644
index 00000000000..d5711bd5c19
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-1024-rsa-intermediate.pem
@@ -0,0 +1,44 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 242 (0xf2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=1024 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ EC Public Key:
+ pub:
+ 04:c6:da:96:04:72:10:1a:ac:92:fd:d6:23:1e:c5:
+ cc:92:2e:09:2e:76:39:a4:d5:ca:e3:c1:2d:e4:8e:
+ b1:84:90:9d:6e:6a:ca:46:48:22:80:50:ed:80:83:
+ b8:43:96:c7:be:77:a8:23:f3:bf:f4:0f:c6:78:55:
+ 8f:0c:9e:6d:a6
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 54:45:00:a1:6b:41:42:0b:5f:ac:23:75:06:2c:58:e7:46:05:
+ 1e:f1:f6:cb:37:e6:d2:3d:84:db:0c:b8:fd:4d:d6:f5:13:ff:
+ 1a:22:14:01:fe:ff:09:8d:e3:fd:64:68:12:7e:d1:ae:31:cb:
+ c4:17:99:fe:20:1e:68:7c:d7:d6:93:f6:2e:88:d0:89:34:8e:
+ a6:59:17:1e:4f:2a:53:69:9a:46:5a:80:91:65:47:ca:17:87:
+ 5e:2d:b8:41:bf:05:fa:80:fb:57:20:23:d6:99:5e:7e:5d:bf:
+ bc:6d:0b:83:86:53:bb:28:f1:4a:83:3c:32:14:06:00:ce:cc:
+ 62:d7
+-----BEGIN CERTIFICATE-----
+MIIBxjCCAS8CAgDyMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTEwMjQgcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1NFoXDTIxMTIwOTIy
+NDc1NFowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMbalgRyEBqskv3WIx7F
+zJIuCS52OaTVyuPBLeSOsYSQnW5qykZIIoBQ7YCDuEOWx753qCPzv/QPxnhVjwye
+baajEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADgYEAVEUAoWtB
+QgtfrCN1BixY50YFHvH2yzfm0j2E2wy4/U3W9RP/GiIUAf7/CY3j/WRoEn7RrjHL
+xBeZ/iAeaHzX1pP2LojQiTSOplkXHk8qU2maRlqAkWVHyheHXi24Qb8F+oD7VyAj
+1plefl2/vG0Lg4ZTuyjxSoM8MhQGAM7MYtc=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-2048-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-2048-rsa-intermediate.pem
new file mode 100644
index 00000000000..aea77ee3ce0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-2048-rsa-intermediate.pem
@@ -0,0 +1,54 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 242 (0xf2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ EC Public Key:
+ pub:
+ 04:c6:80:6a:45:dd:d9:c1:83:91:b8:9f:d6:59:cb:
+ 08:0d:5b:b1:b3:52:83:4d:e8:68:61:3e:df:df:82:
+ 7c:84:63:ce:08:a9:8f:04:20:81:8d:ad:c0:80:25:
+ 72:0e:b3:f3:06:fe:7c:46:0d:4d:cd:9f:0a:72:5b:
+ a5:7b:ee:f2:31
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 17:05:5c:7d:c4:c7:07:d1:08:09:fd:8f:01:29:8e:91:cd:de:
+ d8:f2:22:66:86:63:b1:8b:fb:41:f9:46:31:f5:24:3f:19:a4:
+ 4b:40:4e:ef:e6:1a:5f:90:24:3d:04:3b:ae:42:17:be:a4:1d:
+ 0c:8d:b1:e3:8f:05:fd:d7:b5:a7:c1:ee:b6:97:72:1a:25:86:
+ 61:dc:31:11:81:af:20:3c:4a:c0:b1:ff:03:23:8c:7c:2b:94:
+ e0:4b:25:74:7c:13:5e:0e:a7:72:e4:8e:a1:27:86:c4:ea:b1:
+ a3:b7:f3:80:b6:5f:76:91:6b:04:d5:55:96:01:35:10:a6:33:
+ 4f:cc:ea:2f:d2:f1:fc:a4:a1:14:77:1f:61:a9:a1:c4:b5:90:
+ bb:73:c1:ed:bb:63:47:a7:e8:27:a3:8e:27:88:c9:7e:dc:00:
+ 76:44:2e:89:a7:b0:ef:9f:bb:f2:58:e1:c5:01:7b:b1:a0:b2:
+ dc:ce:c7:cf:a7:5d:0d:37:b8:86:4a:5a:61:9c:59:98:ef:4d:
+ af:61:35:de:ed:5b:b2:94:16:7f:3d:2a:96:87:9e:63:0b:0d:
+ 80:ac:36:1b:ac:bf:2f:c8:4b:be:c2:6d:ea:8b:7b:e3:8f:b5:
+ d6:62:0d:dc:c8:17:d4:eb:78:40:a4:9e:95:e7:38:75:c0:31:
+ 64:2b:ad:d8
+-----BEGIN CERTIFICATE-----
+MIICRzCCAS8CAgDyMA0GCSqGSIb3DQEBBQUAMCgxJjAkBgNVBAMMHTIwNDggcnNh
+IFRlc3QgaW50ZXJtZWRpYXRlIENBMB4XDTExMTIxMjIyNDc1NFoXDTIxMTIwOTIy
+NDc1NFowYDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNV
+BAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEy
+Ny4wLjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMaAakXd2cGDkbif1lnL
+CA1bsbNSg03oaGE+39+CfIRjzgipjwQggY2twIAlcg6z8wb+fEYNTc2fCnJbpXvu
+8jGjEzARMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBABcFXH3E
+xwfRCAn9jwEpjpHN3tjyImaGY7GL+0H5RjH1JD8ZpEtATu/mGl+QJD0EO65CF76k
+HQyNseOPBf3XtafB7raXcholhmHcMRGBryA8SsCx/wMjjHwrlOBLJXR8E14Op3Lk
+jqEnhsTqsaO384C2X3aRawTVVZYBNRCmM0/M6i/S8fykoRR3H2GpocS1kLtzwe27
+Y0en6CejjieIyX7cAHZELomnsO+fu/JY4cUBe7GgstzOx8+nXQ03uIZKWmGcWZjv
+Ta9hNd7tW7KUFn89KpaHnmMLDYCsNhusvy/IS77CbeqLe+OPtdZiDdzIF9TreECk
+npXnOHXAMWQrrdg=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-768-rsa-intermediate.pem b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-768-rsa-intermediate.pem
new file mode 100644
index 00000000000..434b93f4d9c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-768-rsa-intermediate.pem
@@ -0,0 +1,41 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 242 (0xf2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=768 rsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:54 2011 GMT
+ Not After : Dec 9 22:47:54 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ EC Public Key:
+ pub:
+ 04:bd:53:19:05:f9:a1:53:38:f9:94:53:38:c3:5f:
+ 65:91:ca:00:98:ff:8c:78:cb:a9:ce:a7:f4:ac:74:
+ 48:94:0a:6d:8e:6e:12:16:0c:ba:fb:4d:39:6f:75:
+ 96:0e:f6:6a:ab:e9:9b:a4:6f:3b:35:fc:ad:dc:6b:
+ 12:97:1e:de:79
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 5a:27:e9:f3:8a:ea:29:4b:32:0d:27:de:e4:c8:94:d4:72:1e:
+ b9:97:f6:a1:d1:13:df:fc:7d:98:1e:5e:bc:05:0b:24:9a:0a:
+ 4c:cc:5f:66:8a:7a:51:18:42:48:b1:1b:56:1b:46:bf:9b:56:
+ bd:c5:46:54:da:c1:7f:00:2c:c5:f1:e9:24:da:1e:19:83:96:
+ 5f:df:71:15:61:73:8e:6c:ca:3d:50:e7:de:2d:4a:ce:9a:a1:
+ 44:51:64:0e:b0:6a
+-----BEGIN CERTIFICATE-----
+MIIBpDCCAS4CAgDyMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNVBAMMHDc2OCByc2Eg
+VGVzdCBpbnRlcm1lZGlhdGUgQ0EwHhcNMTExMjEyMjI0NzU0WhcNMjExMjA5MjI0
+NzU0WjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE
+BwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3
+LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvVMZBfmhUzj5lFM4w19l
+kcoAmP+MeMupzqf0rHRIlAptjm4SFgy6+005b3WWDvZqq+mbpG87Nfyt3GsSlx7e
+eaMTMBEwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAANhAFon6fOK6ilL
+Mg0n3uTIlNRyHrmX9qHRE9/8fZgeXrwFCySaCkzMX2aKelEYQkixG1YbRr+bVr3F
+RlTawX8ALMXx6STaHhmDll/fcRVhc45syj1Q594tSs6aoURRZA6wag==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-prime256v1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-prime256v1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..6e00ebfd886
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-ee-by-prime256v1-ecdsa-intermediate.pem
@@ -0,0 +1,39 @@
+Certificate:
+ Data:
+ Version: 1 (0x0)
+ Serial Number: 242 (0xf2)
+ Signature Algorithm: ecdsa-with-SHA1
+ Issuer: CN=prime256v1 ecdsa Test intermediate CA
+ Validity
+ Not Before: Dec 12 22:47:55 2011 GMT
+ Not After : Dec 9 22:47:55 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ EC Public Key:
+ pub:
+ 04:79:bf:01:56:fb:d3:32:5c:95:eb:7f:78:d3:ec:
+ bc:4d:df:ae:5f:58:ed:c1:c8:21:a8:23:96:6d:37:
+ b1:ea:21:a6:3e:4a:2e:36:1d:d9:3f:ff:b2:8d:36:
+ 10:02:44:38:8a:a7:63:d0:b1:89:6b:2e:da:e2:a9:
+ f2:c6:fa:66:a5
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: ecdsa-with-SHA1
+ 30:46:02:21:00:8d:99:02:83:bf:bd:8d:45:06:7f:1b:3e:e3:
+ 61:31:3e:93:ca:27:62:f3:7a:6f:1e:b6:6b:67:26:c4:ee:95:
+ e9:02:21:00:d6:34:24:c3:53:8e:56:92:0a:fb:2b:bf:b2:33:
+ 3a:2d:7a:23:7e:a8:d3:d1:67:42:e0:b0:4e:c6:01:b6:a0:8a
+-----BEGIN CERTIFICATE-----
+MIIBjTCCATMCAgDyMAkGByqGSM49BAEwMDEuMCwGA1UEAwwlcHJpbWUyNTZ2MSBl
+Y2RzYSBUZXN0IGludGVybWVkaWF0ZSBDQTAeFw0xMTEyMTIyMjQ3NTVaFw0yMTEy
+MDkyMjQ3NTVaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYw
+FAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQD
+DAkxMjcuMC4wLjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAR5vwFW+9MyXJXr
+f3jT7LxN365fWO3ByCGoI5ZtN7HqIaY+Si42Hdk//7KNNhACRDiKp2PQsYlrLtri
+qfLG+maloxMwETAPBgNVHREECDAGhwR/AAABMAkGByqGSM49BAEDSQAwRgIhAI2Z
+AoO/vY1FBn8bPuNhMT6Tyidi83pvHrZrZybE7pXpAiEA1jQkw1OOVpIK+yu/sjM6
+LXojfqjT0WdC4LBOxgG2oIo=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/prime256v1-ecdsa-intermediate.pem b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-intermediate.pem
new file mode 100644
index 00000000000..589c001cf7b
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/prime256v1-ecdsa-intermediate.pem
@@ -0,0 +1,58 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 242 (0xf2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=2048 RSA Test Root CA
+ Validity
+ Not Before: Dec 12 22:47:53 2011 GMT
+ Not After : Dec 9 22:47:53 2021 GMT
+ Subject: CN=prime256v1 ecdsa Test intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ EC Public Key:
+ pub:
+ 04:d1:35:14:53:74:2f:e1:e4:9b:41:9e:42:9d:10:
+ 6b:0b:f4:16:8f:bc:a7:c7:a4:39:09:73:34:cb:87:
+ df:2f:7e:4a:5f:b1:b5:e4:dc:49:41:4e:a8:81:34:
+ b5:da:7d:27:7d:05:c1:bd:0a:29:6d:ad:a3:5d:37:
+ 7b:56:b7:1b:60
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 95:31:9C:D4:B9:C2:F8:A6:08:71:5F:3A:17:F7:2C:1E:A9:AD:46:41
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 22:38:c0:f9:09:fc:7b:2d:d3:31:8e:eb:7d:bb:b9:78:b7:89:
+ f4:7a:85:b4:f8:0f:2a:1b:a5:c3:53:f1:55:a4:bd:a3:dd:d2:
+ 2d:e5:af:63:bd:fc:eb:6d:95:90:7a:de:1e:ed:fa:a9:b4:df:
+ 82:f1:22:10:3a:b9:c7:a1:23:d0:9f:2a:60:81:61:83:a0:e6:
+ 2b:de:b7:a0:4e:f6:81:a7:8f:02:c7:14:06:16:f1:9e:85:c9:
+ 7d:7d:f3:31:aa:78:cd:a9:a8:81:5b:e4:26:2f:fa:93:e7:6a:
+ 6e:a8:2f:1b:0a:b9:20:cc:f0:93:89:81:43:58:c8:b6:0a:40:
+ d3:24:b9:e9:c6:64:93:13:16:9c:0b:e8:bd:77:91:6e:96:bf:
+ 22:85:db:ba:88:5b:db:18:9a:5d:10:5d:45:07:ba:99:9b:60:
+ c0:30:4b:41:64:20:03:fa:97:94:82:59:5b:85:e6:f3:be:a5:
+ e8:c9:52:11:1a:62:e1:f0:24:7e:47:30:e2:e2:8d:8c:5b:84:
+ 10:ec:cc:f5:d2:4c:9e:47:ea:20:66:20:31:0f:8b:03:77:b3:
+ b6:54:9f:b4:7a:60:6e:1a:63:47:9f:b4:00:b7:7c:35:be:58:
+ e3:8b:22:0e:3d:79:b9:6f:0e:4f:05:06:1a:16:e1:b9:92:1c:
+ ea:f5:ef:9d
+-----BEGIN CERTIFICATE-----
+MIICQzCCASugAwIBAgICAPIwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UEAwwVMjA0
+OCBSU0EgVGVzdCBSb290IENBMB4XDTExMTIxMjIyNDc1M1oXDTIxMTIwOTIyNDc1
+M1owMDEuMCwGA1UEAwwlcHJpbWUyNTZ2MSBlY2RzYSBUZXN0IGludGVybWVkaWF0
+ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNE1FFN0L+Hkm0GeQp0Qawv0
+Fo+8p8ekOQlzNMuH3y9+Sl+xteTcSUFOqIE0tdp9J30Fwb0KKW2to103e1a3G2Cj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJUxnNS5wvimCHFfOhf3LB6p
+rUZBMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAIjjA+Qn8ey3T
+MY7rfbu5eLeJ9HqFtPgPKhulw1PxVaS9o93SLeWvY738622VkHreHu36qbTfgvEi
+EDq5x6Ej0J8qYIFhg6DmK963oE72gaePAscUBhbxnoXJfX3zMap4zamogVvkJi/6
+k+dqbqgvGwq5IMzwk4mBQ1jItgpA0yS56cZkkxMWnAvovXeRbpa/IoXbuohb2xia
+XRBdRQe6mZtgwDBLQWQgA/qXlIJZW4Xm876l6MlSERpi4fAkfkcw4uKNjFuEEOzM
+9dJMnkfqIGYgMQ+LA3eztlSftHpgbhpjR5+0ALd8Nb5Y44siDj15uW8OTwUGGhbh
+uZIc6vXvnQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/punycodetest.der b/chromium/net/data/ssl/certificates/punycodetest.der
new file mode 100644
index 00000000000..b19618f5c79
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/punycodetest.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/quic_intermediate.crt b/chromium/net/data/ssl/certificates/quic_intermediate.crt
new file mode 100644
index 00000000000..ca1e6f6f13e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/quic_intermediate.crt
@@ -0,0 +1,53 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Acme Co, CN=Root CA
+ Validity
+ Not Before: Jan 1 10:00:00 2013 GMT
+ Not After : Dec 31 10:00:00 2023 GMT
+ Subject: O=Acme Co, CN=Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:af:95:dd:a0:eb:d7:c3:ba:a6:ae:db:6e:05:68:
+ a0:00:15:a1:85:d1:89:ba:be:3a:7a:3b:8c:3b:41:
+ 07:76:63:71:28:f7:bf:a5:fb:b3:28:94:f9:9a:de:
+ 1d:03:00:ce:5e:25:06:6a:e6:c7:0a:6b:6d:d3:76:
+ 95:57:f5:16:f8:f0:43:de:b7:c7:1b:0b:83:f4:70:
+ e6:29:a1:8d:22:12:9a:df:4b:31:e8:9b:86:7d:95:
+ 29:97:18:c1:34:2f:b6:a7:c1:c7:46:d6:9c:c6:a6:
+ ae:6e:dd:8f:be:c2:ec:02:00:d2:54:f6:0f:a0:cc:
+ af:04:85:65:98:a1:ea:73:f1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 9a:68:79:17:6c:13:20:b3:5f:01:ca:ae:c0:bf:d2:7b:98:bf:
+ dd:4d:d1:c3:a5:ab:01:47:2e:c8:61:b4:f5:1d:55:04:f0:eb:
+ 5d:84:5a:78:09:b0:f1:42:64:14:e8:9e:ba:c3:38:32:d3:16:
+ fe:e1:65:1f:76:da:e4:c0:83:62:4a:ae:d0:4e:00:2e:38:52:
+ 91:81:62:94:b0:3d:69:b3:87:72:39:55:94:9e:ca:2c:ca:51:
+ 3c:d3:3f:d2:1c:92:d3:de:df:ba:bc:45:9b:30:99:b4:39:f8:
+ 17:55:94:7d:3a:ba:0e:e9:3f:2d:bc:f0:ea:6d:17:85:23:e4:
+ ca:94
+-----BEGIN CERTIFICATE-----
+MIIB+DCCAWOgAwIBAgIBAjALBgkqhkiG9w0BAQUwJDEQMA4GA1UEChMHQWNtZSBD
+bzEQMA4GA1UEAxMHUm9vdCBDQTAeFw0xMzAxMDExMDAwMDBaFw0yMzEyMzExMDAw
+MDBaMCwxEDAOBgNVBAoTB0FjbWUgQ28xGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBD
+QTCBnTALBgkqhkiG9w0BAQEDgY0AMIGJAoGBAK+V3aDr18O6pq7bbgVooAAVoYXR
+ibq+Ono7jDtBB3ZjcSj3v6X7syiU+ZreHQMAzl4lBmrmxwprbdN2lVf1FvjwQ963
+xxsLg/Rw5imhjSISmt9LMeibhn2VKZcYwTQvtqfBx0bWnMamrm7dj77C7AIA0lT2
+D6DMrwSFZZih6nPxAgMBAAGjODA2MA4GA1UdDwEB/wQEAwIABDATBgNVHSUEDDAK
+BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MAsGCSqGSIb3DQEBBQOBgQCaaHkX
+bBMgs18Byq7Av9J7mL/dTdHDpasBRy7IYbT1HVUE8OtdhFp4CbDxQmQU6J66wzgy
+0xb+4WUfdtrkwINiSq7QTgAuOFKRgWKUsD1ps4dyOVWUnsosylE80z/SHJLT3t+6
+vEWbMJm0OfgXVZR9OroO6T8tvPDqbReFI+TKlA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/quic_root.crt b/chromium/net/data/ssl/certificates/quic_root.crt
new file mode 100644
index 00000000000..55502e6b94e
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/quic_root.crt
@@ -0,0 +1,106 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Acme Co, CN=Root CA
+ Validity
+ Not Before: Jan 1 10:00:00 2013 GMT
+ Not After : Dec 31 10:00:00 2023 GMT
+ Subject: O=Acme Co, CN=Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:f3:8f:b5:01:f7:8f:bf:0e:c3:bc:2e:43:f9:63:
+ 32:ae:e2:70:2d:77:70:bf:32:57:77:dd:00:f4:16:
+ 08:e2:f4:b8:b4:c9:bc:41:be:54:ba:44:3f:6f:77:
+ f9:d1:1b:52:25:16:7d:df:f9:29:79:3c:7c:8f:16:
+ e3:85:d5:7c:96:5e:2e:60:b3:80:e1:fc:09:b9:04:
+ 4d:ff:bc:05:25:55:96:b8:e7:7e:03:ed:f4:a1:93:
+ 54:66:b6:d5:e4:1f:92:94:52:7d:c3:60:89:5f:79:
+ f9:63:d1:f4:bb:4d:fa:da:4d:2e:d2:1d:ac:dc:7a:
+ 4f:52:67:3f:ad:eb:ed:ba:cd
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 7c:0f:20:54:85:ea:e9:68:c5:15:fc:10:8a:09:98:0e:51:53:
+ 7a:a3:37:c3:ec:8d:61:2c:49:66:d0:34:0b:8b:68:50:58:75:
+ 1f:fb:76:87:89:16:7d:56:d1:be:2f:bb:ef:95:26:92:55:37:
+ 6f:ca:82:e2:d4:93:33:80:1f:9c:b9:2d:1e:ee:3b:90:7d:13:
+ 2e:28:9b:17:8c:15:5f:12:eb:ed:f2:86:2f:a5:f5:59:e4:f3:
+ 07:a9:99:2d:32:70:d4:2a:d0:43:f2:1c:92:6d:75:f8:60:fa:
+ b5:8f:4f:07:6b:f6:c0:80:b3:4f:c8:9f:ed:11:bd:4d:d9:d7:
+ 4a:2c
+-----BEGIN CERTIFICATE-----
+MIIB8DCCAVugAwIBAgIBATALBgkqhkiG9w0BAQUwJDEQMA4GA1UEChMHQWNtZSBD
+bzEQMA4GA1UEAxMHUm9vdCBDQTAeFw0xMzAxMDExMDAwMDBaFw0yMzEyMzExMDAw
+MDBaMCQxEDAOBgNVBAoTB0FjbWUgQ28xEDAOBgNVBAMTB1Jvb3QgQ0EwgZ0wCwYJ
+KoZIhvcNAQEBA4GNADCBiQKBgQDzj7UB94+/DsO8LkP5YzKu4nAtd3C/Mld33QD0
+Fgji9Li0ybxBvlS6RD9vd/nRG1IlFn3f+Sl5PHyPFuOF1XyWXi5gs4Dh/Am5BE3/
+vAUlVZa4534D7fShk1RmttXkH5KUUn3DYIlfeflj0fS7TfraTS7SHazcek9SZz+t
+6+26zQIDAQABozgwNjAOBgNVHQ8BAf8EBAMCAAQwEwYDVR0lBAwwCgYIKwYBBQUH
+AwEwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQUDgYEAfA8gVIXq6WjFFfwQ
+igmYDlFTeqM3w+yNYSxJZtA0C4toUFh1H/t2h4kWfVbRvi+775UmklU3b8qC4tST
+M4AfnLktHu47kH0TLiibF4wVXxLr7fKGL6X1WeTzB6mZLTJw1CrQQ/Ickm11+GD6
+tY9PB2v2wICzT8if7RG9TdnXSiw=
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Acme Co, CN=Root CA
+ Validity
+ Not Before: Jan 1 10:00:00 2013 GMT
+ Not After : Dec 31 10:00:00 2023 GMT
+ Subject: O=Acme Co, CN=Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:f3:8f:b5:01:f7:8f:bf:0e:c3:bc:2e:43:f9:63:
+ 32:ae:e2:70:2d:77:70:bf:32:57:77:dd:00:f4:16:
+ 08:e2:f4:b8:b4:c9:bc:41:be:54:ba:44:3f:6f:77:
+ f9:d1:1b:52:25:16:7d:df:f9:29:79:3c:7c:8f:16:
+ e3:85:d5:7c:96:5e:2e:60:b3:80:e1:fc:09:b9:04:
+ 4d:ff:bc:05:25:55:96:b8:e7:7e:03:ed:f4:a1:93:
+ 54:66:b6:d5:e4:1f:92:94:52:7d:c3:60:89:5f:79:
+ f9:63:d1:f4:bb:4d:fa:da:4d:2e:d2:1d:ac:dc:7a:
+ 4f:52:67:3f:ad:eb:ed:ba:cd
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 7c:0f:20:54:85:ea:e9:68:c5:15:fc:10:8a:09:98:0e:51:53:
+ 7a:a3:37:c3:ec:8d:61:2c:49:66:d0:34:0b:8b:68:50:58:75:
+ 1f:fb:76:87:89:16:7d:56:d1:be:2f:bb:ef:95:26:92:55:37:
+ 6f:ca:82:e2:d4:93:33:80:1f:9c:b9:2d:1e:ee:3b:90:7d:13:
+ 2e:28:9b:17:8c:15:5f:12:eb:ed:f2:86:2f:a5:f5:59:e4:f3:
+ 07:a9:99:2d:32:70:d4:2a:d0:43:f2:1c:92:6d:75:f8:60:fa:
+ b5:8f:4f:07:6b:f6:c0:80:b3:4f:c8:9f:ed:11:bd:4d:d9:d7:
+ 4a:2c
+-----BEGIN CERTIFICATE-----
+MIIB8DCCAVugAwIBAgIBATALBgkqhkiG9w0BAQUwJDEQMA4GA1UEChMHQWNtZSBD
+bzEQMA4GA1UEAxMHUm9vdCBDQTAeFw0xMzAxMDExMDAwMDBaFw0yMzEyMzExMDAw
+MDBaMCQxEDAOBgNVBAoTB0FjbWUgQ28xEDAOBgNVBAMTB1Jvb3QgQ0EwgZ0wCwYJ
+KoZIhvcNAQEBA4GNADCBiQKBgQDzj7UB94+/DsO8LkP5YzKu4nAtd3C/Mld33QD0
+Fgji9Li0ybxBvlS6RD9vd/nRG1IlFn3f+Sl5PHyPFuOF1XyWXi5gs4Dh/Am5BE3/
+vAUlVZa4534D7fShk1RmttXkH5KUUn3DYIlfeflj0fS7TfraTS7SHazcek9SZz+t
+6+26zQIDAQABozgwNjAOBgNVHQ8BAf8EBAMCAAQwEwYDVR0lBAwwCgYIKwYBBQUH
+AwEwDwYDVR0TAQH/BAUwAwEB/zALBgkqhkiG9w0BAQUDgYEAfA8gVIXq6WjFFfwQ
+igmYDlFTeqM3w+yNYSxJZtA0C4toUFh1H/t2h4kWfVbRvi+775UmklU3b8qC4tST
+M4AfnLktHu47kH0TLiibF4wVXxLr7fKGL6X1WeTzB6mZLTJw1CrQQ/Ickm11+GD6
+tY9PB2v2wICzT8if7RG9TdnXSiw=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/quic_test.example.com.crt b/chromium/net/data/ssl/certificates/quic_test.example.com.crt
new file mode 100644
index 00000000000..375cedd0339
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/quic_test.example.com.crt
@@ -0,0 +1,56 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 3 (0x3)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Acme Co, CN=Intermediate CA
+ Validity
+ Not Before: Jan 1 10:00:00 2013 GMT
+ Not After : Dec 31 10:00:00 2023 GMT
+ Subject: O=Acme Co, CN=Leaf certificate
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:c1:31:32:b2:28:dc:0d:a4:e0:4b:54:d6:fa:b7:
+ d2:0c:45:29:bf:67:c7:d1:b8:a9:90:63:51:c4:96:
+ 9f:86:a9:47:d7:67:f6:f9:1d:37:29:c2:0a:55:a7:
+ 8c:29:97:dc:f2:7f:f4:97:d0:d5:44:c9:04:1c:48:
+ ea:cc:a9:48:5c:eb:69:11:75:6e:db:7d:1a:5a:c0:
+ 9f:ad:a7:b8:0e:3b:a1:61:24:24:6f:64:84:ad:bb:
+ 28:06:c2:4a:c8:07:7b:46:33:8a:c7:81:77:92:4f:
+ 9d:88:1c:52:04:23:61:12:97:c7:e4:af:90:67:7e:
+ fb:ac:3d:23:92:f0:c9:39:6d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Alternative Name:
+ DNS:test.example.com
+ Signature Algorithm: sha1WithRSAEncryption
+ ad:33:55:2a:80:4c:ab:bc:b3:34:f7:b3:7e:fb:05:a8:11:3f:
+ a1:35:56:4c:46:2f:8d:24:70:35:3a:66:8d:14:c4:fb:7f:d9:
+ 76:de:c4:52:a7:42:8f:70:1d:fd:d5:33:04:69:5d:3c:18:03:
+ 8f:db:19:d0:14:d8:1c:0b:b6:74:9c:cf:41:ba:24:d9:c4:c3:
+ cf:86:fb:15:3d:c4:99:ea:af:6a:29:34:ed:97:03:38:ed:38:
+ b3:21:39:a0:f0:16:ac:81:d3:88:52:d8:5e:a3:6d:e6:ec:3f:
+ e9:20:ac:d3:78:7b:ae:59:9e:5d:3b:5e:61:bb:43:88:cd:8e:
+ d0:0d
+-----BEGIN CERTIFICATE-----
+MIICGzCCAYagAwIBAgIBAzALBgkqhkiG9w0BAQUwLDEQMA4GA1UEChMHQWNtZSBD
+bzEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMB4XDTEzMDEwMTEwMDAwMFoXDTIz
+MTIzMTEwMDAwMFowLTEQMA4GA1UEChMHQWNtZSBDbzEZMBcGA1UEAxMQTGVhZiBj
+ZXJ0aWZpY2F0ZTCBnTALBgkqhkiG9w0BAQEDgY0AMIGJAoGBAMExMrIo3A2k4EtU
+1vq30gxFKb9nx9G4qZBjUcSWn4apR9dn9vkdNynCClWnjCmX3PJ/9JfQ1UTJBBxI
+6sypSFzraRF1btt9GlrAn62nuA47oWEkJG9khK27KAbCSsgHe0YziseBd5JPnYgc
+UgQjYRKXx+SvkGd++6w9I5LwyTltAgMBAAGjUjBQMA4GA1UdDwEB/wQEAwIAoDAT
+BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBsGA1UdEQQUMBKCEHRl
+c3QuZXhhbXBsZS5jb20wCwYJKoZIhvcNAQEFA4GBAK0zVSqATKu8szT3s377BagR
+P6E1VkxGL40kcDU6Zo0UxPt/2XbexFKnQo9wHf3VMwRpXTwYA4/bGdAU2BwLtnSc
+z0G6JNnEw8+G+xU9xJnqr2opNO2XAzjtOLMhOaDwFqyB04hS2F6jbebsP+kgrNN4
+e65Znl07XmG7Q4jNjtAN
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/quic_test_ecc.example.com.crt b/chromium/net/data/ssl/certificates/quic_test_ecc.example.com.crt
new file mode 100644
index 00000000000..0e6bfbaa8c2
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/quic_test_ecc.example.com.crt
@@ -0,0 +1,50 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4 (0x4)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Acme Co, CN=Intermediate CA
+ Validity
+ Not Before: Jan 1 10:00:00 2013 GMT
+ Not After : Dec 31 10:00:00 2023 GMT
+ Subject: O=Acme Co, CN=ECDSA Leaf certificate
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ Public-Key: (256 bit)
+ pub:
+ 04:6d:48:d0:30:76:bb:bf:91:b1:d7:03:c2:fc:95:
+ 9b:e0:ea:42:ed:43:2c:a6:b2:23:c4:52:33:93:95:
+ 25:fc:16:75:83:9e:0f:0f:91:a5:47:b1:21:91:d4:
+ 94:94:30:b8:00:dc:1c:79:2c:fa:72:99:62:b2:fa:
+ af:b0:ca:f2:42
+ ASN1 OID: prime256v1
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Alternative Name:
+ DNS:test.example.com
+ Signature Algorithm: sha1WithRSAEncryption
+ 5d:23:47:b4:b5:0f:38:18:cd:63:90:24:37:f1:da:67:66:a4:
+ fe:8d:53:3f:c5:a7:10:e6:21:a3:1d:b7:42:b0:1a:e7:d7:83:
+ 3d:ea:7b:6b:89:85:bb:13:77:4d:45:ab:b2:e7:1e:ac:6e:74:
+ b6:9f:c4:e0:76:1c:e4:13:e9:6c:b1:20:a3:34:e8:1e:8a:71:
+ 51:cb:00:44:71:64:f6:4b:9e:9a:2d:d9:9a:44:62:f5:8c:3c:
+ c5:ec:c1:1c:d5:bb:05:53:33:af:70:44:1d:5b:aa:23:67:30:
+ 3e:d3:a9:5e:a2:57:84:86:aa:be:bd:7b:4f:74:d9:3b:cd:2e:
+ 7e:d1
+-----BEGIN CERTIFICATE-----
+MIIB3DCCAUegAwIBAgIBBDALBgkqhkiG9w0BAQUwLDEQMA4GA1UEChMHQWNtZSBD
+bzEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlIENBMB4XDTEzMDEwMTEwMDAwMFoXDTIz
+MTIzMTEwMDAwMFowMzEQMA4GA1UEChMHQWNtZSBDbzEfMB0GA1UEAxMWRUNEU0Eg
+TGVhZiBjZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABG1I0DB2
+u7+RsdcDwvyVm+DqQu1DLKayI8RSM5OVJfwWdYOeDw+RpUexIZHUlJQwuADcHHks
++nKZYrL6r7DK8kKjUjBQMA4GA1UdDwEB/wQEAwIAgDATBgNVHSUEDDAKBggrBgEF
+BQcDATAMBgNVHRMBAf8EAjAAMBsGA1UdEQQUMBKCEHRlc3QuZXhhbXBsZS5jb20w
+CwYJKoZIhvcNAQEFA4GBAF0jR7S1DzgYzWOQJDfx2mdmpP6NUz/FpxDmIaMdt0Kw
+GufXgz3qe2uJhbsTd01Fq7LnHqxudLafxOB2HOQT6WyxIKM06B6KcVHLAERxZPZL
+npot2ZpEYvWMPMXswRzVuwVTM69wRB1bqiNnMD7TqV6iV4SGqr69e0902TvNLn7R
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/redundant-server-chain.pem b/chromium/net/data/ssl/certificates/redundant-server-chain.pem
new file mode 100644
index 00000000000..1411d1ce7fe
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/redundant-server-chain.pem
@@ -0,0 +1,271 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA6NE6YtvtLz7IsraneD9Z/cQ+NURvx3bIYcdmCUvV4dklQnW4
+vpBqRioqElU9EyUY1cgTotOaliOIectRKGB7M9A5v4LWDeZHoifDcx+tpKms2EuY
+mm9AOs35Xbi4Q95CILTvV3OdHez6l++sz+8Ctc361ugd1gq+wKmRn91Qq7k90piC
+spanoHZEx+3BZJMkD5yVGX6c1KUcUQ2YkNH0sOKZ6Ed1v/TQFTQIIXPHs2WLQD/X
+lQEaFOnhKeBF2VGwML170j3xfF/2icoLzWGBNg2pRK9bOziSHfEvOOw7AQ4Ya5ys
+/XC/NuRHc1eGrGBv5rFH/1gu0xIoileLycCGDQIDAQABAoIBADnrU0sky2zlgah0
+KFWR7SFkoNU/oU9ODavFn2zQoPT+wHY4My21X7r04mKNMhSBNhx5Gel4Gw0e6eTi
+393bosrREozCT95FW6zLl6QcTWaZj5Z/uAczhhcbBt56Bd1cfbcFTEXFTWEUg4Mo
+7SUNoO75v12XgVSud6YWiVPsCxWtFmiE91pZINfOM0rBacASY7F+/jJwhlmt04Ru
+qwyG2bWmVB97cj2IcNFMwrH/3vbml5YttTKEb1wlKodrj8BqzOcP5VW3DgXbjbz8
+gZtP+pjpP5e9F6UcmwXek12dGwqp+9Mo8veidI8dXNSEiHpdFiTOolsLTGVXziVP
++spFbAECgYEA96mAIB4RidCH2wYtKoepiGisJmFvebGFpyBUWjKtDFKg0ufJ3S/3
+HFq5Pn8473ii8qVjBwlin/bs9dOEuwEI2lvM39QeoUASVdfLOHoYKsK8dyFo7rTX
+bQdwMQpSqYbJyi5OElHH/Z28celhF522Lt73uKVOFfiyGYlGzjApZV0CgYEA8KfJ
+QCxA27mWTzzHjYtTEGlYskbGpV0XNhjSoCueJvqE8+FYYylSpIB7+yUUI/5g5Iau
+aQlVFt+c2IyG5Fg+k7rc0arFkRm8HGp8df9aE7xdHwdw5BL/6wQDfFuP4sDIWVab
+IdJDUgdp+G4OGKcSgVCBbIfMrlKll/fMqBWxaHECgYEAv0mZH7V5wGNje2VC33WX
+GTgXtzFMw8a8v4A2BtDbXgg4FY5YGVJh3/Gm4MGs/THFUfsyCI5UMc+r6JduDm5X
+IykCjeMtoUh2oP0jBsUvA2AT50PT44OkXJ8BJa+edzgXheTMAlROTvJVSfqDNpVm
+0L8AwQpUzJ2hGh4wpTMH1jUCgYEAz+llqZeSAUL5ZUOxc8wm20roYj1baYpff1E6
+xz5nyG0vaDQL1L/islR+yJ9kIySmOUlSbVSuurA+Jahi8ex7Q85w8IOFZLLDHhmx
+pZATFnHqUeBv29u+ViCFkm7YhKLhdK2qITIzDy9wkj0i2JGfHzGaX1WDtCebAQwJ
+OD5lo0ECgYEA06/8JE1CBm3+NuwLzOEYMabfTUe9oe/2shP6SrGIGKf4s5vleDlk
+yXNIGLFCtx8C3BXQbyEXg3l44dTOFka/rTe3LSfIzw+ed/sRkyoJuJljdL4dAvB+
+EU6DmsPAHawRxNqGDdaIw7USUYQz0OVyKGLnOmmsQUJR1OgDTils9z8=
+-----END RSA PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=B CA
+ Validity
+ Not Before: Feb 29 19:15:59 2012 GMT
+ Not After : Feb 26 19:15:59 2022 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:e8:d1:3a:62:db:ed:2f:3e:c8:b2:b6:a7:78:3f:
+ 59:fd:c4:3e:35:44:6f:c7:76:c8:61:c7:66:09:4b:
+ d5:e1:d9:25:42:75:b8:be:90:6a:46:2a:2a:12:55:
+ 3d:13:25:18:d5:c8:13:a2:d3:9a:96:23:88:79:cb:
+ 51:28:60:7b:33:d0:39:bf:82:d6:0d:e6:47:a2:27:
+ c3:73:1f:ad:a4:a9:ac:d8:4b:98:9a:6f:40:3a:cd:
+ f9:5d:b8:b8:43:de:42:20:b4:ef:57:73:9d:1d:ec:
+ fa:97:ef:ac:cf:ef:02:b5:cd:fa:d6:e8:1d:d6:0a:
+ be:c0:a9:91:9f:dd:50:ab:b9:3d:d2:98:82:b2:96:
+ a7:a0:76:44:c7:ed:c1:64:93:24:0f:9c:95:19:7e:
+ 9c:d4:a5:1c:51:0d:98:90:d1:f4:b0:e2:99:e8:47:
+ 75:bf:f4:d0:15:34:08:21:73:c7:b3:65:8b:40:3f:
+ d7:95:01:1a:14:e9:e1:29:e0:45:d9:51:b0:30:bd:
+ 7b:d2:3d:f1:7c:5f:f6:89:ca:0b:cd:61:81:36:0d:
+ a9:44:af:5b:3b:38:92:1d:f1:2f:38:ec:3b:01:0e:
+ 18:6b:9c:ac:fd:70:bf:36:e4:47:73:57:86:ac:60:
+ 6f:e6:b1:47:ff:58:2e:d3:12:28:8a:57:8b:c9:c0:
+ 86:0d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 78:3F:CB:F8:30:EA:63:A3:6E:FE:86:22:50:DE:24:BD:22:C8:BE:9D
+ X509v3 Authority Key Identifier:
+ keyid:4C:29:01:6A:B4:74:98:F4:B1:66:50:F0:8F:83:88:F0:C3:9D:5B:6D
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ aa:a9:e5:68:e2:e9:94:d5:7d:fd:f8:76:e8:e3:23:2e:b9:a6:
+ 7c:0d:7a:d8:8b:9e:91:19:79:56:2d:1b:15:ad:90:1e:9a:d6:
+ 47:c0:3f:28:f3:ec:88:dd:25:4c:68:73:b5:b2:27:21:50:f6:
+ a6:b0:81:16:13:0f:b7:18:4e:a2:ed:2d:fe:ad:af:19:c5:f4:
+ b6:68:b9:50:05:37:29:f1:2d:97:d8:9f:fe:59:a1:f5:f7:ec:
+ 6c:18:18:7e:f4:e6:99:08:01:73:ab:60:98:51:4f:c3:ca:70:
+ e6:18:ab:90:04:7c:73:f2:84:0c:35:e5:1b:22:f1:50:ee:f4:
+ d8:24:7b:84:7b:39:21:a6:e4:53:04:7f:a5:38:58:da:29:86:
+ 1e:40:f0:dc:6d:ec:92:1c:4b:da:af:79:e6:27:ce:3f:53:f8:
+ dc:f1:48:3a:f0:e8:7b:9d:81:8b:44:28:c6:d7:4f:23:98:09:
+ 53:b8:68:db:76:0c:09:d8:59:4f:c8:34:bb:1b:b1:b4:09:59:
+ 09:5d:53:b4:b9:9e:6d:4d:a3:f0:08:5d:2a:a0:b9:dd:9d:64:
+ 37:13:d6:41:61:6c:a8:18:37:7b:a7:55:3c:e5:78:ba:c0:aa:
+ d1:a7:a0:d5:1e:65:e7:34:41:b0:da:b6:05:cc:d7:51:66:cc:
+ 3a:00:c0:b1
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwEQiBD
+QTAeFw0xMjAyMjkxOTE1NTlaFw0yMjAyMjYxOTE1NTlaMGAxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAw
+DgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDo0Tpi2+0vPsiytqd4P1n9xD41RG/Hdshhx2YJ
+S9Xh2SVCdbi+kGpGKioSVT0TJRjVyBOi05qWI4h5y1EoYHsz0Dm/gtYN5keiJ8Nz
+H62kqazYS5iab0A6zflduLhD3kIgtO9Xc50d7PqX76zP7wK1zfrW6B3WCr7AqZGf
+3VCruT3SmIKylqegdkTH7cFkkyQPnJUZfpzUpRxRDZiQ0fSw4pnoR3W/9NAVNAgh
+c8ezZYtAP9eVARoU6eEp4EXZUbAwvXvSPfF8X/aJygvNYYE2DalEr1s7OJId8S84
+7DsBDhhrnKz9cL825EdzV4asYG/msUf/WC7TEiiKV4vJwIYNAgMBAAGjbzBtMAwG
+A1UdEwEB/wQCMAAwHQYDVR0OBBYEFHg/y/gw6mOjbv6GIlDeJL0iyL6dMB8GA1Ud
+IwQYMBaAFEwpAWq0dJj0sWZQ8I+DiPDDnVttMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+BggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAqqnlaOLplNV9/fh26OMjLrmm
+fA162IuekRl5Vi0bFa2QHprWR8A/KPPsiN0lTGhztbInIVD2prCBFhMPtxhOou0t
+/q2vGcX0tmi5UAU3KfEtl9if/lmh9ffsbBgYfvTmmQgBc6tgmFFPw8pw5hirkAR8
+c/KEDDXlGyLxUO702CR7hHs5IabkUwR/pThY2imGHkDw3G3skhxL2q955ifOP1P4
+3PFIOvDoe52Bi0QoxtdPI5gJU7ho23YMCdhZT8g0uxuxtAlZCV1TtLmebU2j8Ahd
+KqC53Z1kNxPWQWFsqBg3e6dVPOV4usCq0aeg1R5l5zRBsNq2BczXUWbMOgDAsQ==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=C CA
+ Validity
+ Not Before: Feb 29 19:15:59 2012 GMT
+ Not After : Feb 26 19:15:59 2022 GMT
+ Subject: CN=B CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:d5:6d:be:6c:68:cd:70:e2:d6:02:3a:16:40:21:
+ 2c:93:56:de:74:88:61:ca:b4:0e:ab:cc:e9:bc:79:
+ 51:47:bf:a8:88:6d:3a:ad:93:db:43:f3:58:db:29:
+ 8a:47:21:4c:54:0e:e7:24:26:cc:83:aa:ec:ae:cc:
+ d1:ce:14:c2:ce:56:c8:02:6a:4d:39:9f:6e:67:ff:
+ b1:e2:fe:d6:99:9f:af:90:bb:87:08:c4:77:6e:e7:
+ 07:79:d4:72:cf:1c:20:51:54:1f:ef:bc:76:02:d4:
+ 9e:c7:27:a6:53:fb:62:2b:b8:b1:63:ba:f6:13:84:
+ 05:b3:aa:bb:33:81:66:8f:37:6d:b9:fb:30:56:a6:
+ eb:69:fe:2f:a8:2a:ab:2f:f9:49:31:c1:d2:9c:9c:
+ 20:72:67:fd:35:37:bf:8e:f6:4c:58:52:f3:4c:ee:
+ a4:c4:68:21:ef:42:e4:f2:ba:e1:84:d5:4a:86:2b:
+ f2:25:11:07:52:6a:18:62:c9:ca:68:b8:d0:92:d9:
+ 09:d8:c0:16:8e:fd:56:c2:e3:63:8c:cd:49:23:ac:
+ 75:7d:24:19:c6:81:b3:a5:90:e3:56:78:7a:35:c8:
+ 35:97:3b:c5:e1:60:51:97:02:c3:1e:bb:33:68:8d:
+ eb:37:f7:c4:62:b4:11:b9:e5:29:95:4e:a4:e3:14:
+ 66:c5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 4C:29:01:6A:B4:74:98:F4:B1:66:50:F0:8F:83:88:F0:C3:9D:5B:6D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 42:71:38:e7:27:f1:c4:3b:59:57:c3:68:99:1f:95:81:9c:2d:
+ 8e:c8:91:85:40:31:24:d2:1c:92:8e:d5:22:95:80:55:7b:a9:
+ db:48:a5:fd:5e:a3:46:f6:a0:17:1b:13:79:79:f8:c3:c7:fe:
+ 62:c2:c9:fa:fe:c4:59:97:19:12:92:98:c1:47:a4:5f:7c:d6:
+ 25:b7:84:6e:08:6a:9f:77:e0:2b:62:fb:ee:23:f5:3d:d7:99:
+ d2:2e:92:47:cc:b3:c1:d5:4b:6d:92:3e:1a:6f:68:93:af:2d:
+ a7:f5:2f:a2:6a:27:d2:32:ab:39:53:1f:0a:1e:cc:4e:af:46:
+ 77:a4:ed:b9:99:b3:13:06:f0:01:9d:db:ad:fd:0e:8b:53:ed:
+ 90:3a:e6:c2:c5:fb:13:ce:e4:1a:51:f9:1b:f3:76:3d:e6:da:
+ dd:e2:77:6e:72:18:0b:b4:74:fa:bf:78:72:80:98:b3:3c:59:
+ 2a:70:74:08:c5:73:0f:66:a6:1c:f6:79:f9:59:21:a8:0b:12:
+ f2:a7:6d:3b:18:e9:80:12:71:4c:2c:59:ac:fa:57:f4:e1:ab:
+ 04:76:e3:ff:60:e1:7d:f5:bd:12:0c:01:54:46:e4:f3:ca:f2:
+ 06:dd:5e:2f:87:07:cb:9a:04:6e:c5:33:dd:8e:52:c6:73:7a:
+ 65:21:b9:a4
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAcSgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwEQyBD
+QTAeFw0xMjAyMjkxOTE1NTlaFw0yMjAyMjYxOTE1NTlaMA8xDTALBgNVBAMMBEIg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVbb5saM1w4tYCOhZA
+ISyTVt50iGHKtA6rzOm8eVFHv6iIbTqtk9tD81jbKYpHIUxUDuckJsyDquyuzNHO
+FMLOVsgCak05n25n/7Hi/taZn6+Qu4cIxHdu5wd51HLPHCBRVB/vvHYC1J7HJ6ZT
++2IruLFjuvYThAWzqrszgWaPN225+zBWputp/i+oKqsv+UkxwdKcnCByZ/01N7+O
+9kxYUvNM7qTEaCHvQuTyuuGE1UqGK/IlEQdSahhiycpouNCS2QnYwBaO/VbC42OM
+zUkjrHV9JBnGgbOlkONWeHo1yDWXO8XhYFGXAsMeuzNojes398RitBG55SmVTqTj
+FGbFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEwpAWq0dJj0
+sWZQ8I+DiPDDnVttMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEA
+QnE45yfxxDtZV8NomR+VgZwtjsiRhUAxJNIcko7VIpWAVXup20il/V6jRvagFxsT
+eXn4w8f+YsLJ+v7EWZcZEpKYwUekX3zWJbeEbghqn3fgK2L77iP1PdeZ0i6SR8yz
+wdVLbZI+Gm9ok68tp/Uvomon0jKrOVMfCh7MTq9Gd6TtuZmzEwbwAZ3brf0Oi1Pt
+kDrmwsX7E87kGlH5G/N2Peba3eJ3bnIYC7R0+r94coCYszxZKnB0CMVzD2amHPZ5
++VkhqAsS8qdtOxjpgBJxTCxZrPpX9OGrBHbj/2DhffW9EgwBVEbk88ryBt1eL4cH
+y5oEbsUz3Y5SxnN6ZSG5pA==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=D Root CA
+ Validity
+ Not Before: Feb 29 19:15:59 2012 GMT
+ Not After : Feb 26 19:15:59 2022 GMT
+ Subject: CN=C CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:a5:fc:1e:cc:76:82:f7:6a:d2:ed:5c:6a:9d:5b:
+ de:83:64:de:69:14:f6:54:8d:ce:01:ee:51:40:c4:
+ cc:d6:73:4c:c5:73:ca:60:4d:64:dc:84:f9:08:90:
+ ce:45:7a:84:4d:4b:3d:07:32:6b:95:6d:18:48:21:
+ 56:49:01:d0:11:75:54:c0:8c:a7:43:d8:33:bd:bf:
+ d8:ef:89:a3:d9:43:2b:83:b6:7e:5a:e5:d9:53:58:
+ 3f:1c:40:56:dd:6b:6c:67:eb:83:27:69:7e:4f:ff:
+ a4:23:6d:54:33:85:ed:d4:e3:01:47:29:2c:a7:91:
+ b7:2b:89:cd:64:96:3b:6d:fb:b2:1b:80:a6:c2:ec:
+ 32:4c:79:ef:80:aa:84:3c:77:60:47:2e:3f:bd:71:
+ 67:c5:7a:f4:98:70:73:17:53:a3:43:ff:f9:a2:9c:
+ d3:3b:69:61:99:eb:82:0d:fa:10:f0:68:3f:6f:3f:
+ f5:d5:04:7e:ac:2f:4e:d1:74:5f:19:39:b8:57:5c:
+ 79:82:ac:95:e7:4c:d0:8b:fc:59:2e:0a:d4:bc:e8:
+ 1b:1f:70:b5:ae:07:b8:f4:e7:97:4f:0b:3c:90:03:
+ e3:c3:b2:ed:5b:aa:ce:8f:cc:b9:e3:94:29:69:87:
+ c5:fe:a7:29:a6:a9:59:c8:17:10:34:31:0c:a8:61:
+ 8c:ab
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ B7:9B:E7:1E:00:25:BE:D8:ED:12:69:0D:4B:73:6D:A1:3A:5E:F1:4C
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 44:22:94:02:ad:82:a3:c8:6d:70:b6:20:42:d3:8f:29:62:3c:
+ b6:dd:e4:e7:9d:b2:77:2d:0f:e9:9c:8c:b3:61:4b:ca:1e:24:
+ da:0d:93:88:1f:c9:2d:3a:b1:24:3f:79:62:51:88:0a:66:49:
+ 8c:95:a9:34:52:a5:b0:25:d6:41:f1:81:6b:26:93:dc:cc:29:
+ 17:1f:ae:b8:27:18:40:00:2d:9c:de:e6:17:1d:29:52:f8:b1:
+ 5e:3e:8a:f6:0a:06:e2:f6:3f:73:37:89:fe:af:ee:fb:81:7a:
+ c9:16:89:22:4d:81:ad:5a:73:17:d5:99:08:63:71:c0:c1:09:
+ 5d:f6:66:04:73:5c:c6:16:b5:77:e0:3f:80:6b:08:18:4c:12:
+ 98:07:97:ac:cb:92:b8:48:47:a6:ef:d1:c7:48:35:7c:cf:53:
+ c6:0d:28:c6:98:0c:d8:60:4e:99:f5:49:b3:3c:2c:34:60:0d:
+ bd:aa:98:c5:60:5a:b6:b1:28:ca:e2:53:55:e5:c2:31:43:f3:
+ bf:de:45:2c:d2:b4:a6:75:25:3f:2b:91:42:5b:57:a5:25:98:
+ 39:30:71:d8:66:b8:35:c5:77:d8:f6:53:b3:9f:ee:1f:73:8d:
+ cc:31:11:76:bc:f3:65:4b:1a:59:60:04:7c:ec:76:9e:4b:8a:
+ fb:17:88:55
+-----BEGIN CERTIFICATE-----
+MIIC4TCCAcmgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJRCBS
+b290IENBMB4XDTEyMDIyOTE5MTU1OVoXDTIyMDIyNjE5MTU1OVowDzENMAsGA1UE
+AwwEQyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKX8Hsx2gvdq
+0u1cap1b3oNk3mkU9lSNzgHuUUDEzNZzTMVzymBNZNyE+QiQzkV6hE1LPQcya5Vt
+GEghVkkB0BF1VMCMp0PYM72/2O+Jo9lDK4O2flrl2VNYPxxAVt1rbGfrgydpfk//
+pCNtVDOF7dTjAUcpLKeRtyuJzWSWO237shuApsLsMkx574CqhDx3YEcuP71xZ8V6
+9JhwcxdTo0P/+aKc0ztpYZnrgg36EPBoP28/9dUEfqwvTtF0Xxk5uFdceYKsledM
+0Iv8WS4K1LzoGx9wta4HuPTnl08LPJAD48Oy7Vuqzo/MueOUKWmHxf6nKaapWcgX
+EDQxDKhhjKsCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUt5vn
+HgAlvtjtEmkNS3NtoTpe8UwwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUA
+A4IBAQBEIpQCrYKjyG1wtiBC048pYjy23eTnnbJ3LQ/pnIyzYUvKHiTaDZOIH8kt
+OrEkP3liUYgKZkmMlak0UqWwJdZB8YFrJpPczCkXH664JxhAAC2c3uYXHSlS+LFe
+Por2Cgbi9j9zN4n+r+77gXrJFokiTYGtWnMX1ZkIY3HAwQld9mYEc1zGFrV34D+A
+awgYTBKYB5esy5K4SEem79HHSDV8z1PGDSjGmAzYYE6Z9UmzPCw0YA29qpjFYFq2
+sSjK4lNV5cIxQ/O/3kUs0rSmdSU/K5FCW1elJZg5MHHYZrg1xXfY9lOzn+4fc43M
+MRF2vPNlSxpZYAR87HaeS4r7F4hV
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICpDCCAYwCCQCGninElsmhPzANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDDAlE
+IFJvb3QgQ0EwHhcNMTIwMjI5MTkxNTU5WhcNMjIwMjI2MTkxNTU5WjAUMRIwEAYD
+VQQDDAlEIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+
+wxbkVPUSUZ/UyGiWSOgnvyJSFU0VIQMtbvmyy71XlCI21euFyVMTWukkJeHXV7lA
+2nsQ3XfC6FvsbxYPd5auSSM2sIWCNO49KEX/xJXL5zQswh+WcDTz079fhjOf/dz2
+TLDufP2IuFaIGRJennTlaNBCRUVJyPcdysfiw3UfnCwxG5V7bpF/Rfr5y0UdC2W5
+yhKUfe7U1NfYQwfJ058OgBydSNuNaVnFm7E09khsBjET3Z/EsNq4n/Q3SnE16I9G
+Bu+DrsrKiabKfh28bBzKLCHtbf+0FlAhAj/eSY3rVW23OushOeyFWWsxbmo5FM/V
+ACEHD3OzFwBgnoiJHNn1AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAGZ0lFGL7+Et
+uuQcbDLkc3NWKtqISAJx/Bms8nigQ2J/IFHDVp19NjCj29wJi7Dy8+p0Thfy8O2F
+4/3aV3Ptl5Ay+1PVqVVhM4RkqLpuLHY2pHtjgREeflJEtcDGLX3v3zia7plHEGo5
+T22O6vLvFK/RbuAkFvmOjLif2JBnQQAaI+dUvrRtKGI0Ax1b8XkYD5p2Zalbbkd2
+uJOC0Mc7iyRkbUbP2e/fAzq9B/OXI2gD6uU25x4nskTptMvO6YNvTny8/zyhXdDc
+U8Ue+UDeZ6VFg8K02N1gF5e0WgIdiM8ndkNS6r1g+2DNEZfoCFYGW1Ta8D5+n+uV
+jFY6jvAVnlQ=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/redundant-validated-chain-root.pem b/chromium/net/data/ssl/certificates/redundant-validated-chain-root.pem
new file mode 100644
index 00000000000..6acfc1e216d
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/redundant-validated-chain-root.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICmjCCAYICCQCjrv+JsRC02TANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDDARD
+IENBMB4XDTEyMDIyOTE5MTU1OVoXDTIyMDIyNjE5MTU1OVowDzENMAsGA1UEAwwE
+QyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKX8Hsx2gvdq0u1c
+ap1b3oNk3mkU9lSNzgHuUUDEzNZzTMVzymBNZNyE+QiQzkV6hE1LPQcya5VtGEgh
+VkkB0BF1VMCMp0PYM72/2O+Jo9lDK4O2flrl2VNYPxxAVt1rbGfrgydpfk//pCNt
+VDOF7dTjAUcpLKeRtyuJzWSWO237shuApsLsMkx574CqhDx3YEcuP71xZ8V69Jhw
+cxdTo0P/+aKc0ztpYZnrgg36EPBoP28/9dUEfqwvTtF0Xxk5uFdceYKsledM0Iv8
+WS4K1LzoGx9wta4HuPTnl08LPJAD48Oy7Vuqzo/MueOUKWmHxf6nKaapWcgXEDQx
+DKhhjKsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAcDhE2mVuhe8Z5IGzakzIRbN4
+5jQieOQhg+eO9h0ywr+Z0c1Ib88CoTQa3oJXwBojo86zn0aPoifRsOSj8mV1l9Te
+tGupoZwCjpPYHgL7j49ZY1nLMIQCmhiCaORXoJJTZWaQL79s4cnJ8bdIC3HPOtXF
+inhESDT3+B2vkozWIzUZytAfcu0PCubbQ2AmLT0GZgP9yhg8R90m81yF3ZYnIuJt
+bJSPo6at+aypb8NL//rVUAgzwMXn56DQ5+VcaPVVT8hgdpmQNXreCPTwbXWuXr1J
+56OQVe9KHKXlpScLmwDFdc+6Kh+AM9Oz/czpdJZmPMnsAtgOeBN2Ad0Sqq6BBQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/redundant-validated-chain.pem b/chromium/net/data/ssl/certificates/redundant-validated-chain.pem
new file mode 100644
index 00000000000..211b5f68d02
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/redundant-validated-chain.pem
@@ -0,0 +1,196 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA6NE6YtvtLz7IsraneD9Z/cQ+NURvx3bIYcdmCUvV4dklQnW4
+vpBqRioqElU9EyUY1cgTotOaliOIectRKGB7M9A5v4LWDeZHoifDcx+tpKms2EuY
+mm9AOs35Xbi4Q95CILTvV3OdHez6l++sz+8Ctc361ugd1gq+wKmRn91Qq7k90piC
+spanoHZEx+3BZJMkD5yVGX6c1KUcUQ2YkNH0sOKZ6Ed1v/TQFTQIIXPHs2WLQD/X
+lQEaFOnhKeBF2VGwML170j3xfF/2icoLzWGBNg2pRK9bOziSHfEvOOw7AQ4Ya5ys
+/XC/NuRHc1eGrGBv5rFH/1gu0xIoileLycCGDQIDAQABAoIBADnrU0sky2zlgah0
+KFWR7SFkoNU/oU9ODavFn2zQoPT+wHY4My21X7r04mKNMhSBNhx5Gel4Gw0e6eTi
+393bosrREozCT95FW6zLl6QcTWaZj5Z/uAczhhcbBt56Bd1cfbcFTEXFTWEUg4Mo
+7SUNoO75v12XgVSud6YWiVPsCxWtFmiE91pZINfOM0rBacASY7F+/jJwhlmt04Ru
+qwyG2bWmVB97cj2IcNFMwrH/3vbml5YttTKEb1wlKodrj8BqzOcP5VW3DgXbjbz8
+gZtP+pjpP5e9F6UcmwXek12dGwqp+9Mo8veidI8dXNSEiHpdFiTOolsLTGVXziVP
++spFbAECgYEA96mAIB4RidCH2wYtKoepiGisJmFvebGFpyBUWjKtDFKg0ufJ3S/3
+HFq5Pn8473ii8qVjBwlin/bs9dOEuwEI2lvM39QeoUASVdfLOHoYKsK8dyFo7rTX
+bQdwMQpSqYbJyi5OElHH/Z28celhF522Lt73uKVOFfiyGYlGzjApZV0CgYEA8KfJ
+QCxA27mWTzzHjYtTEGlYskbGpV0XNhjSoCueJvqE8+FYYylSpIB7+yUUI/5g5Iau
+aQlVFt+c2IyG5Fg+k7rc0arFkRm8HGp8df9aE7xdHwdw5BL/6wQDfFuP4sDIWVab
+IdJDUgdp+G4OGKcSgVCBbIfMrlKll/fMqBWxaHECgYEAv0mZH7V5wGNje2VC33WX
+GTgXtzFMw8a8v4A2BtDbXgg4FY5YGVJh3/Gm4MGs/THFUfsyCI5UMc+r6JduDm5X
+IykCjeMtoUh2oP0jBsUvA2AT50PT44OkXJ8BJa+edzgXheTMAlROTvJVSfqDNpVm
+0L8AwQpUzJ2hGh4wpTMH1jUCgYEAz+llqZeSAUL5ZUOxc8wm20roYj1baYpff1E6
+xz5nyG0vaDQL1L/islR+yJ9kIySmOUlSbVSuurA+Jahi8ex7Q85w8IOFZLLDHhmx
+pZATFnHqUeBv29u+ViCFkm7YhKLhdK2qITIzDy9wkj0i2JGfHzGaX1WDtCebAQwJ
+OD5lo0ECgYEA06/8JE1CBm3+NuwLzOEYMabfTUe9oe/2shP6SrGIGKf4s5vleDlk
+yXNIGLFCtx8C3BXQbyEXg3l44dTOFka/rTe3LSfIzw+ed/sRkyoJuJljdL4dAvB+
+EU6DmsPAHawRxNqGDdaIw7USUYQz0OVyKGLnOmmsQUJR1OgDTils9z8=
+-----END RSA PRIVATE KEY-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=B CA
+ Validity
+ Not Before: Feb 29 19:15:59 2012 GMT
+ Not After : Feb 26 19:15:59 2022 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:e8:d1:3a:62:db:ed:2f:3e:c8:b2:b6:a7:78:3f:
+ 59:fd:c4:3e:35:44:6f:c7:76:c8:61:c7:66:09:4b:
+ d5:e1:d9:25:42:75:b8:be:90:6a:46:2a:2a:12:55:
+ 3d:13:25:18:d5:c8:13:a2:d3:9a:96:23:88:79:cb:
+ 51:28:60:7b:33:d0:39:bf:82:d6:0d:e6:47:a2:27:
+ c3:73:1f:ad:a4:a9:ac:d8:4b:98:9a:6f:40:3a:cd:
+ f9:5d:b8:b8:43:de:42:20:b4:ef:57:73:9d:1d:ec:
+ fa:97:ef:ac:cf:ef:02:b5:cd:fa:d6:e8:1d:d6:0a:
+ be:c0:a9:91:9f:dd:50:ab:b9:3d:d2:98:82:b2:96:
+ a7:a0:76:44:c7:ed:c1:64:93:24:0f:9c:95:19:7e:
+ 9c:d4:a5:1c:51:0d:98:90:d1:f4:b0:e2:99:e8:47:
+ 75:bf:f4:d0:15:34:08:21:73:c7:b3:65:8b:40:3f:
+ d7:95:01:1a:14:e9:e1:29:e0:45:d9:51:b0:30:bd:
+ 7b:d2:3d:f1:7c:5f:f6:89:ca:0b:cd:61:81:36:0d:
+ a9:44:af:5b:3b:38:92:1d:f1:2f:38:ec:3b:01:0e:
+ 18:6b:9c:ac:fd:70:bf:36:e4:47:73:57:86:ac:60:
+ 6f:e6:b1:47:ff:58:2e:d3:12:28:8a:57:8b:c9:c0:
+ 86:0d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 78:3F:CB:F8:30:EA:63:A3:6E:FE:86:22:50:DE:24:BD:22:C8:BE:9D
+ X509v3 Authority Key Identifier:
+ keyid:4C:29:01:6A:B4:74:98:F4:B1:66:50:F0:8F:83:88:F0:C3:9D:5B:6D
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ aa:a9:e5:68:e2:e9:94:d5:7d:fd:f8:76:e8:e3:23:2e:b9:a6:
+ 7c:0d:7a:d8:8b:9e:91:19:79:56:2d:1b:15:ad:90:1e:9a:d6:
+ 47:c0:3f:28:f3:ec:88:dd:25:4c:68:73:b5:b2:27:21:50:f6:
+ a6:b0:81:16:13:0f:b7:18:4e:a2:ed:2d:fe:ad:af:19:c5:f4:
+ b6:68:b9:50:05:37:29:f1:2d:97:d8:9f:fe:59:a1:f5:f7:ec:
+ 6c:18:18:7e:f4:e6:99:08:01:73:ab:60:98:51:4f:c3:ca:70:
+ e6:18:ab:90:04:7c:73:f2:84:0c:35:e5:1b:22:f1:50:ee:f4:
+ d8:24:7b:84:7b:39:21:a6:e4:53:04:7f:a5:38:58:da:29:86:
+ 1e:40:f0:dc:6d:ec:92:1c:4b:da:af:79:e6:27:ce:3f:53:f8:
+ dc:f1:48:3a:f0:e8:7b:9d:81:8b:44:28:c6:d7:4f:23:98:09:
+ 53:b8:68:db:76:0c:09:d8:59:4f:c8:34:bb:1b:b1:b4:09:59:
+ 09:5d:53:b4:b9:9e:6d:4d:a3:f0:08:5d:2a:a0:b9:dd:9d:64:
+ 37:13:d6:41:61:6c:a8:18:37:7b:a7:55:3c:e5:78:ba:c0:aa:
+ d1:a7:a0:d5:1e:65:e7:34:41:b0:da:b6:05:cc:d7:51:66:cc:
+ 3a:00:c0:b1
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwEQiBD
+QTAeFw0xMjAyMjkxOTE1NTlaFw0yMjAyMjYxOTE1NTlaMGAxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAw
+DgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDo0Tpi2+0vPsiytqd4P1n9xD41RG/Hdshhx2YJ
+S9Xh2SVCdbi+kGpGKioSVT0TJRjVyBOi05qWI4h5y1EoYHsz0Dm/gtYN5keiJ8Nz
+H62kqazYS5iab0A6zflduLhD3kIgtO9Xc50d7PqX76zP7wK1zfrW6B3WCr7AqZGf
+3VCruT3SmIKylqegdkTH7cFkkyQPnJUZfpzUpRxRDZiQ0fSw4pnoR3W/9NAVNAgh
+c8ezZYtAP9eVARoU6eEp4EXZUbAwvXvSPfF8X/aJygvNYYE2DalEr1s7OJId8S84
+7DsBDhhrnKz9cL825EdzV4asYG/msUf/WC7TEiiKV4vJwIYNAgMBAAGjbzBtMAwG
+A1UdEwEB/wQCMAAwHQYDVR0OBBYEFHg/y/gw6mOjbv6GIlDeJL0iyL6dMB8GA1Ud
+IwQYMBaAFEwpAWq0dJj0sWZQ8I+DiPDDnVttMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+BggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAqqnlaOLplNV9/fh26OMjLrmm
+fA162IuekRl5Vi0bFa2QHprWR8A/KPPsiN0lTGhztbInIVD2prCBFhMPtxhOou0t
+/q2vGcX0tmi5UAU3KfEtl9if/lmh9ffsbBgYfvTmmQgBc6tgmFFPw8pw5hirkAR8
+c/KEDDXlGyLxUO702CR7hHs5IabkUwR/pThY2imGHkDw3G3skhxL2q955ifOP1P4
+3PFIOvDoe52Bi0QoxtdPI5gJU7ho23YMCdhZT8g0uxuxtAlZCV1TtLmebU2j8Ahd
+KqC53Z1kNxPWQWFsqBg3e6dVPOV4usCq0aeg1R5l5zRBsNq2BczXUWbMOgDAsQ==
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 236 (0xec)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=C CA
+ Validity
+ Not Before: Feb 29 19:15:59 2012 GMT
+ Not After : Feb 26 19:15:59 2022 GMT
+ Subject: CN=B CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (2048 bit)
+ Modulus (2048 bit):
+ 00:d5:6d:be:6c:68:cd:70:e2:d6:02:3a:16:40:21:
+ 2c:93:56:de:74:88:61:ca:b4:0e:ab:cc:e9:bc:79:
+ 51:47:bf:a8:88:6d:3a:ad:93:db:43:f3:58:db:29:
+ 8a:47:21:4c:54:0e:e7:24:26:cc:83:aa:ec:ae:cc:
+ d1:ce:14:c2:ce:56:c8:02:6a:4d:39:9f:6e:67:ff:
+ b1:e2:fe:d6:99:9f:af:90:bb:87:08:c4:77:6e:e7:
+ 07:79:d4:72:cf:1c:20:51:54:1f:ef:bc:76:02:d4:
+ 9e:c7:27:a6:53:fb:62:2b:b8:b1:63:ba:f6:13:84:
+ 05:b3:aa:bb:33:81:66:8f:37:6d:b9:fb:30:56:a6:
+ eb:69:fe:2f:a8:2a:ab:2f:f9:49:31:c1:d2:9c:9c:
+ 20:72:67:fd:35:37:bf:8e:f6:4c:58:52:f3:4c:ee:
+ a4:c4:68:21:ef:42:e4:f2:ba:e1:84:d5:4a:86:2b:
+ f2:25:11:07:52:6a:18:62:c9:ca:68:b8:d0:92:d9:
+ 09:d8:c0:16:8e:fd:56:c2:e3:63:8c:cd:49:23:ac:
+ 75:7d:24:19:c6:81:b3:a5:90:e3:56:78:7a:35:c8:
+ 35:97:3b:c5:e1:60:51:97:02:c3:1e:bb:33:68:8d:
+ eb:37:f7:c4:62:b4:11:b9:e5:29:95:4e:a4:e3:14:
+ 66:c5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 4C:29:01:6A:B4:74:98:F4:B1:66:50:F0:8F:83:88:F0:C3:9D:5B:6D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 42:71:38:e7:27:f1:c4:3b:59:57:c3:68:99:1f:95:81:9c:2d:
+ 8e:c8:91:85:40:31:24:d2:1c:92:8e:d5:22:95:80:55:7b:a9:
+ db:48:a5:fd:5e:a3:46:f6:a0:17:1b:13:79:79:f8:c3:c7:fe:
+ 62:c2:c9:fa:fe:c4:59:97:19:12:92:98:c1:47:a4:5f:7c:d6:
+ 25:b7:84:6e:08:6a:9f:77:e0:2b:62:fb:ee:23:f5:3d:d7:99:
+ d2:2e:92:47:cc:b3:c1:d5:4b:6d:92:3e:1a:6f:68:93:af:2d:
+ a7:f5:2f:a2:6a:27:d2:32:ab:39:53:1f:0a:1e:cc:4e:af:46:
+ 77:a4:ed:b9:99:b3:13:06:f0:01:9d:db:ad:fd:0e:8b:53:ed:
+ 90:3a:e6:c2:c5:fb:13:ce:e4:1a:51:f9:1b:f3:76:3d:e6:da:
+ dd:e2:77:6e:72:18:0b:b4:74:fa:bf:78:72:80:98:b3:3c:59:
+ 2a:70:74:08:c5:73:0f:66:a6:1c:f6:79:f9:59:21:a8:0b:12:
+ f2:a7:6d:3b:18:e9:80:12:71:4c:2c:59:ac:fa:57:f4:e1:ab:
+ 04:76:e3:ff:60:e1:7d:f5:bd:12:0c:01:54:46:e4:f3:ca:f2:
+ 06:dd:5e:2f:87:07:cb:9a:04:6e:c5:33:dd:8e:52:c6:73:7a:
+ 65:21:b9:a4
+-----BEGIN CERTIFICATE-----
+MIIC3DCCAcSgAwIBAgICAOwwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAwwEQyBD
+QTAeFw0xMjAyMjkxOTE1NTlaFw0yMjAyMjYxOTE1NTlaMA8xDTALBgNVBAMMBEIg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVbb5saM1w4tYCOhZA
+ISyTVt50iGHKtA6rzOm8eVFHv6iIbTqtk9tD81jbKYpHIUxUDuckJsyDquyuzNHO
+FMLOVsgCak05n25n/7Hi/taZn6+Qu4cIxHdu5wd51HLPHCBRVB/vvHYC1J7HJ6ZT
++2IruLFjuvYThAWzqrszgWaPN225+zBWputp/i+oKqsv+UkxwdKcnCByZ/01N7+O
+9kxYUvNM7qTEaCHvQuTyuuGE1UqGK/IlEQdSahhiycpouNCS2QnYwBaO/VbC42OM
+zUkjrHV9JBnGgbOlkONWeHo1yDWXO8XhYFGXAsMeuzNojes398RitBG55SmVTqTj
+FGbFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEwpAWq0dJj0
+sWZQ8I+DiPDDnVttMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEA
+QnE45yfxxDtZV8NomR+VgZwtjsiRhUAxJNIcko7VIpWAVXup20il/V6jRvagFxsT
+eXn4w8f+YsLJ+v7EWZcZEpKYwUekX3zWJbeEbghqn3fgK2L77iP1PdeZ0i6SR8yz
+wdVLbZI+Gm9ok68tp/Uvomon0jKrOVMfCh7MTq9Gd6TtuZmzEwbwAZ3brf0Oi1Pt
+kDrmwsX7E87kGlH5G/N2Peba3eJ3bnIYC7R0+r94coCYszxZKnB0CMVzD2amHPZ5
++VkhqAsS8qdtOxjpgBJxTCxZrPpX9OGrBHbj/2DhffW9EgwBVEbk88ryBt1eL4cH
+y5oEbsUz3Y5SxnN6ZSG5pA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICmjCCAYICCQCjrv+JsRC02TANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDDARD
+IENBMB4XDTEyMDIyOTE5MTU1OVoXDTIyMDIyNjE5MTU1OVowDzENMAsGA1UEAwwE
+QyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKX8Hsx2gvdq0u1c
+ap1b3oNk3mkU9lSNzgHuUUDEzNZzTMVzymBNZNyE+QiQzkV6hE1LPQcya5VtGEgh
+VkkB0BF1VMCMp0PYM72/2O+Jo9lDK4O2flrl2VNYPxxAVt1rbGfrgydpfk//pCNt
+VDOF7dTjAUcpLKeRtyuJzWSWO237shuApsLsMkx574CqhDx3YEcuP71xZ8V69Jhw
+cxdTo0P/+aKc0ztpYZnrgg36EPBoP28/9dUEfqwvTtF0Xxk5uFdceYKsledM0Iv8
+WS4K1LzoGx9wta4HuPTnl08LPJAD48Oy7Vuqzo/MueOUKWmHxf6nKaapWcgXEDQx
+DKhhjKsCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAcDhE2mVuhe8Z5IGzakzIRbN4
+5jQieOQhg+eO9h0ywr+Z0c1Ib88CoTQa3oJXwBojo86zn0aPoifRsOSj8mV1l9Te
+tGupoZwCjpPYHgL7j49ZY1nLMIQCmhiCaORXoJJTZWaQL79s4cnJ8bdIC3HPOtXF
+inhESDT3+B2vkozWIzUZytAfcu0PCubbQ2AmLT0GZgP9yhg8R90m81yF3ZYnIuJt
+bJSPo6at+aypb8NL//rVUAgzwMXn56DQ5+VcaPVVT8hgdpmQNXreCPTwbXWuXr1J
+56OQVe9KHKXlpScLmwDFdc+6Kh+AM9Oz/czpdJZmPMnsAtgOeBN2Ad0Sqq6BBQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/root_ca_cert.pem b/chromium/net/data/ssl/certificates/root_ca_cert.pem
new file mode 100644
index 00000000000..0e58f2ab5a9
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/root_ca_cert.pem
@@ -0,0 +1,102 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAq6OEFgWu9ICFgaeoWfq7Dl57BNzERHpBBTedRaFr3uj+D4nT
+OXjraAFPFcBLE6RMJZXtpLvZrfdUDPEzTtcliLAoXmQB8DN8TTvYXEgEr3dSb+qZ
+sAfmbbtjnjOtGJQwlkb0QdZp4+5V3vrD1DbT0XGHKDu4/Estvzzi+4zo+plEDL1d
+y+Op9g09HOu2gB6+pVG1YAR3ckeWFw2ORO76xF+rMRbcaJqfmnmUBLkPFN/Bmvo3
+q39wuIDdSCXtvUNnAcEynXah/sFk2AB3c9E/IYaScuiRNkWEi7cUXrAyXKPtMNo2
+RdvfVUEYz/42N+270wkf1taR0thfcwJS06oNIwIDAQABAoIBAQCml41KurV3h48N
+qydSPnR0m+uXapftRyP45mP2A5lpwIdnmYS00dLbHfoeyIeWRu/Jw13neJTtr5Ob
+BDmimlxJ8YEdLMBc3fwWErILz7MMhCo1imUlYGWduDYWhV3K73rBQZkulegtXVVn
+mcmdHqI1NZXTzTPdXTLH3VcKhVhUcftA+foSmY5Sr2EgWL34lkF0ciWSbYf4dIAc
+RS+DikEpAM552hPu9+oo0+5aheH9ijuF4SCRZcvToC2YaTUR5OG4j31oGtDMXmeu
+5alBhaUZqS3b8FX8Q2GbqJ4sR+2XrgovrVteOGN0QvoExZeZIdt6ArqArKGQ/hQf
+gPITpX7RAoGBANp/9E3a+CB9fculDW/Vdpo2jVMLFmlMzWCxA5V0BwIgx4yCKgK7
+Zx1gsO+vaOKu1uLfAj5wWd1qNRbAAvL0CpSkEqfteHInyyN6muSWuDEELCIYU/0u
+IdhPH7yU/SYKsZK3qnOZBwSJMI0iJAKXO0qVYepTtN3MXzhIWkaPqoj9AoGBAMkY
+qjS/QDmyIukeUrhpEn6F2nh6/Upek0+xFUP/0SIeaCAfFiJ9+mFLSDLQ0Rvsq5G6
+glfMFaQz/+610+6YZ5ioEPUlVMAx0qNZRS8Gy97MDoe9Z+PmbKdvbZXLTP6nhpGy
+9Nhilog+U8MZCwiklDEKyldQBg/TghMsqhYEyFifAoGBAIGJ9obp2M06WQgQ1ISG
+44fN679eBW1sUR6QE3XZkgYa9PNCgDGadSmfl4aiUeaCjXd3i1cFOOHiA5N3RshP
+Eq7JDx5r1UqoePCR0Z7QFGdK9/dGwRSK8xnQ3xnooqSZFmnLZcye3uA7jYM+yGaY
+zlgLdD9+XO5aKeGuU4cdyZbpAoGAIm8cxgdxoe+E1tIga/9h8VROQo8czB1BsEWs
+VIZ7cxIt1kI8cg+MThir9jeXduahwkRjiVEi64kT6/YqohJqutMchD2uM3LUlK/2
+jsTlJ/NdEZU3MukD9e+4ngu/1WMuQciY8GyBRjcV9LRXFVXJIlSsrSLAxvj6rvnY
+7ghHnhUCgYBiKCwWTxuoh450TwZMv8X3/6+88w6CieUa0EvdVDcmSQ1cnlAw/7Uh
+inzU7HizHG2tq8Rg5HjaXTnkNhacT9tg2WELSH8Cay3287CmIhdkvZMJIPRhMMCW
+NUQMCtufqhgyX4Qn0W1dbhBzyuyfsj1Ec3BrUp1aPHr0QRm4HdsAIA==
+-----END RSA PRIVATE KEY-----
+
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 12573969073242583322 (0xae7fb6409b5f311a)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Root CA
+ Validity
+ Not Before: Jun 18 19:52:02 2013 GMT
+ Not After : Jun 16 19:52:02 2023 GMT
+ Subject: CN=Test Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ab:a3:84:16:05:ae:f4:80:85:81:a7:a8:59:fa:
+ bb:0e:5e:7b:04:dc:c4:44:7a:41:05:37:9d:45:a1:
+ 6b:de:e8:fe:0f:89:d3:39:78:eb:68:01:4f:15:c0:
+ 4b:13:a4:4c:25:95:ed:a4:bb:d9:ad:f7:54:0c:f1:
+ 33:4e:d7:25:88:b0:28:5e:64:01:f0:33:7c:4d:3b:
+ d8:5c:48:04:af:77:52:6f:ea:99:b0:07:e6:6d:bb:
+ 63:9e:33:ad:18:94:30:96:46:f4:41:d6:69:e3:ee:
+ 55:de:fa:c3:d4:36:d3:d1:71:87:28:3b:b8:fc:4b:
+ 2d:bf:3c:e2:fb:8c:e8:fa:99:44:0c:bd:5d:cb:e3:
+ a9:f6:0d:3d:1c:eb:b6:80:1e:be:a5:51:b5:60:04:
+ 77:72:47:96:17:0d:8e:44:ee:fa:c4:5f:ab:31:16:
+ dc:68:9a:9f:9a:79:94:04:b9:0f:14:df:c1:9a:fa:
+ 37:ab:7f:70:b8:80:dd:48:25:ed:bd:43:67:01:c1:
+ 32:9d:76:a1:fe:c1:64:d8:00:77:73:d1:3f:21:86:
+ 92:72:e8:91:36:45:84:8b:b7:14:5e:b0:32:5c:a3:
+ ed:30:da:36:45:db:df:55:41:18:cf:fe:36:37:ed:
+ bb:d3:09:1f:d6:d6:91:d2:d8:5f:73:02:52:d3:aa:
+ 0d:23
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 2B:88:93:E1:D2:54:50:F4:B8:A4:20:BD:B1:79:E6:0B:AA:EB:EC:1A
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ a8:58:42:e4:7c:b1:46:11:ee:56:b7:09:08:fb:06:44:f0:a9:
+ 60:03:f0:05:23:09:3c:36:d6:28:1b:e5:d6:61:15:a0:6f:de:
+ 69:ac:28:58:05:f1:ce:9b:61:c2:58:b0:5d:ed:6c:75:44:e2:
+ 68:01:91:59:b1:4f:f3:51:f2:23:f6:47:42:41:57:26:4f:87:
+ 1e:d2:9f:94:3a:e2:d0:4e:6f:02:d2:92:76:2c:0a:dd:58:93:
+ e1:47:b9:02:a3:3d:75:b4:ba:24:70:87:32:87:cf:76:4e:a0:
+ 41:8b:86:42:18:55:ed:a5:ae:5d:6a:3a:8c:28:70:4c:f1:c5:
+ 36:6c:ec:01:a9:d6:51:39:32:31:30:24:82:9f:88:d9:f5:c1:
+ 09:6b:5a:6b:f1:95:d3:9d:3f:e0:42:63:fc:b7:32:90:55:56:
+ f2:76:1b:71:38:bd:bd:fb:3b:23:50:46:4c:2c:4e:49:48:52:
+ ea:05:5f:16:f2:98:51:af:2f:79:36:2a:a0:ba:36:68:1b:29:
+ 8b:7b:e8:8c:ea:73:31:e5:86:d7:2c:d8:56:06:43:d7:72:d2:
+ f0:27:4e:64:0a:2b:27:38:36:cd:be:c1:33:db:74:4b:4e:74:
+ be:21:bd:f6:81:66:d2:fd:2b:7f:f4:55:36:c0:ed:a7:44:ca:
+ b1:78:1d:0f
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAdugAwIBAgIJAK5/tkCbXzEaMA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV
+BAMMDFRlc3QgUm9vdCBDQTAeFw0xMzA2MTgxOTUyMDJaFw0yMzA2MTYxOTUyMDJa
+MBcxFTATBgNVBAMMDFRlc3QgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAKujhBYFrvSAhYGnqFn6uw5eewTcxER6QQU3nUWha97o/g+J0zl4
+62gBTxXASxOkTCWV7aS72a33VAzxM07XJYiwKF5kAfAzfE072FxIBK93Um/qmbAH
+5m27Y54zrRiUMJZG9EHWaePuVd76w9Q209Fxhyg7uPxLLb884vuM6PqZRAy9Xcvj
+qfYNPRzrtoAevqVRtWAEd3JHlhcNjkTu+sRfqzEW3Gian5p5lAS5DxTfwZr6N6t/
+cLiA3Ugl7b1DZwHBMp12of7BZNgAd3PRPyGGknLokTZFhIu3FF6wMlyj7TDaNkXb
+31VBGM/+Njftu9MJH9bWkdLYX3MCUtOqDSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUK4iT4dJUUPS4pCC9sXnmC6rr7BowDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCoWELkfLFGEe5WtwkI+wZE8KlgA/AFIwk8
+NtYoG+XWYRWgb95prChYBfHOm2HCWLBd7Wx1ROJoAZFZsU/zUfIj9kdCQVcmT4ce
+0p+UOuLQTm8C0pJ2LArdWJPhR7kCoz11tLokcIcyh892TqBBi4ZCGFXtpa5dajqM
+KHBM8cU2bOwBqdZROTIxMCSCn4jZ9cEJa1pr8ZXTnT/gQmP8tzKQVVbydhtxOL29
++zsjUEZMLE5JSFLqBV8W8phRry95NiqgujZoGymLe+iM6nMx5YbXLNhWBkPXctLw
+J05kCisnODbNvsEz23RLTnS+Ib32gWbS/St/9FU2wO2nRMqxeB0P
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/salesforce_com_test.pem b/chromium/net/data/ssl/certificates/salesforce_com_test.pem
new file mode 100644
index 00000000000..1522d0b92ea
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/salesforce_com_test.pem
@@ -0,0 +1,81 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 30:88:41:0a:28:b4:3e:3d:9a:f3:b3:90:a0:24:bc:d6
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign International Server CA - Class 3, OU=www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD.(c)97 VeriSign
+ Validity
+ Not Before: Sep 18 00:00:00 2010 GMT
+ Not After : Sep 18 23:59:59 2012 GMT
+ Subject: C=US, ST=California, L=San Francisco, O=Salesforce.com, Inc., OU=Applications, CN=prerelna1.pre.salesforce.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:b8:0d:04:b7:23:60:d2:ff:12:bb:29:43:81:30:
+ 0f:dd:8b:cd:cc:2c:8d:d0:14:de:5c:7b:a3:33:ea:
+ f2:7e:88:6e:04:42:17:70:67:91:a8:20:87:81:a8:
+ be:c4:57:d5:f5:3c:cf:34:96:cb:fb:7c:c3:db:ba:
+ 36:c2:08:9f:c1:1d:91:fa:b7:21:03:50:32:bb:30:
+ be:ff:f8:bf:8d:c0:7d:16:e4:d2:81:ef:e2:1a:89:
+ 13:7c:40:6d:dd:1f:32:9d:3f:ca:a2:ab:e6:ae:9f:
+ 96:91:66:32:e9:e5:ca:e3:9d:fc:62:31:aa:de:a6:
+ 50:21:ba:e2:8e:77:00:41:17
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Key Usage:
+ Digital Signature, Key Encipherment
+ X509v3 CRL Distribution Points:
+ URI:http://SVRIntl-crl.verisign.com/SVRIntl.crl
+
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.23.3
+ CPS: https://www.verisign.com/rpa
+
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, TLS Web Server Authentication, TLS Web Client Authentication
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+ CA Issuers - URI:http://SVRIntl-aia.verisign.com/SVRIntl-aia.cer
+
+ 1.3.6.1.5.5.7.1.12:
+ 0`.^.\0Z0X0V..image/gif0!0.0...+......Kk.(.....R8.).K..!..0&.$http://logo.verisign.com/vslogo1.gif
+ Signature Algorithm: sha1WithRSAEncryption
+ b8:e3:37:ba:5a:37:37:bf:e5:bc:88:fe:1e:fa:b4:4f:7d:52:
+ ca:26:b6:83:a1:de:28:6b:01:a8:cd:5d:f2:9a:2c:f0:6e:89:
+ 69:ab:94:b5:14:f2:c3:ca:d8:5d:2f:6d:13:9a:83:f1:ed:4e:
+ 85:87:93:69:19:53:3a:a4:f1:98:96:b7:28:13:32:7e:d2:e4:
+ 7a:7a:f2:8d:80:7d:af:89:64:43:7b:f6:05:3d:16:7c:f1:2a:
+ 6f:bb:9b:3a:57:a5:f1:f7:77:a0:07:68:92:39:bd:45:c7:c8:
+ 75:ed:ac:c6:ac:45:02:18:0b:41:ba:01:68:3e:c2:3b:f6:8b:
+ 4e:50
+-----BEGIN CERTIFICATE-----
+MIIEhzCCA/CgAwIBAgIQMIhBCii0Pj2a87OQoCS81jANBgkqhkiG9w0BAQUFADCB
+ujEfMB0GA1UEChMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVy
+aVNpZ24sIEluYy4xMzAxBgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2Vy
+dmVyIENBIC0gQ2xhc3MgMzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMg
+SW5jb3JwLmJ5IFJlZi4gTElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjAeFw0x
+MDA5MTgwMDAwMDBaFw0xMjA5MTgyMzU5NTlaMIGXMQswCQYDVQQGEwJVUzETMBEG
+A1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxQNU2FuIEZyYW5jaXNjbzEdMBsGA1UE
+ChQUU2FsZXNmb3JjZS5jb20sIEluYy4xFTATBgNVBAsUDEFwcGxpY2F0aW9uczEl
+MCMGA1UEAxQccHJlcmVsbmExLnByZS5zYWxlc2ZvcmNlLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAuA0EtyNg0v8SuylDgTAP3YvNzCyN0BTeXHujM+ry
+fohuBEIXcGeRqCCHgai+xFfV9TzPNJbL+3zD27o2wgifwR2R+rchA1AyuzC+//i/
+jcB9FuTSge/iGokTfEBt3R8ynT/Koqvmrp+WkWYy6eXK4538YjGq3qZQIbrijncA
+QRcCAwEAAaOCAa0wggGpMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDwGA1UdHwQ1
+MDMwMaAvoC2GK2h0dHA6Ly9TVlJJbnRsLWNybC52ZXJpc2lnbi5jb20vU1ZSSW50
+bC5jcmwwRAYDVR0gBD0wOzA5BgtghkgBhvhFAQcXAzAqMCgGCCsGAQUFBwIBFhxo
+dHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMCgGA1UdJQQhMB8GCWCGSAGG+EIE
+AQYIKwYBBQUHAwEGCCsGAQUFBwMCMHEGCCsGAQUFBwEBBGUwYzAkBggrBgEFBQcw
+AYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMDsGCCsGAQUFBzAChi9odHRwOi8v
+U1ZSSW50bC1haWEudmVyaXNpZ24uY29tL1NWUkludGwtYWlhLmNlcjBuBggrBgEF
+BQcBDARiMGChXqBcMFowWDBWFglpbWFnZS9naWYwITAfMAcGBSsOAwIaBBRLa7ko
+lgYMu9BSOJsprEsHiyEFGDAmFiRodHRwOi8vbG9nby52ZXJpc2lnbi5jb20vdnNs
+b2dvMS5naWYwDQYJKoZIhvcNAQEFBQADgYEAuOM3ulo3N7/lvIj+Hvq0T31Syia2
+g6HeKGsBqM1d8pos8G6JaauUtRTyw8rYXS9tE5qD8e1OhYeTaRlTOqTxmJa3KBMy
+ftLkenryjYB9r4lkQ3v2BT0WfPEqb7ubOlel8fd3oAdokjm9RcfIde2sxqxFAhgL
+QboBaD7CO/aLTlA=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/satveda.pem b/chromium/net/data/ssl/certificates/satveda.pem
new file mode 100644
index 00000000000..4f79703d8c0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/satveda.pem
@@ -0,0 +1,207 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 21120020890699950 (0x4b088c0ed6c8ae)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
+ Validity
+ Not Before: Mar 9 07:19:24 2013 GMT
+ Not After : May 24 09:39:06 2019 GMT
+ Subject: OU=Domain Control Validated, CN=www.satveda.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bb:e0:ea:82:8e:50:bf:ba:94:89:e3:f4:dc:b4:
+ a1:06:91:c1:46:bc:33:37:74:e0:c6:71:e7:f0:09:
+ ec:d8:8e:ac:48:82:3f:b6:b4:49:80:98:04:04:61:
+ f7:ea:d2:ad:23:ed:2b:28:54:f2:14:e2:f4:84:88:
+ 9c:4f:d1:b1:1b:52:98:a6:3e:85:e3:eb:22:df:09:
+ 86:ff:14:9c:41:46:dd:13:ed:d9:f0:5d:a5:fe:7f:
+ 6f:31:6b:a0:50:a5:f2:9a:ba:ea:8c:77:4d:1c:64:
+ 82:7a:ea:f4:54:5b:f3:92:81:5e:5c:b1:04:da:c1:
+ d6:72:7d:e1:e5:ec:ad:53:ae:3d:14:21:44:2e:67:
+ f3:a2:c9:7d:9e:0b:98:4d:89:fc:c8:1e:a6:00:45:
+ 8b:b6:a7:b9:dc:5e:5a:ff:0c:52:c6:92:7e:60:08:
+ d4:8d:34:6c:00:98:bc:43:e9:7b:e1:92:0b:f5:81:
+ f0:48:09:18:5a:35:8a:e2:74:f2:9d:da:48:b0:7d:
+ 02:f8:a4:2b:5e:a0:22:cf:a0:15:9f:fb:ca:4d:8c:
+ f3:26:cb:62:74:a3:04:6e:e2:38:aa:0a:19:42:e8:
+ e3:57:a5:d3:97:64:38:31:89:3e:af:93:af:d6:e3:
+ 60:c1:c3:6a:9c:58:da:16:60:c7:78:01:cf:dc:7c:
+ e1:11
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.godaddy.com/gds1-87.crl
+
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.114413.1.7.23.1
+ CPS: http://certificates.godaddy.com/repository/
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.godaddy.com/
+ CA Issuers - URI:http://certificates.godaddy.com/repository/gd_intermediate.crt
+
+ X509v3 Authority Key Identifier:
+ keyid:FD:AC:61:32:93:6C:45:D6:E2:EE:85:5F:9A:BA:E7:76:99:68:CC:E7
+
+ X509v3 Subject Alternative Name:
+ DNS:www.satveda.com, DNS:satveda.com
+ X509v3 Subject Key Identifier:
+ A7:39:2E:DC:0F:22:D5:D6:C6:B1:3B:35:65:3D:0D:B1:75:5B:F7:69
+ Signature Algorithm: sha1WithRSAEncryption
+ 15:a9:fd:28:f6:cd:d1:f0:2d:d7:1c:df:b5:48:5c:c5:2c:44:
+ 59:ad:ba:3d:bc:08:30:6f:50:a4:9f:0b:05:28:d7:5e:62:87:
+ f9:5d:24:c0:b1:ce:a1:d2:eb:aa:77:9b:01:21:1b:56:dd:e5:
+ 32:18:38:44:24:60:76:14:4d:4a:6a:d2:37:8b:64:45:5a:ba:
+ 4f:bf:b0:33:dd:f6:59:dc:fd:47:a9:3b:4f:29:65:3d:a4:0e:
+ c7:89:22:48:e7:6b:e4:38:b7:d4:e2:27:1f:22:9c:99:b0:bd:
+ b4:59:6d:8d:53:30:fa:28:ef:6c:66:b8:af:6c:9b:93:52:72:
+ 37:b3:2f:c1:bd:73:22:b4:2e:fa:08:fd:0c:95:89:21:eb:01:
+ 34:82:18:15:12:3c:a1:2c:d9:fc:f3:f9:48:1f:09:44:18:b8:
+ 7a:5b:57:ea:10:62:59:90:8c:dc:6f:52:f2:2a:a2:da:fc:2d:
+ b4:8a:fb:11:cd:60:da:f9:dd:31:08:31:04:11:81:4e:4b:8a:
+ 81:40:70:5e:00:99:87:cb:d6:e0:d8:85:fe:4a:2e:97:99:a0:
+ 3d:6e:6f:26:a9:4d:e6:97:cb:c5:09:ef:49:24:c7:96:27:7e:
+ bf:e4:cb:02:f8:00:63:43:7f:ca:05:75:d2:89:7a:f0:25:52:
+ ac:47:fb:e6
+-----BEGIN CERTIFICATE-----
+MIIFRTCCBC2gAwIBAgIHSwiMDtbIrjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
+BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAY
+BgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydGlm
+aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0dvIERhZGR5
+IFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTERMA8GA1UEBRMIMDc5Njky
+ODcwHhcNMTMwMzA5MDcxOTI0WhcNMTkwNTI0MDkzOTA2WjA9MSEwHwYDVQQLExhE
+b21haW4gQ29udHJvbCBWYWxpZGF0ZWQxGDAWBgNVBAMTD3d3dy5zYXR2ZWRhLmNv
+bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALvg6oKOUL+6lInj9Ny0
+oQaRwUa8Mzd04MZx5/AJ7NiOrEiCP7a0SYCYBARh9+rSrSPtKyhU8hTi9ISInE/R
+sRtSmKY+hePrIt8Jhv8UnEFG3RPt2fBdpf5/bzFroFCl8pq66ox3TRxkgnrq9FRb
+85KBXlyxBNrB1nJ94eXsrVOuPRQhRC5n86LJfZ4LmE2J/MgepgBFi7anudxeWv8M
+UsaSfmAI1I00bACYvEPpe+GSC/WB8EgJGFo1iuJ08p3aSLB9AvikK16gIs+gFZ/7
+yk2M8ybLYnSjBG7iOKoKGULo41el05dkODGJPq+Tr9bjYMHDapxY2hZgx3gBz9x8
+4RECAwEAAaOCAbowggG2MA8GA1UdEwEB/wQFMAMBAQAwHQYDVR0lBBYwFAYIKwYB
+BQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIFoDAzBgNVHR8ELDAqMCigJqAk
+hiJodHRwOi8vY3JsLmdvZGFkZHkuY29tL2dkczEtODcuY3JsMFMGA1UdIARMMEow
+SAYLYIZIAYb9bQEHFwEwOTA3BggrBgEFBQcCARYraHR0cDovL2NlcnRpZmljYXRl
+cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzCBgAYIKwYBBQUHAQEEdDByMCQGCCsG
+AQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wSgYIKwYBBQUHMAKGPmh0
+dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS9nZF9pbnRl
+cm1lZGlhdGUuY3J0MB8GA1UdIwQYMBaAFP2sYTKTbEXW4u6FX5q653aZaMznMCcG
+A1UdEQQgMB6CD3d3dy5zYXR2ZWRhLmNvbYILc2F0dmVkYS5jb20wHQYDVR0OBBYE
+FKc5LtwPItXWxrE7NWU9DbF1W/dpMA0GCSqGSIb3DQEBBQUAA4IBAQAVqf0o9s3R
+8C3XHN+1SFzFLERZrbo9vAgwb1CknwsFKNdeYof5XSTAsc6h0uuqd5sBIRtW3eUy
+GDhEJGB2FE1KatI3i2RFWrpPv7Az3fZZ3P1HqTtPKWU9pA7HiSJI52vkOLfU4icf
+IpyZsL20WW2NUzD6KO9sZrivbJuTUnI3sy/BvXMitC76CP0MlYkh6wE0ghgVEjyh
+LNn88/lIHwlEGLh6W1fqEGJZkIzcb1LyKqLa/C20ivsRzWDa+d0xCDEEEYFOS4qB
+QHBeAJmHy9bg2IX+Si6XmaA9bm8mqU3ml8vFCe9JJMeWJ36/5MsC+ABjQ3/KBXXS
+iXrwJVKsR/vm
+-----END CERTIFICATE-----
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 769 (0x301)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
+ Validity
+ Not Before: Nov 16 01:54:37 2006 GMT
+ Not After : Nov 16 01:54:37 2026 GMT
+ Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c4:2d:d5:15:8c:9c:26:4c:ec:32:35:eb:5f:b8:
+ 59:01:5a:a6:61:81:59:3b:70:63:ab:e3:dc:3d:c7:
+ 2a:b8:c9:33:d3:79:e4:3a:ed:3c:30:23:84:8e:b3:
+ 30:14:b6:b2:87:c3:3d:95:54:04:9e:df:99:dd:0b:
+ 25:1e:21:de:65:29:7e:35:a8:a9:54:eb:f6:f7:32:
+ 39:d4:26:55:95:ad:ef:fb:fe:58:86:d7:9e:f4:00:
+ 8d:8c:2a:0c:bd:42:04:ce:a7:3f:04:f6:ee:80:f2:
+ aa:ef:52:a1:69:66:da:be:1a:ad:5d:da:2c:66:ea:
+ 1a:6b:bb:e5:1a:51:4a:00:2f:48:c7:98:75:d8:b9:
+ 29:c8:ee:f8:66:6d:0a:9c:b3:f3:fc:78:7c:a2:f8:
+ a3:f2:b5:c3:f3:b9:7a:91:c1:a7:e6:25:2e:9c:a8:
+ ed:12:65:6e:6a:f6:12:44:53:70:30:95:c3:9c:2b:
+ 58:2b:3d:08:74:4a:f2:be:51:b0:bf:87:d0:4c:27:
+ 58:6b:b5:35:c5:9d:af:17:31:f8:0b:8f:ee:ad:81:
+ 36:05:89:08:98:cf:3a:af:25:87:c0:49:ea:a7:fd:
+ 67:f7:45:8e:97:cc:14:39:e2:36:85:b5:7e:1a:37:
+ fd:16:f6:71:11:9a:74:30:16:fe:13:94:a3:3f:84:
+ 0d:4f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ FD:AC:61:32:93:6C:45:D6:E2:EE:85:5F:9A:BA:E7:76:99:68:CC:E7
+ X509v3 Authority Key Identifier:
+ keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.godaddy.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://certificates.godaddy.com/repository/gdroot.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://certificates.godaddy.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ d2:86:c0:ec:bd:f9:a1:b6:67:ee:66:0b:a2:06:3a:04:50:8e:
+ 15:72:ac:4a:74:95:53:cb:37:cb:44:49:ef:07:90:6b:33:d9:
+ 96:f0:94:56:a5:13:30:05:3c:85:32:21:7b:c9:c7:0a:a8:24:
+ a4:90:de:46:d3:25:23:14:03:67:c2:10:d6:6f:0f:5d:7b:7a:
+ cc:9f:c5:58:2a:c1:c4:9e:21:a8:5a:f3:ac:a4:46:f3:9e:e4:
+ 63:cb:2f:90:a4:29:29:01:d9:72:2c:29:df:37:01:27:bc:4f:
+ ee:68:d3:21:8f:c0:b3:e4:f5:09:ed:d2:10:aa:53:b4:be:f0:
+ cc:59:0b:d6:3b:96:1c:95:24:49:df:ce:ec:fd:a7:48:91:14:
+ 45:0e:3a:36:6f:da:45:b3:45:a2:41:c9:d4:d7:44:4e:3e:b9:
+ 74:76:d5:a2:13:55:2c:c6:87:a3:b5:99:ac:06:84:87:7f:75:
+ 06:fc:bf:14:4c:0e:cc:6e:c4:df:3d:b7:12:71:f4:e8:f1:51:
+ 40:22:28:49:e0:1d:4b:87:a8:34:cc:06:a2:dd:12:5a:d1:86:
+ 36:64:03:35:6f:6f:77:6e:eb:f2:85:50:98:5e:ab:03:53:ad:
+ 91:23:63:1f:16:9c:cd:b9:b2:05:63:3a:e1:f4:68:1b:17:05:
+ 35:95:53:ee
+-----BEGIN CERTIFICATE-----
+MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
+ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
+MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
+QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
+b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
+b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
+KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
+VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
+SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
+cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
+6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
+MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
+kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
+BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
+c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
+AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
+BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
+OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
+A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
+0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
+RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
+qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
+U+4=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/spdy_pooling.pem b/chromium/net/data/ssl/certificates/spdy_pooling.pem
new file mode 100644
index 00000000000..b6588b59e7d
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/spdy_pooling.pem
@@ -0,0 +1,53 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ e4:6f:98:d6:b9:79:70:02
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Validity
+ Not Before: Sep 12 21:47:23 2012 GMT
+ Not After : Sep 10 21:47:23 2022 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d2:e6:4b:b7:a0:a3:5e:f7:af:fa:36:12:59:1e:
+ c8:bd:0c:ef:eb:be:71:a2:79:88:9b:7e:99:46:80:
+ bf:53:31:1e:d0:88:a1:38:22:8c:86:f1:12:7c:6b:
+ bd:2d:75:53:3a:61:65:2f:2e:98:f3:03:8b:6b:44:
+ 93:ef:a2:a9:67:aa:cc:b6:43:c4:ab:c7:a0:27:8b:
+ 7f:a6:7f:ea:d6:7f:0f:0a:54:52:f8:d2:cd:21:f2:
+ 07:6c:f4:e1:1c:e6:29:a8:79:25:9e:e5:31:38:1e:
+ 45:d0:2c:48:6b:1d:2a:fd:71:07:bf:03:1a:c1:0b:
+ 39:0f:3e:9a:98:4f:2c:fa:83
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ DNS:www.example.org, DNS:mail.example.org, DNS:mail.example.com
+ Signature Algorithm: sha1WithRSAEncryption
+ 8e:b2:bb:67:8d:15:c5:ce:85:ec:b4:15:80:03:5c:f3:3c:b4:
+ 79:0c:27:e9:0d:4c:3d:80:d7:4f:b0:38:7c:f1:b2:0e:59:8e:
+ 81:66:f4:b7:21:0b:ca:05:e8:0e:a8:74:c2:b5:39:47:16:7d:
+ 01:43:8d:ac:10:21:a0:25:a9:66:b6:29:5d:ee:50:ba:4b:70:
+ fb:02:5d:fb:20:c5:7d:90:ae:24:2a:f1:32:25:cd:4f:7b:0a:
+ c9:31:b3:7e:3f:49:b4:4d:2e:14:24:73:7d:05:6a:f7:e3:92:
+ cc:34:e3:46:b8:08:88:50:8b:d8:9d:cb:22:7c:5a:9d:4d:2b:
+ 1f:55
+-----BEGIN CERTIFICATE-----
+MIICgDCCAemgAwIBAgIJAORvmNa5eXACMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
+aWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEwHhcNMTIw
+OTEyMjE0NzIzWhcNMjIwOTEwMjE0NzIzWjBgMQswCQYDVQQGEwJVUzETMBEGA1UE
+CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwH
+VGVzdCBDQTESMBAGA1UEAwwJMTI3LjAuMC4xMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDS5ku3oKNe96/6NhJZHsi9DO/rvnGieYibfplGgL9TMR7QiKE4IoyG
+8RJ8a70tdVM6YWUvLpjzA4trRJPvoqlnqsy2Q8Srx6Ani3+mf+rWfw8KVFL40s0h
+8gds9OEc5imoeSWe5TE4HkXQLEhrHSr9cQe/AxrBCzkPPpqYTyz6gwIDAQABo0Iw
+QDA+BgNVHREENzA1gg93d3cuZXhhbXBsZS5vcmeCEG1haWwuZXhhbXBsZS5vcmeC
+EG1haWwuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADgYEAjrK7Z40Vxc6F7LQV
+gANc8zy0eQwn6Q1MPYDXT7A4fPGyDlmOgWb0tyELygXoDqh0wrU5RxZ9AUONrBAh
+oCWpZrYpXe5Quktw+wJd+yDFfZCuJCrxMiXNT3sKyTGzfj9JtE0uFCRzfQVq9+OS
+zDTjRrgIiFCL2J3LInxanU0rH1U=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/subjectAltName_sanity_check.pem b/chromium/net/data/ssl/certificates/subjectAltName_sanity_check.pem
new file mode 100644
index 00000000000..46cf58de0cb
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/subjectAltName_sanity_check.pem
@@ -0,0 +1,54 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ f2:f1:e7:8b:cf:09:30:f1
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Validity
+ Not Before: Apr 3 00:46:54 2012 GMT
+ Not After : Apr 1 00:46:54 2022 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c8:0e:13:bb:da:d5:5a:d4:68:a2:11:90:ae:c3:
+ b3:f9:72:52:7d:e9:73:5c:49:60:ef:d3:49:05:9a:
+ c7:4e:01:4f:b0:c8:4c:18:34:2f:7b:84:27:ad:94:
+ 12:9b:e7:3d:38:6b:49:15:55:f6:c7:3a:8d:03:ec:
+ 3e:59:90:5c:b9:a6:41:af:f0:12:b8:87:b9:54:4d:
+ 1e:18:ba:41:96:d0:f3:bb:a0:d6:80:8e:29:10:72:
+ eb:3c:4c:c0:e2:f7:d8:61:2f:d8:63:c7:a7:79:f5:
+ 74:e0:2a:f0:5d:3e:eb:a2:36:09:4b:5d:35:31:56:
+ 1c:86:0e:8a:22:ad:1b:3f:27
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.2, IP Address:FE80:0:0:0:0:0:0:1, DNS:test.example, email:test@test.example, othername:<unsupported>, DirName:/CN=127.0.0.3
+ Signature Algorithm: sha1WithRSAEncryption
+ 32:46:49:70:be:e4:db:05:0e:7e:7a:e4:ea:5c:90:c6:4c:65:
+ 2d:03:ac:fb:d1:de:e4:26:e5:83:dc:5a:c8:4f:ff:b5:10:4e:
+ 39:21:7f:c8:37:f3:c6:7a:de:96:b3:30:e7:c7:87:6d:75:1e:
+ 14:30:17:6b:d2:76:0b:b8:43:39:c4:63:4c:50:8e:e1:0f:09:
+ ff:6c:7d:ab:c8:97:46:e8:04:70:9d:f5:e5:8c:b6:8c:b7:3d:
+ 8e:0f:59:1f:6a:fd:03:c2:be:a1:40:b7:9b:38:ca:55:f5:18:
+ c3:0d:35:01:12:a0:8d:ba:1b:41:a3:6e:68:8c:cf:52:f9:96:
+ 90:64
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIJAPLx54vPCTDxMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
+aWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAkxMjcuMC4wLjEwHhcNMTIw
+NDAzMDA0NjU0WhcNMjIwNDAxMDA0NjU0WjBgMQswCQYDVQQGEwJVUzETMBEGA1UE
+CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwH
+VGVzdCBDQTESMBAGA1UEAwwJMTI3LjAuMC4xMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDIDhO72tVa1GiiEZCuw7P5clJ96XNcSWDv00kFmsdOAU+wyEwYNC97
+hCetlBKb5z04a0kVVfbHOo0D7D5ZkFy5pkGv8BK4h7lUTR4YukGW0PO7oNaAjikQ
+cus8TMDi99hhL9hjx6d59XTgKvBdPuuiNglLXTUxVhyGDooirRs/JwIDAQABo3Iw
+cDBuBgNVHREEZzBlhwR/AAAChxD+gAAAAAAAAAAAAAAAAAABggx0ZXN0LmV4YW1w
+bGWBEXRlc3RAdGVzdC5leGFtcGxloBIGAyoDBKALDAlpZ25vcmUgbWWkFjAUMRIw
+EAYDVQQDDAkxMjcuMC4wLjMwDQYJKoZIhvcNAQEFBQADgYEAMkZJcL7k2wUOfnrk
+6lyQxkxlLQOs+9He5Cblg9xayE//tRBOOSF/yDfzxnrelrMw58eHbXUeFDAXa9J2
+C7hDOcRjTFCO4Q8J/2x9q8iXRugEcJ315Yy2jLc9jg9ZH2r9A8K+oUC3mzjKVfUY
+ww01ARKgjbobQaNuaIzPUvmWkGQ=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/test_mail_google_com.pem b/chromium/net/data/ssl/certificates/test_mail_google_com.pem
new file mode 100644
index 00000000000..d72d562ba5b
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/test_mail_google_com.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEXDCCAkSgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBgMRAwDgYDVQQDEwdUZXN0
+IENBMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN
+TW91bnRhaW4gVmlldzESMBAGA1UEChMJQ2VydCBUZXN0MB4XDTExMTAxMTE5MTYy
+MVoXDTEzMDcyNzAwMDAwMFowbTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm
+b3JuaWExEjAQBgNVBAoTCUNlcnQgVGVzdDEbMBkGA1UECxMSR29hdCBUZWxlcG9y
+dGF0aW9uMRgwFgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wXDANBgkqhkiG9w0BAQEF
+AANLADBIAkEAvy9N7zZ2yuMamRGUDc7KiLHq+OwVkfmvDRsrj77+MMR1DkUx1Qez
+s+tKtm6dyi5mariRL5ChbgIBqNYhb/cecQIDAQABo4HbMIHYMBIGA1UdEwEB/wQI
+MAYBAf8CAQEwDgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBQQFF3/oZUdPuM69r0i
+Gl5tEK5e7TCBkgYDVR0jBIGKMIGHgBRdzn+Z49QZQTFPxs+xJfVar+OXMaFkpGIw
+YDEQMA4GA1UEAxMHVGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm
+b3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEjAQBgNVBAoTCUNlcnQgVGVz
+dIIJANRRk9Q/3tlOMA0GCSqGSIb3DQEBBQUAA4ICAQCPO6wgG6cFmu5ZgAN9q+dS
+BVrMiJhHj62Tlw7qNjD+VAfidTTtQPM8T0y2LtNe2epO6jDOyIpRwsKkFi5mozcs
+Dd3CfXAs7fkdY4ZnAxjXhhk1fvMkomR6CfTHEwcGkfwVm2MDozZmYbS83OP+E82B
++yKA41ppbw75/meJzH4nSECBd/Whzi8AuuX6e3bSae6XEAdhBQoLHyNAvZ0IEeCb
+sI3DvXdpIP0LyYJH6+F/KG5Jugby44HuAK1MBn9/f5tYplucOj5cyw/fYWd8REGD
+Ob71lh9/eZVcYjvbF6LxlizZQ+DNHV2QkHvSQqAACDbpFCUcU9KO5xvN8RaVtFmJ
+sDuHtxDDXFcXHhLh6bcC2KFrsmwEV68jmek0++eMa/W99ADzNWUWCmGoyZQafP2e
+eqQ6Ry8wgH+ZVkhQaaGk4fCKZATpX7//qdj7IzO52Kpx0dwsW7mHxPjdRKQzThkn
+lwFSiKByJDMOm9JbjpGf52JsCX4OSFuHCRcc2TB867xKRfBoAXE06fMS2lTwAcQh
+3vdzO0gEv9WOvdvehvngcrWzGwIdGaP6BBXi+9b5wPBR8ravMPAgQBXg01vME+/+
+TkpEaCFACOttO0YkVqG6lFFT1wigsh3k4/+Eyh/RsLTsFObZBJsMLetbY/XzwhTf
+LyeXa2sT1sk6l+EfrzWS1Q==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/thawte.single.pem b/chromium/net/data/ssl/certificates/thawte.single.pem
new file mode 100644
index 00000000000..d3264590e99
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/thawte.single.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIzCCAoygAwIBAgIEMAAAAjANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsTLkNsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNTEzMDAw
+MDAwWhcNMTQwNTEyMjM1OTU5WjBMMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
+d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBD
+QTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1NNn0I0Vf67NMf59HZGhPwtx
+PKzMyGT7Y/wySweUvW+Aui/hBJPAM/wJMyPpC3QrccQDxtLN4i/1CWPN/0ilAL/g
+5/OIty0y3pg25gqtAHvEZEo7hHUD8nCSfQ5i9SGraTaEMXWQ+L/HbIgbBpV8yeWo
+3nWhLHpo39XKHIdYYBkCAwEAAaOB/jCB+zASBgNVHRMBAf8ECDAGAQH/AgEAMAsG
+A1UdDwQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAX
+BgNVBAMTEFByaXZhdGVMYWJlbDMtMTUwMQYDVR0fBCowKDAmoCSgIoYgaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwMgYIKwYBBQUHAQEEJjAkMCIGCCsG
+AQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMDQGA1UdJQQtMCsGCCsGAQUF
+BwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEB
+BQUAA4GBAFWsY+reod3SkF+fC852vhNRj5PZBSvIG3dLrWlQoe7e3P3bB+noOZTc
+q3J5Lwa/q4FwxKjt6lM07e8eU9kGx1Yr0Vz00YqOtCuxN5BICEIlxT6Ky3/rbwTR
+bcV0oveifHtgPHfNDs5IAn8BL7abN+AqKjbc1YXWrOU/VG+WHgWv
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/chromium/net/data/ssl/certificates/unescaped.pem b/chromium/net/data/ssl/certificates/unescaped.pem
new file mode 100644
index 00000000000..33dbb40ef0b
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/unescaped.pem
@@ -0,0 +1,62 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ ed:cd:27:0f:4c:ca:cc:fc
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=127.0.0.1, L=Mountain View, ST=California/streetAddress=1600 Amphitheatre Parkway, C=US, O=Chromium = "net_unittests", OU=net_unittests, OU=Chromium
+ Validity
+ Not Before: Dec 2 03:56:43 2011 GMT
+ Not After : Jan 1 03:56:43 2012 GMT
+ Subject: CN=127.0.0.1, L=Mountain View, ST=California/streetAddress=1600 Amphitheatre Parkway, C=US, O=Chromium = "net_unittests", OU=net_unittests, OU=Chromium
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:ac:25:6c:de:22:4f:be:77:07:3a:6c:f9:5f:e6:
+ 0a:ce:69:f7:ca:c5:b8:08:2b:b8:b0:04:12:55:fe:
+ 65:4c:c2:aa:b9:3d:b2:87:f5:59:c3:72:ac:a9:d4:
+ 1e:0b:ec:7f:2e:df:4c:ad:e7:cc:52:93:b4:ed:0a:
+ 99:40:7e:6a:35:c3:b0:8b:93:c7:e5:97:54:b9:7f:
+ 68:26:04:17:a6:b4:50:9e:d6:d7:6b:19:a1:ce:0b:
+ 5e:73:80:a6:b9:ef:5d:34:8e:6f:f7:8c:de:cf:78:
+ cd:16:93:30:23:c3:5c:8c:9f:78:ce:18:c6:0f:e1:
+ 32:76:8a:c4:c4:54:30:56:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 38:85:CF:3E:10:D5:47:FC:08:81:FA:85:7F:84:7A:2F:3D:69:2B:A6
+ X509v3 Authority Key Identifier:
+ keyid:38:85:CF:3E:10:D5:47:FC:08:81:FA:85:7F:84:7A:2F:3D:69:2B:A6
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 32:0a:90:29:b1:9f:c0:c7:55:da:37:8f:5b:b8:09:a0:97:65:
+ 15:3a:79:d1:3e:24:f4:44:ad:3f:eb:84:b4:ae:e3:7a:ba:43:
+ 8c:d4:df:ef:ca:46:c5:99:c4:99:b5:ae:7e:2c:a9:85:05:d0:
+ 8f:07:d2:ee:9e:72:b0:e0:87:51:5c:e8:f4:5a:a1:44:25:a5:
+ 47:6a:67:b3:79:2e:66:7d:93:7d:f7:cf:31:13:c1:fc:af:62:
+ c6:ec:22:14:50:7f:d2:38:ff:5d:11:d5:58:c8:5e:43:08:bb:
+ ca:9c:45:78:f4:28:08:cc:98:75:1d:4c:d2:43:a5:34:f0:86:
+ 56:37
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAr2gAwIBAgIJAO3NJw9Mysz8MA0GCSqGSIb3DQEBBQUAMIHCMRIwEAYD
+VQQDDAkxMjcuMC4wLjExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEzARBgNVBAgM
+CkNhbGlmb3JuaWExIjAgBgNVBAkMGTE2MDAgQW1waGl0aGVhdHJlIFBhcmt3YXkx
+CzAJBgNVBAYTAlVTMSMwIQYDVQQKDBpDaHJvbWl1bSA9ICJuZXRfdW5pdHRlc3Rz
+IjEWMBQGA1UECwwNbmV0X3VuaXR0ZXN0czERMA8GA1UECwwIQ2hyb21pdW0wHhcN
+MTExMjAyMDM1NjQzWhcNMTIwMTAxMDM1NjQzWjCBwjESMBAGA1UEAwwJMTI3LjAu
+MC4xMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQIDApDYWxpZm9ybmlh
+MSIwIAYDVQQJDBkxNjAwIEFtcGhpdGhlYXRyZSBQYXJrd2F5MQswCQYDVQQGEwJV
+UzEjMCEGA1UECgwaQ2hyb21pdW0gPSAibmV0X3VuaXR0ZXN0cyIxFjAUBgNVBAsM
+DW5ldF91bml0dGVzdHMxETAPBgNVBAsMCENocm9taXVtMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQCsJWzeIk++dwc6bPlf5grOaffKxbgIK7iwBBJV/mVMwqq5
+PbKH9VnDcqyp1B4L7H8u30yt58xSk7TtCplAfmo1w7CLk8fll1S5f2gmBBemtFCe
+1tdrGaHOC15zgKa57100jm/3jN7PeM0WkzAjw1yMn3jOGMYP4TJ2isTEVDBWOQID
+AQABo1AwTjAdBgNVHQ4EFgQUOIXPPhDVR/wIgfqFf4R6Lz1pK6YwHwYDVR0jBBgw
+FoAUOIXPPhDVR/wIgfqFf4R6Lz1pK6YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B
+AQUFAAOBgQAyCpApsZ/Ax1XaN49buAmgl2UVOnnRPiT0RK0/64S0ruN6ukOM1N/v
+ykbFmcSZta5+LKmFBdCPB9LunnKw4IdRXOj0WqFEJaVHamezeS5mfZN9988xE8H8
+r2LG7CIUUH/SOP9dEdVYyF5DCLvKnEV49CgIzJh1HUzSQ6U08IZWNw==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/unittest.key.bin b/chromium/net/data/ssl/certificates/unittest.key.bin
new file mode 100644
index 00000000000..2ec712b3f17
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/unittest.key.bin
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/unittest.originbound.der b/chromium/net/data/ssl/certificates/unittest.originbound.der
new file mode 100644
index 00000000000..c77e126a199
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/unittest.originbound.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/unittest.originbound.key.der b/chromium/net/data/ssl/certificates/unittest.originbound.key.der
new file mode 100644
index 00000000000..1b4170dfe81
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/unittest.originbound.key.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/unittest.selfsigned.der b/chromium/net/data/ssl/certificates/unittest.selfsigned.der
new file mode 100644
index 00000000000..48c8d743b0a
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/unittest.selfsigned.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2011.pem b/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2011.pem
new file mode 100644
index 00000000000..27dc85bd8a7
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2011.pem
@@ -0,0 +1,71 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 25:4b:8a:85:38:42:cc:e3:58:f8:c5:dd:ae:22:6e:a4
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Apr 17 00:00:00 1997 GMT
+ Not After : Oct 24 23:59:59 2011 GMT
+ Subject: O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign International Server CA - Class 3, OU=www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD.(c)97 VeriSign
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d8:82:80:e8:d6:19:02:7d:1f:85:18:39:25:a2:
+ 65:2b:e1:bf:d4:05:d3:bc:e6:36:3b:aa:f0:4c:6c:
+ 5b:b6:e7:aa:3c:73:45:55:b2:f1:bd:ea:97:42:ed:
+ 9a:34:0a:15:d4:a9:5c:f5:40:25:dd:d9:07:c1:32:
+ b2:75:6c:c4:ca:bb:a3:fe:56:27:71:43:aa:63:f5:
+ 30:3e:93:28:e5:fa:f1:09:3b:f3:b7:4d:4e:39:f7:
+ 5c:49:5a:b8:c1:1d:d3:b2:8a:fe:70:30:95:42:cb:
+ fe:2b:51:8b:5a:3c:3a:f9:22:4f:90:b2:02:a7:53:
+ 9c:4f:34:e7:ab:04:b2:7b:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.1.1
+ CPS: https://www.verisign.com/CPS
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ X509v3 CRL Distribution Points:
+ URI:http://crl.verisign.com/pca3.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 08:01:ec:e4:68:94:03:42:f1:73:f1:23:a2:3a:de:e9:f1:da:
+ c6:54:c4:23:3e:86:ea:cf:6a:3a:33:ab:ea:9c:04:14:07:36:
+ 06:0b:f9:88:6f:d5:13:ee:29:2b:c3:e4:72:8d:44:ed:d1:ac:
+ 20:09:2d:e1:f6:e1:19:05:38:b0:3d:0f:9f:7f:f8:9e:02:dc:
+ 86:02:86:61:4e:26:5f:5e:9f:92:1e:0c:24:a4:f5:d0:70:13:
+ cf:26:c3:43:3d:49:1d:9e:82:2e:52:5f:bc:3e:c6:66:29:01:
+ 8e:4e:92:2c:bc:46:75:03:82:ac:73:e9:d9:7e:0b:67:ef:54:
+ 52:1a
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIQJUuKhThCzONY+MXdriJupDANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNOTcwNDE3MDAwMDAwWhcNMTExMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
+BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
+MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
+TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
+veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
+OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
+4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
+KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
+HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
+ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
+oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
+BQUAA4GBAAgB7ORolANC8XPxI6I63unx2sZUxCM+hurPajozq+qcBBQHNgYL+Yhv
+1RPuKSvD5HKNRO3RrCAJLeH24RkFOLA9D59/+J4C3IYChmFOJl9en5IeDCSk9dBw
+E88mw0M9SR2egi5SX7w+xmYpAY5Okiy8RnUDgqxz6dl+C2fvVFIa
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2016.pem b/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2016.pem
new file mode 100644
index 00000000000..195133e798a
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/verisign_intermediate_ca_2016.pem
@@ -0,0 +1,71 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 46:fc:eb:ba:b4:d0:2f:0f:92:60:98:23:3f:93:07:8f
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Apr 17 00:00:00 1997 GMT
+ Not After : Oct 24 23:59:59 2016 GMT
+ Subject: O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign International Server CA - Class 3, OU=www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD.(c)97 VeriSign
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d8:82:80:e8:d6:19:02:7d:1f:85:18:39:25:a2:
+ 65:2b:e1:bf:d4:05:d3:bc:e6:36:3b:aa:f0:4c:6c:
+ 5b:b6:e7:aa:3c:73:45:55:b2:f1:bd:ea:97:42:ed:
+ 9a:34:0a:15:d4:a9:5c:f5:40:25:dd:d9:07:c1:32:
+ b2:75:6c:c4:ca:bb:a3:fe:56:27:71:43:aa:63:f5:
+ 30:3e:93:28:e5:fa:f1:09:3b:f3:b7:4d:4e:39:f7:
+ 5c:49:5a:b8:c1:1d:d3:b2:8a:fe:70:30:95:42:cb:
+ fe:2b:51:8b:5a:3c:3a:f9:22:4f:90:b2:02:a7:53:
+ 9c:4f:34:e7:ab:04:b2:7b:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.1.1
+ CPS: https://www.verisign.com/CPS
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ X509v3 CRL Distribution Points:
+ URI:http://crl.verisign.com/pca3.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 40:8e:49:97:96:8a:73:dd:8e:4d:ef:3e:61:b7:ca:a0:62:ad:
+ f4:0e:0a:bb:75:3d:e2:6e:d8:2c:c7:bf:f4:b9:8c:36:9b:ca:
+ a2:d0:9c:72:46:39:f6:a6:82:03:65:11:c4:bc:bf:2d:a6:f5:
+ d9:3b:0a:b5:98:fa:b3:78:b9:1e:f2:2b:4c:62:d5:fd:b2:7a:
+ 1d:df:33:fd:73:f9:a5:d8:2d:8c:2a:ea:d1:fc:b0:28:b6:e9:
+ 49:48:13:4b:83:8a:1b:48:7b:24:f7:38:de:6f:41:54:b8:ab:
+ 57:6b:06:df:c7:a2:d4:a9:f6:f1:36:62:80:88:f2:8b:75:d6:
+ 80:71
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIQRvzrurTQLw+SYJgjP5MHjzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNOTcwNDE3MDAwMDAwWhcNMTYxMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
+BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
+MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
+TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
+veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
+OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
+4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
+KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
+HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
+ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
+oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
+BQUAA4GBAECOSZeWinPdjk3vPmG3yqBirfQOCrt1PeJu2CzHv/S5jDabyqLQnHJG
+OfamggNlEcS8vy2m9dk7CrWY+rN4uR7yK0xi1f2yeh3fM/1z+aXYLYwq6tH8sCi2
+6UlIE0uDihtIeyT3ON5vQVS4q1drBt/HotSp9vE2YoCI8ot11oBx
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md2_ee.pem b/chromium/net/data/ssl/certificates/weak_digest_md2_ee.pem
new file mode 100644
index 00000000000..6475ccc80b4
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md2_ee.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4 (0x4)
+ Signature Algorithm: md2WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Intermediate CA
+ Validity
+ Not Before: Oct 26 03:46:49 2011 GMT
+ Not After : Oct 23 03:46:49 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38:
+ 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9:
+ 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf:
+ 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13:
+ be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d:
+ fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee:
+ 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db:
+ 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57:
+ dc:d0:de:df:29:72:45:6f:e3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD
+ X509v3 Authority Key Identifier:
+ keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: md2WithRSAEncryption
+ 87:d2:29:b3:6b:ba:36:99:ac:56:47:d8:7d:63:9e:74:a2:b5:
+ 42:5e:2b:96:08:f8:ab:e2:ce:ea:99:21:47:25:2c:55:f2:db:
+ 9d:d7:ed:d9:68:ba:09:90:b1:43:64:be:af:ef:9a:b4:10:86:
+ 99:85:7f:68:fe:aa:fd:d4:6a:f1:68:e9:8f:61:d8:46:21:e4:
+ 17:4c:89:db:82:37:36:8d:7f:93:4a:63:b1:da:ba:6b:19:ad:
+ 34:8b:f8:11:c3:25:14:2a:4e:7b:75:6b:03:79:c1:e5:1a:5b:
+ ff:b4:91:47:4f:48:91:68:33:c7:3e:a5:95:45:81:2b:0d:35:
+ 42:c4
+-----BEGIN CERTIFICATE-----
+MIICiDCCAfGgAwIBAgIBBDANBgkqhkiG9w0BAQIFADAxMS8wLQYDVQQDDCZUZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2
+NDlaFw0yMTEwMjMwMzQ2NDlaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
+Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
+AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI
+6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN
+DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV
+HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME
+GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQIFAAOBgQCH0imz
+a7o2maxWR9h9Y550orVCXiuWCPir4s7qmSFHJSxV8tud1+3ZaLoJkLFDZL6v75q0
+EIaZhX9o/qr91GrxaOmPYdhGIeQXTInbgjc2jX+TSmOx2rprGa00i/gRwyUUKk57
+dWsDecHlGlv/tJFHT0iRaDPHPqWVRYErDTVCxA==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md2_intermediate.pem b/chromium/net/data/ssl/certificates/weak_digest_md2_intermediate.pem
new file mode 100644
index 00000000000..2f2765d02c0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md2_intermediate.pem
@@ -0,0 +1,57 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4 (0x4)
+ Signature Algorithm: md2WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Root CA
+ Validity
+ Not Before: Oct 26 03:46:49 2011 GMT
+ Not After : Oct 23 03:46:49 2021 GMT
+ Subject: CN=Test Deprecated Digest Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30:
+ 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26:
+ 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63:
+ 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89:
+ 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd:
+ b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78:
+ 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58:
+ f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49:
+ 62:d4:53:b0:d8:9c:34:47:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+ X509v3 Authority Key Identifier:
+ keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: md2WithRSAEncryption
+ 95:17:b3:5f:81:5b:9e:d6:e9:de:67:0e:a7:01:2f:b7:f8:db:
+ 13:25:6b:a3:15:2d:53:08:c6:20:65:9d:8f:e9:9e:e4:bc:87:
+ 78:59:f6:1f:f4:0e:85:c7:a8:c6:c8:6d:65:7e:b9:f4:73:9b:
+ 9f:70:2b:b2:0d:03:06:c5:52:5f:59:87:b5:2b:d0:5c:0d:ee:
+ 8f:40:cd:eb:95:f2:0e:f4:51:a8:e8:76:17:82:71:1a:d1:ea:
+ 99:54:e4:b7:75:27:54:76:36:6f:c0:4d:5d:fa:bb:98:08:1e:
+ d4:95:d1:9a:c7:35:83:d5:a1:79:2a:1f:78:b4:2b:de:17:93:
+ 0c:1b
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZygAwIBAgIBBDANBgkqhkiG9w0BAQIFADApMScwJQYDVQQDDB5UZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ5WhcNMjEx
+MDIzMDM0NjQ5WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu
+dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ
+RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z
+2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5
++lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU
+eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+AgUAA4GBAJUXs1+BW57W6d5nDqcBL7f42xMla6MVLVMIxiBlnY/pnuS8h3hZ9h/0
+DoXHqMbIbWV+ufRzm59wK7INAwbFUl9Zh7Ur0FwN7o9AzeuV8g70UajodheCcRrR
+6plU5Ld1J1R2Nm/ATV36u5gIHtSV0ZrHNYPVoXkqH3i0K94Xkwwb
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md2_root.pem b/chromium/net/data/ssl/certificates/weak_digest_md2_root.pem
new file mode 100644
index 00000000000..140174d0326
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md2_root.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICEjCCAXugAwIBAgIJAMq0TW/f2gFbMA0GCSqGSIb3DQEBAgUAMCkxJzAlBgNV
+BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2
+NDlaFw0yMTEwMjMwMzQ2NDlaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE
+aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj
+BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv
+HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00
+P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2
+gctDMA0GCSqGSIb3DQEBAgUAA4GBABnPJVnXJXtImcjcBj31JelbPkLgt8HHjxa+
+LOMNZKIc9d6KWdjMoTNz7Y9dAKiLAJmPp9QAKU4cu0voWRK27O8CjR9Ng7SpfuZ7
+bQ4P22TlcVViAq56+bz/DFRabwBAZtndoawyn04r4Lo/3n/nEONeVTIqsixjN5Au
+0snKiMJj
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md4_ee.pem b/chromium/net/data/ssl/certificates/weak_digest_md4_ee.pem
new file mode 100644
index 00000000000..6ea4b257d5c
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md4_ee.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 3 (0x3)
+ Signature Algorithm: md4WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Intermediate CA
+ Validity
+ Not Before: Oct 26 03:46:49 2011 GMT
+ Not After : Oct 23 03:46:49 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38:
+ 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9:
+ 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf:
+ 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13:
+ be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d:
+ fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee:
+ 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db:
+ 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57:
+ dc:d0:de:df:29:72:45:6f:e3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD
+ X509v3 Authority Key Identifier:
+ keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: md4WithRSAEncryption
+ a5:f6:ae:83:a1:44:5a:dd:c4:91:a2:d6:88:d8:c6:d1:e5:6d:
+ c8:71:7a:43:3e:e2:ce:42:a4:7d:94:16:5d:0a:df:33:e3:ea:
+ c9:22:e3:52:9d:f7:72:3e:24:d5:78:38:67:9f:2d:46:cb:73:
+ c5:1f:eb:4b:02:5c:25:41:e0:c5:07:03:4c:4c:55:87:db:32:
+ d0:2e:3e:aa:d4:a6:69:75:12:75:2e:b6:98:24:0e:18:c4:1c:
+ 60:aa:c5:19:c1:1c:ad:ba:f4:c8:c0:55:2b:61:7d:a4:f4:c6:
+ 73:0d:61:7e:04:42:e2:69:8d:9c:9d:83:22:e4:cc:cc:3f:b5:
+ 2a:6d
+-----BEGIN CERTIFICATE-----
+MIICiDCCAfGgAwIBAgIBAzANBgkqhkiG9w0BAQMFADAxMS8wLQYDVQQDDCZUZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2
+NDlaFw0yMTEwMjMwMzQ2NDlaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
+Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
+AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI
+6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN
+DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV
+HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME
+GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQMFAAOBgQCl9q6D
+oURa3cSRotaI2MbR5W3IcXpDPuLOQqR9lBZdCt8z4+rJIuNSnfdyPiTVeDhnny1G
+y3PFH+tLAlwlQeDFBwNMTFWH2zLQLj6q1KZpdRJ1LraYJA4YxBxgqsUZwRytuvTI
+wFUrYX2k9MZzDWF+BELiaY2cnYMi5MzMP7UqbQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md4_intermediate.pem b/chromium/net/data/ssl/certificates/weak_digest_md4_intermediate.pem
new file mode 100644
index 00000000000..2ba01dd82cf
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md4_intermediate.pem
@@ -0,0 +1,57 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 3 (0x3)
+ Signature Algorithm: md4WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Root CA
+ Validity
+ Not Before: Oct 26 03:46:49 2011 GMT
+ Not After : Oct 23 03:46:49 2021 GMT
+ Subject: CN=Test Deprecated Digest Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30:
+ 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26:
+ 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63:
+ 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89:
+ 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd:
+ b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78:
+ 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58:
+ f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49:
+ 62:d4:53:b0:d8:9c:34:47:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+ X509v3 Authority Key Identifier:
+ keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: md4WithRSAEncryption
+ 7e:ca:14:3d:14:04:f4:a4:1a:cf:b5:c6:c7:c2:d3:e7:68:08:
+ 55:1f:fa:93:28:fa:34:aa:97:29:f7:31:6f:30:a4:25:bd:c5:
+ fe:28:3d:a9:92:b0:4f:ca:24:3f:7b:1a:16:2e:0d:08:73:8e:
+ ca:9f:50:da:e9:64:4f:bd:31:c4:72:89:98:8d:55:55:57:96:
+ 6a:e0:5e:00:12:07:8b:3a:30:06:9a:47:a5:94:39:74:a0:f7:
+ e1:00:48:2a:90:08:84:80:e3:6b:83:91:c6:74:d8:d9:c2:72:
+ c7:b9:6e:33:7f:38:46:c1:26:14:5c:1b:85:a3:aa:bb:72:a0:
+ 84:b2
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZygAwIBAgIBAzANBgkqhkiG9w0BAQMFADApMScwJQYDVQQDDB5UZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ5WhcNMjEx
+MDIzMDM0NjQ5WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu
+dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ
+RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z
+2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5
++lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU
+eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+AwUAA4GBAH7KFD0UBPSkGs+1xsfC0+doCFUf+pMo+jSqlyn3MW8wpCW9xf4oPamS
+sE/KJD97GhYuDQhzjsqfUNrpZE+9McRyiZiNVVVXlmrgXgASB4s6MAaaR6WUOXSg
+9+EASCqQCISA42uDkcZ02NnCcse5bjN/OEbBJhRcG4WjqrtyoISy
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md4_root.pem b/chromium/net/data/ssl/certificates/weak_digest_md4_root.pem
new file mode 100644
index 00000000000..4d5fc291aa0
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md4_root.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICEjCCAXugAwIBAgIJAPqB3U0Vl/N1MA0GCSqGSIb3DQEBAwUAMCkxJzAlBgNV
+BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2
+NDlaFw0yMTEwMjMwMzQ2NDlaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE
+aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj
+BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv
+HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00
+P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2
+gctDMA0GCSqGSIb3DQEBAwUAA4GBAEvEn5YHixuMeYW3TpCVpyvNocToAlHiy5xt
+iXVN9V31w8X7I7vcUAgqWQYtB0qngQ28akmiY+yyfYkWB3H8B0DCr0STFCbMq0c6
+Ydt5pV3lBQpHUKZFvv5moVVWPXr0f0smZI26KGalHgxdrFJnnP4bp6VhYt8G3KFA
+h+nxg1RW
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md5_ee.pem b/chromium/net/data/ssl/certificates/weak_digest_md5_ee.pem
new file mode 100644
index 00000000000..c5a1eb4c5af
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md5_ee.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Intermediate CA
+ Validity
+ Not Before: Oct 26 03:46:48 2011 GMT
+ Not After : Oct 23 03:46:48 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38:
+ 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9:
+ 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf:
+ 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13:
+ be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d:
+ fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee:
+ 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db:
+ 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57:
+ dc:d0:de:df:29:72:45:6f:e3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD
+ X509v3 Authority Key Identifier:
+ keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: md5WithRSAEncryption
+ 5c:36:ba:dd:8c:ae:4c:2d:00:32:d9:ed:4d:1d:4b:07:52:28:
+ 9c:16:18:3f:38:02:9d:d7:8e:16:e6:4b:2d:8c:84:cc:b1:90:
+ 6c:b4:42:55:56:7c:e6:ec:15:2b:90:0b:7e:89:08:15:5a:11:
+ 0e:5d:1b:a3:cc:81:79:1e:ea:96:82:75:d8:14:96:0f:17:a5:
+ cd:50:fd:50:f0:5b:7f:03:54:b3:e3:b5:66:03:c8:00:1d:61:
+ 36:f3:78:2d:07:82:61:0a:fd:d9:7c:8a:fe:cb:e1:09:df:fb:
+ b6:2f:09:7b:0b:62:d8:27:18:4e:6e:fe:92:1b:1a:2b:7d:56:
+ e0:87
+-----BEGIN CERTIFICATE-----
+MIICiDCCAfGgAwIBAgIBAjANBgkqhkiG9w0BAQQFADAxMS8wLQYDVQQDDCZUZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2
+NDhaFw0yMTEwMjMwMzQ2NDhaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
+Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
+AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI
+6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN
+DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV
+HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME
+GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQQFAAOBgQBcNrrd
+jK5MLQAy2e1NHUsHUiicFhg/OAKd144W5kstjITMsZBstEJVVnzm7BUrkAt+iQgV
+WhEOXRujzIF5HuqWgnXYFJYPF6XNUP1Q8Ft/A1Sz47VmA8gAHWE283gtB4JhCv3Z
+fIr+y+EJ3/u2Lwl7C2LYJxhObv6SGxorfVbghw==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md5_intermediate.pem b/chromium/net/data/ssl/certificates/weak_digest_md5_intermediate.pem
new file mode 100644
index 00000000000..6192ffe97a4
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md5_intermediate.pem
@@ -0,0 +1,57 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Root CA
+ Validity
+ Not Before: Oct 26 03:46:48 2011 GMT
+ Not After : Oct 23 03:46:48 2021 GMT
+ Subject: CN=Test Deprecated Digest Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30:
+ 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26:
+ 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63:
+ 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89:
+ 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd:
+ b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78:
+ 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58:
+ f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49:
+ 62:d4:53:b0:d8:9c:34:47:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+ X509v3 Authority Key Identifier:
+ keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: md5WithRSAEncryption
+ a3:9d:4e:8b:42:7b:c2:3a:71:5c:7a:a9:ec:9b:da:04:a4:7d:
+ f2:53:ba:b5:97:97:21:ae:94:03:23:7e:75:0e:c7:cc:1f:57:
+ f2:76:ec:aa:bf:4f:2f:d1:2d:d2:3d:10:55:ce:a0:1c:93:b6:
+ 8a:b6:65:9b:67:7a:a6:2f:04:62:e9:31:69:f4:26:08:a3:41:
+ d0:11:3a:21:31:b6:32:5e:a0:4c:32:2d:ca:f8:a0:76:be:f2:
+ a1:bf:15:98:73:26:41:2d:d5:8e:63:e7:5e:ef:61:08:f0:9d:
+ fb:af:55:1e:37:9c:2a:13:f7:7e:ab:5c:f4:d5:f8:7c:a7:fb:
+ c0:42
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZygAwIBAgIBAjANBgkqhkiG9w0BAQQFADApMScwJQYDVQQDDB5UZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ4WhcNMjEx
+MDIzMDM0NjQ4WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu
+dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ
+RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z
+2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5
++lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU
+eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BAUAA4GBAKOdTotCe8I6cVx6qeyb2gSkffJTurWXlyGulAMjfnUOx8wfV/J27Kq/
+Ty/RLdI9EFXOoByTtoq2ZZtneqYvBGLpMWn0JgijQdAROiExtjJeoEwyLcr4oHa+
+8qG/FZhzJkEt1Y5j517vYQjwnfuvVR43nCoT936rXPTV+Hyn+8BC
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_md5_root.pem b/chromium/net/data/ssl/certificates/weak_digest_md5_root.pem
new file mode 100644
index 00000000000..cea1d700466
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_md5_root.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICEjCCAXugAwIBAgIJANhsW8HvYIVtMA0GCSqGSIb3DQEBBAUAMCkxJzAlBgNV
+BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2
+NDhaFw0yMTEwMjMwMzQ2NDhaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE
+aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj
+BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv
+HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00
+P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2
+gctDMA0GCSqGSIb3DQEBBAUAA4GBAC1qyqlaaPzmY78GXsw1MY2VbSNmGyRxWw3W
+dJVSkdKv8jeeZnVT6JaiHzmM0zQ9E8x0szILJlJ3r9CNKiuXgpCvbaWqiWwytFny
+8Mea/xS8FwIfPoxiOt/MdjvnfUWi1ukZaOy88rg5V7/mVdObTzu4VouD4qxhpdTa
+QRn7eFqR
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_sha1_ee.pem b/chromium/net/data/ssl/certificates/weak_digest_sha1_ee.pem
new file mode 100644
index 00000000000..5368e62c9c1
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_sha1_ee.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Intermediate CA
+ Validity
+ Not Before: Oct 26 03:46:48 2011 GMT
+ Not After : Oct 23 03:46:48 2021 GMT
+ Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c7:48:eb:5c:00:17:94:01:09:d3:bd:47:41:38:
+ 74:b8:4f:cb:ea:f1:15:eb:cb:e7:b5:6c:bd:fe:d9:
+ 97:6d:1e:1b:ee:75:9e:c1:6f:4a:5c:8c:d7:19:cf:
+ 51:89:48:e8:7d:79:41:ab:e3:a7:77:d1:de:f2:13:
+ be:36:e7:44:c2:10:dd:56:83:03:f1:cd:e1:13:8d:
+ fe:45:d6:1a:98:d8:8d:08:b9:32:10:36:0d:ec:ee:
+ 2d:66:22:eb:6a:0d:0e:f4:15:91:dd:9d:3e:92:db:
+ 9e:26:c8:af:4b:b7:fb:93:f8:68:07:c3:53:02:57:
+ dc:d0:de:df:29:72:45:6f:e3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 35:5C:C8:0F:21:D0:A2:F5:69:44:5C:9E:B0:DC:0F:75:74:24:7A:FD
+ X509v3 Authority Key Identifier:
+ keyid:A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Subject Alternative Name:
+ IP Address:127.0.0.1
+ Signature Algorithm: sha1WithRSAEncryption
+ ab:a4:58:6a:d8:f4:87:00:11:45:23:ea:75:a9:0d:cd:87:73:
+ 0e:73:f2:97:d3:74:b0:cd:90:c9:45:83:03:c3:82:ee:2f:79:
+ 51:31:12:1c:39:a0:e2:45:f2:c2:4e:70:8c:e4:f3:af:15:4c:
+ be:5d:e7:c3:96:79:c8:a4:98:6d:37:8d:3f:9f:9e:89:32:ca:
+ a6:a7:e2:c8:f3:84:64:08:34:57:bd:10:22:96:78:39:b4:33:
+ dc:f2:db:83:ec:0c:20:58:ce:ba:98:44:dc:ca:a2:10:6c:5a:
+ d5:57:85:b9:3c:f0:48:99:98:e1:80:88:08:4c:cc:83:0d:40:
+ ff:8d
+-----BEGIN CERTIFICATE-----
+MIICiDCCAfGgAwIBAgIBATANBgkqhkiG9w0BAQUFADAxMS8wLQYDVQQDDCZUZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IEludGVybWVkaWF0ZSBDQTAeFw0xMTEwMjYwMzQ2
+NDhaFw0yMTEwMjMwMzQ2NDhaMGAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
+Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0IENB
+MRIwEAYDVQQDDAkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
+AMdI61wAF5QBCdO9R0E4dLhPy+rxFevL57Vsvf7Zl20eG+51nsFvSlyM1xnPUYlI
+6H15Qavjp3fR3vITvjbnRMIQ3VaDA/HN4RON/kXWGpjYjQi5MhA2DezuLWYi62oN
+DvQVkd2dPpLbnibIr0u3+5P4aAfDUwJX3NDe3ylyRW/jAgMBAAGjgYAwfjAMBgNV
+HRMBAf8EAjAAMB0GA1UdDgQWBBQ1XMgPIdCi9WlEXJ6w3A91dCR6/TAfBgNVHSME
+GDAWgBSoHQaNrT8lUQDwO+k1xmV0ElEgGTAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+KwYBBQUHAwIwDwYDVR0RBAgwBocEfwAAATANBgkqhkiG9w0BAQUFAAOBgQCrpFhq
+2PSHABFFI+p1qQ3Nh3MOc/KX03SwzZDJRYMDw4LuL3lRMRIcOaDiRfLCTnCM5POv
+FUy+XefDlnnIpJhtN40/n56JMsqmp+LI84RkCDRXvRAilng5tDPc8tuD7AwgWM66
+mETcyqIQbFrVV4W5PPBImZjhgIgITMyDDUD/jQ==
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem b/chromium/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem
new file mode 100644
index 00000000000..478d116d4ce
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_sha1_intermediate.pem
@@ -0,0 +1,57 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Test Deprecated Digest Root CA
+ Validity
+ Not Before: Oct 26 03:46:48 2011 GMT
+ Not After : Oct 23 03:46:48 2021 GMT
+ Subject: CN=Test Deprecated Digest Intermediate CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:ac:9b:c0:4b:fc:59:45:7a:d6:3f:a3:89:23:30:
+ 5b:70:ad:ab:78:62:4b:53:85:9f:f9:7d:7f:c1:26:
+ 08:23:80:61:0c:ba:6d:36:06:14:df:29:d4:9c:63:
+ 94:04:ee:14:b6:b9:81:06:2f:33:d8:35:9a:1a:89:
+ 17:ad:21:61:fa:24:75:b9:0c:ef:c1:15:6a:02:bd:
+ b2:a5:29:df:d8:5f:80:7c:4e:c9:c1:b4:bb:fd:78:
+ 44:63:34:b5:a5:51:aa:e9:23:77:44:53:f9:fa:58:
+ f6:46:6e:9d:d2:cd:00:a3:28:fe:51:e4:30:7e:49:
+ 62:d4:53:b0:d8:9c:34:47:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ A8:1D:06:8D:AD:3F:25:51:00:F0:3B:E9:35:C6:65:74:12:51:20:19
+ X509v3 Authority Key Identifier:
+ keyid:79:82:C5:B4:EB:60:12:4B:B5:87:79:1B:E2:3A:9C:17:76:81:CB:43
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 4e:30:a8:25:da:ac:90:a9:5e:6c:23:7f:76:1e:2d:64:79:78:
+ 61:84:dc:06:12:43:72:a6:18:f1:f2:23:fa:e9:1f:de:3a:52:
+ 1c:ce:cd:f7:7e:3c:92:ce:7f:f3:1f:f5:bc:18:17:95:cb:57:
+ 34:f1:88:b1:c8:1f:51:e1:d3:3d:dd:17:c6:d4:af:f1:42:ec:
+ 85:d7:bf:16:22:e0:88:82:92:cc:94:89:e5:eb:9d:cc:fe:31:
+ 50:6f:ea:d8:70:f9:ef:6b:ca:3e:af:bd:61:42:33:ce:23:bf:
+ 50:5f:55:14:64:2b:f7:fd:a6:29:41:a8:65:c3:fa:c4:f0:c7:
+ 71:a5
+-----BEGIN CERTIFICATE-----
+MIICMzCCAZygAwIBAgIBATANBgkqhkiG9w0BAQUFADApMScwJQYDVQQDDB5UZXN0
+IERlcHJlY2F0ZWQgRGlnZXN0IFJvb3QgQ0EwHhcNMTExMDI2MDM0NjQ4WhcNMjEx
+MDIzMDM0NjQ4WjAxMS8wLQYDVQQDDCZUZXN0IERlcHJlY2F0ZWQgRGlnZXN0IElu
+dGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArJvAS/xZ
+RXrWP6OJIzBbcK2reGJLU4Wf+X1/wSYII4BhDLptNgYU3ynUnGOUBO4UtrmBBi8z
+2DWaGokXrSFh+iR1uQzvwRVqAr2ypSnf2F+AfE7JwbS7/XhEYzS1pVGq6SN3RFP5
++lj2Rm6d0s0Aoyj+UeQwfkli1FOw2Jw0RwcCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUqB0Gja0/JVEA8DvpNcZldBJRIBkwHwYDVR0jBBgwFoAU
+eYLFtOtgEku1h3kb4jqcF3aBy0MwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BQUAA4GBAE4wqCXarJCpXmwjf3YeLWR5eGGE3AYSQ3KmGPHyI/rpH946UhzOzfd+
+PJLOf/Mf9bwYF5XLVzTxiLHIH1Hh0z3dF8bUr/FC7IXXvxYi4IiCksyUieXrncz+
+MVBv6thw+e9ryj6vvWFCM84jv1BfVRRkK/f9pilBqGXD+sTwx3Gl
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/weak_digest_sha1_root.pem b/chromium/net/data/ssl/certificates/weak_digest_sha1_root.pem
new file mode 100644
index 00000000000..a10f00911fa
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/weak_digest_sha1_root.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICEjCCAXugAwIBAgIJAOojr7l1i8pcMA0GCSqGSIb3DQEBBQUAMCkxJzAlBgNV
+BAMMHlRlc3QgRGVwcmVjYXRlZCBEaWdlc3QgUm9vdCBDQTAeFw0xMTEwMjYwMzQ2
+NDhaFw0yMTEwMjMwMzQ2NDhaMCkxJzAlBgNVBAMMHlRlc3QgRGVwcmVjYXRlZCBE
+aWdlc3QgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwFtzW+hj
+BwMylx+rrgeKjltrzYabuJDdTDTYr1lViwO39m6CtHYdcFvZ1nU9oDjW4Lb1NQYv
+HoR8+SD0X1R2Y0yF6AyS9NX5E9TQ8TJUSQEehfznbBovMkRaQQMRD6ksRIQr+s00
+P6n0lAYJyN32lmTCbJ+k1aGHPFtKhTNQF/cCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHmCxbTrYBJLtYd5G+I6nBd2
+gctDMA0GCSqGSIb3DQEBBQUAA4GBAFfvM72mFeBd4HfP/U0HTmeQsPTorL01BRGe
+kIbHSBfliYF5fTXbHHjXqvnmNvCwfjO1+HyCxg3opwmDS5DiwkT2XtqYeF80h8/X
+J+hsdo+wJJiD0G8V3wOkBjlS5N3WaH3vhPikLkvmr2UzeeO3ORaaDUlRpzzOS2Pn
+28TAE0Wq
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/websocket_cacert.pem b/chromium/net/data/ssl/certificates/websocket_cacert.pem
new file mode 100644
index 00000000000..50c5baa37dd
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/websocket_cacert.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 94:72:cf:87:d6:a3:b4:82
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket
+ Validity
+ Not Before: Oct 16 04:53:09 2012 GMT
+ Not After : Apr 29 04:53:09 2033 GMT
+ Subject: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d8:f9:57:61:4a:d3:22:96:91:79:c5:1b:fa:43:
+ e1:f6:ac:ef:16:ca:5e:a3:b2:9e:11:3c:c7:bc:f7:
+ e3:86:2f:ef:1b:52:a0:86:79:b0:0e:89:b3:8b:f1:
+ 54:98:7c:b8:18:44:d5:0b:26:da:b7:31:de:9e:f1:
+ ba:01:99:f8:b1:e9:5c:aa:d9:ea:7f:46:6f:2d:03:
+ 8d:64:bb:e4:bc:c2:61:9b:47:bc:56:d1:aa:d1:b4:
+ e8:7c:7b:ad:83:c8:48:ef:56:c5:cf:01:22:56:75:
+ 48:07:a3:15:82:32:59:08:44:d1:eb:04:ea:02:34:
+ 22:7b:2b:41:6f:14:03:4e:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 33:BB:11:3D:8D:AF:1C:27:2B:F6:67:36:A7:53:4E:49:94:22:17:EB
+ X509v3 Authority Key Identifier:
+ keyid:33:BB:11:3D:8D:AF:1C:27:2B:F6:67:36:A7:53:4E:49:94:22:17:EB
+ DirName:/C=JP/ST=Tokyo/O=pywebsocket/CN=pywebsocket
+ serial:94:72:CF:87:D6:A3:B4:82
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 4f:be:f4:12:fe:89:ff:bb:3b:1b:f3:78:6a:8b:ae:9c:da:31:
+ f0:76:b9:22:19:8d:4c:89:be:59:97:ff:0f:9f:0d:74:88:e2:
+ ca:4d:b2:d7:8e:9f:1d:3e:86:9d:40:84:fe:1a:57:53:ba:4b:
+ 35:91:f4:49:ba:16:3f:a1:bf:b1:a3:d8:55:f6:4d:5c:a7:2a:
+ 2d:e0:e1:ca:2b:44:67:c4:33:4f:31:0b:8d:3e:83:99:79:d1:
+ e3:50:4d:30:ab:29:d7:46:83:07:b5:9a:84:e5:3d:cb:92:05:
+ 49:93:2d:4e:92:a6:4b:a3:af:24:03:42:82:c6:26:76:f7:91:
+ c9:02
+-----BEGIN CERTIFICATE-----
+MIICvDCCAiWgAwIBAgIJAJRyz4fWo7SCMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
+BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQxFDAS
+BgNVBAMTC3B5d2Vic29ja2V0MB4XDTEyMTAxNjA0NTMwOVoXDTMzMDQyOTA0NTMw
+OVowSTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMRQwEgYDVQQKEwtweXdl
+YnNvY2tldDEUMBIGA1UEAxMLcHl3ZWJzb2NrZXQwgZ8wDQYJKoZIhvcNAQEBBQAD
+gY0AMIGJAoGBANj5V2FK0yKWkXnFG/pD4fas7xbKXqOynhE8x7z344Yv7xtSoIZ5
+sA6Js4vxVJh8uBhE1Qsm2rcx3p7xugGZ+LHpXKrZ6n9Gby0DjWS75LzCYZtHvFbR
+qtG06Hx7rYPISO9Wxc8BIlZ1SAejFYIyWQhE0esE6gI0InsrQW8UA04HAgMBAAGj
+gaswgagwHQYDVR0OBBYEFDO7ET2NrxwnK/ZnNqdTTkmUIhfrMHkGA1UdIwRyMHCA
+FDO7ET2NrxwnK/ZnNqdTTkmUIhfroU2kSzBJMQswCQYDVQQGEwJKUDEOMAwGA1UE
+CBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtweXdlYnNv
+Y2tldIIJAJRyz4fWo7SCMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
+T770Ev6J/7s7G/N4aouunNox8Ha5IhmNTIm+WZf/D58NdIjiyk2y146fHT6GnUCE
+/hpXU7pLNZH0SboWP6G/saPYVfZNXKcqLeDhyitEZ8QzTzELjT6DmXnR41BNMKsp
+10aDB7WahOU9y5IFSZMtTpKmS6OvJANCgsYmdveRyQI=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/certificates/websocket_client_cert.p12 b/chromium/net/data/ssl/certificates/websocket_client_cert.p12
new file mode 100644
index 00000000000..1ec70b23318
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/websocket_client_cert.p12
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/www_us_army_mil_cert.der b/chromium/net/data/ssl/certificates/www_us_army_mil_cert.der
new file mode 100644
index 00000000000..bee38ff7b42
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/www_us_army_mil_cert.der
Binary files differ
diff --git a/chromium/net/data/ssl/certificates/x509_verify_results.chain.pem b/chromium/net/data/ssl/certificates/x509_verify_results.chain.pem
new file mode 100644
index 00000000000..11c3187ab36
--- /dev/null
+++ b/chromium/net/data/ssl/certificates/x509_verify_results.chain.pem
@@ -0,0 +1,50 @@
+-----BEGIN CERTIFICATE-----
+MIICwjCCAiugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHVGVzdCBDQTEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcN
+MTEwNDE4MDA1MzE4WhcNMjAxMTE2MDA1MzE4WjBgMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UE
+CgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3LjAuMC4xMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQCmXYTWf2ivhIc3uf3qdg8u9c8EobXlXHSGq2iYJBedlGwyFUa1
+lijbi4OhP53pVv/gP9rZJXKACWDI4ZryAa2La1CiTAgesewzPIIVzrJ2KaNUFpvO
+e7YLZVqa0AnMNv1LNVp8eT05rcvN0XxFqvOdaFASBKnjOTqdvdkLtqwYawIDAQAB
+o4GAMH4wDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUhEM3aeclNoZ1e/vksAaYOkCW
+txUwHwYDVR0jBBgwFoAUlJpwHgPeLEqeOAaz6Y7gch7YeCgwHQYDVR0lBBYwFAYI
+KwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEF
+BQADgYEAFMILenxcrqHu6+a77HDjZQ1TUHZcVnokdhbCR8n+jxO+QdrQcJuu9VPa
+0XpYR+x5VzN26Tbf88jGyCGEkDrtr7sBsw+YzUvDx18Rl9SfE4jFNuvNRCY+/oMH
+eq4v35gB0tHOH2As3QGywnp2zEAA8eVMJzSFHS18WR/vBUGpvzo=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICpzCCAhCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHVGVzdCBDQTEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTExMDQxNzIy
+MjU1OFoXDTIxMDIyMzIyMjU1OFowazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
+bGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3Qg
+Q0ExHTAbBgNVBAMMFFRlc3QgSW50ZXJtZWRpYXRlIENBMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQCdrVeFppenZ20T6f76yHp+qIOuWpA9ojNKJW+p93kY/2wC
+bUxVndJ+YYQ2/HGHURRGwBs/1yYCaeyCeVF3Ckr3Ei65fDAHqZFpZ/IsjUT9eRBO
+BODSaKFfvCF7Iv+521PZq5QaIPZOA99CaL5nqlEUbtas+4ziEg76Ngc2KxII1QID
+AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSUmnAeA94sSp44BrPp
+juByHth4KDAfBgNVHSMEGDAWgBRqsJiqdonmw+gkmvYOysT9lJgj2zAOBgNVHQ8B
+Af8EBAMCAQYwDQYJKoZIhvcNAQEFBQADgYEAzGgopGXKvuBlz04KEFMu46fUrxYe
+O6B6csR7Rt7elDvIMu2+rdkaP9Ccv0PqtXv4DRFCxdrG8+LhL3B4SofYxCqTtq9e
+rNtFRIR5Xad07djNBshor9PjNeKHnrGc1rXesqCfILpg2fq/Qv1OM1yrpLjbcuM6
+eCG7aIfsh9LwP3o=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIChjCCAe+gAwIBAgIJAOa+aXDInZR1MA0GCSqGSIb3DQEBBQUAMGMxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
+aWV3MRAwDgYDVQQKDAdUZXN0IENBMRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0EwHhcN
+MTEwNDE3MjIzMTEzWhcNMjEwNDE0MjIzMTEzWjBjMQswCQYDVQQGEwJVUzETMBEG
+A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UE
+CgwHVGVzdCBDQTEVMBMGA1UEAwwMVGVzdCBSb290IENBMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQD0pusirvvLaxBXtAu7kE9X7q4K/iOjIQIBNIbGdKtPDZus
+9I7SCNa3ZWcvTKTOp1biFCuXLJ404cxSOqo6YB7s4hcyHVEMz+EHhxHUYQmmZb0P
+12QwWVteJd2MZvkwupPeaNJhlyR0Vf51m9kpPVuFLrMb4Smvgcx9jbwOut1ACwID
+AQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUarCYqnaJ5sPoJJr2DsrE/ZSYI9swDQYJKoZIhvcNAQEFBQADgYEAck/rD/Vy
+vD53HXph/Bc7+qo9t3Nmyy9rbgl0dQq9h5+6Ria4Btw3Uj8iaQZTeyyFvGP0NHR2
+ek2hgX7fDwD3fRKEJus4shs16BzuSCBmYKw/G5PehEC9trIly52pMuUGphczbpzp
+6RdYOT3pG713l25Kt436hDnCoonwUqB6mhs=
+-----END CERTIFICATE-----
diff --git a/chromium/net/data/ssl/scripts/aia-test.cnf b/chromium/net/data/ssl/scripts/aia-test.cnf
new file mode 100644
index 00000000000..f89d68a8842
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/aia-test.cnf
@@ -0,0 +1,55 @@
+CA_DIR=out
+CA_NAME=aia-test-root
+AIA_URL=http://aia-test.invalid
+
+[ca]
+default_ca = CA_root
+preserve = yes
+
+[CA_root]
+dir = ${ENV::CA_DIR}
+key_size = 2048
+algo = sha1
+database = $dir/${ENV::CA_NAME}-index.txt
+new_certs_dir = $dir
+serial = $dir/${ENV::CA_NAME}-serial
+certificate = $dir/${ENV::CA_NAME}.pem
+private_key = $dir/${ENV::CA_NAME}.key
+RANDFILE = $dir/.rand
+default_days = 3650
+default_crl_days = 30
+default_md = sha1
+policy = policy_anything
+unique_subject = no
+copy_extensions = copy
+
+[user_cert]
+basicConstraints = critical, CA:false
+extendedKeyUsage = serverAuth, clientAuth
+authorityInfoAccess = caIssuers;URI:${ENV::AIA_URL}
+
+[ca_cert]
+basicConstraints = critical, CA:true
+keyUsage = critical, keyCertSign, cRLSign
+
+[policy_anything]
+# Default signing policy
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+[req]
+default_bits = 2048
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = req_env_dn
+
+[req_env_dn]
+CN = ${ENV::CA_COMMON_NAME}
+
diff --git a/chromium/net/data/ssl/scripts/ca.cnf b/chromium/net/data/ssl/scripts/ca.cnf
new file mode 100644
index 00000000000..8a1d1e75f1e
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/ca.cnf
@@ -0,0 +1,93 @@
+# Defaults in the event they're not set in the environment
+CA_DIR = out
+KEY_SIZE = 2048
+ALGO = sha1
+CERT_TYPE = root
+CA_NAME = req_env_dn
+
+[ca]
+default_ca = CA_root
+preserve = yes
+
+# The default test root, used to generate certificates and CRLs.
+[CA_root]
+dir = $ENV::CA_DIR
+key_size = $ENV::KEY_SIZE
+algo = $ENV::ALGO
+cert_type = $ENV::CERT_TYPE
+type = $key_size-$algo-$cert_type
+database = $dir/$type-index.txt
+new_certs_dir = $dir
+serial = $dir/$type-serial
+certificate = $dir/$type.pem
+private_key = $dir/$type.key
+RANDFILE = $dir/.rand
+default_days = 3650
+default_crl_days = 30
+default_md = sha1
+policy = policy_anything
+unique_subject = no
+copy_extensions = copy
+
+[user_cert]
+# Extensions to add when signing a request for an EE cert
+basicConstraints = critical, CA:false
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always
+extendedKeyUsage = serverAuth,clientAuth
+
+[ca_cert]
+# Extensions to add when signing a request for an intermediate/CA cert
+basicConstraints = critical, CA:true
+subjectKeyIdentifier = hash
+#authorityKeyIdentifier = keyid:always
+keyUsage = critical, keyCertSign, cRLSign
+
+[crl_extensions]
+# Extensions to add when signing a CRL
+authorityKeyIdentifier = keyid:always
+
+[policy_anything]
+# Default signing policy
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+[req]
+# The request section used to generate the root CA certificate. This should
+# not be used to generate end-entity certificates. For certificates other
+# than the root CA, see README to find the appropriate configuration file
+# (ie: openssl_cert.cnf).
+default_bits = $ENV::KEY_SIZE
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = $ENV::CA_NAME
+x509_extensions = req_ca_exts
+
+[req_ca_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = Test Root CA
+
+[req_intermediate_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = Test Intermediate CA
+
+[req_env_dn]
+CN = $ENV::CA_COMMON_NAME
+
+[req_ca_exts]
+basicConstraints = critical, CA:true
+keyUsage = critical, keyCertSign, cRLSign
+subjectKeyIdentifier = hash
diff --git a/chromium/net/data/ssl/scripts/client-certs.cnf b/chromium/net/data/ssl/scripts/client-certs.cnf
new file mode 100644
index 00000000000..1efa04a2983
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/client-certs.cnf
@@ -0,0 +1,51 @@
+ID=1
+CA_DIR=out
+
+[ca]
+default_ca = ca_settings
+preserve = yes
+
+[ca_settings]
+dir = ${ENV::CA_DIR}
+database = $dir/${ENV::ID}-index.txt
+new_certs_dir = $dir
+serial = $dir/${ENV::ID}-serial
+certificate = $dir/${ENV::ID}.pem
+private_key = $dir/${ENV::ID}.key
+RANDFILE = $dir/rand
+default_md = sha1
+default_days = 3650
+policy = policy_anything
+unique_subject = no
+copy_extensions = copy
+
+[policy_anything]
+# Default signing policy
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+[req]
+default_bits = 2048
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = req_env_dn
+
+[user_cert]
+# Extensions to add when signing a request for an EE cert
+basicConstraints = critical, CA:false
+extendedKeyUsage = serverAuth,clientAuth
+
+[ca_cert]
+# Extensions to add when signing a request for an intermediate/CA cert
+basicConstraints = critical, CA:true
+keyUsage = critical, keyCertSign, cRLSign
+
+[req_env_dn]
+CN = ${ENV::COMMON_NAME}
diff --git a/chromium/net/data/ssl/scripts/ee.cnf b/chromium/net/data/ssl/scripts/ee.cnf
new file mode 100644
index 00000000000..ad786c80ca8
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/ee.cnf
@@ -0,0 +1,51 @@
+SUBJECT_NAME = req_dn
+KEY_SIZE = 2048
+
+[req]
+default_bits = ${ENV::KEY_SIZE}
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = ${ENV::SUBJECT_NAME}
+req_extensions = req_extensions
+
+[req_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = 127.0.0.1
+
+[req_duplicate_cn_1]
+O = Foo
+CN = Duplicate
+
+[req_duplicate_cn_2]
+O = Bar
+CN = Duplicate
+
+[req_extensions]
+subjectAltName = IP:127.0.0.1
+
+[req_san_sanity]
+subjectAltName = @san_sanity
+
+[san_sanity]
+IP.1 = 127.0.0.2
+IP.2 = FE80::1
+DNS = test.example
+email = test@test.example
+otherName = 1.2.3.4;UTF8:ignore me
+dirName = more_san_sanity
+
+[req_spdy_pooling]
+subjectAltName = @spdy_pooling
+
+[more_san_sanity]
+CN=127.0.0.3
+
+[spdy_pooling]
+DNS.1 = www.example.org
+DNS.2 = mail.example.org
+DNS.3 = mail.example.com
diff --git a/chromium/net/data/ssl/scripts/eku-test.cnf b/chromium/net/data/ssl/scripts/eku-test.cnf
new file mode 100644
index 00000000000..7ced049f7f2
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/eku-test.cnf
@@ -0,0 +1,26 @@
+[req]
+default_bits = 2048
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = req_dn
+
+[req_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = 127.0.0.1
+
+[crit-codeSigning]
+subjectAltName = IP:127.0.0.1
+basicConstraints = critical, CA:false
+subjectKeyIdentifier = hash
+extendedKeyUsage = critical, codeSigning
+
+[non-crit-codeSigning]
+subjectAltName = IP:127.0.0.1
+basicConstraints = critical, CA:false
+subjectKeyIdentifier = hash
+extendedKeyUsage = codeSigning
diff --git a/chromium/net/data/ssl/scripts/generate-aia-certs.sh b/chromium/net/data/ssl/scripts/generate-aia-certs.sh
new file mode 100755
index 00000000000..98b858e139a
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-aia-certs.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+# Copyright 2013 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.
+
+# This script generates a set of test (end-entity, intermediate, root)
+# certificates that can be used to test fetching of an intermediate via AIA.
+
+try() {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+# Create the serial number files.
+try echo 1 > out/aia-test-root-serial
+try echo 1 > out/aia-test-intermediate-serial
+
+# Create the signers' DB files.
+touch out/aia-test-root-index.txt
+touch out/aia-test-intermediate-index.txt
+
+# Generate the keys
+try openssl genrsa -out out/aia-test-root.key 2048
+try openssl genrsa -out out/aia-test-intermediate.key 2048
+try openssl genrsa -out out/aia-test-cert.key 2048
+
+# Generate the root certificate
+CA_COMMON_NAME="AIA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=aia-test-root \
+ try openssl req \
+ -new \
+ -key out/aia-test-root.key \
+ -out out/aia-test-root.csr \
+ -config aia-test.cnf
+
+CA_COMMON_NAME="AIA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=aia-test-root \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/aia-test-root.csr \
+ -out out/aia-test-root.pem \
+ -signkey out/aia-test-root.key \
+ -extfile aia-test.cnf \
+ -extensions ca_cert
+
+# Generate the intermediate
+CA_COMMON_NAME="AIA Test Intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=aia-test-root \
+ try openssl req \
+ -new \
+ -key out/aia-test-intermediate.key \
+ -out out/aia-test-intermediate.csr \
+ -config aia-test.cnf
+
+CA_COMMON_NAME="AIA Test Intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=aia-test-root \
+ try openssl ca \
+ -batch \
+ -in out/aia-test-intermediate.csr \
+ -out out/aia-test-intermediate.pem \
+ -config aia-test.cnf \
+ -extensions ca_cert
+
+# Generate the leaf
+CA_COMMON_NAME="aia-host.invalid" \
+CA_DIR=out \
+CA_NAME=aia-test-intermediate \
+try openssl req \
+ -new \
+ -key out/aia-test-cert.key \
+ -out out/aia-test-cert.csr \
+ -config aia-test.cnf
+
+CA_COMMON_NAME="AIA Test Intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=aia-test-intermediate \
+ AIA_URL=http://aia-test.invalid \
+ try openssl ca \
+ -batch \
+ -in out/aia-test-cert.csr \
+ -out out/aia-test-cert.pem \
+ -config aia-test.cnf \
+ -extensions user_cert
diff --git a/chromium/net/data/ssl/scripts/generate-android-test-keys.sh b/chromium/net/data/ssl/scripts/generate-android-test-keys.sh
new file mode 100755
index 00000000000..1c297e3a86f
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-android-test-keys.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# Copyright (c) 2013 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.
+
+# This script is used to generate the test keys for the unit test in
+# android/keystore_unittest.c.
+#
+# These are test RSA / DSA / ECDSA private keys in PKCS#8 format, as well
+# as the corresponding DSA / ECDSA public keys.
+#
+
+# Exit script as soon a something fails.
+set -e
+
+mkdir -p out
+rm -rf out/*
+
+# Generate a single 2048-bits RSA private key in PKCS#8 format.
+KEY=android-test-key-rsa
+openssl genrsa \
+ -out out/$KEY.pem \
+ 2048
+
+# Generate a 2048-bits DSA private key in PKCS#8 format,
+# as well as its public key in X.509 DER format.
+KEY=android-test-key-dsa
+openssl dsaparam \
+ -out out/$KEY.param.pem \
+ 2048
+
+openssl gendsa \
+ -out out/$KEY.pem \
+ out/$KEY.param.pem
+
+openssl dsa \
+ -in out/$KEY.pem \
+ -outform PEM \
+ -out out/$KEY-public.pem \
+ -pubout
+
+rm out/$KEY.param.pem
+
+# Generate an ECDSA private key, in PKCS#8 format,
+# as well as its public key in X.509 DER format.
+KEY=android-test-key-ecdsa
+openssl ecparam -genkey -name prime256v1 -out out/$KEY.pem
+
+openssl ec \
+ -in out/$KEY.pem \
+ -outform PEM \
+ -out out/$KEY-public.pem \
+ -pubout
+
+# We're done here.
diff --git a/chromium/net/data/ssl/scripts/generate-bad-eku-certs.sh b/chromium/net/data/ssl/scripts/generate-bad-eku-certs.sh
new file mode 100755
index 00000000000..11e41d4da5a
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-bad-eku-certs.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Copyright 2013 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.
+
+# This script generates a set of test (end-entity, root) certificate chains
+# whose EEs have (critical, non-critical) eKUs for codeSigning. We then try
+# to use them as EEs for a web server in unit tests, to make sure that we
+# don't accept such certs as web server certs.
+
+try () {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+eku_test_root="eku-test-root"
+
+# Create the serial number files.
+try echo 1 > out/$eku_test_root-serial
+
+# Make sure the signers' DB files exist.
+touch out/$eku_test_root-index.txt
+
+# Generate one root CA certificate.
+try openssl genrsa -out out/$eku_test_root.key 2048
+
+CA_COMMON_NAME="2048 RSA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ try openssl req \
+ -new \
+ -key out/$eku_test_root.key \
+ -extensions ca_cert \
+ -out out/$eku_test_root.csr \
+ -config ca.cnf
+
+CA_COMMON_NAME="2048 RSA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/$eku_test_root.csr \
+ -extensions ca_cert \
+ -signkey out/$eku_test_root.key \
+ -out out/$eku_test_root.pem
+
+# Generate EE certs.
+for cert_type in non-crit-codeSigning crit-codeSigning
+do
+ try openssl genrsa -out out/$cert_type.key 2048
+
+ try openssl req \
+ -new \
+ -key out/$cert_type.key \
+ -out out/$cert_type.csr \
+ -config eku-test.cnf \
+ -reqexts "$cert_type"
+
+ CA_COMMON_NAME="2048 rsa Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ try openssl ca \
+ -batch \
+ -in out/$cert_type.csr \
+ -out out/$cert_type.pem \
+ -config ca.cnf
+done
diff --git a/chromium/net/data/ssl/scripts/generate-client-certificates.sh b/chromium/net/data/ssl/scripts/generate-client-certificates.sh
new file mode 100755
index 00000000000..33782993036
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-client-certificates.sh
@@ -0,0 +1,163 @@
+#!/bin/bash
+
+# Copyright (c) 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.
+
+# This script generates certificates that can be used to test SSL client
+# authentication. Outputs for automated tests are stored in
+# net/data/ssl/certificates, but may be re-generated for manual testing.
+#
+# This script generates two chains of test client certificates:
+#
+# 1. A (end-entity) -> B -> C (self-signed root)
+# 2. D (end-entity) -> E -> C (self-signed root)
+#
+# In which A, B, C, D, and E all have distinct keypairs. Both client
+# certificates share the same root, but are issued by different
+# intermediates. The names of these intermediates are hardcoded within
+# unit tests, and thus should not be changed.
+
+try () {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+echo Create the serial number files and indices.
+serial = 100
+for i in B C E
+do
+ try echo $serial > out/$i-serial
+ serial=$(expr $serial + 1)
+ touch out/$i-index.txt
+ touch out/$i-index.txt.attr
+done
+
+echo Generate the keys.
+for i in A B C D E
+do
+ try openssl genrsa -out out/$i.key 2048
+done
+
+echo Generate the C CSR
+COMMON_NAME="C Root CA" \
+ CA_DIR=out \
+ ID=C \
+ try openssl req \
+ -new \
+ -key out/C.key \
+ -out out/C.csr \
+ -config client-certs.cnf
+
+echo C signs itself.
+COMMON_NAME="C Root CA" \
+ CA_DIR=out \
+ ID=C \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/C.csr \
+ -extensions ca_cert \
+ -signkey out/C.key \
+ -out out/C.pem
+
+echo Generate the intermediates
+COMMON_NAME="B CA" \
+ CA_DIR=out \
+ ID=B \
+ try openssl req \
+ -new \
+ -key out/B.key \
+ -out out/B.csr \
+ -config client-certs.cnf
+
+COMMON_NAME="C CA" \
+ CA_DIR=out \
+ ID=C \
+ try openssl ca \
+ -batch \
+ -extensions ca_cert \
+ -in out/B.csr \
+ -out out/B.pem \
+ -config client-certs.cnf
+
+COMMON_NAME="E CA" \
+ CA_DIR=out \
+ ID=E \
+ try openssl req \
+ -new \
+ -key out/E.key \
+ -out out/E.csr \
+ -config client-certs.cnf
+
+COMMON_NAME="C CA" \
+ CA_DIR=out \
+ ID=C \
+ try openssl ca \
+ -batch \
+ -extensions ca_cert \
+ -in out/E.csr \
+ -out out/E.pem \
+ -config client-certs.cnf
+
+echo Generate the leaf certs
+for id in A D
+do
+ COMMON_NAME="Client Cert $id" \
+ ID=$id \
+ try openssl req \
+ -new \
+ -key out/$id.key \
+ -out out/$id.csr \
+ -config client-certs.cnf
+done
+
+echo B signs A
+COMMON_NAME="B CA" \
+ CA_DIR=out \
+ ID=B \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -in out/A.csr \
+ -out out/A.pem \
+ -config client-certs.cnf
+
+echo E signs D
+COMMON_NAME="E CA" \
+ CA_DIR=out \
+ ID=E \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -in out/D.csr \
+ -out out/D.pem \
+ -config client-certs.cnf
+
+echo Package the client certs and private keys into PKCS12 files
+# This is done for easily importing all of the certs needed for clients.
+cat out/A.pem out/A.key out/B.pem out/C.pem > out/A-chain.pem
+cat out/D.pem out/D.key out/E.pem out/C.pem > out/D-chain.pem
+
+try openssl pkcs12 \
+ -in out/A-chain.pem \
+ -out client_1.p12 \
+ -export \
+ -passout pass:chrome
+
+try openssl pkcs12 \
+ -in out/D-chain.pem \
+ -out client_2.p12 \
+ -export \
+ -passout pass:chrome
+
+echo Package the client certs for unit tests
+cp out/A.pem client_1.pem
+cp out/A.key client_1.key
+cp out/B.pem client_1_ca.pem
+
+cp out/D.pem client_2.pem
+cp out/D.key client_2.key
+cp out/E.pem client_2_ca.pem
diff --git a/chromium/net/data/ssl/scripts/generate-cross-signed-certs.sh b/chromium/net/data/ssl/scripts/generate-cross-signed-certs.sh
new file mode 100755
index 00000000000..a40ca74a082
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-cross-signed-certs.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+# Copyright 2013 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.
+
+# This script generates a two roots - one legacy one signed with MD5, and
+# another (newer) one signed with SHA1 - and has a leaf certificate signed
+# by these without any distinguishers.
+#
+# The "cross-signed" comes from the fact that both the MD5 and SHA1 roots share
+# the same Authority Key ID, Subject Key ID, Subject, and Subject Public Key
+# Info. When the chain building algorithm is evaluating paths, if it prefers
+# untrusted over trusted, then it will see the MD5 certificate as a self-signed
+# cert that is "cross-signed" by the trusted SHA1 root.
+#
+# The SHA1 root should be (temporarily) trusted, and the resulting chain
+# should be leaf -> SHA1root, not leaf -> MD5root, leaf -> SHA1root -> MD5root,
+# or leaf -> MD5root -> SHA1root
+
+try() {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+try echo 1 > out/2048-sha1-root-serial
+try echo 2 > out/2048-md5-root-serial
+touch out/2048-sha1-root-index.txt
+touch out/2048-md5-root-index.txt
+
+# Generate the key
+try openssl genrsa -out out/2048-sha1-root.key 2048
+
+# Generate the root certificate
+CA_COMMON_NAME="Test Dup-Hash Root CA" \
+ try openssl req \
+ -new \
+ -key out/2048-sha1-root.key \
+ -out out/2048-sha1-root.req \
+ -config ca.cnf
+
+CA_COMMON_NAME="Test Dup-Hash Root CA" \
+ try openssl x509 \
+ -req -days 3650 \
+ -sha1 \
+ -in out/2048-sha1-root.req \
+ -out out/2048-sha1-root.pem \
+ -text \
+ -signkey out/2048-sha1-root.key \
+ -extfile ca.cnf \
+ -extensions ca_cert
+
+CA_COMMON_NAME="Test Dup-Hash Root CA" \
+ try openssl x509 \
+ -req -days 3650 \
+ -md5 \
+ -in out/2048-sha1-root.req \
+ -out out/2048-md5-root.pem \
+ -text \
+ -signkey out/2048-sha1-root.key \
+ -extfile ca.cnf \
+ -extensions ca_cert
+
+# Generate the leaf certificate request
+try openssl req \
+ -new \
+ -keyout out/ok_cert.key \
+ -out out/ok_cert.req \
+ -config ee.cnf
+
+# Generate the leaf certificates
+CA_COMMON_NAME="Test Dup-Hash Root CA" \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -days 3650 \
+ -in out/ok_cert.req \
+ -out out/ok_cert.pem \
+ -config ca.cnf
+
+try openssl x509 -text \
+ -in out/2048-md5-root.pem \
+ -out ../certificates/cross-signed-root-md5.pem
+try openssl x509 -text \
+ -in out/2048-sha1-root.pem \
+ -out ../certificates/cross-signed-root-sha1.pem
+try openssl x509 -text \
+ -in out/ok_cert.pem \
+ -out ../certificates/cross-signed-leaf.pem
diff --git a/chromium/net/data/ssl/scripts/generate-duplicate-cn-certs.sh b/chromium/net/data/ssl/scripts/generate-duplicate-cn-certs.sh
new file mode 100755
index 00000000000..8e48454342f
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-duplicate-cn-certs.sh
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+# Copyright (c) 2013 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.
+
+# This script generates two chains of test certificates:
+# 1. A1 (end-entity) -> B (self-signed root)
+# 2. A2 (end-entity) -> B (self-signed root)
+#
+# In which A1 and A2 share the same key, the same subject common name, but have
+# distinct O values in their subjects.
+#
+# This is used to test that NSS can properly generate unique certificate
+# nicknames for both certificates.
+
+try () {
+ echo "$@"
+ $@ || exit 1
+}
+
+generate_key_command () {
+ case "$1" in
+ rsa)
+ echo genrsa
+ ;;
+ *)
+ exit 1
+ esac
+}
+
+try rm -rf out
+try mkdir out
+
+echo Create the serial number and index files.
+try echo 1 > out/B-serial
+try touch out/B-index.txt
+
+echo Generate the keys.
+try openssl genrsa -out out/A.key 2048
+try openssl genrsa -out out/B.key 2048
+
+echo Generate the B CSR.
+CA_COMMON_NAME="B Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=B CERTIFICATE=B \
+ try openssl req \
+ -new \
+ -key out/B.key \
+ -out out/B.csr \
+ -config redundant-ca.cnf
+
+echo B signs itself.
+CA_COMMON_NAME="B Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/B.csr \
+ -extfile redundant-ca.cnf \
+ -extensions ca_cert \
+ -signkey out/B.key \
+ -out out/B.pem
+
+echo Generate the A1 end-entity CSR.
+SUBJECT_NAME=req_duplicate_cn_1 \
+ try openssl req \
+ -new \
+ -key out/A.key \
+ -out out/A1.csr \
+ -config ee.cnf
+
+echo Generate the A2 end-entity CSR
+SUBJECT_NAME=req_duplicate_cn_2 \
+ try openssl req \
+ -new \
+ -key out/A.key \
+ -out out/A2.csr \
+ -config ee.cnf
+
+
+echo B signs A1.
+CA_COMMON_NAME="B CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=sha1 \
+ CERT_TYPE=intermediate \
+ TYPE=B CERTIFICATE=B \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -in out/A1.csr \
+ -out out/A1.pem \
+ -config redundant-ca.cnf
+
+echo B signs A2.
+CA_COMMON_NAME="B CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=sha1 \
+ CERT_TYPE=intermediate \
+ TYPE=B CERTIFICATE=B \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -in out/A2.csr \
+ -out out/A2.pem \
+ -config redundant-ca.cnf
+
+echo Exporting the certificates to PKCS#12
+try openssl pkcs12 \
+ -export \
+ -inkey out/A.key \
+ -in out/A1.pem \
+ -out ../certificates/duplicate_cn_1.p12 \
+ -passout pass:chrome
+
+try openssl pkcs12 \
+ -export \
+ -inkey out/A.key \
+ -in out/A2.pem \
+ -out ../certificates/duplicate_cn_2.p12 \
+ -passout pass:chrome
+
+cp out/A1.pem ../certificates/duplicate_cn_1.pem
+cp out/A2.pem ../certificates/duplicate_cn_2.pem
diff --git a/chromium/net/data/ssl/scripts/generate-policy-certs.sh b/chromium/net/data/ssl/scripts/generate-policy-certs.sh
new file mode 100755
index 00000000000..4a6b35dc463
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-policy-certs.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+# Copyright 2013 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.
+
+# This script generates a (end-entity, intermediate, root) certificate, where
+# the root has no explicit policies associated, the intermediate has multiple
+# policies, and the leaf has a single policy.
+#
+# When validating, supplying no policy OID should not result in an error.
+
+try() {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+# Create the serial number files.
+try echo 1 > out/policy-root-serial
+try echo 1 > out/policy-intermediate-serial
+
+# Create the signers' DB files.
+touch out/policy-root-index.txt
+touch out/policy-intermediate-index.txt
+
+# Generate the keys
+try openssl genrsa -out out/policy-root.key 2048
+try openssl genrsa -out out/policy-intermediate.key 2048
+try openssl genrsa -out out/policy-cert.key 2048
+
+# Generate the root certificate
+COMMON_NAME="Policy Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=policy-root \
+ try openssl req \
+ -new \
+ -key out/policy-root.key \
+ -out out/policy-root.csr \
+ -config policy.cnf
+
+COMMON_NAME="Policy Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=policy-root \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/policy-root.csr \
+ -out out/policy-root.pem \
+ -signkey out/policy-root.key \
+ -extfile policy.cnf \
+ -extensions ca_cert
+
+# Generate the intermediate
+COMMON_NAME="Policy Test Intermediate CA" \
+ CA_DIR=out \
+ try openssl req \
+ -new \
+ -key out/policy-intermediate.key \
+ -out out/policy-intermediate.csr \
+ -config policy.cnf
+
+COMMON_NAME="UNUSED" \
+ CA_DIR=out \
+ CA_NAME=policy-root \
+ try openssl ca \
+ -batch \
+ -in out/policy-intermediate.csr \
+ -out out/policy-intermediate.pem \
+ -config policy.cnf \
+ -extensions intermediate_cert
+
+# Generate the leaf
+COMMON_NAME="policy_test.example" \
+CA_DIR=out \
+CA_NAME=policy-intermediate \
+try openssl req \
+ -new \
+ -key out/policy-cert.key \
+ -out out/policy-cert.csr \
+ -config policy.cnf
+
+COMMON_NAME="Policy Test Intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=policy-intermediate \
+ try openssl ca \
+ -batch \
+ -in out/policy-cert.csr \
+ -out out/policy-cert.pem \
+ -config policy.cnf \
+ -extensions user_cert
+
+cat out/policy-cert.pem \
+ out/policy-intermediate.pem \
+ out/policy-root.pem >../certificates/explicit-policy-chain.pem
diff --git a/chromium/net/data/ssl/scripts/generate-redundant-test-chains.sh b/chromium/net/data/ssl/scripts/generate-redundant-test-chains.sh
new file mode 100755
index 00000000000..58768e84963
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-redundant-test-chains.sh
@@ -0,0 +1,187 @@
+#!/bin/sh
+
+# Copyright (c) 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.
+
+# This script generates two chains of test certificates:
+#
+# 1. A (end-entity) -> B -> C -> D (self-signed root)
+# 2. A (end-entity) -> B -> C2 (self-signed root)
+#
+# in which A, B, C, and D have distinct keypairs. C2 is a self-signed root
+# certificate that uses the same keypair as C.
+#
+# We use these cert chains in
+# SSLClientSocketTest.VerifyReturnChainProperlyOrdered to ensure that
+# SSLInfo objects see the certificate chain as validated rather than as
+# served by the server. The server serves chain 1. The client has C2, NOT D,
+# installed as a trusted root. Therefore, the chain will validate as chain
+# 2, even though the server served chain 1.
+
+try () {
+ echo "$@"
+ $@ || exit 1
+}
+
+generate_key_command () {
+ case "$1" in
+ rsa)
+ echo genrsa
+ ;;
+ *)
+ exit 1
+ esac
+}
+
+try rm -rf out
+try mkdir out
+
+echo Create the serial number files.
+serial=100
+for i in B C C2 D
+do
+ try echo $serial > out/$i-serial
+ serial=$(expr $serial + 1)
+done
+
+echo Generate the keys.
+try openssl genrsa -out out/A.key 2048
+try openssl genrsa -out out/B.key 2048
+try openssl genrsa -out out/C.key 2048
+try openssl genrsa -out out/D.key 2048
+
+echo Generate the D CSR.
+CA_COMMON_NAME="D Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=D CERTIFICATE=D \
+ try openssl req \
+ -new \
+ -key out/D.key \
+ -out out/D.csr \
+ -config redundant-ca.cnf
+
+echo D signs itself.
+CA_COMMON_NAME="D Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/D.csr \
+ -extensions ca_cert \
+ -signkey out/D.key \
+ -out out/D.pem
+
+echo Generate the C2 root CSR.
+CA_COMMON_NAME="C CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=C2 CERTIFICATE=C2 \
+ try openssl req \
+ -new \
+ -key out/C.key \
+ -out out/C2.csr \
+ -config redundant-ca.cnf
+
+echo C2 signs itself.
+CA_COMMON_NAME="C CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/C2.csr \
+ -extensions ca_cert \
+ -signkey out/C.key \
+ -out out/C2.pem
+
+echo Generate the B and C intermediaries\' CSRs.
+for i in B C
+do
+ name="$i Intermediate CA"
+ CA_COMMON_NAME="$i CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=$i CERTIFICATE=$i \
+ try openssl req \
+ -new \
+ -key out/$i.key \
+ -out out/$i.csr \
+ -config redundant-ca.cnf
+done
+
+echo D signs the C intermediate.
+# Make sure the signer's DB file exists.
+touch out/D-index.txt
+CA_COMMON_NAME="D Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=D CERTIFICATE=D \
+ try openssl ca \
+ -batch \
+ -extensions ca_cert \
+ -in out/C.csr \
+ -out out/C.pem \
+ -config redundant-ca.cnf
+
+echo C signs the B intermediate.
+touch out/C-index.txt
+CA_COMMON_NAME="C CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ TYPE=C CERTIFICATE=C \
+ try openssl ca \
+ -batch \
+ -extensions ca_cert \
+ -in out/B.csr \
+ -out out/B.pem \
+ -config redundant-ca.cnf
+
+echo Generate the A end-entity CSR.
+try openssl req \
+ -new \
+ -key out/A.key \
+ -out out/A.csr \
+ -config ee.cnf
+
+echo B signs A.
+touch out/B-index.txt
+CA_COMMON_NAME="B CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=$signer_key_size \
+ ALGO=$signer_algo \
+ CERT_TYPE=intermediate \
+ TYPE=B CERTIFICATE=B \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -in out/A.csr \
+ -out out/A.pem \
+ -config redundant-ca.cnf
+
+echo Create redundant-server-chain.pem
+cat out/A.key out/A.pem out/B.pem out/C.pem out/D.pem \
+ > redundant-server-chain.pem
+
+echo Create redundant-validated-chain.pem
+cat out/A.key out/A.pem out/B.pem out/C2.pem > redundant-validated-chain.pem
+
+echo Create redundant-validated-chain-root.pem
+cp out/C2.pem redundant-validated-chain-root.pem
+
diff --git a/chromium/net/data/ssl/scripts/generate-test-certs.sh b/chromium/net/data/ssl/scripts/generate-test-certs.sh
new file mode 100755
index 00000000000..9cff81072c6
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-test-certs.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+# Copyright 2013 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.
+
+# This script generates a set of test (end-entity, intermediate, root)
+# certificates that can be used to test fetching of an intermediate via AIA.
+
+try() {
+ echo "$@"
+ $@ || exit 1
+}
+
+try rm -rf out
+try mkdir out
+
+try echo 1 > out/2048-sha1-root-serial
+touch out/2048-sha1-root-index.txt
+
+# Generate the key
+try openssl genrsa -out out/2048-sha1-root.key 2048
+
+# Generate the root certificate
+CA_COMMON_NAME="Test Root CA" \
+ try openssl req \
+ -new \
+ -key out/2048-sha1-root.key \
+ -out out/2048-sha1-root.req \
+ -config ca.cnf
+
+CA_COMMON_NAME="Test Root CA" \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/2048-sha1-root.req \
+ -out out/2048-sha1-root.pem \
+ -text \
+ -signkey out/2048-sha1-root.key \
+ -extfile ca.cnf \
+ -extensions ca_cert
+
+# Generate the leaf certificate requests
+try openssl req \
+ -new \
+ -keyout out/expired_cert.key \
+ -out out/expired_cert.req \
+ -config ee.cnf
+
+try openssl req \
+ -new \
+ -keyout out/ok_cert.key \
+ -out out/ok_cert.req \
+ -config ee.cnf
+
+# Generate the leaf certificates
+CA_COMMON_NAME="Test Root CA" \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -startdate 060101000000Z \
+ -enddate 070101000000Z \
+ -in out/expired_cert.req \
+ -out out/expired_cert.pem \
+ -config ca.cnf
+
+CA_COMMON_NAME="Test Root CA" \
+ try openssl ca \
+ -batch \
+ -extensions user_cert \
+ -days 3650 \
+ -in out/ok_cert.req \
+ -out out/ok_cert.pem \
+ -config ca.cnf
+
+cat out/ok_cert.key out/ok_cert.pem \
+ > ../certificates/ok_cert.pem
+cat out/expired_cert.key out/expired_cert.pem \
+ > ../certificates/expired_cert.pem
+cat out/2048-sha1-root.key out/2048-sha1-root.pem \
+ > ../certificates/root_ca_cert.pem
+
diff --git a/chromium/net/data/ssl/scripts/generate-weak-test-chains.sh b/chromium/net/data/ssl/scripts/generate-weak-test-chains.sh
new file mode 100755
index 00000000000..845c8ab60e3
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/generate-weak-test-chains.sh
@@ -0,0 +1,168 @@
+#!/bin/sh
+
+# Copyright (c) 2011 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.
+
+# This script generates a set of test (end-entity, intermediate, root)
+# certificates with (weak, strong), (RSA, DSA, ECDSA) key pairs.
+
+key_types="768-rsa 1024-rsa 2048-rsa prime256v1-ecdsa"
+
+try () {
+ echo "$@"
+ $@ || exit 1
+}
+
+generate_key_command () {
+ case "$1" in
+ dsa)
+ echo "dsaparam -genkey"
+ ;;
+ ecdsa)
+ echo "ecparam -genkey"
+ ;;
+ rsa)
+ echo genrsa
+ ;;
+ *)
+ exit 1
+ esac
+}
+
+try rm -rf out
+try mkdir out
+
+# Create the serial number files.
+try echo 1 > out/2048-rsa-root-serial
+for key_type in $key_types
+do
+ try echo 1 > out/$key_type-intermediate-serial
+done
+
+# Generate one root CA certificate.
+try openssl genrsa -out out/2048-rsa-root.key 2048
+
+CA_COMMON_NAME="2048 RSA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ try openssl req \
+ -new \
+ -key out/2048-rsa-root.key \
+ -extensions ca_cert \
+ -out out/2048-rsa-root.csr \
+ -config ca.cnf
+
+CA_COMMON_NAME="2048 RSA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ try openssl x509 \
+ -req -days 3650 \
+ -in out/2048-rsa-root.csr \
+ -extensions ca_cert \
+ -signkey out/2048-rsa-root.key \
+ -out out/2048-rsa-root.pem
+
+# Generate private keys of all types and strengths for intermediate CAs and
+# end-entities.
+for key_type in $key_types
+do
+ key_size=$(echo "$key_type" | sed -E 's/-.+//')
+ algo=$(echo "$key_type" | sed -E 's/.+-//')
+
+ if [ ecdsa = $algo ]
+ then
+ key_size="-name $key_size"
+ fi
+
+ try openssl $(generate_key_command $algo) \
+ -out out/$key_type-intermediate.key $key_size
+done
+
+for key_type in $key_types
+do
+ key_size=$(echo "$key_type" | sed -E 's/-.+//')
+ algo=$(echo "$key_type" | sed -E 's/.+-//')
+
+ if [ ecdsa = $algo ]
+ then
+ key_size="-name $key_size"
+ fi
+
+ for signer_key_type in $key_types
+ do
+ try openssl $(generate_key_command $algo) \
+ -out out/$key_type-ee-by-$signer_key_type-intermediate.key $key_size
+ done
+done
+
+# The root signs the intermediates.
+for key_type in $key_types
+do
+ key_size=$(echo "$key_type" | sed -E 's/-.+//')
+ algo=$(echo "$key_type" | sed -E 's/.+-//')
+
+ CA_COMMON_NAME="$key_size $algo Test intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=$key_size \
+ ALGO=$algo \
+ CERT_TYPE=intermediate \
+ try openssl req \
+ -new \
+ -key out/$key_type-intermediate.key \
+ -out out/$key_type-intermediate.csr \
+ -config ca.cnf
+
+ # Make sure the signer's DB file exists.
+ touch out/2048-rsa-root-index.txt
+
+ CA_COMMON_NAME="2048 RSA Test Root CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=2048 \
+ ALGO=rsa \
+ CERT_TYPE=root \
+ try openssl ca \
+ -batch \
+ -extensions ca_cert \
+ -in out/$key_type-intermediate.csr \
+ -out out/$key_type-intermediate.pem \
+ -config ca.cnf
+done
+
+# The intermediates sign the end-entities.
+for key_type in $key_types
+do
+ for signer_key_type in $key_types
+ do
+ key_size=$(echo "$key_type" | sed -E 's/-.+//')
+ algo=$(echo "$key_type" | sed -E 's/.+-//')
+ signer_key_size=$(echo "$signer_key_type" | sed -E 's/-.+//')
+ signer_algo=$(echo "$signer_key_type" | sed -E 's/.+-//')
+ touch out/$signer_key_type-intermediate-index.txt
+
+ KEY_SIZE=$key_size \
+ try openssl req \
+ -new \
+ -key out/$key_type-ee-by-$signer_key_type-intermediate.key \
+ -out out/$key_type-ee-by-$signer_key_type-intermediate.csr \
+ -config ee.cnf
+
+ CA_COMMON_NAME="$signer_key_size $algo Test intermediate CA" \
+ CA_DIR=out \
+ CA_NAME=req_env_dn \
+ KEY_SIZE=$signer_key_size \
+ ALGO=$signer_algo \
+ CERT_TYPE=intermediate \
+ try openssl ca \
+ -batch \
+ -in out/$key_type-ee-by-$signer_key_type-intermediate.csr \
+ -out out/$key_type-ee-by-$signer_key_type-intermediate.pem \
+ -config ca.cnf
+ done
+done
+
diff --git a/chromium/net/data/ssl/scripts/policy.cnf b/chromium/net/data/ssl/scripts/policy.cnf
new file mode 100644
index 00000000000..f5f1e0b1f17
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/policy.cnf
@@ -0,0 +1,60 @@
+CA_DIR=out
+CA_NAME=policy-root
+
+[ca]
+default_ca = CA_root
+preserve = yes
+
+[CA_root]
+dir = ${ENV::CA_DIR}
+key_size = 2048
+algo = sha1
+database = $dir/${ENV::CA_NAME}-index.txt
+new_certs_dir = $dir
+serial = $dir/${ENV::CA_NAME}-serial
+certificate = $dir/${ENV::CA_NAME}.pem
+private_key = $dir/${ENV::CA_NAME}.key
+RANDFILE = $dir/.rand
+default_days = 3650
+default_crl_days = 30
+default_md = sha1
+policy = policy_anything
+unique_subject = no
+copy_extensions = copy
+
+[user_cert]
+basicConstraints = critical, CA:false
+extendedKeyUsage = serverAuth, clientAuth
+certificatePolicies = 1.2.3.4
+
+[ca_cert]
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, keyCertSign, cRLSign
+
+[intermediate_cert]
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, keyCertSign, cRLSign
+policyConstraints = requireExplicitPolicy:0
+certificatePolicies = 1.2.3.4, 1.2.3.4.5, 1.2.3.5
+
+[policy_anything]
+# Default signing policy
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+[req]
+default_bits = 2048
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = req_env_dn
+
+[req_env_dn]
+CN = ${ENV::COMMON_NAME}
+
diff --git a/chromium/net/data/ssl/scripts/redundant-ca.cnf b/chromium/net/data/ssl/scripts/redundant-ca.cnf
new file mode 100644
index 00000000000..e1b24e0cc40
--- /dev/null
+++ b/chromium/net/data/ssl/scripts/redundant-ca.cnf
@@ -0,0 +1,80 @@
+[ca]
+default_ca = CA_root
+preserve = yes
+
+# The default test root, used to generate certificates and CRLs.
+[CA_root]
+dir = $ENV::CA_DIR
+key_size = $ENV::KEY_SIZE
+algo = $ENV::ALGO
+cert_type = $ENV::CERT_TYPE
+type = $ENV::TYPE
+certificate = $ENV::CERTIFICATE
+database = $dir/$type-index.txt
+new_certs_dir = $dir
+serial = $dir/$type-serial
+certificate = $dir/$certificate.pem
+private_key = $dir/$type.key
+RANDFILE = $dir/rand
+default_days = 3650
+default_crl_days = 30
+default_md = sha1
+policy = policy_anything
+unique_subject = no
+
+[user_cert]
+# Extensions to add when signing a request for an EE cert
+basicConstraints = critical, CA:false
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always
+extendedKeyUsage = serverAuth,clientAuth
+
+[ca_cert]
+# Extensions to add when signing a request for an intermediate/CA cert
+basicConstraints = critical, CA:true
+subjectKeyIdentifier = hash
+#authorityKeyIdentifier = keyid:always
+keyUsage = critical, keyCertSign, cRLSign
+
+[crl_extensions]
+# Extensions to add when signing a CRL
+authorityKeyIdentifier = keyid:always
+
+[policy_anything]
+# Default signing policy
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = optional
+emailAddress = optional
+
+[req]
+# The request section used to generate the root CA certificate. This should
+# not be used to generate end-entity certificates. For certificates other
+# than the root CA, see README to find the appropriate configuration file
+# (ie: openssl_cert.cnf).
+default_bits = $ENV::KEY_SIZE
+default_md = sha1
+string_mask = utf8only
+prompt = no
+encrypt_key = no
+distinguished_name = $ENV::CA_NAME
+
+[req_ca_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = Test Root 2 CA
+
+[req_intermediate_dn]
+C = US
+ST = California
+L = Mountain View
+O = Test CA
+CN = Test Intermediate 2 CA
+
+[req_env_dn]
+CN = $ENV::CA_COMMON_NAME
diff --git a/chromium/net/data/test.html b/chromium/net/data/test.html
new file mode 100644
index 00000000000..63c016cdc12
--- /dev/null
+++ b/chromium/net/data/test.html
@@ -0,0 +1 @@
+<p>Hello World!</p> \ No newline at end of file
diff --git a/chromium/net/data/url_request_unittest/BullRunSpeech.txt b/chromium/net/data/url_request_unittest/BullRunSpeech.txt
new file mode 100644
index 00000000000..dd0670dcbb2
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/BullRunSpeech.txt
@@ -0,0 +1,78 @@
+"It Takes More Than That to Kill a Bull Moose":
+The Leader and The Cause*
+
+* Address at Milwaukee, Wis., October, 14, 1912. Just before entering the auditorium at Milwaukee, an attempt was made on Colonel Roosevelt's life. The above speech is from a stenographic report, differing considerably from the prepared manuscript.
+
+[TR was shot in an assasination attempt by John Schrank, who had been having disturbing dreams about TR's predecessor, William McKinley and also thought that no president should serve more than two terms.
+Schrank spent the rest of his life in a mental institution. No one came to visit him. He died shortly after Franklin Delano Roosevelt, TR's fifth cousin, was elected to a third term. Schrank had stalked TR for thousands of miles before getting a clear shot at him in Milwaukee. Schrank was caught on the spot.]
+Friends, I shall ask you to be as quiet as possible. I don't know whether you fully understand that I have just been shot; but it takes more than that to kill a Bull Moose. But fortunately I had my manuscript, so you see I was going to make a long speech, and there is a bullet - there is where the bullet went through - and it probably saved me from it going into my heart. The bullet is in me now, so that I cannot make a very long speech, but I will try my best.
+
+And now, friends, I want to take advantage of this incident to say a word of solemn warning to my fellow countrymen. First of all, I want to say this about myself: I have altogether too important things to think of to feel any concern over my own death; and now I cannot speak to you insincerely within five minutes of being shot. I am telling you the literal truth when I say that my concern is for many other things. It is not in the least for my own life. I want you to understand that I am ahead of the game, anyway. No man has had a happier life than I have led; a happier life in every way. I have been able to do certain things that I greatly wished to do, and I am interested in doing other things. I can tell you with absolute truthfulness that I am very much uninterested in whether I am shot or not. It was just as when I was colonel of my regiment. I always felt that a private was to be excused for feeling at times some pangs of anxiety about his personal safety, but I cannot understand a man fit to be a colonel who can pay any heed to his personal safety when he is occupied as he ought to be with the absorbing desire to do his duty.
+
+I am in this cause with my whole heart and soul. I believe that the Progressive movement is making life a little easier for all our people; a movement to try to take the burdens off the men and especially the women and children of this country. I am absorbed in the success of that movement.
+
+Friends, I ask you now this evening to accept what I am saying as absolutely true, when I tell you I am not thinking of my own success. I am not thinking of my life or of anything connected with me personally. I am thinking of the movement. I say this by way of introduction, because I want to say something very serious to our people and especially to the newspapers. I don't know anything about who the man was who shot me to-night. He was seized at once by one of the stenographers in my party, Mr. Martin, and I suppose is now in the hands of the police. He shot to kill. He shot - the shot, the bullet went in here - I will show you.
+
+I am going to ask you to be as quiet as possible for I am not able to give to challenge of the bull moose quite as loudly. Now, I do not know who he was or what he represented. He was a coward. He stood in the darkness in the crowd around the automobile and when they cheered me, and I got up to bow, he stepped forward and shot me in the darkness.
+
+Now, friends, of course, I do not know, as I say, anything about him; but it is a very natural thing that weak and vicious minds should be inflamed to acts of violence by the kind of awful mendacity and abuse that have been heaped upon me for the last three months by the papers in the interest of not only Mr. Debs but of Mr. Wilson and Mr. Taft.
+
+Friends, I will disown and repudiate any man of my party who attacks with such foul slander and abuse any opponent of any other party; and now I wish to say seriously to all the daily newspapers, to the Republicans, the Democrat, and Socialist parties, that they cannot, month in month out and year in and year out, make the kind of untruthful, of bitter assault that they have made and not expect that brutal, violent natures, or brutal and violent characters, especially when the brutality is accompanied by a not very strong mind; they cannot expect that such natures will be unaffected by it.
+
+Now, friends, I am not speaking for myself at all, I give you my word, I do not care a rap about being shot; not a rap.
+
+I have had a good many experiences in my time and this is one of them. What I care for is my country. I wish I were able to impress upon my people -- our people, the duty to feel strongly but to speak the truth of their opponents. I say now, I have never said one word one the stump against any opponent that I cannot defend. I have said nothing that I could not substantiate and nothing that I ought not to have said -- nothing that I -- nothing that, looking back at, I would not say again.
+
+Now, friends, it ought not to be too much to ask that our opponents -[speaking to some one on the stage]-I am not sick at all. I am all right. I cannot tell you of what infinitesimal importance I regard this incident as compared with the great issues at stake in this campaign, and I ask it not for my sake, not the least in the world, but for the sake of common country, that they make up their minds to speak only the truth, and not use that kind of slander and mendacity which if taken seriously must incite weak and violent natures to crimes of violence. Don't you make any mistake. Don't you pity me. I am all right. I am all right and you cannot escape listening to the speech either.
+
+And now, friends, this incident that has just occurred - this effort to assassinate me- emphasizes to a peculiar degree the need of the Progressive movement. Friends, every good citizen ought to do everything in his or her power to prevent the coming of the day when we shall see in this country two recognized creeds fighting one another, when we shall see the creed of the "Havenots" arraigned against the creed of the "Haves." When that day comes then such incidents as this to-night will be commonplace in our history. When you make poor men - when you permit the conditions to grow such that the poor man as such will be swayed by his sense of injury against the men who try to hold what they improperly have won, when that day comes, the most awful passions will be let loose and it will be an ill day for our country.
+
+Now, friends, what we who are in this movement are endeavoring to do is forestall any such movement for justice now - a movement in which we ask all just men of generous hearts to join with the men who feel in their souls that lift upward which bids them refuse to be satisfied themselves while their countrymen and countrywomen suffer from avoidable misery. Now, friends, what we Progressives are trying to do is to enroll rich or poor, whatever their social or industrial position, to stand together for the most elementary rights of good citizenship, those elementary rights which are the foundation of good citizenship in this great Republic of ours.
+
+(At this point a renewed effort was made to persuade Mr. Roosevelt to conclude his speech.)
+
+My friends are a little more nervous than I am. Don't you waste any sympathy on me. I have had an A-1 time in life and I am having it now.
+
+I never in my life was in any movement in which I was able to serve with such whole-hearted devotion as in this; in which I was able to feel as I do in this that common weal. I have fought for the good of our common country.
+
+And now, friends, I shall have to cut short much of that speech that I meant to give you, but I want to touch on just two or three points.
+
+In the first place, speaking to you here in Milwaukee, I wish to say that the Progressive party is making its appeals to all our fellow citizens without any regard to their creed or to their birthplace. We do not regard as essential the way in which a man worships his God or as being affected by where he was born. We regard it as a matter of spirit and purpose. In New York, while I was police commissioner, the two men from whom I got the most assistance were Jacob Riis, who was born in Denmark, and Arthur von Briesen, who was born in Germany - both of them as fine examples of the best and highest American citizenship as you could find in any part of this country.
+
+I have just been introduced by one of your own men here - Henry Cochems. His grandfather, his father, and that father's seven brothers, all served in the United States army, and they entered it four years after they had come to this country from Germany. Two of them left their lives, spent their lives, on the field of battle. I am all right - I am a little sore. Anybody has a right to be sore with a bullet in him. You would find that if I was in battle now I would be leading my men just the same. Just the same way I am going to make this speech.
+
+At one time I promoted five men for gallantry on the field of battle. Afterward in making some inquiries about them I found that two of them were Protestants, two Catholic, and one a Jew. One Protestant came from Germany and one was born in Ireland. I did not promote them because of their religion. It just happened that way. If all five of them had been Jews I would have promoted them, or if all five of them had been Protestants I would have promoted them; or if they had been Catholics. In that regiment I had a man born in Italy who distinguished himself by gallantry; there was another young fellow, a son of Polish parents, and another who came here when he was a child from Bohemia, who likewise distinguished themselves; and friends, I assure you, that I was incapable of considering any question whatever, but the worth of each individual as a fighting man. If he was a good fighting man, then I saw that Uncle Sam got the benefit of it. That is all.
+
+I make the same appeal to our citizenship. I ask in our civic life that we in the same way pay heed only to the man's quality of citizenship, to repudiate as the worst enemy that we can have whoever tries to get us to discriminate for or against any man because of his creed or birthplace.
+
+Now, friends, in the same way I want out people to stand by one another without regard to differences or class or occupation. I have always stood by labor-unions. I am going to make one omission to-night. I have prepared my speech because Mr. Wilson had seen fit to attack me by showing up his record in comparison with mine. But I am not going to do that to-night. I am going to simply speak of what I myself have done and what I think ought to be done in this country of ours.
+
+It is essential that here should be organizations of labor. This is an era of organization. Capital organizes and therefore labor must organize. My appeal for organized labor is two-fold; to the outsider and the capitalist I make my appeal to treat the laborer fairly, to recognize the fact that he must organize that there must be such organization, that the laboring man must organize for his own protection, and that it is the duty of the rest of is to help him and not hinder him in organizing. That is one-half appeal that I make.
+
+Now, the other half is to the labor man himself. My appeal to him is to remember that as he wants justice, so he must do justice. I want every labor man, every labor leader, every organized union man, to take the lead in denouncing disorder and in denouncing the inciting of riot; that in this country we shall proceed under the protection of our laws and with all respect to the laws, I want the labor men to feel in their turn that exactly as justice must be done them so they must do justice. They must bear their duty as citizens, their duty to this great country of ours, and that they must not rest content unless they do that duty to the fullest degree.
+
+I know these doctors, when they get hold of me, will never let me go back, and there are just a few more things that I want to say to you.
+
+And here I have got to make one comparison between Mr. Wilson and myself, simply because he has invited it and I cannot shrink from it. Mr. Wilson has seen fit to attack me, to say that I did not do much against the trusts when I was President. I have got two answers to make to that. In the first place what I did, and then I want to compare what I did when I was President with what Mr. Wilson did not do when he was governor.
+
+When I took the office the antitrust law was practically a dead letter and the interstate commerce law in as poor a condition. I had to revive both laws. I did. I enforced both. It will be easy enough to do now what I did then, but the reason that it is easy now is because I did it when it was hard.
+
+Nobody was doing anything. I found speedily that the interstate commerce law by being made perfect could be made a most useful instrument for helping solve some of our industrial problems. So with the antitrust law. I speedily found out that almost the only positive good achieved by such a successful lawsuit as the Northern Securities suit, for instance, was in establishing the principle that the government was supreme over the big corporation, but by itself that the law did not accomplish any of the things that we ought to have accomplished; and so I began to fight for the amendment of the law along the lines of the interstate commerce law, and now we propose, we Progressives, to establish and interstate commission having the same power over industrial concerns that the Interstate Commerce Commission has over railroads, so that whenever there is in the future a decision rendered in such important matters as the recent suits against the Standard Oil, the Sugar - no, not that - Tobacco - Tobacco Trust - we will have a commission which will see that the decree of the court is really made effective; that it is not made a merely nominal decree.
+
+Our opponents have said that we intend to legalize monopoly. Nonsense. They have legalized monopoly. At this moment the Standard Oil and Tobacco Trust monopolies are legalized; they are being carried on under the decree of the Supreme Court. Our proposal is really to break up monopoly. Our proposal is to lay down certain requirements, and then to require the commerce commission - the industrial commission - to see that the trusts live up to those requirements. Our opponents have spoken as if we were going to let the commission declare what those requirements should be. Not at all. We are going to put the requirements in the law and then see that the commission requires them to obey that law.
+
+And now, friends, as Mr. Wilson has invited the comparison, I only want to say this: Mr. Wilson has said that the States are the proper authorities to deal with the trusts. Well, about eighty percent of the trusts are organized in New Jersey. The Standard Oil, the Tobacco, the Sugar, the Beef, all those trusts are organized in the state of New Jersey and the laws of New Jersey say that their charters can at any time be amended or repealed if they misbehave themselves and give the government ample power to act about those laws, and Mr. Wilson has been governor a year and nine months and he has not opened his lips. The chapter describing what Mr. Wilson has done about trusts in New Jersey would read precisely like a chapter describing snakes in Ireland, which ran: "There are no snakes in Ireland." Mr. Wilson has done precisely and exactly nothing about the trusts.
+
+I tell you, and I told you at the beginning, I do not say anything on the stump that I do not believe. I do not say anything I do not know. Let any of Mr. Wilson's friends on Tuesday point out one thing or let Mr. Wilson point out one thing that he has done about the trusts as governor of New Jersey.
+
+And now, friends, there is one thing I want to say especially to you people here in Wisconsin. All that I have said so far is what I would say in any part of the Union. I have a peculiar right to ask that in this great contest you men and women of Wisconsin shall stand with us. You have taken the lead in progressive movements here in Wisconsin. You have taught the rest of us to look to you for inspiration and leadership. Now, friends, you have made that movement here locally. You will being doing a dreadful injustice to yourselves; you will be doing a dreadful injustice to the rest of us throughout the Union, if you fail to stand with us now that we are making this national movement. What I am about to say now I want yo to understand. If I speak of Mr. Wilson I speak with no mind of bitterness. I merely want to discuss the difference of policy between the Progressive and the Democratic party and to ask you to think for yourselves which party you will follow. I will say that, friends, because the Republican party is beaten. Nobody needs to have any idea that anything can be done with the Republican party.
+
+When the Republican party - not the Republican party - when the bosses in control of the Republican party, the Barneses and Penroses, last June stole the nomination and wrecked the Republican party for good and all - I want to point out to you that nominally they stole that nomination from me, but it was really from you. They did not like me, and the longer they live the less cause they will have to like me. But while they don't like me, they dread you. You are the people that they dread. They dread the people themselves, and those bosses and the big special interests behind them made up their mind that they would rather see the Republican party wrecked than see it come under the control of the people themselves. So I am not dealing with the Republican party. There are only two ways you can vote this year. You can be progressive or reactionary. Whether you vote Republican or Democratic it does not make a difference, you are voting reactionary.
+
+Now, the Democratic party in its platform and through the utterances of Mr. Wilson has distinctly committed itself to the old flintlock, muzzle-loaded doctrine of States' rights, and I have said distinctly we are for people's rights. We are for the rights of the people. If they can be obtained best through National Government, then we are for national rights. We are for people's rights however it is necessary to secure them.
+
+Mr. Wilson has made a long essay against Senator Beveridge's bill to abolish child labor. It is the same kind of argument that would be made against our bill to prohibit women from working more than eight hours a day in industry. It is the same kind of argument that would have to be made; if it is true, it would apply equally against our proposal to insist that in continuous industries there shall be by law one day's rest in seven and three-shift eight-hour day. You have labor laws here in Wisconsin, and chamber of commerce will tell you that because of that fact there are industries that will not come to Wisconsin. They prefer to stay outside where they can work children of tender years, where they can work women fourteen and sixteen hours a day, where if it is a continuous industry, they can work men twelve hours a day and seven days a week.
+
+Now, friends, I know that you of Wisconsin would never repeal those laws even if they are at your commercial hurt, just as I am trying to get New York to adopt such laws even though it will be to the New York's commercial hurt. But if possible I want to arrange it so that we can have justice without commercial hurt, and you can only get that if you have justice enforced nationally. You won't be burdened in Wisconsin with industries not coming to the State if the same good laws are extended all over the other States. Do you see what I mean? The States all compete in a common market; and it is not justice to the employers of a State that has enforced just and proper laws to have them exposed to the competition of another State where no such laws are enforced. Now, the Democratic platform, and their speakers declare we shall not have such laws. Mr. Wilson has distinctly declared that we shall not have a national law to prohibit the labor of children, to prohibit child labor. He has distinctly declared that we shall not have a law to establish a minimum wage for women.
+
+I ask you to look at our declaration and hear and read our platform about social and industrial justice and then, friends, vote for the Progressive ticket without regard to me, without regard to my personality, for only by voting for that platform can you be true to the cause of progress throughout this Union. \ No newline at end of file
diff --git a/chromium/net/data/url_request_unittest/content-type-normalization.html b/chromium/net/data/url_request_unittest/content-type-normalization.html
new file mode 100644
index 00000000000..4e54a7887ff
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/content-type-normalization.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Space in Content-Type</title>
+</head>
+<body>
+Space in Content-Type
+</body>
+</html>
diff --git a/chromium/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers b/chromium/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers
new file mode 100644
index 00000000000..8004f45bbeb
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/content-type-normalization.html.mock-http-headers
@@ -0,0 +1,5 @@
+HTTP/1.1 200 OK
+Content-Type: tEXt/Html ; charset=utF-8
+Content-Length: 104
+Date: Mon, 13 Nov 2006 21:38:09 GMT
+Expires: Tue, 14 Nov 2006 19:23:58 GMT
diff --git a/chromium/net/data/url_request_unittest/hpkp-headers.html b/chromium/net/data/url_request_unittest/hpkp-headers.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hpkp-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers b/chromium/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers
new file mode 100644
index 00000000000..f4918405732
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hpkp-headers.html.mock-http-headers
@@ -0,0 +1,6 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="
diff --git a/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html.mock-http-headers b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html.mock-http-headers
new file mode 100644
index 00000000000..1fa93c9c1e4
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers.html.mock-http-headers
@@ -0,0 +1,8 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Strict-Transport-Security: max-age=12300
+Strict-Transport-Security: max-age=12300; includeSubdomains
+Public-Key-Pins: max-age=50000; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="; pin-sha256="9CC/1Ckv3HMym0R5QmBCAdq7cAql/VpDoOV05p1cqUQ="
diff --git a/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers
new file mode 100644
index 00000000000..9c0feda595e
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-and-hpkp-headers2.html.mock-http-headers
@@ -0,0 +1,8 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Strict-Transport-Security: max-age=12300; includeSubdomains
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="
+Public-Key-Pins: max-age=50000; pin-sha1="K9e3/nFL5j90GuVJOJBv6WXpvcs="; pin-sha256="kd16uBd5KFa9IJjF0X+8B+BXdAWkYYRZruNKDZ0M9Zw="; pin-sha1="Wws2/Z7YhKlX73v3rYHBBxO4OLE="; includeSubdomains
diff --git a/chromium/net/data/url_request_unittest/hsts-headers.html b/chromium/net/data/url_request_unittest/hsts-headers.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/hsts-headers.html.mock-http-headers b/chromium/net/data/url_request_unittest/hsts-headers.html.mock-http-headers
new file mode 100644
index 00000000000..e3972965736
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-headers.html.mock-http-headers
@@ -0,0 +1,6 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Strict-Transport-Security: max-age=123; includeSubdomains
diff --git a/chromium/net/data/url_request_unittest/hsts-multiple-headers.html b/chromium/net/data/url_request_unittest/hsts-multiple-headers.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-multiple-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/hsts-multiple-headers.html.mock-http-headers b/chromium/net/data/url_request_unittest/hsts-multiple-headers.html.mock-http-headers
new file mode 100644
index 00000000000..e83b7220314
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/hsts-multiple-headers.html.mock-http-headers
@@ -0,0 +1,7 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
+Strict-Transport-Security: max-age=123
+Strict-Transport-Security: max-age=123; includeSubdomains
diff --git a/chromium/net/data/url_request_unittest/redirect-test.html b/chromium/net/data/url_request_unittest/redirect-test.html
new file mode 100644
index 00000000000..ce013625030
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-test.html
@@ -0,0 +1 @@
+hello
diff --git a/chromium/net/data/url_request_unittest/redirect-test.html.mock-http-headers b/chromium/net/data/url_request_unittest/redirect-test.html.mock-http-headers
new file mode 100644
index 00000000000..9fdd1c0b7b9
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-test.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 302 Redirect
+Location: with-headers.html
diff --git a/chromium/net/data/url_request_unittest/redirect-to-echoall b/chromium/net/data/url_request_unittest/redirect-to-echoall
new file mode 100644
index 00000000000..78981922613
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-echoall
@@ -0,0 +1 @@
+a
diff --git a/chromium/net/data/url_request_unittest/redirect-to-echoall.mock-http-headers b/chromium/net/data/url_request_unittest/redirect-to-echoall.mock-http-headers
new file mode 100644
index 00000000000..b1cc94a6af5
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-echoall.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 302 Yo
+Location: /echoall/
diff --git a/chromium/net/data/url_request_unittest/redirect-to-file.html b/chromium/net/data/url_request_unittest/redirect-to-file.html
new file mode 100644
index 00000000000..ce013625030
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-file.html
@@ -0,0 +1 @@
+hello
diff --git a/chromium/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers b/chromium/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers
new file mode 100644
index 00000000000..10994b7e7fc
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-file.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 302 Here I Am
+Location: file:///c:/windows
diff --git a/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html b/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html
new file mode 100644
index 00000000000..ce013625030
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html
@@ -0,0 +1 @@
+hello
diff --git a/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html.mock-http-headers b/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html.mock-http-headers
new file mode 100644
index 00000000000..e106b6cc729
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect-to-invalid-url.html.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 302 Found
+Location: http://user:pass@/
diff --git a/chromium/net/data/url_request_unittest/redirect301-to-echo b/chromium/net/data/url_request_unittest/redirect301-to-echo
new file mode 100644
index 00000000000..78981922613
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect301-to-echo
@@ -0,0 +1 @@
+a
diff --git a/chromium/net/data/url_request_unittest/redirect301-to-echo.mock-http-headers b/chromium/net/data/url_request_unittest/redirect301-to-echo.mock-http-headers
new file mode 100644
index 00000000000..2f97903d516
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect301-to-echo.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 301 Yo
+Location: /echo
diff --git a/chromium/net/data/url_request_unittest/redirect302-to-echo b/chromium/net/data/url_request_unittest/redirect302-to-echo
new file mode 100644
index 00000000000..78981922613
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect302-to-echo
@@ -0,0 +1 @@
+a
diff --git a/chromium/net/data/url_request_unittest/redirect302-to-echo.mock-http-headers b/chromium/net/data/url_request_unittest/redirect302-to-echo.mock-http-headers
new file mode 100644
index 00000000000..819878d3721
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect302-to-echo.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 302 Yo
+Location: /echo
diff --git a/chromium/net/data/url_request_unittest/redirect303-to-echo b/chromium/net/data/url_request_unittest/redirect303-to-echo
new file mode 100644
index 00000000000..78981922613
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect303-to-echo
@@ -0,0 +1 @@
+a
diff --git a/chromium/net/data/url_request_unittest/redirect303-to-echo.mock-http-headers b/chromium/net/data/url_request_unittest/redirect303-to-echo.mock-http-headers
new file mode 100644
index 00000000000..63e08846539
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect303-to-echo.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 303 Yo
+Location: /echo
diff --git a/chromium/net/data/url_request_unittest/redirect307-to-echo b/chromium/net/data/url_request_unittest/redirect307-to-echo
new file mode 100644
index 00000000000..78981922613
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect307-to-echo
@@ -0,0 +1 @@
+a
diff --git a/chromium/net/data/url_request_unittest/redirect307-to-echo.mock-http-headers b/chromium/net/data/url_request_unittest/redirect307-to-echo.mock-http-headers
new file mode 100644
index 00000000000..3d9f6a9390b
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/redirect307-to-echo.mock-http-headers
@@ -0,0 +1,2 @@
+HTTP/1.1 307 Yo
+Location: /echo
diff --git a/chromium/net/data/url_request_unittest/with-headers.html b/chromium/net/data/url_request_unittest/with-headers.html
new file mode 100644
index 00000000000..364322de38d
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/with-headers.html
@@ -0,0 +1 @@
+This file is boring; all the action's in the .mock-http-headers.
diff --git a/chromium/net/data/url_request_unittest/with-headers.html.mock-http-headers b/chromium/net/data/url_request_unittest/with-headers.html.mock-http-headers
new file mode 100644
index 00000000000..ba8c15f8012
--- /dev/null
+++ b/chromium/net/data/url_request_unittest/with-headers.html.mock-http-headers
@@ -0,0 +1,5 @@
+HTTP/1.1 200 OK
+Cache-Control: private
+Content-Type: text/html; charset=ISO-8859-1
+X-Multiple-Entries: a
+X-Multiple-Entries: b
diff --git a/chromium/net/data/websocket/OWNERS b/chromium/net/data/websocket/OWNERS
new file mode 100644
index 00000000000..7af5bc89e36
--- /dev/null
+++ b/chromium/net/data/websocket/OWNERS
@@ -0,0 +1,3 @@
+bashi@chromium.org
+toyoshim@chromium.org
+yutak@chromium.org
diff --git a/chromium/net/data/websocket/README b/chromium/net/data/websocket/README
new file mode 100644
index 00000000000..9dab697eba2
--- /dev/null
+++ b/chromium/net/data/websocket/README
@@ -0,0 +1,49 @@
+This directory contains resources used by WebSocket server for testing.
+Multiple tests may share one resource, or URI handler.
+
+- connect_check.html : A page provides simple WebSocket connectivity check.
+ This page changes page title to "PASS" to notify content::TitleWatcher.
+ Used by ProxyBrowserTest.BasicAuthWSConnect,
+ SSLUITest.TestWSSInvalidCertAndGoForward, SSLUITest.TestWSSClientCert,
+ and SSLUITestIgnoreCertErrors.TestWSS.
+
+- split_packet_check.html : A page for testing split packet handling. Here,
+ packets mean TCP segments for WebSocket, or SSL records for secure
+ WebSocket. This page changes the title to "PASS" to notify
+ content::TitleWatcher.
+ Used by WebSocketBrowserTest.WebSocketSplitSegments and
+ WebSocketBrowserTest.SecureWebSocketSplitRecords.
+
+- websocket_shared_worker.html : A page provides simple WebSocket test in
+ shared worker. This page changes page title to "PASS" to notify
+ content::TitleWatcher.
+ Used by WorkerTest.WebSocketSharedWorker.
+
+- websocket_worker_simple.js : A JavaScript runs on Workers created from the
+ websocket_shared_worker.html.
+ Used by WorkerTest.WebSocketSharedWorker.
+
+- echo-with-no-extension_wsh.py : A WebSocket URL handler for echo testing.
+ This handler disables all WebSocket extension so that we can perform
+ frame data dependent tests.
+ Used by kinds of PPAPI tests for WebSocket, ExtensionApiTest.WebSocket,
+ and WorkerTest.WebSocketSharedWorker.
+
+- close_wsh.py : A WebSocket URL handler for testing outgoing close code and
+ reason.
+ Used by kinds of PPAPI tests for WebSocket.
+
+- close-code-and-reason_wsh.py : A WebSocket URL handler for testing server
+ initiated closing handshake. A client can ask server to close the
+ connection with arbitrary code and reason.
+ Used by kinds of PPAPI tests for WebSocket.
+
+- close-with-split-packet_wsh.py : A WebSocket URL handler for testing split
+ packet handling. Here, packets mean TCP segments for WebSocket, or SSL
+ records for secure WebSocket.
+ Used by WebSocketBrowserTest.WebSocketSplitSegments and
+ WebSocketBrowserTest.SecureWebSocketSplitRecords.
+
+- protocol-test_wsh.py : A WebSocket URL handler for testing outgoing opening
+ handshake protocol.
+ Used by kinds of PPAPI tests for WebSocket.
diff --git a/chromium/net/data/websocket/close-code-and-reason_wsh.py b/chromium/net/data/websocket/close-code-and-reason_wsh.py
new file mode 100644
index 00000000000..c98bfd4418a
--- /dev/null
+++ b/chromium/net/data/websocket/close-code-and-reason_wsh.py
@@ -0,0 +1,25 @@
+# Copyright (c) 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.
+
+import struct
+
+from mod_pywebsocket import stream
+
+
+def web_socket_do_extra_handshake(_request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if line == '-':
+ data = ''
+ elif line == '--':
+ data = 'X'
+ else:
+ code, reason = line.split(' ', 1)
+ data = struct.pack('!H', int(code)) + reason.encode('utf-8')
+ request.connection.write(stream.create_close_frame(data))
diff --git a/chromium/net/data/websocket/close-with-split-packet_wsh.py b/chromium/net/data/websocket/close-with-split-packet_wsh.py
new file mode 100644
index 00000000000..d5185c4114b
--- /dev/null
+++ b/chromium/net/data/websocket/close-with-split-packet_wsh.py
@@ -0,0 +1,30 @@
+# Copyright (c) 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.
+
+import struct
+
+from mod_pywebsocket import handshake
+from mod_pywebsocket import stream
+
+
+def web_socket_do_extra_handshake(_request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, unicode):
+ request.ws_stream.send_message(line, binary=False)
+ else:
+ request.ws_stream.send_message(line, binary=True)
+
+
+def web_socket_passive_closing_handshake(request):
+ code = struct.pack('!H', 1000)
+ packet = stream.create_close_frame(code + 'split test'.encode('utf-8'))
+ request.connection.write(packet[:1])
+ request.connection.write(packet[1:])
+ raise handshake.AbortedByUserException('Abort the connection')
diff --git a/chromium/net/data/websocket/close_wsh.py b/chromium/net/data/websocket/close_wsh.py
new file mode 100644
index 00000000000..741effa5a8d
--- /dev/null
+++ b/chromium/net/data/websocket/close_wsh.py
@@ -0,0 +1,26 @@
+# Copyright (c) 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.
+
+_GOODBYE_MESSAGE = u'Goodbye'
+
+
+def web_socket_do_extra_handshake(_request):
+ pass # Always accept.
+
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, unicode):
+ request.ws_stream.send_message(line, binary=False)
+ if line == _GOODBYE_MESSAGE:
+ return
+ else:
+ request.ws_stream.send_message(line, binary=True)
+
+
+def web_socket_passive_closing_handshake(request):
+ return request.ws_close_code, request.ws_close_reason
diff --git a/chromium/net/data/websocket/connect_check.html b/chromium/net/data/websocket/connect_check.html
new file mode 100644
index 00000000000..4a1ca8c5dfc
--- /dev/null
+++ b/chromium/net/data/websocket/connect_check.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test ws connection</title>
+<script type="text/javascript">
+
+var href = window.location.href;
+var hostBegin = href.indexOf('/') + 2;
+var hostEnd = href.lastIndexOf(':');
+var host = href.slice(hostBegin, hostEnd);
+var portBegin = hostEnd + 1;
+var portEnd = href.lastIndexOf('/');
+var port = href.slice(portBegin, portEnd);
+var scheme = href.indexOf('https') >= 0 ? 'wss' : 'ws';
+var url = scheme + '://' + host + ':' + port + '/echo-with-no-extension';
+
+// Do connection test.
+var ws = new WebSocket(url);
+
+ws.onopen = function()
+{
+ // Set document title to 'PASS'. The test observer catches this title changes
+ // to know the result.
+ document.title = 'PASS';
+}
+
+ws.onclose = function()
+{
+ // Set document title to 'FAIL'.
+ document.title = 'FAIL';
+}
+
+</script>
+</head>
+</html>
diff --git a/chromium/net/data/websocket/echo-with-no-extension_wsh.py b/chromium/net/data/websocket/echo-with-no-extension_wsh.py
new file mode 100644
index 00000000000..a7dca019f4e
--- /dev/null
+++ b/chromium/net/data/websocket/echo-with-no-extension_wsh.py
@@ -0,0 +1,22 @@
+# Copyright (c) 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.
+
+_GOODBYE_MESSAGE = u'Goodbye'
+
+
+def web_socket_do_extra_handshake(request):
+ request.ws_extension_processors = []
+
+
+def web_socket_transfer_data(request):
+ while True:
+ line = request.ws_stream.receive_message()
+ if line is None:
+ return
+ if isinstance(line, unicode):
+ request.ws_stream.send_message(line, binary=False)
+ if line == _GOODBYE_MESSAGE:
+ return
+ else:
+ request.ws_stream.send_message(line, binary=True)
diff --git a/chromium/net/data/websocket/protocol-test_wsh.py b/chromium/net/data/websocket/protocol-test_wsh.py
new file mode 100644
index 00000000000..378b2e3cf63
--- /dev/null
+++ b/chromium/net/data/websocket/protocol-test_wsh.py
@@ -0,0 +1,19 @@
+# Copyright (c) 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.
+
+import cgi
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+ r = request.ws_resource.split('?', 1)
+ if len(r) == 1:
+ return
+ param = cgi.parse_qs(r[1])
+ if 'protocol' in param:
+ request.ws_protocol = param['protocol'][0]
+
+
+def web_socket_transfer_data(request):
+ msgutil.send_message(request, request.ws_protocol)
diff --git a/chromium/net/data/websocket/split_packet_check.html b/chromium/net/data/websocket/split_packet_check.html
new file mode 100644
index 00000000000..8e273ec2849
--- /dev/null
+++ b/chromium/net/data/websocket/split_packet_check.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test ws split packet</title>
+<script type="text/javascript">
+
+var href = window.location.href;
+var hostBegin = href.indexOf('/') + 2;
+var hostEnd = href.lastIndexOf(':');
+var host = href.slice(hostBegin, hostEnd);
+var portBegin = hostEnd + 1;
+var portEnd = href.lastIndexOf('/');
+var port = href.slice(portBegin, portEnd);
+var scheme = href.indexOf('https') >= 0 ? 'wss' : 'ws';
+var url = scheme + '://' + host + ':' + port + '/close-with-split-packet';
+
+// Do connection test.
+var ws = new WebSocket(url);
+
+ws.onopen = function()
+{
+ // Close WebSocket connection once it is established.
+ ws.close();
+}
+
+ws.onclose = function(event)
+{
+ // Check wasClean, then set proper title.
+ if (event.wasClean)
+ document.title = 'PASS';
+ else
+ document.title = 'FAIL';
+}
+
+</script>
+</head>
+</html>
diff --git a/chromium/net/data/websocket/websocket_shared_worker.html b/chromium/net/data/websocket/websocket_shared_worker.html
new file mode 100644
index 00000000000..7acb4ab3741
--- /dev/null
+++ b/chromium/net/data/websocket/websocket_shared_worker.html
@@ -0,0 +1,31 @@
+<html>
+<body>
+<div id=result></div>
+<script>
+function log(message)
+{
+ document.getElementById("result").innerHTML += message + "<br>";
+}
+var worker = new SharedWorker("websocket_worker_simple.js");
+var href = window.location.href;
+var hostBegin = href.indexOf("/") + 2;
+var hostEnd = href.lastIndexOf(":");
+var host = href.slice(hostBegin, hostEnd);
+var portBegin = hostEnd + 1;
+var portEnd = href.lastIndexOf("/");
+var port = href.slice(portBegin, portEnd);
+var url = "ws://" + host + ":" + port + "/echo-with-no-extension";
+worker.port.onmessage = function (evt) {
+ log(evt.data);
+ if (evt.data == "DONE") {
+ document.title = "OK";
+ } else {
+ document.title = "FAIL";
+ }
+};
+worker.port.postMessage(url);
+
+</script>
+</body>
+</html>
+
diff --git a/chromium/net/data/websocket/websocket_worker_simple.js b/chromium/net/data/websocket/websocket_worker_simple.js
new file mode 100644
index 00000000000..37e3be8b80d
--- /dev/null
+++ b/chromium/net/data/websocket/websocket_worker_simple.js
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+if (!self.postMessage) {
+ // This is a shared worker - mimic dedicated worker APIs
+ onconnect = function(event) {
+ event.ports[0].onmessage = function(e) {
+ self.postMessage = function (msg) {
+ event.ports[0].postMessage(msg);
+ };
+ runTests(e.data);
+ };
+ };
+} else {
+ runTests(event.data);
+}
+
+// This test is compatible with shared-worker-simple.html layout test.
+runTests = function (url) {
+ var ws;
+ var greeting = "hello";
+ try {
+ ws = new WebSocket(url);
+ ws.onopen = function() {
+ ws.send(greeting);
+ };
+ ws.onmessage = function(e) {
+ // Receive echoed "hello".
+ if (e.data != greeting) {
+ postMessage("FAIL: received data is wrong: " + e.data);
+ } else {
+ ws.close();
+ }
+ };
+ ws.onclose = function(e) {
+ if (!e.wasClean) {
+ postMessage("FAIL: close is not clean");
+ } else {
+ postMessage("DONE");
+ }
+ };
+ } catch (e) {
+ postMessage("FAIL: worker: Unexpected exception: " + e);
+ }
+};
diff --git a/chromium/net/disk_cache/addr.cc b/chromium/net/disk_cache/addr.cc
new file mode 100644
index 00000000000..8f41e6fe278
--- /dev/null
+++ b/chromium/net/disk_cache/addr.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/addr.h"
+
+#include "base/logging.h"
+
+namespace disk_cache {
+
+int Addr::start_block() const {
+ DCHECK(is_block_file());
+ return value_ & kStartBlockMask;
+}
+
+int Addr::num_blocks() const {
+ DCHECK(is_block_file() || !value_);
+ return ((value_ & kNumBlocksMask) >> kNumBlocksOffset) + 1;
+}
+
+bool Addr::SetFileNumber(int file_number) {
+ DCHECK(is_separate_file());
+ if (file_number & ~kFileNameMask)
+ return false;
+ value_ = kInitializedMask | file_number;
+ return true;
+}
+
+bool Addr::SanityCheckV2() const {
+ if (!is_initialized())
+ return !value_;
+
+ if (file_type() > BLOCK_4K)
+ return false;
+
+ if (is_separate_file())
+ return true;
+
+ return !reserved_bits();
+}
+
+bool Addr::SanityCheckV3() const {
+ if (!is_initialized())
+ return !value_;
+
+ // For actual entries, SanityCheckForEntryV3 should be used.
+ if (file_type() > BLOCK_FILES)
+ return false;
+
+ if (is_separate_file())
+ return true;
+
+ return !reserved_bits();
+}
+
+bool Addr::SanityCheckForEntryV2() const {
+ if (!SanityCheckV2() || !is_initialized())
+ return false;
+
+ if (is_separate_file() || file_type() != BLOCK_256)
+ return false;
+
+ return true;
+}
+
+bool Addr::SanityCheckForEntryV3() const {
+ if (!is_initialized())
+ return false;
+
+ if (reserved_bits())
+ return false;
+
+ if (file_type() != BLOCK_ENTRIES && file_type() != BLOCK_EVICTED)
+ return false;
+
+ if (num_blocks() != 1)
+ return false;
+
+ return true;
+}
+
+bool Addr::SanityCheckForRankings() const {
+ if (!SanityCheckV2() || !is_initialized())
+ return false;
+
+ if (is_separate_file() || file_type() != RANKINGS || num_blocks() != 1)
+ return false;
+
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/addr.h b/chromium/net/disk_cache/addr.h
new file mode 100644
index 00000000000..f0fb1ca5701
--- /dev/null
+++ b/chromium/net/disk_cache/addr.h
@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+
+// This is an internal class that handles the address of a cache record.
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_ADDR_H_
+#define NET_DISK_CACHE_ADDR_H_
+
+#include "net/base/net_export.h"
+#include "net/disk_cache/disk_format_base.h"
+
+namespace disk_cache {
+
+enum FileType {
+ EXTERNAL = 0,
+ RANKINGS = 1,
+ BLOCK_256 = 2,
+ BLOCK_1K = 3,
+ BLOCK_4K = 4,
+ BLOCK_FILES = 5,
+ BLOCK_ENTRIES = 6,
+ BLOCK_EVICTED = 7
+};
+
+const int kMaxBlockSize = 4096 * 4;
+const int kMaxBlockFile = 255;
+const int kMaxNumBlocks = 4;
+const int kFirstAdditionalBlockFile = 4;
+const int kFirstAdditionalBlockFileV3 = 7;
+
+// Defines a storage address for a cache record
+//
+// Header:
+// 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit
+// 0111 0000 0000 0000 0000 0000 0000 0000 : file type
+//
+// File type values:
+// 0 = separate file on disk
+// 1 = rankings block file
+// 2 = 256 byte block file
+// 3 = 1k byte block file
+// 4 = 4k byte block file
+// 5 = external files block file
+// 6 = active entries block file
+// 7 = evicted entries block file
+//
+// If separate file:
+// 0000 1111 1111 1111 1111 1111 1111 1111 : file# 0 - 268,435,456 (2^28)
+//
+// If block file:
+// 0000 1100 0000 0000 0000 0000 0000 0000 : reserved bits
+// 0000 0011 0000 0000 0000 0000 0000 0000 : number of contiguous blocks 1-4
+// 0000 0000 1111 1111 0000 0000 0000 0000 : file selector 0 - 255
+// 0000 0000 0000 0000 1111 1111 1111 1111 : block# 0 - 65,535 (2^16)
+class NET_EXPORT_PRIVATE Addr {
+ public:
+ Addr() : value_(0) {}
+ explicit Addr(CacheAddr address) : value_(address) {}
+ Addr(FileType file_type, int max_blocks, int block_file, int index) {
+ value_ = ((file_type << kFileTypeOffset) & kFileTypeMask) |
+ (((max_blocks - 1) << kNumBlocksOffset) & kNumBlocksMask) |
+ ((block_file << kFileSelectorOffset) & kFileSelectorMask) |
+ (index & kStartBlockMask) | kInitializedMask;
+ }
+
+ CacheAddr value() const { return value_; }
+ void set_value(CacheAddr address) {
+ value_ = address;
+ }
+
+ bool is_initialized() const {
+ return (value_ & kInitializedMask) != 0;
+ }
+
+ bool is_separate_file() const {
+ return (value_ & kFileTypeMask) == 0;
+ }
+
+ bool is_block_file() const {
+ return !is_separate_file();
+ }
+
+ FileType file_type() const {
+ return static_cast<FileType>((value_ & kFileTypeMask) >> kFileTypeOffset);
+ }
+
+ int FileNumber() const {
+ if (is_separate_file())
+ return value_ & kFileNameMask;
+ else
+ return ((value_ & kFileSelectorMask) >> kFileSelectorOffset);
+ }
+
+ int start_block() const;
+ int num_blocks() const;
+ bool SetFileNumber(int file_number);
+ int BlockSize() const {
+ return BlockSizeForFileType(file_type());
+ }
+
+ bool operator==(Addr other) const {
+ return value_ == other.value_;
+ }
+
+ bool operator!=(Addr other) const {
+ return value_ != other.value_;
+ }
+
+ static Addr FromEntryAddress(uint32 value) {
+ return Addr(kInitializedMask + (BLOCK_ENTRIES << kFileTypeOffset) + value);
+ }
+
+ static Addr FromEvictedAddress(uint32 value) {
+ return Addr(kInitializedMask + (BLOCK_EVICTED << kFileTypeOffset) + value);
+ }
+
+ static int BlockSizeForFileType(FileType file_type) {
+ switch (file_type) {
+ case RANKINGS:
+ return 36;
+ case BLOCK_256:
+ return 256;
+ case BLOCK_1K:
+ return 1024;
+ case BLOCK_4K:
+ return 4096;
+ case BLOCK_FILES:
+ return 8;
+ case BLOCK_ENTRIES:
+ return 104;
+ case BLOCK_EVICTED:
+ return 48;
+ default:
+ return 0;
+ }
+ }
+
+ static FileType RequiredFileType(int size) {
+ if (size < 1024)
+ return BLOCK_256;
+ else if (size < 4096)
+ return BLOCK_1K;
+ else if (size <= 4096 * 4)
+ return BLOCK_4K;
+ else
+ return EXTERNAL;
+ }
+
+ static int RequiredBlocks(int size, FileType file_type) {
+ int block_size = BlockSizeForFileType(file_type);
+ return (size + block_size - 1) / block_size;
+ }
+
+ // Returns true if this address looks like a valid one.
+ bool SanityCheckV2() const;
+ bool SanityCheckV3() const;
+ bool SanityCheckForEntryV2() const;
+ bool SanityCheckForEntryV3() const;
+ bool SanityCheckForRankings() const;
+
+ private:
+ uint32 reserved_bits() const {
+ return value_ & kReservedBitsMask;
+ }
+
+ static const uint32 kInitializedMask = 0x80000000;
+ static const uint32 kFileTypeMask = 0x70000000;
+ static const uint32 kFileTypeOffset = 28;
+ static const uint32 kReservedBitsMask = 0x0c000000;
+ static const uint32 kNumBlocksMask = 0x03000000;
+ static const uint32 kNumBlocksOffset = 24;
+ static const uint32 kFileSelectorMask = 0x00ff0000;
+ static const uint32 kFileSelectorOffset = 16;
+ static const uint32 kStartBlockMask = 0x0000FFFF;
+ static const uint32 kFileNameMask = 0x0FFFFFFF;
+
+ CacheAddr value_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ADDR_H_
diff --git a/chromium/net/disk_cache/addr_unittest.cc b/chromium/net/disk_cache/addr_unittest.cc
new file mode 100644
index 00000000000..a6da03cf777
--- /dev/null
+++ b/chromium/net/disk_cache/addr_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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/disk_cache/addr.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace disk_cache {
+
+TEST_F(DiskCacheTest, CacheAddr_Size) {
+ Addr addr1(0);
+ EXPECT_FALSE(addr1.is_initialized());
+
+ // The object should not be more expensive than the actual address.
+ EXPECT_EQ(sizeof(uint32), sizeof(addr1));
+}
+
+TEST_F(DiskCacheTest, CacheAddr_ValidValues) {
+ Addr addr2(BLOCK_1K, 3, 5, 25);
+ EXPECT_EQ(BLOCK_1K, addr2.file_type());
+ EXPECT_EQ(3, addr2.num_blocks());
+ EXPECT_EQ(5, addr2.FileNumber());
+ EXPECT_EQ(25, addr2.start_block());
+ EXPECT_EQ(1024, addr2.BlockSize());
+}
+
+TEST_F(DiskCacheTest, CacheAddr_InvalidValues) {
+ Addr addr3(BLOCK_4K, 0x44, 0x41508, 0x952536);
+ EXPECT_EQ(BLOCK_4K, addr3.file_type());
+ EXPECT_EQ(4, addr3.num_blocks());
+ EXPECT_EQ(8, addr3.FileNumber());
+ EXPECT_EQ(0x2536, addr3.start_block());
+ EXPECT_EQ(4096, addr3.BlockSize());
+}
+
+TEST_F(DiskCacheTest, CacheAddr_SanityCheck) {
+ // First a few valid values.
+ EXPECT_TRUE(Addr(0).SanityCheckV2());
+ EXPECT_TRUE(Addr(0x80001000).SanityCheckV2());
+ EXPECT_TRUE(Addr(0xC3FFFFFF).SanityCheckV2());
+ EXPECT_TRUE(Addr(0xC0FFFFFF).SanityCheckV2());
+ EXPECT_TRUE(Addr(0xD0001000).SanityCheckV3());
+
+ // Not initialized.
+ EXPECT_FALSE(Addr(0x20).SanityCheckV2());
+ EXPECT_FALSE(Addr(0x10001000).SanityCheckV2());
+
+ // Invalid file type.
+ EXPECT_FALSE(Addr(0xD0001000).SanityCheckV2());
+ EXPECT_FALSE(Addr(0xE0001000).SanityCheckV3());
+ EXPECT_FALSE(Addr(0xF0000000).SanityCheckV2());
+
+ // Reserved bits.
+ EXPECT_FALSE(Addr(0x14000000).SanityCheckV2());
+ EXPECT_FALSE(Addr(0x18000000).SanityCheckV2());
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/backend_impl.cc b/chromium/net/disk_cache/backend_impl.cc
new file mode 100644
index 00000000000..8d7fd461102
--- /dev/null
+++ b/chromium/net/disk_cache/backend_impl.cc
@@ -0,0 +1,2120 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/backend_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/hash.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/file.h"
+
+// This has to be defined before including histogram_macros.h from this file.
+#define NET_DISK_CACHE_BACKEND_IMPL_CC_
+#include "net/disk_cache/histogram_macros.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+const char* kIndexName = "index";
+
+// Seems like ~240 MB correspond to less than 50k entries for 99% of the people.
+// Note that the actual target is to keep the index table load factor under 55%
+// for most users.
+const int k64kEntriesStore = 240 * 1000 * 1000;
+const int kBaseTableLen = 64 * 1024;
+const int kDefaultCacheSize = 80 * 1024 * 1024;
+
+// Avoid trimming the cache for the first 5 minutes (10 timer ticks).
+const int kTrimDelay = 10;
+
+int DesiredIndexTableLen(int32 storage_size) {
+ if (storage_size <= k64kEntriesStore)
+ return kBaseTableLen;
+ if (storage_size <= k64kEntriesStore * 2)
+ return kBaseTableLen * 2;
+ if (storage_size <= k64kEntriesStore * 4)
+ return kBaseTableLen * 4;
+ if (storage_size <= k64kEntriesStore * 8)
+ return kBaseTableLen * 8;
+
+ // The biggest storage_size for int32 requires a 4 MB table.
+ return kBaseTableLen * 16;
+}
+
+int MaxStorageSizeForTable(int table_len) {
+ return table_len * (k64kEntriesStore / kBaseTableLen);
+}
+
+size_t GetIndexSize(int table_len) {
+ size_t table_size = sizeof(disk_cache::CacheAddr) * table_len;
+ return sizeof(disk_cache::IndexHeader) + table_size;
+}
+
+// ------------------------------------------------------------------------
+
+// Sets group for the current experiment. Returns false if the files should be
+// discarded.
+bool InitExperiment(disk_cache::IndexHeader* header, bool cache_created) {
+ if (header->experiment == disk_cache::EXPERIMENT_OLD_FILE1 ||
+ header->experiment == disk_cache::EXPERIMENT_OLD_FILE2) {
+ // Discard current cache.
+ return false;
+ }
+
+ if (base::FieldTrialList::FindFullName("SimpleCacheTrial") ==
+ "ExperimentControl") {
+ if (cache_created) {
+ header->experiment = disk_cache::EXPERIMENT_SIMPLE_CONTROL;
+ return true;
+ }
+ return header->experiment == disk_cache::EXPERIMENT_SIMPLE_CONTROL;
+ }
+
+ header->experiment = disk_cache::NO_EXPERIMENT;
+ return true;
+}
+
+// A callback to perform final cleanup on the background thread.
+void FinalCleanupCallback(disk_cache::BackendImpl* backend) {
+ backend->CleanupCache();
+}
+
+} // namespace
+
+// ------------------------------------------------------------------------
+
+namespace disk_cache {
+
+// Returns the preferred maximum number of bytes for the cache given the
+// number of available bytes.
+int PreferedCacheSize(int64 available) {
+ // Return 80% of the available space if there is not enough space to use
+ // kDefaultCacheSize.
+ if (available < kDefaultCacheSize * 10 / 8)
+ return static_cast<int32>(available * 8 / 10);
+
+ // Return kDefaultCacheSize if it uses 80% to 10% of the available space.
+ if (available < kDefaultCacheSize * 10)
+ return kDefaultCacheSize;
+
+ // Return 10% of the available space if the target size
+ // (2.5 * kDefaultCacheSize) is more than 10%.
+ if (available < static_cast<int64>(kDefaultCacheSize) * 25)
+ return static_cast<int32>(available / 10);
+
+ // Return the target size (2.5 * kDefaultCacheSize) if it uses 10% to 1%
+ // of the available space.
+ if (available < static_cast<int64>(kDefaultCacheSize) * 250)
+ return kDefaultCacheSize * 5 / 2;
+
+ // Return 1% of the available space if it does not exceed kint32max.
+ if (available < static_cast<int64>(kint32max) * 100)
+ return static_cast<int32>(available / 100);
+
+ return kint32max;
+}
+
+// ------------------------------------------------------------------------
+
+BackendImpl::BackendImpl(const base::FilePath& path,
+ base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log)
+ : background_queue_(this, cache_thread),
+ path_(path),
+ block_files_(path),
+ mask_(0),
+ max_size_(0),
+ up_ticks_(0),
+ cache_type_(net::DISK_CACHE),
+ uma_report_(0),
+ user_flags_(0),
+ init_(false),
+ restarted_(false),
+ unit_test_(false),
+ read_only_(false),
+ disabled_(false),
+ new_eviction_(false),
+ first_timer_(true),
+ user_load_(false),
+ net_log_(net_log),
+ done_(true, false),
+ ptr_factory_(this) {
+}
+
+BackendImpl::BackendImpl(const base::FilePath& path,
+ uint32 mask,
+ base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log)
+ : background_queue_(this, cache_thread),
+ path_(path),
+ block_files_(path),
+ mask_(mask),
+ max_size_(0),
+ up_ticks_(0),
+ cache_type_(net::DISK_CACHE),
+ uma_report_(0),
+ user_flags_(kMask),
+ init_(false),
+ restarted_(false),
+ unit_test_(false),
+ read_only_(false),
+ disabled_(false),
+ new_eviction_(false),
+ first_timer_(true),
+ user_load_(false),
+ net_log_(net_log),
+ done_(true, false),
+ ptr_factory_(this) {
+}
+
+BackendImpl::~BackendImpl() {
+ if (user_flags_ & kNoRandom) {
+ // This is a unit test, so we want to be strict about not leaking entries
+ // and completing all the work.
+ background_queue_.WaitForPendingIO();
+ } else {
+ // This is most likely not a test, so we want to do as little work as
+ // possible at this time, at the price of leaving dirty entries behind.
+ background_queue_.DropPendingIO();
+ }
+
+ if (background_queue_.BackgroundIsCurrentThread()) {
+ // Unit tests may use the same thread for everything.
+ CleanupCache();
+ } else {
+ background_queue_.background_thread()->PostTask(
+ FROM_HERE, base::Bind(&FinalCleanupCallback, base::Unretained(this)));
+ // http://crbug.com/74623
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ done_.Wait();
+ }
+}
+
+int BackendImpl::Init(const CompletionCallback& callback) {
+ background_queue_.Init(callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::SyncInit() {
+#if defined(NET_BUILD_STRESS_CACHE)
+ // Start evictions right away.
+ up_ticks_ = kTrimDelay * 2;
+#endif
+ DCHECK(!init_);
+ if (init_)
+ return net::ERR_FAILED;
+
+ bool create_files = false;
+ if (!InitBackingStore(&create_files)) {
+ ReportError(ERR_STORAGE_ERROR);
+ return net::ERR_FAILED;
+ }
+
+ num_refs_ = num_pending_io_ = max_refs_ = 0;
+ entry_count_ = byte_count_ = 0;
+
+ if (!restarted_) {
+ buffer_bytes_ = 0;
+ trace_object_ = TraceObject::GetTraceObject();
+ // Create a recurrent timer of 30 secs.
+ int timer_delay = unit_test_ ? 1000 : 30000;
+ timer_.reset(new base::RepeatingTimer<BackendImpl>());
+ timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(timer_delay), this,
+ &BackendImpl::OnStatsTimer);
+ }
+
+ init_ = true;
+ Trace("Init");
+
+ if (data_->header.experiment != NO_EXPERIMENT &&
+ cache_type_ != net::DISK_CACHE) {
+ // No experiment for other caches.
+ return net::ERR_FAILED;
+ }
+
+ if (!(user_flags_ & kNoRandom)) {
+ // The unit test controls directly what to test.
+ new_eviction_ = (cache_type_ == net::DISK_CACHE);
+ }
+
+ if (!CheckIndex()) {
+ ReportError(ERR_INIT_FAILED);
+ return net::ERR_FAILED;
+ }
+
+ if (!restarted_ && (create_files || !data_->header.num_entries))
+ ReportError(ERR_CACHE_CREATED);
+
+ if (!(user_flags_ & kNoRandom) && cache_type_ == net::DISK_CACHE &&
+ !InitExperiment(&data_->header, create_files)) {
+ return net::ERR_FAILED;
+ }
+
+ // We don't care if the value overflows. The only thing we care about is that
+ // the id cannot be zero, because that value is used as "not dirty".
+ // Increasing the value once per second gives us many years before we start
+ // having collisions.
+ data_->header.this_id++;
+ if (!data_->header.this_id)
+ data_->header.this_id++;
+
+ bool previous_crash = (data_->header.crash != 0);
+ data_->header.crash = 1;
+
+ if (!block_files_.Init(create_files))
+ return net::ERR_FAILED;
+
+ // We want to minimize the changes to cache for an AppCache.
+ if (cache_type() == net::APP_CACHE) {
+ DCHECK(!new_eviction_);
+ read_only_ = true;
+ } else if (cache_type() == net::SHADER_CACHE) {
+ DCHECK(!new_eviction_);
+ }
+
+ eviction_.Init(this);
+
+ // stats_ and rankings_ may end up calling back to us so we better be enabled.
+ disabled_ = false;
+ if (!InitStats())
+ return net::ERR_FAILED;
+
+ disabled_ = !rankings_.Init(this, new_eviction_);
+
+#if defined(STRESS_CACHE_EXTENDED_VALIDATION)
+ trace_object_->EnableTracing(false);
+ int sc = SelfCheck();
+ if (sc < 0 && sc != ERR_NUM_ENTRIES_MISMATCH)
+ NOTREACHED();
+ trace_object_->EnableTracing(true);
+#endif
+
+ if (previous_crash) {
+ ReportError(ERR_PREVIOUS_CRASH);
+ } else if (!restarted_) {
+ ReportError(ERR_NO_ERROR);
+ }
+
+ FlushIndex();
+
+ return disabled_ ? net::ERR_FAILED : net::OK;
+}
+
+void BackendImpl::CleanupCache() {
+ Trace("Backend Cleanup");
+ eviction_.Stop();
+ timer_.reset();
+
+ if (init_) {
+ StoreStats();
+ if (data_)
+ data_->header.crash = 0;
+
+ if (user_flags_ & kNoRandom) {
+ // This is a net_unittest, verify that we are not 'leaking' entries.
+ File::WaitForPendingIO(&num_pending_io_);
+ DCHECK(!num_refs_);
+ } else {
+ File::DropPendingIO();
+ }
+ }
+ block_files_.CloseFiles();
+ FlushIndex();
+ index_ = NULL;
+ ptr_factory_.InvalidateWeakPtrs();
+ done_.Signal();
+}
+
+// ------------------------------------------------------------------------
+
+int BackendImpl::OpenPrevEntry(void** iter, Entry** prev_entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.OpenPrevEntry(iter, prev_entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::SyncOpenEntry(const std::string& key, Entry** entry) {
+ DCHECK(entry);
+ *entry = OpenEntryImpl(key);
+ return (*entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncCreateEntry(const std::string& key, Entry** entry) {
+ DCHECK(entry);
+ *entry = CreateEntryImpl(key);
+ return (*entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncDoomEntry(const std::string& key) {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* entry = OpenEntryImpl(key);
+ if (!entry)
+ return net::ERR_FAILED;
+
+ entry->DoomImpl();
+ entry->Release();
+ return net::OK;
+}
+
+int BackendImpl::SyncDoomAllEntries() {
+ // This is not really an error, but it is an interesting condition.
+ ReportError(ERR_CACHE_DOOMED);
+ stats_.OnEvent(Stats::DOOM_CACHE);
+ if (!num_refs_) {
+ RestartCache(false);
+ return disabled_ ? net::ERR_FAILED : net::OK;
+ } else {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ eviction_.TrimCache(true);
+ return net::OK;
+ }
+}
+
+int BackendImpl::SyncDoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ DCHECK_NE(net::APP_CACHE, cache_type_);
+ if (end_time.is_null())
+ return SyncDoomEntriesSince(initial_time);
+
+ DCHECK(end_time >= initial_time);
+
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* node;
+ void* iter = NULL;
+ EntryImpl* next = OpenNextEntryImpl(&iter);
+ if (!next)
+ return net::OK;
+
+ while (next) {
+ node = next;
+ next = OpenNextEntryImpl(&iter);
+
+ if (node->GetLastUsed() >= initial_time &&
+ node->GetLastUsed() < end_time) {
+ node->DoomImpl();
+ } else if (node->GetLastUsed() < initial_time) {
+ if (next)
+ next->Release();
+ next = NULL;
+ SyncEndEnumeration(iter);
+ }
+
+ node->Release();
+ }
+
+ return net::OK;
+}
+
+// We use OpenNextEntryImpl to retrieve elements from the cache, until we get
+// entries that are too old.
+int BackendImpl::SyncDoomEntriesSince(const base::Time initial_time) {
+ DCHECK_NE(net::APP_CACHE, cache_type_);
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ stats_.OnEvent(Stats::DOOM_RECENT);
+ for (;;) {
+ void* iter = NULL;
+ EntryImpl* entry = OpenNextEntryImpl(&iter);
+ if (!entry)
+ return net::OK;
+
+ if (initial_time > entry->GetLastUsed()) {
+ entry->Release();
+ SyncEndEnumeration(iter);
+ return net::OK;
+ }
+
+ entry->DoomImpl();
+ entry->Release();
+ SyncEndEnumeration(iter); // Dooming the entry invalidates the iterator.
+ }
+}
+
+int BackendImpl::SyncOpenNextEntry(void** iter, Entry** next_entry) {
+ *next_entry = OpenNextEntryImpl(iter);
+ return (*next_entry) ? net::OK : net::ERR_FAILED;
+}
+
+int BackendImpl::SyncOpenPrevEntry(void** iter, Entry** prev_entry) {
+ *prev_entry = OpenPrevEntryImpl(iter);
+ return (*prev_entry) ? net::OK : net::ERR_FAILED;
+}
+
+void BackendImpl::SyncEndEnumeration(void* iter) {
+ scoped_ptr<Rankings::Iterator> iterator(
+ reinterpret_cast<Rankings::Iterator*>(iter));
+}
+
+void BackendImpl::SyncOnExternalCacheHit(const std::string& key) {
+ if (disabled_)
+ return;
+
+ uint32 hash = base::Hash(key);
+ bool error;
+ EntryImpl* cache_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (cache_entry) {
+ if (ENTRY_NORMAL == cache_entry->entry()->Data()->state) {
+ UpdateRank(cache_entry, cache_type() == net::SHADER_CACHE);
+ }
+ cache_entry->Release();
+ }
+}
+
+EntryImpl* BackendImpl::OpenEntryImpl(const std::string& key) {
+ if (disabled_)
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ uint32 hash = base::Hash(key);
+ Trace("Open hash 0x%x", hash);
+
+ bool error;
+ EntryImpl* cache_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (cache_entry && ENTRY_NORMAL != cache_entry->entry()->Data()->state) {
+ // The entry was already evicted.
+ cache_entry->Release();
+ cache_entry = NULL;
+ }
+
+ int current_size = data_->header.num_bytes / (1024 * 1024);
+ int64 total_hours = stats_.GetCounter(Stats::TIMER) / 120;
+ int64 no_use_hours = stats_.GetCounter(Stats::LAST_REPORT_TIMER) / 120;
+ int64 use_hours = total_hours - no_use_hours;
+
+ if (!cache_entry) {
+ CACHE_UMA(AGE_MS, "OpenTime.Miss", 0, start);
+ CACHE_UMA(COUNTS_10000, "AllOpenBySize.Miss", 0, current_size);
+ CACHE_UMA(HOURS, "AllOpenByTotalHours.Miss", 0, total_hours);
+ CACHE_UMA(HOURS, "AllOpenByUseHours.Miss", 0, use_hours);
+ stats_.OnEvent(Stats::OPEN_MISS);
+ return NULL;
+ }
+
+ eviction_.OnOpenEntry(cache_entry);
+ entry_count_++;
+
+ Trace("Open hash 0x%x end: 0x%x", hash,
+ cache_entry->entry()->address().value());
+ CACHE_UMA(AGE_MS, "OpenTime", 0, start);
+ CACHE_UMA(COUNTS_10000, "AllOpenBySize.Hit", 0, current_size);
+ CACHE_UMA(HOURS, "AllOpenByTotalHours.Hit", 0, total_hours);
+ CACHE_UMA(HOURS, "AllOpenByUseHours.Hit", 0, use_hours);
+ stats_.OnEvent(Stats::OPEN_HIT);
+ SIMPLE_STATS_COUNTER("disk_cache.hit");
+ return cache_entry;
+}
+
+EntryImpl* BackendImpl::CreateEntryImpl(const std::string& key) {
+ if (disabled_ || key.empty())
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ uint32 hash = base::Hash(key);
+ Trace("Create hash 0x%x", hash);
+
+ scoped_refptr<EntryImpl> parent;
+ Addr entry_address(data_->table[hash & mask_]);
+ if (entry_address.is_initialized()) {
+ // We have an entry already. It could be the one we are looking for, or just
+ // a hash conflict.
+ bool error;
+ EntryImpl* old_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (old_entry)
+ return ResurrectEntry(old_entry);
+
+ EntryImpl* parent_entry = MatchEntry(key, hash, true, Addr(), &error);
+ DCHECK(!error);
+ if (parent_entry) {
+ parent.swap(&parent_entry);
+ } else if (data_->table[hash & mask_]) {
+ // We should have corrected the problem.
+ NOTREACHED();
+ return NULL;
+ }
+ }
+
+ // The general flow is to allocate disk space and initialize the entry data,
+ // followed by saving that to disk, then linking the entry though the index
+ // and finally through the lists. If there is a crash in this process, we may
+ // end up with:
+ // a. Used, unreferenced empty blocks on disk (basically just garbage).
+ // b. Used, unreferenced but meaningful data on disk (more garbage).
+ // c. A fully formed entry, reachable only through the index.
+ // d. A fully formed entry, also reachable through the lists, but still dirty.
+ //
+ // Anything after (b) can be automatically cleaned up. We may consider saving
+ // the current operation (as we do while manipulating the lists) so that we
+ // can detect and cleanup (a) and (b).
+
+ int num_blocks = EntryImpl::NumBlocksForEntry(key.size());
+ if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ Addr node_address(0);
+ if (!block_files_.CreateBlock(RANKINGS, 1, &node_address)) {
+ block_files_.DeleteBlock(entry_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(
+ new EntryImpl(this, entry_address, false));
+ IncreaseNumRefs();
+
+ if (!cache_entry->CreateEntry(node_address, key, hash)) {
+ block_files_.DeleteBlock(entry_address, false);
+ block_files_.DeleteBlock(node_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ cache_entry->BeginLogging(net_log_, true);
+
+ // We are not failing the operation; let's add this to the map.
+ open_entries_[entry_address.value()] = cache_entry.get();
+
+ // Save the entry.
+ cache_entry->entry()->Store();
+ cache_entry->rankings()->Store();
+ IncreaseNumEntries();
+ entry_count_++;
+
+ // Link this entry through the index.
+ if (parent.get()) {
+ parent->SetNextAddress(entry_address);
+ } else {
+ data_->table[hash & mask_] = entry_address.value();
+ }
+
+ // Link this entry through the lists.
+ eviction_.OnCreateEntry(cache_entry.get());
+
+ CACHE_UMA(AGE_MS, "CreateTime", 0, start);
+ stats_.OnEvent(Stats::CREATE_HIT);
+ SIMPLE_STATS_COUNTER("disk_cache.miss");
+ Trace("create entry hit ");
+ FlushIndex();
+ cache_entry->AddRef();
+ return cache_entry.get();
+}
+
+EntryImpl* BackendImpl::OpenNextEntryImpl(void** iter) {
+ return OpenFollowingEntry(true, iter);
+}
+
+EntryImpl* BackendImpl::OpenPrevEntryImpl(void** iter) {
+ return OpenFollowingEntry(false, iter);
+}
+
+bool BackendImpl::SetMaxSize(int max_bytes) {
+ COMPILE_ASSERT(sizeof(max_bytes) == sizeof(max_size_), unsupported_int_model);
+ if (max_bytes < 0)
+ return false;
+
+ // Zero size means use the default.
+ if (!max_bytes)
+ return true;
+
+ // Avoid a DCHECK later on.
+ if (max_bytes >= kint32max - kint32max / 10)
+ max_bytes = kint32max - kint32max / 10 - 1;
+
+ user_flags_ |= kMaxSize;
+ max_size_ = max_bytes;
+ return true;
+}
+
+void BackendImpl::SetType(net::CacheType type) {
+ DCHECK_NE(net::MEMORY_CACHE, type);
+ cache_type_ = type;
+}
+
+base::FilePath BackendImpl::GetFileName(Addr address) const {
+ if (!address.is_separate_file() || !address.is_initialized()) {
+ NOTREACHED();
+ return base::FilePath();
+ }
+
+ std::string tmp = base::StringPrintf("f_%06x", address.FileNumber());
+ return path_.AppendASCII(tmp);
+}
+
+MappedFile* BackendImpl::File(Addr address) {
+ if (disabled_)
+ return NULL;
+ return block_files_.GetFile(address);
+}
+
+base::WeakPtr<InFlightBackendIO> BackendImpl::GetBackgroundQueue() {
+ return background_queue_.GetWeakPtr();
+}
+
+bool BackendImpl::CreateExternalFile(Addr* address) {
+ int file_number = data_->header.last_file + 1;
+ Addr file_address(0);
+ bool success = false;
+ for (int i = 0; i < 0x0fffffff; i++, file_number++) {
+ if (!file_address.SetFileNumber(file_number)) {
+ file_number = 1;
+ continue;
+ }
+ base::FilePath name = GetFileName(file_address);
+ int flags = base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE;
+ base::PlatformFileError error;
+ scoped_refptr<disk_cache::File> file(new disk_cache::File(
+ base::CreatePlatformFile(name, flags, NULL, &error)));
+ if (!file->IsValid()) {
+ if (error != base::PLATFORM_FILE_ERROR_EXISTS) {
+ LOG(ERROR) << "Unable to create file: " << error;
+ return false;
+ }
+ continue;
+ }
+
+ success = true;
+ break;
+ }
+
+ DCHECK(success);
+ if (!success)
+ return false;
+
+ data_->header.last_file = file_number;
+ address->set_value(file_address.value());
+ return true;
+}
+
+bool BackendImpl::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ return block_files_.CreateBlock(block_type, block_count, block_address);
+}
+
+void BackendImpl::DeleteBlock(Addr block_address, bool deep) {
+ block_files_.DeleteBlock(block_address, deep);
+}
+
+LruData* BackendImpl::GetLruData() {
+ return &data_->header.lru;
+}
+
+void BackendImpl::UpdateRank(EntryImpl* entry, bool modified) {
+ if (read_only_ || (!modified && cache_type() == net::SHADER_CACHE))
+ return;
+ eviction_.UpdateRank(entry, modified);
+}
+
+void BackendImpl::RecoveredEntry(CacheRankingsBlock* rankings) {
+ Addr address(rankings->Data()->contents);
+ EntryImpl* cache_entry = NULL;
+ if (NewEntry(address, &cache_entry)) {
+ STRESS_NOTREACHED();
+ return;
+ }
+
+ uint32 hash = cache_entry->GetHash();
+ cache_entry->Release();
+
+ // Anything on the table means that this entry is there.
+ if (data_->table[hash & mask_])
+ return;
+
+ data_->table[hash & mask_] = address.value();
+ FlushIndex();
+}
+
+void BackendImpl::InternalDoomEntry(EntryImpl* entry) {
+ uint32 hash = entry->GetHash();
+ std::string key = entry->GetKey();
+ Addr entry_addr = entry->entry()->address();
+ bool error;
+ EntryImpl* parent_entry = MatchEntry(key, hash, true, entry_addr, &error);
+ CacheAddr child(entry->GetNextAddress());
+
+ Trace("Doom entry 0x%p", entry);
+
+ if (!entry->doomed()) {
+ // We may have doomed this entry from within MatchEntry.
+ eviction_.OnDoomEntry(entry);
+ entry->InternalDoom();
+ if (!new_eviction_) {
+ DecreaseNumEntries();
+ }
+ stats_.OnEvent(Stats::DOOM_ENTRY);
+ }
+
+ if (parent_entry) {
+ parent_entry->SetNextAddress(Addr(child));
+ parent_entry->Release();
+ } else if (!error) {
+ data_->table[hash & mask_] = child;
+ }
+
+ FlushIndex();
+}
+
+#if defined(NET_BUILD_STRESS_CACHE)
+
+CacheAddr BackendImpl::GetNextAddr(Addr address) {
+ EntriesMap::iterator it = open_entries_.find(address.value());
+ if (it != open_entries_.end()) {
+ EntryImpl* this_entry = it->second;
+ return this_entry->GetNextAddress();
+ }
+ DCHECK(block_files_.IsValid(address));
+ DCHECK(!address.is_separate_file() && address.file_type() == BLOCK_256);
+
+ CacheEntryBlock entry(File(address), address);
+ CHECK(entry.Load());
+ return entry.Data()->next;
+}
+
+void BackendImpl::NotLinked(EntryImpl* entry) {
+ Addr entry_addr = entry->entry()->address();
+ uint32 i = entry->GetHash() & mask_;
+ Addr address(data_->table[i]);
+ if (!address.is_initialized())
+ return;
+
+ for (;;) {
+ DCHECK(entry_addr.value() != address.value());
+ address.set_value(GetNextAddr(address));
+ if (!address.is_initialized())
+ break;
+ }
+}
+#endif // NET_BUILD_STRESS_CACHE
+
+// An entry may be linked on the DELETED list for a while after being doomed.
+// This function is called when we want to remove it.
+void BackendImpl::RemoveEntry(EntryImpl* entry) {
+#if defined(NET_BUILD_STRESS_CACHE)
+ NotLinked(entry);
+#endif
+ if (!new_eviction_)
+ return;
+
+ DCHECK_NE(ENTRY_NORMAL, entry->entry()->Data()->state);
+
+ Trace("Remove entry 0x%p", entry);
+ eviction_.OnDestroyEntry(entry);
+ DecreaseNumEntries();
+}
+
+void BackendImpl::OnEntryDestroyBegin(Addr address) {
+ EntriesMap::iterator it = open_entries_.find(address.value());
+ if (it != open_entries_.end())
+ open_entries_.erase(it);
+}
+
+void BackendImpl::OnEntryDestroyEnd() {
+ DecreaseNumRefs();
+ if (data_->header.num_bytes > max_size_ && !read_only_ &&
+ (up_ticks_ > kTrimDelay || user_flags_ & kNoRandom))
+ eviction_.TrimCache(false);
+}
+
+EntryImpl* BackendImpl::GetOpenEntry(CacheRankingsBlock* rankings) const {
+ DCHECK(rankings->HasData());
+ EntriesMap::const_iterator it =
+ open_entries_.find(rankings->Data()->contents);
+ if (it != open_entries_.end()) {
+ // We have this entry in memory.
+ return it->second;
+ }
+
+ return NULL;
+}
+
+int32 BackendImpl::GetCurrentEntryId() const {
+ return data_->header.this_id;
+}
+
+int BackendImpl::MaxFileSize() const {
+ return max_size_ / 8;
+}
+
+void BackendImpl::ModifyStorageSize(int32 old_size, int32 new_size) {
+ if (disabled_ || old_size == new_size)
+ return;
+ if (old_size > new_size)
+ SubstractStorageSize(old_size - new_size);
+ else
+ AddStorageSize(new_size - old_size);
+
+ FlushIndex();
+
+ // Update the usage statistics.
+ stats_.ModifyStorageStats(old_size, new_size);
+}
+
+void BackendImpl::TooMuchStorageRequested(int32 size) {
+ stats_.ModifyStorageStats(0, size);
+}
+
+bool BackendImpl::IsAllocAllowed(int current_size, int new_size) {
+ DCHECK_GT(new_size, current_size);
+ if (user_flags_ & kNoBuffering)
+ return false;
+
+ int to_add = new_size - current_size;
+ if (buffer_bytes_ + to_add > MaxBuffersSize())
+ return false;
+
+ buffer_bytes_ += to_add;
+ CACHE_UMA(COUNTS_50000, "BufferBytes", 0, buffer_bytes_ / 1024);
+ return true;
+}
+
+void BackendImpl::BufferDeleted(int size) {
+ buffer_bytes_ -= size;
+ DCHECK_GE(size, 0);
+}
+
+bool BackendImpl::IsLoaded() const {
+ CACHE_UMA(COUNTS, "PendingIO", 0, num_pending_io_);
+ if (user_flags_ & kNoLoadProtection)
+ return false;
+
+ return (num_pending_io_ > 5 || user_load_);
+}
+
+std::string BackendImpl::HistogramName(const char* name, int experiment) const {
+ if (!experiment)
+ return base::StringPrintf("DiskCache.%d.%s", cache_type_, name);
+ return base::StringPrintf("DiskCache.%d.%s_%d", cache_type_,
+ name, experiment);
+}
+
+base::WeakPtr<BackendImpl> BackendImpl::GetWeakPtr() {
+ return ptr_factory_.GetWeakPtr();
+}
+
+// We want to remove biases from some histograms so we only send data once per
+// week.
+bool BackendImpl::ShouldReportAgain() {
+ if (uma_report_)
+ return uma_report_ == 2;
+
+ uma_report_++;
+ int64 last_report = stats_.GetCounter(Stats::LAST_REPORT);
+ Time last_time = Time::FromInternalValue(last_report);
+ if (!last_report || (Time::Now() - last_time).InDays() >= 7) {
+ stats_.SetCounter(Stats::LAST_REPORT, Time::Now().ToInternalValue());
+ uma_report_++;
+ return true;
+ }
+ return false;
+}
+
+void BackendImpl::FirstEviction() {
+ DCHECK(data_->header.create_time);
+ if (!GetEntryCount())
+ return; // This is just for unit tests.
+
+ Time create_time = Time::FromInternalValue(data_->header.create_time);
+ CACHE_UMA(AGE, "FillupAge", 0, create_time);
+
+ int64 use_time = stats_.GetCounter(Stats::TIMER);
+ CACHE_UMA(HOURS, "FillupTime", 0, static_cast<int>(use_time / 120));
+ CACHE_UMA(PERCENTAGE, "FirstHitRatio", 0, stats_.GetHitRatio());
+
+ if (!use_time)
+ use_time = 1;
+ CACHE_UMA(COUNTS_10000, "FirstEntryAccessRate", 0,
+ static_cast<int>(data_->header.num_entries / use_time));
+ CACHE_UMA(COUNTS, "FirstByteIORate", 0,
+ static_cast<int>((data_->header.num_bytes / 1024) / use_time));
+
+ int avg_size = data_->header.num_bytes / GetEntryCount();
+ CACHE_UMA(COUNTS, "FirstEntrySize", 0, avg_size);
+
+ int large_entries_bytes = stats_.GetLargeEntriesSize();
+ int large_ratio = large_entries_bytes * 100 / data_->header.num_bytes;
+ CACHE_UMA(PERCENTAGE, "FirstLargeEntriesRatio", 0, large_ratio);
+
+ if (new_eviction_) {
+ CACHE_UMA(PERCENTAGE, "FirstResurrectRatio", 0, stats_.GetResurrectRatio());
+ CACHE_UMA(PERCENTAGE, "FirstNoUseRatio", 0,
+ data_->header.lru.sizes[0] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "FirstLowUseRatio", 0,
+ data_->header.lru.sizes[1] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "FirstHighUseRatio", 0,
+ data_->header.lru.sizes[2] * 100 / data_->header.num_entries);
+ }
+
+ stats_.ResetRatios();
+}
+
+void BackendImpl::CriticalError(int error) {
+ STRESS_NOTREACHED();
+ LOG(ERROR) << "Critical error found " << error;
+ if (disabled_)
+ return;
+
+ stats_.OnEvent(Stats::FATAL_ERROR);
+ LogStats();
+ ReportError(error);
+
+ // Setting the index table length to an invalid value will force re-creation
+ // of the cache files.
+ data_->header.table_len = 1;
+ disabled_ = true;
+
+ if (!num_refs_)
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&BackendImpl::RestartCache, GetWeakPtr(), true));
+}
+
+void BackendImpl::ReportError(int error) {
+ STRESS_DCHECK(!error || error == ERR_PREVIOUS_CRASH ||
+ error == ERR_CACHE_CREATED);
+
+ // We transmit positive numbers, instead of direct error codes.
+ DCHECK_LE(error, 0);
+ CACHE_UMA(CACHE_ERROR, "Error", 0, error * -1);
+}
+
+void BackendImpl::OnEvent(Stats::Counters an_event) {
+ stats_.OnEvent(an_event);
+}
+
+void BackendImpl::OnRead(int32 bytes) {
+ DCHECK_GE(bytes, 0);
+ byte_count_ += bytes;
+ if (byte_count_ < 0)
+ byte_count_ = kint32max;
+}
+
+void BackendImpl::OnWrite(int32 bytes) {
+ // We use the same implementation as OnRead... just log the number of bytes.
+ OnRead(bytes);
+}
+
+void BackendImpl::OnStatsTimer() {
+ stats_.OnEvent(Stats::TIMER);
+ int64 time = stats_.GetCounter(Stats::TIMER);
+ int64 current = stats_.GetCounter(Stats::OPEN_ENTRIES);
+
+ // OPEN_ENTRIES is a sampled average of the number of open entries, avoiding
+ // the bias towards 0.
+ if (num_refs_ && (current != num_refs_)) {
+ int64 diff = (num_refs_ - current) / 50;
+ if (!diff)
+ diff = num_refs_ > current ? 1 : -1;
+ current = current + diff;
+ stats_.SetCounter(Stats::OPEN_ENTRIES, current);
+ stats_.SetCounter(Stats::MAX_ENTRIES, max_refs_);
+ }
+
+ CACHE_UMA(COUNTS, "NumberOfReferences", 0, num_refs_);
+
+ CACHE_UMA(COUNTS_10000, "EntryAccessRate", 0, entry_count_);
+ CACHE_UMA(COUNTS, "ByteIORate", 0, byte_count_ / 1024);
+
+ // These values cover about 99.5% of the population (Oct 2011).
+ user_load_ = (entry_count_ > 300 || byte_count_ > 7 * 1024 * 1024);
+ entry_count_ = 0;
+ byte_count_ = 0;
+ up_ticks_++;
+
+ if (!data_)
+ first_timer_ = false;
+ if (first_timer_) {
+ first_timer_ = false;
+ if (ShouldReportAgain())
+ ReportStats();
+ }
+
+ // Save stats to disk at 5 min intervals.
+ if (time % 10 == 0)
+ StoreStats();
+}
+
+void BackendImpl::IncrementIoCount() {
+ num_pending_io_++;
+}
+
+void BackendImpl::DecrementIoCount() {
+ num_pending_io_--;
+}
+
+void BackendImpl::SetUnitTestMode() {
+ user_flags_ |= kUnitTestMode;
+ unit_test_ = true;
+}
+
+void BackendImpl::SetUpgradeMode() {
+ user_flags_ |= kUpgradeMode;
+ read_only_ = true;
+}
+
+void BackendImpl::SetNewEviction() {
+ user_flags_ |= kNewEviction;
+ new_eviction_ = true;
+}
+
+void BackendImpl::SetFlags(uint32 flags) {
+ user_flags_ |= flags;
+}
+
+void BackendImpl::ClearRefCountForTest() {
+ num_refs_ = 0;
+}
+
+int BackendImpl::FlushQueueForTest(const CompletionCallback& callback) {
+ background_queue_.FlushQueue(callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::RunTaskForTest(const base::Closure& task,
+ const CompletionCallback& callback) {
+ background_queue_.RunTask(task, callback);
+ return net::ERR_IO_PENDING;
+}
+
+void BackendImpl::TrimForTest(bool empty) {
+ eviction_.SetTestMode();
+ eviction_.TrimCache(empty);
+}
+
+void BackendImpl::TrimDeletedListForTest(bool empty) {
+ eviction_.SetTestMode();
+ eviction_.TrimDeletedList(empty);
+}
+
+int BackendImpl::SelfCheck() {
+ if (!init_) {
+ LOG(ERROR) << "Init failed";
+ return ERR_INIT_FAILED;
+ }
+
+ int num_entries = rankings_.SelfCheck();
+ if (num_entries < 0) {
+ LOG(ERROR) << "Invalid rankings list, error " << num_entries;
+#if !defined(NET_BUILD_STRESS_CACHE)
+ return num_entries;
+#endif
+ }
+
+ if (num_entries != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries mismatch";
+#if !defined(NET_BUILD_STRESS_CACHE)
+ return ERR_NUM_ENTRIES_MISMATCH;
+#endif
+ }
+
+ return CheckAllEntries();
+}
+
+void BackendImpl::FlushIndex() {
+ if (index_.get() && !disabled_)
+ index_->Flush();
+}
+
+// ------------------------------------------------------------------------
+
+net::CacheType BackendImpl::GetCacheType() const {
+ return cache_type_;
+}
+
+int32 BackendImpl::GetEntryCount() const {
+ if (!index_.get() || disabled_)
+ return 0;
+ // num_entries includes entries already evicted.
+ int32 not_deleted = data_->header.num_entries -
+ data_->header.lru.sizes[Rankings::DELETED];
+
+ if (not_deleted < 0) {
+ NOTREACHED();
+ not_deleted = 0;
+ }
+
+ return not_deleted;
+}
+
+int BackendImpl::OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.OpenEntry(key, entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.CreateEntry(key, entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::DoomEntry(const std::string& key,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.DoomEntry(key, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::DoomAllEntries(const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.DoomAllEntries(callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.DoomEntriesBetween(initial_time, end_time, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::DoomEntriesSince(const base::Time initial_time,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.DoomEntriesSince(initial_time, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int BackendImpl::OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.OpenNextEntry(iter, next_entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+void BackendImpl::EndEnumeration(void** iter) {
+ background_queue_.EndEnumeration(*iter);
+ *iter = NULL;
+}
+
+void BackendImpl::GetStats(StatsItems* stats) {
+ if (disabled_)
+ return;
+
+ std::pair<std::string, std::string> item;
+
+ item.first = "Entries";
+ item.second = base::StringPrintf("%d", data_->header.num_entries);
+ stats->push_back(item);
+
+ item.first = "Pending IO";
+ item.second = base::StringPrintf("%d", num_pending_io_);
+ stats->push_back(item);
+
+ item.first = "Max size";
+ item.second = base::StringPrintf("%d", max_size_);
+ stats->push_back(item);
+
+ item.first = "Current size";
+ item.second = base::StringPrintf("%d", data_->header.num_bytes);
+ stats->push_back(item);
+
+ item.first = "Cache type";
+ item.second = "Blockfile Cache";
+ stats->push_back(item);
+
+ stats_.GetItems(stats);
+}
+
+void BackendImpl::OnExternalCacheHit(const std::string& key) {
+ background_queue_.OnExternalCacheHit(key);
+}
+
+// ------------------------------------------------------------------------
+
+// We just created a new file so we're going to write the header and set the
+// file length to include the hash table (zero filled).
+bool BackendImpl::CreateBackingStore(disk_cache::File* file) {
+ AdjustMaxCacheSize(0);
+
+ IndexHeader header;
+ header.table_len = DesiredIndexTableLen(max_size_);
+
+ // We need file version 2.1 for the new eviction algorithm.
+ if (new_eviction_)
+ header.version = 0x20001;
+
+ header.create_time = Time::Now().ToInternalValue();
+
+ if (!file->Write(&header, sizeof(header), 0))
+ return false;
+
+ return file->SetLength(GetIndexSize(header.table_len));
+}
+
+bool BackendImpl::InitBackingStore(bool* file_created) {
+ if (!file_util::CreateDirectory(path_))
+ return false;
+
+ base::FilePath index_name = path_.AppendASCII(kIndexName);
+
+ int flags = base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE;
+ scoped_refptr<disk_cache::File> file(new disk_cache::File(
+ base::CreatePlatformFile(index_name, flags, file_created, NULL)));
+
+ if (!file->IsValid())
+ return false;
+
+ bool ret = true;
+ if (*file_created)
+ ret = CreateBackingStore(file.get());
+
+ file = NULL;
+ if (!ret)
+ return false;
+
+ index_ = new MappedFile();
+ data_ = reinterpret_cast<Index*>(index_->Init(index_name, 0));
+ if (!data_) {
+ LOG(ERROR) << "Unable to map Index file";
+ return false;
+ }
+
+ if (index_->GetLength() < sizeof(Index)) {
+ // We verify this again on CheckIndex() but it's easier to make sure now
+ // that the header is there.
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ return true;
+}
+
+// The maximum cache size will be either set explicitly by the caller, or
+// calculated by this code.
+void BackendImpl::AdjustMaxCacheSize(int table_len) {
+ if (max_size_)
+ return;
+
+ // If table_len is provided, the index file exists.
+ DCHECK(!table_len || data_->header.magic);
+
+ // The user is not setting the size, let's figure it out.
+ int64 available = base::SysInfo::AmountOfFreeDiskSpace(path_);
+ if (available < 0) {
+ max_size_ = kDefaultCacheSize;
+ return;
+ }
+
+ if (table_len)
+ available += data_->header.num_bytes;
+
+ max_size_ = PreferedCacheSize(available);
+
+ // Let's not use more than the default size while we tune-up the performance
+ // of bigger caches. TODO(rvargas): remove this limit.
+ if (max_size_ > kDefaultCacheSize * 4)
+ max_size_ = kDefaultCacheSize * 4;
+
+ if (!table_len)
+ return;
+
+ // If we already have a table, adjust the size to it.
+ int current_max_size = MaxStorageSizeForTable(table_len);
+ if (max_size_ > current_max_size)
+ max_size_= current_max_size;
+}
+
+bool BackendImpl::InitStats() {
+ Addr address(data_->header.stats);
+ int size = stats_.StorageSize();
+
+ if (!address.is_initialized()) {
+ FileType file_type = Addr::RequiredFileType(size);
+ DCHECK_NE(file_type, EXTERNAL);
+ int num_blocks = Addr::RequiredBlocks(size, file_type);
+
+ if (!CreateBlock(file_type, num_blocks, &address))
+ return false;
+
+ data_->header.stats = address.value();
+ return stats_.Init(NULL, 0, address);
+ }
+
+ if (!address.is_block_file()) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Load the required data.
+ size = address.num_blocks() * address.BlockSize();
+ MappedFile* file = File(address);
+ if (!file)
+ return false;
+
+ scoped_ptr<char[]> data(new char[size]);
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (!file->Read(data.get(), size, offset))
+ return false;
+
+ if (!stats_.Init(data.get(), size, address))
+ return false;
+ if (cache_type_ == net::DISK_CACHE && ShouldReportAgain())
+ stats_.InitSizeHistogram();
+ return true;
+}
+
+void BackendImpl::StoreStats() {
+ int size = stats_.StorageSize();
+ scoped_ptr<char[]> data(new char[size]);
+ Addr address;
+ size = stats_.SerializeStats(data.get(), size, &address);
+ DCHECK(size);
+ if (!address.is_initialized())
+ return;
+
+ MappedFile* file = File(address);
+ if (!file)
+ return;
+
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ file->Write(data.get(), size, offset); // ignore result.
+}
+
+void BackendImpl::RestartCache(bool failure) {
+ int64 errors = stats_.GetCounter(Stats::FATAL_ERROR);
+ int64 full_dooms = stats_.GetCounter(Stats::DOOM_CACHE);
+ int64 partial_dooms = stats_.GetCounter(Stats::DOOM_RECENT);
+ int64 last_report = stats_.GetCounter(Stats::LAST_REPORT);
+
+ PrepareForRestart();
+ if (failure) {
+ DCHECK(!num_refs_);
+ DCHECK(!open_entries_.size());
+ DelayedCacheCleanup(path_);
+ } else {
+ DeleteCache(path_, false);
+ }
+
+ // Don't call Init() if directed by the unit test: we are simulating a failure
+ // trying to re-enable the cache.
+ if (unit_test_)
+ init_ = true; // Let the destructor do proper cleanup.
+ else if (SyncInit() == net::OK) {
+ stats_.SetCounter(Stats::FATAL_ERROR, errors);
+ stats_.SetCounter(Stats::DOOM_CACHE, full_dooms);
+ stats_.SetCounter(Stats::DOOM_RECENT, partial_dooms);
+ stats_.SetCounter(Stats::LAST_REPORT, last_report);
+ }
+}
+
+void BackendImpl::PrepareForRestart() {
+ // Reset the mask_ if it was not given by the user.
+ if (!(user_flags_ & kMask))
+ mask_ = 0;
+
+ if (!(user_flags_ & kNewEviction))
+ new_eviction_ = false;
+
+ disabled_ = true;
+ data_->header.crash = 0;
+ index_->Flush();
+ index_ = NULL;
+ data_ = NULL;
+ block_files_.CloseFiles();
+ rankings_.Reset();
+ init_ = false;
+ restarted_ = true;
+}
+
+int BackendImpl::NewEntry(Addr address, EntryImpl** entry) {
+ EntriesMap::iterator it = open_entries_.find(address.value());
+ if (it != open_entries_.end()) {
+ // Easy job. This entry is already in memory.
+ EntryImpl* this_entry = it->second;
+ this_entry->AddRef();
+ *entry = this_entry;
+ return 0;
+ }
+
+ STRESS_DCHECK(block_files_.IsValid(address));
+
+ if (!address.SanityCheckForEntryV2()) {
+ LOG(WARNING) << "Wrong entry address.";
+ STRESS_NOTREACHED();
+ return ERR_INVALID_ADDRESS;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(
+ new EntryImpl(this, address, read_only_));
+ IncreaseNumRefs();
+ *entry = NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ if (!cache_entry->entry()->Load())
+ return ERR_READ_FAILURE;
+
+ if (IsLoaded()) {
+ CACHE_UMA(AGE_MS, "LoadTime", 0, start);
+ }
+
+ if (!cache_entry->SanityCheck()) {
+ LOG(WARNING) << "Messed up entry found.";
+ STRESS_NOTREACHED();
+ return ERR_INVALID_ENTRY;
+ }
+
+ STRESS_DCHECK(block_files_.IsValid(
+ Addr(cache_entry->entry()->Data()->rankings_node)));
+
+ if (!cache_entry->LoadNodeAddress())
+ return ERR_READ_FAILURE;
+
+ if (!rankings_.SanityCheck(cache_entry->rankings(), false)) {
+ STRESS_NOTREACHED();
+ cache_entry->SetDirtyFlag(0);
+ // Don't remove this from the list (it is not linked properly). Instead,
+ // break the link back to the entry because it is going away, and leave the
+ // rankings node to be deleted if we find it through a list.
+ rankings_.SetContents(cache_entry->rankings(), 0);
+ } else if (!rankings_.DataSanityCheck(cache_entry->rankings(), false)) {
+ STRESS_NOTREACHED();
+ cache_entry->SetDirtyFlag(0);
+ rankings_.SetContents(cache_entry->rankings(), address.value());
+ }
+
+ if (!cache_entry->DataSanityCheck()) {
+ LOG(WARNING) << "Messed up entry found.";
+ cache_entry->SetDirtyFlag(0);
+ cache_entry->FixForDelete();
+ }
+
+ // Prevent overwriting the dirty flag on the destructor.
+ cache_entry->SetDirtyFlag(GetCurrentEntryId());
+
+ if (cache_entry->dirty()) {
+ Trace("Dirty entry 0x%p 0x%x", reinterpret_cast<void*>(cache_entry.get()),
+ address.value());
+ }
+
+ open_entries_[address.value()] = cache_entry.get();
+
+ cache_entry->BeginLogging(net_log_, false);
+ cache_entry.swap(entry);
+ return 0;
+}
+
+EntryImpl* BackendImpl::MatchEntry(const std::string& key, uint32 hash,
+ bool find_parent, Addr entry_addr,
+ bool* match_error) {
+ Addr address(data_->table[hash & mask_]);
+ scoped_refptr<EntryImpl> cache_entry, parent_entry;
+ EntryImpl* tmp = NULL;
+ bool found = false;
+ std::set<CacheAddr> visited;
+ *match_error = false;
+
+ for (;;) {
+ if (disabled_)
+ break;
+
+ if (visited.find(address.value()) != visited.end()) {
+ // It's possible for a buggy version of the code to write a loop. Just
+ // break it.
+ Trace("Hash collision loop 0x%x", address.value());
+ address.set_value(0);
+ parent_entry->SetNextAddress(address);
+ }
+ visited.insert(address.value());
+
+ if (!address.is_initialized()) {
+ if (find_parent)
+ found = true;
+ break;
+ }
+
+ int error = NewEntry(address, &tmp);
+ cache_entry.swap(&tmp);
+
+ if (error || cache_entry->dirty()) {
+ // This entry is dirty on disk (it was not properly closed): we cannot
+ // trust it.
+ Addr child(0);
+ if (!error)
+ child.set_value(cache_entry->GetNextAddress());
+
+ if (parent_entry.get()) {
+ parent_entry->SetNextAddress(child);
+ parent_entry = NULL;
+ } else {
+ data_->table[hash & mask_] = child.value();
+ }
+
+ Trace("MatchEntry dirty %d 0x%x 0x%x", find_parent, entry_addr.value(),
+ address.value());
+
+ if (!error) {
+ // It is important to call DestroyInvalidEntry after removing this
+ // entry from the table.
+ DestroyInvalidEntry(cache_entry.get());
+ cache_entry = NULL;
+ } else {
+ Trace("NewEntry failed on MatchEntry 0x%x", address.value());
+ }
+
+ // Restart the search.
+ address.set_value(data_->table[hash & mask_]);
+ visited.clear();
+ continue;
+ }
+
+ DCHECK_EQ(hash & mask_, cache_entry->entry()->Data()->hash & mask_);
+ if (cache_entry->IsSameEntry(key, hash)) {
+ if (!cache_entry->Update())
+ cache_entry = NULL;
+ found = true;
+ if (find_parent && entry_addr.value() != address.value()) {
+ Trace("Entry not on the index 0x%x", address.value());
+ *match_error = true;
+ parent_entry = NULL;
+ }
+ break;
+ }
+ if (!cache_entry->Update())
+ cache_entry = NULL;
+ parent_entry = cache_entry;
+ cache_entry = NULL;
+ if (!parent_entry.get())
+ break;
+
+ address.set_value(parent_entry->GetNextAddress());
+ }
+
+ if (parent_entry.get() && (!find_parent || !found))
+ parent_entry = NULL;
+
+ if (find_parent && entry_addr.is_initialized() && !cache_entry.get()) {
+ *match_error = true;
+ parent_entry = NULL;
+ }
+
+ if (cache_entry.get() && (find_parent || !found))
+ cache_entry = NULL;
+
+ find_parent ? parent_entry.swap(&tmp) : cache_entry.swap(&tmp);
+ FlushIndex();
+ return tmp;
+}
+
+// This is the actual implementation for OpenNextEntry and OpenPrevEntry.
+EntryImpl* BackendImpl::OpenFollowingEntry(bool forward, void** iter) {
+ if (disabled_)
+ return NULL;
+
+ DCHECK(iter);
+
+ const int kListsToSearch = 3;
+ scoped_refptr<EntryImpl> entries[kListsToSearch];
+ scoped_ptr<Rankings::Iterator> iterator(
+ reinterpret_cast<Rankings::Iterator*>(*iter));
+ *iter = NULL;
+
+ if (!iterator.get()) {
+ iterator.reset(new Rankings::Iterator(&rankings_));
+ bool ret = false;
+
+ // Get an entry from each list.
+ for (int i = 0; i < kListsToSearch; i++) {
+ EntryImpl* temp = NULL;
+ ret |= OpenFollowingEntryFromList(forward, static_cast<Rankings::List>(i),
+ &iterator->nodes[i], &temp);
+ entries[i].swap(&temp); // The entry was already addref'd.
+ }
+ if (!ret)
+ return NULL;
+ } else {
+ // Get the next entry from the last list, and the actual entries for the
+ // elements on the other lists.
+ for (int i = 0; i < kListsToSearch; i++) {
+ EntryImpl* temp = NULL;
+ if (iterator->list == i) {
+ OpenFollowingEntryFromList(forward, iterator->list,
+ &iterator->nodes[i], &temp);
+ } else {
+ temp = GetEnumeratedEntry(iterator->nodes[i],
+ static_cast<Rankings::List>(i));
+ }
+
+ entries[i].swap(&temp); // The entry was already addref'd.
+ }
+ }
+
+ int newest = -1;
+ int oldest = -1;
+ Time access_times[kListsToSearch];
+ for (int i = 0; i < kListsToSearch; i++) {
+ if (entries[i].get()) {
+ access_times[i] = entries[i]->GetLastUsed();
+ if (newest < 0) {
+ DCHECK_LT(oldest, 0);
+ newest = oldest = i;
+ continue;
+ }
+ if (access_times[i] > access_times[newest])
+ newest = i;
+ if (access_times[i] < access_times[oldest])
+ oldest = i;
+ }
+ }
+
+ if (newest < 0 || oldest < 0)
+ return NULL;
+
+ EntryImpl* next_entry;
+ if (forward) {
+ next_entry = entries[newest].get();
+ iterator->list = static_cast<Rankings::List>(newest);
+ } else {
+ next_entry = entries[oldest].get();
+ iterator->list = static_cast<Rankings::List>(oldest);
+ }
+
+ *iter = iterator.release();
+ next_entry->AddRef();
+ return next_entry;
+}
+
+bool BackendImpl::OpenFollowingEntryFromList(bool forward, Rankings::List list,
+ CacheRankingsBlock** from_entry,
+ EntryImpl** next_entry) {
+ if (disabled_)
+ return false;
+
+ if (!new_eviction_ && Rankings::NO_USE != list)
+ return false;
+
+ Rankings::ScopedRankingsBlock rankings(&rankings_, *from_entry);
+ CacheRankingsBlock* next_block = forward ?
+ rankings_.GetNext(rankings.get(), list) :
+ rankings_.GetPrev(rankings.get(), list);
+ Rankings::ScopedRankingsBlock next(&rankings_, next_block);
+ *from_entry = NULL;
+
+ *next_entry = GetEnumeratedEntry(next.get(), list);
+ if (!*next_entry)
+ return false;
+
+ *from_entry = next.release();
+ return true;
+}
+
+EntryImpl* BackendImpl::GetEnumeratedEntry(CacheRankingsBlock* next,
+ Rankings::List list) {
+ if (!next || disabled_)
+ return NULL;
+
+ EntryImpl* entry;
+ int rv = NewEntry(Addr(next->Data()->contents), &entry);
+ if (rv) {
+ STRESS_NOTREACHED();
+ rankings_.Remove(next, list, false);
+ if (rv == ERR_INVALID_ADDRESS) {
+ // There is nothing linked from the index. Delete the rankings node.
+ DeleteBlock(next->address(), true);
+ }
+ return NULL;
+ }
+
+ if (entry->dirty()) {
+ // We cannot trust this entry.
+ InternalDoomEntry(entry);
+ entry->Release();
+ return NULL;
+ }
+
+ if (!entry->Update()) {
+ STRESS_NOTREACHED();
+ entry->Release();
+ return NULL;
+ }
+
+ // Note that it is unfortunate (but possible) for this entry to be clean, but
+ // not actually the real entry. In other words, we could have lost this entry
+ // from the index, and it could have been replaced with a newer one. It's not
+ // worth checking that this entry is "the real one", so we just return it and
+ // let the enumeration continue; this entry will be evicted at some point, and
+ // the regular path will work with the real entry. With time, this problem
+ // will disasappear because this scenario is just a bug.
+
+ // Make sure that we save the key for later.
+ entry->GetKey();
+
+ return entry;
+}
+
+EntryImpl* BackendImpl::ResurrectEntry(EntryImpl* deleted_entry) {
+ if (ENTRY_NORMAL == deleted_entry->entry()->Data()->state) {
+ deleted_entry->Release();
+ stats_.OnEvent(Stats::CREATE_MISS);
+ Trace("create entry miss ");
+ return NULL;
+ }
+
+ // We are attempting to create an entry and found out that the entry was
+ // previously deleted.
+
+ eviction_.OnCreateEntry(deleted_entry);
+ entry_count_++;
+
+ stats_.OnEvent(Stats::RESURRECT_HIT);
+ Trace("Resurrect entry hit ");
+ return deleted_entry;
+}
+
+void BackendImpl::DestroyInvalidEntry(EntryImpl* entry) {
+ LOG(WARNING) << "Destroying invalid entry.";
+ Trace("Destroying invalid entry 0x%p", entry);
+
+ entry->SetPointerForInvalidEntry(GetCurrentEntryId());
+
+ eviction_.OnDoomEntry(entry);
+ entry->InternalDoom();
+
+ if (!new_eviction_)
+ DecreaseNumEntries();
+ stats_.OnEvent(Stats::INVALID_ENTRY);
+}
+
+void BackendImpl::AddStorageSize(int32 bytes) {
+ data_->header.num_bytes += bytes;
+ DCHECK_GE(data_->header.num_bytes, 0);
+}
+
+void BackendImpl::SubstractStorageSize(int32 bytes) {
+ data_->header.num_bytes -= bytes;
+ DCHECK_GE(data_->header.num_bytes, 0);
+}
+
+void BackendImpl::IncreaseNumRefs() {
+ num_refs_++;
+ if (max_refs_ < num_refs_)
+ max_refs_ = num_refs_;
+}
+
+void BackendImpl::DecreaseNumRefs() {
+ DCHECK(num_refs_);
+ num_refs_--;
+
+ if (!num_refs_ && disabled_)
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&BackendImpl::RestartCache, GetWeakPtr(), true));
+}
+
+void BackendImpl::IncreaseNumEntries() {
+ data_->header.num_entries++;
+ DCHECK_GT(data_->header.num_entries, 0);
+}
+
+void BackendImpl::DecreaseNumEntries() {
+ data_->header.num_entries--;
+ if (data_->header.num_entries < 0) {
+ NOTREACHED();
+ data_->header.num_entries = 0;
+ }
+}
+
+void BackendImpl::LogStats() {
+ StatsItems stats;
+ GetStats(&stats);
+
+ for (size_t index = 0; index < stats.size(); index++)
+ VLOG(1) << stats[index].first << ": " << stats[index].second;
+}
+
+void BackendImpl::ReportStats() {
+ CACHE_UMA(COUNTS, "Entries", 0, data_->header.num_entries);
+
+ int current_size = data_->header.num_bytes / (1024 * 1024);
+ int max_size = max_size_ / (1024 * 1024);
+ int hit_ratio_as_percentage = stats_.GetHitRatio();
+
+ CACHE_UMA(COUNTS_10000, "Size2", 0, current_size);
+ // For any bin in HitRatioBySize2, the hit ratio of caches of that size is the
+ // ratio of that bin's total count to the count in the same bin in the Size2
+ // histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(COUNTS_10000, "HitRatioBySize2", 0, current_size);
+ CACHE_UMA(COUNTS_10000, "MaxSize2", 0, max_size);
+ if (!max_size)
+ max_size++;
+ CACHE_UMA(PERCENTAGE, "UsedSpace", 0, current_size * 100 / max_size);
+
+ CACHE_UMA(COUNTS_10000, "AverageOpenEntries2", 0,
+ static_cast<int>(stats_.GetCounter(Stats::OPEN_ENTRIES)));
+ CACHE_UMA(COUNTS_10000, "MaxOpenEntries2", 0,
+ static_cast<int>(stats_.GetCounter(Stats::MAX_ENTRIES)));
+ stats_.SetCounter(Stats::MAX_ENTRIES, 0);
+
+ CACHE_UMA(COUNTS_10000, "TotalFatalErrors", 0,
+ static_cast<int>(stats_.GetCounter(Stats::FATAL_ERROR)));
+ CACHE_UMA(COUNTS_10000, "TotalDoomCache", 0,
+ static_cast<int>(stats_.GetCounter(Stats::DOOM_CACHE)));
+ CACHE_UMA(COUNTS_10000, "TotalDoomRecentEntries", 0,
+ static_cast<int>(stats_.GetCounter(Stats::DOOM_RECENT)));
+ stats_.SetCounter(Stats::FATAL_ERROR, 0);
+ stats_.SetCounter(Stats::DOOM_CACHE, 0);
+ stats_.SetCounter(Stats::DOOM_RECENT, 0);
+
+ int64 total_hours = stats_.GetCounter(Stats::TIMER) / 120;
+ if (!data_->header.create_time || !data_->header.lru.filled) {
+ int cause = data_->header.create_time ? 0 : 1;
+ if (!data_->header.lru.filled)
+ cause |= 2;
+ CACHE_UMA(CACHE_ERROR, "ShortReport", 0, cause);
+ CACHE_UMA(HOURS, "TotalTimeNotFull", 0, static_cast<int>(total_hours));
+ return;
+ }
+
+ // This is an up to date client that will report FirstEviction() data. After
+ // that event, start reporting this:
+
+ CACHE_UMA(HOURS, "TotalTime", 0, static_cast<int>(total_hours));
+ // For any bin in HitRatioByTotalTime, the hit ratio of caches of that total
+ // time is the ratio of that bin's total count to the count in the same bin in
+ // the TotalTime histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(HOURS, "HitRatioByTotalTime", 0, implicit_cast<int>(total_hours));
+
+ int64 use_hours = stats_.GetCounter(Stats::LAST_REPORT_TIMER) / 120;
+ stats_.SetCounter(Stats::LAST_REPORT_TIMER, stats_.GetCounter(Stats::TIMER));
+
+ // We may see users with no use_hours at this point if this is the first time
+ // we are running this code.
+ if (use_hours)
+ use_hours = total_hours - use_hours;
+
+ if (!use_hours || !GetEntryCount() || !data_->header.num_bytes)
+ return;
+
+ CACHE_UMA(HOURS, "UseTime", 0, static_cast<int>(use_hours));
+ // For any bin in HitRatioByUseTime, the hit ratio of caches of that use time
+ // is the ratio of that bin's total count to the count in the same bin in the
+ // UseTime histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(HOURS, "HitRatioByUseTime", 0, implicit_cast<int>(use_hours));
+ CACHE_UMA(PERCENTAGE, "HitRatio", 0, hit_ratio_as_percentage);
+
+ int64 trim_rate = stats_.GetCounter(Stats::TRIM_ENTRY) / use_hours;
+ CACHE_UMA(COUNTS, "TrimRate", 0, static_cast<int>(trim_rate));
+
+ int avg_size = data_->header.num_bytes / GetEntryCount();
+ CACHE_UMA(COUNTS, "EntrySize", 0, avg_size);
+ CACHE_UMA(COUNTS, "EntriesFull", 0, data_->header.num_entries);
+
+ CACHE_UMA(PERCENTAGE, "IndexLoad", 0,
+ data_->header.num_entries * 100 / (mask_ + 1));
+
+ int large_entries_bytes = stats_.GetLargeEntriesSize();
+ int large_ratio = large_entries_bytes * 100 / data_->header.num_bytes;
+ CACHE_UMA(PERCENTAGE, "LargeEntriesRatio", 0, large_ratio);
+
+ if (new_eviction_) {
+ CACHE_UMA(PERCENTAGE, "ResurrectRatio", 0, stats_.GetResurrectRatio());
+ CACHE_UMA(PERCENTAGE, "NoUseRatio", 0,
+ data_->header.lru.sizes[0] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "LowUseRatio", 0,
+ data_->header.lru.sizes[1] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "HighUseRatio", 0,
+ data_->header.lru.sizes[2] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "DeletedRatio", 0,
+ data_->header.lru.sizes[4] * 100 / data_->header.num_entries);
+ }
+
+ stats_.ResetRatios();
+ stats_.SetCounter(Stats::TRIM_ENTRY, 0);
+
+ if (cache_type_ == net::DISK_CACHE)
+ block_files_.ReportStats();
+}
+
+void BackendImpl::UpgradeTo2_1() {
+ // 2.1 is basically the same as 2.0, except that new fields are actually
+ // updated by the new eviction algorithm.
+ DCHECK(0x20000 == data_->header.version);
+ data_->header.version = 0x20001;
+ data_->header.lru.sizes[Rankings::NO_USE] = data_->header.num_entries;
+}
+
+bool BackendImpl::CheckIndex() {
+ DCHECK(data_);
+
+ size_t current_size = index_->GetLength();
+ if (current_size < sizeof(Index)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ if (new_eviction_) {
+ // We support versions 2.0 and 2.1, upgrading 2.0 to 2.1.
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion >> 16 != data_->header.version >> 16) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ if (kCurrentVersion == data_->header.version) {
+ // We need file version 2.1 for the new eviction algorithm.
+ UpgradeTo2_1();
+ }
+ } else {
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion != data_->header.version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ }
+
+ if (!data_->header.table_len) {
+ LOG(ERROR) << "Invalid table size";
+ return false;
+ }
+
+ if (current_size < GetIndexSize(data_->header.table_len) ||
+ data_->header.table_len & (kBaseTableLen - 1)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ AdjustMaxCacheSize(data_->header.table_len);
+
+#if !defined(NET_BUILD_STRESS_CACHE)
+ if (data_->header.num_bytes < 0 ||
+ (max_size_ < kint32max - kDefaultCacheSize &&
+ data_->header.num_bytes > max_size_ + kDefaultCacheSize)) {
+ LOG(ERROR) << "Invalid cache (current) size";
+ return false;
+ }
+#endif
+
+ if (data_->header.num_entries < 0) {
+ LOG(ERROR) << "Invalid number of entries";
+ return false;
+ }
+
+ if (!mask_)
+ mask_ = data_->header.table_len - 1;
+
+ // Load the table into memory with a single read.
+ scoped_ptr<char[]> buf(new char[current_size]);
+ return index_->Read(buf.get(), current_size, 0);
+}
+
+int BackendImpl::CheckAllEntries() {
+ int num_dirty = 0;
+ int num_entries = 0;
+ DCHECK(mask_ < kuint32max);
+ for (unsigned int i = 0; i <= mask_; i++) {
+ Addr address(data_->table[i]);
+ if (!address.is_initialized())
+ continue;
+ for (;;) {
+ EntryImpl* tmp;
+ int ret = NewEntry(address, &tmp);
+ if (ret) {
+ STRESS_NOTREACHED();
+ return ret;
+ }
+ scoped_refptr<EntryImpl> cache_entry;
+ cache_entry.swap(&tmp);
+
+ if (cache_entry->dirty())
+ num_dirty++;
+ else if (CheckEntry(cache_entry.get()))
+ num_entries++;
+ else
+ return ERR_INVALID_ENTRY;
+
+ DCHECK_EQ(i, cache_entry->entry()->Data()->hash & mask_);
+ address.set_value(cache_entry->GetNextAddress());
+ if (!address.is_initialized())
+ break;
+ }
+ }
+
+ Trace("CheckAllEntries End");
+ if (num_entries + num_dirty != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries " << num_entries << " " << num_dirty <<
+ " " << data_->header.num_entries;
+ DCHECK_LT(num_entries, data_->header.num_entries);
+ return ERR_NUM_ENTRIES_MISMATCH;
+ }
+
+ return num_dirty;
+}
+
+bool BackendImpl::CheckEntry(EntryImpl* cache_entry) {
+ bool ok = block_files_.IsValid(cache_entry->entry()->address());
+ ok = ok && block_files_.IsValid(cache_entry->rankings()->address());
+ EntryStore* data = cache_entry->entry()->Data();
+ for (size_t i = 0; i < arraysize(data->data_addr); i++) {
+ if (data->data_addr[i]) {
+ Addr address(data->data_addr[i]);
+ if (address.is_block_file())
+ ok = ok && block_files_.IsValid(address);
+ }
+ }
+
+ return ok && cache_entry->rankings()->VerifyHash();
+}
+
+int BackendImpl::MaxBuffersSize() {
+ static int64 total_memory = base::SysInfo::AmountOfPhysicalMemory();
+ static bool done = false;
+
+ if (!done) {
+ const int kMaxBuffersSize = 30 * 1024 * 1024;
+
+ // We want to use up to 2% of the computer's memory.
+ total_memory = total_memory * 2 / 100;
+ if (total_memory > kMaxBuffersSize || total_memory <= 0)
+ total_memory = kMaxBuffersSize;
+
+ done = true;
+ }
+
+ return static_cast<int>(total_memory);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/backend_impl.h b/chromium/net/disk_cache/backend_impl.h
new file mode 100644
index 00000000000..982bee64818
--- /dev/null
+++ b/chromium/net/disk_cache/backend_impl.h
@@ -0,0 +1,400 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_BACKEND_IMPL_H_
+#define NET_DISK_CACHE_BACKEND_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/timer/timer.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/eviction.h"
+#include "net/disk_cache/in_flight_backend_io.h"
+#include "net/disk_cache/rankings.h"
+#include "net/disk_cache/stats.h"
+#include "net/disk_cache/stress_support.h"
+#include "net/disk_cache/trace.h"
+
+namespace net {
+class NetLog;
+} // namespace net
+
+namespace disk_cache {
+
+struct Index;
+
+enum BackendFlags {
+ kNone = 0,
+ kMask = 1, // A mask (for the index table) was specified.
+ kMaxSize = 1 << 1, // A maximum size was provided.
+ kUnitTestMode = 1 << 2, // We are modifying the behavior for testing.
+ kUpgradeMode = 1 << 3, // This is the upgrade tool (dump).
+ kNewEviction = 1 << 4, // Use of new eviction was specified.
+ kNoRandom = 1 << 5, // Don't add randomness to the behavior.
+ kNoLoadProtection = 1 << 6, // Don't act conservatively under load.
+ kNoBuffering = 1 << 7 // Disable extended IO buffering.
+};
+
+// This class implements the Backend interface. An object of this
+// class handles the operations of the cache for a particular profile.
+class NET_EXPORT_PRIVATE BackendImpl : public Backend {
+ friend class Eviction;
+ public:
+ BackendImpl(const base::FilePath& path, base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log);
+ // mask can be used to limit the usable size of the hash table, for testing.
+ BackendImpl(const base::FilePath& path, uint32 mask,
+ base::MessageLoopProxy* cache_thread, net::NetLog* net_log);
+ virtual ~BackendImpl();
+
+ // Performs general initialization for this current instance of the cache.
+ int Init(const CompletionCallback& callback);
+
+ // Performs the actual initialization and final cleanup on destruction.
+ int SyncInit();
+ void CleanupCache();
+
+ // Same behavior as OpenNextEntry but walks the list from back to front.
+ int OpenPrevEntry(void** iter, Entry** prev_entry,
+ const CompletionCallback& callback);
+
+ // Synchronous implementation of the asynchronous interface.
+ int SyncOpenEntry(const std::string& key, Entry** entry);
+ int SyncCreateEntry(const std::string& key, Entry** entry);
+ int SyncDoomEntry(const std::string& key);
+ int SyncDoomAllEntries();
+ int SyncDoomEntriesBetween(base::Time initial_time,
+ base::Time end_time);
+ int SyncDoomEntriesSince(base::Time initial_time);
+ int SyncOpenNextEntry(void** iter, Entry** next_entry);
+ int SyncOpenPrevEntry(void** iter, Entry** prev_entry);
+ void SyncEndEnumeration(void* iter);
+ void SyncOnExternalCacheHit(const std::string& key);
+
+ // Open or create an entry for the given |key| or |iter|.
+ EntryImpl* OpenEntryImpl(const std::string& key);
+ EntryImpl* CreateEntryImpl(const std::string& key);
+ EntryImpl* OpenNextEntryImpl(void** iter);
+ EntryImpl* OpenPrevEntryImpl(void** iter);
+
+ // Sets the maximum size for the total amount of data stored by this instance.
+ bool SetMaxSize(int max_bytes);
+
+ // Sets the cache type for this backend.
+ void SetType(net::CacheType type);
+
+ // Returns the full name for an external storage file.
+ base::FilePath GetFileName(Addr address) const;
+
+ // Returns the actual file used to store a given (non-external) address.
+ MappedFile* File(Addr address);
+
+ // Returns a weak pointer to the background queue.
+ base::WeakPtr<InFlightBackendIO> GetBackgroundQueue();
+
+ // Creates an external storage file.
+ bool CreateExternalFile(Addr* address);
+
+ // Creates a new storage block of size block_count.
+ bool CreateBlock(FileType block_type, int block_count,
+ Addr* block_address);
+
+ // Deletes a given storage block. deep set to true can be used to zero-fill
+ // the related storage in addition of releasing the related block.
+ void DeleteBlock(Addr block_address, bool deep);
+
+ // Retrieves a pointer to the LRU-related data.
+ LruData* GetLruData();
+
+ // Updates the ranking information for an entry.
+ void UpdateRank(EntryImpl* entry, bool modified);
+
+ // A node was recovered from a crash, it may not be on the index, so this
+ // method checks it and takes the appropriate action.
+ void RecoveredEntry(CacheRankingsBlock* rankings);
+
+ // Permanently deletes an entry, but still keeps track of it.
+ void InternalDoomEntry(EntryImpl* entry);
+
+#if defined(NET_BUILD_STRESS_CACHE)
+ // Returns the address of the entry linked to the entry at a given |address|.
+ CacheAddr GetNextAddr(Addr address);
+
+ // Verifies that |entry| is not currently reachable through the index.
+ void NotLinked(EntryImpl* entry);
+#endif
+
+ // Removes all references to this entry.
+ void RemoveEntry(EntryImpl* entry);
+
+ // This method must be called when an entry is released for the last time, so
+ // the entry should not be used anymore. |address| is the cache address of the
+ // entry.
+ void OnEntryDestroyBegin(Addr address);
+
+ // This method must be called after all resources for an entry have been
+ // released.
+ void OnEntryDestroyEnd();
+
+ // If the data stored by the provided |rankings| points to an open entry,
+ // returns a pointer to that entry, otherwise returns NULL. Note that this
+ // method does NOT increase the ref counter for the entry.
+ EntryImpl* GetOpenEntry(CacheRankingsBlock* rankings) const;
+
+ // Returns the id being used on this run of the cache.
+ int32 GetCurrentEntryId() const;
+
+ // Returns the maximum size for a file to reside on the cache.
+ int MaxFileSize() const;
+
+ // A user data block is being created, extended or truncated.
+ void ModifyStorageSize(int32 old_size, int32 new_size);
+
+ // Logs requests that are denied due to being too big.
+ void TooMuchStorageRequested(int32 size);
+
+ // Returns true if a temporary buffer is allowed to be extended.
+ bool IsAllocAllowed(int current_size, int new_size);
+
+ // Tracks the release of |size| bytes by an entry buffer.
+ void BufferDeleted(int size);
+
+ // Only intended for testing the two previous methods.
+ int GetTotalBuffersSize() const {
+ return buffer_bytes_;
+ }
+
+ // Returns true if this instance seems to be under heavy load.
+ bool IsLoaded() const;
+
+ // Returns the full histogram name, for the given base |name| and experiment,
+ // and the current cache type. The name will be "DiskCache.t.name_e" where n
+ // is the cache type and e the provided |experiment|.
+ std::string HistogramName(const char* name, int experiment) const;
+
+ net::CacheType cache_type() const {
+ return cache_type_;
+ }
+
+ bool read_only() const {
+ return read_only_;
+ }
+
+ // Returns a weak pointer to this object.
+ base::WeakPtr<BackendImpl> GetWeakPtr();
+
+ // Returns true if we should send histograms for this user again. The caller
+ // must call this function only once per run (because it returns always the
+ // same thing on a given run).
+ bool ShouldReportAgain();
+
+ // Reports some data when we filled up the cache.
+ void FirstEviction();
+
+ // Reports a critical error (and disables the cache).
+ void CriticalError(int error);
+
+ // Reports an uncommon, recoverable error.
+ void ReportError(int error);
+
+ // Called when an interesting event should be logged (counted).
+ void OnEvent(Stats::Counters an_event);
+
+ // Keeps track of payload access (doesn't include metadata).
+ void OnRead(int bytes);
+ void OnWrite(int bytes);
+
+ // Timer callback to calculate usage statistics.
+ void OnStatsTimer();
+
+ // Handles the pending asynchronous IO count.
+ void IncrementIoCount();
+ void DecrementIoCount();
+
+ // Sets internal parameters to enable unit testing mode.
+ void SetUnitTestMode();
+
+ // Sets internal parameters to enable upgrade mode (for internal tools).
+ void SetUpgradeMode();
+
+ // Sets the eviction algorithm to version 2.
+ void SetNewEviction();
+
+ // Sets an explicit set of BackendFlags.
+ void SetFlags(uint32 flags);
+
+ // Clears the counter of references to test handling of corruptions.
+ void ClearRefCountForTest();
+
+ // Sends a dummy operation through the operation queue, for unit tests.
+ int FlushQueueForTest(const CompletionCallback& callback);
+
+ // Runs the provided task on the cache thread. The task will be automatically
+ // deleted after it runs.
+ int RunTaskForTest(const base::Closure& task,
+ const CompletionCallback& callback);
+
+ // Trims an entry (all if |empty| is true) from the list of deleted
+ // entries. This method should be called directly on the cache thread.
+ void TrimForTest(bool empty);
+
+ // Trims an entry (all if |empty| is true) from the list of deleted
+ // entries. This method should be called directly on the cache thread.
+ void TrimDeletedListForTest(bool empty);
+
+ // Performs a simple self-check, and returns the number of dirty items
+ // or an error code (negative value).
+ int SelfCheck();
+
+ // Ensures the index is flushed to disk (a no-op on platforms with mmap).
+ void FlushIndex();
+
+ // Backend implementation.
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(StatsItems* stats) OVERRIDE;
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ private:
+ typedef base::hash_map<CacheAddr, EntryImpl*> EntriesMap;
+
+ // Creates a new backing file for the cache index.
+ bool CreateBackingStore(disk_cache::File* file);
+ bool InitBackingStore(bool* file_created);
+ void AdjustMaxCacheSize(int table_len);
+
+ bool InitStats();
+ void StoreStats();
+
+ // Deletes the cache and starts again.
+ void RestartCache(bool failure);
+ void PrepareForRestart();
+
+ // Creates a new entry object. Returns zero on success, or a disk_cache error
+ // on failure.
+ int NewEntry(Addr address, EntryImpl** entry);
+
+ // Returns a given entry from the cache. The entry to match is determined by
+ // key and hash, and the returned entry may be the matched one or it's parent
+ // on the list of entries with the same hash (or bucket). To look for a parent
+ // of a given entry, |entry_addr| should be grabbed from that entry, so that
+ // if it doesn't match the entry on the index, we know that it was replaced
+ // with a new entry; in this case |*match_error| will be set to true and the
+ // return value will be NULL.
+ EntryImpl* MatchEntry(const std::string& key, uint32 hash, bool find_parent,
+ Addr entry_addr, bool* match_error);
+
+ // Opens the next or previous entry on a cache iteration.
+ EntryImpl* OpenFollowingEntry(bool forward, void** iter);
+
+ // Opens the next or previous entry on a single list. If successful,
+ // |from_entry| will be updated to point to the new entry, otherwise it will
+ // be set to NULL; in other words, it is used as an explicit iterator.
+ bool OpenFollowingEntryFromList(bool forward, Rankings::List list,
+ CacheRankingsBlock** from_entry,
+ EntryImpl** next_entry);
+
+ // Returns the entry that is pointed by |next|, from the given |list|.
+ EntryImpl* GetEnumeratedEntry(CacheRankingsBlock* next, Rankings::List list);
+
+ // Re-opens an entry that was previously deleted.
+ EntryImpl* ResurrectEntry(EntryImpl* deleted_entry);
+
+ void DestroyInvalidEntry(EntryImpl* entry);
+
+ // Handles the used storage count.
+ void AddStorageSize(int32 bytes);
+ void SubstractStorageSize(int32 bytes);
+
+ // Update the number of referenced cache entries.
+ void IncreaseNumRefs();
+ void DecreaseNumRefs();
+ void IncreaseNumEntries();
+ void DecreaseNumEntries();
+
+ // Dumps current cache statistics to the log.
+ void LogStats();
+
+ // Send UMA stats.
+ void ReportStats();
+
+ // Upgrades the index file to version 2.1.
+ void UpgradeTo2_1();
+
+ // Performs basic checks on the index file. Returns false on failure.
+ bool CheckIndex();
+
+ // Part of the self test. Returns the number or dirty entries, or an error.
+ int CheckAllEntries();
+
+ // Part of the self test. Returns false if the entry is corrupt.
+ bool CheckEntry(EntryImpl* cache_entry);
+
+ // Returns the maximum total memory for the memory buffers.
+ int MaxBuffersSize();
+
+ InFlightBackendIO background_queue_; // The controller of pending operations.
+ scoped_refptr<MappedFile> index_; // The main cache index.
+ base::FilePath path_; // Path to the folder used as backing storage.
+ Index* data_; // Pointer to the index data.
+ BlockFiles block_files_; // Set of files used to store all data.
+ Rankings rankings_; // Rankings to be able to trim the cache.
+ uint32 mask_; // Binary mask to map a hash to the hash table.
+ int32 max_size_; // Maximum data size for this instance.
+ Eviction eviction_; // Handler of the eviction algorithm.
+ EntriesMap open_entries_; // Map of open entries.
+ int num_refs_; // Number of referenced cache entries.
+ int max_refs_; // Max number of referenced cache entries.
+ int num_pending_io_; // Number of pending IO operations.
+ int entry_count_; // Number of entries accessed lately.
+ int byte_count_; // Number of bytes read/written lately.
+ int buffer_bytes_; // Total size of the temporary entries' buffers.
+ int up_ticks_; // The number of timer ticks received (OnStatsTimer).
+ net::CacheType cache_type_;
+ int uma_report_; // Controls transmission of UMA data.
+ uint32 user_flags_; // Flags set by the user.
+ bool init_; // controls the initialization of the system.
+ bool restarted_;
+ bool unit_test_;
+ bool read_only_; // Prevents updates of the rankings data (used by tools).
+ bool disabled_;
+ bool new_eviction_; // What eviction algorithm should be used.
+ bool first_timer_; // True if the timer has not been called.
+ bool user_load_; // True if we see a high load coming from the caller.
+
+ net::NetLog* net_log_;
+
+ Stats stats_; // Usage statistics.
+ scoped_ptr<base::RepeatingTimer<BackendImpl> > timer_; // Usage timer.
+ base::WaitableEvent done_; // Signals the end of background work.
+ scoped_refptr<TraceObject> trace_object_; // Initializes internal tracing.
+ base::WeakPtrFactory<BackendImpl> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackendImpl);
+};
+
+// Returns the preferred max cache size given the available disk space.
+NET_EXPORT_PRIVATE int PreferedCacheSize(int64 available);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BACKEND_IMPL_H_
diff --git a/chromium/net/disk_cache/backend_unittest.cc b/chromium/net/disk_cache/backend_unittest.cc
new file mode 100644
index 00000000000..7eeeee1fd83
--- /dev/null
+++ b/chromium/net/disk_cache/backend_unittest.cc
@@ -0,0 +1,3415 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/metrics/field_trial.h"
+#include "base/port.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/mapped_file.h"
+#include "net/disk_cache/mem_backend_impl.h"
+#include "net/disk_cache/simple/simple_backend_impl.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_test_util.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "net/disk_cache/tracing_cache_backend.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+using base::Time;
+
+namespace {
+
+const char kExistingEntryKey[] = "existing entry key";
+
+scoped_ptr<disk_cache::BackendImpl> CreateExistingEntryCache(
+ const base::Thread& cache_thread,
+ base::FilePath& cache_path) {
+ net::TestCompletionCallback cb;
+
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ cache_path, cache_thread.message_loop_proxy(), NULL));
+ int rv = cache->Init(cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return scoped_ptr<disk_cache::BackendImpl>();
+
+ disk_cache::Entry* entry = NULL;
+ rv = cache->CreateEntry(kExistingEntryKey, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return scoped_ptr<disk_cache::BackendImpl>();
+ entry->Close();
+
+ return cache.Pass();
+}
+
+} // namespace
+
+// Tests that can run with different types of caches.
+class DiskCacheBackendTest : public DiskCacheTestWithCache {
+ protected:
+ void BackendBasics();
+ void BackendKeying();
+ void BackendShutdownWithPendingFileIO(bool fast);
+ void BackendShutdownWithPendingIO(bool fast);
+ void BackendShutdownWithPendingCreate(bool fast);
+ void BackendSetSize();
+ void BackendLoad();
+ void BackendChain();
+ void BackendValidEntry();
+ void BackendInvalidEntry();
+ void BackendInvalidEntryRead();
+ void BackendInvalidEntryWithLoad();
+ void BackendTrimInvalidEntry();
+ void BackendTrimInvalidEntry2();
+ void BackendEnumerations();
+ void BackendEnumerations2();
+ void BackendInvalidEntryEnumeration();
+ void BackendFixEnumerators();
+ void BackendDoomRecent();
+
+ // Adds 5 sparse entries. |doomed_start| and |doomed_end| if not NULL,
+ // will be filled with times, used by DoomEntriesSince and DoomEntriesBetween.
+ // There are 4 entries after doomed_start and 2 after doomed_end.
+ void InitSparseCache(base::Time* doomed_start, base::Time* doomed_end);
+
+ void BackendDoomBetween();
+ void BackendTransaction(const std::string& name, int num_entries, bool load);
+ void BackendRecoverInsert();
+ void BackendRecoverRemove();
+ void BackendRecoverWithEviction();
+ void BackendInvalidEntry2();
+ void BackendInvalidEntry3();
+ void BackendInvalidEntry7();
+ void BackendInvalidEntry8();
+ void BackendInvalidEntry9(bool eviction);
+ void BackendInvalidEntry10(bool eviction);
+ void BackendInvalidEntry11(bool eviction);
+ void BackendTrimInvalidEntry12();
+ void BackendDoomAll();
+ void BackendDoomAll2();
+ void BackendInvalidRankings();
+ void BackendInvalidRankings2();
+ void BackendDisable();
+ void BackendDisable2();
+ void BackendDisable3();
+ void BackendDisable4();
+ void TracingBackendBasics();
+
+ bool CreateSetOfRandomEntries(std::set<std::string>* key_pool);
+ bool EnumerateAndMatchKeys(int max_to_open,
+ void** iter,
+ std::set<std::string>* keys_to_match,
+ size_t* count);
+};
+
+void DiskCacheBackendTest::BackendBasics() {
+ InitCache();
+ disk_cache::Entry *entry1 = NULL, *entry2 = NULL;
+ EXPECT_NE(net::OK, OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+ entry1->Close();
+ entry1 = NULL;
+
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ ASSERT_TRUE(NULL != entry1);
+ entry1->Close();
+ entry1 = NULL;
+
+ EXPECT_NE(net::OK, CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("some other key", &entry2));
+ ASSERT_EQ(net::OK, CreateEntry("some other key", &entry2));
+ ASSERT_TRUE(NULL != entry1);
+ ASSERT_TRUE(NULL != entry2);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ disk_cache::Entry* entry3 = NULL;
+ ASSERT_EQ(net::OK, OpenEntry("some other key", &entry3));
+ ASSERT_TRUE(NULL != entry3);
+ EXPECT_TRUE(entry2 == entry3);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ EXPECT_EQ(net::OK, DoomEntry("some other key"));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ entry1->Close();
+ entry2->Close();
+ entry3->Close();
+
+ EXPECT_EQ(net::OK, DoomEntry("the first key"));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("some other key", &entry2));
+ entry1->Doom();
+ entry1->Close();
+ EXPECT_EQ(net::OK, DoomEntry("some other key"));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ entry2->Close();
+}
+
+TEST_F(DiskCacheBackendTest, Basics) {
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionBasics) {
+ SetNewEviction();
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyBasics) {
+ SetMemoryOnlyMode();
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheBasics) {
+ SetCacheType(net::APP_CACHE);
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheBasics) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendBasics();
+}
+
+void DiskCacheBackendTest::BackendKeying() {
+ InitCache();
+ const char* kName1 = "the first key";
+ const char* kName2 = "the first Key";
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_EQ(net::OK, CreateEntry(kName1, &entry1));
+
+ ASSERT_EQ(net::OK, CreateEntry(kName2, &entry2));
+ EXPECT_TRUE(entry1 != entry2) << "Case sensitive";
+ entry2->Close();
+
+ char buffer[30];
+ base::strlcpy(buffer, kName1, arraysize(buffer));
+ ASSERT_EQ(net::OK, OpenEntry(buffer, &entry2));
+ EXPECT_TRUE(entry1 == entry2);
+ entry2->Close();
+
+ base::strlcpy(buffer + 1, kName1, arraysize(buffer) - 1);
+ ASSERT_EQ(net::OK, OpenEntry(buffer + 1, &entry2));
+ EXPECT_TRUE(entry1 == entry2);
+ entry2->Close();
+
+ base::strlcpy(buffer + 3, kName1, arraysize(buffer) - 3);
+ ASSERT_EQ(net::OK, OpenEntry(buffer + 3, &entry2));
+ EXPECT_TRUE(entry1 == entry2);
+ entry2->Close();
+
+ // Now verify long keys.
+ char buffer2[20000];
+ memset(buffer2, 's', sizeof(buffer2));
+ buffer2[1023] = '\0';
+ ASSERT_EQ(net::OK, CreateEntry(buffer2, &entry2)) << "key on block file";
+ entry2->Close();
+
+ buffer2[1023] = 'g';
+ buffer2[19999] = '\0';
+ ASSERT_EQ(net::OK, CreateEntry(buffer2, &entry2)) << "key on external file";
+ entry2->Close();
+ entry1->Close();
+}
+
+TEST_F(DiskCacheBackendTest, Keying) {
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionKeying) {
+ SetNewEviction();
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyKeying) {
+ SetMemoryOnlyMode();
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheKeying) {
+ SetCacheType(net::APP_CACHE);
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheKeying) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendKeying();
+}
+
+TEST_F(DiskCacheTest, CreateBackend) {
+ net::TestCompletionCallback cb;
+
+ {
+ ASSERT_TRUE(CleanupCacheDir());
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ // Test the private factory method(s).
+ scoped_ptr<disk_cache::Backend> cache;
+ cache = disk_cache::MemBackendImpl::CreateBackend(0, NULL);
+ ASSERT_TRUE(cache.get());
+ cache.reset();
+
+ // Now test the public API.
+ int rv =
+ disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_DEFAULT,
+ cache_path_,
+ 0,
+ false,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &cache,
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ ASSERT_TRUE(cache.get());
+ cache.reset();
+
+ rv = disk_cache::CreateCacheBackend(net::MEMORY_CACHE,
+ net::CACHE_BACKEND_DEFAULT,
+ base::FilePath(), 0,
+ false, NULL, NULL, &cache,
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ ASSERT_TRUE(cache.get());
+ cache.reset();
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Tests that |BackendImpl| fails to initialize with a missing file.
+TEST_F(DiskCacheBackendTest, CreateBackend_MissingFile) {
+ ASSERT_TRUE(CopyTestCache("bad_entry"));
+ base::FilePath filename = cache_path_.AppendASCII("data_1");
+ base::DeleteFile(filename, false);
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ net::TestCompletionCallback cb;
+
+ bool prev = base::ThreadRestrictions::SetIOAllowed(false);
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ cache_path_, cache_thread.message_loop_proxy().get(), NULL));
+ int rv = cache->Init(cb.callback());
+ EXPECT_EQ(net::ERR_FAILED, cb.GetResult(rv));
+ base::ThreadRestrictions::SetIOAllowed(prev);
+
+ cache.reset();
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, ExternalFiles) {
+ InitCache();
+ // First, let's create a file on the folder.
+ base::FilePath filename = cache_path_.AppendASCII("f_000001");
+
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, false);
+ ASSERT_EQ(kSize, file_util::WriteFile(filename, buffer1->data(), kSize));
+
+ // Now let's create a file with the cache.
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
+ ASSERT_EQ(0, WriteData(entry, 0, 20000, buffer1.get(), 0, false));
+ entry->Close();
+
+ // And verify that the first file is still there.
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ ASSERT_EQ(kSize, file_util::ReadFile(filename, buffer2->data(), kSize));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer2->data(), kSize));
+}
+
+// Tests that we deal with file-level pending operations at destruction time.
+void DiskCacheBackendTest::BackendShutdownWithPendingFileIO(bool fast) {
+ net::TestCompletionCallback cb;
+ int rv;
+
+ {
+ ASSERT_TRUE(CleanupCacheDir());
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ uint32 flags = disk_cache::kNoBuffering;
+ if (!fast)
+ flags |= disk_cache::kNoRandom;
+
+ UseCurrentThread();
+ CreateBackend(flags, NULL);
+
+ disk_cache::EntryImpl* entry;
+ rv = cache_->CreateEntry(
+ "some key", reinterpret_cast<disk_cache::Entry**>(&entry),
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ const int kSize = 25000;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+
+ for (int i = 0; i < 10 * 1024 * 1024; i += 64 * 1024) {
+ // We are using the current thread as the cache thread because we want to
+ // be able to call directly this method to make sure that the OS (instead
+ // of us switching thread) is returning IO pending.
+ rv =
+ entry->WriteDataImpl(0, i, buffer.get(), kSize, cb.callback(), false);
+ if (rv == net::ERR_IO_PENDING)
+ break;
+ EXPECT_EQ(kSize, rv);
+ }
+
+ // Don't call Close() to avoid going through the queue or we'll deadlock
+ // waiting for the operation to finish.
+ entry->Release();
+
+ // The cache destructor will see one pending operation here.
+ cache_.reset();
+
+ if (rv == net::ERR_IO_PENDING) {
+ if (fast)
+ EXPECT_FALSE(cb.have_result());
+ else
+ EXPECT_TRUE(cb.have_result());
+ }
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+#if defined(OS_WIN)
+ // Wait for the actual operation to complete, or we'll keep a file handle that
+ // may cause issues later. Note that on Posix systems even though this test
+ // uses a single thread, the actual IO is posted to a worker thread and the
+ // cache destructor breaks the link to reach cb when the operation completes.
+ rv = cb.GetResult(rv);
+#endif
+}
+
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingFileIO) {
+ BackendShutdownWithPendingFileIO(false);
+}
+
+// Here and below, tests that simulate crashes are not compiled in LeakSanitizer
+// builds because they contain a lot of intentional memory leaks.
+// The wrapper scripts used to run tests under Valgrind Memcheck and
+// Heapchecker will also disable these tests under those tools. See:
+// tools/valgrind/gtest_exclude/net_unittests.gtest-memcheck.txt
+// tools/heapcheck/net_unittests.gtest-heapcheck.txt
+#if !defined(LEAK_SANITIZER)
+// We'll be leaking from this test.
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingFileIO_Fast) {
+ // The integrity test sets kNoRandom so there's a version mismatch if we don't
+ // force new eviction.
+ SetNewEviction();
+ BackendShutdownWithPendingFileIO(true);
+}
+#endif
+
+// Tests that we deal with background-thread pending operations.
+void DiskCacheBackendTest::BackendShutdownWithPendingIO(bool fast) {
+ net::TestCompletionCallback cb;
+
+ {
+ ASSERT_TRUE(CleanupCacheDir());
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ uint32 flags = disk_cache::kNoBuffering;
+ if (!fast)
+ flags |= disk_cache::kNoRandom;
+
+ CreateBackend(flags, &cache_thread);
+
+ disk_cache::Entry* entry;
+ int rv = cache_->CreateEntry("some key", &entry, cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ entry->Close();
+
+ // The cache destructor will see one pending operation here.
+ cache_.reset();
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingIO) {
+ BackendShutdownWithPendingIO(false);
+}
+
+#if !defined(LEAK_SANITIZER)
+// We'll be leaking from this test.
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingIO_Fast) {
+ // The integrity test sets kNoRandom so there's a version mismatch if we don't
+ // force new eviction.
+ SetNewEviction();
+ BackendShutdownWithPendingIO(true);
+}
+#endif
+
+// Tests that we deal with create-type pending operations.
+void DiskCacheBackendTest::BackendShutdownWithPendingCreate(bool fast) {
+ net::TestCompletionCallback cb;
+
+ {
+ ASSERT_TRUE(CleanupCacheDir());
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ disk_cache::BackendFlags flags =
+ fast ? disk_cache::kNone : disk_cache::kNoRandom;
+ CreateBackend(flags, &cache_thread);
+
+ disk_cache::Entry* entry;
+ int rv = cache_->CreateEntry("some key", &entry, cb.callback());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ cache_.reset();
+ EXPECT_FALSE(cb.have_result());
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingCreate) {
+ BackendShutdownWithPendingCreate(false);
+}
+
+#if !defined(LEAK_SANITIZER)
+// We'll be leaking an entry from this test.
+TEST_F(DiskCacheBackendTest, ShutdownWithPendingCreate_Fast) {
+ // The integrity test sets kNoRandom so there's a version mismatch if we don't
+ // force new eviction.
+ SetNewEviction();
+ BackendShutdownWithPendingCreate(true);
+}
+#endif
+
+TEST_F(DiskCacheTest, TruncatedIndex) {
+ ASSERT_TRUE(CleanupCacheDir());
+ base::FilePath index = cache_path_.AppendASCII("index");
+ ASSERT_EQ(5, file_util::WriteFile(index, "hello", 5));
+
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ net::TestCompletionCallback cb;
+
+ scoped_ptr<disk_cache::Backend> backend;
+ int rv =
+ disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_BLOCKFILE,
+ cache_path_,
+ 0,
+ false,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &backend,
+ cb.callback());
+ ASSERT_NE(net::OK, cb.GetResult(rv));
+
+ ASSERT_FALSE(backend);
+}
+
+void DiskCacheBackendTest::BackendSetSize() {
+ const int cache_size = 0x10000; // 64 kB
+ SetMaxSize(cache_size);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(cache_size));
+ memset(buffer->data(), 0, cache_size);
+ EXPECT_EQ(cache_size / 10,
+ WriteData(entry, 0, 0, buffer.get(), cache_size / 10, false))
+ << "normal file";
+
+ EXPECT_EQ(net::ERR_FAILED,
+ WriteData(entry, 1, 0, buffer.get(), cache_size / 5, false))
+ << "file size above the limit";
+
+ // By doubling the total size, we make this file cacheable.
+ SetMaxSize(cache_size * 2);
+ EXPECT_EQ(cache_size / 5,
+ WriteData(entry, 1, 0, buffer.get(), cache_size / 5, false));
+
+ // Let's fill up the cache!.
+ SetMaxSize(cache_size * 10);
+ EXPECT_EQ(cache_size * 3 / 4,
+ WriteData(entry, 0, 0, buffer.get(), cache_size * 3 / 4, false));
+ entry->Close();
+ FlushQueueForTest();
+
+ SetMaxSize(cache_size);
+
+ // The cache is 95% full.
+
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+ EXPECT_EQ(cache_size / 10,
+ WriteData(entry, 0, 0, buffer.get(), cache_size / 10, false));
+
+ disk_cache::Entry* entry2;
+ ASSERT_EQ(net::OK, CreateEntry("an extra key", &entry2));
+ EXPECT_EQ(cache_size / 10,
+ WriteData(entry2, 0, 0, buffer.get(), cache_size / 10, false));
+ entry2->Close(); // This will trigger the cache trim.
+
+ EXPECT_NE(net::OK, OpenEntry(first, &entry2));
+
+ FlushQueueForTest(); // Make sure that we are done trimming the cache.
+ FlushQueueForTest(); // We may have posted two tasks to evict stuff.
+
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(second, &entry));
+ EXPECT_EQ(cache_size / 10, entry->GetDataSize(0));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, SetSize) {
+ BackendSetSize();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionSetSize) {
+ SetNewEviction();
+ BackendSetSize();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlySetSize) {
+ SetMemoryOnlyMode();
+ BackendSetSize();
+}
+
+void DiskCacheBackendTest::BackendLoad() {
+ InitCache();
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ disk_cache::Entry* entries[100];
+ for (int i = 0; i < 100; i++) {
+ std::string key = GenerateKey(true);
+ ASSERT_EQ(net::OK, CreateEntry(key, &entries[i]));
+ }
+ EXPECT_EQ(100, cache_->GetEntryCount());
+
+ for (int i = 0; i < 100; i++) {
+ int source1 = rand() % 100;
+ int source2 = rand() % 100;
+ disk_cache::Entry* temp = entries[source1];
+ entries[source1] = entries[source2];
+ entries[source2] = temp;
+ }
+
+ for (int i = 0; i < 100; i++) {
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, OpenEntry(entries[i]->GetKey(), &entry));
+ EXPECT_TRUE(entry == entries[i]);
+ entry->Close();
+ entries[i]->Doom();
+ entries[i]->Close();
+ }
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, Load) {
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionLoad) {
+ SetNewEviction();
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyLoad) {
+ SetMaxSize(0x100000);
+ SetMemoryOnlyMode();
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheLoad) {
+ SetCacheType(net::APP_CACHE);
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheLoad) {
+ SetCacheType(net::SHADER_CACHE);
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ BackendLoad();
+}
+
+// Tests the chaining of an entry to the current head.
+void DiskCacheBackendTest::BackendChain() {
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ InitCache();
+
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("The first key", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("The Second key", &entry));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, Chain) {
+ BackendChain();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionChain) {
+ SetNewEviction();
+ BackendChain();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheChain) {
+ SetCacheType(net::APP_CACHE);
+ BackendChain();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheChain) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendChain();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionTrim) {
+ SetNewEviction();
+ InitCache();
+
+ disk_cache::Entry* entry;
+ for (int i = 0; i < 100; i++) {
+ std::string name(base::StringPrintf("Key %d", i));
+ ASSERT_EQ(net::OK, CreateEntry(name, &entry));
+ entry->Close();
+ if (i < 90) {
+ // Entries 0 to 89 are in list 1; 90 to 99 are in list 0.
+ ASSERT_EQ(net::OK, OpenEntry(name, &entry));
+ entry->Close();
+ }
+ }
+
+ // The first eviction must come from list 1 (10% limit), the second must come
+ // from list 0.
+ TrimForTest(false);
+ EXPECT_NE(net::OK, OpenEntry("Key 0", &entry));
+ TrimForTest(false);
+ EXPECT_NE(net::OK, OpenEntry("Key 90", &entry));
+
+ // Double check that we still have the list tails.
+ ASSERT_EQ(net::OK, OpenEntry("Key 1", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry("Key 91", &entry));
+ entry->Close();
+}
+
+// Before looking for invalid entries, let's check a valid entry.
+void DiskCacheBackendTest::BackendValidEntry() {
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ memset(buffer1->data(), 0, kSize);
+ base::strlcpy(buffer1->data(), "And the data to save", kSize);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer1.get(), kSize, false));
+ entry->Close();
+ SimulateCrash();
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ memset(buffer2->data(), 0, kSize);
+ EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer2.get(), kSize));
+ entry->Close();
+ EXPECT_STREQ(buffer1->data(), buffer2->data());
+}
+
+TEST_F(DiskCacheBackendTest, ValidEntry) {
+ BackendValidEntry();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionValidEntry) {
+ SetNewEviction();
+ BackendValidEntry();
+}
+
+// The same logic of the previous test (ValidEntry), but this time force the
+// entry to be invalid, simulating a crash in the middle.
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendInvalidEntry() {
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ memset(buffer->data(), 0, kSize);
+ base::strlcpy(buffer->data(), "And the data to save", kSize);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+ SimulateCrash();
+
+ EXPECT_NE(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+#if !defined(LEAK_SANITIZER)
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntry) {
+ BackendInvalidEntry();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry) {
+ SetNewEviction();
+ BackendInvalidEntry();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, AppCacheInvalidEntry) {
+ SetCacheType(net::APP_CACHE);
+ BackendInvalidEntry();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, ShaderCacheInvalidEntry) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendInvalidEntry();
+}
+
+// Almost the same test, but this time crash the cache after reading an entry.
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendInvalidEntryRead() {
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ memset(buffer->data(), 0, kSize);
+ base::strlcpy(buffer->data(), "And the data to save", kSize);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer.get(), kSize));
+
+ SimulateCrash();
+
+ if (type_ == net::APP_CACHE) {
+ // Reading an entry and crashing should not make it dirty.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ entry->Close();
+ } else {
+ EXPECT_NE(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ }
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryRead) {
+ BackendInvalidEntryRead();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntryRead) {
+ SetNewEviction();
+ BackendInvalidEntryRead();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, AppCacheInvalidEntryRead) {
+ SetCacheType(net::APP_CACHE);
+ BackendInvalidEntryRead();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, ShaderCacheInvalidEntryRead) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendInvalidEntryRead();
+}
+
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendInvalidEntryWithLoad() {
+ // Work with a tiny index table (16 entries)
+ SetMask(0xf);
+ SetMaxSize(0x100000);
+ InitCache();
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ const int kNumEntries = 100;
+ disk_cache::Entry* entries[kNumEntries];
+ for (int i = 0; i < kNumEntries; i++) {
+ std::string key = GenerateKey(true);
+ ASSERT_EQ(net::OK, CreateEntry(key, &entries[i]));
+ }
+ EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
+
+ for (int i = 0; i < kNumEntries; i++) {
+ int source1 = rand() % kNumEntries;
+ int source2 = rand() % kNumEntries;
+ disk_cache::Entry* temp = entries[source1];
+ entries[source1] = entries[source2];
+ entries[source2] = temp;
+ }
+
+ std::string keys[kNumEntries];
+ for (int i = 0; i < kNumEntries; i++) {
+ keys[i] = entries[i]->GetKey();
+ if (i < kNumEntries / 2)
+ entries[i]->Close();
+ }
+
+ SimulateCrash();
+
+ for (int i = kNumEntries / 2; i < kNumEntries; i++) {
+ disk_cache::Entry* entry;
+ EXPECT_NE(net::OK, OpenEntry(keys[i], &entry));
+ }
+
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, OpenEntry(keys[i], &entry));
+ entry->Close();
+ }
+
+ EXPECT_EQ(kNumEntries / 2, cache_->GetEntryCount());
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryWithLoad) {
+ BackendInvalidEntryWithLoad();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntryWithLoad) {
+ SetNewEviction();
+ BackendInvalidEntryWithLoad();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, AppCacheInvalidEntryWithLoad) {
+ SetCacheType(net::APP_CACHE);
+ BackendInvalidEntryWithLoad();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, ShaderCacheInvalidEntryWithLoad) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendInvalidEntryWithLoad();
+}
+
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendTrimInvalidEntry() {
+ const int kSize = 0x3000; // 12 kB
+ SetMaxSize(kSize * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ memset(buffer->data(), 0, kSize);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+
+ // Simulate a crash.
+ SimulateCrash();
+
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ SetMaxSize(kSize);
+ entry->Close(); // Trim the cache.
+ FlushQueueForTest();
+
+ // If we evicted the entry in less than 20mS, we have one entry in the cache;
+ // if it took more than that, we posted a task and we'll delete the second
+ // entry too.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // This may be not thread-safe in general, but for now it's OK so add some
+ // ThreadSanitizer annotations to ignore data races on cache_.
+ // See http://crbug.com/55970
+ ANNOTATE_IGNORE_READS_BEGIN();
+ EXPECT_GE(1, cache_->GetEntryCount());
+ ANNOTATE_IGNORE_READS_END();
+
+ EXPECT_NE(net::OK, OpenEntry(first, &entry));
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry) {
+ BackendTrimInvalidEntry();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionTrimInvalidEntry) {
+ SetNewEviction();
+ BackendTrimInvalidEntry();
+}
+
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendTrimInvalidEntry2() {
+ SetMask(0xf); // 16-entry table.
+
+ const int kSize = 0x3000; // 12 kB
+ SetMaxSize(kSize * 40);
+ InitCache();
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ memset(buffer->data(), 0, kSize);
+ disk_cache::Entry* entry;
+
+ // Writing 32 entries to this cache chains most of them.
+ for (int i = 0; i < 32; i++) {
+ std::string key(base::StringPrintf("some key %d", i));
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ // Note that we are not closing the entries.
+ }
+
+ // Simulate a crash.
+ SimulateCrash();
+
+ ASSERT_EQ(net::OK, CreateEntry("Something else", &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+
+ FlushQueueForTest();
+ EXPECT_EQ(33, cache_->GetEntryCount());
+ SetMaxSize(kSize);
+
+ // For the new eviction code, all corrupt entries are on the second list so
+ // they are not going away that easy.
+ if (new_eviction_) {
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ }
+
+ entry->Close(); // Trim the cache.
+ FlushQueueForTest();
+
+ // We may abort the eviction before cleaning up everything.
+ base::MessageLoop::current()->RunUntilIdle();
+ FlushQueueForTest();
+ // If it's not clear enough: we may still have eviction tasks running at this
+ // time, so the number of entries is changing while we read it.
+ ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN();
+ EXPECT_GE(30, cache_->GetEntryCount());
+ ANNOTATE_IGNORE_READS_AND_WRITES_END();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry2) {
+ BackendTrimInvalidEntry2();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionTrimInvalidEntry2) {
+ SetNewEviction();
+ BackendTrimInvalidEntry2();
+}
+#endif // !defined(LEAK_SANITIZER)
+
+void DiskCacheBackendTest::BackendEnumerations() {
+ InitCache();
+ Time initial = Time::Now();
+
+ const int kNumEntries = 100;
+ for (int i = 0; i < kNumEntries; i++) {
+ std::string key = GenerateKey(true);
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Close();
+ }
+ EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
+ Time final = Time::Now();
+
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ int count = 0;
+ Time last_modified[kNumEntries];
+ Time last_used[kNumEntries];
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(NULL != entry);
+ if (count < kNumEntries) {
+ last_modified[count] = entry->GetLastModified();
+ last_used[count] = entry->GetLastUsed();
+ EXPECT_TRUE(initial <= last_modified[count]);
+ EXPECT_TRUE(final >= last_modified[count]);
+ }
+
+ entry->Close();
+ count++;
+ };
+ EXPECT_EQ(kNumEntries, count);
+
+ iter = NULL;
+ count = 0;
+ // The previous enumeration should not have changed the timestamps.
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(NULL != entry);
+ if (count < kNumEntries) {
+ EXPECT_TRUE(last_modified[count] == entry->GetLastModified());
+ EXPECT_TRUE(last_used[count] == entry->GetLastUsed());
+ }
+ entry->Close();
+ count++;
+ };
+ EXPECT_EQ(kNumEntries, count);
+}
+
+TEST_F(DiskCacheBackendTest, Enumerations) {
+ BackendEnumerations();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionEnumerations) {
+ SetNewEviction();
+ BackendEnumerations();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyEnumerations) {
+ SetMemoryOnlyMode();
+ BackendEnumerations();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheEnumerations) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendEnumerations();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheEnumerations) {
+ SetCacheType(net::APP_CACHE);
+ BackendEnumerations();
+}
+
+// Verifies enumerations while entries are open.
+void DiskCacheBackendTest::BackendEnumerations2() {
+ InitCache();
+ const std::string first("first");
+ const std::string second("second");
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry1));
+ entry1->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry2));
+ entry2->Close();
+ FlushQueueForTest();
+
+ // Make sure that the timestamp is not the same.
+ AddDelay();
+ ASSERT_EQ(net::OK, OpenEntry(second, &entry1));
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
+ EXPECT_EQ(entry2->GetKey(), second);
+
+ // Two entries and the iterator pointing at "first".
+ entry1->Close();
+ entry2->Close();
+
+ // The iterator should still be valid, so we should not crash.
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
+ EXPECT_EQ(entry2->GetKey(), first);
+ entry2->Close();
+ cache_->EndEnumeration(&iter);
+
+ // Modify the oldest entry and get the newest element.
+ ASSERT_EQ(net::OK, OpenEntry(first, &entry1));
+ EXPECT_EQ(0, WriteData(entry1, 0, 200, NULL, 0, false));
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
+ if (type_ == net::APP_CACHE) {
+ // The list is not updated.
+ EXPECT_EQ(entry2->GetKey(), second);
+ } else {
+ EXPECT_EQ(entry2->GetKey(), first);
+ }
+
+ entry1->Close();
+ entry2->Close();
+ cache_->EndEnumeration(&iter);
+}
+
+TEST_F(DiskCacheBackendTest, Enumerations2) {
+ BackendEnumerations2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionEnumerations2) {
+ SetNewEviction();
+ BackendEnumerations2();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyEnumerations2) {
+ SetMemoryOnlyMode();
+ BackendEnumerations2();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheEnumerations2) {
+ SetCacheType(net::APP_CACHE);
+ BackendEnumerations2();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheEnumerations2) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendEnumerations2();
+}
+
+// Verify that ReadData calls do not update the LRU cache
+// when using the SHADER_CACHE type.
+TEST_F(DiskCacheBackendTest, ShaderCacheEnumerationReadData) {
+ SetCacheType(net::SHADER_CACHE);
+ InitCache();
+ const std::string first("first");
+ const std::string second("second");
+ disk_cache::Entry *entry1, *entry2;
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry1));
+ memset(buffer1->data(), 0, kSize);
+ base::strlcpy(buffer1->data(), "And the data to save", kSize);
+ EXPECT_EQ(kSize, WriteData(entry1, 0, 0, buffer1.get(), kSize, false));
+
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry2));
+ entry2->Close();
+
+ FlushQueueForTest();
+
+ // Make sure that the timestamp is not the same.
+ AddDelay();
+
+ // Read from the last item in the LRU.
+ EXPECT_EQ(kSize, ReadData(entry1, 0, 0, buffer1.get(), kSize));
+ entry1->Close();
+
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry2));
+ EXPECT_EQ(entry2->GetKey(), second);
+ entry2->Close();
+ cache_->EndEnumeration(&iter);
+}
+
+#if !defined(LEAK_SANITIZER)
+// Verify handling of invalid entries while doing enumerations.
+// We'll be leaking memory from this test.
+void DiskCacheBackendTest::BackendInvalidEntryEnumeration() {
+ InitCache();
+
+ std::string key("Some key");
+ disk_cache::Entry *entry, *entry1, *entry2;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
+
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ memset(buffer1->data(), 0, kSize);
+ base::strlcpy(buffer1->data(), "And the data to save", kSize);
+ EXPECT_EQ(kSize, WriteData(entry1, 0, 0, buffer1.get(), kSize, false));
+ entry1->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry1));
+ EXPECT_EQ(kSize, ReadData(entry1, 0, 0, buffer1.get(), kSize));
+
+ std::string key2("Another key");
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
+ entry2->Close();
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ SimulateCrash();
+
+ void* iter = NULL;
+ int count = 0;
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(NULL != entry);
+ EXPECT_EQ(key2, entry->GetKey());
+ entry->Close();
+ count++;
+ };
+ EXPECT_EQ(1, count);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, InvalidEntryEnumeration) {
+ BackendInvalidEntryEnumeration();
+}
+
+// We'll be leaking memory from this test.
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntryEnumeration) {
+ SetNewEviction();
+ BackendInvalidEntryEnumeration();
+}
+#endif // !defined(LEAK_SANITIZER)
+
+// Tests that if for some reason entries are modified close to existing cache
+// iterators, we don't generate fatal errors or reset the cache.
+void DiskCacheBackendTest::BackendFixEnumerators() {
+ InitCache();
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ const int kNumEntries = 10;
+ for (int i = 0; i < kNumEntries; i++) {
+ std::string key = GenerateKey(true);
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Close();
+ }
+ EXPECT_EQ(kNumEntries, cache_->GetEntryCount());
+
+ disk_cache::Entry *entry1, *entry2;
+ void* iter1 = NULL;
+ void* iter2 = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter1, &entry1));
+ ASSERT_TRUE(NULL != entry1);
+ entry1->Close();
+ entry1 = NULL;
+
+ // Let's go to the middle of the list.
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ if (entry1)
+ entry1->Close();
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter1, &entry1));
+ ASSERT_TRUE(NULL != entry1);
+
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
+ ASSERT_TRUE(NULL != entry2);
+ entry2->Close();
+ }
+
+ // Messing up with entry1 will modify entry2->next.
+ entry1->Doom();
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
+ ASSERT_TRUE(NULL != entry2);
+
+ // The link entry2->entry1 should be broken.
+ EXPECT_NE(entry2->GetKey(), entry1->GetKey());
+ entry1->Close();
+ entry2->Close();
+
+ // And the second iterator should keep working.
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter2, &entry2));
+ ASSERT_TRUE(NULL != entry2);
+ entry2->Close();
+
+ cache_->EndEnumeration(&iter1);
+ cache_->EndEnumeration(&iter2);
+}
+
+TEST_F(DiskCacheBackendTest, FixEnumerators) {
+ BackendFixEnumerators();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionFixEnumerators) {
+ SetNewEviction();
+ BackendFixEnumerators();
+}
+
+void DiskCacheBackendTest::BackendDoomRecent() {
+ InitCache();
+
+ disk_cache::Entry *entry;
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ AddDelay();
+ Time middle = Time::Now();
+
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ AddDelay();
+ Time final = Time::Now();
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_EQ(net::OK, DoomEntriesSince(final));
+ ASSERT_EQ(4, cache_->GetEntryCount());
+
+ EXPECT_EQ(net::OK, DoomEntriesSince(middle));
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ ASSERT_EQ(net::OK, OpenEntry("second", &entry));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, DoomRecent) {
+ BackendDoomRecent();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDoomRecent) {
+ SetNewEviction();
+ BackendDoomRecent();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomRecent) {
+ SetMemoryOnlyMode();
+ BackendDoomRecent();
+}
+
+void DiskCacheBackendTest::InitSparseCache(base::Time* doomed_start,
+ base::Time* doomed_end) {
+ InitCache();
+
+ const int kSize = 50;
+ // This must be greater then MemEntryImpl::kMaxSparseEntrySize.
+ const int kOffset = 10 + 1024 * 1024;
+
+ disk_cache::Entry* entry0 = NULL;
+ disk_cache::Entry* entry1 = NULL;
+ disk_cache::Entry* entry2 = NULL;
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+
+ ASSERT_EQ(net::OK, CreateEntry("zeroth", &entry0));
+ ASSERT_EQ(kSize, WriteSparseData(entry0, 0, buffer.get(), kSize));
+ ASSERT_EQ(kSize,
+ WriteSparseData(entry0, kOffset + kSize, buffer.get(), kSize));
+ entry0->Close();
+
+ FlushQueueForTest();
+ AddDelay();
+ if (doomed_start)
+ *doomed_start = base::Time::Now();
+
+ // Order in rankings list:
+ // first_part1, first_part2, second_part1, second_part2
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry1));
+ ASSERT_EQ(kSize, WriteSparseData(entry1, 0, buffer.get(), kSize));
+ ASSERT_EQ(kSize,
+ WriteSparseData(entry1, kOffset + kSize, buffer.get(), kSize));
+ entry1->Close();
+
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry2));
+ ASSERT_EQ(kSize, WriteSparseData(entry2, 0, buffer.get(), kSize));
+ ASSERT_EQ(kSize,
+ WriteSparseData(entry2, kOffset + kSize, buffer.get(), kSize));
+ entry2->Close();
+
+ FlushQueueForTest();
+ AddDelay();
+ if (doomed_end)
+ *doomed_end = base::Time::Now();
+
+ // Order in rankings list:
+ // third_part1, fourth_part1, third_part2, fourth_part2
+ disk_cache::Entry* entry3 = NULL;
+ disk_cache::Entry* entry4 = NULL;
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry3));
+ ASSERT_EQ(kSize, WriteSparseData(entry3, 0, buffer.get(), kSize));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry4));
+ ASSERT_EQ(kSize, WriteSparseData(entry4, 0, buffer.get(), kSize));
+ ASSERT_EQ(kSize,
+ WriteSparseData(entry3, kOffset + kSize, buffer.get(), kSize));
+ ASSERT_EQ(kSize,
+ WriteSparseData(entry4, kOffset + kSize, buffer.get(), kSize));
+ entry3->Close();
+ entry4->Close();
+
+ FlushQueueForTest();
+ AddDelay();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomEntriesSinceSparse) {
+ SetMemoryOnlyMode();
+ base::Time start;
+ InitSparseCache(&start, NULL);
+ DoomEntriesSince(start);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DoomEntriesSinceSparse) {
+ base::Time start;
+ InitSparseCache(&start, NULL);
+ DoomEntriesSince(start);
+ // NOTE: BackendImpl counts child entries in its GetEntryCount(), while
+ // MemBackendImpl does not. Thats why expected value differs here from
+ // MemoryOnlyDoomEntriesSinceSparse.
+ EXPECT_EQ(3, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomAllSparse) {
+ SetMemoryOnlyMode();
+ InitSparseCache(NULL, NULL);
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DoomAllSparse) {
+ InitSparseCache(NULL, NULL);
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+void DiskCacheBackendTest::BackendDoomBetween() {
+ InitCache();
+
+ disk_cache::Entry *entry;
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ AddDelay();
+ Time middle_start = Time::Now();
+
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ AddDelay();
+ Time middle_end = Time::Now();
+
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry("fourth", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ AddDelay();
+ Time final = Time::Now();
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_EQ(net::OK, DoomEntriesBetween(middle_start, middle_end));
+ ASSERT_EQ(2, cache_->GetEntryCount());
+
+ ASSERT_EQ(net::OK, OpenEntry("fourth", &entry));
+ entry->Close();
+
+ EXPECT_EQ(net::OK, DoomEntriesBetween(middle_start, final));
+ ASSERT_EQ(1, cache_->GetEntryCount());
+
+ ASSERT_EQ(net::OK, OpenEntry("first", &entry));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, DoomBetween) {
+ BackendDoomBetween();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDoomBetween) {
+ SetNewEviction();
+ BackendDoomBetween();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomBetween) {
+ SetMemoryOnlyMode();
+ BackendDoomBetween();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomEntriesBetweenSparse) {
+ SetMemoryOnlyMode();
+ base::Time start, end;
+ InitSparseCache(&start, &end);
+ DoomEntriesBetween(start, end);
+ EXPECT_EQ(3, cache_->GetEntryCount());
+
+ start = end;
+ end = base::Time::Now();
+ DoomEntriesBetween(start, end);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DoomEntriesBetweenSparse) {
+ base::Time start, end;
+ InitSparseCache(&start, &end);
+ DoomEntriesBetween(start, end);
+ EXPECT_EQ(9, cache_->GetEntryCount());
+
+ start = end;
+ end = base::Time::Now();
+ DoomEntriesBetween(start, end);
+ EXPECT_EQ(3, cache_->GetEntryCount());
+}
+
+void DiskCacheBackendTest::BackendTransaction(const std::string& name,
+ int num_entries, bool load) {
+ success_ = false;
+ ASSERT_TRUE(CopyTestCache(name));
+ DisableFirstCleanup();
+
+ uint32 mask;
+ if (load) {
+ mask = 0xf;
+ SetMaxSize(0x100000);
+ } else {
+ // Clear the settings from the previous run.
+ mask = 0;
+ SetMaxSize(0);
+ }
+ SetMask(mask);
+
+ InitCache();
+ ASSERT_EQ(num_entries + 1, cache_->GetEntryCount());
+
+ std::string key("the first key");
+ disk_cache::Entry* entry1;
+ ASSERT_NE(net::OK, OpenEntry(key, &entry1));
+
+ int actual = cache_->GetEntryCount();
+ if (num_entries != actual) {
+ ASSERT_TRUE(load);
+ // If there is a heavy load, inserting an entry will make another entry
+ // dirty (on the hash bucket) so two entries are removed.
+ ASSERT_EQ(num_entries - 1, actual);
+ }
+
+ cache_.reset();
+ cache_impl_ = NULL;
+
+ ASSERT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask));
+ success_ = true;
+}
+
+void DiskCacheBackendTest::BackendRecoverInsert() {
+ // Tests with an empty cache.
+ BackendTransaction("insert_empty1", 0, false);
+ ASSERT_TRUE(success_) << "insert_empty1";
+ BackendTransaction("insert_empty2", 0, false);
+ ASSERT_TRUE(success_) << "insert_empty2";
+ BackendTransaction("insert_empty3", 0, false);
+ ASSERT_TRUE(success_) << "insert_empty3";
+
+ // Tests with one entry on the cache.
+ BackendTransaction("insert_one1", 1, false);
+ ASSERT_TRUE(success_) << "insert_one1";
+ BackendTransaction("insert_one2", 1, false);
+ ASSERT_TRUE(success_) << "insert_one2";
+ BackendTransaction("insert_one3", 1, false);
+ ASSERT_TRUE(success_) << "insert_one3";
+
+ // Tests with one hundred entries on the cache, tiny index.
+ BackendTransaction("insert_load1", 100, true);
+ ASSERT_TRUE(success_) << "insert_load1";
+ BackendTransaction("insert_load2", 100, true);
+ ASSERT_TRUE(success_) << "insert_load2";
+}
+
+TEST_F(DiskCacheBackendTest, RecoverInsert) {
+ BackendRecoverInsert();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionRecoverInsert) {
+ SetNewEviction();
+ BackendRecoverInsert();
+}
+
+void DiskCacheBackendTest::BackendRecoverRemove() {
+ // Removing the only element.
+ BackendTransaction("remove_one1", 0, false);
+ ASSERT_TRUE(success_) << "remove_one1";
+ BackendTransaction("remove_one2", 0, false);
+ ASSERT_TRUE(success_) << "remove_one2";
+ BackendTransaction("remove_one3", 0, false);
+ ASSERT_TRUE(success_) << "remove_one3";
+
+ // Removing the head.
+ BackendTransaction("remove_head1", 1, false);
+ ASSERT_TRUE(success_) << "remove_head1";
+ BackendTransaction("remove_head2", 1, false);
+ ASSERT_TRUE(success_) << "remove_head2";
+ BackendTransaction("remove_head3", 1, false);
+ ASSERT_TRUE(success_) << "remove_head3";
+
+ // Removing the tail.
+ BackendTransaction("remove_tail1", 1, false);
+ ASSERT_TRUE(success_) << "remove_tail1";
+ BackendTransaction("remove_tail2", 1, false);
+ ASSERT_TRUE(success_) << "remove_tail2";
+ BackendTransaction("remove_tail3", 1, false);
+ ASSERT_TRUE(success_) << "remove_tail3";
+
+ // Removing with one hundred entries on the cache, tiny index.
+ BackendTransaction("remove_load1", 100, true);
+ ASSERT_TRUE(success_) << "remove_load1";
+ BackendTransaction("remove_load2", 100, true);
+ ASSERT_TRUE(success_) << "remove_load2";
+ BackendTransaction("remove_load3", 100, true);
+ ASSERT_TRUE(success_) << "remove_load3";
+
+ // This case cannot be reverted.
+ BackendTransaction("remove_one4", 0, false);
+ ASSERT_TRUE(success_) << "remove_one4";
+ BackendTransaction("remove_head4", 1, false);
+ ASSERT_TRUE(success_) << "remove_head4";
+}
+
+TEST_F(DiskCacheBackendTest, RecoverRemove) {
+ BackendRecoverRemove();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionRecoverRemove) {
+ SetNewEviction();
+ BackendRecoverRemove();
+}
+
+void DiskCacheBackendTest::BackendRecoverWithEviction() {
+ success_ = false;
+ ASSERT_TRUE(CopyTestCache("insert_load1"));
+ DisableFirstCleanup();
+
+ SetMask(0xf);
+ SetMaxSize(0x1000);
+
+ // We should not crash here.
+ InitCache();
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, RecoverWithEviction) {
+ BackendRecoverWithEviction();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionRecoverWithEviction) {
+ SetNewEviction();
+ BackendRecoverWithEviction();
+}
+
+// Tests that the |BackendImpl| fails to start with the wrong cache version.
+TEST_F(DiskCacheTest, WrongVersion) {
+ ASSERT_TRUE(CopyTestCache("wrong_version"));
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ net::TestCompletionCallback cb;
+
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ cache_path_, cache_thread.message_loop_proxy().get(), NULL));
+ int rv = cache->Init(cb.callback());
+ ASSERT_EQ(net::ERR_FAILED, cb.GetResult(rv));
+}
+
+class BadEntropyProvider : public base::FieldTrial::EntropyProvider {
+ public:
+ virtual ~BadEntropyProvider() {}
+
+ virtual double GetEntropyForTrial(const std::string& trial_name,
+ uint32 randomization_seed) const OVERRIDE {
+ return 0.5;
+ }
+};
+
+// Tests that the disk cache successfully joins the control group, dropping the
+// existing cache in favour of a new empty cache.
+TEST_F(DiskCacheTest, SimpleCacheControlJoin) {
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ scoped_ptr<disk_cache::BackendImpl> cache =
+ CreateExistingEntryCache(cache_thread, cache_path_);
+ ASSERT_TRUE(cache.get());
+ cache.reset();
+
+ // Instantiate the SimpleCacheTrial, forcing this run into the
+ // ExperimentControl group.
+ base::FieldTrialList field_trial_list(new BadEntropyProvider());
+ base::FieldTrialList::CreateFieldTrial("SimpleCacheTrial",
+ "ExperimentControl");
+ net::TestCompletionCallback cb;
+ scoped_ptr<disk_cache::Backend> base_cache;
+ int rv =
+ disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_BLOCKFILE,
+ cache_path_,
+ 0,
+ true,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &base_cache,
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_EQ(0, base_cache->GetEntryCount());
+}
+
+// Tests that the disk cache can restart in the control group preserving
+// existing entries.
+TEST_F(DiskCacheTest, SimpleCacheControlRestart) {
+ // Instantiate the SimpleCacheTrial, forcing this run into the
+ // ExperimentControl group.
+ base::FieldTrialList field_trial_list(new BadEntropyProvider());
+ base::FieldTrialList::CreateFieldTrial("SimpleCacheTrial",
+ "ExperimentControl");
+
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ scoped_ptr<disk_cache::BackendImpl> cache =
+ CreateExistingEntryCache(cache_thread, cache_path_);
+ ASSERT_TRUE(cache.get());
+
+ net::TestCompletionCallback cb;
+
+ const int kRestartCount = 5;
+ for (int i=0; i < kRestartCount; ++i) {
+ cache.reset(new disk_cache::BackendImpl(
+ cache_path_, cache_thread.message_loop_proxy(), NULL));
+ int rv = cache->Init(cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_EQ(1, cache->GetEntryCount());
+
+ disk_cache::Entry* entry = NULL;
+ rv = cache->OpenEntry(kExistingEntryKey, &entry, cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_TRUE(entry);
+ entry->Close();
+ }
+}
+
+// Tests that the disk cache can leave the control group preserving existing
+// entries.
+TEST_F(DiskCacheTest, SimpleCacheControlLeave) {
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ {
+ // Instantiate the SimpleCacheTrial, forcing this run into the
+ // ExperimentControl group.
+ base::FieldTrialList field_trial_list(new BadEntropyProvider());
+ base::FieldTrialList::CreateFieldTrial("SimpleCacheTrial",
+ "ExperimentControl");
+
+ scoped_ptr<disk_cache::BackendImpl> cache =
+ CreateExistingEntryCache(cache_thread, cache_path_);
+ ASSERT_TRUE(cache.get());
+ }
+
+ // Instantiate the SimpleCacheTrial, forcing this run into the
+ // ExperimentNo group.
+ base::FieldTrialList field_trial_list(new BadEntropyProvider());
+ base::FieldTrialList::CreateFieldTrial("SimpleCacheTrial", "ExperimentNo");
+ net::TestCompletionCallback cb;
+
+ const int kRestartCount = 5;
+ for (int i = 0; i < kRestartCount; ++i) {
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ cache_path_, cache_thread.message_loop_proxy(), NULL));
+ int rv = cache->Init(cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_EQ(1, cache->GetEntryCount());
+
+ disk_cache::Entry* entry = NULL;
+ rv = cache->OpenEntry(kExistingEntryKey, &entry, cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+ EXPECT_TRUE(entry);
+ entry->Close();
+ }
+}
+
+// Tests that the cache is properly restarted on recovery error.
+TEST_F(DiskCacheBackendTest, DeleteOld) {
+ ASSERT_TRUE(CopyTestCache("wrong_version"));
+ SetNewEviction();
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ net::TestCompletionCallback cb;
+ bool prev = base::ThreadRestrictions::SetIOAllowed(false);
+ base::FilePath path(cache_path_);
+ int rv =
+ disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_BLOCKFILE,
+ path,
+ 0,
+ true,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &cache_,
+ cb.callback());
+ path.clear(); // Make sure path was captured by the previous call.
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ base::ThreadRestrictions::SetIOAllowed(prev);
+ cache_.reset();
+ EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_));
+}
+
+// We want to be able to deal with messed up entries on disk.
+void DiskCacheBackendTest::BackendInvalidEntry2() {
+ ASSERT_TRUE(CopyTestCache("bad_entry"));
+ DisableFirstCleanup();
+ InitCache();
+
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry1));
+ EXPECT_NE(net::OK, OpenEntry("some other key", &entry2));
+ entry1->Close();
+
+ // CheckCacheIntegrity will fail at this point.
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry2) {
+ BackendInvalidEntry2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry2) {
+ SetNewEviction();
+ BackendInvalidEntry2();
+}
+
+// Tests that we don't crash or hang when enumerating this cache.
+void DiskCacheBackendTest::BackendInvalidEntry3() {
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ entry->Close();
+ }
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry3) {
+ ASSERT_TRUE(CopyTestCache("dirty_entry3"));
+ BackendInvalidEntry3();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry3) {
+ ASSERT_TRUE(CopyTestCache("dirty_entry4"));
+ SetNewEviction();
+ BackendInvalidEntry3();
+ DisableIntegrityCheck();
+}
+
+// Test that we handle a dirty entry on the LRU list, already replaced with
+// the same key, and with hash collisions.
+TEST_F(DiskCacheBackendTest, InvalidEntry4) {
+ ASSERT_TRUE(CopyTestCache("dirty_entry3"));
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ TrimForTest(false);
+}
+
+// Test that we handle a dirty entry on the deleted list, already replaced with
+// the same key, and with hash collisions.
+TEST_F(DiskCacheBackendTest, InvalidEntry5) {
+ ASSERT_TRUE(CopyTestCache("dirty_entry4"));
+ SetNewEviction();
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ TrimDeletedListForTest(false);
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry6) {
+ ASSERT_TRUE(CopyTestCache("dirty_entry5"));
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ // There is a dirty entry (but marked as clean) at the end, pointing to a
+ // deleted entry through the hash collision list. We should not re-insert the
+ // deleted entry into the index table.
+
+ TrimForTest(false);
+ // The cache should be clean (as detected by CheckCacheIntegrity).
+}
+
+// Tests that we don't hang when there is a loop on the hash collision list.
+// The test cache could be a result of bug 69135.
+TEST_F(DiskCacheBackendTest, BadNextEntry1) {
+ ASSERT_TRUE(CopyTestCache("list_loop2"));
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ // The second entry points at itselft, and the first entry is not accessible
+ // though the index, but it is at the head of the LRU.
+
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("The first key", &entry));
+ entry->Close();
+
+ TrimForTest(false);
+ TrimForTest(false);
+ ASSERT_EQ(net::OK, OpenEntry("The first key", &entry));
+ entry->Close();
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+// Tests that we don't hang when there is a loop on the hash collision list.
+// The test cache could be a result of bug 69135.
+TEST_F(DiskCacheBackendTest, BadNextEntry2) {
+ ASSERT_TRUE(CopyTestCache("list_loop3"));
+ SetMask(0x1); // 2-entry table.
+ SetMaxSize(0x3000); // 12 kB.
+ DisableFirstCleanup();
+ InitCache();
+
+ // There is a wide loop of 5 entries.
+
+ disk_cache::Entry* entry;
+ ASSERT_NE(net::OK, OpenEntry("Not present key", &entry));
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry6) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings3"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+
+ // The second entry is dirty, but removing it should not corrupt the list.
+ disk_cache::Entry* entry;
+ ASSERT_NE(net::OK, OpenEntry("the second key", &entry));
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry));
+
+ // This should not delete the cache.
+ entry->Doom();
+ FlushQueueForTest();
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry("some other key", &entry));
+ entry->Close();
+}
+
+// Tests handling of corrupt entries by keeping the rankings node around, with
+// a fatal failure.
+void DiskCacheBackendTest::BackendInvalidEntry7() {
+ const int kSize = 0x3000; // 12 kB.
+ SetMaxSize(kSize * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->rankings()->Data()->next = 0;
+ entry_impl->rankings()->Store();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ // This should detect the bad entry.
+ EXPECT_NE(net::OK, OpenEntry(second, &entry));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+
+ // We should delete the cache. The list still has a corrupt node.
+ void* iter = NULL;
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry7) {
+ BackendInvalidEntry7();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry7) {
+ SetNewEviction();
+ BackendInvalidEntry7();
+}
+
+// Tests handling of corrupt entries by keeping the rankings node around, with
+// a non fatal failure.
+void DiskCacheBackendTest::BackendInvalidEntry8() {
+ const int kSize = 0x3000; // 12 kB
+ SetMaxSize(kSize * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->rankings()->Data()->contents = 0;
+ entry_impl->rankings()->Store();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ // This should detect the bad entry.
+ EXPECT_NE(net::OK, OpenEntry(second, &entry));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+
+ // We should not delete the cache.
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry8) {
+ BackendInvalidEntry8();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry8) {
+ SetNewEviction();
+ BackendInvalidEntry8();
+}
+
+// Tests handling of corrupt entries detected by enumerations. Note that these
+// tests (xx9 to xx11) are basically just going though slightly different
+// codepaths so they are tighlty coupled with the code, but that is better than
+// not testing error handling code.
+void DiskCacheBackendTest::BackendInvalidEntry9(bool eviction) {
+ const int kSize = 0x3000; // 12 kB.
+ SetMaxSize(kSize * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->entry()->Data()->state = 0xbad;
+ entry_impl->entry()->Store();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ if (eviction) {
+ TrimForTest(false);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ TrimForTest(false);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ } else {
+ // We should detect the problem through the list, but we should not delete
+ // the entry, just fail the iteration.
+ void* iter = NULL;
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+
+ // Now a full iteration will work, and return one entry.
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+
+ // This should detect what's left of the bad entry.
+ EXPECT_NE(net::OK, OpenEntry(second, &entry));
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ }
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry9) {
+ BackendInvalidEntry9(false);
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidEntry9) {
+ SetNewEviction();
+ BackendInvalidEntry9(false);
+}
+
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry9) {
+ BackendInvalidEntry9(true);
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionTrimInvalidEntry9) {
+ SetNewEviction();
+ BackendInvalidEntry9(true);
+}
+
+// Tests handling of corrupt entries detected by enumerations.
+void DiskCacheBackendTest::BackendInvalidEntry10(bool eviction) {
+ const int kSize = 0x3000; // 12 kB.
+ SetMaxSize(kSize * 10);
+ SetNewEviction();
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(first, &entry));
+ EXPECT_EQ(0, WriteData(entry, 0, 200, NULL, 0, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->entry()->Data()->state = 0xbad;
+ entry_impl->entry()->Store();
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
+ entry->Close();
+ EXPECT_EQ(3, cache_->GetEntryCount());
+
+ // We have:
+ // List 0: third -> second (bad).
+ // List 1: first.
+
+ if (eviction) {
+ // Detection order: second -> first -> third.
+ TrimForTest(false);
+ EXPECT_EQ(3, cache_->GetEntryCount());
+ TrimForTest(false);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ TrimForTest(false);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ } else {
+ // Detection order: third -> second -> first.
+ // We should detect the problem through the list, but we should not delete
+ // the entry.
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ EXPECT_EQ(first, entry->GetKey());
+ entry->Close();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ }
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry10) {
+ BackendInvalidEntry10(false);
+}
+
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry10) {
+ BackendInvalidEntry10(true);
+}
+
+// Tests handling of corrupt entries detected by enumerations.
+void DiskCacheBackendTest::BackendInvalidEntry11(bool eviction) {
+ const int kSize = 0x3000; // 12 kB.
+ SetMaxSize(kSize * 10);
+ SetNewEviction();
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(first, &entry));
+ EXPECT_EQ(0, WriteData(entry, 0, 200, NULL, 0, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(second, &entry));
+ EXPECT_EQ(0, WriteData(entry, 0, 200, NULL, 0, false));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->entry()->Data()->state = 0xbad;
+ entry_impl->entry()->Store();
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(3, cache_->GetEntryCount());
+
+ // We have:
+ // List 0: third.
+ // List 1: second (bad) -> first.
+
+ if (eviction) {
+ // Detection order: third -> first -> second.
+ TrimForTest(false);
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ TrimForTest(false);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ TrimForTest(false);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ } else {
+ // Detection order: third -> second.
+ // We should detect the problem through the list, but we should not delete
+ // the entry, just fail the iteration.
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+
+ // Now a full iteration will work, and return two entries.
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ }
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidEntry11) {
+ BackendInvalidEntry11(false);
+}
+
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry11) {
+ BackendInvalidEntry11(true);
+}
+
+// Tests handling of corrupt entries in the middle of a long eviction run.
+void DiskCacheBackendTest::BackendTrimInvalidEntry12() {
+ const int kSize = 0x3000; // 12 kB
+ SetMaxSize(kSize * 10);
+ InitCache();
+
+ std::string first("some key");
+ std::string second("something else");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(first, &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry(second, &entry));
+
+ // Corrupt this entry.
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+
+ entry_impl->entry()->Data()->state = 0xbad;
+ entry_impl->entry()->Store();
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry));
+ entry->Close();
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry));
+ TrimForTest(true);
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ entry->Close();
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, TrimInvalidEntry12) {
+ BackendTrimInvalidEntry12();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionTrimInvalidEntry12) {
+ SetNewEviction();
+ BackendTrimInvalidEntry12();
+}
+
+// We want to be able to deal with messed up entries on disk.
+void DiskCacheBackendTest::BackendInvalidRankings2() {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+
+ disk_cache::Entry *entry1, *entry2;
+ EXPECT_NE(net::OK, OpenEntry("the first key", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("some other key", &entry2));
+ entry2->Close();
+
+ // CheckCacheIntegrity will fail at this point.
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidRankings2) {
+ BackendInvalidRankings2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidRankings2) {
+ SetNewEviction();
+ BackendInvalidRankings2();
+}
+
+// If the LRU is corrupt, we delete the cache.
+void DiskCacheBackendTest::BackendInvalidRankings() {
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry));
+ entry->Close();
+ EXPECT_EQ(2, cache_->GetEntryCount());
+
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry));
+ FlushQueueForTest(); // Allow the restart to finish.
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, InvalidRankingsSuccess) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+ BackendInvalidRankings();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidRankingsSuccess) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ BackendInvalidRankings();
+}
+
+TEST_F(DiskCacheBackendTest, InvalidRankingsFailure) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendInvalidRankings();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionInvalidRankingsFailure) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendInvalidRankings();
+}
+
+// If the LRU is corrupt and we have open entries, we disable the cache.
+void DiskCacheBackendTest::BackendDisable() {
+ disk_cache::Entry *entry1, *entry2;
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
+
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry2));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ EXPECT_NE(net::OK, CreateEntry("Something new", &entry2));
+
+ entry1->Close();
+ FlushQueueForTest(); // Flushing the Close posts a task to restart the cache.
+ FlushQueueForTest(); // This one actually allows that task to complete.
+
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DisableSuccess) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+ BackendDisable();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ BackendDisable();
+}
+
+TEST_F(DiskCacheBackendTest, DisableFailure) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendDisable();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableFailure) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendDisable();
+}
+
+// This is another type of corruption on the LRU; disable the cache.
+void DiskCacheBackendTest::BackendDisable2() {
+ EXPECT_EQ(8, cache_->GetEntryCount());
+
+ disk_cache::Entry* entry;
+ void* iter = NULL;
+ int count = 0;
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(NULL != entry);
+ entry->Close();
+ count++;
+ ASSERT_LT(count, 9);
+ };
+
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DisableSuccess2) {
+ ASSERT_TRUE(CopyTestCache("list_loop"));
+ DisableFirstCleanup();
+ InitCache();
+ BackendDisable2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess2) {
+ ASSERT_TRUE(CopyTestCache("list_loop"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ BackendDisable2();
+}
+
+TEST_F(DiskCacheBackendTest, DisableFailure2) {
+ ASSERT_TRUE(CopyTestCache("list_loop"));
+ DisableFirstCleanup();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendDisable2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableFailure2) {
+ ASSERT_TRUE(CopyTestCache("list_loop"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ SetTestMode(); // Fail cache reinitialization.
+ BackendDisable2();
+}
+
+// If the index size changes when we disable the cache, we should not crash.
+void DiskCacheBackendTest::BackendDisable3() {
+ disk_cache::Entry *entry1, *entry2;
+ void* iter = NULL;
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
+ entry1->Close();
+
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry2));
+ FlushQueueForTest();
+
+ ASSERT_EQ(net::OK, CreateEntry("Something new", &entry2));
+ entry2->Close();
+
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DisableSuccess3) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
+ DisableFirstCleanup();
+ SetMaxSize(20 * 1024 * 1024);
+ InitCache();
+ BackendDisable3();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess3) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
+ DisableFirstCleanup();
+ SetMaxSize(20 * 1024 * 1024);
+ SetNewEviction();
+ InitCache();
+ BackendDisable3();
+}
+
+// If we disable the cache, already open entries should work as far as possible.
+void DiskCacheBackendTest::BackendDisable4() {
+ disk_cache::Entry *entry1, *entry2, *entry3, *entry4;
+ void* iter = NULL;
+ ASSERT_EQ(net::OK, OpenNextEntry(&iter, &entry1));
+
+ char key2[2000];
+ char key3[20000];
+ CacheTestFillBuffer(key2, sizeof(key2), true);
+ CacheTestFillBuffer(key3, sizeof(key3), true);
+ key2[sizeof(key2) - 1] = '\0';
+ key3[sizeof(key3) - 1] = '\0';
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
+ ASSERT_EQ(net::OK, CreateEntry(key3, &entry3));
+
+ const int kBufSize = 20000;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufSize));
+ memset(buf->data(), 0, kBufSize);
+ EXPECT_EQ(100, WriteData(entry2, 0, 0, buf.get(), 100, false));
+ EXPECT_EQ(kBufSize, WriteData(entry3, 0, 0, buf.get(), kBufSize, false));
+
+ // This line should disable the cache but not delete it.
+ EXPECT_NE(net::OK, OpenNextEntry(&iter, &entry4));
+ EXPECT_EQ(0, cache_->GetEntryCount());
+
+ EXPECT_NE(net::OK, CreateEntry("cache is disabled", &entry4));
+
+ EXPECT_EQ(100, ReadData(entry2, 0, 0, buf.get(), 100));
+ EXPECT_EQ(100, WriteData(entry2, 0, 0, buf.get(), 100, false));
+ EXPECT_EQ(100, WriteData(entry2, 1, 0, buf.get(), 100, false));
+
+ EXPECT_EQ(kBufSize, ReadData(entry3, 0, 0, buf.get(), kBufSize));
+ EXPECT_EQ(kBufSize, WriteData(entry3, 0, 0, buf.get(), kBufSize, false));
+ EXPECT_EQ(kBufSize, WriteData(entry3, 1, 0, buf.get(), kBufSize, false));
+
+ std::string key = entry2->GetKey();
+ EXPECT_EQ(sizeof(key2) - 1, key.size());
+ key = entry3->GetKey();
+ EXPECT_EQ(sizeof(key3) - 1, key.size());
+
+ entry1->Close();
+ entry2->Close();
+ entry3->Close();
+ FlushQueueForTest(); // Flushing the Close posts a task to restart the cache.
+ FlushQueueForTest(); // This one actually allows that task to complete.
+
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DisableSuccess4) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ InitCache();
+ BackendDisable4();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDisableSuccess4) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings"));
+ DisableFirstCleanup();
+ SetNewEviction();
+ InitCache();
+ BackendDisable4();
+}
+
+TEST_F(DiskCacheTest, Backend_UsageStatsTimer) {
+ MessageLoopHelper helper;
+
+ ASSERT_TRUE(CleanupCacheDir());
+ scoped_ptr<disk_cache::BackendImpl> cache;
+ cache.reset(new disk_cache::BackendImpl(
+ cache_path_, base::MessageLoopProxy::current().get(), NULL));
+ ASSERT_TRUE(NULL != cache.get());
+ cache->SetUnitTestMode();
+ ASSERT_EQ(net::OK, cache->SyncInit());
+
+ // Wait for a callback that never comes... about 2 secs :). The message loop
+ // has to run to allow invocation of the usage timer.
+ helper.WaitUntilCacheIoFinished(1);
+}
+
+TEST_F(DiskCacheBackendTest, Backend_UsageStats) {
+ InitCache();
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
+ entry->Close();
+ FlushQueueForTest();
+
+ disk_cache::StatsItems stats;
+ cache_->GetStats(&stats);
+ EXPECT_FALSE(stats.empty());
+
+ disk_cache::StatsItems::value_type hits("Create hit", "0x1");
+ EXPECT_EQ(1, std::count(stats.begin(), stats.end(), hits));
+
+ cache_.reset();
+
+ // Now open the cache and verify that the stats are still there.
+ DisableFirstCleanup();
+ InitCache();
+ EXPECT_EQ(1, cache_->GetEntryCount());
+
+ stats.clear();
+ cache_->GetStats(&stats);
+ EXPECT_FALSE(stats.empty());
+
+ EXPECT_EQ(1, std::count(stats.begin(), stats.end(), hits));
+}
+
+void DiskCacheBackendTest::BackendDoomAll() {
+ InitCache();
+
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_EQ(net::OK, CreateEntry("first", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("second", &entry2));
+ entry1->Close();
+ entry2->Close();
+
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry2));
+
+ ASSERT_EQ(4, cache_->GetEntryCount());
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ ASSERT_EQ(0, cache_->GetEntryCount());
+
+ // We should stop posting tasks at some point (if we post any).
+ base::MessageLoop::current()->RunUntilIdle();
+
+ disk_cache::Entry *entry3, *entry4;
+ EXPECT_NE(net::OK, OpenEntry("third", &entry3));
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry3));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry4));
+
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ ASSERT_EQ(0, cache_->GetEntryCount());
+
+ entry1->Close();
+ entry2->Close();
+ entry3->Doom(); // The entry should be already doomed, but this must work.
+ entry3->Close();
+ entry4->Close();
+
+ // Now try with all references released.
+ ASSERT_EQ(net::OK, CreateEntry("third", &entry1));
+ ASSERT_EQ(net::OK, CreateEntry("fourth", &entry2));
+ entry1->Close();
+ entry2->Close();
+
+ ASSERT_EQ(2, cache_->GetEntryCount());
+ EXPECT_EQ(net::OK, DoomAllEntries());
+ ASSERT_EQ(0, cache_->GetEntryCount());
+
+ EXPECT_EQ(net::OK, DoomAllEntries());
+}
+
+TEST_F(DiskCacheBackendTest, DoomAll) {
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDoomAll) {
+ SetNewEviction();
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, MemoryOnlyDoomAll) {
+ SetMemoryOnlyMode();
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, AppCacheOnlyDoomAll) {
+ SetCacheType(net::APP_CACHE);
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheOnlyDoomAll) {
+ SetCacheType(net::SHADER_CACHE);
+ BackendDoomAll();
+}
+
+// If the index size changes when we doom the cache, we should not crash.
+void DiskCacheBackendTest::BackendDoomAll2() {
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ EXPECT_EQ(net::OK, DoomAllEntries());
+
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("Something new", &entry));
+ entry->Close();
+
+ EXPECT_EQ(1, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheBackendTest, DoomAll2) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
+ DisableFirstCleanup();
+ SetMaxSize(20 * 1024 * 1024);
+ InitCache();
+ BackendDoomAll2();
+}
+
+TEST_F(DiskCacheBackendTest, NewEvictionDoomAll2) {
+ ASSERT_TRUE(CopyTestCache("bad_rankings2"));
+ DisableFirstCleanup();
+ SetMaxSize(20 * 1024 * 1024);
+ SetNewEviction();
+ InitCache();
+ BackendDoomAll2();
+}
+
+// We should be able to create the same entry on multiple simultaneous instances
+// of the cache.
+TEST_F(DiskCacheTest, MultipleInstances) {
+ base::ScopedTempDir store1, store2;
+ ASSERT_TRUE(store1.CreateUniqueTempDir());
+ ASSERT_TRUE(store2.CreateUniqueTempDir());
+
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ net::TestCompletionCallback cb;
+
+ const int kNumberOfCaches = 2;
+ scoped_ptr<disk_cache::Backend> cache[kNumberOfCaches];
+
+ int rv =
+ disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_DEFAULT,
+ store1.path(),
+ 0,
+ false,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &cache[0],
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ rv = disk_cache::CreateCacheBackend(net::MEDIA_CACHE,
+ net::CACHE_BACKEND_DEFAULT,
+ store2.path(),
+ 0,
+ false,
+ cache_thread.message_loop_proxy().get(),
+ NULL,
+ &cache[1],
+ cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ ASSERT_TRUE(cache[0].get() != NULL && cache[1].get() != NULL);
+
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ for (int i = 0; i < kNumberOfCaches; i++) {
+ rv = cache[i]->CreateEntry(key, &entry, cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ entry->Close();
+ }
+}
+
+// Test the six regions of the curve that determines the max cache size.
+TEST_F(DiskCacheTest, AutomaticMaxSize) {
+ const int kDefaultSize = 80 * 1024 * 1024;
+ int64 large_size = kDefaultSize;
+ int64 largest_size = kint32max;
+
+ // Region 1: expected = available * 0.8
+ EXPECT_EQ((kDefaultSize - 1) * 8 / 10,
+ disk_cache::PreferedCacheSize(large_size - 1));
+ EXPECT_EQ(kDefaultSize * 8 / 10,
+ disk_cache::PreferedCacheSize(large_size));
+ EXPECT_EQ(kDefaultSize - 1,
+ disk_cache::PreferedCacheSize(large_size * 10 / 8 - 1));
+
+ // Region 2: expected = default_size
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10 / 8));
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10 - 1));
+
+ // Region 3: expected = available * 0.1
+ EXPECT_EQ(kDefaultSize,
+ disk_cache::PreferedCacheSize(large_size * 10));
+ EXPECT_EQ((kDefaultSize * 25 - 1) / 10,
+ disk_cache::PreferedCacheSize(large_size * 25 - 1));
+
+ // Region 4: expected = default_size * 2.5
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 25));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 100 - 1));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 100));
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 250 - 1));
+
+ // Region 5: expected = available * 0.1
+ EXPECT_EQ(kDefaultSize * 25 / 10,
+ disk_cache::PreferedCacheSize(large_size * 250));
+ EXPECT_EQ(kint32max - 1,
+ disk_cache::PreferedCacheSize(largest_size * 100 - 1));
+
+ // Region 6: expected = kint32max
+ EXPECT_EQ(kint32max,
+ disk_cache::PreferedCacheSize(largest_size * 100));
+ EXPECT_EQ(kint32max,
+ disk_cache::PreferedCacheSize(largest_size * 10000));
+}
+
+// Tests that we can "migrate" a running instance from one experiment group to
+// another.
+TEST_F(DiskCacheBackendTest, Histograms) {
+ InitCache();
+ disk_cache::BackendImpl* backend_ = cache_impl_; // Needed be the macro.
+
+ for (int i = 1; i < 3; i++) {
+ CACHE_UMA(HOURS, "FillupTime", i, 28);
+ }
+}
+
+// Make sure that we keep the total memory used by the internal buffers under
+// control.
+TEST_F(DiskCacheBackendTest, TotalBuffersSize1) {
+ InitCache();
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 200;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, true);
+
+ for (int i = 0; i < 10; i++) {
+ SCOPED_TRACE(i);
+ // Allocate 2MB for this entry.
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, true));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 0, buffer.get(), kSize, true));
+ EXPECT_EQ(kSize,
+ WriteData(entry, 0, 1024 * 1024, buffer.get(), kSize, false));
+ EXPECT_EQ(kSize,
+ WriteData(entry, 1, 1024 * 1024, buffer.get(), kSize, false));
+
+ // Delete one of the buffers and truncate the other.
+ EXPECT_EQ(0, WriteData(entry, 0, 0, buffer.get(), 0, true));
+ EXPECT_EQ(0, WriteData(entry, 1, 10, buffer.get(), 0, true));
+
+ // Delete the second buffer, writing 10 bytes to disk.
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ }
+
+ entry->Close();
+ EXPECT_EQ(0, cache_impl_->GetTotalBuffersSize());
+}
+
+// This test assumes at least 150MB of system memory.
+TEST_F(DiskCacheBackendTest, TotalBuffersSize2) {
+ InitCache();
+
+ const int kOneMB = 1024 * 1024;
+ EXPECT_TRUE(cache_impl_->IsAllocAllowed(0, kOneMB));
+ EXPECT_EQ(kOneMB, cache_impl_->GetTotalBuffersSize());
+
+ EXPECT_TRUE(cache_impl_->IsAllocAllowed(0, kOneMB));
+ EXPECT_EQ(kOneMB * 2, cache_impl_->GetTotalBuffersSize());
+
+ EXPECT_TRUE(cache_impl_->IsAllocAllowed(0, kOneMB));
+ EXPECT_EQ(kOneMB * 3, cache_impl_->GetTotalBuffersSize());
+
+ cache_impl_->BufferDeleted(kOneMB);
+ EXPECT_EQ(kOneMB * 2, cache_impl_->GetTotalBuffersSize());
+
+ // Check the upper limit.
+ EXPECT_FALSE(cache_impl_->IsAllocAllowed(0, 30 * kOneMB));
+
+ for (int i = 0; i < 30; i++)
+ cache_impl_->IsAllocAllowed(0, kOneMB); // Ignore the result.
+
+ EXPECT_FALSE(cache_impl_->IsAllocAllowed(0, kOneMB));
+}
+
+// Tests that sharing of external files works and we are able to delete the
+// files when we need to.
+TEST_F(DiskCacheBackendTest, FileSharing) {
+ InitCache();
+
+ disk_cache::Addr address(0x80000001);
+ ASSERT_TRUE(cache_impl_->CreateExternalFile(&address));
+ base::FilePath name = cache_impl_->GetFileName(address);
+
+ scoped_refptr<disk_cache::File> file(new disk_cache::File(false));
+ file->Init(name);
+
+#if defined(OS_WIN)
+ DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ DWORD access = GENERIC_READ | GENERIC_WRITE;
+ base::win::ScopedHandle file2(CreateFile(
+ name.value().c_str(), access, sharing, NULL, OPEN_EXISTING, 0, NULL));
+ EXPECT_FALSE(file2.IsValid());
+
+ sharing |= FILE_SHARE_DELETE;
+ file2.Set(CreateFile(name.value().c_str(), access, sharing, NULL,
+ OPEN_EXISTING, 0, NULL));
+ EXPECT_TRUE(file2.IsValid());
+#endif
+
+ EXPECT_TRUE(base::DeleteFile(name, false));
+
+ // We should be able to use the file.
+ const int kSize = 200;
+ char buffer1[kSize];
+ char buffer2[kSize];
+ memset(buffer1, 't', kSize);
+ memset(buffer2, 0, kSize);
+ EXPECT_TRUE(file->Write(buffer1, kSize, 0));
+ EXPECT_TRUE(file->Read(buffer2, kSize, 0));
+ EXPECT_EQ(0, memcmp(buffer1, buffer2, kSize));
+
+ EXPECT_TRUE(disk_cache::DeleteCacheFile(name));
+}
+
+TEST_F(DiskCacheBackendTest, UpdateRankForExternalCacheHit) {
+ InitCache();
+
+ disk_cache::Entry* entry;
+
+ for (int i = 0; i < 2; ++i) {
+ std::string key = base::StringPrintf("key%d", i);
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Close();
+ }
+
+ // Ping the oldest entry.
+ cache_->OnExternalCacheHit("key0");
+
+ TrimForTest(false);
+
+ // Make sure the older key remains.
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ ASSERT_EQ(net::OK, OpenEntry("key0", &entry));
+ entry->Close();
+}
+
+TEST_F(DiskCacheBackendTest, ShaderCacheUpdateRankForExternalCacheHit) {
+ SetCacheType(net::SHADER_CACHE);
+ InitCache();
+
+ disk_cache::Entry* entry;
+
+ for (int i = 0; i < 2; ++i) {
+ std::string key = base::StringPrintf("key%d", i);
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Close();
+ }
+
+ // Ping the oldest entry.
+ cache_->OnExternalCacheHit("key0");
+
+ TrimForTest(false);
+
+ // Make sure the older key remains.
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ ASSERT_EQ(net::OK, OpenEntry("key0", &entry));
+ entry->Close();
+}
+
+void DiskCacheBackendTest::TracingBackendBasics() {
+ InitCache();
+ cache_.reset(new disk_cache::TracingCacheBackend(cache_.Pass()));
+ cache_impl_ = NULL;
+ EXPECT_EQ(net::DISK_CACHE, cache_->GetCacheType());
+ if (!simple_cache_mode_) {
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ }
+
+ net::TestCompletionCallback cb;
+ disk_cache::Entry* entry = NULL;
+ EXPECT_NE(net::OK, OpenEntry("key", &entry));
+ EXPECT_TRUE(NULL == entry);
+
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
+ EXPECT_TRUE(NULL != entry);
+
+ disk_cache::Entry* same_entry = NULL;
+ ASSERT_EQ(net::OK, OpenEntry("key", &same_entry));
+ EXPECT_TRUE(NULL != same_entry);
+
+ if (!simple_cache_mode_) {
+ EXPECT_EQ(1, cache_->GetEntryCount());
+ }
+ entry->Close();
+ entry = NULL;
+ same_entry->Close();
+ same_entry = NULL;
+}
+
+TEST_F(DiskCacheBackendTest, TracingBackendBasics) {
+ TracingBackendBasics();
+}
+
+// The simple cache backend isn't intended to work on windows, which has very
+// different file system guarantees from Windows.
+#if !defined(OS_WIN)
+
+TEST_F(DiskCacheBackendTest, SimpleCacheBasics) {
+ SetSimpleCacheMode();
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheAppCacheBasics) {
+ SetCacheType(net::APP_CACHE);
+ SetSimpleCacheMode();
+ BackendBasics();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheKeying) {
+ SetSimpleCacheMode();
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheAppCacheKeying) {
+ SetSimpleCacheMode();
+ SetCacheType(net::APP_CACHE);
+ BackendKeying();
+}
+
+TEST_F(DiskCacheBackendTest, DISABLED_SimpleCacheSetSize) {
+ SetSimpleCacheMode();
+ BackendSetSize();
+}
+
+// MacOS has a default open file limit of 256 files, which is incompatible with
+// this simple cache test.
+#if defined(OS_MACOSX)
+#define SIMPLE_MAYBE_MACOS(TestName) DISABLED_ ## TestName
+#else
+#define SIMPLE_MAYBE_MACOS(TestName) TestName
+#endif
+
+TEST_F(DiskCacheBackendTest, SIMPLE_MAYBE_MACOS(SimpleCacheLoad)) {
+ SetMaxSize(0x100000);
+ SetSimpleCacheMode();
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, SIMPLE_MAYBE_MACOS(SimpleCacheAppCacheLoad)) {
+ SetCacheType(net::APP_CACHE);
+ SetSimpleCacheMode();
+ SetMaxSize(0x100000);
+ BackendLoad();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleDoomRecent) {
+ SetSimpleCacheMode();
+ BackendDoomRecent();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleDoomBetween) {
+ SetSimpleCacheMode();
+ BackendDoomBetween();
+}
+
+// See http://crbug.com/237450.
+TEST_F(DiskCacheBackendTest, FLAKY_SimpleCacheDoomAll) {
+ SetSimpleCacheMode();
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, FLAKY_SimpleCacheAppCacheOnlyDoomAll) {
+ SetCacheType(net::APP_CACHE);
+ SetSimpleCacheMode();
+ BackendDoomAll();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheTracingBackendBasics) {
+ SetSimpleCacheMode();
+ TracingBackendBasics();
+ // TODO(pasko): implement integrity checking on the Simple Backend.
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheOpenMissingFile) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char* key = "the first key";
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ ASSERT_TRUE(entry != NULL);
+ entry->Close();
+ entry = NULL;
+
+ // To make sure the file creation completed we need to call open again so that
+ // we block until it actually created the files.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ASSERT_TRUE(entry != NULL);
+ entry->Close();
+ entry = NULL;
+
+ // Delete one of the files in the entry.
+ base::FilePath to_delete_file = cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0));
+ EXPECT_TRUE(base::PathExists(to_delete_file));
+ EXPECT_TRUE(disk_cache::DeleteCacheFile(to_delete_file));
+
+ // Failing to open the entry should delete the rest of these files.
+ ASSERT_EQ(net::ERR_FAILED, OpenEntry(key, &entry));
+
+ // Confirm the rest of the files are gone.
+ for (int i = 1; i < disk_cache::kSimpleEntryFileCount; ++i) {
+ base::FilePath
+ should_be_gone_file(cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, i)));
+ EXPECT_FALSE(base::PathExists(should_be_gone_file));
+ }
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheOpenBadFile) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char* key = "the first key";
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ disk_cache::Entry* null = NULL;
+ ASSERT_NE(null, entry);
+ entry->Close();
+ entry = NULL;
+
+ // To make sure the file creation completed we need to call open again so that
+ // we block until it actually created the files.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ASSERT_NE(null, entry);
+ entry->Close();
+ entry = NULL;
+
+ // Write an invalid header on stream 1.
+ base::FilePath entry_file1_path = cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 1));
+
+ disk_cache::SimpleFileHeader header;
+ header.initial_magic_number = GG_UINT64_C(0xbadf00d);
+ EXPECT_EQ(
+ implicit_cast<int>(sizeof(header)),
+ file_util::WriteFile(entry_file1_path, reinterpret_cast<char*>(&header),
+ sizeof(header)));
+ ASSERT_EQ(net::ERR_FAILED, OpenEntry(key, &entry));
+}
+
+// Tests that the Simple Cache Backend fails to initialize with non-matching
+// file structure on disk.
+TEST_F(DiskCacheBackendTest, SimpleCacheOverBlockfileCache) {
+ // Create a cache structure with the |BackendImpl|.
+ InitCache();
+ disk_cache::Entry* entry;
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
+ ASSERT_EQ(0, WriteData(entry, 0, 0, buffer.get(), 0, false));
+ entry->Close();
+ cache_.reset();
+
+ // Check that the |SimpleBackendImpl| does not favor this structure.
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ disk_cache::SimpleBackendImpl* simple_cache =
+ new disk_cache::SimpleBackendImpl(cache_path_,
+ 0,
+ net::DISK_CACHE,
+ cache_thread.message_loop_proxy().get(),
+ NULL);
+ net::TestCompletionCallback cb;
+ int rv = simple_cache->Init(cb.callback());
+ EXPECT_NE(net::OK, cb.GetResult(rv));
+ delete simple_cache;
+ DisableIntegrityCheck();
+}
+
+// Tests that the |BackendImpl| refuses to initialize on top of the files
+// generated by the Simple Cache Backend.
+TEST_F(DiskCacheBackendTest, BlockfileCacheOverSimpleCache) {
+ // Create a cache structure with the |SimpleBackendImpl|.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* entry;
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ ASSERT_EQ(net::OK, CreateEntry("key", &entry));
+ ASSERT_EQ(0, WriteData(entry, 0, 0, buffer.get(), 0, false));
+ entry->Close();
+ cache_.reset();
+
+ // Check that the |BackendImpl| does not favor this structure.
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(
+ cache_path_, base::MessageLoopProxy::current().get(), NULL);
+ cache->SetUnitTestMode();
+ net::TestCompletionCallback cb;
+ int rv = cache->Init(cb.callback());
+ EXPECT_NE(net::OK, cb.GetResult(rv));
+ delete cache;
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheBackendTest, SimpleCacheFixEnumerators) {
+ SetSimpleCacheMode();
+ BackendFixEnumerators();
+}
+
+// Creates entries based on random keys. Stores these keys in |key_pool|.
+bool DiskCacheBackendTest::CreateSetOfRandomEntries(
+ std::set<std::string>* key_pool) {
+ const int kNumEntries = 10;
+
+ for (int i = 0; i < kNumEntries; ++i) {
+ std::string key = GenerateKey(true);
+ disk_cache::Entry* entry;
+ if (CreateEntry(key, &entry) != net::OK)
+ return false;
+ key_pool->insert(key);
+ entry->Close();
+ }
+ return key_pool->size() == implicit_cast<size_t>(cache_->GetEntryCount());
+}
+
+// Performs iteration over the backend and checks that the keys of entries
+// opened are in |keys_to_match|, then erases them. Up to |max_to_open| entries
+// will be opened, if it is positive. Otherwise, iteration will continue until
+// OpenNextEntry stops returning net::OK.
+bool DiskCacheBackendTest::EnumerateAndMatchKeys(
+ int max_to_open,
+ void** iter,
+ std::set<std::string>* keys_to_match,
+ size_t* count) {
+ disk_cache::Entry* entry;
+
+ while (OpenNextEntry(iter, &entry) == net::OK) {
+ if (!entry)
+ return false;
+ EXPECT_EQ(1U, keys_to_match->erase(entry->GetKey()));
+ entry->Close();
+ ++(*count);
+ if (max_to_open >= 0 && implicit_cast<int>(*count) >= max_to_open)
+ break;
+ };
+
+ return true;
+}
+
+// Tests basic functionality of the SimpleBackend implementation of the
+// enumeration API.
+TEST_F(DiskCacheBackendTest, SimpleCacheEnumerationBasics) {
+ SetSimpleCacheMode();
+ InitCache();
+ std::set<std::string> key_pool;
+ ASSERT_TRUE(CreateSetOfRandomEntries(&key_pool));
+
+ // Check that enumeration returns all entries.
+ std::set<std::string> keys_to_match(key_pool);
+ void* iter = NULL;
+ size_t count = 0;
+ ASSERT_TRUE(EnumerateAndMatchKeys(-1, &iter, &keys_to_match, &count));
+ cache_->EndEnumeration(&iter);
+ EXPECT_EQ(key_pool.size(), count);
+ EXPECT_TRUE(keys_to_match.empty());
+
+ // Check that opening entries does not affect enumeration.
+ keys_to_match = key_pool;
+ iter = NULL;
+ count = 0;
+ disk_cache::Entry* entry_opened_before;
+ ASSERT_EQ(net::OK, OpenEntry(*(key_pool.begin()), &entry_opened_before));
+ ASSERT_TRUE(EnumerateAndMatchKeys(key_pool.size()/2,
+ &iter,
+ &keys_to_match,
+ &count));
+
+ disk_cache::Entry* entry_opened_middle;
+ ASSERT_EQ(net::OK,
+ OpenEntry(*(keys_to_match.begin()), &entry_opened_middle));
+ ASSERT_TRUE(EnumerateAndMatchKeys(-1, &iter, &keys_to_match, &count));
+ cache_->EndEnumeration(&iter);
+ entry_opened_before->Close();
+ entry_opened_middle->Close();
+
+ EXPECT_EQ(key_pool.size(), count);
+ EXPECT_TRUE(keys_to_match.empty());
+}
+
+// Tests that the enumerations are not affected by dooming an entry in the
+// middle.
+TEST_F(DiskCacheBackendTest, SimpleCacheEnumerationWhileDoomed) {
+ SetSimpleCacheMode();
+ InitCache();
+ std::set<std::string> key_pool;
+ ASSERT_TRUE(CreateSetOfRandomEntries(&key_pool));
+
+ // Check that enumeration returns all entries but the doomed one.
+ std::set<std::string> keys_to_match(key_pool);
+ void* iter = NULL;
+ size_t count = 0;
+ ASSERT_TRUE(EnumerateAndMatchKeys(key_pool.size()/2,
+ &iter,
+ &keys_to_match,
+ &count));
+
+ std::string key_to_delete = *(keys_to_match.begin());
+ DoomEntry(key_to_delete);
+ keys_to_match.erase(key_to_delete);
+ key_pool.erase(key_to_delete);
+ ASSERT_TRUE(EnumerateAndMatchKeys(-1, &iter, &keys_to_match, &count));
+ cache_->EndEnumeration(&iter);
+
+ EXPECT_EQ(key_pool.size(), count);
+ EXPECT_TRUE(keys_to_match.empty());
+}
+
+// Tests that enumerations are not affected by corrupt files.
+TEST_F(DiskCacheBackendTest, SimpleCacheEnumerationCorruption) {
+ SetSimpleCacheMode();
+ InitCache();
+ std::set<std::string> key_pool;
+ ASSERT_TRUE(CreateSetOfRandomEntries(&key_pool));
+
+ // Create a corrupt entry. The write/read sequence ensures that the entry will
+ // have been created before corrupting the platform files, in the case of
+ // optimistic operations.
+ const std::string key = "the key";
+ disk_cache::Entry* corrupted_entry;
+
+ ASSERT_EQ(net::OK, CreateEntry(key, &corrupted_entry));
+ ASSERT_TRUE(corrupted_entry);
+ const int kSize = 50;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ ASSERT_EQ(kSize,
+ WriteData(corrupted_entry, 0, 0, buffer.get(), kSize, false));
+ ASSERT_EQ(kSize, ReadData(corrupted_entry, 0, 0, buffer.get(), kSize));
+ corrupted_entry->Close();
+
+ EXPECT_TRUE(disk_cache::simple_util::CreateCorruptFileForTests(
+ key, cache_path_));
+ EXPECT_EQ(key_pool.size() + 1,
+ implicit_cast<size_t>(cache_->GetEntryCount()));
+
+ // Check that enumeration returns all entries but the corrupt one.
+ std::set<std::string> keys_to_match(key_pool);
+ void* iter = NULL;
+ size_t count = 0;
+ ASSERT_TRUE(EnumerateAndMatchKeys(-1, &iter, &keys_to_match, &count));
+ cache_->EndEnumeration(&iter);
+
+ EXPECT_EQ(key_pool.size(), count);
+ EXPECT_TRUE(keys_to_match.empty());
+}
+
+#endif // !defined(OS_WIN)
diff --git a/chromium/net/disk_cache/bitmap.cc b/chromium/net/disk_cache/bitmap.cc
new file mode 100644
index 00000000000..6d469dfe3dc
--- /dev/null
+++ b/chromium/net/disk_cache/bitmap.cc
@@ -0,0 +1,311 @@
+// Copyright (c) 2009 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/disk_cache/bitmap.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+namespace {
+
+// Returns the number of trailing zeros.
+int FindLSBSetNonZero(uint32 word) {
+ // Get the LSB, put it on the exponent of a 32 bit float and remove the
+ // mantisa and the bias. This code requires IEEE 32 bit float compliance.
+ float f = static_cast<float>(word & -static_cast<int>(word));
+
+ // We use a union to go around strict-aliasing complains.
+ union {
+ float ieee_float;
+ uint32 as_uint;
+ } x;
+
+ x.ieee_float = f;
+ return (x.as_uint >> 23) - 0x7f;
+}
+
+// Returns the index of the first bit set to |value| from |word|. This code
+// assumes that we'll be able to find that bit.
+int FindLSBNonEmpty(uint32 word, bool value) {
+ // If we are looking for 0, negate |word| and look for 1.
+ if (!value)
+ word = ~word;
+
+ return FindLSBSetNonZero(word);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+Bitmap::Bitmap(int num_bits, bool clear_bits)
+ : num_bits_(num_bits),
+ array_size_(RequiredArraySize(num_bits)),
+ alloc_(true) {
+ map_ = new uint32[array_size_];
+
+ // Initialize all of the bits.
+ if (clear_bits)
+ Clear();
+}
+
+Bitmap::Bitmap(uint32* map, int num_bits, int num_words)
+ : map_(map),
+ num_bits_(num_bits),
+ // If size is larger than necessary, trim because array_size_ is used
+ // as a bound by various methods.
+ array_size_(std::min(RequiredArraySize(num_bits), num_words)),
+ alloc_(false) {
+}
+
+Bitmap::~Bitmap() {
+ if (alloc_)
+ delete [] map_;
+}
+
+void Bitmap::Resize(int num_bits, bool clear_bits) {
+ DCHECK(alloc_ || !map_);
+ const int old_maxsize = num_bits_;
+ const int old_array_size = array_size_;
+ array_size_ = RequiredArraySize(num_bits);
+
+ if (array_size_ != old_array_size) {
+ uint32* new_map = new uint32[array_size_];
+ // Always clear the unused bits in the last word.
+ new_map[array_size_ - 1] = 0;
+ memcpy(new_map, map_,
+ sizeof(*map_) * std::min(array_size_, old_array_size));
+ if (alloc_)
+ delete[] map_; // No need to check for NULL.
+ map_ = new_map;
+ alloc_ = true;
+ }
+
+ num_bits_ = num_bits;
+ if (old_maxsize < num_bits_ && clear_bits) {
+ SetRange(old_maxsize, num_bits_, false);
+ }
+}
+
+void Bitmap::Set(int index, bool value) {
+ DCHECK_LT(index, num_bits_);
+ DCHECK_GE(index, 0);
+ const int i = index & (kIntBits - 1);
+ const int j = index / kIntBits;
+ if (value)
+ map_[j] |= (1 << i);
+ else
+ map_[j] &= ~(1 << i);
+}
+
+bool Bitmap::Get(int index) const {
+ DCHECK_LT(index, num_bits_);
+ DCHECK_GE(index, 0);
+ const int i = index & (kIntBits-1);
+ const int j = index / kIntBits;
+ return ((map_[j] & (1 << i)) != 0);
+}
+
+void Bitmap::Toggle(int index) {
+ DCHECK_LT(index, num_bits_);
+ DCHECK_GE(index, 0);
+ const int i = index & (kIntBits - 1);
+ const int j = index / kIntBits;
+ map_[j] ^= (1 << i);
+}
+
+void Bitmap::SetMapElement(int array_index, uint32 value) {
+ DCHECK_LT(array_index, array_size_);
+ DCHECK_GE(array_index, 0);
+ map_[array_index] = value;
+}
+
+uint32 Bitmap::GetMapElement(int array_index) const {
+ DCHECK_LT(array_index, array_size_);
+ DCHECK_GE(array_index, 0);
+ return map_[array_index];
+}
+
+void Bitmap::SetMap(const uint32* map, int size) {
+ memcpy(map_, map, std::min(size, array_size_) * sizeof(*map_));
+}
+
+void Bitmap::SetRange(int begin, int end, bool value) {
+ DCHECK_LE(begin, end);
+ int start_offset = begin & (kIntBits - 1);
+ if (start_offset) {
+ // Set the bits in the first word.
+ int len = std::min(end - begin, kIntBits - start_offset);
+ SetWordBits(begin, len, value);
+ begin += len;
+ }
+
+ if (begin == end)
+ return;
+
+ // Now set the bits in the last word.
+ int end_offset = end & (kIntBits - 1);
+ end -= end_offset;
+ SetWordBits(end, end_offset, value);
+
+ // Set all the words in the middle.
+ memset(map_ + (begin / kIntBits), (value ? 0xFF : 0x00),
+ ((end / kIntBits) - (begin / kIntBits)) * sizeof(*map_));
+}
+
+// Return true if any bit between begin inclusive and end exclusive
+// is set. 0 <= begin <= end <= bits() is required.
+bool Bitmap::TestRange(int begin, int end, bool value) const {
+ DCHECK_LT(begin, num_bits_);
+ DCHECK_LE(end, num_bits_);
+ DCHECK_LE(begin, end);
+ DCHECK_GE(begin, 0);
+ DCHECK_GE(end, 0);
+
+ // Return false immediately if the range is empty.
+ if (begin >= end || end <= 0)
+ return false;
+
+ // Calculate the indices of the words containing the first and last bits,
+ // along with the positions of the bits within those words.
+ int word = begin / kIntBits;
+ int offset = begin & (kIntBits - 1);
+ int last_word = (end - 1) / kIntBits;
+ int last_offset = (end - 1) & (kIntBits - 1);
+
+ // If we are looking for zeros, negate the data from the map.
+ uint32 this_word = map_[word];
+ if (!value)
+ this_word = ~this_word;
+
+ // If the range spans multiple words, discard the extraneous bits of the
+ // first word by shifting to the right, and then test the remaining bits.
+ if (word < last_word) {
+ if (this_word >> offset)
+ return true;
+ offset = 0;
+
+ word++;
+ // Test each of the "middle" words that lies completely within the range.
+ while (word < last_word) {
+ this_word = map_[word++];
+ if (!value)
+ this_word = ~this_word;
+ if (this_word)
+ return true;
+ }
+ }
+
+ // Test the portion of the last word that lies within the range. (This logic
+ // also handles the case where the entire range lies within a single word.)
+ const uint32 mask = ((2 << (last_offset - offset)) - 1) << offset;
+
+ this_word = map_[last_word];
+ if (!value)
+ this_word = ~this_word;
+
+ return (this_word & mask) != 0;
+}
+
+bool Bitmap::FindNextBit(int* index, int limit, bool value) const {
+ DCHECK_LT(*index, num_bits_);
+ DCHECK_LE(limit, num_bits_);
+ DCHECK_LE(*index, limit);
+ DCHECK_GE(*index, 0);
+ DCHECK_GE(limit, 0);
+
+ const int bit_index = *index;
+ if (bit_index >= limit || limit <= 0)
+ return false;
+
+ // From now on limit != 0, since if it was we would have returned false.
+ int word_index = bit_index >> kLogIntBits;
+ uint32 one_word = map_[word_index];
+
+ // Simple optimization where we can immediately return true if the first
+ // bit is set. This helps for cases where many bits are set, and doesn't
+ // hurt too much if not.
+ if (Get(bit_index) == value)
+ return true;
+
+ const int first_bit_offset = bit_index & (kIntBits - 1);
+
+ // First word is special - we need to mask off leading bits.
+ uint32 mask = 0xFFFFFFFF << first_bit_offset;
+ if (value) {
+ one_word &= mask;
+ } else {
+ one_word |= ~mask;
+ }
+
+ uint32 empty_value = value ? 0 : 0xFFFFFFFF;
+
+ // Loop through all but the last word. Note that 'limit' is one
+ // past the last bit we want to check, and we don't want to read
+ // past the end of "words". E.g. if num_bits_ == 32 only words[0] is
+ // valid, so we want to avoid reading words[1] when limit == 32.
+ const int last_word_index = (limit - 1) >> kLogIntBits;
+ while (word_index < last_word_index) {
+ if (one_word != empty_value) {
+ *index = (word_index << kLogIntBits) + FindLSBNonEmpty(one_word, value);
+ return true;
+ }
+ one_word = map_[++word_index];
+ }
+
+ // Last word is special - we may need to mask off trailing bits. Note that
+ // 'limit' is one past the last bit we want to check, and if limit is a
+ // multiple of 32 we want to check all bits in this word.
+ const int last_bit_offset = (limit - 1) & (kIntBits - 1);
+ mask = 0xFFFFFFFE << last_bit_offset;
+ if (value) {
+ one_word &= ~mask;
+ } else {
+ one_word |= mask;
+ }
+ if (one_word != empty_value) {
+ *index = (word_index << kLogIntBits) + FindLSBNonEmpty(one_word, value);
+ return true;
+ }
+ return false;
+}
+
+int Bitmap::FindBits(int* index, int limit, bool value) const {
+ DCHECK_LT(*index, num_bits_);
+ DCHECK_LE(limit, num_bits_);
+ DCHECK_LE(*index, limit);
+ DCHECK_GE(*index, 0);
+ DCHECK_GE(limit, 0);
+
+ if (!FindNextBit(index, limit, value))
+ return false;
+
+ // Now see how many bits have the same value.
+ int end = *index;
+ if (!FindNextBit(&end, limit, !value))
+ return limit - *index;
+
+ return end - *index;
+}
+
+void Bitmap::SetWordBits(int start, int len, bool value) {
+ DCHECK_LT(len, kIntBits);
+ DCHECK_GE(len, 0);
+ if (!len)
+ return;
+
+ int word = start / kIntBits;
+ int offset = start % kIntBits;
+
+ uint32 to_add = 0xffffffff << len;
+ to_add = (~to_add) << offset;
+ if (value) {
+ map_[word] |= to_add;
+ } else {
+ map_[word] &= ~to_add;
+ }
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/bitmap.h b/chromium/net/disk_cache/bitmap.h
new file mode 100644
index 00000000000..81c434c39b4
--- /dev/null
+++ b/chromium/net/disk_cache/bitmap.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2011 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_DISK_CACHE_BITMAP_H_
+#define NET_DISK_CACHE_BITMAP_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+
+// This class provides support for simple maps of bits.
+class NET_EXPORT_PRIVATE Bitmap {
+ public:
+ Bitmap() : map_(NULL), num_bits_(0), array_size_(0), alloc_(false) {}
+
+ // This constructor will allocate on a uint32 boundary. If |clear_bits| is
+ // false, the bitmap bits will not be initialized.
+ Bitmap(int num_bits, bool clear_bits);
+
+ // Constructs a Bitmap with the actual storage provided by the caller. |map|
+ // has to be valid until this object destruction. |num_bits| is the number of
+ // bits in the bitmap, and |num_words| is the size of |map| in 32-bit words.
+ Bitmap(uint32* map, int num_bits, int num_words);
+
+ ~Bitmap();
+
+ // Resizes the bitmap.
+ // If |num_bits| < Size(), the extra bits will be discarded.
+ // If |num_bits| > Size(), the extra bits will be filled with zeros if
+ // |clear_bits| is true.
+ // This object cannot be using memory provided during construction.
+ void Resize(int num_bits, bool clear_bits);
+
+ // Returns the number of bits in the bitmap.
+ int Size() const { return num_bits_; }
+
+ // Returns the number of 32-bit words in the bitmap.
+ int ArraySize() const { return array_size_; }
+
+ // Sets all the bits to true or false.
+ void SetAll(bool value) {
+ memset(map_, (value ? 0xFF : 0x00), array_size_ * sizeof(*map_));
+ }
+
+ // Clears all bits in the bitmap
+ void Clear() { SetAll(false); }
+
+ // Sets the value, gets the value or toggles the value of a given bit.
+ void Set(int index, bool value);
+ bool Get(int index) const;
+ void Toggle(int index);
+
+ // Directly sets an element of the internal map. Requires |array_index| <
+ // ArraySize();
+ void SetMapElement(int array_index, uint32 value);
+
+ // Gets an entry of the internal map. Requires array_index <
+ // ArraySize()
+ uint32 GetMapElement(int array_index) const;
+
+ // Directly sets the whole internal map. |size| is the number of 32-bit words
+ // to set from |map|. If |size| > array_size(), it ignores the end of |map|.
+ void SetMap(const uint32* map, int size);
+
+ // Gets a pointer to the internal map.
+ const uint32* GetMap() const { return map_; }
+
+ // Sets a range of bits to |value|.
+ void SetRange(int begin, int end, bool value);
+
+ // Returns true if any bit between begin inclusive and end exclusive is set.
+ // 0 <= |begin| <= |end| <= Size() is required.
+ bool TestRange(int begin, int end, bool value) const;
+
+ // Scans bits starting at bit *|index|, looking for a bit set to |value|. If
+ // it finds that bit before reaching bit index |limit|, sets *|index| to the
+ // bit index and returns true. Otherwise returns false.
+ // Requires |limit| <= Size().
+ //
+ // Note that to use these methods in a loop you must increment the index
+ // after each use, as in:
+ //
+ // for (int index = 0 ; map.FindNextBit(&index, limit, value) ; ++index) {
+ // DoSomethingWith(index);
+ // }
+ bool FindNextBit(int* index, int limit, bool value) const;
+
+ // Finds the first offset >= *|index| and < |limit| that has its bit set.
+ // See FindNextBit() for more info.
+ bool FindNextSetBitBeforeLimit(int* index, int limit) const {
+ return FindNextBit(index, limit, true);
+ }
+
+ // Finds the first offset >= *|index| that has its bit set.
+ // See FindNextBit() for more info.
+ bool FindNextSetBit(int *index) const {
+ return FindNextSetBitBeforeLimit(index, num_bits_);
+ }
+
+ // Scans bits starting at bit *|index|, looking for a bit set to |value|. If
+ // it finds that bit before reaching bit index |limit|, sets *|index| to the
+ // bit index and then counts the number of consecutive bits set to |value|
+ // (before reaching |limit|), and returns that count. If no bit is found
+ // returns 0. Requires |limit| <= Size().
+ int FindBits(int* index, int limit, bool value) const;
+
+ // Returns number of allocated words required for a bitmap of size |num_bits|.
+ static int RequiredArraySize(int num_bits) {
+ // Force at least one allocated word.
+ if (num_bits <= kIntBits)
+ return 1;
+
+ return (num_bits + kIntBits - 1) >> kLogIntBits;
+ }
+
+ private:
+ static const int kIntBits = sizeof(uint32) * 8;
+ static const int kLogIntBits = 5; // 2^5 == 32 bits per word.
+
+ // Sets |len| bits from |start| to |value|. All the bits to be set should be
+ // stored in the same word, and len < kIntBits.
+ void SetWordBits(int start, int len, bool value);
+
+ uint32* map_; // The bitmap.
+ int num_bits_; // The upper bound of the bitmap.
+ int array_size_; // The physical size (in uint32s) of the bitmap.
+ bool alloc_; // Whether or not we allocated the memory.
+
+ DISALLOW_COPY_AND_ASSIGN(Bitmap);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BITMAP_H_
diff --git a/chromium/net/disk_cache/bitmap_unittest.cc b/chromium/net/disk_cache/bitmap_unittest.cc
new file mode 100644
index 00000000000..d80ea742682
--- /dev/null
+++ b/chromium/net/disk_cache/bitmap_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2009 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/disk_cache/bitmap.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(BitmapTest, OverAllocate) {
+ // Test that we don't over allocate on boundaries.
+ disk_cache::Bitmap map32(32, false);
+ EXPECT_EQ(1, map32.ArraySize());
+
+ disk_cache::Bitmap map64(64, false);
+ EXPECT_EQ(2, map64.ArraySize());
+}
+
+TEST(BitmapTest, DefaultConstructor) {
+ // Verify that the default constructor doesn't allocate a bitmap.
+ disk_cache::Bitmap map;
+ EXPECT_EQ(0, map.Size());
+ EXPECT_EQ(0, map.ArraySize());
+ EXPECT_TRUE(NULL == map.GetMap());
+}
+
+TEST(BitmapTest, Basics) {
+ disk_cache::Bitmap bitmap(80, true);
+ const uint32 kValue = 0x74f10060;
+
+ // Test proper allocation size.
+ EXPECT_EQ(80, bitmap.Size());
+ EXPECT_EQ(3, bitmap.ArraySize());
+
+ // Test Set/GetMapElement.
+ EXPECT_EQ(0U, bitmap.GetMapElement(1));
+ bitmap.SetMapElement(1, kValue);
+ EXPECT_EQ(kValue, bitmap.GetMapElement(1));
+
+ // Test Set/Get.
+ EXPECT_TRUE(bitmap.Get(48));
+ EXPECT_FALSE(bitmap.Get(49));
+ EXPECT_FALSE(bitmap.Get(50));
+ bitmap.Set(49, true);
+ EXPECT_TRUE(bitmap.Get(48));
+ EXPECT_TRUE(bitmap.Get(49));
+ EXPECT_FALSE(bitmap.Get(50));
+ bitmap.Set(49, false);
+ EXPECT_TRUE(bitmap.Get(48));
+ EXPECT_FALSE(bitmap.Get(49));
+ EXPECT_FALSE(bitmap.Get(50));
+
+ for (int i = 0; i < 80; i++)
+ bitmap.Set(i, (i % 7) == 0);
+ for (int i = 0; i < 80; i++)
+ EXPECT_EQ(bitmap.Get(i), (i % 7) == 0);
+}
+
+TEST(BitmapTest, Toggle) {
+ static const int kSize = 100;
+ disk_cache::Bitmap map(kSize, true);
+ for (int i = 0; i < 100; i += 3)
+ map.Toggle(i);
+ for (int i = 0; i < 100; i += 9)
+ map.Toggle(i);
+ for (int i = 0; i < 100; ++i)
+ EXPECT_EQ((i % 3 == 0) && (i % 9 != 0), map.Get(i));
+}
+
+TEST(BitmapTest, Resize) {
+ const int kSize1 = 50;
+ const int kSize2 = 100;
+ const int kSize3 = 30;
+ disk_cache::Bitmap map(kSize1, true);
+ map.Resize(kSize1, true);
+ EXPECT_EQ(kSize1, map.Size());
+ EXPECT_FALSE(map.Get(0));
+ EXPECT_FALSE(map.Get(kSize1 - 1));
+
+ map.Resize(kSize2, true);
+ EXPECT_FALSE(map.Get(kSize1 - 1));
+ EXPECT_FALSE(map.Get(kSize1));
+ EXPECT_FALSE(map.Get(kSize2 - 1));
+ EXPECT_EQ(kSize2, map.Size());
+
+ map.Resize(kSize3, true);
+ EXPECT_FALSE(map.Get(kSize3 - 1));
+ EXPECT_EQ(kSize3, map.Size());
+}
+
+TEST(BitmapTest, Map) {
+ // Tests Set/GetMap and the constructor that takes an array.
+ const int kMapSize = 80;
+ char local_map[kMapSize];
+ for (int i = 0; i < kMapSize; i++)
+ local_map[i] = static_cast<char>(i);
+
+ disk_cache::Bitmap bitmap(kMapSize * 8, false);
+ bitmap.SetMap(reinterpret_cast<uint32*>(local_map), kMapSize / 4);
+ for (int i = 0; i < kMapSize; i++) {
+ if (i % 2)
+ EXPECT_TRUE(bitmap.Get(i * 8));
+ else
+ EXPECT_FALSE(bitmap.Get(i * 8));
+ }
+
+ EXPECT_EQ(0, memcmp(local_map, bitmap.GetMap(), kMapSize));
+
+ // Now let's create a bitmap that shares local_map as storage.
+ disk_cache::Bitmap bitmap2(reinterpret_cast<uint32*>(local_map),
+ kMapSize * 8, kMapSize / 4);
+ EXPECT_EQ(0, memcmp(local_map, bitmap2.GetMap(), kMapSize));
+
+ local_map[kMapSize / 2] = 'a';
+ EXPECT_EQ(0, memcmp(local_map, bitmap2.GetMap(), kMapSize));
+ EXPECT_NE(0, memcmp(local_map, bitmap.GetMap(), kMapSize));
+}
+
+TEST(BitmapTest, SetAll) {
+ // Tests SetAll and Clear.
+ const int kMapSize = 80;
+ char ones[kMapSize];
+ char zeros[kMapSize];
+ memset(ones, 0xff, kMapSize);
+ memset(zeros, 0, kMapSize);
+
+ disk_cache::Bitmap map(kMapSize * 8, true);
+ EXPECT_EQ(0, memcmp(zeros, map.GetMap(), kMapSize));
+ map.SetAll(true);
+ EXPECT_EQ(0, memcmp(ones, map.GetMap(), kMapSize));
+ map.SetAll(false);
+ EXPECT_EQ(0, memcmp(zeros, map.GetMap(), kMapSize));
+ map.SetAll(true);
+ map.Clear();
+ EXPECT_EQ(0, memcmp(zeros, map.GetMap(), kMapSize));
+}
+
+TEST(BitmapTest, Range) {
+ // Tests SetRange() and TestRange().
+ disk_cache::Bitmap map(100, true);
+ EXPECT_FALSE(map.TestRange(0, 100, true));
+ map.Set(50, true);
+ EXPECT_TRUE(map.TestRange(0, 100, true));
+
+ map.SetAll(false);
+ EXPECT_FALSE(map.TestRange(0, 1, true));
+ EXPECT_FALSE(map.TestRange(30, 31, true));
+ EXPECT_FALSE(map.TestRange(98, 99, true));
+ EXPECT_FALSE(map.TestRange(99, 100, true));
+ EXPECT_FALSE(map.TestRange(0, 100, true));
+
+ EXPECT_TRUE(map.TestRange(0, 1, false));
+ EXPECT_TRUE(map.TestRange(31, 32, false));
+ EXPECT_TRUE(map.TestRange(32, 33, false));
+ EXPECT_TRUE(map.TestRange(99, 100, false));
+ EXPECT_TRUE(map.TestRange(0, 32, false));
+
+ map.SetRange(11, 21, true);
+ for (int i = 0; i < 100; i++)
+ EXPECT_EQ(map.Get(i), (i >= 11) && (i < 21));
+
+ EXPECT_TRUE(map.TestRange(0, 32, true));
+ EXPECT_TRUE(map.TestRange(0, 100, true));
+ EXPECT_TRUE(map.TestRange(11, 21, true));
+ EXPECT_TRUE(map.TestRange(15, 16, true));
+ EXPECT_TRUE(map.TestRange(5, 12, true));
+ EXPECT_TRUE(map.TestRange(5, 11, false));
+ EXPECT_TRUE(map.TestRange(20, 60, true));
+ EXPECT_TRUE(map.TestRange(21, 60, false));
+
+ map.SetAll(true);
+ EXPECT_FALSE(map.TestRange(0, 100, false));
+
+ map.SetRange(70, 99, false);
+ EXPECT_TRUE(map.TestRange(69, 99, false));
+ EXPECT_TRUE(map.TestRange(70, 100, false));
+ EXPECT_FALSE(map.TestRange(70, 99, true));
+}
+
+TEST(BitmapTest, FindNextSetBitBeforeLimit) {
+ // Test FindNextSetBitBeforeLimit. Only check bits from 111 to 277 (limit
+ // bit == 278). Should find all multiples of 27 in that range.
+ disk_cache::Bitmap map(500, true);
+ for (int i = 0; i < 500; i++)
+ map.Set(i, (i % 27) == 0);
+
+ int find_me = 135; // First one expected.
+ for (int index = 111; map.FindNextSetBitBeforeLimit(&index, 278);
+ ++index) {
+ EXPECT_EQ(index, find_me);
+ find_me += 27;
+ }
+ EXPECT_EQ(find_me, 297); // The next find_me after 278.
+}
+
+TEST(BitmapTest, FindNextSetBitBeforeLimitAligned) {
+ // Test FindNextSetBitBeforeLimit on aligned scans.
+ disk_cache::Bitmap map(256, true);
+ for (int i = 0; i < 256; i++)
+ map.Set(i, (i % 32) == 0);
+ for (int i = 0; i < 256; i += 32) {
+ int index = i + 1;
+ EXPECT_FALSE(map.FindNextSetBitBeforeLimit(&index, i + 32));
+ }
+}
+
+TEST(BitmapTest, FindNextSetBit) {
+ // Test FindNextSetBit. Check all bits in map. Should find multiples
+ // of 7 from 0 to 98.
+ disk_cache::Bitmap map(100, true);
+ for (int i = 0; i < 100; i++)
+ map.Set(i, (i % 7) == 0);
+
+ int find_me = 0; // First one expected.
+ for (int index = 0; map.FindNextSetBit(&index); ++index) {
+ EXPECT_EQ(index, find_me);
+ find_me += 7;
+ }
+ EXPECT_EQ(find_me, 105); // The next find_me after 98.
+}
+
+TEST(BitmapTest, FindNextBit) {
+ // Almost the same test as FindNextSetBit, but find zeros instead of ones.
+ disk_cache::Bitmap map(100, false);
+ map.SetAll(true);
+ for (int i = 0; i < 100; i++)
+ map.Set(i, (i % 7) != 0);
+
+ int find_me = 0; // First one expected.
+ for (int index = 0; map.FindNextBit(&index, 100, false); ++index) {
+ EXPECT_EQ(index, find_me);
+ find_me += 7;
+ }
+ EXPECT_EQ(find_me, 105); // The next find_me after 98.
+}
+
+TEST(BitmapTest, SimpleFindBits) {
+ disk_cache::Bitmap bitmap(64, true);
+ bitmap.SetMapElement(0, 0x7ff10060);
+
+ // Bit at index off.
+ int index = 0;
+ EXPECT_EQ(5, bitmap.FindBits(&index, 63, false));
+ EXPECT_EQ(0, index);
+
+ EXPECT_EQ(2, bitmap.FindBits(&index, 63, true));
+ EXPECT_EQ(5, index);
+
+ index = 0;
+ EXPECT_EQ(2, bitmap.FindBits(&index, 63, true));
+ EXPECT_EQ(5, index);
+
+ index = 6;
+ EXPECT_EQ(9, bitmap.FindBits(&index, 63, false));
+ EXPECT_EQ(7, index);
+
+ // Bit at index on.
+ index = 16;
+ EXPECT_EQ(1, bitmap.FindBits(&index, 63, true));
+ EXPECT_EQ(16, index);
+
+ index = 17;
+ EXPECT_EQ(11, bitmap.FindBits(&index, 63, true));
+ EXPECT_EQ(20, index);
+
+ index = 31;
+ EXPECT_EQ(0, bitmap.FindBits(&index, 63, true));
+ EXPECT_EQ(31, index);
+
+ // With a limit.
+ index = 8;
+ EXPECT_EQ(0, bitmap.FindBits(&index, 16, true));
+}
+
+TEST(BitmapTest, MultiWordFindBits) {
+ disk_cache::Bitmap bitmap(500, true);
+ bitmap.SetMapElement(10, 0xff00);
+
+ int index = 0;
+ EXPECT_EQ(0, bitmap.FindBits(&index, 300, true));
+
+ EXPECT_EQ(8, bitmap.FindBits(&index, 500, true));
+ EXPECT_EQ(328, index);
+
+ bitmap.SetMapElement(10, 0xff000000);
+ bitmap.SetMapElement(11, 0xff);
+
+ index = 0;
+ EXPECT_EQ(16, bitmap.FindBits(&index, 500, true));
+ EXPECT_EQ(344, index);
+
+ index = 0;
+ EXPECT_EQ(4, bitmap.FindBits(&index, 348, true));
+ EXPECT_EQ(344, index);
+}
diff --git a/chromium/net/disk_cache/block_files.cc b/chromium/net/disk_cache/block_files.cc
new file mode 100644
index 00000000000..fc378e6c449
--- /dev/null
+++ b/chromium/net/disk_cache/block_files.cc
@@ -0,0 +1,695 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/block_files.h"
+
+#include "base/atomicops.h"
+#include "base/files/file_path.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/file_lock.h"
+#include "net/disk_cache/trace.h"
+
+using base::TimeTicks;
+
+namespace {
+
+const char* kBlockName = "data_";
+
+// This array is used to perform a fast lookup of the nibble bit pattern to the
+// type of entry that can be stored there (number of consecutive blocks).
+const char s_types[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Returns the type of block (number of consecutive blocks that can be stored)
+// for a given nibble of the bitmap.
+inline int GetMapBlockType(uint8 value) {
+ value &= 0xf;
+ return s_types[value];
+}
+
+} // namespace
+
+namespace disk_cache {
+
+BlockHeader::BlockHeader() : header_(NULL) {
+}
+
+BlockHeader::BlockHeader(BlockFileHeader* header) : header_(header) {
+}
+
+BlockHeader::BlockHeader(MappedFile* file)
+ : header_(reinterpret_cast<BlockFileHeader*>(file->buffer())) {
+}
+
+BlockHeader::BlockHeader(const BlockHeader& other) : header_(other.header_) {
+}
+
+BlockHeader::~BlockHeader() {
+}
+
+bool BlockHeader::CreateMapBlock(int target, int size, int* index) {
+ if (target <= 0 || target > kMaxNumBlocks ||
+ size <= 0 || size > kMaxNumBlocks) {
+ NOTREACHED();
+ return false;
+ }
+
+ TimeTicks start = TimeTicks::Now();
+ // We are going to process the map on 32-block chunks (32 bits), and on every
+ // chunk, iterate through the 8 nibbles where the new block can be located.
+ int current = header_->hints[target - 1];
+ for (int i = 0; i < header_->max_entries / 32; i++, current++) {
+ if (current == header_->max_entries / 32)
+ current = 0;
+ uint32 map_block = header_->allocation_map[current];
+
+ for (int j = 0; j < 8; j++, map_block >>= 4) {
+ if (GetMapBlockType(map_block) != target)
+ continue;
+
+ disk_cache::FileLock lock(header_);
+ int index_offset = j * 4 + 4 - target;
+ *index = current * 32 + index_offset;
+ DCHECK_EQ(*index / 4, (*index + size - 1) / 4);
+ uint32 to_add = ((1 << size) - 1) << index_offset;
+ header_->num_entries++;
+
+ // Note that there is no race in the normal sense here, but if we enforce
+ // the order of memory accesses between num_entries and allocation_map, we
+ // can assert that even if we crash here, num_entries will never be less
+ // than the actual number of used blocks.
+ base::subtle::MemoryBarrier();
+ header_->allocation_map[current] |= to_add;
+
+ header_->hints[target - 1] = current;
+ header_->empty[target - 1]--;
+ DCHECK_GE(header_->empty[target - 1], 0);
+ if (target != size) {
+ header_->empty[target - size - 1]++;
+ }
+ HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start);
+ return true;
+ }
+ }
+
+ // It is possible to have an undetected corruption (for example when the OS
+ // crashes), fix it here.
+ LOG(ERROR) << "Failing CreateMapBlock";
+ FixAllocationCounters();
+ return false;
+}
+
+void BlockHeader::DeleteMapBlock(int index, int size) {
+ if (size < 0 || size > kMaxNumBlocks) {
+ NOTREACHED();
+ return;
+ }
+ TimeTicks start = TimeTicks::Now();
+ int byte_index = index / 8;
+ uint8* byte_map = reinterpret_cast<uint8*>(header_->allocation_map);
+ uint8 map_block = byte_map[byte_index];
+
+ if (index % 8 >= 4)
+ map_block >>= 4;
+
+ // See what type of block will be available after we delete this one.
+ int bits_at_end = 4 - size - index % 4;
+ uint8 end_mask = (0xf << (4 - bits_at_end)) & 0xf;
+ bool update_counters = (map_block & end_mask) == 0;
+ uint8 new_value = map_block & ~(((1 << size) - 1) << (index % 4));
+ int new_type = GetMapBlockType(new_value);
+
+ disk_cache::FileLock lock(header_);
+ DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
+ uint8 to_clear = ((1 << size) - 1) << (index % 8);
+ DCHECK((byte_map[byte_index] & to_clear) == to_clear);
+ byte_map[byte_index] &= ~to_clear;
+
+ if (update_counters) {
+ if (bits_at_end)
+ header_->empty[bits_at_end - 1]--;
+ header_->empty[new_type - 1]++;
+ DCHECK_GE(header_->empty[bits_at_end - 1], 0);
+ }
+ base::subtle::MemoryBarrier();
+ header_->num_entries--;
+ DCHECK_GE(header_->num_entries, 0);
+ HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start);
+}
+
+// Note that this is a simplified version of DeleteMapBlock().
+bool BlockHeader::UsedMapBlock(int index, int size) {
+ if (size < 0 || size > kMaxNumBlocks) {
+ NOTREACHED();
+ return false;
+ }
+ int byte_index = index / 8;
+ uint8* byte_map = reinterpret_cast<uint8*>(header_->allocation_map);
+ uint8 map_block = byte_map[byte_index];
+
+ if (index % 8 >= 4)
+ map_block >>= 4;
+
+ DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
+ uint8 to_clear = ((1 << size) - 1) << (index % 8);
+ return ((byte_map[byte_index] & to_clear) == to_clear);
+}
+
+void BlockHeader::FixAllocationCounters() {
+ for (int i = 0; i < kMaxNumBlocks; i++) {
+ header_->hints[i] = 0;
+ header_->empty[i] = 0;
+ }
+
+ for (int i = 0; i < header_->max_entries / 32; i++) {
+ uint32 map_block = header_->allocation_map[i];
+
+ for (int j = 0; j < 8; j++, map_block >>= 4) {
+ int type = GetMapBlockType(map_block);
+ if (type)
+ header_->empty[type -1]++;
+ }
+ }
+}
+
+bool BlockHeader::NeedToGrowBlockFile(int block_count) {
+ bool have_space = false;
+ int empty_blocks = 0;
+ for (int i = 0; i < kMaxNumBlocks; i++) {
+ empty_blocks += header_->empty[i] * (i + 1);
+ if (i >= block_count - 1 && header_->empty[i])
+ have_space = true;
+ }
+
+ if (header_->next_file && (empty_blocks < kMaxBlocks / 10)) {
+ // This file is almost full but we already created another one, don't use
+ // this file yet so that it is easier to find empty blocks when we start
+ // using this file again.
+ return true;
+ }
+ return !have_space;
+}
+
+int BlockHeader::EmptyBlocks() const {
+ int empty_blocks = 0;
+ for (int i = 0; i < disk_cache::kMaxNumBlocks; i++) {
+ empty_blocks += header_->empty[i] * (i + 1);
+ if (header_->empty[i] < 0)
+ return 0;
+ }
+ return empty_blocks;
+}
+
+bool BlockHeader::ValidateCounters() const {
+ if (header_->max_entries < 0 || header_->max_entries > kMaxBlocks ||
+ header_->num_entries < 0)
+ return false;
+
+ int empty_blocks = EmptyBlocks();
+ if (empty_blocks + header_->num_entries > header_->max_entries)
+ return false;
+
+ return true;
+}
+
+int BlockHeader::Size() const {
+ return static_cast<int>(sizeof(*header_));
+}
+
+// ------------------------------------------------------------------------
+
+BlockFiles::BlockFiles(const base::FilePath& path)
+ : init_(false), zero_buffer_(NULL), path_(path) {
+}
+
+BlockFiles::~BlockFiles() {
+ if (zero_buffer_)
+ delete[] zero_buffer_;
+ CloseFiles();
+}
+
+bool BlockFiles::Init(bool create_files) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ thread_checker_.reset(new base::ThreadChecker);
+
+ block_files_.resize(kFirstAdditionalBlockFile);
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
+ if (create_files)
+ if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
+ return false;
+
+ if (!OpenBlockFile(i))
+ return false;
+
+ // Walk this chain of files removing empty ones.
+ if (!RemoveEmptyFile(static_cast<FileType>(i + 1)))
+ return false;
+ }
+
+ init_ = true;
+ return true;
+}
+
+MappedFile* BlockFiles::GetFile(Addr address) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ DCHECK(block_files_.size() >= 4);
+ DCHECK(address.is_block_file() || !address.is_initialized());
+ if (!address.is_initialized())
+ return NULL;
+
+ int file_index = address.FileNumber();
+ if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
+ !block_files_[file_index]) {
+ // We need to open the file
+ if (!OpenBlockFile(file_index))
+ return NULL;
+ }
+ DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
+ return block_files_[file_index];
+}
+
+bool BlockFiles::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ if (block_type < RANKINGS || block_type > BLOCK_4K ||
+ block_count < 1 || block_count > 4)
+ return false;
+ if (!init_)
+ return false;
+
+ MappedFile* file = FileForNewBlock(block_type, block_count);
+ if (!file)
+ return false;
+
+ ScopedFlush flush(file);
+ BlockHeader header(file);
+
+ int target_size = 0;
+ for (int i = block_count; i <= 4; i++) {
+ if (header->empty[i - 1]) {
+ target_size = i;
+ break;
+ }
+ }
+
+ DCHECK(target_size);
+ int index;
+ if (!header.CreateMapBlock(target_size, block_count, &index))
+ return false;
+
+ Addr address(block_type, block_count, header->this_file, index);
+ block_address->set_value(address.value());
+ Trace("CreateBlock 0x%x", address.value());
+ return true;
+}
+
+void BlockFiles::DeleteBlock(Addr address, bool deep) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ if (!address.is_initialized() || address.is_separate_file())
+ return;
+
+ if (!zero_buffer_) {
+ zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
+ memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
+ }
+ MappedFile* file = GetFile(address);
+ if (!file)
+ return;
+
+ Trace("DeleteBlock 0x%x", address.value());
+
+ size_t size = address.BlockSize() * address.num_blocks();
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (deep)
+ file->Write(zero_buffer_, size, offset);
+
+ BlockHeader header(file);
+ header.DeleteMapBlock(address.start_block(), address.num_blocks());
+ file->Flush();
+
+ if (!header->num_entries) {
+ // This file is now empty. Let's try to delete it.
+ FileType type = Addr::RequiredFileType(header->entry_size);
+ if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size)
+ type = RANKINGS;
+ RemoveEmptyFile(type); // Ignore failures.
+ }
+}
+
+void BlockFiles::CloseFiles() {
+ if (init_) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ }
+ init_ = false;
+ for (unsigned int i = 0; i < block_files_.size(); i++) {
+ if (block_files_[i]) {
+ block_files_[i]->Release();
+ block_files_[i] = NULL;
+ }
+ }
+ block_files_.clear();
+}
+
+void BlockFiles::ReportStats() {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ int used_blocks[kFirstAdditionalBlockFile];
+ int load[kFirstAdditionalBlockFile];
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
+ GetFileStats(i, &used_blocks[i], &load[i]);
+ }
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks[0]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks[1]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks[2]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks[3]);
+
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
+}
+
+bool BlockFiles::IsValid(Addr address) {
+#ifdef NDEBUG
+ return true;
+#else
+ if (!address.is_initialized() || address.is_separate_file())
+ return false;
+
+ MappedFile* file = GetFile(address);
+ if (!file)
+ return false;
+
+ BlockHeader header(file);
+ bool rv = header.UsedMapBlock(address.start_block(), address.num_blocks());
+ DCHECK(rv);
+
+ static bool read_contents = false;
+ if (read_contents) {
+ scoped_ptr<char[]> buffer;
+ buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]);
+ size_t size = address.BlockSize() * address.num_blocks();
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ bool ok = file->Read(buffer.get(), size, offset);
+ DCHECK(ok);
+ }
+
+ return rv;
+#endif
+}
+
+bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
+ base::FilePath name = Name(index);
+ int flags =
+ force ? base::PLATFORM_FILE_CREATE_ALWAYS : base::PLATFORM_FILE_CREATE;
+ flags |= base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE;
+
+ scoped_refptr<File> file(new File(
+ base::CreatePlatformFile(name, flags, NULL, NULL)));
+ if (!file->IsValid())
+ return false;
+
+ BlockFileHeader header;
+ memset(&header, 0, sizeof(header));
+ header.magic = kBlockMagic;
+ header.version = kBlockVersion2;
+ header.entry_size = Addr::BlockSizeForFileType(file_type);
+ header.this_file = static_cast<int16>(index);
+ DCHECK(index <= kint16max && index >= 0);
+
+ return file->Write(&header, sizeof(header), 0);
+}
+
+bool BlockFiles::OpenBlockFile(int index) {
+ if (block_files_.size() - 1 < static_cast<unsigned int>(index)) {
+ DCHECK(index > 0);
+ int to_add = index - static_cast<int>(block_files_.size()) + 1;
+ block_files_.resize(block_files_.size() + to_add);
+ }
+
+ base::FilePath name = Name(index);
+ scoped_refptr<MappedFile> file(new MappedFile());
+
+ if (!file->Init(name, kBlockHeaderSize)) {
+ LOG(ERROR) << "Failed to open " << name.value();
+ return false;
+ }
+
+ size_t file_len = file->GetLength();
+ if (file_len < static_cast<size_t>(kBlockHeaderSize)) {
+ LOG(ERROR) << "File too small " << name.value();
+ return false;
+ }
+
+ BlockHeader header(file.get());
+ if (kBlockMagic != header->magic || kBlockVersion2 != header->version) {
+ LOG(ERROR) << "Invalid file version or magic " << name.value();
+ return false;
+ }
+
+ if (header->updating || !header.ValidateCounters()) {
+ // Last instance was not properly shutdown, or counters are out of sync.
+ if (!FixBlockFileHeader(file.get())) {
+ LOG(ERROR) << "Unable to fix block file " << name.value();
+ return false;
+ }
+ }
+
+ if (static_cast<int>(file_len) <
+ header->max_entries * header->entry_size + kBlockHeaderSize) {
+ LOG(ERROR) << "File too small " << name.value();
+ return false;
+ }
+
+ if (index == 0) {
+ // Load the links file into memory with a single read.
+ scoped_ptr<char[]> buf(new char[file_len]);
+ if (!file->Read(buf.get(), file_len, 0))
+ return false;
+ }
+
+ ScopedFlush flush(file.get());
+ DCHECK(!block_files_[index]);
+ file.swap(&block_files_[index]);
+ return true;
+}
+
+bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) {
+ if (kMaxBlocks == header->max_entries)
+ return false;
+
+ ScopedFlush flush(file);
+ DCHECK(!header->empty[3]);
+ int new_size = header->max_entries + 1024;
+ if (new_size > kMaxBlocks)
+ new_size = kMaxBlocks;
+
+ int new_size_bytes = new_size * header->entry_size + sizeof(*header);
+
+ if (!file->SetLength(new_size_bytes)) {
+ // Most likely we are trying to truncate the file, so the header is wrong.
+ if (header->updating < 10 && !FixBlockFileHeader(file)) {
+ // If we can't fix the file increase the lock guard so we'll pick it on
+ // the next start and replace it.
+ header->updating = 100;
+ return false;
+ }
+ return (header->max_entries >= new_size);
+ }
+
+ FileLock lock(header);
+ header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
+ header->max_entries = new_size;
+
+ return true;
+}
+
+MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
+ COMPILE_ASSERT(RANKINGS == 1, invalid_file_type);
+ MappedFile* file = block_files_[block_type - 1];
+ BlockHeader header(file);
+
+ TimeTicks start = TimeTicks::Now();
+ while (header.NeedToGrowBlockFile(block_count)) {
+ if (kMaxBlocks == header->max_entries) {
+ file = NextFile(file);
+ if (!file)
+ return NULL;
+ header = BlockHeader(file);
+ continue;
+ }
+
+ if (!GrowBlockFile(file, header.Get()))
+ return NULL;
+ break;
+ }
+ HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start);
+ return file;
+}
+
+MappedFile* BlockFiles::NextFile(MappedFile* file) {
+ ScopedFlush flush(file);
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ int new_file = header->next_file;
+ if (!new_file) {
+ // RANKINGS is not reported as a type for small entries, but we may be
+ // extending the rankings block file.
+ FileType type = Addr::RequiredFileType(header->entry_size);
+ if (header->entry_size == Addr::BlockSizeForFileType(RANKINGS))
+ type = RANKINGS;
+
+ new_file = CreateNextBlockFile(type);
+ if (!new_file)
+ return NULL;
+
+ FileLock lock(header);
+ header->next_file = new_file;
+ }
+
+ // Only the block_file argument is relevant for what we want.
+ Addr address(BLOCK_256, 1, new_file, 0);
+ return GetFile(address);
+}
+
+int BlockFiles::CreateNextBlockFile(FileType block_type) {
+ for (int i = kFirstAdditionalBlockFile; i <= kMaxBlockFile; i++) {
+ if (CreateBlockFile(i, block_type, false))
+ return i;
+ }
+ return 0;
+}
+
+// We walk the list of files for this particular block type, deleting the ones
+// that are empty.
+bool BlockFiles::RemoveEmptyFile(FileType block_type) {
+ MappedFile* file = block_files_[block_type - 1];
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ while (header->next_file) {
+ // Only the block_file argument is relevant for what we want.
+ Addr address(BLOCK_256, 1, header->next_file, 0);
+ MappedFile* next_file = GetFile(address);
+ if (!next_file)
+ return false;
+
+ BlockFileHeader* next_header =
+ reinterpret_cast<BlockFileHeader*>(next_file->buffer());
+ if (!next_header->num_entries) {
+ DCHECK_EQ(next_header->entry_size, header->entry_size);
+ // Delete next_file and remove it from the chain.
+ int file_index = header->next_file;
+ header->next_file = next_header->next_file;
+ DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
+ file->Flush();
+
+ // We get a new handle to the file and release the old one so that the
+ // file gets unmmaped... so we can delete it.
+ base::FilePath name = Name(file_index);
+ scoped_refptr<File> this_file(new File(false));
+ this_file->Init(name);
+ block_files_[file_index]->Release();
+ block_files_[file_index] = NULL;
+
+ int failure = DeleteCacheFile(name) ? 0 : 1;
+ UMA_HISTOGRAM_COUNTS("DiskCache.DeleteFailed2", failure);
+ if (failure)
+ LOG(ERROR) << "Failed to delete " << name.value() << " from the cache.";
+ continue;
+ }
+
+ header = next_header;
+ file = next_file;
+ }
+ return true;
+}
+
+// Note that we expect to be called outside of a FileLock... however, we cannot
+// DCHECK on header->updating because we may be fixing a crash.
+bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
+ ScopedFlush flush(file);
+ BlockHeader header(file);
+ int file_size = static_cast<int>(file->GetLength());
+ if (file_size < header.Size())
+ return false; // file_size > 2GB is also an error.
+
+ const int kMinBlockSize = 36;
+ const int kMaxBlockSize = 4096;
+ if (header->entry_size < kMinBlockSize ||
+ header->entry_size > kMaxBlockSize || header->num_entries < 0)
+ return false;
+
+ // Make sure that we survive crashes.
+ header->updating = 1;
+ int expected = header->entry_size * header->max_entries + header.Size();
+ if (file_size != expected) {
+ int max_expected = header->entry_size * kMaxBlocks + header.Size();
+ if (file_size < expected || header->empty[3] || file_size > max_expected) {
+ NOTREACHED();
+ LOG(ERROR) << "Unexpected file size";
+ return false;
+ }
+ // We were in the middle of growing the file.
+ int num_entries = (file_size - header.Size()) / header->entry_size;
+ header->max_entries = num_entries;
+ }
+
+ header.FixAllocationCounters();
+ int empty_blocks = header.EmptyBlocks();
+ if (empty_blocks + header->num_entries > header->max_entries)
+ header->num_entries = header->max_entries - empty_blocks;
+
+ if (!header.ValidateCounters())
+ return false;
+
+ header->updating = 0;
+ return true;
+}
+
+// We are interested in the total number of blocks used by this file type, and
+// the max number of blocks that we can store (reported as the percentage of
+// used blocks). In order to find out the number of used blocks, we have to
+// substract the empty blocks from the total blocks for each file in the chain.
+void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
+ int max_blocks = 0;
+ *used_count = 0;
+ *load = 0;
+ for (;;) {
+ if (!block_files_[index] && !OpenBlockFile(index))
+ return;
+
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
+
+ max_blocks += header->max_entries;
+ int used = header->max_entries;
+ for (int i = 0; i < 4; i++) {
+ used -= header->empty[i] * (i + 1);
+ DCHECK_GE(used, 0);
+ }
+ *used_count += used;
+
+ if (!header->next_file)
+ break;
+ index = header->next_file;
+ }
+ if (max_blocks)
+ *load = *used_count * 100 / max_blocks;
+}
+
+base::FilePath BlockFiles::Name(int index) {
+ // The file format allows for 256 files.
+ DCHECK(index < 256 || index >= 0);
+ std::string tmp = base::StringPrintf("%s%d", kBlockName, index);
+ return path_.AppendASCII(tmp);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/block_files.h b/chromium/net/disk_cache/block_files.h
new file mode 100644
index 00000000000..353c5663df0
--- /dev/null
+++ b/chromium/net/disk_cache/block_files.h
@@ -0,0 +1,152 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface.
+
+#ifndef NET_DISK_CACHE_BLOCK_FILES_H_
+#define NET_DISK_CACHE_BLOCK_FILES_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/addr.h"
+#include "net/disk_cache/disk_format_base.h"
+#include "net/disk_cache/mapped_file.h"
+
+namespace base {
+class ThreadChecker;
+}
+
+namespace disk_cache {
+
+// An instance of this class represents the header of a block file in memory.
+// Note that this class doesn't perform any file operation.
+class NET_EXPORT_PRIVATE BlockHeader {
+ public:
+ BlockHeader();
+ explicit BlockHeader(BlockFileHeader* header);
+ explicit BlockHeader(MappedFile* file);
+ BlockHeader(const BlockHeader& other);
+ ~BlockHeader();
+
+ // Creates a new entry on the allocation map, updating the apropriate
+ // counters. |target| is the type of block to use (number of empty blocks),
+ // and |size| is the actual number of blocks to use.
+ bool CreateMapBlock(int target, int size, int* index);
+
+ // Deletes the block pointed by |index|.
+ void DeleteMapBlock(int index, int block_size);
+
+ // Returns true if the specified block is used.
+ bool UsedMapBlock(int index, int size);
+
+ // Restores the "empty counters" and allocation hints.
+ void FixAllocationCounters();
+
+ // Returns true if the current block file should not be used as-is to store
+ // more records. |block_count| is the number of blocks to allocate.
+ bool NeedToGrowBlockFile(int block_count);
+
+ // Returns the number of empty blocks for this file.
+ int EmptyBlocks() const;
+
+ // Returns true if the counters look OK.
+ bool ValidateCounters() const;
+
+ // Returns the size of the wrapped structure (BlockFileHeader).
+ int Size() const;
+
+ BlockFileHeader* operator->() { return header_; }
+ void operator=(const BlockHeader& other) { header_ = other.header_; }
+ BlockFileHeader* Get() { return header_; }
+
+ private:
+ BlockFileHeader* header_;
+};
+
+typedef std::vector<BlockHeader> BlockFilesBitmaps;
+
+// This class handles the set of block-files open by the disk cache.
+class NET_EXPORT_PRIVATE BlockFiles {
+ public:
+ explicit BlockFiles(const base::FilePath& path);
+ ~BlockFiles();
+
+ // Performs the object initialization. create_files indicates if the backing
+ // files should be created or just open.
+ bool Init(bool create_files);
+
+ // Returns the file that stores a given address.
+ MappedFile* GetFile(Addr address);
+
+ // Creates a new entry on a block file. block_type indicates the size of block
+ // to be used (as defined on cache_addr.h), block_count is the number of
+ // blocks to allocate, and block_address is the address of the new entry.
+ bool CreateBlock(FileType block_type, int block_count, Addr* block_address);
+
+ // Removes an entry from the block files. If deep is true, the storage is zero
+ // filled; otherwise the entry is removed but the data is not altered (must be
+ // already zeroed).
+ void DeleteBlock(Addr address, bool deep);
+
+ // Close all the files and set the internal state to be initializad again. The
+ // cache is being purged.
+ void CloseFiles();
+
+ // Sends UMA stats.
+ void ReportStats();
+
+ // Returns true if the blocks pointed by a given address are currently used.
+ // This method is only intended for debugging.
+ bool IsValid(Addr address);
+
+ private:
+ // Set force to true to overwrite the file if it exists.
+ bool CreateBlockFile(int index, FileType file_type, bool force);
+ bool OpenBlockFile(int index);
+
+ // Attemp to grow this file. Fails if the file cannot be extended anymore.
+ bool GrowBlockFile(MappedFile* file, BlockFileHeader* header);
+
+ // Returns the appropriate file to use for a new block.
+ MappedFile* FileForNewBlock(FileType block_type, int block_count);
+
+ // Returns the next block file on this chain, creating new files if needed.
+ MappedFile* NextFile(MappedFile* file);
+
+ // Creates an empty block file and returns its index.
+ int CreateNextBlockFile(FileType block_type);
+
+ // Removes a chained block file that is now empty.
+ bool RemoveEmptyFile(FileType block_type);
+
+ // Restores the header of a potentially inconsistent file.
+ bool FixBlockFileHeader(MappedFile* file);
+
+ // Retrieves stats for the given file index.
+ void GetFileStats(int index, int* used_count, int* load);
+
+ // Returns the filename for a given file index.
+ base::FilePath Name(int index);
+
+ bool init_;
+ char* zero_buffer_; // Buffer to speed-up cleaning deleted entries.
+ base::FilePath path_; // Path to the backing folder.
+ std::vector<MappedFile*> block_files_; // The actual files.
+ scoped_ptr<base::ThreadChecker> thread_checker_;
+
+ FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_ZeroSizeFile);
+ FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_TruncatedFile);
+ FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_InvalidFile);
+ FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_Stats);
+
+ DISALLOW_COPY_AND_ASSIGN(BlockFiles);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BLOCK_FILES_H_
diff --git a/chromium/net/disk_cache/block_files_unittest.cc b/chromium/net/disk_cache/block_files_unittest.cc
new file mode 100644
index 00000000000..fa7c5dbb742
--- /dev/null
+++ b/chromium/net/disk_cache/block_files_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 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.
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+
+namespace {
+
+// Returns the number of files in this folder.
+int NumberOfFiles(const base::FilePath& path) {
+ base::FileEnumerator iter(path, false, base::FileEnumerator::FILES);
+ int count = 0;
+ for (base::FilePath file = iter.Next(); !file.value().empty();
+ file = iter.Next()) {
+ count++;
+ }
+ return count;
+}
+
+} // namespace;
+
+namespace disk_cache {
+
+TEST_F(DiskCacheTest, BlockFiles_Grow) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kMaxSize = 35000;
+ Addr address[kMaxSize];
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < kMaxSize; i++) {
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i]));
+ }
+ EXPECT_EQ(6, NumberOfFiles(cache_path_));
+
+ // Make sure we don't keep adding files.
+ for (int i = 0; i < kMaxSize * 4; i += 2) {
+ int target = i % kMaxSize;
+ files.DeleteBlock(address[target], false);
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[target]));
+ }
+ EXPECT_EQ(6, NumberOfFiles(cache_path_));
+}
+
+// We should be able to delete empty block files.
+TEST_F(DiskCacheTest, BlockFiles_Shrink) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kMaxSize = 35000;
+ Addr address[kMaxSize];
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < kMaxSize; i++) {
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i]));
+ }
+
+ // Now delete all the blocks, so that we can delete the two extra files.
+ for (int i = 0; i < kMaxSize; i++) {
+ files.DeleteBlock(address[i], false);
+ }
+ EXPECT_EQ(4, NumberOfFiles(cache_path_));
+}
+
+// Handling of block files not properly closed.
+TEST_F(DiskCacheTest, BlockFiles_Recover) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kNumEntries = 2000;
+ CacheAddr entries[kNumEntries];
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ for (int i = 0; i < kNumEntries; i++) {
+ Addr address(0);
+ int size = (rand() % 4) + 1;
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, size, &address));
+ entries[i] = address.value();
+ }
+
+ for (int i = 0; i < kNumEntries; i++) {
+ int source1 = rand() % kNumEntries;
+ int source2 = rand() % kNumEntries;
+ CacheAddr temp = entries[source1];
+ entries[source1] = entries[source2];
+ entries[source2] = temp;
+ }
+
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ Addr address(entries[i]);
+ files.DeleteBlock(address, false);
+ }
+
+ // At this point, there are kNumEntries / 2 entries on the file, randomly
+ // distributed both on location and size.
+
+ Addr address(entries[kNumEntries / 2]);
+ MappedFile* file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ ASSERT_EQ(0, header->updating);
+
+ int max_entries = header->max_entries;
+ int empty_1 = header->empty[0];
+ int empty_2 = header->empty[1];
+ int empty_3 = header->empty[2];
+ int empty_4 = header->empty[3];
+
+ // Corrupt the file.
+ header->max_entries = header->empty[0] = 0;
+ header->empty[1] = header->empty[2] = header->empty[3] = 0;
+ header->updating = -1;
+
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+
+ // The file must have been fixed.
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ ASSERT_EQ(0, header->updating);
+
+ EXPECT_EQ(max_entries, header->max_entries);
+ EXPECT_EQ(empty_1, header->empty[0]);
+ EXPECT_EQ(empty_2, header->empty[1]);
+ EXPECT_EQ(empty_3, header->empty[2]);
+ EXPECT_EQ(empty_4, header->empty[3]);
+}
+
+// Handling of truncated files.
+TEST_F(DiskCacheTest, BlockFiles_ZeroSizeFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ base::FilePath filename = files.Name(0);
+ files.CloseFiles();
+ // Truncate one of the files.
+ {
+ scoped_refptr<File> file(new File);
+ ASSERT_TRUE(file->Init(filename));
+ EXPECT_TRUE(file->SetLength(0));
+ }
+
+ // Initializing should fail, not crash.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// Handling of truncated files (non empty).
+TEST_F(DiskCacheTest, BlockFiles_TruncatedFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+ Addr address;
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
+
+ base::FilePath filename = files.Name(0);
+ files.CloseFiles();
+ // Truncate one of the files.
+ {
+ scoped_refptr<File> file(new File);
+ ASSERT_TRUE(file->Init(filename));
+ EXPECT_TRUE(file->SetLength(15000));
+ }
+
+ // Initializing should fail, not crash.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// Tests detection of out of sync counters.
+TEST_F(DiskCacheTest, BlockFiles_Counters) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Create a block of size 2.
+ Addr address(0);
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
+
+ MappedFile* file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+ ASSERT_EQ(0, header->updating);
+
+ // Alter the counters so that the free space doesn't add up.
+ header->empty[2] = 50; // 50 free blocks of size 3.
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ // The file must have been fixed.
+ ASSERT_EQ(0, header->empty[2]);
+
+ // Change the number of entries.
+ header->num_entries = 3;
+ header->updating = 1;
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ // The file must have been "fixed".
+ ASSERT_EQ(2, header->num_entries);
+
+ // Change the number of entries.
+ header->num_entries = -1;
+ header->updating = 1;
+ files.CloseFiles();
+
+ // Detect the error.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// An invalid file can be detected after init.
+TEST_F(DiskCacheTest, BlockFiles_InvalidFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Let's access block 10 of file 5. (There is no file).
+ Addr addr(BLOCK_256, 1, 5, 10);
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+
+ // Let's create an invalid file.
+ base::FilePath filename(files.Name(5));
+ char header[kBlockHeaderSize];
+ memset(header, 'a', kBlockHeaderSize);
+ EXPECT_EQ(kBlockHeaderSize,
+ file_util::WriteFile(filename, header, kBlockHeaderSize));
+
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+
+ // The file should not have been changed (it is still invalid).
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+}
+
+// Tests that we generate the correct file stats.
+TEST_F(DiskCacheTest, BlockFiles_Stats) {
+ ASSERT_TRUE(CopyTestCache("remove_load1"));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(false));
+ int used, load;
+
+ files.GetFileStats(0, &used, &load);
+ EXPECT_EQ(101, used);
+ EXPECT_EQ(9, load);
+
+ files.GetFileStats(1, &used, &load);
+ EXPECT_EQ(203, used);
+ EXPECT_EQ(19, load);
+
+ files.GetFileStats(2, &used, &load);
+ EXPECT_EQ(0, used);
+ EXPECT_EQ(0, load);
+}
+
+// Tests that we add and remove blocks correctly.
+TEST_F(DiskCacheTest, AllocationMap) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Create a bunch of entries.
+ const int kSize = 100;
+ Addr address[kSize];
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ int block_size = i % 4 + 1;
+ EXPECT_TRUE(files.CreateBlock(BLOCK_1K, block_size, &address[i]));
+ EXPECT_EQ(BLOCK_1K, address[i].file_type());
+ EXPECT_EQ(block_size, address[i].num_blocks());
+ int start = address[i].start_block();
+ EXPECT_EQ(start / 4, (start + block_size - 1) / 4);
+ }
+
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_TRUE(files.IsValid(address[i]));
+ }
+
+ // The first part of the allocation map should be completely filled. We used
+ // 10 bits per each four entries, so 250 bits total.
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(files.GetFile(address[0])->buffer());
+ uint8* buffer = reinterpret_cast<uint8*>(&header->allocation_map);
+ for (int i =0; i < 29; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_EQ(0xff, buffer[i]);
+ }
+
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ files.DeleteBlock(address[i], false);
+ }
+
+ // The allocation map should be empty.
+ for (int i =0; i < 50; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_EQ(0, buffer[i]);
+ }
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/cache_creator.cc b/chromium/net/disk_cache/cache_creator.cc
new file mode 100644
index 00000000000..07a26c9d858
--- /dev/null
+++ b/chromium/net/disk_cache/cache_creator.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 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.
+
+#include "base/files/file_path.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/cache_type.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/mem_backend_impl.h"
+#include "net/disk_cache/simple/simple_backend_impl.h"
+
+#ifdef USE_TRACING_CACHE_BACKEND
+#include "net/disk_cache/tracing_cache_backend.h"
+#endif
+
+namespace {
+
+// Builds an instance of the backend depending on platform, type, experiments
+// etc. Takes care of the retry state. This object will self-destroy when
+// finished.
+class CacheCreator {
+ public:
+ CacheCreator(const base::FilePath& path, bool force, int max_bytes,
+ net::CacheType type, net::BackendType backend_type, uint32 flags,
+ base::MessageLoopProxy* thread, net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback);
+
+ // Creates the backend.
+ int Run();
+
+ private:
+ ~CacheCreator();
+
+ void DoCallback(int result);
+
+ void OnIOComplete(int result);
+
+ const base::FilePath path_;
+ bool force_;
+ bool retry_;
+ int max_bytes_;
+ net::CacheType type_;
+ net::BackendType backend_type_;
+ uint32 flags_;
+ scoped_refptr<base::MessageLoopProxy> thread_;
+ scoped_ptr<disk_cache::Backend>* backend_;
+ net::CompletionCallback callback_;
+ scoped_ptr<disk_cache::Backend> created_cache_;
+ net::NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(CacheCreator);
+};
+
+CacheCreator::CacheCreator(
+ const base::FilePath& path, bool force, int max_bytes,
+ net::CacheType type, net::BackendType backend_type, uint32 flags,
+ base::MessageLoopProxy* thread, net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback)
+ : path_(path),
+ force_(force),
+ retry_(false),
+ max_bytes_(max_bytes),
+ type_(type),
+ backend_type_(backend_type),
+ flags_(flags),
+ thread_(thread),
+ backend_(backend),
+ callback_(callback),
+ net_log_(net_log) {
+}
+
+CacheCreator::~CacheCreator() {
+}
+
+int CacheCreator::Run() {
+ // TODO(gavinp,pasko): While simple backend development proceeds, we're only
+ // testing it against net::DISK_CACHE. Turn it on for more cache types as
+ // appropriate.
+ if (backend_type_ == net::CACHE_BACKEND_SIMPLE && type_ == net::DISK_CACHE) {
+ disk_cache::SimpleBackendImpl* simple_cache =
+ new disk_cache::SimpleBackendImpl(path_, max_bytes_, type_,
+ thread_.get(), net_log_);
+ created_cache_.reset(simple_cache);
+ return simple_cache->Init(
+ base::Bind(&CacheCreator::OnIOComplete, base::Unretained(this)));
+ }
+ disk_cache::BackendImpl* new_cache =
+ new disk_cache::BackendImpl(path_, thread_.get(), net_log_);
+ created_cache_.reset(new_cache);
+ new_cache->SetMaxSize(max_bytes_);
+ new_cache->SetType(type_);
+ new_cache->SetFlags(flags_);
+ int rv = new_cache->Init(
+ base::Bind(&CacheCreator::OnIOComplete, base::Unretained(this)));
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+ return rv;
+}
+
+void CacheCreator::DoCallback(int result) {
+ DCHECK_NE(net::ERR_IO_PENDING, result);
+ if (result == net::OK) {
+#ifndef USE_TRACING_CACHE_BACKEND
+ *backend_ = created_cache_.Pass();
+#else
+ *backend_.reset(
+ new disk_cache::TracingCacheBackend(created_cache_.Pass()));
+#endif
+ } else {
+ LOG(ERROR) << "Unable to create cache";
+ }
+ callback_.Run(result);
+ delete this;
+}
+
+// If the initialization of the cache fails, and |force| is true, we will
+// discard the whole cache and create a new one.
+void CacheCreator::OnIOComplete(int result) {
+ if (result == net::OK || !force_ || retry_)
+ return DoCallback(result);
+
+ // This is a failure and we are supposed to try again, so delete the object,
+ // delete all the files, and try again.
+ retry_ = true;
+ created_cache_.reset();
+ if (!disk_cache::DelayedCacheCleanup(path_))
+ return DoCallback(result);
+
+ // The worker thread will start deleting files soon, but the original folder
+ // is not there anymore... let's create a new set of files.
+ int rv = Run();
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+int CreateCacheBackend(net::CacheType type,
+ net::BackendType backend_type,
+ const base::FilePath& path,
+ int max_bytes,
+ bool force, base::MessageLoopProxy* thread,
+ net::NetLog* net_log, scoped_ptr<Backend>* backend,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (type == net::MEMORY_CACHE) {
+ *backend = disk_cache::MemBackendImpl::CreateBackend(max_bytes, net_log);
+ return *backend ? net::OK : net::ERR_FAILED;
+ }
+ DCHECK(thread);
+ CacheCreator* creator = new CacheCreator(path, force, max_bytes, type,
+ backend_type, kNone,
+ thread, net_log, backend, callback);
+ return creator->Run();
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/cache_util.cc b/chromium/net/disk_cache/cache_util.cc
new file mode 100644
index 00000000000..7389960a16a
--- /dev/null
+++ b/chromium/net/disk_cache/cache_util.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/cache_util.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/location.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
+
+namespace {
+
+const int kMaxOldFolders = 100;
+
+// Returns a fully qualified name from path and name, using a given name prefix
+// and index number. For instance, if the arguments are "/foo", "bar" and 5, it
+// will return "/foo/old_bar_005".
+base::FilePath GetPrefixedName(const base::FilePath& path,
+ const std::string& name,
+ int index) {
+ std::string tmp = base::StringPrintf("%s%s_%03d", "old_",
+ name.c_str(), index);
+ return path.AppendASCII(tmp);
+}
+
+// This is a simple callback to cleanup old caches.
+void CleanupCallback(const base::FilePath& path, const std::string& name) {
+ for (int i = 0; i < kMaxOldFolders; i++) {
+ base::FilePath to_delete = GetPrefixedName(path, name, i);
+ disk_cache::DeleteCache(to_delete, true);
+ }
+}
+
+// Returns a full path to rename the current cache, in order to delete it. path
+// is the current folder location, and name is the current folder name.
+base::FilePath GetTempCacheName(const base::FilePath& path,
+ const std::string& name) {
+ // We'll attempt to have up to kMaxOldFolders folders for deletion.
+ for (int i = 0; i < kMaxOldFolders; i++) {
+ base::FilePath to_delete = GetPrefixedName(path, name, i);
+ if (!base::PathExists(to_delete))
+ return to_delete;
+ }
+ return base::FilePath();
+}
+
+} // namespace
+
+namespace disk_cache {
+
+void DeleteCache(const base::FilePath& path, bool remove_folder) {
+ if (remove_folder) {
+ if (!base::DeleteFile(path, /* recursive */ true))
+ LOG(WARNING) << "Unable to delete cache folder.";
+ return;
+ }
+
+ base::FileEnumerator iter(
+ path,
+ /* recursive */ false,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ for (base::FilePath file = iter.Next(); !file.value().empty();
+ file = iter.Next()) {
+ if (!base::DeleteFile(file, /* recursive */ true)) {
+ LOG(WARNING) << "Unable to delete cache.";
+ return;
+ }
+ }
+}
+
+// In order to process a potentially large number of files, we'll rename the
+// cache directory to old_ + original_name + number, (located on the same parent
+// directory), and use a worker thread to delete all the files on all the stale
+// cache directories. The whole process can still fail if we are not able to
+// rename the cache directory (for instance due to a sharing violation), and in
+// that case a cache for this profile (on the desired path) cannot be created.
+bool DelayedCacheCleanup(const base::FilePath& full_path) {
+ // GetTempCacheName() and MoveCache() use synchronous file
+ // operations.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ base::FilePath current_path = full_path.StripTrailingSeparators();
+
+ base::FilePath path = current_path.DirName();
+ base::FilePath name = current_path.BaseName();
+#if defined(OS_POSIX)
+ std::string name_str = name.value();
+#elif defined(OS_WIN)
+ // We created this file so it should only contain ASCII.
+ std::string name_str = WideToASCII(name.value());
+#endif
+
+ base::FilePath to_delete = GetTempCacheName(path, name_str);
+ if (to_delete.empty()) {
+ LOG(ERROR) << "Unable to get another cache folder";
+ return false;
+ }
+
+ if (!disk_cache::MoveCache(full_path, to_delete)) {
+ LOG(ERROR) << "Unable to move cache folder " << full_path.value() << " to "
+ << to_delete.value();
+ return false;
+ }
+
+ base::WorkerPool::PostTask(
+ FROM_HERE, base::Bind(&CleanupCallback, path, name_str), true);
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/cache_util.h b/chromium/net/disk_cache/cache_util.h
new file mode 100644
index 00000000000..2005ba5e240
--- /dev/null
+++ b/chromium/net/disk_cache/cache_util.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 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_DISK_CACHE_CACHE_UTIL_H_
+#define NET_DISK_CACHE_CACHE_UTIL_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace disk_cache {
+
+// Moves the cache files from the given path to another location.
+// Fails if the destination exists already, or if it doesn't have
+// permission for the operation. This is basically a rename operation
+// for the cache directory. Returns true if successful. On ChromeOS,
+// this moves the cache contents, and leaves the empty cache
+// directory.
+NET_EXPORT_PRIVATE bool MoveCache(const base::FilePath& from_path,
+ const base::FilePath& to_path);
+
+// Deletes the cache files stored on |path|, and optionally also attempts to
+// delete the folder itself.
+NET_EXPORT_PRIVATE void DeleteCache(const base::FilePath& path,
+ bool remove_folder);
+
+// Deletes a cache file.
+NET_EXPORT_PRIVATE bool DeleteCacheFile(const base::FilePath& name);
+
+// Renames cache directory synchronously and fires off a background cleanup
+// task. Used by cache creator itself or by backends for self-restart on error.
+bool DelayedCacheCleanup(const base::FilePath& full_path);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_CACHE_UTIL_H_
diff --git a/chromium/net/disk_cache/cache_util_posix.cc b/chromium/net/disk_cache/cache_util_posix.cc
new file mode 100644
index 00000000000..b33c560a000
--- /dev/null
+++ b/chromium/net/disk_cache/cache_util_posix.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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/disk_cache/cache_util.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+
+namespace disk_cache {
+
+bool MoveCache(const base::FilePath& from_path, const base::FilePath& to_path) {
+#if defined(OS_CHROMEOS)
+ // For ChromeOS, we don't actually want to rename the cache
+ // directory, because if we do, then it'll get recreated through the
+ // encrypted filesystem (with encrypted names), and we won't be able
+ // to see these directories anymore in an unmounted encrypted
+ // filesystem, so we just move each item in the cache to a new
+ // directory.
+ if (!file_util::CreateDirectory(to_path)) {
+ LOG(ERROR) << "Unable to create destination cache directory.";
+ return false;
+ }
+ base::FileEnumerator iter(from_path, false /* not recursive */,
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
+ for (base::FilePath name = iter.Next(); !name.value().empty();
+ name = iter.Next()) {
+ base::FilePath destination = to_path.Append(name.BaseName());
+ if (!base::Move(name, destination)) {
+ LOG(ERROR) << "Unable to move cache item.";
+ return false;
+ }
+ }
+ return true;
+#else
+ return base::Move(from_path, to_path);
+#endif
+}
+
+bool DeleteCacheFile(const base::FilePath& name) {
+ return base::DeleteFile(name, false);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/cache_util_unittest.cc b/chromium/net/disk_cache/cache_util_unittest.cc
new file mode 100644
index 00000000000..d2e76054f7f
--- /dev/null
+++ b/chromium/net/disk_cache/cache_util_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "net/disk_cache/cache_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace disk_cache {
+
+class CacheUtilTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+ ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
+ cache_dir_ = tmp_dir_.path().Append(FILE_PATH_LITERAL("Cache"));
+ file1_ = base::FilePath(cache_dir_.Append(FILE_PATH_LITERAL("file01")));
+ file2_ = base::FilePath(cache_dir_.Append(FILE_PATH_LITERAL(".file02")));
+ dir1_ = base::FilePath(cache_dir_.Append(FILE_PATH_LITERAL("dir01")));
+ file3_ = base::FilePath(dir1_.Append(FILE_PATH_LITERAL("file03")));
+ ASSERT_TRUE(file_util::CreateDirectory(cache_dir_));
+ FILE *fp = file_util::OpenFile(file1_, "w");
+ ASSERT_TRUE(fp != NULL);
+ file_util::CloseFile(fp);
+ fp = file_util::OpenFile(file2_, "w");
+ ASSERT_TRUE(fp != NULL);
+ file_util::CloseFile(fp);
+ ASSERT_TRUE(file_util::CreateDirectory(dir1_));
+ fp = file_util::OpenFile(file3_, "w");
+ ASSERT_TRUE(fp != NULL);
+ file_util::CloseFile(fp);
+ dest_dir_ = tmp_dir_.path().Append(FILE_PATH_LITERAL("old_Cache_001"));
+ dest_file1_ = base::FilePath(dest_dir_.Append(FILE_PATH_LITERAL("file01")));
+ dest_file2_ =
+ base::FilePath(dest_dir_.Append(FILE_PATH_LITERAL(".file02")));
+ dest_dir1_ = base::FilePath(dest_dir_.Append(FILE_PATH_LITERAL("dir01")));
+ }
+
+ protected:
+ base::ScopedTempDir tmp_dir_;
+ base::FilePath cache_dir_;
+ base::FilePath file1_;
+ base::FilePath file2_;
+ base::FilePath dir1_;
+ base::FilePath file3_;
+ base::FilePath dest_dir_;
+ base::FilePath dest_file1_;
+ base::FilePath dest_file2_;
+ base::FilePath dest_dir1_;
+};
+
+TEST_F(CacheUtilTest, MoveCache) {
+ EXPECT_TRUE(disk_cache::MoveCache(cache_dir_, dest_dir_));
+ EXPECT_TRUE(base::PathExists(dest_dir_));
+ EXPECT_TRUE(base::PathExists(dest_file1_));
+ EXPECT_TRUE(base::PathExists(dest_file2_));
+ EXPECT_TRUE(base::PathExists(dest_dir1_));
+#if defined(OS_CHROMEOS)
+ EXPECT_TRUE(base::PathExists(cache_dir_)); // old cache dir stays
+#else
+ EXPECT_FALSE(base::PathExists(cache_dir_)); // old cache is gone
+#endif
+ EXPECT_FALSE(base::PathExists(file1_));
+ EXPECT_FALSE(base::PathExists(file2_));
+ EXPECT_FALSE(base::PathExists(dir1_));
+}
+
+TEST_F(CacheUtilTest, DeleteCache) {
+ disk_cache::DeleteCache(cache_dir_, false);
+ EXPECT_TRUE(base::PathExists(cache_dir_)); // cache dir stays
+ EXPECT_FALSE(base::PathExists(dir1_));
+ EXPECT_FALSE(base::PathExists(file1_));
+ EXPECT_FALSE(base::PathExists(file2_));
+ EXPECT_FALSE(base::PathExists(file3_));
+}
+
+TEST_F(CacheUtilTest, DeleteCacheAndDir) {
+ disk_cache::DeleteCache(cache_dir_, true);
+ EXPECT_FALSE(base::PathExists(cache_dir_)); // cache dir is gone
+ EXPECT_FALSE(base::PathExists(dir1_));
+ EXPECT_FALSE(base::PathExists(file1_));
+ EXPECT_FALSE(base::PathExists(file2_));
+ EXPECT_FALSE(base::PathExists(file3_));
+}
+
+TEST_F(CacheUtilTest, DeleteCacheFile) {
+ EXPECT_TRUE(disk_cache::DeleteCacheFile(file1_));
+ EXPECT_FALSE(base::PathExists(file1_));
+ EXPECT_TRUE(base::PathExists(cache_dir_)); // cache dir stays
+ EXPECT_TRUE(base::PathExists(dir1_));
+ EXPECT_TRUE(base::PathExists(file3_));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/cache_util_win.cc b/chromium/net/disk_cache/cache_util_win.cc
new file mode 100644
index 00000000000..51ed2a032e2
--- /dev/null
+++ b/chromium/net/disk_cache/cache_util_win.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/cache_util.h"
+
+#include <windows.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/win/scoped_handle.h"
+
+namespace disk_cache {
+
+bool MoveCache(const base::FilePath& from_path, const base::FilePath& to_path) {
+ // I don't want to use the shell version of move because if something goes
+ // wrong, that version will attempt to move file by file and fail at the end.
+ if (!MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), 0)) {
+ LOG(ERROR) << "Unable to move the cache: " << GetLastError();
+ return false;
+ }
+ return true;
+}
+
+bool DeleteCacheFile(const base::FilePath& name) {
+ // We do a simple delete, without ever falling back to SHFileOperation, as the
+ // version from base does.
+ if (!DeleteFile(name.value().c_str())) {
+ // There is an error, but we share delete access so let's see if there is a
+ // file to open. Note that this code assumes that we have a handle to the
+ // file at all times (even now), so nobody can have a handle that prevents
+ // us from opening the file again (unless it was deleted).
+ DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ DWORD access = SYNCHRONIZE;
+ base::win::ScopedHandle file(CreateFile(
+ name.value().c_str(), access, sharing, NULL, OPEN_EXISTING, 0, NULL));
+ if (file.IsValid())
+ return false;
+
+ // Most likely there is no file to open... and that's what we wanted.
+ }
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/disk_cache.h b/chromium/net/disk_cache/disk_cache.h
new file mode 100644
index 00000000000..3ae8bf4369b
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache.h
@@ -0,0 +1,324 @@
+// Copyright (c) 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.
+
+// Defines the public interface of the disk cache. For more details see
+// http://dev.chromium.org/developers/design-documents/network-stack/disk-cache
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_H_
+#define NET_DISK_CACHE_DISK_CACHE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/cache_type.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class FilePath;
+class MessageLoopProxy;
+}
+
+namespace net {
+class IOBuffer;
+class NetLog;
+}
+
+namespace disk_cache {
+
+class Entry;
+class Backend;
+
+// Returns an instance of a Backend of the given |type|. |path| points to a
+// folder where the cached data will be stored (if appropriate). This cache
+// instance must be the only object that will be reading or writing files to
+// that folder. The returned object should be deleted when not needed anymore.
+// If |force| is true, and there is a problem with the cache initialization, the
+// files will be deleted and a new set will be created. |max_bytes| is the
+// maximum size the cache can grow to. If zero is passed in as |max_bytes|, the
+// cache will determine the value to use. |thread| can be used to perform IO
+// operations if a dedicated thread is required; a valid value is expected for
+// any backend that performs operations on a disk. The returned pointer can be
+// NULL if a fatal error is found. The actual return value of the function is a
+// net error code. If this function returns ERR_IO_PENDING, the |callback| will
+// be invoked when a backend is available or a fatal error condition is reached.
+// The pointer to receive the |backend| must remain valid until the operation
+// completes (the callback is notified).
+NET_EXPORT int CreateCacheBackend(net::CacheType type,
+ net::BackendType backend_type,
+ const base::FilePath& path,
+ int max_bytes,
+ bool force,
+ base::MessageLoopProxy* thread,
+ net::NetLog* net_log,
+ scoped_ptr<Backend>* backend,
+ const net::CompletionCallback& callback);
+
+// The root interface for a disk cache instance.
+class NET_EXPORT Backend {
+ public:
+ typedef net::CompletionCallback CompletionCallback;
+
+ // If the backend is destroyed when there are operations in progress (any
+ // callback that has not been invoked yet), this method cancels said
+ // operations so the callbacks are not invoked, possibly leaving the work
+ // half way (for instance, dooming just a few entries). Note that pending IO
+ // for a given Entry (as opposed to the Backend) will still generate a
+ // callback from within this method.
+ virtual ~Backend() {}
+
+ // Returns the type of this cache.
+ virtual net::CacheType GetCacheType() const = 0;
+
+ // Returns the number of entries in the cache.
+ virtual int32 GetEntryCount() const = 0;
+
+ // Opens an existing entry. Upon success, |entry| holds a pointer to an Entry
+ // object representing the specified disk cache entry. When the entry pointer
+ // is no longer needed, its Close method should be called. The return value is
+ // a net error code. If this method returns ERR_IO_PENDING, the |callback|
+ // will be invoked when the entry is available. The pointer to receive the
+ // |entry| must remain valid until the operation completes.
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) = 0;
+
+ // Creates a new entry. Upon success, the out param holds a pointer to an
+ // Entry object representing the newly created disk cache entry. When the
+ // entry pointer is no longer needed, its Close method should be called. The
+ // return value is a net error code. If this method returns ERR_IO_PENDING,
+ // the |callback| will be invoked when the entry is available. The pointer to
+ // receive the |entry| must remain valid until the operation completes.
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) = 0;
+
+ // Marks the entry, specified by the given key, for deletion. The return value
+ // is a net error code. If this method returns ERR_IO_PENDING, the |callback|
+ // will be invoked after the entry is doomed.
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) = 0;
+
+ // Marks all entries for deletion. The return value is a net error code. If
+ // this method returns ERR_IO_PENDING, the |callback| will be invoked when the
+ // operation completes.
+ virtual int DoomAllEntries(const CompletionCallback& callback) = 0;
+
+ // Marks a range of entries for deletion. This supports unbounded deletes in
+ // either direction by using null Time values for either argument. The return
+ // value is a net error code. If this method returns ERR_IO_PENDING, the
+ // |callback| will be invoked when the operation completes.
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) = 0;
+
+ // Marks all entries accessed since |initial_time| for deletion. The return
+ // value is a net error code. If this method returns ERR_IO_PENDING, the
+ // |callback| will be invoked when the operation completes.
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) = 0;
+
+ // Enumerates the cache. Initialize |iter| to NULL before calling this method
+ // the first time. That will cause the enumeration to start at the head of
+ // the cache. For subsequent calls, pass the same |iter| pointer again without
+ // changing its value. This method returns ERR_FAILED when there are no more
+ // entries to enumerate. When the entry pointer is no longer needed, its
+ // Close method should be called. The return value is a net error code. If
+ // this method returns ERR_IO_PENDING, the |callback| will be invoked when the
+ // |next_entry| is available. The pointer to receive the |next_entry| must
+ // remain valid until the operation completes.
+ //
+ // NOTE: This method does not modify the last_used field of the entry, and
+ // therefore it does not impact the eviction ranking of the entry. However,
+ // an enumeration will go through all entries on the cache only if the cache
+ // is not modified while the enumeration is taking place. Significantly
+ // altering the entry pointed by |iter| (for example, deleting the entry) will
+ // invalidate |iter|. Performing operations on an entry that modify the entry
+ // may result in loops in the iteration, skipped entries or similar.
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) = 0;
+
+ // Releases iter without returning the next entry. Whenever OpenNextEntry()
+ // returns true, but the caller is not interested in continuing the
+ // enumeration by calling OpenNextEntry() again, the enumeration must be
+ // ended by calling this method with iter returned by OpenNextEntry().
+ virtual void EndEnumeration(void** iter) = 0;
+
+ // Return a list of cache statistics.
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) = 0;
+
+ // Called whenever an external cache in the system reuses the resource
+ // referred to by |key|.
+ virtual void OnExternalCacheHit(const std::string& key) = 0;
+};
+
+// This interface represents an entry in the disk cache.
+class NET_EXPORT Entry {
+ public:
+ typedef net::CompletionCallback CompletionCallback;
+ typedef net::IOBuffer IOBuffer;
+
+ // Marks this cache entry for deletion.
+ virtual void Doom() = 0;
+
+ // Releases this entry. Calling this method does not cancel pending IO
+ // operations on this entry. Even after the last reference to this object has
+ // been released, pending completion callbacks may be invoked.
+ virtual void Close() = 0;
+
+ // Returns the key associated with this cache entry.
+ virtual std::string GetKey() const = 0;
+
+ // Returns the time when this cache entry was last used.
+ virtual base::Time GetLastUsed() const = 0;
+
+ // Returns the time when this cache entry was last modified.
+ virtual base::Time GetLastModified() const = 0;
+
+ // Returns the size of the cache data with the given index.
+ virtual int32 GetDataSize(int index) const = 0;
+
+ // Copies cached data into the given buffer of length |buf_len|. Returns the
+ // number of bytes read or a network error code. If this function returns
+ // ERR_IO_PENDING, the completion callback will be called on the current
+ // thread when the operation completes, and a reference to |buf| will be
+ // retained until the callback is called. Note that as long as the function
+ // does not complete immediately, the callback will always be invoked, even
+ // after Close has been called; in other words, the caller may close this
+ // entry without having to wait for all the callbacks, and still rely on the
+ // cleanup performed from the callback code.
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Copies data from the given buffer of length |buf_len| into the cache.
+ // Returns the number of bytes written or a network error code. If this
+ // function returns ERR_IO_PENDING, the completion callback will be called
+ // on the current thread when the operation completes, and a reference to
+ // |buf| will be retained until the callback is called. Note that as long as
+ // the function does not complete immediately, the callback will always be
+ // invoked, even after Close has been called; in other words, the caller may
+ // close this entry without having to wait for all the callbacks, and still
+ // rely on the cleanup performed from the callback code.
+ // If truncate is true, this call will truncate the stored data at the end of
+ // what we are writing here.
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) = 0;
+
+ // Sparse entries support:
+ //
+ // A Backend implementation can support sparse entries, so the cache keeps
+ // track of which parts of the entry have been written before. The backend
+ // will never return data that was not written previously, so reading from
+ // such region will return 0 bytes read (or actually the number of bytes read
+ // before reaching that region).
+ //
+ // There are only two streams for sparse entries: a regular control stream
+ // (index 0) that must be accessed through the regular API (ReadData and
+ // WriteData), and one sparse stream that must me accessed through the sparse-
+ // aware API that follows. Calling a non-sparse aware method with an index
+ // argument other than 0 is a mistake that results in implementation specific
+ // behavior. Using a sparse-aware method with an entry that was not stored
+ // using the same API, or with a backend that doesn't support sparse entries
+ // will return ERR_CACHE_OPERATION_NOT_SUPPORTED.
+ //
+ // The storage granularity of the implementation should be at least 1 KB. In
+ // other words, storing less than 1 KB may result in an implementation
+ // dropping the data completely, and writing at offsets not aligned with 1 KB,
+ // or with lengths not a multiple of 1 KB may result in the first or last part
+ // of the data being discarded. However, two consecutive writes should not
+ // result in a hole in between the two parts as long as they are sequential
+ // (the second one starts where the first one ended), and there is no other
+ // write between them.
+ //
+ // The Backend implementation is free to evict any range from the cache at any
+ // moment, so in practice, the previously stated granularity of 1 KB is not
+ // as bad as it sounds.
+ //
+ // The sparse methods don't support multiple simultaneous IO operations to the
+ // same physical entry, so in practice a single object should be instantiated
+ // for a given key at any given time. Once an operation has been issued, the
+ // caller should wait until it completes before starting another one. This
+ // requirement includes the case when an entry is closed while some operation
+ // is in progress and another object is instantiated; any IO operation will
+ // fail while the previous operation is still in-flight. In order to deal with
+ // this requirement, the caller could either wait until the operation
+ // completes before closing the entry, or call CancelSparseIO() before closing
+ // the entry, and call ReadyForSparseIO() on the new entry and wait for the
+ // callback before issuing new operations.
+
+ // Behaves like ReadData() except that this method is used to access sparse
+ // entries.
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Behaves like WriteData() except that this method is used to access sparse
+ // entries. |truncate| is not part of this interface because a sparse entry
+ // is not expected to be reused with new data. To delete the old data and
+ // start again, or to reduce the total size of the stream data (which implies
+ // that the content has changed), the whole entry should be doomed and
+ // re-created.
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Returns information about the currently stored portion of a sparse entry.
+ // |offset| and |len| describe a particular range that should be scanned to
+ // find out if it is stored or not. |start| will contain the offset of the
+ // first byte that is stored within this range, and the return value is the
+ // minimum number of consecutive stored bytes. Note that it is possible that
+ // this entry has stored more than the returned value. This method returns a
+ // net error code whenever the request cannot be completed successfully. If
+ // this method returns ERR_IO_PENDING, the |callback| will be invoked when the
+ // operation completes, and |start| must remain valid until that point.
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) = 0;
+
+ // Returns true if this entry could be a sparse entry or false otherwise. This
+ // is a quick test that may return true even if the entry is not really
+ // sparse. This method doesn't modify the state of this entry (it will not
+ // create sparse tracking data). GetAvailableRange or ReadSparseData can be
+ // used to perform a definitive test of whether an existing entry is sparse or
+ // not, but that method may modify the current state of the entry (making it
+ // sparse, for instance). The purpose of this method is to test an existing
+ // entry, but without generating actual IO to perform a thorough check.
+ virtual bool CouldBeSparse() const = 0;
+
+ // Cancels any pending sparse IO operation (if any). The completion callback
+ // of the operation in question will still be called when the operation
+ // finishes, but the operation will finish sooner when this method is used.
+ virtual void CancelSparseIO() = 0;
+
+ // Returns OK if this entry can be used immediately. If that is not the
+ // case, returns ERR_IO_PENDING and invokes the provided callback when this
+ // entry is ready to use. This method always returns OK for non-sparse
+ // entries, and returns ERR_IO_PENDING when a previous operation was cancelled
+ // (by calling CancelSparseIO), but the cache is still busy with it. If there
+ // is a pending operation that has not been cancelled, this method will return
+ // OK although another IO operation cannot be issued at this time; in this
+ // case the caller should just wait for the regular callback to be invoked
+ // instead of using this method to provide another callback.
+ //
+ // Note that CancelSparseIO may have been called on another instance of this
+ // object that refers to the same physical disk entry.
+ // Note: This method is deprecated.
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) = 0;
+
+ protected:
+ virtual ~Entry() {}
+};
+
+struct EntryDeleter {
+ void operator()(Entry* entry) {
+ // Note that |entry| is ref-counted.
+ entry->Close();
+ }
+};
+
+// Automatically closes an entry when it goes out of scope.
+typedef scoped_ptr<Entry, EntryDeleter> ScopedEntryPtr;
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_DISK_CACHE_H_
diff --git a/chromium/net/disk_cache/disk_cache_perftest.cc b/chromium/net/disk_cache/disk_cache_perftest.cc
new file mode 100644
index 00000000000..f7a1b5969c6
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache_perftest.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/hash.h"
+#include "base/perftimer.h"
+#include "base/strings/string_util.h"
+#include "base/test/test_file_util.h"
+#include "base/threading/thread.h"
+#include "base/timer/timer.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+using base::Time;
+
+namespace {
+
+struct TestEntry {
+ std::string key;
+ int data_len;
+};
+typedef std::vector<TestEntry> TestEntries;
+
+const int kMaxSize = 16 * 1024 - 1;
+
+// Creates num_entries on the cache, and writes 200 bytes of metadata and up
+// to kMaxSize of data to each entry.
+bool TimeWrite(int num_entries, disk_cache::Backend* cache,
+ TestEntries* entries) {
+ const int kSize1 = 200;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kMaxSize));
+
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kMaxSize, false);
+
+ int expected = 0;
+
+ MessageLoopHelper helper;
+ CallbackTest callback(&helper, true);
+
+ PerfTimeLogger timer("Write disk cache entries");
+
+ for (int i = 0; i < num_entries; i++) {
+ TestEntry entry;
+ entry.key = GenerateKey(true);
+ entry.data_len = rand() % kMaxSize;
+ entries->push_back(entry);
+
+ disk_cache::Entry* cache_entry;
+ net::TestCompletionCallback cb;
+ int rv = cache->CreateEntry(entry.key, &cache_entry, cb.callback());
+ if (net::OK != cb.GetResult(rv))
+ break;
+ int ret = cache_entry->WriteData(
+ 0, 0, buffer1.get(), kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (kSize1 != ret)
+ break;
+
+ ret = cache_entry->WriteData(
+ 1, 0, buffer2.get(), entry.data_len,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback)), false);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (entry.data_len != ret)
+ break;
+ cache_entry->Close();
+ }
+
+ helper.WaitUntilCacheIoFinished(expected);
+ timer.Done();
+
+ return (expected == helper.callbacks_called());
+}
+
+// Reads the data and metadata from each entry listed on |entries|.
+bool TimeRead(int num_entries, disk_cache::Backend* cache,
+ const TestEntries& entries, bool cold) {
+ const int kSize1 = 200;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kMaxSize));
+
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kMaxSize, false);
+
+ int expected = 0;
+
+ MessageLoopHelper helper;
+ CallbackTest callback(&helper, true);
+
+ const char* message = cold ? "Read disk cache entries (cold)" :
+ "Read disk cache entries (warm)";
+ PerfTimeLogger timer(message);
+
+ for (int i = 0; i < num_entries; i++) {
+ disk_cache::Entry* cache_entry;
+ net::TestCompletionCallback cb;
+ int rv = cache->OpenEntry(entries[i].key, &cache_entry, cb.callback());
+ if (net::OK != cb.GetResult(rv))
+ break;
+ int ret = cache_entry->ReadData(
+ 0, 0, buffer1.get(), kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback)));
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (kSize1 != ret)
+ break;
+
+ ret = cache_entry->ReadData(
+ 1, 0, buffer2.get(), entries[i].data_len,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback)));
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ else if (entries[i].data_len != ret)
+ break;
+ cache_entry->Close();
+ }
+
+ helper.WaitUntilCacheIoFinished(expected);
+ timer.Done();
+
+ return (expected == helper.callbacks_called());
+}
+
+int BlockSize() {
+ // We can use form 1 to 4 blocks.
+ return (rand() & 0x3) + 1;
+}
+
+} // namespace
+
+TEST_F(DiskCacheTest, Hash) {
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ PerfTimeLogger timer("Hash disk cache keys");
+ for (int i = 0; i < 300000; i++) {
+ std::string key = GenerateKey(true);
+ base::Hash(key);
+ }
+ timer.Done();
+}
+
+TEST_F(DiskCacheTest, CacheBackendPerformance) {
+ base::Thread cache_thread("CacheThread");
+ ASSERT_TRUE(cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+
+ ASSERT_TRUE(CleanupCacheDir());
+ net::TestCompletionCallback cb;
+ scoped_ptr<disk_cache::Backend> cache;
+ int rv = disk_cache::CreateCacheBackend(
+ net::DISK_CACHE, net::CACHE_BACKEND_BLOCKFILE, cache_path_, 0, false,
+ cache_thread.message_loop_proxy().get(), NULL, &cache, cb.callback());
+
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ TestEntries entries;
+ int num_entries = 1000;
+
+ EXPECT_TRUE(TimeWrite(num_entries, cache.get(), &entries));
+
+ base::MessageLoop::current()->RunUntilIdle();
+ cache.reset();
+
+ ASSERT_TRUE(file_util::EvictFileFromSystemCache(
+ cache_path_.AppendASCII("index")));
+ ASSERT_TRUE(file_util::EvictFileFromSystemCache(
+ cache_path_.AppendASCII("data_0")));
+ ASSERT_TRUE(file_util::EvictFileFromSystemCache(
+ cache_path_.AppendASCII("data_1")));
+ ASSERT_TRUE(file_util::EvictFileFromSystemCache(
+ cache_path_.AppendASCII("data_2")));
+ ASSERT_TRUE(file_util::EvictFileFromSystemCache(
+ cache_path_.AppendASCII("data_3")));
+
+ rv = disk_cache::CreateCacheBackend(
+ net::DISK_CACHE, net::CACHE_BACKEND_BLOCKFILE, cache_path_, 0, false,
+ cache_thread.message_loop_proxy().get(), NULL, &cache, cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+
+ EXPECT_TRUE(TimeRead(num_entries, cache.get(), entries, true));
+
+ EXPECT_TRUE(TimeRead(num_entries, cache.get(), entries, false));
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Creating and deleting "entries" on a block-file is something quite frequent
+// (after all, almost everything is stored on block files). The operation is
+// almost free when the file is empty, but can be expensive if the file gets
+// fragmented, or if we have multiple files. This test measures that scenario,
+// by using multiple, highly fragmented files.
+TEST_F(DiskCacheTest, BlockFilesPerformance) {
+ ASSERT_TRUE(CleanupCacheDir());
+
+ disk_cache::BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ const int kNumEntries = 60000;
+ disk_cache::Addr* address = new disk_cache::Addr[kNumEntries];
+
+ PerfTimeLogger timer1("Fill three block-files");
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < kNumEntries; i++) {
+ EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, BlockSize(),
+ &address[i]));
+ }
+
+ timer1.Done();
+ PerfTimeLogger timer2("Create and delete blocks");
+
+ for (int i = 0; i < 200000; i++) {
+ int entry = rand() * (kNumEntries / RAND_MAX + 1);
+ if (entry >= kNumEntries)
+ entry = 0;
+
+ files.DeleteBlock(address[entry], false);
+ EXPECT_TRUE(files.CreateBlock(disk_cache::RANKINGS, BlockSize(),
+ &address[entry]));
+ }
+
+ timer2.Done();
+ base::MessageLoop::current()->RunUntilIdle();
+ delete[] address;
+}
diff --git a/chromium/net/disk_cache/disk_cache_test_base.cc b/chromium/net/disk_cache/disk_cache_test_base.cc
new file mode 100644
index 00000000000..dc7bb6c5fb2
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache_test_base.cc
@@ -0,0 +1,307 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/disk_cache_test_base.h"
+
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/mem_backend_impl.h"
+#include "net/disk_cache/simple/simple_backend_impl.h"
+#include "net/disk_cache/simple/simple_index.h"
+
+DiskCacheTest::DiskCacheTest() {
+ CHECK(temp_dir_.CreateUniqueTempDir());
+ cache_path_ = temp_dir_.path();
+ if (!base::MessageLoop::current())
+ message_loop_.reset(new base::MessageLoopForIO());
+}
+
+DiskCacheTest::~DiskCacheTest() {
+}
+
+bool DiskCacheTest::CopyTestCache(const std::string& name) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("cache_tests");
+ path = path.AppendASCII(name);
+
+ if (!CleanupCacheDir())
+ return false;
+ return base::CopyDirectory(path, cache_path_, false);
+}
+
+bool DiskCacheTest::CleanupCacheDir() {
+ return DeleteCache(cache_path_);
+}
+
+void DiskCacheTest::TearDown() {
+ base::RunLoop().RunUntilIdle();
+}
+
+DiskCacheTestWithCache::DiskCacheTestWithCache()
+ : cache_impl_(NULL),
+ simple_cache_impl_(NULL),
+ mem_cache_(NULL),
+ mask_(0),
+ size_(0),
+ type_(net::DISK_CACHE),
+ memory_only_(false),
+ simple_cache_mode_(false),
+ simple_cache_wait_for_index_(true),
+ force_creation_(false),
+ new_eviction_(false),
+ first_cleanup_(true),
+ integrity_(true),
+ use_current_thread_(false),
+ cache_thread_("CacheThread") {
+}
+
+DiskCacheTestWithCache::~DiskCacheTestWithCache() {}
+
+void DiskCacheTestWithCache::InitCache() {
+ if (memory_only_)
+ InitMemoryCache();
+ else
+ InitDiskCache();
+
+ ASSERT_TRUE(NULL != cache_);
+ if (first_cleanup_)
+ ASSERT_EQ(0, cache_->GetEntryCount());
+}
+
+// We are expected to leak memory when simulating crashes.
+void DiskCacheTestWithCache::SimulateCrash() {
+ ASSERT_TRUE(!memory_only_);
+ net::TestCompletionCallback cb;
+ int rv = cache_impl_->FlushQueueForTest(cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ cache_impl_->ClearRefCountForTest();
+
+ cache_.reset();
+ EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_));
+
+ CreateBackend(disk_cache::kNoRandom, &cache_thread_);
+}
+
+void DiskCacheTestWithCache::SetTestMode() {
+ ASSERT_TRUE(!memory_only_);
+ cache_impl_->SetUnitTestMode();
+}
+
+void DiskCacheTestWithCache::SetMaxSize(int size) {
+ size_ = size;
+ if (simple_cache_impl_)
+ EXPECT_TRUE(simple_cache_impl_->SetMaxSize(size));
+
+ if (cache_impl_)
+ EXPECT_TRUE(cache_impl_->SetMaxSize(size));
+
+ if (mem_cache_)
+ EXPECT_TRUE(mem_cache_->SetMaxSize(size));
+}
+
+int DiskCacheTestWithCache::OpenEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->OpenEntry(key, entry, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->CreateEntry(key, entry, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntry(const std::string& key) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->DoomEntry(key, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomAllEntries() {
+ net::TestCompletionCallback cb;
+ int rv = cache_->DoomAllEntries(cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->DoomEntriesBetween(initial_time, end_time, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::DoomEntriesSince(const base::Time initial_time) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->DoomEntriesSince(initial_time, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::OpenNextEntry(void** iter,
+ disk_cache::Entry** next_entry) {
+ net::TestCompletionCallback cb;
+ int rv = cache_->OpenNextEntry(iter, next_entry, cb.callback());
+ return cb.GetResult(rv);
+}
+
+void DiskCacheTestWithCache::FlushQueueForTest() {
+ if (memory_only_ || !cache_impl_)
+ return;
+
+ net::TestCompletionCallback cb;
+ int rv = cache_impl_->FlushQueueForTest(cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+}
+
+void DiskCacheTestWithCache::RunTaskForTest(const base::Closure& closure) {
+ if (memory_only_ || !cache_impl_) {
+ closure.Run();
+ return;
+ }
+
+ net::TestCompletionCallback cb;
+ int rv = cache_impl_->RunTaskForTest(closure, cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+}
+
+int DiskCacheTestWithCache::ReadData(disk_cache::Entry* entry, int index,
+ int offset, net::IOBuffer* buf, int len) {
+ net::TestCompletionCallback cb;
+ int rv = entry->ReadData(index, offset, buf, len, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::WriteData(disk_cache::Entry* entry, int index,
+ int offset, net::IOBuffer* buf, int len,
+ bool truncate) {
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(index, offset, buf, len, cb.callback(), truncate);
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::ReadSparseData(disk_cache::Entry* entry,
+ int64 offset, net::IOBuffer* buf,
+ int len) {
+ net::TestCompletionCallback cb;
+ int rv = entry->ReadSparseData(offset, buf, len, cb.callback());
+ return cb.GetResult(rv);
+}
+
+int DiskCacheTestWithCache::WriteSparseData(disk_cache::Entry* entry,
+ int64 offset,
+ net::IOBuffer* buf, int len) {
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteSparseData(offset, buf, len, cb.callback());
+ return cb.GetResult(rv);
+}
+
+void DiskCacheTestWithCache::TrimForTest(bool empty) {
+ RunTaskForTest(base::Bind(&disk_cache::BackendImpl::TrimForTest,
+ base::Unretained(cache_impl_),
+ empty));
+}
+
+void DiskCacheTestWithCache::TrimDeletedListForTest(bool empty) {
+ RunTaskForTest(base::Bind(&disk_cache::BackendImpl::TrimDeletedListForTest,
+ base::Unretained(cache_impl_),
+ empty));
+}
+
+void DiskCacheTestWithCache::AddDelay() {
+ base::Time initial = base::Time::Now();
+ while (base::Time::Now() <= initial) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+ };
+}
+
+void DiskCacheTestWithCache::TearDown() {
+ base::RunLoop().RunUntilIdle();
+ cache_.reset();
+ if (cache_thread_.IsRunning())
+ cache_thread_.Stop();
+
+ if (!memory_only_ && !simple_cache_mode_ && integrity_) {
+ EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_));
+ }
+
+ PlatformTest::TearDown();
+}
+
+void DiskCacheTestWithCache::InitMemoryCache() {
+ mem_cache_ = new disk_cache::MemBackendImpl(NULL);
+ cache_.reset(mem_cache_);
+ ASSERT_TRUE(cache_);
+
+ if (size_)
+ EXPECT_TRUE(mem_cache_->SetMaxSize(size_));
+
+ ASSERT_TRUE(mem_cache_->Init());
+}
+
+void DiskCacheTestWithCache::InitDiskCache() {
+ if (first_cleanup_)
+ ASSERT_TRUE(CleanupCacheDir());
+
+ if (!cache_thread_.IsRunning()) {
+ ASSERT_TRUE(cache_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+ }
+ ASSERT_TRUE(cache_thread_.message_loop() != NULL);
+
+ CreateBackend(disk_cache::kNoRandom, &cache_thread_);
+}
+
+void DiskCacheTestWithCache::CreateBackend(uint32 flags, base::Thread* thread) {
+ base::MessageLoopProxy* runner;
+ if (use_current_thread_)
+ runner = base::MessageLoopProxy::current().get();
+ else
+ runner = thread->message_loop_proxy().get();
+
+ if (simple_cache_mode_) {
+ net::TestCompletionCallback cb;
+ scoped_ptr<disk_cache::SimpleBackendImpl> simple_backend(
+ new disk_cache::SimpleBackendImpl(
+ cache_path_, size_, type_, make_scoped_refptr(runner).get(), NULL));
+ int rv = simple_backend->Init(cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+ simple_cache_impl_ = simple_backend.get();
+ cache_ = simple_backend.PassAs<disk_cache::Backend>();
+ if (simple_cache_wait_for_index_) {
+ net::TestCompletionCallback wait_for_index_cb;
+ rv = simple_cache_impl_->index()->ExecuteWhenReady(
+ wait_for_index_cb.callback());
+ ASSERT_EQ(net::OK, wait_for_index_cb.GetResult(rv));
+ }
+ return;
+ }
+
+ if (mask_)
+ cache_impl_ = new disk_cache::BackendImpl(cache_path_, mask_, runner, NULL);
+ else
+ cache_impl_ = new disk_cache::BackendImpl(cache_path_, runner, NULL);
+ cache_.reset(cache_impl_);
+ ASSERT_TRUE(cache_);
+ if (size_)
+ EXPECT_TRUE(cache_impl_->SetMaxSize(size_));
+ if (new_eviction_)
+ cache_impl_->SetNewEviction();
+ cache_impl_->SetType(type_);
+ cache_impl_->SetFlags(flags);
+ net::TestCompletionCallback cb;
+ int rv = cache_impl_->Init(cb.callback());
+ ASSERT_EQ(net::OK, cb.GetResult(rv));
+}
diff --git a/chromium/net/disk_cache/disk_cache_test_base.h b/chromium/net/disk_cache/disk_cache_test_base.h
new file mode 100644
index 00000000000..b7249062895
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache_test_base.h
@@ -0,0 +1,176 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
+#define NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "net/base/cache_type.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class IOBuffer;
+
+} // namespace net
+
+namespace disk_cache {
+
+class Backend;
+class BackendImpl;
+class Entry;
+class MemBackendImpl;
+class SimpleBackendImpl;
+
+} // namespace disk_cache
+
+// These tests can use the path service, which uses autoreleased objects on the
+// Mac, so this needs to be a PlatformTest. Even tests that do not require a
+// cache (and that do not need to be a DiskCacheTestWithCache) are susceptible
+// to this problem; all such tests should use TEST_F(DiskCacheTest, ...).
+class DiskCacheTest : public PlatformTest {
+ protected:
+ DiskCacheTest();
+ virtual ~DiskCacheTest();
+
+ // Copies a set of cache files from the data folder to the test folder.
+ bool CopyTestCache(const std::string& name);
+
+ // Deletes the contents of |cache_path_|.
+ bool CleanupCacheDir();
+
+ virtual void TearDown() OVERRIDE;
+
+ base::FilePath cache_path_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+};
+
+// Provides basic support for cache related tests.
+class DiskCacheTestWithCache : public DiskCacheTest {
+ protected:
+ DiskCacheTestWithCache();
+ virtual ~DiskCacheTestWithCache();
+
+ void CreateBackend(uint32 flags, base::Thread* thread);
+
+ void InitCache();
+ void SimulateCrash();
+ void SetTestMode();
+
+ void SetMemoryOnlyMode() {
+ memory_only_ = true;
+ }
+
+ void SetSimpleCacheMode() {
+ simple_cache_mode_ = true;
+ }
+
+ void SetMask(uint32 mask) {
+ mask_ = mask;
+ }
+
+ void SetMaxSize(int size);
+
+ // Deletes and re-creates the files on initialization errors.
+ void SetForceCreation() {
+ force_creation_ = true;
+ }
+
+ void SetNewEviction() {
+ new_eviction_ = true;
+ }
+
+ void DisableSimpleCacheWaitForIndex() {
+ simple_cache_wait_for_index_ = false;
+ }
+
+ void DisableFirstCleanup() {
+ first_cleanup_ = false;
+ }
+
+ void DisableIntegrityCheck() {
+ integrity_ = false;
+ }
+
+ void UseCurrentThread() {
+ use_current_thread_ = true;
+ }
+
+ void SetCacheType(net::CacheType type) {
+ type_ = type;
+ }
+
+ // Utility methods to access the cache and wait for each operation to finish.
+ int OpenEntry(const std::string& key, disk_cache::Entry** entry);
+ int CreateEntry(const std::string& key, disk_cache::Entry** entry);
+ int DoomEntry(const std::string& key);
+ int DoomAllEntries();
+ int DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ int DoomEntriesSince(const base::Time initial_time);
+ int OpenNextEntry(void** iter, disk_cache::Entry** next_entry);
+ void FlushQueueForTest();
+ void RunTaskForTest(const base::Closure& closure);
+ int ReadData(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int len);
+ int WriteData(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int len, bool truncate);
+ int ReadSparseData(disk_cache::Entry* entry, int64 offset, net::IOBuffer* buf,
+ int len);
+ int WriteSparseData(disk_cache::Entry* entry, int64 offset,
+ net::IOBuffer* buf, int len);
+
+ // Asks the cache to trim an entry. If |empty| is true, the whole cache is
+ // deleted.
+ void TrimForTest(bool empty);
+
+ // Asks the cache to trim an entry from the deleted list. If |empty| is
+ // true, the whole list is deleted.
+ void TrimDeletedListForTest(bool empty);
+
+ // Makes sure that some time passes before continuing the test. Time::Now()
+ // before and after this method will not be the same.
+ void AddDelay();
+
+ // DiskCacheTest:
+ virtual void TearDown() OVERRIDE;
+
+ // cache_ will always have a valid object, regardless of how the cache was
+ // initialized. The implementation pointers can be NULL.
+ scoped_ptr<disk_cache::Backend> cache_;
+ disk_cache::BackendImpl* cache_impl_;
+ disk_cache::SimpleBackendImpl* simple_cache_impl_;
+ disk_cache::MemBackendImpl* mem_cache_;
+
+ uint32 mask_;
+ int size_;
+ net::CacheType type_;
+ bool memory_only_;
+ bool simple_cache_mode_;
+ bool simple_cache_wait_for_index_;
+ bool force_creation_;
+ bool new_eviction_;
+ bool first_cleanup_;
+ bool integrity_;
+ bool use_current_thread_;
+ // This is intentionally left uninitialized, to be used by any test.
+ bool success_;
+
+ private:
+ void InitMemoryCache();
+ void InitDiskCache();
+
+ base::Thread cache_thread_;
+ DISALLOW_COPY_AND_ASSIGN(DiskCacheTestWithCache);
+};
+
+#endif // NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
diff --git a/chromium/net/disk_cache/disk_cache_test_util.cc b/chromium/net/disk_cache/disk_cache_test_util.cc
new file mode 100644
index 00000000000..8f334f05370
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache_test_util.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2011 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/disk_cache/disk_cache_test_util.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/file.h"
+
+using base::Time;
+using base::TimeDelta;
+
+std::string GenerateKey(bool same_length) {
+ char key[200];
+ CacheTestFillBuffer(key, sizeof(key), same_length);
+
+ key[199] = '\0';
+ return std::string(key);
+}
+
+void CacheTestFillBuffer(char* buffer, size_t len, bool no_nulls) {
+ static bool called = false;
+ if (!called) {
+ called = true;
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ buffer[i] = static_cast<char>(rand());
+ if (!buffer[i] && no_nulls)
+ buffer[i] = 'g';
+ }
+ if (len && !buffer[0])
+ buffer[0] = 'g';
+}
+
+bool CreateCacheTestFile(const base::FilePath& name) {
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE;
+
+ scoped_refptr<disk_cache::File> file(new disk_cache::File(
+ base::CreatePlatformFile(name, flags, NULL, NULL)));
+ if (!file->IsValid())
+ return false;
+
+ file->SetLength(4 * 1024 * 1024);
+ return true;
+}
+
+bool DeleteCache(const base::FilePath& path) {
+ disk_cache::DeleteCache(path, false);
+ return true;
+}
+
+bool CheckCacheIntegrity(const base::FilePath& path, bool new_eviction,
+ uint32 mask) {
+ scoped_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
+ path, mask, base::MessageLoopProxy::current().get(), NULL));
+ if (!cache.get())
+ return false;
+ if (new_eviction)
+ cache->SetNewEviction();
+ cache->SetFlags(disk_cache::kNoRandom);
+ if (cache->SyncInit() != net::OK)
+ return false;
+ return cache->SelfCheck() >= 0;
+}
+
+// -----------------------------------------------------------------------
+
+MessageLoopHelper::MessageLoopHelper()
+ : num_callbacks_(0),
+ num_iterations_(0),
+ last_(0),
+ completed_(false),
+ callback_reused_error_(false),
+ callbacks_called_(0) {
+}
+
+MessageLoopHelper::~MessageLoopHelper() {
+}
+
+bool MessageLoopHelper::WaitUntilCacheIoFinished(int num_callbacks) {
+ if (num_callbacks == callbacks_called_)
+ return true;
+
+ ExpectCallbacks(num_callbacks);
+ // Create a recurrent timer of 50 mS.
+ if (!timer_.IsRunning())
+ timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(50), this,
+ &MessageLoopHelper::TimerExpired);
+ base::MessageLoop::current()->Run();
+ return completed_;
+}
+
+// Quits the message loop when all callbacks are called or we've been waiting
+// too long for them (2 secs without a callback).
+void MessageLoopHelper::TimerExpired() {
+ CHECK_LE(callbacks_called_, num_callbacks_);
+ if (callbacks_called_ == num_callbacks_) {
+ completed_ = true;
+ base::MessageLoop::current()->Quit();
+ } else {
+ // Not finished yet. See if we have to abort.
+ if (last_ == callbacks_called_)
+ num_iterations_++;
+ else
+ last_ = callbacks_called_;
+ if (40 == num_iterations_)
+ base::MessageLoop::current()->Quit();
+ }
+}
+
+// -----------------------------------------------------------------------
+
+CallbackTest::CallbackTest(MessageLoopHelper* helper,
+ bool reuse)
+ : helper_(helper),
+ reuse_(reuse ? 0 : 1) {
+}
+
+CallbackTest::~CallbackTest() {
+}
+
+// On the actual callback, increase the number of tests received and check for
+// errors (an unexpected test received)
+void CallbackTest::Run(int result) {
+ last_result_ = result;
+
+ if (reuse_) {
+ DCHECK_EQ(1, reuse_);
+ if (2 == reuse_)
+ helper_->set_callback_reused_error(true);
+ reuse_++;
+ }
+
+ helper_->CallbackWasCalled();
+}
diff --git a/chromium/net/disk_cache/disk_cache_test_util.h b/chromium/net/disk_cache/disk_cache_test_util.h
new file mode 100644
index 00000000000..ad6b6ac9414
--- /dev/null
+++ b/chromium/net/disk_cache/disk_cache_test_util.h
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
+#define NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/timer/timer.h"
+#include "base/tuple.h"
+#include "build/build_config.h"
+
+// Re-creates a given test file inside the cache test folder.
+bool CreateCacheTestFile(const base::FilePath& name);
+
+// Deletes all file son the cache.
+bool DeleteCache(const base::FilePath& path);
+
+// Fills buffer with random values (may contain nulls unless no_nulls is true).
+void CacheTestFillBuffer(char* buffer, size_t len, bool no_nulls);
+
+// Generates a random key of up to 200 bytes.
+std::string GenerateKey(bool same_length);
+
+// Returns true if the cache is not corrupt.
+bool CheckCacheIntegrity(const base::FilePath& path, bool new_eviction,
+ uint32 mask);
+
+// -----------------------------------------------------------------------
+
+// Simple helper to deal with the message loop on a test.
+class MessageLoopHelper {
+ public:
+ MessageLoopHelper();
+ ~MessageLoopHelper();
+
+ // Run the message loop and wait for num_callbacks before returning. Returns
+ // false if we are waiting to long. Each callback that will be waited on is
+ // required to call CallbackWasCalled() to indicate when it was called.
+ bool WaitUntilCacheIoFinished(int num_callbacks);
+
+ // True if a given callback was called more times than it expected.
+ bool callback_reused_error() const { return callback_reused_error_; }
+ void set_callback_reused_error(bool error) {
+ callback_reused_error_ = error;
+ }
+
+ int callbacks_called() const { return callbacks_called_; }
+ // Report that a callback was called. Each callback that will be waited on
+ // via WaitUntilCacheIoFinished() is expected to call this method to
+ // indicate when it has been executed.
+ void CallbackWasCalled() { ++callbacks_called_; }
+
+ private:
+ // Sets the number of callbacks that can be received so far.
+ void ExpectCallbacks(int num_callbacks) {
+ num_callbacks_ = num_callbacks;
+ num_iterations_ = last_ = 0;
+ completed_ = false;
+ }
+
+ // Called periodically to test if WaitUntilCacheIoFinished should return.
+ void TimerExpired();
+
+ base::RepeatingTimer<MessageLoopHelper> timer_;
+ int num_callbacks_;
+ int num_iterations_;
+ int last_;
+ bool completed_;
+
+ // True if a callback was called/reused more than expected.
+ bool callback_reused_error_;
+ int callbacks_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageLoopHelper);
+};
+
+// -----------------------------------------------------------------------
+
+// Simple callback to process IO completions from the cache. It allows tests
+// with multiple simultaneous IO operations.
+class CallbackTest {
+ public:
+ // Creates a new CallbackTest object. When the callback is called, it will
+ // update |helper|. If |reuse| is false and a callback is called more than
+ // once, or if |reuse| is true and a callback is called more than twice, an
+ // error will be reported to |helper|.
+ CallbackTest(MessageLoopHelper* helper, bool reuse);
+ ~CallbackTest();
+
+ void Run(int result);
+
+ int last_result() const { return last_result_; }
+
+ private:
+ MessageLoopHelper* helper_;
+ int reuse_;
+ int last_result_;
+ DISALLOW_COPY_AND_ASSIGN(CallbackTest);
+};
+
+#endif // NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
diff --git a/chromium/net/disk_cache/disk_format.cc b/chromium/net/disk_cache/disk_format.cc
new file mode 100644
index 00000000000..5b08954e088
--- /dev/null
+++ b/chromium/net/disk_cache/disk_format.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 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/disk_cache/disk_format.h"
+
+namespace disk_cache {
+
+IndexHeader::IndexHeader() {
+ memset(this, 0, sizeof(*this));
+ magic = kIndexMagic;
+ version = kCurrentVersion;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/disk_format.h b/chromium/net/disk_cache/disk_format.h
new file mode 100644
index 00000000000..5d7597ab17d
--- /dev/null
+++ b/chromium/net/disk_cache/disk_format.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2011 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.
+
+// The cache is stored on disk as a collection of block-files, plus an index
+// file plus a collection of external files.
+//
+// Any data blob bigger than kMaxBlockSize (disk_cache/addr.h) will be stored in
+// a separate file named f_xxx where x is a hexadecimal number. Shorter data
+// will be stored as a series of blocks on a block-file. In any case, CacheAddr
+// represents the address of the data inside the cache.
+//
+// The index file is just a simple hash table that maps a particular entry to
+// a CacheAddr value. Linking for a given hash bucket is handled internally
+// by the cache entry.
+//
+// The last element of the cache is the block-file. A block file is a file
+// designed to store blocks of data of a given size. For more details see
+// disk_cache/disk_format_base.h
+//
+// A new cache is initialized with four block files (named data_0 through
+// data_3), each one dedicated to store blocks of a given size. The number at
+// the end of the file name is the block file number (in decimal).
+//
+// There are two "special" types of blocks: an entry and a rankings node. An
+// entry keeps track of all the information related to the same cache entry,
+// such as the key, hash value, data pointers etc. A rankings node keeps track
+// of the information that is updated frequently for a given entry, such as its
+// location on the LRU lists, last access time etc.
+//
+// The files that store internal information for the cache (blocks and index)
+// are at least partially memory mapped. They have a location that is signaled
+// every time the internal structures are modified, so it is possible to detect
+// (most of the time) when the process dies in the middle of an update.
+//
+// In order to prevent dirty data to be used as valid (after a crash), every
+// cache entry has a dirty identifier. Each running instance of the cache keeps
+// a separate identifier (maintained on the "this_id" header field) that is used
+// to mark every entry that is created or modified. When the entry is closed,
+// and all the data can be trusted, the dirty flag is cleared from the entry.
+// When the cache encounters an entry whose identifier is different than the one
+// being currently used, it means that the entry was not properly closed on a
+// previous run, so it is discarded.
+
+#ifndef NET_DISK_CACHE_DISK_FORMAT_H_
+#define NET_DISK_CACHE_DISK_FORMAT_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/disk_format_base.h"
+
+namespace disk_cache {
+
+const int kIndexTablesize = 0x10000;
+const uint32 kIndexMagic = 0xC103CAC3;
+const uint32 kCurrentVersion = 0x20000; // Version 2.0.
+
+struct LruData {
+ int32 pad1[2];
+ int32 filled; // Flag to tell when we filled the cache.
+ int32 sizes[5];
+ CacheAddr heads[5];
+ CacheAddr tails[5];
+ CacheAddr transaction; // In-flight operation target.
+ int32 operation; // Actual in-flight operation.
+ int32 operation_list; // In-flight operation list.
+ int32 pad2[7];
+};
+
+// Header for the master index file.
+struct NET_EXPORT_PRIVATE IndexHeader {
+ IndexHeader();
+
+ uint32 magic;
+ uint32 version;
+ int32 num_entries; // Number of entries currently stored.
+ int32 num_bytes; // Total size of the stored data.
+ int32 last_file; // Last external file created.
+ int32 this_id; // Id for all entries being changed (dirty flag).
+ CacheAddr stats; // Storage for usage data.
+ int32 table_len; // Actual size of the table (0 == kIndexTablesize).
+ int32 crash; // Signals a previous crash.
+ int32 experiment; // Id of an ongoing test.
+ uint64 create_time; // Creation time for this set of files.
+ int32 pad[52];
+ LruData lru; // Eviction control data.
+};
+
+// The structure of the whole index file.
+struct Index {
+ IndexHeader header;
+ CacheAddr table[kIndexTablesize]; // Default size. Actual size controlled
+ // by header.table_len.
+};
+
+// Main structure for an entry on the backing storage. If the key is longer than
+// what can be stored on this structure, it will be extended on consecutive
+// blocks (adding 256 bytes each time), up to 4 blocks (1024 - 32 - 1 chars).
+// After that point, the whole key will be stored as a data block or external
+// file.
+struct EntryStore {
+ uint32 hash; // Full hash of the key.
+ CacheAddr next; // Next entry with the same hash or bucket.
+ CacheAddr rankings_node; // Rankings node for this entry.
+ int32 reuse_count; // How often is this entry used.
+ int32 refetch_count; // How often is this fetched from the net.
+ int32 state; // Current state.
+ uint64 creation_time;
+ int32 key_len;
+ CacheAddr long_key; // Optional address of a long key.
+ int32 data_size[4]; // We can store up to 4 data streams for each
+ CacheAddr data_addr[4]; // entry.
+ uint32 flags; // Any combination of EntryFlags.
+ int32 pad[4];
+ uint32 self_hash; // The hash of EntryStore up to this point.
+ char key[256 - 24 * 4]; // null terminated
+};
+
+COMPILE_ASSERT(sizeof(EntryStore) == 256, bad_EntyStore);
+const int kMaxInternalKeyLength = 4 * sizeof(EntryStore) -
+ offsetof(EntryStore, key) - 1;
+
+// Possible states for a given entry.
+enum EntryState {
+ ENTRY_NORMAL = 0,
+ ENTRY_EVICTED, // The entry was recently evicted from the cache.
+ ENTRY_DOOMED // The entry was doomed.
+};
+
+// Flags that can be applied to an entry.
+enum EntryFlags {
+ PARENT_ENTRY = 1, // This entry has children (sparse) entries.
+ CHILD_ENTRY = 1 << 1 // Child entry that stores sparse data.
+};
+
+#pragma pack(push, 4)
+// Rankings information for a given entry.
+struct RankingsNode {
+ uint64 last_used; // LRU info.
+ uint64 last_modified; // LRU info.
+ CacheAddr next; // LRU list.
+ CacheAddr prev; // LRU list.
+ CacheAddr contents; // Address of the EntryStore.
+ int32 dirty; // The entry is being modifyied.
+ uint32 self_hash; // RankingsNode's hash.
+};
+#pragma pack(pop)
+
+COMPILE_ASSERT(sizeof(RankingsNode) == 36, bad_RankingsNode);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_DISK_FORMAT_H_
diff --git a/chromium/net/disk_cache/disk_format_base.h b/chromium/net/disk_cache/disk_format_base.h
new file mode 100644
index 00000000000..c8b7490abfd
--- /dev/null
+++ b/chromium/net/disk_cache/disk_format_base.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2011 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.
+
+// For a general description of the files used by the cache see file_format.h.
+//
+// A block file is a file designed to store blocks of data of a given size. It
+// is able to store data that spans from one to four consecutive "blocks", and
+// it grows as needed to store up to approximately 65000 blocks. It has a fixed
+// size header used for book keeping such as tracking free of blocks on the
+// file. For example, a block-file for 1KB blocks will grow from 8KB when
+// totally empty to about 64MB when completely full. At that point, data blocks
+// of 1KB will be stored on a second block file that will store the next set of
+// 65000 blocks. The first file contains the number of the second file, and the
+// second file contains the number of a third file, created when the second file
+// reaches its limit. It is important to remember that no matter how long the
+// chain of files is, any given block can be located directly by its address,
+// which contains the file number and starting block inside the file.
+
+#ifndef NET_DISK_CACHE_DISK_FORMAT_BASE_H_
+#define NET_DISK_CACHE_DISK_FORMAT_BASE_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+
+typedef uint32 CacheAddr;
+
+const uint32 kBlockVersion2 = 0x20000; // Version 2.0.
+
+const uint32 kBlockMagic = 0xC104CAC3;
+const int kBlockHeaderSize = 8192; // Two pages: almost 64k entries
+const int kMaxBlocks = (kBlockHeaderSize - 80) * 8;
+
+// Bitmap to track used blocks on a block-file.
+typedef uint32 AllocBitmap[kMaxBlocks / 32];
+
+// A block-file is the file used to store information in blocks (could be
+// EntryStore blocks, RankingsNode blocks or user-data blocks).
+// We store entries that can expand for up to 4 consecutive blocks, and keep
+// counters of the number of blocks available for each type of entry. For
+// instance, an entry of 3 blocks is an entry of type 3. We also keep track of
+// where did we find the last entry of that type (to avoid searching the bitmap
+// from the beginning every time).
+// This Structure is the header of a block-file:
+struct BlockFileHeader {
+ uint32 magic;
+ uint32 version;
+ int16 this_file; // Index of this file.
+ int16 next_file; // Next file when this one is full.
+ int32 entry_size; // Size of the blocks of this file.
+ int32 num_entries; // Number of stored entries.
+ int32 max_entries; // Current maximum number of entries.
+ int32 empty[4]; // Counters of empty entries for each type.
+ int32 hints[4]; // Last used position for each entry type.
+ volatile int32 updating; // Keep track of updates to the header.
+ int32 user[5];
+ AllocBitmap allocation_map;
+};
+
+COMPILE_ASSERT(sizeof(BlockFileHeader) == kBlockHeaderSize, bad_header);
+
+// Sparse data support:
+// We keep a two level hierarchy to enable sparse data for an entry: the first
+// level consists of using separate "child" entries to store ranges of 1 MB,
+// and the second level stores blocks of 1 KB inside each child entry.
+//
+// Whenever we need to access a particular sparse offset, we first locate the
+// child entry that stores that offset, so we discard the 20 least significant
+// bits of the offset, and end up with the child id. For instance, the child id
+// to store the first megabyte is 0, and the child that should store offset
+// 0x410000 has an id of 4.
+//
+// The child entry is stored the same way as any other entry, so it also has a
+// name (key). The key includes a signature to be able to identify children
+// created for different generations of the same resource. In other words, given
+// that a given sparse entry can have a large number of child entries, and the
+// resource can be invalidated and replaced with a new version at any time, it
+// is important to be sure that a given child actually belongs to certain entry.
+//
+// The full name of a child entry is composed with a prefix ("Range_"), and two
+// hexadecimal 64-bit numbers at the end, separated by semicolons. The first
+// number is the signature of the parent key, and the second number is the child
+// id as described previously. The signature itself is also stored internally by
+// the child and the parent entries. For example, a sparse entry with a key of
+// "sparse entry name", and a signature of 0x052AF76, may have a child entry
+// named "Range_sparse entry name:052af76:4", which stores data in the range
+// 0x400000 to 0x4FFFFF.
+//
+// Each child entry keeps track of all the 1 KB blocks that have been written
+// to the entry, but being a regular entry, it will happily return zeros for any
+// read that spans data not written before. The actual sparse data is stored in
+// one of the data streams of the child entry (at index 1), while the control
+// information is stored in another stream (at index 2), both by parents and
+// the children.
+
+// This structure contains the control information for parent and child entries.
+// It is stored at offset 0 of the data stream with index 2.
+// It is possible to write to a child entry in a way that causes the last block
+// to be only partialy filled. In that case, last_block and last_block_len will
+// keep track of that block.
+struct SparseHeader {
+ int64 signature; // The parent and children signature.
+ uint32 magic; // Structure identifier (equal to kIndexMagic).
+ int32 parent_key_len; // Key length for the parent entry.
+ int32 last_block; // Index of the last written block.
+ int32 last_block_len; // Lenght of the last written block.
+ int32 dummy[10];
+};
+
+// The SparseHeader will be followed by a bitmap, as described by this
+// structure.
+struct SparseData {
+ SparseHeader header;
+ uint32 bitmap[32]; // Bitmap representation of known children (if this
+ // is a parent entry), or used blocks (for child
+ // entries. The size is fixed for child entries but
+ // not for parents; it can be as small as 4 bytes
+ // and as large as 8 KB.
+};
+
+// The number of blocks stored by a child entry.
+const int kNumSparseBits = 1024;
+COMPILE_ASSERT(sizeof(SparseData) == sizeof(SparseHeader) + kNumSparseBits / 8,
+ Invalid_SparseData_bitmap);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_DISK_FORMAT_BASE_H_
diff --git a/chromium/net/disk_cache/entry_impl.cc b/chromium/net/disk_cache/entry_impl.cc
new file mode 100644
index 00000000000..4b6e4cf2b04
--- /dev/null
+++ b/chromium/net/disk_cache/entry_impl.cc
@@ -0,0 +1,1550 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/entry_impl.h"
+
+#include "base/hash.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/bitmap.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/net_log_parameters.h"
+#include "net/disk_cache/sparse_control.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+// Index for the file used to store the key, if any (files_[kKeyFileIndex]).
+const int kKeyFileIndex = 3;
+
+// This class implements FileIOCallback to buffer the callback from a file IO
+// operation from the actual net class.
+class SyncCallback: public disk_cache::FileIOCallback {
+ public:
+ // |end_event_type| is the event type to log on completion. Logs nothing on
+ // discard, or when the NetLog is not set to log all events.
+ SyncCallback(disk_cache::EntryImpl* entry, net::IOBuffer* buffer,
+ const net::CompletionCallback& callback,
+ net::NetLog::EventType end_event_type)
+ : entry_(entry), callback_(callback), buf_(buffer),
+ start_(TimeTicks::Now()), end_event_type_(end_event_type) {
+ entry->AddRef();
+ entry->IncrementIoCount();
+ }
+ virtual ~SyncCallback() {}
+
+ virtual void OnFileIOComplete(int bytes_copied) OVERRIDE;
+ void Discard();
+
+ private:
+ disk_cache::EntryImpl* entry_;
+ net::CompletionCallback callback_;
+ scoped_refptr<net::IOBuffer> buf_;
+ TimeTicks start_;
+ const net::NetLog::EventType end_event_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncCallback);
+};
+
+void SyncCallback::OnFileIOComplete(int bytes_copied) {
+ entry_->DecrementIoCount();
+ if (!callback_.is_null()) {
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().EndEvent(
+ end_event_type_,
+ disk_cache::CreateNetLogReadWriteCompleteCallback(bytes_copied));
+ }
+ entry_->ReportIOTime(disk_cache::EntryImpl::kAsyncIO, start_);
+ buf_ = NULL; // Release the buffer before invoking the callback.
+ callback_.Run(bytes_copied);
+ }
+ entry_->Release();
+ delete this;
+}
+
+void SyncCallback::Discard() {
+ callback_.Reset();
+ buf_ = NULL;
+ OnFileIOComplete(0);
+}
+
+const int kMaxBufferSize = 1024 * 1024; // 1 MB.
+
+} // namespace
+
+namespace disk_cache {
+
+// This class handles individual memory buffers that store data before it is
+// sent to disk. The buffer can start at any offset, but if we try to write to
+// anywhere in the first 16KB of the file (kMaxBlockSize), we set the offset to
+// zero. The buffer grows up to a size determined by the backend, to keep the
+// total memory used under control.
+class EntryImpl::UserBuffer {
+ public:
+ explicit UserBuffer(BackendImpl* backend)
+ : backend_(backend->GetWeakPtr()), offset_(0), grow_allowed_(true) {
+ buffer_.reserve(kMaxBlockSize);
+ }
+ ~UserBuffer() {
+ if (backend_.get())
+ backend_->BufferDeleted(capacity() - kMaxBlockSize);
+ }
+
+ // Returns true if we can handle writing |len| bytes to |offset|.
+ bool PreWrite(int offset, int len);
+
+ // Truncates the buffer to |offset| bytes.
+ void Truncate(int offset);
+
+ // Writes |len| bytes from |buf| at the given |offset|.
+ void Write(int offset, IOBuffer* buf, int len);
+
+ // Returns true if we can read |len| bytes from |offset|, given that the
+ // actual file has |eof| bytes stored. Note that the number of bytes to read
+ // may be modified by this method even though it returns false: that means we
+ // should do a smaller read from disk.
+ bool PreRead(int eof, int offset, int* len);
+
+ // Read |len| bytes from |buf| at the given |offset|.
+ int Read(int offset, IOBuffer* buf, int len);
+
+ // Prepare this buffer for reuse.
+ void Reset();
+
+ char* Data() { return buffer_.size() ? &buffer_[0] : NULL; }
+ int Size() { return static_cast<int>(buffer_.size()); }
+ int Start() { return offset_; }
+ int End() { return offset_ + Size(); }
+
+ private:
+ int capacity() { return static_cast<int>(buffer_.capacity()); }
+ bool GrowBuffer(int required, int limit);
+
+ base::WeakPtr<BackendImpl> backend_;
+ int offset_;
+ std::vector<char> buffer_;
+ bool grow_allowed_;
+ DISALLOW_COPY_AND_ASSIGN(UserBuffer);
+};
+
+bool EntryImpl::UserBuffer::PreWrite(int offset, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(len, 0);
+ DCHECK_GE(offset + len, 0);
+
+ // We don't want to write before our current start.
+ if (offset < offset_)
+ return false;
+
+ // Lets get the common case out of the way.
+ if (offset + len <= capacity())
+ return true;
+
+ // If we are writing to the first 16K (kMaxBlockSize), we want to keep the
+ // buffer offset_ at 0.
+ if (!Size() && offset > kMaxBlockSize)
+ return GrowBuffer(len, kMaxBufferSize);
+
+ int required = offset - offset_ + len;
+ return GrowBuffer(required, kMaxBufferSize * 6 / 5);
+}
+
+void EntryImpl::UserBuffer::Truncate(int offset) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(offset, offset_);
+ DVLOG(3) << "Buffer truncate at " << offset << " current " << offset_;
+
+ offset -= offset_;
+ if (Size() >= offset)
+ buffer_.resize(offset);
+}
+
+void EntryImpl::UserBuffer::Write(int offset, IOBuffer* buf, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(len, 0);
+ DCHECK_GE(offset + len, 0);
+ DCHECK_GE(offset, offset_);
+ DVLOG(3) << "Buffer write at " << offset << " current " << offset_;
+
+ if (!Size() && offset > kMaxBlockSize)
+ offset_ = offset;
+
+ offset -= offset_;
+
+ if (offset > Size())
+ buffer_.resize(offset);
+
+ if (!len)
+ return;
+
+ char* buffer = buf->data();
+ int valid_len = Size() - offset;
+ int copy_len = std::min(valid_len, len);
+ if (copy_len) {
+ memcpy(&buffer_[offset], buffer, copy_len);
+ len -= copy_len;
+ buffer += copy_len;
+ }
+ if (!len)
+ return;
+
+ buffer_.insert(buffer_.end(), buffer, buffer + len);
+}
+
+bool EntryImpl::UserBuffer::PreRead(int eof, int offset, int* len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GT(*len, 0);
+
+ if (offset < offset_) {
+ // We are reading before this buffer.
+ if (offset >= eof)
+ return true;
+
+ // If the read overlaps with the buffer, change its length so that there is
+ // no overlap.
+ *len = std::min(*len, offset_ - offset);
+ *len = std::min(*len, eof - offset);
+
+ // We should read from disk.
+ return false;
+ }
+
+ if (!Size())
+ return false;
+
+ // See if we can fulfill the first part of the operation.
+ return (offset - offset_ < Size());
+}
+
+int EntryImpl::UserBuffer::Read(int offset, IOBuffer* buf, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GT(len, 0);
+ DCHECK(Size() || offset < offset_);
+
+ int clean_bytes = 0;
+ if (offset < offset_) {
+ // We don't have a file so lets fill the first part with 0.
+ clean_bytes = std::min(offset_ - offset, len);
+ memset(buf->data(), 0, clean_bytes);
+ if (len == clean_bytes)
+ return len;
+ offset = offset_;
+ len -= clean_bytes;
+ }
+
+ int start = offset - offset_;
+ int available = Size() - start;
+ DCHECK_GE(start, 0);
+ DCHECK_GE(available, 0);
+ len = std::min(len, available);
+ memcpy(buf->data() + clean_bytes, &buffer_[start], len);
+ return len + clean_bytes;
+}
+
+void EntryImpl::UserBuffer::Reset() {
+ if (!grow_allowed_) {
+ if (backend_.get())
+ backend_->BufferDeleted(capacity() - kMaxBlockSize);
+ grow_allowed_ = true;
+ std::vector<char> tmp;
+ buffer_.swap(tmp);
+ buffer_.reserve(kMaxBlockSize);
+ }
+ offset_ = 0;
+ buffer_.clear();
+}
+
+bool EntryImpl::UserBuffer::GrowBuffer(int required, int limit) {
+ DCHECK_GE(required, 0);
+ int current_size = capacity();
+ if (required <= current_size)
+ return true;
+
+ if (required > limit)
+ return false;
+
+ if (!backend_.get())
+ return false;
+
+ int to_add = std::max(required - current_size, kMaxBlockSize * 4);
+ to_add = std::max(current_size, to_add);
+ required = std::min(current_size + to_add, limit);
+
+ grow_allowed_ = backend_->IsAllocAllowed(current_size, required);
+ if (!grow_allowed_)
+ return false;
+
+ DVLOG(3) << "Buffer grow to " << required;
+
+ buffer_.reserve(required);
+ return true;
+}
+
+// ------------------------------------------------------------------------
+
+EntryImpl::EntryImpl(BackendImpl* backend, Addr address, bool read_only)
+ : entry_(NULL, Addr(0)), node_(NULL, Addr(0)),
+ backend_(backend->GetWeakPtr()), doomed_(false), read_only_(read_only),
+ dirty_(false) {
+ entry_.LazyInit(backend->File(address), address);
+ for (int i = 0; i < kNumStreams; i++) {
+ unreported_size_[i] = 0;
+ }
+}
+
+void EntryImpl::DoomImpl() {
+ if (doomed_ || !backend_.get())
+ return;
+
+ SetPointerForInvalidEntry(backend_->GetCurrentEntryId());
+ backend_->InternalDoomEntry(this);
+}
+
+int EntryImpl::ReadDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, false));
+ }
+
+ int result = InternalReadData(index, offset, buf, buf_len, callback);
+
+ if (result != net::ERR_IO_PENDING && net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int EntryImpl::WriteDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, truncate));
+ }
+
+ int result = InternalWriteData(index, offset, buf, buf_len, callback,
+ truncate);
+
+ if (result != net::ERR_IO_PENDING && net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int EntryImpl::ReadSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ TimeTicks start = TimeTicks::Now();
+ result = sparse_->StartIO(SparseControl::kReadOperation, offset, buf, buf_len,
+ callback);
+ ReportIOTime(kSparseRead, start);
+ return result;
+}
+
+int EntryImpl::WriteSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ TimeTicks start = TimeTicks::Now();
+ result = sparse_->StartIO(SparseControl::kWriteOperation, offset, buf,
+ buf_len, callback);
+ ReportIOTime(kSparseWrite, start);
+ return result;
+}
+
+int EntryImpl::GetAvailableRangeImpl(int64 offset, int len, int64* start) {
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ return sparse_->GetAvailableRange(offset, len, start);
+}
+
+void EntryImpl::CancelSparseIOImpl() {
+ if (!sparse_.get())
+ return;
+
+ sparse_->CancelIO();
+}
+
+int EntryImpl::ReadyForSparseIOImpl(const CompletionCallback& callback) {
+ DCHECK(sparse_.get());
+ return sparse_->ReadyToUse(callback);
+}
+
+uint32 EntryImpl::GetHash() {
+ return entry_.Data()->hash;
+}
+
+bool EntryImpl::CreateEntry(Addr node_address, const std::string& key,
+ uint32 hash) {
+ Trace("Create entry In");
+ EntryStore* entry_store = entry_.Data();
+ RankingsNode* node = node_.Data();
+ memset(entry_store, 0, sizeof(EntryStore) * entry_.address().num_blocks());
+ memset(node, 0, sizeof(RankingsNode));
+ if (!node_.LazyInit(backend_->File(node_address), node_address))
+ return false;
+
+ entry_store->rankings_node = node_address.value();
+ node->contents = entry_.address().value();
+
+ entry_store->hash = hash;
+ entry_store->creation_time = Time::Now().ToInternalValue();
+ entry_store->key_len = static_cast<int32>(key.size());
+ if (entry_store->key_len > kMaxInternalKeyLength) {
+ Addr address(0);
+ if (!CreateBlock(entry_store->key_len + 1, &address))
+ return false;
+
+ entry_store->long_key = address.value();
+ File* key_file = GetBackingFile(address, kKeyFileIndex);
+ key_ = key;
+
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!key_file || !key_file->Write(key.data(), key.size(), offset)) {
+ DeleteData(address, kKeyFileIndex);
+ return false;
+ }
+
+ if (address.is_separate_file())
+ key_file->SetLength(key.size() + 1);
+ } else {
+ memcpy(entry_store->key, key.data(), key.size());
+ entry_store->key[key.size()] = '\0';
+ }
+ backend_->ModifyStorageSize(0, static_cast<int32>(key.size()));
+ CACHE_UMA(COUNTS, "KeySize", 0, static_cast<int32>(key.size()));
+ node->dirty = backend_->GetCurrentEntryId();
+ Log("Create Entry ");
+ return true;
+}
+
+bool EntryImpl::IsSameEntry(const std::string& key, uint32 hash) {
+ if (entry_.Data()->hash != hash ||
+ static_cast<size_t>(entry_.Data()->key_len) != key.size())
+ return false;
+
+ return (key.compare(GetKey()) == 0);
+}
+
+void EntryImpl::InternalDoom() {
+ net_log_.AddEvent(net::NetLog::TYPE_ENTRY_DOOM);
+ DCHECK(node_.HasData());
+ if (!node_.Data()->dirty) {
+ node_.Data()->dirty = backend_->GetCurrentEntryId();
+ node_.Store();
+ }
+ doomed_ = true;
+}
+
+void EntryImpl::DeleteEntryData(bool everything) {
+ DCHECK(doomed_ || !everything);
+
+ if (GetEntryFlags() & PARENT_ENTRY) {
+ // We have some child entries that must go away.
+ SparseControl::DeleteChildren(this);
+ }
+
+ if (GetDataSize(0))
+ CACHE_UMA(COUNTS, "DeleteHeader", 0, GetDataSize(0));
+ if (GetDataSize(1))
+ CACHE_UMA(COUNTS, "DeleteData", 0, GetDataSize(1));
+ for (int index = 0; index < kNumStreams; index++) {
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized()) {
+ backend_->ModifyStorageSize(entry_.Data()->data_size[index] -
+ unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+ }
+ }
+
+ if (!everything)
+ return;
+
+ // Remove all traces of this entry.
+ backend_->RemoveEntry(this);
+
+ // Note that at this point node_ and entry_ are just two blocks of data, and
+ // even if they reference each other, nobody should be referencing them.
+
+ Addr address(entry_.Data()->long_key);
+ DeleteData(address, kKeyFileIndex);
+ backend_->ModifyStorageSize(entry_.Data()->key_len, 0);
+
+ backend_->DeleteBlock(entry_.address(), true);
+ entry_.Discard();
+
+ if (!LeaveRankingsBehind()) {
+ backend_->DeleteBlock(node_.address(), true);
+ node_.Discard();
+ }
+}
+
+CacheAddr EntryImpl::GetNextAddress() {
+ return entry_.Data()->next;
+}
+
+void EntryImpl::SetNextAddress(Addr address) {
+ DCHECK_NE(address.value(), entry_.address().value());
+ entry_.Data()->next = address.value();
+ bool success = entry_.Store();
+ DCHECK(success);
+}
+
+bool EntryImpl::LoadNodeAddress() {
+ Addr address(entry_.Data()->rankings_node);
+ if (!node_.LazyInit(backend_->File(address), address))
+ return false;
+ return node_.Load();
+}
+
+bool EntryImpl::Update() {
+ DCHECK(node_.HasData());
+
+ if (read_only_)
+ return true;
+
+ RankingsNode* rankings = node_.Data();
+ if (!rankings->dirty) {
+ rankings->dirty = backend_->GetCurrentEntryId();
+ if (!node_.Store())
+ return false;
+ }
+ return true;
+}
+
+void EntryImpl::SetDirtyFlag(int32 current_id) {
+ DCHECK(node_.HasData());
+ if (node_.Data()->dirty && current_id != node_.Data()->dirty)
+ dirty_ = true;
+
+ if (!current_id)
+ dirty_ = true;
+}
+
+void EntryImpl::SetPointerForInvalidEntry(int32 new_id) {
+ node_.Data()->dirty = new_id;
+ node_.Store();
+}
+
+bool EntryImpl::LeaveRankingsBehind() {
+ return !node_.Data()->contents;
+}
+
+// This only includes checks that relate to the first block of the entry (the
+// first 256 bytes), and values that should be set from the entry creation.
+// Basically, even if there is something wrong with this entry, we want to see
+// if it is possible to load the rankings node and delete them together.
+bool EntryImpl::SanityCheck() {
+ if (!entry_.VerifyHash())
+ return false;
+
+ EntryStore* stored = entry_.Data();
+ if (!stored->rankings_node || stored->key_len <= 0)
+ return false;
+
+ if (stored->reuse_count < 0 || stored->refetch_count < 0)
+ return false;
+
+ Addr rankings_addr(stored->rankings_node);
+ if (!rankings_addr.SanityCheckForRankings())
+ return false;
+
+ Addr next_addr(stored->next);
+ if (next_addr.is_initialized() && !next_addr.SanityCheckForEntryV2()) {
+ STRESS_NOTREACHED();
+ return false;
+ }
+ STRESS_DCHECK(next_addr.value() != entry_.address().value());
+
+ if (stored->state > ENTRY_DOOMED || stored->state < ENTRY_NORMAL)
+ return false;
+
+ Addr key_addr(stored->long_key);
+ if ((stored->key_len <= kMaxInternalKeyLength && key_addr.is_initialized()) ||
+ (stored->key_len > kMaxInternalKeyLength && !key_addr.is_initialized()))
+ return false;
+
+ if (!key_addr.SanityCheckV2())
+ return false;
+
+ if (key_addr.is_initialized() &&
+ ((stored->key_len < kMaxBlockSize && key_addr.is_separate_file()) ||
+ (stored->key_len >= kMaxBlockSize && key_addr.is_block_file())))
+ return false;
+
+ int num_blocks = NumBlocksForEntry(stored->key_len);
+ if (entry_.address().num_blocks() != num_blocks)
+ return false;
+
+ return true;
+}
+
+bool EntryImpl::DataSanityCheck() {
+ EntryStore* stored = entry_.Data();
+ Addr key_addr(stored->long_key);
+
+ // The key must be NULL terminated.
+ if (!key_addr.is_initialized() && stored->key[stored->key_len])
+ return false;
+
+ if (stored->hash != base::Hash(GetKey()))
+ return false;
+
+ for (int i = 0; i < kNumStreams; i++) {
+ Addr data_addr(stored->data_addr[i]);
+ int data_size = stored->data_size[i];
+ if (data_size < 0)
+ return false;
+ if (!data_size && data_addr.is_initialized())
+ return false;
+ if (!data_addr.SanityCheckV2())
+ return false;
+ if (!data_size)
+ continue;
+ if (data_size <= kMaxBlockSize && data_addr.is_separate_file())
+ return false;
+ if (data_size > kMaxBlockSize && data_addr.is_block_file())
+ return false;
+ }
+ return true;
+}
+
+void EntryImpl::FixForDelete() {
+ EntryStore* stored = entry_.Data();
+ Addr key_addr(stored->long_key);
+
+ if (!key_addr.is_initialized())
+ stored->key[stored->key_len] = '\0';
+
+ for (int i = 0; i < kNumStreams; i++) {
+ Addr data_addr(stored->data_addr[i]);
+ int data_size = stored->data_size[i];
+ if (data_addr.is_initialized()) {
+ if ((data_size <= kMaxBlockSize && data_addr.is_separate_file()) ||
+ (data_size > kMaxBlockSize && data_addr.is_block_file()) ||
+ !data_addr.SanityCheckV2()) {
+ STRESS_NOTREACHED();
+ // The address is weird so don't attempt to delete it.
+ stored->data_addr[i] = 0;
+ // In general, trust the stored size as it should be in sync with the
+ // total size tracked by the backend.
+ }
+ }
+ if (data_size < 0)
+ stored->data_size[i] = 0;
+ }
+ entry_.Store();
+}
+
+void EntryImpl::IncrementIoCount() {
+ backend_->IncrementIoCount();
+}
+
+void EntryImpl::DecrementIoCount() {
+ if (backend_.get())
+ backend_->DecrementIoCount();
+}
+
+void EntryImpl::OnEntryCreated(BackendImpl* backend) {
+ // Just grab a reference to the backround queue.
+ background_queue_ = backend->GetBackgroundQueue();
+}
+
+void EntryImpl::SetTimes(base::Time last_used, base::Time last_modified) {
+ node_.Data()->last_used = last_used.ToInternalValue();
+ node_.Data()->last_modified = last_modified.ToInternalValue();
+ node_.set_modified();
+}
+
+void EntryImpl::ReportIOTime(Operation op, const base::TimeTicks& start) {
+ if (!backend_.get())
+ return;
+
+ switch (op) {
+ case kRead:
+ CACHE_UMA(AGE_MS, "ReadTime", 0, start);
+ break;
+ case kWrite:
+ CACHE_UMA(AGE_MS, "WriteTime", 0, start);
+ break;
+ case kSparseRead:
+ CACHE_UMA(AGE_MS, "SparseReadTime", 0, start);
+ break;
+ case kSparseWrite:
+ CACHE_UMA(AGE_MS, "SparseWriteTime", 0, start);
+ break;
+ case kAsyncIO:
+ CACHE_UMA(AGE_MS, "AsyncIOTime", 0, start);
+ break;
+ case kReadAsync1:
+ CACHE_UMA(AGE_MS, "AsyncReadDispatchTime", 0, start);
+ break;
+ case kWriteAsync1:
+ CACHE_UMA(AGE_MS, "AsyncWriteDispatchTime", 0, start);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void EntryImpl::BeginLogging(net::NetLog* net_log, bool created) {
+ DCHECK(!net_log_.net_log());
+ net_log_ = net::BoundNetLog::Make(
+ net_log, net::NetLog::SOURCE_DISK_CACHE_ENTRY);
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL,
+ CreateNetLogEntryCreationCallback(this, created));
+}
+
+const net::BoundNetLog& EntryImpl::net_log() const {
+ return net_log_;
+}
+
+// static
+int EntryImpl::NumBlocksForEntry(int key_size) {
+ // The longest key that can be stored using one block.
+ int key1_len =
+ static_cast<int>(sizeof(EntryStore) - offsetof(EntryStore, key));
+
+ if (key_size < key1_len || key_size > kMaxInternalKeyLength)
+ return 1;
+
+ return ((key_size - key1_len) / 256 + 2);
+}
+
+// ------------------------------------------------------------------------
+
+void EntryImpl::Doom() {
+ if (background_queue_.get())
+ background_queue_->DoomEntryImpl(this);
+}
+
+void EntryImpl::Close() {
+ if (background_queue_.get())
+ background_queue_->CloseEntryImpl(this);
+}
+
+std::string EntryImpl::GetKey() const {
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ int key_len = entry->Data()->key_len;
+ if (key_len <= kMaxInternalKeyLength)
+ return std::string(entry->Data()->key);
+
+ // We keep a copy of the key so that we can always return it, even if the
+ // backend is disabled.
+ if (!key_.empty())
+ return key_;
+
+ Addr address(entry->Data()->long_key);
+ DCHECK(address.is_initialized());
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ COMPILE_ASSERT(kNumStreams == kKeyFileIndex, invalid_key_index);
+ File* key_file = const_cast<EntryImpl*>(this)->GetBackingFile(address,
+ kKeyFileIndex);
+ if (!key_file)
+ return std::string();
+
+ ++key_len; // We store a trailing \0 on disk that we read back below.
+ if (!offset && key_file->GetLength() != static_cast<size_t>(key_len))
+ return std::string();
+
+ if (!key_file->Read(WriteInto(&key_, key_len), key_len, offset))
+ key_.clear();
+ return key_;
+}
+
+Time EntryImpl::GetLastUsed() const {
+ CacheRankingsBlock* node = const_cast<CacheRankingsBlock*>(&node_);
+ return Time::FromInternalValue(node->Data()->last_used);
+}
+
+Time EntryImpl::GetLastModified() const {
+ CacheRankingsBlock* node = const_cast<CacheRankingsBlock*>(&node_);
+ return Time::FromInternalValue(node->Data()->last_modified);
+}
+
+int32 EntryImpl::GetDataSize(int index) const {
+ if (index < 0 || index >= kNumStreams)
+ return 0;
+
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ return entry->Data()->data_size[index];
+}
+
+int EntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return ReadDataImpl(index, offset, buf, buf_len, callback);
+
+ DCHECK(node_.Data()->dirty || read_only_);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = entry_.Data()->data_size[index];
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadData(this, index, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate) {
+ if (callback.is_null())
+ return WriteDataImpl(index, offset, buf, buf_len, callback, truncate);
+
+ DCHECK(node_.Data()->dirty || read_only_);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->WriteData(this, index, offset, buf, buf_len, truncate,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return ReadSparseDataImpl(offset, buf, buf_len, callback);
+
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadSparseData(this, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return WriteSparseDataImpl(offset, buf, buf_len, callback);
+
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->WriteSparseData(this, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) {
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->GetAvailableRange(this, offset, len, start, callback);
+ return net::ERR_IO_PENDING;
+}
+
+bool EntryImpl::CouldBeSparse() const {
+ if (sparse_.get())
+ return true;
+
+ scoped_ptr<SparseControl> sparse;
+ sparse.reset(new SparseControl(const_cast<EntryImpl*>(this)));
+ return sparse->CouldBeSparse();
+}
+
+void EntryImpl::CancelSparseIO() {
+ if (background_queue_.get())
+ background_queue_->CancelSparseIO(this);
+}
+
+int EntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
+ if (!sparse_.get())
+ return net::OK;
+
+ if (!background_queue_.get())
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadyForSparseIO(this, callback);
+ return net::ERR_IO_PENDING;
+}
+
+// When an entry is deleted from the cache, we clean up all the data associated
+// with it for two reasons: to simplify the reuse of the block (we know that any
+// unused block is filled with zeros), and to simplify the handling of write /
+// read partial information from an entry (don't have to worry about returning
+// data related to a previous cache entry because the range was not fully
+// written before).
+EntryImpl::~EntryImpl() {
+ if (!backend_.get()) {
+ entry_.clear_modified();
+ node_.clear_modified();
+ return;
+ }
+ Log("~EntryImpl in");
+
+ // Save the sparse info to disk. This will generate IO for this entry and
+ // maybe for a child entry, so it is important to do it before deleting this
+ // entry.
+ sparse_.reset();
+
+ // Remove this entry from the list of open entries.
+ backend_->OnEntryDestroyBegin(entry_.address());
+
+ if (doomed_) {
+ DeleteEntryData(true);
+ } else {
+#if defined(NET_BUILD_STRESS_CACHE)
+ SanityCheck();
+#endif
+ net_log_.AddEvent(net::NetLog::TYPE_ENTRY_CLOSE);
+ bool ret = true;
+ for (int index = 0; index < kNumStreams; index++) {
+ if (user_buffers_[index].get()) {
+ if (!(ret = Flush(index, 0)))
+ LOG(ERROR) << "Failed to save user data";
+ }
+ if (unreported_size_[index]) {
+ backend_->ModifyStorageSize(
+ entry_.Data()->data_size[index] - unreported_size_[index],
+ entry_.Data()->data_size[index]);
+ }
+ }
+
+ if (!ret) {
+ // There was a failure writing the actual data. Mark the entry as dirty.
+ int current_id = backend_->GetCurrentEntryId();
+ node_.Data()->dirty = current_id == 1 ? -1 : current_id - 1;
+ node_.Store();
+ } else if (node_.HasData() && !dirty_ && node_.Data()->dirty) {
+ node_.Data()->dirty = 0;
+ node_.Store();
+ }
+ }
+
+ Trace("~EntryImpl out 0x%p", reinterpret_cast<void*>(this));
+ net_log_.EndEvent(net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL);
+ backend_->OnEntryDestroyEnd();
+}
+
+// ------------------------------------------------------------------------
+
+int EntryImpl::InternalReadData(int index, int offset,
+ IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ DVLOG(2) << "Read from " << index << " at " << offset << " : " << buf_len;
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = entry_.Data()->data_size[index];
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!backend_.get())
+ return net::ERR_UNEXPECTED;
+
+ TimeTicks start = TimeTicks::Now();
+
+ if (offset + buf_len > entry_size)
+ buf_len = entry_size - offset;
+
+ UpdateRank(false);
+
+ backend_->OnEvent(Stats::READ_DATA);
+ backend_->OnRead(buf_len);
+
+ Addr address(entry_.Data()->data_addr[index]);
+ int eof = address.is_initialized() ? entry_size : 0;
+ if (user_buffers_[index].get() &&
+ user_buffers_[index]->PreRead(eof, offset, &buf_len)) {
+ // Complete the operation locally.
+ buf_len = user_buffers_[index]->Read(offset, buf, buf_len);
+ ReportIOTime(kRead, start);
+ return buf_len;
+ }
+
+ address.set_value(entry_.Data()->data_addr[index]);
+ DCHECK(address.is_initialized());
+ if (!address.is_initialized()) {
+ DoomImpl();
+ return net::ERR_FAILED;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file) {
+ DoomImpl();
+ LOG(ERROR) << "No file for " << std::hex << address.value();
+ return net::ERR_FILE_NOT_FOUND;
+ }
+
+ size_t file_offset = offset;
+ if (address.is_block_file()) {
+ DCHECK_LE(offset + buf_len, kMaxBlockSize);
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ }
+
+ SyncCallback* io_callback = NULL;
+ if (!callback.is_null()) {
+ io_callback = new SyncCallback(this, buf, callback,
+ net::NetLog::TYPE_ENTRY_READ_DATA);
+ }
+
+ TimeTicks start_async = TimeTicks::Now();
+
+ bool completed;
+ if (!file->Read(buf->data(), buf_len, file_offset, io_callback, &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ DoomImpl();
+ return net::ERR_CACHE_READ_FAILURE;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ if (io_callback)
+ ReportIOTime(kReadAsync1, start_async);
+
+ ReportIOTime(kRead, start);
+ return (completed || callback.is_null()) ? buf_len : net::ERR_IO_PENDING;
+}
+
+int EntryImpl::InternalWriteData(int index, int offset,
+ IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ DVLOG(2) << "Write to " << index << " at " << offset << " : " << buf_len;
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!backend_.get())
+ return net::ERR_UNEXPECTED;
+
+ int max_file_size = backend_->MaxFileSize();
+
+ // offset or buf_len could be negative numbers.
+ if (offset > max_file_size || buf_len > max_file_size ||
+ offset + buf_len > max_file_size) {
+ int size = offset + buf_len;
+ if (size <= max_file_size)
+ size = kint32max;
+ backend_->TooMuchStorageRequested(size);
+ return net::ERR_FAILED;
+ }
+
+ TimeTicks start = TimeTicks::Now();
+
+ // Read the size at this point (it may change inside prepare).
+ int entry_size = entry_.Data()->data_size[index];
+ bool extending = entry_size < offset + buf_len;
+ truncate = truncate && entry_size > offset + buf_len;
+ Trace("To PrepareTarget 0x%x", entry_.address().value());
+ if (!PrepareTarget(index, offset, buf_len, truncate))
+ return net::ERR_FAILED;
+
+ Trace("From PrepareTarget 0x%x", entry_.address().value());
+ if (extending || truncate)
+ UpdateSize(index, entry_size, offset + buf_len);
+
+ UpdateRank(true);
+
+ backend_->OnEvent(Stats::WRITE_DATA);
+ backend_->OnWrite(buf_len);
+
+ if (user_buffers_[index].get()) {
+ // Complete the operation locally.
+ user_buffers_[index]->Write(offset, buf, buf_len);
+ ReportIOTime(kWrite, start);
+ return buf_len;
+ }
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (offset + buf_len == 0) {
+ if (truncate) {
+ DCHECK(!address.is_initialized());
+ }
+ return 0;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return net::ERR_FILE_NOT_FOUND;
+
+ size_t file_offset = offset;
+ if (address.is_block_file()) {
+ DCHECK_LE(offset + buf_len, kMaxBlockSize);
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ } else if (truncate || (extending && !buf_len)) {
+ if (!file->SetLength(offset + buf_len))
+ return net::ERR_FAILED;
+ }
+
+ if (!buf_len)
+ return 0;
+
+ SyncCallback* io_callback = NULL;
+ if (!callback.is_null()) {
+ io_callback = new SyncCallback(this, buf, callback,
+ net::NetLog::TYPE_ENTRY_WRITE_DATA);
+ }
+
+ TimeTicks start_async = TimeTicks::Now();
+
+ bool completed;
+ if (!file->Write(buf->data(), buf_len, file_offset, io_callback,
+ &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ return net::ERR_CACHE_WRITE_FAILURE;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ if (io_callback)
+ ReportIOTime(kWriteAsync1, start_async);
+
+ ReportIOTime(kWrite, start);
+ return (completed || callback.is_null()) ? buf_len : net::ERR_IO_PENDING;
+}
+
+// ------------------------------------------------------------------------
+
+bool EntryImpl::CreateDataBlock(int index, int size) {
+ DCHECK(index >= 0 && index < kNumStreams);
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (!CreateBlock(size, &address))
+ return false;
+
+ entry_.Data()->data_addr[index] = address.value();
+ entry_.Store();
+ return true;
+}
+
+bool EntryImpl::CreateBlock(int size, Addr* address) {
+ DCHECK(!address->is_initialized());
+ if (!backend_.get())
+ return false;
+
+ FileType file_type = Addr::RequiredFileType(size);
+ if (EXTERNAL == file_type) {
+ if (size > backend_->MaxFileSize())
+ return false;
+ if (!backend_->CreateExternalFile(address))
+ return false;
+ } else {
+ int num_blocks = Addr::RequiredBlocks(size, file_type);
+
+ if (!backend_->CreateBlock(file_type, num_blocks, address))
+ return false;
+ }
+ return true;
+}
+
+// Note that this method may end up modifying a block file so upon return the
+// involved block will be free, and could be reused for something else. If there
+// is a crash after that point (and maybe before returning to the caller), the
+// entry will be left dirty... and at some point it will be discarded; it is
+// important that the entry doesn't keep a reference to this address, or we'll
+// end up deleting the contents of |address| once again.
+void EntryImpl::DeleteData(Addr address, int index) {
+ DCHECK(backend_.get());
+ if (!address.is_initialized())
+ return;
+ if (address.is_separate_file()) {
+ int failure = !DeleteCacheFile(backend_->GetFileName(address));
+ CACHE_UMA(COUNTS, "DeleteFailed", 0, failure);
+ if (failure) {
+ LOG(ERROR) << "Failed to delete " <<
+ backend_->GetFileName(address).value() << " from the cache.";
+ }
+ if (files_[index].get())
+ files_[index] = NULL; // Releases the object.
+ } else {
+ backend_->DeleteBlock(address, true);
+ }
+}
+
+void EntryImpl::UpdateRank(bool modified) {
+ if (!backend_.get())
+ return;
+
+ if (!doomed_) {
+ // Everything is handled by the backend.
+ backend_->UpdateRank(this, modified);
+ return;
+ }
+
+ Time current = Time::Now();
+ node_.Data()->last_used = current.ToInternalValue();
+
+ if (modified)
+ node_.Data()->last_modified = current.ToInternalValue();
+}
+
+File* EntryImpl::GetBackingFile(Addr address, int index) {
+ if (!backend_.get())
+ return NULL;
+
+ File* file;
+ if (address.is_separate_file())
+ file = GetExternalFile(address, index);
+ else
+ file = backend_->File(address);
+ return file;
+}
+
+File* EntryImpl::GetExternalFile(Addr address, int index) {
+ DCHECK(index >= 0 && index <= kKeyFileIndex);
+ if (!files_[index].get()) {
+ // For a key file, use mixed mode IO.
+ scoped_refptr<File> file(new File(kKeyFileIndex == index));
+ if (file->Init(backend_->GetFileName(address)))
+ files_[index].swap(file);
+ }
+ return files_[index].get();
+}
+
+// We keep a memory buffer for everything that ends up stored on a block file
+// (because we don't know yet the final data size), and for some of the data
+// that end up on external files. This function will initialize that memory
+// buffer and / or the files needed to store the data.
+//
+// In general, a buffer may overlap data already stored on disk, and in that
+// case, the contents of the buffer are the most accurate. It may also extend
+// the file, but we don't want to read from disk just to keep the buffer up to
+// date. This means that as soon as there is a chance to get confused about what
+// is the most recent version of some part of a file, we'll flush the buffer and
+// reuse it for the new data. Keep in mind that the normal use pattern is quite
+// simple (write sequentially from the beginning), so we optimize for handling
+// that case.
+bool EntryImpl::PrepareTarget(int index, int offset, int buf_len,
+ bool truncate) {
+ if (truncate)
+ return HandleTruncation(index, offset, buf_len);
+
+ if (!offset && !buf_len)
+ return true;
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized()) {
+ if (address.is_block_file() && !MoveToLocalBuffer(index))
+ return false;
+
+ if (!user_buffers_[index].get() && offset < kMaxBlockSize) {
+ // We are about to create a buffer for the first 16KB, make sure that we
+ // preserve existing data.
+ if (!CopyToLocalBuffer(index))
+ return false;
+ }
+ }
+
+ if (!user_buffers_[index].get())
+ user_buffers_[index].reset(new UserBuffer(backend_.get()));
+
+ return PrepareBuffer(index, offset, buf_len);
+}
+
+// We get to this function with some data already stored. If there is a
+// truncation that results on data stored internally, we'll explicitly
+// handle the case here.
+bool EntryImpl::HandleTruncation(int index, int offset, int buf_len) {
+ Addr address(entry_.Data()->data_addr[index]);
+
+ int current_size = entry_.Data()->data_size[index];
+ int new_size = offset + buf_len;
+
+ if (!new_size) {
+ // This is by far the most common scenario.
+ backend_->ModifyStorageSize(current_size - unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ unreported_size_[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+
+ user_buffers_[index].reset();
+ return true;
+ }
+
+ // We never postpone truncating a file, if there is one, but we may postpone
+ // telling the backend about the size reduction.
+ if (user_buffers_[index].get()) {
+ DCHECK_GE(current_size, user_buffers_[index]->Start());
+ if (!address.is_initialized()) {
+ // There is no overlap between the buffer and disk.
+ if (new_size > user_buffers_[index]->Start()) {
+ // Just truncate our buffer.
+ DCHECK_LT(new_size, user_buffers_[index]->End());
+ user_buffers_[index]->Truncate(new_size);
+ return true;
+ }
+
+ // Just discard our buffer.
+ user_buffers_[index]->Reset();
+ return PrepareBuffer(index, offset, buf_len);
+ }
+
+ // There is some overlap or we need to extend the file before the
+ // truncation.
+ if (offset > user_buffers_[index]->Start())
+ user_buffers_[index]->Truncate(new_size);
+ UpdateSize(index, current_size, new_size);
+ if (!Flush(index, 0))
+ return false;
+ user_buffers_[index].reset();
+ }
+
+ // We have data somewhere, and it is not in a buffer.
+ DCHECK(!user_buffers_[index].get());
+ DCHECK(address.is_initialized());
+
+ if (new_size > kMaxBlockSize)
+ return true; // Let the operation go directly to disk.
+
+ return ImportSeparateFile(index, offset + buf_len);
+}
+
+bool EntryImpl::CopyToLocalBuffer(int index) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(!user_buffers_[index].get());
+ DCHECK(address.is_initialized());
+
+ int len = std::min(entry_.Data()->data_size[index], kMaxBlockSize);
+ user_buffers_[index].reset(new UserBuffer(backend_.get()));
+ user_buffers_[index]->Write(len, NULL, 0);
+
+ File* file = GetBackingFile(address, index);
+ int offset = 0;
+
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!file ||
+ !file->Read(user_buffers_[index]->Data(), len, offset, NULL, NULL)) {
+ user_buffers_[index].reset();
+ return false;
+ }
+ return true;
+}
+
+bool EntryImpl::MoveToLocalBuffer(int index) {
+ if (!CopyToLocalBuffer(index))
+ return false;
+
+ Addr address(entry_.Data()->data_addr[index]);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+
+ // If we lose this entry we'll see it as zero sized.
+ int len = entry_.Data()->data_size[index];
+ backend_->ModifyStorageSize(len - unreported_size_[index], 0);
+ unreported_size_[index] = len;
+ return true;
+}
+
+bool EntryImpl::ImportSeparateFile(int index, int new_size) {
+ if (entry_.Data()->data_size[index] > new_size)
+ UpdateSize(index, entry_.Data()->data_size[index], new_size);
+
+ return MoveToLocalBuffer(index);
+}
+
+bool EntryImpl::PrepareBuffer(int index, int offset, int buf_len) {
+ DCHECK(user_buffers_[index].get());
+ if ((user_buffers_[index]->End() && offset > user_buffers_[index]->End()) ||
+ offset > entry_.Data()->data_size[index]) {
+ // We are about to extend the buffer or the file (with zeros), so make sure
+ // that we are not overwriting anything.
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized() && address.is_separate_file()) {
+ if (!Flush(index, 0))
+ return false;
+ // There is an actual file already, and we don't want to keep track of
+ // its length so we let this operation go straight to disk.
+ // The only case when a buffer is allowed to extend the file (as in fill
+ // with zeros before the start) is when there is no file yet to extend.
+ user_buffers_[index].reset();
+ return true;
+ }
+ }
+
+ if (!user_buffers_[index]->PreWrite(offset, buf_len)) {
+ if (!Flush(index, offset + buf_len))
+ return false;
+
+ // Lets try again.
+ if (offset > user_buffers_[index]->End() ||
+ !user_buffers_[index]->PreWrite(offset, buf_len)) {
+ // We cannot complete the operation with a buffer.
+ DCHECK(!user_buffers_[index]->Size());
+ DCHECK(!user_buffers_[index]->Start());
+ user_buffers_[index].reset();
+ }
+ }
+ return true;
+}
+
+bool EntryImpl::Flush(int index, int min_len) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(user_buffers_[index].get());
+ DCHECK(!address.is_initialized() || address.is_separate_file());
+ DVLOG(3) << "Flush";
+
+ int size = std::max(entry_.Data()->data_size[index], min_len);
+ if (size && !address.is_initialized() && !CreateDataBlock(index, size))
+ return false;
+
+ if (!entry_.Data()->data_size[index]) {
+ DCHECK(!user_buffers_[index]->Size());
+ return true;
+ }
+
+ address.set_value(entry_.Data()->data_addr[index]);
+
+ int len = user_buffers_[index]->Size();
+ int offset = user_buffers_[index]->Start();
+ if (!len && !offset)
+ return true;
+
+ if (address.is_block_file()) {
+ DCHECK_EQ(len, entry_.Data()->data_size[index]);
+ DCHECK(!offset);
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return false;
+
+ if (!file->Write(user_buffers_[index]->Data(), len, offset, NULL, NULL))
+ return false;
+ user_buffers_[index]->Reset();
+
+ return true;
+}
+
+void EntryImpl::UpdateSize(int index, int old_size, int new_size) {
+ if (entry_.Data()->data_size[index] == new_size)
+ return;
+
+ unreported_size_[index] += new_size - old_size;
+ entry_.Data()->data_size[index] = new_size;
+ entry_.set_modified();
+}
+
+int EntryImpl::InitSparseData() {
+ if (sparse_.get())
+ return net::OK;
+
+ // Use a local variable so that sparse_ never goes from 'valid' to NULL.
+ scoped_ptr<SparseControl> sparse(new SparseControl(this));
+ int result = sparse->Init();
+ if (net::OK == result)
+ sparse_.swap(sparse);
+
+ return result;
+}
+
+void EntryImpl::SetEntryFlags(uint32 flags) {
+ entry_.Data()->flags |= flags;
+ entry_.set_modified();
+}
+
+uint32 EntryImpl::GetEntryFlags() {
+ return entry_.Data()->flags;
+}
+
+void EntryImpl::GetData(int index, char** buffer, Addr* address) {
+ DCHECK(backend_.get());
+ if (user_buffers_[index].get() && user_buffers_[index]->Size() &&
+ !user_buffers_[index]->Start()) {
+ // The data is already in memory, just copy it and we're done.
+ int data_len = entry_.Data()->data_size[index];
+ if (data_len <= user_buffers_[index]->Size()) {
+ DCHECK(!user_buffers_[index]->Start());
+ *buffer = new char[data_len];
+ memcpy(*buffer, user_buffers_[index]->Data(), data_len);
+ return;
+ }
+ }
+
+ // Bad news: we'd have to read the info from disk so instead we'll just tell
+ // the caller where to read from.
+ *buffer = NULL;
+ address->set_value(entry_.Data()->data_addr[index]);
+ if (address->is_initialized()) {
+ // Prevent us from deleting the block from the backing store.
+ backend_->ModifyStorageSize(entry_.Data()->data_size[index] -
+ unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ }
+}
+
+void EntryImpl::Log(const char* msg) {
+ int dirty = 0;
+ if (node_.HasData()) {
+ dirty = node_.Data()->dirty;
+ }
+
+ Trace("%s 0x%p 0x%x 0x%x", msg, reinterpret_cast<void*>(this),
+ entry_.address().value(), node_.address().value());
+
+ Trace(" data: 0x%x 0x%x 0x%x", entry_.Data()->data_addr[0],
+ entry_.Data()->data_addr[1], entry_.Data()->long_key);
+
+ Trace(" doomed: %d 0x%x", doomed_, dirty);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/entry_impl.h b/chromium/net/disk_cache/entry_impl.h
new file mode 100644
index 00000000000..13d077c2f8e
--- /dev/null
+++ b/chromium/net/disk_cache/entry_impl.h
@@ -0,0 +1,278 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_ENTRY_IMPL_H_
+#define NET_DISK_CACHE_ENTRY_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/storage_block.h"
+#include "net/disk_cache/storage_block-inl.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+class InFlightBackendIO;
+class SparseControl;
+typedef StorageBlock<EntryStore> CacheEntryBlock;
+typedef StorageBlock<RankingsNode> CacheRankingsBlock;
+
+// This class implements the Entry interface. An object of this
+// class represents a single entry on the cache.
+class NET_EXPORT_PRIVATE EntryImpl
+ : public Entry,
+ public base::RefCounted<EntryImpl> {
+ friend class base::RefCounted<EntryImpl>;
+ friend class SparseControl;
+ public:
+ enum Operation {
+ kRead,
+ kWrite,
+ kSparseRead,
+ kSparseWrite,
+ kAsyncIO,
+ kReadAsync1,
+ kWriteAsync1
+ };
+
+ EntryImpl(BackendImpl* backend, Addr address, bool read_only);
+
+ // Background implementation of the Entry interface.
+ void DoomImpl();
+ int ReadDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int WriteDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate);
+ int ReadSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int WriteSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int GetAvailableRangeImpl(int64 offset, int len, int64* start);
+ void CancelSparseIOImpl();
+ int ReadyForSparseIOImpl(const CompletionCallback& callback);
+
+ inline CacheEntryBlock* entry() {
+ return &entry_;
+ }
+
+ inline CacheRankingsBlock* rankings() {
+ return &node_;
+ }
+
+ uint32 GetHash();
+
+ // Performs the initialization of a EntryImpl that will be added to the
+ // cache.
+ bool CreateEntry(Addr node_address, const std::string& key, uint32 hash);
+
+ // Returns true if this entry matches the lookup arguments.
+ bool IsSameEntry(const std::string& key, uint32 hash);
+
+ // Permamently destroys this entry.
+ void InternalDoom();
+
+ // Deletes this entry from disk. If |everything| is false, only the user data
+ // will be removed, leaving the key and control data intact.
+ void DeleteEntryData(bool everything);
+
+ // Returns the address of the next entry on the list of entries with the same
+ // hash.
+ CacheAddr GetNextAddress();
+
+ // Sets the address of the next entry on the list of entries with the same
+ // hash.
+ void SetNextAddress(Addr address);
+
+ // Reloads the rankings node information.
+ bool LoadNodeAddress();
+
+ // Updates the stored data to reflect the run-time information for this entry.
+ // Returns false if the data could not be updated. The purpose of this method
+ // is to be able to detect entries that are currently in use.
+ bool Update();
+
+ bool dirty() {
+ return dirty_;
+ }
+
+ bool doomed() {
+ return doomed_;
+ }
+
+ // Marks this entry as dirty (in memory) if needed. This is intended only for
+ // entries that are being read from disk, to be called during loading.
+ void SetDirtyFlag(int32 current_id);
+
+ // Fixes this entry so it can be treated as valid (to delete it).
+ void SetPointerForInvalidEntry(int32 new_id);
+
+ // Returns true if this entry is so meesed up that not everything is going to
+ // be removed.
+ bool LeaveRankingsBehind();
+
+ // Returns false if the entry is clearly invalid.
+ bool SanityCheck();
+ bool DataSanityCheck();
+
+ // Attempts to make this entry reachable though the key.
+ void FixForDelete();
+
+ // Handle the pending asynchronous IO count.
+ void IncrementIoCount();
+ void DecrementIoCount();
+
+ // This entry is being returned to the user. It is always called from the
+ // primary thread (not the dedicated cache thread).
+ void OnEntryCreated(BackendImpl* backend);
+
+ // Set the access times for this entry. This method provides support for
+ // the upgrade tool.
+ void SetTimes(base::Time last_used, base::Time last_modified);
+
+ // Generates a histogram for the time spent working on this operation.
+ void ReportIOTime(Operation op, const base::TimeTicks& start);
+
+ // Logs a begin event and enables logging for the EntryImpl. Will also cause
+ // an end event to be logged on destruction. The EntryImpl must have its key
+ // initialized before this is called. |created| is true if the Entry was
+ // created rather than opened.
+ void BeginLogging(net::NetLog* net_log, bool created);
+
+ const net::BoundNetLog& net_log() const;
+
+ // Returns the number of blocks needed to store an EntryStore.
+ static int NumBlocksForEntry(int key_size);
+
+ // Entry interface.
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ enum {
+ kNumStreams = 3
+ };
+ class UserBuffer;
+
+ virtual ~EntryImpl();
+
+ // Do all the work for ReadDataImpl and WriteDataImpl. Implemented as
+ // separate functions to make logging of results simpler.
+ int InternalReadData(int index, int offset, IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback);
+ int InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate);
+
+ // Initializes the storage for an internal or external data block.
+ bool CreateDataBlock(int index, int size);
+
+ // Initializes the storage for an internal or external generic block.
+ bool CreateBlock(int size, Addr* address);
+
+ // Deletes the data pointed by address, maybe backed by files_[index].
+ // Note that most likely the caller should delete (and store) the reference to
+ // |address| *before* calling this method because we don't want to have an
+ // entry using an address that is already free.
+ void DeleteData(Addr address, int index);
+
+ // Updates ranking information.
+ void UpdateRank(bool modified);
+
+ // Returns a pointer to the file that stores the given address.
+ File* GetBackingFile(Addr address, int index);
+
+ // Returns a pointer to the file that stores external data.
+ File* GetExternalFile(Addr address, int index);
+
+ // Prepares the target file or buffer for a write of buf_len bytes at the
+ // given offset.
+ bool PrepareTarget(int index, int offset, int buf_len, bool truncate);
+
+ // Adjusts the internal buffer and file handle for a write that truncates this
+ // stream.
+ bool HandleTruncation(int index, int offset, int buf_len);
+
+ // Copies data from disk to the internal buffer.
+ bool CopyToLocalBuffer(int index);
+
+ // Reads from a block data file to this object's memory buffer.
+ bool MoveToLocalBuffer(int index);
+
+ // Loads the external file to this object's memory buffer.
+ bool ImportSeparateFile(int index, int new_size);
+
+ // Makes sure that the internal buffer can handle the a write of |buf_len|
+ // bytes to |offset|.
+ bool PrepareBuffer(int index, int offset, int buf_len);
+
+ // Flushes the in-memory data to the backing storage. The data destination
+ // is determined based on the current data length and |min_len|.
+ bool Flush(int index, int min_len);
+
+ // Updates the size of a given data stream.
+ void UpdateSize(int index, int old_size, int new_size);
+
+ // Initializes the sparse control object. Returns a net error code.
+ int InitSparseData();
+
+ // Adds the provided |flags| to the current EntryFlags for this entry.
+ void SetEntryFlags(uint32 flags);
+
+ // Returns the current EntryFlags for this entry.
+ uint32 GetEntryFlags();
+
+ // Gets the data stored at the given index. If the information is in memory,
+ // a buffer will be allocated and the data will be copied to it (the caller
+ // can find out the size of the buffer before making this call). Otherwise,
+ // the cache address of the data will be returned, and that address will be
+ // removed from the regular book keeping of this entry so the caller is
+ // responsible for deleting the block (or file) from the backing store at some
+ // point; there is no need to report any storage-size change, only to do the
+ // actual cleanup.
+ void GetData(int index, char** buffer, Addr* address);
+
+ // Logs this entry to the internal trace buffer.
+ void Log(const char* msg);
+
+ CacheEntryBlock entry_; // Key related information for this entry.
+ CacheRankingsBlock node_; // Rankings related information for this entry.
+ base::WeakPtr<BackendImpl> backend_; // Back pointer to the cache.
+ base::WeakPtr<InFlightBackendIO> background_queue_; // In-progress queue.
+ scoped_ptr<UserBuffer> user_buffers_[kNumStreams]; // Stores user data.
+ // Files to store external user data and key.
+ scoped_refptr<File> files_[kNumStreams + 1];
+ mutable std::string key_; // Copy of the key.
+ int unreported_size_[kNumStreams]; // Bytes not reported yet to the backend.
+ bool doomed_; // True if this entry was removed from the cache.
+ bool read_only_; // True if not yet writing.
+ bool dirty_; // True if we detected that this is a dirty entry.
+ scoped_ptr<SparseControl> sparse_; // Support for sparse entries.
+
+ net::BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(EntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ENTRY_IMPL_H_
diff --git a/chromium/net/disk_cache/entry_unittest.cc b/chromium/net/disk_cache/entry_unittest.cc
new file mode 100644
index 00000000000..7addc85e5a0
--- /dev/null
+++ b/chromium/net/disk_cache/entry_unittest.cc
@@ -0,0 +1,3405 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/platform_thread.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/mem_entry_impl.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_entry_impl.h"
+#include "net/disk_cache/simple/simple_test_util.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+using disk_cache::ScopedEntryPtr;
+
+// Tests that can run with different types of caches.
+class DiskCacheEntryTest : public DiskCacheTestWithCache {
+ public:
+ void InternalSyncIOBackground(disk_cache::Entry* entry);
+ void ExternalSyncIOBackground(disk_cache::Entry* entry);
+
+ protected:
+ void InternalSyncIO();
+ void InternalAsyncIO();
+ void ExternalSyncIO();
+ void ExternalAsyncIO();
+ void ReleaseBuffer();
+ void StreamAccess();
+ void GetKey();
+ void GetTimes();
+ void GrowData();
+ void TruncateData();
+ void ZeroLengthIO();
+ void Buffering();
+ void SizeAtCreate();
+ void SizeChanges();
+ void ReuseEntry(int size);
+ void InvalidData();
+ void ReadWriteDestroyBuffer();
+ void DoomNormalEntry();
+ void DoomEntryNextToOpenEntry();
+ void DoomedEntry();
+ void BasicSparseIO();
+ void HugeSparseIO();
+ void GetAvailableRange();
+ void CouldBeSparse();
+ void UpdateSparseEntry();
+ void DoomSparseEntry();
+ void PartialSparseEntry();
+ bool SimpleCacheMakeBadChecksumEntry(const char* key, int* data_size);
+};
+
+// This part of the test runs on the background thread.
+void DiskCacheEntryTest::InternalSyncIOBackground(disk_cache::Entry* entry) {
+ const int kSize1 = 10;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ EXPECT_EQ(
+ 0,
+ entry->ReadData(0, 0, buffer1.get(), kSize1, net::CompletionCallback()));
+ base::strlcpy(buffer1->data(), "the data", kSize1);
+ EXPECT_EQ(10,
+ entry->WriteData(
+ 0, 0, buffer1.get(), kSize1, net::CompletionCallback(), false));
+ memset(buffer1->data(), 0, kSize1);
+ EXPECT_EQ(
+ 10,
+ entry->ReadData(0, 0, buffer1.get(), kSize1, net::CompletionCallback()));
+ EXPECT_STREQ("the data", buffer1->data());
+
+ const int kSize2 = 5000;
+ const int kSize3 = 10000;
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ scoped_refptr<net::IOBuffer> buffer3(new net::IOBuffer(kSize3));
+ memset(buffer3->data(), 0, kSize3);
+ CacheTestFillBuffer(buffer2->data(), kSize2, false);
+ base::strlcpy(buffer2->data(), "The really big data goes here", kSize2);
+ EXPECT_EQ(
+ 5000,
+ entry->WriteData(
+ 1, 1500, buffer2.get(), kSize2, net::CompletionCallback(), false));
+ memset(buffer2->data(), 0, kSize2);
+ EXPECT_EQ(4989,
+ entry->ReadData(
+ 1, 1511, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_STREQ("big data goes here", buffer2->data());
+ EXPECT_EQ(
+ 5000,
+ entry->ReadData(1, 0, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_EQ(0, memcmp(buffer2->data(), buffer3->data(), 1500));
+ EXPECT_EQ(1500,
+ entry->ReadData(
+ 1, 5000, buffer2.get(), kSize2, net::CompletionCallback()));
+
+ EXPECT_EQ(0,
+ entry->ReadData(
+ 1, 6500, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_EQ(
+ 6500,
+ entry->ReadData(1, 0, buffer3.get(), kSize3, net::CompletionCallback()));
+ EXPECT_EQ(8192,
+ entry->WriteData(
+ 1, 0, buffer3.get(), 8192, net::CompletionCallback(), false));
+ EXPECT_EQ(
+ 8192,
+ entry->ReadData(1, 0, buffer3.get(), kSize3, net::CompletionCallback()));
+ EXPECT_EQ(8192, entry->GetDataSize(1));
+
+ // We need to delete the memory buffer on this thread.
+ EXPECT_EQ(0, entry->WriteData(
+ 0, 0, NULL, 0, net::CompletionCallback(), true));
+ EXPECT_EQ(0, entry->WriteData(
+ 1, 0, NULL, 0, net::CompletionCallback(), true));
+}
+
+// We need to support synchronous IO even though it is not a supported operation
+// from the point of view of the disk cache's public interface, because we use
+// it internally, not just by a few tests, but as part of the implementation
+// (see sparse_control.cc, for example).
+void DiskCacheEntryTest::InternalSyncIO() {
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+ ASSERT_TRUE(NULL != entry);
+
+ // The bulk of the test runs from within the callback, on the cache thread.
+ RunTaskForTest(base::Bind(&DiskCacheEntryTest::InternalSyncIOBackground,
+ base::Unretained(this),
+ entry));
+
+
+ entry->Doom();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, InternalSyncIO) {
+ InitCache();
+ InternalSyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInternalSyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InternalSyncIO();
+}
+
+void DiskCacheEntryTest::InternalAsyncIO() {
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+ ASSERT_TRUE(NULL != entry);
+
+ // Avoid using internal buffers for the test. We have to write something to
+ // the entry and close it so that we flush the internal buffer to disk. After
+ // that, IO operations will be really hitting the disk. We don't care about
+ // the content, so just extending the entry is enough (all extensions zero-
+ // fill any holes).
+ EXPECT_EQ(0, WriteData(entry, 0, 15 * 1024, NULL, 0, false));
+ EXPECT_EQ(0, WriteData(entry, 1, 15 * 1024, NULL, 0, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry));
+
+ MessageLoopHelper helper;
+ // Let's verify that each IO goes to the right callback object.
+ CallbackTest callback1(&helper, false);
+ CallbackTest callback2(&helper, false);
+ CallbackTest callback3(&helper, false);
+ CallbackTest callback4(&helper, false);
+ CallbackTest callback5(&helper, false);
+ CallbackTest callback6(&helper, false);
+ CallbackTest callback7(&helper, false);
+ CallbackTest callback8(&helper, false);
+ CallbackTest callback9(&helper, false);
+ CallbackTest callback10(&helper, false);
+ CallbackTest callback11(&helper, false);
+ CallbackTest callback12(&helper, false);
+ CallbackTest callback13(&helper, false);
+
+ const int kSize1 = 10;
+ const int kSize2 = 5000;
+ const int kSize3 = 10000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ scoped_refptr<net::IOBuffer> buffer3(new net::IOBuffer(kSize3));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kSize2, false);
+ CacheTestFillBuffer(buffer3->data(), kSize3, false);
+
+ EXPECT_EQ(0,
+ entry->ReadData(
+ 0,
+ 15 * 1024,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback1))));
+ base::strlcpy(buffer1->data(), "the data", kSize1);
+ int expected = 0;
+ int ret = entry->WriteData(
+ 0,
+ 0,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback2)),
+ false);
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ memset(buffer2->data(), 0, kSize2);
+ ret = entry->ReadData(
+ 0,
+ 0,
+ buffer2.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback3)));
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("the data", buffer2->data());
+
+ base::strlcpy(buffer2->data(), "The really big data goes here", kSize2);
+ ret = entry->WriteData(
+ 1,
+ 1500,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback4)),
+ true);
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ memset(buffer3->data(), 0, kSize3);
+ ret = entry->ReadData(
+ 1,
+ 1511,
+ buffer3.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback5)));
+ EXPECT_TRUE(4989 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("big data goes here", buffer3->data());
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback6)));
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ memset(buffer3->data(), 0, kSize3);
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(0, memcmp(buffer2->data(), buffer3->data(), 1500));
+ ret = entry->ReadData(
+ 1,
+ 5000,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback7)));
+ EXPECT_TRUE(1500 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer3.get(),
+ kSize3,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback9)));
+ EXPECT_TRUE(6500 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry->WriteData(
+ 1,
+ 0,
+ buffer3.get(),
+ 8192,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback10)),
+ true);
+ EXPECT_TRUE(8192 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer3.get(),
+ kSize3,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback11)));
+ EXPECT_TRUE(8192 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_EQ(8192, entry->GetDataSize(1));
+
+ ret = entry->ReadData(
+ 0,
+ 0,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback12)));
+ EXPECT_TRUE(10 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback13)));
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ EXPECT_FALSE(helper.callback_reused_error());
+
+ entry->Doom();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, InternalAsyncIO) {
+ InitCache();
+ InternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInternalAsyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InternalAsyncIO();
+}
+
+// This part of the test runs on the background thread.
+void DiskCacheEntryTest::ExternalSyncIOBackground(disk_cache::Entry* entry) {
+ const int kSize1 = 17000;
+ const int kSize2 = 25000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kSize2, false);
+ base::strlcpy(buffer1->data(), "the data", kSize1);
+ EXPECT_EQ(17000,
+ entry->WriteData(
+ 0, 0, buffer1.get(), kSize1, net::CompletionCallback(), false));
+ memset(buffer1->data(), 0, kSize1);
+ EXPECT_EQ(
+ 17000,
+ entry->ReadData(0, 0, buffer1.get(), kSize1, net::CompletionCallback()));
+ EXPECT_STREQ("the data", buffer1->data());
+
+ base::strlcpy(buffer2->data(), "The really big data goes here", kSize2);
+ EXPECT_EQ(
+ 25000,
+ entry->WriteData(
+ 1, 10000, buffer2.get(), kSize2, net::CompletionCallback(), false));
+ memset(buffer2->data(), 0, kSize2);
+ EXPECT_EQ(24989,
+ entry->ReadData(
+ 1, 10011, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_STREQ("big data goes here", buffer2->data());
+ EXPECT_EQ(
+ 25000,
+ entry->ReadData(1, 0, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_EQ(5000,
+ entry->ReadData(
+ 1, 30000, buffer2.get(), kSize2, net::CompletionCallback()));
+
+ EXPECT_EQ(0,
+ entry->ReadData(
+ 1, 35000, buffer2.get(), kSize2, net::CompletionCallback()));
+ EXPECT_EQ(
+ 17000,
+ entry->ReadData(1, 0, buffer1.get(), kSize1, net::CompletionCallback()));
+ EXPECT_EQ(
+ 17000,
+ entry->WriteData(
+ 1, 20000, buffer1.get(), kSize1, net::CompletionCallback(), false));
+ EXPECT_EQ(37000, entry->GetDataSize(1));
+
+ // We need to delete the memory buffer on this thread.
+ EXPECT_EQ(0, entry->WriteData(
+ 0, 0, NULL, 0, net::CompletionCallback(), true));
+ EXPECT_EQ(0, entry->WriteData(
+ 1, 0, NULL, 0, net::CompletionCallback(), true));
+}
+
+void DiskCacheEntryTest::ExternalSyncIO() {
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+
+ // The bulk of the test runs from within the callback, on the cache thread.
+ RunTaskForTest(base::Bind(&DiskCacheEntryTest::ExternalSyncIOBackground,
+ base::Unretained(this),
+ entry));
+
+ entry->Doom();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, ExternalSyncIO) {
+ InitCache();
+ ExternalSyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, ExternalSyncIONoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ ExternalSyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyExternalSyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ExternalSyncIO();
+}
+
+void DiskCacheEntryTest::ExternalAsyncIO() {
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+
+ int expected = 0;
+
+ MessageLoopHelper helper;
+ // Let's verify that each IO goes to the right callback object.
+ CallbackTest callback1(&helper, false);
+ CallbackTest callback2(&helper, false);
+ CallbackTest callback3(&helper, false);
+ CallbackTest callback4(&helper, false);
+ CallbackTest callback5(&helper, false);
+ CallbackTest callback6(&helper, false);
+ CallbackTest callback7(&helper, false);
+ CallbackTest callback8(&helper, false);
+ CallbackTest callback9(&helper, false);
+
+ const int kSize1 = 17000;
+ const int kSize2 = 25000;
+ const int kSize3 = 25000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ scoped_refptr<net::IOBuffer> buffer3(new net::IOBuffer(kSize3));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kSize2, false);
+ CacheTestFillBuffer(buffer3->data(), kSize3, false);
+ base::strlcpy(buffer1->data(), "the data", kSize1);
+ int ret = entry->WriteData(
+ 0,
+ 0,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback1)),
+ false);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ memset(buffer2->data(), 0, kSize1);
+ ret = entry->ReadData(
+ 0,
+ 0,
+ buffer2.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback2)));
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("the data", buffer2->data());
+
+ base::strlcpy(buffer2->data(), "The really big data goes here", kSize2);
+ ret = entry->WriteData(
+ 1,
+ 10000,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback3)),
+ false);
+ EXPECT_TRUE(25000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+
+ memset(buffer3->data(), 0, kSize3);
+ ret = entry->ReadData(
+ 1,
+ 10011,
+ buffer3.get(),
+ kSize3,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback4)));
+ EXPECT_TRUE(24989 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_STREQ("big data goes here", buffer3->data());
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback5)));
+ EXPECT_TRUE(25000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ memset(buffer3->data(), 0, kSize3);
+ EXPECT_EQ(0, memcmp(buffer2->data(), buffer3->data(), 10000));
+ ret = entry->ReadData(
+ 1,
+ 30000,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback6)));
+ EXPECT_TRUE(5000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_EQ(0,
+ entry->ReadData(
+ 1,
+ 35000,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback7))));
+ ret = entry->ReadData(
+ 1,
+ 0,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback8)));
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+ ret = entry->WriteData(
+ 1,
+ 20000,
+ buffer3.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback9)),
+ false);
+ EXPECT_TRUE(17000 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(37000, entry->GetDataSize(1));
+
+ EXPECT_FALSE(helper.callback_reused_error());
+
+ entry->Doom();
+ entry->Close();
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, ExternalAsyncIO) {
+ InitCache();
+ ExternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, ExternalAsyncIONoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ ExternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyExternalAsyncIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ExternalAsyncIO();
+}
+
+// Tests that IOBuffers are not referenced after IO completes.
+void DiskCacheEntryTest::ReleaseBuffer() {
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+ ASSERT_TRUE(NULL != entry);
+
+ const int kBufferSize = 1024;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+ CacheTestFillBuffer(buffer->data(), kBufferSize, false);
+
+ net::ReleaseBufferCompletionCallback cb(buffer.get());
+ int rv =
+ entry->WriteData(0, 0, buffer.get(), kBufferSize, cb.callback(), false);
+ EXPECT_EQ(kBufferSize, cb.GetResult(rv));
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, ReleaseBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ ReleaseBuffer();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyReleaseBuffer) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ReleaseBuffer();
+}
+
+void DiskCacheEntryTest::StreamAccess() {
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry("the first key", &entry));
+ ASSERT_TRUE(NULL != entry);
+
+ const int kBufferSize = 1024;
+ const int kNumStreams = 3;
+ scoped_refptr<net::IOBuffer> reference_buffers[kNumStreams];
+ for (int i = 0; i < kNumStreams; i++) {
+ reference_buffers[i] = new net::IOBuffer(kBufferSize);
+ CacheTestFillBuffer(reference_buffers[i]->data(), kBufferSize, false);
+ }
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kBufferSize));
+ for (int i = 0; i < kNumStreams; i++) {
+ EXPECT_EQ(
+ kBufferSize,
+ WriteData(entry, i, 0, reference_buffers[i].get(), kBufferSize, false));
+ memset(buffer1->data(), 0, kBufferSize);
+ EXPECT_EQ(kBufferSize, ReadData(entry, i, 0, buffer1.get(), kBufferSize));
+ EXPECT_EQ(
+ 0, memcmp(reference_buffers[i]->data(), buffer1->data(), kBufferSize));
+ }
+ EXPECT_EQ(net::ERR_INVALID_ARGUMENT,
+ ReadData(entry, kNumStreams, 0, buffer1.get(), kBufferSize));
+ entry->Close();
+
+ // Open the entry and read it in chunks, including a read past the end.
+ ASSERT_EQ(net::OK, OpenEntry("the first key", &entry));
+ ASSERT_TRUE(NULL != entry);
+ const int kReadBufferSize = 600;
+ const int kFinalReadSize = kBufferSize - kReadBufferSize;
+ COMPILE_ASSERT(kFinalReadSize < kReadBufferSize, should_be_exactly_two_reads);
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kReadBufferSize));
+ for (int i = 0; i < kNumStreams; i++) {
+ memset(buffer2->data(), 0, kReadBufferSize);
+ EXPECT_EQ(kReadBufferSize,
+ ReadData(entry, i, 0, buffer2.get(), kReadBufferSize));
+ EXPECT_EQ(
+ 0,
+ memcmp(reference_buffers[i]->data(), buffer2->data(), kReadBufferSize));
+
+ memset(buffer2->data(), 0, kReadBufferSize);
+ EXPECT_EQ(
+ kFinalReadSize,
+ ReadData(entry, i, kReadBufferSize, buffer2.get(), kReadBufferSize));
+ EXPECT_EQ(0,
+ memcmp(reference_buffers[i]->data() + kReadBufferSize,
+ buffer2->data(),
+ kFinalReadSize));
+ }
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, StreamAccess) {
+ InitCache();
+ StreamAccess();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyStreamAccess) {
+ SetMemoryOnlyMode();
+ InitCache();
+ StreamAccess();
+}
+
+void DiskCacheEntryTest::GetKey() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_EQ(key, entry->GetKey()) << "short key";
+ entry->Close();
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ char key_buffer[20000];
+
+ CacheTestFillBuffer(key_buffer, 3000, true);
+ key_buffer[1000] = '\0';
+
+ key = key_buffer;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_TRUE(key == entry->GetKey()) << "1000 bytes key";
+ entry->Close();
+
+ key_buffer[1000] = 'p';
+ key_buffer[3000] = '\0';
+ key = key_buffer;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_TRUE(key == entry->GetKey()) << "medium size key";
+ entry->Close();
+
+ CacheTestFillBuffer(key_buffer, sizeof(key_buffer), true);
+ key_buffer[19999] = '\0';
+
+ key = key_buffer;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_TRUE(key == entry->GetKey()) << "long key";
+ entry->Close();
+
+ CacheTestFillBuffer(key_buffer, 0x4000, true);
+ key_buffer[0x4000] = '\0';
+
+ key = key_buffer;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_TRUE(key == entry->GetKey()) << "16KB key";
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GetKey) {
+ InitCache();
+ GetKey();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGetKey) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GetKey();
+}
+
+void DiskCacheEntryTest::GetTimes() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+
+ Time t1 = Time::Now();
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_TRUE(entry->GetLastModified() >= t1);
+ EXPECT_TRUE(entry->GetLastModified() == entry->GetLastUsed());
+
+ AddDelay();
+ Time t2 = Time::Now();
+ EXPECT_TRUE(t2 > t1);
+ EXPECT_EQ(0, WriteData(entry, 0, 200, NULL, 0, false));
+ if (type_ == net::APP_CACHE) {
+ EXPECT_TRUE(entry->GetLastModified() < t2);
+ } else {
+ EXPECT_TRUE(entry->GetLastModified() >= t2);
+ }
+ EXPECT_TRUE(entry->GetLastModified() == entry->GetLastUsed());
+
+ AddDelay();
+ Time t3 = Time::Now();
+ EXPECT_TRUE(t3 > t2);
+ const int kSize = 200;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ EXPECT_EQ(kSize, ReadData(entry, 0, 0, buffer.get(), kSize));
+ if (type_ == net::APP_CACHE) {
+ EXPECT_TRUE(entry->GetLastUsed() < t2);
+ EXPECT_TRUE(entry->GetLastModified() < t2);
+ } else if (type_ == net::SHADER_CACHE) {
+ EXPECT_TRUE(entry->GetLastUsed() < t3);
+ EXPECT_TRUE(entry->GetLastModified() < t3);
+ } else {
+ EXPECT_TRUE(entry->GetLastUsed() >= t3);
+ EXPECT_TRUE(entry->GetLastModified() < t3);
+ }
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GetTimes) {
+ InitCache();
+ GetTimes();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGetTimes) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GetTimes();
+}
+
+TEST_F(DiskCacheEntryTest, AppCacheGetTimes) {
+ SetCacheType(net::APP_CACHE);
+ InitCache();
+ GetTimes();
+}
+
+TEST_F(DiskCacheEntryTest, ShaderCacheGetTimes) {
+ SetCacheType(net::SHADER_CACHE);
+ InitCache();
+ GetTimes();
+}
+
+void DiskCacheEntryTest::GrowData() {
+ std::string key1("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+
+ const int kSize = 20000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, false);
+ memset(buffer2->data(), 0, kSize);
+
+ base::strlcpy(buffer1->data(), "the data", kSize);
+ EXPECT_EQ(10, WriteData(entry, 0, 0, buffer1.get(), 10, false));
+ EXPECT_EQ(10, ReadData(entry, 0, 0, buffer2.get(), 10));
+ EXPECT_STREQ("the data", buffer2->data());
+ EXPECT_EQ(10, entry->GetDataSize(0));
+
+ EXPECT_EQ(2000, WriteData(entry, 0, 0, buffer1.get(), 2000, false));
+ EXPECT_EQ(2000, entry->GetDataSize(0));
+ EXPECT_EQ(2000, ReadData(entry, 0, 0, buffer2.get(), 2000));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), 2000));
+
+ EXPECT_EQ(20000, WriteData(entry, 0, 0, buffer1.get(), kSize, false));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(20000, ReadData(entry, 0, 0, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), kSize));
+ entry->Close();
+
+ memset(buffer2->data(), 0, kSize);
+ std::string key2("Second key");
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
+ EXPECT_EQ(10, WriteData(entry, 0, 0, buffer1.get(), 10, false));
+ EXPECT_EQ(10, entry->GetDataSize(0));
+ entry->Close();
+
+ // Go from an internal address to a bigger block size.
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
+ EXPECT_EQ(2000, WriteData(entry, 0, 0, buffer1.get(), 2000, false));
+ EXPECT_EQ(2000, entry->GetDataSize(0));
+ EXPECT_EQ(2000, ReadData(entry, 0, 0, buffer2.get(), 2000));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), 2000));
+ entry->Close();
+ memset(buffer2->data(), 0, kSize);
+
+ // Go from an internal address to an external one.
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
+ EXPECT_EQ(20000, WriteData(entry, 0, 0, buffer1.get(), kSize, false));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(20000, ReadData(entry, 0, 0, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), kSize));
+ entry->Close();
+
+ // Double check the size from disk.
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+
+ // Now extend the entry without actual data.
+ EXPECT_EQ(0, WriteData(entry, 0, 45500, buffer1.get(), 0, false));
+ entry->Close();
+
+ // And check again from disk.
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
+ EXPECT_EQ(45500, entry->GetDataSize(0));
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GrowData) {
+ InitCache();
+ GrowData();
+}
+
+TEST_F(DiskCacheEntryTest, GrowDataNoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ GrowData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGrowData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GrowData();
+}
+
+void DiskCacheEntryTest::TruncateData() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize1 = 20000;
+ const int kSize2 = 20000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ memset(buffer2->data(), 0, kSize2);
+
+ // Simple truncation:
+ EXPECT_EQ(200, WriteData(entry, 0, 0, buffer1.get(), 200, false));
+ EXPECT_EQ(200, entry->GetDataSize(0));
+ EXPECT_EQ(100, WriteData(entry, 0, 0, buffer1.get(), 100, false));
+ EXPECT_EQ(200, entry->GetDataSize(0));
+ EXPECT_EQ(100, WriteData(entry, 0, 0, buffer1.get(), 100, true));
+ EXPECT_EQ(100, entry->GetDataSize(0));
+ EXPECT_EQ(0, WriteData(entry, 0, 50, buffer1.get(), 0, true));
+ EXPECT_EQ(50, entry->GetDataSize(0));
+ EXPECT_EQ(0, WriteData(entry, 0, 0, buffer1.get(), 0, true));
+ EXPECT_EQ(0, entry->GetDataSize(0));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ // Go to an external file.
+ EXPECT_EQ(20000, WriteData(entry, 0, 0, buffer1.get(), 20000, true));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(20000, ReadData(entry, 0, 0, buffer2.get(), 20000));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), 20000));
+ memset(buffer2->data(), 0, kSize2);
+
+ // External file truncation
+ EXPECT_EQ(18000, WriteData(entry, 0, 0, buffer1.get(), 18000, false));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(18000, WriteData(entry, 0, 0, buffer1.get(), 18000, true));
+ EXPECT_EQ(18000, entry->GetDataSize(0));
+ EXPECT_EQ(0, WriteData(entry, 0, 17500, buffer1.get(), 0, true));
+ EXPECT_EQ(17500, entry->GetDataSize(0));
+
+ // And back to an internal block.
+ EXPECT_EQ(600, WriteData(entry, 0, 1000, buffer1.get(), 600, true));
+ EXPECT_EQ(1600, entry->GetDataSize(0));
+ EXPECT_EQ(600, ReadData(entry, 0, 1000, buffer2.get(), 600));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), 600));
+ EXPECT_EQ(1000, ReadData(entry, 0, 0, buffer2.get(), 1000));
+ EXPECT_TRUE(!memcmp(buffer1->data(), buffer2->data(), 1000))
+ << "Preserves previous data";
+
+ // Go from external file to zero length.
+ EXPECT_EQ(20000, WriteData(entry, 0, 0, buffer1.get(), 20000, true));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(0, WriteData(entry, 0, 0, buffer1.get(), 0, true));
+ EXPECT_EQ(0, entry->GetDataSize(0));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, TruncateData) {
+ InitCache();
+ TruncateData();
+}
+
+TEST_F(DiskCacheEntryTest, TruncateDataNoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ TruncateData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyTruncateData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ TruncateData();
+}
+
+void DiskCacheEntryTest::ZeroLengthIO() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ EXPECT_EQ(0, ReadData(entry, 0, 0, NULL, 0));
+ EXPECT_EQ(0, WriteData(entry, 0, 0, NULL, 0, false));
+
+ // This write should extend the entry.
+ EXPECT_EQ(0, WriteData(entry, 0, 1000, NULL, 0, false));
+ EXPECT_EQ(0, ReadData(entry, 0, 500, NULL, 0));
+ EXPECT_EQ(0, ReadData(entry, 0, 2000, NULL, 0));
+ EXPECT_EQ(1000, entry->GetDataSize(0));
+
+ EXPECT_EQ(0, WriteData(entry, 0, 100000, NULL, 0, true));
+ EXPECT_EQ(0, ReadData(entry, 0, 50000, NULL, 0));
+ EXPECT_EQ(100000, entry->GetDataSize(0));
+
+ // Let's verify the actual content.
+ const int kSize = 20;
+ const char zeros[kSize] = {};
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ EXPECT_EQ(kSize, ReadData(entry, 0, 500, buffer.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer->data(), zeros, kSize));
+
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ EXPECT_EQ(kSize, ReadData(entry, 0, 5000, buffer.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer->data(), zeros, kSize));
+
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ EXPECT_EQ(kSize, ReadData(entry, 0, 50000, buffer.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer->data(), zeros, kSize));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, ZeroLengthIO) {
+ InitCache();
+ ZeroLengthIO();
+}
+
+TEST_F(DiskCacheEntryTest, ZeroLengthIONoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ ZeroLengthIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyZeroLengthIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ ZeroLengthIO();
+}
+
+// Tests that we handle the content correctly when buffering, a feature of the
+// standard cache that permits fast responses to certain reads.
+void DiskCacheEntryTest::Buffering() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 200;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, true);
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+
+ EXPECT_EQ(kSize, WriteData(entry, 1, 0, buffer1.get(), kSize, false));
+ entry->Close();
+
+ // Write a little more and read what we wrote before.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 5000, buffer1.get(), kSize, false));
+ EXPECT_EQ(kSize, ReadData(entry, 1, 0, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+
+ // Now go to an external file.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 18000, buffer1.get(), kSize, false));
+ entry->Close();
+
+ // Write something else and verify old data.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 10000, buffer1.get(), kSize, false));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 5000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 0, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 18000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+
+ // Extend the file some more.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 23000, buffer1.get(), kSize, false));
+ entry->Close();
+
+ // And now make sure that we can deal with data in both places (ram/disk).
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 17000, buffer1.get(), kSize, false));
+
+ // We should not overwrite the data at 18000 with this.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 19000, buffer1.get(), kSize, false));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 18000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 17000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+
+ EXPECT_EQ(kSize, WriteData(entry, 1, 22900, buffer1.get(), kSize, false));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(100, ReadData(entry, 1, 23000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data() + 100, 100));
+
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(100, ReadData(entry, 1, 23100, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data() + 100, 100));
+
+ // Extend the file again and read before without closing the entry.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 25000, buffer1.get(), kSize, false));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 45000, buffer1.get(), kSize, false));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 25000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 45000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data(), kSize));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, Buffering) {
+ InitCache();
+ Buffering();
+}
+
+TEST_F(DiskCacheEntryTest, BufferingNoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ Buffering();
+}
+
+// Checks that entries are zero length when created.
+void DiskCacheEntryTest::SizeAtCreate() {
+ const char key[] = "the first key";
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kNumStreams = 3;
+ for (int i = 0; i < kNumStreams; ++i)
+ EXPECT_EQ(0, entry->GetDataSize(i));
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, SizeAtCreate) {
+ InitCache();
+ SizeAtCreate();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlySizeAtCreate) {
+ SetMemoryOnlyMode();
+ InitCache();
+ SizeAtCreate();
+}
+
+// Some extra tests to make sure that buffering works properly when changing
+// the entry size.
+void DiskCacheEntryTest::SizeChanges() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 200;
+ const char zeros[kSize] = {};
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, true);
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+
+ EXPECT_EQ(kSize, WriteData(entry, 1, 0, buffer1.get(), kSize, true));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 17000, buffer1.get(), kSize, true));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 23000, buffer1.get(), kSize, true));
+ entry->Close();
+
+ // Extend the file and read between the old size and the new write.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(23000 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 25000, buffer1.get(), kSize, true));
+ EXPECT_EQ(25000 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(kSize, ReadData(entry, 1, 24000, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), zeros, kSize));
+
+ // Read at the end of the old file size.
+ EXPECT_EQ(kSize,
+ ReadData(entry, 1, 23000 + kSize - 35, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data() + kSize - 35, 35));
+
+ // Read slightly before the last write.
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 24900, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), zeros, 100));
+ EXPECT_TRUE(!memcmp(buffer2->data() + 100, buffer1->data(), kSize - 100));
+
+ // Extend the entry a little more.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 26000, buffer1.get(), kSize, true));
+ EXPECT_EQ(26000 + kSize, entry->GetDataSize(1));
+ CacheTestFillBuffer(buffer2->data(), kSize, true);
+ EXPECT_EQ(kSize, ReadData(entry, 1, 25900, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), zeros, 100));
+ EXPECT_TRUE(!memcmp(buffer2->data() + 100, buffer1->data(), kSize - 100));
+
+ // And now reduce the size.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 25000, buffer1.get(), kSize, true));
+ EXPECT_EQ(25000 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(28, ReadData(entry, 1, 25000 + kSize - 28, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), buffer1->data() + kSize - 28, 28));
+
+ // Reduce the size with a buffer that is not extending the size.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 24000, buffer1.get(), kSize, false));
+ EXPECT_EQ(25000 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 24500, buffer1.get(), kSize, true));
+ EXPECT_EQ(24500 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(kSize, ReadData(entry, 1, 23900, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), zeros, 100));
+ EXPECT_TRUE(!memcmp(buffer2->data() + 100, buffer1->data(), kSize - 100));
+
+ // And now reduce the size below the old size.
+ EXPECT_EQ(kSize, WriteData(entry, 1, 19000, buffer1.get(), kSize, true));
+ EXPECT_EQ(19000 + kSize, entry->GetDataSize(1));
+ EXPECT_EQ(kSize, ReadData(entry, 1, 18900, buffer2.get(), kSize));
+ EXPECT_TRUE(!memcmp(buffer2->data(), zeros, 100));
+ EXPECT_TRUE(!memcmp(buffer2->data() + 100, buffer1->data(), kSize - 100));
+
+ // Verify that the actual file is truncated.
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(19000 + kSize, entry->GetDataSize(1));
+
+ // Extend the newly opened file with a zero length write, expect zero fill.
+ EXPECT_EQ(0, WriteData(entry, 1, 20000 + kSize, buffer1.get(), 0, false));
+ EXPECT_EQ(kSize, ReadData(entry, 1, 19000 + kSize, buffer1.get(), kSize));
+ EXPECT_EQ(0, memcmp(buffer1->data(), zeros, kSize));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, SizeChanges) {
+ InitCache();
+ SizeChanges();
+}
+
+TEST_F(DiskCacheEntryTest, SizeChangesNoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ SizeChanges();
+}
+
+// Write more than the total cache capacity but to a single entry. |size| is the
+// amount of bytes to write each time.
+void DiskCacheEntryTest::ReuseEntry(int size) {
+ std::string key1("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+
+ entry->Close();
+ std::string key2("the second key");
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry));
+
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size));
+ CacheTestFillBuffer(buffer->data(), size, false);
+
+ for (int i = 0; i < 15; i++) {
+ EXPECT_EQ(0, WriteData(entry, 0, 0, buffer.get(), 0, true));
+ EXPECT_EQ(size, WriteData(entry, 0, 0, buffer.get(), size, false));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key2, &entry));
+ }
+
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key1, &entry)) << "have not evicted this entry";
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, ReuseExternalEntry) {
+ SetMaxSize(200 * 1024);
+ InitCache();
+ ReuseEntry(20 * 1024);
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyReuseExternalEntry) {
+ SetMemoryOnlyMode();
+ SetMaxSize(200 * 1024);
+ InitCache();
+ ReuseEntry(20 * 1024);
+}
+
+TEST_F(DiskCacheEntryTest, ReuseInternalEntry) {
+ SetMaxSize(100 * 1024);
+ InitCache();
+ ReuseEntry(10 * 1024);
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyReuseInternalEntry) {
+ SetMemoryOnlyMode();
+ SetMaxSize(100 * 1024);
+ InitCache();
+ ReuseEntry(10 * 1024);
+}
+
+// Reading somewhere that was not written should return zeros.
+void DiskCacheEntryTest::InvalidData() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize1 = 20000;
+ const int kSize2 = 20000;
+ const int kSize3 = 20000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ scoped_refptr<net::IOBuffer> buffer3(new net::IOBuffer(kSize3));
+
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ memset(buffer2->data(), 0, kSize2);
+
+ // Simple data grow:
+ EXPECT_EQ(200, WriteData(entry, 0, 400, buffer1.get(), 200, false));
+ EXPECT_EQ(600, entry->GetDataSize(0));
+ EXPECT_EQ(100, ReadData(entry, 0, 300, buffer3.get(), 100));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 100));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ // The entry is now on disk. Load it and extend it.
+ EXPECT_EQ(200, WriteData(entry, 0, 800, buffer1.get(), 200, false));
+ EXPECT_EQ(1000, entry->GetDataSize(0));
+ EXPECT_EQ(100, ReadData(entry, 0, 700, buffer3.get(), 100));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 100));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ // This time using truncate.
+ EXPECT_EQ(200, WriteData(entry, 0, 1800, buffer1.get(), 200, true));
+ EXPECT_EQ(2000, entry->GetDataSize(0));
+ EXPECT_EQ(100, ReadData(entry, 0, 1500, buffer3.get(), 100));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 100));
+
+ // Go to an external file.
+ EXPECT_EQ(200, WriteData(entry, 0, 19800, buffer1.get(), 200, false));
+ EXPECT_EQ(20000, entry->GetDataSize(0));
+ EXPECT_EQ(4000, ReadData(entry, 0, 14000, buffer3.get(), 4000));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 4000));
+
+ // And back to an internal block.
+ EXPECT_EQ(600, WriteData(entry, 0, 1000, buffer1.get(), 600, true));
+ EXPECT_EQ(1600, entry->GetDataSize(0));
+ EXPECT_EQ(600, ReadData(entry, 0, 1000, buffer3.get(), 600));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer1->data(), 600));
+
+ // Extend it again.
+ EXPECT_EQ(600, WriteData(entry, 0, 2000, buffer1.get(), 600, false));
+ EXPECT_EQ(2600, entry->GetDataSize(0));
+ EXPECT_EQ(200, ReadData(entry, 0, 1800, buffer3.get(), 200));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 200));
+
+ // And again (with truncation flag).
+ EXPECT_EQ(600, WriteData(entry, 0, 3000, buffer1.get(), 600, true));
+ EXPECT_EQ(3600, entry->GetDataSize(0));
+ EXPECT_EQ(200, ReadData(entry, 0, 2800, buffer3.get(), 200));
+ EXPECT_TRUE(!memcmp(buffer3->data(), buffer2->data(), 200));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, InvalidData) {
+ InitCache();
+ InvalidData();
+}
+
+TEST_F(DiskCacheEntryTest, InvalidDataNoBuffer) {
+ InitCache();
+ cache_impl_->SetFlags(disk_cache::kNoBuffering);
+ InvalidData();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyInvalidData) {
+ SetMemoryOnlyMode();
+ InitCache();
+ InvalidData();
+}
+
+// Tests that the cache preserves the buffer of an IO operation.
+void DiskCacheEntryTest::ReadWriteDestroyBuffer() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 200;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+
+ net::TestCompletionCallback cb;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->WriteData(0, 0, buffer.get(), kSize, cb.callback(), false));
+
+ // Release our reference to the buffer.
+ buffer = NULL;
+ EXPECT_EQ(kSize, cb.WaitForResult());
+
+ // And now test with a Read().
+ buffer = new net::IOBuffer(kSize);
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(0, 0, buffer.get(), kSize, cb.callback()));
+ buffer = NULL;
+ EXPECT_EQ(kSize, cb.WaitForResult());
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, ReadWriteDestroyBuffer) {
+ InitCache();
+ ReadWriteDestroyBuffer();
+}
+
+void DiskCacheEntryTest::DoomNormalEntry() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Doom();
+ entry->Close();
+
+ const int kSize = 20000;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, true);
+ buffer->data()[19999] = '\0';
+
+ key = buffer->data();
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_EQ(20000, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+ EXPECT_EQ(20000, WriteData(entry, 1, 0, buffer.get(), kSize, false));
+ entry->Doom();
+ entry->Close();
+
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, DoomEntry) {
+ InitCache();
+ DoomNormalEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyDoomEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ DoomNormalEntry();
+}
+
+// Tests dooming an entry that's linked to an open entry.
+void DiskCacheEntryTest::DoomEntryNextToOpenEntry() {
+ disk_cache::Entry* entry1;
+ disk_cache::Entry* entry2;
+ ASSERT_EQ(net::OK, CreateEntry("fixed", &entry1));
+ entry1->Close();
+ ASSERT_EQ(net::OK, CreateEntry("foo", &entry1));
+ entry1->Close();
+ ASSERT_EQ(net::OK, CreateEntry("bar", &entry1));
+ entry1->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry("foo", &entry1));
+ ASSERT_EQ(net::OK, OpenEntry("bar", &entry2));
+ entry2->Doom();
+ entry2->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry("foo", &entry2));
+ entry2->Doom();
+ entry2->Close();
+ entry1->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry("fixed", &entry1));
+ entry1->Close();
+}
+
+TEST_F(DiskCacheEntryTest, DoomEntryNextToOpenEntry) {
+ InitCache();
+ DoomEntryNextToOpenEntry();
+}
+
+TEST_F(DiskCacheEntryTest, NewEvictionDoomEntryNextToOpenEntry) {
+ SetNewEviction();
+ InitCache();
+ DoomEntryNextToOpenEntry();
+}
+
+TEST_F(DiskCacheEntryTest, AppCacheDoomEntryNextToOpenEntry) {
+ SetCacheType(net::APP_CACHE);
+ InitCache();
+ DoomEntryNextToOpenEntry();
+}
+
+// Verify that basic operations work as expected with doomed entries.
+void DiskCacheEntryTest::DoomedEntry() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ entry->Doom();
+
+ FlushQueueForTest();
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ Time initial = Time::Now();
+ AddDelay();
+
+ const int kSize1 = 2000;
+ const int kSize2 = 2000;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ memset(buffer2->data(), 0, kSize2);
+
+ EXPECT_EQ(2000, WriteData(entry, 0, 0, buffer1.get(), 2000, false));
+ EXPECT_EQ(2000, ReadData(entry, 0, 0, buffer2.get(), 2000));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer2->data(), kSize1));
+ EXPECT_EQ(key, entry->GetKey());
+ EXPECT_TRUE(initial < entry->GetLastModified());
+ EXPECT_TRUE(initial < entry->GetLastUsed());
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, DoomedEntry) {
+ InitCache();
+ DoomedEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyDoomedEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ DoomedEntry();
+}
+
+// Tests that we discard entries if the data is missing.
+TEST_F(DiskCacheEntryTest, MissingData) {
+ InitCache();
+
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ // Write to an external file.
+ const int kSize = 20000;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer->data(), kSize, false);
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer.get(), kSize, false));
+ entry->Close();
+ FlushQueueForTest();
+
+ disk_cache::Addr address(0x80000001);
+ base::FilePath name = cache_impl_->GetFileName(address);
+ EXPECT_TRUE(base::DeleteFile(name, false));
+
+ // Attempt to read the data.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND,
+ ReadData(entry, 0, 0, buffer.get(), kSize));
+ entry->Close();
+
+ // The entry should be gone.
+ ASSERT_NE(net::OK, OpenEntry(key, &entry));
+}
+
+// Test that child entries in a memory cache backend are not visible from
+// enumerations.
+TEST_F(DiskCacheEntryTest, MemoryOnlyEnumerationWithSparseEntries) {
+ SetMemoryOnlyMode();
+ InitCache();
+
+ const int kSize = 4096;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ std::string key("the first key");
+ disk_cache::Entry* parent_entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &parent_entry));
+
+ // Writes to the parent entry.
+ EXPECT_EQ(kSize,
+ parent_entry->WriteSparseData(
+ 0, buf.get(), kSize, net::CompletionCallback()));
+
+ // This write creates a child entry and writes to it.
+ EXPECT_EQ(kSize,
+ parent_entry->WriteSparseData(
+ 8192, buf.get(), kSize, net::CompletionCallback()));
+
+ parent_entry->Close();
+
+ // Perform the enumerations.
+ void* iter = NULL;
+ disk_cache::Entry* entry = NULL;
+ int count = 0;
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(entry != NULL);
+ ++count;
+ disk_cache::MemEntryImpl* mem_entry =
+ reinterpret_cast<disk_cache::MemEntryImpl*>(entry);
+ EXPECT_EQ(disk_cache::MemEntryImpl::kParentEntry, mem_entry->type());
+ mem_entry->Close();
+ }
+ EXPECT_EQ(1, count);
+}
+
+// Writes |buf_1| to offset and reads it back as |buf_2|.
+void VerifySparseIO(disk_cache::Entry* entry, int64 offset,
+ net::IOBuffer* buf_1, int size, net::IOBuffer* buf_2) {
+ net::TestCompletionCallback cb;
+
+ memset(buf_2->data(), 0, size);
+ int ret = entry->ReadSparseData(offset, buf_2, size, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(ret));
+
+ ret = entry->WriteSparseData(offset, buf_1, size, cb.callback());
+ EXPECT_EQ(size, cb.GetResult(ret));
+
+ ret = entry->ReadSparseData(offset, buf_2, size, cb.callback());
+ EXPECT_EQ(size, cb.GetResult(ret));
+
+ EXPECT_EQ(0, memcmp(buf_1->data(), buf_2->data(), size));
+}
+
+// Reads |size| bytes from |entry| at |offset| and verifies that they are the
+// same as the content of the provided |buffer|.
+void VerifyContentSparseIO(disk_cache::Entry* entry, int64 offset, char* buffer,
+ int size) {
+ net::TestCompletionCallback cb;
+
+ scoped_refptr<net::IOBuffer> buf_1(new net::IOBuffer(size));
+ memset(buf_1->data(), 0, size);
+ int ret = entry->ReadSparseData(offset, buf_1.get(), size, cb.callback());
+ EXPECT_EQ(size, cb.GetResult(ret));
+ EXPECT_EQ(0, memcmp(buf_1->data(), buffer, size));
+}
+
+void DiskCacheEntryTest::BasicSparseIO() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 2048;
+ scoped_refptr<net::IOBuffer> buf_1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buf_2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf_1->data(), kSize, false);
+
+ // Write at offset 0.
+ VerifySparseIO(entry, 0, buf_1.get(), kSize, buf_2.get());
+
+ // Write at offset 0x400000 (4 MB).
+ VerifySparseIO(entry, 0x400000, buf_1.get(), kSize, buf_2.get());
+
+ // Write at offset 0x800000000 (32 GB).
+ VerifySparseIO(entry, 0x800000000LL, buf_1.get(), kSize, buf_2.get());
+
+ entry->Close();
+
+ // Check everything again.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ VerifyContentSparseIO(entry, 0, buf_1->data(), kSize);
+ VerifyContentSparseIO(entry, 0x400000, buf_1->data(), kSize);
+ VerifyContentSparseIO(entry, 0x800000000LL, buf_1->data(), kSize);
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, BasicSparseIO) {
+ InitCache();
+ BasicSparseIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyBasicSparseIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ BasicSparseIO();
+}
+
+void DiskCacheEntryTest::HugeSparseIO() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ // Write 1.2 MB so that we cover multiple entries.
+ const int kSize = 1200 * 1024;
+ scoped_refptr<net::IOBuffer> buf_1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buf_2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf_1->data(), kSize, false);
+
+ // Write at offset 0x20F0000 (33 MB - 64 KB).
+ VerifySparseIO(entry, 0x20F0000, buf_1.get(), kSize, buf_2.get());
+ entry->Close();
+
+ // Check it again.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ VerifyContentSparseIO(entry, 0x20F0000, buf_1->data(), kSize);
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, HugeSparseIO) {
+ InitCache();
+ HugeSparseIO();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyHugeSparseIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+ HugeSparseIO();
+}
+
+void DiskCacheEntryTest::GetAvailableRange() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 16 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ // Write at offset 0x20F0000 (33 MB - 64 KB), and 0x20F4400 (33 MB - 47 KB).
+ EXPECT_EQ(kSize, WriteSparseData(entry, 0x20F0000, buf.get(), kSize));
+ EXPECT_EQ(kSize, WriteSparseData(entry, 0x20F4400, buf.get(), kSize));
+
+ // We stop at the first empty block.
+ int64 start;
+ net::TestCompletionCallback cb;
+ int rv = entry->GetAvailableRange(
+ 0x20F0000, kSize * 2, &start, cb.callback());
+ EXPECT_EQ(kSize, cb.GetResult(rv));
+ EXPECT_EQ(0x20F0000, start);
+
+ start = 0;
+ rv = entry->GetAvailableRange(0, kSize, &start, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->GetAvailableRange(
+ 0x20F0000 - kSize, kSize, &start, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->GetAvailableRange(0, 0x2100000, &start, cb.callback());
+ EXPECT_EQ(kSize, cb.GetResult(rv));
+ EXPECT_EQ(0x20F0000, start);
+
+ // We should be able to Read based on the results of GetAvailableRange.
+ start = -1;
+ rv = entry->GetAvailableRange(0x2100000, kSize, &start, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(rv));
+ rv = entry->ReadSparseData(start, buf.get(), kSize, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(rv));
+
+ start = 0;
+ rv = entry->GetAvailableRange(0x20F2000, kSize, &start, cb.callback());
+ EXPECT_EQ(0x2000, cb.GetResult(rv));
+ EXPECT_EQ(0x20F2000, start);
+ EXPECT_EQ(0x2000, ReadSparseData(entry, start, buf.get(), kSize));
+
+ // Make sure that we respect the |len| argument.
+ start = 0;
+ rv = entry->GetAvailableRange(
+ 0x20F0001 - kSize, kSize, &start, cb.callback());
+ EXPECT_EQ(1, cb.GetResult(rv));
+ EXPECT_EQ(0x20F0000, start);
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, GetAvailableRange) {
+ InitCache();
+ GetAvailableRange();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyGetAvailableRange) {
+ SetMemoryOnlyMode();
+ InitCache();
+ GetAvailableRange();
+}
+
+void DiskCacheEntryTest::CouldBeSparse() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 16 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ // Write at offset 0x20F0000 (33 MB - 64 KB).
+ EXPECT_EQ(kSize, WriteSparseData(entry, 0x20F0000, buf.get(), kSize));
+
+ EXPECT_TRUE(entry->CouldBeSparse());
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_TRUE(entry->CouldBeSparse());
+ entry->Close();
+
+ // Now verify a regular entry.
+ key.assign("another key");
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_FALSE(entry->CouldBeSparse());
+
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buf.get(), kSize, false));
+ EXPECT_EQ(kSize, WriteData(entry, 1, 0, buf.get(), kSize, false));
+ EXPECT_EQ(kSize, WriteData(entry, 2, 0, buf.get(), kSize, false));
+
+ EXPECT_FALSE(entry->CouldBeSparse());
+ entry->Close();
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_FALSE(entry->CouldBeSparse());
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, CouldBeSparse) {
+ InitCache();
+ CouldBeSparse();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryCouldBeSparse) {
+ SetMemoryOnlyMode();
+ InitCache();
+ CouldBeSparse();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyMisalignedSparseIO) {
+ SetMemoryOnlyMode();
+ InitCache();
+
+ const int kSize = 8192;
+ scoped_refptr<net::IOBuffer> buf_1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buf_2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf_1->data(), kSize, false);
+
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ // This loop writes back to back starting from offset 0 and 9000.
+ for (int i = 0; i < kSize; i += 1024) {
+ scoped_refptr<net::WrappedIOBuffer> buf_3(
+ new net::WrappedIOBuffer(buf_1->data() + i));
+ VerifySparseIO(entry, i, buf_3.get(), 1024, buf_2.get());
+ VerifySparseIO(entry, 9000 + i, buf_3.get(), 1024, buf_2.get());
+ }
+
+ // Make sure we have data written.
+ VerifyContentSparseIO(entry, 0, buf_1->data(), kSize);
+ VerifyContentSparseIO(entry, 9000, buf_1->data(), kSize);
+
+ // This tests a large write that spans 3 entries from a misaligned offset.
+ VerifySparseIO(entry, 20481, buf_1.get(), 8192, buf_2.get());
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyMisalignedGetAvailableRange) {
+ SetMemoryOnlyMode();
+ InitCache();
+
+ const int kSize = 8192;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ disk_cache::Entry* entry;
+ std::string key("the first key");
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ // Writes in the middle of an entry.
+ EXPECT_EQ(
+ 1024,
+ entry->WriteSparseData(0, buf.get(), 1024, net::CompletionCallback()));
+ EXPECT_EQ(
+ 1024,
+ entry->WriteSparseData(5120, buf.get(), 1024, net::CompletionCallback()));
+ EXPECT_EQ(1024,
+ entry->WriteSparseData(
+ 10000, buf.get(), 1024, net::CompletionCallback()));
+
+ // Writes in the middle of an entry and spans 2 child entries.
+ EXPECT_EQ(8192,
+ entry->WriteSparseData(
+ 50000, buf.get(), 8192, net::CompletionCallback()));
+
+ int64 start;
+ net::TestCompletionCallback cb;
+ // Test that we stop at a discontinuous child at the second block.
+ int rv = entry->GetAvailableRange(0, 10000, &start, cb.callback());
+ EXPECT_EQ(1024, cb.GetResult(rv));
+ EXPECT_EQ(0, start);
+
+ // Test that number of bytes is reported correctly when we start from the
+ // middle of a filled region.
+ rv = entry->GetAvailableRange(512, 10000, &start, cb.callback());
+ EXPECT_EQ(512, cb.GetResult(rv));
+ EXPECT_EQ(512, start);
+
+ // Test that we found bytes in the child of next block.
+ rv = entry->GetAvailableRange(1024, 10000, &start, cb.callback());
+ EXPECT_EQ(1024, cb.GetResult(rv));
+ EXPECT_EQ(5120, start);
+
+ // Test that the desired length is respected. It starts within a filled
+ // region.
+ rv = entry->GetAvailableRange(5500, 512, &start, cb.callback());
+ EXPECT_EQ(512, cb.GetResult(rv));
+ EXPECT_EQ(5500, start);
+
+ // Test that the desired length is respected. It starts before a filled
+ // region.
+ rv = entry->GetAvailableRange(5000, 620, &start, cb.callback());
+ EXPECT_EQ(500, cb.GetResult(rv));
+ EXPECT_EQ(5120, start);
+
+ // Test that multiple blocks are scanned.
+ rv = entry->GetAvailableRange(40000, 20000, &start, cb.callback());
+ EXPECT_EQ(8192, cb.GetResult(rv));
+ EXPECT_EQ(50000, start);
+
+ entry->Close();
+}
+
+void DiskCacheEntryTest::UpdateSparseEntry() {
+ std::string key("the first key");
+ disk_cache::Entry* entry1;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry1));
+
+ const int kSize = 2048;
+ scoped_refptr<net::IOBuffer> buf_1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buf_2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf_1->data(), kSize, false);
+
+ // Write at offset 0.
+ VerifySparseIO(entry1, 0, buf_1.get(), kSize, buf_2.get());
+ entry1->Close();
+
+ // Write at offset 2048.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry1));
+ VerifySparseIO(entry1, 2048, buf_1.get(), kSize, buf_2.get());
+
+ disk_cache::Entry* entry2;
+ ASSERT_EQ(net::OK, CreateEntry("the second key", &entry2));
+
+ entry1->Close();
+ entry2->Close();
+ FlushQueueForTest();
+ if (memory_only_)
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ else
+ EXPECT_EQ(3, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, UpdateSparseEntry) {
+ SetCacheType(net::MEDIA_CACHE);
+ InitCache();
+ UpdateSparseEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyUpdateSparseEntry) {
+ SetMemoryOnlyMode();
+ SetCacheType(net::MEDIA_CACHE);
+ InitCache();
+ UpdateSparseEntry();
+}
+
+void DiskCacheEntryTest::DoomSparseEntry() {
+ std::string key1("the first key");
+ std::string key2("the second key");
+ disk_cache::Entry *entry1, *entry2;
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry1));
+ ASSERT_EQ(net::OK, CreateEntry(key2, &entry2));
+
+ const int kSize = 4 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ int64 offset = 1024;
+ // Write to a bunch of ranges.
+ for (int i = 0; i < 12; i++) {
+ EXPECT_EQ(kSize,
+ entry1->WriteSparseData(
+ offset, buf.get(), kSize, net::CompletionCallback()));
+ // Keep the second map under the default size.
+ if (i < 9) {
+ EXPECT_EQ(kSize,
+ entry2->WriteSparseData(
+ offset, buf.get(), kSize, net::CompletionCallback()));
+ }
+
+ offset *= 4;
+ }
+
+ if (memory_only_)
+ EXPECT_EQ(2, cache_->GetEntryCount());
+ else
+ EXPECT_EQ(15, cache_->GetEntryCount());
+
+ // Doom the first entry while it's still open.
+ entry1->Doom();
+ entry1->Close();
+ entry2->Close();
+
+ // Doom the second entry after it's fully saved.
+ EXPECT_EQ(net::OK, DoomEntry(key2));
+
+ // Make sure we do all needed work. This may fail for entry2 if between Close
+ // and DoomEntry the system decides to remove all traces of the file from the
+ // system cache so we don't see that there is pending IO.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ if (memory_only_) {
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ } else {
+ if (5 == cache_->GetEntryCount()) {
+ // Most likely we are waiting for the result of reading the sparse info
+ // (it's always async on Posix so it is easy to miss). Unfortunately we
+ // don't have any signal to watch for so we can only wait.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500));
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ EXPECT_EQ(0, cache_->GetEntryCount());
+ }
+}
+
+TEST_F(DiskCacheEntryTest, DoomSparseEntry) {
+ UseCurrentThread();
+ InitCache();
+ DoomSparseEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryOnlyDoomSparseEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ DoomSparseEntry();
+}
+
+// A CompletionCallback wrapper that deletes the cache from within the callback.
+// The way a CompletionCallback works means that all tasks (even new ones)
+// are executed by the message loop before returning to the caller so the only
+// way to simulate a race is to execute what we want on the callback.
+class SparseTestCompletionCallback: public net::TestCompletionCallback {
+ public:
+ explicit SparseTestCompletionCallback(scoped_ptr<disk_cache::Backend> cache)
+ : cache_(cache.Pass()) {
+ }
+
+ private:
+ virtual void SetResult(int result) OVERRIDE {
+ cache_.reset();
+ TestCompletionCallback::SetResult(result);
+ }
+
+ scoped_ptr<disk_cache::Backend> cache_;
+ DISALLOW_COPY_AND_ASSIGN(SparseTestCompletionCallback);
+};
+
+// Tests that we don't crash when the backend is deleted while we are working
+// deleting the sub-entries of a sparse entry.
+TEST_F(DiskCacheEntryTest, DoomSparseEntry2) {
+ UseCurrentThread();
+ InitCache();
+ std::string key("the key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 4 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ int64 offset = 1024;
+ // Write to a bunch of ranges.
+ for (int i = 0; i < 12; i++) {
+ EXPECT_EQ(kSize,
+ entry->WriteSparseData(
+ offset, buf.get(), kSize, net::CompletionCallback()));
+ offset *= 4;
+ }
+ EXPECT_EQ(9, cache_->GetEntryCount());
+
+ entry->Close();
+ disk_cache::Backend* cache = cache_.get();
+ SparseTestCompletionCallback cb(cache_.Pass());
+ int rv = cache->DoomEntry(key, cb.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ EXPECT_EQ(net::OK, cb.WaitForResult());
+}
+
+void DiskCacheEntryTest::PartialSparseEntry() {
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ // We should be able to deal with IO that is not aligned to the block size
+ // of a sparse entry, at least to write a big range without leaving holes.
+ const int kSize = 4 * 1024;
+ const int kSmallSize = 128;
+ scoped_refptr<net::IOBuffer> buf1(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf1->data(), kSize, false);
+
+ // The first write is just to extend the entry. The third write occupies
+ // a 1KB block partially, it may not be written internally depending on the
+ // implementation.
+ EXPECT_EQ(kSize, WriteSparseData(entry, 20000, buf1.get(), kSize));
+ EXPECT_EQ(kSize, WriteSparseData(entry, 500, buf1.get(), kSize));
+ EXPECT_EQ(kSmallSize,
+ WriteSparseData(entry, 1080321, buf1.get(), kSmallSize));
+ entry->Close();
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ scoped_refptr<net::IOBuffer> buf2(new net::IOBuffer(kSize));
+ memset(buf2->data(), 0, kSize);
+ EXPECT_EQ(0, ReadSparseData(entry, 8000, buf2.get(), kSize));
+
+ EXPECT_EQ(500, ReadSparseData(entry, kSize, buf2.get(), kSize));
+ EXPECT_EQ(0, memcmp(buf2->data(), buf1->data() + kSize - 500, 500));
+ EXPECT_EQ(0, ReadSparseData(entry, 0, buf2.get(), kSize));
+
+ // This read should not change anything.
+ EXPECT_EQ(96, ReadSparseData(entry, 24000, buf2.get(), kSize));
+ EXPECT_EQ(500, ReadSparseData(entry, kSize, buf2.get(), kSize));
+ EXPECT_EQ(0, ReadSparseData(entry, 99, buf2.get(), kSize));
+
+ int rv;
+ int64 start;
+ net::TestCompletionCallback cb;
+ if (memory_only_) {
+ rv = entry->GetAvailableRange(0, 600, &start, cb.callback());
+ EXPECT_EQ(100, cb.GetResult(rv));
+ EXPECT_EQ(500, start);
+ } else {
+ rv = entry->GetAvailableRange(0, 2048, &start, cb.callback());
+ EXPECT_EQ(1024, cb.GetResult(rv));
+ EXPECT_EQ(1024, start);
+ }
+ rv = entry->GetAvailableRange(kSize, kSize, &start, cb.callback());
+ EXPECT_EQ(500, cb.GetResult(rv));
+ EXPECT_EQ(kSize, start);
+ rv = entry->GetAvailableRange(20 * 1024, 10000, &start, cb.callback());
+ EXPECT_EQ(3616, cb.GetResult(rv));
+ EXPECT_EQ(20 * 1024, start);
+
+ // 1. Query before a filled 1KB block.
+ // 2. Query within a filled 1KB block.
+ // 3. Query beyond a filled 1KB block.
+ if (memory_only_) {
+ rv = entry->GetAvailableRange(19400, kSize, &start, cb.callback());
+ EXPECT_EQ(3496, cb.GetResult(rv));
+ EXPECT_EQ(20000, start);
+ } else {
+ rv = entry->GetAvailableRange(19400, kSize, &start, cb.callback());
+ EXPECT_EQ(3016, cb.GetResult(rv));
+ EXPECT_EQ(20480, start);
+ }
+ rv = entry->GetAvailableRange(3073, kSize, &start, cb.callback());
+ EXPECT_EQ(1523, cb.GetResult(rv));
+ EXPECT_EQ(3073, start);
+ rv = entry->GetAvailableRange(4600, kSize, &start, cb.callback());
+ EXPECT_EQ(0, cb.GetResult(rv));
+ EXPECT_EQ(4600, start);
+
+ // Now make another write and verify that there is no hole in between.
+ EXPECT_EQ(kSize, WriteSparseData(entry, 500 + kSize, buf1.get(), kSize));
+ rv = entry->GetAvailableRange(1024, 10000, &start, cb.callback());
+ EXPECT_EQ(7 * 1024 + 500, cb.GetResult(rv));
+ EXPECT_EQ(1024, start);
+ EXPECT_EQ(kSize, ReadSparseData(entry, kSize, buf2.get(), kSize));
+ EXPECT_EQ(0, memcmp(buf2->data(), buf1->data() + kSize - 500, 500));
+ EXPECT_EQ(0, memcmp(buf2->data() + 500, buf1->data(), kSize - 500));
+
+ entry->Close();
+}
+
+TEST_F(DiskCacheEntryTest, PartialSparseEntry) {
+ InitCache();
+ PartialSparseEntry();
+}
+
+TEST_F(DiskCacheEntryTest, MemoryPartialSparseEntry) {
+ SetMemoryOnlyMode();
+ InitCache();
+ PartialSparseEntry();
+}
+
+// Tests that corrupt sparse children are removed automatically.
+TEST_F(DiskCacheEntryTest, CleanupSparseEntry) {
+ InitCache();
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 4 * 1024;
+ scoped_refptr<net::IOBuffer> buf1(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf1->data(), kSize, false);
+
+ const int k1Meg = 1024 * 1024;
+ EXPECT_EQ(kSize, WriteSparseData(entry, 8192, buf1.get(), kSize));
+ EXPECT_EQ(kSize, WriteSparseData(entry, k1Meg + 8192, buf1.get(), kSize));
+ EXPECT_EQ(kSize, WriteSparseData(entry, 2 * k1Meg + 8192, buf1.get(), kSize));
+ entry->Close();
+ EXPECT_EQ(4, cache_->GetEntryCount());
+
+ void* iter = NULL;
+ int count = 0;
+ std::string child_key[2];
+ while (OpenNextEntry(&iter, &entry) == net::OK) {
+ ASSERT_TRUE(entry != NULL);
+ // Writing to an entry will alter the LRU list and invalidate the iterator.
+ if (entry->GetKey() != key && count < 2)
+ child_key[count++] = entry->GetKey();
+ entry->Close();
+ }
+ for (int i = 0; i < 2; i++) {
+ ASSERT_EQ(net::OK, OpenEntry(child_key[i], &entry));
+ // Overwrite the header's magic and signature.
+ EXPECT_EQ(12, WriteData(entry, 2, 0, buf1.get(), 12, false));
+ entry->Close();
+ }
+
+ EXPECT_EQ(4, cache_->GetEntryCount());
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+
+ // Two children should be gone. One while reading and one while writing.
+ EXPECT_EQ(0, ReadSparseData(entry, 2 * k1Meg + 8192, buf1.get(), kSize));
+ EXPECT_EQ(kSize, WriteSparseData(entry, k1Meg + 16384, buf1.get(), kSize));
+ EXPECT_EQ(0, ReadSparseData(entry, k1Meg + 8192, buf1.get(), kSize));
+
+ // We never touched this one.
+ EXPECT_EQ(kSize, ReadSparseData(entry, 8192, buf1.get(), kSize));
+ entry->Close();
+
+ // We re-created one of the corrupt children.
+ EXPECT_EQ(3, cache_->GetEntryCount());
+}
+
+TEST_F(DiskCacheEntryTest, CancelSparseIO) {
+ UseCurrentThread();
+ InitCache();
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ const int kSize = 40 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buf->data(), kSize, false);
+
+ // This will open and write two "real" entries.
+ net::TestCompletionCallback cb1, cb2, cb3, cb4, cb5;
+ int rv = entry->WriteSparseData(
+ 1024 * 1024 - 4096, buf.get(), kSize, cb1.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ int64 offset = 0;
+ rv = entry->GetAvailableRange(offset, kSize, &offset, cb5.callback());
+ rv = cb5.GetResult(rv);
+ if (!cb1.have_result()) {
+ // We may or may not have finished writing to the entry. If we have not,
+ // we cannot start another operation at this time.
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED, rv);
+ }
+
+ // We cancel the pending operation, and register multiple notifications.
+ entry->CancelSparseIO();
+ EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadyForSparseIO(cb2.callback()));
+ EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadyForSparseIO(cb3.callback()));
+ entry->CancelSparseIO(); // Should be a no op at this point.
+ EXPECT_EQ(net::ERR_IO_PENDING, entry->ReadyForSparseIO(cb4.callback()));
+
+ if (!cb1.have_result()) {
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
+ entry->ReadSparseData(
+ offset, buf.get(), kSize, net::CompletionCallback()));
+ EXPECT_EQ(net::ERR_CACHE_OPERATION_NOT_SUPPORTED,
+ entry->WriteSparseData(
+ offset, buf.get(), kSize, net::CompletionCallback()));
+ }
+
+ // Now see if we receive all notifications. Note that we should not be able
+ // to write everything (unless the timing of the system is really weird).
+ rv = cb1.WaitForResult();
+ EXPECT_TRUE(rv == 4096 || rv == kSize);
+ EXPECT_EQ(net::OK, cb2.WaitForResult());
+ EXPECT_EQ(net::OK, cb3.WaitForResult());
+ EXPECT_EQ(net::OK, cb4.WaitForResult());
+
+ rv = entry->GetAvailableRange(offset, kSize, &offset, cb5.callback());
+ EXPECT_EQ(0, cb5.GetResult(rv));
+ entry->Close();
+}
+
+// Tests that we perform sanity checks on an entry's key. Note that there are
+// other tests that exercise sanity checks by using saved corrupt files.
+TEST_F(DiskCacheEntryTest, KeySanityCheck) {
+ UseCurrentThread();
+ InitCache();
+ std::string key("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ disk_cache::EntryImpl* entry_impl =
+ static_cast<disk_cache::EntryImpl*>(entry);
+ disk_cache::EntryStore* store = entry_impl->entry()->Data();
+
+ // We have reserved space for a short key (one block), let's say that the key
+ // takes more than one block, and remove the NULLs after the actual key.
+ store->key_len = 800;
+ memset(store->key + key.size(), 'k', sizeof(store->key) - key.size());
+ entry_impl->entry()->set_modified();
+ entry->Close();
+
+ // We have a corrupt entry. Now reload it. We should NOT read beyond the
+ // allocated buffer here.
+ ASSERT_NE(net::OK, OpenEntry(key, &entry));
+ DisableIntegrityCheck();
+}
+
+// The simple cache backend isn't intended to work on Windows, which has very
+// different file system guarantees from Linux.
+#if defined(OS_POSIX)
+
+TEST_F(DiskCacheEntryTest, SimpleCacheInternalAsyncIO) {
+ SetSimpleCacheMode();
+ InitCache();
+ InternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheExternalAsyncIO) {
+ SetSimpleCacheMode();
+ InitCache();
+ ExternalAsyncIO();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheReleaseBuffer) {
+ SetSimpleCacheMode();
+ InitCache();
+ ReleaseBuffer();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheStreamAccess) {
+ SetSimpleCacheMode();
+ InitCache();
+ StreamAccess();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheGetKey) {
+ SetSimpleCacheMode();
+ InitCache();
+ GetKey();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheGetTimes) {
+ SetSimpleCacheMode();
+ InitCache();
+ GetTimes();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheGrowData) {
+ SetSimpleCacheMode();
+ InitCache();
+ GrowData();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheTruncateData) {
+ SetSimpleCacheMode();
+ InitCache();
+ TruncateData();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheZeroLengthIO) {
+ SetSimpleCacheMode();
+ InitCache();
+ ZeroLengthIO();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheSizeAtCreate) {
+ SetSimpleCacheMode();
+ InitCache();
+ SizeAtCreate();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheReuseExternalEntry) {
+ SetSimpleCacheMode();
+ SetMaxSize(200 * 1024);
+ InitCache();
+ ReuseEntry(20 * 1024);
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheReuseInternalEntry) {
+ SetSimpleCacheMode();
+ SetMaxSize(100 * 1024);
+ InitCache();
+ ReuseEntry(10 * 1024);
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheSizeChanges) {
+ SetSimpleCacheMode();
+ InitCache();
+ SizeChanges();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheInvalidData) {
+ SetSimpleCacheMode();
+ InitCache();
+ InvalidData();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheReadWriteDestroyBuffer) {
+ SetSimpleCacheMode();
+ InitCache();
+ ReadWriteDestroyBuffer();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheDoomEntry) {
+ SetSimpleCacheMode();
+ InitCache();
+ DoomNormalEntry();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheDoomEntryNextToOpenEntry) {
+ SetSimpleCacheMode();
+ InitCache();
+ DoomEntryNextToOpenEntry();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheDoomedEntry) {
+ SetSimpleCacheMode();
+ InitCache();
+ DoomedEntry();
+}
+
+// Creates an entry with corrupted last byte in stream 0.
+// Requires SimpleCacheMode.
+bool DiskCacheEntryTest::SimpleCacheMakeBadChecksumEntry(const char* key,
+ int* data_size) {
+ disk_cache::Entry* entry = NULL;
+
+ if (CreateEntry(key, &entry) != net::OK || !entry) {
+ LOG(ERROR) << "Could not create entry";
+ return false;
+ }
+
+ const char data[] = "this is very good data";
+ const int kDataSize = arraysize(data);
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kDataSize));
+ base::strlcpy(buffer->data(), data, kDataSize);
+
+ EXPECT_EQ(kDataSize, WriteData(entry, 0, 0, buffer.get(), kDataSize, false));
+ entry->Close();
+ entry = NULL;
+
+ // Corrupt the last byte of the data.
+ base::FilePath entry_file0_path = cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0));
+ int flags = base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_OPEN;
+ base::PlatformFile entry_file0 =
+ base::CreatePlatformFile(entry_file0_path, flags, NULL, NULL);
+ if (entry_file0 == base::kInvalidPlatformFileValue)
+ return false;
+ int64 file_offset =
+ disk_cache::simple_util::GetFileOffsetFromKeyAndDataOffset(
+ key, kDataSize - 2);
+ EXPECT_EQ(1, base::WritePlatformFile(entry_file0, file_offset, "X", 1));
+ if (!base::ClosePlatformFile(entry_file0))
+ return false;
+ *data_size = kDataSize;
+ return true;
+}
+
+// Tests that the simple cache can detect entries that have bad data.
+TEST_F(DiskCacheEntryTest, SimpleCacheBadChecksum) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+ int size_unused;
+ ASSERT_TRUE(SimpleCacheMakeBadChecksumEntry(key, &size_unused));
+
+ disk_cache::Entry* entry = NULL;
+
+ // Open the entry.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kReadBufferSize = 200;
+ EXPECT_GE(kReadBufferSize, entry->GetDataSize(0));
+ scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize));
+ EXPECT_EQ(net::ERR_CACHE_CHECKSUM_MISMATCH,
+ ReadData(entry, 0, 0, read_buffer.get(), kReadBufferSize));
+}
+
+// Tests that an entry that has had an IO error occur can still be Doomed().
+TEST_F(DiskCacheEntryTest, SimpleCacheErrorThenDoom) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+ int size_unused;
+ ASSERT_TRUE(SimpleCacheMakeBadChecksumEntry(key, &size_unused));
+
+ disk_cache::Entry* entry = NULL;
+
+ // Open the entry, forcing an IO error.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kReadBufferSize = 200;
+ EXPECT_GE(kReadBufferSize, entry->GetDataSize(0));
+ scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize));
+ EXPECT_EQ(net::ERR_CACHE_CHECKSUM_MISMATCH,
+ ReadData(entry, 0, 0, read_buffer.get(), kReadBufferSize));
+
+ entry->Doom(); // Should not crash.
+}
+
+bool TruncatePath(const base::FilePath& file_path, int64 length) {
+ const int flags = base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_OPEN;
+ base::PlatformFile file =
+ base::CreatePlatformFile(file_path, flags, NULL, NULL);
+ if (base::kInvalidPlatformFileValue == file)
+ return false;
+ const bool result = base::TruncatePlatformFile(file, length);
+ base::ClosePlatformFile(file);
+ return result;
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheNoEOF) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ disk_cache::Entry* null = NULL;
+ EXPECT_NE(null, entry);
+ entry->Close();
+ entry = NULL;
+
+ // Force the entry to flush to disk, so subsequent platform file operations
+ // succed.
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ entry->Close();
+ entry = NULL;
+
+ // Truncate the file such that the length isn't sufficient to have an EOF
+ // record.
+ int kTruncationBytes = -implicit_cast<int>(sizeof(disk_cache::SimpleFileEOF));
+ const base::FilePath entry_path = cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0));
+ const int64 invalid_size =
+ disk_cache::simple_util::GetFileSizeFromKeyAndDataSize(key,
+ kTruncationBytes);
+ EXPECT_TRUE(TruncatePath(entry_path, invalid_size));
+ EXPECT_EQ(net::ERR_FAILED, OpenEntry(key, &entry));
+ DisableIntegrityCheck();
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheNonOptimisticOperationsBasic) {
+ // Test sequence:
+ // Create, Write, Read, Close.
+ SetCacheType(net::APP_CACHE); // APP_CACHE doesn't use optimistic operations.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* const null_entry = NULL;
+
+ disk_cache::Entry* entry = NULL;
+ EXPECT_EQ(net::OK, CreateEntry("my key", &entry));
+ ASSERT_NE(null_entry, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kBufferSize = 10;
+ scoped_refptr<net::IOBufferWithSize> write_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+ CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false);
+ EXPECT_EQ(
+ write_buffer->size(),
+ WriteData(entry, 0, 0, write_buffer.get(), write_buffer->size(), false));
+
+ scoped_refptr<net::IOBufferWithSize> read_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+ EXPECT_EQ(
+ read_buffer->size(),
+ ReadData(entry, 0, 0, read_buffer.get(), read_buffer->size()));
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheNonOptimisticOperationsDontBlock) {
+ // Test sequence:
+ // Create, Write, Close.
+ SetCacheType(net::APP_CACHE); // APP_CACHE doesn't use optimistic operations.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* const null_entry = NULL;
+
+ MessageLoopHelper helper;
+ CallbackTest create_callback(&helper, false);
+
+ int expected_callback_runs = 0;
+ const int kBufferSize = 10;
+ scoped_refptr<net::IOBufferWithSize> write_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+
+ disk_cache::Entry* entry = NULL;
+ EXPECT_EQ(net::OK, CreateEntry("my key", &entry));
+ ASSERT_NE(null_entry, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false);
+ CallbackTest write_callback(&helper, false);
+ int ret = entry->WriteData(
+ 0,
+ 0,
+ write_buffer.get(),
+ write_buffer->size(),
+ base::Bind(&CallbackTest::Run, base::Unretained(&write_callback)),
+ false);
+ ASSERT_EQ(net::ERR_IO_PENDING, ret);
+ helper.WaitUntilCacheIoFinished(++expected_callback_runs);
+}
+
+TEST_F(DiskCacheEntryTest,
+ SimpleCacheNonOptimisticOperationsBasicsWithoutWaiting) {
+ // Test sequence:
+ // Create, Write, Read, Close.
+ SetCacheType(net::APP_CACHE); // APP_CACHE doesn't use optimistic operations.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* const null_entry = NULL;
+ MessageLoopHelper helper;
+
+ disk_cache::Entry* entry = NULL;
+ // Note that |entry| is only set once CreateEntry() completed which is why we
+ // have to wait (i.e. use the helper CreateEntry() function).
+ EXPECT_EQ(net::OK, CreateEntry("my key", &entry));
+ ASSERT_NE(null_entry, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kBufferSize = 10;
+ scoped_refptr<net::IOBufferWithSize> write_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+ CacheTestFillBuffer(write_buffer->data(), write_buffer->size(), false);
+ CallbackTest write_callback(&helper, false);
+ int ret = entry->WriteData(
+ 0,
+ 0,
+ write_buffer.get(),
+ write_buffer->size(),
+ base::Bind(&CallbackTest::Run, base::Unretained(&write_callback)),
+ false);
+ EXPECT_EQ(net::ERR_IO_PENDING, ret);
+ int expected_callback_runs = 1;
+
+ scoped_refptr<net::IOBufferWithSize> read_buffer(
+ new net::IOBufferWithSize(kBufferSize));
+ CallbackTest read_callback(&helper, false);
+ ret = entry->ReadData(
+ 0,
+ 0,
+ read_buffer.get(),
+ read_buffer->size(),
+ base::Bind(&CallbackTest::Run, base::Unretained(&read_callback)));
+ EXPECT_EQ(net::ERR_IO_PENDING, ret);
+ ++expected_callback_runs;
+
+ helper.WaitUntilCacheIoFinished(expected_callback_runs);
+ ASSERT_EQ(read_buffer->size(), write_buffer->size());
+ EXPECT_EQ(
+ 0,
+ memcmp(read_buffer->data(), write_buffer->data(), read_buffer->size()));
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic) {
+ // Test sequence:
+ // Create, Write, Read, Write, Read, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ MessageLoopHelper helper;
+ CallbackTest callback1(&helper, false);
+ CallbackTest callback2(&helper, false);
+ CallbackTest callback3(&helper, false);
+ CallbackTest callback4(&helper, false);
+ CallbackTest callback5(&helper, false);
+
+ int expected = 0;
+ const int kSize1 = 10;
+ const int kSize2 = 20;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer1_read(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize2));
+ scoped_refptr<net::IOBuffer> buffer2_read(new net::IOBuffer(kSize2));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ CacheTestFillBuffer(buffer2->data(), kSize2, false);
+
+ disk_cache::Entry* entry = NULL;
+ // Create is optimistic, must return OK.
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&callback1))));
+ EXPECT_NE(null, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ // This write may or may not be optimistic (it depends if the previous
+ // optimistic create already finished by the time we call the write here).
+ int ret = entry->WriteData(
+ 0,
+ 0,
+ buffer1.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback2)),
+ false);
+ EXPECT_TRUE(kSize1 == ret || net::ERR_IO_PENDING == ret);
+ if (net::ERR_IO_PENDING == ret)
+ expected++;
+
+ // This Read must not be optimistic, since we don't support that yet.
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(
+ 0,
+ 0,
+ buffer1_read.get(),
+ kSize1,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback3))));
+ expected++;
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read->data(), kSize1));
+
+ // At this point after waiting, the pending operations queue on the entry
+ // should be empty, so the next Write operation must run as optimistic.
+ EXPECT_EQ(kSize2,
+ entry->WriteData(
+ 0,
+ 0,
+ buffer2.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback4)),
+ false));
+
+ // Lets do another read so we block until both the write and the read
+ // operation finishes and we can then test for HasOneRef() below.
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(
+ 0,
+ 0,
+ buffer2_read.get(),
+ kSize2,
+ base::Bind(&CallbackTest::Run, base::Unretained(&callback5))));
+ expected++;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(0, memcmp(buffer2->data(), buffer2_read->data(), kSize2));
+
+ // Check that we are not leaking.
+ EXPECT_NE(entry, null);
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic2) {
+ // Test sequence:
+ // Create, Open, Close, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ MessageLoopHelper helper;
+ CallbackTest callback1(&helper, false);
+ CallbackTest callback2(&helper, false);
+
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&callback1))));
+ EXPECT_NE(null, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ disk_cache::Entry* entry2 = NULL;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ cache_->OpenEntry(key, &entry2,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&callback2))));
+ ASSERT_TRUE(helper.WaitUntilCacheIoFinished(1));
+
+ EXPECT_NE(null, entry2);
+ EXPECT_EQ(entry, entry2);
+
+ // We have to call close twice, since we called create and open above.
+ entry->Close();
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic3) {
+ // Test sequence:
+ // Create, Close, Open, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ EXPECT_NE(null, entry);
+ entry->Close();
+
+ net::TestCompletionCallback cb;
+ disk_cache::Entry* entry2 = NULL;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ cache_->OpenEntry(key, &entry2, cb.callback()));
+ ASSERT_EQ(net::OK, cb.GetResult(net::ERR_IO_PENDING));
+ ScopedEntryPtr entry_closer(entry2);
+
+ EXPECT_NE(null, entry2);
+ EXPECT_EQ(entry, entry2);
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry2)->HasOneRef());
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic4) {
+ // Test sequence:
+ // Create, Close, Write, Open, Open, Close, Write, Read, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ net::TestCompletionCallback cb;
+ const int kSize1 = 10;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ EXPECT_NE(null, entry);
+ entry->Close();
+
+ // Lets do a Write so we block until both the Close and the Write
+ // operation finishes. Write must fail since we are writing in a closed entry.
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false));
+ EXPECT_EQ(net::ERR_FAILED, cb.GetResult(net::ERR_IO_PENDING));
+
+ // Finish running the pending tasks so that we fully complete the close
+ // operation and destroy the entry object.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // At this point the |entry| must have been destroyed, and called
+ // RemoveSelfFromBackend().
+ disk_cache::Entry* entry2 = NULL;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ cache_->OpenEntry(key, &entry2, cb.callback()));
+ ASSERT_EQ(net::OK, cb.GetResult(net::ERR_IO_PENDING));
+ EXPECT_NE(null, entry2);
+
+ disk_cache::Entry* entry3 = NULL;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ cache_->OpenEntry(key, &entry3, cb.callback()));
+ ASSERT_EQ(net::OK, cb.GetResult(net::ERR_IO_PENDING));
+ EXPECT_NE(null, entry3);
+ EXPECT_EQ(entry2, entry3);
+ entry3->Close();
+
+ // The previous Close doesn't actually closes the entry since we opened it
+ // twice, so the next Write operation must succeed and it must be able to
+ // perform it optimistically, since there is no operation running on this
+ // entry.
+ EXPECT_EQ(kSize1,
+ entry2->WriteData(
+ 0, 0, buffer1.get(), kSize1, net::CompletionCallback(), false));
+
+ // Lets do another read so we block until both the write and the read
+ // operation finishes and we can then test for HasOneRef() below.
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry2->ReadData(0, 0, buffer1.get(), kSize1, cb.callback()));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry2)->HasOneRef());
+ entry2->Close();
+}
+
+// This test is flaky because of the race of Create followed by a Doom.
+// See test SimpleCacheCreateDoomRace.
+TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheOptimistic5) {
+ // Test sequence:
+ // Create, Doom, Write, Read, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ net::TestCompletionCallback cb;
+ const int kSize1 = 10;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ EXPECT_NE(null, entry);
+ ScopedEntryPtr entry_closer(entry);
+ entry->Doom();
+
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(0, 0, buffer1.get(), kSize1, cb.callback()));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimistic6) {
+ // Test sequence:
+ // Create, Write, Doom, Doom, Read, Doom, Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ net::TestCompletionCallback cb;
+ const int kSize1 = 10;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ scoped_refptr<net::IOBuffer> buffer1_read(new net::IOBuffer(kSize1));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ EXPECT_NE(null, entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+
+ entry->Doom();
+ entry->Doom();
+
+ // This Read must not be optimistic, since we don't support that yet.
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(0, 0, buffer1_read.get(), kSize1, cb.callback()));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read->data(), kSize1));
+
+ entry->Doom();
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+}
+
+// Confirm that IO buffers are not referenced by the Simple Cache after a write
+// completes.
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimisticWriteReleases) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+ disk_cache::Entry* entry = NULL;
+
+ // First, an optimistic create.
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ ASSERT_TRUE(entry);
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kWriteSize = 512;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kWriteSize));
+ EXPECT_TRUE(buffer1->HasOneRef());
+ CacheTestFillBuffer(buffer1->data(), kWriteSize, false);
+
+ // An optimistic write happens only when there is an empty queue of pending
+ // operations. To ensure the queue is empty, we issue a write and wait until
+ // it completes.
+ EXPECT_EQ(kWriteSize,
+ WriteData(entry, 0, 0, buffer1.get(), kWriteSize, false));
+ EXPECT_TRUE(buffer1->HasOneRef());
+
+ // Finally, we should perform an optimistic write and confirm that all
+ // references to the IO buffer have been released.
+ EXPECT_EQ(
+ kWriteSize,
+ entry->WriteData(
+ 1, 0, buffer1.get(), kWriteSize, net::CompletionCallback(), false));
+ EXPECT_TRUE(buffer1->HasOneRef());
+}
+
+TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheCreateDoomRace) {
+ // Test sequence:
+ // Create, Doom, Write, Close, Check files are not on disk anymore.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ net::TestCompletionCallback cb;
+ const int kSize1 = 10;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize1));
+ CacheTestFillBuffer(buffer1->data(), kSize1, false);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ EXPECT_NE(null, entry);
+
+ cache_->DoomEntry(key, cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(net::ERR_IO_PENDING));
+
+ // Lets do a Write so we block until all operations are done, so we can check
+ // the HasOneRef() below. This call can't be optimistic and we are checking
+ // that here.
+ EXPECT_EQ(
+ net::ERR_IO_PENDING,
+ entry->WriteData(0, 0, buffer1.get(), kSize1, cb.callback(), false));
+ EXPECT_EQ(kSize1, cb.GetResult(net::ERR_IO_PENDING));
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+ entry->Close();
+
+ // Finish running the pending tasks so that we fully complete the close
+ // operation and destroy the entry object.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ for (int i = 0; i < disk_cache::kSimpleEntryFileCount; ++i) {
+ base::FilePath entry_file_path = cache_path_.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, i));
+ base::PlatformFileInfo info;
+ EXPECT_FALSE(file_util::GetFileInfo(entry_file_path, &info));
+ }
+}
+
+// Checks that an optimistic Create would fail later on a racing Open.
+TEST_F(DiskCacheEntryTest, SimpleCacheOptimisticCreateFailsOnOpen) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ // Create a corrupt file in place of a future entry. Optimistic create should
+ // initially succeed, but realize later that creation failed.
+ const std::string key = "the key";
+ net::TestCompletionCallback cb;
+ disk_cache::Entry* entry = NULL;
+ disk_cache::Entry* entry2 = NULL;
+
+ EXPECT_TRUE(disk_cache::simple_util::CreateCorruptFileForTests(
+ key, cache_path_));
+ EXPECT_EQ(net::OK, cache_->CreateEntry(key, &entry, cb.callback()));
+ ASSERT_TRUE(entry);
+ ScopedEntryPtr entry_closer(entry);
+ ASSERT_NE(net::OK, OpenEntry(key, &entry2));
+
+ // Check that we are not leaking.
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+
+ DisableIntegrityCheck();
+}
+
+// Tests that old entries are evicted while new entries remain in the index.
+// This test relies on non-mandatory properties of the simple Cache Backend:
+// LRU eviction, specific values of high-watermark and low-watermark etc.
+// When changing the eviction algorithm, the test will have to be re-engineered.
+TEST_F(DiskCacheEntryTest, SimpleCacheEvictOldEntries) {
+ const int kMaxSize = 200 * 1024;
+ const int kWriteSize = kMaxSize / 10;
+ const int kNumExtraEntries = 12;
+ SetSimpleCacheMode();
+ SetMaxSize(kMaxSize);
+ InitCache();
+
+ std::string key1("the first key");
+ disk_cache::Entry* entry;
+ ASSERT_EQ(net::OK, CreateEntry(key1, &entry));
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kWriteSize));
+ CacheTestFillBuffer(buffer->data(), kWriteSize, false);
+ EXPECT_EQ(kWriteSize,
+ WriteData(entry, 0, 0, buffer.get(), kWriteSize, false));
+ entry->Close();
+
+ std::string key2("the key prefix");
+ for (int i = 0; i < kNumExtraEntries; i++) {
+ ASSERT_EQ(net::OK, CreateEntry(key2 + base::StringPrintf("%d", i), &entry));
+ ScopedEntryPtr entry_closer(entry);
+ EXPECT_EQ(kWriteSize,
+ WriteData(entry, 0, 0, buffer.get(), kWriteSize, false));
+ }
+
+ // TODO(pasko): Find a way to wait for the eviction task(s) to finish by using
+ // the internal knowledge about |SimpleBackendImpl|.
+ ASSERT_NE(net::OK, OpenEntry(key1, &entry))
+ << "Should have evicted the old entry";
+ for (int i = 0; i < 2; i++) {
+ int entry_no = kNumExtraEntries - i - 1;
+ // Generally there is no guarantee that at this point the backround eviction
+ // is finished. We are testing the positive case, i.e. when the eviction
+ // never reaches this entry, should be non-flaky.
+ ASSERT_EQ(net::OK, OpenEntry(key2 + base::StringPrintf("%d", entry_no),
+ &entry))
+ << "Should not have evicted fresh entry " << entry_no;
+ entry->Close();
+ }
+}
+
+// Tests that if a read and a following in-flight truncate are both in progress
+// simultaniously that they both can occur successfully. See
+// http://crbug.com/239223
+TEST_F(DiskCacheEntryTest, SimpleCacheInFlightTruncate) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+
+ const int kBufferSize = 1024;
+ scoped_refptr<net::IOBuffer> write_buffer(new net::IOBuffer(kBufferSize));
+ CacheTestFillBuffer(write_buffer->data(), kBufferSize, false);
+
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+
+ EXPECT_EQ(kBufferSize,
+ WriteData(entry, 0, 0, write_buffer.get(), kBufferSize, false));
+ entry->Close();
+ entry = NULL;
+
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ScopedEntryPtr entry_closer(entry);
+
+ MessageLoopHelper helper;
+ int expected = 0;
+
+ // Make a short read.
+ const int kReadBufferSize = 512;
+ scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kReadBufferSize));
+ CallbackTest read_callback(&helper, false);
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(0,
+ 0,
+ read_buffer.get(),
+ kReadBufferSize,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&read_callback))));
+ ++expected;
+
+ // Truncate the entry to the length of that read.
+ scoped_refptr<net::IOBuffer>
+ truncate_buffer(new net::IOBuffer(kReadBufferSize));
+ CacheTestFillBuffer(truncate_buffer->data(), kReadBufferSize, false);
+ CallbackTest truncate_callback(&helper, false);
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->WriteData(0,
+ 0,
+ truncate_buffer.get(),
+ kReadBufferSize,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&truncate_callback)),
+ true));
+ ++expected;
+
+ // Wait for both the read and truncation to finish, and confirm that both
+ // succeeded.
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(kReadBufferSize, read_callback.last_result());
+ EXPECT_EQ(kReadBufferSize, truncate_callback.last_result());
+ EXPECT_EQ(0,
+ memcmp(write_buffer->data(), read_buffer->data(), kReadBufferSize));
+}
+
+// Tests that if a write and a read dependant on it are both in flight
+// simultaneiously that they both can complete successfully without erroneous
+// early returns. See http://crbug.com/239223
+TEST_F(DiskCacheEntryTest, SimpleCacheInFlightRead) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "the first key";
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK,
+ cache_->CreateEntry(key, &entry, net::CompletionCallback()));
+ ScopedEntryPtr entry_closer(entry);
+
+ const int kBufferSize = 1024;
+ scoped_refptr<net::IOBuffer> write_buffer(new net::IOBuffer(kBufferSize));
+ CacheTestFillBuffer(write_buffer->data(), kBufferSize, false);
+
+ MessageLoopHelper helper;
+ int expected = 0;
+
+ CallbackTest write_callback(&helper, false);
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->WriteData(0,
+ 0,
+ write_buffer.get(),
+ kBufferSize,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&write_callback)),
+ true));
+ ++expected;
+
+ scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(kBufferSize));
+ CallbackTest read_callback(&helper, false);
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ entry->ReadData(0,
+ 0,
+ read_buffer.get(),
+ kBufferSize,
+ base::Bind(&CallbackTest::Run,
+ base::Unretained(&read_callback))));
+ ++expected;
+
+ EXPECT_TRUE(helper.WaitUntilCacheIoFinished(expected));
+ EXPECT_EQ(kBufferSize, write_callback.last_result());
+ EXPECT_EQ(kBufferSize, read_callback.last_result());
+ EXPECT_EQ(0, memcmp(write_buffer->data(), read_buffer->data(), kBufferSize));
+}
+
+TEST_F(DiskCacheEntryTest, SimpleCacheOpenCreateRaceWithNoIndex) {
+ SetSimpleCacheMode();
+ DisableSimpleCacheWaitForIndex();
+ DisableIntegrityCheck();
+ InitCache();
+
+ // Assume the index is not initialized, which is likely, since we are blocking
+ // the IO thread from executing the index finalization step.
+ disk_cache::Entry* entry1;
+ net::TestCompletionCallback cb1;
+ disk_cache::Entry* entry2;
+ net::TestCompletionCallback cb2;
+ int rv1 = cache_->OpenEntry("key", &entry1, cb1.callback());
+ int rv2 = cache_->CreateEntry("key", &entry2, cb2.callback());
+
+ EXPECT_EQ(net::ERR_FAILED, cb1.GetResult(rv1));
+ ASSERT_EQ(net::OK, cb2.GetResult(rv2));
+ entry2->Close();
+}
+
+// Checks that reading two entries simultaneously does not discard a CRC check.
+// TODO(pasko): make it work with Simple Cache.
+TEST_F(DiskCacheEntryTest, DISABLED_SimpleCacheMultipleReadersCheckCRC) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "key";
+
+ int size;
+ ASSERT_TRUE(SimpleCacheMakeBadChecksumEntry(key, &size));
+
+ scoped_refptr<net::IOBuffer> read_buffer1(new net::IOBuffer(size));
+ scoped_refptr<net::IOBuffer> read_buffer2(new net::IOBuffer(size));
+
+ // Advance the first reader a little.
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ EXPECT_EQ(1, ReadData(entry, 0, 0, read_buffer1.get(), 1));
+
+ // Make the second reader pass the point where the first one is, and close.
+ disk_cache::Entry* entry2 = NULL;
+ EXPECT_EQ(net::OK, OpenEntry(key, &entry2));
+ EXPECT_EQ(1, ReadData(entry2, 0, 0, read_buffer2.get(), 1));
+ EXPECT_EQ(1, ReadData(entry2, 0, 1, read_buffer2.get(), 1));
+ entry2->Close();
+
+ // Read the data till the end should produce an error.
+ EXPECT_GT(0, ReadData(entry, 0, 1, read_buffer1.get(), size));
+ entry->Close();
+ DisableIntegrityCheck();
+}
+
+// Checking one more scenario of overlapped reading of a bad entry.
+// Differs from the |SimpleCacheMultipleReadersCheckCRC| only by the order of
+// last two reads.
+TEST_F(DiskCacheEntryTest, SimpleCacheMultipleReadersCheckCRC2) {
+ SetSimpleCacheMode();
+ InitCache();
+
+ const char key[] = "key";
+ int size;
+ ASSERT_TRUE(SimpleCacheMakeBadChecksumEntry(key, &size));
+
+ scoped_refptr<net::IOBuffer> read_buffer1(new net::IOBuffer(size));
+ scoped_refptr<net::IOBuffer> read_buffer2(new net::IOBuffer(size));
+
+ // Advance the first reader a little.
+ disk_cache::Entry* entry = NULL;
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry));
+ ScopedEntryPtr entry_closer(entry);
+ EXPECT_EQ(1, ReadData(entry, 0, 0, read_buffer1.get(), 1));
+
+ // Advance the 2nd reader by the same amount.
+ disk_cache::Entry* entry2 = NULL;
+ EXPECT_EQ(net::OK, OpenEntry(key, &entry2));
+ ScopedEntryPtr entry2_closer(entry2);
+ EXPECT_EQ(1, ReadData(entry2, 0, 0, read_buffer2.get(), 1));
+
+ // Continue reading 1st.
+ EXPECT_GT(0, ReadData(entry, 0, 1, read_buffer1.get(), size));
+
+ // This read should fail as well because we have previous read failures.
+ EXPECT_GT(0, ReadData(entry2, 0, 1, read_buffer2.get(), 1));
+ DisableIntegrityCheck();
+}
+
+// Test if we can sequentially read each subset of the data until all the data
+// is read, then the CRC is calculated correctly and the reads are successful.
+TEST_F(DiskCacheEntryTest, SimpleCacheReadCombineCRC) {
+ // Test sequence:
+ // Create, Write, Read (first half of data), Read (second half of data),
+ // Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ const int kHalfSize = 200;
+ const int kSize = 2 * kHalfSize;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, false);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_NE(null, entry);
+
+ EXPECT_EQ(kSize, WriteData(entry, 0, 0, buffer1.get(), kSize, false));
+ entry->Close();
+
+ disk_cache::Entry* entry2 = NULL;
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry2));
+ EXPECT_EQ(entry, entry2);
+
+ // Read the first half of the data.
+ int offset = 0;
+ int buf_len = kHalfSize;
+ scoped_refptr<net::IOBuffer> buffer1_read1(new net::IOBuffer(buf_len));
+ EXPECT_EQ(buf_len, ReadData(entry2, 0, offset, buffer1_read1.get(), buf_len));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read1->data(), buf_len));
+
+ // Read the second half of the data.
+ offset = buf_len;
+ buf_len = kHalfSize;
+ scoped_refptr<net::IOBuffer> buffer1_read2(new net::IOBuffer(buf_len));
+ EXPECT_EQ(buf_len, ReadData(entry2, 0, offset, buffer1_read2.get(), buf_len));
+ char* buffer1_data = buffer1->data() + offset;
+ EXPECT_EQ(0, memcmp(buffer1_data, buffer1_read2->data(), buf_len));
+
+ // Check that we are not leaking.
+ EXPECT_NE(entry, null);
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+ entry->Close();
+ entry = NULL;
+}
+
+// Test if we can write the data not in sequence and read correctly. In
+// this case the CRC will not be present.
+TEST_F(DiskCacheEntryTest, SimpleCacheNonSequentialWrite) {
+ // Test sequence:
+ // Create, Write (second half of data), Write (first half of data), Read,
+ // Close.
+ SetSimpleCacheMode();
+ InitCache();
+ disk_cache::Entry* null = NULL;
+ const char key[] = "the first key";
+
+ const int kHalfSize = 200;
+ const int kSize = 2 * kHalfSize;
+ scoped_refptr<net::IOBuffer> buffer1(new net::IOBuffer(kSize));
+ scoped_refptr<net::IOBuffer> buffer2(new net::IOBuffer(kSize));
+ CacheTestFillBuffer(buffer1->data(), kSize, false);
+ char* buffer1_data = buffer1->data() + kHalfSize;
+ memcpy(buffer2->data(), buffer1_data, kHalfSize);
+ disk_cache::Entry* entry = NULL;
+
+ ASSERT_EQ(net::OK, CreateEntry(key, &entry));
+ EXPECT_NE(null, entry);
+
+ int offset = kHalfSize;
+ int buf_len = kHalfSize;
+
+ EXPECT_EQ(buf_len,
+ WriteData(entry, 0, offset, buffer2.get(), buf_len, false));
+ offset = 0;
+ buf_len = kHalfSize;
+ EXPECT_EQ(buf_len,
+ WriteData(entry, 0, offset, buffer1.get(), buf_len, false));
+ entry->Close();
+
+ disk_cache::Entry* entry2 = NULL;
+ ASSERT_EQ(net::OK, OpenEntry(key, &entry2));
+ EXPECT_EQ(entry, entry2);
+
+ scoped_refptr<net::IOBuffer> buffer1_read1(new net::IOBuffer(kSize));
+ EXPECT_EQ(kSize, ReadData(entry2, 0, 0, buffer1_read1.get(), kSize));
+ EXPECT_EQ(0, memcmp(buffer1->data(), buffer1_read1->data(), kSize));
+
+ // Check that we are not leaking.
+ ASSERT_NE(entry, null);
+ EXPECT_TRUE(
+ static_cast<disk_cache::SimpleEntryImpl*>(entry)->HasOneRef());
+ entry->Close();
+ entry = NULL;
+}
+
+#endif // defined(OS_POSIX)
diff --git a/chromium/net/disk_cache/errors.h b/chromium/net/disk_cache/errors.h
new file mode 100644
index 00000000000..1c69d42f770
--- /dev/null
+++ b/chromium/net/disk_cache/errors.h
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+// Error codes reported by self tests or to UMA.
+
+#ifndef NET_DISK_CACHE_ERRORS_H__
+#define NET_DISK_CACHE_ERRORS_H__
+
+namespace disk_cache {
+
+enum {
+ ERR_NO_ERROR = 0,
+ ERR_INIT_FAILED = -1,
+ ERR_INVALID_TAIL = -2,
+ ERR_INVALID_HEAD = -3,
+ ERR_INVALID_PREV = -4,
+ ERR_INVALID_NEXT = -5,
+ ERR_INVALID_ENTRY = -6,
+ ERR_INVALID_ADDRESS = -7,
+ ERR_INVALID_LINKS = -8,
+ ERR_NUM_ENTRIES_MISMATCH = -9,
+ ERR_READ_FAILURE = -10,
+ ERR_PREVIOUS_CRASH = -11,
+ ERR_STORAGE_ERROR = -12,
+ ERR_INVALID_MASK = -13,
+ ERR_CACHE_DOOMED = -14, // Not really an error condition.
+ ERR_CACHE_CREATED = -15 // Not really an error condition.
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ERRORS_H__
diff --git a/chromium/net/disk_cache/eviction.cc b/chromium/net/disk_cache/eviction.cc
new file mode 100644
index 00000000000..47b52552ef5
--- /dev/null
+++ b/chromium/net/disk_cache/eviction.cc
@@ -0,0 +1,597 @@
+// Copyright (c) 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.
+
+// The eviction policy is a very simple pure LRU, so the elements at the end of
+// the list are evicted until kCleanUpMargin free space is available. There is
+// only one list in use (Rankings::NO_USE), and elements are sent to the front
+// of the list whenever they are accessed.
+
+// The new (in-development) eviction policy adds re-use as a factor to evict
+// an entry. The story so far:
+
+// Entries are linked on separate lists depending on how often they are used.
+// When we see an element for the first time, it goes to the NO_USE list; if
+// the object is reused later on, we move it to the LOW_USE list, until it is
+// used kHighUse times, at which point it is moved to the HIGH_USE list.
+// Whenever an element is evicted, we move it to the DELETED list so that if the
+// element is accessed again, we remember the fact that it was already stored
+// and maybe in the future we don't evict that element.
+
+// When we have to evict an element, first we try to use the last element from
+// the NO_USE list, then we move to the LOW_USE and only then we evict an entry
+// from the HIGH_USE. We attempt to keep entries on the cache for at least
+// kTargetTime hours (with frequently accessed items stored for longer periods),
+// but if we cannot do that, we fall-back to keep each list roughly the same
+// size so that we have a chance to see an element again and move it to another
+// list.
+
+#include "net/disk_cache/eviction.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/trace.h"
+
+using base::Time;
+using base::TimeTicks;
+
+namespace {
+
+const int kCleanUpMargin = 1024 * 1024;
+const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
+const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
+const int kMaxDelayedTrims = 60;
+
+int LowWaterAdjust(int high_water) {
+ if (high_water < kCleanUpMargin)
+ return 0;
+
+ return high_water - kCleanUpMargin;
+}
+
+bool FallingBehind(int current_size, int max_size) {
+ return current_size > max_size - kCleanUpMargin * 20;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+// The real initialization happens during Init(), init_ is the only member that
+// has to be initialized here.
+Eviction::Eviction()
+ : backend_(NULL),
+ init_(false),
+ ptr_factory_(this) {
+}
+
+Eviction::~Eviction() {
+}
+
+void Eviction::Init(BackendImpl* backend) {
+ // We grab a bunch of info from the backend to make the code a little cleaner
+ // when we're actually doing work.
+ backend_ = backend;
+ rankings_ = &backend->rankings_;
+ header_ = &backend_->data_->header;
+ max_size_ = LowWaterAdjust(backend_->max_size_);
+ index_size_ = backend->mask_ + 1;
+ new_eviction_ = backend->new_eviction_;
+ first_trim_ = true;
+ trimming_ = false;
+ delay_trim_ = false;
+ trim_delays_ = 0;
+ init_ = true;
+ test_mode_ = false;
+}
+
+void Eviction::Stop() {
+ // It is possible for the backend initialization to fail, in which case this
+ // object was never initialized... and there is nothing to do.
+ if (!init_)
+ return;
+
+ // We want to stop further evictions, so let's pretend that we are busy from
+ // this point on.
+ DCHECK(!trimming_);
+ trimming_ = true;
+ ptr_factory_.InvalidateWeakPtrs();
+}
+
+void Eviction::TrimCache(bool empty) {
+ if (backend_->disabled_ || trimming_)
+ return;
+
+ if (!empty && !ShouldTrim())
+ return PostDelayedTrim();
+
+ if (new_eviction_)
+ return TrimCacheV2(empty);
+
+ Trace("*** Trim Cache ***");
+ trimming_ = true;
+ TimeTicks start = TimeTicks::Now();
+ Rankings::ScopedRankingsBlock node(rankings_);
+ Rankings::ScopedRankingsBlock next(
+ rankings_, rankings_->GetPrev(node.get(), Rankings::NO_USE));
+ int deleted_entries = 0;
+ int target_size = empty ? 0 : max_size_;
+ while ((header_->num_bytes > target_size || test_mode_) && next.get()) {
+ // The iterator could be invalidated within EvictEntry().
+ if (!next->HasData())
+ break;
+ node.reset(next.release());
+ next.reset(rankings_->GetPrev(node.get(), Rankings::NO_USE));
+ if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
+ // This entry is not being used by anybody.
+ // Do NOT use node as an iterator after this point.
+ rankings_->TrackRankingsBlock(node.get(), false);
+ if (EvictEntry(node.get(), empty, Rankings::NO_USE) && !test_mode_)
+ deleted_entries++;
+
+ if (!empty && test_mode_)
+ break;
+ }
+ if (!empty && (deleted_entries > 20 ||
+ (TimeTicks::Now() - start).InMilliseconds() > 20)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
+ break;
+ }
+ }
+
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV1", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV1", 0, start);
+ }
+ CACHE_UMA(COUNTS, "TrimItemsV1", 0, deleted_entries);
+
+ trimming_ = false;
+ Trace("*** Trim Cache end ***");
+ return;
+}
+
+void Eviction::UpdateRank(EntryImpl* entry, bool modified) {
+ if (new_eviction_)
+ return UpdateRankV2(entry, modified);
+
+ rankings_->UpdateRank(entry->rankings(), modified, GetListForEntry(entry));
+}
+
+void Eviction::OnOpenEntry(EntryImpl* entry) {
+ if (new_eviction_)
+ return OnOpenEntryV2(entry);
+}
+
+void Eviction::OnCreateEntry(EntryImpl* entry) {
+ if (new_eviction_)
+ return OnCreateEntryV2(entry);
+
+ rankings_->Insert(entry->rankings(), true, GetListForEntry(entry));
+}
+
+void Eviction::OnDoomEntry(EntryImpl* entry) {
+ if (new_eviction_)
+ return OnDoomEntryV2(entry);
+
+ if (entry->LeaveRankingsBehind())
+ return;
+
+ rankings_->Remove(entry->rankings(), GetListForEntry(entry), true);
+}
+
+void Eviction::OnDestroyEntry(EntryImpl* entry) {
+ if (new_eviction_)
+ return OnDestroyEntryV2(entry);
+}
+
+void Eviction::SetTestMode() {
+ test_mode_ = true;
+}
+
+void Eviction::TrimDeletedList(bool empty) {
+ DCHECK(test_mode_ && new_eviction_);
+ TrimDeleted(empty);
+}
+
+void Eviction::PostDelayedTrim() {
+ // Prevent posting multiple tasks.
+ if (delay_trim_)
+ return;
+ delay_trim_ = true;
+ trim_delays_++;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Eviction::DelayedTrim, ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(1000));
+}
+
+void Eviction::DelayedTrim() {
+ delay_trim_ = false;
+ if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
+ return PostDelayedTrim();
+
+ TrimCache(false);
+}
+
+bool Eviction::ShouldTrim() {
+ if (!FallingBehind(header_->num_bytes, max_size_) &&
+ trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded()) {
+ return false;
+ }
+
+ UMA_HISTOGRAM_COUNTS("DiskCache.TrimDelays", trim_delays_);
+ trim_delays_ = 0;
+ return true;
+}
+
+bool Eviction::ShouldTrimDeleted() {
+ int index_load = header_->num_entries * 100 / index_size_;
+
+ // If the index is not loaded, the deleted list will tend to double the size
+ // of the other lists 3 lists (40% of the total). Otherwise, all lists will be
+ // about the same size.
+ int max_length = (index_load < 25) ? header_->num_entries * 2 / 5 :
+ header_->num_entries / 4;
+ return (!test_mode_ && header_->lru.sizes[Rankings::DELETED] > max_length);
+}
+
+void Eviction::ReportTrimTimes(EntryImpl* entry) {
+ if (first_trim_) {
+ first_trim_ = false;
+ if (backend_->ShouldReportAgain()) {
+ CACHE_UMA(AGE, "TrimAge", 0, entry->GetLastUsed());
+ ReportListStats();
+ }
+
+ if (header_->lru.filled)
+ return;
+
+ header_->lru.filled = 1;
+
+ if (header_->create_time) {
+ // This is the first entry that we have to evict, generate some noise.
+ backend_->FirstEviction();
+ } else {
+ // This is an old file, but we may want more reports from this user so
+ // lets save some create_time.
+ Time::Exploded old = {0};
+ old.year = 2009;
+ old.month = 3;
+ old.day_of_month = 1;
+ header_->create_time = Time::FromLocalExploded(old).ToInternalValue();
+ }
+ }
+}
+
+Rankings::List Eviction::GetListForEntry(EntryImpl* entry) {
+ return Rankings::NO_USE;
+}
+
+bool Eviction::EvictEntry(CacheRankingsBlock* node, bool empty,
+ Rankings::List list) {
+ EntryImpl* entry = backend_->GetEnumeratedEntry(node, list);
+ if (!entry) {
+ Trace("NewEntry failed on Trim 0x%x", node->address().value());
+ return false;
+ }
+
+ ReportTrimTimes(entry);
+ if (empty || !new_eviction_) {
+ entry->DoomImpl();
+ } else {
+ entry->DeleteEntryData(false);
+ EntryStore* info = entry->entry()->Data();
+ DCHECK_EQ(ENTRY_NORMAL, info->state);
+
+ rankings_->Remove(entry->rankings(), GetListForEntryV2(entry), true);
+ info->state = ENTRY_EVICTED;
+ entry->entry()->Store();
+ rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
+ }
+ if (!empty)
+ backend_->OnEvent(Stats::TRIM_ENTRY);
+
+ entry->Release();
+
+ return true;
+}
+
+// -----------------------------------------------------------------------
+
+void Eviction::TrimCacheV2(bool empty) {
+ Trace("*** Trim Cache ***");
+ trimming_ = true;
+ TimeTicks start = TimeTicks::Now();
+
+ const int kListsToSearch = 3;
+ Rankings::ScopedRankingsBlock next[kListsToSearch];
+ int list = Rankings::LAST_ELEMENT;
+
+ // Get a node from each list.
+ for (int i = 0; i < kListsToSearch; i++) {
+ bool done = false;
+ next[i].set_rankings(rankings_);
+ if (done)
+ continue;
+ next[i].reset(rankings_->GetPrev(NULL, static_cast<Rankings::List>(i)));
+ if (!empty && NodeIsOldEnough(next[i].get(), i)) {
+ list = static_cast<Rankings::List>(i);
+ done = true;
+ }
+ }
+
+ // If we are not meeting the time targets lets move on to list length.
+ if (!empty && Rankings::LAST_ELEMENT == list)
+ list = SelectListByLength(next);
+
+ if (empty)
+ list = 0;
+
+ Rankings::ScopedRankingsBlock node(rankings_);
+ int deleted_entries = 0;
+ int target_size = empty ? 0 : max_size_;
+
+ for (; list < kListsToSearch; list++) {
+ while ((header_->num_bytes > target_size || test_mode_) &&
+ next[list].get()) {
+ // The iterator could be invalidated within EvictEntry().
+ if (!next[list]->HasData())
+ break;
+ node.reset(next[list].release());
+ next[list].reset(rankings_->GetPrev(node.get(),
+ static_cast<Rankings::List>(list)));
+ if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
+ // This entry is not being used by anybody.
+ // Do NOT use node as an iterator after this point.
+ rankings_->TrackRankingsBlock(node.get(), false);
+ if (EvictEntry(node.get(), empty, static_cast<Rankings::List>(list)))
+ deleted_entries++;
+
+ if (!empty && test_mode_)
+ break;
+ }
+ if (!empty && (deleted_entries > 20 ||
+ (TimeTicks::Now() - start).InMilliseconds() > 20)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
+ break;
+ }
+ }
+ if (!empty)
+ list = kListsToSearch;
+ }
+
+ if (empty) {
+ TrimDeleted(true);
+ } else if (ShouldTrimDeleted()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), empty));
+ }
+
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV2", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV2", 0, start);
+ }
+ CACHE_UMA(COUNTS, "TrimItemsV2", 0, deleted_entries);
+
+ Trace("*** Trim Cache end ***");
+ trimming_ = false;
+ return;
+}
+
+void Eviction::UpdateRankV2(EntryImpl* entry, bool modified) {
+ rankings_->UpdateRank(entry->rankings(), modified, GetListForEntryV2(entry));
+}
+
+void Eviction::OnOpenEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ DCHECK_EQ(ENTRY_NORMAL, info->state);
+
+ if (info->reuse_count < kint32max) {
+ info->reuse_count++;
+ entry->entry()->set_modified();
+
+ // We may need to move this to a new list.
+ if (1 == info->reuse_count) {
+ rankings_->Remove(entry->rankings(), Rankings::NO_USE, true);
+ rankings_->Insert(entry->rankings(), false, Rankings::LOW_USE);
+ entry->entry()->Store();
+ } else if (kHighUse == info->reuse_count) {
+ rankings_->Remove(entry->rankings(), Rankings::LOW_USE, true);
+ rankings_->Insert(entry->rankings(), false, Rankings::HIGH_USE);
+ entry->entry()->Store();
+ }
+ }
+}
+
+void Eviction::OnCreateEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ switch (info->state) {
+ case ENTRY_NORMAL: {
+ DCHECK(!info->reuse_count);
+ DCHECK(!info->refetch_count);
+ break;
+ };
+ case ENTRY_EVICTED: {
+ if (info->refetch_count < kint32max)
+ info->refetch_count++;
+
+ if (info->refetch_count > kHighUse && info->reuse_count < kHighUse) {
+ info->reuse_count = kHighUse;
+ } else {
+ info->reuse_count++;
+ }
+ info->state = ENTRY_NORMAL;
+ entry->entry()->Store();
+ rankings_->Remove(entry->rankings(), Rankings::DELETED, true);
+ break;
+ };
+ default:
+ NOTREACHED();
+ }
+
+ rankings_->Insert(entry->rankings(), true, GetListForEntryV2(entry));
+}
+
+void Eviction::OnDoomEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ if (ENTRY_NORMAL != info->state)
+ return;
+
+ if (entry->LeaveRankingsBehind()) {
+ info->state = ENTRY_DOOMED;
+ entry->entry()->Store();
+ return;
+ }
+
+ rankings_->Remove(entry->rankings(), GetListForEntryV2(entry), true);
+
+ info->state = ENTRY_DOOMED;
+ entry->entry()->Store();
+ rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
+}
+
+void Eviction::OnDestroyEntryV2(EntryImpl* entry) {
+ if (entry->LeaveRankingsBehind())
+ return;
+
+ rankings_->Remove(entry->rankings(), Rankings::DELETED, true);
+}
+
+Rankings::List Eviction::GetListForEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ DCHECK_EQ(ENTRY_NORMAL, info->state);
+
+ if (!info->reuse_count)
+ return Rankings::NO_USE;
+
+ if (info->reuse_count < kHighUse)
+ return Rankings::LOW_USE;
+
+ return Rankings::HIGH_USE;
+}
+
+// This is a minimal implementation that just discards the oldest nodes.
+// TODO(rvargas): Do something better here.
+void Eviction::TrimDeleted(bool empty) {
+ Trace("*** Trim Deleted ***");
+ if (backend_->disabled_)
+ return;
+
+ TimeTicks start = TimeTicks::Now();
+ Rankings::ScopedRankingsBlock node(rankings_);
+ Rankings::ScopedRankingsBlock next(
+ rankings_, rankings_->GetPrev(node.get(), Rankings::DELETED));
+ int deleted_entries = 0;
+ while (next.get() &&
+ (empty || (deleted_entries < 20 &&
+ (TimeTicks::Now() - start).InMilliseconds() < 20))) {
+ node.reset(next.release());
+ next.reset(rankings_->GetPrev(node.get(), Rankings::DELETED));
+ if (RemoveDeletedNode(node.get()))
+ deleted_entries++;
+ if (test_mode_)
+ break;
+ }
+
+ if (deleted_entries && !empty && ShouldTrimDeleted()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), false));
+ }
+
+ CACHE_UMA(AGE_MS, "TotalTrimDeletedTime", 0, start);
+ CACHE_UMA(COUNTS, "TrimDeletedItems", 0, deleted_entries);
+ Trace("*** Trim Deleted end ***");
+ return;
+}
+
+bool Eviction::RemoveDeletedNode(CacheRankingsBlock* node) {
+ EntryImpl* entry = backend_->GetEnumeratedEntry(node, Rankings::DELETED);
+ if (!entry) {
+ Trace("NewEntry failed on Trim 0x%x", node->address().value());
+ return false;
+ }
+
+ bool doomed = (entry->entry()->Data()->state == ENTRY_DOOMED);
+ entry->entry()->Data()->state = ENTRY_DOOMED;
+ entry->DoomImpl();
+ entry->Release();
+ return !doomed;
+}
+
+bool Eviction::NodeIsOldEnough(CacheRankingsBlock* node, int list) {
+ if (!node)
+ return false;
+
+ // If possible, we want to keep entries on each list at least kTargetTime
+ // hours. Each successive list on the enumeration has 2x the target time of
+ // the previous list.
+ Time used = Time::FromInternalValue(node->Data()->last_used);
+ int multiplier = 1 << list;
+ return (Time::Now() - used).InHours() > kTargetTime * multiplier;
+}
+
+int Eviction::SelectListByLength(Rankings::ScopedRankingsBlock* next) {
+ int data_entries = header_->num_entries -
+ header_->lru.sizes[Rankings::DELETED];
+ // Start by having each list to be roughly the same size.
+ if (header_->lru.sizes[0] > data_entries / 3)
+ return 0;
+
+ int list = (header_->lru.sizes[1] > data_entries / 3) ? 1 : 2;
+
+ // Make sure that frequently used items are kept for a minimum time; we know
+ // that this entry is not older than its current target, but it must be at
+ // least older than the target for list 0 (kTargetTime), as long as we don't
+ // exhaust list 0.
+ if (!NodeIsOldEnough(next[list].get(), 0) &&
+ header_->lru.sizes[0] > data_entries / 10)
+ list = 0;
+
+ return list;
+}
+
+void Eviction::ReportListStats() {
+ if (!new_eviction_)
+ return;
+
+ Rankings::ScopedRankingsBlock last1(rankings_,
+ rankings_->GetPrev(NULL, Rankings::NO_USE));
+ Rankings::ScopedRankingsBlock last2(rankings_,
+ rankings_->GetPrev(NULL, Rankings::LOW_USE));
+ Rankings::ScopedRankingsBlock last3(rankings_,
+ rankings_->GetPrev(NULL, Rankings::HIGH_USE));
+ Rankings::ScopedRankingsBlock last4(rankings_,
+ rankings_->GetPrev(NULL, Rankings::DELETED));
+
+ if (last1.get())
+ CACHE_UMA(AGE, "NoUseAge", 0,
+ Time::FromInternalValue(last1.get()->Data()->last_used));
+ if (last2.get())
+ CACHE_UMA(AGE, "LowUseAge", 0,
+ Time::FromInternalValue(last2.get()->Data()->last_used));
+ if (last3.get())
+ CACHE_UMA(AGE, "HighUseAge", 0,
+ Time::FromInternalValue(last3.get()->Data()->last_used));
+ if (last4.get())
+ CACHE_UMA(AGE, "DeletedAge", 0,
+ Time::FromInternalValue(last4.get()->Data()->last_used));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/eviction.h b/chromium/net/disk_cache/eviction.h
new file mode 100644
index 00000000000..f6224a936fd
--- /dev/null
+++ b/chromium/net/disk_cache/eviction.h
@@ -0,0 +1,91 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_EVICTION_H_
+#define NET_DISK_CACHE_EVICTION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/disk_cache/rankings.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+class EntryImpl;
+struct IndexHeader;
+
+// This class implements the eviction algorithm for the cache and it is tightly
+// integrated with BackendImpl.
+class Eviction {
+ public:
+ Eviction();
+ ~Eviction();
+
+ void Init(BackendImpl* backend);
+ void Stop();
+
+ // Deletes entries from the cache until the current size is below the limit.
+ // If empty is true, the whole cache will be trimmed, regardless of being in
+ // use.
+ void TrimCache(bool empty);
+
+ // Updates the ranking information for an entry.
+ void UpdateRank(EntryImpl* entry, bool modified);
+
+ // Notifications of interesting events for a given entry.
+ void OnOpenEntry(EntryImpl* entry);
+ void OnCreateEntry(EntryImpl* entry);
+ void OnDoomEntry(EntryImpl* entry);
+ void OnDestroyEntry(EntryImpl* entry);
+
+ // Testing interface.
+ void SetTestMode();
+ void TrimDeletedList(bool empty);
+
+ private:
+ void PostDelayedTrim();
+ void DelayedTrim();
+ bool ShouldTrim();
+ bool ShouldTrimDeleted();
+ void ReportTrimTimes(EntryImpl* entry);
+ Rankings::List GetListForEntry(EntryImpl* entry);
+ bool EvictEntry(CacheRankingsBlock* node, bool empty, Rankings::List list);
+
+ // We'll just keep for a while a separate set of methods that implement the
+ // new eviction algorithm. This code will replace the original methods when
+ // finished.
+ void TrimCacheV2(bool empty);
+ void UpdateRankV2(EntryImpl* entry, bool modified);
+ void OnOpenEntryV2(EntryImpl* entry);
+ void OnCreateEntryV2(EntryImpl* entry);
+ void OnDoomEntryV2(EntryImpl* entry);
+ void OnDestroyEntryV2(EntryImpl* entry);
+ Rankings::List GetListForEntryV2(EntryImpl* entry);
+ void TrimDeleted(bool empty);
+ bool RemoveDeletedNode(CacheRankingsBlock* node);
+
+ bool NodeIsOldEnough(CacheRankingsBlock* node, int list);
+ int SelectListByLength(Rankings::ScopedRankingsBlock* next);
+ void ReportListStats();
+
+ BackendImpl* backend_;
+ Rankings* rankings_;
+ IndexHeader* header_;
+ int max_size_;
+ int trim_delays_;
+ int index_size_;
+ bool new_eviction_;
+ bool first_trim_;
+ bool trimming_;
+ bool delay_trim_;
+ bool init_;
+ bool test_mode_;
+ base::WeakPtrFactory<Eviction> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Eviction);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_EVICTION_H_
diff --git a/chromium/net/disk_cache/experiments.h b/chromium/net/disk_cache/experiments.h
new file mode 100644
index 00000000000..d7d4e58437b
--- /dev/null
+++ b/chromium/net/disk_cache/experiments.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 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_DISK_CACHE_EXPERIMENTS_H_
+#define NET_DISK_CACHE_EXPERIMENTS_H_
+
+
+namespace disk_cache {
+
+// This lists the experiment groups that we care about. Only add new groups at
+// the end of the list, and always increase the number.
+enum {
+ NO_EXPERIMENT = 0,
+ EXPERIMENT_OLD_FILE1 = 3,
+ EXPERIMENT_OLD_FILE2 = 4,
+ EXPERIMENT_DELETED_LIST_OUT = 11,
+ EXPERIMENT_DELETED_LIST_CONTROL = 12,
+ EXPERIMENT_DELETED_LIST_IN = 13,
+ EXPERIMENT_DELETED_LIST_OUT2 = 14,
+ // There is no EXPERIMENT_SIMPLE_YES since this enum is used in the standard
+ // backend only.
+ EXPERIMENT_SIMPLE_CONTROL = 15,
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_EXPERIMENTS_H_
diff --git a/chromium/net/disk_cache/file.cc b/chromium/net/disk_cache/file.cc
new file mode 100644
index 00000000000..6b569518354
--- /dev/null
+++ b/chromium/net/disk_cache/file.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2010 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/disk_cache/file.h"
+
+namespace disk_cache {
+
+// Cross platform constructors. Platform specific code is in
+// file_{win,posix}.cc.
+
+File::File() : init_(false), mixed_(false) {}
+
+File::File(bool mixed_mode) : init_(false), mixed_(mixed_mode) {}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/file.h b/chromium/net/disk_cache/file.h
new file mode 100644
index 00000000000..3038d884142
--- /dev/null
+++ b/chromium/net/disk_cache/file.h
@@ -0,0 +1,95 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_FILE_H_
+#define NET_DISK_CACHE_FILE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/platform_file.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace disk_cache {
+
+// This interface is used to support asynchronous ReadData and WriteData calls.
+class FileIOCallback {
+ public:
+ // Notified of the actual number of bytes read or written. This value is
+ // negative if an error occurred.
+ virtual void OnFileIOComplete(int bytes_copied) = 0;
+
+ protected:
+ virtual ~FileIOCallback() {}
+};
+
+// Simple wrapper around a file that allows asynchronous operations.
+class NET_EXPORT_PRIVATE File : public base::RefCounted<File> {
+ friend class base::RefCounted<File>;
+ public:
+ File();
+ // mixed_mode set to true enables regular synchronous operations for the file.
+ explicit File(bool mixed_mode);
+
+ // Initializes the object to use the passed in file instead of opening it with
+ // the Init() call. No asynchronous operations can be performed with this
+ // object.
+ explicit File(base::PlatformFile file);
+
+ // Initializes the object to point to a given file. The file must aready exist
+ // on disk, and allow shared read and write.
+ bool Init(const base::FilePath& name);
+
+ // Returns the handle or file descriptor.
+ base::PlatformFile platform_file() const;
+
+ // Returns true if the file was opened properly.
+ bool IsValid() const;
+
+ // Performs synchronous IO.
+ bool Read(void* buffer, size_t buffer_len, size_t offset);
+ bool Write(const void* buffer, size_t buffer_len, size_t offset);
+
+ // Performs asynchronous IO. callback will be called when the IO completes,
+ // as an APC on the thread that queued the operation.
+ bool Read(void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed);
+ bool Write(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed);
+
+ // Sets the file's length. The file is truncated or extended with zeros to
+ // the new length.
+ bool SetLength(size_t length);
+ size_t GetLength();
+
+ // Blocks until |num_pending_io| IO operations complete.
+ static void WaitForPendingIO(int* num_pending_io);
+
+ // Drops current pending operations without waiting for them to complete.
+ static void DropPendingIO();
+
+ protected:
+ virtual ~File();
+
+ // Performs the actual asynchronous write. If notify is set and there is no
+ // callback, the call will be re-synchronized.
+ bool AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed);
+
+ private:
+ bool init_;
+ bool mixed_;
+ base::PlatformFile platform_file_; // Regular, asynchronous IO handle.
+ base::PlatformFile sync_platform_file_; // Synchronous IO handle.
+
+ DISALLOW_COPY_AND_ASSIGN(File);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FILE_H_
diff --git a/chromium/net/disk_cache/file_block.h b/chromium/net/disk_cache/file_block.h
new file mode 100644
index 00000000000..25709207df6
--- /dev/null
+++ b/chromium/net/disk_cache/file_block.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2006-2008 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_FILE_BLOCK_H__
+#define NET_DISK_CACHE_FILE_BLOCK_H__
+
+namespace disk_cache {
+
+// This interface exposes common functionality for a single block of data
+// stored on a file-block, regardless of the real type or size of the block.
+// Used to simplify loading / storing the block from disk.
+class FileBlock {
+ public:
+ virtual ~FileBlock() {}
+
+ // Returns a pointer to the actual data.
+ virtual void* buffer() const = 0;
+
+ // Returns the size of the block;
+ virtual size_t size() const = 0;
+
+ // Returns the file offset of this block.
+ virtual int offset() const = 0;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FILE_BLOCK_H__
diff --git a/chromium/net/disk_cache/file_lock.cc b/chromium/net/disk_cache/file_lock.cc
new file mode 100644
index 00000000000..3d1cfa52d8b
--- /dev/null
+++ b/chromium/net/disk_cache/file_lock.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/file_lock.h"
+
+#include "base/atomicops.h"
+
+namespace {
+
+void Barrier() {
+#if !defined(COMPILER_MSVC)
+ // VS uses memory barrier semantics for volatiles.
+ base::subtle::MemoryBarrier();
+#endif
+}
+
+} // namespace
+
+namespace disk_cache {
+
+FileLock::FileLock(BlockFileHeader* header) {
+ updating_ = &header->updating;
+ (*updating_)++;
+ Barrier();
+ acquired_ = true;
+}
+
+FileLock::~FileLock() {
+ Unlock();
+}
+
+void FileLock::Lock() {
+ if (acquired_)
+ return;
+ (*updating_)++;
+ Barrier();
+}
+
+void FileLock::Unlock() {
+ if (!acquired_)
+ return;
+ Barrier();
+ (*updating_)--;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/file_lock.h b/chromium/net/disk_cache/file_lock.h
new file mode 100644
index 00000000000..7fcf75df05a
--- /dev/null
+++ b/chromium/net/disk_cache/file_lock.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_FILE_LOCK_H_
+#define NET_DISK_CACHE_FILE_LOCK_H_
+
+#include "net/base/net_export.h"
+#include "net/disk_cache/disk_format_base.h"
+
+namespace disk_cache {
+
+// This class implements a file lock that lives on the header of a memory mapped
+// file. This is NOT a thread related lock, it is a lock to detect corruption
+// of the file when the process crashes in the middle of an update.
+// The lock is acquired on the constructor and released on the destructor.
+// The typical use of the class is:
+// {
+// BlockFileHeader* header = GetFileHeader();
+// FileLock lock(header);
+// header->max_entries = num_entries;
+// // At this point the destructor is going to release the lock.
+// }
+// It is important to perform Lock() and Unlock() operations in the right order,
+// because otherwise the desired effect of the "lock" will not be achieved. If
+// the operations are inlined / optimized, the "locked" operations can happen
+// outside the lock.
+class NET_EXPORT_PRIVATE FileLock {
+ public:
+ explicit FileLock(BlockFileHeader* header);
+ virtual ~FileLock();
+
+ // Virtual to make sure the compiler never inlines the calls.
+ virtual void Lock();
+ virtual void Unlock();
+ private:
+ bool acquired_;
+ volatile int32* updating_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FILE_LOCK_H_
diff --git a/chromium/net/disk_cache/file_posix.cc b/chromium/net/disk_cache/file_posix.cc
new file mode 100644
index 00000000000..2ad3db916e3
--- /dev/null
+++ b/chromium/net/disk_cache/file_posix.cc
@@ -0,0 +1,309 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/file.h"
+
+#include <fcntl.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/in_flight_io.h"
+
+namespace {
+
+// This class represents a single asynchronous IO operation while it is being
+// bounced between threads.
+class FileBackgroundIO : public disk_cache::BackgroundIO {
+ public:
+ // Other than the actual parameters for the IO operation (including the
+ // |callback| that must be notified at the end), we need the controller that
+ // is keeping track of all operations. When done, we notify the controller
+ // (we do NOT invoke the callback), in the worker thead that completed the
+ // operation.
+ FileBackgroundIO(disk_cache::File* file, const void* buf, size_t buf_len,
+ size_t offset, disk_cache::FileIOCallback* callback,
+ disk_cache::InFlightIO* controller)
+ : disk_cache::BackgroundIO(controller), callback_(callback), file_(file),
+ buf_(buf), buf_len_(buf_len), offset_(offset) {
+ }
+
+ disk_cache::FileIOCallback* callback() {
+ return callback_;
+ }
+
+ disk_cache::File* file() {
+ return file_;
+ }
+
+ // Read and Write are the operations that can be performed asynchronously.
+ // The actual parameters for the operation are setup in the constructor of
+ // the object. Both methods should be called from a worker thread, by posting
+ // a task to the WorkerPool (they are RunnableMethods). When finished,
+ // controller->OnIOComplete() is called.
+ void Read();
+ void Write();
+
+ private:
+ virtual ~FileBackgroundIO() {}
+
+ disk_cache::FileIOCallback* callback_;
+
+ disk_cache::File* file_;
+ const void* buf_;
+ size_t buf_len_;
+ size_t offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileBackgroundIO);
+};
+
+
+// The specialized controller that keeps track of current operations.
+class FileInFlightIO : public disk_cache::InFlightIO {
+ public:
+ FileInFlightIO() {}
+ virtual ~FileInFlightIO() {}
+
+ // These methods start an asynchronous operation. The arguments have the same
+ // semantics of the File asynchronous operations, with the exception that the
+ // operation never finishes synchronously.
+ void PostRead(disk_cache::File* file, void* buf, size_t buf_len,
+ size_t offset, disk_cache::FileIOCallback* callback);
+ void PostWrite(disk_cache::File* file, const void* buf, size_t buf_len,
+ size_t offset, disk_cache::FileIOCallback* callback);
+
+ protected:
+ // Invokes the users' completion callback at the end of the IO operation.
+ // |cancel| is true if the actual task posted to the thread is still
+ // queued (because we are inside WaitForPendingIO), and false if said task is
+ // the one performing the call.
+ virtual void OnOperationComplete(disk_cache::BackgroundIO* operation,
+ bool cancel) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileInFlightIO);
+};
+
+// ---------------------------------------------------------------------------
+
+// Runs on a worker thread.
+void FileBackgroundIO::Read() {
+ if (file_->Read(const_cast<void*>(buf_), buf_len_, offset_)) {
+ result_ = static_cast<int>(buf_len_);
+ } else {
+ result_ = net::ERR_CACHE_READ_FAILURE;
+ }
+ NotifyController();
+}
+
+// Runs on a worker thread.
+void FileBackgroundIO::Write() {
+ bool rv = file_->Write(buf_, buf_len_, offset_);
+
+ result_ = rv ? static_cast<int>(buf_len_) : net::ERR_CACHE_WRITE_FAILURE;
+ NotifyController();
+}
+
+// ---------------------------------------------------------------------------
+
+void FileInFlightIO::PostRead(disk_cache::File *file, void* buf, size_t buf_len,
+ size_t offset, disk_cache::FileIOCallback *callback) {
+ scoped_refptr<FileBackgroundIO> operation(
+ new FileBackgroundIO(file, buf, buf_len, offset, callback, this));
+ file->AddRef(); // Balanced on OnOperationComplete()
+
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(&FileBackgroundIO::Read, operation.get()), true);
+ OnOperationPosted(operation.get());
+}
+
+void FileInFlightIO::PostWrite(disk_cache::File* file, const void* buf,
+ size_t buf_len, size_t offset,
+ disk_cache::FileIOCallback* callback) {
+ scoped_refptr<FileBackgroundIO> operation(
+ new FileBackgroundIO(file, buf, buf_len, offset, callback, this));
+ file->AddRef(); // Balanced on OnOperationComplete()
+
+ base::WorkerPool::PostTask(FROM_HERE,
+ base::Bind(&FileBackgroundIO::Write, operation.get()), true);
+ OnOperationPosted(operation.get());
+}
+
+// Runs on the IO thread.
+void FileInFlightIO::OnOperationComplete(disk_cache::BackgroundIO* operation,
+ bool cancel) {
+ FileBackgroundIO* op = static_cast<FileBackgroundIO*>(operation);
+
+ disk_cache::FileIOCallback* callback = op->callback();
+ int bytes = operation->result();
+
+ // Release the references acquired in PostRead / PostWrite.
+ op->file()->Release();
+ callback->OnFileIOComplete(bytes);
+}
+
+// A static object tha will broker all async operations.
+FileInFlightIO* s_file_operations = NULL;
+
+// Returns the current FileInFlightIO.
+FileInFlightIO* GetFileInFlightIO() {
+ if (!s_file_operations) {
+ s_file_operations = new FileInFlightIO;
+ }
+ return s_file_operations;
+}
+
+// Deletes the current FileInFlightIO.
+void DeleteFileInFlightIO() {
+ DCHECK(s_file_operations);
+ delete s_file_operations;
+ s_file_operations = NULL;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+File::File(base::PlatformFile file)
+ : init_(true),
+ mixed_(true),
+ platform_file_(file),
+ sync_platform_file_(base::kInvalidPlatformFileValue) {
+}
+
+bool File::Init(const base::FilePath& name) {
+ if (init_)
+ return false;
+
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE;
+ platform_file_ = base::CreatePlatformFile(name, flags, NULL, NULL);
+ if (platform_file_ < 0) {
+ platform_file_ = 0;
+ return false;
+ }
+
+ init_ = true;
+ return true;
+}
+
+base::PlatformFile File::platform_file() const {
+ return platform_file_;
+}
+
+bool File::IsValid() const {
+ if (!init_)
+ return false;
+ return (base::kInvalidPlatformFileValue != platform_file_);
+}
+
+bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
+ DCHECK(init_);
+ if (buffer_len > static_cast<size_t>(kint32max) ||
+ offset > static_cast<size_t>(kint32max))
+ return false;
+
+ int ret = base::ReadPlatformFile(platform_file_, offset,
+ static_cast<char*>(buffer), buffer_len);
+ return (static_cast<size_t>(ret) == buffer_len);
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
+ DCHECK(init_);
+ if (buffer_len > static_cast<size_t>(kint32max) ||
+ offset > static_cast<size_t>(kint32max))
+ return false;
+
+ int ret = base::WritePlatformFile(platform_file_, offset,
+ static_cast<const char*>(buffer),
+ buffer_len);
+ return (static_cast<size_t>(ret) == buffer_len);
+}
+
+// We have to increase the ref counter of the file before performing the IO to
+// prevent the completion to happen with an invalid handle (if the file is
+// closed while the IO is in flight).
+bool File::Read(void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ if (!callback) {
+ if (completed)
+ *completed = true;
+ return Read(buffer, buffer_len, offset);
+ }
+
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ GetFileInFlightIO()->PostRead(this, buffer, buffer_len, offset, callback);
+
+ *completed = false;
+ return true;
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ if (!callback) {
+ if (completed)
+ *completed = true;
+ return Write(buffer, buffer_len, offset);
+ }
+
+ return AsyncWrite(buffer, buffer_len, offset, callback, completed);
+}
+
+bool File::SetLength(size_t length) {
+ DCHECK(init_);
+ if (length > ULONG_MAX)
+ return false;
+
+ return base::TruncatePlatformFile(platform_file_, length);
+}
+
+size_t File::GetLength() {
+ DCHECK(init_);
+ off_t ret = lseek(platform_file_, 0, SEEK_END);
+ if (ret < 0)
+ return 0;
+ return ret;
+}
+
+// Static.
+void File::WaitForPendingIO(int* num_pending_io) {
+ // We may be running unit tests so we should allow be able to reset the
+ // message loop.
+ GetFileInFlightIO()->WaitForPendingIO();
+ DeleteFileInFlightIO();
+}
+
+// Static.
+void File::DropPendingIO() {
+ GetFileInFlightIO()->DropPendingIO();
+ DeleteFileInFlightIO();
+}
+
+File::~File() {
+ if (IsValid())
+ base::ClosePlatformFile(platform_file_);
+}
+
+bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ GetFileInFlightIO()->PostWrite(this, buffer, buffer_len, offset, callback);
+
+ if (completed)
+ *completed = false;
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/file_win.cc b/chromium/net/disk_cache/file_win.cc
new file mode 100644
index 00000000000..f284b501045
--- /dev/null
+++ b/chromium/net/disk_cache/file_win.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/file.h"
+
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace {
+
+// Structure used for asynchronous operations.
+struct MyOverlapped {
+ MyOverlapped(disk_cache::File* file, size_t offset,
+ disk_cache::FileIOCallback* callback);
+ ~MyOverlapped() {}
+ OVERLAPPED* overlapped() {
+ return &context_.overlapped;
+ }
+
+ base::MessageLoopForIO::IOContext context_;
+ scoped_refptr<disk_cache::File> file_;
+ disk_cache::FileIOCallback* callback_;
+};
+
+COMPILE_ASSERT(!offsetof(MyOverlapped, context_), starts_with_overlapped);
+
+// Helper class to handle the IO completion notifications from the message loop.
+class CompletionHandler : public base::MessageLoopForIO::IOHandler {
+ virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD actual_bytes,
+ DWORD error);
+};
+
+static base::LazyInstance<CompletionHandler> g_completion_handler =
+ LAZY_INSTANCE_INITIALIZER;
+
+void CompletionHandler::OnIOCompleted(
+ base::MessageLoopForIO::IOContext* context,
+ DWORD actual_bytes,
+ DWORD error) {
+ MyOverlapped* data = reinterpret_cast<MyOverlapped*>(context);
+
+ if (error) {
+ DCHECK(!actual_bytes);
+ actual_bytes = static_cast<DWORD>(net::ERR_CACHE_READ_FAILURE);
+ NOTREACHED();
+ }
+
+ if (data->callback_)
+ data->callback_->OnFileIOComplete(static_cast<int>(actual_bytes));
+
+ delete data;
+}
+
+MyOverlapped::MyOverlapped(disk_cache::File* file, size_t offset,
+ disk_cache::FileIOCallback* callback) {
+ memset(this, 0, sizeof(*this));
+ context_.handler = g_completion_handler.Pointer();
+ context_.overlapped.Offset = static_cast<DWORD>(offset);
+ file_ = file;
+ callback_ = callback;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+File::File(base::PlatformFile file)
+ : init_(true), mixed_(true), platform_file_(INVALID_HANDLE_VALUE),
+ sync_platform_file_(file) {
+}
+
+bool File::Init(const base::FilePath& name) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ DWORD access = GENERIC_READ | GENERIC_WRITE | DELETE;
+ platform_file_ = CreateFile(name.value().c_str(), access, sharing, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+
+ if (INVALID_HANDLE_VALUE == platform_file_)
+ return false;
+
+ base::MessageLoopForIO::current()->RegisterIOHandler(
+ platform_file_, g_completion_handler.Pointer());
+
+ init_ = true;
+ sync_platform_file_ = CreateFile(name.value().c_str(), access, sharing, NULL,
+ OPEN_EXISTING, 0, NULL);
+
+ if (INVALID_HANDLE_VALUE == sync_platform_file_)
+ return false;
+
+ return true;
+}
+
+File::~File() {
+ if (!init_)
+ return;
+
+ if (INVALID_HANDLE_VALUE != platform_file_)
+ CloseHandle(platform_file_);
+ if (INVALID_HANDLE_VALUE != sync_platform_file_)
+ CloseHandle(sync_platform_file_);
+}
+
+base::PlatformFile File::platform_file() const {
+ DCHECK(init_);
+ return (INVALID_HANDLE_VALUE == platform_file_) ? sync_platform_file_ :
+ platform_file_;
+}
+
+bool File::IsValid() const {
+ if (!init_)
+ return false;
+ return (INVALID_HANDLE_VALUE != platform_file_ ||
+ INVALID_HANDLE_VALUE != sync_platform_file_);
+}
+
+bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
+ DCHECK(init_);
+ if (buffer_len > ULONG_MAX || offset > LONG_MAX)
+ return false;
+
+ DWORD ret = SetFilePointer(sync_platform_file_, static_cast<LONG>(offset),
+ NULL, FILE_BEGIN);
+ if (INVALID_SET_FILE_POINTER == ret)
+ return false;
+
+ DWORD actual;
+ DWORD size = static_cast<DWORD>(buffer_len);
+ if (!ReadFile(sync_platform_file_, buffer, size, &actual, NULL))
+ return false;
+ return actual == size;
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
+ DCHECK(init_);
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ DWORD ret = SetFilePointer(sync_platform_file_, static_cast<LONG>(offset),
+ NULL, FILE_BEGIN);
+ if (INVALID_SET_FILE_POINTER == ret)
+ return false;
+
+ DWORD actual;
+ DWORD size = static_cast<DWORD>(buffer_len);
+ if (!WriteFile(sync_platform_file_, buffer, size, &actual, NULL))
+ return false;
+ return actual == size;
+}
+
+// We have to increase the ref counter of the file before performing the IO to
+// prevent the completion to happen with an invalid handle (if the file is
+// closed while the IO is in flight).
+bool File::Read(void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ if (!callback) {
+ if (completed)
+ *completed = true;
+ return Read(buffer, buffer_len, offset);
+ }
+
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ MyOverlapped* data = new MyOverlapped(this, offset, callback);
+ DWORD size = static_cast<DWORD>(buffer_len);
+
+ DWORD actual;
+ if (!ReadFile(platform_file_, buffer, size, &actual, data->overlapped())) {
+ *completed = false;
+ if (GetLastError() == ERROR_IO_PENDING)
+ return true;
+ delete data;
+ return false;
+ }
+
+ // The operation completed already. We'll be called back anyway.
+ *completed = (actual == size);
+ DCHECK_EQ(size, actual);
+ data->callback_ = NULL;
+ data->file_ = NULL; // There is no reason to hold on to this anymore.
+ return *completed;
+}
+
+bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ if (!callback) {
+ if (completed)
+ *completed = true;
+ return Write(buffer, buffer_len, offset);
+ }
+
+ return AsyncWrite(buffer, buffer_len, offset, callback, completed);
+}
+
+bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
+ FileIOCallback* callback, bool* completed) {
+ DCHECK(init_);
+ DCHECK(callback);
+ DCHECK(completed);
+ if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
+ return false;
+
+ MyOverlapped* data = new MyOverlapped(this, offset, callback);
+ DWORD size = static_cast<DWORD>(buffer_len);
+
+ DWORD actual;
+ if (!WriteFile(platform_file_, buffer, size, &actual, data->overlapped())) {
+ *completed = false;
+ if (GetLastError() == ERROR_IO_PENDING)
+ return true;
+ delete data;
+ return false;
+ }
+
+ // The operation completed already. We'll be called back anyway.
+ *completed = (actual == size);
+ DCHECK_EQ(size, actual);
+ data->callback_ = NULL;
+ data->file_ = NULL; // There is no reason to hold on to this anymore.
+ return *completed;
+}
+
+bool File::SetLength(size_t length) {
+ DCHECK(init_);
+ if (length > ULONG_MAX)
+ return false;
+
+ DWORD size = static_cast<DWORD>(length);
+ HANDLE file = platform_file();
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(file, size, NULL, FILE_BEGIN))
+ return false;
+
+ return TRUE == SetEndOfFile(file);
+}
+
+size_t File::GetLength() {
+ DCHECK(init_);
+ LARGE_INTEGER size;
+ HANDLE file = platform_file();
+ if (!GetFileSizeEx(file, &size))
+ return 0;
+ if (size.HighPart)
+ return ULONG_MAX;
+
+ return static_cast<size_t>(size.LowPart);
+}
+
+// Static.
+void File::WaitForPendingIO(int* num_pending_io) {
+ while (*num_pending_io) {
+ // Asynchronous IO operations may be in flight and the completion may end
+ // up calling us back so let's wait for them.
+ base::MessageLoopForIO::IOHandler* handler = g_completion_handler.Pointer();
+ base::MessageLoopForIO::current()->WaitForIOCompletion(100, handler);
+ }
+}
+
+// Static.
+void File::DropPendingIO() {
+ // Nothing to do here.
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/flash_cache_test_base.cc b/chromium/net/disk_cache/flash/flash_cache_test_base.cc
new file mode 100644
index 00000000000..164eb338970
--- /dev/null
+++ b/chromium/net/disk_cache/flash/flash_cache_test_base.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/flash/flash_cache_test_base.h"
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/time/time.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/storage.h"
+
+FlashCacheTest::FlashCacheTest() {
+ int seed = static_cast<int>(base::Time::Now().ToInternalValue());
+ srand(seed);
+}
+
+FlashCacheTest::~FlashCacheTest() {
+}
+
+void FlashCacheTest::SetUp() {
+ const base::FilePath::StringType kCachePath = FILE_PATH_LITERAL("cache");
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ path_ = temp_dir_.path().Append(kCachePath);
+}
+
+void FlashCacheTest::TearDown() {
+}
diff --git a/chromium/net/disk_cache/flash/flash_cache_test_base.h b/chromium/net/disk_cache/flash/flash_cache_test_base.h
new file mode 100644
index 00000000000..eb082276171
--- /dev/null
+++ b/chromium/net/disk_cache/flash/flash_cache_test_base.h
@@ -0,0 +1,43 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_DISK_CACHE_FLASH_TEST_BASE_H_
+#define NET_DISK_CACHE_DISK_CACHE_FLASH_TEST_BASE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/disk_cache/flash/format.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int32 kNumTestSegments = 10;
+const int32 kStorageSize = kNumTestSegments * disk_cache::kFlashSegmentSize;
+
+} // namespace
+
+namespace disk_cache {
+
+class LogStore;
+
+} // namespace disk_cache
+
+class FlashCacheTest : public testing::Test {
+ protected:
+ FlashCacheTest();
+ virtual ~FlashCacheTest();
+
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ base::ScopedTempDir temp_dir_;
+ base::FilePath path_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FlashCacheTest);
+};
+
+#endif // NET_DISK_CACHE_DISK_CACHE_FLASH_TEST_BASE_H_
diff --git a/chromium/net/disk_cache/flash/flash_entry_impl.cc b/chromium/net/disk_cache/flash/flash_entry_impl.cc
new file mode 100644
index 00000000000..a0e785cae10
--- /dev/null
+++ b/chromium/net/disk_cache/flash/flash_entry_impl.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 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.
+
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/task_runner_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/flash/flash_entry_impl.h"
+#include "net/disk_cache/flash/internal_entry.h"
+
+namespace disk_cache {
+
+FlashEntryImpl::FlashEntryImpl(const std::string& key,
+ LogStore* store,
+ base::MessageLoopProxy* cache_thread)
+ : init_(false),
+ key_(key),
+ new_internal_entry_(new InternalEntry(key, store)),
+ cache_thread_(cache_thread) {
+ memset(stream_sizes_, 0, sizeof(stream_sizes_));
+}
+
+FlashEntryImpl::FlashEntryImpl(int32 id,
+ LogStore* store,
+ base::MessageLoopProxy* cache_thread)
+ : init_(false),
+ old_internal_entry_(new InternalEntry(id, store)),
+ cache_thread_(cache_thread) {
+}
+
+int FlashEntryImpl::Init(const CompletionCallback& callback) {
+ if (new_internal_entry_.get()) {
+ DCHECK(callback.is_null());
+ init_ = true;
+ return net::OK;
+ }
+ DCHECK(!callback.is_null() && old_internal_entry_.get());
+ callback_ = callback;
+ PostTaskAndReplyWithResult(cache_thread_.get(),
+ FROM_HERE,
+ Bind(&InternalEntry::Init, old_internal_entry_),
+ Bind(&FlashEntryImpl::OnInitComplete, this));
+ return net::ERR_IO_PENDING;
+}
+
+void FlashEntryImpl::Doom() {
+ DCHECK(init_);
+ NOTREACHED();
+}
+
+void FlashEntryImpl::Close() {
+ DCHECK(init_);
+ Release();
+}
+
+std::string FlashEntryImpl::GetKey() const {
+ DCHECK(init_);
+ return key_;
+}
+
+base::Time FlashEntryImpl::GetLastUsed() const {
+ DCHECK(init_);
+ NOTREACHED();
+ return base::Time::Now();
+}
+
+base::Time FlashEntryImpl::GetLastModified() const {
+ DCHECK(init_);
+ NOTREACHED();
+ return base::Time::Now();
+}
+
+int32 FlashEntryImpl::GetDataSize(int index) const {
+ DCHECK(init_);
+ return new_internal_entry_->GetDataSize(index);
+}
+
+int FlashEntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(init_);
+ return new_internal_entry_->ReadData(index, offset, buf, buf_len, callback);
+}
+
+int FlashEntryImpl::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ DCHECK(init_);
+ return new_internal_entry_->WriteData(index, offset, buf, buf_len, callback);
+}
+
+int FlashEntryImpl::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(init_);
+ NOTREACHED();
+ return net::ERR_FAILED;
+}
+
+int FlashEntryImpl::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(init_);
+ NOTREACHED();
+ return net::ERR_FAILED;
+}
+
+int FlashEntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) {
+ DCHECK(init_);
+ NOTREACHED();
+ return net::ERR_FAILED;
+}
+
+bool FlashEntryImpl::CouldBeSparse() const {
+ DCHECK(init_);
+ NOTREACHED();
+ return false;
+}
+
+void FlashEntryImpl::CancelSparseIO() {
+ DCHECK(init_);
+ NOTREACHED();
+}
+
+int FlashEntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
+ DCHECK(init_);
+ NOTREACHED();
+ return net::ERR_FAILED;
+}
+
+void FlashEntryImpl::OnInitComplete(
+ scoped_ptr<KeyAndStreamSizes> key_and_stream_sizes) {
+ DCHECK(!callback_.is_null());
+ if (!key_and_stream_sizes) {
+ callback_.Run(net::ERR_FAILED);
+ } else {
+ key_ = key_and_stream_sizes->key;
+ memcpy(stream_sizes_, key_and_stream_sizes->stream_sizes,
+ sizeof(stream_sizes_));
+ init_ = true;
+ callback_.Run(net::OK);
+ }
+}
+
+FlashEntryImpl::~FlashEntryImpl() {
+ cache_thread_->PostTask(FROM_HERE,
+ Bind(&InternalEntry::Close, new_internal_entry_));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/flash_entry_impl.h b/chromium/net/disk_cache/flash/flash_entry_impl.h
new file mode 100644
index 00000000000..32f489f899d
--- /dev/null
+++ b/chromium/net/disk_cache/flash/flash_entry_impl.h
@@ -0,0 +1,98 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_ENTRY_IMPL_H_
+#define NET_DISK_CACHE_FLASH_ENTRY_IMPL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/flash/internal_entry.h"
+
+namespace base {
+
+class MessageLoopProxy;
+
+} // namespace base
+
+namespace disk_cache {
+
+class InternalEntry;
+class IOBuffer;
+class LogStore;
+
+// We use split objects to minimize the context switches between the main thread
+// and the cache thread in the most common case of creating a new entry.
+//
+// All calls on a new entry are served synchronously. When an object is
+// destructed (via final Close() call), a message is posted to the cache thread
+// to save the object to storage.
+//
+// When an entry is not new, every asynchronous call is posted to the cache
+// thread, just as before; synchronous calls like GetKey() and GetDataSize() are
+// served from the main thread.
+class NET_EXPORT_PRIVATE FlashEntryImpl
+ : public Entry,
+ public base::RefCountedThreadSafe<FlashEntryImpl> {
+ friend class base::RefCountedThreadSafe<FlashEntryImpl>;
+ public:
+ FlashEntryImpl(const std::string& key,
+ LogStore* store,
+ base::MessageLoopProxy* cache_thread);
+ FlashEntryImpl(int32 id,
+ LogStore* store,
+ base::MessageLoopProxy* cache_thread);
+
+ int Init(const CompletionCallback& callback);
+
+ // disk_cache::Entry interface.
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ void OnInitComplete(scoped_ptr<KeyAndStreamSizes> key_and_stream_sizes);
+ virtual ~FlashEntryImpl();
+
+ bool init_;
+ std::string key_;
+ int stream_sizes_[kFlashLogStoreEntryNumStreams];
+
+ // Used if |this| is an newly created entry.
+ scoped_refptr<InternalEntry> new_internal_entry_;
+
+ // Used if |this| is an existing entry.
+ scoped_refptr<InternalEntry> old_internal_entry_;
+
+ // Copy of the callback for asynchronous calls on |old_internal_entry_|.
+ CompletionCallback callback_;
+
+ scoped_refptr<base::MessageLoopProxy> cache_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlashEntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_ENTRY_IMPL_H_
diff --git a/chromium/net/disk_cache/flash/format.h b/chromium/net/disk_cache/flash/format.h
new file mode 100644
index 00000000000..872d96b34a2
--- /dev/null
+++ b/chromium/net/disk_cache/flash/format.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 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_DISK_CACHE_FLASH_FORMAT_H_
+#define NET_DISK_CACHE_FLASH_FORMAT_H_
+
+namespace disk_cache {
+
+// Storage constants.
+const int32 kFlashPageSize = 8 * 1024;
+const int32 kFlashBlockSize = 512 * kFlashPageSize;
+
+// Segment constants.
+const int32 kFlashSegmentSize = 4 * 1024 * 1024;
+const int32 kFlashSmallEntrySize = 4 * 1024;
+const size_t kFlashMaxEntryCount = kFlashSegmentSize / kFlashSmallEntrySize - 1;
+
+// Segment summary consists of a fixed region at the end of the segment
+// containing a counter specifying the number of saved offsets followed by the
+// offsets.
+const int32 kFlashSummarySize = (1 + kFlashMaxEntryCount) * sizeof(int32);
+const int32 kFlashSegmentFreeSpace = kFlashSegmentSize - kFlashSummarySize;
+
+// An entry consists of a fixed number of streams.
+const int32 kFlashLogStoreEntryNumStreams = 4;
+const int32 kFlashLogStoreEntryHeaderSize =
+ kFlashLogStoreEntryNumStreams * sizeof(int32);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_FORMAT_H_
diff --git a/chromium/net/disk_cache/flash/internal_entry.cc b/chromium/net/disk_cache/flash/internal_entry.cc
new file mode 100644
index 00000000000..c6bae8926aa
--- /dev/null
+++ b/chromium/net/disk_cache/flash/internal_entry.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/flash/internal_entry.h"
+
+#include "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/log_store_entry.h"
+
+using net::IOBuffer;
+using net::StringIOBuffer;
+using net::CompletionCallback;
+
+namespace disk_cache {
+
+KeyAndStreamSizes::KeyAndStreamSizes() {
+}
+
+InternalEntry::InternalEntry(const std::string& key, LogStore* store)
+ : store_(store),
+ entry_(new LogStoreEntry(store_)) {
+ entry_->Init();
+ WriteKey(entry_.get(), key);
+}
+
+InternalEntry::InternalEntry(int32 id, LogStore* store)
+ : store_(store),
+ entry_(new LogStoreEntry(store_, id)) {
+}
+
+InternalEntry::~InternalEntry() {
+}
+
+scoped_ptr<KeyAndStreamSizes> InternalEntry::Init() {
+ scoped_ptr<KeyAndStreamSizes> null;
+ if (entry_->IsNew())
+ return null.Pass();
+ if (!entry_->Init())
+ return null.Pass();
+
+ scoped_ptr<KeyAndStreamSizes> rv(new KeyAndStreamSizes);
+ if (!ReadKey(entry_.get(), &rv->key))
+ return null.Pass();
+ for (int i = 0; i < kFlashLogStoreEntryNumStreams; ++i)
+ rv->stream_sizes[i] = entry_->GetDataSize(i+1);
+ return rv.Pass();
+}
+
+int32 InternalEntry::GetDataSize(int index) const {
+ return entry_->GetDataSize(++index);
+}
+
+int InternalEntry::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return entry_->ReadData(++index, offset, buf, buf_len);
+}
+
+int InternalEntry::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return entry_->WriteData(++index, offset, buf, buf_len);
+}
+
+void InternalEntry::Close() {
+ entry_->Close();
+}
+
+bool InternalEntry::WriteKey(LogStoreEntry* entry, const std::string& key) {
+ int key_size = static_cast<int>(key.size());
+ scoped_refptr<IOBuffer> key_buf(new StringIOBuffer(key));
+ return entry->WriteData(0, 0, key_buf.get(), key_size) == key_size;
+}
+
+bool InternalEntry::ReadKey(LogStoreEntry* entry, std::string* key) {
+ int key_size = entry->GetDataSize(0);
+ scoped_refptr<net::IOBuffer> key_buf(new net::IOBuffer(key_size));
+ if (entry->ReadData(0, 0, key_buf.get(), key_size) != key_size)
+ return false;
+ key->assign(key_buf->data(), key_size);
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/internal_entry.h b/chromium/net/disk_cache/flash/internal_entry.h
new file mode 100644
index 00000000000..eeb2793f4c1
--- /dev/null
+++ b/chromium/net/disk_cache/flash/internal_entry.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_INTERNAL_ENTRY_H_
+#define NET_DISK_CACHE_FLASH_INTERNAL_ENTRY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/flash/format.h"
+
+namespace net {
+
+class IOBuffer;
+
+} // namespace net
+
+namespace disk_cache {
+
+struct KeyAndStreamSizes {
+ KeyAndStreamSizes();
+ std::string key;
+ int stream_sizes[kFlashLogStoreEntryNumStreams];
+};
+
+class LogStore;
+class LogStoreEntry;
+
+// Actual entry implementation that does all the work of reading, writing and
+// storing data.
+class NET_EXPORT_PRIVATE InternalEntry
+ : public base::RefCountedThreadSafe<InternalEntry> {
+ friend class base::RefCountedThreadSafe<InternalEntry>;
+ public:
+ InternalEntry(const std::string& key, LogStore* store);
+ InternalEntry(int32 id, LogStore* store);
+
+ scoped_ptr<KeyAndStreamSizes> Init();
+ int32 GetDataSize(int index) const;
+ int ReadData(int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback);
+ int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback);
+ void Close();
+
+ private:
+ bool WriteKey(LogStoreEntry* entry, const std::string& key);
+ bool ReadKey(LogStoreEntry* entry, std::string* key);
+ ~InternalEntry();
+
+ LogStore* store_;
+ scoped_ptr<LogStoreEntry> entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(InternalEntry);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_INTERNAL_ENTRY_H_
diff --git a/chromium/net/disk_cache/flash/log_store.cc b/chromium/net/disk_cache/flash/log_store.cc
new file mode 100644
index 00000000000..a2a82827027
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/segment.h"
+#include "net/disk_cache/flash/storage.h"
+
+namespace disk_cache {
+
+LogStore::LogStore(const base::FilePath& path, int32 size)
+ : storage_(path, size),
+ num_segments_(size / kFlashSegmentSize),
+ open_segments_(num_segments_),
+ write_index_(0),
+ current_entry_id_(-1),
+ current_entry_num_bytes_left_to_write_(0),
+ init_(false),
+ closed_(false) {
+ DCHECK(size % kFlashSegmentSize == 0);
+}
+
+LogStore::~LogStore() {
+ DCHECK(!init_ || closed_);
+ STLDeleteElements(&open_segments_);
+}
+
+bool LogStore::Init() {
+ DCHECK(!init_);
+ if (!storage_.Init())
+ return false;
+
+ // TODO(agayev): Once we start persisting segment metadata to disk, we will
+ // start from where we left off during the last shutdown.
+ scoped_ptr<Segment> segment(new Segment(write_index_, false, &storage_));
+ if (!segment->Init())
+ return false;
+
+ segment->AddUser();
+ open_segments_[write_index_] = segment.release();
+ init_ = true;
+ return true;
+}
+
+bool LogStore::Close() {
+ DCHECK(init_ && !closed_);
+ open_segments_[write_index_]->ReleaseUser();
+ if (!open_segments_[write_index_]->Close())
+ return false;
+ closed_ = true;
+ return true;
+ // TODO(agayev): persist metadata to disk.
+}
+
+bool LogStore::CreateEntry(int32 size, int32* id) {
+ DCHECK(init_ && !closed_);
+ DCHECK(current_entry_id_ == -1 && size <= disk_cache::kFlashSegmentFreeSpace);
+
+ // TODO(agayev): Avoid large entries from leaving the segments almost empty.
+ if (!open_segments_[write_index_]->CanHold(size)) {
+ if (!open_segments_[write_index_]->Close())
+ return false;
+
+ open_segments_[write_index_]->ReleaseUser();
+ if (open_segments_[write_index_]->HasNoUsers()) {
+ delete open_segments_[write_index_];
+ open_segments_[write_index_] = NULL;
+ }
+
+ write_index_ = GetNextSegmentIndex();
+ scoped_ptr<Segment> segment(new Segment(write_index_, false, &storage_));
+ if (!segment->Init())
+ return false;
+
+ segment->AddUser();
+ open_segments_[write_index_] = segment.release();
+ }
+
+ *id = open_segments_[write_index_]->write_offset();
+ open_segments_[write_index_]->StoreOffset(*id);
+ current_entry_id_ = *id;
+ current_entry_num_bytes_left_to_write_ = size;
+ open_entries_.insert(current_entry_id_);
+ return true;
+}
+
+void LogStore::DeleteEntry(int32 id, int32 size) {
+ DCHECK(init_ && !closed_);
+ DCHECK(open_entries_.find(id) == open_entries_.end());
+ // TODO(agayev): Increment the number of dead bytes in the segment metadata
+ // for the segment identified by |index|.
+}
+
+bool LogStore::WriteData(const void* buffer, int32 size) {
+ DCHECK(init_ && !closed_);
+ DCHECK(current_entry_id_ != -1 &&
+ size <= current_entry_num_bytes_left_to_write_);
+ if (open_segments_[write_index_]->WriteData(buffer, size)) {
+ current_entry_num_bytes_left_to_write_ -= size;
+ return true;
+ }
+ return false;
+}
+
+bool LogStore::OpenEntry(int32 id) {
+ DCHECK(init_ && !closed_);
+ if (open_entries_.find(id) != open_entries_.end())
+ return false;
+
+ // Segment is already open.
+ int32 index = id / disk_cache::kFlashSegmentSize;
+ if (open_segments_[index]) {
+ if (!open_segments_[index]->HaveOffset(id))
+ return false;
+ open_segments_[index]->AddUser();
+ open_entries_.insert(id);
+ return true;
+ }
+
+ // Segment is not open.
+ scoped_ptr<Segment> segment(new Segment(index, true, &storage_));
+ if (!segment->Init() || !segment->HaveOffset(id))
+ return false;
+
+ segment->AddUser();
+ open_segments_[index] = segment.release();
+ open_entries_.insert(id);
+ return true;
+}
+
+bool LogStore::ReadData(int32 id, void* buffer, int32 size,
+ int32 offset) const {
+ DCHECK(init_ && !closed_);
+ DCHECK(open_entries_.find(id) != open_entries_.end());
+
+ int32 index = id / disk_cache::kFlashSegmentSize;
+ DCHECK(open_segments_[index] && open_segments_[index]->HaveOffset(id));
+ return open_segments_[index]->ReadData(buffer, size, id + offset);
+}
+
+void LogStore::CloseEntry(int32 id) {
+ DCHECK(init_ && !closed_);
+ std::set<int32>::iterator entry_iter = open_entries_.find(id);
+ DCHECK(entry_iter != open_entries_.end());
+
+ if (current_entry_id_ != -1) {
+ DCHECK(id == current_entry_id_ && !current_entry_num_bytes_left_to_write_);
+ open_entries_.erase(entry_iter);
+ current_entry_id_ = -1;
+ return;
+ }
+
+ int32 index = id / disk_cache::kFlashSegmentSize;
+ DCHECK(open_segments_[index]);
+ open_entries_.erase(entry_iter);
+
+ open_segments_[index]->ReleaseUser();
+ if (open_segments_[index]->HasNoUsers()) {
+ delete open_segments_[index];
+ open_segments_[index] = NULL;
+ }
+}
+
+int32 LogStore::GetNextSegmentIndex() {
+ DCHECK(init_ && !closed_);
+ int32 next_index = (write_index_ + 1) % num_segments_;
+
+ while (InUse(next_index)) {
+ next_index = (next_index + 1) % num_segments_;
+ DCHECK_NE(next_index, write_index_);
+ }
+ return next_index;
+}
+
+bool LogStore::InUse(int32 index) const {
+ DCHECK(init_ && !closed_);
+ DCHECK(index >= 0 && index < num_segments_);
+ return open_segments_[index] != NULL;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/log_store.h b/chromium/net/disk_cache/flash/log_store.h
new file mode 100644
index 00000000000..e53b83e1625
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store.h
@@ -0,0 +1,101 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_LOG_STORE_H_
+#define NET_DISK_CACHE_FLASH_LOG_STORE_H_
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/flash/storage.h"
+
+namespace disk_cache {
+
+class Segment;
+
+// This class implements a general purpose store for storing and retrieving
+// entries consisting of arbitrary binary data. The store has log semantics,
+// i.e. it's not possible to overwrite data in place. In order to update an
+// entry, a new version must be written. Only one entry can be written to at
+// any given time, while concurrent reading of multiple entries is supported.
+class NET_EXPORT_PRIVATE LogStore {
+ public:
+ LogStore(const base::FilePath& path, int32 size);
+ ~LogStore();
+
+ // Performs initialization. Must be the first function called and further
+ // calls should be made only if it is successful.
+ bool Init();
+
+ // Closes the store. Should be the last function called before destruction.
+ bool Close();
+
+ // Creates an entry of |size| bytes. The id of the created entry is stored in
+ // |entry_id|.
+ bool CreateEntry(int32 size, int32* entry_id);
+
+ // Deletes |entry_id|; the client should keep track of |size| and provide it
+ // here. Only inactive (i.e. not currently open or being created) entries can
+ // be deleted.
+ void DeleteEntry(int32 entry_id, int32 size);
+
+ // Appends data to the end of the last created entry.
+ bool WriteData(const void* buffer, int32 size);
+
+ // Opens an entry with id |entry_id|.
+ bool OpenEntry(int32 entry_id);
+
+ // Reads |size| bytes starting from |offset| into |buffer|, where |offset| is
+ // relative to the entry's content, from an entry identified by |entry_id|.
+ bool ReadData(int32 entry_id, void* buffer, int32 size, int32 offset) const;
+
+ // Closes an entry that was either opened with OpenEntry or created with
+ // CreateEntry.
+ void CloseEntry(int32 id);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FlashCacheTest, LogStoreReadFromClosedSegment);
+ FRIEND_TEST_ALL_PREFIXES(FlashCacheTest, LogStoreSegmentSelectionIsFifo);
+ FRIEND_TEST_ALL_PREFIXES(FlashCacheTest, LogStoreInUseSegmentIsSkipped);
+ FRIEND_TEST_ALL_PREFIXES(FlashCacheTest, LogStoreReadFromCurrentAfterClose);
+
+ int32 GetNextSegmentIndex();
+ bool InUse(int32 segment_index) const;
+
+ Storage storage_;
+
+ int32 num_segments_;
+
+ // Currently open segments, either for reading or writing. There can only be
+ // one segment open for writing, and multiple open for reading.
+ std::vector<Segment*> open_segments_;
+
+ // The index of the segment currently being written to. It's an index to
+ // |open_segments_| vector.
+ int32 write_index_;
+
+ // Ids of entries currently open, either CreatEntry'ed or OpenEntry'ed.
+ std::set<int32> open_entries_;
+
+ // Id of the entry that is currently being written to, -1 if there is no entry
+ // currently being written to.
+ int32 current_entry_id_;
+
+ // Number of bytes left to be written to the entry identified by
+ // |current_entry_id_|. Its value makes sense iff |current_entry_id_| is not
+ // -1.
+ int32 current_entry_num_bytes_left_to_write_;
+
+ bool init_; // Init was called.
+ bool closed_; // Close was called.
+
+ DISALLOW_COPY_AND_ASSIGN(LogStore);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_LOG_STORE_H_
diff --git a/chromium/net/disk_cache/flash/log_store_entry.cc b/chromium/net/disk_cache/flash/log_store_entry.cc
new file mode 100644
index 00000000000..1e26ec54461
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store_entry.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/log_store_entry.h"
+
+namespace disk_cache {
+
+LogStoreEntry::LogStoreEntry(LogStore* store)
+ : store_(store), id_(-1), init_(false), closed_(false), deleted_(false) {
+ DCHECK(store);
+}
+
+LogStoreEntry::LogStoreEntry(LogStore* store, int32 id)
+ : store_(store), id_(id), init_(false), closed_(false), deleted_(false) {
+ DCHECK(store);
+}
+
+LogStoreEntry::~LogStoreEntry() {
+ DCHECK(!init_ || closed_);
+}
+
+bool LogStoreEntry::Init() {
+ DCHECK(!init_);
+ if (IsNew()) {
+ init_ = true;
+ return true;
+ }
+
+ int32 stream_sizes[kFlashLogStoreEntryNumStreams];
+ COMPILE_ASSERT(sizeof(stream_sizes) == kFlashLogStoreEntryHeaderSize,
+ invalid_log_store_entry_header_size);
+
+ if (!store_->OpenEntry(id_) ||
+ !store_->ReadData(id_, stream_sizes, kFlashLogStoreEntryHeaderSize, 0)) {
+ return false;
+ }
+ for (int i = 0, offset = kFlashLogStoreEntryHeaderSize;
+ i < kFlashLogStoreEntryNumStreams; ++i) {
+ streams_[i].offset = offset;
+ streams_[i].size = stream_sizes[i];
+ offset += stream_sizes[i];
+ }
+ init_ = true;
+ return true;
+}
+
+bool LogStoreEntry::Close() {
+ DCHECK(init_ && !closed_);
+
+ if (IsNew()) {
+ closed_ = deleted_ ? true : Save();
+ } else {
+ store_->CloseEntry(id_);
+ if (deleted_)
+ store_->DeleteEntry(id_, Size());
+ closed_ = true;
+ }
+ return closed_;
+}
+
+int32 LogStoreEntry::id() const {
+ DCHECK(init_);
+ return id_;
+}
+
+int32 LogStoreEntry::GetDataSize(int index) const {
+ DCHECK(init_);
+ return InvalidStream(index) ? 0 : streams_[index].size;
+}
+
+int LogStoreEntry::ReadData(int index, int offset, net::IOBuffer* buf,
+ int buf_len) {
+ DCHECK(init_);
+ if (InvalidStream(index))
+ return net::ERR_INVALID_ARGUMENT;
+
+ int stream_size = streams_[index].size;
+ if (offset >= stream_size || offset < 0 || buf_len == 0)
+ return 0;
+ if (offset + buf_len > stream_size)
+ buf_len = stream_size - offset;
+
+ if (!IsNew()) {
+ offset += streams_[index].offset;
+ if (store_->ReadData(id_, buf->data(), buf_len, offset))
+ return buf_len;
+ return net::ERR_FAILED;
+ }
+ memcpy(buf->data(), &streams_[index].write_buffer[offset], buf_len);
+ return buf_len;
+}
+
+int LogStoreEntry::WriteData(int index, int offset, net::IOBuffer* buf,
+ int buf_len) {
+ DCHECK(init_ && !closed_);
+ if (InvalidStream(index))
+ return net::ERR_INVALID_ARGUMENT;
+
+ DCHECK(offset >= 0 && buf_len >= 0);
+ Stream& stream = streams_[index];
+ size_t new_size = static_cast<size_t>(offset + buf_len);
+ if (new_size) {
+ // TODO(agayev): Currently, only append and overwrite is supported. Add
+ // support for arbitrary writes.
+ DCHECK(!offset || offset == stream.size);
+ if (stream.write_buffer.size() < new_size)
+ stream.write_buffer.resize(new_size);
+ memcpy(&streams_[index].write_buffer[offset], buf->data(), buf_len);
+ }
+ stream.size = new_size;
+ return buf_len;
+}
+
+void LogStoreEntry::Delete() {
+ DCHECK(init_ && !closed_);
+ deleted_ = true;
+}
+
+bool LogStoreEntry::IsNew() const {
+ return id_ == -1;
+}
+
+bool LogStoreEntry::InvalidStream(int stream_index) const {
+ return stream_index < 0 || stream_index >= kFlashLogStoreEntryNumStreams;
+}
+
+int32 LogStoreEntry::Size() const {
+ DCHECK(init_);
+ int32 size = kFlashLogStoreEntryHeaderSize;
+ for (int i = 0; i < kFlashLogStoreEntryNumStreams; ++i)
+ size += streams_[i].size;
+ DCHECK(size > 0 && size <= kFlashSegmentFreeSpace);
+ return size;
+}
+
+bool LogStoreEntry::Save() {
+ DCHECK(init_ && !closed_ && !deleted_ && IsNew());
+ int32 stream_sizes[kFlashLogStoreEntryNumStreams];
+ COMPILE_ASSERT(sizeof(stream_sizes) == kFlashLogStoreEntryHeaderSize,
+ invalid_log_store_entry_header_size);
+
+ for (int i = 0; i < kFlashLogStoreEntryNumStreams; ++i)
+ stream_sizes[i] = streams_[i].size;
+
+ if (!store_->CreateEntry(Size(), &id_))
+ return false;
+ if (!store_->WriteData(stream_sizes, kFlashLogStoreEntryHeaderSize))
+ return false;
+ for (int i = 0; i < kFlashLogStoreEntryNumStreams; ++i) {
+ if (streams_[i].size > 0 &&
+ !store_->WriteData(&streams_[i].write_buffer[0], streams_[i].size)) {
+ return false;
+ }
+ }
+ store_->CloseEntry(id_);
+ return true;
+}
+
+LogStoreEntry::Stream::Stream() : offset(0), size(0) {
+}
+
+LogStoreEntry::Stream::~Stream() {
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/log_store_entry.h b/chromium/net/disk_cache/flash/log_store_entry.h
new file mode 100644
index 00000000000..579194913af
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store_entry.h
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_LOG_STORE_ENTRY_H_
+#define NET_DISK_CACHE_FLASH_LOG_STORE_ENTRY_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/flash/format.h"
+
+namespace net {
+class IOBuffer;
+};
+
+namespace disk_cache {
+
+class LogStore;
+
+class NET_EXPORT_PRIVATE LogStoreEntry {
+ public:
+ explicit LogStoreEntry(LogStore* store);
+ LogStoreEntry(LogStore* store, int32 id);
+ ~LogStoreEntry();
+
+ bool Init();
+ bool Close();
+
+ int32 id() const;
+ bool IsNew() const;
+ int32 GetDataSize(int index) const;
+
+ int ReadData(int index, int offset, net::IOBuffer* buf, int buf_len);
+ int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len);
+ void Delete();
+
+ private:
+ struct Stream {
+ Stream();
+ ~Stream();
+ int offset;
+ int size;
+ std::vector<char> write_buffer;
+ };
+
+ bool InvalidStream(int stream_index) const;
+ int32 Size() const;
+ bool Save();
+
+ LogStore* store_;
+ int32 id_;
+ Stream streams_[kFlashLogStoreEntryNumStreams];
+ bool init_;
+ bool closed_;
+ bool deleted_;
+
+ DISALLOW_COPY_AND_ASSIGN(LogStoreEntry);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_LOG_STORE_ENTRY_H_
diff --git a/chromium/net/disk_cache/flash/log_store_entry_unittest.cc b/chromium/net/disk_cache/flash/log_store_entry_unittest.cc
new file mode 100644
index 00000000000..100c9a83ea0
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store_entry_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/flash/flash_cache_test_base.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/log_store_entry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using disk_cache::LogStoreEntry;
+
+// Tests the behavior of a LogStoreEntry with empty streams.
+TEST_F(FlashCacheTest, LogStoreEntryEmpty) {
+ disk_cache::LogStore log_store(path_, kStorageSize);
+ ASSERT_TRUE(log_store.Init());
+
+ scoped_ptr<LogStoreEntry> entry(new LogStoreEntry(&log_store));
+ EXPECT_TRUE(entry->Init());
+ EXPECT_TRUE(entry->Close());
+
+ entry.reset(new LogStoreEntry(&log_store, entry->id()));
+ EXPECT_TRUE(entry->Init());
+
+ for (int i = 0; i < disk_cache::kFlashLogStoreEntryNumStreams; ++i) {
+ const int kSize = 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ EXPECT_EQ(0, entry->GetDataSize(i));
+ EXPECT_EQ(0, entry->ReadData(i, 0, buf.get(), kSize));
+ }
+ EXPECT_TRUE(entry->Close());
+ ASSERT_TRUE(log_store.Close());
+}
+
+TEST_F(FlashCacheTest, LogStoreEntryWriteRead) {
+ disk_cache::LogStore log_store(path_, kStorageSize);
+ ASSERT_TRUE(log_store.Init());
+
+ scoped_ptr<LogStoreEntry> entry(new LogStoreEntry(&log_store));
+ EXPECT_TRUE(entry->Init());
+
+ int sizes[disk_cache::kFlashLogStoreEntryNumStreams] = {333, 444, 555, 666};
+ scoped_refptr<net::IOBuffer> buffers[
+ disk_cache::kFlashLogStoreEntryNumStreams];
+
+ for (int i = 0; i < disk_cache::kFlashLogStoreEntryNumStreams; ++i) {
+ buffers[i] = new net::IOBuffer(sizes[i]);
+ CacheTestFillBuffer(buffers[i]->data(), sizes[i], false);
+ EXPECT_EQ(sizes[i], entry->WriteData(i, 0, buffers[i].get(), sizes[i]));
+ }
+ EXPECT_TRUE(entry->Close());
+
+ int32 id = entry->id();
+ entry.reset(new LogStoreEntry(&log_store, id));
+ EXPECT_TRUE(entry->Init());
+
+ for (int i = 0; i < disk_cache::kFlashLogStoreEntryNumStreams; ++i) {
+ EXPECT_EQ(sizes[i], entry->GetDataSize(i));
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(sizes[i]));
+ EXPECT_EQ(sizes[i], entry->ReadData(i, 0, buffer.get(), sizes[i]));
+ EXPECT_EQ(0, memcmp(buffers[i]->data(), buffer->data(), sizes[i]));
+ }
+ EXPECT_TRUE(entry->Close());
+ EXPECT_EQ(id, entry->id());
+ ASSERT_TRUE(log_store.Close());
+}
diff --git a/chromium/net/disk_cache/flash/log_store_unittest.cc b/chromium/net/disk_cache/flash/log_store_unittest.cc
new file mode 100644
index 00000000000..2678316d499
--- /dev/null
+++ b/chromium/net/disk_cache/flash/log_store_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/flash/flash_cache_test_base.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/log_store.h"
+#include "net/disk_cache/flash/segment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace disk_cache {
+
+TEST_F(FlashCacheTest, LogStoreCreateEntry) {
+ LogStore log_store(path_, kStorageSize);
+ EXPECT_TRUE(log_store.Init());
+
+ const int32 kSize = 100;
+ const std::string buf(kSize, 0);
+
+ int32 id;
+ EXPECT_TRUE(log_store.CreateEntry(kSize, &id));
+ EXPECT_TRUE(log_store.WriteData(buf.data(), kSize/2));
+ EXPECT_TRUE(log_store.WriteData(buf.data(), kSize/2));
+ log_store.CloseEntry(id);
+
+ EXPECT_TRUE(log_store.Close());
+}
+
+// Also tests reading from current segment.
+TEST_F(FlashCacheTest, LogStoreOpenEntry) {
+ LogStore log_store(path_, kStorageSize);
+ EXPECT_TRUE(log_store.Init());
+
+ const int32 kSize = 100;
+ const std::vector<char> expected(kSize, 'b');
+
+ int32 id;
+ EXPECT_TRUE(log_store.CreateEntry(kSize, &id));
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize));
+ log_store.CloseEntry(id);
+
+ EXPECT_TRUE(log_store.OpenEntry(id));
+ std::vector<char> actual(kSize, 0);
+ EXPECT_TRUE(log_store.ReadData(id, &actual[0], kSize, 0));
+ log_store.CloseEntry(id);
+
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(log_store.Close());
+}
+
+// Also tests that writing advances segments.
+TEST_F(FlashCacheTest, LogStoreReadFromClosedSegment) {
+ LogStore log_store(path_, kStorageSize);
+ EXPECT_TRUE(log_store.Init());
+
+ const int32 kSize = disk_cache::kFlashSegmentFreeSpace;
+ const std::vector<char> expected(kSize, 'a');
+
+ // First two entries go to segment 0.
+ int32 id1;
+ EXPECT_EQ(0, log_store.write_index_);
+ EXPECT_TRUE(log_store.CreateEntry(kSize/2, &id1));
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize/2));
+ log_store.CloseEntry(id1);
+
+ int32 id2;
+ EXPECT_EQ(0, log_store.write_index_);
+ EXPECT_TRUE(log_store.CreateEntry(kSize/2, &id2));
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize/2));
+ log_store.CloseEntry(id2);
+
+ // This entry goes to segment 1.
+ int32 id3;
+ EXPECT_TRUE(log_store.CreateEntry(kSize, &id3));
+ EXPECT_EQ(1, log_store.write_index_);
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize));
+ log_store.CloseEntry(id3);
+
+ // We read from segment 0.
+ EXPECT_TRUE(log_store.OpenEntry(id1));
+ std::vector<char> actual(kSize, 0);
+ EXPECT_TRUE(log_store.ReadData(id1, &actual[0], kSize, id1));
+ log_store.CloseEntry(id1);
+
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(log_store.Close());
+}
+
+TEST_F(FlashCacheTest, LogStoreReadFromCurrentAfterClose) {
+ LogStore log_store(path_, kStorageSize);
+ EXPECT_TRUE(log_store.Init());
+
+ const int32 kSize = disk_cache::kFlashSegmentFreeSpace;
+ const std::vector<char> expected(kSize, 'a');
+
+ int32 id1;
+ EXPECT_EQ(0, log_store.write_index_);
+ EXPECT_TRUE(log_store.CreateEntry(kSize/2, &id1));
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize/2));
+ log_store.CloseEntry(id1);
+
+ // Create a reference to above entry.
+ EXPECT_TRUE(log_store.OpenEntry(id1));
+
+ // This entry fills the first segment.
+ int32 id2;
+ EXPECT_EQ(0, log_store.write_index_);
+ EXPECT_TRUE(log_store.CreateEntry(kSize/2, &id2));
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize/2));
+ log_store.CloseEntry(id2);
+
+ // Creating this entry forces closing of the first segment.
+ int32 id3;
+ EXPECT_TRUE(log_store.CreateEntry(kSize, &id3));
+ EXPECT_EQ(1, log_store.write_index_);
+ EXPECT_TRUE(log_store.WriteData(&expected[0], kSize));
+ log_store.CloseEntry(id3);
+
+ // Now attempt to read from the closed segment.
+ std::vector<char> actual(kSize, 0);
+ EXPECT_TRUE(log_store.ReadData(id1, &actual[0], kSize, id1));
+ log_store.CloseEntry(id1);
+
+ EXPECT_EQ(expected, actual);
+ EXPECT_TRUE(log_store.Close());
+}
+
+// TODO(agayev): Add a test that confirms that in-use segment is not selected as
+// the next write segment.
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/segment.cc b/chromium/net/disk_cache/flash/segment.cc
new file mode 100644
index 00000000000..3457497a22f
--- /dev/null
+++ b/chromium/net/disk_cache/flash/segment.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "net/disk_cache/flash/format.h"
+#include "net/disk_cache/flash/segment.h"
+#include "net/disk_cache/flash/storage.h"
+
+namespace disk_cache {
+
+Segment::Segment(int32 index, bool read_only, Storage* storage)
+ : index_(index),
+ num_users_(0),
+ read_only_(read_only),
+ init_(false),
+ storage_(storage),
+ offset_(index * kFlashSegmentSize),
+ summary_offset_(offset_ + kFlashSegmentSize - kFlashSummarySize),
+ write_offset_(offset_) {
+ DCHECK(storage);
+ DCHECK(storage->size() % kFlashSegmentSize == 0);
+}
+
+Segment::~Segment() {
+ DCHECK(!init_ || read_only_);
+ if (num_users_ != 0)
+ LOG(WARNING) << "Users exist, but we don't care? " << num_users_;
+}
+
+bool Segment::HaveOffset(int32 offset) const {
+ DCHECK(init_);
+ return std::binary_search(offsets_.begin(), offsets_.end(), offset);
+}
+
+void Segment::AddUser() {
+ DCHECK(init_);
+ ++num_users_;
+}
+
+void Segment::ReleaseUser() {
+ DCHECK(init_);
+ --num_users_;
+}
+
+bool Segment::HasNoUsers() const {
+ DCHECK(init_);
+ return num_users_ == 0;
+}
+
+bool Segment::Init() {
+ DCHECK(!init_);
+
+ if (offset_ < 0 || offset_ + kFlashSegmentSize > storage_->size())
+ return false;
+
+ if (!read_only_) {
+ init_ = true;
+ return true;
+ }
+
+ int32 summary[kFlashMaxEntryCount + 1];
+ if (!storage_->Read(summary, kFlashSummarySize, summary_offset_))
+ return false;
+
+ size_t entry_count = summary[0];
+ DCHECK_LE(entry_count, kFlashMaxEntryCount);
+
+ std::vector<int32> tmp(summary + 1, summary + 1 + entry_count);
+ offsets_.swap(tmp);
+ init_ = true;
+ return true;
+}
+
+bool Segment::WriteData(const void* buffer, int32 size) {
+ DCHECK(init_ && !read_only_);
+ DCHECK(write_offset_ + size <= summary_offset_);
+ if (!storage_->Write(buffer, size, write_offset_))
+ return false;
+ write_offset_ += size;
+ return true;
+}
+
+void Segment::StoreOffset(int32 offset) {
+ DCHECK(init_ && !read_only_);
+ DCHECK(offsets_.size() < kFlashMaxEntryCount);
+ offsets_.push_back(offset);
+}
+
+bool Segment::ReadData(void* buffer, int32 size, int32 offset) const {
+ DCHECK(init_);
+ DCHECK(offset >= offset_ && offset + size <= offset_ + kFlashSegmentSize);
+ return storage_->Read(buffer, size, offset);
+}
+
+bool Segment::Close() {
+ DCHECK(init_);
+ if (read_only_)
+ return true;
+
+ DCHECK(offsets_.size() <= kFlashMaxEntryCount);
+
+ int32 summary[kFlashMaxEntryCount + 1];
+ memset(summary, 0, kFlashSummarySize);
+ summary[0] = offsets_.size();
+ std::copy(offsets_.begin(), offsets_.end(), summary + 1);
+ if (!storage_->Write(summary, kFlashSummarySize, summary_offset_))
+ return false;
+
+ read_only_ = true;
+ return true;
+}
+
+bool Segment::CanHold(int32 size) const {
+ DCHECK(init_);
+ return offsets_.size() < kFlashMaxEntryCount &&
+ write_offset_ + size <= summary_offset_;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/segment.h b/chromium/net/disk_cache/flash/segment.h
new file mode 100644
index 00000000000..97551e2531d
--- /dev/null
+++ b/chromium/net/disk_cache/flash/segment.h
@@ -0,0 +1,118 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_SEGMENT_H_
+#define NET_DISK_CACHE_FLASH_SEGMENT_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+
+class Storage;
+
+// The underlying storage represented by Storage class, is divided into fixed
+// size logical segments, represented by this class. Since segment size is
+// fixed, the storage size should be a multiple of segment size. The picture
+// below describes the relation between storage and segments:
+//
+// |-----------+-----------+-----+-------------+-----------|
+// | segment 0 | segment 1 | ... | segment n-1 | segment n |
+// |-----------+-----------+-----+-------------+-----------|
+//
+// |-------------------------------------------------------|
+// | storage |
+// |-------------------------------------------------------|
+//
+// A segment is constructed by taking its index within the storage, a flag
+// indicating whether it is a read-only segment and a pointer to the storage on
+// which it resides. It provides an API for reading/writing entries residing on
+// it. Init() function must be called right after the construction of a segment
+// and one should proceed to calling other functions only if Init() has
+// succeeded. After a successful initialization, one may proceed to call
+// non-mutating functions; mutating functions can be called if the segment is
+// not read-only. Finally, Close() function must be called right before the
+// destruction. Calling Close() makes the segment immutable, which means
+// mutating functions cannot be called on the object after that.
+//
+// Segment can only be used as a log, i.e. all writes are laid out sequentially
+// on a segment. As a result, WriteData() function does not take an offset.
+// Current write offset can be learned by calling write_offset().
+//
+// Once the entries are written to the Segment and Close() called on it and the
+// object destroyed, we should later be able to instantiate a read-only Segment
+// object and recreate all the entries that were previously written to it. To
+// achieve this, a tiny region of Segment is used for its metadata and Segment
+// provides two calls for interacting with metadata: StoreOffset() and
+// GetOffsets(). The former can be used to store an offset that was returned by
+// write_offset() and the latter can be used to retrieve all the offsets that
+// were stored in the Segment. Before attempting to write an entry, the client
+// should call CanHold() to make sure that there is enough space in the segment.
+//
+// ReadData can be called over the range that was previously written with
+// WriteData. Reading from area that was not written will fail.
+
+class NET_EXPORT_PRIVATE Segment {
+ public:
+ // |index| is the index of this segment on |storage|. If the storage size is
+ // X and the segment size is Y, where X >> Y and X % Y == 0, then the valid
+ // values for the index are integers within the range [0, X/Y). Thus, if
+ // |index| is given value Z, then it covers bytes on storage starting at the
+ // offset Z*Y and ending at the offset Z*Y+Y-1.
+ Segment(int32 index, bool read_only, Storage* storage);
+ ~Segment();
+
+ int32 index() const { return index_; }
+ int32 write_offset() const { return write_offset_; }
+
+ bool HaveOffset(int32 offset) const;
+ std::vector<int32> GetOffsets() const { return offsets_; }
+
+ // Manage the number of users of this segment.
+ void AddUser();
+ void ReleaseUser();
+ bool HasNoUsers() const;
+
+ // Performs segment initialization. Must be the first function called on the
+ // segment and further calls should be made only if it is successful.
+ bool Init();
+
+ // Writes |size| bytes of data from |buffer| to segment, returns false if
+ // fails and true if succeeds. Can block for a long time.
+ bool WriteData(const void* buffer, int32 size);
+
+ // Reads |size| bytes of data living at |offset| into |buffer| returns true on
+ // success and false on failure.
+ bool ReadData(void* buffer, int32 size, int32 offset) const;
+
+ // Stores the offset in the metadata.
+ void StoreOffset(int32 offset);
+
+ // Closes the segment, returns true on success and false on failure. Closing
+ // a segment makes it immutable.
+ bool Close();
+
+ // Returns true if segment can accommodate an entry of |size| bytes.
+ bool CanHold(int32 size) const;
+
+ private:
+ int32 index_;
+ int32 num_users_;
+ bool read_only_; // Indicates whether the segment can be written to.
+ bool init_; // Indicates whether segment was initialized.
+ Storage* storage_; // Storage on which the segment resides.
+ const int32 offset_; // Offset of the segment on |storage_|.
+ const int32 summary_offset_; // Offset of the segment summary.
+ int32 write_offset_; // Current write offset.
+ std::vector<int32> offsets_;
+
+ DISALLOW_COPY_AND_ASSIGN(Segment);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_SEGMENT_H_
diff --git a/chromium/net/disk_cache/flash/segment_unittest.cc b/chromium/net/disk_cache/flash/segment_unittest.cc
new file mode 100644
index 00000000000..3f61701cbe0
--- /dev/null
+++ b/chromium/net/disk_cache/flash/segment_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 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.
+
+#include "base/memory/scoped_ptr.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/flash/segment.h"
+#include "net/disk_cache/flash/storage.h"
+#include "net/disk_cache/flash/flash_cache_test_base.h"
+#include "net/disk_cache/flash/format.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+template<int SIZE>
+struct Entry {
+ enum { size = SIZE };
+
+ Entry() { CacheTestFillBuffer(data, size, false); }
+
+ bool operator==(const Entry& rhs) const {
+ return std::equal(data, data + size, rhs.data);
+ }
+
+ char data[size];
+};
+
+const int32 kSmallEntrySize = 100;
+const int32 kLargeEntrySize = disk_cache::kFlashSegmentSize / 4;
+
+typedef Entry<kSmallEntrySize> SmallEntry;
+typedef Entry<kLargeEntrySize> LargeEntry;
+
+const int32 kSegmentFreeSpace = disk_cache::kFlashSegmentSize -
+ disk_cache::kFlashSummarySize;
+
+} // namespace
+
+TEST_F(FlashCacheTest, SegmentUserTracking) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ ASSERT_TRUE(storage.Init());
+
+ scoped_ptr<disk_cache::Segment> segment(
+ new disk_cache::Segment(0, false, &storage));
+ EXPECT_TRUE(segment->Init());
+
+ EXPECT_TRUE(segment->HasNoUsers());
+ segment->AddUser();
+ segment->AddUser();
+ EXPECT_FALSE(segment->HasNoUsers());
+
+ segment->ReleaseUser();
+ EXPECT_FALSE(segment->HasNoUsers());
+ segment->ReleaseUser();
+ EXPECT_TRUE(segment->HasNoUsers());
+
+ EXPECT_TRUE(segment->Close());
+}
+
+TEST_F(FlashCacheTest, SegmentCreateDestroy) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ ASSERT_TRUE(storage.Init());
+
+ int32 index = 0;
+ scoped_ptr<disk_cache::Segment> segment(
+ new disk_cache::Segment(index, false, &storage));
+ EXPECT_TRUE(segment->Init());
+ EXPECT_TRUE(segment->Close());
+
+ index = kNumTestSegments - 1;
+ segment.reset(new disk_cache::Segment(index, false, &storage));
+ EXPECT_TRUE(segment->Init());
+ EXPECT_TRUE(segment->Close());
+
+ int32 invalid_index = kNumTestSegments;
+ segment.reset(new disk_cache::Segment(invalid_index, false, &storage));
+ EXPECT_FALSE(segment->Init());
+
+ invalid_index = -1;
+ segment.reset(new disk_cache::Segment(invalid_index, false, &storage));
+ EXPECT_FALSE(segment->Init());
+}
+
+TEST_F(FlashCacheTest, SegmentWriteDataReadData) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ ASSERT_TRUE(storage.Init());
+
+ int32 index = rand() % kNumTestSegments;
+ scoped_ptr<disk_cache::Segment> segment(
+ new disk_cache::Segment(index, false, &storage));
+
+ EXPECT_TRUE(segment->Init());
+ SmallEntry entry1;
+ EXPECT_TRUE(segment->CanHold(entry1.size));
+ int32 offset = segment->write_offset();
+ EXPECT_TRUE(segment->WriteData(entry1.data, entry1.size));
+ segment->StoreOffset(offset);
+ EXPECT_TRUE(segment->Close());
+
+ segment.reset(new disk_cache::Segment(index, true, &storage));
+ EXPECT_TRUE(segment->Init());
+ SmallEntry entry2;
+ EXPECT_TRUE(segment->ReadData(entry2.data, entry2.size, offset));
+ EXPECT_EQ(entry1, entry2);
+ EXPECT_TRUE(segment->Close());
+}
+
+TEST_F(FlashCacheTest, SegmentFillWithSmallEntries) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ ASSERT_TRUE(storage.Init());
+
+ int32 index = rand() % kNumTestSegments;
+ scoped_ptr<disk_cache::Segment> segment(
+ new disk_cache::Segment(index, false, &storage));
+
+ EXPECT_TRUE(segment->Init());
+ SmallEntry entry;
+ int32 num_bytes_written = 0;
+ while (segment->CanHold(entry.size)) {
+ int32 offset = segment->write_offset();
+ EXPECT_TRUE(segment->WriteData(entry.data, entry.size));
+ segment->StoreOffset(offset);
+ num_bytes_written += entry.size;
+ }
+ int32 space_left = kSegmentFreeSpace - num_bytes_written;
+ EXPECT_GE(space_left, entry.size);
+ EXPECT_EQ(segment->GetOffsets().size(), disk_cache::kFlashMaxEntryCount);
+ EXPECT_TRUE(segment->Close());
+}
+
+TEST_F(FlashCacheTest, SegmentFillWithLargeEntries) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ ASSERT_TRUE(storage.Init());
+
+ int32 index = rand() % kNumTestSegments;
+ scoped_ptr<disk_cache::Segment> segment(
+ new disk_cache::Segment(index, false, &storage));
+
+ EXPECT_TRUE(segment->Init());
+ scoped_ptr<LargeEntry> entry(new LargeEntry);
+ int32 num_bytes_written = 0;
+ while (segment->CanHold(entry->size)) {
+ int32 offset = segment->write_offset();
+ EXPECT_TRUE(segment->WriteData(entry->data, entry->size));
+ segment->StoreOffset(offset);
+ num_bytes_written += entry->size;
+ }
+ int32 space_left = kSegmentFreeSpace - num_bytes_written;
+ EXPECT_LT(space_left, entry->size);
+ EXPECT_LT(segment->GetOffsets().size(), disk_cache::kFlashMaxEntryCount);
+ EXPECT_TRUE(segment->Close());
+}
diff --git a/chromium/net/disk_cache/flash/storage.cc b/chromium/net/disk_cache/flash/storage.cc
new file mode 100644
index 00000000000..c7136ef869b
--- /dev/null
+++ b/chromium/net/disk_cache/flash/storage.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/flash/storage.h"
+
+#include <fcntl.h>
+
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/flash/format.h"
+
+namespace disk_cache {
+
+Storage::Storage(const base::FilePath& path,
+ int32 size)
+ : path_(path), size_(size) {
+ COMPILE_ASSERT(kFlashPageSize % 2 == 0, invalid_page_size);
+ COMPILE_ASSERT(kFlashBlockSize % kFlashPageSize == 0, invalid_block_size);
+ DCHECK(size_ % kFlashBlockSize == 0);
+}
+
+bool Storage::Init() {
+ int flags = base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_OPEN_ALWAYS;
+
+ file_ = base::CreatePlatformFile(path_, flags, NULL, NULL);
+ if (file_ == base::kInvalidPlatformFileValue)
+ return false;
+
+ // TODO(agayev): if file already exists, do some validation and return either
+ // true or false based on the result.
+
+#if defined(OS_LINUX)
+ fallocate(file_, 0, 0, size_);
+#endif
+
+ return true;
+}
+
+Storage::~Storage() {
+ base::ClosePlatformFile(file_);
+}
+
+bool Storage::Read(void* buffer, int32 size, int32 offset) {
+ DCHECK(offset >= 0 && offset + size <= size_);
+
+ int rv = base::ReadPlatformFile(file_, offset, static_cast<char*>(buffer),
+ size);
+ return rv == size;
+}
+
+bool Storage::Write(const void* buffer, int32 size, int32 offset) {
+ DCHECK(offset >= 0 && offset + size <= size_);
+
+ int rv = base::WritePlatformFile(file_, offset,
+ static_cast<const char*>(buffer), size);
+ return rv == size;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/flash/storage.h b/chromium/net/disk_cache/flash/storage.h
new file mode 100644
index 00000000000..38c4a4e488b
--- /dev/null
+++ b/chromium/net/disk_cache/flash/storage.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_FLASH_STORAGE_H_
+#define NET_DISK_CACHE_FLASH_STORAGE_H_
+
+#include "base/basictypes.h"
+#include "base/platform_file.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+
+class NET_EXPORT_PRIVATE Storage {
+ public:
+ Storage(const base::FilePath& path, int32 size);
+ bool Init();
+ ~Storage();
+
+ int32 size() const { return size_; }
+
+ bool Read(void* buffer, int32 size, int32 offset);
+ bool Write(const void* buffer, int32 size, int32 offset);
+
+ private:
+ base::FilePath path_;
+ int32 size_;
+ base::PlatformFile file_;
+
+ DISALLOW_COPY_AND_ASSIGN(Storage);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_FLASH_STORAGE_H_
diff --git a/chromium/net/disk_cache/flash/storage_unittest.cc b/chromium/net/disk_cache/flash/storage_unittest.cc
new file mode 100644
index 00000000000..e8a1e3f18c8
--- /dev/null
+++ b/chromium/net/disk_cache/flash/storage_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/flash/flash_cache_test_base.h"
+#include "net/disk_cache/flash/storage.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int32 kSizes[] = {512, 1024, 4096, 133, 1333, 13333};
+const int32 kOffsets[] = {0, 1, 3333, 125, 12443, 4431};
+
+} // namespace
+
+TEST_F(FlashCacheTest, StorageReadWrite) {
+ disk_cache::Storage storage(path_, kStorageSize);
+ EXPECT_TRUE(storage.Init());
+
+ for (size_t i = 0; i < arraysize(kOffsets); ++i) {
+ int32 size = kSizes[i];
+ int32 offset = kOffsets[i];
+
+ scoped_refptr<net::IOBuffer> write_buffer(new net::IOBuffer(size));
+ scoped_refptr<net::IOBuffer> read_buffer(new net::IOBuffer(size));
+
+ CacheTestFillBuffer(write_buffer->data(), size, false);
+
+ bool rv = storage.Write(write_buffer->data(), size, offset);
+ EXPECT_TRUE(rv);
+
+ rv = storage.Read(read_buffer->data(), size, offset);
+ EXPECT_TRUE(rv);
+
+ EXPECT_EQ(0, memcmp(read_buffer->data(), write_buffer->data(), size));
+ }
+}
diff --git a/chromium/net/disk_cache/histogram_macros.h b/chromium/net/disk_cache/histogram_macros.h
new file mode 100644
index 00000000000..3d8011c27f6
--- /dev/null
+++ b/chromium/net/disk_cache/histogram_macros.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2011 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.
+
+// This file contains macros to simplify histogram reporting from the disk
+// cache. The main issue is that we want to have separate histograms for each
+// type of cache (regular vs. media, etc), without adding the complexity of
+// keeping track of a potentially large number of histogram objects that have to
+// survive the backend object that created them.
+
+#ifndef NET_DISK_CACHE_HISTOGRAM_MACROS_H_
+#define NET_DISK_CACHE_HISTOGRAM_MACROS_H_
+
+// -----------------------------------------------------------------------------
+
+// These histograms follow the definition of UMA_HISTOGRAMN_XXX except that
+// whenever the name changes (the experiment group changes), the histrogram
+// object is re-created.
+// Note: These macros are only run on one thread, so the declarations of
+// |counter| was made static (i.e., there will be no race for reinitialization).
+
+#define CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \
+ do { \
+ static base::HistogramBase* counter(NULL); \
+ if (!counter || name != counter->histogram_name()) \
+ counter = base::Histogram::FactoryGet( \
+ name, min, max, bucket_count, \
+ base::Histogram::kUmaTargetedHistogramFlag); \
+ counter->Add(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_COUNTS(name, sample) CACHE_HISTOGRAM_CUSTOM_COUNTS( \
+ name, sample, 1, 1000000, 50)
+
+#define CACHE_HISTOGRAM_COUNTS_10000(name, sample) \
+ CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 10000, 50)
+
+#define CACHE_HISTOGRAM_COUNTS_50000(name, sample) \
+ CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 50000000, 50)
+
+#define CACHE_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
+ do { \
+ static base::HistogramBase* counter(NULL); \
+ if (!counter || name != counter->histogram_name()) \
+ counter = base::Histogram::FactoryTimeGet( \
+ name, min, max, bucket_count, \
+ base::Histogram::kUmaTargetedHistogramFlag); \
+ counter->AddTime(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_TIMES(name, sample) CACHE_HISTOGRAM_CUSTOM_TIMES( \
+ name, sample, base::TimeDelta::FromMilliseconds(1), \
+ base::TimeDelta::FromSeconds(10), 50)
+
+#define CACHE_HISTOGRAM_ENUMERATION(name, sample, boundary_value) do { \
+ static base::HistogramBase* counter(NULL); \
+ if (!counter || name != counter->histogram_name()) \
+ counter = base::LinearHistogram::FactoryGet( \
+ name, 1, boundary_value, boundary_value + 1, \
+ base::Histogram::kUmaTargetedHistogramFlag); \
+ counter->Add(sample); \
+ } while (0)
+
+#define CACHE_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \
+ CACHE_HISTOGRAM_ENUMERATION(name, under_one_hundred, 101)
+
+// -----------------------------------------------------------------------------
+
+// HISTOGRAM_HOURS will collect time related data with a granularity of hours
+// and normal values of a few months.
+#define CACHE_HISTOGRAM_HOURS CACHE_HISTOGRAM_COUNTS_10000
+
+// HISTOGRAM_AGE will collect time elapsed since |initial_time|, with a
+// granularity of hours and normal values of a few months.
+#define CACHE_HISTOGRAM_AGE(name, initial_time) \
+ CACHE_HISTOGRAM_COUNTS_10000(name, \
+ (base::Time::Now() - initial_time).InHours())
+
+// HISTOGRAM_AGE_MS will collect time elapsed since |initial_time|, with the
+// normal resolution of the UMA_HISTOGRAM_TIMES.
+#define CACHE_HISTOGRAM_AGE_MS(name, initial_time)\
+ CACHE_HISTOGRAM_TIMES(name, base::TimeTicks::Now() - initial_time)
+
+#define CACHE_HISTOGRAM_CACHE_ERROR(name, sample) \
+ CACHE_HISTOGRAM_ENUMERATION(name, sample, 50)
+
+#ifdef NET_DISK_CACHE_BACKEND_IMPL_CC_
+#define BACKEND_OBJ this
+#else
+#define BACKEND_OBJ backend_
+#endif
+
+// Generates a UMA histogram of the given type, generating the proper name for
+// it (asking backend_->HistogramName), and adding the provided sample.
+// For example, to generate a regualar UMA_HISTOGRAM_COUNTS, this macro would
+// be used as:
+// CACHE_UMA(COUNTS, "MyName", 0, 20);
+// CACHE_UMA(COUNTS, "MyExperiment", 530, 55);
+// which roughly translates to:
+// UMA_HISTOGRAM_COUNTS("DiskCache.2.MyName", 20); // "2" is the CacheType.
+// UMA_HISTOGRAM_COUNTS("DiskCache.2.MyExperiment_530", 55);
+//
+#define CACHE_UMA(type, name, experiment, sample) {\
+ const std::string my_name = BACKEND_OBJ->HistogramName(name, experiment);\
+ switch (BACKEND_OBJ->cache_type()) {\
+ case net::DISK_CACHE:\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
+ break;\
+ case net::MEDIA_CACHE:\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
+ break;\
+ case net::APP_CACHE:\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
+ break;\
+ case net::SHADER_CACHE:\
+ CACHE_HISTOGRAM_##type(my_name.data(), sample);\
+ break;\
+ default:\
+ NOTREACHED();\
+ break;\
+ }\
+ }
+
+#endif // NET_DISK_CACHE_HISTOGRAM_MACROS_H_
diff --git a/chromium/net/disk_cache/in_flight_backend_io.cc b/chromium/net/disk_cache/in_flight_backend_io.cc
new file mode 100644
index 00000000000..3ed9e4dd191
--- /dev/null
+++ b/chromium/net/disk_cache/in_flight_backend_io.cc
@@ -0,0 +1,522 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/in_flight_backend_io.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/histogram_macros.h"
+
+namespace disk_cache {
+
+BackendIO::BackendIO(InFlightIO* controller, BackendImpl* backend,
+ const net::CompletionCallback& callback)
+ : BackgroundIO(controller),
+ backend_(backend),
+ callback_(callback),
+ operation_(OP_NONE),
+ entry_ptr_(NULL),
+ iter_ptr_(NULL),
+ iter_(NULL),
+ entry_(NULL),
+ index_(0),
+ offset_(0),
+ buf_len_(0),
+ truncate_(false),
+ offset64_(0),
+ start_(NULL) {
+ start_time_ = base::TimeTicks::Now();
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteOperation() {
+ if (IsEntryOperation())
+ return ExecuteEntryOperation();
+
+ ExecuteBackendOperation();
+}
+
+// Runs on the background thread.
+void BackendIO::OnIOComplete(int result) {
+ DCHECK(IsEntryOperation());
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+ result_ = result;
+ NotifyController();
+}
+
+// Runs on the primary thread.
+void BackendIO::OnDone(bool cancel) {
+ if (IsEntryOperation()) {
+ CACHE_UMA(TIMES, "TotalIOTime", 0, ElapsedTime());
+ }
+
+ if (!ReturnsEntry())
+ return;
+
+ if (result() == net::OK) {
+ static_cast<EntryImpl*>(*entry_ptr_)->OnEntryCreated(backend_);
+ if (cancel)
+ (*entry_ptr_)->Close();
+ }
+}
+
+bool BackendIO::IsEntryOperation() {
+ return operation_ > OP_MAX_BACKEND;
+}
+
+// Runs on the background thread.
+void BackendIO::ReferenceEntry() {
+ entry_->AddRef();
+}
+
+void BackendIO::Init() {
+ operation_ = OP_INIT;
+}
+
+void BackendIO::OpenEntry(const std::string& key, Entry** entry) {
+ operation_ = OP_OPEN;
+ key_ = key;
+ entry_ptr_ = entry;
+}
+
+void BackendIO::CreateEntry(const std::string& key, Entry** entry) {
+ operation_ = OP_CREATE;
+ key_ = key;
+ entry_ptr_ = entry;
+}
+
+void BackendIO::DoomEntry(const std::string& key) {
+ operation_ = OP_DOOM;
+ key_ = key;
+}
+
+void BackendIO::DoomAllEntries() {
+ operation_ = OP_DOOM_ALL;
+}
+
+void BackendIO::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ operation_ = OP_DOOM_BETWEEN;
+ initial_time_ = initial_time;
+ end_time_ = end_time;
+}
+
+void BackendIO::DoomEntriesSince(const base::Time initial_time) {
+ operation_ = OP_DOOM_SINCE;
+ initial_time_ = initial_time;
+}
+
+void BackendIO::OpenNextEntry(void** iter, Entry** next_entry) {
+ operation_ = OP_OPEN_NEXT;
+ iter_ptr_ = iter;
+ entry_ptr_ = next_entry;
+}
+
+void BackendIO::OpenPrevEntry(void** iter, Entry** prev_entry) {
+ operation_ = OP_OPEN_PREV;
+ iter_ptr_ = iter;
+ entry_ptr_ = prev_entry;
+}
+
+void BackendIO::EndEnumeration(void* iterator) {
+ operation_ = OP_END_ENUMERATION;
+ iter_ = iterator;
+}
+
+void BackendIO::OnExternalCacheHit(const std::string& key) {
+ operation_ = OP_ON_EXTERNAL_CACHE_HIT;
+ key_ = key;
+}
+
+void BackendIO::CloseEntryImpl(EntryImpl* entry) {
+ operation_ = OP_CLOSE_ENTRY;
+ entry_ = entry;
+}
+
+void BackendIO::DoomEntryImpl(EntryImpl* entry) {
+ operation_ = OP_DOOM_ENTRY;
+ entry_ = entry;
+}
+
+void BackendIO::FlushQueue() {
+ operation_ = OP_FLUSH_QUEUE;
+}
+
+void BackendIO::RunTask(const base::Closure& task) {
+ operation_ = OP_RUN_TASK;
+ task_ = task;
+}
+
+void BackendIO::ReadData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_READ;
+ entry_ = entry;
+ index_ = index;
+ offset_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::WriteData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len, bool truncate) {
+ operation_ = OP_WRITE;
+ entry_ = entry;
+ index_ = index;
+ offset_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+ truncate_ = truncate;
+}
+
+void BackendIO::ReadSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_READ_SPARSE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::WriteSparseData(EntryImpl* entry, int64 offset,
+ net::IOBuffer* buf, int buf_len) {
+ operation_ = OP_WRITE_SPARSE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_ = buf;
+ buf_len_ = buf_len;
+}
+
+void BackendIO::GetAvailableRange(EntryImpl* entry, int64 offset, int len,
+ int64* start) {
+ operation_ = OP_GET_RANGE;
+ entry_ = entry;
+ offset64_ = offset;
+ buf_len_ = len;
+ start_ = start;
+}
+
+void BackendIO::CancelSparseIO(EntryImpl* entry) {
+ operation_ = OP_CANCEL_IO;
+ entry_ = entry;
+}
+
+void BackendIO::ReadyForSparseIO(EntryImpl* entry) {
+ operation_ = OP_IS_READY;
+ entry_ = entry;
+}
+
+BackendIO::~BackendIO() {}
+
+bool BackendIO::ReturnsEntry() {
+ return (operation_ == OP_OPEN || operation_ == OP_CREATE ||
+ operation_ == OP_OPEN_NEXT || operation_ == OP_OPEN_PREV);
+}
+
+base::TimeDelta BackendIO::ElapsedTime() const {
+ return base::TimeTicks::Now() - start_time_;
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteBackendOperation() {
+ switch (operation_) {
+ case OP_INIT:
+ result_ = backend_->SyncInit();
+ break;
+ case OP_OPEN:
+ result_ = backend_->SyncOpenEntry(key_, entry_ptr_);
+ break;
+ case OP_CREATE:
+ result_ = backend_->SyncCreateEntry(key_, entry_ptr_);
+ break;
+ case OP_DOOM:
+ result_ = backend_->SyncDoomEntry(key_);
+ break;
+ case OP_DOOM_ALL:
+ result_ = backend_->SyncDoomAllEntries();
+ break;
+ case OP_DOOM_BETWEEN:
+ result_ = backend_->SyncDoomEntriesBetween(initial_time_, end_time_);
+ break;
+ case OP_DOOM_SINCE:
+ result_ = backend_->SyncDoomEntriesSince(initial_time_);
+ break;
+ case OP_OPEN_NEXT:
+ result_ = backend_->SyncOpenNextEntry(iter_ptr_, entry_ptr_);
+ break;
+ case OP_OPEN_PREV:
+ result_ = backend_->SyncOpenPrevEntry(iter_ptr_, entry_ptr_);
+ break;
+ case OP_END_ENUMERATION:
+ backend_->SyncEndEnumeration(iter_);
+ result_ = net::OK;
+ break;
+ case OP_ON_EXTERNAL_CACHE_HIT:
+ backend_->SyncOnExternalCacheHit(key_);
+ result_ = net::OK;
+ break;
+ case OP_CLOSE_ENTRY:
+ entry_->Release();
+ result_ = net::OK;
+ break;
+ case OP_DOOM_ENTRY:
+ entry_->DoomImpl();
+ result_ = net::OK;
+ break;
+ case OP_FLUSH_QUEUE:
+ result_ = net::OK;
+ break;
+ case OP_RUN_TASK:
+ task_.Run();
+ result_ = net::OK;
+ break;
+ default:
+ NOTREACHED() << "Invalid Operation";
+ result_ = net::ERR_UNEXPECTED;
+ }
+ DCHECK_NE(net::ERR_IO_PENDING, result_);
+ NotifyController();
+}
+
+// Runs on the background thread.
+void BackendIO::ExecuteEntryOperation() {
+ switch (operation_) {
+ case OP_READ:
+ result_ =
+ entry_->ReadDataImpl(index_, offset_, buf_.get(), buf_len_,
+ base::Bind(&BackendIO::OnIOComplete, this));
+ break;
+ case OP_WRITE:
+ result_ =
+ entry_->WriteDataImpl(index_, offset_, buf_.get(), buf_len_,
+ base::Bind(&BackendIO::OnIOComplete, this),
+ truncate_);
+ break;
+ case OP_READ_SPARSE:
+ result_ = entry_->ReadSparseDataImpl(
+ offset64_, buf_.get(), buf_len_,
+ base::Bind(&BackendIO::OnIOComplete, this));
+ break;
+ case OP_WRITE_SPARSE:
+ result_ = entry_->WriteSparseDataImpl(
+ offset64_, buf_.get(), buf_len_,
+ base::Bind(&BackendIO::OnIOComplete, this));
+ break;
+ case OP_GET_RANGE:
+ result_ = entry_->GetAvailableRangeImpl(offset64_, buf_len_, start_);
+ break;
+ case OP_CANCEL_IO:
+ entry_->CancelSparseIOImpl();
+ result_ = net::OK;
+ break;
+ case OP_IS_READY:
+ result_ = entry_->ReadyForSparseIOImpl(
+ base::Bind(&BackendIO::OnIOComplete, this));
+ break;
+ default:
+ NOTREACHED() << "Invalid Operation";
+ result_ = net::ERR_UNEXPECTED;
+ }
+ buf_ = NULL;
+ if (result_ != net::ERR_IO_PENDING)
+ NotifyController();
+}
+
+InFlightBackendIO::InFlightBackendIO(BackendImpl* backend,
+ base::MessageLoopProxy* background_thread)
+ : backend_(backend),
+ background_thread_(background_thread),
+ ptr_factory_(this) {
+}
+
+InFlightBackendIO::~InFlightBackendIO() {
+}
+
+void InFlightBackendIO::Init(const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->Init();
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::OpenEntry(const std::string& key, Entry** entry,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->OpenEntry(key, entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::CreateEntry(const std::string& key, Entry** entry,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->CreateEntry(key, entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::DoomEntry(const std::string& key,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->DoomEntry(key);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::DoomAllEntries(
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->DoomAllEntries();
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->DoomEntriesBetween(initial_time, end_time);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::DoomEntriesSince(
+ const base::Time initial_time, const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->DoomEntriesSince(initial_time);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::OpenNextEntry(void** iter, Entry** next_entry,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->OpenNextEntry(iter, next_entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::OpenPrevEntry(void** iter, Entry** prev_entry,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->OpenPrevEntry(iter, prev_entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::EndEnumeration(void* iterator) {
+ scoped_refptr<BackendIO> operation(
+ new BackendIO(this, backend_, net::CompletionCallback()));
+ operation->EndEnumeration(iterator);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::OnExternalCacheHit(const std::string& key) {
+ scoped_refptr<BackendIO> operation(
+ new BackendIO(this, backend_, net::CompletionCallback()));
+ operation->OnExternalCacheHit(key);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::CloseEntryImpl(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation(
+ new BackendIO(this, backend_, net::CompletionCallback()));
+ operation->CloseEntryImpl(entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::DoomEntryImpl(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation(
+ new BackendIO(this, backend_, net::CompletionCallback()));
+ operation->DoomEntryImpl(entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::FlushQueue(const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->FlushQueue();
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::RunTask(
+ const base::Closure& task, const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->RunTask(task);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::ReadData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->ReadData(entry, index, offset, buf, buf_len);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::WriteData(EntryImpl* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ bool truncate,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->WriteData(entry, index, offset, buf, buf_len, truncate);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::ReadSparseData(
+ EntryImpl* entry, int64 offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->ReadSparseData(entry, offset, buf, buf_len);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::WriteSparseData(
+ EntryImpl* entry, int64 offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->WriteSparseData(entry, offset, buf, buf_len);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::GetAvailableRange(
+ EntryImpl* entry, int64 offset, int len, int64* start,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->GetAvailableRange(entry, offset, len, start);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::CancelSparseIO(EntryImpl* entry) {
+ scoped_refptr<BackendIO> operation(
+ new BackendIO(this, backend_, net::CompletionCallback()));
+ operation->CancelSparseIO(entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::ReadyForSparseIO(
+ EntryImpl* entry, const net::CompletionCallback& callback) {
+ scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
+ operation->ReadyForSparseIO(entry);
+ PostOperation(operation.get());
+}
+
+void InFlightBackendIO::WaitForPendingIO() {
+ InFlightIO::WaitForPendingIO();
+}
+
+void InFlightBackendIO::OnOperationComplete(BackgroundIO* operation,
+ bool cancel) {
+ BackendIO* op = static_cast<BackendIO*>(operation);
+ op->OnDone(cancel);
+
+ if (!op->callback().is_null() && (!cancel || op->IsEntryOperation()))
+ op->callback().Run(op->result());
+}
+
+void InFlightBackendIO::PostOperation(BackendIO* operation) {
+ background_thread_->PostTask(FROM_HERE,
+ base::Bind(&BackendIO::ExecuteOperation, operation));
+ OnOperationPosted(operation);
+}
+
+base::WeakPtr<InFlightBackendIO> InFlightBackendIO::GetWeakPtr() {
+ return ptr_factory_.GetWeakPtr();
+}
+
+} // namespace
diff --git a/chromium/net/disk_cache/in_flight_backend_io.h b/chromium/net/disk_cache/in_flight_backend_io.h
new file mode 100644
index 00000000000..4ff081d07c5
--- /dev/null
+++ b/chromium/net/disk_cache/in_flight_backend_io.h
@@ -0,0 +1,223 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
+#define NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
+
+#include <list>
+#include <string>
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/in_flight_io.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+class Entry;
+class EntryImpl;
+
+// This class represents a single asynchronous disk cache IO operation while it
+// is being bounced between threads.
+class BackendIO : public BackgroundIO {
+ public:
+ BackendIO(InFlightIO* controller, BackendImpl* backend,
+ const net::CompletionCallback& callback);
+
+ // Runs the actual operation on the background thread.
+ void ExecuteOperation();
+
+ // Callback implementation.
+ void OnIOComplete(int result);
+
+ // Called when we are finishing this operation. If |cancel| is true, the user
+ // callback will not be invoked.
+ void OnDone(bool cancel);
+
+ // Returns true if this operation is directed to an entry (vs. the backend).
+ bool IsEntryOperation();
+
+ net::CompletionCallback callback() const { return callback_; }
+
+ // Grabs an extra reference of entry_.
+ void ReferenceEntry();
+
+ // The operations we proxy:
+ void Init();
+ void OpenEntry(const std::string& key, Entry** entry);
+ void CreateEntry(const std::string& key, Entry** entry);
+ void DoomEntry(const std::string& key);
+ void DoomAllEntries();
+ void DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ void DoomEntriesSince(const base::Time initial_time);
+ void OpenNextEntry(void** iter, Entry** next_entry);
+ void OpenPrevEntry(void** iter, Entry** prev_entry);
+ void EndEnumeration(void* iterator);
+ void OnExternalCacheHit(const std::string& key);
+ void CloseEntryImpl(EntryImpl* entry);
+ void DoomEntryImpl(EntryImpl* entry);
+ void FlushQueue(); // Dummy operation.
+ void RunTask(const base::Closure& task);
+ void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len);
+ void WriteData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, bool truncate);
+ void ReadSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len);
+ void WriteSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len);
+ void GetAvailableRange(EntryImpl* entry, int64 offset, int len, int64* start);
+ void CancelSparseIO(EntryImpl* entry);
+ void ReadyForSparseIO(EntryImpl* entry);
+
+ private:
+ // There are two types of operations to proxy: regular backend operations are
+ // executed sequentially (queued by the message loop). On the other hand,
+ // operations targeted to a given entry can be long lived and support multiple
+ // simultaneous users (multiple reads or writes to the same entry), and they
+ // are subject to throttling, so we keep an explicit queue.
+ enum Operation {
+ OP_NONE = 0,
+ OP_INIT,
+ OP_OPEN,
+ OP_CREATE,
+ OP_DOOM,
+ OP_DOOM_ALL,
+ OP_DOOM_BETWEEN,
+ OP_DOOM_SINCE,
+ OP_OPEN_NEXT,
+ OP_OPEN_PREV,
+ OP_END_ENUMERATION,
+ OP_ON_EXTERNAL_CACHE_HIT,
+ OP_CLOSE_ENTRY,
+ OP_DOOM_ENTRY,
+ OP_FLUSH_QUEUE,
+ OP_RUN_TASK,
+ OP_MAX_BACKEND,
+ OP_READ,
+ OP_WRITE,
+ OP_READ_SPARSE,
+ OP_WRITE_SPARSE,
+ OP_GET_RANGE,
+ OP_CANCEL_IO,
+ OP_IS_READY
+ };
+
+ virtual ~BackendIO();
+
+ // Returns true if this operation returns an entry.
+ bool ReturnsEntry();
+
+ // Returns the time that has passed since the operation was created.
+ base::TimeDelta ElapsedTime() const;
+
+ void ExecuteBackendOperation();
+ void ExecuteEntryOperation();
+
+ BackendImpl* backend_;
+ net::CompletionCallback callback_;
+ Operation operation_;
+
+ // The arguments of all the operations we proxy:
+ std::string key_;
+ Entry** entry_ptr_;
+ base::Time initial_time_;
+ base::Time end_time_;
+ void** iter_ptr_;
+ void* iter_;
+ EntryImpl* entry_;
+ int index_;
+ int offset_;
+ scoped_refptr<net::IOBuffer> buf_;
+ int buf_len_;
+ bool truncate_;
+ int64 offset64_;
+ int64* start_;
+ base::TimeTicks start_time_;
+ base::Closure task_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackendIO);
+};
+
+// The specialized controller that keeps track of current operations.
+class InFlightBackendIO : public InFlightIO {
+ public:
+ InFlightBackendIO(BackendImpl* backend,
+ base::MessageLoopProxy* background_thread);
+ virtual ~InFlightBackendIO();
+
+ // Proxied operations.
+ void Init(const net::CompletionCallback& callback);
+ void OpenEntry(const std::string& key, Entry** entry,
+ const net::CompletionCallback& callback);
+ void CreateEntry(const std::string& key, Entry** entry,
+ const net::CompletionCallback& callback);
+ void DoomEntry(const std::string& key,
+ const net::CompletionCallback& callback);
+ void DoomAllEntries(const net::CompletionCallback& callback);
+ void DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ const net::CompletionCallback& callback);
+ void DoomEntriesSince(const base::Time initial_time,
+ const net::CompletionCallback& callback);
+ void OpenNextEntry(void** iter, Entry** next_entry,
+ const net::CompletionCallback& callback);
+ void OpenPrevEntry(void** iter, Entry** prev_entry,
+ const net::CompletionCallback& callback);
+ void EndEnumeration(void* iterator);
+ void OnExternalCacheHit(const std::string& key);
+ void CloseEntryImpl(EntryImpl* entry);
+ void DoomEntryImpl(EntryImpl* entry);
+ void FlushQueue(const net::CompletionCallback& callback);
+ void RunTask(const base::Closure& task,
+ const net::CompletionCallback& callback);
+ void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, const net::CompletionCallback& callback);
+ void WriteData(
+ EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
+ int buf_len, bool truncate, const net::CompletionCallback& callback);
+ void ReadSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len, const net::CompletionCallback& callback);
+ void WriteSparseData(EntryImpl* entry, int64 offset, net::IOBuffer* buf,
+ int buf_len, const net::CompletionCallback& callback);
+ void GetAvailableRange(EntryImpl* entry, int64 offset, int len, int64* start,
+ const net::CompletionCallback& callback);
+ void CancelSparseIO(EntryImpl* entry);
+ void ReadyForSparseIO(EntryImpl* entry,
+ const net::CompletionCallback& callback);
+
+ // Blocks until all operations are cancelled or completed.
+ void WaitForPendingIO();
+
+ scoped_refptr<base::MessageLoopProxy> background_thread() {
+ return background_thread_;
+ }
+
+ // Returns true if the current thread is the background thread.
+ bool BackgroundIsCurrentThread() {
+ return background_thread_->BelongsToCurrentThread();
+ }
+
+ base::WeakPtr<InFlightBackendIO> GetWeakPtr();
+
+ protected:
+ virtual void OnOperationComplete(BackgroundIO* operation,
+ bool cancel) OVERRIDE;
+
+ private:
+ void PostOperation(BackendIO* operation);
+
+ BackendImpl* backend_;
+ scoped_refptr<base::MessageLoopProxy> background_thread_;
+ base::WeakPtrFactory<InFlightBackendIO> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InFlightBackendIO);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_IN_FLIGHT_BACKEND_IO_H_
diff --git a/chromium/net/disk_cache/in_flight_io.cc b/chromium/net/disk_cache/in_flight_io.cc
new file mode 100644
index 00000000000..467814f22f1
--- /dev/null
+++ b/chromium/net/disk_cache/in_flight_io.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/in_flight_io.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace disk_cache {
+
+BackgroundIO::BackgroundIO(InFlightIO* controller)
+ : result_(-1), io_completed_(true, false), controller_(controller) {
+}
+
+// Runs on the primary thread.
+void BackgroundIO::OnIOSignalled() {
+ if (controller_)
+ controller_->InvokeCallback(this, false);
+}
+
+void BackgroundIO::Cancel() {
+ // controller_ may be in use from the background thread at this time.
+ base::AutoLock lock(controller_lock_);
+ DCHECK(controller_);
+ controller_ = NULL;
+}
+
+BackgroundIO::~BackgroundIO() {
+}
+
+// ---------------------------------------------------------------------------
+
+InFlightIO::InFlightIO()
+ : callback_thread_(base::MessageLoopProxy::current()),
+ running_(false), single_thread_(false) {
+}
+
+InFlightIO::~InFlightIO() {
+}
+
+// Runs on the background thread.
+void BackgroundIO::NotifyController() {
+ base::AutoLock lock(controller_lock_);
+ if (controller_)
+ controller_->OnIOComplete(this);
+}
+
+void InFlightIO::WaitForPendingIO() {
+ while (!io_list_.empty()) {
+ // Block the current thread until all pending IO completes.
+ IOList::iterator it = io_list_.begin();
+ InvokeCallback(it->get(), true);
+ }
+}
+
+void InFlightIO::DropPendingIO() {
+ while (!io_list_.empty()) {
+ IOList::iterator it = io_list_.begin();
+ BackgroundIO* operation = it->get();
+ operation->Cancel();
+ DCHECK(io_list_.find(operation) != io_list_.end());
+ io_list_.erase(make_scoped_refptr(operation));
+ }
+}
+
+// Runs on a background thread.
+void InFlightIO::OnIOComplete(BackgroundIO* operation) {
+#ifndef NDEBUG
+ if (callback_thread_->BelongsToCurrentThread()) {
+ DCHECK(single_thread_ || !running_);
+ single_thread_ = true;
+ }
+#endif
+
+ callback_thread_->PostTask(FROM_HERE,
+ base::Bind(&BackgroundIO::OnIOSignalled,
+ operation));
+ operation->io_completed()->Signal();
+}
+
+// Runs on the primary thread.
+void InFlightIO::InvokeCallback(BackgroundIO* operation, bool cancel_task) {
+ {
+ // http://crbug.com/74623
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ operation->io_completed()->Wait();
+ }
+ running_ = true;
+
+ if (cancel_task)
+ operation->Cancel();
+
+ // Make sure that we remove the operation from the list before invoking the
+ // callback (so that a subsequent cancel does not invoke the callback again).
+ DCHECK(io_list_.find(operation) != io_list_.end());
+ DCHECK(!operation->HasOneRef());
+ io_list_.erase(make_scoped_refptr(operation));
+ OnOperationComplete(operation, cancel_task);
+}
+
+// Runs on the primary thread.
+void InFlightIO::OnOperationPosted(BackgroundIO* operation) {
+ DCHECK(callback_thread_->BelongsToCurrentThread());
+ io_list_.insert(make_scoped_refptr(operation));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/in_flight_io.h b/chromium/net/disk_cache/in_flight_io.h
new file mode 100644
index 00000000000..2a3330445ba
--- /dev/null
+++ b/chromium/net/disk_cache/in_flight_io.h
@@ -0,0 +1,136 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_IN_FLIGHT_IO_H_
+#define NET_DISK_CACHE_IN_FLIGHT_IO_H_
+
+#include <set>
+
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+
+namespace disk_cache {
+
+class InFlightIO;
+
+// This class represents a single asynchronous IO operation while it is being
+// bounced between threads.
+class BackgroundIO : public base::RefCountedThreadSafe<BackgroundIO> {
+ public:
+ // Other than the actual parameters for the IO operation (including the
+ // |callback| that must be notified at the end), we need the controller that
+ // is keeping track of all operations. When done, we notify the controller
+ // (we do NOT invoke the callback), in the worker thead that completed the
+ // operation.
+ explicit BackgroundIO(InFlightIO* controller);
+
+ // This method signals the controller that this operation is finished, in the
+ // original thread. In practice, this is a RunableMethod that allows
+ // cancellation.
+ void OnIOSignalled();
+
+ // Allows the cancellation of the task to notify the controller (step number 8
+ // in the diagram below). In practice, if the controller waits for the
+ // operation to finish it doesn't have to wait for the final task to be
+ // processed by the message loop so calling this method prevents its delivery.
+ // Note that this method is not intended to cancel the actual IO operation or
+ // to prevent the first notification to take place (OnIOComplete).
+ void Cancel();
+
+ int result() { return result_; }
+
+ base::WaitableEvent* io_completed() {
+ return &io_completed_;
+ }
+
+ protected:
+ virtual ~BackgroundIO();
+
+ // Notifies the controller about the end of the operation, from the background
+ // thread.
+ void NotifyController();
+
+ int result_; // Final operation result.
+
+ private:
+ friend class base::RefCountedThreadSafe<BackgroundIO>;
+
+ // An event to signal when the operation completes.
+ base::WaitableEvent io_completed_;
+ InFlightIO* controller_; // The controller that tracks all operations.
+ base::Lock controller_lock_; // A lock protecting clearing of controller_.
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundIO);
+};
+
+// This class keeps track of asynchronous IO operations. A single instance
+// of this class is meant to be used to start an asynchronous operation (using
+// PostXX, exposed by a derived class). This class will post the operation to a
+// worker thread, hanlde the notification when the operation finishes and
+// perform the callback on the same thread that was used to start the operation.
+//
+// The regular sequence of calls is:
+// Thread_1 Worker_thread
+// 1. DerivedInFlightIO::PostXX()
+// 2. -> PostTask ->
+// 3. InFlightIO::OnOperationPosted()
+// 4. DerivedBackgroundIO::XX()
+// 5. IO operation completes
+// 6. InFlightIO::OnIOComplete()
+// 7. <- PostTask <-
+// 8. BackgroundIO::OnIOSignalled()
+// 9. InFlightIO::InvokeCallback()
+// 10. DerivedInFlightIO::OnOperationComplete()
+// 11. invoke callback
+//
+// Shutdown is a special case that is handled though WaitForPendingIO() instead
+// of just waiting for step 7.
+class InFlightIO {
+ public:
+ InFlightIO();
+ virtual ~InFlightIO();
+
+ // Blocks the current thread until all IO operations tracked by this object
+ // complete.
+ void WaitForPendingIO();
+
+ // Drops current pending operations without waiting for them to complete.
+ void DropPendingIO();
+
+ // Called on a background thread when |operation| completes.
+ void OnIOComplete(BackgroundIO* operation);
+
+ // Invokes the users' completion callback at the end of the IO operation.
+ // |cancel_task| is true if the actual task posted to the thread is still
+ // queued (because we are inside WaitForPendingIO), and false if said task is
+ // the one performing the call.
+ void InvokeCallback(BackgroundIO* operation, bool cancel_task);
+
+ protected:
+ // This method is called to signal the completion of the |operation|. |cancel|
+ // is true if the operation is being cancelled. This method is called on the
+ // thread that created this object.
+ virtual void OnOperationComplete(BackgroundIO* operation, bool cancel) = 0;
+
+ // Signals this object that the derived class just posted the |operation| to
+ // be executed on a background thread. This method must be called on the same
+ // thread used to create this object.
+ void OnOperationPosted(BackgroundIO* operation);
+
+ private:
+ typedef std::set<scoped_refptr<BackgroundIO> > IOList;
+
+ IOList io_list_; // List of pending, in-flight io operations.
+ scoped_refptr<base::MessageLoopProxy> callback_thread_;
+
+ bool running_; // True after the first posted operation completes.
+ bool single_thread_; // True if we only have one thread.
+
+ DISALLOW_COPY_AND_ASSIGN(InFlightIO);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_IN_FLIGHT_IO_H_
diff --git a/chromium/net/disk_cache/mapped_file.cc b/chromium/net/disk_cache/mapped_file.cc
new file mode 100644
index 00000000000..f17a1004a90
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mapped_file.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace disk_cache {
+
+void* MappedFile::Init(const base::FilePath& name, size_t size) {
+ DCHECK(!init_);
+ if (init_ || !File::Init(name))
+ return NULL;
+
+ buffer_ = NULL;
+ init_ = true;
+ section_ = CreateFileMapping(platform_file(), NULL, PAGE_READWRITE, 0,
+ static_cast<DWORD>(size), NULL);
+ if (!section_)
+ return NULL;
+
+ buffer_ = MapViewOfFile(section_, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size);
+ DCHECK(buffer_);
+ view_size_ = size;
+
+ // Make sure we detect hardware failures reading the headers.
+ size_t temp_len = size ? size : 4096;
+ scoped_ptr<char[]> temp(new char[temp_len]);
+ if (!Read(temp.get(), temp_len, 0))
+ return NULL;
+
+ return buffer_;
+}
+
+MappedFile::~MappedFile() {
+ if (!init_)
+ return;
+
+ if (buffer_) {
+ BOOL ret = UnmapViewOfFile(buffer_);
+ DCHECK(ret);
+ }
+
+ if (section_)
+ CloseHandle(section_);
+}
+
+bool MappedFile::Load(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Read(block->buffer(), block->size(), offset);
+}
+
+bool MappedFile::Store(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Write(block->buffer(), block->size(), offset);
+}
+
+void MappedFile::Flush() {
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mapped_file.h b/chromium/net/disk_cache/mapped_file.h
new file mode 100644
index 00000000000..4649b90d1c9
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file.h
@@ -0,0 +1,73 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_MAPPED_FILE_H_
+#define NET_DISK_CACHE_MAPPED_FILE_H_
+
+#include "net/base/net_export.h"
+#include "net/disk_cache/file.h"
+#include "net/disk_cache/file_block.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace disk_cache {
+
+// This class implements a memory mapped file used to access block-files. The
+// idea is that the header and bitmap will be memory mapped all the time, and
+// the actual data for the blocks will be access asynchronously (most of the
+// time).
+class NET_EXPORT_PRIVATE MappedFile : public File {
+ public:
+ MappedFile() : File(true), init_(false) {}
+
+ // Performs object initialization. name is the file to use, and size is the
+ // amount of data to memory map from the file. If size is 0, the whole file
+ // will be mapped in memory.
+ void* Init(const base::FilePath& name, size_t size);
+
+ void* buffer() const {
+ return buffer_;
+ }
+
+ // Loads or stores a given block from the backing file (synchronously).
+ bool Load(const FileBlock* block);
+ bool Store(const FileBlock* block);
+
+ // Flush the memory-mapped section to disk (synchronously).
+ void Flush();
+
+ private:
+ virtual ~MappedFile();
+
+ bool init_;
+#if defined(OS_WIN)
+ HANDLE section_;
+#endif
+ void* buffer_; // Address of the memory mapped buffer.
+ size_t view_size_; // Size of the memory pointed by buffer_.
+#if defined(POSIX_AVOID_MMAP)
+ void* snapshot_; // Copy of the buffer taken when it was last flushed.
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(MappedFile);
+};
+
+// Helper class for calling Flush() on exit from the current scope.
+class ScopedFlush {
+ public:
+ explicit ScopedFlush(MappedFile* file) : file_(file) {}
+ ~ScopedFlush() {
+ file_->Flush();
+ }
+ private:
+ MappedFile* file_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MAPPED_FILE_H_
diff --git a/chromium/net/disk_cache/mapped_file_avoid_mmap_posix.cc b/chromium/net/disk_cache/mapped_file_avoid_mmap_posix.cc
new file mode 100644
index 00000000000..cd514a366e2
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file_avoid_mmap_posix.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mapped_file.h"
+
+#include <stdlib.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+
+namespace disk_cache {
+
+void* MappedFile::Init(const base::FilePath& name, size_t size) {
+ DCHECK(!init_);
+ if (init_ || !File::Init(name))
+ return NULL;
+
+ if (!size)
+ size = GetLength();
+
+ buffer_ = malloc(size);
+ snapshot_ = malloc(size);
+ if (buffer_ && snapshot_ && Read(buffer_, size, 0)) {
+ memcpy(snapshot_, buffer_, size);
+ } else {
+ free(buffer_);
+ free(snapshot_);
+ buffer_ = snapshot_ = 0;
+ }
+
+ init_ = true;
+ view_size_ = size;
+ return buffer_;
+}
+
+bool MappedFile::Load(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Read(block->buffer(), block->size(), offset);
+}
+
+bool MappedFile::Store(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Write(block->buffer(), block->size(), offset);
+}
+
+void MappedFile::Flush() {
+ DCHECK(buffer_);
+ DCHECK(snapshot_);
+ const char* buffer_ptr = static_cast<const char*>(buffer_);
+ char* snapshot_ptr = static_cast<char*>(snapshot_);
+ const size_t block_size = 4096;
+ for (size_t offset = 0; offset < view_size_; offset += block_size) {
+ size_t size = std::min(view_size_ - offset, block_size);
+ if (memcmp(snapshot_ptr + offset, buffer_ptr + offset, size)) {
+ memcpy(snapshot_ptr + offset, buffer_ptr + offset, size);
+ Write(snapshot_ptr + offset, size, offset);
+ }
+ }
+}
+
+MappedFile::~MappedFile() {
+ if (!init_)
+ return;
+
+ if (buffer_ && snapshot_) {
+ Flush();
+ }
+ free(buffer_);
+ free(snapshot_);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mapped_file_posix.cc b/chromium/net/disk_cache/mapped_file_posix.cc
new file mode 100644
index 00000000000..2146245d4aa
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file_posix.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mapped_file.h"
+
+#include <errno.h>
+#include <sys/mman.h>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace disk_cache {
+
+void* MappedFile::Init(const base::FilePath& name, size_t size) {
+ DCHECK(!init_);
+ if (init_ || !File::Init(name))
+ return NULL;
+
+ size_t temp_len = size ? size : 4096;
+ if (!size)
+ size = GetLength();
+
+ buffer_ = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
+ platform_file(), 0);
+ init_ = true;
+ view_size_ = size;
+ DCHECK(reinterpret_cast<intptr_t>(buffer_) != -1);
+ if (reinterpret_cast<intptr_t>(buffer_) == -1)
+ buffer_ = 0;
+
+ // Make sure we detect hardware failures reading the headers.
+ scoped_ptr<char[]> temp(new char[temp_len]);
+ if (!Read(temp.get(), temp_len, 0))
+ return NULL;
+
+ return buffer_;
+}
+
+bool MappedFile::Load(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Read(block->buffer(), block->size(), offset);
+}
+
+bool MappedFile::Store(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Write(block->buffer(), block->size(), offset);
+}
+
+void MappedFile::Flush() {
+}
+
+MappedFile::~MappedFile() {
+ if (!init_)
+ return;
+
+ if (buffer_) {
+ int ret = munmap(buffer_, view_size_);
+ DCHECK_EQ(0, ret);
+ }
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mapped_file_unittest.cc b/chromium/net/disk_cache/mapped_file_unittest.cc
new file mode 100644
index 00000000000..8798db0170c
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 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 "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/strings/string_util.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/mapped_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Implementation of FileIOCallback for the tests.
+class FileCallbackTest: public disk_cache::FileIOCallback {
+ public:
+ FileCallbackTest(int id, MessageLoopHelper* helper, int* max_id)
+ : id_(id),
+ helper_(helper),
+ max_id_(max_id) {
+ }
+ virtual ~FileCallbackTest() {}
+
+ virtual void OnFileIOComplete(int bytes_copied) OVERRIDE;
+
+ private:
+ int id_;
+ MessageLoopHelper* helper_;
+ int* max_id_;
+};
+
+void FileCallbackTest::OnFileIOComplete(int bytes_copied) {
+ if (id_ > *max_id_) {
+ NOTREACHED();
+ helper_->set_callback_reused_error(true);
+ }
+
+ helper_->CallbackWasCalled();
+}
+
+} // namespace
+
+TEST_F(DiskCacheTest, MappedFile_SyncIO) {
+ base::FilePath filename = cache_path_.AppendASCII("a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ char buffer1[20];
+ char buffer2[20];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ base::strlcpy(buffer1, "the data", arraysize(buffer1));
+ EXPECT_TRUE(file->Write(buffer1, sizeof(buffer1), 8192));
+ EXPECT_TRUE(file->Read(buffer2, sizeof(buffer2), 8192));
+ EXPECT_STREQ(buffer1, buffer2);
+}
+
+TEST_F(DiskCacheTest, MappedFile_AsyncIO) {
+ base::FilePath filename = cache_path_.AppendASCII("a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ int max_id = 0;
+ MessageLoopHelper helper;
+ FileCallbackTest callback(1, &helper, &max_id);
+
+ char buffer1[20];
+ char buffer2[20];
+ CacheTestFillBuffer(buffer1, sizeof(buffer1), false);
+ base::strlcpy(buffer1, "the data", arraysize(buffer1));
+ bool completed;
+ EXPECT_TRUE(file->Write(buffer1, sizeof(buffer1), 1024 * 1024, &callback,
+ &completed));
+ int expected = completed ? 0 : 1;
+
+ max_id = 1;
+ helper.WaitUntilCacheIoFinished(expected);
+
+ EXPECT_TRUE(file->Read(buffer2, sizeof(buffer2), 1024 * 1024, &callback,
+ &completed));
+ if (!completed)
+ expected++;
+
+ helper.WaitUntilCacheIoFinished(expected);
+
+ EXPECT_EQ(expected, helper.callbacks_called());
+ EXPECT_FALSE(helper.callback_reused_error());
+ EXPECT_STREQ(buffer1, buffer2);
+}
diff --git a/chromium/net/disk_cache/mapped_file_win.cc b/chromium/net/disk_cache/mapped_file_win.cc
new file mode 100644
index 00000000000..f17a1004a90
--- /dev/null
+++ b/chromium/net/disk_cache/mapped_file_win.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mapped_file.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace disk_cache {
+
+void* MappedFile::Init(const base::FilePath& name, size_t size) {
+ DCHECK(!init_);
+ if (init_ || !File::Init(name))
+ return NULL;
+
+ buffer_ = NULL;
+ init_ = true;
+ section_ = CreateFileMapping(platform_file(), NULL, PAGE_READWRITE, 0,
+ static_cast<DWORD>(size), NULL);
+ if (!section_)
+ return NULL;
+
+ buffer_ = MapViewOfFile(section_, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size);
+ DCHECK(buffer_);
+ view_size_ = size;
+
+ // Make sure we detect hardware failures reading the headers.
+ size_t temp_len = size ? size : 4096;
+ scoped_ptr<char[]> temp(new char[temp_len]);
+ if (!Read(temp.get(), temp_len, 0))
+ return NULL;
+
+ return buffer_;
+}
+
+MappedFile::~MappedFile() {
+ if (!init_)
+ return;
+
+ if (buffer_) {
+ BOOL ret = UnmapViewOfFile(buffer_);
+ DCHECK(ret);
+ }
+
+ if (section_)
+ CloseHandle(section_);
+}
+
+bool MappedFile::Load(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Read(block->buffer(), block->size(), offset);
+}
+
+bool MappedFile::Store(const FileBlock* block) {
+ size_t offset = block->offset() + view_size_;
+ return Write(block->buffer(), block->size(), offset);
+}
+
+void MappedFile::Flush() {
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mem_backend_impl.cc b/chromium/net/disk_cache/mem_backend_impl.cc
new file mode 100644
index 00000000000..a6f1bf13bed
--- /dev/null
+++ b/chromium/net/disk_cache/mem_backend_impl.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mem_backend_impl.h"
+
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/mem_entry_impl.h"
+
+using base::Time;
+
+namespace {
+
+const int kDefaultCacheSize = 10 * 1024 * 1024;
+const int kCleanUpMargin = 1024 * 1024;
+
+int LowWaterAdjust(int high_water) {
+ if (high_water < kCleanUpMargin)
+ return 0;
+
+ return high_water - kCleanUpMargin;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+MemBackendImpl::MemBackendImpl(net::NetLog* net_log)
+ : max_size_(0), current_size_(0), net_log_(net_log) {}
+
+MemBackendImpl::~MemBackendImpl() {
+ EntryMap::iterator it = entries_.begin();
+ while (it != entries_.end()) {
+ it->second->Doom();
+ it = entries_.begin();
+ }
+ DCHECK(!current_size_);
+}
+
+// Static.
+scoped_ptr<Backend> MemBackendImpl::CreateBackend(int max_bytes,
+ net::NetLog* net_log) {
+ scoped_ptr<MemBackendImpl> cache(new MemBackendImpl(net_log));
+ cache->SetMaxSize(max_bytes);
+ if (cache->Init())
+ return cache.PassAs<Backend>();
+
+ LOG(ERROR) << "Unable to create cache";
+ return scoped_ptr<Backend>();
+}
+
+bool MemBackendImpl::Init() {
+ if (max_size_)
+ return true;
+
+ int64 total_memory = base::SysInfo::AmountOfPhysicalMemory();
+
+ if (total_memory <= 0) {
+ max_size_ = kDefaultCacheSize;
+ return true;
+ }
+
+ // We want to use up to 2% of the computer's memory, with a limit of 50 MB,
+ // reached on systemd with more than 2.5 GB of RAM.
+ total_memory = total_memory * 2 / 100;
+ if (total_memory > kDefaultCacheSize * 5)
+ max_size_ = kDefaultCacheSize * 5;
+ else
+ max_size_ = static_cast<int32>(total_memory);
+
+ return true;
+}
+
+bool MemBackendImpl::SetMaxSize(int max_bytes) {
+ COMPILE_ASSERT(sizeof(max_bytes) == sizeof(max_size_), unsupported_int_model);
+ if (max_bytes < 0)
+ return false;
+
+ // Zero size means use the default.
+ if (!max_bytes)
+ return true;
+
+ max_size_ = max_bytes;
+ return true;
+}
+
+void MemBackendImpl::InternalDoomEntry(MemEntryImpl* entry) {
+ // Only parent entries can be passed into this method.
+ DCHECK(entry->type() == MemEntryImpl::kParentEntry);
+
+ rankings_.Remove(entry);
+ EntryMap::iterator it = entries_.find(entry->GetKey());
+ if (it != entries_.end())
+ entries_.erase(it);
+ else
+ NOTREACHED();
+
+ entry->InternalDoom();
+}
+
+void MemBackendImpl::UpdateRank(MemEntryImpl* node) {
+ rankings_.UpdateRank(node);
+}
+
+void MemBackendImpl::ModifyStorageSize(int32 old_size, int32 new_size) {
+ if (old_size >= new_size)
+ SubstractStorageSize(old_size - new_size);
+ else
+ AddStorageSize(new_size - old_size);
+}
+
+int MemBackendImpl::MaxFileSize() const {
+ return max_size_ / 8;
+}
+
+void MemBackendImpl::InsertIntoRankingList(MemEntryImpl* entry) {
+ rankings_.Insert(entry);
+}
+
+void MemBackendImpl::RemoveFromRankingList(MemEntryImpl* entry) {
+ rankings_.Remove(entry);
+}
+
+net::CacheType MemBackendImpl::GetCacheType() const {
+ return net::MEMORY_CACHE;
+}
+
+int32 MemBackendImpl::GetEntryCount() const {
+ return static_cast<int32>(entries_.size());
+}
+
+int MemBackendImpl::OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ if (OpenEntry(key, entry))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ if (CreateEntry(key, entry))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::DoomEntry(const std::string& key,
+ const CompletionCallback& callback) {
+ if (DoomEntry(key))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::DoomAllEntries(const CompletionCallback& callback) {
+ if (DoomAllEntries())
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ const CompletionCallback& callback) {
+ if (DoomEntriesBetween(initial_time, end_time))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::DoomEntriesSince(const base::Time initial_time,
+ const CompletionCallback& callback) {
+ if (DoomEntriesSince(initial_time))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+int MemBackendImpl::OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) {
+ if (OpenNextEntry(iter, next_entry))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
+void MemBackendImpl::EndEnumeration(void** iter) {
+ *iter = NULL;
+}
+
+void MemBackendImpl::OnExternalCacheHit(const std::string& key) {
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end()) {
+ UpdateRank(it->second);
+ }
+}
+
+bool MemBackendImpl::OpenEntry(const std::string& key, Entry** entry) {
+ EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end())
+ return false;
+
+ it->second->Open();
+
+ *entry = it->second;
+ return true;
+}
+
+bool MemBackendImpl::CreateEntry(const std::string& key, Entry** entry) {
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end())
+ return false;
+
+ MemEntryImpl* cache_entry = new MemEntryImpl(this);
+ if (!cache_entry->CreateEntry(key, net_log_)) {
+ delete entry;
+ return false;
+ }
+
+ rankings_.Insert(cache_entry);
+ entries_[key] = cache_entry;
+
+ *entry = cache_entry;
+ return true;
+}
+
+bool MemBackendImpl::DoomEntry(const std::string& key) {
+ Entry* entry;
+ if (!OpenEntry(key, &entry))
+ return false;
+
+ entry->Doom();
+ entry->Close();
+ return true;
+}
+
+bool MemBackendImpl::DoomAllEntries() {
+ TrimCache(true);
+ return true;
+}
+
+bool MemBackendImpl::DoomEntriesBetween(const Time initial_time,
+ const Time end_time) {
+ if (end_time.is_null())
+ return DoomEntriesSince(initial_time);
+
+ DCHECK(end_time >= initial_time);
+
+ MemEntryImpl* node = rankings_.GetNext(NULL);
+ // Last valid entry before |node|.
+ // Note, that entries after |node| may become invalid during |node| doom in
+ // case when they are child entries of it. It is guaranteed that
+ // parent node will go prior to it childs in ranking list (see
+ // InternalReadSparseData and InternalWriteSparseData).
+ MemEntryImpl* last_valid = NULL;
+
+ // rankings_ is ordered by last used, this will descend through the cache
+ // and start dooming items before the end_time, and will stop once it reaches
+ // an item used before the initial time.
+ while (node) {
+ if (node->GetLastUsed() < initial_time)
+ break;
+
+ if (node->GetLastUsed() < end_time)
+ node->Doom();
+ else
+ last_valid = node;
+ node = rankings_.GetNext(last_valid);
+ }
+
+ return true;
+}
+
+bool MemBackendImpl::DoomEntriesSince(const Time initial_time) {
+ for (;;) {
+ // Get the entry in the front.
+ Entry* entry = rankings_.GetNext(NULL);
+
+ // Break the loop when there are no more entries or the entry is too old.
+ if (!entry || entry->GetLastUsed() < initial_time)
+ return true;
+ entry->Doom();
+ }
+}
+
+bool MemBackendImpl::OpenNextEntry(void** iter, Entry** next_entry) {
+ MemEntryImpl* current = reinterpret_cast<MemEntryImpl*>(*iter);
+ MemEntryImpl* node = rankings_.GetNext(current);
+ // We should never return a child entry so iterate until we hit a parent
+ // entry.
+ while (node && node->type() != MemEntryImpl::kParentEntry) {
+ node = rankings_.GetNext(node);
+ }
+ *next_entry = node;
+ *iter = node;
+
+ if (node)
+ node->Open();
+
+ return NULL != node;
+}
+
+void MemBackendImpl::TrimCache(bool empty) {
+ MemEntryImpl* next = rankings_.GetPrev(NULL);
+ if (!next)
+ return;
+
+ int target_size = empty ? 0 : LowWaterAdjust(max_size_);
+ while (current_size_ > target_size && next) {
+ MemEntryImpl* node = next;
+ next = rankings_.GetPrev(next);
+ if (!node->InUse() || empty) {
+ node->Doom();
+ }
+ }
+
+ return;
+}
+
+void MemBackendImpl::AddStorageSize(int32 bytes) {
+ current_size_ += bytes;
+ DCHECK_GE(current_size_, 0);
+
+ if (current_size_ > max_size_)
+ TrimCache(false);
+}
+
+void MemBackendImpl::SubstractStorageSize(int32 bytes) {
+ current_size_ -= bytes;
+ DCHECK_GE(current_size_, 0);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mem_backend_impl.h b/chromium/net/disk_cache/mem_backend_impl.h
new file mode 100644
index 00000000000..8da39cc7f26
--- /dev/null
+++ b/chromium/net/disk_cache/mem_backend_impl.h
@@ -0,0 +1,120 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_MEM_BACKEND_IMPL_H__
+#define NET_DISK_CACHE_MEM_BACKEND_IMPL_H__
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/mem_rankings.h"
+
+namespace net {
+class NetLog;
+} // namespace net
+
+namespace disk_cache {
+
+class MemEntryImpl;
+
+// This class implements the Backend interface. An object of this class handles
+// the operations of the cache without writing to disk.
+class NET_EXPORT_PRIVATE MemBackendImpl : public Backend {
+ public:
+ explicit MemBackendImpl(net::NetLog* net_log);
+ virtual ~MemBackendImpl();
+
+ // Returns an instance of a Backend implemented only in memory. The returned
+ // object should be deleted when not needed anymore. max_bytes is the maximum
+ // size the cache can grow to. If zero is passed in as max_bytes, the cache
+ // will determine the value to use based on the available memory. The returned
+ // pointer can be NULL if a fatal error is found.
+ static scoped_ptr<Backend> CreateBackend(int max_bytes, net::NetLog* net_log);
+
+ // Performs general initialization for this current instance of the cache.
+ bool Init();
+
+ // Sets the maximum size for the total amount of data stored by this instance.
+ bool SetMaxSize(int max_bytes);
+
+ // Permanently deletes an entry.
+ void InternalDoomEntry(MemEntryImpl* entry);
+
+ // Updates the ranking information for an entry.
+ void UpdateRank(MemEntryImpl* node);
+
+ // A user data block is being created, extended or truncated.
+ void ModifyStorageSize(int32 old_size, int32 new_size);
+
+ // Returns the maximum size for a file to reside on the cache.
+ int MaxFileSize() const;
+
+ // Insert an MemEntryImpl into the ranking list. This method is only called
+ // from MemEntryImpl to insert child entries. The reference can be removed
+ // by calling RemoveFromRankingList(|entry|).
+ void InsertIntoRankingList(MemEntryImpl* entry);
+
+ // Remove |entry| from ranking list. This method is only called from
+ // MemEntryImpl to remove a child entry from the ranking list.
+ void RemoveFromRankingList(MemEntryImpl* entry);
+
+ // Backend interface.
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) OVERRIDE {}
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ private:
+ typedef base::hash_map<std::string, MemEntryImpl*> EntryMap;
+
+ // Old Backend interface.
+ bool OpenEntry(const std::string& key, Entry** entry);
+ bool CreateEntry(const std::string& key, Entry** entry);
+ bool DoomEntry(const std::string& key);
+ bool DoomAllEntries();
+ bool DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+ bool DoomEntriesSince(const base::Time initial_time);
+ bool OpenNextEntry(void** iter, Entry** next_entry);
+
+ // Deletes entries from the cache until the current size is below the limit.
+ // If empty is true, the whole cache will be trimmed, regardless of being in
+ // use.
+ void TrimCache(bool empty);
+
+ // Handles the used storage count.
+ void AddStorageSize(int32 bytes);
+ void SubstractStorageSize(int32 bytes);
+
+ EntryMap entries_;
+ MemRankings rankings_; // Rankings to be able to trim the cache.
+ int32 max_size_; // Maximum data size for this instance.
+ int32 current_size_;
+
+ net::NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemBackendImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_BACKEND_IMPL_H__
diff --git a/chromium/net/disk_cache/mem_entry_impl.cc b/chromium/net/disk_cache/mem_entry_impl.cc
new file mode 100644
index 00000000000..7d0095898b4
--- /dev/null
+++ b/chromium/net/disk_cache/mem_entry_impl.cc
@@ -0,0 +1,631 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/mem_entry_impl.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/mem_backend_impl.h"
+#include "net/disk_cache/net_log_parameters.h"
+
+using base::Time;
+
+namespace {
+
+const int kSparseData = 1;
+
+// Maximum size of a sparse entry is 2 to the power of this number.
+const int kMaxSparseEntryBits = 12;
+
+// Sparse entry has maximum size of 4KB.
+const int kMaxSparseEntrySize = 1 << kMaxSparseEntryBits;
+
+// Convert global offset to child index.
+inline int ToChildIndex(int64 offset) {
+ return static_cast<int>(offset >> kMaxSparseEntryBits);
+}
+
+// Convert global offset to offset in child entry.
+inline int ToChildOffset(int64 offset) {
+ return static_cast<int>(offset & (kMaxSparseEntrySize - 1));
+}
+
+// Returns a name for a child entry given the base_name of the parent and the
+// child_id. This name is only used for logging purposes.
+// If the entry is called entry_name, child entries will be named something
+// like Range_entry_name:YYY where YYY is the number of the particular child.
+std::string GenerateChildName(const std::string& base_name, int child_id) {
+ return base::StringPrintf("Range_%s:%i", base_name.c_str(), child_id);
+}
+
+// Returns NetLog parameters for the creation of a child MemEntryImpl. Separate
+// function needed because child entries don't suppport GetKey().
+base::Value* NetLogChildEntryCreationCallback(
+ const disk_cache::MemEntryImpl* parent,
+ int child_id,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("key", GenerateChildName(parent->GetKey(), child_id));
+ dict->SetBoolean("created", true);
+ return dict;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+MemEntryImpl::MemEntryImpl(MemBackendImpl* backend) {
+ doomed_ = false;
+ backend_ = backend;
+ ref_count_ = 0;
+ parent_ = NULL;
+ child_id_ = 0;
+ child_first_pos_ = 0;
+ next_ = NULL;
+ prev_ = NULL;
+ for (int i = 0; i < NUM_STREAMS; i++)
+ data_size_[i] = 0;
+}
+
+// ------------------------------------------------------------------------
+
+bool MemEntryImpl::CreateEntry(const std::string& key, net::NetLog* net_log) {
+ key_ = key;
+ Time current = Time::Now();
+ last_modified_ = current;
+ last_used_ = current;
+
+ net_log_ = net::BoundNetLog::Make(net_log,
+ net::NetLog::SOURCE_MEMORY_CACHE_ENTRY);
+ // Must be called after |key_| is set, so GetKey() works.
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL,
+ CreateNetLogEntryCreationCallback(this, true));
+
+ Open();
+ backend_->ModifyStorageSize(0, static_cast<int32>(key.size()));
+ return true;
+}
+
+void MemEntryImpl::InternalDoom() {
+ net_log_.AddEvent(net::NetLog::TYPE_ENTRY_DOOM);
+ doomed_ = true;
+ if (!ref_count_) {
+ if (type() == kParentEntry) {
+ // If this is a parent entry, we need to doom all the child entries.
+ if (children_.get()) {
+ EntryMap children;
+ children.swap(*children_);
+ for (EntryMap::iterator i = children.begin();
+ i != children.end(); ++i) {
+ // Since a pointer to this object is also saved in the map, avoid
+ // dooming it.
+ if (i->second != this)
+ i->second->Doom();
+ }
+ DCHECK(children_->empty());
+ }
+ } else {
+ // If this is a child entry, detach it from the parent.
+ parent_->DetachChild(child_id_);
+ }
+ delete this;
+ }
+}
+
+void MemEntryImpl::Open() {
+ // Only a parent entry can be opened.
+ // TODO(hclam): make sure it's correct to not apply the concept of ref
+ // counting to child entry.
+ DCHECK(type() == kParentEntry);
+ ref_count_++;
+ DCHECK_GE(ref_count_, 0);
+ DCHECK(!doomed_);
+}
+
+bool MemEntryImpl::InUse() {
+ if (type() == kParentEntry) {
+ return ref_count_ > 0;
+ } else {
+ // A child entry is always not in use. The consequence is that a child entry
+ // can always be evicted while the associated parent entry is currently in
+ // used (i.e. opened).
+ return false;
+ }
+}
+
+// ------------------------------------------------------------------------
+
+void MemEntryImpl::Doom() {
+ if (doomed_)
+ return;
+ if (type() == kParentEntry) {
+ // Perform internal doom from the backend if this is a parent entry.
+ backend_->InternalDoomEntry(this);
+ } else {
+ // Manually detach from the backend and perform internal doom.
+ backend_->RemoveFromRankingList(this);
+ InternalDoom();
+ }
+}
+
+void MemEntryImpl::Close() {
+ // Only a parent entry can be closed.
+ DCHECK(type() == kParentEntry);
+ ref_count_--;
+ DCHECK_GE(ref_count_, 0);
+ if (!ref_count_ && doomed_)
+ InternalDoom();
+}
+
+std::string MemEntryImpl::GetKey() const {
+ // A child entry doesn't have key so this method should not be called.
+ DCHECK(type() == kParentEntry);
+ return key_;
+}
+
+Time MemEntryImpl::GetLastUsed() const {
+ return last_used_;
+}
+
+Time MemEntryImpl::GetLastModified() const {
+ return last_modified_;
+}
+
+int32 MemEntryImpl::GetDataSize(int index) const {
+ if (index < 0 || index >= NUM_STREAMS)
+ return 0;
+ return data_size_[index];
+}
+
+int MemEntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, false));
+ }
+
+ int result = InternalReadData(index, offset, buf, buf_len);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int MemEntryImpl::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, truncate));
+ }
+
+ int result = InternalWriteData(index, offset, buf, buf_len, truncate);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int MemEntryImpl::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_SPARSE_READ,
+ CreateNetLogSparseOperationCallback(offset, buf_len));
+ }
+ int result = InternalReadSparseData(offset, buf, buf_len);
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.EndEvent(net::NetLog::TYPE_SPARSE_READ);
+ return result;
+}
+
+int MemEntryImpl::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_SPARSE_WRITE,
+ CreateNetLogSparseOperationCallback(offset, buf_len));
+ }
+ int result = InternalWriteSparseData(offset, buf, buf_len);
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.EndEvent(net::NetLog::TYPE_SPARSE_WRITE);
+ return result;
+}
+
+int MemEntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_SPARSE_GET_RANGE,
+ CreateNetLogSparseOperationCallback(offset, len));
+ }
+ int result = GetAvailableRange(offset, len, start);
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_SPARSE_GET_RANGE,
+ CreateNetLogGetAvailableRangeResultCallback(*start, result));
+ }
+ return result;
+}
+
+bool MemEntryImpl::CouldBeSparse() const {
+ DCHECK_EQ(kParentEntry, type());
+ return (children_.get() != NULL);
+}
+
+int MemEntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
+ return net::OK;
+}
+
+// ------------------------------------------------------------------------
+
+MemEntryImpl::~MemEntryImpl() {
+ for (int i = 0; i < NUM_STREAMS; i++)
+ backend_->ModifyStorageSize(data_size_[i], 0);
+ backend_->ModifyStorageSize(static_cast<int32>(key_.size()), 0);
+ net_log_.EndEvent(net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL);
+}
+
+int MemEntryImpl::InternalReadData(int index, int offset, IOBuffer* buf,
+ int buf_len) {
+ DCHECK(type() == kParentEntry || index == kSparseData);
+
+ if (index < 0 || index >= NUM_STREAMS)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = GetDataSize(index);
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset + buf_len > entry_size)
+ buf_len = entry_size - offset;
+
+ UpdateRank(false);
+
+ memcpy(buf->data(), &(data_[index])[offset], buf_len);
+ return buf_len;
+}
+
+int MemEntryImpl::InternalWriteData(int index, int offset, IOBuffer* buf,
+ int buf_len, bool truncate) {
+ DCHECK(type() == kParentEntry || index == kSparseData);
+
+ if (index < 0 || index >= NUM_STREAMS)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int max_file_size = backend_->MaxFileSize();
+
+ // offset of buf_len could be negative numbers.
+ if (offset > max_file_size || buf_len > max_file_size ||
+ offset + buf_len > max_file_size) {
+ return net::ERR_FAILED;
+ }
+
+ // Read the size at this point.
+ int entry_size = GetDataSize(index);
+
+ PrepareTarget(index, offset, buf_len);
+
+ if (entry_size < offset + buf_len) {
+ backend_->ModifyStorageSize(entry_size, offset + buf_len);
+ data_size_[index] = offset + buf_len;
+ } else if (truncate) {
+ if (entry_size > offset + buf_len) {
+ backend_->ModifyStorageSize(entry_size, offset + buf_len);
+ data_size_[index] = offset + buf_len;
+ }
+ }
+
+ UpdateRank(true);
+
+ if (!buf_len)
+ return 0;
+
+ memcpy(&(data_[index])[offset], buf->data(), buf_len);
+ return buf_len;
+}
+
+int MemEntryImpl::InternalReadSparseData(int64 offset, IOBuffer* buf,
+ int buf_len) {
+ DCHECK(type() == kParentEntry);
+
+ if (!InitSparseInfo())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ // We will keep using this buffer and adjust the offset in this buffer.
+ scoped_refptr<net::DrainableIOBuffer> io_buf(
+ new net::DrainableIOBuffer(buf, buf_len));
+
+ // Iterate until we have read enough.
+ while (io_buf->BytesRemaining()) {
+ MemEntryImpl* child = OpenChild(offset + io_buf->BytesConsumed(), false);
+
+ // No child present for that offset.
+ if (!child)
+ break;
+
+ // We then need to prepare the child offset and len.
+ int child_offset = ToChildOffset(offset + io_buf->BytesConsumed());
+
+ // If we are trying to read from a position that the child entry has no data
+ // we should stop.
+ if (child_offset < child->child_first_pos_)
+ break;
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_SPARSE_READ_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child->net_log().source(),
+ io_buf->BytesRemaining()));
+ }
+ int ret = child->ReadData(kSparseData, child_offset, io_buf.get(),
+ io_buf->BytesRemaining(), CompletionCallback());
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(
+ net::NetLog::TYPE_SPARSE_READ_CHILD_DATA, ret);
+ }
+
+ // If we encounter an error in one entry, return immediately.
+ if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ break;
+
+ // Increment the counter by number of bytes read in the child entry.
+ io_buf->DidConsume(ret);
+ }
+
+ UpdateRank(false);
+
+ return io_buf->BytesConsumed();
+}
+
+int MemEntryImpl::InternalWriteSparseData(int64 offset, IOBuffer* buf,
+ int buf_len) {
+ DCHECK(type() == kParentEntry);
+
+ if (!InitSparseInfo())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ scoped_refptr<net::DrainableIOBuffer> io_buf(
+ new net::DrainableIOBuffer(buf, buf_len));
+
+ // This loop walks through child entries continuously starting from |offset|
+ // and writes blocks of data (of maximum size kMaxSparseEntrySize) into each
+ // child entry until all |buf_len| bytes are written. The write operation can
+ // start in the middle of an entry.
+ while (io_buf->BytesRemaining()) {
+ MemEntryImpl* child = OpenChild(offset + io_buf->BytesConsumed(), true);
+ int child_offset = ToChildOffset(offset + io_buf->BytesConsumed());
+
+ // Find the right amount to write, this evaluates the remaining bytes to
+ // write and remaining capacity of this child entry.
+ int write_len = std::min(static_cast<int>(io_buf->BytesRemaining()),
+ kMaxSparseEntrySize - child_offset);
+
+ // Keep a record of the last byte position (exclusive) in the child.
+ int data_size = child->GetDataSize(kSparseData);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child->net_log().source(),
+ write_len));
+ }
+
+ // Always writes to the child entry. This operation may overwrite data
+ // previously written.
+ // TODO(hclam): if there is data in the entry and this write is not
+ // continuous we may want to discard this write.
+ int ret = child->WriteData(kSparseData, child_offset, io_buf.get(),
+ write_len, CompletionCallback(), true);
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(
+ net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA, ret);
+ }
+ if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ break;
+
+ // Keep a record of the first byte position in the child if the write was
+ // not aligned nor continuous. This is to enable witting to the middle
+ // of an entry and still keep track of data off the aligned edge.
+ if (data_size != child_offset)
+ child->child_first_pos_ = child_offset;
+
+ // Adjust the offset in the IO buffer.
+ io_buf->DidConsume(ret);
+ }
+
+ UpdateRank(true);
+
+ return io_buf->BytesConsumed();
+}
+
+int MemEntryImpl::GetAvailableRange(int64 offset, int len, int64* start) {
+ DCHECK(type() == kParentEntry);
+ DCHECK(start);
+
+ if (!InitSparseInfo())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (offset < 0 || len < 0 || !start)
+ return net::ERR_INVALID_ARGUMENT;
+
+ MemEntryImpl* current_child = NULL;
+
+ // Find the first child and record the number of empty bytes.
+ int empty = FindNextChild(offset, len, &current_child);
+ if (current_child) {
+ *start = offset + empty;
+ len -= empty;
+
+ // Counts the number of continuous bytes.
+ int continuous = 0;
+
+ // This loop scan for continuous bytes.
+ while (len && current_child) {
+ // Number of bytes available in this child.
+ int data_size = current_child->GetDataSize(kSparseData) -
+ ToChildOffset(*start + continuous);
+ if (data_size > len)
+ data_size = len;
+
+ // We have found more continuous bytes so increment the count. Also
+ // decrement the length we should scan.
+ continuous += data_size;
+ len -= data_size;
+
+ // If the next child is discontinuous, break the loop.
+ if (FindNextChild(*start + continuous, len, &current_child))
+ break;
+ }
+ return continuous;
+ }
+ *start = offset;
+ return 0;
+}
+
+void MemEntryImpl::PrepareTarget(int index, int offset, int buf_len) {
+ int entry_size = GetDataSize(index);
+
+ if (entry_size >= offset + buf_len)
+ return; // Not growing the stored data.
+
+ if (static_cast<int>(data_[index].size()) < offset + buf_len)
+ data_[index].resize(offset + buf_len);
+
+ if (offset <= entry_size)
+ return; // There is no "hole" on the stored data.
+
+ // Cleanup the hole not written by the user. The point is to avoid returning
+ // random stuff later on.
+ memset(&(data_[index])[entry_size], 0, offset - entry_size);
+}
+
+void MemEntryImpl::UpdateRank(bool modified) {
+ Time current = Time::Now();
+ last_used_ = current;
+
+ if (modified)
+ last_modified_ = current;
+
+ if (!doomed_)
+ backend_->UpdateRank(this);
+}
+
+bool MemEntryImpl::InitSparseInfo() {
+ DCHECK(type() == kParentEntry);
+
+ if (!children_.get()) {
+ // If we already have some data in sparse stream but we are being
+ // initialized as a sparse entry, we should fail.
+ if (GetDataSize(kSparseData))
+ return false;
+ children_.reset(new EntryMap());
+
+ // The parent entry stores data for the first block, so save this object to
+ // index 0.
+ (*children_)[0] = this;
+ }
+ return true;
+}
+
+bool MemEntryImpl::InitChildEntry(MemEntryImpl* parent, int child_id,
+ net::NetLog* net_log) {
+ DCHECK(!parent_);
+ DCHECK(!child_id_);
+
+ net_log_ = net::BoundNetLog::Make(net_log,
+ net::NetLog::SOURCE_MEMORY_CACHE_ENTRY);
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL,
+ base::Bind(&NetLogChildEntryCreationCallback, parent, child_id_));
+
+ parent_ = parent;
+ child_id_ = child_id;
+ Time current = Time::Now();
+ last_modified_ = current;
+ last_used_ = current;
+ // Insert this to the backend's ranking list.
+ backend_->InsertIntoRankingList(this);
+ return true;
+}
+
+MemEntryImpl* MemEntryImpl::OpenChild(int64 offset, bool create) {
+ DCHECK(type() == kParentEntry);
+ int index = ToChildIndex(offset);
+ EntryMap::iterator i = children_->find(index);
+ if (i != children_->end()) {
+ return i->second;
+ } else if (create) {
+ MemEntryImpl* child = new MemEntryImpl(backend_);
+ child->InitChildEntry(this, index, net_log_.net_log());
+ (*children_)[index] = child;
+ return child;
+ }
+ return NULL;
+}
+
+int MemEntryImpl::FindNextChild(int64 offset, int len, MemEntryImpl** child) {
+ DCHECK(child);
+ *child = NULL;
+ int scanned_len = 0;
+
+ // This loop tries to find the first existing child.
+ while (scanned_len < len) {
+ // This points to the current offset in the child.
+ int current_child_offset = ToChildOffset(offset + scanned_len);
+ MemEntryImpl* current_child = OpenChild(offset + scanned_len, false);
+ if (current_child) {
+ int child_first_pos = current_child->child_first_pos_;
+
+ // This points to the first byte that we should be reading from, we need
+ // to take care of the filled region and the current offset in the child.
+ int first_pos = std::max(current_child_offset, child_first_pos);
+
+ // If the first byte position we should read from doesn't exceed the
+ // filled region, we have found the first child.
+ if (first_pos < current_child->GetDataSize(kSparseData)) {
+ *child = current_child;
+
+ // We need to advance the scanned length.
+ scanned_len += first_pos - current_child_offset;
+ break;
+ }
+ }
+ scanned_len += kMaxSparseEntrySize - current_child_offset;
+ }
+ return scanned_len;
+}
+
+void MemEntryImpl::DetachChild(int child_id) {
+ children_->erase(child_id);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mem_entry_impl.h b/chromium/net/disk_cache/mem_entry_impl.h
new file mode 100644
index 00000000000..ef91f6d7b0c
--- /dev/null
+++ b/chromium/net/disk_cache/mem_entry_impl.h
@@ -0,0 +1,189 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_MEM_ENTRY_IMPL_H_
+#define NET_DISK_CACHE_MEM_ENTRY_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace disk_cache {
+
+class MemBackendImpl;
+
+// This class implements the Entry interface for the memory-only cache. An
+// object of this class represents a single entry on the cache. We use two
+// types of entries, parent and child to support sparse caching.
+//
+// A parent entry is non-sparse until a sparse method is invoked (i.e.
+// ReadSparseData, WriteSparseData, GetAvailableRange) when sparse information
+// is initialized. It then manages a list of child entries and delegates the
+// sparse API calls to the child entries. It creates and deletes child entries
+// and updates the list when needed.
+//
+// A child entry is used to carry partial cache content, non-sparse methods like
+// ReadData and WriteData cannot be applied to them. The lifetime of a child
+// entry is managed by the parent entry that created it except that the entry
+// can be evicted independently. A child entry does not have a key and it is not
+// registered in the backend's entry map. It is registered in the backend's
+// ranking list to enable eviction of a partial content.
+//
+// A sparse entry has a fixed maximum size and can be partially filled. There
+// can only be one continous filled region in a sparse entry, as illustrated by
+// the following example:
+// | xxx ooooo |
+// x = unfilled region
+// o = filled region
+// It is guranteed that there is at most one unfilled region and one filled
+// region, and the unfilled region (if there is one) is always before the filled
+// region. The book keeping for filled region in a sparse entry is done by using
+// the variable |child_first_pos_| (inclusive).
+
+class MemEntryImpl : public Entry {
+ public:
+ enum EntryType {
+ kParentEntry,
+ kChildEntry,
+ };
+
+ explicit MemEntryImpl(MemBackendImpl* backend);
+
+ // Performs the initialization of a EntryImpl that will be added to the
+ // cache.
+ bool CreateEntry(const std::string& key, net::NetLog* net_log);
+
+ // Permanently destroys this entry.
+ void InternalDoom();
+
+ void Open();
+ bool InUse();
+
+ MemEntryImpl* next() const {
+ return next_;
+ }
+
+ MemEntryImpl* prev() const {
+ return prev_;
+ }
+
+ void set_next(MemEntryImpl* next) {
+ next_ = next;
+ }
+
+ void set_prev(MemEntryImpl* prev) {
+ prev_ = prev;
+ }
+
+ EntryType type() const {
+ return parent_ ? kChildEntry : kParentEntry;
+ }
+
+ std::string& key() {
+ return key_;
+ }
+
+ net::BoundNetLog& net_log() {
+ return net_log_;
+ }
+
+ // Entry interface.
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE {}
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ typedef base::hash_map<int, MemEntryImpl*> EntryMap;
+
+ enum {
+ NUM_STREAMS = 3
+ };
+
+ virtual ~MemEntryImpl();
+
+ // Do all the work for corresponding public functions. Implemented as
+ // separate functions to make logging of results simpler.
+ int InternalReadData(int index, int offset, IOBuffer* buf, int buf_len);
+ int InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ bool truncate);
+ int InternalReadSparseData(int64 offset, IOBuffer* buf, int buf_len);
+ int InternalWriteSparseData(int64 offset, IOBuffer* buf, int buf_len);
+
+ // Old Entry interface.
+ int GetAvailableRange(int64 offset, int len, int64* start);
+
+ // Grows and cleans up the data buffer.
+ void PrepareTarget(int index, int offset, int buf_len);
+
+ // Updates ranking information.
+ void UpdateRank(bool modified);
+
+ // Initializes the children map and sparse info. This method is only called
+ // on a parent entry.
+ bool InitSparseInfo();
+
+ // Performs the initialization of a MemEntryImpl as a child entry.
+ // |parent| is the pointer to the parent entry. |child_id| is the ID of
+ // the new child.
+ bool InitChildEntry(MemEntryImpl* parent, int child_id, net::NetLog* net_log);
+
+ // Returns an entry responsible for |offset|. The returned entry can be a
+ // child entry or this entry itself if |offset| points to the first range.
+ // If such entry does not exist and |create| is true, a new child entry is
+ // created.
+ MemEntryImpl* OpenChild(int64 offset, bool create);
+
+ // Finds the first child located within the range [|offset|, |offset + len|).
+ // Returns the number of bytes ahead of |offset| to reach the first available
+ // bytes in the entry. The first child found is output to |child|.
+ int FindNextChild(int64 offset, int len, MemEntryImpl** child);
+
+ // Removes child indexed by |child_id| from the children map.
+ void DetachChild(int child_id);
+
+ std::string key_;
+ std::vector<char> data_[NUM_STREAMS]; // User data.
+ int32 data_size_[NUM_STREAMS];
+ int ref_count_;
+
+ int child_id_; // The ID of a child entry.
+ int child_first_pos_; // The position of the first byte in a child
+ // entry.
+ MemEntryImpl* next_; // Pointers for the LRU list.
+ MemEntryImpl* prev_;
+ MemEntryImpl* parent_; // Pointer to the parent entry.
+ scoped_ptr<EntryMap> children_;
+
+ base::Time last_modified_; // LRU information.
+ base::Time last_used_;
+ MemBackendImpl* backend_; // Back pointer to the cache.
+ bool doomed_; // True if this entry was removed from the cache.
+
+ net::BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemEntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_ENTRY_IMPL_H_
diff --git a/chromium/net/disk_cache/mem_rankings.cc b/chromium/net/disk_cache/mem_rankings.cc
new file mode 100644
index 00000000000..d5f4a6536a7
--- /dev/null
+++ b/chromium/net/disk_cache/mem_rankings.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2006-2008 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/disk_cache/mem_rankings.h"
+
+#include "base/logging.h"
+#include "net/disk_cache/mem_entry_impl.h"
+
+namespace disk_cache {
+
+MemRankings::~MemRankings() {
+ DCHECK(!head_ && !tail_);
+}
+
+void MemRankings::Insert(MemEntryImpl* node) {
+ if (head_)
+ head_->set_prev(node);
+
+ if (!tail_)
+ tail_ = node;
+
+ node->set_prev(NULL);
+ node->set_next(head_);
+ head_ = node;
+}
+
+void MemRankings::Remove(MemEntryImpl* node) {
+ MemEntryImpl* prev = node->prev();
+ MemEntryImpl* next = node->next();
+
+ if (head_ == node)
+ head_ = next;
+
+ if (tail_ == node)
+ tail_ = prev;
+
+ if (prev)
+ prev->set_next(next);
+
+ if (next)
+ next->set_prev(prev);
+
+ node->set_next(NULL);
+ node->set_prev(NULL);
+}
+
+void MemRankings::UpdateRank(MemEntryImpl* node) {
+ Remove(node);
+ Insert(node);
+}
+
+MemEntryImpl* MemRankings::GetNext(MemEntryImpl* node) {
+ if (!node)
+ return head_;
+
+ return node->next();
+}
+
+MemEntryImpl* MemRankings::GetPrev(MemEntryImpl* node) {
+ if (!node)
+ return tail_;
+
+ return node->prev();
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/mem_rankings.h b/chromium/net/disk_cache/mem_rankings.h
new file mode 100644
index 00000000000..fa906888639
--- /dev/null
+++ b/chromium/net/disk_cache/mem_rankings.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2010 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.
+
+// See net/disk_cache/disk_cache.h for the public interface.
+
+#ifndef NET_DISK_CACHE_MEM_RANKINGS_H__
+#define NET_DISK_CACHE_MEM_RANKINGS_H__
+
+#include "base/basictypes.h"
+
+namespace disk_cache {
+
+class MemEntryImpl;
+
+// This class handles the ranking information for the memory-only cache.
+class MemRankings {
+ public:
+ MemRankings() : head_(NULL), tail_(NULL) {}
+ ~MemRankings();
+
+ // Inserts a given entry at the head of the queue.
+ void Insert(MemEntryImpl* node);
+
+ // Removes a given entry from the LRU list.
+ void Remove(MemEntryImpl* node);
+
+ // Moves a given entry to the head.
+ void UpdateRank(MemEntryImpl* node);
+
+ // Iterates through the list.
+ MemEntryImpl* GetNext(MemEntryImpl* node);
+ MemEntryImpl* GetPrev(MemEntryImpl* node);
+
+ private:
+ MemEntryImpl* head_;
+ MemEntryImpl* tail_;
+
+ DISALLOW_COPY_AND_ASSIGN(MemRankings);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_MEM_RANKINGS_H__
diff --git a/chromium/net/disk_cache/net_log_parameters.cc b/chromium/net/disk_cache/net_log_parameters.cc
new file mode 100644
index 00000000000..5d7e50f5595
--- /dev/null
+++ b/chromium/net/disk_cache/net_log_parameters.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/net_log_parameters.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+
+namespace {
+
+base::Value* NetLogEntryCreationCallback(
+ const disk_cache::Entry* entry,
+ bool created,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("key", entry->GetKey());
+ dict->SetBoolean("created", created);
+ return dict;
+}
+
+base::Value* NetLogReadWriteDataCallback(
+ int index,
+ int offset,
+ int buf_len,
+ bool truncate,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("index", index);
+ dict->SetInteger("offset", offset);
+ dict->SetInteger("buf_len", buf_len);
+ if (truncate)
+ dict->SetBoolean("truncate", truncate);
+ return dict;
+}
+
+base::Value* NetLogReadWriteCompleteCallback(
+ int bytes_copied,
+ net::NetLog::LogLevel /* log_level */) {
+ DCHECK_NE(bytes_copied, net::ERR_IO_PENDING);
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (bytes_copied < 0) {
+ dict->SetInteger("net_error", bytes_copied);
+ } else {
+ dict->SetInteger("bytes_copied", bytes_copied);
+ }
+ return dict;
+}
+
+base::Value* NetLogSparseOperationCallback(
+ int64 offset,
+ int buff_len,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ // Values can only be created with at most 32-bit integers. Using a string
+ // instead circumvents that restriction.
+ dict->SetString("offset", base::Int64ToString(offset));
+ dict->SetInteger("buff_len", buff_len);
+ return dict;
+}
+
+base::Value* NetLogSparseReadWriteCallback(
+ const net::NetLog::Source& source,
+ int child_len,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ source.AddToEventParameters(dict);
+ dict->SetInteger("child_len", child_len);
+ return dict;
+}
+
+base::Value* NetLogGetAvailableRangeResultCallback(
+ int64 start,
+ int result,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (result > 0) {
+ dict->SetInteger("length", result);
+ dict->SetString("start", base::Int64ToString(start));
+ } else {
+ dict->SetInteger("net_error", result);
+ }
+ return dict;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+net::NetLog::ParametersCallback CreateNetLogEntryCreationCallback(
+ const Entry* entry,
+ bool created) {
+ DCHECK(entry);
+ return base::Bind(&NetLogEntryCreationCallback, entry, created);
+}
+
+net::NetLog::ParametersCallback CreateNetLogReadWriteDataCallback(
+ int index,
+ int offset,
+ int buf_len,
+ bool truncate) {
+ return base::Bind(&NetLogReadWriteDataCallback,
+ index, offset, buf_len, truncate);
+}
+
+net::NetLog::ParametersCallback CreateNetLogReadWriteCompleteCallback(
+ int bytes_copied) {
+ return base::Bind(&NetLogReadWriteCompleteCallback, bytes_copied);
+}
+
+net::NetLog::ParametersCallback CreateNetLogSparseOperationCallback(
+ int64 offset,
+ int buff_len) {
+ return base::Bind(&NetLogSparseOperationCallback, offset, buff_len);
+}
+
+net::NetLog::ParametersCallback CreateNetLogSparseReadWriteCallback(
+ const net::NetLog::Source& source,
+ int child_len) {
+ return base::Bind(&NetLogSparseReadWriteCallback, source, child_len);
+}
+
+net::NetLog::ParametersCallback CreateNetLogGetAvailableRangeResultCallback(
+ int64 start,
+ int result) {
+ return base::Bind(&NetLogGetAvailableRangeResultCallback, start, result);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/net_log_parameters.h b/chromium/net/disk_cache/net_log_parameters.h
new file mode 100644
index 00000000000..3598cda7b12
--- /dev/null
+++ b/chromium/net/disk_cache/net_log_parameters.h
@@ -0,0 +1,62 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_NET_LOG_PARAMETERS_H_
+#define NET_DISK_CACHE_NET_LOG_PARAMETERS_H_
+
+#include <string>
+
+#include "net/base/net_log.h"
+
+// This file contains a set of functions to create NetLog::ParametersCallbacks
+// shared by EntryImpls and MemEntryImpls.
+namespace disk_cache {
+
+class Entry;
+
+// Creates a NetLog callback that returns parameters for the creation of an
+// Entry. Contains the Entry's key and whether it was created or opened.
+// |entry| can't be NULL, must support GetKey(), and must outlive the returned
+// callback.
+net::NetLog::ParametersCallback CreateNetLogEntryCreationCallback(
+ const Entry* entry,
+ bool created);
+
+// Creates a NetLog callback that returns parameters for start of a non-sparse
+// read or write of an Entry. For reads, |truncate| must be false.
+net::NetLog::ParametersCallback CreateNetLogReadWriteDataCallback(
+ int index,
+ int offset,
+ int buf_len,
+ bool truncate);
+
+// Creates a NetLog callback that returns parameters for when a non-sparse
+// read or write completes. For reads, |truncate| must be false.
+// |bytes_copied| is either the number of bytes copied or a network error
+// code. |bytes_copied| must not be ERR_IO_PENDING, as it's not a valid
+// result for an operation.
+net::NetLog::ParametersCallback CreateNetLogReadWriteCompleteCallback(
+ int bytes_copied);
+
+// Creates a NetLog callback that returns parameters for when a sparse
+// operation is started.
+net::NetLog::ParametersCallback CreateNetLogSparseOperationCallback(
+ int64 offset,
+ int buff_len);
+
+// Creates a NetLog callback that returns parameters for when a read or write
+// for a sparse entry's child is started.
+net::NetLog::ParametersCallback CreateNetLogSparseReadWriteCallback(
+ const net::NetLog::Source& source,
+ int child_len);
+
+// Creates a NetLog callback that returns parameters for when a call to
+// GetAvailableRange returns.
+net::NetLog::ParametersCallback CreateNetLogGetAvailableRangeResultCallback(
+ int64 start,
+ int result);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_NET_LOG_CACHE_PARAMETERS_H_
diff --git a/chromium/net/disk_cache/rankings.cc b/chromium/net/disk_cache/rankings.cc
new file mode 100644
index 00000000000..ff9913e252a
--- /dev/null
+++ b/chromium/net/disk_cache/rankings.cc
@@ -0,0 +1,922 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/rankings.h"
+
+#include "base/metrics/histogram.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/stress_support.h"
+
+using base::Time;
+using base::TimeTicks;
+
+namespace disk_cache {
+// This is used by crash_cache.exe to generate unit test files.
+NET_EXPORT_PRIVATE RankCrashes g_rankings_crash = NO_CRASH;
+}
+
+namespace {
+
+enum Operation {
+ INSERT = 1,
+ REMOVE
+};
+
+// This class provides a simple lock for the LRU list of rankings. Whenever an
+// entry is to be inserted or removed from the list, a transaction object should
+// be created to keep track of the operation. If the process crashes before
+// finishing the operation, the transaction record (stored as part of the user
+// data on the file header) can be used to finish the operation.
+class Transaction {
+ public:
+ // addr is the cache addres of the node being inserted or removed. We want to
+ // avoid having the compiler doing optimizations on when to read or write
+ // from user_data because it is the basis of the crash detection. Maybe
+ // volatile is not enough for that, but it should be a good hint.
+ Transaction(volatile disk_cache::LruData* data, disk_cache::Addr addr,
+ Operation op, int list);
+ ~Transaction();
+ private:
+ volatile disk_cache::LruData* data_;
+ DISALLOW_COPY_AND_ASSIGN(Transaction);
+};
+
+Transaction::Transaction(volatile disk_cache::LruData* data,
+ disk_cache::Addr addr, Operation op, int list)
+ : data_(data) {
+ DCHECK(!data_->transaction);
+ DCHECK(addr.is_initialized());
+ data_->operation = op;
+ data_->operation_list = list;
+ data_->transaction = addr.value();
+}
+
+Transaction::~Transaction() {
+ DCHECK(data_->transaction);
+ data_->transaction = 0;
+ data_->operation = 0;
+ data_->operation_list = 0;
+}
+
+// Code locations that can generate crashes.
+enum CrashLocation {
+ ON_INSERT_1, ON_INSERT_2, ON_INSERT_3, ON_INSERT_4, ON_REMOVE_1, ON_REMOVE_2,
+ ON_REMOVE_3, ON_REMOVE_4, ON_REMOVE_5, ON_REMOVE_6, ON_REMOVE_7, ON_REMOVE_8
+};
+
+#ifndef NDEBUG
+void TerminateSelf() {
+#if defined(OS_WIN)
+ // Windows does more work on _exit() than we would like, so we force exit.
+ TerminateProcess(GetCurrentProcess(), 0);
+#elif defined(OS_POSIX)
+ // On POSIX, _exit() will terminate the process with minimal cleanup,
+ // and it is cleaner than killing.
+ _exit(0);
+#endif
+}
+#endif // NDEBUG
+
+// Generates a crash on debug builds, acording to the value of g_rankings_crash.
+// This used by crash_cache.exe to generate unit-test files.
+void GenerateCrash(CrashLocation location) {
+#ifndef NDEBUG
+ if (disk_cache::NO_CRASH == disk_cache::g_rankings_crash)
+ return;
+ switch (location) {
+ case ON_INSERT_1:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::INSERT_ONE_1:
+ case disk_cache::INSERT_LOAD_1:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ case ON_INSERT_2:
+ if (disk_cache::INSERT_EMPTY_1 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_INSERT_3:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::INSERT_EMPTY_2:
+ case disk_cache::INSERT_ONE_2:
+ case disk_cache::INSERT_LOAD_2:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ case ON_INSERT_4:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::INSERT_EMPTY_3:
+ case disk_cache::INSERT_ONE_3:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ case ON_REMOVE_1:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::REMOVE_ONE_1:
+ case disk_cache::REMOVE_HEAD_1:
+ case disk_cache::REMOVE_TAIL_1:
+ case disk_cache::REMOVE_LOAD_1:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ case ON_REMOVE_2:
+ if (disk_cache::REMOVE_ONE_2 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_REMOVE_3:
+ if (disk_cache::REMOVE_ONE_3 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_REMOVE_4:
+ if (disk_cache::REMOVE_HEAD_2 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_REMOVE_5:
+ if (disk_cache::REMOVE_TAIL_2 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_REMOVE_6:
+ if (disk_cache::REMOVE_TAIL_3 == disk_cache::g_rankings_crash)
+ TerminateSelf();
+ break;
+ case ON_REMOVE_7:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::REMOVE_ONE_4:
+ case disk_cache::REMOVE_LOAD_2:
+ case disk_cache::REMOVE_HEAD_3:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ case ON_REMOVE_8:
+ switch (disk_cache::g_rankings_crash) {
+ case disk_cache::REMOVE_HEAD_4:
+ case disk_cache::REMOVE_LOAD_3:
+ TerminateSelf();
+ default:
+ break;
+ }
+ break;
+ default:
+ NOTREACHED();
+ return;
+ }
+#endif // NDEBUG
+}
+
+// Update the timestamp fields of |node|.
+void UpdateTimes(disk_cache::CacheRankingsBlock* node, bool modified) {
+ base::Time now = base::Time::Now();
+ node->Data()->last_used = now.ToInternalValue();
+ if (modified)
+ node->Data()->last_modified = now.ToInternalValue();
+}
+
+} // namespace
+
+namespace disk_cache {
+
+Rankings::ScopedRankingsBlock::ScopedRankingsBlock() : rankings_(NULL) {}
+
+Rankings::ScopedRankingsBlock::ScopedRankingsBlock(Rankings* rankings)
+ : rankings_(rankings) {}
+
+Rankings::ScopedRankingsBlock::ScopedRankingsBlock(
+ Rankings* rankings, CacheRankingsBlock* node)
+ : scoped_ptr<CacheRankingsBlock>(node), rankings_(rankings) {}
+
+Rankings::Iterator::Iterator(Rankings* rankings) {
+ memset(this, 0, sizeof(Iterator));
+ my_rankings = rankings;
+}
+
+Rankings::Iterator::~Iterator() {
+ for (int i = 0; i < 3; i++)
+ ScopedRankingsBlock(my_rankings, nodes[i]);
+}
+
+Rankings::Rankings() : init_(false) {}
+
+Rankings::~Rankings() {}
+
+bool Rankings::Init(BackendImpl* backend, bool count_lists) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ backend_ = backend;
+ control_data_ = backend_->GetLruData();
+ count_lists_ = count_lists;
+
+ ReadHeads();
+ ReadTails();
+
+ if (control_data_->transaction)
+ CompleteTransaction();
+
+ init_ = true;
+ return true;
+}
+
+void Rankings::Reset() {
+ init_ = false;
+ for (int i = 0; i < LAST_ELEMENT; i++) {
+ heads_[i].set_value(0);
+ tails_[i].set_value(0);
+ }
+ control_data_ = NULL;
+}
+
+void Rankings::Insert(CacheRankingsBlock* node, bool modified, List list) {
+ Trace("Insert 0x%x l %d", node->address().value(), list);
+ DCHECK(node->HasData());
+ Addr& my_head = heads_[list];
+ Addr& my_tail = tails_[list];
+ Transaction lock(control_data_, node->address(), INSERT, list);
+ CacheRankingsBlock head(backend_->File(my_head), my_head);
+ if (my_head.is_initialized()) {
+ if (!GetRanking(&head))
+ return;
+
+ if (head.Data()->prev != my_head.value() && // Normal path.
+ head.Data()->prev != node->address().value()) { // FinishInsert().
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return;
+ }
+
+ head.Data()->prev = node->address().value();
+ head.Store();
+ GenerateCrash(ON_INSERT_1);
+ UpdateIterators(&head);
+ }
+
+ node->Data()->next = my_head.value();
+ node->Data()->prev = node->address().value();
+ my_head.set_value(node->address().value());
+
+ if (!my_tail.is_initialized() || my_tail.value() == node->address().value()) {
+ my_tail.set_value(node->address().value());
+ node->Data()->next = my_tail.value();
+ WriteTail(list);
+ GenerateCrash(ON_INSERT_2);
+ }
+
+ UpdateTimes(node, modified);
+ node->Store();
+ GenerateCrash(ON_INSERT_3);
+
+ // The last thing to do is move our head to point to a node already stored.
+ WriteHead(list);
+ IncrementCounter(list);
+ GenerateCrash(ON_INSERT_4);
+ backend_->FlushIndex();
+}
+
+// If a, b and r are elements on the list, and we want to remove r, the possible
+// states for the objects if a crash happens are (where y(x, z) means for object
+// y, prev is x and next is z):
+// A. One element:
+// 1. r(r, r), head(r), tail(r) initial state
+// 2. r(r, r), head(0), tail(r) WriteHead()
+// 3. r(r, r), head(0), tail(0) WriteTail()
+// 4. r(0, 0), head(0), tail(0) next.Store()
+//
+// B. Remove a random element:
+// 1. a(x, r), r(a, b), b(r, y), head(x), tail(y) initial state
+// 2. a(x, r), r(a, b), b(a, y), head(x), tail(y) next.Store()
+// 3. a(x, b), r(a, b), b(a, y), head(x), tail(y) prev.Store()
+// 4. a(x, b), r(0, 0), b(a, y), head(x), tail(y) node.Store()
+//
+// C. Remove head:
+// 1. r(r, b), b(r, y), head(r), tail(y) initial state
+// 2. r(r, b), b(r, y), head(b), tail(y) WriteHead()
+// 3. r(r, b), b(b, y), head(b), tail(y) next.Store()
+// 4. r(0, 0), b(b, y), head(b), tail(y) prev.Store()
+//
+// D. Remove tail:
+// 1. a(x, r), r(a, r), head(x), tail(r) initial state
+// 2. a(x, r), r(a, r), head(x), tail(a) WriteTail()
+// 3. a(x, a), r(a, r), head(x), tail(a) prev.Store()
+// 4. a(x, a), r(0, 0), head(x), tail(a) next.Store()
+void Rankings::Remove(CacheRankingsBlock* node, List list, bool strict) {
+ Trace("Remove 0x%x (0x%x 0x%x) l %d", node->address().value(),
+ node->Data()->next, node->Data()->prev, list);
+ DCHECK(node->HasData());
+ if (strict)
+ InvalidateIterators(node);
+
+ Addr next_addr(node->Data()->next);
+ Addr prev_addr(node->Data()->prev);
+ if (!next_addr.is_initialized() || next_addr.is_separate_file() ||
+ !prev_addr.is_initialized() || prev_addr.is_separate_file()) {
+ if (next_addr.is_initialized() || prev_addr.is_initialized()) {
+ LOG(ERROR) << "Invalid rankings info.";
+ STRESS_NOTREACHED();
+ }
+ return;
+ }
+
+ CacheRankingsBlock next(backend_->File(next_addr), next_addr);
+ CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
+ if (!GetRanking(&next) || !GetRanking(&prev)) {
+ STRESS_NOTREACHED();
+ return;
+ }
+
+ if (!CheckLinks(node, &prev, &next, &list))
+ return;
+
+ Transaction lock(control_data_, node->address(), REMOVE, list);
+ prev.Data()->next = next.address().value();
+ next.Data()->prev = prev.address().value();
+ GenerateCrash(ON_REMOVE_1);
+
+ CacheAddr node_value = node->address().value();
+ Addr& my_head = heads_[list];
+ Addr& my_tail = tails_[list];
+ if (node_value == my_head.value() || node_value == my_tail.value()) {
+ if (my_head.value() == my_tail.value()) {
+ my_head.set_value(0);
+ my_tail.set_value(0);
+
+ WriteHead(list);
+ GenerateCrash(ON_REMOVE_2);
+ WriteTail(list);
+ GenerateCrash(ON_REMOVE_3);
+ } else if (node_value == my_head.value()) {
+ my_head.set_value(next.address().value());
+ next.Data()->prev = next.address().value();
+
+ WriteHead(list);
+ GenerateCrash(ON_REMOVE_4);
+ } else if (node_value == my_tail.value()) {
+ my_tail.set_value(prev.address().value());
+ prev.Data()->next = prev.address().value();
+
+ WriteTail(list);
+ GenerateCrash(ON_REMOVE_5);
+
+ // Store the new tail to make sure we can undo the operation if we crash.
+ prev.Store();
+ GenerateCrash(ON_REMOVE_6);
+ }
+ }
+
+ // Nodes out of the list can be identified by invalid pointers.
+ node->Data()->next = 0;
+ node->Data()->prev = 0;
+
+ // The last thing to get to disk is the node itself, so before that there is
+ // enough info to recover.
+ next.Store();
+ GenerateCrash(ON_REMOVE_7);
+ prev.Store();
+ GenerateCrash(ON_REMOVE_8);
+ node->Store();
+ DecrementCounter(list);
+ UpdateIterators(&next);
+ UpdateIterators(&prev);
+ backend_->FlushIndex();
+}
+
+// A crash in between Remove and Insert will lead to a dirty entry not on the
+// list. We want to avoid that case as much as we can (as while waiting for IO),
+// but the net effect is just an assert on debug when attempting to remove the
+// entry. Otherwise we'll need reentrant transactions, which is an overkill.
+void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified, List list) {
+ Addr& my_head = heads_[list];
+ if (my_head.value() == node->address().value()) {
+ UpdateTimes(node, modified);
+ node->set_modified();
+ return;
+ }
+
+ TimeTicks start = TimeTicks::Now();
+ Remove(node, list, true);
+ Insert(node, modified, list);
+ CACHE_UMA(AGE_MS, "UpdateRank", 0, start);
+}
+
+CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node, List list) {
+ ScopedRankingsBlock next(this);
+ if (!node) {
+ Addr& my_head = heads_[list];
+ if (!my_head.is_initialized())
+ return NULL;
+ next.reset(new CacheRankingsBlock(backend_->File(my_head), my_head));
+ } else {
+ if (!node->HasData())
+ node->Load();
+ Addr& my_tail = tails_[list];
+ if (!my_tail.is_initialized())
+ return NULL;
+ if (my_tail.value() == node->address().value())
+ return NULL;
+ Addr address(node->Data()->next);
+ if (address.value() == node->address().value())
+ return NULL; // Another tail? fail it.
+ next.reset(new CacheRankingsBlock(backend_->File(address), address));
+ }
+
+ TrackRankingsBlock(next.get(), true);
+
+ if (!GetRanking(next.get()))
+ return NULL;
+
+ ConvertToLongLived(next.get());
+ if (node && !CheckSingleLink(node, next.get()))
+ return NULL;
+
+ return next.release();
+}
+
+CacheRankingsBlock* Rankings::GetPrev(CacheRankingsBlock* node, List list) {
+ ScopedRankingsBlock prev(this);
+ if (!node) {
+ Addr& my_tail = tails_[list];
+ if (!my_tail.is_initialized())
+ return NULL;
+ prev.reset(new CacheRankingsBlock(backend_->File(my_tail), my_tail));
+ } else {
+ if (!node->HasData())
+ node->Load();
+ Addr& my_head = heads_[list];
+ if (!my_head.is_initialized())
+ return NULL;
+ if (my_head.value() == node->address().value())
+ return NULL;
+ Addr address(node->Data()->prev);
+ if (address.value() == node->address().value())
+ return NULL; // Another head? fail it.
+ prev.reset(new CacheRankingsBlock(backend_->File(address), address));
+ }
+
+ TrackRankingsBlock(prev.get(), true);
+
+ if (!GetRanking(prev.get()))
+ return NULL;
+
+ ConvertToLongLived(prev.get());
+ if (node && !CheckSingleLink(prev.get(), node))
+ return NULL;
+
+ return prev.release();
+}
+
+void Rankings::FreeRankingsBlock(CacheRankingsBlock* node) {
+ TrackRankingsBlock(node, false);
+}
+
+void Rankings::TrackRankingsBlock(CacheRankingsBlock* node,
+ bool start_tracking) {
+ if (!node)
+ return;
+
+ IteratorPair current(node->address().value(), node);
+
+ if (start_tracking)
+ iterators_.push_back(current);
+ else
+ iterators_.remove(current);
+}
+
+int Rankings::SelfCheck() {
+ int total = 0;
+ int error = 0;
+ for (int i = 0; i < LAST_ELEMENT; i++) {
+ int partial = CheckList(static_cast<List>(i));
+ if (partial < 0 && !error)
+ error = partial;
+ else if (partial > 0)
+ total += partial;
+ }
+
+ return error ? error : total;
+}
+
+bool Rankings::SanityCheck(CacheRankingsBlock* node, bool from_list) const {
+ if (!node->VerifyHash())
+ return false;
+
+ const RankingsNode* data = node->Data();
+
+ if ((!data->next && data->prev) || (data->next && !data->prev))
+ return false;
+
+ // Both pointers on zero is a node out of the list.
+ if (!data->next && !data->prev && from_list)
+ return false;
+
+ List list = NO_USE; // Initialize it to something.
+ if ((node->address().value() == data->prev) && !IsHead(data->prev, &list))
+ return false;
+
+ if ((node->address().value() == data->next) && !IsTail(data->next, &list))
+ return false;
+
+ if (!data->next && !data->prev)
+ return true;
+
+ Addr next_addr(data->next);
+ Addr prev_addr(data->prev);
+ if (!next_addr.SanityCheckV2() || next_addr.file_type() != RANKINGS ||
+ !prev_addr.SanityCheckV2() || prev_addr.file_type() != RANKINGS)
+ return false;
+
+ return true;
+}
+
+bool Rankings::DataSanityCheck(CacheRankingsBlock* node, bool from_list) const {
+ const RankingsNode* data = node->Data();
+ if (!data->contents)
+ return false;
+
+ // It may have never been inserted.
+ if (from_list && (!data->last_used || !data->last_modified))
+ return false;
+
+ return true;
+}
+
+void Rankings::SetContents(CacheRankingsBlock* node, CacheAddr address) {
+ node->Data()->contents = address;
+ node->Store();
+}
+
+void Rankings::ReadHeads() {
+ for (int i = 0; i < LAST_ELEMENT; i++)
+ heads_[i] = Addr(control_data_->heads[i]);
+}
+
+void Rankings::ReadTails() {
+ for (int i = 0; i < LAST_ELEMENT; i++)
+ tails_[i] = Addr(control_data_->tails[i]);
+}
+
+void Rankings::WriteHead(List list) {
+ control_data_->heads[list] = heads_[list].value();
+}
+
+void Rankings::WriteTail(List list) {
+ control_data_->tails[list] = tails_[list].value();
+}
+
+bool Rankings::GetRanking(CacheRankingsBlock* rankings) {
+ if (!rankings->address().is_initialized())
+ return false;
+
+ TimeTicks start = TimeTicks::Now();
+ if (!rankings->Load())
+ return false;
+
+ if (!SanityCheck(rankings, true)) {
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return false;
+ }
+
+ backend_->OnEvent(Stats::OPEN_RANKINGS);
+
+ // Note that if the cache is in read_only mode, open entries are not marked
+ // as dirty, except when an entry is doomed. We have to look for open entries.
+ if (!backend_->read_only() && !rankings->Data()->dirty)
+ return true;
+
+ EntryImpl* entry = backend_->GetOpenEntry(rankings);
+ if (!entry) {
+ if (backend_->read_only())
+ return true;
+
+ // We cannot trust this entry, but we cannot initiate a cleanup from this
+ // point (we may be in the middle of a cleanup already). The entry will be
+ // deleted when detected from a regular open/create path.
+ rankings->Data()->dirty = backend_->GetCurrentEntryId() - 1;
+ if (!rankings->Data()->dirty)
+ rankings->Data()->dirty--;
+ return true;
+ }
+
+ // Note that we should not leave this module without deleting rankings first.
+ rankings->SetData(entry->rankings()->Data());
+
+ CACHE_UMA(AGE_MS, "GetRankings", 0, start);
+ return true;
+}
+
+void Rankings::ConvertToLongLived(CacheRankingsBlock* rankings) {
+ if (rankings->own_data())
+ return;
+
+ // We cannot return a shared node because we are not keeping a reference
+ // to the entry that owns the buffer. Make this node a copy of the one that
+ // we have, and let the iterator logic update it when the entry changes.
+ CacheRankingsBlock temp(NULL, Addr(0));
+ *temp.Data() = *rankings->Data();
+ rankings->StopSharingData();
+ *rankings->Data() = *temp.Data();
+}
+
+void Rankings::CompleteTransaction() {
+ Addr node_addr(static_cast<CacheAddr>(control_data_->transaction));
+ if (!node_addr.is_initialized() || node_addr.is_separate_file()) {
+ NOTREACHED();
+ LOG(ERROR) << "Invalid rankings info.";
+ return;
+ }
+
+ Trace("CompleteTransaction 0x%x", node_addr.value());
+
+ CacheRankingsBlock node(backend_->File(node_addr), node_addr);
+ if (!node.Load())
+ return;
+
+ node.Store();
+
+ Addr& my_head = heads_[control_data_->operation_list];
+ Addr& my_tail = tails_[control_data_->operation_list];
+
+ // We want to leave the node inside the list. The entry must me marked as
+ // dirty, and will be removed later. Otherwise, we'll get assertions when
+ // attempting to remove the dirty entry.
+ if (INSERT == control_data_->operation) {
+ Trace("FinishInsert h:0x%x t:0x%x", my_head.value(), my_tail.value());
+ FinishInsert(&node);
+ } else if (REMOVE == control_data_->operation) {
+ Trace("RevertRemove h:0x%x t:0x%x", my_head.value(), my_tail.value());
+ RevertRemove(&node);
+ } else {
+ NOTREACHED();
+ LOG(ERROR) << "Invalid operation to recover.";
+ }
+}
+
+void Rankings::FinishInsert(CacheRankingsBlock* node) {
+ control_data_->transaction = 0;
+ control_data_->operation = 0;
+ Addr& my_head = heads_[control_data_->operation_list];
+ Addr& my_tail = tails_[control_data_->operation_list];
+ if (my_head.value() != node->address().value()) {
+ if (my_tail.value() == node->address().value()) {
+ // This part will be skipped by the logic of Insert.
+ node->Data()->next = my_tail.value();
+ }
+
+ Insert(node, true, static_cast<List>(control_data_->operation_list));
+ }
+
+ // Tell the backend about this entry.
+ backend_->RecoveredEntry(node);
+}
+
+void Rankings::RevertRemove(CacheRankingsBlock* node) {
+ Addr next_addr(node->Data()->next);
+ Addr prev_addr(node->Data()->prev);
+ if (!next_addr.is_initialized() || !prev_addr.is_initialized()) {
+ // The operation actually finished. Nothing to do.
+ control_data_->transaction = 0;
+ return;
+ }
+ if (next_addr.is_separate_file() || prev_addr.is_separate_file()) {
+ NOTREACHED();
+ LOG(WARNING) << "Invalid rankings info.";
+ control_data_->transaction = 0;
+ return;
+ }
+
+ CacheRankingsBlock next(backend_->File(next_addr), next_addr);
+ CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
+ if (!next.Load() || !prev.Load())
+ return;
+
+ CacheAddr node_value = node->address().value();
+ DCHECK(prev.Data()->next == node_value ||
+ prev.Data()->next == prev_addr.value() ||
+ prev.Data()->next == next.address().value());
+ DCHECK(next.Data()->prev == node_value ||
+ next.Data()->prev == next_addr.value() ||
+ next.Data()->prev == prev.address().value());
+
+ if (node_value != prev_addr.value())
+ prev.Data()->next = node_value;
+ if (node_value != next_addr.value())
+ next.Data()->prev = node_value;
+
+ List my_list = static_cast<List>(control_data_->operation_list);
+ Addr& my_head = heads_[my_list];
+ Addr& my_tail = tails_[my_list];
+ if (!my_head.is_initialized() || !my_tail.is_initialized()) {
+ my_head.set_value(node_value);
+ my_tail.set_value(node_value);
+ WriteHead(my_list);
+ WriteTail(my_list);
+ } else if (my_head.value() == next.address().value()) {
+ my_head.set_value(node_value);
+ prev.Data()->next = next.address().value();
+ WriteHead(my_list);
+ } else if (my_tail.value() == prev.address().value()) {
+ my_tail.set_value(node_value);
+ next.Data()->prev = prev.address().value();
+ WriteTail(my_list);
+ }
+
+ next.Store();
+ prev.Store();
+ control_data_->transaction = 0;
+ control_data_->operation = 0;
+ backend_->FlushIndex();
+}
+
+bool Rankings::CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
+ CacheRankingsBlock* next, List* list) {
+ CacheAddr node_addr = node->address().value();
+ if (prev->Data()->next == node_addr &&
+ next->Data()->prev == node_addr) {
+ // A regular linked node.
+ return true;
+ }
+
+ Trace("CheckLinks 0x%x (0x%x 0x%x)", node_addr,
+ prev->Data()->next, next->Data()->prev);
+
+ if (node_addr != prev->address().value() &&
+ node_addr != next->address().value() &&
+ prev->Data()->next == next->address().value() &&
+ next->Data()->prev == prev->address().value()) {
+ // The list is actually ok, node is wrong.
+ Trace("node 0x%x out of list %d", node_addr, list);
+ node->Data()->next = 0;
+ node->Data()->prev = 0;
+ node->Store();
+ return false;
+ }
+
+ if (prev->Data()->next == node_addr ||
+ next->Data()->prev == node_addr) {
+ // Only one link is weird, lets double check.
+ if (prev->Data()->next != node_addr && IsHead(node_addr, list))
+ return true;
+
+ if (next->Data()->prev != node_addr && IsTail(node_addr, list))
+ return true;
+ }
+
+ LOG(ERROR) << "Inconsistent LRU.";
+ STRESS_NOTREACHED();
+
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return false;
+}
+
+bool Rankings::CheckSingleLink(CacheRankingsBlock* prev,
+ CacheRankingsBlock* next) {
+ if (prev->Data()->next != next->address().value() ||
+ next->Data()->prev != prev->address().value()) {
+ LOG(ERROR) << "Inconsistent LRU.";
+
+ backend_->CriticalError(ERR_INVALID_LINKS);
+ return false;
+ }
+
+ return true;
+}
+
+int Rankings::CheckList(List list) {
+ Addr last1, last2;
+ int head_items;
+ int rv = CheckListSection(list, last1, last2, true, // Head to tail.
+ &last1, &last2, &head_items);
+ if (rv == ERR_NO_ERROR)
+ return head_items;
+
+ return rv;
+}
+
+// Note that the returned error codes assume a forward walk (from head to tail)
+// so they have to be adjusted accordingly by the caller. We use two stop values
+// to be able to detect a corrupt node at the end that is not linked going back.
+int Rankings::CheckListSection(List list, Addr end1, Addr end2, bool forward,
+ Addr* last, Addr* second_last, int* num_items) {
+ Addr current = forward ? heads_[list] : tails_[list];
+ *last = *second_last = current;
+ *num_items = 0;
+ if (!current.is_initialized())
+ return ERR_NO_ERROR;
+
+ if (!current.SanityCheckForRankings())
+ return ERR_INVALID_HEAD;
+
+ scoped_ptr<CacheRankingsBlock> node;
+ Addr prev_addr(current);
+ do {
+ node.reset(new CacheRankingsBlock(backend_->File(current), current));
+ node->Load();
+ if (!SanityCheck(node.get(), true))
+ return ERR_INVALID_ENTRY;
+
+ CacheAddr next = forward ? node->Data()->next : node->Data()->prev;
+ CacheAddr prev = forward ? node->Data()->prev : node->Data()->next;
+
+ if (prev != prev_addr.value())
+ return ERR_INVALID_PREV;
+
+ Addr next_addr(next);
+ if (!next_addr.SanityCheckForRankings())
+ return ERR_INVALID_NEXT;
+
+ prev_addr = current;
+ current = next_addr;
+ *second_last = *last;
+ *last = current;
+ (*num_items)++;
+
+ if (next_addr == prev_addr) {
+ Addr last = forward ? tails_[list] : heads_[list];
+ if (next_addr == last)
+ return ERR_NO_ERROR;
+ return ERR_INVALID_TAIL;
+ }
+ } while (current != end1 && current != end2);
+ return ERR_NO_ERROR;
+}
+
+bool Rankings::IsHead(CacheAddr addr, List* list) const {
+ for (int i = 0; i < LAST_ELEMENT; i++) {
+ if (addr == heads_[i].value()) {
+ if (*list != i)
+ Trace("Changing list %d to %d", *list, i);
+ *list = static_cast<List>(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Rankings::IsTail(CacheAddr addr, List* list) const {
+ for (int i = 0; i < LAST_ELEMENT; i++) {
+ if (addr == tails_[i].value()) {
+ if (*list != i)
+ Trace("Changing list %d to %d", *list, i);
+ *list = static_cast<List>(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+// We expect to have just a few iterators at any given time, maybe two or three,
+// But we could have more than one pointing at the same mode. We walk the list
+// of cache iterators and update all that are pointing to the given node.
+void Rankings::UpdateIterators(CacheRankingsBlock* node) {
+ CacheAddr address = node->address().value();
+ for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
+ ++it) {
+ if (it->first == address && it->second->HasData()) {
+ CacheRankingsBlock* other = it->second;
+ *other->Data() = *node->Data();
+ }
+ }
+}
+
+void Rankings::InvalidateIterators(CacheRankingsBlock* node) {
+ CacheAddr address = node->address().value();
+ for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
+ ++it) {
+ if (it->first == address) {
+ DLOG(INFO) << "Invalidating iterator at 0x" << std::hex << address;
+ it->second->Discard();
+ }
+ }
+}
+
+void Rankings::IncrementCounter(List list) {
+ if (!count_lists_)
+ return;
+
+ DCHECK(control_data_->sizes[list] < kint32max);
+ if (control_data_->sizes[list] < kint32max)
+ control_data_->sizes[list]++;
+}
+
+void Rankings::DecrementCounter(List list) {
+ if (!count_lists_)
+ return;
+
+ DCHECK(control_data_->sizes[list] > 0);
+ if (control_data_->sizes[list] > 0)
+ control_data_->sizes[list]--;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/rankings.h b/chromium/net/disk_cache/rankings.h
new file mode 100644
index 00000000000..cd94eaf1c59
--- /dev/null
+++ b/chromium/net/disk_cache/rankings.h
@@ -0,0 +1,214 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface.
+
+#ifndef NET_DISK_CACHE_RANKINGS_H_
+#define NET_DISK_CACHE_RANKINGS_H_
+
+#include <list>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/disk_cache/addr.h"
+#include "net/disk_cache/mapped_file.h"
+#include "net/disk_cache/storage_block.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+struct LruData;
+struct RankingsNode;
+typedef StorageBlock<RankingsNode> CacheRankingsBlock;
+
+// Type of crashes generated for the unit tests.
+enum RankCrashes {
+ NO_CRASH = 0,
+ INSERT_EMPTY_1,
+ INSERT_EMPTY_2,
+ INSERT_EMPTY_3,
+ INSERT_ONE_1,
+ INSERT_ONE_2,
+ INSERT_ONE_3,
+ INSERT_LOAD_1,
+ INSERT_LOAD_2,
+ REMOVE_ONE_1,
+ REMOVE_ONE_2,
+ REMOVE_ONE_3,
+ REMOVE_ONE_4,
+ REMOVE_HEAD_1,
+ REMOVE_HEAD_2,
+ REMOVE_HEAD_3,
+ REMOVE_HEAD_4,
+ REMOVE_TAIL_1,
+ REMOVE_TAIL_2,
+ REMOVE_TAIL_3,
+ REMOVE_LOAD_1,
+ REMOVE_LOAD_2,
+ REMOVE_LOAD_3,
+ MAX_CRASH
+};
+
+// This class handles the ranking information for the cache.
+class Rankings {
+ public:
+ // Possible lists of entries.
+ enum List {
+ NO_USE = 0, // List of entries that have not been reused.
+ LOW_USE, // List of entries with low reuse.
+ HIGH_USE, // List of entries with high reuse.
+ RESERVED, // Reserved for future use.
+ DELETED, // List of recently deleted or doomed entries.
+ LAST_ELEMENT
+ };
+
+ // This class provides a specialized version of scoped_ptr, that calls
+ // Rankings whenever a CacheRankingsBlock is deleted, to keep track of cache
+ // iterators that may go stale.
+ class ScopedRankingsBlock : public scoped_ptr<CacheRankingsBlock> {
+ public:
+ ScopedRankingsBlock();
+ explicit ScopedRankingsBlock(Rankings* rankings);
+ ScopedRankingsBlock(Rankings* rankings, CacheRankingsBlock* node);
+
+ ~ScopedRankingsBlock() {
+ rankings_->FreeRankingsBlock(get());
+ }
+
+ void set_rankings(Rankings* rankings) {
+ rankings_ = rankings;
+ }
+
+ // scoped_ptr::reset will delete the object.
+ void reset(CacheRankingsBlock* p = NULL) {
+ if (p != get())
+ rankings_->FreeRankingsBlock(get());
+ scoped_ptr<CacheRankingsBlock>::reset(p);
+ }
+
+ private:
+ Rankings* rankings_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedRankingsBlock);
+ };
+
+ // If we have multiple lists, we have to iterate through all at the same time.
+ // This structure keeps track of where we are on the iteration.
+ struct Iterator {
+ explicit Iterator(Rankings* rankings);
+ ~Iterator();
+
+ List list; // Which entry was returned to the user.
+ CacheRankingsBlock* nodes[3]; // Nodes on the first three lists.
+ Rankings* my_rankings;
+ };
+
+ Rankings();
+ ~Rankings();
+
+ bool Init(BackendImpl* backend, bool count_lists);
+
+ // Restores original state, leaving the object ready for initialization.
+ void Reset();
+
+ // Inserts a given entry at the head of the queue.
+ void Insert(CacheRankingsBlock* node, bool modified, List list);
+
+ // Removes a given entry from the LRU list. If |strict| is true, this method
+ // assumes that |node| is not pointed to by an active iterator. On the other
+ // hand, removing that restriction allows the current "head" of an iterator
+ // to be removed from the list (basically without control of the code that is
+ // performing the iteration), so it should be used with extra care.
+ void Remove(CacheRankingsBlock* node, List list, bool strict);
+
+ // Moves a given entry to the head.
+ void UpdateRank(CacheRankingsBlock* node, bool modified, List list);
+
+ // Iterates through the list.
+ CacheRankingsBlock* GetNext(CacheRankingsBlock* node, List list);
+ CacheRankingsBlock* GetPrev(CacheRankingsBlock* node, List list);
+ void FreeRankingsBlock(CacheRankingsBlock* node);
+
+ // Controls tracking of nodes used for enumerations.
+ void TrackRankingsBlock(CacheRankingsBlock* node, bool start_tracking);
+
+ // Peforms a simple self-check of the lists, and returns the number of items
+ // or an error code (negative value).
+ int SelfCheck();
+
+ // Returns false if the entry is clearly invalid. from_list is true if the
+ // node comes from the LRU list.
+ bool SanityCheck(CacheRankingsBlock* node, bool from_list) const;
+ bool DataSanityCheck(CacheRankingsBlock* node, bool from_list) const;
+
+ // Sets the |contents| field of |node| to |address|.
+ void SetContents(CacheRankingsBlock* node, CacheAddr address);
+
+ private:
+ typedef std::pair<CacheAddr, CacheRankingsBlock*> IteratorPair;
+ typedef std::list<IteratorPair> IteratorList;
+
+ void ReadHeads();
+ void ReadTails();
+ void WriteHead(List list);
+ void WriteTail(List list);
+
+ // Gets the rankings information for a given rankings node. We may end up
+ // sharing the actual memory with a loaded entry, but we are not taking a
+ // reference to that entry, so |rankings| must be short lived.
+ bool GetRanking(CacheRankingsBlock* rankings);
+
+ // Makes |rankings| suitable to live a long life.
+ void ConvertToLongLived(CacheRankingsBlock* rankings);
+
+ // Finishes a list modification after a crash.
+ void CompleteTransaction();
+ void FinishInsert(CacheRankingsBlock* rankings);
+ void RevertRemove(CacheRankingsBlock* rankings);
+
+ // Returns false if node is not properly linked. This method may change the
+ // provided |list| to reflect the list where this node is actually stored.
+ bool CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
+ CacheRankingsBlock* next, List* list);
+
+ // Checks the links between two consecutive nodes.
+ bool CheckSingleLink(CacheRankingsBlock* prev, CacheRankingsBlock* next);
+
+ // Peforms a simple check of the list, and returns the number of items or an
+ // error code (negative value).
+ int CheckList(List list);
+
+ // Walks a list in the desired direction until the nodes |end1| or |end2| are
+ // reached. Returns an error code (0 on success), the number of items verified
+ // and the addresses of the last nodes visited.
+ int CheckListSection(List list, Addr end1, Addr end2, bool forward,
+ Addr* last, Addr* second_last, int* num_items);
+
+ // Returns true if addr is the head or tail of any list. When there is a
+ // match |list| will contain the list number for |addr|.
+ bool IsHead(CacheAddr addr, List* list) const;
+ bool IsTail(CacheAddr addr, List* list) const;
+
+ // Updates the iterators whenever node is being changed.
+ void UpdateIterators(CacheRankingsBlock* node);
+
+ // Invalidates the iterators pointing to this node.
+ void InvalidateIterators(CacheRankingsBlock* node);
+
+ // Keeps track of the number of entries on a list.
+ void IncrementCounter(List list);
+ void DecrementCounter(List list);
+
+ bool init_;
+ bool count_lists_;
+ Addr heads_[LAST_ELEMENT];
+ Addr tails_[LAST_ELEMENT];
+ BackendImpl* backend_;
+ LruData* control_data_; // Data related to the LRU lists.
+ IteratorList iterators_;
+
+ DISALLOW_COPY_AND_ASSIGN(Rankings);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_RANKINGS_H_
diff --git a/chromium/net/disk_cache/simple/OWNERS b/chromium/net/disk_cache/simple/OWNERS
new file mode 100644
index 00000000000..6ed8b171fe5
--- /dev/null
+++ b/chromium/net/disk_cache/simple/OWNERS
@@ -0,0 +1,2 @@
+gavinp@chromium.org
+pasko@chromium.org
diff --git a/chromium/net/disk_cache/simple/simple_backend_impl.cc b/chromium/net/disk_cache/simple/simple_backend_impl.cc
new file mode 100644
index 00000000000..2877c01f701
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_backend_impl.cc
@@ -0,0 +1,570 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_backend_impl.h"
+
+#include <algorithm>
+#include <cstdlib>
+
+#if defined(OS_POSIX)
+#include <sys/resource.h>
+#endif
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_entry_impl.h"
+#include "net/disk_cache/simple/simple_index.h"
+#include "net/disk_cache/simple/simple_index_file.h"
+#include "net/disk_cache/simple/simple_synchronous_entry.h"
+#include "net/disk_cache/simple/simple_util.h"
+
+using base::Closure;
+using base::FilePath;
+using base::MessageLoopProxy;
+using base::SequencedWorkerPool;
+using base::SingleThreadTaskRunner;
+using base::Time;
+using base::DirectoryExists;
+using file_util::CreateDirectory;
+
+namespace {
+
+// Maximum number of concurrent worker pool threads, which also is the limit
+// on concurrent IO (as we use one thread per IO request).
+const int kDefaultMaxWorkerThreads = 50;
+
+const char kThreadNamePrefix[] = "SimpleCache";
+
+// Cache size when all other size heuristics failed.
+const uint64 kDefaultCacheSize = 80 * 1024 * 1024;
+
+// Maximum fraction of the cache that one entry can consume.
+const int kMaxFileRatio = 8;
+
+// A global sequenced worker pool to use for launching all tasks.
+SequencedWorkerPool* g_sequenced_worker_pool = NULL;
+
+void MaybeCreateSequencedWorkerPool() {
+ if (!g_sequenced_worker_pool) {
+ int max_worker_threads = kDefaultMaxWorkerThreads;
+
+ const std::string thread_count_field_trial =
+ base::FieldTrialList::FindFullName("SimpleCacheMaxThreads");
+ if (!thread_count_field_trial.empty()) {
+ max_worker_threads =
+ std::max(1, std::atoi(thread_count_field_trial.c_str()));
+ }
+
+ g_sequenced_worker_pool = new SequencedWorkerPool(max_worker_threads,
+ kThreadNamePrefix);
+ g_sequenced_worker_pool->AddRef(); // Leak it.
+ }
+}
+
+bool g_fd_limit_histogram_has_been_populated = false;
+
+void MaybeHistogramFdLimit() {
+ if (g_fd_limit_histogram_has_been_populated)
+ return;
+
+ // Used in histograms; add new entries at end.
+ enum FdLimitStatus {
+ FD_LIMIT_STATUS_UNSUPPORTED = 0,
+ FD_LIMIT_STATUS_FAILED = 1,
+ FD_LIMIT_STATUS_SUCCEEDED = 2,
+ FD_LIMIT_STATUS_MAX = 3
+ };
+ FdLimitStatus fd_limit_status = FD_LIMIT_STATUS_UNSUPPORTED;
+ int soft_fd_limit = 0;
+ int hard_fd_limit = 0;
+
+#if defined(OS_POSIX)
+ struct rlimit nofile;
+ if (!getrlimit(RLIMIT_NOFILE, &nofile)) {
+ soft_fd_limit = nofile.rlim_cur;
+ hard_fd_limit = nofile.rlim_max;
+ fd_limit_status = FD_LIMIT_STATUS_SUCCEEDED;
+ } else {
+ fd_limit_status = FD_LIMIT_STATUS_FAILED;
+ }
+#endif
+
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimitStatus",
+ fd_limit_status, FD_LIMIT_STATUS_MAX);
+ if (fd_limit_status == FD_LIMIT_STATUS_SUCCEEDED) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleCache.FileDescriptorLimitSoft",
+ soft_fd_limit);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleCache.FileDescriptorLimitHard",
+ hard_fd_limit);
+ }
+
+ g_fd_limit_histogram_has_been_populated = true;
+}
+
+// Must run on IO Thread.
+void DeleteBackendImpl(disk_cache::Backend** backend,
+ const net::CompletionCallback& callback,
+ int result) {
+ DCHECK(*backend);
+ delete *backend;
+ *backend = NULL;
+ callback.Run(result);
+}
+
+// Detects if the files in the cache directory match the current disk cache
+// backend type and version. If the directory contains no cache, occupies it
+// with the fresh structure.
+//
+// There is a convention among disk cache backends: looking at the magic in the
+// file "index" it should be sufficient to determine if the cache belongs to the
+// currently running backend. The Simple Backend stores its index in the file
+// "the-real-index" (see simple_index.cc) and the file "index" only signifies
+// presence of the implementation's magic and version. There are two reasons for
+// that:
+// 1. Absence of the index is itself not a fatal error in the Simple Backend
+// 2. The Simple Backend has pickled file format for the index making it hacky
+// to have the magic in the right place.
+bool FileStructureConsistent(const base::FilePath& path) {
+ if (!base::PathExists(path) && !file_util::CreateDirectory(path)) {
+ LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
+ return false;
+ }
+ const base::FilePath fake_index = path.AppendASCII("index");
+ base::PlatformFileError error;
+ base::PlatformFile fake_index_file = base::CreatePlatformFile(
+ fake_index,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
+ NULL,
+ &error);
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ base::PlatformFile file = base::CreatePlatformFile(
+ fake_index,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ NULL, &error);
+ disk_cache::SimpleFileHeader file_contents;
+ file_contents.initial_magic_number = disk_cache::kSimpleInitialMagicNumber;
+ file_contents.version = disk_cache::kSimpleVersion;
+ int bytes_written = base::WritePlatformFile(
+ file, 0, reinterpret_cast<char*>(&file_contents),
+ sizeof(file_contents));
+ if (!base::ClosePlatformFile(file) ||
+ bytes_written != sizeof(file_contents)) {
+ LOG(ERROR) << "Failed to write cache structure file: "
+ << path.LossyDisplayName();
+ return false;
+ }
+ return true;
+ } else if (error != base::PLATFORM_FILE_OK) {
+ LOG(ERROR) << "Could not open cache structure file: "
+ << path.LossyDisplayName();
+ return false;
+ } else {
+ disk_cache::SimpleFileHeader file_header;
+ int bytes_read = base::ReadPlatformFile(
+ fake_index_file, 0, reinterpret_cast<char*>(&file_header),
+ sizeof(file_header));
+ if (!base::ClosePlatformFile(fake_index_file) ||
+ bytes_read != sizeof(file_header) ||
+ file_header.initial_magic_number !=
+ disk_cache::kSimpleInitialMagicNumber ||
+ file_header.version != disk_cache::kSimpleVersion) {
+ LOG(ERROR) << "File structure does not match the disk cache backend.";
+ return false;
+ }
+ return true;
+ }
+}
+
+void CallCompletionCallback(const net::CompletionCallback& callback,
+ int error_code) {
+ DCHECK(!callback.is_null());
+ callback.Run(error_code);
+}
+
+void RecordIndexLoad(base::TimeTicks constructed_since, int result) {
+ const base::TimeDelta creation_to_index = base::TimeTicks::Now() -
+ constructed_since;
+ if (result == net::OK)
+ UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndex", creation_to_index);
+ else
+ UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndexFail", creation_to_index);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+SimpleBackendImpl::SimpleBackendImpl(const FilePath& path,
+ int max_bytes,
+ net::CacheType type,
+ base::SingleThreadTaskRunner* cache_thread,
+ net::NetLog* net_log)
+ : path_(path),
+ cache_thread_(cache_thread),
+ orig_max_size_(max_bytes),
+ entry_operations_mode_(
+ type == net::DISK_CACHE ?
+ SimpleEntryImpl::OPTIMISTIC_OPERATIONS :
+ SimpleEntryImpl::NON_OPTIMISTIC_OPERATIONS),
+ net_log_(net_log) {
+ MaybeHistogramFdLimit();
+}
+
+SimpleBackendImpl::~SimpleBackendImpl() {
+ index_->WriteToDisk();
+}
+
+int SimpleBackendImpl::Init(const CompletionCallback& completion_callback) {
+ MaybeCreateSequencedWorkerPool();
+
+ worker_pool_ = g_sequenced_worker_pool->GetTaskRunnerWithShutdownBehavior(
+ SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+
+ index_.reset(
+ new SimpleIndex(MessageLoopProxy::current().get(),
+ path_,
+ make_scoped_ptr(new SimpleIndexFile(
+ cache_thread_.get(), worker_pool_.get(), path_))));
+ index_->ExecuteWhenReady(base::Bind(&RecordIndexLoad,
+ base::TimeTicks::Now()));
+
+ PostTaskAndReplyWithResult(
+ cache_thread_,
+ FROM_HERE,
+ base::Bind(&SimpleBackendImpl::InitCacheStructureOnDisk, path_,
+ orig_max_size_),
+ base::Bind(&SimpleBackendImpl::InitializeIndex, AsWeakPtr(),
+ completion_callback));
+ return net::ERR_IO_PENDING;
+}
+
+bool SimpleBackendImpl::SetMaxSize(int max_bytes) {
+ orig_max_size_ = max_bytes;
+ return index_->SetMaxSize(max_bytes);
+}
+
+int SimpleBackendImpl::GetMaxFileSize() const {
+ return index_->max_size() / kMaxFileRatio;
+}
+
+void SimpleBackendImpl::OnDeactivated(const SimpleEntryImpl* entry) {
+ active_entries_.erase(entry->entry_hash());
+}
+
+net::CacheType SimpleBackendImpl::GetCacheType() const {
+ return net::DISK_CACHE;
+}
+
+int32 SimpleBackendImpl::GetEntryCount() const {
+ // TODO(pasko): Use directory file count when index is not ready.
+ return index_->GetEntryCount();
+}
+
+int SimpleBackendImpl::OpenEntry(const std::string& key,
+ Entry** entry,
+ const CompletionCallback& callback) {
+ scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
+ CompletionCallback backend_callback =
+ base::Bind(&SimpleBackendImpl::OnEntryOpenedFromKey,
+ AsWeakPtr(),
+ key,
+ entry,
+ simple_entry,
+ callback);
+ return simple_entry->OpenEntry(entry, backend_callback);
+}
+
+int SimpleBackendImpl::CreateEntry(const std::string& key,
+ Entry** entry,
+ const CompletionCallback& callback) {
+ DCHECK(key.size() > 0);
+ scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
+ return simple_entry->CreateEntry(entry, callback);
+}
+
+int SimpleBackendImpl::DoomEntry(const std::string& key,
+ const net::CompletionCallback& callback) {
+ scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
+ return simple_entry->DoomEntry(callback);
+}
+
+int SimpleBackendImpl::DoomAllEntries(const CompletionCallback& callback) {
+ return DoomEntriesBetween(Time(), Time(), callback);
+}
+
+void SimpleBackendImpl::IndexReadyForDoom(Time initial_time,
+ Time end_time,
+ const CompletionCallback& callback,
+ int result) {
+ if (result != net::OK) {
+ callback.Run(result);
+ return;
+ }
+ scoped_ptr<std::vector<uint64> > removed_key_hashes(
+ index_->RemoveEntriesBetween(initial_time, end_time).release());
+
+ // If any of the entries we are dooming are currently open, we need to remove
+ // them from |active_entries_|, so that attempts to create new entries will
+ // succeed and attempts to open them will fail.
+ for (int i = removed_key_hashes->size() - 1; i >= 0; --i) {
+ const uint64 entry_hash = (*removed_key_hashes)[i];
+ EntryMap::iterator it = active_entries_.find(entry_hash);
+ if (it == active_entries_.end())
+ continue;
+ SimpleEntryImpl* entry = it->second.get();
+ entry->Doom();
+
+ (*removed_key_hashes)[i] = removed_key_hashes->back();
+ removed_key_hashes->resize(removed_key_hashes->size() - 1);
+ }
+
+ PostTaskAndReplyWithResult(
+ worker_pool_, FROM_HERE,
+ base::Bind(&SimpleSynchronousEntry::DoomEntrySet,
+ base::Passed(&removed_key_hashes), path_),
+ base::Bind(&CallCompletionCallback, callback));
+}
+
+int SimpleBackendImpl::DoomEntriesBetween(
+ const Time initial_time,
+ const Time end_time,
+ const CompletionCallback& callback) {
+ return index_->ExecuteWhenReady(
+ base::Bind(&SimpleBackendImpl::IndexReadyForDoom, AsWeakPtr(),
+ initial_time, end_time, callback));
+}
+
+int SimpleBackendImpl::DoomEntriesSince(
+ const Time initial_time,
+ const CompletionCallback& callback) {
+ return DoomEntriesBetween(initial_time, Time(), callback);
+}
+
+int SimpleBackendImpl::OpenNextEntry(void** iter,
+ Entry** next_entry,
+ const CompletionCallback& callback) {
+ CompletionCallback get_next_entry =
+ base::Bind(&SimpleBackendImpl::GetNextEntryInIterator, AsWeakPtr(), iter,
+ next_entry, callback);
+ return index_->ExecuteWhenReady(get_next_entry);
+}
+
+void SimpleBackendImpl::EndEnumeration(void** iter) {
+ SimpleIndex::HashList* entry_list =
+ static_cast<SimpleIndex::HashList*>(*iter);
+ delete entry_list;
+ *iter = NULL;
+}
+
+void SimpleBackendImpl::GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) {
+ std::pair<std::string, std::string> item;
+ item.first = "Cache type";
+ item.second = "Simple Cache";
+ stats->push_back(item);
+}
+
+void SimpleBackendImpl::OnExternalCacheHit(const std::string& key) {
+ index_->UseIfExists(key);
+}
+
+void SimpleBackendImpl::InitializeIndex(const CompletionCallback& callback,
+ const DiskStatResult& result) {
+ if (result.net_error == net::OK) {
+ index_->SetMaxSize(result.max_size);
+ index_->Initialize(result.cache_dir_mtime);
+ }
+ callback.Run(result.net_error);
+}
+
+SimpleBackendImpl::DiskStatResult SimpleBackendImpl::InitCacheStructureOnDisk(
+ const base::FilePath& path,
+ uint64 suggested_max_size) {
+ DiskStatResult result;
+ result.max_size = suggested_max_size;
+ result.net_error = net::OK;
+ if (!FileStructureConsistent(path)) {
+ LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
+ << path.LossyDisplayName();
+ result.net_error = net::ERR_FAILED;
+ } else {
+ bool mtime_result =
+ disk_cache::simple_util::GetMTime(path, &result.cache_dir_mtime);
+ DCHECK(mtime_result);
+ if (!result.max_size) {
+ int64 available = base::SysInfo::AmountOfFreeDiskSpace(path);
+ if (available < 0)
+ result.max_size = kDefaultCacheSize;
+ else
+ // TODO(pasko): Move PreferedCacheSize() to cache_util.h. Also fix the
+ // spelling.
+ result.max_size = disk_cache::PreferedCacheSize(available);
+ }
+ DCHECK(result.max_size);
+ }
+ return result;
+}
+
+scoped_refptr<SimpleEntryImpl> SimpleBackendImpl::CreateOrFindActiveEntry(
+ const std::string& key) {
+ const uint64 entry_hash = simple_util::GetEntryHashKey(key);
+
+ std::pair<EntryMap::iterator, bool> insert_result =
+ active_entries_.insert(std::make_pair(entry_hash,
+ base::WeakPtr<SimpleEntryImpl>()));
+ EntryMap::iterator& it = insert_result.first;
+ if (insert_result.second)
+ DCHECK(!it->second.get());
+ if (!it->second.get()) {
+ SimpleEntryImpl* entry = new SimpleEntryImpl(
+ path_, entry_hash, entry_operations_mode_, this, net_log_);
+ entry->SetKey(key);
+ it->second = entry->AsWeakPtr();
+ }
+ DCHECK(it->second.get());
+ // It's possible, but unlikely, that we have an entry hash collision with a
+ // currently active entry.
+ if (key != it->second->key()) {
+ it->second->Doom();
+ DCHECK_EQ(0U, active_entries_.count(entry_hash));
+ return CreateOrFindActiveEntry(key);
+ }
+ return make_scoped_refptr(it->second.get());
+}
+
+int SimpleBackendImpl::OpenEntryFromHash(uint64 hash,
+ Entry** entry,
+ const CompletionCallback& callback) {
+ EntryMap::iterator has_active = active_entries_.find(hash);
+ if (has_active != active_entries_.end())
+ return OpenEntry(has_active->second->key(), entry, callback);
+
+ scoped_refptr<SimpleEntryImpl> simple_entry =
+ new SimpleEntryImpl(path_, hash, entry_operations_mode_, this, net_log_);
+ CompletionCallback backend_callback =
+ base::Bind(&SimpleBackendImpl::OnEntryOpenedFromHash,
+ AsWeakPtr(),
+ hash, entry, simple_entry, callback);
+ return simple_entry->OpenEntry(entry, backend_callback);
+}
+
+void SimpleBackendImpl::GetNextEntryInIterator(
+ void** iter,
+ Entry** next_entry,
+ const CompletionCallback& callback,
+ int error_code) {
+ if (error_code != net::OK) {
+ CallCompletionCallback(callback, error_code);
+ return;
+ }
+ if (*iter == NULL) {
+ *iter = index()->GetAllHashes().release();
+ }
+ SimpleIndex::HashList* entry_list =
+ static_cast<SimpleIndex::HashList*>(*iter);
+ while (entry_list->size() > 0) {
+ uint64 entry_hash = entry_list->back();
+ entry_list->pop_back();
+ if (index()->Has(entry_hash)) {
+ *next_entry = NULL;
+ CompletionCallback continue_iteration = base::Bind(
+ &SimpleBackendImpl::CheckIterationReturnValue,
+ AsWeakPtr(),
+ iter,
+ next_entry,
+ callback);
+ int error_code_open = OpenEntryFromHash(entry_hash,
+ next_entry,
+ continue_iteration);
+ if (error_code_open == net::ERR_IO_PENDING)
+ return;
+ if (error_code_open != net::ERR_FAILED) {
+ CallCompletionCallback(callback, error_code_open);
+ return;
+ }
+ }
+ }
+ CallCompletionCallback(callback, net::ERR_FAILED);
+}
+
+void SimpleBackendImpl::OnEntryOpenedFromHash(
+ uint64 hash,
+ Entry** entry,
+ scoped_refptr<SimpleEntryImpl> simple_entry,
+ const CompletionCallback& callback,
+ int error_code) {
+ if (error_code != net::OK) {
+ CallCompletionCallback(callback, error_code);
+ return;
+ }
+ DCHECK(*entry);
+ std::pair<EntryMap::iterator, bool> insert_result =
+ active_entries_.insert(std::make_pair(hash,
+ base::WeakPtr<SimpleEntryImpl>()));
+ EntryMap::iterator& it = insert_result.first;
+ const bool did_insert = insert_result.second;
+ if (did_insert) {
+ // There is no active entry corresponding to this hash. The entry created
+ // is put in the map of active entries and returned to the caller.
+ it->second = simple_entry->AsWeakPtr();
+ CallCompletionCallback(callback, error_code);
+ } else {
+ // The entry was made active with the key while the creation from hash
+ // occurred. The entry created from hash needs to be closed, and the one
+ // coming from the key returned to the caller.
+ simple_entry->Close();
+ it->second->OpenEntry(entry, callback);
+ }
+}
+
+void SimpleBackendImpl::OnEntryOpenedFromKey(
+ const std::string key,
+ Entry** entry,
+ scoped_refptr<SimpleEntryImpl> simple_entry,
+ const CompletionCallback& callback,
+ int error_code) {
+ int final_code = error_code;
+ if (final_code == net::OK) {
+ bool key_matches = key.compare(simple_entry->key()) == 0;
+ if (!key_matches) {
+ // TODO(clamy): Add a unit test to check this code path.
+ DLOG(WARNING) << "Key mismatch on open.";
+ simple_entry->Doom();
+ simple_entry->Close();
+ final_code = net::ERR_FAILED;
+ } else {
+ DCHECK_EQ(simple_entry->entry_hash(), simple_util::GetEntryHashKey(key));
+ }
+ UMA_HISTOGRAM_BOOLEAN("SimpleCache.KeyMatchedOnOpen", key_matches);
+ }
+ CallCompletionCallback(callback, final_code);
+}
+
+void SimpleBackendImpl::CheckIterationReturnValue(
+ void** iter,
+ Entry** entry,
+ const CompletionCallback& callback,
+ int error_code) {
+ if (error_code == net::ERR_FAILED) {
+ OpenNextEntry(iter, entry, callback);
+ return;
+ }
+ CallCompletionCallback(callback, error_code);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_backend_impl.h b/chromium/net/disk_cache/simple/simple_backend_impl.h
new file mode 100644
index 00000000000..4f01351752e
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_backend_impl.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "net/base/cache_type.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/simple/simple_entry_impl.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class TaskRunner;
+}
+
+namespace disk_cache {
+
+// SimpleBackendImpl is a new cache backend that stores entries in individual
+// files.
+// See http://www.chromium.org/developers/design-documents/network-stack/disk-cache/very-simple-backend
+//
+// The non-static functions below must be called on the IO thread unless
+// otherwise stated.
+
+class SimpleEntryImpl;
+class SimpleIndex;
+
+class NET_EXPORT_PRIVATE SimpleBackendImpl : public Backend,
+ public base::SupportsWeakPtr<SimpleBackendImpl> {
+ public:
+ SimpleBackendImpl(const base::FilePath& path, int max_bytes,
+ net::CacheType type,
+ base::SingleThreadTaskRunner* cache_thread,
+ net::NetLog* net_log);
+
+ virtual ~SimpleBackendImpl();
+
+ SimpleIndex* index() { return index_.get(); }
+
+ base::TaskRunner* worker_pool() { return worker_pool_.get(); }
+
+ int Init(const CompletionCallback& completion_callback);
+
+ // Sets the maximum size for the total amount of data stored by this instance.
+ bool SetMaxSize(int max_bytes);
+
+ // Returns the maximum file size permitted in this backend.
+ int GetMaxFileSize() const;
+
+ // Removes |entry| from the |active_entries_| set, forcing future Open/Create
+ // operations to construct a new object.
+ void OnDeactivated(const SimpleEntryImpl* entry);
+
+ // Backend:
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) OVERRIDE;
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ private:
+ typedef base::hash_map<uint64, base::WeakPtr<SimpleEntryImpl> > EntryMap;
+
+ typedef base::Callback<void(base::Time mtime, uint64 max_size, int result)>
+ InitializeIndexCallback;
+
+ // Return value of InitCacheStructureOnDisk().
+ struct DiskStatResult {
+ base::Time cache_dir_mtime;
+ uint64 max_size;
+ bool detected_magic_number_mismatch;
+ int net_error;
+ };
+
+ void InitializeIndex(const CompletionCallback& callback,
+ const DiskStatResult& result);
+
+ // Dooms all entries previously accessed between |initial_time| and
+ // |end_time|. Invoked when the index is ready.
+ void IndexReadyForDoom(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback,
+ int result);
+
+ // Try to create the directory if it doesn't exist. This must run on the IO
+ // thread.
+ static DiskStatResult InitCacheStructureOnDisk(const base::FilePath& path,
+ uint64 suggested_max_size);
+
+ // Searches |active_entries_| for the entry corresponding to |key|. If found,
+ // returns the found entry. Otherwise, creates a new entry and returns that.
+ scoped_refptr<SimpleEntryImpl> CreateOrFindActiveEntry(
+ const std::string& key);
+
+ // Given a hash, will try to open the corresponding Entry. If we have an Entry
+ // corresponding to |hash| in the map of active entries, opens it. Otherwise,
+ // a new empty Entry will be created, opened and filled with information from
+ // the disk.
+ int OpenEntryFromHash(uint64 hash,
+ Entry** entry,
+ const CompletionCallback& callback);
+
+ // Called when the index is initilized to find the next entry in the iterator
+ // |iter|. If there are no more hashes in the iterator list, net::ERR_FAILED
+ // is returned. Otherwise, calls OpenEntryFromHash.
+ void GetNextEntryInIterator(void** iter,
+ Entry** next_entry,
+ const CompletionCallback& callback,
+ int error_code);
+
+ // Called when we tried to open an entry with hash alone. When a blank entry
+ // has been created and filled in with information from the disk - based on a
+ // hash alone - this checks that a duplicate active entry was not created
+ // using a key in the meantime.
+ void OnEntryOpenedFromHash(uint64 hash,
+ Entry** entry,
+ scoped_refptr<SimpleEntryImpl> simple_entry,
+ const CompletionCallback& callback,
+ int error_code);
+
+ // Called when we tried to open an entry from key. When the entry has been
+ // opened, a check for key mismatch is performed.
+ void OnEntryOpenedFromKey(const std::string key,
+ Entry** entry,
+ scoped_refptr<SimpleEntryImpl> simple_entry,
+ const CompletionCallback& callback,
+ int error_code);
+
+ // Called at the end of the asynchronous operation triggered by
+ // OpenEntryFromHash. Makes sure to continue iterating if the open entry was
+ // not a success.
+ void CheckIterationReturnValue(void** iter,
+ Entry** entry,
+ const CompletionCallback& callback,
+ int error_code);
+
+ const base::FilePath path_;
+ scoped_ptr<SimpleIndex> index_;
+ const scoped_refptr<base::SingleThreadTaskRunner> cache_thread_;
+ scoped_refptr<base::TaskRunner> worker_pool_;
+
+ int orig_max_size_;
+ const SimpleEntryImpl::OperationsMode entry_operations_mode_;
+
+ // TODO(gavinp): Store the entry_hash in SimpleEntryImpl, and index this map
+ // by hash. This will save memory, and make IndexReadyForDoom easier.
+ EntryMap active_entries_;
+
+ net::NetLog* const net_log_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
diff --git a/chromium/net/disk_cache/simple/simple_entry_format.cc b/chromium/net/disk_cache/simple/simple_entry_format.cc
new file mode 100644
index 00000000000..d35174a9a7f
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_format.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_entry_format.h"
+
+#include <cstring>
+
+namespace disk_cache {
+
+SimpleFileHeader::SimpleFileHeader() {
+ // Make hashing repeatable: leave no padding bytes untouched.
+ std::memset(this, 0, sizeof(*this));
+}
+
+SimpleFileEOF::SimpleFileEOF() {
+ // Make hashing repeatable: leave no padding bytes untouched.
+ std::memset(this, 0, sizeof(*this));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_entry_format.h b/chromium/net/disk_cache/simple/simple_entry_format.h
new file mode 100644
index 00000000000..d06ab1139c5
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_format.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
+
+
+#include "base/basictypes.h"
+#include "base/port.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace disk_cache {
+
+const uint64 kSimpleInitialMagicNumber = GG_UINT64_C(0xfcfb6d1ba7725c30);
+const uint64 kSimpleFinalMagicNumber = GG_UINT64_C(0xf4fa6f45970d41d8);
+
+// A file in the Simple cache consists of a SimpleFileHeader followed
+// by data.
+
+// A file in the Simple cache consists of:
+// - a SimpleFileHeader.
+// - the key.
+// - the data.
+// - at the end, a SimpleFileEOF record.
+const uint32 kSimpleVersion = 4;
+
+static const int kSimpleEntryFileCount = 3;
+
+struct NET_EXPORT_PRIVATE SimpleFileHeader {
+ SimpleFileHeader();
+
+ uint64 initial_magic_number;
+ uint32 version;
+ uint32 key_length;
+ uint32 key_hash;
+};
+
+struct SimpleFileEOF {
+ enum Flags {
+ FLAG_HAS_CRC32 = (1U << 0),
+ };
+
+ SimpleFileEOF();
+
+ uint64 final_magic_number;
+ uint32 flags;
+ uint32 data_crc32;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
diff --git a/chromium/net/disk_cache/simple/simple_entry_impl.cc b/chromium/net/disk_cache/simple/simple_entry_impl.cc
new file mode 100644
index 00000000000..3c3ec7daff6
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_impl.cc
@@ -0,0 +1,1187 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_entry_impl.h"
+
+#include <algorithm>
+#include <cstring>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/net_log_parameters.h"
+#include "net/disk_cache/simple/simple_backend_impl.h"
+#include "net/disk_cache/simple/simple_index.h"
+#include "net/disk_cache/simple/simple_net_log_parameters.h"
+#include "net/disk_cache/simple/simple_synchronous_entry.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "third_party/zlib/zlib.h"
+
+namespace {
+
+// Used in histograms, please only add entries at the end.
+enum ReadResult {
+ READ_RESULT_SUCCESS = 0,
+ READ_RESULT_INVALID_ARGUMENT = 1,
+ READ_RESULT_NONBLOCK_EMPTY_RETURN = 2,
+ READ_RESULT_BAD_STATE = 3,
+ READ_RESULT_FAST_EMPTY_RETURN = 4,
+ READ_RESULT_SYNC_READ_FAILURE = 5,
+ READ_RESULT_SYNC_CHECKSUM_FAILURE = 6,
+ READ_RESULT_MAX = 7,
+};
+
+// Used in histograms, please only add entries at the end.
+enum WriteResult {
+ WRITE_RESULT_SUCCESS = 0,
+ WRITE_RESULT_INVALID_ARGUMENT = 1,
+ WRITE_RESULT_OVER_MAX_SIZE = 2,
+ WRITE_RESULT_BAD_STATE = 3,
+ WRITE_RESULT_SYNC_WRITE_FAILURE = 4,
+ WRITE_RESULT_MAX = 5,
+};
+
+// Used in histograms, please only add entries at the end.
+enum HeaderSizeChange {
+ HEADER_SIZE_CHANGE_INITIAL,
+ HEADER_SIZE_CHANGE_SAME,
+ HEADER_SIZE_CHANGE_INCREASE,
+ HEADER_SIZE_CHANGE_DECREASE,
+ HEADER_SIZE_CHANGE_UNEXPECTED_WRITE,
+ HEADER_SIZE_CHANGE_MAX
+};
+
+void RecordReadResult(ReadResult result) {
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.ReadResult", result, READ_RESULT_MAX);
+};
+
+void RecordWriteResult(WriteResult result) {
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.WriteResult",
+ result, WRITE_RESULT_MAX);
+};
+
+// TODO(ttuttle): Consider removing this once we have a good handle on header
+// size changes.
+void RecordHeaderSizeChange(int old_size, int new_size) {
+ HeaderSizeChange size_change;
+
+ UMA_HISTOGRAM_COUNTS_10000("SimpleCache.HeaderSize", new_size);
+
+ if (old_size == 0) {
+ size_change = HEADER_SIZE_CHANGE_INITIAL;
+ } else if (new_size == old_size) {
+ size_change = HEADER_SIZE_CHANGE_SAME;
+ } else if (new_size > old_size) {
+ int delta = new_size - old_size;
+ UMA_HISTOGRAM_COUNTS_10000("SimpleCache.HeaderSizeIncreaseAbsolute",
+ delta);
+ UMA_HISTOGRAM_PERCENTAGE("SimpleCache.HeaderSizeIncreasePercentage",
+ delta * 100 / old_size);
+ size_change = HEADER_SIZE_CHANGE_INCREASE;
+ } else { // new_size < old_size
+ int delta = old_size - new_size;
+ UMA_HISTOGRAM_COUNTS_10000("SimpleCache.HeaderSizeDecreaseAbsolute",
+ delta);
+ UMA_HISTOGRAM_PERCENTAGE("SimpleCache.HeaderSizeDecreasePercentage",
+ delta * 100 / old_size);
+ size_change = HEADER_SIZE_CHANGE_DECREASE;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.HeaderSizeChange",
+ size_change,
+ HEADER_SIZE_CHANGE_MAX);
+}
+
+void RecordUnexpectedStream0Write() {
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.HeaderSizeChange",
+ HEADER_SIZE_CHANGE_UNEXPECTED_WRITE,
+ HEADER_SIZE_CHANGE_MAX);
+}
+
+// Short trampoline to take an owned input parameter and call a net completion
+// callback with its value.
+void CallCompletionCallback(const net::CompletionCallback& callback,
+ scoped_ptr<int> result) {
+ DCHECK(result);
+ if (!callback.is_null())
+ callback.Run(*result);
+}
+
+int g_open_entry_count = 0;
+
+void AdjustOpenEntryCountBy(int offset) {
+ g_open_entry_count += offset;
+ UMA_HISTOGRAM_COUNTS_10000("SimpleCache.GlobalOpenEntryCount",
+ g_open_entry_count);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+using base::Closure;
+using base::FilePath;
+using base::MessageLoopProxy;
+using base::Time;
+using base::TaskRunner;
+
+// A helper class to insure that RunNextOperationIfNeeded() is called when
+// exiting the current stack frame.
+class SimpleEntryImpl::ScopedOperationRunner {
+ public:
+ explicit ScopedOperationRunner(SimpleEntryImpl* entry) : entry_(entry) {
+ }
+
+ ~ScopedOperationRunner() {
+ entry_->RunNextOperationIfNeeded();
+ }
+
+ private:
+ SimpleEntryImpl* const entry_;
+};
+
+SimpleEntryImpl::SimpleEntryImpl(const FilePath& path,
+ const uint64 entry_hash,
+ OperationsMode operations_mode,
+ SimpleBackendImpl* backend,
+ net::NetLog* net_log)
+ : backend_(backend->AsWeakPtr()),
+ worker_pool_(backend->worker_pool()),
+ path_(path),
+ entry_hash_(entry_hash),
+ use_optimistic_operations_(operations_mode == OPTIMISTIC_OPERATIONS),
+ last_used_(Time::Now()),
+ last_modified_(last_used_),
+ open_count_(0),
+ state_(STATE_UNINITIALIZED),
+ synchronous_entry_(NULL),
+ net_log_(net::BoundNetLog::Make(
+ net_log, net::NetLog::SOURCE_DISK_CACHE_ENTRY)) {
+ COMPILE_ASSERT(arraysize(data_size_) == arraysize(crc32s_end_offset_),
+ arrays_should_be_same_size);
+ COMPILE_ASSERT(arraysize(data_size_) == arraysize(crc32s_),
+ arrays_should_be_same_size);
+ COMPILE_ASSERT(arraysize(data_size_) == arraysize(have_written_),
+ arrays_should_be_same_size);
+ COMPILE_ASSERT(arraysize(data_size_) == arraysize(crc_check_state_),
+ arrays_should_be_same_size);
+ MakeUninitialized();
+ net_log_.BeginEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY,
+ CreateNetLogSimpleEntryConstructionCallback(this));
+}
+
+int SimpleEntryImpl::OpenEntry(Entry** out_entry,
+ const CompletionCallback& callback) {
+ DCHECK(backend_.get());
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_CALL);
+
+ bool have_index = backend_->index()->initialized();
+ // This enumeration is used in histograms, add entries only at end.
+ enum OpenEntryIndexEnum {
+ INDEX_NOEXIST = 0,
+ INDEX_MISS = 1,
+ INDEX_HIT = 2,
+ INDEX_MAX = 3,
+ };
+ OpenEntryIndexEnum open_entry_index_enum = INDEX_NOEXIST;
+ if (have_index) {
+ if (backend_->index()->Has(entry_hash_))
+ open_entry_index_enum = INDEX_HIT;
+ else
+ open_entry_index_enum = INDEX_MISS;
+ }
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.OpenEntryIndexState",
+ open_entry_index_enum, INDEX_MAX);
+
+ // If entry is not known to the index, initiate fast failover to the network.
+ if (open_entry_index_enum == INDEX_MISS) {
+ net_log_.AddEventWithNetErrorCode(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END,
+ net::ERR_FAILED);
+ return net::ERR_FAILED;
+ }
+
+ pending_operations_.push(SimpleEntryOperation::OpenOperation(
+ this, have_index, callback, out_entry));
+ RunNextOperationIfNeeded();
+ return net::ERR_IO_PENDING;
+}
+
+int SimpleEntryImpl::CreateEntry(Entry** out_entry,
+ const CompletionCallback& callback) {
+ DCHECK(backend_.get());
+ DCHECK_EQ(entry_hash_, simple_util::GetEntryHashKey(key_));
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_CALL);
+
+ bool have_index = backend_->index()->initialized();
+ int ret_value = net::ERR_FAILED;
+ if (use_optimistic_operations_ &&
+ state_ == STATE_UNINITIALIZED && pending_operations_.size() == 0) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_OPTIMISTIC);
+
+ ReturnEntryToCaller(out_entry);
+ pending_operations_.push(SimpleEntryOperation::CreateOperation(
+ this, have_index, CompletionCallback(), static_cast<Entry**>(NULL)));
+ ret_value = net::OK;
+ } else {
+ pending_operations_.push(SimpleEntryOperation::CreateOperation(
+ this, have_index, callback, out_entry));
+ ret_value = net::ERR_IO_PENDING;
+ }
+
+ // We insert the entry in the index before creating the entry files in the
+ // SimpleSynchronousEntry, because this way the worst scenario is when we
+ // have the entry in the index but we don't have the created files yet, this
+ // way we never leak files. CreationOperationComplete will remove the entry
+ // from the index if the creation fails.
+ backend_->index()->Insert(key_);
+
+ RunNextOperationIfNeeded();
+ return ret_value;
+}
+
+int SimpleEntryImpl::DoomEntry(const CompletionCallback& callback) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_DOOM_CALL);
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_DOOM_BEGIN);
+
+ MarkAsDoomed();
+ scoped_ptr<int> result(new int());
+ Closure task = base::Bind(&SimpleSynchronousEntry::DoomEntry, path_, key_,
+ entry_hash_, result.get());
+ Closure reply = base::Bind(&CallCompletionCallback,
+ callback, base::Passed(&result));
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+ return net::ERR_IO_PENDING;
+}
+
+void SimpleEntryImpl::SetKey(const std::string& key) {
+ key_ = key;
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_SET_KEY,
+ net::NetLog::StringCallback("key", &key));
+}
+
+void SimpleEntryImpl::Doom() {
+ DoomEntry(CompletionCallback());
+}
+
+void SimpleEntryImpl::Close() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK_LT(0, open_count_);
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CLOSE_CALL);
+
+ if (--open_count_ > 0) {
+ DCHECK(!HasOneRef());
+ Release(); // Balanced in ReturnEntryToCaller().
+ return;
+ }
+
+ pending_operations_.push(SimpleEntryOperation::CloseOperation(this));
+ DCHECK(!HasOneRef());
+ Release(); // Balanced in ReturnEntryToCaller().
+ RunNextOperationIfNeeded();
+}
+
+std::string SimpleEntryImpl::GetKey() const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ return key_;
+}
+
+Time SimpleEntryImpl::GetLastUsed() const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ return last_used_;
+}
+
+Time SimpleEntryImpl::GetLastModified() const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ return last_modified_;
+}
+
+int32 SimpleEntryImpl::GetDataSize(int stream_index) const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK_LE(0, data_size_[stream_index]);
+ return data_size_[stream_index];
+}
+
+int SimpleEntryImpl::ReadData(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_CALL,
+ CreateNetLogReadWriteDataCallback(stream_index, offset, buf_len,
+ false));
+ }
+
+ if (stream_index < 0 || stream_index >= kSimpleEntryFileCount ||
+ buf_len < 0) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END,
+ CreateNetLogReadWriteCompleteCallback(net::ERR_INVALID_ARGUMENT));
+ }
+
+ RecordReadResult(READ_RESULT_INVALID_ARGUMENT);
+ return net::ERR_INVALID_ARGUMENT;
+ }
+ if (pending_operations_.empty() && (offset >= GetDataSize(stream_index) ||
+ offset < 0 || !buf_len)) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END,
+ CreateNetLogReadWriteCompleteCallback(0));
+ }
+
+ RecordReadResult(READ_RESULT_NONBLOCK_EMPTY_RETURN);
+ return 0;
+ }
+
+ // TODO(felipeg): Optimization: Add support for truly parallel read
+ // operations.
+ bool alone_in_queue =
+ pending_operations_.size() == 0 && state_ == STATE_READY;
+ pending_operations_.push(SimpleEntryOperation::ReadOperation(
+ this, stream_index, offset, buf_len, buf, callback, alone_in_queue));
+ RunNextOperationIfNeeded();
+ return net::ERR_IO_PENDING;
+}
+
+int SimpleEntryImpl::WriteData(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_CALL,
+ CreateNetLogReadWriteDataCallback(stream_index, offset, buf_len,
+ truncate));
+ }
+
+ if (stream_index < 0 || stream_index >= kSimpleEntryFileCount || offset < 0 ||
+ buf_len < 0) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_END,
+ CreateNetLogReadWriteCompleteCallback(net::ERR_INVALID_ARGUMENT));
+ }
+ RecordWriteResult(WRITE_RESULT_INVALID_ARGUMENT);
+ return net::ERR_INVALID_ARGUMENT;
+ }
+ if (backend_.get() && offset + buf_len > backend_->GetMaxFileSize()) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_END,
+ CreateNetLogReadWriteCompleteCallback(net::ERR_FAILED));
+ }
+ RecordWriteResult(WRITE_RESULT_OVER_MAX_SIZE);
+ return net::ERR_FAILED;
+ }
+ ScopedOperationRunner operation_runner(this);
+
+ // Currently, Simple Cache is only used for HTTP, which stores the headers in
+ // stream 0 and always writes them with a single, truncating write. Detect
+ // these writes and record the size and size changes of the headers. Also,
+ // note writes to stream 0 that violate those assumptions.
+ if (stream_index == 0) {
+ if (offset == 0 && truncate)
+ RecordHeaderSizeChange(data_size_[0], buf_len);
+ else
+ RecordUnexpectedStream0Write();
+ }
+
+ // We can only do optimistic Write if there is no pending operations, so
+ // that we are sure that the next call to RunNextOperationIfNeeded will
+ // actually run the write operation that sets the stream size. It also
+ // prevents from previous possibly-conflicting writes that could be stacked
+ // in the |pending_operations_|. We could optimize this for when we have
+ // only read operations enqueued.
+ const bool optimistic =
+ (use_optimistic_operations_ && state_ == STATE_READY &&
+ pending_operations_.size() == 0);
+ CompletionCallback op_callback;
+ scoped_refptr<net::IOBuffer> op_buf;
+ int ret_value = net::ERR_FAILED;
+ if (!optimistic) {
+ op_buf = buf;
+ op_callback = callback;
+ ret_value = net::ERR_IO_PENDING;
+ } else {
+ // TODO(gavinp,pasko): For performance, don't use a copy of an IOBuffer
+ // here to avoid paying the price of the RefCountedThreadSafe atomic
+ // operations.
+ if (buf) {
+ op_buf = new IOBuffer(buf_len);
+ memcpy(op_buf->data(), buf->data(), buf_len);
+ }
+ op_callback = CompletionCallback();
+ ret_value = buf_len;
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_OPTIMISTIC,
+ CreateNetLogReadWriteCompleteCallback(buf_len));
+ }
+ }
+
+ pending_operations_.push(SimpleEntryOperation::WriteOperation(this,
+ stream_index,
+ offset,
+ buf_len,
+ op_buf.get(),
+ truncate,
+ optimistic,
+ op_callback));
+ return ret_value;
+}
+
+int SimpleEntryImpl::ReadSparseData(int64 offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ NOTIMPLEMENTED();
+ return net::ERR_FAILED;
+}
+
+int SimpleEntryImpl::WriteSparseData(int64 offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ NOTIMPLEMENTED();
+ return net::ERR_FAILED;
+}
+
+int SimpleEntryImpl::GetAvailableRange(int64 offset,
+ int len,
+ int64* start,
+ const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ NOTIMPLEMENTED();
+ return net::ERR_FAILED;
+}
+
+bool SimpleEntryImpl::CouldBeSparse() const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ return false;
+}
+
+void SimpleEntryImpl::CancelSparseIO() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ NOTIMPLEMENTED();
+}
+
+int SimpleEntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // TODO(gavinp): Determine if the simple backend should support sparse data.
+ NOTIMPLEMENTED();
+ return net::ERR_FAILED;
+}
+
+SimpleEntryImpl::~SimpleEntryImpl() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(0U, pending_operations_.size());
+ DCHECK(state_ == STATE_UNINITIALIZED || state_ == STATE_FAILURE);
+ DCHECK(!synchronous_entry_);
+ RemoveSelfFromBackend();
+ net_log_.EndEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY);
+}
+
+void SimpleEntryImpl::MakeUninitialized() {
+ state_ = STATE_UNINITIALIZED;
+ std::memset(crc32s_end_offset_, 0, sizeof(crc32s_end_offset_));
+ std::memset(crc32s_, 0, sizeof(crc32s_));
+ std::memset(have_written_, 0, sizeof(have_written_));
+ std::memset(data_size_, 0, sizeof(data_size_));
+ for (size_t i = 0; i < arraysize(crc_check_state_); ++i) {
+ crc_check_state_[i] = CRC_CHECK_NEVER_READ_AT_ALL;
+ }
+}
+
+void SimpleEntryImpl::ReturnEntryToCaller(Entry** out_entry) {
+ DCHECK(out_entry);
+ ++open_count_;
+ AddRef(); // Balanced in Close()
+ *out_entry = this;
+}
+
+void SimpleEntryImpl::RemoveSelfFromBackend() {
+ if (!backend_.get())
+ return;
+ backend_->OnDeactivated(this);
+ backend_.reset();
+}
+
+void SimpleEntryImpl::MarkAsDoomed() {
+ if (!backend_.get())
+ return;
+ backend_->index()->Remove(key_);
+ RemoveSelfFromBackend();
+}
+
+void SimpleEntryImpl::RunNextOperationIfNeeded() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.EntryOperationsPending",
+ pending_operations_.size(), 0, 100, 20);
+ if (!pending_operations_.empty() && state_ != STATE_IO_PENDING) {
+ scoped_ptr<SimpleEntryOperation> operation(
+ new SimpleEntryOperation(pending_operations_.front()));
+ pending_operations_.pop();
+ switch (operation->type()) {
+ case SimpleEntryOperation::TYPE_OPEN:
+ OpenEntryInternal(operation->have_index(),
+ operation->callback(),
+ operation->out_entry());
+ break;
+ case SimpleEntryOperation::TYPE_CREATE:
+ CreateEntryInternal(operation->have_index(),
+ operation->callback(),
+ operation->out_entry());
+ break;
+ case SimpleEntryOperation::TYPE_CLOSE:
+ CloseInternal();
+ break;
+ case SimpleEntryOperation::TYPE_READ:
+ RecordReadIsParallelizable(*operation);
+ ReadDataInternal(operation->index(),
+ operation->offset(),
+ operation->buf(),
+ operation->length(),
+ operation->callback());
+ break;
+ case SimpleEntryOperation::TYPE_WRITE:
+ RecordWriteDependencyType(*operation);
+ WriteDataInternal(operation->index(),
+ operation->offset(),
+ operation->buf(),
+ operation->length(),
+ operation->callback(),
+ operation->truncate());
+ break;
+ default:
+ NOTREACHED();
+ }
+ // The operation is kept for histograms. Makes sure it does not leak
+ // resources.
+ executing_operation_.swap(operation);
+ executing_operation_->ReleaseReferences();
+ // |this| may have been deleted.
+ }
+}
+
+void SimpleEntryImpl::OpenEntryInternal(bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry) {
+ ScopedOperationRunner operation_runner(this);
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_BEGIN);
+
+ if (state_ == STATE_READY) {
+ ReturnEntryToCaller(out_entry);
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(callback,
+ net::OK));
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END,
+ CreateNetLogSimpleEntryCreationCallback(this, net::OK));
+ return;
+ } else if (state_ == STATE_FAILURE) {
+ if (!callback.is_null()) {
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ callback, net::ERR_FAILED));
+ }
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END,
+ CreateNetLogSimpleEntryCreationCallback(this, net::ERR_FAILED));
+ return;
+ }
+
+ DCHECK_EQ(STATE_UNINITIALIZED, state_);
+ DCHECK(!synchronous_entry_);
+ state_ = STATE_IO_PENDING;
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+ scoped_ptr<SimpleEntryCreationResults> results(
+ new SimpleEntryCreationResults(
+ SimpleEntryStat(last_used_, last_modified_, data_size_)));
+ Closure task = base::Bind(&SimpleSynchronousEntry::OpenEntry,
+ path_,
+ entry_hash_,
+ have_index,
+ results.get());
+ Closure reply = base::Bind(&SimpleEntryImpl::CreationOperationComplete,
+ this,
+ callback,
+ start_time,
+ base::Passed(&results),
+ out_entry,
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_OPEN_END);
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+}
+
+void SimpleEntryImpl::CreateEntryInternal(bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry) {
+ ScopedOperationRunner operation_runner(this);
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_BEGIN);
+
+ if (state_ != STATE_UNINITIALIZED) {
+ // There is already an active normal entry.
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_END,
+ CreateNetLogSimpleEntryCreationCallback(this, net::ERR_FAILED));
+
+ if (!callback.is_null()) {
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ callback, net::ERR_FAILED));
+ }
+ return;
+ }
+ DCHECK_EQ(STATE_UNINITIALIZED, state_);
+ DCHECK(!synchronous_entry_);
+
+ state_ = STATE_IO_PENDING;
+
+ // Since we don't know the correct values for |last_used_| and
+ // |last_modified_| yet, we make this approximation.
+ last_used_ = last_modified_ = base::Time::Now();
+
+ // If creation succeeds, we should mark all streams to be saved on close.
+ for (int i = 0; i < kSimpleEntryFileCount; ++i)
+ have_written_[i] = true;
+
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+ scoped_ptr<SimpleEntryCreationResults> results(
+ new SimpleEntryCreationResults(
+ SimpleEntryStat(last_used_, last_modified_, data_size_)));
+ Closure task = base::Bind(&SimpleSynchronousEntry::CreateEntry,
+ path_,
+ key_,
+ entry_hash_,
+ have_index,
+ results.get());
+ Closure reply = base::Bind(&SimpleEntryImpl::CreationOperationComplete,
+ this,
+ callback,
+ start_time,
+ base::Passed(&results),
+ out_entry,
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CREATE_END);
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+}
+
+void SimpleEntryImpl::CloseInternal() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ typedef SimpleSynchronousEntry::CRCRecord CRCRecord;
+ scoped_ptr<std::vector<CRCRecord> >
+ crc32s_to_write(new std::vector<CRCRecord>());
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CLOSE_BEGIN);
+
+ if (state_ == STATE_READY) {
+ DCHECK(synchronous_entry_);
+ state_ = STATE_IO_PENDING;
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ if (have_written_[i]) {
+ if (GetDataSize(i) == crc32s_end_offset_[i]) {
+ int32 crc = GetDataSize(i) == 0 ? crc32(0, Z_NULL, 0) : crc32s_[i];
+ crc32s_to_write->push_back(CRCRecord(i, true, crc));
+ } else {
+ crc32s_to_write->push_back(CRCRecord(i, false, 0));
+ }
+ }
+ }
+ } else {
+ DCHECK(STATE_UNINITIALIZED == state_ || STATE_FAILURE == state_);
+ }
+
+ if (synchronous_entry_) {
+ Closure task =
+ base::Bind(&SimpleSynchronousEntry::Close,
+ base::Unretained(synchronous_entry_),
+ SimpleEntryStat(last_used_, last_modified_, data_size_),
+ base::Passed(&crc32s_to_write));
+ Closure reply = base::Bind(&SimpleEntryImpl::CloseOperationComplete, this);
+ synchronous_entry_ = NULL;
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ if (!have_written_[i]) {
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.CheckCRCResult",
+ crc_check_state_[i], CRC_CHECK_MAX);
+ }
+ }
+ } else {
+ synchronous_entry_ = NULL;
+ CloseOperationComplete();
+ }
+}
+
+void SimpleEntryImpl::ReadDataInternal(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ ScopedOperationRunner operation_runner(this);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_BEGIN,
+ CreateNetLogReadWriteDataCallback(stream_index, offset, buf_len,
+ false));
+ }
+
+ if (state_ == STATE_FAILURE || state_ == STATE_UNINITIALIZED) {
+ if (!callback.is_null()) {
+ RecordReadResult(READ_RESULT_BAD_STATE);
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ callback, net::ERR_FAILED));
+ }
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END,
+ CreateNetLogReadWriteCompleteCallback(net::ERR_FAILED));
+ }
+ return;
+ }
+ DCHECK_EQ(STATE_READY, state_);
+ if (offset >= GetDataSize(stream_index) || offset < 0 || !buf_len) {
+ RecordReadResult(READ_RESULT_FAST_EMPTY_RETURN);
+ // If there is nothing to read, we bail out before setting state_ to
+ // STATE_IO_PENDING.
+ if (!callback.is_null())
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ callback, 0));
+ return;
+ }
+
+ buf_len = std::min(buf_len, GetDataSize(stream_index) - offset);
+
+ state_ = STATE_IO_PENDING;
+ if (backend_.get())
+ backend_->index()->UseIfExists(key_);
+
+ scoped_ptr<uint32> read_crc32(new uint32());
+ scoped_ptr<int> result(new int());
+ scoped_ptr<base::Time> last_used(new base::Time());
+ Closure task = base::Bind(
+ &SimpleSynchronousEntry::ReadData,
+ base::Unretained(synchronous_entry_),
+ SimpleSynchronousEntry::EntryOperationData(stream_index, offset, buf_len),
+ make_scoped_refptr(buf),
+ read_crc32.get(),
+ last_used.get(),
+ result.get());
+ Closure reply = base::Bind(&SimpleEntryImpl::ReadOperationComplete,
+ this,
+ stream_index,
+ offset,
+ callback,
+ base::Passed(&read_crc32),
+ base::Passed(&last_used),
+ base::Passed(&result));
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+}
+
+void SimpleEntryImpl::WriteDataInternal(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ ScopedOperationRunner operation_runner(this);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_BEGIN,
+ CreateNetLogReadWriteDataCallback(stream_index, offset, buf_len,
+ truncate));
+ }
+
+ if (state_ == STATE_FAILURE || state_ == STATE_UNINITIALIZED) {
+ RecordWriteResult(WRITE_RESULT_BAD_STATE);
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_END,
+ CreateNetLogReadWriteCompleteCallback(net::ERR_FAILED));
+ }
+ if (!callback.is_null()) {
+ // We need to posttask so that we don't go in a loop when we call the
+ // callback directly.
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ callback, net::ERR_FAILED));
+ }
+ // |this| may be destroyed after return here.
+ return;
+ }
+
+ DCHECK_EQ(STATE_READY, state_);
+ state_ = STATE_IO_PENDING;
+ if (backend_.get())
+ backend_->index()->UseIfExists(key_);
+ // It is easy to incrementally compute the CRC from [0 .. |offset + buf_len|)
+ // if |offset == 0| or we have already computed the CRC for [0 .. offset).
+ // We rely on most write operations being sequential, start to end to compute
+ // the crc of the data. When we write to an entry and close without having
+ // done a sequential write, we don't check the CRC on read.
+ if (offset == 0 || crc32s_end_offset_[stream_index] == offset) {
+ uint32 initial_crc = (offset != 0) ? crc32s_[stream_index]
+ : crc32(0, Z_NULL, 0);
+ if (buf_len > 0) {
+ crc32s_[stream_index] = crc32(initial_crc,
+ reinterpret_cast<const Bytef*>(buf->data()),
+ buf_len);
+ }
+ crc32s_end_offset_[stream_index] = offset + buf_len;
+ }
+
+ // |entry_stat| needs to be initialized before modifying |data_size_|.
+ scoped_ptr<SimpleEntryStat> entry_stat(
+ new SimpleEntryStat(last_used_, last_modified_, data_size_));
+ if (truncate) {
+ data_size_[stream_index] = offset + buf_len;
+ } else {
+ data_size_[stream_index] = std::max(offset + buf_len,
+ GetDataSize(stream_index));
+ }
+
+ // Since we don't know the correct values for |last_used_| and
+ // |last_modified_| yet, we make this approximation.
+ last_used_ = last_modified_ = base::Time::Now();
+
+ have_written_[stream_index] = true;
+
+ scoped_ptr<int> result(new int());
+ Closure task = base::Bind(&SimpleSynchronousEntry::WriteData,
+ base::Unretained(synchronous_entry_),
+ SimpleSynchronousEntry::EntryOperationData(
+ stream_index, offset, buf_len, truncate),
+ make_scoped_refptr(buf),
+ entry_stat.get(),
+ result.get());
+ Closure reply = base::Bind(&SimpleEntryImpl::WriteOperationComplete,
+ this,
+ stream_index,
+ callback,
+ base::Passed(&entry_stat),
+ base::Passed(&result));
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+}
+
+void SimpleEntryImpl::CreationOperationComplete(
+ const CompletionCallback& completion_callback,
+ const base::TimeTicks& start_time,
+ scoped_ptr<SimpleEntryCreationResults> in_results,
+ Entry** out_entry,
+ net::NetLog::EventType end_event_type) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, STATE_IO_PENDING);
+ DCHECK(in_results);
+ ScopedOperationRunner operation_runner(this);
+ UMA_HISTOGRAM_BOOLEAN(
+ "SimpleCache.EntryCreationResult", in_results->result == net::OK);
+ if (in_results->result != net::OK) {
+ if (in_results->result != net::ERR_FILE_EXISTS)
+ MarkAsDoomed();
+
+ net_log_.AddEventWithNetErrorCode(end_event_type, net::ERR_FAILED);
+
+ if (!completion_callback.is_null()) {
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ completion_callback, net::ERR_FAILED));
+ }
+ MakeUninitialized();
+ return;
+ }
+ // If out_entry is NULL, it means we already called ReturnEntryToCaller from
+ // the optimistic Create case.
+ if (out_entry)
+ ReturnEntryToCaller(out_entry);
+
+ state_ = STATE_READY;
+ synchronous_entry_ = in_results->sync_entry;
+ if (key_.empty()) {
+ SetKey(synchronous_entry_->key());
+ } else {
+ // This should only be triggered when creating an entry. The key check in
+ // the open case is handled in SimpleBackendImpl.
+ DCHECK_EQ(key_, synchronous_entry_->key());
+ }
+ UpdateDataFromEntryStat(in_results->entry_stat);
+ UMA_HISTOGRAM_TIMES("SimpleCache.EntryCreationTime",
+ (base::TimeTicks::Now() - start_time));
+ AdjustOpenEntryCountBy(1);
+
+ net_log_.AddEvent(end_event_type);
+ if (!completion_callback.is_null()) {
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ completion_callback, net::OK));
+ }
+}
+
+void SimpleEntryImpl::EntryOperationComplete(
+ int stream_index,
+ const CompletionCallback& completion_callback,
+ const SimpleEntryStat& entry_stat,
+ scoped_ptr<int> result) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK(synchronous_entry_);
+ DCHECK_EQ(STATE_IO_PENDING, state_);
+ DCHECK(result);
+ state_ = STATE_READY;
+ if (*result < 0) {
+ MarkAsDoomed();
+ state_ = STATE_FAILURE;
+ crc32s_end_offset_[stream_index] = 0;
+ } else {
+ UpdateDataFromEntryStat(entry_stat);
+ }
+
+ if (!completion_callback.is_null()) {
+ MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(
+ completion_callback, *result));
+ }
+ RunNextOperationIfNeeded();
+}
+
+void SimpleEntryImpl::ReadOperationComplete(
+ int stream_index,
+ int offset,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<uint32> read_crc32,
+ scoped_ptr<base::Time> last_used,
+ scoped_ptr<int> result) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK(synchronous_entry_);
+ DCHECK_EQ(STATE_IO_PENDING, state_);
+ DCHECK(read_crc32);
+ DCHECK(result);
+
+ if (*result > 0 &&
+ crc_check_state_[stream_index] == CRC_CHECK_NEVER_READ_AT_ALL) {
+ crc_check_state_[stream_index] = CRC_CHECK_NEVER_READ_TO_END;
+ }
+
+ if (*result > 0 && crc32s_end_offset_[stream_index] == offset) {
+ uint32 current_crc = offset == 0 ? crc32(0, Z_NULL, 0)
+ : crc32s_[stream_index];
+ crc32s_[stream_index] = crc32_combine(current_crc, *read_crc32, *result);
+ crc32s_end_offset_[stream_index] += *result;
+ if (!have_written_[stream_index] &&
+ GetDataSize(stream_index) == crc32s_end_offset_[stream_index]) {
+ // We have just read a file from start to finish, and so we have
+ // computed a crc of the entire file. We can check it now. If a cache
+ // entry has a single reader, the normal pattern is to read from start
+ // to finish.
+
+ // Other cases are possible. In the case of two readers on the same
+ // entry, one reader can be behind the other. In this case we compute
+ // the crc as the most advanced reader progresses, and check it for
+ // both readers as they read the last byte.
+
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CHECKSUM_BEGIN);
+
+ scoped_ptr<int> new_result(new int());
+ Closure task = base::Bind(&SimpleSynchronousEntry::CheckEOFRecord,
+ base::Unretained(synchronous_entry_),
+ stream_index,
+ data_size_[stream_index],
+ crc32s_[stream_index],
+ new_result.get());
+ Closure reply = base::Bind(&SimpleEntryImpl::ChecksumOperationComplete,
+ this, *result, stream_index,
+ completion_callback,
+ base::Passed(&new_result));
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, reply);
+ crc_check_state_[stream_index] = CRC_CHECK_DONE;
+ return;
+ }
+ }
+
+ if (*result < 0) {
+ RecordReadResult(READ_RESULT_SYNC_READ_FAILURE);
+ } else {
+ RecordReadResult(READ_RESULT_SUCCESS);
+ if (crc_check_state_[stream_index] == CRC_CHECK_NEVER_READ_TO_END &&
+ offset + *result == GetDataSize(stream_index)) {
+ crc_check_state_[stream_index] = CRC_CHECK_NOT_DONE;
+ }
+ }
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END,
+ CreateNetLogReadWriteCompleteCallback(*result));
+ }
+
+ EntryOperationComplete(
+ stream_index,
+ completion_callback,
+ SimpleEntryStat(*last_used, last_modified_, data_size_),
+ result.Pass());
+}
+
+void SimpleEntryImpl::WriteOperationComplete(
+ int stream_index,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<SimpleEntryStat> entry_stat,
+ scoped_ptr<int> result) {
+ if (*result >= 0)
+ RecordWriteResult(WRITE_RESULT_SUCCESS);
+ else
+ RecordWriteResult(WRITE_RESULT_SYNC_WRITE_FAILURE);
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_WRITE_END,
+ CreateNetLogReadWriteCompleteCallback(*result));
+ }
+
+ EntryOperationComplete(
+ stream_index, completion_callback, *entry_stat, result.Pass());
+}
+
+void SimpleEntryImpl::ChecksumOperationComplete(
+ int orig_result,
+ int stream_index,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<int> result) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK(synchronous_entry_);
+ DCHECK_EQ(STATE_IO_PENDING, state_);
+ DCHECK(result);
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEventWithNetErrorCode(
+ net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CHECKSUM_END,
+ *result);
+ }
+
+ if (*result == net::OK) {
+ *result = orig_result;
+ if (orig_result >= 0)
+ RecordReadResult(READ_RESULT_SUCCESS);
+ else
+ RecordReadResult(READ_RESULT_SYNC_READ_FAILURE);
+ } else {
+ RecordReadResult(READ_RESULT_SYNC_CHECKSUM_FAILURE);
+ }
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_READ_END,
+ CreateNetLogReadWriteCompleteCallback(*result));
+ }
+
+ EntryOperationComplete(
+ stream_index,
+ completion_callback,
+ SimpleEntryStat(last_used_, last_modified_, data_size_),
+ result.Pass());
+}
+
+void SimpleEntryImpl::CloseOperationComplete() {
+ DCHECK(!synchronous_entry_);
+ DCHECK_EQ(0, open_count_);
+ DCHECK(STATE_IO_PENDING == state_ || STATE_FAILURE == state_ ||
+ STATE_UNINITIALIZED == state_);
+ net_log_.AddEvent(net::NetLog::TYPE_SIMPLE_CACHE_ENTRY_CLOSE_END);
+ AdjustOpenEntryCountBy(-1);
+ MakeUninitialized();
+ RunNextOperationIfNeeded();
+}
+
+void SimpleEntryImpl::UpdateDataFromEntryStat(
+ const SimpleEntryStat& entry_stat) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK(synchronous_entry_);
+ DCHECK_EQ(STATE_READY, state_);
+
+ last_used_ = entry_stat.last_used;
+ last_modified_ = entry_stat.last_modified;
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ data_size_[i] = entry_stat.data_size[i];
+ }
+ if (backend_.get())
+ backend_->index()->UpdateEntrySize(key_, GetDiskUsage());
+}
+
+int64 SimpleEntryImpl::GetDiskUsage() const {
+ int64 file_size = 0;
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ file_size +=
+ simple_util::GetFileSizeFromKeyAndDataSize(key_, data_size_[i]);
+ }
+ return file_size;
+}
+
+void SimpleEntryImpl::RecordReadIsParallelizable(
+ const SimpleEntryOperation& operation) const {
+ if (!executing_operation_)
+ return;
+ // TODO(clamy): The values of this histogram should be changed to something
+ // more useful.
+ bool parallelizable_read =
+ !operation.alone_in_queue() &&
+ executing_operation_->type() == SimpleEntryOperation::TYPE_READ;
+ UMA_HISTOGRAM_BOOLEAN("SimpleCache.ReadIsParallelizable",
+ parallelizable_read);
+}
+
+void SimpleEntryImpl::RecordWriteDependencyType(
+ const SimpleEntryOperation& operation) const {
+ if (!executing_operation_)
+ return;
+ // Used in histograms, please only add entries at the end.
+ enum WriteDependencyType {
+ WRITE_OPTIMISTIC = 0,
+ WRITE_FOLLOWS_CONFLICTING_OPTIMISTIC = 1,
+ WRITE_FOLLOWS_NON_CONFLICTING_OPTIMISTIC = 2,
+ WRITE_FOLLOWS_CONFLICTING_WRITE = 3,
+ WRITE_FOLLOWS_NON_CONFLICTING_WRITE = 4,
+ WRITE_FOLLOWS_CONFLICTING_READ = 5,
+ WRITE_FOLLOWS_NON_CONFLICTING_READ = 6,
+ WRITE_FOLLOWS_OTHER = 7,
+ WRITE_DEPENDENCY_TYPE_MAX = 8,
+ };
+
+ WriteDependencyType type = WRITE_FOLLOWS_OTHER;
+ if (operation.optimistic()) {
+ type = WRITE_OPTIMISTIC;
+ } else if (executing_operation_->type() == SimpleEntryOperation::TYPE_READ ||
+ executing_operation_->type() == SimpleEntryOperation::TYPE_WRITE) {
+ bool conflicting = executing_operation_->ConflictsWith(operation);
+
+ if (executing_operation_->type() == SimpleEntryOperation::TYPE_READ) {
+ type = conflicting ? WRITE_FOLLOWS_CONFLICTING_READ
+ : WRITE_FOLLOWS_NON_CONFLICTING_READ;
+ } else if (executing_operation_->optimistic()) {
+ type = conflicting ? WRITE_FOLLOWS_CONFLICTING_OPTIMISTIC
+ : WRITE_FOLLOWS_NON_CONFLICTING_OPTIMISTIC;
+ } else {
+ type = conflicting ? WRITE_FOLLOWS_CONFLICTING_WRITE
+ : WRITE_FOLLOWS_NON_CONFLICTING_WRITE;
+ }
+ }
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.WriteDependencyType", type, WRITE_DEPENDENCY_TYPE_MAX);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_entry_impl.h b/chromium/net/disk_cache/simple/simple_entry_impl.h
new file mode 100644
index 00000000000..7eb8914e873
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_impl.h
@@ -0,0 +1,290 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
+
+#include <queue>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "net/base/net_log.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_entry_operation.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace disk_cache {
+
+class SimpleBackendImpl;
+class SimpleSynchronousEntry;
+struct SimpleEntryStat;
+struct SimpleEntryCreationResults;
+
+// SimpleEntryImpl is the IO thread interface to an entry in the very simple
+// disk cache. It proxies for the SimpleSynchronousEntry, which performs IO
+// on the worker thread.
+class SimpleEntryImpl : public Entry, public base::RefCounted<SimpleEntryImpl>,
+ public base::SupportsWeakPtr<SimpleEntryImpl> {
+ friend class base::RefCounted<SimpleEntryImpl>;
+ public:
+ enum OperationsMode {
+ NON_OPTIMISTIC_OPERATIONS,
+ OPTIMISTIC_OPERATIONS,
+ };
+
+ SimpleEntryImpl(const base::FilePath& path,
+ uint64 entry_hash,
+ OperationsMode operations_mode,
+ SimpleBackendImpl* backend,
+ net::NetLog* net_log);
+
+ // Adds another reader/writer to this entry, if possible, returning |this| to
+ // |entry|.
+ int OpenEntry(Entry** entry, const CompletionCallback& callback);
+
+ // Creates this entry, if possible. Returns |this| to |entry|.
+ int CreateEntry(Entry** entry, const CompletionCallback& callback);
+
+ // Identical to Backend::Doom() except that it accepts a CompletionCallback.
+ int DoomEntry(const CompletionCallback& callback);
+
+ const std::string& key() const { return key_; }
+ uint64 entry_hash() const { return entry_hash_; }
+ void SetKey(const std::string& key);
+
+ // From Entry:
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int stream_index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset,
+ int len,
+ int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ class ScopedOperationRunner;
+ friend class ScopedOperationRunner;
+
+ enum State {
+ // The state immediately after construction, but before |synchronous_entry_|
+ // has been assigned. This is the state at construction, and is the only
+ // legal state to destruct an entry in.
+ STATE_UNINITIALIZED,
+
+ // This entry is available for regular IO.
+ STATE_READY,
+
+ // IO is currently in flight, operations must wait for completion before
+ // launching.
+ STATE_IO_PENDING,
+
+ // A failure occurred in the current or previous operation. All operations
+ // after that must fail, until we receive a Close().
+ STATE_FAILURE,
+ };
+
+ // Used in histograms, please only add entries at the end.
+ enum CheckCrcResult {
+ CRC_CHECK_NEVER_READ_TO_END = 0,
+ CRC_CHECK_NOT_DONE = 1,
+ CRC_CHECK_DONE = 2,
+ CRC_CHECK_NEVER_READ_AT_ALL = 3,
+ CRC_CHECK_MAX = 4,
+ };
+
+ virtual ~SimpleEntryImpl();
+
+ // Sets entry to STATE_UNINITIALIZED.
+ void MakeUninitialized();
+
+ // Return this entry to a user of the API in |out_entry|. Increments the user
+ // count.
+ void ReturnEntryToCaller(Entry** out_entry);
+
+ // Ensures that |this| is no longer referenced by our |backend_|, this
+ // guarantees that this entry cannot have OpenEntry/CreateEntry called again.
+ void RemoveSelfFromBackend();
+
+ // An error occured, and the SimpleSynchronousEntry should have Doomed
+ // us at this point. We need to remove |this| from the Backend and the
+ // index.
+ void MarkAsDoomed();
+
+ // Runs the next operation in the queue, if any and if there is no other
+ // operation running at the moment.
+ // WARNING: May delete |this|, as an operation in the queue can contain
+ // the last reference.
+ void RunNextOperationIfNeeded();
+
+ void OpenEntryInternal(bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry);
+
+ void CreateEntryInternal(bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry);
+
+ void CloseInternal();
+
+ void ReadDataInternal(int index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback);
+
+ void WriteDataInternal(int index,
+ int offset,
+ net::IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback,
+ bool truncate);
+
+ // Called after a SimpleSynchronousEntry has completed CreateEntry() or
+ // OpenEntry(). If |in_sync_entry| is non-NULL, creation is successful and we
+ // can return |this| SimpleEntryImpl to |*out_entry|. Runs
+ // |completion_callback|.
+ void CreationOperationComplete(
+ const CompletionCallback& completion_callback,
+ const base::TimeTicks& start_time,
+ scoped_ptr<SimpleEntryCreationResults> in_results,
+ Entry** out_entry,
+ net::NetLog::EventType end_event_type);
+
+ // Called after we've closed and written the EOF record to our entry. Until
+ // this point it hasn't been safe to OpenEntry() the same entry, but from this
+ // point it is.
+ void CloseOperationComplete();
+
+ // Internal utility method used by other completion methods. Calls
+ // |completion_callback| after updating state and dooming on errors.
+ void EntryOperationComplete(int stream_index,
+ const CompletionCallback& completion_callback,
+ const SimpleEntryStat& entry_stat,
+ scoped_ptr<int> result);
+
+ // Called after an asynchronous read. Updates |crc32s_| if possible.
+ void ReadOperationComplete(int stream_index,
+ int offset,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<uint32> read_crc32,
+ scoped_ptr<base::Time> last_used,
+ scoped_ptr<int> result);
+
+ // Called after an asynchronous write completes.
+ void WriteOperationComplete(int stream_index,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<SimpleEntryStat> entry_stat,
+ scoped_ptr<int> result);
+
+ // Called after validating the checksums on an entry. Passes through the
+ // original result if successful, propogates the error if the checksum does
+ // not validate.
+ void ChecksumOperationComplete(
+ int stream_index,
+ int orig_result,
+ const CompletionCallback& completion_callback,
+ scoped_ptr<int> result);
+
+ // Called after completion of asynchronous IO and receiving file metadata for
+ // the entry in |entry_stat|. Updates the metadata in the entry and in the
+ // index to make them available on next IO operations.
+ void UpdateDataFromEntryStat(const SimpleEntryStat& entry_stat);
+
+ int64 GetDiskUsage() const;
+
+ // Used to report histograms.
+ void RecordReadIsParallelizable(const SimpleEntryOperation& operation) const;
+ void RecordWriteDependencyType(const SimpleEntryOperation& operation) const;
+
+ // All nonstatic SimpleEntryImpl methods should always be called on the IO
+ // thread, in all cases. |io_thread_checker_| documents and enforces this.
+ base::ThreadChecker io_thread_checker_;
+
+ base::WeakPtr<SimpleBackendImpl> backend_;
+ const scoped_refptr<base::TaskRunner> worker_pool_;
+ const base::FilePath path_;
+ const uint64 entry_hash_;
+ const bool use_optimistic_operations_;
+ std::string key_;
+
+ // |last_used_|, |last_modified_| and |data_size_| are copied from the
+ // synchronous entry at the completion of each item of asynchronous IO.
+ // TODO(clamy): Unify last_used_ with data in the index.
+ base::Time last_used_;
+ base::Time last_modified_;
+ int32 data_size_[kSimpleEntryFileCount];
+
+ // Number of times this object has been returned from Backend::OpenEntry() and
+ // Backend::CreateEntry() without subsequent Entry::Close() calls. Used to
+ // notify the backend when this entry not used by any callers.
+ int open_count_;
+
+ State state_;
+
+ // When possible, we compute a crc32, for the data in each entry as we read or
+ // write. For each stream, |crc32s_[index]| is the crc32 of that stream from
+ // [0 .. |crc32s_end_offset_|). If |crc32s_end_offset_[index] == 0| then the
+ // value of |crc32s_[index]| is undefined.
+ int32 crc32s_end_offset_[kSimpleEntryFileCount];
+ uint32 crc32s_[kSimpleEntryFileCount];
+
+ // If |have_written_[index]| is true, we have written to the stream |index|.
+ bool have_written_[kSimpleEntryFileCount];
+
+ // Reflects how much CRC checking has been done with the entry. This state is
+ // reported on closing each entry stream.
+ CheckCrcResult crc_check_state_[kSimpleEntryFileCount];
+
+ // The |synchronous_entry_| is the worker thread object that performs IO on
+ // entries. It's owned by this SimpleEntryImpl whenever |operation_running_|
+ // is false (i.e. when an operation is not pending on the worker pool).
+ SimpleSynchronousEntry* synchronous_entry_;
+
+ std::queue<SimpleEntryOperation> pending_operations_;
+
+ net::BoundNetLog net_log_;
+
+ scoped_ptr<SimpleEntryOperation> executing_operation_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
diff --git a/chromium/net/disk_cache/simple/simple_entry_operation.cc b/chromium/net/disk_cache/simple/simple_entry_operation.cc
new file mode 100644
index 00000000000..81d5f7c888b
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_operation.cc
@@ -0,0 +1,184 @@
+// Copyright 2013 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/disk_cache/simple/simple_entry_operation.h"
+
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/simple/simple_entry_impl.h"
+
+namespace disk_cache {
+
+SimpleEntryOperation::SimpleEntryOperation(const SimpleEntryOperation& other)
+ : entry_(other.entry_.get()),
+ buf_(other.buf_),
+ callback_(other.callback_),
+ out_entry_(other.out_entry_),
+ offset_(other.offset_),
+ length_(other.length_),
+ type_(other.type_),
+ have_index_(other.have_index_),
+ index_(other.index_),
+ truncate_(other.truncate_),
+ optimistic_(other.optimistic_),
+ alone_in_queue_(other.alone_in_queue_) {
+}
+
+SimpleEntryOperation::~SimpleEntryOperation() {}
+
+// Static.
+SimpleEntryOperation SimpleEntryOperation::OpenOperation(
+ SimpleEntryImpl* entry,
+ bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry) {
+ return SimpleEntryOperation(entry,
+ NULL,
+ callback,
+ out_entry,
+ 0,
+ 0,
+ TYPE_OPEN,
+ have_index,
+ 0,
+ false,
+ false,
+ false);
+}
+
+// Static.
+SimpleEntryOperation SimpleEntryOperation::CreateOperation(
+ SimpleEntryImpl* entry,
+ bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry) {
+ return SimpleEntryOperation(entry,
+ NULL,
+ callback,
+ out_entry,
+ 0,
+ 0,
+ TYPE_CREATE,
+ have_index,
+ 0,
+ false,
+ false,
+ false);
+}
+
+// Static.
+SimpleEntryOperation SimpleEntryOperation::CloseOperation(
+ SimpleEntryImpl* entry) {
+ return SimpleEntryOperation(entry,
+ NULL,
+ CompletionCallback(),
+ NULL,
+ 0,
+ 0,
+ TYPE_CLOSE,
+ false,
+ 0,
+ false,
+ false,
+ false);
+}
+
+// Static.
+SimpleEntryOperation SimpleEntryOperation::ReadOperation(
+ SimpleEntryImpl* entry,
+ int index,
+ int offset,
+ int length,
+ net::IOBuffer* buf,
+ const CompletionCallback& callback,
+ bool alone_in_queue) {
+ return SimpleEntryOperation(entry,
+ buf,
+ callback,
+ NULL,
+ offset,
+ length,
+ TYPE_READ,
+ false,
+ index,
+ false,
+ false,
+ alone_in_queue);
+}
+
+// Static.
+SimpleEntryOperation SimpleEntryOperation::WriteOperation(
+ SimpleEntryImpl* entry,
+ int index,
+ int offset,
+ int length,
+ net::IOBuffer* buf,
+ bool truncate,
+ bool optimistic,
+ const CompletionCallback& callback) {
+ return SimpleEntryOperation(entry,
+ buf,
+ callback,
+ NULL,
+ offset,
+ length,
+ TYPE_WRITE,
+ false,
+ index,
+ truncate,
+ optimistic,
+ false);
+}
+
+bool SimpleEntryOperation::ConflictsWith(
+ const SimpleEntryOperation& other_op) const {
+ if (type_ != TYPE_READ && type_ != TYPE_WRITE)
+ return true;
+ if (other_op.type() != TYPE_READ && other_op.type() != TYPE_WRITE)
+ return true;
+ if (type() == TYPE_READ && other_op.type() == TYPE_READ)
+ return false;
+ if (index_ != other_op.index_)
+ return false;
+ int end = (type_ == TYPE_WRITE && truncate_) ? INT_MAX : offset_ + length_;
+ int other_op_end = (other_op.type() == TYPE_WRITE && other_op.truncate())
+ ? INT_MAX
+ : other_op.offset() + other_op.length();
+ return (offset_ < other_op_end && other_op.offset() < end);
+}
+
+void SimpleEntryOperation::ReleaseReferences() {
+ callback_ = CompletionCallback();
+ buf_ = NULL;
+ entry_ = NULL;
+}
+
+SimpleEntryOperation::SimpleEntryOperation(SimpleEntryImpl* entry,
+ net::IOBuffer* buf,
+ const CompletionCallback& callback,
+ Entry** out_entry,
+ int offset,
+ int length,
+ EntryOperationType type,
+ bool have_index,
+ int index,
+ bool truncate,
+ bool optimistic,
+ bool alone_in_queue)
+ : entry_(entry),
+ buf_(buf),
+ callback_(callback),
+ out_entry_(out_entry),
+ offset_(offset),
+ length_(length),
+ type_(type),
+ have_index_(have_index),
+ index_(index),
+ truncate_(truncate),
+ optimistic_(optimistic),
+ alone_in_queue_(alone_in_queue) {
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_entry_operation.h b/chromium/net/disk_cache/simple/simple_entry_operation.h
new file mode 100644
index 00000000000..acdd60a3207
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_entry_operation.h
@@ -0,0 +1,125 @@
+// Copyright 2013 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_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace disk_cache {
+
+class Entry;
+class SimpleEntryImpl;
+
+// SimpleEntryOperation stores the information regarding operations in
+// SimpleEntryImpl, between the moment they are issued by users of the backend,
+// and the moment when they are executed.
+class SimpleEntryOperation {
+ public:
+ typedef net::CompletionCallback CompletionCallback;
+
+ enum EntryOperationType {
+ TYPE_OPEN = 0,
+ TYPE_CREATE = 1,
+ TYPE_CLOSE = 2,
+ TYPE_READ = 3,
+ TYPE_WRITE = 4,
+ };
+
+ SimpleEntryOperation(const SimpleEntryOperation& other);
+ ~SimpleEntryOperation();
+
+ static SimpleEntryOperation OpenOperation(SimpleEntryImpl* entry,
+ bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry);
+ static SimpleEntryOperation CreateOperation(
+ SimpleEntryImpl* entry,
+ bool have_index,
+ const CompletionCallback& callback,
+ Entry** out_entry);
+ static SimpleEntryOperation CloseOperation(SimpleEntryImpl* entry);
+ static SimpleEntryOperation ReadOperation(SimpleEntryImpl* entry,
+ int index,
+ int offset,
+ int length,
+ net::IOBuffer* buf,
+ const CompletionCallback& callback,
+ bool alone_in_queue);
+ static SimpleEntryOperation WriteOperation(
+ SimpleEntryImpl* entry,
+ int index,
+ int offset,
+ int length,
+ net::IOBuffer* buf,
+ bool truncate,
+ bool optimistic,
+ const CompletionCallback& callback);
+
+ bool ConflictsWith(const SimpleEntryOperation& other_op) const;
+ // Releases all references. After calling this operation, SimpleEntryOperation
+ // will only hold POD members.
+ void ReleaseReferences();
+
+ EntryOperationType type() const {
+ return static_cast<EntryOperationType>(type_);
+ }
+ const CompletionCallback& callback() const { return callback_; }
+ Entry** out_entry() { return out_entry_; }
+ bool have_index() const { return have_index_; }
+ int index() const { return index_; }
+ int offset() const { return offset_; }
+ int length() const { return length_; }
+ net::IOBuffer* buf() { return buf_.get(); }
+ bool truncate() const { return truncate_; }
+ bool optimistic() const { return optimistic_; }
+ bool alone_in_queue() const { return alone_in_queue_; }
+
+ private:
+ SimpleEntryOperation(SimpleEntryImpl* entry,
+ net::IOBuffer* buf,
+ const CompletionCallback& callback,
+ Entry** out_entry,
+ int offset,
+ int length,
+ EntryOperationType type,
+ bool have_index,
+ int index,
+ bool truncate,
+ bool optimistic,
+ bool alone_in_queue);
+
+ // This ensures entry will not be deleted until the operation has ran.
+ scoped_refptr<SimpleEntryImpl> entry_;
+ scoped_refptr<net::IOBuffer> buf_;
+ CompletionCallback callback_;
+
+ // Used in open and create operations.
+ Entry** out_entry_;
+
+ // Used in write and read operations.
+ const int offset_;
+ const int length_;
+
+ const unsigned int type_ : 3; /* 3 */
+ // Used in open and create operations.
+ const unsigned int have_index_ : 1; /* 4 */
+ // Used in write and read operations.
+ const unsigned int index_ : 2; /* 6 */
+ // Used only in write operations.
+ const unsigned int truncate_ : 1; /* 7 */
+ const unsigned int optimistic_ : 1; /* 8 */
+ // Used only in SimpleCache.ReadIsParallelizable histogram.
+ const unsigned int alone_in_queue_ : 1; /* 9 */
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
diff --git a/chromium/net/disk_cache/simple/simple_index.cc b/chromium/net/disk_cache/simple/simple_index.cc
new file mode 100644
index 00000000000..78ce87ed71d
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index.cc
@@ -0,0 +1,461 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_index.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/task_runner.h"
+#include "base/threading/worker_pool.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_index_file.h"
+#include "net/disk_cache/simple/simple_synchronous_entry.h"
+#include "net/disk_cache/simple/simple_util.h"
+
+#if defined(OS_POSIX)
+#include <sys/stat.h>
+#include <sys/time.h>
+#endif
+
+namespace {
+
+// How many milliseconds we delay writing the index to disk since the last cache
+// operation has happened.
+const int kDefaultWriteToDiskDelayMSecs = 20000;
+const int kDefaultWriteToDiskOnBackgroundDelayMSecs = 100;
+
+// Divides the cache space into this amount of parts to evict when only one part
+// is left.
+const uint32 kEvictionMarginDivisor = 20;
+
+const uint32 kBytesInKb = 1024;
+
+// Utility class used for timestamp comparisons in entry metadata while sorting.
+class CompareHashesForTimestamp {
+ typedef disk_cache::SimpleIndex SimpleIndex;
+ typedef disk_cache::SimpleIndex::EntrySet EntrySet;
+ public:
+ explicit CompareHashesForTimestamp(const EntrySet& set);
+
+ bool operator()(uint64 hash1, uint64 hash2);
+ private:
+ const EntrySet& entry_set_;
+};
+
+CompareHashesForTimestamp::CompareHashesForTimestamp(const EntrySet& set)
+ : entry_set_(set) {
+}
+
+bool CompareHashesForTimestamp::operator()(uint64 hash1, uint64 hash2) {
+ EntrySet::const_iterator it1 = entry_set_.find(hash1);
+ DCHECK(it1 != entry_set_.end());
+ EntrySet::const_iterator it2 = entry_set_.find(hash2);
+ DCHECK(it2 != entry_set_.end());
+ return it1->second.GetLastUsedTime() < it2->second.GetLastUsedTime();
+}
+
+} // namespace
+
+namespace disk_cache {
+
+EntryMetadata::EntryMetadata() : last_used_time_(0), entry_size_(0) {}
+
+EntryMetadata::EntryMetadata(base::Time last_used_time, uint64 entry_size)
+ : last_used_time_(last_used_time.ToInternalValue()),
+ entry_size_(entry_size) {}
+
+base::Time EntryMetadata::GetLastUsedTime() const {
+ return base::Time::FromInternalValue(last_used_time_);
+}
+
+void EntryMetadata::SetLastUsedTime(const base::Time& last_used_time) {
+ last_used_time_ = last_used_time.ToInternalValue();
+}
+
+void EntryMetadata::Serialize(Pickle* pickle) const {
+ DCHECK(pickle);
+ COMPILE_ASSERT(sizeof(EntryMetadata) == (sizeof(int64) + sizeof(uint64)),
+ EntryMetadata_has_two_member_variables);
+ pickle->WriteInt64(last_used_time_);
+ pickle->WriteUInt64(entry_size_);
+}
+
+bool EntryMetadata::Deserialize(PickleIterator* it) {
+ DCHECK(it);
+ return it->ReadInt64(&last_used_time_) && it->ReadUInt64(&entry_size_);
+}
+
+SimpleIndex::SimpleIndex(base::SingleThreadTaskRunner* io_thread,
+ const base::FilePath& cache_directory,
+ scoped_ptr<SimpleIndexFile> index_file)
+ : cache_size_(0),
+ max_size_(0),
+ high_watermark_(0),
+ low_watermark_(0),
+ eviction_in_progress_(false),
+ initialized_(false),
+ cache_directory_(cache_directory),
+ index_file_(index_file.Pass()),
+ io_thread_(io_thread),
+ // Creating the callback once so it is reused every time
+ // write_to_disk_timer_.Start() is called.
+ write_to_disk_cb_(base::Bind(&SimpleIndex::WriteToDisk, AsWeakPtr())),
+ app_on_background_(false) {}
+
+SimpleIndex::~SimpleIndex() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+
+ // Fail all callbacks waiting for the index to come up.
+ for (CallbackList::iterator it = to_run_when_initialized_.begin(),
+ end = to_run_when_initialized_.end(); it != end; ++it) {
+ it->Run(net::ERR_ABORTED);
+ }
+}
+
+void SimpleIndex::Initialize(base::Time cache_mtime) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+
+ // Take the foreground and background index flush delays from the experiment
+ // settings only if both are valid.
+ foreground_flush_delay_ = kDefaultWriteToDiskDelayMSecs;
+ background_flush_delay_ = kDefaultWriteToDiskOnBackgroundDelayMSecs;
+ const std::string index_flush_intervals = base::FieldTrialList::FindFullName(
+ "SimpleCacheIndexFlushDelay_Foreground_Background");
+ if (!index_flush_intervals.empty()) {
+ base::StringTokenizer tokens(index_flush_intervals, "_");
+ int foreground_delay, background_delay;
+ if (tokens.GetNext() &&
+ base::StringToInt(tokens.token(), &foreground_delay) &&
+ tokens.GetNext() &&
+ base::StringToInt(tokens.token(), &background_delay)) {
+ foreground_flush_delay_ = foreground_delay;
+ background_flush_delay_ = background_delay;
+ }
+ }
+
+#if defined(OS_ANDROID)
+ activity_status_listener_.reset(new base::android::ActivityStatus::Listener(
+ base::Bind(&SimpleIndex::OnActivityStateChange, AsWeakPtr())));
+#endif
+
+ SimpleIndexLoadResult* load_result = new SimpleIndexLoadResult();
+ scoped_ptr<SimpleIndexLoadResult> load_result_scoped(load_result);
+ base::Closure reply = base::Bind(
+ &SimpleIndex::MergeInitializingSet,
+ AsWeakPtr(),
+ base::Passed(&load_result_scoped));
+ index_file_->LoadIndexEntries(cache_mtime, reply, load_result);
+}
+
+bool SimpleIndex::SetMaxSize(int max_bytes) {
+ if (max_bytes < 0)
+ return false;
+
+ // Zero size means use the default.
+ if (!max_bytes)
+ return true;
+
+ max_size_ = max_bytes;
+ high_watermark_ = max_size_ - max_size_ / kEvictionMarginDivisor;
+ low_watermark_ = max_size_ - 2 * (max_size_ / kEvictionMarginDivisor);
+ return true;
+}
+
+int SimpleIndex::ExecuteWhenReady(const net::CompletionCallback& task) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ if (initialized_)
+ io_thread_->PostTask(FROM_HERE, base::Bind(task, net::OK));
+ else
+ to_run_when_initialized_.push_back(task);
+ return net::ERR_IO_PENDING;
+}
+
+scoped_ptr<SimpleIndex::HashList> SimpleIndex::RemoveEntriesBetween(
+ const base::Time initial_time, const base::Time end_time) {
+ return ExtractEntriesBetween(initial_time, end_time, true);
+}
+
+scoped_ptr<SimpleIndex::HashList> SimpleIndex::GetAllHashes() {
+ const base::Time null_time = base::Time();
+ return ExtractEntriesBetween(null_time, null_time, false);
+}
+
+int32 SimpleIndex::GetEntryCount() const {
+ // TODO(pasko): return a meaningful initial estimate before initialized.
+ return entries_set_.size();
+}
+
+void SimpleIndex::Insert(const std::string& key) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // Upon insert we don't know yet the size of the entry.
+ // It will be updated later when the SimpleEntryImpl finishes opening or
+ // creating the new entry, and then UpdateEntrySize will be called.
+ const uint64 hash_key = simple_util::GetEntryHashKey(key);
+ InsertInEntrySet(
+ hash_key, EntryMetadata(base::Time::Now(), 0), &entries_set_);
+ if (!initialized_)
+ removed_entries_.erase(hash_key);
+ PostponeWritingToDisk();
+}
+
+void SimpleIndex::Remove(const std::string& key) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ const uint64 hash_key = simple_util::GetEntryHashKey(key);
+ EntrySet::iterator it = entries_set_.find(hash_key);
+ if (it != entries_set_.end()) {
+ UpdateEntryIteratorSize(&it, 0);
+ entries_set_.erase(it);
+ }
+
+ if (!initialized_)
+ removed_entries_.insert(hash_key);
+ PostponeWritingToDisk();
+}
+
+bool SimpleIndex::Has(uint64 hash) const {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // If not initialized, always return true, forcing it to go to the disk.
+ return !initialized_ || entries_set_.count(hash) > 0;
+}
+
+bool SimpleIndex::UseIfExists(const std::string& key) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // Always update the last used time, even if it is during initialization.
+ // It will be merged later.
+ EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
+ if (it == entries_set_.end())
+ // If not initialized, always return true, forcing it to go to the disk.
+ return !initialized_;
+ it->second.SetLastUsedTime(base::Time::Now());
+ PostponeWritingToDisk();
+ return true;
+}
+
+void SimpleIndex::StartEvictionIfNeeded() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ if (eviction_in_progress_ || cache_size_ <= high_watermark_)
+ return;
+
+ // Take all live key hashes from the index and sort them by time.
+ eviction_in_progress_ = true;
+ eviction_start_time_ = base::TimeTicks::Now();
+ UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.CacheSizeOnStart2",
+ cache_size_ / kBytesInKb);
+ UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.MaxCacheSizeOnStart2",
+ max_size_ / kBytesInKb);
+ scoped_ptr<std::vector<uint64> > entry_hashes(new std::vector<uint64>());
+ for (EntrySet::const_iterator it = entries_set_.begin(),
+ end = entries_set_.end(); it != end; ++it) {
+ entry_hashes->push_back(it->first);
+ }
+ std::sort(entry_hashes->begin(), entry_hashes->end(),
+ CompareHashesForTimestamp(entries_set_));
+
+ // Remove as many entries from the index to get below |low_watermark_|.
+ std::vector<uint64>::iterator it = entry_hashes->begin();
+ uint64 evicted_so_far_size = 0;
+ while (evicted_so_far_size < cache_size_ - low_watermark_) {
+ DCHECK(it != entry_hashes->end());
+ EntrySet::iterator found_meta = entries_set_.find(*it);
+ DCHECK(found_meta != entries_set_.end());
+ uint64 to_evict_size = found_meta->second.GetEntrySize();
+ evicted_so_far_size += to_evict_size;
+ entries_set_.erase(found_meta);
+ ++it;
+ }
+ cache_size_ -= evicted_so_far_size;
+
+ // Take out the rest of hashes from the eviction list.
+ entry_hashes->erase(it, entry_hashes->end());
+ UMA_HISTOGRAM_COUNTS("SimpleCache.Eviction.EntryCount", entry_hashes->size());
+ UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToSelectEntries",
+ base::TimeTicks::Now() - eviction_start_time_);
+ UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeOfEvicted2",
+ evicted_so_far_size / kBytesInKb);
+
+ index_file_->DoomEntrySet(
+ entry_hashes.Pass(),
+ base::Bind(&SimpleIndex::EvictionDone, AsWeakPtr()));
+}
+
+bool SimpleIndex::UpdateEntrySize(const std::string& key, uint64 entry_size) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ EntrySet::iterator it = entries_set_.find(simple_util::GetEntryHashKey(key));
+ if (it == entries_set_.end())
+ return false;
+
+ UpdateEntryIteratorSize(&it, entry_size);
+ PostponeWritingToDisk();
+ StartEvictionIfNeeded();
+ return true;
+}
+
+void SimpleIndex::EvictionDone(int result) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+
+ // Ignore the result of eviction. We did our best.
+ eviction_in_progress_ = false;
+ UMA_HISTOGRAM_BOOLEAN("SimpleCache.Eviction.Result", result == net::OK);
+ UMA_HISTOGRAM_TIMES("SimpleCache.Eviction.TimeToDone",
+ base::TimeTicks::Now() - eviction_start_time_);
+ UMA_HISTOGRAM_MEMORY_KB("SimpleCache.Eviction.SizeWhenDone2",
+ cache_size_ / kBytesInKb);
+}
+
+// static
+void SimpleIndex::InsertInEntrySet(
+ uint64 hash_key,
+ const disk_cache::EntryMetadata& entry_metadata,
+ EntrySet* entry_set) {
+ DCHECK(entry_set);
+ entry_set->insert(std::make_pair(hash_key, entry_metadata));
+}
+
+void SimpleIndex::PostponeWritingToDisk() {
+ if (!initialized_)
+ return;
+ const int delay = app_on_background_ ? background_flush_delay_
+ : foreground_flush_delay_;
+ // If the timer is already active, Start() will just Reset it, postponing it.
+ write_to_disk_timer_.Start(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(delay), write_to_disk_cb_);
+}
+
+void SimpleIndex::UpdateEntryIteratorSize(EntrySet::iterator* it,
+ uint64 entry_size) {
+ // Update the total cache size with the new entry size.
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK_GE(cache_size_, (*it)->second.GetEntrySize());
+ cache_size_ -= (*it)->second.GetEntrySize();
+ cache_size_ += entry_size;
+ (*it)->second.SetEntrySize(entry_size);
+}
+
+void SimpleIndex::MergeInitializingSet(
+ scoped_ptr<SimpleIndexLoadResult> load_result) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ DCHECK(load_result->did_load);
+
+ SimpleIndex::EntrySet* index_file_entries = &load_result->entries;
+ // First, remove the entries that are in the |removed_entries_| from both
+ // sets.
+ for (base::hash_set<uint64>::const_iterator it =
+ removed_entries_.begin(); it != removed_entries_.end(); ++it) {
+ entries_set_.erase(*it);
+ index_file_entries->erase(*it);
+ }
+
+ for (EntrySet::const_iterator it = entries_set_.begin();
+ it != entries_set_.end(); ++it) {
+ const uint64 entry_hash = it->first;
+ std::pair<EntrySet::iterator, bool> insert_result =
+ index_file_entries->insert(EntrySet::value_type(entry_hash,
+ EntryMetadata()));
+ EntrySet::iterator& possibly_inserted_entry = insert_result.first;
+ possibly_inserted_entry->second = it->second;
+ }
+
+ uint64 merged_cache_size = 0;
+ for (EntrySet::iterator it = index_file_entries->begin();
+ it != index_file_entries->end(); ++it) {
+ merged_cache_size += it->second.GetEntrySize();
+ }
+
+ entries_set_.swap(*index_file_entries);
+ cache_size_ = merged_cache_size;
+ initialized_ = true;
+ removed_entries_.clear();
+
+ // The actual IO is asynchronous, so calling WriteToDisk() shouldn't slow the
+ // merge down much.
+ if (load_result->flush_required)
+ WriteToDisk();
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexInitializationWaiters",
+ to_run_when_initialized_.size(), 0, 100, 20);
+ // Run all callbacks waiting for the index to come up.
+ for (CallbackList::iterator it = to_run_when_initialized_.begin(),
+ end = to_run_when_initialized_.end(); it != end; ++it) {
+ io_thread_->PostTask(FROM_HERE, base::Bind((*it), net::OK));
+ }
+ to_run_when_initialized_.clear();
+}
+
+#if defined(OS_ANDROID)
+void SimpleIndex::OnActivityStateChange(
+ base::android::ActivityState state) {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ // For more info about android activities, see:
+ // developer.android.com/training/basics/activity-lifecycle/pausing.html
+ // These values are defined in the file ActivityStatus.java
+ if (state == base::android::ACTIVITY_STATE_RESUMED) {
+ app_on_background_ = false;
+ } else if (state == base::android::ACTIVITY_STATE_STOPPED) {
+ app_on_background_ = true;
+ WriteToDisk();
+ }
+}
+#endif
+
+void SimpleIndex::WriteToDisk() {
+ DCHECK(io_thread_checker_.CalledOnValidThread());
+ if (!initialized_)
+ return;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.IndexNumEntriesOnWrite",
+ entries_set_.size(), 0, 100000, 50);
+ const base::TimeTicks start = base::TimeTicks::Now();
+ if (!last_write_to_disk_.is_null()) {
+ if (app_on_background_) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Background",
+ start - last_write_to_disk_);
+ } else {
+ UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexWriteInterval.Foreground",
+ start - last_write_to_disk_);
+ }
+ }
+ last_write_to_disk_ = start;
+
+ index_file_->WriteToDisk(entries_set_, cache_size_,
+ start, app_on_background_);
+}
+
+scoped_ptr<SimpleIndex::HashList> SimpleIndex::ExtractEntriesBetween(
+ const base::Time initial_time, const base::Time end_time,
+ bool delete_entries) {
+ DCHECK_EQ(true, initialized_);
+ const base::Time extended_end_time =
+ end_time.is_null() ? base::Time::Max() : end_time;
+ DCHECK(extended_end_time >= initial_time);
+ scoped_ptr<HashList> ret_hashes(new HashList());
+ for (EntrySet::iterator it = entries_set_.begin(), end = entries_set_.end();
+ it != end;) {
+ EntryMetadata& metadata = it->second;
+ base::Time entry_time = metadata.GetLastUsedTime();
+ if (initial_time <= entry_time && entry_time < extended_end_time) {
+ ret_hashes->push_back(it->first);
+ if (delete_entries) {
+ cache_size_ -= metadata.GetEntrySize();
+ entries_set_.erase(it++);
+ continue;
+ }
+ }
+ ++it;
+ }
+ return ret_hashes.Pass();
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_index.h b/chromium/net/disk_cache/simple/simple_index.h
new file mode 100644
index 00000000000..788ffb2cfe8
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index.h
@@ -0,0 +1,203 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/activity_status.h"
+#endif
+
+class Pickle;
+class PickleIterator;
+
+namespace disk_cache {
+
+class SimpleIndexFile;
+struct SimpleIndexLoadResult;
+
+class NET_EXPORT_PRIVATE EntryMetadata {
+ public:
+ EntryMetadata();
+ EntryMetadata(base::Time last_used_time, uint64 entry_size);
+
+ base::Time GetLastUsedTime() const;
+ void SetLastUsedTime(const base::Time& last_used_time);
+
+ uint64 GetEntrySize() const { return entry_size_; }
+ void SetEntrySize(uint64 entry_size) { entry_size_ = entry_size; }
+
+ // Serialize the data into the provided pickle.
+ void Serialize(Pickle* pickle) const;
+ bool Deserialize(PickleIterator* it);
+
+ private:
+ friend class SimpleIndexFileTest;
+
+ // When adding new members here, you should update the Serialize() and
+ // Deserialize() methods.
+
+ // This is the serialized format from Time::ToInternalValue().
+ // If you want to make calculations/comparisons, you should use the
+ // base::Time() class. Use the GetLastUsedTime() method above.
+ // TODO(felipeg): Use Time() here.
+ int64 last_used_time_;
+
+ uint64 entry_size_; // Storage size in bytes.
+};
+
+// This class is not Thread-safe.
+class NET_EXPORT_PRIVATE SimpleIndex
+ : public base::SupportsWeakPtr<SimpleIndex> {
+ public:
+ typedef std::vector<uint64> HashList;
+
+ SimpleIndex(base::SingleThreadTaskRunner* io_thread,
+ const base::FilePath& cache_directory,
+ scoped_ptr<SimpleIndexFile> simple_index_file);
+
+ virtual ~SimpleIndex();
+
+ void Initialize(base::Time cache_mtime);
+
+ bool SetMaxSize(int max_bytes);
+ int max_size() const { return max_size_; }
+
+ void Insert(const std::string& key);
+ void Remove(const std::string& key);
+
+ // Check whether the index has the entry given the hash of its key.
+ bool Has(uint64 hash) const;
+
+ // Update the last used time of the entry with the given key and return true
+ // iff the entry exist in the index.
+ bool UseIfExists(const std::string& key);
+
+ void WriteToDisk();
+
+ // Update the size (in bytes) of an entry, in the metadata stored in the
+ // index. This should be the total disk-file size including all streams of the
+ // entry.
+ bool UpdateEntrySize(const std::string& key, uint64 entry_size);
+
+ typedef base::hash_map<uint64, EntryMetadata> EntrySet;
+
+ static void InsertInEntrySet(uint64 hash_key,
+ const EntryMetadata& entry_metadata,
+ EntrySet* entry_set);
+
+ // Executes the |callback| when the index is ready. Allows multiple callbacks.
+ int ExecuteWhenReady(const net::CompletionCallback& callback);
+
+ // Takes out entries from the index that have last accessed time matching the
+ // range between |initial_time| and |end_time| where open intervals are
+ // possible according to the definition given in |DoomEntriesBetween()| in the
+ // disk cache backend interface. Returns the set of hashes taken out.
+ scoped_ptr<HashList> RemoveEntriesBetween(const base::Time initial_time,
+ const base::Time end_time);
+
+ // Returns the list of all entries key hash.
+ scoped_ptr<HashList> GetAllHashes();
+
+ // Returns number of indexed entries.
+ int32 GetEntryCount() const;
+
+ // Returns whether the index has been initialized yet.
+ bool initialized() const { return initialized_; }
+
+ private:
+ friend class SimpleIndexTest;
+ FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, IndexSizeCorrectOnMerge);
+ FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteQueued);
+ FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteExecuted);
+ FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWritePostponed);
+
+ void StartEvictionIfNeeded();
+ void EvictionDone(int result);
+
+ void PostponeWritingToDisk();
+
+ void UpdateEntryIteratorSize(EntrySet::iterator* it, uint64 entry_size);
+
+ // Must run on IO Thread.
+ void MergeInitializingSet(scoped_ptr<SimpleIndexLoadResult> load_result);
+
+#if defined(OS_ANDROID)
+ void OnActivityStateChange(base::android::ActivityState state);
+
+ scoped_ptr<base::android::ActivityStatus::Listener> activity_status_listener_;
+#endif
+
+ scoped_ptr<HashList> ExtractEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ bool delete_entries);
+
+ EntrySet entries_set_;
+
+ uint64 cache_size_; // Total cache storage size in bytes.
+ uint64 max_size_;
+ uint64 high_watermark_;
+ uint64 low_watermark_;
+ bool eviction_in_progress_;
+ base::TimeTicks eviction_start_time_;
+
+ // This stores all the hash_key of entries that are removed during
+ // initialization.
+ base::hash_set<uint64> removed_entries_;
+ bool initialized_;
+
+ const base::FilePath& cache_directory_;
+ scoped_ptr<SimpleIndexFile> index_file_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
+
+ // All nonstatic SimpleEntryImpl methods should always be called on the IO
+ // thread, in all cases. |io_thread_checker_| documents and enforces this.
+ base::ThreadChecker io_thread_checker_;
+
+ // Timestamp of the last time we wrote the index to disk.
+ // PostponeWritingToDisk() may give up postponing and allow the write if it
+ // has been a while since last time we wrote.
+ base::TimeTicks last_write_to_disk_;
+
+ base::OneShotTimer<SimpleIndex> write_to_disk_timer_;
+ base::Closure write_to_disk_cb_;
+
+ typedef std::list<net::CompletionCallback> CallbackList;
+ CallbackList to_run_when_initialized_;
+
+ // Set to true when the app is on the background. When the app is in the
+ // background we can write the index much more frequently, to insure fresh
+ // index on next startup.
+ bool app_on_background_;
+
+ // The time in milliseconds for the index to be idle before it gets flushed to
+ // the disk. When the app is on foreground the delay is different from the
+ // background state.
+ int foreground_flush_delay_;
+ int background_flush_delay_;
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
diff --git a/chromium/net/disk_cache/simple/simple_index_file.cc b/chromium/net/disk_cache/simple/simple_index_file.cc
new file mode 100644
index 00000000000..7bcea7cdfa8
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index_file.cc
@@ -0,0 +1,423 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_index_file.h"
+
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/hash.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_index.h"
+#include "net/disk_cache/simple/simple_synchronous_entry.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "third_party/zlib/zlib.h"
+
+
+namespace {
+
+const uint64 kMaxEntiresInIndex = 100000000;
+
+uint32 CalculatePickleCRC(const Pickle& pickle) {
+ return crc32(crc32(0, Z_NULL, 0),
+ reinterpret_cast<const Bytef*>(pickle.payload()),
+ pickle.payload_size());
+}
+
+void DoomEntrySetReply(const net::CompletionCallback& reply_callback,
+ int result) {
+ reply_callback.Run(result);
+}
+
+void WriteToDiskInternal(const base::FilePath& index_filename,
+ const base::FilePath& temp_index_filename,
+ scoped_ptr<Pickle> pickle,
+ const base::TimeTicks& start_time,
+ bool app_on_background) {
+ int bytes_written = file_util::WriteFile(
+ temp_index_filename,
+ reinterpret_cast<const char*>(pickle->data()),
+ pickle->size());
+ DCHECK_EQ(bytes_written, implicit_cast<int>(pickle->size()));
+ if (bytes_written != static_cast<int>(pickle->size())) {
+ // TODO(felipeg): Add better error handling.
+ LOG(ERROR) << "Could not write Simple Cache index to temporary file: "
+ << temp_index_filename.value();
+ base::DeleteFile(temp_index_filename, /* recursive = */ false);
+ } else {
+ // Swap temp and index_file.
+ bool result = base::ReplaceFile(temp_index_filename, index_filename, NULL);
+ DCHECK(result);
+ }
+ if (app_on_background) {
+ UMA_HISTOGRAM_TIMES("SimpleCache.IndexWriteToDiskTime.Background",
+ (base::TimeTicks::Now() - start_time));
+ } else {
+ UMA_HISTOGRAM_TIMES("SimpleCache.IndexWriteToDiskTime.Foreground",
+ (base::TimeTicks::Now() - start_time));
+ }
+}
+
+} // namespace
+
+namespace disk_cache {
+
+SimpleIndexLoadResult::SimpleIndexLoadResult() : did_load(false),
+ flush_required(false) {
+}
+
+SimpleIndexLoadResult::~SimpleIndexLoadResult() {
+}
+
+void SimpleIndexLoadResult::Reset() {
+ did_load = false;
+ flush_required = false;
+ entries.clear();
+}
+
+// static
+const char SimpleIndexFile::kIndexFileName[] = "the-real-index";
+// static
+const char SimpleIndexFile::kTempIndexFileName[] = "temp-index";
+
+SimpleIndexFile::IndexMetadata::IndexMetadata() :
+ magic_number_(kSimpleIndexMagicNumber),
+ version_(kSimpleVersion),
+ number_of_entries_(0),
+ cache_size_(0) {}
+
+SimpleIndexFile::IndexMetadata::IndexMetadata(
+ uint64 number_of_entries, uint64 cache_size) :
+ magic_number_(kSimpleIndexMagicNumber),
+ version_(kSimpleVersion),
+ number_of_entries_(number_of_entries),
+ cache_size_(cache_size) {}
+
+void SimpleIndexFile::IndexMetadata::Serialize(Pickle* pickle) const {
+ DCHECK(pickle);
+ pickle->WriteUInt64(magic_number_);
+ pickle->WriteUInt32(version_);
+ pickle->WriteUInt64(number_of_entries_);
+ pickle->WriteUInt64(cache_size_);
+}
+
+bool SimpleIndexFile::IndexMetadata::Deserialize(PickleIterator* it) {
+ DCHECK(it);
+ return it->ReadUInt64(&magic_number_) &&
+ it->ReadUInt32(&version_) &&
+ it->ReadUInt64(&number_of_entries_)&&
+ it->ReadUInt64(&cache_size_);
+}
+
+bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() {
+ return number_of_entries_ <= kMaxEntiresInIndex &&
+ magic_number_ == disk_cache::kSimpleIndexMagicNumber &&
+ version_ == disk_cache::kSimpleVersion;
+}
+
+SimpleIndexFile::SimpleIndexFile(
+ base::SingleThreadTaskRunner* cache_thread,
+ base::TaskRunner* worker_pool,
+ const base::FilePath& cache_directory)
+ : cache_thread_(cache_thread),
+ worker_pool_(worker_pool),
+ cache_directory_(cache_directory),
+ index_file_(cache_directory_.AppendASCII(kIndexFileName)),
+ temp_index_file_(cache_directory_.AppendASCII(kTempIndexFileName)) {
+}
+
+SimpleIndexFile::~SimpleIndexFile() {}
+
+void SimpleIndexFile::LoadIndexEntries(base::Time cache_last_modified,
+ const base::Closure& callback,
+ SimpleIndexLoadResult* out_result) {
+ base::Closure task = base::Bind(&SimpleIndexFile::SyncLoadIndexEntries,
+ cache_last_modified, cache_directory_,
+ index_file_, out_result);
+ worker_pool_->PostTaskAndReply(FROM_HERE, task, callback);
+}
+
+void SimpleIndexFile::WriteToDisk(const SimpleIndex::EntrySet& entry_set,
+ uint64 cache_size,
+ const base::TimeTicks& start,
+ bool app_on_background) {
+ IndexMetadata index_metadata(entry_set.size(), cache_size);
+ scoped_ptr<Pickle> pickle = Serialize(index_metadata, entry_set);
+ cache_thread_->PostTask(FROM_HERE, base::Bind(
+ &WriteToDiskInternal,
+ index_file_,
+ temp_index_file_,
+ base::Passed(&pickle),
+ base::TimeTicks::Now(),
+ app_on_background));
+}
+
+void SimpleIndexFile::DoomEntrySet(
+ scoped_ptr<std::vector<uint64> > entry_hashes,
+ const net::CompletionCallback& reply_callback) {
+ PostTaskAndReplyWithResult(
+ worker_pool_,
+ FROM_HERE,
+ base::Bind(&SimpleSynchronousEntry::DoomEntrySet,
+ base::Passed(entry_hashes.Pass()), cache_directory_),
+ base::Bind(&DoomEntrySetReply, reply_callback));
+}
+
+// static
+void SimpleIndexFile::SyncLoadIndexEntries(
+ base::Time cache_last_modified,
+ const base::FilePath& cache_directory,
+ const base::FilePath& index_file_path,
+ SimpleIndexLoadResult* out_result) {
+ // TODO(felipeg): probably could load a stale index and use it for something.
+ const SimpleIndex::EntrySet& entries = out_result->entries;
+
+ const bool index_file_exists = base::PathExists(index_file_path);
+
+ // Used in histograms. Please only add new values at the end.
+ enum {
+ INDEX_STATE_CORRUPT = 0,
+ INDEX_STATE_STALE = 1,
+ INDEX_STATE_FRESH = 2,
+ INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3,
+ INDEX_STATE_MAX = 4,
+ } index_file_state;
+
+ // Only load if the index is not stale.
+ if (IsIndexFileStale(cache_last_modified, index_file_path)) {
+ index_file_state = INDEX_STATE_STALE;
+ } else {
+ index_file_state = INDEX_STATE_FRESH;
+ base::Time latest_dir_mtime;
+ if (simple_util::GetMTime(cache_directory, &latest_dir_mtime) &&
+ IsIndexFileStale(latest_dir_mtime, index_file_path)) {
+ // A file operation has updated the directory since we last looked at it
+ // during backend initialization.
+ index_file_state = INDEX_STATE_FRESH_CONCURRENT_UPDATES;
+ }
+
+ const base::TimeTicks start = base::TimeTicks::Now();
+ SyncLoadFromDisk(index_file_path, out_result);
+ UMA_HISTOGRAM_TIMES("SimpleCache.IndexLoadTime",
+ base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_COUNTS("SimpleCache.IndexEntriesLoaded",
+ out_result->did_load ? entries.size() : 0);
+ if (!out_result->did_load)
+ index_file_state = INDEX_STATE_CORRUPT;
+ }
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.IndexFileStateOnLoad",
+ index_file_state,
+ INDEX_STATE_MAX);
+
+ if (!out_result->did_load) {
+ const base::TimeTicks start = base::TimeTicks::Now();
+ SyncRestoreFromDisk(cache_directory, index_file_path, out_result);
+ UMA_HISTOGRAM_MEDIUM_TIMES("SimpleCache.IndexRestoreTime",
+ base::TimeTicks::Now() - start);
+ UMA_HISTOGRAM_COUNTS("SimpleCache.IndexEntriesRestored",
+ entries.size());
+ }
+
+ // Used in histograms. Please only add new values at the end.
+ enum {
+ INITIALIZE_METHOD_RECOVERED = 0,
+ INITIALIZE_METHOD_LOADED = 1,
+ INITIALIZE_METHOD_NEWCACHE = 2,
+ INITIALIZE_METHOD_MAX = 3,
+ };
+ int initialize_method;
+ if (index_file_exists) {
+ if (out_result->flush_required)
+ initialize_method = INITIALIZE_METHOD_RECOVERED;
+ else
+ initialize_method = INITIALIZE_METHOD_LOADED;
+ } else {
+ UMA_HISTOGRAM_COUNTS("SimpleCache.IndexCreatedEntryCount",
+ entries.size());
+ initialize_method = INITIALIZE_METHOD_NEWCACHE;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.IndexInitializeMethod",
+ initialize_method, INITIALIZE_METHOD_MAX);
+}
+
+// static
+void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename,
+ SimpleIndexLoadResult* out_result) {
+ out_result->Reset();
+
+ base::MemoryMappedFile index_file_map;
+ if (!index_file_map.Initialize(index_filename)) {
+ LOG(WARNING) << "Could not map Simple Index file.";
+ base::DeleteFile(index_filename, false);
+ return;
+ }
+
+ SimpleIndexFile::Deserialize(
+ reinterpret_cast<const char*>(index_file_map.data()),
+ index_file_map.length(), out_result);
+
+ if (!out_result->did_load)
+ base::DeleteFile(index_filename, false);
+}
+
+// static
+scoped_ptr<Pickle> SimpleIndexFile::Serialize(
+ const SimpleIndexFile::IndexMetadata& index_metadata,
+ const SimpleIndex::EntrySet& entries) {
+ scoped_ptr<Pickle> pickle(new Pickle(sizeof(SimpleIndexFile::PickleHeader)));
+
+ index_metadata.Serialize(pickle.get());
+ for (SimpleIndex::EntrySet::const_iterator it = entries.begin();
+ it != entries.end(); ++it) {
+ pickle->WriteUInt64(it->first);
+ it->second.Serialize(pickle.get());
+ }
+ SimpleIndexFile::PickleHeader* header_p =
+ pickle->headerT<SimpleIndexFile::PickleHeader>();
+ header_p->crc = CalculatePickleCRC(*pickle);
+ return pickle.Pass();
+}
+
+// static
+void SimpleIndexFile::Deserialize(const char* data, int data_len,
+ SimpleIndexLoadResult* out_result) {
+ DCHECK(data);
+
+ out_result->Reset();
+ SimpleIndex::EntrySet* entries = &out_result->entries;
+
+ Pickle pickle(data, data_len);
+ if (!pickle.data()) {
+ LOG(WARNING) << "Corrupt Simple Index File.";
+ return;
+ }
+
+ PickleIterator pickle_it(pickle);
+
+ SimpleIndexFile::PickleHeader* header_p =
+ pickle.headerT<SimpleIndexFile::PickleHeader>();
+ const uint32 crc_read = header_p->crc;
+ const uint32 crc_calculated = CalculatePickleCRC(pickle);
+
+ if (crc_read != crc_calculated) {
+ LOG(WARNING) << "Invalid CRC in Simple Index file.";
+ return;
+ }
+
+ SimpleIndexFile::IndexMetadata index_metadata;
+ if (!index_metadata.Deserialize(&pickle_it)) {
+ LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
+ return;
+ }
+
+ if (!index_metadata.CheckIndexMetadata()) {
+ LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
+ return;
+ }
+
+#if !defined(OS_WIN)
+ // TODO(gavinp): Consider using std::unordered_map.
+ entries->resize(index_metadata.GetNumberOfEntries() + kExtraSizeForMerge);
+#endif
+ while (entries->size() < index_metadata.GetNumberOfEntries()) {
+ uint64 hash_key;
+ EntryMetadata entry_metadata;
+ if (!pickle_it.ReadUInt64(&hash_key) ||
+ !entry_metadata.Deserialize(&pickle_it)) {
+ LOG(WARNING) << "Invalid EntryMetadata in Simple Index file.";
+ entries->clear();
+ return;
+ }
+ SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries);
+ }
+
+ out_result->did_load = true;
+}
+
+// static
+void SimpleIndexFile::SyncRestoreFromDisk(
+ const base::FilePath& cache_directory,
+ const base::FilePath& index_file_path,
+ SimpleIndexLoadResult* out_result) {
+ LOG(INFO) << "Simple Cache Index is being restored from disk.";
+
+ base::DeleteFile(index_file_path, /* recursive = */ false);
+ out_result->Reset();
+ SimpleIndex::EntrySet* entries = &out_result->entries;
+
+ // TODO(felipeg,gavinp): Fix this once we have a one-file per entry format.
+ COMPILE_ASSERT(kSimpleEntryFileCount == 3,
+ file_pattern_must_match_file_count);
+
+ const int kFileSuffixLength = sizeof("_0") - 1;
+ const base::FilePath::StringType file_pattern = FILE_PATH_LITERAL("*_[0-2]");
+ base::FileEnumerator enumerator(cache_directory,
+ false /* recursive */,
+ base::FileEnumerator::FILES,
+ file_pattern);
+ for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
+ file_path = enumerator.Next()) {
+ const base::FilePath::StringType base_name = file_path.BaseName().value();
+ // Converting to std::string is OK since we never use UTF8 wide chars in our
+ // file names.
+ const std::string hash_key_string(base_name.begin(),
+ base_name.end() - kFileSuffixLength);
+ uint64 hash_key = 0;
+ if (!simple_util::GetEntryHashKeyFromHexString(
+ hash_key_string, &hash_key)) {
+ LOG(WARNING) << "Invalid Entry Hash Key filename while restoring "
+ << "Simple Index from disk: " << base_name;
+ // TODO(felipeg): Should we delete the invalid file here ?
+ continue;
+ }
+
+ base::FileEnumerator::FileInfo info = enumerator.GetInfo();
+ base::Time last_used_time;
+#if defined(OS_POSIX)
+ // For POSIX systems, a last access time is available. However, it's not
+ // guaranteed to be more accurate than mtime. It is no worse though.
+ last_used_time = base::Time::FromTimeT(info.stat().st_atime);
+#endif
+ if (last_used_time.is_null())
+ last_used_time = info.GetLastModifiedTime();
+
+ int64 file_size = info.GetSize();
+ SimpleIndex::EntrySet::iterator it = entries->find(hash_key);
+ if (it == entries->end()) {
+ SimpleIndex::InsertInEntrySet(
+ hash_key,
+ EntryMetadata(last_used_time, file_size),
+ entries);
+ } else {
+ // Summing up the total size of the entry through all the *_[0-2] files
+ it->second.SetEntrySize(it->second.GetEntrySize() + file_size);
+ }
+ }
+
+ out_result->did_load = true;
+
+ // When we restore from disk we write the merged index file to disk right
+ // away, this might save us from having to restore again next time.
+ out_result->flush_required = true;
+}
+
+// static
+bool SimpleIndexFile::IsIndexFileStale(base::Time cache_last_modified,
+ const base::FilePath& index_file_path) {
+ base::Time index_mtime;
+ if (!simple_util::GetMTime(index_file_path, &index_mtime))
+ return true;
+ return index_mtime < cache_last_modified;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_index_file.h b/chromium/net/disk_cache/simple/simple_index_file.h
new file mode 100644
index 00000000000..b536df9a1e7
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index_file.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/port.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/simple/simple_index.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class TaskRunner;
+}
+
+namespace disk_cache {
+
+const uint64 kSimpleIndexMagicNumber = GG_UINT64_C(0x656e74657220796f);
+
+struct NET_EXPORT_PRIVATE SimpleIndexLoadResult {
+ SimpleIndexLoadResult();
+ ~SimpleIndexLoadResult();
+ void Reset();
+
+ bool did_load;
+ SimpleIndex::EntrySet entries;
+ bool flush_required;
+};
+
+// Simple Index File format is a pickle serialized data of IndexMetadata and
+// EntryMetadata objects. The file format is as follows: one instance of
+// serialized |IndexMetadata| followed serialized |EntryMetadata| entries
+// repeated |number_of_entries| amount of times. To know more about the format,
+// see SimpleIndexFile::Serialize() and SeeSimpleIndexFile::LoadFromDisk()
+// methods.
+//
+// The non-static methods must run on the IO thread. All the real
+// work is done in the static methods, which are run on the cache thread
+// or in worker threads. Synchronization between methods is the
+// responsibility of the caller.
+class NET_EXPORT_PRIVATE SimpleIndexFile {
+ public:
+ class NET_EXPORT_PRIVATE IndexMetadata {
+ public:
+ IndexMetadata();
+ IndexMetadata(uint64 number_of_entries, uint64 cache_size);
+
+ void Serialize(Pickle* pickle) const;
+ bool Deserialize(PickleIterator* it);
+
+ bool CheckIndexMetadata();
+
+ uint64 GetNumberOfEntries() { return number_of_entries_; }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(IndexMetadataTest, Basics);
+ FRIEND_TEST_ALL_PREFIXES(IndexMetadataTest, Serialize);
+
+ uint64 magic_number_;
+ uint32 version_;
+ uint64 number_of_entries_;
+ uint64 cache_size_; // Total cache storage size in bytes.
+ };
+
+ SimpleIndexFile(base::SingleThreadTaskRunner* cache_thread,
+ base::TaskRunner* worker_pool,
+ const base::FilePath& cache_directory);
+ virtual ~SimpleIndexFile();
+
+ // Get index entries based on current disk context.
+ virtual void LoadIndexEntries(base::Time cache_last_modified,
+ const base::Closure& callback,
+ SimpleIndexLoadResult* out_result);
+
+ // Write the specified set of entries to disk.
+ virtual void WriteToDisk(const SimpleIndex::EntrySet& entry_set,
+ uint64 cache_size,
+ const base::TimeTicks& start,
+ bool app_on_background);
+
+ // Doom the entries specified in |entry_hashes|, calling |reply_callback|
+ // with the result on the current thread when done.
+ virtual void DoomEntrySet(scoped_ptr<std::vector<uint64> > entry_hashes,
+ const base::Callback<void(int)>& reply_callback);
+
+ private:
+ friend class WrappedSimpleIndexFile;
+
+ // When loading the entries from disk, add this many extra hash buckets to
+ // prevent reallocation on the IO thread when merging in new live entries.
+ static const int kExtraSizeForMerge = 512;
+
+ // Synchronous (IO performing) implementation of LoadIndexEntries.
+ static void SyncLoadIndexEntries(base::Time cache_last_modified,
+ const base::FilePath& cache_directory,
+ const base::FilePath& index_file_path,
+ SimpleIndexLoadResult* out_result);
+
+ // Load the index file from disk returning an EntrySet. Upon failure, returns
+ // NULL.
+ static void SyncLoadFromDisk(const base::FilePath& index_filename,
+ SimpleIndexLoadResult* out_result);
+
+ // Returns a scoped_ptr for a newly allocated Pickle containing the serialized
+ // data to be written to a file.
+ static scoped_ptr<Pickle> Serialize(
+ const SimpleIndexFile::IndexMetadata& index_metadata,
+ const SimpleIndex::EntrySet& entries);
+
+ // Given the contents of an index file |data| of length |data_len|, returns
+ // the corresponding EntrySet. Returns NULL on error.
+ static void Deserialize(const char* data, int data_len,
+ SimpleIndexLoadResult* out_result);
+
+ // Scan the index directory for entries, returning an EntrySet of all entries
+ // found.
+ static void SyncRestoreFromDisk(const base::FilePath& cache_directory,
+ const base::FilePath& index_file_path,
+ SimpleIndexLoadResult* out_result);
+
+ // Determines if an index file is stale relative to the time of last
+ // modification of the cache directory.
+ static bool IsIndexFileStale(base::Time cache_last_modified,
+ const base::FilePath& index_file_path);
+
+ struct PickleHeader : public Pickle::Header {
+ uint32 crc;
+ };
+
+ const scoped_refptr<base::SingleThreadTaskRunner> cache_thread_;
+ const scoped_refptr<base::TaskRunner> worker_pool_;
+ const base::FilePath cache_directory_;
+ const base::FilePath index_file_;
+ const base::FilePath temp_index_file_;
+
+ static const char kIndexFileName[];
+ static const char kTempIndexFileName[];
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleIndexFile);
+};
+
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
diff --git a/chromium/net/disk_cache/simple/simple_index_file_unittest.cc b/chromium/net/disk_cache/simple/simple_index_file_unittest.cc
new file mode 100644
index 00000000000..bf7ee83c30e
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index_file_unittest.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2011 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/hash.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/pickle.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+#include "net/disk_cache/simple/simple_index.h"
+#include "net/disk_cache/simple/simple_index_file.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+using disk_cache::SimpleIndexFile;
+using disk_cache::SimpleIndex;
+
+namespace disk_cache {
+
+TEST(IndexMetadataTest, Basics) {
+ SimpleIndexFile::IndexMetadata index_metadata;
+
+ EXPECT_EQ(disk_cache::kSimpleIndexMagicNumber, index_metadata.magic_number_);
+ EXPECT_EQ(disk_cache::kSimpleVersion, index_metadata.version_);
+ EXPECT_EQ(0U, index_metadata.GetNumberOfEntries());
+ EXPECT_EQ(0U, index_metadata.cache_size_);
+
+ EXPECT_TRUE(index_metadata.CheckIndexMetadata());
+}
+
+TEST(IndexMetadataTest, Serialize) {
+ SimpleIndexFile::IndexMetadata index_metadata(123, 456);
+ Pickle pickle;
+ index_metadata.Serialize(&pickle);
+ PickleIterator it(pickle);
+ SimpleIndexFile::IndexMetadata new_index_metadata;
+ new_index_metadata.Deserialize(&it);
+
+ EXPECT_EQ(new_index_metadata.magic_number_, index_metadata.magic_number_);
+ EXPECT_EQ(new_index_metadata.version_, index_metadata.version_);
+ EXPECT_EQ(new_index_metadata.GetNumberOfEntries(),
+ index_metadata.GetNumberOfEntries());
+ EXPECT_EQ(new_index_metadata.cache_size_, index_metadata.cache_size_);
+
+ EXPECT_TRUE(new_index_metadata.CheckIndexMetadata());
+}
+
+// This friend derived class is able to reexport its ancestors private methods
+// as public, for use in tests.
+class WrappedSimpleIndexFile : public SimpleIndexFile {
+ public:
+ using SimpleIndexFile::Deserialize;
+ using SimpleIndexFile::IsIndexFileStale;
+ using SimpleIndexFile::Serialize;
+
+ explicit WrappedSimpleIndexFile(const base::FilePath& index_file_directory)
+ : SimpleIndexFile(base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ index_file_directory) {}
+ virtual ~WrappedSimpleIndexFile() {
+ }
+
+ const base::FilePath& GetIndexFilePath() const {
+ return index_file_;
+ }
+};
+
+class SimpleIndexFileTest : public testing::Test {
+ public:
+ bool CompareTwoEntryMetadata(const EntryMetadata& a, const EntryMetadata& b) {
+ return a.last_used_time_ == b.last_used_time_ &&
+ a.entry_size_ == b.entry_size_;
+ }
+
+ protected:
+ SimpleIndexFileTest() : callback_called_(false) {}
+
+ base::Closure GetCallback() {
+ return base::Bind(&SimpleIndexFileTest::LoadIndexEntriesCallback,
+ base::Unretained(this));
+ }
+
+ bool callback_called() { return callback_called_; }
+
+ private:
+ void LoadIndexEntriesCallback() {
+ EXPECT_FALSE(callback_called_);
+ callback_called_ = true;
+ }
+
+ bool callback_called_;
+};
+
+TEST_F(SimpleIndexFileTest, Serialize) {
+ SimpleIndex::EntrySet entries;
+ static const uint64 kHashes[] = { 11, 22, 33 };
+ static const size_t kNumHashes = arraysize(kHashes);
+ EntryMetadata metadata_entries[kNumHashes];
+
+ SimpleIndexFile::IndexMetadata index_metadata(static_cast<uint64>(kNumHashes),
+ 456);
+ for (size_t i = 0; i < kNumHashes; ++i) {
+ uint64 hash = kHashes[i];
+ metadata_entries[i] =
+ EntryMetadata(Time::FromInternalValue(hash), hash);
+ SimpleIndex::InsertInEntrySet(hash, metadata_entries[i], &entries);
+ }
+
+ scoped_ptr<Pickle> pickle = WrappedSimpleIndexFile::Serialize(
+ index_metadata, entries);
+ EXPECT_TRUE(pickle.get() != NULL);
+
+ SimpleIndexLoadResult deserialize_result;
+ WrappedSimpleIndexFile::Deserialize(static_cast<const char*>(pickle->data()),
+ pickle->size(),
+ &deserialize_result);
+ EXPECT_TRUE(deserialize_result.did_load);
+ const SimpleIndex::EntrySet& new_entries = deserialize_result.entries;
+ EXPECT_EQ(entries.size(), new_entries.size());
+
+ for (size_t i = 0; i < kNumHashes; ++i) {
+ SimpleIndex::EntrySet::const_iterator it = new_entries.find(kHashes[i]);
+ EXPECT_TRUE(new_entries.end() != it);
+ EXPECT_TRUE(CompareTwoEntryMetadata(it->second, metadata_entries[i]));
+ }
+}
+
+TEST_F(SimpleIndexFileTest, IsIndexFileStale) {
+ base::ScopedTempDir cache_dir;
+ ASSERT_TRUE(cache_dir.CreateUniqueTempDir());
+ base::Time cache_mtime;
+ const base::FilePath cache_path = cache_dir.path();
+
+ ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime));
+ WrappedSimpleIndexFile simple_index_file(cache_path);
+ const base::FilePath& index_path = simple_index_file.GetIndexFilePath();
+ EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime,
+ index_path));
+ const std::string kDummyData = "nothing to be seen here";
+ EXPECT_EQ(static_cast<int>(kDummyData.size()),
+ file_util::WriteFile(index_path,
+ kDummyData.data(),
+ kDummyData.size()));
+ ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime));
+ EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime,
+ index_path));
+
+ const base::Time past_time = base::Time::Now() -
+ base::TimeDelta::FromSeconds(10);
+ EXPECT_TRUE(file_util::TouchFile(index_path, past_time, past_time));
+ EXPECT_TRUE(file_util::TouchFile(cache_path, past_time, past_time));
+ ASSERT_TRUE(simple_util::GetMTime(cache_path, &cache_mtime));
+ EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime,
+ index_path));
+ const base::Time even_older =
+ past_time - base::TimeDelta::FromSeconds(10);
+ EXPECT_TRUE(file_util::TouchFile(index_path, even_older, even_older));
+ EXPECT_TRUE(WrappedSimpleIndexFile::IsIndexFileStale(cache_mtime,
+ index_path));
+
+}
+
+TEST_F(SimpleIndexFileTest, WriteThenLoadIndex) {
+ base::ScopedTempDir cache_dir;
+ ASSERT_TRUE(cache_dir.CreateUniqueTempDir());
+
+ SimpleIndex::EntrySet entries;
+ static const uint64 kHashes[] = { 11, 22, 33 };
+ static const size_t kNumHashes = arraysize(kHashes);
+ EntryMetadata metadata_entries[kNumHashes];
+ for (size_t i = 0; i < kNumHashes; ++i) {
+ uint64 hash = kHashes[i];
+ metadata_entries[i] =
+ EntryMetadata(Time::FromInternalValue(hash), hash);
+ SimpleIndex::InsertInEntrySet(hash, metadata_entries[i], &entries);
+ }
+
+ const uint64 kCacheSize = 456U;
+ {
+ WrappedSimpleIndexFile simple_index_file(cache_dir.path());
+ simple_index_file.WriteToDisk(entries, kCacheSize,
+ base::TimeTicks(), false);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(simple_index_file.GetIndexFilePath()));
+ }
+
+ WrappedSimpleIndexFile simple_index_file(cache_dir.path());
+ base::Time fake_cache_mtime;
+ ASSERT_TRUE(simple_util::GetMTime(simple_index_file.GetIndexFilePath(),
+ &fake_cache_mtime));
+ SimpleIndexLoadResult load_index_result;
+ simple_index_file.LoadIndexEntries(fake_cache_mtime,
+ GetCallback(),
+ &load_index_result);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(base::PathExists(simple_index_file.GetIndexFilePath()));
+ ASSERT_TRUE(callback_called());
+ EXPECT_TRUE(load_index_result.did_load);
+ EXPECT_FALSE(load_index_result.flush_required);
+
+ EXPECT_EQ(kNumHashes, load_index_result.entries.size());
+ for (size_t i = 0; i < kNumHashes; ++i)
+ EXPECT_EQ(1U, load_index_result.entries.count(kHashes[i]));
+}
+
+TEST_F(SimpleIndexFileTest, LoadCorruptIndex) {
+ base::ScopedTempDir cache_dir;
+ ASSERT_TRUE(cache_dir.CreateUniqueTempDir());
+
+ WrappedSimpleIndexFile simple_index_file(cache_dir.path());
+ const base::FilePath& index_path = simple_index_file.GetIndexFilePath();
+ const std::string kDummyData = "nothing to be seen here";
+ EXPECT_EQ(static_cast<int>(kDummyData.size()),
+ file_util::WriteFile(index_path,
+ kDummyData.data(),
+ kDummyData.size()));
+ base::Time fake_cache_mtime;
+ ASSERT_TRUE(simple_util::GetMTime(simple_index_file.GetIndexFilePath(),
+ &fake_cache_mtime));
+ EXPECT_FALSE(WrappedSimpleIndexFile::IsIndexFileStale(fake_cache_mtime,
+ index_path));
+
+ SimpleIndexLoadResult load_index_result;
+ simple_index_file.LoadIndexEntries(fake_cache_mtime,
+ GetCallback(),
+ &load_index_result);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(base::PathExists(index_path));
+ ASSERT_TRUE(callback_called());
+ EXPECT_TRUE(load_index_result.did_load);
+ EXPECT_TRUE(load_index_result.flush_required);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_index_unittest.cc b/chromium/net/disk_cache/simple/simple_index_unittest.cc
new file mode 100644
index 00000000000..0c845b21318
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_index_unittest.cc
@@ -0,0 +1,581 @@
+// Copyright (c) 2013 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 "base/files/scoped_temp_dir.h"
+#include "base/hash.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "net/disk_cache/simple/simple_index.h"
+#include "net/disk_cache/simple/simple_index_file.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const int64 kTestLastUsedTimeInternal = 12345;
+const base::Time kTestLastUsedTime =
+ base::Time::FromInternalValue(kTestLastUsedTimeInternal);
+const uint64 kTestEntrySize = 789;
+const uint64 kKey1Hash = disk_cache::simple_util::GetEntryHashKey("key1");
+const uint64 kKey2Hash = disk_cache::simple_util::GetEntryHashKey("key2");
+const uint64 kKey3Hash = disk_cache::simple_util::GetEntryHashKey("key3");
+
+} // namespace
+
+namespace disk_cache {
+
+class EntryMetadataTest : public testing::Test {
+ public:
+ EntryMetadata NewEntryMetadataWithValues() {
+ return EntryMetadata(kTestLastUsedTime, kTestEntrySize);
+ }
+
+ void CheckEntryMetadataValues(const EntryMetadata& entry_metadata) {
+ EXPECT_EQ(kTestLastUsedTime, entry_metadata.GetLastUsedTime());
+ EXPECT_EQ(kTestEntrySize, entry_metadata.GetEntrySize());
+ }
+};
+
+class MockSimpleIndexFile : public SimpleIndexFile,
+ public base::SupportsWeakPtr<MockSimpleIndexFile> {
+ public:
+ MockSimpleIndexFile()
+ : SimpleIndexFile(NULL, NULL, base::FilePath()),
+ load_result_(NULL),
+ load_index_entries_calls_(0),
+ doom_entry_set_calls_(0),
+ disk_writes_(0) {}
+
+ virtual void LoadIndexEntries(
+ base::Time cache_last_modified,
+ const base::Closure& callback,
+ SimpleIndexLoadResult* out_load_result) OVERRIDE {
+ load_callback_ = callback;
+ load_result_ = out_load_result;
+ ++load_index_entries_calls_;
+ }
+
+ virtual void WriteToDisk(const SimpleIndex::EntrySet& entry_set,
+ uint64 cache_size,
+ const base::TimeTicks& start,
+ bool app_on_background) OVERRIDE {
+ disk_writes_++;
+ disk_write_entry_set_ = entry_set;
+ }
+
+ virtual void DoomEntrySet(
+ scoped_ptr<std::vector<uint64> > entry_hashes,
+ const base::Callback<void(int)>& reply_callback) OVERRIDE {
+ last_doom_entry_hashes_ = *entry_hashes.get();
+ last_doom_reply_callback_ = reply_callback;
+ ++doom_entry_set_calls_;
+ }
+
+ void GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet* entry_set) {
+ entry_set->swap(disk_write_entry_set_);
+ }
+
+ const base::Closure& load_callback() const { return load_callback_; }
+ SimpleIndexLoadResult* load_result() const { return load_result_; }
+ int load_index_entries_calls() const { return load_index_entries_calls_; }
+ int disk_writes() const { return disk_writes_; }
+ const std::vector<uint64>& last_doom_entry_hashes() const {
+ return last_doom_entry_hashes_;
+ }
+ int doom_entry_set_calls() const { return doom_entry_set_calls_; }
+
+ private:
+ base::Closure load_callback_;
+ SimpleIndexLoadResult* load_result_;
+ int load_index_entries_calls_;
+ std::vector<uint64> last_doom_entry_hashes_;
+ int doom_entry_set_calls_;
+ base::Callback<void(int)> last_doom_reply_callback_;
+ int disk_writes_;
+ SimpleIndex::EntrySet disk_write_entry_set_;
+};
+
+class SimpleIndexTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ scoped_ptr<MockSimpleIndexFile> index_file(new MockSimpleIndexFile());
+ index_file_ = index_file->AsWeakPtr();
+ index_.reset(new SimpleIndex(NULL, base::FilePath(),
+ index_file.PassAs<SimpleIndexFile>()));
+
+ index_->Initialize(base::Time());
+ }
+
+ void WaitForTimeChange() {
+ base::Time now(base::Time::Now());
+
+ do {
+ base::PlatformThread::YieldCurrentThread();
+ } while (now == base::Time::Now());
+ }
+
+ // Redirect to allow single "friend" declaration in base class.
+ bool GetEntryForTesting(const std::string& key, EntryMetadata* metadata) {
+ const uint64 hash_key = simple_util::GetEntryHashKey(key);
+ SimpleIndex::EntrySet::iterator it = index_->entries_set_.find(hash_key);
+ if (index_->entries_set_.end() == it)
+ return false;
+ *metadata = it->second;
+ return true;
+ }
+
+ void InsertIntoIndexFileReturn(const std::string& key,
+ base::Time last_used_time,
+ uint64 entry_size) {
+ uint64 hash_key(simple_util::GetEntryHashKey(key));
+ index_file_->load_result()->entries.insert(std::make_pair(
+ hash_key, EntryMetadata(last_used_time, entry_size)));
+ }
+
+ void ReturnIndexFile() {
+ index_file_->load_result()->did_load = true;
+ index_file_->load_callback().Run();
+ }
+
+ // Non-const for timer manipulation.
+ SimpleIndex* index() { return index_.get(); }
+ const MockSimpleIndexFile* index_file() const { return index_file_.get(); }
+
+ protected:
+ scoped_ptr<SimpleIndex> index_;
+ base::WeakPtr<MockSimpleIndexFile> index_file_;
+};
+
+TEST_F(EntryMetadataTest, Basics) {
+ EntryMetadata entry_metadata;
+ EXPECT_EQ(base::Time::FromInternalValue(0), entry_metadata.GetLastUsedTime());
+ EXPECT_EQ(size_t(0), entry_metadata.GetEntrySize());
+
+ entry_metadata = NewEntryMetadataWithValues();
+ CheckEntryMetadataValues(entry_metadata);
+
+ const base::Time new_time = base::Time::FromInternalValue(5);
+ entry_metadata.SetLastUsedTime(new_time);
+ EXPECT_EQ(new_time, entry_metadata.GetLastUsedTime());
+}
+
+TEST_F(EntryMetadataTest, Serialize) {
+ EntryMetadata entry_metadata = NewEntryMetadataWithValues();
+
+ Pickle pickle;
+ entry_metadata.Serialize(&pickle);
+
+ PickleIterator it(pickle);
+ EntryMetadata new_entry_metadata;
+ new_entry_metadata.Deserialize(&it);
+ CheckEntryMetadataValues(new_entry_metadata);
+}
+
+TEST_F(SimpleIndexTest, IndexSizeCorrectOnMerge) {
+ typedef disk_cache::SimpleIndex::EntrySet EntrySet;
+ index()->SetMaxSize(100);
+ index()->Insert("two");
+ index()->UpdateEntrySize("two", 2);
+ index()->Insert("five");
+ index()->UpdateEntrySize("five", 5);
+ index()->Insert("seven");
+ index()->UpdateEntrySize("seven", 7);
+ EXPECT_EQ(14U, index()->cache_size_);
+ {
+ scoped_ptr<SimpleIndexLoadResult> result(new SimpleIndexLoadResult());
+ result->did_load = true;
+ index()->MergeInitializingSet(result.Pass());
+ }
+ EXPECT_EQ(14U, index()->cache_size_);
+ {
+ scoped_ptr<SimpleIndexLoadResult> result(new SimpleIndexLoadResult());
+ result->did_load = true;
+ const uint64 new_hash_key = simple_util::GetEntryHashKey("eleven");
+ result->entries.insert(
+ std::make_pair(new_hash_key, EntryMetadata(base::Time::Now(), 11)));
+ const uint64 redundant_hash_key = simple_util::GetEntryHashKey("seven");
+ result->entries.insert(std::make_pair(redundant_hash_key,
+ EntryMetadata(base::Time::Now(), 7)));
+ index()->MergeInitializingSet(result.Pass());
+ }
+ EXPECT_EQ(2U + 5U + 7U + 11U, index()->cache_size_);
+}
+
+// State of index changes as expected with an insert and a remove.
+TEST_F(SimpleIndexTest, BasicInsertRemove) {
+ // Confirm blank state.
+ EntryMetadata metadata;
+ EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+
+ // Confirm state after insert.
+ index()->Insert("key1");
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ base::Time now(base::Time::Now());
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+
+ // Confirm state after remove.
+ metadata = EntryMetadata();
+ index()->Remove("key1");
+ EXPECT_FALSE(GetEntryForTesting("key1", &metadata));
+ EXPECT_EQ(base::Time(), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+}
+
+TEST_F(SimpleIndexTest, Has) {
+ // Confirm the base index has dispatched the request for index entries.
+ EXPECT_TRUE(index_file_.get());
+ EXPECT_EQ(1, index_file_->load_index_entries_calls());
+
+ // Confirm "Has()" always returns true before the callback is called.
+ EXPECT_TRUE(index()->Has(kKey1Hash));
+ index()->Insert("key1");
+ EXPECT_TRUE(index()->Has(kKey1Hash));
+ index()->Remove("key1");
+ // TODO(rdsmith): Maybe return false on explicitly removed entries?
+ EXPECT_TRUE(index()->Has(kKey1Hash));
+
+ ReturnIndexFile();
+
+ // Confirm "Has() returns conditionally now.
+ EXPECT_FALSE(index()->Has(kKey1Hash));
+ index()->Insert("key1");
+ EXPECT_TRUE(index()->Has(kKey1Hash));
+ index()->Remove("key1");
+}
+
+TEST_F(SimpleIndexTest, UseIfExists) {
+ // Confirm the base index has dispatched the request for index entries.
+ EXPECT_TRUE(index_file_.get());
+ EXPECT_EQ(1, index_file_->load_index_entries_calls());
+
+ // Confirm "UseIfExists()" always returns true before the callback is called
+ // and updates mod time if the entry was really there.
+ EntryMetadata metadata1, metadata2;
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+ EXPECT_FALSE(GetEntryForTesting("key1", &metadata1));
+ index()->Insert("key1");
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata1));
+ WaitForTimeChange();
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata2));
+ EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata2));
+ EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
+ index()->Remove("key1");
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+
+ ReturnIndexFile();
+
+ // Confirm "UseIfExists() returns conditionally now
+ EXPECT_FALSE(index()->UseIfExists("key1"));
+ EXPECT_FALSE(GetEntryForTesting("key1", &metadata1));
+ index()->Insert("key1");
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata1));
+ WaitForTimeChange();
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata2));
+ EXPECT_EQ(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
+ EXPECT_TRUE(index()->UseIfExists("key1"));
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata2));
+ EXPECT_LT(metadata1.GetLastUsedTime(), metadata2.GetLastUsedTime());
+ index()->Remove("key1");
+ EXPECT_FALSE(index()->UseIfExists("key1"));
+}
+
+TEST_F(SimpleIndexTest, UpdateEntrySize) {
+ base::Time now(base::Time::Now());
+
+ index()->SetMaxSize(1000);
+
+ InsertIntoIndexFileReturn("key1",
+ now - base::TimeDelta::FromDays(2),
+ 475u);
+ ReturnIndexFile();
+
+ EntryMetadata metadata;
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ EXPECT_EQ(now - base::TimeDelta::FromDays(2), metadata.GetLastUsedTime());
+ EXPECT_EQ(475u, metadata.GetEntrySize());
+
+ index()->UpdateEntrySize("key1", 600u);
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ EXPECT_EQ(600u, metadata.GetEntrySize());
+ EXPECT_EQ(1, index()->GetEntryCount());
+}
+
+TEST_F(SimpleIndexTest, GetEntryCount) {
+ EXPECT_EQ(0, index()->GetEntryCount());
+ index()->Insert("key1");
+ EXPECT_EQ(1, index()->GetEntryCount());
+ index()->Insert("key2");
+ EXPECT_EQ(2, index()->GetEntryCount());
+ index()->Insert("key3");
+ EXPECT_EQ(3, index()->GetEntryCount());
+ index()->Insert("key3");
+ EXPECT_EQ(3, index()->GetEntryCount());
+ index()->Remove("key2");
+ EXPECT_EQ(2, index()->GetEntryCount());
+ index()->Insert("key4");
+ EXPECT_EQ(3, index()->GetEntryCount());
+ index()->Remove("key3");
+ EXPECT_EQ(2, index()->GetEntryCount());
+ index()->Remove("key3");
+ EXPECT_EQ(2, index()->GetEntryCount());
+ index()->Remove("key1");
+ EXPECT_EQ(1, index()->GetEntryCount());
+ index()->Remove("key4");
+ EXPECT_EQ(0, index()->GetEntryCount());
+}
+
+// Confirm that we get the results we expect from a simple init.
+TEST_F(SimpleIndexTest, BasicInit) {
+ base::Time now(base::Time::Now());
+
+ InsertIntoIndexFileReturn("key1",
+ now - base::TimeDelta::FromDays(2),
+ 10u);
+ InsertIntoIndexFileReturn("key2",
+ now - base::TimeDelta::FromDays(3),
+ 100u);
+
+ ReturnIndexFile();
+
+ EntryMetadata metadata;
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ EXPECT_EQ(now - base::TimeDelta::FromDays(2), metadata.GetLastUsedTime());
+ EXPECT_EQ(10ul, metadata.GetEntrySize());
+ EXPECT_TRUE(GetEntryForTesting("key2", &metadata));
+ EXPECT_EQ(now - base::TimeDelta::FromDays(3), metadata.GetLastUsedTime());
+ EXPECT_EQ(100ul, metadata.GetEntrySize());
+}
+
+// Remove something that's going to come in from the loaded index.
+TEST_F(SimpleIndexTest, RemoveBeforeInit) {
+ index()->Remove("key1");
+
+ InsertIntoIndexFileReturn("key1",
+ base::Time::Now() - base::TimeDelta::FromDays(2),
+ 10u);
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->Has(kKey1Hash));
+}
+
+// Insert something that's going to come in from the loaded index; correct
+// result?
+TEST_F(SimpleIndexTest, InsertBeforeInit) {
+ index()->Insert("key1");
+
+ InsertIntoIndexFileReturn("key1",
+ base::Time::Now() - base::TimeDelta::FromDays(2),
+ 10u);
+ ReturnIndexFile();
+
+ EntryMetadata metadata;
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ base::Time now(base::Time::Now());
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+}
+
+// Insert and Remove something that's going to come in from the loaded index.
+TEST_F(SimpleIndexTest, InsertRemoveBeforeInit) {
+ index()->Insert("key1");
+ index()->Remove("key1");
+
+ InsertIntoIndexFileReturn("key1",
+ base::Time::Now() - base::TimeDelta::FromDays(2),
+ 10u);
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->Has(kKey1Hash));
+}
+
+// Insert and Remove something that's going to come in from the loaded index.
+TEST_F(SimpleIndexTest, RemoveInsertBeforeInit) {
+ index()->Remove("key1");
+ index()->Insert("key1");
+
+ InsertIntoIndexFileReturn("key1",
+ base::Time::Now() - base::TimeDelta::FromDays(2),
+ 10u);
+ ReturnIndexFile();
+
+ EntryMetadata metadata;
+ EXPECT_TRUE(GetEntryForTesting("key1", &metadata));
+ base::Time now(base::Time::Now());
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+}
+
+// Do all above tests at once + a non-conflict to test for cross-key
+// interactions.
+TEST_F(SimpleIndexTest, AllInitConflicts) {
+ base::Time now(base::Time::Now());
+
+ index()->Remove("key1");
+ InsertIntoIndexFileReturn("key1",
+ now - base::TimeDelta::FromDays(2),
+ 10u);
+ index()->Insert("key2");
+ InsertIntoIndexFileReturn("key2",
+ now - base::TimeDelta::FromDays(3),
+ 100u);
+ index()->Insert("key3");
+ index()->Remove("key3");
+ InsertIntoIndexFileReturn("key3",
+ now - base::TimeDelta::FromDays(4),
+ 1000u);
+ index()->Remove("key4");
+ index()->Insert("key4");
+ InsertIntoIndexFileReturn("key4",
+ now - base::TimeDelta::FromDays(5),
+ 10000u);
+ InsertIntoIndexFileReturn("key5",
+ now - base::TimeDelta::FromDays(6),
+ 100000u);
+
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->Has(kKey1Hash));
+
+ EntryMetadata metadata;
+ EXPECT_TRUE(GetEntryForTesting("key2", &metadata));
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+
+ EXPECT_FALSE(index()->Has(kKey3Hash));
+
+ EXPECT_TRUE(GetEntryForTesting("key4", &metadata));
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), metadata.GetLastUsedTime());
+ EXPECT_EQ(0ul, metadata.GetEntrySize());
+
+ EXPECT_TRUE(GetEntryForTesting("key5", &metadata));
+ EXPECT_EQ(now - base::TimeDelta::FromDays(6), metadata.GetLastUsedTime());
+ EXPECT_EQ(100000u, metadata.GetEntrySize());
+}
+
+TEST_F(SimpleIndexTest, BasicEviction) {
+ base::Time now(base::Time::Now());
+ index()->SetMaxSize(1000);
+ InsertIntoIndexFileReturn("key1",
+ now - base::TimeDelta::FromDays(2),
+ 475u);
+ index()->Insert("key2");
+ index()->UpdateEntrySize("key2", 475);
+ ReturnIndexFile();
+
+ WaitForTimeChange();
+
+ index()->Insert("key3");
+ // Confirm index is as expected: No eviction, everything there.
+ EXPECT_EQ(3, index()->GetEntryCount());
+ EXPECT_EQ(0, index_file()->doom_entry_set_calls());
+ EXPECT_TRUE(index()->Has(kKey1Hash));
+ EXPECT_TRUE(index()->Has(kKey2Hash));
+ EXPECT_TRUE(index()->Has(kKey3Hash));
+
+ // Trigger an eviction, and make sure the right things are tossed.
+ // TODO(rdsmith): This is dependent on the innards of the implementation
+ // as to at exactly what point we trigger eviction. Not sure how to fix
+ // that.
+ index()->UpdateEntrySize("key3", 475);
+ EXPECT_EQ(1, index_file()->doom_entry_set_calls());
+ EXPECT_EQ(1, index()->GetEntryCount());
+ EXPECT_FALSE(index()->Has(kKey1Hash));
+ EXPECT_FALSE(index()->Has(kKey2Hash));
+ EXPECT_TRUE(index()->Has(kKey3Hash));
+ ASSERT_EQ(2u, index_file_->last_doom_entry_hashes().size());
+}
+
+// Confirm all the operations queue a disk write at some point in the
+// future.
+TEST_F(SimpleIndexTest, DiskWriteQueued) {
+ index()->SetMaxSize(1000);
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
+
+ index()->Insert("key1");
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ index()->write_to_disk_timer_.Stop();
+ EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
+
+ index()->UseIfExists("key1");
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ index()->write_to_disk_timer_.Stop();
+
+ index()->UpdateEntrySize("key1", 20);
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ index()->write_to_disk_timer_.Stop();
+
+ index()->Remove("key1");
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ index()->write_to_disk_timer_.Stop();
+}
+
+TEST_F(SimpleIndexTest, DiskWriteExecuted) {
+ index()->SetMaxSize(1000);
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
+
+ index()->Insert("key1");
+ index()->UpdateEntrySize("key1", 20);
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ base::Closure user_task(index()->write_to_disk_timer_.user_task());
+ index()->write_to_disk_timer_.Stop();
+
+ EXPECT_EQ(0, index_file_->disk_writes());
+ user_task.Run();
+ EXPECT_EQ(1, index_file_->disk_writes());
+ SimpleIndex::EntrySet entry_set;
+ index_file_->GetAndResetDiskWriteEntrySet(&entry_set);
+
+ uint64 hash_key(simple_util::GetEntryHashKey("key1"));
+ base::Time now(base::Time::Now());
+ ASSERT_EQ(1u, entry_set.size());
+ EXPECT_EQ(hash_key, entry_set.begin()->first);
+ const EntryMetadata& entry1(entry_set.begin()->second);
+ EXPECT_LT(now - base::TimeDelta::FromMinutes(1), entry1.GetLastUsedTime());
+ EXPECT_GT(now + base::TimeDelta::FromMinutes(1), entry1.GetLastUsedTime());
+ EXPECT_EQ(20u, entry1.GetEntrySize());
+}
+
+TEST_F(SimpleIndexTest, DiskWritePostponed) {
+ index()->SetMaxSize(1000);
+ ReturnIndexFile();
+
+ EXPECT_FALSE(index()->write_to_disk_timer_.IsRunning());
+
+ index()->Insert("key1");
+ index()->UpdateEntrySize("key1", 20);
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ base::TimeTicks expected_trigger(
+ index()->write_to_disk_timer_.desired_run_time());
+
+ WaitForTimeChange();
+ EXPECT_EQ(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
+ index()->Insert("key2");
+ index()->UpdateEntrySize("key2", 40);
+ EXPECT_TRUE(index()->write_to_disk_timer_.IsRunning());
+ EXPECT_LT(expected_trigger, index()->write_to_disk_timer_.desired_run_time());
+ index()->write_to_disk_timer_.Stop();
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_net_log_parameters.cc b/chromium/net/disk_cache/simple/simple_net_log_parameters.cc
new file mode 100644
index 00000000000..9acd66396a9
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_net_log_parameters.cc
@@ -0,0 +1,55 @@
+// Copyright 2013 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/disk_cache/simple/simple_net_log_parameters.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/simple/simple_entry_impl.h"
+
+namespace {
+
+base::Value* NetLogSimpleEntryConstructionCallback(
+ const disk_cache::SimpleEntryImpl* entry,
+ net::NetLog::LogLevel log_level ALLOW_UNUSED) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("entry_hash",
+ base::StringPrintf("%#016" PRIx64, entry->entry_hash()));
+ return dict;
+}
+
+base::Value* NetLogSimpleEntryCreationCallback(
+ const disk_cache::SimpleEntryImpl* entry,
+ int net_error,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ if (net_error == net::OK)
+ dict->SetString("key", entry->key());
+ return dict;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+net::NetLog::ParametersCallback CreateNetLogSimpleEntryConstructionCallback(
+ const SimpleEntryImpl* entry) {
+ DCHECK(entry);
+ return base::Bind(&NetLogSimpleEntryConstructionCallback, entry);
+}
+
+net::NetLog::ParametersCallback CreateNetLogSimpleEntryCreationCallback(
+ const SimpleEntryImpl* entry,
+ int net_error) {
+ DCHECK(entry);
+ return base::Bind(&NetLogSimpleEntryCreationCallback, entry, net_error);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_net_log_parameters.h b/chromium/net/disk_cache/simple/simple_net_log_parameters.h
new file mode 100644
index 00000000000..b6f386f4a99
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_net_log_parameters.h
@@ -0,0 +1,32 @@
+// Copyright 2013 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_DISK_CACHE_SIMPLE_NET_LOG_PARAMETERS_H_
+#define NET_DISK_CACHE_SIMPLE_NET_LOG_PARAMETERS_H_
+
+#include "net/base/net_log.h"
+
+// This file augments the functions in net/disk_cache/net_log_parameters.h to
+// include ones that deal with specifics of the Simple Cache backend.
+namespace disk_cache {
+
+class SimpleEntryImpl;
+
+// Creates a NetLog callback that returns parameters for the construction of a
+// SimpleEntryImpl. Contains the entry's hash. |entry| can't be NULL and must
+// outlive the returned callback.
+net::NetLog::ParametersCallback CreateNetLogSimpleEntryConstructionCallback(
+ const SimpleEntryImpl* entry);
+
+// Creates a NetLog callback that returns parameters for the result of calling
+// |CreateEntry| or |OpenEntry| on a SimpleEntryImpl. Contains the |net_error|
+// and, if successful, the entry's key. |entry| can't be NULL and must outlive
+// the returned callback.
+net::NetLog::ParametersCallback CreateNetLogSimpleEntryCreationCallback(
+ const SimpleEntryImpl* entry,
+ int net_error);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_NET_LOG_PARAMETERS_H_
diff --git a/chromium/net/disk_cache/simple/simple_synchronous_entry.cc b/chromium/net/disk_cache/simple/simple_synchronous_entry.cc
new file mode 100644
index 00000000000..e6f1eaa4f21
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_synchronous_entry.cc
@@ -0,0 +1,635 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_synchronous_entry.h"
+
+#include <algorithm>
+#include <cstring>
+#include <functional>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/hash.h"
+#include "base/location.h"
+#include "base/metrics/histogram.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "third_party/zlib/zlib.h"
+
+using base::kInvalidPlatformFileValue;
+using base::ClosePlatformFile;
+using base::FilePath;
+using base::GetPlatformFileInfo;
+using base::PlatformFileError;
+using base::PlatformFileInfo;
+using base::PLATFORM_FILE_CREATE;
+using base::PLATFORM_FILE_ERROR_EXISTS;
+using base::PLATFORM_FILE_OK;
+using base::PLATFORM_FILE_OPEN;
+using base::PLATFORM_FILE_READ;
+using base::PLATFORM_FILE_WRITE;
+using base::ReadPlatformFile;
+using base::Time;
+using base::TruncatePlatformFile;
+using base::WritePlatformFile;
+
+namespace {
+
+// Used in histograms, please only add entries at the end.
+enum OpenEntryResult {
+ OPEN_ENTRY_SUCCESS = 0,
+ OPEN_ENTRY_PLATFORM_FILE_ERROR = 1,
+ OPEN_ENTRY_CANT_READ_HEADER = 2,
+ OPEN_ENTRY_BAD_MAGIC_NUMBER = 3,
+ OPEN_ENTRY_BAD_VERSION = 4,
+ OPEN_ENTRY_CANT_READ_KEY = 5,
+ // OPEN_ENTRY_KEY_MISMATCH = 6, Deprecated.
+ OPEN_ENTRY_KEY_HASH_MISMATCH = 7,
+ OPEN_ENTRY_MAX = 8,
+};
+
+// Used in histograms, please only add entries at the end.
+enum CreateEntryResult {
+ CREATE_ENTRY_SUCCESS = 0,
+ CREATE_ENTRY_PLATFORM_FILE_ERROR = 1,
+ CREATE_ENTRY_CANT_WRITE_HEADER = 2,
+ CREATE_ENTRY_CANT_WRITE_KEY = 3,
+ CREATE_ENTRY_MAX = 4,
+};
+
+// Used in histograms, please only add entries at the end.
+enum WriteResult {
+ WRITE_RESULT_SUCCESS = 0,
+ WRITE_RESULT_PRETRUNCATE_FAILURE,
+ WRITE_RESULT_WRITE_FAILURE,
+ WRITE_RESULT_TRUNCATE_FAILURE,
+ WRITE_RESULT_MAX,
+};
+
+// Used in histograms, please only add entries at the end.
+enum CheckEOFResult {
+ CHECK_EOF_RESULT_SUCCESS,
+ CHECK_EOF_RESULT_READ_FAILURE,
+ CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH,
+ CHECK_EOF_RESULT_CRC_MISMATCH,
+ CHECK_EOF_RESULT_MAX,
+};
+
+// Used in histograms, please only add entries at the end.
+enum CloseResult {
+ CLOSE_RESULT_SUCCESS,
+ CLOSE_RESULT_WRITE_FAILURE,
+};
+
+void RecordSyncOpenResult(OpenEntryResult result, bool had_index) {
+ DCHECK_GT(OPEN_ENTRY_MAX, result);
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncOpenResult", result, OPEN_ENTRY_MAX);
+ if (had_index) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncOpenResult_WithIndex", result, OPEN_ENTRY_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncOpenResult_WithoutIndex", result, OPEN_ENTRY_MAX);
+ }
+}
+
+void RecordSyncCreateResult(CreateEntryResult result, bool had_index) {
+ DCHECK_GT(CREATE_ENTRY_MAX, result);
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCreateResult", result, CREATE_ENTRY_MAX);
+ if (had_index) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCreateResult_WithIndex", result, CREATE_ENTRY_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCreateResult_WithoutIndex", result, CREATE_ENTRY_MAX);
+ }
+}
+
+void RecordWriteResult(WriteResult result) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncWriteResult", result, WRITE_RESULT_MAX);
+}
+
+void RecordCheckEOFResult(CheckEOFResult result) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCheckEOFResult", result, CHECK_EOF_RESULT_MAX);
+}
+
+void RecordCloseResult(CloseResult result) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCloseResult", result, WRITE_RESULT_MAX);
+}
+
+} // namespace
+
+namespace disk_cache {
+
+using simple_util::ConvertEntryHashKeyToHexString;
+using simple_util::GetEntryHashKey;
+using simple_util::GetFilenameFromEntryHashAndIndex;
+using simple_util::GetDataSizeFromKeyAndFileSize;
+using simple_util::GetFileSizeFromKeyAndDataSize;
+using simple_util::GetFileOffsetFromKeyAndDataOffset;
+
+SimpleEntryStat::SimpleEntryStat() {}
+
+SimpleEntryStat::SimpleEntryStat(base::Time last_used_p,
+ base::Time last_modified_p,
+ const int32 data_size_p[])
+ : last_used(last_used_p),
+ last_modified(last_modified_p) {
+ memcpy(data_size, data_size_p, sizeof(data_size));
+}
+
+SimpleEntryCreationResults::SimpleEntryCreationResults(
+ SimpleEntryStat entry_stat)
+ : sync_entry(NULL),
+ entry_stat(entry_stat),
+ result(net::OK) {
+}
+
+SimpleEntryCreationResults::~SimpleEntryCreationResults() {
+}
+
+SimpleSynchronousEntry::CRCRecord::CRCRecord() : index(-1),
+ has_crc32(false),
+ data_crc32(0) {
+}
+
+SimpleSynchronousEntry::CRCRecord::CRCRecord(int index_p,
+ bool has_crc32_p,
+ uint32 data_crc32_p)
+ : index(index_p),
+ has_crc32(has_crc32_p),
+ data_crc32(data_crc32_p) {}
+
+SimpleSynchronousEntry::EntryOperationData::EntryOperationData(int index_p,
+ int offset_p,
+ int buf_len_p)
+ : index(index_p),
+ offset(offset_p),
+ buf_len(buf_len_p) {}
+
+SimpleSynchronousEntry::EntryOperationData::EntryOperationData(int index_p,
+ int offset_p,
+ int buf_len_p,
+ bool truncate_p)
+ : index(index_p),
+ offset(offset_p),
+ buf_len(buf_len_p),
+ truncate(truncate_p) {}
+
+// static
+void SimpleSynchronousEntry::OpenEntry(
+ const FilePath& path,
+ const uint64 entry_hash,
+ bool had_index,
+ SimpleEntryCreationResults *out_results) {
+ SimpleSynchronousEntry* sync_entry = new SimpleSynchronousEntry(path, "",
+ entry_hash);
+ out_results->result = sync_entry->InitializeForOpen(
+ had_index, &out_results->entry_stat);
+ if (out_results->result != net::OK) {
+ sync_entry->Doom();
+ delete sync_entry;
+ out_results->sync_entry = NULL;
+ return;
+ }
+ out_results->sync_entry = sync_entry;
+}
+
+// static
+void SimpleSynchronousEntry::CreateEntry(
+ const FilePath& path,
+ const std::string& key,
+ const uint64 entry_hash,
+ bool had_index,
+ SimpleEntryCreationResults *out_results) {
+ DCHECK_EQ(entry_hash, GetEntryHashKey(key));
+ SimpleSynchronousEntry* sync_entry = new SimpleSynchronousEntry(path, key,
+ entry_hash);
+ out_results->result = sync_entry->InitializeForCreate(
+ had_index, &out_results->entry_stat);
+ if (out_results->result != net::OK) {
+ if (out_results->result != net::ERR_FILE_EXISTS)
+ sync_entry->Doom();
+ delete sync_entry;
+ out_results->sync_entry = NULL;
+ return;
+ }
+ out_results->sync_entry = sync_entry;
+}
+
+// TODO(gavinp): Move this function to its correct location in this .cc file.
+// static
+bool SimpleSynchronousEntry::DeleteFilesForEntryHash(
+ const FilePath& path,
+ const uint64 entry_hash) {
+ bool result = true;
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ FilePath to_delete = path.AppendASCII(
+ GetFilenameFromEntryHashAndIndex(entry_hash, i));
+ if (!base::DeleteFile(to_delete, false)) {
+ result = false;
+ DLOG(ERROR) << "Could not delete " << to_delete.MaybeAsASCII();
+ }
+ }
+ return result;
+}
+
+// static
+void SimpleSynchronousEntry::DoomEntry(
+ const FilePath& path,
+ const std::string& key,
+ uint64 entry_hash,
+ int* out_result) {
+ DCHECK_EQ(entry_hash, GetEntryHashKey(key));
+ bool deleted_well = DeleteFilesForEntryHash(path, entry_hash);
+ *out_result = deleted_well ? net::OK : net::ERR_FAILED;
+}
+
+// static
+int SimpleSynchronousEntry::DoomEntrySet(
+ scoped_ptr<std::vector<uint64> > key_hashes,
+ const FilePath& path) {
+ const size_t did_delete_count = std::count_if(
+ key_hashes->begin(), key_hashes->end(), std::bind1st(
+ std::ptr_fun(SimpleSynchronousEntry::DeleteFilesForEntryHash), path));
+ return (did_delete_count == key_hashes->size()) ? net::OK : net::ERR_FAILED;
+}
+
+void SimpleSynchronousEntry::ReadData(const EntryOperationData& in_entry_op,
+ net::IOBuffer* out_buf,
+ uint32* out_crc32,
+ base::Time* out_last_used,
+ int* out_result) const {
+ DCHECK(initialized_);
+ int64 file_offset =
+ GetFileOffsetFromKeyAndDataOffset(key_, in_entry_op.offset);
+ int bytes_read = ReadPlatformFile(files_[in_entry_op.index],
+ file_offset,
+ out_buf->data(),
+ in_entry_op.buf_len);
+ if (bytes_read > 0) {
+ *out_last_used = Time::Now();
+ *out_crc32 = crc32(crc32(0L, Z_NULL, 0),
+ reinterpret_cast<const Bytef*>(out_buf->data()),
+ bytes_read);
+ }
+ if (bytes_read >= 0) {
+ *out_result = bytes_read;
+ } else {
+ *out_result = net::ERR_CACHE_READ_FAILURE;
+ Doom();
+ }
+}
+
+void SimpleSynchronousEntry::WriteData(const EntryOperationData& in_entry_op,
+ net::IOBuffer* in_buf,
+ SimpleEntryStat* out_entry_stat,
+ int* out_result) const {
+ DCHECK(initialized_);
+ int index = in_entry_op.index;
+ int offset = in_entry_op.offset;
+ int buf_len = in_entry_op.buf_len;
+ int truncate = in_entry_op.truncate;
+
+ bool extending_by_write = offset + buf_len > out_entry_stat->data_size[index];
+ if (extending_by_write) {
+ // We are extending the file, and need to insure the EOF record is zeroed.
+ const int64 file_eof_offset = GetFileOffsetFromKeyAndDataOffset(
+ key_, out_entry_stat->data_size[index]);
+ if (!TruncatePlatformFile(files_[index], file_eof_offset)) {
+ RecordWriteResult(WRITE_RESULT_PRETRUNCATE_FAILURE);
+ Doom();
+ *out_result = net::ERR_CACHE_WRITE_FAILURE;
+ return;
+ }
+ }
+ const int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, offset);
+ if (buf_len > 0) {
+ if (WritePlatformFile(
+ files_[index], file_offset, in_buf->data(), buf_len) != buf_len) {
+ RecordWriteResult(WRITE_RESULT_WRITE_FAILURE);
+ Doom();
+ *out_result = net::ERR_CACHE_WRITE_FAILURE;
+ return;
+ }
+ }
+ if (!truncate && (buf_len > 0 || !extending_by_write)) {
+ out_entry_stat->data_size[index] =
+ std::max(out_entry_stat->data_size[index], offset + buf_len);
+ } else {
+ if (!TruncatePlatformFile(files_[index], file_offset + buf_len)) {
+ RecordWriteResult(WRITE_RESULT_TRUNCATE_FAILURE);
+ Doom();
+ *out_result = net::ERR_CACHE_WRITE_FAILURE;
+ return;
+ }
+ out_entry_stat->data_size[index] = offset + buf_len;
+ }
+
+ RecordWriteResult(WRITE_RESULT_SUCCESS);
+ out_entry_stat->last_used = out_entry_stat->last_modified = Time::Now();
+ *out_result = buf_len;
+}
+
+void SimpleSynchronousEntry::CheckEOFRecord(int index,
+ int32 data_size,
+ uint32 expected_crc32,
+ int* out_result) const {
+ DCHECK(initialized_);
+
+ SimpleFileEOF eof_record;
+ int64 file_offset = GetFileOffsetFromKeyAndDataOffset(key_, data_size);
+ if (ReadPlatformFile(files_[index],
+ file_offset,
+ reinterpret_cast<char*>(&eof_record),
+ sizeof(eof_record)) != sizeof(eof_record)) {
+ RecordCheckEOFResult(CHECK_EOF_RESULT_READ_FAILURE);
+ Doom();
+ *out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE;
+ return;
+ }
+
+ if (eof_record.final_magic_number != kSimpleFinalMagicNumber) {
+ RecordCheckEOFResult(CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH);
+ DLOG(INFO) << "eof record had bad magic number.";
+ Doom();
+ *out_result = net::ERR_CACHE_CHECKSUM_READ_FAILURE;
+ return;
+ }
+
+ const bool has_crc = (eof_record.flags & SimpleFileEOF::FLAG_HAS_CRC32) ==
+ SimpleFileEOF::FLAG_HAS_CRC32;
+ UMA_HISTOGRAM_BOOLEAN("SimpleCache.SyncCheckEOFHasCrc", has_crc);
+ if (has_crc && eof_record.data_crc32 != expected_crc32) {
+ RecordCheckEOFResult(CHECK_EOF_RESULT_CRC_MISMATCH);
+ DLOG(INFO) << "eof record had bad crc.";
+ Doom();
+ *out_result = net::ERR_CACHE_CHECKSUM_MISMATCH;
+ return;
+ }
+
+ RecordCheckEOFResult(CHECK_EOF_RESULT_SUCCESS);
+ *out_result = net::OK;
+}
+
+void SimpleSynchronousEntry::Close(
+ const SimpleEntryStat& entry_stat,
+ scoped_ptr<std::vector<CRCRecord> > crc32s_to_write) {
+ for (std::vector<CRCRecord>::const_iterator it = crc32s_to_write->begin();
+ it != crc32s_to_write->end(); ++it) {
+ SimpleFileEOF eof_record;
+ eof_record.final_magic_number = kSimpleFinalMagicNumber;
+ eof_record.flags = 0;
+ if (it->has_crc32)
+ eof_record.flags |= SimpleFileEOF::FLAG_HAS_CRC32;
+ eof_record.data_crc32 = it->data_crc32;
+ int64 file_offset = GetFileOffsetFromKeyAndDataOffset(
+ key_, entry_stat.data_size[it->index]);
+ if (WritePlatformFile(files_[it->index],
+ file_offset,
+ reinterpret_cast<const char*>(&eof_record),
+ sizeof(eof_record)) != sizeof(eof_record)) {
+ RecordCloseResult(CLOSE_RESULT_WRITE_FAILURE);
+ DLOG(INFO) << "Could not write eof record.";
+ Doom();
+ break;
+ }
+ const int64 file_size = file_offset + sizeof(eof_record);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("SimpleCache.LastClusterSize",
+ file_size % 4096, 0, 4097, 50);
+ const int64 cluster_loss = file_size % 4096 ? 4096 - file_size % 4096 : 0;
+ UMA_HISTOGRAM_PERCENTAGE("SimpleCache.LastClusterLossPercent",
+ cluster_loss * 100 / (cluster_loss + file_size));
+ }
+
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ bool did_close_file = ClosePlatformFile(files_[i]);
+ CHECK(did_close_file);
+ }
+ RecordCloseResult(CLOSE_RESULT_SUCCESS);
+ have_open_files_ = false;
+ delete this;
+}
+
+SimpleSynchronousEntry::SimpleSynchronousEntry(const FilePath& path,
+ const std::string& key,
+ const uint64 entry_hash)
+ : path_(path),
+ entry_hash_(entry_hash),
+ key_(key),
+ have_open_files_(false),
+ initialized_(false) {
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ files_[i] = kInvalidPlatformFileValue;
+ }
+}
+
+SimpleSynchronousEntry::~SimpleSynchronousEntry() {
+ DCHECK(!(have_open_files_ && initialized_));
+ if (have_open_files_)
+ CloseFiles();
+}
+
+bool SimpleSynchronousEntry::OpenOrCreateFiles(
+ bool create,
+ bool had_index,
+ SimpleEntryStat* out_entry_stat) {
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ FilePath filename = path_.AppendASCII(
+ GetFilenameFromEntryHashAndIndex(entry_hash_, i));
+ int flags = PLATFORM_FILE_READ | PLATFORM_FILE_WRITE;
+ if (create)
+ flags |= PLATFORM_FILE_CREATE;
+ else
+ flags |= PLATFORM_FILE_OPEN;
+ PlatformFileError error;
+ files_[i] = CreatePlatformFile(filename, flags, NULL, &error);
+ if (error != PLATFORM_FILE_OK) {
+ // TODO(ttuttle,gavinp): Remove one each of these triplets of histograms.
+ // We can calculate the third as the sum or difference of the other two.
+ if (create) {
+ RecordSyncCreateResult(CREATE_ENTRY_PLATFORM_FILE_ERROR, had_index);
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.SyncCreatePlatformFileError",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ if (had_index) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCreatePlatformFileError_WithIndex",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncCreatePlatformFileError_WithoutIndex",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ }
+ } else {
+ RecordSyncOpenResult(OPEN_ENTRY_PLATFORM_FILE_ERROR, had_index);
+ UMA_HISTOGRAM_ENUMERATION("SimpleCache.SyncOpenPlatformFileError",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ if (had_index) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncOpenPlatformFileError_WithIndex",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "SimpleCache.SyncOpenPlatformFileError_WithoutIndex",
+ -error, -base::PLATFORM_FILE_ERROR_MAX);
+ }
+ }
+ while (--i >= 0) {
+ bool ALLOW_UNUSED did_close = ClosePlatformFile(files_[i]);
+ DLOG_IF(INFO, !did_close) << "Could not close file "
+ << filename.MaybeAsASCII();
+ }
+ return false;
+ }
+ }
+
+ have_open_files_ = true;
+ if (create) {
+ out_entry_stat->last_modified = out_entry_stat->last_used = Time::Now();
+ for (int i = 0; i < kSimpleEntryFileCount; ++i)
+ out_entry_stat->data_size[i] = 0;
+ } else {
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ PlatformFileInfo file_info;
+ bool success = GetPlatformFileInfo(files_[i], &file_info);
+ base::Time file_last_modified;
+ if (!success) {
+ DLOG(WARNING) << "Could not get platform file info.";
+ continue;
+ }
+ out_entry_stat->last_used = file_info.last_accessed;
+ if (simple_util::GetMTime(path_, &file_last_modified))
+ out_entry_stat->last_modified = file_last_modified;
+ else
+ out_entry_stat->last_modified = file_info.last_modified;
+
+ // Keep the file size in |data size_| briefly until the key is initialized
+ // properly.
+ out_entry_stat->data_size[i] = file_info.size;
+ }
+ }
+
+ return true;
+}
+
+void SimpleSynchronousEntry::CloseFiles() {
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ DCHECK_NE(kInvalidPlatformFileValue, files_[i]);
+ bool did_close = ClosePlatformFile(files_[i]);
+ DCHECK(did_close);
+ }
+}
+
+int SimpleSynchronousEntry::InitializeForOpen(bool had_index,
+ SimpleEntryStat* out_entry_stat) {
+ DCHECK(!initialized_);
+ if (!OpenOrCreateFiles(false, had_index, out_entry_stat))
+ return net::ERR_FAILED;
+
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ SimpleFileHeader header;
+ int header_read_result =
+ ReadPlatformFile(files_[i], 0, reinterpret_cast<char*>(&header),
+ sizeof(header));
+ if (header_read_result != sizeof(header)) {
+ DLOG(WARNING) << "Cannot read header from entry.";
+ RecordSyncOpenResult(OPEN_ENTRY_CANT_READ_HEADER, had_index);
+ return net::ERR_FAILED;
+ }
+
+ if (header.initial_magic_number != kSimpleInitialMagicNumber) {
+ // TODO(gavinp): This seems very bad; for now we log at WARNING, but we
+ // should give consideration to not saturating the log with these if that
+ // becomes a problem.
+ DLOG(WARNING) << "Magic number did not match.";
+ RecordSyncOpenResult(OPEN_ENTRY_BAD_MAGIC_NUMBER, had_index);
+ return net::ERR_FAILED;
+ }
+
+ if (header.version != kSimpleVersion) {
+ DLOG(WARNING) << "Unreadable version.";
+ RecordSyncOpenResult(OPEN_ENTRY_BAD_VERSION, had_index);
+ return net::ERR_FAILED;
+ }
+
+ scoped_ptr<char[]> key(new char[header.key_length]);
+ int key_read_result = ReadPlatformFile(files_[i], sizeof(header),
+ key.get(), header.key_length);
+ if (key_read_result != implicit_cast<int>(header.key_length)) {
+ DLOG(WARNING) << "Cannot read key from entry.";
+ RecordSyncOpenResult(OPEN_ENTRY_CANT_READ_KEY, had_index);
+ return net::ERR_FAILED;
+ }
+
+ key_ = std::string(key.get(), header.key_length);
+ out_entry_stat->data_size[i] =
+ GetDataSizeFromKeyAndFileSize(key_, out_entry_stat->data_size[i]);
+ if (out_entry_stat->data_size[i] < 0) {
+ // This entry can't possibly be valid, as it does not have enough space to
+ // store a valid SimpleFileEOF record.
+ return net::ERR_FAILED;
+ }
+
+ if (base::Hash(key.get(), header.key_length) != header.key_hash) {
+ DLOG(WARNING) << "Hash mismatch on key.";
+ RecordSyncOpenResult(OPEN_ENTRY_KEY_HASH_MISMATCH, had_index);
+ return net::ERR_FAILED;
+ }
+ }
+ RecordSyncOpenResult(OPEN_ENTRY_SUCCESS, had_index);
+ initialized_ = true;
+ return net::OK;
+}
+
+int SimpleSynchronousEntry::InitializeForCreate(
+ bool had_index,
+ SimpleEntryStat* out_entry_stat) {
+ DCHECK(!initialized_);
+ if (!OpenOrCreateFiles(true, had_index, out_entry_stat)) {
+ DLOG(WARNING) << "Could not create platform files.";
+ return net::ERR_FILE_EXISTS;
+ }
+ for (int i = 0; i < kSimpleEntryFileCount; ++i) {
+ SimpleFileHeader header;
+ header.initial_magic_number = kSimpleInitialMagicNumber;
+ header.version = kSimpleVersion;
+
+ header.key_length = key_.size();
+ header.key_hash = base::Hash(key_);
+
+ if (WritePlatformFile(files_[i], 0, reinterpret_cast<char*>(&header),
+ sizeof(header)) != sizeof(header)) {
+ DLOG(WARNING) << "Could not write headers to new cache entry.";
+ RecordSyncCreateResult(CREATE_ENTRY_CANT_WRITE_HEADER, had_index);
+ return net::ERR_FAILED;
+ }
+
+ if (WritePlatformFile(files_[i], sizeof(header), key_.data(),
+ key_.size()) != implicit_cast<int>(key_.size())) {
+ DLOG(WARNING) << "Could not write keys to new cache entry.";
+ RecordSyncCreateResult(CREATE_ENTRY_CANT_WRITE_KEY, had_index);
+ return net::ERR_FAILED;
+ }
+ }
+ RecordSyncCreateResult(CREATE_ENTRY_SUCCESS, had_index);
+ initialized_ = true;
+ return net::OK;
+}
+
+void SimpleSynchronousEntry::Doom() const {
+ // TODO(gavinp): Consider if we should guard against redundant Doom() calls.
+ DeleteFilesForEntryHash(path_, entry_hash_);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_synchronous_entry.h b/chromium/net/disk_cache/simple/simple_synchronous_entry.h
new file mode 100644
index 00000000000..f591c9a0f93
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_synchronous_entry.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "base/time/time.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace disk_cache {
+
+class SimpleSynchronousEntry;
+
+struct SimpleEntryStat {
+ SimpleEntryStat();
+ SimpleEntryStat(base::Time last_used_p,
+ base::Time last_modified_p,
+ const int32 data_size_p[]);
+
+ base::Time last_used;
+ base::Time last_modified;
+ int32 data_size[kSimpleEntryFileCount];
+};
+
+struct SimpleEntryCreationResults {
+ SimpleEntryCreationResults(SimpleEntryStat entry_stat);
+ ~SimpleEntryCreationResults();
+
+ SimpleSynchronousEntry* sync_entry;
+ SimpleEntryStat entry_stat;
+ int result;
+};
+
+// Worker thread interface to the very simple cache. This interface is not
+// thread safe, and callers must ensure that it is only ever accessed from
+// a single thread between synchronization points.
+class SimpleSynchronousEntry {
+ public:
+ struct CRCRecord {
+ CRCRecord();
+ CRCRecord(int index_p, bool has_crc32_p, uint32 data_crc32_p);
+
+ int index;
+ bool has_crc32;
+ uint32 data_crc32;
+ };
+
+ struct EntryOperationData {
+ EntryOperationData(int index_p, int offset_p, int buf_len_p);
+ EntryOperationData(int index_p,
+ int offset_p,
+ int buf_len_p,
+ bool truncate_p);
+
+ int index;
+ int offset;
+ int buf_len;
+ bool truncate;
+ };
+
+ static void OpenEntry(const base::FilePath& path,
+ uint64 entry_hash,
+ bool had_index,
+ SimpleEntryCreationResults* out_results);
+
+ static void CreateEntry(const base::FilePath& path,
+ const std::string& key,
+ uint64 entry_hash,
+ bool had_index,
+ SimpleEntryCreationResults* out_results);
+
+ // Deletes an entry without first Opening it. Does not check if there is
+ // already an Entry object in memory holding the open files. Be careful! This
+ // is meant to be used by the Backend::DoomEntry() call. |callback| will be
+ // run by |callback_runner|.
+ static void DoomEntry(const base::FilePath& path,
+ const std::string& key,
+ uint64 entry_hash,
+ int* out_result);
+
+ // Like |DoomEntry()| above. Deletes all entries corresponding to the
+ // |key_hashes|. Succeeds only when all entries are deleted. Returns a net
+ // error code.
+ static int DoomEntrySet(scoped_ptr<std::vector<uint64> > key_hashes,
+ const base::FilePath& path);
+
+ // N.B. ReadData(), WriteData(), CheckEOFRecord() and Close() may block on IO.
+ void ReadData(const EntryOperationData& in_entry_op,
+ net::IOBuffer* out_buf,
+ uint32* out_crc32,
+ base::Time* out_last_used,
+ int* out_result) const;
+ void WriteData(const EntryOperationData& in_entry_op,
+ net::IOBuffer* in_buf,
+ SimpleEntryStat* out_entry_stat,
+ int* out_result) const;
+ void CheckEOFRecord(int index,
+ int data_size,
+ uint32 expected_crc32,
+ int* out_result) const;
+
+ // Close all streams, and add write EOF records to streams indicated by the
+ // CRCRecord entries in |crc32s_to_write|.
+ void Close(const SimpleEntryStat& entry_stat,
+ scoped_ptr<std::vector<CRCRecord> > crc32s_to_write);
+
+ const base::FilePath& path() const { return path_; }
+ std::string key() const { return key_; }
+
+ private:
+ SimpleSynchronousEntry(
+ const base::FilePath& path,
+ const std::string& key,
+ uint64 entry_hash);
+
+ // Like Entry, the SimpleSynchronousEntry self releases when Close() is
+ // called.
+ ~SimpleSynchronousEntry();
+
+ bool OpenOrCreateFiles(bool create,
+ bool had_index,
+ SimpleEntryStat* out_entry_stat);
+ void CloseFiles();
+
+ // Returns a net error, i.e. net::OK on success. |had_index| is passed
+ // from the main entry for metrics purposes, and is true if the index was
+ // initialized when the open operation began.
+ int InitializeForOpen(bool had_index, SimpleEntryStat* out_entry_stat);
+
+ // Returns a net error, including net::OK on success and net::FILE_EXISTS
+ // when the entry already exists. |had_index| is passed from the main entry
+ // for metrics purposes, and is true if the index was initialized when the
+ // create operation began.
+ int InitializeForCreate(bool had_index, SimpleEntryStat* out_entry_stat);
+
+ void Doom() const;
+
+ static bool DeleteFilesForEntryHash(const base::FilePath& path,
+ uint64 entry_hash);
+
+ const base::FilePath path_;
+ const uint64 entry_hash_;
+ std::string key_;
+
+ bool have_open_files_;
+ bool initialized_;
+
+ base::PlatformFile files_[kSimpleEntryFileCount];
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
diff --git a/chromium/net/disk_cache/simple/simple_test_util.cc b/chromium/net/disk_cache/simple/simple_test_util.cc
new file mode 100644
index 00000000000..483cbec1cdd
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_test_util.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 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/disk_cache/simple/simple_test_util.h"
+
+#include "base/file_util.h"
+#include "net/disk_cache/simple/simple_util.h"
+
+namespace disk_cache {
+
+namespace simple_util {
+
+bool CreateCorruptFileForTests(const std::string& key,
+ const base::FilePath& cache_path) {
+ base::FilePath entry_file_path = cache_path.AppendASCII(
+ disk_cache::simple_util::GetFilenameFromKeyAndIndex(key, 0));
+ int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE;
+ base::PlatformFile entry_file =
+ base::CreatePlatformFile(entry_file_path, flags, NULL, NULL);
+
+ if (base::kInvalidPlatformFileValue == entry_file)
+ return false;
+ if (base::WritePlatformFile(entry_file, 0, "dummy", 1) != 1)
+ return false;
+ if (!base::ClosePlatformFile(entry_file))
+ return false;
+
+ return true;
+}
+
+} // namespace simple_backend
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_test_util.h b/chromium/net/disk_cache/simple/simple_test_util.h
new file mode 100644
index 00000000000..98c140b1f14
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_test_util.h
@@ -0,0 +1,29 @@
+// Copyright 2013 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_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace disk_cache {
+
+namespace simple_util {
+
+// Creates a corrupt file to be used in tests.
+bool CreateCorruptFileForTests(const std::string& key,
+ const base::FilePath& cache_path);
+
+} // namespace simple_backend
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
diff --git a/chromium/net/disk_cache/simple/simple_util.cc b/chromium/net/disk_cache/simple/simple_util.cc
new file mode 100644
index 00000000000..72a4612271f
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_util.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 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/disk_cache/simple/simple_util.h"
+
+#include <limits>
+
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "net/disk_cache/simple/simple_entry_format.h"
+
+namespace {
+
+// Size of the uint64 hash_key number in Hex format in a string.
+const size_t kEntryHashKeyAsHexStringSize = 2 * sizeof(uint64);
+
+} // namespace
+
+namespace disk_cache {
+
+namespace simple_util {
+
+std::string ConvertEntryHashKeyToHexString(uint64 hash_key) {
+ const std::string hash_key_str = base::StringPrintf("%016" PRIx64, hash_key);
+ DCHECK_EQ(kEntryHashKeyAsHexStringSize, hash_key_str.size());
+ return hash_key_str;
+}
+
+std::string GetEntryHashKeyAsHexString(const std::string& key) {
+ std::string hash_key_str =
+ ConvertEntryHashKeyToHexString(GetEntryHashKey(key));
+ DCHECK_EQ(kEntryHashKeyAsHexStringSize, hash_key_str.size());
+ return hash_key_str;
+}
+
+bool GetEntryHashKeyFromHexString(const std::string& hash_key,
+ uint64* hash_key_out) {
+ if (hash_key.size() != kEntryHashKeyAsHexStringSize) {
+ return false;
+ }
+ return base::HexStringToUInt64(hash_key, hash_key_out);
+}
+
+uint64 GetEntryHashKey(const std::string& key) {
+ union {
+ unsigned char sha_hash[base::kSHA1Length];
+ uint64 key_hash;
+ } u;
+ base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(key.data()),
+ key.size(), u.sha_hash);
+ return u.key_hash;
+}
+
+std::string GetFilenameFromEntryHashAndIndex(uint64 entry_hash,
+ int index) {
+ return base::StringPrintf("%016" PRIx64 "_%1d", entry_hash, index);
+}
+
+std::string GetFilenameFromKeyAndIndex(const std::string& key, int index) {
+ return GetEntryHashKeyAsHexString(key) + base::StringPrintf("_%1d", index);
+}
+
+int32 GetDataSizeFromKeyAndFileSize(const std::string& key, int64 file_size) {
+ int64 data_size = file_size - key.size() - sizeof(SimpleFileHeader) -
+ sizeof(SimpleFileEOF);
+ DCHECK_GE(implicit_cast<int64>(std::numeric_limits<int32>::max()), data_size);
+ return data_size;
+}
+
+int64 GetFileSizeFromKeyAndDataSize(const std::string& key, int32 data_size) {
+ return data_size + key.size() + sizeof(SimpleFileHeader) +
+ sizeof(SimpleFileEOF);
+}
+
+int64 GetFileOffsetFromKeyAndDataOffset(const std::string& key,
+ int data_offset) {
+ const int64 headers_size = sizeof(disk_cache::SimpleFileHeader) + key.size();
+ return headers_size + data_offset;
+}
+
+// TODO(clamy, gavinp): this should go in base
+bool GetMTime(const base::FilePath& path, base::Time* out_mtime) {
+ DCHECK(out_mtime);
+ base::PlatformFileInfo file_info;
+ if (!file_util::GetFileInfo(path, &file_info))
+ return false;
+ *out_mtime = file_info.last_modified;
+ return true;
+}
+
+} // namespace simple_backend
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/simple/simple_util.h b/chromium/net/disk_cache/simple/simple_util.h
new file mode 100644
index 00000000000..2e92b4a049d
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_util.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2013 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_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
+#define NET_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace disk_cache {
+
+namespace simple_util {
+
+NET_EXPORT_PRIVATE std::string ConvertEntryHashKeyToHexString(uint64 hash_key);
+
+// |key| is the regular cache key, such as an URL.
+// Returns the Hex ascii representation of the uint64 hash_key.
+NET_EXPORT_PRIVATE std::string GetEntryHashKeyAsHexString(
+ const std::string& key);
+
+// |key| is the regular HTTP Cache key, which is a URL.
+// Returns the hash of the key as uint64.
+NET_EXPORT_PRIVATE uint64 GetEntryHashKey(const std::string& key);
+
+// Parses the |hash_key| string into a uint64 buffer.
+// |hash_key| string must be of the form: FFFFFFFFFFFFFFFF .
+NET_EXPORT_PRIVATE bool GetEntryHashKeyFromHexString(
+ const std::string& hash_key,
+ uint64* hash_key_out);
+
+// Given a |key| for a (potential) entry in the simple backend and the |index|
+// of a stream on that entry, returns the filename in which that stream would be
+// stored.
+NET_EXPORT_PRIVATE std::string GetFilenameFromKeyAndIndex(
+ const std::string& key,
+ int index);
+
+// Same as |GetFilenameFromKeyAndIndex| above, but using a hex string.
+std::string GetFilenameFromEntryHashAndIndex(uint64 entry_hash, int index);
+
+// Given the size of a file holding a stream in the simple backend and the key
+// to an entry, returns the number of bytes in the stream.
+NET_EXPORT_PRIVATE int32 GetDataSizeFromKeyAndFileSize(const std::string& key,
+ int64 file_size);
+
+// Given the size of a stream in the simple backend and the key to an entry,
+// returns the number of bytes in the file.
+NET_EXPORT_PRIVATE int64 GetFileSizeFromKeyAndDataSize(const std::string& key,
+ int32 data_size);
+
+// Given the key to an entry, and an offset into a stream on that entry, returns
+// the file offset corresponding to |data_offset|.
+NET_EXPORT_PRIVATE int64 GetFileOffsetFromKeyAndDataOffset(
+ const std::string& key,
+ int data_offset);
+
+// Fills |out_time| with the time the file last modified time.
+// TODO(gavinp): Remove this function.
+NET_EXPORT_PRIVATE bool GetMTime(const base::FilePath& path,
+ base::Time* out_mtime);
+} // namespace simple_backend
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
diff --git a/chromium/net/disk_cache/simple/simple_util_unittest.cc b/chromium/net/disk_cache/simple/simple_util_unittest.cc
new file mode 100644
index 00000000000..35388e9f172
--- /dev/null
+++ b/chromium/net/disk_cache/simple/simple_util_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "base/port.h"
+#include "net/disk_cache/simple/simple_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using disk_cache::simple_util::ConvertEntryHashKeyToHexString;
+using disk_cache::simple_util::GetEntryHashKeyAsHexString;
+using disk_cache::simple_util::GetEntryHashKeyFromHexString;
+using disk_cache::simple_util::GetEntryHashKey;
+using disk_cache::simple_util::GetFileSizeFromKeyAndDataSize;
+using disk_cache::simple_util::GetDataSizeFromKeyAndFileSize;
+
+class SimpleUtilTest : public testing::Test {};
+
+TEST_F(SimpleUtilTest, ConvertEntryHashKeyToHexString) {
+ EXPECT_EQ("0000000005f5e0ff",
+ ConvertEntryHashKeyToHexString(GG_UINT64_C(99999999)));
+ EXPECT_EQ("7fffffffffffffff",
+ ConvertEntryHashKeyToHexString(GG_UINT64_C(9223372036854775807)));
+ EXPECT_EQ("8000000000000000",
+ ConvertEntryHashKeyToHexString(GG_UINT64_C(9223372036854775808)));
+ EXPECT_EQ("ffffffffffffffff",
+ ConvertEntryHashKeyToHexString(GG_UINT64_C(18446744073709551615)));
+}
+
+TEST_F(SimpleUtilTest, GetEntryHashKey) {
+ EXPECT_EQ("7ac408c1dff9c84b",
+ GetEntryHashKeyAsHexString("http://www.amazon.com/"));
+ EXPECT_EQ(GG_UINT64_C(0x7ac408c1dff9c84b), GetEntryHashKey("http://www.amazon.com/"));
+
+ EXPECT_EQ("9fe947998c2ccf47",
+ GetEntryHashKeyAsHexString("www.amazon.com"));
+ EXPECT_EQ(GG_UINT64_C(0x9fe947998c2ccf47), GetEntryHashKey("www.amazon.com"));
+
+ EXPECT_EQ("0d4b6b5eeea339da", GetEntryHashKeyAsHexString(""));
+ EXPECT_EQ(GG_UINT64_C(0x0d4b6b5eeea339da), GetEntryHashKey(""));
+
+ EXPECT_EQ("a68ac2ecc87dfd04", GetEntryHashKeyAsHexString("http://www.domain.com/uoQ76Kb2QL5hzaVOSAKWeX0W9LfDLqphmRXpsfHN8tgF5lCsfTxlOVWY8vFwzhsRzoNYKhUIOTc5TnUlT0vpdQflPyk2nh7vurXOj60cDnkG3nsrXMhFCsPjhcZAic2jKpF9F9TYRYQwJo81IMi6gY01RK3ZcNl8WGfqcvoZ702UIdetvR7kiaqo1czwSJCMjRFdG6EgMzgXrwE8DYMz4fWqoa1F1c1qwTCBk3yOcmGTbxsPSJK5QRyNea9IFLrBTjfE7ZlN2vZiI7adcDYJef.htm"));
+
+ EXPECT_EQ(GG_UINT64_C(0xa68ac2ecc87dfd04), GetEntryHashKey("http://www.domain.com/uoQ76Kb2QL5hzaVOSAKWeX0W9LfDLqphmRXpsfHN8tgF5lCsfTxlOVWY8vFwzhsRzoNYKhUIOTc5TnUlT0vpdQflPyk2nh7vurXOj60cDnkG3nsrXMhFCsPjhcZAic2jKpF9F9TYRYQwJo81IMi6gY01RK3ZcNl8WGfqcvoZ702UIdetvR7kiaqo1czwSJCMjRFdG6EgMzgXrwE8DYMz4fWqoa1F1c1qwTCBk3yOcmGTbxsPSJK5QRyNea9IFLrBTjfE7ZlN2vZiI7adcDYJef.htm"));
+}
+
+TEST_F(SimpleUtilTest, GetEntryHashKeyFromHexString) {
+ uint64 hash_key = 0;
+ EXPECT_TRUE(GetEntryHashKeyFromHexString("0000000005f5e0ff", &hash_key));
+ EXPECT_EQ(GG_UINT64_C(99999999), hash_key);
+
+ EXPECT_TRUE(GetEntryHashKeyFromHexString("7ffffffffffffffF", &hash_key));
+ EXPECT_EQ(GG_UINT64_C(9223372036854775807), hash_key);
+
+ EXPECT_TRUE(GetEntryHashKeyFromHexString("8000000000000000", &hash_key));
+ EXPECT_EQ(GG_UINT64_C(9223372036854775808), hash_key);
+
+ EXPECT_TRUE(GetEntryHashKeyFromHexString("FFFFFFFFFFFFFFFF", &hash_key));
+ EXPECT_EQ(GG_UINT64_C(18446744073709551615), hash_key);
+
+ // Wrong hash string size.
+ EXPECT_FALSE(GetEntryHashKeyFromHexString("FFFFFFFFFFFFFFF", &hash_key));
+
+ // Wrong hash string size.
+ EXPECT_FALSE(GetEntryHashKeyFromHexString("FFFFFFFFFFFFFFFFF", &hash_key));
+
+ EXPECT_FALSE(GetEntryHashKeyFromHexString("iwr8wglhg8*(&1231((", &hash_key));
+}
+
+TEST_F(SimpleUtilTest, SizesAndOffsets) {
+ const char key[] = "This is an example key";
+ const int data_size = 1000;
+ const int file_size = GetFileSizeFromKeyAndDataSize(key, data_size);
+ EXPECT_EQ(data_size, GetDataSizeFromKeyAndFileSize(key, file_size));
+}
diff --git a/chromium/net/disk_cache/sparse_control.cc b/chromium/net/disk_cache/sparse_control.cc
new file mode 100644
index 00000000000..b96ccc9faff
--- /dev/null
+++ b/chromium/net/disk_cache/sparse_control.cc
@@ -0,0 +1,884 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/sparse_control.h"
+
+#include "base/bind.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/file.h"
+#include "net/disk_cache/net_log_parameters.h"
+
+using base::Time;
+
+namespace {
+
+// Stream of the sparse data index.
+const int kSparseIndex = 2;
+
+// Stream of the sparse data.
+const int kSparseData = 1;
+
+// We can have up to 64k children.
+const int kMaxMapSize = 8 * 1024;
+
+// The maximum number of bytes that a child can store.
+const int kMaxEntrySize = 0x100000;
+
+// The size of each data block (tracked by the child allocation bitmap).
+const int kBlockSize = 1024;
+
+// Returns the name of a child entry given the base_name and signature of the
+// parent and the child_id.
+// If the entry is called entry_name, child entries will be named something
+// like Range_entry_name:XXX:YYY where XXX is the entry signature and YYY is the
+// number of the particular child.
+std::string GenerateChildName(const std::string& base_name, int64 signature,
+ int64 child_id) {
+ return base::StringPrintf("Range_%s:%" PRIx64 ":%" PRIx64, base_name.c_str(),
+ signature, child_id);
+}
+
+// This class deletes the children of a sparse entry.
+class ChildrenDeleter
+ : public base::RefCounted<ChildrenDeleter>,
+ public disk_cache::FileIOCallback {
+ public:
+ ChildrenDeleter(disk_cache::BackendImpl* backend, const std::string& name)
+ : backend_(backend->GetWeakPtr()), name_(name), signature_(0) {}
+
+ virtual void OnFileIOComplete(int bytes_copied) OVERRIDE;
+
+ // Two ways of deleting the children: if we have the children map, use Start()
+ // directly, otherwise pass the data address to ReadData().
+ void Start(char* buffer, int len);
+ void ReadData(disk_cache::Addr address, int len);
+
+ private:
+ friend class base::RefCounted<ChildrenDeleter>;
+ virtual ~ChildrenDeleter() {}
+
+ void DeleteChildren();
+
+ base::WeakPtr<disk_cache::BackendImpl> backend_;
+ std::string name_;
+ disk_cache::Bitmap children_map_;
+ int64 signature_;
+ scoped_ptr<char[]> buffer_;
+ DISALLOW_COPY_AND_ASSIGN(ChildrenDeleter);
+};
+
+// This is the callback of the file operation.
+void ChildrenDeleter::OnFileIOComplete(int bytes_copied) {
+ char* buffer = buffer_.release();
+ Start(buffer, bytes_copied);
+}
+
+void ChildrenDeleter::Start(char* buffer, int len) {
+ buffer_.reset(buffer);
+ if (len < static_cast<int>(sizeof(disk_cache::SparseData)))
+ return Release();
+
+ // Just copy the information from |buffer|, delete |buffer| and start deleting
+ // the child entries.
+ disk_cache::SparseData* data =
+ reinterpret_cast<disk_cache::SparseData*>(buffer);
+ signature_ = data->header.signature;
+
+ int num_bits = (len - sizeof(disk_cache::SparseHeader)) * 8;
+ children_map_.Resize(num_bits, false);
+ children_map_.SetMap(data->bitmap, num_bits / 32);
+ buffer_.reset();
+
+ DeleteChildren();
+}
+
+void ChildrenDeleter::ReadData(disk_cache::Addr address, int len) {
+ DCHECK(address.is_block_file());
+ if (!backend_.get())
+ return Release();
+
+ disk_cache::File* file(backend_->File(address));
+ if (!file)
+ return Release();
+
+ size_t file_offset = address.start_block() * address.BlockSize() +
+ disk_cache::kBlockHeaderSize;
+
+ buffer_.reset(new char[len]);
+ bool completed;
+ if (!file->Read(buffer_.get(), len, file_offset, this, &completed))
+ return Release();
+
+ if (completed)
+ OnFileIOComplete(len);
+
+ // And wait until OnFileIOComplete gets called.
+}
+
+void ChildrenDeleter::DeleteChildren() {
+ int child_id = 0;
+ if (!children_map_.FindNextSetBit(&child_id) || !backend_.get()) {
+ // We are done. Just delete this object.
+ return Release();
+ }
+ std::string child_name = GenerateChildName(name_, signature_, child_id);
+ backend_->SyncDoomEntry(child_name);
+ children_map_.Set(child_id, false);
+
+ // Post a task to delete the next child.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ChildrenDeleter::DeleteChildren, this));
+}
+
+// Returns the NetLog event type corresponding to a SparseOperation.
+net::NetLog::EventType GetSparseEventType(
+ disk_cache::SparseControl::SparseOperation operation) {
+ switch (operation) {
+ case disk_cache::SparseControl::kReadOperation:
+ return net::NetLog::TYPE_SPARSE_READ;
+ case disk_cache::SparseControl::kWriteOperation:
+ return net::NetLog::TYPE_SPARSE_WRITE;
+ case disk_cache::SparseControl::kGetRangeOperation:
+ return net::NetLog::TYPE_SPARSE_GET_RANGE;
+ default:
+ NOTREACHED();
+ return net::NetLog::TYPE_CANCELLED;
+ }
+}
+
+// Logs the end event for |operation| on a child entry. Range operations log
+// no events for each child they search through.
+void LogChildOperationEnd(const net::BoundNetLog& net_log,
+ disk_cache::SparseControl::SparseOperation operation,
+ int result) {
+ if (net_log.IsLoggingAllEvents()) {
+ net::NetLog::EventType event_type;
+ switch (operation) {
+ case disk_cache::SparseControl::kReadOperation:
+ event_type = net::NetLog::TYPE_SPARSE_READ_CHILD_DATA;
+ break;
+ case disk_cache::SparseControl::kWriteOperation:
+ event_type = net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA;
+ break;
+ case disk_cache::SparseControl::kGetRangeOperation:
+ return;
+ default:
+ NOTREACHED();
+ return;
+ }
+ net_log.EndEventWithNetErrorCode(event_type, result);
+ }
+}
+
+} // namespace.
+
+namespace disk_cache {
+
+SparseControl::SparseControl(EntryImpl* entry)
+ : entry_(entry),
+ child_(NULL),
+ operation_(kNoOperation),
+ pending_(false),
+ finished_(false),
+ init_(false),
+ range_found_(false),
+ abort_(false),
+ child_map_(child_data_.bitmap, kNumSparseBits, kNumSparseBits / 32),
+ offset_(0),
+ buf_len_(0),
+ child_offset_(0),
+ child_len_(0),
+ result_(0) {
+ memset(&sparse_header_, 0, sizeof(sparse_header_));
+ memset(&child_data_, 0, sizeof(child_data_));
+}
+
+SparseControl::~SparseControl() {
+ if (child_)
+ CloseChild();
+ if (init_)
+ WriteSparseData();
+}
+
+int SparseControl::Init() {
+ DCHECK(!init_);
+
+ // We should not have sparse data for the exposed entry.
+ if (entry_->GetDataSize(kSparseData))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Now see if there is something where we store our data.
+ int rv = net::OK;
+ int data_len = entry_->GetDataSize(kSparseIndex);
+ if (!data_len) {
+ rv = CreateSparseEntry();
+ } else {
+ rv = OpenSparseEntry(data_len);
+ }
+
+ if (rv == net::OK)
+ init_ = true;
+ return rv;
+}
+
+bool SparseControl::CouldBeSparse() const {
+ DCHECK(!init_);
+
+ if (entry_->GetDataSize(kSparseData))
+ return false;
+
+ // We don't verify the data, just see if it could be there.
+ return (entry_->GetDataSize(kSparseIndex) != 0);
+}
+
+int SparseControl::StartIO(SparseOperation op, int64 offset, net::IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback) {
+ DCHECK(init_);
+ // We don't support simultaneous IO for sparse data.
+ if (operation_ != kNoOperation)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ // We only support up to 64 GB.
+ if (offset + buf_len >= 0x1000000000LL || offset + buf_len < 0)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ DCHECK(!user_buf_.get());
+ DCHECK(user_callback_.is_null());
+
+ if (!buf && (op == kReadOperation || op == kWriteOperation))
+ return 0;
+
+ // Copy the operation parameters.
+ operation_ = op;
+ offset_ = offset;
+ user_buf_ = buf ? new net::DrainableIOBuffer(buf, buf_len) : NULL;
+ buf_len_ = buf_len;
+ user_callback_ = callback;
+
+ result_ = 0;
+ pending_ = false;
+ finished_ = false;
+ abort_ = false;
+
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ GetSparseEventType(operation_),
+ CreateNetLogSparseOperationCallback(offset_, buf_len_));
+ }
+ DoChildrenIO();
+
+ if (!pending_) {
+ // Everything was done synchronously.
+ operation_ = kNoOperation;
+ user_buf_ = NULL;
+ user_callback_.Reset();
+ return result_;
+ }
+
+ return net::ERR_IO_PENDING;
+}
+
+int SparseControl::GetAvailableRange(int64 offset, int len, int64* start) {
+ DCHECK(init_);
+ // We don't support simultaneous IO for sparse data.
+ if (operation_ != kNoOperation)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ DCHECK(start);
+
+ range_found_ = false;
+ int result = StartIO(
+ kGetRangeOperation, offset, NULL, len, CompletionCallback());
+ if (range_found_) {
+ *start = offset_;
+ return result;
+ }
+
+ // This is a failure. We want to return a valid start value in any case.
+ *start = offset;
+ return result < 0 ? result : 0; // Don't mask error codes to the caller.
+}
+
+void SparseControl::CancelIO() {
+ if (operation_ == kNoOperation)
+ return;
+ abort_ = true;
+}
+
+int SparseControl::ReadyToUse(const CompletionCallback& callback) {
+ if (!abort_)
+ return net::OK;
+
+ // We'll grab another reference to keep this object alive because we just have
+ // one extra reference due to the pending IO operation itself, but we'll
+ // release that one before invoking user_callback_.
+ entry_->AddRef(); // Balanced in DoAbortCallbacks.
+ abort_callbacks_.push_back(callback);
+ return net::ERR_IO_PENDING;
+}
+
+// Static
+void SparseControl::DeleteChildren(EntryImpl* entry) {
+ DCHECK(entry->GetEntryFlags() & PARENT_ENTRY);
+ int data_len = entry->GetDataSize(kSparseIndex);
+ if (data_len < static_cast<int>(sizeof(SparseData)) ||
+ entry->GetDataSize(kSparseData))
+ return;
+
+ int map_len = data_len - sizeof(SparseHeader);
+ if (map_len > kMaxMapSize || map_len % 4)
+ return;
+
+ char* buffer;
+ Addr address;
+ entry->GetData(kSparseIndex, &buffer, &address);
+ if (!buffer && !address.is_initialized())
+ return;
+
+ entry->net_log().AddEvent(net::NetLog::TYPE_SPARSE_DELETE_CHILDREN);
+
+ DCHECK(entry->backend_.get());
+ ChildrenDeleter* deleter = new ChildrenDeleter(entry->backend_.get(),
+ entry->GetKey());
+ // The object will self destruct when finished.
+ deleter->AddRef();
+
+ if (buffer) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ChildrenDeleter::Start, deleter, buffer, data_len));
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ChildrenDeleter::ReadData, deleter, address, data_len));
+ }
+}
+
+// We are going to start using this entry to store sparse data, so we have to
+// initialize our control info.
+int SparseControl::CreateSparseEntry() {
+ if (CHILD_ENTRY & entry_->GetEntryFlags())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ memset(&sparse_header_, 0, sizeof(sparse_header_));
+ sparse_header_.signature = Time::Now().ToInternalValue();
+ sparse_header_.magic = kIndexMagic;
+ sparse_header_.parent_key_len = entry_->GetKey().size();
+ children_map_.Resize(kNumSparseBits, true);
+
+ // Save the header. The bitmap is saved in the destructor.
+ scoped_refptr<net::IOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
+
+ int rv = entry_->WriteData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
+ CompletionCallback(), false);
+ if (rv != sizeof(sparse_header_)) {
+ DLOG(ERROR) << "Unable to save sparse_header_";
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ }
+
+ entry_->SetEntryFlags(PARENT_ENTRY);
+ return net::OK;
+}
+
+// We are opening an entry from disk. Make sure that our control data is there.
+int SparseControl::OpenSparseEntry(int data_len) {
+ if (data_len < static_cast<int>(sizeof(SparseData)))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (entry_->GetDataSize(kSparseData))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (!(PARENT_ENTRY & entry_->GetEntryFlags()))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Dont't go over board with the bitmap. 8 KB gives us offsets up to 64 GB.
+ int map_len = data_len - sizeof(sparse_header_);
+ if (map_len > kMaxMapSize || map_len % 4)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ scoped_refptr<net::IOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
+
+ // Read header.
+ int rv = entry_->ReadData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
+ CompletionCallback());
+ if (rv != static_cast<int>(sizeof(sparse_header_)))
+ return net::ERR_CACHE_READ_FAILURE;
+
+ // The real validation should be performed by the caller. This is just to
+ // double check.
+ if (sparse_header_.magic != kIndexMagic ||
+ sparse_header_.parent_key_len !=
+ static_cast<int>(entry_->GetKey().size()))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Read the actual bitmap.
+ buf = new net::IOBuffer(map_len);
+ rv = entry_->ReadData(kSparseIndex, sizeof(sparse_header_), buf.get(),
+ map_len, CompletionCallback());
+ if (rv != map_len)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ // Grow the bitmap to the current size and copy the bits.
+ children_map_.Resize(map_len * 8, false);
+ children_map_.SetMap(reinterpret_cast<uint32*>(buf->data()), map_len);
+ return net::OK;
+}
+
+bool SparseControl::OpenChild() {
+ DCHECK_GE(result_, 0);
+
+ std::string key = GenerateChildKey();
+ if (child_) {
+ // Keep using the same child or open another one?.
+ if (key == child_->GetKey())
+ return true;
+ CloseChild();
+ }
+
+ // See if we are tracking this child.
+ if (!ChildPresent())
+ return ContinueWithoutChild(key);
+
+ if (!entry_->backend_.get())
+ return false;
+
+ child_ = entry_->backend_->OpenEntryImpl(key);
+ if (!child_)
+ return ContinueWithoutChild(key);
+
+ EntryImpl* child = static_cast<EntryImpl*>(child_);
+ if (!(CHILD_ENTRY & child->GetEntryFlags()) ||
+ child->GetDataSize(kSparseIndex) <
+ static_cast<int>(sizeof(child_data_)))
+ return KillChildAndContinue(key, false);
+
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ // Read signature.
+ int rv = child_->ReadData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback());
+ if (rv != sizeof(child_data_))
+ return KillChildAndContinue(key, true); // This is a fatal failure.
+
+ if (child_data_.header.signature != sparse_header_.signature ||
+ child_data_.header.magic != kIndexMagic)
+ return KillChildAndContinue(key, false);
+
+ if (child_data_.header.last_block_len < 0 ||
+ child_data_.header.last_block_len > kBlockSize) {
+ // Make sure these values are always within range.
+ child_data_.header.last_block_len = 0;
+ child_data_.header.last_block = -1;
+ }
+
+ return true;
+}
+
+void SparseControl::CloseChild() {
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ // Save the allocation bitmap before closing the child entry.
+ int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback(), false);
+ if (rv != sizeof(child_data_))
+ DLOG(ERROR) << "Failed to save child data";
+ child_->Release();
+ child_ = NULL;
+}
+
+std::string SparseControl::GenerateChildKey() {
+ return GenerateChildName(entry_->GetKey(), sparse_header_.signature,
+ offset_ >> 20);
+}
+
+// We are deleting the child because something went wrong.
+bool SparseControl::KillChildAndContinue(const std::string& key, bool fatal) {
+ SetChildBit(false);
+ child_->DoomImpl();
+ child_->Release();
+ child_ = NULL;
+ if (fatal) {
+ result_ = net::ERR_CACHE_READ_FAILURE;
+ return false;
+ }
+ return ContinueWithoutChild(key);
+}
+
+// We were not able to open this child; see what we can do.
+bool SparseControl::ContinueWithoutChild(const std::string& key) {
+ if (kReadOperation == operation_)
+ return false;
+ if (kGetRangeOperation == operation_)
+ return true;
+
+ if (!entry_->backend_.get())
+ return false;
+
+ child_ = entry_->backend_->CreateEntryImpl(key);
+ if (!child_) {
+ child_ = NULL;
+ result_ = net::ERR_CACHE_READ_FAILURE;
+ return false;
+ }
+ // Write signature.
+ InitChildData();
+ return true;
+}
+
+bool SparseControl::ChildPresent() {
+ int child_bit = static_cast<int>(offset_ >> 20);
+ if (children_map_.Size() <= child_bit)
+ return false;
+
+ return children_map_.Get(child_bit);
+}
+
+void SparseControl::SetChildBit(bool value) {
+ int child_bit = static_cast<int>(offset_ >> 20);
+
+ // We may have to increase the bitmap of child entries.
+ if (children_map_.Size() <= child_bit)
+ children_map_.Resize(Bitmap::RequiredArraySize(child_bit + 1) * 32, true);
+
+ children_map_.Set(child_bit, value);
+}
+
+void SparseControl::WriteSparseData() {
+ scoped_refptr<net::IOBuffer> buf(new net::WrappedIOBuffer(
+ reinterpret_cast<const char*>(children_map_.GetMap())));
+
+ int len = children_map_.ArraySize() * 4;
+ int rv = entry_->WriteData(kSparseIndex, sizeof(sparse_header_), buf.get(),
+ len, CompletionCallback(), false);
+ if (rv != len) {
+ DLOG(ERROR) << "Unable to save sparse map";
+ }
+}
+
+bool SparseControl::VerifyRange() {
+ DCHECK_GE(result_, 0);
+
+ child_offset_ = static_cast<int>(offset_) & (kMaxEntrySize - 1);
+ child_len_ = std::min(buf_len_, kMaxEntrySize - child_offset_);
+
+ // We can write to (or get info from) anywhere in this child.
+ if (operation_ != kReadOperation)
+ return true;
+
+ // Check that there are no holes in this range.
+ int last_bit = (child_offset_ + child_len_ + 1023) >> 10;
+ int start = child_offset_ >> 10;
+ if (child_map_.FindNextBit(&start, last_bit, false)) {
+ // Something is not here.
+ DCHECK_GE(child_data_.header.last_block_len, 0);
+ DCHECK_LT(child_data_.header.last_block_len, kMaxEntrySize);
+ int partial_block_len = PartialBlockLength(start);
+ if (start == child_offset_ >> 10) {
+ // It looks like we don't have anything.
+ if (partial_block_len <= (child_offset_ & (kBlockSize - 1)))
+ return false;
+ }
+
+ // We have the first part.
+ child_len_ = (start << 10) - child_offset_;
+ if (partial_block_len) {
+ // We may have a few extra bytes.
+ child_len_ = std::min(child_len_ + partial_block_len, buf_len_);
+ }
+ // There is no need to read more after this one.
+ buf_len_ = child_len_;
+ }
+ return true;
+}
+
+void SparseControl::UpdateRange(int result) {
+ if (result <= 0 || operation_ != kWriteOperation)
+ return;
+
+ DCHECK_GE(child_data_.header.last_block_len, 0);
+ DCHECK_LT(child_data_.header.last_block_len, kMaxEntrySize);
+
+ // Write the bitmap.
+ int first_bit = child_offset_ >> 10;
+ int block_offset = child_offset_ & (kBlockSize - 1);
+ if (block_offset && (child_data_.header.last_block != first_bit ||
+ child_data_.header.last_block_len < block_offset)) {
+ // The first block is not completely filled; ignore it.
+ first_bit++;
+ }
+
+ int last_bit = (child_offset_ + result) >> 10;
+ block_offset = (child_offset_ + result) & (kBlockSize - 1);
+
+ // This condition will hit with the following criteria:
+ // 1. The first byte doesn't follow the last write.
+ // 2. The first byte is in the middle of a block.
+ // 3. The first byte and the last byte are in the same block.
+ if (first_bit > last_bit)
+ return;
+
+ if (block_offset && !child_map_.Get(last_bit)) {
+ // The last block is not completely filled; save it for later.
+ child_data_.header.last_block = last_bit;
+ child_data_.header.last_block_len = block_offset;
+ } else {
+ child_data_.header.last_block = -1;
+ }
+
+ child_map_.SetRange(first_bit, last_bit, true);
+}
+
+int SparseControl::PartialBlockLength(int block_index) const {
+ if (block_index == child_data_.header.last_block)
+ return child_data_.header.last_block_len;
+
+ // This may be the last stored index.
+ int entry_len = child_->GetDataSize(kSparseData);
+ if (block_index == entry_len >> 10)
+ return entry_len & (kBlockSize - 1);
+
+ // This is really empty.
+ return 0;
+}
+
+void SparseControl::InitChildData() {
+ // We know the real type of child_.
+ EntryImpl* child = static_cast<EntryImpl*>(child_);
+ child->SetEntryFlags(CHILD_ENTRY);
+
+ memset(&child_data_, 0, sizeof(child_data_));
+ child_data_.header = sparse_header_;
+
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback(), false);
+ if (rv != sizeof(child_data_))
+ DLOG(ERROR) << "Failed to save child data";
+ SetChildBit(true);
+}
+
+void SparseControl::DoChildrenIO() {
+ while (DoChildIO()) continue;
+
+ // Range operations are finished synchronously, often without setting
+ // |finished_| to true.
+ if (kGetRangeOperation == operation_ &&
+ entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().EndEvent(
+ net::NetLog::TYPE_SPARSE_GET_RANGE,
+ CreateNetLogGetAvailableRangeResultCallback(offset_, result_));
+ }
+ if (finished_) {
+ if (kGetRangeOperation != operation_ &&
+ entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().EndEvent(GetSparseEventType(operation_));
+ }
+ if (pending_)
+ DoUserCallback(); // Don't touch this object after this point.
+ }
+}
+
+bool SparseControl::DoChildIO() {
+ finished_ = true;
+ if (!buf_len_ || result_ < 0)
+ return false;
+
+ if (!OpenChild())
+ return false;
+
+ if (!VerifyRange())
+ return false;
+
+ // We have more work to do. Let's not trigger a callback to the caller.
+ finished_ = false;
+ CompletionCallback callback;
+ if (!user_callback_.is_null()) {
+ callback =
+ base::Bind(&SparseControl::OnChildIOCompleted, base::Unretained(this));
+ }
+
+ int rv = 0;
+ switch (operation_) {
+ case kReadOperation:
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ net::NetLog::TYPE_SPARSE_READ_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
+ child_len_));
+ }
+ rv = child_->ReadDataImpl(kSparseData, child_offset_, user_buf_.get(),
+ child_len_, callback);
+ break;
+ case kWriteOperation:
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
+ child_len_));
+ }
+ rv = child_->WriteDataImpl(kSparseData, child_offset_, user_buf_.get(),
+ child_len_, callback, false);
+ break;
+ case kGetRangeOperation:
+ rv = DoGetAvailableRange();
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (rv == net::ERR_IO_PENDING) {
+ if (!pending_) {
+ pending_ = true;
+ // The child will protect himself against closing the entry while IO is in
+ // progress. However, this entry can still be closed, and that would not
+ // be a good thing for us, so we increase the refcount until we're
+ // finished doing sparse stuff.
+ entry_->AddRef(); // Balanced in DoUserCallback.
+ }
+ return false;
+ }
+ if (!rv)
+ return false;
+
+ DoChildIOCompleted(rv);
+ return true;
+}
+
+int SparseControl::DoGetAvailableRange() {
+ if (!child_)
+ return child_len_; // Move on to the next child.
+
+ // Check that there are no holes in this range.
+ int last_bit = (child_offset_ + child_len_ + 1023) >> 10;
+ int start = child_offset_ >> 10;
+ int partial_start_bytes = PartialBlockLength(start);
+ int found = start;
+ int bits_found = child_map_.FindBits(&found, last_bit, true);
+
+ // We don't care if there is a partial block in the middle of the range.
+ int block_offset = child_offset_ & (kBlockSize - 1);
+ if (!bits_found && partial_start_bytes <= block_offset)
+ return child_len_;
+
+ // We are done. Just break the loop and reset result_ to our real result.
+ range_found_ = true;
+
+ // found now points to the first 1. Lets see if we have zeros before it.
+ int empty_start = std::max((found << 10) - child_offset_, 0);
+
+ int bytes_found = bits_found << 10;
+ bytes_found += PartialBlockLength(found + bits_found);
+
+ if (start == found)
+ bytes_found -= block_offset;
+
+ // If the user is searching past the end of this child, bits_found is the
+ // right result; otherwise, we have some empty space at the start of this
+ // query that we have to subtract from the range that we searched.
+ result_ = std::min(bytes_found, child_len_ - empty_start);
+
+ if (!bits_found) {
+ result_ = std::min(partial_start_bytes - block_offset, child_len_);
+ empty_start = 0;
+ }
+
+ // Only update offset_ when this query found zeros at the start.
+ if (empty_start)
+ offset_ += empty_start;
+
+ // This will actually break the loop.
+ buf_len_ = 0;
+ return 0;
+}
+
+void SparseControl::DoChildIOCompleted(int result) {
+ LogChildOperationEnd(entry_->net_log(), operation_, result);
+ if (result < 0) {
+ // We fail the whole operation if we encounter an error.
+ result_ = result;
+ return;
+ }
+
+ UpdateRange(result);
+
+ result_ += result;
+ offset_ += result;
+ buf_len_ -= result;
+
+ // We'll be reusing the user provided buffer for the next chunk.
+ if (buf_len_ && user_buf_.get())
+ user_buf_->DidConsume(result);
+}
+
+void SparseControl::OnChildIOCompleted(int result) {
+ DCHECK_NE(net::ERR_IO_PENDING, result);
+ DoChildIOCompleted(result);
+
+ if (abort_) {
+ // We'll return the current result of the operation, which may be less than
+ // the bytes to read or write, but the user cancelled the operation.
+ abort_ = false;
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().AddEvent(net::NetLog::TYPE_CANCELLED);
+ entry_->net_log().EndEvent(GetSparseEventType(operation_));
+ }
+ // We have an indirect reference to this object for every callback so if
+ // there is only one callback, we may delete this object before reaching
+ // DoAbortCallbacks.
+ bool has_abort_callbacks = !abort_callbacks_.empty();
+ DoUserCallback();
+ if (has_abort_callbacks)
+ DoAbortCallbacks();
+ return;
+ }
+
+ // We are running a callback from the message loop. It's time to restart what
+ // we were doing before.
+ DoChildrenIO();
+}
+
+void SparseControl::DoUserCallback() {
+ DCHECK(!user_callback_.is_null());
+ CompletionCallback cb = user_callback_;
+ user_callback_.Reset();
+ user_buf_ = NULL;
+ pending_ = false;
+ operation_ = kNoOperation;
+ int rv = result_;
+ entry_->Release(); // Don't touch object after this line.
+ cb.Run(rv);
+}
+
+void SparseControl::DoAbortCallbacks() {
+ for (size_t i = 0; i < abort_callbacks_.size(); i++) {
+ // Releasing all references to entry_ may result in the destruction of this
+ // object so we should not be touching it after the last Release().
+ CompletionCallback cb = abort_callbacks_[i];
+ if (i == abort_callbacks_.size() - 1)
+ abort_callbacks_.clear();
+
+ entry_->Release(); // Don't touch object after this line.
+ cb.Run(net::OK);
+ }
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/sparse_control.h b/chromium/net/disk_cache/sparse_control.h
new file mode 100644
index 00000000000..a018e18742e
--- /dev/null
+++ b/chromium/net/disk_cache/sparse_control.h
@@ -0,0 +1,177 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_SPARSE_CONTROL_H_
+#define NET_DISK_CACHE_SPARSE_CONTROL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/completion_callback.h"
+#include "net/disk_cache/bitmap.h"
+#include "net/disk_cache/disk_format.h"
+
+namespace net {
+class IOBuffer;
+class DrainableIOBuffer;
+}
+
+namespace disk_cache {
+
+class Entry;
+class EntryImpl;
+
+// This class provides support for the sparse capabilities of the disk cache.
+// Basically, sparse IO is directed from EntryImpl to this class, and we split
+// the operation into multiple small pieces, sending each one to the
+// appropriate entry. An instance of this class is asociated with each entry
+// used directly for sparse operations (the entry passed in to the constructor).
+class SparseControl {
+ public:
+ typedef net::CompletionCallback CompletionCallback;
+
+ // The operation to perform.
+ enum SparseOperation {
+ kNoOperation,
+ kReadOperation,
+ kWriteOperation,
+ kGetRangeOperation
+ };
+
+ explicit SparseControl(EntryImpl* entry);
+ ~SparseControl();
+
+ // Initializes the object for the current entry. If this entry already stores
+ // sparse data, or can be used to do it, it updates the relevant information
+ // on disk and returns net::OK. Otherwise it returns a net error code.
+ int Init();
+
+ // Performs a quick test to see if the entry is sparse or not, without
+ // generating disk IO (so the answer provided is only a best effort).
+ bool CouldBeSparse() const;
+
+ // Performs an actual sparse read or write operation for this entry. |op| is
+ // the operation to perform, |offset| is the desired sparse offset, |buf| and
+ // |buf_len| specify the actual data to use and |callback| is the callback
+ // to use for asynchronous operations. See the description of the Read /
+ // WriteSparseData for details about the arguments. The return value is the
+ // number of bytes read or written, or a net error code.
+ int StartIO(SparseOperation op, int64 offset, net::IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback);
+
+ // Implements Entry::GetAvailableRange().
+ int GetAvailableRange(int64 offset, int len, int64* start);
+
+ // Cancels the current sparse operation (if any).
+ void CancelIO();
+
+ // Returns OK if the entry can be used for new IO or ERR_IO_PENDING if we are
+ // busy. If the entry is busy, we'll invoke the callback when we are ready
+ // again. See disk_cache::Entry::ReadyToUse() for more info.
+ int ReadyToUse(const CompletionCallback& completion_callback);
+
+ // Deletes the children entries of |entry|.
+ static void DeleteChildren(EntryImpl* entry);
+
+ private:
+ // Creates a new sparse entry or opens an aready created entry from disk.
+ // These methods just read / write the required info from disk for the current
+ // entry, and verify that everything is correct. The return value is a net
+ // error code.
+ int CreateSparseEntry();
+ int OpenSparseEntry(int data_len);
+
+ // Opens and closes a child entry. A child entry is a regular EntryImpl object
+ // with a key derived from the key of the resource to store and the range
+ // stored by that child.
+ bool OpenChild();
+ void CloseChild();
+ std::string GenerateChildKey();
+
+ // Deletes the current child and continues the current operation (open).
+ bool KillChildAndContinue(const std::string& key, bool fatal);
+
+ // Continues the current operation (open) without a current child.
+ bool ContinueWithoutChild(const std::string& key);
+
+ // Returns true if the required child is tracked by the parent entry, i.e. it
+ // was already created.
+ bool ChildPresent();
+
+ // Sets the bit for the current child to the provided |value|. In other words,
+ // starts or stops tracking this child.
+ void SetChildBit(bool value);
+
+ // Writes to disk the tracking information for this entry.
+ void WriteSparseData();
+
+ // Verify that the range to be accessed for the current child is appropriate.
+ // Returns false if an error is detected or there is no need to perform the
+ // current IO operation (for instance if the required range is not stored by
+ // the child).
+ bool VerifyRange();
+
+ // Updates the contents bitmap for the current range, based on the result of
+ // the current operation.
+ void UpdateRange(int result);
+
+ // Returns the number of bytes stored at |block_index|, if its allocation-bit
+ // is off (because it is not completely filled).
+ int PartialBlockLength(int block_index) const;
+
+ // Initializes the sparse info for the current child.
+ void InitChildData();
+
+ // Iterates through all the children needed to complete the current operation.
+ void DoChildrenIO();
+
+ // Performs a single operation with the current child. Returns true when we
+ // should move on to the next child and false when we should interrupt our
+ // work.
+ bool DoChildIO();
+
+ // Performs the required work for GetAvailableRange for one child.
+ int DoGetAvailableRange();
+
+ // Performs the required work after a single IO operations finishes.
+ void DoChildIOCompleted(int result);
+
+ // Invoked by the callback of asynchronous operations.
+ void OnChildIOCompleted(int result);
+
+ // Reports to the user that we are done.
+ void DoUserCallback();
+ void DoAbortCallbacks();
+
+ EntryImpl* entry_; // The sparse entry.
+ EntryImpl* child_; // The current child entry.
+ SparseOperation operation_;
+ bool pending_; // True if any child IO operation returned pending.
+ bool finished_;
+ bool init_;
+ bool range_found_; // True if GetAvailableRange found something.
+ bool abort_; // True if we should abort the current operation ASAP.
+
+ SparseHeader sparse_header_; // Data about the children of entry_.
+ Bitmap children_map_; // The actual bitmap of children.
+ SparseData child_data_; // Parent and allocation map of child_.
+ Bitmap child_map_; // The allocation map as a bitmap.
+
+ CompletionCallback user_callback_;
+ std::vector<CompletionCallback> abort_callbacks_;
+ int64 offset_; // Current sparse offset.
+ scoped_refptr<net::DrainableIOBuffer> user_buf_;
+ int buf_len_; // Bytes to read or write.
+ int child_offset_; // Offset to use for the current child.
+ int child_len_; // Bytes to read or write for this child.
+ int result_;
+
+ DISALLOW_COPY_AND_ASSIGN(SparseControl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SPARSE_CONTROL_H_
diff --git a/chromium/net/disk_cache/stats.cc b/chromium/net/disk_cache/stats.cc
new file mode 100644
index 00000000000..33d9d1c534a
--- /dev/null
+++ b/chromium/net/disk_cache/stats.cc
@@ -0,0 +1,309 @@
+// Copyright (c) 2011 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/disk_cache/stats.h"
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+const int32 kDiskSignature = 0xF01427E0;
+
+struct OnDiskStats {
+ int32 signature;
+ int size;
+ int data_sizes[disk_cache::Stats::kDataSizesLength];
+ int64 counters[disk_cache::Stats::MAX_COUNTER];
+};
+COMPILE_ASSERT(sizeof(OnDiskStats) < 512, needs_more_than_2_blocks);
+
+// Returns the "floor" (as opposed to "ceiling") of log base 2 of number.
+int LogBase2(int32 number) {
+ unsigned int value = static_cast<unsigned int>(number);
+ const unsigned int mask[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};
+ const unsigned int s[] = {1, 2, 4, 8, 16};
+
+ unsigned int result = 0;
+ for (int i = 4; i >= 0; i--) {
+ if (value & mask[i]) {
+ value >>= s[i];
+ result |= s[i];
+ }
+ }
+ return static_cast<int>(result);
+}
+
+// WARNING: Add new stats only at the end, or change LoadStats().
+static const char* kCounterNames[] = {
+ "Open miss",
+ "Open hit",
+ "Create miss",
+ "Create hit",
+ "Resurrect hit",
+ "Create error",
+ "Trim entry",
+ "Doom entry",
+ "Doom cache",
+ "Invalid entry",
+ "Open entries",
+ "Max entries",
+ "Timer",
+ "Read data",
+ "Write data",
+ "Open rankings",
+ "Get rankings",
+ "Fatal error",
+ "Last report",
+ "Last report timer",
+ "Doom recent entries",
+ "unused"
+};
+COMPILE_ASSERT(arraysize(kCounterNames) == disk_cache::Stats::MAX_COUNTER,
+ update_the_names);
+
+} // namespace
+
+namespace disk_cache {
+
+bool VerifyStats(OnDiskStats* stats) {
+ if (stats->signature != kDiskSignature)
+ return false;
+
+ // We don't want to discard the whole cache every time we have one extra
+ // counter; we keep old data if we can.
+ if (static_cast<unsigned int>(stats->size) > sizeof(*stats)) {
+ memset(stats, 0, sizeof(*stats));
+ stats->signature = kDiskSignature;
+ } else if (static_cast<unsigned int>(stats->size) != sizeof(*stats)) {
+ size_t delta = sizeof(*stats) - static_cast<unsigned int>(stats->size);
+ memset(reinterpret_cast<char*>(stats) + stats->size, 0, delta);
+ stats->size = sizeof(*stats);
+ }
+
+ return true;
+}
+
+Stats::Stats() : size_histogram_(NULL) {
+}
+
+Stats::~Stats() {
+}
+
+bool Stats::Init(void* data, int num_bytes, Addr address) {
+ OnDiskStats local_stats;
+ OnDiskStats* stats = &local_stats;
+ if (!num_bytes) {
+ memset(stats, 0, sizeof(local_stats));
+ local_stats.signature = kDiskSignature;
+ local_stats.size = sizeof(local_stats);
+ } else if (num_bytes >= static_cast<int>(sizeof(*stats))) {
+ stats = reinterpret_cast<OnDiskStats*>(data);
+ if (!VerifyStats(stats))
+ return false;
+ } else {
+ return false;
+ }
+
+ storage_addr_ = address;
+
+ memcpy(data_sizes_, stats->data_sizes, sizeof(data_sizes_));
+ memcpy(counters_, stats->counters, sizeof(counters_));
+
+ // Clean up old value.
+ SetCounter(UNUSED, 0);
+ return true;
+}
+
+void Stats::InitSizeHistogram() {
+ // It seems impossible to support this histogram for more than one
+ // simultaneous objects with the current infrastructure.
+ static bool first_time = true;
+ if (first_time) {
+ first_time = false;
+ if (!size_histogram_) {
+ // Stats may be reused when the cache is re-created, but we want only one
+ // histogram at any given time.
+ size_histogram_ = StatsHistogram::FactoryGet("DiskCache.SizeStats", this);
+ }
+ }
+}
+
+int Stats::StorageSize() {
+ // If we have more than 512 bytes of counters, change kDiskSignature so we
+ // don't overwrite something else (LoadStats must fail).
+ COMPILE_ASSERT(sizeof(OnDiskStats) <= 256 * 2, use_more_blocks);
+ return 256 * 2;
+}
+
+void Stats::ModifyStorageStats(int32 old_size, int32 new_size) {
+ // We keep a counter of the data block size on an array where each entry is
+ // the adjusted log base 2 of the size. The first entry counts blocks of 256
+ // bytes, the second blocks up to 512 bytes, etc. With 20 entries, the last
+ // one stores entries of more than 64 MB
+ int new_index = GetStatsBucket(new_size);
+ int old_index = GetStatsBucket(old_size);
+
+ if (new_size)
+ data_sizes_[new_index]++;
+
+ if (old_size)
+ data_sizes_[old_index]--;
+}
+
+void Stats::OnEvent(Counters an_event) {
+ DCHECK(an_event >= MIN_COUNTER && an_event < MAX_COUNTER);
+ counters_[an_event]++;
+}
+
+void Stats::SetCounter(Counters counter, int64 value) {
+ DCHECK(counter >= MIN_COUNTER && counter < MAX_COUNTER);
+ counters_[counter] = value;
+}
+
+int64 Stats::GetCounter(Counters counter) const {
+ DCHECK(counter >= MIN_COUNTER && counter < MAX_COUNTER);
+ return counters_[counter];
+}
+
+void Stats::GetItems(StatsItems* items) {
+ std::pair<std::string, std::string> item;
+ for (int i = 0; i < kDataSizesLength; i++) {
+ item.first = base::StringPrintf("Size%02d", i);
+ item.second = base::StringPrintf("0x%08x", data_sizes_[i]);
+ items->push_back(item);
+ }
+
+ for (int i = MIN_COUNTER; i < MAX_COUNTER; i++) {
+ item.first = kCounterNames[i];
+ item.second = base::StringPrintf("0x%" PRIx64, counters_[i]);
+ items->push_back(item);
+ }
+}
+
+int Stats::GetHitRatio() const {
+ return GetRatio(OPEN_HIT, OPEN_MISS);
+}
+
+int Stats::GetResurrectRatio() const {
+ return GetRatio(RESURRECT_HIT, CREATE_HIT);
+}
+
+void Stats::ResetRatios() {
+ SetCounter(OPEN_HIT, 0);
+ SetCounter(OPEN_MISS, 0);
+ SetCounter(RESURRECT_HIT, 0);
+ SetCounter(CREATE_HIT, 0);
+}
+
+int Stats::GetLargeEntriesSize() {
+ int total = 0;
+ // data_sizes_[20] stores values between 512 KB and 1 MB (see comment before
+ // GetStatsBucket()).
+ for (int bucket = 20; bucket < kDataSizesLength; bucket++)
+ total += data_sizes_[bucket] * GetBucketRange(bucket);
+
+ return total;
+}
+
+int Stats::SerializeStats(void* data, int num_bytes, Addr* address) {
+ OnDiskStats* stats = reinterpret_cast<OnDiskStats*>(data);
+ if (num_bytes < static_cast<int>(sizeof(*stats)))
+ return 0;
+
+ stats->signature = kDiskSignature;
+ stats->size = sizeof(*stats);
+ memcpy(stats->data_sizes, data_sizes_, sizeof(data_sizes_));
+ memcpy(stats->counters, counters_, sizeof(counters_));
+
+ *address = storage_addr_;
+ return sizeof(*stats);
+}
+
+int Stats::GetBucketRange(size_t i) const {
+ if (i < 2)
+ return static_cast<int>(1024 * i);
+
+ if (i < 12)
+ return static_cast<int>(2048 * (i - 1));
+
+ if (i < 17)
+ return static_cast<int>(4096 * (i - 11)) + 20 * 1024;
+
+ int n = 64 * 1024;
+ if (i > static_cast<size_t>(kDataSizesLength)) {
+ NOTREACHED();
+ i = kDataSizesLength;
+ }
+
+ i -= 17;
+ n <<= i;
+ return n;
+}
+
+void Stats::Snapshot(base::HistogramSamples* samples) const {
+ for (int i = 0; i < kDataSizesLength; i++) {
+ int count = data_sizes_[i];
+ if (count < 0)
+ count = 0;
+ samples->Accumulate(GetBucketRange(i), count);
+ }
+}
+
+// The array will be filled this way:
+// index size
+// 0 [0, 1024)
+// 1 [1024, 2048)
+// 2 [2048, 4096)
+// 3 [4K, 6K)
+// ...
+// 10 [18K, 20K)
+// 11 [20K, 24K)
+// 12 [24k, 28K)
+// ...
+// 15 [36k, 40K)
+// 16 [40k, 64K)
+// 17 [64K, 128K)
+// 18 [128K, 256K)
+// ...
+// 23 [4M, 8M)
+// 24 [8M, 16M)
+// 25 [16M, 32M)
+// 26 [32M, 64M)
+// 27 [64M, ...)
+int Stats::GetStatsBucket(int32 size) {
+ if (size < 1024)
+ return 0;
+
+ // 10 slots more, until 20K.
+ if (size < 20 * 1024)
+ return size / 2048 + 1;
+
+ // 5 slots more, from 20K to 40K.
+ if (size < 40 * 1024)
+ return (size - 20 * 1024) / 4096 + 11;
+
+ // From this point on, use a logarithmic scale.
+ int result = LogBase2(size) + 1;
+
+ COMPILE_ASSERT(kDataSizesLength > 16, update_the_scale);
+ if (result >= kDataSizesLength)
+ result = kDataSizesLength - 1;
+
+ return result;
+}
+
+int Stats::GetRatio(Counters hit, Counters miss) const {
+ int64 ratio = GetCounter(hit) * 100;
+ if (!ratio)
+ return 0;
+
+ ratio /= (GetCounter(hit) + GetCounter(miss));
+ return static_cast<int>(ratio);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/stats.h b/chromium/net/disk_cache/stats.h
new file mode 100644
index 00000000000..440334af207
--- /dev/null
+++ b/chromium/net/disk_cache/stats.h
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_STATS_H_
+#define NET_DISK_CACHE_STATS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/disk_cache/addr.h"
+#include "net/disk_cache/stats_histogram.h"
+
+namespace base {
+class HistogramSamples;
+} // namespace base
+
+namespace disk_cache {
+
+typedef std::vector<std::pair<std::string, std::string> > StatsItems;
+
+// This class stores cache-specific usage information, for tunning purposes.
+class Stats {
+ public:
+ static const int kDataSizesLength = 28;
+ enum Counters {
+ MIN_COUNTER = 0,
+ OPEN_MISS = MIN_COUNTER,
+ OPEN_HIT,
+ CREATE_MISS,
+ CREATE_HIT,
+ RESURRECT_HIT,
+ CREATE_ERROR,
+ TRIM_ENTRY,
+ DOOM_ENTRY,
+ DOOM_CACHE,
+ INVALID_ENTRY,
+ OPEN_ENTRIES, // Average number of open entries.
+ MAX_ENTRIES, // Maximum number of open entries.
+ TIMER,
+ READ_DATA,
+ WRITE_DATA,
+ OPEN_RANKINGS, // An entry has to be read just to modify rankings.
+ GET_RANKINGS, // We got the ranking info without reading the whole entry.
+ FATAL_ERROR,
+ LAST_REPORT, // Time of the last time we sent a report.
+ LAST_REPORT_TIMER, // Timer count of the last time we sent a report.
+ DOOM_RECENT, // The cache was partially cleared.
+ UNUSED, // Was: ga.js was evicted from the cache.
+ MAX_COUNTER
+ };
+
+ Stats();
+ ~Stats();
+
+ // Initializes this object with |data| from disk.
+ bool Init(void* data, int num_bytes, Addr address);
+
+ // Generates a size distribution histogram.
+ void InitSizeHistogram();
+
+ // Returns the number of bytes needed to store the stats on disk.
+ int StorageSize();
+
+ // Tracks changes to the stoage space used by an entry.
+ void ModifyStorageStats(int32 old_size, int32 new_size);
+
+ // Tracks general events.
+ void OnEvent(Counters an_event);
+ void SetCounter(Counters counter, int64 value);
+ int64 GetCounter(Counters counter) const;
+
+ void GetItems(StatsItems* items);
+ int GetHitRatio() const;
+ int GetResurrectRatio() const;
+ void ResetRatios();
+
+ // Returns the lower bound of the space used by entries bigger than 512 KB.
+ int GetLargeEntriesSize();
+
+ // Writes the stats into |data|, to be stored at the given cache address.
+ // Returns the number of bytes copied.
+ int SerializeStats(void* data, int num_bytes, Addr* address);
+
+ // Support for StatsHistograms. Together, these methods allow StatsHistograms
+ // to take a snapshot of the data_sizes_ as the histogram data.
+ int GetBucketRange(size_t i) const;
+ void Snapshot(base::HistogramSamples* samples) const;
+
+ private:
+ int GetStatsBucket(int32 size);
+ int GetRatio(Counters hit, Counters miss) const;
+
+ Addr storage_addr_;
+ int data_sizes_[kDataSizesLength];
+ int64 counters_[MAX_COUNTER];
+ StatsHistogram* size_histogram_;
+
+ DISALLOW_COPY_AND_ASSIGN(Stats);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STATS_H_
diff --git a/chromium/net/disk_cache/stats_histogram.cc b/chromium/net/disk_cache/stats_histogram.cc
new file mode 100644
index 00000000000..33adfeaae49
--- /dev/null
+++ b/chromium/net/disk_cache/stats_histogram.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/stats_histogram.h"
+
+#include "base/debug/leak_annotations.h"
+#include "base/logging.h"
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/sample_vector.h"
+#include "base/metrics/statistics_recorder.h"
+#include "net/disk_cache/stats.h"
+
+namespace disk_cache {
+
+using base::BucketRanges;
+using base::Histogram;
+using base::HistogramSamples;
+using base::SampleVector;
+using base::StatisticsRecorder;
+
+StatsHistogram::StatsHistogram(const std::string& name,
+ Sample minimum,
+ Sample maximum,
+ const BucketRanges* ranges,
+ const Stats* stats)
+ : Histogram(name, minimum, maximum, ranges),
+ stats_(stats) {}
+
+StatsHistogram::~StatsHistogram() {}
+
+// static
+void StatsHistogram::InitializeBucketRanges(const Stats* stats,
+ BucketRanges* ranges) {
+ for (size_t i = 0; i < ranges->size(); ++i) {
+ ranges->set_range(i, stats->GetBucketRange(i));
+ }
+ ranges->ResetChecksum();
+}
+
+StatsHistogram* StatsHistogram::FactoryGet(const std::string& name,
+ const Stats* stats) {
+ Sample minimum = 1;
+ Sample maximum = disk_cache::Stats::kDataSizesLength - 1;
+ size_t bucket_count = disk_cache::Stats::kDataSizesLength;
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ if (!histogram) {
+ DCHECK(stats);
+
+ // To avoid racy destruction at shutdown, the following will be leaked.
+ BucketRanges* ranges = new BucketRanges(bucket_count + 1);
+ InitializeBucketRanges(stats, ranges);
+ const BucketRanges* registered_ranges =
+ StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges);
+
+ // To avoid racy destruction at shutdown, the following will be leaked.
+ StatsHistogram* stats_histogram =
+ new StatsHistogram(name, minimum, maximum, registered_ranges, stats);
+ stats_histogram->SetFlags(kUmaTargetedHistogramFlag);
+ histogram = StatisticsRecorder::RegisterOrDeleteDuplicate(stats_histogram);
+ }
+
+ DCHECK(base::HISTOGRAM == histogram->GetHistogramType());
+ DCHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count));
+
+ // We're preparing for an otherwise unsafe upcast by ensuring we have the
+ // proper class type.
+ StatsHistogram* return_histogram = static_cast<StatsHistogram*>(histogram);
+ return return_histogram;
+}
+
+scoped_ptr<HistogramSamples> StatsHistogram::SnapshotSamples() const {
+ scoped_ptr<SampleVector> samples(new SampleVector(bucket_ranges()));
+ stats_->Snapshot(samples.get());
+
+ // Only report UMA data once.
+ StatsHistogram* mutable_me = const_cast<StatsHistogram*>(this);
+ mutable_me->ClearFlags(kUmaTargetedHistogramFlag);
+
+ return samples.PassAs<HistogramSamples>();
+}
+
+int StatsHistogram::FindCorruption(const HistogramSamples& samples) const {
+ // This class won't monitor inconsistencies.
+ return HistogramBase::NO_INCONSISTENCIES;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/stats_histogram.h b/chromium/net/disk_cache/stats_histogram.h
new file mode 100644
index 00000000000..279a1c3c71c
--- /dev/null
+++ b/chromium/net/disk_cache/stats_histogram.h
@@ -0,0 +1,55 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_STATS_HISTOGRAM_H_
+#define NET_DISK_CACHE_STATS_HISTOGRAM_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+
+namespace base {
+class BucketRanges;
+class HistogramSamples;
+class SampleVector;
+} // namespace base
+
+namespace disk_cache {
+
+class Stats;
+
+// This class provides support for sending the disk cache size stats as a UMA
+// histogram. We'll provide our own storage and management for the data, and a
+// SampleVector with a copy of our data.
+//
+// Class derivation of Histogram "deprecated," and should not be copied, and
+// may eventually go away.
+//
+class StatsHistogram : public base::Histogram {
+ public:
+ StatsHistogram(const std::string& name,
+ Sample minimum,
+ Sample maximum,
+ const base::BucketRanges* ranges,
+ const Stats* stats);
+ virtual ~StatsHistogram();
+
+ static void InitializeBucketRanges(const Stats* stats,
+ base::BucketRanges* ranges);
+ static StatsHistogram* FactoryGet(const std::string& name,
+ const Stats* stats);
+
+ virtual scoped_ptr<base::HistogramSamples> SnapshotSamples() const OVERRIDE;
+ virtual int FindCorruption(
+ const base::HistogramSamples& samples) const OVERRIDE;
+
+ private:
+ const Stats* stats_;
+ DISALLOW_COPY_AND_ASSIGN(StatsHistogram);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STATS_HISTOGRAM_H_
diff --git a/chromium/net/disk_cache/storage_block-inl.h b/chromium/net/disk_cache/storage_block-inl.h
new file mode 100644
index 00000000000..098cd74afa7
--- /dev/null
+++ b/chromium/net/disk_cache/storage_block-inl.h
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_STORAGE_BLOCK_INL_H_
+#define NET_DISK_CACHE_STORAGE_BLOCK_INL_H_
+
+#include "net/disk_cache/storage_block.h"
+
+#include "base/hash.h"
+#include "base/logging.h"
+#include "net/disk_cache/trace.h"
+
+namespace disk_cache {
+
+template<typename T> StorageBlock<T>::StorageBlock(MappedFile* file,
+ Addr address)
+ : data_(NULL), file_(file), address_(address), modified_(false),
+ own_data_(false), extended_(false) {
+ if (address.num_blocks() > 1)
+ extended_ = true;
+ DCHECK(!address.is_initialized() || sizeof(*data_) == address.BlockSize());
+}
+
+template<typename T> StorageBlock<T>::~StorageBlock() {
+ if (modified_)
+ Store();
+ DeleteData();
+}
+
+template<typename T> void* StorageBlock<T>::buffer() const {
+ return data_;
+}
+
+template<typename T> size_t StorageBlock<T>::size() const {
+ if (!extended_)
+ return sizeof(*data_);
+ return address_.num_blocks() * sizeof(*data_);
+}
+
+template<typename T> int StorageBlock<T>::offset() const {
+ return address_.start_block() * address_.BlockSize();
+}
+
+template<typename T> bool StorageBlock<T>::LazyInit(MappedFile* file,
+ Addr address) {
+ if (file_ || address_.is_initialized()) {
+ NOTREACHED();
+ return false;
+ }
+ file_ = file;
+ address_.set_value(address.value());
+ if (address.num_blocks() > 1)
+ extended_ = true;
+
+ DCHECK(sizeof(*data_) == address.BlockSize());
+ return true;
+}
+
+template<typename T> void StorageBlock<T>::SetData(T* other) {
+ DCHECK(!modified_);
+ DeleteData();
+ data_ = other;
+}
+
+template<typename T> void StorageBlock<T>::Discard() {
+ if (!data_)
+ return;
+ if (!own_data_) {
+ NOTREACHED();
+ return;
+ }
+ DeleteData();
+ data_ = NULL;
+ modified_ = false;
+ extended_ = false;
+}
+
+template<typename T> void StorageBlock<T>::StopSharingData() {
+ if (!data_ || own_data_)
+ return;
+ DCHECK(!modified_);
+ data_ = NULL;
+}
+
+template<typename T> void StorageBlock<T>::set_modified() {
+ DCHECK(data_);
+ modified_ = true;
+}
+
+template<typename T> void StorageBlock<T>::clear_modified() {
+ modified_ = false;
+}
+
+template<typename T> T* StorageBlock<T>::Data() {
+ if (!data_)
+ AllocateData();
+ return data_;
+}
+
+template<typename T> bool StorageBlock<T>::HasData() const {
+ return (NULL != data_);
+}
+
+template<typename T> bool StorageBlock<T>::VerifyHash() const {
+ uint32 hash = CalculateHash();
+ return (!data_->self_hash || data_->self_hash == hash);
+}
+
+template<typename T> bool StorageBlock<T>::own_data() const {
+ return own_data_;
+}
+
+template<typename T> const Addr StorageBlock<T>::address() const {
+ return address_;
+}
+
+template<typename T> bool StorageBlock<T>::Load() {
+ if (file_) {
+ if (!data_)
+ AllocateData();
+
+ if (file_->Load(this)) {
+ modified_ = false;
+ return true;
+ }
+ }
+ LOG(WARNING) << "Failed data load.";
+ Trace("Failed data load.");
+ return false;
+}
+
+template<typename T> bool StorageBlock<T>::Store() {
+ if (file_ && data_) {
+ data_->self_hash = CalculateHash();
+ if (file_->Store(this)) {
+ modified_ = false;
+ return true;
+ }
+ }
+ LOG(ERROR) << "Failed data store.";
+ Trace("Failed data store.");
+ return false;
+}
+
+template<typename T> void StorageBlock<T>::AllocateData() {
+ DCHECK(!data_);
+ if (!extended_) {
+ data_ = new T;
+ } else {
+ void* buffer = new char[address_.num_blocks() * sizeof(*data_)];
+ data_ = new(buffer) T;
+ }
+ own_data_ = true;
+}
+
+template<typename T> void StorageBlock<T>::DeleteData() {
+ if (own_data_) {
+ if (!extended_) {
+ delete data_;
+ } else {
+ data_->~T();
+ delete[] reinterpret_cast<char*>(data_);
+ }
+ own_data_ = false;
+ }
+}
+
+template<typename T> uint32 StorageBlock<T>::CalculateHash() const {
+ return base::Hash(reinterpret_cast<char*>(data_), offsetof(T, self_hash));
+}
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STORAGE_BLOCK_INL_H_
diff --git a/chromium/net/disk_cache/storage_block.h b/chromium/net/disk_cache/storage_block.h
new file mode 100644
index 00000000000..65c67fc4b44
--- /dev/null
+++ b/chromium/net/disk_cache/storage_block.h
@@ -0,0 +1,95 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface.
+
+#ifndef NET_DISK_CACHE_STORAGE_BLOCK_H_
+#define NET_DISK_CACHE_STORAGE_BLOCK_H_
+
+#include "net/disk_cache/addr.h"
+#include "net/disk_cache/mapped_file.h"
+
+namespace disk_cache {
+
+// This class encapsulates common behavior of a single "block" of data that is
+// stored on a block-file. It implements the FileBlock interface, so it can be
+// serialized directly to the backing file.
+// This object provides a memory buffer for the related data, and it can be used
+// to actually share that memory with another instance of the class.
+//
+// The following example shows how to share storage with another object:
+// StorageBlock<TypeA> a(file, address);
+// StorageBlock<TypeB> b(file, address);
+// a.Load();
+// DoSomething(a.Data());
+// b.SetData(a.Data());
+// ModifySomething(b.Data());
+// // Data modified on the previous call will be saved by b's destructor.
+// b.set_modified();
+template<typename T>
+class StorageBlock : public FileBlock {
+ public:
+ StorageBlock(MappedFile* file, Addr address);
+ virtual ~StorageBlock();
+
+ // FileBlock interface.
+ virtual void* buffer() const;
+ virtual size_t size() const;
+ virtual int offset() const;
+
+ // Allows the overide of dummy values passed on the constructor.
+ bool LazyInit(MappedFile* file, Addr address);
+
+ // Sets the internal storage to share the memory provided by other instance.
+ void SetData(T* other);
+
+ // Deletes the data, even if it was modified and not saved. This object must
+ // own the memory buffer (it cannot be shared).
+ void Discard();
+
+ // Stops sharing the data with another object.
+ void StopSharingData();
+
+ // Sets the object to lazily save the in-memory data on destruction.
+ void set_modified();
+
+ // Forgets that the data was modified, so it's not lazily saved.
+ void clear_modified();
+
+ // Gets a pointer to the internal storage (allocates storage if needed).
+ T* Data();
+
+ // Returns true if there is data associated with this object.
+ bool HasData() const;
+
+ // Returns true if the internal hash is correct.
+ bool VerifyHash() const;
+
+ // Returns true if this object owns the data buffer, false if it is shared.
+ bool own_data() const;
+
+ const Addr address() const;
+
+ // Loads and store the data.
+ bool Load();
+ bool Store();
+
+ private:
+ void AllocateData();
+ void DeleteData();
+ uint32 CalculateHash() const;
+
+ T* data_;
+ MappedFile* file_;
+ Addr address_;
+ bool modified_;
+ bool own_data_; // Is data_ owned by this object or shared with someone else.
+ bool extended_; // Used to store an entry of more than one block.
+
+ DISALLOW_COPY_AND_ASSIGN(StorageBlock);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STORAGE_BLOCK_H_
diff --git a/chromium/net/disk_cache/storage_block_unittest.cc b/chromium/net/disk_cache/storage_block_unittest.cc
new file mode 100644
index 00000000000..f91b2b92ff2
--- /dev/null
+++ b/chromium/net/disk_cache/storage_block_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 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 "base/files/file_path.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/storage_block.h"
+#include "net/disk_cache/storage_block-inl.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef disk_cache::StorageBlock<disk_cache::EntryStore> CacheEntryBlock;
+
+TEST_F(DiskCacheTest, StorageBlock_LoadStore) {
+ base::FilePath filename = cache_path_.AppendASCII("a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ CacheEntryBlock entry1(file.get(), disk_cache::Addr(0xa0010001));
+ memset(entry1.Data(), 0, sizeof(disk_cache::EntryStore));
+ entry1.Data()->hash = 0xaa5555aa;
+ entry1.Data()->rankings_node = 0xa0010002;
+
+ EXPECT_TRUE(entry1.Store());
+ entry1.Data()->hash = 0x88118811;
+ entry1.Data()->rankings_node = 0xa0040009;
+
+ EXPECT_TRUE(entry1.Load());
+ EXPECT_EQ(0xaa5555aa, entry1.Data()->hash);
+ EXPECT_EQ(0xa0010002, entry1.Data()->rankings_node);
+}
+
+TEST_F(DiskCacheTest, StorageBlock_SetData) {
+ base::FilePath filename = cache_path_.AppendASCII("a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ CacheEntryBlock entry1(file.get(), disk_cache::Addr(0xa0010001));
+ entry1.Data()->hash = 0xaa5555aa;
+
+ CacheEntryBlock entry2(file.get(), disk_cache::Addr(0xa0010002));
+ EXPECT_TRUE(entry2.Load());
+ EXPECT_TRUE(entry2.Data() != NULL);
+ EXPECT_TRUE(0 == entry2.Data()->hash);
+
+ EXPECT_TRUE(entry2.Data() != entry1.Data());
+ entry2.SetData(entry1.Data());
+ EXPECT_EQ(0xaa5555aa, entry2.Data()->hash);
+ EXPECT_TRUE(entry2.Data() == entry1.Data());
+}
+
+TEST_F(DiskCacheTest, StorageBlock_SetModified) {
+ base::FilePath filename = cache_path_.AppendASCII("a_test");
+ scoped_refptr<disk_cache::MappedFile> file(new disk_cache::MappedFile);
+ ASSERT_TRUE(CreateCacheTestFile(filename));
+ ASSERT_TRUE(file->Init(filename, 8192));
+
+ CacheEntryBlock* entry1 =
+ new CacheEntryBlock(file.get(), disk_cache::Addr(0xa0010003));
+ EXPECT_TRUE(entry1->Load());
+ EXPECT_TRUE(0 == entry1->Data()->hash);
+ entry1->Data()->hash = 0x45687912;
+ entry1->set_modified();
+ delete entry1;
+
+ CacheEntryBlock entry2(file.get(), disk_cache::Addr(0xa0010003));
+ EXPECT_TRUE(entry2.Load());
+ EXPECT_TRUE(0x45687912 == entry2.Data()->hash);
+}
diff --git a/chromium/net/disk_cache/stress_cache.cc b/chromium/net/disk_cache/stress_cache.cc
new file mode 100644
index 00000000000..9c3c5a6333e
--- /dev/null
+++ b/chromium/net/disk_cache/stress_cache.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 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.
+
+// This is a simple application that stress-tests the crash recovery of the disk
+// cache. The main application starts a copy of itself on a loop, checking the
+// exit code of the child process. When the child dies in an unexpected way,
+// the main application quits.
+
+// The child application has two threads: one to exercise the cache in an
+// infinite loop, and another one to asynchronously kill the process.
+
+// A regular build should never crash.
+// To test that the disk cache doesn't generate critical errors with regular
+// application level crashes, edit stress_support.h.
+
+#include <string>
+#include <vector>
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/debug/debugger.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/stress_support.h"
+#include "net/disk_cache/trace.h"
+
+#if defined(OS_WIN)
+#include "base/logging_win.h"
+#endif
+
+using base::Time;
+
+const int kError = -1;
+const int kExpectedCrash = 100;
+
+// Starts a new process.
+int RunSlave(int iteration) {
+ base::FilePath exe;
+ PathService::Get(base::FILE_EXE, &exe);
+
+ CommandLine cmdline(exe);
+ cmdline.AppendArg(base::IntToString(iteration));
+
+ base::ProcessHandle handle;
+ if (!base::LaunchProcess(cmdline, base::LaunchOptions(), &handle)) {
+ printf("Unable to run test\n");
+ return kError;
+ }
+
+ int exit_code;
+ if (!base::WaitForExitCode(handle, &exit_code)) {
+ printf("Unable to get return code\n");
+ return kError;
+ }
+ return exit_code;
+}
+
+// Main loop for the master process.
+int MasterCode() {
+ for (int i = 0; i < 100000; i++) {
+ int ret = RunSlave(i);
+ if (kExpectedCrash != ret)
+ return ret;
+ }
+
+ printf("More than enough...\n");
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+
+std::string GenerateStressKey() {
+ char key[20 * 1024];
+ size_t size = 50 + rand() % 20000;
+ CacheTestFillBuffer(key, size, true);
+
+ key[size - 1] = '\0';
+ return std::string(key);
+}
+
+// This thread will loop forever, adding and removing entries from the cache.
+// iteration is the current crash cycle, so the entries on the cache are marked
+// to know which instance of the application wrote them.
+void StressTheCache(int iteration) {
+ int cache_size = 0x2000000; // 32MB.
+ uint32 mask = 0xfff; // 4096 entries.
+
+ base::FilePath path;
+ PathService::Get(base::DIR_TEMP, &path);
+ path = path.AppendASCII("cache_test_stress");
+
+ base::Thread cache_thread("CacheThread");
+ if (!cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)))
+ return;
+
+ disk_cache::BackendImpl* cache =
+ new disk_cache::BackendImpl(path, mask,
+ cache_thread.message_loop_proxy().get(),
+ NULL);
+ cache->SetMaxSize(cache_size);
+ cache->SetFlags(disk_cache::kNoLoadProtection);
+
+ net::TestCompletionCallback cb;
+ int rv = cache->Init(cb.callback());
+
+ if (cb.GetResult(rv) != net::OK) {
+ printf("Unable to initialize cache.\n");
+ return;
+ }
+ printf("Iteration %d, initial entries: %d\n", iteration,
+ cache->GetEntryCount());
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ // kNumKeys is meant to be enough to have about 3x or 4x iterations before
+ // the process crashes.
+#ifdef NDEBUG
+ const int kNumKeys = 4000;
+#else
+ const int kNumKeys = 1200;
+#endif
+ const int kNumEntries = 30;
+ std::string keys[kNumKeys];
+ disk_cache::Entry* entries[kNumEntries] = {0};
+
+ for (int i = 0; i < kNumKeys; i++) {
+ keys[i] = GenerateStressKey();
+ }
+
+ const int kSize = 20000;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kSize));
+ memset(buffer->data(), 'k', kSize);
+
+ for (int i = 0;; i++) {
+ int slot = rand() % kNumEntries;
+ int key = rand() % kNumKeys;
+ bool truncate = (rand() % 2 == 0);
+ int size = kSize - (rand() % 20) * kSize / 20;
+
+ if (entries[slot])
+ entries[slot]->Close();
+
+ net::TestCompletionCallback cb;
+ rv = cache->OpenEntry(keys[key], &entries[slot], cb.callback());
+ if (cb.GetResult(rv) != net::OK) {
+ rv = cache->CreateEntry(keys[key], &entries[slot], cb.callback());
+ CHECK_EQ(net::OK, cb.GetResult(rv));
+ }
+
+ base::snprintf(buffer->data(), kSize,
+ "i: %d iter: %d, size: %d, truncate: %d ", i, iteration,
+ size, truncate ? 1 : 0);
+ rv = entries[slot]->WriteData(0, 0, buffer.get(), size, cb.callback(),
+ truncate);
+ CHECK_EQ(size, cb.GetResult(rv));
+
+ if (rand() % 100 > 80) {
+ key = rand() % kNumKeys;
+ net::TestCompletionCallback cb2;
+ rv = cache->DoomEntry(keys[key], cb2.callback());
+ cb2.GetResult(rv);
+ }
+
+ if (!(i % 100))
+ printf("Entries: %d \r", i);
+ }
+}
+
+// We want to prevent the timer thread from killing the process while we are
+// waiting for the debugger to attach.
+bool g_crashing = false;
+
+// RunSoon() and CrashCallback() reference each other, unfortunately.
+void RunSoon(base::MessageLoop* target_loop);
+
+void CrashCallback() {
+ // Keep trying to run.
+ RunSoon(base::MessageLoop::current());
+
+ if (g_crashing)
+ return;
+
+ if (rand() % 100 > 30) {
+ printf("sweet death...\n");
+#if defined(OS_WIN)
+ // Windows does more work on _exit() that we would like, so we use Kill.
+ base::KillProcessById(base::GetCurrentProcId(), kExpectedCrash, false);
+#elif defined(OS_POSIX)
+ // On POSIX, _exit() will terminate the process with minimal cleanup,
+ // and it is cleaner than killing.
+ _exit(kExpectedCrash);
+#endif
+ }
+}
+
+void RunSoon(base::MessageLoop* target_loop) {
+ const base::TimeDelta kTaskDelay = base::TimeDelta::FromSeconds(10);
+ target_loop->PostDelayedTask(
+ FROM_HERE, base::Bind(&CrashCallback), kTaskDelay);
+}
+
+// We leak everything here :)
+bool StartCrashThread() {
+ base::Thread* thread = new base::Thread("party_crasher");
+ if (!thread->Start())
+ return false;
+
+ RunSoon(thread->message_loop());
+ return true;
+}
+
+void CrashHandler(const std::string& str) {
+ g_crashing = true;
+ base::debug::BreakDebugger();
+}
+
+bool MessageHandler(int severity, const char* file, int line,
+ size_t message_start, const std::string& str) {
+ const size_t kMaxMessageLen = 48;
+ char message[kMaxMessageLen];
+ size_t len = std::min(str.length() - message_start, kMaxMessageLen - 1);
+
+ memcpy(message, str.c_str() + message_start, len);
+ message[len] = '\0';
+#if !defined(DISK_CACHE_TRACE_TO_LOG)
+ disk_cache::Trace("%s", message);
+#endif
+ return false;
+}
+
+// -----------------------------------------------------------------------
+
+#if defined(OS_WIN)
+// {B9A153D4-31C3-48e4-9ABF-D54383F14A0D}
+const GUID kStressCacheTraceProviderName = {
+ 0xb9a153d4, 0x31c3, 0x48e4,
+ { 0x9a, 0xbf, 0xd5, 0x43, 0x83, 0xf1, 0x4a, 0xd } };
+#endif
+
+int main(int argc, const char* argv[]) {
+ // Setup an AtExitManager so Singleton objects will be destructed.
+ base::AtExitManager at_exit_manager;
+
+ if (argc < 2)
+ return MasterCode();
+
+ logging::SetLogAssertHandler(CrashHandler);
+ logging::SetLogMessageHandler(MessageHandler);
+
+#if defined(OS_WIN)
+ logging::LogEventProvider::Initialize(kStressCacheTraceProviderName);
+#else
+ CommandLine::Init(argc, argv);
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+#endif
+
+ // Some time for the memory manager to flush stuff.
+ base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(3));
+ base::MessageLoop message_loop(base::MessageLoop::TYPE_IO);
+
+ char* end;
+ long int iteration = strtol(argv[1], &end, 0);
+
+ if (!StartCrashThread()) {
+ printf("failed to start thread\n");
+ return kError;
+ }
+
+ StressTheCache(iteration);
+ return 0;
+}
diff --git a/chromium/net/disk_cache/stress_support.h b/chromium/net/disk_cache/stress_support.h
new file mode 100644
index 00000000000..48d7b5b7df2
--- /dev/null
+++ b/chromium/net/disk_cache/stress_support.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 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_DISK_CACHE_STRESS_SUPPORT_H_
+#define NET_DISK_CACHE_STRESS_SUPPORT_H_
+
+#include "base/logging.h"
+
+namespace disk_cache {
+
+// Uncomment this line to generate a debug build of stress_cache with checks
+// to ensure that we are not producing corrupt entries.
+// #define NET_BUILD_STRESS_CACHE 1
+
+// Uncomment this line to direct the in-memory disk cache tracing to the base
+// logging system. On Windows this option will enable ETW (Event Tracing for
+// Windows) so logs across multiple runs can be collected.
+// #define DISK_CACHE_TRACE_TO_LOG 1
+
+// Uncomment this line to perform extended integrity checks during init. It is
+// not recommended to enable this option unless some corruption is being tracked
+// down.
+// #define STRESS_CACHE_EXTENDED_VALIDATION 1
+
+#if defined(NET_BUILD_STRESS_CACHE)
+#define STRESS_NOTREACHED() NOTREACHED()
+#define STRESS_DCHECK(a) DCHECK(a)
+#else
+// We don't support streams with these macros, but that's a small price to pay
+// to have a straightforward logic here. Please don't add something like
+// LogMessageVoidify.
+#define STRESS_NOTREACHED() {}
+#define STRESS_DCHECK(a) {}
+#endif
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_STRESS_SUPPORT_H_
diff --git a/chromium/net/disk_cache/trace.cc b/chromium/net/disk_cache/trace.cc
new file mode 100644
index 00000000000..56ebe9bb7ea
--- /dev/null
+++ b/chromium/net/disk_cache/trace.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2011 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/disk_cache/trace.h"
+
+#include <stdio.h>
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "net/disk_cache/stress_support.h"
+
+// Change this value to 1 to enable tracing on a release build. By default,
+// tracing is enabled only on debug builds.
+#define ENABLE_TRACING 0
+
+#ifndef NDEBUG
+#undef ENABLE_TRACING
+#define ENABLE_TRACING 1
+#endif
+
+namespace {
+
+const int kEntrySize = 12 * sizeof(size_t);
+#if defined(NET_BUILD_STRESS_CACHE)
+const int kNumberOfEntries = 500000;
+#else
+const int kNumberOfEntries = 5000; // 240 KB on 32bit, 480 KB on 64bit
+#endif
+
+bool s_trace_enabled = false;
+base::LazyInstance<base::Lock>::Leaky s_lock = LAZY_INSTANCE_INITIALIZER;
+
+struct TraceBuffer {
+ int num_traces;
+ int current;
+ char buffer[kNumberOfEntries][kEntrySize];
+};
+
+#if ENABLE_TRACING
+void DebugOutput(const char* msg) {
+#if defined(OS_WIN)
+ OutputDebugStringA(msg);
+#else
+ NOTIMPLEMENTED();
+#endif
+}
+#endif // ENABLE_TRACING
+
+} // namespace
+
+namespace disk_cache {
+
+// s_trace_buffer and s_trace_object are not singletons because I want the
+// buffer to be destroyed and re-created when the last user goes away, and it
+// must be straightforward to access the buffer from the debugger.
+static TraceObject* s_trace_object = NULL;
+
+// Static.
+TraceObject* TraceObject::GetTraceObject() {
+ base::AutoLock lock(s_lock.Get());
+
+ if (s_trace_object)
+ return s_trace_object;
+
+ s_trace_object = new TraceObject();
+ return s_trace_object;
+}
+
+TraceObject::TraceObject() {
+ InitTrace();
+}
+
+TraceObject::~TraceObject() {
+ DestroyTrace();
+}
+
+void TraceObject::EnableTracing(bool enable) {
+ base::AutoLock lock(s_lock.Get());
+ s_trace_enabled = enable;
+}
+
+#if ENABLE_TRACING
+
+static TraceBuffer* s_trace_buffer = NULL;
+
+void InitTrace(void) {
+ s_trace_enabled = true;
+ if (s_trace_buffer)
+ return;
+
+ s_trace_buffer = new TraceBuffer;
+ memset(s_trace_buffer, 0, sizeof(*s_trace_buffer));
+}
+
+void DestroyTrace(void) {
+ base::AutoLock lock(s_lock.Get());
+
+ delete s_trace_buffer;
+ s_trace_buffer = NULL;
+ s_trace_object = NULL;
+}
+
+void Trace(const char* format, ...) {
+ if (!s_trace_buffer || !s_trace_enabled)
+ return;
+
+ va_list ap;
+ va_start(ap, format);
+ char line[kEntrySize + 2];
+
+#if defined(OS_WIN)
+ vsprintf_s(line, format, ap);
+#else
+ vsnprintf(line, kEntrySize, format, ap);
+#endif
+
+#if defined(DISK_CACHE_TRACE_TO_LOG)
+ line[kEntrySize] = '\0';
+ LOG(INFO) << line;
+#endif
+
+ va_end(ap);
+
+ {
+ base::AutoLock lock(s_lock.Get());
+ if (!s_trace_buffer || !s_trace_enabled)
+ return;
+
+ memcpy(s_trace_buffer->buffer[s_trace_buffer->current], line, kEntrySize);
+
+ s_trace_buffer->num_traces++;
+ s_trace_buffer->current++;
+ if (s_trace_buffer->current == kNumberOfEntries)
+ s_trace_buffer->current = 0;
+ }
+}
+
+// Writes the last num_traces to the debugger output.
+void DumpTrace(int num_traces) {
+ DCHECK(s_trace_buffer);
+ DebugOutput("Last traces:\n");
+
+ if (num_traces > kNumberOfEntries || num_traces < 0)
+ num_traces = kNumberOfEntries;
+
+ if (s_trace_buffer->num_traces) {
+ char line[kEntrySize + 2];
+
+ int current = s_trace_buffer->current - num_traces;
+ if (current < 0)
+ current += kNumberOfEntries;
+
+ for (int i = 0; i < num_traces; i++) {
+ memcpy(line, s_trace_buffer->buffer[current], kEntrySize);
+ line[kEntrySize] = '\0';
+ size_t length = strlen(line);
+ if (length) {
+ line[length] = '\n';
+ line[length + 1] = '\0';
+ DebugOutput(line);
+ }
+
+ current++;
+ if (current == kNumberOfEntries)
+ current = 0;
+ }
+ }
+
+ DebugOutput("End of Traces\n");
+}
+
+#else // ENABLE_TRACING
+
+void InitTrace(void) {
+ return;
+}
+
+void DestroyTrace(void) {
+ s_trace_object = NULL;
+}
+
+void Trace(const char* format, ...) {
+}
+
+#endif // ENABLE_TRACING
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/trace.h b/chromium/net/disk_cache/trace.h
new file mode 100644
index 00000000000..b0bf1ad7284
--- /dev/null
+++ b/chromium/net/disk_cache/trace.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 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.
+
+// This file provides support for basic in-memory tracing of short events. We
+// keep a static circular buffer where we store the last traced events, so we
+// can review the cache recent behavior should we need it.
+
+#ifndef NET_DISK_CACHE_TRACE_H__
+#define NET_DISK_CACHE_TRACE_H__
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+
+// Create and destroy the tracing buffer.
+void InitTrace(void);
+void DestroyTrace(void);
+
+// Simple class to handle the trace buffer lifetime. Any object interested in
+// tracing should keep a reference to the object returned by GetTraceObject().
+class TraceObject : public base::RefCounted<TraceObject> {
+ friend class base::RefCounted<TraceObject>;
+ public:
+ static TraceObject* GetTraceObject();
+ void EnableTracing(bool enable);
+
+ private:
+ TraceObject();
+ ~TraceObject();
+ DISALLOW_COPY_AND_ASSIGN(TraceObject);
+};
+
+// Traces to the internal buffer.
+NET_EXPORT_PRIVATE void Trace(const char* format, ...);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_TRACE_H__
diff --git a/chromium/net/disk_cache/tracing_cache_backend.cc b/chromium/net/disk_cache/tracing_cache_backend.cc
new file mode 100644
index 00000000000..4966133f00d
--- /dev/null
+++ b/chromium/net/disk_cache/tracing_cache_backend.cc
@@ -0,0 +1,317 @@
+// Copyright (c) 2013 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/disk_cache/tracing_cache_backend.h"
+
+#include "net/base/net_errors.h"
+
+namespace disk_cache {
+
+// Proxies entry objects created by the real underlying backend. Backend users
+// will only see the proxy entries. It is necessary for recording the backend
+// operations since often non-trivial work is invoked directly on entries.
+class EntryProxy : public Entry, public base::RefCountedThreadSafe<EntryProxy> {
+ public:
+ EntryProxy(Entry *entry, TracingCacheBackend* backend);
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<EntryProxy>;
+ typedef TracingCacheBackend::Operation Operation;
+ virtual ~EntryProxy();
+
+ struct RwOpExtra {
+ int index;
+ int offset;
+ int buf_len;
+ bool truncate;
+ };
+
+ void RecordEvent(base::TimeTicks start_time, Operation op, RwOpExtra extra,
+ int result_to_record);
+ void EntryOpComplete(base::TimeTicks start_time, Operation op,
+ RwOpExtra extra, const CompletionCallback& cb,
+ int result);
+ Entry* entry_;
+ base::WeakPtr<TracingCacheBackend> backend_;
+
+ DISALLOW_COPY_AND_ASSIGN(EntryProxy);
+};
+
+EntryProxy::EntryProxy(Entry *entry, TracingCacheBackend* backend)
+ : entry_(entry),
+ backend_(backend->AsWeakPtr()) {
+}
+
+void EntryProxy::Doom() {
+ // TODO(pasko): Record the event.
+ entry_->Doom();
+}
+
+void EntryProxy::Close() {
+ // TODO(pasko): Record the event.
+ entry_->Close();
+ Release();
+}
+
+std::string EntryProxy::GetKey() const {
+ return entry_->GetKey();
+}
+
+base::Time EntryProxy::GetLastUsed() const {
+ return entry_->GetLastUsed();
+}
+
+base::Time EntryProxy::GetLastModified() const {
+ return entry_->GetLastModified();
+}
+
+int32 EntryProxy::GetDataSize(int index) const {
+ return entry_->GetDataSize(index);
+}
+
+int EntryProxy::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ RwOpExtra extra;
+ extra.index = index;
+ extra.offset = offset;
+ extra.buf_len = buf_len;
+ extra.truncate = false;
+ int rv = entry_->ReadData(
+ index, offset, buf, buf_len,
+ base::Bind(&EntryProxy::EntryOpComplete, this, start_time,
+ TracingCacheBackend::OP_READ, extra, callback));
+ if (rv != net::ERR_IO_PENDING) {
+ RecordEvent(start_time, TracingCacheBackend::OP_READ, extra, rv);
+ }
+ return rv;
+}
+
+int EntryProxy::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ RwOpExtra extra;
+ extra.index = index;
+ extra.offset = offset;
+ extra.buf_len = buf_len;
+ extra.truncate = truncate;
+ int rv = entry_->WriteData(index, offset, buf, buf_len,
+ base::Bind(&EntryProxy::EntryOpComplete, this, start_time,
+ TracingCacheBackend::OP_WRITE, extra, callback),
+ truncate);
+ if (rv != net::ERR_IO_PENDING) {
+ RecordEvent(start_time, TracingCacheBackend::OP_WRITE, extra, rv);
+ }
+ return rv;
+}
+
+int EntryProxy::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ // TODO(pasko): Record the event.
+ return entry_->ReadSparseData(offset, buf, buf_len, callback);
+}
+
+int EntryProxy::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ // TODO(pasko): Record the event.
+ return entry_->WriteSparseData(offset, buf, buf_len, callback);
+}
+
+int EntryProxy::GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) {
+ return entry_->GetAvailableRange(offset, len, start, callback);
+}
+
+bool EntryProxy::CouldBeSparse() const {
+ return entry_->CouldBeSparse();
+}
+
+void EntryProxy::CancelSparseIO() {
+ return entry_->CancelSparseIO();
+}
+
+int EntryProxy::ReadyForSparseIO(const CompletionCallback& callback) {
+ return entry_->ReadyForSparseIO(callback);
+}
+
+void EntryProxy::RecordEvent(base::TimeTicks start_time, Operation op,
+ RwOpExtra extra, int result_to_record) {
+ // TODO(pasko): Implement.
+}
+
+void EntryProxy::EntryOpComplete(base::TimeTicks start_time, Operation op,
+ RwOpExtra extra, const CompletionCallback& cb,
+ int result) {
+ RecordEvent(start_time, op, extra, result);
+ if (!cb.is_null()) {
+ cb.Run(result);
+ }
+}
+
+EntryProxy::~EntryProxy() {
+ if (backend_.get()) {
+ backend_->OnDeleteEntry(entry_);
+ }
+}
+
+TracingCacheBackend::TracingCacheBackend(scoped_ptr<Backend> backend)
+ : backend_(backend.Pass()) {
+}
+
+TracingCacheBackend::~TracingCacheBackend() {
+}
+
+net::CacheType TracingCacheBackend::GetCacheType() const {
+ return backend_->GetCacheType();
+}
+
+int32 TracingCacheBackend::GetEntryCount() const {
+ return backend_->GetEntryCount();
+}
+
+void TracingCacheBackend::RecordEvent(base::TimeTicks start_time, Operation op,
+ std::string key, Entry* entry, int rv) {
+ // TODO(pasko): Implement.
+}
+
+EntryProxy* TracingCacheBackend::FindOrCreateEntryProxy(Entry* entry) {
+ EntryProxy* entry_proxy;
+ EntryToProxyMap::iterator it = open_entries_.find(entry);
+ if (it != open_entries_.end()) {
+ entry_proxy = it->second;
+ entry_proxy->AddRef();
+ return entry_proxy;
+ }
+ entry_proxy = new EntryProxy(entry, this);
+ entry_proxy->AddRef();
+ open_entries_[entry] = entry_proxy;
+ return entry_proxy;
+}
+
+void TracingCacheBackend::OnDeleteEntry(Entry* entry) {
+ EntryToProxyMap::iterator it = open_entries_.find(entry);
+ if (it != open_entries_.end()) {
+ open_entries_.erase(it);
+ }
+}
+
+void TracingCacheBackend::BackendOpComplete(base::TimeTicks start_time,
+ Operation op,
+ std::string key,
+ Entry** entry,
+ const CompletionCallback& callback,
+ int result) {
+ RecordEvent(start_time, op, key, *entry, result);
+ if (*entry) {
+ *entry = FindOrCreateEntryProxy(*entry);
+ }
+ if (!callback.is_null()) {
+ callback.Run(result);
+ }
+}
+
+net::CompletionCallback TracingCacheBackend::BindCompletion(
+ Operation op, base::TimeTicks start_time, const std::string& key,
+ Entry **entry, const net::CompletionCallback& cb) {
+ return base::Bind(&TracingCacheBackend::BackendOpComplete,
+ AsWeakPtr(), start_time, op, key, entry, cb);
+}
+
+int TracingCacheBackend::OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ DCHECK(*entry == NULL);
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ int rv = backend_->OpenEntry(key, entry,
+ BindCompletion(OP_OPEN, start_time, key, entry,
+ callback));
+ if (rv != net::ERR_IO_PENDING) {
+ RecordEvent(start_time, OP_OPEN, key, *entry, rv);
+ if (*entry) {
+ *entry = FindOrCreateEntryProxy(*entry);
+ }
+ }
+ return rv;
+}
+
+int TracingCacheBackend::CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ int rv = backend_->CreateEntry(key, entry,
+ BindCompletion(OP_CREATE, start_time, key,
+ entry, callback));
+ if (rv != net::ERR_IO_PENDING) {
+ RecordEvent(start_time, OP_CREATE, key, *entry, rv);
+ if (*entry) {
+ *entry = FindOrCreateEntryProxy(*entry);
+ }
+ }
+ return rv;
+}
+
+int TracingCacheBackend::DoomEntry(const std::string& key,
+ const CompletionCallback& callback) {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ int rv = backend_->DoomEntry(key, BindCompletion(OP_DOOM_ENTRY,
+ start_time, key, NULL,
+ callback));
+ if (rv != net::ERR_IO_PENDING) {
+ RecordEvent(start_time, OP_DOOM_ENTRY, key, NULL, rv);
+ }
+ return rv;
+}
+
+int TracingCacheBackend::DoomAllEntries(const CompletionCallback& callback) {
+ return backend_->DoomAllEntries(callback);
+}
+
+int TracingCacheBackend::DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& cb) {
+ return backend_->DoomEntriesBetween(initial_time, end_time, cb);
+}
+
+int TracingCacheBackend::DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) {
+ return backend_->DoomEntriesSince(initial_time, callback);
+}
+
+int TracingCacheBackend::OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) {
+ return backend_->OpenNextEntry(iter, next_entry, callback);
+}
+
+void TracingCacheBackend::EndEnumeration(void** iter) {
+ return backend_->EndEnumeration(iter);
+}
+
+void TracingCacheBackend::GetStats(StatsItems* stats) {
+ return backend_->GetStats(stats);
+}
+
+void TracingCacheBackend::OnExternalCacheHit(const std::string& key) {
+ return backend_->OnExternalCacheHit(key);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/tracing_cache_backend.h b/chromium/net/disk_cache/tracing_cache_backend.h
new file mode 100644
index 00000000000..304a7334d83
--- /dev/null
+++ b/chromium/net/disk_cache/tracing_cache_backend.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2013 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_DISK_CACHE_TRACING_CACHE_BACKEND_H_
+#define NET_DISK_CACHE_TRACING_CACHE_BACKEND_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/stats.h"
+
+namespace disk_cache {
+
+class EntryProxy;
+
+// The TracingCacheBackend implements the Cache Backend interface. It intercepts
+// all backend operations from the IO thread and records the time from the start
+// of the operation until the result is delivered.
+class NET_EXPORT TracingCacheBackend : public Backend,
+ public base::SupportsWeakPtr<TracingCacheBackend> {
+ public:
+ explicit TracingCacheBackend(scoped_ptr<Backend> backend);
+
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(StatsItems* stats) OVERRIDE;
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ private:
+ friend class EntryProxy;
+ enum Operation {
+ OP_OPEN,
+ OP_CREATE,
+ OP_DOOM_ENTRY,
+ OP_READ,
+ OP_WRITE
+ };
+
+ virtual ~TracingCacheBackend();
+
+ EntryProxy* FindOrCreateEntryProxy(Entry* entry);
+
+ void OnDeleteEntry(Entry* e);
+
+ void RecordEvent(base::TimeTicks start_time, Operation op, std::string key,
+ Entry* entry, int result);
+
+ void BackendOpComplete(base::TimeTicks start_time, Operation op,
+ std::string key, Entry** entry,
+ const CompletionCallback& callback, int result);
+
+ net::CompletionCallback BindCompletion(Operation op,
+ base::TimeTicks start_time,
+ const std::string& key, Entry **entry,
+ const net::CompletionCallback& cb);
+
+ scoped_ptr<Backend> backend_;
+ typedef std::map<Entry*, EntryProxy*> EntryToProxyMap;
+ EntryToProxyMap open_entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracingCacheBackend);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_TRACING_CACHE_BACKEND_H_
diff --git a/chromium/net/disk_cache/v3/backend_impl_v3.cc b/chromium/net/disk_cache/v3/backend_impl_v3.cc
new file mode 100644
index 00000000000..92ea272226b
--- /dev/null
+++ b/chromium/net/disk_cache/v3/backend_impl_v3.cc
@@ -0,0 +1,1640 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/backend_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/hash.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/file.h"
+
+// This has to be defined before including histogram_macros.h from this file.
+#define NET_DISK_CACHE_BACKEND_IMPL_CC_
+#include "net/disk_cache/histogram_macros.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+const char* kIndexName = "index";
+
+// Seems like ~240 MB correspond to less than 50k entries for 99% of the people.
+// Note that the actual target is to keep the index table load factor under 55%
+// for most users.
+const int k64kEntriesStore = 240 * 1000 * 1000;
+const int kBaseTableLen = 64 * 1024;
+const int kDefaultCacheSize = 80 * 1024 * 1024;
+
+// Avoid trimming the cache for the first 5 minutes (10 timer ticks).
+const int kTrimDelay = 10;
+
+int DesiredIndexTableLen(int32 storage_size) {
+ if (storage_size <= k64kEntriesStore)
+ return kBaseTableLen;
+ if (storage_size <= k64kEntriesStore * 2)
+ return kBaseTableLen * 2;
+ if (storage_size <= k64kEntriesStore * 4)
+ return kBaseTableLen * 4;
+ if (storage_size <= k64kEntriesStore * 8)
+ return kBaseTableLen * 8;
+
+ // The biggest storage_size for int32 requires a 4 MB table.
+ return kBaseTableLen * 16;
+}
+
+int MaxStorageSizeForTable(int table_len) {
+ return table_len * (k64kEntriesStore / kBaseTableLen);
+}
+
+size_t GetIndexSize(int table_len) {
+ size_t table_size = sizeof(disk_cache::CacheAddr) * table_len;
+ return sizeof(disk_cache::IndexHeader) + table_size;
+}
+
+} // namespace
+
+// ------------------------------------------------------------------------
+
+namespace disk_cache {
+
+BackendImpl::BackendImpl(const base::FilePath& path,
+ base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log)
+ : background_queue_(this, cache_thread),
+ path_(path),
+ block_files_(path),
+ mask_(0),
+ max_size_(0),
+ up_ticks_(0),
+ cache_type_(net::DISK_CACHE),
+ uma_report_(0),
+ user_flags_(0),
+ init_(false),
+ restarted_(false),
+ unit_test_(false),
+ read_only_(false),
+ disabled_(false),
+ new_eviction_(false),
+ first_timer_(true),
+ user_load_(false),
+ net_log_(net_log),
+ done_(true, false),
+ ptr_factory_(this) {
+}
+
+BackendImpl::BackendImpl(const base::FilePath& path,
+ uint32 mask,
+ base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log)
+ : background_queue_(this, cache_thread),
+ path_(path),
+ block_files_(path),
+ mask_(mask),
+ max_size_(0),
+ up_ticks_(0),
+ cache_type_(net::DISK_CACHE),
+ uma_report_(0),
+ user_flags_(kMask),
+ init_(false),
+ restarted_(false),
+ unit_test_(false),
+ read_only_(false),
+ disabled_(false),
+ new_eviction_(false),
+ first_timer_(true),
+ user_load_(false),
+ net_log_(net_log),
+ done_(true, false),
+ ptr_factory_(this) {
+}
+
+BackendImpl::~BackendImpl() {
+ if (user_flags_ & kNoRandom) {
+ // This is a unit test, so we want to be strict about not leaking entries
+ // and completing all the work.
+ background_queue_.WaitForPendingIO();
+ } else {
+ // This is most likely not a test, so we want to do as little work as
+ // possible at this time, at the price of leaving dirty entries behind.
+ background_queue_.DropPendingIO();
+ }
+
+ if (background_queue_.BackgroundIsCurrentThread()) {
+ // Unit tests may use the same thread for everything.
+ CleanupCache();
+ } else {
+ background_queue_.background_thread()->PostTask(
+ FROM_HERE, base::Bind(&FinalCleanupCallback, base::Unretained(this)));
+ // http://crbug.com/74623
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ done_.Wait();
+ }
+}
+
+int BackendImpl::Init(const CompletionCallback& callback) {
+ background_queue_.Init(callback);
+ return net::ERR_IO_PENDING;
+}
+
+// ------------------------------------------------------------------------
+
+int BackendImpl::OpenPrevEntry(void** iter, Entry** prev_entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.OpenPrevEntry(iter, prev_entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+bool BackendImpl::SetMaxSize(int max_bytes) {
+ COMPILE_ASSERT(sizeof(max_bytes) == sizeof(max_size_), unsupported_int_model);
+ if (max_bytes < 0)
+ return false;
+
+ // Zero size means use the default.
+ if (!max_bytes)
+ return true;
+
+ // Avoid a DCHECK later on.
+ if (max_bytes >= kint32max - kint32max / 10)
+ max_bytes = kint32max - kint32max / 10 - 1;
+
+ user_flags_ |= kMaxSize;
+ max_size_ = max_bytes;
+ return true;
+}
+
+void BackendImpl::SetType(net::CacheType type) {
+ DCHECK_NE(net::MEMORY_CACHE, type);
+ cache_type_ = type;
+}
+
+bool BackendImpl::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ return block_files_.CreateBlock(block_type, block_count, block_address);
+}
+
+void BackendImpl::UpdateRank(EntryImpl* entry, bool modified) {
+ if (read_only_ || (!modified && cache_type() == net::SHADER_CACHE))
+ return;
+ eviction_.UpdateRank(entry, modified);
+}
+
+void BackendImpl::InternalDoomEntry(EntryImpl* entry) {
+ uint32 hash = entry->GetHash();
+ std::string key = entry->GetKey();
+ Addr entry_addr = entry->entry()->address();
+ bool error;
+ EntryImpl* parent_entry = MatchEntry(key, hash, true, entry_addr, &error);
+ CacheAddr child(entry->GetNextAddress());
+
+ Trace("Doom entry 0x%p", entry);
+
+ if (!entry->doomed()) {
+ // We may have doomed this entry from within MatchEntry.
+ eviction_.OnDoomEntry(entry);
+ entry->InternalDoom();
+ if (!new_eviction_) {
+ DecreaseNumEntries();
+ }
+ stats_.OnEvent(Stats::DOOM_ENTRY);
+ }
+
+ if (parent_entry) {
+ parent_entry->SetNextAddress(Addr(child));
+ parent_entry->Release();
+ } else if (!error) {
+ data_->table[hash & mask_] = child;
+ }
+
+ FlushIndex();
+}
+
+void BackendImpl::OnEntryDestroyBegin(Addr address) {
+ EntriesMap::iterator it = open_entries_.find(address.value());
+ if (it != open_entries_.end())
+ open_entries_.erase(it);
+}
+
+void BackendImpl::OnEntryDestroyEnd() {
+ DecreaseNumRefs();
+ if (data_->header.num_bytes > max_size_ && !read_only_ &&
+ (up_ticks_ > kTrimDelay || user_flags_ & kNoRandom))
+ eviction_.TrimCache(false);
+}
+
+EntryImpl* BackendImpl::GetOpenEntry(CacheRankingsBlock* rankings) const {
+ DCHECK(rankings->HasData());
+ EntriesMap::const_iterator it =
+ open_entries_.find(rankings->Data()->contents);
+ if (it != open_entries_.end()) {
+ // We have this entry in memory.
+ return it->second;
+ }
+
+ return NULL;
+}
+
+int BackendImpl::MaxFileSize() const {
+ return max_size_ / 8;
+}
+
+void BackendImpl::ModifyStorageSize(int32 old_size, int32 new_size) {
+ if (disabled_ || old_size == new_size)
+ return;
+ if (old_size > new_size)
+ SubstractStorageSize(old_size - new_size);
+ else
+ AddStorageSize(new_size - old_size);
+
+ FlushIndex();
+
+ // Update the usage statistics.
+ stats_.ModifyStorageStats(old_size, new_size);
+}
+
+void BackendImpl::TooMuchStorageRequested(int32 size) {
+ stats_.ModifyStorageStats(0, size);
+}
+
+bool BackendImpl::IsAllocAllowed(int current_size, int new_size) {
+ DCHECK_GT(new_size, current_size);
+ if (user_flags_ & kNoBuffering)
+ return false;
+
+ int to_add = new_size - current_size;
+ if (buffer_bytes_ + to_add > MaxBuffersSize())
+ return false;
+
+ buffer_bytes_ += to_add;
+ CACHE_UMA(COUNTS_50000, "BufferBytes", 0, buffer_bytes_ / 1024);
+ return true;
+}
+
+void BackendImpl::BufferDeleted(int size) {
+ buffer_bytes_ -= size;
+ DCHECK_GE(size, 0);
+}
+
+bool BackendImpl::IsLoaded() const {
+ CACHE_UMA(COUNTS, "PendingIO", 0, num_pending_io_);
+ if (user_flags_ & kNoLoadProtection)
+ return false;
+
+ return (num_pending_io_ > 5 || user_load_);
+}
+
+std::string BackendImpl::HistogramName(const char* name, int experiment) const {
+ if (!experiment)
+ return base::StringPrintf("DiskCache.%d.%s", cache_type_, name);
+ return base::StringPrintf("DiskCache.%d.%s_%d", cache_type_,
+ name, experiment);
+}
+
+base::WeakPtr<BackendImpl> BackendImpl::GetWeakPtr() {
+ return ptr_factory_.GetWeakPtr();
+}
+
+// We want to remove biases from some histograms so we only send data once per
+// week.
+bool BackendImpl::ShouldReportAgain() {
+ if (uma_report_)
+ return uma_report_ == 2;
+
+ uma_report_++;
+ int64 last_report = stats_.GetCounter(Stats::LAST_REPORT);
+ Time last_time = Time::FromInternalValue(last_report);
+ if (!last_report || (Time::Now() - last_time).InDays() >= 7) {
+ stats_.SetCounter(Stats::LAST_REPORT, Time::Now().ToInternalValue());
+ uma_report_++;
+ return true;
+ }
+ return false;
+}
+
+void BackendImpl::FirstEviction() {
+ DCHECK(data_->header.create_time);
+ if (!GetEntryCount())
+ return; // This is just for unit tests.
+
+ Time create_time = Time::FromInternalValue(data_->header.create_time);
+ CACHE_UMA(AGE, "FillupAge", 0, create_time);
+
+ int64 use_time = stats_.GetCounter(Stats::TIMER);
+ CACHE_UMA(HOURS, "FillupTime", 0, static_cast<int>(use_time / 120));
+ CACHE_UMA(PERCENTAGE, "FirstHitRatio", 0, stats_.GetHitRatio());
+
+ if (!use_time)
+ use_time = 1;
+ CACHE_UMA(COUNTS_10000, "FirstEntryAccessRate", 0,
+ static_cast<int>(data_->header.num_entries / use_time));
+ CACHE_UMA(COUNTS, "FirstByteIORate", 0,
+ static_cast<int>((data_->header.num_bytes / 1024) / use_time));
+
+ int avg_size = data_->header.num_bytes / GetEntryCount();
+ CACHE_UMA(COUNTS, "FirstEntrySize", 0, avg_size);
+
+ int large_entries_bytes = stats_.GetLargeEntriesSize();
+ int large_ratio = large_entries_bytes * 100 / data_->header.num_bytes;
+ CACHE_UMA(PERCENTAGE, "FirstLargeEntriesRatio", 0, large_ratio);
+
+ if (new_eviction_) {
+ CACHE_UMA(PERCENTAGE, "FirstResurrectRatio", 0, stats_.GetResurrectRatio());
+ CACHE_UMA(PERCENTAGE, "FirstNoUseRatio", 0,
+ data_->header.lru.sizes[0] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "FirstLowUseRatio", 0,
+ data_->header.lru.sizes[1] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "FirstHighUseRatio", 0,
+ data_->header.lru.sizes[2] * 100 / data_->header.num_entries);
+ }
+
+ stats_.ResetRatios();
+}
+
+void BackendImpl::OnEvent(Stats::Counters an_event) {
+ stats_.OnEvent(an_event);
+}
+
+void BackendImpl::OnRead(int32 bytes) {
+ DCHECK_GE(bytes, 0);
+ byte_count_ += bytes;
+ if (byte_count_ < 0)
+ byte_count_ = kint32max;
+}
+
+void BackendImpl::OnWrite(int32 bytes) {
+ // We use the same implementation as OnRead... just log the number of bytes.
+ OnRead(bytes);
+}
+
+void BackendImpl::OnStatsTimer() {
+ stats_.OnEvent(Stats::TIMER);
+ int64 time = stats_.GetCounter(Stats::TIMER);
+ int64 current = stats_.GetCounter(Stats::OPEN_ENTRIES);
+
+ // OPEN_ENTRIES is a sampled average of the number of open entries, avoiding
+ // the bias towards 0.
+ if (num_refs_ && (current != num_refs_)) {
+ int64 diff = (num_refs_ - current) / 50;
+ if (!diff)
+ diff = num_refs_ > current ? 1 : -1;
+ current = current + diff;
+ stats_.SetCounter(Stats::OPEN_ENTRIES, current);
+ stats_.SetCounter(Stats::MAX_ENTRIES, max_refs_);
+ }
+
+ CACHE_UMA(COUNTS, "NumberOfReferences", 0, num_refs_);
+
+ CACHE_UMA(COUNTS_10000, "EntryAccessRate", 0, entry_count_);
+ CACHE_UMA(COUNTS, "ByteIORate", 0, byte_count_ / 1024);
+
+ // These values cover about 99.5% of the population (Oct 2011).
+ user_load_ = (entry_count_ > 300 || byte_count_ > 7 * 1024 * 1024);
+ entry_count_ = 0;
+ byte_count_ = 0;
+ up_ticks_++;
+
+ if (!data_)
+ first_timer_ = false;
+ if (first_timer_) {
+ first_timer_ = false;
+ if (ShouldReportAgain())
+ ReportStats();
+ }
+
+ // Save stats to disk at 5 min intervals.
+ if (time % 10 == 0)
+ StoreStats();
+}
+
+void BackendImpl::SetUnitTestMode() {
+ user_flags_ |= kUnitTestMode;
+ unit_test_ = true;
+}
+
+void BackendImpl::SetUpgradeMode() {
+ user_flags_ |= kUpgradeMode;
+ read_only_ = true;
+}
+
+void BackendImpl::SetNewEviction() {
+ user_flags_ |= kNewEviction;
+ new_eviction_ = true;
+}
+
+void BackendImpl::SetFlags(uint32 flags) {
+ user_flags_ |= flags;
+}
+
+int BackendImpl::FlushQueueForTest(const CompletionCallback& callback) {
+ background_queue_.FlushQueue(callback);
+ return net::ERR_IO_PENDING;
+}
+
+void BackendImpl::TrimForTest(bool empty) {
+ eviction_.SetTestMode();
+ eviction_.TrimCache(empty);
+}
+
+void BackendImpl::TrimDeletedListForTest(bool empty) {
+ eviction_.SetTestMode();
+ eviction_.TrimDeletedList(empty);
+}
+
+int BackendImpl::SelfCheck() {
+ if (!init_) {
+ LOG(ERROR) << "Init failed";
+ return ERR_INIT_FAILED;
+ }
+
+ int num_entries = rankings_.SelfCheck();
+ if (num_entries < 0) {
+ LOG(ERROR) << "Invalid rankings list, error " << num_entries;
+#if !defined(NET_BUILD_STRESS_CACHE)
+ return num_entries;
+#endif
+ }
+
+ if (num_entries != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries mismatch";
+#if !defined(NET_BUILD_STRESS_CACHE)
+ return ERR_NUM_ENTRIES_MISMATCH;
+#endif
+ }
+
+ return CheckAllEntries();
+}
+
+// ------------------------------------------------------------------------
+
+net::CacheType BackendImpl::GetCacheType() const {
+ return cache_type_;
+}
+
+int32 BackendImpl::GetEntryCount() const {
+ if (!index_.get() || disabled_)
+ return 0;
+ // num_entries includes entries already evicted.
+ int32 not_deleted = data_->header.num_entries -
+ data_->header.lru.sizes[Rankings::DELETED];
+
+ if (not_deleted < 0) {
+ NOTREACHED();
+ not_deleted = 0;
+ }
+
+ return not_deleted;
+}
+
+EntryImpl* BackendImpl::OpenEntryImpl(const std::string& key) {
+ if (disabled_)
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ uint32 hash = base::Hash(key);
+ Trace("Open hash 0x%x", hash);
+
+ bool error;
+ EntryImpl* cache_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (cache_entry && ENTRY_NORMAL != cache_entry->entry()->Data()->state) {
+ // The entry was already evicted.
+ cache_entry->Release();
+ cache_entry = NULL;
+ }
+
+ int current_size = data_->header.num_bytes / (1024 * 1024);
+ int64 total_hours = stats_.GetCounter(Stats::TIMER) / 120;
+ int64 no_use_hours = stats_.GetCounter(Stats::LAST_REPORT_TIMER) / 120;
+ int64 use_hours = total_hours - no_use_hours;
+
+ if (!cache_entry) {
+ CACHE_UMA(AGE_MS, "OpenTime.Miss", 0, start);
+ CACHE_UMA(COUNTS_10000, "AllOpenBySize.Miss", 0, current_size);
+ CACHE_UMA(HOURS, "AllOpenByTotalHours.Miss", 0, total_hours);
+ CACHE_UMA(HOURS, "AllOpenByUseHours.Miss", 0, use_hours);
+ stats_.OnEvent(Stats::OPEN_MISS);
+ return NULL;
+ }
+
+ eviction_.OnOpenEntry(cache_entry);
+ entry_count_++;
+
+ Trace("Open hash 0x%x end: 0x%x", hash,
+ cache_entry->entry()->address().value());
+ CACHE_UMA(AGE_MS, "OpenTime", 0, start);
+ CACHE_UMA(COUNTS_10000, "AllOpenBySize.Hit", 0, current_size);
+ CACHE_UMA(HOURS, "AllOpenByTotalHours.Hit", 0, total_hours);
+ CACHE_UMA(HOURS, "AllOpenByUseHours.Hit", 0, use_hours);
+ stats_.OnEvent(Stats::OPEN_HIT);
+ SIMPLE_STATS_COUNTER("disk_cache.hit");
+ return cache_entry;
+}
+
+EntryImpl* BackendImpl::CreateEntryImpl(const std::string& key) {
+ if (disabled_ || key.empty())
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ Trace("Create hash 0x%x", hash);
+
+ scoped_refptr<EntryImpl> parent;
+ Addr entry_address(data_->table[hash & mask_]);
+ if (entry_address.is_initialized()) {
+ // We have an entry already. It could be the one we are looking for, or just
+ // a hash conflict.
+ bool error;
+ EntryImpl* old_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (old_entry)
+ return ResurrectEntry(old_entry);
+
+ EntryImpl* parent_entry = MatchEntry(key, hash, true, Addr(), &error);
+ DCHECK(!error);
+ if (parent_entry) {
+ parent.swap(&parent_entry);
+ } else if (data_->table[hash & mask_]) {
+ // We should have corrected the problem.
+ NOTREACHED();
+ return NULL;
+ }
+ }
+
+ // The general flow is to allocate disk space and initialize the entry data,
+ // followed by saving that to disk, then linking the entry though the index
+ // and finally through the lists. If there is a crash in this process, we may
+ // end up with:
+ // a. Used, unreferenced empty blocks on disk (basically just garbage).
+ // b. Used, unreferenced but meaningful data on disk (more garbage).
+ // c. A fully formed entry, reachable only through the index.
+ // d. A fully formed entry, also reachable through the lists, but still dirty.
+ //
+ // Anything after (b) can be automatically cleaned up. We may consider saving
+ // the current operation (as we do while manipulating the lists) so that we
+ // can detect and cleanup (a) and (b).
+
+ int num_blocks = EntryImpl::NumBlocksForEntry(key.size());
+ if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ Addr node_address(0);
+ if (!block_files_.CreateBlock(RANKINGS, 1, &node_address)) {
+ block_files_.DeleteBlock(entry_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(
+ new EntryImpl(this, entry_address, false));
+ IncreaseNumRefs();
+
+ if (!cache_entry->CreateEntry(node_address, key, hash)) {
+ block_files_.DeleteBlock(entry_address, false);
+ block_files_.DeleteBlock(node_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ cache_entry->BeginLogging(net_log_, true);
+
+ // We are not failing the operation; let's add this to the map.
+ open_entries_[entry_address.value()] = cache_entry.get();
+
+ // Save the entry.
+ cache_entry->entry()->Store();
+ cache_entry->rankings()->Store();
+ IncreaseNumEntries();
+ entry_count_++;
+
+ // Link this entry through the index.
+ if (parent.get()) {
+ parent->SetNextAddress(entry_address);
+ } else {
+ data_->table[hash & mask_] = entry_address.value();
+ }
+
+ // Link this entry through the lists.
+ eviction_.OnCreateEntry(cache_entry.get());
+
+ CACHE_UMA(AGE_MS, "CreateTime", 0, start);
+ stats_.OnEvent(Stats::CREATE_HIT);
+ SIMPLE_STATS_COUNTER("disk_cache.miss");
+ Trace("create entry hit ");
+ FlushIndex();
+ cache_entry->AddRef();
+ return cache_entry.get();
+}
+
+int BackendImpl::SyncDoomEntry(const std::string& key) {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* entry = OpenEntryImpl(key);
+ if (!entry)
+ return net::ERR_FAILED;
+
+ entry->DoomImpl();
+ entry->Release();
+ return net::OK;
+}
+
+int BackendImpl::SyncDoomAllEntries() {
+ // This is not really an error, but it is an interesting condition.
+ ReportError(ERR_CACHE_DOOMED);
+ stats_.OnEvent(Stats::DOOM_CACHE);
+ if (!num_refs_) {
+ RestartCache(false);
+ return disabled_ ? net::ERR_FAILED : net::OK;
+ } else {
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ eviction_.TrimCache(true);
+ return net::OK;
+ }
+}
+
+int BackendImpl::SyncDoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time) {
+ DCHECK_NE(net::APP_CACHE, cache_type_);
+ if (end_time.is_null())
+ return SyncDoomEntriesSince(initial_time);
+
+ DCHECK(end_time >= initial_time);
+
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ EntryImpl* node;
+ void* iter = NULL;
+ EntryImpl* next = OpenNextEntryImpl(&iter);
+ if (!next)
+ return net::OK;
+
+ while (next) {
+ node = next;
+ next = OpenNextEntryImpl(&iter);
+
+ if (node->GetLastUsed() >= initial_time &&
+ node->GetLastUsed() < end_time) {
+ node->DoomImpl();
+ } else if (node->GetLastUsed() < initial_time) {
+ if (next)
+ next->Release();
+ next = NULL;
+ SyncEndEnumeration(iter);
+ }
+
+ node->Release();
+ }
+
+ return net::OK;
+}
+
+// We use OpenNextEntryImpl to retrieve elements from the cache, until we get
+// entries that are too old.
+int BackendImpl::SyncDoomEntriesSince(const base::Time initial_time) {
+ DCHECK_NE(net::APP_CACHE, cache_type_);
+ if (disabled_)
+ return net::ERR_FAILED;
+
+ stats_.OnEvent(Stats::DOOM_RECENT);
+ for (;;) {
+ void* iter = NULL;
+ EntryImpl* entry = OpenNextEntryImpl(&iter);
+ if (!entry)
+ return net::OK;
+
+ if (initial_time > entry->GetLastUsed()) {
+ entry->Release();
+ SyncEndEnumeration(iter);
+ return net::OK;
+ }
+
+ entry->DoomImpl();
+ entry->Release();
+ SyncEndEnumeration(iter); // Dooming the entry invalidates the iterator.
+ }
+}
+
+int BackendImpl::OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ background_queue_.OpenNextEntry(iter, next_entry, callback);
+ return net::ERR_IO_PENDING;
+}
+
+void BackendImpl::EndEnumeration(void** iter) {
+ background_queue_.EndEnumeration(*iter);
+ *iter = NULL;
+}
+
+void BackendImpl::GetStats(StatsItems* stats) {
+ if (disabled_)
+ return;
+
+ std::pair<std::string, std::string> item;
+
+ item.first = "Entries";
+ item.second = base::StringPrintf("%d", data_->header.num_entries);
+ stats->push_back(item);
+
+ item.first = "Pending IO";
+ item.second = base::StringPrintf("%d", num_pending_io_);
+ stats->push_back(item);
+
+ item.first = "Max size";
+ item.second = base::StringPrintf("%d", max_size_);
+ stats->push_back(item);
+
+ item.first = "Current size";
+ item.second = base::StringPrintf("%d", data_->header.num_bytes);
+ stats->push_back(item);
+
+ item.first = "Cache type";
+ item.second = "Blockfile Cache";
+ stats->push_back(item);
+
+ stats_.GetItems(stats);
+}
+
+void BackendImpl::SyncOnExternalCacheHit(const std::string& key) {
+ if (disabled_)
+ return;
+
+ uint32 hash = base::Hash(key);
+ bool error;
+ EntryImpl* cache_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (cache_entry) {
+ if (ENTRY_NORMAL == cache_entry->entry()->Data()->state) {
+ UpdateRank(cache_entry, cache_type() == net::SHADER_CACHE);
+ }
+ cache_entry->Release();
+ }
+}
+
+// ------------------------------------------------------------------------
+
+// The maximum cache size will be either set explicitly by the caller, or
+// calculated by this code.
+void BackendImpl::AdjustMaxCacheSize(int table_len) {
+ if (max_size_)
+ return;
+
+ // If table_len is provided, the index file exists.
+ DCHECK(!table_len || data_->header.magic);
+
+ // The user is not setting the size, let's figure it out.
+ int64 available = base::SysInfo::AmountOfFreeDiskSpace(path_);
+ if (available < 0) {
+ max_size_ = kDefaultCacheSize;
+ return;
+ }
+
+ if (table_len)
+ available += data_->header.num_bytes;
+
+ max_size_ = PreferedCacheSize(available);
+
+ // Let's not use more than the default size while we tune-up the performance
+ // of bigger caches. TODO(rvargas): remove this limit.
+ if (max_size_ > kDefaultCacheSize * 4)
+ max_size_ = kDefaultCacheSize * 4;
+
+ if (!table_len)
+ return;
+
+ // If we already have a table, adjust the size to it.
+ int current_max_size = MaxStorageSizeForTable(table_len);
+ if (max_size_ > current_max_size)
+ max_size_= current_max_size;
+}
+
+bool BackendImpl::InitStats() {
+ Addr address(data_->header.stats);
+ int size = stats_.StorageSize();
+
+ if (!address.is_initialized()) {
+ FileType file_type = Addr::RequiredFileType(size);
+ DCHECK_NE(file_type, EXTERNAL);
+ int num_blocks = Addr::RequiredBlocks(size, file_type);
+
+ if (!CreateBlock(file_type, num_blocks, &address))
+ return false;
+ return stats_.Init(NULL, 0, address);
+ }
+
+ if (!address.is_block_file()) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Load the required data.
+ size = address.num_blocks() * address.BlockSize();
+ MappedFile* file = File(address);
+ if (!file)
+ return false;
+
+ scoped_ptr<char[]> data(new char[size]);
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (!file->Read(data.get(), size, offset))
+ return false;
+
+ if (!stats_.Init(data.get(), size, address))
+ return false;
+ if (cache_type_ == net::DISK_CACHE && ShouldReportAgain())
+ stats_.InitSizeHistogram();
+ return true;
+}
+
+void BackendImpl::StoreStats() {
+ int size = stats_.StorageSize();
+ scoped_ptr<char[]> data(new char[size]);
+ Addr address;
+ size = stats_.SerializeStats(data.get(), size, &address);
+ DCHECK(size);
+ if (!address.is_initialized())
+ return;
+
+ MappedFile* file = File(address);
+ if (!file)
+ return;
+
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ file->Write(data.get(), size, offset); // ignore result.
+}
+
+void BackendImpl::RestartCache(bool failure) {
+ int64 errors = stats_.GetCounter(Stats::FATAL_ERROR);
+ int64 full_dooms = stats_.GetCounter(Stats::DOOM_CACHE);
+ int64 partial_dooms = stats_.GetCounter(Stats::DOOM_RECENT);
+ int64 last_report = stats_.GetCounter(Stats::LAST_REPORT);
+
+ PrepareForRestart();
+ if (failure) {
+ DCHECK(!num_refs_);
+ DCHECK(!open_entries_.size());
+ DelayedCacheCleanup(path_);
+ } else {
+ DeleteCache(path_, false);
+ }
+
+ // Don't call Init() if directed by the unit test: we are simulating a failure
+ // trying to re-enable the cache.
+ if (unit_test_)
+ init_ = true; // Let the destructor do proper cleanup.
+ else if (SyncInit() == net::OK) {
+ stats_.SetCounter(Stats::FATAL_ERROR, errors);
+ stats_.SetCounter(Stats::DOOM_CACHE, full_dooms);
+ stats_.SetCounter(Stats::DOOM_RECENT, partial_dooms);
+ stats_.SetCounter(Stats::LAST_REPORT, last_report);
+ }
+}
+
+void BackendImpl::PrepareForRestart() {
+ // Reset the mask_ if it was not given by the user.
+ if (!(user_flags_ & kMask))
+ mask_ = 0;
+
+ if (!(user_flags_ & kNewEviction))
+ new_eviction_ = false;
+
+ disabled_ = true;
+ data_->header.crash = 0;
+ index_->Flush();
+ index_ = NULL;
+ data_ = NULL;
+ block_files_.CloseFiles();
+ rankings_.Reset();
+ init_ = false;
+ restarted_ = true;
+}
+
+void BackendImpl::CleanupCache() {
+ Trace("Backend Cleanup");
+ eviction_.Stop();
+ timer_.reset();
+
+ if (init_) {
+ StoreStats();
+ if (data_)
+ data_->header.crash = 0;
+
+ if (user_flags_ & kNoRandom) {
+ // This is a net_unittest, verify that we are not 'leaking' entries.
+ File::WaitForPendingIO(&num_pending_io_);
+ DCHECK(!num_refs_);
+ } else {
+ File::DropPendingIO();
+ }
+ }
+ block_files_.CloseFiles();
+ FlushIndex();
+ index_ = NULL;
+ ptr_factory_.InvalidateWeakPtrs();
+ done_.Signal();
+}
+
+int BackendImpl::NewEntry(Addr address, EntryImpl** entry) {
+ EntriesMap::iterator it = open_entries_.find(address.value());
+ if (it != open_entries_.end()) {
+ // Easy job. This entry is already in memory.
+ EntryImpl* this_entry = it->second;
+ this_entry->AddRef();
+ *entry = this_entry;
+ return 0;
+ }
+
+ STRESS_DCHECK(block_files_.IsValid(address));
+
+ if (!address.SanityCheckForEntry()) {
+ LOG(WARNING) << "Wrong entry address.";
+ STRESS_NOTREACHED();
+ return ERR_INVALID_ADDRESS;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(
+ new EntryImpl(this, address, read_only_));
+ IncreaseNumRefs();
+ *entry = NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ if (!cache_entry->entry()->Load())
+ return ERR_READ_FAILURE;
+
+ if (IsLoaded()) {
+ CACHE_UMA(AGE_MS, "LoadTime", 0, start);
+ }
+
+ if (!cache_entry->SanityCheck()) {
+ LOG(WARNING) << "Messed up entry found.";
+ STRESS_NOTREACHED();
+ return ERR_INVALID_ENTRY;
+ }
+
+ STRESS_DCHECK(block_files_.IsValid(
+ Addr(cache_entry->entry()->Data()->rankings_node)));
+
+ if (!cache_entry->LoadNodeAddress())
+ return ERR_READ_FAILURE;
+
+ if (!rankings_.SanityCheck(cache_entry->rankings(), false)) {
+ STRESS_NOTREACHED();
+ cache_entry->SetDirtyFlag(0);
+ // Don't remove this from the list (it is not linked properly). Instead,
+ // break the link back to the entry because it is going away, and leave the
+ // rankings node to be deleted if we find it through a list.
+ rankings_.SetContents(cache_entry->rankings(), 0);
+ } else if (!rankings_.DataSanityCheck(cache_entry->rankings(), false)) {
+ STRESS_NOTREACHED();
+ cache_entry->SetDirtyFlag(0);
+ rankings_.SetContents(cache_entry->rankings(), address.value());
+ }
+
+ if (!cache_entry->DataSanityCheck()) {
+ LOG(WARNING) << "Messed up entry found.";
+ cache_entry->SetDirtyFlag(0);
+ cache_entry->FixForDelete();
+ }
+
+ // Prevent overwriting the dirty flag on the destructor.
+ cache_entry->SetDirtyFlag(GetCurrentEntryId());
+
+ if (cache_entry->dirty()) {
+ Trace("Dirty entry 0x%p 0x%x", reinterpret_cast<void*>(cache_entry.get()),
+ address.value());
+ }
+
+ open_entries_[address.value()] = cache_entry.get();
+
+ cache_entry->BeginLogging(net_log_, false);
+ cache_entry.swap(entry);
+ return 0;
+}
+
+// This is the actual implementation for OpenNextEntry and OpenPrevEntry.
+EntryImpl* BackendImpl::OpenFollowingEntry(bool forward, void** iter) {
+ if (disabled_)
+ return NULL;
+
+ DCHECK(iter);
+
+ const int kListsToSearch = 3;
+ scoped_refptr<EntryImpl> entries[kListsToSearch];
+ scoped_ptr<Rankings::Iterator> iterator(
+ reinterpret_cast<Rankings::Iterator*>(*iter));
+ *iter = NULL;
+
+ if (!iterator.get()) {
+ iterator.reset(new Rankings::Iterator(&rankings_));
+ bool ret = false;
+
+ // Get an entry from each list.
+ for (int i = 0; i < kListsToSearch; i++) {
+ EntryImpl* temp = NULL;
+ ret |= OpenFollowingEntryFromList(forward, static_cast<Rankings::List>(i),
+ &iterator->nodes[i], &temp);
+ entries[i].swap(&temp); // The entry was already addref'd.
+ }
+ if (!ret)
+ return NULL;
+ } else {
+ // Get the next entry from the last list, and the actual entries for the
+ // elements on the other lists.
+ for (int i = 0; i < kListsToSearch; i++) {
+ EntryImpl* temp = NULL;
+ if (iterator->list == i) {
+ OpenFollowingEntryFromList(forward, iterator->list,
+ &iterator->nodes[i], &temp);
+ } else {
+ temp = GetEnumeratedEntry(iterator->nodes[i],
+ static_cast<Rankings::List>(i));
+ }
+
+ entries[i].swap(&temp); // The entry was already addref'd.
+ }
+ }
+
+ int newest = -1;
+ int oldest = -1;
+ Time access_times[kListsToSearch];
+ for (int i = 0; i < kListsToSearch; i++) {
+ if (entries[i].get()) {
+ access_times[i] = entries[i]->GetLastUsed();
+ if (newest < 0) {
+ DCHECK_LT(oldest, 0);
+ newest = oldest = i;
+ continue;
+ }
+ if (access_times[i] > access_times[newest])
+ newest = i;
+ if (access_times[i] < access_times[oldest])
+ oldest = i;
+ }
+ }
+
+ if (newest < 0 || oldest < 0)
+ return NULL;
+
+ EntryImpl* next_entry;
+ if (forward) {
+ next_entry = entries[newest].get();
+ iterator->list = static_cast<Rankings::List>(newest);
+ } else {
+ next_entry = entries[oldest].get();
+ iterator->list = static_cast<Rankings::List>(oldest);
+ }
+
+ *iter = iterator.release();
+ next_entry->AddRef();
+ return next_entry;
+}
+
+void BackendImpl::AddStorageSize(int32 bytes) {
+ data_->header.num_bytes += bytes;
+ DCHECK_GE(data_->header.num_bytes, 0);
+}
+
+void BackendImpl::SubstractStorageSize(int32 bytes) {
+ data_->header.num_bytes -= bytes;
+ DCHECK_GE(data_->header.num_bytes, 0);
+}
+
+void BackendImpl::IncreaseNumRefs() {
+ num_refs_++;
+ if (max_refs_ < num_refs_)
+ max_refs_ = num_refs_;
+}
+
+void BackendImpl::DecreaseNumRefs() {
+ DCHECK(num_refs_);
+ num_refs_--;
+
+ if (!num_refs_ && disabled_)
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&BackendImpl::RestartCache, GetWeakPtr(), true));
+}
+
+void BackendImpl::IncreaseNumEntries() {
+ data_->header.num_entries++;
+ DCHECK_GT(data_->header.num_entries, 0);
+}
+
+void BackendImpl::DecreaseNumEntries() {
+ data_->header.num_entries--;
+ if (data_->header.num_entries < 0) {
+ NOTREACHED();
+ data_->header.num_entries = 0;
+ }
+}
+
+int BackendImpl::SyncInit() {
+#if defined(NET_BUILD_STRESS_CACHE)
+ // Start evictions right away.
+ up_ticks_ = kTrimDelay * 2;
+#endif
+ DCHECK(!init_);
+ if (init_)
+ return net::ERR_FAILED;
+
+ bool create_files = false;
+ if (!InitBackingStore(&create_files)) {
+ ReportError(ERR_STORAGE_ERROR);
+ return net::ERR_FAILED;
+ }
+
+ num_refs_ = num_pending_io_ = max_refs_ = 0;
+ entry_count_ = byte_count_ = 0;
+
+ if (!restarted_) {
+ buffer_bytes_ = 0;
+ trace_object_ = TraceObject::GetTraceObject();
+ // Create a recurrent timer of 30 secs.
+ int timer_delay = unit_test_ ? 1000 : 30000;
+ timer_.reset(new base::RepeatingTimer<BackendImpl>());
+ timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(timer_delay), this,
+ &BackendImpl::OnStatsTimer);
+ }
+
+ init_ = true;
+ Trace("Init");
+
+ if (data_->header.experiment != NO_EXPERIMENT &&
+ cache_type_ != net::DISK_CACHE) {
+ // No experiment for other caches.
+ return net::ERR_FAILED;
+ }
+
+ if (!(user_flags_ & kNoRandom)) {
+ // The unit test controls directly what to test.
+ new_eviction_ = (cache_type_ == net::DISK_CACHE);
+ }
+
+ if (!CheckIndex()) {
+ ReportError(ERR_INIT_FAILED);
+ return net::ERR_FAILED;
+ }
+
+ if (!restarted_ && (create_files || !data_->header.num_entries))
+ ReportError(ERR_CACHE_CREATED);
+
+ if (!(user_flags_ & kNoRandom) && cache_type_ == net::DISK_CACHE &&
+ !InitExperiment(&data_->header, create_files)) {
+ return net::ERR_FAILED;
+ }
+
+ // We don't care if the value overflows. The only thing we care about is that
+ // the id cannot be zero, because that value is used as "not dirty".
+ // Increasing the value once per second gives us many years before we start
+ // having collisions.
+ data_->header.this_id++;
+ if (!data_->header.this_id)
+ data_->header.this_id++;
+
+ bool previous_crash = (data_->header.crash != 0);
+ data_->header.crash = 1;
+
+ if (!block_files_.Init(create_files))
+ return net::ERR_FAILED;
+
+ // We want to minimize the changes to cache for an AppCache.
+ if (cache_type() == net::APP_CACHE) {
+ DCHECK(!new_eviction_);
+ read_only_ = true;
+ } else if (cache_type() == net::SHADER_CACHE) {
+ DCHECK(!new_eviction_);
+ }
+
+ eviction_.Init(this);
+
+ // stats_ and rankings_ may end up calling back to us so we better be enabled.
+ disabled_ = false;
+ if (!InitStats())
+ return net::ERR_FAILED;
+
+ disabled_ = !rankings_.Init(this, new_eviction_);
+
+#if defined(STRESS_CACHE_EXTENDED_VALIDATION)
+ trace_object_->EnableTracing(false);
+ int sc = SelfCheck();
+ if (sc < 0 && sc != ERR_NUM_ENTRIES_MISMATCH)
+ NOTREACHED();
+ trace_object_->EnableTracing(true);
+#endif
+
+ if (previous_crash) {
+ ReportError(ERR_PREVIOUS_CRASH);
+ } else if (!restarted_) {
+ ReportError(ERR_NO_ERROR);
+ }
+
+ FlushIndex();
+
+ return disabled_ ? net::ERR_FAILED : net::OK;
+}
+
+EntryImpl* BackendImpl::ResurrectEntry(EntryImpl* deleted_entry) {
+ if (ENTRY_NORMAL == deleted_entry->entry()->Data()->state) {
+ deleted_entry->Release();
+ stats_.OnEvent(Stats::CREATE_MISS);
+ Trace("create entry miss ");
+ return NULL;
+ }
+
+ // We are attempting to create an entry and found out that the entry was
+ // previously deleted.
+
+ eviction_.OnCreateEntry(deleted_entry);
+ entry_count_++;
+
+ stats_.OnEvent(Stats::RESURRECT_HIT);
+ Trace("Resurrect entry hit ");
+ return deleted_entry;
+}
+
+EntryImpl* BackendImpl::CreateEntryImpl(const std::string& key) {
+ if (disabled_ || key.empty())
+ return NULL;
+
+ TimeTicks start = TimeTicks::Now();
+ Trace("Create hash 0x%x", hash);
+
+ scoped_refptr<EntryImpl> parent;
+ Addr entry_address(data_->table[hash & mask_]);
+ if (entry_address.is_initialized()) {
+ // We have an entry already. It could be the one we are looking for, or just
+ // a hash conflict.
+ bool error;
+ EntryImpl* old_entry = MatchEntry(key, hash, false, Addr(), &error);
+ if (old_entry)
+ return ResurrectEntry(old_entry);
+
+ EntryImpl* parent_entry = MatchEntry(key, hash, true, Addr(), &error);
+ DCHECK(!error);
+ if (parent_entry) {
+ parent.swap(&parent_entry);
+ } else if (data_->table[hash & mask_]) {
+ // We should have corrected the problem.
+ NOTREACHED();
+ return NULL;
+ }
+ }
+
+ // The general flow is to allocate disk space and initialize the entry data,
+ // followed by saving that to disk, then linking the entry though the index
+ // and finally through the lists. If there is a crash in this process, we may
+ // end up with:
+ // a. Used, unreferenced empty blocks on disk (basically just garbage).
+ // b. Used, unreferenced but meaningful data on disk (more garbage).
+ // c. A fully formed entry, reachable only through the index.
+ // d. A fully formed entry, also reachable through the lists, but still dirty.
+ //
+ // Anything after (b) can be automatically cleaned up. We may consider saving
+ // the current operation (as we do while manipulating the lists) so that we
+ // can detect and cleanup (a) and (b).
+
+ int num_blocks = EntryImpl::NumBlocksForEntry(key.size());
+ if (!block_files_.CreateBlock(BLOCK_256, num_blocks, &entry_address)) {
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ Addr node_address(0);
+ if (!block_files_.CreateBlock(RANKINGS, 1, &node_address)) {
+ block_files_.DeleteBlock(entry_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ scoped_refptr<EntryImpl> cache_entry(
+ new EntryImpl(this, entry_address, false));
+ IncreaseNumRefs();
+
+ if (!cache_entry->CreateEntry(node_address, key, hash)) {
+ block_files_.DeleteBlock(entry_address, false);
+ block_files_.DeleteBlock(node_address, false);
+ LOG(ERROR) << "Create entry failed " << key.c_str();
+ stats_.OnEvent(Stats::CREATE_ERROR);
+ return NULL;
+ }
+
+ cache_entry->BeginLogging(net_log_, true);
+
+ // We are not failing the operation; let's add this to the map.
+ open_entries_[entry_address.value()] = cache_entry;
+
+ // Save the entry.
+ cache_entry->entry()->Store();
+ cache_entry->rankings()->Store();
+ IncreaseNumEntries();
+ entry_count_++;
+
+ // Link this entry through the index.
+ if (parent.get()) {
+ parent->SetNextAddress(entry_address);
+ } else {
+ data_->table[hash & mask_] = entry_address.value();
+ }
+
+ // Link this entry through the lists.
+ eviction_.OnCreateEntry(cache_entry);
+
+ CACHE_UMA(AGE_MS, "CreateTime", 0, start);
+ stats_.OnEvent(Stats::CREATE_HIT);
+ SIMPLE_STATS_COUNTER("disk_cache.miss");
+ Trace("create entry hit ");
+ FlushIndex();
+ cache_entry->AddRef();
+ return cache_entry.get();
+}
+
+void BackendImpl::LogStats() {
+ StatsItems stats;
+ GetStats(&stats);
+
+ for (size_t index = 0; index < stats.size(); index++)
+ VLOG(1) << stats[index].first << ": " << stats[index].second;
+}
+
+void BackendImpl::ReportStats() {
+ CACHE_UMA(COUNTS, "Entries", 0, data_->header.num_entries);
+
+ int current_size = data_->header.num_bytes / (1024 * 1024);
+ int max_size = max_size_ / (1024 * 1024);
+ int hit_ratio_as_percentage = stats_.GetHitRatio();
+
+ CACHE_UMA(COUNTS_10000, "Size2", 0, current_size);
+ // For any bin in HitRatioBySize2, the hit ratio of caches of that size is the
+ // ratio of that bin's total count to the count in the same bin in the Size2
+ // histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(COUNTS_10000, "HitRatioBySize2", 0, current_size);
+ CACHE_UMA(COUNTS_10000, "MaxSize2", 0, max_size);
+ if (!max_size)
+ max_size++;
+ CACHE_UMA(PERCENTAGE, "UsedSpace", 0, current_size * 100 / max_size);
+
+ CACHE_UMA(COUNTS_10000, "AverageOpenEntries2", 0,
+ static_cast<int>(stats_.GetCounter(Stats::OPEN_ENTRIES)));
+ CACHE_UMA(COUNTS_10000, "MaxOpenEntries2", 0,
+ static_cast<int>(stats_.GetCounter(Stats::MAX_ENTRIES)));
+ stats_.SetCounter(Stats::MAX_ENTRIES, 0);
+
+ CACHE_UMA(COUNTS_10000, "TotalFatalErrors", 0,
+ static_cast<int>(stats_.GetCounter(Stats::FATAL_ERROR)));
+ CACHE_UMA(COUNTS_10000, "TotalDoomCache", 0,
+ static_cast<int>(stats_.GetCounter(Stats::DOOM_CACHE)));
+ CACHE_UMA(COUNTS_10000, "TotalDoomRecentEntries", 0,
+ static_cast<int>(stats_.GetCounter(Stats::DOOM_RECENT)));
+ stats_.SetCounter(Stats::FATAL_ERROR, 0);
+ stats_.SetCounter(Stats::DOOM_CACHE, 0);
+ stats_.SetCounter(Stats::DOOM_RECENT, 0);
+
+ int64 total_hours = stats_.GetCounter(Stats::TIMER) / 120;
+ if (!data_->header.create_time || !data_->header.lru.filled) {
+ int cause = data_->header.create_time ? 0 : 1;
+ if (!data_->header.lru.filled)
+ cause |= 2;
+ CACHE_UMA(CACHE_ERROR, "ShortReport", 0, cause);
+ CACHE_UMA(HOURS, "TotalTimeNotFull", 0, static_cast<int>(total_hours));
+ return;
+ }
+
+ // This is an up to date client that will report FirstEviction() data. After
+ // that event, start reporting this:
+
+ CACHE_UMA(HOURS, "TotalTime", 0, static_cast<int>(total_hours));
+ // For any bin in HitRatioByTotalTime, the hit ratio of caches of that total
+ // time is the ratio of that bin's total count to the count in the same bin in
+ // the TotalTime histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(HOURS, "HitRatioByTotalTime", 0, implicit_cast<int>(total_hours));
+
+ int64 use_hours = stats_.GetCounter(Stats::LAST_REPORT_TIMER) / 120;
+ stats_.SetCounter(Stats::LAST_REPORT_TIMER, stats_.GetCounter(Stats::TIMER));
+
+ // We may see users with no use_hours at this point if this is the first time
+ // we are running this code.
+ if (use_hours)
+ use_hours = total_hours - use_hours;
+
+ if (!use_hours || !GetEntryCount() || !data_->header.num_bytes)
+ return;
+
+ CACHE_UMA(HOURS, "UseTime", 0, static_cast<int>(use_hours));
+ // For any bin in HitRatioByUseTime, the hit ratio of caches of that use time
+ // is the ratio of that bin's total count to the count in the same bin in the
+ // UseTime histogram.
+ if (base::RandInt(0, 99) < hit_ratio_as_percentage)
+ CACHE_UMA(HOURS, "HitRatioByUseTime", 0, implicit_cast<int>(use_hours));
+ CACHE_UMA(PERCENTAGE, "HitRatio", 0, hit_ratio_as_percentage);
+
+ int64 trim_rate = stats_.GetCounter(Stats::TRIM_ENTRY) / use_hours;
+ CACHE_UMA(COUNTS, "TrimRate", 0, static_cast<int>(trim_rate));
+
+ int avg_size = data_->header.num_bytes / GetEntryCount();
+ CACHE_UMA(COUNTS, "EntrySize", 0, avg_size);
+ CACHE_UMA(COUNTS, "EntriesFull", 0, data_->header.num_entries);
+
+ CACHE_UMA(PERCENTAGE, "IndexLoad", 0,
+ data_->header.num_entries * 100 / (mask_ + 1));
+
+ int large_entries_bytes = stats_.GetLargeEntriesSize();
+ int large_ratio = large_entries_bytes * 100 / data_->header.num_bytes;
+ CACHE_UMA(PERCENTAGE, "LargeEntriesRatio", 0, large_ratio);
+
+ if (new_eviction_) {
+ CACHE_UMA(PERCENTAGE, "ResurrectRatio", 0, stats_.GetResurrectRatio());
+ CACHE_UMA(PERCENTAGE, "NoUseRatio", 0,
+ data_->header.lru.sizes[0] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "LowUseRatio", 0,
+ data_->header.lru.sizes[1] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "HighUseRatio", 0,
+ data_->header.lru.sizes[2] * 100 / data_->header.num_entries);
+ CACHE_UMA(PERCENTAGE, "DeletedRatio", 0,
+ data_->header.lru.sizes[4] * 100 / data_->header.num_entries);
+ }
+
+ stats_.ResetRatios();
+ stats_.SetCounter(Stats::TRIM_ENTRY, 0);
+
+ if (cache_type_ == net::DISK_CACHE)
+ block_files_.ReportStats();
+}
+
+void BackendImpl::ReportError(int error) {
+ STRESS_DCHECK(!error || error == ERR_PREVIOUS_CRASH ||
+ error == ERR_CACHE_CREATED);
+
+ // We transmit positive numbers, instead of direct error codes.
+ DCHECK_LE(error, 0);
+ CACHE_UMA(CACHE_ERROR, "Error", 0, error * -1);
+}
+
+bool BackendImpl::CheckIndex() {
+ DCHECK(data_);
+
+ size_t current_size = index_->GetLength();
+ if (current_size < sizeof(Index)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ if (new_eviction_) {
+ // We support versions 2.0 and 2.1, upgrading 2.0 to 2.1.
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion >> 16 != data_->header.version >> 16) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ if (kCurrentVersion == data_->header.version) {
+ // We need file version 2.1 for the new eviction algorithm.
+ UpgradeTo2_1();
+ }
+ } else {
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion != data_->header.version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ }
+
+ if (!data_->header.table_len) {
+ LOG(ERROR) << "Invalid table size";
+ return false;
+ }
+
+ if (current_size < GetIndexSize(data_->header.table_len) ||
+ data_->header.table_len & (kBaseTableLen - 1)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ AdjustMaxCacheSize(data_->header.table_len);
+
+#if !defined(NET_BUILD_STRESS_CACHE)
+ if (data_->header.num_bytes < 0 ||
+ (max_size_ < kint32max - kDefaultCacheSize &&
+ data_->header.num_bytes > max_size_ + kDefaultCacheSize)) {
+ LOG(ERROR) << "Invalid cache (current) size";
+ return false;
+ }
+#endif
+
+ if (data_->header.num_entries < 0) {
+ LOG(ERROR) << "Invalid number of entries";
+ return false;
+ }
+
+ if (!mask_)
+ mask_ = data_->header.table_len - 1;
+
+ // Load the table into memory with a single read.
+ scoped_ptr<char[]> buf(new char[current_size]);
+ return index_->Read(buf.get(), current_size, 0);
+}
+
+int BackendImpl::CheckAllEntries() {
+ int num_dirty = 0;
+ int num_entries = 0;
+ DCHECK(mask_ < kuint32max);
+ for (unsigned int i = 0; i <= mask_; i++) {
+ Addr address(data_->table[i]);
+ if (!address.is_initialized())
+ continue;
+ for (;;) {
+ EntryImpl* tmp;
+ int ret = NewEntry(address, &tmp);
+ if (ret) {
+ STRESS_NOTREACHED();
+ return ret;
+ }
+ scoped_refptr<EntryImpl> cache_entry;
+ cache_entry.swap(&tmp);
+
+ if (cache_entry->dirty())
+ num_dirty++;
+ else if (CheckEntry(cache_entry.get()))
+ num_entries++;
+ else
+ return ERR_INVALID_ENTRY;
+
+ DCHECK_EQ(i, cache_entry->entry()->Data()->hash & mask_);
+ address.set_value(cache_entry->GetNextAddress());
+ if (!address.is_initialized())
+ break;
+ }
+ }
+
+ Trace("CheckAllEntries End");
+ if (num_entries + num_dirty != data_->header.num_entries) {
+ LOG(ERROR) << "Number of entries " << num_entries << " " << num_dirty <<
+ " " << data_->header.num_entries;
+ DCHECK_LT(num_entries, data_->header.num_entries);
+ return ERR_NUM_ENTRIES_MISMATCH;
+ }
+
+ return num_dirty;
+}
+
+bool BackendImpl::CheckEntry(EntryImpl* cache_entry) {
+ bool ok = block_files_.IsValid(cache_entry->entry()->address());
+ ok = ok && block_files_.IsValid(cache_entry->rankings()->address());
+ EntryStore* data = cache_entry->entry()->Data();
+ for (size_t i = 0; i < arraysize(data->data_addr); i++) {
+ if (data->data_addr[i]) {
+ Addr address(data->data_addr[i]);
+ if (address.is_block_file())
+ ok = ok && block_files_.IsValid(address);
+ }
+ }
+
+ return ok && cache_entry->rankings()->VerifyHash();
+}
+
+int BackendImpl::MaxBuffersSize() {
+ static int64 total_memory = base::SysInfo::AmountOfPhysicalMemory();
+ static bool done = false;
+
+ if (!done) {
+ const int kMaxBuffersSize = 30 * 1024 * 1024;
+
+ // We want to use up to 2% of the computer's memory.
+ total_memory = total_memory * 2 / 100;
+ if (total_memory > kMaxBuffersSize || total_memory <= 0)
+ total_memory = kMaxBuffersSize;
+
+ done = true;
+ }
+
+ return static_cast<int>(total_memory);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/backend_impl_v3.h b/chromium/net/disk_cache/v3/backend_impl_v3.h
new file mode 100644
index 00000000000..08cc5b1dd52
--- /dev/null
+++ b/chromium/net/disk_cache/v3/backend_impl_v3.h
@@ -0,0 +1,288 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_BACKEND_IMPL_H_
+#define NET_DISK_CACHE_BACKEND_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/timer/timer.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/eviction.h"
+#include "net/disk_cache/in_flight_backend_io.h"
+#include "net/disk_cache/rankings.h"
+#include "net/disk_cache/stats.h"
+#include "net/disk_cache/stress_support.h"
+#include "net/disk_cache/trace.h"
+
+namespace net {
+class NetLog;
+} // namespace net
+
+namespace disk_cache {
+
+enum BackendFlags {
+ kNone = 0,
+ kMask = 1, // A mask (for the index table) was specified.
+ kMaxSize = 1 << 1, // A maximum size was provided.
+ kUnitTestMode = 1 << 2, // We are modifying the behavior for testing.
+ kUpgradeMode = 1 << 3, // This is the upgrade tool (dump).
+ kNewEviction = 1 << 4, // Use of new eviction was specified.
+ kNoRandom = 1 << 5, // Don't add randomness to the behavior.
+ kNoLoadProtection = 1 << 6, // Don't act conservatively under load.
+ kNoBuffering = 1 << 7 // Disable extended IO buffering.
+};
+
+// This class implements the Backend interface. An object of this
+// class handles the operations of the cache for a particular profile.
+class NET_EXPORT_PRIVATE BackendImpl : public Backend {
+ friend class Eviction;
+ public:
+ BackendImpl(const base::FilePath& path, base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log);
+ // mask can be used to limit the usable size of the hash table, for testing.
+ BackendImpl(const base::FilePath& path, uint32 mask,
+ base::MessageLoopProxy* cache_thread, net::NetLog* net_log);
+ virtual ~BackendImpl();
+
+ // Performs general initialization for this current instance of the cache.
+ int Init(const CompletionCallback& callback);
+
+ // Same behavior as OpenNextEntry but walks the list from back to front.
+ int OpenPrevEntry(void** iter, Entry** prev_entry,
+ const CompletionCallback& callback);
+
+ // Sets the maximum size for the total amount of data stored by this instance.
+ bool SetMaxSize(int max_bytes);
+
+ // Sets the cache type for this backend.
+ void SetType(net::CacheType type);
+
+ // Creates a new storage block of size block_count.
+ bool CreateBlock(FileType block_type, int block_count,
+ Addr* block_address);
+
+ // Updates the ranking information for an entry.
+ void UpdateRank(EntryImpl* entry, bool modified);
+
+ // Permanently deletes an entry, but still keeps track of it.
+ void InternalDoomEntry(EntryImpl* entry);
+
+ // This method must be called when an entry is released for the last time, so
+ // the entry should not be used anymore. |address| is the cache address of the
+ // entry.
+ void OnEntryDestroyBegin(Addr address);
+
+ // This method must be called after all resources for an entry have been
+ // released.
+ void OnEntryDestroyEnd();
+
+ // If the data stored by the provided |rankings| points to an open entry,
+ // returns a pointer to that entry, otherwise returns NULL. Note that this
+ // method does NOT increase the ref counter for the entry.
+ EntryImpl* GetOpenEntry(CacheRankingsBlock* rankings) const;
+
+ // Returns the id being used on this run of the cache.
+ int32 GetCurrentEntryId() const;
+
+ // Returns the maximum size for a file to reside on the cache.
+ int MaxFileSize() const;
+
+ // A user data block is being created, extended or truncated.
+ void ModifyStorageSize(int32 old_size, int32 new_size);
+
+ // Logs requests that are denied due to being too big.
+ void TooMuchStorageRequested(int32 size);
+
+ // Returns true if a temporary buffer is allowed to be extended.
+ bool IsAllocAllowed(int current_size, int new_size);
+
+ // Tracks the release of |size| bytes by an entry buffer.
+ void BufferDeleted(int size);
+
+ // Only intended for testing the two previous methods.
+ int GetTotalBuffersSize() const {
+ return buffer_bytes_;
+ }
+
+ // Returns true if this instance seems to be under heavy load.
+ bool IsLoaded() const;
+
+ // Returns the full histogram name, for the given base |name| and experiment,
+ // and the current cache type. The name will be "DiskCache.t.name_e" where n
+ // is the cache type and e the provided |experiment|.
+ std::string HistogramName(const char* name, int experiment) const;
+
+ net::CacheType cache_type() const {
+ return cache_type_;
+ }
+
+ bool read_only() const {
+ return read_only_;
+ }
+
+ // Returns a weak pointer to this object.
+ base::WeakPtr<BackendImpl> GetWeakPtr();
+
+ // Returns true if we should send histograms for this user again. The caller
+ // must call this function only once per run (because it returns always the
+ // same thing on a given run).
+ bool ShouldReportAgain();
+
+ // Reports some data when we filled up the cache.
+ void FirstEviction();
+
+ // Called when an interesting event should be logged (counted).
+ void OnEvent(Stats::Counters an_event);
+
+ // Keeps track of payload access (doesn't include metadata).
+ void OnRead(int bytes);
+ void OnWrite(int bytes);
+
+ // Timer callback to calculate usage statistics.
+ void OnStatsTimer();
+
+ // Sets internal parameters to enable unit testing mode.
+ void SetUnitTestMode();
+
+ // Sets internal parameters to enable upgrade mode (for internal tools).
+ void SetUpgradeMode();
+
+ // Sets the eviction algorithm to version 2.
+ void SetNewEviction();
+
+ // Sets an explicit set of BackendFlags.
+ void SetFlags(uint32 flags);
+
+ // Sends a dummy operation through the operation queue, for unit tests.
+ int FlushQueueForTest(const CompletionCallback& callback);
+
+ // Trims an entry (all if |empty| is true) from the list of deleted
+ // entries. This method should be called directly on the cache thread.
+ void TrimForTest(bool empty);
+
+ // Trims an entry (all if |empty| is true) from the list of deleted
+ // entries. This method should be called directly on the cache thread.
+ void TrimDeletedListForTest(bool empty);
+
+ // Performs a simple self-check, and returns the number of dirty items
+ // or an error code (negative value).
+ int SelfCheck();
+
+ // Backend implementation.
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, Entry** entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(base::Time initial_time,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, Entry** next_entry,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(StatsItems* stats) OVERRIDE;
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ private:
+ typedef base::hash_map<CacheAddr, EntryImpl*> EntriesMap;
+
+ void AdjustMaxCacheSize(int table_len);
+
+ bool InitStats();
+ void StoreStats();
+
+ // Deletes the cache and starts again.
+ void RestartCache(bool failure);
+ void PrepareForRestart();
+
+ void CleanupCache();
+
+ // Creates a new entry object. Returns zero on success, or a disk_cache error
+ // on failure.
+ int NewEntry(Addr address, EntryImpl** entry);
+
+ // Opens the next or previous entry on a cache iteration.
+ EntryImpl* OpenFollowingEntry(bool forward, void** iter);
+
+ // Handles the used storage count.
+ void AddStorageSize(int32 bytes);
+ void SubstractStorageSize(int32 bytes);
+
+ // Update the number of referenced cache entries.
+ void IncreaseNumRefs();
+ void DecreaseNumRefs();
+ void IncreaseNumEntries();
+ void DecreaseNumEntries();
+
+ // Dumps current cache statistics to the log.
+ void LogStats();
+
+ // Send UMA stats.
+ void ReportStats();
+
+ // Reports an uncommon, recoverable error.
+ void ReportError(int error);
+
+ // Performs basic checks on the index file. Returns false on failure.
+ bool CheckIndex();
+
+ // Part of the self test. Returns the number or dirty entries, or an error.
+ int CheckAllEntries();
+
+ // Part of the self test. Returns false if the entry is corrupt.
+ bool CheckEntry(EntryImpl* cache_entry);
+
+ // Returns the maximum total memory for the memory buffers.
+ int MaxBuffersSize();
+
+ scoped_refptr<MappedFile> index_; // The main cache index.
+ base::FilePath path_; // Path to the folder used as backing storage.
+ BlockFiles block_files_; // Set of files used to store all data.
+ int32 max_size_; // Maximum data size for this instance.
+ Eviction eviction_; // Handler of the eviction algorithm.
+ EntriesMap open_entries_; // Map of open entries.
+ int num_refs_; // Number of referenced cache entries.
+ int max_refs_; // Max number of referenced cache entries.
+ int entry_count_; // Number of entries accessed lately.
+ int byte_count_; // Number of bytes read/written lately.
+ int buffer_bytes_; // Total size of the temporary entries' buffers.
+ int up_ticks_; // The number of timer ticks received (OnStatsTimer).
+ net::CacheType cache_type_;
+ int uma_report_; // Controls transmission of UMA data.
+ uint32 user_flags_; // Flags set by the user.
+ bool init_; // controls the initialization of the system.
+ bool restarted_;
+ bool unit_test_;
+ bool read_only_; // Prevents updates of the rankings data (used by tools).
+ bool disabled_;
+ bool new_eviction_; // What eviction algorithm should be used.
+ bool first_timer_; // True if the timer has not been called.
+ bool user_load_; // True if we see a high load coming from the caller.
+
+ net::NetLog* net_log_;
+
+ Stats stats_; // Usage statistics.
+ scoped_ptr<base::RepeatingTimer<BackendImpl> > timer_; // Usage timer.
+ scoped_refptr<TraceObject> trace_object_; // Initializes internal tracing.
+ base::WeakPtrFactory<BackendImpl> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackendImpl);
+};
+
+// Returns the preferred max cache size given the available disk space.
+NET_EXPORT_PRIVATE int PreferedCacheSize(int64 available);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BACKEND_IMPL_H_
diff --git a/chromium/net/disk_cache/v3/backend_worker.cc b/chromium/net/disk_cache/v3/backend_worker.cc
new file mode 100644
index 00000000000..cbccfddb5c6
--- /dev/null
+++ b/chromium/net/disk_cache/v3/backend_worker.cc
@@ -0,0 +1,485 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/backend_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/hash.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/errors.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/file.h"
+
+// This has to be defined before including histogram_macros.h from this file.
+#define NET_DISK_CACHE_BACKEND_IMPL_CC_
+#include "net/disk_cache/histogram_macros.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+const char* kIndexName = "index";
+
+// Seems like ~240 MB correspond to less than 50k entries for 99% of the people.
+// Note that the actual target is to keep the index table load factor under 55%
+// for most users.
+const int k64kEntriesStore = 240 * 1000 * 1000;
+const int kBaseTableLen = 64 * 1024;
+const int kDefaultCacheSize = 80 * 1024 * 1024;
+
+// Avoid trimming the cache for the first 5 minutes (10 timer ticks).
+const int kTrimDelay = 10;
+
+int DesiredIndexTableLen(int32 storage_size) {
+ if (storage_size <= k64kEntriesStore)
+ return kBaseTableLen;
+ if (storage_size <= k64kEntriesStore * 2)
+ return kBaseTableLen * 2;
+ if (storage_size <= k64kEntriesStore * 4)
+ return kBaseTableLen * 4;
+ if (storage_size <= k64kEntriesStore * 8)
+ return kBaseTableLen * 8;
+
+ // The biggest storage_size for int32 requires a 4 MB table.
+ return kBaseTableLen * 16;
+}
+
+int MaxStorageSizeForTable(int table_len) {
+ return table_len * (k64kEntriesStore / kBaseTableLen);
+}
+
+size_t GetIndexSize(int table_len) {
+ size_t table_size = sizeof(disk_cache::CacheAddr) * table_len;
+ return sizeof(disk_cache::IndexHeader) + table_size;
+}
+
+// ------------------------------------------------------------------------
+
+// Sets group for the current experiment. Returns false if the files should be
+// discarded.
+bool InitExperiment(disk_cache::IndexHeader* header, bool cache_created) {
+ if (header->experiment == disk_cache::EXPERIMENT_OLD_FILE1 ||
+ header->experiment == disk_cache::EXPERIMENT_OLD_FILE2) {
+ // Discard current cache.
+ return false;
+ }
+
+ if (base::FieldTrialList::FindFullName("SimpleCacheTrial") ==
+ "ExperimentControl") {
+ if (cache_created) {
+ header->experiment = disk_cache::EXPERIMENT_SIMPLE_CONTROL;
+ return true;
+ } else if (header->experiment != disk_cache::EXPERIMENT_SIMPLE_CONTROL) {
+ return false;
+ }
+ }
+
+ header->experiment = disk_cache::NO_EXPERIMENT;
+ return true;
+}
+
+} // namespace
+
+// ------------------------------------------------------------------------
+
+namespace disk_cache {
+
+BackendImpl::BackendImpl(const base::FilePath& path,
+ base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log)
+ : background_queue_(this, cache_thread),
+ path_(path),
+ block_files_(path),
+ mask_(0),
+ max_size_(0),
+ up_ticks_(0),
+ cache_type_(net::DISK_CACHE),
+ uma_report_(0),
+ user_flags_(0),
+ init_(false),
+ restarted_(false),
+ unit_test_(false),
+ read_only_(false),
+ disabled_(false),
+ new_eviction_(false),
+ first_timer_(true),
+ user_load_(false),
+ net_log_(net_log),
+ done_(true, false),
+ ptr_factory_(this) {
+}
+
+int BackendImpl::SyncInit() {
+#if defined(NET_BUILD_STRESS_CACHE)
+ // Start evictions right away.
+ up_ticks_ = kTrimDelay * 2;
+#endif
+ DCHECK(!init_);
+ if (init_)
+ return net::ERR_FAILED;
+
+ bool create_files = false;
+ if (!InitBackingStore(&create_files)) {
+ ReportError(ERR_STORAGE_ERROR);
+ return net::ERR_FAILED;
+ }
+
+ num_refs_ = num_pending_io_ = max_refs_ = 0;
+ entry_count_ = byte_count_ = 0;
+
+ if (!restarted_) {
+ buffer_bytes_ = 0;
+ trace_object_ = TraceObject::GetTraceObject();
+ // Create a recurrent timer of 30 secs.
+ int timer_delay = unit_test_ ? 1000 : 30000;
+ timer_.reset(new base::RepeatingTimer<BackendImpl>());
+ timer_->Start(FROM_HERE, TimeDelta::FromMilliseconds(timer_delay), this,
+ &BackendImpl::OnStatsTimer);
+ }
+
+ init_ = true;
+ Trace("Init");
+
+ if (data_->header.experiment != NO_EXPERIMENT &&
+ cache_type_ != net::DISK_CACHE) {
+ // No experiment for other caches.
+ return net::ERR_FAILED;
+ }
+
+ if (!(user_flags_ & kNoRandom)) {
+ // The unit test controls directly what to test.
+ new_eviction_ = (cache_type_ == net::DISK_CACHE);
+ }
+
+ if (!CheckIndex()) {
+ ReportError(ERR_INIT_FAILED);
+ return net::ERR_FAILED;
+ }
+
+ if (!restarted_ && (create_files || !data_->header.num_entries))
+ ReportError(ERR_CACHE_CREATED);
+
+ if (!(user_flags_ & kNoRandom) && cache_type_ == net::DISK_CACHE &&
+ !InitExperiment(&data_->header, create_files)) {
+ return net::ERR_FAILED;
+ }
+
+ // We don't care if the value overflows. The only thing we care about is that
+ // the id cannot be zero, because that value is used as "not dirty".
+ // Increasing the value once per second gives us many years before we start
+ // having collisions.
+ data_->header.this_id++;
+ if (!data_->header.this_id)
+ data_->header.this_id++;
+
+ bool previous_crash = (data_->header.crash != 0);
+ data_->header.crash = 1;
+
+ if (!block_files_.Init(create_files))
+ return net::ERR_FAILED;
+
+ // We want to minimize the changes to cache for an AppCache.
+ if (cache_type() == net::APP_CACHE) {
+ DCHECK(!new_eviction_);
+ read_only_ = true;
+ } else if (cache_type() == net::SHADER_CACHE) {
+ DCHECK(!new_eviction_);
+ }
+
+ eviction_.Init(this);
+
+ // stats_ and rankings_ may end up calling back to us so we better be enabled.
+ disabled_ = false;
+ if (!InitStats())
+ return net::ERR_FAILED;
+
+ disabled_ = !rankings_.Init(this, new_eviction_);
+
+#if defined(STRESS_CACHE_EXTENDED_VALIDATION)
+ trace_object_->EnableTracing(false);
+ int sc = SelfCheck();
+ if (sc < 0 && sc != ERR_NUM_ENTRIES_MISMATCH)
+ NOTREACHED();
+ trace_object_->EnableTracing(true);
+#endif
+
+ if (previous_crash) {
+ ReportError(ERR_PREVIOUS_CRASH);
+ } else if (!restarted_) {
+ ReportError(ERR_NO_ERROR);
+ }
+
+ FlushIndex();
+
+ return disabled_ ? net::ERR_FAILED : net::OK;
+}
+
+void BackendImpl::PrepareForRestart() {
+ // Reset the mask_ if it was not given by the user.
+ if (!(user_flags_ & kMask))
+ mask_ = 0;
+
+ if (!(user_flags_ & kNewEviction))
+ new_eviction_ = false;
+
+ disabled_ = true;
+ data_->header.crash = 0;
+ index_->Flush();
+ index_ = NULL;
+ data_ = NULL;
+ block_files_.CloseFiles();
+ rankings_.Reset();
+ init_ = false;
+ restarted_ = true;
+}
+
+BackendImpl::~BackendImpl() {
+ if (user_flags_ & kNoRandom) {
+ // This is a unit test, so we want to be strict about not leaking entries
+ // and completing all the work.
+ background_queue_.WaitForPendingIO();
+ } else {
+ // This is most likely not a test, so we want to do as little work as
+ // possible at this time, at the price of leaving dirty entries behind.
+ background_queue_.DropPendingIO();
+ }
+
+ if (background_queue_.BackgroundIsCurrentThread()) {
+ // Unit tests may use the same thread for everything.
+ CleanupCache();
+ } else {
+ background_queue_.background_thread()->PostTask(
+ FROM_HERE, base::Bind(&FinalCleanupCallback, base::Unretained(this)));
+ // http://crbug.com/74623
+ base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ done_.Wait();
+ }
+}
+
+void BackendImpl::CleanupCache() {
+ Trace("Backend Cleanup");
+ eviction_.Stop();
+ timer_.reset();
+
+ if (init_) {
+ StoreStats();
+ if (data_)
+ data_->header.crash = 0;
+
+ if (user_flags_ & kNoRandom) {
+ // This is a net_unittest, verify that we are not 'leaking' entries.
+ File::WaitForPendingIO(&num_pending_io_);
+ DCHECK(!num_refs_);
+ } else {
+ File::DropPendingIO();
+ }
+ }
+ block_files_.CloseFiles();
+ FlushIndex();
+ index_ = NULL;
+ ptr_factory_.InvalidateWeakPtrs();
+ done_.Signal();
+}
+
+base::FilePath BackendImpl::GetFileName(Addr address) const {
+ if (!address.is_separate_file() || !address.is_initialized()) {
+ NOTREACHED();
+ return base::FilePath();
+ }
+
+ std::string tmp = base::StringPrintf("f_%06x", address.FileNumber());
+ return path_.AppendASCII(tmp);
+}
+
+// We just created a new file so we're going to write the header and set the
+// file length to include the hash table (zero filled).
+bool BackendImpl::CreateBackingStore(disk_cache::File* file) {
+ AdjustMaxCacheSize(0);
+
+ IndexHeader header;
+ header.table_len = DesiredIndexTableLen(max_size_);
+
+ // We need file version 2.1 for the new eviction algorithm.
+ if (new_eviction_)
+ header.version = 0x20001;
+
+ header.create_time = Time::Now().ToInternalValue();
+
+ if (!file->Write(&header, sizeof(header), 0))
+ return false;
+
+ return file->SetLength(GetIndexSize(header.table_len));
+}
+
+bool BackendImpl::InitBackingStore(bool* file_created) {
+ if (!file_util::CreateDirectory(path_))
+ return false;
+
+ base::FilePath index_name = path_.AppendASCII(kIndexName);
+
+ int flags = base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE;
+ scoped_refptr<disk_cache::File> file(new disk_cache::File(
+ base::CreatePlatformFile(index_name, flags, file_created, NULL)));
+
+ if (!file->IsValid())
+ return false;
+
+ bool ret = true;
+ if (*file_created)
+ ret = CreateBackingStore(file.get());
+
+ file = NULL;
+ if (!ret)
+ return false;
+
+ index_ = new MappedFile();
+ data_ = reinterpret_cast<Index*>(index_->Init(index_name, 0));
+ if (!data_) {
+ LOG(ERROR) << "Unable to map Index file";
+ return false;
+ }
+
+ if (index_->GetLength() < sizeof(Index)) {
+ // We verify this again on CheckIndex() but it's easier to make sure now
+ // that the header is there.
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ return true;
+}
+
+void BackendImpl::ReportError(int error) {
+ STRESS_DCHECK(!error || error == ERR_PREVIOUS_CRASH ||
+ error == ERR_CACHE_CREATED);
+
+ // We transmit positive numbers, instead of direct error codes.
+ DCHECK_LE(error, 0);
+ CACHE_UMA(CACHE_ERROR, "Error", 0, error * -1);
+}
+
+
+bool BackendImpl::CheckIndex() {
+ DCHECK(data_);
+
+ size_t current_size = index_->GetLength();
+ if (current_size < sizeof(Index)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ if (new_eviction_) {
+ // We support versions 2.0 and 2.1, upgrading 2.0 to 2.1.
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion >> 16 != data_->header.version >> 16) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ if (kCurrentVersion == data_->header.version) {
+ // We need file version 2.1 for the new eviction algorithm.
+ UpgradeTo2_1();
+ }
+ } else {
+ if (kIndexMagic != data_->header.magic ||
+ kCurrentVersion != data_->header.version) {
+ LOG(ERROR) << "Invalid file version or magic";
+ return false;
+ }
+ }
+
+ if (!data_->header.table_len) {
+ LOG(ERROR) << "Invalid table size";
+ return false;
+ }
+
+ if (current_size < GetIndexSize(data_->header.table_len) ||
+ data_->header.table_len & (kBaseTableLen - 1)) {
+ LOG(ERROR) << "Corrupt Index file";
+ return false;
+ }
+
+ AdjustMaxCacheSize(data_->header.table_len);
+
+#if !defined(NET_BUILD_STRESS_CACHE)
+ if (data_->header.num_bytes < 0 ||
+ (max_size_ < kint32max - kDefaultCacheSize &&
+ data_->header.num_bytes > max_size_ + kDefaultCacheSize)) {
+ LOG(ERROR) << "Invalid cache (current) size";
+ return false;
+ }
+#endif
+
+ if (data_->header.num_entries < 0) {
+ LOG(ERROR) << "Invalid number of entries";
+ return false;
+ }
+
+ if (!mask_)
+ mask_ = data_->header.table_len - 1;
+
+ // Load the table into memory with a single read.
+ scoped_ptr<char[]> buf(new char[current_size]);
+ return index_->Read(buf.get(), current_size, 0);
+}
+
+bool BackendImpl::InitStats() {
+ Addr address(data_->header.stats);
+ int size = stats_.StorageSize();
+
+ if (!address.is_initialized()) {
+ FileType file_type = Addr::RequiredFileType(size);
+ DCHECK_NE(file_type, EXTERNAL);
+ int num_blocks = Addr::RequiredBlocks(size, file_type);
+
+ if (!CreateBlock(file_type, num_blocks, &address))
+ return false;
+ return stats_.Init(NULL, 0, address);
+ }
+
+ if (!address.is_block_file()) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Load the required data.
+ size = address.num_blocks() * address.BlockSize();
+ MappedFile* file = File(address);
+ if (!file)
+ return false;
+
+ scoped_ptr<char[]> data(new char[size]);
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (!file->Read(data.get(), size, offset))
+ return false;
+
+ if (!stats_.Init(data.get(), size, address))
+ return false;
+ if (cache_type_ == net::DISK_CACHE && ShouldReportAgain())
+ stats_.InitSizeHistogram();
+ return true;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/backend_worker.h b/chromium/net/disk_cache/v3/backend_worker.h
new file mode 100644
index 00000000000..42fd4b232f8
--- /dev/null
+++ b/chromium/net/disk_cache/v3/backend_worker.h
@@ -0,0 +1,60 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface of the cache.
+
+#ifndef NET_DISK_CACHE_BACKEND_IMPL_H_
+#define NET_DISK_CACHE_BACKEND_IMPL_H_
+
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/timer/timer.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/eviction.h"
+#include "net/disk_cache/in_flight_backend_io.h"
+#include "net/disk_cache/rankings.h"
+#include "net/disk_cache/stats.h"
+#include "net/disk_cache/stress_support.h"
+#include "net/disk_cache/trace.h"
+
+namespace disk_cache {
+
+// This class implements the Backend interface. An object of this
+// class handles the operations of the cache for a particular profile.
+class NET_EXPORT_PRIVATE BackendImpl : public Backend {
+ friend class Eviction;
+ public:
+ BackendImpl(const base::FilePath& path, base::MessageLoopProxy* cache_thread,
+ net::NetLog* net_log);
+
+ // Performs general initialization for this current instance of the cache.
+ int Init(const CompletionCallback& callback);
+
+ private:
+ void CleanupCache();
+
+ // Returns the full name for an external storage file.
+ base::FilePath GetFileName(Addr address) const;
+
+ // Creates a new backing file for the cache index.
+ bool CreateBackingStore(disk_cache::File* file);
+ bool InitBackingStore(bool* file_created);
+
+ // Reports an uncommon, recoverable error.
+ void ReportError(int error);
+
+ // Performs basic checks on the index file. Returns false on failure.
+ bool CheckIndex();
+
+ base::FilePath path_; // Path to the folder used as backing storage.
+ BlockFiles block_files_; // Set of files used to store all data.
+ bool init_; // controls the initialization of the system.
+
+ DISALLOW_COPY_AND_ASSIGN(BackendImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BACKEND_IMPL_H_
diff --git a/chromium/net/disk_cache/v3/block_bitmaps.cc b/chromium/net/disk_cache/v3/block_bitmaps.cc
new file mode 100644
index 00000000000..0d0317b39dc
--- /dev/null
+++ b/chromium/net/disk_cache/v3/block_bitmaps.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/block_files.h"
+
+#include "base/atomicops.h"
+#include "base/file_util.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/file_lock.h"
+#include "net/disk_cache/trace.h"
+
+using base::TimeTicks;
+
+namespace disk_cache {
+
+BlockFiles::BlockFiles(const base::FilePath& path)
+ : init_(false), zero_buffer_(NULL), path_(path) {
+}
+
+BlockFiles::~BlockFiles() {
+ if (zero_buffer_)
+ delete[] zero_buffer_;
+ CloseFiles();
+}
+
+bool BlockFiles::Init(bool create_files) {
+ DCHECK(!init_);
+ if (init_)
+ return false;
+
+ thread_checker_.reset(new base::ThreadChecker);
+
+ block_files_.resize(kFirstAdditionalBlockFile);
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
+ if (create_files)
+ if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
+ return false;
+
+ if (!OpenBlockFile(i))
+ return false;
+
+ // Walk this chain of files removing empty ones.
+ if (!RemoveEmptyFile(static_cast<FileType>(i + 1)))
+ return false;
+ }
+
+ init_ = true;
+ return true;
+}
+
+bool BlockFiles::CreateBlock(FileType block_type, int block_count,
+ Addr* block_address) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ if (block_type < RANKINGS || block_type > BLOCK_4K ||
+ block_count < 1 || block_count > 4)
+ return false;
+ if (!init_)
+ return false;
+
+ MappedFile* file = FileForNewBlock(block_type, block_count);
+ if (!file)
+ return false;
+
+ ScopedFlush flush(file);
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ int target_size = 0;
+ for (int i = block_count; i <= 4; i++) {
+ if (header->empty[i - 1]) {
+ target_size = i;
+ break;
+ }
+ }
+
+ DCHECK(target_size);
+ int index;
+ if (!CreateMapBlock(target_size, block_count, header, &index))
+ return false;
+
+ Addr address(block_type, block_count, header->this_file, index);
+ block_address->set_value(address.value());
+ Trace("CreateBlock 0x%x", address.value());
+ return true;
+}
+
+void BlockFiles::DeleteBlock(Addr address, bool deep) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ if (!address.is_initialized() || address.is_separate_file())
+ return;
+
+ if (!zero_buffer_) {
+ zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
+ memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
+ }
+ MappedFile* file = GetFile(address);
+ if (!file)
+ return;
+
+ Trace("DeleteBlock 0x%x", address.value());
+
+ size_t size = address.BlockSize() * address.num_blocks();
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ if (deep)
+ file->Write(zero_buffer_, size, offset);
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ DeleteMapBlock(address.start_block(), address.num_blocks(), header);
+ file->Flush();
+
+ if (!header->num_entries) {
+ // This file is now empty. Let's try to delete it.
+ FileType type = Addr::RequiredFileType(header->entry_size);
+ if (Addr::BlockSizeForFileType(RANKINGS) == header->entry_size)
+ type = RANKINGS;
+ RemoveEmptyFile(type); // Ignore failures.
+ }
+}
+
+void BlockFiles::CloseFiles() {
+ if (init_) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ }
+ init_ = false;
+ for (unsigned int i = 0; i < block_files_.size(); i++) {
+ if (block_files_[i]) {
+ block_files_[i]->Release();
+ block_files_[i] = NULL;
+ }
+ }
+ block_files_.clear();
+}
+
+void BlockFiles::ReportStats() {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ int used_blocks[kFirstAdditionalBlockFile];
+ int load[kFirstAdditionalBlockFile];
+ for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
+ GetFileStats(i, &used_blocks[i], &load[i]);
+ }
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_0", used_blocks[0]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_1", used_blocks[1]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_2", used_blocks[2]);
+ UMA_HISTOGRAM_COUNTS("DiskCache.Blocks_3", used_blocks[3]);
+
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
+ UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
+}
+
+bool BlockFiles::IsValid(Addr address) {
+#ifdef NDEBUG
+ return true;
+#else
+ if (!address.is_initialized() || address.is_separate_file())
+ return false;
+
+ MappedFile* file = GetFile(address);
+ if (!file)
+ return false;
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ bool rv = UsedMapBlock(address.start_block(), address.num_blocks(), header);
+ DCHECK(rv);
+
+ static bool read_contents = false;
+ if (read_contents) {
+ scoped_ptr<char[]> buffer;
+ buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]);
+ size_t size = address.BlockSize() * address.num_blocks();
+ size_t offset = address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ bool ok = file->Read(buffer.get(), size, offset);
+ DCHECK(ok);
+ }
+
+ return rv;
+#endif
+}
+
+MappedFile* BlockFiles::GetFile(Addr address) {
+ DCHECK(thread_checker_->CalledOnValidThread());
+ DCHECK(block_files_.size() >= 4);
+ DCHECK(address.is_block_file() || !address.is_initialized());
+ if (!address.is_initialized())
+ return NULL;
+
+ int file_index = address.FileNumber();
+ if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
+ !block_files_[file_index]) {
+ // We need to open the file
+ if (!OpenBlockFile(file_index))
+ return NULL;
+ }
+ DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
+ return block_files_[file_index];
+}
+
+bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) {
+ if (kMaxBlocks == header->max_entries)
+ return false;
+
+ ScopedFlush flush(file);
+ DCHECK(!header->empty[3]);
+ int new_size = header->max_entries + 1024;
+ if (new_size > kMaxBlocks)
+ new_size = kMaxBlocks;
+
+ int new_size_bytes = new_size * header->entry_size + sizeof(*header);
+
+ if (!file->SetLength(new_size_bytes)) {
+ // Most likely we are trying to truncate the file, so the header is wrong.
+ if (header->updating < 10 && !FixBlockFileHeader(file)) {
+ // If we can't fix the file increase the lock guard so we'll pick it on
+ // the next start and replace it.
+ header->updating = 100;
+ return false;
+ }
+ return (header->max_entries >= new_size);
+ }
+
+ FileLock lock(header);
+ header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
+ header->max_entries = new_size;
+
+ return true;
+}
+
+MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
+ COMPILE_ASSERT(RANKINGS == 1, invalid_file_type);
+ MappedFile* file = block_files_[block_type - 1];
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+
+ TimeTicks start = TimeTicks::Now();
+ while (NeedToGrowBlockFile(header, block_count)) {
+ if (kMaxBlocks == header->max_entries) {
+ file = NextFile(file);
+ if (!file)
+ return NULL;
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ continue;
+ }
+
+ if (!GrowBlockFile(file, header))
+ return NULL;
+ break;
+ }
+ HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock", TimeTicks::Now() - start);
+ return file;
+}
+
+// Note that we expect to be called outside of a FileLock... however, we cannot
+// DCHECK on header->updating because we may be fixing a crash.
+bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
+ ScopedFlush flush(file);
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ int file_size = static_cast<int>(file->GetLength());
+ if (file_size < static_cast<int>(sizeof(*header)))
+ return false; // file_size > 2GB is also an error.
+
+ const int kMinBlockSize = 36;
+ const int kMaxBlockSize = 4096;
+ if (header->entry_size < kMinBlockSize ||
+ header->entry_size > kMaxBlockSize || header->num_entries < 0)
+ return false;
+
+ // Make sure that we survive crashes.
+ header->updating = 1;
+ int expected = header->entry_size * header->max_entries + sizeof(*header);
+ if (file_size != expected) {
+ int max_expected = header->entry_size * kMaxBlocks + sizeof(*header);
+ if (file_size < expected || header->empty[3] || file_size > max_expected) {
+ NOTREACHED();
+ LOG(ERROR) << "Unexpected file size";
+ return false;
+ }
+ // We were in the middle of growing the file.
+ int num_entries = (file_size - sizeof(*header)) / header->entry_size;
+ header->max_entries = num_entries;
+ }
+
+ FixAllocationCounters(header);
+ int empty_blocks = EmptyBlocks(header);
+ if (empty_blocks + header->num_entries > header->max_entries)
+ header->num_entries = header->max_entries - empty_blocks;
+
+ if (!ValidateCounters(header))
+ return false;
+
+ header->updating = 0;
+ return true;
+}
+
+// We are interested in the total number of blocks used by this file type, and
+// the max number of blocks that we can store (reported as the percentage of
+// used blocks). In order to find out the number of used blocks, we have to
+// substract the empty blocks from the total blocks for each file in the chain.
+void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
+ int max_blocks = 0;
+ *used_count = 0;
+ *load = 0;
+ for (;;) {
+ if (!block_files_[index] && !OpenBlockFile(index))
+ return;
+
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
+
+ max_blocks += header->max_entries;
+ int used = header->max_entries;
+ for (int i = 0; i < 4; i++) {
+ used -= header->empty[i] * (i + 1);
+ DCHECK_GE(used, 0);
+ }
+ *used_count += used;
+
+ if (!header->next_file)
+ break;
+ index = header->next_file;
+ }
+ if (max_blocks)
+ *load = *used_count * 100 / max_blocks;
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/block_bitmaps.h b/chromium/net/disk_cache/v3/block_bitmaps.h
new file mode 100644
index 00000000000..eaf87609912
--- /dev/null
+++ b/chromium/net/disk_cache/v3/block_bitmaps.h
@@ -0,0 +1,75 @@
+// Copyright (c) 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.
+
+// See net/disk_cache/disk_cache.h for the public interface.
+
+#ifndef NET_DISK_CACHE_BLOCK_FILES_H_
+#define NET_DISK_CACHE_BLOCK_FILES_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/disk_cache/addr.h"
+#include "net/disk_cache/mapped_file.h"
+
+namespace disk_cache {
+
+// This class handles the set of block-files open by the disk cache.
+class NET_EXPORT_PRIVATE BlockFiles {
+ public:
+ explicit BlockFiles(const base::FilePath& path);
+ ~BlockFiles();
+
+ // Performs the object initialization. create_files indicates if the backing
+ // files should be created or just open.
+ bool Init(bool create_files);
+
+ // Creates a new entry on a block file. block_type indicates the size of block
+ // to be used (as defined on cache_addr.h), block_count is the number of
+ // blocks to allocate, and block_address is the address of the new entry.
+ bool CreateBlock(FileType block_type, int block_count, Addr* block_address);
+
+ // Removes an entry from the block files. If deep is true, the storage is zero
+ // filled; otherwise the entry is removed but the data is not altered (must be
+ // already zeroed).
+ void DeleteBlock(Addr address, bool deep);
+
+ // Close all the files and set the internal state to be initializad again. The
+ // cache is being purged.
+ void CloseFiles();
+
+ // Sends UMA stats.
+ void ReportStats();
+
+ // Returns true if the blocks pointed by a given address are currently used.
+ // This method is only intended for debugging.
+ bool IsValid(Addr address);
+
+ private:
+ // Returns the file that stores a given address.
+ MappedFile* GetFile(Addr address);
+
+ // Attemp to grow this file. Fails if the file cannot be extended anymore.
+ bool GrowBlockFile(MappedFile* file, BlockFileHeader* header);
+
+ // Returns the appropriate file to use for a new block.
+ MappedFile* FileForNewBlock(FileType block_type, int block_count);
+
+ // Restores the header of a potentially inconsistent file.
+ bool FixBlockFileHeader(MappedFile* file);
+
+ // Retrieves stats for the given file index.
+ void GetFileStats(int index, int* used_count, int* load);
+
+ bool init_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockFiles);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_BLOCK_FILES_H_
diff --git a/chromium/net/disk_cache/v3/block_bitmaps_unittest.cc b/chromium/net/disk_cache/v3/block_bitmaps_unittest.cc
new file mode 100644
index 00000000000..fa7c5dbb742
--- /dev/null
+++ b/chromium/net/disk_cache/v3/block_bitmaps_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (c) 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.
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_base.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+
+namespace {
+
+// Returns the number of files in this folder.
+int NumberOfFiles(const base::FilePath& path) {
+ base::FileEnumerator iter(path, false, base::FileEnumerator::FILES);
+ int count = 0;
+ for (base::FilePath file = iter.Next(); !file.value().empty();
+ file = iter.Next()) {
+ count++;
+ }
+ return count;
+}
+
+} // namespace;
+
+namespace disk_cache {
+
+TEST_F(DiskCacheTest, BlockFiles_Grow) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kMaxSize = 35000;
+ Addr address[kMaxSize];
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < kMaxSize; i++) {
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i]));
+ }
+ EXPECT_EQ(6, NumberOfFiles(cache_path_));
+
+ // Make sure we don't keep adding files.
+ for (int i = 0; i < kMaxSize * 4; i += 2) {
+ int target = i % kMaxSize;
+ files.DeleteBlock(address[target], false);
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[target]));
+ }
+ EXPECT_EQ(6, NumberOfFiles(cache_path_));
+}
+
+// We should be able to delete empty block files.
+TEST_F(DiskCacheTest, BlockFiles_Shrink) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kMaxSize = 35000;
+ Addr address[kMaxSize];
+
+ // Fill up the 32-byte block file (use three files).
+ for (int i = 0; i < kMaxSize; i++) {
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 4, &address[i]));
+ }
+
+ // Now delete all the blocks, so that we can delete the two extra files.
+ for (int i = 0; i < kMaxSize; i++) {
+ files.DeleteBlock(address[i], false);
+ }
+ EXPECT_EQ(4, NumberOfFiles(cache_path_));
+}
+
+// Handling of block files not properly closed.
+TEST_F(DiskCacheTest, BlockFiles_Recover) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ const int kNumEntries = 2000;
+ CacheAddr entries[kNumEntries];
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ for (int i = 0; i < kNumEntries; i++) {
+ Addr address(0);
+ int size = (rand() % 4) + 1;
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, size, &address));
+ entries[i] = address.value();
+ }
+
+ for (int i = 0; i < kNumEntries; i++) {
+ int source1 = rand() % kNumEntries;
+ int source2 = rand() % kNumEntries;
+ CacheAddr temp = entries[source1];
+ entries[source1] = entries[source2];
+ entries[source2] = temp;
+ }
+
+ for (int i = 0; i < kNumEntries / 2; i++) {
+ Addr address(entries[i]);
+ files.DeleteBlock(address, false);
+ }
+
+ // At this point, there are kNumEntries / 2 entries on the file, randomly
+ // distributed both on location and size.
+
+ Addr address(entries[kNumEntries / 2]);
+ MappedFile* file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ ASSERT_EQ(0, header->updating);
+
+ int max_entries = header->max_entries;
+ int empty_1 = header->empty[0];
+ int empty_2 = header->empty[1];
+ int empty_3 = header->empty[2];
+ int empty_4 = header->empty[3];
+
+ // Corrupt the file.
+ header->max_entries = header->empty[0] = 0;
+ header->empty[1] = header->empty[2] = header->empty[3] = 0;
+ header->updating = -1;
+
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+
+ // The file must have been fixed.
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ ASSERT_EQ(0, header->updating);
+
+ EXPECT_EQ(max_entries, header->max_entries);
+ EXPECT_EQ(empty_1, header->empty[0]);
+ EXPECT_EQ(empty_2, header->empty[1]);
+ EXPECT_EQ(empty_3, header->empty[2]);
+ EXPECT_EQ(empty_4, header->empty[3]);
+}
+
+// Handling of truncated files.
+TEST_F(DiskCacheTest, BlockFiles_ZeroSizeFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ base::FilePath filename = files.Name(0);
+ files.CloseFiles();
+ // Truncate one of the files.
+ {
+ scoped_refptr<File> file(new File);
+ ASSERT_TRUE(file->Init(filename));
+ EXPECT_TRUE(file->SetLength(0));
+ }
+
+ // Initializing should fail, not crash.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// Handling of truncated files (non empty).
+TEST_F(DiskCacheTest, BlockFiles_TruncatedFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+ Addr address;
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
+
+ base::FilePath filename = files.Name(0);
+ files.CloseFiles();
+ // Truncate one of the files.
+ {
+ scoped_refptr<File> file(new File);
+ ASSERT_TRUE(file->Init(filename));
+ EXPECT_TRUE(file->SetLength(15000));
+ }
+
+ // Initializing should fail, not crash.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// Tests detection of out of sync counters.
+TEST_F(DiskCacheTest, BlockFiles_Counters) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Create a block of size 2.
+ Addr address(0);
+ EXPECT_TRUE(files.CreateBlock(RANKINGS, 2, &address));
+
+ MappedFile* file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+
+ BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+ ASSERT_EQ(0, header->updating);
+
+ // Alter the counters so that the free space doesn't add up.
+ header->empty[2] = 50; // 50 free blocks of size 3.
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ // The file must have been fixed.
+ ASSERT_EQ(0, header->empty[2]);
+
+ // Change the number of entries.
+ header->num_entries = 3;
+ header->updating = 1;
+ files.CloseFiles();
+
+ ASSERT_TRUE(files.Init(false));
+ file = files.GetFile(address);
+ ASSERT_TRUE(NULL != file);
+ header = reinterpret_cast<BlockFileHeader*>(file->buffer());
+ ASSERT_TRUE(NULL != header);
+
+ // The file must have been "fixed".
+ ASSERT_EQ(2, header->num_entries);
+
+ // Change the number of entries.
+ header->num_entries = -1;
+ header->updating = 1;
+ files.CloseFiles();
+
+ // Detect the error.
+ ASSERT_FALSE(files.Init(false));
+}
+
+// An invalid file can be detected after init.
+TEST_F(DiskCacheTest, BlockFiles_InvalidFile) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Let's access block 10 of file 5. (There is no file).
+ Addr addr(BLOCK_256, 1, 5, 10);
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+
+ // Let's create an invalid file.
+ base::FilePath filename(files.Name(5));
+ char header[kBlockHeaderSize];
+ memset(header, 'a', kBlockHeaderSize);
+ EXPECT_EQ(kBlockHeaderSize,
+ file_util::WriteFile(filename, header, kBlockHeaderSize));
+
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+
+ // The file should not have been changed (it is still invalid).
+ EXPECT_TRUE(NULL == files.GetFile(addr));
+}
+
+// Tests that we generate the correct file stats.
+TEST_F(DiskCacheTest, BlockFiles_Stats) {
+ ASSERT_TRUE(CopyTestCache("remove_load1"));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(false));
+ int used, load;
+
+ files.GetFileStats(0, &used, &load);
+ EXPECT_EQ(101, used);
+ EXPECT_EQ(9, load);
+
+ files.GetFileStats(1, &used, &load);
+ EXPECT_EQ(203, used);
+ EXPECT_EQ(19, load);
+
+ files.GetFileStats(2, &used, &load);
+ EXPECT_EQ(0, used);
+ EXPECT_EQ(0, load);
+}
+
+// Tests that we add and remove blocks correctly.
+TEST_F(DiskCacheTest, AllocationMap) {
+ ASSERT_TRUE(CleanupCacheDir());
+ ASSERT_TRUE(file_util::CreateDirectory(cache_path_));
+
+ BlockFiles files(cache_path_);
+ ASSERT_TRUE(files.Init(true));
+
+ // Create a bunch of entries.
+ const int kSize = 100;
+ Addr address[kSize];
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ int block_size = i % 4 + 1;
+ EXPECT_TRUE(files.CreateBlock(BLOCK_1K, block_size, &address[i]));
+ EXPECT_EQ(BLOCK_1K, address[i].file_type());
+ EXPECT_EQ(block_size, address[i].num_blocks());
+ int start = address[i].start_block();
+ EXPECT_EQ(start / 4, (start + block_size - 1) / 4);
+ }
+
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_TRUE(files.IsValid(address[i]));
+ }
+
+ // The first part of the allocation map should be completely filled. We used
+ // 10 bits per each four entries, so 250 bits total.
+ BlockFileHeader* header =
+ reinterpret_cast<BlockFileHeader*>(files.GetFile(address[0])->buffer());
+ uint8* buffer = reinterpret_cast<uint8*>(&header->allocation_map);
+ for (int i =0; i < 29; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_EQ(0xff, buffer[i]);
+ }
+
+ for (int i = 0; i < kSize; i++) {
+ SCOPED_TRACE(i);
+ files.DeleteBlock(address[i], false);
+ }
+
+ // The allocation map should be empty.
+ for (int i =0; i < 50; i++) {
+ SCOPED_TRACE(i);
+ EXPECT_EQ(0, buffer[i]);
+ }
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/disk_format_v3.h b/chromium/net/disk_cache/v3/disk_format_v3.h
new file mode 100644
index 00000000000..56163770cfa
--- /dev/null
+++ b/chromium/net/disk_cache/v3/disk_format_v3.h
@@ -0,0 +1,190 @@
+// Copyright (c) 2011 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.
+
+// The cache is stored on disk as a collection of block-files, plus an index
+// plus a collection of external files.
+//
+// Any data blob bigger than kMaxBlockSize (disk_cache/addr.h) will be stored in
+// a separate file named f_xxx where x is a hexadecimal number. Shorter data
+// will be stored as a series of blocks on a block-file. In any case, CacheAddr
+// represents the address of the data inside the cache.
+//
+// The index is actually a collection of four files that store a hash table with
+// allocation bitmaps and backup data. Hash collisions are handled directly by
+// the table, which from some point of view behaves like a 4-way associative
+// cache with overflow buckets (so not really open addressing).
+//
+// Basically the hash table is a collection of buckets. The first part of the
+// table has a fixed number of buckets and it is directly addressed by the hash,
+// while the second part of the table (stored on a second file) has a variable
+// number of buckets. Each bucket stores up to four cells (each cell represents
+// a possibl entry). The index bitmap tracks the state of individual cells.
+//
+// The last element of the cache is the block-file. A block file is a file
+// designed to store blocks of data of a given size. For more details see
+// disk_cache/disk_format_base.h
+//
+// A new cache is initialized with a set of block files (named data_0 through
+// data_6), each one dedicated to store blocks of a given size or function. The
+// number at the end of the file name is the block file number (in decimal).
+//
+// There are three "special" types of blocks: normal entries, evicted entries
+// and control data for external files.
+//
+// The files that store internal information for the cache (blocks and index)
+// are memory mapped. They have a location that is signaled every time the
+// internal structures are modified, so it is possible to detect (most of the
+// time) when the process dies in the middle of an update. There are dedicated
+// backup files for cache bitmaps, used to detect entries out of date.
+
+#ifndef NET_DISK_CACHE_V3_DISK_FORMAT_V3_H_
+#define NET_DISK_CACHE_V3_DISK_FORMAT_V3_H_
+
+#include "base/basictypes.h"
+#include "net/disk_cache/disk_format_base.h"
+
+namespace disk_cache {
+
+const int kBaseTableLen = 0x10000;
+const uint32 kIndexMagicV3 = 0xC103CAC3;
+const uint32 kVersion3 = 0x30000; // Version 3.0.
+
+// Flags for a given cache.
+enum CacheFlags {
+ CACHE_EVICTION_2 = 1, // Keep multiple lists for eviction.
+ CACHE_EVICTED = 1 << 1 // Already evicted at least one entry.
+};
+
+// Header for the master index file.
+struct IndexHeaderV3 {
+ uint32 magic;
+ uint32 version;
+ int32 num_entries; // Number of entries currently stored.
+ int32 num_bytes; // Total size of the stored data.
+ int32 last_file; // Last external file created.
+ int32 reserved1;
+ CacheAddr stats; // Storage for usage data.
+ int32 table_len; // Actual size of the table.
+ int32 crash; // Signals a previous crash.
+ int32 experiment; // Id of an ongoing test.
+ int32 max_bytes; // Total maximum size of the stored data.
+ uint32 flags;
+ int32 used_cells;
+ int32 max_bucket;
+ uint64 create_time; // Creation time for this set of files.
+ uint64 base_time; // Current base for timestamps.
+ uint64 old_time; // Previous time used for timestamps.
+ int32 max_block_file;
+ int32 num_no_use_entries;
+ int32 num_low_use_entries;
+ int32 num_high_use_entries;
+ int32 reserved;
+ int32 num_evicted_entries;
+ int32 pad[6];
+};
+
+const int kBaseBitmapBytes = 3968;
+// The IndexBitmap is directly saved to a file named index. The file grows in
+// page increments (4096 bytes), but all bits don't have to be in use at any
+// given time. The required file size can be computed from header.table_len.
+struct IndexBitmap {
+ IndexHeaderV3 header;
+ uint32 bitmap[kBaseBitmapBytes / 4]; // First page of the bitmap.
+};
+COMPILE_ASSERT(sizeof(IndexBitmap) == 4096, bad_IndexHeader);
+
+// Possible states for a given entry.
+enum EntryState {
+ ENTRY_FREE = 0, // Available slot.
+ ENTRY_NEW, // The entry is being created.
+ ENTRY_OPEN, // The entry is being accessed.
+ ENTRY_MODIFIED, // The entry is being modified.
+ ENTRY_DELETED, // The entry is being deleted.
+ ENTRY_FIXING, // Inconsistent state. The entry is being verified.
+ ENTRY_USED // The slot is in use (entry is present).
+};
+COMPILE_ASSERT(ENTRY_USED <= 7, state_uses_3_bits);
+
+enum EntryGroup {
+ ENTRY_NO_USE = 0, // The entry has not been reused.
+ ENTRY_LOW_USE, // The entry has low reuse.
+ ENTRY_HIGH_USE, // The entry has high reuse.
+ ENTRY_RESERVED, // Reserved for future use.
+ ENTRY_EVICTED // The entry was deleted.
+};
+COMPILE_ASSERT(ENTRY_USED <= 7, group_uses_3_bits);
+
+#pragma pack(push, 1)
+struct IndexCell {
+ void Clear() { memset(this, 0, sizeof(*this)); }
+
+ uint64 address : 22;
+ uint64 hash : 18;
+ uint64 timestamp : 20;
+ uint64 reuse : 4;
+ uint8 state : 3;
+ uint8 group : 3;
+ uint8 sum : 2;
+};
+COMPILE_ASSERT(sizeof(IndexCell) == 9, bad_IndexCell);
+
+struct IndexBucket {
+ IndexCell cells[4];
+ int32 next;
+ uint32 hash : 24; // The last byte is only defined for buckets of
+ uint32 reserved : 8; // the extra table.
+};
+COMPILE_ASSERT(sizeof(IndexBucket) == 44, bad_IndexBucket);
+const int kBytesPerCell = 44 / 4;
+
+// The main cache index. Backed by a file named index_tb1.
+// The extra table (index_tb2) has a similar format, but different size.
+struct Index {
+ // Default size. Actual size controlled by header.table_len.
+ IndexBucket table[kBaseTableLen / 4];
+};
+#pragma pack(pop)
+
+// Flags that can be applied to an entry.
+enum EntryFlags {
+ PARENT_ENTRY = 1, // This entry has children (sparse) entries.
+ CHILD_ENTRY = 1 << 1 // Child entry that stores sparse data.
+};
+
+struct EntryRecord {
+ uint32 hash;
+ uint32 pad1;
+ uint8 reuse_count;
+ uint8 refetch_count;
+ int8 state; // Current EntryState.
+ uint8 flags; // Any combination of EntryFlags.
+ int32 key_len;
+ int32 data_size[4]; // We can store up to 4 data streams for each
+ CacheAddr data_addr[4]; // entry.
+ uint32 data_hash[4];
+ uint64 creation_time;
+ uint64 last_modified_time;
+ uint64 last_access_time;
+ int32 pad[3];
+ uint32 self_hash;
+};
+COMPILE_ASSERT(sizeof(EntryRecord) == 104, bad_EntryRecord);
+
+struct ShortEntryRecord {
+ uint32 hash;
+ uint32 pad1;
+ uint8 reuse_count;
+ uint8 refetch_count;
+ int8 state; // Current EntryState.
+ uint8 flags;
+ int32 key_len;
+ uint64 last_access_time;
+ uint32 long_hash[5];
+ uint32 self_hash;
+};
+COMPILE_ASSERT(sizeof(ShortEntryRecord) == 48, bad_ShortEntryRecord);
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_V3_DISK_FORMAT_V3_H_
diff --git a/chromium/net/disk_cache/v3/entry_impl_v3.cc b/chromium/net/disk_cache/v3/entry_impl_v3.cc
new file mode 100644
index 00000000000..35b2e5644a7
--- /dev/null
+++ b/chromium/net/disk_cache/v3/entry_impl_v3.cc
@@ -0,0 +1,1395 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/entry_impl.h"
+
+#include "base/hash.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/bitmap.h"
+#include "net/disk_cache/cache_util.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/net_log_parameters.h"
+#include "net/disk_cache/sparse_control.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+const int kMaxBufferSize = 1024 * 1024; // 1 MB.
+
+} // namespace
+
+namespace disk_cache {
+
+// This class handles individual memory buffers that store data before it is
+// sent to disk. The buffer can start at any offset, but if we try to write to
+// anywhere in the first 16KB of the file (kMaxBlockSize), we set the offset to
+// zero. The buffer grows up to a size determined by the backend, to keep the
+// total memory used under control.
+class EntryImpl::UserBuffer {
+ public:
+ explicit UserBuffer(BackendImpl* backend)
+ : backend_(backend->GetWeakPtr()), offset_(0), grow_allowed_(true) {
+ buffer_.reserve(kMaxBlockSize);
+ }
+ ~UserBuffer() {
+ if (backend_)
+ backend_->BufferDeleted(capacity() - kMaxBlockSize);
+ }
+
+ // Returns true if we can handle writing |len| bytes to |offset|.
+ bool PreWrite(int offset, int len);
+
+ // Truncates the buffer to |offset| bytes.
+ void Truncate(int offset);
+
+ // Writes |len| bytes from |buf| at the given |offset|.
+ void Write(int offset, IOBuffer* buf, int len);
+
+ // Returns true if we can read |len| bytes from |offset|, given that the
+ // actual file has |eof| bytes stored. Note that the number of bytes to read
+ // may be modified by this method even though it returns false: that means we
+ // should do a smaller read from disk.
+ bool PreRead(int eof, int offset, int* len);
+
+ // Read |len| bytes from |buf| at the given |offset|.
+ int Read(int offset, IOBuffer* buf, int len);
+
+ // Prepare this buffer for reuse.
+ void Reset();
+
+ char* Data() { return buffer_.size() ? &buffer_[0] : NULL; }
+ int Size() { return static_cast<int>(buffer_.size()); }
+ int Start() { return offset_; }
+ int End() { return offset_ + Size(); }
+
+ private:
+ int capacity() { return static_cast<int>(buffer_.capacity()); }
+ bool GrowBuffer(int required, int limit);
+
+ base::WeakPtr<BackendImpl> backend_;
+ int offset_;
+ std::vector<char> buffer_;
+ bool grow_allowed_;
+ DISALLOW_COPY_AND_ASSIGN(UserBuffer);
+};
+
+bool EntryImpl::UserBuffer::PreWrite(int offset, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(len, 0);
+ DCHECK_GE(offset + len, 0);
+
+ // We don't want to write before our current start.
+ if (offset < offset_)
+ return false;
+
+ // Lets get the common case out of the way.
+ if (offset + len <= capacity())
+ return true;
+
+ // If we are writing to the first 16K (kMaxBlockSize), we want to keep the
+ // buffer offset_ at 0.
+ if (!Size() && offset > kMaxBlockSize)
+ return GrowBuffer(len, kMaxBufferSize);
+
+ int required = offset - offset_ + len;
+ return GrowBuffer(required, kMaxBufferSize * 6 / 5);
+}
+
+void EntryImpl::UserBuffer::Truncate(int offset) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(offset, offset_);
+ DVLOG(3) << "Buffer truncate at " << offset << " current " << offset_;
+
+ offset -= offset_;
+ if (Size() >= offset)
+ buffer_.resize(offset);
+}
+
+void EntryImpl::UserBuffer::Write(int offset, IOBuffer* buf, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GE(len, 0);
+ DCHECK_GE(offset + len, 0);
+ DCHECK_GE(offset, offset_);
+ DVLOG(3) << "Buffer write at " << offset << " current " << offset_;
+
+ if (!Size() && offset > kMaxBlockSize)
+ offset_ = offset;
+
+ offset -= offset_;
+
+ if (offset > Size())
+ buffer_.resize(offset);
+
+ if (!len)
+ return;
+
+ char* buffer = buf->data();
+ int valid_len = Size() - offset;
+ int copy_len = std::min(valid_len, len);
+ if (copy_len) {
+ memcpy(&buffer_[offset], buffer, copy_len);
+ len -= copy_len;
+ buffer += copy_len;
+ }
+ if (!len)
+ return;
+
+ buffer_.insert(buffer_.end(), buffer, buffer + len);
+}
+
+bool EntryImpl::UserBuffer::PreRead(int eof, int offset, int* len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GT(*len, 0);
+
+ if (offset < offset_) {
+ // We are reading before this buffer.
+ if (offset >= eof)
+ return true;
+
+ // If the read overlaps with the buffer, change its length so that there is
+ // no overlap.
+ *len = std::min(*len, offset_ - offset);
+ *len = std::min(*len, eof - offset);
+
+ // We should read from disk.
+ return false;
+ }
+
+ if (!Size())
+ return false;
+
+ // See if we can fulfill the first part of the operation.
+ return (offset - offset_ < Size());
+}
+
+int EntryImpl::UserBuffer::Read(int offset, IOBuffer* buf, int len) {
+ DCHECK_GE(offset, 0);
+ DCHECK_GT(len, 0);
+ DCHECK(Size() || offset < offset_);
+
+ int clean_bytes = 0;
+ if (offset < offset_) {
+ // We don't have a file so lets fill the first part with 0.
+ clean_bytes = std::min(offset_ - offset, len);
+ memset(buf->data(), 0, clean_bytes);
+ if (len == clean_bytes)
+ return len;
+ offset = offset_;
+ len -= clean_bytes;
+ }
+
+ int start = offset - offset_;
+ int available = Size() - start;
+ DCHECK_GE(start, 0);
+ DCHECK_GE(available, 0);
+ len = std::min(len, available);
+ memcpy(buf->data() + clean_bytes, &buffer_[start], len);
+ return len + clean_bytes;
+}
+
+void EntryImpl::UserBuffer::Reset() {
+ if (!grow_allowed_) {
+ if (backend_)
+ backend_->BufferDeleted(capacity() - kMaxBlockSize);
+ grow_allowed_ = true;
+ std::vector<char> tmp;
+ buffer_.swap(tmp);
+ buffer_.reserve(kMaxBlockSize);
+ }
+ offset_ = 0;
+ buffer_.clear();
+}
+
+bool EntryImpl::UserBuffer::GrowBuffer(int required, int limit) {
+ DCHECK_GE(required, 0);
+ int current_size = capacity();
+ if (required <= current_size)
+ return true;
+
+ if (required > limit)
+ return false;
+
+ if (!backend_)
+ return false;
+
+ int to_add = std::max(required - current_size, kMaxBlockSize * 4);
+ to_add = std::max(current_size, to_add);
+ required = std::min(current_size + to_add, limit);
+
+ grow_allowed_ = backend_->IsAllocAllowed(current_size, required);
+ if (!grow_allowed_)
+ return false;
+
+ DVLOG(3) << "Buffer grow to " << required;
+
+ buffer_.reserve(required);
+ return true;
+}
+
+// ------------------------------------------------------------------------
+
+EntryImpl::EntryImpl(BackendImpl* backend, Addr address, bool read_only)
+ : entry_(NULL, Addr(0)), node_(NULL, Addr(0)),
+ backend_(backend->GetWeakPtr()), doomed_(false), read_only_(read_only),
+ dirty_(false) {
+ entry_.LazyInit(backend->File(address), address);
+ for (int i = 0; i < kNumStreams; i++) {
+ unreported_size_[i] = 0;
+ }
+}
+
+bool EntryImpl::CreateEntry(Addr node_address, const std::string& key,
+ uint32 hash) {
+ Trace("Create entry In");
+ EntryStore* entry_store = entry_.Data();
+ RankingsNode* node = node_.Data();
+ memset(entry_store, 0, sizeof(EntryStore) * entry_.address().num_blocks());
+ memset(node, 0, sizeof(RankingsNode));
+ if (!node_.LazyInit(backend_->File(node_address), node_address))
+ return false;
+
+ entry_store->rankings_node = node_address.value();
+ node->contents = entry_.address().value();
+
+ entry_store->hash = hash;
+ entry_store->creation_time = Time::Now().ToInternalValue();
+ entry_store->key_len = static_cast<int32>(key.size());
+ if (entry_store->key_len > kMaxInternalKeyLength) {
+ Addr address(0);
+ if (!CreateBlock(entry_store->key_len + 1, &address))
+ return false;
+
+ entry_store->long_key = address.value();
+ File* key_file = GetBackingFile(address, kKeyFileIndex);
+ key_ = key;
+
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!key_file || !key_file->Write(key.data(), key.size(), offset)) {
+ DeleteData(address, kKeyFileIndex);
+ return false;
+ }
+
+ if (address.is_separate_file())
+ key_file->SetLength(key.size() + 1);
+ } else {
+ memcpy(entry_store->key, key.data(), key.size());
+ entry_store->key[key.size()] = '\0';
+ }
+ backend_->ModifyStorageSize(0, static_cast<int32>(key.size()));
+ CACHE_UMA(COUNTS, "KeySize", 0, static_cast<int32>(key.size()));
+ node->dirty = backend_->GetCurrentEntryId();
+ Log("Create Entry ");
+ return true;
+}
+
+uint32 EntryImpl::GetHash() {
+ return entry_.Data()->hash;
+}
+
+bool EntryImpl::IsSameEntry(const std::string& key, uint32 hash) {
+ if (entry_.Data()->hash != hash ||
+ static_cast<size_t>(entry_.Data()->key_len) != key.size())
+ return false;
+
+ return (key.compare(GetKey()) == 0);
+}
+
+void EntryImpl::InternalDoom() {
+ net_log_.AddEvent(net::NetLog::TYPE_ENTRY_DOOM);
+ DCHECK(node_.HasData());
+ if (!node_.Data()->dirty) {
+ node_.Data()->dirty = backend_->GetCurrentEntryId();
+ node_.Store();
+ }
+ doomed_ = true;
+}
+
+// This only includes checks that relate to the first block of the entry (the
+// first 256 bytes), and values that should be set from the entry creation.
+// Basically, even if there is something wrong with this entry, we want to see
+// if it is possible to load the rankings node and delete them together.
+bool EntryImpl::SanityCheck() {
+ if (!entry_.VerifyHash())
+ return false;
+
+ EntryStore* stored = entry_.Data();
+ if (!stored->rankings_node || stored->key_len <= 0)
+ return false;
+
+ if (stored->reuse_count < 0 || stored->refetch_count < 0)
+ return false;
+
+ Addr rankings_addr(stored->rankings_node);
+ if (!rankings_addr.SanityCheckForRankings())
+ return false;
+
+ Addr next_addr(stored->next);
+ if (next_addr.is_initialized() && !next_addr.SanityCheckForEntry()) {
+ STRESS_NOTREACHED();
+ return false;
+ }
+ STRESS_DCHECK(next_addr.value() != entry_.address().value());
+
+ if (stored->state > ENTRY_DOOMED || stored->state < ENTRY_NORMAL)
+ return false;
+
+ Addr key_addr(stored->long_key);
+ if ((stored->key_len <= kMaxInternalKeyLength && key_addr.is_initialized()) ||
+ (stored->key_len > kMaxInternalKeyLength && !key_addr.is_initialized()))
+ return false;
+
+ if (!key_addr.SanityCheck())
+ return false;
+
+ if (key_addr.is_initialized() &&
+ ((stored->key_len < kMaxBlockSize && key_addr.is_separate_file()) ||
+ (stored->key_len >= kMaxBlockSize && key_addr.is_block_file())))
+ return false;
+
+ int num_blocks = NumBlocksForEntry(stored->key_len);
+ if (entry_.address().num_blocks() != num_blocks)
+ return false;
+
+ return true;
+}
+
+bool EntryImpl::DataSanityCheck() {
+ EntryStore* stored = entry_.Data();
+ Addr key_addr(stored->long_key);
+
+ // The key must be NULL terminated.
+ if (!key_addr.is_initialized() && stored->key[stored->key_len])
+ return false;
+
+ if (stored->hash != base::Hash(GetKey()))
+ return false;
+
+ for (int i = 0; i < kNumStreams; i++) {
+ Addr data_addr(stored->data_addr[i]);
+ int data_size = stored->data_size[i];
+ if (data_size < 0)
+ return false;
+ if (!data_size && data_addr.is_initialized())
+ return false;
+ if (!data_addr.SanityCheck())
+ return false;
+ if (!data_size)
+ continue;
+ if (data_size <= kMaxBlockSize && data_addr.is_separate_file())
+ return false;
+ if (data_size > kMaxBlockSize && data_addr.is_block_file())
+ return false;
+ }
+ return true;
+}
+
+void EntryImpl::FixForDelete() {
+ EntryStore* stored = entry_.Data();
+ Addr key_addr(stored->long_key);
+
+ if (!key_addr.is_initialized())
+ stored->key[stored->key_len] = '\0';
+
+ for (int i = 0; i < kNumStreams; i++) {
+ Addr data_addr(stored->data_addr[i]);
+ int data_size = stored->data_size[i];
+ if (data_addr.is_initialized()) {
+ if ((data_size <= kMaxBlockSize && data_addr.is_separate_file()) ||
+ (data_size > kMaxBlockSize && data_addr.is_block_file()) ||
+ !data_addr.SanityCheck()) {
+ STRESS_NOTREACHED();
+ // The address is weird so don't attempt to delete it.
+ stored->data_addr[i] = 0;
+ // In general, trust the stored size as it should be in sync with the
+ // total size tracked by the backend.
+ }
+ }
+ if (data_size < 0)
+ stored->data_size[i] = 0;
+ }
+ entry_.Store();
+}
+
+void EntryImpl::SetTimes(base::Time last_used, base::Time last_modified) {
+ node_.Data()->last_used = last_used.ToInternalValue();
+ node_.Data()->last_modified = last_modified.ToInternalValue();
+ node_.set_modified();
+}
+
+void EntryImpl::BeginLogging(net::NetLog* net_log, bool created) {
+ DCHECK(!net_log_.net_log());
+ net_log_ = net::BoundNetLog::Make(
+ net_log, net::NetLog::SOURCE_DISK_CACHE_ENTRY);
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL,
+ CreateNetLogEntryCreationCallback(this, created));
+}
+
+const net::BoundNetLog& EntryImpl::net_log() const {
+ return net_log_;
+}
+
+// ------------------------------------------------------------------------
+
+void EntryImpl::Doom() {
+ if (background_queue_)
+ background_queue_->DoomEntryImpl(this);
+}
+
+void EntryImpl::DoomImpl() {
+ if (doomed_ || !backend_)
+ return;
+
+ SetPointerForInvalidEntry(backend_->GetCurrentEntryId());
+ backend_->InternalDoomEntry(this);
+}
+
+void EntryImpl::Close() {
+ if (background_queue_)
+ background_queue_->CloseEntryImpl(this);
+}
+
+std::string EntryImpl::GetKey() const {
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ int key_len = entry->Data()->key_len;
+ if (key_len <= kMaxInternalKeyLength)
+ return std::string(entry->Data()->key);
+
+ // We keep a copy of the key so that we can always return it, even if the
+ // backend is disabled.
+ if (!key_.empty())
+ return key_;
+
+ Addr address(entry->Data()->long_key);
+ DCHECK(address.is_initialized());
+ size_t offset = 0;
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ COMPILE_ASSERT(kNumStreams == kKeyFileIndex, invalid_key_index);
+ File* key_file = const_cast<EntryImpl*>(this)->GetBackingFile(address,
+ kKeyFileIndex);
+ if (!key_file)
+ return std::string();
+
+ ++key_len; // We store a trailing \0 on disk that we read back below.
+ if (!offset && key_file->GetLength() != static_cast<size_t>(key_len))
+ return std::string();
+
+ if (!key_file->Read(WriteInto(&key_, key_len), key_len, offset))
+ key_.clear();
+ return key_;
+}
+
+Time EntryImpl::GetLastUsed() const {
+ CacheRankingsBlock* node = const_cast<CacheRankingsBlock*>(&node_);
+ return Time::FromInternalValue(node->Data()->last_used);
+}
+
+Time EntryImpl::GetLastModified() const {
+ CacheRankingsBlock* node = const_cast<CacheRankingsBlock*>(&node_);
+ return Time::FromInternalValue(node->Data()->last_modified);
+}
+
+int32 EntryImpl::GetDataSize(int index) const {
+ if (index < 0 || index >= kNumStreams)
+ return 0;
+
+ CacheEntryBlock* entry = const_cast<CacheEntryBlock*>(&entry_);
+ return entry->Data()->data_size[index];
+}
+
+int EntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return ReadDataImpl(index, offset, buf, buf_len, callback);
+
+ DCHECK(node_.Data()->dirty || read_only_);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = entry_.Data()->data_size[index];
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadData(this, index, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::ReadDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, false));
+ }
+
+ int result = InternalReadData(index, offset, buf, buf_len, callback);
+
+ if (result != net::ERR_IO_PENDING && net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_READ_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int EntryImpl::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate) {
+ if (callback.is_null())
+ return WriteDataImpl(index, offset, buf, buf_len, callback, truncate);
+
+ DCHECK(node_.Data()->dirty || read_only_);
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->WriteData(this, index, offset, buf, buf_len, truncate,
+ callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.BeginEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteDataCallback(index, offset, buf_len, truncate));
+ }
+
+ int result = InternalWriteData(index, offset, buf, buf_len, callback,
+ truncate);
+
+ if (result != net::ERR_IO_PENDING && net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEvent(
+ net::NetLog::TYPE_ENTRY_WRITE_DATA,
+ CreateNetLogReadWriteCompleteCallback(result));
+ }
+ return result;
+}
+
+int EntryImpl::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return ReadSparseDataImpl(offset, buf, buf_len, callback);
+
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadSparseData(this, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::ReadSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ TimeTicks start = TimeTicks::Now();
+ result = sparse_->StartIO(SparseControl::kReadOperation, offset, buf, buf_len,
+ callback);
+ ReportIOTime(kSparseRead, start);
+ return result;
+}
+
+int EntryImpl::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (callback.is_null())
+ return WriteSparseDataImpl(offset, buf, buf_len, callback);
+
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->WriteSparseData(this, offset, buf, buf_len, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::WriteSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ TimeTicks start = TimeTicks::Now();
+ result = sparse_->StartIO(SparseControl::kWriteOperation, offset, buf,
+ buf_len, callback);
+ ReportIOTime(kSparseWrite, start);
+ return result;
+}
+
+int EntryImpl::GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) {
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->GetAvailableRange(this, offset, len, start, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::GetAvailableRangeImpl(int64 offset, int len, int64* start) {
+ int result = InitSparseData();
+ if (net::OK != result)
+ return result;
+
+ return sparse_->GetAvailableRange(offset, len, start);
+}
+
+bool EntryImpl::CouldBeSparse() const {
+ if (sparse_.get())
+ return true;
+
+ scoped_ptr<SparseControl> sparse;
+ sparse.reset(new SparseControl(const_cast<EntryImpl*>(this)));
+ return sparse->CouldBeSparse();
+}
+
+void EntryImpl::CancelSparseIO() {
+ if (background_queue_)
+ background_queue_->CancelSparseIO(this);
+}
+
+void EntryImpl::CancelSparseIOImpl() {
+ if (!sparse_.get())
+ return;
+
+ sparse_->CancelIO();
+}
+
+int EntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
+ if (!sparse_.get())
+ return net::OK;
+
+ if (!background_queue_)
+ return net::ERR_UNEXPECTED;
+
+ background_queue_->ReadyForSparseIO(this, callback);
+ return net::ERR_IO_PENDING;
+}
+
+int EntryImpl::ReadyForSparseIOImpl(const CompletionCallback& callback) {
+ DCHECK(sparse_.get());
+ return sparse_->ReadyToUse(callback);
+}
+
+// ------------------------------------------------------------------------
+
+// When an entry is deleted from the cache, we clean up all the data associated
+// with it for two reasons: to simplify the reuse of the block (we know that any
+// unused block is filled with zeros), and to simplify the handling of write /
+// read partial information from an entry (don't have to worry about returning
+// data related to a previous cache entry because the range was not fully
+// written before).
+EntryImpl::~EntryImpl() {
+ if (!backend_) {
+ entry_.clear_modified();
+ node_.clear_modified();
+ return;
+ }
+ Log("~EntryImpl in");
+
+ // Save the sparse info to disk. This will generate IO for this entry and
+ // maybe for a child entry, so it is important to do it before deleting this
+ // entry.
+ sparse_.reset();
+
+ // Remove this entry from the list of open entries.
+ backend_->OnEntryDestroyBegin(entry_.address());
+
+ if (doomed_) {
+ DeleteEntryData(true);
+ } else {
+#if defined(NET_BUILD_STRESS_CACHE)
+ SanityCheck();
+#endif
+ net_log_.AddEvent(net::NetLog::TYPE_ENTRY_CLOSE);
+ bool ret = true;
+ for (int index = 0; index < kNumStreams; index++) {
+ if (user_buffers_[index].get()) {
+ if (!(ret = Flush(index, 0)))
+ LOG(ERROR) << "Failed to save user data";
+ }
+ if (unreported_size_[index]) {
+ backend_->ModifyStorageSize(
+ entry_.Data()->data_size[index] - unreported_size_[index],
+ entry_.Data()->data_size[index]);
+ }
+ }
+
+ if (!ret) {
+ // There was a failure writing the actual data. Mark the entry as dirty.
+ int current_id = backend_->GetCurrentEntryId();
+ node_.Data()->dirty = current_id == 1 ? -1 : current_id - 1;
+ node_.Store();
+ } else if (node_.HasData() && !dirty_ && node_.Data()->dirty) {
+ node_.Data()->dirty = 0;
+ node_.Store();
+ }
+ }
+
+ Trace("~EntryImpl out 0x%p", reinterpret_cast<void*>(this));
+ net_log_.EndEvent(net::NetLog::TYPE_DISK_CACHE_ENTRY_IMPL);
+ backend_->OnEntryDestroyEnd();
+}
+
+int EntryImpl::InternalReadData(int index, int offset,
+ IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ DVLOG(2) << "Read from " << index << " at " << offset << " : " << buf_len;
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ int entry_size = entry_.Data()->data_size[index];
+ if (offset >= entry_size || offset < 0 || !buf_len)
+ return 0;
+
+ if (buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!backend_)
+ return net::ERR_UNEXPECTED;
+
+ TimeTicks start = TimeTicks::Now();
+
+ if (offset + buf_len > entry_size)
+ buf_len = entry_size - offset;
+
+ UpdateRank(false);
+
+ backend_->OnEvent(Stats::READ_DATA);
+ backend_->OnRead(buf_len);
+
+ Addr address(entry_.Data()->data_addr[index]);
+ int eof = address.is_initialized() ? entry_size : 0;
+ if (user_buffers_[index].get() &&
+ user_buffers_[index]->PreRead(eof, offset, &buf_len)) {
+ // Complete the operation locally.
+ buf_len = user_buffers_[index]->Read(offset, buf, buf_len);
+ ReportIOTime(kRead, start);
+ return buf_len;
+ }
+
+ address.set_value(entry_.Data()->data_addr[index]);
+ DCHECK(address.is_initialized());
+ if (!address.is_initialized()) {
+ DoomImpl();
+ return net::ERR_FAILED;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file) {
+ DoomImpl();
+ LOG(ERROR) << "No file for " << std::hex << address.value();
+ return net::ERR_FILE_NOT_FOUND;
+ }
+
+ size_t file_offset = offset;
+ if (address.is_block_file()) {
+ DCHECK_LE(offset + buf_len, kMaxBlockSize);
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ }
+
+ SyncCallback* io_callback = NULL;
+ if (!callback.is_null()) {
+ io_callback = new SyncCallback(this, buf, callback,
+ net::NetLog::TYPE_ENTRY_READ_DATA);
+ }
+
+ TimeTicks start_async = TimeTicks::Now();
+
+ bool completed;
+ if (!file->Read(buf->data(), buf_len, file_offset, io_callback, &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ DoomImpl();
+ return net::ERR_CACHE_READ_FAILURE;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ if (io_callback)
+ ReportIOTime(kReadAsync1, start_async);
+
+ ReportIOTime(kRead, start);
+ return (completed || callback.is_null()) ? buf_len : net::ERR_IO_PENDING;
+}
+
+int EntryImpl::InternalWriteData(int index, int offset,
+ IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) {
+ DCHECK(node_.Data()->dirty || read_only_);
+ DVLOG(2) << "Write to " << index << " at " << offset << " : " << buf_len;
+ if (index < 0 || index >= kNumStreams)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ if (!backend_)
+ return net::ERR_UNEXPECTED;
+
+ int max_file_size = backend_->MaxFileSize();
+
+ // offset or buf_len could be negative numbers.
+ if (offset > max_file_size || buf_len > max_file_size ||
+ offset + buf_len > max_file_size) {
+ int size = offset + buf_len;
+ if (size <= max_file_size)
+ size = kint32max;
+ backend_->TooMuchStorageRequested(size);
+ return net::ERR_FAILED;
+ }
+
+ TimeTicks start = TimeTicks::Now();
+
+ // Read the size at this point (it may change inside prepare).
+ int entry_size = entry_.Data()->data_size[index];
+ bool extending = entry_size < offset + buf_len;
+ truncate = truncate && entry_size > offset + buf_len;
+ Trace("To PrepareTarget 0x%x", entry_.address().value());
+ if (!PrepareTarget(index, offset, buf_len, truncate))
+ return net::ERR_FAILED;
+
+ Trace("From PrepareTarget 0x%x", entry_.address().value());
+ if (extending || truncate)
+ UpdateSize(index, entry_size, offset + buf_len);
+
+ UpdateRank(true);
+
+ backend_->OnEvent(Stats::WRITE_DATA);
+ backend_->OnWrite(buf_len);
+
+ if (user_buffers_[index].get()) {
+ // Complete the operation locally.
+ user_buffers_[index]->Write(offset, buf, buf_len);
+ ReportIOTime(kWrite, start);
+ return buf_len;
+ }
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (offset + buf_len == 0) {
+ if (truncate) {
+ DCHECK(!address.is_initialized());
+ }
+ return 0;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return net::ERR_FILE_NOT_FOUND;
+
+ size_t file_offset = offset;
+ if (address.is_block_file()) {
+ DCHECK_LE(offset + buf_len, kMaxBlockSize);
+ file_offset += address.start_block() * address.BlockSize() +
+ kBlockHeaderSize;
+ } else if (truncate || (extending && !buf_len)) {
+ if (!file->SetLength(offset + buf_len))
+ return net::ERR_FAILED;
+ }
+
+ if (!buf_len)
+ return 0;
+
+ SyncCallback* io_callback = NULL;
+ if (!callback.is_null()) {
+ io_callback = new SyncCallback(this, buf, callback,
+ net::NetLog::TYPE_ENTRY_WRITE_DATA);
+ }
+
+ TimeTicks start_async = TimeTicks::Now();
+
+ bool completed;
+ if (!file->Write(buf->data(), buf_len, file_offset, io_callback,
+ &completed)) {
+ if (io_callback)
+ io_callback->Discard();
+ return net::ERR_CACHE_WRITE_FAILURE;
+ }
+
+ if (io_callback && completed)
+ io_callback->Discard();
+
+ if (io_callback)
+ ReportIOTime(kWriteAsync1, start_async);
+
+ ReportIOTime(kWrite, start);
+ return (completed || callback.is_null()) ? buf_len : net::ERR_IO_PENDING;
+}
+
+// ------------------------------------------------------------------------
+
+bool EntryImpl::CreateDataBlock(int index, int size) {
+ DCHECK(index >= 0 && index < kNumStreams);
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (!CreateBlock(size, &address))
+ return false;
+
+ entry_.Data()->data_addr[index] = address.value();
+ entry_.Store();
+ return true;
+}
+
+bool EntryImpl::CreateBlock(int size, Addr* address) {
+ DCHECK(!address->is_initialized());
+ if (!backend_)
+ return false;
+
+ FileType file_type = Addr::RequiredFileType(size);
+ if (EXTERNAL == file_type) {
+ if (size > backend_->MaxFileSize())
+ return false;
+ if (!backend_->CreateExternalFile(address))
+ return false;
+ } else {
+ int num_blocks = Addr::RequiredBlocks(size, file_type);
+
+ if (!backend_->CreateBlock(file_type, num_blocks, address))
+ return false;
+ }
+ return true;
+}
+
+// Note that this method may end up modifying a block file so upon return the
+// involved block will be free, and could be reused for something else. If there
+// is a crash after that point (and maybe before returning to the caller), the
+// entry will be left dirty... and at some point it will be discarded; it is
+// important that the entry doesn't keep a reference to this address, or we'll
+// end up deleting the contents of |address| once again.
+void EntryImpl::DeleteData(Addr address, int index) {
+ DCHECK(backend_);
+ if (!address.is_initialized())
+ return;
+ if (address.is_separate_file()) {
+ int failure = !DeleteCacheFile(backend_->GetFileName(address));
+ CACHE_UMA(COUNTS, "DeleteFailed", 0, failure);
+ if (failure) {
+ LOG(ERROR) << "Failed to delete " <<
+ backend_->GetFileName(address).value() << " from the cache.";
+ }
+ if (files_[index])
+ files_[index] = NULL; // Releases the object.
+ } else {
+ backend_->DeleteBlock(address, true);
+ }
+}
+
+void EntryImpl::UpdateRank(bool modified) {
+ if (!backend_)
+ return;
+
+ if (!doomed_) {
+ // Everything is handled by the backend.
+ backend_->UpdateRank(this, modified);
+ return;
+ }
+
+ Time current = Time::Now();
+ node_.Data()->last_used = current.ToInternalValue();
+
+ if (modified)
+ node_.Data()->last_modified = current.ToInternalValue();
+}
+
+void EntryImpl::DeleteEntryData(bool everything) {
+ DCHECK(doomed_ || !everything);
+
+ if (GetEntryFlags() & PARENT_ENTRY) {
+ // We have some child entries that must go away.
+ SparseControl::DeleteChildren(this);
+ }
+
+ if (GetDataSize(0))
+ CACHE_UMA(COUNTS, "DeleteHeader", 0, GetDataSize(0));
+ if (GetDataSize(1))
+ CACHE_UMA(COUNTS, "DeleteData", 0, GetDataSize(1));
+ for (int index = 0; index < kNumStreams; index++) {
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized()) {
+ backend_->ModifyStorageSize(entry_.Data()->data_size[index] -
+ unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+ }
+ }
+
+ if (!everything)
+ return;
+
+ // Remove all traces of this entry.
+ backend_->RemoveEntry(this);
+
+ // Note that at this point node_ and entry_ are just two blocks of data, and
+ // even if they reference each other, nobody should be referencing them.
+
+ Addr address(entry_.Data()->long_key);
+ DeleteData(address, kKeyFileIndex);
+ backend_->ModifyStorageSize(entry_.Data()->key_len, 0);
+
+ backend_->DeleteBlock(entry_.address(), true);
+ entry_.Discard();
+
+ if (!LeaveRankingsBehind()) {
+ backend_->DeleteBlock(node_.address(), true);
+ node_.Discard();
+ }
+}
+
+// We keep a memory buffer for everything that ends up stored on a block file
+// (because we don't know yet the final data size), and for some of the data
+// that end up on external files. This function will initialize that memory
+// buffer and / or the files needed to store the data.
+//
+// In general, a buffer may overlap data already stored on disk, and in that
+// case, the contents of the buffer are the most accurate. It may also extend
+// the file, but we don't want to read from disk just to keep the buffer up to
+// date. This means that as soon as there is a chance to get confused about what
+// is the most recent version of some part of a file, we'll flush the buffer and
+// reuse it for the new data. Keep in mind that the normal use pattern is quite
+// simple (write sequentially from the beginning), so we optimize for handling
+// that case.
+bool EntryImpl::PrepareTarget(int index, int offset, int buf_len,
+ bool truncate) {
+ if (truncate)
+ return HandleTruncation(index, offset, buf_len);
+
+ if (!offset && !buf_len)
+ return true;
+
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized()) {
+ if (address.is_block_file() && !MoveToLocalBuffer(index))
+ return false;
+
+ if (!user_buffers_[index].get() && offset < kMaxBlockSize) {
+ // We are about to create a buffer for the first 16KB, make sure that we
+ // preserve existing data.
+ if (!CopyToLocalBuffer(index))
+ return false;
+ }
+ }
+
+ if (!user_buffers_[index].get())
+ user_buffers_[index].reset(new UserBuffer(backend_.get()));
+
+ return PrepareBuffer(index, offset, buf_len);
+}
+
+// We get to this function with some data already stored. If there is a
+// truncation that results on data stored internally, we'll explicitly
+// handle the case here.
+bool EntryImpl::HandleTruncation(int index, int offset, int buf_len) {
+ Addr address(entry_.Data()->data_addr[index]);
+
+ int current_size = entry_.Data()->data_size[index];
+ int new_size = offset + buf_len;
+
+ if (!new_size) {
+ // This is by far the most common scenario.
+ backend_->ModifyStorageSize(current_size - unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ unreported_size_[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+
+ user_buffers_[index].reset();
+ return true;
+ }
+
+ // We never postpone truncating a file, if there is one, but we may postpone
+ // telling the backend about the size reduction.
+ if (user_buffers_[index].get()) {
+ DCHECK_GE(current_size, user_buffers_[index]->Start());
+ if (!address.is_initialized()) {
+ // There is no overlap between the buffer and disk.
+ if (new_size > user_buffers_[index]->Start()) {
+ // Just truncate our buffer.
+ DCHECK_LT(new_size, user_buffers_[index]->End());
+ user_buffers_[index]->Truncate(new_size);
+ return true;
+ }
+
+ // Just discard our buffer.
+ user_buffers_[index]->Reset();
+ return PrepareBuffer(index, offset, buf_len);
+ }
+
+ // There is some overlap or we need to extend the file before the
+ // truncation.
+ if (offset > user_buffers_[index]->Start())
+ user_buffers_[index]->Truncate(new_size);
+ UpdateSize(index, current_size, new_size);
+ if (!Flush(index, 0))
+ return false;
+ user_buffers_[index].reset();
+ }
+
+ // We have data somewhere, and it is not in a buffer.
+ DCHECK(!user_buffers_[index].get());
+ DCHECK(address.is_initialized());
+
+ if (new_size > kMaxBlockSize)
+ return true; // Let the operation go directly to disk.
+
+ return ImportSeparateFile(index, offset + buf_len);
+}
+
+bool EntryImpl::CopyToLocalBuffer(int index) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(!user_buffers_[index].get());
+ DCHECK(address.is_initialized());
+
+ int len = std::min(entry_.Data()->data_size[index], kMaxBlockSize);
+ user_buffers_[index].reset(new UserBuffer(backend_.get()));
+ user_buffers_[index]->Write(len, NULL, 0);
+
+ File* file = GetBackingFile(address, index);
+ int offset = 0;
+
+ if (address.is_block_file())
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+
+ if (!file ||
+ !file->Read(user_buffers_[index]->Data(), len, offset, NULL, NULL)) {
+ user_buffers_[index].reset();
+ return false;
+ }
+ return true;
+}
+
+bool EntryImpl::MoveToLocalBuffer(int index) {
+ if (!CopyToLocalBuffer(index))
+ return false;
+
+ Addr address(entry_.Data()->data_addr[index]);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Store();
+ DeleteData(address, index);
+
+ // If we lose this entry we'll see it as zero sized.
+ int len = entry_.Data()->data_size[index];
+ backend_->ModifyStorageSize(len - unreported_size_[index], 0);
+ unreported_size_[index] = len;
+ return true;
+}
+
+bool EntryImpl::ImportSeparateFile(int index, int new_size) {
+ if (entry_.Data()->data_size[index] > new_size)
+ UpdateSize(index, entry_.Data()->data_size[index], new_size);
+
+ return MoveToLocalBuffer(index);
+}
+
+bool EntryImpl::PrepareBuffer(int index, int offset, int buf_len) {
+ DCHECK(user_buffers_[index].get());
+ if ((user_buffers_[index]->End() && offset > user_buffers_[index]->End()) ||
+ offset > entry_.Data()->data_size[index]) {
+ // We are about to extend the buffer or the file (with zeros), so make sure
+ // that we are not overwriting anything.
+ Addr address(entry_.Data()->data_addr[index]);
+ if (address.is_initialized() && address.is_separate_file()) {
+ if (!Flush(index, 0))
+ return false;
+ // There is an actual file already, and we don't want to keep track of
+ // its length so we let this operation go straight to disk.
+ // The only case when a buffer is allowed to extend the file (as in fill
+ // with zeros before the start) is when there is no file yet to extend.
+ user_buffers_[index].reset();
+ return true;
+ }
+ }
+
+ if (!user_buffers_[index]->PreWrite(offset, buf_len)) {
+ if (!Flush(index, offset + buf_len))
+ return false;
+
+ // Lets try again.
+ if (offset > user_buffers_[index]->End() ||
+ !user_buffers_[index]->PreWrite(offset, buf_len)) {
+ // We cannot complete the operation with a buffer.
+ DCHECK(!user_buffers_[index]->Size());
+ DCHECK(!user_buffers_[index]->Start());
+ user_buffers_[index].reset();
+ }
+ }
+ return true;
+}
+
+bool EntryImpl::Flush(int index, int min_len) {
+ Addr address(entry_.Data()->data_addr[index]);
+ DCHECK(user_buffers_[index].get());
+ DCHECK(!address.is_initialized() || address.is_separate_file());
+ DVLOG(3) << "Flush";
+
+ int size = std::max(entry_.Data()->data_size[index], min_len);
+ if (size && !address.is_initialized() && !CreateDataBlock(index, size))
+ return false;
+
+ if (!entry_.Data()->data_size[index]) {
+ DCHECK(!user_buffers_[index]->Size());
+ return true;
+ }
+
+ address.set_value(entry_.Data()->data_addr[index]);
+
+ int len = user_buffers_[index]->Size();
+ int offset = user_buffers_[index]->Start();
+ if (!len && !offset)
+ return true;
+
+ if (address.is_block_file()) {
+ DCHECK_EQ(len, entry_.Data()->data_size[index]);
+ DCHECK(!offset);
+ offset = address.start_block() * address.BlockSize() + kBlockHeaderSize;
+ }
+
+ File* file = GetBackingFile(address, index);
+ if (!file)
+ return false;
+
+ if (!file->Write(user_buffers_[index]->Data(), len, offset, NULL, NULL))
+ return false;
+ user_buffers_[index]->Reset();
+
+ return true;
+}
+
+void EntryImpl::UpdateSize(int index, int old_size, int new_size) {
+ if (entry_.Data()->data_size[index] == new_size)
+ return;
+
+ unreported_size_[index] += new_size - old_size;
+ entry_.Data()->data_size[index] = new_size;
+ entry_.set_modified();
+}
+
+int EntryImpl::InitSparseData() {
+ if (sparse_.get())
+ return net::OK;
+
+ // Use a local variable so that sparse_ never goes from 'valid' to NULL.
+ scoped_ptr<SparseControl> sparse(new SparseControl(this));
+ int result = sparse->Init();
+ if (net::OK == result)
+ sparse_.swap(sparse);
+
+ return result;
+}
+
+void EntryImpl::SetEntryFlags(uint32 flags) {
+ entry_.Data()->flags |= flags;
+ entry_.set_modified();
+}
+
+uint32 EntryImpl::GetEntryFlags() {
+ return entry_.Data()->flags;
+}
+
+void EntryImpl::GetData(int index, char** buffer, Addr* address) {
+ DCHECK(backend_);
+ if (user_buffers_[index].get() && user_buffers_[index]->Size() &&
+ !user_buffers_[index]->Start()) {
+ // The data is already in memory, just copy it and we're done.
+ int data_len = entry_.Data()->data_size[index];
+ if (data_len <= user_buffers_[index]->Size()) {
+ DCHECK(!user_buffers_[index]->Start());
+ *buffer = new char[data_len];
+ memcpy(*buffer, user_buffers_[index]->Data(), data_len);
+ return;
+ }
+ }
+
+ // Bad news: we'd have to read the info from disk so instead we'll just tell
+ // the caller where to read from.
+ *buffer = NULL;
+ address->set_value(entry_.Data()->data_addr[index]);
+ if (address->is_initialized()) {
+ // Prevent us from deleting the block from the backing store.
+ backend_->ModifyStorageSize(entry_.Data()->data_size[index] -
+ unreported_size_[index], 0);
+ entry_.Data()->data_addr[index] = 0;
+ entry_.Data()->data_size[index] = 0;
+ }
+}
+
+void EntryImpl::ReportIOTime(Operation op, const base::TimeTicks& start) {
+ if (!backend_)
+ return;
+
+ switch (op) {
+ case kRead:
+ CACHE_UMA(AGE_MS, "ReadTime", 0, start);
+ break;
+ case kWrite:
+ CACHE_UMA(AGE_MS, "WriteTime", 0, start);
+ break;
+ case kSparseRead:
+ CACHE_UMA(AGE_MS, "SparseReadTime", 0, start);
+ break;
+ case kSparseWrite:
+ CACHE_UMA(AGE_MS, "SparseWriteTime", 0, start);
+ break;
+ case kAsyncIO:
+ CACHE_UMA(AGE_MS, "AsyncIOTime", 0, start);
+ break;
+ case kReadAsync1:
+ CACHE_UMA(AGE_MS, "AsyncReadDispatchTime", 0, start);
+ break;
+ case kWriteAsync1:
+ CACHE_UMA(AGE_MS, "AsyncWriteDispatchTime", 0, start);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void EntryImpl::Log(const char* msg) {
+ int dirty = 0;
+ if (node_.HasData()) {
+ dirty = node_.Data()->dirty;
+ }
+
+ Trace("%s 0x%p 0x%x 0x%x", msg, reinterpret_cast<void*>(this),
+ entry_.address().value(), node_.address().value());
+
+ Trace(" data: 0x%x 0x%x 0x%x", entry_.Data()->data_addr[0],
+ entry_.Data()->data_addr[1], entry_.Data()->long_key);
+
+ Trace(" doomed: %d 0x%x", doomed_, dirty);
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/entry_impl_v3.h b/chromium/net/disk_cache/v3/entry_impl_v3.h
new file mode 100644
index 00000000000..af5bb4f5561
--- /dev/null
+++ b/chromium/net/disk_cache/v3/entry_impl_v3.h
@@ -0,0 +1,223 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_ENTRY_IMPL_H_
+#define NET_DISK_CACHE_ENTRY_IMPL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/storage_block.h"
+#include "net/disk_cache/storage_block-inl.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+class InFlightBackendIO;
+class SparseControl;
+
+// This class implements the Entry interface. An object of this
+// class represents a single entry on the cache.
+class NET_EXPORT_PRIVATE EntryImpl
+ : public Entry,
+ public base::RefCounted<EntryImpl> {
+ friend class base::RefCounted<EntryImpl>;
+ friend class SparseControl;
+ public:
+ enum Operation {
+ kRead,
+ kWrite,
+ kSparseRead,
+ kSparseWrite,
+ kAsyncIO,
+ kReadAsync1,
+ kWriteAsync1
+ };
+
+ EntryImpl(BackendImpl* backend, Addr address, bool read_only);
+
+ // Background implementation of the Entry interface.
+ void DoomImpl();
+ int ReadDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int WriteDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate);
+ int ReadSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int WriteSparseDataImpl(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int GetAvailableRangeImpl(int64 offset, int len, int64* start);
+ void CancelSparseIOImpl();
+ int ReadyForSparseIOImpl(const CompletionCallback& callback);
+
+ // Performs the initialization of a EntryImpl that will be added to the
+ // cache.
+ bool CreateEntry(Addr node_address, const std::string& key, uint32 hash);
+
+ uint32 GetHash();
+
+ // Returns true if this entry matches the lookup arguments.
+ bool IsSameEntry(const std::string& key, uint32 hash);
+
+ // Permamently destroys this entry.
+ void InternalDoom();
+
+ bool dirty() {
+ return dirty_;
+ }
+
+ bool doomed() {
+ return doomed_;
+ }
+
+ // Returns false if the entry is clearly invalid.
+ bool SanityCheck();
+ bool DataSanityCheck();
+
+ // Attempts to make this entry reachable though the key.
+ void FixForDelete();
+
+ // Set the access times for this entry. This method provides support for
+ // the upgrade tool.
+ void SetTimes(base::Time last_used, base::Time last_modified);
+
+ // Logs a begin event and enables logging for the EntryImpl. Will also cause
+ // an end event to be logged on destruction. The EntryImpl must have its key
+ // initialized before this is called. |created| is true if the Entry was
+ // created rather than opened.
+ void BeginLogging(net::NetLog* net_log, bool created);
+
+ const net::BoundNetLog& net_log() const;
+
+ // Entry interface.
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(int64 offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(int64 offset, int len, int64* start,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ enum {
+ kNumStreams = 3
+ };
+ class UserBuffer;
+
+ virtual ~EntryImpl();
+
+ // Do all the work for ReadDataImpl and WriteDataImpl. Implemented as
+ // separate functions to make logging of results simpler.
+ int InternalReadData(int index, int offset, IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback);
+ int InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback, bool truncate);
+
+ // Initializes the storage for an internal or external data block.
+ bool CreateDataBlock(int index, int size);
+
+ // Initializes the storage for an internal or external generic block.
+ bool CreateBlock(int size, Addr* address);
+
+ // Deletes the data pointed by address, maybe backed by files_[index].
+ // Note that most likely the caller should delete (and store) the reference to
+ // |address| *before* calling this method because we don't want to have an
+ // entry using an address that is already free.
+ void DeleteData(Addr address, int index);
+
+ // Updates ranking information.
+ void UpdateRank(bool modified);
+
+ // Deletes this entry from disk. If |everything| is false, only the user data
+ // will be removed, leaving the key and control data intact.
+ void DeleteEntryData(bool everything);
+
+ // Prepares the target file or buffer for a write of buf_len bytes at the
+ // given offset.
+ bool PrepareTarget(int index, int offset, int buf_len, bool truncate);
+
+ // Adjusts the internal buffer and file handle for a write that truncates this
+ // stream.
+ bool HandleTruncation(int index, int offset, int buf_len);
+
+ // Copies data from disk to the internal buffer.
+ bool CopyToLocalBuffer(int index);
+
+ // Reads from a block data file to this object's memory buffer.
+ bool MoveToLocalBuffer(int index);
+
+ // Loads the external file to this object's memory buffer.
+ bool ImportSeparateFile(int index, int new_size);
+
+ // Makes sure that the internal buffer can handle the a write of |buf_len|
+ // bytes to |offset|.
+ bool PrepareBuffer(int index, int offset, int buf_len);
+
+ // Flushes the in-memory data to the backing storage. The data destination
+ // is determined based on the current data length and |min_len|.
+ bool Flush(int index, int min_len);
+
+ // Updates the size of a given data stream.
+ void UpdateSize(int index, int old_size, int new_size);
+
+ // Initializes the sparse control object. Returns a net error code.
+ int InitSparseData();
+
+ // Adds the provided |flags| to the current EntryFlags for this entry.
+ void SetEntryFlags(uint32 flags);
+
+ // Returns the current EntryFlags for this entry.
+ uint32 GetEntryFlags();
+
+ // Gets the data stored at the given index. If the information is in memory,
+ // a buffer will be allocated and the data will be copied to it (the caller
+ // can find out the size of the buffer before making this call). Otherwise,
+ // the cache address of the data will be returned, and that address will be
+ // removed from the regular book keeping of this entry so the caller is
+ // responsible for deleting the block (or file) from the backing store at some
+ // point; there is no need to report any storage-size change, only to do the
+ // actual cleanup.
+ void GetData(int index, char** buffer, Addr* address);
+
+ // Generates a histogram for the time spent working on this operation.
+ void ReportIOTime(Operation op, const base::TimeTicks& start);
+
+ // Logs this entry to the internal trace buffer.
+ void Log(const char* msg);
+
+ CacheEntryBlock entry_; // Key related information for this entry.
+ CacheRankingsBlock node_; // Rankings related information for this entry.
+ base::WeakPtr<BackendImpl> backend_; // Back pointer to the cache.
+ base::WeakPtr<InFlightBackendIO> background_queue_; // In-progress queue.
+ scoped_ptr<UserBuffer> user_buffers_[kNumStreams]; // Stores user data.
+ // Files to store external user data and key.
+ scoped_refptr<File> files_[kNumStreams + 1];
+ mutable std::string key_; // Copy of the key.
+ int unreported_size_[kNumStreams]; // Bytes not reported yet to the backend.
+ bool doomed_; // True if this entry was removed from the cache.
+ bool read_only_; // True if not yet writing.
+ bool dirty_; // True if we detected that this is a dirty entry.
+ scoped_ptr<SparseControl> sparse_; // Support for sparse entries.
+
+ net::BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(EntryImpl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_ENTRY_IMPL_H_
diff --git a/chromium/net/disk_cache/v3/eviction_v3.cc b/chromium/net/disk_cache/v3/eviction_v3.cc
new file mode 100644
index 00000000000..91275fc7bc2
--- /dev/null
+++ b/chromium/net/disk_cache/v3/eviction_v3.cc
@@ -0,0 +1,502 @@
+// Copyright (c) 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.
+
+// The eviction policy is a very simple pure LRU, so the elements at the end of
+// the list are evicted until kCleanUpMargin free space is available. There is
+// only one list in use (Rankings::NO_USE), and elements are sent to the front
+// of the list whenever they are accessed.
+
+// The new (in-development) eviction policy adds re-use as a factor to evict
+// an entry. The story so far:
+
+// Entries are linked on separate lists depending on how often they are used.
+// When we see an element for the first time, it goes to the NO_USE list; if
+// the object is reused later on, we move it to the LOW_USE list, until it is
+// used kHighUse times, at which point it is moved to the HIGH_USE list.
+// Whenever an element is evicted, we move it to the DELETED list so that if the
+// element is accessed again, we remember the fact that it was already stored
+// and maybe in the future we don't evict that element.
+
+// When we have to evict an element, first we try to use the last element from
+// the NO_USE list, then we move to the LOW_USE and only then we evict an entry
+// from the HIGH_USE. We attempt to keep entries on the cache for at least
+// kTargetTime hours (with frequently accessed items stored for longer periods),
+// but if we cannot do that, we fall-back to keep each list roughly the same
+// size so that we have a chance to see an element again and move it to another
+// list.
+
+#include "net/disk_cache/eviction.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/experiments.h"
+#include "net/disk_cache/histogram_macros.h"
+#include "net/disk_cache/trace.h"
+
+using base::Time;
+using base::TimeTicks;
+
+namespace {
+
+const int kCleanUpMargin = 1024 * 1024;
+const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
+const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
+const int kMaxDelayedTrims = 60;
+
+int LowWaterAdjust(int high_water) {
+ if (high_water < kCleanUpMargin)
+ return 0;
+
+ return high_water - kCleanUpMargin;
+}
+
+bool FallingBehind(int current_size, int max_size) {
+ return current_size > max_size - kCleanUpMargin * 20;
+}
+
+} // namespace
+
+namespace disk_cache {
+
+// The real initialization happens during Init(), init_ is the only member that
+// has to be initialized here.
+Eviction::Eviction()
+ : backend_(NULL),
+ init_(false),
+ ptr_factory_(this) {
+}
+
+Eviction::~Eviction() {
+}
+
+void Eviction::Init(BackendImpl* backend) {
+ // We grab a bunch of info from the backend to make the code a little cleaner
+ // when we're actually doing work.
+ backend_ = backend;
+ rankings_ = &backend->rankings_;
+ header_ = &backend_->data_->header;
+ max_size_ = LowWaterAdjust(backend_->max_size_);
+ index_size_ = backend->mask_ + 1;
+ new_eviction_ = backend->new_eviction_;
+ first_trim_ = true;
+ trimming_ = false;
+ delay_trim_ = false;
+ trim_delays_ = 0;
+ init_ = true;
+ test_mode_ = false;
+}
+
+void Eviction::Stop() {
+ // It is possible for the backend initialization to fail, in which case this
+ // object was never initialized... and there is nothing to do.
+ if (!init_)
+ return;
+
+ // We want to stop further evictions, so let's pretend that we are busy from
+ // this point on.
+ DCHECK(!trimming_);
+ trimming_ = true;
+ ptr_factory_.InvalidateWeakPtrs();
+}
+
+void Eviction::TrimCache(bool empty) {
+ if (backend_->disabled_ || trimming_)
+ return;
+
+ if (!empty && !ShouldTrim())
+ return PostDelayedTrim();
+
+ if (new_eviction_)
+ return TrimCacheV2(empty);
+
+ Trace("*** Trim Cache ***");
+ trimming_ = true;
+ TimeTicks start = TimeTicks::Now();
+ Rankings::ScopedRankingsBlock node(rankings_);
+ Rankings::ScopedRankingsBlock next(
+ rankings_, rankings_->GetPrev(node.get(), Rankings::NO_USE));
+ int deleted_entries = 0;
+ int target_size = empty ? 0 : max_size_;
+ while ((header_->num_bytes > target_size || test_mode_) && next.get()) {
+ // The iterator could be invalidated within EvictEntry().
+ if (!next->HasData())
+ break;
+ node.reset(next.release());
+ next.reset(rankings_->GetPrev(node.get(), Rankings::NO_USE));
+ if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
+ // This entry is not being used by anybody.
+ // Do NOT use node as an iterator after this point.
+ rankings_->TrackRankingsBlock(node.get(), false);
+ if (EvictEntry(node.get(), empty, Rankings::NO_USE) && !test_mode_)
+ deleted_entries++;
+
+ if (!empty && test_mode_)
+ break;
+ }
+ if (!empty && (deleted_entries > 20 ||
+ (TimeTicks::Now() - start).InMilliseconds() > 20)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
+ break;
+ }
+ }
+
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV1", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV1", 0, start);
+ }
+ CACHE_UMA(COUNTS, "TrimItemsV1", 0, deleted_entries);
+
+ trimming_ = false;
+ Trace("*** Trim Cache end ***");
+ return;
+}
+
+void Eviction::OnOpenEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ DCHECK_EQ(ENTRY_NORMAL, info->state);
+
+ if (info->reuse_count < kint32max) {
+ info->reuse_count++;
+ entry->entry()->set_modified();
+
+ // We may need to move this to a new list.
+ if (1 == info->reuse_count) {
+ rankings_->Remove(entry->rankings(), Rankings::NO_USE, true);
+ rankings_->Insert(entry->rankings(), false, Rankings::LOW_USE);
+ entry->entry()->Store();
+ } else if (kHighUse == info->reuse_count) {
+ rankings_->Remove(entry->rankings(), Rankings::LOW_USE, true);
+ rankings_->Insert(entry->rankings(), false, Rankings::HIGH_USE);
+ entry->entry()->Store();
+ }
+ }
+}
+
+void Eviction::OnCreateEntryV2(EntryImpl* entry) {
+ EntryStore* info = entry->entry()->Data();
+ switch (info->state) {
+ case ENTRY_NORMAL: {
+ DCHECK(!info->reuse_count);
+ DCHECK(!info->refetch_count);
+ break;
+ };
+ case ENTRY_EVICTED: {
+ if (info->refetch_count < kint32max)
+ info->refetch_count++;
+
+ if (info->refetch_count > kHighUse && info->reuse_count < kHighUse) {
+ info->reuse_count = kHighUse;
+ } else {
+ info->reuse_count++;
+ }
+ info->state = ENTRY_NORMAL;
+ entry->entry()->Store();
+ rankings_->Remove(entry->rankings(), Rankings::DELETED, true);
+ break;
+ };
+ default:
+ NOTREACHED();
+ }
+
+ rankings_->Insert(entry->rankings(), true, GetListForEntryV2(entry));
+}
+
+void Eviction::SetTestMode() {
+ test_mode_ = true;
+}
+
+void Eviction::TrimDeletedList(bool empty) {
+ DCHECK(test_mode_ && new_eviction_);
+ TrimDeleted(empty);
+}
+
+// -----------------------------------------------------------------------
+
+void Eviction::PostDelayedTrim() {
+ // Prevent posting multiple tasks.
+ if (delay_trim_)
+ return;
+ delay_trim_ = true;
+ trim_delays_++;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Eviction::DelayedTrim, ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(1000));
+}
+
+void Eviction::DelayedTrim() {
+ delay_trim_ = false;
+ if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
+ return PostDelayedTrim();
+
+ TrimCache(false);
+}
+
+bool Eviction::ShouldTrim() {
+ if (!FallingBehind(header_->num_bytes, max_size_) &&
+ trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded()) {
+ return false;
+ }
+
+ UMA_HISTOGRAM_COUNTS("DiskCache.TrimDelays", trim_delays_);
+ trim_delays_ = 0;
+ return true;
+}
+
+bool Eviction::ShouldTrimDeleted() {
+ int index_load = header_->num_entries * 100 / index_size_;
+
+ // If the index is not loaded, the deleted list will tend to double the size
+ // of the other lists 3 lists (40% of the total). Otherwise, all lists will be
+ // about the same size.
+ int max_length = (index_load < 25) ? header_->num_entries * 2 / 5 :
+ header_->num_entries / 4;
+ return (!test_mode_ && header_->lru.sizes[Rankings::DELETED] > max_length);
+}
+
+bool Eviction::EvictEntry(CacheRankingsBlock* node, bool empty,
+ Rankings::List list) {
+ EntryImpl* entry = backend_->GetEnumeratedEntry(node, list);
+ if (!entry) {
+ Trace("NewEntry failed on Trim 0x%x", node->address().value());
+ return false;
+ }
+
+ ReportTrimTimes(entry);
+ if (empty || !new_eviction_) {
+ entry->DoomImpl();
+ } else {
+ entry->DeleteEntryData(false);
+ EntryStore* info = entry->entry()->Data();
+ DCHECK_EQ(ENTRY_NORMAL, info->state);
+
+ rankings_->Remove(entry->rankings(), GetListForEntryV2(entry), true);
+ info->state = ENTRY_EVICTED;
+ entry->entry()->Store();
+ rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
+ }
+ if (!empty)
+ backend_->OnEvent(Stats::TRIM_ENTRY);
+
+ entry->Release();
+
+ return true;
+}
+
+void Eviction::TrimCacheV2(bool empty) {
+ Trace("*** Trim Cache ***");
+ trimming_ = true;
+ TimeTicks start = TimeTicks::Now();
+
+ const int kListsToSearch = 3;
+ Rankings::ScopedRankingsBlock next[kListsToSearch];
+ int list = Rankings::LAST_ELEMENT;
+
+ // Get a node from each list.
+ for (int i = 0; i < kListsToSearch; i++) {
+ bool done = false;
+ next[i].set_rankings(rankings_);
+ if (done)
+ continue;
+ next[i].reset(rankings_->GetPrev(NULL, static_cast<Rankings::List>(i)));
+ if (!empty && NodeIsOldEnough(next[i].get(), i)) {
+ list = static_cast<Rankings::List>(i);
+ done = true;
+ }
+ }
+
+ // If we are not meeting the time targets lets move on to list length.
+ if (!empty && Rankings::LAST_ELEMENT == list)
+ list = SelectListByLength(next);
+
+ if (empty)
+ list = 0;
+
+ Rankings::ScopedRankingsBlock node(rankings_);
+ int deleted_entries = 0;
+ int target_size = empty ? 0 : max_size_;
+
+ for (; list < kListsToSearch; list++) {
+ while ((header_->num_bytes > target_size || test_mode_) &&
+ next[list].get()) {
+ // The iterator could be invalidated within EvictEntry().
+ if (!next[list]->HasData())
+ break;
+ node.reset(next[list].release());
+ next[list].reset(rankings_->GetPrev(node.get(),
+ static_cast<Rankings::List>(list)));
+ if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
+ // This entry is not being used by anybody.
+ // Do NOT use node as an iterator after this point.
+ rankings_->TrackRankingsBlock(node.get(), false);
+ if (EvictEntry(node.get(), empty, static_cast<Rankings::List>(list)))
+ deleted_entries++;
+
+ if (!empty && test_mode_)
+ break;
+ }
+ if (!empty && (deleted_entries > 20 ||
+ (TimeTicks::Now() - start).InMilliseconds() > 20)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
+ break;
+ }
+ }
+ if (!empty)
+ list = kListsToSearch;
+ }
+
+ if (empty) {
+ TrimDeleted(true);
+ } else if (ShouldTrimDeleted()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), empty));
+ }
+
+ if (empty) {
+ CACHE_UMA(AGE_MS, "TotalClearTimeV2", 0, start);
+ } else {
+ CACHE_UMA(AGE_MS, "TotalTrimTimeV2", 0, start);
+ }
+ CACHE_UMA(COUNTS, "TrimItemsV2", 0, deleted_entries);
+
+ Trace("*** Trim Cache end ***");
+ trimming_ = false;
+ return;
+}
+
+// This is a minimal implementation that just discards the oldest nodes.
+// TODO(rvargas): Do something better here.
+void Eviction::TrimDeleted(bool empty) {
+ Trace("*** Trim Deleted ***");
+ if (backend_->disabled_)
+ return;
+
+ TimeTicks start = TimeTicks::Now();
+ Rankings::ScopedRankingsBlock node(rankings_);
+ Rankings::ScopedRankingsBlock next(
+ rankings_, rankings_->GetPrev(node.get(), Rankings::DELETED));
+ int deleted_entries = 0;
+ while (next.get() &&
+ (empty || (deleted_entries < 20 &&
+ (TimeTicks::Now() - start).InMilliseconds() < 20))) {
+ node.reset(next.release());
+ next.reset(rankings_->GetPrev(node.get(), Rankings::DELETED));
+ if (RemoveDeletedNode(node.get()))
+ deleted_entries++;
+ if (test_mode_)
+ break;
+ }
+
+ if (deleted_entries && !empty && ShouldTrimDeleted()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), false));
+ }
+
+ CACHE_UMA(AGE_MS, "TotalTrimDeletedTime", 0, start);
+ CACHE_UMA(COUNTS, "TrimDeletedItems", 0, deleted_entries);
+ Trace("*** Trim Deleted end ***");
+ return;
+}
+
+void Eviction::ReportTrimTimes(EntryImpl* entry) {
+ if (first_trim_) {
+ first_trim_ = false;
+ if (backend_->ShouldReportAgain()) {
+ CACHE_UMA(AGE, "TrimAge", 0, entry->GetLastUsed());
+ ReportListStats();
+ }
+
+ if (header_->lru.filled)
+ return;
+
+ header_->lru.filled = 1;
+
+ if (header_->create_time) {
+ // This is the first entry that we have to evict, generate some noise.
+ backend_->FirstEviction();
+ } else {
+ // This is an old file, but we may want more reports from this user so
+ // lets save some create_time.
+ Time::Exploded old = {0};
+ old.year = 2009;
+ old.month = 3;
+ old.day_of_month = 1;
+ header_->create_time = Time::FromLocalExploded(old).ToInternalValue();
+ }
+ }
+}
+
+bool Eviction::NodeIsOldEnough(CacheRankingsBlock* node, int list) {
+ if (!node)
+ return false;
+
+ // If possible, we want to keep entries on each list at least kTargetTime
+ // hours. Each successive list on the enumeration has 2x the target time of
+ // the previous list.
+ Time used = Time::FromInternalValue(node->Data()->last_used);
+ int multiplier = 1 << list;
+ return (Time::Now() - used).InHours() > kTargetTime * multiplier;
+}
+
+int Eviction::SelectListByLength(Rankings::ScopedRankingsBlock* next) {
+ int data_entries = header_->num_entries -
+ header_->lru.sizes[Rankings::DELETED];
+ // Start by having each list to be roughly the same size.
+ if (header_->lru.sizes[0] > data_entries / 3)
+ return 0;
+
+ int list = (header_->lru.sizes[1] > data_entries / 3) ? 1 : 2;
+
+ // Make sure that frequently used items are kept for a minimum time; we know
+ // that this entry is not older than its current target, but it must be at
+ // least older than the target for list 0 (kTargetTime), as long as we don't
+ // exhaust list 0.
+ if (!NodeIsOldEnough(next[list].get(), 0) &&
+ header_->lru.sizes[0] > data_entries / 10)
+ list = 0;
+
+ return list;
+}
+
+void Eviction::ReportListStats() {
+ if (!new_eviction_)
+ return;
+
+ Rankings::ScopedRankingsBlock last1(rankings_,
+ rankings_->GetPrev(NULL, Rankings::NO_USE));
+ Rankings::ScopedRankingsBlock last2(rankings_,
+ rankings_->GetPrev(NULL, Rankings::LOW_USE));
+ Rankings::ScopedRankingsBlock last3(rankings_,
+ rankings_->GetPrev(NULL, Rankings::HIGH_USE));
+ Rankings::ScopedRankingsBlock last4(rankings_,
+ rankings_->GetPrev(NULL, Rankings::DELETED));
+
+ if (last1.get())
+ CACHE_UMA(AGE, "NoUseAge", 0,
+ Time::FromInternalValue(last1.get()->Data()->last_used));
+ if (last2.get())
+ CACHE_UMA(AGE, "LowUseAge", 0,
+ Time::FromInternalValue(last2.get()->Data()->last_used));
+ if (last3.get())
+ CACHE_UMA(AGE, "HighUseAge", 0,
+ Time::FromInternalValue(last3.get()->Data()->last_used));
+ if (last4.get())
+ CACHE_UMA(AGE, "DeletedAge", 0,
+ Time::FromInternalValue(last4.get()->Data()->last_used));
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/eviction_v3.h b/chromium/net/disk_cache/v3/eviction_v3.h
new file mode 100644
index 00000000000..1f05b0e0881
--- /dev/null
+++ b/chromium/net/disk_cache/v3/eviction_v3.h
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_EVICTION_H_
+#define NET_DISK_CACHE_EVICTION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/rankings.h"
+
+namespace disk_cache {
+
+class BackendImpl;
+class EntryImpl;
+
+// This class implements the eviction algorithm for the cache and it is tightly
+// integrated with BackendImpl.
+class Eviction {
+ public:
+ Eviction();
+ ~Eviction();
+
+ void Init(BackendImpl* backend);
+ void Stop();
+
+ // Deletes entries from the cache until the current size is below the limit.
+ // If empty is true, the whole cache will be trimmed, regardless of being in
+ // use.
+ void TrimCache(bool empty);
+
+ // Notifications of interesting events for a given entry.
+ void OnOpenEntry(EntryImpl* entry);
+ void OnCreateEntry(EntryImpl* entry);
+
+ // Testing interface.
+ void SetTestMode();
+ void TrimDeletedList(bool empty);
+
+ private:
+ void PostDelayedTrim();
+ void DelayedTrim();
+ bool ShouldTrim();
+ bool ShouldTrimDeleted();
+ bool EvictEntry(CacheRankingsBlock* node, bool empty, Rankings::List list);
+
+ void TrimCacheV2(bool empty);
+ void TrimDeleted(bool empty);
+
+ bool NodeIsOldEnough(CacheRankingsBlock* node, int list);
+ int SelectListByLength(Rankings::ScopedRankingsBlock* next);
+ void ReportListStats();
+
+ BackendImpl* backend_;
+ Rankings* rankings_;
+ IndexHeader* header_;
+ int max_size_;
+ int trim_delays_;
+ int index_size_;
+ bool new_eviction_;
+ bool first_trim_;
+ bool trimming_;
+ bool delay_trim_;
+ bool init_;
+ bool test_mode_;
+ base::WeakPtrFactory<Eviction> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Eviction);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_EVICTION_H_
diff --git a/chromium/net/disk_cache/v3/sparse_control_v3.cc b/chromium/net/disk_cache/v3/sparse_control_v3.cc
new file mode 100644
index 00000000000..d9700adc89c
--- /dev/null
+++ b/chromium/net/disk_cache/v3/sparse_control_v3.cc
@@ -0,0 +1,868 @@
+// Copyright (c) 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.
+
+#include "net/disk_cache/sparse_control.h"
+
+#include "base/bind.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/disk_cache/file.h"
+#include "net/disk_cache/net_log_parameters.h"
+
+using base::Time;
+
+namespace {
+
+// Stream of the sparse data index.
+const int kSparseIndex = 2;
+
+// Stream of the sparse data.
+const int kSparseData = 1;
+
+// We can have up to 64k children.
+const int kMaxMapSize = 8 * 1024;
+
+// The maximum number of bytes that a child can store.
+const int kMaxEntrySize = 0x100000;
+
+// The size of each data block (tracked by the child allocation bitmap).
+const int kBlockSize = 1024;
+
+// Returns the name of a child entry given the base_name and signature of the
+// parent and the child_id.
+// If the entry is called entry_name, child entries will be named something
+// like Range_entry_name:XXX:YYY where XXX is the entry signature and YYY is the
+// number of the particular child.
+std::string GenerateChildName(const std::string& base_name, int64 signature,
+ int64 child_id) {
+ return base::StringPrintf("Range_%s:%" PRIx64 ":%" PRIx64, base_name.c_str(),
+ signature, child_id);
+}
+
+// This class deletes the children of a sparse entry.
+class ChildrenDeleter
+ : public base::RefCounted<ChildrenDeleter>,
+ public disk_cache::FileIOCallback {
+ public:
+ ChildrenDeleter(disk_cache::BackendImpl* backend, const std::string& name)
+ : backend_(backend->GetWeakPtr()), name_(name), signature_(0) {}
+
+ virtual void OnFileIOComplete(int bytes_copied) OVERRIDE;
+
+ // Two ways of deleting the children: if we have the children map, use Start()
+ // directly, otherwise pass the data address to ReadData().
+ void Start(char* buffer, int len);
+ void ReadData(disk_cache::Addr address, int len);
+
+ private:
+ friend class base::RefCounted<ChildrenDeleter>;
+ virtual ~ChildrenDeleter() {}
+
+ void DeleteChildren();
+
+ base::WeakPtr<disk_cache::BackendImpl> backend_;
+ std::string name_;
+ disk_cache::Bitmap children_map_;
+ int64 signature_;
+ scoped_ptr<char[]> buffer_;
+ DISALLOW_COPY_AND_ASSIGN(ChildrenDeleter);
+};
+
+// This is the callback of the file operation.
+void ChildrenDeleter::OnFileIOComplete(int bytes_copied) {
+ char* buffer = buffer_.release();
+ Start(buffer, bytes_copied);
+}
+
+void ChildrenDeleter::Start(char* buffer, int len) {
+ buffer_.reset(buffer);
+ if (len < static_cast<int>(sizeof(disk_cache::SparseData)))
+ return Release();
+
+ // Just copy the information from |buffer|, delete |buffer| and start deleting
+ // the child entries.
+ disk_cache::SparseData* data =
+ reinterpret_cast<disk_cache::SparseData*>(buffer);
+ signature_ = data->header.signature;
+
+ int num_bits = (len - sizeof(disk_cache::SparseHeader)) * 8;
+ children_map_.Resize(num_bits, false);
+ children_map_.SetMap(data->bitmap, num_bits / 32);
+ buffer_.reset();
+
+ DeleteChildren();
+}
+
+void ChildrenDeleter::ReadData(disk_cache::Addr address, int len) {
+ DCHECK(address.is_block_file());
+ if (!backend_)
+ return Release();
+
+ disk_cache::File* file(backend_->File(address));
+ if (!file)
+ return Release();
+
+ size_t file_offset = address.start_block() * address.BlockSize() +
+ disk_cache::kBlockHeaderSize;
+
+ buffer_.reset(new char[len]);
+ bool completed;
+ if (!file->Read(buffer_.get(), len, file_offset, this, &completed))
+ return Release();
+
+ if (completed)
+ OnFileIOComplete(len);
+
+ // And wait until OnFileIOComplete gets called.
+}
+
+void ChildrenDeleter::DeleteChildren() {
+ int child_id = 0;
+ if (!children_map_.FindNextSetBit(&child_id) || !backend_) {
+ // We are done. Just delete this object.
+ return Release();
+ }
+ std::string child_name = GenerateChildName(name_, signature_, child_id);
+ backend_->SyncDoomEntry(child_name);
+ children_map_.Set(child_id, false);
+
+ // Post a task to delete the next child.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&ChildrenDeleter::DeleteChildren, this));
+}
+
+// -----------------------------------------------------------------------
+
+// Returns the NetLog event type corresponding to a SparseOperation.
+net::NetLog::EventType GetSparseEventType(
+ disk_cache::SparseControl::SparseOperation operation) {
+ switch (operation) {
+ case disk_cache::SparseControl::kReadOperation:
+ return net::NetLog::TYPE_SPARSE_READ;
+ case disk_cache::SparseControl::kWriteOperation:
+ return net::NetLog::TYPE_SPARSE_WRITE;
+ case disk_cache::SparseControl::kGetRangeOperation:
+ return net::NetLog::TYPE_SPARSE_GET_RANGE;
+ default:
+ NOTREACHED();
+ return net::NetLog::TYPE_CANCELLED;
+ }
+}
+
+// Logs the end event for |operation| on a child entry. Range operations log
+// no events for each child they search through.
+void LogChildOperationEnd(const net::BoundNetLog& net_log,
+ disk_cache::SparseControl::SparseOperation operation,
+ int result) {
+ if (net_log.IsLoggingAllEvents()) {
+ net::NetLog::EventType event_type;
+ switch (operation) {
+ case disk_cache::SparseControl::kReadOperation:
+ event_type = net::NetLog::TYPE_SPARSE_READ_CHILD_DATA;
+ break;
+ case disk_cache::SparseControl::kWriteOperation:
+ event_type = net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA;
+ break;
+ case disk_cache::SparseControl::kGetRangeOperation:
+ return;
+ default:
+ NOTREACHED();
+ return;
+ }
+ net_log.EndEventWithNetErrorCode(event_type, result);
+ }
+}
+
+} // namespace.
+
+namespace disk_cache {
+
+SparseControl::SparseControl(EntryImpl* entry)
+ : entry_(entry),
+ child_(NULL),
+ operation_(kNoOperation),
+ pending_(false),
+ finished_(false),
+ init_(false),
+ range_found_(false),
+ abort_(false),
+ child_map_(child_data_.bitmap, kNumSparseBits, kNumSparseBits / 32),
+ offset_(0),
+ buf_len_(0),
+ child_offset_(0),
+ child_len_(0),
+ result_(0) {
+ memset(&sparse_header_, 0, sizeof(sparse_header_));
+ memset(&child_data_, 0, sizeof(child_data_));
+}
+
+SparseControl::~SparseControl() {
+ if (child_)
+ CloseChild();
+ if (init_)
+ WriteSparseData();
+}
+
+bool SparseControl::CouldBeSparse() const {
+ DCHECK(!init_);
+
+ if (entry_->GetDataSize(kSparseData))
+ return false;
+
+ // We don't verify the data, just see if it could be there.
+ return (entry_->GetDataSize(kSparseIndex) != 0);
+}
+
+int SparseControl::StartIO(SparseOperation op, int64 offset, net::IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback) {
+ DCHECK(init_);
+ // We don't support simultaneous IO for sparse data.
+ if (operation_ != kNoOperation)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (offset < 0 || buf_len < 0)
+ return net::ERR_INVALID_ARGUMENT;
+
+ // We only support up to 64 GB.
+ if (offset + buf_len >= 0x1000000000LL || offset + buf_len < 0)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ DCHECK(!user_buf_);
+ DCHECK(user_callback_.is_null());
+
+ if (!buf && (op == kReadOperation || op == kWriteOperation))
+ return 0;
+
+ // Copy the operation parameters.
+ operation_ = op;
+ offset_ = offset;
+ user_buf_ = buf ? new net::DrainableIOBuffer(buf, buf_len) : NULL;
+ buf_len_ = buf_len;
+ user_callback_ = callback;
+
+ result_ = 0;
+ pending_ = false;
+ finished_ = false;
+ abort_ = false;
+
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ GetSparseEventType(operation_),
+ CreateNetLogSparseOperationCallback(offset_, buf_len_));
+ }
+ DoChildrenIO();
+
+ if (!pending_) {
+ // Everything was done synchronously.
+ operation_ = kNoOperation;
+ user_buf_ = NULL;
+ user_callback_.Reset();
+ return result_;
+ }
+
+ return net::ERR_IO_PENDING;
+}
+
+int SparseControl::GetAvailableRange(int64 offset, int len, int64* start) {
+ DCHECK(init_);
+ // We don't support simultaneous IO for sparse data.
+ if (operation_ != kNoOperation)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ DCHECK(start);
+
+ range_found_ = false;
+ int result = StartIO(
+ kGetRangeOperation, offset, NULL, len, CompletionCallback());
+ if (range_found_) {
+ *start = offset_;
+ return result;
+ }
+
+ // This is a failure. We want to return a valid start value in any case.
+ *start = offset;
+ return result < 0 ? result : 0; // Don't mask error codes to the caller.
+}
+
+void SparseControl::CancelIO() {
+ if (operation_ == kNoOperation)
+ return;
+ abort_ = true;
+}
+
+int SparseControl::ReadyToUse(const CompletionCallback& callback) {
+ if (!abort_)
+ return net::OK;
+
+ // We'll grab another reference to keep this object alive because we just have
+ // one extra reference due to the pending IO operation itself, but we'll
+ // release that one before invoking user_callback_.
+ entry_->AddRef(); // Balanced in DoAbortCallbacks.
+ abort_callbacks_.push_back(callback);
+ return net::ERR_IO_PENDING;
+}
+
+// Static
+void SparseControl::DeleteChildren(EntryImpl* entry) {
+ DCHECK(entry->GetEntryFlags() & PARENT_ENTRY);
+ int data_len = entry->GetDataSize(kSparseIndex);
+ if (data_len < static_cast<int>(sizeof(SparseData)) ||
+ entry->GetDataSize(kSparseData))
+ return;
+
+ int map_len = data_len - sizeof(SparseHeader);
+ if (map_len > kMaxMapSize || map_len % 4)
+ return;
+
+ char* buffer;
+ Addr address;
+ entry->GetData(kSparseIndex, &buffer, &address);
+ if (!buffer && !address.is_initialized())
+ return;
+
+ entry->net_log().AddEvent(net::NetLog::TYPE_SPARSE_DELETE_CHILDREN);
+
+ DCHECK(entry->backend_);
+ ChildrenDeleter* deleter = new ChildrenDeleter(entry->backend_.get(),
+ entry->GetKey());
+ // The object will self destruct when finished.
+ deleter->AddRef();
+
+ if (buffer) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ChildrenDeleter::Start, deleter, buffer, data_len));
+ } else {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ChildrenDeleter::ReadData, deleter, address, data_len));
+ }
+}
+
+// -----------------------------------------------------------------------
+
+int SparseControl::Init() {
+ DCHECK(!init_);
+
+ // We should not have sparse data for the exposed entry.
+ if (entry_->GetDataSize(kSparseData))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Now see if there is something where we store our data.
+ int rv = net::OK;
+ int data_len = entry_->GetDataSize(kSparseIndex);
+ if (!data_len) {
+ rv = CreateSparseEntry();
+ } else {
+ rv = OpenSparseEntry(data_len);
+ }
+
+ if (rv == net::OK)
+ init_ = true;
+ return rv;
+}
+
+// We are going to start using this entry to store sparse data, so we have to
+// initialize our control info.
+int SparseControl::CreateSparseEntry() {
+ if (CHILD_ENTRY & entry_->GetEntryFlags())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ memset(&sparse_header_, 0, sizeof(sparse_header_));
+ sparse_header_.signature = Time::Now().ToInternalValue();
+ sparse_header_.magic = kIndexMagic;
+ sparse_header_.parent_key_len = entry_->GetKey().size();
+ children_map_.Resize(kNumSparseBits, true);
+
+ // Save the header. The bitmap is saved in the destructor.
+ scoped_refptr<net::IOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
+
+ int rv = entry_->WriteData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
+ CompletionCallback(), false);
+ if (rv != sizeof(sparse_header_)) {
+ DLOG(ERROR) << "Unable to save sparse_header_";
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ }
+
+ entry_->SetEntryFlags(PARENT_ENTRY);
+ return net::OK;
+}
+
+// We are opening an entry from disk. Make sure that our control data is there.
+int SparseControl::OpenSparseEntry(int data_len) {
+ if (data_len < static_cast<int>(sizeof(SparseData)))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (entry_->GetDataSize(kSparseData))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ if (!(PARENT_ENTRY & entry_->GetEntryFlags()))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Dont't go over board with the bitmap. 8 KB gives us offsets up to 64 GB.
+ int map_len = data_len - sizeof(sparse_header_);
+ if (map_len > kMaxMapSize || map_len % 4)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ scoped_refptr<net::IOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
+
+ // Read header.
+ int rv = entry_->ReadData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
+ CompletionCallback());
+ if (rv != static_cast<int>(sizeof(sparse_header_)))
+ return net::ERR_CACHE_READ_FAILURE;
+
+ // The real validation should be performed by the caller. This is just to
+ // double check.
+ if (sparse_header_.magic != kIndexMagic ||
+ sparse_header_.parent_key_len !=
+ static_cast<int>(entry_->GetKey().size()))
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+
+ // Read the actual bitmap.
+ buf = new net::IOBuffer(map_len);
+ rv = entry_->ReadData(kSparseIndex, sizeof(sparse_header_), buf.get(),
+ map_len, CompletionCallback());
+ if (rv != map_len)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ // Grow the bitmap to the current size and copy the bits.
+ children_map_.Resize(map_len * 8, false);
+ children_map_.SetMap(reinterpret_cast<uint32*>(buf->data()), map_len);
+ return net::OK;
+}
+
+bool SparseControl::OpenChild() {
+ DCHECK_GE(result_, 0);
+
+ std::string key = GenerateChildKey();
+ if (child_) {
+ // Keep using the same child or open another one?.
+ if (key == child_->GetKey())
+ return true;
+ CloseChild();
+ }
+
+ // See if we are tracking this child.
+ if (!ChildPresent())
+ return ContinueWithoutChild(key);
+
+ if (!entry_->backend_)
+ return false;
+
+ child_ = entry_->backend_->OpenEntryImpl(key);
+ if (!child_)
+ return ContinueWithoutChild(key);
+
+ EntryImpl* child = static_cast<EntryImpl*>(child_);
+ if (!(CHILD_ENTRY & child->GetEntryFlags()) ||
+ child->GetDataSize(kSparseIndex) <
+ static_cast<int>(sizeof(child_data_)))
+ return KillChildAndContinue(key, false);
+
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ // Read signature.
+ int rv = child_->ReadData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback());
+ if (rv != sizeof(child_data_))
+ return KillChildAndContinue(key, true); // This is a fatal failure.
+
+ if (child_data_.header.signature != sparse_header_.signature ||
+ child_data_.header.magic != kIndexMagic)
+ return KillChildAndContinue(key, false);
+
+ if (child_data_.header.last_block_len < 0 ||
+ child_data_.header.last_block_len > kBlockSize) {
+ // Make sure these values are always within range.
+ child_data_.header.last_block_len = 0;
+ child_data_.header.last_block = -1;
+ }
+
+ return true;
+}
+
+void SparseControl::CloseChild() {
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ // Save the allocation bitmap before closing the child entry.
+ int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback(),
+ false);
+ if (rv != sizeof(child_data_))
+ DLOG(ERROR) << "Failed to save child data";
+ child_->Release();
+ child_ = NULL;
+}
+
+// We were not able to open this child; see what we can do.
+bool SparseControl::ContinueWithoutChild(const std::string& key) {
+ if (kReadOperation == operation_)
+ return false;
+ if (kGetRangeOperation == operation_)
+ return true;
+
+ if (!entry_->backend_)
+ return false;
+
+ child_ = entry_->backend_->CreateEntryImpl(key);
+ if (!child_) {
+ child_ = NULL;
+ result_ = net::ERR_CACHE_READ_FAILURE;
+ return false;
+ }
+ // Write signature.
+ InitChildData();
+ return true;
+}
+
+void SparseControl::WriteSparseData() {
+ scoped_refptr<net::IOBuffer> buf(new net::WrappedIOBuffer(
+ reinterpret_cast<const char*>(children_map_.GetMap())));
+
+ int len = children_map_.ArraySize() * 4;
+ int rv = entry_->WriteData(kSparseIndex, sizeof(sparse_header_), buf.get(),
+ len, CompletionCallback(), false);
+ if (rv != len) {
+ DLOG(ERROR) << "Unable to save sparse map";
+ }
+}
+
+bool SparseControl::DoChildIO() {
+ finished_ = true;
+ if (!buf_len_ || result_ < 0)
+ return false;
+
+ if (!OpenChild())
+ return false;
+
+ if (!VerifyRange())
+ return false;
+
+ // We have more work to do. Let's not trigger a callback to the caller.
+ finished_ = false;
+ CompletionCallback callback;
+ if (!user_callback_.is_null()) {
+ callback =
+ base::Bind(&SparseControl::OnChildIOCompleted, base::Unretained(this));
+ }
+
+ int rv = 0;
+ switch (operation_) {
+ case kReadOperation:
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ net::NetLog::TYPE_SPARSE_READ_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
+ child_len_));
+ }
+ rv = child_->ReadDataImpl(kSparseData, child_offset_, user_buf_.get(),
+ child_len_, callback);
+ break;
+ case kWriteOperation:
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().BeginEvent(
+ net::NetLog::TYPE_SPARSE_WRITE_CHILD_DATA,
+ CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
+ child_len_));
+ }
+ rv = child_->WriteDataImpl(kSparseData, child_offset_, user_buf_.get(),
+ child_len_, callback, false);
+ break;
+ case kGetRangeOperation:
+ rv = DoGetAvailableRange();
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ if (rv == net::ERR_IO_PENDING) {
+ if (!pending_) {
+ pending_ = true;
+ // The child will protect himself against closing the entry while IO is in
+ // progress. However, this entry can still be closed, and that would not
+ // be a good thing for us, so we increase the refcount until we're
+ // finished doing sparse stuff.
+ entry_->AddRef(); // Balanced in DoUserCallback.
+ }
+ return false;
+ }
+ if (!rv)
+ return false;
+
+ DoChildIOCompleted(rv);
+ return true;
+}
+
+void SparseControl::DoChildIOCompleted(int result) {
+ LogChildOperationEnd(entry_->net_log(), operation_, result);
+ if (result < 0) {
+ // We fail the whole operation if we encounter an error.
+ result_ = result;
+ return;
+ }
+
+ UpdateRange(result);
+
+ result_ += result;
+ offset_ += result;
+ buf_len_ -= result;
+
+ // We'll be reusing the user provided buffer for the next chunk.
+ if (buf_len_ && user_buf_)
+ user_buf_->DidConsume(result);
+}
+
+std::string SparseControl::GenerateChildKey() {
+ return GenerateChildName(entry_->GetKey(), sparse_header_.signature,
+ offset_ >> 20);
+}
+
+// We are deleting the child because something went wrong.
+bool SparseControl::KillChildAndContinue(const std::string& key, bool fatal) {
+ SetChildBit(false);
+ child_->DoomImpl();
+ child_->Release();
+ child_ = NULL;
+ if (fatal) {
+ result_ = net::ERR_CACHE_READ_FAILURE;
+ return false;
+ }
+ return ContinueWithoutChild(key);
+}
+
+bool SparseControl::ChildPresent() {
+ int child_bit = static_cast<int>(offset_ >> 20);
+ if (children_map_.Size() <= child_bit)
+ return false;
+
+ return children_map_.Get(child_bit);
+}
+
+void SparseControl::SetChildBit(bool value) {
+ int child_bit = static_cast<int>(offset_ >> 20);
+
+ // We may have to increase the bitmap of child entries.
+ if (children_map_.Size() <= child_bit)
+ children_map_.Resize(Bitmap::RequiredArraySize(child_bit + 1) * 32, true);
+
+ children_map_.Set(child_bit, value);
+}
+
+bool SparseControl::VerifyRange() {
+ DCHECK_GE(result_, 0);
+
+ child_offset_ = static_cast<int>(offset_) & (kMaxEntrySize - 1);
+ child_len_ = std::min(buf_len_, kMaxEntrySize - child_offset_);
+
+ // We can write to (or get info from) anywhere in this child.
+ if (operation_ != kReadOperation)
+ return true;
+
+ // Check that there are no holes in this range.
+ int last_bit = (child_offset_ + child_len_ + 1023) >> 10;
+ int start = child_offset_ >> 10;
+ if (child_map_.FindNextBit(&start, last_bit, false)) {
+ // Something is not here.
+ DCHECK_GE(child_data_.header.last_block_len, 0);
+ DCHECK_LT(child_data_.header.last_block_len, kMaxEntrySize);
+ int partial_block_len = PartialBlockLength(start);
+ if (start == child_offset_ >> 10) {
+ // It looks like we don't have anything.
+ if (partial_block_len <= (child_offset_ & (kBlockSize - 1)))
+ return false;
+ }
+
+ // We have the first part.
+ child_len_ = (start << 10) - child_offset_;
+ if (partial_block_len) {
+ // We may have a few extra bytes.
+ child_len_ = std::min(child_len_ + partial_block_len, buf_len_);
+ }
+ // There is no need to read more after this one.
+ buf_len_ = child_len_;
+ }
+ return true;
+}
+
+void SparseControl::UpdateRange(int result) {
+ if (result <= 0 || operation_ != kWriteOperation)
+ return;
+
+ DCHECK_GE(child_data_.header.last_block_len, 0);
+ DCHECK_LT(child_data_.header.last_block_len, kMaxEntrySize);
+
+ // Write the bitmap.
+ int first_bit = child_offset_ >> 10;
+ int block_offset = child_offset_ & (kBlockSize - 1);
+ if (block_offset && (child_data_.header.last_block != first_bit ||
+ child_data_.header.last_block_len < block_offset)) {
+ // The first block is not completely filled; ignore it.
+ first_bit++;
+ }
+
+ int last_bit = (child_offset_ + result) >> 10;
+ block_offset = (child_offset_ + result) & (kBlockSize - 1);
+
+ // This condition will hit with the following criteria:
+ // 1. The first byte doesn't follow the last write.
+ // 2. The first byte is in the middle of a block.
+ // 3. The first byte and the last byte are in the same block.
+ if (first_bit > last_bit)
+ return;
+
+ if (block_offset && !child_map_.Get(last_bit)) {
+ // The last block is not completely filled; save it for later.
+ child_data_.header.last_block = last_bit;
+ child_data_.header.last_block_len = block_offset;
+ } else {
+ child_data_.header.last_block = -1;
+ }
+
+ child_map_.SetRange(first_bit, last_bit, true);
+}
+
+int SparseControl::PartialBlockLength(int block_index) const {
+ if (block_index == child_data_.header.last_block)
+ return child_data_.header.last_block_len;
+
+ // This may be the last stored index.
+ int entry_len = child_->GetDataSize(kSparseData);
+ if (block_index == entry_len >> 10)
+ return entry_len & (kBlockSize - 1);
+
+ // This is really empty.
+ return 0;
+}
+
+void SparseControl::InitChildData() {
+ // We know the real type of child_.
+ EntryImpl* child = static_cast<EntryImpl*>(child_);
+ child->SetEntryFlags(CHILD_ENTRY);
+
+ memset(&child_data_, 0, sizeof(child_data_));
+ child_data_.header = sparse_header_;
+
+ scoped_refptr<net::WrappedIOBuffer> buf(
+ new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
+
+ int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
+ CompletionCallback(), false);
+ if (rv != sizeof(child_data_))
+ DLOG(ERROR) << "Failed to save child data";
+ SetChildBit(true);
+}
+
+int SparseControl::DoGetAvailableRange() {
+ if (!child_)
+ return child_len_; // Move on to the next child.
+
+ // Check that there are no holes in this range.
+ int last_bit = (child_offset_ + child_len_ + 1023) >> 10;
+ int start = child_offset_ >> 10;
+ int partial_start_bytes = PartialBlockLength(start);
+ int found = start;
+ int bits_found = child_map_.FindBits(&found, last_bit, true);
+
+ // We don't care if there is a partial block in the middle of the range.
+ int block_offset = child_offset_ & (kBlockSize - 1);
+ if (!bits_found && partial_start_bytes <= block_offset)
+ return child_len_;
+
+ // We are done. Just break the loop and reset result_ to our real result.
+ range_found_ = true;
+
+ // found now points to the first 1. Lets see if we have zeros before it.
+ int empty_start = std::max((found << 10) - child_offset_, 0);
+
+ int bytes_found = bits_found << 10;
+ bytes_found += PartialBlockLength(found + bits_found);
+
+ if (start == found)
+ bytes_found -= block_offset;
+
+ // If the user is searching past the end of this child, bits_found is the
+ // right result; otherwise, we have some empty space at the start of this
+ // query that we have to subtract from the range that we searched.
+ result_ = std::min(bytes_found, child_len_ - empty_start);
+
+ if (!bits_found) {
+ result_ = std::min(partial_start_bytes - block_offset, child_len_);
+ empty_start = 0;
+ }
+
+ // Only update offset_ when this query found zeros at the start.
+ if (empty_start)
+ offset_ += empty_start;
+
+ // This will actually break the loop.
+ buf_len_ = 0;
+ return 0;
+}
+
+void SparseControl::DoUserCallback() {
+ DCHECK(!user_callback_.is_null());
+ CompletionCallback cb = user_callback_;
+ user_callback_.Reset();
+ user_buf_ = NULL;
+ pending_ = false;
+ operation_ = kNoOperation;
+ int rv = result_;
+ entry_->Release(); // Don't touch object after this line.
+ cb.Run(rv);
+}
+
+void SparseControl::DoAbortCallbacks() {
+ for (size_t i = 0; i < abort_callbacks_.size(); i++) {
+ // Releasing all references to entry_ may result in the destruction of this
+ // object so we should not be touching it after the last Release().
+ CompletionCallback cb = abort_callbacks_[i];
+ if (i == abort_callbacks_.size() - 1)
+ abort_callbacks_.clear();
+
+ entry_->Release(); // Don't touch object after this line.
+ cb.Run(net::OK);
+ }
+}
+
+void SparseControl::OnChildIOCompleted(int result) {
+ DCHECK_NE(net::ERR_IO_PENDING, result);
+ DoChildIOCompleted(result);
+
+ if (abort_) {
+ // We'll return the current result of the operation, which may be less than
+ // the bytes to read or write, but the user cancelled the operation.
+ abort_ = false;
+ if (entry_->net_log().IsLoggingAllEvents()) {
+ entry_->net_log().AddEvent(net::NetLog::TYPE_CANCELLED);
+ entry_->net_log().EndEvent(GetSparseEventType(operation_));
+ }
+ // We have an indirect reference to this object for every callback so if
+ // there is only one callback, we may delete this object before reaching
+ // DoAbortCallbacks.
+ bool has_abort_callbacks = !abort_callbacks_.empty();
+ DoUserCallback();
+ if (has_abort_callbacks)
+ DoAbortCallbacks();
+ return;
+ }
+
+ // We are running a callback from the message loop. It's time to restart what
+ // we were doing before.
+ DoChildrenIO();
+}
+
+} // namespace disk_cache
diff --git a/chromium/net/disk_cache/v3/sparse_control_v3.h b/chromium/net/disk_cache/v3/sparse_control_v3.h
new file mode 100644
index 00000000000..8455ad724b5
--- /dev/null
+++ b/chromium/net/disk_cache/v3/sparse_control_v3.h
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#ifndef NET_DISK_CACHE_SPARSE_CONTROL_H_
+#define NET_DISK_CACHE_SPARSE_CONTROL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/completion_callback.h"
+#include "net/disk_cache/bitmap.h"
+#include "net/disk_cache/disk_format.h"
+
+namespace net {
+class IOBuffer;
+class DrainableIOBuffer;
+}
+
+namespace disk_cache {
+
+class Entry;
+class EntryImpl;
+
+// This class provides support for the sparse capabilities of the disk cache.
+// Basically, sparse IO is directed from EntryImpl to this class, and we split
+// the operation into multiple small pieces, sending each one to the
+// appropriate entry. An instance of this class is asociated with each entry
+// used directly for sparse operations (the entry passed in to the constructor).
+class SparseControl {
+ public:
+ typedef net::CompletionCallback CompletionCallback;
+
+ // The operation to perform.
+ enum SparseOperation {
+ kNoOperation,
+ kReadOperation,
+ kWriteOperation,
+ kGetRangeOperation
+ };
+
+ explicit SparseControl(EntryImpl* entry);
+ ~SparseControl();
+
+ // Performs a quick test to see if the entry is sparse or not, without
+ // generating disk IO (so the answer provided is only a best effort).
+ bool CouldBeSparse() const;
+
+ // Performs an actual sparse read or write operation for this entry. |op| is
+ // the operation to perform, |offset| is the desired sparse offset, |buf| and
+ // |buf_len| specify the actual data to use and |callback| is the callback
+ // to use for asynchronous operations. See the description of the Read /
+ // WriteSparseData for details about the arguments. The return value is the
+ // number of bytes read or written, or a net error code.
+ int StartIO(SparseOperation op, int64 offset, net::IOBuffer* buf,
+ int buf_len, const CompletionCallback& callback);
+
+ // Implements Entry::GetAvailableRange().
+ int GetAvailableRange(int64 offset, int len, int64* start);
+
+ // Cancels the current sparse operation (if any).
+ void CancelIO();
+
+ // Returns OK if the entry can be used for new IO or ERR_IO_PENDING if we are
+ // busy. If the entry is busy, we'll invoke the callback when we are ready
+ // again. See disk_cache::Entry::ReadyToUse() for more info.
+ int ReadyToUse(const CompletionCallback& completion_callback);
+
+ // Deletes the children entries of |entry|.
+ static void DeleteChildren(EntryImpl* entry);
+
+ private:
+ // Initializes the object for the current entry. If this entry already stores
+ // sparse data, or can be used to do it, it updates the relevant information
+ // on disk and returns net::OK. Otherwise it returns a net error code.
+ int Init();
+
+ // Creates a new sparse entry or opens an aready created entry from disk.
+ // These methods just read / write the required info from disk for the current
+ // entry, and verify that everything is correct. The return value is a net
+ // error code.
+ int CreateSparseEntry();
+ int OpenSparseEntry(int data_len);
+
+ // Opens and closes a child entry. A child entry is a regular EntryImpl object
+ // with a key derived from the key of the resource to store and the range
+ // stored by that child.
+ bool OpenChild();
+ void CloseChild();
+
+ // Continues the current operation (open) without a current child.
+ bool ContinueWithoutChild(const std::string& key);
+
+ // Writes to disk the tracking information for this entry.
+ void WriteSparseData();
+
+ // Performs a single operation with the current child. Returns true when we
+ // should move on to the next child and false when we should interrupt our
+ // work.
+ bool DoChildIO();
+
+ // Performs the required work after a single IO operations finishes.
+ void DoChildIOCompleted(int result);
+
+ std::string GenerateChildKey();
+
+ // Deletes the current child and continues the current operation (open).
+ bool KillChildAndContinue(const std::string& key, bool fatal);
+
+ // Returns true if the required child is tracked by the parent entry, i.e. it
+ // was already created.
+ bool ChildPresent();
+
+ // Sets the bit for the current child to the provided |value|. In other words,
+ // starts or stops tracking this child.
+ void SetChildBit(bool value);
+
+ // Verify that the range to be accessed for the current child is appropriate.
+ // Returns false if an error is detected or there is no need to perform the
+ // current IO operation (for instance if the required range is not stored by
+ // the child).
+ bool VerifyRange();
+
+ // Updates the contents bitmap for the current range, based on the result of
+ // the current operation.
+ void UpdateRange(int result);
+
+ // Returns the number of bytes stored at |block_index|, if its allocation-bit
+ // is off (because it is not completely filled).
+ int PartialBlockLength(int block_index) const;
+
+ // Initializes the sparse info for the current child.
+ void InitChildData();
+
+ // Performs the required work for GetAvailableRange for one child.
+ int DoGetAvailableRange();
+
+ // Reports to the user that we are done.
+ void DoUserCallback();
+ void DoAbortCallbacks();
+
+ // Invoked by the callback of asynchronous operations.
+ void OnChildIOCompleted(int result);
+
+ EntryImpl* entry_; // The sparse entry.
+ EntryImpl* child_; // The current child entry.
+ SparseOperation operation_;
+ bool pending_; // True if any child IO operation returned pending.
+ bool finished_;
+ bool init_;
+ bool range_found_; // True if GetAvailableRange found something.
+ bool abort_; // True if we should abort the current operation ASAP.
+
+ SparseHeader sparse_header_; // Data about the children of entry_.
+ Bitmap children_map_; // The actual bitmap of children.
+ SparseData child_data_; // Parent and allocation map of child_.
+ Bitmap child_map_; // The allocation map as a bitmap.
+
+ CompletionCallback user_callback_;
+ std::vector<CompletionCallback> abort_callbacks_;
+ int64 offset_; // Current sparse offset.
+ scoped_refptr<net::DrainableIOBuffer> user_buf_;
+ int buf_len_; // Bytes to read or write.
+ int child_offset_; // Offset to use for the current child.
+ int child_len_; // Bytes to read or write for this child.
+ int result_;
+
+ DISALLOW_COPY_AND_ASSIGN(SparseControl);
+};
+
+} // namespace disk_cache
+
+#endif // NET_DISK_CACHE_SPARSE_CONTROL_H_
diff --git a/chromium/net/dns/address_sorter.h b/chromium/net/dns/address_sorter.h
new file mode 100644
index 00000000000..6ac943086ef
--- /dev/null
+++ b/chromium/net/dns/address_sorter.h
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_ADDRESS_SORTER_H_
+#define NET_DNS_ADDRESS_SORTER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class AddressList;
+
+// Sorts AddressList according to RFC3484, by likelihood of successful
+// connection. Depending on the platform, the sort could be performed
+// asynchronously by the OS, or synchronously by local implementation.
+// AddressSorter does not necessarily preserve port numbers on the sorted list.
+class NET_EXPORT AddressSorter {
+ public:
+ typedef base::Callback<void(bool success,
+ const AddressList& list)> CallbackType;
+
+ virtual ~AddressSorter() {}
+
+ // Sorts |list|, which must include at least one IPv6 address.
+ // Calls |callback| upon completion. Could complete synchronously. Could
+ // complete after this AddressSorter is destroyed.
+ virtual void Sort(const AddressList& list,
+ const CallbackType& callback) const = 0;
+
+ // Creates platform-dependent AddressSorter.
+ static scoped_ptr<AddressSorter> CreateAddressSorter();
+
+ protected:
+ AddressSorter() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AddressSorter);
+};
+
+} // namespace net
+
+#endif // NET_DNS_ADDRESS_SORTER_H_
diff --git a/chromium/net/dns/address_sorter_posix.cc b/chromium/net/dns/address_sorter_posix.cc
new file mode 100644
index 00000000000..8d87774587d
--- /dev/null
+++ b/chromium/net/dns/address_sorter_posix.cc
@@ -0,0 +1,426 @@
+// Copyright (c) 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.
+
+#include "net/dns/address_sorter_posix.h"
+
+#include <netinet/in.h>
+
+#if defined(OS_MACOSX) || defined(OS_BSD)
+#include <sys/socket.h> // Must be included before ifaddrs.h.
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <netinet/in_var.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#endif
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/posix/eintr_wrapper.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/udp/datagram_client_socket.h"
+
+#if defined(OS_LINUX)
+#include "net/base/address_tracker_linux.h"
+#endif
+
+namespace net {
+
+namespace {
+
+// Address sorting is performed according to RFC3484 with revisions.
+// http://tools.ietf.org/html/draft-ietf-6man-rfc3484bis-06
+// Precedence and label are separate to support override through /etc/gai.conf.
+
+// Returns true if |p1| should precede |p2| in the table.
+// Sorts table by decreasing prefix size to allow longest prefix matching.
+bool ComparePolicy(const AddressSorterPosix::PolicyEntry& p1,
+ const AddressSorterPosix::PolicyEntry& p2) {
+ return p1.prefix_length > p2.prefix_length;
+}
+
+// Creates sorted PolicyTable from |table| with |size| entries.
+AddressSorterPosix::PolicyTable LoadPolicy(
+ AddressSorterPosix::PolicyEntry* table,
+ size_t size) {
+ AddressSorterPosix::PolicyTable result(table, table + size);
+ std::sort(result.begin(), result.end(), ComparePolicy);
+ return result;
+}
+
+// Search |table| for matching prefix of |address|. |table| must be sorted by
+// descending prefix (prefix of another prefix must be later in table).
+unsigned GetPolicyValue(const AddressSorterPosix::PolicyTable& table,
+ const IPAddressNumber& address) {
+ if (address.size() == kIPv4AddressSize)
+ return GetPolicyValue(table, ConvertIPv4NumberToIPv6Number(address));
+ for (unsigned i = 0; i < table.size(); ++i) {
+ const AddressSorterPosix::PolicyEntry& entry = table[i];
+ IPAddressNumber prefix(entry.prefix, entry.prefix + kIPv6AddressSize);
+ if (IPNumberMatchesPrefix(address, prefix, entry.prefix_length))
+ return entry.value;
+ }
+ NOTREACHED();
+ // The last entry is the least restrictive, so assume it's default.
+ return table.back().value;
+}
+
+bool IsIPv6Multicast(const IPAddressNumber& address) {
+ DCHECK_EQ(kIPv6AddressSize, address.size());
+ return address[0] == 0xFF;
+}
+
+AddressSorterPosix::AddressScope GetIPv6MulticastScope(
+ const IPAddressNumber& address) {
+ DCHECK_EQ(kIPv6AddressSize, address.size());
+ return static_cast<AddressSorterPosix::AddressScope>(address[1] & 0x0F);
+}
+
+bool IsIPv6Loopback(const IPAddressNumber& address) {
+ DCHECK_EQ(kIPv6AddressSize, address.size());
+ // IN6_IS_ADDR_LOOPBACK
+ unsigned char kLoopback[kIPv6AddressSize] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ };
+ return address == IPAddressNumber(kLoopback, kLoopback + kIPv6AddressSize);
+}
+
+bool IsIPv6LinkLocal(const IPAddressNumber& address) {
+ DCHECK_EQ(kIPv6AddressSize, address.size());
+ // IN6_IS_ADDR_LINKLOCAL
+ return (address[0] == 0xFE) && ((address[1] & 0xC0) == 0x80);
+}
+
+bool IsIPv6SiteLocal(const IPAddressNumber& address) {
+ DCHECK_EQ(kIPv6AddressSize, address.size());
+ // IN6_IS_ADDR_SITELOCAL
+ return (address[0] == 0xFE) && ((address[1] & 0xC0) == 0xC0);
+}
+
+AddressSorterPosix::AddressScope GetScope(
+ const AddressSorterPosix::PolicyTable& ipv4_scope_table,
+ const IPAddressNumber& address) {
+ if (address.size() == kIPv6AddressSize) {
+ if (IsIPv6Multicast(address)) {
+ return GetIPv6MulticastScope(address);
+ } else if (IsIPv6Loopback(address) || IsIPv6LinkLocal(address)) {
+ return AddressSorterPosix::SCOPE_LINKLOCAL;
+ } else if (IsIPv6SiteLocal(address)) {
+ return AddressSorterPosix::SCOPE_SITELOCAL;
+ } else {
+ return AddressSorterPosix::SCOPE_GLOBAL;
+ }
+ } else if (address.size() == kIPv4AddressSize) {
+ return static_cast<AddressSorterPosix::AddressScope>(
+ GetPolicyValue(ipv4_scope_table, address));
+ } else {
+ NOTREACHED();
+ return AddressSorterPosix::SCOPE_NODELOCAL;
+ }
+}
+
+// Default policy table. RFC 3484, Section 2.1.
+AddressSorterPosix::PolicyEntry kDefaultPrecedenceTable[] = {
+ // ::1/128 -- loopback
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, 128, 50 },
+ // ::/0 -- any
+ { { }, 0, 40 },
+ // ::ffff:0:0/96 -- IPv4 mapped
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, 96, 35 },
+ // 2002::/16 -- 6to4
+ { { 0x20, 0x02, }, 16, 30 },
+ // 2001::/32 -- Teredo
+ { { 0x20, 0x01, 0, 0 }, 32, 5 },
+ // fc00::/7 -- unique local address
+ { { 0xFC }, 7, 3 },
+ // ::/96 -- IPv4 compatible
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 96, 1 },
+ // fec0::/10 -- site-local expanded scope
+ { { 0xFE, 0xC0 }, 10, 1 },
+ // 3ffe::/16 -- 6bone
+ { { 0x3F, 0xFE }, 16, 1 },
+};
+
+AddressSorterPosix::PolicyEntry kDefaultLabelTable[] = {
+ // ::1/128 -- loopback
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, 128, 0 },
+ // ::/0 -- any
+ { { }, 0, 1 },
+ // ::ffff:0:0/96 -- IPv4 mapped
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, 96, 4 },
+ // 2002::/16 -- 6to4
+ { { 0x20, 0x02, }, 16, 2 },
+ // 2001::/32 -- Teredo
+ { { 0x20, 0x01, 0, 0 }, 32, 5 },
+ // fc00::/7 -- unique local address
+ { { 0xFC }, 7, 13 },
+ // ::/96 -- IPv4 compatible
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 96, 3 },
+ // fec0::/10 -- site-local expanded scope
+ { { 0xFE, 0xC0 }, 10, 11 },
+ // 3ffe::/16 -- 6bone
+ { { 0x3F, 0xFE }, 16, 12 },
+};
+
+// Default mapping of IPv4 addresses to scope.
+AddressSorterPosix::PolicyEntry kDefaultIPv4ScopeTable[] = {
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0x7F }, 104,
+ AddressSorterPosix::SCOPE_LINKLOCAL },
+ { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xA9, 0xFE }, 112,
+ AddressSorterPosix::SCOPE_LINKLOCAL },
+ { { }, 0, AddressSorterPosix::SCOPE_GLOBAL },
+};
+
+// Returns number of matching initial bits between the addresses |a1| and |a2|.
+unsigned CommonPrefixLength(const IPAddressNumber& a1,
+ const IPAddressNumber& a2) {
+ DCHECK_EQ(a1.size(), a2.size());
+ for (size_t i = 0; i < a1.size(); ++i) {
+ unsigned diff = a1[i] ^ a2[i];
+ if (!diff)
+ continue;
+ for (unsigned j = 0; j < CHAR_BIT; ++j) {
+ if (diff & (1 << (CHAR_BIT - 1)))
+ return i * CHAR_BIT + j;
+ diff <<= 1;
+ }
+ NOTREACHED();
+ }
+ return a1.size() * CHAR_BIT;
+}
+
+// Computes the number of leading 1-bits in |mask|.
+unsigned MaskPrefixLength(const IPAddressNumber& mask) {
+ IPAddressNumber all_ones(mask.size(), 0xFF);
+ return CommonPrefixLength(mask, all_ones);
+}
+
+struct DestinationInfo {
+ IPAddressNumber address;
+ AddressSorterPosix::AddressScope scope;
+ unsigned precedence;
+ unsigned label;
+ const AddressSorterPosix::SourceAddressInfo* src;
+ unsigned common_prefix_length;
+};
+
+// Returns true iff |dst_a| should precede |dst_b| in the address list.
+// RFC 3484, section 6.
+bool CompareDestinations(const DestinationInfo* dst_a,
+ const DestinationInfo* dst_b) {
+ // Rule 1: Avoid unusable destinations.
+ // Unusable destinations are already filtered out.
+ DCHECK(dst_a->src);
+ DCHECK(dst_b->src);
+
+ // Rule 2: Prefer matching scope.
+ bool scope_match1 = (dst_a->src->scope == dst_a->scope);
+ bool scope_match2 = (dst_b->src->scope == dst_b->scope);
+ if (scope_match1 != scope_match2)
+ return scope_match1;
+
+ // Rule 3: Avoid deprecated addresses.
+ if (dst_a->src->deprecated != dst_b->src->deprecated)
+ return !dst_a->src->deprecated;
+
+ // Rule 4: Prefer home addresses.
+ if (dst_a->src->home != dst_b->src->home)
+ return dst_a->src->home;
+
+ // Rule 5: Prefer matching label.
+ bool label_match1 = (dst_a->src->label == dst_a->label);
+ bool label_match2 = (dst_b->src->label == dst_b->label);
+ if (label_match1 != label_match2)
+ return label_match1;
+
+ // Rule 6: Prefer higher precedence.
+ if (dst_a->precedence != dst_b->precedence)
+ return dst_a->precedence > dst_b->precedence;
+
+ // Rule 7: Prefer native transport.
+ if (dst_a->src->native != dst_b->src->native)
+ return dst_a->src->native;
+
+ // Rule 8: Prefer smaller scope.
+ if (dst_a->scope != dst_b->scope)
+ return dst_a->scope < dst_b->scope;
+
+ // Rule 9: Use longest matching prefix. Only for matching address families.
+ if (dst_a->address.size() == dst_b->address.size()) {
+ if (dst_a->common_prefix_length != dst_b->common_prefix_length)
+ return dst_a->common_prefix_length > dst_b->common_prefix_length;
+ }
+
+ // Rule 10: Leave the order unchanged.
+ // stable_sort takes care of that.
+ return false;
+}
+
+} // namespace
+
+AddressSorterPosix::AddressSorterPosix(ClientSocketFactory* socket_factory)
+ : socket_factory_(socket_factory),
+ precedence_table_(LoadPolicy(kDefaultPrecedenceTable,
+ arraysize(kDefaultPrecedenceTable))),
+ label_table_(LoadPolicy(kDefaultLabelTable,
+ arraysize(kDefaultLabelTable))),
+ ipv4_scope_table_(LoadPolicy(kDefaultIPv4ScopeTable,
+ arraysize(kDefaultIPv4ScopeTable))) {
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ OnIPAddressChanged();
+}
+
+AddressSorterPosix::~AddressSorterPosix() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+}
+
+void AddressSorterPosix::Sort(const AddressList& list,
+ const CallbackType& callback) const {
+ DCHECK(CalledOnValidThread());
+ ScopedVector<DestinationInfo> sort_list;
+
+ for (size_t i = 0; i < list.size(); ++i) {
+ scoped_ptr<DestinationInfo> info(new DestinationInfo());
+ info->address = list[i].address();
+ info->scope = GetScope(ipv4_scope_table_, info->address);
+ info->precedence = GetPolicyValue(precedence_table_, info->address);
+ info->label = GetPolicyValue(label_table_, info->address);
+
+ // Each socket can only be bound once.
+ scoped_ptr<DatagramClientSocket> socket(
+ socket_factory_->CreateDatagramClientSocket(
+ DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL /* NetLog */,
+ NetLog::Source()));
+
+ // Even though no packets are sent, cannot use port 0 in Connect.
+ IPEndPoint dest(info->address, 80 /* port */);
+ int rv = socket->Connect(dest);
+ if (rv != OK) {
+ LOG(WARNING) << "Could not connect to " << dest.ToStringWithoutPort()
+ << " reason " << rv;
+ continue;
+ }
+ // Filter out unusable destinations.
+ IPEndPoint src;
+ rv = socket->GetLocalAddress(&src);
+ if (rv != OK) {
+ LOG(WARNING) << "Could not get local address for "
+ << src.ToStringWithoutPort() << " reason " << rv;
+ continue;
+ }
+
+ SourceAddressInfo& src_info = source_map_[src.address()];
+ if (src_info.scope == SCOPE_UNDEFINED) {
+ // If |source_info_| is out of date, |src| might be missing, but we still
+ // want to sort, even though the HostCache will be cleared soon.
+ FillPolicy(src.address(), &src_info);
+ }
+ info->src = &src_info;
+
+ if (info->address.size() == src.address().size()) {
+ info->common_prefix_length = std::min(
+ CommonPrefixLength(info->address, src.address()),
+ info->src->prefix_length);
+ }
+ sort_list.push_back(info.release());
+ }
+
+ std::stable_sort(sort_list.begin(), sort_list.end(), CompareDestinations);
+
+ AddressList result;
+ for (size_t i = 0; i < sort_list.size(); ++i)
+ result.push_back(IPEndPoint(sort_list[i]->address, 0 /* port */));
+
+ callback.Run(true, result);
+}
+
+void AddressSorterPosix::OnIPAddressChanged() {
+ DCHECK(CalledOnValidThread());
+ source_map_.clear();
+#if defined(OS_LINUX)
+ const internal::AddressTrackerLinux* tracker =
+ NetworkChangeNotifier::GetAddressTracker();
+ if (!tracker)
+ return;
+ typedef internal::AddressTrackerLinux::AddressMap AddressMap;
+ AddressMap map = tracker->GetAddressMap();
+ for (AddressMap::const_iterator it = map.begin(); it != map.end(); ++it) {
+ const IPAddressNumber& address = it->first;
+ const struct ifaddrmsg& msg = it->second;
+ SourceAddressInfo& info = source_map_[address];
+ info.native = false; // TODO(szym): obtain this via netlink.
+ info.deprecated = msg.ifa_flags & IFA_F_DEPRECATED;
+ info.home = msg.ifa_flags & IFA_F_HOMEADDRESS;
+ info.prefix_length = msg.ifa_prefixlen;
+ FillPolicy(address, &info);
+ }
+#elif defined(OS_MACOSX) || defined(OS_BSD)
+ // It's not clear we will receive notification when deprecated flag changes.
+ // Socket for ioctl.
+ int ioctl_socket = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (ioctl_socket < 0)
+ return;
+
+ struct ifaddrs* addrs;
+ int rv = getifaddrs(&addrs);
+ if (rv < 0) {
+ LOG(WARNING) << "getifaddrs failed " << rv;
+ close(ioctl_socket);
+ return;
+ }
+
+ for (struct ifaddrs* ifa = addrs; ifa != NULL; ifa = ifa->ifa_next) {
+ IPEndPoint src;
+ if (!src.FromSockAddr(ifa->ifa_addr, ifa->ifa_addr->sa_len))
+ continue;
+ SourceAddressInfo& info = source_map_[src.address()];
+ // Note: no known way to fill in |native| and |home|.
+ info.native = info.home = info.deprecated = false;
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ struct in6_ifreq ifr = {};
+ strncpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name) - 1);
+ DCHECK_LE(ifa->ifa_addr->sa_len, sizeof(ifr.ifr_ifru.ifru_addr));
+ memcpy(&ifr.ifr_ifru.ifru_addr, ifa->ifa_addr, ifa->ifa_addr->sa_len);
+ int rv = ioctl(ioctl_socket, SIOCGIFAFLAG_IN6, &ifr);
+ if (rv >= 0) {
+ info.deprecated = ifr.ifr_ifru.ifru_flags & IN6_IFF_DEPRECATED;
+ } else {
+ LOG(WARNING) << "SIOCGIFAFLAG_IN6 failed " << rv;
+ }
+ }
+ if (ifa->ifa_netmask) {
+ IPEndPoint netmask;
+ if (netmask.FromSockAddr(ifa->ifa_netmask, ifa->ifa_addr->sa_len)) {
+ info.prefix_length = MaskPrefixLength(netmask.address());
+ } else {
+ LOG(WARNING) << "FromSockAddr failed on netmask";
+ }
+ }
+ FillPolicy(src.address(), &info);
+ }
+ freeifaddrs(addrs);
+ close(ioctl_socket);
+#endif
+}
+
+void AddressSorterPosix::FillPolicy(const IPAddressNumber& address,
+ SourceAddressInfo* info) const {
+ DCHECK(CalledOnValidThread());
+ info->scope = GetScope(ipv4_scope_table_, address);
+ info->label = GetPolicyValue(label_table_, address);
+}
+
+// static
+scoped_ptr<AddressSorter> AddressSorter::CreateAddressSorter() {
+ return scoped_ptr<AddressSorter>(
+ new AddressSorterPosix(ClientSocketFactory::GetDefaultFactory()));
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/address_sorter_posix.h b/chromium/net/dns/address_sorter_posix.h
new file mode 100644
index 00000000000..1c88ad2f978
--- /dev/null
+++ b/chromium/net/dns/address_sorter_posix.h
@@ -0,0 +1,94 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_ADDRESS_SORTER_POSIX_H_
+#define NET_DNS_ADDRESS_SORTER_POSIX_H_
+
+#include <map>
+#include <vector>
+
+#include "base/threading/non_thread_safe.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/dns/address_sorter.h"
+
+namespace net {
+
+class ClientSocketFactory;
+
+// This implementation uses explicit policy to perform the sorting. It is not
+// thread-safe and always completes synchronously.
+class NET_EXPORT_PRIVATE AddressSorterPosix
+ : public AddressSorter,
+ public base::NonThreadSafe,
+ public NetworkChangeNotifier::IPAddressObserver {
+ public:
+ // Generic policy entry.
+ struct PolicyEntry {
+ // IPv4 addresses must be mapped to IPv6.
+ unsigned char prefix[kIPv6AddressSize];
+ unsigned prefix_length;
+ unsigned value;
+ };
+
+ typedef std::vector<PolicyEntry> PolicyTable;
+
+ enum AddressScope {
+ SCOPE_UNDEFINED = 0,
+ SCOPE_NODELOCAL = 1,
+ SCOPE_LINKLOCAL = 2,
+ SCOPE_SITELOCAL = 5,
+ SCOPE_ORGLOCAL = 8,
+ SCOPE_GLOBAL = 14,
+ };
+
+ struct SourceAddressInfo {
+ // Values read from policy tables.
+ AddressScope scope;
+ unsigned label;
+
+ // Values from the OS, matter only if more than one source address is used.
+ unsigned prefix_length;
+ bool deprecated; // vs. preferred RFC4862
+ bool home; // vs. care-of RFC6275
+ bool native;
+ };
+
+ typedef std::map<IPAddressNumber, SourceAddressInfo> SourceAddressMap;
+
+ explicit AddressSorterPosix(ClientSocketFactory* socket_factory);
+ virtual ~AddressSorterPosix();
+
+ // AddressSorter:
+ virtual void Sort(const AddressList& list,
+ const CallbackType& callback) const OVERRIDE;
+
+ private:
+ friend class AddressSorterPosixTest;
+
+ // NetworkChangeNotifier::IPAddressObserver:
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // Fills |info| with values for |address| from policy tables.
+ void FillPolicy(const IPAddressNumber& address,
+ SourceAddressInfo* info) const;
+
+ // Mutable to allow using default values for source addresses which were not
+ // found in most recent OnIPAddressChanged.
+ mutable SourceAddressMap source_map_;
+
+ ClientSocketFactory* socket_factory_;
+ PolicyTable precedence_table_;
+ PolicyTable label_table_;
+ PolicyTable ipv4_scope_table_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressSorterPosix);
+};
+
+} // namespace net
+
+#endif // NET_DNS_ADDRESS_SORTER_POSIX_H_
diff --git a/chromium/net/dns/address_sorter_posix_unittest.cc b/chromium/net/dns/address_sorter_posix_unittest.cc
new file mode 100644
index 00000000000..c4517379957
--- /dev/null
+++ b/chromium/net/dns/address_sorter_posix_unittest.cc
@@ -0,0 +1,327 @@
+// Copyright (c) 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.
+
+#include "net/dns/address_sorter_posix.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/udp/datagram_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+// Used to map destination address to source address.
+typedef std::map<IPAddressNumber, IPAddressNumber> AddressMapping;
+
+IPAddressNumber ParseIP(const std::string& str) {
+ IPAddressNumber addr;
+ CHECK(ParseIPLiteralToNumber(str, &addr));
+ return addr;
+}
+
+// A mock socket which binds to source address according to AddressMapping.
+class TestUDPClientSocket : public DatagramClientSocket {
+ public:
+ explicit TestUDPClientSocket(const AddressMapping* mapping)
+ : mapping_(mapping), connected_(false) {}
+
+ virtual ~TestUDPClientSocket() {}
+
+ virtual int Read(IOBuffer*, int, const CompletionCallback&) OVERRIDE {
+ NOTIMPLEMENTED();
+ return OK;
+ }
+ virtual int Write(IOBuffer*, int, const CompletionCallback&) OVERRIDE {
+ NOTIMPLEMENTED();
+ return OK;
+ }
+ virtual bool SetReceiveBufferSize(int32) OVERRIDE {
+ return true;
+ }
+ virtual bool SetSendBufferSize(int32) OVERRIDE {
+ return true;
+ }
+
+ virtual void Close() OVERRIDE {}
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return OK;
+ }
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+ *address = local_endpoint_;
+ return OK;
+ }
+
+ virtual int Connect(const IPEndPoint& remote) OVERRIDE {
+ if (connected_)
+ return ERR_UNEXPECTED;
+ AddressMapping::const_iterator it = mapping_->find(remote.address());
+ if (it == mapping_->end())
+ return ERR_FAILED;
+ connected_ = true;
+ local_endpoint_ = IPEndPoint(it->second, 39874 /* arbitrary port */);
+ return OK;
+ }
+
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ private:
+ BoundNetLog net_log_;
+ const AddressMapping* mapping_;
+ bool connected_;
+ IPEndPoint local_endpoint_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUDPClientSocket);
+};
+
+// Creates TestUDPClientSockets and maintains an AddressMapping.
+class TestSocketFactory : public ClientSocketFactory {
+ public:
+ TestSocketFactory() {}
+ virtual ~TestSocketFactory() {}
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType,
+ const RandIntCallback&,
+ NetLog*,
+ const NetLog::Source&) OVERRIDE {
+ return scoped_ptr<DatagramClientSocket>(new TestUDPClientSocket(&mapping_));
+ }
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList&,
+ NetLog*,
+ const NetLog::Source&) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<StreamSocket>();
+ }
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle>,
+ const HostPortPair&,
+ const SSLConfig&,
+ const SSLClientSocketContext&) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLClientSocket>();
+ }
+ virtual void ClearSSLSessionCache() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ void AddMapping(const IPAddressNumber& dst, const IPAddressNumber& src) {
+ mapping_[dst] = src;
+ }
+
+ private:
+ AddressMapping mapping_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSocketFactory);
+};
+
+void OnSortComplete(AddressList* result_buf,
+ const CompletionCallback& callback,
+ bool success,
+ const AddressList& result) {
+ EXPECT_TRUE(success);
+ if (success)
+ *result_buf = result;
+ callback.Run(OK);
+}
+
+} // namespace
+
+class AddressSorterPosixTest : public testing::Test {
+ protected:
+ AddressSorterPosixTest() : sorter_(&socket_factory_) {}
+
+ void AddMapping(const std::string& dst, const std::string& src) {
+ socket_factory_.AddMapping(ParseIP(dst), ParseIP(src));
+ }
+
+ AddressSorterPosix::SourceAddressInfo* GetSourceInfo(
+ const std::string& addr) {
+ IPAddressNumber address = ParseIP(addr);
+ AddressSorterPosix::SourceAddressInfo* info = &sorter_.source_map_[address];
+ if (info->scope == AddressSorterPosix::SCOPE_UNDEFINED)
+ sorter_.FillPolicy(address, info);
+ return info;
+ }
+
+ // Verify that NULL-terminated |addresses| matches (-1)-terminated |order|
+ // after sorting.
+ void Verify(const char* addresses[], const int order[]) {
+ AddressList list;
+ for (const char** addr = addresses; *addr != NULL; ++addr)
+ list.push_back(IPEndPoint(ParseIP(*addr), 80));
+ for (size_t i = 0; order[i] >= 0; ++i)
+ CHECK_LT(order[i], static_cast<int>(list.size()));
+
+ AddressList result;
+ TestCompletionCallback callback;
+ sorter_.Sort(list, base::Bind(&OnSortComplete, &result,
+ callback.callback()));
+ callback.WaitForResult();
+
+ for (size_t i = 0; (i < result.size()) || (order[i] >= 0); ++i) {
+ IPEndPoint expected = order[i] >= 0 ? list[order[i]] : IPEndPoint();
+ IPEndPoint actual = i < result.size() ? result[i] : IPEndPoint();
+ EXPECT_TRUE(expected.address() == actual.address()) <<
+ "Address out of order at position " << i << "\n" <<
+ " Actual: " << actual.ToStringWithoutPort() << "\n" <<
+ "Expected: " << expected.ToStringWithoutPort();
+ }
+ }
+
+ TestSocketFactory socket_factory_;
+ AddressSorterPosix sorter_;
+};
+
+// Rule 1: Avoid unusable destinations.
+TEST_F(AddressSorterPosixTest, Rule1) {
+ AddMapping("10.0.0.231", "10.0.0.1");
+ const char* addresses[] = { "::1", "10.0.0.231", "127.0.0.1", NULL };
+ const int order[] = { 1, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 2: Prefer matching scope.
+TEST_F(AddressSorterPosixTest, Rule2) {
+ AddMapping("3002::1", "4000::10"); // matching global
+ AddMapping("ff32::1", "fe81::10"); // matching link-local
+ AddMapping("fec1::1", "fec1::10"); // matching node-local
+ AddMapping("3002::2", "::1"); // global vs. link-local
+ AddMapping("fec1::2", "fe81::10"); // site-local vs. link-local
+ AddMapping("8.0.0.1", "169.254.0.10"); // global vs. link-local
+ // In all three cases, matching scope is preferred.
+ const int order[] = { 1, 0, -1 };
+ const char* addresses1[] = { "3002::2", "3002::1", NULL };
+ Verify(addresses1, order);
+ const char* addresses2[] = { "fec1::2", "ff32::1", NULL };
+ Verify(addresses2, order);
+ const char* addresses3[] = { "8.0.0.1", "fec1::1", NULL };
+ Verify(addresses3, order);
+}
+
+// Rule 3: Avoid deprecated addresses.
+TEST_F(AddressSorterPosixTest, Rule3) {
+ // Matching scope.
+ AddMapping("3002::1", "4000::10");
+ GetSourceInfo("4000::10")->deprecated = true;
+ AddMapping("3002::2", "4000::20");
+ const char* addresses[] = { "3002::1", "3002::2", NULL };
+ const int order[] = { 1, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 4: Prefer home addresses.
+TEST_F(AddressSorterPosixTest, Rule4) {
+ AddMapping("3002::1", "4000::10");
+ AddMapping("3002::2", "4000::20");
+ GetSourceInfo("4000::20")->home = true;
+ const char* addresses[] = { "3002::1", "3002::2", NULL };
+ const int order[] = { 1, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 5: Prefer matching label.
+TEST_F(AddressSorterPosixTest, Rule5) {
+ AddMapping("::1", "::1"); // matching loopback
+ AddMapping("::ffff:1234:1", "::ffff:1234:10"); // matching IPv4-mapped
+ AddMapping("2001::1", "::ffff:1234:10"); // Teredo vs. IPv4-mapped
+ AddMapping("2002::1", "2001::10"); // 6to4 vs. Teredo
+ const int order[] = { 1, 0, -1 };
+ {
+ const char* addresses[] = { "2001::1", "::1", NULL };
+ Verify(addresses, order);
+ }
+ {
+ const char* addresses[] = { "2002::1", "::ffff:1234:1", NULL };
+ Verify(addresses, order);
+ }
+}
+
+// Rule 6: Prefer higher precedence.
+TEST_F(AddressSorterPosixTest, Rule6) {
+ AddMapping("::1", "::1"); // loopback
+ AddMapping("ff32::1", "fe81::10"); // multicast
+ AddMapping("::ffff:1234:1", "::ffff:1234:10"); // IPv4-mapped
+ AddMapping("2001::1", "2001::10"); // Teredo
+ const char* addresses[] = { "2001::1", "::ffff:1234:1", "ff32::1", "::1",
+ NULL };
+ const int order[] = { 3, 2, 1, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 7: Prefer native transport.
+TEST_F(AddressSorterPosixTest, Rule7) {
+ AddMapping("3002::1", "4000::10");
+ AddMapping("3002::2", "4000::20");
+ GetSourceInfo("4000::20")->native = true;
+ const char* addresses[] = { "3002::1", "3002::2", NULL };
+ const int order[] = { 1, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 8: Prefer smaller scope.
+TEST_F(AddressSorterPosixTest, Rule8) {
+ // Matching scope. Should precede the others by Rule 2.
+ AddMapping("fe81::1", "fe81::10"); // link-local
+ AddMapping("3000::1", "4000::10"); // global
+ // Mismatched scope.
+ AddMapping("ff32::1", "4000::10"); // link-local
+ AddMapping("ff35::1", "4000::10"); // site-local
+ AddMapping("ff38::1", "4000::10"); // org-local
+ const char* addresses[] = { "ff38::1", "3000::1", "ff35::1", "ff32::1",
+ "fe81::1", NULL };
+ const int order[] = { 4, 1, 3, 2, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 9: Use longest matching prefix.
+TEST_F(AddressSorterPosixTest, Rule9) {
+ AddMapping("3000::1", "3000:ffff::10"); // 16 bit match
+ GetSourceInfo("3000:ffff::10")->prefix_length = 16;
+ AddMapping("4000::1", "4000::10"); // 123 bit match, limited to 15
+ GetSourceInfo("4000::10")->prefix_length = 15;
+ AddMapping("4002::1", "4000::10"); // 14 bit match
+ AddMapping("4080::1", "4000::10"); // 8 bit match
+ const char* addresses[] = { "4080::1", "4002::1", "4000::1", "3000::1",
+ NULL };
+ const int order[] = { 3, 2, 1, 0, -1 };
+ Verify(addresses, order);
+}
+
+// Rule 10: Leave the order unchanged.
+TEST_F(AddressSorterPosixTest, Rule10) {
+ AddMapping("4000::1", "4000::10");
+ AddMapping("4000::2", "4000::10");
+ AddMapping("4000::3", "4000::10");
+ const char* addresses[] = { "4000::1", "4000::2", "4000::3", NULL };
+ const int order[] = { 0, 1, 2, -1 };
+ Verify(addresses, order);
+}
+
+TEST_F(AddressSorterPosixTest, MultipleRules) {
+ AddMapping("::1", "::1"); // loopback
+ AddMapping("ff32::1", "fe81::10"); // link-local multicast
+ AddMapping("ff3e::1", "4000::10"); // global multicast
+ AddMapping("4000::1", "4000::10"); // global unicast
+ AddMapping("ff32::2", "fe81::20"); // deprecated link-local multicast
+ GetSourceInfo("fe81::20")->deprecated = true;
+ const char* addresses[] = { "ff3e::1", "ff32::2", "4000::1", "ff32::1", "::1",
+ "8.0.0.1", NULL };
+ const int order[] = { 4, 3, 0, 2, 1, -1 };
+ Verify(addresses, order);
+}
+
+} // namespace net
diff --git a/chromium/net/dns/address_sorter_unittest.cc b/chromium/net/dns/address_sorter_unittest.cc
new file mode 100644
index 00000000000..0c2be884d29
--- /dev/null
+++ b/chromium/net/dns/address_sorter_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#include "net/dns/address_sorter.h"
+
+#if defined(OS_WIN)
+#include <winsock2.h>
+#endif
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "net/base/address_list.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
+
+namespace net {
+namespace {
+
+IPEndPoint MakeEndPoint(const std::string& str) {
+ IPAddressNumber addr;
+ CHECK(ParseIPLiteralToNumber(str, &addr));
+ return IPEndPoint(addr, 0);
+}
+
+void OnSortComplete(AddressList* result_buf,
+ const CompletionCallback& callback,
+ bool success,
+ const AddressList& result) {
+ if (success)
+ *result_buf = result;
+ callback.Run(success ? OK : ERR_FAILED);
+}
+
+TEST(AddressSorterTest, Sort) {
+ int expected_result = OK;
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+ SOCKET sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock == INVALID_SOCKET) {
+ expected_result = ERR_FAILED;
+ } else {
+ closesocket(sock);
+ }
+#endif
+ scoped_ptr<AddressSorter> sorter(AddressSorter::CreateAddressSorter());
+ AddressList list;
+ list.push_back(MakeEndPoint("10.0.0.1"));
+ list.push_back(MakeEndPoint("8.8.8.8"));
+ list.push_back(MakeEndPoint("::1"));
+ list.push_back(MakeEndPoint("2001:4860:4860::8888"));
+
+ AddressList result;
+ TestCompletionCallback callback;
+ sorter->Sort(list, base::Bind(&OnSortComplete, &result,
+ callback.callback()));
+ EXPECT_EQ(expected_result, callback.WaitForResult());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/dns/address_sorter_win.cc b/chromium/net/dns/address_sorter_win.cc
new file mode 100644
index 00000000000..3e1afa734cc
--- /dev/null
+++ b/chromium/net/dns/address_sorter_win.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 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.
+
+#include "net/dns/address_sorter.h"
+
+#include <winsock2.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/threading/worker_pool.h"
+#include "base/win/windows_version.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/winsock_init.h"
+
+namespace net {
+
+namespace {
+
+class AddressSorterWin : public AddressSorter {
+ public:
+ AddressSorterWin() {
+ EnsureWinsockInit();
+ }
+
+ virtual ~AddressSorterWin() {}
+
+ // AddressSorter:
+ virtual void Sort(const AddressList& list,
+ const CallbackType& callback) const OVERRIDE {
+ DCHECK(!list.empty());
+ scoped_refptr<Job> job = new Job(list, callback);
+ }
+
+ private:
+ // Executes the SIO_ADDRESS_LIST_SORT ioctl on the WorkerPool, and
+ // performs the necessary conversions to/from AddressList.
+ class Job : public base::RefCountedThreadSafe<Job> {
+ public:
+ Job(const AddressList& list, const CallbackType& callback)
+ : callback_(callback),
+ buffer_size_(sizeof(SOCKET_ADDRESS_LIST) +
+ list.size() * (sizeof(SOCKET_ADDRESS) +
+ sizeof(SOCKADDR_STORAGE))),
+ input_buffer_(reinterpret_cast<SOCKET_ADDRESS_LIST*>(
+ malloc(buffer_size_))),
+ output_buffer_(reinterpret_cast<SOCKET_ADDRESS_LIST*>(
+ malloc(buffer_size_))),
+ success_(false) {
+ input_buffer_->iAddressCount = list.size();
+ SOCKADDR_STORAGE* storage = reinterpret_cast<SOCKADDR_STORAGE*>(
+ input_buffer_->Address + input_buffer_->iAddressCount);
+
+ for (size_t i = 0; i < list.size(); ++i) {
+ IPEndPoint ipe = list[i];
+ // Addresses must be sockaddr_in6.
+ if (ipe.GetFamily() == ADDRESS_FAMILY_IPV4) {
+ ipe = IPEndPoint(ConvertIPv4NumberToIPv6Number(ipe.address()),
+ ipe.port());
+ }
+
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(storage + i);
+ socklen_t addr_len = sizeof(SOCKADDR_STORAGE);
+ bool result = ipe.ToSockAddr(addr, &addr_len);
+ DCHECK(result);
+ input_buffer_->Address[i].lpSockaddr = addr;
+ input_buffer_->Address[i].iSockaddrLength = addr_len;
+ }
+
+ if (!base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&Job::Run, this),
+ base::Bind(&Job::OnComplete, this),
+ false /* task is slow */)) {
+ LOG(ERROR) << "WorkerPool::PostTaskAndReply failed";
+ OnComplete();
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Job>;
+ ~Job() {}
+
+ // Executed on the WorkerPool.
+ void Run() {
+ SOCKET sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock == INVALID_SOCKET)
+ return;
+ DWORD result_size = 0;
+ int result = WSAIoctl(sock, SIO_ADDRESS_LIST_SORT, input_buffer_.get(),
+ buffer_size_, output_buffer_.get(), buffer_size_,
+ &result_size, NULL, NULL);
+ if (result == SOCKET_ERROR) {
+ LOG(ERROR) << "SIO_ADDRESS_LIST_SORT failed " << WSAGetLastError();
+ } else {
+ success_ = true;
+ }
+ closesocket(sock);
+ }
+
+ // Executed on the calling thread.
+ void OnComplete() {
+ AddressList list;
+ if (success_) {
+ list.reserve(output_buffer_->iAddressCount);
+ for (int i = 0; i < output_buffer_->iAddressCount; ++i) {
+ IPEndPoint ipe;
+ ipe.FromSockAddr(output_buffer_->Address[i].lpSockaddr,
+ output_buffer_->Address[i].iSockaddrLength);
+ // Unmap V4MAPPED IPv6 addresses so that Happy Eyeballs works.
+ if (IsIPv4Mapped(ipe.address())) {
+ ipe = IPEndPoint(ConvertIPv4MappedToIPv4(ipe.address()),
+ ipe.port());
+ }
+ list.push_back(ipe);
+ }
+ }
+ callback_.Run(success_, list);
+ }
+
+ const CallbackType callback_;
+ const size_t buffer_size_;
+ scoped_ptr_malloc<SOCKET_ADDRESS_LIST> input_buffer_;
+ scoped_ptr_malloc<SOCKET_ADDRESS_LIST> output_buffer_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(Job);
+ };
+
+ DISALLOW_COPY_AND_ASSIGN(AddressSorterWin);
+};
+
+// Merges |list_ipv4| and |list_ipv6| before passing it to |callback|, but
+// only if |success| is true.
+void MergeResults(const AddressSorter::CallbackType& callback,
+ const AddressList& list_ipv4,
+ bool success,
+ const AddressList& list_ipv6) {
+ if (!success) {
+ callback.Run(false, AddressList());
+ return;
+ }
+ AddressList list;
+ list.insert(list.end(), list_ipv6.begin(), list_ipv6.end());
+ list.insert(list.end(), list_ipv4.begin(), list_ipv4.end());
+ callback.Run(true, list);
+}
+
+// Wrapper for AddressSorterWin which does not sort IPv4 or IPv4-mapped
+// addresses but always puts them at the end of the list. Needed because the
+// SIO_ADDRESS_LIST_SORT does not support IPv4 addresses on Windows XP.
+class AddressSorterWinXP : public AddressSorter {
+ public:
+ AddressSorterWinXP() {}
+ virtual ~AddressSorterWinXP() {}
+
+ // AddressSorter:
+ virtual void Sort(const AddressList& list,
+ const CallbackType& callback) const OVERRIDE {
+ AddressList list_ipv4;
+ AddressList list_ipv6;
+ for (size_t i = 0; i < list.size(); ++i) {
+ const IPEndPoint& ipe = list[i];
+ if (ipe.GetFamily() == ADDRESS_FAMILY_IPV4) {
+ list_ipv4.push_back(ipe);
+ } else {
+ list_ipv6.push_back(ipe);
+ }
+ }
+ if (!list_ipv6.empty()) {
+ sorter_.Sort(list_ipv6, base::Bind(&MergeResults, callback, list_ipv4));
+ } else {
+ NOTREACHED() << "Should not be called with IPv4-only addresses.";
+ callback.Run(true, list);
+ }
+ }
+
+ private:
+ AddressSorterWin sorter_;
+
+ DISALLOW_COPY_AND_ASSIGN(AddressSorterWinXP);
+};
+
+} // namespace
+
+// static
+scoped_ptr<AddressSorter> AddressSorter::CreateAddressSorter() {
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return scoped_ptr<AddressSorter>(new AddressSorterWinXP());
+ return scoped_ptr<AddressSorter>(new AddressSorterWin());
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_client.cc b/chromium/net/dns/dns_client.cc
new file mode 100644
index 00000000000..976f1533905
--- /dev/null
+++ b/chromium/net/dns/dns_client.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_client.h"
+
+#include "base/bind.h"
+#include "base/rand_util.h"
+#include "net/base/net_log.h"
+#include "net/dns/address_sorter.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_session.h"
+#include "net/dns/dns_socket_pool.h"
+#include "net/dns/dns_transaction.h"
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+namespace {
+
+class DnsClientImpl : public DnsClient {
+ public:
+ explicit DnsClientImpl(NetLog* net_log)
+ : address_sorter_(AddressSorter::CreateAddressSorter()),
+ net_log_(net_log) {}
+
+ virtual void SetConfig(const DnsConfig& config) OVERRIDE {
+ factory_.reset();
+ session_ = NULL;
+ if (config.IsValid()) {
+ ClientSocketFactory* factory = ClientSocketFactory::GetDefaultFactory();
+ scoped_ptr<DnsSocketPool> socket_pool(
+ config.randomize_ports ? DnsSocketPool::CreateDefault(factory)
+ : DnsSocketPool::CreateNull(factory));
+ session_ = new DnsSession(config,
+ socket_pool.Pass(),
+ base::Bind(&base::RandInt),
+ net_log_);
+ factory_ = DnsTransactionFactory::CreateFactory(session_.get());
+ }
+ }
+
+ virtual const DnsConfig* GetConfig() const OVERRIDE {
+ return session_.get() ? &session_->config() : NULL;
+ }
+
+ virtual DnsTransactionFactory* GetTransactionFactory() OVERRIDE {
+ return session_.get() ? factory_.get() : NULL;
+ }
+
+ virtual AddressSorter* GetAddressSorter() OVERRIDE {
+ return address_sorter_.get();
+ }
+
+ private:
+ scoped_refptr<DnsSession> session_;
+ scoped_ptr<DnsTransactionFactory> factory_;
+ scoped_ptr<AddressSorter> address_sorter_;
+
+ NetLog* net_log_;
+};
+
+} // namespace
+
+// static
+scoped_ptr<DnsClient> DnsClient::CreateClient(NetLog* net_log) {
+ return scoped_ptr<DnsClient>(new DnsClientImpl(net_log));
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_client.h b/chromium/net/dns/dns_client.h
new file mode 100644
index 00000000000..650c7d0d416
--- /dev/null
+++ b/chromium/net/dns/dns_client.h
@@ -0,0 +1,44 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_CLIENT_H_
+#define NET_DNS_DNS_CLIENT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class AddressSorter;
+struct DnsConfig;
+class DnsTransactionFactory;
+class NetLog;
+
+// Convenience wrapper which allows easy injection of DnsTransaction into
+// HostResolverImpl. Pointers returned by the Get* methods are only guaranteed
+// to remain valid until next time SetConfig is called.
+class NET_EXPORT DnsClient {
+ public:
+ virtual ~DnsClient() {}
+
+ // Creates a new DnsTransactionFactory according to the new |config|.
+ virtual void SetConfig(const DnsConfig& config) = 0;
+
+ // Returns NULL if the current config is not valid.
+ virtual const DnsConfig* GetConfig() const = 0;
+
+ // Returns NULL if the current config is not valid.
+ virtual DnsTransactionFactory* GetTransactionFactory() = 0;
+
+ // Returns NULL if the current config is not valid.
+ virtual AddressSorter* GetAddressSorter() = 0;
+
+ // Creates default client.
+ static scoped_ptr<DnsClient> CreateClient(NetLog* net_log);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_CLIENT_H_
+
diff --git a/chromium/net/dns/dns_config_service.cc b/chromium/net/dns/dns_config_service.cc
new file mode 100644
index 00000000000..ea8a3421cd2
--- /dev/null
+++ b/chromium/net/dns/dns_config_service.cc
@@ -0,0 +1,226 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_config_service.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+
+// Default values are taken from glibc resolv.h except timeout which is set to
+// |kDnsTimeoutSeconds|.
+DnsConfig::DnsConfig()
+ : append_to_multi_label_name(true),
+ randomize_ports(false),
+ ndots(1),
+ timeout(base::TimeDelta::FromSeconds(kDnsTimeoutSeconds)),
+ attempts(2),
+ rotate(false),
+ edns0(false) {}
+
+DnsConfig::~DnsConfig() {}
+
+bool DnsConfig::Equals(const DnsConfig& d) const {
+ return EqualsIgnoreHosts(d) && (hosts == d.hosts);
+}
+
+bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const {
+ return (nameservers == d.nameservers) &&
+ (search == d.search) &&
+ (append_to_multi_label_name == d.append_to_multi_label_name) &&
+ (ndots == d.ndots) &&
+ (timeout == d.timeout) &&
+ (attempts == d.attempts) &&
+ (rotate == d.rotate) &&
+ (edns0 == d.edns0);
+}
+
+void DnsConfig::CopyIgnoreHosts(const DnsConfig& d) {
+ nameservers = d.nameservers;
+ search = d.search;
+ append_to_multi_label_name = d.append_to_multi_label_name;
+ ndots = d.ndots;
+ timeout = d.timeout;
+ attempts = d.attempts;
+ rotate = d.rotate;
+ edns0 = d.edns0;
+}
+
+base::Value* DnsConfig::ToValue() const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ base::ListValue* list = new base::ListValue();
+ for (size_t i = 0; i < nameservers.size(); ++i)
+ list->Append(new base::StringValue(nameservers[i].ToString()));
+ dict->Set("nameservers", list);
+
+ list = new base::ListValue();
+ for (size_t i = 0; i < search.size(); ++i)
+ list->Append(new base::StringValue(search[i]));
+ dict->Set("search", list);
+
+ dict->SetBoolean("append_to_multi_label_name", append_to_multi_label_name);
+ dict->SetInteger("ndots", ndots);
+ dict->SetDouble("timeout", timeout.InSecondsF());
+ dict->SetInteger("attempts", attempts);
+ dict->SetBoolean("rotate", rotate);
+ dict->SetBoolean("edns0", edns0);
+ dict->SetInteger("num_hosts", hosts.size());
+
+ return dict;
+}
+
+
+DnsConfigService::DnsConfigService()
+ : watch_failed_(false),
+ have_config_(false),
+ have_hosts_(false),
+ need_update_(false),
+ last_sent_empty_(true) {}
+
+DnsConfigService::~DnsConfigService() {
+}
+
+void DnsConfigService::ReadConfig(const CallbackType& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ ReadNow();
+}
+
+void DnsConfigService::WatchConfig(const CallbackType& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ watch_failed_ = !StartWatching();
+ ReadNow();
+}
+
+void DnsConfigService::InvalidateConfig() {
+ DCHECK(CalledOnValidThread());
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (!last_invalidate_config_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("AsyncDNS.ConfigNotifyInterval",
+ now - last_invalidate_config_time_);
+ }
+ last_invalidate_config_time_ = now;
+ if (!have_config_)
+ return;
+ have_config_ = false;
+ StartTimer();
+}
+
+void DnsConfigService::InvalidateHosts() {
+ DCHECK(CalledOnValidThread());
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (!last_invalidate_hosts_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("AsyncDNS.HostsNotifyInterval",
+ now - last_invalidate_hosts_time_);
+ }
+ last_invalidate_hosts_time_ = now;
+ if (!have_hosts_)
+ return;
+ have_hosts_ = false;
+ StartTimer();
+}
+
+void DnsConfigService::OnConfigRead(const DnsConfig& config) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(config.IsValid());
+
+ bool changed = false;
+ if (!config.EqualsIgnoreHosts(dns_config_)) {
+ dns_config_.CopyIgnoreHosts(config);
+ need_update_ = true;
+ changed = true;
+ }
+ if (!changed && !last_sent_empty_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("AsyncDNS.UnchangedConfigInterval",
+ base::TimeTicks::Now() - last_sent_empty_time_);
+ }
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigChange", changed);
+
+ have_config_ = true;
+ if (have_hosts_ || watch_failed_)
+ OnCompleteConfig();
+}
+
+void DnsConfigService::OnHostsRead(const DnsHosts& hosts) {
+ DCHECK(CalledOnValidThread());
+
+ bool changed = false;
+ if (hosts != dns_config_.hosts) {
+ dns_config_.hosts = hosts;
+ need_update_ = true;
+ changed = true;
+ }
+ if (!changed && !last_sent_empty_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("AsyncDNS.UnchangedHostsInterval",
+ base::TimeTicks::Now() - last_sent_empty_time_);
+ }
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostsChange", changed);
+
+ have_hosts_ = true;
+ if (have_config_ || watch_failed_)
+ OnCompleteConfig();
+}
+
+void DnsConfigService::StartTimer() {
+ DCHECK(CalledOnValidThread());
+ if (last_sent_empty_) {
+ DCHECK(!timer_.IsRunning());
+ return; // No need to withdraw again.
+ }
+ timer_.Stop();
+
+ // Give it a short timeout to come up with a valid config. Otherwise withdraw
+ // the config from the receiver. The goal is to avoid perceivable network
+ // outage (when using the wrong config) but at the same time avoid
+ // unnecessary Job aborts in HostResolverImpl. The signals come from multiple
+ // sources so it might receive multiple events during a config change.
+
+ // DHCP and user-induced changes are on the order of seconds, so 150ms should
+ // not add perceivable delay. On the other hand, config readers should finish
+ // within 150ms with the rare exception of I/O block or extra large HOSTS.
+ const base::TimeDelta kTimeout = base::TimeDelta::FromMilliseconds(150);
+
+ timer_.Start(FROM_HERE,
+ kTimeout,
+ this,
+ &DnsConfigService::OnTimeout);
+}
+
+void DnsConfigService::OnTimeout() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!last_sent_empty_);
+ // Indicate that even if there is no change in On*Read, we will need to
+ // update the receiver when the config becomes complete.
+ need_update_ = true;
+ // Empty config is considered invalid.
+ last_sent_empty_ = true;
+ last_sent_empty_time_ = base::TimeTicks::Now();
+ callback_.Run(DnsConfig());
+}
+
+void DnsConfigService::OnCompleteConfig() {
+ timer_.Stop();
+ if (!need_update_)
+ return;
+ need_update_ = false;
+ last_sent_empty_ = false;
+ if (watch_failed_) {
+ // If a watch failed, the config may not be accurate, so report empty.
+ callback_.Run(DnsConfig());
+ } else {
+ callback_.Run(dns_config_);
+ }
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_config_service.h b/chromium/net/dns/dns_config_service.h
new file mode 100644
index 00000000000..4babb9e7d37
--- /dev/null
+++ b/chromium/net/dns/dns_config_service.h
@@ -0,0 +1,174 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_CONFIG_SERVICE_H_
+#define NET_DNS_DNS_CONFIG_SERVICE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+// Needed on shared build with MSVS2010 to avoid multiple definitions of
+// std::vector<IPEndPoint>.
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h" // win requires size of IPEndPoint
+#include "net/base/net_export.h"
+#include "net/dns/dns_hosts.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+// Always use 1 second timeout (followed by binary exponential backoff).
+// TODO(szym): Remove code which reads timeout from system.
+const unsigned kDnsTimeoutSeconds = 1;
+
+// DnsConfig stores configuration of the system resolver.
+struct NET_EXPORT_PRIVATE DnsConfig {
+ DnsConfig();
+ virtual ~DnsConfig();
+
+ bool Equals(const DnsConfig& d) const;
+
+ bool EqualsIgnoreHosts(const DnsConfig& d) const;
+
+ void CopyIgnoreHosts(const DnsConfig& src);
+
+ // Returns a Value representation of |this|. Caller takes ownership of the
+ // returned Value. For performance reasons, the Value only contains the
+ // number of hosts rather than the full list.
+ base::Value* ToValue() const;
+
+ bool IsValid() const {
+ return !nameservers.empty();
+ }
+
+ // List of name server addresses.
+ std::vector<IPEndPoint> nameservers;
+ // Suffix search list; used on first lookup when number of dots in given name
+ // is less than |ndots|.
+ std::vector<std::string> search;
+
+ DnsHosts hosts;
+
+ // AppendToMultiLabelName: is suffix search performed for multi-label names?
+ // True, except on Windows where it can be configured.
+ bool append_to_multi_label_name;
+
+ // Indicates that source port randomization is required. This uses additional
+ // resources on some platforms.
+ bool randomize_ports;
+
+ // Resolver options; see man resolv.conf.
+
+ // Minimum number of dots before global resolution precedes |search|.
+ int ndots;
+ // Time between retransmissions, see res_state.retrans.
+ base::TimeDelta timeout;
+ // Maximum number of attempts, see res_state.retry.
+ int attempts;
+ // Round robin entries in |nameservers| for subsequent requests.
+ bool rotate;
+ // Enable EDNS0 extensions.
+ bool edns0;
+};
+
+
+// Service for reading system DNS settings, on demand or when signalled by
+// internal watchers and NetworkChangeNotifier.
+class NET_EXPORT_PRIVATE DnsConfigService
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Callback interface for the client, called on the same thread as
+ // ReadConfig() and WatchConfig().
+ typedef base::Callback<void(const DnsConfig& config)> CallbackType;
+
+ // Creates the platform-specific DnsConfigService.
+ static scoped_ptr<DnsConfigService> CreateSystemService();
+
+ DnsConfigService();
+ virtual ~DnsConfigService();
+
+ // Attempts to read the configuration. Will run |callback| when succeeded.
+ // Can be called at most once.
+ void ReadConfig(const CallbackType& callback);
+
+ // Registers systems watchers. Will attempt to read config after watch starts,
+ // but only if watchers started successfully. Will run |callback| iff config
+ // changes from last call or has to be withdrawn. Can be called at most once.
+ // Might require MessageLoopForIO.
+ void WatchConfig(const CallbackType& callback);
+
+ protected:
+ enum WatchStatus {
+ DNS_CONFIG_WATCH_STARTED = 0,
+ DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
+ DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
+ DNS_CONFIG_WATCH_FAILED_CONFIG,
+ DNS_CONFIG_WATCH_FAILED_HOSTS,
+ DNS_CONFIG_WATCH_MAX,
+ };
+
+ // Immediately attempts to read the current configuration.
+ virtual void ReadNow() = 0;
+ // Registers system watchers. Returns true iff succeeds.
+ virtual bool StartWatching() = 0;
+
+ // Called when the current config (except hosts) has changed.
+ void InvalidateConfig();
+ // Called when the current hosts have changed.
+ void InvalidateHosts();
+
+ // Called with new config. |config|.hosts is ignored.
+ void OnConfigRead(const DnsConfig& config);
+ // Called with new hosts. Rest of the config is assumed unchanged.
+ void OnHostsRead(const DnsHosts& hosts);
+
+ void set_watch_failed(bool value) { watch_failed_ = value; }
+
+ private:
+ // The timer counts from the last Invalidate* until complete config is read.
+ void StartTimer();
+ void OnTimeout();
+ // Called when the config becomes complete. Stops the timer.
+ void OnCompleteConfig();
+
+ CallbackType callback_;
+
+ DnsConfig dns_config_;
+
+ // True if any of the necessary watchers failed. In that case, the service
+ // will communicate changes via OnTimeout, but will only send empty DnsConfig.
+ bool watch_failed_;
+ // True after On*Read, before Invalidate*. Tells if the config is complete.
+ bool have_config_;
+ bool have_hosts_;
+ // True if receiver needs to be updated when the config becomes complete.
+ bool need_update_;
+ // True if the last config sent was empty (instead of |dns_config_|).
+ // Set when |timer_| expires.
+ bool last_sent_empty_;
+
+ // Initialized and updated on Invalidate* call.
+ base::TimeTicks last_invalidate_config_time_;
+ base::TimeTicks last_invalidate_hosts_time_;
+ // Initialized and updated when |timer_| expires.
+ base::TimeTicks last_sent_empty_time_;
+
+ // Started in Invalidate*, cleared in On*Read.
+ base::OneShotTimer<DnsConfigService> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsConfigService);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_CONFIG_SERVICE_H_
diff --git a/chromium/net/dns/dns_config_service_posix.cc b/chromium/net/dns/dns_config_service_posix.cc
new file mode 100644
index 00000000000..ff2295e704a
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_posix.cc
@@ -0,0 +1,404 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_config_service_posix.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_hosts.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/notify_watcher_mac.h"
+#include "net/dns/serial_worker.h"
+
+namespace net {
+
+#if !defined(OS_ANDROID)
+namespace internal {
+
+namespace {
+
+const base::FilePath::CharType* kFilePathHosts =
+ FILE_PATH_LITERAL("/etc/hosts");
+
+#if defined(OS_MACOSX)
+// From 10.7.3 configd-395.10/dnsinfo/dnsinfo.h
+static const char* kDnsNotifyKey =
+ "com.apple.system.SystemConfiguration.dns_configuration";
+
+class ConfigWatcher {
+ public:
+ bool Watch(const base::Callback<void(bool succeeded)>& callback) {
+ return watcher_.Watch(kDnsNotifyKey, callback);
+ }
+
+ private:
+ NotifyWatcherMac watcher_;
+};
+#else
+
+#ifndef _PATH_RESCONF // Normally defined in <resolv.h>
+#define _PATH_RESCONF "/etc/resolv.conf"
+#endif
+
+static const base::FilePath::CharType* kFilePathConfig =
+ FILE_PATH_LITERAL(_PATH_RESCONF);
+
+class ConfigWatcher {
+ public:
+ typedef base::Callback<void(bool succeeded)> CallbackType;
+
+ bool Watch(const CallbackType& callback) {
+ callback_ = callback;
+ return watcher_.Watch(base::FilePath(kFilePathConfig), false,
+ base::Bind(&ConfigWatcher::OnCallback,
+ base::Unretained(this)));
+ }
+
+ private:
+ void OnCallback(const base::FilePath& path, bool error) {
+ callback_.Run(!error);
+ }
+
+ base::FilePathWatcher watcher_;
+ CallbackType callback_;
+};
+#endif
+
+ConfigParsePosixResult ReadDnsConfig(DnsConfig* config) {
+ ConfigParsePosixResult result;
+#if defined(OS_OPENBSD)
+ // Note: res_ninit in glibc always returns 0 and sets RES_INIT.
+ // res_init behaves the same way.
+ memset(&_res, 0, sizeof(_res));
+ if (res_init() == 0) {
+ result = ConvertResStateToDnsConfig(_res, config);
+ } else {
+ result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
+ }
+#else // all other OS_POSIX
+ struct __res_state res;
+ memset(&res, 0, sizeof(res));
+ if (res_ninit(&res) == 0) {
+ result = ConvertResStateToDnsConfig(res, config);
+ } else {
+ result = CONFIG_PARSE_POSIX_RES_INIT_FAILED;
+ }
+ // Prefer res_ndestroy where available.
+#if defined(OS_MACOSX) || defined(OS_FREEBSD)
+ res_ndestroy(&res);
+#else
+ res_nclose(&res);
+#endif
+#endif
+ // Override timeout value to match default setting on Windows.
+ config->timeout = base::TimeDelta::FromSeconds(kDnsTimeoutSeconds);
+ return result;
+}
+
+} // namespace
+
+class DnsConfigServicePosix::Watcher {
+ public:
+ explicit Watcher(DnsConfigServicePosix* service)
+ : weak_factory_(this),
+ service_(service) {}
+ ~Watcher() {}
+
+ bool Watch() {
+ bool success = true;
+ if (!config_watcher_.Watch(base::Bind(&Watcher::OnConfigChanged,
+ base::Unretained(this)))) {
+ LOG(ERROR) << "DNS config watch failed to start.";
+ success = false;
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
+ DNS_CONFIG_WATCH_MAX);
+ }
+ if (!hosts_watcher_.Watch(base::FilePath(kFilePathHosts), false,
+ base::Bind(&Watcher::OnHostsChanged,
+ base::Unretained(this)))) {
+ LOG(ERROR) << "DNS hosts watch failed to start.";
+ success = false;
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
+ DNS_CONFIG_WATCH_MAX);
+ }
+ return success;
+ }
+
+ private:
+ void OnConfigChanged(bool succeeded) {
+ // Ignore transient flutter of resolv.conf by delaying the signal a bit.
+ const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(50);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Watcher::OnConfigChangedDelayed,
+ weak_factory_.GetWeakPtr(),
+ succeeded),
+ kDelay);
+ }
+ void OnConfigChangedDelayed(bool succeeded) {
+ service_->OnConfigChanged(succeeded);
+ }
+ void OnHostsChanged(const base::FilePath& path, bool error) {
+ service_->OnHostsChanged(!error);
+ }
+
+ base::WeakPtrFactory<Watcher> weak_factory_;
+ DnsConfigServicePosix* service_;
+ ConfigWatcher config_watcher_;
+ base::FilePathWatcher hosts_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(Watcher);
+};
+
+// A SerialWorker that uses libresolv to initialize res_state and converts
+// it to DnsConfig.
+class DnsConfigServicePosix::ConfigReader : public SerialWorker {
+ public:
+ explicit ConfigReader(DnsConfigServicePosix* service)
+ : service_(service), success_(false) {}
+
+ virtual void DoWork() OVERRIDE {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ ConfigParsePosixResult result = ReadDnsConfig(&dns_config_);
+ success_ = (result == CONFIG_PARSE_POSIX_OK);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParsePosix",
+ result, CONFIG_PARSE_POSIX_MAX);
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ virtual void OnWorkFinished() OVERRIDE {
+ DCHECK(!IsCancelled());
+ if (success_) {
+ service_->OnConfigRead(dns_config_);
+ } else {
+ LOG(WARNING) << "Failed to read DnsConfig.";
+ }
+ }
+
+ private:
+ virtual ~ConfigReader() {}
+
+ DnsConfigServicePosix* service_;
+ // Written in DoWork, read in OnWorkFinished, no locking necessary.
+ DnsConfig dns_config_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConfigReader);
+};
+
+// A SerialWorker that reads the HOSTS file and runs Callback.
+class DnsConfigServicePosix::HostsReader : public SerialWorker {
+ public:
+ explicit HostsReader(DnsConfigServicePosix* service)
+ : service_(service), path_(kFilePathHosts), success_(false) {}
+
+ private:
+ virtual ~HostsReader() {}
+
+ virtual void DoWork() OVERRIDE {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ success_ = ParseHostsFile(path_, &hosts_);
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ virtual void OnWorkFinished() OVERRIDE {
+ if (success_) {
+ service_->OnHostsRead(hosts_);
+ } else {
+ LOG(WARNING) << "Failed to read DnsHosts.";
+ }
+ }
+
+ DnsConfigServicePosix* service_;
+ const base::FilePath path_;
+ // Written in DoWork, read in OnWorkFinished, no locking necessary.
+ DnsHosts hosts_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostsReader);
+};
+
+DnsConfigServicePosix::DnsConfigServicePosix()
+ : config_reader_(new ConfigReader(this)),
+ hosts_reader_(new HostsReader(this)) {}
+
+DnsConfigServicePosix::~DnsConfigServicePosix() {
+ config_reader_->Cancel();
+ hosts_reader_->Cancel();
+}
+
+void DnsConfigServicePosix::ReadNow() {
+ config_reader_->WorkNow();
+ hosts_reader_->WorkNow();
+}
+
+bool DnsConfigServicePosix::StartWatching() {
+ // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
+ watcher_.reset(new Watcher(this));
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
+ DNS_CONFIG_WATCH_MAX);
+ return watcher_->Watch();
+}
+
+void DnsConfigServicePosix::OnConfigChanged(bool succeeded) {
+ InvalidateConfig();
+ if (succeeded) {
+ config_reader_->WorkNow();
+ } else {
+ LOG(ERROR) << "DNS config watch failed.";
+ set_watch_failed(true);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_CONFIG,
+ DNS_CONFIG_WATCH_MAX);
+ }
+}
+
+void DnsConfigServicePosix::OnHostsChanged(bool succeeded) {
+ InvalidateHosts();
+ if (succeeded) {
+ hosts_reader_->WorkNow();
+ } else {
+ LOG(ERROR) << "DNS hosts watch failed.";
+ set_watch_failed(true);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_HOSTS,
+ DNS_CONFIG_WATCH_MAX);
+ }
+}
+
+ConfigParsePosixResult ConvertResStateToDnsConfig(const struct __res_state& res,
+ DnsConfig* dns_config) {
+ CHECK(dns_config != NULL);
+ if (!(res.options & RES_INIT))
+ return CONFIG_PARSE_POSIX_RES_INIT_UNSET;
+
+ dns_config->nameservers.clear();
+
+#if defined(OS_MACOSX) || defined(OS_FREEBSD)
+ union res_sockaddr_union addresses[MAXNS];
+ int nscount = res_getservers(const_cast<res_state>(&res), addresses, MAXNS);
+ DCHECK_GE(nscount, 0);
+ DCHECK_LE(nscount, MAXNS);
+ for (int i = 0; i < nscount; ++i) {
+ IPEndPoint ipe;
+ if (!ipe.FromSockAddr(
+ reinterpret_cast<const struct sockaddr*>(&addresses[i]),
+ sizeof addresses[i])) {
+ return CONFIG_PARSE_POSIX_BAD_ADDRESS;
+ }
+ dns_config->nameservers.push_back(ipe);
+ }
+#elif defined(OS_LINUX)
+ COMPILE_ASSERT(arraysize(res.nsaddr_list) >= MAXNS &&
+ arraysize(res._u._ext.nsaddrs) >= MAXNS,
+ incompatible_libresolv_res_state);
+ DCHECK_LE(res.nscount, MAXNS);
+ // Initially, glibc stores IPv6 in |_ext.nsaddrs| and IPv4 in |nsaddr_list|.
+ // In res_send.c:res_nsend, it merges |nsaddr_list| into |nsaddrs|,
+ // but we have to combine the two arrays ourselves.
+ for (int i = 0; i < res.nscount; ++i) {
+ IPEndPoint ipe;
+ const struct sockaddr* addr = NULL;
+ size_t addr_len = 0;
+ if (res.nsaddr_list[i].sin_family) { // The indicator used by res_nsend.
+ addr = reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]);
+ addr_len = sizeof res.nsaddr_list[i];
+ } else if (res._u._ext.nsaddrs[i] != NULL) {
+ addr = reinterpret_cast<const struct sockaddr*>(res._u._ext.nsaddrs[i]);
+ addr_len = sizeof *res._u._ext.nsaddrs[i];
+ } else {
+ return CONFIG_PARSE_POSIX_BAD_EXT_STRUCT;
+ }
+ if (!ipe.FromSockAddr(addr, addr_len))
+ return CONFIG_PARSE_POSIX_BAD_ADDRESS;
+ dns_config->nameservers.push_back(ipe);
+ }
+#else // !(defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_FREEBSD))
+ DCHECK_LE(res.nscount, MAXNS);
+ for (int i = 0; i < res.nscount; ++i) {
+ IPEndPoint ipe;
+ if (!ipe.FromSockAddr(
+ reinterpret_cast<const struct sockaddr*>(&res.nsaddr_list[i]),
+ sizeof res.nsaddr_list[i])) {
+ return CONFIG_PARSE_POSIX_BAD_ADDRESS;
+ }
+ dns_config->nameservers.push_back(ipe);
+ }
+#endif
+
+ dns_config->search.clear();
+ for (int i = 0; (i < MAXDNSRCH) && res.dnsrch[i]; ++i) {
+ dns_config->search.push_back(std::string(res.dnsrch[i]));
+ }
+
+ dns_config->ndots = res.ndots;
+ dns_config->timeout = base::TimeDelta::FromSeconds(res.retrans);
+ dns_config->attempts = res.retry;
+#if defined(RES_ROTATE)
+ dns_config->rotate = res.options & RES_ROTATE;
+#endif
+ dns_config->edns0 = res.options & RES_USE_EDNS0;
+
+ // The current implementation assumes these options are set. They normally
+ // cannot be overwritten by /etc/resolv.conf
+ unsigned kRequiredOptions = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
+ if ((res.options & kRequiredOptions) != kRequiredOptions)
+ return CONFIG_PARSE_POSIX_MISSING_OPTIONS;
+
+ unsigned kUnhandledOptions = RES_USEVC | RES_IGNTC | RES_USE_DNSSEC;
+ if (res.options & kUnhandledOptions)
+ return CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS;
+
+ if (dns_config->nameservers.empty())
+ return CONFIG_PARSE_POSIX_NO_NAMESERVERS;
+
+ // If any name server is 0.0.0.0, assume the configuration is invalid.
+ // TODO(szym): Measure how often this happens. http://crbug.com/125599
+ const IPAddressNumber kEmptyAddress(kIPv4AddressSize);
+ for (unsigned i = 0; i < dns_config->nameservers.size(); ++i) {
+ if (dns_config->nameservers[i].address() == kEmptyAddress)
+ return CONFIG_PARSE_POSIX_NULL_ADDRESS;
+ }
+ return CONFIG_PARSE_POSIX_OK;
+}
+
+} // namespace internal
+
+// static
+scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
+ return scoped_ptr<DnsConfigService>(new internal::DnsConfigServicePosix());
+}
+
+#else // defined(OS_ANDROID)
+// Android NDK provides only a stub <resolv.h> header.
+class StubDnsConfigService : public DnsConfigService {
+ public:
+ StubDnsConfigService() {}
+ virtual ~StubDnsConfigService() {}
+ private:
+ virtual void ReadNow() OVERRIDE {}
+ virtual bool StartWatching() OVERRIDE { return false; }
+};
+// static
+scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
+ return scoped_ptr<DnsConfigService>(new StubDnsConfigService());
+}
+#endif
+
+} // namespace net
diff --git a/chromium/net/dns/dns_config_service_posix.h b/chromium/net/dns/dns_config_service_posix.h
new file mode 100644
index 00000000000..95a4377d932
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_posix.h
@@ -0,0 +1,67 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_
+#define NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <resolv.h>
+
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/dns/dns_config_service.h"
+
+namespace net {
+
+// Use DnsConfigService::CreateSystemService to use it outside of tests.
+namespace internal {
+
+class NET_EXPORT_PRIVATE DnsConfigServicePosix : public DnsConfigService {
+ public:
+ DnsConfigServicePosix();
+ virtual ~DnsConfigServicePosix();
+
+ protected:
+ // DnsConfigService:
+ virtual void ReadNow() OVERRIDE;
+ virtual bool StartWatching() OVERRIDE;
+
+ private:
+ class Watcher;
+ class ConfigReader;
+ class HostsReader;
+
+ void OnConfigChanged(bool succeeded);
+ void OnHostsChanged(bool succeeded);
+
+ scoped_ptr<Watcher> watcher_;
+ scoped_refptr<ConfigReader> config_reader_;
+ scoped_refptr<HostsReader> hosts_reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsConfigServicePosix);
+};
+
+enum ConfigParsePosixResult {
+ CONFIG_PARSE_POSIX_OK = 0,
+ CONFIG_PARSE_POSIX_RES_INIT_FAILED,
+ CONFIG_PARSE_POSIX_RES_INIT_UNSET,
+ CONFIG_PARSE_POSIX_BAD_ADDRESS,
+ CONFIG_PARSE_POSIX_BAD_EXT_STRUCT,
+ CONFIG_PARSE_POSIX_NULL_ADDRESS,
+ CONFIG_PARSE_POSIX_NO_NAMESERVERS,
+ CONFIG_PARSE_POSIX_MISSING_OPTIONS,
+ CONFIG_PARSE_POSIX_UNHANDLED_OPTIONS,
+ CONFIG_PARSE_POSIX_MAX // Bounding values for enumeration.
+};
+
+// Fills in |dns_config| from |res|.
+ConfigParsePosixResult NET_EXPORT_PRIVATE ConvertResStateToDnsConfig(
+ const struct __res_state& res, DnsConfig* dns_config);
+
+} // namespace internal
+
+} // namespace net
+
+#endif // NET_DNS_DNS_CONFIG_SERVICE_POSIX_H_
diff --git a/chromium/net/dns/dns_config_service_posix_unittest.cc b/chromium/net/dns/dns_config_service_posix_unittest.cc
new file mode 100644
index 00000000000..92e46ed1977
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_posix_unittest.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 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.
+
+#include <resolv.h>
+
+#include "base/sys_byteorder.h"
+#include "net/dns/dns_config_service_posix.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+// MAXNS is normally 3, but let's test 4 if possible.
+const char* kNameserversIPv4[] = {
+ "8.8.8.8",
+ "192.168.1.1",
+ "63.1.2.4",
+ "1.0.0.1",
+};
+
+#if defined(OS_LINUX)
+const char* kNameserversIPv6[] = {
+ NULL,
+ "2001:DB8:0::42",
+ NULL,
+ "::FFFF:129.144.52.38",
+};
+#endif
+
+// Fills in |res| with sane configuration.
+void InitializeResState(res_state res) {
+ memset(res, 0, sizeof(*res));
+ res->options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH |
+ RES_ROTATE;
+ res->ndots = 2;
+ res->retrans = 4;
+ res->retry = 7;
+
+ const char kDnsrch[] = "chromium.org" "\0" "example.com";
+ memcpy(res->defdname, kDnsrch, sizeof(kDnsrch));
+ res->dnsrch[0] = res->defdname;
+ res->dnsrch[1] = res->defdname + sizeof("chromium.org");
+
+ for (unsigned i = 0; i < arraysize(kNameserversIPv4) && i < MAXNS; ++i) {
+ struct sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_port = base::HostToNet16(NS_DEFAULTPORT + i);
+ inet_pton(AF_INET, kNameserversIPv4[i], &sa.sin_addr);
+ res->nsaddr_list[i] = sa;
+ ++res->nscount;
+ }
+
+#if defined(OS_LINUX)
+ // Install IPv6 addresses, replacing the corresponding IPv4 addresses.
+ unsigned nscount6 = 0;
+ for (unsigned i = 0; i < arraysize(kNameserversIPv6) && i < MAXNS; ++i) {
+ if (!kNameserversIPv6[i])
+ continue;
+ // Must use malloc to mimick res_ninit.
+ struct sockaddr_in6 *sa6;
+ sa6 = (struct sockaddr_in6 *)malloc(sizeof(*sa6));
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_port = base::HostToNet16(NS_DEFAULTPORT - i);
+ inet_pton(AF_INET6, kNameserversIPv6[i], &sa6->sin6_addr);
+ res->_u._ext.nsaddrs[i] = sa6;
+ memset(&res->nsaddr_list[i], 0, sizeof res->nsaddr_list[i]);
+ ++nscount6;
+ }
+ res->_u._ext.nscount6 = nscount6;
+#endif
+}
+
+void CloseResState(res_state res) {
+#if defined(OS_LINUX)
+ for (int i = 0; i < res->nscount; ++i) {
+ if (res->_u._ext.nsaddrs[i] != NULL)
+ free(res->_u._ext.nsaddrs[i]);
+ }
+#endif
+}
+
+void InitializeExpectedConfig(DnsConfig* config) {
+ config->ndots = 2;
+ config->timeout = base::TimeDelta::FromSeconds(4);
+ config->attempts = 7;
+ config->rotate = true;
+ config->edns0 = false;
+ config->append_to_multi_label_name = true;
+ config->search.clear();
+ config->search.push_back("chromium.org");
+ config->search.push_back("example.com");
+
+ config->nameservers.clear();
+ for (unsigned i = 0; i < arraysize(kNameserversIPv4) && i < MAXNS; ++i) {
+ IPAddressNumber ip;
+ ParseIPLiteralToNumber(kNameserversIPv4[i], &ip);
+ config->nameservers.push_back(IPEndPoint(ip, NS_DEFAULTPORT + i));
+ }
+
+#if defined(OS_LINUX)
+ for (unsigned i = 0; i < arraysize(kNameserversIPv6) && i < MAXNS; ++i) {
+ if (!kNameserversIPv6[i])
+ continue;
+ IPAddressNumber ip;
+ ParseIPLiteralToNumber(kNameserversIPv6[i], &ip);
+ config->nameservers[i] = IPEndPoint(ip, NS_DEFAULTPORT - i);
+ }
+#endif
+}
+
+TEST(DnsConfigServicePosixTest, ConvertResStateToDnsConfig) {
+ struct __res_state res;
+ DnsConfig config;
+ EXPECT_FALSE(config.IsValid());
+ InitializeResState(&res);
+ ASSERT_EQ(internal::CONFIG_PARSE_POSIX_OK,
+ internal::ConvertResStateToDnsConfig(res, &config));
+ CloseResState(&res);
+ EXPECT_TRUE(config.IsValid());
+
+ DnsConfig expected_config;
+ EXPECT_FALSE(expected_config.EqualsIgnoreHosts(config));
+ InitializeExpectedConfig(&expected_config);
+ EXPECT_TRUE(expected_config.EqualsIgnoreHosts(config));
+}
+
+TEST(DnsConfigServicePosixTest, RejectEmptyNameserver) {
+ struct __res_state res = {};
+ res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
+ const char kDnsrch[] = "chromium.org";
+ memcpy(res.defdname, kDnsrch, sizeof(kDnsrch));
+ res.dnsrch[0] = res.defdname;
+
+ struct sockaddr_in sa = {};
+ sa.sin_family = AF_INET;
+ sa.sin_port = base::HostToNet16(NS_DEFAULTPORT);
+ sa.sin_addr.s_addr = INADDR_ANY;
+ res.nsaddr_list[0] = sa;
+ sa.sin_addr.s_addr = 0xCAFE1337;
+ res.nsaddr_list[1] = sa;
+ res.nscount = 2;
+
+ DnsConfig config;
+ EXPECT_EQ(internal::CONFIG_PARSE_POSIX_NULL_ADDRESS,
+ internal::ConvertResStateToDnsConfig(res, &config));
+
+ sa.sin_addr.s_addr = 0xDEADBEEF;
+ res.nsaddr_list[0] = sa;
+ EXPECT_EQ(internal::CONFIG_PARSE_POSIX_OK,
+ internal::ConvertResStateToDnsConfig(res, &config));
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/dns/dns_config_service_unittest.cc b/chromium/net/dns/dns_config_service_unittest.cc
new file mode 100644
index 00000000000..f42068032de
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_unittest.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_config_service.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/cancelable_callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/test_timeouts.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class DnsConfigServiceTest : public testing::Test {
+ public:
+ void OnConfigChanged(const DnsConfig& config) {
+ last_config_ = config;
+ if (quit_on_config_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ protected:
+ class TestDnsConfigService : public DnsConfigService {
+ public:
+ virtual void ReadNow() OVERRIDE {}
+ virtual bool StartWatching() OVERRIDE { return true; }
+
+ // Expose the protected methods to this test suite.
+ void InvalidateConfig() {
+ DnsConfigService::InvalidateConfig();
+ }
+
+ void InvalidateHosts() {
+ DnsConfigService::InvalidateHosts();
+ }
+
+ void OnConfigRead(const DnsConfig& config) {
+ DnsConfigService::OnConfigRead(config);
+ }
+
+ void OnHostsRead(const DnsHosts& hosts) {
+ DnsConfigService::OnHostsRead(hosts);
+ }
+
+ void set_watch_failed(bool value) {
+ DnsConfigService::set_watch_failed(value);
+ }
+ };
+
+ void WaitForConfig(base::TimeDelta timeout) {
+ base::CancelableClosure closure(base::MessageLoop::QuitClosure());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, closure.callback(), timeout);
+ quit_on_config_ = true;
+ base::MessageLoop::current()->Run();
+ quit_on_config_ = false;
+ closure.Cancel();
+ }
+
+ // Generate a config using the given seed..
+ DnsConfig MakeConfig(unsigned seed) {
+ DnsConfig config;
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("1.2.3.4", &ip));
+ config.nameservers.push_back(IPEndPoint(ip, seed & 0xFFFF));
+ EXPECT_TRUE(config.IsValid());
+ return config;
+ }
+
+ // Generate hosts using the given seed.
+ DnsHosts MakeHosts(unsigned seed) {
+ DnsHosts hosts;
+ std::string hosts_content = "127.0.0.1 localhost";
+ hosts_content.append(seed, '1');
+ ParseHosts(hosts_content, &hosts);
+ EXPECT_FALSE(hosts.empty());
+ return hosts;
+ }
+
+ virtual void SetUp() OVERRIDE {
+ quit_on_config_ = false;
+
+ service_.reset(new TestDnsConfigService());
+ service_->WatchConfig(base::Bind(&DnsConfigServiceTest::OnConfigChanged,
+ base::Unretained(this)));
+ EXPECT_FALSE(last_config_.IsValid());
+ }
+
+ DnsConfig last_config_;
+ bool quit_on_config_;
+
+ // Service under test.
+ scoped_ptr<TestDnsConfigService> service_;
+};
+
+} // namespace
+
+TEST_F(DnsConfigServiceTest, FirstConfig) {
+ DnsConfig config = MakeConfig(1);
+
+ service_->OnConfigRead(config);
+ // No hosts yet, so no config.
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ service_->OnHostsRead(config.hosts);
+ EXPECT_TRUE(last_config_.Equals(config));
+}
+
+TEST_F(DnsConfigServiceTest, Timeout) {
+ DnsConfig config = MakeConfig(1);
+ config.hosts = MakeHosts(1);
+ ASSERT_TRUE(config.IsValid());
+
+ service_->OnConfigRead(config);
+ service_->OnHostsRead(config.hosts);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config));
+
+ service_->InvalidateConfig();
+ WaitForConfig(TestTimeouts::action_timeout());
+ EXPECT_FALSE(last_config_.Equals(config));
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ service_->OnConfigRead(config);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config));
+
+ service_->InvalidateHosts();
+ WaitForConfig(TestTimeouts::action_timeout());
+ EXPECT_FALSE(last_config_.Equals(config));
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ DnsConfig bad_config = last_config_ = MakeConfig(0xBAD);
+ service_->InvalidateConfig();
+ // We don't expect an update. This should time out.
+ WaitForConfig(base::TimeDelta::FromMilliseconds(100) +
+ TestTimeouts::tiny_timeout());
+ EXPECT_TRUE(last_config_.Equals(bad_config)) << "Unexpected change";
+
+ last_config_ = DnsConfig();
+ service_->OnConfigRead(config);
+ service_->OnHostsRead(config.hosts);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config));
+}
+
+TEST_F(DnsConfigServiceTest, SameConfig) {
+ DnsConfig config = MakeConfig(1);
+ config.hosts = MakeHosts(1);
+
+ service_->OnConfigRead(config);
+ service_->OnHostsRead(config.hosts);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config));
+
+ last_config_ = DnsConfig();
+ service_->OnConfigRead(config);
+ EXPECT_TRUE(last_config_.Equals(DnsConfig())) << "Unexpected change";
+
+ service_->OnHostsRead(config.hosts);
+ EXPECT_TRUE(last_config_.Equals(DnsConfig())) << "Unexpected change";
+}
+
+TEST_F(DnsConfigServiceTest, DifferentConfig) {
+ DnsConfig config1 = MakeConfig(1);
+ DnsConfig config2 = MakeConfig(2);
+ DnsConfig config3 = MakeConfig(1);
+ config1.hosts = MakeHosts(1);
+ config2.hosts = MakeHosts(1);
+ config3.hosts = MakeHosts(2);
+ ASSERT_TRUE(config1.EqualsIgnoreHosts(config3));
+ ASSERT_FALSE(config1.Equals(config2));
+ ASSERT_FALSE(config1.Equals(config3));
+ ASSERT_FALSE(config2.Equals(config3));
+
+ service_->OnConfigRead(config1);
+ service_->OnHostsRead(config1.hosts);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config1));
+
+ // It doesn't matter for this tests, but increases coverage.
+ service_->InvalidateConfig();
+ service_->InvalidateHosts();
+
+ service_->OnConfigRead(config2);
+ EXPECT_TRUE(last_config_.Equals(config1)) << "Unexpected change";
+ service_->OnHostsRead(config2.hosts); // Not an actual change.
+ EXPECT_FALSE(last_config_.Equals(config1));
+ EXPECT_TRUE(last_config_.Equals(config2));
+
+ service_->OnConfigRead(config3);
+ EXPECT_TRUE(last_config_.EqualsIgnoreHosts(config3));
+ service_->OnHostsRead(config3.hosts);
+ EXPECT_FALSE(last_config_.Equals(config2));
+ EXPECT_TRUE(last_config_.Equals(config3));
+}
+
+TEST_F(DnsConfigServiceTest, WatchFailure) {
+ DnsConfig config1 = MakeConfig(1);
+ DnsConfig config2 = MakeConfig(2);
+ config1.hosts = MakeHosts(1);
+ config2.hosts = MakeHosts(2);
+
+ service_->OnConfigRead(config1);
+ service_->OnHostsRead(config1.hosts);
+ EXPECT_FALSE(last_config_.Equals(DnsConfig()));
+ EXPECT_TRUE(last_config_.Equals(config1));
+
+ // Simulate watch failure.
+ service_->set_watch_failed(true);
+ service_->InvalidateConfig();
+ WaitForConfig(TestTimeouts::action_timeout());
+ EXPECT_FALSE(last_config_.Equals(config1));
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ DnsConfig bad_config = last_config_ = MakeConfig(0xBAD);
+ // Actual change in config, so expect an update, but it should be empty.
+ service_->OnConfigRead(config1);
+ EXPECT_FALSE(last_config_.Equals(bad_config));
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ last_config_ = bad_config;
+ // Actual change in config, so expect an update, but it should be empty.
+ service_->InvalidateConfig();
+ service_->OnConfigRead(config2);
+ EXPECT_FALSE(last_config_.Equals(bad_config));
+ EXPECT_TRUE(last_config_.Equals(DnsConfig()));
+
+ last_config_ = bad_config;
+ // No change, so no update.
+ service_->InvalidateConfig();
+ service_->OnConfigRead(config2);
+ EXPECT_TRUE(last_config_.Equals(bad_config));
+}
+
+#if (defined(OS_POSIX) && !defined(OS_ANDROID)) || defined(OS_WIN)
+// TODO(szym): This is really an integration test and can time out if HOSTS is
+// huge. http://crbug.com/107810
+TEST_F(DnsConfigServiceTest, DISABLED_GetSystemConfig) {
+ service_.reset();
+ scoped_ptr<DnsConfigService> service(DnsConfigService::CreateSystemService());
+
+ service->ReadConfig(base::Bind(&DnsConfigServiceTest::OnConfigChanged,
+ base::Unretained(this)));
+ base::TimeDelta kTimeout = TestTimeouts::action_max_timeout();
+ WaitForConfig(kTimeout);
+ ASSERT_TRUE(last_config_.IsValid()) << "Did not receive DnsConfig in " <<
+ kTimeout.InSecondsF() << "s";
+}
+#endif // OS_POSIX || OS_WIN
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_config_service_win.cc b/chromium/net/dns/dns_config_service_win.cc
new file mode 100644
index 00000000000..6d12155d8cc
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_win.cc
@@ -0,0 +1,737 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_config_service_win.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/win/object_watcher.h"
+#include "base/win/registry.h"
+#include "base/win/windows_version.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/dns/dns_hosts.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/serial_worker.h"
+#include "url/url_canon.h"
+
+#pragma comment(lib, "iphlpapi.lib")
+
+namespace net {
+
+namespace internal {
+
+namespace {
+
+// Interval between retries to parse config. Used only until parsing succeeds.
+const int kRetryIntervalSeconds = 5;
+
+const wchar_t* const kPrimaryDnsSuffixPath =
+ L"SOFTWARE\\Policies\\Microsoft\\System\\DNSClient";
+
+enum HostsParseWinResult {
+ HOSTS_PARSE_WIN_OK = 0,
+ HOSTS_PARSE_WIN_UNREADABLE_HOSTS_FILE,
+ HOSTS_PARSE_WIN_COMPUTER_NAME_FAILED,
+ HOSTS_PARSE_WIN_IPHELPER_FAILED,
+ HOSTS_PARSE_WIN_BAD_ADDRESS,
+ HOSTS_PARSE_WIN_MAX // Bounding values for enumeration.
+};
+
+// Convenience for reading values using RegKey.
+class RegistryReader : public base::NonThreadSafe {
+ public:
+ explicit RegistryReader(const wchar_t* key) {
+ // Ignoring the result. |key_.Valid()| will catch failures.
+ key_.Open(HKEY_LOCAL_MACHINE, key, KEY_QUERY_VALUE);
+ }
+
+ bool ReadString(const wchar_t* name,
+ DnsSystemSettings::RegString* out) const {
+ DCHECK(CalledOnValidThread());
+ out->set = false;
+ if (!key_.Valid()) {
+ // Assume that if the |key_| is invalid then the key is missing.
+ return true;
+ }
+ LONG result = key_.ReadValue(name, &out->value);
+ if (result == ERROR_SUCCESS) {
+ out->set = true;
+ return true;
+ }
+ return (result == ERROR_FILE_NOT_FOUND);
+ }
+
+ bool ReadDword(const wchar_t* name,
+ DnsSystemSettings::RegDword* out) const {
+ DCHECK(CalledOnValidThread());
+ out->set = false;
+ if (!key_.Valid()) {
+ // Assume that if the |key_| is invalid then the key is missing.
+ return true;
+ }
+ LONG result = key_.ReadValueDW(name, &out->value);
+ if (result == ERROR_SUCCESS) {
+ out->set = true;
+ return true;
+ }
+ return (result == ERROR_FILE_NOT_FOUND);
+ }
+
+ private:
+ base::win::RegKey key_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistryReader);
+};
+
+// Wrapper for GetAdaptersAddresses. Returns NULL if failed.
+scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> ReadIpHelper(ULONG flags) {
+ base::ThreadRestrictions::AssertIOAllowed();
+
+ scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> out;
+ ULONG len = 15000; // As recommended by MSDN for GetAdaptersAddresses.
+ UINT rv = ERROR_BUFFER_OVERFLOW;
+ // Try up to three times.
+ for (unsigned tries = 0; (tries < 3) && (rv == ERROR_BUFFER_OVERFLOW);
+ tries++) {
+ out.reset(reinterpret_cast<PIP_ADAPTER_ADDRESSES>(malloc(len)));
+ rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, out.get(), &len);
+ }
+ if (rv != NO_ERROR)
+ out.reset();
+ return out.Pass();
+}
+
+// Converts a base::string16 domain name to ASCII, possibly using punycode.
+// Returns true if the conversion succeeds and output is not empty. In case of
+// failure, |domain| might become dirty.
+bool ParseDomainASCII(const base::string16& widestr, std::string* domain) {
+ DCHECK(domain);
+ if (widestr.empty())
+ return false;
+
+ // Check if already ASCII.
+ if (IsStringASCII(widestr)) {
+ *domain = UTF16ToASCII(widestr);
+ return true;
+ }
+
+ // Otherwise try to convert it from IDN to punycode.
+ const int kInitialBufferSize = 256;
+ url_canon::RawCanonOutputT<char16, kInitialBufferSize> punycode;
+ if (!url_canon::IDNToASCII(widestr.data(), widestr.length(), &punycode))
+ return false;
+
+ // |punycode_output| should now be ASCII; convert it to a std::string.
+ // (We could use UTF16ToASCII() instead, but that requires an extra string
+ // copy. Since ASCII is a subset of UTF8 the following is equivalent).
+ bool success = UTF16ToUTF8(punycode.data(), punycode.length(), domain);
+ DCHECK(success);
+ DCHECK(IsStringASCII(*domain));
+ return success && !domain->empty();
+}
+
+bool ReadDevolutionSetting(const RegistryReader& reader,
+ DnsSystemSettings::DevolutionSetting* setting) {
+ return reader.ReadDword(L"UseDomainNameDevolution", &setting->enabled) &&
+ reader.ReadDword(L"DomainNameDevolutionLevel", &setting->level);
+}
+
+// Reads DnsSystemSettings from IpHelper and registry.
+ConfigParseWinResult ReadSystemSettings(DnsSystemSettings* settings) {
+ settings->addresses = ReadIpHelper(GAA_FLAG_SKIP_ANYCAST |
+ GAA_FLAG_SKIP_UNICAST |
+ GAA_FLAG_SKIP_MULTICAST |
+ GAA_FLAG_SKIP_FRIENDLY_NAME);
+ if (!settings->addresses.get())
+ return CONFIG_PARSE_WIN_READ_IPHELPER;
+
+ RegistryReader tcpip_reader(kTcpipPath);
+ RegistryReader tcpip6_reader(kTcpip6Path);
+ RegistryReader dnscache_reader(kDnscachePath);
+ RegistryReader policy_reader(kPolicyPath);
+ RegistryReader primary_dns_suffix_reader(kPrimaryDnsSuffixPath);
+
+ if (!policy_reader.ReadString(L"SearchList",
+ &settings->policy_search_list)) {
+ return CONFIG_PARSE_WIN_READ_POLICY_SEARCHLIST;
+ }
+
+ if (!tcpip_reader.ReadString(L"SearchList", &settings->tcpip_search_list))
+ return CONFIG_PARSE_WIN_READ_TCPIP_SEARCHLIST;
+
+ if (!tcpip_reader.ReadString(L"Domain", &settings->tcpip_domain))
+ return CONFIG_PARSE_WIN_READ_DOMAIN;
+
+ if (!ReadDevolutionSetting(policy_reader, &settings->policy_devolution))
+ return CONFIG_PARSE_WIN_READ_POLICY_DEVOLUTION;
+
+ if (!ReadDevolutionSetting(dnscache_reader, &settings->dnscache_devolution))
+ return CONFIG_PARSE_WIN_READ_DNSCACHE_DEVOLUTION;
+
+ if (!ReadDevolutionSetting(tcpip_reader, &settings->tcpip_devolution))
+ return CONFIG_PARSE_WIN_READ_TCPIP_DEVOLUTION;
+
+ if (!policy_reader.ReadDword(L"AppendToMultiLabelName",
+ &settings->append_to_multi_label_name)) {
+ return CONFIG_PARSE_WIN_READ_APPEND_MULTILABEL;
+ }
+
+ if (!primary_dns_suffix_reader.ReadString(L"PrimaryDnsSuffix",
+ &settings->primary_dns_suffix)) {
+ return CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX;
+ }
+ return CONFIG_PARSE_WIN_OK;
+}
+
+// Default address of "localhost" and local computer name can be overridden
+// by the HOSTS file, but if it's not there, then we need to fill it in.
+HostsParseWinResult AddLocalhostEntries(DnsHosts* hosts) {
+ const unsigned char kIPv4Localhost[] = { 127, 0, 0, 1 };
+ const unsigned char kIPv6Localhost[] = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1 };
+ IPAddressNumber loopback_ipv4(kIPv4Localhost,
+ kIPv4Localhost + arraysize(kIPv4Localhost));
+ IPAddressNumber loopback_ipv6(kIPv6Localhost,
+ kIPv6Localhost + arraysize(kIPv6Localhost));
+
+ // This does not override any pre-existing entries from the HOSTS file.
+ hosts->insert(std::make_pair(DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4),
+ loopback_ipv4));
+ hosts->insert(std::make_pair(DnsHostsKey("localhost", ADDRESS_FAMILY_IPV6),
+ loopback_ipv6));
+
+ WCHAR buffer[MAX_PATH];
+ DWORD size = MAX_PATH;
+ std::string localname;
+ if (!GetComputerNameExW(ComputerNameDnsHostname, buffer, &size) ||
+ !ParseDomainASCII(buffer, &localname)) {
+ return HOSTS_PARSE_WIN_COMPUTER_NAME_FAILED;
+ }
+ StringToLowerASCII(&localname);
+
+ bool have_ipv4 =
+ hosts->count(DnsHostsKey(localname, ADDRESS_FAMILY_IPV4)) > 0;
+ bool have_ipv6 =
+ hosts->count(DnsHostsKey(localname, ADDRESS_FAMILY_IPV6)) > 0;
+
+ if (have_ipv4 && have_ipv6)
+ return HOSTS_PARSE_WIN_OK;
+
+ scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> addresses =
+ ReadIpHelper(GAA_FLAG_SKIP_ANYCAST |
+ GAA_FLAG_SKIP_DNS_SERVER |
+ GAA_FLAG_SKIP_MULTICAST |
+ GAA_FLAG_SKIP_FRIENDLY_NAME);
+ if (!addresses.get())
+ return HOSTS_PARSE_WIN_IPHELPER_FAILED;
+
+ // The order of adapters is the network binding order, so stick to the
+ // first good adapter for each family.
+ for (const IP_ADAPTER_ADDRESSES* adapter = addresses.get();
+ adapter != NULL && (!have_ipv4 || !have_ipv6);
+ adapter = adapter->Next) {
+ if (adapter->OperStatus != IfOperStatusUp)
+ continue;
+ if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
+ continue;
+
+ for (const IP_ADAPTER_UNICAST_ADDRESS* address =
+ adapter->FirstUnicastAddress;
+ address != NULL;
+ address = address->Next) {
+ IPEndPoint ipe;
+ if (!ipe.FromSockAddr(address->Address.lpSockaddr,
+ address->Address.iSockaddrLength)) {
+ return HOSTS_PARSE_WIN_BAD_ADDRESS;
+ }
+ if (!have_ipv4 && (ipe.GetFamily() == ADDRESS_FAMILY_IPV4)) {
+ have_ipv4 = true;
+ (*hosts)[DnsHostsKey(localname, ADDRESS_FAMILY_IPV4)] = ipe.address();
+ } else if (!have_ipv6 && (ipe.GetFamily() == ADDRESS_FAMILY_IPV6)) {
+ have_ipv6 = true;
+ (*hosts)[DnsHostsKey(localname, ADDRESS_FAMILY_IPV6)] = ipe.address();
+ }
+ }
+ }
+ return HOSTS_PARSE_WIN_OK;
+}
+
+// Watches a single registry key for changes.
+class RegistryWatcher : public base::win::ObjectWatcher::Delegate,
+ public base::NonThreadSafe {
+ public:
+ typedef base::Callback<void(bool succeeded)> CallbackType;
+ RegistryWatcher() {}
+
+ bool Watch(const wchar_t* key, const CallbackType& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ if (key_.Open(HKEY_LOCAL_MACHINE, key, KEY_NOTIFY) != ERROR_SUCCESS)
+ return false;
+ if (key_.StartWatching() != ERROR_SUCCESS)
+ return false;
+ if (!watcher_.StartWatching(key_.watch_event(), this))
+ return false;
+ return true;
+ }
+
+ virtual void OnObjectSignaled(HANDLE object) OVERRIDE {
+ DCHECK(CalledOnValidThread());
+ bool succeeded = (key_.StartWatching() == ERROR_SUCCESS) &&
+ watcher_.StartWatching(key_.watch_event(), this);
+ if (!succeeded && key_.Valid()) {
+ watcher_.StopWatching();
+ key_.StopWatching();
+ key_.Close();
+ }
+ if (!callback_.is_null())
+ callback_.Run(succeeded);
+ }
+
+ private:
+ CallbackType callback_;
+ base::win::RegKey key_;
+ base::win::ObjectWatcher watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistryWatcher);
+};
+
+// Returns true iff |address| is DNS address from IPv6 stateless discovery,
+// i.e., matches fec0:0:0:ffff::{1,2,3}.
+// http://tools.ietf.org/html/draft-ietf-ipngwg-dns-discovery
+bool IsStatelessDiscoveryAddress(const IPAddressNumber& address) {
+ if (address.size() != kIPv6AddressSize)
+ return false;
+ const uint8 kPrefix[] = {
+ 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ return std::equal(kPrefix, kPrefix + arraysize(kPrefix),
+ address.begin()) && (address.back() < 4);
+}
+
+} // namespace
+
+base::FilePath GetHostsPath() {
+ TCHAR buffer[MAX_PATH];
+ UINT rc = GetSystemDirectory(buffer, MAX_PATH);
+ DCHECK(0 < rc && rc < MAX_PATH);
+ return base::FilePath(buffer).Append(
+ FILE_PATH_LITERAL("drivers\\etc\\hosts"));
+}
+
+bool ParseSearchList(const base::string16& value,
+ std::vector<std::string>* output) {
+ DCHECK(output);
+ if (value.empty())
+ return false;
+
+ output->clear();
+
+ // If the list includes an empty hostname (",," or ", ,"), it is terminated.
+ // Although nslookup and network connection property tab ignore such
+ // fragments ("a,b,,c" becomes ["a", "b", "c"]), our reference is getaddrinfo
+ // (which sees ["a", "b"]). WMI queries also return a matching search list.
+ std::vector<base::string16> woutput;
+ base::SplitString(value, ',', &woutput);
+ for (size_t i = 0; i < woutput.size(); ++i) {
+ // Convert non-ASCII to punycode, although getaddrinfo does not properly
+ // handle such suffixes.
+ const base::string16& t = woutput[i];
+ std::string parsed;
+ if (!ParseDomainASCII(t, &parsed))
+ break;
+ output->push_back(parsed);
+ }
+ return !output->empty();
+}
+
+ConfigParseWinResult ConvertSettingsToDnsConfig(
+ const DnsSystemSettings& settings,
+ DnsConfig* config) {
+ *config = DnsConfig();
+
+ // Use GetAdapterAddresses to get effective DNS server order and
+ // connection-specific DNS suffix. Ignore disconnected and loopback adapters.
+ // The order of adapters is the network binding order, so stick to the
+ // first good adapter.
+ for (const IP_ADAPTER_ADDRESSES* adapter = settings.addresses.get();
+ adapter != NULL && config->nameservers.empty();
+ adapter = adapter->Next) {
+ if (adapter->OperStatus != IfOperStatusUp)
+ continue;
+ if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
+ continue;
+
+ for (const IP_ADAPTER_DNS_SERVER_ADDRESS* address =
+ adapter->FirstDnsServerAddress;
+ address != NULL;
+ address = address->Next) {
+ IPEndPoint ipe;
+ if (ipe.FromSockAddr(address->Address.lpSockaddr,
+ address->Address.iSockaddrLength)) {
+ if (IsStatelessDiscoveryAddress(ipe.address()))
+ continue;
+ // Override unset port.
+ if (!ipe.port())
+ ipe = IPEndPoint(ipe.address(), dns_protocol::kDefaultPort);
+ config->nameservers.push_back(ipe);
+ } else {
+ return CONFIG_PARSE_WIN_BAD_ADDRESS;
+ }
+ }
+
+ // IP_ADAPTER_ADDRESSES in Vista+ has a search list at |FirstDnsSuffix|,
+ // but it came up empty in all trials.
+ // |DnsSuffix| stores the effective connection-specific suffix, which is
+ // obtained via DHCP (regkey: Tcpip\Parameters\Interfaces\{XXX}\DhcpDomain)
+ // or specified by the user (regkey: Tcpip\Parameters\Domain).
+ std::string dns_suffix;
+ if (ParseDomainASCII(adapter->DnsSuffix, &dns_suffix))
+ config->search.push_back(dns_suffix);
+ }
+
+ if (config->nameservers.empty())
+ return CONFIG_PARSE_WIN_NO_NAMESERVERS; // No point continuing.
+
+ // Windows always tries a multi-label name "as is" before using suffixes.
+ config->ndots = 1;
+
+ if (!settings.append_to_multi_label_name.set) {
+ // The default setting is true for XP, false for Vista+.
+ if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
+ config->append_to_multi_label_name = false;
+ } else {
+ config->append_to_multi_label_name = true;
+ }
+ } else {
+ config->append_to_multi_label_name =
+ (settings.append_to_multi_label_name.value != 0);
+ }
+
+ // SearchList takes precedence, so check it first.
+ if (settings.policy_search_list.set) {
+ std::vector<std::string> search;
+ if (ParseSearchList(settings.policy_search_list.value, &search)) {
+ config->search.swap(search);
+ return CONFIG_PARSE_WIN_OK;
+ }
+ // Even if invalid, the policy disables the user-specified setting below.
+ } else if (settings.tcpip_search_list.set) {
+ std::vector<std::string> search;
+ if (ParseSearchList(settings.tcpip_search_list.value, &search)) {
+ config->search.swap(search);
+ return CONFIG_PARSE_WIN_OK;
+ }
+ }
+
+ // In absence of explicit search list, suffix search is:
+ // [primary suffix, connection-specific suffix, devolution of primary suffix].
+ // Primary suffix can be set by policy (primary_dns_suffix) or
+ // user setting (tcpip_domain).
+ //
+ // The policy (primary_dns_suffix) can be edited via Group Policy Editor
+ // (gpedit.msc) at Local Computer Policy => Computer Configuration
+ // => Administrative Template => Network => DNS Client => Primary DNS Suffix.
+ //
+ // The user setting (tcpip_domain) can be configurred at Computer Name in
+ // System Settings
+ std::string primary_suffix;
+ if ((settings.primary_dns_suffix.set &&
+ ParseDomainASCII(settings.primary_dns_suffix.value, &primary_suffix)) ||
+ (settings.tcpip_domain.set &&
+ ParseDomainASCII(settings.tcpip_domain.value, &primary_suffix))) {
+ // Primary suffix goes in front.
+ config->search.insert(config->search.begin(), primary_suffix);
+ } else {
+ return CONFIG_PARSE_WIN_OK; // No primary suffix, hence no devolution.
+ }
+
+ // Devolution is determined by precedence: policy > dnscache > tcpip.
+ // |enabled|: UseDomainNameDevolution and |level|: DomainNameDevolutionLevel
+ // are overridden independently.
+ DnsSystemSettings::DevolutionSetting devolution = settings.policy_devolution;
+
+ if (!devolution.enabled.set)
+ devolution.enabled = settings.dnscache_devolution.enabled;
+ if (!devolution.enabled.set)
+ devolution.enabled = settings.tcpip_devolution.enabled;
+ if (devolution.enabled.set && (devolution.enabled.value == 0))
+ return CONFIG_PARSE_WIN_OK; // Devolution disabled.
+
+ // By default devolution is enabled.
+
+ if (!devolution.level.set)
+ devolution.level = settings.dnscache_devolution.level;
+ if (!devolution.level.set)
+ devolution.level = settings.tcpip_devolution.level;
+
+ // After the recent update, Windows will try to determine a safe default
+ // value by comparing the forest root domain (FRD) to the primary suffix.
+ // See http://support.microsoft.com/kb/957579 for details.
+ // For now, if the level is not set, we disable devolution, assuming that
+ // we will fallback to the system getaddrinfo anyway. This might cause
+ // performance loss for resolutions which depend on the system default
+ // devolution setting.
+ //
+ // If the level is explicitly set below 2, devolution is disabled.
+ if (!devolution.level.set || devolution.level.value < 2)
+ return CONFIG_PARSE_WIN_OK; // Devolution disabled.
+
+ // Devolve the primary suffix. This naive logic matches the observed
+ // behavior (see also ParseSearchList). If a suffix is not valid, it will be
+ // discarded when the fully-qualified name is converted to DNS format.
+
+ unsigned num_dots = std::count(primary_suffix.begin(),
+ primary_suffix.end(), '.');
+
+ for (size_t offset = 0; num_dots >= devolution.level.value; --num_dots) {
+ offset = primary_suffix.find('.', offset + 1);
+ config->search.push_back(primary_suffix.substr(offset + 1));
+ }
+ return CONFIG_PARSE_WIN_OK;
+}
+
+// Watches registry and HOSTS file for changes. Must live on a thread which
+// allows IO.
+class DnsConfigServiceWin::Watcher
+ : public NetworkChangeNotifier::IPAddressObserver {
+ public:
+ explicit Watcher(DnsConfigServiceWin* service) : service_(service) {}
+ ~Watcher() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ }
+
+ bool Watch() {
+ RegistryWatcher::CallbackType callback =
+ base::Bind(&DnsConfigServiceWin::OnConfigChanged,
+ base::Unretained(service_));
+
+ bool success = true;
+
+ // The Tcpip key must be present.
+ if (!tcpip_watcher_.Watch(kTcpipPath, callback)) {
+ LOG(ERROR) << "DNS registry watch failed to start.";
+ success = false;
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG,
+ DNS_CONFIG_WATCH_MAX);
+ }
+
+ // Watch for IPv6 nameservers.
+ tcpip6_watcher_.Watch(kTcpip6Path, callback);
+
+ // DNS suffix search list and devolution can be configured via group
+ // policy which sets this registry key. If the key is missing, the policy
+ // does not apply, and the DNS client uses Tcpip and Dnscache settings.
+ // If a policy is installed, DnsConfigService will need to be restarted.
+ // BUG=99509
+
+ dnscache_watcher_.Watch(kDnscachePath, callback);
+ policy_watcher_.Watch(kPolicyPath, callback);
+
+ if (!hosts_watcher_.Watch(GetHostsPath(), false,
+ base::Bind(&Watcher::OnHostsChanged,
+ base::Unretained(this)))) {
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS,
+ DNS_CONFIG_WATCH_MAX);
+ LOG(ERROR) << "DNS hosts watch failed to start.";
+ success = false;
+ } else {
+ // Also need to observe changes to local non-loopback IP for DnsHosts.
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ }
+ return success;
+ }
+
+ private:
+ void OnHostsChanged(const base::FilePath& path, bool error) {
+ if (error)
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ service_->OnHostsChanged(!error);
+ }
+
+ // NetworkChangeNotifier::IPAddressObserver:
+ virtual void OnIPAddressChanged() OVERRIDE {
+ // Need to update non-loopback IP of local host.
+ service_->OnHostsChanged(true);
+ }
+
+ DnsConfigServiceWin* service_;
+
+ RegistryWatcher tcpip_watcher_;
+ RegistryWatcher tcpip6_watcher_;
+ RegistryWatcher dnscache_watcher_;
+ RegistryWatcher policy_watcher_;
+ base::FilePathWatcher hosts_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(Watcher);
+};
+
+// Reads config from registry and IpHelper. All work performed on WorkerPool.
+class DnsConfigServiceWin::ConfigReader : public SerialWorker {
+ public:
+ explicit ConfigReader(DnsConfigServiceWin* service)
+ : service_(service),
+ success_(false) {}
+
+ private:
+ virtual ~ConfigReader() {}
+
+ virtual void DoWork() OVERRIDE {
+ // Should be called on WorkerPool.
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ DnsSystemSettings settings = {};
+ ConfigParseWinResult result = ReadSystemSettings(&settings);
+ if (result == CONFIG_PARSE_WIN_OK)
+ result = ConvertSettingsToDnsConfig(settings, &dns_config_);
+ success_ = (result == CONFIG_PARSE_WIN_OK);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParseWin",
+ result, CONFIG_PARSE_WIN_MAX);
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ConfigParseResult", success_);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ virtual void OnWorkFinished() OVERRIDE {
+ DCHECK(loop()->BelongsToCurrentThread());
+ DCHECK(!IsCancelled());
+ if (success_) {
+ service_->OnConfigRead(dns_config_);
+ } else {
+ LOG(WARNING) << "Failed to read DnsConfig.";
+ // Try again in a while in case DnsConfigWatcher missed the signal.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ConfigReader::WorkNow, this),
+ base::TimeDelta::FromSeconds(kRetryIntervalSeconds));
+ }
+ }
+
+ DnsConfigServiceWin* service_;
+ // Written in DoWork(), read in OnWorkFinished(). No locking required.
+ DnsConfig dns_config_;
+ bool success_;
+};
+
+// Reads hosts from HOSTS file and fills in localhost and local computer name if
+// necessary. All work performed on WorkerPool.
+class DnsConfigServiceWin::HostsReader : public SerialWorker {
+ public:
+ explicit HostsReader(DnsConfigServiceWin* service)
+ : path_(GetHostsPath()),
+ service_(service),
+ success_(false) {
+ }
+
+ private:
+ virtual ~HostsReader() {}
+
+ virtual void DoWork() OVERRIDE {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ HostsParseWinResult result = HOSTS_PARSE_WIN_UNREADABLE_HOSTS_FILE;
+ if (ParseHostsFile(path_, &hosts_))
+ result = AddLocalhostEntries(&hosts_);
+ success_ = (result == HOSTS_PARSE_WIN_OK);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.HostsParseWin",
+ result, HOSTS_PARSE_WIN_MAX);
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration",
+ base::TimeTicks::Now() - start_time);
+ }
+
+ virtual void OnWorkFinished() OVERRIDE {
+ DCHECK(loop()->BelongsToCurrentThread());
+ if (success_) {
+ service_->OnHostsRead(hosts_);
+ } else {
+ LOG(WARNING) << "Failed to read DnsHosts.";
+ }
+ }
+
+ const base::FilePath path_;
+ DnsConfigServiceWin* service_;
+ // Written in DoWork, read in OnWorkFinished, no locking necessary.
+ DnsHosts hosts_;
+ bool success_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostsReader);
+};
+
+DnsConfigServiceWin::DnsConfigServiceWin()
+ : config_reader_(new ConfigReader(this)),
+ hosts_reader_(new HostsReader(this)) {}
+
+DnsConfigServiceWin::~DnsConfigServiceWin() {
+ config_reader_->Cancel();
+ hosts_reader_->Cancel();
+}
+
+void DnsConfigServiceWin::ReadNow() {
+ config_reader_->WorkNow();
+ hosts_reader_->WorkNow();
+}
+
+bool DnsConfigServiceWin::StartWatching() {
+ // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139
+ watcher_.reset(new Watcher(this));
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED,
+ DNS_CONFIG_WATCH_MAX);
+ return watcher_->Watch();
+}
+
+void DnsConfigServiceWin::OnConfigChanged(bool succeeded) {
+ InvalidateConfig();
+ if (succeeded) {
+ config_reader_->WorkNow();
+ } else {
+ LOG(ERROR) << "DNS config watch failed.";
+ set_watch_failed(true);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_CONFIG,
+ DNS_CONFIG_WATCH_MAX);
+ }
+}
+
+void DnsConfigServiceWin::OnHostsChanged(bool succeeded) {
+ InvalidateHosts();
+ if (succeeded) {
+ hosts_reader_->WorkNow();
+ } else {
+ LOG(ERROR) << "DNS hosts watch failed.";
+ set_watch_failed(true);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus",
+ DNS_CONFIG_WATCH_FAILED_HOSTS,
+ DNS_CONFIG_WATCH_MAX);
+ }
+}
+
+} // namespace internal
+
+// static
+scoped_ptr<DnsConfigService> DnsConfigService::CreateSystemService() {
+ return scoped_ptr<DnsConfigService>(new internal::DnsConfigServiceWin());
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_config_service_win.h b/chromium/net/dns/dns_config_service_win.h
new file mode 100644
index 00000000000..06fc0d9663b
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_win.h
@@ -0,0 +1,154 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_CONFIG_SERVICE_WIN_H_
+#define NET_DNS_DNS_CONFIG_SERVICE_WIN_H_
+
+// The sole purpose of dns_config_service_win.h is for unittests so we just
+// include these headers here.
+#include <winsock2.h>
+#include <iphlpapi.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+#include "net/dns/dns_config_service.h"
+
+// The general effort of DnsConfigServiceWin is to configure |nameservers| and
+// |search| in DnsConfig. The settings are stored in the Windows registry, but
+// to simplify the task we use the IP Helper API wherever possible. That API
+// yields the complete and ordered |nameservers|, but to determine |search| we
+// need to use the registry. On Windows 7, WMI does return the correct |search|
+// but on earlier versions it is insufficient.
+//
+// Experimental evaluation of Windows behavior suggests that domain parsing is
+// naive. Domain suffixes in |search| are not validated until they are appended
+// to the resolved name. We attempt to replicate this behavior.
+
+namespace net {
+
+namespace internal {
+
+// Registry key paths.
+const wchar_t* const kTcpipPath =
+ L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
+const wchar_t* const kTcpip6Path =
+ L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters";
+const wchar_t* const kDnscachePath =
+ L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters";
+const wchar_t* const kPolicyPath =
+ L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient";
+
+// Returns the path to the HOSTS file.
+base::FilePath GetHostsPath();
+
+// Parses |value| as search list (comma-delimited list of domain names) from
+// a registry key and stores it in |out|. Returns true on success. Empty
+// entries (e.g., "chromium.org,,org") terminate the list. Non-ascii hostnames
+// are converted to punycode.
+bool NET_EXPORT_PRIVATE ParseSearchList(const base::string16& value,
+ std::vector<std::string>* out);
+
+// All relevant settings read from registry and IP Helper. This isolates our
+// logic from system calls and is exposed for unit tests. Keep it an aggregate
+// struct for easy initialization.
+struct NET_EXPORT_PRIVATE DnsSystemSettings {
+ // The |set| flag distinguishes between empty and unset values.
+ struct RegString {
+ bool set;
+ base::string16 value;
+ };
+
+ struct RegDword {
+ bool set;
+ DWORD value;
+ };
+
+ struct DevolutionSetting {
+ // UseDomainNameDevolution
+ RegDword enabled;
+ // DomainNameDevolutionLevel
+ RegDword level;
+ };
+
+ // Filled in by GetAdapterAddresses. Note that the alternative
+ // GetNetworkParams does not include IPv6 addresses.
+ scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> addresses;
+
+ // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\SearchList
+ RegString policy_search_list;
+ // SYSTEM\CurrentControlSet\Tcpip\Parameters\SearchList
+ RegString tcpip_search_list;
+ // SYSTEM\CurrentControlSet\Tcpip\Parameters\Domain
+ RegString tcpip_domain;
+ // SOFTWARE\Policies\Microsoft\System\DNSClient\PrimaryDnsSuffix
+ RegString primary_dns_suffix;
+
+ // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient
+ DevolutionSetting policy_devolution;
+ // SYSTEM\CurrentControlSet\Dnscache\Parameters
+ DevolutionSetting dnscache_devolution;
+ // SYSTEM\CurrentControlSet\Tcpip\Parameters
+ DevolutionSetting tcpip_devolution;
+
+ // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\AppendToMultiLabelName
+ RegDword append_to_multi_label_name;
+};
+
+enum ConfigParseWinResult {
+ CONFIG_PARSE_WIN_OK = 0,
+ CONFIG_PARSE_WIN_READ_IPHELPER,
+ CONFIG_PARSE_WIN_READ_POLICY_SEARCHLIST,
+ CONFIG_PARSE_WIN_READ_TCPIP_SEARCHLIST,
+ CONFIG_PARSE_WIN_READ_DOMAIN,
+ CONFIG_PARSE_WIN_READ_POLICY_DEVOLUTION,
+ CONFIG_PARSE_WIN_READ_DNSCACHE_DEVOLUTION,
+ CONFIG_PARSE_WIN_READ_TCPIP_DEVOLUTION,
+ CONFIG_PARSE_WIN_READ_APPEND_MULTILABEL,
+ CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX,
+ CONFIG_PARSE_WIN_BAD_ADDRESS,
+ CONFIG_PARSE_WIN_NO_NAMESERVERS,
+ CONFIG_PARSE_WIN_MAX // Bounding values for enumeration.
+};
+
+// Fills in |dns_config| from |settings|. Exposed for tests.
+ConfigParseWinResult NET_EXPORT_PRIVATE ConvertSettingsToDnsConfig(
+ const DnsSystemSettings& settings,
+ DnsConfig* dns_config);
+
+// Use DnsConfigService::CreateSystemService to use it outside of tests.
+class NET_EXPORT_PRIVATE DnsConfigServiceWin : public DnsConfigService {
+ public:
+ DnsConfigServiceWin();
+ virtual ~DnsConfigServiceWin();
+
+ private:
+ class Watcher;
+ class ConfigReader;
+ class HostsReader;
+
+ // DnsConfigService:
+ virtual void ReadNow() OVERRIDE;
+ virtual bool StartWatching() OVERRIDE;
+
+ void OnConfigChanged(bool succeeded);
+ void OnHostsChanged(bool succeeded);
+
+ scoped_ptr<Watcher> watcher_;
+ scoped_refptr<ConfigReader> config_reader_;
+ scoped_refptr<HostsReader> hosts_reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsConfigServiceWin);
+};
+
+} // namespace internal
+
+} // namespace net
+
+#endif // NET_DNS_DNS_CONFIG_SERVICE_WIN_H_
+
diff --git a/chromium/net/dns/dns_config_service_win_unittest.cc b/chromium/net/dns/dns_config_service_win_unittest.cc
new file mode 100644
index 00000000000..b28b8e9554a
--- /dev/null
+++ b/chromium/net/dns/dns_config_service_win_unittest.cc
@@ -0,0 +1,430 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_config_service_win.h"
+
+#include "base/logging.h"
+#include "base/win/windows_version.h"
+#include "net/dns/dns_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(DnsConfigServiceWinTest, ParseSearchList) {
+ const struct TestCase {
+ const wchar_t* input;
+ const char* output[4]; // NULL-terminated, empty if expected false
+ } cases[] = {
+ { L"chromium.org", { "chromium.org", NULL } },
+ { L"chromium.org,org", { "chromium.org", "org", NULL } },
+ // Empty suffixes terminate the list
+ { L"crbug.com,com,,org", { "crbug.com", "com", NULL } },
+ // IDN are converted to punycode
+ { L"\u017c\xf3\u0142ta.pi\u0119\u015b\u0107.pl,pl",
+ { "xn--ta-4ja03asj.xn--pi-wla5e0q.pl", "pl", NULL } },
+ // Empty search list is invalid
+ { L"", { NULL } },
+ { L",,", { NULL } },
+ };
+
+ std::vector<std::string> actual_output, expected_output;
+ for (unsigned i = 0; i < arraysize(cases); ++i) {
+ const TestCase& t = cases[i];
+ actual_output.clear();
+ actual_output.push_back("UNSET");
+ expected_output.clear();
+ for (const char* const* output = t.output; *output; ++output) {
+ expected_output.push_back(*output);
+ }
+ bool result = internal::ParseSearchList(t.input, &actual_output);
+ if (!expected_output.empty()) {
+ EXPECT_TRUE(result);
+ EXPECT_EQ(expected_output, actual_output);
+ } else {
+ EXPECT_FALSE(result) << "Unexpected parse success on " << t.input;
+ }
+ }
+}
+
+struct AdapterInfo {
+ IFTYPE if_type;
+ IF_OPER_STATUS oper_status;
+ const WCHAR* dns_suffix;
+ std::string dns_server_addresses[4]; // Empty string indicates end.
+ int ports[4];
+};
+
+scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> CreateAdapterAddresses(
+ const AdapterInfo* infos) {
+ size_t num_adapters = 0;
+ size_t num_addresses = 0;
+ for (size_t i = 0; infos[i].if_type; ++i) {
+ ++num_adapters;
+ for (size_t j = 0; !infos[i].dns_server_addresses[j].empty(); ++j) {
+ ++num_addresses;
+ }
+ }
+
+ size_t heap_size = num_adapters * sizeof(IP_ADAPTER_ADDRESSES) +
+ num_addresses * (sizeof(IP_ADAPTER_DNS_SERVER_ADDRESS) +
+ sizeof(struct sockaddr_storage));
+ scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> heap(
+ reinterpret_cast<IP_ADAPTER_ADDRESSES*>(malloc(heap_size)));
+ CHECK(heap.get());
+ memset(heap.get(), 0, heap_size);
+
+ IP_ADAPTER_ADDRESSES* adapters = heap.get();
+ IP_ADAPTER_DNS_SERVER_ADDRESS* addresses =
+ reinterpret_cast<IP_ADAPTER_DNS_SERVER_ADDRESS*>(adapters + num_adapters);
+ struct sockaddr_storage* storage =
+ reinterpret_cast<struct sockaddr_storage*>(addresses + num_addresses);
+
+ for (size_t i = 0; i < num_adapters; ++i) {
+ const AdapterInfo& info = infos[i];
+ IP_ADAPTER_ADDRESSES* adapter = adapters + i;
+ if (i + 1 < num_adapters)
+ adapter->Next = adapter + 1;
+ adapter->IfType = info.if_type;
+ adapter->OperStatus = info.oper_status;
+ adapter->DnsSuffix = const_cast<PWCHAR>(info.dns_suffix);
+ IP_ADAPTER_DNS_SERVER_ADDRESS* address = NULL;
+ for (size_t j = 0; !info.dns_server_addresses[j].empty(); ++j) {
+ --num_addresses;
+ if (j == 0) {
+ address = adapter->FirstDnsServerAddress = addresses + num_addresses;
+ } else {
+ // Note that |address| is moving backwards.
+ address = address->Next = address - 1;
+ }
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber(info.dns_server_addresses[j], &ip));
+ IPEndPoint ipe(ip, info.ports[j]);
+ address->Address.lpSockaddr =
+ reinterpret_cast<LPSOCKADDR>(storage + num_addresses);
+ socklen_t length = sizeof(struct sockaddr_storage);
+ CHECK(ipe.ToSockAddr(address->Address.lpSockaddr, &length));
+ address->Address.iSockaddrLength = static_cast<int>(length);
+ }
+ }
+
+ return heap.Pass();
+}
+
+TEST(DnsConfigServiceWinTest, ConvertAdapterAddresses) {
+ // Check nameservers and connection-specific suffix.
+ const struct TestCase {
+ AdapterInfo input_adapters[4]; // |if_type| == 0 indicates end.
+ std::string expected_nameservers[4]; // Empty string indicates end.
+ std::string expected_suffix;
+ int expected_ports[4];
+ } cases[] = {
+ { // Ignore loopback and inactive adapters.
+ {
+ { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
+ { "2.0.0.2" } },
+ { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
+ { "1.0.0.1" } },
+ { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
+ { "10.0.0.10", "2001:FFFF::1111" } },
+ { 0 },
+ },
+ { "10.0.0.10", "2001:FFFF::1111" },
+ "chromium.org",
+ },
+ { // Respect configured ports.
+ {
+ { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
+ { "10.0.0.10", "2001:FFFF::1111" }, { 1024, 24 } },
+ { 0 },
+ },
+ { "10.0.0.10", "2001:FFFF::1111" },
+ "chromium.org",
+ { 1024, 24 },
+ },
+ { // Use the preferred adapter (first in binding order) and filter
+ // stateless DNS discovery addresses.
+ {
+ { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"funnyloop",
+ { "2.0.0.2" } },
+ { IF_TYPE_FASTETHER, IfOperStatusUp, L"example.com",
+ { "1.0.0.1", "fec0:0:0:ffff::2", "8.8.8.8" } },
+ { IF_TYPE_USB, IfOperStatusUp, L"chromium.org",
+ { "10.0.0.10", "2001:FFFF::1111" } },
+ { 0 },
+ },
+ { "1.0.0.1", "8.8.8.8" },
+ "example.com",
+ },
+ { // No usable adapters.
+ {
+ { IF_TYPE_SOFTWARE_LOOPBACK, IfOperStatusUp, L"localhost",
+ { "2.0.0.2" } },
+ { IF_TYPE_FASTETHER, IfOperStatusDormant, L"example.com",
+ { "1.0.0.1" } },
+ { IF_TYPE_USB, IfOperStatusUp, L"chromium.org" },
+ { 0 },
+ },
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ const TestCase& t = cases[i];
+ internal::DnsSystemSettings settings = {
+ CreateAdapterAddresses(t.input_adapters),
+ // Default settings for the rest.
+ };
+ std::vector<IPEndPoint> expected_nameservers;
+ for (size_t j = 0; !t.expected_nameservers[j].empty(); ++j) {
+ IPAddressNumber ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber(t.expected_nameservers[j], &ip));
+ int port = t.expected_ports[j];
+ if (!port)
+ port = dns_protocol::kDefaultPort;
+ expected_nameservers.push_back(IPEndPoint(ip, port));
+ }
+
+ DnsConfig config;
+ internal::ConfigParseWinResult result =
+ internal::ConvertSettingsToDnsConfig(settings, &config);
+ internal::ConfigParseWinResult expected_result =
+ expected_nameservers.empty() ? internal::CONFIG_PARSE_WIN_NO_NAMESERVERS
+ : internal::CONFIG_PARSE_WIN_OK;
+ EXPECT_EQ(expected_result, result);
+ EXPECT_EQ(expected_nameservers, config.nameservers);
+ if (result == internal::CONFIG_PARSE_WIN_OK) {
+ ASSERT_EQ(1u, config.search.size());
+ EXPECT_EQ(t.expected_suffix, config.search[0]);
+ }
+ }
+}
+
+TEST(DnsConfigServiceWinTest, ConvertSuffixSearch) {
+ AdapterInfo infos[2] = {
+ { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
+ { 0 },
+ };
+
+ const struct TestCase {
+ internal::DnsSystemSettings input_settings;
+ std::string expected_search[5];
+ } cases[] = {
+ { // Policy SearchList override.
+ {
+ CreateAdapterAddresses(infos),
+ { true, L"policy.searchlist.a,policy.searchlist.b" },
+ { true, L"tcpip.searchlist.a,tcpip.searchlist.b" },
+ { true, L"tcpip.domain" },
+ { true, L"primary.dns.suffix" },
+ },
+ { "policy.searchlist.a", "policy.searchlist.b" },
+ },
+ { // User-specified SearchList override.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { true, L"tcpip.searchlist.a,tcpip.searchlist.b" },
+ { true, L"tcpip.domain" },
+ { true, L"primary.dns.suffix" },
+ },
+ { "tcpip.searchlist.a", "tcpip.searchlist.b" },
+ },
+ { // Void SearchList. Using tcpip.domain
+ {
+ CreateAdapterAddresses(infos),
+ { true, L",bad.searchlist,parsed.as.empty" },
+ { true, L"tcpip.searchlist,good.but.overridden" },
+ { true, L"tcpip.domain" },
+ { false },
+ },
+ { "tcpip.domain", "connection.suffix" },
+ },
+ { // Void SearchList. Using primary.dns.suffix
+ {
+ CreateAdapterAddresses(infos),
+ { true, L",bad.searchlist,parsed.as.empty" },
+ { true, L"tcpip.searchlist,good.but.overridden" },
+ { true, L"tcpip.domain" },
+ { true, L"primary.dns.suffix" },
+ },
+ { "primary.dns.suffix", "connection.suffix" },
+ },
+ { // Void SearchList. Using tcpip.domain when primary.dns.suffix is empty
+ {
+ CreateAdapterAddresses(infos),
+ { true, L",bad.searchlist,parsed.as.empty" },
+ { true, L"tcpip.searchlist,good.but.overridden" },
+ { true, L"tcpip.domain" },
+ { true, L"" },
+ },
+ { "tcpip.domain", "connection.suffix" },
+ },
+ { // Void SearchList. Using tcpip.domain when primary.dns.suffix is NULL
+ {
+ CreateAdapterAddresses(infos),
+ { true, L",bad.searchlist,parsed.as.empty" },
+ { true, L"tcpip.searchlist,good.but.overridden" },
+ { true, L"tcpip.domain" },
+ { true },
+ },
+ { "tcpip.domain", "connection.suffix" },
+ },
+ { // No primary suffix. Devolution does not matter.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true },
+ { true },
+ { { true, 1 }, { true, 2 } },
+ },
+ { "connection.suffix" },
+ },
+ { // Devolution enabled by policy, level by dnscache.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { false },
+ { { true, 1 }, { false } }, // policy_devolution: enabled, level
+ { { true, 0 }, { true, 3 } }, // dnscache_devolution
+ { { true, 0 }, { true, 1 } }, // tcpip_devolution
+ },
+ { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" },
+ },
+ { // Devolution enabled by dnscache, level by policy.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { true, L"f.g.i.l.j" },
+ { { false }, { true, 4 } },
+ { { true, 1 }, { false } },
+ { { true, 0 }, { true, 3 } },
+ },
+ { "f.g.i.l.j", "connection.suffix", "g.i.l.j" },
+ },
+ { // Devolution enabled by default.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { false },
+ { { false }, { false } },
+ { { false }, { true, 3 } },
+ { { false }, { true, 1 } },
+ },
+ { "a.b.c.d.e", "connection.suffix", "b.c.d.e", "c.d.e" },
+ },
+ { // Devolution enabled at level = 2, but nothing to devolve.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b" },
+ { false },
+ { { false }, { false } },
+ { { false }, { true, 2 } },
+ { { false }, { true, 2 } },
+ },
+ { "a.b", "connection.suffix" },
+ },
+ { // Devolution disabled when no explicit level.
+ // Windows XP and Vista use a default level = 2, but we don't.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { false },
+ { { true, 1 }, { false } },
+ { { true, 1 }, { false } },
+ { { true, 1 }, { false } },
+ },
+ { "a.b.c.d.e", "connection.suffix" },
+ },
+ { // Devolution disabled by policy level.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { false },
+ { { false }, { true, 1 } },
+ { { true, 1 }, { true, 3 } },
+ { { true, 1 }, { true, 4 } },
+ },
+ { "a.b.c.d.e", "connection.suffix" },
+ },
+ { // Devolution disabled by user setting.
+ {
+ CreateAdapterAddresses(infos),
+ { false },
+ { false },
+ { true, L"a.b.c.d.e" },
+ { false },
+ { { false }, { true, 3 } },
+ { { false }, { true, 3 } },
+ { { true, 0 }, { true, 3 } },
+ },
+ { "a.b.c.d.e", "connection.suffix" },
+ },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ const TestCase& t = cases[i];
+ DnsConfig config;
+ EXPECT_EQ(internal::CONFIG_PARSE_WIN_OK,
+ internal::ConvertSettingsToDnsConfig(t.input_settings, &config));
+ std::vector<std::string> expected_search;
+ for (size_t j = 0; !t.expected_search[j].empty(); ++j) {
+ expected_search.push_back(t.expected_search[j]);
+ }
+ EXPECT_EQ(expected_search, config.search);
+ }
+}
+
+TEST(DnsConfigServiceWinTest, AppendToMultiLabelName) {
+ AdapterInfo infos[2] = {
+ { IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", { "1.0.0.1" } },
+ { 0 },
+ };
+
+ // The default setting was true pre-Vista.
+ bool default_value = (base::win::GetVersion() < base::win::VERSION_VISTA);
+
+ const struct TestCase {
+ internal::DnsSystemSettings::RegDword input;
+ bool expected_output;
+ } cases[] = {
+ { { true, 0 }, false },
+ { { true, 1 }, true },
+ { { false, 0 }, default_value },
+ };
+
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ const TestCase& t = cases[i];
+ internal::DnsSystemSettings settings = {
+ CreateAdapterAddresses(infos),
+ { false }, { false }, { false }, { false },
+ { { false }, { false } },
+ { { false }, { false } },
+ { { false }, { false } },
+ t.input,
+ };
+ DnsConfig config;
+ EXPECT_EQ(internal::CONFIG_PARSE_WIN_OK,
+ internal::ConvertSettingsToDnsConfig(settings, &config));
+ EXPECT_EQ(config.append_to_multi_label_name, t.expected_output);
+ }
+}
+
+} // namespace
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_hosts.cc b/chromium/net/dns/dns_hosts.cc
new file mode 100644
index 00000000000..852d35c8bb4
--- /dev/null
+++ b/chromium/net/dns/dns_hosts.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_hosts.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_tokenizer.h"
+
+using base::StringPiece;
+
+namespace net {
+
+// Parses the contents of a hosts file. Returns one token (IP or hostname) at
+// a time. Doesn't copy anything; accepts the file as a StringPiece and
+// returns tokens as StringPieces.
+class HostsParser {
+ public:
+ explicit HostsParser(const StringPiece& text)
+ : text_(text),
+ data_(text.data()),
+ end_(text.size()),
+ pos_(0),
+ token_(),
+ token_is_ip_(false) {}
+
+ // Advances to the next token (IP or hostname). Returns whether another
+ // token was available. |token_is_ip| and |token| can be used to find out
+ // the type and text of the token.
+ bool Advance() {
+ bool next_is_ip = (pos_ == 0);
+ while (pos_ < end_ && pos_ != std::string::npos) {
+ switch (text_[pos_]) {
+ case ' ':
+ case '\t':
+ SkipWhitespace();
+ break;
+
+ case '\r':
+ case '\n':
+ next_is_ip = true;
+ pos_++;
+ break;
+
+ case '#':
+ SkipRestOfLine();
+ break;
+
+ default: {
+ size_t token_start = pos_;
+ SkipToken();
+ size_t token_end = (pos_ == std::string::npos) ? end_ : pos_;
+
+ token_ = StringPiece(data_ + token_start, token_end - token_start);
+ token_is_ip_ = next_is_ip;
+
+ return true;
+ }
+ }
+ }
+
+ text_ = StringPiece();
+ return false;
+ }
+
+ // Fast-forwards the parser to the next line. Should be called if an IP
+ // address doesn't parse, to avoid wasting time tokenizing hostnames that
+ // will be ignored.
+ void SkipRestOfLine() {
+ pos_ = text_.find("\n", pos_);
+ }
+
+ // Returns whether the last-parsed token is an IP address (true) or a
+ // hostname (false).
+ bool token_is_ip() { return token_is_ip_; }
+
+ // Returns the text of the last-parsed token as a StringPiece referencing
+ // the same underlying memory as the StringPiece passed to the constructor.
+ // Returns an empty StringPiece if no token has been parsed or the end of
+ // the input string has been reached.
+ const StringPiece& token() { return token_; }
+
+ private:
+ void SkipToken() {
+ pos_ = text_.find_first_of(" \t\n\r#", pos_);
+ }
+
+ void SkipWhitespace() {
+ pos_ = text_.find_first_not_of(" \t", pos_);
+ }
+
+ StringPiece text_;
+ const char* data_;
+ const size_t end_;
+
+ size_t pos_;
+ StringPiece token_;
+ bool token_is_ip_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostsParser);
+};
+
+
+
+void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) {
+ CHECK(dns_hosts);
+ DnsHosts& hosts = *dns_hosts;
+
+ StringPiece ip_text;
+ IPAddressNumber ip;
+ AddressFamily family = ADDRESS_FAMILY_IPV4;
+ HostsParser parser(contents);
+ while (parser.Advance()) {
+ if (parser.token_is_ip()) {
+ StringPiece new_ip_text = parser.token();
+ // Some ad-blocking hosts files contain thousands of entries pointing to
+ // the same IP address (usually 127.0.0.1). Don't bother parsing the IP
+ // again if it's the same as the one above it.
+ if (new_ip_text != ip_text) {
+ IPAddressNumber new_ip;
+ if (ParseIPLiteralToNumber(parser.token().as_string(), &new_ip)) {
+ ip_text = new_ip_text;
+ ip.swap(new_ip);
+ family = (ip.size() == 4) ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6;
+ } else {
+ parser.SkipRestOfLine();
+ }
+ }
+ } else {
+ DnsHostsKey key(parser.token().as_string(), family);
+ StringToLowerASCII(&key.first);
+ IPAddressNumber& mapped_ip = hosts[key];
+ if (mapped_ip.empty())
+ mapped_ip = ip;
+ // else ignore this entry (first hit counts)
+ }
+ }
+}
+
+bool ParseHostsFile(const base::FilePath& path, DnsHosts* dns_hosts) {
+ dns_hosts->clear();
+ // Missing file indicates empty HOSTS.
+ if (!base::PathExists(path))
+ return true;
+
+ int64 size;
+ if (!file_util::GetFileSize(path, &size))
+ return false;
+
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.HostsSize", size);
+
+ // Reject HOSTS files larger than |kMaxHostsSize| bytes.
+ const int64 kMaxHostsSize = 1 << 25; // 32MB
+ if (size > kMaxHostsSize)
+ return false;
+
+ std::string contents;
+ if (!file_util::ReadFileToString(path, &contents))
+ return false;
+
+ ParseHosts(contents, dns_hosts);
+ return true;
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_hosts.h b/chromium/net/dns/dns_hosts.h
new file mode 100644
index 00000000000..c2b290907ad
--- /dev/null
+++ b/chromium/net/dns/dns_hosts.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_HOSTS_H_
+#define NET_DNS_DNS_HOSTS_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "net/base/address_family.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h" // can't forward-declare IPAddressNumber
+
+namespace net {
+ typedef std::pair<std::string, AddressFamily> DnsHostsKey;
+};
+
+namespace BASE_HASH_NAMESPACE {
+#if defined(COMPILER_GCC)
+
+template<>
+struct hash<net::DnsHostsKey> {
+ std::size_t operator()(const net::DnsHostsKey& key) const {
+ hash<base::StringPiece> string_piece_hash;
+ return string_piece_hash(key.first) + key.second;
+ }
+};
+
+#elif defined(COMPILER_MSVC)
+
+inline size_t hash_value(const net::DnsHostsKey& key) {
+ return hash_value(key.first) + key.second;
+}
+
+#endif // COMPILER
+
+} // namespace BASE_HASH_NAMESPACE
+
+namespace net {
+
+// Parsed results of a Hosts file.
+//
+// Although Hosts files map IP address to a list of domain names, for name
+// resolution the desired mapping direction is: domain name to IP address.
+// When parsing Hosts, we apply the "first hit" rule as Windows and glibc do.
+// With a Hosts file of:
+// 300.300.300.300 localhost # bad ip
+// 127.0.0.1 localhost
+// 10.0.0.1 localhost
+// The expected resolution of localhost is 127.0.0.1.
+#if !defined(OS_ANDROID)
+typedef base::hash_map<DnsHostsKey, IPAddressNumber> DnsHosts;
+#else
+// Android's hash_map doesn't support ==, so fall back to map. (Chromium on
+// Android doesn't use the built-in DNS resolver anyway, so it's irrelevant.)
+typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts;
+#endif
+
+// Parses |contents| (as read from /etc/hosts or equivalent) and stores results
+// in |dns_hosts|. Invalid lines are ignored (as in most implementations).
+void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents,
+ DnsHosts* dns_hosts);
+
+// As above but reads the file pointed to by |path|.
+bool NET_EXPORT_PRIVATE ParseHostsFile(const base::FilePath& path,
+ DnsHosts* dns_hosts);
+
+
+
+} // namespace net
+
+#endif // NET_DNS_DNS_HOSTS_H_
+
diff --git a/chromium/net/dns/dns_hosts_unittest.cc b/chromium/net/dns/dns_hosts_unittest.cc
new file mode 100644
index 00000000000..c0e8805fc47
--- /dev/null
+++ b/chromium/net/dns/dns_hosts_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_hosts.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(DnsHostsTest, ParseHosts) {
+ std::string contents =
+ "127.0.0.1 localhost\tlocalhost.localdomain # standard\n"
+ "\n"
+ "1.0.0.1 localhost # ignored, first hit above\n"
+ "fe00::x example company # ignored, malformed IPv6\n"
+ "1.0.0.300 company # ignored, malformed IPv4\n"
+ "1.0.0.1 # ignored, missing hostname\n"
+ "1.0.0.1\t CoMpANy # normalized to 'company' \n"
+ "::1\tlocalhost ip6-localhost ip6-loopback # comment # within a comment\n"
+ "\t fe00::0 ip6-localnet\r\n"
+ "2048::2 example\n"
+ "2048::1 company example # ignored for 'example' \n"
+ "127.0.0.1 cache1\n"
+ "127.0.0.1 cache2 # should reuse parsed IP\n"
+ "256.0.0.0 cache3 # bogus IP should not clear parsed IP cache\n"
+ "127.0.0.1 cache4 # should still be reused\n"
+ "127.0.0.2 cache5\n"
+ "gibberish";
+
+ const struct {
+ const char* host;
+ AddressFamily family;
+ const char* ip;
+ } entries[] = {
+ { "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
+ { "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
+ { "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" },
+ { "localhost", ADDRESS_FAMILY_IPV6, "::1" },
+ { "ip6-localhost", ADDRESS_FAMILY_IPV6, "::1" },
+ { "ip6-loopback", ADDRESS_FAMILY_IPV6, "::1" },
+ { "ip6-localnet", ADDRESS_FAMILY_IPV6, "fe00::0" },
+ { "company", ADDRESS_FAMILY_IPV6, "2048::1" },
+ { "example", ADDRESS_FAMILY_IPV6, "2048::2" },
+ { "cache1", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
+ { "cache2", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
+ { "cache4", ADDRESS_FAMILY_IPV4, "127.0.0.1" },
+ { "cache5", ADDRESS_FAMILY_IPV4, "127.0.0.2" },
+ };
+
+ DnsHosts expected;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(entries); ++i) {
+ DnsHostsKey key(entries[i].host, entries[i].family);
+ IPAddressNumber& ip = expected[key];
+ ASSERT_TRUE(ip.empty());
+ ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip));
+ ASSERT_EQ(ip.size(), (entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u);
+ }
+
+ DnsHosts hosts;
+ ParseHosts(contents, &hosts);
+ ASSERT_EQ(expected, hosts);
+}
+
+TEST(DnsHostsTest, HostsParser_Empty) {
+ DnsHosts hosts;
+ ParseHosts("", &hosts);
+ EXPECT_EQ(0u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_OnlyWhitespace) {
+ DnsHosts hosts;
+ ParseHosts(" ", &hosts);
+ EXPECT_EQ(0u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithNothing) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithWhitespace) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost ", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithComment) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost # comment", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithNewline) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost\n", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithTwoNewlines) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost\n\n", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithNewlineAndWhitespace) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost\n ", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+TEST(DnsHostsTest, HostsParser_EndsWithNewlineAndToken) {
+ DnsHosts hosts;
+ ParseHosts("127.0.0.1 localhost\ntoken", &hosts);
+ EXPECT_EQ(1u, hosts.size());
+}
+
+} // namespace
+
+} // namespace net
+
diff --git a/chromium/net/dns/dns_protocol.h b/chromium/net/dns/dns_protocol.h
new file mode 100644
index 00000000000..a8aad65c2fb
--- /dev/null
+++ b/chromium/net/dns/dns_protocol.h
@@ -0,0 +1,143 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_PROTOCOL_H_
+#define NET_DNS_DNS_PROTOCOL_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+namespace dns_protocol {
+
+static const uint16 kDefaultPort = 53;
+static const uint16 kDefaultPortMulticast = 5353;
+
+// DNS packet consists of a header followed by questions and/or answers.
+// For the meaning of specific fields, please see RFC 1035 and 2535
+
+// Header format.
+// 1 1 1 1 1 1
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ID |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// |QR| Opcode |AA|TC|RD|RA| Z|AD|CD| RCODE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | QDCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ANCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | NSCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | ARCOUNT |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+// Question format.
+// 1 1 1 1 1 1
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | |
+// / QNAME /
+// / /
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | QTYPE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | QCLASS |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+// Answer format.
+// 1 1 1 1 1 1
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | |
+// / /
+// / NAME /
+// | |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | TYPE |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | CLASS |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | TTL |
+// | |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+// | RDLENGTH |
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
+// / RDATA /
+// / /
+// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+#pragma pack(push)
+#pragma pack(1)
+
+// On-the-wire header. All uint16 are in network order.
+// Used internally in DnsQuery and DnsResponseParser.
+struct NET_EXPORT_PRIVATE Header {
+ uint16 id;
+ uint16 flags;
+ uint16 qdcount;
+ uint16 ancount;
+ uint16 nscount;
+ uint16 arcount;
+};
+
+#pragma pack(pop)
+
+static const uint8 kLabelMask = 0xc0;
+static const uint8 kLabelPointer = 0xc0;
+static const uint8 kLabelDirect = 0x0;
+static const uint16 kOffsetMask = 0x3fff;
+
+// In MDns the most significant bit of the rrclass is designated as the
+// "cache-flush bit", as described in http://www.rfc-editor.org/rfc/rfc6762.txt
+// section 10.2.
+static const uint16 kMDnsClassMask = 0x7FFF;
+
+static const int kMaxNameLength = 255;
+
+// RFC 1035, section 4.2.1: Messages carried by UDP are restricted to 512
+// bytes (not counting the IP nor UDP headers).
+static const int kMaxUDPSize = 512;
+
+// RFC 6762, section 17: Messages over the local link are restricted by the
+// medium's MTU, and must be under 9000 bytes
+static const int kMaxMulticastSize = 9000;
+
+// DNS class types.
+static const uint16 kClassIN = 1;
+
+// DNS resource record types. See
+// http://www.iana.org/assignments/dns-parameters
+static const uint16 kTypeA = 1;
+static const uint16 kTypeCNAME = 5;
+static const uint16 kTypePTR = 12;
+static const uint16 kTypeTXT = 16;
+static const uint16 kTypeAAAA = 28;
+static const uint16 kTypeSRV = 33;
+static const uint16 kTypeNSEC = 47;
+
+
+// DNS rcode values.
+static const uint8 kRcodeMask = 0xf;
+static const uint8 kRcodeNOERROR = 0;
+static const uint8 kRcodeFORMERR = 1;
+static const uint8 kRcodeSERVFAIL = 2;
+static const uint8 kRcodeNXDOMAIN = 3;
+static const uint8 kRcodeNOTIMP = 4;
+static const uint8 kRcodeREFUSED = 5;
+
+// DNS flags.
+static const uint16 kFlagResponse = 0x8000;
+static const uint16 kFlagRA = 0x80;
+static const uint16 kFlagRD = 0x100;
+static const uint16 kFlagTC = 0x200;
+static const uint16 kFlagAA = 0x400;
+
+} // namespace dns_protocol
+
+} // namespace net
+
+#endif // NET_DNS_DNS_PROTOCOL_H_
diff --git a/chromium/net/dns/dns_query.cc b/chromium/net/dns/dns_query.cc
new file mode 100644
index 00000000000..270757e7be9
--- /dev/null
+++ b/chromium/net/dns/dns_query.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_query.h"
+
+#include <limits>
+
+#include "base/sys_byteorder.h"
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/dns/dns_protocol.h"
+
+namespace net {
+
+// DNS query consists of a 12-byte header followed by a question section.
+// For details, see RFC 1035 section 4.1.1. This header template sets RD
+// bit, which directs the name server to pursue query recursively, and sets
+// the QDCOUNT to 1, meaning the question section has a single entry.
+DnsQuery::DnsQuery(uint16 id, const base::StringPiece& qname, uint16 qtype)
+ : qname_size_(qname.size()) {
+ DCHECK(!DNSDomainToString(qname).empty());
+ // QNAME + QTYPE + QCLASS
+ size_t question_size = qname_size_ + sizeof(uint16) + sizeof(uint16);
+ io_buffer_ = new IOBufferWithSize(sizeof(dns_protocol::Header) +
+ question_size);
+ dns_protocol::Header* header =
+ reinterpret_cast<dns_protocol::Header*>(io_buffer_->data());
+ memset(header, 0, sizeof(dns_protocol::Header));
+ header->id = base::HostToNet16(id);
+ header->flags = base::HostToNet16(dns_protocol::kFlagRD);
+ header->qdcount = base::HostToNet16(1);
+
+ // Write question section after the header.
+ BigEndianWriter writer(reinterpret_cast<char*>(header + 1), question_size);
+ writer.WriteBytes(qname.data(), qname.size());
+ writer.WriteU16(qtype);
+ writer.WriteU16(dns_protocol::kClassIN);
+}
+
+DnsQuery::~DnsQuery() {
+}
+
+DnsQuery* DnsQuery::CloneWithNewId(uint16 id) const {
+ return new DnsQuery(*this, id);
+}
+
+uint16 DnsQuery::id() const {
+ const dns_protocol::Header* header =
+ reinterpret_cast<const dns_protocol::Header*>(io_buffer_->data());
+ return base::NetToHost16(header->id);
+}
+
+base::StringPiece DnsQuery::qname() const {
+ return base::StringPiece(io_buffer_->data() + sizeof(dns_protocol::Header),
+ qname_size_);
+}
+
+uint16 DnsQuery::qtype() const {
+ uint16 type;
+ ReadBigEndian<uint16>(io_buffer_->data() +
+ sizeof(dns_protocol::Header) +
+ qname_size_, &type);
+ return type;
+}
+
+base::StringPiece DnsQuery::question() const {
+ return base::StringPiece(io_buffer_->data() + sizeof(dns_protocol::Header),
+ qname_size_ + sizeof(uint16) + sizeof(uint16));
+}
+
+DnsQuery::DnsQuery(const DnsQuery& orig, uint16 id) {
+ qname_size_ = orig.qname_size_;
+ io_buffer_ = new IOBufferWithSize(orig.io_buffer()->size());
+ memcpy(io_buffer_.get()->data(), orig.io_buffer()->data(),
+ io_buffer_.get()->size());
+ dns_protocol::Header* header =
+ reinterpret_cast<dns_protocol::Header*>(io_buffer_->data());
+ header->id = base::HostToNet16(id);
+}
+
+void DnsQuery::set_flags(uint16 flags) {
+ dns_protocol::Header* header =
+ reinterpret_cast<dns_protocol::Header*>(io_buffer_->data());
+ header->flags = flags;
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_query.h b/chromium/net/dns/dns_query.h
new file mode 100644
index 00000000000..e1469bdfbc0
--- /dev/null
+++ b/chromium/net/dns/dns_query.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 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_DNS_DNS_QUERY_H_
+#define NET_DNS_DNS_QUERY_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IOBufferWithSize;
+
+// Represents on-the-wire DNS query message as an object.
+// TODO(szym): add support for the OPT pseudo-RR (EDNS0/DNSSEC).
+class NET_EXPORT_PRIVATE DnsQuery {
+ public:
+ // Constructs a query message from |qname| which *MUST* be in a valid
+ // DNS name format, and |qtype|. The qclass is set to IN.
+ DnsQuery(uint16 id, const base::StringPiece& qname, uint16 qtype);
+ ~DnsQuery();
+
+ // Clones |this| verbatim, with ID field of the header set to |id|.
+ DnsQuery* CloneWithNewId(uint16 id) const;
+
+ // DnsQuery field accessors.
+ uint16 id() const;
+ base::StringPiece qname() const;
+ uint16 qtype() const;
+
+ // Returns the Question section of the query. Used when matching the
+ // response.
+ base::StringPiece question() const;
+
+ // IOBuffer accessor to be used for writing out the query.
+ IOBufferWithSize* io_buffer() const { return io_buffer_.get(); }
+
+ void set_flags(uint16 flags);
+
+ private:
+ DnsQuery(const DnsQuery& orig, uint16 id);
+
+ // Size of the DNS name (*NOT* hostname) we are trying to resolve; used
+ // to calculate offsets.
+ size_t qname_size_;
+
+ // Contains query bytes to be consumed by higher level Write() call.
+ scoped_refptr<IOBufferWithSize> io_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsQuery);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_QUERY_H_
diff --git a/chromium/net/dns/dns_query_unittest.cc b/chromium/net/dns/dns_query_unittest.cc
new file mode 100644
index 00000000000..ffe02e70a61
--- /dev/null
+++ b/chromium/net/dns/dns_query_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 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/dns/dns_query.h"
+
+#include "base/bind.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/dns/dns_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(DnsQueryTest, Constructor) {
+ // This includes \0 at the end.
+ const char qname_data[] = "\x03""www""\x07""example""\x03""com";
+ const uint8 query_data[] = {
+ // Header
+ 0xbe, 0xef,
+ 0x01, 0x00, // Flags -- set RD (recursion desired) bit.
+ 0x00, 0x01, // Set QDCOUNT (question count) to 1, all the
+ // rest are 0 for a query.
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+
+ // Question
+ 0x03, 'w', 'w', 'w', // QNAME: www.example.com in DNS format.
+ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00,
+
+ 0x00, 0x01, // QTYPE: A query.
+ 0x00, 0x01, // QCLASS: IN class.
+ };
+
+ base::StringPiece qname(qname_data, sizeof(qname_data));
+ DnsQuery q1(0xbeef, qname, dns_protocol::kTypeA);
+ EXPECT_EQ(dns_protocol::kTypeA, q1.qtype());
+
+ ASSERT_EQ(static_cast<int>(sizeof(query_data)), q1.io_buffer()->size());
+ EXPECT_EQ(0, memcmp(q1.io_buffer()->data(), query_data, sizeof(query_data)));
+ EXPECT_EQ(qname, q1.qname());
+
+ base::StringPiece question(reinterpret_cast<const char*>(query_data) + 12,
+ 21);
+ EXPECT_EQ(question, q1.question());
+}
+
+TEST(DnsQueryTest, Clone) {
+ // This includes \0 at the end.
+ const char qname_data[] = "\x03""www""\x07""example""\x03""com";
+ base::StringPiece qname(qname_data, sizeof(qname_data));
+
+ DnsQuery q1(0, qname, dns_protocol::kTypeA);
+ EXPECT_EQ(0, q1.id());
+ scoped_ptr<DnsQuery> q2(q1.CloneWithNewId(42));
+ EXPECT_EQ(42, q2->id());
+ EXPECT_EQ(q1.io_buffer()->size(), q2->io_buffer()->size());
+ EXPECT_EQ(q1.qtype(), q2->qtype());
+ EXPECT_EQ(q1.question(), q2->question());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/dns_response.cc b/chromium/net/dns/dns_response.cc
new file mode 100644
index 00000000000..d29d3c4813c
--- /dev/null
+++ b/chromium/net/dns/dns_response.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_response.h"
+
+#include "base/strings/string_util.h"
+#include "base/sys_byteorder.h"
+#include "net/base/address_list.h"
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+
+namespace net {
+
+DnsResourceRecord::DnsResourceRecord() {
+}
+
+DnsResourceRecord::~DnsResourceRecord() {
+}
+
+DnsRecordParser::DnsRecordParser() : packet_(NULL), length_(0), cur_(0) {
+}
+
+DnsRecordParser::DnsRecordParser(const void* packet,
+ size_t length,
+ size_t offset)
+ : packet_(reinterpret_cast<const char*>(packet)),
+ length_(length),
+ cur_(packet_ + offset) {
+ DCHECK_LE(offset, length);
+}
+
+unsigned DnsRecordParser::ReadName(const void* const vpos,
+ std::string* out) const {
+ const char* const pos = reinterpret_cast<const char*>(vpos);
+ DCHECK(packet_);
+ DCHECK_LE(packet_, pos);
+ DCHECK_LE(pos, packet_ + length_);
+
+ const char* p = pos;
+ const char* end = packet_ + length_;
+ // Count number of seen bytes to detect loops.
+ unsigned seen = 0;
+ // Remember how many bytes were consumed before first jump.
+ unsigned consumed = 0;
+
+ if (pos >= end)
+ return 0;
+
+ if (out) {
+ out->clear();
+ out->reserve(dns_protocol::kMaxNameLength);
+ }
+
+ for (;;) {
+ // The first two bits of the length give the type of the length. It's
+ // either a direct length or a pointer to the remainder of the name.
+ switch (*p & dns_protocol::kLabelMask) {
+ case dns_protocol::kLabelPointer: {
+ if (p + sizeof(uint16) > end)
+ return 0;
+ if (consumed == 0) {
+ consumed = p - pos + sizeof(uint16);
+ if (!out)
+ return consumed; // If name is not stored, that's all we need.
+ }
+ seen += sizeof(uint16);
+ // If seen the whole packet, then we must be in a loop.
+ if (seen > length_)
+ return 0;
+ uint16 offset;
+ ReadBigEndian<uint16>(p, &offset);
+ offset &= dns_protocol::kOffsetMask;
+ p = packet_ + offset;
+ if (p >= end)
+ return 0;
+ break;
+ }
+ case dns_protocol::kLabelDirect: {
+ uint8 label_len = *p;
+ ++p;
+ // Note: root domain (".") is NOT included.
+ if (label_len == 0) {
+ if (consumed == 0) {
+ consumed = p - pos;
+ } // else we set |consumed| before first jump
+ return consumed;
+ }
+ if (p + label_len >= end)
+ return 0; // Truncated or missing label.
+ if (out) {
+ if (!out->empty())
+ out->append(".");
+ out->append(p, label_len);
+ }
+ p += label_len;
+ seen += 1 + label_len;
+ break;
+ }
+ default:
+ // unhandled label type
+ return 0;
+ }
+ }
+}
+
+bool DnsRecordParser::ReadRecord(DnsResourceRecord* out) {
+ DCHECK(packet_);
+ size_t consumed = ReadName(cur_, &out->name);
+ if (!consumed)
+ return false;
+ BigEndianReader reader(cur_ + consumed,
+ packet_ + length_ - (cur_ + consumed));
+ uint16 rdlen;
+ if (reader.ReadU16(&out->type) &&
+ reader.ReadU16(&out->klass) &&
+ reader.ReadU32(&out->ttl) &&
+ reader.ReadU16(&rdlen) &&
+ reader.ReadPiece(&out->rdata, rdlen)) {
+ cur_ = reader.ptr();
+ return true;
+ }
+ return false;
+}
+
+bool DnsRecordParser::SkipQuestion() {
+ size_t consumed = ReadName(cur_, NULL);
+ if (!consumed)
+ return false;
+
+ const char* next = cur_ + consumed + 2 * sizeof(uint16); // QTYPE + QCLASS
+ if (next > packet_ + length_)
+ return false;
+
+ cur_ = next;
+
+ return true;
+}
+
+DnsResponse::DnsResponse()
+ : io_buffer_(new IOBufferWithSize(dns_protocol::kMaxUDPSize + 1)) {
+}
+
+DnsResponse::DnsResponse(size_t length)
+ : io_buffer_(new IOBufferWithSize(length)) {
+}
+
+DnsResponse::DnsResponse(const void* data,
+ size_t length,
+ size_t answer_offset)
+ : io_buffer_(new IOBufferWithSize(length)),
+ parser_(io_buffer_->data(), length, answer_offset) {
+ DCHECK(data);
+ memcpy(io_buffer_->data(), data, length);
+}
+
+DnsResponse::~DnsResponse() {
+}
+
+bool DnsResponse::InitParse(int nbytes, const DnsQuery& query) {
+ DCHECK_GE(nbytes, 0);
+ // Response includes query, it should be at least that size.
+ if (nbytes < query.io_buffer()->size() || nbytes >= io_buffer_->size())
+ return false;
+
+ // Match the query id.
+ if (base::NetToHost16(header()->id) != query.id())
+ return false;
+
+ // Match question count.
+ if (base::NetToHost16(header()->qdcount) != 1)
+ return false;
+
+ // Match the question section.
+ const size_t hdr_size = sizeof(dns_protocol::Header);
+ const base::StringPiece question = query.question();
+ if (question != base::StringPiece(io_buffer_->data() + hdr_size,
+ question.size())) {
+ return false;
+ }
+
+ // Construct the parser.
+ parser_ = DnsRecordParser(io_buffer_->data(),
+ nbytes,
+ hdr_size + question.size());
+ return true;
+}
+
+bool DnsResponse::InitParseWithoutQuery(int nbytes) {
+ DCHECK_GE(nbytes, 0);
+
+ size_t hdr_size = sizeof(dns_protocol::Header);
+
+ if (nbytes < static_cast<int>(hdr_size) || nbytes >= io_buffer_->size())
+ return false;
+
+ parser_ = DnsRecordParser(
+ io_buffer_->data(), nbytes, hdr_size);
+
+ unsigned qdcount = base::NetToHost16(header()->qdcount);
+ for (unsigned i = 0; i < qdcount; ++i) {
+ if (!parser_.SkipQuestion()) {
+ parser_ = DnsRecordParser(); // Make parser invalid again.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool DnsResponse::IsValid() const {
+ return parser_.IsValid();
+}
+
+uint16 DnsResponse::flags() const {
+ DCHECK(parser_.IsValid());
+ return base::NetToHost16(header()->flags) & ~(dns_protocol::kRcodeMask);
+}
+
+uint8 DnsResponse::rcode() const {
+ DCHECK(parser_.IsValid());
+ return base::NetToHost16(header()->flags) & dns_protocol::kRcodeMask;
+}
+
+unsigned DnsResponse::answer_count() const {
+ DCHECK(parser_.IsValid());
+ return base::NetToHost16(header()->ancount);
+}
+
+unsigned DnsResponse::additional_answer_count() const {
+ DCHECK(parser_.IsValid());
+ return base::NetToHost16(header()->arcount);
+}
+
+base::StringPiece DnsResponse::qname() const {
+ DCHECK(parser_.IsValid());
+ // The response is HEADER QNAME QTYPE QCLASS ANSWER.
+ // |parser_| is positioned at the beginning of ANSWER, so the end of QNAME is
+ // two uint16s before it.
+ const size_t hdr_size = sizeof(dns_protocol::Header);
+ const size_t qname_size = parser_.GetOffset() - 2 * sizeof(uint16) - hdr_size;
+ return base::StringPiece(io_buffer_->data() + hdr_size, qname_size);
+}
+
+uint16 DnsResponse::qtype() const {
+ DCHECK(parser_.IsValid());
+ // QTYPE starts where QNAME ends.
+ const size_t type_offset = parser_.GetOffset() - 2 * sizeof(uint16);
+ uint16 type;
+ ReadBigEndian<uint16>(io_buffer_->data() + type_offset, &type);
+ return type;
+}
+
+std::string DnsResponse::GetDottedName() const {
+ return DNSDomainToString(qname());
+}
+
+DnsRecordParser DnsResponse::Parser() const {
+ DCHECK(parser_.IsValid());
+ // Return a copy of the parser.
+ return parser_;
+}
+
+const dns_protocol::Header* DnsResponse::header() const {
+ return reinterpret_cast<const dns_protocol::Header*>(io_buffer_->data());
+}
+
+DnsResponse::Result DnsResponse::ParseToAddressList(
+ AddressList* addr_list,
+ base::TimeDelta* ttl) const {
+ DCHECK(IsValid());
+ // DnsTransaction already verified that |response| matches the issued query.
+ // We still need to determine if there is a valid chain of CNAMEs from the
+ // query name to the RR owner name.
+ // We err on the side of caution with the assumption that if we are too picky,
+ // we can always fall back to the system getaddrinfo.
+
+ // Expected owner of record. No trailing dot.
+ std::string expected_name = GetDottedName();
+
+ uint16 expected_type = qtype();
+ DCHECK(expected_type == dns_protocol::kTypeA ||
+ expected_type == dns_protocol::kTypeAAAA);
+
+ size_t expected_size = (expected_type == dns_protocol::kTypeAAAA)
+ ? kIPv6AddressSize : kIPv4AddressSize;
+
+ uint32 ttl_sec = kuint32max;
+ IPAddressList ip_addresses;
+ DnsRecordParser parser = Parser();
+ DnsResourceRecord record;
+ unsigned ancount = answer_count();
+ for (unsigned i = 0; i < ancount; ++i) {
+ if (!parser.ReadRecord(&record))
+ return DNS_MALFORMED_RESPONSE;
+
+ if (record.type == dns_protocol::kTypeCNAME) {
+ // Following the CNAME chain, only if no addresses seen.
+ if (!ip_addresses.empty())
+ return DNS_CNAME_AFTER_ADDRESS;
+
+ if (base::strcasecmp(record.name.c_str(), expected_name.c_str()) != 0)
+ return DNS_NAME_MISMATCH;
+
+ if (record.rdata.size() !=
+ parser.ReadName(record.rdata.begin(), &expected_name))
+ return DNS_MALFORMED_CNAME;
+
+ ttl_sec = std::min(ttl_sec, record.ttl);
+ } else if (record.type == expected_type) {
+ if (record.rdata.size() != expected_size)
+ return DNS_SIZE_MISMATCH;
+
+ if (base::strcasecmp(record.name.c_str(), expected_name.c_str()) != 0)
+ return DNS_NAME_MISMATCH;
+
+ ttl_sec = std::min(ttl_sec, record.ttl);
+ ip_addresses.push_back(IPAddressNumber(record.rdata.begin(),
+ record.rdata.end()));
+ }
+ }
+
+ // TODO(szym): Extract TTL for NODATA results. http://crbug.com/115051
+
+ // getcanonname in eglibc returns the first owner name of an A or AAAA RR.
+ // If the response passed all the checks so far, then |expected_name| is it.
+ *addr_list = AddressList::CreateFromIPAddressList(ip_addresses,
+ expected_name);
+ *ttl = base::TimeDelta::FromSeconds(ttl_sec);
+ return DNS_PARSE_OK;
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_response.h b/chromium/net/dns/dns_response.h
new file mode 100644
index 00000000000..57abecf6253
--- /dev/null
+++ b/chromium/net/dns/dns_response.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_RESPONSE_H_
+#define NET_DNS_DNS_RESPONSE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+class AddressList;
+class DnsQuery;
+class IOBufferWithSize;
+
+namespace dns_protocol {
+struct Header;
+}
+
+// Parsed resource record.
+struct NET_EXPORT_PRIVATE DnsResourceRecord {
+ DnsResourceRecord();
+ ~DnsResourceRecord();
+
+ std::string name; // in dotted form
+ uint16 type;
+ uint16 klass;
+ uint32 ttl;
+ base::StringPiece rdata; // points to the original response buffer
+};
+
+// Iterator to walk over resource records of the DNS response packet.
+class NET_EXPORT_PRIVATE DnsRecordParser {
+ public:
+ // Construct an uninitialized iterator.
+ DnsRecordParser();
+
+ // Construct an iterator to process the |packet| of given |length|.
+ // |offset| points to the beginning of the answer section.
+ DnsRecordParser(const void* packet, size_t length, size_t offset);
+
+ // Returns |true| if initialized.
+ bool IsValid() const { return packet_ != NULL; }
+
+ // Returns |true| if no more bytes remain in the packet.
+ bool AtEnd() const { return cur_ == packet_ + length_; }
+
+ // Returns current offset into the packet.
+ size_t GetOffset() const { return cur_ - packet_; }
+
+ // Parses a (possibly compressed) DNS name from the packet starting at
+ // |pos|. Stores output (even partial) in |out| unless |out| is NULL. |out|
+ // is stored in the dotted form, e.g., "example.com". Returns number of bytes
+ // consumed or 0 on failure.
+ // This is exposed to allow parsing compressed names within RRDATA for TYPEs
+ // such as NS, CNAME, PTR, MX, SOA.
+ // See RFC 1035 section 4.1.4.
+ unsigned ReadName(const void* pos, std::string* out) const;
+
+ // Parses the next resource record into |record|. Returns true if succeeded.
+ bool ReadRecord(DnsResourceRecord* record);
+
+ // Skip a question section, returns true if succeeded.
+ bool SkipQuestion();
+
+ private:
+ const char* packet_;
+ size_t length_;
+ // Current offset within the packet.
+ const char* cur_;
+};
+
+// Buffer-holder for the DNS response allowing easy access to the header fields
+// and resource records. After reading into |io_buffer| must call InitParse to
+// position the RR parser.
+class NET_EXPORT_PRIVATE DnsResponse {
+ public:
+ // Possible results from ParseToAddressList.
+ enum Result {
+ DNS_PARSE_OK = 0,
+ DNS_MALFORMED_RESPONSE, // DnsRecordParser failed before the end of
+ // packet.
+ DNS_MALFORMED_CNAME, // Could not parse CNAME out of RRDATA.
+ DNS_NAME_MISMATCH, // Got an address but no ordered chain of CNAMEs
+ // leads there.
+ DNS_SIZE_MISMATCH, // Got an address but size does not match.
+ DNS_CNAME_AFTER_ADDRESS, // Found CNAME after an address record.
+ DNS_ADDRESS_TTL_MISMATCH, // OBSOLETE. No longer used.
+ DNS_NO_ADDRESSES, // OBSOLETE. No longer used.
+ // Only add new values here.
+ DNS_PARSE_RESULT_MAX, // Bounding value for histograms.
+ };
+
+ // Constructs a response buffer large enough to store one byte more than
+ // largest possible response, to detect malformed responses.
+ DnsResponse();
+
+ // Constructs a response buffer of given length. Used for TCP transactions.
+ explicit DnsResponse(size_t length);
+
+ // Constructs a response from |data|. Used for testing purposes only!
+ DnsResponse(const void* data, size_t length, size_t answer_offset);
+
+ ~DnsResponse();
+
+ // Internal buffer accessor into which actual bytes of response will be
+ // read.
+ IOBufferWithSize* io_buffer() { return io_buffer_.get(); }
+
+ // Assuming the internal buffer holds |nbytes| bytes, returns true iff the
+ // packet matches the |query| id and question.
+ bool InitParse(int nbytes, const DnsQuery& query);
+
+ // Assuming the internal buffer holds |nbytes| bytes, initialize the parser
+ // without matching it against an existing query.
+ bool InitParseWithoutQuery(int nbytes);
+
+ // Returns true if response is valid, that is, after successful InitParse.
+ bool IsValid() const;
+
+ // All of the methods below are valid only if the response is valid.
+
+ // Accessors for the header.
+ uint16 flags() const; // excluding rcode
+ uint8 rcode() const;
+
+ unsigned answer_count() const;
+ unsigned additional_answer_count() const;
+
+ // Accessors to the question. The qname is unparsed.
+ base::StringPiece qname() const;
+ uint16 qtype() const;
+
+ // Returns qname in dotted format.
+ std::string GetDottedName() const;
+
+ // Returns an iterator to the resource records in the answer section.
+ // The iterator is valid only in the scope of the DnsResponse.
+ // This operation is idempotent.
+ DnsRecordParser Parser() const;
+
+ // Extracts an AddressList from this response. Returns SUCCESS if succeeded.
+ // Otherwise returns a detailed error number.
+ Result ParseToAddressList(AddressList* addr_list, base::TimeDelta* ttl) const;
+
+ private:
+ // Convenience for header access.
+ const dns_protocol::Header* header() const;
+
+ // Buffer into which response bytes are read.
+ scoped_refptr<IOBufferWithSize> io_buffer_;
+
+ // Iterator constructed after InitParse positioned at the answer section.
+ // It is never updated afterwards, so can be used in accessors.
+ DnsRecordParser parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsResponse);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_RESPONSE_H_
diff --git a/chromium/net/dns/dns_response_unittest.cc b/chromium/net/dns/dns_response_unittest.cc
new file mode 100644
index 00000000000..12b0377fea6
--- /dev/null
+++ b/chromium/net/dns/dns_response_unittest.cc
@@ -0,0 +1,581 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_response.h"
+
+#include "base/time/time.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(DnsRecordParserTest, Constructor) {
+ const char data[] = { 0 };
+
+ EXPECT_FALSE(DnsRecordParser().IsValid());
+ EXPECT_TRUE(DnsRecordParser(data, 1, 0).IsValid());
+ EXPECT_TRUE(DnsRecordParser(data, 1, 1).IsValid());
+
+ EXPECT_FALSE(DnsRecordParser(data, 1, 0).AtEnd());
+ EXPECT_TRUE(DnsRecordParser(data, 1, 1).AtEnd());
+}
+
+TEST(DnsRecordParserTest, ReadName) {
+ const uint8 data[] = {
+ // all labels "foo.example.com"
+ 0x03, 'f', 'o', 'o',
+ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ // byte 0x10
+ 0x00,
+ // byte 0x11
+ // part label, part pointer, "bar.example.com"
+ 0x03, 'b', 'a', 'r',
+ 0xc0, 0x04,
+ // byte 0x17
+ // all pointer to "bar.example.com", 2 jumps
+ 0xc0, 0x11,
+ // byte 0x1a
+ };
+
+ std::string out;
+ DnsRecordParser parser(data, sizeof(data), 0);
+ ASSERT_TRUE(parser.IsValid());
+
+ EXPECT_EQ(0x11u, parser.ReadName(data + 0x00, &out));
+ EXPECT_EQ("foo.example.com", out);
+ // Check that the last "." is never stored.
+ out.clear();
+ EXPECT_EQ(0x1u, parser.ReadName(data + 0x10, &out));
+ EXPECT_EQ("", out);
+ out.clear();
+ EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, &out));
+ EXPECT_EQ("bar.example.com", out);
+ out.clear();
+ EXPECT_EQ(0x2u, parser.ReadName(data + 0x17, &out));
+ EXPECT_EQ("bar.example.com", out);
+
+ // Parse name without storing it.
+ EXPECT_EQ(0x11u, parser.ReadName(data + 0x00, NULL));
+ EXPECT_EQ(0x1u, parser.ReadName(data + 0x10, NULL));
+ EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, NULL));
+ EXPECT_EQ(0x2u, parser.ReadName(data + 0x17, NULL));
+
+ // Check that it works even if initial position is different.
+ parser = DnsRecordParser(data, sizeof(data), 0x12);
+ EXPECT_EQ(0x6u, parser.ReadName(data + 0x11, NULL));
+}
+
+TEST(DnsRecordParserTest, ReadNameFail) {
+ const uint8 data[] = {
+ // label length beyond packet
+ 0x30, 'x', 'x',
+ 0x00,
+ // pointer offset beyond packet
+ 0xc0, 0x20,
+ // pointer loop
+ 0xc0, 0x08,
+ 0xc0, 0x06,
+ // incorrect label type (currently supports only direct and pointer)
+ 0x80, 0x00,
+ // truncated name (missing root label)
+ 0x02, 'x', 'x',
+ };
+
+ DnsRecordParser parser(data, sizeof(data), 0);
+ ASSERT_TRUE(parser.IsValid());
+
+ std::string out;
+ EXPECT_EQ(0u, parser.ReadName(data + 0x00, &out));
+ EXPECT_EQ(0u, parser.ReadName(data + 0x04, &out));
+ EXPECT_EQ(0u, parser.ReadName(data + 0x08, &out));
+ EXPECT_EQ(0u, parser.ReadName(data + 0x0a, &out));
+ EXPECT_EQ(0u, parser.ReadName(data + 0x0c, &out));
+ EXPECT_EQ(0u, parser.ReadName(data + 0x0e, &out));
+}
+
+TEST(DnsRecordParserTest, ReadRecord) {
+ const uint8 data[] = {
+ // Type CNAME record.
+ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x05, // TYPE is CNAME.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, 0x24, 0x74, // TTL is 0x00012474.
+ 0x00, 0x06, // RDLENGTH is 6 bytes.
+ 0x03, 'f', 'o', 'o', // compressed name in record
+ 0xc0, 0x00,
+ // Type A record.
+ 0x03, 'b', 'a', 'r', // compressed owner name
+ 0xc0, 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x20, 0x13, 0x55, // TTL is 0x00201355.
+ 0x00, 0x04, // RDLENGTH is 4 bytes.
+ 0x7f, 0x02, 0x04, 0x01, // IP is 127.2.4.1
+ };
+
+ std::string out;
+ DnsRecordParser parser(data, sizeof(data), 0);
+
+ DnsResourceRecord record;
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_EQ("example.com", record.name);
+ EXPECT_EQ(dns_protocol::kTypeCNAME, record.type);
+ EXPECT_EQ(dns_protocol::kClassIN, record.klass);
+ EXPECT_EQ(0x00012474u, record.ttl);
+ EXPECT_EQ(6u, record.rdata.length());
+ EXPECT_EQ(6u, parser.ReadName(record.rdata.data(), &out));
+ EXPECT_EQ("foo.example.com", out);
+ EXPECT_FALSE(parser.AtEnd());
+
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_EQ("bar.example.com", record.name);
+ EXPECT_EQ(dns_protocol::kTypeA, record.type);
+ EXPECT_EQ(dns_protocol::kClassIN, record.klass);
+ EXPECT_EQ(0x00201355u, record.ttl);
+ EXPECT_EQ(4u, record.rdata.length());
+ EXPECT_EQ(base::StringPiece("\x7f\x02\x04\x01"), record.rdata);
+ EXPECT_TRUE(parser.AtEnd());
+
+ // Test truncated record.
+ parser = DnsRecordParser(data, sizeof(data) - 2, 0);
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_FALSE(parser.AtEnd());
+ EXPECT_FALSE(parser.ReadRecord(&record));
+}
+
+TEST(DnsResponseTest, InitParse) {
+ // This includes \0 at the end.
+ const char qname_data[] = "\x0A""codereview""\x08""chromium""\x03""org";
+ const base::StringPiece qname(qname_data, sizeof(qname_data));
+ // Compilers want to copy when binding temporary to const &, so must use heap.
+ scoped_ptr<DnsQuery> query(new DnsQuery(0xcafe, qname, dns_protocol::kTypeA));
+
+ const uint8 response_data[] = {
+ // Header
+ 0xca, 0xfe, // ID
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x01, // 1 question
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Question
+ // This part is echoed back from the respective query.
+ 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+
+ // Answer 1
+ 0xc0, 0x0c, // NAME is a pointer to name in Question section.
+ 0x00, 0x05, // TYPE is CNAME.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x12, // RDLENGTH is 18 bytes.
+ // ghs.l.google.com in DNS format.
+ 0x03, 'g', 'h', 's',
+ 0x01, 'l',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00,
+
+ // Answer 2
+ 0xc0, 0x35, // NAME is a pointer to name in Answer 1.
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x00, // TTL (4 bytes) is 53 seconds.
+ 0x00, 0x35,
+ 0x00, 0x04, // RDLENGTH is 4 bytes.
+ 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121
+ 0x5f, 0x79,
+ };
+
+ DnsResponse resp;
+ memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data));
+
+ // Reject too short.
+ EXPECT_FALSE(resp.InitParse(query->io_buffer()->size() - 1, *query));
+ EXPECT_FALSE(resp.IsValid());
+
+ // Reject wrong id.
+ scoped_ptr<DnsQuery> other_query(query->CloneWithNewId(0xbeef));
+ EXPECT_FALSE(resp.InitParse(sizeof(response_data), *other_query));
+ EXPECT_FALSE(resp.IsValid());
+
+ // Reject wrong question.
+ scoped_ptr<DnsQuery> wrong_query(
+ new DnsQuery(0xcafe, qname, dns_protocol::kTypeCNAME));
+ EXPECT_FALSE(resp.InitParse(sizeof(response_data), *wrong_query));
+ EXPECT_FALSE(resp.IsValid());
+
+ // Accept matching question.
+ EXPECT_TRUE(resp.InitParse(sizeof(response_data), *query));
+ EXPECT_TRUE(resp.IsValid());
+
+ // Check header access.
+ EXPECT_EQ(0x8180, resp.flags());
+ EXPECT_EQ(0x0, resp.rcode());
+ EXPECT_EQ(2u, resp.answer_count());
+
+ // Check question access.
+ EXPECT_EQ(query->qname(), resp.qname());
+ EXPECT_EQ(query->qtype(), resp.qtype());
+ EXPECT_EQ("codereview.chromium.org", resp.GetDottedName());
+
+ DnsResourceRecord record;
+ DnsRecordParser parser = resp.Parser();
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_FALSE(parser.AtEnd());
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_TRUE(parser.AtEnd());
+ EXPECT_FALSE(parser.ReadRecord(&record));
+}
+
+TEST(DnsResponseTest, InitParseWithoutQuery) {
+ DnsResponse resp;
+ memcpy(resp.io_buffer()->data(), kT0ResponseDatagram,
+ sizeof(kT0ResponseDatagram));
+
+ // Accept matching question.
+ EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(kT0ResponseDatagram)));
+ EXPECT_TRUE(resp.IsValid());
+
+ // Check header access.
+ EXPECT_EQ(0x8180, resp.flags());
+ EXPECT_EQ(0x0, resp.rcode());
+ EXPECT_EQ(kT0RecordCount, resp.answer_count());
+
+ // Check question access.
+ EXPECT_EQ(kT0Qtype, resp.qtype());
+ EXPECT_EQ(kT0HostName, resp.GetDottedName());
+
+ DnsResourceRecord record;
+ DnsRecordParser parser = resp.Parser();
+ for (unsigned i = 0; i < kT0RecordCount; i ++) {
+ EXPECT_FALSE(parser.AtEnd());
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ }
+ EXPECT_TRUE(parser.AtEnd());
+ EXPECT_FALSE(parser.ReadRecord(&record));
+}
+
+TEST(DnsResponseTest, InitParseWithoutQueryNoQuestions) {
+ const uint8 response_data[] = {
+ // Header
+ 0xca, 0xfe, // ID
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No question
+ 0x00, 0x01, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x00, // TTL (4 bytes) is 53 seconds.
+ 0x00, 0x35,
+ 0x00, 0x04, // RDLENGTH is 4 bytes.
+ 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121
+ 0x5f, 0x79,
+ };
+
+ DnsResponse resp;
+ memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data));
+
+ EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(response_data)));
+
+ // Check header access.
+ EXPECT_EQ(0x8180, resp.flags());
+ EXPECT_EQ(0x0, resp.rcode());
+ EXPECT_EQ(0x1u, resp.answer_count());
+
+ DnsResourceRecord record;
+ DnsRecordParser parser = resp.Parser();
+
+ EXPECT_FALSE(parser.AtEnd());
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_EQ("codereview.chromium.org", record.name);
+ EXPECT_EQ(0x00000035u, record.ttl);
+ EXPECT_EQ(dns_protocol::kTypeA, record.type);
+
+ EXPECT_TRUE(parser.AtEnd());
+ EXPECT_FALSE(parser.ReadRecord(&record));
+}
+
+TEST(DnsResponseTest, InitParseWithoutQueryTwoQuestions) {
+ const uint8 response_data[] = {
+ // Header
+ 0xca, 0xfe, // ID
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x02, // 2 questions
+ 0x00, 0x01, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Question 1
+ 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+
+ // Question 2
+ 0x0b, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w', '2',
+ 0xc0, 0x18, // pointer to "chromium.org"
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+
+ // Answer 1
+ 0xc0, 0x0c, // NAME is a pointer to name in Question section.
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x00, // TTL (4 bytes) is 53 seconds.
+ 0x00, 0x35,
+ 0x00, 0x04, // RDLENGTH is 4 bytes.
+ 0x4a, 0x7d, // RDATA is the IP: 74.125.95.121
+ 0x5f, 0x79,
+ };
+
+ DnsResponse resp;
+ memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data));
+
+ EXPECT_TRUE(resp.InitParseWithoutQuery(sizeof(response_data)));
+
+ // Check header access.
+ EXPECT_EQ(0x8180, resp.flags());
+ EXPECT_EQ(0x0, resp.rcode());
+ EXPECT_EQ(0x01u, resp.answer_count());
+
+ DnsResourceRecord record;
+ DnsRecordParser parser = resp.Parser();
+
+ EXPECT_FALSE(parser.AtEnd());
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ EXPECT_EQ("codereview.chromium.org", record.name);
+ EXPECT_EQ(0x35u, record.ttl);
+ EXPECT_EQ(dns_protocol::kTypeA, record.type);
+
+ EXPECT_TRUE(parser.AtEnd());
+ EXPECT_FALSE(parser.ReadRecord(&record));
+}
+
+TEST(DnsResponseTest, InitParseWithoutQueryPacketTooShort) {
+ const uint8 response_data[] = {
+ // Header
+ 0xca, 0xfe, // ID
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No question
+ };
+
+ DnsResponse resp;
+ memcpy(resp.io_buffer()->data(), response_data, sizeof(response_data));
+
+ EXPECT_FALSE(resp.InitParseWithoutQuery(sizeof(response_data)));
+}
+
+void VerifyAddressList(const std::vector<const char*>& ip_addresses,
+ const AddressList& addrlist) {
+ ASSERT_EQ(ip_addresses.size(), addrlist.size());
+
+ for (size_t i = 0; i < addrlist.size(); ++i) {
+ EXPECT_EQ(ip_addresses[i], addrlist[i].ToStringWithoutPort());
+ }
+}
+
+TEST(DnsResponseTest, ParseToAddressList) {
+ const struct TestCase {
+ size_t query_size;
+ const uint8* response_data;
+ size_t response_size;
+ const char* const* expected_addresses;
+ size_t num_expected_addresses;
+ const char* expected_cname;
+ int expected_ttl_sec;
+ } cases[] = {
+ {
+ kT0QuerySize,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram),
+ kT0IpAddresses, arraysize(kT0IpAddresses),
+ kT0CanonName,
+ kT0TTL,
+ },
+ {
+ kT1QuerySize,
+ kT1ResponseDatagram, arraysize(kT1ResponseDatagram),
+ kT1IpAddresses, arraysize(kT1IpAddresses),
+ kT1CanonName,
+ kT1TTL,
+ },
+ {
+ kT2QuerySize,
+ kT2ResponseDatagram, arraysize(kT2ResponseDatagram),
+ kT2IpAddresses, arraysize(kT2IpAddresses),
+ kT2CanonName,
+ kT2TTL,
+ },
+ {
+ kT3QuerySize,
+ kT3ResponseDatagram, arraysize(kT3ResponseDatagram),
+ kT3IpAddresses, arraysize(kT3IpAddresses),
+ kT3CanonName,
+ kT3TTL,
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ const TestCase& t = cases[i];
+ DnsResponse response(t.response_data, t.response_size, t.query_size);
+ AddressList addr_list;
+ base::TimeDelta ttl;
+ EXPECT_EQ(DnsResponse::DNS_PARSE_OK,
+ response.ParseToAddressList(&addr_list, &ttl));
+ std::vector<const char*> expected_addresses(
+ t.expected_addresses,
+ t.expected_addresses + t.num_expected_addresses);
+ VerifyAddressList(expected_addresses, addr_list);
+ EXPECT_EQ(t.expected_cname, addr_list.canonical_name());
+ EXPECT_EQ(base::TimeDelta::FromSeconds(t.expected_ttl_sec), ttl);
+ }
+}
+
+const uint8 kResponseTruncatedRecord[] = {
+ // Header: 1 question, 1 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'a', type = A, TTL = 0xFF, RDATA = 10.10.10.10
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, // Truncated RDATA.
+};
+
+const uint8 kResponseTruncatedCNAME[] = {
+ // Header: 1 question, 1 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'a', type = CNAME, TTL = 0xFF, RDATA = 'foo' (truncated)
+ 0x01, 'a', 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x03, 0x03, 'f', 'o', // Truncated name.
+};
+
+const uint8 kResponseNameMismatch[] = {
+ // Header: 1 question, 1 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'b', type = A, TTL = 0xFF, RDATA = 10.10.10.10
+ 0x01, 'b', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0A,
+};
+
+const uint8 kResponseNameMismatchInChain[] = {
+ // Header: 1 question, 3 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'a', type = CNAME, TTL = 0xFF, RDATA = 'b'
+ 0x01, 'a', 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x03, 0x01, 'b', 0x00,
+ // Answer: name = 'b', type = A, TTL = 0xFF, RDATA = 10.10.10.10
+ 0x01, 'b', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0A,
+ // Answer: name = 'c', type = A, TTL = 0xFF, RDATA = 10.10.10.11
+ 0x01, 'c', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0B,
+};
+
+const uint8 kResponseSizeMismatch[] = {
+ // Header: 1 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = AAAA (0x1c)
+ 0x01, 'a', 0x00, 0x00, 0x1c, 0x00, 0x01,
+ // Answer: name = 'a', type = AAAA, TTL = 0xFF, RDATA = 10.10.10.10
+ 0x01, 'a', 0x00, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0A,
+};
+
+const uint8 kResponseCNAMEAfterAddress[] = {
+ // Header: 2 answer RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'a', type = A, TTL = 0xFF, RDATA = 10.10.10.10.
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0A,
+ // Answer: name = 'a', type = CNAME, TTL = 0xFF, RDATA = 'b'
+ 0x01, 'a', 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x03, 0x01, 'b', 0x00,
+};
+
+const uint8 kResponseNoAddresses[] = {
+ // Header: 1 question, 1 answer RR, 1 authority RR
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ // Question: name = 'a', type = A (0x1)
+ 0x01, 'a', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Answer: name = 'a', type = CNAME, TTL = 0xFF, RDATA = 'b'
+ 0x01, 'a', 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x03, 0x01, 'b', 0x00,
+ // Authority section
+ // Answer: name = 'b', type = A, TTL = 0xFF, RDATA = 10.10.10.10
+ 0x01, 'b', 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x04, 0x0A, 0x0A, 0x0A, 0x0A,
+};
+
+TEST(DnsResponseTest, ParseToAddressListFail) {
+ const struct TestCase {
+ const uint8* data;
+ size_t size;
+ DnsResponse::Result expected_result;
+ } cases[] = {
+ { kResponseTruncatedRecord, arraysize(kResponseTruncatedRecord),
+ DnsResponse::DNS_MALFORMED_RESPONSE },
+ { kResponseTruncatedCNAME, arraysize(kResponseTruncatedCNAME),
+ DnsResponse::DNS_MALFORMED_CNAME },
+ { kResponseNameMismatch, arraysize(kResponseNameMismatch),
+ DnsResponse::DNS_NAME_MISMATCH },
+ { kResponseNameMismatchInChain, arraysize(kResponseNameMismatchInChain),
+ DnsResponse::DNS_NAME_MISMATCH },
+ { kResponseSizeMismatch, arraysize(kResponseSizeMismatch),
+ DnsResponse::DNS_SIZE_MISMATCH },
+ { kResponseCNAMEAfterAddress, arraysize(kResponseCNAMEAfterAddress),
+ DnsResponse::DNS_CNAME_AFTER_ADDRESS },
+ // Not actually a failure, just an empty result.
+ { kResponseNoAddresses, arraysize(kResponseNoAddresses),
+ DnsResponse::DNS_PARSE_OK },
+ };
+
+ const size_t kQuerySize = 12 + 7;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ const TestCase& t = cases[i];
+
+ DnsResponse response(t.data, t.size, kQuerySize);
+ AddressList addr_list;
+ base::TimeDelta ttl;
+ EXPECT_EQ(t.expected_result,
+ response.ParseToAddressList(&addr_list, &ttl));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/dns_session.cc b/chromium/net/dns/dns_session.cc
new file mode 100644
index 00000000000..ea8b6a14274
--- /dev/null
+++ b/chromium/net/dns/dns_session.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_session.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sample_vector.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/time/time.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_socket_pool.h"
+#include "net/socket/stream_socket.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+// Never exceed max timeout.
+const unsigned kMaxTimeoutMs = 5000;
+// Set min timeout, in case we are talking to a local DNS proxy.
+const unsigned kMinTimeoutMs = 10;
+
+// Number of buckets in the histogram of observed RTTs.
+const size_t kRTTBucketCount = 100;
+// Target percentile in the RTT histogram used for retransmission timeout.
+const unsigned kRTOPercentile = 99;
+} // namespace
+
+// Runtime statistics of DNS server.
+struct DnsSession::ServerStats {
+ ServerStats(base::TimeDelta rtt_estimate_param, RttBuckets* buckets)
+ : last_failure_count(0), rtt_estimate(rtt_estimate_param) {
+ rtt_histogram.reset(new base::SampleVector(buckets));
+ // Seed histogram with 2 samples at |rtt_estimate| timeout.
+ rtt_histogram->Accumulate(rtt_estimate.InMilliseconds(), 2);
+ }
+
+ // Count of consecutive failures after last success.
+ int last_failure_count;
+
+ // Last time when server returned failure or timeout.
+ base::Time last_failure;
+ // Last time when server returned success.
+ base::Time last_success;
+
+ // Estimated RTT using moving average.
+ base::TimeDelta rtt_estimate;
+ // Estimated error in the above.
+ base::TimeDelta rtt_deviation;
+
+ // A histogram of observed RTT .
+ scoped_ptr<base::SampleVector> rtt_histogram;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerStats);
+};
+
+// static
+base::LazyInstance<DnsSession::RttBuckets>::Leaky DnsSession::rtt_buckets_ =
+ LAZY_INSTANCE_INITIALIZER;
+
+DnsSession::RttBuckets::RttBuckets() : base::BucketRanges(kRTTBucketCount + 1) {
+ base::Histogram::InitializeBucketRanges(1, 5000, this);
+}
+
+DnsSession::SocketLease::SocketLease(scoped_refptr<DnsSession> session,
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket)
+ : session_(session), server_index_(server_index), socket_(socket.Pass()) {}
+
+DnsSession::SocketLease::~SocketLease() {
+ session_->FreeSocket(server_index_, socket_.Pass());
+}
+
+DnsSession::DnsSession(const DnsConfig& config,
+ scoped_ptr<DnsSocketPool> socket_pool,
+ const RandIntCallback& rand_int_callback,
+ NetLog* net_log)
+ : config_(config),
+ socket_pool_(socket_pool.Pass()),
+ rand_callback_(base::Bind(rand_int_callback, 0, kuint16max)),
+ net_log_(net_log),
+ server_index_(0) {
+ socket_pool_->Initialize(&config_.nameservers, net_log);
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "AsyncDNS.ServerCount", config_.nameservers.size(), 0, 10, 10);
+ for (size_t i = 0; i < config_.nameservers.size(); ++i) {
+ server_stats_.push_back(new ServerStats(config_.timeout,
+ rtt_buckets_.Pointer()));
+ }
+}
+
+DnsSession::~DnsSession() {
+ RecordServerStats();
+}
+
+int DnsSession::NextQueryId() const { return rand_callback_.Run(); }
+
+unsigned DnsSession::NextFirstServerIndex() {
+ unsigned index = NextGoodServerIndex(server_index_);
+ if (config_.rotate)
+ server_index_ = (server_index_ + 1) % config_.nameservers.size();
+ return index;
+}
+
+unsigned DnsSession::NextGoodServerIndex(unsigned server_index) {
+ unsigned index = server_index;
+ base::Time oldest_server_failure(base::Time::Now());
+ unsigned oldest_server_failure_index = 0;
+
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.ServerIsGood",
+ server_stats_[server_index]->last_failure.is_null());
+
+ do {
+ base::Time cur_server_failure = server_stats_[index]->last_failure;
+ // If number of failures on this server doesn't exceed number of allowed
+ // attempts, return its index.
+ if (server_stats_[server_index]->last_failure_count < config_.attempts) {
+ return index;
+ }
+ // Track oldest failed server.
+ if (cur_server_failure < oldest_server_failure) {
+ oldest_server_failure = cur_server_failure;
+ oldest_server_failure_index = index;
+ }
+ index = (index + 1) % config_.nameservers.size();
+ } while (index != server_index);
+
+ // If we are here it means that there are no successful servers, so we have
+ // to use one that has failed oldest.
+ return oldest_server_failure_index;
+}
+
+void DnsSession::RecordServerFailure(unsigned server_index) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "AsyncDNS.ServerFailureIndex", server_index, 0, 10, 10);
+ ++(server_stats_[server_index]->last_failure_count);
+ server_stats_[server_index]->last_failure = base::Time::Now();
+}
+
+void DnsSession::RecordServerSuccess(unsigned server_index) {
+ if (server_stats_[server_index]->last_success.is_null()) {
+ UMA_HISTOGRAM_COUNTS_100("AsyncDNS.ServerFailuresAfterNetworkChange",
+ server_stats_[server_index]->last_failure_count);
+ } else {
+ UMA_HISTOGRAM_COUNTS_100("AsyncDNS.ServerFailuresBeforeSuccess",
+ server_stats_[server_index]->last_failure_count);
+ }
+ server_stats_[server_index]->last_failure_count = 0;
+ server_stats_[server_index]->last_failure = base::Time();
+ server_stats_[server_index]->last_success = base::Time::Now();
+}
+
+void DnsSession::RecordRTT(unsigned server_index, base::TimeDelta rtt) {
+ DCHECK_LT(server_index, server_stats_.size());
+
+ // For measurement, assume it is the first attempt (no backoff).
+ base::TimeDelta timeout_jacobson = NextTimeoutFromJacobson(server_index, 0);
+ base::TimeDelta timeout_histogram = NextTimeoutFromHistogram(server_index, 0);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutErrorJacobson", rtt - timeout_jacobson);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutErrorHistogram",
+ rtt - timeout_histogram);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutErrorJacobsonUnder",
+ timeout_jacobson - rtt);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutErrorHistogramUnder",
+ timeout_histogram - rtt);
+
+ // Jacobson/Karels algorithm for TCP.
+ // Using parameters: alpha = 1/8, delta = 1/4, beta = 4
+ base::TimeDelta& estimate = server_stats_[server_index]->rtt_estimate;
+ base::TimeDelta& deviation = server_stats_[server_index]->rtt_deviation;
+ base::TimeDelta current_error = rtt - estimate;
+ estimate += current_error / 8; // * alpha
+ base::TimeDelta abs_error = base::TimeDelta::FromInternalValue(
+ std::abs(current_error.ToInternalValue()));
+ deviation += (abs_error - deviation) / 4; // * delta
+
+ // Histogram-based method.
+ server_stats_[server_index]->rtt_histogram
+ ->Accumulate(rtt.InMilliseconds(), 1);
+}
+
+void DnsSession::RecordLostPacket(unsigned server_index, int attempt) {
+ base::TimeDelta timeout_jacobson =
+ NextTimeoutFromJacobson(server_index, attempt);
+ base::TimeDelta timeout_histogram =
+ NextTimeoutFromHistogram(server_index, attempt);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutSpentJacobson", timeout_jacobson);
+ UMA_HISTOGRAM_TIMES("AsyncDNS.TimeoutSpentHistogram", timeout_histogram);
+}
+
+void DnsSession::RecordServerStats() {
+ for (size_t index = 0; index < server_stats_.size(); ++index) {
+ if (server_stats_[index]->last_failure_count) {
+ if (server_stats_[index]->last_success.is_null()) {
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.ServerFailuresWithoutSuccess",
+ server_stats_[index]->last_failure_count);
+ } else {
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.ServerFailuresAfterSuccess",
+ server_stats_[index]->last_failure_count);
+ }
+ }
+ }
+}
+
+
+base::TimeDelta DnsSession::NextTimeout(unsigned server_index, int attempt) {
+ // Respect config timeout if it exceeds |kMaxTimeoutMs|.
+ if (config_.timeout.InMilliseconds() >= kMaxTimeoutMs)
+ return config_.timeout;
+ return NextTimeoutFromHistogram(server_index, attempt);
+}
+
+// Allocate a socket, already connected to the server address.
+scoped_ptr<DnsSession::SocketLease> DnsSession::AllocateSocket(
+ unsigned server_index, const NetLog::Source& source) {
+ scoped_ptr<DatagramClientSocket> socket;
+
+ socket = socket_pool_->AllocateSocket(server_index);
+ if (!socket.get())
+ return scoped_ptr<SocketLease>();
+
+ socket->NetLog().BeginEvent(NetLog::TYPE_SOCKET_IN_USE,
+ source.ToEventParametersCallback());
+
+ SocketLease* lease = new SocketLease(this, server_index, socket.Pass());
+ return scoped_ptr<SocketLease>(lease);
+}
+
+scoped_ptr<StreamSocket> DnsSession::CreateTCPSocket(
+ unsigned server_index, const NetLog::Source& source) {
+ return socket_pool_->CreateTCPSocket(server_index, source);
+}
+
+// Release a socket.
+void DnsSession::FreeSocket(unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) {
+ DCHECK(socket.get());
+
+ socket->NetLog().EndEvent(NetLog::TYPE_SOCKET_IN_USE);
+
+ socket_pool_->FreeSocket(server_index, socket.Pass());
+}
+
+base::TimeDelta DnsSession::NextTimeoutFromJacobson(unsigned server_index,
+ int attempt) {
+ DCHECK_LT(server_index, server_stats_.size());
+
+ base::TimeDelta timeout = server_stats_[server_index]->rtt_estimate +
+ 4 * server_stats_[server_index]->rtt_deviation;
+
+ timeout = std::max(timeout, base::TimeDelta::FromMilliseconds(kMinTimeoutMs));
+
+ // The timeout doubles every full round.
+ unsigned num_backoffs = attempt / config_.nameservers.size();
+
+ return std::min(timeout * (1 << num_backoffs),
+ base::TimeDelta::FromMilliseconds(kMaxTimeoutMs));
+}
+
+base::TimeDelta DnsSession::NextTimeoutFromHistogram(unsigned server_index,
+ int attempt) {
+ DCHECK_LT(server_index, server_stats_.size());
+
+ COMPILE_ASSERT(std::numeric_limits<base::HistogramBase::Count>::is_signed,
+ histogram_base_count_assumed_to_be_signed);
+
+ // Use fixed percentile of observed samples.
+ const base::SampleVector& samples =
+ *server_stats_[server_index]->rtt_histogram;
+
+ base::HistogramBase::Count total = samples.TotalCount();
+ base::HistogramBase::Count remaining_count = kRTOPercentile * total / 100;
+ size_t index = 0;
+ while (remaining_count > 0 && index < rtt_buckets_.Get().size()) {
+ remaining_count -= samples.GetCountAtIndex(index);
+ ++index;
+ }
+
+ base::TimeDelta timeout =
+ base::TimeDelta::FromMilliseconds(rtt_buckets_.Get().range(index));
+
+ timeout = std::max(timeout, base::TimeDelta::FromMilliseconds(kMinTimeoutMs));
+
+ // The timeout still doubles every full round.
+ unsigned num_backoffs = attempt / config_.nameservers.size();
+
+ return std::min(timeout * (1 << num_backoffs),
+ base::TimeDelta::FromMilliseconds(kMaxTimeoutMs));
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_session.h b/chromium/net/dns/dns_session.h
new file mode 100644
index 00000000000..01ba5e5d154
--- /dev/null
+++ b/chromium/net/dns/dns_session.h
@@ -0,0 +1,147 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_SESSION_H_
+#define NET_DNS_DNS_SESSION_H_
+
+#include <vector>
+
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/metrics/bucket_ranges.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/base/rand_callback.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_socket_pool.h"
+
+namespace base {
+class BucketRanges;
+class SampleVector;
+}
+
+namespace net {
+
+class ClientSocketFactory;
+class DatagramClientSocket;
+class NetLog;
+class StreamSocket;
+
+// Session parameters and state shared between DNS transactions.
+// Ref-counted so that DnsClient::Request can keep working in absence of
+// DnsClient. A DnsSession must be recreated when DnsConfig changes.
+class NET_EXPORT_PRIVATE DnsSession
+ : NON_EXPORTED_BASE(public base::RefCounted<DnsSession>) {
+ public:
+ typedef base::Callback<int()> RandCallback;
+
+ class NET_EXPORT_PRIVATE SocketLease {
+ public:
+ SocketLease(scoped_refptr<DnsSession> session,
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket);
+ ~SocketLease();
+
+ unsigned server_index() const { return server_index_; }
+
+ DatagramClientSocket* socket() { return socket_.get(); }
+
+ private:
+ scoped_refptr<DnsSession> session_;
+ unsigned server_index_;
+ scoped_ptr<DatagramClientSocket> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketLease);
+ };
+
+ DnsSession(const DnsConfig& config,
+ scoped_ptr<DnsSocketPool> socket_pool,
+ const RandIntCallback& rand_int_callback,
+ NetLog* net_log);
+
+ const DnsConfig& config() const { return config_; }
+ NetLog* net_log() const { return net_log_; }
+
+ // Return the next random query ID.
+ int NextQueryId() const;
+
+ // Return the index of the first configured server to use on first attempt.
+ unsigned NextFirstServerIndex();
+
+ // Start with |server_index| and find the index of the next known good server
+ // to use on this attempt. Returns |server_index| if this server has no
+ // recorded failures, or if there are no other servers that have not failed
+ // or have failed longer time ago.
+ unsigned NextGoodServerIndex(unsigned server_index);
+
+ // Record that server failed to respond (due to SRV_FAIL or timeout).
+ void RecordServerFailure(unsigned server_index);
+
+ // Record that server responded successfully.
+ void RecordServerSuccess(unsigned server_index);
+
+ // Record how long it took to receive a response from the server.
+ void RecordRTT(unsigned server_index, base::TimeDelta rtt);
+
+ // Record suspected loss of a packet for a specific server.
+ void RecordLostPacket(unsigned server_index, int attempt);
+
+ // Record server stats before it is destroyed.
+ void RecordServerStats();
+
+ // Return the timeout for the next query. |attempt| counts from 0 and is used
+ // for exponential backoff.
+ base::TimeDelta NextTimeout(unsigned server_index, int attempt);
+
+ // Allocate a socket, already connected to the server address.
+ // When the SocketLease is destroyed, the socket will be freed.
+ scoped_ptr<SocketLease> AllocateSocket(unsigned server_index,
+ const NetLog::Source& source);
+
+ // Creates a StreamSocket from the factory for a transaction over TCP. These
+ // sockets are not pooled.
+ scoped_ptr<StreamSocket> CreateTCPSocket(unsigned server_index,
+ const NetLog::Source& source);
+
+ private:
+ friend class base::RefCounted<DnsSession>;
+ ~DnsSession();
+
+ // Release a socket.
+ void FreeSocket(unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket);
+
+ // Return the timeout using the TCP timeout method.
+ base::TimeDelta NextTimeoutFromJacobson(unsigned server_index, int attempt);
+
+ // Compute the timeout using the histogram method.
+ base::TimeDelta NextTimeoutFromHistogram(unsigned server_index, int attempt);
+
+ const DnsConfig config_;
+ scoped_ptr<DnsSocketPool> socket_pool_;
+ RandCallback rand_callback_;
+ NetLog* net_log_;
+
+ // Current index into |config_.nameservers| to begin resolution with.
+ int server_index_;
+
+ struct ServerStats;
+
+ // Track runtime statistics of each DNS server.
+ ScopedVector<ServerStats> server_stats_;
+
+ // Buckets shared for all |ServerStats::rtt_histogram|.
+ struct RttBuckets : public base::BucketRanges {
+ RttBuckets();
+ };
+ static base::LazyInstance<RttBuckets>::Leaky rtt_buckets_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsSession);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_SESSION_H_
diff --git a/chromium/net/dns/dns_session_unittest.cc b/chromium/net/dns/dns_session_unittest.cc
new file mode 100644
index 00000000000..ed726f23234
--- /dev/null
+++ b/chromium/net/dns/dns_session_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_session.h"
+
+#include <list>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "net/base/net_log.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_socket_pool.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class TestClientSocketFactory : public ClientSocketFactory {
+ public:
+ virtual ~TestClientSocketFactory();
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) OVERRIDE;
+
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog*, const NetLog::Source&) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<StreamSocket>();
+ }
+
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLClientSocket>();
+ }
+
+ virtual void ClearSSLSessionCache() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ private:
+ std::list<SocketDataProvider*> data_providers_;
+};
+
+struct PoolEvent {
+ enum { ALLOCATE, FREE } action;
+ unsigned server_index;
+};
+
+class DnsSessionTest : public testing::Test {
+ public:
+ void OnSocketAllocated(unsigned server_index);
+ void OnSocketFreed(unsigned server_index);
+
+ protected:
+ void Initialize(unsigned num_servers);
+ scoped_ptr<DnsSession::SocketLease> Allocate(unsigned server_index);
+ bool DidAllocate(unsigned server_index);
+ bool DidFree(unsigned server_index);
+ bool NoMoreEvents();
+
+ DnsConfig config_;
+ scoped_ptr<TestClientSocketFactory> test_client_socket_factory_;
+ scoped_refptr<DnsSession> session_;
+ NetLog::Source source_;
+
+ private:
+ bool ExpectEvent(const PoolEvent& event);
+ std::list<PoolEvent> events_;
+};
+
+class MockDnsSocketPool : public DnsSocketPool {
+ public:
+ MockDnsSocketPool(ClientSocketFactory* factory, DnsSessionTest* test)
+ : DnsSocketPool(factory), test_(test) { }
+
+ virtual ~MockDnsSocketPool() { }
+
+ virtual void Initialize(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) OVERRIDE {
+ InitializeInternal(nameservers, net_log);
+ }
+
+ virtual scoped_ptr<DatagramClientSocket> AllocateSocket(
+ unsigned server_index) OVERRIDE {
+ test_->OnSocketAllocated(server_index);
+ return CreateConnectedSocket(server_index);
+ }
+
+ virtual void FreeSocket(
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) OVERRIDE {
+ test_->OnSocketFreed(server_index);
+ }
+
+ private:
+ DnsSessionTest* test_;
+};
+
+void DnsSessionTest::Initialize(unsigned num_servers) {
+ CHECK(num_servers < 256u);
+ config_.nameservers.clear();
+ IPAddressNumber dns_ip;
+ bool rv = ParseIPLiteralToNumber("192.168.1.0", &dns_ip);
+ EXPECT_TRUE(rv);
+ for (unsigned char i = 0; i < num_servers; ++i) {
+ dns_ip[3] = i;
+ IPEndPoint dns_endpoint(dns_ip, dns_protocol::kDefaultPort);
+ config_.nameservers.push_back(dns_endpoint);
+ }
+
+ test_client_socket_factory_.reset(new TestClientSocketFactory());
+
+ DnsSocketPool* dns_socket_pool =
+ new MockDnsSocketPool(test_client_socket_factory_.get(), this);
+
+ session_ = new DnsSession(config_,
+ scoped_ptr<DnsSocketPool>(dns_socket_pool),
+ base::Bind(&base::RandInt),
+ NULL /* NetLog */);
+
+ events_.clear();
+}
+
+scoped_ptr<DnsSession::SocketLease> DnsSessionTest::Allocate(
+ unsigned server_index) {
+ return session_->AllocateSocket(server_index, source_);
+}
+
+bool DnsSessionTest::DidAllocate(unsigned server_index) {
+ PoolEvent expected_event = { PoolEvent::ALLOCATE, server_index };
+ return ExpectEvent(expected_event);
+}
+
+bool DnsSessionTest::DidFree(unsigned server_index) {
+ PoolEvent expected_event = { PoolEvent::FREE, server_index };
+ return ExpectEvent(expected_event);
+}
+
+bool DnsSessionTest::NoMoreEvents() {
+ return events_.empty();
+}
+
+void DnsSessionTest::OnSocketAllocated(unsigned server_index) {
+ PoolEvent event = { PoolEvent::ALLOCATE, server_index };
+ events_.push_back(event);
+}
+
+void DnsSessionTest::OnSocketFreed(unsigned server_index) {
+ PoolEvent event = { PoolEvent::FREE, server_index };
+ events_.push_back(event);
+}
+
+bool DnsSessionTest::ExpectEvent(const PoolEvent& expected) {
+ if (events_.empty()) {
+ return false;
+ }
+
+ const PoolEvent actual = events_.front();
+ if ((expected.action != actual.action)
+ || (expected.server_index != actual.server_index)) {
+ return false;
+ }
+ events_.pop_front();
+
+ return true;
+}
+
+scoped_ptr<DatagramClientSocket>
+TestClientSocketFactory::CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) {
+ // We're not actually expecting to send or receive any data, so use the
+ // simplest SocketDataProvider with no data supplied.
+ SocketDataProvider* data_provider = new StaticSocketDataProvider();
+ data_providers_.push_back(data_provider);
+ scoped_ptr<MockUDPClientSocket> socket(
+ new MockUDPClientSocket(data_provider, net_log));
+ data_provider->set_socket(socket.get());
+ return socket.PassAs<DatagramClientSocket>();
+}
+
+TestClientSocketFactory::~TestClientSocketFactory() {
+ STLDeleteElements(&data_providers_);
+}
+
+TEST_F(DnsSessionTest, AllocateFree) {
+ scoped_ptr<DnsSession::SocketLease> lease1, lease2;
+
+ Initialize(2);
+ EXPECT_TRUE(NoMoreEvents());
+
+ lease1 = Allocate(0);
+ EXPECT_TRUE(DidAllocate(0));
+ EXPECT_TRUE(NoMoreEvents());
+
+ lease2 = Allocate(1);
+ EXPECT_TRUE(DidAllocate(1));
+ EXPECT_TRUE(NoMoreEvents());
+
+ lease1.reset();
+ EXPECT_TRUE(DidFree(0));
+ EXPECT_TRUE(NoMoreEvents());
+
+ lease2.reset();
+ EXPECT_TRUE(DidFree(1));
+ EXPECT_TRUE(NoMoreEvents());
+}
+
+// Expect default calculated timeout to be within 10ms of in DnsConfig.
+TEST_F(DnsSessionTest, HistogramTimeoutNormal) {
+ Initialize(2);
+ base::TimeDelta timeoutDelta = session_->NextTimeout(0, 0) - config_.timeout;
+ EXPECT_LT(timeoutDelta.InMilliseconds(), 10);
+}
+
+// Expect short calculated timeout to be within 10ms of in DnsConfig.
+TEST_F(DnsSessionTest, HistogramTimeoutShort) {
+ config_.timeout = base::TimeDelta::FromMilliseconds(15);
+ Initialize(2);
+ base::TimeDelta timeoutDelta = session_->NextTimeout(0, 0) - config_.timeout;
+ EXPECT_LT(timeoutDelta.InMilliseconds(), 10);
+}
+
+// Expect long calculated timeout to be equal to one in DnsConfig.
+TEST_F(DnsSessionTest, HistogramTimeoutLong) {
+ config_.timeout = base::TimeDelta::FromSeconds(15);
+ Initialize(2);
+ base::TimeDelta timeout = session_->NextTimeout(0, 0);
+ EXPECT_EQ(config_.timeout.InMilliseconds(), timeout.InMilliseconds());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/dns_socket_pool.cc b/chromium/net/dns/dns_socket_pool.cc
new file mode 100644
index 00000000000..7a7ecd6ee8f
--- /dev/null
+++ b/chromium/net/dns/dns_socket_pool.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_socket_pool.h"
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/rand_callback.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/stream_socket.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+
+// When we initialize the SocketPool, we allocate kInitialPoolSize sockets.
+// When we allocate a socket, we ensure we have at least kAllocateMinSize
+// sockets to choose from. When we free a socket, we retain it if we have
+// less than kRetainMaxSize sockets in the pool.
+
+// On Windows, we can't request specific (random) ports, since that will
+// trigger firewall prompts, so request default ones, but keep a pile of
+// them. Everywhere else, request fresh, random ports each time.
+#if defined(OS_WIN)
+const DatagramSocket::BindType kBindType = DatagramSocket::DEFAULT_BIND;
+const unsigned kInitialPoolSize = 256;
+const unsigned kAllocateMinSize = 256;
+const unsigned kRetainMaxSize = 0;
+#else
+const DatagramSocket::BindType kBindType = DatagramSocket::RANDOM_BIND;
+const unsigned kInitialPoolSize = 0;
+const unsigned kAllocateMinSize = 1;
+const unsigned kRetainMaxSize = 0;
+#endif
+
+} // namespace
+
+DnsSocketPool::DnsSocketPool(ClientSocketFactory* socket_factory)
+ : socket_factory_(socket_factory),
+ net_log_(NULL),
+ nameservers_(NULL),
+ initialized_(false) {
+}
+
+void DnsSocketPool::InitializeInternal(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) {
+ DCHECK(nameservers);
+ DCHECK(!initialized_);
+
+ net_log_ = net_log;
+ nameservers_ = nameservers;
+ initialized_ = true;
+}
+
+scoped_ptr<StreamSocket> DnsSocketPool::CreateTCPSocket(
+ unsigned server_index,
+ const NetLog::Source& source) {
+ DCHECK_LT(server_index, nameservers_->size());
+
+ return scoped_ptr<StreamSocket>(
+ socket_factory_->CreateTransportClientSocket(
+ AddressList((*nameservers_)[server_index]), net_log_, source));
+}
+
+scoped_ptr<DatagramClientSocket> DnsSocketPool::CreateConnectedSocket(
+ unsigned server_index) {
+ DCHECK_LT(server_index, nameservers_->size());
+
+ scoped_ptr<DatagramClientSocket> socket;
+
+ NetLog::Source no_source;
+ socket = socket_factory_->CreateDatagramClientSocket(
+ kBindType, base::Bind(&base::RandInt), net_log_, no_source);
+
+ if (socket.get()) {
+ int rv = socket->Connect((*nameservers_)[server_index]);
+ if (rv != OK) {
+ LOG(WARNING) << "Failed to connect socket: " << rv;
+ socket.reset();
+ }
+ } else {
+ LOG(WARNING) << "Failed to create socket.";
+ }
+
+ return socket.Pass();
+}
+
+class NullDnsSocketPool : public DnsSocketPool {
+ public:
+ NullDnsSocketPool(ClientSocketFactory* factory)
+ : DnsSocketPool(factory) {
+ }
+
+ virtual void Initialize(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) OVERRIDE {
+ InitializeInternal(nameservers, net_log);
+ }
+
+ virtual scoped_ptr<DatagramClientSocket> AllocateSocket(
+ unsigned server_index) OVERRIDE {
+ return CreateConnectedSocket(server_index);
+ }
+
+ virtual void FreeSocket(
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) OVERRIDE {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullDnsSocketPool);
+};
+
+// static
+scoped_ptr<DnsSocketPool> DnsSocketPool::CreateNull(
+ ClientSocketFactory* factory) {
+ return scoped_ptr<DnsSocketPool>(new NullDnsSocketPool(factory));
+}
+
+class DefaultDnsSocketPool : public DnsSocketPool {
+ public:
+ DefaultDnsSocketPool(ClientSocketFactory* factory)
+ : DnsSocketPool(factory) {
+ };
+
+ virtual ~DefaultDnsSocketPool();
+
+ virtual void Initialize(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) OVERRIDE;
+
+ virtual scoped_ptr<DatagramClientSocket> AllocateSocket(
+ unsigned server_index) OVERRIDE;
+
+ virtual void FreeSocket(
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) OVERRIDE;
+
+ private:
+ void FillPool(unsigned server_index, unsigned size);
+
+ typedef std::vector<DatagramClientSocket*> SocketVector;
+
+ std::vector<SocketVector> pools_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultDnsSocketPool);
+};
+
+// static
+scoped_ptr<DnsSocketPool> DnsSocketPool::CreateDefault(
+ ClientSocketFactory* factory) {
+ return scoped_ptr<DnsSocketPool>(new DefaultDnsSocketPool(factory));
+}
+
+void DefaultDnsSocketPool::Initialize(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) {
+ InitializeInternal(nameservers, net_log);
+
+ DCHECK(pools_.empty());
+ const unsigned num_servers = nameservers->size();
+ pools_.resize(num_servers);
+ for (unsigned server_index = 0; server_index < num_servers; ++server_index)
+ FillPool(server_index, kInitialPoolSize);
+}
+
+DefaultDnsSocketPool::~DefaultDnsSocketPool() {
+ unsigned num_servers = pools_.size();
+ for (unsigned server_index = 0; server_index < num_servers; ++server_index) {
+ SocketVector& pool = pools_[server_index];
+ STLDeleteElements(&pool);
+ }
+}
+
+scoped_ptr<DatagramClientSocket> DefaultDnsSocketPool::AllocateSocket(
+ unsigned server_index) {
+ DCHECK_LT(server_index, pools_.size());
+ SocketVector& pool = pools_[server_index];
+
+ FillPool(server_index, kAllocateMinSize);
+ if (pool.size() == 0) {
+ LOG(WARNING) << "No DNS sockets available in pool " << server_index << "!";
+ return scoped_ptr<DatagramClientSocket>();
+ }
+
+ if (pool.size() < kAllocateMinSize) {
+ LOG(WARNING) << "Low DNS port entropy: wanted " << kAllocateMinSize
+ << " sockets to choose from, but only have " << pool.size()
+ << " in pool " << server_index << ".";
+ }
+
+ unsigned socket_index = base::RandInt(0, pool.size() - 1);
+ DatagramClientSocket* socket = pool[socket_index];
+ pool[socket_index] = pool.back();
+ pool.pop_back();
+
+ return scoped_ptr<DatagramClientSocket>(socket);
+}
+
+void DefaultDnsSocketPool::FreeSocket(
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) {
+ DCHECK_LT(server_index, pools_.size());
+
+ // In some builds, kRetainMaxSize will be 0 if we never reuse sockets.
+ // In that case, don't compile this code to avoid a "tautological
+ // comparison" warning from clang.
+#if kRetainMaxSize > 0
+ SocketVector& pool = pools_[server_index];
+ if (pool.size() < kRetainMaxSize)
+ pool.push_back(socket.release());
+#endif
+}
+
+void DefaultDnsSocketPool::FillPool(unsigned server_index, unsigned size) {
+ SocketVector& pool = pools_[server_index];
+
+ for (unsigned pool_index = pool.size(); pool_index < size; ++pool_index) {
+ DatagramClientSocket* socket =
+ CreateConnectedSocket(server_index).release();
+ if (!socket)
+ break;
+ pool.push_back(socket);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_socket_pool.h b/chromium/net/dns/dns_socket_pool.h
new file mode 100644
index 00000000000..6bfe474d6a9
--- /dev/null
+++ b/chromium/net/dns/dns_socket_pool.h
@@ -0,0 +1,91 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_SOCKET_POOL_H_
+#define NET_DNS_DNS_SOCKET_POOL_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class DatagramClientSocket;
+class IPEndPoint;
+class NetLog;
+class StreamSocket;
+
+// A DnsSocketPool is an abstraction layer around a ClientSocketFactory that
+// allows preallocation, reuse, or other strategies to manage sockets connected
+// to DNS servers.
+class NET_EXPORT_PRIVATE DnsSocketPool {
+ public:
+ virtual ~DnsSocketPool() { }
+
+ // Creates a DnsSocketPool that implements the default strategy for managing
+ // sockets. (This varies by platform; see DnsSocketPoolImpl in
+ // dns_socket_pool.cc for details.)
+ static scoped_ptr<DnsSocketPool> CreateDefault(
+ ClientSocketFactory* factory);
+
+ // Creates a DnsSocketPool that implements a "null" strategy -- no sockets are
+ // preallocated, allocation requests are satisfied by calling the factory
+ // directly, and returned sockets are deleted immediately.
+ static scoped_ptr<DnsSocketPool> CreateNull(
+ ClientSocketFactory* factory);
+
+ // Initializes the DnsSocketPool. |nameservers| is the list of nameservers
+ // for which the DnsSocketPool will manage sockets; |net_log| is the NetLog
+ // used when constructing sockets with the factory.
+ //
+ // Initialize may not be called more than once, and must be called before
+ // calling AllocateSocket or FreeSocket.
+ virtual void Initialize(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log) = 0;
+
+ // Allocates a socket that is already connected to the nameserver referenced
+ // by |server_index|. May return a scoped_ptr to NULL if no sockets are
+ // available to reuse and the factory fails to produce a socket (or produces
+ // one on which Connect fails).
+ virtual scoped_ptr<DatagramClientSocket> AllocateSocket(
+ unsigned server_index) = 0;
+
+ // Frees a socket allocated by AllocateSocket. |server_index| must be the
+ // same index passed to AllocateSocket.
+ virtual void FreeSocket(
+ unsigned server_index,
+ scoped_ptr<DatagramClientSocket> socket) = 0;
+
+ // Creates a StreamSocket from the factory for a transaction over TCP. These
+ // sockets are not pooled.
+ scoped_ptr<StreamSocket> CreateTCPSocket(
+ unsigned server_index,
+ const NetLog::Source& source);
+
+ protected:
+ DnsSocketPool(ClientSocketFactory* socket_factory);
+
+ void InitializeInternal(
+ const std::vector<IPEndPoint>* nameservers,
+ NetLog* net_log);
+
+ scoped_ptr<DatagramClientSocket> CreateConnectedSocket(
+ unsigned server_index);
+
+ private:
+ ClientSocketFactory* socket_factory_;
+ NetLog* net_log_;
+ const std::vector<IPEndPoint>* nameservers_;
+ bool initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsSocketPool);
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_SOCKET_POOL_H_
diff --git a/chromium/net/dns/dns_test_util.cc b/chromium/net/dns/dns_test_util.cc
new file mode 100644
index 00000000000..37bf855dd10
--- /dev/null
+++ b/chromium/net/dns/dns_test_util.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_test_util.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sys_byteorder.h"
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/dns/address_sorter.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+// A DnsTransaction which uses MockDnsClientRuleList to determine the response.
+class MockTransaction : public DnsTransaction,
+ public base::SupportsWeakPtr<MockTransaction> {
+ public:
+ MockTransaction(const MockDnsClientRuleList& rules,
+ const std::string& hostname,
+ uint16 qtype,
+ const DnsTransactionFactory::CallbackType& callback)
+ : result_(MockDnsClientRule::FAIL),
+ hostname_(hostname),
+ qtype_(qtype),
+ callback_(callback),
+ started_(false) {
+ // Find the relevant rule which matches |qtype| and prefix of |hostname|.
+ for (size_t i = 0; i < rules.size(); ++i) {
+ const std::string& prefix = rules[i].prefix;
+ if ((rules[i].qtype == qtype) &&
+ (hostname.size() >= prefix.size()) &&
+ (hostname.compare(0, prefix.size(), prefix) == 0)) {
+ result_ = rules[i].result;
+ break;
+ }
+ }
+ }
+
+ virtual const std::string& GetHostname() const OVERRIDE {
+ return hostname_;
+ }
+
+ virtual uint16 GetType() const OVERRIDE {
+ return qtype_;
+ }
+
+ virtual void Start() OVERRIDE {
+ EXPECT_FALSE(started_);
+ started_ = true;
+ // Using WeakPtr to cleanly cancel when transaction is destroyed.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&MockTransaction::Finish, AsWeakPtr()));
+ }
+
+ private:
+ void Finish() {
+ switch (result_) {
+ case MockDnsClientRule::EMPTY:
+ case MockDnsClientRule::OK: {
+ std::string qname;
+ DNSDomainFromDot(hostname_, &qname);
+ DnsQuery query(0, qname, qtype_);
+
+ DnsResponse response;
+ char* buffer = response.io_buffer()->data();
+ int nbytes = query.io_buffer()->size();
+ memcpy(buffer, query.io_buffer()->data(), nbytes);
+ dns_protocol::Header* header =
+ reinterpret_cast<dns_protocol::Header*>(buffer);
+ header->flags |= dns_protocol::kFlagResponse;
+
+ if (MockDnsClientRule::OK == result_) {
+ const uint16 kPointerToQueryName =
+ static_cast<uint16>(0xc000 | sizeof(*header));
+
+ const uint32 kTTL = 86400; // One day.
+
+ // Size of RDATA which is a IPv4 or IPv6 address.
+ size_t rdata_size = qtype_ == net::dns_protocol::kTypeA ?
+ net::kIPv4AddressSize : net::kIPv6AddressSize;
+
+ // 12 is the sum of sizes of the compressed name reference, TYPE,
+ // CLASS, TTL and RDLENGTH.
+ size_t answer_size = 12 + rdata_size;
+
+ // Write answer with loopback IP address.
+ header->ancount = base::HostToNet16(1);
+ BigEndianWriter writer(buffer + nbytes, answer_size);
+ writer.WriteU16(kPointerToQueryName);
+ writer.WriteU16(qtype_);
+ writer.WriteU16(net::dns_protocol::kClassIN);
+ writer.WriteU32(kTTL);
+ writer.WriteU16(rdata_size);
+ if (qtype_ == net::dns_protocol::kTypeA) {
+ char kIPv4Loopback[] = { 0x7f, 0, 0, 1 };
+ writer.WriteBytes(kIPv4Loopback, sizeof(kIPv4Loopback));
+ } else {
+ char kIPv6Loopback[] = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1 };
+ writer.WriteBytes(kIPv6Loopback, sizeof(kIPv6Loopback));
+ }
+ nbytes += answer_size;
+ }
+ EXPECT_TRUE(response.InitParse(nbytes, query));
+ callback_.Run(this, OK, &response);
+ } break;
+ case MockDnsClientRule::FAIL:
+ callback_.Run(this, ERR_NAME_NOT_RESOLVED, NULL);
+ break;
+ case MockDnsClientRule::TIMEOUT:
+ callback_.Run(this, ERR_DNS_TIMED_OUT, NULL);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ MockDnsClientRule::Result result_;
+ const std::string hostname_;
+ const uint16 qtype_;
+ DnsTransactionFactory::CallbackType callback_;
+ bool started_;
+};
+
+
+// A DnsTransactionFactory which creates MockTransaction.
+class MockTransactionFactory : public DnsTransactionFactory {
+ public:
+ explicit MockTransactionFactory(const MockDnsClientRuleList& rules)
+ : rules_(rules) {}
+ virtual ~MockTransactionFactory() {}
+
+ virtual scoped_ptr<DnsTransaction> CreateTransaction(
+ const std::string& hostname,
+ uint16 qtype,
+ const DnsTransactionFactory::CallbackType& callback,
+ const BoundNetLog&) OVERRIDE {
+ return scoped_ptr<DnsTransaction>(
+ new MockTransaction(rules_, hostname, qtype, callback));
+ }
+
+ private:
+ MockDnsClientRuleList rules_;
+};
+
+class MockAddressSorter : public AddressSorter {
+ public:
+ virtual ~MockAddressSorter() {}
+ virtual void Sort(const AddressList& list,
+ const CallbackType& callback) const OVERRIDE {
+ // Do nothing.
+ callback.Run(true, list);
+ }
+};
+
+// MockDnsClient provides MockTransactionFactory.
+class MockDnsClient : public DnsClient {
+ public:
+ MockDnsClient(const DnsConfig& config,
+ const MockDnsClientRuleList& rules)
+ : config_(config), factory_(rules) {}
+ virtual ~MockDnsClient() {}
+
+ virtual void SetConfig(const DnsConfig& config) OVERRIDE {
+ config_ = config;
+ }
+
+ virtual const DnsConfig* GetConfig() const OVERRIDE {
+ return config_.IsValid() ? &config_ : NULL;
+ }
+
+ virtual DnsTransactionFactory* GetTransactionFactory() OVERRIDE {
+ return config_.IsValid() ? &factory_ : NULL;
+ }
+
+ virtual AddressSorter* GetAddressSorter() OVERRIDE {
+ return &address_sorter_;
+ }
+
+ private:
+ DnsConfig config_;
+ MockTransactionFactory factory_;
+ MockAddressSorter address_sorter_;
+};
+
+} // namespace
+
+// static
+scoped_ptr<DnsClient> CreateMockDnsClient(const DnsConfig& config,
+ const MockDnsClientRuleList& rules) {
+ return scoped_ptr<DnsClient>(new MockDnsClient(config, rules));
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_test_util.h b/chromium/net/dns/dns_test_util.h
new file mode 100644
index 00000000000..d447b299c86
--- /dev/null
+++ b/chromium/net/dns/dns_test_util.h
@@ -0,0 +1,205 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_TEST_UTIL_H_
+#define NET_DNS_DNS_TEST_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_protocol.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// Query/response set for www.google.com, ID is fixed to 0.
+static const char kT0HostName[] = "www.google.com";
+static const uint16 kT0Qtype = dns_protocol::kTypeA;
+static const char kT0DnsName[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00
+};
+static const size_t kT0QuerySize = 32;
+static const uint8 kT0ResponseDatagram[] = {
+ // response contains one CNAME for www.l.google.com and the following
+ // IP addresses: 74.125.226.{179,180,176,177,178}
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01,
+ 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01,
+ 0x4d, 0x13, 0x00, 0x08, 0x03, 0x77, 0x77, 0x77,
+ 0x01, 0x6c, 0xc0, 0x10, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb3, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb4, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb0, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb1, 0xc0, 0x2c, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x04,
+ 0x4a, 0x7d, 0xe2, 0xb2
+};
+static const char* const kT0IpAddresses[] = {
+ "74.125.226.179", "74.125.226.180", "74.125.226.176",
+ "74.125.226.177", "74.125.226.178"
+};
+static const char kT0CanonName[] = "www.l.google.com";
+static const int kT0TTL = 0x000000e4;
+// +1 for the CNAME record.
+static const unsigned kT0RecordCount = arraysize(kT0IpAddresses) + 1;
+
+//-----------------------------------------------------------------------------
+// Query/response set for codereview.chromium.org, ID is fixed to 1.
+static const char kT1HostName[] = "codereview.chromium.org";
+static const uint16 kT1Qtype = dns_protocol::kTypeA;
+static const char kT1DnsName[] = {
+ 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00
+};
+static const size_t kT1QuerySize = 41;
+static const uint8 kT1ResponseDatagram[] = {
+ // response contains one CNAME for ghs.l.google.com and the following
+ // IP address: 64.233.169.121
+ 0x00, 0x01, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, 0x64,
+ 0x65, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x08,
+ 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x69, 0x75, 0x6d,
+ 0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00,
+ 0x01, 0x41, 0x75, 0x00, 0x12, 0x03, 0x67, 0x68,
+ 0x73, 0x01, 0x6c, 0x06, 0x67, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0xc0,
+ 0x35, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x0b, 0x00, 0x04, 0x40, 0xe9, 0xa9, 0x79
+};
+static const char* const kT1IpAddresses[] = {
+ "64.233.169.121"
+};
+static const char kT1CanonName[] = "ghs.l.google.com";
+static const int kT1TTL = 0x0000010b;
+// +1 for the CNAME record.
+static const unsigned kT1RecordCount = arraysize(kT1IpAddresses) + 1;
+
+//-----------------------------------------------------------------------------
+// Query/response set for www.ccs.neu.edu, ID is fixed to 2.
+static const char kT2HostName[] = "www.ccs.neu.edu";
+static const uint16 kT2Qtype = dns_protocol::kTypeA;
+static const char kT2DnsName[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x03, 'c', 'c', 's',
+ 0x03, 'n', 'e', 'u',
+ 0x03, 'e', 'd', 'u',
+ 0x00
+};
+static const size_t kT2QuerySize = 33;
+static const uint8 kT2ResponseDatagram[] = {
+ // response contains one CNAME for vulcan.ccs.neu.edu and the following
+ // IP address: 129.10.116.81
+ 0x00, 0x02, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
+ 0x03, 0x63, 0x63, 0x73, 0x03, 0x6e, 0x65, 0x75,
+ 0x03, 0x65, 0x64, 0x75, 0x00, 0x00, 0x01, 0x00,
+ 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00,
+ 0x00, 0x01, 0x2c, 0x00, 0x09, 0x06, 0x76, 0x75,
+ 0x6c, 0x63, 0x61, 0x6e, 0xc0, 0x10, 0xc0, 0x2d,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2c,
+ 0x00, 0x04, 0x81, 0x0a, 0x74, 0x51
+};
+static const char* const kT2IpAddresses[] = {
+ "129.10.116.81"
+};
+static const char kT2CanonName[] = "vulcan.ccs.neu.edu";
+static const int kT2TTL = 0x0000012c;
+// +1 for the CNAME record.
+static const unsigned kT2RecordCount = arraysize(kT2IpAddresses) + 1;
+
+//-----------------------------------------------------------------------------
+// Query/response set for www.google.az, ID is fixed to 3.
+static const char kT3HostName[] = "www.google.az";
+static const uint16 kT3Qtype = dns_protocol::kTypeA;
+static const char kT3DnsName[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x02, 'a', 'z',
+ 0x00
+};
+static const size_t kT3QuerySize = 31;
+static const uint8 kT3ResponseDatagram[] = {
+ // response contains www.google.com as CNAME for www.google.az and
+ // www.l.google.com as CNAME for www.google.com and the following
+ // IP addresses: 74.125.226.{178,179,180,176,177}
+ // The TTLs on the records are: 0x00015099, 0x00025099, 0x00000415,
+ // 0x00003015, 0x00002015, 0x00000015, 0x00001015.
+ // The last record is an imaginary TXT record for t.google.com.
+ 0x00, 0x03, 0x81, 0x80, 0x00, 0x01, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, 0x77, 0x77,
+ 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x02,
+ 0x61, 0x7a, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0,
+ 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x01, 0x50,
+ 0x99, 0x00, 0x10, 0x03, 0x77, 0x77, 0x77, 0x06,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63,
+ 0x6f, 0x6d, 0x00, 0xc0, 0x2b, 0x00, 0x05, 0x00,
+ 0x01, 0x00, 0x02, 0x50, 0x99, 0x00, 0x08, 0x03,
+ 0x77, 0x77, 0x77, 0x01, 0x6c, 0xc0, 0x2f, 0xc0,
+ 0x47, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x04,
+ 0x15, 0x00, 0x04, 0x4a, 0x7d, 0xe2, 0xb2, 0xc0,
+ 0x47, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x30,
+ 0x15, 0x00, 0x04, 0x4a, 0x7d, 0xe2, 0xb3, 0xc0,
+ 0x47, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x20,
+ 0x15, 0x00, 0x04, 0x4a, 0x7d, 0xe2, 0xb4, 0xc0,
+ 0x47, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x15, 0x00, 0x04, 0x4a, 0x7d, 0xe2, 0xb0, 0xc0,
+ 0x47, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x10,
+ 0x15, 0x00, 0x04, 0x4a, 0x7d, 0xe2, 0xb1, 0x01,
+ 0x74, 0xc0, 0x2f, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x04, 0xde, 0xad, 0xfe,
+ 0xed
+};
+static const char* const kT3IpAddresses[] = {
+ "74.125.226.178", "74.125.226.179", "74.125.226.180",
+ "74.125.226.176", "74.125.226.177"
+};
+static const char kT3CanonName[] = "www.l.google.com";
+static const int kT3TTL = 0x00000015;
+// +2 for the CNAME records, +1 for TXT record.
+static const unsigned kT3RecordCount = arraysize(kT3IpAddresses) + 3;
+
+class DnsClient;
+
+struct MockDnsClientRule {
+ enum Result {
+ FAIL, // Fail asynchronously with ERR_NAME_NOT_RESOLVED.
+ TIMEOUT, // Fail asynchronously with ERR_DNS_TIMEOUT.
+ EMPTY, // Return an empty response.
+ OK, // Return a response with loopback address.
+ };
+
+ MockDnsClientRule(const std::string& prefix_arg,
+ uint16 qtype_arg,
+ Result result_arg)
+ : result(result_arg), prefix(prefix_arg), qtype(qtype_arg) { }
+
+ Result result;
+ std::string prefix;
+ uint16 qtype;
+};
+
+typedef std::vector<MockDnsClientRule> MockDnsClientRuleList;
+
+// Creates mock DnsClient for testing HostResolverImpl.
+scoped_ptr<DnsClient> CreateMockDnsClient(const DnsConfig& config,
+ const MockDnsClientRuleList& rules);
+
+} // namespace net
+
+#endif // NET_DNS_DNS_TEST_UTIL_H_
diff --git a/chromium/net/dns/dns_transaction.cc b/chromium/net/dns/dns_transaction.cc
new file mode 100644
index 00000000000..170ea678d4b
--- /dev/null
+++ b/chromium/net/dns/dns_transaction.cc
@@ -0,0 +1,963 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_transaction.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/timer/timer.h"
+#include "base/values.h"
+#include "net/base/big_endian.h"
+#include "net/base/completion_callback.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_session.h"
+#include "net/socket/stream_socket.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+
+// Provide a common macro to simplify code and readability. We must use a
+// macro as the underlying HISTOGRAM macro creates static variables.
+#define DNS_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES(name, time, \
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100)
+
+// Count labels in the fully-qualified name in DNS format.
+int CountLabels(const std::string& name) {
+ size_t count = 0;
+ for (size_t i = 0; i < name.size() && name[i]; i += name[i] + 1)
+ ++count;
+ return count;
+}
+
+bool IsIPLiteral(const std::string& hostname) {
+ IPAddressNumber ip;
+ return ParseIPLiteralToNumber(hostname, &ip);
+}
+
+base::Value* NetLogStartCallback(const std::string* hostname,
+ uint16 qtype,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("hostname", *hostname);
+ dict->SetInteger("query_type", qtype);
+ return dict;
+};
+
+// ----------------------------------------------------------------------------
+
+// A single asynchronous DNS exchange, which consists of sending out a
+// DNS query, waiting for a response, and returning the response that it
+// matches. Logging is done in the socket and in the outer DnsTransaction.
+class DnsAttempt {
+ public:
+ explicit DnsAttempt(unsigned server_index)
+ : result_(ERR_FAILED), server_index_(server_index) {}
+
+ virtual ~DnsAttempt() {}
+ // Starts the attempt. Returns ERR_IO_PENDING if cannot complete synchronously
+ // and calls |callback| upon completion.
+ virtual int Start(const CompletionCallback& callback) = 0;
+
+ // Returns the query of this attempt.
+ virtual const DnsQuery* GetQuery() const = 0;
+
+ // Returns the response or NULL if has not received a matching response from
+ // the server.
+ virtual const DnsResponse* GetResponse() const = 0;
+
+ // Returns the net log bound to the source of the socket.
+ virtual const BoundNetLog& GetSocketNetLog() const = 0;
+
+ // Returns the index of the destination server within DnsConfig::nameservers.
+ unsigned server_index() const { return server_index_; }
+
+ // Returns a Value representing the received response, along with a reference
+ // to the NetLog source source of the UDP socket used. The request must have
+ // completed before this is called.
+ base::Value* NetLogResponseCallback(NetLog::LogLevel log_level) const {
+ DCHECK(GetResponse()->IsValid());
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("rcode", GetResponse()->rcode());
+ dict->SetInteger("answer_count", GetResponse()->answer_count());
+ GetSocketNetLog().source().AddToEventParameters(dict);
+ return dict;
+ }
+
+ void set_result(int result) {
+ result_ = result;
+ }
+
+ // True if current attempt is pending (waiting for server response).
+ bool is_pending() const {
+ return result_ == ERR_IO_PENDING;
+ }
+
+ // True if attempt is completed (received server response).
+ bool is_completed() const {
+ return (result_ == OK) || (result_ == ERR_NAME_NOT_RESOLVED) ||
+ (result_ == ERR_DNS_SERVER_REQUIRES_TCP);
+ }
+
+ private:
+ // Result of last operation.
+ int result_;
+
+ const unsigned server_index_;
+};
+
+class DnsUDPAttempt : public DnsAttempt {
+ public:
+ DnsUDPAttempt(unsigned server_index,
+ scoped_ptr<DnsSession::SocketLease> socket_lease,
+ scoped_ptr<DnsQuery> query)
+ : DnsAttempt(server_index),
+ next_state_(STATE_NONE),
+ received_malformed_response_(false),
+ socket_lease_(socket_lease.Pass()),
+ query_(query.Pass()) {}
+
+ // DnsAttempt:
+ virtual int Start(const CompletionCallback& callback) OVERRIDE {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ callback_ = callback;
+ start_time_ = base::TimeTicks::Now();
+ next_state_ = STATE_SEND_QUERY;
+ return DoLoop(OK);
+ }
+
+ virtual const DnsQuery* GetQuery() const OVERRIDE {
+ return query_.get();
+ }
+
+ virtual const DnsResponse* GetResponse() const OVERRIDE {
+ const DnsResponse* resp = response_.get();
+ return (resp != NULL && resp->IsValid()) ? resp : NULL;
+ }
+
+ virtual const BoundNetLog& GetSocketNetLog() const OVERRIDE {
+ return socket_lease_->socket()->NetLog();
+ }
+
+ private:
+ enum State {
+ STATE_SEND_QUERY,
+ STATE_SEND_QUERY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_NONE,
+ };
+
+ DatagramClientSocket* socket() {
+ return socket_lease_->socket();
+ }
+
+ int DoLoop(int result) {
+ CHECK_NE(STATE_NONE, next_state_);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_SEND_QUERY:
+ rv = DoSendQuery();
+ break;
+ case STATE_SEND_QUERY_COMPLETE:
+ rv = DoSendQueryComplete(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ rv = DoReadResponse();
+ break;
+ case STATE_READ_RESPONSE_COMPLETE:
+ rv = DoReadResponseComplete(rv);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ set_result(rv);
+ // If we received a malformed response, and are now waiting for another one,
+ // indicate to the transaction that the server might be misbehaving.
+ if (rv == ERR_IO_PENDING && received_malformed_response_)
+ return ERR_DNS_MALFORMED_RESPONSE;
+ if (rv == OK) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DNS_HISTOGRAM("AsyncDNS.UDPAttemptSuccess",
+ base::TimeTicks::Now() - start_time_);
+ } else if (rv != ERR_IO_PENDING) {
+ DNS_HISTOGRAM("AsyncDNS.UDPAttemptFail",
+ base::TimeTicks::Now() - start_time_);
+ }
+ return rv;
+ }
+
+ int DoSendQuery() {
+ next_state_ = STATE_SEND_QUERY_COMPLETE;
+ return socket()->Write(query_->io_buffer(),
+ query_->io_buffer()->size(),
+ base::Bind(&DnsUDPAttempt::OnIOComplete,
+ base::Unretained(this)));
+ }
+
+ int DoSendQueryComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ // Writing to UDP should not result in a partial datagram.
+ if (rv != query_->io_buffer()->size())
+ return ERR_MSG_TOO_BIG;
+
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+ }
+
+ int DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ response_.reset(new DnsResponse());
+ return socket()->Read(response_->io_buffer(),
+ response_->io_buffer()->size(),
+ base::Bind(&DnsUDPAttempt::OnIOComplete,
+ base::Unretained(this)));
+ }
+
+ int DoReadResponseComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ DCHECK(rv);
+ if (!response_->InitParse(rv, *query_)) {
+ // Other implementations simply ignore mismatched responses. Since each
+ // DnsUDPAttempt binds to a different port, we might find that responses
+ // to previously timed out queries lead to failures in the future.
+ // Our solution is to make another attempt, in case the query truly
+ // failed, but keep this attempt alive, in case it was a false alarm.
+ received_malformed_response_ = true;
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+ }
+ if (response_->flags() & dns_protocol::kFlagTC)
+ return ERR_DNS_SERVER_REQUIRES_TCP;
+ // TODO(szym): Extract TTL for NXDOMAIN results. http://crbug.com/115051
+ if (response_->rcode() == dns_protocol::kRcodeNXDOMAIN)
+ return ERR_NAME_NOT_RESOLVED;
+ if (response_->rcode() != dns_protocol::kRcodeNOERROR)
+ return ERR_DNS_SERVER_FAILED;
+
+ return OK;
+ }
+
+ void OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+ if (rv != ERR_IO_PENDING)
+ callback_.Run(rv);
+ }
+
+ State next_state_;
+ bool received_malformed_response_;
+ base::TimeTicks start_time_;
+
+ scoped_ptr<DnsSession::SocketLease> socket_lease_;
+ scoped_ptr<DnsQuery> query_;
+
+ scoped_ptr<DnsResponse> response_;
+
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsUDPAttempt);
+};
+
+class DnsTCPAttempt : public DnsAttempt {
+ public:
+ DnsTCPAttempt(unsigned server_index,
+ scoped_ptr<StreamSocket> socket,
+ scoped_ptr<DnsQuery> query)
+ : DnsAttempt(server_index),
+ next_state_(STATE_NONE),
+ socket_(socket.Pass()),
+ query_(query.Pass()),
+ length_buffer_(new IOBufferWithSize(sizeof(uint16))),
+ response_length_(0) {}
+
+ // DnsAttempt:
+ virtual int Start(const CompletionCallback& callback) OVERRIDE {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ callback_ = callback;
+ start_time_ = base::TimeTicks::Now();
+ next_state_ = STATE_CONNECT_COMPLETE;
+ int rv = socket_->Connect(base::Bind(&DnsTCPAttempt::OnIOComplete,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ set_result(rv);
+ return rv;
+ }
+ return DoLoop(rv);
+ }
+
+ virtual const DnsQuery* GetQuery() const OVERRIDE {
+ return query_.get();
+ }
+
+ virtual const DnsResponse* GetResponse() const OVERRIDE {
+ const DnsResponse* resp = response_.get();
+ return (resp != NULL && resp->IsValid()) ? resp : NULL;
+ }
+
+ virtual const BoundNetLog& GetSocketNetLog() const OVERRIDE {
+ return socket_->NetLog();
+ }
+
+ private:
+ enum State {
+ STATE_CONNECT_COMPLETE,
+ STATE_SEND_LENGTH,
+ STATE_SEND_QUERY,
+ STATE_READ_LENGTH,
+ STATE_READ_RESPONSE,
+ STATE_NONE,
+ };
+
+ int DoLoop(int result) {
+ CHECK_NE(STATE_NONE, next_state_);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ case STATE_SEND_LENGTH:
+ rv = DoSendLength(rv);
+ break;
+ case STATE_SEND_QUERY:
+ rv = DoSendQuery(rv);
+ break;
+ case STATE_READ_LENGTH:
+ rv = DoReadLength(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ rv = DoReadResponse(rv);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ set_result(rv);
+ if (rv == OK) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DNS_HISTOGRAM("AsyncDNS.TCPAttemptSuccess",
+ base::TimeTicks::Now() - start_time_);
+ } else if (rv != ERR_IO_PENDING) {
+ DNS_HISTOGRAM("AsyncDNS.TCPAttemptFail",
+ base::TimeTicks::Now() - start_time_);
+ }
+ return rv;
+ }
+
+ int DoConnectComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ WriteBigEndian<uint16>(length_buffer_->data(), query_->io_buffer()->size());
+ buffer_ =
+ new DrainableIOBuffer(length_buffer_.get(), length_buffer_->size());
+ next_state_ = STATE_SEND_LENGTH;
+ return OK;
+ }
+
+ int DoSendLength(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ buffer_->DidConsume(rv);
+ if (buffer_->BytesRemaining() > 0) {
+ next_state_ = STATE_SEND_LENGTH;
+ return socket_->Write(
+ buffer_.get(),
+ buffer_->BytesRemaining(),
+ base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
+ }
+ buffer_ = new DrainableIOBuffer(query_->io_buffer(),
+ query_->io_buffer()->size());
+ next_state_ = STATE_SEND_QUERY;
+ return OK;
+ }
+
+ int DoSendQuery(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ buffer_->DidConsume(rv);
+ if (buffer_->BytesRemaining() > 0) {
+ next_state_ = STATE_SEND_QUERY;
+ return socket_->Write(
+ buffer_.get(),
+ buffer_->BytesRemaining(),
+ base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
+ }
+ buffer_ =
+ new DrainableIOBuffer(length_buffer_.get(), length_buffer_->size());
+ next_state_ = STATE_READ_LENGTH;
+ return OK;
+ }
+
+ int DoReadLength(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ buffer_->DidConsume(rv);
+ if (buffer_->BytesRemaining() > 0) {
+ next_state_ = STATE_READ_LENGTH;
+ return socket_->Read(
+ buffer_.get(),
+ buffer_->BytesRemaining(),
+ base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
+ }
+ ReadBigEndian<uint16>(length_buffer_->data(), &response_length_);
+ // Check if advertised response is too short. (Optimization only.)
+ if (response_length_ < query_->io_buffer()->size())
+ return ERR_DNS_MALFORMED_RESPONSE;
+ // Allocate more space so that DnsResponse::InitParse sanity check passes.
+ response_.reset(new DnsResponse(response_length_ + 1));
+ buffer_ = new DrainableIOBuffer(response_->io_buffer(), response_length_);
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+ }
+
+ int DoReadResponse(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv < 0)
+ return rv;
+
+ buffer_->DidConsume(rv);
+ if (buffer_->BytesRemaining() > 0) {
+ next_state_ = STATE_READ_RESPONSE;
+ return socket_->Read(
+ buffer_.get(),
+ buffer_->BytesRemaining(),
+ base::Bind(&DnsTCPAttempt::OnIOComplete, base::Unretained(this)));
+ }
+ if (!response_->InitParse(buffer_->BytesConsumed(), *query_))
+ return ERR_DNS_MALFORMED_RESPONSE;
+ if (response_->flags() & dns_protocol::kFlagTC)
+ return ERR_UNEXPECTED;
+ // TODO(szym): Frankly, none of these are expected.
+ if (response_->rcode() == dns_protocol::kRcodeNXDOMAIN)
+ return ERR_NAME_NOT_RESOLVED;
+ if (response_->rcode() != dns_protocol::kRcodeNOERROR)
+ return ERR_DNS_SERVER_FAILED;
+
+ return OK;
+ }
+
+ void OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+ if (rv != ERR_IO_PENDING)
+ callback_.Run(rv);
+ }
+
+ State next_state_;
+ base::TimeTicks start_time_;
+
+ scoped_ptr<StreamSocket> socket_;
+ scoped_ptr<DnsQuery> query_;
+ scoped_refptr<IOBufferWithSize> length_buffer_;
+ scoped_refptr<DrainableIOBuffer> buffer_;
+
+ uint16 response_length_;
+ scoped_ptr<DnsResponse> response_;
+
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsTCPAttempt);
+};
+
+// ----------------------------------------------------------------------------
+
+// Implements DnsTransaction. Configuration is supplied by DnsSession.
+// The suffix list is built according to the DnsConfig from the session.
+// The timeout for each DnsUDPAttempt is given by DnsSession::NextTimeout.
+// The first server to attempt on each query is given by
+// DnsSession::NextFirstServerIndex, and the order is round-robin afterwards.
+// Each server is attempted DnsConfig::attempts times.
+class DnsTransactionImpl : public DnsTransaction,
+ public base::NonThreadSafe,
+ public base::SupportsWeakPtr<DnsTransactionImpl> {
+ public:
+ DnsTransactionImpl(DnsSession* session,
+ const std::string& hostname,
+ uint16 qtype,
+ const DnsTransactionFactory::CallbackType& callback,
+ const BoundNetLog& net_log)
+ : session_(session),
+ hostname_(hostname),
+ qtype_(qtype),
+ callback_(callback),
+ net_log_(net_log),
+ qnames_initial_size_(0),
+ attempts_count_(0),
+ had_tcp_attempt_(false),
+ first_server_index_(0) {
+ DCHECK(session_.get());
+ DCHECK(!hostname_.empty());
+ DCHECK(!callback_.is_null());
+ DCHECK(!IsIPLiteral(hostname_));
+ }
+
+ virtual ~DnsTransactionImpl() {
+ if (!callback_.is_null()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION,
+ ERR_ABORTED);
+ } // otherwise logged in DoCallback or Start
+ }
+
+ virtual const std::string& GetHostname() const OVERRIDE {
+ DCHECK(CalledOnValidThread());
+ return hostname_;
+ }
+
+ virtual uint16 GetType() const OVERRIDE {
+ DCHECK(CalledOnValidThread());
+ return qtype_;
+ }
+
+ virtual void Start() OVERRIDE {
+ DCHECK(!callback_.is_null());
+ DCHECK(attempts_.empty());
+ net_log_.BeginEvent(NetLog::TYPE_DNS_TRANSACTION,
+ base::Bind(&NetLogStartCallback, &hostname_, qtype_));
+ AttemptResult result(PrepareSearch(), NULL);
+ if (result.rv == OK) {
+ qnames_initial_size_ = qnames_.size();
+ if (qtype_ == dns_protocol::kTypeA)
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.SuffixSearchStart", qnames_.size());
+ result = ProcessAttemptResult(StartQuery());
+ }
+
+ // Must always return result asynchronously, to avoid reentrancy.
+ if (result.rv != ERR_IO_PENDING) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&DnsTransactionImpl::DoCallback, AsWeakPtr(), result));
+ }
+ }
+
+ private:
+ // Wrapper for the result of a DnsUDPAttempt.
+ struct AttemptResult {
+ AttemptResult(int rv, const DnsAttempt* attempt)
+ : rv(rv), attempt(attempt) {}
+
+ int rv;
+ const DnsAttempt* attempt;
+ };
+
+ // Prepares |qnames_| according to the DnsConfig.
+ int PrepareSearch() {
+ const DnsConfig& config = session_->config();
+
+ std::string labeled_hostname;
+ if (!DNSDomainFromDot(hostname_, &labeled_hostname))
+ return ERR_INVALID_ARGUMENT;
+
+ if (hostname_[hostname_.size() - 1] == '.') {
+ // It's a fully-qualified name, no suffix search.
+ qnames_.push_back(labeled_hostname);
+ return OK;
+ }
+
+ int ndots = CountLabels(labeled_hostname) - 1;
+
+ if (ndots > 0 && !config.append_to_multi_label_name) {
+ qnames_.push_back(labeled_hostname);
+ return OK;
+ }
+
+ // Set true when |labeled_hostname| is put on the list.
+ bool had_hostname = false;
+
+ if (ndots >= config.ndots) {
+ qnames_.push_back(labeled_hostname);
+ had_hostname = true;
+ }
+
+ std::string qname;
+ for (size_t i = 0; i < config.search.size(); ++i) {
+ // Ignore invalid (too long) combinations.
+ if (!DNSDomainFromDot(hostname_ + "." + config.search[i], &qname))
+ continue;
+ if (qname.size() == labeled_hostname.size()) {
+ if (had_hostname)
+ continue;
+ had_hostname = true;
+ }
+ qnames_.push_back(qname);
+ }
+
+ if (ndots > 0 && !had_hostname)
+ qnames_.push_back(labeled_hostname);
+
+ return qnames_.empty() ? ERR_DNS_SEARCH_EMPTY : OK;
+ }
+
+ void DoCallback(AttemptResult result) {
+ DCHECK(!callback_.is_null());
+ DCHECK_NE(ERR_IO_PENDING, result.rv);
+ const DnsResponse* response = result.attempt ?
+ result.attempt->GetResponse() : NULL;
+ CHECK(result.rv != OK || response != NULL);
+
+ timer_.Stop();
+ RecordLostPacketsIfAny();
+ if (result.rv == OK)
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.AttemptCountSuccess", attempts_count_);
+ else
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.AttemptCountFail", attempts_count_);
+
+ if (response && qtype_ == dns_protocol::kTypeA) {
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.SuffixSearchRemain", qnames_.size());
+ UMA_HISTOGRAM_COUNTS("AsyncDNS.SuffixSearchDone",
+ qnames_initial_size_ - qnames_.size());
+ }
+
+ DnsTransactionFactory::CallbackType callback = callback_;
+ callback_.Reset();
+
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION, result.rv);
+ callback.Run(this, result.rv, response);
+ }
+
+ // Makes another attempt at the current name, |qnames_.front()|, using the
+ // next nameserver.
+ AttemptResult MakeAttempt() {
+ unsigned attempt_number = attempts_.size();
+
+ uint16 id = session_->NextQueryId();
+ scoped_ptr<DnsQuery> query;
+ if (attempts_.empty()) {
+ query.reset(new DnsQuery(id, qnames_.front(), qtype_));
+ } else {
+ query.reset(attempts_[0]->GetQuery()->CloneWithNewId(id));
+ }
+
+ const DnsConfig& config = session_->config();
+
+ unsigned server_index =
+ (first_server_index_ + attempt_number) % config.nameservers.size();
+ // Skip over known failed servers.
+ server_index = session_->NextGoodServerIndex(server_index);
+
+ scoped_ptr<DnsSession::SocketLease> lease =
+ session_->AllocateSocket(server_index, net_log_.source());
+
+ bool got_socket = !!lease.get();
+
+ DnsUDPAttempt* attempt =
+ new DnsUDPAttempt(server_index, lease.Pass(), query.Pass());
+
+ attempts_.push_back(attempt);
+ ++attempts_count_;
+
+ if (!got_socket)
+ return AttemptResult(ERR_CONNECTION_REFUSED, NULL);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_DNS_TRANSACTION_ATTEMPT,
+ attempt->GetSocketNetLog().source().ToEventParametersCallback());
+
+ int rv = attempt->Start(
+ base::Bind(&DnsTransactionImpl::OnUdpAttemptComplete,
+ base::Unretained(this), attempt_number,
+ base::TimeTicks::Now()));
+ if (rv == ERR_IO_PENDING) {
+ base::TimeDelta timeout = session_->NextTimeout(server_index,
+ attempt_number);
+ timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
+ }
+ return AttemptResult(rv, attempt);
+ }
+
+ AttemptResult MakeTCPAttempt(const DnsAttempt* previous_attempt) {
+ DCHECK(previous_attempt);
+ DCHECK(!had_tcp_attempt_);
+
+ unsigned server_index = previous_attempt->server_index();
+
+ scoped_ptr<StreamSocket> socket(
+ session_->CreateTCPSocket(server_index, net_log_.source()));
+
+ // TODO(szym): Reuse the same id to help the server?
+ uint16 id = session_->NextQueryId();
+ scoped_ptr<DnsQuery> query(
+ previous_attempt->GetQuery()->CloneWithNewId(id));
+
+ RecordLostPacketsIfAny();
+ // Cancel all other attempts, no point waiting on them.
+ attempts_.clear();
+
+ unsigned attempt_number = attempts_.size();
+
+ DnsTCPAttempt* attempt = new DnsTCPAttempt(server_index, socket.Pass(),
+ query.Pass());
+
+ attempts_.push_back(attempt);
+ ++attempts_count_;
+ had_tcp_attempt_ = true;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_DNS_TRANSACTION_TCP_ATTEMPT,
+ attempt->GetSocketNetLog().source().ToEventParametersCallback());
+
+ int rv = attempt->Start(base::Bind(&DnsTransactionImpl::OnAttemptComplete,
+ base::Unretained(this),
+ attempt_number));
+ if (rv == ERR_IO_PENDING) {
+ // Custom timeout for TCP attempt.
+ base::TimeDelta timeout = timer_.GetCurrentDelay() * 2;
+ timer_.Start(FROM_HERE, timeout, this, &DnsTransactionImpl::OnTimeout);
+ }
+ return AttemptResult(rv, attempt);
+ }
+
+ // Begins query for the current name. Makes the first attempt.
+ AttemptResult StartQuery() {
+ std::string dotted_qname = DNSDomainToString(qnames_.front());
+ net_log_.BeginEvent(NetLog::TYPE_DNS_TRANSACTION_QUERY,
+ NetLog::StringCallback("qname", &dotted_qname));
+
+ first_server_index_ = session_->NextFirstServerIndex();
+ RecordLostPacketsIfAny();
+ attempts_.clear();
+ had_tcp_attempt_ = false;
+ return MakeAttempt();
+ }
+
+ void OnUdpAttemptComplete(unsigned attempt_number,
+ base::TimeTicks start,
+ int rv) {
+ DCHECK_LT(attempt_number, attempts_.size());
+ const DnsAttempt* attempt = attempts_[attempt_number];
+ if (attempt->GetResponse()) {
+ session_->RecordRTT(attempt->server_index(),
+ base::TimeTicks::Now() - start);
+ }
+ OnAttemptComplete(attempt_number, rv);
+ }
+
+ void OnAttemptComplete(unsigned attempt_number, int rv) {
+ if (callback_.is_null())
+ return;
+ DCHECK_LT(attempt_number, attempts_.size());
+ const DnsAttempt* attempt = attempts_[attempt_number];
+ AttemptResult result = ProcessAttemptResult(AttemptResult(rv, attempt));
+ if (result.rv != ERR_IO_PENDING)
+ DoCallback(result);
+ }
+
+ // Record packet loss for any incomplete attempts.
+ void RecordLostPacketsIfAny() {
+ // Loop through attempts until we find first that is completed
+ size_t first_completed = 0;
+ for (first_completed = 0; first_completed < attempts_.size();
+ ++first_completed) {
+ if (attempts_[first_completed]->is_completed())
+ break;
+ }
+ // If there were no completed attempts, then we must be offline, so don't
+ // record any attempts as lost packets.
+ if (first_completed == attempts_.size())
+ return;
+
+ size_t num_servers = session_->config().nameservers.size();
+ std::vector<int> server_attempts(num_servers);
+ for (size_t i = 0; i < first_completed; ++i) {
+ unsigned server_index = attempts_[i]->server_index();
+ int server_attempt = server_attempts[server_index]++;
+ // Don't record lost packet unless attempt is in pending state.
+ if (!attempts_[i]->is_pending())
+ continue;
+ session_->RecordLostPacket(server_index, server_attempt);
+ }
+ }
+
+ void LogResponse(const DnsAttempt* attempt) {
+ if (attempt && attempt->GetResponse()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_DNS_TRANSACTION_RESPONSE,
+ base::Bind(&DnsAttempt::NetLogResponseCallback,
+ base::Unretained(attempt)));
+ }
+ }
+
+ bool MoreAttemptsAllowed() const {
+ if (had_tcp_attempt_)
+ return false;
+ const DnsConfig& config = session_->config();
+ return attempts_.size() < config.attempts * config.nameservers.size();
+ }
+
+ // Resolves the result of a DnsAttempt until a terminal result is reached
+ // or it will complete asynchronously (ERR_IO_PENDING).
+ AttemptResult ProcessAttemptResult(AttemptResult result) {
+ while (result.rv != ERR_IO_PENDING) {
+ LogResponse(result.attempt);
+
+ switch (result.rv) {
+ case OK:
+ session_->RecordServerSuccess(result.attempt->server_index());
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION_QUERY,
+ result.rv);
+ DCHECK(result.attempt);
+ DCHECK(result.attempt->GetResponse());
+ return result;
+ case ERR_NAME_NOT_RESOLVED:
+ session_->RecordServerSuccess(result.attempt->server_index());
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_DNS_TRANSACTION_QUERY,
+ result.rv);
+ // Try next suffix.
+ qnames_.pop_front();
+ if (qnames_.empty()) {
+ return AttemptResult(ERR_NAME_NOT_RESOLVED, NULL);
+ } else {
+ result = StartQuery();
+ }
+ break;
+ case ERR_CONNECTION_REFUSED:
+ case ERR_DNS_TIMED_OUT:
+ if (result.attempt)
+ session_->RecordServerFailure(result.attempt->server_index());
+ if (MoreAttemptsAllowed()) {
+ result = MakeAttempt();
+ } else {
+ return result;
+ }
+ break;
+ case ERR_DNS_SERVER_REQUIRES_TCP:
+ result = MakeTCPAttempt(result.attempt);
+ break;
+ default:
+ // Server failure.
+ DCHECK(result.attempt);
+ if (result.attempt != attempts_.back()) {
+ // This attempt already timed out. Ignore it.
+ session_->RecordServerFailure(result.attempt->server_index());
+ return AttemptResult(ERR_IO_PENDING, NULL);
+ }
+ if (MoreAttemptsAllowed()) {
+ result = MakeAttempt();
+ } else if (result.rv == ERR_DNS_MALFORMED_RESPONSE &&
+ !had_tcp_attempt_) {
+ // For UDP only, ignore the response and wait until the last attempt
+ // times out.
+ return AttemptResult(ERR_IO_PENDING, NULL);
+ } else {
+ return AttemptResult(result.rv, NULL);
+ }
+ break;
+ }
+ }
+ return result;
+ }
+
+ void OnTimeout() {
+ if (callback_.is_null())
+ return;
+ DCHECK(!attempts_.empty());
+ AttemptResult result = ProcessAttemptResult(
+ AttemptResult(ERR_DNS_TIMED_OUT, attempts_.back()));
+ if (result.rv != ERR_IO_PENDING)
+ DoCallback(result);
+ }
+
+ scoped_refptr<DnsSession> session_;
+ std::string hostname_;
+ uint16 qtype_;
+ // Cleared in DoCallback.
+ DnsTransactionFactory::CallbackType callback_;
+
+ BoundNetLog net_log_;
+
+ // Search list of fully-qualified DNS names to query next (in DNS format).
+ std::deque<std::string> qnames_;
+ size_t qnames_initial_size_;
+
+ // List of attempts for the current name.
+ ScopedVector<DnsAttempt> attempts_;
+ // Count of attempts, not reset when |attempts_| vector is cleared.
+ int attempts_count_;
+ bool had_tcp_attempt_;
+
+ // Index of the first server to try on each search query.
+ int first_server_index_;
+
+ base::OneShotTimer<DnsTransactionImpl> timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsTransactionImpl);
+};
+
+// ----------------------------------------------------------------------------
+
+// Implementation of DnsTransactionFactory that returns instances of
+// DnsTransactionImpl.
+class DnsTransactionFactoryImpl : public DnsTransactionFactory {
+ public:
+ explicit DnsTransactionFactoryImpl(DnsSession* session) {
+ session_ = session;
+ }
+
+ virtual scoped_ptr<DnsTransaction> CreateTransaction(
+ const std::string& hostname,
+ uint16 qtype,
+ const CallbackType& callback,
+ const BoundNetLog& net_log) OVERRIDE {
+ return scoped_ptr<DnsTransaction>(new DnsTransactionImpl(
+ session_.get(), hostname, qtype, callback, net_log));
+ }
+
+ private:
+ scoped_refptr<DnsSession> session_;
+};
+
+} // namespace
+
+// static
+scoped_ptr<DnsTransactionFactory> DnsTransactionFactory::CreateFactory(
+ DnsSession* session) {
+ return scoped_ptr<DnsTransactionFactory>(
+ new DnsTransactionFactoryImpl(session));
+}
+
+} // namespace net
diff --git a/chromium/net/dns/dns_transaction.h b/chromium/net/dns/dns_transaction.h
new file mode 100644
index 00000000000..faf4f64e79d
--- /dev/null
+++ b/chromium/net/dns/dns_transaction.h
@@ -0,0 +1,78 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_DNS_TRANSACTION_H_
+#define NET_DNS_DNS_TRANSACTION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class BoundNetLog;
+class DnsResponse;
+class DnsSession;
+
+// DnsTransaction implements a stub DNS resolver as defined in RFC 1034.
+// The DnsTransaction takes care of retransmissions, name server fallback (or
+// round-robin), suffix search, and simple response validation ("does it match
+// the query") to fight poisoning.
+//
+// Destroying DnsTransaction cancels the underlying network effort.
+class NET_EXPORT_PRIVATE DnsTransaction {
+ public:
+ virtual ~DnsTransaction() {}
+
+ // Returns the original |hostname|.
+ virtual const std::string& GetHostname() const = 0;
+
+ // Returns the |qtype|.
+ virtual uint16 GetType() const = 0;
+
+ // Starts the transaction. Always completes asynchronously.
+ virtual void Start() = 0;
+};
+
+// Creates DnsTransaction which performs asynchronous DNS search.
+// It does NOT perform caching, aggregation or prioritization of transactions.
+//
+// Destroying the factory does NOT affect any already created DnsTransactions.
+class NET_EXPORT_PRIVATE DnsTransactionFactory {
+ public:
+ // Called with the response or NULL if no matching response was received.
+ // Note that the |GetDottedName()| of the response may be different than the
+ // original |hostname| as a result of suffix search.
+ typedef base::Callback<void(DnsTransaction* transaction,
+ int neterror,
+ const DnsResponse* response)> CallbackType;
+
+ virtual ~DnsTransactionFactory() {}
+
+ // Creates DnsTransaction for the given |hostname| and |qtype| (assuming
+ // QCLASS is IN). |hostname| should be in the dotted form. A dot at the end
+ // implies the domain name is fully-qualified and will be exempt from suffix
+ // search. |hostname| should not be an IP literal.
+ //
+ // The transaction will run |callback| upon asynchronous completion.
+ // The |net_log| is used as the parent log.
+ virtual scoped_ptr<DnsTransaction> CreateTransaction(
+ const std::string& hostname,
+ uint16 qtype,
+ const CallbackType& callback,
+ const BoundNetLog& net_log) WARN_UNUSED_RESULT = 0;
+
+ // Creates a DnsTransactionFactory which creates DnsTransactionImpl using the
+ // |session|.
+ static scoped_ptr<DnsTransactionFactory> CreateFactory(
+ DnsSession* session) WARN_UNUSED_RESULT;
+};
+
+} // namespace net
+
+#endif // NET_DNS_DNS_TRANSACTION_H_
+
diff --git a/chromium/net/dns/dns_transaction_unittest.cc b/chromium/net/dns/dns_transaction_unittest.cc
new file mode 100644
index 00000000000..7040e44be16
--- /dev/null
+++ b/chromium/net/dns/dns_transaction_unittest.cc
@@ -0,0 +1,940 @@
+// Copyright (c) 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.
+
+#include "net/dns/dns_transaction.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/rand_util.h"
+#include "base/sys_byteorder.h"
+#include "base/test/test_timeouts.h"
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/base/net_log.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_session.h"
+#include "net/dns/dns_test_util.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+std::string DomainFromDot(const base::StringPiece& dotted) {
+ std::string out;
+ EXPECT_TRUE(DNSDomainFromDot(dotted, &out));
+ return out;
+}
+
+// A SocketDataProvider builder.
+class DnsSocketData {
+ public:
+ // The ctor takes parameters for the DnsQuery.
+ DnsSocketData(uint16 id,
+ const char* dotted_name,
+ uint16 qtype,
+ IoMode mode,
+ bool use_tcp)
+ : query_(new DnsQuery(id, DomainFromDot(dotted_name), qtype)),
+ use_tcp_(use_tcp) {
+ if (use_tcp_) {
+ scoped_ptr<uint16> length(new uint16);
+ *length = base::HostToNet16(query_->io_buffer()->size());
+ writes_.push_back(MockWrite(mode,
+ reinterpret_cast<const char*>(length.get()),
+ sizeof(uint16)));
+ lengths_.push_back(length.release());
+ }
+ writes_.push_back(MockWrite(mode,
+ query_->io_buffer()->data(),
+ query_->io_buffer()->size()));
+ }
+ ~DnsSocketData() {}
+
+ // All responses must be added before GetProvider.
+
+ // Adds pre-built DnsResponse. |tcp_length| will be used in TCP mode only.
+ void AddResponseWithLength(scoped_ptr<DnsResponse> response, IoMode mode,
+ uint16 tcp_length) {
+ CHECK(!provider_.get());
+ if (use_tcp_) {
+ scoped_ptr<uint16> length(new uint16);
+ *length = base::HostToNet16(tcp_length);
+ reads_.push_back(MockRead(mode,
+ reinterpret_cast<const char*>(length.get()),
+ sizeof(uint16)));
+ lengths_.push_back(length.release());
+ }
+ reads_.push_back(MockRead(mode,
+ response->io_buffer()->data(),
+ response->io_buffer()->size()));
+ responses_.push_back(response.release());
+ }
+
+ // Adds pre-built DnsResponse.
+ void AddResponse(scoped_ptr<DnsResponse> response, IoMode mode) {
+ uint16 tcp_length = response->io_buffer()->size();
+ AddResponseWithLength(response.Pass(), mode, tcp_length);
+ }
+
+ // Adds pre-built response from |data| buffer.
+ void AddResponseData(const uint8* data, size_t length, IoMode mode) {
+ CHECK(!provider_.get());
+ AddResponse(make_scoped_ptr(
+ new DnsResponse(reinterpret_cast<const char*>(data), length, 0)), mode);
+ }
+
+ // Add no-answer (RCODE only) response matching the query.
+ void AddRcode(int rcode, IoMode mode) {
+ scoped_ptr<DnsResponse> response(
+ new DnsResponse(query_->io_buffer()->data(),
+ query_->io_buffer()->size(),
+ 0));
+ dns_protocol::Header* header =
+ reinterpret_cast<dns_protocol::Header*>(response->io_buffer()->data());
+ header->flags |= base::HostToNet16(dns_protocol::kFlagResponse | rcode);
+ AddResponse(response.Pass(), mode);
+ }
+
+ // Build, if needed, and return the SocketDataProvider. No new responses
+ // should be added afterwards.
+ SocketDataProvider* GetProvider() {
+ if (provider_.get())
+ return provider_.get();
+ // Terminate the reads with ERR_IO_PENDING to prevent overrun and default to
+ // timeout.
+ reads_.push_back(MockRead(ASYNC, ERR_IO_PENDING));
+ provider_.reset(new DelayedSocketData(1, &reads_[0], reads_.size(),
+ &writes_[0], writes_.size()));
+ if (use_tcp_) {
+ provider_->set_connect_data(MockConnect(reads_[0].mode, OK));
+ }
+ return provider_.get();
+ }
+
+ uint16 query_id() const {
+ return query_->id();
+ }
+
+ // Returns true if the expected query was written to the socket.
+ bool was_written() const {
+ CHECK(provider_.get());
+ return provider_->write_index() > 0;
+ }
+
+ private:
+ scoped_ptr<DnsQuery> query_;
+ bool use_tcp_;
+ ScopedVector<uint16> lengths_;
+ ScopedVector<DnsResponse> responses_;
+ std::vector<MockWrite> writes_;
+ std::vector<MockRead> reads_;
+ scoped_ptr<DelayedSocketData> provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsSocketData);
+};
+
+class TestSocketFactory;
+
+// A variant of MockUDPClientSocket which always fails to Connect.
+class FailingUDPClientSocket : public MockUDPClientSocket {
+ public:
+ FailingUDPClientSocket(SocketDataProvider* data,
+ net::NetLog* net_log)
+ : MockUDPClientSocket(data, net_log) {
+ }
+ virtual ~FailingUDPClientSocket() {}
+ virtual int Connect(const IPEndPoint& endpoint) OVERRIDE {
+ return ERR_CONNECTION_REFUSED;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FailingUDPClientSocket);
+};
+
+// A variant of MockUDPClientSocket which notifies the factory OnConnect.
+class TestUDPClientSocket : public MockUDPClientSocket {
+ public:
+ TestUDPClientSocket(TestSocketFactory* factory,
+ SocketDataProvider* data,
+ net::NetLog* net_log)
+ : MockUDPClientSocket(data, net_log), factory_(factory) {
+ }
+ virtual ~TestUDPClientSocket() {}
+ virtual int Connect(const IPEndPoint& endpoint) OVERRIDE;
+
+ private:
+ TestSocketFactory* factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestUDPClientSocket);
+};
+
+// Creates TestUDPClientSockets and keeps endpoints reported via OnConnect.
+class TestSocketFactory : public MockClientSocketFactory {
+ public:
+ TestSocketFactory() : fail_next_socket_(false) {}
+ virtual ~TestSocketFactory() {}
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) OVERRIDE {
+ if (fail_next_socket_) {
+ fail_next_socket_ = false;
+ return scoped_ptr<DatagramClientSocket>(
+ new FailingUDPClientSocket(&empty_data_, net_log));
+ }
+ SocketDataProvider* data_provider = mock_data().GetNext();
+ scoped_ptr<TestUDPClientSocket> socket(
+ new TestUDPClientSocket(this, data_provider, net_log));
+ data_provider->set_socket(socket.get());
+ return socket.PassAs<DatagramClientSocket>();
+ }
+
+ void OnConnect(const IPEndPoint& endpoint) {
+ remote_endpoints_.push_back(endpoint);
+ }
+
+ std::vector<IPEndPoint> remote_endpoints_;
+ bool fail_next_socket_;
+
+ private:
+ StaticSocketDataProvider empty_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSocketFactory);
+};
+
+int TestUDPClientSocket::Connect(const IPEndPoint& endpoint) {
+ factory_->OnConnect(endpoint);
+ return MockUDPClientSocket::Connect(endpoint);
+}
+
+// Helper class that holds a DnsTransaction and handles OnTransactionComplete.
+class TransactionHelper {
+ public:
+ // If |expected_answer_count| < 0 then it is the expected net error.
+ TransactionHelper(const char* hostname,
+ uint16 qtype,
+ int expected_answer_count)
+ : hostname_(hostname),
+ qtype_(qtype),
+ expected_answer_count_(expected_answer_count),
+ cancel_in_callback_(false),
+ quit_in_callback_(false),
+ completed_(false) {
+ }
+
+ // Mark that the transaction shall be destroyed immediately upon callback.
+ void set_cancel_in_callback() {
+ cancel_in_callback_ = true;
+ }
+
+ // Mark to call MessageLoop::Quit() upon callback.
+ void set_quit_in_callback() {
+ quit_in_callback_ = true;
+ }
+
+ void StartTransaction(DnsTransactionFactory* factory) {
+ EXPECT_EQ(NULL, transaction_.get());
+ transaction_ = factory->CreateTransaction(
+ hostname_,
+ qtype_,
+ base::Bind(&TransactionHelper::OnTransactionComplete,
+ base::Unretained(this)),
+ BoundNetLog());
+ EXPECT_EQ(hostname_, transaction_->GetHostname());
+ EXPECT_EQ(qtype_, transaction_->GetType());
+ transaction_->Start();
+ }
+
+ void Cancel() {
+ ASSERT_TRUE(transaction_.get() != NULL);
+ transaction_.reset(NULL);
+ }
+
+ void OnTransactionComplete(DnsTransaction* t,
+ int rv,
+ const DnsResponse* response) {
+ EXPECT_FALSE(completed_);
+ EXPECT_EQ(transaction_.get(), t);
+
+ completed_ = true;
+
+ if (cancel_in_callback_) {
+ Cancel();
+ return;
+ }
+
+ // Tell MessageLoop to quit now, in case any ASSERT_* fails.
+ if (quit_in_callback_)
+ base::MessageLoop::current()->Quit();
+
+ if (expected_answer_count_ >= 0) {
+ ASSERT_EQ(OK, rv);
+ ASSERT_TRUE(response != NULL);
+ EXPECT_EQ(static_cast<unsigned>(expected_answer_count_),
+ response->answer_count());
+ EXPECT_EQ(qtype_, response->qtype());
+
+ DnsRecordParser parser = response->Parser();
+ DnsResourceRecord record;
+ for (int i = 0; i < expected_answer_count_; ++i) {
+ EXPECT_TRUE(parser.ReadRecord(&record));
+ }
+ } else {
+ EXPECT_EQ(expected_answer_count_, rv);
+ }
+ }
+
+ bool has_completed() const {
+ return completed_;
+ }
+
+ // Shorthands for commonly used commands.
+
+ bool Run(DnsTransactionFactory* factory) {
+ StartTransaction(factory);
+ base::MessageLoop::current()->RunUntilIdle();
+ return has_completed();
+ }
+
+ // Use when some of the responses are timeouts.
+ bool RunUntilDone(DnsTransactionFactory* factory) {
+ set_quit_in_callback();
+ StartTransaction(factory);
+ base::MessageLoop::current()->Run();
+ return has_completed();
+ }
+
+ private:
+ std::string hostname_;
+ uint16 qtype_;
+ scoped_ptr<DnsTransaction> transaction_;
+ int expected_answer_count_;
+ bool cancel_in_callback_;
+ bool quit_in_callback_;
+
+ bool completed_;
+};
+
+class DnsTransactionTest : public testing::Test {
+ public:
+ DnsTransactionTest() {}
+
+ // Generates |nameservers| for DnsConfig.
+ void ConfigureNumServers(unsigned num_servers) {
+ CHECK_LE(num_servers, 255u);
+ config_.nameservers.clear();
+ IPAddressNumber dns_ip;
+ {
+ bool rv = ParseIPLiteralToNumber("192.168.1.0", &dns_ip);
+ EXPECT_TRUE(rv);
+ }
+ for (unsigned i = 0; i < num_servers; ++i) {
+ dns_ip[3] = i;
+ config_.nameservers.push_back(IPEndPoint(dns_ip,
+ dns_protocol::kDefaultPort));
+ }
+ }
+
+ // Called after fully configuring |config|.
+ void ConfigureFactory() {
+ socket_factory_.reset(new TestSocketFactory());
+ session_ = new DnsSession(
+ config_,
+ DnsSocketPool::CreateNull(socket_factory_.get()),
+ base::Bind(&DnsTransactionTest::GetNextId, base::Unretained(this)),
+ NULL /* NetLog */);
+ transaction_factory_ = DnsTransactionFactory::CreateFactory(session_.get());
+ }
+
+ void AddSocketData(scoped_ptr<DnsSocketData> data) {
+ CHECK(socket_factory_.get());
+ transaction_ids_.push_back(data->query_id());
+ socket_factory_->AddSocketDataProvider(data->GetProvider());
+ socket_data_.push_back(data.release());
+ }
+
+ // Add expected query for |dotted_name| and |qtype| with |id| and response
+ // taken verbatim from |data| of |data_length| bytes. The transaction id in
+ // |data| should equal |id|, unless testing mismatched response.
+ void AddQueryAndResponse(uint16 id,
+ const char* dotted_name,
+ uint16 qtype,
+ const uint8* response_data,
+ size_t response_length,
+ IoMode mode,
+ bool use_tcp) {
+ CHECK(socket_factory_.get());
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(id, dotted_name, qtype, mode, use_tcp));
+ data->AddResponseData(response_data, response_length, mode);
+ AddSocketData(data.Pass());
+ }
+
+ void AddAsyncQueryAndResponse(uint16 id,
+ const char* dotted_name,
+ uint16 qtype,
+ const uint8* data,
+ size_t data_length) {
+ AddQueryAndResponse(id, dotted_name, qtype, data, data_length, ASYNC,
+ false);
+ }
+
+ void AddSyncQueryAndResponse(uint16 id,
+ const char* dotted_name,
+ uint16 qtype,
+ const uint8* data,
+ size_t data_length) {
+ AddQueryAndResponse(id, dotted_name, qtype, data, data_length, SYNCHRONOUS,
+ false);
+ }
+
+ // Add expected query of |dotted_name| and |qtype| and no response.
+ void AddQueryAndTimeout(const char* dotted_name, uint16 qtype) {
+ uint16 id = base::RandInt(0, kuint16max);
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(id, dotted_name, qtype, ASYNC, false));
+ AddSocketData(data.Pass());
+ }
+
+ // Add expected query of |dotted_name| and |qtype| and matching response with
+ // no answer and RCODE set to |rcode|. The id will be generated randomly.
+ void AddQueryAndRcode(const char* dotted_name,
+ uint16 qtype,
+ int rcode,
+ IoMode mode,
+ bool use_tcp) {
+ CHECK_NE(dns_protocol::kRcodeNOERROR, rcode);
+ uint16 id = base::RandInt(0, kuint16max);
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(id, dotted_name, qtype, mode, use_tcp));
+ data->AddRcode(rcode, mode);
+ AddSocketData(data.Pass());
+ }
+
+ void AddAsyncQueryAndRcode(const char* dotted_name, uint16 qtype, int rcode) {
+ AddQueryAndRcode(dotted_name, qtype, rcode, ASYNC, false);
+ }
+
+ void AddSyncQueryAndRcode(const char* dotted_name, uint16 qtype, int rcode) {
+ AddQueryAndRcode(dotted_name, qtype, rcode, SYNCHRONOUS, false);
+ }
+
+ // Checks if the sockets were connected in the order matching the indices in
+ // |servers|.
+ void CheckServerOrder(const unsigned* servers, size_t num_attempts) {
+ ASSERT_EQ(num_attempts, socket_factory_->remote_endpoints_.size());
+ for (size_t i = 0; i < num_attempts; ++i) {
+ EXPECT_EQ(socket_factory_->remote_endpoints_[i],
+ session_->config().nameservers[servers[i]]);
+ }
+ }
+
+ virtual void SetUp() OVERRIDE {
+ // By default set one server,
+ ConfigureNumServers(1);
+ // and no retransmissions,
+ config_.attempts = 1;
+ // but long enough timeout for memory tests.
+ config_.timeout = TestTimeouts::action_timeout();
+ ConfigureFactory();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Check that all socket data was at least written to.
+ for (size_t i = 0; i < socket_data_.size(); ++i) {
+ EXPECT_TRUE(socket_data_[i]->was_written()) << i;
+ }
+ }
+
+ protected:
+ int GetNextId(int min, int max) {
+ EXPECT_FALSE(transaction_ids_.empty());
+ int id = transaction_ids_.front();
+ transaction_ids_.pop_front();
+ EXPECT_GE(id, min);
+ EXPECT_LE(id, max);
+ return id;
+ }
+
+ DnsConfig config_;
+
+ ScopedVector<DnsSocketData> socket_data_;
+
+ std::deque<int> transaction_ids_;
+ scoped_ptr<TestSocketFactory> socket_factory_;
+ scoped_refptr<DnsSession> session_;
+ scoped_ptr<DnsTransactionFactory> transaction_factory_;
+};
+
+TEST_F(DnsTransactionTest, Lookup) {
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+// Concurrent lookup tests assume that DnsTransaction::Start immediately
+// consumes a socket from ClientSocketFactory.
+TEST_F(DnsTransactionTest, ConcurrentLookup) {
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+ AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
+ kT1ResponseDatagram, arraysize(kT1ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ helper0.StartTransaction(transaction_factory_.get());
+ TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount);
+ helper1.StartTransaction(transaction_factory_.get());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_TRUE(helper0.has_completed());
+ EXPECT_TRUE(helper1.has_completed());
+}
+
+TEST_F(DnsTransactionTest, CancelLookup) {
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+ AddAsyncQueryAndResponse(1 /* id */, kT1HostName, kT1Qtype,
+ kT1ResponseDatagram, arraysize(kT1ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ helper0.StartTransaction(transaction_factory_.get());
+ TransactionHelper helper1(kT1HostName, kT1Qtype, kT1RecordCount);
+ helper1.StartTransaction(transaction_factory_.get());
+
+ helper0.Cancel();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(helper0.has_completed());
+ EXPECT_TRUE(helper1.has_completed());
+}
+
+TEST_F(DnsTransactionTest, DestroyFactory) {
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ helper0.StartTransaction(transaction_factory_.get());
+
+ // Destroying the client does not affect running requests.
+ transaction_factory_.reset(NULL);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_TRUE(helper0.has_completed());
+}
+
+TEST_F(DnsTransactionTest, CancelFromCallback) {
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ helper0.set_cancel_in_callback();
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, MismatchedResponseSync) {
+ config_.attempts = 2;
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ // Attempt receives mismatched response followed by valid response.
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, SYNCHRONOUS, false));
+ data->AddResponseData(kT1ResponseDatagram,
+ arraysize(kT1ResponseDatagram), SYNCHRONOUS);
+ data->AddResponseData(kT0ResponseDatagram,
+ arraysize(kT0ResponseDatagram), SYNCHRONOUS);
+ AddSocketData(data.Pass());
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, MismatchedResponseAsync) {
+ config_.attempts = 2;
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ // First attempt receives mismatched response followed by valid response.
+ // Second attempt times out.
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, false));
+ data->AddResponseData(kT1ResponseDatagram,
+ arraysize(kT1ResponseDatagram), ASYNC);
+ data->AddResponseData(kT0ResponseDatagram,
+ arraysize(kT0ResponseDatagram), ASYNC);
+ AddSocketData(data.Pass());
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, MismatchedResponseFail) {
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ // Attempt receives mismatched response but times out because only one attempt
+ // is allowed.
+ AddAsyncQueryAndResponse(1 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT);
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, ServerFail) {
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, NoDomain) {
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, Timeout) {
+ config_.attempts = 3;
+ // Use short timeout to speed up the test.
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT);
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+ EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting());
+}
+
+TEST_F(DnsTransactionTest, ServerFallbackAndRotate) {
+ // Test that we fallback on both server failure and timeout.
+ config_.attempts = 2;
+ // The next request should start from the next server.
+ config_.rotate = true;
+ ConfigureNumServers(3);
+ // Use short timeout to speed up the test.
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ // Responses for first request.
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
+ AddQueryAndTimeout(kT0HostName, kT0Qtype);
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL);
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeNXDOMAIN);
+ // Responses for second request.
+ AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeSERVFAIL);
+ AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeSERVFAIL);
+ AddAsyncQueryAndRcode(kT1HostName, kT1Qtype, dns_protocol::kRcodeNXDOMAIN);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_NAME_NOT_RESOLVED);
+ TransactionHelper helper1(kT1HostName, kT1Qtype, ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+ EXPECT_TRUE(helper1.Run(transaction_factory_.get()));
+
+ unsigned kOrder[] = {
+ 0, 1, 2, 0, 1, // The first transaction.
+ 1, 2, 0, // The second transaction starts from the next server.
+ };
+ CheckServerOrder(kOrder, arraysize(kOrder));
+}
+
+TEST_F(DnsTransactionTest, SuffixSearchAboveNdots) {
+ config_.ndots = 2;
+ config_.search.push_back("a");
+ config_.search.push_back("b");
+ config_.search.push_back("c");
+ config_.rotate = true;
+ ConfigureNumServers(2);
+ ConfigureFactory();
+
+ AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.z.a", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.z.b", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.z.c", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+
+ TransactionHelper helper0("x.y.z", dns_protocol::kTypeA,
+ ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+
+ // Also check if suffix search causes server rotation.
+ unsigned kOrder0[] = { 0, 1, 0, 1 };
+ CheckServerOrder(kOrder0, arraysize(kOrder0));
+}
+
+TEST_F(DnsTransactionTest, SuffixSearchBelowNdots) {
+ config_.ndots = 2;
+ config_.search.push_back("a");
+ config_.search.push_back("b");
+ config_.search.push_back("c");
+ ConfigureFactory();
+
+ // Responses for first transaction.
+ AddAsyncQueryAndRcode("x.y.a", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.b", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.c", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ // Responses for second transaction.
+ AddAsyncQueryAndRcode("x.a", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.b", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.c", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ // Responses for third transaction.
+ AddAsyncQueryAndRcode("x", dns_protocol::kTypeAAAA,
+ dns_protocol::kRcodeNXDOMAIN);
+
+ TransactionHelper helper0("x.y", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+
+ // A single-label name.
+ TransactionHelper helper1("x", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper1.Run(transaction_factory_.get()));
+
+ // A fully-qualified name.
+ TransactionHelper helper2("x.", dns_protocol::kTypeAAAA,
+ ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper2.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, EmptySuffixSearch) {
+ // Responses for first transaction.
+ AddAsyncQueryAndRcode("x", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+
+ // A fully-qualified name.
+ TransactionHelper helper0("x.", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED);
+
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+
+ // A single label name is not even attempted.
+ TransactionHelper helper1("singlelabel", dns_protocol::kTypeA,
+ ERR_DNS_SEARCH_EMPTY);
+
+ helper1.Run(transaction_factory_.get());
+ EXPECT_TRUE(helper1.has_completed());
+}
+
+TEST_F(DnsTransactionTest, DontAppendToMultiLabelName) {
+ config_.search.push_back("a");
+ config_.search.push_back("b");
+ config_.search.push_back("c");
+ config_.append_to_multi_label_name = false;
+ ConfigureFactory();
+
+ // Responses for first transaction.
+ AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ // Responses for second transaction.
+ AddAsyncQueryAndRcode("x.y", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ // Responses for third transaction.
+ AddAsyncQueryAndRcode("x.a", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.b", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.c", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+
+ TransactionHelper helper0("x.y.z", dns_protocol::kTypeA,
+ ERR_NAME_NOT_RESOLVED);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+
+ TransactionHelper helper1("x.y", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED);
+ EXPECT_TRUE(helper1.Run(transaction_factory_.get()));
+
+ TransactionHelper helper2("x", dns_protocol::kTypeA, ERR_NAME_NOT_RESOLVED);
+ EXPECT_TRUE(helper2.Run(transaction_factory_.get()));
+}
+
+const uint8 kResponseNoData[] = {
+ 0x00, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ // Question
+ 0x01, 'x', 0x01, 'y', 0x01, 'z', 0x01, 'b', 0x00, 0x00, 0x01, 0x00, 0x01,
+ // Authority section, SOA record, TTL 0x3E6
+ 0x01, 'z', 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE6,
+ // Minimal RDATA, 18 bytes
+ 0x00, 0x12,
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+TEST_F(DnsTransactionTest, SuffixSearchStop) {
+ config_.ndots = 2;
+ config_.search.push_back("a");
+ config_.search.push_back("b");
+ config_.search.push_back("c");
+ ConfigureFactory();
+
+ AddAsyncQueryAndRcode("x.y.z", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndRcode("x.y.z.a", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddAsyncQueryAndResponse(0 /* id */, "x.y.z.b", dns_protocol::kTypeA,
+ kResponseNoData, arraysize(kResponseNoData));
+
+ TransactionHelper helper0("x.y.z", dns_protocol::kTypeA, 0 /* answers */);
+
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, SyncFirstQuery) {
+ config_.search.push_back("lab.ccs.neu.edu");
+ config_.search.push_back("ccs.neu.edu");
+ ConfigureFactory();
+
+ AddSyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, SyncFirstQueryWithSearch) {
+ config_.search.push_back("lab.ccs.neu.edu");
+ config_.search.push_back("ccs.neu.edu");
+ ConfigureFactory();
+
+ AddSyncQueryAndRcode("www.lab.ccs.neu.edu", kT2Qtype,
+ dns_protocol::kRcodeNXDOMAIN);
+ // "www.ccs.neu.edu"
+ AddAsyncQueryAndResponse(2 /* id */, kT2HostName, kT2Qtype,
+ kT2ResponseDatagram, arraysize(kT2ResponseDatagram));
+
+ TransactionHelper helper0("www", kT2Qtype, kT2RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, SyncSearchQuery) {
+ config_.search.push_back("lab.ccs.neu.edu");
+ config_.search.push_back("ccs.neu.edu");
+ ConfigureFactory();
+
+ AddAsyncQueryAndRcode("www.lab.ccs.neu.edu", dns_protocol::kTypeA,
+ dns_protocol::kRcodeNXDOMAIN);
+ AddSyncQueryAndResponse(2 /* id */, kT2HostName, kT2Qtype,
+ kT2ResponseDatagram, arraysize(kT2ResponseDatagram));
+
+ TransactionHelper helper0("www", kT2Qtype, kT2RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, ConnectFailure) {
+ socket_factory_->fail_next_socket_ = true;
+ transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt.
+ TransactionHelper helper0("www.chromium.org", dns_protocol::kTypeA,
+ ERR_CONNECTION_REFUSED);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, ConnectFailureFollowedBySuccess) {
+ // Retry after server failure.
+ config_.attempts = 2;
+ ConfigureFactory();
+ // First server connection attempt fails.
+ transaction_ids_.push_back(0); // Needed to make a DnsUDPAttempt.
+ socket_factory_->fail_next_socket_ = true;
+ // Second DNS query succeeds.
+ AddAsyncQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram));
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, TCPLookup) {
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
+ dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
+ AddQueryAndResponse(0 /* id */, kT0HostName, kT0Qtype,
+ kT0ResponseDatagram, arraysize(kT0ResponseDatagram),
+ ASYNC, true /* use_tcp */);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, kT0RecordCount);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, TCPFailure) {
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
+ dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
+ AddQueryAndRcode(kT0HostName, kT0Qtype, dns_protocol::kRcodeSERVFAIL,
+ ASYNC, true /* use_tcp */);
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_SERVER_FAILED);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, TCPMalformed) {
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
+ dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
+ scoped_ptr<DnsSocketData> data(
+ new DnsSocketData(0 /* id */, kT0HostName, kT0Qtype, ASYNC, true));
+ // Valid response but length too short.
+ data->AddResponseWithLength(
+ make_scoped_ptr(
+ new DnsResponse(reinterpret_cast<const char*>(kT0ResponseDatagram),
+ arraysize(kT0ResponseDatagram), 0)),
+ ASYNC,
+ static_cast<uint16>(kT0QuerySize - 1));
+ AddSocketData(data.Pass());
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_MALFORMED_RESPONSE);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, TCPTimeout) {
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+ AddAsyncQueryAndRcode(kT0HostName, kT0Qtype,
+ dns_protocol::kRcodeNOERROR | dns_protocol::kFlagTC);
+ AddSocketData(make_scoped_ptr(
+ new DnsSocketData(1 /* id */, kT0HostName, kT0Qtype, ASYNC, true)));
+
+ TransactionHelper helper0(kT0HostName, kT0Qtype, ERR_DNS_TIMED_OUT);
+ EXPECT_TRUE(helper0.RunUntilDone(transaction_factory_.get()));
+}
+
+TEST_F(DnsTransactionTest, InvalidQuery) {
+ config_.timeout = TestTimeouts::tiny_timeout();
+ ConfigureFactory();
+
+ TransactionHelper helper0(".", dns_protocol::kTypeA, ERR_INVALID_ARGUMENT);
+ EXPECT_TRUE(helper0.Run(transaction_factory_.get()));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/host_cache.cc b/chromium/net/dns/host_cache.cc
new file mode 100644
index 00000000000..0e6ff15cd5d
--- /dev/null
+++ b/chromium/net/dns/host_cache.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_cache.h"
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+HostCache::Entry::Entry(int error, const AddressList& addrlist,
+ base::TimeDelta ttl)
+ : error(error),
+ addrlist(addrlist),
+ ttl(ttl) {
+ DCHECK(ttl >= base::TimeDelta());
+}
+
+HostCache::Entry::Entry(int error, const AddressList& addrlist)
+ : error(error),
+ addrlist(addrlist),
+ ttl(base::TimeDelta::FromSeconds(-1)) {
+}
+
+HostCache::Entry::~Entry() {
+}
+
+//-----------------------------------------------------------------------------
+
+HostCache::HostCache(size_t max_entries)
+ : entries_(max_entries) {
+}
+
+HostCache::~HostCache() {
+}
+
+const HostCache::Entry* HostCache::Lookup(const Key& key,
+ base::TimeTicks now) {
+ DCHECK(CalledOnValidThread());
+ if (caching_is_disabled())
+ return NULL;
+
+ return entries_.Get(key, now);
+}
+
+void HostCache::Set(const Key& key,
+ const Entry& entry,
+ base::TimeTicks now,
+ base::TimeDelta ttl) {
+ DCHECK(CalledOnValidThread());
+ if (caching_is_disabled())
+ return;
+
+ entries_.Put(key, entry, now, now + ttl);
+}
+
+void HostCache::clear() {
+ DCHECK(CalledOnValidThread());
+ entries_.Clear();
+}
+
+size_t HostCache::size() const {
+ DCHECK(CalledOnValidThread());
+ return entries_.size();
+}
+
+size_t HostCache::max_entries() const {
+ DCHECK(CalledOnValidThread());
+ return entries_.max_entries();
+}
+
+// Note that this map may contain expired entries.
+const HostCache::EntryMap& HostCache::entries() const {
+ DCHECK(CalledOnValidThread());
+ return entries_;
+}
+
+// static
+scoped_ptr<HostCache> HostCache::CreateDefaultCache() {
+ // Cache capacity is determined by the field trial.
+#if defined(ENABLE_BUILT_IN_DNS)
+ const size_t kDefaultMaxEntries = 1000;
+#else
+ const size_t kDefaultMaxEntries = 100;
+#endif
+ const size_t kSaneMaxEntries = 1 << 20;
+ size_t max_entries = 0;
+ base::StringToSizeT(base::FieldTrialList::FindFullName("HostCacheSize"),
+ &max_entries);
+ if ((max_entries == 0) || (max_entries > kSaneMaxEntries))
+ max_entries = kDefaultMaxEntries;
+ return make_scoped_ptr(new HostCache(max_entries));
+}
+
+void HostCache::EvictionHandler::Handle(
+ const Key& key,
+ const Entry& entry,
+ const base::TimeTicks& expiration,
+ const base::TimeTicks& now,
+ bool on_get) const {
+ if (on_get) {
+ DCHECK(now >= expiration);
+ UMA_HISTOGRAM_CUSTOM_TIMES("DNS.CacheExpiredOnGet", now - expiration,
+ base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(1), 100);
+ return;
+ }
+ if (expiration > now) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("DNS.CacheEvicted", expiration - now,
+ base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(1), 100);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_TIMES("DNS.CacheExpired", now - expiration,
+ base::TimeDelta::FromSeconds(1), base::TimeDelta::FromDays(1), 100);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/dns/host_cache.h b/chromium/net/dns/host_cache.h
new file mode 100644
index 00000000000..e8628fb04ec
--- /dev/null
+++ b/chromium/net/dns/host_cache.h
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_HOST_CACHE_H_
+#define NET_DNS_HOST_CACHE_H_
+
+#include <functional>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/address_family.h"
+#include "net/base/address_list.h"
+#include "net/base/expiring_cache.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Cache used by HostResolver to map hostnames to their resolved result.
+class NET_EXPORT HostCache : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Stores the latest address list that was looked up for a hostname.
+ struct NET_EXPORT Entry {
+ Entry(int error, const AddressList& addrlist, base::TimeDelta ttl);
+ // Use when |ttl| is unknown.
+ Entry(int error, const AddressList& addrlist);
+ ~Entry();
+
+ bool has_ttl() const { return ttl >= base::TimeDelta(); }
+
+ // The resolve results for this entry.
+ int error;
+ AddressList addrlist;
+ // TTL obtained from the nameserver. Negative if unknown.
+ base::TimeDelta ttl;
+ };
+
+ struct Key {
+ Key(const std::string& hostname, AddressFamily address_family,
+ HostResolverFlags host_resolver_flags)
+ : hostname(hostname),
+ address_family(address_family),
+ host_resolver_flags(host_resolver_flags) {}
+
+ bool operator<(const Key& other) const {
+ // |address_family| and |host_resolver_flags| are compared before
+ // |hostname| under assumption that integer comparisons are faster than
+ // string comparisons.
+ if (address_family != other.address_family)
+ return address_family < other.address_family;
+ if (host_resolver_flags != other.host_resolver_flags)
+ return host_resolver_flags < other.host_resolver_flags;
+ return hostname < other.hostname;
+ }
+
+ std::string hostname;
+ AddressFamily address_family;
+ HostResolverFlags host_resolver_flags;
+ };
+
+ struct EvictionHandler {
+ void Handle(const Key& key,
+ const Entry& entry,
+ const base::TimeTicks& expiration,
+ const base::TimeTicks& now,
+ bool onGet) const;
+ };
+
+ typedef ExpiringCache<Key, Entry, base::TimeTicks,
+ std::less<base::TimeTicks>,
+ EvictionHandler> EntryMap;
+
+ // Constructs a HostCache that stores up to |max_entries|.
+ explicit HostCache(size_t max_entries);
+
+ ~HostCache();
+
+ // Returns a pointer to the entry for |key|, which is valid at time
+ // |now|. If there is no such entry, returns NULL.
+ const Entry* Lookup(const Key& key, base::TimeTicks now);
+
+ // Overwrites or creates an entry for |key|.
+ // |entry| is the value to set, |now| is the current time
+ // |ttl| is the "time to live".
+ void Set(const Key& key,
+ const Entry& entry,
+ base::TimeTicks now,
+ base::TimeDelta ttl);
+
+ // Empties the cache
+ void clear();
+
+ // Returns the number of entries in the cache.
+ size_t size() const;
+
+ // Following are used by net_internals UI.
+ size_t max_entries() const;
+
+ const EntryMap& entries() const;
+
+ // Creates a default cache.
+ static scoped_ptr<HostCache> CreateDefaultCache();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HostCacheTest, NoCache);
+
+ // Returns true if this HostCache can contain no entries.
+ bool caching_is_disabled() const {
+ return entries_.max_entries() == 0;
+ }
+
+ // Map from hostname (presumably in lowercase canonicalized format) to
+ // a resolved result entry.
+ EntryMap entries_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostCache);
+};
+
+} // namespace net
+
+#endif // NET_DNS_HOST_CACHE_H_
diff --git a/chromium/net/dns/host_cache_unittest.cc b/chromium/net/dns/host_cache_unittest.cc
new file mode 100644
index 00000000000..34309c1223c
--- /dev/null
+++ b/chromium/net/dns/host_cache_unittest.cc
@@ -0,0 +1,388 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_cache.h"
+
+#include "base/format_macros.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxCacheEntries = 10;
+
+// Builds a key for |hostname|, defaulting the address family to unspecified.
+HostCache::Key Key(const std::string& hostname) {
+ return HostCache::Key(hostname, ADDRESS_FAMILY_UNSPECIFIED, 0);
+}
+
+} // namespace
+
+TEST(HostCacheTest, Basic) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1 = Key("foobar.com");
+ HostCache::Key key2 = Key("foobar2.com");
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry for "foobar.com" at t=0.
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key1, now)->error == entry.error);
+
+ EXPECT_EQ(1U, cache.size());
+
+ // Advance to t=5.
+ now += base::TimeDelta::FromSeconds(5);
+
+ // Add an entry for "foobar2.com" at t=5.
+ EXPECT_FALSE(cache.Lookup(key2, now));
+ cache.Set(key2, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key2, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Advance to t=9
+ now += base::TimeDelta::FromSeconds(4);
+
+ // Verify that the entries we added are still retrievable, and usable.
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+ EXPECT_NE(cache.Lookup(key1, now), cache.Lookup(key2, now));
+
+ // Advance to t=10; key is now expired.
+ now += base::TimeDelta::FromSeconds(1);
+
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+
+ // Update key1, so it is no longer expired.
+ cache.Set(key1, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Both entries should still be retrievable and usable.
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+
+ // Advance to t=20; both entries are now expired.
+ now += base::TimeDelta::FromSeconds(10);
+
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ EXPECT_FALSE(cache.Lookup(key2, now));
+}
+
+// Try caching entries for a failed resolve attempt -- since we set the TTL of
+// such entries to 0 it won't store, but it will kick out the previous result.
+TEST(HostCacheTest, NoCacheZeroTTL) {
+ const base::TimeDelta kSuccessEntryTTL = base::TimeDelta::FromSeconds(10);
+ const base::TimeDelta kFailureEntryTTL = base::TimeDelta::FromSeconds(0);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // Set t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1 = Key("foobar.com");
+ HostCache::Key key2 = Key("foobar2.com");
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kFailureEntryTTL);
+ EXPECT_EQ(1U, cache.size());
+
+ // We disallow use of negative entries.
+ EXPECT_FALSE(cache.Lookup(key1, now));
+
+ // Now overwrite with a valid entry, and then overwrite with negative entry
+ // again -- the valid entry should be kicked out.
+ cache.Set(key1, entry, now, kSuccessEntryTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kFailureEntryTTL);
+ EXPECT_FALSE(cache.Lookup(key1, now));
+}
+
+// Try caching entries for a failed resolves for 10 seconds.
+TEST(HostCacheTest, CacheNegativeEntry) {
+ const base::TimeDelta kFailureEntryTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // Start at t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1 = Key("foobar.com");
+ HostCache::Key key2 = Key("foobar2.com");
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry for "foobar.com" at t=0.
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kFailureEntryTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_EQ(1U, cache.size());
+
+ // Advance to t=5.
+ now += base::TimeDelta::FromSeconds(5);
+
+ // Add an entry for "foobar2.com" at t=5.
+ EXPECT_FALSE(cache.Lookup(key2, now));
+ cache.Set(key2, entry, now, kFailureEntryTTL);
+ EXPECT_TRUE(cache.Lookup(key2, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Advance to t=9
+ now += base::TimeDelta::FromSeconds(4);
+
+ // Verify that the entries we added are still retrievable, and usable.
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+
+ // Advance to t=10; key1 is now expired.
+ now += base::TimeDelta::FromSeconds(1);
+
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+
+ // Update key1, so it is no longer expired.
+ cache.Set(key1, entry, now, kFailureEntryTTL);
+ // Re-uses existing entry storage.
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Both entries should still be retrievable and usable.
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_TRUE(cache.Lookup(key2, now));
+
+ // Advance to t=20; both entries are now expired.
+ now += base::TimeDelta::FromSeconds(10);
+
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ EXPECT_FALSE(cache.Lookup(key2, now));
+}
+
+// Tests that the same hostname can be duplicated in the cache, so long as
+// the address family differs.
+TEST(HostCacheTest, AddressFamilyIsPartOfKey) {
+ const base::TimeDelta kSuccessEntryTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1("foobar.com", ADDRESS_FAMILY_UNSPECIFIED, 0);
+ HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4, 0);
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry for ("foobar.com", UNSPECIFIED) at t=0.
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kSuccessEntryTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_EQ(1U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4_ONLY) at t=0.
+ EXPECT_FALSE(cache.Lookup(key2, now));
+ cache.Set(key2, entry, now, kSuccessEntryTTL);
+ EXPECT_TRUE(cache.Lookup(key2, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Even though the hostnames were the same, we should have two unique
+ // entries (because the address families differ).
+ EXPECT_NE(cache.Lookup(key1, now), cache.Lookup(key2, now));
+}
+
+// Tests that the same hostname can be duplicated in the cache, so long as
+// the HostResolverFlags differ.
+TEST(HostCacheTest, HostResolverFlagsArePartOfKey) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // t=0.
+ base::TimeTicks now;
+
+ HostCache::Key key1("foobar.com", ADDRESS_FAMILY_IPV4, 0);
+ HostCache::Key key2("foobar.com", ADDRESS_FAMILY_IPV4,
+ HOST_RESOLVER_CANONNAME);
+ HostCache::Key key3("foobar.com", ADDRESS_FAMILY_IPV4,
+ HOST_RESOLVER_LOOPBACK_ONLY);
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_EQ(0U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4, NONE) at t=0.
+ EXPECT_FALSE(cache.Lookup(key1, now));
+ cache.Set(key1, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key1, now));
+ EXPECT_EQ(1U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4, CANONNAME) at t=0.
+ EXPECT_FALSE(cache.Lookup(key2, now));
+ cache.Set(key2, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key2, now));
+ EXPECT_EQ(2U, cache.size());
+
+ // Add an entry for ("foobar.com", IPV4, LOOPBACK_ONLY) at t=0.
+ EXPECT_FALSE(cache.Lookup(key3, now));
+ cache.Set(key3, entry, now, kTTL);
+ EXPECT_TRUE(cache.Lookup(key3, now));
+ EXPECT_EQ(3U, cache.size());
+
+ // Even though the hostnames were the same, we should have two unique
+ // entries (because the HostResolverFlags differ).
+ EXPECT_NE(cache.Lookup(key1, now), cache.Lookup(key2, now));
+ EXPECT_NE(cache.Lookup(key1, now), cache.Lookup(key3, now));
+ EXPECT_NE(cache.Lookup(key2, now), cache.Lookup(key3, now));
+}
+
+TEST(HostCacheTest, NoCache) {
+ // Disable caching.
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(0);
+ EXPECT_TRUE(cache.caching_is_disabled());
+
+ // Set t=0.
+ base::TimeTicks now;
+
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ // Lookup and Set should have no effect.
+ EXPECT_FALSE(cache.Lookup(Key("foobar.com"),now));
+ cache.Set(Key("foobar.com"), entry, now, kTTL);
+ EXPECT_FALSE(cache.Lookup(Key("foobar.com"), now));
+
+ EXPECT_EQ(0U, cache.size());
+}
+
+TEST(HostCacheTest, Clear) {
+ const base::TimeDelta kTTL = base::TimeDelta::FromSeconds(10);
+
+ HostCache cache(kMaxCacheEntries);
+
+ // Set t=0.
+ base::TimeTicks now;
+
+ HostCache::Entry entry = HostCache::Entry(OK, AddressList());
+
+ EXPECT_EQ(0u, cache.size());
+
+ // Add three entries.
+ cache.Set(Key("foobar1.com"), entry, now, kTTL);
+ cache.Set(Key("foobar2.com"), entry, now, kTTL);
+ cache.Set(Key("foobar3.com"), entry, now, kTTL);
+
+ EXPECT_EQ(3u, cache.size());
+
+ cache.clear();
+
+ EXPECT_EQ(0u, cache.size());
+}
+
+// Tests the less than and equal operators for HostCache::Key work.
+TEST(HostCacheTest, KeyComparators) {
+ struct {
+ // Inputs.
+ HostCache::Key key1;
+ HostCache::Key key2;
+
+ // Expectation.
+ // -1 means key1 is less than key2
+ // 0 means key1 equals key2
+ // 1 means key1 is greater than key2
+ int expected_comparison;
+ } tests[] = {
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ 0
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ 1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_IPV4, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ 1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host2", ADDRESS_FAMILY_IPV4, 0),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ -1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED, 0),
+ 1
+ },
+ {
+ HostCache::Key("host1", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ HostCache::Key("host2", ADDRESS_FAMILY_UNSPECIFIED,
+ HOST_RESOLVER_CANONNAME),
+ -1
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i));
+
+ const HostCache::Key& key1 = tests[i].key1;
+ const HostCache::Key& key2 = tests[i].key2;
+
+ switch (tests[i].expected_comparison) {
+ case -1:
+ EXPECT_TRUE(key1 < key2);
+ EXPECT_FALSE(key2 < key1);
+ break;
+ case 0:
+ EXPECT_FALSE(key1 < key2);
+ EXPECT_FALSE(key2 < key1);
+ break;
+ case 1:
+ EXPECT_FALSE(key1 < key2);
+ EXPECT_TRUE(key2 < key1);
+ break;
+ default:
+ FAIL() << "Invalid expectation. Can be only -1, 0, 1";
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/dns/host_resolver.cc b/chromium/net/dns/host_resolver.cc
new file mode 100644
index 00000000000..d74be91beb0
--- /dev/null
+++ b/chromium/net/dns/host_resolver.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_resolver.h"
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/host_resolver_impl.h"
+
+namespace net {
+
+namespace {
+
+// Maximum of 6 concurrent resolver threads (excluding retries).
+// Some routers (or resolvers) appear to start to provide host-not-found if
+// too many simultaneous resolutions are pending. This number needs to be
+// further optimized, but 8 is what FF currently does. We found some routers
+// that limit this to 6, so we're temporarily holding it at that level.
+const size_t kDefaultMaxProcTasks = 6u;
+
+// When configuring from field trial, do not allow
+const size_t kSaneMaxProcTasks = 20u;
+
+PrioritizedDispatcher::Limits GetDispatcherLimits(
+ const HostResolver::Options& options) {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES,
+ options.max_concurrent_resolves);
+
+ // If not using default, do not use the field trial.
+ if (limits.total_jobs != HostResolver::kDefaultParallelism)
+ return limits;
+
+ // Default, without trial is no reserved slots.
+ limits.total_jobs = kDefaultMaxProcTasks;
+
+ // Parallelism is determined by the field trial.
+ std::string group = base::FieldTrialList::FindFullName(
+ "HostResolverDispatch");
+
+ if (group.empty())
+ return limits;
+
+ // The format of the group name is a list of non-negative integers separated
+ // by ':'. Each of the elements in the list corresponds to an element in
+ // |reserved_slots|, except the last one which is the |total_jobs|.
+
+ std::vector<std::string> group_parts;
+ base::SplitString(group, ':', &group_parts);
+ if (group_parts.size() != NUM_PRIORITIES + 1) {
+ NOTREACHED();
+ return limits;
+ }
+
+ std::vector<size_t> parsed(group_parts.size());
+ size_t total_reserved_slots = 0;
+
+ for (size_t i = 0; i < group_parts.size(); ++i) {
+ if (!base::StringToSizeT(group_parts[i], &parsed[i])) {
+ NOTREACHED();
+ return limits;
+ }
+ }
+
+ size_t total_jobs = parsed.back();
+ parsed.pop_back();
+ for (size_t i = 0; i < parsed.size(); ++i) {
+ total_reserved_slots += parsed[i];
+ }
+
+ // There must be some unreserved slots available for the all priorities.
+ if (total_reserved_slots > total_jobs ||
+ (total_reserved_slots == total_jobs && parsed[MINIMUM_PRIORITY] == 0)) {
+ NOTREACHED();
+ return limits;
+ }
+
+ limits.total_jobs = total_jobs;
+ limits.reserved_slots = parsed;
+ return limits;
+}
+
+} // namespace
+
+HostResolver::Options::Options()
+ : max_concurrent_resolves(kDefaultParallelism),
+ max_retry_attempts(kDefaultRetryAttempts),
+ enable_caching(true) {
+}
+
+HostResolver::RequestInfo::RequestInfo(const HostPortPair& host_port_pair)
+ : host_port_pair_(host_port_pair),
+ address_family_(ADDRESS_FAMILY_UNSPECIFIED),
+ host_resolver_flags_(0),
+ allow_cached_response_(true),
+ is_speculative_(false),
+ priority_(MEDIUM) {
+}
+
+HostResolver::~HostResolver() {
+}
+
+AddressFamily HostResolver::GetDefaultAddressFamily() const {
+ return ADDRESS_FAMILY_UNSPECIFIED;
+}
+
+void HostResolver::SetDnsClientEnabled(bool enabled) {
+}
+
+HostCache* HostResolver::GetHostCache() {
+ return NULL;
+}
+
+base::Value* HostResolver::GetDnsConfigAsValue() const {
+ return NULL;
+}
+
+// static
+scoped_ptr<HostResolver>
+HostResolver::CreateSystemResolver(const Options& options, NetLog* net_log) {
+ scoped_ptr<HostCache> cache;
+ if (options.enable_caching)
+ cache = HostCache::CreateDefaultCache();
+ return scoped_ptr<HostResolver>(new HostResolverImpl(
+ cache.Pass(),
+ GetDispatcherLimits(options),
+ HostResolverImpl::ProcTaskParams(NULL, options.max_retry_attempts),
+ net_log));
+}
+
+// static
+scoped_ptr<HostResolver>
+HostResolver::CreateDefaultResolver(NetLog* net_log) {
+ return CreateSystemResolver(Options(), net_log);
+}
+
+HostResolver::HostResolver() {
+}
+
+} // namespace net
diff --git a/chromium/net/dns/host_resolver.h b/chromium/net/dns/host_resolver.h
new file mode 100644
index 00000000000..558a1ddc4b2
--- /dev/null
+++ b/chromium/net/dns/host_resolver.h
@@ -0,0 +1,204 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_HOST_RESOLVER_H_
+#define NET_DNS_HOST_RESOLVER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_family.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h"
+#include "net/base/request_priority.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class AddressList;
+class BoundNetLog;
+class HostCache;
+class HostResolverProc;
+class NetLog;
+
+// This class represents the task of resolving hostnames (or IP address
+// literal) to an AddressList object.
+//
+// HostResolver can handle multiple requests at a time, so when cancelling a
+// request the RequestHandle that was returned by Resolve() needs to be
+// given. A simpler alternative for consumers that only have 1 outstanding
+// request at a time is to create a SingleRequestHostResolver wrapper around
+// HostResolver (which will automatically cancel the single request when it
+// goes out of scope).
+class NET_EXPORT HostResolver {
+ public:
+ // |max_concurrent_resolves| is how many resolve requests will be allowed to
+ // run in parallel. Pass HostResolver::kDefaultParallelism to choose a
+ // default value.
+ // |max_retry_attempts| is the maximum number of times we will retry for host
+ // resolution. Pass HostResolver::kDefaultRetryAttempts to choose a default
+ // value.
+ // |enable_caching| controls whether a HostCache is used.
+ struct NET_EXPORT Options {
+ Options();
+
+ size_t max_concurrent_resolves;
+ size_t max_retry_attempts;
+ bool enable_caching;
+ };
+
+ // The parameters for doing a Resolve(). A hostname and port are required,
+ // the rest are optional (and have reasonable defaults).
+ class NET_EXPORT RequestInfo {
+ public:
+ explicit RequestInfo(const HostPortPair& host_port_pair);
+
+ const HostPortPair& host_port_pair() const { return host_port_pair_; }
+ void set_host_port_pair(const HostPortPair& host_port_pair) {
+ host_port_pair_ = host_port_pair;
+ }
+
+ int port() const { return host_port_pair_.port(); }
+ const std::string& hostname() const { return host_port_pair_.host(); }
+
+ AddressFamily address_family() const { return address_family_; }
+ void set_address_family(AddressFamily address_family) {
+ address_family_ = address_family;
+ }
+
+ HostResolverFlags host_resolver_flags() const {
+ return host_resolver_flags_;
+ }
+ void set_host_resolver_flags(HostResolverFlags host_resolver_flags) {
+ host_resolver_flags_ = host_resolver_flags;
+ }
+
+ bool allow_cached_response() const { return allow_cached_response_; }
+ void set_allow_cached_response(bool b) { allow_cached_response_ = b; }
+
+ bool is_speculative() const { return is_speculative_; }
+ void set_is_speculative(bool b) { is_speculative_ = b; }
+
+ RequestPriority priority() const { return priority_; }
+ void set_priority(RequestPriority priority) { priority_ = priority; }
+
+ private:
+ // The hostname to resolve, and the port to use in resulting sockaddrs.
+ HostPortPair host_port_pair_;
+
+ // The address family to restrict results to.
+ AddressFamily address_family_;
+
+ // Flags to use when resolving this request.
+ HostResolverFlags host_resolver_flags_;
+
+ // Whether it is ok to return a result from the host cache.
+ bool allow_cached_response_;
+
+ // Whether this request was started by the DNS prefetcher.
+ bool is_speculative_;
+
+ // The priority for the request.
+ RequestPriority priority_;
+ };
+
+ // Opaque type used to cancel a request.
+ typedef void* RequestHandle;
+
+ // This value can be passed into CreateSystemResolver as the
+ // |max_concurrent_resolves| parameter. It will select a default level of
+ // concurrency.
+ static const size_t kDefaultParallelism = 0;
+
+ // This value can be passed into CreateSystemResolver as the
+ // |max_retry_attempts| parameter.
+ static const size_t kDefaultRetryAttempts = -1;
+
+ // If any completion callbacks are pending when the resolver is destroyed,
+ // the host resolutions are cancelled, and the completion callbacks will not
+ // be called.
+ virtual ~HostResolver();
+
+ // Resolves the given hostname (or IP address literal), filling out the
+ // |addresses| object upon success. The |info.port| parameter will be set as
+ // the sin(6)_port field of the sockaddr_in{6} struct. Returns OK if
+ // successful or an error code upon failure. Returns
+ // ERR_NAME_NOT_RESOLVED if hostname is invalid, or if it is an
+ // incompatible IP literal (e.g. IPv6 is disabled and it is an IPv6
+ // literal).
+ //
+ // If the operation cannot be completed synchronously, ERR_IO_PENDING will
+ // be returned and the real result code will be passed to the completion
+ // callback. Otherwise the result code is returned immediately from this
+ // call.
+ //
+ // If |out_req| is non-NULL, then |*out_req| will be filled with a handle to
+ // the async request. This handle is not valid after the request has
+ // completed.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) = 0;
+
+ // Resolves the given hostname (or IP address literal) out of cache or HOSTS
+ // file (if enabled) only. This is guaranteed to complete synchronously.
+ // This acts like |Resolve()| if the hostname is IP literal, or cached value
+ // or HOSTS entry exists. Otherwise, ERR_DNS_CACHE_MISS is returned.
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) = 0;
+
+ // Cancels the specified request. |req| is the handle returned by Resolve().
+ // After a request is canceled, its completion callback will not be called.
+ // CancelRequest must NOT be called after the request's completion callback
+ // has already run or the request was canceled.
+ virtual void CancelRequest(RequestHandle req) = 0;
+
+ // Sets the default AddressFamily to use when requests have left it
+ // unspecified. For example, this could be used to restrict resolution
+ // results to AF_INET by passing in ADDRESS_FAMILY_IPV4, or to
+ // AF_INET6 by passing in ADDRESS_FAMILY_IPV6.
+ virtual void SetDefaultAddressFamily(AddressFamily address_family) {}
+ virtual AddressFamily GetDefaultAddressFamily() const;
+
+ // Enable or disable the built-in asynchronous DnsClient.
+ virtual void SetDnsClientEnabled(bool enabled);
+
+ // Returns the HostResolverCache |this| uses, or NULL if there isn't one.
+ // Used primarily to clear the cache and for getting debug information.
+ virtual HostCache* GetHostCache();
+
+ // Returns the current DNS configuration |this| is using, as a Value, or NULL
+ // if it's configured to always use the system host resolver. Caller takes
+ // ownership of the returned Value.
+ virtual base::Value* GetDnsConfigAsValue() const;
+
+ // Creates a HostResolver implementation that queries the underlying system.
+ // (Except if a unit-test has changed the global HostResolverProc using
+ // ScopedHostResolverProc to intercept requests to the system).
+ static scoped_ptr<HostResolver> CreateSystemResolver(
+ const Options& options,
+ NetLog* net_log);
+
+ // As above, but uses default parameters.
+ static scoped_ptr<HostResolver> CreateDefaultResolver(NetLog* net_log);
+
+ protected:
+ HostResolver();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HostResolver);
+};
+
+} // namespace net
+
+#endif // NET_DNS_HOST_RESOLVER_H_
diff --git a/chromium/net/dns/host_resolver_impl.cc b/chromium/net/dns/host_resolver_impl.cc
new file mode 100644
index 00000000000..10631773291
--- /dev/null
+++ b/chromium/net/dns/host_resolver_impl.cc
@@ -0,0 +1,2206 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_resolver_impl.h"
+
+#if defined(OS_WIN)
+#include <Winsock2.h>
+#elif defined(OS_POSIX)
+#include <netdb.h>
+#endif
+
+#include <cmath>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/debug/stack_trace.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/worker_pool.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/address_family.h"
+#include "net/base/address_list.h"
+#include "net/base/dns_reloader.h"
+#include "net/base/dns_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/dns/address_sorter.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_transaction.h"
+#include "net/dns/host_resolver_proc.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/udp/datagram_client_socket.h"
+
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
+
+namespace net {
+
+namespace {
+
+// Limit the size of hostnames that will be resolved to combat issues in
+// some platform's resolvers.
+const size_t kMaxHostLength = 4096;
+
+// Default TTL for successful resolutions with ProcTask.
+const unsigned kCacheEntryTTLSeconds = 60;
+
+// Default TTL for unsuccessful resolutions with ProcTask.
+const unsigned kNegativeCacheEntryTTLSeconds = 0;
+
+// Minimum TTL for successful resolutions with DnsTask.
+const unsigned kMinimumTTLSeconds = kCacheEntryTTLSeconds;
+
+// Number of consecutive failures of DnsTask (with successful fallback) before
+// the DnsClient is disabled until the next DNS change.
+const unsigned kMaximumDnsFailures = 16;
+
+// We use a separate histogram name for each platform to facilitate the
+// display of error codes by their symbolic name (since each platform has
+// different mappings).
+const char kOSErrorsForGetAddrinfoHistogramName[] =
+#if defined(OS_WIN)
+ "Net.OSErrorsForGetAddrinfo_Win";
+#elif defined(OS_MACOSX)
+ "Net.OSErrorsForGetAddrinfo_Mac";
+#elif defined(OS_LINUX)
+ "Net.OSErrorsForGetAddrinfo_Linux";
+#else
+ "Net.OSErrorsForGetAddrinfo";
+#endif
+
+// Gets a list of the likely error codes that getaddrinfo() can return
+// (non-exhaustive). These are the error codes that we will track via
+// a histogram.
+std::vector<int> GetAllGetAddrinfoOSErrors() {
+ int os_errors[] = {
+#if defined(OS_POSIX)
+#if !defined(OS_FREEBSD)
+#if !defined(OS_ANDROID)
+ // EAI_ADDRFAMILY has been declared obsolete in Android's and
+ // FreeBSD's netdb.h.
+ EAI_ADDRFAMILY,
+#endif
+ // EAI_NODATA has been declared obsolete in FreeBSD's netdb.h.
+ EAI_NODATA,
+#endif
+ EAI_AGAIN,
+ EAI_BADFLAGS,
+ EAI_FAIL,
+ EAI_FAMILY,
+ EAI_MEMORY,
+ EAI_NONAME,
+ EAI_SERVICE,
+ EAI_SOCKTYPE,
+ EAI_SYSTEM,
+#elif defined(OS_WIN)
+ // See: http://msdn.microsoft.com/en-us/library/ms738520(VS.85).aspx
+ WSA_NOT_ENOUGH_MEMORY,
+ WSAEAFNOSUPPORT,
+ WSAEINVAL,
+ WSAESOCKTNOSUPPORT,
+ WSAHOST_NOT_FOUND,
+ WSANO_DATA,
+ WSANO_RECOVERY,
+ WSANOTINITIALISED,
+ WSATRY_AGAIN,
+ WSATYPE_NOT_FOUND,
+ // The following are not in doc, but might be to appearing in results :-(.
+ WSA_INVALID_HANDLE,
+#endif
+ };
+
+ // Ensure all errors are positive, as histogram only tracks positive values.
+ for (size_t i = 0; i < arraysize(os_errors); ++i) {
+ os_errors[i] = std::abs(os_errors[i]);
+ }
+
+ return base::CustomHistogram::ArrayToCustomRanges(os_errors,
+ arraysize(os_errors));
+}
+
+enum DnsResolveStatus {
+ RESOLVE_STATUS_DNS_SUCCESS = 0,
+ RESOLVE_STATUS_PROC_SUCCESS,
+ RESOLVE_STATUS_FAIL,
+ RESOLVE_STATUS_SUSPECT_NETBIOS,
+ RESOLVE_STATUS_MAX
+};
+
+void UmaAsyncDnsResolveStatus(DnsResolveStatus result) {
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ResolveStatus",
+ result,
+ RESOLVE_STATUS_MAX);
+}
+
+bool ResemblesNetBIOSName(const std::string& hostname) {
+ return (hostname.size() < 16) && (hostname.find('.') == std::string::npos);
+}
+
+// True if |hostname| ends with either ".local" or ".local.".
+bool ResemblesMulticastDNSName(const std::string& hostname) {
+ DCHECK(!hostname.empty());
+ const char kSuffix[] = ".local.";
+ const size_t kSuffixLen = sizeof(kSuffix) - 1;
+ const size_t kSuffixLenTrimmed = kSuffixLen - 1;
+ if (hostname[hostname.size() - 1] == '.') {
+ return hostname.size() > kSuffixLen &&
+ !hostname.compare(hostname.size() - kSuffixLen, kSuffixLen, kSuffix);
+ }
+ return hostname.size() > kSuffixLenTrimmed &&
+ !hostname.compare(hostname.size() - kSuffixLenTrimmed, kSuffixLenTrimmed,
+ kSuffix, kSuffixLenTrimmed);
+}
+
+// Attempts to connect a UDP socket to |dest|:53.
+bool IsGloballyReachable(const IPAddressNumber& dest,
+ const BoundNetLog& net_log) {
+ scoped_ptr<DatagramClientSocket> socket(
+ ClientSocketFactory::GetDefaultFactory()->CreateDatagramClientSocket(
+ DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ net_log.net_log(),
+ net_log.source()));
+ int rv = socket->Connect(IPEndPoint(dest, 53));
+ if (rv != OK)
+ return false;
+ IPEndPoint endpoint;
+ rv = socket->GetLocalAddress(&endpoint);
+ if (rv != OK)
+ return false;
+ DCHECK(endpoint.GetFamily() == ADDRESS_FAMILY_IPV6);
+ const IPAddressNumber& address = endpoint.address();
+ bool is_link_local = (address[0] == 0xFE) && ((address[1] & 0xC0) == 0x80);
+ if (is_link_local)
+ return false;
+ const uint8 kTeredoPrefix[] = { 0x20, 0x01, 0, 0 };
+ bool is_teredo = std::equal(kTeredoPrefix,
+ kTeredoPrefix + arraysize(kTeredoPrefix),
+ address.begin());
+ if (is_teredo)
+ return false;
+ return true;
+}
+
+// Provide a common macro to simplify code and readability. We must use a
+// macro as the underlying HISTOGRAM macro creates static variables.
+#define DNS_HISTOGRAM(name, time) UMA_HISTOGRAM_CUSTOM_TIMES(name, time, \
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(1), 100)
+
+// A macro to simplify code and readability.
+#define DNS_HISTOGRAM_BY_PRIORITY(basename, priority, time) \
+ do { \
+ switch (priority) { \
+ case HIGHEST: DNS_HISTOGRAM(basename "_HIGHEST", time); break; \
+ case MEDIUM: DNS_HISTOGRAM(basename "_MEDIUM", time); break; \
+ case LOW: DNS_HISTOGRAM(basename "_LOW", time); break; \
+ case LOWEST: DNS_HISTOGRAM(basename "_LOWEST", time); break; \
+ case IDLE: DNS_HISTOGRAM(basename "_IDLE", time); break; \
+ default: NOTREACHED(); break; \
+ } \
+ DNS_HISTOGRAM(basename, time); \
+ } while (0)
+
+// Record time from Request creation until a valid DNS response.
+void RecordTotalTime(bool had_dns_config,
+ bool speculative,
+ base::TimeDelta duration) {
+ if (had_dns_config) {
+ if (speculative) {
+ DNS_HISTOGRAM("AsyncDNS.TotalTime_speculative", duration);
+ } else {
+ DNS_HISTOGRAM("AsyncDNS.TotalTime", duration);
+ }
+ } else {
+ if (speculative) {
+ DNS_HISTOGRAM("DNS.TotalTime_speculative", duration);
+ } else {
+ DNS_HISTOGRAM("DNS.TotalTime", duration);
+ }
+ }
+}
+
+void RecordTTL(base::TimeDelta ttl) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("AsyncDNS.TTL", ttl,
+ base::TimeDelta::FromSeconds(1),
+ base::TimeDelta::FromDays(1), 100);
+}
+
+bool ConfigureAsyncDnsNoFallbackFieldTrial() {
+ const bool kDefault = false;
+
+ // Configure the AsyncDns field trial as follows:
+ // groups AsyncDnsNoFallbackA and AsyncDnsNoFallbackB: return true,
+ // groups AsyncDnsA and AsyncDnsB: return false,
+ // groups SystemDnsA and SystemDnsB: return false,
+ // otherwise (trial absent): return default.
+ std::string group_name = base::FieldTrialList::FindFullName("AsyncDns");
+ if (!group_name.empty())
+ return StartsWithASCII(group_name, "AsyncDnsNoFallback", false);
+ return kDefault;
+}
+
+//-----------------------------------------------------------------------------
+
+AddressList EnsurePortOnAddressList(const AddressList& list, uint16 port) {
+ if (list.empty() || list.front().port() == port)
+ return list;
+ return AddressList::CopyWithPort(list, port);
+}
+
+// Returns true if |addresses| contains only IPv4 loopback addresses.
+bool IsAllIPv4Loopback(const AddressList& addresses) {
+ for (unsigned i = 0; i < addresses.size(); ++i) {
+ const IPAddressNumber& address = addresses[i].address();
+ switch (addresses[i].GetFamily()) {
+ case ADDRESS_FAMILY_IPV4:
+ if (address[0] != 127)
+ return false;
+ break;
+ case ADDRESS_FAMILY_IPV6:
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+ return true;
+}
+
+// Creates NetLog parameters when the resolve failed.
+base::Value* NetLogProcTaskFailedCallback(uint32 attempt_number,
+ int net_error,
+ int os_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (attempt_number)
+ dict->SetInteger("attempt_number", attempt_number);
+
+ dict->SetInteger("net_error", net_error);
+
+ if (os_error) {
+ dict->SetInteger("os_error", os_error);
+#if defined(OS_POSIX)
+ dict->SetString("os_error_string", gai_strerror(os_error));
+#elif defined(OS_WIN)
+ // Map the error code to a human-readable string.
+ LPWSTR error_string = NULL;
+ int size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, // Use the internal message table.
+ os_error,
+ 0, // Use default language.
+ (LPWSTR)&error_string,
+ 0, // Buffer size.
+ 0); // Arguments (unused).
+ dict->SetString("os_error_string", WideToUTF8(error_string));
+ LocalFree(error_string);
+#endif
+ }
+
+ return dict;
+}
+
+// Creates NetLog parameters when the DnsTask failed.
+base::Value* NetLogDnsTaskFailedCallback(int net_error,
+ int dns_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ if (dns_error)
+ dict->SetInteger("dns_error", dns_error);
+ return dict;
+};
+
+// Creates NetLog parameters containing the information in a RequestInfo object,
+// along with the associated NetLog::Source.
+base::Value* NetLogRequestInfoCallback(const NetLog::Source& source,
+ const HostResolver::RequestInfo* info,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ source.AddToEventParameters(dict);
+
+ dict->SetString("host", info->host_port_pair().ToString());
+ dict->SetInteger("address_family",
+ static_cast<int>(info->address_family()));
+ dict->SetBoolean("allow_cached_response", info->allow_cached_response());
+ dict->SetBoolean("is_speculative", info->is_speculative());
+ dict->SetInteger("priority", info->priority());
+ return dict;
+}
+
+// Creates NetLog parameters for the creation of a HostResolverImpl::Job.
+base::Value* NetLogJobCreationCallback(const NetLog::Source& source,
+ const std::string* host,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ source.AddToEventParameters(dict);
+ dict->SetString("host", *host);
+ return dict;
+}
+
+// Creates NetLog parameters for HOST_RESOLVER_IMPL_JOB_ATTACH/DETACH events.
+base::Value* NetLogJobAttachCallback(const NetLog::Source& source,
+ RequestPriority priority,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ source.AddToEventParameters(dict);
+ dict->SetInteger("priority", priority);
+ return dict;
+}
+
+// Creates NetLog parameters for the DNS_CONFIG_CHANGED event.
+base::Value* NetLogDnsConfigCallback(const DnsConfig* config,
+ NetLog::LogLevel /* log_level */) {
+ return config->ToValue();
+}
+
+// The logging routines are defined here because some requests are resolved
+// without a Request object.
+
+// Logs when a request has just been started.
+void LogStartRequest(const BoundNetLog& source_net_log,
+ const BoundNetLog& request_net_log,
+ const HostResolver::RequestInfo& info) {
+ source_net_log.BeginEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL,
+ request_net_log.source().ToEventParametersCallback());
+
+ request_net_log.BeginEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST,
+ base::Bind(&NetLogRequestInfoCallback, source_net_log.source(), &info));
+}
+
+// Logs when a request has just completed (before its callback is run).
+void LogFinishRequest(const BoundNetLog& source_net_log,
+ const BoundNetLog& request_net_log,
+ const HostResolver::RequestInfo& info,
+ int net_error) {
+ request_net_log.EndEventWithNetErrorCode(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST, net_error);
+ source_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL);
+}
+
+// Logs when a request has been cancelled.
+void LogCancelRequest(const BoundNetLog& source_net_log,
+ const BoundNetLog& request_net_log,
+ const HostResolverImpl::RequestInfo& info) {
+ request_net_log.AddEvent(NetLog::TYPE_CANCELLED);
+ request_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_REQUEST);
+ source_net_log.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL);
+}
+
+//-----------------------------------------------------------------------------
+
+// Keeps track of the highest priority.
+class PriorityTracker {
+ public:
+ explicit PriorityTracker(RequestPriority initial_priority)
+ : highest_priority_(initial_priority), total_count_(0) {
+ memset(counts_, 0, sizeof(counts_));
+ }
+
+ RequestPriority highest_priority() const {
+ return highest_priority_;
+ }
+
+ size_t total_count() const {
+ return total_count_;
+ }
+
+ void Add(RequestPriority req_priority) {
+ ++total_count_;
+ ++counts_[req_priority];
+ if (highest_priority_ < req_priority)
+ highest_priority_ = req_priority;
+ }
+
+ void Remove(RequestPriority req_priority) {
+ DCHECK_GT(total_count_, 0u);
+ DCHECK_GT(counts_[req_priority], 0u);
+ --total_count_;
+ --counts_[req_priority];
+ size_t i;
+ for (i = highest_priority_; i > MINIMUM_PRIORITY && !counts_[i]; --i);
+ highest_priority_ = static_cast<RequestPriority>(i);
+
+ // In absence of requests, default to MINIMUM_PRIORITY.
+ if (total_count_ == 0)
+ DCHECK_EQ(MINIMUM_PRIORITY, highest_priority_);
+ }
+
+ private:
+ RequestPriority highest_priority_;
+ size_t total_count_;
+ size_t counts_[NUM_PRIORITIES];
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+// Holds the data for a request that could not be completed synchronously.
+// It is owned by a Job. Canceled Requests are only marked as canceled rather
+// than removed from the Job's |requests_| list.
+class HostResolverImpl::Request {
+ public:
+ Request(const BoundNetLog& source_net_log,
+ const BoundNetLog& request_net_log,
+ const RequestInfo& info,
+ const CompletionCallback& callback,
+ AddressList* addresses)
+ : source_net_log_(source_net_log),
+ request_net_log_(request_net_log),
+ info_(info),
+ job_(NULL),
+ callback_(callback),
+ addresses_(addresses),
+ request_time_(base::TimeTicks::Now()) {
+ }
+
+ // Mark the request as canceled.
+ void MarkAsCanceled() {
+ job_ = NULL;
+ addresses_ = NULL;
+ callback_.Reset();
+ }
+
+ bool was_canceled() const {
+ return callback_.is_null();
+ }
+
+ void set_job(Job* job) {
+ DCHECK(job);
+ // Identify which job the request is waiting on.
+ job_ = job;
+ }
+
+ // Prepare final AddressList and call completion callback.
+ void OnComplete(int error, const AddressList& addr_list) {
+ DCHECK(!was_canceled());
+ if (error == OK)
+ *addresses_ = EnsurePortOnAddressList(addr_list, info_.port());
+ CompletionCallback callback = callback_;
+ MarkAsCanceled();
+ callback.Run(error);
+ }
+
+ Job* job() const {
+ return job_;
+ }
+
+ // NetLog for the source, passed in HostResolver::Resolve.
+ const BoundNetLog& source_net_log() {
+ return source_net_log_;
+ }
+
+ // NetLog for this request.
+ const BoundNetLog& request_net_log() {
+ return request_net_log_;
+ }
+
+ const RequestInfo& info() const {
+ return info_;
+ }
+
+ base::TimeTicks request_time() const {
+ return request_time_;
+ }
+
+ private:
+ BoundNetLog source_net_log_;
+ BoundNetLog request_net_log_;
+
+ // The request info that started the request.
+ RequestInfo info_;
+
+ // The resolve job that this request is dependent on.
+ Job* job_;
+
+ // The user's callback to invoke when the request completes.
+ CompletionCallback callback_;
+
+ // The address list to save result into.
+ AddressList* addresses_;
+
+ const base::TimeTicks request_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+//------------------------------------------------------------------------------
+
+// Calls HostResolverProc on the WorkerPool. Performs retries if necessary.
+//
+// Whenever we try to resolve the host, we post a delayed task to check if host
+// resolution (OnLookupComplete) is completed or not. If the original attempt
+// hasn't completed, then we start another attempt for host resolution. We take
+// the results from the first attempt that finishes and ignore the results from
+// all other attempts.
+//
+// TODO(szym): Move to separate source file for testing and mocking.
+//
+class HostResolverImpl::ProcTask
+ : public base::RefCountedThreadSafe<HostResolverImpl::ProcTask> {
+ public:
+ typedef base::Callback<void(int net_error,
+ const AddressList& addr_list)> Callback;
+
+ ProcTask(const Key& key,
+ const ProcTaskParams& params,
+ const Callback& callback,
+ const BoundNetLog& job_net_log)
+ : key_(key),
+ params_(params),
+ callback_(callback),
+ origin_loop_(base::MessageLoopProxy::current()),
+ attempt_number_(0),
+ completed_attempt_number_(0),
+ completed_attempt_error_(ERR_UNEXPECTED),
+ had_non_speculative_request_(false),
+ net_log_(job_net_log) {
+ if (!params_.resolver_proc.get())
+ params_.resolver_proc = HostResolverProc::GetDefault();
+ // If default is unset, use the system proc.
+ if (!params_.resolver_proc.get())
+ params_.resolver_proc = new SystemHostResolverProc();
+ }
+
+ void Start() {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ net_log_.BeginEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK);
+ StartLookupAttempt();
+ }
+
+ // Cancels this ProcTask. It will be orphaned. Any outstanding resolve
+ // attempts running on worker threads will continue running. Only once all the
+ // attempts complete will the final reference to this ProcTask be released.
+ void Cancel() {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+
+ if (was_canceled() || was_completed())
+ return;
+
+ callback_.Reset();
+ net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK);
+ }
+
+ void set_had_non_speculative_request() {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ had_non_speculative_request_ = true;
+ }
+
+ bool was_canceled() const {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ return callback_.is_null();
+ }
+
+ bool was_completed() const {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ return completed_attempt_number_ > 0;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<ProcTask>;
+ ~ProcTask() {}
+
+ void StartLookupAttempt() {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ ++attempt_number_;
+ // Dispatch the lookup attempt to a worker thread.
+ if (!base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&ProcTask::DoLookup, this, start_time, attempt_number_),
+ true)) {
+ NOTREACHED();
+
+ // Since we could be running within Resolve() right now, we can't just
+ // call OnLookupComplete(). Instead we must wait until Resolve() has
+ // returned (IO_PENDING).
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ProcTask::OnLookupComplete, this, AddressList(),
+ start_time, attempt_number_, ERR_UNEXPECTED, 0));
+ return;
+ }
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_ATTEMPT_STARTED,
+ NetLog::IntegerCallback("attempt_number", attempt_number_));
+
+ // If we don't get the results within a given time, RetryIfNotComplete
+ // will start a new attempt on a different worker thread if none of our
+ // outstanding attempts have completed yet.
+ if (attempt_number_ <= params_.max_retry_attempts) {
+ origin_loop_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProcTask::RetryIfNotComplete, this),
+ params_.unresponsive_delay);
+ }
+ }
+
+ // WARNING: This code runs inside a worker pool. The shutdown code cannot
+ // wait for it to finish, so we must be very careful here about using other
+ // objects (like MessageLoops, Singletons, etc). During shutdown these objects
+ // may no longer exist. Multiple DoLookups() could be running in parallel, so
+ // any state inside of |this| must not mutate .
+ void DoLookup(const base::TimeTicks& start_time,
+ const uint32 attempt_number) {
+ AddressList results;
+ int os_error = 0;
+ // Running on the worker thread
+ int error = params_.resolver_proc->Resolve(key_.hostname,
+ key_.address_family,
+ key_.host_resolver_flags,
+ &results,
+ &os_error);
+
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&ProcTask::OnLookupComplete, this, results, start_time,
+ attempt_number, error, os_error));
+ }
+
+ // Makes next attempt if DoLookup() has not finished (runs on origin thread).
+ void RetryIfNotComplete() {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+
+ if (was_completed() || was_canceled())
+ return;
+
+ params_.unresponsive_delay *= params_.retry_factor;
+ StartLookupAttempt();
+ }
+
+ // Callback for when DoLookup() completes (runs on origin thread).
+ void OnLookupComplete(const AddressList& results,
+ const base::TimeTicks& start_time,
+ const uint32 attempt_number,
+ int error,
+ const int os_error) {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ // If results are empty, we should return an error.
+ bool empty_list_on_ok = (error == OK && results.empty());
+ UMA_HISTOGRAM_BOOLEAN("DNS.EmptyAddressListAndNoError", empty_list_on_ok);
+ if (empty_list_on_ok)
+ error = ERR_NAME_NOT_RESOLVED;
+
+ bool was_retry_attempt = attempt_number > 1;
+
+ // Ideally the following code would be part of host_resolver_proc.cc,
+ // however it isn't safe to call NetworkChangeNotifier from worker threads.
+ // So we do it here on the IO thread instead.
+ if (error != OK && NetworkChangeNotifier::IsOffline())
+ error = ERR_INTERNET_DISCONNECTED;
+
+ // If this is the first attempt that is finishing later, then record data
+ // for the first attempt. Won't contaminate with retry attempt's data.
+ if (!was_retry_attempt)
+ RecordPerformanceHistograms(start_time, error, os_error);
+
+ RecordAttemptHistograms(start_time, attempt_number, error, os_error);
+
+ if (was_canceled())
+ return;
+
+ NetLog::ParametersCallback net_log_callback;
+ if (error != OK) {
+ net_log_callback = base::Bind(&NetLogProcTaskFailedCallback,
+ attempt_number,
+ error,
+ os_error);
+ } else {
+ net_log_callback = NetLog::IntegerCallback("attempt_number",
+ attempt_number);
+ }
+ net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_ATTEMPT_FINISHED,
+ net_log_callback);
+
+ if (was_completed())
+ return;
+
+ // Copy the results from the first worker thread that resolves the host.
+ results_ = results;
+ completed_attempt_number_ = attempt_number;
+ completed_attempt_error_ = error;
+
+ if (was_retry_attempt) {
+ // If retry attempt finishes before 1st attempt, then get stats on how
+ // much time is saved by having spawned an extra attempt.
+ retry_attempt_finished_time_ = base::TimeTicks::Now();
+ }
+
+ if (error != OK) {
+ net_log_callback = base::Bind(&NetLogProcTaskFailedCallback,
+ 0, error, os_error);
+ } else {
+ net_log_callback = results_.CreateNetLogCallback();
+ }
+ net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_PROC_TASK,
+ net_log_callback);
+
+ callback_.Run(error, results_);
+ }
+
+ void RecordPerformanceHistograms(const base::TimeTicks& start_time,
+ const int error,
+ const int os_error) const {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ enum Category { // Used in HISTOGRAM_ENUMERATION.
+ RESOLVE_SUCCESS,
+ RESOLVE_FAIL,
+ RESOLVE_SPECULATIVE_SUCCESS,
+ RESOLVE_SPECULATIVE_FAIL,
+ RESOLVE_MAX, // Bounding value.
+ };
+ int category = RESOLVE_MAX; // Illegal value for later DCHECK only.
+
+ base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+ if (error == OK) {
+ if (had_non_speculative_request_) {
+ category = RESOLVE_SUCCESS;
+ DNS_HISTOGRAM("DNS.ResolveSuccess", duration);
+ } else {
+ category = RESOLVE_SPECULATIVE_SUCCESS;
+ DNS_HISTOGRAM("DNS.ResolveSpeculativeSuccess", duration);
+ }
+
+ // Log DNS lookups based on |address_family|. This will help us determine
+ // if IPv4 or IPv4/6 lookups are faster or slower.
+ switch(key_.address_family) {
+ case ADDRESS_FAMILY_IPV4:
+ DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV4", duration);
+ break;
+ case ADDRESS_FAMILY_IPV6:
+ DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_IPV6", duration);
+ break;
+ case ADDRESS_FAMILY_UNSPECIFIED:
+ DNS_HISTOGRAM("DNS.ResolveSuccess_FAMILY_UNSPEC", duration);
+ break;
+ }
+ } else {
+ if (had_non_speculative_request_) {
+ category = RESOLVE_FAIL;
+ DNS_HISTOGRAM("DNS.ResolveFail", duration);
+ } else {
+ category = RESOLVE_SPECULATIVE_FAIL;
+ DNS_HISTOGRAM("DNS.ResolveSpeculativeFail", duration);
+ }
+ // Log DNS lookups based on |address_family|. This will help us determine
+ // if IPv4 or IPv4/6 lookups are faster or slower.
+ switch(key_.address_family) {
+ case ADDRESS_FAMILY_IPV4:
+ DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV4", duration);
+ break;
+ case ADDRESS_FAMILY_IPV6:
+ DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_IPV6", duration);
+ break;
+ case ADDRESS_FAMILY_UNSPECIFIED:
+ DNS_HISTOGRAM("DNS.ResolveFail_FAMILY_UNSPEC", duration);
+ break;
+ }
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(kOSErrorsForGetAddrinfoHistogramName,
+ std::abs(os_error),
+ GetAllGetAddrinfoOSErrors());
+ }
+ DCHECK_LT(category, static_cast<int>(RESOLVE_MAX)); // Be sure it was set.
+
+ UMA_HISTOGRAM_ENUMERATION("DNS.ResolveCategory", category, RESOLVE_MAX);
+ }
+
+ void RecordAttemptHistograms(const base::TimeTicks& start_time,
+ const uint32 attempt_number,
+ const int error,
+ const int os_error) const {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+ bool first_attempt_to_complete =
+ completed_attempt_number_ == attempt_number;
+ bool is_first_attempt = (attempt_number == 1);
+
+ if (first_attempt_to_complete) {
+ // If this was first attempt to complete, then record the resolution
+ // status of the attempt.
+ if (completed_attempt_error_ == OK) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "DNS.AttemptFirstSuccess", attempt_number, 100);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(
+ "DNS.AttemptFirstFailure", attempt_number, 100);
+ }
+ }
+
+ if (error == OK)
+ UMA_HISTOGRAM_ENUMERATION("DNS.AttemptSuccess", attempt_number, 100);
+ else
+ UMA_HISTOGRAM_ENUMERATION("DNS.AttemptFailure", attempt_number, 100);
+
+ // If first attempt didn't finish before retry attempt, then calculate stats
+ // on how much time is saved by having spawned an extra attempt.
+ if (!first_attempt_to_complete && is_first_attempt && !was_canceled()) {
+ DNS_HISTOGRAM("DNS.AttemptTimeSavedByRetry",
+ base::TimeTicks::Now() - retry_attempt_finished_time_);
+ }
+
+ if (was_canceled() || !first_attempt_to_complete) {
+ // Count those attempts which completed after the job was already canceled
+ // OR after the job was already completed by an earlier attempt (so in
+ // effect).
+ UMA_HISTOGRAM_ENUMERATION("DNS.AttemptDiscarded", attempt_number, 100);
+
+ // Record if job is canceled.
+ if (was_canceled())
+ UMA_HISTOGRAM_ENUMERATION("DNS.AttemptCancelled", attempt_number, 100);
+ }
+
+ base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+ if (error == OK)
+ DNS_HISTOGRAM("DNS.AttemptSuccessDuration", duration);
+ else
+ DNS_HISTOGRAM("DNS.AttemptFailDuration", duration);
+ }
+
+ // Set on the origin thread, read on the worker thread.
+ Key key_;
+
+ // Holds an owning reference to the HostResolverProc that we are going to use.
+ // This may not be the current resolver procedure by the time we call
+ // ResolveAddrInfo, but that's OK... we'll use it anyways, and the owning
+ // reference ensures that it remains valid until we are done.
+ ProcTaskParams params_;
+
+ // The listener to the results of this ProcTask.
+ Callback callback_;
+
+ // Used to post ourselves onto the origin thread.
+ scoped_refptr<base::MessageLoopProxy> origin_loop_;
+
+ // Keeps track of the number of attempts we have made so far to resolve the
+ // host. Whenever we start an attempt to resolve the host, we increase this
+ // number.
+ uint32 attempt_number_;
+
+ // The index of the attempt which finished first (or 0 if the job is still in
+ // progress).
+ uint32 completed_attempt_number_;
+
+ // The result (a net error code) from the first attempt to complete.
+ int completed_attempt_error_;
+
+ // The time when retry attempt was finished.
+ base::TimeTicks retry_attempt_finished_time_;
+
+ // True if a non-speculative request was ever attached to this job
+ // (regardless of whether or not it was later canceled.
+ // This boolean is used for histogramming the duration of jobs used to
+ // service non-speculative requests.
+ bool had_non_speculative_request_;
+
+ AddressList results_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcTask);
+};
+
+//-----------------------------------------------------------------------------
+
+// Wraps a call to HaveOnlyLoopbackAddresses to be executed on the WorkerPool as
+// it takes 40-100ms and should not block initialization.
+class HostResolverImpl::LoopbackProbeJob {
+ public:
+ explicit LoopbackProbeJob(const base::WeakPtr<HostResolverImpl>& resolver)
+ : resolver_(resolver),
+ result_(false) {
+ DCHECK(resolver.get());
+ const bool kIsSlow = true;
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&LoopbackProbeJob::DoProbe, base::Unretained(this)),
+ base::Bind(&LoopbackProbeJob::OnProbeComplete, base::Owned(this)),
+ kIsSlow);
+ }
+
+ virtual ~LoopbackProbeJob() {}
+
+ private:
+ // Runs on worker thread.
+ void DoProbe() {
+ result_ = HaveOnlyLoopbackAddresses();
+ }
+
+ void OnProbeComplete() {
+ if (!resolver_.get())
+ return;
+ resolver_->SetHaveOnlyLoopbackAddresses(result_);
+ }
+
+ // Used/set only on origin thread.
+ base::WeakPtr<HostResolverImpl> resolver_;
+
+ bool result_;
+
+ DISALLOW_COPY_AND_ASSIGN(LoopbackProbeJob);
+};
+
+//-----------------------------------------------------------------------------
+
+// Resolves the hostname using DnsTransaction.
+// TODO(szym): This could be moved to separate source file as well.
+class HostResolverImpl::DnsTask : public base::SupportsWeakPtr<DnsTask> {
+ public:
+ typedef base::Callback<void(int net_error,
+ const AddressList& addr_list,
+ base::TimeDelta ttl)> Callback;
+
+ DnsTask(DnsClient* client,
+ const Key& key,
+ const Callback& callback,
+ const BoundNetLog& job_net_log)
+ : client_(client),
+ family_(key.address_family),
+ callback_(callback),
+ net_log_(job_net_log) {
+ DCHECK(client);
+ DCHECK(!callback.is_null());
+
+ // If unspecified, do IPv4 first, because suffix search will be faster.
+ uint16 qtype = (family_ == ADDRESS_FAMILY_IPV6) ?
+ dns_protocol::kTypeAAAA :
+ dns_protocol::kTypeA;
+ transaction_ = client_->GetTransactionFactory()->CreateTransaction(
+ key.hostname,
+ qtype,
+ base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this),
+ true /* first_query */, base::TimeTicks::Now()),
+ net_log_);
+ }
+
+ void Start() {
+ net_log_.BeginEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK);
+ transaction_->Start();
+ }
+
+ private:
+ void OnTransactionComplete(bool first_query,
+ const base::TimeTicks& start_time,
+ DnsTransaction* transaction,
+ int net_error,
+ const DnsResponse* response) {
+ DCHECK(transaction);
+ base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+ // Run |callback_| last since the owning Job will then delete this DnsTask.
+ if (net_error != OK) {
+ DNS_HISTOGRAM("AsyncDNS.TransactionFailure", duration);
+ OnFailure(net_error, DnsResponse::DNS_PARSE_OK);
+ return;
+ }
+
+ CHECK(response);
+ DNS_HISTOGRAM("AsyncDNS.TransactionSuccess", duration);
+ switch (transaction->GetType()) {
+ case dns_protocol::kTypeA:
+ DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_A", duration);
+ break;
+ case dns_protocol::kTypeAAAA:
+ DNS_HISTOGRAM("AsyncDNS.TransactionSuccess_AAAA", duration);
+ break;
+ }
+ AddressList addr_list;
+ base::TimeDelta ttl;
+ DnsResponse::Result result = response->ParseToAddressList(&addr_list, &ttl);
+ UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ParseToAddressList",
+ result,
+ DnsResponse::DNS_PARSE_RESULT_MAX);
+ if (result != DnsResponse::DNS_PARSE_OK) {
+ // Fail even if the other query succeeds.
+ OnFailure(ERR_DNS_MALFORMED_RESPONSE, result);
+ return;
+ }
+
+ bool needs_sort = false;
+ if (first_query) {
+ DCHECK(client_->GetConfig()) <<
+ "Transaction should have been aborted when config changed!";
+ if (family_ == ADDRESS_FAMILY_IPV6) {
+ needs_sort = (addr_list.size() > 1);
+ } else if (family_ == ADDRESS_FAMILY_UNSPECIFIED) {
+ first_addr_list_ = addr_list;
+ first_ttl_ = ttl;
+ // Use fully-qualified domain name to avoid search.
+ transaction_ = client_->GetTransactionFactory()->CreateTransaction(
+ response->GetDottedName() + ".",
+ dns_protocol::kTypeAAAA,
+ base::Bind(&DnsTask::OnTransactionComplete, base::Unretained(this),
+ false /* first_query */, base::TimeTicks::Now()),
+ net_log_);
+ transaction_->Start();
+ return;
+ }
+ } else {
+ DCHECK_EQ(ADDRESS_FAMILY_UNSPECIFIED, family_);
+ bool has_ipv6_addresses = !addr_list.empty();
+ if (!first_addr_list_.empty()) {
+ ttl = std::min(ttl, first_ttl_);
+ // Place IPv4 addresses after IPv6.
+ addr_list.insert(addr_list.end(), first_addr_list_.begin(),
+ first_addr_list_.end());
+ }
+ needs_sort = (has_ipv6_addresses && addr_list.size() > 1);
+ }
+
+ if (addr_list.empty()) {
+ // TODO(szym): Don't fallback to ProcTask in this case.
+ OnFailure(ERR_NAME_NOT_RESOLVED, DnsResponse::DNS_PARSE_OK);
+ return;
+ }
+
+ if (needs_sort) {
+ // Sort could complete synchronously.
+ client_->GetAddressSorter()->Sort(
+ addr_list,
+ base::Bind(&DnsTask::OnSortComplete,
+ AsWeakPtr(),
+ base::TimeTicks::Now(),
+ ttl));
+ } else {
+ OnSuccess(addr_list, ttl);
+ }
+ }
+
+ void OnSortComplete(base::TimeTicks start_time,
+ base::TimeDelta ttl,
+ bool success,
+ const AddressList& addr_list) {
+ if (!success) {
+ DNS_HISTOGRAM("AsyncDNS.SortFailure",
+ base::TimeTicks::Now() - start_time);
+ OnFailure(ERR_DNS_SORT_ERROR, DnsResponse::DNS_PARSE_OK);
+ return;
+ }
+
+ DNS_HISTOGRAM("AsyncDNS.SortSuccess",
+ base::TimeTicks::Now() - start_time);
+
+ // AddressSorter prunes unusable destinations.
+ if (addr_list.empty()) {
+ LOG(WARNING) << "Address list empty after RFC3484 sort";
+ OnFailure(ERR_NAME_NOT_RESOLVED, DnsResponse::DNS_PARSE_OK);
+ return;
+ }
+
+ OnSuccess(addr_list, ttl);
+ }
+
+ void OnFailure(int net_error, DnsResponse::Result result) {
+ DCHECK_NE(OK, net_error);
+ net_log_.EndEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK,
+ base::Bind(&NetLogDnsTaskFailedCallback, net_error, result));
+ callback_.Run(net_error, AddressList(), base::TimeDelta());
+ }
+
+ void OnSuccess(const AddressList& addr_list, base::TimeDelta ttl) {
+ net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_DNS_TASK,
+ addr_list.CreateNetLogCallback());
+ callback_.Run(OK, addr_list, ttl);
+ }
+
+ DnsClient* client_;
+ AddressFamily family_;
+ // The listener to the results of this DnsTask.
+ Callback callback_;
+ const BoundNetLog net_log_;
+
+ scoped_ptr<DnsTransaction> transaction_;
+
+ // Results from the first transaction. Used only if |family_| is unspecified.
+ AddressList first_addr_list_;
+ base::TimeDelta first_ttl_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsTask);
+};
+
+//-----------------------------------------------------------------------------
+
+// Aggregates all Requests for the same Key. Dispatched via PriorityDispatch.
+class HostResolverImpl::Job : public PrioritizedDispatcher::Job {
+ public:
+ // Creates new job for |key| where |request_net_log| is bound to the
+ // request that spawned it.
+ Job(const base::WeakPtr<HostResolverImpl>& resolver,
+ const Key& key,
+ RequestPriority priority,
+ const BoundNetLog& request_net_log)
+ : resolver_(resolver),
+ key_(key),
+ priority_tracker_(priority),
+ had_non_speculative_request_(false),
+ had_dns_config_(false),
+ dns_task_error_(OK),
+ creation_time_(base::TimeTicks::Now()),
+ priority_change_time_(creation_time_),
+ net_log_(BoundNetLog::Make(request_net_log.net_log(),
+ NetLog::SOURCE_HOST_RESOLVER_IMPL_JOB)) {
+ request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_CREATE_JOB);
+
+ net_log_.BeginEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_JOB,
+ base::Bind(&NetLogJobCreationCallback,
+ request_net_log.source(),
+ &key_.hostname));
+ }
+
+ virtual ~Job() {
+ if (is_running()) {
+ // |resolver_| was destroyed with this Job still in flight.
+ // Clean-up, record in the log, but don't run any callbacks.
+ if (is_proc_running()) {
+ proc_task_->Cancel();
+ proc_task_ = NULL;
+ }
+ // Clean up now for nice NetLog.
+ dns_task_.reset(NULL);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB,
+ ERR_ABORTED);
+ } else if (is_queued()) {
+ // |resolver_| was destroyed without running this Job.
+ // TODO(szym): is there any benefit in having this distinction?
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB);
+ }
+ // else CompleteRequests logged EndEvent.
+
+ // Log any remaining Requests as cancelled.
+ for (RequestsList::const_iterator it = requests_.begin();
+ it != requests_.end(); ++it) {
+ Request* req = *it;
+ if (req->was_canceled())
+ continue;
+ DCHECK_EQ(this, req->job());
+ LogCancelRequest(req->source_net_log(), req->request_net_log(),
+ req->info());
+ }
+ }
+
+ // Add this job to the dispatcher.
+ void Schedule() {
+ handle_ = resolver_->dispatcher_.Add(this, priority());
+ }
+
+ void AddRequest(scoped_ptr<Request> req) {
+ DCHECK_EQ(key_.hostname, req->info().hostname());
+
+ req->set_job(this);
+ priority_tracker_.Add(req->info().priority());
+
+ req->request_net_log().AddEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_ATTACH,
+ net_log_.source().ToEventParametersCallback());
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_REQUEST_ATTACH,
+ base::Bind(&NetLogJobAttachCallback,
+ req->request_net_log().source(),
+ priority()));
+
+ // TODO(szym): Check if this is still needed.
+ if (!req->info().is_speculative()) {
+ had_non_speculative_request_ = true;
+ if (proc_task_.get())
+ proc_task_->set_had_non_speculative_request();
+ }
+
+ requests_.push_back(req.release());
+
+ UpdatePriority();
+ }
+
+ // Marks |req| as cancelled. If it was the last active Request, also finishes
+ // this Job, marking it as cancelled, and deletes it.
+ void CancelRequest(Request* req) {
+ DCHECK_EQ(key_.hostname, req->info().hostname());
+ DCHECK(!req->was_canceled());
+
+ // Don't remove it from |requests_| just mark it canceled.
+ req->MarkAsCanceled();
+ LogCancelRequest(req->source_net_log(), req->request_net_log(),
+ req->info());
+
+ priority_tracker_.Remove(req->info().priority());
+ net_log_.AddEvent(
+ NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_REQUEST_DETACH,
+ base::Bind(&NetLogJobAttachCallback,
+ req->request_net_log().source(),
+ priority()));
+
+ if (num_active_requests() > 0) {
+ UpdatePriority();
+ } else {
+ // If we were called from a Request's callback within CompleteRequests,
+ // that Request could not have been cancelled, so num_active_requests()
+ // could not be 0. Therefore, we are not in CompleteRequests().
+ CompleteRequestsWithError(OK /* cancelled */);
+ }
+ }
+
+ // Called from AbortAllInProgressJobs. Completes all requests and destroys
+ // the job. This currently assumes the abort is due to a network change.
+ void Abort() {
+ DCHECK(is_running());
+ CompleteRequestsWithError(ERR_NETWORK_CHANGED);
+ }
+
+ // If DnsTask present, abort it and fall back to ProcTask.
+ void AbortDnsTask() {
+ if (dns_task_) {
+ dns_task_.reset();
+ dns_task_error_ = OK;
+ StartProcTask();
+ }
+ }
+
+ // Called by HostResolverImpl when this job is evicted due to queue overflow.
+ // Completes all requests and destroys the job.
+ void OnEvicted() {
+ DCHECK(!is_running());
+ DCHECK(is_queued());
+ handle_.Reset();
+
+ net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_EVICTED);
+
+ // This signals to CompleteRequests that this job never ran.
+ CompleteRequestsWithError(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE);
+ }
+
+ // Attempts to serve the job from HOSTS. Returns true if succeeded and
+ // this Job was destroyed.
+ bool ServeFromHosts() {
+ DCHECK_GT(num_active_requests(), 0u);
+ AddressList addr_list;
+ if (resolver_->ServeFromHosts(key(),
+ requests_.front()->info(),
+ &addr_list)) {
+ // This will destroy the Job.
+ CompleteRequests(
+ HostCache::Entry(OK, MakeAddressListForRequest(addr_list)),
+ base::TimeDelta());
+ return true;
+ }
+ return false;
+ }
+
+ const Key key() const {
+ return key_;
+ }
+
+ bool is_queued() const {
+ return !handle_.is_null();
+ }
+
+ bool is_running() const {
+ return is_dns_running() || is_proc_running();
+ }
+
+ private:
+ void UpdatePriority() {
+ if (is_queued()) {
+ if (priority() != static_cast<RequestPriority>(handle_.priority()))
+ priority_change_time_ = base::TimeTicks::Now();
+ handle_ = resolver_->dispatcher_.ChangePriority(handle_, priority());
+ }
+ }
+
+ AddressList MakeAddressListForRequest(const AddressList& list) const {
+ if (requests_.empty())
+ return list;
+ return AddressList::CopyWithPort(list, requests_.front()->info().port());
+ }
+
+ // PriorityDispatch::Job:
+ virtual void Start() OVERRIDE {
+ DCHECK(!is_running());
+ handle_.Reset();
+
+ net_log_.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB_STARTED);
+
+ had_dns_config_ = resolver_->HaveDnsConfig();
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta queue_time = now - creation_time_;
+ base::TimeDelta queue_time_after_change = now - priority_change_time_;
+
+ if (had_dns_config_) {
+ DNS_HISTOGRAM_BY_PRIORITY("AsyncDNS.JobQueueTime", priority(),
+ queue_time);
+ DNS_HISTOGRAM_BY_PRIORITY("AsyncDNS.JobQueueTimeAfterChange", priority(),
+ queue_time_after_change);
+ } else {
+ DNS_HISTOGRAM_BY_PRIORITY("DNS.JobQueueTime", priority(), queue_time);
+ DNS_HISTOGRAM_BY_PRIORITY("DNS.JobQueueTimeAfterChange", priority(),
+ queue_time_after_change);
+ }
+
+ // Caution: Job::Start must not complete synchronously.
+ if (had_dns_config_ && !ResemblesMulticastDNSName(key_.hostname)) {
+ StartDnsTask();
+ } else {
+ StartProcTask();
+ }
+ }
+
+ // TODO(szym): Since DnsTransaction does not consume threads, we can increase
+ // the limits on |dispatcher_|. But in order to keep the number of WorkerPool
+ // threads low, we will need to use an "inner" PrioritizedDispatcher with
+ // tighter limits.
+ void StartProcTask() {
+ DCHECK(!is_dns_running());
+ proc_task_ = new ProcTask(
+ key_,
+ resolver_->proc_params_,
+ base::Bind(&Job::OnProcTaskComplete, base::Unretained(this),
+ base::TimeTicks::Now()),
+ net_log_);
+
+ if (had_non_speculative_request_)
+ proc_task_->set_had_non_speculative_request();
+ // Start() could be called from within Resolve(), hence it must NOT directly
+ // call OnProcTaskComplete, for example, on synchronous failure.
+ proc_task_->Start();
+ }
+
+ // Called by ProcTask when it completes.
+ void OnProcTaskComplete(base::TimeTicks start_time,
+ int net_error,
+ const AddressList& addr_list) {
+ DCHECK(is_proc_running());
+
+ if (!resolver_->resolved_known_ipv6_hostname_ &&
+ net_error == OK &&
+ key_.address_family == ADDRESS_FAMILY_UNSPECIFIED) {
+ if (key_.hostname == "www.google.com") {
+ resolver_->resolved_known_ipv6_hostname_ = true;
+ bool got_ipv6_address = false;
+ for (size_t i = 0; i < addr_list.size(); ++i) {
+ if (addr_list[i].GetFamily() == ADDRESS_FAMILY_IPV6) {
+ got_ipv6_address = true;
+ break;
+ }
+ }
+ UMA_HISTOGRAM_BOOLEAN("Net.UnspecResolvedIPv6", got_ipv6_address);
+ }
+ }
+
+ if (dns_task_error_ != OK) {
+ base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+ if (net_error == OK) {
+ DNS_HISTOGRAM("AsyncDNS.FallbackSuccess", duration);
+ if ((dns_task_error_ == ERR_NAME_NOT_RESOLVED) &&
+ ResemblesNetBIOSName(key_.hostname)) {
+ UmaAsyncDnsResolveStatus(RESOLVE_STATUS_SUSPECT_NETBIOS);
+ } else {
+ UmaAsyncDnsResolveStatus(RESOLVE_STATUS_PROC_SUCCESS);
+ }
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION("AsyncDNS.ResolveError",
+ std::abs(dns_task_error_),
+ GetAllErrorCodesForUma());
+ resolver_->OnDnsTaskResolve(dns_task_error_);
+ } else {
+ DNS_HISTOGRAM("AsyncDNS.FallbackFail", duration);
+ UmaAsyncDnsResolveStatus(RESOLVE_STATUS_FAIL);
+ }
+ }
+
+ base::TimeDelta ttl =
+ base::TimeDelta::FromSeconds(kNegativeCacheEntryTTLSeconds);
+ if (net_error == OK)
+ ttl = base::TimeDelta::FromSeconds(kCacheEntryTTLSeconds);
+
+ // Don't store the |ttl| in cache since it's not obtained from the server.
+ CompleteRequests(
+ HostCache::Entry(net_error, MakeAddressListForRequest(addr_list)),
+ ttl);
+ }
+
+ void StartDnsTask() {
+ DCHECK(resolver_->HaveDnsConfig());
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ dns_task_.reset(new DnsTask(
+ resolver_->dns_client_.get(),
+ key_,
+ base::Bind(&Job::OnDnsTaskComplete, base::Unretained(this), start_time),
+ net_log_));
+
+ dns_task_->Start();
+ }
+
+ // Called if DnsTask fails. It is posted from StartDnsTask, so Job may be
+ // deleted before this callback. In this case dns_task is deleted as well,
+ // so we use it as indicator whether Job is still valid.
+ void OnDnsTaskFailure(const base::WeakPtr<DnsTask>& dns_task,
+ base::TimeDelta duration,
+ int net_error) {
+ DNS_HISTOGRAM("AsyncDNS.ResolveFail", duration);
+
+ if (dns_task == NULL)
+ return;
+
+ dns_task_error_ = net_error;
+
+ // TODO(szym): Run ServeFromHosts now if nsswitch.conf says so.
+ // http://crbug.com/117655
+
+ // TODO(szym): Some net errors indicate lack of connectivity. Starting
+ // ProcTask in that case is a waste of time.
+ if (resolver_->fallback_to_proctask_) {
+ dns_task_.reset();
+ StartProcTask();
+ } else {
+ UmaAsyncDnsResolveStatus(RESOLVE_STATUS_FAIL);
+ CompleteRequestsWithError(net_error);
+ }
+ }
+
+ // Called by DnsTask when it completes.
+ void OnDnsTaskComplete(base::TimeTicks start_time,
+ int net_error,
+ const AddressList& addr_list,
+ base::TimeDelta ttl) {
+ DCHECK(is_dns_running());
+
+ base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+ if (net_error != OK) {
+ OnDnsTaskFailure(dns_task_->AsWeakPtr(), duration, net_error);
+ return;
+ }
+ DNS_HISTOGRAM("AsyncDNS.ResolveSuccess", duration);
+ // Log DNS lookups based on |address_family|.
+ switch(key_.address_family) {
+ case ADDRESS_FAMILY_IPV4:
+ DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV4", duration);
+ break;
+ case ADDRESS_FAMILY_IPV6:
+ DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_IPV6", duration);
+ break;
+ case ADDRESS_FAMILY_UNSPECIFIED:
+ DNS_HISTOGRAM("AsyncDNS.ResolveSuccess_FAMILY_UNSPEC", duration);
+ break;
+ }
+
+ UmaAsyncDnsResolveStatus(RESOLVE_STATUS_DNS_SUCCESS);
+ RecordTTL(ttl);
+
+ resolver_->OnDnsTaskResolve(OK);
+
+ base::TimeDelta bounded_ttl =
+ std::max(ttl, base::TimeDelta::FromSeconds(kMinimumTTLSeconds));
+
+ CompleteRequests(
+ HostCache::Entry(net_error, MakeAddressListForRequest(addr_list), ttl),
+ bounded_ttl);
+ }
+
+ // Performs Job's last rites. Completes all Requests. Deletes this.
+ void CompleteRequests(const HostCache::Entry& entry,
+ base::TimeDelta ttl) {
+ CHECK(resolver_.get());
+
+ // This job must be removed from resolver's |jobs_| now to make room for a
+ // new job with the same key in case one of the OnComplete callbacks decides
+ // to spawn one. Consequently, the job deletes itself when CompleteRequests
+ // is done.
+ scoped_ptr<Job> self_deleter(this);
+
+ resolver_->RemoveJob(this);
+
+ if (is_running()) {
+ DCHECK(!is_queued());
+ if (is_proc_running()) {
+ proc_task_->Cancel();
+ proc_task_ = NULL;
+ }
+ dns_task_.reset();
+
+ // Signal dispatcher that a slot has opened.
+ resolver_->dispatcher_.OnJobFinished();
+ } else if (is_queued()) {
+ resolver_->dispatcher_.Cancel(handle_);
+ handle_.Reset();
+ }
+
+ if (num_active_requests() == 0) {
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB,
+ OK);
+ return;
+ }
+
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HOST_RESOLVER_IMPL_JOB,
+ entry.error);
+
+ DCHECK(!requests_.empty());
+
+ if (entry.error == OK) {
+ // Record this histogram here, when we know the system has a valid DNS
+ // configuration.
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HaveDnsConfig",
+ resolver_->received_dns_config_);
+ }
+
+ bool did_complete = (entry.error != ERR_NETWORK_CHANGED) &&
+ (entry.error != ERR_HOST_RESOLVER_QUEUE_TOO_LARGE);
+ if (did_complete)
+ resolver_->CacheResult(key_, entry, ttl);
+
+ // Complete all of the requests that were attached to the job.
+ for (RequestsList::const_iterator it = requests_.begin();
+ it != requests_.end(); ++it) {
+ Request* req = *it;
+
+ if (req->was_canceled())
+ continue;
+
+ DCHECK_EQ(this, req->job());
+ // Update the net log and notify registered observers.
+ LogFinishRequest(req->source_net_log(), req->request_net_log(),
+ req->info(), entry.error);
+ if (did_complete) {
+ // Record effective total time from creation to completion.
+ RecordTotalTime(had_dns_config_, req->info().is_speculative(),
+ base::TimeTicks::Now() - req->request_time());
+ }
+ req->OnComplete(entry.error, entry.addrlist);
+
+ // Check if the resolver was destroyed as a result of running the
+ // callback. If it was, we could continue, but we choose to bail.
+ if (!resolver_.get())
+ return;
+ }
+ }
+
+ // Convenience wrapper for CompleteRequests in case of failure.
+ void CompleteRequestsWithError(int net_error) {
+ CompleteRequests(HostCache::Entry(net_error, AddressList()),
+ base::TimeDelta());
+ }
+
+ RequestPriority priority() const {
+ return priority_tracker_.highest_priority();
+ }
+
+ // Number of non-canceled requests in |requests_|.
+ size_t num_active_requests() const {
+ return priority_tracker_.total_count();
+ }
+
+ bool is_dns_running() const {
+ return dns_task_.get() != NULL;
+ }
+
+ bool is_proc_running() const {
+ return proc_task_.get() != NULL;
+ }
+
+ base::WeakPtr<HostResolverImpl> resolver_;
+
+ Key key_;
+
+ // Tracks the highest priority across |requests_|.
+ PriorityTracker priority_tracker_;
+
+ bool had_non_speculative_request_;
+
+ // Distinguishes measurements taken while DnsClient was fully configured.
+ bool had_dns_config_;
+
+ // Result of DnsTask.
+ int dns_task_error_;
+
+ const base::TimeTicks creation_time_;
+ base::TimeTicks priority_change_time_;
+
+ BoundNetLog net_log_;
+
+ // Resolves the host using a HostResolverProc.
+ scoped_refptr<ProcTask> proc_task_;
+
+ // Resolves the host using a DnsTransaction.
+ scoped_ptr<DnsTask> dns_task_;
+
+ // All Requests waiting for the result of this Job. Some can be canceled.
+ RequestsList requests_;
+
+ // A handle used in |HostResolverImpl::dispatcher_|.
+ PrioritizedDispatcher::Handle handle_;
+};
+
+//-----------------------------------------------------------------------------
+
+HostResolverImpl::ProcTaskParams::ProcTaskParams(
+ HostResolverProc* resolver_proc,
+ size_t max_retry_attempts)
+ : resolver_proc(resolver_proc),
+ max_retry_attempts(max_retry_attempts),
+ unresponsive_delay(base::TimeDelta::FromMilliseconds(6000)),
+ retry_factor(2) {
+}
+
+HostResolverImpl::ProcTaskParams::~ProcTaskParams() {}
+
+HostResolverImpl::HostResolverImpl(
+ scoped_ptr<HostCache> cache,
+ const PrioritizedDispatcher::Limits& job_limits,
+ const ProcTaskParams& proc_params,
+ NetLog* net_log)
+ : cache_(cache.Pass()),
+ dispatcher_(job_limits),
+ max_queued_jobs_(job_limits.total_jobs * 100u),
+ proc_params_(proc_params),
+ net_log_(net_log),
+ default_address_family_(ADDRESS_FAMILY_UNSPECIFIED),
+ weak_ptr_factory_(this),
+ probe_weak_ptr_factory_(this),
+ received_dns_config_(false),
+ num_dns_failures_(0),
+ probe_ipv6_support_(true),
+ resolved_known_ipv6_hostname_(false),
+ additional_resolver_flags_(0),
+ fallback_to_proctask_(true) {
+
+ DCHECK_GE(dispatcher_.num_priorities(), static_cast<size_t>(NUM_PRIORITIES));
+
+ // Maximum of 4 retry attempts for host resolution.
+ static const size_t kDefaultMaxRetryAttempts = 4u;
+
+ if (proc_params_.max_retry_attempts == HostResolver::kDefaultRetryAttempts)
+ proc_params_.max_retry_attempts = kDefaultMaxRetryAttempts;
+
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ new LoopbackProbeJob(weak_ptr_factory_.GetWeakPtr());
+#endif
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ NetworkChangeNotifier::AddDNSObserver(this);
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \
+ !defined(OS_ANDROID)
+ EnsureDnsReloaderInit();
+#endif
+
+ // TODO(szym): Remove when received_dns_config_ is removed, once
+ // http://crbug.com/137914 is resolved.
+ {
+ DnsConfig dns_config;
+ NetworkChangeNotifier::GetDnsConfig(&dns_config);
+ received_dns_config_ = dns_config.IsValid();
+ }
+
+ fallback_to_proctask_ = !ConfigureAsyncDnsNoFallbackFieldTrial();
+}
+
+HostResolverImpl::~HostResolverImpl() {
+ // This will also cancel all outstanding requests.
+ STLDeleteValues(&jobs_);
+
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ NetworkChangeNotifier::RemoveDNSObserver(this);
+}
+
+void HostResolverImpl::SetMaxQueuedJobs(size_t value) {
+ DCHECK_EQ(0u, dispatcher_.num_queued_jobs());
+ DCHECK_GT(value, 0u);
+ max_queued_jobs_ = value;
+}
+
+int HostResolverImpl::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& source_net_log) {
+ DCHECK(addresses);
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(false, callback.is_null());
+
+ // Check that the caller supplied a valid hostname to resolve.
+ std::string labeled_hostname;
+ if (!DNSDomainFromDot(info.hostname(), &labeled_hostname))
+ return ERR_NAME_NOT_RESOLVED;
+
+ // Make a log item for the request.
+ BoundNetLog request_net_log = BoundNetLog::Make(net_log_,
+ NetLog::SOURCE_HOST_RESOLVER_IMPL_REQUEST);
+
+ LogStartRequest(source_net_log, request_net_log, info);
+
+ // Build a key that identifies the request in the cache and in the
+ // outstanding jobs map.
+ Key key = GetEffectiveKeyForRequest(info, request_net_log);
+
+ int rv = ResolveHelper(key, info, addresses, request_net_log);
+ if (rv != ERR_DNS_CACHE_MISS) {
+ LogFinishRequest(source_net_log, request_net_log, info, rv);
+ RecordTotalTime(HaveDnsConfig(), info.is_speculative(), base::TimeDelta());
+ return rv;
+ }
+
+ // Next we need to attach our request to a "job". This job is responsible for
+ // calling "getaddrinfo(hostname)" on a worker thread.
+
+ JobMap::iterator jobit = jobs_.find(key);
+ Job* job;
+ if (jobit == jobs_.end()) {
+ job = new Job(weak_ptr_factory_.GetWeakPtr(), key, info.priority(),
+ request_net_log);
+ job->Schedule();
+
+ // Check for queue overflow.
+ if (dispatcher_.num_queued_jobs() > max_queued_jobs_) {
+ Job* evicted = static_cast<Job*>(dispatcher_.EvictOldestLowest());
+ DCHECK(evicted);
+ evicted->OnEvicted(); // Deletes |evicted|.
+ if (evicted == job) {
+ rv = ERR_HOST_RESOLVER_QUEUE_TOO_LARGE;
+ LogFinishRequest(source_net_log, request_net_log, info, rv);
+ return rv;
+ }
+ }
+ jobs_.insert(jobit, std::make_pair(key, job));
+ } else {
+ job = jobit->second;
+ }
+
+ // Can't complete synchronously. Create and attach request.
+ scoped_ptr<Request> req(new Request(source_net_log,
+ request_net_log,
+ info,
+ callback,
+ addresses));
+ if (out_req)
+ *out_req = reinterpret_cast<RequestHandle>(req.get());
+
+ job->AddRequest(req.Pass());
+ // Completion happens during Job::CompleteRequests().
+ return ERR_IO_PENDING;
+}
+
+int HostResolverImpl::ResolveHelper(const Key& key,
+ const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& request_net_log) {
+ // The result of |getaddrinfo| for empty hosts is inconsistent across systems.
+ // On Windows it gives the default interface's address, whereas on Linux it
+ // gives an error. We will make it fail on all platforms for consistency.
+ if (info.hostname().empty() || info.hostname().size() > kMaxHostLength)
+ return ERR_NAME_NOT_RESOLVED;
+
+ int net_error = ERR_UNEXPECTED;
+ if (ResolveAsIP(key, info, &net_error, addresses))
+ return net_error;
+ if (ServeFromCache(key, info, &net_error, addresses)) {
+ request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_CACHE_HIT);
+ return net_error;
+ }
+ // TODO(szym): Do not do this if nsswitch.conf instructs not to.
+ // http://crbug.com/117655
+ if (ServeFromHosts(key, info, addresses)) {
+ request_net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_HOSTS_HIT);
+ return OK;
+ }
+ return ERR_DNS_CACHE_MISS;
+}
+
+int HostResolverImpl::ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& source_net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(addresses);
+
+ // Make a log item for the request.
+ BoundNetLog request_net_log = BoundNetLog::Make(net_log_,
+ NetLog::SOURCE_HOST_RESOLVER_IMPL_REQUEST);
+
+ // Update the net log and notify registered observers.
+ LogStartRequest(source_net_log, request_net_log, info);
+
+ Key key = GetEffectiveKeyForRequest(info, request_net_log);
+
+ int rv = ResolveHelper(key, info, addresses, request_net_log);
+ LogFinishRequest(source_net_log, request_net_log, info, rv);
+ return rv;
+}
+
+void HostResolverImpl::CancelRequest(RequestHandle req_handle) {
+ DCHECK(CalledOnValidThread());
+ Request* req = reinterpret_cast<Request*>(req_handle);
+ DCHECK(req);
+ Job* job = req->job();
+ DCHECK(job);
+ job->CancelRequest(req);
+}
+
+void HostResolverImpl::SetDefaultAddressFamily(AddressFamily address_family) {
+ DCHECK(CalledOnValidThread());
+ default_address_family_ = address_family;
+ probe_ipv6_support_ = false;
+}
+
+AddressFamily HostResolverImpl::GetDefaultAddressFamily() const {
+ return default_address_family_;
+}
+
+void HostResolverImpl::SetDnsClientEnabled(bool enabled) {
+ DCHECK(CalledOnValidThread());
+#if defined(ENABLE_BUILT_IN_DNS)
+ if (enabled && !dns_client_) {
+ SetDnsClient(DnsClient::CreateClient(net_log_));
+ } else if (!enabled && dns_client_) {
+ SetDnsClient(scoped_ptr<DnsClient>());
+ }
+#endif
+}
+
+HostCache* HostResolverImpl::GetHostCache() {
+ return cache_.get();
+}
+
+base::Value* HostResolverImpl::GetDnsConfigAsValue() const {
+ // Check if async DNS is disabled.
+ if (!dns_client_.get())
+ return NULL;
+
+ // Check if async DNS is enabled, but we currently have no configuration
+ // for it.
+ const DnsConfig* dns_config = dns_client_->GetConfig();
+ if (dns_config == NULL)
+ return new base::DictionaryValue();
+
+ return dns_config->ToValue();
+}
+
+bool HostResolverImpl::ResolveAsIP(const Key& key,
+ const RequestInfo& info,
+ int* net_error,
+ AddressList* addresses) {
+ DCHECK(addresses);
+ DCHECK(net_error);
+ IPAddressNumber ip_number;
+ if (!ParseIPLiteralToNumber(key.hostname, &ip_number))
+ return false;
+
+ DCHECK_EQ(key.host_resolver_flags &
+ ~(HOST_RESOLVER_CANONNAME | HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6),
+ 0) << " Unhandled flag";
+ bool ipv6_disabled = (default_address_family_ == ADDRESS_FAMILY_IPV4) &&
+ !probe_ipv6_support_;
+ *net_error = OK;
+ if ((ip_number.size() == kIPv6AddressSize) && ipv6_disabled) {
+ *net_error = ERR_NAME_NOT_RESOLVED;
+ } else {
+ *addresses = AddressList::CreateFromIPAddress(ip_number, info.port());
+ if (key.host_resolver_flags & HOST_RESOLVER_CANONNAME)
+ addresses->SetDefaultCanonicalName();
+ }
+ return true;
+}
+
+bool HostResolverImpl::ServeFromCache(const Key& key,
+ const RequestInfo& info,
+ int* net_error,
+ AddressList* addresses) {
+ DCHECK(addresses);
+ DCHECK(net_error);
+ if (!info.allow_cached_response() || !cache_.get())
+ return false;
+
+ const HostCache::Entry* cache_entry = cache_->Lookup(
+ key, base::TimeTicks::Now());
+ if (!cache_entry)
+ return false;
+
+ *net_error = cache_entry->error;
+ if (*net_error == OK) {
+ if (cache_entry->has_ttl())
+ RecordTTL(cache_entry->ttl);
+ *addresses = EnsurePortOnAddressList(cache_entry->addrlist, info.port());
+ }
+ return true;
+}
+
+bool HostResolverImpl::ServeFromHosts(const Key& key,
+ const RequestInfo& info,
+ AddressList* addresses) {
+ DCHECK(addresses);
+ if (!HaveDnsConfig())
+ return false;
+ addresses->clear();
+
+ // HOSTS lookups are case-insensitive.
+ std::string hostname = StringToLowerASCII(key.hostname);
+
+ const DnsHosts& hosts = dns_client_->GetConfig()->hosts;
+
+ // If |address_family| is ADDRESS_FAMILY_UNSPECIFIED other implementations
+ // (glibc and c-ares) return the first matching line. We have more
+ // flexibility, but lose implicit ordering.
+ // We prefer IPv6 because "happy eyeballs" will fall back to IPv4 if
+ // necessary.
+ if (key.address_family == ADDRESS_FAMILY_IPV6 ||
+ key.address_family == ADDRESS_FAMILY_UNSPECIFIED) {
+ DnsHosts::const_iterator it = hosts.find(
+ DnsHostsKey(hostname, ADDRESS_FAMILY_IPV6));
+ if (it != hosts.end())
+ addresses->push_back(IPEndPoint(it->second, info.port()));
+ }
+
+ if (key.address_family == ADDRESS_FAMILY_IPV4 ||
+ key.address_family == ADDRESS_FAMILY_UNSPECIFIED) {
+ DnsHosts::const_iterator it = hosts.find(
+ DnsHostsKey(hostname, ADDRESS_FAMILY_IPV4));
+ if (it != hosts.end())
+ addresses->push_back(IPEndPoint(it->second, info.port()));
+ }
+
+ // If got only loopback addresses and the family was restricted, resolve
+ // again, without restrictions. See SystemHostResolverCall for rationale.
+ if ((key.host_resolver_flags &
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) &&
+ IsAllIPv4Loopback(*addresses)) {
+ Key new_key(key);
+ new_key.address_family = ADDRESS_FAMILY_UNSPECIFIED;
+ new_key.host_resolver_flags &=
+ ~HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ return ServeFromHosts(new_key, info, addresses);
+ }
+ return !addresses->empty();
+}
+
+void HostResolverImpl::CacheResult(const Key& key,
+ const HostCache::Entry& entry,
+ base::TimeDelta ttl) {
+ if (cache_.get())
+ cache_->Set(key, entry, base::TimeTicks::Now(), ttl);
+}
+
+void HostResolverImpl::RemoveJob(Job* job) {
+ DCHECK(job);
+ JobMap::iterator it = jobs_.find(job->key());
+ if (it != jobs_.end() && it->second == job)
+ jobs_.erase(it);
+}
+
+void HostResolverImpl::SetHaveOnlyLoopbackAddresses(bool result) {
+ if (result) {
+ additional_resolver_flags_ |= HOST_RESOLVER_LOOPBACK_ONLY;
+ } else {
+ additional_resolver_flags_ &= ~HOST_RESOLVER_LOOPBACK_ONLY;
+ }
+}
+
+HostResolverImpl::Key HostResolverImpl::GetEffectiveKeyForRequest(
+ const RequestInfo& info, const BoundNetLog& net_log) const {
+ HostResolverFlags effective_flags =
+ info.host_resolver_flags() | additional_resolver_flags_;
+ AddressFamily effective_address_family = info.address_family();
+
+ if (info.address_family() == ADDRESS_FAMILY_UNSPECIFIED) {
+ if (probe_ipv6_support_) {
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ // Google DNS address.
+ const uint8 kIPv6Address[] =
+ { 0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88 };
+ IPAddressNumber address(kIPv6Address,
+ kIPv6Address + arraysize(kIPv6Address));
+ bool rv6 = IsGloballyReachable(address, net_log);
+ if (rv6)
+ net_log.AddEvent(NetLog::TYPE_HOST_RESOLVER_IMPL_IPV6_SUPPORTED);
+
+ UMA_HISTOGRAM_TIMES("Net.IPv6ConnectDuration",
+ base::TimeTicks::Now() - start_time);
+ if (rv6) {
+ UMA_HISTOGRAM_BOOLEAN("Net.IPv6ConnectSuccessMatch",
+ default_address_family_ == ADDRESS_FAMILY_UNSPECIFIED);
+ } else {
+ UMA_HISTOGRAM_BOOLEAN("Net.IPv6ConnectFailureMatch",
+ default_address_family_ != ADDRESS_FAMILY_UNSPECIFIED);
+
+ effective_address_family = ADDRESS_FAMILY_IPV4;
+ effective_flags |= HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ }
+ } else {
+ effective_address_family = default_address_family_;
+ }
+ }
+
+ return Key(info.hostname(), effective_address_family, effective_flags);
+}
+
+void HostResolverImpl::AbortAllInProgressJobs() {
+ // In Abort, a Request callback could spawn new Jobs with matching keys, so
+ // first collect and remove all running jobs from |jobs_|.
+ ScopedVector<Job> jobs_to_abort;
+ for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ) {
+ Job* job = it->second;
+ if (job->is_running()) {
+ jobs_to_abort.push_back(job);
+ jobs_.erase(it++);
+ } else {
+ DCHECK(job->is_queued());
+ ++it;
+ }
+ }
+
+ // Check if no dispatcher slots leaked out.
+ DCHECK_EQ(dispatcher_.num_running_jobs(), jobs_to_abort.size());
+
+ // Life check to bail once |this| is deleted.
+ base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr();
+
+ // Then Abort them.
+ for (size_t i = 0; self.get() && i < jobs_to_abort.size(); ++i) {
+ jobs_to_abort[i]->Abort();
+ jobs_to_abort[i] = NULL;
+ }
+}
+
+void HostResolverImpl::TryServingAllJobsFromHosts() {
+ if (!HaveDnsConfig())
+ return;
+
+ // TODO(szym): Do not do this if nsswitch.conf instructs not to.
+ // http://crbug.com/117655
+
+ // Life check to bail once |this| is deleted.
+ base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr();
+
+ for (JobMap::iterator it = jobs_.begin(); self.get() && it != jobs_.end();) {
+ Job* job = it->second;
+ ++it;
+ // This could remove |job| from |jobs_|, but iterator will remain valid.
+ job->ServeFromHosts();
+ }
+}
+
+void HostResolverImpl::OnIPAddressChanged() {
+ resolved_known_ipv6_hostname_ = false;
+ // Abandon all ProbeJobs.
+ probe_weak_ptr_factory_.InvalidateWeakPtrs();
+ if (cache_.get())
+ cache_->clear();
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ new LoopbackProbeJob(probe_weak_ptr_factory_.GetWeakPtr());
+#endif
+ AbortAllInProgressJobs();
+ // |this| may be deleted inside AbortAllInProgressJobs().
+}
+
+void HostResolverImpl::OnDNSChanged() {
+ DnsConfig dns_config;
+ NetworkChangeNotifier::GetDnsConfig(&dns_config);
+
+ if (net_log_) {
+ net_log_->AddGlobalEntry(
+ NetLog::TYPE_DNS_CONFIG_CHANGED,
+ base::Bind(&NetLogDnsConfigCallback, &dns_config));
+ }
+
+ // TODO(szym): Remove once http://crbug.com/137914 is resolved.
+ received_dns_config_ = dns_config.IsValid();
+
+ num_dns_failures_ = 0;
+
+ // We want a new DnsSession in place, before we Abort running Jobs, so that
+ // the newly started jobs use the new config.
+ if (dns_client_.get()) {
+ dns_client_->SetConfig(dns_config);
+ if (dns_config.IsValid())
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", true);
+ }
+
+ // If the DNS server has changed, existing cached info could be wrong so we
+ // have to drop our internal cache :( Note that OS level DNS caches, such
+ // as NSCD's cache should be dropped automatically by the OS when
+ // resolv.conf changes so we don't need to do anything to clear that cache.
+ if (cache_.get())
+ cache_->clear();
+
+ // Life check to bail once |this| is deleted.
+ base::WeakPtr<HostResolverImpl> self = weak_ptr_factory_.GetWeakPtr();
+
+ // Existing jobs will have been sent to the original server so they need to
+ // be aborted.
+ AbortAllInProgressJobs();
+
+ // |this| may be deleted inside AbortAllInProgressJobs().
+ if (self.get())
+ TryServingAllJobsFromHosts();
+}
+
+bool HostResolverImpl::HaveDnsConfig() const {
+ // Use DnsClient only if it's fully configured and there is no override by
+ // ScopedDefaultHostResolverProc.
+ // The alternative is to use NetworkChangeNotifier to override DnsConfig,
+ // but that would introduce construction order requirements for NCN and SDHRP.
+ return (dns_client_.get() != NULL) && (dns_client_->GetConfig() != NULL) &&
+ !(proc_params_.resolver_proc.get() == NULL &&
+ HostResolverProc::GetDefault() != NULL);
+}
+
+void HostResolverImpl::OnDnsTaskResolve(int net_error) {
+ DCHECK(dns_client_);
+ if (net_error == OK) {
+ num_dns_failures_ = 0;
+ return;
+ }
+ ++num_dns_failures_;
+ if (num_dns_failures_ < kMaximumDnsFailures)
+ return;
+ // Disable DnsClient until the next DNS change.
+ for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
+ it->second->AbortDnsTask();
+ dns_client_->SetConfig(DnsConfig());
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", false);
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION("AsyncDNS.DnsClientDisabledReason",
+ std::abs(net_error),
+ GetAllErrorCodesForUma());
+}
+
+void HostResolverImpl::SetDnsClient(scoped_ptr<DnsClient> dns_client) {
+ if (HaveDnsConfig()) {
+ for (JobMap::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
+ it->second->AbortDnsTask();
+ }
+ dns_client_ = dns_client.Pass();
+ if (!dns_client_ || dns_client_->GetConfig() ||
+ num_dns_failures_ >= kMaximumDnsFailures) {
+ return;
+ }
+ DnsConfig dns_config;
+ NetworkChangeNotifier::GetDnsConfig(&dns_config);
+ dns_client_->SetConfig(dns_config);
+ num_dns_failures_ = 0;
+ if (dns_config.IsValid())
+ UMA_HISTOGRAM_BOOLEAN("AsyncDNS.DnsClientEnabled", true);
+}
+
+} // namespace net
diff --git a/chromium/net/dns/host_resolver_impl.h b/chromium/net/dns/host_resolver_impl.h
new file mode 100644
index 00000000000..928d07af8b8
--- /dev/null
+++ b/chromium/net/dns/host_resolver_impl.h
@@ -0,0 +1,285 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_HOST_RESOLVER_IMPL_H_
+#define NET_DNS_HOST_RESOLVER_IMPL_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/prioritized_dispatcher.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/host_resolver_proc.h"
+
+namespace net {
+
+class BoundNetLog;
+class DnsClient;
+class NetLog;
+
+// For each hostname that is requested, HostResolver creates a
+// HostResolverImpl::Job. When this job gets dispatched it creates a ProcTask
+// which runs the given HostResolverProc on a WorkerPool thread. If requests for
+// that same host are made during the job's lifetime, they are attached to the
+// existing job rather than creating a new one. This avoids doing parallel
+// resolves for the same host.
+//
+// The way these classes fit together is illustrated by:
+//
+//
+// +----------- HostResolverImpl -------------+
+// | | |
+// Job Job Job
+// (for host1, fam1) (for host2, fam2) (for hostx, famx)
+// / | | / | | / | |
+// Request ... Request Request ... Request Request ... Request
+// (port1) (port2) (port3) (port4) (port5) (portX)
+//
+// When a HostResolverImpl::Job finishes, the callbacks of each waiting request
+// are run on the origin thread.
+//
+// Thread safety: This class is not threadsafe, and must only be called
+// from one thread!
+//
+// The HostResolverImpl enforces limits on the maximum number of concurrent
+// threads using PrioritizedDispatcher::Limits.
+//
+// Jobs are ordered in the queue based on their priority and order of arrival.
+class NET_EXPORT HostResolverImpl
+ : public HostResolver,
+ NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::DNSObserver {
+ public:
+ // Parameters for ProcTask which resolves hostnames using HostResolveProc.
+ //
+ // |resolver_proc| is used to perform the actual resolves; it must be
+ // thread-safe since it is run from multiple worker threads. If
+ // |resolver_proc| is NULL then the default host resolver procedure is
+ // used (which is SystemHostResolverProc except if overridden).
+ //
+ // For each attempt, we could start another attempt if host is not resolved
+ // within |unresponsive_delay| time. We keep attempting to resolve the host
+ // for |max_retry_attempts|. For every retry attempt, we grow the
+ // |unresponsive_delay| by the |retry_factor| amount (that is retry interval
+ // is multiplied by the retry factor each time). Once we have retried
+ // |max_retry_attempts|, we give up on additional attempts.
+ //
+ struct NET_EXPORT_PRIVATE ProcTaskParams {
+ // Sets up defaults.
+ ProcTaskParams(HostResolverProc* resolver_proc, size_t max_retry_attempts);
+
+ ~ProcTaskParams();
+
+ // The procedure to use for resolving host names. This will be NULL, except
+ // in the case of unit-tests which inject custom host resolving behaviors.
+ scoped_refptr<HostResolverProc> resolver_proc;
+
+ // Maximum number retry attempts to resolve the hostname.
+ // Pass HostResolver::kDefaultRetryAttempts to choose a default value.
+ size_t max_retry_attempts;
+
+ // This is the limit after which we make another attempt to resolve the host
+ // if the worker thread has not responded yet.
+ base::TimeDelta unresponsive_delay;
+
+ // Factor to grow |unresponsive_delay| when we re-re-try.
+ uint32 retry_factor;
+ };
+
+ // Creates a HostResolver that first uses the local cache |cache|, and then
+ // falls back to |proc_params.resolver_proc|.
+ //
+ // If |cache| is NULL, then no caching is used. Otherwise we take
+ // ownership of the |cache| pointer, and will free it during destruction.
+ //
+ // |job_limits| specifies the maximum number of jobs that the resolver will
+ // run at once. This upper-bounds the total number of outstanding
+ // DNS transactions (not counting retransmissions and retries).
+ //
+ // |net_log| must remain valid for the life of the HostResolverImpl.
+ HostResolverImpl(scoped_ptr<HostCache> cache,
+ const PrioritizedDispatcher::Limits& job_limits,
+ const ProcTaskParams& proc_params,
+ NetLog* net_log);
+
+ // If any completion callbacks are pending when the resolver is destroyed,
+ // the host resolutions are cancelled, and the completion callbacks will not
+ // be called.
+ virtual ~HostResolverImpl();
+
+ // Configures maximum number of Jobs in the queue. Exposed for testing.
+ // Only allowed when the queue is empty.
+ void SetMaxQueuedJobs(size_t value);
+
+ // Set the DnsClient to be used for resolution. In case of failure, the
+ // HostResolverProc from ProcTaskParams will be queried. If the DnsClient is
+ // not pre-configured with a valid DnsConfig, a new config is fetched from
+ // NetworkChangeNotifier.
+ void SetDnsClient(scoped_ptr<DnsClient> dns_client);
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& source_net_log) OVERRIDE;
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& source_net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle req) OVERRIDE;
+ virtual void SetDefaultAddressFamily(AddressFamily address_family) OVERRIDE;
+ virtual AddressFamily GetDefaultAddressFamily() const OVERRIDE;
+ virtual void SetDnsClientEnabled(bool enabled) OVERRIDE;
+ virtual HostCache* GetHostCache() OVERRIDE;
+ virtual base::Value* GetDnsConfigAsValue() const OVERRIDE;
+
+ private:
+ friend class HostResolverImplTest;
+ class Job;
+ class ProcTask;
+ class LoopbackProbeJob;
+ class DnsTask;
+ class Request;
+ typedef HostCache::Key Key;
+ typedef std::map<Key, Job*> JobMap;
+ typedef ScopedVector<Request> RequestsList;
+
+ // Helper used by |Resolve()| and |ResolveFromCache()|. Performs IP
+ // literal, cache and HOSTS lookup (if enabled), returns OK if successful,
+ // ERR_NAME_NOT_RESOLVED if either hostname is invalid or IP literal is
+ // incompatible, ERR_DNS_CACHE_MISS if entry was not found in cache and HOSTS.
+ int ResolveHelper(const Key& key,
+ const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& request_net_log);
+
+ // Tries to resolve |key| as an IP, returns true and sets |net_error| if
+ // succeeds, returns false otherwise.
+ bool ResolveAsIP(const Key& key,
+ const RequestInfo& info,
+ int* net_error,
+ AddressList* addresses);
+
+ // If |key| is not found in cache returns false, otherwise returns
+ // true, sets |net_error| to the cached error code and fills |addresses|
+ // if it is a positive entry.
+ bool ServeFromCache(const Key& key,
+ const RequestInfo& info,
+ int* net_error,
+ AddressList* addresses);
+
+ // If we have a DnsClient with a valid DnsConfig, and |key| is found in the
+ // HOSTS file, returns true and fills |addresses|. Otherwise returns false.
+ bool ServeFromHosts(const Key& key,
+ const RequestInfo& info,
+ AddressList* addresses);
+
+ // Callback from HaveOnlyLoopbackAddresses probe.
+ void SetHaveOnlyLoopbackAddresses(bool result);
+
+ // Returns the (hostname, address_family) key to use for |info|, choosing an
+ // "effective" address family by inheriting the resolver's default address
+ // family when the request leaves it unspecified.
+ Key GetEffectiveKeyForRequest(const RequestInfo& info,
+ const BoundNetLog& net_log) const;
+
+ // Records the result in cache if cache is present.
+ void CacheResult(const Key& key,
+ const HostCache::Entry& entry,
+ base::TimeDelta ttl);
+
+ // Removes |job| from |jobs_|, only if it exists.
+ void RemoveJob(Job* job);
+
+ // Aborts all in progress jobs with ERR_NETWORK_CHANGED and notifies their
+ // requests. Might start new jobs.
+ void AbortAllInProgressJobs();
+
+ // Attempts to serve each Job in |jobs_| from the HOSTS file if we have
+ // a DnsClient with a valid DnsConfig.
+ void TryServingAllJobsFromHosts();
+
+ // NetworkChangeNotifier::IPAddressObserver:
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // NetworkChangeNotifier::DNSObserver:
+ virtual void OnDNSChanged() OVERRIDE;
+
+ // True if have a DnsClient with a valid DnsConfig.
+ bool HaveDnsConfig() const;
+
+ // Called when a host name is successfully resolved and DnsTask was run on it
+ // and resulted in |net_error|.
+ void OnDnsTaskResolve(int net_error);
+
+ // Allows the tests to catch slots leaking out of the dispatcher.
+ size_t num_running_jobs_for_tests() const {
+ return dispatcher_.num_running_jobs();
+ }
+
+ // Cache of host resolution results.
+ scoped_ptr<HostCache> cache_;
+
+ // Map from HostCache::Key to a Job.
+ JobMap jobs_;
+
+ // Starts Jobs according to their priority and the configured limits.
+ PrioritizedDispatcher dispatcher_;
+
+ // Limit on the maximum number of jobs queued in |dispatcher_|.
+ size_t max_queued_jobs_;
+
+ // Parameters for ProcTask.
+ ProcTaskParams proc_params_;
+
+ NetLog* net_log_;
+
+ // Address family to use when the request doesn't specify one.
+ AddressFamily default_address_family_;
+
+ base::WeakPtrFactory<HostResolverImpl> weak_ptr_factory_;
+
+ base::WeakPtrFactory<HostResolverImpl> probe_weak_ptr_factory_;
+
+ // If present, used by DnsTask and ServeFromHosts to resolve requests.
+ scoped_ptr<DnsClient> dns_client_;
+
+ // True if received valid config from |dns_config_service_|. Temporary, used
+ // to measure performance of DnsConfigService: http://crbug.com/125599
+ bool received_dns_config_;
+
+ // Number of consecutive failures of DnsTask, counted when fallback succeeds.
+ unsigned num_dns_failures_;
+
+ // True if probing is done for each Request to set address family. When false,
+ // explicit setting in |default_address_family_| is used.
+ bool probe_ipv6_support_;
+
+ // True iff ProcTask has successfully resolved a hostname known to have IPv6
+ // addresses using ADDRESS_FAMILY_UNSPECIFIED. Reset on IP address change.
+ bool resolved_known_ipv6_hostname_;
+
+ // Any resolver flags that should be added to a request by default.
+ HostResolverFlags additional_resolver_flags_;
+
+ // Allow fallback to ProcTask if DnsTask fails.
+ bool fallback_to_proctask_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostResolverImpl);
+};
+
+} // namespace net
+
+#endif // NET_DNS_HOST_RESOLVER_IMPL_H_
diff --git a/chromium/net/dns/host_resolver_impl_unittest.cc b/chromium/net/dns/host_resolver_impl_unittest.cc
new file mode 100644
index 00000000000..f6b7f690675
--- /dev/null
+++ b/chromium/net/dns/host_resolver_impl_unittest.cc
@@ -0,0 +1,1641 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_resolver_impl.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_test_util.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const size_t kMaxJobs = 10u;
+const size_t kMaxRetryAttempts = 4u;
+
+PrioritizedDispatcher::Limits DefaultLimits() {
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, kMaxJobs);
+ return limits;
+}
+
+HostResolverImpl::ProcTaskParams DefaultParams(
+ HostResolverProc* resolver_proc) {
+ return HostResolverImpl::ProcTaskParams(resolver_proc, kMaxRetryAttempts);
+}
+
+// A HostResolverProc that pushes each host mapped into a list and allows
+// waiting for a specific number of requests. Unlike RuleBasedHostResolverProc
+// it never calls SystemHostResolverCall. By default resolves all hostnames to
+// "127.0.0.1". After AddRule(), it resolves only names explicitly specified.
+class MockHostResolverProc : public HostResolverProc {
+ public:
+ struct ResolveKey {
+ ResolveKey(const std::string& hostname, AddressFamily address_family)
+ : hostname(hostname), address_family(address_family) {}
+ bool operator<(const ResolveKey& other) const {
+ return address_family < other.address_family ||
+ (address_family == other.address_family && hostname < other.hostname);
+ }
+ std::string hostname;
+ AddressFamily address_family;
+ };
+
+ typedef std::vector<ResolveKey> CaptureList;
+
+ MockHostResolverProc()
+ : HostResolverProc(NULL),
+ num_requests_waiting_(0),
+ num_slots_available_(0),
+ requests_waiting_(&lock_),
+ slots_available_(&lock_) {
+ }
+
+ // Waits until |count| calls to |Resolve| are blocked. Returns false when
+ // timed out.
+ bool WaitFor(unsigned count) {
+ base::AutoLock lock(lock_);
+ base::Time start_time = base::Time::Now();
+ while (num_requests_waiting_ < count) {
+ requests_waiting_.TimedWait(TestTimeouts::action_timeout());
+ if (base::Time::Now() > start_time + TestTimeouts::action_timeout())
+ return false;
+ }
+ return true;
+ }
+
+ // Signals |count| waiting calls to |Resolve|. First come first served.
+ void SignalMultiple(unsigned count) {
+ base::AutoLock lock(lock_);
+ num_slots_available_ += count;
+ slots_available_.Broadcast();
+ }
+
+ // Signals all waiting calls to |Resolve|. Beware of races.
+ void SignalAll() {
+ base::AutoLock lock(lock_);
+ num_slots_available_ = num_requests_waiting_;
+ slots_available_.Broadcast();
+ }
+
+ void AddRule(const std::string& hostname, AddressFamily family,
+ const AddressList& result) {
+ base::AutoLock lock(lock_);
+ rules_[ResolveKey(hostname, family)] = result;
+ }
+
+ void AddRule(const std::string& hostname, AddressFamily family,
+ const std::string& ip_list) {
+ AddressList result;
+ int rv = ParseAddressList(ip_list, std::string(), &result);
+ DCHECK_EQ(OK, rv);
+ AddRule(hostname, family, result);
+ }
+
+ void AddRuleForAllFamilies(const std::string& hostname,
+ const std::string& ip_list) {
+ AddressList result;
+ int rv = ParseAddressList(ip_list, std::string(), &result);
+ DCHECK_EQ(OK, rv);
+ AddRule(hostname, ADDRESS_FAMILY_UNSPECIFIED, result);
+ AddRule(hostname, ADDRESS_FAMILY_IPV4, result);
+ AddRule(hostname, ADDRESS_FAMILY_IPV6, result);
+ }
+
+ virtual int Resolve(const std::string& hostname,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) OVERRIDE {
+ base::AutoLock lock(lock_);
+ capture_list_.push_back(ResolveKey(hostname, address_family));
+ ++num_requests_waiting_;
+ requests_waiting_.Broadcast();
+ while (!num_slots_available_)
+ slots_available_.Wait();
+ DCHECK_GT(num_requests_waiting_, 0u);
+ --num_slots_available_;
+ --num_requests_waiting_;
+ if (rules_.empty()) {
+ int rv = ParseAddressList("127.0.0.1", std::string(), addrlist);
+ DCHECK_EQ(OK, rv);
+ return OK;
+ }
+ ResolveKey key(hostname, address_family);
+ if (rules_.count(key) == 0)
+ return ERR_NAME_NOT_RESOLVED;
+ *addrlist = rules_[key];
+ return OK;
+ }
+
+ CaptureList GetCaptureList() const {
+ CaptureList copy;
+ {
+ base::AutoLock lock(lock_);
+ copy = capture_list_;
+ }
+ return copy;
+ }
+
+ bool HasBlockedRequests() const {
+ base::AutoLock lock(lock_);
+ return num_requests_waiting_ > num_slots_available_;
+ }
+
+ protected:
+ virtual ~MockHostResolverProc() {}
+
+ private:
+ mutable base::Lock lock_;
+ std::map<ResolveKey, AddressList> rules_;
+ CaptureList capture_list_;
+ unsigned num_requests_waiting_;
+ unsigned num_slots_available_;
+ base::ConditionVariable requests_waiting_;
+ base::ConditionVariable slots_available_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHostResolverProc);
+};
+
+bool AddressListContains(const AddressList& list, const std::string& address,
+ int port) {
+ IPAddressNumber ip;
+ bool rv = ParseIPLiteralToNumber(address, &ip);
+ DCHECK(rv);
+ return std::find(list.begin(),
+ list.end(),
+ IPEndPoint(ip, port)) != list.end();
+}
+
+// A wrapper for requests to a HostResolver.
+class Request {
+ public:
+ // Base class of handlers to be executed on completion of requests.
+ struct Handler {
+ virtual ~Handler() {}
+ virtual void Handle(Request* request) = 0;
+ };
+
+ Request(const HostResolver::RequestInfo& info,
+ size_t index,
+ HostResolver* resolver,
+ Handler* handler)
+ : info_(info),
+ index_(index),
+ resolver_(resolver),
+ handler_(handler),
+ quit_on_complete_(false),
+ result_(ERR_UNEXPECTED),
+ handle_(NULL) {}
+
+ int Resolve() {
+ DCHECK(resolver_);
+ DCHECK(!handle_);
+ list_ = AddressList();
+ result_ = resolver_->Resolve(
+ info_, &list_, base::Bind(&Request::OnComplete, base::Unretained(this)),
+ &handle_, BoundNetLog());
+ if (!list_.empty())
+ EXPECT_EQ(OK, result_);
+ return result_;
+ }
+
+ int ResolveFromCache() {
+ DCHECK(resolver_);
+ DCHECK(!handle_);
+ return resolver_->ResolveFromCache(info_, &list_, BoundNetLog());
+ }
+
+ void Cancel() {
+ DCHECK(resolver_);
+ DCHECK(handle_);
+ resolver_->CancelRequest(handle_);
+ handle_ = NULL;
+ }
+
+ const HostResolver::RequestInfo& info() const { return info_; }
+ size_t index() const { return index_; }
+ const AddressList& list() const { return list_; }
+ int result() const { return result_; }
+ bool completed() const { return result_ != ERR_IO_PENDING; }
+ bool pending() const { return handle_ != NULL; }
+
+ bool HasAddress(const std::string& address, int port) const {
+ return AddressListContains(list_, address, port);
+ }
+
+ // Returns the number of addresses in |list_|.
+ unsigned NumberOfAddresses() const {
+ return list_.size();
+ }
+
+ bool HasOneAddress(const std::string& address, int port) const {
+ return HasAddress(address, port) && (NumberOfAddresses() == 1u);
+ }
+
+ // Returns ERR_UNEXPECTED if timed out.
+ int WaitForResult() {
+ if (completed())
+ return result_;
+ base::CancelableClosure closure(base::MessageLoop::QuitClosure());
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, closure.callback(), TestTimeouts::action_max_timeout());
+ quit_on_complete_ = true;
+ base::MessageLoop::current()->Run();
+ bool did_quit = !quit_on_complete_;
+ quit_on_complete_ = false;
+ closure.Cancel();
+ if (did_quit)
+ return result_;
+ else
+ return ERR_UNEXPECTED;
+ }
+
+ private:
+ void OnComplete(int rv) {
+ EXPECT_TRUE(pending());
+ EXPECT_EQ(ERR_IO_PENDING, result_);
+ EXPECT_NE(ERR_IO_PENDING, rv);
+ result_ = rv;
+ handle_ = NULL;
+ if (!list_.empty()) {
+ EXPECT_EQ(OK, result_);
+ EXPECT_EQ(info_.port(), list_.front().port());
+ }
+ if (handler_)
+ handler_->Handle(this);
+ if (quit_on_complete_) {
+ base::MessageLoop::current()->Quit();
+ quit_on_complete_ = false;
+ }
+ }
+
+ HostResolver::RequestInfo info_;
+ size_t index_;
+ HostResolver* resolver_;
+ Handler* handler_;
+ bool quit_on_complete_;
+
+ AddressList list_;
+ int result_;
+ HostResolver::RequestHandle handle_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+// Using LookupAttemptHostResolverProc simulate very long lookups, and control
+// which attempt resolves the host.
+class LookupAttemptHostResolverProc : public HostResolverProc {
+ public:
+ LookupAttemptHostResolverProc(HostResolverProc* previous,
+ int attempt_number_to_resolve,
+ int total_attempts)
+ : HostResolverProc(previous),
+ attempt_number_to_resolve_(attempt_number_to_resolve),
+ current_attempt_number_(0),
+ total_attempts_(total_attempts),
+ total_attempts_resolved_(0),
+ resolved_attempt_number_(0),
+ all_done_(&lock_) {
+ }
+
+ // Test harness will wait for all attempts to finish before checking the
+ // results.
+ void WaitForAllAttemptsToFinish(const base::TimeDelta& wait_time) {
+ base::TimeTicks end_time = base::TimeTicks::Now() + wait_time;
+ {
+ base::AutoLock auto_lock(lock_);
+ while (total_attempts_resolved_ != total_attempts_ &&
+ base::TimeTicks::Now() < end_time) {
+ all_done_.TimedWait(end_time - base::TimeTicks::Now());
+ }
+ }
+ }
+
+ // All attempts will wait for an attempt to resolve the host.
+ void WaitForAnAttemptToComplete() {
+ base::TimeDelta wait_time = base::TimeDelta::FromSeconds(60);
+ base::TimeTicks end_time = base::TimeTicks::Now() + wait_time;
+ {
+ base::AutoLock auto_lock(lock_);
+ while (resolved_attempt_number_ == 0 && base::TimeTicks::Now() < end_time)
+ all_done_.TimedWait(end_time - base::TimeTicks::Now());
+ }
+ all_done_.Broadcast(); // Tell all waiting attempts to proceed.
+ }
+
+ // Returns the number of attempts that have finished the Resolve() method.
+ int total_attempts_resolved() { return total_attempts_resolved_; }
+
+ // Returns the first attempt that that has resolved the host.
+ int resolved_attempt_number() { return resolved_attempt_number_; }
+
+ // HostResolverProc methods.
+ virtual int Resolve(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) OVERRIDE {
+ bool wait_for_right_attempt_to_complete = true;
+ {
+ base::AutoLock auto_lock(lock_);
+ ++current_attempt_number_;
+ if (current_attempt_number_ == attempt_number_to_resolve_) {
+ resolved_attempt_number_ = current_attempt_number_;
+ wait_for_right_attempt_to_complete = false;
+ }
+ }
+
+ if (wait_for_right_attempt_to_complete)
+ // Wait for the attempt_number_to_resolve_ attempt to resolve.
+ WaitForAnAttemptToComplete();
+
+ int result = ResolveUsingPrevious(host, address_family, host_resolver_flags,
+ addrlist, os_error);
+
+ {
+ base::AutoLock auto_lock(lock_);
+ ++total_attempts_resolved_;
+ }
+
+ all_done_.Broadcast(); // Tell all attempts to proceed.
+
+ // Since any negative number is considered a network error, with -1 having
+ // special meaning (ERR_IO_PENDING). We could return the attempt that has
+ // resolved the host as a negative number. For example, if attempt number 3
+ // resolves the host, then this method returns -4.
+ if (result == OK)
+ return -1 - resolved_attempt_number_;
+ else
+ return result;
+ }
+
+ protected:
+ virtual ~LookupAttemptHostResolverProc() {}
+
+ private:
+ int attempt_number_to_resolve_;
+ int current_attempt_number_; // Incremented whenever Resolve is called.
+ int total_attempts_;
+ int total_attempts_resolved_;
+ int resolved_attempt_number_;
+
+ // All attempts wait for right attempt to be resolve.
+ base::Lock lock_;
+ base::ConditionVariable all_done_;
+};
+
+} // namespace
+
+class HostResolverImplTest : public testing::Test {
+ public:
+ static const int kDefaultPort = 80;
+
+ HostResolverImplTest() : proc_(new MockHostResolverProc()) {}
+
+ protected:
+ // A Request::Handler which is a proxy to the HostResolverImplTest fixture.
+ struct Handler : public Request::Handler {
+ virtual ~Handler() {}
+
+ // Proxy functions so that classes derived from Handler can access them.
+ Request* CreateRequest(const HostResolver::RequestInfo& info) {
+ return test->CreateRequest(info);
+ }
+ Request* CreateRequest(const std::string& hostname, int port) {
+ return test->CreateRequest(hostname, port);
+ }
+ Request* CreateRequest(const std::string& hostname) {
+ return test->CreateRequest(hostname);
+ }
+ ScopedVector<Request>& requests() { return test->requests_; }
+
+ void DeleteResolver() { test->resolver_.reset(); }
+
+ HostResolverImplTest* test;
+ };
+
+ void CreateResolver() {
+ resolver_.reset(new HostResolverImpl(HostCache::CreateDefaultCache(),
+ DefaultLimits(),
+ DefaultParams(proc_.get()),
+ NULL));
+ }
+
+ // This HostResolverImpl will only allow 1 outstanding resolve at a time and
+ // perform no retries.
+ void CreateSerialResolver() {
+ HostResolverImpl::ProcTaskParams params = DefaultParams(proc_.get());
+ params.max_retry_attempts = 0u;
+ PrioritizedDispatcher::Limits limits(NUM_PRIORITIES, 1);
+ resolver_.reset(new HostResolverImpl(
+ HostCache::CreateDefaultCache(),
+ limits,
+ params,
+ NULL));
+ }
+
+ // The Request will not be made until a call to |Resolve()|, and the Job will
+ // not start until released by |proc_->SignalXXX|.
+ Request* CreateRequest(const HostResolver::RequestInfo& info) {
+ Request* req = new Request(info, requests_.size(), resolver_.get(),
+ handler_.get());
+ requests_.push_back(req);
+ return req;
+ }
+
+ Request* CreateRequest(const std::string& hostname,
+ int port,
+ RequestPriority priority,
+ AddressFamily family) {
+ HostResolver::RequestInfo info(HostPortPair(hostname, port));
+ info.set_priority(priority);
+ info.set_address_family(family);
+ return CreateRequest(info);
+ }
+
+ Request* CreateRequest(const std::string& hostname,
+ int port,
+ RequestPriority priority) {
+ return CreateRequest(hostname, port, priority, ADDRESS_FAMILY_UNSPECIFIED);
+ }
+
+ Request* CreateRequest(const std::string& hostname, int port) {
+ return CreateRequest(hostname, port, MEDIUM);
+ }
+
+ Request* CreateRequest(const std::string& hostname) {
+ return CreateRequest(hostname, kDefaultPort);
+ }
+
+ virtual void SetUp() OVERRIDE {
+ CreateResolver();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (resolver_.get())
+ EXPECT_EQ(0u, resolver_->num_running_jobs_for_tests());
+ EXPECT_FALSE(proc_->HasBlockedRequests());
+ }
+
+ void set_handler(Handler* handler) {
+ handler_.reset(handler);
+ handler_->test = this;
+ }
+
+ // Friendship is not inherited, so use proxies to access those.
+ size_t num_running_jobs() const {
+ DCHECK(resolver_.get());
+ return resolver_->num_running_jobs_for_tests();
+ }
+
+ void set_fallback_to_proctask(bool fallback_to_proctask) {
+ DCHECK(resolver_.get());
+ resolver_->fallback_to_proctask_ = fallback_to_proctask;
+ }
+
+ scoped_refptr<MockHostResolverProc> proc_;
+ scoped_ptr<HostResolverImpl> resolver_;
+ ScopedVector<Request> requests_;
+
+ scoped_ptr<Handler> handler_;
+};
+
+TEST_F(HostResolverImplTest, AsynchronousLookup) {
+ proc_->AddRuleForAllFamilies("just.testing", "192.168.1.42");
+ proc_->SignalMultiple(1u);
+
+ Request* req = CreateRequest("just.testing", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+
+ EXPECT_TRUE(req->HasOneAddress("192.168.1.42", 80));
+
+ EXPECT_EQ("just.testing", proc_->GetCaptureList()[0].hostname);
+}
+
+TEST_F(HostResolverImplTest, EmptyListMeansNameNotResolved) {
+ proc_->AddRuleForAllFamilies("just.testing", "");
+ proc_->SignalMultiple(1u);
+
+ Request* req = CreateRequest("just.testing", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->WaitForResult());
+ EXPECT_EQ(0u, req->NumberOfAddresses());
+ EXPECT_EQ("just.testing", proc_->GetCaptureList()[0].hostname);
+}
+
+TEST_F(HostResolverImplTest, FailedAsynchronousLookup) {
+ proc_->AddRuleForAllFamilies(std::string(),
+ "0.0.0.0"); // Default to failures.
+ proc_->SignalMultiple(1u);
+
+ Request* req = CreateRequest("just.testing", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->WaitForResult());
+
+ EXPECT_EQ("just.testing", proc_->GetCaptureList()[0].hostname);
+
+ // Also test that the error is not cached.
+ EXPECT_EQ(ERR_DNS_CACHE_MISS, req->ResolveFromCache());
+}
+
+TEST_F(HostResolverImplTest, AbortedAsynchronousLookup) {
+ Request* req0 = CreateRequest("just.testing", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req0->Resolve());
+
+ EXPECT_TRUE(proc_->WaitFor(1u));
+
+ // Resolver is destroyed while job is running on WorkerPool.
+ resolver_.reset();
+
+ proc_->SignalAll();
+
+ // To ensure there was no spurious callback, complete with a new resolver.
+ CreateResolver();
+ Request* req1 = CreateRequest("just.testing", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req1->Resolve());
+
+ proc_->SignalMultiple(2u);
+
+ EXPECT_EQ(OK, req1->WaitForResult());
+
+ // This request was canceled.
+ EXPECT_FALSE(req0->completed());
+}
+
+TEST_F(HostResolverImplTest, NumericIPv4Address) {
+ // Stevens says dotted quads with AI_UNSPEC resolve to a single sockaddr_in.
+ Request* req = CreateRequest("127.1.2.3", 5555);
+ EXPECT_EQ(OK, req->Resolve());
+
+ EXPECT_TRUE(req->HasOneAddress("127.1.2.3", 5555));
+}
+
+TEST_F(HostResolverImplTest, NumericIPv6Address) {
+ // Resolve a plain IPv6 address. Don't worry about [brackets], because
+ // the caller should have removed them.
+ Request* req = CreateRequest("2001:db8::1", 5555);
+ EXPECT_EQ(OK, req->Resolve());
+
+ EXPECT_TRUE(req->HasOneAddress("2001:db8::1", 5555));
+}
+
+TEST_F(HostResolverImplTest, EmptyHost) {
+ Request* req = CreateRequest(std::string(), 5555);
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->Resolve());
+}
+
+TEST_F(HostResolverImplTest, EmptyDotsHost) {
+ for (int i = 0; i < 16; ++i) {
+ Request* req = CreateRequest(std::string(i, '.'), 5555);
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->Resolve());
+ }
+}
+
+TEST_F(HostResolverImplTest, LongHost) {
+ Request* req = CreateRequest(std::string(4097, 'a'), 5555);
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->Resolve());
+}
+
+TEST_F(HostResolverImplTest, DeDupeRequests) {
+ // Start 5 requests, duplicating hosts "a" and "b". Since the resolver_proc is
+ // blocked, these should all pile up until we signal it.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 81)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 82)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve());
+
+ proc_->SignalMultiple(2u); // One for "a", one for "b".
+
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+ }
+}
+
+TEST_F(HostResolverImplTest, CancelMultipleRequests) {
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 81)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 82)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve());
+
+ // Cancel everything except request for ("a", 82).
+ requests_[0]->Cancel();
+ requests_[1]->Cancel();
+ requests_[2]->Cancel();
+ requests_[4]->Cancel();
+
+ proc_->SignalMultiple(2u); // One for "a", one for "b".
+
+ EXPECT_EQ(OK, requests_[3]->WaitForResult());
+}
+
+TEST_F(HostResolverImplTest, CanceledRequestsReleaseJobSlots) {
+ // Fill up the dispatcher and queue.
+ for (unsigned i = 0; i < kMaxJobs + 1; ++i) {
+ std::string hostname = "a_";
+ hostname[1] = 'a' + i;
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 81)->Resolve());
+ }
+
+ EXPECT_TRUE(proc_->WaitFor(kMaxJobs));
+
+ // Cancel all but last two.
+ for (unsigned i = 0; i < requests_.size() - 2; ++i) {
+ requests_[i]->Cancel();
+ }
+
+ EXPECT_TRUE(proc_->WaitFor(kMaxJobs + 1));
+
+ proc_->SignalAll();
+
+ size_t num_requests = requests_.size();
+ EXPECT_EQ(OK, requests_[num_requests - 1]->WaitForResult());
+ EXPECT_EQ(OK, requests_[num_requests - 2]->result());
+}
+
+TEST_F(HostResolverImplTest, CancelWithinCallback) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ // Port 80 is the first request that the callback will be invoked for.
+ // While we are executing within that callback, cancel the other requests
+ // in the job and start another request.
+ if (req->index() == 0) {
+ // Once "a:80" completes, it will cancel "a:81" and "a:82".
+ requests()[1]->Cancel();
+ requests()[2]->Cancel();
+ }
+ }
+ };
+ set_handler(new MyHandler());
+
+ for (size_t i = 0; i < 4; ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(2u); // One for "a". One for "finalrequest".
+
+ EXPECT_EQ(OK, requests_[0]->WaitForResult());
+
+ Request* final_request = CreateRequest("finalrequest", 70);
+ EXPECT_EQ(ERR_IO_PENDING, final_request->Resolve());
+ EXPECT_EQ(OK, final_request->WaitForResult());
+ EXPECT_TRUE(requests_[3]->completed());
+}
+
+TEST_F(HostResolverImplTest, DeleteWithinCallback) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ EXPECT_EQ("a", req->info().hostname());
+ EXPECT_EQ(80, req->info().port());
+
+ DeleteResolver();
+
+ // Quit after returning from OnCompleted (to give it a chance at
+ // incorrectly running the cancelled tasks).
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ }
+ };
+ set_handler(new MyHandler());
+
+ for (size_t i = 0; i < 4; ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(1u); // One for "a".
+
+ // |MyHandler| will send quit message once all the requests have finished.
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(HostResolverImplTest, DeleteWithinAbortedCallback) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ EXPECT_EQ("a", req->info().hostname());
+ EXPECT_EQ(80, req->info().port());
+
+ DeleteResolver();
+
+ // Quit after returning from OnCompleted (to give it a chance at
+ // incorrectly running the cancelled tasks).
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ }
+ };
+ set_handler(new MyHandler());
+
+ // This test assumes that the Jobs will be Aborted in order ["a", "b"]
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve());
+ // HostResolverImpl will be deleted before later Requests can complete.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 81)->Resolve());
+ // Job for 'b' will be aborted before it can complete.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 82)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b", 83)->Resolve());
+
+ EXPECT_TRUE(proc_->WaitFor(1u));
+
+ // Triggering an IP address change.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+
+ // |MyHandler| will send quit message once all the requests have finished.
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->result());
+ EXPECT_EQ(ERR_IO_PENDING, requests_[1]->result());
+ EXPECT_EQ(ERR_IO_PENDING, requests_[2]->result());
+ EXPECT_EQ(ERR_IO_PENDING, requests_[3]->result());
+ // Clean up.
+ proc_->SignalMultiple(requests_.size());
+}
+
+TEST_F(HostResolverImplTest, StartWithinCallback) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ if (req->index() == 0) {
+ // On completing the first request, start another request for "a".
+ // Since caching is disabled, this will result in another async request.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 70)->Resolve());
+ }
+ }
+ };
+ set_handler(new MyHandler());
+
+ // Turn off caching for this host resolver.
+ resolver_.reset(new HostResolverImpl(scoped_ptr<HostCache>(),
+ DefaultLimits(),
+ DefaultParams(proc_.get()),
+ NULL));
+
+ for (size_t i = 0; i < 4; ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80 + i)->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(2u); // One for "a". One for the second "a".
+
+ EXPECT_EQ(OK, requests_[0]->WaitForResult());
+ ASSERT_EQ(5u, requests_.size());
+ EXPECT_EQ(OK, requests_.back()->WaitForResult());
+
+ EXPECT_EQ(2u, proc_->GetCaptureList().size());
+}
+
+TEST_F(HostResolverImplTest, BypassCache) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ if (req->index() == 0) {
+ // On completing the first request, start another request for "a".
+ // Since caching is enabled, this should complete synchronously.
+ std::string hostname = req->info().hostname();
+ EXPECT_EQ(OK, CreateRequest(hostname, 70)->Resolve());
+ EXPECT_EQ(OK, CreateRequest(hostname, 75)->ResolveFromCache());
+
+ // Ok good. Now make sure that if we ask to bypass the cache, it can no
+ // longer service the request synchronously.
+ HostResolver::RequestInfo info(HostPortPair(hostname, 71));
+ info.set_allow_cached_response(false);
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info)->Resolve());
+ } else if (71 == req->info().port()) {
+ // Test is done.
+ base::MessageLoop::current()->Quit();
+ } else {
+ FAIL() << "Unexpected request";
+ }
+ }
+ };
+ set_handler(new MyHandler());
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a", 80)->Resolve());
+ proc_->SignalMultiple(3u); // Only need two, but be generous.
+
+ // |verifier| will send quit message once all the requests have finished.
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(2u, proc_->GetCaptureList().size());
+}
+
+// Test that IP address changes flush the cache.
+TEST_F(HostResolverImplTest, FlushCacheOnIPAddressChange) {
+ proc_->SignalMultiple(2u); // One before the flush, one after.
+
+ Request* req = CreateRequest("host1", 70);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+
+ req = CreateRequest("host1", 75);
+ EXPECT_EQ(OK, req->Resolve()); // Should complete synchronously.
+
+ // Flush cache by triggering an IP address change.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+
+ // Resolve "host1" again -- this time it won't be served from cache, so it
+ // will complete asynchronously.
+ req = CreateRequest("host1", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+}
+
+// Test that IP address changes send ERR_NETWORK_CHANGED to pending requests.
+TEST_F(HostResolverImplTest, AbortOnIPAddressChanged) {
+ Request* req = CreateRequest("host1", 70);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+
+ EXPECT_TRUE(proc_->WaitFor(1u));
+ // Triggering an IP address change.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+ proc_->SignalAll();
+
+ EXPECT_EQ(ERR_NETWORK_CHANGED, req->WaitForResult());
+ EXPECT_EQ(0u, resolver_->GetHostCache()->size());
+}
+
+// Obey pool constraints after IP address has changed.
+TEST_F(HostResolverImplTest, ObeyPoolConstraintsAfterIPAddressChange) {
+ // Runs at most one job at a time.
+ CreateSerialResolver();
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("a")->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("b")->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("c")->Resolve());
+
+ EXPECT_TRUE(proc_->WaitFor(1u));
+ // Triggering an IP address change.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+ proc_->SignalMultiple(3u); // Let the false-start go so that we can catch it.
+
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->WaitForResult());
+
+ EXPECT_EQ(1u, num_running_jobs());
+
+ EXPECT_FALSE(requests_[1]->completed());
+ EXPECT_FALSE(requests_[2]->completed());
+
+ EXPECT_EQ(OK, requests_[2]->WaitForResult());
+ EXPECT_EQ(OK, requests_[1]->result());
+}
+
+// Tests that a new Request made from the callback of a previously aborted one
+// will not be aborted.
+TEST_F(HostResolverImplTest, AbortOnlyExistingRequestsOnIPAddressChange) {
+ struct MyHandler : public Handler {
+ virtual void Handle(Request* req) OVERRIDE {
+ // Start new request for a different hostname to ensure that the order
+ // of jobs in HostResolverImpl is not stable.
+ std::string hostname;
+ if (req->index() == 0)
+ hostname = "zzz";
+ else if (req->index() == 1)
+ hostname = "aaa";
+ else if (req->index() == 2)
+ hostname = "eee";
+ else
+ return; // A request started from within MyHandler.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname)->Resolve()) << hostname;
+ }
+ };
+ set_handler(new MyHandler());
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("bbb")->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("eee")->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ccc")->Resolve());
+
+ // Wait until all are blocked;
+ EXPECT_TRUE(proc_->WaitFor(3u));
+ // Trigger an IP address change.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ // This should abort all running jobs.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[0]->result());
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[1]->result());
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[2]->result());
+ ASSERT_EQ(6u, requests_.size());
+ // Unblock all calls to proc.
+ proc_->SignalMultiple(requests_.size());
+ // Run until the re-started requests finish.
+ EXPECT_EQ(OK, requests_[3]->WaitForResult());
+ EXPECT_EQ(OK, requests_[4]->WaitForResult());
+ EXPECT_EQ(OK, requests_[5]->WaitForResult());
+ // Verify that results of aborted Jobs were not cached.
+ EXPECT_EQ(6u, proc_->GetCaptureList().size());
+ EXPECT_EQ(3u, resolver_->GetHostCache()->size());
+}
+
+// Tests that when the maximum threads is set to 1, requests are dequeued
+// in order of priority.
+TEST_F(HostResolverImplTest, HigherPriorityRequestsStartedFirst) {
+ CreateSerialResolver();
+
+ // Note that at this point the MockHostResolverProc is blocked, so any
+ // requests we make will not complete.
+ CreateRequest("req0", 80, LOW);
+ CreateRequest("req1", 80, MEDIUM);
+ CreateRequest("req2", 80, MEDIUM);
+ CreateRequest("req3", 80, LOW);
+ CreateRequest("req4", 80, HIGHEST);
+ CreateRequest("req5", 80, LOW);
+ CreateRequest("req6", 80, LOW);
+ CreateRequest("req5", 80, HIGHEST);
+
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i;
+ }
+
+ // Unblock the resolver thread so the requests can run.
+ proc_->SignalMultiple(requests_.size()); // More than needed.
+
+ // Wait for all the requests to complete succesfully.
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+ }
+
+ // Since we have restricted to a single concurrent thread in the jobpool,
+ // the requests should complete in order of priority (with the exception
+ // of the first request, which gets started right away, since there is
+ // nothing outstanding).
+ MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList();
+ ASSERT_EQ(7u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req4", capture_list[1].hostname);
+ EXPECT_EQ("req5", capture_list[2].hostname);
+ EXPECT_EQ("req1", capture_list[3].hostname);
+ EXPECT_EQ("req2", capture_list[4].hostname);
+ EXPECT_EQ("req3", capture_list[5].hostname);
+ EXPECT_EQ("req6", capture_list[6].hostname);
+}
+
+// Try cancelling a job which has not started yet.
+TEST_F(HostResolverImplTest, CancelPendingRequest) {
+ CreateSerialResolver();
+
+ CreateRequest("req0", 80, LOWEST);
+ CreateRequest("req1", 80, HIGHEST); // Will cancel.
+ CreateRequest("req2", 80, MEDIUM);
+ CreateRequest("req3", 80, LOW);
+ CreateRequest("req4", 80, HIGHEST); // Will cancel.
+ CreateRequest("req5", 80, LOWEST); // Will cancel.
+ CreateRequest("req6", 80, MEDIUM);
+
+ // Start all of the requests.
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i;
+ }
+
+ // Cancel some requests
+ requests_[1]->Cancel();
+ requests_[4]->Cancel();
+ requests_[5]->Cancel();
+
+ // Unblock the resolver thread so the requests can run.
+ proc_->SignalMultiple(requests_.size()); // More than needed.
+
+ // Wait for all the requests to complete succesfully.
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ if (!requests_[i]->pending())
+ continue; // Don't wait for the requests we cancelled.
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+ }
+
+ // Verify that they called out the the resolver proc (which runs on the
+ // resolver thread) in the expected order.
+ MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList();
+ ASSERT_EQ(4u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req2", capture_list[1].hostname);
+ EXPECT_EQ("req6", capture_list[2].hostname);
+ EXPECT_EQ("req3", capture_list[3].hostname);
+}
+
+// Test that when too many requests are enqueued, old ones start to be aborted.
+TEST_F(HostResolverImplTest, QueueOverflow) {
+ CreateSerialResolver();
+
+ // Allow only 3 queued jobs.
+ const size_t kMaxPendingJobs = 3u;
+ resolver_->SetMaxQueuedJobs(kMaxPendingJobs);
+
+ // Note that at this point the MockHostResolverProc is blocked, so any
+ // requests we make will not complete.
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req0", 80, LOWEST)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req1", 80, HIGHEST)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req2", 80, MEDIUM)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req3", 80, MEDIUM)->Resolve());
+
+ // At this point, there are 3 enqueued jobs.
+ // Insertion of subsequent requests will cause evictions
+ // based on priority.
+
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE,
+ CreateRequest("req4", 80, LOW)->Resolve()); // Evicts itself!
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req5", 80, MEDIUM)->Resolve());
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[2]->result());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req6", 80, HIGHEST)->Resolve());
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[3]->result());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("req7", 80, MEDIUM)->Resolve());
+ EXPECT_EQ(ERR_HOST_RESOLVER_QUEUE_TOO_LARGE, requests_[5]->result());
+
+ // Unblock the resolver thread so the requests can run.
+ proc_->SignalMultiple(4u);
+
+ // The rest should succeed.
+ EXPECT_EQ(OK, requests_[7]->WaitForResult());
+ EXPECT_EQ(OK, requests_[0]->result());
+ EXPECT_EQ(OK, requests_[1]->result());
+ EXPECT_EQ(OK, requests_[6]->result());
+
+ // Verify that they called out the the resolver proc (which runs on the
+ // resolver thread) in the expected order.
+ MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList();
+ ASSERT_EQ(4u, capture_list.size());
+
+ EXPECT_EQ("req0", capture_list[0].hostname);
+ EXPECT_EQ("req1", capture_list[1].hostname);
+ EXPECT_EQ("req6", capture_list[2].hostname);
+ EXPECT_EQ("req7", capture_list[3].hostname);
+
+ // Verify that the evicted (incomplete) requests were not cached.
+ EXPECT_EQ(4u, resolver_->GetHostCache()->size());
+
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_TRUE(requests_[i]->completed()) << i;
+ }
+}
+
+// Tests that after changing the default AddressFamily to IPV4, requests
+// with UNSPECIFIED address family map to IPV4.
+TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv4) {
+ CreateSerialResolver(); // To guarantee order of resolutions.
+
+ proc_->AddRule("h1", ADDRESS_FAMILY_IPV4, "1.0.0.1");
+ proc_->AddRule("h1", ADDRESS_FAMILY_IPV6, "::2");
+
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_UNSPECIFIED);
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV4);
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV6);
+
+ // Start all of the requests.
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(requests_.size());
+
+ // Wait for all the requests to complete.
+ for (size_t i = 0u; i < requests_.size(); ++i) {
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+ }
+
+ // Since the requests all had the same priority and we limited the thread
+ // count to 1, they should have completed in the same order as they were
+ // requested. Moreover, request0 and request1 will have been serviced by
+ // the same job.
+
+ MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList();
+ ASSERT_EQ(2u, capture_list.size());
+
+ EXPECT_EQ("h1", capture_list[0].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[0].address_family);
+
+ EXPECT_EQ("h1", capture_list[1].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[1].address_family);
+
+ // Now check that the correct resolved IP addresses were returned.
+ EXPECT_TRUE(requests_[0]->HasOneAddress("1.0.0.1", 80));
+ EXPECT_TRUE(requests_[1]->HasOneAddress("1.0.0.1", 80));
+ EXPECT_TRUE(requests_[2]->HasOneAddress("::2", 80));
+}
+
+// This is the exact same test as SetDefaultAddressFamily_IPv4, except the
+// default family is set to IPv6 and the family of requests is flipped where
+// specified.
+TEST_F(HostResolverImplTest, SetDefaultAddressFamily_IPv6) {
+ CreateSerialResolver(); // To guarantee order of resolutions.
+
+ // Don't use IPv6 replacements here since some systems don't support it.
+ proc_->AddRule("h1", ADDRESS_FAMILY_IPV4, "1.0.0.1");
+ proc_->AddRule("h1", ADDRESS_FAMILY_IPV6, "::2");
+
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV6);
+
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_UNSPECIFIED);
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV6);
+ CreateRequest("h1", 80, MEDIUM, ADDRESS_FAMILY_IPV4);
+
+ // Start all of the requests.
+ for (size_t i = 0; i < requests_.size(); ++i) {
+ EXPECT_EQ(ERR_IO_PENDING, requests_[i]->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(requests_.size());
+
+ // Wait for all the requests to complete.
+ for (size_t i = 0u; i < requests_.size(); ++i) {
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+ }
+
+ // Since the requests all had the same priority and we limited the thread
+ // count to 1, they should have completed in the same order as they were
+ // requested. Moreover, request0 and request1 will have been serviced by
+ // the same job.
+
+ MockHostResolverProc::CaptureList capture_list = proc_->GetCaptureList();
+ ASSERT_EQ(2u, capture_list.size());
+
+ EXPECT_EQ("h1", capture_list[0].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, capture_list[0].address_family);
+
+ EXPECT_EQ("h1", capture_list[1].hostname);
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, capture_list[1].address_family);
+
+ // Now check that the correct resolved IP addresses were returned.
+ EXPECT_TRUE(requests_[0]->HasOneAddress("::2", 80));
+ EXPECT_TRUE(requests_[1]->HasOneAddress("::2", 80));
+ EXPECT_TRUE(requests_[2]->HasOneAddress("1.0.0.1", 80));
+}
+
+TEST_F(HostResolverImplTest, ResolveFromCache) {
+ proc_->AddRuleForAllFamilies("just.testing", "192.168.1.42");
+ proc_->SignalMultiple(1u); // Need only one.
+
+ HostResolver::RequestInfo info(HostPortPair("just.testing", 80));
+
+ // First hit will miss the cache.
+ EXPECT_EQ(ERR_DNS_CACHE_MISS, CreateRequest(info)->ResolveFromCache());
+
+ // This time, we fetch normally.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(info)->Resolve());
+ EXPECT_EQ(OK, requests_[1]->WaitForResult());
+
+ // Now we should be able to fetch from the cache.
+ EXPECT_EQ(OK, CreateRequest(info)->ResolveFromCache());
+ EXPECT_TRUE(requests_[2]->HasOneAddress("192.168.1.42", 80));
+}
+
+// Test the retry attempts simulating host resolver proc that takes too long.
+TEST_F(HostResolverImplTest, MultipleAttempts) {
+ // Total number of attempts would be 3 and we want the 3rd attempt to resolve
+ // the host. First and second attempt will be forced to sleep until they get
+ // word that a resolution has completed. The 3rd resolution attempt will try
+ // to get done ASAP, and won't sleep..
+ int kAttemptNumberToResolve = 3;
+ int kTotalAttempts = 3;
+
+ scoped_refptr<LookupAttemptHostResolverProc> resolver_proc(
+ new LookupAttemptHostResolverProc(
+ NULL, kAttemptNumberToResolve, kTotalAttempts));
+
+ HostResolverImpl::ProcTaskParams params = DefaultParams(resolver_proc.get());
+
+ // Specify smaller interval for unresponsive_delay_ for HostResolverImpl so
+ // that unit test runs faster. For example, this test finishes in 1.5 secs
+ // (500ms * 3).
+ params.unresponsive_delay = base::TimeDelta::FromMilliseconds(500);
+
+ resolver_.reset(
+ new HostResolverImpl(HostCache::CreateDefaultCache(),
+ DefaultLimits(),
+ params,
+ NULL));
+
+ // Resolve "host1".
+ HostResolver::RequestInfo info(HostPortPair("host1", 70));
+ Request* req = CreateRequest(info);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+
+ // Resolve returns -4 to indicate that 3rd attempt has resolved the host.
+ EXPECT_EQ(-4, req->WaitForResult());
+
+ resolver_proc->WaitForAllAttemptsToFinish(
+ base::TimeDelta::FromMilliseconds(60000));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(resolver_proc->total_attempts_resolved(), kTotalAttempts);
+ EXPECT_EQ(resolver_proc->resolved_attempt_number(), kAttemptNumberToResolve);
+}
+
+DnsConfig CreateValidDnsConfig() {
+ IPAddressNumber dns_ip;
+ bool rv = ParseIPLiteralToNumber("192.168.1.0", &dns_ip);
+ EXPECT_TRUE(rv);
+
+ DnsConfig config;
+ config.nameservers.push_back(IPEndPoint(dns_ip, dns_protocol::kDefaultPort));
+ EXPECT_TRUE(config.IsValid());
+ return config;
+}
+
+// Specialized fixture for tests of DnsTask.
+class HostResolverImplDnsTest : public HostResolverImplTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ AddDnsRule("nx", dns_protocol::kTypeA, MockDnsClientRule::FAIL);
+ AddDnsRule("nx", dns_protocol::kTypeAAAA, MockDnsClientRule::FAIL);
+ AddDnsRule("ok", dns_protocol::kTypeA, MockDnsClientRule::OK);
+ AddDnsRule("ok", dns_protocol::kTypeAAAA, MockDnsClientRule::OK);
+ AddDnsRule("4ok", dns_protocol::kTypeA, MockDnsClientRule::OK);
+ AddDnsRule("4ok", dns_protocol::kTypeAAAA, MockDnsClientRule::EMPTY);
+ AddDnsRule("6ok", dns_protocol::kTypeA, MockDnsClientRule::EMPTY);
+ AddDnsRule("6ok", dns_protocol::kTypeAAAA, MockDnsClientRule::OK);
+ AddDnsRule("4nx", dns_protocol::kTypeA, MockDnsClientRule::OK);
+ AddDnsRule("4nx", dns_protocol::kTypeAAAA, MockDnsClientRule::FAIL);
+ CreateResolver();
+ }
+
+ void CreateResolver() {
+ resolver_.reset(new HostResolverImpl(HostCache::CreateDefaultCache(),
+ DefaultLimits(),
+ DefaultParams(proc_.get()),
+ NULL));
+ // Disable IPv6 support probing.
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_UNSPECIFIED);
+ resolver_->SetDnsClient(CreateMockDnsClient(DnsConfig(), dns_rules_));
+ }
+
+ // Adds a rule to |dns_rules_|. Must be followed by |CreateResolver| to apply.
+ void AddDnsRule(const std::string& prefix,
+ uint16 qtype,
+ MockDnsClientRule::Result result) {
+ dns_rules_.push_back(MockDnsClientRule(prefix, qtype, result));
+ }
+
+ void ChangeDnsConfig(const DnsConfig& config) {
+ NetworkChangeNotifier::SetDnsConfig(config);
+ // Notification is delivered asynchronously.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ MockDnsClientRuleList dns_rules_;
+};
+
+// TODO(szym): Test AbortAllInProgressJobs due to DnsConfig change.
+
+// TODO(cbentzel): Test a mix of requests with different HostResolverFlags.
+
+// Test successful and fallback resolutions in HostResolverImpl::DnsTask.
+TEST_F(HostResolverImplDnsTest, DnsTask) {
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+
+ proc_->AddRuleForAllFamilies("nx_succeed", "192.168.1.102");
+ // All other hostnames will fail in proc_.
+
+ // Initially there is no config, so client should not be invoked.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve());
+ proc_->SignalMultiple(requests_.size());
+
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[0]->WaitForResult());
+
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_fail", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_succeed", 80)->Resolve());
+
+ proc_->SignalMultiple(requests_.size());
+
+ for (size_t i = 1; i < requests_.size(); ++i)
+ EXPECT_NE(ERR_UNEXPECTED, requests_[i]->WaitForResult()) << i;
+
+ EXPECT_EQ(OK, requests_[1]->result());
+ // Resolved by MockDnsClient.
+ EXPECT_TRUE(requests_[1]->HasOneAddress("127.0.0.1", 80));
+ // Fallback to ProcTask.
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[2]->result());
+ EXPECT_EQ(OK, requests_[3]->result());
+ EXPECT_TRUE(requests_[3]->HasOneAddress("192.168.1.102", 80));
+}
+
+// Test successful and failing resolutions in HostResolverImpl::DnsTask when
+// fallback to ProcTask is disabled.
+TEST_F(HostResolverImplDnsTest, NoFallbackToProcTask) {
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+ set_fallback_to_proctask(false);
+
+ proc_->AddRuleForAllFamilies("nx_succeed", "192.168.1.102");
+ // All other hostnames will fail in proc_.
+
+ // Set empty DnsConfig.
+ ChangeDnsConfig(DnsConfig());
+ // Initially there is no config, so client should not be invoked.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve());
+ // There is no config, so fallback to ProcTask must work.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_succeed", 80)->Resolve());
+ proc_->SignalMultiple(requests_.size());
+
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[0]->WaitForResult());
+ EXPECT_EQ(OK, requests_[1]->WaitForResult());
+ EXPECT_TRUE(requests_[1]->HasOneAddress("192.168.1.102", 80));
+
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_abort", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_abort", 80)->Resolve());
+
+ // Simulate the case when the preference or policy has disabled the DNS client
+ // causing AbortDnsTasks.
+ resolver_->SetDnsClient(CreateMockDnsClient(DnsConfig(), dns_rules_));
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ // First request is resolved by MockDnsClient, others should fail due to
+ // disabled fallback to ProcTask.
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok_fail", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_fail", 80)->Resolve());
+ proc_->SignalMultiple(requests_.size());
+
+ // Aborted due to Network Change.
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[2]->WaitForResult());
+ EXPECT_EQ(ERR_NETWORK_CHANGED, requests_[3]->WaitForResult());
+ // Resolved by MockDnsClient.
+ EXPECT_EQ(OK, requests_[4]->WaitForResult());
+ EXPECT_TRUE(requests_[4]->HasOneAddress("127.0.0.1", 80));
+ // Fallback to ProcTask is disabled.
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[5]->WaitForResult());
+}
+
+// Test behavior of OnDnsTaskFailure when Job is aborted.
+TEST_F(HostResolverImplDnsTest, OnDnsTaskFailureAbortedJob) {
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+ ChangeDnsConfig(CreateValidDnsConfig());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_abort", 80)->Resolve());
+ // Abort all jobs here.
+ CreateResolver();
+ proc_->SignalMultiple(requests_.size());
+ // Run to completion.
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+ // It shouldn't crash during OnDnsTaskFailure callbacks.
+ EXPECT_EQ(ERR_IO_PENDING, requests_[0]->result());
+
+ // Repeat test with Fallback to ProcTask disabled
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+ set_fallback_to_proctask(false);
+ ChangeDnsConfig(CreateValidDnsConfig());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("nx_abort", 80)->Resolve());
+ // Abort all jobs here.
+ CreateResolver();
+ // Run to completion.
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+ // It shouldn't crash during OnDnsTaskFailure callbacks.
+ EXPECT_EQ(ERR_IO_PENDING, requests_[1]->result());
+}
+
+TEST_F(HostResolverImplDnsTest, DnsTaskUnspec) {
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ proc_->AddRuleForAllFamilies("4nx", "192.168.1.101");
+ // All other hostnames will fail in proc_.
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("4ok", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("6ok", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("4nx", 80)->Resolve());
+
+ proc_->SignalMultiple(requests_.size());
+
+ for (size_t i = 0; i < requests_.size(); ++i)
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+
+ EXPECT_EQ(2u, requests_[0]->NumberOfAddresses());
+ EXPECT_TRUE(requests_[0]->HasAddress("127.0.0.1", 80));
+ EXPECT_TRUE(requests_[0]->HasAddress("::1", 80));
+ EXPECT_EQ(1u, requests_[1]->NumberOfAddresses());
+ EXPECT_TRUE(requests_[1]->HasAddress("127.0.0.1", 80));
+ EXPECT_EQ(1u, requests_[2]->NumberOfAddresses());
+ EXPECT_TRUE(requests_[2]->HasAddress("::1", 80));
+ EXPECT_EQ(1u, requests_[3]->NumberOfAddresses());
+ EXPECT_TRUE(requests_[3]->HasAddress("192.168.1.101", 80));
+}
+
+TEST_F(HostResolverImplDnsTest, ServeFromHosts) {
+ // Initially, use empty HOSTS file.
+ DnsConfig config = CreateValidDnsConfig();
+ ChangeDnsConfig(config);
+
+ proc_->AddRuleForAllFamilies(std::string(),
+ std::string()); // Default to failures.
+ proc_->SignalMultiple(1u); // For the first request which misses.
+
+ Request* req0 = CreateRequest("nx_ipv4", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req0->Resolve());
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req0->WaitForResult());
+
+ IPAddressNumber local_ipv4, local_ipv6;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &local_ipv4));
+ ASSERT_TRUE(ParseIPLiteralToNumber("::1", &local_ipv6));
+
+ DnsHosts hosts;
+ hosts[DnsHostsKey("nx_ipv4", ADDRESS_FAMILY_IPV4)] = local_ipv4;
+ hosts[DnsHostsKey("nx_ipv6", ADDRESS_FAMILY_IPV6)] = local_ipv6;
+ hosts[DnsHostsKey("nx_both", ADDRESS_FAMILY_IPV4)] = local_ipv4;
+ hosts[DnsHostsKey("nx_both", ADDRESS_FAMILY_IPV6)] = local_ipv6;
+
+ // Update HOSTS file.
+ config.hosts = hosts;
+ ChangeDnsConfig(config);
+
+ Request* req1 = CreateRequest("nx_ipv4", 80);
+ EXPECT_EQ(OK, req1->Resolve());
+ EXPECT_TRUE(req1->HasOneAddress("127.0.0.1", 80));
+
+ Request* req2 = CreateRequest("nx_ipv6", 80);
+ EXPECT_EQ(OK, req2->Resolve());
+ EXPECT_TRUE(req2->HasOneAddress("::1", 80));
+
+ Request* req3 = CreateRequest("nx_both", 80);
+ EXPECT_EQ(OK, req3->Resolve());
+ EXPECT_TRUE(req3->HasAddress("127.0.0.1", 80) &&
+ req3->HasAddress("::1", 80));
+
+ // Requests with specified AddressFamily.
+ Request* req4 = CreateRequest("nx_ipv4", 80, MEDIUM, ADDRESS_FAMILY_IPV4);
+ EXPECT_EQ(OK, req4->Resolve());
+ EXPECT_TRUE(req4->HasOneAddress("127.0.0.1", 80));
+
+ Request* req5 = CreateRequest("nx_ipv6", 80, MEDIUM, ADDRESS_FAMILY_IPV6);
+ EXPECT_EQ(OK, req5->Resolve());
+ EXPECT_TRUE(req5->HasOneAddress("::1", 80));
+
+ // Request with upper case.
+ Request* req6 = CreateRequest("nx_IPV4", 80);
+ EXPECT_EQ(OK, req6->Resolve());
+ EXPECT_TRUE(req6->HasOneAddress("127.0.0.1", 80));
+}
+
+TEST_F(HostResolverImplDnsTest, BypassDnsTask) {
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ proc_->AddRuleForAllFamilies(std::string(),
+ std::string()); // Default to failures.
+
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok.local", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok.local.", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("oklocal", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("oklocal.", 80)->Resolve());
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest("ok", 80)->Resolve());
+
+ proc_->SignalMultiple(requests_.size());
+
+ for (size_t i = 0; i < 2; ++i)
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, requests_[i]->WaitForResult()) << i;
+
+ for (size_t i = 2; i < requests_.size(); ++i)
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+}
+
+TEST_F(HostResolverImplDnsTest, DisableDnsClientOnPersistentFailure) {
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ proc_->AddRuleForAllFamilies(std::string(),
+ std::string()); // Default to failures.
+
+ // Check that DnsTask works.
+ Request* req = CreateRequest("ok_1", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+
+ for (unsigned i = 0; i < 20; ++i) {
+ // Use custom names to require separate Jobs.
+ std::string hostname = base::StringPrintf("nx_%u", i);
+ // Ensure fallback to ProcTask succeeds.
+ proc_->AddRuleForAllFamilies(hostname, "192.168.1.101");
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(requests_.size());
+
+ for (size_t i = 0; i < requests_.size(); ++i)
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+
+ ASSERT_FALSE(proc_->HasBlockedRequests());
+
+ // DnsTask should be disabled by now.
+ req = CreateRequest("ok_2", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ proc_->SignalMultiple(1u);
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req->WaitForResult());
+
+ // Check that it is re-enabled after DNS change.
+ ChangeDnsConfig(CreateValidDnsConfig());
+ req = CreateRequest("ok_3", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+}
+
+TEST_F(HostResolverImplDnsTest, DontDisableDnsClientOnSporadicFailure) {
+ ChangeDnsConfig(CreateValidDnsConfig());
+
+ // |proc_| defaults to successes.
+
+ // 20 failures interleaved with 20 successes.
+ for (unsigned i = 0; i < 40; ++i) {
+ // Use custom names to require separate Jobs.
+ std::string hostname = (i % 2) == 0 ? base::StringPrintf("nx_%u", i)
+ : base::StringPrintf("ok_%u", i);
+ EXPECT_EQ(ERR_IO_PENDING, CreateRequest(hostname, 80)->Resolve()) << i;
+ }
+
+ proc_->SignalMultiple(requests_.size());
+
+ for (size_t i = 0; i < requests_.size(); ++i)
+ EXPECT_EQ(OK, requests_[i]->WaitForResult()) << i;
+
+ // Make |proc_| default to failures.
+ proc_->AddRuleForAllFamilies(std::string(), std::string());
+
+ // DnsTask should still be enabled.
+ Request* req = CreateRequest("ok_last", 80);
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+}
+
+// Confirm that resolving "localhost" is unrestricted even if there are no
+// global IPv6 address. See SystemHostResolverCall for rationale.
+// Test both the DnsClient and system host resolver paths.
+TEST_F(HostResolverImplDnsTest, DualFamilyLocalhost) {
+ // Use regular SystemHostResolverCall!
+ scoped_refptr<HostResolverProc> proc(new SystemHostResolverProc());
+ resolver_.reset(new HostResolverImpl(HostCache::CreateDefaultCache(),
+ DefaultLimits(),
+ DefaultParams(proc.get()),
+ NULL));
+ resolver_->SetDnsClient(CreateMockDnsClient(DnsConfig(), dns_rules_));
+ resolver_->SetDefaultAddressFamily(ADDRESS_FAMILY_IPV4);
+
+ // Get the expected output.
+ AddressList addrlist;
+ int rv = proc->Resolve("localhost", ADDRESS_FAMILY_UNSPECIFIED, 0, &addrlist,
+ NULL);
+ if (rv != OK)
+ return;
+
+ for (unsigned i = 0; i < addrlist.size(); ++i)
+ LOG(WARNING) << addrlist[i].ToString();
+
+ bool saw_ipv4 = AddressListContains(addrlist, "127.0.0.1", 0);
+ bool saw_ipv6 = AddressListContains(addrlist, "::1", 0);
+ if (!saw_ipv4 && !saw_ipv6)
+ return;
+
+ HostResolver::RequestInfo info(HostPortPair("localhost", 80));
+ info.set_address_family(ADDRESS_FAMILY_UNSPECIFIED);
+ info.set_host_resolver_flags(HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6);
+
+ // Try without DnsClient.
+ ChangeDnsConfig(DnsConfig());
+ Request* req = CreateRequest(info);
+ // It is resolved via getaddrinfo, so expect asynchronous result.
+ EXPECT_EQ(ERR_IO_PENDING, req->Resolve());
+ EXPECT_EQ(OK, req->WaitForResult());
+
+ EXPECT_EQ(saw_ipv4, req->HasAddress("127.0.0.1", 80));
+ EXPECT_EQ(saw_ipv6, req->HasAddress("::1", 80));
+
+ // Configure DnsClient with dual-host HOSTS file.
+ DnsConfig config = CreateValidDnsConfig();
+ DnsHosts hosts;
+ IPAddressNumber local_ipv4, local_ipv6;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &local_ipv4));
+ ASSERT_TRUE(ParseIPLiteralToNumber("::1", &local_ipv6));
+ if (saw_ipv4)
+ hosts[DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4)] = local_ipv4;
+ if (saw_ipv6)
+ hosts[DnsHostsKey("localhost", ADDRESS_FAMILY_IPV6)] = local_ipv6;
+ config.hosts = hosts;
+
+ ChangeDnsConfig(config);
+ req = CreateRequest(info);
+ // Expect synchronous resolution from DnsHosts.
+ EXPECT_EQ(OK, req->Resolve());
+
+ EXPECT_EQ(saw_ipv4, req->HasAddress("127.0.0.1", 80));
+ EXPECT_EQ(saw_ipv6, req->HasAddress("::1", 80));
+}
+
+} // namespace net
diff --git a/chromium/net/dns/host_resolver_proc.cc b/chromium/net/dns/host_resolver_proc.cc
new file mode 100644
index 00000000000..f2b10c649ba
--- /dev/null
+++ b/chromium/net/dns/host_resolver_proc.cc
@@ -0,0 +1,267 @@
+// Copyright (c) 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.
+
+#include "net/dns/host_resolver_proc.h"
+
+#include "build/build_config.h"
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "net/base/address_list.h"
+#include "net/base/dns_reloader.h"
+#include "net/base/net_errors.h"
+#include "net/base/sys_addrinfo.h"
+
+#if defined(OS_OPENBSD)
+#define AI_ADDRCONFIG 0
+#endif
+
+namespace net {
+
+namespace {
+
+bool IsAllLocalhostOfOneFamily(const struct addrinfo* ai) {
+ bool saw_v4_localhost = false;
+ bool saw_v6_localhost = false;
+ for (; ai != NULL; ai = ai->ai_next) {
+ switch (ai->ai_family) {
+ case AF_INET: {
+ const struct sockaddr_in* addr_in =
+ reinterpret_cast<struct sockaddr_in*>(ai->ai_addr);
+ if ((base::NetToHost32(addr_in->sin_addr.s_addr) & 0xff000000) ==
+ 0x7f000000)
+ saw_v4_localhost = true;
+ else
+ return false;
+ break;
+ }
+ case AF_INET6: {
+ const struct sockaddr_in6* addr_in6 =
+ reinterpret_cast<struct sockaddr_in6*>(ai->ai_addr);
+ if (IN6_IS_ADDR_LOOPBACK(&addr_in6->sin6_addr))
+ saw_v6_localhost = true;
+ else
+ return false;
+ break;
+ }
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ return saw_v4_localhost != saw_v6_localhost;
+}
+
+} // namespace
+
+HostResolverProc* HostResolverProc::default_proc_ = NULL;
+
+HostResolverProc::HostResolverProc(HostResolverProc* previous) {
+ SetPreviousProc(previous);
+
+ // Implicitly fall-back to the global default procedure.
+ if (!previous)
+ SetPreviousProc(default_proc_);
+}
+
+HostResolverProc::~HostResolverProc() {
+}
+
+int HostResolverProc::ResolveUsingPrevious(
+ const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ if (previous_proc_.get()) {
+ return previous_proc_->Resolve(
+ host, address_family, host_resolver_flags, addrlist, os_error);
+ }
+
+ // Final fallback is the system resolver.
+ return SystemHostResolverCall(host, address_family, host_resolver_flags,
+ addrlist, os_error);
+}
+
+void HostResolverProc::SetPreviousProc(HostResolverProc* proc) {
+ HostResolverProc* current_previous = previous_proc_.get();
+ previous_proc_ = NULL;
+ // Now that we've guaranteed |this| is the last proc in a chain, we can
+ // detect potential cycles using GetLastProc().
+ previous_proc_ = (GetLastProc(proc) == this) ? current_previous : proc;
+}
+
+void HostResolverProc::SetLastProc(HostResolverProc* proc) {
+ GetLastProc(this)->SetPreviousProc(proc);
+}
+
+// static
+HostResolverProc* HostResolverProc::GetLastProc(HostResolverProc* proc) {
+ if (proc == NULL)
+ return NULL;
+ HostResolverProc* last_proc = proc;
+ while (last_proc->previous_proc_.get() != NULL)
+ last_proc = last_proc->previous_proc_.get();
+ return last_proc;
+}
+
+// static
+HostResolverProc* HostResolverProc::SetDefault(HostResolverProc* proc) {
+ HostResolverProc* old = default_proc_;
+ default_proc_ = proc;
+ return old;
+}
+
+// static
+HostResolverProc* HostResolverProc::GetDefault() {
+ return default_proc_;
+}
+
+int SystemHostResolverCall(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ if (os_error)
+ *os_error = 0;
+
+ struct addrinfo* ai = NULL;
+ struct addrinfo hints = {0};
+
+ switch (address_family) {
+ case ADDRESS_FAMILY_IPV4:
+ hints.ai_family = AF_INET;
+ break;
+ case ADDRESS_FAMILY_IPV6:
+ hints.ai_family = AF_INET6;
+ break;
+ case ADDRESS_FAMILY_UNSPECIFIED:
+ hints.ai_family = AF_UNSPEC;
+ break;
+ default:
+ NOTREACHED();
+ hints.ai_family = AF_UNSPEC;
+ }
+
+#if defined(OS_WIN)
+ // DO NOT USE AI_ADDRCONFIG ON WINDOWS.
+ //
+ // The following comment in <winsock2.h> is the best documentation I found
+ // on AI_ADDRCONFIG for Windows:
+ // Flags used in "hints" argument to getaddrinfo()
+ // - AI_ADDRCONFIG is supported starting with Vista
+ // - default is AI_ADDRCONFIG ON whether the flag is set or not
+ // because the performance penalty in not having ADDRCONFIG in
+ // the multi-protocol stack environment is severe;
+ // this defaulting may be disabled by specifying the AI_ALL flag,
+ // in that case AI_ADDRCONFIG must be EXPLICITLY specified to
+ // enable ADDRCONFIG behavior
+ //
+ // Not only is AI_ADDRCONFIG unnecessary, but it can be harmful. If the
+ // computer is not connected to a network, AI_ADDRCONFIG causes getaddrinfo
+ // to fail with WSANO_DATA (11004) for "localhost", probably because of the
+ // following note on AI_ADDRCONFIG in the MSDN getaddrinfo page:
+ // The IPv4 or IPv6 loopback address is not considered a valid global
+ // address.
+ // See http://crbug.com/5234.
+ //
+ // OpenBSD does not support it, either.
+ hints.ai_flags = 0;
+#else
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+
+ // On Linux AI_ADDRCONFIG doesn't consider loopback addreses, even if only
+ // loopback addresses are configured. So don't use it when there are only
+ // loopback addresses.
+ if (host_resolver_flags & HOST_RESOLVER_LOOPBACK_ONLY)
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+
+ if (host_resolver_flags & HOST_RESOLVER_CANONNAME)
+ hints.ai_flags |= AI_CANONNAME;
+
+ // Restrict result set to only this socket type to avoid duplicates.
+ hints.ai_socktype = SOCK_STREAM;
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_OPENBSD) && \
+ !defined(OS_ANDROID)
+ DnsReloaderMaybeReload();
+#endif
+ int err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
+ bool should_retry = false;
+ // If the lookup was restricted (either by address family, or address
+ // detection), and the results where all localhost of a single family,
+ // maybe we should retry. There were several bugs related to these
+ // issues, for example http://crbug.com/42058 and http://crbug.com/49024
+ if ((hints.ai_family != AF_UNSPEC || hints.ai_flags & AI_ADDRCONFIG) &&
+ err == 0 && IsAllLocalhostOfOneFamily(ai)) {
+ if (host_resolver_flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6) {
+ hints.ai_family = AF_UNSPEC;
+ should_retry = true;
+ }
+ if (hints.ai_flags & AI_ADDRCONFIG) {
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ should_retry = true;
+ }
+ }
+ if (should_retry) {
+ if (ai != NULL) {
+ freeaddrinfo(ai);
+ ai = NULL;
+ }
+ err = getaddrinfo(host.c_str(), NULL, &hints, &ai);
+ }
+
+ if (err) {
+#if defined(OS_WIN)
+ err = WSAGetLastError();
+#endif
+
+ // Return the OS error to the caller.
+ if (os_error)
+ *os_error = err;
+
+ // If the call to getaddrinfo() failed because of a system error, report
+ // it separately from ERR_NAME_NOT_RESOLVED.
+#if defined(OS_WIN)
+ if (err != WSAHOST_NOT_FOUND && err != WSANO_DATA)
+ return ERR_NAME_RESOLUTION_FAILED;
+#elif defined(OS_POSIX) && !defined(OS_FREEBSD)
+ if (err != EAI_NONAME && err != EAI_NODATA)
+ return ERR_NAME_RESOLUTION_FAILED;
+#endif
+
+ return ERR_NAME_NOT_RESOLVED;
+ }
+
+#if defined(OS_ANDROID)
+ // Workaround for Android's getaddrinfo leaving ai==NULL without an error.
+ // http://crbug.com/134142
+ if (ai == NULL)
+ return ERR_NAME_NOT_RESOLVED;
+#endif
+
+ *addrlist = AddressList::CreateFromAddrinfo(ai);
+ freeaddrinfo(ai);
+ return OK;
+}
+
+SystemHostResolverProc::SystemHostResolverProc() : HostResolverProc(NULL) {}
+
+int SystemHostResolverProc::Resolve(const std::string& hostname,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addr_list,
+ int* os_error) {
+ return SystemHostResolverCall(hostname,
+ address_family,
+ host_resolver_flags,
+ addr_list,
+ os_error);
+}
+
+SystemHostResolverProc::~SystemHostResolverProc() {}
+
+} // namespace net
diff --git a/chromium/net/dns/host_resolver_proc.h b/chromium/net/dns/host_resolver_proc.h
new file mode 100644
index 00000000000..014a720e375
--- /dev/null
+++ b/chromium/net/dns/host_resolver_proc.h
@@ -0,0 +1,111 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_HOST_RESOLVER_PROC_H_
+#define NET_DNS_HOST_RESOLVER_PROC_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/address_family.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class AddressList;
+
+// Interface for a getaddrinfo()-like procedure. This is used by unit-tests
+// to control the underlying resolutions in HostResolverImpl. HostResolverProcs
+// can be chained together; they fallback to the next procedure in the chain
+// by calling ResolveUsingPrevious().
+//
+// Note that implementations of HostResolverProc *MUST BE THREADSAFE*, since
+// the HostResolver implementation using them can be multi-threaded.
+class NET_EXPORT HostResolverProc
+ : public base::RefCountedThreadSafe<HostResolverProc> {
+ public:
+ explicit HostResolverProc(HostResolverProc* previous);
+
+ // Resolves |host| to an address list, restricting the results to addresses
+ // in |address_family|. If successful returns OK and fills |addrlist| with
+ // a list of socket addresses. Otherwise returns a network error code, and
+ // fills |os_error| with a more specific error if it was non-NULL.
+ virtual int Resolve(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<HostResolverProc>;
+
+ virtual ~HostResolverProc();
+
+ // Asks the fallback procedure (if set) to do the resolve.
+ int ResolveUsingPrevious(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error);
+
+ private:
+ friend class HostResolverImpl;
+ friend class MockHostResolverBase;
+ friend class ScopedDefaultHostResolverProc;
+
+ // Sets the previous procedure in the chain. Aborts if this would result in a
+ // cycle.
+ void SetPreviousProc(HostResolverProc* proc);
+
+ // Sets the last procedure in the chain, i.e. appends |proc| to the end of the
+ // current chain. Aborts if this would result in a cycle.
+ void SetLastProc(HostResolverProc* proc);
+
+ // Returns the last procedure in the chain starting at |proc|. Will return
+ // NULL iff |proc| is NULL.
+ static HostResolverProc* GetLastProc(HostResolverProc* proc);
+
+ // Sets the default host resolver procedure that is used by HostResolverImpl.
+ // This can be used through ScopedDefaultHostResolverProc to set a catch-all
+ // DNS block in unit-tests (individual tests should use MockHostResolver to
+ // prevent hitting the network).
+ static HostResolverProc* SetDefault(HostResolverProc* proc);
+ static HostResolverProc* GetDefault();
+
+ scoped_refptr<HostResolverProc> previous_proc_;
+ static HostResolverProc* default_proc_;
+
+ DISALLOW_COPY_AND_ASSIGN(HostResolverProc);
+};
+
+// Resolves |host| to an address list, using the system's default host resolver.
+// (i.e. this calls out to getaddrinfo()). If successful returns OK and fills
+// |addrlist| with a list of socket addresses. Otherwise returns a
+// network error code, and fills |os_error| with a more specific error if it
+// was non-NULL.
+NET_EXPORT_PRIVATE int SystemHostResolverCall(
+ const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error);
+
+// Wraps call to SystemHostResolverCall as an instance of HostResolverProc.
+class NET_EXPORT_PRIVATE SystemHostResolverProc : public HostResolverProc {
+ public:
+ SystemHostResolverProc();
+ virtual int Resolve(const std::string& hostname,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addr_list,
+ int* os_error) OVERRIDE;
+ protected:
+ virtual ~SystemHostResolverProc();
+
+ DISALLOW_COPY_AND_ASSIGN(SystemHostResolverProc);
+};
+
+} // namespace net
+
+#endif // NET_DNS_HOST_RESOLVER_PROC_H_
diff --git a/chromium/net/dns/mapped_host_resolver.cc b/chromium/net/dns/mapped_host_resolver.cc
new file mode 100644
index 00000000000..4db7bc97928
--- /dev/null
+++ b/chromium/net/dns/mapped_host_resolver.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#include "net/dns/mapped_host_resolver.h"
+
+#include "base/strings/string_util.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+MappedHostResolver::MappedHostResolver(scoped_ptr<HostResolver> impl)
+ : impl_(impl.Pass()) {
+}
+
+MappedHostResolver::~MappedHostResolver() {
+}
+
+int MappedHostResolver::Resolve(const RequestInfo& original_info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ RequestInfo info = original_info;
+ int rv = ApplyRules(&info);
+ if (rv != OK)
+ return rv;
+
+ return impl_->Resolve(info, addresses, callback, out_req, net_log);
+}
+
+int MappedHostResolver::ResolveFromCache(const RequestInfo& original_info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) {
+ RequestInfo info = original_info;
+ int rv = ApplyRules(&info);
+ if (rv != OK)
+ return rv;
+
+ return impl_->ResolveFromCache(info, addresses, net_log);
+}
+
+void MappedHostResolver::CancelRequest(RequestHandle req) {
+ impl_->CancelRequest(req);
+}
+
+HostCache* MappedHostResolver::GetHostCache() {
+ return impl_->GetHostCache();
+}
+
+int MappedHostResolver::ApplyRules(RequestInfo* info) const {
+ HostPortPair host_port(info->host_port_pair());
+ if (rules_.RewriteHost(&host_port)) {
+ if (host_port.host() == "~NOTFOUND")
+ return ERR_NAME_NOT_RESOLVED;
+ info->set_host_port_pair(host_port);
+ }
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mapped_host_resolver.h b/chromium/net/dns/mapped_host_resolver.h
new file mode 100644
index 00000000000..50062a9848e
--- /dev/null
+++ b/chromium/net/dns/mapped_host_resolver.h
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_MAPPED_HOST_RESOLVER_H_
+#define NET_DNS_MAPPED_HOST_RESOLVER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/host_mapping_rules.h"
+#include "net/base/net_export.h"
+#include "net/dns/host_resolver.h"
+
+namespace net {
+
+// This class wraps an existing HostResolver instance, but modifies the
+// request before passing it off to |impl|. This is different from
+// MockHostResolver which does the remapping at the HostResolverProc
+// layer, so it is able to preserve the effectiveness of the cache.
+class NET_EXPORT MappedHostResolver : public HostResolver {
+ public:
+ // Creates a MappedHostResolver that forwards all of its requests through
+ // |impl|.
+ explicit MappedHostResolver(scoped_ptr<HostResolver> impl);
+ virtual ~MappedHostResolver();
+
+ // Adds a rule to this mapper. The format of the rule can be one of:
+ //
+ // "MAP" <hostname_pattern> <replacement_host> [":" <replacement_port>]
+ // "EXCLUDE" <hostname_pattern>
+ //
+ // The <replacement_host> can be either a hostname, or an IP address literal,
+ // or "~NOTFOUND". If it is "~NOTFOUND" then all matched hostnames will fail
+ // to be resolved with ERR_NAME_NOT_RESOLVED.
+ //
+ // Returns true if the rule was successfully parsed and added.
+ bool AddRuleFromString(const std::string& rule_string) {
+ return rules_.AddRuleFromString(rule_string);
+ }
+
+ // Takes a comma separated list of rules, and assigns them to this resolver.
+ void SetRulesFromString(const std::string& rules_string) {
+ rules_.SetRulesFromString(rules_string);
+ }
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle req) OVERRIDE;
+ virtual HostCache* GetHostCache() OVERRIDE;
+
+ private:
+ // Modify the request |info| according to |rules_|. Returns either OK or
+ // the network error code that the hostname's resolution mapped to.
+ int ApplyRules(RequestInfo* info) const;
+
+ scoped_ptr<HostResolver> impl_;
+
+ HostMappingRules rules_;
+};
+
+} // namespace net
+
+#endif // NET_DNS_MAPPED_HOST_RESOLVER_H_
diff --git a/chromium/net/dns/mapped_host_resolver_unittest.cc b/chromium/net/dns/mapped_host_resolver_unittest.cc
new file mode 100644
index 00000000000..d8594663c9d
--- /dev/null
+++ b/chromium/net/dns/mapped_host_resolver_unittest.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 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.
+
+#include "net/dns/mapped_host_resolver.h"
+
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+std::string FirstAddress(const AddressList& address_list) {
+ if (address_list.empty())
+ return std::string();
+ return address_list.front().ToString();
+}
+
+TEST(MappedHostResolverTest, Inclusion) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver());
+ resolver_impl->rules()->AddSimulatedFailure("*google.com");
+ resolver_impl->rules()->AddRule("baz.com", "192.168.1.5");
+ resolver_impl->rules()->AddRule("foo.com", "192.168.1.8");
+ resolver_impl->rules()->AddRule("proxy", "192.168.1.11");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_ptr<MappedHostResolver> resolver(
+ new MappedHostResolver(resolver_impl.PassAs<HostResolver>()));
+
+ int rv;
+ AddressList address_list;
+
+ // Try resolving "www.google.com:80". There are no mappings yet, so this
+ // hits |resolver_impl| and fails.
+ TestCompletionCallback callback;
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.google.com", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
+
+ // Remap *.google.com to baz.com.
+ EXPECT_TRUE(resolver->AddRuleFromString("map *.google.com baz.com"));
+
+ // Try resolving "www.google.com:80". Should be remapped to "baz.com:80".
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.google.com", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list));
+
+ // Try resolving "foo.com:77". This will NOT be remapped, so result
+ // is "foo.com:77".
+ rv = resolver->Resolve(HostResolver::RequestInfo(HostPortPair("foo.com", 77)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.8:77", FirstAddress(address_list));
+
+ // Remap "*.org" to "proxy:99".
+ EXPECT_TRUE(resolver->AddRuleFromString("Map *.org proxy:99"));
+
+ // Try resolving "chromium.org:61". Should be remapped to "proxy:99".
+ rv = resolver->Resolve(HostResolver::RequestInfo
+ (HostPortPair("chromium.org", 61)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.11:99", FirstAddress(address_list));
+}
+
+// Tests that exclusions are respected.
+TEST(MappedHostResolverTest, Exclusion) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver());
+ resolver_impl->rules()->AddRule("baz", "192.168.1.5");
+ resolver_impl->rules()->AddRule("www.google.com", "192.168.1.3");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_ptr<MappedHostResolver> resolver(
+ new MappedHostResolver(resolver_impl.PassAs<HostResolver>()));
+
+ int rv;
+ AddressList address_list;
+ TestCompletionCallback callback;
+
+ // Remap "*.com" to "baz".
+ EXPECT_TRUE(resolver->AddRuleFromString("map *.com baz"));
+
+ // Add an exclusion for "*.google.com".
+ EXPECT_TRUE(resolver->AddRuleFromString("EXCLUDE *.google.com"));
+
+ // Try resolving "www.google.com". Should not be remapped due to exclusion).
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.google.com", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.3:80", FirstAddress(address_list));
+
+ // Try resolving "chrome.com:80". Should be remapped to "baz:80".
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("chrome.com", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list));
+}
+
+TEST(MappedHostResolverTest, SetRulesFromString) {
+ // Create a mock host resolver, with specific hostname to IP mappings.
+ scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver());
+ resolver_impl->rules()->AddRule("baz", "192.168.1.7");
+ resolver_impl->rules()->AddRule("bar", "192.168.1.9");
+
+ // Create a remapped resolver that uses |resolver_impl|.
+ scoped_ptr<MappedHostResolver> resolver(
+ new MappedHostResolver(resolver_impl.PassAs<HostResolver>()));
+
+ int rv;
+ AddressList address_list;
+ TestCompletionCallback callback;
+
+ // Remap "*.com" to "baz", and *.net to "bar:60".
+ resolver->SetRulesFromString("map *.com baz , map *.net bar:60");
+
+ // Try resolving "www.google.com". Should be remapped to "baz".
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.google.com", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.7:80", FirstAddress(address_list));
+
+ // Try resolving "chrome.net:80". Should be remapped to "bar:60".
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("chrome.net", 80)),
+ &address_list, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.9:60", FirstAddress(address_list));
+}
+
+// Parsing bad rules should silently discard the rule (and never crash).
+TEST(MappedHostResolverTest, ParseInvalidRules) {
+ scoped_ptr<MappedHostResolver> resolver(
+ new MappedHostResolver(scoped_ptr<HostResolver>()));
+
+ EXPECT_FALSE(resolver->AddRuleFromString("xyz"));
+ EXPECT_FALSE(resolver->AddRuleFromString(std::string()));
+ EXPECT_FALSE(resolver->AddRuleFromString(" "));
+ EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE"));
+ EXPECT_FALSE(resolver->AddRuleFromString("EXCLUDE foo bar"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x"));
+ EXPECT_FALSE(resolver->AddRuleFromString("INCLUDE x :10"));
+}
+
+// Test mapping hostnames to resolving failures.
+TEST(MappedHostResolverTest, MapToError) {
+ scoped_ptr<MockHostResolver> resolver_impl(new MockHostResolver());
+ resolver_impl->rules()->AddRule("*", "192.168.1.5");
+
+ scoped_ptr<MappedHostResolver> resolver(
+ new MappedHostResolver(resolver_impl.PassAs<HostResolver>()));
+
+ int rv;
+ AddressList address_list;
+
+ // Remap *.google.com to resolving failures.
+ EXPECT_TRUE(resolver->AddRuleFromString("MAP *.google.com ~NOTFOUND"));
+
+ // Try resolving www.google.com --> Should give an error.
+ TestCompletionCallback callback1;
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.google.com", 80)),
+ &address_list, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
+
+ // Try resolving www.foo.com --> Should succeed.
+ TestCompletionCallback callback2;
+ rv = resolver->Resolve(HostResolver::RequestInfo(
+ HostPortPair("www.foo.com", 80)),
+ &address_list, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("192.168.1.5:80", FirstAddress(address_list));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/mdns_cache.cc b/chromium/net/dns/mdns_cache.cc
new file mode 100644
index 00000000000..010a34f45d4
--- /dev/null
+++ b/chromium/net/dns/mdns_cache.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2013 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/dns/mdns_cache.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/record_parsed.h"
+#include "net/dns/record_rdata.h"
+
+// TODO(noamsml): Recursive CNAME closure (backwards and forwards).
+
+namespace net {
+
+// The effective TTL given to records with a nominal zero TTL.
+// Allows time for hosts to send updated records, as detailed in RFC 6762
+// Section 10.1.
+static const unsigned kZeroTTLSeconds = 1;
+
+MDnsCache::Key::Key(unsigned type, const std::string& name,
+ const std::string& optional)
+ : type_(type), name_(name), optional_(optional) {
+}
+
+MDnsCache::Key::Key(
+ const MDnsCache::Key& other)
+ : type_(other.type_), name_(other.name_), optional_(other.optional_) {
+}
+
+
+MDnsCache::Key& MDnsCache::Key::operator=(
+ const MDnsCache::Key& other) {
+ type_ = other.type_;
+ name_ = other.name_;
+ optional_ = other.optional_;
+ return *this;
+}
+
+MDnsCache::Key::~Key() {
+}
+
+bool MDnsCache::Key::operator<(const MDnsCache::Key& key) const {
+ if (name_ != key.name_)
+ return name_ < key.name_;
+
+ if (type_ != key.type_)
+ return type_ < key.type_;
+
+ if (optional_ != key.optional_)
+ return optional_ < key.optional_;
+ return false; // keys are equal
+}
+
+bool MDnsCache::Key::operator==(const MDnsCache::Key& key) const {
+ return type_ == key.type_ && name_ == key.name_ && optional_ == key.optional_;
+}
+
+// static
+MDnsCache::Key MDnsCache::Key::CreateFor(const RecordParsed* record) {
+ return Key(record->type(),
+ record->name(),
+ GetOptionalFieldForRecord(record));
+}
+
+
+MDnsCache::MDnsCache() {
+}
+
+MDnsCache::~MDnsCache() {
+ Clear();
+}
+
+void MDnsCache::Clear() {
+ next_expiration_ = base::Time();
+ STLDeleteValues(&mdns_cache_);
+}
+
+const RecordParsed* MDnsCache::LookupKey(const Key& key) {
+ RecordMap::iterator found = mdns_cache_.find(key);
+ if (found != mdns_cache_.end()) {
+ return found->second;
+ }
+ return NULL;
+}
+
+MDnsCache::UpdateType MDnsCache::UpdateDnsRecord(
+ scoped_ptr<const RecordParsed> record) {
+ Key cache_key = Key::CreateFor(record.get());
+
+ // Ignore "goodbye" packets for records not in cache.
+ if (record->ttl() == 0 && mdns_cache_.find(cache_key) == mdns_cache_.end())
+ return NoChange;
+
+ base::Time new_expiration = GetEffectiveExpiration(record.get());
+ if (next_expiration_ != base::Time())
+ new_expiration = std::min(new_expiration, next_expiration_);
+
+ std::pair<RecordMap::iterator, bool> insert_result =
+ mdns_cache_.insert(std::make_pair(cache_key, (const RecordParsed*)NULL));
+ UpdateType type = NoChange;
+ if (insert_result.second) {
+ type = RecordAdded;
+ } else {
+ const RecordParsed* other_record = insert_result.first->second;
+
+ if (record->ttl() != 0 && !record->IsEqual(other_record, true)) {
+ type = RecordChanged;
+ }
+ delete other_record;
+ }
+
+ insert_result.first->second = record.release();
+ next_expiration_ = new_expiration;
+ return type;
+}
+
+void MDnsCache::CleanupRecords(
+ base::Time now,
+ const RecordRemovedCallback& record_removed_callback) {
+ base::Time next_expiration;
+
+ // We are guaranteed that |next_expiration_| will be at or before the next
+ // expiration. This allows clients to eagrely call CleanupRecords with
+ // impunity.
+ if (now < next_expiration_) return;
+
+ for (RecordMap::iterator i = mdns_cache_.begin();
+ i != mdns_cache_.end(); ) {
+ base::Time expiration = GetEffectiveExpiration(i->second);
+ if (now >= expiration) {
+ record_removed_callback.Run(i->second);
+ delete i->second;
+ mdns_cache_.erase(i++);
+ } else {
+ if (next_expiration == base::Time() || expiration < next_expiration) {
+ next_expiration = expiration;
+ }
+ ++i;
+ }
+ }
+
+ next_expiration_ = next_expiration;
+}
+
+void MDnsCache::FindDnsRecords(unsigned type,
+ const std::string& name,
+ std::vector<const RecordParsed*>* results,
+ base::Time now) const {
+ DCHECK(results);
+ results->clear();
+
+ RecordMap::const_iterator i = mdns_cache_.lower_bound(Key(type, name, ""));
+ for (; i != mdns_cache_.end(); ++i) {
+ if (i->first.name() != name ||
+ (type != 0 && i->first.type() != type)) {
+ break;
+ }
+
+ const RecordParsed* record = i->second;
+
+ // Records are deleted only upon request.
+ if (now >= GetEffectiveExpiration(record)) continue;
+
+ results->push_back(record);
+ }
+}
+
+scoped_ptr<const RecordParsed> MDnsCache::RemoveRecord(
+ const RecordParsed* record) {
+ Key key = Key::CreateFor(record);
+ RecordMap::iterator found = mdns_cache_.find(key);
+
+ if (found != mdns_cache_.end() && found->second == record) {
+ mdns_cache_.erase(key);
+ return scoped_ptr<const RecordParsed>(record);
+ }
+
+ return scoped_ptr<const RecordParsed>();
+}
+
+// static
+std::string MDnsCache::GetOptionalFieldForRecord(
+ const RecordParsed* record) {
+ switch (record->type()) {
+ case PtrRecordRdata::kType: {
+ const PtrRecordRdata* rdata = record->rdata<PtrRecordRdata>();
+ return rdata->ptrdomain();
+ }
+ default: // Most records are considered unique for our purposes
+ return "";
+ }
+}
+
+// static
+base::Time MDnsCache::GetEffectiveExpiration(const RecordParsed* record) {
+ base::TimeDelta ttl;
+
+ if (record->ttl()) {
+ ttl = base::TimeDelta::FromSeconds(record->ttl());
+ } else {
+ ttl = base::TimeDelta::FromSeconds(kZeroTTLSeconds);
+ }
+
+ return record->time_created() + ttl;
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mdns_cache.h b/chromium/net/dns/mdns_cache.h
new file mode 100644
index 00000000000..27a14d8fa44
--- /dev/null
+++ b/chromium/net/dns/mdns_cache.h
@@ -0,0 +1,119 @@
+// Copyright (c) 2013 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_DNS_MDNS_CACHE_H_
+#define NET_DNS_MDNS_CACHE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class ParsedDnsRecord;
+class RecordParsed;
+
+// mDNS Cache
+// This is a cache of mDNS records. It keeps track of expiration times and is
+// guaranteed not to return expired records. It also has facilities for timely
+// record expiration.
+class NET_EXPORT_PRIVATE MDnsCache {
+ public:
+ // Key type for the record map. It is a 3-tuple of type, name and optional
+ // value ordered by type, then name, then optional value. This allows us to
+ // query for all records of a certain type and name, while also allowing us
+ // to set records of a certain type, name and optionally value as unique.
+ class Key {
+ public:
+ Key(unsigned type, const std::string& name, const std::string& optional);
+ Key(const Key&);
+ Key& operator=(const Key&);
+ ~Key();
+ bool operator<(const Key& key) const;
+ bool operator==(const Key& key) const;
+
+ unsigned type() const { return type_; }
+ const std::string& name() const { return name_; }
+ const std::string& optional() const { return optional_; }
+
+ // Create the cache key corresponding to |record|.
+ static Key CreateFor(const RecordParsed* record);
+ private:
+ unsigned type_;
+ std::string name_;
+ std::string optional_;
+ };
+
+ typedef base::Callback<void(const RecordParsed*)> RecordRemovedCallback;
+
+ enum UpdateType {
+ RecordAdded,
+ RecordChanged,
+ NoChange
+ };
+
+ MDnsCache();
+ ~MDnsCache();
+
+ // Return value indicates whether the record was added, changed
+ // (existed previously with different value) or not changed (existed
+ // previously with same value).
+ UpdateType UpdateDnsRecord(scoped_ptr<const RecordParsed> record);
+
+ // Check cache for record with key |key|. Return the record if it exists, or
+ // NULL if it doesn't.
+ const RecordParsed* LookupKey(const Key& key);
+
+ // Return records with type |type| and name |name|. Expired records will not
+ // be returned. If |type| is zero, return all records with name |name|.
+ void FindDnsRecords(unsigned type,
+ const std::string& name,
+ std::vector<const RecordParsed*>* records,
+ base::Time now) const;
+
+ // Remove expired records, call |record_removed_callback| for every removed
+ // record.
+ void CleanupRecords(base::Time now,
+ const RecordRemovedCallback& record_removed_callback);
+
+ // Returns a time less than or equal to the next time a record will expire.
+ // Is updated when CleanupRecords or UpdateDnsRecord are called. Returns
+ // base::Time when the cache is empty.
+ base::Time next_expiration() const { return next_expiration_; }
+
+ // Remove a record from the cache. Returns a scoped version of the pointer
+ // passed in if it was removed, scoped null otherwise.
+ scoped_ptr<const RecordParsed> RemoveRecord(const RecordParsed* record);
+
+ void Clear();
+
+ private:
+ typedef std::map<Key, const RecordParsed*> RecordMap;
+
+ // Get the effective expiration of a cache entry, based on its creation time
+ // and TTL. Does adjustments so entries with a TTL of zero will have a
+ // nonzero TTL, as explained in RFC 6762 Section 10.1.
+ static base::Time GetEffectiveExpiration(const RecordParsed* entry);
+
+ // Get optional part of the DNS key for shared records. For example, in PTR
+ // records this is the pointed domain, since multiple PTR records may exist
+ // for the same name.
+ static std::string GetOptionalFieldForRecord(
+ const RecordParsed* record);
+
+ RecordMap mdns_cache_;
+
+ base::Time next_expiration_;
+
+ DISALLOW_COPY_AND_ASSIGN(MDnsCache);
+};
+
+} // namespace net
+
+#endif // NET_DNS_MDNS_CACHE_H_
diff --git a/chromium/net/dns/mdns_cache_unittest.cc b/chromium/net/dns/mdns_cache_unittest.cc
new file mode 100644
index 00000000000..c12ad6b6ec3
--- /dev/null
+++ b/chromium/net/dns/mdns_cache_unittest.cc
@@ -0,0 +1,375 @@
+// Copyright (c) 2013 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 <algorithm>
+
+#include "base/bind.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_test_util.h"
+#include "net/dns/mdns_cache.h"
+#include "net/dns/record_parsed.h"
+#include "net/dns/record_rdata.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Return;
+using ::testing::StrictMock;
+
+namespace net {
+
+static const uint8 kTestResponsesDifferentAnswers[] = {
+ // Answer 1
+ // ghs.l.google.com in DNS format.
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+
+ // Answer 2
+ // Pointer to answer 1
+ 0xc0, 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 122, // RDATA is the IP: 74.125.95.122
+};
+
+static const uint8 kTestResponsesSameAnswers[] = {
+ // Answer 1
+ // ghs.l.google.com in DNS format.
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+
+ // Answer 2
+ // Pointer to answer 1
+ 0xc0, 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 112, // TTL (4 bytes) is 112 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+};
+
+static const uint8 kTestResponseTwoRecords[] = {
+ // Answer 1
+ // ghs.l.google.com in DNS format. (A)
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+
+ // Answer 2
+ // ghs.l.google.com in DNS format. (AAAA)
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x1c, // TYPE is AAA.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 16, // RDLENGTH is 16 bytes.
+ 0x4a, 0x7d, 0x4a, 0x7d,
+ 0x5f, 0x79, 0x5f, 0x79,
+ 0x5f, 0x79, 0x5f, 0x79,
+ 0x5f, 0x79, 0x5f, 0x79,
+};
+
+static const uint8 kTestResponsesGoodbyePacket[] = {
+ // Answer 1
+ // ghs.l.google.com in DNS format. (Goodbye packet)
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 0, // TTL (4 bytes) is zero.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+
+ // Answer 2
+ // ghs.l.google.com in DNS format.
+ 3, 'g', 'h', 's',
+ 1, 'l',
+ 6, 'g', 'o', 'o', 'g', 'l', 'e',
+ 3, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0, 0, 0, 53, // TTL (4 bytes) is 53 seconds.
+ 0, 4, // RDLENGTH is 4 bytes.
+ 74, 125, 95, 121, // RDATA is the IP: 74.125.95.121
+};
+
+class RecordRemovalMock {
+ public:
+ MOCK_METHOD1(OnRecordRemoved, void(const RecordParsed*));
+};
+
+class MDnsCacheTest : public ::testing::Test {
+ public:
+ MDnsCacheTest()
+ : default_time_(base::Time::FromDoubleT(1234.0)) {}
+ virtual ~MDnsCacheTest() {}
+
+ protected:
+ base::Time default_time_;
+ StrictMock<RecordRemovalMock> record_removal_;
+ MDnsCache cache_;
+};
+
+// Test a single insert, corresponding lookup, and unsuccessful lookup.
+TEST_F(MDnsCacheTest, InsertLookupSingle) {
+ DnsRecordParser parser(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+ sizeof(dns_protocol::Header));
+ parser.SkipQuestion();
+
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ record2 = RecordParsed::CreateFrom(&parser, default_time_);
+
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record2.Pass()));
+
+ cache_.FindDnsRecords(ARecordRdata::kType, "ghs.l.google.com", &results,
+ default_time_);
+
+ EXPECT_EQ(1u, results.size());
+ EXPECT_EQ(default_time_, results.front()->time_created());
+
+ EXPECT_EQ("ghs.l.google.com", results.front()->name());
+
+ results.clear();
+ cache_.FindDnsRecords(PtrRecordRdata::kType, "ghs.l.google.com", &results,
+ default_time_);
+
+ EXPECT_EQ(0u, results.size());
+}
+
+// Test that records expire when their ttl has passed.
+TEST_F(MDnsCacheTest, Expiration) {
+ DnsRecordParser parser(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+ sizeof(dns_protocol::Header));
+ parser.SkipQuestion();
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+
+ std::vector<const RecordParsed*> results;
+ const RecordParsed* record_to_be_deleted;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ base::TimeDelta ttl1 = base::TimeDelta::FromSeconds(record1->ttl());
+
+ record2 = RecordParsed::CreateFrom(&parser, default_time_);
+ base::TimeDelta ttl2 = base::TimeDelta::FromSeconds(record2->ttl());
+ record_to_be_deleted = record2.get();
+
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record2.Pass()));
+
+ cache_.FindDnsRecords(ARecordRdata::kType, "ghs.l.google.com", &results,
+ default_time_);
+
+ EXPECT_EQ(1u, results.size());
+
+ EXPECT_EQ(default_time_ + ttl2, cache_.next_expiration());
+
+
+ cache_.FindDnsRecords(ARecordRdata::kType, "ghs.l.google.com", &results,
+ default_time_ + ttl2);
+
+ EXPECT_EQ(0u, results.size());
+
+ EXPECT_CALL(record_removal_, OnRecordRemoved(record_to_be_deleted));
+
+ cache_.CleanupRecords(default_time_ + ttl2, base::Bind(
+ &RecordRemovalMock::OnRecordRemoved, base::Unretained(&record_removal_)));
+
+ // To make sure that we've indeed removed them from the map, check no funny
+ // business happens once they're deleted for good.
+
+ EXPECT_EQ(default_time_ + ttl1, cache_.next_expiration());
+ cache_.FindDnsRecords(ARecordRdata::kType, "ghs.l.google.com", &results,
+ default_time_ + ttl2);
+
+ EXPECT_EQ(0u, results.size());
+}
+
+// Test that a new record replacing one with the same identity (name/rrtype for
+// unique records) causes the cache to output a "record changed" event.
+TEST_F(MDnsCacheTest, RecordChange) {
+ DnsRecordParser parser(kTestResponsesDifferentAnswers,
+ sizeof(kTestResponsesDifferentAnswers),
+ 0);
+
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ record2 = RecordParsed::CreateFrom(&parser, default_time_);
+
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+ EXPECT_EQ(MDnsCache::RecordChanged,
+ cache_.UpdateDnsRecord(record2.Pass()));
+}
+
+// Test that a new record replacing an otherwise identical one already in the
+// cache causes the cache to output a "no change" event.
+TEST_F(MDnsCacheTest, RecordNoChange) {
+ DnsRecordParser parser(kTestResponsesSameAnswers,
+ sizeof(kTestResponsesSameAnswers),
+ 0);
+
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ record2 = RecordParsed::CreateFrom(&parser, default_time_ +
+ base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+ EXPECT_EQ(MDnsCache::NoChange, cache_.UpdateDnsRecord(record2.Pass()));
+}
+
+// Test that the next expiration time of the cache is updated properly on record
+// insertion.
+TEST_F(MDnsCacheTest, RecordPreemptExpirationTime) {
+ DnsRecordParser parser(kTestResponsesSameAnswers,
+ sizeof(kTestResponsesSameAnswers),
+ 0);
+
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ record2 = RecordParsed::CreateFrom(&parser, default_time_);
+ base::TimeDelta ttl1 = base::TimeDelta::FromSeconds(record1->ttl());
+ base::TimeDelta ttl2 = base::TimeDelta::FromSeconds(record2->ttl());
+
+ EXPECT_EQ(base::Time(), cache_.next_expiration());
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record2.Pass()));
+ EXPECT_EQ(default_time_ + ttl2, cache_.next_expiration());
+ EXPECT_EQ(MDnsCache::NoChange, cache_.UpdateDnsRecord(record1.Pass()));
+ EXPECT_EQ(default_time_ + ttl1, cache_.next_expiration());
+}
+
+// Test that the cache handles mDNS "goodbye" packets correctly, not adding the
+// records to the cache if they are not already there, and eventually removing
+// records from the cache if they are.
+TEST_F(MDnsCacheTest, GoodbyePacket) {
+ DnsRecordParser parser(kTestResponsesGoodbyePacket,
+ sizeof(kTestResponsesGoodbyePacket),
+ 0);
+
+ scoped_ptr<const RecordParsed> record_goodbye;
+ scoped_ptr<const RecordParsed> record_hello;
+ scoped_ptr<const RecordParsed> record_goodbye2;
+ std::vector<const RecordParsed*> results;
+
+ record_goodbye = RecordParsed::CreateFrom(&parser, default_time_);
+ record_hello = RecordParsed::CreateFrom(&parser, default_time_);
+ parser = DnsRecordParser(kTestResponsesGoodbyePacket,
+ sizeof(kTestResponsesGoodbyePacket),
+ 0);
+ record_goodbye2 = RecordParsed::CreateFrom(&parser, default_time_);
+
+ base::TimeDelta ttl = base::TimeDelta::FromSeconds(record_hello->ttl());
+
+ EXPECT_EQ(base::Time(), cache_.next_expiration());
+ EXPECT_EQ(MDnsCache::NoChange, cache_.UpdateDnsRecord(record_goodbye.Pass()));
+ EXPECT_EQ(base::Time(), cache_.next_expiration());
+ EXPECT_EQ(MDnsCache::RecordAdded,
+ cache_.UpdateDnsRecord(record_hello.Pass()));
+ EXPECT_EQ(default_time_ + ttl, cache_.next_expiration());
+ EXPECT_EQ(MDnsCache::NoChange,
+ cache_.UpdateDnsRecord(record_goodbye2.Pass()));
+ EXPECT_EQ(default_time_ + base::TimeDelta::FromSeconds(1),
+ cache_.next_expiration());
+}
+
+TEST_F(MDnsCacheTest, AnyRRType) {
+ DnsRecordParser parser(kTestResponseTwoRecords,
+ sizeof(kTestResponseTwoRecords),
+ 0);
+
+ scoped_ptr<const RecordParsed> record1;
+ scoped_ptr<const RecordParsed> record2;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ record2 = RecordParsed::CreateFrom(&parser, default_time_);
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record2.Pass()));
+
+ cache_.FindDnsRecords(0, "ghs.l.google.com", &results, default_time_);
+
+ EXPECT_EQ(2u, results.size());
+ EXPECT_EQ(default_time_, results.front()->time_created());
+
+ EXPECT_EQ("ghs.l.google.com", results[0]->name());
+ EXPECT_EQ("ghs.l.google.com", results[1]->name());
+ EXPECT_EQ(dns_protocol::kTypeA,
+ std::min(results[0]->type(), results[1]->type()));
+ EXPECT_EQ(dns_protocol::kTypeAAAA,
+ std::max(results[0]->type(), results[1]->type()));
+}
+
+TEST_F(MDnsCacheTest, RemoveRecord) {
+ DnsRecordParser parser(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+ sizeof(dns_protocol::Header));
+ parser.SkipQuestion();
+
+ scoped_ptr<const RecordParsed> record1;
+ std::vector<const RecordParsed*> results;
+
+ record1 = RecordParsed::CreateFrom(&parser, default_time_);
+ EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+
+ cache_.FindDnsRecords(dns_protocol::kTypeCNAME, "codereview.chromium.org",
+ &results, default_time_);
+
+ EXPECT_EQ(1u, results.size());
+
+ scoped_ptr<const RecordParsed> record_out =
+ cache_.RemoveRecord(results.front());
+
+ EXPECT_EQ(record_out.get(), results.front());
+
+ cache_.FindDnsRecords(dns_protocol::kTypeCNAME, "codereview.chromium.org",
+ &results, default_time_);
+
+ EXPECT_EQ(0u, results.size());
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mdns_client.cc b/chromium/net/dns/mdns_client.cc
new file mode 100644
index 00000000000..631b01a706e
--- /dev/null
+++ b/chromium/net/dns/mdns_client.cc
@@ -0,0 +1,17 @@
+// Copyright 2013 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/dns/mdns_client.h"
+
+#include "net/dns/mdns_client_impl.h"
+
+namespace net {
+
+// static
+scoped_ptr<MDnsClient> MDnsClient::CreateDefault() {
+ return scoped_ptr<MDnsClient>(
+ new MDnsClientImpl(MDnsConnection::SocketFactory::CreateDefault()));
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mdns_client.h b/chromium/net/dns/mdns_client.h
new file mode 100644
index 00000000000..f12c6e292cd
--- /dev/null
+++ b/chromium/net/dns/mdns_client.h
@@ -0,0 +1,158 @@
+// Copyright 2013 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_DNS_MDNS_CLIENT_H_
+#define NET_DNS_MDNS_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/record_parsed.h"
+
+namespace net {
+
+class RecordParsed;
+
+// Represents a one-time record lookup. A transaction takes one
+// associated callback (see |MDnsClient::CreateTransaction|) and calls it
+// whenever a matching record has been found, either from the cache or
+// by querying the network (it may choose to query either or both based on its
+// creation flags, see MDnsTransactionFlags). Network-based transactions will
+// time out after a reasonable number of seconds.
+class NET_EXPORT MDnsTransaction {
+ public:
+ // Used to signify what type of result the transaction has recieved.
+ enum Result {
+ // Passed whenever a record is found.
+ RESULT_RECORD,
+ // The transaction is done. Applies to non-single-valued transactions. Is
+ // called when the transaction has finished (this is the last call to the
+ // callback).
+ RESULT_DONE,
+ // No results have been found. Applies to single-valued transactions. Is
+ // called when the transaction has finished without finding any results.
+ // For transactions that use the network, this happens when a timeout
+ // occurs, for transactions that are cache-only, this happens when no
+ // results are in the cache.
+ RESULT_NO_RESULTS,
+ // Called when an NSec record is read for this transaction's
+ // query. This means there cannot possibly be a record of the type
+ // and name for this transaction.
+ RESULT_NSEC
+ };
+
+ // Used when creating an MDnsTransaction.
+ enum Flags {
+ // Transaction should return only one result, and stop listening after it.
+ // Note that single result transactions will signal when their timeout is
+ // reached, whereas multi-result transactions will not.
+ SINGLE_RESULT = 1 << 0,
+ // Query the cache or the network. May both be used. One must be present.
+ QUERY_CACHE = 1 << 1,
+ QUERY_NETWORK = 1 << 2,
+ // TODO(noamsml): Add flag for flushing cache when feature is implemented
+ // Mask of all possible flags on MDnsTransaction.
+ FLAG_MASK = (1 << 3) - 1,
+ };
+
+ typedef base::Callback<void(Result, const RecordParsed*)>
+ ResultCallback;
+
+ // Destroying the transaction cancels it.
+ virtual ~MDnsTransaction() {}
+
+ // Start the transaction. Return true on success. Cache-based transactions
+ // will execute the callback synchronously.
+ virtual bool Start() = 0;
+
+ // Get the host or service name for the transaction.
+ virtual const std::string& GetName() const = 0;
+
+ // Get the type for this transaction (SRV, TXT, A, AAA, etc)
+ virtual uint16 GetType() const = 0;
+};
+
+// A listener listens for updates regarding a specific record or set of records.
+// Created by the MDnsClient (see |MDnsClient::CreateListener|) and used to keep
+// track of listeners.
+class NET_EXPORT MDnsListener {
+ public:
+ // Used in the MDnsListener delegate to signify what type of change has been
+ // made to a record.
+ enum UpdateType {
+ RECORD_ADDED,
+ RECORD_CHANGED,
+ RECORD_REMOVED
+ };
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called when a record is added, removed or updated.
+ virtual void OnRecordUpdate(UpdateType update,
+ const RecordParsed* record) = 0;
+
+ // Called when a record is marked nonexistent by an NSEC record.
+ virtual void OnNsecRecord(const std::string& name, unsigned type) = 0;
+
+ // Called when the cache is purged (due, for example, ot the network
+ // disconnecting).
+ virtual void OnCachePurged() = 0;
+ };
+
+ // Destroying the listener stops listening.
+ virtual ~MDnsListener() {}
+
+ // Start the listener. Return true on success.
+ virtual bool Start() = 0;
+
+ // Get the host or service name for this query.
+ // Return an empty string for no name.
+ virtual const std::string& GetName() const = 0;
+
+ // Get the type for this query (SRV, TXT, A, AAA, etc)
+ virtual uint16 GetType() const = 0;
+};
+
+// Listens for Multicast DNS on the local network. You can access information
+// regarding multicast DNS either by creating an |MDnsListener| to be notified
+// of new records, or by creating an |MDnsTransaction| to look up the value of a
+// specific records. When all listeners and active transactions are destroyed,
+// the client stops listening on the network and destroys the cache.
+class NET_EXPORT MDnsClient {
+ public:
+ virtual ~MDnsClient() {}
+
+ // Create listener object for RRType |rrtype| and name |name|.
+ virtual scoped_ptr<MDnsListener> CreateListener(
+ uint16 rrtype,
+ const std::string& name,
+ MDnsListener::Delegate* delegate) = 0;
+
+ // Create a transaction that can be used to query either the MDns cache, the
+ // network, or both for records of type |rrtype| and name |name|. |flags| is
+ // defined by MDnsTransactionFlags.
+ virtual scoped_ptr<MDnsTransaction> CreateTransaction(
+ uint16 rrtype,
+ const std::string& name,
+ int flags,
+ const MDnsTransaction::ResultCallback& callback) = 0;
+
+ virtual bool StartListening() = 0;
+
+ // Do not call this inside callbacks from related MDnsListener and
+ // MDnsTransaction objects.
+ virtual void StopListening() = 0;
+ virtual bool IsListening() const = 0;
+
+ // Create the default MDnsClient
+ static scoped_ptr<MDnsClient> CreateDefault();
+};
+
+} // namespace net
+#endif // NET_DNS_MDNS_CLIENT_H_
diff --git a/chromium/net/dns/mdns_client_impl.cc b/chromium/net/dns/mdns_client_impl.cc
new file mode 100644
index 00000000000..8f79edf4cdf
--- /dev/null
+++ b/chromium/net/dns/mdns_client_impl.cc
@@ -0,0 +1,671 @@
+// Copyright 2013 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/dns/mdns_client_impl.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "base/time/default_clock.h"
+#include "net/base/dns_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/rand_callback.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/record_rdata.h"
+#include "net/udp/datagram_socket.h"
+
+// TODO(gene): Remove this temporary method of disabling NSEC support once it
+// becomes clear whether this feature should be
+// supported. http://crbug.com/255232
+#define ENABLE_NSEC
+
+namespace net {
+
+namespace {
+const char kMDnsMulticastGroupIPv4[] = "224.0.0.251";
+const char kMDnsMulticastGroupIPv6[] = "FF02::FB";
+const unsigned MDnsTransactionTimeoutSeconds = 3;
+}
+
+MDnsConnection::SocketHandler::SocketHandler(
+ MDnsConnection* connection, const IPEndPoint& multicast_addr,
+ MDnsConnection::SocketFactory* socket_factory)
+ : socket_(socket_factory->CreateSocket()), connection_(connection),
+ response_(new DnsResponse(dns_protocol::kMaxMulticastSize)),
+ multicast_addr_(multicast_addr) {
+}
+
+MDnsConnection::SocketHandler::~SocketHandler() {
+}
+
+int MDnsConnection::SocketHandler::Start() {
+ int rv = BindSocket();
+ if (rv != OK) {
+ return rv;
+ }
+
+ return DoLoop(0);
+}
+
+int MDnsConnection::SocketHandler::DoLoop(int rv) {
+ do {
+ if (rv > 0)
+ connection_->OnDatagramReceived(response_.get(), recv_addr_, rv);
+
+ rv = socket_->RecvFrom(
+ response_->io_buffer(),
+ response_->io_buffer()->size(),
+ &recv_addr_,
+ base::Bind(&MDnsConnection::SocketHandler::OnDatagramReceived,
+ base::Unretained(this)));
+ } while (rv > 0);
+
+ if (rv != ERR_IO_PENDING)
+ return rv;
+
+ return OK;
+}
+
+void MDnsConnection::SocketHandler::OnDatagramReceived(int rv) {
+ if (rv >= OK)
+ rv = DoLoop(rv);
+
+ if (rv != OK)
+ connection_->OnError(this, rv);
+}
+
+int MDnsConnection::SocketHandler::Send(IOBuffer* buffer, unsigned size) {
+ return socket_->SendTo(
+ buffer, size, multicast_addr_,
+ base::Bind(&MDnsConnection::SocketHandler::SendDone,
+ base::Unretained(this) ));
+}
+
+void MDnsConnection::SocketHandler::SendDone(int rv) {
+ // TODO(noamsml): Retry logic.
+}
+
+int MDnsConnection::SocketHandler::BindSocket() {
+ IPAddressNumber address_any(multicast_addr_.address().size());
+
+ IPEndPoint bind_endpoint(address_any, multicast_addr_.port());
+
+ socket_->AllowAddressReuse();
+ int rv = socket_->Listen(bind_endpoint);
+
+ if (rv < OK) return rv;
+
+ socket_->SetMulticastLoopbackMode(false);
+
+ return socket_->JoinGroup(multicast_addr_.address());
+}
+
+MDnsConnection::MDnsConnection(MDnsConnection::SocketFactory* socket_factory,
+ MDnsConnection::Delegate* delegate)
+ : socket_handler_ipv4_(this,
+ GetMDnsIPEndPoint(kMDnsMulticastGroupIPv4),
+ socket_factory),
+ socket_handler_ipv6_(this,
+ GetMDnsIPEndPoint(kMDnsMulticastGroupIPv6),
+ socket_factory),
+ delegate_(delegate) {
+}
+
+MDnsConnection::~MDnsConnection() {
+}
+
+int MDnsConnection::Init() {
+ int rv;
+
+ rv = socket_handler_ipv4_.Start();
+ if (rv != OK) return rv;
+ rv = socket_handler_ipv6_.Start();
+ if (rv != OK) return rv;
+
+ return OK;
+}
+
+int MDnsConnection::Send(IOBuffer* buffer, unsigned size) {
+ int rv;
+
+ rv = socket_handler_ipv4_.Send(buffer, size);
+ if (rv < OK && rv != ERR_IO_PENDING) return rv;
+
+ rv = socket_handler_ipv6_.Send(buffer, size);
+ if (rv < OK && rv != ERR_IO_PENDING) return rv;
+
+ return OK;
+}
+
+void MDnsConnection::OnError(SocketHandler* loop,
+ int error) {
+ // TODO(noamsml): Specific handling of intermittent errors that can be handled
+ // in the connection.
+ delegate_->OnConnectionError(error);
+}
+
+IPEndPoint MDnsConnection::GetMDnsIPEndPoint(const char* address) {
+ IPAddressNumber multicast_group_number;
+ bool success = ParseIPLiteralToNumber(address,
+ &multicast_group_number);
+ DCHECK(success);
+ return IPEndPoint(multicast_group_number,
+ dns_protocol::kDefaultPortMulticast);
+}
+
+void MDnsConnection::OnDatagramReceived(
+ DnsResponse* response,
+ const IPEndPoint& recv_addr,
+ int bytes_read) {
+ // TODO(noamsml): More sophisticated error handling.
+ DCHECK_GT(bytes_read, 0);
+ delegate_->HandlePacket(response, bytes_read);
+}
+
+class MDnsConnectionSocketFactoryImpl
+ : public MDnsConnection::SocketFactory {
+ public:
+ MDnsConnectionSocketFactoryImpl();
+ virtual ~MDnsConnectionSocketFactoryImpl();
+
+ virtual scoped_ptr<DatagramServerSocket> CreateSocket() OVERRIDE;
+};
+
+MDnsConnectionSocketFactoryImpl::MDnsConnectionSocketFactoryImpl() {
+}
+
+MDnsConnectionSocketFactoryImpl::~MDnsConnectionSocketFactoryImpl() {
+}
+
+scoped_ptr<DatagramServerSocket>
+MDnsConnectionSocketFactoryImpl::CreateSocket() {
+ return scoped_ptr<DatagramServerSocket>(new UDPServerSocket(
+ NULL, NetLog::Source()));
+}
+
+// static
+scoped_ptr<MDnsConnection::SocketFactory>
+MDnsConnection::SocketFactory::CreateDefault() {
+ return scoped_ptr<MDnsConnection::SocketFactory>(
+ new MDnsConnectionSocketFactoryImpl);
+}
+
+MDnsClientImpl::Core::Core(MDnsClientImpl* client,
+ MDnsConnection::SocketFactory* socket_factory)
+ : client_(client), connection_(new MDnsConnection(socket_factory, this)) {
+}
+
+MDnsClientImpl::Core::~Core() {
+ STLDeleteValues(&listeners_);
+}
+
+bool MDnsClientImpl::Core::Init() {
+ return connection_->Init() == OK;
+}
+
+bool MDnsClientImpl::Core::SendQuery(uint16 rrtype, std::string name) {
+ std::string name_dns;
+ if (!DNSDomainFromDot(name, &name_dns))
+ return false;
+
+ DnsQuery query(0, name_dns, rrtype);
+ query.set_flags(0); // Remove the RD flag from the query. It is unneeded.
+
+ return connection_->Send(query.io_buffer(), query.io_buffer()->size()) == OK;
+}
+
+void MDnsClientImpl::Core::HandlePacket(DnsResponse* response,
+ int bytes_read) {
+ unsigned offset;
+ // Note: We store cache keys rather than record pointers to avoid
+ // erroneous behavior in case a packet contains multiple exclusive
+ // records with the same type and name.
+ std::map<MDnsCache::Key, MDnsListener::UpdateType> update_keys;
+
+ if (!response->InitParseWithoutQuery(bytes_read)) {
+ LOG(WARNING) << "Could not understand an mDNS packet.";
+ return; // Message is unreadable.
+ }
+
+ // TODO(noamsml): duplicate query suppression.
+ if (!(response->flags() & dns_protocol::kFlagResponse))
+ return; // Message is a query. ignore it.
+
+ DnsRecordParser parser = response->Parser();
+ unsigned answer_count = response->answer_count() +
+ response->additional_answer_count();
+
+ for (unsigned i = 0; i < answer_count; i++) {
+ offset = parser.GetOffset();
+ scoped_ptr<const RecordParsed> record = RecordParsed::CreateFrom(
+ &parser, base::Time::Now());
+
+ if (!record) {
+ LOG(WARNING) << "Could not understand an mDNS record.";
+
+ if (offset == parser.GetOffset()) {
+ LOG(WARNING) << "Abandoned parsing the rest of the packet.";
+ return; // The parser did not advance, abort reading the packet.
+ } else {
+ continue; // We may be able to extract other records from the packet.
+ }
+ }
+
+ if ((record->klass() & dns_protocol::kMDnsClassMask) !=
+ dns_protocol::kClassIN) {
+ LOG(WARNING) << "Received an mDNS record with non-IN class. Ignoring.";
+ continue; // Ignore all records not in the IN class.
+ }
+
+ MDnsCache::Key update_key = MDnsCache::Key::CreateFor(record.get());
+ MDnsCache::UpdateType update = cache_.UpdateDnsRecord(record.Pass());
+
+ // Cleanup time may have changed.
+ ScheduleCleanup(cache_.next_expiration());
+
+ if (update != MDnsCache::NoChange) {
+ MDnsListener::UpdateType update_external;
+
+ switch (update) {
+ case MDnsCache::RecordAdded:
+ update_external = MDnsListener::RECORD_ADDED;
+ break;
+ case MDnsCache::RecordChanged:
+ update_external = MDnsListener::RECORD_CHANGED;
+ break;
+ case MDnsCache::NoChange:
+ default:
+ NOTREACHED();
+ // Dummy assignment to suppress compiler warning.
+ update_external = MDnsListener::RECORD_CHANGED;
+ break;
+ }
+
+ update_keys.insert(std::make_pair(update_key, update_external));
+ }
+ }
+
+ for (std::map<MDnsCache::Key, MDnsListener::UpdateType>::iterator i =
+ update_keys.begin(); i != update_keys.end(); i++) {
+ const RecordParsed* record = cache_.LookupKey(i->first);
+ if (!record)
+ continue;
+
+ if (record->type() == dns_protocol::kTypeNSEC) {
+#if defined(ENABLE_NSEC)
+ NotifyNsecRecord(record);
+#endif
+ } else {
+ AlertListeners(i->second, ListenerKey(record->name(), record->type()),
+ record);
+ }
+ }
+}
+
+void MDnsClientImpl::Core::NotifyNsecRecord(const RecordParsed* record) {
+ DCHECK_EQ(dns_protocol::kTypeNSEC, record->type());
+ const NsecRecordRdata* rdata = record->rdata<NsecRecordRdata>();
+ DCHECK(rdata);
+
+ // Remove all cached records matching the nonexistent RR types.
+ std::vector<const RecordParsed*> records_to_remove;
+
+ cache_.FindDnsRecords(0, record->name(), &records_to_remove,
+ base::Time::Now());
+
+ for (std::vector<const RecordParsed*>::iterator i = records_to_remove.begin();
+ i != records_to_remove.end(); i++) {
+ if ((*i)->type() == dns_protocol::kTypeNSEC)
+ continue;
+ if (!rdata->GetBit((*i)->type())) {
+ scoped_ptr<const RecordParsed> record_removed = cache_.RemoveRecord((*i));
+ DCHECK(record_removed);
+ OnRecordRemoved(record_removed.get());
+ }
+ }
+
+ // Alert all listeners waiting for the nonexistent RR types.
+ ListenerMap::iterator i =
+ listeners_.upper_bound(ListenerKey(record->name(), 0));
+ for (; i != listeners_.end() && i->first.first == record->name(); i++) {
+ if (!rdata->GetBit(i->first.second)) {
+ FOR_EACH_OBSERVER(MDnsListenerImpl, *i->second, AlertNsecRecord());
+ }
+ }
+}
+
+void MDnsClientImpl::Core::OnConnectionError(int error) {
+ // TODO(noamsml): On connection error, recreate connection and flush cache.
+}
+
+void MDnsClientImpl::Core::AlertListeners(
+ MDnsListener::UpdateType update_type,
+ const ListenerKey& key,
+ const RecordParsed* record) {
+ ListenerMap::iterator listener_map_iterator = listeners_.find(key);
+ if (listener_map_iterator == listeners_.end()) return;
+
+ FOR_EACH_OBSERVER(MDnsListenerImpl, *listener_map_iterator->second,
+ AlertDelegate(update_type, record));
+}
+
+void MDnsClientImpl::Core::AddListener(
+ MDnsListenerImpl* listener) {
+ ListenerKey key(listener->GetName(), listener->GetType());
+ std::pair<ListenerMap::iterator, bool> observer_insert_result =
+ listeners_.insert(
+ make_pair(key, static_cast<ObserverList<MDnsListenerImpl>*>(NULL)));
+
+ // If an equivalent key does not exist, actually create the observer list.
+ if (observer_insert_result.second)
+ observer_insert_result.first->second = new ObserverList<MDnsListenerImpl>();
+
+ ObserverList<MDnsListenerImpl>* observer_list =
+ observer_insert_result.first->second;
+
+ observer_list->AddObserver(listener);
+}
+
+void MDnsClientImpl::Core::RemoveListener(MDnsListenerImpl* listener) {
+ ListenerKey key(listener->GetName(), listener->GetType());
+ ListenerMap::iterator observer_list_iterator = listeners_.find(key);
+
+ DCHECK(observer_list_iterator != listeners_.end());
+ DCHECK(observer_list_iterator->second->HasObserver(listener));
+
+ observer_list_iterator->second->RemoveObserver(listener);
+
+ // Remove the observer list from the map if it is empty
+ if (observer_list_iterator->second->size() == 0) {
+ // Schedule the actual removal for later in case the listener removal
+ // happens while iterating over the observer list.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(
+ &MDnsClientImpl::Core::CleanupObserverList, AsWeakPtr(), key));
+ }
+}
+
+void MDnsClientImpl::Core::CleanupObserverList(const ListenerKey& key) {
+ ListenerMap::iterator found = listeners_.find(key);
+ if (found != listeners_.end() && found->second->size() == 0) {
+ delete found->second;
+ listeners_.erase(found);
+ }
+}
+
+void MDnsClientImpl::Core::ScheduleCleanup(base::Time cleanup) {
+ // Cleanup is already scheduled, no need to do anything.
+ if (cleanup == scheduled_cleanup_) return;
+ scheduled_cleanup_ = cleanup;
+
+ // This cancels the previously scheduled cleanup.
+ cleanup_callback_.Reset(base::Bind(
+ &MDnsClientImpl::Core::DoCleanup, base::Unretained(this)));
+
+ // If |cleanup| is empty, then no cleanup necessary.
+ if (cleanup != base::Time()) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ cleanup_callback_.callback(),
+ cleanup - base::Time::Now());
+ }
+}
+
+void MDnsClientImpl::Core::DoCleanup() {
+ cache_.CleanupRecords(base::Time::Now(), base::Bind(
+ &MDnsClientImpl::Core::OnRecordRemoved, base::Unretained(this)));
+
+ ScheduleCleanup(cache_.next_expiration());
+}
+
+void MDnsClientImpl::Core::OnRecordRemoved(
+ const RecordParsed* record) {
+ AlertListeners(MDnsListener::RECORD_REMOVED,
+ ListenerKey(record->name(), record->type()), record);
+}
+
+void MDnsClientImpl::Core::QueryCache(
+ uint16 rrtype, const std::string& name,
+ std::vector<const RecordParsed*>* records) const {
+ cache_.FindDnsRecords(rrtype, name, records, base::Time::Now());
+}
+
+MDnsClientImpl::MDnsClientImpl(
+ scoped_ptr<MDnsConnection::SocketFactory> socket_factory)
+ : socket_factory_(socket_factory.Pass()) {
+}
+
+MDnsClientImpl::~MDnsClientImpl() {
+}
+
+bool MDnsClientImpl::StartListening() {
+ DCHECK(!core_.get());
+ core_.reset(new Core(this, socket_factory_.get()));
+ if (!core_->Init()) {
+ core_.reset();
+ return false;
+ }
+ return true;
+}
+
+void MDnsClientImpl::StopListening() {
+ core_.reset();
+}
+
+bool MDnsClientImpl::IsListening() const {
+ return core_.get() != NULL;
+}
+
+scoped_ptr<MDnsListener> MDnsClientImpl::CreateListener(
+ uint16 rrtype,
+ const std::string& name,
+ MDnsListener::Delegate* delegate) {
+ return scoped_ptr<net::MDnsListener>(
+ new MDnsListenerImpl(rrtype, name, delegate, this));
+}
+
+scoped_ptr<MDnsTransaction> MDnsClientImpl::CreateTransaction(
+ uint16 rrtype,
+ const std::string& name,
+ int flags,
+ const MDnsTransaction::ResultCallback& callback) {
+ return scoped_ptr<MDnsTransaction>(
+ new MDnsTransactionImpl(rrtype, name, flags, callback, this));
+}
+
+MDnsListenerImpl::MDnsListenerImpl(
+ uint16 rrtype,
+ const std::string& name,
+ MDnsListener::Delegate* delegate,
+ MDnsClientImpl* client)
+ : rrtype_(rrtype), name_(name), client_(client), delegate_(delegate),
+ started_(false) {
+}
+
+bool MDnsListenerImpl::Start() {
+ DCHECK(!started_);
+
+ started_ = true;
+
+ DCHECK(client_->core());
+ client_->core()->AddListener(this);
+
+ return true;
+}
+
+MDnsListenerImpl::~MDnsListenerImpl() {
+ if (started_) {
+ DCHECK(client_->core());
+ client_->core()->RemoveListener(this);
+ }
+}
+
+const std::string& MDnsListenerImpl::GetName() const {
+ return name_;
+}
+
+uint16 MDnsListenerImpl::GetType() const {
+ return rrtype_;
+}
+
+void MDnsListenerImpl::AlertDelegate(MDnsListener::UpdateType update_type,
+ const RecordParsed* record) {
+ DCHECK(started_);
+ delegate_->OnRecordUpdate(update_type, record);
+}
+
+void MDnsListenerImpl::AlertNsecRecord() {
+ DCHECK(started_);
+ delegate_->OnNsecRecord(name_, rrtype_);
+}
+
+MDnsTransactionImpl::MDnsTransactionImpl(
+ uint16 rrtype,
+ const std::string& name,
+ int flags,
+ const MDnsTransaction::ResultCallback& callback,
+ MDnsClientImpl* client)
+ : rrtype_(rrtype), name_(name), callback_(callback), client_(client),
+ started_(false), flags_(flags) {
+ DCHECK((flags_ & MDnsTransaction::FLAG_MASK) == flags_);
+ DCHECK(flags_ & MDnsTransaction::QUERY_CACHE ||
+ flags_ & MDnsTransaction::QUERY_NETWORK);
+}
+
+MDnsTransactionImpl::~MDnsTransactionImpl() {
+ timeout_.Cancel();
+}
+
+bool MDnsTransactionImpl::Start() {
+ DCHECK(!started_);
+ started_ = true;
+
+ base::WeakPtr<MDnsTransactionImpl> weak_this = AsWeakPtr();
+ if (flags_ & MDnsTransaction::QUERY_CACHE) {
+ ServeRecordsFromCache();
+
+ if (!weak_this || !is_active()) return true;
+ }
+
+ if (flags_ & MDnsTransaction::QUERY_NETWORK) {
+ return QueryAndListen();
+ }
+
+ // If this is a cache only query, signal that the transaction is over
+ // immediately.
+ SignalTransactionOver();
+ return true;
+}
+
+const std::string& MDnsTransactionImpl::GetName() const {
+ return name_;
+}
+
+uint16 MDnsTransactionImpl::GetType() const {
+ return rrtype_;
+}
+
+void MDnsTransactionImpl::CacheRecordFound(const RecordParsed* record) {
+ DCHECK(started_);
+ OnRecordUpdate(MDnsListener::RECORD_ADDED, record);
+}
+
+void MDnsTransactionImpl::TriggerCallback(MDnsTransaction::Result result,
+ const RecordParsed* record) {
+ DCHECK(started_);
+ if (!is_active()) return;
+
+ // Ensure callback is run after touching all class state, so that
+ // the callback can delete the transaction.
+ MDnsTransaction::ResultCallback callback = callback_;
+
+ // Reset the transaction if it expects a single result, or if the result
+ // is a final one (everything except for a record).
+ if (flags_ & MDnsTransaction::SINGLE_RESULT ||
+ result != MDnsTransaction::RESULT_RECORD) {
+ Reset();
+ }
+
+ callback.Run(result, record);
+}
+
+void MDnsTransactionImpl::Reset() {
+ callback_.Reset();
+ listener_.reset();
+ timeout_.Cancel();
+}
+
+void MDnsTransactionImpl::OnRecordUpdate(MDnsListener::UpdateType update,
+ const RecordParsed* record) {
+ DCHECK(started_);
+ if (update == MDnsListener::RECORD_ADDED ||
+ update == MDnsListener::RECORD_CHANGED)
+ TriggerCallback(MDnsTransaction::RESULT_RECORD, record);
+}
+
+void MDnsTransactionImpl::SignalTransactionOver() {
+ DCHECK(started_);
+ if (flags_ & MDnsTransaction::SINGLE_RESULT) {
+ TriggerCallback(MDnsTransaction::RESULT_NO_RESULTS, NULL);
+ } else {
+ TriggerCallback(MDnsTransaction::RESULT_DONE, NULL);
+ }
+}
+
+void MDnsTransactionImpl::ServeRecordsFromCache() {
+ std::vector<const RecordParsed*> records;
+ base::WeakPtr<MDnsTransactionImpl> weak_this = AsWeakPtr();
+
+ if (client_->core()) {
+ client_->core()->QueryCache(rrtype_, name_, &records);
+ for (std::vector<const RecordParsed*>::iterator i = records.begin();
+ i != records.end() && weak_this; ++i) {
+ weak_this->TriggerCallback(MDnsTransaction::RESULT_RECORD, *i);
+ }
+
+#if defined(ENABLE_NSEC)
+ if (records.empty()) {
+ DCHECK(weak_this);
+ client_->core()->QueryCache(dns_protocol::kTypeNSEC, name_, &records);
+ if (!records.empty()) {
+ const NsecRecordRdata* rdata =
+ records.front()->rdata<NsecRecordRdata>();
+ DCHECK(rdata);
+ if (!rdata->GetBit(rrtype_))
+ weak_this->TriggerCallback(MDnsTransaction::RESULT_NSEC, NULL);
+ }
+ }
+#endif
+ }
+}
+
+bool MDnsTransactionImpl::QueryAndListen() {
+ listener_ = client_->CreateListener(rrtype_, name_, this);
+ if (!listener_->Start())
+ return false;
+
+ DCHECK(client_->core());
+ if (!client_->core()->SendQuery(rrtype_, name_))
+ return false;
+
+ timeout_.Reset(base::Bind(&MDnsTransactionImpl::SignalTransactionOver,
+ AsWeakPtr()));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ timeout_.callback(),
+ base::TimeDelta::FromSeconds(MDnsTransactionTimeoutSeconds));
+
+ return true;
+}
+
+void MDnsTransactionImpl::OnNsecRecord(const std::string& name, unsigned type) {
+ TriggerCallback(RESULT_NSEC, NULL);
+}
+
+void MDnsTransactionImpl::OnCachePurged() {
+ // TODO(noamsml): Cache purge situations not yet implemented
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mdns_client_impl.h b/chromium/net/dns/mdns_client_impl.h
new file mode 100644
index 00000000000..9fe3f99e7dd
--- /dev/null
+++ b/chromium/net/dns/mdns_client_impl.h
@@ -0,0 +1,298 @@
+// Copyright 2013 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_DNS_MDNS_CLIENT_IMPL_H_
+#define NET_DNS_MDNS_CLIENT_IMPL_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "base/observer_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/mdns_cache.h"
+#include "net/dns/mdns_client.h"
+#include "net/udp/datagram_server_socket.h"
+#include "net/udp/udp_server_socket.h"
+#include "net/udp/udp_socket.h"
+
+namespace net {
+
+// A connection to the network for multicast DNS clients. It reads data into
+// DnsResponse objects and alerts the delegate that a packet has been received.
+class NET_EXPORT_PRIVATE MDnsConnection {
+ public:
+ class SocketFactory {
+ public:
+ virtual ~SocketFactory() {}
+
+ virtual scoped_ptr<DatagramServerSocket> CreateSocket() = 0;
+
+ static scoped_ptr<SocketFactory> CreateDefault();
+ };
+
+ class Delegate {
+ public:
+ // Handle an mDNS packet buffered in |response| with a size of |bytes_read|.
+ virtual void HandlePacket(DnsResponse* response, int bytes_read) = 0;
+ virtual void OnConnectionError(int error) = 0;
+ virtual ~Delegate() {}
+ };
+
+ explicit MDnsConnection(SocketFactory* socket_factory,
+ MDnsConnection::Delegate* delegate);
+
+ virtual ~MDnsConnection();
+
+ int Init();
+ int Send(IOBuffer* buffer, unsigned size);
+
+ private:
+ class SocketHandler {
+ public:
+ SocketHandler(MDnsConnection* connection,
+ const IPEndPoint& multicast_addr,
+ SocketFactory* socket_factory);
+ ~SocketHandler();
+ int DoLoop(int rv);
+ int Start();
+
+ int Send(IOBuffer* buffer, unsigned size);
+
+ private:
+ int BindSocket();
+ void OnDatagramReceived(int rv);
+
+ // Callback for when sending a query has finished.
+ void SendDone(int rv);
+
+ scoped_ptr<DatagramServerSocket> socket_;
+
+ MDnsConnection* connection_;
+ IPEndPoint recv_addr_;
+ scoped_ptr<DnsResponse> response_;
+ IPEndPoint multicast_addr_;
+ };
+
+ // Callback for handling a datagram being received on either ipv4 or ipv6.
+ void OnDatagramReceived(DnsResponse* response,
+ const IPEndPoint& recv_addr,
+ int bytes_read);
+
+ void OnError(SocketHandler* loop, int error);
+
+ IPEndPoint GetMDnsIPEndPoint(const char* address);
+
+ SocketHandler socket_handler_ipv4_;
+ SocketHandler socket_handler_ipv6_;
+
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(MDnsConnection);
+};
+
+class MDnsListenerImpl;
+
+class NET_EXPORT_PRIVATE MDnsClientImpl : public MDnsClient {
+ public:
+ // The core object exists while the MDnsClient is listening, and is deleted
+ // whenever the number of listeners reaches zero. The deletion happens
+ // asychronously, so destroying the last listener does not immediately
+ // invalidate the core.
+ class Core : public base::SupportsWeakPtr<Core>, MDnsConnection::Delegate {
+ public:
+ Core(MDnsClientImpl* client,
+ MDnsConnection::SocketFactory* socket_factory);
+ virtual ~Core();
+
+ // Initialize the core. Returns true on success.
+ bool Init();
+
+ // Send a query with a specific rrtype and name. Returns true on success.
+ bool SendQuery(uint16 rrtype, std::string name);
+
+ // Add/remove a listener to the list of listeners.
+ void AddListener(MDnsListenerImpl* listener);
+ void RemoveListener(MDnsListenerImpl* listener);
+
+ // Query the cache for records of a specific type and name.
+ void QueryCache(uint16 rrtype, const std::string& name,
+ std::vector<const RecordParsed*>* records) const;
+
+ // Parse the response and alert relevant listeners.
+ virtual void HandlePacket(DnsResponse* response, int bytes_read) OVERRIDE;
+
+ virtual void OnConnectionError(int error) OVERRIDE;
+
+ private:
+ typedef std::pair<std::string, uint16> ListenerKey;
+ typedef std::map<ListenerKey, ObserverList<MDnsListenerImpl>* >
+ ListenerMap;
+
+ // Alert listeners of an update to the cache.
+ void AlertListeners(MDnsListener::UpdateType update_type,
+ const ListenerKey& key, const RecordParsed* record);
+
+ // Schedule a cache cleanup to a specific time, cancelling other cleanups.
+ void ScheduleCleanup(base::Time cleanup);
+
+ // Clean up the cache and schedule a new cleanup.
+ void DoCleanup();
+
+ // Callback for when a record is removed from the cache.
+ void OnRecordRemoved(const RecordParsed* record);
+
+ void NotifyNsecRecord(const RecordParsed* record);
+
+ // Delete and erase the observer list for |key|. Only deletes the observer
+ // list if is empty.
+ void CleanupObserverList(const ListenerKey& key);
+
+ ListenerMap listeners_;
+
+ MDnsClientImpl* client_;
+ MDnsCache cache_;
+
+ base::CancelableCallback<void()> cleanup_callback_;
+ base::Time scheduled_cleanup_;
+
+ scoped_ptr<MDnsConnection> connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+ };
+
+ explicit MDnsClientImpl(
+ scoped_ptr<MDnsConnection::SocketFactory> socket_factory_);
+ virtual ~MDnsClientImpl();
+
+ // MDnsClient implementation:
+ virtual scoped_ptr<MDnsListener> CreateListener(
+ uint16 rrtype,
+ const std::string& name,
+ MDnsListener::Delegate* delegate) OVERRIDE;
+
+ virtual scoped_ptr<MDnsTransaction> CreateTransaction(
+ uint16 rrtype,
+ const std::string& name,
+ int flags,
+ const MDnsTransaction::ResultCallback& callback) OVERRIDE;
+
+ virtual bool StartListening() OVERRIDE;
+ virtual void StopListening() OVERRIDE;
+ virtual bool IsListening() const OVERRIDE;
+
+ Core* core() { return core_.get(); }
+
+ private:
+ scoped_ptr<Core> core_;
+
+ scoped_ptr<MDnsConnection::SocketFactory> socket_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MDnsClientImpl);
+};
+
+class MDnsListenerImpl : public MDnsListener,
+ public base::SupportsWeakPtr<MDnsListenerImpl> {
+ public:
+ MDnsListenerImpl(uint16 rrtype,
+ const std::string& name,
+ MDnsListener::Delegate* delegate,
+ MDnsClientImpl* client);
+
+ virtual ~MDnsListenerImpl();
+
+ // MDnsListener implementation:
+ virtual bool Start() OVERRIDE;
+
+ virtual const std::string& GetName() const OVERRIDE;
+
+ virtual uint16 GetType() const OVERRIDE;
+
+ MDnsListener::Delegate* delegate() { return delegate_; }
+
+ // Alert the delegate of a record update.
+ void AlertDelegate(MDnsListener::UpdateType update_type,
+ const RecordParsed* record_parsed);
+
+ // Alert the delegate of the existence of an Nsec record.
+ void AlertNsecRecord();
+
+ private:
+ uint16 rrtype_;
+ std::string name_;
+ MDnsClientImpl* client_;
+ MDnsListener::Delegate* delegate_;
+
+ bool started_;
+ DISALLOW_COPY_AND_ASSIGN(MDnsListenerImpl);
+};
+
+class MDnsTransactionImpl : public base::SupportsWeakPtr<MDnsTransactionImpl>,
+ public MDnsTransaction,
+ public MDnsListener::Delegate {
+ public:
+ MDnsTransactionImpl(uint16 rrtype,
+ const std::string& name,
+ int flags,
+ const MDnsTransaction::ResultCallback& callback,
+ MDnsClientImpl* client);
+ virtual ~MDnsTransactionImpl();
+
+ // MDnsTransaction implementation:
+ virtual bool Start() OVERRIDE;
+
+ virtual const std::string& GetName() const OVERRIDE;
+ virtual uint16 GetType() const OVERRIDE;
+
+ // MDnsListener::Delegate implementation:
+ virtual void OnRecordUpdate(MDnsListener::UpdateType update,
+ const RecordParsed* record) OVERRIDE;
+ virtual void OnNsecRecord(const std::string& name, unsigned type) OVERRIDE;
+
+ virtual void OnCachePurged() OVERRIDE;
+
+ private:
+ bool is_active() { return !callback_.is_null(); }
+
+ void Reset();
+
+ // Trigger the callback and reset all related variables.
+ void TriggerCallback(MDnsTransaction::Result result,
+ const RecordParsed* record);
+
+ // Internal callback for when a cache record is found.
+ void CacheRecordFound(const RecordParsed* record);
+
+ // Signal the transactionis over and release all related resources.
+ void SignalTransactionOver();
+
+ // Reads records from the cache and calls the callback for every
+ // record read.
+ void ServeRecordsFromCache();
+
+ // Send a query to the network and set up a timeout to time out the
+ // transaction. Returns false if it fails to start listening on the network
+ // or if it fails to send a query.
+ bool QueryAndListen();
+
+ uint16 rrtype_;
+ std::string name_;
+ MDnsTransaction::ResultCallback callback_;
+
+ scoped_ptr<MDnsListener> listener_;
+ base::CancelableCallback<void()> timeout_;
+
+ MDnsClientImpl* client_;
+
+ bool started_;
+ int flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(MDnsTransactionImpl);
+};
+
+} // namespace net
+#endif // NET_DNS_MDNS_CLIENT_IMPL_H_
diff --git a/chromium/net/dns/mdns_client_unittest.cc b/chromium/net/dns/mdns_client_unittest.cc
new file mode 100644
index 00000000000..324b4dfbee0
--- /dev/null
+++ b/chromium/net/dns/mdns_client_unittest.cc
@@ -0,0 +1,1176 @@
+// Copyright 2013 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 <queue>
+
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/rand_callback.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mdns_client_impl.h"
+#include "net/dns/mock_mdns_socket_factory.h"
+#include "net/dns/record_rdata.h"
+#include "net/udp/udp_client_socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
+using ::testing::StrictMock;
+using ::testing::NiceMock;
+using ::testing::Exactly;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::_;
+
+namespace net {
+
+namespace {
+
+const uint8 kSamplePacket1[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x00, // TTL (4 bytes) is 1 second;
+ 0x00, 0x01,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x0c,
+
+ // Answer 2
+ 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r',
+ 0xc0, 0x14, // Pointer to "._tcp.local"
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 49 seconds.
+ 0x24, 0x75,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x32
+};
+
+const uint8 kCorruptedPacketBadQuestion[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x01, // One question
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Question is corrupted and cannot be read.
+ 0x99, 'h', 'e', 'l', 'l', 'o',
+ 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x99, // RDLENGTH is impossible
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x0c,
+
+ // Answer 2
+ 0x08, '_', 'p', 'r', // Useless trailing data.
+};
+
+const uint8 kCorruptedPacketUnsalvagable[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x99, // RDLENGTH is impossible
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x0c,
+
+ // Answer 2
+ 0x08, '_', 'p', 'r', // Useless trailing data.
+};
+
+const uint8 kCorruptedPacketDoubleRecord[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x06, 'p', 'r', 'i', 'v', 'e', 't',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x04, // RDLENGTH is 4
+ 0x05, 0x03,
+ 0xc0, 0x0c,
+
+ // Answer 2 -- Same key
+ 0x06, 'p', 'r', 'i', 'v', 'e', 't',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x04, // RDLENGTH is 4
+ 0x02, 0x03,
+ 0x04, 0x05,
+};
+
+const uint8 kCorruptedPacketSalvagable[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x99, 'h', 'e', 'l', 'l', 'o', // Bad RDATA format.
+ 0xc0, 0x0c,
+
+ // Answer 2
+ 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r',
+ 0xc0, 0x14, // Pointer to "._tcp.local"
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 49 seconds.
+ 0x24, 0x75,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x32
+};
+
+const uint8 kSamplePacket2[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x02, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'z', 'z', 'z', 'z', 'z',
+ 0xc0, 0x0c,
+
+ // Answer 2
+ 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r',
+ 0xc0, 0x14, // Pointer to "._tcp.local"
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'z', 'z', 'z', 'z', 'z',
+ 0xc0, 0x32
+};
+
+const uint8 kQueryPacketPrivet[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x00, 0x00, // No flags.
+ 0x00, 0x01, // One question.
+ 0x00, 0x00, // 0 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Question
+ // This part is echoed back from the respective query.
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+};
+
+const uint8 kSamplePacketAdditionalOnly[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x00, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x01, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'h', 'e', 'l', 'l', 'o',
+ 0xc0, 0x0c,
+};
+
+const uint8 kSamplePacketNsec[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x01, // 1 RR (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x2f, // TYPE is NSEC.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x06, // RDLENGTH is 6 bytes.
+ 0xc0, 0x0c,
+ 0x00, 0x02, 0x00, 0x08 // Only A record present
+};
+
+const uint8 kSamplePacketAPrivet[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x01, // 1 RR (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x01, // TYPE is A.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x04, // RDLENGTH is 4 bytes.
+ 0xc0, 0x0c,
+ 0x00, 0x02,
+};
+
+const uint8 kSamplePacketGoodbye[] = {
+ // Header
+ 0x00, 0x00, // ID is zeroed out
+ 0x81, 0x80, // Standard query response, RA, no error
+ 0x00, 0x00, // No questions (for simplicity)
+ 0x00, 0x01, // 2 RRs (answers)
+ 0x00, 0x00, // 0 authority RRs
+ 0x00, 0x00, // 0 additional RRs
+
+ // Answer 1
+ 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+ 0x04, '_', 't', 'c', 'p',
+ 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00,
+ 0x00, 0x0c, // TYPE is PTR.
+ 0x00, 0x01, // CLASS is IN.
+ 0x00, 0x00, // TTL (4 bytes) is zero;
+ 0x00, 0x00,
+ 0x00, 0x08, // RDLENGTH is 8 bytes.
+ 0x05, 'z', 'z', 'z', 'z', 'z',
+ 0xc0, 0x0c,
+};
+
+std::string MakeString(const uint8* data, unsigned size) {
+ return std::string(reinterpret_cast<const char*>(data), size);
+}
+
+class PtrRecordCopyContainer {
+ public:
+ PtrRecordCopyContainer() {}
+ ~PtrRecordCopyContainer() {}
+
+ bool is_set() const { return set_; }
+
+ void SaveWithDummyArg(int unused, const RecordParsed* value) {
+ Save(value);
+ }
+
+ void Save(const RecordParsed* value) {
+ set_ = true;
+ name_ = value->name();
+ ptrdomain_ = value->rdata<PtrRecordRdata>()->ptrdomain();
+ ttl_ = value->ttl();
+ }
+
+ bool IsRecordWith(std::string name, std::string ptrdomain) {
+ return set_ && name_ == name && ptrdomain_ == ptrdomain;
+ }
+
+ const std::string& name() { return name_; }
+ const std::string& ptrdomain() { return ptrdomain_; }
+ int ttl() { return ttl_; }
+
+ private:
+ bool set_;
+ std::string name_;
+ std::string ptrdomain_;
+ int ttl_;
+};
+
+class MDnsTest : public ::testing::Test {
+ public:
+ MDnsTest();
+ virtual ~MDnsTest();
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+ void DeleteTransaction();
+ void DeleteBothListeners();
+ void RunFor(base::TimeDelta time_period);
+ void Stop();
+
+ MOCK_METHOD2(MockableRecordCallback, void(MDnsTransaction::Result result,
+ const RecordParsed* record));
+
+ MOCK_METHOD2(MockableRecordCallback2, void(MDnsTransaction::Result result,
+ const RecordParsed* record));
+
+
+ protected:
+ void ExpectPacket(const uint8* packet, unsigned size);
+ void SimulatePacketReceive(const uint8* packet, unsigned size);
+
+ scoped_ptr<MDnsClientImpl> test_client_;
+ IPEndPoint mdns_ipv4_endpoint_;
+ StrictMock<MockMDnsSocketFactory>* socket_factory_;
+
+ // Transactions and listeners that can be deleted by class methods for
+ // reentrancy tests.
+ scoped_ptr<MDnsTransaction> transaction_;
+ scoped_ptr<MDnsListener> listener1_;
+ scoped_ptr<MDnsListener> listener2_;
+};
+
+class MockListenerDelegate : public MDnsListener::Delegate {
+ public:
+ MOCK_METHOD2(OnRecordUpdate,
+ void(MDnsListener::UpdateType update,
+ const RecordParsed* records));
+ MOCK_METHOD2(OnNsecRecord, void(const std::string&, unsigned));
+ MOCK_METHOD0(OnCachePurged, void());
+};
+
+MDnsTest::MDnsTest() {
+ socket_factory_ = new StrictMock<MockMDnsSocketFactory>();
+ test_client_.reset(new MDnsClientImpl(
+ scoped_ptr<MDnsConnection::SocketFactory>(socket_factory_)));
+}
+
+MDnsTest::~MDnsTest() {
+}
+
+void MDnsTest::SetUp() {
+ test_client_->StartListening();
+}
+
+void MDnsTest::TearDown() {
+}
+
+void MDnsTest::SimulatePacketReceive(const uint8* packet, unsigned size) {
+ socket_factory_->SimulateReceive(packet, size);
+}
+
+void MDnsTest::ExpectPacket(const uint8* packet, unsigned size) {
+ EXPECT_CALL(*socket_factory_, OnSendTo(MakeString(packet, size)))
+ .Times(2);
+}
+
+void MDnsTest::DeleteTransaction() {
+ transaction_.reset();
+}
+
+void MDnsTest::DeleteBothListeners() {
+ listener1_.reset();
+ listener2_.reset();
+}
+
+void MDnsTest::RunFor(base::TimeDelta time_period) {
+ base::CancelableCallback<void()> callback(base::Bind(&MDnsTest::Stop,
+ base::Unretained(this)));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, callback.callback(), time_period);
+
+ base::MessageLoop::current()->Run();
+ callback.Cancel();
+}
+
+void MDnsTest::Stop() {
+ base::MessageLoop::current()->Quit();
+}
+
+TEST_F(MDnsTest, PassiveListeners) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+ StrictMock<MockListenerDelegate> delegate_printer;
+
+ PtrRecordCopyContainer record_privet;
+ PtrRecordCopyContainer record_printer;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet);
+ scoped_ptr<MDnsListener> listener_printer = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_printer._tcp.local", &delegate_printer);
+
+ ASSERT_TRUE(listener_privet->Start());
+ ASSERT_TRUE(listener_printer->Start());
+
+ // Send the same packet twice to ensure no records are double-counted.
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(
+ &record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ EXPECT_CALL(delegate_printer, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(
+ &record_printer,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+
+ EXPECT_TRUE(record_printer.IsRecordWith("_printer._tcp.local",
+ "hello._printer._tcp.local"));
+
+ listener_privet.reset();
+ listener_printer.reset();
+}
+
+TEST_F(MDnsTest, PassiveListenersCacheCleanup) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ PtrRecordCopyContainer record_privet;
+ PtrRecordCopyContainer record_privet2;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet);
+
+ ASSERT_TRUE(listener_privet->Start());
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(
+ &record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+
+ // Expect record is removed when its TTL expires.
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_REMOVED, _))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(InvokeWithoutArgs(this, &MDnsTest::Stop),
+ Invoke(&record_privet2,
+ &PtrRecordCopyContainer::SaveWithDummyArg)));
+
+ RunFor(base::TimeDelta::FromSeconds(record_privet.ttl() + 1));
+
+ EXPECT_TRUE(record_privet2.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+}
+
+TEST_F(MDnsTest, MalformedPacket) {
+ StrictMock<MockListenerDelegate> delegate_printer;
+
+ PtrRecordCopyContainer record_printer;
+
+ scoped_ptr<MDnsListener> listener_printer = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_printer._tcp.local", &delegate_printer);
+
+ ASSERT_TRUE(listener_printer->Start());
+
+ EXPECT_CALL(delegate_printer, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(
+ &record_printer,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ // First, send unsalvagable packet to ensure we can deal with it.
+ SimulatePacketReceive(kCorruptedPacketUnsalvagable,
+ sizeof(kCorruptedPacketUnsalvagable));
+
+ // Regression test: send a packet where the question cannot be read.
+ SimulatePacketReceive(kCorruptedPacketBadQuestion,
+ sizeof(kCorruptedPacketBadQuestion));
+
+ // Then send salvagable packet to ensure we can extract useful records.
+ SimulatePacketReceive(kCorruptedPacketSalvagable,
+ sizeof(kCorruptedPacketSalvagable));
+
+ EXPECT_TRUE(record_printer.IsRecordWith("_printer._tcp.local",
+ "hello._printer._tcp.local"));
+}
+
+TEST_F(MDnsTest, TransactionWithEmptyCache) {
+ ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
+
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_privet->Start());
+
+ PtrRecordCopyContainer record_privet;
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_RECORD, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(&record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+}
+
+TEST_F(MDnsTest, TransactionCacheOnlyNoResult) {
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*this,
+ MockableRecordCallback(MDnsTransaction::RESULT_NO_RESULTS, _))
+ .Times(Exactly(1));
+
+ ASSERT_TRUE(transaction_privet->Start());
+}
+
+TEST_F(MDnsTest, TransactionWithCache) {
+ // Listener to force the client to listen
+ StrictMock<MockListenerDelegate> delegate_irrelevant;
+ scoped_ptr<MDnsListener> listener_irrelevant = test_client_->CreateListener(
+ dns_protocol::kTypeA, "codereview.chromium.local",
+ &delegate_irrelevant);
+
+ ASSERT_TRUE(listener_irrelevant->Start());
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+
+ PtrRecordCopyContainer record_privet;
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_RECORD, _))
+ .WillOnce(Invoke(&record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_privet->Start());
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+}
+
+TEST_F(MDnsTest, AdditionalRecords) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ PtrRecordCopyContainer record_privet;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ &delegate_privet);
+
+ ASSERT_TRUE(listener_privet->Start());
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(Invoke(
+ &record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ SimulatePacketReceive(kSamplePacketAdditionalOnly,
+ sizeof(kSamplePacketAdditionalOnly));
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+}
+
+TEST_F(MDnsTest, TransactionTimeout) {
+ ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
+
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_privet->Start());
+
+ EXPECT_CALL(*this,
+ MockableRecordCallback(MDnsTransaction::RESULT_NO_RESULTS, NULL))
+ .Times(Exactly(1))
+ .WillOnce(InvokeWithoutArgs(this, &MDnsTest::Stop));
+
+ RunFor(base::TimeDelta::FromSeconds(4));
+}
+
+TEST_F(MDnsTest, TransactionMultipleRecords) {
+ ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
+
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE ,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_privet->Start());
+
+ PtrRecordCopyContainer record_privet;
+ PtrRecordCopyContainer record_privet2;
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_RECORD, _))
+ .Times(Exactly(2))
+ .WillOnce(Invoke(&record_privet,
+ &PtrRecordCopyContainer::SaveWithDummyArg))
+ .WillOnce(Invoke(&record_privet2,
+ &PtrRecordCopyContainer::SaveWithDummyArg));
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+ SimulatePacketReceive(kSamplePacket2, sizeof(kSamplePacket2));
+
+ EXPECT_TRUE(record_privet.IsRecordWith("_privet._tcp.local",
+ "hello._privet._tcp.local"));
+
+ EXPECT_TRUE(record_privet2.IsRecordWith("_privet._tcp.local",
+ "zzzzz._privet._tcp.local"));
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_DONE, NULL))
+ .WillOnce(InvokeWithoutArgs(this, &MDnsTest::Stop));
+
+ RunFor(base::TimeDelta::FromSeconds(4));
+}
+
+TEST_F(MDnsTest, TransactionReentrantDelete) {
+ ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
+
+ transaction_ = test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_->Start());
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_NO_RESULTS,
+ NULL))
+ .Times(Exactly(1))
+ .WillOnce(DoAll(InvokeWithoutArgs(this, &MDnsTest::DeleteTransaction),
+ InvokeWithoutArgs(this, &MDnsTest::Stop)));
+
+ RunFor(base::TimeDelta::FromSeconds(4));
+
+ EXPECT_EQ(NULL, transaction_.get());
+}
+
+TEST_F(MDnsTest, TransactionReentrantDeleteFromCache) {
+ StrictMock<MockListenerDelegate> delegate_irrelevant;
+ scoped_ptr<MDnsListener> listener_irrelevant = test_client_->CreateListener(
+ dns_protocol::kTypeA, "codereview.chromium.local",
+ &delegate_irrelevant);
+ ASSERT_TRUE(listener_irrelevant->Start());
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+ transaction_ = test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_RECORD, _))
+ .Times(Exactly(1))
+ .WillOnce(InvokeWithoutArgs(this, &MDnsTest::DeleteTransaction));
+
+ ASSERT_TRUE(transaction_->Start());
+
+ EXPECT_EQ(NULL, transaction_.get());
+}
+
+TEST_F(MDnsTest, TransactionReentrantCacheLookupStart) {
+ ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
+
+ scoped_ptr<MDnsTransaction> transaction1 = test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ scoped_ptr<MDnsTransaction> transaction2 = test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_printer._tcp.local",
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback2,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*this, MockableRecordCallback2(MDnsTransaction::RESULT_RECORD,
+ _))
+ .Times(Exactly(1));
+
+ EXPECT_CALL(*this, MockableRecordCallback(MDnsTransaction::RESULT_RECORD,
+ _))
+ .Times(Exactly(1))
+ .WillOnce(IgnoreResult(InvokeWithoutArgs(transaction2.get(),
+ &MDnsTransaction::Start)));
+
+ ASSERT_TRUE(transaction1->Start());
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+}
+
+TEST_F(MDnsTest, GoodbyePacketNotification) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet);
+ ASSERT_TRUE(listener_privet->Start());
+
+ SimulatePacketReceive(kSamplePacketGoodbye, sizeof(kSamplePacketGoodbye));
+
+ RunFor(base::TimeDelta::FromSeconds(2));
+}
+
+TEST_F(MDnsTest, GoodbyePacketRemoval) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet);
+ ASSERT_TRUE(listener_privet->Start());
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1));
+
+ SimulatePacketReceive(kSamplePacket2, sizeof(kSamplePacket2));
+
+ SimulatePacketReceive(kSamplePacketGoodbye, sizeof(kSamplePacketGoodbye));
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_REMOVED, _))
+ .Times(Exactly(1));
+
+ RunFor(base::TimeDelta::FromSeconds(2));
+}
+
+// In order to reliably test reentrant listener deletes, we create two listeners
+// and have each of them delete both, so we're guaranteed to try and deliver a
+// callback to at least one deleted listener.
+
+TEST_F(MDnsTest, ListenerReentrantDelete) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ listener1_ = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ &delegate_privet);
+
+ listener2_ = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ &delegate_privet);
+
+ ASSERT_TRUE(listener1_->Start());
+
+ ASSERT_TRUE(listener2_->Start());
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(InvokeWithoutArgs(this, &MDnsTest::DeleteBothListeners));
+
+ SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
+
+ EXPECT_EQ(NULL, listener1_.get());
+ EXPECT_EQ(NULL, listener2_.get());
+}
+
+ACTION_P(SaveIPAddress, ip_container) {
+ ::testing::StaticAssertTypeEq<const RecordParsed*, arg1_type>();
+ ::testing::StaticAssertTypeEq<IPAddressNumber*, ip_container_type>();
+
+ *ip_container = arg1->template rdata<ARecordRdata>()->address();
+}
+
+TEST_F(MDnsTest, DoubleRecordDisagreeing) {
+ IPAddressNumber address;
+ StrictMock<MockListenerDelegate> delegate_privet;
+
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypeA, "privet.local", &delegate_privet);
+
+ ASSERT_TRUE(listener_privet->Start());
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .Times(Exactly(1))
+ .WillOnce(SaveIPAddress(&address));
+
+ SimulatePacketReceive(kCorruptedPacketDoubleRecord,
+ sizeof(kCorruptedPacketDoubleRecord));
+
+ EXPECT_EQ("2.3.4.5", IPAddressToString(address));
+}
+
+TEST_F(MDnsTest, NsecWithListener) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypeA, "_privet._tcp.local", &delegate_privet);
+
+ // Test to make sure nsec callback is NOT called for PTR
+ // (which is marked as existing).
+ StrictMock<MockListenerDelegate> delegate_privet2;
+ scoped_ptr<MDnsListener> listener_privet2 = test_client_->CreateListener(
+ dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet2);
+
+ ASSERT_TRUE(listener_privet->Start());
+
+ EXPECT_CALL(delegate_privet,
+ OnNsecRecord("_privet._tcp.local", dns_protocol::kTypeA));
+
+ SimulatePacketReceive(kSamplePacketNsec,
+ sizeof(kSamplePacketNsec));
+}
+
+TEST_F(MDnsTest, NsecWithTransactionFromNetwork) {
+ scoped_ptr<MDnsTransaction> transaction_privet =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypeA, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*socket_factory_, OnSendTo(_))
+ .Times(2);
+
+ ASSERT_TRUE(transaction_privet->Start());
+
+ EXPECT_CALL(*this,
+ MockableRecordCallback(MDnsTransaction::RESULT_NSEC, NULL));
+
+ SimulatePacketReceive(kSamplePacketNsec,
+ sizeof(kSamplePacketNsec));
+}
+
+TEST_F(MDnsTest, NsecWithTransactionFromCache) {
+ // Force mDNS to listen.
+ StrictMock<MockListenerDelegate> delegate_irrelevant;
+ scoped_ptr<MDnsListener> listener_irrelevant =
+ test_client_->CreateListener(dns_protocol::kTypePTR, "_privet._tcp.local",
+ &delegate_irrelevant);
+ listener_irrelevant->Start();
+
+ SimulatePacketReceive(kSamplePacketNsec,
+ sizeof(kSamplePacketNsec));
+
+ EXPECT_CALL(*this,
+ MockableRecordCallback(MDnsTransaction::RESULT_NSEC, NULL));
+
+ scoped_ptr<MDnsTransaction> transaction_privet_a =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypeA, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ ASSERT_TRUE(transaction_privet_a->Start());
+
+ // Test that a PTR transaction does NOT consider the same NSEC record to be a
+ // valid answer to the query
+
+ scoped_ptr<MDnsTransaction> transaction_privet_ptr =
+ test_client_->CreateTransaction(
+ dns_protocol::kTypePTR, "_privet._tcp.local",
+ MDnsTransaction::QUERY_NETWORK |
+ MDnsTransaction::QUERY_CACHE |
+ MDnsTransaction::SINGLE_RESULT,
+ base::Bind(&MDnsTest::MockableRecordCallback,
+ base::Unretained(this)));
+
+ EXPECT_CALL(*socket_factory_, OnSendTo(_))
+ .Times(2);
+
+ ASSERT_TRUE(transaction_privet_ptr->Start());
+}
+
+TEST_F(MDnsTest, NsecConflictRemoval) {
+ StrictMock<MockListenerDelegate> delegate_privet;
+ scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+ dns_protocol::kTypeA, "_privet._tcp.local", &delegate_privet);
+
+ ASSERT_TRUE(listener_privet->Start());
+
+ const RecordParsed* record1;
+ const RecordParsed* record2;
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+ .WillOnce(SaveArg<1>(&record1));
+
+ SimulatePacketReceive(kSamplePacketAPrivet,
+ sizeof(kSamplePacketAPrivet));
+
+ EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_REMOVED, _))
+ .WillOnce(SaveArg<1>(&record2));
+
+ EXPECT_CALL(delegate_privet,
+ OnNsecRecord("_privet._tcp.local", dns_protocol::kTypeA));
+
+ SimulatePacketReceive(kSamplePacketNsec,
+ sizeof(kSamplePacketNsec));
+
+ EXPECT_EQ(record1, record2);
+}
+
+
+// Note: These tests assume that the ipv4 socket will always be created first.
+// This is a simplifying assumption based on the way the code works now.
+
+class SimpleMockSocketFactory
+ : public MDnsConnection::SocketFactory {
+ public:
+ SimpleMockSocketFactory() {
+ }
+ virtual ~SimpleMockSocketFactory() {
+ }
+
+ virtual scoped_ptr<DatagramServerSocket> CreateSocket() OVERRIDE {
+ scoped_ptr<MockMDnsDatagramServerSocket> socket(
+ new StrictMock<MockMDnsDatagramServerSocket>);
+ sockets_.push(socket.get());
+ return socket.PassAs<DatagramServerSocket>();
+ }
+
+ MockMDnsDatagramServerSocket* PopFirstSocket() {
+ MockMDnsDatagramServerSocket* socket = sockets_.front();
+ sockets_.pop();
+ return socket;
+ }
+
+ size_t num_sockets() {
+ return sockets_.size();
+ }
+
+ private:
+ std::queue<MockMDnsDatagramServerSocket*> sockets_;
+};
+
+class MockMDnsConnectionDelegate : public MDnsConnection::Delegate {
+ public:
+ virtual void HandlePacket(DnsResponse* response, int size) {
+ HandlePacketInternal(std::string(response->io_buffer()->data(), size));
+ }
+
+ MOCK_METHOD1(HandlePacketInternal, void(std::string packet));
+
+ MOCK_METHOD1(OnConnectionError, void(int error));
+};
+
+class MDnsConnectionTest : public ::testing::Test {
+ public:
+ MDnsConnectionTest() : connection_(&factory_, &delegate_) {
+ }
+
+ protected:
+ // Follow successful connection initialization.
+ virtual void SetUp() OVERRIDE {
+ ASSERT_EQ(2u, factory_.num_sockets());
+
+ socket_ipv4_ = factory_.PopFirstSocket();
+ socket_ipv6_ = factory_.PopFirstSocket();
+ }
+
+ bool InitConnection() {
+ EXPECT_CALL(*socket_ipv4_, AllowAddressReuse());
+ EXPECT_CALL(*socket_ipv6_, AllowAddressReuse());
+
+ EXPECT_CALL(*socket_ipv4_, SetMulticastLoopbackMode(false));
+ EXPECT_CALL(*socket_ipv6_, SetMulticastLoopbackMode(false));
+
+ EXPECT_CALL(*socket_ipv4_, ListenInternal("0.0.0.0:5353"))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*socket_ipv6_, ListenInternal("[::]:5353"))
+ .WillOnce(Return(OK));
+
+ EXPECT_CALL(*socket_ipv4_, JoinGroupInternal("224.0.0.251"))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*socket_ipv6_, JoinGroupInternal("ff02::fb"))
+ .WillOnce(Return(OK));
+
+ return connection_.Init() == OK;
+ }
+
+ StrictMock<MockMDnsConnectionDelegate> delegate_;
+
+ MockMDnsDatagramServerSocket* socket_ipv4_;
+ MockMDnsDatagramServerSocket* socket_ipv6_;
+ SimpleMockSocketFactory factory_;
+ MDnsConnection connection_;
+ TestCompletionCallback callback_;
+};
+
+TEST_F(MDnsConnectionTest, ReceiveSynchronous) {
+ std::string sample_packet = MakeString(kSamplePacket1,
+ sizeof(kSamplePacket1));
+
+ socket_ipv6_->SetResponsePacket(sample_packet);
+ EXPECT_CALL(*socket_ipv4_, RecvFrom(_, _, _, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*socket_ipv6_, RecvFrom(_, _, _, _))
+ .WillOnce(
+ Invoke(socket_ipv6_, &MockMDnsDatagramServerSocket::HandleRecvNow))
+ .WillOnce(Return(ERR_IO_PENDING));
+
+ EXPECT_CALL(delegate_, HandlePacketInternal(sample_packet));
+
+ ASSERT_TRUE(InitConnection());
+}
+
+TEST_F(MDnsConnectionTest, ReceiveAsynchronous) {
+ std::string sample_packet = MakeString(kSamplePacket1,
+ sizeof(kSamplePacket1));
+ socket_ipv6_->SetResponsePacket(sample_packet);
+ EXPECT_CALL(*socket_ipv4_, RecvFrom(_, _, _, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*socket_ipv6_, RecvFrom(_, _, _, _))
+ .WillOnce(
+ Invoke(socket_ipv6_, &MockMDnsDatagramServerSocket::HandleRecvLater))
+ .WillOnce(Return(ERR_IO_PENDING));
+
+ ASSERT_TRUE(InitConnection());
+
+ EXPECT_CALL(delegate_, HandlePacketInternal(sample_packet));
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(MDnsConnectionTest, Send) {
+ std::string sample_packet = MakeString(kSamplePacket1,
+ sizeof(kSamplePacket1));
+
+ scoped_refptr<IOBufferWithSize> buf(
+ new IOBufferWithSize(sizeof kSamplePacket1));
+ memcpy(buf->data(), kSamplePacket1, sizeof(kSamplePacket1));
+
+ EXPECT_CALL(*socket_ipv4_, RecvFrom(_, _, _, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*socket_ipv6_, RecvFrom(_, _, _, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+
+ ASSERT_TRUE(InitConnection());
+
+ EXPECT_CALL(*socket_ipv4_,
+ SendToInternal(sample_packet, "224.0.0.251:5353", _));
+ EXPECT_CALL(*socket_ipv6_,
+ SendToInternal(sample_packet, "[ff02::fb]:5353", _));
+
+ connection_.Send(buf, buf->size());
+}
+
+TEST_F(MDnsConnectionTest, Error) {
+ CompletionCallback callback;
+
+ EXPECT_CALL(*socket_ipv4_, RecvFrom(_, _, _, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*socket_ipv6_, RecvFrom(_, _, _, _))
+ .WillOnce(DoAll(SaveArg<3>(&callback), Return(ERR_IO_PENDING)));
+
+ ASSERT_TRUE(InitConnection());
+
+ EXPECT_CALL(delegate_, OnConnectionError(ERR_SOCKET_NOT_CONNECTED));
+ callback.Run(ERR_SOCKET_NOT_CONNECTED);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/dns/mock_host_resolver.cc b/chromium/net/dns/mock_host_resolver.cc
new file mode 100644
index 00000000000..b3d1489c9d7
--- /dev/null
+++ b/chromium/net/dns/mock_host_resolver.cc
@@ -0,0 +1,408 @@
+// Copyright (c) 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.
+
+#include "net/dns/mock_host_resolver.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/host_cache.h"
+
+#if defined(OS_WIN)
+#include "net/base/winsock_init.h"
+#endif
+
+namespace net {
+
+namespace {
+
+// Cache size for the MockCachingHostResolver.
+const unsigned kMaxCacheEntries = 100;
+// TTL for the successful resolutions. Failures are not cached.
+const unsigned kCacheEntryTTLSeconds = 60;
+
+} // namespace
+
+int ParseAddressList(const std::string& host_list,
+ const std::string& canonical_name,
+ AddressList* addrlist) {
+ *addrlist = AddressList();
+ std::vector<std::string> addresses;
+ base::SplitString(host_list, ',', &addresses);
+ addrlist->set_canonical_name(canonical_name);
+ for (size_t index = 0; index < addresses.size(); ++index) {
+ IPAddressNumber ip_number;
+ if (!ParseIPLiteralToNumber(addresses[index], &ip_number)) {
+ LOG(WARNING) << "Not a supported IP literal: " << addresses[index];
+ return ERR_UNEXPECTED;
+ }
+ addrlist->push_back(IPEndPoint(ip_number, -1));
+ }
+ return OK;
+}
+
+struct MockHostResolverBase::Request {
+ Request(const RequestInfo& req_info,
+ AddressList* addr,
+ const CompletionCallback& cb)
+ : info(req_info), addresses(addr), callback(cb) {}
+ RequestInfo info;
+ AddressList* addresses;
+ CompletionCallback callback;
+};
+
+MockHostResolverBase::~MockHostResolverBase() {
+ STLDeleteValues(&requests_);
+}
+
+int MockHostResolverBase::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* handle,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ num_resolve_++;
+ size_t id = next_request_id_++;
+ int rv = ResolveFromIPLiteralOrCache(info, addresses);
+ if (rv != ERR_DNS_CACHE_MISS) {
+ return rv;
+ }
+ if (synchronous_mode_) {
+ return ResolveProc(id, info, addresses);
+ }
+ // Store the request for asynchronous resolution
+ Request* req = new Request(info, addresses, callback);
+ requests_[id] = req;
+ if (handle)
+ *handle = reinterpret_cast<RequestHandle>(id);
+
+ if (!ondemand_mode_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockHostResolverBase::ResolveNow, AsWeakPtr(), id));
+ }
+
+ return ERR_IO_PENDING;
+}
+
+int MockHostResolverBase::ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) {
+ num_resolve_from_cache_++;
+ DCHECK(CalledOnValidThread());
+ next_request_id_++;
+ int rv = ResolveFromIPLiteralOrCache(info, addresses);
+ return rv;
+}
+
+void MockHostResolverBase::CancelRequest(RequestHandle handle) {
+ DCHECK(CalledOnValidThread());
+ size_t id = reinterpret_cast<size_t>(handle);
+ RequestMap::iterator it = requests_.find(id);
+ if (it != requests_.end()) {
+ scoped_ptr<Request> req(it->second);
+ requests_.erase(it);
+ } else {
+ NOTREACHED() << "CancelRequest must NOT be called after request is "
+ "complete or canceled.";
+ }
+}
+
+HostCache* MockHostResolverBase::GetHostCache() {
+ return cache_.get();
+}
+
+void MockHostResolverBase::ResolveAllPending() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(ondemand_mode_);
+ for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockHostResolverBase::ResolveNow, AsWeakPtr(), i->first));
+ }
+}
+
+// start id from 1 to distinguish from NULL RequestHandle
+MockHostResolverBase::MockHostResolverBase(bool use_caching)
+ : synchronous_mode_(false),
+ ondemand_mode_(false),
+ next_request_id_(1),
+ num_resolve_(0),
+ num_resolve_from_cache_(0) {
+ rules_ = CreateCatchAllHostResolverProc();
+
+ if (use_caching) {
+ cache_.reset(new HostCache(kMaxCacheEntries));
+ }
+}
+
+int MockHostResolverBase::ResolveFromIPLiteralOrCache(const RequestInfo& info,
+ AddressList* addresses) {
+ IPAddressNumber ip;
+ if (ParseIPLiteralToNumber(info.hostname(), &ip)) {
+ *addresses = AddressList::CreateFromIPAddress(ip, info.port());
+ if (info.host_resolver_flags() & HOST_RESOLVER_CANONNAME)
+ addresses->SetDefaultCanonicalName();
+ return OK;
+ }
+ int rv = ERR_DNS_CACHE_MISS;
+ if (cache_.get() && info.allow_cached_response()) {
+ HostCache::Key key(info.hostname(),
+ info.address_family(),
+ info.host_resolver_flags());
+ const HostCache::Entry* entry = cache_->Lookup(key, base::TimeTicks::Now());
+ if (entry) {
+ rv = entry->error;
+ if (rv == OK)
+ *addresses = AddressList::CopyWithPort(entry->addrlist, info.port());
+ }
+ }
+ return rv;
+}
+
+int MockHostResolverBase::ResolveProc(size_t id,
+ const RequestInfo& info,
+ AddressList* addresses) {
+ AddressList addr;
+ int rv = rules_->Resolve(info.hostname(),
+ info.address_family(),
+ info.host_resolver_flags(),
+ &addr,
+ NULL);
+ if (cache_.get()) {
+ HostCache::Key key(info.hostname(),
+ info.address_family(),
+ info.host_resolver_flags());
+ // Storing a failure with TTL 0 so that it overwrites previous value.
+ base::TimeDelta ttl;
+ if (rv == OK)
+ ttl = base::TimeDelta::FromSeconds(kCacheEntryTTLSeconds);
+ cache_->Set(key, HostCache::Entry(rv, addr), base::TimeTicks::Now(), ttl);
+ }
+ if (rv == OK)
+ *addresses = AddressList::CopyWithPort(addr, info.port());
+ return rv;
+}
+
+void MockHostResolverBase::ResolveNow(size_t id) {
+ RequestMap::iterator it = requests_.find(id);
+ if (it == requests_.end())
+ return; // was canceled
+
+ scoped_ptr<Request> req(it->second);
+ requests_.erase(it);
+ int rv = ResolveProc(id, req->info, req->addresses);
+ if (!req->callback.is_null())
+ req->callback.Run(rv);
+}
+
+//-----------------------------------------------------------------------------
+
+RuleBasedHostResolverProc::RuleBasedHostResolverProc(HostResolverProc* previous)
+ : HostResolverProc(previous) {
+}
+
+void RuleBasedHostResolverProc::AddRule(const std::string& host_pattern,
+ const std::string& replacement) {
+ AddRuleForAddressFamily(host_pattern, ADDRESS_FAMILY_UNSPECIFIED,
+ replacement);
+}
+
+void RuleBasedHostResolverProc::AddRuleForAddressFamily(
+ const std::string& host_pattern,
+ AddressFamily address_family,
+ const std::string& replacement) {
+ DCHECK(!replacement.empty());
+ HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ Rule rule(Rule::kResolverTypeSystem,
+ host_pattern,
+ address_family,
+ flags,
+ replacement,
+ std::string(),
+ 0);
+ rules_.push_back(rule);
+}
+
+void RuleBasedHostResolverProc::AddIPLiteralRule(
+ const std::string& host_pattern,
+ const std::string& ip_literal,
+ const std::string& canonical_name) {
+ // Literals are always resolved to themselves by HostResolverImpl,
+ // consequently we do not support remapping them.
+ IPAddressNumber ip_number;
+ DCHECK(!ParseIPLiteralToNumber(host_pattern, &ip_number));
+ HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ if (!canonical_name.empty())
+ flags |= HOST_RESOLVER_CANONNAME;
+ Rule rule(Rule::kResolverTypeIPLiteral, host_pattern,
+ ADDRESS_FAMILY_UNSPECIFIED, flags, ip_literal, canonical_name,
+ 0);
+ rules_.push_back(rule);
+}
+
+void RuleBasedHostResolverProc::AddRuleWithLatency(
+ const std::string& host_pattern,
+ const std::string& replacement,
+ int latency_ms) {
+ DCHECK(!replacement.empty());
+ HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ Rule rule(Rule::kResolverTypeSystem,
+ host_pattern,
+ ADDRESS_FAMILY_UNSPECIFIED,
+ flags,
+ replacement,
+ std::string(),
+ latency_ms);
+ rules_.push_back(rule);
+}
+
+void RuleBasedHostResolverProc::AllowDirectLookup(
+ const std::string& host_pattern) {
+ HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ Rule rule(Rule::kResolverTypeSystem,
+ host_pattern,
+ ADDRESS_FAMILY_UNSPECIFIED,
+ flags,
+ std::string(),
+ std::string(),
+ 0);
+ rules_.push_back(rule);
+}
+
+void RuleBasedHostResolverProc::AddSimulatedFailure(
+ const std::string& host_pattern) {
+ HostResolverFlags flags = HOST_RESOLVER_LOOPBACK_ONLY |
+ HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
+ Rule rule(Rule::kResolverTypeFail,
+ host_pattern,
+ ADDRESS_FAMILY_UNSPECIFIED,
+ flags,
+ std::string(),
+ std::string(),
+ 0);
+ rules_.push_back(rule);
+}
+
+void RuleBasedHostResolverProc::ClearRules() {
+ rules_.clear();
+}
+
+int RuleBasedHostResolverProc::Resolve(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) {
+ RuleList::iterator r;
+ for (r = rules_.begin(); r != rules_.end(); ++r) {
+ bool matches_address_family =
+ r->address_family == ADDRESS_FAMILY_UNSPECIFIED ||
+ r->address_family == address_family;
+ // Flags match if all of the bitflags in host_resolver_flags are enabled
+ // in the rule's host_resolver_flags. However, the rule may have additional
+ // flags specified, in which case the flags should still be considered a
+ // match.
+ bool matches_flags = (r->host_resolver_flags & host_resolver_flags) ==
+ host_resolver_flags;
+ if (matches_flags && matches_address_family &&
+ MatchPattern(host, r->host_pattern)) {
+ if (r->latency_ms != 0) {
+ base::PlatformThread::Sleep(
+ base::TimeDelta::FromMilliseconds(r->latency_ms));
+ }
+
+ // Remap to a new host.
+ const std::string& effective_host =
+ r->replacement.empty() ? host : r->replacement;
+
+ // Apply the resolving function to the remapped hostname.
+ switch (r->resolver_type) {
+ case Rule::kResolverTypeFail:
+ return ERR_NAME_NOT_RESOLVED;
+ case Rule::kResolverTypeSystem:
+#if defined(OS_WIN)
+ net::EnsureWinsockInit();
+#endif
+ return SystemHostResolverCall(effective_host,
+ address_family,
+ host_resolver_flags,
+ addrlist, os_error);
+ case Rule::kResolverTypeIPLiteral:
+ return ParseAddressList(effective_host,
+ r->canonical_name,
+ addrlist);
+ default:
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ }
+ }
+ return ResolveUsingPrevious(host, address_family,
+ host_resolver_flags, addrlist, os_error);
+}
+
+RuleBasedHostResolverProc::~RuleBasedHostResolverProc() {
+}
+
+RuleBasedHostResolverProc* CreateCatchAllHostResolverProc() {
+ RuleBasedHostResolverProc* catchall = new RuleBasedHostResolverProc(NULL);
+ catchall->AddIPLiteralRule("*", "127.0.0.1", "localhost");
+
+ // Next add a rules-based layer the use controls.
+ return new RuleBasedHostResolverProc(catchall);
+}
+
+//-----------------------------------------------------------------------------
+
+int HangingHostResolver::Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) {
+ return ERR_IO_PENDING;
+}
+
+int HangingHostResolver::ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) {
+ return ERR_DNS_CACHE_MISS;
+}
+
+//-----------------------------------------------------------------------------
+
+ScopedDefaultHostResolverProc::ScopedDefaultHostResolverProc() {}
+
+ScopedDefaultHostResolverProc::ScopedDefaultHostResolverProc(
+ HostResolverProc* proc) {
+ Init(proc);
+}
+
+ScopedDefaultHostResolverProc::~ScopedDefaultHostResolverProc() {
+ HostResolverProc* old_proc =
+ HostResolverProc::SetDefault(previous_proc_.get());
+ // The lifetimes of multiple instances must be nested.
+ CHECK_EQ(old_proc, current_proc_);
+}
+
+void ScopedDefaultHostResolverProc::Init(HostResolverProc* proc) {
+ current_proc_ = proc;
+ previous_proc_ = HostResolverProc::SetDefault(current_proc_.get());
+ current_proc_->SetLastProc(previous_proc_.get());
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mock_host_resolver.h b/chromium/net/dns/mock_host_resolver.h
new file mode 100644
index 00000000000..282521cc3b9
--- /dev/null
+++ b/chromium/net/dns/mock_host_resolver.h
@@ -0,0 +1,284 @@
+// Copyright (c) 2011 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_DNS_MOCK_HOST_RESOLVER_H_
+#define NET_DNS_MOCK_HOST_RESOLVER_H_
+
+#include <list>
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/host_resolver_proc.h"
+
+namespace net {
+
+class HostCache;
+class RuleBasedHostResolverProc;
+
+// Fills |*addrlist| with a socket address for |host_list| which should be a
+// comma-separated list of IPv4 or IPv6 literal(s) without enclosing brackets.
+// If |canonical_name| is non-empty it is used as the DNS canonical name for
+// the host. Returns OK on success, ERR_UNEXPECTED otherwise.
+int ParseAddressList(const std::string& host_list,
+ const std::string& canonical_name,
+ AddressList* addrlist);
+
+// In most cases, it is important that unit tests avoid relying on making actual
+// DNS queries since the resulting tests can be flaky, especially if the network
+// is unreliable for some reason. To simplify writing tests that avoid making
+// actual DNS queries, pass a MockHostResolver as the HostResolver dependency.
+// The socket addresses returned can be configured using the
+// RuleBasedHostResolverProc:
+//
+// host_resolver->rules()->AddRule("foo.com", "1.2.3.4");
+// host_resolver->rules()->AddRule("bar.com", "2.3.4.5");
+//
+// The above rules define a static mapping from hostnames to IP address
+// literals. The first parameter to AddRule specifies a host pattern to match
+// against, and the second parameter indicates what value should be used to
+// replace the given hostname. So, the following is also supported:
+//
+// host_mapper->AddRule("*.com", "127.0.0.1");
+//
+// Replacement doesn't have to be string representing an IP address. It can
+// re-map one hostname to another as well.
+//
+// By default, MockHostResolvers include a single rule that maps all hosts to
+// 127.0.0.1.
+
+// Base class shared by MockHostResolver and MockCachingHostResolver.
+class MockHostResolverBase : public HostResolver,
+ public base::SupportsWeakPtr<MockHostResolverBase>,
+ public base::NonThreadSafe {
+ public:
+ virtual ~MockHostResolverBase();
+
+ RuleBasedHostResolverProc* rules() { return rules_.get(); }
+ void set_rules(RuleBasedHostResolverProc* rules) { rules_ = rules; }
+
+ // Controls whether resolutions complete synchronously or asynchronously.
+ void set_synchronous_mode(bool is_synchronous) {
+ synchronous_mode_ = is_synchronous;
+ }
+
+ // Asynchronous requests are automatically resolved by default.
+ // If set_ondemand_mode() is set then Resolve() returns IO_PENDING and
+ // ResolveAllPending() must be explicitly invoked to resolve all requests
+ // that are pending.
+ void set_ondemand_mode(bool is_ondemand) {
+ ondemand_mode_ = is_ondemand;
+ }
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle req) OVERRIDE;
+ virtual HostCache* GetHostCache() OVERRIDE;
+
+ // Resolves all pending requests. It is only valid to invoke this if
+ // set_ondemand_mode was set before. The requests are resolved asynchronously,
+ // after this call returns.
+ void ResolveAllPending();
+
+ // Returns true if there are pending requests that can be resolved by invoking
+ // ResolveAllPending().
+ bool has_pending_requests() const { return !requests_.empty(); }
+
+ // The number of times that Resolve() has been called.
+ size_t num_resolve() const {
+ return num_resolve_;
+ }
+
+ // The number of times that ResolveFromCache() has been called.
+ size_t num_resolve_from_cache() const {
+ return num_resolve_from_cache_;
+ }
+
+ protected:
+ explicit MockHostResolverBase(bool use_caching);
+
+ private:
+ struct Request;
+ typedef std::map<size_t, Request*> RequestMap;
+
+ // Resolve as IP or from |cache_| return cached error or
+ // DNS_CACHE_MISS if failed.
+ int ResolveFromIPLiteralOrCache(const RequestInfo& info,
+ AddressList* addresses);
+ // Resolve via |proc_|.
+ int ResolveProc(size_t id, const RequestInfo& info, AddressList* addresses);
+ // Resolve request stored in |requests_|. Pass rv to callback.
+ void ResolveNow(size_t id);
+
+ bool synchronous_mode_;
+ bool ondemand_mode_;
+ scoped_refptr<RuleBasedHostResolverProc> rules_;
+ scoped_ptr<HostCache> cache_;
+ RequestMap requests_;
+ size_t next_request_id_;
+
+ size_t num_resolve_;
+ size_t num_resolve_from_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHostResolverBase);
+};
+
+class MockHostResolver : public MockHostResolverBase {
+ public:
+ MockHostResolver() : MockHostResolverBase(false /*use_caching*/) {}
+ virtual ~MockHostResolver() {}
+};
+
+// Same as MockHostResolver, except internally it uses a host-cache.
+//
+// Note that tests are advised to use MockHostResolver instead, since it is
+// more predictable. (MockHostResolver also can be put into synchronous
+// operation mode in case that is what you needed from the caching version).
+class MockCachingHostResolver : public MockHostResolverBase {
+ public:
+ MockCachingHostResolver() : MockHostResolverBase(true /*use_caching*/) {}
+ virtual ~MockCachingHostResolver() {}
+};
+
+// RuleBasedHostResolverProc applies a set of rules to map a host string to
+// a replacement host string. It then uses the system host resolver to return
+// a socket address. Generally the replacement should be an IPv4 literal so
+// there is no network dependency.
+class RuleBasedHostResolverProc : public HostResolverProc {
+ public:
+ explicit RuleBasedHostResolverProc(HostResolverProc* previous);
+
+ // Any hostname matching the given pattern will be replaced with the given
+ // replacement value. Usually, replacement should be an IP address literal.
+ void AddRule(const std::string& host_pattern,
+ const std::string& replacement);
+
+ // Same as AddRule(), but further restricts to |address_family|.
+ void AddRuleForAddressFamily(const std::string& host_pattern,
+ AddressFamily address_family,
+ const std::string& replacement);
+
+ // Same as AddRule(), but the replacement is expected to be an IPv4 or IPv6
+ // literal. This can be used in place of AddRule() to bypass the system's
+ // host resolver (the address list will be constructed manually).
+ // If |canonical_name| is non-empty, it is copied to the resulting AddressList
+ // but does not impact DNS resolution.
+ // |ip_literal| can be a single IP address like "192.168.1.1" or a comma
+ // separated list of IP addresses, like "::1,192:168.1.2".
+ void AddIPLiteralRule(const std::string& host_pattern,
+ const std::string& ip_literal,
+ const std::string& canonical_name);
+
+ void AddRuleWithLatency(const std::string& host_pattern,
+ const std::string& replacement,
+ int latency_ms);
+
+ // Make sure that |host| will not be re-mapped or even processed by underlying
+ // host resolver procedures. It can also be a pattern.
+ void AllowDirectLookup(const std::string& host);
+
+ // Simulate a lookup failure for |host| (it also can be a pattern).
+ void AddSimulatedFailure(const std::string& host);
+
+ // Deletes all the rules that have been added.
+ void ClearRules();
+
+ // HostResolverProc methods:
+ virtual int Resolve(const std::string& host,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ AddressList* addrlist,
+ int* os_error) OVERRIDE;
+
+ private:
+ struct Rule {
+ enum ResolverType {
+ kResolverTypeFail,
+ kResolverTypeSystem,
+ kResolverTypeIPLiteral,
+ };
+
+ ResolverType resolver_type;
+ std::string host_pattern;
+ AddressFamily address_family;
+ HostResolverFlags host_resolver_flags;
+ std::string replacement;
+ std::string canonical_name;
+ int latency_ms; // In milliseconds.
+
+ Rule(ResolverType resolver_type,
+ const std::string& host_pattern,
+ AddressFamily address_family,
+ HostResolverFlags host_resolver_flags,
+ const std::string& replacement,
+ const std::string& canonical_name,
+ int latency_ms)
+ : resolver_type(resolver_type),
+ host_pattern(host_pattern),
+ address_family(address_family),
+ host_resolver_flags(host_resolver_flags),
+ replacement(replacement),
+ canonical_name(canonical_name),
+ latency_ms(latency_ms) {}
+ };
+
+ typedef std::list<Rule> RuleList;
+
+ virtual ~RuleBasedHostResolverProc();
+
+ RuleList rules_;
+};
+
+// Create rules that map all requests to localhost.
+RuleBasedHostResolverProc* CreateCatchAllHostResolverProc();
+
+// HangingHostResolver never completes its |Resolve| request.
+class HangingHostResolver : public HostResolver {
+ public:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle req) OVERRIDE {}
+};
+
+// This class sets the default HostResolverProc for a particular scope. The
+// chain of resolver procs starting at |proc| is placed in front of any existing
+// default resolver proc(s). This means that if multiple
+// ScopedDefaultHostResolverProcs are declared, then resolving will start with
+// the procs given to the last-allocated one, then fall back to the procs given
+// to the previously-allocated one, and so forth.
+//
+// NOTE: Only use this as a catch-all safety net. Individual tests should use
+// MockHostResolver.
+class ScopedDefaultHostResolverProc {
+ public:
+ ScopedDefaultHostResolverProc();
+ explicit ScopedDefaultHostResolverProc(HostResolverProc* proc);
+
+ ~ScopedDefaultHostResolverProc();
+
+ void Init(HostResolverProc* proc);
+
+ private:
+ scoped_refptr<HostResolverProc> current_proc_;
+ scoped_refptr<HostResolverProc> previous_proc_;
+};
+
+} // namespace net
+
+#endif // NET_DNS_MOCK_HOST_RESOLVER_H_
diff --git a/chromium/net/dns/mock_mdns_socket_factory.cc b/chromium/net/dns/mock_mdns_socket_factory.cc
new file mode 100644
index 00000000000..8c08c150cd1
--- /dev/null
+++ b/chromium/net/dns/mock_mdns_socket_factory.cc
@@ -0,0 +1,115 @@
+// Copyright 2013 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 <algorithm>
+
+#include "net/base/net_errors.h"
+#include "net/dns/mock_mdns_socket_factory.h"
+
+using testing::_;
+using testing::Invoke;
+
+namespace net {
+
+MockMDnsDatagramServerSocket::MockMDnsDatagramServerSocket() {
+}
+
+MockMDnsDatagramServerSocket::~MockMDnsDatagramServerSocket() {
+}
+
+int MockMDnsDatagramServerSocket::SendTo(IOBuffer* buf, int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) {
+ return SendToInternal(std::string(buf->data(), buf_len), address.ToString(),
+ callback);
+}
+
+int MockMDnsDatagramServerSocket::Listen(const IPEndPoint& address) {
+ return ListenInternal(address.ToString());
+}
+
+int MockMDnsDatagramServerSocket::JoinGroup(
+ const IPAddressNumber& group_address) const {
+ return JoinGroupInternal(IPAddressToString(group_address));
+}
+
+int MockMDnsDatagramServerSocket::LeaveGroup(
+ const IPAddressNumber& group_address) const {
+ return LeaveGroupInternal(IPAddressToString(group_address));
+}
+
+void MockMDnsDatagramServerSocket::SetResponsePacket(
+ std::string response_packet) {
+ response_packet_ = response_packet;
+}
+
+int MockMDnsDatagramServerSocket::HandleRecvNow(
+ IOBuffer* buffer, int size, IPEndPoint* address,
+ const CompletionCallback& callback) {
+ int size_returned =
+ std::min(response_packet_.size(), static_cast<size_t>(size));
+ memcpy(buffer->data(), response_packet_.data(), size_returned);
+ return size_returned;
+}
+
+int MockMDnsDatagramServerSocket::HandleRecvLater(
+ IOBuffer* buffer, int size, IPEndPoint* address,
+ const CompletionCallback& callback) {
+ int rv = HandleRecvNow(buffer, size, address, callback);
+ base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+}
+
+MockMDnsSocketFactory::MockMDnsSocketFactory() {
+}
+
+MockMDnsSocketFactory::~MockMDnsSocketFactory() {
+}
+
+scoped_ptr<DatagramServerSocket> MockMDnsSocketFactory::CreateSocket() {
+ scoped_ptr<MockMDnsDatagramServerSocket> new_socket(
+ new testing:: NiceMock<MockMDnsDatagramServerSocket>);
+
+ ON_CALL(*new_socket, SendToInternal(_, _, _))
+ .WillByDefault(Invoke(
+ this,
+ &MockMDnsSocketFactory::SendToInternal));
+
+ ON_CALL(*new_socket, RecvFrom(_, _, _, _))
+ .WillByDefault(Invoke(
+ this,
+ &MockMDnsSocketFactory::RecvFromInternal));
+
+ return new_socket.PassAs<DatagramServerSocket>();
+}
+
+void MockMDnsSocketFactory::SimulateReceive(const uint8* packet, int size) {
+ DCHECK(recv_buffer_size_ >= size);
+ DCHECK(recv_buffer_.get());
+ DCHECK(!recv_callback_.is_null());
+
+ memcpy(recv_buffer_->data(), packet, size);
+ CompletionCallback recv_callback = recv_callback_;
+ recv_callback_.Reset();
+ recv_callback.Run(size);
+}
+
+int MockMDnsSocketFactory::RecvFromInternal(
+ IOBuffer* buffer, int size,
+ IPEndPoint* address,
+ const CompletionCallback& callback) {
+ recv_buffer_ = buffer;
+ recv_buffer_size_ = size;
+ recv_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int MockMDnsSocketFactory::SendToInternal(
+ const std::string& packet, const std::string& address,
+ const CompletionCallback& callback) {
+ OnSendTo(packet);
+ return packet.size();
+}
+
+} // namespace net
diff --git a/chromium/net/dns/mock_mdns_socket_factory.h b/chromium/net/dns/mock_mdns_socket_factory.h
new file mode 100644
index 00000000000..f60b08c591b
--- /dev/null
+++ b/chromium/net/dns/mock_mdns_socket_factory.h
@@ -0,0 +1,101 @@
+// Copyright 2013 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_DNS_MOCK_MDNS_SOCKET_FACTORY_H_
+#define NET_DNS_MOCK_MDNS_SOCKET_FACTORY_H_
+
+#include <string>
+
+#include "net/dns/mdns_client_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+
+class MockMDnsDatagramServerSocket : public DatagramServerSocket {
+ public:
+ MockMDnsDatagramServerSocket();
+ ~MockMDnsDatagramServerSocket();
+
+ // DatagramServerSocket implementation:
+ int Listen(const IPEndPoint& address);
+
+ MOCK_METHOD1(ListenInternal, int(const std::string& address));
+
+ MOCK_METHOD4(RecvFrom, int(IOBuffer* buffer, int size,
+ IPEndPoint* address,
+ const CompletionCallback& callback));
+
+ int SendTo(IOBuffer* buf, int buf_len, const IPEndPoint& address,
+ const CompletionCallback& callback);
+
+ MOCK_METHOD3(SendToInternal, int(const std::string& packet,
+ const std::string address,
+ const CompletionCallback& callback));
+
+ MOCK_METHOD1(SetReceiveBufferSize, bool(int32 size));
+ MOCK_METHOD1(SetSendBufferSize, bool(int32 size));
+
+ MOCK_METHOD0(Close, void());
+
+ MOCK_CONST_METHOD1(GetPeerAddress, int(IPEndPoint* address));
+ MOCK_CONST_METHOD1(GetLocalAddress, int(IPEndPoint* address));
+ MOCK_CONST_METHOD0(NetLog, const BoundNetLog&());
+
+ MOCK_METHOD0(AllowAddressReuse, void());
+ MOCK_METHOD0(AllowBroadcast, void());
+
+ int JoinGroup(const IPAddressNumber& group_address) const;
+
+ MOCK_CONST_METHOD1(JoinGroupInternal, int(const std::string& group));
+
+ int LeaveGroup(const IPAddressNumber& group_address) const;
+
+ MOCK_CONST_METHOD1(LeaveGroupInternal, int(const std::string& group));
+
+ MOCK_METHOD1(SetMulticastTimeToLive, int(int ttl));
+
+ MOCK_METHOD1(SetMulticastLoopbackMode, int(bool loopback));
+
+ void SetResponsePacket(std::string response_packet);
+
+ int HandleRecvNow(IOBuffer* buffer, int size, IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ int HandleRecvLater(IOBuffer* buffer, int size, IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ private:
+ std::string response_packet_;
+};
+
+class MockMDnsSocketFactory : public MDnsConnection::SocketFactory {
+ public:
+ MockMDnsSocketFactory();
+
+ virtual ~MockMDnsSocketFactory();
+
+ virtual scoped_ptr<DatagramServerSocket> CreateSocket() OVERRIDE;
+
+ void SimulateReceive(const uint8* packet, int size);
+
+ MOCK_METHOD1(OnSendTo, void(const std::string&));
+
+ private:
+ int SendToInternal(const std::string& packet, const std::string& address,
+ const CompletionCallback& callback);
+
+ // The latest receive callback is always saved, since the MDnsConnection
+ // does not care which socket a packet is received on.
+ int RecvFromInternal(IOBuffer* buffer, int size,
+ IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ scoped_refptr<IOBuffer> recv_buffer_;
+ int recv_buffer_size_;
+ CompletionCallback recv_callback_;
+};
+
+} // namespace net
+
+#endif // NET_DNS_MOCK_MDNS_SOCKET_FACTORY_H_
diff --git a/chromium/net/dns/notify_watcher_mac.cc b/chromium/net/dns/notify_watcher_mac.cc
new file mode 100644
index 00000000000..286f18bb7bb
--- /dev/null
+++ b/chromium/net/dns/notify_watcher_mac.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 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.
+
+#include "net/dns/notify_watcher_mac.h"
+
+#include <notify.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+
+namespace net {
+
+NotifyWatcherMac::NotifyWatcherMac() : notify_fd_(-1), notify_token_(-1) {}
+
+NotifyWatcherMac::~NotifyWatcherMac() {
+ Cancel();
+}
+
+bool NotifyWatcherMac::Watch(const char* key, const CallbackType& callback) {
+ DCHECK(key);
+ DCHECK(!callback.is_null());
+ Cancel();
+ uint32_t status = notify_register_file_descriptor(
+ key, &notify_fd_, 0, &notify_token_);
+ if (status != NOTIFY_STATUS_OK)
+ return false;
+ DCHECK_GE(notify_fd_, 0);
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ notify_fd_,
+ true,
+ base::MessageLoopForIO::WATCH_READ,
+ &watcher_,
+ this)) {
+ Cancel();
+ return false;
+ }
+ callback_ = callback;
+ return true;
+}
+
+void NotifyWatcherMac::Cancel() {
+ if (notify_fd_ >= 0) {
+ notify_cancel(notify_token_); // Also closes |notify_fd_|.
+ notify_fd_ = -1;
+ callback_.Reset();
+ watcher_.StopWatchingFileDescriptor();
+ }
+}
+
+void NotifyWatcherMac::OnFileCanReadWithoutBlocking(int fd) {
+ int token;
+ int status = HANDLE_EINTR(read(notify_fd_, &token, sizeof(token)));
+ if (status != sizeof(token)) {
+ Cancel();
+ callback_.Run(false);
+ return;
+ }
+ // Ignoring |token| value to avoid possible endianness mismatch:
+ // http://openradar.appspot.com/8821081
+ callback_.Run(true);
+}
+
+} // namespace net
diff --git a/chromium/net/dns/notify_watcher_mac.h b/chromium/net/dns/notify_watcher_mac.h
new file mode 100644
index 00000000000..01375d56637
--- /dev/null
+++ b/chromium/net/dns/notify_watcher_mac.h
@@ -0,0 +1,47 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_NOTIFY_WATCHER_MAC_H_
+#define NET_DNS_NOTIFY_WATCHER_MAC_H_
+
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+
+namespace net {
+
+// Watches for notifications from Libnotify and delivers them to a Callback.
+// After failure the watch is cancelled and will have to be restarted.
+class NotifyWatcherMac : public base::MessageLoopForIO::Watcher {
+ public:
+ // Called on received notification with true on success and false on error.
+ typedef base::Callback<void(bool succeeded)> CallbackType;
+
+ NotifyWatcherMac();
+
+ // When deleted, automatically cancels.
+ virtual ~NotifyWatcherMac();
+
+ // Registers for notifications for |key|. Returns true if succeeds. If so,
+ // will deliver asynchronous notifications and errors to |callback|.
+ bool Watch(const char* key, const CallbackType& callback);
+
+ // Cancels the watch.
+ void Cancel();
+
+ private:
+ // MessageLoopForIO::Watcher:
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
+
+ int notify_fd_;
+ int notify_token_;
+ CallbackType callback_;
+ base::MessageLoopForIO::FileDescriptorWatcher watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(NotifyWatcherMac);
+};
+
+} // namespace net
+
+#endif // NET_DNS_NOTIFY_WATCHER_MAC_H_
diff --git a/chromium/net/dns/record_parsed.cc b/chromium/net/dns/record_parsed.cc
new file mode 100644
index 00000000000..bee6c7aac63
--- /dev/null
+++ b/chromium/net/dns/record_parsed.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2013 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/dns/record_parsed.h"
+
+#include "base/logging.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/record_rdata.h"
+
+namespace net {
+
+RecordParsed::RecordParsed(const std::string& name, uint16 type, uint16 klass,
+ uint32 ttl, scoped_ptr<const RecordRdata> rdata,
+ base::Time time_created)
+ : name_(name), type_(type), klass_(klass), ttl_(ttl), rdata_(rdata.Pass()),
+ time_created_(time_created) {
+}
+
+RecordParsed::~RecordParsed() {
+}
+
+// static
+scoped_ptr<const RecordParsed> RecordParsed::CreateFrom(
+ DnsRecordParser* parser,
+ base::Time time_created) {
+ DnsResourceRecord record;
+ scoped_ptr<const RecordRdata> rdata;
+
+ if (!parser->ReadRecord(&record))
+ return scoped_ptr<const RecordParsed>();
+
+ switch (record.type) {
+ case ARecordRdata::kType:
+ rdata = ARecordRdata::Create(record.rdata, *parser);
+ break;
+ case AAAARecordRdata::kType:
+ rdata = AAAARecordRdata::Create(record.rdata, *parser);
+ break;
+ case CnameRecordRdata::kType:
+ rdata = CnameRecordRdata::Create(record.rdata, *parser);
+ break;
+ case PtrRecordRdata::kType:
+ rdata = PtrRecordRdata::Create(record.rdata, *parser);
+ break;
+ case SrvRecordRdata::kType:
+ rdata = SrvRecordRdata::Create(record.rdata, *parser);
+ break;
+ case TxtRecordRdata::kType:
+ rdata = TxtRecordRdata::Create(record.rdata, *parser);
+ break;
+ case NsecRecordRdata::kType:
+ rdata = NsecRecordRdata::Create(record.rdata, *parser);
+ break;
+ default:
+ LOG(WARNING) << "Unknown RData type for recieved record: " << record.type;
+ return scoped_ptr<const RecordParsed>();
+ }
+
+ if (!rdata.get())
+ return scoped_ptr<const RecordParsed>();
+
+ return scoped_ptr<const RecordParsed>(new RecordParsed(record.name,
+ record.type,
+ record.klass,
+ record.ttl,
+ rdata.Pass(),
+ time_created));
+}
+
+bool RecordParsed::IsEqual(const RecordParsed* other, bool is_mdns) const {
+ DCHECK(other);
+ uint16 klass = klass_;
+ uint16 other_klass = other->klass_;
+
+ if (is_mdns) {
+ klass &= dns_protocol::kMDnsClassMask;
+ other_klass &= dns_protocol::kMDnsClassMask;
+ }
+
+ return name_ == other->name_ &&
+ klass == other_klass &&
+ type_ == other->type_ &&
+ rdata_->IsEqual(other->rdata_.get());
+}
+}
diff --git a/chromium/net/dns/record_parsed.h b/chromium/net/dns/record_parsed.h
new file mode 100644
index 00000000000..016c4910bfb
--- /dev/null
+++ b/chromium/net/dns/record_parsed.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2013 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_DNS_RECORD_PARSED_H_
+#define NET_DNS_RECORD_PARSED_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class DnsRecordParser;
+class RecordRdata;
+
+// Parsed record. This is a form of DnsResourceRecord where the rdata section
+// has been parsed into a data structure.
+class NET_EXPORT_PRIVATE RecordParsed {
+ public:
+ virtual ~RecordParsed();
+
+ // All records are inherently immutable. Return a const pointer.
+ static scoped_ptr<const RecordParsed> CreateFrom(DnsRecordParser* parser,
+ base::Time time_created);
+
+ const std::string& name() const { return name_; }
+ uint16 type() const { return type_; }
+ uint16 klass() const { return klass_; }
+ uint32 ttl() const { return ttl_; }
+
+ base::Time time_created() const { return time_created_; }
+
+ template <class T> const T* rdata() const {
+ if (T::kType != type_)
+ return NULL;
+ return static_cast<const T*>(rdata_.get());
+ }
+
+ // Check if two records have the same data. Ignores time_created and ttl.
+ // If |is_mdns| is true, ignore the top bit of the class
+ // (the cache flush bit).
+ bool IsEqual(const RecordParsed* other, bool is_mdns) const;
+
+ private:
+ RecordParsed(const std::string& name, uint16 type, uint16 klass,
+ uint32 ttl, scoped_ptr<const RecordRdata> rdata,
+ base::Time time_created);
+
+ std::string name_; // in dotted form
+ const uint16 type_;
+ const uint16 klass_;
+ const uint32 ttl_;
+
+ const scoped_ptr<const RecordRdata> rdata_;
+
+ const base::Time time_created_;
+};
+
+} // namespace net
+
+#endif // NET_DNS_RECORD_PARSED_H_
diff --git a/chromium/net/dns/record_parsed_unittest.cc b/chromium/net/dns/record_parsed_unittest.cc
new file mode 100644
index 00000000000..2864dcbe761
--- /dev/null
+++ b/chromium/net/dns/record_parsed_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2013 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/dns/record_parsed.h"
+
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_test_util.h"
+#include "net/dns/record_rdata.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+static const uint8 kT1ResponseWithCacheFlushBit[] = {
+ 0x0a, 'c', 'o', 'd', 'e', 'r', 'e', 'v', 'i', 'e', 'w',
+ 0x08, 'c', 'h', 'r', 'o', 'm', 'i', 'u', 'm',
+ 0x03, 'o', 'r', 'g',
+ 0x00,
+ 0x00, 0x05, // TYPE is CNAME.
+ 0x80, 0x01, // CLASS is IN with cache flush bit set.
+ 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+ 0x24, 0x74,
+ 0x00, 0x12, // RDLENGTH is 18 bytes.
+ // ghs.l.google.com in DNS format.
+ 0x03, 'g', 'h', 's',
+ 0x01, 'l',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00
+};
+
+TEST(RecordParsedTest, ParseSingleRecord) {
+ DnsRecordParser parser(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+ sizeof(dns_protocol::Header));
+ scoped_ptr<const RecordParsed> record;
+ const CnameRecordRdata* rdata;
+
+ parser.SkipQuestion();
+ record = RecordParsed::CreateFrom(&parser, base::Time());
+ EXPECT_TRUE(record != NULL);
+
+ ASSERT_EQ("codereview.chromium.org", record->name());
+ ASSERT_EQ(dns_protocol::kTypeCNAME, record->type());
+ ASSERT_EQ(dns_protocol::kClassIN, record->klass());
+
+ rdata = record->rdata<CnameRecordRdata>();
+ ASSERT_TRUE(rdata != NULL);
+ ASSERT_EQ(kT1CanonName, rdata->cname());
+
+ ASSERT_FALSE(record->rdata<SrvRecordRdata>());
+ ASSERT_TRUE(record->IsEqual(record.get(), true));
+}
+
+TEST(RecordParsedTest, CacheFlushBitCompare) {
+ DnsRecordParser parser1(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+ sizeof(dns_protocol::Header));
+ parser1.SkipQuestion();
+ scoped_ptr<const RecordParsed> record1 =
+ RecordParsed::CreateFrom(&parser1, base::Time());
+
+ DnsRecordParser parser2(kT1ResponseWithCacheFlushBit,
+ sizeof(kT1ResponseWithCacheFlushBit),
+ 0);
+
+ scoped_ptr<const RecordParsed> record2 =
+ RecordParsed::CreateFrom(&parser2, base::Time());
+
+ EXPECT_FALSE(record1->IsEqual(record2.get(), false));
+ EXPECT_TRUE(record1->IsEqual(record2.get(), true));
+ EXPECT_FALSE(record2->IsEqual(record1.get(), false));
+ EXPECT_TRUE(record2->IsEqual(record1.get(), true));
+}
+
+} //namespace net
diff --git a/chromium/net/dns/record_rdata.cc b/chromium/net/dns/record_rdata.cc
new file mode 100644
index 00000000000..4ebc643a377
--- /dev/null
+++ b/chromium/net/dns/record_rdata.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2013 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/dns/record_rdata.h"
+
+#include "net/base/big_endian.h"
+#include "net/base/dns_util.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_response.h"
+
+namespace net {
+
+static const size_t kSrvRecordMinimumSize = 6;
+
+RecordRdata::RecordRdata() {
+}
+
+SrvRecordRdata::SrvRecordRdata() : priority_(0), weight_(0), port_(0) {
+}
+
+SrvRecordRdata::~SrvRecordRdata() {}
+
+// static
+scoped_ptr<SrvRecordRdata> SrvRecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ if (data.size() < kSrvRecordMinimumSize) return scoped_ptr<SrvRecordRdata>();
+
+ scoped_ptr<SrvRecordRdata> rdata(new SrvRecordRdata);
+
+ BigEndianReader reader(data.data(), data.size());
+ // 2 bytes for priority, 2 bytes for weight, 2 bytes for port.
+ reader.ReadU16(&rdata->priority_);
+ reader.ReadU16(&rdata->weight_);
+ reader.ReadU16(&rdata->port_);
+
+ if (!parser.ReadName(data.substr(kSrvRecordMinimumSize).begin(),
+ &rdata->target_))
+ return scoped_ptr<SrvRecordRdata>();
+
+ return rdata.Pass();
+}
+
+uint16 SrvRecordRdata::Type() const {
+ return SrvRecordRdata::kType;
+}
+
+bool SrvRecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const SrvRecordRdata* srv_other = static_cast<const SrvRecordRdata*>(other);
+ return weight_ == srv_other->weight_ &&
+ port_ == srv_other->port_ &&
+ priority_ == srv_other->priority_ &&
+ target_ == srv_other->target_;
+}
+
+ARecordRdata::ARecordRdata() {
+}
+
+ARecordRdata::~ARecordRdata() {
+}
+
+// static
+scoped_ptr<ARecordRdata> ARecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ if (data.size() != kIPv4AddressSize)
+ return scoped_ptr<ARecordRdata>();
+
+ scoped_ptr<ARecordRdata> rdata(new ARecordRdata);
+
+ rdata->address_.resize(kIPv4AddressSize);
+ for (unsigned i = 0; i < kIPv4AddressSize; ++i) {
+ rdata->address_[i] = data[i];
+ }
+
+ return rdata.Pass();
+}
+
+uint16 ARecordRdata::Type() const {
+ return ARecordRdata::kType;
+}
+
+bool ARecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const ARecordRdata* a_other = static_cast<const ARecordRdata*>(other);
+ return address_ == a_other->address_;
+}
+
+AAAARecordRdata::AAAARecordRdata() {
+}
+
+AAAARecordRdata::~AAAARecordRdata() {
+}
+
+// static
+scoped_ptr<AAAARecordRdata> AAAARecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ if (data.size() != kIPv6AddressSize)
+ return scoped_ptr<AAAARecordRdata>();
+
+ scoped_ptr<AAAARecordRdata> rdata(new AAAARecordRdata);
+
+ rdata->address_.resize(kIPv6AddressSize);
+ for (unsigned i = 0; i < kIPv6AddressSize; ++i) {
+ rdata->address_[i] = data[i];
+ }
+
+ return rdata.Pass();
+}
+
+uint16 AAAARecordRdata::Type() const {
+ return AAAARecordRdata::kType;
+}
+
+bool AAAARecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const AAAARecordRdata* a_other = static_cast<const AAAARecordRdata*>(other);
+ return address_ == a_other->address_;
+}
+
+CnameRecordRdata::CnameRecordRdata() {
+}
+
+CnameRecordRdata::~CnameRecordRdata() {
+}
+
+// static
+scoped_ptr<CnameRecordRdata> CnameRecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ scoped_ptr<CnameRecordRdata> rdata(new CnameRecordRdata);
+
+ if (!parser.ReadName(data.begin(), &rdata->cname_))
+ return scoped_ptr<CnameRecordRdata>();
+
+ return rdata.Pass();
+}
+
+uint16 CnameRecordRdata::Type() const {
+ return CnameRecordRdata::kType;
+}
+
+bool CnameRecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const CnameRecordRdata* cname_other =
+ static_cast<const CnameRecordRdata*>(other);
+ return cname_ == cname_other->cname_;
+}
+
+PtrRecordRdata::PtrRecordRdata() {
+}
+
+PtrRecordRdata::~PtrRecordRdata() {
+}
+
+// static
+scoped_ptr<PtrRecordRdata> PtrRecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ scoped_ptr<PtrRecordRdata> rdata(new PtrRecordRdata);
+
+ if (!parser.ReadName(data.begin(), &rdata->ptrdomain_))
+ return scoped_ptr<PtrRecordRdata>();
+
+ return rdata.Pass();
+}
+
+uint16 PtrRecordRdata::Type() const {
+ return PtrRecordRdata::kType;
+}
+
+bool PtrRecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const PtrRecordRdata* ptr_other = static_cast<const PtrRecordRdata*>(other);
+ return ptrdomain_ == ptr_other->ptrdomain_;
+}
+
+TxtRecordRdata::TxtRecordRdata() {
+}
+
+TxtRecordRdata::~TxtRecordRdata() {
+}
+
+// static
+scoped_ptr<TxtRecordRdata> TxtRecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ scoped_ptr<TxtRecordRdata> rdata(new TxtRecordRdata);
+
+ for (size_t i = 0; i < data.size(); ) {
+ uint8 length = data[i];
+
+ if (i + length >= data.size())
+ return scoped_ptr<TxtRecordRdata>();
+
+ rdata->texts_.push_back(data.substr(i + 1, length).as_string());
+
+ // Move to the next string.
+ i += length + 1;
+ }
+
+ return rdata.Pass();
+}
+
+uint16 TxtRecordRdata::Type() const {
+ return TxtRecordRdata::kType;
+}
+
+bool TxtRecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type()) return false;
+ const TxtRecordRdata* txt_other = static_cast<const TxtRecordRdata*>(other);
+ return texts_ == txt_other->texts_;
+}
+
+NsecRecordRdata::NsecRecordRdata() {
+}
+
+NsecRecordRdata::~NsecRecordRdata() {
+}
+
+// static
+scoped_ptr<NsecRecordRdata> NsecRecordRdata::Create(
+ const base::StringPiece& data,
+ const DnsRecordParser& parser) {
+ scoped_ptr<NsecRecordRdata> rdata(new NsecRecordRdata);
+
+ // Read the "next domain". This part for the NSEC record format is
+ // ignored for mDNS, since it has no semantic meaning.
+ unsigned next_domain_length = parser.ReadName(data.data(), NULL);
+
+ // If we did not succeed in getting the next domain or the data length
+ // is too short for reading the bitmap header, return.
+ if (next_domain_length == 0 || data.length() < next_domain_length + 2)
+ return scoped_ptr<NsecRecordRdata>();
+
+ struct BitmapHeader {
+ uint8 block_number; // The block number should be zero.
+ uint8 length; // Bitmap length in bytes. Between 1 and 32.
+ };
+
+ const BitmapHeader* header = reinterpret_cast<const BitmapHeader*>(
+ data.data() + next_domain_length);
+
+ // The block number must be zero in mDns-specific NSEC records. The bitmap
+ // length must be between 1 and 32.
+ if (header->block_number != 0 || header->length == 0 || header->length > 32)
+ return scoped_ptr<NsecRecordRdata>();
+
+ base::StringPiece bitmap_data = data.substr(next_domain_length + 2);
+
+ // Since we may only have one block, the data length must be exactly equal to
+ // the domain length plus bitmap size.
+ if (bitmap_data.length() != header->length)
+ return scoped_ptr<NsecRecordRdata>();
+
+ rdata->bitmap_.insert(rdata->bitmap_.begin(),
+ bitmap_data.begin(),
+ bitmap_data.end());
+
+ return rdata.Pass();
+}
+
+uint16 NsecRecordRdata::Type() const {
+ return NsecRecordRdata::kType;
+}
+
+bool NsecRecordRdata::IsEqual(const RecordRdata* other) const {
+ if (other->Type() != Type())
+ return false;
+ const NsecRecordRdata* nsec_other =
+ static_cast<const NsecRecordRdata*>(other);
+ return bitmap_ == nsec_other->bitmap_;
+}
+
+bool NsecRecordRdata::GetBit(unsigned i) const {
+ unsigned byte_num = i/8;
+ if (bitmap_.size() < byte_num + 1)
+ return false;
+
+ unsigned bit_num = 7 - i % 8;
+ return (bitmap_[byte_num] & (1 << bit_num)) != 0;
+}
+
+} // namespace net
diff --git a/chromium/net/dns/record_rdata.h b/chromium/net/dns/record_rdata.h
new file mode 100644
index 00000000000..f83a48650e1
--- /dev/null
+++ b/chromium/net/dns/record_rdata.h
@@ -0,0 +1,217 @@
+// Copyright (c) 2013 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_DNS_RECORD_RDATA_H_
+#define NET_DNS_RECORD_RDATA_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/big_endian.h"
+#include "net/base/net_export.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_protocol.h"
+
+namespace net {
+
+class DnsRecordParser;
+
+// Parsed represenation of the extra data in a record. Does not include standard
+// DNS record data such as TTL, Name, Type and Class.
+class NET_EXPORT_PRIVATE RecordRdata {
+ public:
+ virtual ~RecordRdata() {}
+
+ virtual bool IsEqual(const RecordRdata* other) const = 0;
+ virtual uint16 Type() const = 0;
+
+ protected:
+ RecordRdata();
+
+ DISALLOW_COPY_AND_ASSIGN(RecordRdata);
+};
+
+// SRV record format (http://www.ietf.org/rfc/rfc2782.txt):
+// 2 bytes network-order unsigned priority
+// 2 bytes network-order unsigned weight
+// 2 bytes network-order unsigned port
+// target: domain name (on-the-wire representation)
+class NET_EXPORT_PRIVATE SrvRecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeSRV;
+
+ virtual ~SrvRecordRdata();
+ static scoped_ptr<SrvRecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ uint16 priority() const { return priority_; }
+ uint16 weight() const { return weight_; }
+ uint16 port() const { return port_; }
+
+ const std::string& target() const { return target_; }
+
+ private:
+ SrvRecordRdata();
+
+ uint16 priority_;
+ uint16 weight_;
+ uint16 port_;
+
+ std::string target_;
+
+ DISALLOW_COPY_AND_ASSIGN(SrvRecordRdata);
+};
+
+// A Record format (http://www.ietf.org/rfc/rfc1035.txt):
+// 4 bytes for IP address.
+class NET_EXPORT_PRIVATE ARecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeA;
+
+ virtual ~ARecordRdata();
+ static scoped_ptr<ARecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ const IPAddressNumber& address() const { return address_; }
+
+ private:
+ ARecordRdata();
+
+ IPAddressNumber address_;
+
+ DISALLOW_COPY_AND_ASSIGN(ARecordRdata);
+};
+
+// AAAA Record format (http://www.ietf.org/rfc/rfc1035.txt):
+// 16 bytes for IP address.
+class NET_EXPORT_PRIVATE AAAARecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeAAAA;
+
+ virtual ~AAAARecordRdata();
+ static scoped_ptr<AAAARecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ const IPAddressNumber& address() const { return address_; }
+
+ private:
+ AAAARecordRdata();
+
+ IPAddressNumber address_;
+
+ DISALLOW_COPY_AND_ASSIGN(AAAARecordRdata);
+};
+
+// CNAME record format (http://www.ietf.org/rfc/rfc1035.txt):
+// cname: On the wire representation of domain name.
+class NET_EXPORT_PRIVATE CnameRecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeCNAME;
+
+ virtual ~CnameRecordRdata();
+ static scoped_ptr<CnameRecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ std::string cname() const { return cname_; }
+
+ private:
+ CnameRecordRdata();
+
+ std::string cname_;
+
+ DISALLOW_COPY_AND_ASSIGN(CnameRecordRdata);
+};
+
+// PTR record format (http://www.ietf.org/rfc/rfc1035.txt):
+// domain: On the wire representation of domain name.
+class NET_EXPORT_PRIVATE PtrRecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypePTR;
+
+ virtual ~PtrRecordRdata();
+ static scoped_ptr<PtrRecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ std::string ptrdomain() const { return ptrdomain_; }
+
+ private:
+ PtrRecordRdata();
+
+ std::string ptrdomain_;
+
+ DISALLOW_COPY_AND_ASSIGN(PtrRecordRdata);
+};
+
+// TXT record format (http://www.ietf.org/rfc/rfc1035.txt):
+// texts: One or more <character-string>s.
+// a <character-string> is a length octet followed by as many characters.
+class NET_EXPORT_PRIVATE TxtRecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeTXT;
+
+ virtual ~TxtRecordRdata();
+ static scoped_ptr<TxtRecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ const std::vector<std::string>& texts() const { return texts_; }
+
+ private:
+ TxtRecordRdata();
+
+ std::vector<std::string> texts_;
+
+ DISALLOW_COPY_AND_ASSIGN(TxtRecordRdata);
+};
+
+// Only the subset of the NSEC record format required by mDNS is supported.
+// Nsec record format is described in http://www.ietf.org/rfc/rfc3845.txt and
+// the limited version required for mDNS described in
+// http://www.rfc-editor.org/rfc/rfc6762.txt Section 6.1.
+class NET_EXPORT_PRIVATE NsecRecordRdata : public RecordRdata {
+ public:
+ static const uint16 kType = dns_protocol::kTypeNSEC;
+
+ virtual ~NsecRecordRdata();
+ static scoped_ptr<NsecRecordRdata> Create(const base::StringPiece& data,
+ const DnsRecordParser& parser);
+ virtual bool IsEqual(const RecordRdata* other) const OVERRIDE;
+ virtual uint16 Type() const OVERRIDE;
+
+ // Length of the bitmap in bits.
+ unsigned bitmap_length() const { return bitmap_.size() * 8; }
+
+ // Returns bit i-th bit in the bitmap, where bits withing a byte are organized
+ // most to least significant. If it is set, a record with rrtype i exists for
+ // the domain name of this nsec record.
+ bool GetBit(unsigned i) const;
+
+ private:
+ NsecRecordRdata();
+
+ std::vector<uint8> bitmap_;
+
+ DISALLOW_COPY_AND_ASSIGN(NsecRecordRdata);
+};
+
+
+} // namespace net
+
+#endif // NET_DNS_RECORD_RDATA_H_
diff --git a/chromium/net/dns/record_rdata_unittest.cc b/chromium/net/dns/record_rdata_unittest.cc
new file mode 100644
index 00000000000..90bac446e2e
--- /dev/null
+++ b/chromium/net/dns/record_rdata_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2013 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 "base/memory/scoped_ptr.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/record_rdata.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+base::StringPiece MakeStringPiece(const uint8* data, unsigned size) {
+ const char* data_cc = reinterpret_cast<const char*>(data);
+ return base::StringPiece(data_cc, size);
+}
+
+TEST(RecordRdataTest, ParseSrvRecord) {
+ scoped_ptr<SrvRecordRdata> record1_obj;
+ scoped_ptr<SrvRecordRdata> record2_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x00, 0x01,
+ 0x00, 0x02,
+ 0x00, 0x50,
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00,
+ 0x01, 0x01,
+ 0x01, 0x02,
+ 0x01, 0x03,
+ 0x04, 'w', 'w', 'w', '2',
+ 0xc0, 0x0a, // Pointer to "google.com"
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ const unsigned first_record_len = 22;
+ base::StringPiece record1_strpiece = MakeStringPiece(
+ record, first_record_len);
+ base::StringPiece record2_strpiece = MakeStringPiece(
+ record + first_record_len, sizeof(record) - first_record_len);
+
+ record1_obj = SrvRecordRdata::Create(record1_strpiece, parser);
+ ASSERT_TRUE(record1_obj != NULL);
+ ASSERT_EQ(1, record1_obj->priority());
+ ASSERT_EQ(2, record1_obj->weight());
+ ASSERT_EQ(80, record1_obj->port());
+
+ ASSERT_EQ("www.google.com", record1_obj->target());
+
+ record2_obj = SrvRecordRdata::Create(record2_strpiece, parser);
+ ASSERT_TRUE(record2_obj != NULL);
+ ASSERT_EQ(257, record2_obj->priority());
+ ASSERT_EQ(258, record2_obj->weight());
+ ASSERT_EQ(259, record2_obj->port());
+
+ ASSERT_EQ("www2.google.com", record2_obj->target());
+
+ ASSERT_TRUE(record1_obj->IsEqual(record1_obj.get()));
+ ASSERT_FALSE(record1_obj->IsEqual(record2_obj.get()));
+}
+
+TEST(RecordRdataTest, ParseARecord) {
+ scoped_ptr<ARecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x7F, 0x00, 0x00, 0x01 // 127.0.0.1
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = ARecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ ASSERT_EQ("127.0.0.1", IPAddressToString(record_obj->address()));
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+TEST(RecordRdataTest, ParseAAAARecord) {
+ scoped_ptr<AAAARecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x12, 0x34, 0x56, 0x78,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x09 // 1234:5678::9A
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = AAAARecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ ASSERT_EQ("1234:5678::9",
+ IPAddressToString(record_obj->address()));
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+TEST(RecordRdataTest, ParseCnameRecord) {
+ scoped_ptr<CnameRecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = CnameRecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ ASSERT_EQ("www.google.com", record_obj->cname());
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+TEST(RecordRdataTest, ParsePtrRecord) {
+ scoped_ptr<PtrRecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = PtrRecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ ASSERT_EQ("www.google.com", record_obj->ptrdomain());
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+TEST(RecordRdataTest, ParseTxtRecord) {
+ scoped_ptr<TxtRecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm'
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = TxtRecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ std::vector<std::string> expected;
+ expected.push_back("www");
+ expected.push_back("google");
+ expected.push_back("com");
+
+ ASSERT_EQ(expected, record_obj->texts());
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+TEST(RecordRdataTest, ParseNsecRecord) {
+ scoped_ptr<NsecRecordRdata> record_obj;
+
+ // These are just the rdata portions of the DNS records, rather than complete
+ // records, but it works well enough for this test.
+
+ const uint8 record[] = {
+ 0x03, 'w', 'w', 'w',
+ 0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+ 0x03, 'c', 'o', 'm',
+ 0x00,
+ 0x00, 0x02, 0x40, 0x01
+ };
+
+ DnsRecordParser parser(record, sizeof(record), 0);
+ base::StringPiece record_strpiece = MakeStringPiece(record, sizeof(record));
+
+ record_obj = NsecRecordRdata::Create(record_strpiece, parser);
+ ASSERT_TRUE(record_obj != NULL);
+
+ ASSERT_EQ(16u, record_obj->bitmap_length());
+
+ EXPECT_FALSE(record_obj->GetBit(0));
+ EXPECT_TRUE(record_obj->GetBit(1));
+ for (int i = 2; i < 15; i++) {
+ EXPECT_FALSE(record_obj->GetBit(i));
+ }
+ EXPECT_TRUE(record_obj->GetBit(15));
+
+ ASSERT_TRUE(record_obj->IsEqual(record_obj.get()));
+}
+
+
+} // namespace net
diff --git a/chromium/net/dns/serial_worker.cc b/chromium/net/dns/serial_worker.cc
new file mode 100644
index 00000000000..394721c1a65
--- /dev/null
+++ b/chromium/net/dns/serial_worker.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 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.
+
+#include "net/dns/serial_worker.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/worker_pool.h"
+
+namespace net {
+
+namespace {
+ // Delay between calls to WorkerPool::PostTask
+ const int kWorkerPoolRetryDelayMs = 100;
+}
+
+SerialWorker::SerialWorker()
+ : message_loop_(base::MessageLoopProxy::current()),
+ state_(IDLE) {}
+
+SerialWorker::~SerialWorker() {}
+
+void SerialWorker::WorkNow() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ switch (state_) {
+ case IDLE:
+ if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind(
+ &SerialWorker::DoWorkJob, this), false)) {
+#if defined(OS_POSIX)
+ // See worker_pool_posix.cc.
+ NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix";
+#else
+ LOG(WARNING) << "Failed to WorkerPool::PostTask, will retry later";
+ message_loop_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SerialWorker::RetryWork, this),
+ base::TimeDelta::FromMilliseconds(kWorkerPoolRetryDelayMs));
+ state_ = WAITING;
+ return;
+#endif
+ }
+ state_ = WORKING;
+ return;
+ case WORKING:
+ // Remember to re-read after |DoRead| finishes.
+ state_ = PENDING;
+ return;
+ case CANCELLED:
+ case PENDING:
+ case WAITING:
+ return;
+ default:
+ NOTREACHED() << "Unexpected state " << state_;
+ }
+}
+
+void SerialWorker::Cancel() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ state_ = CANCELLED;
+}
+
+void SerialWorker::DoWorkJob() {
+ this->DoWork();
+ // If this fails, the loop is gone, so there is no point retrying.
+ message_loop_->PostTask(FROM_HERE, base::Bind(
+ &SerialWorker::OnWorkJobFinished, this));
+}
+
+void SerialWorker::OnWorkJobFinished() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ switch (state_) {
+ case CANCELLED:
+ return;
+ case WORKING:
+ state_ = IDLE;
+ this->OnWorkFinished();
+ return;
+ case PENDING:
+ state_ = IDLE;
+ WorkNow();
+ return;
+ default:
+ NOTREACHED() << "Unexpected state " << state_;
+ }
+}
+
+void SerialWorker::RetryWork() {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ switch (state_) {
+ case CANCELLED:
+ return;
+ case WAITING:
+ state_ = IDLE;
+ WorkNow();
+ return;
+ default:
+ NOTREACHED() << "Unexpected state " << state_;
+ }
+}
+
+} // namespace net
+
diff --git a/chromium/net/dns/serial_worker.h b/chromium/net/dns/serial_worker.h
new file mode 100644
index 00000000000..59a4d3f189c
--- /dev/null
+++ b/chromium/net/dns/serial_worker.h
@@ -0,0 +1,96 @@
+// Copyright (c) 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.
+
+#ifndef NET_DNS_SERIAL_WORKER_H_
+#define NET_DNS_SERIAL_WORKER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+// Forward declaration
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace net {
+
+// SerialWorker executes a job on WorkerPool serially -- **once at a time**.
+// On |WorkNow|, a call to |DoWork| is scheduled on the WorkerPool. Once it
+// completes, |OnWorkFinished| is called on the origin thread.
+// If |WorkNow| is called (1 or more times) while |DoWork| is already under way,
+// |DoWork| will be called once: after current |DoWork| completes, before a
+// call to |OnWorkFinished|.
+//
+// This behavior is designed for updating a result after some trigger, for
+// example reading a file once FilePathWatcher indicates it changed.
+//
+// Derived classes should store results of work done in |DoWork| in dedicated
+// fields and read them in |OnWorkFinished| which is executed on the origin
+// thread. This avoids the need to template this class.
+//
+// This implementation avoids locking by using the |state_| member to ensure
+// that |DoWork| and |OnWorkFinished| cannot execute in parallel.
+//
+// TODO(szym): update to WorkerPool::PostTaskAndReply once available.
+class NET_EXPORT_PRIVATE SerialWorker
+ : NON_EXPORTED_BASE(public base::RefCountedThreadSafe<SerialWorker>) {
+ public:
+ SerialWorker();
+
+ // Unless already scheduled, post |DoWork| to WorkerPool.
+ // Made virtual to allow mocking.
+ virtual void WorkNow();
+
+ // Stop scheduling jobs.
+ void Cancel();
+
+ bool IsCancelled() const { return state_ == CANCELLED; }
+
+ protected:
+ friend class base::RefCountedThreadSafe<SerialWorker>;
+ // protected to allow sub-classing, but prevent deleting
+ virtual ~SerialWorker();
+
+ // Executed on WorkerPool, at most once at a time.
+ virtual void DoWork() = 0;
+
+ // Executed on origin thread after |DoRead| completes.
+ virtual void OnWorkFinished() = 0;
+
+ base::MessageLoopProxy* loop() { return message_loop_.get(); }
+
+ private:
+ enum State {
+ CANCELLED = -1,
+ IDLE = 0,
+ WORKING, // |DoWorkJob| posted on WorkerPool, until |OnWorkJobFinished|
+ PENDING, // |WorkNow| while WORKING, must re-do work
+ WAITING, // WorkerPool is busy, |RetryWork| is posted
+ };
+
+ // Called on the worker thread, executes |DoWork| and notifies the origin
+ // thread.
+ void DoWorkJob();
+
+ // Called on the the origin thread after |DoWork| completes.
+ void OnWorkJobFinished();
+
+ // Posted to message loop in case WorkerPool is busy. (state == WAITING)
+ void RetryWork();
+
+ // Message loop for the thread of origin.
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(SerialWorker);
+};
+
+} // namespace net
+
+#endif // NET_DNS_SERIAL_WORKER_H_
+
diff --git a/chromium/net/dns/serial_worker_unittest.cc b/chromium/net/dns/serial_worker_unittest.cc
new file mode 100644
index 00000000000..442526f29d2
--- /dev/null
+++ b/chromium/net/dns/serial_worker_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2011 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/dns/serial_worker.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class SerialWorkerTest : public testing::Test {
+ public:
+ // The class under test
+ class TestSerialWorker : public SerialWorker {
+ public:
+ explicit TestSerialWorker(SerialWorkerTest* t)
+ : test_(t) {}
+ virtual void DoWork() OVERRIDE {
+ ASSERT_TRUE(test_);
+ test_->OnWork();
+ }
+ virtual void OnWorkFinished() OVERRIDE {
+ ASSERT_TRUE(test_);
+ test_->OnWorkFinished();
+ }
+ private:
+ virtual ~TestSerialWorker() {}
+ SerialWorkerTest* test_;
+ };
+
+ // Mocks
+
+ void OnWork() {
+ { // Check that OnWork is executed serially.
+ base::AutoLock lock(work_lock_);
+ EXPECT_FALSE(work_running_) << "DoRead is not called serially!";
+ work_running_ = true;
+ }
+ BreakNow("OnWork");
+ work_allowed_.Wait();
+ // Calling from WorkerPool, but protected by work_allowed_/work_called_.
+ output_value_ = input_value_;
+
+ { // This lock might be destroyed after work_called_ is signalled.
+ base::AutoLock lock(work_lock_);
+ work_running_ = false;
+ }
+ work_called_.Signal();
+ }
+
+ void OnWorkFinished() {
+ EXPECT_TRUE(message_loop_ == base::MessageLoop::current());
+ EXPECT_EQ(output_value_, input_value_);
+ BreakNow("OnWorkFinished");
+ }
+
+ protected:
+ void BreakCallback(std::string breakpoint) {
+ breakpoint_ = breakpoint;
+ base::MessageLoop::current()->QuitNow();
+ }
+
+ void BreakNow(std::string b) {
+ message_loop_->PostTask(FROM_HERE,
+ base::Bind(&SerialWorkerTest::BreakCallback,
+ base::Unretained(this), b));
+ }
+
+ void RunUntilBreak(std::string b) {
+ message_loop_->Run();
+ ASSERT_EQ(breakpoint_, b);
+ }
+
+ SerialWorkerTest()
+ : input_value_(0),
+ output_value_(-1),
+ work_allowed_(false, false),
+ work_called_(false, false),
+ work_running_(false) {
+ }
+
+ // Helpers for tests.
+
+ // Lets OnWork run and waits for it to complete. Can only return if OnWork is
+ // executed on a concurrent thread.
+ void WaitForWork() {
+ RunUntilBreak("OnWork");
+ work_allowed_.Signal();
+ work_called_.Wait();
+ }
+
+ // test::Test methods
+ virtual void SetUp() OVERRIDE {
+ message_loop_ = base::MessageLoop::current();
+ worker_ = new TestSerialWorker(this);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Cancel the worker to catch if it makes a late DoWork call.
+ worker_->Cancel();
+ // Check if OnWork is stalled.
+ EXPECT_FALSE(work_running_) << "OnWork should be done by TearDown";
+ // Release it for cleanliness.
+ if (work_running_) {
+ WaitForWork();
+ }
+ }
+
+ // Input value read on WorkerPool.
+ int input_value_;
+ // Output value written on WorkerPool.
+ int output_value_;
+
+ // read is called on WorkerPool so we need to synchronize with it.
+ base::WaitableEvent work_allowed_;
+ base::WaitableEvent work_called_;
+
+ // Protected by read_lock_. Used to verify that read calls are serialized.
+ bool work_running_;
+ base::Lock work_lock_;
+
+ // Loop for this thread.
+ base::MessageLoop* message_loop_;
+
+ // WatcherDelegate under test.
+ scoped_refptr<TestSerialWorker> worker_;
+
+ std::string breakpoint_;
+};
+
+TEST_F(SerialWorkerTest, ExecuteAndSerializeReads) {
+ for (int i = 0; i < 3; ++i) {
+ ++input_value_;
+ worker_->WorkNow();
+ WaitForWork();
+ RunUntilBreak("OnWorkFinished");
+
+ EXPECT_TRUE(message_loop_->IsIdleForTesting());
+ }
+
+ // Schedule two calls. OnWork checks if it is called serially.
+ ++input_value_;
+ worker_->WorkNow();
+ // read is blocked, so this will have to induce re-work
+ worker_->WorkNow();
+ WaitForWork();
+ WaitForWork();
+ RunUntilBreak("OnWorkFinished");
+
+ // No more tasks should remain.
+ EXPECT_TRUE(message_loop_->IsIdleForTesting());
+}
+
+} // namespace
+
+} // namespace net
+
diff --git a/chromium/net/dns/single_request_host_resolver.cc b/chromium/net/dns/single_request_host_resolver.cc
new file mode 100644
index 00000000000..31ef4c56b6e
--- /dev/null
+++ b/chromium/net/dns/single_request_host_resolver.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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/dns/single_request_host_resolver.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+SingleRequestHostResolver::SingleRequestHostResolver(HostResolver* resolver)
+ : resolver_(resolver),
+ cur_request_(NULL),
+ callback_(
+ base::Bind(&SingleRequestHostResolver::OnResolveCompletion,
+ base::Unretained(this))) {
+ DCHECK(resolver_ != NULL);
+}
+
+SingleRequestHostResolver::~SingleRequestHostResolver() {
+ Cancel();
+}
+
+int SingleRequestHostResolver::Resolve(
+ const HostResolver::RequestInfo& info, AddressList* addresses,
+ const CompletionCallback& callback, const BoundNetLog& net_log) {
+ DCHECK(addresses);
+ DCHECK_EQ(false, callback.is_null());
+ DCHECK(cur_request_callback_.is_null()) << "resolver already in use";
+
+ HostResolver::RequestHandle request = NULL;
+
+ // We need to be notified of completion before |callback| is called, so that
+ // we can clear out |cur_request_*|.
+ CompletionCallback transient_callback =
+ callback.is_null() ? CompletionCallback() : callback_;
+
+ int rv = resolver_->Resolve(
+ info, addresses, transient_callback, &request, net_log);
+
+ if (rv == ERR_IO_PENDING) {
+ DCHECK_EQ(false, callback.is_null());
+ // Cleared in OnResolveCompletion().
+ cur_request_ = request;
+ cur_request_callback_ = callback;
+ }
+
+ return rv;
+}
+
+void SingleRequestHostResolver::Cancel() {
+ if (!cur_request_callback_.is_null()) {
+ resolver_->CancelRequest(cur_request_);
+ cur_request_ = NULL;
+ cur_request_callback_.Reset();
+ }
+}
+
+void SingleRequestHostResolver::OnResolveCompletion(int result) {
+ DCHECK(cur_request_);
+ DCHECK_EQ(false, cur_request_callback_.is_null());
+
+ CompletionCallback callback = cur_request_callback_;
+
+ // Clear the outstanding request information.
+ cur_request_ = NULL;
+ cur_request_callback_.Reset();
+
+ // Call the user's original callback.
+ callback.Run(result);
+}
+
+} // namespace net
diff --git a/chromium/net/dns/single_request_host_resolver.h b/chromium/net/dns/single_request_host_resolver.h
new file mode 100644
index 00000000000..52d01328911
--- /dev/null
+++ b/chromium/net/dns/single_request_host_resolver.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2011 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_DNS_SINGLE_REQUEST_HOST_RESOLVER_H_
+#define NET_DNS_SINGLE_REQUEST_HOST_RESOLVER_H_
+
+#include "net/dns/host_resolver.h"
+
+namespace net {
+
+// This class represents the task of resolving a hostname (or IP address
+// literal) to an AddressList object. It wraps HostResolver to resolve only a
+// single hostname at a time and cancels this request when going out of scope.
+class NET_EXPORT SingleRequestHostResolver {
+ public:
+ // |resolver| must remain valid for the lifetime of |this|.
+ explicit SingleRequestHostResolver(HostResolver* resolver);
+
+ // If a completion callback is pending when the resolver is destroyed, the
+ // host resolution is cancelled, and the completion callback will not be
+ // called.
+ ~SingleRequestHostResolver();
+
+ // Resolves the given hostname (or IP address literal), filling out the
+ // |addresses| object upon success. See HostResolver::Resolve() for details.
+ int Resolve(const HostResolver::RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log);
+
+ // Cancels the in-progress request, if any. This prevents the callback
+ // from being invoked. Resolve() can be called again after cancelling.
+ void Cancel();
+
+ private:
+ // Callback for when the request to |resolver_| completes, so we dispatch
+ // to the user's callback.
+ void OnResolveCompletion(int result);
+
+ // The actual host resolver that will handle the request.
+ HostResolver* const resolver_;
+
+ // The current request (if any).
+ HostResolver::RequestHandle cur_request_;
+ CompletionCallback cur_request_callback_;
+
+ // Completion callback for when request to |resolver_| completes.
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SingleRequestHostResolver);
+};
+
+} // namespace net
+
+#endif // NET_DNS_SINGLE_REQUEST_HOST_RESOLVER_H_
diff --git a/chromium/net/dns/single_request_host_resolver_unittest.cc b/chromium/net/dns/single_request_host_resolver_unittest.cc
new file mode 100644
index 00000000000..1b0198f4fde
--- /dev/null
+++ b/chromium/net/dns/single_request_host_resolver_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#include "net/dns/single_request_host_resolver.h"
+
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Helper class used by SingleRequestHostResolverTest.Cancel test.
+// It checks that only one request is outstanding at a time, and that
+// it is cancelled before the class is destroyed.
+class HangingHostResolver : public HostResolver {
+ public:
+ HangingHostResolver() : outstanding_request_(NULL) {}
+
+ virtual ~HangingHostResolver() {
+ EXPECT_TRUE(!has_outstanding_request());
+ }
+
+ bool has_outstanding_request() const {
+ return outstanding_request_ != NULL;
+ }
+
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE {
+ EXPECT_FALSE(has_outstanding_request());
+ outstanding_request_ = reinterpret_cast<RequestHandle>(0x1234);
+ *out_req = outstanding_request_;
+
+ // Never complete this request! Caller is expected to cancel it
+ // before destroying the resolver.
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE {
+ NOTIMPLEMENTED();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual void CancelRequest(RequestHandle req) OVERRIDE {
+ EXPECT_TRUE(has_outstanding_request());
+ EXPECT_EQ(req, outstanding_request_);
+ outstanding_request_ = NULL;
+ }
+
+ private:
+ RequestHandle outstanding_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(HangingHostResolver);
+};
+
+// Test that a regular end-to-end lookup returns the expected result.
+TEST(SingleRequestHostResolverTest, NormalResolve) {
+ // Create a host resolver dependency that returns address "199.188.1.166"
+ // for resolutions of "watsup".
+ MockHostResolver resolver;
+ resolver.rules()->AddIPLiteralRule("watsup", "199.188.1.166", std::string());
+
+ SingleRequestHostResolver single_request_resolver(&resolver);
+
+ // Resolve "watsup:90" using our SingleRequestHostResolver.
+ AddressList addrlist;
+ TestCompletionCallback callback;
+ HostResolver::RequestInfo request(HostPortPair("watsup", 90));
+ int rv = single_request_resolver.Resolve(
+ request, &addrlist, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Verify that the result is what we specified in the MockHostResolver.
+ ASSERT_FALSE(addrlist.empty());
+ EXPECT_EQ("199.188.1.166", addrlist.front().ToStringWithoutPort());
+}
+
+// Test that the Cancel() method cancels any outstanding request.
+TEST(SingleRequestHostResolverTest, Cancel) {
+ HangingHostResolver resolver;
+
+ {
+ SingleRequestHostResolver single_request_resolver(&resolver);
+
+ // Resolve "watsup:90" using our SingleRequestHostResolver.
+ AddressList addrlist;
+ TestCompletionCallback callback;
+ HostResolver::RequestInfo request(HostPortPair("watsup", 90));
+ int rv = single_request_resolver.Resolve(
+ request, &addrlist, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_TRUE(resolver.has_outstanding_request());
+ }
+
+ // Now that the SingleRequestHostResolver has been destroyed, the
+ // in-progress request should have been aborted.
+ EXPECT_FALSE(resolver.has_outstanding_request());
+}
+
+// Test that the Cancel() method is a no-op when there is no outstanding
+// request.
+TEST(SingleRequestHostResolverTest, CancelWhileNoPendingRequest) {
+ HangingHostResolver resolver;
+ SingleRequestHostResolver single_request_resolver(&resolver);
+ single_request_resolver.Cancel();
+
+ // To pass, HangingHostResolver should not have received a cancellation
+ // request (since there is nothing to cancel). If it does, it will crash.
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/OWNERS b/chromium/net/ftp/OWNERS
new file mode 100644
index 00000000000..92ecc889fd3
--- /dev/null
+++ b/chromium/net/ftp/OWNERS
@@ -0,0 +1 @@
+phajdan.jr@chromium.org
diff --git a/chromium/net/ftp/ftp_auth_cache.cc b/chromium/net/ftp/ftp_auth_cache.cc
new file mode 100644
index 00000000000..370cee57379
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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/ftp/ftp_auth_cache.h"
+
+#include "base/logging.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// static
+const size_t FtpAuthCache::kMaxEntries = 10;
+
+FtpAuthCache::Entry::Entry(const GURL& origin,
+ const AuthCredentials& credentials)
+ : origin(origin),
+ credentials(credentials) {
+}
+
+FtpAuthCache::Entry::~Entry() {}
+
+FtpAuthCache::FtpAuthCache() {}
+
+FtpAuthCache::~FtpAuthCache() {}
+
+FtpAuthCache::Entry* FtpAuthCache::Lookup(const GURL& origin) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin == origin)
+ return &(*it);
+ }
+ return NULL;
+}
+
+void FtpAuthCache::Add(const GURL& origin, const AuthCredentials& credentials) {
+ DCHECK(origin.SchemeIs("ftp"));
+ DCHECK_EQ(origin.GetOrigin(), origin);
+
+ Entry* entry = Lookup(origin);
+ if (entry) {
+ entry->credentials = credentials;
+ } else {
+ entries_.push_front(Entry(origin, credentials));
+
+ // Prevent unbound memory growth of the cache.
+ if (entries_.size() > kMaxEntries)
+ entries_.pop_back();
+ }
+}
+
+void FtpAuthCache::Remove(const GURL& origin,
+ const AuthCredentials& credentials) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin == origin && it->credentials.Equals(credentials)) {
+ entries_.erase(it);
+ DCHECK(!Lookup(origin));
+ return;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_auth_cache.h b/chromium/net/ftp/ftp_auth_cache.h
new file mode 100644
index 00000000000..526b3588782
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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_FTP_FTP_AUTH_CACHE_H_
+#define NET_FTP_FTP_AUTH_CACHE_H_
+
+#include <list>
+
+#include "net/base/auth.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// The FtpAuthCache class is a simple cache structure to store authentication
+// information for ftp. Provides lookup, insertion, and deletion of entries.
+// The parameter for doing lookups, insertions, and deletions is a GURL of the
+// server's address (not a full URL with path, since FTP auth isn't per path).
+// For example:
+// GURL("ftp://myserver") -- OK (implied port of 21)
+// GURL("ftp://myserver:21") -- OK
+// GURL("ftp://myserver/PATH") -- WRONG, paths not allowed
+class NET_EXPORT_PRIVATE FtpAuthCache {
+ public:
+ // Maximum number of entries we allow in the cache.
+ static const size_t kMaxEntries;
+
+ struct Entry {
+ Entry(const GURL& origin, const AuthCredentials& credentials);
+ ~Entry();
+
+ GURL origin;
+ AuthCredentials credentials;
+ };
+
+ FtpAuthCache();
+ ~FtpAuthCache();
+
+ // Return Entry corresponding to given |origin| or NULL if not found.
+ Entry* Lookup(const GURL& origin);
+
+ // Add an entry for |origin| to the cache using |credentials|. If there is
+ // already an entry for |origin|, it will be overwritten.
+ void Add(const GURL& origin, const AuthCredentials& credentials);
+
+ // Remove the entry for |origin| from the cache, if one exists and matches
+ // |credentials|.
+ void Remove(const GURL& origin, const AuthCredentials& credentials);
+
+ private:
+ typedef std::list<Entry> EntryList;
+
+ // Internal representation of cache, an STL list. This makes lookups O(n),
+ // but we expect n to be very low.
+ EntryList entries_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_AUTH_CACHE_H_
diff --git a/chromium/net/ftp/ftp_auth_cache_unittest.cc b/chromium/net/ftp/ftp_auth_cache_unittest.cc
new file mode 100644
index 00000000000..153d08be49f
--- /dev/null
+++ b/chromium/net/ftp/ftp_auth_cache_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2011 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/ftp/ftp_auth_cache.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using net::FtpAuthCache;
+
+namespace {
+
+const base::string16 kBogus(ASCIIToUTF16("bogus"));
+const base::string16 kOthername(ASCIIToUTF16("othername"));
+const base::string16 kOtherword(ASCIIToUTF16("otherword"));
+const base::string16 kPassword(ASCIIToUTF16("password"));
+const base::string16 kPassword1(ASCIIToUTF16("password1"));
+const base::string16 kPassword2(ASCIIToUTF16("password2"));
+const base::string16 kPassword3(ASCIIToUTF16("password3"));
+const base::string16 kUsername(ASCIIToUTF16("username"));
+const base::string16 kUsername1(ASCIIToUTF16("username1"));
+const base::string16 kUsername2(ASCIIToUTF16("username2"));
+const base::string16 kUsername3(ASCIIToUTF16("username3"));
+
+} // namespace
+
+TEST(FtpAuthCacheTest, LookupAddRemove) {
+ FtpAuthCache cache;
+
+ GURL origin1("ftp://foo1");
+ GURL origin2("ftp://foo2");
+
+ // Lookup non-existent entry.
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+
+ // Add entry for origin1.
+ cache.Add(origin1, net::AuthCredentials(kUsername1, kPassword1));
+ FtpAuthCache::Entry* entry1 = cache.Lookup(origin1);
+ ASSERT_TRUE(entry1);
+ EXPECT_EQ(origin1, entry1->origin);
+ EXPECT_EQ(kUsername1, entry1->credentials.username());
+ EXPECT_EQ(kPassword1, entry1->credentials.password());
+
+ // Add an entry for origin2.
+ cache.Add(origin2, net::AuthCredentials(kUsername2, kPassword2));
+ FtpAuthCache::Entry* entry2 = cache.Lookup(origin2);
+ ASSERT_TRUE(entry2);
+ EXPECT_EQ(origin2, entry2->origin);
+ EXPECT_EQ(kUsername2, entry2->credentials.username());
+ EXPECT_EQ(kPassword2, entry2->credentials.password());
+
+ // The original entry1 should still be there.
+ EXPECT_EQ(entry1, cache.Lookup(origin1));
+
+ // Overwrite the entry for origin1.
+ cache.Add(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ FtpAuthCache::Entry* entry3 = cache.Lookup(origin1);
+ ASSERT_TRUE(entry3);
+ EXPECT_EQ(origin1, entry3->origin);
+ EXPECT_EQ(kUsername3, entry3->credentials.username());
+ EXPECT_EQ(kPassword3, entry3->credentials.password());
+
+ // Remove entry of origin1.
+ cache.Remove(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+
+ // Remove non-existent entry.
+ cache.Remove(origin1, net::AuthCredentials(kUsername3, kPassword3));
+ EXPECT_TRUE(cache.Lookup(origin1) == NULL);
+}
+
+// Check that if the origin differs only by port number, it is considered
+// a separate origin.
+TEST(FtpAuthCacheTest, LookupWithPort) {
+ FtpAuthCache cache;
+
+ GURL origin1("ftp://foo:80");
+ GURL origin2("ftp://foo:21");
+
+ cache.Add(origin1, net::AuthCredentials(kUsername, kPassword));
+ cache.Add(origin2, net::AuthCredentials(kUsername, kPassword));
+
+ EXPECT_NE(cache.Lookup(origin1), cache.Lookup(origin2));
+}
+
+TEST(FtpAuthCacheTest, NormalizedKey) {
+ // GURL is automatically canonicalized. Hence the following variations in
+ // url format should all map to the same entry (case insensitive host,
+ // default port of 21).
+
+ FtpAuthCache cache;
+
+ // Add.
+ cache.Add(GURL("ftp://HoSt:21"), net::AuthCredentials(kUsername, kPassword));
+
+ // Lookup.
+ FtpAuthCache::Entry* entry1 = cache.Lookup(GURL("ftp://HoSt:21"));
+ ASSERT_TRUE(entry1);
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host:21")));
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host")));
+
+ // Overwrite.
+ cache.Add(GURL("ftp://host"), net::AuthCredentials(kOthername, kOtherword));
+ FtpAuthCache::Entry* entry2 = cache.Lookup(GURL("ftp://HoSt:21"));
+ ASSERT_TRUE(entry2);
+ EXPECT_EQ(GURL("ftp://host"), entry2->origin);
+ EXPECT_EQ(kOthername, entry2->credentials.username());
+ EXPECT_EQ(kOtherword, entry2->credentials.password());
+
+ // Remove
+ cache.Remove(GURL("ftp://HOsT"),
+ net::AuthCredentials(kOthername, kOtherword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == NULL);
+}
+
+TEST(FtpAuthCacheTest, OnlyRemoveMatching) {
+ FtpAuthCache cache;
+
+ cache.Add(GURL("ftp://host"), net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
+
+ // Auth data doesn't match, shouldn't remove.
+ cache.Remove(GURL("ftp://host"), net::AuthCredentials(kBogus, kBogus));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
+
+ // Auth data matches, should remove.
+ cache.Remove(GURL("ftp://host"), net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == NULL);
+}
+
+TEST(FtpAuthCacheTest, EvictOldEntries) {
+ FtpAuthCache cache;
+
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
+ cache.Add(GURL("ftp://host" + base::IntToString(i)),
+ net::AuthCredentials(kUsername, kPassword));
+ }
+
+ // No entries should be evicted before reaching the limit.
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::IntToString(i))));
+ }
+
+ // Adding one entry should cause eviction of the first entry.
+ cache.Add(GURL("ftp://last_host"),
+ net::AuthCredentials(kUsername, kPassword));
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host0")) == NULL);
+
+ // Remaining entries should not get evicted.
+ for (size_t i = 1; i < FtpAuthCache::kMaxEntries; i++) {
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::IntToString(i))));
+ }
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://last_host")));
+}
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer.cc b/chromium/net/ftp/ftp_ctrl_response_buffer.cc
new file mode 100644
index 00000000000..f7b14678095
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2011 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/ftp/ftp_ctrl_response_buffer.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// static
+const int FtpCtrlResponse::kInvalidStatusCode = -1;
+
+FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}
+
+FtpCtrlResponse::~FtpCtrlResponse() {}
+
+FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const BoundNetLog& net_log)
+ : multiline_(false),
+ net_log_(net_log) {
+}
+
+FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() {}
+
+int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
+ buffer_.append(data, data_length);
+ ExtractFullLinesFromBuffer();
+
+ while (!lines_.empty()) {
+ ParsedLine line = lines_.front();
+ lines_.pop();
+
+ if (multiline_) {
+ if (!line.is_complete || line.status_code != response_buf_.status_code) {
+ line_buf_.append(line.raw_text);
+ continue;
+ }
+
+ response_buf_.lines.push_back(line_buf_);
+
+ line_buf_ = line.status_text;
+ DCHECK_EQ(line.status_code, response_buf_.status_code);
+
+ if (!line.is_multiline) {
+ response_buf_.lines.push_back(line_buf_);
+ responses_.push(response_buf_);
+
+ // Prepare to handle following lines.
+ response_buf_ = FtpCtrlResponse();
+ line_buf_.clear();
+ multiline_ = false;
+ }
+ } else {
+ if (!line.is_complete)
+ return ERR_INVALID_RESPONSE;
+
+ response_buf_.status_code = line.status_code;
+ if (line.is_multiline) {
+ line_buf_ = line.status_text;
+ multiline_ = true;
+ } else {
+ response_buf_.lines.push_back(line.status_text);
+ responses_.push(response_buf_);
+
+ // Prepare to handle following lines.
+ response_buf_ = FtpCtrlResponse();
+ line_buf_.clear();
+ }
+ }
+ }
+
+ return OK;
+}
+
+namespace {
+
+base::Value* NetLogFtpCtrlResponseCallback(const FtpCtrlResponse* response,
+ NetLog::LogLevel log_level) {
+ base::ListValue* lines = new base::ListValue();
+ lines->AppendStrings(response->lines);
+
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("status_code", response->status_code);
+ dict->Set("lines", lines);
+ return dict;
+}
+
+} // namespace
+
+FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
+ FtpCtrlResponse result = responses_.front();
+ responses_.pop();
+
+ net_log_.AddEvent(NetLog::TYPE_FTP_CONTROL_RESPONSE,
+ base::Bind(&NetLogFtpCtrlResponseCallback, &result));
+
+ return result;
+}
+
+FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
+ : has_status_code(false),
+ is_multiline(false),
+ is_complete(false),
+ status_code(FtpCtrlResponse::kInvalidStatusCode) {
+}
+
+// static
+FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
+ const std::string& line) {
+ ParsedLine result;
+
+ if (line.length() >= 3) {
+ if (base::StringToInt(base::StringPiece(line.begin(), line.begin() + 3),
+ &result.status_code))
+ result.has_status_code = (100 <= result.status_code &&
+ result.status_code <= 599);
+ if (result.has_status_code && line.length() >= 4 && line[3] == ' ') {
+ result.is_complete = true;
+ } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') {
+ result.is_complete = true;
+ result.is_multiline = true;
+ }
+ }
+
+ if (result.is_complete) {
+ result.status_text = line.substr(4);
+ } else {
+ result.status_text = line;
+ }
+
+ result.raw_text = line;
+
+ return result;
+}
+
+void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() {
+ int cut_pos = 0;
+ for (size_t i = 0; i < buffer_.length(); i++) {
+ if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
+ lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
+ cut_pos = i + 1;
+ }
+ }
+ buffer_.erase(0, cut_pos);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer.h b/chromium/net/ftp/ftp_ctrl_response_buffer.h
new file mode 100644
index 00000000000..fa5c03115b3
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 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_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
+#define NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
+
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+struct NET_EXPORT_PRIVATE FtpCtrlResponse {
+ static const int kInvalidStatusCode;
+
+ FtpCtrlResponse();
+ ~FtpCtrlResponse();
+
+ int status_code; // Three-digit status code.
+ std::vector<std::string> lines; // Response lines, without CRLFs.
+};
+
+class NET_EXPORT_PRIVATE FtpCtrlResponseBuffer {
+ public:
+ FtpCtrlResponseBuffer(const BoundNetLog& net_log);
+ ~FtpCtrlResponseBuffer();
+
+ // Called when data is received from the control socket. Returns error code.
+ int ConsumeData(const char* data, int data_length);
+
+ bool ResponseAvailable() const {
+ return !responses_.empty();
+ }
+
+ // Returns the next response. It is an error to call this function
+ // unless ResponseAvailable returns true.
+ FtpCtrlResponse PopResponse();
+
+ private:
+ struct ParsedLine {
+ ParsedLine();
+
+ // Indicates that this line begins with a valid 3-digit status code.
+ bool has_status_code;
+
+ // Indicates that this line has the dash (-) after the code, which
+ // means a multiline response.
+ bool is_multiline;
+
+ // Indicates that this line could be parsed as a complete and valid
+ // response line, without taking into account preceding lines (which
+ // may change its meaning into a continuation of the previous line).
+ bool is_complete;
+
+ // Part of response parsed as status code.
+ int status_code;
+
+ // Part of response parsed as status text.
+ std::string status_text;
+
+ // Text before parsing, without terminating CRLF.
+ std::string raw_text;
+ };
+
+ static ParsedLine ParseLine(const std::string& line);
+
+ void ExtractFullLinesFromBuffer();
+
+ // We keep not-yet-parsed data in a string buffer.
+ std::string buffer_;
+
+ std::queue<ParsedLine> lines_;
+
+ // True if we are in the middle of parsing a multi-line response.
+ bool multiline_;
+
+ // When parsing a multiline response, we don't know beforehand if a line
+ // will have a continuation. So always store last line of multiline response
+ // so we can append the continuation to it.
+ std::string line_buf_;
+
+ // Keep the response data while we add all lines to it.
+ FtpCtrlResponse response_buf_;
+
+ // As we read full responses (possibly multiline), we add them to the queue.
+ std::queue<FtpCtrlResponse> responses_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpCtrlResponseBuffer);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
diff --git a/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc b/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc
new file mode 100644
index 00000000000..f74b28724a9
--- /dev/null
+++ b/chromium/net/ftp/ftp_ctrl_response_buffer_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2009 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/ftp/ftp_ctrl_response_buffer.h"
+
+#include <string.h>
+
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class FtpCtrlResponseBufferTest : public testing::Test {
+ public:
+ FtpCtrlResponseBufferTest() : buffer_(net::BoundNetLog()) {
+ }
+
+ protected:
+ int PushDataToBuffer(const char* data) {
+ return buffer_.ConsumeData(data, strlen(data));
+ }
+
+ net::FtpCtrlResponseBuffer buffer_;
+};
+
+TEST_F(FtpCtrlResponseBufferTest, Basic) {
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("200 Status Text\r\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(200, response.status_code);
+ ASSERT_EQ(1U, response.lines.size());
+ EXPECT_EQ("Status Text", response.lines[0]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, Chunks) {
+ EXPECT_EQ(net::OK, PushDataToBuffer("20"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(net::OK, PushDataToBuffer("0 Status"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(net::OK, PushDataToBuffer(" Text"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(net::OK, PushDataToBuffer("\r"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(net::OK, PushDataToBuffer("\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(200, response.status_code);
+ ASSERT_EQ(1U, response.lines.size());
+ EXPECT_EQ("Status Text", response.lines[0]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, Continuation) {
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-SecondLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230 LastLine\r\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(230, response.status_code);
+ ASSERT_EQ(3U, response.lines.size());
+ EXPECT_EQ("FirstLine", response.lines[0]);
+ EXPECT_EQ("SecondLine", response.lines[1]);
+ EXPECT_EQ("LastLine", response.lines[2]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, MultilineContinuation) {
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("Continued\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-SecondLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("215 Continued\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230 LastLine\r\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(230, response.status_code);
+ ASSERT_EQ(3U, response.lines.size());
+ EXPECT_EQ("FirstLineContinued", response.lines[0]);
+ EXPECT_EQ("SecondLine215 Continued", response.lines[1]);
+ EXPECT_EQ("LastLine", response.lines[2]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, MultilineContinuationZeroLength) {
+ // For the corner case from bug 29322.
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("example.com\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230 LastLine\r\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(230, response.status_code);
+ ASSERT_EQ(2U, response.lines.size());
+ EXPECT_EQ("example.com", response.lines[0]);
+ EXPECT_EQ("LastLine", response.lines[1]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, SimilarContinuation) {
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ // Notice the space at the start of the line. It should be recognized
+ // as a continuation, and not the last line.
+ EXPECT_EQ(net::OK, PushDataToBuffer(" 230 Continued\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230 TrueLastLine\r\n"));
+ EXPECT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(230, response.status_code);
+ ASSERT_EQ(2U, response.lines.size());
+ EXPECT_EQ("FirstLine 230 Continued", response.lines[0]);
+ EXPECT_EQ("TrueLastLine", response.lines[1]);
+}
+
+// The nesting of multi-line responses is not allowed.
+TEST_F(FtpCtrlResponseBufferTest, NoNesting) {
+ EXPECT_EQ(net::OK, PushDataToBuffer("230-FirstLine\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("300-Continuation\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("300 Still continuation\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+
+ EXPECT_EQ(net::OK, PushDataToBuffer("230 Real End\r\n"));
+ ASSERT_TRUE(buffer_.ResponseAvailable());
+
+ net::FtpCtrlResponse response = buffer_.PopResponse();
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+ EXPECT_EQ(230, response.status_code);
+ ASSERT_EQ(2U, response.lines.size());
+ EXPECT_EQ("FirstLine300-Continuation300 Still continuation",
+ response.lines[0]);
+ EXPECT_EQ("Real End", response.lines[1]);
+}
+
+TEST_F(FtpCtrlResponseBufferTest, NonNumericResponse) {
+ EXPECT_EQ(net::ERR_INVALID_RESPONSE, PushDataToBuffer("Non-numeric\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+}
+
+TEST_F(FtpCtrlResponseBufferTest, OutOfRangeResponse) {
+ EXPECT_EQ(net::ERR_INVALID_RESPONSE, PushDataToBuffer("777 OK?\r\n"));
+ EXPECT_FALSE(buffer_.ResponseAvailable());
+}
+
+} // namespace
diff --git a/chromium/net/ftp/ftp_directory_listing_parser.cc b/chromium/net/ftp/ftp_directory_listing_parser.cc
new file mode 100644
index 00000000000..03b77bb8205
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/i18n/icu_encoding_detection.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
+#include "net/ftp/ftp_directory_listing_parser_os2.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
+#include "net/ftp/ftp_server_type_histograms.h"
+
+namespace net {
+
+namespace {
+
+// Fills in |raw_name| for all |entries| using |encoding|. Returns network
+// error code.
+int FillInRawName(const std::string& encoding,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < entries->size(); i++) {
+ if (!base::UTF16ToCodepage(entries->at(i).name, encoding.c_str(),
+ base::OnStringConversionError::FAIL,
+ &entries->at(i).raw_name)) {
+ return ERR_ENCODING_CONVERSION_FAILED;
+ }
+ }
+
+ return OK;
+}
+
+// Parses |text| as an FTP directory listing. Fills in |entries|
+// and |server_type| and returns network error code.
+int ParseListing(const base::string16& text,
+ const base::string16& newline_separator,
+ const std::string& encoding,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries,
+ FtpServerType* server_type) {
+ std::vector<base::string16> lines;
+ base::SplitStringUsingSubstr(text, newline_separator, &lines);
+
+ struct {
+ base::Callback<bool(void)> callback;
+ FtpServerType server_type;
+ } parsers[] = {
+ {
+ base::Bind(&ParseFtpDirectoryListingLs, lines, current_time, entries),
+ SERVER_LS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingWindows, lines, entries),
+ SERVER_WINDOWS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingVms, lines, entries),
+ SERVER_VMS
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingNetware,
+ lines, current_time, entries),
+ SERVER_NETWARE
+ },
+ {
+ base::Bind(&ParseFtpDirectoryListingOS2, lines, entries),
+ SERVER_OS2
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(parsers); i++) {
+ entries->clear();
+ if (parsers[i].callback.Run()) {
+ *server_type = parsers[i].server_type;
+ return FillInRawName(encoding, entries);
+ }
+ }
+
+ entries->clear();
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
+}
+
+// Detects encoding of |text| and parses it as an FTP directory listing.
+// Fills in |entries| and |server_type| and returns network error code.
+int DecodeAndParse(const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries,
+ FtpServerType* server_type) {
+ const char* kNewlineSeparators[] = { "\n", "\r\n" };
+
+ std::vector<std::string> encodings;
+ if (!base::DetectAllEncodings(text, &encodings))
+ return ERR_ENCODING_DETECTION_FAILED;
+
+ // Use first encoding that can be used to decode the text.
+ for (size_t i = 0; i < encodings.size(); i++) {
+ base::string16 converted_text;
+ if (base::CodepageToUTF16(text,
+ encodings[i].c_str(),
+ base::OnStringConversionError::FAIL,
+ &converted_text)) {
+ for (size_t j = 0; j < arraysize(kNewlineSeparators); j++) {
+ int rv = ParseListing(converted_text,
+ ASCIIToUTF16(kNewlineSeparators[j]),
+ encodings[i],
+ current_time,
+ entries,
+ server_type);
+ if (rv == OK)
+ return rv;
+ }
+ }
+ }
+
+ entries->clear();
+ *server_type = SERVER_UNKNOWN;
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
+}
+
+} // namespace
+
+FtpDirectoryListingEntry::FtpDirectoryListingEntry()
+ : type(UNKNOWN),
+ size(-1) {
+}
+
+int ParseFtpDirectoryListing(const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ FtpServerType server_type = SERVER_UNKNOWN;
+ int rv = DecodeAndParse(text, current_time, entries, &server_type);
+ UpdateFtpServerTypeHistograms(server_type);
+ return rv;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser.h b/chromium/net/ftp/ftp_directory_listing_parser.h
new file mode 100644
index 00000000000..d2dd677bb09
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry {
+ enum Type {
+ UNKNOWN,
+ FILE,
+ DIRECTORY,
+ SYMLINK,
+ };
+
+ FtpDirectoryListingEntry();
+
+ Type type;
+ base::string16 name; // Name (UTF-16-encoded).
+ std::string raw_name; // Name in original character encoding.
+ int64 size; // File size, in bytes. -1 if not applicable.
+
+ // Last modified time, in local time zone.
+ base::Time last_modified;
+};
+
+// Parses an FTP directory listing |text|. On success fills in |entries|.
+// Returns network error code.
+NET_EXPORT int ParseFtpDirectoryListing(
+ const std::string& text,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls.cc b/chromium/net/ftp/ftp_directory_listing_parser_ls.cc
new file mode 100644
index 00000000000..41e29b60a9b
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 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.
+
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool TwoColumnDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format YYYY-MM-DD.
+ std::vector<base::string16> date_parts;
+ base::SplitString(date, '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.year))
+ return false;
+ if (!base::StringToInt(date_parts[1], &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.day_of_month))
+ return false;
+
+ // Time should be in format HH:MM
+ if (time.length() != 5)
+ return false;
+
+ std::vector<base::string16> time_parts;
+ base::SplitString(time, ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+ if (!time_exploded.HasValidValues())
+ return false;
+
+ // We don't know the time zone of the server, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// Returns the column index of the end of the date listing and detected
+// last modification time.
+bool DetectColumnOffsetSizeAndModificationTime(
+ const std::vector<base::string16>& columns,
+ const base::Time& current_time,
+ size_t* offset,
+ base::string16* size,
+ base::Time* modification_time) {
+ // The column offset can be arbitrarily large if some fields
+ // like owner or group name contain spaces. Try offsets from left to right
+ // and use the first one that matches a date listing.
+ //
+ // Here is how a listing line should look like. A star ("*") indicates
+ // a required field:
+ //
+ // * 1. permission listing
+ // 2. number of links (optional)
+ // * 3. owner name (may contain spaces)
+ // 4. group name (optional, may contain spaces)
+ // * 5. size in bytes
+ // * 6. month
+ // * 7. day of month
+ // * 8. year or time <-- column_offset will be the index of this column
+ // 9. file name (optional, may contain spaces)
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (net::FtpUtil::LsDateListingToTime(columns[i - 2],
+ columns[i - 1],
+ columns[i],
+ current_time,
+ modification_time)) {
+ *size = columns[i - 3];
+ *offset = i;
+ return true;
+ }
+ }
+
+ // Some FTP listings have swapped the "month" and "day of month" columns
+ // (for example Russian listings). We try to recognize them only after making
+ // sure no column offset works above (this is a more strict way).
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (net::FtpUtil::LsDateListingToTime(columns[i - 1],
+ columns[i - 2],
+ columns[i],
+ current_time,
+ modification_time)) {
+ *size = columns[i - 3];
+ *offset = i;
+ return true;
+ }
+ }
+
+ // Some FTP listings use a different date format.
+ for (size_t i = 5U; i < columns.size(); i++) {
+ if (TwoColumnDateListingToTime(columns[i - 1],
+ columns[i],
+ modification_time)) {
+ *size = columns[i - 2];
+ *offset = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
+namespace net {
+
+bool ParseFtpDirectoryListingLs(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ // True after we have received a "total n" listing header, where n is an
+ // integer. Only one such header is allowed per listing.
+ bool received_total_line = false;
+
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Some FTP servers put a "total n" line at the beginning of the listing
+ // (n is an integer). Allow such a line, but only once, and only if it's
+ // the first non-empty line. Do not match the word exactly, because it may
+ // be in different languages (at least English and German have been seen
+ // in the field).
+ if (columns.size() == 2 && !received_total_line) {
+ received_total_line = true;
+
+ int64 total_number;
+ if (!base::StringToInt64(columns[1], &total_number))
+ return false;
+ if (total_number < 0)
+ return false;
+
+ continue;
+ }
+
+ FtpDirectoryListingEntry entry;
+
+ size_t column_offset;
+ base::string16 size;
+ if (!DetectColumnOffsetSizeAndModificationTime(columns,
+ current_time,
+ &column_offset,
+ &size,
+ &entry.last_modified)) {
+ // Some servers send a message in one of the first few lines.
+ // All those messages have in common is the string ".:",
+ // where "." means the current directory, and ":" separates it
+ // from the rest of the message, which may be empty.
+ if (lines[i].find(ASCIIToUTF16(".:")) != base::string16::npos)
+ continue;
+
+ return false;
+ }
+
+ // Do not check "validity" of the permission listing. It's quirky,
+ // and some servers send garbage here while other parts of the line are OK.
+
+ if (!columns[0].empty() && columns[0][0] == 'l') {
+ entry.type = FtpDirectoryListingEntry::SYMLINK;
+ } else if (!columns[0].empty() && columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ }
+
+ if (!base::StringToInt64(size, &entry.size)) {
+ // Some FTP servers do not separate owning group name from file size,
+ // like "group1234". We still want to display the file name for that
+ // entry, but can't really get the size (What if the group is named
+ // "group1", and the size is in fact 234? We can't distinguish between
+ // that and "group" with size 1234). Use a dummy value for the size.
+ // TODO(phajdan.jr): Use a value that means "unknown" instead of 0 bytes.
+ entry.size = 0;
+ }
+ if (entry.size < 0) {
+ // Some FTP servers have bugs that cause them to display the file size
+ // as negative. They're most likely big files like DVD ISO images.
+ // We still want to display them, so just say the real file size
+ // is unknown.
+ entry.size = -1;
+ }
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ if (column_offset == columns.size() - 1) {
+ // If the end of the date listing is the last column, there is no file
+ // name. Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so we ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i],
+ column_offset + 1);
+
+ if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
+ base::string16::size_type pos = entry.name.rfind(ASCIIToUTF16(" -> "));
+
+ // We don't require the " -> " to be present. Some FTP servers don't send
+ // the symlink target, possibly for security reasons.
+ if (pos != base::string16::npos)
+ entry.name = entry.name.substr(0, pos);
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls.h b/chromium/net/ftp/ftp_directory_listing_parser_ls.h
new file mode 100644
index 00000000000..9f7020f8ac9
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses "ls -l" FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingLs(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
new file mode 100644
index 00000000000..a7389443500
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserLsTest;
+
+TEST_F(FtpDirectoryListingParserLsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
+ FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
+ 1994, 5, 15, 18, 11 },
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
+ FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
+ 1994, 10, 12, 13, 37 },
+ { "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2007, 2, 20, 0, 0 },
+ { "drwxr-xr-x 4 (?) (?) 4096 Apr 8 2007 jigdo",
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
+ 2007, 4, 8, 0, 0 },
+ { "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
+ 1994, 7, 1, 2, 15 },
+ { "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
+ 2009, 5, 18, 0, 0 },
+ { "d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming",
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
+ 1993, 12, 8, 15, 54 },
+ { "drwxrwxrwx 1 owner group 1024 Sep 13 0:30 audio",
+ FtpDirectoryListingEntry::DIRECTORY, "audio", -1,
+ 1994, 9, 13, 0, 30 },
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "-rw-r--r-- 1 ftp ftp -528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", -1,
+ 2007, 11, 1, 0, 0 },
+
+ // Tests for the wu-ftpd variant:
+ { "drwxr-xr-x 2 sys 512 Mar 27 2009 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2009, 3, 27, 0, 0 },
+ { "lrwxrwxrwx 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
+ 2008, 9, 18, 0, 0 },
+ { "drwxr-xr-x (?) (?) 4096 Apr 8 2007 jigdo",
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
+ 2007, 4, 8, 0, 0 },
+ { "-rw-r--r-- 2 3 3447432 May 18 2009 Foo - Manual.pdf",
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
+ 2009, 5, 18, 0, 0 },
+
+ // Tests for "ls -l" style listings sent by an OS/2 server (FtpServer):
+ { "-r--r--r-- 1 ftp -A--- 13274 Mar 1 2006 UpTime.exe",
+ FtpDirectoryListingEntry::FILE, "UpTime.exe", 13274,
+ 2006, 3, 1, 0, 0 },
+ { "dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels",
+ FtpDirectoryListingEntry::DIRECTORY, "kernels", -1,
+ 1993, 11, 17, 17, 8 },
+
+ // Tests for "ls -l" style listing sent by Xplain FTP Server.
+ { "drwxr-xr-x folder 0 Jul 17 2006 online",
+ FtpDirectoryListingEntry::DIRECTORY, "online", -1,
+ 2006, 7, 17, 0, 0 },
+
+ // Tests for "ls -l" style listing with owning group name
+ // not separated from file size (http://crbug.com/58963).
+ { "-rw-r--r-- 1 ftpadmin ftpadmin125435904 Apr 9 2008 .pureftpd-upload",
+ FtpDirectoryListingEntry::FILE, ".pureftpd-upload", 0,
+ 2008, 4, 9, 0, 0 },
+
+ // Tests for "ls -l" style listing with number of links
+ // not separated from permission listing (http://crbug.com/70394).
+ { "drwxr-xr-x1732 266 111 90112 Jun 21 2001 .rda_2",
+ FtpDirectoryListingEntry::DIRECTORY, ".rda_2", -1,
+ 2001, 6, 21, 0, 0 },
+
+ // Tests for "ls -l" style listing with group name containing spaces.
+ { "drwxrwxr-x 3 %%%% Domain Users 4096 Dec 9 2009 %%%%%",
+ FtpDirectoryListingEntry::DIRECTORY, "%%%%%", -1,
+ 2009, 12, 9, 0, 0 },
+
+ // Tests for "ls -l" style listing in Russian locale (note the swapped
+ // parts order: the day of month is the first, before month).
+ { "-rwxrwxr-x 1 ftp ftp 123 23 \xd0\xbc\xd0\xb0\xd0\xb9 2011 test",
+ FtpDirectoryListingEntry::FILE, "test", 123,
+ 2011, 5, 23, 0, 0 },
+ { "drwxrwxr-x 1 ftp ftp 4096 19 \xd0\xbe\xd0\xba\xd1\x82 2011 dir",
+ FtpDirectoryListingEntry::DIRECTORY, "dir", -1,
+ 2011, 10, 19, 0, 0 },
+
+ // Plan9 sends entry type "a" for append-only files.
+ { "ar-xr-xr-x 2 none none 512 Apr 26 17:52 plan9",
+ FtpDirectoryListingEntry::FILE, "plan9", 512,
+ 1994, 4, 26, 17, 52 },
+
+ // Hylafax sends a shorter permission listing.
+ { "drwxrwx 2 10 4096 Jul 28 02:41 tmp",
+ FtpDirectoryListingEntry::DIRECTORY, "tmp", -1,
+ 1994, 7, 28, 2, 41 },
+
+ // Completely different date format (YYYY-MM-DD).
+ { "drwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
+ FtpDirectoryListingEntry::DIRECTORY, "notas_servico", -1,
+ 2012, 2, 7, 0, 31 },
+ { "-rwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
+ FtpDirectoryListingEntry::FILE, "notas_servico", 4096,
+ 2012, 2, 7, 0, 31 },
+
+ // Weird permission bits.
+ { "drwx--l--- 2 0 10 512 Dec 22 1994 swetzel",
+ FtpDirectoryListingEntry::DIRECTORY, "swetzel", -1,
+ 1994, 12, 22, 0, 0 },
+
+ // Garbage in date (but still parseable).
+ { "lrw-rw-rw- 1 user group 542 "
+ "/t11/member/incomingFeb 8 2007 "
+ "Shortcut to incoming.lnk -> /t11/member/incoming",
+ FtpDirectoryListingEntry::SYMLINK, "Shortcut to incoming.lnk", -1,
+ 2007, 2, 8, 0, 0 },
+
+ // Garbage in permissions (with no effect on other bits).
+ // Also test multiple "columns" resulting from the garbage.
+ { "garbage 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "gar bage 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ { "g a r b a g e 1 ftp ftp 528 Nov 01 2007 README",
+ FtpDirectoryListingEntry::FILE, "README", 528,
+ 2007, 11, 1, 0, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
+ GetSingleLineTestCase(good_cases[i].input),
+ GetMockCurrentTime(),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserLsTest, Ignored) {
+ const char* ignored_cases[] = {
+ "drwxr-xr-x 2 0 0 4096 Mar 18 2007 ", // http://crbug.com/60065
+
+ "ftpd: .: Permission denied",
+ "ftpd-BSD: .: Permission denied",
+ "ls: .: EDC5111I Permission denied.",
+
+ // Tests important for security: verify that after we detect the column
+ // offset we don't try to access invalid memory on malformed input.
+ "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11",
+ "drwxr-xr-x 3 ftp 4096 May 15 18:11",
+ "drwxr-xr-x folder 0 May 15 18:11",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
+ GetSingleLineTestCase(ignored_cases[i]),
+ GetMockCurrentTime(),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserLsTest, Bad) {
+ const char* bad_cases[] = {
+ " foo",
+ "garbage",
+ "-rw-r--r-- ftp ftp",
+ "-rw-r--r-- ftp ftp 528 Foo 01 2007 README",
+ "-rw-r--r-- 1 ftp ftp",
+ "-rw-r--r-- 1 ftp ftp 528 Foo 01 2007 README",
+ "drwxrwxrwx 1 owner group 1024 Sep 13 0:3 audio",
+
+ // Invalid month value (30).
+ "drwxrwxrwx 2 root root 4096 2012-30-07 00:31 notas_servico",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingLs(GetSingleLineTestCase(bad_cases[i]),
+ GetMockCurrentTime(),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware.cc b/chromium/net/ftp/ftp_directory_listing_parser_netware.cc
new file mode 100644
index 00000000000..2306513fa60
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_netware.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace {
+
+bool LooksLikeNetwarePermissionsListing(const base::string16& text) {
+ if (text.length() != 10)
+ return false;
+
+ if (text[0] != '[' || text[9] != ']')
+ return false;
+ return (text[1] == 'R' || text[1] == '-') &&
+ (text[2] == 'W' || text[2] == '-') &&
+ (text[3] == 'C' || text[3] == '-') &&
+ (text[4] == 'E' || text[4] == '-') &&
+ (text[5] == 'A' || text[5] == '-') &&
+ (text[6] == 'F' || text[6] == '-') &&
+ (text[7] == 'M' || text[7] == '-') &&
+ (text[8] == 'S' || text[8] == '-');
+}
+
+} // namespace
+
+namespace net {
+
+bool ParseFtpDirectoryListingNetware(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ if (!lines.empty() && !StartsWith(lines[0], ASCIIToUTF16("total "), true))
+ return false;
+
+ for (size_t i = 1U; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ if (columns.size() < 8)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+
+ if (columns[0].length() != 1)
+ return false;
+ if (columns[0][0] == 'd') {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ } else if (columns[0][0] == '-') {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ } else {
+ return false;
+ }
+
+ // Note: on older Netware systems the permissions listing is in the same
+ // column as the entry type (just there is no space between them). We do not
+ // support the older format here for simplicity.
+ if (!LooksLikeNetwarePermissionsListing(columns[1]))
+ return false;
+
+ if (!base::StringToInt64(columns[3], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+
+ // Netware uses the same date listing format as Unix "ls -l".
+ if (!FtpUtil::LsDateListingToTime(columns[4], columns[5], columns[6],
+ current_time, &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 7);
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware.h b/chromium/net/ftp/ftp_directory_listing_parser_netware.h
new file mode 100644
index 00000000000..a70e21718d7
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses Netware FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingNetware(
+ const std::vector<base::string16>& lines,
+ const base::Time& current_time,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_NETWARE_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
new file mode 100644
index 00000000000..4863713ca86
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_netware_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser_netware.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserNetwareTest;
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub",
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
+ 2004, 1, 29, 0, 0 },
+ { "- [RW------] ftpadmin 123 Nov 11 18:25 afile",
+ FtpDirectoryListingEntry::FILE, "afile", 123,
+ 1994, 11, 11, 18, 25 },
+ { "d [RWCEAFMS] daniel 512 May 17 2010 NVP anyagok",
+ FtpDirectoryListingEntry::DIRECTORY, "NVP anyagok", -1,
+ 2010, 5, 17, 0, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase(good_cases[i].input));
+
+ // The parser requires a "total n" line before accepting regular input.
+ lines.insert(lines.begin(), ASCIIToUTF16("total 1"));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingNetware(lines,
+ GetMockCurrentTime(),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserNetwareTest, Bad) {
+ const char* bad_cases[] = {
+ " foo",
+ "garbage",
+ "d [] ftpadmin 512 Jan 29 2004 pub",
+ "d [XGARBAGE] ftpadmin 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] 512 Jan 29 2004 pub",
+ "d [RWCEAFMS] ftpadmin -1 Jan 29 2004 pub",
+ "l [RW------] ftpadmin 512 Jan 29 2004 pub",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<base::string16> lines(GetSingleLineTestCase(bad_cases[i]));
+
+ // The parser requires a "total n" line before accepting regular input.
+ lines.insert(lines.begin(), ASCIIToUTF16("total 1"));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingNetware(lines,
+ GetMockCurrentTime(),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2.cc b/chromium/net/ftp/ftp_directory_listing_parser_os2.cc
new file mode 100644
index 00000000000..9a0cce53c81
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_os2.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+bool ParseFtpDirectoryListingOS2(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Every line of the listing consists of the following:
+ //
+ // 1. size in bytes (0 for directories)
+ // 2. type (A for files, DIR for directories)
+ // 3. date
+ // 4. time
+ // 5. filename (may be empty or contain spaces)
+ //
+ // For now, make sure we have 1-4, and handle 5 later.
+ if (columns.size() < 4)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ if (!base::StringToInt64(columns[0], &entry.size))
+ return false;
+ if (EqualsASCII(columns[1], "DIR")) {
+ if (entry.size != 0)
+ return false;
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ entry.size = -1;
+ } else if (EqualsASCII(columns[1], "A")) {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ if (entry.size < 0)
+ return false;
+ } else {
+ return false;
+ }
+
+ if (!FtpUtil::WindowsDateListingToTime(columns[2],
+ columns[3],
+ &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 4);
+ if (entry.name.empty()) {
+ // Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2.h b/chromium/net/ftp/ftp_directory_listing_parser_os2.h
new file mode 100644
index 00000000000..9cd471fb0b1
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses OS/2 FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingOS2(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_OS2_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc
new file mode 100644
index 00000000000..7302f4c9a9d
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_os2_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_os2.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserOS2Test;
+
+TEST_F(FtpDirectoryListingParserOS2Test, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "0 DIR 11-02-09 17:32 NT",
+ FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
+ 2009, 11, 2, 17, 32 },
+ { "458 A 01-06-09 14:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2009, 1, 6, 14, 42 },
+ { "1 A 01-06-09 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
+ 2009, 1, 6, 2, 42 },
+ { "458 A 01-06-01 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2001, 1, 6, 2, 42 },
+ { "458 A 01-06-00 02:42 Corner1.txt",
+ FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
+ 2000, 1, 6, 2, 42 },
+ { "458 A 01-06-99 02:42 Corner2.txt",
+ FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
+ 1999, 1, 6, 2, 42 },
+ { "458 A 01-06-80 02:42 Corner3.txt",
+ FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
+ 1980, 1, 6, 2, 42 },
+ { "458 A 01-06-1979 02:42 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 1979, 1, 6, 2, 42 },
+ { "0 DIR 11-02-09 17:32 My Directory",
+ FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
+ 2009, 11, 2, 17, 32 },
+ { "0 DIR 12-25-10 00:00 Christmas Midnight",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
+ 2010, 12, 25, 0, 0 },
+ { "0 DIR 12-25-10 12:00 Christmas Midday",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
+ 2010, 12, 25, 12, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(good_cases[i].input),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserOS2Test, Ignored) {
+ const char* ignored_cases[] = {
+ "1234 A 12-07-10 12:05",
+ "0 DIR 11-02-09 05:32",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(ignored_cases[i]),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserOS2Test, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "0 GARBAGE 11-02-09 05:32",
+ "0 GARBAGE 11-02-09 05:32 NT",
+ "0 DIR 11-FEB-09 05:32",
+ "0 DIR 11-02 05:32",
+ "-1 A 11-02-09 05:32",
+ "0 DIR 11-FEB-09 05:32",
+ "0 DIR 11-02 05:32 NT",
+ "-1 A 11-02-09 05:32 NT",
+ "0 A 99-25-10 12:00",
+ "0 A 12-99-10 12:00",
+ "0 A 12-25-10 99:00",
+ "0 A 12-25-10 12:99",
+ "0 A 99-25-10 12:00 months out of range",
+ "0 A 12-99-10 12:00 days out of range",
+ "0 A 12-25-10 99:00 hours out of range",
+ "0 A 12-25-10 12:99 minutes out of range",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingOS2(
+ GetSingleLineTestCase(bad_cases[i]),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc
new file mode 100644
index 00000000000..4eaf1dd97e4
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright (c) 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.
+
+#include "net/ftp/ftp_directory_listing_parser.h"
+
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class FtpDirectoryListingParserTest
+ : public testing::TestWithParam<const char*> {
+};
+
+TEST_P(FtpDirectoryListingParserTest, Parse) {
+ base::FilePath test_dir;
+ PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
+ test_dir = test_dir.AppendASCII("net");
+ test_dir = test_dir.AppendASCII("data");
+ test_dir = test_dir.AppendASCII("ftp");
+
+ base::Time::Exploded mock_current_time_exploded = { 0 };
+ mock_current_time_exploded.year = 1994;
+ mock_current_time_exploded.month = 11;
+ mock_current_time_exploded.day_of_month = 15;
+ mock_current_time_exploded.hour = 12;
+ mock_current_time_exploded.minute = 45;
+ base::Time mock_current_time(
+ base::Time::FromLocalExploded(mock_current_time_exploded));
+
+ SCOPED_TRACE(base::StringPrintf("Test case: %s", GetParam()));
+
+ std::string test_listing;
+ EXPECT_TRUE(file_util::ReadFileToString(test_dir.AppendASCII(GetParam()),
+ &test_listing));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_EQ(OK, ParseFtpDirectoryListing(test_listing,
+ mock_current_time,
+ &entries));
+
+ std::string expected_listing;
+ ASSERT_TRUE(file_util::ReadFileToString(
+ test_dir.AppendASCII(std::string(GetParam()) + ".expected"),
+ &expected_listing));
+
+ std::vector<std::string> lines;
+ base::SplitStringUsingSubstr(expected_listing, "\r\n", &lines);
+
+ // Special case for empty listings.
+ if (lines.size() == 1 && lines[0].empty())
+ lines.clear();
+
+ ASSERT_EQ(9 * entries.size(), lines.size());
+
+ for (size_t i = 0; i < lines.size() / 9; i++) {
+ std::string type(lines[9 * i]);
+ std::string name(lines[9 * i + 1]);
+ int64 size;
+ base::StringToInt64(lines[9 * i + 2], &size);
+
+ SCOPED_TRACE(base::StringPrintf("Filename: %s", name.c_str()));
+
+ int year, month, day_of_month, hour, minute;
+ base::StringToInt(lines[9 * i + 3], &year);
+ base::StringToInt(lines[9 * i + 4], &month);
+ base::StringToInt(lines[9 * i + 5], &day_of_month);
+ base::StringToInt(lines[9 * i + 6], &hour);
+ base::StringToInt(lines[9 * i + 7], &minute);
+
+ const FtpDirectoryListingEntry& entry = entries[i];
+
+ if (type == "d") {
+ EXPECT_EQ(FtpDirectoryListingEntry::DIRECTORY, entry.type);
+ } else if (type == "-") {
+ EXPECT_EQ(FtpDirectoryListingEntry::FILE, entry.type);
+ } else if (type == "l") {
+ EXPECT_EQ(FtpDirectoryListingEntry::SYMLINK, entry.type);
+ } else {
+ ADD_FAILURE() << "invalid gold test data: " << type;
+ }
+
+ EXPECT_EQ(UTF8ToUTF16(name), entry.name);
+ EXPECT_EQ(size, entry.size);
+
+ base::Time::Exploded time_exploded;
+ entry.last_modified.LocalExplode(&time_exploded);
+ EXPECT_EQ(year, time_exploded.year);
+ EXPECT_EQ(month, time_exploded.month);
+ EXPECT_EQ(day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(hour, time_exploded.hour);
+ EXPECT_EQ(minute, time_exploded.minute);
+ }
+}
+
+const char* kTestFiles[] = {
+ "dir-listing-ls-1",
+ "dir-listing-ls-1-utf8",
+ "dir-listing-ls-2",
+ "dir-listing-ls-3",
+ "dir-listing-ls-4",
+ "dir-listing-ls-5",
+ "dir-listing-ls-6",
+ "dir-listing-ls-7",
+ "dir-listing-ls-8",
+ "dir-listing-ls-9",
+ "dir-listing-ls-10",
+ "dir-listing-ls-11",
+ "dir-listing-ls-12",
+ "dir-listing-ls-13",
+ "dir-listing-ls-14",
+ "dir-listing-ls-15",
+ "dir-listing-ls-16",
+ "dir-listing-ls-17",
+ "dir-listing-ls-18",
+ "dir-listing-ls-19",
+ "dir-listing-ls-20", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-21", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-22", // TODO(phajdan.jr): should use windows-1251 encoding.
+ "dir-listing-ls-23",
+ "dir-listing-ls-24",
+
+ // Tests for Russian listings. The only difference between those
+ // files is character encoding:
+ "dir-listing-ls-25", // UTF-8
+ "dir-listing-ls-26", // KOI8-R
+ "dir-listing-ls-27", // windows-1251
+
+ "dir-listing-ls-28", // Hylafax FTP server
+ "dir-listing-ls-29",
+ "dir-listing-ls-30",
+ "dir-listing-ls-31",
+
+ "dir-listing-netware-1",
+ "dir-listing-netware-2",
+ "dir-listing-netware-3", // Spaces in file names.
+ "dir-listing-os2-1",
+ "dir-listing-vms-1",
+ "dir-listing-vms-2",
+ "dir-listing-vms-3",
+ "dir-listing-vms-4",
+ "dir-listing-vms-5",
+ "dir-listing-vms-6",
+ "dir-listing-vms-7",
+ "dir-listing-vms-8",
+ "dir-listing-windows-1",
+ "dir-listing-windows-2",
+};
+
+INSTANTIATE_TEST_CASE_P(, FtpDirectoryListingParserTest,
+ testing::ValuesIn(kTestFiles));
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_unittest.h b/chromium/net/ftp/ftp_directory_listing_parser_unittest.h
new file mode 100644
index 00000000000..9ac42d867b5
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_unittest.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
+#include <vector>
+
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class FtpDirectoryListingParserTest : public testing::Test {
+ public:
+ struct SingleLineTestData {
+ const char* input;
+ FtpDirectoryListingEntry::Type type;
+ const char* filename;
+ int64 size;
+ int year;
+ int month;
+ int day_of_month;
+ int hour;
+ int minute;
+ };
+
+ protected:
+ FtpDirectoryListingParserTest() {}
+
+ std::vector<base::string16> GetSingleLineTestCase(const std::string& text) {
+ std::vector<base::string16> lines;
+ lines.push_back(UTF8ToUTF16(text));
+ return lines;
+ }
+
+ void VerifySingleLineTestCase(
+ const SingleLineTestData& test_case,
+ const std::vector<FtpDirectoryListingEntry>& entries) {
+ ASSERT_FALSE(entries.empty());
+
+ FtpDirectoryListingEntry entry = entries[0];
+ EXPECT_EQ(test_case.type, entry.type);
+ EXPECT_EQ(UTF8ToUTF16(test_case.filename), entry.name);
+ EXPECT_EQ(test_case.size, entry.size);
+
+ base::Time::Exploded time_exploded;
+ entry.last_modified.LocalExplode(&time_exploded);
+
+ // Only test members displayed on the directory listing.
+ EXPECT_EQ(test_case.year, time_exploded.year);
+ EXPECT_EQ(test_case.month, time_exploded.month);
+ EXPECT_EQ(test_case.day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(test_case.hour, time_exploded.hour);
+ EXPECT_EQ(test_case.minute, time_exploded.minute);
+
+ EXPECT_EQ(1U, entries.size());
+ }
+
+ base::Time GetMockCurrentTime() {
+ base::Time::Exploded mock_current_time_exploded = { 0 };
+ mock_current_time_exploded.year = 1994;
+ mock_current_time_exploded.month = 11;
+ mock_current_time_exploded.day_of_month = 15;
+ mock_current_time_exploded.hour = 12;
+ mock_current_time_exploded.minute = 45;
+ return base::Time::FromLocalExploded(mock_current_time_exploded);
+ }
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
+
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms.cc b/chromium/net/ftp/ftp_directory_listing_parser_vms.cc
new file mode 100644
index 00000000000..4b44d73b5b5
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 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.
+
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+namespace {
+
+// Converts the filename component in listing to the filename we can display.
+// Returns true on success.
+bool ParseVmsFilename(const base::string16& raw_filename,
+ base::string16* parsed_filename,
+ FtpDirectoryListingEntry::Type* type) {
+ // On VMS, the files and directories are versioned. The version number is
+ // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
+ std::vector<base::string16> listing_parts;
+ base::SplitString(raw_filename, ';', &listing_parts);
+ if (listing_parts.size() != 2)
+ return false;
+ int version_number;
+ if (!base::StringToInt(listing_parts[1], &version_number))
+ return false;
+ if (version_number < 0)
+ return false;
+
+ // Even directories have extensions in the listings. Don't display extensions
+ // for directories; it's awkward for non-VMS users. Also, VMS is
+ // case-insensitive, but generally uses uppercase characters. This may look
+ // awkward, so we convert them to lower case.
+ std::vector<base::string16> filename_parts;
+ base::SplitString(listing_parts[0], '.', &filename_parts);
+ if (filename_parts.size() != 2)
+ return false;
+ if (EqualsASCII(filename_parts[1], "DIR")) {
+ *parsed_filename = StringToLowerASCII(filename_parts[0]);
+ *type = FtpDirectoryListingEntry::DIRECTORY;
+ } else {
+ *parsed_filename = StringToLowerASCII(listing_parts[0]);
+ *type = FtpDirectoryListingEntry::FILE;
+ }
+ return true;
+}
+
+bool ParseVmsFilesize(const base::string16& input, int64* size) {
+ if (ContainsOnlyChars(input, ASCIIToUTF16("*"))) {
+ // Response consisting of asterisks means unknown size.
+ *size = -1;
+ return true;
+ }
+
+ // VMS's directory listing gives us file size in blocks. We assume that
+ // the block size is 512 bytes. It doesn't give accurate file size, but is the
+ // best information we have.
+ const int kBlockSize = 512;
+
+ if (base::StringToInt64(input, size)) {
+ if (*size < 0)
+ return false;
+ *size *= kBlockSize;
+ return true;
+ }
+
+ std::vector<base::string16> parts;
+ base::SplitString(input, '/', &parts);
+ if (parts.size() != 2)
+ return false;
+
+ int64 blocks_used, blocks_allocated;
+ if (!base::StringToInt64(parts[0], &blocks_used))
+ return false;
+ if (!base::StringToInt64(parts[1], &blocks_allocated))
+ return false;
+ if (blocks_used > blocks_allocated)
+ return false;
+ if (blocks_used < 0 || blocks_allocated < 0)
+ return false;
+
+ *size = blocks_used * kBlockSize;
+ return true;
+}
+
+bool LooksLikeVmsFileProtectionListingPart(const base::string16& input) {
+ if (input.length() > 4)
+ return false;
+
+ // On VMS there are four different permission bits: Read, Write, Execute,
+ // and Delete. They appear in that order in the permission listing.
+ std::string pattern("RWED");
+ base::string16 match(input);
+ while (!match.empty() && !pattern.empty()) {
+ if (match[0] == pattern[0])
+ match = match.substr(1);
+ pattern = pattern.substr(1);
+ }
+ return match.empty();
+}
+
+bool LooksLikeVmsFileProtectionListing(const base::string16& input) {
+ if (input.length() < 2)
+ return false;
+ if (input[0] != '(' || input[input.length() - 1] != ')')
+ return false;
+
+ // We expect four parts of the file protection listing: for System, Owner,
+ // Group, and World.
+ std::vector<base::string16> parts;
+ base::SplitString(input.substr(1, input.length() - 2), ',', &parts);
+ if (parts.size() != 4)
+ return false;
+
+ return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[1]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[2]) &&
+ LooksLikeVmsFileProtectionListingPart(parts[3]);
+}
+
+bool LooksLikeVmsUserIdentificationCode(const base::string16& input) {
+ if (input.length() < 2)
+ return false;
+ return input[0] == '[' && input[input.length() - 1] == ']';
+}
+
+bool LooksLikeVMSError(const base::string16& text) {
+ static const char* kPermissionDeniedMessages[] = {
+ "%RMS-E-FNF", // File not found.
+ "%RMS-E-PRV", // Access denied.
+ "%SYSTEM-F-NOPRIV",
+ "privilege",
+ };
+
+ for (size_t i = 0; i < arraysize(kPermissionDeniedMessages); i++) {
+ if (text.find(ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
+ base::string16::npos)
+ return true;
+ }
+
+ return false;
+}
+
+bool VmsDateListingToTime(const std::vector<base::string16>& columns,
+ base::Time* time) {
+ DCHECK_EQ(4U, columns.size());
+
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format DD-MMM-YYYY.
+ std::vector<base::string16> date_parts;
+ base::SplitString(columns[2], '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
+ return false;
+ if (!FtpUtil::AbbreviatedMonthToNumber(date_parts[1],
+ &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
+ return false;
+
+ // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
+ // last type first. Do not parse the seconds, they will be ignored anyway.
+ base::string16 time_column(columns[3]);
+ if (time_column.length() == 11 && time_column[8] == '.')
+ time_column = time_column.substr(0, 8);
+ if (time_column.length() == 8 && time_column[5] == ':')
+ time_column = time_column.substr(0, 5);
+ if (time_column.length() != 5)
+ return false;
+ std::vector<base::string16> time_parts;
+ base::SplitString(time_column, ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+
+ // We don't know the time zone of the server, so just use local time.
+ *time = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+} // namespace
+
+bool ParseFtpDirectoryListingVms(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ // The first non-empty line is the listing header. It often
+ // starts with "Directory ", but not always. We set a flag after
+ // seing the header.
+ bool seen_header = false;
+
+ // Sometimes the listing doesn't end with a "Total" line, but
+ // it's only okay when it contains some errors (it's needed
+ // to distinguish it from "ls -l" format).
+ bool seen_error = false;
+
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ if (StartsWith(lines[i], ASCIIToUTF16("Total of "), true)) {
+ // After the "total" line, all following lines must be empty.
+ for (size_t j = i + 1; j < lines.size(); j++)
+ if (!lines[j].empty())
+ return false;
+
+ return true;
+ }
+
+ if (!seen_header) {
+ seen_header = true;
+ continue;
+ }
+
+ if (LooksLikeVMSError(lines[i])) {
+ seen_error = true;
+ continue;
+ }
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ if (columns.size() == 1) {
+ // There can be no continuation if the current line is the last one.
+ if (i == lines.size() - 1)
+ return false;
+
+ // Skip the next line.
+ i++;
+
+ // This refers to the continuation line.
+ if (LooksLikeVMSError(lines[i])) {
+ seen_error = true;
+ continue;
+ }
+
+ // Join the current and next line and split them into columns.
+ base::SplitString(
+ CollapseWhitespace(lines[i - 1] + ASCIIToUTF16(" ") + lines[i],
+ false),
+ ' ',
+ &columns);
+ }
+
+ FtpDirectoryListingEntry entry;
+ if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
+ return false;
+
+ // There are different variants of a VMS listing. Some display
+ // the protection listing and user identification code, some do not.
+ if (columns.size() == 6) {
+ if (!LooksLikeVmsFileProtectionListing(columns[5]))
+ return false;
+ if (!LooksLikeVmsUserIdentificationCode(columns[4]))
+ return false;
+
+ // Drop the unneeded data, so that the following code can always expect
+ // just four columns.
+ columns.resize(4);
+ }
+
+ if (columns.size() != 4)
+ return false;
+
+ if (!ParseVmsFilesize(columns[1], &entry.size))
+ return false;
+ if (entry.type != FtpDirectoryListingEntry::FILE)
+ entry.size = -1;
+ if (!VmsDateListingToTime(columns, &entry.last_modified))
+ return false;
+
+ entries->push_back(entry);
+ }
+
+ // The only place where we return true is after receiving the "Total" line,
+ // that should be present in every VMS listing. Alternatively, if the listing
+ // contains error messages, it's OK not to have the "Total" line.
+ return seen_error;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms.h b/chromium/net/ftp/ftp_directory_listing_parser_vms.h
new file mode 100644
index 00000000000..98e6a9275bd
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses VMS FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingVms(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
new file mode 100644
index 00000000000..3690f7e18c6
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserVmsTest;
+
+TEST_F(FtpDirectoryListingParserVmsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "README.TXT;4 2 18-APR-2000 10:40:39.90",
+ FtpDirectoryListingEntry::FILE, "readme.txt", 1024,
+ 2000, 4, 18, 10, 40 },
+ { ".WELCOME;1 2 13-FEB-2002 23:32:40.47",
+ FtpDirectoryListingEntry::FILE, ".welcome", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "FILE.;1 2 13-FEB-2002 23:32:40.47",
+ FtpDirectoryListingEntry::FILE, "file.", 1024,
+ 2002, 2, 13, 23, 32 },
+ { "EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)",
+ FtpDirectoryListingEntry::FILE, "example.txt", 512,
+ 2009, 11, 4, 6, 2 },
+ { "ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "TEST.DIR;1 1 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] (RWE,RWE,RWE,RWE)",
+ FtpDirectoryListingEntry::DIRECTORY, "test", -1,
+ 1999, 3, 4, 22, 14 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (,,,)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (R,RW,RWD,RE)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (ED,RED,WD,WED)",
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512,
+ 2005, 3, 12, 8, 44 },
+ { "VMS721.ISO;2 ****** 6-MAY-2008 09:29 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)",
+ FtpDirectoryListingEntry::FILE, "vms721.iso", -1,
+ 2008, 5, 6, 9, 29 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase(good_cases[i].input));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserVmsTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+
+ // Missing file version number.
+ "README.TXT 2 18-APR-2000 10:40:39",
+
+ // Missing extension.
+ "README;1 2 18-APR-2000 10:40:39",
+
+ // Malformed file size.
+ "README.TXT;1 garbage 18-APR-2000 10:40:39",
+ "README.TXT;1 -2 18-APR-2000 10:40:39",
+
+ // Malformed date.
+ "README.TXT;1 2 APR-2000 10:40:39",
+ "README.TXT;1 2 -18-APR-2000 10:40:39",
+ "README.TXT;1 2 18-APR 10:40:39",
+ "README.TXT;1 2 18-APR-2000 10",
+ "README.TXT;1 2 18-APR-2000 10:40.25",
+ "README.TXT;1 2 18-APR-2000 10:40.25.25",
+
+ // Malformed security information.
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (RWED,RWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM]",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (SYSTEM) (RWED,RWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM] [RWED,RWED,RE,RE]",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,RE,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWEDRWED,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,DEWR,RE,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,Q,RE)",
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RRWWEEDD,RE,RE)",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
+
+ std::vector<base::string16> lines(GetSingleLineTestCase(bad_cases[i]));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+}
+
+TEST_F(FtpDirectoryListingParserVmsTest, BadDataAfterFooter) {
+ const char* bad_cases[] = {
+ "garbage",
+ "Total of 1 file, 2 blocks.",
+ "Directory ANYNYMOUS_ROOT:[000000]",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
+
+ std::vector<base::string16> lines(
+ GetSingleLineTestCase("README.TXT;4 2 18-APR-2000 10:40:39.90"));
+
+ // The parser requires a directory header before accepting regular input.
+ lines.insert(lines.begin(),
+ ASCIIToUTF16("Directory ANONYMOUS_ROOT:[000000]"));
+
+ // A valid listing must also have a "Total" line at the end.
+ lines.insert(lines.end(),
+ ASCIIToUTF16("Total of 1 file, 2 blocks."));
+
+ {
+ // Make sure the listing is valid before we add data after footer.
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+
+ {
+ // Insert a line at the end of the listing that should make it invalid.
+ lines.insert(lines.end(),
+ ASCIIToUTF16(bad_cases[i]));
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
+ &entries));
+ }
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows.cc b/chromium/net/ftp/ftp_directory_listing_parser_windows.cc
new file mode 100644
index 00000000000..5ec4a523434
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_windows.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/ftp/ftp_directory_listing_parser.h"
+#include "net/ftp/ftp_util.h"
+
+namespace net {
+
+bool ParseFtpDirectoryListingWindows(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries) {
+ for (size_t i = 0; i < lines.size(); i++) {
+ if (lines[i].empty())
+ continue;
+
+ std::vector<base::string16> columns;
+ base::SplitString(CollapseWhitespace(lines[i], false), ' ', &columns);
+
+ // Every line of the listing consists of the following:
+ //
+ // 1. date
+ // 2. time
+ // 3. size in bytes (or "<DIR>" for directories)
+ // 4. filename (may be empty or contain spaces)
+ //
+ // For now, make sure we have 1-3, and handle 4 later.
+ if (columns.size() < 3)
+ return false;
+
+ FtpDirectoryListingEntry entry;
+ if (EqualsASCII(columns[2], "<DIR>")) {
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
+ entry.size = -1;
+ } else {
+ entry.type = FtpDirectoryListingEntry::FILE;
+ if (!base::StringToInt64(columns[2], &entry.size))
+ return false;
+ if (entry.size < 0)
+ return false;
+ }
+
+ if (!FtpUtil::WindowsDateListingToTime(columns[0],
+ columns[1],
+ &entry.last_modified)) {
+ return false;
+ }
+
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 3);
+ if (entry.name.empty()) {
+ // Some FTP servers send listing entries with empty names.
+ // It's not obvious how to display such an entry, so ignore them.
+ // We don't want to make the parsing fail at this point though.
+ // Other entries can still be useful.
+ continue;
+ }
+
+ entries->push_back(entry);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows.h b/chromium/net/ftp/ftp_directory_listing_parser_windows.h
new file mode 100644
index 00000000000..e66f8e5d051
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2011 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_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+struct FtpDirectoryListingEntry;
+
+// Parses Windows FTP directory listing. Returns true on success.
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingWindows(
+ const std::vector<base::string16>& lines,
+ std::vector<FtpDirectoryListingEntry>* entries);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
diff --git a/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc b/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
new file mode 100644
index 00000000000..d3d47da1529
--- /dev/null
+++ b/chromium/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2011 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/ftp/ftp_directory_listing_parser_unittest.h"
+
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
+
+namespace net {
+
+namespace {
+
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserWindowsTest;
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Good) {
+ const struct SingleLineTestData good_cases[] = {
+ { "11-02-09 05:32PM <DIR> NT",
+ FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
+ 2009, 11, 2, 17, 32 },
+ { "01-06-09 02:42PM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2009, 1, 6, 14, 42 },
+ { "01-06-09 02:42AM 1 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
+ 2009, 1, 6, 2, 42 },
+ { "01-06-01 02:42AM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 2001, 1, 6, 2, 42 },
+ { "01-06-00 02:42AM 458 Corner1.txt",
+ FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
+ 2000, 1, 6, 2, 42 },
+ { "01-06-99 02:42AM 458 Corner2.txt",
+ FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
+ 1999, 1, 6, 2, 42 },
+ { "01-06-80 02:42AM 458 Corner3.txt",
+ FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
+ 1980, 1, 6, 2, 42 },
+#if !defined(OS_LINUX) && !defined(OS_ANDROID)
+ // TODO(phajdan.jr): Re-enable when 2038-year problem is fixed on Linux.
+ { "01-06-79 02:42AM 458 Corner4",
+ FtpDirectoryListingEntry::FILE, "Corner4", 458,
+ 2079, 1, 6, 2, 42 },
+#endif // !defined (OS_LINUX) && !defined(OS_ANDROID)
+ { "01-06-1979 02:42AM 458 Readme.txt",
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
+ 1979, 1, 6, 2, 42 },
+ { "11-02-09 05:32PM <DIR> My Directory",
+ FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
+ 2009, 11, 2, 17, 32 },
+ { "12-25-10 12:00AM <DIR> Christmas Midnight",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
+ 2010, 12, 25, 0, 0 },
+ { "12-25-10 12:00PM <DIR> Christmas Midday",
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
+ 2010, 12, 25, 12, 0 },
+ };
+ for (size_t i = 0; i < arraysize(good_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ good_cases[i].input));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(good_cases[i].input),
+ &entries));
+ VerifySingleLineTestCase(good_cases[i], entries);
+ }
+}
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Ignored) {
+ const char* ignored_cases[] = {
+ "12-07-10 12:05AM <DIR> ", // http://crbug.com/66097
+ "12-07-10 12:05AM 1234 ",
+ "11-02-09 05:32 <DIR>",
+ "11-02-09 05:32PM <DIR>",
+ };
+ for (size_t i = 0; i < arraysize(ignored_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ ignored_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(ignored_cases[i]),
+ &entries));
+ EXPECT_EQ(0U, entries.size());
+ }
+}
+
+TEST_F(FtpDirectoryListingParserWindowsTest, Bad) {
+ const char* bad_cases[] = {
+ "garbage",
+ "11-02-09 05:32PM <GARBAGE>",
+ "11-02-09 05:32PM <GARBAGE> NT",
+ "11-FEB-09 05:32PM <DIR>",
+ "11-02 05:32PM <DIR>",
+ "11-02-09 05:32PM -1",
+ "11-FEB-09 05:32PM <DIR> NT",
+ "11-02 05:32PM <DIR> NT",
+ "11-02-09 05:32PM -1 NT",
+ "99-25-10 12:00AM 0",
+ "12-99-10 12:00AM 0",
+ "12-25-10 99:00AM 0",
+ "12-25-10 12:99AM 0",
+ "12-25-10 12:00ZM 0",
+ "99-25-10 12:00AM 0 months out of range",
+ "12-99-10 12:00AM 0 days out of range",
+ "12-25-10 99:00AM 0 hours out of range",
+ "12-25-10 12:99AM 0 minutes out of range",
+ "12-25-10 12:00ZM 0 what does ZM mean",
+ };
+ for (size_t i = 0; i < arraysize(bad_cases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
+ bad_cases[i]));
+
+ std::vector<FtpDirectoryListingEntry> entries;
+ EXPECT_FALSE(ParseFtpDirectoryListingWindows(
+ GetSingleLineTestCase(bad_cases[i]),
+ &entries));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_layer.cc b/chromium/net/ftp/ftp_network_layer.cc
new file mode 100644
index 00000000000..098fb9fb0a6
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_layer.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2008 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/ftp/ftp_network_layer.h"
+
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_network_transaction.h"
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+FtpNetworkLayer::FtpNetworkLayer(HostResolver* host_resolver)
+ : session_(new FtpNetworkSession(host_resolver)),
+ suspended_(false) {
+}
+
+FtpNetworkLayer::~FtpNetworkLayer() {
+}
+
+// static
+FtpTransactionFactory* FtpNetworkLayer::CreateFactory(
+ HostResolver* host_resolver) {
+ return new FtpNetworkLayer(host_resolver);
+}
+
+FtpTransaction* FtpNetworkLayer::CreateTransaction() {
+ if (suspended_)
+ return NULL;
+
+ return new FtpNetworkTransaction(session_.get(),
+ ClientSocketFactory::GetDefaultFactory());
+}
+
+void FtpNetworkLayer::Suspend(bool suspend) {
+ suspended_ = suspend;
+
+ /* TODO(darin): We'll need this code once we have a connection manager.
+ if (suspend)
+ session_->connection_manager()->CloseIdleSockets();
+ */
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_layer.h b/chromium/net/ftp/ftp_network_layer.h
new file mode 100644
index 00000000000..6040d029415
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_layer.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_FTP_FTP_NETWORK_LAYER_H_
+#define NET_FTP_FTP_NETWORK_LAYER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/ftp/ftp_transaction_factory.h"
+
+namespace net {
+
+class FtpNetworkSession;
+class HostResolver;
+
+class NET_EXPORT FtpNetworkLayer : public FtpTransactionFactory {
+ public:
+ explicit FtpNetworkLayer(HostResolver* host_resolver);
+ virtual ~FtpNetworkLayer();
+
+ static FtpTransactionFactory* CreateFactory(HostResolver* host_resolver);
+
+ // FtpTransactionFactory methods:
+ virtual FtpTransaction* CreateTransaction() OVERRIDE;
+ virtual void Suspend(bool suspend) OVERRIDE;
+
+ private:
+ scoped_refptr<FtpNetworkSession> session_;
+ bool suspended_;
+ DISALLOW_COPY_AND_ASSIGN(FtpNetworkLayer);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_LAYER_H_
diff --git a/chromium/net/ftp/ftp_network_session.cc b/chromium/net/ftp/ftp_network_session.cc
new file mode 100644
index 00000000000..65dd218a4d4
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_session.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 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/ftp/ftp_network_session.h"
+
+namespace net {
+
+FtpNetworkSession::FtpNetworkSession(HostResolver* host_resolver)
+ : host_resolver_(host_resolver) {}
+
+FtpNetworkSession::~FtpNetworkSession() {}
+
+} // namespace net
+
diff --git a/chromium/net/ftp/ftp_network_session.h b/chromium/net/ftp/ftp_network_session.h
new file mode 100644
index 00000000000..e92bb06643f
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_session.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 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_FTP_FTP_NETWORK_SESSION_H_
+#define NET_FTP_FTP_NETWORK_SESSION_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class HostResolver;
+
+// This class holds session objects used by FtpNetworkTransaction objects.
+class NET_EXPORT_PRIVATE FtpNetworkSession
+ : public base::RefCounted<FtpNetworkSession> {
+ public:
+ explicit FtpNetworkSession(HostResolver* host_resolver);
+
+ HostResolver* host_resolver() { return host_resolver_; }
+
+ private:
+ friend class base::RefCounted<FtpNetworkSession>;
+
+ virtual ~FtpNetworkSession();
+
+ HostResolver* const host_resolver_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_SESSION_H_
diff --git a/chromium/net/ftp/ftp_network_transaction.cc b/chromium/net/ftp/ftp_network_transaction.cc
new file mode 100644
index 00000000000..f9f7b820168
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction.cc
@@ -0,0 +1,1400 @@
+// Copyright (c) 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.
+
+#include "net/ftp/ftp_network_transaction.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/ftp/ftp_util.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/stream_socket.h"
+
+const char kCRLF[] = "\r\n";
+
+const int kCtrlBufLen = 1024;
+
+namespace {
+
+// Returns true if |input| can be safely used as a part of FTP command.
+bool IsValidFTPCommandString(const std::string& input) {
+ // RFC 959 only allows ASCII strings, but at least Firefox can send non-ASCII
+ // characters in the command if the request path contains them. To be
+ // compatible, we do the same and allow non-ASCII characters in a command.
+
+ // Protect agains newline injection attack.
+ if (input.find_first_of("\r\n") != std::string::npos)
+ return false;
+
+ return true;
+}
+
+enum ErrorClass {
+ // The requested action was initiated. The client should expect another
+ // reply before issuing the next command.
+ ERROR_CLASS_INITIATED,
+
+ // The requested action has been successfully completed.
+ ERROR_CLASS_OK,
+
+ // The command has been accepted, but to complete the operation, more
+ // information must be sent by the client.
+ ERROR_CLASS_INFO_NEEDED,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is temporary, and the client is encouraged to restart the
+ // command sequence.
+ ERROR_CLASS_TRANSIENT_ERROR,
+
+ // The command was not accepted and the requested action did not take place.
+ // This condition is rather permanent, and the client is discouraged from
+ // repeating the exact request.
+ ERROR_CLASS_PERMANENT_ERROR,
+};
+
+// Returns the error class for given response code. Caller should ensure
+// that |response_code| is in range 100-599.
+ErrorClass GetErrorClass(int response_code) {
+ if (response_code >= 100 && response_code <= 199)
+ return ERROR_CLASS_INITIATED;
+
+ if (response_code >= 200 && response_code <= 299)
+ return ERROR_CLASS_OK;
+
+ if (response_code >= 300 && response_code <= 399)
+ return ERROR_CLASS_INFO_NEEDED;
+
+ if (response_code >= 400 && response_code <= 499)
+ return ERROR_CLASS_TRANSIENT_ERROR;
+
+ if (response_code >= 500 && response_code <= 599)
+ return ERROR_CLASS_PERMANENT_ERROR;
+
+ // We should not be called on invalid error codes.
+ NOTREACHED() << response_code;
+ return ERROR_CLASS_PERMANENT_ERROR;
+}
+
+// Returns network error code for received FTP |response_code|.
+int GetNetErrorCodeForFtpResponseCode(int response_code) {
+ switch (response_code) {
+ case 421:
+ return net::ERR_FTP_SERVICE_UNAVAILABLE;
+ case 426:
+ return net::ERR_FTP_TRANSFER_ABORTED;
+ case 450:
+ return net::ERR_FTP_FILE_BUSY;
+ case 500:
+ case 501:
+ return net::ERR_FTP_SYNTAX_ERROR;
+ case 502:
+ case 504:
+ return net::ERR_FTP_COMMAND_NOT_SUPPORTED;
+ case 503:
+ return net::ERR_FTP_BAD_COMMAND_SEQUENCE;
+ default:
+ return net::ERR_FTP_FAILED;
+ }
+}
+
+// From RFC 2428 Section 3:
+// The text returned in response to the EPSV command MUST be:
+// <some text> (<d><d><d><tcp-port><d>)
+// <d> is a delimiter character, ideally to be |
+bool ExtractPortFromEPSVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+ const char* ptr = response.lines[0].c_str();
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (!*ptr)
+ return false;
+ char sep = *(++ptr);
+ if (!sep || isdigit(sep) || *(++ptr) != sep || *(++ptr) != sep)
+ return false;
+ if (!isdigit(*(++ptr)))
+ return false;
+ *port = *ptr - '0';
+ while (isdigit(*(++ptr))) {
+ *port *= 10;
+ *port += *ptr - '0';
+ }
+ if (*ptr != sep)
+ return false;
+
+ return true;
+}
+
+// There are two way we can receive IP address and port.
+// (127,0,0,1,23,21) IP address and port encapsulated in ().
+// 127,0,0,1,23,21 IP address and port without ().
+//
+// See RFC 959, Section 4.1.2
+bool ExtractPortFromPASVResponse(const net::FtpCtrlResponse& response,
+ int* port) {
+ if (response.lines.size() != 1)
+ return false;
+
+ std::string line(response.lines[0]);
+ if (!IsStringASCII(line))
+ return false;
+ if (line.length() < 2)
+ return false;
+
+ size_t paren_pos = line.find('(');
+ if (paren_pos == std::string::npos) {
+ // Find the first comma and use it to locate the beginning
+ // of the response data.
+ size_t comma_pos = line.find(',');
+ if (comma_pos == std::string::npos)
+ return false;
+
+ size_t space_pos = line.rfind(' ', comma_pos);
+ if (space_pos != std::string::npos)
+ line = line.substr(space_pos + 1);
+ } else {
+ // Remove the parentheses and use the text inside them.
+ size_t closing_paren_pos = line.rfind(')');
+ if (closing_paren_pos == std::string::npos)
+ return false;
+ if (closing_paren_pos <= paren_pos)
+ return false;
+
+ line = line.substr(paren_pos + 1, closing_paren_pos - paren_pos - 1);
+ }
+
+ // Split the line into comma-separated pieces and extract
+ // the last two.
+ std::vector<std::string> pieces;
+ base::SplitString(line, ',', &pieces);
+ if (pieces.size() != 6)
+ return false;
+
+ // Ignore the IP address supplied in the response. We are always going
+ // to connect back to the same server to prevent FTP PASV port scanning.
+ int p0, p1;
+ if (!base::StringToInt(pieces[4], &p0))
+ return false;
+ if (!base::StringToInt(pieces[5], &p1))
+ return false;
+ *port = (p0 << 8) + p1;
+
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+FtpNetworkTransaction::FtpNetworkTransaction(
+ FtpNetworkSession* session,
+ ClientSocketFactory* socket_factory)
+ : command_sent_(COMMAND_NONE),
+ io_callback_(base::Bind(&FtpNetworkTransaction::OnIOComplete,
+ base::Unretained(this))),
+ session_(session),
+ request_(NULL),
+ resolver_(session->host_resolver()),
+ read_ctrl_buf_(new IOBuffer(kCtrlBufLen)),
+ read_data_buf_len_(0),
+ last_error_(OK),
+ system_type_(SYSTEM_TYPE_UNKNOWN),
+ // Use image (binary) transfer by default. It should always work,
+ // whereas the ascii transfer may damage binary data.
+ data_type_(DATA_TYPE_IMAGE),
+ resource_type_(RESOURCE_TYPE_UNKNOWN),
+ use_epsv_(true),
+ data_connection_port_(0),
+ socket_factory_(socket_factory),
+ next_state_(STATE_NONE),
+ state_after_data_connect_complete_(STATE_CTRL_WRITE_SIZE) {}
+
+FtpNetworkTransaction::~FtpNetworkTransaction() {
+}
+
+int FtpNetworkTransaction::Stop(int error) {
+ if (command_sent_ == COMMAND_QUIT)
+ return error;
+
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ last_error_ = error;
+ return OK;
+}
+
+int FtpNetworkTransaction::RestartIgnoringLastError(
+ const CompletionCallback& callback) {
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int FtpNetworkTransaction::Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ net_log_ = net_log;
+ request_ = request_info;
+
+ ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_));
+
+ if (request_->url.has_username()) {
+ base::string16 username;
+ base::string16 password;
+ GetIdentityFromURL(request_->url, &username, &password);
+ credentials_.Set(username, password);
+ } else {
+ credentials_.Set(ASCIIToUTF16("anonymous"),
+ ASCIIToUTF16("chrome@example.com"));
+ }
+
+ DetectTypecode();
+
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int FtpNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) {
+ ResetStateForRestart();
+
+ credentials_ = credentials;
+
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+int FtpNetworkTransaction::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+
+ read_data_buf_ = buf;
+ read_data_buf_len_ = buf_len;
+
+ next_state_ = STATE_DATA_READ;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+const FtpResponseInfo* FtpNetworkTransaction::GetResponseInfo() const {
+ return &response_;
+}
+
+LoadState FtpNetworkTransaction::GetLoadState() const {
+ if (next_state_ == STATE_CTRL_RESOLVE_HOST_COMPLETE)
+ return LOAD_STATE_RESOLVING_HOST;
+
+ if (next_state_ == STATE_CTRL_CONNECT_COMPLETE ||
+ next_state_ == STATE_DATA_CONNECT_COMPLETE)
+ return LOAD_STATE_CONNECTING;
+
+ if (next_state_ == STATE_DATA_READ_COMPLETE)
+ return LOAD_STATE_READING_RESPONSE;
+
+ if (command_sent_ == COMMAND_RETR && read_data_buf_.get())
+ return LOAD_STATE_READING_RESPONSE;
+
+ if (command_sent_ == COMMAND_QUIT)
+ return LOAD_STATE_IDLE;
+
+ if (command_sent_ != COMMAND_NONE)
+ return LOAD_STATE_SENDING_REQUEST;
+
+ return LOAD_STATE_IDLE;
+}
+
+uint64 FtpNetworkTransaction::GetUploadProgress() const {
+ return 0;
+}
+
+void FtpNetworkTransaction::ResetStateForRestart() {
+ command_sent_ = COMMAND_NONE;
+ user_callback_.Reset();
+ response_ = FtpResponseInfo();
+ read_ctrl_buf_ = new IOBuffer(kCtrlBufLen);
+ ctrl_response_buffer_.reset(new FtpCtrlResponseBuffer(net_log_));
+ read_data_buf_ = NULL;
+ read_data_buf_len_ = 0;
+ if (write_buf_.get())
+ write_buf_->SetOffset(0);
+ last_error_ = OK;
+ data_connection_port_ = 0;
+ ctrl_socket_.reset();
+ data_socket_.reset();
+ next_state_ = STATE_NONE;
+ state_after_data_connect_complete_ = STATE_CTRL_WRITE_SIZE;
+}
+
+void FtpNetworkTransaction::ResetDataConnectionAfterError(State next_state) {
+ // The server _might_ have reset the data connection
+ // (see RFC 959 3.2. ESTABLISHING DATA CONNECTIONS:
+ // "The server MUST close the data connection under the following
+ // conditions:
+ // ...
+ // 5. An irrecoverable error condition occurs.")
+ //
+ // It is ambiguous what an irrecoverable error condition is,
+ // so we take no chances.
+ state_after_data_connect_complete_ = next_state;
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+}
+
+void FtpNetworkTransaction::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!user_callback_.is_null());
+
+ // Since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback c = user_callback_;
+ user_callback_.Reset();
+ c.Run(rv);
+}
+
+void FtpNetworkTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int FtpNetworkTransaction::ProcessCtrlResponse() {
+ FtpCtrlResponse response = ctrl_response_buffer_->PopResponse();
+
+ int rv = OK;
+ switch (command_sent_) {
+ case COMMAND_NONE:
+ // TODO(phajdan.jr): Check for errors in the welcome message.
+ next_state_ = STATE_CTRL_WRITE_USER;
+ break;
+ case COMMAND_USER:
+ rv = ProcessResponseUSER(response);
+ break;
+ case COMMAND_PASS:
+ rv = ProcessResponsePASS(response);
+ break;
+ case COMMAND_SYST:
+ rv = ProcessResponseSYST(response);
+ break;
+ case COMMAND_PWD:
+ rv = ProcessResponsePWD(response);
+ break;
+ case COMMAND_TYPE:
+ rv = ProcessResponseTYPE(response);
+ break;
+ case COMMAND_EPSV:
+ rv = ProcessResponseEPSV(response);
+ break;
+ case COMMAND_PASV:
+ rv = ProcessResponsePASV(response);
+ break;
+ case COMMAND_SIZE:
+ rv = ProcessResponseSIZE(response);
+ break;
+ case COMMAND_RETR:
+ rv = ProcessResponseRETR(response);
+ break;
+ case COMMAND_CWD:
+ rv = ProcessResponseCWD(response);
+ break;
+ case COMMAND_LIST:
+ rv = ProcessResponseLIST(response);
+ break;
+ case COMMAND_QUIT:
+ rv = ProcessResponseQUIT(response);
+ break;
+ default:
+ LOG(DFATAL) << "Unexpected value of command_sent_: " << command_sent_;
+ return ERR_UNEXPECTED;
+ }
+
+ // We may get multiple responses for some commands,
+ // see http://crbug.com/18036.
+ while (ctrl_response_buffer_->ResponseAvailable() && rv == OK) {
+ response = ctrl_response_buffer_->PopResponse();
+
+ switch (command_sent_) {
+ case COMMAND_RETR:
+ rv = ProcessResponseRETR(response);
+ break;
+ case COMMAND_LIST:
+ rv = ProcessResponseLIST(response);
+ break;
+ default:
+ // Multiple responses for other commands are invalid.
+ return Stop(ERR_INVALID_RESPONSE);
+ }
+ }
+
+ return rv;
+}
+
+// Used to prepare and send FTP command.
+int FtpNetworkTransaction::SendFtpCommand(const std::string& command,
+ const std::string& command_for_log,
+ Command cmd) {
+ // If we send a new command when we still have unprocessed responses
+ // for previous commands, the response receiving code will have no way to know
+ // which responses are for which command.
+ DCHECK(!ctrl_response_buffer_->ResponseAvailable());
+
+ DCHECK(!write_command_buf_.get());
+ DCHECK(!write_buf_.get());
+
+ if (!IsValidFTPCommandString(command)) {
+ // Callers should validate the command themselves and return a more specific
+ // error code.
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ command_sent_ = cmd;
+
+ write_command_buf_ = new IOBufferWithSize(command.length() + 2);
+ write_buf_ = new DrainableIOBuffer(write_command_buf_.get(),
+ write_command_buf_->size());
+ memcpy(write_command_buf_->data(), command.data(), command.length());
+ memcpy(write_command_buf_->data() + command.length(), kCRLF, 2);
+
+ net_log_.AddEvent(NetLog::TYPE_FTP_COMMAND_SENT,
+ NetLog::StringCallback("command", &command_for_log));
+
+ next_state_ = STATE_CTRL_WRITE;
+ return OK;
+}
+
+std::string FtpNetworkTransaction::GetRequestPathForFtpCommand(
+ bool is_directory) const {
+ std::string path(current_remote_directory_);
+ if (request_->url.has_path()) {
+ std::string gurl_path(request_->url.path());
+
+ // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos != std::string::npos)
+ gurl_path.resize(pos);
+
+ path.append(gurl_path);
+ }
+ // Make sure that if the path is expected to be a file, it won't end
+ // with a trailing slash.
+ if (!is_directory && path.length() > 1 && path[path.length() - 1] == '/')
+ path.erase(path.length() - 1);
+ UnescapeRule::Type unescape_rules = UnescapeRule::SPACES |
+ UnescapeRule::URL_SPECIAL_CHARS;
+ // This may unescape to non-ASCII characters, but we allow that. See the
+ // comment for IsValidFTPCommandString.
+ path = net::UnescapeURLComponent(path, unescape_rules);
+
+ if (system_type_ == SYSTEM_TYPE_VMS) {
+ if (is_directory)
+ path = FtpUtil::UnixDirectoryPathToVMS(path);
+ else
+ path = FtpUtil::UnixFilePathToVMS(path);
+ }
+
+ DCHECK(IsValidFTPCommandString(path));
+ return path;
+}
+
+void FtpNetworkTransaction::DetectTypecode() {
+ if (!request_->url.has_path())
+ return;
+ std::string gurl_path(request_->url.path());
+
+ // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path.
+ std::string::size_type pos = gurl_path.rfind(';');
+ if (pos == std::string::npos)
+ return;
+ std::string typecode_string(gurl_path.substr(pos));
+ if (typecode_string == ";type=a") {
+ data_type_ = DATA_TYPE_ASCII;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=i") {
+ data_type_ = DATA_TYPE_IMAGE;
+ resource_type_ = RESOURCE_TYPE_FILE;
+ } else if (typecode_string == ";type=d") {
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+ }
+}
+
+int FtpNetworkTransaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CTRL_RESOLVE_HOST:
+ DCHECK(rv == OK);
+ rv = DoCtrlResolveHost();
+ break;
+ case STATE_CTRL_RESOLVE_HOST_COMPLETE:
+ rv = DoCtrlResolveHostComplete(rv);
+ break;
+ case STATE_CTRL_CONNECT:
+ DCHECK(rv == OK);
+ rv = DoCtrlConnect();
+ break;
+ case STATE_CTRL_CONNECT_COMPLETE:
+ rv = DoCtrlConnectComplete(rv);
+ break;
+ case STATE_CTRL_READ:
+ DCHECK(rv == OK);
+ rv = DoCtrlRead();
+ break;
+ case STATE_CTRL_READ_COMPLETE:
+ rv = DoCtrlReadComplete(rv);
+ break;
+ case STATE_CTRL_WRITE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWrite();
+ break;
+ case STATE_CTRL_WRITE_COMPLETE:
+ rv = DoCtrlWriteComplete(rv);
+ break;
+ case STATE_CTRL_WRITE_USER:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteUSER();
+ break;
+ case STATE_CTRL_WRITE_PASS:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePASS();
+ break;
+ case STATE_CTRL_WRITE_SYST:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteSYST();
+ break;
+ case STATE_CTRL_WRITE_PWD:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePWD();
+ break;
+ case STATE_CTRL_WRITE_TYPE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteTYPE();
+ break;
+ case STATE_CTRL_WRITE_EPSV:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteEPSV();
+ break;
+ case STATE_CTRL_WRITE_PASV:
+ DCHECK(rv == OK);
+ rv = DoCtrlWritePASV();
+ break;
+ case STATE_CTRL_WRITE_RETR:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteRETR();
+ break;
+ case STATE_CTRL_WRITE_SIZE:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteSIZE();
+ break;
+ case STATE_CTRL_WRITE_CWD:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteCWD();
+ break;
+ case STATE_CTRL_WRITE_LIST:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteLIST();
+ break;
+ case STATE_CTRL_WRITE_QUIT:
+ DCHECK(rv == OK);
+ rv = DoCtrlWriteQUIT();
+ break;
+ case STATE_DATA_CONNECT:
+ DCHECK(rv == OK);
+ rv = DoDataConnect();
+ break;
+ case STATE_DATA_CONNECT_COMPLETE:
+ rv = DoDataConnectComplete(rv);
+ break;
+ case STATE_DATA_READ:
+ DCHECK(rv == OK);
+ rv = DoDataRead();
+ break;
+ case STATE_DATA_READ_COMPLETE:
+ rv = DoDataReadComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int FtpNetworkTransaction::DoCtrlResolveHost() {
+ next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE;
+
+ HostResolver::RequestInfo info(HostPortPair::FromURL(request_->url));
+ // No known referrer.
+ return resolver_.Resolve(
+ info, &addresses_,
+ base::Bind(&FtpNetworkTransaction::OnIOComplete, base::Unretained(this)),
+ net_log_);
+}
+
+int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_CTRL_CONNECT;
+ return result;
+}
+
+int FtpNetworkTransaction::DoCtrlConnect() {
+ next_state_ = STATE_CTRL_CONNECT_COMPLETE;
+ ctrl_socket_ = socket_factory_->CreateTransportClientSocket(
+ addresses_, net_log_.net_log(), net_log_.source());
+ net_log_.AddEvent(
+ NetLog::TYPE_FTP_CONTROL_CONNECTION,
+ ctrl_socket_->NetLog().source().ToEventParametersCallback());
+ return ctrl_socket_->Connect(io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlConnectComplete(int result) {
+ if (result == OK) {
+ // Put the peer's IP address and port into the response.
+ IPEndPoint ip_endpoint;
+ result = ctrl_socket_->GetPeerAddress(&ip_endpoint);
+ if (result == OK) {
+ response_.socket_address = HostPortPair::FromIPEndPoint(ip_endpoint);
+ next_state_ = STATE_CTRL_READ;
+
+ if (ip_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4) {
+ // Do not use EPSV for IPv4 connections. Some servers become confused
+ // and we time out while waiting to connect. PASV is perfectly fine for
+ // IPv4. Note that this blacklists IPv4 not to use EPSV instead of
+ // whitelisting IPv6 to use it, to make the code more future-proof:
+ // all future protocols should just use EPSV.
+ use_epsv_ = false;
+ }
+ }
+ }
+ return result;
+}
+
+int FtpNetworkTransaction::DoCtrlRead() {
+ next_state_ = STATE_CTRL_READ_COMPLETE;
+ return ctrl_socket_->Read(read_ctrl_buf_.get(), kCtrlBufLen, io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlReadComplete(int result) {
+ if (result == 0) {
+ // Some servers (for example Pure-FTPd) apparently close the control
+ // connection when anonymous login is not permitted. For more details
+ // see http://crbug.com/25023.
+ if (command_sent_ == COMMAND_USER &&
+ credentials_.username() == ASCIIToUTF16("anonymous")) {
+ response_.needs_auth = true;
+ }
+ return Stop(ERR_EMPTY_RESPONSE);
+ }
+ if (result < 0)
+ return Stop(result);
+
+ ctrl_response_buffer_->ConsumeData(read_ctrl_buf_->data(), result);
+
+ if (!ctrl_response_buffer_->ResponseAvailable()) {
+ // Read more data from the control socket.
+ next_state_ = STATE_CTRL_READ;
+ return OK;
+ }
+
+ return ProcessCtrlResponse();
+}
+
+int FtpNetworkTransaction::DoCtrlWrite() {
+ next_state_ = STATE_CTRL_WRITE_COMPLETE;
+
+ return ctrl_socket_->Write(
+ write_buf_.get(), write_buf_->BytesRemaining(), io_callback_);
+}
+
+int FtpNetworkTransaction::DoCtrlWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ write_buf_->DidConsume(result);
+ if (write_buf_->BytesRemaining() == 0) {
+ // Clear the write buffer.
+ write_buf_ = NULL;
+ write_command_buf_ = NULL;
+
+ next_state_ = STATE_CTRL_READ;
+ } else {
+ next_state_ = STATE_CTRL_WRITE;
+ }
+ return OK;
+}
+
+// FTP Commands and responses
+
+// USER Command.
+int FtpNetworkTransaction::DoCtrlWriteUSER() {
+ std::string command = "USER " + UTF16ToUTF8(credentials_.username());
+
+ if (!IsValidFTPCommandString(command))
+ return Stop(ERR_MALFORMED_IDENTITY);
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, "USER ***", COMMAND_USER);
+}
+
+int FtpNetworkTransaction::ProcessResponseUSER(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_SYST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ next_state_ = STATE_CTRL_WRITE_PASS;
+ break;
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PASS command.
+int FtpNetworkTransaction::DoCtrlWritePASS() {
+ std::string command = "PASS " + UTF16ToUTF8(credentials_.password());
+
+ if (!IsValidFTPCommandString(command))
+ return Stop(ERR_MALFORMED_IDENTITY);
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, "PASS ***", COMMAND_PASS);
+}
+
+int FtpNetworkTransaction::ProcessResponsePASS(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_SYST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ response_.needs_auth = true;
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// SYST command.
+int FtpNetworkTransaction::DoCtrlWriteSYST() {
+ std::string command = "SYST";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_SYST);
+}
+
+int FtpNetworkTransaction::ProcessResponseSYST(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK: {
+ // All important info should be on the first line.
+ std::string line = response.lines[0];
+ // The response should be ASCII, which allows us to do case-insensitive
+ // comparisons easily. If it is not ASCII, we leave the system type
+ // as unknown.
+ if (IsStringASCII(line)) {
+ line = StringToLowerASCII(line);
+
+ // Remove all whitespace, to correctly handle cases like fancy "V M S"
+ // response instead of "VMS".
+ RemoveChars(line, kWhitespaceASCII, &line);
+
+ // The "magic" strings we test for below have been gathered by an
+ // empirical study. VMS needs to come first because some VMS systems
+ // also respond with "UNIX emulation", which is not perfect. It is much
+ // more reliable to talk to these servers in their native language.
+ if (line.find("vms") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_VMS;
+ } else if (line.find("l8") != std::string::npos ||
+ line.find("unix") != std::string::npos ||
+ line.find("bsd") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_UNIX;
+ } else if (line.find("win32") != std::string::npos ||
+ line.find("windows") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_WINDOWS;
+ } else if (line.find("os/2") != std::string::npos) {
+ system_type_ = SYSTEM_TYPE_OS2;
+ }
+ }
+ next_state_ = STATE_CTRL_WRITE_PWD;
+ break;
+ }
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // Server does not recognize the SYST command so proceed.
+ next_state_ = STATE_CTRL_WRITE_PWD;
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PWD command.
+int FtpNetworkTransaction::DoCtrlWritePWD() {
+ std::string command = "PWD";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_PWD);
+}
+
+int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK: {
+ // The info we look for should be on the first line.
+ std::string line = response.lines[0];
+ if (line.empty())
+ return Stop(ERR_INVALID_RESPONSE);
+ std::string::size_type quote_pos = line.find('"');
+ if (quote_pos != std::string::npos) {
+ line = line.substr(quote_pos + 1);
+ quote_pos = line.find('"');
+ if (quote_pos == std::string::npos)
+ return Stop(ERR_INVALID_RESPONSE);
+ line = line.substr(0, quote_pos);
+ }
+ if (system_type_ == SYSTEM_TYPE_VMS)
+ line = FtpUtil::VMSPathToUnix(line);
+ if (line.length() && line[line.length() - 1] == '/')
+ line.erase(line.length() - 1);
+ current_remote_directory_ = line;
+ next_state_ = STATE_CTRL_WRITE_TYPE;
+ break;
+ }
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// TYPE command.
+int FtpNetworkTransaction::DoCtrlWriteTYPE() {
+ std::string command = "TYPE ";
+ if (data_type_ == DATA_TYPE_ASCII) {
+ command += "A";
+ } else if (data_type_ == DATA_TYPE_IMAGE) {
+ command += "I";
+ } else {
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_TYPE);
+}
+
+int FtpNetworkTransaction::ProcessResponseTYPE(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// EPSV command
+int FtpNetworkTransaction::DoCtrlWriteEPSV() {
+ const std::string command = "EPSV";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_EPSV);
+}
+
+int FtpNetworkTransaction::ProcessResponseEPSV(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ if (!ExtractPortFromEPSVResponse( response, &data_connection_port_))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ case ERROR_CLASS_PERMANENT_ERROR:
+ use_epsv_ = false;
+ next_state_ = STATE_CTRL_WRITE_PASV;
+ return OK;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// PASV command
+int FtpNetworkTransaction::DoCtrlWritePASV() {
+ std::string command = "PASV";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_PASV);
+}
+
+int FtpNetworkTransaction::ProcessResponsePASV(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ if (!ExtractPortFromPASVResponse(response, &data_connection_port_))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (data_connection_port_ < 1024 ||
+ !IsPortAllowedByFtp(data_connection_port_))
+ return Stop(ERR_UNSAFE_PORT);
+ next_state_ = STATE_DATA_CONNECT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// RETR command
+int FtpNetworkTransaction::DoCtrlWriteRETR() {
+ std::string command = "RETR " + GetRequestPathForFtpCommand(false);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_RETR);
+}
+
+int FtpNetworkTransaction::ProcessResponseRETR(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
+ resource_type_ = RESOURCE_TYPE_FILE;
+ break;
+ case ERROR_CLASS_OK:
+ resource_type_ = RESOURCE_TYPE_FILE;
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // Code 550 means "Failed to open file". Other codes are unrelated,
+ // like "Not logged in" etc.
+ if (response.status_code != 550 || resource_type_ == RESOURCE_TYPE_FILE)
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+
+ // It's possible that RETR failed because the path is a directory.
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
+
+ // We're going to try CWD next, but first send a PASV one more time,
+ // because some FTP servers, including FileZilla, require that.
+ // See http://crbug.com/25316.
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ // We should be sure about our resource type now. Otherwise we risk
+ // an infinite loop (RETR can later send CWD, and CWD can later send RETR).
+ DCHECK_NE(RESOURCE_TYPE_UNKNOWN, resource_type_);
+
+ return OK;
+}
+
+// SIZE command
+int FtpNetworkTransaction::DoCtrlWriteSIZE() {
+ std::string command = "SIZE " + GetRequestPathForFtpCommand(false);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_SIZE);
+}
+
+int FtpNetworkTransaction::ProcessResponseSIZE(
+ const FtpCtrlResponse& response) {
+ State state_after_size;
+ if (resource_type_ == RESOURCE_TYPE_FILE)
+ state_after_size = STATE_CTRL_WRITE_RETR;
+ else
+ state_after_size = STATE_CTRL_WRITE_CWD;
+
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_OK:
+ if (response.lines.size() != 1)
+ return Stop(ERR_INVALID_RESPONSE);
+ int64 size;
+ if (!base::StringToInt64(response.lines[0], &size))
+ return Stop(ERR_INVALID_RESPONSE);
+ if (size < 0)
+ return Stop(ERR_INVALID_RESPONSE);
+
+ // A successful response to SIZE does not mean the resource is a file.
+ // Some FTP servers (for example, the qnx one) send a SIZE even for
+ // directories.
+ response_.expected_content_size = size;
+
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ next_state_ = state_after_size;
+ break;
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ ResetDataConnectionAfterError(state_after_size);
+ break;
+ case ERROR_CLASS_PERMANENT_ERROR:
+ // It's possible that SIZE failed because the path is a directory.
+ if (resource_type_ == RESOURCE_TYPE_UNKNOWN &&
+ response.status_code != 550) {
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ }
+
+ ResetDataConnectionAfterError(state_after_size);
+ break;
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ return OK;
+}
+
+// CWD command
+int FtpNetworkTransaction::DoCtrlWriteCWD() {
+ std::string command = "CWD " + GetRequestPathForFtpCommand(true);
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_CWD);
+}
+
+int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) {
+ // We should never issue CWD if we know the target resource is a file.
+ DCHECK_NE(RESOURCE_TYPE_FILE, resource_type_);
+
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_OK:
+ next_state_ = STATE_CTRL_WRITE_LIST;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ // Some FTP servers send response 451 (not a valid CWD response according
+ // to RFC 959) instead of 550.
+ if (response.status_code == 451)
+ return ProcessResponseCWDNotADirectory();
+
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ if (response.status_code == 550)
+ return ProcessResponseCWDNotADirectory();
+
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+
+ return OK;
+}
+
+int FtpNetworkTransaction::ProcessResponseCWDNotADirectory() {
+ if (resource_type_ == RESOURCE_TYPE_DIRECTORY) {
+ // We're assuming that the resource is a directory, but the server
+ // says it's not true. The most probable interpretation is that it
+ // doesn't exist (with FTP we can't be sure).
+ return Stop(ERR_FILE_NOT_FOUND);
+ }
+
+ // We are here because SIZE failed and we are not sure what the resource
+ // type is. It could still be file, and SIZE could fail because of
+ // an access error (http://crbug.com/56734). Try RETR just to be sure.
+ resource_type_ = RESOURCE_TYPE_FILE;
+
+ ResetDataConnectionAfterError(STATE_CTRL_WRITE_RETR);
+ return OK;
+}
+
+// LIST command
+int FtpNetworkTransaction::DoCtrlWriteLIST() {
+ // Use the -l option for mod_ftp configured in LISTIsNLST mode: the option
+ // forces LIST output instead of NLST (which would be ambiguous for us
+ // to parse).
+ std::string command("LIST -l");
+ if (system_type_ == SYSTEM_TYPE_VMS)
+ command = "LIST *.*;0";
+
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_LIST);
+}
+
+int FtpNetworkTransaction::ProcessResponseLIST(
+ const FtpCtrlResponse& response) {
+ switch (GetErrorClass(response.status_code)) {
+ case ERROR_CLASS_INITIATED:
+ // We want the client to start reading the response at this point.
+ // It got here either through Start or RestartWithAuth. We want that
+ // method to complete. Not setting next state here will make DoLoop exit
+ // and in turn make Start/RestartWithAuth complete.
+ response_.is_directory_listing = true;
+ break;
+ case ERROR_CLASS_OK:
+ response_.is_directory_listing = true;
+ next_state_ = STATE_CTRL_WRITE_QUIT;
+ break;
+ case ERROR_CLASS_INFO_NEEDED:
+ return Stop(ERR_INVALID_RESPONSE);
+ case ERROR_CLASS_TRANSIENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ case ERROR_CLASS_PERMANENT_ERROR:
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
+ default:
+ NOTREACHED();
+ return Stop(ERR_UNEXPECTED);
+ }
+ return OK;
+}
+
+// QUIT command
+int FtpNetworkTransaction::DoCtrlWriteQUIT() {
+ std::string command = "QUIT";
+ next_state_ = STATE_CTRL_READ;
+ return SendFtpCommand(command, command, COMMAND_QUIT);
+}
+
+int FtpNetworkTransaction::ProcessResponseQUIT(
+ const FtpCtrlResponse& response) {
+ ctrl_socket_->Disconnect();
+ return last_error_;
+}
+
+// Data Connection
+
+int FtpNetworkTransaction::DoDataConnect() {
+ next_state_ = STATE_DATA_CONNECT_COMPLETE;
+ IPEndPoint ip_endpoint;
+ AddressList data_address;
+ // Connect to the same host as the control socket to prevent PASV port
+ // scanning attacks.
+ int rv = ctrl_socket_->GetPeerAddress(&ip_endpoint);
+ if (rv != OK)
+ return Stop(rv);
+ data_address = AddressList::CreateFromIPAddress(
+ ip_endpoint.address(), data_connection_port_);
+ data_socket_ = socket_factory_->CreateTransportClientSocket(
+ data_address, net_log_.net_log(), net_log_.source());
+ net_log_.AddEvent(
+ NetLog::TYPE_FTP_DATA_CONNECTION,
+ data_socket_->NetLog().source().ToEventParametersCallback());
+ return data_socket_->Connect(io_callback_);
+}
+
+int FtpNetworkTransaction::DoDataConnectComplete(int result) {
+ if (result != OK && use_epsv_) {
+ // It's possible we hit a broken server, sadly. They can break in different
+ // ways. Some time out, some reset a connection. Fall back to PASV.
+ // TODO(phajdan.jr): remember it for future transactions with this server.
+ // TODO(phajdan.jr): write a test for this code path.
+ use_epsv_ = false;
+ next_state_ = STATE_CTRL_WRITE_PASV;
+ return OK;
+ }
+
+ // Only record the connection error after we've applied all our fallbacks.
+ // We want to capture the final error, one we're not going to recover from.
+ RecordDataConnectionError(result);
+
+ if (result != OK)
+ return Stop(result);
+
+ next_state_ = state_after_data_connect_complete_;
+ return OK;
+}
+
+int FtpNetworkTransaction::DoDataRead() {
+ DCHECK(read_data_buf_.get());
+ DCHECK_GT(read_data_buf_len_, 0);
+
+ if (data_socket_ == NULL || !data_socket_->IsConnected()) {
+ // If we don't destroy the data socket completely, some servers will wait
+ // for us (http://crbug.com/21127). The half-closed TCP connection needs
+ // to be closed on our side too.
+ data_socket_.reset();
+
+ if (ctrl_socket_->IsConnected()) {
+ // Wait for the server's response, we should get it before sending QUIT.
+ next_state_ = STATE_CTRL_READ;
+ return OK;
+ }
+
+ // We are no longer connected to the server, so just finish the transaction.
+ return Stop(OK);
+ }
+
+ next_state_ = STATE_DATA_READ_COMPLETE;
+ read_data_buf_->data()[0] = 0;
+ return data_socket_->Read(
+ read_data_buf_.get(), read_data_buf_len_, io_callback_);
+}
+
+int FtpNetworkTransaction::DoDataReadComplete(int result) {
+ return result;
+}
+
+// We're using a histogram as a group of counters, with one bucket for each
+// enumeration value. We're only interested in the values of the counters.
+// Ignore the shape, average, and standard deviation of the histograms because
+// they are meaningless.
+//
+// We use two histograms. In the first histogram we tally whether the user has
+// seen an error of that type during the session. In the second histogram we
+// tally the total number of times the users sees each errer.
+void FtpNetworkTransaction::RecordDataConnectionError(int result) {
+ // Gather data for http://crbug.com/3073. See how many users have trouble
+ // establishing FTP data connection in passive FTP mode.
+ enum {
+ // Data connection successful.
+ NET_ERROR_OK = 0,
+
+ // Local firewall blocked the connection.
+ NET_ERROR_ACCESS_DENIED = 1,
+
+ // Connection timed out.
+ NET_ERROR_TIMED_OUT = 2,
+
+ // Connection has been estabilished, but then got broken (either reset
+ // or aborted).
+ NET_ERROR_CONNECTION_BROKEN = 3,
+
+ // Connection has been refused.
+ NET_ERROR_CONNECTION_REFUSED = 4,
+
+ // No connection to the internet.
+ NET_ERROR_INTERNET_DISCONNECTED = 5,
+
+ // Could not reach the destination address.
+ NET_ERROR_ADDRESS_UNREACHABLE = 6,
+
+ // A programming error in our network stack.
+ NET_ERROR_UNEXPECTED = 7,
+
+ // Other kind of error.
+ NET_ERROR_OTHER = 20,
+
+ NUM_OF_NET_ERROR_TYPES
+ } type;
+ switch (result) {
+ case OK:
+ type = NET_ERROR_OK;
+ break;
+ case ERR_ACCESS_DENIED:
+ case ERR_NETWORK_ACCESS_DENIED:
+ type = NET_ERROR_ACCESS_DENIED;
+ break;
+ case ERR_TIMED_OUT:
+ type = NET_ERROR_TIMED_OUT;
+ break;
+ case ERR_CONNECTION_ABORTED:
+ case ERR_CONNECTION_RESET:
+ case ERR_CONNECTION_CLOSED:
+ type = NET_ERROR_CONNECTION_BROKEN;
+ break;
+ case ERR_CONNECTION_FAILED:
+ case ERR_CONNECTION_REFUSED:
+ type = NET_ERROR_CONNECTION_REFUSED;
+ break;
+ case ERR_INTERNET_DISCONNECTED:
+ type = NET_ERROR_INTERNET_DISCONNECTED;
+ break;
+ case ERR_ADDRESS_INVALID:
+ case ERR_ADDRESS_UNREACHABLE:
+ type = NET_ERROR_ADDRESS_UNREACHABLE;
+ break;
+ case ERR_UNEXPECTED:
+ type = NET_ERROR_UNEXPECTED;
+ break;
+ default:
+ type = NET_ERROR_OTHER;
+ break;
+ };
+ static bool had_error_type[NUM_OF_NET_ERROR_TYPES];
+
+ DCHECK(type >= 0 && type < NUM_OF_NET_ERROR_TYPES);
+ if (!had_error_type[type]) {
+ had_error_type[type] = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorHappened",
+ type, NUM_OF_NET_ERROR_TYPES);
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpDataConnectionErrorCount",
+ type, NUM_OF_NET_ERROR_TYPES);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_network_transaction.h b/chromium/net/ftp/ftp_network_transaction.h
new file mode 100644
index 00000000000..5eb6aae7361
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction.h
@@ -0,0 +1,262 @@
+// Copyright (c) 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.
+
+#ifndef NET_FTP_FTP_NETWORK_TRANSACTION_H_
+#define NET_FTP_FTP_NETWORK_TRANSACTION_H_
+
+#include <string>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/auth.h"
+#include "net/base/net_log.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/ftp/ftp_ctrl_response_buffer.h"
+#include "net/ftp/ftp_response_info.h"
+#include "net/ftp/ftp_transaction.h"
+
+namespace net {
+
+class ClientSocketFactory;
+class FtpNetworkSession;
+class StreamSocket;
+
+class NET_EXPORT_PRIVATE FtpNetworkTransaction : public FtpTransaction {
+ public:
+ FtpNetworkTransaction(FtpNetworkSession* session,
+ ClientSocketFactory* socket_factory);
+ virtual ~FtpNetworkTransaction();
+
+ virtual int Stop(int error);
+ virtual int RestartIgnoringLastError(const CompletionCallback& callback);
+
+ // FtpTransaction methods:
+ virtual int Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual const FtpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual uint64 GetUploadProgress() const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(FtpNetworkTransactionTest,
+ DownloadTransactionEvilPasvUnsafeHost);
+
+ enum Command {
+ COMMAND_NONE,
+ COMMAND_USER,
+ COMMAND_PASS,
+ COMMAND_SYST,
+ COMMAND_TYPE,
+ COMMAND_EPSV,
+ COMMAND_PASV,
+ COMMAND_PWD,
+ COMMAND_SIZE,
+ COMMAND_RETR,
+ COMMAND_CWD,
+ COMMAND_LIST,
+ COMMAND_QUIT,
+ };
+
+ // Major categories of remote system types, as returned by SYST command.
+ enum SystemType {
+ SYSTEM_TYPE_UNKNOWN,
+ SYSTEM_TYPE_UNIX,
+ SYSTEM_TYPE_WINDOWS,
+ SYSTEM_TYPE_OS2,
+ SYSTEM_TYPE_VMS,
+ };
+
+ // Data representation type, see RFC 959 section 3.1.1. Data Types.
+ // We only support the two most popular data types.
+ enum DataType {
+ DATA_TYPE_ASCII,
+ DATA_TYPE_IMAGE,
+ };
+
+ // In FTP we need to issue different commands depending on whether a resource
+ // is a file or directory. If we don't know that, we're going to autodetect
+ // it.
+ enum ResourceType {
+ RESOURCE_TYPE_UNKNOWN,
+ RESOURCE_TYPE_FILE,
+ RESOURCE_TYPE_DIRECTORY,
+ };
+
+ enum State {
+ // Control connection states:
+ STATE_CTRL_RESOLVE_HOST,
+ STATE_CTRL_RESOLVE_HOST_COMPLETE,
+ STATE_CTRL_CONNECT,
+ STATE_CTRL_CONNECT_COMPLETE,
+ STATE_CTRL_READ,
+ STATE_CTRL_READ_COMPLETE,
+ STATE_CTRL_WRITE,
+ STATE_CTRL_WRITE_COMPLETE,
+ STATE_CTRL_WRITE_USER,
+ STATE_CTRL_WRITE_PASS,
+ STATE_CTRL_WRITE_SYST,
+ STATE_CTRL_WRITE_TYPE,
+ STATE_CTRL_WRITE_EPSV,
+ STATE_CTRL_WRITE_PASV,
+ STATE_CTRL_WRITE_PWD,
+ STATE_CTRL_WRITE_RETR,
+ STATE_CTRL_WRITE_SIZE,
+ STATE_CTRL_WRITE_CWD,
+ STATE_CTRL_WRITE_LIST,
+ STATE_CTRL_WRITE_QUIT,
+ // Data connection states:
+ STATE_DATA_CONNECT,
+ STATE_DATA_CONNECT_COMPLETE,
+ STATE_DATA_READ,
+ STATE_DATA_READ_COMPLETE,
+ STATE_NONE
+ };
+
+ // Resets the members of the transaction so it can be restarted.
+ void ResetStateForRestart();
+
+ // Resets the data connection after an error and switches to |next_state|.
+ void ResetDataConnectionAfterError(State next_state);
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ // Executes correct ProcessResponse + command_name function based on last
+ // issued command. Returns error code.
+ int ProcessCtrlResponse();
+
+ int SendFtpCommand(const std::string& command,
+ const std::string& command_for_log,
+ Command cmd);
+
+ // Returns request path suitable to be included in an FTP command. If the path
+ // will be used as a directory, |is_directory| should be true.
+ std::string GetRequestPathForFtpCommand(bool is_directory) const;
+
+ // See if the request URL contains a typecode and make us respect it.
+ void DetectTypecode();
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoCtrlResolveHost();
+ int DoCtrlResolveHostComplete(int result);
+ int DoCtrlConnect();
+ int DoCtrlConnectComplete(int result);
+ int DoCtrlRead();
+ int DoCtrlReadComplete(int result);
+ int DoCtrlWrite();
+ int DoCtrlWriteComplete(int result);
+ int DoCtrlWriteUSER();
+ int ProcessResponseUSER(const FtpCtrlResponse& response);
+ int DoCtrlWritePASS();
+ int ProcessResponsePASS(const FtpCtrlResponse& response);
+ int DoCtrlWriteSYST();
+ int ProcessResponseSYST(const FtpCtrlResponse& response);
+ int DoCtrlWritePWD();
+ int ProcessResponsePWD(const FtpCtrlResponse& response);
+ int DoCtrlWriteTYPE();
+ int ProcessResponseTYPE(const FtpCtrlResponse& response);
+ int DoCtrlWriteEPSV();
+ int ProcessResponseEPSV(const FtpCtrlResponse& response);
+ int DoCtrlWritePASV();
+ int ProcessResponsePASV(const FtpCtrlResponse& response);
+ int DoCtrlWriteRETR();
+ int ProcessResponseRETR(const FtpCtrlResponse& response);
+ int DoCtrlWriteSIZE();
+ int ProcessResponseSIZE(const FtpCtrlResponse& response);
+ int DoCtrlWriteCWD();
+ int ProcessResponseCWD(const FtpCtrlResponse& response);
+ int ProcessResponseCWDNotADirectory();
+ int DoCtrlWriteLIST();
+ int ProcessResponseLIST(const FtpCtrlResponse& response);
+ int DoCtrlWriteQUIT();
+ int ProcessResponseQUIT(const FtpCtrlResponse& response);
+
+ int DoDataConnect();
+ int DoDataConnectComplete(int result);
+ int DoDataRead();
+ int DoDataReadComplete(int result);
+
+ void RecordDataConnectionError(int result);
+
+ Command command_sent_;
+
+ CompletionCallback io_callback_;
+ CompletionCallback user_callback_;
+
+ scoped_refptr<FtpNetworkSession> session_;
+
+ BoundNetLog net_log_;
+ const FtpRequestInfo* request_;
+ FtpResponseInfo response_;
+
+ // Cancels the outstanding request on destruction.
+ SingleRequestHostResolver resolver_;
+ AddressList addresses_;
+
+ // User buffer passed to the Read method for control socket.
+ scoped_refptr<IOBuffer> read_ctrl_buf_;
+
+ scoped_ptr<FtpCtrlResponseBuffer> ctrl_response_buffer_;
+
+ scoped_refptr<IOBuffer> read_data_buf_;
+ int read_data_buf_len_;
+
+ // Buffer holding the command line to be written to the control socket.
+ scoped_refptr<IOBufferWithSize> write_command_buf_;
+
+ // Buffer passed to the Write method of control socket. It actually writes
+ // to the write_command_buf_ at correct offset.
+ scoped_refptr<DrainableIOBuffer> write_buf_;
+
+ int last_error_;
+
+ SystemType system_type_;
+
+ // Data type to be used for the TYPE command.
+ DataType data_type_;
+
+ // Detected resource type (file or directory).
+ ResourceType resource_type_;
+
+ // Initially we favour EPSV over PASV for transfers but should any
+ // EPSV fail, we fall back to PASV for the duration of connection.
+ bool use_epsv_;
+
+ AuthCredentials credentials_;
+
+ // Current directory on the remote server, as returned by last PWD command,
+ // with any trailing slash removed.
+ std::string current_remote_directory_;
+
+ int data_connection_port_;
+
+ ClientSocketFactory* socket_factory_;
+
+ scoped_ptr<StreamSocket> ctrl_socket_;
+ scoped_ptr<StreamSocket> data_socket_;
+
+ State next_state_;
+
+ // State to switch to after data connection is complete.
+ State state_after_data_connect_complete_;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_NETWORK_TRANSACTION_H_
diff --git a/chromium/net/ftp/ftp_network_transaction_unittest.cc b/chromium/net/ftp/ftp_network_transaction_unittest.cc
new file mode 100644
index 00000000000..a244cc51c5a
--- /dev/null
+++ b/chromium/net/ftp/ftp_network_transaction_unittest.cc
@@ -0,0 +1,1602 @@
+// Copyright (c) 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.
+
+#include "net/ftp/ftp_network_transaction.h"
+
+#include "build/build_config.h"
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/ftp/ftp_network_session.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+// Size we use for IOBuffers used to receive data from the test data socket.
+const int kBufferSize = 128;
+
+} // namespace
+
+namespace net {
+
+class FtpSocketDataProvider : public DynamicSocketDataProvider {
+ public:
+ enum State {
+ NONE,
+ PRE_USER,
+ PRE_PASSWD,
+ PRE_SYST,
+ PRE_PWD,
+ PRE_TYPE,
+ PRE_SIZE,
+ PRE_EPSV,
+ PRE_PASV,
+ PRE_LIST,
+ PRE_RETR,
+ PRE_RETR_EPSV,
+ PRE_RETR_PASV,
+ PRE_CWD_EPSV,
+ PRE_CWD_PASV,
+ PRE_CWD,
+ PRE_QUIT,
+ PRE_NOPASV,
+ QUIT
+ };
+
+ FtpSocketDataProvider()
+ : failure_injection_state_(NONE),
+ multiline_welcome_(false),
+ use_epsv_(true),
+ data_type_('I') {
+ Init();
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_USER:
+ return Verify("USER anonymous\r\n", data, PRE_PASSWD,
+ "331 Password needed\r\n");
+ case PRE_PASSWD:
+ {
+ const char* response_one = "230 Welcome\r\n";
+ const char* response_multi = "230- One\r\n230- Two\r\n230 Three\r\n";
+ return Verify("PASS chrome@example.com\r\n", data, PRE_SYST,
+ multiline_welcome_ ? response_multi : response_one);
+ }
+ case PRE_SYST:
+ return Verify("SYST\r\n", data, PRE_PWD, "215 UNIX\r\n");
+ case PRE_PWD:
+ return Verify("PWD\r\n", data, PRE_TYPE,
+ "257 \"/\" is your current location\r\n");
+ case PRE_TYPE:
+ return Verify(std::string("TYPE ") + data_type_ + "\r\n", data,
+ use_epsv_ ? PRE_EPSV : PRE_PASV,
+ "200 TYPE set successfully\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_SIZE,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_CWD_EPSV:
+ return Verify("EPSV\r\n", data, PRE_CWD,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_RETR_EPSV:
+ return Verify("EPSV\r\n", data, PRE_RETR,
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
+ case PRE_CWD_PASV:
+ return Verify("PASV\r\n", data, PRE_CWD,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ case PRE_RETR_PASV:
+ return Verify("PASV\r\n", data, PRE_RETR,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, PRE_SIZE,
+ "227 Entering Passive Mode 127,0,0,1,123,456\r\n");
+ case PRE_NOPASV:
+ // Use unallocated 599 FTP error code to make sure it falls into the
+ // generic ERR_FTP_FAILED bucket.
+ return Verify("PASV\r\n", data, PRE_QUIT,
+ "599 fail\r\n");
+ case PRE_QUIT:
+ return Verify("QUIT\r\n", data, QUIT, "221 Goodbye.\r\n");
+ default:
+ NOTREACHED() << "State not handled " << state();
+ return MockWriteResult(ASYNC, ERR_UNEXPECTED);
+ }
+ }
+
+ void InjectFailure(State state, State next_state, const char* response) {
+ DCHECK_EQ(NONE, failure_injection_state_);
+ DCHECK_NE(NONE, state);
+ DCHECK_NE(NONE, next_state);
+ DCHECK_NE(state, next_state);
+ failure_injection_state_ = state;
+ failure_injection_next_state_ = next_state;
+ fault_response_ = response;
+ }
+
+ State state() const {
+ return state_;
+ }
+
+ virtual void Reset() OVERRIDE {
+ DynamicSocketDataProvider::Reset();
+ Init();
+ }
+
+ void set_multiline_welcome(bool multiline) { multiline_welcome_ = multiline; }
+
+ bool use_epsv() const { return use_epsv_; }
+ void set_use_epsv(bool use_epsv) { use_epsv_ = use_epsv; }
+
+ void set_data_type(char data_type) { data_type_ = data_type; }
+
+ protected:
+ void Init() {
+ state_ = PRE_USER;
+ SimulateRead("220 host TestFTPd\r\n");
+ }
+
+ // If protocol fault injection has been requested, adjusts state and mocked
+ // read and returns true.
+ bool InjectFault() {
+ if (state_ != failure_injection_state_)
+ return false;
+ SimulateRead(fault_response_);
+ state_ = failure_injection_next_state_;
+ return true;
+ }
+
+ MockWriteResult Verify(const std::string& expected,
+ const std::string& data,
+ State next_state,
+ const char* next_read,
+ const size_t next_read_length) {
+ EXPECT_EQ(expected, data);
+ if (expected == data) {
+ state_ = next_state;
+ SimulateRead(next_read, next_read_length);
+ return MockWriteResult(ASYNC, data.length());
+ }
+ return MockWriteResult(ASYNC, ERR_UNEXPECTED);
+ }
+
+ MockWriteResult Verify(const std::string& expected,
+ const std::string& data,
+ State next_state,
+ const char* next_read) {
+ return Verify(expected, data, next_state,
+ next_read, std::strlen(next_read));
+ }
+
+
+ private:
+ State state_;
+ State failure_injection_state_;
+ State failure_injection_next_state_;
+ const char* fault_response_;
+
+ // If true, we will send multiple 230 lines as response after PASS.
+ bool multiline_welcome_;
+
+ // If true, we will use EPSV command.
+ bool use_epsv_;
+
+ // Data type to be used for TYPE command.
+ char data_type_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProvider);
+};
+
+class FtpSocketDataProviderDirectoryListing : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderDirectoryListing() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /\r\n", data,
+ use_epsv() ? PRE_CWD_EPSV : PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD /\r\n", data, PRE_LIST, "200 OK\r\n");
+ case PRE_LIST:
+ return Verify("LIST -l\r\n", data, PRE_QUIT, "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListing);
+};
+
+class FtpSocketDataProviderDirectoryListingWithPasvFallback
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingWithPasvFallback() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 no EPSV for you\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE /\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderDirectoryListingWithPasvFallback);
+};
+
+class FtpSocketDataProviderDirectoryListingZeroSize
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingZeroSize() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /\r\n", data, PRE_CWD, "213 0\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListingZeroSize);
+};
+
+class FtpSocketDataProviderVMSDirectoryListing : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderVMSDirectoryListing() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SYST:
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
+ case PRE_PWD:
+ return Verify("PWD\r\n", data, PRE_TYPE,
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV, "500 Invalid command\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data, PRE_LIST,
+ "200 OK\r\n");
+ case PRE_LIST:
+ return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSDirectoryListing);
+};
+
+class FtpSocketDataProviderVMSDirectoryListingRootDirectory
+ : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SYST:
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
+ case PRE_PWD:
+ return Verify("PWD\r\n", data, PRE_TYPE,
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 EPSV command unknown\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_CWD_PASV,
+ "550 I can only retrieve regular files\r\n");
+ case PRE_CWD:
+ return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data, PRE_LIST,
+ "200 OK\r\n");
+ case PRE_LIST:
+ return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory);
+};
+
+class FtpSocketDataProviderFileDownloadWithFileTypecode
+ : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderFileDownloadWithFileTypecode() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data, PRE_RETR,
+ "213 18\r\n");
+ case PRE_RETR:
+ return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithFileTypecode);
+};
+
+class FtpSocketDataProviderFileDownload : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderFileDownload() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data, PRE_CWD,
+ "213 18\r\n");
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "550 Not a directory\r\n");
+ case PRE_RETR:
+ return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownload);
+};
+
+class FtpSocketDataProviderFileNotFound : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderFileNotFound() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data,
+ use_epsv() ? PRE_CWD_EPSV : PRE_CWD_PASV,
+ "550 File Not Found\r\n");
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "550 File Not Found\r\n");
+ case PRE_RETR:
+ return Verify("RETR /file\r\n", data, PRE_QUIT,
+ "550 File Not Found\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileNotFound);
+};
+
+class FtpSocketDataProviderFileDownloadWithPasvFallback
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadWithPasvFallback() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 No can do\r\n");
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data, PRE_RETR_PASV,
+ "550 Not a directory\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithPasvFallback);
+};
+
+class FtpSocketDataProviderFileDownloadZeroSize
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadZeroSize() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data, PRE_CWD,
+ "213 0\r\n");
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "550 not a directory\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadZeroSize);
+};
+
+class FtpSocketDataProviderFileDownloadCWD451
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadCWD451() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_CWD:
+ return Verify("CWD /file\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "451 not a directory\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadCWD451);
+};
+
+class FtpSocketDataProviderVMSFileDownload : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderVMSFileDownload() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SYST:
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
+ case PRE_PWD:
+ return Verify("PWD\r\n", data, PRE_TYPE,
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, PRE_PASV,
+ "500 EPSV command unknown\r\n");
+ case PRE_SIZE:
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_CWD,
+ "213 18\r\n");
+ case PRE_CWD:
+ return Verify("CWD ANONYMOUS_ROOT:[file]\r\n", data, PRE_RETR_PASV,
+ "550 Not a directory\r\n");
+ case PRE_RETR:
+ return Verify("RETR ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_QUIT,
+ "200 OK\r\n");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSFileDownload);
+};
+
+class FtpSocketDataProviderEscaping : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEscaping() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE / !\"#$%y\200\201\r\n", data, PRE_CWD,
+ "213 18\r\n");
+ case PRE_CWD:
+ return Verify("CWD / !\"#$%y\200\201\r\n", data,
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
+ "550 Not a directory\r\n");
+ case PRE_RETR:
+ return Verify("RETR / !\"#$%y\200\201\r\n", data, PRE_QUIT,
+ "200 OK\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEscaping);
+};
+
+class FtpSocketDataProviderFileDownloadTransferStarting
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadTransferStarting() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_RETR:
+ return Verify("RETR /file\r\n", data, PRE_QUIT,
+ "125-Data connection already open.\r\n"
+ "125 Transfer starting.\r\n"
+ "226 Transfer complete.\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadTransferStarting);
+};
+
+class FtpSocketDataProviderDirectoryListingTransferStarting
+ : public FtpSocketDataProviderDirectoryListing {
+ public:
+ FtpSocketDataProviderDirectoryListingTransferStarting() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_LIST:
+ return Verify("LIST -l\r\n", data, PRE_QUIT,
+ "125-Data connection already open.\r\n"
+ "125 Transfer starting.\r\n"
+ "226 Transfer complete.\r\n");
+ default:
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(
+ FtpSocketDataProviderDirectoryListingTransferStarting);
+};
+
+class FtpSocketDataProviderFileDownloadInvalidResponse
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderFileDownloadInvalidResponse() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ // Use unallocated 599 FTP error code to make sure it falls into the
+ // generic ERR_FTP_FAILED bucket.
+ return Verify("SIZE /file\r\n", data, PRE_QUIT,
+ "599 Evil Response\r\n"
+ "599 More Evil\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadInvalidResponse);
+};
+
+class FtpSocketDataProviderEvilEpsv : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
+ State expected_state)
+ : epsv_response_(epsv_response),
+ epsv_response_length_(std::strlen(epsv_response)),
+ expected_state_(expected_state) {}
+
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
+ size_t epsv_response_length,
+ State expected_state)
+ : epsv_response_(epsv_response),
+ epsv_response_length_(epsv_response_length),
+ expected_state_(expected_state) {}
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_EPSV:
+ return Verify("EPSV\r\n", data, expected_state_,
+ epsv_response_, epsv_response_length_);
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* epsv_response_;
+ const size_t epsv_response_length_;
+ const State expected_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilEpsv);
+};
+
+class FtpSocketDataProviderEvilPasv
+ : public FtpSocketDataProviderFileDownloadWithPasvFallback {
+ public:
+ FtpSocketDataProviderEvilPasv(const char* pasv_response, State expected_state)
+ : pasv_response_(pasv_response),
+ expected_state_(expected_state) {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_PASV:
+ return Verify("PASV\r\n", data, expected_state_, pasv_response_);
+ default:
+ return FtpSocketDataProviderFileDownloadWithPasvFallback::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* pasv_response_;
+ const State expected_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilPasv);
+};
+
+class FtpSocketDataProviderEvilSize : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEvilSize(const char* size_response, State expected_state)
+ : size_response_(size_response),
+ expected_state_(expected_state) {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_SIZE:
+ return Verify("SIZE /file\r\n", data, expected_state_, size_response_);
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* size_response_;
+ const State expected_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilSize);
+};
+
+class FtpSocketDataProviderEvilLogin
+ : public FtpSocketDataProviderFileDownload {
+ public:
+ FtpSocketDataProviderEvilLogin(const char* expected_user,
+ const char* expected_password)
+ : expected_user_(expected_user),
+ expected_password_(expected_password) {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_USER:
+ return Verify(std::string("USER ") + expected_user_ + "\r\n", data,
+ PRE_PASSWD, "331 Password needed\r\n");
+ case PRE_PASSWD:
+ return Verify(std::string("PASS ") + expected_password_ + "\r\n", data,
+ PRE_SYST, "230 Welcome\r\n");
+ default:
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
+ }
+ }
+
+ private:
+ const char* expected_user_;
+ const char* expected_password_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilLogin);
+};
+
+class FtpSocketDataProviderCloseConnection : public FtpSocketDataProvider {
+ public:
+ FtpSocketDataProviderCloseConnection() {
+ }
+
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE {
+ if (InjectFault())
+ return MockWriteResult(ASYNC, data.length());
+ switch (state()) {
+ case PRE_USER:
+ return Verify("USER anonymous\r\n", data,
+ PRE_QUIT, "");
+ default:
+ return FtpSocketDataProvider::OnWrite(data);
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderCloseConnection);
+};
+
+class FtpNetworkTransactionTest
+ : public PlatformTest,
+ public ::testing::WithParamInterface<int> {
+ public:
+ FtpNetworkTransactionTest()
+ : host_resolver_(new MockHostResolver),
+ session_(new FtpNetworkSession(host_resolver_.get())),
+ transaction_(session_.get(), &mock_socket_factory_) {
+ scoped_refptr<RuleBasedHostResolverProc> rules(
+ new RuleBasedHostResolverProc(NULL));
+ if (GetFamily() == AF_INET) {
+ rules->AddIPLiteralRule("*", "127.0.0.1", "127.0.0.1");
+ } else if (GetFamily() == AF_INET6) {
+ rules->AddIPLiteralRule("*", "::1", "::1");
+ } else {
+ NOTREACHED();
+ }
+ host_resolver_->set_rules(rules.get());
+ }
+
+ protected:
+ // Accessor to make code refactoring-friendly, e.g. when we change the way
+ // parameters are passed (like more parameters).
+ int GetFamily() {
+ return GetParam();
+ }
+
+ FtpRequestInfo GetRequestInfo(const std::string& url) {
+ FtpRequestInfo info;
+ info.url = GURL(url);
+ return info;
+ }
+
+ void ExecuteTransaction(FtpSocketDataProvider* ctrl_socket,
+ const char* request,
+ int data_socket,
+ int expected_result) {
+ // Expect EPSV usage for non-IPv4 control connections.
+ ctrl_socket->set_use_epsv((GetFamily() != AF_INET));
+
+ mock_socket_factory_.AddSocketDataProvider(ctrl_socket);
+
+ std::string mock_data("mock-data");
+ MockRead data_reads[] = {
+ // Usually FTP servers close the data connection after the entire data has
+ // been received.
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(mock_data.c_str()),
+ };
+
+ ScopedVector<StaticSocketDataProvider> data_sockets;
+ data_sockets.reserve(data_socket);
+ for (int i = 0; i < data_socket + 1; i++) {
+ // We only read from one data socket, other ones are dummy.
+ if (i == data_socket) {
+ data_sockets.push_back(new StaticSocketDataProvider(
+ data_reads, arraysize(data_reads), NULL, 0));
+ } else {
+ data_sockets.push_back(new StaticSocketDataProvider);
+ }
+ mock_socket_factory_.AddSocketDataProvider(data_sockets[i]);
+ }
+
+ FtpRequestInfo request_info = GetRequestInfo(request);
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ EXPECT_NE(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ ASSERT_EQ(expected_result, callback_.WaitForResult());
+ if (expected_result == OK) {
+ scoped_refptr<IOBuffer> io_buffer(new IOBuffer(kBufferSize));
+ memset(io_buffer->data(), 0, kBufferSize);
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Read(io_buffer.get(), kBufferSize,
+ callback_.callback()));
+ ASSERT_EQ(static_cast<int>(mock_data.length()),
+ callback_.WaitForResult());
+ EXPECT_EQ(mock_data, std::string(io_buffer->data(), mock_data.length()));
+
+ // Do another Read to detect that the data socket is now closed.
+ int rv = transaction_.Read(io_buffer.get(), kBufferSize,
+ callback_.callback());
+ if (rv == ERR_IO_PENDING) {
+ EXPECT_EQ(0, callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(0, rv);
+ }
+ }
+ EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket->state());
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ }
+
+ void TransactionFailHelper(FtpSocketDataProvider* ctrl_socket,
+ const char* request,
+ FtpSocketDataProvider::State state,
+ FtpSocketDataProvider::State next_state,
+ const char* response,
+ int expected_result) {
+ ctrl_socket->InjectFailure(state, next_state, response);
+ ExecuteTransaction(ctrl_socket, request, 1, expected_result);
+ }
+
+ scoped_ptr<MockHostResolver> host_resolver_;
+ scoped_refptr<FtpNetworkSession> session_;
+ MockClientSocketFactory mock_socket_factory_;
+ FtpNetworkTransaction transaction_;
+ TestCompletionCallback callback_;
+};
+
+TEST_P(FtpNetworkTransactionTest, FailedLookup) {
+ FtpRequestInfo request_info = GetRequestInfo("ftp://badhost");
+ scoped_refptr<RuleBasedHostResolverProc> rules(
+ new RuleBasedHostResolverProc(NULL));
+ rules->AddSimulatedFailure("badhost");
+ host_resolver_->set_rules(rules.get());
+
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_.GetLoadState());
+}
+
+// Check that when determining the host, the square brackets decorating IPv6
+// literals in URLs are stripped.
+TEST_P(FtpNetworkTransactionTest, StripBracketsFromIPv6Literals) {
+ // This test only makes sense for IPv6 connections.
+ if (GetFamily() != AF_INET6)
+ return;
+
+ host_resolver_->rules()->AddSimulatedFailure("[::1]");
+
+ // We start a transaction that is expected to fail with ERR_INVALID_RESPONSE.
+ // The important part of this test is to make sure that we don't fail with
+ // ERR_NAME_NOT_RESOLVED, since that would mean the decorated hostname
+ // was used.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 99999999999999999999999999999999\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://[::1]/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransaction) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
+ EXPECT_EQ((GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
+ transaction_.GetResponseInfo()->socket_address.host());
+ EXPECT_EQ(21, transaction_.GetResponseInfo()->socket_address.port());
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithPasvFallback) {
+ FtpSocketDataProviderDirectoryListingWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithTypecode) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host;type=d", 1, OK);
+
+ EXPECT_TRUE(transaction_.GetResponseInfo()->is_directory_listing);
+ EXPECT_EQ(-1, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcome) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ctrl_socket.set_multiline_welcome(true);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads2) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ctrl_socket.set_short_read_limit(2);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads5) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcomeShort) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // The client will not consume all three 230 lines. That's good, we want to
+ // test that scenario.
+ ctrl_socket.allow_unconsumed_reads(true);
+ ctrl_socket.set_multiline_welcome(true);
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+// Regression test for http://crbug.com/60555.
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionZeroSize) {
+ FtpSocketDataProviderDirectoryListingZeroSize ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 0, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMS) {
+ FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/dir", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMSRootDirectory) {
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionTransferStarting) {
+ FtpSocketDataProviderDirectoryListingTransferStarting ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransaction) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+ EXPECT_EQ((GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
+ transaction_.GetResponseInfo()->socket_address.host());
+ EXPECT_EQ(21, transaction_.GetResponseInfo()->socket_address.port());
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithPasvFallback) {
+ FtpSocketDataProviderFileDownloadWithPasvFallback ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeA) {
+ FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
+ ctrl_socket.set_data_type('A');
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=a", 0, OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeI) {
+ FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=i", 0, OK);
+
+ // We pass an artificial value of 18 as a response to the SIZE command.
+ EXPECT_EQ(18, transaction_.GetResponseInfo()->expected_content_size);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionMultilineWelcome) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_multiline_welcome(true);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads2) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_short_read_limit(2);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads5) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ ctrl_socket.set_short_read_limit(5);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionZeroSize) {
+ FtpSocketDataProviderFileDownloadZeroSize ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionCWD451) {
+ FtpSocketDataProviderFileDownloadCWD451 ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionVMS) {
+ FtpSocketDataProviderVMSFileDownload ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionTransferStarting) {
+ FtpSocketDataProviderFileDownloadTransferStarting ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionInvalidResponse) {
+ FtpSocketDataProviderFileDownloadInvalidResponse ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvReallyBadFormat) {
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort1) {
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,0,22)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort2) {
+ // Still unsafe. 1 * 256 + 2 = 258, which is < 1024.
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,1,2)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort3) {
+ // Still unsafe. 3 * 256 + 4 = 772, which is < 1024.
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,3,4)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort4) {
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,8,1)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafeHost) {
+ FtpSocketDataProviderEvilPasv ctrl_socket(
+ "227 Portscan (10,1,2,3,123,456)\r\n", FtpSocketDataProvider::PRE_SIZE);
+ ctrl_socket.set_use_epsv(GetFamily() != AF_INET);
+ std::string mock_data("mock-data");
+ MockRead data_reads[] = {
+ MockRead(mock_data.c_str()),
+ };
+ StaticSocketDataProvider data_socket1;
+ StaticSocketDataProvider data_socket2(data_reads, arraysize(data_reads),
+ NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket);
+ mock_socket_factory_.AddSocketDataProvider(&data_socket1);
+ mock_socket_factory_.AddSocketDataProvider(&data_socket2);
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
+
+ // Start the transaction.
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ ASSERT_EQ(OK, callback_.WaitForResult());
+
+ // The transaction fires the callback when we can start reading data. That
+ // means that the data socket should be open.
+ MockTCPClientSocket* data_socket =
+ static_cast<MockTCPClientSocket*>(transaction_.data_socket_.get());
+ ASSERT_TRUE(data_socket);
+ ASSERT_TRUE(data_socket->IsConnected());
+
+ // Even if the PASV response specified some other address, we connect
+ // to the address we used for control connection (which could be 127.0.0.1
+ // or ::1 depending on whether we use IPv6).
+ for (AddressList::const_iterator it = data_socket->addresses().begin();
+ it != data_socket->addresses().end(); ++it) {
+ EXPECT_NE("10.1.2.3", it->ToStringWithoutPort());
+ }
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat1) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat2) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat3) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat4) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||||)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat5) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ const char response[] = "227 Portscan (\0\0\031773\0)\r\n";
+ FtpSocketDataProviderEvilEpsv ctrl_socket(response, sizeof(response)-1,
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort1) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort2) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||258|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort3) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||772|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort4) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||2049|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvWeirdSep) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$31744$)\r\n",
+ FtpSocketDataProvider::PRE_SIZE);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest,
+ DownloadTransactionEvilEpsvWeirdSepUnsafePort) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$317$)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_UNSAFE_PORT);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvIllegalHost) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|2|::1|31744|)\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadUsername) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello%0Aworld", "test");
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%0Aworld:test@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadPassword) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello%0Dworld");
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%0Dworld@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInLogin) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello world", "test");
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%20world:test@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInPassword) {
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello world");
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%20world@host/file", 1, OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, EvilRestartUser) {
+ FtpSocketDataProvider ctrl_socket1;
+ ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "530 Login authentication failed\r\n");
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket1);
+
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
+
+ MockRead ctrl_reads[] = {
+ MockRead("220 host TestFTPd\r\n"),
+ MockRead("221 Goodbye!\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite ctrl_writes[] = {
+ MockWrite("QUIT\r\n"),
+ };
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.RestartWithAuth(
+ AuthCredentials(
+ ASCIIToUTF16("foo\nownz0red"),
+ ASCIIToUTF16("innocent")),
+ callback_.callback()));
+ EXPECT_EQ(ERR_MALFORMED_IDENTITY, callback_.WaitForResult());
+}
+
+TEST_P(FtpNetworkTransactionTest, EvilRestartPassword) {
+ FtpSocketDataProvider ctrl_socket1;
+ ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "530 Login authentication failed\r\n");
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket1);
+
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.Start(&request_info, callback_.callback(),
+ BoundNetLog()));
+ ASSERT_EQ(ERR_FTP_FAILED, callback_.WaitForResult());
+
+ MockRead ctrl_reads[] = {
+ MockRead("220 host TestFTPd\r\n"),
+ MockRead("331 User okay, send password\r\n"),
+ MockRead("221 Goodbye!\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite ctrl_writes[] = {
+ MockWrite("USER innocent\r\n"),
+ MockWrite("QUIT\r\n"),
+ };
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, arraysize(ctrl_reads),
+ ctrl_writes, arraysize(ctrl_writes));
+ mock_socket_factory_.AddSocketDataProvider(&ctrl_socket2);
+ ASSERT_EQ(ERR_IO_PENDING,
+ transaction_.RestartWithAuth(
+ AuthCredentials(ASCIIToUTF16("innocent"),
+ ASCIIToUTF16("foo\nownz0red")),
+ callback_.callback()));
+ EXPECT_EQ(ERR_MALFORMED_IDENTITY, callback_.WaitForResult());
+}
+
+TEST_P(FtpNetworkTransactionTest, Escaping) {
+ FtpSocketDataProviderEscaping ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/%20%21%22%23%24%25%79%80%81",
+ 1, OK);
+}
+
+// Test for http://crbug.com/23794.
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilSize) {
+ // Try to overflow int64 in the response.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 99999999999999999999999999999999\r\n",
+ FtpSocketDataProvider::PRE_QUIT);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, ERR_INVALID_RESPONSE);
+}
+
+// Test for http://crbug.com/36360.
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionBigSize) {
+ // Pass a valid, but large file size. The transaction should not fail.
+ FtpSocketDataProviderEvilSize ctrl_socket(
+ "213 3204427776\r\n",
+ FtpSocketDataProvider::PRE_CWD);
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 1, OK);
+ EXPECT_EQ(3204427776LL,
+ transaction_.GetResponseInfo()->expected_content_size);
+}
+
+// Regression test for http://crbug.com/25023.
+TEST_P(FtpNetworkTransactionTest, CloseConnection) {
+ FtpSocketDataProviderCloseConnection ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host", 1, ERR_EMPTY_RESPONSE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailUser) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_USER,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "530 Login authentication failed\r\n",
+ ERR_FTP_FAILED);
+}
+
+// Regression test for http://crbug.com/38707.
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass503) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "503 Bad sequence of commands\r\n",
+ ERR_FTP_BAD_COMMAND_SEQUENCE);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailSyst) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_SYST,
+ FtpSocketDataProvider::PRE_PWD,
+ "599 fail\r\n",
+ OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPwd) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_PWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailType) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_TYPE,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailEpsv) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_EPSV,
+ FtpSocketDataProvider::PRE_NOPASV,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailCwd) {
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host",
+ FtpSocketDataProvider::PRE_CWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailList) {
+ FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/dir",
+ FtpSocketDataProvider::PRE_LIST,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailUser) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_USER,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPass) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_PASSWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "530 Login authentication failed\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailSyst) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_SYST,
+ FtpSocketDataProvider::PRE_PWD,
+ "599 fail\r\n",
+ OK);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPwd) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_PWD,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailType) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_TYPE,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailEpsv) {
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
+ if (GetFamily() == AF_INET)
+ return;
+
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_EPSV,
+ FtpSocketDataProvider::PRE_NOPASV,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailRetr) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
+ // ERR_FTP_FAILED bucket.
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_RETR,
+ FtpSocketDataProvider::PRE_QUIT,
+ "599 fail\r\n",
+ ERR_FTP_FAILED);
+}
+
+TEST_P(FtpNetworkTransactionTest, FileNotFound) {
+ FtpSocketDataProviderFileNotFound ctrl_socket;
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", 2, ERR_FTP_FAILED);
+}
+
+// Test for http://crbug.com/38845.
+TEST_P(FtpNetworkTransactionTest, ZeroLengthDirInPWD) {
+ FtpSocketDataProviderFileDownload ctrl_socket;
+ TransactionFailHelper(&ctrl_socket,
+ "ftp://host/file",
+ FtpSocketDataProvider::PRE_PWD,
+ FtpSocketDataProvider::PRE_TYPE,
+ "257 \"\"\r\n",
+ OK);
+}
+
+INSTANTIATE_TEST_CASE_P(FTP,
+ FtpNetworkTransactionTest,
+ ::testing::Values(AF_INET, AF_INET6));
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_request_info.h b/chromium/net/ftp/ftp_request_info.h
new file mode 100644
index 00000000000..be7702fd3d9
--- /dev/null
+++ b/chromium/net/ftp/ftp_request_info.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2010 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_FTP_FTP_REQUEST_INFO_H_
+#define NET_FTP_FTP_REQUEST_INFO_H_
+
+#include "url/gurl.h"
+
+namespace net {
+
+class FtpRequestInfo {
+ public:
+ // The requested URL.
+ GURL url;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_REQUEST_INFO_H_
diff --git a/chromium/net/ftp/ftp_response_info.cc b/chromium/net/ftp/ftp_response_info.cc
new file mode 100644
index 00000000000..ddbd1edf3a4
--- /dev/null
+++ b/chromium/net/ftp/ftp_response_info.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 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/ftp/ftp_response_info.h"
+
+namespace net {
+
+FtpResponseInfo::FtpResponseInfo()
+ : needs_auth(false),
+ expected_content_size(-1),
+ is_directory_listing(false) {
+}
+
+FtpResponseInfo::~FtpResponseInfo() {}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_response_info.h b/chromium/net/ftp/ftp_response_info.h
new file mode 100644
index 00000000000..8bae8c19931
--- /dev/null
+++ b/chromium/net/ftp/ftp_response_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2010 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_FTP_FTP_RESPONSE_INFO_H_
+#define NET_FTP_FTP_RESPONSE_INFO_H_
+
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+
+namespace net {
+
+class FtpResponseInfo {
+ public:
+ FtpResponseInfo();
+ ~FtpResponseInfo();
+
+ // True if authentication failed and valid authentication credentials are
+ // needed.
+ bool needs_auth;
+
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this time could be "far" in the past.
+ base::Time request_time;
+
+ // The time at which the response headers were received. For cached
+ // responses, this time could be "far" in the past.
+ base::Time response_time;
+
+ // Expected content size, in bytes, as reported by SIZE command. Only valid
+ // for file downloads. -1 means unknown size.
+ int64 expected_content_size;
+
+ // True if the response data is of a directory listing.
+ bool is_directory_listing;
+
+ // Remote address of the socket which fetched this resource.
+ HostPortPair socket_address;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_RESPONSE_INFO_H_
diff --git a/chromium/net/ftp/ftp_server_type_histograms.cc b/chromium/net/ftp/ftp_server_type_histograms.cc
new file mode 100644
index 00000000000..84592ed023f
--- /dev/null
+++ b/chromium/net/ftp/ftp_server_type_histograms.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 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/ftp/ftp_server_type_histograms.h"
+
+#include "base/metrics/histogram.h"
+
+namespace net {
+
+// We're using a histogram as a group of counters, with one bucket for each
+// enumeration value. We're only interested in the values of the counters.
+// Ignore the shape, average, and standard deviation of the histograms because
+// they are meaningless.
+//
+// We use two histograms. In the first histogram we tally whether the user has
+// seen an FTP server of a given type during that session. In the second
+// histogram we tally the number of transactions with FTP server of a given type
+// the user has made during that session.
+void UpdateFtpServerTypeHistograms(FtpServerType type) {
+ static bool had_server_type[NUM_OF_SERVER_TYPES];
+ if (type >= 0 && type < NUM_OF_SERVER_TYPES) {
+ if (!had_server_type[type]) {
+ had_server_type[type] = true;
+ UMA_HISTOGRAM_ENUMERATION("Net.HadFtpServerType2",
+ type, NUM_OF_SERVER_TYPES);
+ }
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.FtpServerTypeCount2",
+ type, NUM_OF_SERVER_TYPES);
+}
+
+} // namespace net
diff --git a/chromium/net/ftp/ftp_server_type_histograms.h b/chromium/net/ftp/ftp_server_type_histograms.h
new file mode 100644
index 00000000000..5ae862cd21a
--- /dev/null
+++ b/chromium/net/ftp/ftp_server_type_histograms.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2009 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_FTP_FTP_SERVER_TYPE_HISTOGRAMS_H_
+#define NET_FTP_FTP_SERVER_TYPE_HISTOGRAMS_H_
+
+// The UpdateFtpServerTypeHistograms function collects statistics related
+// to the types of FTP servers that our users are encountering.
+
+namespace net {
+
+enum FtpServerType {
+ // Record cases in which we couldn't parse the server's response. That means
+ // a server type we don't recognize, a security attack (when what we're
+ // connecting to isn't an FTP server), or a broken server.
+ SERVER_UNKNOWN = 0,
+
+ SERVER_LS = 1, // Server using /bin/ls -l listing style.
+ SERVER_WINDOWS = 2, // Server using Windows listing style.
+ SERVER_VMS = 3, // Server using VMS listing style.
+ SERVER_NETWARE = 4, // Server using Netware listing style.
+ SERVER_OS2 = 5, // Server using OS/2 listing style.
+
+ NUM_OF_SERVER_TYPES
+};
+
+void UpdateFtpServerTypeHistograms(FtpServerType type);
+
+} // namespace net
+
+#endif // NET_FTP_FTP_SERVER_TYPE_HISTOGRAMS_H_
diff --git a/chromium/net/ftp/ftp_transaction.h b/chromium/net/ftp/ftp_transaction.h
new file mode 100644
index 00000000000..f0e1b41f26d
--- /dev/null
+++ b/chromium/net/ftp/ftp_transaction.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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_FTP_FTP_TRANSACTION_H_
+#define NET_FTP_FTP_TRANSACTION_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class AuthCredentials;
+class FtpResponseInfo;
+class FtpRequestInfo;
+class BoundNetLog;
+
+// Represents a single FTP transaction.
+class NET_EXPORT_PRIVATE FtpTransaction {
+ public:
+ // Stops any pending IO and destroys the transaction object.
+ virtual ~FtpTransaction() {}
+
+ // Starts the FTP transaction (i.e., sends the FTP request).
+ //
+ // Returns OK if the transaction could be started synchronously, which means
+ // that the request was served from the cache (only supported for directory
+ // listings). ERR_IO_PENDING is returned to indicate that the
+ // CompletionCallback will be notified once response info is available or if
+ // an IO error occurs. Any other return value indicates that the transaction
+ // could not be started.
+ //
+ // Regardless of the return value, the caller is expected to keep the
+ // request_info object alive until Destroy is called on the transaction.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ virtual int Start(const FtpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) = 0;
+
+ // Restarts the FTP transaction with authentication credentials.
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) = 0;
+
+ // Once response info is available for the transaction, response data may be
+ // read by calling this method.
+ //
+ // Response data is copied into the given buffer and the number of bytes
+ // copied is returned. ERR_IO_PENDING is returned if response data is not
+ // yet available. The CompletionCallback is notified when the data copy
+ // completes, and it is passed the number of bytes that were successfully
+ // copied. Or, if a read error occurs, the CompletionCallback is notified of
+ // the error. Any other negative return value indicates that the transaction
+ // could not be read.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Returns the response info for this transaction or NULL if the response
+ // info is not available.
+ virtual const FtpResponseInfo* GetResponseInfo() const = 0;
+
+ // Returns the load state for this transaction.
+ virtual LoadState GetLoadState() const = 0;
+
+ // Returns the upload progress in bytes. If there is no upload data,
+ // zero will be returned.
+ virtual uint64 GetUploadProgress() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_TRANSACTION_H_
diff --git a/chromium/net/ftp/ftp_transaction_factory.h b/chromium/net/ftp/ftp_transaction_factory.h
new file mode 100644
index 00000000000..db32fbeeac5
--- /dev/null
+++ b/chromium/net/ftp/ftp_transaction_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_FTP_FTP_TRANSACTION_FACTORY_H_
+#define NET_FTP_FTP_TRANSACTION_FACTORY_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class FtpTransaction;
+
+// An interface to a class that can create FtpTransaction objects.
+class NET_EXPORT FtpTransactionFactory {
+ public:
+ virtual ~FtpTransactionFactory() {}
+
+ // Creates a FtpTransaction object.
+ virtual FtpTransaction* CreateTransaction() = 0;
+
+ // Suspends the creation of new transactions. If |suspend| is false, creation
+ // of new transactions is resumed.
+ virtual void Suspend(bool suspend) = 0;
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_TRANSACTION_FACTORY_H_
diff --git a/chromium/net/ftp/ftp_util.cc b/chromium/net/ftp/ftp_util.cc
new file mode 100644
index 00000000000..c5e18c8f79d
--- /dev/null
+++ b/chromium/net/ftp/ftp_util.cc
@@ -0,0 +1,376 @@
+// Copyright (c) 2011 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/ftp/ftp_util.h"
+
+#include <map>
+#include <vector>
+
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/char_iterator.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "third_party/icu/source/common/unicode/uchar.h"
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
+#include "third_party/icu/source/i18n/unicode/dtfmtsym.h"
+
+using base::StringPiece16;
+
+// For examples of Unix<->VMS path conversions, see the unit test file. On VMS
+// a path looks differently depending on whether it's a file or directory.
+
+namespace net {
+
+// static
+std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) {
+ if (unix_path.empty())
+ return std::string();
+
+ base::StringTokenizer tokenizer(unix_path, "/");
+ std::vector<std::string> tokens;
+ while (tokenizer.GetNext())
+ tokens.push_back(tokenizer.token());
+
+ if (unix_path[0] == '/') {
+ // It's an absolute path.
+
+ if (tokens.empty()) {
+ DCHECK_EQ(1U, unix_path.length());
+ return "[]";
+ }
+
+ if (tokens.size() == 1)
+ return unix_path.substr(1); // Drop the leading slash.
+
+ std::string result(tokens[0] + ":[");
+ if (tokens.size() == 2) {
+ // Don't ask why, it just works that way on VMS.
+ result.append("000000");
+ } else {
+ result.append(tokens[1]);
+ for (size_t i = 2; i < tokens.size() - 1; i++)
+ result.append("." + tokens[i]);
+ }
+ result.append("]" + tokens[tokens.size() - 1]);
+ return result;
+ }
+
+ if (tokens.size() == 1)
+ return unix_path;
+
+ std::string result("[");
+ for (size_t i = 0; i < tokens.size() - 1; i++)
+ result.append("." + tokens[i]);
+ result.append("]" + tokens[tokens.size() - 1]);
+ return result;
+}
+
+// static
+std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) {
+ if (unix_path.empty())
+ return std::string();
+
+ std::string path(unix_path);
+
+ if (path[path.length() - 1] != '/')
+ path.append("/");
+
+ // Reuse logic from UnixFilePathToVMS by appending a fake file name to the
+ // real path and removing it after conversion.
+ path.append("x");
+ path = UnixFilePathToVMS(path);
+ return path.substr(0, path.length() - 1);
+}
+
+// static
+std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
+ if (vms_path.empty())
+ return ".";
+
+ if (vms_path[0] == '/') {
+ // This is not really a VMS path. Most likely the server is emulating UNIX.
+ // Return path as-is.
+ return vms_path;
+ }
+
+ if (vms_path == "[]")
+ return "/";
+
+ std::string result(vms_path);
+ if (vms_path[0] == '[') {
+ // It's a relative path.
+ ReplaceFirstSubstringAfterOffset(&result, 0, "[.", std::string());
+ } else {
+ // It's an absolute path.
+ result.insert(0, "/");
+ ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/");
+ ReplaceSubstringsAfterOffset(&result, 0, ":[", "/");
+ }
+ std::replace(result.begin(), result.end(), '.', '/');
+ std::replace(result.begin(), result.end(), ']', '/');
+
+ // Make sure the result doesn't end with a slash.
+ if (result.length() && result[result.length() - 1] == '/')
+ result = result.substr(0, result.length() - 1);
+
+ return result;
+}
+
+namespace {
+
+// Lazy-initialized map of abbreviated month names.
+class AbbreviatedMonthsMap {
+ public:
+ static AbbreviatedMonthsMap* GetInstance() {
+ return Singleton<AbbreviatedMonthsMap>::get();
+ }
+
+ // Converts abbreviated month name |text| to its number (in range 1-12).
+ // On success returns true and puts the number in |number|.
+ bool GetMonthNumber(const base::string16& text, int* number) {
+ // Ignore the case of the month names. The simplest way to handle that
+ // is to make everything lowercase.
+ base::string16 text_lower(base::i18n::ToLower(text));
+
+ if (map_.find(text_lower) == map_.end())
+ return false;
+
+ *number = map_[text_lower];
+ return true;
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<AbbreviatedMonthsMap>;
+
+ // Constructor, initializes the map based on ICU data. It is much faster
+ // to do that just once.
+ AbbreviatedMonthsMap() {
+ int32_t locales_count;
+ const icu::Locale* locales =
+ icu::DateFormat::getAvailableLocales(locales_count);
+
+ for (int32_t locale = 0; locale < locales_count; locale++) {
+ UErrorCode status(U_ZERO_ERROR);
+
+ icu::DateFormatSymbols format_symbols(locales[locale], status);
+
+ // If we cannot get format symbols for some locale, it's not a fatal
+ // error. Just try another one.
+ if (U_FAILURE(status))
+ continue;
+
+ int32_t months_count;
+ const icu::UnicodeString* months =
+ format_symbols.getShortMonths(months_count);
+
+ for (int32_t month = 0; month < months_count; month++) {
+ base::string16 month_name(months[month].getBuffer(),
+ static_cast<size_t>(months[month].length()));
+
+ // Ignore the case of the month names. The simplest way to handle that
+ // is to make everything lowercase.
+ month_name = base::i18n::ToLower(month_name);
+
+ map_[month_name] = month + 1;
+
+ // Sometimes ICU returns longer strings, but in FTP listings a shorter
+ // abbreviation is used (for example for the Russian locale). Make sure
+ // we always have a map entry for a three-letter abbreviation.
+ map_[month_name.substr(0, 3)] = month + 1;
+ }
+ }
+
+ // Fail loudly if the data returned by ICU is obviously incomplete.
+ // This is intended to catch cases like http://crbug.com/177428
+ // much earlier. Note that the issue above turned out to be non-trivial
+ // to reproduce - crash data is much better indicator of a problem
+ // than incomplete bug reports.
+ CHECK_EQ(1, map_[ASCIIToUTF16("jan")]);
+ CHECK_EQ(2, map_[ASCIIToUTF16("feb")]);
+ CHECK_EQ(3, map_[ASCIIToUTF16("mar")]);
+ CHECK_EQ(4, map_[ASCIIToUTF16("apr")]);
+ CHECK_EQ(5, map_[ASCIIToUTF16("may")]);
+ CHECK_EQ(6, map_[ASCIIToUTF16("jun")]);
+ CHECK_EQ(7, map_[ASCIIToUTF16("jul")]);
+ CHECK_EQ(8, map_[ASCIIToUTF16("aug")]);
+ CHECK_EQ(9, map_[ASCIIToUTF16("sep")]);
+ CHECK_EQ(10, map_[ASCIIToUTF16("oct")]);
+ CHECK_EQ(11, map_[ASCIIToUTF16("nov")]);
+ CHECK_EQ(12, map_[ASCIIToUTF16("dec")]);
+ }
+
+ // Maps lowercase month names to numbers in range 1-12.
+ std::map<base::string16, int> map_;
+
+ DISALLOW_COPY_AND_ASSIGN(AbbreviatedMonthsMap);
+};
+
+} // namespace
+
+// static
+bool FtpUtil::AbbreviatedMonthToNumber(const base::string16& text,
+ int* number) {
+ return AbbreviatedMonthsMap::GetInstance()->GetMonthNumber(text, number);
+}
+
+// static
+bool FtpUtil::LsDateListingToTime(const base::string16& month,
+ const base::string16& day,
+ const base::string16& rest,
+ const base::Time& current_time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ if (!AbbreviatedMonthToNumber(month, &time_exploded.month)) {
+ // Work around garbage sent by some servers in the same column
+ // as the month. Take just last 3 characters of the string.
+ if (month.length() < 3 ||
+ !AbbreviatedMonthToNumber(month.substr(month.length() - 3),
+ &time_exploded.month)) {
+ return false;
+ }
+ }
+
+ if (!base::StringToInt(day, &time_exploded.day_of_month))
+ return false;
+ if (time_exploded.day_of_month > 31)
+ return false;
+
+ if (!base::StringToInt(rest, &time_exploded.year)) {
+ // Maybe it's time. Does it look like time (HH:MM)?
+ if (rest.length() == 5 && rest[2] == ':') {
+ if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 2),
+ &time_exploded.hour)) {
+ return false;
+ }
+
+ if (!base::StringToInt(StringPiece16(rest.begin() + 3, rest.begin() + 5),
+ &time_exploded.minute)) {
+ return false;
+ }
+ } else if (rest.length() == 4 && rest[1] == ':') {
+ // Sometimes it's just H:MM.
+ if (!base::StringToInt(StringPiece16(rest.begin(), rest.begin() + 1),
+ &time_exploded.hour)) {
+ return false;
+ }
+
+ if (!base::StringToInt(StringPiece16(rest.begin() + 2, rest.begin() + 4),
+ &time_exploded.minute)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ // Guess the year.
+ base::Time::Exploded current_exploded;
+ current_time.LocalExplode(&current_exploded);
+
+ // If it's not possible for the parsed date to be in the current year,
+ // use the previous year.
+ if (time_exploded.month > current_exploded.month ||
+ (time_exploded.month == current_exploded.month &&
+ time_exploded.day_of_month > current_exploded.day_of_month)) {
+ time_exploded.year = current_exploded.year - 1;
+ } else {
+ time_exploded.year = current_exploded.year;
+ }
+ }
+
+ // We don't know the time zone of the listing, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// static
+bool FtpUtil::WindowsDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result) {
+ base::Time::Exploded time_exploded = { 0 };
+
+ // Date should be in format MM-DD-YY[YY].
+ std::vector<base::string16> date_parts;
+ base::SplitString(date, '-', &date_parts);
+ if (date_parts.size() != 3)
+ return false;
+ if (!base::StringToInt(date_parts[0], &time_exploded.month))
+ return false;
+ if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
+ return false;
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
+ return false;
+ if (time_exploded.year < 0)
+ return false;
+ // If year has only two digits then assume that 00-79 is 2000-2079,
+ // and 80-99 is 1980-1999.
+ if (time_exploded.year < 80)
+ time_exploded.year += 2000;
+ else if (time_exploded.year < 100)
+ time_exploded.year += 1900;
+
+ // Time should be in format HH:MM[(AM|PM)]
+ if (time.length() < 5)
+ return false;
+
+ std::vector<base::string16> time_parts;
+ base::SplitString(time.substr(0, 5), ':', &time_parts);
+ if (time_parts.size() != 2)
+ return false;
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
+ return false;
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
+ return false;
+ if (!time_exploded.HasValidValues())
+ return false;
+
+ if (time.length() > 5) {
+ if (time.length() != 7)
+ return false;
+ base::string16 am_or_pm(time.substr(5, 2));
+ if (EqualsASCII(am_or_pm, "PM")) {
+ if (time_exploded.hour < 12)
+ time_exploded.hour += 12;
+ } else if (EqualsASCII(am_or_pm, "AM")) {
+ if (time_exploded.hour == 12)
+ time_exploded.hour = 0;
+ } else {
+ return false;
+ }
+ }
+
+ // We don't know the time zone of the server, so just use local time.
+ *result = base::Time::FromLocalExploded(time_exploded);
+ return true;
+}
+
+// static
+base::string16 FtpUtil::GetStringPartAfterColumns(const base::string16& text,
+ int columns) {
+ base::i18n::UTF16CharIterator iter(&text);
+
+ // TODO(jshin): Is u_isspace the right function to use here?
+ for (int i = 0; i < columns; i++) {
+ // Skip the leading whitespace.
+ while (!iter.end() && u_isspace(iter.get()))
+ iter.Advance();
+
+ // Skip the actual text of i-th column.
+ while (!iter.end() && !u_isspace(iter.get()))
+ iter.Advance();
+ }
+
+ base::string16 result(text.substr(iter.array_pos()));
+ TrimWhitespace(result, TRIM_ALL, &result);
+ return result;
+}
+
+} // namespace
diff --git a/chromium/net/ftp/ftp_util.h b/chromium/net/ftp/ftp_util.h
new file mode 100644
index 00000000000..3e8a899c7d4
--- /dev/null
+++ b/chromium/net/ftp/ftp_util.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 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_FTP_FTP_UTIL_H_
+#define NET_FTP_FTP_UTIL_H_
+
+#include <string>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+
+class NET_EXPORT_PRIVATE FtpUtil {
+ public:
+ // Converts Unix file path to VMS path (must be a file, and not a directory).
+ static std::string UnixFilePathToVMS(const std::string& unix_path);
+
+ // Converts Unix directory path to VMS path (must be a directory).
+ static std::string UnixDirectoryPathToVMS(const std::string& unix_path);
+
+ // Converts VMS path to Unix-style path.
+ static std::string VMSPathToUnix(const std::string& vms_path);
+
+ // Converts abbreviated month (like Nov) to its number (in range 1-12).
+ // Note: in some locales abbreviations are more than three letters long,
+ // and this function also handles them correctly.
+ static bool AbbreviatedMonthToNumber(const base::string16& text, int* number);
+
+ // Converts a "ls -l" date listing to time. The listing comes in three
+ // columns. The first one contains month, the second one contains day
+ // of month. The third one is either a time (and then we guess the year based
+ // on |current_time|), or is a year (and then we don't know the time).
+ static bool LsDateListingToTime(const base::string16& month,
+ const base::string16& day,
+ const base::string16& rest,
+ const base::Time& current_time,
+ base::Time* result);
+
+ // Converts a Windows date listing to time. Returns true on success.
+ static bool WindowsDateListingToTime(const base::string16& date,
+ const base::string16& time,
+ base::Time* result);
+
+ // Skips |columns| columns from |text| (whitespace-delimited), and returns the
+ // remaining part, without leading/trailing whitespace.
+ static base::string16 GetStringPartAfterColumns(const base::string16& text,
+ int columns);
+};
+
+} // namespace net
+
+#endif // NET_FTP_FTP_UTIL_H_
diff --git a/chromium/net/ftp/ftp_util_unittest.cc b/chromium/net/ftp/ftp_util_unittest.cc
new file mode 100644
index 00000000000..2aab7f49f5c
--- /dev/null
+++ b/chromium/net/ftp/ftp_util_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2011 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/ftp/ftp_util.h"
+
+#include "base/basictypes.h"
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(FtpUtilTest, UnixFilePathToVMS) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "" },
+ { "/", "[]" },
+ { "/a", "a" },
+ { "/a/b", "a:[000000]b" },
+ { "/a/b/c", "a:[b]c" },
+ { "/a/b/c/d", "a:[b.c]d" },
+ { "/a/b/c/d/e", "a:[b.c.d]e" },
+ { "a", "a" },
+ { "a/b", "[.a]b" },
+ { "a/b/c", "[.a.b]c" },
+ { "a/b/c/d", "[.a.b.c]d" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::UnixFilePathToVMS(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, UnixDirectoryPathToVMS) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "" },
+ { "/", "" },
+ { "/a", "a:[000000]" },
+ { "/a/", "a:[000000]" },
+ { "/a/b", "a:[b]" },
+ { "/a/b/", "a:[b]" },
+ { "/a/b/c", "a:[b.c]" },
+ { "/a/b/c/", "a:[b.c]" },
+ { "/a/b/c/d", "a:[b.c.d]" },
+ { "/a/b/c/d/", "a:[b.c.d]" },
+ { "/a/b/c/d/e", "a:[b.c.d.e]" },
+ { "/a/b/c/d/e/", "a:[b.c.d.e]" },
+ { "a", "[.a]" },
+ { "a/", "[.a]" },
+ { "a/b", "[.a.b]" },
+ { "a/b/", "[.a.b]" },
+ { "a/b/c", "[.a.b.c]" },
+ { "a/b/c/", "[.a.b.c]" },
+ { "a/b/c/d", "[.a.b.c.d]" },
+ { "a/b/c/d/", "[.a.b.c.d]" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::UnixDirectoryPathToVMS(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, VMSPathToUnix) {
+ const struct {
+ const char* input;
+ const char* expected_output;
+ } kTestCases[] = {
+ { "", "." },
+ { "[]", "/" },
+ { "a", "/a" },
+ { "a:[000000]", "/a" },
+ { "a:[000000]b", "/a/b" },
+ { "a:[b]", "/a/b" },
+ { "a:[b]c", "/a/b/c" },
+ { "a:[b.c]", "/a/b/c" },
+ { "a:[b.c]d", "/a/b/c/d" },
+ { "a:[b.c.d]", "/a/b/c/d" },
+ { "a:[b.c.d]e", "/a/b/c/d/e" },
+ { "a:[b.c.d.e]", "/a/b/c/d/e" },
+ { "[.a]", "a" },
+ { "[.a]b", "a/b" },
+ { "[.a.b]", "a/b" },
+ { "[.a.b]c", "a/b/c" },
+ { "[.a.b.c]", "a/b/c" },
+ { "[.a.b.c]d", "a/b/c/d" },
+ { "[.a.b.c.d]", "a/b/c/d" },
+ { "[.", "" },
+
+ // UNIX emulation:
+ { "/", "/" },
+ { "/a", "/a" },
+ { "/a/b", "/a/b" },
+ { "/a/b/c", "/a/b/c" },
+ { "/a/b/c/d", "/a/b/c/d" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ EXPECT_EQ(kTestCases[i].expected_output,
+ net::FtpUtil::VMSPathToUnix(kTestCases[i].input))
+ << kTestCases[i].input;
+ }
+}
+
+TEST(FtpUtilTest, LsDateListingToTime) {
+ base::Time mock_current_time;
+ ASSERT_TRUE(base::Time::FromString("Tue, 15 Nov 1994 12:45:26 GMT",
+ &mock_current_time));
+
+ const struct {
+ // Input.
+ const char* month;
+ const char* day;
+ const char* rest;
+
+ // Expected output.
+ int expected_year;
+ int expected_month;
+ int expected_day_of_month;
+ int expected_hour;
+ int expected_minute;
+ } kTestCases[] = {
+ { "Nov", "01", "2007", 2007, 11, 1, 0, 0 },
+ { "Jul", "25", "13:37", 1994, 7, 25, 13, 37 },
+
+ // Test date listings in German.
+ { "M\xc3\xa4r", "13", "2009", 2009, 3, 13, 0, 0 },
+ { "Mai", "1", "10:10", 1994, 5, 1, 10, 10 },
+ { "Okt", "14", "21:18", 1994, 10, 14, 21, 18 },
+ { "Dez", "25", "2008", 2008, 12, 25, 0, 0 },
+
+ // Test date listings in Russian.
+ { "\xd1\x8f\xd0\xbd\xd0\xb2", "1", "2011", 2011, 1, 1, 0, 0 },
+ { "\xd1\x84\xd0\xb5\xd0\xb2", "1", "2011", 2011, 2, 1, 0, 0 },
+ { "\xd0\xbc\xd0\xb0\xd1\x80", "1", "2011", 2011, 3, 1, 0, 0 },
+ { "\xd0\xb0\xd0\xbf\xd1\x80", "1", "2011", 2011, 4, 1, 0, 0 },
+ { "\xd0\xbc\xd0\xb0\xd0\xb9", "1", "2011", 2011, 5, 1, 0, 0 },
+ { "\xd0\xb8\xd1\x8e\xd0\xbd", "1", "2011", 2011, 6, 1, 0, 0 },
+ { "\xd0\xb8\xd1\x8e\xd0\xbb", "1", "2011", 2011, 7, 1, 0, 0 },
+ { "\xd0\xb0\xd0\xb2\xd0\xb3", "1", "2011", 2011, 8, 1, 0, 0 },
+ { "\xd1\x81\xd0\xb5\xd0\xbd", "1", "2011", 2011, 9, 1, 0, 0 },
+ { "\xd0\xbe\xd0\xba\xd1\x82", "1", "2011", 2011, 10, 1, 0, 0 },
+ { "\xd0\xbd\xd0\xbe\xd1\x8f", "1", "2011", 2011, 11, 1, 0, 0 },
+ { "\xd0\xb4\xd0\xb5\xd0\xba", "1", "2011", 2011, 12, 1, 0, 0 },
+
+ // Test current year detection.
+ { "Nov", "01", "12:00", 1994, 11, 1, 12, 0 },
+ { "Nov", "15", "12:00", 1994, 11, 15, 12, 0 },
+ { "Nov", "16", "12:00", 1993, 11, 16, 12, 0 },
+ { "Jan", "01", "08:30", 1994, 1, 1, 8, 30 },
+ { "Sep", "02", "09:00", 1994, 9, 2, 9, 0 },
+ { "Dec", "06", "21:00", 1993, 12, 6, 21, 0 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s %s", i,
+ kTestCases[i].month, kTestCases[i].day,
+ kTestCases[i].rest));
+
+ base::Time time;
+ ASSERT_TRUE(net::FtpUtil::LsDateListingToTime(
+ UTF8ToUTF16(kTestCases[i].month), UTF8ToUTF16(kTestCases[i].day),
+ UTF8ToUTF16(kTestCases[i].rest), mock_current_time, &time));
+
+ base::Time::Exploded time_exploded;
+ time.LocalExplode(&time_exploded);
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
+ EXPECT_EQ(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+}
+
+TEST(FtpUtilTest, WindowsDateListingToTime) {
+ const struct {
+ // Input.
+ const char* date;
+ const char* time;
+
+ // Expected output.
+ int expected_year;
+ int expected_month;
+ int expected_day_of_month;
+ int expected_hour;
+ int expected_minute;
+ } kTestCases[] = {
+ { "11-01-07", "12:42", 2007, 11, 1, 12, 42 },
+ { "11-01-07", "12:42AM", 2007, 11, 1, 0, 42 },
+ { "11-01-07", "12:42PM", 2007, 11, 1, 12, 42 },
+
+ { "11-01-2007", "12:42", 2007, 11, 1, 12, 42 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s", i,
+ kTestCases[i].date, kTestCases[i].time));
+
+ base::Time time;
+ ASSERT_TRUE(net::FtpUtil::WindowsDateListingToTime(
+ UTF8ToUTF16(kTestCases[i].date),
+ UTF8ToUTF16(kTestCases[i].time),
+ &time));
+
+ base::Time::Exploded time_exploded;
+ time.LocalExplode(&time_exploded);
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
+ EXPECT_EQ(0, time_exploded.second);
+ EXPECT_EQ(0, time_exploded.millisecond);
+ }
+}
+
+TEST(FtpUtilTest, GetStringPartAfterColumns) {
+ const struct {
+ const char* text;
+ int column;
+ const char* expected_result;
+ } kTestCases[] = {
+ { "", 0, "" },
+ { "", 1, "" },
+ { "foo abc", 0, "foo abc" },
+ { "foo abc", 1, "abc" },
+ { " foo abc", 0, "foo abc" },
+ { " foo abc", 1, "abc" },
+ { " foo abc", 2, "" },
+ { " foo abc ", 0, "foo abc" },
+ { " foo abc ", 1, "abc" },
+ { " foo abc ", 2, "" },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %d", i,
+ kTestCases[i].text, kTestCases[i].column));
+
+ EXPECT_EQ(ASCIIToUTF16(kTestCases[i].expected_result),
+ net::FtpUtil::GetStringPartAfterColumns(
+ ASCIIToUTF16(kTestCases[i].text), kTestCases[i].column));
+ }
+}
+
+} // namespace
diff --git a/chromium/net/http/des.cc b/chromium/net/http/des.cc
new file mode 100644
index 00000000000..79240cff72a
--- /dev/null
+++ b/chromium/net/http/des.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2011 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/http/des.h"
+
+#include "base/logging.h"
+
+#if defined(USE_OPENSSL)
+#include <openssl/des.h>
+#include "crypto/openssl_util.h"
+#elif defined(USE_NSS)
+#include <nss.h>
+#include <pk11pub.h>
+#include "crypto/nss_util.h"
+#elif defined(OS_MACOSX)
+#include <CommonCrypto/CommonCryptor.h>
+#elif defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+#include "crypto/scoped_capi_types.h"
+#endif
+
+// The Mac and Windows (CryptoAPI) versions of DESEncrypt are our own code.
+// DESSetKeyParity, DESMakeKey, and the Linux (NSS) version of DESEncrypt are
+// based on mozilla/security/manager/ssl/src/nsNTLMAuthModule.cpp,
+// CVS rev. 1.14.
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Set odd parity bit (in least significant bit position).
+static uint8 DESSetKeyParity(uint8 x) {
+ if ((((x >> 7) ^ (x >> 6) ^ (x >> 5) ^
+ (x >> 4) ^ (x >> 3) ^ (x >> 2) ^
+ (x >> 1)) & 0x01) == 0) {
+ x |= 0x01;
+ } else {
+ x &= 0xfe;
+ }
+ return x;
+}
+
+namespace net {
+
+void DESMakeKey(const uint8* raw, uint8* key) {
+ key[0] = DESSetKeyParity(raw[0]);
+ key[1] = DESSetKeyParity((raw[0] << 7) | (raw[1] >> 1));
+ key[2] = DESSetKeyParity((raw[1] << 6) | (raw[2] >> 2));
+ key[3] = DESSetKeyParity((raw[2] << 5) | (raw[3] >> 3));
+ key[4] = DESSetKeyParity((raw[3] << 4) | (raw[4] >> 4));
+ key[5] = DESSetKeyParity((raw[4] << 3) | (raw[5] >> 5));
+ key[6] = DESSetKeyParity((raw[5] << 2) | (raw[6] >> 6));
+ key[7] = DESSetKeyParity((raw[6] << 1));
+}
+
+#if defined(USE_OPENSSL)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ crypto::EnsureOpenSSLInit();
+
+ DES_key_schedule ks;
+ DES_set_key_unchecked(
+ reinterpret_cast<const_DES_cblock*>(const_cast<uint8*>(key)), &ks);
+
+ DES_ecb_encrypt(reinterpret_cast<const_DES_cblock*>(const_cast<uint8*>(src)),
+ reinterpret_cast<DES_cblock*>(hash), &ks, DES_ENCRYPT);
+}
+
+#elif defined(USE_NSS)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ CK_MECHANISM_TYPE cipher_mech = CKM_DES_ECB;
+ PK11SlotInfo* slot = NULL;
+ PK11SymKey* symkey = NULL;
+ PK11Context* ctxt = NULL;
+ SECItem key_item;
+ SECItem* param = NULL;
+ SECStatus rv;
+ unsigned int n;
+
+ crypto::EnsureNSSInit();
+
+ slot = PK11_GetInternalSlot();
+ if (!slot)
+ goto done;
+
+ key_item.data = const_cast<uint8*>(key);
+ key_item.len = 8;
+ symkey = PK11_ImportSymKey(slot, cipher_mech,
+ PK11_OriginUnwrap, CKA_ENCRYPT,
+ &key_item, NULL);
+ if (!symkey)
+ goto done;
+
+ // No initialization vector required.
+ param = PK11_ParamFromIV(cipher_mech, NULL);
+ if (!param)
+ goto done;
+
+ ctxt = PK11_CreateContextBySymKey(cipher_mech, CKA_ENCRYPT,
+ symkey, param);
+ if (!ctxt)
+ goto done;
+
+ rv = PK11_CipherOp(ctxt, hash, reinterpret_cast<int*>(&n), 8,
+ const_cast<uint8*>(src), 8);
+ if (rv != SECSuccess)
+ goto done;
+
+ // TODO(wtc): Should this be PK11_Finalize?
+ rv = PK11_DigestFinal(ctxt, hash+8, &n, 0);
+ if (rv != SECSuccess)
+ goto done;
+
+ done:
+ if (ctxt)
+ PK11_DestroyContext(ctxt, PR_TRUE);
+ if (symkey)
+ PK11_FreeSymKey(symkey);
+ if (param)
+ SECITEM_FreeItem(param, PR_TRUE);
+ if (slot)
+ PK11_FreeSlot(slot);
+}
+
+#elif defined(OS_MACOSX)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ CCCryptorStatus status;
+ size_t data_out_moved = 0;
+ status = CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionECBMode,
+ key, 8, NULL, src, 8, hash, 8, &data_out_moved);
+ DCHECK(status == kCCSuccess);
+ DCHECK(data_out_moved == 8);
+}
+
+#elif defined(OS_WIN)
+
+void DESEncrypt(const uint8* key, const uint8* src, uint8* hash) {
+ crypto::ScopedHCRYPTPROV provider;
+ if (!CryptAcquireContext(provider.receive(), NULL, NULL, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT))
+ return;
+
+ {
+ // Import the DES key.
+ struct KeyBlob {
+ BLOBHEADER header;
+ DWORD key_size;
+ BYTE key_data[8];
+ };
+ KeyBlob key_blob;
+ key_blob.header.bType = PLAINTEXTKEYBLOB;
+ key_blob.header.bVersion = CUR_BLOB_VERSION;
+ key_blob.header.reserved = 0;
+ key_blob.header.aiKeyAlg = CALG_DES;
+ key_blob.key_size = 8; // 64 bits
+ memcpy(key_blob.key_data, key, 8);
+
+ crypto::ScopedHCRYPTKEY key;
+ BOOL import_ok = CryptImportKey(provider,
+ reinterpret_cast<BYTE*>(&key_blob),
+ sizeof key_blob, 0, 0, key.receive());
+ // Destroy the copy of the key.
+ SecureZeroMemory(key_blob.key_data, sizeof key_blob.key_data);
+ if (!import_ok)
+ return;
+
+ // No initialization vector required.
+ DWORD cipher_mode = CRYPT_MODE_ECB;
+ if (!CryptSetKeyParam(key, KP_MODE, reinterpret_cast<BYTE*>(&cipher_mode),
+ 0))
+ return;
+
+ // CryptoAPI requires us to copy the plaintext to the output buffer first.
+ CopyMemory(hash, src, 8);
+ // Pass a 'Final' of FALSE, otherwise CryptEncrypt appends one additional
+ // block of padding to the data.
+ DWORD hash_len = 8;
+ CryptEncrypt(key, 0, FALSE, 0, hash, &hash_len, 8);
+ }
+}
+
+#endif
+
+} // namespace net
diff --git a/chromium/net/http/des.h b/chromium/net/http/des.h
new file mode 100644
index 00000000000..a1803ba3a39
--- /dev/null
+++ b/chromium/net/http/des.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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_HTTP_DES_H_
+#define NET_HTTP_DES_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DES support code for NTLM authentication.
+//
+// TODO(wtc): Turn this into a C++ API and move it to the base module.
+
+// Build a 64-bit DES key from a 56-bit raw key.
+NET_EXPORT_PRIVATE void DESMakeKey(const uint8* raw, uint8* key);
+
+// Run the DES encryption algorithm in ECB mode on one block (8 bytes) of
+// data. |key| is a DES key (8 bytes), |src| is the input plaintext (8
+// bytes), and |hash| is an 8-byte buffer receiving the output ciphertext.
+NET_EXPORT_PRIVATE void DESEncrypt(const uint8* key, const uint8* src,
+ uint8* hash);
+
+} // namespace net
+
+#endif // NET_HTTP_DES_H_
diff --git a/chromium/net/http/des_unittest.cc b/chromium/net/http/des_unittest.cc
new file mode 100644
index 00000000000..a615a084495
--- /dev/null
+++ b/chromium/net/http/des_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include <string.h>
+
+#include "net/http/des.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+// This test vector comes from the NSS FIPS power-up self-test.
+TEST(DESTest, KnownAnswerTest1) {
+ // DES known key (56-bits).
+ static const uint8 des_known_key[] = "ANSI DES";
+
+ // DES known plaintext (64-bits).
+ static const uint8 des_ecb_known_plaintext[] = "Netscape";
+
+ // DES known ciphertext (64-bits).
+ static const uint8 des_ecb_known_ciphertext[] = {
+ 0x26, 0x14, 0xe9, 0xc3, 0x28, 0x80, 0x50, 0xb0
+ };
+
+ uint8 ciphertext[8];
+ memset(ciphertext, 0xaf, sizeof(ciphertext));
+
+ DESEncrypt(des_known_key, des_ecb_known_plaintext, ciphertext);
+ EXPECT_EQ(0, memcmp(ciphertext, des_ecb_known_ciphertext, 8));
+}
+
+// This test vector comes from NIST Special Publication 800-17, Modes of
+// Operation Validation System (MOVS): Requirements and Procedures, Appendix
+// A, page 124.
+TEST(DESTest, KnownAnswerTest2) {
+ static const uint8 key[] = {
+ 0x10, 0x31, 0x6e, 0x02, 0x8c, 0x8f, 0x3b, 0x4a
+ };
+ static const uint8 plaintext[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ static const uint8 known_ciphertext[] = {
+ 0x82, 0xdc, 0xba, 0xfb, 0xde, 0xab, 0x66, 0x02
+ };
+ uint8 ciphertext[8];
+ memset(ciphertext, 0xaf, sizeof(ciphertext));
+
+ DESEncrypt(key, plaintext, ciphertext);
+ EXPECT_EQ(0, memcmp(ciphertext, known_ciphertext, 8));
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_atom_list.h b/chromium/net/http/http_atom_list.h
new file mode 100644
index 00000000000..4dac9fb6d22
--- /dev/null
+++ b/chromium/net/http/http_atom_list.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2006-2008 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.
+
+HTTP_ATOM(ACCEPT)
+HTTP_ATOM(ACCEPT_CHARSET)
+HTTP_ATOM(ACCEPT_ENCODING)
+HTTP_ATOM(ACCEPT_LANGUAGE)
+HTTP_ATOM(ACCEPT_RANGES)
+HTTP_ATOM(AGE)
+HTTP_ATOM(ALLOW)
+HTTP_ATOM(AUTHORIZATION)
+HTTP_ATOM(CACHE_CONTROL)
+HTTP_ATOM(CONNECTION)
+HTTP_ATOM(CONTENT_BASE)
+HTTP_ATOM(CONTENT_DISPOSITION)
+HTTP_ATOM(CONTENT_ENCODING)
+HTTP_ATOM(CONTENT_LANGUAGE)
+HTTP_ATOM(CONTENT_LENGTH)
+HTTP_ATOM(CONTENT_LOCATION)
+HTTP_ATOM(CONTENT_MD5)
+HTTP_ATOM(CONTENT_RANGE)
+HTTP_ATOM(CONTENT_TRANSFER_ENCODING)
+HTTP_ATOM(CONTENT_TYPE)
+HTTP_ATOM(COOKIE)
+HTTP_ATOM(DATE)
+HTTP_ATOM(DERIVED_FROM)
+HTTP_ATOM(ETAG)
+HTTP_ATOM(EXPECT)
+HTTP_ATOM(EXPIRES)
+HTTP_ATOM(FORWARDED)
+HTTP_ATOM(FROM)
+HTTP_ATOM(HOST)
+HTTP_ATOM(IF_MATCH)
+HTTP_ATOM(IF_MODIFIED_SINCE)
+HTTP_ATOM(IF_NONE_MATCH)
+HTTP_ATOM(IF_RANGE)
+HTTP_ATOM(IF_UNMODIFIED_SINCE)
+HTTP_ATOM(LAST_MODIFIED)
+HTTP_ATOM(LINK)
+HTTP_ATOM(LOCATION)
+HTTP_ATOM(MAX_FORWARDS)
+HTTP_ATOM(MESSAGE_ID)
+HTTP_ATOM(PRAGMA)
+HTTP_ATOM(PROXY_AUTHENTICATE)
+HTTP_ATOM(PROXY_AUTHORIZATION)
+HTTP_ATOM(PROXY_CONNECTION)
+HTTP_ATOM(RANGE)
+HTTP_ATOM(REFERER)
+HTTP_ATOM(REFRESH)
+HTTP_ATOM(RETRY_AFTER)
+HTTP_ATOM(SERVER)
+HTTP_ATOM(SET_COOKIE)
+HTTP_ATOM(TITLE)
+HTTP_ATOM(TRANSFER_ENCODING)
+HTTP_ATOM(UPGRADE)
+HTTP_ATOM(USER_AGENT)
+HTTP_ATOM(VARY)
+HTTP_ATOM(VIA)
+HTTP_ATOM(WARNING)
+HTTP_ATOM(WWW_AUTHENTICATE)
diff --git a/chromium/net/http/http_auth.cc b/chromium/net/http/http_auth.cc
new file mode 100644
index 00000000000..3cc9db1a122
--- /dev/null
+++ b/chromium/net/http/http_auth.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2011 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/http/http_auth.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+HttpAuth::Identity::Identity() : source(IDENT_SRC_NONE), invalid(true) {}
+
+// static
+void HttpAuth::ChooseBestChallenge(
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const GURL& origin,
+ const std::set<Scheme>& disabled_schemes,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ DCHECK(http_auth_handler_factory);
+ DCHECK(handler->get() == NULL);
+
+ // Choose the challenge whose authentication handler gives the maximum score.
+ scoped_ptr<HttpAuthHandler> best;
+ const std::string header_name = GetChallengeHeaderName(target);
+ std::string cur_challenge;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, header_name, &cur_challenge)) {
+ scoped_ptr<HttpAuthHandler> cur;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ cur_challenge, target, origin, net_log, &cur);
+ if (rv != OK) {
+ VLOG(1) << "Unable to create AuthHandler. Status: "
+ << ErrorToString(rv) << " Challenge: " << cur_challenge;
+ continue;
+ }
+ if (cur.get() && (!best.get() || best->score() < cur->score()) &&
+ (disabled_schemes.find(cur->auth_scheme()) == disabled_schemes.end()))
+ best.swap(cur);
+ }
+ handler->swap(best);
+}
+
+// static
+HttpAuth::AuthorizationResult HttpAuth::HandleChallengeResponse(
+ HttpAuthHandler* handler,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const std::set<Scheme>& disabled_schemes,
+ std::string* challenge_used) {
+ DCHECK(handler);
+ DCHECK(headers);
+ DCHECK(challenge_used);
+ challenge_used->clear();
+ HttpAuth::Scheme current_scheme = handler->auth_scheme();
+ if (disabled_schemes.find(current_scheme) != disabled_schemes.end())
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ std::string current_scheme_name = SchemeToString(current_scheme);
+ const std::string header_name = GetChallengeHeaderName(target);
+ void* iter = NULL;
+ std::string challenge;
+ HttpAuth::AuthorizationResult authorization_result =
+ HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ while (headers->EnumerateHeader(&iter, header_name, &challenge)) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ if (!LowerCaseEqualsASCII(props.scheme(), current_scheme_name.c_str()))
+ continue;
+ authorization_result = handler->HandleAnotherChallenge(&props);
+ if (authorization_result != HttpAuth::AUTHORIZATION_RESULT_INVALID) {
+ *challenge_used = challenge;
+ return authorization_result;
+ }
+ }
+ // Finding no matches is equivalent to rejection.
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+}
+
+HttpAuth::ChallengeTokenizer::ChallengeTokenizer(
+ std::string::const_iterator begin,
+ std::string::const_iterator end)
+ : begin_(begin),
+ end_(end),
+ scheme_begin_(begin),
+ scheme_end_(begin),
+ params_begin_(end),
+ params_end_(end) {
+ Init(begin, end);
+}
+
+HttpUtil::NameValuePairsIterator HttpAuth::ChallengeTokenizer::param_pairs()
+ const {
+ return HttpUtil::NameValuePairsIterator(params_begin_, params_end_, ',');
+}
+
+std::string HttpAuth::ChallengeTokenizer::base64_param() const {
+ // Strip off any padding.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=230351.)
+ //
+ // Our base64 decoder requires that the length be a multiple of 4.
+ int encoded_length = params_end_ - params_begin_;
+ while (encoded_length > 0 && encoded_length % 4 != 0 &&
+ params_begin_[encoded_length - 1] == '=') {
+ --encoded_length;
+ }
+ return std::string(params_begin_, params_begin_ + encoded_length);
+}
+
+void HttpAuth::ChallengeTokenizer::Init(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ // The first space-separated token is the auth-scheme.
+ // NOTE: we are more permissive than RFC 2617 which says auth-scheme
+ // is separated by 1*SP.
+ base::StringTokenizer tok(begin, end, HTTP_LWS);
+ if (!tok.GetNext()) {
+ // Default param and scheme iterators provide empty strings
+ return;
+ }
+
+ // Save the scheme's position.
+ scheme_begin_ = tok.token_begin();
+ scheme_end_ = tok.token_end();
+
+ params_begin_ = scheme_end_;
+ params_end_ = end;
+ HttpUtil::TrimLWS(&params_begin_, &params_end_);
+}
+
+// static
+std::string HttpAuth::GetChallengeHeaderName(Target target) {
+ switch (target) {
+ case AUTH_PROXY:
+ return "Proxy-Authenticate";
+ case AUTH_SERVER:
+ return "WWW-Authenticate";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+std::string HttpAuth::GetAuthorizationHeaderName(Target target) {
+ switch (target) {
+ case AUTH_PROXY:
+ return HttpRequestHeaders::kProxyAuthorization;
+ case AUTH_SERVER:
+ return HttpRequestHeaders::kAuthorization;
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+std::string HttpAuth::GetAuthTargetString(Target target) {
+ switch (target) {
+ case AUTH_PROXY:
+ return "proxy";
+ case AUTH_SERVER:
+ return "server";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+const char* HttpAuth::SchemeToString(Scheme scheme) {
+ static const char* const kSchemeNames[] = {
+ "basic",
+ "digest",
+ "ntlm",
+ "negotiate",
+ "spdyproxy",
+ "mock",
+ };
+ COMPILE_ASSERT(arraysize(kSchemeNames) == AUTH_SCHEME_MAX,
+ http_auth_scheme_names_incorrect_size);
+ if (scheme < AUTH_SCHEME_BASIC || scheme >= AUTH_SCHEME_MAX) {
+ NOTREACHED();
+ return "invalid_scheme";
+ }
+ return kSchemeNames[scheme];
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth.h b/chromium/net/http/http_auth.h
new file mode 100644
index 00000000000..1a03bb1fe1f
--- /dev/null
+++ b/chromium/net/http/http_auth.h
@@ -0,0 +1,216 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_H_
+#define NET_HTTP_HTTP_AUTH_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/auth.h"
+#include "net/base/net_export.h"
+#include "net/http/http_util.h"
+
+template <class T> class scoped_refptr;
+
+namespace net {
+
+class BoundNetLog;
+class HttpAuthHandler;
+class HttpAuthHandlerFactory;
+class HttpResponseHeaders;
+
+// Utility class for http authentication.
+class NET_EXPORT_PRIVATE HttpAuth {
+ public:
+ // Http authentication can be done the the proxy server, origin server,
+ // or both. This enum tracks who the target is.
+ enum Target {
+ AUTH_NONE = -1,
+ // We depend on the valid targets (!= AUTH_NONE) being usable as indexes
+ // in an array, so start from 0.
+ AUTH_PROXY = 0,
+ AUTH_SERVER = 1,
+ AUTH_NUM_TARGETS = 2,
+ };
+
+ // What the HTTP WWW-Authenticate/Proxy-Authenticate headers indicate about
+ // the previous authorization attempt.
+ enum AuthorizationResult {
+ AUTHORIZATION_RESULT_ACCEPT, // The authorization attempt was accepted,
+ // although there still may be additional
+ // rounds of challenges.
+
+ AUTHORIZATION_RESULT_REJECT, // The authorization attempt was rejected.
+
+ AUTHORIZATION_RESULT_STALE, // (Digest) The nonce used in the
+ // authorization attempt is stale, but
+ // otherwise the attempt was valid.
+
+ AUTHORIZATION_RESULT_INVALID, // The authentication challenge headers are
+ // poorly formed (the authorization attempt
+ // itself may have been fine).
+
+ AUTHORIZATION_RESULT_DIFFERENT_REALM, // The authorization
+ // attempt was rejected,
+ // but the realm associated
+ // with the new challenge
+ // is different from the
+ // previous attempt.
+ };
+
+ // Describes where the identity used for authentication came from.
+ enum IdentitySource {
+ // Came from nowhere -- the identity is not initialized.
+ IDENT_SRC_NONE,
+
+ // The identity came from the auth cache, by doing a path-based
+ // lookup (premptive authorization).
+ IDENT_SRC_PATH_LOOKUP,
+
+ // The identity was extracted from a URL of the form:
+ // http://<username>:<password>@host:port
+ IDENT_SRC_URL,
+
+ // The identity was retrieved from the auth cache, by doing a
+ // realm lookup.
+ IDENT_SRC_REALM_LOOKUP,
+
+ // The identity was provided by RestartWithAuth -- it likely
+ // came from a prompt (or maybe the password manager).
+ IDENT_SRC_EXTERNAL,
+
+ // The identity used the default credentials for the computer,
+ // on schemes that support single sign-on.
+ IDENT_SRC_DEFAULT_CREDENTIALS,
+ };
+
+ enum Scheme {
+ AUTH_SCHEME_BASIC = 0,
+ AUTH_SCHEME_DIGEST,
+ AUTH_SCHEME_NTLM,
+ AUTH_SCHEME_NEGOTIATE,
+ AUTH_SCHEME_SPDYPROXY,
+ AUTH_SCHEME_MOCK,
+ AUTH_SCHEME_MAX,
+ };
+
+ // Helper structure used by HttpNetworkTransaction to track
+ // the current identity being used for authorization.
+ struct Identity {
+ Identity();
+
+ IdentitySource source;
+ bool invalid;
+ AuthCredentials credentials;
+ };
+
+ // Get the name of the header containing the auth challenge
+ // (either WWW-Authenticate or Proxy-Authenticate).
+ static std::string GetChallengeHeaderName(Target target);
+
+ // Get the name of the header where the credentials go
+ // (either Authorization or Proxy-Authorization).
+ static std::string GetAuthorizationHeaderName(Target target);
+
+ // Returns a string representation of a Target value that can be used in log
+ // messages.
+ static std::string GetAuthTargetString(Target target);
+
+ // Returns a string representation of an authentication Scheme.
+ static const char* SchemeToString(Scheme scheme);
+
+ // Iterate through the challenge headers, and pick the best one that
+ // we support. Obtains the implementation class for handling the challenge,
+ // and passes it back in |*handler|. If no supported challenge was found,
+ // |*handler| is set to NULL.
+ //
+ // |disabled_schemes| is the set of schemes that we should not use.
+ //
+ // |origin| is used by the NTLM and Negotiation authentication scheme to
+ // construct the service principal name. It is ignored by other schemes.
+ static void ChooseBestChallenge(
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const GURL& origin,
+ const std::set<Scheme>& disabled_schemes,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Handle a 401/407 response from a server/proxy after a previous
+ // authentication attempt. For connection-based authentication schemes, the
+ // new response may be another round in a multi-round authentication sequence.
+ // For request-based schemes, a 401/407 response is typically treated like a
+ // rejection of the previous challenge, except in the Digest case when a
+ // "stale" attribute is present.
+ //
+ // |handler| must be non-NULL, and is the HttpAuthHandler from the previous
+ // authentication round.
+ //
+ // |headers| must be non-NULL and contain the new HTTP response.
+ //
+ // |target| specifies whether the authentication challenge response came
+ // from a server or a proxy.
+ //
+ // |disabled_schemes| are the authentication schemes to ignore.
+ //
+ // |challenge_used| is the text of the authentication challenge used in
+ // support of the returned AuthorizationResult. If no headers were used for
+ // the result (for example, all headers have unknown authentication schemes),
+ // the value is cleared.
+ static AuthorizationResult HandleChallengeResponse(
+ HttpAuthHandler* handler,
+ const HttpResponseHeaders* headers,
+ Target target,
+ const std::set<Scheme>& disabled_schemes,
+ std::string* challenge_used);
+
+ // Breaks up a challenge string into the the auth scheme and parameter list,
+ // according to RFC 2617 Sec 1.2:
+ // challenge = auth-scheme 1*SP 1#auth-param
+ //
+ // Depending on the challenge scheme, it may be appropriate to interpret the
+ // parameters as either a base-64 encoded string or a comma-delimited list
+ // of name-value pairs. param_pairs() and base64_param() methods are provided
+ // to support either usage.
+ class NET_EXPORT_PRIVATE ChallengeTokenizer {
+ public:
+ ChallengeTokenizer(std::string::const_iterator begin,
+ std::string::const_iterator end);
+
+ // Get the original text.
+ std::string challenge_text() const {
+ return std::string(begin_, end_);
+ }
+
+ // Get the auth scheme of the challenge.
+ std::string::const_iterator scheme_begin() const { return scheme_begin_; }
+ std::string::const_iterator scheme_end() const { return scheme_end_; }
+ std::string scheme() const {
+ return std::string(scheme_begin_, scheme_end_);
+ }
+
+ HttpUtil::NameValuePairsIterator param_pairs() const;
+ std::string base64_param() const;
+
+ private:
+ void Init(std::string::const_iterator begin,
+ std::string::const_iterator end);
+
+ std::string::const_iterator begin_;
+ std::string::const_iterator end_;
+
+ std::string::const_iterator scheme_begin_;
+ std::string::const_iterator scheme_end_;
+
+ std::string::const_iterator params_begin_;
+ std::string::const_iterator params_end_;
+ };
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_H_
diff --git a/chromium/net/http/http_auth_cache.cc b/chromium/net/http/http_auth_cache.cc
new file mode 100644
index 00000000000..79ea7fd625b
--- /dev/null
+++ b/chromium/net/http/http_auth_cache.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2011 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/http/http_auth_cache.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+
+namespace {
+
+// Helper to find the containing directory of path. In RFC 2617 this is what
+// they call the "last symbolic element in the absolute path".
+// Examples:
+// "/foo/bar.txt" --> "/foo/"
+// "/foo/" --> "/foo/"
+std::string GetParentDirectory(const std::string& path) {
+ std::string::size_type last_slash = path.rfind("/");
+ if (last_slash == std::string::npos) {
+ // No slash (absolute paths always start with slash, so this must be
+ // the proxy case which uses empty string).
+ DCHECK(path.empty());
+ return path;
+ }
+ return path.substr(0, last_slash + 1);
+}
+
+// Debug helper to check that |path| arguments are properly formed.
+// (should be absolute path, or empty string).
+void CheckPathIsValid(const std::string& path) {
+ DCHECK(path.empty() || path[0] == '/');
+}
+
+// Return true if |path| is a subpath of |container|. In other words, is
+// |container| an ancestor of |path|?
+bool IsEnclosingPath(const std::string& container, const std::string& path) {
+ DCHECK(container.empty() || *(container.end() - 1) == '/');
+ return ((container.empty() && path.empty()) ||
+ (!container.empty() && StartsWithASCII(path, container, true)));
+}
+
+// Debug helper to check that |origin| arguments are properly formed.
+void CheckOriginIsValid(const GURL& origin) {
+ DCHECK(origin.is_valid());
+ // Note that the scheme may be FTP when we're using a HTTP proxy.
+ DCHECK(origin.SchemeIs("http") || origin.SchemeIs("https") ||
+ origin.SchemeIs("ftp"));
+ DCHECK(origin.GetOrigin() == origin);
+}
+
+// Functor used by remove_if.
+struct IsEnclosedBy {
+ explicit IsEnclosedBy(const std::string& path) : path(path) { }
+ bool operator() (const std::string& x) const {
+ return IsEnclosingPath(path, x);
+ }
+ const std::string& path;
+};
+
+} // namespace
+
+namespace net {
+
+HttpAuthCache::HttpAuthCache() {
+}
+
+HttpAuthCache::~HttpAuthCache() {
+}
+
+// Performance: O(n), where n is the number of realm entries.
+HttpAuthCache::Entry* HttpAuthCache::Lookup(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme) {
+ CheckOriginIsValid(origin);
+
+ // Linear scan through the realm entries.
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin() == origin && it->realm() == realm &&
+ it->scheme() == scheme)
+ return &(*it);
+ }
+ return NULL; // No realm entry found.
+}
+
+// Performance: O(n*m), where n is the number of realm entries, m is the number
+// of path entries per realm. Both n amd m are expected to be small; m is
+// kept small because AddPath() only keeps the shallowest entry.
+HttpAuthCache::Entry* HttpAuthCache::LookupByPath(const GURL& origin,
+ const std::string& path) {
+ HttpAuthCache::Entry* best_match = NULL;
+ size_t best_match_length = 0;
+ CheckOriginIsValid(origin);
+ CheckPathIsValid(path);
+
+ // RFC 2617 section 2:
+ // A client SHOULD assume that all paths at or deeper than the depth of
+ // the last symbolic element in the path field of the Request-URI also are
+ // within the protection space ...
+ std::string parent_dir = GetParentDirectory(path);
+
+ // Linear scan through the realm entries.
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ size_t len = 0;
+ if (it->origin() == origin && it->HasEnclosingPath(parent_dir, &len) &&
+ (!best_match || len > best_match_length)) {
+ best_match_length = len;
+ best_match = &(*it);
+ }
+ }
+ return best_match;
+}
+
+HttpAuthCache::Entry* HttpAuthCache::Add(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const std::string& auth_challenge,
+ const AuthCredentials& credentials,
+ const std::string& path) {
+ CheckOriginIsValid(origin);
+ CheckPathIsValid(path);
+
+ // Check for existing entry (we will re-use it if present).
+ HttpAuthCache::Entry* entry = Lookup(origin, realm, scheme);
+ if (!entry) {
+ // Failsafe to prevent unbounded memory growth of the cache.
+ if (entries_.size() >= kMaxNumRealmEntries) {
+ LOG(WARNING) << "Num auth cache entries reached limit -- evicting";
+ entries_.pop_back();
+ }
+
+ entries_.push_front(Entry());
+ entry = &entries_.front();
+ entry->origin_ = origin;
+ entry->realm_ = realm;
+ entry->scheme_ = scheme;
+ }
+ DCHECK_EQ(origin, entry->origin_);
+ DCHECK_EQ(realm, entry->realm_);
+ DCHECK_EQ(scheme, entry->scheme_);
+
+ entry->auth_challenge_ = auth_challenge;
+ entry->credentials_ = credentials;
+ entry->nonce_count_ = 1;
+ entry->AddPath(path);
+
+ return entry;
+}
+
+HttpAuthCache::Entry::~Entry() {
+}
+
+void HttpAuthCache::Entry::UpdateStaleChallenge(
+ const std::string& auth_challenge) {
+ auth_challenge_ = auth_challenge;
+ nonce_count_ = 1;
+}
+
+HttpAuthCache::Entry::Entry()
+ : scheme_(HttpAuth::AUTH_SCHEME_MAX),
+ nonce_count_(0) {
+}
+
+void HttpAuthCache::Entry::AddPath(const std::string& path) {
+ std::string parent_dir = GetParentDirectory(path);
+ if (!HasEnclosingPath(parent_dir, NULL)) {
+ // Remove any entries that have been subsumed by the new entry.
+ paths_.remove_if(IsEnclosedBy(parent_dir));
+
+ // Failsafe to prevent unbounded memory growth of the cache.
+ if (paths_.size() >= kMaxNumPathsPerRealmEntry) {
+ LOG(WARNING) << "Num path entries for " << origin()
+ << " has grown too large -- evicting";
+ paths_.pop_back();
+ }
+
+ // Add new path.
+ paths_.push_front(parent_dir);
+ }
+}
+
+bool HttpAuthCache::Entry::HasEnclosingPath(const std::string& dir,
+ size_t* path_len) {
+ DCHECK(GetParentDirectory(dir) == dir);
+ for (PathList::const_iterator it = paths_.begin(); it != paths_.end();
+ ++it) {
+ if (IsEnclosingPath(*it, dir)) {
+ // No element of paths_ may enclose any other element.
+ // Therefore this path is the tightest bound. Important because
+ // the length returned is used to determine the cache entry that
+ // has the closest enclosing path in LookupByPath().
+ if (path_len)
+ *path_len = it->length();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool HttpAuthCache::Remove(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const AuthCredentials& credentials) {
+ for (EntryList::iterator it = entries_.begin(); it != entries_.end(); ++it) {
+ if (it->origin() == origin && it->realm() == realm &&
+ it->scheme() == scheme) {
+ if (credentials.Equals(it->credentials())) {
+ entries_.erase(it);
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+bool HttpAuthCache::UpdateStaleChallenge(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const std::string& auth_challenge) {
+ HttpAuthCache::Entry* entry = Lookup(origin, realm, scheme);
+ if (!entry)
+ return false;
+ entry->UpdateStaleChallenge(auth_challenge);
+ return true;
+}
+
+void HttpAuthCache::UpdateAllFrom(const HttpAuthCache& other) {
+ for (EntryList::const_iterator it = other.entries_.begin();
+ it != other.entries_.end(); ++it) {
+ // Add an Entry with one of the original entry's paths.
+ DCHECK(it->paths_.size() > 0);
+ Entry* entry = Add(it->origin(), it->realm(), it->scheme(),
+ it->auth_challenge(), it->credentials(),
+ it->paths_.back());
+ // Copy all other paths.
+ for (Entry::PathList::const_reverse_iterator it2 = ++it->paths_.rbegin();
+ it2 != it->paths_.rend(); ++it2)
+ entry->AddPath(*it2);
+ // Copy nonce count (for digest authentication).
+ entry->nonce_count_ = it->nonce_count_;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_cache.h b/chromium/net/http/http_auth_cache.h
new file mode 100644
index 00000000000..75b379f25bd
--- /dev/null
+++ b/chromium/net/http/http_auth_cache.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_CACHE_H_
+#define NET_HTTP_HTTP_AUTH_CACHE_H_
+
+#include <list>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// HttpAuthCache stores HTTP authentication identities and challenge info.
+// For each (origin, realm, scheme) triple the cache stores a
+// HttpAuthCache::Entry, which holds:
+// - the origin server {protocol scheme, host, port}
+// - the last identity used (username/password)
+// - the last auth handler used (contains realm and authentication scheme)
+// - the list of paths which used this realm
+// Entries can be looked up by either (origin, realm, scheme) or (origin, path).
+class NET_EXPORT_PRIVATE HttpAuthCache {
+ public:
+ class NET_EXPORT_PRIVATE Entry {
+ public:
+ ~Entry();
+
+ const GURL& origin() const {
+ return origin_;
+ }
+
+ // The case-sensitive realm string of the challenge.
+ const std::string realm() const {
+ return realm_;
+ }
+
+ // The authentication scheme of the challenge.
+ HttpAuth::Scheme scheme() const {
+ return scheme_;
+ }
+
+ // The authentication challenge.
+ const std::string auth_challenge() const {
+ return auth_challenge_;
+ }
+
+ // The login credentials.
+ const AuthCredentials& credentials() const {
+ return credentials_;
+ }
+
+ int IncrementNonceCount() {
+ return ++nonce_count_;
+ }
+
+ void UpdateStaleChallenge(const std::string& auth_challenge);
+
+ private:
+ friend class HttpAuthCache;
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthCacheTest, AddPath);
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthCacheTest, AddToExistingEntry);
+
+ typedef std::list<std::string> PathList;
+
+ Entry();
+
+ // Adds a path defining the realm's protection space. If the path is
+ // already contained in the protection space, is a no-op.
+ void AddPath(const std::string& path);
+
+ // Returns true if |dir| is contained within the realm's protection
+ // space. |*path_len| is set to the length of the enclosing path if
+ // such a path exists and |path_len| is non-NULL. If no enclosing
+ // path is found, |*path_len| is left unmodified.
+ //
+ // Note that proxy auth cache entries are associated with empty
+ // paths. Therefore it is possible for HasEnclosingPath() to return
+ // true and set |*path_len| to 0.
+ bool HasEnclosingPath(const std::string& dir, size_t* path_len);
+
+ // |origin_| contains the {protocol, host, port} of the server.
+ GURL origin_;
+ std::string realm_;
+ HttpAuth::Scheme scheme_;
+
+ // Identity.
+ std::string auth_challenge_;
+ AuthCredentials credentials_;
+
+ int nonce_count_;
+
+ // List of paths that define the realm's protection space.
+ PathList paths_;
+ };
+
+ // Prevent unbounded memory growth. These are safeguards for abuse; it is
+ // not expected that the limits will be reached in ordinary usage.
+ // This also defines the worst-case lookup times (which grow linearly
+ // with number of elements in the cache).
+ enum { kMaxNumPathsPerRealmEntry = 10 };
+ enum { kMaxNumRealmEntries = 10 };
+
+ HttpAuthCache();
+ ~HttpAuthCache();
+
+ // Find the realm entry on server |origin| for realm |realm| and
+ // scheme |scheme|.
+ // |origin| - the {scheme, host, port} of the server.
+ // |realm| - case sensitive realm string.
+ // |scheme| - the authentication scheme (i.e. basic, negotiate).
+ // returns - the matched entry or NULL.
+ Entry* Lookup(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme);
+
+ // Find the entry on server |origin| whose protection space includes
+ // |path|. This uses the assumption in RFC 2617 section 2 that deeper
+ // paths lie in the same protection space.
+ // |origin| - the {scheme, host, port} of the server.
+ // |path| - absolute path of the resource, or empty string in case of
+ // proxy auth (which does not use the concept of paths).
+ // returns - the matched entry or NULL.
+ Entry* LookupByPath(const GURL& origin, const std::string& path);
+
+ // Add an entry on server |origin| for realm |handler->realm()| and
+ // scheme |handler->scheme()|. If an entry for this (realm,scheme)
+ // already exists, update it rather than replace it -- this preserves the
+ // paths list.
+ // |origin| - the {scheme, host, port} of the server.
+ // |realm| - the auth realm for the challenge.
+ // |scheme| - the authentication scheme (i.e. basic, negotiate).
+ // |credentials| - login information for the realm.
+ // |path| - absolute path for a resource contained in the protection
+ // space; this will be added to the list of known paths.
+ // returns - the entry that was just added/updated.
+ Entry* Add(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const std::string& auth_challenge,
+ const AuthCredentials& credentials,
+ const std::string& path);
+
+ // Remove entry on server |origin| for realm |realm| and scheme |scheme|
+ // if one exists AND if the cached credentials matches |credentials|.
+ // |origin| - the {scheme, host, port} of the server.
+ // |realm| - case sensitive realm string.
+ // |scheme| - the authentication scheme (i.e. basic, negotiate).
+ // |credentials| - the credentials to match.
+ // returns - true if an entry was removed.
+ bool Remove(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const AuthCredentials& credentials);
+
+ // Updates a stale digest entry on server |origin| for realm |realm| and
+ // scheme |scheme|. The cached auth challenge is replaced with
+ // |auth_challenge| and the nonce count is reset.
+ // |UpdateStaleChallenge()| returns true if a matching entry exists in the
+ // cache, false otherwise.
+ bool UpdateStaleChallenge(const GURL& origin,
+ const std::string& realm,
+ HttpAuth::Scheme scheme,
+ const std::string& auth_challenge);
+
+ // Copies all entries from |other| cache.
+ void UpdateAllFrom(const HttpAuthCache& other);
+
+ private:
+ typedef std::list<Entry> EntryList;
+ EntryList entries_;
+};
+
+// An authentication realm entry.
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_CACHE_H_
diff --git a/chromium/net/http/http_auth_cache_unittest.cc b/chromium/net/http/http_auth_cache_unittest.cc
new file mode 100644
index 00000000000..a4ea1c6da03
--- /dev/null
+++ b/chromium/net/http/http_auth_cache_unittest.cc
@@ -0,0 +1,626 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_auth_handler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockAuthHandler : public HttpAuthHandler {
+ public:
+ MockAuthHandler(HttpAuth::Scheme scheme,
+ const std::string& realm,
+ HttpAuth::Target target) {
+ // Can't use initializer list since these are members of the base class.
+ auth_scheme_ = scheme;
+ realm_ = realm;
+ score_ = 1;
+ target_ = target;
+ properties_ = 0;
+ }
+
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE {
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ }
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE {
+ return false; // Unused.
+ }
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials*,
+ const HttpRequestInfo*,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE {
+ *auth_token = "mock-credentials";
+ return OK;
+ }
+
+
+ private:
+ virtual ~MockAuthHandler() {}
+};
+
+const char* kRealm1 = "Realm1";
+const char* kRealm2 = "Realm2";
+const char* kRealm3 = "Realm3";
+const char* kRealm4 = "Realm4";
+const char* kRealm5 = "Realm5";
+const base::string16 k123(ASCIIToUTF16("123"));
+const base::string16 k1234(ASCIIToUTF16("1234"));
+const base::string16 kAdmin(ASCIIToUTF16("admin"));
+const base::string16 kAlice(ASCIIToUTF16("alice"));
+const base::string16 kAlice2(ASCIIToUTF16("alice2"));
+const base::string16 kPassword(ASCIIToUTF16("password"));
+const base::string16 kRoot(ASCIIToUTF16("root"));
+const base::string16 kUsername(ASCIIToUTF16("username"));
+const base::string16 kWileCoyote(ASCIIToUTF16("wilecoyote"));
+
+AuthCredentials CreateASCIICredentials(const char* username,
+ const char* password) {
+ return AuthCredentials(ASCIIToUTF16(username), ASCIIToUTF16(password));
+}
+
+} // namespace
+
+// Test adding and looking-up cache entries (both by realm and by path).
+TEST(HttpAuthCacheTest, Basic) {
+ GURL origin("http://www.google.com");
+ HttpAuthCache cache;
+ HttpAuthCache::Entry* entry;
+
+ // Add cache entries for 4 realms: "Realm1", "Realm2", "Realm3" and
+ // "Realm4"
+
+ scoped_ptr<HttpAuthHandler> realm1_handler(
+ new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC,
+ kRealm1,
+ HttpAuth::AUTH_SERVER));
+ cache.Add(origin, realm1_handler->realm(), realm1_handler->auth_scheme(),
+ "Basic realm=Realm1",
+ CreateASCIICredentials("realm1-user", "realm1-password"),
+ "/foo/bar/index.html");
+
+ scoped_ptr<HttpAuthHandler> realm2_handler(
+ new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC,
+ kRealm2,
+ HttpAuth::AUTH_SERVER));
+ cache.Add(origin, realm2_handler->realm(), realm2_handler->auth_scheme(),
+ "Basic realm=Realm2",
+ CreateASCIICredentials("realm2-user", "realm2-password"),
+ "/foo2/index.html");
+
+ scoped_ptr<HttpAuthHandler> realm3_basic_handler(
+ new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC,
+ kRealm3,
+ HttpAuth::AUTH_PROXY));
+ cache.Add(
+ origin,
+ realm3_basic_handler->realm(),
+ realm3_basic_handler->auth_scheme(),
+ "Basic realm=Realm3",
+ CreateASCIICredentials("realm3-basic-user", "realm3-basic-password"),
+ std::string());
+
+ scoped_ptr<HttpAuthHandler> realm3_digest_handler(
+ new MockAuthHandler(HttpAuth::AUTH_SCHEME_DIGEST,
+ kRealm3,
+ HttpAuth::AUTH_PROXY));
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "Digest realm=Realm3",
+ CreateASCIICredentials("realm3-digest-user",
+ "realm3-digest-password"),
+ "/baz/index.html");
+
+ scoped_ptr<HttpAuthHandler> realm4_basic_handler(
+ new MockAuthHandler(HttpAuth::AUTH_SCHEME_BASIC,
+ kRealm4,
+ HttpAuth::AUTH_SERVER));
+ cache.Add(origin, realm4_basic_handler->realm(),
+ realm4_basic_handler->auth_scheme(), "Basic realm=Realm4",
+ CreateASCIICredentials("realm4-basic-user",
+ "realm4-basic-password"),
+ "/");
+
+ // There is no Realm5
+ entry = cache.Lookup(origin, kRealm5, HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_TRUE(NULL == entry);
+
+ // While Realm3 does exist, the origin scheme is wrong.
+ entry = cache.Lookup(GURL("https://www.google.com"), kRealm3,
+ HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_TRUE(NULL == entry);
+
+ // Realm, origin scheme ok, authentication scheme wrong
+ entry = cache.Lookup
+ (GURL("http://www.google.com"), kRealm1, HttpAuth::AUTH_SCHEME_DIGEST);
+ EXPECT_TRUE(NULL == entry);
+
+ // Valid lookup by origin, realm, scheme.
+ entry = cache.Lookup(
+ GURL("http://www.google.com:80"), kRealm3, HttpAuth::AUTH_SCHEME_BASIC);
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme());
+ EXPECT_EQ(kRealm3, entry->realm());
+ EXPECT_EQ("Basic realm=Realm3", entry->auth_challenge());
+ EXPECT_EQ(ASCIIToUTF16("realm3-basic-user"), entry->credentials().username());
+ EXPECT_EQ(ASCIIToUTF16("realm3-basic-password"),
+ entry->credentials().password());
+
+ // Valid lookup by origin, realm, scheme when there's a duplicate
+ // origin, realm in the cache
+ entry = cache.Lookup(
+ GURL("http://www.google.com:80"), kRealm3, HttpAuth::AUTH_SCHEME_DIGEST);
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_DIGEST, entry->scheme());
+ EXPECT_EQ(kRealm3, entry->realm());
+ EXPECT_EQ("Digest realm=Realm3", entry->auth_challenge());
+ EXPECT_EQ(ASCIIToUTF16("realm3-digest-user"),
+ entry->credentials().username());
+ EXPECT_EQ(ASCIIToUTF16("realm3-digest-password"),
+ entry->credentials().password());
+
+ // Valid lookup by realm.
+ entry = cache.Lookup(origin, kRealm2, HttpAuth::AUTH_SCHEME_BASIC);
+ ASSERT_FALSE(NULL == entry);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme());
+ EXPECT_EQ(kRealm2, entry->realm());
+ EXPECT_EQ("Basic realm=Realm2", entry->auth_challenge());
+ EXPECT_EQ(ASCIIToUTF16("realm2-user"), entry->credentials().username());
+ EXPECT_EQ(ASCIIToUTF16("realm2-password"), entry->credentials().password());
+
+ // Check that subpaths are recognized.
+ HttpAuthCache::Entry* realm2_entry = cache.Lookup(
+ origin, kRealm2, HttpAuth::AUTH_SCHEME_BASIC);
+ HttpAuthCache::Entry* realm4_entry = cache.Lookup(
+ origin, kRealm4, HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_FALSE(NULL == realm2_entry);
+ EXPECT_FALSE(NULL == realm4_entry);
+ // Realm4 applies to '/' and Realm2 applies to '/foo2/'.
+ // LookupByPath() should return the closest enclosing path.
+ // Positive tests:
+ entry = cache.LookupByPath(origin, "/foo2/index.html");
+ EXPECT_TRUE(realm2_entry == entry);
+ entry = cache.LookupByPath(origin, "/foo2/foobar.html");
+ EXPECT_TRUE(realm2_entry == entry);
+ entry = cache.LookupByPath(origin, "/foo2/bar/index.html");
+ EXPECT_TRUE(realm2_entry == entry);
+ entry = cache.LookupByPath(origin, "/foo2/");
+ EXPECT_TRUE(realm2_entry == entry);
+ entry = cache.LookupByPath(origin, "/foo2");
+ EXPECT_TRUE(realm4_entry == entry);
+ entry = cache.LookupByPath(origin, "/");
+ EXPECT_TRUE(realm4_entry == entry);
+
+ // Negative tests:
+ entry = cache.LookupByPath(origin, "/foo3/index.html");
+ EXPECT_FALSE(realm2_entry == entry);
+ entry = cache.LookupByPath(origin, std::string());
+ EXPECT_FALSE(realm2_entry == entry);
+
+ // Confirm we find the same realm, different auth scheme by path lookup
+ HttpAuthCache::Entry* realm3_digest_entry =
+ cache.Lookup(origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST);
+ EXPECT_FALSE(NULL == realm3_digest_entry);
+ entry = cache.LookupByPath(origin, "/baz/index.html");
+ EXPECT_TRUE(realm3_digest_entry == entry);
+ entry = cache.LookupByPath(origin, "/baz/");
+ EXPECT_TRUE(realm3_digest_entry == entry);
+ entry = cache.LookupByPath(origin, "/baz");
+ EXPECT_FALSE(realm3_digest_entry == entry);
+
+ // Confirm we find the same realm, different auth scheme by path lookup
+ HttpAuthCache::Entry* realm3DigestEntry =
+ cache.Lookup(origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST);
+ EXPECT_FALSE(NULL == realm3DigestEntry);
+ entry = cache.LookupByPath(origin, "/baz/index.html");
+ EXPECT_TRUE(realm3DigestEntry == entry);
+ entry = cache.LookupByPath(origin, "/baz/");
+ EXPECT_TRUE(realm3DigestEntry == entry);
+ entry = cache.LookupByPath(origin, "/baz");
+ EXPECT_FALSE(realm3DigestEntry == entry);
+
+ // Lookup using empty path (may be used for proxy).
+ entry = cache.LookupByPath(origin, std::string());
+ EXPECT_FALSE(NULL == entry);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, entry->scheme());
+ EXPECT_EQ(kRealm3, entry->realm());
+}
+
+TEST(HttpAuthCacheTest, AddPath) {
+ HttpAuthCache::Entry entry;
+
+ // All of these paths have a common root /1/2/2/4/5/
+ entry.AddPath("/1/2/3/4/5/x.txt");
+ entry.AddPath("/1/2/3/4/5/y.txt");
+ entry.AddPath("/1/2/3/4/5/z.txt");
+
+ EXPECT_EQ(1U, entry.paths_.size());
+ EXPECT_EQ("/1/2/3/4/5/", entry.paths_.front());
+
+ // Add a new entry (not a subpath).
+ entry.AddPath("/1/XXX/q");
+ EXPECT_EQ(2U, entry.paths_.size());
+ EXPECT_EQ("/1/XXX/", entry.paths_.front());
+ EXPECT_EQ("/1/2/3/4/5/", entry.paths_.back());
+
+ // Add containing paths of /1/2/3/4/5/ -- should swallow up the deeper paths.
+ entry.AddPath("/1/2/3/4/x.txt");
+ EXPECT_EQ(2U, entry.paths_.size());
+ EXPECT_EQ("/1/2/3/4/", entry.paths_.front());
+ EXPECT_EQ("/1/XXX/", entry.paths_.back());
+ entry.AddPath("/1/2/3/x");
+ EXPECT_EQ(2U, entry.paths_.size());
+ EXPECT_EQ("/1/2/3/", entry.paths_.front());
+ EXPECT_EQ("/1/XXX/", entry.paths_.back());
+
+ entry.AddPath("/index.html");
+ EXPECT_EQ(1U, entry.paths_.size());
+ EXPECT_EQ("/", entry.paths_.front());
+}
+
+// Calling Add when the realm entry already exists, should append that
+// path.
+TEST(HttpAuthCacheTest, AddToExistingEntry) {
+ HttpAuthCache cache;
+ GURL origin("http://www.foobar.com:70");
+ const std::string auth_challenge = "Basic realm=MyRealm";
+
+ scoped_ptr<HttpAuthHandler> handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, "MyRealm", HttpAuth::AUTH_SERVER));
+ HttpAuthCache::Entry* orig_entry = cache.Add(
+ origin, handler->realm(), handler->auth_scheme(), auth_challenge,
+ CreateASCIICredentials("user1", "password1"), "/x/y/z/");
+ cache.Add(origin, handler->realm(), handler->auth_scheme(), auth_challenge,
+ CreateASCIICredentials("user2", "password2"), "/z/y/x/");
+ cache.Add(origin, handler->realm(), handler->auth_scheme(), auth_challenge,
+ CreateASCIICredentials("user3", "password3"), "/z/y");
+
+ HttpAuthCache::Entry* entry = cache.Lookup(
+ origin, "MyRealm", HttpAuth::AUTH_SCHEME_BASIC);
+
+ EXPECT_TRUE(entry == orig_entry);
+ EXPECT_EQ(ASCIIToUTF16("user3"), entry->credentials().username());
+ EXPECT_EQ(ASCIIToUTF16("password3"), entry->credentials().password());
+
+ EXPECT_EQ(2U, entry->paths_.size());
+ EXPECT_EQ("/z/", entry->paths_.front());
+ EXPECT_EQ("/x/y/z/", entry->paths_.back());
+}
+
+TEST(HttpAuthCacheTest, Remove) {
+ GURL origin("http://foobar2.com");
+
+ scoped_ptr<HttpAuthHandler> realm1_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm1, HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm2_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm2, HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm3_basic_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm3, HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm3_digest_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_DIGEST, kRealm3, HttpAuth::AUTH_SERVER));
+
+ HttpAuthCache cache;
+ cache.Add(origin, realm1_handler->realm(), realm1_handler->auth_scheme(),
+ "basic realm=Realm1", AuthCredentials(kAlice, k123), "/");
+ cache.Add(origin, realm2_handler->realm(), realm2_handler->auth_scheme(),
+ "basic realm=Realm2", CreateASCIICredentials("bob", "princess"),
+ "/");
+ cache.Add(origin, realm3_basic_handler->realm(),
+ realm3_basic_handler->auth_scheme(), "basic realm=Realm3",
+ AuthCredentials(kAdmin, kPassword), "/");
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "digest realm=Realm3",
+ AuthCredentials(kRoot, kWileCoyote), "/");
+
+ // Fails, because there is no realm "Realm5".
+ EXPECT_FALSE(cache.Remove(
+ origin, kRealm5, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice, k123)));
+
+ // Fails because the origin is wrong.
+ EXPECT_FALSE(cache.Remove(GURL("http://foobar2.com:100"),
+ kRealm1,
+ HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice, k123)));
+
+ // Fails because the username is wrong.
+ EXPECT_FALSE(cache.Remove(
+ origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice2, k123)));
+
+ // Fails because the password is wrong.
+ EXPECT_FALSE(cache.Remove(
+ origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice, k1234)));
+
+ // Fails because the authentication type is wrong.
+ EXPECT_FALSE(cache.Remove(
+ origin, kRealm1, HttpAuth::AUTH_SCHEME_DIGEST,
+ AuthCredentials(kAlice, k123)));
+
+ // Succeeds.
+ EXPECT_TRUE(cache.Remove(
+ origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice, k123)));
+
+ // Fails because we just deleted the entry!
+ EXPECT_FALSE(cache.Remove(
+ origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAlice, k123)));
+
+ // Succeed when there are two authentication types for the same origin,realm.
+ EXPECT_TRUE(cache.Remove(
+ origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST,
+ AuthCredentials(kRoot, kWileCoyote)));
+
+ // Succeed as above, but when entries were added in opposite order
+ cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "digest realm=Realm3",
+ AuthCredentials(kRoot, kWileCoyote), "/");
+ EXPECT_TRUE(cache.Remove(
+ origin, kRealm3, HttpAuth::AUTH_SCHEME_BASIC,
+ AuthCredentials(kAdmin, kPassword)));
+
+ // Make sure that removing one entry still leaves the other available for
+ // lookup.
+ HttpAuthCache::Entry* entry = cache.Lookup(
+ origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST);
+ EXPECT_FALSE(NULL == entry);
+}
+
+TEST(HttpAuthCacheTest, UpdateStaleChallenge) {
+ HttpAuthCache cache;
+ GURL origin("http://foobar2.com");
+ scoped_ptr<HttpAuthHandler> digest_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_DIGEST, kRealm1, HttpAuth::AUTH_PROXY));
+ HttpAuthCache::Entry* entry_pre = cache.Add(
+ origin,
+ digest_handler->realm(),
+ digest_handler->auth_scheme(),
+ "Digest realm=Realm1,"
+ "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\"",
+ CreateASCIICredentials("realm-digest-user", "realm-digest-password"),
+ "/baz/index.html");
+ ASSERT_TRUE(entry_pre != NULL);
+
+ EXPECT_EQ(2, entry_pre->IncrementNonceCount());
+ EXPECT_EQ(3, entry_pre->IncrementNonceCount());
+ EXPECT_EQ(4, entry_pre->IncrementNonceCount());
+
+ bool update_success = cache.UpdateStaleChallenge(
+ origin,
+ digest_handler->realm(),
+ digest_handler->auth_scheme(),
+ "Digest realm=Realm1,"
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\","
+ "stale=\"true\"");
+ EXPECT_TRUE(update_success);
+
+ // After the stale update, the entry should still exist in the cache and
+ // the nonce count should be reset to 0.
+ HttpAuthCache::Entry* entry_post = cache.Lookup(
+ origin,
+ digest_handler->realm(),
+ digest_handler->auth_scheme());
+ ASSERT_TRUE(entry_post != NULL);
+ EXPECT_EQ(2, entry_post->IncrementNonceCount());
+
+ // UpdateStaleChallenge will fail if an entry doesn't exist in the cache.
+ bool update_failure = cache.UpdateStaleChallenge(
+ origin,
+ kRealm2,
+ digest_handler->auth_scheme(),
+ "Digest realm=Realm2,"
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\","
+ "stale=\"true\"");
+ EXPECT_FALSE(update_failure);
+}
+
+TEST(HttpAuthCacheTest, UpdateAllFrom) {
+ GURL origin("http://example.com");
+ std::string path("/some/path");
+ std::string another_path("/another/path");
+
+ scoped_ptr<HttpAuthHandler> realm1_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm1, HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm2_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm2, HttpAuth::AUTH_PROXY));
+
+ scoped_ptr<HttpAuthHandler> realm3_digest_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_DIGEST, kRealm3, HttpAuth::AUTH_SERVER));
+
+ scoped_ptr<HttpAuthHandler> realm4_handler(
+ new MockAuthHandler(
+ HttpAuth::AUTH_SCHEME_BASIC, kRealm4, HttpAuth::AUTH_SERVER));
+
+ HttpAuthCache first_cache;
+ HttpAuthCache::Entry* entry;
+
+ first_cache.Add(origin, realm1_handler->realm(),
+ realm1_handler->auth_scheme(), "basic realm=Realm1",
+ AuthCredentials(kAlice, k123), path);
+ first_cache.Add(origin, realm2_handler->realm(),
+ realm2_handler->auth_scheme(), "basic realm=Realm2",
+ AuthCredentials(kAlice2, k1234), path);
+ first_cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "digest realm=Realm3",
+ AuthCredentials(kRoot, kWileCoyote), path);
+ entry = first_cache.Add(
+ origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "digest realm=Realm3",
+ AuthCredentials(kRoot, kWileCoyote), another_path);
+
+ EXPECT_EQ(2, entry->IncrementNonceCount());
+
+ HttpAuthCache second_cache;
+ // Will be overwritten by kRoot:kWileCoyote.
+ second_cache.Add(origin, realm3_digest_handler->realm(),
+ realm3_digest_handler->auth_scheme(), "digest realm=Realm3",
+ AuthCredentials(kAlice2, k1234), path);
+ // Should be left intact.
+ second_cache.Add(origin, realm4_handler->realm(),
+ realm4_handler->auth_scheme(), "basic realm=Realm4",
+ AuthCredentials(kAdmin, kRoot), path);
+
+ second_cache.UpdateAllFrom(first_cache);
+
+ // Copied from first_cache.
+ entry = second_cache.Lookup(origin, kRealm1, HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_TRUE(NULL != entry);
+ EXPECT_EQ(kAlice, entry->credentials().username());
+ EXPECT_EQ(k123, entry->credentials().password());
+
+ // Copied from first_cache.
+ entry = second_cache.Lookup(origin, kRealm2, HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_TRUE(NULL != entry);
+ EXPECT_EQ(kAlice2, entry->credentials().username());
+ EXPECT_EQ(k1234, entry->credentials().password());
+
+ // Overwritten from first_cache.
+ entry = second_cache.Lookup(origin, kRealm3, HttpAuth::AUTH_SCHEME_DIGEST);
+ EXPECT_TRUE(NULL != entry);
+ EXPECT_EQ(kRoot, entry->credentials().username());
+ EXPECT_EQ(kWileCoyote, entry->credentials().password());
+ // Nonce count should get copied.
+ EXPECT_EQ(3, entry->IncrementNonceCount());
+
+ // All paths should get copied.
+ entry = second_cache.LookupByPath(origin, another_path);
+ EXPECT_TRUE(NULL != entry);
+ EXPECT_EQ(kRoot, entry->credentials().username());
+ EXPECT_EQ(kWileCoyote, entry->credentials().password());
+
+ // Left intact in second_cache.
+ entry = second_cache.Lookup(origin, kRealm4, HttpAuth::AUTH_SCHEME_BASIC);
+ EXPECT_TRUE(NULL != entry);
+ EXPECT_EQ(kAdmin, entry->credentials().username());
+ EXPECT_EQ(kRoot, entry->credentials().password());
+}
+
+// Test fixture class for eviction tests (contains helpers for bulk
+// insertion and existence testing).
+class HttpAuthCacheEvictionTest : public testing::Test {
+ protected:
+ HttpAuthCacheEvictionTest() : origin_("http://www.google.com") { }
+
+ std::string GenerateRealm(int realm_i) {
+ return base::StringPrintf("Realm %d", realm_i);
+ }
+
+ std::string GeneratePath(int realm_i, int path_i) {
+ return base::StringPrintf("/%d/%d/x/y", realm_i, path_i);
+ }
+
+ void AddRealm(int realm_i) {
+ AddPathToRealm(realm_i, 0);
+ }
+
+ void AddPathToRealm(int realm_i, int path_i) {
+ cache_.Add(origin_,
+ GenerateRealm(realm_i),
+ HttpAuth::AUTH_SCHEME_BASIC,
+ std::string(),
+ AuthCredentials(kUsername, kPassword),
+ GeneratePath(realm_i, path_i));
+ }
+
+ void CheckRealmExistence(int realm_i, bool exists) {
+ const HttpAuthCache::Entry* entry =
+ cache_.Lookup(
+ origin_, GenerateRealm(realm_i), HttpAuth::AUTH_SCHEME_BASIC);
+ if (exists) {
+ EXPECT_FALSE(entry == NULL);
+ EXPECT_EQ(GenerateRealm(realm_i), entry->realm());
+ } else {
+ EXPECT_TRUE(entry == NULL);
+ }
+ }
+
+ void CheckPathExistence(int realm_i, int path_i, bool exists) {
+ const HttpAuthCache::Entry* entry =
+ cache_.LookupByPath(origin_, GeneratePath(realm_i, path_i));
+ if (exists) {
+ EXPECT_FALSE(entry == NULL);
+ EXPECT_EQ(GenerateRealm(realm_i), entry->realm());
+ } else {
+ EXPECT_TRUE(entry == NULL);
+ }
+ }
+
+ GURL origin_;
+ HttpAuthCache cache_;
+
+ static const int kMaxPaths = HttpAuthCache::kMaxNumPathsPerRealmEntry;
+ static const int kMaxRealms = HttpAuthCache::kMaxNumRealmEntries;
+};
+
+// Add the maxinim number of realm entries to the cache. Each of these entries
+// must still be retrievable. Next add three more entries -- since the cache is
+// full this causes FIFO eviction of the first three entries.
+TEST_F(HttpAuthCacheEvictionTest, RealmEntryEviction) {
+ for (int i = 0; i < kMaxRealms; ++i)
+ AddRealm(i);
+
+ for (int i = 0; i < kMaxRealms; ++i)
+ CheckRealmExistence(i, true);
+
+ for (int i = 0; i < 3; ++i)
+ AddRealm(i + kMaxRealms);
+
+ for (int i = 0; i < 3; ++i)
+ CheckRealmExistence(i, false);
+
+ for (int i = 0; i < kMaxRealms; ++i)
+ CheckRealmExistence(i + 3, true);
+}
+
+// Add the maximum number of paths to a single realm entry. Each of these
+// paths should be retrievable. Next add 3 more paths -- since the cache is
+// full this causes FIFO eviction of the first three paths.
+TEST_F(HttpAuthCacheEvictionTest, RealmPathEviction) {
+ for (int i = 0; i < kMaxPaths; ++i)
+ AddPathToRealm(0, i);
+
+ for (int i = 1; i < kMaxRealms; ++i)
+ AddRealm(i);
+
+ for (int i = 0; i < 3; ++i)
+ AddPathToRealm(0, i + kMaxPaths);
+
+ for (int i = 0; i < 3; ++i)
+ CheckPathExistence(0, i, false);
+
+ for (int i = 0; i < kMaxPaths; ++i)
+ CheckPathExistence(0, i + 3, true);
+
+ for (int i = 0; i < kMaxRealms; ++i)
+ CheckRealmExistence(i, true);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_controller.cc b/chromium/net/http/http_auth_controller.cc
new file mode 100644
index 00000000000..e9d6171ab5a
--- /dev/null
+++ b/chromium/net/http/http_auth_controller.cc
@@ -0,0 +1,573 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_controller.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/auth.h"
+#include "net/base/net_util.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+namespace {
+
+// Returns a log message for all the response headers related to the auth
+// challenge.
+std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) {
+ std::string msg;
+ std::string header_val;
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) {
+ msg.append("\n Has header Proxy-Authenticate: ");
+ msg.append(header_val);
+ }
+
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) {
+ msg.append("\n Has header WWW-Authenticate: ");
+ msg.append(header_val);
+ }
+
+ // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate
+ // authentication with a "Proxy-Support: Session-Based-Authentication"
+ // response header.
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) {
+ msg.append("\n Has header Proxy-Support: ");
+ msg.append(header_val);
+ }
+
+ return msg;
+}
+
+enum AuthEvent {
+ AUTH_EVENT_START = 0,
+ AUTH_EVENT_REJECT,
+ AUTH_EVENT_MAX,
+};
+
+enum AuthTarget {
+ AUTH_TARGET_PROXY = 0,
+ AUTH_TARGET_SECURE_PROXY,
+ AUTH_TARGET_SERVER,
+ AUTH_TARGET_SECURE_SERVER,
+ AUTH_TARGET_MAX,
+};
+
+AuthTarget DetermineAuthTarget(const HttpAuthHandler* handler) {
+ switch (handler->target()) {
+ case HttpAuth::AUTH_PROXY:
+ if (handler->origin().SchemeIsSecure())
+ return AUTH_TARGET_SECURE_PROXY;
+ else
+ return AUTH_TARGET_PROXY;
+ case HttpAuth::AUTH_SERVER:
+ if (handler->origin().SchemeIsSecure())
+ return AUTH_TARGET_SECURE_SERVER;
+ else
+ return AUTH_TARGET_SERVER;
+ default:
+ NOTREACHED();
+ return AUTH_TARGET_MAX;
+ }
+}
+
+// Records the number of authentication events per authentication scheme.
+void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) {
+#if !defined(NDEBUG)
+ // Note: The on-same-thread check is intentionally not using a lock
+ // to protect access to first_thread. This method is meant to be only
+ // used on the same thread, in which case there are no race conditions. If
+ // there are race conditions (say, a read completes during a partial write),
+ // the DCHECK will correctly fail.
+ static base::PlatformThreadId first_thread =
+ base::PlatformThread::CurrentId();
+ DCHECK_EQ(first_thread, base::PlatformThread::CurrentId());
+#endif
+
+ HttpAuth::Scheme auth_scheme = handler->auth_scheme();
+ DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX);
+
+ // Record start and rejection events for authentication.
+ //
+ // The results map to:
+ // Basic Start: 0
+ // Basic Reject: 1
+ // Digest Start: 2
+ // Digest Reject: 3
+ // NTLM Start: 4
+ // NTLM Reject: 5
+ // Negotiate Start: 6
+ // Negotiate Reject: 7
+ static const int kEventBucketsEnd =
+ HttpAuth::AUTH_SCHEME_MAX * AUTH_EVENT_MAX;
+ int event_bucket = auth_scheme * AUTH_EVENT_MAX + auth_event;
+ DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd);
+ UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket,
+ kEventBucketsEnd);
+
+ // Record the target of the authentication.
+ //
+ // The results map to:
+ // Basic Proxy: 0
+ // Basic Secure Proxy: 1
+ // Basic Server: 2
+ // Basic Secure Server: 3
+ // Digest Proxy: 4
+ // Digest Secure Proxy: 5
+ // Digest Server: 6
+ // Digest Secure Server: 7
+ // NTLM Proxy: 8
+ // NTLM Secure Proxy: 9
+ // NTLM Server: 10
+ // NTLM Secure Server: 11
+ // Negotiate Proxy: 12
+ // Negotiate Secure Proxy: 13
+ // Negotiate Server: 14
+ // Negotiate Secure Server: 15
+ if (auth_event != AUTH_EVENT_START)
+ return;
+ static const int kTargetBucketsEnd =
+ HttpAuth::AUTH_SCHEME_MAX * AUTH_TARGET_MAX;
+ AuthTarget auth_target = DetermineAuthTarget(handler);
+ int target_bucket = auth_scheme * AUTH_TARGET_MAX + auth_target;
+ DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd);
+ UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket,
+ kTargetBucketsEnd);
+}
+
+} // namespace
+
+HttpAuthController::HttpAuthController(
+ HttpAuth::Target target,
+ const GURL& auth_url,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory)
+ : target_(target),
+ auth_url_(auth_url),
+ auth_origin_(auth_url.GetOrigin()),
+ auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()),
+ embedded_identity_used_(false),
+ default_credentials_used_(false),
+ http_auth_cache_(http_auth_cache),
+ http_auth_handler_factory_(http_auth_handler_factory) {
+}
+
+HttpAuthController::~HttpAuthController() {
+ DCHECK(CalledOnValidThread());
+}
+
+int HttpAuthController::MaybeGenerateAuthToken(
+ const HttpRequestInfo* request, const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ bool needs_auth = HaveAuth() || SelectPreemptiveAuth(net_log);
+ if (!needs_auth)
+ return OK;
+ const AuthCredentials* credentials = NULL;
+ if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS)
+ credentials = &identity_.credentials;
+ DCHECK(auth_token_.empty());
+ DCHECK(callback_.is_null());
+ int rv = handler_->GenerateAuthToken(
+ credentials, request,
+ base::Bind(&HttpAuthController::OnIOComplete, base::Unretained(this)),
+ &auth_token_);
+ if (DisableOnAuthHandlerResult(rv))
+ rv = OK;
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ else
+ OnIOComplete(rv);
+ return rv;
+}
+
+bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!HaveAuth());
+ DCHECK(identity_.invalid);
+
+ // Don't do preemptive authorization if the URL contains a username:password,
+ // since we must first be challenged in order to use the URL's identity.
+ if (auth_url_.has_username())
+ return false;
+
+ // SelectPreemptiveAuth() is on the critical path for each request, so it
+ // is expected to be fast. LookupByPath() is fast in the common case, since
+ // the number of http auth cache entries is expected to be very small.
+ // (For most users in fact, it will be 0.)
+ HttpAuthCache::Entry* entry = http_auth_cache_->LookupByPath(
+ auth_origin_, auth_path_);
+ if (!entry)
+ return false;
+
+ // Try to create a handler using the previous auth challenge.
+ scoped_ptr<HttpAuthHandler> handler_preemptive;
+ int rv_create = http_auth_handler_factory_->
+ CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_,
+ auth_origin_,
+ entry->IncrementNonceCount(),
+ net_log, &handler_preemptive);
+ if (rv_create != OK)
+ return false;
+
+ // Set the state
+ identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP;
+ identity_.invalid = false;
+ identity_.credentials = entry->credentials();
+ handler_.swap(handler_preemptive);
+ return true;
+}
+
+void HttpAuthController::AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(HaveAuth());
+ // auth_token_ can be empty if we encountered a permanent error with
+ // the auth scheme and want to retry.
+ if (!auth_token_.empty()) {
+ authorization_headers->SetHeader(
+ HttpAuth::GetAuthorizationHeaderName(target_), auth_token_);
+ auth_token_.clear();
+ }
+}
+
+int HttpAuthController::HandleAuthChallenge(
+ scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(headers.get());
+ DCHECK(auth_origin_.is_valid());
+ VLOG(1) << "The " << HttpAuth::GetAuthTargetString(target_) << " "
+ << auth_origin_ << " requested auth "
+ << AuthChallengeLogMessage(headers.get());
+
+ // Give the existing auth handler first try at the authentication headers.
+ // This will also evict the entry in the HttpAuthCache if the previous
+ // challenge appeared to be rejected, or is using a stale nonce in the Digest
+ // case.
+ if (HaveAuth()) {
+ std::string challenge_used;
+ HttpAuth::AuthorizationResult result =
+ HttpAuth::HandleChallengeResponse(handler_.get(),
+ headers.get(),
+ target_,
+ disabled_schemes_,
+ &challenge_used);
+ switch (result) {
+ case HttpAuth::AUTHORIZATION_RESULT_ACCEPT:
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_INVALID:
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_REJECT:
+ HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT);
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_STALE:
+ if (http_auth_cache_->UpdateStaleChallenge(auth_origin_,
+ handler_->realm(),
+ handler_->auth_scheme(),
+ challenge_used)) {
+ InvalidateCurrentHandler(INVALIDATE_HANDLER);
+ } else {
+ // It's possible that a server could incorrectly issue a stale
+ // response when the entry is not in the cache. Just evict the
+ // current value from the cache.
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
+ }
+ break;
+ case HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM:
+ // If the server changes the authentication realm in a
+ // subsequent challenge, invalidate cached credentials for the
+ // previous realm. If the server rejects a preemptive
+ // authorization and requests credentials for a different
+ // realm, we keep the cached credentials.
+ InvalidateCurrentHandler(
+ (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) ?
+ INVALIDATE_HANDLER :
+ INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ identity_.invalid = true;
+
+ bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER ||
+ !do_not_send_server_auth);
+
+ do {
+ if (!handler_.get() && can_send_auth) {
+ // Find the best authentication challenge that we support.
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory_,
+ headers.get(),
+ target_,
+ auth_origin_,
+ disabled_schemes_,
+ net_log,
+ &handler_);
+ if (handler_.get())
+ HistogramAuthEvent(handler_.get(), AUTH_EVENT_START);
+ }
+
+ if (!handler_.get()) {
+ if (establishing_tunnel) {
+ LOG(ERROR) << "Can't perform auth to the "
+ << HttpAuth::GetAuthTargetString(target_) << " "
+ << auth_origin_ << " when establishing a tunnel"
+ << AuthChallengeLogMessage(headers.get());
+
+ // We are establishing a tunnel, we can't show the error page because an
+ // active network attacker could control its contents. Instead, we just
+ // fail to establish the tunnel.
+ DCHECK(target_ == HttpAuth::AUTH_PROXY);
+ return ERR_PROXY_AUTH_UNSUPPORTED;
+ }
+ // We found no supported challenge -- let the transaction continue so we
+ // end up displaying the error page.
+ return OK;
+ }
+
+ if (handler_->NeedsIdentity()) {
+ // Pick a new auth identity to try, by looking to the URL and auth cache.
+ // If an identity to try is found, it is saved to identity_.
+ SelectNextAuthIdentityToTry();
+ } else {
+ // Proceed with the existing identity or a null identity.
+ identity_.invalid = false;
+ }
+
+ // From this point on, we are restartable.
+
+ if (identity_.invalid) {
+ // We have exhausted all identity possibilities.
+ if (!handler_->AllowsExplicitCredentials()) {
+ // If the handler doesn't accept explicit credentials, then we need to
+ // choose a different auth scheme.
+ HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT);
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
+ } else {
+ // Pass the challenge information back to the client.
+ PopulateAuthChallenge();
+ }
+ } else {
+ auth_info_ = NULL;
+ }
+
+ // If we get here and we don't have a handler_, that's because we
+ // invalidated it due to not having any viable identities to use with it. Go
+ // back and try again.
+ // TODO(asanka): Instead we should create a priority list of
+ // <handler,identity> and iterate through that.
+ } while(!handler_.get());
+ return OK;
+}
+
+void HttpAuthController::ResetAuth(const AuthCredentials& credentials) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(identity_.invalid || credentials.Empty());
+
+ if (identity_.invalid) {
+ // Update the credentials.
+ identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
+ identity_.invalid = false;
+ identity_.credentials = credentials;
+ }
+
+ DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
+
+ // Add the auth entry to the cache before restarting. We don't know whether
+ // the identity is valid yet, but if it is valid we want other transactions
+ // to know about it. If an entry for (origin, handler->realm()) already
+ // exists, we update it.
+ //
+ // If identity_.source is HttpAuth::IDENT_SRC_NONE or
+ // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no
+ // identity because identity is not required yet or we're using default
+ // credentials.
+ //
+ // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in
+ // round 1 and round 2, which is redundant but correct. It would be nice
+ // to add an auth entry to the cache only once, preferrably in round 1.
+ // See http://crbug.com/21015.
+ switch (identity_.source) {
+ case HttpAuth::IDENT_SRC_NONE:
+ case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS:
+ break;
+ default:
+ http_auth_cache_->Add(auth_origin_, handler_->realm(),
+ handler_->auth_scheme(), handler_->challenge(),
+ identity_.credentials, auth_path_);
+ break;
+ }
+}
+
+bool HttpAuthController::HaveAuthHandler() const {
+ return handler_.get() != NULL;
+}
+
+bool HttpAuthController::HaveAuth() const {
+ return handler_.get() && !identity_.invalid;
+}
+
+void HttpAuthController::InvalidateCurrentHandler(
+ InvalidateHandlerAction action) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(handler_.get());
+
+ if (action == INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS)
+ InvalidateRejectedAuthFromCache();
+ if (action == INVALIDATE_HANDLER_AND_DISABLE_SCHEME)
+ DisableAuthScheme(handler_->auth_scheme());
+ handler_.reset();
+ identity_ = HttpAuth::Identity();
+}
+
+void HttpAuthController::InvalidateRejectedAuthFromCache() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(HaveAuth());
+
+ // Clear the cache entry for the identity we just failed on.
+ // Note: we require the credentials to match before invalidating
+ // since the entry in the cache may be newer than what we used last time.
+ http_auth_cache_->Remove(auth_origin_, handler_->realm(),
+ handler_->auth_scheme(), identity_.credentials);
+}
+
+bool HttpAuthController::SelectNextAuthIdentityToTry() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(handler_.get());
+ DCHECK(identity_.invalid);
+
+ // Try to use the username:password encoded into the URL first.
+ if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() &&
+ !embedded_identity_used_) {
+ identity_.source = HttpAuth::IDENT_SRC_URL;
+ identity_.invalid = false;
+ // Extract the username:password from the URL.
+ base::string16 username;
+ base::string16 password;
+ GetIdentityFromURL(auth_url_, &username, &password);
+ identity_.credentials.Set(username, password);
+ embedded_identity_used_ = true;
+ // TODO(eroman): If the password is blank, should we also try combining
+ // with a password from the cache?
+ UMA_HISTOGRAM_BOOLEAN("net.HttpIdentSrcURL", true);
+ return true;
+ }
+
+ // Check the auth cache for a realm entry.
+ HttpAuthCache::Entry* entry =
+ http_auth_cache_->Lookup(auth_origin_, handler_->realm(),
+ handler_->auth_scheme());
+
+ if (entry) {
+ identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
+ identity_.invalid = false;
+ identity_.credentials = entry->credentials();
+ return true;
+ }
+
+ // Use default credentials (single sign on) if this is the first attempt
+ // at identity. Do not allow multiple times as it will infinite loop.
+ // We use default credentials after checking the auth cache so that if
+ // single sign-on doesn't work, we won't try default credentials for future
+ // transactions.
+ if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) {
+ identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS;
+ identity_.invalid = false;
+ default_credentials_used_ = true;
+ return true;
+ }
+
+ return false;
+}
+
+void HttpAuthController::PopulateAuthChallenge() {
+ DCHECK(CalledOnValidThread());
+
+ // Populates response_.auth_challenge with the authentication challenge info.
+ // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
+
+ auth_info_ = new AuthChallengeInfo;
+ auth_info_->is_proxy = (target_ == HttpAuth::AUTH_PROXY);
+ auth_info_->challenger = HostPortPair::FromURL(auth_origin_);
+ auth_info_->scheme = HttpAuth::SchemeToString(handler_->auth_scheme());
+ auth_info_->realm = handler_->realm();
+}
+
+bool HttpAuthController::DisableOnAuthHandlerResult(int result) {
+ DCHECK(CalledOnValidThread());
+
+ switch (result) {
+ // Occurs with GSSAPI, if the user has not already logged in.
+ case ERR_MISSING_AUTH_CREDENTIALS:
+
+ // Can occur with GSSAPI or SSPI if the underlying library reports
+ // a permanent error.
+ case ERR_UNSUPPORTED_AUTH_SCHEME:
+
+ // These two error codes represent failures we aren't handling.
+ case ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
+ case ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
+
+ // Can be returned by SSPI if the authenticating authority or
+ // target is not known.
+ case ERR_MISCONFIGURED_AUTH_ENVIRONMENT:
+
+ // In these cases, disable the current scheme as it cannot
+ // succeed.
+ DisableAuthScheme(handler_->auth_scheme());
+ auth_token_.clear();
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+void HttpAuthController::OnIOComplete(int result) {
+ DCHECK(CalledOnValidThread());
+ if (DisableOnAuthHandlerResult(result))
+ result = OK;
+ if (!callback_.is_null()) {
+ CompletionCallback c = callback_;
+ callback_.Reset();
+ c.Run(result);
+ }
+}
+
+scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() {
+ DCHECK(CalledOnValidThread());
+ return auth_info_;
+}
+
+bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
+ DCHECK(CalledOnValidThread());
+ return disabled_schemes_.find(scheme) != disabled_schemes_.end();
+}
+
+void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
+ DCHECK(CalledOnValidThread());
+ disabled_schemes_.insert(scheme);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_controller.h b/chromium/net/http/http_auth_controller.h
new file mode 100644
index 00000000000..5d5b469a265
--- /dev/null
+++ b/chromium/net/http/http_auth_controller.h
@@ -0,0 +1,170 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_CONTROLLER_H_
+#define NET_HTTP_HTTP_AUTH_CONTROLLER_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class AuthChallengeInfo;
+class AuthCredentials;
+class HttpAuthHandler;
+class HttpAuthHandlerFactory;
+class HttpAuthCache;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+
+class NET_EXPORT_PRIVATE HttpAuthController
+ : public base::RefCounted<HttpAuthController>,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // The arguments are self explanatory except possibly for |auth_url|, which
+ // should be both the auth target and auth path in a single url argument.
+ HttpAuthController(HttpAuth::Target target,
+ const GURL& auth_url,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory);
+
+ // Generate an authentication token for |target| if necessary. The return
+ // value is a net error code. |OK| will be returned both in the case that
+ // a token is correctly generated synchronously, as well as when no tokens
+ // were necessary.
+ virtual int MaybeGenerateAuthToken(const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log);
+
+ // Adds either the proxy auth header, or the origin server auth header,
+ // as specified by |target_|.
+ virtual void AddAuthorizationHeader(
+ HttpRequestHeaders* authorization_headers);
+
+ // Checks for and handles HTTP status code 401 or 407.
+ // |HandleAuthChallenge()| returns OK on success, or a network error code
+ // otherwise. It may also populate |auth_info_|.
+ virtual int HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers,
+ bool do_not_send_server_auth,
+ bool establishing_tunnel,
+ const BoundNetLog& net_log);
+
+ // Store the supplied credentials and prepare to restart the auth.
+ virtual void ResetAuth(const AuthCredentials& credentials);
+
+ virtual bool HaveAuthHandler() const;
+
+ virtual bool HaveAuth() const;
+
+ virtual scoped_refptr<AuthChallengeInfo> auth_info();
+
+ virtual bool IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const;
+ virtual void DisableAuthScheme(HttpAuth::Scheme scheme);
+
+ private:
+ // Actions for InvalidateCurrentHandler()
+ enum InvalidateHandlerAction {
+ INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS,
+ INVALIDATE_HANDLER_AND_DISABLE_SCHEME,
+ INVALIDATE_HANDLER
+ };
+
+ // So that we can mock this object.
+ friend class base::RefCounted<HttpAuthController>;
+
+ virtual ~HttpAuthController();
+
+ // Searches the auth cache for an entry that encompasses the request's path.
+ // If such an entry is found, updates |identity_| and |handler_| with the
+ // cache entry's data and returns true.
+ bool SelectPreemptiveAuth(const BoundNetLog& net_log);
+
+ // Invalidates the current handler. If |action| is
+ // INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS, then also invalidate
+ // the cached credentials used by the handler.
+ void InvalidateCurrentHandler(InvalidateHandlerAction action);
+
+ // Invalidates any auth cache entries after authentication has failed.
+ // The identity that was rejected is |identity_|.
+ void InvalidateRejectedAuthFromCache();
+
+ // Sets |identity_| to the next identity that the transaction should try. It
+ // chooses candidates by searching the auth cache and the URL for a
+ // username:password. Returns true if an identity was found.
+ bool SelectNextAuthIdentityToTry();
+
+ // Populates auth_info_ with the challenge information, so that
+ // URLRequestHttpJob can prompt for credentials.
+ void PopulateAuthChallenge();
+
+ // If |result| indicates a permanent failure, disables the current
+ // auth scheme for this controller and returns true. Returns false
+ // otherwise.
+ bool DisableOnAuthHandlerResult(int result);
+
+ void OnIOComplete(int result);
+
+ // Indicates if this handler is for Proxy auth or Server auth.
+ HttpAuth::Target target_;
+
+ // Holds the {scheme, host, path, port} for the authentication target.
+ const GURL auth_url_;
+
+ // Holds the {scheme, host, port} for the authentication target.
+ const GURL auth_origin_;
+
+ // The absolute path of the resource needing authentication.
+ // For proxy authentication the path is empty.
+ const std::string auth_path_;
+
+ // |handler_| encapsulates the logic for the particular auth-scheme.
+ // This includes the challenge's parameters. If NULL, then there is no
+ // associated auth handler.
+ scoped_ptr<HttpAuthHandler> handler_;
+
+ // |identity_| holds the credentials that should be used by
+ // the handler_ to generate challenge responses. This identity can come from
+ // a number of places (url, cache, prompt).
+ HttpAuth::Identity identity_;
+
+ // |auth_token_| contains the opaque string to pass to the proxy or
+ // server to authenticate the client.
+ std::string auth_token_;
+
+ // Contains information about the auth challenge.
+ scoped_refptr<AuthChallengeInfo> auth_info_;
+
+ // True if we've used the username:password embedded in the URL. This
+ // makes sure we use the embedded identity only once for the transaction,
+ // preventing an infinite auth restart loop.
+ bool embedded_identity_used_;
+
+ // True if default credentials have already been tried for this transaction
+ // in response to an HTTP authentication challenge.
+ bool default_credentials_used_;
+
+ // These two are owned by the HttpNetworkSession/IOThread, which own the
+ // objects which reference |this|. Therefore, these raw pointers are valid
+ // for the lifetime of this object.
+ HttpAuthCache* const http_auth_cache_;
+ HttpAuthHandlerFactory* const http_auth_handler_factory_;
+
+ std::set<HttpAuth::Scheme> disabled_schemes_;
+
+ CompletionCallback callback_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_CONTROLLER_H_
diff --git a/chromium/net/http/http_auth_controller_unittest.cc b/chromium/net/http/http_auth_controller_unittest.cc
new file mode 100644
index 00000000000..2a18369c1f7
--- /dev/null
+++ b/chromium/net/http/http_auth_controller_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2011 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/http/http_auth_controller.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+enum HandlerRunMode {
+ RUN_HANDLER_SYNC,
+ RUN_HANDLER_ASYNC
+};
+
+enum SchemeState {
+ SCHEME_IS_DISABLED,
+ SCHEME_IS_ENABLED
+};
+
+scoped_refptr<HttpResponseHeaders> HeadersFromString(const char* string) {
+ std::string raw_string(string);
+ std::string headers_string = HttpUtil::AssembleRawHeaders(
+ raw_string.c_str(), raw_string.length());
+ scoped_refptr<HttpResponseHeaders> headers(
+ new HttpResponseHeaders(headers_string));
+ return headers;
+}
+
+// Runs an HttpAuthController with a single round mock auth handler
+// that returns |handler_rv| on token generation. The handler runs in
+// async if |run_mode| is RUN_HANDLER_ASYNC. Upon completion, the
+// return value of the controller is tested against
+// |expected_controller_rv|. |scheme_state| indicates whether the
+// auth scheme used should be disabled after this run.
+void RunSingleRoundAuthTest(HandlerRunMode run_mode,
+ int handler_rv,
+ int expected_controller_rv,
+ SchemeState scheme_state) {
+ BoundNetLog dummy_log;
+ HttpAuthCache dummy_auth_cache;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://example.com");
+
+ scoped_refptr<HttpResponseHeaders> headers(HeadersFromString(
+ "HTTP/1.1 407\r\n"
+ "Proxy-Authenticate: MOCK foo\r\n"
+ "\r\n"));
+
+ HttpAuthHandlerMock::Factory auth_handler_factory;
+ HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
+ auth_handler->SetGenerateExpectation((run_mode == RUN_HANDLER_ASYNC),
+ handler_rv);
+ auth_handler_factory.AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY);
+ auth_handler_factory.set_do_init_from_challenge(true);
+
+ scoped_refptr<HttpAuthController> controller(
+ new HttpAuthController(HttpAuth::AUTH_PROXY,
+ GURL("http://example.com"),
+ &dummy_auth_cache, &auth_handler_factory));
+ ASSERT_EQ(OK,
+ controller->HandleAuthChallenge(headers, false, false, dummy_log));
+ ASSERT_TRUE(controller->HaveAuthHandler());
+ controller->ResetAuth(AuthCredentials());
+ EXPECT_TRUE(controller->HaveAuth());
+
+ TestCompletionCallback callback;
+ EXPECT_EQ((run_mode == RUN_HANDLER_ASYNC)? ERR_IO_PENDING:
+ expected_controller_rv,
+ controller->MaybeGenerateAuthToken(&request, callback.callback(),
+ dummy_log));
+ if (run_mode == RUN_HANDLER_ASYNC)
+ EXPECT_EQ(expected_controller_rv, callback.WaitForResult());
+ EXPECT_EQ((scheme_state == SCHEME_IS_DISABLED),
+ controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK));
+}
+
+} // namespace
+
+// If an HttpAuthHandler returns an error code that indicates a
+// permanent error, the HttpAuthController should disable the scheme
+// used and retry the request.
+TEST(HttpAuthControllerTest, PermanentErrors) {
+
+ // Run a synchronous handler that returns
+ // ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS. We expect a return value
+ // of OK from the controller so we can retry the request.
+ RunSingleRoundAuthTest(RUN_HANDLER_SYNC,
+ ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS,
+ OK, SCHEME_IS_DISABLED);
+
+ // Now try an async handler that returns
+ // ERR_MISSING_AUTH_CREDENTIALS. Async and sync handlers invoke
+ // different code paths in HttpAuthController when generating
+ // tokens.
+ RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_MISSING_AUTH_CREDENTIALS, OK,
+ SCHEME_IS_DISABLED);
+
+ // If a non-permanent error is returned by the handler, then the
+ // controller should report it unchanged.
+ RunSingleRoundAuthTest(RUN_HANDLER_ASYNC, ERR_INVALID_AUTH_CREDENTIALS,
+ ERR_INVALID_AUTH_CREDENTIALS, SCHEME_IS_ENABLED);
+}
+
+// If an HttpAuthHandler indicates that it doesn't allow explicit
+// credentials, don't prompt for credentials.
+TEST(HttpAuthControllerTest, NoExplicitCredentialsAllowed) {
+ // Modified mock HttpAuthHandler for this test.
+ class MockHandler : public HttpAuthHandlerMock {
+ public:
+ MockHandler(int expected_rv, HttpAuth::Scheme scheme)
+ : expected_scheme_(scheme) {
+ SetGenerateExpectation(false, expected_rv);
+ }
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE {
+ HttpAuthHandlerMock::Init(challenge);
+ set_allows_default_credentials(true);
+ set_allows_explicit_credentials(false);
+ set_connection_based(true);
+ // Pretend to be SCHEME_BASIC so we can test failover logic.
+ if (challenge->scheme() == "Basic") {
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_BASIC;
+ --score_; // Reduce score, so we rank below Mock.
+ set_allows_explicit_credentials(true);
+ }
+ EXPECT_EQ(expected_scheme_, auth_scheme_);
+ return true;
+ }
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE {
+ int result =
+ HttpAuthHandlerMock::GenerateAuthTokenImpl(credentials,
+ request, callback,
+ auth_token);
+ EXPECT_TRUE(result != OK ||
+ !AllowsExplicitCredentials() ||
+ !credentials->Empty());
+ return result;
+ }
+
+ private:
+ HttpAuth::Scheme expected_scheme_;
+ };
+
+ BoundNetLog dummy_log;
+ HttpAuthCache dummy_auth_cache;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://example.com");
+
+ HttpRequestHeaders request_headers;
+ scoped_refptr<HttpResponseHeaders> headers(HeadersFromString(
+ "HTTP/1.1 401\r\n"
+ "WWW-Authenticate: Mock\r\n"
+ "WWW-Authenticate: Basic\r\n"
+ "\r\n"));
+
+ HttpAuthHandlerMock::Factory auth_handler_factory;
+
+ // Handlers for the first attempt at authentication. AUTH_SCHEME_MOCK handler
+ // accepts the default identity and successfully constructs a token.
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(OK, HttpAuth::AUTH_SCHEME_MOCK), HttpAuth::AUTH_SERVER);
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_BASIC),
+ HttpAuth::AUTH_SERVER);
+
+ // Handlers for the second attempt. Neither should be used to generate a
+ // token. Instead the controller should realize that there are no viable
+ // identities to use with the AUTH_SCHEME_MOCK handler and fail.
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK),
+ HttpAuth::AUTH_SERVER);
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_BASIC),
+ HttpAuth::AUTH_SERVER);
+
+ // Fallback handlers for the second attempt. The AUTH_SCHEME_MOCK handler
+ // should be discarded due to the disabled scheme, and the AUTH_SCHEME_BASIC
+ // handler should successfully be used to generate a token.
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(ERR_UNEXPECTED, HttpAuth::AUTH_SCHEME_MOCK),
+ HttpAuth::AUTH_SERVER);
+ auth_handler_factory.AddMockHandler(
+ new MockHandler(OK, HttpAuth::AUTH_SCHEME_BASIC),
+ HttpAuth::AUTH_SERVER);
+ auth_handler_factory.set_do_init_from_challenge(true);
+
+ scoped_refptr<HttpAuthController> controller(
+ new HttpAuthController(HttpAuth::AUTH_SERVER,
+ GURL("http://example.com"),
+ &dummy_auth_cache, &auth_handler_factory));
+ ASSERT_EQ(OK,
+ controller->HandleAuthChallenge(headers, false, false, dummy_log));
+ ASSERT_TRUE(controller->HaveAuthHandler());
+ controller->ResetAuth(AuthCredentials());
+ EXPECT_TRUE(controller->HaveAuth());
+
+ // Should only succeed if we are using the AUTH_SCHEME_MOCK MockHandler.
+ EXPECT_EQ(OK, controller->MaybeGenerateAuthToken(
+ &request, CompletionCallback(), dummy_log));
+ controller->AddAuthorizationHeader(&request_headers);
+
+ // Once a token is generated, simulate the receipt of a server response
+ // indicating that the authentication attempt was rejected.
+ ASSERT_EQ(OK,
+ controller->HandleAuthChallenge(headers, false, false, dummy_log));
+ ASSERT_TRUE(controller->HaveAuthHandler());
+ controller->ResetAuth(AuthCredentials(ASCIIToUTF16("Hello"),
+ base::string16()));
+ EXPECT_TRUE(controller->HaveAuth());
+ EXPECT_TRUE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_MOCK));
+ EXPECT_FALSE(controller->IsAuthSchemeDisabled(HttpAuth::AUTH_SCHEME_BASIC));
+
+ // Should only succeed if we are using the AUTH_SCHEME_BASIC MockHandler.
+ EXPECT_EQ(OK, controller->MaybeGenerateAuthToken(
+ &request, CompletionCallback(), dummy_log));
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_filter.cc b/chromium/net/http/http_auth_filter.cc
new file mode 100644
index 00000000000..53d81eaab4b
--- /dev/null
+++ b/chromium/net/http/http_auth_filter.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2010 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 "base/strings/string_util.h"
+#include "net/http/http_auth_filter.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// Using a std::set<> has the benefit of removing duplicates automatically.
+typedef std::set<base::string16> RegistryWhitelist;
+
+// TODO(ahendrickson) -- Determine if we want separate whitelists for HTTP and
+// HTTPS, one for both, or only an HTTP one. My understanding is that the HTTPS
+// entries in the registry mean that you are only allowed to connect to the site
+// via HTTPS and still be considered 'safe'.
+
+HttpAuthFilterWhitelist::HttpAuthFilterWhitelist(
+ const std::string& server_whitelist) {
+ SetWhitelist(server_whitelist);
+}
+
+HttpAuthFilterWhitelist::~HttpAuthFilterWhitelist() {
+}
+
+// Add a new domain |filter| to the whitelist, if it's not already there
+bool HttpAuthFilterWhitelist::AddFilter(const std::string& filter,
+ HttpAuth::Target target) {
+ if ((target != HttpAuth::AUTH_SERVER) && (target != HttpAuth::AUTH_PROXY))
+ return false;
+ // All proxies pass
+ if (target == HttpAuth::AUTH_PROXY)
+ return true;
+ rules_.AddRuleFromString(filter);
+ return true;
+}
+
+void HttpAuthFilterWhitelist::AddRuleToBypassLocal() {
+ rules_.AddRuleToBypassLocal();
+}
+
+bool HttpAuthFilterWhitelist::IsValid(const GURL& url,
+ HttpAuth::Target target) const {
+ if ((target != HttpAuth::AUTH_SERVER) && (target != HttpAuth::AUTH_PROXY))
+ return false;
+ // All proxies pass
+ if (target == HttpAuth::AUTH_PROXY)
+ return true;
+ return rules_.Matches(url);
+}
+
+void HttpAuthFilterWhitelist::SetWhitelist(
+ const std::string& server_whitelist) {
+ rules_.ParseFromString(server_whitelist);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_filter.h b/chromium/net/http/http_auth_filter.h
new file mode 100644
index 00000000000..9f09ac13135
--- /dev/null
+++ b/chromium/net/http/http_auth_filter.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_FILTER_H_
+#define NET_HTTP_HTTP_AUTH_FILTER_H_
+
+#include <list>
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+#include "net/proxy/proxy_bypass_rules.h"
+
+class GURL;
+
+namespace net {
+
+// |HttpAuthFilter|s determine whether an authentication scheme should be
+// allowed for a particular peer.
+class NET_EXPORT_PRIVATE HttpAuthFilter {
+ public:
+ virtual ~HttpAuthFilter() {}
+
+ // Checks if (|url|, |target|) is supported by the authentication scheme.
+ // Only the host of |url| is examined.
+ virtual bool IsValid(const GURL& url, HttpAuth::Target target) const = 0;
+};
+
+// Whitelist HTTP authentication filter.
+// Explicit whitelists of domains are set via SetWhitelist().
+//
+// Uses the ProxyBypassRules class to do whitelisting for servers.
+// All proxies are allowed.
+class NET_EXPORT HttpAuthFilterWhitelist : public HttpAuthFilter {
+ public:
+ explicit HttpAuthFilterWhitelist(const std::string& server_whitelist);
+ virtual ~HttpAuthFilterWhitelist();
+
+ // Adds an individual URL |filter| to the list, of the specified |target|.
+ bool AddFilter(const std::string& filter, HttpAuth::Target target);
+
+ // Adds a rule that bypasses all "local" hostnames.
+ void AddRuleToBypassLocal();
+
+ const ProxyBypassRules& rules() const { return rules_; }
+
+ // HttpAuthFilter methods:
+ virtual bool IsValid(const GURL& url, HttpAuth::Target target) const OVERRIDE;
+
+ private:
+ // Installs the whitelist.
+ // |server_whitelist| is parsed by ProxyBypassRules.
+ void SetWhitelist(const std::string& server_whitelist);
+
+ // We are using ProxyBypassRules because they have the functionality that we
+ // want, but we are not using it for proxy bypass.
+ ProxyBypassRules rules_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthFilterWhitelist);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_FILTER_H_
diff --git a/chromium/net/http/http_auth_filter_unittest.cc b/chromium/net/http/http_auth_filter_unittest.cc
new file mode 100644
index 00000000000..25723c136a3
--- /dev/null
+++ b/chromium/net/http/http_auth_filter_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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 <ostream>
+
+
+#include "base/memory/scoped_ptr.h"
+#include "net/http/http_auth_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+static const char* const server_whitelist_array[] = {
+ "google.com",
+ "linkedin.com",
+ "book.com",
+ ".chromium.org",
+ ".gag",
+ "gog"
+};
+
+enum {
+ ALL_SERVERS_MATCH = (1 << arraysize(server_whitelist_array)) - 1
+};
+
+struct UrlData {
+ GURL url;
+ HttpAuth::Target target;
+ bool matches;
+ int match_bits;
+};
+
+static const UrlData urls[] = {
+ { GURL(std::string()), HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://foo.cn"), HttpAuth::AUTH_PROXY, true, ALL_SERVERS_MATCH },
+ { GURL("http://foo.cn"), HttpAuth::AUTH_SERVER, false, 0 },
+ { GURL("http://slashdot.org"), HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://www.google.com"), HttpAuth::AUTH_SERVER, true, 1 << 0 },
+ { GURL("http://www.google.com"), HttpAuth::AUTH_PROXY, true,
+ ALL_SERVERS_MATCH },
+ { GURL("https://login.facebook.com/login.php?login_attempt=1"),
+ HttpAuth::AUTH_NONE, false, 0 },
+ { GURL("http://codereview.chromium.org/634002/show"), HttpAuth::AUTH_SERVER,
+ true, 1 << 3 },
+ { GURL("http://code.google.com/p/chromium/issues/detail?id=34505"),
+ HttpAuth::AUTH_SERVER, true, 1 << 0 },
+ { GURL("http://code.google.com/p/chromium/issues/list?can=2&q=label:"
+ "spdy&sort=owner&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20"
+ "Summary%20Modified%20Owner%20Mstone%20OS"),
+ HttpAuth::AUTH_SERVER, true, 1 << 3 },
+ { GURL("https://www.linkedin.com/secure/login?trk=hb_signin"),
+ HttpAuth::AUTH_SERVER, true, 1 << 1 },
+ { GURL("http://www.linkedin.com/mbox?displayMBoxItem=&"
+ "itemID=I1717980652_2&trk=COMM_HP_MSGVW_MEBC_MEBC&goback=.hom"),
+ HttpAuth::AUTH_SERVER, true, 1 << 1 },
+ { GURL("http://news.slashdot.org/story/10/02/18/190236/"
+ "New-Plan-Lets-Top-HS-Students-Graduate-2-Years-Early"),
+ HttpAuth::AUTH_PROXY, true, ALL_SERVERS_MATCH },
+ { GURL("http://codereview.chromium.org/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 3 },
+ { GURL("http://codereview.chromium.gag/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 4 },
+ { GURL("http://codereview.chromium.gog/646068/diff/4001/5003"),
+ HttpAuth::AUTH_SERVER, true, 1 << 5 },
+};
+
+} // namespace
+
+TEST(HttpAuthFilterTest, EmptyFilter) {
+ // Create an empty filter
+ HttpAuthFilterWhitelist filter((std::string()));
+ for (size_t i = 0; i < arraysize(urls); i++) {
+ EXPECT_EQ(urls[i].target == HttpAuth::AUTH_PROXY,
+ filter.IsValid(urls[i].url, urls[i].target))
+ << " " << i << ": " << urls[i].url;
+ }
+}
+
+TEST(HttpAuthFilterTest, NonEmptyFilter) {
+ // Create an non-empty filter
+ std::string server_whitelist_filter_string;
+ for (size_t i = 0; i < arraysize(server_whitelist_array); ++i) {
+ if (!server_whitelist_filter_string.empty())
+ server_whitelist_filter_string += ",";
+ server_whitelist_filter_string += "*";
+ server_whitelist_filter_string += server_whitelist_array[i];
+ }
+ HttpAuthFilterWhitelist filter(server_whitelist_filter_string);
+ for (size_t i = 0; i < arraysize(urls); i++) {
+ EXPECT_EQ(urls[i].matches, filter.IsValid(urls[i].url, urls[i].target))
+ << " " << i << ": " << urls[i].url;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_filter_win.h b/chromium/net/http/http_auth_filter_win.h
new file mode 100644
index 00000000000..24305b511ec
--- /dev/null
+++ b/chromium/net/http/http_auth_filter_win.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2010 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_HTTP_HTTP_AUTH_FILTER_WIN_H_
+#define NET_HTTP_HTTP_AUTH_FILTER_WIN_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/strings/string16.h"
+
+namespace net {
+
+enum RegistryHiveType {
+ CURRENT_USER,
+ LOCAL_MACHINE
+};
+
+namespace http_auth {
+
+// The common path to all the registry keys containing domain zone information.
+extern const char16 kRegistryInternetSettings[];
+extern const char16 kSettingsMachineOnly[];
+extern const char16* kRegistryEntries[3]; // L"http", L"https", and L"*"
+
+extern const char16* GetRegistryWhitelistKey();
+// Override the whitelist key. Passing in NULL restores the default value.
+extern void SetRegistryWhitelistKey(const char16* new_whitelist_key);
+extern bool UseOnlyMachineSettings();
+
+} // namespace http_auth
+
+} // namespace net
+#endif // OS_WIN
+
+#endif // NET_HTTP_HTTP_AUTH_FILTER_WIN_H_
diff --git a/chromium/net/http/http_auth_gssapi_posix.cc b/chromium/net/http/http_auth_gssapi_posix.cc
new file mode 100644
index 00000000000..4d7bf074061
--- /dev/null
+++ b/chromium/net/http/http_auth_gssapi_posix.cc
@@ -0,0 +1,893 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_gssapi_posix.h"
+
+#include <limits>
+#include <string>
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+// These are defined for the GSSAPI library:
+// Paraphrasing the comments from gssapi.h:
+// "The implementation must reserve static storage for a
+// gss_OID_desc object for each constant. That constant
+// should be initialized to point to that gss_OID_desc."
+// These are encoded using ASN.1 BER encoding.
+namespace {
+
+static gss_OID_desc GSS_C_NT_USER_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01")
+};
+static gss_OID_desc GSS_C_NT_MACHINE_UID_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02")
+};
+static gss_OID_desc GSS_C_NT_STRING_UID_NAME_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03")
+};
+static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_X_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x06\x02")
+};
+static gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_VAL = {
+ 10,
+ const_cast<char*>("\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04")
+};
+static gss_OID_desc GSS_C_NT_ANONYMOUS_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\01\x05\x06\x03")
+};
+static gss_OID_desc GSS_C_NT_EXPORT_NAME_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x06\x04")
+};
+
+} // namespace
+
+// Heimdal >= 1.4 will define the following as preprocessor macros.
+// To avoid conflicting declarations, we have to undefine these.
+#undef GSS_C_NT_USER_NAME
+#undef GSS_C_NT_MACHINE_UID_NAME
+#undef GSS_C_NT_STRING_UID_NAME
+#undef GSS_C_NT_HOSTBASED_SERVICE_X
+#undef GSS_C_NT_HOSTBASED_SERVICE
+#undef GSS_C_NT_ANONYMOUS
+#undef GSS_C_NT_EXPORT_NAME
+
+gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_VAL;
+gss_OID GSS_C_NT_MACHINE_UID_NAME = &GSS_C_NT_MACHINE_UID_NAME_VAL;
+gss_OID GSS_C_NT_STRING_UID_NAME = &GSS_C_NT_STRING_UID_NAME_VAL;
+gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &GSS_C_NT_HOSTBASED_SERVICE_X_VAL;
+gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_VAL;
+gss_OID GSS_C_NT_ANONYMOUS = &GSS_C_NT_ANONYMOUS_VAL;
+gss_OID GSS_C_NT_EXPORT_NAME = &GSS_C_NT_EXPORT_NAME_VAL;
+
+namespace net {
+
+// Exported mechanism for GSSAPI. We always use SPNEGO:
+
+// iso.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
+gss_OID_desc CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL = {
+ 6,
+ const_cast<char*>("\x2b\x06\x01\x05\x05\x02")
+};
+
+gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC =
+ &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
+
+// Debugging helpers.
+namespace {
+
+std::string DisplayStatus(OM_uint32 major_status,
+ OM_uint32 minor_status) {
+ if (major_status == GSS_S_COMPLETE)
+ return "OK";
+ return base::StringPrintf("0x%08X 0x%08X", major_status, minor_status);
+}
+
+std::string DisplayCode(GSSAPILibrary* gssapi_lib,
+ OM_uint32 status,
+ OM_uint32 status_code_type) {
+ const int kMaxDisplayIterations = 8;
+ const size_t kMaxMsgLength = 4096;
+ // msg_ctx needs to be outside the loop because it is invoked multiple times.
+ OM_uint32 msg_ctx = 0;
+ std::string rv = base::StringPrintf("(0x%08X)", status);
+
+ // This loop should continue iterating until msg_ctx is 0 after the first
+ // iteration. To be cautious and prevent an infinite loop, it stops after
+ // a finite number of iterations as well. As an added sanity check, no
+ // individual message may exceed |kMaxMsgLength|, and the final result
+ // will not exceed |kMaxMsgLength|*2-1.
+ for (int i = 0; i < kMaxDisplayIterations && rv.size() < kMaxMsgLength;
+ ++i) {
+ OM_uint32 min_stat;
+ gss_buffer_desc_struct msg = GSS_C_EMPTY_BUFFER;
+ OM_uint32 maj_stat =
+ gssapi_lib->display_status(&min_stat, status, status_code_type,
+ GSS_C_NULL_OID, &msg_ctx, &msg);
+ if (maj_stat == GSS_S_COMPLETE) {
+ int msg_len = (msg.length > kMaxMsgLength) ?
+ static_cast<int>(kMaxMsgLength) :
+ static_cast<int>(msg.length);
+ if (msg_len > 0 && msg.value != NULL) {
+ rv += base::StringPrintf(" %.*s", msg_len,
+ static_cast<char*>(msg.value));
+ }
+ }
+ gssapi_lib->release_buffer(&min_stat, &msg);
+ if (!msg_ctx)
+ break;
+ }
+ return rv;
+}
+
+std::string DisplayExtendedStatus(GSSAPILibrary* gssapi_lib,
+ OM_uint32 major_status,
+ OM_uint32 minor_status) {
+ if (major_status == GSS_S_COMPLETE)
+ return "OK";
+ std::string major = DisplayCode(gssapi_lib, major_status, GSS_C_GSS_CODE);
+ std::string minor = DisplayCode(gssapi_lib, minor_status, GSS_C_MECH_CODE);
+ return base::StringPrintf("Major: %s | Minor: %s", major.c_str(),
+ minor.c_str());
+}
+
+// ScopedName releases a gss_name_t when it goes out of scope.
+class ScopedName {
+ public:
+ ScopedName(gss_name_t name,
+ GSSAPILibrary* gssapi_lib)
+ : name_(name),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+ }
+
+ ~ScopedName() {
+ if (name_ != GSS_C_NO_NAME) {
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status =
+ gssapi_lib_->release_name(&minor_status, &name_);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing name. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ name_ = GSS_C_NO_NAME;
+ }
+ }
+
+ private:
+ gss_name_t name_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedName);
+};
+
+// ScopedBuffer releases a gss_buffer_t when it goes out of scope.
+class ScopedBuffer {
+ public:
+ ScopedBuffer(gss_buffer_t buffer,
+ GSSAPILibrary* gssapi_lib)
+ : buffer_(buffer),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+ }
+
+ ~ScopedBuffer() {
+ if (buffer_ != GSS_C_NO_BUFFER) {
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status =
+ gssapi_lib_->release_buffer(&minor_status, buffer_);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing buffer. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ buffer_ = GSS_C_NO_BUFFER;
+ }
+ }
+
+ private:
+ gss_buffer_t buffer_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedBuffer);
+};
+
+namespace {
+
+std::string AppendIfPredefinedValue(gss_OID oid,
+ gss_OID predefined_oid,
+ const char* predefined_oid_name) {
+ DCHECK(oid);
+ DCHECK(predefined_oid);
+ DCHECK(predefined_oid_name);
+ std::string output;
+ if (oid->length != predefined_oid->length)
+ return output;
+ if (0 != memcmp(oid->elements,
+ predefined_oid->elements,
+ predefined_oid->length))
+ return output;
+
+ output += " (";
+ output += predefined_oid_name;
+ output += ")";
+ return output;
+}
+
+} // namespace
+
+std::string DescribeOid(GSSAPILibrary* gssapi_lib, const gss_OID oid) {
+ if (!oid)
+ return "<NULL>";
+ std::string output;
+ const size_t kMaxCharsToPrint = 1024;
+ OM_uint32 byte_length = oid->length;
+ size_t char_length = byte_length / sizeof(char);
+ if (char_length > kMaxCharsToPrint) {
+ // This might be a plain ASCII string.
+ // Check if the first |kMaxCharsToPrint| characters
+ // contain only printable characters and are NULL terminated.
+ const char* str = reinterpret_cast<const char*>(oid);
+ size_t str_length = 0;
+ for ( ; str_length < kMaxCharsToPrint; ++str_length) {
+ if (!str[str_length] || !isprint(str[str_length]))
+ break;
+ }
+ if (!str[str_length]) {
+ output += base::StringPrintf("\"%s\"", str);
+ return output;
+ }
+ }
+ output = base::StringPrintf("(%u) \"", byte_length);
+ if (!oid->elements) {
+ output += "<NULL>";
+ return output;
+ }
+ const unsigned char* elements =
+ reinterpret_cast<const unsigned char*>(oid->elements);
+ // Don't print more than |kMaxCharsToPrint| characters.
+ size_t i = 0;
+ for ( ; (i < byte_length) && (i < kMaxCharsToPrint); ++i) {
+ output += base::StringPrintf("\\x%02X", elements[i]);
+ }
+ if (i >= kMaxCharsToPrint)
+ output += "...";
+ output += "\"";
+
+ // Check if the OID is one of the predefined values.
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_USER_NAME,
+ "GSS_C_NT_USER_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_MACHINE_UID_NAME,
+ "GSS_C_NT_MACHINE_UID_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_STRING_UID_NAME,
+ "GSS_C_NT_STRING_UID_NAME");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_HOSTBASED_SERVICE_X,
+ "GSS_C_NT_HOSTBASED_SERVICE_X");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ "GSS_C_NT_HOSTBASED_SERVICE");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_ANONYMOUS,
+ "GSS_C_NT_ANONYMOUS");
+ output += AppendIfPredefinedValue(oid,
+ GSS_C_NT_EXPORT_NAME,
+ "GSS_C_NT_EXPORT_NAME");
+
+ return output;
+}
+
+std::string DescribeName(GSSAPILibrary* gssapi_lib, const gss_name_t name) {
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_buffer_desc_struct output_name_buffer = GSS_C_EMPTY_BUFFER;
+ gss_OID_desc output_name_type_desc = GSS_C_EMPTY_BUFFER;
+ gss_OID output_name_type = &output_name_type_desc;
+ major_status = gssapi_lib->display_name(&minor_status,
+ name,
+ &output_name_buffer,
+ &output_name_type);
+ ScopedBuffer scoped_output_name(&output_name_buffer, gssapi_lib);
+ if (major_status != GSS_S_COMPLETE) {
+ std::string error =
+ base::StringPrintf("Unable to describe name 0x%p, %s",
+ name,
+ DisplayExtendedStatus(gssapi_lib,
+ major_status,
+ minor_status).c_str());
+ return error;
+ }
+ int len = output_name_buffer.length;
+ std::string description = base::StringPrintf(
+ "%*s (Type %s)",
+ len,
+ reinterpret_cast<const char*>(output_name_buffer.value),
+ DescribeOid(gssapi_lib, output_name_type).c_str());
+ return description;
+}
+
+std::string DescribeContext(GSSAPILibrary* gssapi_lib,
+ const gss_ctx_id_t context_handle) {
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_name_t src_name = GSS_C_NO_NAME;
+ gss_name_t targ_name = GSS_C_NO_NAME;
+ OM_uint32 lifetime_rec = 0;
+ gss_OID mech_type = GSS_C_NO_OID;
+ OM_uint32 ctx_flags = 0;
+ int locally_initiated = 0;
+ int open = 0;
+ if (context_handle == GSS_C_NO_CONTEXT)
+ return std::string("Context: GSS_C_NO_CONTEXT");
+ major_status = gssapi_lib->inquire_context(&minor_status,
+ context_handle,
+ &src_name,
+ &targ_name,
+ &lifetime_rec,
+ &mech_type,
+ &ctx_flags,
+ &locally_initiated,
+ &open);
+ ScopedName(src_name, gssapi_lib);
+ ScopedName(targ_name, gssapi_lib);
+ if (major_status != GSS_S_COMPLETE) {
+ std::string error =
+ base::StringPrintf("Unable to describe context 0x%p, %s",
+ context_handle,
+ DisplayExtendedStatus(gssapi_lib,
+ major_status,
+ minor_status).c_str());
+ return error;
+ }
+ std::string source(DescribeName(gssapi_lib, src_name));
+ std::string target(DescribeName(gssapi_lib, targ_name));
+ std::string description = base::StringPrintf("Context 0x%p: "
+ "Source \"%s\", "
+ "Target \"%s\", "
+ "lifetime %d, "
+ "mechanism %s, "
+ "flags 0x%08X, "
+ "local %d, "
+ "open %d",
+ context_handle,
+ source.c_str(),
+ target.c_str(),
+ lifetime_rec,
+ DescribeOid(gssapi_lib,
+ mech_type).c_str(),
+ ctx_flags,
+ locally_initiated,
+ open);
+ return description;
+}
+
+} // namespace
+
+GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name)
+ : initialized_(false),
+ gssapi_library_name_(gssapi_library_name),
+ gssapi_library_(NULL),
+ import_name_(NULL),
+ release_name_(NULL),
+ release_buffer_(NULL),
+ display_name_(NULL),
+ display_status_(NULL),
+ init_sec_context_(NULL),
+ wrap_size_limit_(NULL),
+ delete_sec_context_(NULL),
+ inquire_context_(NULL) {
+}
+
+GSSAPISharedLibrary::~GSSAPISharedLibrary() {
+ if (gssapi_library_) {
+ base::UnloadNativeLibrary(gssapi_library_);
+ gssapi_library_ = NULL;
+ }
+}
+
+bool GSSAPISharedLibrary::Init() {
+ if (!initialized_)
+ InitImpl();
+ return initialized_;
+}
+
+bool GSSAPISharedLibrary::InitImpl() {
+ DCHECK(!initialized_);
+#if defined(DLOPEN_KERBEROS)
+ gssapi_library_ = LoadSharedLibrary();
+ if (gssapi_library_ == NULL)
+ return false;
+#endif // defined(DLOPEN_KERBEROS)
+ initialized_ = true;
+ return true;
+}
+
+base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() {
+ const char* const* library_names;
+ size_t num_lib_names;
+ const char* user_specified_library[1];
+ if (!gssapi_library_name_.empty()) {
+ user_specified_library[0] = gssapi_library_name_.c_str();
+ library_names = user_specified_library;
+ num_lib_names = 1;
+ } else {
+ static const char* const kDefaultLibraryNames[] = {
+#if defined(OS_MACOSX)
+ "libgssapi_krb5.dylib" // MIT Kerberos
+#elif defined(OS_OPENBSD)
+ "libgssapi.so" // Heimdal - OpenBSD
+#else
+ "libgssapi_krb5.so.2", // MIT Kerberos - FC, Suse10, Debian
+ "libgssapi.so.4", // Heimdal - Suse10, MDK
+ "libgssapi.so.2", // Heimdal - Gentoo
+ "libgssapi.so.1" // Heimdal - Suse9, CITI - FC, MDK, Suse10
+#endif
+ };
+ library_names = kDefaultLibraryNames;
+ num_lib_names = arraysize(kDefaultLibraryNames);
+ }
+
+ for (size_t i = 0; i < num_lib_names; ++i) {
+ const char* library_name = library_names[i];
+ base::FilePath file_path(library_name);
+
+ // TODO(asanka): Move library loading to a separate thread.
+ // http://crbug.com/66702
+ base::ThreadRestrictions::ScopedAllowIO allow_io_temporarily;
+ base::NativeLibrary lib = base::LoadNativeLibrary(file_path, NULL);
+ if (lib) {
+ // Only return this library if we can bind the functions we need.
+ if (BindMethods(lib))
+ return lib;
+ base::UnloadNativeLibrary(lib);
+ }
+ }
+ LOG(WARNING) << "Unable to find a compatible GSSAPI library";
+ return NULL;
+}
+
+#if defined(DLOPEN_KERBEROS)
+#define BIND(lib, x) \
+ DCHECK(lib); \
+ gss_##x##_type x = reinterpret_cast<gss_##x##_type>( \
+ base::GetFunctionPointerFromNativeLibrary(lib, "gss_" #x)); \
+ if (x == NULL) { \
+ LOG(WARNING) << "Unable to bind function \"" << "gss_" #x << "\""; \
+ return false; \
+ }
+#else
+#define BIND(lib, x) gss_##x##_type x = gss_##x
+#endif
+
+bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) {
+ BIND(lib, import_name);
+ BIND(lib, release_name);
+ BIND(lib, release_buffer);
+ BIND(lib, display_name);
+ BIND(lib, display_status);
+ BIND(lib, init_sec_context);
+ BIND(lib, wrap_size_limit);
+ BIND(lib, delete_sec_context);
+ BIND(lib, inquire_context);
+
+ import_name_ = import_name;
+ release_name_ = release_name;
+ release_buffer_ = release_buffer;
+ display_name_ = display_name;
+ display_status_ = display_status;
+ init_sec_context_ = init_sec_context;
+ wrap_size_limit_ = wrap_size_limit;
+ delete_sec_context_ = delete_sec_context;
+ inquire_context_ = inquire_context;
+
+ return true;
+}
+
+#undef BIND
+
+OM_uint32 GSSAPISharedLibrary::import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) {
+ DCHECK(initialized_);
+ return import_name_(minor_status, input_name_buffer, input_name_type,
+ output_name);
+}
+
+OM_uint32 GSSAPISharedLibrary::release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) {
+ DCHECK(initialized_);
+ return release_name_(minor_status, input_name);
+}
+
+OM_uint32 GSSAPISharedLibrary::release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) {
+ DCHECK(initialized_);
+ return release_buffer_(minor_status, buffer);
+}
+
+OM_uint32 GSSAPISharedLibrary::display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) {
+ DCHECK(initialized_);
+ return display_name_(minor_status,
+ input_name,
+ output_name_buffer,
+ output_name_type);
+}
+
+OM_uint32 GSSAPISharedLibrary::display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_context,
+ gss_buffer_t status_string) {
+ DCHECK(initialized_);
+ return display_status_(minor_status, status_value, status_type, mech_type,
+ message_context, status_string);
+}
+
+OM_uint32 GSSAPISharedLibrary::init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) {
+ DCHECK(initialized_);
+ return init_sec_context_(minor_status,
+ initiator_cred_handle,
+ context_handle,
+ target_name,
+ mech_type,
+ req_flags,
+ time_req,
+ input_chan_bindings,
+ input_token,
+ actual_mech_type,
+ output_token,
+ ret_flags,
+ time_rec);
+}
+
+OM_uint32 GSSAPISharedLibrary::wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) {
+ DCHECK(initialized_);
+ return wrap_size_limit_(minor_status,
+ context_handle,
+ conf_req_flag,
+ qop_req,
+ req_output_size,
+ max_input_size);
+}
+
+OM_uint32 GSSAPISharedLibrary::delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) {
+ // This is called from the owner class' destructor, even if
+ // Init() is not called, so we can't assume |initialized_|
+ // is set.
+ if (!initialized_)
+ return 0;
+ return delete_sec_context_(minor_status,
+ context_handle,
+ output_token);
+}
+
+OM_uint32 GSSAPISharedLibrary::inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) {
+ DCHECK(initialized_);
+ return inquire_context_(minor_status,
+ context_handle,
+ src_name,
+ targ_name,
+ lifetime_rec,
+ mech_type,
+ ctx_flags,
+ locally_initiated,
+ open);
+}
+
+ScopedSecurityContext::ScopedSecurityContext(GSSAPILibrary* gssapi_lib)
+ : security_context_(GSS_C_NO_CONTEXT),
+ gssapi_lib_(gssapi_lib) {
+ DCHECK(gssapi_lib_);
+}
+
+ScopedSecurityContext::~ScopedSecurityContext() {
+ if (security_context_ != GSS_C_NO_CONTEXT) {
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ OM_uint32 minor_status = 0;
+ OM_uint32 major_status = gssapi_lib_->delete_sec_context(
+ &minor_status, &security_context_, &output_token);
+ if (major_status != GSS_S_COMPLETE) {
+ LOG(WARNING) << "Problem releasing security_context. "
+ << DisplayStatus(major_status, minor_status);
+ }
+ security_context_ = GSS_C_NO_CONTEXT;
+ }
+}
+
+HttpAuthGSSAPI::HttpAuthGSSAPI(GSSAPILibrary* library,
+ const std::string& scheme,
+ gss_OID gss_oid)
+ : scheme_(scheme),
+ gss_oid_(gss_oid),
+ library_(library),
+ scoped_sec_context_(library),
+ can_delegate_(false) {
+ DCHECK(library_);
+}
+
+HttpAuthGSSAPI::~HttpAuthGSSAPI() {
+}
+
+bool HttpAuthGSSAPI::Init() {
+ if (!library_)
+ return false;
+ return library_->Init();
+}
+
+bool HttpAuthGSSAPI::NeedsIdentity() const {
+ return decoded_server_auth_token_.empty();
+}
+
+bool HttpAuthGSSAPI::AllowsExplicitCredentials() const {
+ return false;
+}
+
+void HttpAuthGSSAPI::Delegate() {
+ can_delegate_ = true;
+}
+
+HttpAuth::AuthorizationResult HttpAuthGSSAPI::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok) {
+ // Verify the challenge's auth-scheme.
+ if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ std::string encoded_auth_token = tok->base64_param();
+
+ if (encoded_auth_token.empty()) {
+ // If a context has already been established, an empty Negotiate challenge
+ // should be treated as a rejection of the current attempt.
+ if (scoped_sec_context_.get() != GSS_C_NO_CONTEXT)
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ DCHECK(decoded_server_auth_token_.empty());
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ // If a context has not already been established, additional tokens should
+ // not be present in the auth challenge.
+ if (scoped_sec_context_.get() == GSS_C_NO_CONTEXT)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ }
+
+ // Make sure the additional token is base64 encoded.
+ std::string decoded_auth_token;
+ bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
+ if (!base64_rv)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ decoded_server_auth_token_ = decoded_auth_token;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+int HttpAuthGSSAPI::GenerateAuthToken(const AuthCredentials* credentials,
+ const std::wstring& spn,
+ std::string* auth_token) {
+ DCHECK(auth_token);
+
+ gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+ input_token.length = decoded_server_auth_token_.length();
+ input_token.value = (input_token.length > 0) ?
+ const_cast<char*>(decoded_server_auth_token_.data()) :
+ NULL;
+ gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+ ScopedBuffer scoped_output_token(&output_token, library_);
+ int rv = GetNextSecurityToken(spn, &input_token, &output_token);
+ if (rv != OK)
+ return rv;
+
+ // Base64 encode data in output buffer and prepend the scheme.
+ std::string encode_input(static_cast<char*>(output_token.value),
+ output_token.length);
+ std::string encode_output;
+ bool base64_rv = base::Base64Encode(encode_input, &encode_output);
+ if (!base64_rv) {
+ LOG(ERROR) << "Base64 encoding of auth token failed.";
+ return ERR_ENCODING_CONVERSION_FAILED;
+ }
+ *auth_token = scheme_ + " " + encode_output;
+ return OK;
+}
+
+
+namespace {
+
+// GSSAPI status codes consist of a calling error (essentially, a programmer
+// bug), a routine error (defined by the RFC), and supplementary information,
+// all bitwise-or'ed together in different regions of the 32 bit return value.
+// This means a simple switch on the return codes is not sufficient.
+
+int MapImportNameStatusToError(OM_uint32 major_status) {
+ VLOG(1) << "import_name returned 0x" << std::hex << major_status;
+ if (major_status == GSS_S_COMPLETE)
+ return OK;
+ if (GSS_CALLING_ERROR(major_status) != 0)
+ return ERR_UNEXPECTED;
+ OM_uint32 routine_error = GSS_ROUTINE_ERROR(major_status);
+ switch (routine_error) {
+ case GSS_S_FAILURE:
+ // Looking at the MIT Kerberos implementation, this typically is returned
+ // when memory allocation fails. However, the API does not guarantee
+ // that this is the case, so using ERR_UNEXPECTED rather than
+ // ERR_OUT_OF_MEMORY.
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_BAD_NAME:
+ case GSS_S_BAD_NAMETYPE:
+ return ERR_MALFORMED_IDENTITY;
+ case GSS_S_DEFECTIVE_TOKEN:
+ // Not mentioned in the API, but part of code.
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_BAD_MECH:
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ default:
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ }
+}
+
+int MapInitSecContextStatusToError(OM_uint32 major_status) {
+ VLOG(1) << "init_sec_context returned 0x" << std::hex << major_status;
+ // Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like
+ // other code just checks if major_status is equivalent to it to indicate
+ // that there are no other errors included.
+ if (major_status == GSS_S_COMPLETE || major_status == GSS_S_CONTINUE_NEEDED)
+ return OK;
+ if (GSS_CALLING_ERROR(major_status) != 0)
+ return ERR_UNEXPECTED;
+ OM_uint32 routine_status = GSS_ROUTINE_ERROR(major_status);
+ switch (routine_status) {
+ case GSS_S_DEFECTIVE_TOKEN:
+ return ERR_INVALID_RESPONSE;
+ case GSS_S_DEFECTIVE_CREDENTIAL:
+ // Not expected since this implementation uses the default credential.
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_BAD_SIG:
+ // Probably won't happen, but it's a bad response.
+ return ERR_INVALID_RESPONSE;
+ case GSS_S_NO_CRED:
+ return ERR_INVALID_AUTH_CREDENTIALS;
+ case GSS_S_CREDENTIALS_EXPIRED:
+ return ERR_INVALID_AUTH_CREDENTIALS;
+ case GSS_S_BAD_BINDINGS:
+ // This only happens with mutual authentication.
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_NO_CONTEXT:
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_BAD_NAMETYPE:
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ case GSS_S_BAD_NAME:
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ case GSS_S_BAD_MECH:
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case GSS_S_FAILURE:
+ // This should be an "Unexpected Security Status" according to the
+ // GSSAPI documentation, but it's typically used to indicate that
+ // credentials are not correctly set up on a user machine, such
+ // as a missing credential cache or hitting this after calling
+ // kdestroy.
+ // TODO(cbentzel): Use minor code for even better mapping?
+ return ERR_MISSING_AUTH_CREDENTIALS;
+ default:
+ if (routine_status != 0)
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ break;
+ }
+ OM_uint32 supplemental_status = GSS_SUPPLEMENTARY_INFO(major_status);
+ // Replays could indicate an attack.
+ if (supplemental_status & (GSS_S_DUPLICATE_TOKEN | GSS_S_OLD_TOKEN |
+ GSS_S_UNSEQ_TOKEN | GSS_S_GAP_TOKEN))
+ return ERR_INVALID_RESPONSE;
+
+ // At this point, every documented status has been checked.
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+}
+
+}
+
+int HttpAuthGSSAPI::GetNextSecurityToken(const std::wstring& spn,
+ gss_buffer_t in_token,
+ gss_buffer_t out_token) {
+ // Create a name for the principal
+ // TODO(cbentzel): Just do this on the first pass?
+ std::string spn_principal = WideToASCII(spn);
+ gss_buffer_desc spn_buffer = GSS_C_EMPTY_BUFFER;
+ spn_buffer.value = const_cast<char*>(spn_principal.c_str());
+ spn_buffer.length = spn_principal.size() + 1;
+ OM_uint32 minor_status = 0;
+ gss_name_t principal_name = GSS_C_NO_NAME;
+ OM_uint32 major_status = library_->import_name(
+ &minor_status,
+ &spn_buffer,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ &principal_name);
+ int rv = MapImportNameStatusToError(major_status);
+ if (rv != OK) {
+ LOG(ERROR) << "Problem importing name from "
+ << "spn \"" << spn_principal << "\"\n"
+ << DisplayExtendedStatus(library_, major_status, minor_status);
+ return rv;
+ }
+ ScopedName scoped_name(principal_name, library_);
+
+ // Continue creating a security context.
+ OM_uint32 req_flags = 0;
+ if (can_delegate_)
+ req_flags |= GSS_C_DELEG_FLAG;
+ major_status = library_->init_sec_context(
+ &minor_status,
+ GSS_C_NO_CREDENTIAL,
+ scoped_sec_context_.receive(),
+ principal_name,
+ gss_oid_,
+ req_flags,
+ GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ in_token,
+ NULL, // actual_mech_type
+ out_token,
+ NULL, // ret flags
+ NULL);
+ rv = MapInitSecContextStatusToError(major_status);
+ if (rv != OK) {
+ LOG(ERROR) << "Problem initializing context. \n"
+ << DisplayExtendedStatus(library_, major_status, minor_status)
+ << '\n'
+ << DescribeContext(library_, scoped_sec_context_.get());
+ }
+ return rv;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_gssapi_posix.h b/chromium/net/http/http_auth_gssapi_posix.h
new file mode 100644
index 00000000000..afa50a02c9f
--- /dev/null
+++ b/chromium/net/http/http_auth_gssapi_posix.h
@@ -0,0 +1,277 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
+#define NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/native_library.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+
+#if defined(OS_MACOSX) && defined(MAC_OS_X_VERSION_10_9) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
+// Including gssapi.h directly is deprecated in the 10.9 SDK.
+#include <GSS/gssapi.h>
+#else
+#include <gssapi.h>
+#endif
+
+namespace net {
+
+// Mechanism OID for GSSAPI. We always use SPNEGO.
+NET_EXPORT_PRIVATE extern gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC;
+
+// GSSAPILibrary is introduced so unit tests can mock the calls to the GSSAPI
+// library. The default implementation attempts to load one of the standard
+// GSSAPI library implementations, then simply passes the arguments on to
+// that implementation.
+class NET_EXPORT_PRIVATE GSSAPILibrary {
+ public:
+ virtual ~GSSAPILibrary() {}
+
+ // Initializes the library, including any necessary dynamic libraries.
+ // This is done separately from construction (which happens at startup time)
+ // in order to delay work until the class is actually needed.
+ virtual bool Init() = 0;
+
+ // These methods match the ones in the GSSAPI library.
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) = 0;
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) = 0;
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) = 0;
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) = 0;
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string) = 0;
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) = 0;
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) = 0;
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) = 0;
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) = 0;
+
+};
+
+// GSSAPISharedLibrary class is defined here so that unit tests can access it.
+class NET_EXPORT_PRIVATE GSSAPISharedLibrary : public GSSAPILibrary {
+ public:
+ // If |gssapi_library_name| is empty, hard-coded default library names are
+ // used.
+ explicit GSSAPISharedLibrary(const std::string& gssapi_library_name);
+ virtual ~GSSAPISharedLibrary();
+
+ // GSSAPILibrary methods:
+ virtual bool Init() OVERRIDE;
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) OVERRIDE;
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) OVERRIDE;
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) OVERRIDE;
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) OVERRIDE;
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string) OVERRIDE;
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) OVERRIDE;
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) OVERRIDE;
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) OVERRIDE;
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) OVERRIDE;
+
+ private:
+ typedef typeof(&gss_import_name) gss_import_name_type;
+ typedef typeof(&gss_release_name) gss_release_name_type;
+ typedef typeof(&gss_release_buffer) gss_release_buffer_type;
+ typedef typeof(&gss_display_name) gss_display_name_type;
+ typedef typeof(&gss_display_status) gss_display_status_type;
+ typedef typeof(&gss_init_sec_context) gss_init_sec_context_type;
+ typedef typeof(&gss_wrap_size_limit) gss_wrap_size_limit_type;
+ typedef typeof(&gss_delete_sec_context) gss_delete_sec_context_type;
+ typedef typeof(&gss_inquire_context) gss_inquire_context_type;
+
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup);
+
+ bool InitImpl();
+ // Finds a usable dynamic library for GSSAPI and loads it. The criteria are:
+ // 1. The library must exist.
+ // 2. The library must export the functions we need.
+ base::NativeLibrary LoadSharedLibrary();
+ bool BindMethods(base::NativeLibrary lib);
+
+ bool initialized_;
+
+ std::string gssapi_library_name_;
+ // Need some way to invalidate the library.
+ base::NativeLibrary gssapi_library_;
+
+ // Function pointers
+ gss_import_name_type import_name_;
+ gss_release_name_type release_name_;
+ gss_release_buffer_type release_buffer_;
+ gss_display_name_type display_name_;
+ gss_display_status_type display_status_;
+ gss_init_sec_context_type init_sec_context_;
+ gss_wrap_size_limit_type wrap_size_limit_;
+ gss_delete_sec_context_type delete_sec_context_;
+ gss_inquire_context_type inquire_context_;
+};
+
+// ScopedSecurityContext releases a gss_ctx_id_t when it goes out of
+// scope.
+class ScopedSecurityContext {
+ public:
+ explicit ScopedSecurityContext(GSSAPILibrary* gssapi_lib);
+ ~ScopedSecurityContext();
+
+ gss_ctx_id_t get() const { return security_context_; }
+ gss_ctx_id_t* receive() { return &security_context_; }
+
+ private:
+ gss_ctx_id_t security_context_;
+ GSSAPILibrary* gssapi_lib_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSecurityContext);
+};
+
+
+// TODO(ahendrickson): Share code with HttpAuthSSPI.
+class NET_EXPORT_PRIVATE HttpAuthGSSAPI {
+ public:
+ HttpAuthGSSAPI(GSSAPILibrary* library,
+ const std::string& scheme,
+ const gss_OID gss_oid);
+ ~HttpAuthGSSAPI();
+
+ bool Init();
+
+ bool NeedsIdentity() const;
+
+ bool AllowsExplicitCredentials() const;
+
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok);
+
+ // Generates an authentication token.
+ // The return value is an error code. If it's not |OK|, the value of
+ // |*auth_token| is unspecified.
+ // |spn| is the Service Principal Name of the server that the token is
+ // being generated for.
+ // If this is the first round of a multiple round scheme, credentials are
+ // obtained using |*credentials|. If |credentials| is NULL, the default
+ // credentials are used instead.
+ int GenerateAuthToken(const AuthCredentials* credentials,
+ const std::wstring& spn,
+ std::string* auth_token);
+
+ // Delegation is allowed on the Kerberos ticket. This allows certain servers
+ // to act as the user, such as an IIS server retrieiving data from a
+ // Kerberized MSSQL server.
+ void Delegate();
+
+ private:
+ int GetNextSecurityToken(const std::wstring& spn,
+ gss_buffer_t in_token,
+ gss_buffer_t out_token);
+
+ std::string scheme_;
+ gss_OID gss_oid_;
+ GSSAPILibrary* library_;
+ std::string decoded_server_auth_token_;
+ ScopedSecurityContext scoped_sec_context_;
+ bool can_delegate_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
diff --git a/chromium/net/http/http_auth_gssapi_posix_unittest.cc b/chromium/net/http/http_auth_gssapi_posix_unittest.cc
new file mode 100644
index 00000000000..1c3d12b6c29
--- /dev/null
+++ b/chromium/net/http/http_auth_gssapi_posix_unittest.cc
@@ -0,0 +1,274 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_gssapi_posix.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/native_library.h"
+#include "net/base/net_errors.h"
+#include "net/http/mock_gssapi_library_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// gss_buffer_t helpers.
+void ClearBuffer(gss_buffer_t dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ delete [] reinterpret_cast<char*>(dest->value);
+ dest->value = NULL;
+}
+
+void SetBuffer(gss_buffer_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length) {
+ dest->value = new char[length];
+ memcpy(dest->value, src, length);
+ }
+}
+
+void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ SetBuffer(dest, src->value, src->length);
+}
+
+const char kInitialAuthResponse[] = "Mary had a little lamb";
+
+void EstablishInitialContext(test::MockGSSAPILibrary* library) {
+ test::GssContextMockImpl context_info(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ gss_buffer_desc in_buffer = {0, NULL};
+ gss_buffer_desc out_buffer = {arraysize(kInitialAuthResponse),
+ const_cast<char*>(kInitialAuthResponse)};
+ library->ExpectSecurityContext(
+ "Negotiate",
+ GSS_S_CONTINUE_NEEDED,
+ 0,
+ context_info,
+ in_buffer,
+ out_buffer);
+}
+
+} // namespace
+
+TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
+ // TODO(ahendrickson): Manipulate the libraries and paths to test each of the
+ // libraries we expect, and also whether or not they have the interface
+ // functions we want.
+ scoped_ptr<GSSAPILibrary> gssapi(new GSSAPISharedLibrary(std::string()));
+ DCHECK(gssapi.get());
+ EXPECT_TRUE(gssapi.get()->Init());
+}
+
+#if defined(DLOPEN_KERBEROS)
+TEST(HttpAuthGSSAPIPOSIXTest, GSSAPILoadCustomLibrary) {
+ scoped_ptr<GSSAPILibrary> gssapi(
+ new GSSAPISharedLibrary("/this/library/does/not/exist"));
+ EXPECT_FALSE(gssapi.get()->Init());
+}
+#endif // defined(DLOPEN_KERBEROS)
+
+TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) {
+ scoped_ptr<test::MockGSSAPILibrary> mock_library(new test::MockGSSAPILibrary);
+ DCHECK(mock_library.get());
+ mock_library->Init();
+ const char kAuthResponse[] = "Mary had a little lamb";
+ test::GssContextMockImpl context1(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::GssContextMockImpl context2(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 1); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
+ test::MockGSSAPILibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_CONTINUE_NEEDED, // Major response code
+ 0, // Minor response code
+ context1, // Context
+ NULL, // Expected input token
+ kAuthResponse), // Output token
+ test::MockGSSAPILibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_COMPLETE, // Major response code
+ 0, // Minor response code
+ context2, // Context
+ kAuthResponse, // Expected input token
+ kAuthResponse) // Output token
+ };
+
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ mock_library->ExpectSecurityContext(queries[i].expected_package,
+ queries[i].response_code,
+ queries[i].minor_response_code,
+ queries[i].context_info,
+ queries[i].expected_input_token,
+ queries[i].output_token);
+ }
+
+ OM_uint32 major_status = 0;
+ OM_uint32 minor_status = 0;
+ gss_cred_id_t initiator_cred_handle = NULL;
+ gss_ctx_id_t context_handle = NULL;
+ gss_name_t target_name = NULL;
+ gss_OID mech_type = NULL;
+ OM_uint32 req_flags = 0;
+ OM_uint32 time_req = 25;
+ gss_channel_bindings_t input_chan_bindings = NULL;
+ gss_buffer_desc input_token = { 0, NULL };
+ gss_OID actual_mech_type= NULL;
+ gss_buffer_desc output_token = { 0, NULL };
+ OM_uint32 ret_flags = 0;
+ OM_uint32 time_rec = 0;
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ major_status = mock_library->init_sec_context(&minor_status,
+ initiator_cred_handle,
+ &context_handle,
+ target_name,
+ mech_type,
+ req_flags,
+ time_req,
+ input_chan_bindings,
+ &input_token,
+ &actual_mech_type,
+ &output_token,
+ &ret_flags,
+ &time_rec);
+ EXPECT_EQ(queries[i].response_code, major_status);
+ CopyBuffer(&input_token, &output_token);
+ ClearBuffer(&output_token);
+ }
+ ClearBuffer(&input_token);
+ major_status = mock_library->delete_sec_context(&minor_status,
+ &context_handle,
+ GSS_C_NO_BUFFER);
+ EXPECT_EQ(static_cast<OM_uint32>(GSS_S_COMPLETE), major_status);
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_FirstRound) {
+ // The first round should just consist of an unadorned "Negotiate" header.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_SPNEGO_MECH_OID_DESC);
+ std::string challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_TwoRounds) {
+ // The first round should just have "Negotiate", and the second round should
+ // have a valid base64 token associated with it.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_SPNEGO_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ // Generate an auth token and create another thing.
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+
+ std::string second_challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_UnexpectedTokenFirstRound) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_SPNEGO_MECH_OID_DESC);
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_gssapi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_MissingTokenSecondRound) {
+ // If a later-round challenge is simply "Negotiate", it should be treated as
+ // an authentication challenge rejection from the server or proxy.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_SPNEGO_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthGSSAPITest, ParseChallenge_NonBase64EncodedToken) {
+ // If a later-round challenge has an invalid base64 encoded token, it should
+ // be treated as an invalid challenge.
+ test::MockGSSAPILibrary mock_library;
+ HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate",
+ CHROME_GSS_SPNEGO_MECH_OID_DESC);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_gssapi.ParseChallenge(&first_challenge));
+
+ EstablishInitialContext(&mock_library);
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate =happyjoy=";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_gssapi.ParseChallenge(&second_challenge));
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler.cc b/chromium/net/http/http_auth_handler.cc
new file mode 100644
index 00000000000..ab809faefae
--- /dev/null
+++ b/chromium/net/http/http_auth_handler.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_handler.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+HttpAuthHandler::HttpAuthHandler()
+ : auth_scheme_(HttpAuth::AUTH_SCHEME_MAX),
+ score_(-1),
+ target_(HttpAuth::AUTH_NONE),
+ properties_(-1) {
+}
+
+HttpAuthHandler::~HttpAuthHandler() {
+}
+
+bool HttpAuthHandler::InitFromChallenge(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log) {
+ origin_ = origin;
+ target_ = target;
+ score_ = -1;
+ properties_ = -1;
+ net_log_ = net_log;
+
+ auth_challenge_ = challenge->challenge_text();
+ bool ok = Init(challenge);
+
+ // Init() is expected to set the scheme, realm, score, and properties. The
+ // realm may be empty.
+ DCHECK(!ok || score_ != -1);
+ DCHECK(!ok || properties_ != -1);
+ DCHECK(!ok || auth_scheme_ != HttpAuth::AUTH_SCHEME_MAX);
+
+ return ok;
+}
+
+namespace {
+
+NetLog::EventType EventTypeFromAuthTarget(HttpAuth::Target target) {
+ switch (target) {
+ case HttpAuth::AUTH_PROXY:
+ return NetLog::TYPE_AUTH_PROXY;
+ case HttpAuth::AUTH_SERVER:
+ return NetLog::TYPE_AUTH_SERVER;
+ default:
+ NOTREACHED();
+ return NetLog::TYPE_CANCELLED;
+ }
+}
+
+} // namespace
+
+int HttpAuthHandler::GenerateAuthToken(
+ const AuthCredentials* credentials, const HttpRequestInfo* request,
+ const CompletionCallback& callback, std::string* auth_token) {
+ // TODO(cbentzel): Enforce non-NULL callback after cleaning up SocketStream.
+ DCHECK(request);
+ DCHECK(credentials != NULL || AllowsDefaultCredentials());
+ DCHECK(auth_token != NULL);
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ net_log_.BeginEvent(EventTypeFromAuthTarget(target_));
+ int rv = GenerateAuthTokenImpl(
+ credentials, request,
+ base::Bind(&HttpAuthHandler::OnGenerateAuthTokenComplete,
+ base::Unretained(this)),
+ auth_token);
+ if (rv != ERR_IO_PENDING)
+ FinishGenerateAuthToken();
+ return rv;
+}
+
+bool HttpAuthHandler::NeedsIdentity() {
+ return true;
+}
+
+bool HttpAuthHandler::AllowsDefaultCredentials() {
+ return false;
+}
+
+bool HttpAuthHandler::AllowsExplicitCredentials() {
+ return true;
+}
+
+void HttpAuthHandler::OnGenerateAuthTokenComplete(int rv) {
+ CompletionCallback callback = callback_;
+ FinishGenerateAuthToken();
+ if (!callback.is_null())
+ callback.Run(rv);
+}
+
+void HttpAuthHandler::FinishGenerateAuthToken() {
+ // TOOD(cbentzel): Should this be done in OK case only?
+ net_log_.EndEvent(EventTypeFromAuthTarget(target_));
+ callback_.Reset();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler.h b/chromium/net/http/http_auth_handler.h
new file mode 100644
index 00000000000..638bb44aed3
--- /dev/null
+++ b/chromium/net/http/http_auth_handler.h
@@ -0,0 +1,198 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_H_
+
+#include <string>
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+struct HttpRequestInfo;
+
+// HttpAuthHandler is the interface for the authentication schemes
+// (basic, digest, NTLM, Negotiate).
+// HttpAuthHandler objects are typically created by an HttpAuthHandlerFactory.
+class NET_EXPORT_PRIVATE HttpAuthHandler {
+ public:
+ HttpAuthHandler();
+ virtual ~HttpAuthHandler();
+
+ // Initializes the handler using a challenge issued by a server.
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occurring after the
+ // authentication scheme. |target| and |origin| are both stored
+ // for later use, and are not part of the initial challenge.
+ bool InitFromChallenge(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log);
+
+ // Determines how the previous authorization attempt was received.
+ //
+ // This is called when the server/proxy responds with a 401/407 after an
+ // earlier authorization attempt. Although this normally means that the
+ // previous attempt was rejected, in multi-round schemes such as
+ // NTLM+Negotiate it may indicate that another round of challenge+response
+ // is required. For Digest authentication it may also mean that the previous
+ // attempt used a stale nonce (and nonce-count) and that a new attempt should
+ // be made with a different nonce provided in the challenge.
+ //
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occurring after the
+ // authentication scheme.
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) = 0;
+
+ // Generates an authentication token, potentially asynchronously.
+ //
+ // When |credentials| is NULL, the default credentials for the currently
+ // logged in user are used. |AllowsDefaultCredentials()| MUST be true in this
+ // case.
+ //
+ // |request|, |callback|, and |auth_token| must be non-NULL.
+ //
+ // The return value is a net error code.
+ //
+ // If |OK| is returned, |*auth_token| is filled in with an authentication
+ // token which can be inserted in the HTTP request.
+ //
+ // If |ERR_IO_PENDING| is returned, |*auth_token| will be filled in
+ // asynchronously and |callback| will be invoked. The lifetime of
+ // |request|, |callback|, and |auth_token| must last until |callback| is
+ // invoked, but |credentials| is only used during the initial call.
+ //
+ // All other return codes indicate that there was a problem generating a
+ // token, and the value of |*auth_token| is unspecified.
+ int GenerateAuthToken(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token);
+
+ // The authentication scheme as an enumerated value.
+ HttpAuth::Scheme auth_scheme() const {
+ return auth_scheme_;
+ }
+
+ // The realm, encoded as UTF-8. This may be empty.
+ const std::string& realm() const {
+ return realm_;
+ }
+
+ // The challenge which was issued when creating the handler.
+ const std::string challenge() const {
+ return auth_challenge_;
+ }
+
+ // Numeric rank based on the challenge's security level. Higher
+ // numbers are better. Used by HttpAuth::ChooseBestChallenge().
+ int score() const {
+ return score_;
+ }
+
+ HttpAuth::Target target() const {
+ return target_;
+ }
+
+ // Returns the proxy or server which issued the authentication challenge
+ // that this HttpAuthHandler is handling. The URL includes scheme, host, and
+ // port, but does not include path.
+ const GURL& origin() const {
+ return origin_;
+ }
+
+ // Returns true if the authentication scheme does not send the username and
+ // password in the clear.
+ bool encrypts_identity() const {
+ return (properties_ & ENCRYPTS_IDENTITY) != 0;
+ }
+
+ // Returns true if the authentication scheme is connection-based, for
+ // example, NTLM. A connection-based authentication scheme does not support
+ // preemptive authentication, and must use the same handler object
+ // throughout the life of an HTTP transaction.
+ bool is_connection_based() const {
+ return (properties_ & IS_CONNECTION_BASED) != 0;
+ }
+
+ // Returns true if the response to the current authentication challenge
+ // requires an identity.
+ // TODO(wtc): Find a better way to handle a multi-round challenge-response
+ // sequence used by a connection-based authentication scheme.
+ virtual bool NeedsIdentity();
+
+ // Returns whether the default credentials may be used for the |origin| passed
+ // into |InitFromChallenge|. If true, the user does not need to be prompted
+ // for username and password to establish credentials.
+ // NOTE: SSO is a potential security risk.
+ // TODO(cbentzel): Add a pointer to Firefox documentation about risk.
+ virtual bool AllowsDefaultCredentials();
+
+ // Returns whether explicit credentials can be used with this handler. If
+ // true the user may be prompted for credentials if an implicit identity
+ // cannot be determined.
+ virtual bool AllowsExplicitCredentials();
+
+ protected:
+ enum Property {
+ ENCRYPTS_IDENTITY = 1 << 0,
+ IS_CONNECTION_BASED = 1 << 1,
+ };
+
+ // Initializes the handler using a challenge issued by a server.
+ // |challenge| must be non-NULL and have already tokenized the
+ // authentication scheme, but none of the tokens occurring after the
+ // authentication scheme.
+ // Implementations are expected to initialize the following members:
+ // scheme_, realm_, score_, properties_
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) = 0;
+
+ // |GenerateAuthTokenImpl()} is the auth-scheme specific implementation
+ // of generating the next auth token. Callers should use |GenerateAuthToken()|
+ // which will in turn call |GenerateAuthTokenImpl()|
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) = 0;
+
+ // The auth-scheme as an enumerated value.
+ HttpAuth::Scheme auth_scheme_;
+
+ // The realm, encoded as UTF-8. Used by "basic" and "digest".
+ std::string realm_;
+
+ // The auth challenge.
+ std::string auth_challenge_;
+
+ // The {scheme, host, port} for the authentication target. Used by "ntlm"
+ // and "negotiate" to construct the service principal name.
+ GURL origin_;
+
+ // The score for this challenge. Higher numbers are better.
+ int score_;
+
+ // Whether this authentication request is for a proxy server, or an
+ // origin server.
+ HttpAuth::Target target_;
+
+ // A bitmask of the properties of the authentication scheme.
+ int properties_;
+
+ BoundNetLog net_log_;
+
+ private:
+ void OnGenerateAuthTokenComplete(int rv);
+ void FinishGenerateAuthToken();
+
+ CompletionCallback callback_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_H_
diff --git a/chromium/net/http/http_auth_handler_basic.cc b/chromium/net/http/http_auth_handler_basic.cc
new file mode 100644
index 00000000000..af5f188b156
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_basic.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 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/http/http_auth_handler_basic.h"
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+namespace {
+
+// Parses a realm from an auth challenge, and converts to UTF8-encoding.
+// Returns whether the realm is invalid or the parameters are invalid.
+//
+// Note that if a realm was not specified, we will default it to "";
+// so specifying 'Basic realm=""' is equivalent to 'Basic'.
+//
+// This is more generous than RFC 2617, which is pretty clear in the
+// production of challenge that realm is required.
+//
+// We allow it to be compatibility with certain embedded webservers that don't
+// include a realm (see http://crbug.com/20984.)
+//
+// The over-the-wire realm is encoded as ISO-8859-1 (aka Latin-1).
+//
+// TODO(cbentzel): Realm may need to be decoded using RFC 2047 rules as
+// well, see http://crbug.com/25790.
+bool ParseRealm(const HttpAuth::ChallengeTokenizer& tokenizer,
+ std::string* realm) {
+ CHECK(realm);
+ realm->clear();
+ HttpUtil::NameValuePairsIterator parameters = tokenizer.param_pairs();
+ while (parameters.GetNext()) {
+ if (!LowerCaseEqualsASCII(parameters.name(), "realm"))
+ continue;
+
+ if (!base::ConvertToUtf8AndNormalize(
+ parameters.value(), base::kCodepageLatin1, realm))
+ return false;
+ }
+ return parameters.valid();
+}
+
+} // namespace
+
+bool HttpAuthHandlerBasic::Init(HttpAuth::ChallengeTokenizer* challenge) {
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_BASIC;
+ score_ = 1;
+ properties_ = 0;
+ return ParseChallenge(challenge);
+}
+
+bool HttpAuthHandlerBasic::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // Verify the challenge's auth-scheme.
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "basic"))
+ return false;
+
+ std::string realm;
+ if (!ParseRealm(*challenge, &realm))
+ return false;
+
+ realm_ = realm;
+ return true;
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerBasic::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // Basic authentication is always a single round, so any responses
+ // should be treated as a rejection. However, if the new challenge
+ // is for a different realm, then indicate the realm change.
+ std::string realm;
+ if (!ParseRealm(*challenge, &realm))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ return (realm_ != realm)?
+ HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM:
+ HttpAuth::AUTHORIZATION_RESULT_REJECT;
+}
+
+int HttpAuthHandlerBasic::GenerateAuthTokenImpl(
+ const AuthCredentials* credentials, const HttpRequestInfo*,
+ const CompletionCallback&, std::string* auth_token) {
+ DCHECK(credentials);
+ // TODO(eroman): is this the right encoding of username/password?
+ std::string base64_username_password;
+ if (!base::Base64Encode(
+ UTF16ToUTF8(credentials->username()) + ":" +
+ UTF16ToUTF8(credentials->password()),
+ &base64_username_password)) {
+ LOG(ERROR) << "Unexpected problem Base64 encoding.";
+ return ERR_UNEXPECTED;
+ }
+ *auth_token = "Basic " + base64_username_password;
+ return OK;
+}
+
+HttpAuthHandlerBasic::Factory::Factory() {
+}
+
+HttpAuthHandlerBasic::Factory::~Factory() {
+}
+
+int HttpAuthHandlerBasic::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerBasic());
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_basic.h b/chromium/net/http/http_auth_handler_basic.h
new file mode 100644
index 00000000000..ce56152b09f
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_basic.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+
+namespace net {
+
+// Code for handling http basic authentication.
+class NET_EXPORT_PRIVATE HttpAuthHandlerBasic : public HttpAuthHandler {
+ public:
+ class NET_EXPORT_PRIVATE Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ virtual int CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+ };
+
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE;
+
+ private:
+ virtual ~HttpAuthHandlerBasic() {}
+
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* challenge);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_BASIC_H_
diff --git a/chromium/net/http/http_auth_handler_basic_unittest.cc b/chromium/net/http/http_auth_handler_basic_unittest.cc
new file mode 100644
index 00000000000..5e05b76d8b5
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_basic_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_handler_basic.h"
+#include "net/http/http_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerBasicTest, GenerateAuthToken) {
+ static const struct {
+ const char* username;
+ const char* password;
+ const char* expected_credentials;
+ } tests[] = {
+ { "foo", "bar", "Basic Zm9vOmJhcg==" },
+ // Empty username
+ { "", "foobar", "Basic OmZvb2Jhcg==" },
+ // Empty password
+ { "anon", "", "Basic YW5vbjo=" },
+ // Empty username and empty password.
+ { "", "", "Basic Og==" },
+ };
+ GURL origin("http://www.example.com");
+ HttpAuthHandlerBasic::Factory factory;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string challenge = "Basic realm=\"Atlantis\"";
+ scoped_ptr<HttpAuthHandler> basic;
+ EXPECT_EQ(OK, factory.CreateAuthHandlerFromString(
+ challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(), &basic));
+ AuthCredentials credentials(ASCIIToUTF16(tests[i].username),
+ ASCIIToUTF16(tests[i].password));
+ HttpRequestInfo request_info;
+ std::string auth_token;
+ int rv = basic->GenerateAuthToken(&credentials, &request_info,
+ CompletionCallback(), &auth_token);
+ EXPECT_EQ(OK, rv);
+ EXPECT_STREQ(tests[i].expected_credentials, auth_token.c_str());
+ }
+}
+
+TEST(HttpAuthHandlerBasicTest, HandleAnotherChallenge) {
+ static const struct {
+ const char* challenge;
+ HttpAuth::AuthorizationResult expected_rv;
+ } tests[] = {
+ // The handler is initialized using this challenge. The first
+ // time HandleAnotherChallenge is called with it should cause it
+ // to treat the second challenge as a rejection since it is for
+ // the same realm.
+ {
+ "Basic realm=\"First\"",
+ HttpAuth::AUTHORIZATION_RESULT_REJECT
+ },
+
+ // A challenge for a different realm.
+ {
+ "Basic realm=\"Second\"",
+ HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM
+ },
+
+ // Although RFC 2617 isn't explicit about this case, if there is
+ // more than one realm directive, we pick the last one. So this
+ // challenge should be treated as being for "First" realm.
+ {
+ "Basic realm=\"Second\",realm=\"First\"",
+ HttpAuth::AUTHORIZATION_RESULT_REJECT
+ },
+
+ // And this one should be treated as if it was for "Second."
+ {
+ "basic realm=\"First\",realm=\"Second\"",
+ HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM
+ }
+ };
+
+ GURL origin("http://www.example.com");
+ HttpAuthHandlerBasic::Factory factory;
+ scoped_ptr<HttpAuthHandler> basic;
+ EXPECT_EQ(OK, factory.CreateAuthHandlerFromString(
+ tests[0].challenge, HttpAuth::AUTH_SERVER, origin,
+ BoundNetLog(), &basic));
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string challenge(tests[i].challenge);
+ HttpAuth::ChallengeTokenizer tok(challenge.begin(),
+ challenge.end());
+ EXPECT_EQ(tests[i].expected_rv, basic->HandleAnotherChallenge(&tok));
+ }
+}
+
+TEST(HttpAuthHandlerBasicTest, InitFromChallenge) {
+ static const struct {
+ const char* challenge;
+ int expected_rv;
+ const char* expected_realm;
+ } tests[] = {
+ // No realm (we allow this even though realm is supposed to be required
+ // according to RFC 2617.)
+ {
+ "Basic",
+ OK,
+ "",
+ },
+
+ // Realm is empty string.
+ {
+ "Basic realm=\"\"",
+ OK,
+ "",
+ },
+
+ // Realm is valid.
+ {
+ "Basic realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+
+ // The parser ignores tokens which aren't known.
+ {
+ "Basic realm=\"test_realm\",unknown_token=foobar",
+ OK,
+ "test_realm",
+ },
+
+ // The parser skips over tokens which aren't known.
+ {
+ "Basic unknown_token=foobar,realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+
+#if 0
+ // TODO(cbentzel): It's unclear what the parser should do in these cases.
+ // It seems like this should either be treated as invalid,
+ // or the spaces should be used as a separator.
+ {
+ "Basic realm=\"test_realm\" unknown_token=foobar",
+ OK,
+ "test_realm",
+ },
+
+ // The parser skips over tokens which aren't known.
+ {
+ "Basic unknown_token=foobar realm=\"test_realm\"",
+ OK,
+ "test_realm",
+ },
+#endif
+
+ // The parser fails when the first token is not "Basic".
+ {
+ "Negotiate",
+ ERR_INVALID_RESPONSE,
+ ""
+ },
+
+ // Although RFC 2617 isn't explicit about this case, if there is
+ // more than one realm directive, we pick the last one.
+ {
+ "Basic realm=\"foo\",realm=\"bar\"",
+ OK,
+ "bar",
+ },
+
+ // Handle ISO-8859-1 character as part of the realm. The realm is converted
+ // to UTF-8.
+ {
+ "Basic realm=\"foo-\xE5\"",
+ OK,
+ "foo-\xC3\xA5",
+ },
+ };
+ HttpAuthHandlerBasic::Factory factory;
+ GURL origin("http://www.example.com");
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string challenge = tests[i].challenge;
+ scoped_ptr<HttpAuthHandler> basic;
+ int rv = factory.CreateAuthHandlerFromString(
+ challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(), &basic);
+ EXPECT_EQ(tests[i].expected_rv, rv);
+ if (rv == OK)
+ EXPECT_EQ(tests[i].expected_realm, basic->realm());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_digest.cc b/chromium/net/http/http_auth_handler_digest.cc
new file mode 100644
index 00000000000..904430ecb40
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_digest.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2011 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/http/http_auth_handler_digest.h"
+
+#include <string>
+
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+// Digest authentication is specified in RFC 2617.
+// The expanded derivations are listed in the tables below.
+
+//==========+==========+==========================================+
+// qop |algorithm | response |
+//==========+==========+==========================================+
+// ? | ?, md5, | MD5(MD5(A1):nonce:MD5(A2)) |
+// | md5-sess | |
+//--------- +----------+------------------------------------------+
+// auth, | ?, md5, | MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2)) |
+// auth-int | md5-sess | |
+//==========+==========+==========================================+
+// qop |algorithm | A1 |
+//==========+==========+==========================================+
+// | ?, md5 | user:realm:password |
+//----------+----------+------------------------------------------+
+// | md5-sess | MD5(user:realm:password):nonce:cnonce |
+//==========+==========+==========================================+
+// qop |algorithm | A2 |
+//==========+==========+==========================================+
+// ?, auth | | req-method:req-uri |
+//----------+----------+------------------------------------------+
+// auth-int | | req-method:req-uri:MD5(req-entity-body) |
+//=====================+==========================================+
+
+HttpAuthHandlerDigest::NonceGenerator::NonceGenerator() {
+}
+
+HttpAuthHandlerDigest::NonceGenerator::~NonceGenerator() {
+}
+
+HttpAuthHandlerDigest::DynamicNonceGenerator::DynamicNonceGenerator() {
+}
+
+std::string HttpAuthHandlerDigest::DynamicNonceGenerator::GenerateNonce()
+ const {
+ // This is how mozilla generates their cnonce -- a 16 digit hex string.
+ static const char domain[] = "0123456789abcdef";
+ std::string cnonce;
+ cnonce.reserve(16);
+ for (int i = 0; i < 16; ++i)
+ cnonce.push_back(domain[base::RandInt(0, 15)]);
+ return cnonce;
+}
+
+HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator(
+ const std::string& nonce)
+ : nonce_(nonce) {
+}
+
+std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const {
+ return nonce_;
+}
+
+HttpAuthHandlerDigest::Factory::Factory()
+ : nonce_generator_(new DynamicNonceGenerator()) {
+}
+
+HttpAuthHandlerDigest::Factory::~Factory() {
+}
+
+void HttpAuthHandlerDigest::Factory::set_nonce_generator(
+ const NonceGenerator* nonce_generator) {
+ nonce_generator_.reset(nonce_generator);
+}
+
+int HttpAuthHandlerDigest::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get()));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // Even though Digest is not connection based, a "second round" is parsed
+ // to differentiate between stale and rejected responses.
+ // Note that the state of the current handler is not mutated - this way if
+ // there is a rejection the realm hasn't changed.
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
+
+ // Try to find the "stale" value, and also keep track of the realm
+ // for the new challenge.
+ std::string original_realm;
+ while (parameters.GetNext()) {
+ if (LowerCaseEqualsASCII(parameters.name(), "stale")) {
+ if (LowerCaseEqualsASCII(parameters.value(), "true"))
+ return HttpAuth::AUTHORIZATION_RESULT_STALE;
+ } else if (LowerCaseEqualsASCII(parameters.name(), "realm")) {
+ original_realm = parameters.value();
+ }
+ }
+ return (original_realm_ != original_realm) ?
+ HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM :
+ HttpAuth::AUTHORIZATION_RESULT_REJECT;
+}
+
+bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) {
+ return ParseChallenge(challenge);
+}
+
+int HttpAuthHandlerDigest::GenerateAuthTokenImpl(
+ const AuthCredentials* credentials, const HttpRequestInfo* request,
+ const CompletionCallback& callback, std::string* auth_token) {
+ // Generate a random client nonce.
+ std::string cnonce = nonce_generator_->GenerateNonce();
+
+ // Extract the request method and path -- the meaning of 'path' is overloaded
+ // in certain cases, to be a hostname.
+ std::string method;
+ std::string path;
+ GetRequestMethodAndPath(request, &method, &path);
+
+ *auth_token = AssembleCredentials(method, path, *credentials,
+ cnonce, nonce_count_);
+ return OK;
+}
+
+HttpAuthHandlerDigest::HttpAuthHandlerDigest(
+ int nonce_count, const NonceGenerator* nonce_generator)
+ : stale_(false),
+ algorithm_(ALGORITHM_UNSPECIFIED),
+ qop_(QOP_UNSPECIFIED),
+ nonce_count_(nonce_count),
+ nonce_generator_(nonce_generator) {
+ DCHECK(nonce_generator_);
+}
+
+HttpAuthHandlerDigest::~HttpAuthHandlerDigest() {
+}
+
+// The digest challenge header looks like:
+// WWW-Authenticate: Digest
+// [realm="<realm-value>"]
+// nonce="<nonce-value>"
+// [domain="<list-of-URIs>"]
+// [opaque="<opaque-token-value>"]
+// [stale="<true-or-false>"]
+// [algorithm="<digest-algorithm>"]
+// [qop="<list-of-qop-values>"]
+// [<extension-directive>]
+//
+// Note that according to RFC 2617 (section 1.2) the realm is required.
+// However we allow it to be omitted, in which case it will default to the
+// empty string.
+//
+// This allowance is for better compatibility with webservers that fail to
+// send the realm (See http://crbug.com/20984 for an instance where a
+// webserver was not sending the realm with a BASIC challenge).
+bool HttpAuthHandlerDigest::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_DIGEST;
+ score_ = 2;
+ properties_ = ENCRYPTS_IDENTITY;
+
+ // Initialize to defaults.
+ stale_ = false;
+ algorithm_ = ALGORITHM_UNSPECIFIED;
+ qop_ = QOP_UNSPECIFIED;
+ realm_ = original_realm_ = nonce_ = domain_ = opaque_ = std::string();
+
+ // FAIL -- Couldn't match auth-scheme.
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
+ return false;
+
+ HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
+
+ // Loop through all the properties.
+ while (parameters.GetNext()) {
+ // FAIL -- couldn't parse a property.
+ if (!ParseChallengeProperty(parameters.name(),
+ parameters.value()))
+ return false;
+ }
+
+ // Check if tokenizer failed.
+ if (!parameters.valid())
+ return false;
+
+ // Check that a minimum set of properties were provided.
+ if (nonce_.empty())
+ return false;
+
+ return true;
+}
+
+bool HttpAuthHandlerDigest::ParseChallengeProperty(const std::string& name,
+ const std::string& value) {
+ if (LowerCaseEqualsASCII(name, "realm")) {
+ std::string realm;
+ if (!base::ConvertToUtf8AndNormalize(value, base::kCodepageLatin1, &realm))
+ return false;
+ realm_ = realm;
+ original_realm_ = value;
+ } else if (LowerCaseEqualsASCII(name, "nonce")) {
+ nonce_ = value;
+ } else if (LowerCaseEqualsASCII(name, "domain")) {
+ domain_ = value;
+ } else if (LowerCaseEqualsASCII(name, "opaque")) {
+ opaque_ = value;
+ } else if (LowerCaseEqualsASCII(name, "stale")) {
+ // Parse the stale boolean.
+ stale_ = LowerCaseEqualsASCII(value, "true");
+ } else if (LowerCaseEqualsASCII(name, "algorithm")) {
+ // Parse the algorithm.
+ if (LowerCaseEqualsASCII(value, "md5")) {
+ algorithm_ = ALGORITHM_MD5;
+ } else if (LowerCaseEqualsASCII(value, "md5-sess")) {
+ algorithm_ = ALGORITHM_MD5_SESS;
+ } else {
+ DVLOG(1) << "Unknown value of algorithm";
+ return false; // FAIL -- unsupported value of algorithm.
+ }
+ } else if (LowerCaseEqualsASCII(name, "qop")) {
+ // Parse the comma separated list of qops.
+ // auth is the only supported qop, and all other values are ignored.
+ HttpUtil::ValuesIterator qop_values(value.begin(), value.end(), ',');
+ qop_ = QOP_UNSPECIFIED;
+ while (qop_values.GetNext()) {
+ if (LowerCaseEqualsASCII(qop_values.value(), "auth")) {
+ qop_ = QOP_AUTH;
+ break;
+ }
+ }
+ } else {
+ DVLOG(1) << "Skipping unrecognized digest property";
+ // TODO(eroman): perhaps we should fail instead of silently skipping?
+ }
+ return true;
+}
+
+// static
+std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) {
+ switch (qop) {
+ case QOP_UNSPECIFIED:
+ return std::string();
+ case QOP_AUTH:
+ return "auth";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+std::string HttpAuthHandlerDigest::AlgorithmToString(
+ DigestAlgorithm algorithm) {
+ switch (algorithm) {
+ case ALGORITHM_UNSPECIFIED:
+ return std::string();
+ case ALGORITHM_MD5:
+ return "MD5";
+ case ALGORITHM_MD5_SESS:
+ return "MD5-sess";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+void HttpAuthHandlerDigest::GetRequestMethodAndPath(
+ const HttpRequestInfo* request,
+ std::string* method,
+ std::string* path) const {
+ DCHECK(request);
+
+ const GURL& url = request->url;
+
+ if (target_ == HttpAuth::AUTH_PROXY &&
+ (url.SchemeIs("https") || url.SchemeIs("ws") || url.SchemeIs("wss"))) {
+ *method = "CONNECT";
+ *path = GetHostAndPort(url);
+ } else {
+ *method = request->method;
+ *path = HttpUtil::PathForRequest(url);
+ }
+}
+
+std::string HttpAuthHandlerDigest::AssembleResponseDigest(
+ const std::string& method,
+ const std::string& path,
+ const AuthCredentials& credentials,
+ const std::string& cnonce,
+ const std::string& nc) const {
+ // ha1 = MD5(A1)
+ // TODO(eroman): is this the right encoding?
+ std::string ha1 = base::MD5String(UTF16ToUTF8(credentials.username()) + ":" +
+ original_realm_ + ":" +
+ UTF16ToUTF8(credentials.password()));
+ if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
+ ha1 = base::MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
+
+ // ha2 = MD5(A2)
+ // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
+ std::string ha2 = base::MD5String(method + ":" + path);
+
+ std::string nc_part;
+ if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
+ nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
+ }
+
+ return base::MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
+}
+
+std::string HttpAuthHandlerDigest::AssembleCredentials(
+ const std::string& method,
+ const std::string& path,
+ const AuthCredentials& credentials,
+ const std::string& cnonce,
+ int nonce_count) const {
+ // the nonce-count is an 8 digit hex string.
+ std::string nc = base::StringPrintf("%08x", nonce_count);
+
+ // TODO(eroman): is this the right encoding?
+ std::string authorization = (std::string("Digest username=") +
+ HttpUtil::Quote(
+ UTF16ToUTF8(credentials.username())));
+ authorization += ", realm=" + HttpUtil::Quote(original_realm_);
+ authorization += ", nonce=" + HttpUtil::Quote(nonce_);
+ authorization += ", uri=" + HttpUtil::Quote(path);
+
+ if (algorithm_ != ALGORITHM_UNSPECIFIED) {
+ authorization += ", algorithm=" + AlgorithmToString(algorithm_);
+ }
+ std::string response = AssembleResponseDigest(method, path, credentials,
+ cnonce, nc);
+ // No need to call HttpUtil::Quote() as the response digest cannot contain
+ // any characters needing to be escaped.
+ authorization += ", response=\"" + response + "\"";
+
+ if (!opaque_.empty()) {
+ authorization += ", opaque=" + HttpUtil::Quote(opaque_);
+ }
+ if (qop_ != QOP_UNSPECIFIED) {
+ // TODO(eroman): Supposedly IIS server requires quotes surrounding qop.
+ authorization += ", qop=" + QopToString(qop_);
+ authorization += ", nc=" + nc;
+ authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
+ }
+
+ return authorization;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_digest.h b/chromium/net/http/http_auth_handler_digest.h
new file mode 100644
index 00000000000..7edac166c6d
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_digest.h
@@ -0,0 +1,179 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+
+namespace net {
+
+// Code for handling http digest authentication.
+class NET_EXPORT_PRIVATE HttpAuthHandlerDigest : public HttpAuthHandler {
+ public:
+ // A NonceGenerator is a simple interface for generating client nonces.
+ // Unit tests can override the default client nonce behavior with fixed
+ // nonce generation to get reproducible results.
+ class NET_EXPORT_PRIVATE NonceGenerator {
+ public:
+ NonceGenerator();
+ virtual ~NonceGenerator();
+
+ // Generates a client nonce.
+ virtual std::string GenerateNonce() const = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NonceGenerator);
+ };
+
+ // DynamicNonceGenerator does a random shuffle of 16
+ // characters to generate a client nonce.
+ class DynamicNonceGenerator : public NonceGenerator {
+ public:
+ DynamicNonceGenerator();
+ virtual std::string GenerateNonce() const OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DynamicNonceGenerator);
+ };
+
+ // FixedNonceGenerator always uses the same string specified at
+ // construction time as the client nonce.
+ class NET_EXPORT_PRIVATE FixedNonceGenerator : public NonceGenerator {
+ public:
+ explicit FixedNonceGenerator(const std::string& nonce);
+
+ virtual std::string GenerateNonce() const OVERRIDE;
+
+ private:
+ const std::string nonce_;
+ DISALLOW_COPY_AND_ASSIGN(FixedNonceGenerator);
+ };
+
+ class NET_EXPORT_PRIVATE Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ // This factory owns the passed in |nonce_generator|.
+ void set_nonce_generator(const NonceGenerator* nonce_generator);
+
+ virtual int CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+
+ private:
+ scoped_ptr<const NonceGenerator> nonce_generator_;
+ };
+
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthHandlerDigestTest, ParseChallenge);
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthHandlerDigestTest, AssembleCredentials);
+ FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionTest, DigestPreAuthNonceCount);
+
+ // Possible values for the "algorithm" property.
+ enum DigestAlgorithm {
+ // No algorithm was specified. According to RFC 2617 this means
+ // we should default to ALGORITHM_MD5.
+ ALGORITHM_UNSPECIFIED,
+
+ // Hashes are run for every request.
+ ALGORITHM_MD5,
+
+ // Hash is run only once during the first WWW-Authenticate handshake.
+ // (SESS means session).
+ ALGORITHM_MD5_SESS,
+ };
+
+ // Possible values for QualityOfProtection.
+ // auth-int is not supported, see http://crbug.com/62890 for justification.
+ enum QualityOfProtection {
+ QOP_UNSPECIFIED,
+ QOP_AUTH,
+ };
+
+ // |nonce_count| indicates how many times the server-specified nonce has
+ // been used so far.
+ // |nonce_generator| is used to create a client nonce, and is not owned by
+ // the handler. The lifetime of the |nonce_generator| must exceed that of this
+ // handler.
+ HttpAuthHandlerDigest(int nonce_count, const NonceGenerator* nonce_generator);
+ virtual ~HttpAuthHandlerDigest();
+
+ // Parse the challenge, saving the results into this instance.
+ // Returns true on success.
+ bool ParseChallenge(HttpAuth::ChallengeTokenizer* challenge);
+
+ // Parse an individual property. Returns true on success.
+ bool ParseChallengeProperty(const std::string& name,
+ const std::string& value);
+
+ // Generates a random string, to be used for client-nonce.
+ static std::string GenerateNonce();
+
+ // Convert enum value back to string.
+ static std::string QopToString(QualityOfProtection qop);
+ static std::string AlgorithmToString(DigestAlgorithm algorithm);
+
+ // Extract the method and path of the request, as needed by
+ // the 'A2' production. (path may be a hostname for proxy).
+ void GetRequestMethodAndPath(const HttpRequestInfo* request,
+ std::string* method,
+ std::string* path) const;
+
+ // Build up the 'response' production.
+ std::string AssembleResponseDigest(const std::string& method,
+ const std::string& path,
+ const AuthCredentials& credentials,
+ const std::string& cnonce,
+ const std::string& nc) const;
+
+ // Build up the value for (Authorization/Proxy-Authorization).
+ std::string AssembleCredentials(const std::string& method,
+ const std::string& path,
+ const AuthCredentials& credentials,
+ const std::string& cnonce,
+ int nonce_count) const;
+
+ // Information parsed from the challenge.
+ std::string nonce_;
+ std::string domain_;
+ std::string opaque_;
+ bool stale_;
+ DigestAlgorithm algorithm_;
+ QualityOfProtection qop_;
+
+ // The realm as initially encoded over-the-wire. This is used in the
+ // challenge text, rather than |realm_| which has been converted to
+ // UTF-8.
+ std::string original_realm_;
+
+ int nonce_count_;
+ const NonceGenerator* nonce_generator_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_DIGEST_H_
diff --git a/chromium/net/http/http_auth_handler_digest_unittest.cc b/chromium/net/http/http_auth_handler_digest_unittest.cc
new file mode 100644
index 00000000000..dee44ebd5d0
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_digest_unittest.cc
@@ -0,0 +1,696 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const char* const kSimpleChallenge =
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
+
+// RespondToChallenge creates an HttpAuthHandlerDigest for the specified
+// |challenge|, and generates a response to the challenge which is returned in
+// |token|.
+//
+// The return value indicates whether the |token| was successfully created.
+//
+// If |target| is HttpAuth::AUTH_PROXY, then |proxy_name| specifies the source
+// of the |challenge|. Otherwise, the scheme and host and port of |request_url|
+// indicates the origin of the challenge.
+bool RespondToChallenge(HttpAuth::Target target,
+ const std::string& proxy_name,
+ const std::string& request_url,
+ const std::string& challenge,
+ std::string* token) {
+ // Input validation.
+ if (token == NULL) {
+ ADD_FAILURE() << "|token| must be non-NULL";
+ return false;
+ }
+ EXPECT_TRUE(target != HttpAuth::AUTH_PROXY || !proxy_name.empty());
+ EXPECT_FALSE(request_url.empty());
+ EXPECT_FALSE(challenge.empty());
+
+ token->clear();
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
+ HttpAuthHandlerDigest::NonceGenerator* nonce_generator =
+ new HttpAuthHandlerDigest::FixedNonceGenerator("client_nonce");
+ factory->set_nonce_generator(nonce_generator);
+ scoped_ptr<HttpAuthHandler> handler;
+
+ // Create a handler for a particular challenge.
+ GURL url_origin(target == HttpAuth::AUTH_SERVER ? request_url : proxy_name);
+ int rv_create = factory->CreateAuthHandlerFromString(
+ challenge, target, url_origin.GetOrigin(), BoundNetLog(), &handler);
+ if (rv_create != OK || handler.get() == NULL) {
+ ADD_FAILURE() << "Unable to create auth handler.";
+ return false;
+ }
+
+ // Create a token in response to the challenge.
+ // NOTE: HttpAuthHandlerDigest's implementation of GenerateAuthToken always
+ // completes synchronously. That's why this test can get away with a
+ // TestCompletionCallback without an IO thread.
+ TestCompletionCallback callback;
+ scoped_ptr<HttpRequestInfo> request(new HttpRequestInfo());
+ request->url = GURL(request_url);
+ AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"));
+ int rv_generate = handler->GenerateAuthToken(
+ &credentials, request.get(), callback.callback(), token);
+ if (rv_generate != OK) {
+ ADD_FAILURE() << "Problems generating auth token";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+
+TEST(HttpAuthHandlerDigestTest, ParseChallenge) {
+ static const struct {
+ // The challenge string.
+ const char* challenge;
+ // Expected return value of ParseChallenge.
+ bool parsed_success;
+ // The expected values that were parsed.
+ const char* parsed_realm;
+ const char* parsed_nonce;
+ const char* parsed_domain;
+ const char* parsed_opaque;
+ bool parsed_stale;
+ int parsed_algorithm;
+ int parsed_qop;
+ } tests[] = {
+ { // Check that a minimal challenge works correctly.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Realm does not need to be quoted, even though RFC2617 requires it.
+ "Digest nonce=\"xyz\", realm=ThunderBluff",
+ true,
+ "ThunderBluff",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // We allow the realm to be omitted, and will default it to empty string.
+ // See http://crbug.com/20984.
+ "Digest nonce=\"xyz\"",
+ true,
+ "",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Try with realm set to empty string.
+ "Digest realm=\"\", nonce=\"xyz\"",
+ true,
+ "",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ // Handle ISO-8859-1 character as part of the realm. The realm is converted
+ // to UTF-8. However, the credentials will still use the original encoding.
+ {
+ "Digest nonce=\"xyz\", realm=\"foo-\xE5\"",
+ true,
+ "foo-\xC3\xA5",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED,
+ },
+
+ { // At a minimum, a nonce must be provided.
+ "Digest realm=\"Thunder Bluff\"",
+ false,
+ "",
+ "",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // The nonce does not need to be quoted, even though RFC2617
+ // requires it.
+ "Digest nonce=xyz, realm=\"Thunder Bluff\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Unknown authentication parameters are ignored.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", foo=\"bar\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Check that when algorithm has an unsupported value, parsing fails.
+ "Digest nonce=\"xyz\", algorithm=\"awezum\", realm=\"Thunder\"",
+ false,
+ // The remaining values don't matter (but some have been set already).
+ "",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Check that algorithm's value is case insensitive, and that MD5 is
+ // a supported algorithm.
+ "Digest nonce=\"xyz\", algorithm=\"mD5\", realm=\"Oblivion\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_MD5,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Check that md5-sess is a supported algorithm.
+ "Digest nonce=\"xyz\", algorithm=\"md5-sess\", realm=\"Oblivion\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_MD5_SESS,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED,
+ },
+
+ { // Check that qop's value is case insensitive, and that auth is known.
+ "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"aUth\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_AUTH
+ },
+
+ { // auth-int is not handled, but will fall back to default qop.
+ "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth-int\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Unknown qop values are ignored.
+ "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,foo\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_AUTH
+ },
+
+ { // If auth-int is included with auth, then use auth.
+ "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,auth-int\"",
+ true,
+ "Oblivion",
+ "xyz",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_AUTH
+ },
+
+ { // Opaque parameter parsing should work correctly.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=\"foobar\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "foobar",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Opaque parameters do not need to be quoted, even though RFC2617
+ // seems to require it.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=foobar",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "",
+ "foobar",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Domain can be parsed.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
+ "domain=\"http://intranet.example.com/protection\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "http://intranet.example.com/protection",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // Multiple domains can be parsed.
+ "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
+ "domain=\"http://intranet.example.com/protection http://www.google.com\"",
+ true,
+ "Thunder Bluff",
+ "xyz",
+ "http://intranet.example.com/protection http://www.google.com",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+
+ { // If a non-Digest scheme is somehow passed in, it should be rejected.
+ "Basic realm=\"foo\"",
+ false,
+ "",
+ "",
+ "",
+ "",
+ false,
+ HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED,
+ HttpAuthHandlerDigest::QOP_UNSPECIFIED
+ },
+ };
+
+ GURL origin("http://www.example.com");
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = factory->CreateAuthHandlerFromString(tests[i].challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog(),
+ &handler);
+ if (tests[i].parsed_success) {
+ EXPECT_EQ(OK, rv);
+ } else {
+ EXPECT_NE(OK, rv);
+ EXPECT_TRUE(handler.get() == NULL);
+ continue;
+ }
+ ASSERT_TRUE(handler.get() != NULL);
+ HttpAuthHandlerDigest* digest =
+ static_cast<HttpAuthHandlerDigest*>(handler.get());
+ EXPECT_STREQ(tests[i].parsed_realm, digest->realm_.c_str());
+ EXPECT_STREQ(tests[i].parsed_nonce, digest->nonce_.c_str());
+ EXPECT_STREQ(tests[i].parsed_domain, digest->domain_.c_str());
+ EXPECT_STREQ(tests[i].parsed_opaque, digest->opaque_.c_str());
+ EXPECT_EQ(tests[i].parsed_stale, digest->stale_);
+ EXPECT_EQ(tests[i].parsed_algorithm, digest->algorithm_);
+ EXPECT_EQ(tests[i].parsed_qop, digest->qop_);
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ EXPECT_TRUE(handler->NeedsIdentity());
+ EXPECT_FALSE(handler->AllowsDefaultCredentials());
+ }
+}
+
+TEST(HttpAuthHandlerDigestTest, AssembleCredentials) {
+ static const struct {
+ const char* req_method;
+ const char* req_path;
+ const char* challenge;
+ const char* username;
+ const char* password;
+ const char* cnonce;
+ int nonce_count;
+ const char* expected_creds;
+ } tests[] = {
+ { // MD5 with username/password
+ "GET",
+ "/test/drealm1",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "foo", "bar", // username/password
+ "082c875dcb2ca740", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"foo\", realm=\"DRealm1\", "
+ "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
+ "uri=\"/test/drealm1\", algorithm=MD5, "
+ "response=\"bcfaa62f1186a31ff1b474a19a17cf57\", "
+ "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
+ },
+
+ { // MD5 with username but empty password. username has space in it.
+ "GET",
+ "/test/drealm1/",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "foo bar", "", // Username/password
+ "082c875dcb2ca740", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"foo bar\", realm=\"DRealm1\", "
+ "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
+ "uri=\"/test/drealm1/\", algorithm=MD5, "
+ "response=\"93c9c6d5930af3b0eb26c745e02b04a0\", "
+ "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
+ },
+
+ { // MD5 with no username.
+ "GET",
+ "/test/drealm1/",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "", "pass", // Username/password
+ "6509bc74daed8263", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"\", realm=\"DRealm1\", "
+ "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
+ "uri=\"/test/drealm1/\", algorithm=MD5, "
+ "response=\"bc597110f41a62d07f8b70b6977fcb61\", "
+ "qop=auth, nc=00000001, cnonce=\"6509bc74daed8263\""
+ },
+
+ { // MD5 with no username and no password.
+ "GET",
+ "/test/drealm1/",
+
+ // Challenge
+ "Digest realm=\"DRealm1\", "
+ "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
+ "algorithm=MD5, qop=\"auth\"",
+
+ "", "", // Username/password
+ "1522e61005789929", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"\", realm=\"DRealm1\", "
+ "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
+ "uri=\"/test/drealm1/\", algorithm=MD5, "
+ "response=\"22cfa2b30cb500a9591c6d55ec5590a8\", "
+ "qop=auth, nc=00000001, cnonce=\"1522e61005789929\""
+ },
+
+ { // No algorithm, and no qop.
+ "GET",
+ "/",
+
+ // Challenge
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\"",
+
+ "FooBar", "pass", // Username/password
+ "", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"FooBar\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/\", "
+ "response=\"f72ff54ebde2f928860f806ec04acd1b\""
+ },
+
+ { // MD5-sess
+ "GET",
+ "/",
+
+ // Challenge
+ "Digest realm=\"Baztastic\", nonce=\"AAAAAAAA\", "
+ "algorithm=\"md5-sess\", qop=auth",
+
+ "USER", "123", // Username/password
+ "15c07961ed8575c4", // cnonce
+ 1, // nc
+
+ // Authorization
+ "Digest username=\"USER\", realm=\"Baztastic\", "
+ "nonce=\"AAAAAAAA\", uri=\"/\", algorithm=MD5-sess, "
+ "response=\"cbc1139821ee7192069580570c541a03\", "
+ "qop=auth, nc=00000001, cnonce=\"15c07961ed8575c4\""
+ }
+ };
+ GURL origin("http://www.example.com");
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = factory->CreateAuthHandlerFromString(tests[i].challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_TRUE(handler != NULL);
+
+ HttpAuthHandlerDigest* digest =
+ static_cast<HttpAuthHandlerDigest*>(handler.get());
+ std::string creds =
+ digest->AssembleCredentials(tests[i].req_method,
+ tests[i].req_path,
+ AuthCredentials(
+ ASCIIToUTF16(tests[i].username),
+ ASCIIToUTF16(tests[i].password)),
+ tests[i].cnonce,
+ tests[i].nonce_count);
+
+ EXPECT_STREQ(tests[i].expected_creds, creds.c_str());
+ }
+}
+
+TEST(HttpAuthHandlerDigest, HandleAnotherChallenge) {
+ scoped_ptr<HttpAuthHandlerDigest::Factory> factory(
+ new HttpAuthHandlerDigest::Factory());
+ scoped_ptr<HttpAuthHandler> handler;
+ std::string default_challenge =
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
+ GURL origin("intranet.google.com");
+ int rv = factory->CreateAuthHandlerFromString(
+ default_challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_TRUE(handler.get() != NULL);
+ HttpAuth::ChallengeTokenizer tok_default(default_challenge.begin(),
+ default_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ handler->HandleAnotherChallenge(&tok_default));
+
+ std::string stale_challenge = default_challenge + ", stale=true";
+ HttpAuth::ChallengeTokenizer tok_stale(stale_challenge.begin(),
+ stale_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_STALE,
+ handler->HandleAnotherChallenge(&tok_stale));
+
+ std::string stale_false_challenge = default_challenge + ", stale=false";
+ HttpAuth::ChallengeTokenizer tok_stale_false(stale_false_challenge.begin(),
+ stale_false_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ handler->HandleAnotherChallenge(&tok_stale_false));
+
+ std::string realm_change_challenge =
+ "Digest realm=\"SomethingElse\", nonce=\"nonce-value2\"";
+ HttpAuth::ChallengeTokenizer tok_realm_change(realm_change_challenge.begin(),
+ realm_change_challenge.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM,
+ handler->HandleAnotherChallenge(&tok_realm_change));
+}
+
+TEST(HttpAuthHandlerDigest, RespondToServerChallenge) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_SERVER,
+ std::string(),
+ "http://www.example.com/path/to/resource",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
+ "response=\"6779f90bd0d658f937c1af967614fe84\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToHttpsServerChallenge) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_SERVER,
+ std::string(),
+ "https://www.example.com/path/to/resource",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
+ "response=\"6779f90bd0d658f937c1af967614fe84\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToProxyChallenge) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_PROXY,
+ "http://proxy.intranet.corp.com:3128",
+ "http://www.example.com/path/to/resource",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
+ "response=\"6779f90bd0d658f937c1af967614fe84\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToProxyChallengeHttps) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_PROXY,
+ "http://proxy.intranet.corp.com:3128",
+ "https://www.example.com/path/to/resource",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
+ "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToProxyChallengeWs) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_PROXY,
+ "http://proxy.intranet.corp.com:3128",
+ "ws://www.example.com/echo",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"www.example.com:80\", "
+ "response=\"aa1df184f68d5b6ab9d9aa4f88e41b4c\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToProxyChallengeWss) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_PROXY,
+ "http://proxy.intranet.corp.com:3128",
+ "wss://www.example.com/echo",
+ kSimpleChallenge,
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
+ "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToChallengeAuthQop) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_SERVER,
+ std::string(),
+ "http://www.example.com/path/to/resource",
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\", qop=\"auth\"",
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
+ "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
+ "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
+ auth_token);
+}
+
+TEST(HttpAuthHandlerDigest, RespondToChallengeOpaque) {
+ std::string auth_token;
+ EXPECT_TRUE(RespondToChallenge(
+ HttpAuth::AUTH_SERVER,
+ std::string(),
+ "http://www.example.com/path/to/resource",
+ "Digest realm=\"Oblivion\", nonce=\"nonce-value\", "
+ "qop=\"auth\", opaque=\"opaque text\"",
+ &auth_token));
+ EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
+ "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
+ "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
+ "opaque=\"opaque text\", "
+ "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
+ auth_token);
+}
+
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_factory.cc b/chromium/net/http/http_auth_handler_factory.cc
new file mode 100644
index 00000000000..f94bd33d666
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_factory.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 2010 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/http/http_auth_handler_factory.h"
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_filter.h"
+#include "net/http/http_auth_handler_basic.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_auth_handler_ntlm.h"
+
+#if defined(USE_KERBEROS)
+#include "net/http/http_auth_handler_negotiate.h"
+#endif
+
+namespace net {
+
+int HttpAuthHandlerFactory::CreateAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ return CreateAuthHandler(&props, target, origin, CREATE_CHALLENGE, 1,
+ net_log, handler);
+}
+
+int HttpAuthHandlerFactory::CreatePreemptiveAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ HttpAuth::ChallengeTokenizer props(challenge.begin(), challenge.end());
+ return CreateAuthHandler(&props, target, origin, CREATE_PREEMPTIVE,
+ digest_nonce_count, net_log, handler);
+}
+
+// static
+HttpAuthHandlerRegistryFactory* HttpAuthHandlerFactory::CreateDefault(
+ HostResolver* host_resolver) {
+ DCHECK(host_resolver);
+ HttpAuthHandlerRegistryFactory* registry_factory =
+ new HttpAuthHandlerRegistryFactory();
+ registry_factory->RegisterSchemeFactory(
+ "basic", new HttpAuthHandlerBasic::Factory());
+ registry_factory->RegisterSchemeFactory(
+ "digest", new HttpAuthHandlerDigest::Factory());
+
+#if defined(USE_KERBEROS)
+ HttpAuthHandlerNegotiate::Factory* negotiate_factory =
+ new HttpAuthHandlerNegotiate::Factory();
+#if defined(OS_POSIX)
+ negotiate_factory->set_library(new GSSAPISharedLibrary(std::string()));
+#elif defined(OS_WIN)
+ negotiate_factory->set_library(new SSPILibraryDefault());
+#endif
+ negotiate_factory->set_host_resolver(host_resolver);
+ registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory);
+#endif // defined(USE_KERBEROS)
+
+ HttpAuthHandlerNTLM::Factory* ntlm_factory =
+ new HttpAuthHandlerNTLM::Factory();
+#if defined(OS_WIN)
+ ntlm_factory->set_sspi_library(new SSPILibraryDefault());
+#endif
+ registry_factory->RegisterSchemeFactory("ntlm", ntlm_factory);
+ return registry_factory;
+}
+
+namespace {
+
+bool IsSupportedScheme(const std::vector<std::string>& supported_schemes,
+ const std::string& scheme) {
+ std::vector<std::string>::const_iterator it = std::find(
+ supported_schemes.begin(), supported_schemes.end(), scheme);
+ return it != supported_schemes.end();
+}
+
+} // namespace
+
+HttpAuthHandlerRegistryFactory::HttpAuthHandlerRegistryFactory() {
+}
+
+HttpAuthHandlerRegistryFactory::~HttpAuthHandlerRegistryFactory() {
+ STLDeleteContainerPairSecondPointers(factory_map_.begin(),
+ factory_map_.end());
+}
+
+void HttpAuthHandlerRegistryFactory::SetURLSecurityManager(
+ const std::string& scheme,
+ URLSecurityManager* security_manager) {
+ HttpAuthHandlerFactory* factory = GetSchemeFactory(scheme);
+ if (factory)
+ factory->set_url_security_manager(security_manager);
+}
+
+void HttpAuthHandlerRegistryFactory::RegisterSchemeFactory(
+ const std::string& scheme,
+ HttpAuthHandlerFactory* factory) {
+ std::string lower_scheme = StringToLowerASCII(scheme);
+ FactoryMap::iterator it = factory_map_.find(lower_scheme);
+ if (it != factory_map_.end()) {
+ delete it->second;
+ }
+ if (factory)
+ factory_map_[lower_scheme] = factory;
+ else
+ factory_map_.erase(it);
+}
+
+HttpAuthHandlerFactory* HttpAuthHandlerRegistryFactory::GetSchemeFactory(
+ const std::string& scheme) const {
+ std::string lower_scheme = StringToLowerASCII(scheme);
+ FactoryMap::const_iterator it = factory_map_.find(lower_scheme);
+ if (it == factory_map_.end()) {
+ return NULL; // |scheme| is not registered.
+ }
+ return it->second;
+}
+
+// static
+HttpAuthHandlerRegistryFactory* HttpAuthHandlerRegistryFactory::Create(
+ const std::vector<std::string>& supported_schemes,
+ URLSecurityManager* security_manager,
+ HostResolver* host_resolver,
+ const std::string& gssapi_library_name,
+ bool negotiate_disable_cname_lookup,
+ bool negotiate_enable_port) {
+ HttpAuthHandlerRegistryFactory* registry_factory =
+ new HttpAuthHandlerRegistryFactory();
+ if (IsSupportedScheme(supported_schemes, "basic"))
+ registry_factory->RegisterSchemeFactory(
+ "basic", new HttpAuthHandlerBasic::Factory());
+ if (IsSupportedScheme(supported_schemes, "digest"))
+ registry_factory->RegisterSchemeFactory(
+ "digest", new HttpAuthHandlerDigest::Factory());
+ if (IsSupportedScheme(supported_schemes, "ntlm")) {
+ HttpAuthHandlerNTLM::Factory* ntlm_factory =
+ new HttpAuthHandlerNTLM::Factory();
+ ntlm_factory->set_url_security_manager(security_manager);
+#if defined(OS_WIN)
+ ntlm_factory->set_sspi_library(new SSPILibraryDefault());
+#endif
+ registry_factory->RegisterSchemeFactory("ntlm", ntlm_factory);
+ }
+#if defined(USE_KERBEROS)
+ if (IsSupportedScheme(supported_schemes, "negotiate")) {
+ HttpAuthHandlerNegotiate::Factory* negotiate_factory =
+ new HttpAuthHandlerNegotiate::Factory();
+#if defined(OS_POSIX)
+ negotiate_factory->set_library(
+ new GSSAPISharedLibrary(gssapi_library_name));
+#elif defined(OS_WIN)
+ negotiate_factory->set_library(new SSPILibraryDefault());
+#endif
+ negotiate_factory->set_url_security_manager(security_manager);
+ DCHECK(host_resolver || negotiate_disable_cname_lookup);
+ negotiate_factory->set_host_resolver(host_resolver);
+ negotiate_factory->set_disable_cname_lookup(negotiate_disable_cname_lookup);
+ negotiate_factory->set_use_port(negotiate_enable_port);
+ registry_factory->RegisterSchemeFactory("negotiate", negotiate_factory);
+ }
+#endif // defined(USE_KERBEROS)
+
+ return registry_factory;
+}
+
+int HttpAuthHandlerRegistryFactory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ std::string scheme = challenge->scheme();
+ if (scheme.empty()) {
+ handler->reset();
+ return ERR_INVALID_RESPONSE;
+ }
+ std::string lower_scheme = StringToLowerASCII(scheme);
+ FactoryMap::iterator it = factory_map_.find(lower_scheme);
+ if (it == factory_map_.end()) {
+ handler->reset();
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ }
+ DCHECK(it->second);
+ return it->second->CreateAuthHandler(challenge, target, origin, reason,
+ digest_nonce_count, net_log, handler);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_factory.h b/chromium/net/http/http_auth_handler_factory.h
new file mode 100644
index 00000000000..27627514979
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_factory.h
@@ -0,0 +1,202 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+#include "net/http/url_security_manager.h"
+
+class GURL;
+
+namespace net {
+
+class BoundNetLog;
+class HostResolver;
+class HttpAuthHandler;
+class HttpAuthHandlerRegistryFactory;
+
+// An HttpAuthHandlerFactory is used to create HttpAuthHandler objects.
+// The HttpAuthHandlerFactory object _must_ outlive any of the HttpAuthHandler
+// objects that it creates.
+class NET_EXPORT HttpAuthHandlerFactory {
+ public:
+ enum CreateReason {
+ CREATE_CHALLENGE, // Create a handler in response to a challenge.
+ CREATE_PREEMPTIVE, // Create a handler preemptively.
+ };
+
+ HttpAuthHandlerFactory() : url_security_manager_(NULL) {}
+ virtual ~HttpAuthHandlerFactory() {}
+
+ // Sets an URL security manager. HttpAuthHandlerFactory doesn't own the URL
+ // security manager, and the URL security manager should outlive this object.
+ void set_url_security_manager(URLSecurityManager* url_security_manager) {
+ url_security_manager_ = url_security_manager;
+ }
+
+ // Retrieves the associated URL security manager.
+ URLSecurityManager* url_security_manager() {
+ return url_security_manager_;
+ }
+
+ // Creates an HttpAuthHandler object based on the authentication
+ // challenge specified by |*challenge|. |challenge| must point to a valid
+ // non-NULL tokenizer.
+ //
+ // If an HttpAuthHandler object is successfully created it is passed back to
+ // the caller through |*handler| and OK is returned.
+ //
+ // If |*challenge| specifies an unsupported authentication scheme, |*handler|
+ // is set to NULL and ERR_UNSUPPORTED_AUTH_SCHEME is returned.
+ //
+ // If |*challenge| is improperly formed, |*handler| is set to NULL and
+ // ERR_INVALID_RESPONSE is returned.
+ //
+ // |create_reason| indicates why the handler is being created. This is used
+ // since NTLM and Negotiate schemes do not support preemptive creation.
+ //
+ // |digest_nonce_count| is specifically intended for the Digest authentication
+ // scheme, and indicates the number of handlers generated for a particular
+ // server nonce challenge.
+ //
+ // For the NTLM and Negotiate handlers:
+ // If |origin| does not match the authentication method's filters for
+ // the specified |target|, ERR_INVALID_AUTH_CREDENTIALS is returned.
+ // NOTE: This will apply to ALL |origin| values if the filters are empty.
+ //
+ // |*challenge| should not be reused after a call to |CreateAuthHandler()|,
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason create_reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) = 0;
+
+ // Creates an HTTP authentication handler based on the authentication
+ // challenge string |challenge|.
+ // This is a convenience function which creates a ChallengeTokenizer for
+ // |challenge| and calls |CreateAuthHandler|. See |CreateAuthHandler| for
+ // more details on return values.
+ int CreateAuthHandlerFromString(const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Creates an HTTP authentication handler based on the authentication
+ // challenge string |challenge|.
+ // This is a convenience function which creates a ChallengeTokenizer for
+ // |challenge| and calls |CreateAuthHandler|. See |CreateAuthHandler| for
+ // more details on return values.
+ int CreatePreemptiveAuthHandlerFromString(
+ const std::string& challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler);
+
+ // Creates a standard HttpAuthHandlerRegistryFactory. The caller is
+ // responsible for deleting the factory.
+ // The default factory supports Basic, Digest, NTLM, and Negotiate schemes.
+ //
+ // |resolver| is used by the Negotiate authentication handler to perform
+ // CNAME lookups to generate a Kerberos SPN for the server. It must be
+ // non-NULL. |resolver| must remain valid for the lifetime of the
+ // HttpAuthHandlerRegistryFactory and any HttpAuthHandlers created by said
+ // factory.
+ static HttpAuthHandlerRegistryFactory* CreateDefault(HostResolver* resolver);
+
+ private:
+ // The URL security manager
+ URLSecurityManager* url_security_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthHandlerFactory);
+};
+
+// The HttpAuthHandlerRegistryFactory dispatches create requests out
+// to other factories based on the auth scheme.
+class NET_EXPORT HttpAuthHandlerRegistryFactory
+ : public HttpAuthHandlerFactory {
+ public:
+ HttpAuthHandlerRegistryFactory();
+ virtual ~HttpAuthHandlerRegistryFactory();
+
+ // Sets an URL security manager into the factory associated with |scheme|.
+ void SetURLSecurityManager(const std::string& scheme,
+ URLSecurityManager* url_security_manager);
+
+ // Registers a |factory| that will be used for a particular HTTP
+ // authentication scheme such as Basic, Digest, or Negotiate.
+ // The |*factory| object is assumed to be new-allocated, and its lifetime
+ // will be managed by this HttpAuthHandlerRegistryFactory object (including
+ // deleting it when it is no longer used.
+ // A NULL |factory| value means that HttpAuthHandlers's will not be created
+ // for |scheme|. If a factory object used to exist for |scheme|, it will be
+ // deleted.
+ void RegisterSchemeFactory(const std::string& scheme,
+ HttpAuthHandlerFactory* factory);
+
+ // Retrieve the factory for the specified |scheme|. If no factory exists
+ // for the |scheme|, NULL is returned. The returned factory must not be
+ // deleted by the caller, and it is guaranteed to be valid until either
+ // a new factory is registered for the same scheme, or until this
+ // registry factory is destroyed.
+ HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const;
+
+ // Creates an HttpAuthHandlerRegistryFactory.
+ //
+ // |supported_schemes| is a list of authentication schemes. Valid values
+ // include "basic", "digest", "ntlm", and "negotiate", where case matters.
+ //
+ // |security_manager| is used by the NTLM and Negotiate authenticators
+ // to determine which servers Integrated Authentication can be used with. If
+ // NULL, Integrated Authentication will not be used with any server.
+ //
+ // |host_resolver| is used by the Negotiate authentication handler to perform
+ // CNAME lookups to generate a Kerberos SPN for the server. If the "negotiate"
+ // scheme is used and |negotiate_disable_cname_lookup| is false,
+ // |host_resolver| must not be NULL.
+ //
+ // |gssapi_library_name| specifies the name of the GSSAPI library that will
+ // be loaded on all platforms except Windows.
+ //
+ // |negotiate_disable_cname_lookup| and |negotiate_enable_port| both control
+ // how Negotiate does SPN generation, by default these should be false.
+ static HttpAuthHandlerRegistryFactory* Create(
+ const std::vector<std::string>& supported_schemes,
+ URLSecurityManager* security_manager,
+ HostResolver* host_resolver,
+ const std::string& gssapi_library_name,
+ bool negotiate_disable_cname_lookup,
+ bool negotiate_enable_port);
+
+ // Creates an auth handler by dispatching out to the registered factories
+ // based on the first token in |challenge|.
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+
+ private:
+ typedef std::map<std::string, HttpAuthHandlerFactory*> FactoryMap;
+
+ FactoryMap factory_map_;
+ DISALLOW_COPY_AND_ASSIGN(HttpAuthHandlerRegistryFactory);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_FACTORY_H_
diff --git a/chromium/net/http/http_auth_handler_factory_unittest.cc b/chromium/net/http/http_auth_handler_factory_unittest.cc
new file mode 100644
index 00000000000..8e69919c0bf
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_factory_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2011 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 "base/memory/scoped_ptr.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/mock_allow_url_security_manager.h"
+#include "net/http/url_security_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockHttpAuthHandlerFactory : public HttpAuthHandlerFactory {
+ public:
+ explicit MockHttpAuthHandlerFactory(int return_code) :
+ return_code_(return_code) {}
+ virtual ~MockHttpAuthHandlerFactory() {}
+
+ virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE {
+ handler->reset();
+ return return_code_;
+ }
+
+ private:
+ int return_code_;
+};
+
+} // namespace
+
+TEST(HttpAuthHandlerFactoryTest, RegistryFactory) {
+ HttpAuthHandlerRegistryFactory registry_factory;
+ GURL gurl("www.google.com");
+ const int kBasicReturnCode = ERR_INVALID_SPDY_STREAM;
+ MockHttpAuthHandlerFactory* mock_factory_basic =
+ new MockHttpAuthHandlerFactory(kBasicReturnCode);
+
+ const int kDigestReturnCode = ERR_PAC_SCRIPT_FAILED;
+ MockHttpAuthHandlerFactory* mock_factory_digest =
+ new MockHttpAuthHandlerFactory(kDigestReturnCode);
+
+ const int kDigestReturnCodeReplace = ERR_SYN_REPLY_NOT_RECEIVED;
+ MockHttpAuthHandlerFactory* mock_factory_digest_replace =
+ new MockHttpAuthHandlerFactory(kDigestReturnCodeReplace);
+
+ scoped_ptr<HttpAuthHandler> handler;
+
+ // No schemes should be supported in the beginning.
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+
+ // Test what happens with a single scheme.
+ registry_factory.RegisterSchemeFactory("Basic", mock_factory_basic);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+
+ // Test multiple schemes
+ registry_factory.RegisterSchemeFactory("Digest", mock_factory_digest);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(kDigestReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+
+ // Test case-insensitivity
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+
+ // Test replacement of existing auth scheme
+ registry_factory.RegisterSchemeFactory("Digest", mock_factory_digest_replace);
+ EXPECT_EQ(kBasicReturnCode,
+ registry_factory.CreateAuthHandlerFromString(
+ "Basic", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(), &handler));
+ EXPECT_EQ(kDigestReturnCodeReplace,
+ registry_factory.CreateAuthHandlerFromString(
+ "Digest", HttpAuth::AUTH_SERVER, gurl, BoundNetLog(),
+ &handler));
+}
+
+TEST(HttpAuthHandlerFactoryTest, DefaultFactory) {
+ scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
+ MockAllowURLSecurityManager url_security_manager;
+ scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
+ http_auth_handler_factory->SetURLSecurityManager(
+ "negotiate", &url_security_manager);
+ GURL server_origin("http://www.example.com");
+ GURL proxy_origin("http://cache.example.com:3128");
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Basic realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_FALSE(handler.get() == NULL);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_BASIC, handler->auth_scheme());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_FALSE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "UNSUPPORTED realm=\"FooBar\"",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ EXPECT_TRUE(handler.get() == NULL);
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Digest realm=\"FooBar\", nonce=\"xyz\"",
+ HttpAuth::AUTH_PROXY,
+ proxy_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_FALSE(handler.get() == NULL);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_DIGEST, handler->auth_scheme());
+ EXPECT_STREQ("FooBar", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_PROXY, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_FALSE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "NTLM",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+ EXPECT_EQ(OK, rv);
+ ASSERT_FALSE(handler.get() == NULL);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_NTLM, handler->auth_scheme());
+ EXPECT_STREQ("", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_TRUE(handler->is_connection_based());
+ }
+ {
+ scoped_ptr<HttpAuthHandler> handler;
+ int rv = http_auth_handler_factory->CreateAuthHandlerFromString(
+ "Negotiate",
+ HttpAuth::AUTH_SERVER,
+ server_origin,
+ BoundNetLog(),
+ &handler);
+#if defined(USE_KERBEROS)
+ EXPECT_EQ(OK, rv);
+ ASSERT_FALSE(handler.get() == NULL);
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_NEGOTIATE, handler->auth_scheme());
+ EXPECT_STREQ("", handler->realm().c_str());
+ EXPECT_EQ(HttpAuth::AUTH_SERVER, handler->target());
+ EXPECT_TRUE(handler->encrypts_identity());
+ EXPECT_TRUE(handler->is_connection_based());
+#else
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ EXPECT_TRUE(handler.get() == NULL);
+#endif // defined(USE_KERBEROS)
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_mock.cc b/chromium/net/http/http_auth_handler_mock.cc
new file mode 100644
index 00000000000..826c3d6a12b
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_mock.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2011 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/http/http_auth_handler_mock.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+HttpAuthHandlerMock::HttpAuthHandlerMock()
+ : resolve_(RESOLVE_INIT),
+ weak_factory_(this),
+ generate_async_(false),
+ generate_rv_(OK),
+ auth_token_(NULL),
+ first_round_(true),
+ connection_based_(false),
+ allows_default_credentials_(false),
+ allows_explicit_credentials_(true) {
+}
+
+HttpAuthHandlerMock::~HttpAuthHandlerMock() {
+}
+
+void HttpAuthHandlerMock::SetResolveExpectation(Resolve resolve) {
+ EXPECT_EQ(RESOLVE_INIT, resolve_);
+ resolve_ = resolve;
+}
+
+bool HttpAuthHandlerMock::NeedsCanonicalName() {
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ case RESOLVE_ASYNC:
+ return true;
+ case RESOLVE_SKIP:
+ resolve_ = RESOLVE_TESTED;
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+int HttpAuthHandlerMock::ResolveCanonicalName(
+ HostResolver* host_resolver, const CompletionCallback& callback) {
+ EXPECT_NE(RESOLVE_TESTED, resolve_);
+ int rv = OK;
+ switch (resolve_) {
+ case RESOLVE_SYNC:
+ resolve_ = RESOLVE_TESTED;
+ break;
+ case RESOLVE_ASYNC:
+ EXPECT_TRUE(callback_.is_null());
+ rv = ERR_IO_PENDING;
+ callback_ = callback;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpAuthHandlerMock::OnResolveCanonicalName,
+ weak_factory_.GetWeakPtr()));
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return rv;
+}
+
+void HttpAuthHandlerMock::SetGenerateExpectation(bool async, int rv) {
+ generate_async_ = async;
+ generate_rv_ = rv;
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerMock::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ // If we receive an empty challenge for a connection based scheme, or a second
+ // challenge for a non connection based scheme, assume it's a rejection.
+ if (!is_connection_based() || challenge->base64_param().empty())
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ if (!LowerCaseEqualsASCII(challenge->scheme(), "mock"))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+bool HttpAuthHandlerMock::NeedsIdentity() {
+ return first_round_;
+}
+
+bool HttpAuthHandlerMock::AllowsDefaultCredentials() {
+ return allows_default_credentials_;
+}
+
+bool HttpAuthHandlerMock::AllowsExplicitCredentials() {
+ return allows_explicit_credentials_;
+}
+
+bool HttpAuthHandlerMock::Init(HttpAuth::ChallengeTokenizer* challenge) {
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_MOCK;
+ score_ = 1;
+ properties_ = connection_based_ ? IS_CONNECTION_BASED : 0;
+ return true;
+}
+
+int HttpAuthHandlerMock::GenerateAuthTokenImpl(
+ const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) {
+ first_round_ = false;
+ request_url_ = request->url;
+ if (generate_async_) {
+ EXPECT_TRUE(callback_.is_null());
+ EXPECT_TRUE(auth_token_ == NULL);
+ callback_ = callback;
+ auth_token_ = auth_token;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpAuthHandlerMock::OnGenerateAuthToken,
+ weak_factory_.GetWeakPtr()));
+ return ERR_IO_PENDING;
+ } else {
+ if (generate_rv_ == OK)
+ *auth_token = "auth_token";
+ return generate_rv_;
+ }
+}
+
+void HttpAuthHandlerMock::OnResolveCanonicalName() {
+ EXPECT_EQ(RESOLVE_ASYNC, resolve_);
+ EXPECT_TRUE(!callback_.is_null());
+ resolve_ = RESOLVE_TESTED;
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(OK);
+}
+
+void HttpAuthHandlerMock::OnGenerateAuthToken() {
+ EXPECT_TRUE(generate_async_);
+ EXPECT_TRUE(!callback_.is_null());
+ if (generate_rv_ == OK)
+ *auth_token_ = "auth_token";
+ auth_token_ = NULL;
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(generate_rv_);
+}
+
+HttpAuthHandlerMock::Factory::Factory()
+ : do_init_from_challenge_(false) {
+ // TODO(cbentzel): Default do_init_from_challenge_ to true.
+}
+
+HttpAuthHandlerMock::Factory::~Factory() {
+}
+
+void HttpAuthHandlerMock::Factory::AddMockHandler(
+ HttpAuthHandler* handler, HttpAuth::Target target) {
+ handlers_[target].push_back(handler);
+}
+
+int HttpAuthHandlerMock::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (handlers_[target].empty())
+ return ERR_UNEXPECTED;
+ scoped_ptr<HttpAuthHandler> tmp_handler(handlers_[target][0]);
+ std::vector<HttpAuthHandler*>& handlers = handlers_[target].get();
+ handlers.erase(handlers.begin());
+ if (do_init_from_challenge_ &&
+ !tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_mock.h b/chromium/net/http/http_auth_handler_mock.h
new file mode 100644
index 00000000000..7cc441a70cc
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_mock.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
+
+#include <string>
+
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class HostResolver;
+
+// MockAuthHandler is used in tests to reliably trigger edge cases.
+class HttpAuthHandlerMock : public HttpAuthHandler {
+ public:
+ enum Resolve {
+ RESOLVE_INIT,
+ RESOLVE_SKIP,
+ RESOLVE_SYNC,
+ RESOLVE_ASYNC,
+ RESOLVE_TESTED,
+ };
+
+ // The Factory class returns handlers in the order they were added via
+ // AddMockHandler.
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ void AddMockHandler(HttpAuthHandler* handler, HttpAuth::Target target);
+
+ void set_do_init_from_challenge(bool do_init_from_challenge) {
+ do_init_from_challenge_ = do_init_from_challenge;
+ }
+
+ // HttpAuthHandlerFactory:
+ virtual int CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+
+ private:
+ ScopedVector<HttpAuthHandler> handlers_[HttpAuth::AUTH_NUM_TARGETS];
+ bool do_init_from_challenge_;
+ };
+
+ HttpAuthHandlerMock();
+
+ virtual ~HttpAuthHandlerMock();
+
+ void SetResolveExpectation(Resolve resolve);
+
+ virtual bool NeedsCanonicalName();
+
+ virtual int ResolveCanonicalName(HostResolver* host_resolver,
+ const CompletionCallback& callback);
+
+
+ void SetGenerateExpectation(bool async, int rv);
+
+ void set_connection_based(bool connection_based) {
+ connection_based_ = connection_based;
+ }
+
+ void set_allows_default_credentials(bool allows_default_credentials) {
+ allows_default_credentials_ = allows_default_credentials;
+ }
+
+ void set_allows_explicit_credentials(bool allows_explicit_credentials) {
+ allows_explicit_credentials_ = allows_explicit_credentials;
+ }
+
+ const GURL& request_url() const {
+ return request_url_;
+ }
+
+ // HttpAuthHandler:
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+ virtual bool NeedsIdentity() OVERRIDE;
+ virtual bool AllowsDefaultCredentials() OVERRIDE;
+ virtual bool AllowsExplicitCredentials() OVERRIDE;
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE;
+
+ private:
+ void OnResolveCanonicalName();
+
+ void OnGenerateAuthToken();
+
+ Resolve resolve_;
+ CompletionCallback callback_;
+ base::WeakPtrFactory<HttpAuthHandlerMock> weak_factory_;
+ bool generate_async_;
+ int generate_rv_;
+ std::string* auth_token_;
+ bool first_round_;
+ bool connection_based_;
+ bool allows_default_credentials_;
+ bool allows_explicit_credentials_;
+ GURL request_url_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_MOCK_H_
diff --git a/chromium/net/http/http_auth_handler_negotiate.cc b/chromium/net/http/http_auth_handler_negotiate.cc
new file mode 100644
index 00000000000..21f5d65f1f2
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_negotiate.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_handler_negotiate.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/address_family.h"
+#include "net/base/net_errors.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/http/http_auth_filter.h"
+#include "net/http/url_security_manager.h"
+
+namespace net {
+
+HttpAuthHandlerNegotiate::Factory::Factory()
+ : disable_cname_lookup_(false),
+ use_port_(false),
+ resolver_(NULL),
+#if defined(OS_WIN)
+ max_token_length_(0),
+ first_creation_(true),
+#endif
+ is_unsupported_(false) {
+}
+
+HttpAuthHandlerNegotiate::Factory::~Factory() {
+}
+
+void HttpAuthHandlerNegotiate::Factory::set_host_resolver(
+ HostResolver* resolver) {
+ resolver_ = resolver;
+}
+
+int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+#if defined(OS_WIN)
+ if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ if (max_token_length_ == 0) {
+ int rv = DetermineMaxTokenLength(auth_library_.get(), NEGOSSP_NAME,
+ &max_token_length_);
+ if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
+ is_unsupported_ = true;
+ if (rv != OK)
+ return rv;
+ }
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNegotiate(auth_library_.get(), max_token_length_,
+ url_security_manager(), resolver_,
+ disable_cname_lookup_, use_port_));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+#elif defined(OS_POSIX)
+ if (is_unsupported_)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ if (!auth_library_->Init()) {
+ is_unsupported_ = true;
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ }
+ // TODO(ahendrickson): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNegotiate(auth_library_.get(), url_security_manager(),
+ resolver_, disable_cname_lookup_,
+ use_port_));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+#endif
+}
+
+HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(
+ AuthLibrary* auth_library,
+#if defined(OS_WIN)
+ ULONG max_token_length,
+#endif
+ URLSecurityManager* url_security_manager,
+ HostResolver* resolver,
+ bool disable_cname_lookup,
+ bool use_port)
+#if defined(OS_WIN)
+ : auth_system_(auth_library, "Negotiate", NEGOSSP_NAME, max_token_length),
+#elif defined(OS_POSIX)
+ : auth_system_(auth_library, "Negotiate", CHROME_GSS_SPNEGO_MECH_OID_DESC),
+#endif
+ disable_cname_lookup_(disable_cname_lookup),
+ use_port_(use_port),
+ resolver_(resolver),
+ already_called_(false),
+ has_credentials_(false),
+ auth_token_(NULL),
+ next_state_(STATE_NONE),
+ url_security_manager_(url_security_manager) {
+}
+
+HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() {
+}
+
+std::wstring HttpAuthHandlerNegotiate::CreateSPN(
+ const AddressList& address_list, const GURL& origin) {
+ // Kerberos Web Server SPNs are in the form HTTP/<host>:<port> through SSPI,
+ // and in the form HTTP@<host>:<port> through GSSAPI
+ // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx
+ //
+ // However, reality differs from the specification. A good description of
+ // the problems can be found here:
+ // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx
+ //
+ // Typically the <host> portion should be the canonical FQDN for the service.
+ // If this could not be resolved, the original hostname in the URL will be
+ // attempted instead. However, some intranets register SPNs using aliases
+ // for the same canonical DNS name to allow multiple web services to reside
+ // on the same host machine without requiring different ports. IE6 and IE7
+ // have hotpatches that allow the default behavior to be overridden.
+ // http://support.microsoft.com/kb/911149
+ // http://support.microsoft.com/kb/938305
+ //
+ // According to the spec, the <port> option should be included if it is a
+ // non-standard port (i.e. not 80 or 443 in the HTTP case). However,
+ // historically browsers have not included the port, even on non-standard
+ // ports. IE6 required a hotpatch and a registry setting to enable
+ // including non-standard ports, and IE7 and IE8 also require the same
+ // registry setting, but no hotpatch. Firefox does not appear to have an
+ // option to include non-standard ports as of 3.6.
+ // http://support.microsoft.com/kb/908209
+ //
+ // Without any command-line flags, Chrome matches the behavior of Firefox
+ // and IE. Users can override the behavior so aliases are allowed and
+ // non-standard ports are included.
+ int port = origin.EffectiveIntPort();
+ std::string server = address_list.canonical_name();
+ if (server.empty())
+ server = origin.host();
+#if defined(OS_WIN)
+ static const char kSpnSeparator = '/';
+#elif defined(OS_POSIX)
+ static const char kSpnSeparator = '@';
+#endif
+ if (port != 80 && port != 443 && use_port_) {
+ return ASCIIToWide(base::StringPrintf("HTTP%c%s:%d", kSpnSeparator,
+ server.c_str(), port));
+ } else {
+ return ASCIIToWide(base::StringPrintf("HTTP%c%s", kSpnSeparator,
+ server.c_str()));
+ }
+}
+
+HttpAuth::AuthorizationResult HttpAuthHandlerNegotiate::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ return auth_system_.ParseChallenge(challenge);
+}
+
+// Require identity on first pass instead of second.
+bool HttpAuthHandlerNegotiate::NeedsIdentity() {
+ return auth_system_.NeedsIdentity();
+}
+
+bool HttpAuthHandlerNegotiate::AllowsDefaultCredentials() {
+ if (target_ == HttpAuth::AUTH_PROXY)
+ return true;
+ if (!url_security_manager_)
+ return false;
+ return url_security_manager_->CanUseDefaultCredentials(origin_);
+}
+
+bool HttpAuthHandlerNegotiate::AllowsExplicitCredentials() {
+ return auth_system_.AllowsExplicitCredentials();
+}
+
+// The Negotiate challenge header looks like:
+// WWW-Authenticate: NEGOTIATE auth-data
+bool HttpAuthHandlerNegotiate::Init(HttpAuth::ChallengeTokenizer* challenge) {
+#if defined(OS_POSIX)
+ if (!auth_system_.Init()) {
+ VLOG(1) << "can't initialize GSSAPI library";
+ return false;
+ }
+ // GSSAPI does not provide a way to enter username/password to
+ // obtain a TGT. If the default credentials are not allowed for
+ // a particular site (based on whitelist), fall back to a
+ // different scheme.
+ if (!AllowsDefaultCredentials())
+ return false;
+#endif
+ if (CanDelegate())
+ auth_system_.Delegate();
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_NEGOTIATE;
+ score_ = 4;
+ properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+ HttpAuth::AuthorizationResult auth_result =
+ auth_system_.ParseChallenge(challenge);
+ return (auth_result == HttpAuth::AUTHORIZATION_RESULT_ACCEPT);
+}
+
+int HttpAuthHandlerNegotiate::GenerateAuthTokenImpl(
+ const AuthCredentials* credentials, const HttpRequestInfo* request,
+ const CompletionCallback& callback, std::string* auth_token) {
+ DCHECK(callback_.is_null());
+ DCHECK(auth_token_ == NULL);
+ auth_token_ = auth_token;
+ if (already_called_) {
+ DCHECK((!has_credentials_ && credentials == NULL) ||
+ (has_credentials_ && credentials->Equals(credentials_)));
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ } else {
+ already_called_ = true;
+ if (credentials) {
+ has_credentials_ = true;
+ credentials_ = *credentials;
+ }
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME;
+ }
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+void HttpAuthHandlerNegotiate::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+void HttpAuthHandlerNegotiate::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!callback_.is_null());
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(rv);
+}
+
+int HttpAuthHandlerNegotiate::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_RESOLVE_CANONICAL_NAME:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveCanonicalName();
+ break;
+ case STATE_RESOLVE_CANONICAL_NAME_COMPLETE:
+ rv = DoResolveCanonicalNameComplete(rv);
+ break;
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpAuthHandlerNegotiate::DoResolveCanonicalName() {
+ next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE;
+ if (disable_cname_lookup_ || !resolver_)
+ return OK;
+
+ // TODO(cbentzel): Add reverse DNS lookup for numeric addresses.
+ DCHECK(!single_resolve_.get());
+ HostResolver::RequestInfo info(HostPortPair(origin_.host(), 0));
+ info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME);
+ single_resolve_.reset(new SingleRequestHostResolver(resolver_));
+ return single_resolve_->Resolve(
+ info, &address_list_,
+ base::Bind(&HttpAuthHandlerNegotiate::OnIOComplete,
+ base::Unretained(this)),
+ net_log_);
+}
+
+int HttpAuthHandlerNegotiate::DoResolveCanonicalNameComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv != OK) {
+ // Even in the error case, try to use origin_.host instead of
+ // passing the failure on to the caller.
+ VLOG(1) << "Problem finding canonical name for SPN for host "
+ << origin_.host() << ": " << ErrorToString(rv);
+ rv = OK;
+ }
+
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ spn_ = CreateSPN(address_list_, origin_);
+ address_list_ = AddressList();
+ return rv;
+}
+
+int HttpAuthHandlerNegotiate::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ AuthCredentials* credentials = has_credentials_ ? &credentials_ : NULL;
+ // TODO(cbentzel): This should possibly be done async.
+ return auth_system_.GenerateAuthToken(credentials, spn_, auth_token_);
+}
+
+int HttpAuthHandlerNegotiate::DoGenerateAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ auth_token_ = NULL;
+ return rv;
+}
+
+bool HttpAuthHandlerNegotiate::CanDelegate() const {
+ // TODO(cbentzel): Should delegation be allowed on proxies?
+ if (target_ == HttpAuth::AUTH_PROXY)
+ return false;
+ if (!url_security_manager_)
+ return false;
+ return url_security_manager_->CanDelegate(origin_);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_negotiate.h b/chromium/net/http/http_auth_handler_negotiate.h
new file mode 100644
index 00000000000..6fd7aa9a068
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_negotiate.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
+
+#include <string>
+
+#include "build/build_config.h"
+#include "net/base/address_list.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+
+#if defined(OS_WIN)
+#include "net/http/http_auth_sspi_win.h"
+#elif defined(OS_POSIX)
+#include "net/http/http_auth_gssapi_posix.h"
+#endif
+
+namespace net {
+
+class HostResolver;
+class SingleRequestHostResolver;
+class URLSecurityManager;
+
+// Handler for WWW-Authenticate: Negotiate protocol.
+//
+// See http://tools.ietf.org/html/rfc4178 and http://tools.ietf.org/html/rfc4559
+// for more information about the protocol.
+
+class NET_EXPORT_PRIVATE HttpAuthHandlerNegotiate : public HttpAuthHandler {
+ public:
+#if defined(OS_WIN)
+ typedef SSPILibrary AuthLibrary;
+ typedef HttpAuthSSPI AuthSystem;
+#elif defined(OS_POSIX)
+ typedef GSSAPILibrary AuthLibrary;
+ typedef HttpAuthGSSAPI AuthSystem;
+#endif
+
+ class NET_EXPORT_PRIVATE Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ // |disable_cname_lookup()| and |set_disable_cname_lookup()| get/set whether
+ // the auth handlers generated by this factory should skip looking up the
+ // canonical DNS name of the the host that they are authenticating to when
+ // generating the SPN. The default value is false.
+ bool disable_cname_lookup() const { return disable_cname_lookup_; }
+ void set_disable_cname_lookup(bool disable_cname_lookup) {
+ disable_cname_lookup_ = disable_cname_lookup;
+ }
+
+ // |use_port()| and |set_use_port()| get/set whether the auth handlers
+ // generated by this factory should include the port number of the server
+ // they are authenticating to when constructing a Kerberos SPN. The default
+ // value is false.
+ bool use_port() const { return use_port_; }
+ void set_use_port(bool use_port) { use_port_ = use_port; }
+
+ void set_host_resolver(HostResolver* host_resolver);
+
+ // Sets the system library to use, thereby assuming ownership of
+ // |auth_library|.
+ void set_library(AuthLibrary* auth_library) {
+ auth_library_.reset(auth_library);
+ }
+
+ virtual int CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+
+ private:
+ bool disable_cname_lookup_;
+ bool use_port_;
+ HostResolver* resolver_;
+#if defined(OS_WIN)
+ ULONG max_token_length_;
+ bool first_creation_;
+#endif
+ bool is_unsupported_;
+ scoped_ptr<AuthLibrary> auth_library_;
+ };
+
+ HttpAuthHandlerNegotiate(AuthLibrary* sspi_library,
+#if defined(OS_WIN)
+ ULONG max_token_length,
+#endif
+ URLSecurityManager* url_security_manager,
+ HostResolver* host_resolver,
+ bool disable_cname_lookup,
+ bool use_port);
+
+ virtual ~HttpAuthHandlerNegotiate();
+
+ // These are public for unit tests
+ std::wstring CreateSPN(const AddressList& address_list, const GURL& orign);
+ const std::wstring& spn() const { return spn_; }
+
+ // HttpAuthHandler:
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+ virtual bool NeedsIdentity() OVERRIDE;
+ virtual bool AllowsDefaultCredentials() OVERRIDE;
+ virtual bool AllowsExplicitCredentials() OVERRIDE;
+
+ protected:
+ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_RESOLVE_CANONICAL_NAME,
+ STATE_RESOLVE_CANONICAL_NAME_COMPLETE,
+ STATE_GENERATE_AUTH_TOKEN,
+ STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+ STATE_NONE,
+ };
+
+ void OnIOComplete(int result);
+ void DoCallback(int result);
+ int DoLoop(int result);
+
+ int DoResolveCanonicalName();
+ int DoResolveCanonicalNameComplete(int rv);
+ int DoGenerateAuthToken();
+ int DoGenerateAuthTokenComplete(int rv);
+ bool CanDelegate() const;
+
+ AuthSystem auth_system_;
+ bool disable_cname_lookup_;
+ bool use_port_;
+ HostResolver* const resolver_;
+
+ // Members which are needed for DNS lookup + SPN.
+ AddressList address_list_;
+ scoped_ptr<SingleRequestHostResolver> single_resolve_;
+
+ // Things which should be consistent after first call to GenerateAuthToken.
+ bool already_called_;
+ bool has_credentials_;
+ AuthCredentials credentials_;
+ std::wstring spn_;
+
+ // Things which vary each round.
+ CompletionCallback callback_;
+ std::string* auth_token_;
+
+ State next_state_;
+
+ const URLSecurityManager* url_security_manager_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_NEGOTIATE_H_
diff --git a/chromium/net/http/http_auth_handler_negotiate_unittest.cc b/chromium/net/http/http_auth_handler_negotiate_unittest.cc
new file mode 100644
index 00000000000..809f58e1fcc
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_negotiate_unittest.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_handler_negotiate.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_request_info.h"
+#include "net/http/mock_allow_url_security_manager.h"
+#if defined(OS_WIN)
+#include "net/http/mock_sspi_library_win.h"
+#elif defined(OS_POSIX)
+#include "net/http/mock_gssapi_library_posix.h"
+#endif
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+typedef net::MockSSPILibrary MockAuthLibrary;
+#elif defined(OS_POSIX)
+typedef net::test::MockGSSAPILibrary MockAuthLibrary;
+#endif
+
+namespace net {
+
+class HttpAuthHandlerNegotiateTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ auth_library_ = new MockAuthLibrary();
+ resolver_.reset(new MockHostResolver());
+ resolver_->rules()->AddIPLiteralRule("alias", "10.0.0.2",
+ "canonical.example.com");
+
+ url_security_manager_.reset(new MockAllowURLSecurityManager());
+ factory_.reset(new HttpAuthHandlerNegotiate::Factory());
+ factory_->set_url_security_manager(url_security_manager_.get());
+ factory_->set_library(auth_library_);
+ factory_->set_host_resolver(resolver_.get());
+ }
+
+ void SetupMocks(MockAuthLibrary* mock_library) {
+#if defined(OS_WIN)
+ security_package_.reset(new SecPkgInfoW);
+ memset(security_package_.get(), 0x0, sizeof(SecPkgInfoW));
+ security_package_->cbMaxToken = 1337;
+ mock_library->ExpectQuerySecurityPackageInfo(
+ L"Negotiate", SEC_E_OK, security_package_.get());
+#elif defined(OS_POSIX)
+ // Copied from an actual transaction!
+ static const char kAuthResponse[] =
+ "\x60\x82\x02\xCA\x06\x09\x2A\x86\x48\x86\xF7\x12\x01\x02\x02\x01"
+ "\x00\x6E\x82\x02\xB9\x30\x82\x02\xB5\xA0\x03\x02\x01\x05\xA1\x03"
+ "\x02\x01\x0E\xA2\x07\x03\x05\x00\x00\x00\x00\x00\xA3\x82\x01\xC1"
+ "\x61\x82\x01\xBD\x30\x82\x01\xB9\xA0\x03\x02\x01\x05\xA1\x16\x1B"
+ "\x14\x55\x4E\x49\x58\x2E\x43\x4F\x52\x50\x2E\x47\x4F\x4F\x47\x4C"
+ "\x45\x2E\x43\x4F\x4D\xA2\x2C\x30\x2A\xA0\x03\x02\x01\x01\xA1\x23"
+ "\x30\x21\x1B\x04\x68\x6F\x73\x74\x1B\x19\x6E\x69\x6E\x6A\x61\x2E"
+ "\x63\x61\x6D\x2E\x63\x6F\x72\x70\x2E\x67\x6F\x6F\x67\x6C\x65\x2E"
+ "\x63\x6F\x6D\xA3\x82\x01\x6A\x30\x82\x01\x66\xA0\x03\x02\x01\x10"
+ "\xA1\x03\x02\x01\x01\xA2\x82\x01\x58\x04\x82\x01\x54\x2C\xB1\x2B"
+ "\x0A\xA5\xFF\x6F\xEC\xDE\xB0\x19\x6E\x15\x20\x18\x0C\x42\xB3\x2C"
+ "\x4B\xB0\x37\x02\xDE\xD3\x2F\xB4\xBF\xCA\xEC\x0E\xF9\xF3\x45\x6A"
+ "\x43\xF3\x8D\x79\xBD\xCB\xCD\xB2\x2B\xB8\xFC\xD6\xB4\x7F\x09\x48"
+ "\x14\xA7\x4F\xD2\xEE\xBC\x1B\x2F\x18\x3B\x81\x97\x7B\x28\xA4\xAF"
+ "\xA8\xA3\x7A\x31\x1B\xFC\x97\xB6\xBA\x8A\x50\x50\xD7\x44\xB8\x30"
+ "\xA4\x51\x4C\x3A\x95\x6C\xA1\xED\xE2\xEF\x17\xFE\xAB\xD2\xE4\x70"
+ "\xDE\xEB\x7E\x86\x48\xC5\x3E\x19\x5B\x83\x17\xBB\x52\x26\xC0\xF3"
+ "\x38\x0F\xB0\x8C\x72\xC9\xB0\x8B\x99\x96\x18\xE1\x9E\x67\x9D\xDC"
+ "\xF5\x39\x80\x70\x35\x3F\x98\x72\x16\x44\xA2\xC0\x10\xAA\x70\xBD"
+ "\x06\x6F\x83\xB1\xF4\x67\xA4\xBD\xDA\xF7\x79\x1D\x96\xB5\x7E\xF8"
+ "\xC6\xCF\xB4\xD9\x51\xC9\xBB\xB4\x20\x3C\xDD\xB9\x2C\x38\xEA\x40"
+ "\xFB\x02\x6C\xCB\x48\x71\xE8\xF4\x34\x5B\x63\x5D\x13\x57\xBD\xD1"
+ "\x3D\xDE\xE8\x4A\x51\x6E\xBE\x4C\xF5\xA3\x84\xF7\x4C\x4E\x58\x04"
+ "\xBE\xD1\xCC\x22\xA0\x43\xB0\x65\x99\x6A\xE0\x78\x0D\xFC\xE1\x42"
+ "\xA9\x18\xCF\x55\x4D\x23\xBD\x5C\x0D\xB5\x48\x25\x47\xCC\x01\x54"
+ "\x36\x4D\x0C\x6F\xAC\xCD\x33\x21\xC5\x63\x18\x91\x68\x96\xE9\xD1"
+ "\xD8\x23\x1F\x21\xAE\x96\xA3\xBD\x27\xF7\x4B\xEF\x4C\x43\xFF\xF8"
+ "\x22\x57\xCF\x68\x6C\x35\xD5\x21\x48\x5B\x5F\x8F\xA5\xB9\x6F\x99"
+ "\xA6\xE0\x6E\xF0\xC5\x7C\x91\xC8\x0B\x8A\x4B\x4E\x80\x59\x02\xE9"
+ "\xE8\x3F\x87\x04\xA6\xD1\xCA\x26\x3C\xF0\xDA\x57\xFA\xE6\xAF\x25"
+ "\x43\x34\xE1\xA4\x06\x1A\x1C\xF4\xF5\x21\x9C\x00\x98\xDD\xF0\xB4"
+ "\x8E\xA4\x81\xDA\x30\x81\xD7\xA0\x03\x02\x01\x10\xA2\x81\xCF\x04"
+ "\x81\xCC\x20\x39\x34\x60\x19\xF9\x4C\x26\x36\x46\x99\x7A\xFD\x2B"
+ "\x50\x8B\x2D\x47\x72\x38\x20\x43\x0E\x6E\x28\xB3\xA7\x4F\x26\xF1"
+ "\xF1\x7B\x02\x63\x58\x5A\x7F\xC8\xD0\x6E\xF5\xD1\xDA\x28\x43\x1B"
+ "\x6D\x9F\x59\x64\xDE\x90\xEA\x6C\x8C\xA9\x1B\x1E\x92\x29\x24\x23"
+ "\x2C\xE3\xEA\x64\xEF\x91\xA5\x4E\x94\xE1\xDC\x56\x3A\xAF\xD5\xBC"
+ "\xC9\xD3\x9B\x6B\x1F\xBE\x40\xE5\x40\xFF\x5E\x21\xEA\xCE\xFC\xD5"
+ "\xB0\xE5\xBA\x10\x94\xAE\x16\x54\xFC\xEB\xAB\xF1\xD4\x20\x31\xCC"
+ "\x26\xFE\xBE\xFE\x22\xB6\x9B\x1A\xE5\x55\x2C\x93\xB7\x3B\xD6\x4C"
+ "\x35\x35\xC1\x59\x61\xD4\x1F\x2E\x4C\xE1\x72\x8F\x71\x4B\x0C\x39"
+ "\x80\x79\xFA\xCD\xEA\x71\x1B\xAE\x35\x41\xED\xF9\x65\x0C\x59\xF8"
+ "\xE1\x27\xDA\xD6\xD1\x20\x32\xCD\xBF\xD1\xEF\xE2\xED\xAD\x5D\xA7"
+ "\x69\xE3\x55\xF9\x30\xD3\xD4\x08\xC8\xCA\x62\xF8\x64\xEC\x9B\x92"
+ "\x1A\xF1\x03\x2E\xCC\xDC\xEB\x17\xDE\x09\xAC\xA9\x58\x86";
+ test::GssContextMockImpl context1(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::GssContextMockImpl context2(
+ "localhost", // Source name
+ "example.com", // Target name
+ 23, // Lifetime
+ *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 1); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery queries[] = {
+ test::MockGSSAPILibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_CONTINUE_NEEDED, // Major response code
+ 0, // Minor response code
+ context1, // Context
+ NULL, // Expected input token
+ kAuthResponse), // Output token
+ test::MockGSSAPILibrary::SecurityContextQuery(
+ "Negotiate", // Package name
+ GSS_S_COMPLETE, // Major response code
+ 0, // Minor response code
+ context2, // Context
+ kAuthResponse, // Expected input token
+ kAuthResponse) // Output token
+ };
+
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ mock_library->ExpectSecurityContext(queries[i].expected_package,
+ queries[i].response_code,
+ queries[i].minor_response_code,
+ queries[i].context_info,
+ queries[i].expected_input_token,
+ queries[i].output_token);
+ }
+#endif // defined(OS_POSIX)
+ }
+
+#if defined(OS_POSIX)
+ void SetupErrorMocks(MockAuthLibrary* mock_library,
+ int major_status,
+ int minor_status) {
+ const gss_OID_desc kDefaultMech = { 0, NULL };
+ test::GssContextMockImpl context(
+ "localhost", // Source name
+ "example.com", // Target name
+ 0, // Lifetime
+ kDefaultMech, // Mechanism
+ 0, // Context flags
+ 1, // Locally initiated
+ 0); // Open
+ test::MockGSSAPILibrary::SecurityContextQuery query(
+ "Negotiate", // Package name
+ major_status, // Major response code
+ minor_status, // Minor response code
+ context, // Context
+ NULL, // Expected input token
+ NULL); // Output token
+
+ mock_library->ExpectSecurityContext(query.expected_package,
+ query.response_code,
+ query.minor_response_code,
+ query.context_info,
+ query.expected_input_token,
+ query.output_token);
+ }
+
+#endif // defined(OS_POSIX)
+
+ int CreateHandler(bool disable_cname_lookup, bool use_port,
+ bool synchronous_resolve_mode,
+ const std::string& url_string,
+ scoped_ptr<HttpAuthHandlerNegotiate>* handler) {
+ factory_->set_disable_cname_lookup(disable_cname_lookup);
+ factory_->set_use_port(use_port);
+ resolver_->set_synchronous_mode(synchronous_resolve_mode);
+ GURL gurl(url_string);
+
+ // Note: This is a little tricky because CreateAuthHandlerFromString
+ // expects a scoped_ptr<HttpAuthHandler>* rather than a
+ // scoped_ptr<HttpAuthHandlerNegotiate>*. This needs to do the cast
+ // after creating the handler, and make sure that generic_handler
+ // no longer holds on to the HttpAuthHandlerNegotiate object.
+ scoped_ptr<HttpAuthHandler> generic_handler;
+ int rv = factory_->CreateAuthHandlerFromString("Negotiate",
+ HttpAuth::AUTH_SERVER,
+ gurl,
+ BoundNetLog(),
+ &generic_handler);
+ if (rv != OK)
+ return rv;
+ HttpAuthHandlerNegotiate* negotiate_handler =
+ static_cast<HttpAuthHandlerNegotiate*>(generic_handler.release());
+ handler->reset(negotiate_handler);
+ return rv;
+ }
+
+ MockAuthLibrary* AuthLibrary() { return auth_library_; }
+
+ private:
+#if defined(OS_WIN)
+ scoped_ptr<SecPkgInfoW> security_package_;
+#endif
+ // |auth_library_| is passed to |factory_|, which assumes ownership of it.
+ MockAuthLibrary* auth_library_;
+ scoped_ptr<MockHostResolver> resolver_;
+ scoped_ptr<URLSecurityManager> url_security_manager_;
+ scoped_ptr<HttpAuthHandlerNegotiate::Factory> factory_;
+};
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCname) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, false, true, "http://alias:500", &auth_handler));
+
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
+ callback.callback(), &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCnameStandardPort) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, true, true, "http://alias:80", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
+ callback.callback(), &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, DisableCnameNonstandardPort) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ true, true, true, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
+ callback.callback(), &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/alias:500", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@alias:500", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, CnameSync) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, true, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(OK, auth_handler->GenerateAuthToken(NULL, &request_info,
+ callback.callback(), &token));
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/canonical.example.com", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@canonical.example.com", auth_handler->spn());
+#endif
+}
+
+TEST_F(HttpAuthHandlerNegotiateTest, CnameAsync) {
+ SetupMocks(AuthLibrary());
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token));
+ EXPECT_EQ(OK, callback.WaitForResult());
+#if defined(OS_WIN)
+ EXPECT_EQ(L"HTTP/canonical.example.com", auth_handler->spn());
+#elif defined(OS_POSIX)
+ EXPECT_EQ(L"HTTP@canonical.example.com", auth_handler->spn());
+#endif
+}
+
+#if defined(OS_POSIX)
+
+// This test is only for GSSAPI, as we can't use explicit credentials with
+// that library.
+TEST_F(HttpAuthHandlerNegotiateTest, ServerNotInKerberosDatabase) {
+ SetupErrorMocks(AuthLibrary(), GSS_S_FAILURE, 0x96C73A07); // No server
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token));
+ EXPECT_EQ(ERR_MISSING_AUTH_CREDENTIALS, callback.WaitForResult());
+}
+
+// This test is only for GSSAPI, as we can't use explicit credentials with
+// that library.
+TEST_F(HttpAuthHandlerNegotiateTest, NoKerberosCredentials) {
+ SetupErrorMocks(AuthLibrary(), GSS_S_FAILURE, 0x96C73AC3); // No credentials
+ scoped_ptr<HttpAuthHandlerNegotiate> auth_handler;
+ EXPECT_EQ(OK, CreateHandler(
+ false, false, false, "http://alias:500", &auth_handler));
+ ASSERT_TRUE(auth_handler.get() != NULL);
+ TestCompletionCallback callback;
+ HttpRequestInfo request_info;
+ std::string token;
+ EXPECT_EQ(ERR_IO_PENDING, auth_handler->GenerateAuthToken(
+ NULL, &request_info, callback.callback(), &token));
+ EXPECT_EQ(ERR_MISSING_AUTH_CREDENTIALS, callback.WaitForResult());
+}
+
+#if defined(DLOPEN_KERBEROS)
+TEST_F(HttpAuthHandlerNegotiateTest, MissingGSSAPI) {
+ scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
+ MockAllowURLSecurityManager url_security_manager;
+ scoped_ptr<HttpAuthHandlerNegotiate::Factory> negotiate_factory(
+ new HttpAuthHandlerNegotiate::Factory());
+ negotiate_factory->set_host_resolver(host_resolver.get());
+ negotiate_factory->set_url_security_manager(&url_security_manager);
+ negotiate_factory->set_library(
+ new GSSAPISharedLibrary("/this/library/does/not/exist"));
+
+ GURL gurl("http://www.example.com");
+ scoped_ptr<HttpAuthHandler> generic_handler;
+ int rv = negotiate_factory->CreateAuthHandlerFromString(
+ "Negotiate",
+ HttpAuth::AUTH_SERVER,
+ gurl,
+ BoundNetLog(),
+ &generic_handler);
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ EXPECT_TRUE(generic_handler.get() == NULL);
+}
+#endif // defined(DLOPEN_KERBEROS)
+
+#endif // defined(OS_POSIX)
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_ntlm.cc b/chromium/net/http/http_auth_handler_ntlm.cc
new file mode 100644
index 00000000000..4c04234e22e
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_ntlm.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2011 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/http/http_auth_handler_ntlm.h"
+
+#if !defined(NTLM_SSPI)
+#include "base/base64.h"
+#endif
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) {
+ return ParseChallenge(challenge, false);
+}
+
+bool HttpAuthHandlerNTLM::Init(HttpAuth::ChallengeTokenizer* tok) {
+ auth_scheme_ = HttpAuth::AUTH_SCHEME_NTLM;
+ score_ = 3;
+ properties_ = ENCRYPTS_IDENTITY | IS_CONNECTION_BASED;
+
+ return ParseChallenge(tok, true) == HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+int HttpAuthHandlerNTLM::GenerateAuthTokenImpl(
+ const AuthCredentials* credentials, const HttpRequestInfo* request,
+ const CompletionCallback& callback, std::string* auth_token) {
+#if defined(NTLM_SSPI)
+ return auth_sspi_.GenerateAuthToken(
+ credentials,
+ CreateSPN(origin_),
+ auth_token);
+#else // !defined(NTLM_SSPI)
+ // TODO(cbentzel): Shouldn't be hitting this case.
+ if (!credentials) {
+ LOG(ERROR) << "Username and password are expected to be non-NULL.";
+ return ERR_MISSING_AUTH_CREDENTIALS;
+ }
+ // TODO(wtc): See if we can use char* instead of void* for in_buf and
+ // out_buf. This change will need to propagate to GetNextToken,
+ // GenerateType1Msg, and GenerateType3Msg, and perhaps further.
+ const void* in_buf;
+ void* out_buf;
+ uint32 in_buf_len, out_buf_len;
+ std::string decoded_auth_data;
+
+ // The username may be in the form "DOMAIN\user". Parse it into the two
+ // components.
+ base::string16 domain;
+ base::string16 user;
+ const base::string16& username = credentials->username();
+ const base::char16 backslash_character = '\\';
+ size_t backslash_idx = username.find(backslash_character);
+ if (backslash_idx == base::string16::npos) {
+ user = username;
+ } else {
+ domain = username.substr(0, backslash_idx);
+ user = username.substr(backslash_idx + 1);
+ }
+ domain_ = domain;
+ credentials_.Set(user, credentials->password());
+
+ // Initial challenge.
+ if (auth_data_.empty()) {
+ in_buf_len = 0;
+ in_buf = NULL;
+ int rv = InitializeBeforeFirstChallenge();
+ if (rv != OK)
+ return rv;
+ } else {
+ if (!base::Base64Decode(auth_data_, &decoded_auth_data)) {
+ LOG(ERROR) << "Unexpected problem Base64 decoding.";
+ return ERR_UNEXPECTED;
+ }
+ in_buf_len = decoded_auth_data.length();
+ in_buf = decoded_auth_data.data();
+ }
+
+ int rv = GetNextToken(in_buf, in_buf_len, &out_buf, &out_buf_len);
+ if (rv != OK)
+ return rv;
+
+ // Base64 encode data in output buffer and prepend "NTLM ".
+ std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
+ std::string encode_output;
+ bool base64_rv = base::Base64Encode(encode_input, &encode_output);
+ // OK, we are done with |out_buf|
+ free(out_buf);
+ if (!base64_rv) {
+ LOG(ERROR) << "Unexpected problem Base64 encoding.";
+ return ERR_UNEXPECTED;
+ }
+ *auth_token = std::string("NTLM ") + encode_output;
+ return OK;
+#endif
+}
+
+// The NTLM challenge header looks like:
+// WWW-Authenticate: NTLM auth-data
+HttpAuth::AuthorizationResult HttpAuthHandlerNTLM::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok, bool initial_challenge) {
+#if defined(NTLM_SSPI)
+ // auth_sspi_ contains state for whether or not this is the initial challenge.
+ return auth_sspi_.ParseChallenge(tok);
+#else
+ // TODO(cbentzel): Most of the logic between SSPI, GSSAPI, and portable NTLM
+ // authentication parsing could probably be shared - just need to know if
+ // there was previously a challenge round.
+ // TODO(cbentzel): Write a test case to validate that auth_data_ is left empty
+ // in all failure conditions.
+ auth_data_.clear();
+
+ // Verify the challenge's auth-scheme.
+ if (!LowerCaseEqualsASCII(tok->scheme(), "ntlm"))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ std::string base64_param = tok->base64_param();
+ if (base64_param.empty()) {
+ if (!initial_challenge)
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ if (initial_challenge)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ }
+
+ auth_data_ = base64_param;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+#endif // defined(NTLM_SSPI)
+}
+
+// static
+std::wstring HttpAuthHandlerNTLM::CreateSPN(const GURL& origin) {
+ // The service principal name of the destination server. See
+ // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx
+ std::wstring target(L"HTTP/");
+ target.append(ASCIIToWide(GetHostAndPort(origin)));
+ return target;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_ntlm.h b/chromium/net/http/http_auth_handler_ntlm.h
new file mode 100644
index 00000000000..971dd1fc06f
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_ntlm.h
@@ -0,0 +1,173 @@
+// Copyright (c) 2011 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_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
+#define NET_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
+
+#include "build/build_config.h"
+
+// This contains the portable and the SSPI implementations for NTLM.
+// We use NTLM_SSPI for Windows, and NTLM_PORTABLE for other platforms.
+#if defined(OS_WIN)
+#define NTLM_SSPI
+#else
+#define NTLM_PORTABLE
+#endif
+
+#if defined(NTLM_SSPI)
+#define SECURITY_WIN32 1
+#include <windows.h>
+#include <security.h>
+#include "net/http/http_auth_sspi_win.h"
+#endif
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+
+namespace net {
+
+class URLSecurityManager;
+
+// Code for handling HTTP NTLM authentication.
+class NET_EXPORT_PRIVATE HttpAuthHandlerNTLM : public HttpAuthHandler {
+ public:
+ class Factory : public HttpAuthHandlerFactory {
+ public:
+ Factory();
+ virtual ~Factory();
+
+ virtual int CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) OVERRIDE;
+#if defined(NTLM_SSPI)
+ // Set the SSPILibrary to use. Typically the only callers which need to use
+ // this are unit tests which pass in a mocked-out version of the SSPI
+ // library. After the call |sspi_library| will be owned by this Factory and
+ // will be destroyed when the Factory is destroyed.
+ void set_sspi_library(SSPILibrary* sspi_library) {
+ sspi_library_.reset(sspi_library);
+ }
+#endif // defined(NTLM_SSPI)
+ private:
+#if defined(NTLM_SSPI)
+ ULONG max_token_length_;
+ bool first_creation_;
+ bool is_unsupported_;
+ scoped_ptr<SSPILibrary> sspi_library_;
+#endif // defined(NTLM_SSPI)
+ };
+
+#if defined(NTLM_PORTABLE)
+ // A function that generates n random bytes in the output buffer.
+ typedef void (*GenerateRandomProc)(uint8* output, size_t n);
+
+ // A function that returns the local host name. Returns an empty string if
+ // the local host name is not available.
+ typedef std::string (*HostNameProc)();
+
+ // For unit tests to override and restore the GenerateRandom and
+ // GetHostName functions.
+ class ScopedProcSetter {
+ public:
+ ScopedProcSetter(GenerateRandomProc random_proc,
+ HostNameProc host_name_proc) {
+ old_random_proc_ = SetGenerateRandomProc(random_proc);
+ old_host_name_proc_ = SetHostNameProc(host_name_proc);
+ }
+
+ ~ScopedProcSetter() {
+ SetGenerateRandomProc(old_random_proc_);
+ SetHostNameProc(old_host_name_proc_);
+ }
+
+ private:
+ GenerateRandomProc old_random_proc_;
+ HostNameProc old_host_name_proc_;
+ };
+#endif
+
+#if defined(NTLM_PORTABLE)
+ HttpAuthHandlerNTLM();
+#endif
+#if defined(NTLM_SSPI)
+ HttpAuthHandlerNTLM(SSPILibrary* sspi_library, ULONG max_token_length,
+ URLSecurityManager* url_security_manager);
+#endif
+
+ virtual bool NeedsIdentity() OVERRIDE;
+
+ virtual bool AllowsDefaultCredentials() OVERRIDE;
+
+ virtual HttpAuth::AuthorizationResult HandleAnotherChallenge(
+ HttpAuth::ChallengeTokenizer* challenge) OVERRIDE;
+
+ protected:
+ // This function acquires a credentials handle in the SSPI implementation.
+ // It does nothing in the portable implementation.
+ int InitializeBeforeFirstChallenge();
+
+ virtual bool Init(HttpAuth::ChallengeTokenizer* tok) OVERRIDE;
+
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE;
+
+ private:
+ virtual ~HttpAuthHandlerNTLM();
+
+#if defined(NTLM_PORTABLE)
+ // For unit tests to override the GenerateRandom and GetHostName functions.
+ // Returns the old function.
+ static GenerateRandomProc SetGenerateRandomProc(GenerateRandomProc proc);
+ static HostNameProc SetHostNameProc(HostNameProc proc);
+#endif
+
+ // Parse the challenge, saving the results into this instance.
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok, bool initial_challenge);
+
+ // Given an input token received from the server, generate the next output
+ // token to be sent to the server.
+ int GetNextToken(const void* in_token,
+ uint32 in_token_len,
+ void** out_token,
+ uint32* out_token_len);
+
+ // Create an NTLM SPN to identify the |origin| server.
+ static std::wstring CreateSPN(const GURL& origin);
+
+#if defined(NTLM_SSPI)
+ HttpAuthSSPI auth_sspi_;
+#endif
+
+#if defined(NTLM_PORTABLE)
+ static GenerateRandomProc generate_random_proc_;
+ static HostNameProc get_host_name_proc_;
+#endif
+
+ base::string16 domain_;
+ AuthCredentials credentials_;
+
+ // The base64-encoded string following "NTLM" in the "WWW-Authenticate" or
+ // "Proxy-Authenticate" response header.
+ std::string auth_data_;
+
+#if defined(NTLM_SSPI)
+ URLSecurityManager* url_security_manager_;
+#endif
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_HANDLER_NTLM_H_
diff --git a/chromium/net/http/http_auth_handler_ntlm_portable.cc b/chromium/net/http/http_auth_handler_ntlm_portable.cc
new file mode 100644
index 00000000000..d1fbf23c59f
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_ntlm_portable.cc
@@ -0,0 +1,730 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_handler_ntlm.h"
+
+#include <stdlib.h>
+// For gethostname
+#if defined(OS_POSIX)
+#include <unistd.h>
+#elif defined(OS_WIN)
+#include <winsock2.h>
+#endif
+
+#include "base/md5.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/zap.h"
+#include "net/http/des.h"
+#include "net/http/md4.h"
+
+namespace net {
+
+// Based on mozilla/security/manager/ssl/src/nsNTLMAuthModule.cpp,
+// CVS rev. 1.14.
+//
+// TODO(wtc):
+// - The IS_BIG_ENDIAN code is not tested.
+// - Enable the logging code or just delete it.
+// - Delete or comment out the LM code, which hasn't been tested and isn't
+// being used.
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Discover the endianness by testing processor architecture.
+#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_X86_64)\
+ || defined(ARCH_CPU_ARMEL) || defined(ARCH_CPU_MIPSEL)
+#define IS_LITTLE_ENDIAN 1
+#undef IS_BIG_ENDIAN
+#elif defined(ARCH_CPU_MIPSEB)
+#define IS_BIG_ENDIAN 1
+#undef IS_LITTLE_ENDIAN
+#else
+#error "Unknown endianness"
+#endif
+
+#define NTLM_LOG(x) ((void) 0)
+
+//-----------------------------------------------------------------------------
+// This file contains a cross-platform NTLM authentication implementation. It
+// is based on documentation from: http://davenport.sourceforge.net/ntlm.html
+//-----------------------------------------------------------------------------
+
+enum {
+ NTLM_NegotiateUnicode = 0x00000001,
+ NTLM_NegotiateOEM = 0x00000002,
+ NTLM_RequestTarget = 0x00000004,
+ NTLM_Unknown1 = 0x00000008,
+ NTLM_NegotiateSign = 0x00000010,
+ NTLM_NegotiateSeal = 0x00000020,
+ NTLM_NegotiateDatagramStyle = 0x00000040,
+ NTLM_NegotiateLanManagerKey = 0x00000080,
+ NTLM_NegotiateNetware = 0x00000100,
+ NTLM_NegotiateNTLMKey = 0x00000200,
+ NTLM_Unknown2 = 0x00000400,
+ NTLM_Unknown3 = 0x00000800,
+ NTLM_NegotiateDomainSupplied = 0x00001000,
+ NTLM_NegotiateWorkstationSupplied = 0x00002000,
+ NTLM_NegotiateLocalCall = 0x00004000,
+ NTLM_NegotiateAlwaysSign = 0x00008000,
+ NTLM_TargetTypeDomain = 0x00010000,
+ NTLM_TargetTypeServer = 0x00020000,
+ NTLM_TargetTypeShare = 0x00040000,
+ NTLM_NegotiateNTLM2Key = 0x00080000,
+ NTLM_RequestInitResponse = 0x00100000,
+ NTLM_RequestAcceptResponse = 0x00200000,
+ NTLM_RequestNonNTSessionKey = 0x00400000,
+ NTLM_NegotiateTargetInfo = 0x00800000,
+ NTLM_Unknown4 = 0x01000000,
+ NTLM_Unknown5 = 0x02000000,
+ NTLM_Unknown6 = 0x04000000,
+ NTLM_Unknown7 = 0x08000000,
+ NTLM_Unknown8 = 0x10000000,
+ NTLM_Negotiate128 = 0x20000000,
+ NTLM_NegotiateKeyExchange = 0x40000000,
+ NTLM_Negotiate56 = 0x80000000
+};
+
+// We send these flags with our type 1 message.
+enum {
+ NTLM_TYPE1_FLAGS = (NTLM_NegotiateUnicode |
+ NTLM_NegotiateOEM |
+ NTLM_RequestTarget |
+ NTLM_NegotiateNTLMKey |
+ NTLM_NegotiateAlwaysSign |
+ NTLM_NegotiateNTLM2Key)
+};
+
+static const char NTLM_SIGNATURE[] = "NTLMSSP";
+static const char NTLM_TYPE1_MARKER[] = { 0x01, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE2_MARKER[] = { 0x02, 0x00, 0x00, 0x00 };
+static const char NTLM_TYPE3_MARKER[] = { 0x03, 0x00, 0x00, 0x00 };
+
+enum {
+ NTLM_TYPE1_HEADER_LEN = 32,
+ NTLM_TYPE2_HEADER_LEN = 32,
+ NTLM_TYPE3_HEADER_LEN = 64,
+
+ LM_HASH_LEN = 16,
+ LM_RESP_LEN = 24,
+
+ NTLM_HASH_LEN = 16,
+ NTLM_RESP_LEN = 24
+};
+
+//-----------------------------------------------------------------------------
+
+// The return value of this function controls whether or not the LM hash will
+// be included in response to a NTLM challenge.
+//
+// In Mozilla, this function returns the value of the boolean preference
+// "network.ntlm.send-lm-response". By default, the preference is disabled
+// since servers should almost never need the LM hash, and the LM hash is what
+// makes NTLM authentication less secure. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=250691 for further details.
+//
+// We just return a hardcoded false.
+static bool SendLM() {
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+#define LogFlags(x) ((void) 0)
+#define LogBuf(a, b, c) ((void) 0)
+#define LogToken(a, b, c) ((void) 0)
+
+//-----------------------------------------------------------------------------
+
+// Byte order swapping.
+#define SWAP16(x) ((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))
+#define SWAP32(x) ((SWAP16((x) & 0xffff) << 16) | (SWAP16((x) >> 16)))
+
+static void* WriteBytes(void* buf, const void* data, uint32 data_len) {
+ memcpy(buf, data, data_len);
+ return static_cast<char*>(buf) + data_len;
+}
+
+static void* WriteDWORD(void* buf, uint32 dword) {
+#ifdef IS_BIG_ENDIAN
+ // NTLM uses little endian on the wire.
+ dword = SWAP32(dword);
+#endif
+ return WriteBytes(buf, &dword, sizeof(dword));
+}
+
+static void* WriteSecBuf(void* buf, uint16 length, uint32 offset) {
+#ifdef IS_BIG_ENDIAN
+ length = SWAP16(length);
+ offset = SWAP32(offset);
+#endif
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &length, sizeof(length));
+ buf = WriteBytes(buf, &offset, sizeof(offset));
+ return buf;
+}
+
+#ifdef IS_BIG_ENDIAN
+/**
+ * WriteUnicodeLE copies a unicode string from one buffer to another. The
+ * resulting unicode string is in little-endian format. The input string is
+ * assumed to be in the native endianness of the local machine. It is safe
+ * to pass the same buffer as both input and output, which is a handy way to
+ * convert the unicode buffer to little-endian on big-endian platforms.
+ */
+static void* WriteUnicodeLE(void* buf, const char16* str, uint32 str_len) {
+ // Convert input string from BE to LE.
+ uint8* cursor = static_cast<uint8*>(buf);
+ const uint8* input = reinterpret_cast<const uint8*>(str);
+ for (uint32 i = 0; i < str_len; ++i, input += 2, cursor += 2) {
+ // Allow for the case where |buf == str|.
+ uint8 temp = input[0];
+ cursor[0] = input[1];
+ cursor[1] = temp;
+ }
+ return buf;
+}
+#endif
+
+static uint16 ReadUint16(const uint8*& buf) {
+ uint16 x = (static_cast<uint16>(buf[0])) |
+ (static_cast<uint16>(buf[1]) << 8);
+ buf += sizeof(x);
+ return x;
+}
+
+static uint32 ReadUint32(const uint8*& buf) {
+ uint32 x = (static_cast<uint32>(buf[0])) |
+ (static_cast<uint32>(buf[1]) << 8) |
+ (static_cast<uint32>(buf[2]) << 16) |
+ (static_cast<uint32>(buf[3]) << 24);
+ buf += sizeof(x);
+ return x;
+}
+
+//-----------------------------------------------------------------------------
+
+// LM_Hash computes the LM hash of the given password.
+//
+// param password
+// unicode password.
+// param hash
+// 16-byte result buffer
+//
+// Note: This function is not being used because our SendLM() function always
+// returns false.
+static void LM_Hash(const base::string16& password, uint8* hash) {
+ static const uint8 LM_MAGIC[] = "KGS!@#$%";
+
+ // Convert password to OEM character set. We'll just use the native
+ // filesystem charset.
+ std::string passbuf = base::SysWideToNativeMB(UTF16ToWide(password));
+ StringToUpperASCII(&passbuf);
+ passbuf.resize(14, '\0');
+
+ uint8 k1[8], k2[8];
+ DESMakeKey(reinterpret_cast<const uint8*>(passbuf.data()) , k1);
+ DESMakeKey(reinterpret_cast<const uint8*>(passbuf.data()) + 7, k2);
+ ZapString(&passbuf);
+
+ // Use password keys to hash LM magic string twice.
+ DESEncrypt(k1, LM_MAGIC, hash);
+ DESEncrypt(k2, LM_MAGIC, hash + 8);
+}
+
+// NTLM_Hash computes the NTLM hash of the given password.
+//
+// param password
+// null-terminated unicode password.
+// param hash
+// 16-byte result buffer
+static void NTLM_Hash(const base::string16& password, uint8* hash) {
+#ifdef IS_BIG_ENDIAN
+ uint32 len = password.length();
+ uint8* passbuf;
+
+ passbuf = static_cast<uint8*>(malloc(len * 2));
+ WriteUnicodeLE(passbuf, password.data(), len);
+ weak_crypto::MD4Sum(passbuf, len * 2, hash);
+
+ ZapBuf(passbuf, len * 2);
+ free(passbuf);
+#else
+ weak_crypto::MD4Sum(reinterpret_cast<const uint8*>(password.data()),
+ password.length() * 2, hash);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+// LM_Response generates the LM response given a 16-byte password hash and the
+// challenge from the Type-2 message.
+//
+// param hash
+// 16-byte password hash
+// param challenge
+// 8-byte challenge from Type-2 message
+// param response
+// 24-byte buffer to contain the LM response upon return
+static void LM_Response(const uint8* hash,
+ const uint8* challenge,
+ uint8* response) {
+ uint8 keybytes[21], k1[8], k2[8], k3[8];
+
+ memcpy(keybytes, hash, 16);
+ ZapBuf(keybytes + 16, 5);
+
+ DESMakeKey(keybytes , k1);
+ DESMakeKey(keybytes + 7, k2);
+ DESMakeKey(keybytes + 14, k3);
+
+ DESEncrypt(k1, challenge, response);
+ DESEncrypt(k2, challenge, response + 8);
+ DESEncrypt(k3, challenge, response + 16);
+}
+
+//-----------------------------------------------------------------------------
+
+// Returns OK or a network error code.
+static int GenerateType1Msg(void** out_buf, uint32* out_len) {
+ //
+ // Verify that buf_len is sufficient.
+ //
+ *out_len = NTLM_TYPE1_HEADER_LEN;
+ *out_buf = malloc(*out_len);
+ if (!*out_buf)
+ return ERR_OUT_OF_MEMORY;
+
+ //
+ // Write out type 1 message.
+ //
+ void* cursor = *out_buf;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE1_MARKER, sizeof(NTLM_TYPE1_MARKER));
+
+ // 12 : flags
+ cursor = WriteDWORD(cursor, NTLM_TYPE1_FLAGS);
+
+ //
+ // NOTE: It is common for the domain and workstation fields to be empty.
+ // This is true of Win2k clients, and my guess is that there is
+ // little utility to sending these strings before the charset has
+ // been negotiated. We follow suite -- anyways, it doesn't hurt
+ // to save some bytes on the wire ;-)
+ //
+
+ // 16 : supplied domain security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 24 : supplied workstation security buffer (empty)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ return OK;
+}
+
+struct Type2Msg {
+ uint32 flags; // NTLM_Xxx bitwise combination
+ uint8 challenge[8]; // 8 byte challenge
+ const void* target; // target string (type depends on flags)
+ uint32 target_len; // target length in bytes
+};
+
+// Returns OK or a network error code.
+// TODO(wtc): This function returns ERR_UNEXPECTED when the input message is
+// invalid. We should return a better error code.
+static int ParseType2Msg(const void* in_buf, uint32 in_len, Type2Msg* msg) {
+ // Make sure in_buf is long enough to contain a meaningful type2 msg.
+ //
+ // 0 NTLMSSP Signature
+ // 8 NTLM Message Type
+ // 12 Target Name
+ // 20 Flags
+ // 24 Challenge
+ // 32 end of header, start of optional data blocks
+ //
+ if (in_len < NTLM_TYPE2_HEADER_LEN)
+ return ERR_UNEXPECTED;
+
+ const uint8* cursor = (const uint8*) in_buf;
+
+ // verify NTLMSSP signature
+ if (memcmp(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)) != 0)
+ return ERR_UNEXPECTED;
+ cursor += sizeof(NTLM_SIGNATURE);
+
+ // verify Type-2 marker
+ if (memcmp(cursor, NTLM_TYPE2_MARKER, sizeof(NTLM_TYPE2_MARKER)) != 0)
+ return ERR_UNEXPECTED;
+ cursor += sizeof(NTLM_TYPE2_MARKER);
+
+ // read target name security buffer
+ uint32 target_len = ReadUint16(cursor);
+ ReadUint16(cursor); // discard next 16-bit value
+ uint32 offset = ReadUint32(cursor); // get offset from in_buf
+ msg->target_len = 0;
+ msg->target = NULL;
+ // Check the offset / length combo is in range of the input buffer, including
+ // integer overflow checking.
+ if (offset + target_len > offset && offset + target_len <= in_len) {
+ msg->target_len = target_len;
+ msg->target = ((const uint8*) in_buf) + offset;
+ }
+
+ // read flags
+ msg->flags = ReadUint32(cursor);
+
+ // read challenge
+ memcpy(msg->challenge, cursor, sizeof(msg->challenge));
+ cursor += sizeof(msg->challenge);
+
+ NTLM_LOG(("NTLM type 2 message:\n"));
+ LogBuf("target", (const uint8*) msg->target, msg->target_len);
+ LogBuf("flags", (const uint8*) &msg->flags, 4);
+ LogFlags(msg->flags);
+ LogBuf("challenge", msg->challenge, sizeof(msg->challenge));
+
+ // We currently do not implement LMv2/NTLMv2 or NTLM2 responses,
+ // so we can ignore target information. We may want to enable
+ // support for these alternate mechanisms in the future.
+ return OK;
+}
+
+static void GenerateRandom(uint8* output, size_t n) {
+ for (size_t i = 0; i < n; ++i)
+ output[i] = base::RandInt(0, 255);
+}
+
+// Returns OK or a network error code.
+static int GenerateType3Msg(const base::string16& domain,
+ const base::string16& username,
+ const base::string16& password,
+ const std::string& hostname,
+ const void* rand_8_bytes,
+ const void* in_buf,
+ uint32 in_len,
+ void** out_buf,
+ uint32* out_len) {
+ // in_buf contains Type-2 msg (the challenge) from server.
+
+ int rv;
+ Type2Msg msg;
+
+ rv = ParseType2Msg(in_buf, in_len, &msg);
+ if (rv != OK)
+ return rv;
+
+ bool unicode = (msg.flags & NTLM_NegotiateUnicode) != 0;
+
+ // Temporary buffers for unicode strings
+#ifdef IS_BIG_ENDIAN
+ base::string16 ucs_domain_buf, ucs_user_buf;
+#endif
+ base::string16 ucs_host_buf;
+ // Temporary buffers for oem strings
+ std::string oem_domain_buf, oem_user_buf;
+ // Pointers and lengths for the string buffers; encoding is unicode if
+ // the "negotiate unicode" flag was set in the Type-2 message.
+ const void* domain_ptr;
+ const void* user_ptr;
+ const void* host_ptr;
+ uint32 domain_len, user_len, host_len;
+
+ //
+ // Get domain name.
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucs_domain_buf = domain;
+ domain_ptr = ucs_domain_buf.data();
+ domain_len = ucs_domain_buf.length() * 2;
+ WriteUnicodeLE(const_cast<void*>(domain_ptr), (const char16*) domain_ptr,
+ ucs_domain_buf.length());
+#else
+ domain_ptr = domain.data();
+ domain_len = domain.length() * 2;
+#endif
+ } else {
+ oem_domain_buf = base::SysWideToNativeMB(UTF16ToWide(domain));
+ domain_ptr = oem_domain_buf.data();
+ domain_len = oem_domain_buf.length();
+ }
+
+ //
+ // Get user name.
+ //
+ if (unicode) {
+#ifdef IS_BIG_ENDIAN
+ ucs_user_buf = username;
+ user_ptr = ucs_user_buf.data();
+ user_len = ucs_user_buf.length() * 2;
+ WriteUnicodeLE(const_cast<void*>(user_ptr), (const char16*) user_ptr,
+ ucs_user_buf.length());
+#else
+ user_ptr = username.data();
+ user_len = username.length() * 2;
+#endif
+ } else {
+ oem_user_buf = base::SysWideToNativeMB(UTF16ToWide(username));
+ user_ptr = oem_user_buf.data();
+ user_len = oem_user_buf.length();
+ }
+
+ //
+ // Get workstation name (use local machine's hostname).
+ //
+ if (unicode) {
+ // hostname is ASCII, so we can do a simple zero-pad expansion:
+ ucs_host_buf.assign(hostname.begin(), hostname.end());
+ host_ptr = ucs_host_buf.data();
+ host_len = ucs_host_buf.length() * 2;
+#ifdef IS_BIG_ENDIAN
+ WriteUnicodeLE(const_cast<void*>(host_ptr), (const char16*) host_ptr,
+ ucs_host_buf.length());
+#endif
+ } else {
+ host_ptr = hostname.data();
+ host_len = hostname.length();
+ }
+
+ //
+ // Now that we have generated all of the strings, we can allocate out_buf.
+ //
+ *out_len = NTLM_TYPE3_HEADER_LEN + host_len + domain_len + user_len +
+ LM_RESP_LEN + NTLM_RESP_LEN;
+ *out_buf = malloc(*out_len);
+ if (!*out_buf)
+ return ERR_OUT_OF_MEMORY;
+
+ //
+ // Next, we compute the LM and NTLM responses.
+ //
+ uint8 lm_resp[LM_RESP_LEN];
+ uint8 ntlm_resp[NTLM_RESP_LEN];
+ uint8 ntlm_hash[NTLM_HASH_LEN];
+ if (msg.flags & NTLM_NegotiateNTLM2Key) {
+ // compute NTLM2 session response
+ base::MD5Digest session_hash;
+ uint8 temp[16];
+
+ memcpy(lm_resp, rand_8_bytes, 8);
+ memset(lm_resp + 8, 0, LM_RESP_LEN - 8);
+
+ memcpy(temp, msg.challenge, 8);
+ memcpy(temp + 8, lm_resp, 8);
+ base::MD5Sum(temp, 16, &session_hash);
+
+ NTLM_Hash(password, ntlm_hash);
+ LM_Response(ntlm_hash, session_hash.a, ntlm_resp);
+ } else {
+ NTLM_Hash(password, ntlm_hash);
+ LM_Response(ntlm_hash, msg.challenge, ntlm_resp);
+
+ if (SendLM()) {
+ uint8 lm_hash[LM_HASH_LEN];
+ LM_Hash(password, lm_hash);
+ LM_Response(lm_hash, msg.challenge, lm_resp);
+ } else {
+ // According to http://davenport.sourceforge.net/ntlm.html#ntlmVersion2,
+ // the correct way to not send the LM hash is to send the NTLM hash twice
+ // in both the LM and NTLM response fields.
+ LM_Response(ntlm_hash, msg.challenge, lm_resp);
+ }
+ }
+
+ //
+ // Finally, we assemble the Type-3 msg :-)
+ //
+ void* cursor = *out_buf;
+ uint32 offset;
+
+ // 0 : signature
+ cursor = WriteBytes(cursor, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE));
+
+ // 8 : marker
+ cursor = WriteBytes(cursor, NTLM_TYPE3_MARKER, sizeof(NTLM_TYPE3_MARKER));
+
+ // 12 : LM response sec buf
+ offset = NTLM_TYPE3_HEADER_LEN + domain_len + user_len + host_len;
+ cursor = WriteSecBuf(cursor, LM_RESP_LEN, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, lm_resp, LM_RESP_LEN);
+
+ // 20 : NTLM response sec buf
+ offset += LM_RESP_LEN;
+ cursor = WriteSecBuf(cursor, NTLM_RESP_LEN, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, ntlm_resp, NTLM_RESP_LEN);
+
+ // 28 : domain name sec buf
+ offset = NTLM_TYPE3_HEADER_LEN;
+ cursor = WriteSecBuf(cursor, domain_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, domain_ptr, domain_len);
+
+ // 36 : user name sec buf
+ offset += domain_len;
+ cursor = WriteSecBuf(cursor, user_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, user_ptr, user_len);
+
+ // 44 : workstation (host) name sec buf
+ offset += user_len;
+ cursor = WriteSecBuf(cursor, host_len, offset);
+ memcpy(static_cast<uint8*>(*out_buf) + offset, host_ptr, host_len);
+
+ // 52 : session key sec buf (not used)
+ cursor = WriteSecBuf(cursor, 0, 0);
+
+ // 60 : negotiated flags
+ cursor = WriteDWORD(cursor, msg.flags & NTLM_TYPE1_FLAGS);
+
+ return OK;
+}
+
+// NTLM authentication is specified in "NTLM Over HTTP Protocol Specification"
+// [MS-NTHT].
+
+// static
+HttpAuthHandlerNTLM::GenerateRandomProc
+HttpAuthHandlerNTLM::generate_random_proc_ = GenerateRandom;
+
+// static
+HttpAuthHandlerNTLM::HostNameProc
+HttpAuthHandlerNTLM::get_host_name_proc_ = GetHostName;
+
+HttpAuthHandlerNTLM::HttpAuthHandlerNTLM() {
+}
+
+bool HttpAuthHandlerNTLM::NeedsIdentity() {
+ // This gets called for each round-trip. Only require identity on
+ // the first call (when auth_data_ is empty). On subsequent calls,
+ // we use the initially established identity.
+ return auth_data_.empty();
+}
+
+bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
+ // Default credentials are not supported in the portable implementation of
+ // NTLM, but are supported in the SSPI implementation.
+ return false;
+}
+
+int HttpAuthHandlerNTLM::InitializeBeforeFirstChallenge() {
+ return OK;
+}
+
+HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() {
+ credentials_.Zap();
+}
+
+// static
+HttpAuthHandlerNTLM::GenerateRandomProc
+HttpAuthHandlerNTLM::SetGenerateRandomProc(
+ GenerateRandomProc proc) {
+ GenerateRandomProc old_proc = generate_random_proc_;
+ generate_random_proc_ = proc;
+ return old_proc;
+}
+
+// static
+HttpAuthHandlerNTLM::HostNameProc HttpAuthHandlerNTLM::SetHostNameProc(
+ HostNameProc proc) {
+ HostNameProc old_proc = get_host_name_proc_;
+ get_host_name_proc_ = proc;
+ return old_proc;
+}
+
+HttpAuthHandlerNTLM::Factory::Factory() {
+}
+
+HttpAuthHandlerNTLM::Factory::~Factory() {
+}
+
+int HttpAuthHandlerNTLM::GetNextToken(const void* in_token,
+ uint32 in_token_len,
+ void** out_token,
+ uint32* out_token_len) {
+ int rv = 0;
+
+ // If in_token is non-null, then assume it contains a type 2 message...
+ if (in_token) {
+ LogToken("in-token", in_token, in_token_len);
+ std::string hostname = get_host_name_proc_();
+ if (hostname.empty())
+ return ERR_UNEXPECTED;
+ uint8 rand_buf[8];
+ generate_random_proc_(rand_buf, 8);
+ rv = GenerateType3Msg(domain_,
+ credentials_.username(), credentials_.password(),
+ hostname, rand_buf,
+ in_token, in_token_len, out_token, out_token_len);
+ } else {
+ rv = GenerateType1Msg(out_token, out_token_len);
+ }
+
+ if (rv == OK)
+ LogToken("out-token", *out_token, *out_token_len);
+
+ return rv;
+}
+
+int HttpAuthHandlerNTLM::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ // NOTE: Default credentials are not supported for the portable implementation
+ // of NTLM.
+ scoped_ptr<HttpAuthHandler> tmp_handler(new HttpAuthHandlerNTLM);
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_ntlm_win.cc b/chromium/net/http/http_auth_handler_ntlm_win.cc
new file mode 100644
index 00000000000..041522f79e6
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_ntlm_win.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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.
+
+// See "SSPI Sample Application" at
+// http://msdn.microsoft.com/en-us/library/aa918273.aspx
+// and "NTLM Security Support Provider" at
+// http://msdn.microsoft.com/en-us/library/aa923611.aspx.
+
+#include "net/http/http_auth_handler_ntlm.h"
+
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth_sspi_win.h"
+#include "net/http/url_security_manager.h"
+
+#pragma comment(lib, "secur32.lib")
+
+namespace net {
+
+HttpAuthHandlerNTLM::HttpAuthHandlerNTLM(
+ SSPILibrary* sspi_library, ULONG max_token_length,
+ URLSecurityManager* url_security_manager)
+ : auth_sspi_(sspi_library, "NTLM", NTLMSP_NAME, max_token_length),
+ url_security_manager_(url_security_manager) {
+}
+
+HttpAuthHandlerNTLM::~HttpAuthHandlerNTLM() {
+}
+
+// Require identity on first pass instead of second.
+bool HttpAuthHandlerNTLM::NeedsIdentity() {
+ return auth_sspi_.NeedsIdentity();
+}
+
+bool HttpAuthHandlerNTLM::AllowsDefaultCredentials() {
+ if (target_ == HttpAuth::AUTH_PROXY)
+ return true;
+ if (!url_security_manager_)
+ return false;
+ return url_security_manager_->CanUseDefaultCredentials(origin_);
+}
+
+HttpAuthHandlerNTLM::Factory::Factory()
+ : max_token_length_(0),
+ first_creation_(true),
+ is_unsupported_(false) {
+}
+
+HttpAuthHandlerNTLM::Factory::~Factory() {
+}
+
+int HttpAuthHandlerNTLM::Factory::CreateAuthHandler(
+ HttpAuth::ChallengeTokenizer* challenge,
+ HttpAuth::Target target,
+ const GURL& origin,
+ CreateReason reason,
+ int digest_nonce_count,
+ const BoundNetLog& net_log,
+ scoped_ptr<HttpAuthHandler>* handler) {
+ if (is_unsupported_ || reason == CREATE_PREEMPTIVE)
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ if (max_token_length_ == 0) {
+ int rv = DetermineMaxTokenLength(sspi_library_.get(), NTLMSP_NAME,
+ &max_token_length_);
+ if (rv == ERR_UNSUPPORTED_AUTH_SCHEME)
+ is_unsupported_ = true;
+ if (rv != OK)
+ return rv;
+ }
+ // TODO(cbentzel): Move towards model of parsing in the factory
+ // method and only constructing when valid.
+ scoped_ptr<HttpAuthHandler> tmp_handler(
+ new HttpAuthHandlerNTLM(sspi_library_.get(), max_token_length_,
+ url_security_manager()));
+ if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
+ return ERR_INVALID_RESPONSE;
+ handler->swap(tmp_handler);
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_handler_unittest.cc b/chromium/net/http/http_auth_handler_unittest.cc
new file mode 100644
index 00000000000..f8928fcc428
--- /dev/null
+++ b/chromium/net/http/http_auth_handler_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 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.
+
+#include "net/http/http_auth_handler.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpAuthHandlerTest, NetLog) {
+ GURL origin("http://www.example.com");
+ std::string challenge = "Mock asdf";
+ AuthCredentials credentials(ASCIIToUTF16("user"), ASCIIToUTF16("pass"));
+ std::string auth_token;
+ HttpRequestInfo request;
+
+ for (int i = 0; i < 2; ++i) {
+ bool async = (i == 0);
+ for (int j = 0; j < 2; ++j) {
+ int rv = (j == 0) ? OK : ERR_UNEXPECTED;
+ for (int k = 0; k < 2; ++k) {
+ TestCompletionCallback test_callback;
+ HttpAuth::Target target =
+ (k == 0) ? HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+ NetLog::EventType event_type =
+ (k == 0) ? NetLog::TYPE_AUTH_PROXY : NetLog::TYPE_AUTH_SERVER;
+ HttpAuth::ChallengeTokenizer tokenizer(
+ challenge.begin(), challenge.end());
+ HttpAuthHandlerMock mock_handler;
+ CapturingNetLog capturing_net_log;
+ BoundNetLog bound_net_log(BoundNetLog::Make(&capturing_net_log,
+ net::NetLog::SOURCE_NONE));
+
+ mock_handler.InitFromChallenge(&tokenizer, target,
+ origin, bound_net_log);
+ mock_handler.SetGenerateExpectation(async, rv);
+ mock_handler.GenerateAuthToken(&credentials, &request,
+ test_callback.callback(), &auth_token);
+ if (async)
+ test_callback.WaitForResult();
+
+ CapturingNetLog::CapturedEntryList entries;
+ capturing_net_log.GetEntries(&entries);
+
+ EXPECT_EQ(2u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 0, event_type));
+ EXPECT_TRUE(LogContainsEndEvent(entries, 1, event_type));
+ }
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_sspi_win.cc b/chromium/net/http/http_auth_sspi_win.cc
new file mode 100644
index 00000000000..abc80508bd8
--- /dev/null
+++ b/chromium/net/http/http_auth_sspi_win.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 2011 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.
+
+// See "SSPI Sample Application" at
+// http://msdn.microsoft.com/en-us/library/aa918273.aspx
+
+#include "net/http/http_auth_sspi_win.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+namespace {
+
+int MapAcquireCredentialsStatusToError(SECURITY_STATUS status,
+ const SEC_WCHAR* package) {
+ VLOG(1) << "AcquireCredentialsHandle returned 0x" << std::hex << status;
+ switch (status) {
+ case SEC_E_OK:
+ return OK;
+ case SEC_E_INSUFFICIENT_MEMORY:
+ return ERR_OUT_OF_MEMORY;
+ case SEC_E_INTERNAL_ERROR:
+ LOG(WARNING)
+ << "AcquireCredentialsHandle returned unexpected status 0x"
+ << std::hex << status;
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_NOT_OWNER:
+ case SEC_E_UNKNOWN_CREDENTIALS:
+ return ERR_INVALID_AUTH_CREDENTIALS;
+ case SEC_E_SECPKG_NOT_FOUND:
+ // This indicates that the SSPI configuration does not match expectations
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ default:
+ LOG(WARNING)
+ << "AcquireCredentialsHandle returned undocumented status 0x"
+ << std::hex << status;
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ }
+}
+
+int AcquireExplicitCredentials(SSPILibrary* library,
+ const SEC_WCHAR* package,
+ const base::string16& domain,
+ const base::string16& user,
+ const base::string16& password,
+ CredHandle* cred) {
+ SEC_WINNT_AUTH_IDENTITY identity;
+ identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+ identity.User =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str()));
+ identity.UserLength = user.size();
+ identity.Domain =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str()));
+ identity.DomainLength = domain.size();
+ identity.Password =
+ reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str()));
+ identity.PasswordLength = password.size();
+
+ TimeStamp expiry;
+
+ // Pass the username/password to get the credentials handle.
+ SECURITY_STATUS status = library->AcquireCredentialsHandle(
+ NULL, // pszPrincipal
+ const_cast<SEC_WCHAR*>(package), // pszPackage
+ SECPKG_CRED_OUTBOUND, // fCredentialUse
+ NULL, // pvLogonID
+ &identity, // pAuthData
+ NULL, // pGetKeyFn (not used)
+ NULL, // pvGetKeyArgument (not used)
+ cred, // phCredential
+ &expiry); // ptsExpiry
+
+ return MapAcquireCredentialsStatusToError(status, package);
+}
+
+int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package,
+ CredHandle* cred) {
+ TimeStamp expiry;
+
+ // Pass the username/password to get the credentials handle.
+ // Note: Since the 5th argument is NULL, it uses the default
+ // cached credentials for the logged in user, which can be used
+ // for a single sign-on.
+ SECURITY_STATUS status = library->AcquireCredentialsHandle(
+ NULL, // pszPrincipal
+ const_cast<SEC_WCHAR*>(package), // pszPackage
+ SECPKG_CRED_OUTBOUND, // fCredentialUse
+ NULL, // pvLogonID
+ NULL, // pAuthData
+ NULL, // pGetKeyFn (not used)
+ NULL, // pvGetKeyArgument (not used)
+ cred, // phCredential
+ &expiry); // ptsExpiry
+
+ return MapAcquireCredentialsStatusToError(status, package);
+}
+
+int MapInitializeSecurityContextStatusToError(SECURITY_STATUS status) {
+ VLOG(1) << "InitializeSecurityContext returned 0x" << std::hex << status;
+ switch (status) {
+ case SEC_E_OK:
+ case SEC_I_CONTINUE_NEEDED:
+ return OK;
+ case SEC_I_COMPLETE_AND_CONTINUE:
+ case SEC_I_COMPLETE_NEEDED:
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ case SEC_E_INCOMPLETE_MESSAGE:
+ case SEC_E_INTERNAL_ERROR:
+ // These are return codes reported by InitializeSecurityContext
+ // but not expected by Chrome (for example, INCOMPLETE_CREDENTIALS
+ // and INCOMPLETE_MESSAGE are intended for schannel).
+ LOG(WARNING)
+ << "InitializeSecurityContext returned unexpected status 0x"
+ << std::hex << status;
+ return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
+ case SEC_E_INSUFFICIENT_MEMORY:
+ return ERR_OUT_OF_MEMORY;
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ case SEC_E_INVALID_HANDLE:
+ NOTREACHED();
+ return ERR_INVALID_HANDLE;
+ case SEC_E_INVALID_TOKEN:
+ return ERR_INVALID_RESPONSE;
+ case SEC_E_LOGON_DENIED:
+ return ERR_ACCESS_DENIED;
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_WRONG_PRINCIPAL:
+ return ERR_INVALID_AUTH_CREDENTIALS;
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ case SEC_E_TARGET_UNKNOWN:
+ return ERR_MISCONFIGURED_AUTH_ENVIRONMENT;
+ default:
+ LOG(WARNING)
+ << "InitializeSecurityContext returned undocumented status 0x"
+ << std::hex << status;
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ }
+}
+
+int MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status) {
+ VLOG(1) << "QuerySecurityPackageInfo returned 0x" << std::hex << status;
+ switch (status) {
+ case SEC_E_OK:
+ return OK;
+ case SEC_E_SECPKG_NOT_FOUND:
+ // This isn't a documented return code, but has been encountered
+ // during testing.
+ return ERR_UNSUPPORTED_AUTH_SCHEME;
+ default:
+ LOG(WARNING)
+ << "QuerySecurityPackageInfo returned undocumented status 0x"
+ << std::hex << status;
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ }
+}
+
+int MapFreeContextBufferStatusToError(SECURITY_STATUS status) {
+ VLOG(1) << "FreeContextBuffer returned 0x" << std::hex << status;
+ switch (status) {
+ case SEC_E_OK:
+ return OK;
+ default:
+ // The documentation at
+ // http://msdn.microsoft.com/en-us/library/aa375416(VS.85).aspx
+ // only mentions that a non-zero (or non-SEC_E_OK) value is returned
+ // if the function fails, and does not indicate what the failure
+ // conditions are.
+ LOG(WARNING)
+ << "FreeContextBuffer returned undocumented status 0x"
+ << std::hex << status;
+ return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
+ }
+}
+
+} // anonymous namespace
+
+HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library,
+ const std::string& scheme,
+ const SEC_WCHAR* security_package,
+ ULONG max_token_length)
+ : library_(library),
+ scheme_(scheme),
+ security_package_(security_package),
+ max_token_length_(max_token_length),
+ can_delegate_(false) {
+ DCHECK(library_);
+ SecInvalidateHandle(&cred_);
+ SecInvalidateHandle(&ctxt_);
+}
+
+HttpAuthSSPI::~HttpAuthSSPI() {
+ ResetSecurityContext();
+ if (SecIsValidHandle(&cred_)) {
+ library_->FreeCredentialsHandle(&cred_);
+ SecInvalidateHandle(&cred_);
+ }
+}
+
+bool HttpAuthSSPI::NeedsIdentity() const {
+ return decoded_server_auth_token_.empty();
+}
+
+bool HttpAuthSSPI::AllowsExplicitCredentials() const {
+ return true;
+}
+
+void HttpAuthSSPI::Delegate() {
+ can_delegate_ = true;
+}
+
+void HttpAuthSSPI::ResetSecurityContext() {
+ if (SecIsValidHandle(&ctxt_)) {
+ library_->DeleteSecurityContext(&ctxt_);
+ SecInvalidateHandle(&ctxt_);
+ }
+}
+
+HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok) {
+ // Verify the challenge's auth-scheme.
+ if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+
+ std::string encoded_auth_token = tok->base64_param();
+ if (encoded_auth_token.empty()) {
+ // If a context has already been established, an empty challenge
+ // should be treated as a rejection of the current attempt.
+ if (SecIsValidHandle(&ctxt_))
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT;
+ DCHECK(decoded_server_auth_token_.empty());
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+ } else {
+ // If a context has not already been established, additional tokens should
+ // not be present in the auth challenge.
+ if (!SecIsValidHandle(&ctxt_))
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ }
+
+ std::string decoded_auth_token;
+ bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
+ if (!base64_rv)
+ return HttpAuth::AUTHORIZATION_RESULT_INVALID;
+ decoded_server_auth_token_ = decoded_auth_token;
+ return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
+}
+
+int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
+ const std::wstring& spn,
+ std::string* auth_token) {
+ // Initial challenge.
+ if (!SecIsValidHandle(&cred_)) {
+ int rv = OnFirstRound(credentials);
+ if (rv != OK)
+ return rv;
+ }
+
+ DCHECK(SecIsValidHandle(&cred_));
+ void* out_buf;
+ int out_buf_len;
+ int rv = GetNextSecurityToken(
+ spn,
+ static_cast<void *>(const_cast<char *>(
+ decoded_server_auth_token_.c_str())),
+ decoded_server_auth_token_.length(),
+ &out_buf,
+ &out_buf_len);
+ if (rv != OK)
+ return rv;
+
+ // Base64 encode data in output buffer and prepend the scheme.
+ std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
+ std::string encode_output;
+ bool base64_rv = base::Base64Encode(encode_input, &encode_output);
+ // OK, we are done with |out_buf|
+ free(out_buf);
+ if (!base64_rv) {
+ LOG(ERROR) << "Base64 encoding of auth token failed.";
+ return ERR_ENCODING_CONVERSION_FAILED;
+ }
+ *auth_token = scheme_ + " " + encode_output;
+ return OK;
+}
+
+int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) {
+ DCHECK(!SecIsValidHandle(&cred_));
+ int rv = OK;
+ if (credentials) {
+ base::string16 domain;
+ base::string16 user;
+ SplitDomainAndUser(credentials->username(), &domain, &user);
+ rv = AcquireExplicitCredentials(library_, security_package_, domain,
+ user, credentials->password(), &cred_);
+ if (rv != OK)
+ return rv;
+ } else {
+ rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
+ if (rv != OK)
+ return rv;
+ }
+
+ return rv;
+}
+
+int HttpAuthSSPI::GetNextSecurityToken(
+ const std::wstring& spn,
+ const void* in_token,
+ int in_token_len,
+ void** out_token,
+ int* out_token_len) {
+ CtxtHandle* ctxt_ptr;
+ SecBufferDesc in_buffer_desc, out_buffer_desc;
+ SecBufferDesc* in_buffer_desc_ptr;
+ SecBuffer in_buffer, out_buffer;
+
+ if (in_token_len > 0) {
+ // Prepare input buffer.
+ in_buffer_desc.ulVersion = SECBUFFER_VERSION;
+ in_buffer_desc.cBuffers = 1;
+ in_buffer_desc.pBuffers = &in_buffer;
+ in_buffer.BufferType = SECBUFFER_TOKEN;
+ in_buffer.cbBuffer = in_token_len;
+ in_buffer.pvBuffer = const_cast<void*>(in_token);
+ ctxt_ptr = &ctxt_;
+ in_buffer_desc_ptr = &in_buffer_desc;
+ } else {
+ // If there is no input token, then we are starting a new authentication
+ // sequence. If we have already initialized our security context, then
+ // we're incorrectly reusing the auth handler for a new sequence.
+ if (SecIsValidHandle(&ctxt_)) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ ctxt_ptr = NULL;
+ in_buffer_desc_ptr = NULL;
+ }
+
+ // Prepare output buffer.
+ out_buffer_desc.ulVersion = SECBUFFER_VERSION;
+ out_buffer_desc.cBuffers = 1;
+ out_buffer_desc.pBuffers = &out_buffer;
+ out_buffer.BufferType = SECBUFFER_TOKEN;
+ out_buffer.cbBuffer = max_token_length_;
+ out_buffer.pvBuffer = malloc(out_buffer.cbBuffer);
+ if (!out_buffer.pvBuffer)
+ return ERR_OUT_OF_MEMORY;
+
+ DWORD context_flags = 0;
+ // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that
+ // ISC_REQ_MUTUAL_AUTH must also be set.
+ if (can_delegate_)
+ context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH);
+
+ // This returns a token that is passed to the remote server.
+ DWORD context_attribute;
+ SECURITY_STATUS status = library_->InitializeSecurityContext(
+ &cred_, // phCredential
+ ctxt_ptr, // phContext
+ const_cast<wchar_t *>(spn.c_str()), // pszTargetName
+ context_flags, // fContextReq
+ 0, // Reserved1 (must be 0)
+ SECURITY_NATIVE_DREP, // TargetDataRep
+ in_buffer_desc_ptr, // pInput
+ 0, // Reserved2 (must be 0)
+ &ctxt_, // phNewContext
+ &out_buffer_desc, // pOutput
+ &context_attribute, // pfContextAttr
+ NULL); // ptsExpiry
+ int rv = MapInitializeSecurityContextStatusToError(status);
+ if (rv != OK) {
+ ResetSecurityContext();
+ free(out_buffer.pvBuffer);
+ return rv;
+ }
+ if (!out_buffer.cbBuffer) {
+ free(out_buffer.pvBuffer);
+ out_buffer.pvBuffer = NULL;
+ }
+ *out_token = out_buffer.pvBuffer;
+ *out_token_len = out_buffer.cbBuffer;
+ return OK;
+}
+
+void SplitDomainAndUser(const base::string16& combined,
+ base::string16* domain,
+ base::string16* user) {
+ // |combined| may be in the form "user" or "DOMAIN\user".
+ // Separate the two parts if they exist.
+ // TODO(cbentzel): I believe user@domain is also a valid form.
+ size_t backslash_idx = combined.find(L'\\');
+ if (backslash_idx == base::string16::npos) {
+ domain->clear();
+ *user = combined;
+ } else {
+ *domain = combined.substr(0, backslash_idx);
+ *user = combined.substr(backslash_idx + 1);
+ }
+}
+
+int DetermineMaxTokenLength(SSPILibrary* library,
+ const std::wstring& package,
+ ULONG* max_token_length) {
+ DCHECK(library);
+ DCHECK(max_token_length);
+ PSecPkgInfo pkg_info = NULL;
+ SECURITY_STATUS status = library->QuerySecurityPackageInfo(
+ const_cast<wchar_t *>(package.c_str()), &pkg_info);
+ int rv = MapQuerySecurityPackageInfoStatusToError(status);
+ if (rv != OK)
+ return rv;
+ int token_length = pkg_info->cbMaxToken;
+ status = library->FreeContextBuffer(pkg_info);
+ rv = MapFreeContextBufferStatusToError(status);
+ if (rv != OK)
+ return rv;
+ *max_token_length = token_length;
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_sspi_win.h b/chromium/net/http/http_auth_sspi_win.h
new file mode 100644
index 00000000000..57a3daacfc5
--- /dev/null
+++ b/chromium/net/http/http_auth_sspi_win.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2011 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.
+
+// This file contains common routines used by NTLM and Negotiate authentication
+// using the SSPI API on Windows.
+
+#ifndef NET_HTTP_HTTP_AUTH_SSPI_WIN_H_
+#define NET_HTTP_HTTP_AUTH_SSPI_WIN_H_
+
+// security.h needs to be included for CredHandle. Unfortunately CredHandle
+// is a typedef and can't be forward declared.
+#define SECURITY_WIN32 1
+#include <windows.h>
+#include <security.h>
+
+#include <string>
+
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+
+namespace net {
+
+// SSPILibrary is introduced so unit tests can mock the calls to Windows' SSPI
+// implementation. The default implementation simply passes the arguments on to
+// the SSPI implementation provided by Secur32.dll.
+// NOTE(cbentzel): I considered replacing the Secur32.dll with a mock DLL, but
+// decided that it wasn't worth the effort as this is unlikely to be performance
+// sensitive code.
+class SSPILibrary {
+ public:
+ virtual ~SSPILibrary() {}
+
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) = 0;
+
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) = 0;
+
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo) = 0;
+
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential) = 0;
+
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext) = 0;
+
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer) = 0;
+};
+
+class SSPILibraryDefault : public SSPILibrary {
+ public:
+ SSPILibraryDefault() {}
+ virtual ~SSPILibraryDefault() {}
+
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) {
+ return ::AcquireCredentialsHandle(pszPrincipal, pszPackage, fCredentialUse,
+ pvLogonId, pvAuthData, pGetKeyFn,
+ pvGetKeyArgument, phCredential,
+ ptsExpiry);
+ }
+
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) {
+ return ::InitializeSecurityContext(phCredential, phContext, pszTargetName,
+ fContextReq, Reserved1, TargetDataRep,
+ pInput, Reserved2, phNewContext, pOutput,
+ contextAttr, ptsExpiry);
+ }
+
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo) {
+ return ::QuerySecurityPackageInfo(pszPackageName, pkgInfo);
+ }
+
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential) {
+ return ::FreeCredentialsHandle(phCredential);
+ }
+
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext) {
+ return ::DeleteSecurityContext(phContext);
+ }
+
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer) {
+ return ::FreeContextBuffer(pvContextBuffer);
+ }
+};
+
+class NET_EXPORT_PRIVATE HttpAuthSSPI {
+ public:
+ HttpAuthSSPI(SSPILibrary* sspi_library,
+ const std::string& scheme,
+ const SEC_WCHAR* security_package,
+ ULONG max_token_length);
+ ~HttpAuthSSPI();
+
+ bool NeedsIdentity() const;
+
+ bool AllowsExplicitCredentials() const;
+
+ HttpAuth::AuthorizationResult ParseChallenge(
+ HttpAuth::ChallengeTokenizer* tok);
+
+ // Generates an authentication token for the service specified by the
+ // Service Principal Name |spn| and stores the value in |*auth_token|.
+ // If the return value is not |OK|, then the value of |*auth_token| is
+ // unspecified. ERR_IO_PENDING is not a valid return code.
+ // If this is the first round of a multiple round scheme, credentials are
+ // obtained using |*credentials|. If |credentials| is NULL, the credentials
+ // for the currently logged in user are used instead.
+ int GenerateAuthToken(const AuthCredentials* credentials,
+ const std::wstring& spn,
+ std::string* auth_token);
+
+ // Delegation is allowed on the Kerberos ticket. This allows certain servers
+ // to act as the user, such as an IIS server retrieiving data from a
+ // Kerberized MSSQL server.
+ void Delegate();
+
+ private:
+ int OnFirstRound(const AuthCredentials* credentials);
+
+ int GetNextSecurityToken(
+ const std::wstring& spn,
+ const void* in_token,
+ int in_token_len,
+ void** out_token,
+ int* out_token_len);
+
+ void ResetSecurityContext();
+
+ SSPILibrary* library_;
+ std::string scheme_;
+ const SEC_WCHAR* security_package_;
+ std::string decoded_server_auth_token_;
+ ULONG max_token_length_;
+ CredHandle cred_;
+ CtxtHandle ctxt_;
+ bool can_delegate_;
+};
+
+// Splits |combined| into domain and username.
+// If |combined| is of form "FOO\bar", |domain| will contain "FOO" and |user|
+// will contain "bar".
+// If |combined| is of form "bar", |domain| will be empty and |user| will
+// contain "bar".
+// |domain| and |user| must be non-NULL.
+NET_EXPORT_PRIVATE void SplitDomainAndUser(const base::string16& combined,
+ base::string16* domain,
+ base::string16* user);
+
+// Determines the maximum token length in bytes for a particular SSPI package.
+//
+// |library| and |max_token_length| must be non-NULL pointers to valid objects.
+//
+// If the return value is OK, |*max_token_length| contains the maximum token
+// length in bytes.
+//
+// If the return value is ERR_UNSUPPORTED_AUTH_SCHEME, |package| is not an
+// known SSPI authentication scheme on this system. |*max_token_length| is not
+// changed.
+//
+// If the return value is ERR_UNEXPECTED, there was an unanticipated problem
+// in the underlying SSPI call. The details are logged, and |*max_token_length|
+// is not changed.
+NET_EXPORT_PRIVATE int DetermineMaxTokenLength(SSPILibrary* library,
+ const std::wstring& package,
+ ULONG* max_token_length);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_AUTH_SSPI_WIN_H_
diff --git a/chromium/net/http/http_auth_sspi_win_unittest.cc b/chromium/net/http/http_auth_sspi_win_unittest.cc
new file mode 100644
index 00000000000..d8521375900
--- /dev/null
+++ b/chromium/net/http/http_auth_sspi_win_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2011 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 "base/basictypes.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_sspi_win.h"
+#include "net/http/mock_sspi_library_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+void MatchDomainUserAfterSplit(const std::wstring& combined,
+ const std::wstring& expected_domain,
+ const std::wstring& expected_user) {
+ std::wstring actual_domain;
+ std::wstring actual_user;
+ SplitDomainAndUser(combined, &actual_domain, &actual_user);
+ EXPECT_EQ(expected_domain, actual_domain);
+ EXPECT_EQ(expected_user, actual_user);
+}
+
+const ULONG kMaxTokenLength = 100;
+
+} // namespace
+
+TEST(HttpAuthSSPITest, SplitUserAndDomain) {
+ MatchDomainUserAfterSplit(L"foobar", L"", L"foobar");
+ MatchDomainUserAfterSplit(L"FOO\\bar", L"FOO", L"bar");
+}
+
+TEST(HttpAuthSSPITest, DetermineMaxTokenLength_Normal) {
+ SecPkgInfoW package_info;
+ memset(&package_info, 0x0, sizeof(package_info));
+ package_info.cbMaxToken = 1337;
+
+ MockSSPILibrary mock_library;
+ mock_library.ExpectQuerySecurityPackageInfo(L"NTLM", SEC_E_OK, &package_info);
+ ULONG max_token_length = kMaxTokenLength;
+ int rv = DetermineMaxTokenLength(&mock_library, L"NTLM", &max_token_length);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(1337, max_token_length);
+}
+
+TEST(HttpAuthSSPITest, DetermineMaxTokenLength_InvalidPackage) {
+ MockSSPILibrary mock_library;
+ mock_library.ExpectQuerySecurityPackageInfo(L"Foo", SEC_E_SECPKG_NOT_FOUND,
+ NULL);
+ ULONG max_token_length = kMaxTokenLength;
+ int rv = DetermineMaxTokenLength(&mock_library, L"Foo", &max_token_length);
+ EXPECT_EQ(ERR_UNSUPPORTED_AUTH_SCHEME, rv);
+ // |DetermineMaxTokenLength()| interface states that |max_token_length| should
+ // not change on failure.
+ EXPECT_EQ(100, max_token_length);
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_FirstRound) {
+ // The first round should just consist of an unadorned "Negotiate" header.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_TwoRounds) {
+ // The first round should just have "Negotiate", and the second round should
+ // have a valid base64 token associated with it.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ // Generate an auth token and create another thing.
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+
+ std::string second_challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_UnexpectedTokenFirstRound) {
+ // If the first round challenge has an additional authentication token, it
+ // should be treated as an invalid challenge from the server.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string challenge_text = "Negotiate Zm9vYmFy";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_sspi.ParseChallenge(&challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_MissingTokenSecondRound) {
+ // If a later-round challenge is simply "Negotiate", it should be treated as
+ // an authentication challenge rejection from the server or proxy.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
+TEST(HttpAuthSSPITest, ParseChallenge_NonBase64EncodedToken) {
+ // If a later-round challenge has an invalid base64 encoded token, it should
+ // be treated as an invalid challenge.
+ MockSSPILibrary mock_library;
+ HttpAuthSSPI auth_sspi(&mock_library, "Negotiate",
+ NEGOSSP_NAME, kMaxTokenLength);
+ std::string first_challenge_text = "Negotiate";
+ HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(),
+ first_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ auth_sspi.ParseChallenge(&first_challenge));
+
+ std::string auth_token;
+ EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com",
+ &auth_token));
+ std::string second_challenge_text = "Negotiate =happyjoy=";
+ HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(),
+ second_challenge_text.end());
+ EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
+ auth_sspi.ParseChallenge(&second_challenge));
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_auth_unittest.cc b/chromium/net/http/http_auth_unittest.cc
new file mode 100644
index 00000000000..6f1471d472a
--- /dev/null
+++ b/chromium/net/http/http_auth_unittest.cc
@@ -0,0 +1,435 @@
+// Copyright (c) 2011 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 <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_filter.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/http/mock_allow_url_security_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+HttpAuthHandlerMock* CreateMockHandler(bool connection_based) {
+ HttpAuthHandlerMock* auth_handler = new HttpAuthHandlerMock();
+ auth_handler->set_connection_based(connection_based);
+ std::string challenge_text = "Basic";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ GURL origin("www.example.com");
+ EXPECT_TRUE(auth_handler->InitFromChallenge(&challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog()));
+ return auth_handler;
+}
+
+HttpResponseHeaders* HeadersFromResponseText(const std::string& response) {
+ return new HttpResponseHeaders(
+ HttpUtil::AssembleRawHeaders(response.c_str(), response.length()));
+}
+
+HttpAuth::AuthorizationResult HandleChallengeResponse(
+ bool connection_based,
+ const std::string& headers_text,
+ std::string* challenge_used) {
+ scoped_ptr<HttpAuthHandlerMock> mock_handler(
+ CreateMockHandler(connection_based));
+ std::set<HttpAuth::Scheme> disabled_schemes;
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(headers_text));
+ return HttpAuth::HandleChallengeResponse(
+ mock_handler.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ disabled_schemes,
+ challenge_used);
+}
+
+} // namespace
+
+TEST(HttpAuthTest, ChooseBestChallenge) {
+ static const struct {
+ const char* headers;
+ HttpAuth::Scheme challenge_scheme;
+ const char* challenge_realm;
+ } tests[] = {
+ {
+ // Basic is the only challenge type, pick it.
+ "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Basic realm=\"BasicRealm\"\n",
+
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "BasicRealm",
+ },
+ {
+ // Fake is the only challenge type, but it is unsupported.
+ "Y: Digest realm=\"FooBar\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Fake realm=\"FooBar\"\n",
+
+ HttpAuth::AUTH_SCHEME_MAX,
+ "",
+ },
+ {
+ // Pick Digest over Basic.
+ "www-authenticate: Basic realm=\"FooBar\"\n"
+ "www-authenticate: Fake realm=\"FooBar\"\n"
+ "www-authenticate: nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate: Digest realm=\"DigestRealm\", nonce=\"aaaaaaaaaa\"\n",
+
+ HttpAuth::AUTH_SCHEME_DIGEST,
+ "DigestRealm",
+ },
+ {
+ // Handle an empty header correctly.
+ "Y: Digest realm=\"X\", nonce=\"aaaaaaaaaa\"\n"
+ "www-authenticate:\n",
+
+ HttpAuth::AUTH_SCHEME_MAX,
+ "",
+ },
+ {
+ "WWW-Authenticate: Negotiate\n"
+ "WWW-Authenticate: NTLM\n",
+
+#if defined(USE_KERBEROS)
+ // Choose Negotiate over NTLM on all platforms.
+ // TODO(ahendrickson): This may be flaky on Linux and OSX as it
+ // relies on being able to load one of the known .so files
+ // for gssapi.
+ HttpAuth::AUTH_SCHEME_NEGOTIATE,
+#else
+ // On systems that don't use Kerberos fall back to NTLM.
+ HttpAuth::AUTH_SCHEME_NTLM,
+#endif // defined(USE_KERBEROS)
+ "",
+ }
+ };
+ GURL origin("http://www.example.com");
+ std::set<HttpAuth::Scheme> disabled_schemes;
+ MockAllowURLSecurityManager url_security_manager;
+ scoped_ptr<HostResolver> host_resolver(new MockHostResolver());
+ scoped_ptr<HttpAuthHandlerRegistryFactory> http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
+ http_auth_handler_factory->SetURLSecurityManager(
+ "negotiate", &url_security_manager);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ // Make a HttpResponseHeaders object.
+ std::string headers_with_status_line("HTTP/1.1 401 Unauthorized\n");
+ headers_with_status_line += tests[i].headers;
+ scoped_refptr<HttpResponseHeaders> headers(
+ HeadersFromResponseText(headers_with_status_line));
+
+ scoped_ptr<HttpAuthHandler> handler;
+ HttpAuth::ChooseBestChallenge(http_auth_handler_factory.get(),
+ headers.get(),
+ HttpAuth::AUTH_SERVER,
+ origin,
+ disabled_schemes,
+ BoundNetLog(),
+ &handler);
+
+ if (handler.get()) {
+ EXPECT_EQ(tests[i].challenge_scheme, handler->auth_scheme());
+ EXPECT_STREQ(tests[i].challenge_realm, handler->realm().c_str());
+ } else {
+ EXPECT_EQ(HttpAuth::AUTH_SCHEME_MAX, tests[i].challenge_scheme);
+ EXPECT_STREQ("", tests[i].challenge_realm);
+ }
+ }
+}
+
+TEST(HttpAuthTest, HandleChallengeResponse) {
+ std::string challenge_used;
+ const char* const kMockChallenge =
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Mock token_here\n";
+ const char* const kBasicChallenge =
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Basic realm=\"happy\"\n";
+ const char* const kMissingChallenge =
+ "HTTP/1.1 401 Unauthorized\n";
+ const char* const kEmptyChallenge =
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: \n";
+ const char* const kBasicAndMockChallenges =
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Basic realm=\"happy\"\n"
+ "WWW-Authenticate: Mock token_here\n";
+ const char* const kTwoMockChallenges =
+ "HTTP/1.1 401 Unauthorized\n"
+ "WWW-Authenticate: Mock token_a\n"
+ "WWW-Authenticate: Mock token_b\n";
+
+ // Request based schemes should treat any new challenges as rejections of the
+ // previous authentication attempt. (There is a slight exception for digest
+ // authentication and the stale parameter, but that is covered in the
+ // http_auth_handler_digest_unittests).
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kMockChallenge, &challenge_used));
+ EXPECT_EQ("Mock token_here", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kBasicChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kMissingChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kEmptyChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kBasicAndMockChallenges, &challenge_used));
+ EXPECT_EQ("Mock token_here", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(false, kTwoMockChallenges, &challenge_used));
+ EXPECT_EQ("Mock token_a", challenge_used);
+
+ // Connection based schemes will treat new auth challenges for the same scheme
+ // as acceptance (and continuance) of the current approach. If there are
+ // no auth challenges for the same scheme, the response will be treated as
+ // a rejection.
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ HandleChallengeResponse(true, kMockChallenge, &challenge_used));
+ EXPECT_EQ("Mock token_here", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(true, kBasicChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(true, kMissingChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_REJECT,
+ HandleChallengeResponse(true, kEmptyChallenge, &challenge_used));
+ EXPECT_EQ("", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ HandleChallengeResponse(true, kBasicAndMockChallenges, &challenge_used));
+ EXPECT_EQ("Mock token_here", challenge_used);
+
+ EXPECT_EQ(
+ HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
+ HandleChallengeResponse(true, kTwoMockChallenges, &challenge_used));
+ EXPECT_EQ("Mock token_a", challenge_used);
+}
+
+TEST(HttpAuthTest, ChallengeTokenizer) {
+ std::string challenge_str = "Basic realm=\"foobar\"";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a name=value property with no quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerNoQuotes) {
+ std::string challenge_str = "Basic realm=foobar@baz.com";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a name=value property with mismatching quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotes) {
+ std::string challenge_str = "Basic realm=\"foobar@baz.com";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foobar@baz.com"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a name= property without a value and with mismatching quote marks.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesNoValue) {
+ std::string challenge_str = "Basic realm=\"";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string(), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a name=value property with mismatching quote marks and spaces in the
+// value.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesSpaces) {
+ std::string challenge_str = "Basic realm=\"foo bar";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Basic"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foo bar"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use multiple name=value properties with mismatching quote marks in the last
+// value.
+TEST(HttpAuthTest, ChallengeTokenizerMismatchedQuotesMultiple) {
+ std::string challenge_str = "Digest qop=auth-int, algorithm=md5, realm=\"foo";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("qop"), parameters.name());
+ EXPECT_EQ(std::string("auth-int"), parameters.value());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("algorithm"), parameters.name());
+ EXPECT_EQ(std::string("md5"), parameters.value());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("foo"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a name= property which has no value.
+TEST(HttpAuthTest, ChallengeTokenizerNoValue) {
+ std::string challenge_str = "Digest qop=";
+ HttpAuth::ChallengeTokenizer challenge(
+ challenge_str.begin(), challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_FALSE(parameters.GetNext());
+ EXPECT_FALSE(parameters.valid());
+}
+
+// Specify multiple properties, comma separated.
+TEST(HttpAuthTest, ChallengeTokenizerMultiple) {
+ std::string challenge_str =
+ "Digest algorithm=md5, realm=\"Oblivion\", qop=auth-int";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("Digest"), challenge.scheme());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("algorithm"), parameters.name());
+ EXPECT_EQ(std::string("md5"), parameters.value());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("realm"), parameters.name());
+ EXPECT_EQ(std::string("Oblivion"), parameters.value());
+ EXPECT_TRUE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("qop"), parameters.name());
+ EXPECT_EQ(std::string("auth-int"), parameters.value());
+ EXPECT_FALSE(parameters.GetNext());
+ EXPECT_TRUE(parameters.valid());
+}
+
+// Use a challenge which has no property.
+TEST(HttpAuthTest, ChallengeTokenizerNoProperty) {
+ std::string challenge_str = "NTLM";
+ HttpAuth::ChallengeTokenizer challenge(
+ challenge_str.begin(), challenge_str.end());
+ HttpUtil::NameValuePairsIterator parameters = challenge.param_pairs();
+
+ EXPECT_TRUE(parameters.valid());
+ EXPECT_EQ(std::string("NTLM"), challenge.scheme());
+ EXPECT_FALSE(parameters.GetNext());
+}
+
+// Use a challenge with Base64 encoded token.
+TEST(HttpAuthTest, ChallengeTokenizerBase64) {
+ std::string challenge_str = "NTLM SGVsbG8sIFdvcmxkCg===";
+ HttpAuth::ChallengeTokenizer challenge(challenge_str.begin(),
+ challenge_str.end());
+
+ EXPECT_EQ(std::string("NTLM"), challenge.scheme());
+ // Notice the two equal statements below due to padding removal.
+ EXPECT_EQ(std::string("SGVsbG8sIFdvcmxkCg=="), challenge.base64_param());
+}
+
+TEST(HttpAuthTest, GetChallengeHeaderName) {
+ std::string name;
+
+ name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER);
+ EXPECT_STREQ("WWW-Authenticate", name.c_str());
+
+ name = HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_PROXY);
+ EXPECT_STREQ("Proxy-Authenticate", name.c_str());
+}
+
+TEST(HttpAuthTest, GetAuthorizationHeaderName) {
+ std::string name;
+
+ name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_SERVER);
+ EXPECT_STREQ("Authorization", name.c_str());
+
+ name = HttpAuth::GetAuthorizationHeaderName(HttpAuth::AUTH_PROXY);
+ EXPECT_STREQ("Proxy-Authorization", name.c_str());
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_basic_stream.cc b/chromium/net/http/http_basic_stream.cc
new file mode 100644
index 00000000000..c30e17d6203
--- /dev/null
+++ b/chromium/net/http/http_basic_stream.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 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.
+
+#include "net/http/http_basic_stream.h"
+
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_body_drainer.h"
+#include "net/http/http_stream_parser.h"
+#include "net/http/http_util.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+HttpBasicStream::HttpBasicStream(ClientSocketHandle* connection,
+ HttpStreamParser* parser,
+ bool using_proxy)
+ : read_buf_(new GrowableIOBuffer()),
+ parser_(parser),
+ connection_(connection),
+ using_proxy_(using_proxy),
+ request_info_(NULL) {
+}
+
+HttpBasicStream::~HttpBasicStream() {}
+
+int HttpBasicStream::InitializeStream(
+ const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ DCHECK(!parser_.get());
+ request_info_ = request_info;
+ parser_.reset(new HttpStreamParser(
+ connection_.get(), request_info, read_buf_.get(), net_log));
+ return OK;
+}
+
+
+int HttpBasicStream::SendRequest(const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ DCHECK(parser_.get());
+ DCHECK(request_info_);
+ const std::string path = using_proxy_ ?
+ HttpUtil::SpecForRequest(request_info_->url) :
+ HttpUtil::PathForRequest(request_info_->url);
+ request_line_ = base::StringPrintf("%s %s HTTP/1.1\r\n",
+ request_info_->method.c_str(),
+ path.c_str());
+ return parser_->SendRequest(request_line_, headers, response, callback);
+}
+
+UploadProgress HttpBasicStream::GetUploadProgress() const {
+ return parser_->GetUploadProgress();
+}
+
+int HttpBasicStream::ReadResponseHeaders(const CompletionCallback& callback) {
+ return parser_->ReadResponseHeaders(callback);
+}
+
+const HttpResponseInfo* HttpBasicStream::GetResponseInfo() const {
+ return parser_->GetResponseInfo();
+}
+
+int HttpBasicStream::ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return parser_->ReadResponseBody(buf, buf_len, callback);
+}
+
+void HttpBasicStream::Close(bool not_reusable) {
+ parser_->Close(not_reusable);
+}
+
+HttpStream* HttpBasicStream::RenewStreamForAuth() {
+ DCHECK(IsResponseBodyComplete());
+ DCHECK(!parser_->IsMoreDataBuffered());
+ parser_.reset();
+ return new HttpBasicStream(connection_.release(), NULL, using_proxy_);
+}
+
+bool HttpBasicStream::IsResponseBodyComplete() const {
+ return parser_->IsResponseBodyComplete();
+}
+
+bool HttpBasicStream::CanFindEndOfResponse() const {
+ return parser_->CanFindEndOfResponse();
+}
+
+bool HttpBasicStream::IsConnectionReused() const {
+ return parser_->IsConnectionReused();
+}
+
+void HttpBasicStream::SetConnectionReused() {
+ parser_->SetConnectionReused();
+}
+
+bool HttpBasicStream::IsConnectionReusable() const {
+ return parser_->IsConnectionReusable();
+}
+
+bool HttpBasicStream::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ return connection_->GetLoadTimingInfo(IsConnectionReused(), load_timing_info);
+}
+
+void HttpBasicStream::GetSSLInfo(SSLInfo* ssl_info) {
+ parser_->GetSSLInfo(ssl_info);
+}
+
+void HttpBasicStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ parser_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+bool HttpBasicStream::IsSpdyHttpStream() const {
+ return false;
+}
+
+void HttpBasicStream::Drain(HttpNetworkSession* session) {
+ HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(this);
+ drainer->Start(session);
+ // |drainer| will delete itself.
+}
+
+void HttpBasicStream::SetPriority(RequestPriority priority) {
+ // TODO(akalin): Plumb this through to |connection_|.
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_basic_stream.h b/chromium/net/http/http_basic_stream.h
new file mode 100644
index 00000000000..2057837e9a9
--- /dev/null
+++ b/chromium/net/http/http_basic_stream.h
@@ -0,0 +1,104 @@
+// Copyright (c) 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.
+//
+// HttpBasicStream is a simple implementation of HttpStream. It assumes it is
+// not sharing a sharing with any other HttpStreams, therefore it just reads and
+// writes directly to the Http Stream.
+
+#ifndef NET_HTTP_HTTP_BASIC_STREAM_H_
+#define NET_HTTP_HTTP_BASIC_STREAM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/http/http_stream.h"
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class GrowableIOBuffer;
+class HttpResponseInfo;
+struct HttpRequestInfo;
+class HttpRequestHeaders;
+class HttpStreamParser;
+class IOBuffer;
+
+class HttpBasicStream : public HttpStream {
+ public:
+ // Constructs a new HttpBasicStream. If |parser| is NULL, then
+ // InitializeStream should be called to initialize it correctly. If
+ // |parser| is non-null, then InitializeStream should not be called,
+ // as the stream is already initialized.
+ HttpBasicStream(ClientSocketHandle* connection,
+ HttpStreamParser* parser,
+ bool using_proxy);
+ virtual ~HttpBasicStream();
+
+ // HttpStream methods:
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual int SendRequest(const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+
+ virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual void Close(bool not_reusable) OVERRIDE;
+
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+
+ virtual bool IsConnectionReused() const OVERRIDE;
+
+ virtual void SetConnectionReused() OVERRIDE;
+
+ virtual bool IsConnectionReusable() const OVERRIDE;
+
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ private:
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+
+ scoped_ptr<HttpStreamParser> parser_;
+
+ scoped_ptr<ClientSocketHandle> connection_;
+
+ bool using_proxy_;
+
+ std::string request_line_;
+
+ const HttpRequestInfo* request_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpBasicStream);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_BASIC_STREAM_H_
diff --git a/chromium/net/http/http_byte_range.cc b/chromium/net/http/http_byte_range.cc
new file mode 100644
index 00000000000..60683c5584f
--- /dev/null
+++ b/chromium/net/http/http_byte_range.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2009 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 <algorithm>
+
+#include "net/http/http_byte_range.h"
+
+namespace {
+
+const int64 kPositionNotSpecified = -1;
+
+} // namespace
+
+namespace net {
+
+HttpByteRange::HttpByteRange()
+ : first_byte_position_(kPositionNotSpecified),
+ last_byte_position_(kPositionNotSpecified),
+ suffix_length_(kPositionNotSpecified),
+ has_computed_bounds_(false) {
+}
+
+bool HttpByteRange::IsSuffixByteRange() const {
+ return suffix_length_ != kPositionNotSpecified;
+}
+
+bool HttpByteRange::HasFirstBytePosition() const {
+ return first_byte_position_ != kPositionNotSpecified;
+}
+
+bool HttpByteRange::HasLastBytePosition() const {
+ return last_byte_position_ != kPositionNotSpecified;
+}
+
+bool HttpByteRange::IsValid() const {
+ if (suffix_length_ > 0)
+ return true;
+ return (first_byte_position_ >= 0 &&
+ (last_byte_position_ == kPositionNotSpecified ||
+ last_byte_position_ >= first_byte_position_));
+}
+
+bool HttpByteRange::ComputeBounds(int64 size) {
+ if (size < 0)
+ return false;
+ if (has_computed_bounds_)
+ return false;
+ has_computed_bounds_ = true;
+
+ // Empty values.
+ if (!HasFirstBytePosition() &&
+ !HasLastBytePosition() &&
+ !IsSuffixByteRange()) {
+ first_byte_position_ = 0;
+ last_byte_position_ = size - 1;
+ return true;
+ }
+ if (!IsValid())
+ return false;
+ if (IsSuffixByteRange()) {
+ first_byte_position_ = size - std::min(size, suffix_length_);
+ last_byte_position_ = size - 1;
+ return true;
+ }
+ if (first_byte_position_ < size) {
+ if (HasLastBytePosition())
+ last_byte_position_ = std::min(size - 1, last_byte_position_);
+ else
+ last_byte_position_ = size - 1;
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_byte_range.h b/chromium/net/http/http_byte_range.h
new file mode 100644
index 00000000000..2c06434439d
--- /dev/null
+++ b/chromium/net/http/http_byte_range.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2011 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_HTTP_HTTP_BYTE_RANGE_H_
+#define NET_HTTP_HTTP_BYTE_RANGE_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A container class that represents a "range" specified for range request
+// specified by RFC 2616 Section 14.35.1.
+// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
+class NET_EXPORT HttpByteRange {
+ public:
+ HttpByteRange();
+
+ // Since this class is POD, we use constructor, assignment operator
+ // and destructor provided by compiler.
+ int64 first_byte_position() const { return first_byte_position_; }
+ void set_first_byte_position(int64 value) { first_byte_position_ = value; }
+
+ int64 last_byte_position() const { return last_byte_position_; }
+ void set_last_byte_position(int64 value) { last_byte_position_ = value; }
+
+ int64 suffix_length() const { return suffix_length_; }
+ void set_suffix_length(int64 value) { suffix_length_ = value; }
+
+ // Returns true if this is a suffix byte range.
+ bool IsSuffixByteRange() const;
+ // Returns true if the first byte position is specified in this request.
+ bool HasFirstBytePosition() const;
+ // Returns true if the last byte position is specified in this request.
+ bool HasLastBytePosition() const;
+
+ // Returns true if this range is valid.
+ bool IsValid() const;
+
+ // A method that when given the size in bytes of a file, adjust the internal
+ // |first_byte_position_| and |last_byte_position_| values according to the
+ // range specified by this object. If the range specified is invalid with
+ // regard to the size or |size| is negative, returns false and there will be
+ // no side effect.
+ // Returns false if this method is called more than once and there will be
+ // no side effect.
+ bool ComputeBounds(int64 size);
+
+ private:
+ int64 first_byte_position_;
+ int64 last_byte_position_;
+ int64 suffix_length_;
+ bool has_computed_bounds_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_BYTE_RANGE_H_
diff --git a/chromium/net/http/http_byte_range_unittest.cc b/chromium/net/http/http_byte_range_unittest.cc
new file mode 100644
index 00000000000..6629a7c3c59
--- /dev/null
+++ b/chromium/net/http/http_byte_range_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2009 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/http/http_byte_range.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(HttpByteRangeTest, ValidRanges) {
+ const struct {
+ int64 first_byte_position;
+ int64 last_byte_position;
+ int64 suffix_length;
+ bool valid;
+ } tests[] = {
+ { -1, -1, 0, false },
+ { 0, 0, 0, true },
+ { -10, 0, 0, false },
+ { 10, 0, 0, false },
+ { 10, -1, 0, true },
+ { -1, -1, -1, false },
+ { -1, 50, 0, false },
+ { 10, 10000, 0, true },
+ { -1, -1, 100000, true },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::HttpByteRange range;
+ range.set_first_byte_position(tests[i].first_byte_position);
+ range.set_last_byte_position(tests[i].last_byte_position);
+ range.set_suffix_length(tests[i].suffix_length);
+ EXPECT_EQ(tests[i].valid, range.IsValid());
+ }
+}
+
+TEST(HttpByteRangeTest, SetInstanceSize) {
+ const struct {
+ int64 first_byte_position;
+ int64 last_byte_position;
+ int64 suffix_length;
+ int64 instance_size;
+ bool expected_return_value;
+ int64 expected_lower_bound;
+ int64 expected_upper_bound;
+ } tests[] = {
+ { -10, 0, -1, 0, false, -1, -1 },
+ { 10, 0, -1, 0, false, -1, -1 },
+ // Zero instance size is valid, this is the case that user has to handle.
+ { -1, -1, -1, 0, true, 0, -1 },
+ { -1, -1, 500, 0, true, 0, -1 },
+ { -1, 50, -1, 0, false, -1, -1 },
+ { -1, -1, 500, 300, true, 0, 299 },
+ { -1, -1, -1, 100, true, 0, 99 },
+ { 10, -1, -1, 100, true, 10, 99 },
+ { -1, -1, 500, 1000, true, 500, 999 },
+ { 10, 10000, -1, 1000000, true, 10, 10000 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::HttpByteRange range;
+ range.set_first_byte_position(tests[i].first_byte_position);
+ range.set_last_byte_position(tests[i].last_byte_position);
+ range.set_suffix_length(tests[i].suffix_length);
+
+ bool return_value = range.ComputeBounds(tests[i].instance_size);
+ EXPECT_EQ(tests[i].expected_return_value, return_value);
+ if (return_value) {
+ EXPECT_EQ(tests[i].expected_lower_bound, range.first_byte_position());
+ EXPECT_EQ(tests[i].expected_upper_bound, range.last_byte_position());
+
+ // Try to call SetInstanceSize the second time.
+ EXPECT_FALSE(range.ComputeBounds(tests[i].instance_size));
+ // And expect there's no side effect.
+ EXPECT_EQ(tests[i].expected_lower_bound, range.first_byte_position());
+ EXPECT_EQ(tests[i].expected_upper_bound, range.last_byte_position());
+ EXPECT_EQ(tests[i].suffix_length, range.suffix_length());
+ }
+ }
+}
diff --git a/chromium/net/http/http_cache.cc b/chromium/net/http/http_cache.cc
new file mode 100644
index 00000000000..4cdcbb6cd88
--- /dev/null
+++ b/chromium/net/http/http_cache.cc
@@ -0,0 +1,1147 @@
+// Copyright (c) 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.
+
+#include "net/http/http_cache.h"
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#endif
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_data_stream.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache_transaction.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+
+namespace {
+
+// Adaptor to delete a file on a worker thread.
+void DeletePath(base::FilePath path) {
+ base::DeleteFile(path, false);
+}
+
+} // namespace
+
+namespace net {
+
+HttpCache::DefaultBackend::DefaultBackend(CacheType type,
+ BackendType backend_type,
+ const base::FilePath& path,
+ int max_bytes,
+ base::MessageLoopProxy* thread)
+ : type_(type),
+ backend_type_(backend_type),
+ path_(path),
+ max_bytes_(max_bytes),
+ thread_(thread) {
+}
+
+HttpCache::DefaultBackend::~DefaultBackend() {}
+
+// static
+HttpCache::BackendFactory* HttpCache::DefaultBackend::InMemory(int max_bytes) {
+ return new DefaultBackend(MEMORY_CACHE, net::CACHE_BACKEND_DEFAULT,
+ base::FilePath(), max_bytes, NULL);
+}
+
+int HttpCache::DefaultBackend::CreateBackend(
+ NetLog* net_log, scoped_ptr<disk_cache::Backend>* backend,
+ const CompletionCallback& callback) {
+ DCHECK_GE(max_bytes_, 0);
+ return disk_cache::CreateCacheBackend(type_,
+ backend_type_,
+ path_,
+ max_bytes_,
+ true,
+ thread_.get(),
+ net_log,
+ backend,
+ callback);
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* entry)
+ : disk_entry(entry),
+ writer(NULL),
+ will_process_pending_queue(false),
+ doomed(false) {
+}
+
+HttpCache::ActiveEntry::~ActiveEntry() {
+ if (disk_entry) {
+ disk_entry->Close();
+ disk_entry = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+// This structure keeps track of work items that are attempting to create or
+// open cache entries or the backend itself.
+struct HttpCache::PendingOp {
+ PendingOp() : disk_entry(NULL), writer(NULL) {}
+ ~PendingOp() {}
+
+ disk_cache::Entry* disk_entry;
+ scoped_ptr<disk_cache::Backend> backend;
+ WorkItem* writer;
+ CompletionCallback callback; // BackendCallback.
+ WorkItemList pending_queue;
+};
+
+//-----------------------------------------------------------------------------
+
+// The type of operation represented by a work item.
+enum WorkItemOperation {
+ WI_CREATE_BACKEND,
+ WI_OPEN_ENTRY,
+ WI_CREATE_ENTRY,
+ WI_DOOM_ENTRY
+};
+
+// A work item encapsulates a single request to the backend with all the
+// information needed to complete that request.
+class HttpCache::WorkItem {
+ public:
+ WorkItem(WorkItemOperation operation, Transaction* trans, ActiveEntry** entry)
+ : operation_(operation),
+ trans_(trans),
+ entry_(entry),
+ backend_(NULL) {}
+ WorkItem(WorkItemOperation operation, Transaction* trans,
+ const net::CompletionCallback& cb, disk_cache::Backend** backend)
+ : operation_(operation),
+ trans_(trans),
+ entry_(NULL),
+ callback_(cb),
+ backend_(backend) {}
+ ~WorkItem() {}
+
+ // Calls back the transaction with the result of the operation.
+ void NotifyTransaction(int result, ActiveEntry* entry) {
+ DCHECK(!entry || entry->disk_entry);
+ if (entry_)
+ *entry_ = entry;
+ if (trans_)
+ trans_->io_callback().Run(result);
+ }
+
+ // Notifies the caller about the operation completion. Returns true if the
+ // callback was invoked.
+ bool DoCallback(int result, disk_cache::Backend* backend) {
+ if (backend_)
+ *backend_ = backend;
+ if (!callback_.is_null()) {
+ callback_.Run(result);
+ return true;
+ }
+ return false;
+ }
+
+ WorkItemOperation operation() { return operation_; }
+ void ClearTransaction() { trans_ = NULL; }
+ void ClearEntry() { entry_ = NULL; }
+ void ClearCallback() { callback_.Reset(); }
+ bool Matches(Transaction* trans) const { return trans == trans_; }
+ bool IsValid() const { return trans_ || entry_ || !callback_.is_null(); }
+
+ private:
+ WorkItemOperation operation_;
+ Transaction* trans_;
+ ActiveEntry** entry_;
+ net::CompletionCallback callback_; // User callback.
+ disk_cache::Backend** backend_;
+};
+
+//-----------------------------------------------------------------------------
+
+// This class encapsulates a transaction whose only purpose is to write metadata
+// to a given entry.
+class HttpCache::MetadataWriter {
+ public:
+ explicit MetadataWriter(HttpCache::Transaction* trans)
+ : transaction_(trans),
+ verified_(false),
+ buf_len_(0) {
+ }
+
+ ~MetadataWriter() {}
+
+ // Implements the bulk of HttpCache::WriteMetadata.
+ void Write(const GURL& url, base::Time expected_response_time, IOBuffer* buf,
+ int buf_len);
+
+ private:
+ void VerifyResponse(int result);
+ void SelfDestroy();
+ void OnIOComplete(int result);
+
+ scoped_ptr<HttpCache::Transaction> transaction_;
+ bool verified_;
+ scoped_refptr<IOBuffer> buf_;
+ int buf_len_;
+ base::Time expected_response_time_;
+ HttpRequestInfo request_info_;
+ DISALLOW_COPY_AND_ASSIGN(MetadataWriter);
+};
+
+void HttpCache::MetadataWriter::Write(const GURL& url,
+ base::Time expected_response_time,
+ IOBuffer* buf, int buf_len) {
+ DCHECK_GT(buf_len, 0);
+ DCHECK(buf);
+ DCHECK(buf->data());
+ request_info_.url = url;
+ request_info_.method = "GET";
+ request_info_.load_flags = LOAD_ONLY_FROM_CACHE;
+
+ expected_response_time_ = expected_response_time;
+ buf_ = buf;
+ buf_len_ = buf_len;
+ verified_ = false;
+
+ int rv = transaction_->Start(
+ &request_info_,
+ base::Bind(&MetadataWriter::OnIOComplete, base::Unretained(this)),
+ BoundNetLog());
+ if (rv != ERR_IO_PENDING)
+ VerifyResponse(rv);
+}
+
+void HttpCache::MetadataWriter::VerifyResponse(int result) {
+ verified_ = true;
+ if (result != OK)
+ return SelfDestroy();
+
+ const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
+ DCHECK(response_info->was_cached);
+ if (response_info->response_time != expected_response_time_)
+ return SelfDestroy();
+
+ result = transaction_->WriteMetadata(
+ buf_.get(),
+ buf_len_,
+ base::Bind(&MetadataWriter::OnIOComplete, base::Unretained(this)));
+ if (result != ERR_IO_PENDING)
+ SelfDestroy();
+}
+
+void HttpCache::MetadataWriter::SelfDestroy() {
+ delete this;
+}
+
+void HttpCache::MetadataWriter::OnIOComplete(int result) {
+ if (!verified_)
+ return VerifyResponse(result);
+ SelfDestroy();
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::HttpCache(const net::HttpNetworkSession::Params& params,
+ BackendFactory* backend_factory)
+ : net_log_(params.net_log),
+ backend_factory_(backend_factory),
+ building_backend_(false),
+ mode_(NORMAL),
+ network_layer_(new HttpNetworkLayer(new HttpNetworkSession(params))) {
+}
+
+
+HttpCache::HttpCache(HttpNetworkSession* session,
+ BackendFactory* backend_factory)
+ : net_log_(session->net_log()),
+ backend_factory_(backend_factory),
+ building_backend_(false),
+ mode_(NORMAL),
+ network_layer_(new HttpNetworkLayer(session)) {
+}
+
+HttpCache::HttpCache(HttpTransactionFactory* network_layer,
+ NetLog* net_log,
+ BackendFactory* backend_factory)
+ : net_log_(net_log),
+ backend_factory_(backend_factory),
+ building_backend_(false),
+ mode_(NORMAL),
+ network_layer_(network_layer) {
+}
+
+HttpCache::~HttpCache() {
+ // If we have any active entries remaining, then we need to deactivate them.
+ // We may have some pending calls to OnProcessPendingQueue, but since those
+ // won't run (due to our destruction), we can simply ignore the corresponding
+ // will_process_pending_queue flag.
+ while (!active_entries_.empty()) {
+ ActiveEntry* entry = active_entries_.begin()->second;
+ entry->will_process_pending_queue = false;
+ entry->pending_queue.clear();
+ entry->readers.clear();
+ entry->writer = NULL;
+ DeactivateEntry(entry);
+ }
+
+ STLDeleteElements(&doomed_entries_);
+
+ // Before deleting pending_ops_, we have to make sure that the disk cache is
+ // done with said operations, or it will attempt to use deleted data.
+ disk_cache_.reset();
+
+ PendingOpsMap::iterator pending_it = pending_ops_.begin();
+ for (; pending_it != pending_ops_.end(); ++pending_it) {
+ // We are not notifying the transactions about the cache going away, even
+ // though they are waiting for a callback that will never fire.
+ PendingOp* pending_op = pending_it->second;
+ delete pending_op->writer;
+ bool delete_pending_op = true;
+ if (building_backend_) {
+ // If we don't have a backend, when its construction finishes it will
+ // deliver the callbacks.
+ if (!pending_op->callback.is_null()) {
+ // If not null, the callback will delete the pending operation later.
+ delete_pending_op = false;
+ }
+ } else {
+ pending_op->callback.Reset();
+ }
+
+ STLDeleteElements(&pending_op->pending_queue);
+ if (delete_pending_op)
+ delete pending_op;
+ }
+}
+
+int HttpCache::GetBackend(disk_cache::Backend** backend,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (disk_cache_.get()) {
+ *backend = disk_cache_.get();
+ return OK;
+ }
+
+ return CreateBackend(backend, callback);
+}
+
+disk_cache::Backend* HttpCache::GetCurrentBackend() const {
+ return disk_cache_.get();
+}
+
+// static
+bool HttpCache::ParseResponseInfo(const char* data, int len,
+ HttpResponseInfo* response_info,
+ bool* response_truncated) {
+ Pickle pickle(data, len);
+ return response_info->InitFromPickle(pickle, response_truncated);
+}
+
+void HttpCache::WriteMetadata(const GURL& url,
+ RequestPriority priority,
+ base::Time expected_response_time,
+ IOBuffer* buf,
+ int buf_len) {
+ if (!buf_len)
+ return;
+
+ // Do lazy initialization of disk cache if needed.
+ if (!disk_cache_.get()) {
+ // We don't care about the result.
+ CreateBackend(NULL, net::CompletionCallback());
+ }
+
+ HttpCache::Transaction* trans =
+ new HttpCache::Transaction(priority, this, NULL);
+ MetadataWriter* writer = new MetadataWriter(trans);
+
+ // The writer will self destruct when done.
+ writer->Write(url, expected_response_time, buf, buf_len);
+}
+
+void HttpCache::CloseAllConnections() {
+ net::HttpNetworkLayer* network =
+ static_cast<net::HttpNetworkLayer*>(network_layer_.get());
+ HttpNetworkSession* session = network->GetSession();
+ if (session)
+ session->CloseAllConnections();
+ }
+
+void HttpCache::CloseIdleConnections() {
+ net::HttpNetworkLayer* network =
+ static_cast<net::HttpNetworkLayer*>(network_layer_.get());
+ HttpNetworkSession* session = network->GetSession();
+ if (session)
+ session->CloseIdleConnections();
+}
+
+void HttpCache::OnExternalCacheHit(const GURL& url,
+ const std::string& http_method) {
+ if (!disk_cache_.get())
+ return;
+
+ HttpRequestInfo request_info;
+ request_info.url = url;
+ request_info.method = http_method;
+ std::string key = GenerateCacheKey(&request_info);
+ disk_cache_->OnExternalCacheHit(key);
+}
+
+void HttpCache::InitializeInfiniteCache(const base::FilePath& path) {
+ if (base::FieldTrialList::FindFullName("InfiniteCache") != "Yes")
+ return;
+ base::WorkerPool::PostTask(FROM_HERE, base::Bind(&DeletePath, path), true);
+}
+
+int HttpCache::CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) {
+ // Do lazy initialization of disk cache if needed.
+ if (!disk_cache_.get()) {
+ // We don't care about the result.
+ CreateBackend(NULL, net::CompletionCallback());
+ }
+
+ trans->reset(new HttpCache::Transaction(priority, this, delegate));
+ return OK;
+}
+
+HttpCache* HttpCache::GetCache() {
+ return this;
+}
+
+HttpNetworkSession* HttpCache::GetSession() {
+ net::HttpNetworkLayer* network =
+ static_cast<net::HttpNetworkLayer*>(network_layer_.get());
+ return network->GetSession();
+}
+
+//-----------------------------------------------------------------------------
+
+int HttpCache::CreateBackend(disk_cache::Backend** backend,
+ const net::CompletionCallback& callback) {
+ if (!backend_factory_.get())
+ return ERR_FAILED;
+
+ building_backend_ = true;
+
+ scoped_ptr<WorkItem> item(new WorkItem(WI_CREATE_BACKEND, NULL, callback,
+ backend));
+
+ // This is the only operation that we can do that is not related to any given
+ // entry, so we use an empty key for it.
+ PendingOp* pending_op = GetPendingOp(std::string());
+ if (pending_op->writer) {
+ if (!callback.is_null())
+ pending_op->pending_queue.push_back(item.release());
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item.release();
+ pending_op->callback = base::Bind(&HttpCache::OnPendingOpComplete,
+ AsWeakPtr(), pending_op);
+
+ int rv = backend_factory_->CreateBackend(net_log_, &pending_op->backend,
+ pending_op->callback);
+ if (rv != ERR_IO_PENDING) {
+ pending_op->writer->ClearCallback();
+ pending_op->callback.Run(rv);
+ }
+
+ return rv;
+}
+
+int HttpCache::GetBackendForTransaction(Transaction* trans) {
+ if (disk_cache_.get())
+ return OK;
+
+ if (!building_backend_)
+ return ERR_FAILED;
+
+ WorkItem* item = new WorkItem(
+ WI_CREATE_BACKEND, trans, net::CompletionCallback(), NULL);
+ PendingOp* pending_op = GetPendingOp(std::string());
+ DCHECK(pending_op->writer);
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+}
+
+// Generate a key that can be used inside the cache.
+std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) {
+ // Strip out the reference, username, and password sections of the URL.
+ std::string url = HttpUtil::SpecForRequest(request->url);
+
+ DCHECK(mode_ != DISABLE);
+ if (mode_ == NORMAL) {
+ // No valid URL can begin with numerals, so we should not have to worry
+ // about collisions with normal URLs.
+ if (request->upload_data_stream &&
+ request->upload_data_stream->identifier()) {
+ url.insert(0, base::StringPrintf(
+ "%" PRId64 "/", request->upload_data_stream->identifier()));
+ }
+ return url;
+ }
+
+ // In playback and record mode, we cache everything.
+
+ // Lazily initialize.
+ if (playback_cache_map_ == NULL)
+ playback_cache_map_.reset(new PlaybackCacheMap());
+
+ // Each time we request an item from the cache, we tag it with a
+ // generation number. During playback, multiple fetches for the same
+ // item will use the same generation number and pull the proper
+ // instance of an URL from the cache.
+ int generation = 0;
+ DCHECK(playback_cache_map_ != NULL);
+ if (playback_cache_map_->find(url) != playback_cache_map_->end())
+ generation = (*playback_cache_map_)[url];
+ (*playback_cache_map_)[url] = generation + 1;
+
+ // The key into the cache is GENERATION # + METHOD + URL.
+ std::string result = base::IntToString(generation);
+ result.append(request->method);
+ result.append(url);
+ return result;
+}
+
+void HttpCache::DoomActiveEntry(const std::string& key) {
+ ActiveEntriesMap::iterator it = active_entries_.find(key);
+ if (it == active_entries_.end())
+ return;
+
+ // This is not a performance critical operation, this is handling an error
+ // condition so it is OK to look up the entry again.
+ int rv = DoomEntry(key, NULL);
+ DCHECK_EQ(OK, rv);
+}
+
+int HttpCache::DoomEntry(const std::string& key, Transaction* trans) {
+ // Need to abandon the ActiveEntry, but any transaction attached to the entry
+ // should not be impacted. Dooming an entry only means that it will no
+ // longer be returned by FindActiveEntry (and it will also be destroyed once
+ // all consumers are finished with the entry).
+ ActiveEntriesMap::iterator it = active_entries_.find(key);
+ if (it == active_entries_.end()) {
+ DCHECK(trans);
+ return AsyncDoomEntry(key, trans);
+ }
+
+ ActiveEntry* entry = it->second;
+ active_entries_.erase(it);
+
+ // We keep track of doomed entries so that we can ensure that they are
+ // cleaned up properly when the cache is destroyed.
+ doomed_entries_.insert(entry);
+
+ entry->disk_entry->Doom();
+ entry->doomed = true;
+
+ DCHECK(entry->writer || !entry->readers.empty());
+ return OK;
+}
+
+int HttpCache::AsyncDoomEntry(const std::string& key, Transaction* trans) {
+ WorkItem* item = new WorkItem(WI_DOOM_ENTRY, trans, NULL);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item;
+ pending_op->callback = base::Bind(&HttpCache::OnPendingOpComplete,
+ AsWeakPtr(), pending_op);
+
+ int rv = disk_cache_->DoomEntry(key, pending_op->callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearTransaction();
+ pending_op->callback.Run(rv);
+ }
+
+ return rv;
+}
+
+void HttpCache::DoomMainEntryForUrl(const GURL& url) {
+ HttpRequestInfo temp_info;
+ temp_info.url = url;
+ temp_info.method = "GET";
+ std::string key = GenerateCacheKey(&temp_info);
+
+ // Defer to DoomEntry if there is an active entry, otherwise call
+ // AsyncDoomEntry without triggering a callback.
+ if (active_entries_.count(key))
+ DoomEntry(key, NULL);
+ else
+ AsyncDoomEntry(key, NULL);
+}
+
+void HttpCache::FinalizeDoomedEntry(ActiveEntry* entry) {
+ DCHECK(entry->doomed);
+ DCHECK(!entry->writer);
+ DCHECK(entry->readers.empty());
+ DCHECK(entry->pending_queue.empty());
+
+ ActiveEntriesSet::iterator it = doomed_entries_.find(entry);
+ DCHECK(it != doomed_entries_.end());
+ doomed_entries_.erase(it);
+
+ delete entry;
+}
+
+HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) {
+ ActiveEntriesMap::const_iterator it = active_entries_.find(key);
+ return it != active_entries_.end() ? it->second : NULL;
+}
+
+HttpCache::ActiveEntry* HttpCache::ActivateEntry(
+ disk_cache::Entry* disk_entry) {
+ DCHECK(!FindActiveEntry(disk_entry->GetKey()));
+ ActiveEntry* entry = new ActiveEntry(disk_entry);
+ active_entries_[disk_entry->GetKey()] = entry;
+ return entry;
+}
+
+void HttpCache::DeactivateEntry(ActiveEntry* entry) {
+ DCHECK(!entry->will_process_pending_queue);
+ DCHECK(!entry->doomed);
+ DCHECK(!entry->writer);
+ DCHECK(entry->disk_entry);
+ DCHECK(entry->readers.empty());
+ DCHECK(entry->pending_queue.empty());
+
+ std::string key = entry->disk_entry->GetKey();
+ if (key.empty())
+ return SlowDeactivateEntry(entry);
+
+ ActiveEntriesMap::iterator it = active_entries_.find(key);
+ DCHECK(it != active_entries_.end());
+ DCHECK(it->second == entry);
+
+ active_entries_.erase(it);
+ delete entry;
+}
+
+// We don't know this entry's key so we have to find it without it.
+void HttpCache::SlowDeactivateEntry(ActiveEntry* entry) {
+ for (ActiveEntriesMap::iterator it = active_entries_.begin();
+ it != active_entries_.end(); ++it) {
+ if (it->second == entry) {
+ active_entries_.erase(it);
+ delete entry;
+ break;
+ }
+ }
+}
+
+HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) {
+ DCHECK(!FindActiveEntry(key));
+
+ PendingOpsMap::const_iterator it = pending_ops_.find(key);
+ if (it != pending_ops_.end())
+ return it->second;
+
+ PendingOp* operation = new PendingOp();
+ pending_ops_[key] = operation;
+ return operation;
+}
+
+void HttpCache::DeletePendingOp(PendingOp* pending_op) {
+ std::string key;
+ if (pending_op->disk_entry)
+ key = pending_op->disk_entry->GetKey();
+
+ if (!key.empty()) {
+ PendingOpsMap::iterator it = pending_ops_.find(key);
+ DCHECK(it != pending_ops_.end());
+ pending_ops_.erase(it);
+ } else {
+ for (PendingOpsMap::iterator it = pending_ops_.begin();
+ it != pending_ops_.end(); ++it) {
+ if (it->second == pending_op) {
+ pending_ops_.erase(it);
+ break;
+ }
+ }
+ }
+ DCHECK(pending_op->pending_queue.empty());
+
+ delete pending_op;
+}
+
+int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans) {
+ ActiveEntry* active_entry = FindActiveEntry(key);
+ if (active_entry) {
+ *entry = active_entry;
+ return OK;
+ }
+
+ WorkItem* item = new WorkItem(WI_OPEN_ENTRY, trans, entry);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item;
+ pending_op->callback = base::Bind(&HttpCache::OnPendingOpComplete,
+ AsWeakPtr(), pending_op);
+
+ int rv = disk_cache_->OpenEntry(key, &(pending_op->disk_entry),
+ pending_op->callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearTransaction();
+ pending_op->callback.Run(rv);
+ }
+
+ return rv;
+}
+
+int HttpCache::CreateEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans) {
+ if (FindActiveEntry(key)) {
+ return ERR_CACHE_RACE;
+ }
+
+ WorkItem* item = new WorkItem(WI_CREATE_ENTRY, trans, entry);
+ PendingOp* pending_op = GetPendingOp(key);
+ if (pending_op->writer) {
+ pending_op->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(pending_op->pending_queue.empty());
+
+ pending_op->writer = item;
+ pending_op->callback = base::Bind(&HttpCache::OnPendingOpComplete,
+ AsWeakPtr(), pending_op);
+
+ int rv = disk_cache_->CreateEntry(key, &(pending_op->disk_entry),
+ pending_op->callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearTransaction();
+ pending_op->callback.Run(rv);
+ }
+
+ return rv;
+}
+
+void HttpCache::DestroyEntry(ActiveEntry* entry) {
+ if (entry->doomed) {
+ FinalizeDoomedEntry(entry);
+ } else {
+ DeactivateEntry(entry);
+ }
+}
+
+int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) {
+ DCHECK(entry);
+ DCHECK(entry->disk_entry);
+
+ // We implement a basic reader/writer lock for the disk cache entry. If
+ // there is already a writer, then everyone has to wait for the writer to
+ // finish before they can access the cache entry. There can be multiple
+ // readers.
+ //
+ // NOTE: If the transaction can only write, then the entry should not be in
+ // use (since any existing entry should have already been doomed).
+
+ if (entry->writer || entry->will_process_pending_queue) {
+ entry->pending_queue.push_back(trans);
+ return ERR_IO_PENDING;
+ }
+
+ if (trans->mode() & Transaction::WRITE) {
+ // transaction needs exclusive access to the entry
+ if (entry->readers.empty()) {
+ entry->writer = trans;
+ } else {
+ entry->pending_queue.push_back(trans);
+ return ERR_IO_PENDING;
+ }
+ } else {
+ // transaction needs read access to the entry
+ entry->readers.push_back(trans);
+ }
+
+ // We do this before calling EntryAvailable to force any further calls to
+ // AddTransactionToEntry to add their transaction to the pending queue, which
+ // ensures FIFO ordering.
+ if (!entry->writer && !entry->pending_queue.empty())
+ ProcessPendingQueue(entry);
+
+ return OK;
+}
+
+void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans,
+ bool cancel) {
+ // If we already posted a task to move on to the next transaction and this was
+ // the writer, there is nothing to cancel.
+ if (entry->will_process_pending_queue && entry->readers.empty())
+ return;
+
+ if (entry->writer) {
+ DCHECK(trans == entry->writer);
+
+ // Assume there was a failure.
+ bool success = false;
+ if (cancel) {
+ DCHECK(entry->disk_entry);
+ // This is a successful operation in the sense that we want to keep the
+ // entry.
+ success = trans->AddTruncatedFlag();
+ // The previous operation may have deleted the entry.
+ if (!trans->entry())
+ return;
+ }
+ DoneWritingToEntry(entry, success);
+ } else {
+ DoneReadingFromEntry(entry, trans);
+ }
+}
+
+void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) {
+ DCHECK(entry->readers.empty());
+
+ entry->writer = NULL;
+
+ if (success) {
+ ProcessPendingQueue(entry);
+ } else {
+ DCHECK(!entry->will_process_pending_queue);
+
+ // We failed to create this entry.
+ TransactionList pending_queue;
+ pending_queue.swap(entry->pending_queue);
+
+ entry->disk_entry->Doom();
+ DestroyEntry(entry);
+
+ // We need to do something about these pending entries, which now need to
+ // be added to a new entry.
+ while (!pending_queue.empty()) {
+ // ERR_CACHE_RACE causes the transaction to restart the whole process.
+ pending_queue.front()->io_callback().Run(ERR_CACHE_RACE);
+ pending_queue.pop_front();
+ }
+ }
+}
+
+void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) {
+ DCHECK(!entry->writer);
+
+ TransactionList::iterator it =
+ std::find(entry->readers.begin(), entry->readers.end(), trans);
+ DCHECK(it != entry->readers.end());
+
+ entry->readers.erase(it);
+
+ ProcessPendingQueue(entry);
+}
+
+void HttpCache::ConvertWriterToReader(ActiveEntry* entry) {
+ DCHECK(entry->writer);
+ DCHECK(entry->writer->mode() == Transaction::READ_WRITE);
+ DCHECK(entry->readers.empty());
+
+ Transaction* trans = entry->writer;
+
+ entry->writer = NULL;
+ entry->readers.push_back(trans);
+
+ ProcessPendingQueue(entry);
+}
+
+LoadState HttpCache::GetLoadStateForPendingTransaction(
+ const Transaction* trans) {
+ ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
+ if (i == active_entries_.end()) {
+ // If this is really a pending transaction, and it is not part of
+ // active_entries_, we should be creating the backend or the entry.
+ return LOAD_STATE_WAITING_FOR_CACHE;
+ }
+
+ Transaction* writer = i->second->writer;
+ return writer ? writer->GetWriterLoadState() : LOAD_STATE_WAITING_FOR_CACHE;
+}
+
+void HttpCache::RemovePendingTransaction(Transaction* trans) {
+ ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
+ bool found = false;
+ if (i != active_entries_.end())
+ found = RemovePendingTransactionFromEntry(i->second, trans);
+
+ if (found)
+ return;
+
+ if (building_backend_) {
+ PendingOpsMap::const_iterator j = pending_ops_.find(std::string());
+ if (j != pending_ops_.end())
+ found = RemovePendingTransactionFromPendingOp(j->second, trans);
+
+ if (found)
+ return;
+ }
+
+ PendingOpsMap::const_iterator j = pending_ops_.find(trans->key());
+ if (j != pending_ops_.end())
+ found = RemovePendingTransactionFromPendingOp(j->second, trans);
+
+ if (found)
+ return;
+
+ ActiveEntriesSet::iterator k = doomed_entries_.begin();
+ for (; k != doomed_entries_.end() && !found; ++k)
+ found = RemovePendingTransactionFromEntry(*k, trans);
+
+ DCHECK(found) << "Pending transaction not found";
+}
+
+bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry,
+ Transaction* trans) {
+ TransactionList& pending_queue = entry->pending_queue;
+
+ TransactionList::iterator j =
+ find(pending_queue.begin(), pending_queue.end(), trans);
+ if (j == pending_queue.end())
+ return false;
+
+ pending_queue.erase(j);
+ return true;
+}
+
+bool HttpCache::RemovePendingTransactionFromPendingOp(PendingOp* pending_op,
+ Transaction* trans) {
+ if (pending_op->writer->Matches(trans)) {
+ pending_op->writer->ClearTransaction();
+ pending_op->writer->ClearEntry();
+ return true;
+ }
+ WorkItemList& pending_queue = pending_op->pending_queue;
+
+ WorkItemList::iterator it = pending_queue.begin();
+ for (; it != pending_queue.end(); ++it) {
+ if ((*it)->Matches(trans)) {
+ delete *it;
+ pending_queue.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+void HttpCache::ProcessPendingQueue(ActiveEntry* entry) {
+ // Multiple readers may finish with an entry at once, so we want to batch up
+ // calls to OnProcessPendingQueue. This flag also tells us that we should
+ // not delete the entry before OnProcessPendingQueue runs.
+ if (entry->will_process_pending_queue)
+ return;
+ entry->will_process_pending_queue = true;
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpCache::OnProcessPendingQueue, AsWeakPtr(), entry));
+}
+
+void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) {
+ entry->will_process_pending_queue = false;
+ DCHECK(!entry->writer);
+
+ // If no one is interested in this entry, then we can deactivate it.
+ if (entry->pending_queue.empty()) {
+ if (entry->readers.empty())
+ DestroyEntry(entry);
+ return;
+ }
+
+ // Promote next transaction from the pending queue.
+ Transaction* next = entry->pending_queue.front();
+ if ((next->mode() & Transaction::WRITE) && !entry->readers.empty())
+ return; // Have to wait.
+
+ entry->pending_queue.erase(entry->pending_queue.begin());
+
+ int rv = AddTransactionToEntry(entry, next);
+ if (rv != ERR_IO_PENDING) {
+ next->io_callback().Run(rv);
+ }
+}
+
+void HttpCache::OnIOComplete(int result, PendingOp* pending_op) {
+ WorkItemOperation op = pending_op->writer->operation();
+
+ // Completing the creation of the backend is simpler than the other cases.
+ if (op == WI_CREATE_BACKEND)
+ return OnBackendCreated(result, pending_op);
+
+ scoped_ptr<WorkItem> item(pending_op->writer);
+ bool fail_requests = false;
+
+ ActiveEntry* entry = NULL;
+ std::string key;
+ if (result == OK) {
+ if (op == WI_DOOM_ENTRY) {
+ // Anything after a Doom has to be restarted.
+ fail_requests = true;
+ } else if (item->IsValid()) {
+ key = pending_op->disk_entry->GetKey();
+ entry = ActivateEntry(pending_op->disk_entry);
+ } else {
+ // The writer transaction is gone.
+ if (op == WI_CREATE_ENTRY)
+ pending_op->disk_entry->Doom();
+ pending_op->disk_entry->Close();
+ pending_op->disk_entry = NULL;
+ fail_requests = true;
+ }
+ }
+
+ // We are about to notify a bunch of transactions, and they may decide to
+ // re-issue a request (or send a different one). If we don't delete
+ // pending_op, the new request will be appended to the end of the list, and
+ // we'll see it again from this point before it has a chance to complete (and
+ // we'll be messing out the request order). The down side is that if for some
+ // reason notifying request A ends up cancelling request B (for the same key),
+ // we won't find request B anywhere (because it would be in a local variable
+ // here) and that's bad. If there is a chance for that to happen, we'll have
+ // to move the callback used to be a CancelableCallback. By the way, for this
+ // to happen the action (to cancel B) has to be synchronous to the
+ // notification for request A.
+ WorkItemList pending_items;
+ pending_items.swap(pending_op->pending_queue);
+ DeletePendingOp(pending_op);
+
+ item->NotifyTransaction(result, entry);
+
+ while (!pending_items.empty()) {
+ item.reset(pending_items.front());
+ pending_items.pop_front();
+
+ if (item->operation() == WI_DOOM_ENTRY) {
+ // A queued doom request is always a race.
+ fail_requests = true;
+ } else if (result == OK) {
+ entry = FindActiveEntry(key);
+ if (!entry)
+ fail_requests = true;
+ }
+
+ if (fail_requests) {
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ continue;
+ }
+
+ if (item->operation() == WI_CREATE_ENTRY) {
+ if (result == OK) {
+ // A second Create request, but the first request succeeded.
+ item->NotifyTransaction(ERR_CACHE_CREATE_FAILURE, NULL);
+ } else {
+ if (op != WI_CREATE_ENTRY) {
+ // Failed Open followed by a Create.
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ fail_requests = true;
+ } else {
+ item->NotifyTransaction(result, entry);
+ }
+ }
+ } else {
+ if (op == WI_CREATE_ENTRY && result != OK) {
+ // Failed Create followed by an Open.
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ fail_requests = true;
+ } else {
+ item->NotifyTransaction(result, entry);
+ }
+ }
+ }
+}
+
+// static
+void HttpCache::OnPendingOpComplete(const base::WeakPtr<HttpCache>& cache,
+ PendingOp* pending_op,
+ int rv) {
+ if (cache.get()) {
+ cache->OnIOComplete(rv, pending_op);
+ } else {
+ // The callback was cancelled so we should delete the pending_op that
+ // was used with this callback.
+ delete pending_op;
+ }
+}
+
+void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) {
+ scoped_ptr<WorkItem> item(pending_op->writer);
+ WorkItemOperation op = item->operation();
+ DCHECK_EQ(WI_CREATE_BACKEND, op);
+
+ // We don't need the callback anymore.
+ pending_op->callback.Reset();
+
+ if (backend_factory_.get()) {
+ // We may end up calling OnBackendCreated multiple times if we have pending
+ // work items. The first call saves the backend and releases the factory,
+ // and the last call clears building_backend_.
+ backend_factory_.reset(); // Reclaim memory.
+ if (result == OK)
+ disk_cache_ = pending_op->backend.Pass();
+ }
+
+ if (!pending_op->pending_queue.empty()) {
+ WorkItem* pending_item = pending_op->pending_queue.front();
+ pending_op->pending_queue.pop_front();
+ DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation());
+
+ // We want to process a single callback at a time, because the cache may
+ // go away from the callback.
+ pending_op->writer = pending_item;
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &HttpCache::OnBackendCreated, AsWeakPtr(), result, pending_op));
+ } else {
+ building_backend_ = false;
+ DeletePendingOp(pending_op);
+ }
+
+ // The cache may be gone when we return from the callback.
+ if (!item->DoCallback(result, disk_cache_.get()))
+ item->NotifyTransaction(result, NULL);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_cache.h b/chromium/net/http/http_cache.h
new file mode 100644
index 00000000000..db2db6906d5
--- /dev/null
+++ b/chromium/net/http/http_cache.h
@@ -0,0 +1,404 @@
+// Copyright (c) 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.
+
+// This file declares a HttpTransactionFactory implementation that can be
+// layered on top of another HttpTransactionFactory to add HTTP caching. The
+// caching logic follows RFC 2616 (any exceptions are called out in the code).
+//
+// The HttpCache takes a disk_cache::Backend as a parameter, and uses that for
+// the cache storage.
+//
+// See HttpTransactionFactory and HttpTransaction for more details.
+
+#ifndef NET_HTTP_HTTP_CACHE_H_
+#define NET_HTTP_HTTP_CACHE_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/cache_type.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+
+class GURL;
+
+namespace disk_cache {
+class Backend;
+class Entry;
+}
+
+namespace net {
+
+class CertVerifier;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpNetworkSession;
+class HttpResponseInfo;
+class HttpServerProperties;
+class IOBuffer;
+class NetLog;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class SSLConfigService;
+class TransportSecurityState;
+class ViewCacheHelper;
+struct HttpRequestInfo;
+
+class NET_EXPORT HttpCache : public HttpTransactionFactory,
+ public base::SupportsWeakPtr<HttpCache>,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // The cache mode of operation.
+ enum Mode {
+ // Normal mode just behaves like a standard web cache.
+ NORMAL = 0,
+ // Record mode caches everything for purposes of offline playback.
+ RECORD,
+ // Playback mode replays from a cache without considering any
+ // standard invalidations.
+ PLAYBACK,
+ // Disables reads and writes from the cache.
+ // Equivalent to setting LOAD_DISABLE_CACHE on every request.
+ DISABLE
+ };
+
+ // A BackendFactory creates a backend object to be used by the HttpCache.
+ class NET_EXPORT BackendFactory {
+ public:
+ virtual ~BackendFactory() {}
+
+ // The actual method to build the backend. Returns a net error code. If
+ // ERR_IO_PENDING is returned, the |callback| will be notified when the
+ // operation completes, and |backend| must remain valid until the
+ // notification arrives.
+ // The implementation must not access the factory object after invoking the
+ // |callback| because the object can be deleted from within the callback.
+ virtual int CreateBackend(NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const CompletionCallback& callback) = 0;
+ };
+
+ // A default backend factory for the common use cases.
+ class NET_EXPORT DefaultBackend : public BackendFactory {
+ public:
+ // |path| is the destination for any files used by the backend, and
+ // |cache_thread| is the thread where disk operations should take place. If
+ // |max_bytes| is zero, a default value will be calculated automatically.
+ DefaultBackend(CacheType type, BackendType backend_type,
+ const base::FilePath& path, int max_bytes,
+ base::MessageLoopProxy* thread);
+ virtual ~DefaultBackend();
+
+ // Returns a factory for an in-memory cache.
+ static BackendFactory* InMemory(int max_bytes);
+
+ // BackendFactory implementation.
+ virtual int CreateBackend(NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ CacheType type_;
+ BackendType backend_type_;
+ const base::FilePath path_;
+ int max_bytes_;
+ scoped_refptr<base::MessageLoopProxy> thread_;
+ };
+
+ // The disk cache is initialized lazily (by CreateTransaction) in this case.
+ // The HttpCache takes ownership of the |backend_factory|.
+ HttpCache(const net::HttpNetworkSession::Params& params,
+ BackendFactory* backend_factory);
+
+ // The disk cache is initialized lazily (by CreateTransaction) in this case.
+ // Provide an existing HttpNetworkSession, the cache can construct a
+ // network layer with a shared HttpNetworkSession in order for multiple
+ // network layers to share information (e.g. authentication data). The
+ // HttpCache takes ownership of the |backend_factory|.
+ HttpCache(HttpNetworkSession* session, BackendFactory* backend_factory);
+
+ // Initialize the cache from its component parts, which is useful for
+ // testing. The lifetime of the network_layer and backend_factory are managed
+ // by the HttpCache and will be destroyed using |delete| when the HttpCache is
+ // destroyed.
+ HttpCache(HttpTransactionFactory* network_layer,
+ NetLog* net_log,
+ BackendFactory* backend_factory);
+
+ virtual ~HttpCache();
+
+ HttpTransactionFactory* network_layer() { return network_layer_.get(); }
+
+ // Retrieves the cache backend for this HttpCache instance. If the backend
+ // is not initialized yet, this method will initialize it. The return value is
+ // a network error code, and it could be ERR_IO_PENDING, in which case the
+ // |callback| will be notified when the operation completes. The pointer that
+ // receives the |backend| must remain valid until the operation completes.
+ int GetBackend(disk_cache::Backend** backend,
+ const net::CompletionCallback& callback);
+
+ // Returns the current backend (can be NULL).
+ disk_cache::Backend* GetCurrentBackend() const;
+
+ // Given a header data blob, convert it to a response info object.
+ static bool ParseResponseInfo(const char* data, int len,
+ HttpResponseInfo* response_info,
+ bool* response_truncated);
+
+ // Writes |buf_len| bytes of metadata stored in |buf| to the cache entry
+ // referenced by |url|, as long as the entry's |expected_response_time| has
+ // not changed. This method returns without blocking, and the operation will
+ // be performed asynchronously without any completion notification.
+ void WriteMetadata(const GURL& url,
+ RequestPriority priority,
+ base::Time expected_response_time,
+ IOBuffer* buf,
+ int buf_len);
+
+ // Get/Set the cache's mode.
+ void set_mode(Mode value) { mode_ = value; }
+ Mode mode() { return mode_; }
+
+ // Close currently active sockets so that fresh page loads will not use any
+ // recycled connections. For sockets currently in use, they may not close
+ // immediately, but they will not be reusable. This is for debugging.
+ void CloseAllConnections();
+
+ // Close all idle connections. Will close all sockets not in active use.
+ void CloseIdleConnections();
+
+ // Called whenever an external cache in the system reuses the resource
+ // referred to by |url| and |http_method|.
+ void OnExternalCacheHit(const GURL& url, const std::string& http_method);
+
+ // Initializes the Infinite Cache, if selected by the field trial.
+ void InitializeInfiniteCache(const base::FilePath& path);
+
+ // HttpTransactionFactory implementation:
+ virtual int CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) OVERRIDE;
+ virtual HttpCache* GetCache() OVERRIDE;
+ virtual HttpNetworkSession* GetSession() OVERRIDE;
+
+ protected:
+ // Disk cache entry data indices.
+ enum {
+ kResponseInfoIndex = 0,
+ kResponseContentIndex,
+ kMetadataIndex,
+
+ // Must remain at the end of the enum.
+ kNumCacheEntryDataIndices
+ };
+ friend class ViewCacheHelper;
+
+ private:
+ // Types --------------------------------------------------------------------
+
+ class MetadataWriter;
+ class Transaction;
+ class WorkItem;
+ friend class Transaction;
+ struct PendingOp; // Info for an entry under construction.
+
+ typedef std::list<Transaction*> TransactionList;
+ typedef std::list<WorkItem*> WorkItemList;
+
+ struct ActiveEntry {
+ explicit ActiveEntry(disk_cache::Entry* entry);
+ ~ActiveEntry();
+
+ disk_cache::Entry* disk_entry;
+ Transaction* writer;
+ TransactionList readers;
+ TransactionList pending_queue;
+ bool will_process_pending_queue;
+ bool doomed;
+ };
+
+ typedef base::hash_map<std::string, ActiveEntry*> ActiveEntriesMap;
+ typedef base::hash_map<std::string, PendingOp*> PendingOpsMap;
+ typedef std::set<ActiveEntry*> ActiveEntriesSet;
+ typedef base::hash_map<std::string, int> PlaybackCacheMap;
+
+ // Methods ------------------------------------------------------------------
+
+ // Creates the |backend| object and notifies the |callback| when the operation
+ // completes. Returns an error code.
+ int CreateBackend(disk_cache::Backend** backend,
+ const net::CompletionCallback& callback);
+
+ // Makes sure that the backend creation is complete before allowing the
+ // provided transaction to use the object. Returns an error code. |trans|
+ // will be notified via its IO callback if this method returns ERR_IO_PENDING.
+ // The transaction is free to use the backend directly at any time after
+ // receiving the notification.
+ int GetBackendForTransaction(Transaction* trans);
+
+ // Generates the cache key for this request.
+ std::string GenerateCacheKey(const HttpRequestInfo*);
+
+ // Dooms the entry selected by |key|, if it is currently in the list of active
+ // entries.
+ void DoomActiveEntry(const std::string& key);
+
+ // Dooms the entry selected by |key|. |trans| will be notified via its IO
+ // callback if this method returns ERR_IO_PENDING. The entry can be
+ // currently in use or not.
+ int DoomEntry(const std::string& key, Transaction* trans);
+
+ // Dooms the entry selected by |key|. |trans| will be notified via its IO
+ // callback if this method returns ERR_IO_PENDING. The entry should not
+ // be currently in use.
+ int AsyncDoomEntry(const std::string& key, Transaction* trans);
+
+ // Dooms the entry associated with a GET for a given |url|.
+ void DoomMainEntryForUrl(const GURL& url);
+
+ // Closes a previously doomed entry.
+ void FinalizeDoomedEntry(ActiveEntry* entry);
+
+ // Returns an entry that is currently in use and not doomed, or NULL.
+ ActiveEntry* FindActiveEntry(const std::string& key);
+
+ // Creates a new ActiveEntry and starts tracking it. |disk_entry| is the disk
+ // cache entry.
+ ActiveEntry* ActivateEntry(disk_cache::Entry* disk_entry);
+
+ // Deletes an ActiveEntry.
+ void DeactivateEntry(ActiveEntry* entry);
+
+ // Deletes an ActiveEntry using an exhaustive search.
+ void SlowDeactivateEntry(ActiveEntry* entry);
+
+ // Returns the PendingOp for the desired |key|. If an entry is not under
+ // construction already, a new PendingOp structure is created.
+ PendingOp* GetPendingOp(const std::string& key);
+
+ // Deletes a PendingOp.
+ void DeletePendingOp(PendingOp* pending_op);
+
+ // Opens the disk cache entry associated with |key|, returning an ActiveEntry
+ // in |*entry|. |trans| will be notified via its IO callback if this method
+ // returns ERR_IO_PENDING.
+ int OpenEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans);
+
+ // Creates the disk cache entry associated with |key|, returning an
+ // ActiveEntry in |*entry|. |trans| will be notified via its IO callback if
+ // this method returns ERR_IO_PENDING.
+ int CreateEntry(const std::string& key, ActiveEntry** entry,
+ Transaction* trans);
+
+ // Destroys an ActiveEntry (active or doomed).
+ void DestroyEntry(ActiveEntry* entry);
+
+ // Adds a transaction to an ActiveEntry. If this method returns ERR_IO_PENDING
+ // the transaction will be notified about completion via its IO callback. This
+ // method returns ERR_CACHE_RACE to signal the transaction that it cannot be
+ // added to the provided entry, and it should retry the process with another
+ // one (in this case, the entry is no longer valid).
+ int AddTransactionToEntry(ActiveEntry* entry, Transaction* trans);
+
+ // Called when the transaction has finished working with this entry. |cancel|
+ // is true if the operation was cancelled by the caller instead of running
+ // to completion.
+ void DoneWithEntry(ActiveEntry* entry, Transaction* trans, bool cancel);
+
+ // Called when the transaction has finished writing to this entry. |success|
+ // is false if the cache entry should be deleted.
+ void DoneWritingToEntry(ActiveEntry* entry, bool success);
+
+ // Called when the transaction has finished reading from this entry.
+ void DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans);
+
+ // Converts the active writer transaction to a reader so that other
+ // transactions can start reading from this entry.
+ void ConvertWriterToReader(ActiveEntry* entry);
+
+ // Returns the LoadState of the provided pending transaction.
+ LoadState GetLoadStateForPendingTransaction(const Transaction* trans);
+
+ // Removes the transaction |trans|, from the pending list of an entry
+ // (PendingOp, active or doomed entry).
+ void RemovePendingTransaction(Transaction* trans);
+
+ // Removes the transaction |trans|, from the pending list of |entry|.
+ bool RemovePendingTransactionFromEntry(ActiveEntry* entry,
+ Transaction* trans);
+
+ // Removes the transaction |trans|, from the pending list of |pending_op|.
+ bool RemovePendingTransactionFromPendingOp(PendingOp* pending_op,
+ Transaction* trans);
+
+ // Resumes processing the pending list of |entry|.
+ void ProcessPendingQueue(ActiveEntry* entry);
+
+ // Events (called via PostTask) ---------------------------------------------
+
+ void OnProcessPendingQueue(ActiveEntry* entry);
+
+ // Callbacks ----------------------------------------------------------------
+
+ // Processes BackendCallback notifications.
+ void OnIOComplete(int result, PendingOp* entry);
+
+ // Helper to conditionally delete |pending_op| if the HttpCache object it
+ // is meant for has been deleted.
+ //
+ // TODO(ajwong): The PendingOp lifetime management is very tricky. It might
+ // be possible to simplify it using either base::Owned() or base::Passed()
+ // with the callback.
+ static void OnPendingOpComplete(const base::WeakPtr<HttpCache>& cache,
+ PendingOp* pending_op,
+ int result);
+
+ // Processes the backend creation notification.
+ void OnBackendCreated(int result, PendingOp* pending_op);
+
+ // Variables ----------------------------------------------------------------
+
+ NetLog* net_log_;
+
+ // Used when lazily constructing the disk_cache_.
+ scoped_ptr<BackendFactory> backend_factory_;
+ bool building_backend_;
+
+ Mode mode_;
+
+ const scoped_ptr<HttpTransactionFactory> network_layer_;
+ scoped_ptr<disk_cache::Backend> disk_cache_;
+
+ // The set of active entries indexed by cache key.
+ ActiveEntriesMap active_entries_;
+
+ // The set of doomed entries.
+ ActiveEntriesSet doomed_entries_;
+
+ // The set of entries "under construction".
+ PendingOpsMap pending_ops_;
+
+ scoped_ptr<PlaybackCacheMap> playback_cache_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpCache);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CACHE_H_
diff --git a/chromium/net/http/http_cache_transaction.cc b/chromium/net/http/http_cache_transaction.cc
new file mode 100644
index 00000000000..8d4a45573b8
--- /dev/null
+++ b/chromium/net/http/http_cache_transaction.cc
@@ -0,0 +1,2612 @@
+// Copyright (c) 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.
+
+#include "net/http/http_cache_transaction.h"
+
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/upload_data_stream.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_delegate.h"
+#include "net/http/http_util.h"
+#include "net/http/partial_data.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_config_service.h"
+
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace {
+
+// From http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-21#section-6
+// a "non-error response" is one with a 2xx (Successful) or 3xx
+// (Redirection) status code.
+bool NonErrorResponse(int status_code) {
+ int status_code_range = status_code / 100;
+ return status_code_range == 2 || status_code_range == 3;
+}
+
+// Error codes that will be considered indicative of a page being offline/
+// unreachable for LOAD_FROM_CACHE_IF_OFFLINE.
+bool IsOfflineError(int error) {
+ return (error == net::ERR_NAME_NOT_RESOLVED ||
+ error == net::ERR_INTERNET_DISCONNECTED ||
+ error == net::ERR_ADDRESS_UNREACHABLE ||
+ error == net::ERR_CONNECTION_TIMED_OUT);
+}
+
+// Enum for UMA, indicating the status (with regard to offline mode) of
+// a particular request.
+enum RequestOfflineStatus {
+ // A cache transaction hit in cache (data was present and not stale)
+ // and returned it.
+ OFFLINE_STATUS_FRESH_CACHE,
+
+ // A network request was required for a cache entry, and it succeeded.
+ OFFLINE_STATUS_NETWORK_SUCCEEDED,
+
+ // A network request was required for a cache entry, and it failed with
+ // a non-offline error.
+ OFFLINE_STATUS_NETWORK_FAILED,
+
+ // A network request was required for a cache entry, it failed with an
+ // offline error, and we could serve stale data if
+ // LOAD_FROM_CACHE_IF_OFFLINE was set.
+ OFFLINE_STATUS_DATA_AVAILABLE_OFFLINE,
+
+ // A network request was required for a cache entry, it failed with
+ // an offline error, and there was no servable data in cache (even
+ // stale data).
+ OFFLINE_STATUS_DATA_UNAVAILABLE_OFFLINE,
+
+ OFFLINE_STATUS_MAX_ENTRIES
+};
+
+void RecordOfflineStatus(int load_flags, RequestOfflineStatus status) {
+ // Restrict to main frame to keep statistics close to
+ // "would have shown them something useful if offline mode was enabled".
+ if (load_flags & net::LOAD_MAIN_FRAME) {
+ UMA_HISTOGRAM_ENUMERATION("HttpCache.OfflineStatus", status,
+ OFFLINE_STATUS_MAX_ENTRIES);
+ }
+}
+
+} // namespace
+
+namespace net {
+
+struct HeaderNameAndValue {
+ const char* name;
+ const char* value;
+};
+
+// If the request includes one of these request headers, then avoid caching
+// to avoid getting confused.
+static const HeaderNameAndValue kPassThroughHeaders[] = {
+ { "if-unmodified-since", NULL }, // causes unexpected 412s
+ { "if-match", NULL }, // causes unexpected 412s
+ { "if-range", NULL },
+ { NULL, NULL }
+};
+
+struct ValidationHeaderInfo {
+ const char* request_header_name;
+ const char* related_response_header_name;
+};
+
+static const ValidationHeaderInfo kValidationHeaders[] = {
+ { "if-modified-since", "last-modified" },
+ { "if-none-match", "etag" },
+};
+
+// If the request includes one of these request headers, then avoid reusing
+// our cached copy if any.
+static const HeaderNameAndValue kForceFetchHeaders[] = {
+ { "cache-control", "no-cache" },
+ { "pragma", "no-cache" },
+ { NULL, NULL }
+};
+
+// If the request includes one of these request headers, then force our
+// cached copy (if any) to be revalidated before reusing it.
+static const HeaderNameAndValue kForceValidateHeaders[] = {
+ { "cache-control", "max-age=0" },
+ { NULL, NULL }
+};
+
+static bool HeaderMatches(const HttpRequestHeaders& headers,
+ const HeaderNameAndValue* search) {
+ for (; search->name; ++search) {
+ std::string header_value;
+ if (!headers.GetHeader(search->name, &header_value))
+ continue;
+
+ if (!search->value)
+ return true;
+
+ HttpUtil::ValuesIterator v(header_value.begin(), header_value.end(), ',');
+ while (v.GetNext()) {
+ if (LowerCaseEqualsASCII(v.value_begin(), v.value_end(), search->value))
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::Transaction::Transaction(
+ RequestPriority priority,
+ HttpCache* cache,
+ HttpTransactionDelegate* transaction_delegate)
+ : next_state_(STATE_NONE),
+ request_(NULL),
+ priority_(priority),
+ cache_(cache->AsWeakPtr()),
+ entry_(NULL),
+ new_entry_(NULL),
+ new_response_(NULL),
+ mode_(NONE),
+ target_state_(STATE_NONE),
+ reading_(false),
+ invalid_range_(false),
+ truncated_(false),
+ is_sparse_(false),
+ range_requested_(false),
+ handling_206_(false),
+ cache_pending_(false),
+ done_reading_(false),
+ vary_mismatch_(false),
+ couldnt_conditionalize_request_(false),
+ io_buf_len_(0),
+ read_offset_(0),
+ effective_load_flags_(0),
+ write_len_(0),
+ weak_factory_(this),
+ io_callback_(base::Bind(&Transaction::OnIOComplete,
+ weak_factory_.GetWeakPtr())),
+ transaction_pattern_(PATTERN_UNDEFINED),
+ defer_cache_sensitivity_delay_(false),
+ transaction_delegate_(transaction_delegate) {
+ COMPILE_ASSERT(HttpCache::Transaction::kNumValidationHeaders ==
+ arraysize(kValidationHeaders),
+ Invalid_number_of_validation_headers);
+ base::StringToInt(
+ base::FieldTrialList::FindFullName("CacheSensitivityAnalysis"),
+ &sensitivity_analysis_percent_increase_);
+}
+
+HttpCache::Transaction::~Transaction() {
+ // We may have to issue another IO, but we should never invoke the callback_
+ // after this point.
+ callback_.Reset();
+
+ transaction_delegate_ = NULL;
+ cache_io_start_ = base::TimeTicks();
+ deferred_cache_sensitivity_delay_ = base::TimeDelta();
+
+ if (cache_.get()) {
+ if (entry_) {
+ bool cancel_request = reading_;
+ if (cancel_request) {
+ if (partial_.get()) {
+ entry_->disk_entry->CancelSparseIO();
+ } else {
+ cancel_request &= (response_.headers->response_code() == 200);
+ }
+ }
+
+ cache_->DoneWithEntry(entry_, this, cancel_request);
+ } else if (cache_pending_) {
+ cache_->RemovePendingTransaction(this);
+ }
+ }
+
+ // Cancel any outstanding callbacks before we drop our reference to the
+ // HttpCache. This probably isn't strictly necessary, but might as well.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // We could still have a cache read or write in progress, so we just null the
+ // cache_ pointer to signal that we are dead. See DoCacheReadCompleted.
+ cache_.reset();
+}
+
+int HttpCache::Transaction::WriteMetadata(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(!callback.is_null());
+ if (!cache_.get() || !entry_)
+ return ERR_UNEXPECTED;
+
+ // We don't need to track this operation for anything.
+ // It could be possible to check if there is something already written and
+ // avoid writing again (it should be the same, right?), but let's allow the
+ // caller to "update" the contents with something new.
+ return entry_->disk_entry->WriteData(kMetadataIndex, 0, buf, buf_len,
+ callback, true);
+}
+
+bool HttpCache::Transaction::AddTruncatedFlag() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+
+ // Don't set the flag for sparse entries.
+ if (partial_.get() && !truncated_)
+ return true;
+
+ if (!CanResume(true))
+ return false;
+
+ // We may have received the whole resource already.
+ if (done_reading_)
+ return true;
+
+ truncated_ = true;
+ target_state_ = STATE_NONE;
+ next_state_ = STATE_CACHE_WRITE_TRUNCATED_RESPONSE;
+ DoLoop(OK);
+ return true;
+}
+
+LoadState HttpCache::Transaction::GetWriterLoadState() const {
+ if (network_trans_.get())
+ return network_trans_->GetLoadState();
+ if (entry_ || !request_)
+ return LOAD_STATE_IDLE;
+ return LOAD_STATE_WAITING_FOR_CACHE;
+}
+
+const BoundNetLog& HttpCache::Transaction::net_log() const {
+ return net_log_;
+}
+
+int HttpCache::Transaction::Start(const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ DCHECK(request);
+ DCHECK(!callback.is_null());
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(callback_.is_null());
+ DCHECK(!reading_);
+ DCHECK(!network_trans_.get());
+ DCHECK(!entry_);
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ SetRequest(net_log, request);
+
+ // We have to wait until the backend is initialized so we start the SM.
+ next_state_ = STATE_GET_BACKEND;
+ int rv = DoLoop(OK);
+
+ // Setting this here allows us to check for the existence of a callback_ to
+ // determine if we are still inside Start.
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartIgnoringLastError(
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(callback_.is_null());
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ int rv = RestartNetworkRequest();
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartWithCertificate(
+ X509Certificate* client_cert,
+ const CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(callback_.is_null());
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ int rv = RestartNetworkRequestWithCertificate(client_cert);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartWithAuth(
+ const AuthCredentials& credentials,
+ const CompletionCallback& callback) {
+ DCHECK(auth_response_.headers.get());
+ DCHECK(!callback.is_null());
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(callback_.is_null());
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ // Clear the intermediate response since we are going to start over.
+ auth_response_ = HttpResponseInfo();
+
+ int rv = RestartNetworkRequestWithAuth(credentials);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+bool HttpCache::Transaction::IsReadyToRestartForAuth() {
+ if (!network_trans_.get())
+ return false;
+ return network_trans_->IsReadyToRestartForAuth();
+}
+
+int HttpCache::Transaction::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(!callback.is_null());
+
+ DCHECK(callback_.is_null());
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ // If we have an intermediate auth response at this point, then it means the
+ // user wishes to read the network response (the error page). If there is a
+ // previous response in the cache then we should leave it intact.
+ if (auth_response_.headers.get() && mode_ != NONE) {
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ DCHECK(mode_ & WRITE);
+ DoneWritingToEntry(mode_ == READ_WRITE);
+ mode_ = NONE;
+ }
+
+ reading_ = true;
+ int rv;
+
+ switch (mode_) {
+ case READ_WRITE:
+ DCHECK(partial_.get());
+ if (!network_trans_.get()) {
+ // We are just reading from the cache, but we may be writing later.
+ rv = ReadFromEntry(buf, buf_len);
+ break;
+ }
+ case NONE:
+ case WRITE:
+ DCHECK(network_trans_.get());
+ rv = ReadFromNetwork(buf, buf_len);
+ break;
+ case READ:
+ rv = ReadFromEntry(buf, buf_len);
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ DCHECK(callback_.is_null());
+ callback_ = callback;
+ }
+ return rv;
+}
+
+void HttpCache::Transaction::StopCaching() {
+ // We really don't know where we are now. Hopefully there is no operation in
+ // progress, but nothing really prevents this method to be called after we
+ // returned ERR_IO_PENDING. We cannot attempt to truncate the entry at this
+ // point because we need the state machine for that (and even if we are really
+ // free, that would be an asynchronous operation). In other words, keep the
+ // entry how it is (it will be marked as truncated at destruction), and let
+ // the next piece of code that executes know that we are now reading directly
+ // from the net.
+ if (cache_.get() && entry_ && (mode_ & WRITE) && network_trans_.get() &&
+ !is_sparse_ && !range_requested_) {
+ mode_ = NONE;
+ }
+}
+
+bool HttpCache::Transaction::GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ if (network_trans_)
+ return network_trans_->GetFullRequestHeaders(headers);
+
+ // TODO(ttuttle): Read headers from cache.
+ return false;
+}
+
+void HttpCache::Transaction::DoneReading() {
+ if (cache_.get() && entry_) {
+ DCHECK(reading_);
+ DCHECK_NE(mode_, UPDATE);
+ if (mode_ & WRITE)
+ DoneWritingToEntry(true);
+ }
+}
+
+const HttpResponseInfo* HttpCache::Transaction::GetResponseInfo() const {
+ // Null headers means we encountered an error or haven't a response yet
+ if (auth_response_.headers.get())
+ return &auth_response_;
+ return (response_.headers.get() || response_.ssl_info.cert.get() ||
+ response_.cert_request_info.get())
+ ? &response_
+ : NULL;
+}
+
+LoadState HttpCache::Transaction::GetLoadState() const {
+ LoadState state = GetWriterLoadState();
+ if (state != LOAD_STATE_WAITING_FOR_CACHE)
+ return state;
+
+ if (cache_.get())
+ return cache_->GetLoadStateForPendingTransaction(this);
+
+ return LOAD_STATE_IDLE;
+}
+
+UploadProgress HttpCache::Transaction::GetUploadProgress() const {
+ if (network_trans_.get())
+ return network_trans_->GetUploadProgress();
+ return final_upload_progress_;
+}
+
+bool HttpCache::Transaction::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ if (network_trans_)
+ return network_trans_->GetLoadTimingInfo(load_timing_info);
+
+ if (old_network_trans_load_timing_) {
+ *load_timing_info = *old_network_trans_load_timing_;
+ return true;
+ }
+
+ if (first_cache_access_since_.is_null())
+ return false;
+
+ // If the cache entry was opened, return that time.
+ load_timing_info->send_start = first_cache_access_since_;
+ // This time doesn't make much sense when reading from the cache, so just use
+ // the same time as send_start.
+ load_timing_info->send_end = first_cache_access_since_;
+ return true;
+}
+
+void HttpCache::Transaction::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+ if (network_trans_)
+ network_trans_->SetPriority(priority_);
+}
+
+//-----------------------------------------------------------------------------
+
+void HttpCache::Transaction::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!callback_.is_null());
+
+ read_buf_ = NULL; // Release the buffer before invoking the callback.
+
+ // Since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback c = callback_;
+ callback_.Reset();
+ c.Run(rv);
+}
+
+int HttpCache::Transaction::HandleResult(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ if (!callback_.is_null())
+ DoCallback(rv);
+
+ return rv;
+}
+
+// A few common patterns: (Foo* means Foo -> FooComplete)
+//
+// Not-cached entry:
+// Start():
+// GetBackend* -> InitEntry -> OpenEntry* -> CreateEntry* -> AddToEntry* ->
+// SendRequest* -> SuccessfulSendRequest -> OverwriteCachedResponse ->
+// CacheWriteResponse* -> TruncateCachedData* -> TruncateCachedMetadata* ->
+// PartialHeadersReceived
+//
+// Read():
+// NetworkRead* -> CacheWriteData*
+//
+// Cached entry, no validation:
+// Start():
+// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*
+// -> BeginPartialCacheValidation() -> BeginCacheValidation()
+//
+// Read():
+// CacheReadData*
+//
+// Cached entry, validation (304):
+// Start():
+// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*
+// -> BeginPartialCacheValidation() -> BeginCacheValidation() ->
+// SendRequest* -> SuccessfulSendRequest -> UpdateCachedResponse ->
+// CacheWriteResponse* -> UpdateCachedResponseComplete ->
+// OverwriteCachedResponse -> PartialHeadersReceived
+//
+// Read():
+// CacheReadData*
+//
+// Cached entry, validation and replace (200):
+// Start():
+// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*
+// -> BeginPartialCacheValidation() -> BeginCacheValidation() ->
+// SendRequest* -> SuccessfulSendRequest -> OverwriteCachedResponse ->
+// CacheWriteResponse* -> DoTruncateCachedData* -> TruncateCachedMetadata* ->
+// PartialHeadersReceived
+//
+// Read():
+// NetworkRead* -> CacheWriteData*
+//
+// Sparse entry, partially cached, byte range request:
+// Start():
+// GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse*
+// -> BeginPartialCacheValidation() -> CacheQueryData* ->
+// ValidateEntryHeadersAndContinue() -> StartPartialCacheValidation ->
+// CompletePartialCacheValidation -> BeginCacheValidation() -> SendRequest* ->
+// SuccessfulSendRequest -> UpdateCachedResponse -> CacheWriteResponse* ->
+// UpdateCachedResponseComplete -> OverwriteCachedResponse ->
+// PartialHeadersReceived
+//
+// Read() 1:
+// NetworkRead* -> CacheWriteData*
+//
+// Read() 2:
+// NetworkRead* -> CacheWriteData* -> StartPartialCacheValidation ->
+// CompletePartialCacheValidation -> CacheReadData* ->
+//
+// Read() 3:
+// CacheReadData* -> StartPartialCacheValidation ->
+// CompletePartialCacheValidation -> BeginCacheValidation() -> SendRequest* ->
+// SuccessfulSendRequest -> UpdateCachedResponse* -> OverwriteCachedResponse
+// -> PartialHeadersReceived -> NetworkRead* -> CacheWriteData*
+//
+int HttpCache::Transaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GET_BACKEND:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetBackend();
+ break;
+ case STATE_GET_BACKEND_COMPLETE:
+ rv = DoGetBackendComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ break;
+ case STATE_SUCCESSFUL_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ rv = DoSuccessfulSendRequest();
+ break;
+ case STATE_NETWORK_READ:
+ DCHECK_EQ(OK, rv);
+ rv = DoNetworkRead();
+ break;
+ case STATE_NETWORK_READ_COMPLETE:
+ rv = DoNetworkReadComplete(rv);
+ break;
+ case STATE_INIT_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoInitEntry();
+ break;
+ case STATE_OPEN_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_CREATE_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoCreateEntry();
+ break;
+ case STATE_CREATE_ENTRY_COMPLETE:
+ rv = DoCreateEntryComplete(rv);
+ break;
+ case STATE_DOOM_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDoomEntry();
+ break;
+ case STATE_DOOM_ENTRY_COMPLETE:
+ rv = DoDoomEntryComplete(rv);
+ break;
+ case STATE_ADD_TO_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoAddToEntry();
+ break;
+ case STATE_ADD_TO_ENTRY_COMPLETE:
+ rv = DoAddToEntryComplete(rv);
+ break;
+ case STATE_ADD_TO_ENTRY_COMPLETE_AFTER_DELAY:
+ rv = DoAddToEntryCompleteAfterDelay(rv);
+ break;
+ case STATE_START_PARTIAL_CACHE_VALIDATION:
+ DCHECK_EQ(OK, rv);
+ rv = DoStartPartialCacheValidation();
+ break;
+ case STATE_COMPLETE_PARTIAL_CACHE_VALIDATION:
+ rv = DoCompletePartialCacheValidation(rv);
+ break;
+ case STATE_UPDATE_CACHED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoUpdateCachedResponse();
+ break;
+ case STATE_UPDATE_CACHED_RESPONSE_COMPLETE:
+ rv = DoUpdateCachedResponseComplete(rv);
+ break;
+ case STATE_OVERWRITE_CACHED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoOverwriteCachedResponse();
+ break;
+ case STATE_TRUNCATE_CACHED_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoTruncateCachedData();
+ break;
+ case STATE_TRUNCATE_CACHED_DATA_COMPLETE:
+ rv = DoTruncateCachedDataComplete(rv);
+ break;
+ case STATE_TRUNCATE_CACHED_METADATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoTruncateCachedMetadata();
+ break;
+ case STATE_TRUNCATE_CACHED_METADATA_COMPLETE:
+ rv = DoTruncateCachedMetadataComplete(rv);
+ break;
+ case STATE_PARTIAL_HEADERS_RECEIVED:
+ DCHECK_EQ(OK, rv);
+ rv = DoPartialHeadersReceived();
+ break;
+ case STATE_CACHE_READ_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadResponse();
+ break;
+ case STATE_CACHE_READ_RESPONSE_COMPLETE:
+ rv = DoCacheReadResponseComplete(rv);
+ break;
+ case STATE_CACHE_WRITE_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheWriteResponse();
+ break;
+ case STATE_CACHE_WRITE_TRUNCATED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheWriteTruncatedResponse();
+ break;
+ case STATE_CACHE_WRITE_RESPONSE_COMPLETE:
+ rv = DoCacheWriteResponseComplete(rv);
+ break;
+ case STATE_CACHE_READ_METADATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadMetadata();
+ break;
+ case STATE_CACHE_READ_METADATA_COMPLETE:
+ rv = DoCacheReadMetadataComplete(rv);
+ break;
+ case STATE_CACHE_QUERY_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheQueryData();
+ break;
+ case STATE_CACHE_QUERY_DATA_COMPLETE:
+ rv = DoCacheQueryDataComplete(rv);
+ break;
+ case STATE_CACHE_READ_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadData();
+ break;
+ case STATE_CACHE_READ_DATA_COMPLETE:
+ rv = DoCacheReadDataComplete(rv);
+ break;
+ case STATE_CACHE_WRITE_DATA:
+ rv = DoCacheWriteData(rv);
+ break;
+ case STATE_CACHE_WRITE_DATA_COMPLETE:
+ rv = DoCacheWriteDataComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ if (rv != ERR_IO_PENDING)
+ HandleResult(rv);
+
+ return rv;
+}
+
+int HttpCache::Transaction::DoGetBackend() {
+ cache_pending_ = true;
+ next_state_ = STATE_GET_BACKEND_COMPLETE;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_GET_BACKEND);
+ ReportCacheActionStart();
+ return cache_->GetBackendForTransaction(this);
+}
+
+int HttpCache::Transaction::DoGetBackendComplete(int result) {
+ DCHECK(result == OK || result == ERR_FAILED);
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_GET_BACKEND,
+ result);
+ cache_pending_ = false;
+
+ if (!ShouldPassThrough()) {
+ cache_key_ = cache_->GenerateCacheKey(request_);
+
+ // Requested cache access mode.
+ if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) {
+ mode_ = READ;
+ } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) {
+ mode_ = WRITE;
+ } else {
+ mode_ = READ_WRITE;
+ }
+
+ // Downgrade to UPDATE if the request has been externally conditionalized.
+ if (external_validation_.initialized) {
+ if (mode_ & WRITE) {
+ // Strip off the READ_DATA bit (and maybe add back a READ_META bit
+ // in case READ was off).
+ mode_ = UPDATE;
+ } else {
+ mode_ = NONE;
+ }
+ }
+ }
+
+ // Use PUT and DELETE only to invalidate existing stored entries.
+ if ((request_->method == "PUT" || request_->method == "DELETE") &&
+ mode_ != READ_WRITE && mode_ != WRITE) {
+ mode_ = NONE;
+ }
+
+ // If must use cache, then we must fail. This can happen for back/forward
+ // navigations to a page generated via a form post.
+ if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
+ return ERR_CACHE_MISS;
+
+ if (mode_ == NONE) {
+ if (partial_.get()) {
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ partial_.reset();
+ }
+ next_state_ = STATE_SEND_REQUEST;
+ } else {
+ next_state_ = STATE_INIT_ENTRY;
+ }
+
+ // This is only set if we have something to do with the response.
+ range_requested_ = (partial_.get() != NULL);
+
+ return OK;
+}
+
+int HttpCache::Transaction::DoSendRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(!network_trans_.get());
+
+ send_request_since_ = TimeTicks::Now();
+
+ // Create a network transaction.
+ int rv = cache_->network_layer_->CreateTransaction(
+ priority_, &network_trans_, NULL);
+ if (rv != OK)
+ return rv;
+
+ // Old load timing information, if any, is now obsolete.
+ old_network_trans_load_timing_.reset();
+
+ ReportNetworkActionStart();
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ rv = network_trans_->Start(request_, io_callback_, net_log_);
+ return rv;
+}
+
+int HttpCache::Transaction::DoSendRequestComplete(int result) {
+ ReportNetworkActionFinish();
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ // If requested, and we have a readable cache entry, and we have
+ // an error indicating that we're offline as opposed to in contact
+ // with a bad server, read from cache anyway.
+ if (IsOfflineError(result)) {
+ if (mode_ == READ_WRITE && entry_ && !partial_) {
+ RecordOfflineStatus(effective_load_flags_,
+ OFFLINE_STATUS_DATA_AVAILABLE_OFFLINE);
+ if (effective_load_flags_ & LOAD_FROM_CACHE_IF_OFFLINE) {
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ response_.server_data_unavailable = true;
+ return SetupEntryForRead();
+ }
+ } else {
+ RecordOfflineStatus(effective_load_flags_,
+ OFFLINE_STATUS_DATA_UNAVAILABLE_OFFLINE);
+ }
+ } else {
+ RecordOfflineStatus(effective_load_flags_,
+ (result == OK ? OFFLINE_STATUS_NETWORK_SUCCEEDED :
+ OFFLINE_STATUS_NETWORK_FAILED));
+ }
+
+ // If we tried to conditionalize the request and failed, we know
+ // we won't be reading from the cache after this point.
+ if (couldnt_conditionalize_request_)
+ mode_ = WRITE;
+
+ if (result == OK) {
+ next_state_ = STATE_SUCCESSFUL_SEND_REQUEST;
+ return OK;
+ }
+
+ // Do not record requests that have network errors or restarts.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ if (IsCertificateError(result)) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ // If we get a certificate error, then there is a certificate in ssl_info,
+ // so GetResponseInfo() should never return NULL here.
+ DCHECK(response);
+ response_.ssl_info = response->ssl_info;
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ DCHECK(response);
+ response_.cert_request_info = response->cert_request_info;
+ } else if (response_.was_cached) {
+ DoneWritingToEntry(true);
+ }
+ return result;
+}
+
+// We received the response headers and there is no error.
+int HttpCache::Transaction::DoSuccessfulSendRequest() {
+ DCHECK(!new_response_);
+ const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
+
+ if (new_response->headers->response_code() == 401 ||
+ new_response->headers->response_code() == 407) {
+ auth_response_ = *new_response;
+ return OK;
+ }
+
+ new_response_ = new_response;
+ if (!ValidatePartialResponse() && !auth_response_.headers.get()) {
+ // Something went wrong with this request and we have to restart it.
+ // If we have an authentication response, we are exposed to weird things
+ // hapenning if the user cancels the authentication before we receive
+ // the new response.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ response_ = HttpResponseInfo();
+ ResetNetworkTransaction();
+ new_response_ = NULL;
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (handling_206_ && mode_ == READ_WRITE && !truncated_ && !is_sparse_) {
+ // We have stored the full entry, but it changed and the server is
+ // sending a range. We have to delete the old entry.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ DoneWritingToEntry(false);
+ }
+ if (new_response_->headers->response_code() == 416 &&
+ (request_->method == "GET" || request_->method == "POST")) {
+ DCHECK_EQ(NONE, mode_);
+ response_ = *new_response_;
+ return OK;
+ }
+
+ if (mode_ == WRITE &&
+ transaction_pattern_ != PATTERN_ENTRY_CANT_CONDITIONALIZE) {
+ UpdateTransactionPattern(PATTERN_ENTRY_NOT_CACHED);
+ }
+
+ if (mode_ == WRITE &&
+ (request_->method == "PUT" || request_->method == "DELETE")) {
+ if (NonErrorResponse(new_response->headers->response_code())) {
+ int ret = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, ret);
+ }
+ cache_->DoneWritingToEntry(entry_, true);
+ entry_ = NULL;
+ mode_ = NONE;
+ }
+
+ if (mode_ != NONE && request_->method == "POST" &&
+ NonErrorResponse(new_response->headers->response_code())) {
+ cache_->DoomMainEntryForUrl(request_->url);
+ }
+
+ // Are we expecting a response to a conditional query?
+ if (mode_ == READ_WRITE || mode_ == UPDATE) {
+ if (new_response->headers->response_code() == 304 || handling_206_) {
+ UpdateTransactionPattern(PATTERN_ENTRY_VALIDATED);
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE;
+ return OK;
+ }
+ UpdateTransactionPattern(PATTERN_ENTRY_UPDATED);
+ mode_ = WRITE;
+ }
+
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoNetworkRead() {
+ ReportNetworkActionStart();
+ next_state_ = STATE_NETWORK_READ_COMPLETE;
+ return network_trans_->Read(read_buf_.get(), io_buf_len_, io_callback_);
+}
+
+int HttpCache::Transaction::DoNetworkReadComplete(int result) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+
+ ReportNetworkActionFinish();
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ // If there is an error or we aren't saving the data, we are done; just wait
+ // until the destructor runs to see if we can keep the data.
+ if (mode_ == NONE || result < 0)
+ return result;
+
+ next_state_ = STATE_CACHE_WRITE_DATA;
+ return result;
+}
+
+int HttpCache::Transaction::DoInitEntry() {
+ DCHECK(!new_entry_);
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ if (mode_ == WRITE) {
+ next_state_ = STATE_DOOM_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int HttpCache::Transaction::DoOpenEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
+ first_cache_access_since_ = TimeTicks::Now();
+ ReportCacheActionStart();
+ defer_cache_sensitivity_delay_ = true;
+ return ResetCacheIOStart(cache_->OpenEntry(cache_key_, &new_entry_, this));
+}
+
+int HttpCache::Transaction::DoOpenEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY, result);
+ cache_pending_ = false;
+ if (result == OK) {
+ next_state_ = STATE_ADD_TO_ENTRY;
+ return OK;
+ }
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (request_->method == "PUT" || request_->method == "DELETE") {
+ DCHECK(mode_ == READ_WRITE || mode_ == WRITE);
+ mode_ = NONE;
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+
+ if (mode_ == READ_WRITE) {
+ mode_ = WRITE;
+ next_state_ = STATE_CREATE_ENTRY;
+ return OK;
+ }
+ if (mode_ == UPDATE) {
+ // There is no cache entry to update; proceed without caching.
+ mode_ = NONE;
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (cache_->mode() == PLAYBACK)
+ DVLOG(1) << "Playback Cache Miss: " << request_->url;
+
+ // The entry does not exist, and we are not permitted to create a new entry,
+ // so we must fail.
+ return ERR_CACHE_MISS;
+}
+
+int HttpCache::Transaction::DoCreateEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_CREATE_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
+ ReportCacheActionStart();
+ defer_cache_sensitivity_delay_ = true;
+ return ResetCacheIOStart(cache_->CreateEntry(cache_key_, &new_entry_, this));
+}
+
+int HttpCache::Transaction::DoCreateEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY,
+ result);
+ cache_pending_ = false;
+ next_state_ = STATE_ADD_TO_ENTRY;
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result == OK) {
+ UMA_HISTOGRAM_BOOLEAN("HttpCache.OpenToCreateRace", false);
+ } else {
+ UMA_HISTOGRAM_BOOLEAN("HttpCache.OpenToCreateRace", true);
+ // We have a race here: Maybe we failed to open the entry and decided to
+ // create one, but by the time we called create, another transaction already
+ // created the entry. If we want to eliminate this issue, we need an atomic
+ // OpenOrCreate() method exposed by the disk cache.
+ DLOG(WARNING) << "Unable to create cache entry";
+ mode_ = NONE;
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoDoomEntry() {
+ next_state_ = STATE_DOOM_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ if (first_cache_access_since_.is_null())
+ first_cache_access_since_ = TimeTicks::Now();
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY);
+ ReportCacheActionStart();
+ return ResetCacheIOStart(cache_->DoomEntry(cache_key_, this));
+}
+
+int HttpCache::Transaction::DoDoomEntryComplete(int result) {
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY, result);
+ next_state_ = STATE_CREATE_ENTRY;
+ cache_pending_ = false;
+ if (result == ERR_CACHE_RACE)
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+}
+
+int HttpCache::Transaction::DoAddToEntry() {
+ DCHECK(new_entry_);
+ cache_pending_ = true;
+ next_state_ = STATE_ADD_TO_ENTRY_COMPLETE;
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY);
+ DCHECK(entry_lock_waiting_since_.is_null());
+ entry_lock_waiting_since_ = TimeTicks::Now();
+ return cache_->AddTransactionToEntry(new_entry_, this);
+}
+
+int HttpCache::Transaction::DoAddToEntryComplete(int result) {
+ DCHECK(defer_cache_sensitivity_delay_);
+ defer_cache_sensitivity_delay_ = false;
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY,
+ result);
+ const TimeDelta entry_lock_wait =
+ TimeTicks::Now() - entry_lock_waiting_since_;
+ UMA_HISTOGRAM_TIMES("HttpCache.EntryLockWait", entry_lock_wait);
+
+ entry_lock_waiting_since_ = TimeTicks();
+ DCHECK(new_entry_);
+ cache_pending_ = false;
+
+ if (result == OK)
+ entry_ = new_entry_;
+
+ // If there is a failure, the cache should have taken care of new_entry_.
+ new_entry_ = NULL;
+
+ next_state_ = STATE_ADD_TO_ENTRY_COMPLETE_AFTER_DELAY;
+
+ if (deferred_cache_sensitivity_delay_ == base::TimeDelta())
+ return result;
+
+ base::TimeDelta delay = deferred_cache_sensitivity_delay_;
+ deferred_cache_sensitivity_delay_ = base::TimeDelta();
+ ScheduleDelayedLoop(delay, result);
+ return ERR_IO_PENDING;
+}
+
+int HttpCache::Transaction::DoAddToEntryCompleteAfterDelay(int result) {
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result != OK) {
+ NOTREACHED();
+ return result;
+ }
+
+ if (mode_ == WRITE) {
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ } else {
+ // We have to read the headers from the cached entry.
+ DCHECK(mode_ & READ_META);
+ next_state_ = STATE_CACHE_READ_RESPONSE;
+ }
+ return OK;
+}
+
+// We may end up here multiple times for a given request.
+int HttpCache::Transaction::DoStartPartialCacheValidation() {
+ if (mode_ == NONE)
+ return OK;
+
+ next_state_ = STATE_COMPLETE_PARTIAL_CACHE_VALIDATION;
+ return ResetCacheIOStart(
+ partial_->ShouldValidateCache(entry_->disk_entry, io_callback_));
+}
+
+int HttpCache::Transaction::DoCompletePartialCacheValidation(int result) {
+ if (!result) {
+ // This is the end of the request.
+ if (mode_ & WRITE) {
+ DoneWritingToEntry(true);
+ } else {
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ return result;
+ }
+
+ if (result < 0)
+ return result;
+
+ partial_->PrepareCacheValidation(entry_->disk_entry,
+ &custom_request_->extra_headers);
+
+ if (reading_ && partial_->IsCurrentRangeCached()) {
+ next_state_ = STATE_CACHE_READ_DATA;
+ return OK;
+ }
+
+ return BeginCacheValidation();
+}
+
+// We received 304 or 206 and we want to update the cached response headers.
+int HttpCache::Transaction::DoUpdateCachedResponse() {
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ int rv = OK;
+ // Update cached response based on headers in new_response.
+ // TODO(wtc): should we update cached certificate (response_.ssl_info), too?
+ response_.headers->Update(*new_response_->headers.get());
+ response_.response_time = new_response_->response_time;
+ response_.request_time = new_response_->request_time;
+ response_.network_accessed = new_response_->network_accessed;
+
+ if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
+ if (!entry_->doomed) {
+ int ret = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, ret);
+ }
+ } else {
+ // If we are already reading, we already updated the headers for this
+ // request; doing it again will change Content-Length.
+ if (!reading_) {
+ target_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ next_state_ = STATE_CACHE_WRITE_RESPONSE;
+ rv = OK;
+ }
+ }
+ return rv;
+}
+
+int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) {
+ if (mode_ == UPDATE) {
+ DCHECK(!handling_206_);
+ // We got a "not modified" response and already updated the corresponding
+ // cache entry above.
+ //
+ // By closing the cached entry now, we make sure that the 304 rather than
+ // the cached 200 response, is what will be returned to the user.
+ DoneWritingToEntry(true);
+ } else if (entry_ && !handling_206_) {
+ DCHECK_EQ(READ_WRITE, mode_);
+ if (!partial_.get() || partial_->IsLastRange()) {
+ cache_->ConvertWriterToReader(entry_);
+ mode_ = READ;
+ }
+ // We no longer need the network transaction, so destroy it.
+ final_upload_progress_ = network_trans_->GetUploadProgress();
+ ResetNetworkTransaction();
+ } else if (entry_ && handling_206_ && truncated_ &&
+ partial_->initial_validation()) {
+ // We just finished the validation of a truncated entry, and the server
+ // is willing to resume the operation. Now we go back and start serving
+ // the first part to the user.
+ ResetNetworkTransaction();
+ new_response_ = NULL;
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
+ partial_->SetRangeToStartDownload();
+ return OK;
+ }
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoOverwriteCachedResponse() {
+ if (mode_ & READ) {
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ }
+
+ // We change the value of Content-Length for partial content.
+ if (handling_206_ && partial_.get())
+ partial_->FixContentLength(new_response_->headers.get());
+
+ response_ = *new_response_;
+
+ if (handling_206_ && !CanResume(false)) {
+ // There is no point in storing this resource because it will never be used.
+ DoneWritingToEntry(false);
+ if (partial_.get())
+ partial_->FixResponseHeaders(response_.headers.get(), true);
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ }
+
+ target_state_ = STATE_TRUNCATE_CACHED_DATA;
+ next_state_ = truncated_ ? STATE_CACHE_WRITE_TRUNCATED_RESPONSE :
+ STATE_CACHE_WRITE_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoTruncateCachedData() {
+ next_state_ = STATE_TRUNCATE_CACHED_DATA_COMPLETE;
+ if (!entry_)
+ return OK;
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WRITE_DATA);
+ ReportCacheActionStart();
+ // Truncate the stream.
+ return ResetCacheIOStart(
+ WriteToEntry(kResponseContentIndex, 0, NULL, 0, io_callback_));
+}
+
+int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) {
+ if (entry_) {
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_WRITE_DATA,
+ result);
+ }
+ }
+
+ next_state_ = STATE_TRUNCATE_CACHED_METADATA;
+ return OK;
+}
+
+int HttpCache::Transaction::DoTruncateCachedMetadata() {
+ next_state_ = STATE_TRUNCATE_CACHED_METADATA_COMPLETE;
+ if (!entry_)
+ return OK;
+
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WRITE_INFO);
+ ReportCacheActionStart();
+ return ResetCacheIOStart(
+ WriteToEntry(kMetadataIndex, 0, NULL, 0, io_callback_));
+}
+
+int HttpCache::Transaction::DoTruncateCachedMetadataComplete(int result) {
+ if (entry_) {
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_WRITE_INFO,
+ result);
+ }
+ }
+
+ // If this response is a redirect, then we can stop writing now. (We don't
+ // need to cache the response body of a redirect.)
+ if (response_.headers->IsRedirect(NULL))
+ DoneWritingToEntry(true);
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+}
+
+int HttpCache::Transaction::DoPartialHeadersReceived() {
+ new_response_ = NULL;
+ if (entry_ && !partial_.get() &&
+ entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
+
+ if (!partial_.get())
+ return OK;
+
+ if (reading_) {
+ if (network_trans_.get()) {
+ next_state_ = STATE_NETWORK_READ;
+ } else {
+ next_state_ = STATE_CACHE_READ_DATA;
+ }
+ } else if (mode_ != NONE) {
+ // We are about to return the headers for a byte-range request to the user,
+ // so let's fix them.
+ partial_->FixResponseHeaders(response_.headers.get(), true);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheReadResponse() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_RESPONSE_COMPLETE;
+
+ io_buf_len_ = entry_->disk_entry->GetDataSize(kResponseInfoIndex);
+ read_buf_ = new IOBuffer(io_buf_len_);
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO);
+ ReportCacheActionStart();
+ return ResetCacheIOStart(entry_->disk_entry->ReadData(
+ kResponseInfoIndex, 0, read_buf_.get(), io_buf_len_, io_callback_));
+}
+
+int HttpCache::Transaction::DoCacheReadResponseComplete(int result) {
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_READ_INFO, result);
+ if (result != io_buf_len_ ||
+ !HttpCache::ParseResponseInfo(read_buf_->data(), io_buf_len_,
+ &response_, &truncated_)) {
+ return OnCacheReadError(result, true);
+ }
+
+ // Some resources may have slipped in as truncated when they're not.
+ int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
+ if (response_.headers->GetContentLength() == current_size)
+ truncated_ = false;
+
+ // We now have access to the cache entry.
+ //
+ // o if we are a reader for the transaction, then we can start reading the
+ // cache entry.
+ //
+ // o if we can read or write, then we should check if the cache entry needs
+ // to be validated and then issue a network request if needed or just read
+ // from the cache if the cache entry is already valid.
+ //
+ // o if we are set to UPDATE, then we are handling an externally
+ // conditionalized request (if-modified-since / if-none-match). We check
+ // if the request headers define a validation request.
+ //
+ switch (mode_) {
+ case READ:
+ UpdateTransactionPattern(PATTERN_ENTRY_USED);
+ result = BeginCacheRead();
+ break;
+ case READ_WRITE:
+ result = BeginPartialCacheValidation();
+ break;
+ case UPDATE:
+ result = BeginExternallyConditionalizedRequest();
+ break;
+ case WRITE:
+ default:
+ NOTREACHED();
+ result = ERR_FAILED;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteResponse() {
+ if (entry_) {
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WRITE_INFO);
+ ReportCacheActionStart();
+ }
+ return WriteResponseInfoToEntry(false);
+}
+
+int HttpCache::Transaction::DoCacheWriteTruncatedResponse() {
+ if (entry_) {
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WRITE_INFO);
+ ReportCacheActionStart();
+ }
+ return WriteResponseInfoToEntry(true);
+}
+
+int HttpCache::Transaction::DoCacheWriteResponseComplete(int result) {
+ next_state_ = target_state_;
+ target_state_ = STATE_NONE;
+ if (!entry_)
+ return OK;
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_WRITE_INFO,
+ result);
+ }
+
+ // Balance the AddRef from WriteResponseInfoToEntry.
+ if (result != io_buf_len_) {
+ DLOG(ERROR) << "failed to write response info to cache";
+ DoneWritingToEntry(false);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheReadMetadata() {
+ DCHECK(entry_);
+ DCHECK(!response_.metadata.get());
+ next_state_ = STATE_CACHE_READ_METADATA_COMPLETE;
+
+ response_.metadata =
+ new IOBufferWithSize(entry_->disk_entry->GetDataSize(kMetadataIndex));
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_INFO);
+ ReportCacheActionStart();
+ return ResetCacheIOStart(
+ entry_->disk_entry->ReadData(kMetadataIndex,
+ 0,
+ response_.metadata.get(),
+ response_.metadata->size(),
+ io_callback_));
+}
+
+int HttpCache::Transaction::DoCacheReadMetadataComplete(int result) {
+ ReportCacheActionFinish();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_READ_INFO, result);
+ if (result != response_.metadata->size())
+ return OnCacheReadError(result, false);
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheQueryData() {
+ next_state_ = STATE_CACHE_QUERY_DATA_COMPLETE;
+
+ // Balanced in DoCacheQueryDataComplete.
+ return ResetCacheIOStart(
+ entry_->disk_entry->ReadyForSparseIO(io_callback_));
+}
+
+int HttpCache::Transaction::DoCacheQueryDataComplete(int result) {
+ DCHECK_EQ(OK, result);
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ return ValidateEntryHeadersAndContinue();
+}
+
+int HttpCache::Transaction::DoCacheReadData() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_DATA_COMPLETE;
+
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_READ_DATA);
+ ReportCacheActionStart();
+ if (partial_.get()) {
+ return ResetCacheIOStart(partial_->CacheRead(
+ entry_->disk_entry, read_buf_.get(), io_buf_len_, io_callback_));
+ }
+
+ return ResetCacheIOStart(entry_->disk_entry->ReadData(kResponseContentIndex,
+ read_offset_,
+ read_buf_.get(),
+ io_buf_len_,
+ io_callback_));
+}
+
+int HttpCache::Transaction::DoCacheReadDataComplete(int result) {
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_READ_DATA,
+ result);
+ }
+
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ if (partial_.get()) {
+ // Partial requests are confusing to report in histograms because they may
+ // have multiple underlying requests.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ return DoPartialCacheReadCompleted(result);
+ }
+
+ if (result > 0) {
+ read_offset_ += result;
+ } else if (result == 0) { // End of file.
+ RecordHistograms();
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ } else {
+ return OnCacheReadError(result, false);
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteData(int num_bytes) {
+ next_state_ = STATE_CACHE_WRITE_DATA_COMPLETE;
+ write_len_ = num_bytes;
+ if (entry_) {
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WRITE_DATA);
+ ReportCacheActionStart();
+ }
+
+ return ResetCacheIOStart(
+ AppendResponseDataToEntry(read_buf_.get(), num_bytes, io_callback_));
+}
+
+int HttpCache::Transaction::DoCacheWriteDataComplete(int result) {
+ if (entry_) {
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_HTTP_CACHE_WRITE_DATA,
+ result);
+ }
+ }
+ // Balance the AddRef from DoCacheWriteData.
+ if (!cache_.get())
+ return ERR_UNEXPECTED;
+
+ if (result != write_len_) {
+ DLOG(ERROR) << "failed to write response data to cache";
+ DoneWritingToEntry(false);
+
+ // We want to ignore errors writing to disk and just keep reading from
+ // the network.
+ result = write_len_;
+ } else if (!done_reading_ && entry_) {
+ int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
+ int64 body_size = response_.headers->GetContentLength();
+ if (body_size >= 0 && body_size <= current_size)
+ done_reading_ = true;
+ }
+
+ if (partial_.get()) {
+ // This may be the last request.
+ if (!(result == 0 && !truncated_ &&
+ (partial_->IsLastRange() || mode_ == WRITE)))
+ return DoPartialNetworkReadCompleted(result);
+ }
+
+ if (result == 0) {
+ // End of file. This may be the result of a connection problem so see if we
+ // have to keep the entry around to be flagged as truncated later on.
+ if (done_reading_ || !entry_ || partial_.get() ||
+ response_.headers->GetContentLength() <= 0)
+ DoneWritingToEntry(true);
+ }
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+void HttpCache::Transaction::SetRequest(const BoundNetLog& net_log,
+ const HttpRequestInfo* request) {
+ net_log_ = net_log;
+ request_ = request;
+ effective_load_flags_ = request_->load_flags;
+
+ switch (cache_->mode()) {
+ case NORMAL:
+ break;
+ case RECORD:
+ // When in record mode, we want to NEVER load from the cache.
+ // The reason for this is beacuse we save the Set-Cookie headers
+ // (intentionally). If we read from the cache, we replay them
+ // prematurely.
+ effective_load_flags_ |= LOAD_BYPASS_CACHE;
+ break;
+ case PLAYBACK:
+ // When in playback mode, we want to load exclusively from the cache.
+ effective_load_flags_ |= LOAD_ONLY_FROM_CACHE;
+ break;
+ case DISABLE:
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ break;
+ }
+
+ // Some headers imply load flags. The order here is significant.
+ //
+ // LOAD_DISABLE_CACHE : no cache read or write
+ // LOAD_BYPASS_CACHE : no cache read
+ // LOAD_VALIDATE_CACHE : no cache read unless validation
+ //
+ // The former modes trump latter modes, so if we find a matching header we
+ // can stop iterating kSpecialHeaders.
+ //
+ static const struct {
+ const HeaderNameAndValue* search;
+ int load_flag;
+ } kSpecialHeaders[] = {
+ { kPassThroughHeaders, LOAD_DISABLE_CACHE },
+ { kForceFetchHeaders, LOAD_BYPASS_CACHE },
+ { kForceValidateHeaders, LOAD_VALIDATE_CACHE },
+ };
+
+ bool range_found = false;
+ bool external_validation_error = false;
+
+ if (request_->extra_headers.HasHeader(HttpRequestHeaders::kRange))
+ range_found = true;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSpecialHeaders); ++i) {
+ if (HeaderMatches(request_->extra_headers, kSpecialHeaders[i].search)) {
+ effective_load_flags_ |= kSpecialHeaders[i].load_flag;
+ break;
+ }
+ }
+
+ // Check for conditionalization headers which may correspond with a
+ // cache validation request.
+ for (size_t i = 0; i < arraysize(kValidationHeaders); ++i) {
+ const ValidationHeaderInfo& info = kValidationHeaders[i];
+ std::string validation_value;
+ if (request_->extra_headers.GetHeader(
+ info.request_header_name, &validation_value)) {
+ if (!external_validation_.values[i].empty() ||
+ validation_value.empty()) {
+ external_validation_error = true;
+ }
+ external_validation_.values[i] = validation_value;
+ external_validation_.initialized = true;
+ }
+ }
+
+ // We don't support ranges and validation headers.
+ if (range_found && external_validation_.initialized) {
+ LOG(WARNING) << "Byte ranges AND validation headers found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ }
+
+ // If there is more than one validation header, we can't treat this request as
+ // a cache validation, since we don't know for sure which header the server
+ // will give us a response for (and they could be contradictory).
+ if (external_validation_error) {
+ LOG(WARNING) << "Multiple or malformed validation headers found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ }
+
+ if (range_found && !(effective_load_flags_ & LOAD_DISABLE_CACHE)) {
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ partial_.reset(new PartialData);
+ if (request_->method == "GET" && partial_->Init(request_->extra_headers)) {
+ // We will be modifying the actual range requested to the server, so
+ // let's remove the header here.
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ custom_request_->extra_headers.RemoveHeader(HttpRequestHeaders::kRange);
+ request_ = custom_request_.get();
+ partial_->SetHeaders(custom_request_->extra_headers);
+ } else {
+ // The range is invalid or we cannot handle it properly.
+ VLOG(1) << "Invalid byte range found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ partial_.reset(NULL);
+ }
+ }
+}
+
+bool HttpCache::Transaction::ShouldPassThrough() {
+ // We may have a null disk_cache if there is an error we cannot recover from,
+ // like not enough disk space, or sharing violations.
+ if (!cache_->disk_cache_.get())
+ return true;
+
+ // When using the record/playback modes, we always use the cache
+ // and we never pass through.
+ if (cache_->mode() == RECORD || cache_->mode() == PLAYBACK)
+ return false;
+
+ if (effective_load_flags_ & LOAD_DISABLE_CACHE)
+ return true;
+
+ if (request_->method == "GET")
+ return false;
+
+ if (request_->method == "POST" && request_->upload_data_stream &&
+ request_->upload_data_stream->identifier()) {
+ return false;
+ }
+
+ if (request_->method == "PUT" && request_->upload_data_stream)
+ return false;
+
+ if (request_->method == "DELETE")
+ return false;
+
+ // TODO(darin): add support for caching HEAD responses
+ return true;
+}
+
+int HttpCache::Transaction::BeginCacheRead() {
+ // We don't support any combination of LOAD_ONLY_FROM_CACHE and byte ranges.
+ if (response_.headers->response_code() == 206 || partial_.get()) {
+ NOTREACHED();
+ return ERR_CACHE_MISS;
+ }
+
+ // We don't have the whole resource.
+ if (truncated_)
+ return ERR_CACHE_MISS;
+
+ if (entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
+
+ return OK;
+}
+
+int HttpCache::Transaction::BeginCacheValidation() {
+ DCHECK(mode_ == READ_WRITE);
+
+ bool skip_validation = !RequiresValidation();
+
+ if (truncated_) {
+ // Truncated entries can cause partial gets, so we shouldn't record this
+ // load in histograms.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ skip_validation = !partial_->initial_validation();
+ }
+
+ if (partial_.get() && (is_sparse_ || truncated_) &&
+ (!partial_->IsCurrentRangeCached() || invalid_range_)) {
+ // Force revalidation for sparse or truncated entries. Note that we don't
+ // want to ignore the regular validation logic just because a byte range was
+ // part of the request.
+ skip_validation = false;
+ }
+
+ if (skip_validation) {
+ UpdateTransactionPattern(PATTERN_ENTRY_USED);
+ RecordOfflineStatus(effective_load_flags_, OFFLINE_STATUS_FRESH_CACHE);
+ return SetupEntryForRead();
+ } else {
+ // Make the network request conditional, to see if we may reuse our cached
+ // response. If we cannot do so, then we just resort to a normal fetch.
+ // Our mode remains READ_WRITE for a conditional request. Even if the
+ // conditionalization fails, we don't switch to WRITE mode until we
+ // know we won't be falling back to using the cache entry in the
+ // LOAD_FROM_CACHE_IF_OFFLINE case.
+ if (!ConditionalizeRequest()) {
+ couldnt_conditionalize_request_ = true;
+ UpdateTransactionPattern(PATTERN_ENTRY_CANT_CONDITIONALIZE);
+ if (partial_.get())
+ return DoRestartPartialRequest();
+
+ DCHECK_NE(206, response_.headers->response_code());
+ }
+ next_state_ = STATE_SEND_REQUEST;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::BeginPartialCacheValidation() {
+ DCHECK(mode_ == READ_WRITE);
+
+ if (response_.headers->response_code() != 206 && !partial_.get() &&
+ !truncated_) {
+ return BeginCacheValidation();
+ }
+
+ // Partial requests should not be recorded in histograms.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ if (range_requested_) {
+ next_state_ = STATE_CACHE_QUERY_DATA;
+ return OK;
+ }
+ // The request is not for a range, but we have stored just ranges.
+ partial_.reset(new PartialData());
+ partial_->SetHeaders(request_->extra_headers);
+ if (!custom_request_.get()) {
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+ }
+
+ return ValidateEntryHeadersAndContinue();
+}
+
+// This should only be called once per request.
+int HttpCache::Transaction::ValidateEntryHeadersAndContinue() {
+ DCHECK(mode_ == READ_WRITE);
+
+ if (!partial_->UpdateFromStoredHeaders(
+ response_.headers.get(), entry_->disk_entry, truncated_)) {
+ return DoRestartPartialRequest();
+ }
+
+ if (response_.headers->response_code() == 206)
+ is_sparse_ = true;
+
+ if (!partial_->IsRequestedRangeOK()) {
+ // The stored data is fine, but the request may be invalid.
+ invalid_range_ = true;
+ }
+
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
+ return OK;
+}
+
+int HttpCache::Transaction::BeginExternallyConditionalizedRequest() {
+ DCHECK_EQ(UPDATE, mode_);
+ DCHECK(external_validation_.initialized);
+
+ for (size_t i = 0; i < arraysize(kValidationHeaders); i++) {
+ if (external_validation_.values[i].empty())
+ continue;
+ // Retrieve either the cached response's "etag" or "last-modified" header.
+ std::string validator;
+ response_.headers->EnumerateHeader(
+ NULL,
+ kValidationHeaders[i].related_response_header_name,
+ &validator);
+
+ if (response_.headers->response_code() != 200 || truncated_ ||
+ validator.empty() || validator != external_validation_.values[i]) {
+ // The externally conditionalized request is not a validation request
+ // for our existing cache entry. Proceed with caching disabled.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ DoneWritingToEntry(true);
+ }
+ }
+
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+}
+
+int HttpCache::Transaction::RestartNetworkRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ ReportNetworkActionStart();
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartIgnoringLastError(io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequestWithCertificate(
+ X509Certificate* client_cert) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ ReportNetworkActionStart();
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartWithCertificate(client_cert, io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequestWithAuth(
+ const AuthCredentials& credentials) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ ReportNetworkActionStart();
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartWithAuth(credentials, io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(rv);
+ return rv;
+}
+
+bool HttpCache::Transaction::RequiresValidation() {
+ // TODO(darin): need to do more work here:
+ // - make sure we have a matching request method
+ // - watch out for cached responses that depend on authentication
+
+ // In playback mode, nothing requires validation.
+ if (cache_->mode() == net::HttpCache::PLAYBACK)
+ return false;
+
+ if (response_.vary_data.is_valid() &&
+ !response_.vary_data.MatchesRequest(*request_,
+ *response_.headers.get())) {
+ vary_mismatch_ = true;
+ return true;
+ }
+
+ if (effective_load_flags_ & LOAD_PREFERRING_CACHE)
+ return false;
+
+ if (effective_load_flags_ & LOAD_VALIDATE_CACHE)
+ return true;
+
+ if (request_->method == "PUT" || request_->method == "DELETE")
+ return true;
+
+ if (response_.headers->RequiresValidation(
+ response_.request_time, response_.response_time, Time::Now())) {
+ return true;
+ }
+
+ return false;
+}
+
+bool HttpCache::Transaction::ConditionalizeRequest() {
+ DCHECK(response_.headers.get());
+
+ if (request_->method == "PUT" || request_->method == "DELETE")
+ return false;
+
+ // This only makes sense for cached 200 or 206 responses.
+ if (response_.headers->response_code() != 200 &&
+ response_.headers->response_code() != 206) {
+ return false;
+ }
+
+ // We should have handled this case before.
+ DCHECK(response_.headers->response_code() != 206 ||
+ response_.headers->HasStrongValidators());
+
+ // Just use the first available ETag and/or Last-Modified header value.
+ // TODO(darin): Or should we use the last?
+
+ std::string etag_value;
+ if (response_.headers->GetHttpVersion() >= HttpVersion(1, 1))
+ response_.headers->EnumerateHeader(NULL, "etag", &etag_value);
+
+ std::string last_modified_value;
+ if (!vary_mismatch_) {
+ response_.headers->EnumerateHeader(NULL, "last-modified",
+ &last_modified_value);
+ }
+
+ if (etag_value.empty() && last_modified_value.empty())
+ return false;
+
+ if (!partial_.get()) {
+ // Need to customize the request, so this forces us to allocate :(
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+ }
+ DCHECK(custom_request_.get());
+
+ bool use_if_range = partial_.get() && !partial_->IsCurrentRangeCached() &&
+ !invalid_range_;
+
+ if (!etag_value.empty()) {
+ if (use_if_range) {
+ // We don't want to switch to WRITE mode if we don't have this block of a
+ // byte-range request because we may have other parts cached.
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfRange, etag_value);
+ } else {
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfNoneMatch, etag_value);
+ }
+ // For byte-range requests, make sure that we use only one way to validate
+ // the request.
+ if (partial_.get() && !partial_->IsCurrentRangeCached())
+ return true;
+ }
+
+ if (!last_modified_value.empty()) {
+ if (use_if_range) {
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfRange, last_modified_value);
+ } else {
+ custom_request_->extra_headers.SetHeader(
+ HttpRequestHeaders::kIfModifiedSince, last_modified_value);
+ }
+ }
+
+ return true;
+}
+
+// We just received some headers from the server. We may have asked for a range,
+// in which case partial_ has an object. This could be the first network request
+// we make to fulfill the original request, or we may be already reading (from
+// the net and / or the cache). If we are not expecting a certain response, we
+// just bypass the cache for this request (but again, maybe we are reading), and
+// delete partial_ (so we are not able to "fix" the headers that we return to
+// the user). This results in either a weird response for the caller (we don't
+// expect it after all), or maybe a range that was not exactly what it was asked
+// for.
+//
+// If the server is simply telling us that the resource has changed, we delete
+// the cached entry and restart the request as the caller intended (by returning
+// false from this method). However, we may not be able to do that at any point,
+// for instance if we already returned the headers to the user.
+//
+// WARNING: Whenever this code returns false, it has to make sure that the next
+// time it is called it will return true so that we don't keep retrying the
+// request.
+bool HttpCache::Transaction::ValidatePartialResponse() {
+ const HttpResponseHeaders* headers = new_response_->headers.get();
+ int response_code = headers->response_code();
+ bool partial_response = (response_code == 206);
+ handling_206_ = false;
+
+ if (!entry_ || request_->method != "GET")
+ return true;
+
+ if (invalid_range_) {
+ // We gave up trying to match this request with the stored data. If the
+ // server is ok with the request, delete the entry, otherwise just ignore
+ // this request
+ DCHECK(!reading_);
+ if (partial_response || response_code == 200) {
+ DoomPartialEntry(true);
+ mode_ = NONE;
+ } else {
+ if (response_code == 304)
+ FailRangeRequest();
+ IgnoreRangeRequest();
+ }
+ return true;
+ }
+
+ if (!partial_.get()) {
+ // We are not expecting 206 but we may have one.
+ if (partial_response)
+ IgnoreRangeRequest();
+
+ return true;
+ }
+
+ // TODO(rvargas): Do we need to consider other results here?.
+ bool failure = response_code == 200 || response_code == 416;
+
+ if (partial_->IsCurrentRangeCached()) {
+ // We asked for "If-None-Match: " so a 206 means a new object.
+ if (partial_response)
+ failure = true;
+
+ if (response_code == 304 && partial_->ResponseHeadersOK(headers))
+ return true;
+ } else {
+ // We asked for "If-Range: " so a 206 means just another range.
+ if (partial_response && partial_->ResponseHeadersOK(headers)) {
+ handling_206_ = true;
+ return true;
+ }
+
+ if (!reading_ && !is_sparse_ && !partial_response) {
+ // See if we can ignore the fact that we issued a byte range request.
+ // If the server sends 200, just store it. If it sends an error, redirect
+ // or something else, we may store the response as long as we didn't have
+ // anything already stored.
+ if (response_code == 200 ||
+ (!truncated_ && response_code != 304 && response_code != 416)) {
+ // The server is sending something else, and we can save it.
+ DCHECK((truncated_ && !partial_->IsLastRange()) || range_requested_);
+ partial_.reset();
+ truncated_ = false;
+ return true;
+ }
+ }
+
+ // 304 is not expected here, but we'll spare the entry (unless it was
+ // truncated).
+ if (truncated_)
+ failure = true;
+ }
+
+ if (failure) {
+ // We cannot truncate this entry, it has to be deleted.
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ DoomPartialEntry(false);
+ mode_ = NONE;
+ if (!reading_ && !partial_->IsLastRange()) {
+ // We'll attempt to issue another network request, this time without us
+ // messing up the headers.
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ partial_.reset();
+ truncated_ = false;
+ return false;
+ }
+ LOG(WARNING) << "Failed to revalidate partial entry";
+ partial_.reset();
+ return true;
+ }
+
+ IgnoreRangeRequest();
+ return true;
+}
+
+void HttpCache::Transaction::IgnoreRangeRequest() {
+ // We have a problem. We may or may not be reading already (in which case we
+ // returned the headers), but we'll just pretend that this request is not
+ // using the cache and see what happens. Most likely this is the first
+ // response from the server (it's not changing its mind midway, right?).
+ UpdateTransactionPattern(PATTERN_NOT_COVERED);
+ if (mode_ & WRITE)
+ DoneWritingToEntry(mode_ != WRITE);
+ else if (mode_ & READ && entry_)
+ cache_->DoneReadingFromEntry(entry_, this);
+
+ partial_.reset(NULL);
+ entry_ = NULL;
+ mode_ = NONE;
+}
+
+void HttpCache::Transaction::FailRangeRequest() {
+ response_ = *new_response_;
+ partial_->FixResponseHeaders(response_.headers.get(), false);
+}
+
+int HttpCache::Transaction::SetupEntryForRead() {
+ if (network_trans_)
+ ResetNetworkTransaction();
+ if (partial_.get()) {
+ if (truncated_ || is_sparse_ || !invalid_range_) {
+ // We are going to return the saved response headers to the caller, so
+ // we may need to adjust them first.
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ } else {
+ partial_.reset();
+ }
+ }
+ cache_->ConvertWriterToReader(entry_);
+ mode_ = READ;
+
+ if (entry_->disk_entry->GetDataSize(kMetadataIndex))
+ next_state_ = STATE_CACHE_READ_METADATA;
+ return OK;
+}
+
+
+int HttpCache::Transaction::ReadFromNetwork(IOBuffer* data, int data_len) {
+ read_buf_ = data;
+ io_buf_len_ = data_len;
+ next_state_ = STATE_NETWORK_READ;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::ReadFromEntry(IOBuffer* data, int data_len) {
+ read_buf_ = data;
+ io_buf_len_ = data_len;
+ next_state_ = STATE_CACHE_READ_DATA;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::WriteToEntry(int index, int offset,
+ IOBuffer* data, int data_len,
+ const CompletionCallback& callback) {
+ if (!entry_)
+ return data_len;
+
+ int rv = 0;
+ if (!partial_.get() || !data_len) {
+ rv = entry_->disk_entry->WriteData(index, offset, data, data_len, callback,
+ true);
+ } else {
+ rv = partial_->CacheWrite(entry_->disk_entry, data, data_len, callback);
+ }
+ return rv;
+}
+
+int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) {
+ next_state_ = STATE_CACHE_WRITE_RESPONSE_COMPLETE;
+ if (!entry_)
+ return OK;
+
+ // Do not cache no-store content (unless we are record mode). Do not cache
+ // content with cert errors either. This is to prevent not reporting net
+ // errors when loading a resource from the cache. When we load a page over
+ // HTTPS with a cert error we show an SSL blocking page. If the user clicks
+ // proceed we reload the resource ignoring the errors. The loaded resource
+ // is then cached. If that resource is subsequently loaded from the cache,
+ // no net error is reported (even though the cert status contains the actual
+ // errors) and no SSL blocking page is shown. An alternative would be to
+ // reverse-map the cert status to a net error and replay the net error.
+ if ((cache_->mode() != RECORD &&
+ response_.headers->HasHeaderValue("cache-control", "no-store")) ||
+ net::IsCertStatusError(response_.ssl_info.cert_status)) {
+ DoneWritingToEntry(false);
+ ReportCacheActionFinish();
+ if (net_log_.IsLoggingAllEvents())
+ net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_WRITE_INFO);
+ return OK;
+ }
+
+ // When writing headers, we normally only write the non-transient
+ // headers; when in record mode, record everything.
+ bool skip_transient_headers = (cache_->mode() != RECORD);
+
+ if (truncated)
+ DCHECK_EQ(200, response_.headers->response_code());
+
+ scoped_refptr<PickledIOBuffer> data(new PickledIOBuffer());
+ response_.Persist(data->pickle(), skip_transient_headers, truncated);
+ data->Done();
+
+ io_buf_len_ = data->pickle()->size();
+ return ResetCacheIOStart(entry_->disk_entry->WriteData(
+ kResponseInfoIndex, 0, data.get(), io_buf_len_, io_callback_, true));
+}
+
+int HttpCache::Transaction::AppendResponseDataToEntry(
+ IOBuffer* data, int data_len, const CompletionCallback& callback) {
+ if (!entry_ || !data_len)
+ return data_len;
+
+ int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
+ return WriteToEntry(kResponseContentIndex, current_size, data, data_len,
+ callback);
+}
+
+void HttpCache::Transaction::DoneWritingToEntry(bool success) {
+ if (!entry_)
+ return;
+
+ RecordHistograms();
+
+ cache_->DoneWritingToEntry(entry_, success);
+ entry_ = NULL;
+ mode_ = NONE; // switch to 'pass through' mode
+}
+
+int HttpCache::Transaction::OnCacheReadError(int result, bool restart) {
+ DLOG(ERROR) << "ReadData failed: " << result;
+ const int result_for_histogram = std::max(0, -result);
+ if (restart) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("HttpCache.ReadErrorRestartable",
+ result_for_histogram);
+ } else {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("HttpCache.ReadErrorNonRestartable",
+ result_for_histogram);
+ }
+
+ // Avoid using this entry in the future.
+ if (cache_.get())
+ cache_->DoomActiveEntry(cache_key_);
+
+ if (restart) {
+ DCHECK(!reading_);
+ DCHECK(!network_trans_.get());
+ cache_->DoneWithEntry(entry_, this, false);
+ entry_ = NULL;
+ is_sparse_ = false;
+ partial_.reset();
+ next_state_ = STATE_GET_BACKEND;
+ return OK;
+ }
+
+ return ERR_CACHE_READ_FAILURE;
+}
+
+void HttpCache::Transaction::DoomPartialEntry(bool delete_object) {
+ DVLOG(2) << "DoomPartialEntry";
+ int rv = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, rv);
+ cache_->DoneWithEntry(entry_, this, false);
+ entry_ = NULL;
+ is_sparse_ = false;
+ if (delete_object)
+ partial_.reset(NULL);
+}
+
+int HttpCache::Transaction::DoPartialNetworkReadCompleted(int result) {
+ partial_->OnNetworkReadCompleted(result);
+
+ if (result == 0) {
+ // We need to move on to the next range.
+ ResetNetworkTransaction();
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoPartialCacheReadCompleted(int result) {
+ partial_->OnCacheReadCompleted(result);
+
+ if (result == 0 && mode_ == READ_WRITE) {
+ // We need to move on to the next range.
+ next_state_ = STATE_START_PARTIAL_CACHE_VALIDATION;
+ } else if (result < 0) {
+ return OnCacheReadError(result, false);
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoRestartPartialRequest() {
+ // The stored data cannot be used. Get rid of it and restart this request.
+ // We need to also reset the |truncated_| flag as a new entry is created.
+ DoomPartialEntry(!range_requested_);
+ mode_ = WRITE;
+ truncated_ = false;
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+}
+
+// Histogram data from the end of 2010 show the following distribution of
+// response headers:
+//
+// Content-Length............... 87%
+// Date......................... 98%
+// Last-Modified................ 49%
+// Etag......................... 19%
+// Accept-Ranges: bytes......... 25%
+// Accept-Ranges: none.......... 0.4%
+// Strong Validator............. 50%
+// Strong Validator + ranges.... 24%
+// Strong Validator + CL........ 49%
+//
+bool HttpCache::Transaction::CanResume(bool has_data) {
+ // Double check that there is something worth keeping.
+ if (has_data && !entry_->disk_entry->GetDataSize(kResponseContentIndex))
+ return false;
+
+ if (request_->method != "GET")
+ return false;
+
+ if (response_.headers->GetContentLength() <= 0 ||
+ response_.headers->HasHeaderValue("Accept-Ranges", "none") ||
+ !response_.headers->HasStrongValidators()) {
+ return false;
+ }
+
+ return true;
+}
+
+void HttpCache::Transaction::OnIOComplete(int result) {
+ if (!cache_io_start_.is_null()) {
+ base::TimeDelta cache_time = base::TimeTicks::Now() - cache_io_start_;
+ cache_io_start_ = base::TimeTicks();
+ if (sensitivity_analysis_percent_increase_ > 0) {
+ cache_time *= sensitivity_analysis_percent_increase_;
+ cache_time /= 100;
+ if (!defer_cache_sensitivity_delay_) {
+ ScheduleDelayedLoop(cache_time, result);
+ return;
+ } else {
+ deferred_cache_sensitivity_delay_ += cache_time;
+ }
+ }
+ }
+ DCHECK(cache_io_start_.is_null());
+ DoLoop(result);
+}
+
+void HttpCache::Transaction::ScheduleDelayedLoop(base::TimeDelta delay,
+ int result) {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&HttpCache::Transaction::RunDelayedLoop,
+ weak_factory_.GetWeakPtr(),
+ base::TimeTicks::Now(),
+ delay,
+ result),
+ delay);
+}
+
+void HttpCache::Transaction::RunDelayedLoop(base::TimeTicks delay_start_time,
+ base::TimeDelta intended_delay,
+ int result) {
+ base::TimeDelta actual_delay = base::TimeTicks::Now() - delay_start_time;
+ int64 ratio;
+ int64 inverse_ratio;
+ if (intended_delay.InMicroseconds() > 0) {
+ ratio =
+ 100 * actual_delay.InMicroseconds() / intended_delay.InMicroseconds();
+ } else {
+ ratio = 0;
+ }
+ if (actual_delay.InMicroseconds() > 0) {
+ inverse_ratio =
+ 100 * intended_delay.InMicroseconds() / actual_delay.InMicroseconds();
+ } else {
+ inverse_ratio = 0;
+ }
+ bool ratio_sample = base::RandInt(0, 99) < ratio;
+ bool inverse_ratio_sample = base::RandInt(0, 99) < inverse_ratio;
+ int intended_delay_ms = intended_delay.InMilliseconds();
+ UMA_HISTOGRAM_COUNTS_10000(
+ "HttpCache.CacheSensitivityAnalysis_IntendedDelayMs",
+ intended_delay_ms);
+ if (ratio_sample) {
+ UMA_HISTOGRAM_COUNTS_10000(
+ "HttpCache.CacheSensitivityAnalysis_RatioByIntendedDelayMs",
+ intended_delay_ms);
+ }
+ if (inverse_ratio_sample) {
+ UMA_HISTOGRAM_COUNTS_10000(
+ "HttpCache.CacheSensitivityAnalysis_InverseRatioByIntendedDelayMs",
+ intended_delay_ms);
+ }
+
+ DCHECK(cache_io_start_.is_null());
+ DCHECK(deferred_cache_sensitivity_delay_ == base::TimeDelta());
+ DoLoop(result);
+}
+
+void HttpCache::Transaction::ReportCacheActionStart() {
+ if (transaction_delegate_)
+ transaction_delegate_->OnCacheActionStart();
+}
+
+void HttpCache::Transaction::ReportCacheActionFinish() {
+ if (transaction_delegate_)
+ transaction_delegate_->OnCacheActionFinish();
+}
+
+void HttpCache::Transaction::ReportNetworkActionStart() {
+ if (transaction_delegate_)
+ transaction_delegate_->OnNetworkActionStart();
+}
+
+void HttpCache::Transaction::ReportNetworkActionFinish() {
+ if (transaction_delegate_)
+ transaction_delegate_->OnNetworkActionFinish();
+}
+
+void HttpCache::Transaction::UpdateTransactionPattern(
+ TransactionPattern new_transaction_pattern) {
+ if (transaction_pattern_ == PATTERN_NOT_COVERED)
+ return;
+ DCHECK(transaction_pattern_ == PATTERN_UNDEFINED ||
+ new_transaction_pattern == PATTERN_NOT_COVERED);
+ transaction_pattern_ = new_transaction_pattern;
+}
+
+void HttpCache::Transaction::RecordHistograms() {
+ DCHECK_NE(PATTERN_UNDEFINED, transaction_pattern_);
+ if (!cache_.get() || !cache_->GetCurrentBackend() ||
+ cache_->GetCurrentBackend()->GetCacheType() != DISK_CACHE ||
+ cache_->mode() != NORMAL || request_->method != "GET") {
+ return;
+ }
+ UMA_HISTOGRAM_ENUMERATION(
+ "HttpCache.Pattern", transaction_pattern_, PATTERN_MAX);
+ if (transaction_pattern_ == PATTERN_NOT_COVERED)
+ return;
+ DCHECK(!range_requested_);
+ DCHECK(!first_cache_access_since_.is_null());
+
+ TimeDelta total_time = base::TimeTicks::Now() - first_cache_access_since_;
+
+ UMA_HISTOGRAM_TIMES("HttpCache.AccessToDone", total_time);
+
+ bool did_send_request = !send_request_since_.is_null();
+ DCHECK(
+ (did_send_request &&
+ (transaction_pattern_ == PATTERN_ENTRY_NOT_CACHED ||
+ transaction_pattern_ == PATTERN_ENTRY_VALIDATED ||
+ transaction_pattern_ == PATTERN_ENTRY_UPDATED ||
+ transaction_pattern_ == PATTERN_ENTRY_CANT_CONDITIONALIZE)) ||
+ (!did_send_request && transaction_pattern_ == PATTERN_ENTRY_USED));
+
+ if (!did_send_request) {
+ DCHECK(transaction_pattern_ == PATTERN_ENTRY_USED);
+ UMA_HISTOGRAM_TIMES("HttpCache.AccessToDone.Used", total_time);
+ return;
+ }
+
+ TimeDelta before_send_time = send_request_since_ - first_cache_access_since_;
+ int before_send_percent =
+ total_time.ToInternalValue() == 0 ? 0
+ : before_send_time * 100 / total_time;
+ DCHECK_LE(0, before_send_percent);
+ DCHECK_GE(100, before_send_percent);
+
+ UMA_HISTOGRAM_TIMES("HttpCache.AccessToDone.SentRequest", total_time);
+ UMA_HISTOGRAM_TIMES("HttpCache.BeforeSend", before_send_time);
+ UMA_HISTOGRAM_PERCENTAGE("HttpCache.PercentBeforeSend", before_send_percent);
+
+ // TODO(gavinp): Remove or minimize these histograms, particularly the ones
+ // below this comment after we have received initial data.
+ switch (transaction_pattern_) {
+ case PATTERN_ENTRY_CANT_CONDITIONALIZE: {
+ UMA_HISTOGRAM_TIMES("HttpCache.BeforeSend.CantConditionalize",
+ before_send_time);
+ UMA_HISTOGRAM_PERCENTAGE("HttpCache.PercentBeforeSend.CantConditionalize",
+ before_send_percent);
+ break;
+ }
+ case PATTERN_ENTRY_NOT_CACHED: {
+ UMA_HISTOGRAM_TIMES("HttpCache.BeforeSend.NotCached", before_send_time);
+ UMA_HISTOGRAM_PERCENTAGE("HttpCache.PercentBeforeSend.NotCached",
+ before_send_percent);
+ break;
+ }
+ case PATTERN_ENTRY_VALIDATED: {
+ UMA_HISTOGRAM_TIMES("HttpCache.BeforeSend.Validated", before_send_time);
+ UMA_HISTOGRAM_PERCENTAGE("HttpCache.PercentBeforeSend.Validated",
+ before_send_percent);
+ break;
+ }
+ case PATTERN_ENTRY_UPDATED: {
+ UMA_HISTOGRAM_TIMES("HttpCache.BeforeSend.Updated", before_send_time);
+ UMA_HISTOGRAM_PERCENTAGE("HttpCache.PercentBeforeSend.Updated",
+ before_send_percent);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+}
+
+int HttpCache::Transaction::ResetCacheIOStart(int return_value) {
+ DCHECK(cache_io_start_.is_null());
+ if (return_value == ERR_IO_PENDING)
+ cache_io_start_ = base::TimeTicks::Now();
+ return return_value;
+}
+
+void HttpCache::Transaction::ResetNetworkTransaction() {
+ DCHECK(!old_network_trans_load_timing_);
+ DCHECK(network_trans_);
+ LoadTimingInfo load_timing;
+ if (network_trans_->GetLoadTimingInfo(&load_timing))
+ old_network_trans_load_timing_.reset(new LoadTimingInfo(load_timing));
+ network_trans_.reset();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_cache_transaction.h b/chromium/net/http/http_cache_transaction.h
new file mode 100644
index 00000000000..0d70a256b87
--- /dev/null
+++ b/chromium/net/http/http_cache_transaction.h
@@ -0,0 +1,466 @@
+// Copyright (c) 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.
+
+// This file declares HttpCache::Transaction, a private class of HttpCache so
+// it should only be included by http_cache.cc
+
+#ifndef NET_HTTP_HTTP_CACHE_TRANSACTION_H_
+#define NET_HTTP_HTTP_CACHE_TRANSACTION_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+
+namespace net {
+
+class PartialData;
+struct HttpRequestInfo;
+class HttpTransactionDelegate;
+struct LoadTimingInfo;
+
+// This is the transaction that is returned by the HttpCache transaction
+// factory.
+class HttpCache::Transaction : public HttpTransaction {
+ public:
+ // The transaction has the following modes, which apply to how it may access
+ // its cache entry.
+ //
+ // o If the mode of the transaction is NONE, then it is in "pass through"
+ // mode and all methods just forward to the inner network transaction.
+ //
+ // o If the mode of the transaction is only READ, then it may only read from
+ // the cache entry.
+ //
+ // o If the mode of the transaction is only WRITE, then it may only write to
+ // the cache entry.
+ //
+ // o If the mode of the transaction is READ_WRITE, then the transaction may
+ // optionally modify the cache entry (e.g., possibly corresponding to
+ // cache validation).
+ //
+ // o If the mode of the transaction is UPDATE, then the transaction may
+ // update existing cache entries, but will never create a new entry or
+ // respond using the entry read from the cache.
+ enum Mode {
+ NONE = 0,
+ READ_META = 1 << 0,
+ READ_DATA = 1 << 1,
+ READ = READ_META | READ_DATA,
+ WRITE = 1 << 2,
+ READ_WRITE = READ | WRITE,
+ UPDATE = READ_META | WRITE, // READ_WRITE & ~READ_DATA
+ };
+
+ Transaction(RequestPriority priority,
+ HttpCache* cache,
+ HttpTransactionDelegate* transaction_delegate);
+ virtual ~Transaction();
+
+ Mode mode() const { return mode_; }
+
+ const std::string& key() const { return cache_key_; }
+
+ // Writes |buf_len| bytes of meta-data from the provided buffer |buf|. to the
+ // HTTP cache entry that backs this transaction (if any).
+ // Returns the number of bytes actually written, or a net error code. If the
+ // operation cannot complete immediately, returns ERR_IO_PENDING, grabs a
+ // reference to the buffer (until completion), and notifies the caller using
+ // the provided |callback| when the operation finishes.
+ //
+ // The first time this method is called for a given transaction, previous
+ // meta-data will be overwritten with the provided data, and subsequent
+ // invocations will keep appending to the cached entry.
+ //
+ // In order to guarantee that the metadata is set to the correct entry, the
+ // response (or response info) must be evaluated by the caller, for instance
+ // to make sure that the response_time is as expected, before calling this
+ // method.
+ int WriteMetadata(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback);
+
+ // This transaction is being deleted and we are not done writing to the cache.
+ // We need to indicate that the response data was truncated. Returns true on
+ // success. Keep in mind that this operation may have side effects, such as
+ // deleting the active entry.
+ bool AddTruncatedFlag();
+
+ HttpCache::ActiveEntry* entry() { return entry_; }
+
+ // Returns the LoadState of the writer transaction of a given ActiveEntry. In
+ // other words, returns the LoadState of this transaction without asking the
+ // http cache, because this transaction should be the one currently writing
+ // to the cache entry.
+ LoadState GetWriterLoadState() const;
+
+ const CompletionCallback& io_callback() { return io_callback_; }
+
+ const BoundNetLog& net_log() const;
+
+ // HttpTransaction methods:
+ virtual int Start(const HttpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int RestartIgnoringLastError(
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int RestartWithCertificate(
+ X509Certificate* client_cert,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool IsReadyToRestartForAuth() OVERRIDE;
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void StopCaching() OVERRIDE;
+ virtual bool GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void DoneReading() OVERRIDE;
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual UploadProgress GetUploadProgress(void) const OVERRIDE;
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ private:
+ static const size_t kNumValidationHeaders = 2;
+ // Helper struct to pair a header name with its value, for
+ // headers used to validate cache entries.
+ struct ValidationHeaders {
+ ValidationHeaders() : initialized(false) {}
+
+ std::string values[kNumValidationHeaders];
+ bool initialized;
+ };
+
+ enum State {
+ STATE_NONE,
+ STATE_GET_BACKEND,
+ STATE_GET_BACKEND_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_SUCCESSFUL_SEND_REQUEST,
+ STATE_NETWORK_READ,
+ STATE_NETWORK_READ_COMPLETE,
+ STATE_INIT_ENTRY,
+ STATE_OPEN_ENTRY,
+ STATE_OPEN_ENTRY_COMPLETE,
+ STATE_CREATE_ENTRY,
+ STATE_CREATE_ENTRY_COMPLETE,
+ STATE_DOOM_ENTRY,
+ STATE_DOOM_ENTRY_COMPLETE,
+ STATE_ADD_TO_ENTRY,
+ STATE_ADD_TO_ENTRY_COMPLETE,
+ STATE_ADD_TO_ENTRY_COMPLETE_AFTER_DELAY,
+ STATE_START_PARTIAL_CACHE_VALIDATION,
+ STATE_COMPLETE_PARTIAL_CACHE_VALIDATION,
+ STATE_UPDATE_CACHED_RESPONSE,
+ STATE_UPDATE_CACHED_RESPONSE_COMPLETE,
+ STATE_OVERWRITE_CACHED_RESPONSE,
+ STATE_TRUNCATE_CACHED_DATA,
+ STATE_TRUNCATE_CACHED_DATA_COMPLETE,
+ STATE_TRUNCATE_CACHED_METADATA,
+ STATE_TRUNCATE_CACHED_METADATA_COMPLETE,
+ STATE_PARTIAL_HEADERS_RECEIVED,
+ STATE_CACHE_READ_RESPONSE,
+ STATE_CACHE_READ_RESPONSE_COMPLETE,
+ STATE_CACHE_WRITE_RESPONSE,
+ STATE_CACHE_WRITE_TRUNCATED_RESPONSE,
+ STATE_CACHE_WRITE_RESPONSE_COMPLETE,
+ STATE_CACHE_READ_METADATA,
+ STATE_CACHE_READ_METADATA_COMPLETE,
+ STATE_CACHE_QUERY_DATA,
+ STATE_CACHE_QUERY_DATA_COMPLETE,
+ STATE_CACHE_READ_DATA,
+ STATE_CACHE_READ_DATA_COMPLETE,
+ STATE_CACHE_WRITE_DATA,
+ STATE_CACHE_WRITE_DATA_COMPLETE
+ };
+
+ // Used for categorizing transactions for reporting in histograms. Patterns
+ // cover relatively common use cases being measured and considered for
+ // optimization. Many use cases that are more complex or uncommon are binned
+ // as PATTERN_NOT_COVERED, and details are not reported.
+ // NOTE: This enumeration is used in histograms, so please do not add entries
+ // in the middle.
+ enum TransactionPattern {
+ PATTERN_UNDEFINED,
+ PATTERN_NOT_COVERED,
+ PATTERN_ENTRY_NOT_CACHED,
+ PATTERN_ENTRY_USED,
+ PATTERN_ENTRY_VALIDATED,
+ PATTERN_ENTRY_UPDATED,
+ PATTERN_ENTRY_CANT_CONDITIONALIZE,
+ PATTERN_MAX,
+ };
+
+ // This is a helper function used to trigger a completion callback. It may
+ // only be called if callback_ is non-null.
+ void DoCallback(int rv);
+
+ // This will trigger the completion callback if appropriate.
+ int HandleResult(int rv);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. If there is an
+ // argument, the value corresponds to the return of the previous state or
+ // corresponding callback.
+ int DoGetBackend();
+ int DoGetBackendComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoSuccessfulSendRequest();
+ int DoNetworkRead();
+ int DoNetworkReadComplete(int result);
+ int DoInitEntry();
+ int DoOpenEntry();
+ int DoOpenEntryComplete(int result);
+ int DoCreateEntry();
+ int DoCreateEntryComplete(int result);
+ int DoDoomEntry();
+ int DoDoomEntryComplete(int result);
+ int DoAddToEntry();
+ int DoAddToEntryComplete(int result);
+ int DoAddToEntryCompleteAfterDelay(int result);
+ int DoStartPartialCacheValidation();
+ int DoCompletePartialCacheValidation(int result);
+ int DoUpdateCachedResponse();
+ int DoUpdateCachedResponseComplete(int result);
+ int DoOverwriteCachedResponse();
+ int DoTruncateCachedData();
+ int DoTruncateCachedDataComplete(int result);
+ int DoTruncateCachedMetadata();
+ int DoTruncateCachedMetadataComplete(int result);
+ int DoPartialHeadersReceived();
+ int DoCacheReadResponse();
+ int DoCacheReadResponseComplete(int result);
+ int DoCacheWriteResponse();
+ int DoCacheWriteTruncatedResponse();
+ int DoCacheWriteResponseComplete(int result);
+ int DoCacheReadMetadata();
+ int DoCacheReadMetadataComplete(int result);
+ int DoCacheQueryData();
+ int DoCacheQueryDataComplete(int result);
+ int DoCacheReadData();
+ int DoCacheReadDataComplete(int result);
+ int DoCacheWriteData(int num_bytes);
+ int DoCacheWriteDataComplete(int result);
+
+ // Sets request_ and fields derived from it.
+ void SetRequest(const BoundNetLog& net_log, const HttpRequestInfo* request);
+
+ // Returns true if the request should be handled exclusively by the network
+ // layer (skipping the cache entirely).
+ bool ShouldPassThrough();
+
+ // Called to begin reading from the cache. Returns network error code.
+ int BeginCacheRead();
+
+ // Called to begin validating the cache entry. Returns network error code.
+ int BeginCacheValidation();
+
+ // Called to begin validating an entry that stores partial content. Returns
+ // a network error code.
+ int BeginPartialCacheValidation();
+
+ // Validates the entry headers against the requested range and continues with
+ // the validation of the rest of the entry. Returns a network error code.
+ int ValidateEntryHeadersAndContinue();
+
+ // Called to start requests which were given an "if-modified-since" or
+ // "if-none-match" validation header by the caller (NOT when the request was
+ // conditionalized internally in response to LOAD_VALIDATE_CACHE).
+ // Returns a network error code.
+ int BeginExternallyConditionalizedRequest();
+
+ // Called to restart a network transaction after an error. Returns network
+ // error code.
+ int RestartNetworkRequest();
+
+ // Called to restart a network transaction with a client certificate.
+ // Returns network error code.
+ int RestartNetworkRequestWithCertificate(X509Certificate* client_cert);
+
+ // Called to restart a network transaction with authentication credentials.
+ // Returns network error code.
+ int RestartNetworkRequestWithAuth(const AuthCredentials& credentials);
+
+ // Called to determine if we need to validate the cache entry before using it.
+ bool RequiresValidation();
+
+ // Called to make the request conditional (to ask the server if the cached
+ // copy is valid). Returns true if able to make the request conditional.
+ bool ConditionalizeRequest();
+
+ // Makes sure that a 206 response is expected. Returns true on success.
+ // On success, handling_206_ will be set to true if we are processing a
+ // partial entry.
+ bool ValidatePartialResponse();
+
+ // Handles a response validation error by bypassing the cache.
+ void IgnoreRangeRequest();
+
+ // Changes the response code of a range request to be 416 (Requested range not
+ // satisfiable).
+ void FailRangeRequest();
+
+ // Setups the transaction for reading from the cache entry.
+ int SetupEntryForRead();
+
+ // Reads data from the network.
+ int ReadFromNetwork(IOBuffer* data, int data_len);
+
+ // Reads data from the cache entry.
+ int ReadFromEntry(IOBuffer* data, int data_len);
+
+ // Called to write data to the cache entry. If the write fails, then the
+ // cache entry is destroyed. Future calls to this function will just do
+ // nothing without side-effect. Returns a network error code.
+ int WriteToEntry(int index, int offset, IOBuffer* data, int data_len,
+ const CompletionCallback& callback);
+
+ // Called to write response_ to the cache entry. |truncated| indicates if the
+ // entry should be marked as incomplete.
+ int WriteResponseInfoToEntry(bool truncated);
+
+ // Called to append response data to the cache entry. Returns a network error
+ // code.
+ int AppendResponseDataToEntry(IOBuffer* data, int data_len,
+ const CompletionCallback& callback);
+
+ // Called when we are done writing to the cache entry.
+ void DoneWritingToEntry(bool success);
+
+ // Returns an error to signal the caller that the current read failed. The
+ // current operation |result| is also logged. If |restart| is true, the
+ // transaction should be restarted.
+ int OnCacheReadError(int result, bool restart);
+
+ // Deletes the current partial cache entry (sparse), and optionally removes
+ // the control object (partial_).
+ void DoomPartialEntry(bool delete_object);
+
+ // Performs the needed work after receiving data from the network, when
+ // working with range requests.
+ int DoPartialNetworkReadCompleted(int result);
+
+ // Performs the needed work after receiving data from the cache, when
+ // working with range requests.
+ int DoPartialCacheReadCompleted(int result);
+
+ // Restarts this transaction after deleting the cached data. It is meant to
+ // be used when the current request cannot be fulfilled due to conflicts
+ // between the byte range request and the cached entry.
+ int DoRestartPartialRequest();
+
+ // Returns true if we should bother attempting to resume this request if it
+ // is aborted while in progress. If |has_data| is true, the size of the stored
+ // data is considered for the result.
+ bool CanResume(bool has_data);
+
+ // Called to signal completion of asynchronous IO.
+ void OnIOComplete(int result);
+
+ void ReportCacheActionStart();
+ void ReportCacheActionFinish();
+ void ReportNetworkActionStart();
+ void ReportNetworkActionFinish();
+ void UpdateTransactionPattern(TransactionPattern new_transaction_pattern);
+ void RecordHistograms();
+
+ // Resets cache_io_start_ to the current time, if |return_value| is
+ // ERR_IO_PENDING.
+ // Returns |return_value|.
+ int ResetCacheIOStart(int return_value);
+
+ void ScheduleDelayedLoop(base::TimeDelta delay, int result);
+ void RunDelayedLoop(base::TimeTicks delay_start_time,
+ base::TimeDelta intended_delay, int result);
+
+ // Resets |network_trans_|, which must be non-NULL. Also updates
+ // |old_network_trans_load_timing_|, which must be NULL when this is called.
+ void ResetNetworkTransaction();
+
+ State next_state_;
+ const HttpRequestInfo* request_;
+ RequestPriority priority_;
+ BoundNetLog net_log_;
+ scoped_ptr<HttpRequestInfo> custom_request_;
+ HttpRequestHeaders request_headers_copy_;
+ // If extra_headers specified a "if-modified-since" or "if-none-match",
+ // |external_validation_| contains the value of those headers.
+ ValidationHeaders external_validation_;
+ base::WeakPtr<HttpCache> cache_;
+ HttpCache::ActiveEntry* entry_;
+ HttpCache::ActiveEntry* new_entry_;
+ scoped_ptr<HttpTransaction> network_trans_;
+ CompletionCallback callback_; // Consumer's callback.
+ HttpResponseInfo response_;
+ HttpResponseInfo auth_response_;
+ const HttpResponseInfo* new_response_;
+ std::string cache_key_;
+ Mode mode_;
+ State target_state_;
+ bool reading_; // We are already reading.
+ bool invalid_range_; // We may bypass the cache for this request.
+ bool truncated_; // We don't have all the response data.
+ bool is_sparse_; // The data is stored in sparse byte ranges.
+ bool range_requested_; // The user requested a byte range.
+ bool handling_206_; // We must deal with this 206 response.
+ bool cache_pending_; // We are waiting for the HttpCache.
+ bool done_reading_;
+ bool vary_mismatch_; // The request doesn't match the stored vary data.
+ bool couldnt_conditionalize_request_;
+ scoped_refptr<IOBuffer> read_buf_;
+ int io_buf_len_;
+ int read_offset_;
+ int effective_load_flags_;
+ int write_len_;
+ scoped_ptr<PartialData> partial_; // We are dealing with range requests.
+ UploadProgress final_upload_progress_;
+ base::WeakPtrFactory<Transaction> weak_factory_;
+ CompletionCallback io_callback_;
+
+ // Members used to track data for histograms.
+ TransactionPattern transaction_pattern_;
+ base::TimeTicks entry_lock_waiting_since_;
+ base::TimeTicks first_cache_access_since_;
+ base::TimeTicks send_request_since_;
+
+ // For sensitivity analysis (field trials emulating longer cache IO times),
+ // the time at which a cache IO action has started, or base::TimeTicks()
+ // if no cache IO action is currently in progress.
+ base::TimeTicks cache_io_start_;
+
+ // For OpenEntry and CreateEntry, if sensitivity analysis would mandate
+ // a delay on return, we must defer that delay until AddToEntry has been
+ // called, to avoid a race condition on the address returned.
+ base::TimeDelta deferred_cache_sensitivity_delay_;
+ bool defer_cache_sensitivity_delay_;
+
+ // For sensitivity analysis, the simulated increase in cache service times,
+ // in percent.
+ int sensitivity_analysis_percent_increase_;
+
+ HttpTransactionDelegate* transaction_delegate_;
+
+ // Load timing information for the last network request, if any. Set in the
+ // 304 and 206 response cases, as the network transaction may be destroyed
+ // before the caller requests load timing information.
+ scoped_ptr<LoadTimingInfo> old_network_trans_load_timing_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CACHE_TRANSACTION_H_
diff --git a/chromium/net/http/http_cache_unittest.cc b/chromium/net/http/http_cache_unittest.cc
new file mode 100644
index 00000000000..37e53346f42
--- /dev/null
+++ b/chromium/net/http/http_cache_unittest.cc
@@ -0,0 +1,6003 @@
+// Copyright (c) 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.
+
+#include "net/http/http_cache.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/cache_type.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_delegate.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/http/http_util.h"
+#include "net/http/mock_http_cache.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::Time;
+
+namespace {
+
+// Tests the load timing values of a request that goes through a
+// MockNetworkTransaction.
+void TestLoadTimingNetworkRequest(const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+
+ net::ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ net::CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+ EXPECT_LE(load_timing_info.connect_timing.connect_end,
+ load_timing_info.send_start);
+
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set by URLRequest / URLRequestHttpJob, at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+// Tests the load timing values of a request that receives a cached response.
+void TestLoadTimingCachedResponse(const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_EQ(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+
+ net::ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+
+ // Only the send start / end times should be sent, and they should have the
+ // same value.
+ EXPECT_FALSE(load_timing_info.send_start.is_null());
+ EXPECT_EQ(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set by URLRequest / URLRequestHttpJob, at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+class DeleteCacheCompletionCallback : public net::TestCompletionCallbackBase {
+ public:
+ explicit DeleteCacheCompletionCallback(MockHttpCache* cache)
+ : cache_(cache),
+ callback_(base::Bind(&DeleteCacheCompletionCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ const net::CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ delete cache_;
+ SetResult(result);
+ }
+
+ MockHttpCache* cache_;
+ net::CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteCacheCompletionCallback);
+};
+
+//-----------------------------------------------------------------------------
+// helpers
+
+class TestHttpTransactionDelegate : public net::HttpTransactionDelegate {
+ public:
+ TestHttpTransactionDelegate(int num_cache_actions_to_observe,
+ int num_network_actions_to_observe)
+ : num_callbacks_observed_(0),
+ num_remaining_cache_actions_to_observe_(num_cache_actions_to_observe),
+ num_remaining_network_actions_to_observe_(
+ num_network_actions_to_observe),
+ cache_action_in_progress_(false),
+ network_action_in_progress_(false) {
+ }
+ virtual ~TestHttpTransactionDelegate() {
+ EXPECT_EQ(0, num_remaining_cache_actions_to_observe_);
+ EXPECT_EQ(0, num_remaining_network_actions_to_observe_);
+ EXPECT_FALSE(cache_action_in_progress_);
+ EXPECT_FALSE(network_action_in_progress_);
+ }
+ virtual void OnCacheActionStart() OVERRIDE {
+ num_callbacks_observed_++;
+ EXPECT_FALSE(cache_action_in_progress_);
+ EXPECT_FALSE(network_action_in_progress_);
+ EXPECT_GT(num_remaining_cache_actions_to_observe_, 0);
+ num_remaining_cache_actions_to_observe_--;
+ cache_action_in_progress_ = true;
+ }
+ virtual void OnCacheActionFinish() OVERRIDE {
+ num_callbacks_observed_++;
+ EXPECT_TRUE(cache_action_in_progress_);
+ cache_action_in_progress_ = false;
+ }
+ virtual void OnNetworkActionStart() OVERRIDE {
+ num_callbacks_observed_++;
+ EXPECT_FALSE(cache_action_in_progress_);
+ EXPECT_FALSE(network_action_in_progress_);
+ EXPECT_GT(num_remaining_network_actions_to_observe_, 0);
+ num_remaining_network_actions_to_observe_--;
+ network_action_in_progress_ = true;
+ }
+ virtual void OnNetworkActionFinish() OVERRIDE {
+ num_callbacks_observed_++;
+ EXPECT_TRUE(network_action_in_progress_);
+ network_action_in_progress_ = false;
+ }
+
+ int num_callbacks_observed() { return num_callbacks_observed_; }
+
+ private:
+ int num_callbacks_observed_;
+ int num_remaining_cache_actions_to_observe_;
+ int num_remaining_network_actions_to_observe_;
+ bool cache_action_in_progress_;
+ bool network_action_in_progress_;
+};
+
+void ReadAndVerifyTransaction(net::HttpTransaction* trans,
+ const MockTransaction& trans_info) {
+ std::string content;
+ int rv = ReadTransaction(trans, &content);
+
+ EXPECT_EQ(net::OK, rv);
+ std::string expected(trans_info.data);
+ EXPECT_EQ(expected, content);
+}
+
+const int kNoDelegateTransactionCheck = -1;
+
+void RunTransactionTestWithRequestAndDelegateAndGetTiming(
+ net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ const MockHttpRequest& request,
+ net::HttpResponseInfo* response_info,
+ int num_cache_delegate_actions,
+ int num_network_delegate_actions,
+ const net::BoundNetLog& net_log,
+ net::LoadTimingInfo* load_timing_info) {
+ net::TestCompletionCallback callback;
+
+ // write to the cache
+
+ scoped_ptr<TestHttpTransactionDelegate> delegate;
+ if (num_cache_delegate_actions != kNoDelegateTransactionCheck &&
+ num_network_delegate_actions != kNoDelegateTransactionCheck) {
+ delegate.reset(
+ new TestHttpTransactionDelegate(num_cache_delegate_actions,
+ num_network_delegate_actions));
+ }
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, delegate.get());
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net_log);
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(trans_info.return_code, rv);
+
+ if (net::OK != rv)
+ return;
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response);
+
+ if (response_info)
+ *response_info = *response;
+
+ if (load_timing_info) {
+ // If a fake network connection is used, need a NetLog to get a fake socket
+ // ID.
+ EXPECT_TRUE(net_log.net_log());
+ *load_timing_info = net::LoadTimingInfo();
+ trans->GetLoadTimingInfo(load_timing_info);
+ }
+
+ ReadAndVerifyTransaction(trans.get(), trans_info);
+}
+
+void RunTransactionTestWithRequestAndDelegate(
+ net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ const MockHttpRequest& request,
+ net::HttpResponseInfo* response_info,
+ int num_cache_delegate_actions,
+ int num_network_delegate_actions) {
+ RunTransactionTestWithRequestAndDelegateAndGetTiming(
+ cache, trans_info, request, response_info, num_cache_delegate_actions,
+ num_network_delegate_actions, net::BoundNetLog(), NULL);
+}
+
+void RunTransactionTestWithRequest(net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ const MockHttpRequest& request,
+ net::HttpResponseInfo* response_info) {
+ RunTransactionTestWithRequestAndDelegate(
+ cache, trans_info, request, response_info, kNoDelegateTransactionCheck,
+ kNoDelegateTransactionCheck);
+}
+
+void RunTransactionTestAndGetTiming(
+ net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ const net::BoundNetLog& log,
+ net::LoadTimingInfo* load_timing_info) {
+ RunTransactionTestWithRequestAndDelegateAndGetTiming(
+ cache, trans_info, MockHttpRequest(trans_info), NULL,
+ kNoDelegateTransactionCheck, kNoDelegateTransactionCheck, log,
+ load_timing_info);
+}
+
+void RunTransactionTestWithDelegate(net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ int num_cache_delegate_actions,
+ int num_network_delegate_actions) {
+ RunTransactionTestWithRequestAndDelegate(
+ cache, trans_info, MockHttpRequest(trans_info), NULL,
+ num_cache_delegate_actions, num_network_delegate_actions);
+}
+
+void RunTransactionTest(net::HttpCache* cache,
+ const MockTransaction& trans_info) {
+ RunTransactionTestAndGetTiming(cache, trans_info, net::BoundNetLog(), NULL);
+}
+
+void RunTransactionTestWithResponseInfo(net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ net::HttpResponseInfo* response) {
+ RunTransactionTestWithRequest(
+ cache, trans_info, MockHttpRequest(trans_info), response);
+}
+
+void RunTransactionTestWithResponseInfoAndGetTiming(
+ net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ net::HttpResponseInfo* response,
+ const net::BoundNetLog& log,
+ net::LoadTimingInfo* load_timing_info) {
+ RunTransactionTestWithRequestAndDelegateAndGetTiming(
+ cache, trans_info, MockHttpRequest(trans_info), response,
+ kNoDelegateTransactionCheck, kNoDelegateTransactionCheck, log,
+ load_timing_info);
+}
+
+void RunTransactionTestWithResponse(net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ std::string* response_headers) {
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache, trans_info, &response);
+ response.headers->GetNormalizedHeaders(response_headers);
+}
+
+void RunTransactionTestWithResponseAndGetTiming(
+ net::HttpCache* cache,
+ const MockTransaction& trans_info,
+ std::string* response_headers,
+ const net::BoundNetLog& log,
+ net::LoadTimingInfo* load_timing_info) {
+ net::HttpResponseInfo response;
+ RunTransactionTestWithRequestAndDelegateAndGetTiming(
+ cache, trans_info, MockHttpRequest(trans_info), &response,
+ kNoDelegateTransactionCheck, kNoDelegateTransactionCheck,
+ log, load_timing_info);
+ response.headers->GetNormalizedHeaders(response_headers);
+}
+
+// This class provides a handler for kFastNoStoreGET_Transaction so that the
+// no-store header can be included on demand.
+class FastTransactionServer {
+ public:
+ FastTransactionServer() {
+ no_store = false;
+ }
+ ~FastTransactionServer() {}
+
+ void set_no_store(bool value) { no_store = value; }
+
+ static void FastNoStoreHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ if (no_store)
+ *response_headers = "Cache-Control: no-store\n";
+ }
+
+ private:
+ static bool no_store;
+ DISALLOW_COPY_AND_ASSIGN(FastTransactionServer);
+};
+bool FastTransactionServer::no_store;
+
+const MockTransaction kFastNoStoreGET_Transaction = {
+ "http://www.google.com/nostore",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_VALIDATE_CACHE,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_SYNC_NET_START,
+ &FastTransactionServer::FastNoStoreHandler,
+ 0,
+ net::OK
+};
+
+// This class provides a handler for kRangeGET_TransactionOK so that the range
+// request can be served on demand.
+class RangeTransactionServer {
+ public:
+ RangeTransactionServer() {
+ not_modified_ = false;
+ modified_ = false;
+ bad_200_ = false;
+ }
+ ~RangeTransactionServer() {
+ not_modified_ = false;
+ modified_ = false;
+ bad_200_ = false;
+ }
+
+ // Returns only 416 or 304 when set.
+ void set_not_modified(bool value) { not_modified_ = value; }
+
+ // Returns 206 when revalidating a range (instead of 304).
+ void set_modified(bool value) { modified_ = value; }
+
+ // Returns 200 instead of 206 (a malformed response overall).
+ void set_bad_200(bool value) { bad_200_ = value; }
+
+ static void RangeHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data);
+
+ private:
+ static bool not_modified_;
+ static bool modified_;
+ static bool bad_200_;
+ DISALLOW_COPY_AND_ASSIGN(RangeTransactionServer);
+};
+bool RangeTransactionServer::not_modified_ = false;
+bool RangeTransactionServer::modified_ = false;
+bool RangeTransactionServer::bad_200_ = false;
+
+// A dummy extra header that must be preserved on a given request.
+
+// EXTRA_HEADER_LINE doesn't include a line terminator because it
+// will be passed to AddHeaderFromString() which doesn't accept them.
+#define EXTRA_HEADER_LINE "Extra: header"
+
+// EXTRA_HEADER contains a line terminator, as expected by
+// AddHeadersFromString() (_not_ AddHeaderFromString()).
+#define EXTRA_HEADER EXTRA_HEADER_LINE "\r\n"
+
+static const char kExtraHeaderKey[] = "Extra";
+
+// Static.
+void RangeTransactionServer::RangeHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ if (request->extra_headers.IsEmpty()) {
+ response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable");
+ response_data->clear();
+ return;
+ }
+
+ // We want to make sure we don't delete extra headers.
+ EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
+
+ if (not_modified_) {
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_data->clear();
+ return;
+ }
+
+ std::vector<net::HttpByteRange> ranges;
+ std::string range_header;
+ if (!request->extra_headers.GetHeader(
+ net::HttpRequestHeaders::kRange, &range_header) ||
+ !net::HttpUtil::ParseRangeHeader(range_header, &ranges) || bad_200_ ||
+ ranges.size() != 1) {
+ // This is not a byte range request. We return 200.
+ response_status->assign("HTTP/1.1 200 OK");
+ response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT");
+ response_data->assign("Not a range");
+ return;
+ }
+
+ // We can handle this range request.
+ net::HttpByteRange byte_range = ranges[0];
+ if (byte_range.first_byte_position() > 79) {
+ response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable");
+ response_data->clear();
+ return;
+ }
+
+ EXPECT_TRUE(byte_range.ComputeBounds(80));
+ int start = static_cast<int>(byte_range.first_byte_position());
+ int end = static_cast<int>(byte_range.last_byte_position());
+
+ EXPECT_LT(end, 80);
+
+ std::string content_range = base::StringPrintf(
+ "Content-Range: bytes %d-%d/80\n", start, end);
+ response_headers->append(content_range);
+
+ if (!request->extra_headers.HasHeader("If-None-Match") || modified_) {
+ std::string data;
+ if (end == start) {
+ EXPECT_EQ(0, end % 10);
+ data = "r";
+ } else {
+ EXPECT_EQ(9, (end - start) % 10);
+ for (int block_start = start; block_start < end; block_start += 10) {
+ base::StringAppendF(&data, "rg: %02d-%02d ",
+ block_start, block_start + 9);
+ }
+ }
+ *response_data = data;
+
+ if (end - start != 9) {
+ // We also have to fix content-length.
+ int len = end - start + 1;
+ std::string content_length = base::StringPrintf("Content-Length: %d\n",
+ len);
+ response_headers->replace(response_headers->find("Content-Length:"),
+ content_length.size(), content_length);
+ }
+ } else {
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_data->clear();
+ }
+}
+
+const MockTransaction kRangeGET_TransactionOK = {
+ "http://www.google.com/range",
+ "GET",
+ base::Time(),
+ "Range: bytes = 40-49\r\n"
+ EXTRA_HEADER,
+ net::LOAD_NORMAL,
+ "HTTP/1.1 206 Partial Content",
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n",
+ base::Time(),
+ "rg: 40-49 ",
+ TEST_MODE_NORMAL,
+ &RangeTransactionServer::RangeHandler,
+ 0,
+ net::OK
+};
+
+// Verifies the response headers (|response|) match a partial content
+// response for the range starting at |start| and ending at |end|.
+void Verify206Response(std::string response, int start, int end) {
+ std::string raw_headers(net::HttpUtil::AssembleRawHeaders(response.data(),
+ response.size()));
+ scoped_refptr<net::HttpResponseHeaders> headers(
+ new net::HttpResponseHeaders(raw_headers));
+
+ ASSERT_EQ(206, headers->response_code());
+
+ int64 range_start, range_end, object_size;
+ ASSERT_TRUE(
+ headers->GetContentRange(&range_start, &range_end, &object_size));
+ int64 content_length = headers->GetContentLength();
+
+ int length = end - start + 1;
+ ASSERT_EQ(length, content_length);
+ ASSERT_EQ(start, range_start);
+ ASSERT_EQ(end, range_end);
+}
+
+// Creates a truncated entry that can be resumed using byte ranges.
+void CreateTruncatedEntry(std::string raw_headers, MockHttpCache* cache) {
+ // Create a disk cache entry that stores an incomplete resource.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache->CreateBackendEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
+
+ raw_headers = net::HttpUtil::AssembleRawHeaders(raw_headers.data(),
+ raw_headers.size());
+
+ net::HttpResponseInfo response;
+ response.response_time = base::Time::Now();
+ response.request_time = base::Time::Now();
+ response.headers = new net::HttpResponseHeaders(raw_headers);
+ // Set the last argument for this to be an incomplete request.
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(100));
+ int len = static_cast<int>(base::strlcpy(buf->data(),
+ "rg: 00-09 rg: 10-19 ", 100));
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf.get(), len, cb.callback(), true);
+ EXPECT_EQ(len, cb.GetResult(rv));
+ entry->Close();
+}
+
+// Helper to represent a network HTTP response.
+struct Response {
+ // Set this response into |trans|.
+ void AssignTo(MockTransaction* trans) const {
+ trans->status = status;
+ trans->response_headers = headers;
+ trans->data = body;
+ }
+
+ std::string status_and_headers() const {
+ return std::string(status) + "\n" + std::string(headers);
+ }
+
+ const char* status;
+ const char* headers;
+ const char* body;
+};
+
+struct Context {
+ Context() : result(net::ERR_IO_PENDING) {}
+
+ int result;
+ net::TestCompletionCallback callback;
+ scoped_ptr<net::HttpTransaction> trans;
+};
+
+} // namespace
+
+
+//-----------------------------------------------------------------------------
+// Tests.
+
+TEST(HttpCache, CreateThenDestroy) {
+ MockHttpCache cache;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+}
+
+TEST(HttpCache, GetBackend) {
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(0));
+
+ disk_cache::Backend* backend;
+ net::TestCompletionCallback cb;
+ // This will lazily initialize the backend.
+ int rv = cache.http_cache()->GetBackend(&backend, cb.callback());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+}
+
+TEST(HttpCache, SimpleGET) {
+ MockHttpCache cache;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+
+ // Write to the cache.
+ RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction,
+ log.bound(), &load_timing_info);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+TEST(HttpCache, SimpleGETNoDiskCache) {
+ MockHttpCache cache;
+
+ cache.disk_cache()->set_fail_requests();
+
+ net::CapturingBoundNetLog log;
+ log.SetLogLevel(net::NetLog::LOG_BASIC);
+ net::LoadTimingInfo load_timing_info;
+
+ // Read from the network, and don't use the cache.
+ RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction,
+ log.bound(), &load_timing_info);
+
+ // Check that the NetLog was filled as expected.
+ // (We attempted to both Open and Create entries, but both failed).
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(6u, entries.size());
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 0, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 1, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+TEST(HttpCache, SimpleGETNoDiskCache2) {
+ // This will initialize a cache object with NULL backend.
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ factory->set_fail(true);
+ factory->FinishCreation(); // We'll complete synchronously.
+ MockHttpCache cache(factory);
+
+ // Read from the network, and don't use the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_FALSE(cache.http_cache()->GetCurrentBackend());
+}
+
+// Tests that IOBuffers are not referenced after IO completes.
+TEST(HttpCache, ReleaseBuffer) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ ASSERT_EQ(net::OK, rv);
+
+ const int kBufferSize = 10;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(kBufferSize));
+ net::ReleaseBufferCompletionCallback cb(buffer.get());
+
+ rv = trans->Start(&request, cb.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, cb.GetResult(rv));
+
+ rv = trans->Read(buffer.get(), kBufferSize, cb.callback());
+ EXPECT_EQ(kBufferSize, cb.GetResult(rv));
+}
+
+TEST(HttpCache, SimpleGETWithDiskFailures) {
+ MockHttpCache cache;
+
+ cache.disk_cache()->set_soft_failures(true);
+
+ // Read from the network, and fail to write to the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // This one should see an empty cache again.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that disk failures after the transaction has started don't cause the
+// request to fail.
+TEST(HttpCache, SimpleGETWithDiskFailures2) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ scoped_ptr<Context> c(new Context());
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = c->callback.WaitForResult();
+
+ // Start failing request now.
+ cache.disk_cache()->set_soft_failures(true);
+
+ // We have to open the entry again to propagate the failure flag.
+ disk_cache::Entry* en;
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &en));
+ en->Close();
+
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ c.reset();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // This one should see an empty cache again.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we handle failures to read from the cache.
+TEST(HttpCache, SimpleGETWithDiskFailures3) {
+ MockHttpCache cache;
+
+ // Read from the network, and write to the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ cache.disk_cache()->set_soft_failures(true);
+
+ // Now fail to read from the cache.
+ scoped_ptr<Context> c(new Context());
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, c->callback.GetResult(rv));
+
+ // Now verify that the entry was removed from the cache.
+ cache.disk_cache()->set_soft_failures(false);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(3, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) {
+ MockHttpCache cache;
+
+ net::CapturingBoundNetLog log;
+
+ // This prevents a number of write events from being logged.
+ log.SetLogLevel(net::NetLog::LOG_BASIC);
+
+ net::LoadTimingInfo load_timing_info;
+
+ // Write to the cache.
+ RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction,
+ log.bound(), &load_timing_info);
+
+ // Check that the NetLog was filled as expected.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(8u, entries.size());
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 0, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 1, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 6, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 7, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ // Force this transaction to read from the cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ log.Clear();
+
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ // Check that the NetLog was filled as expected.
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(8u, entries.size());
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 0, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 1, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 4, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 5, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 6, net::NetLog::TYPE_HTTP_CACHE_READ_INFO));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 7, net::NetLog::TYPE_HTTP_CACHE_READ_INFO));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingCachedResponse(load_timing_info);
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Miss) {
+ MockHttpCache cache;
+
+ // force this transaction to read from the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans.reset();
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadPreferringCache_Hit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to read from the cache if valid
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadPreferringCache_Miss) {
+ MockHttpCache cache;
+
+ // force this transaction to read from the cache if valid
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests LOAD_PREFERRING_CACHE in the presence of vary headers.
+TEST(HttpCache, SimpleGET_LoadPreferringCache_VaryMatch) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "Foo: bar\r\n";
+ transaction.response_headers = "Cache-Control: max-age=10000\n"
+ "Vary: Foo\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Read from the cache.
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests LOAD_PREFERRING_CACHE in the presence of vary headers.
+TEST(HttpCache, SimpleGET_LoadPreferringCache_VaryMismatch) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "Foo: bar\r\n";
+ transaction.response_headers = "Cache-Control: max-age=10000\n"
+ "Vary: Foo\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Attempt to read from the cache... this is a vary mismatch that must reach
+ // the network again.
+ transaction.load_flags |= net::LOAD_PREFERRING_CACHE;
+ transaction.request_headers = "Foo: none\r\n";
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that LOAD_FROM_CACHE_IF_OFFLINE returns proper response on
+// network success
+TEST(HttpCache, SimpleGET_CacheOverride_Network) {
+ MockHttpCache cache;
+
+ // Prime cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_FROM_CACHE_IF_OFFLINE;
+ transaction.response_headers = "Cache-Control: no-cache\n";
+
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+
+ // Re-run transaction; make sure the result came from the network,
+ // not the cache.
+ transaction.data = "Changed data.";
+ AddMockTransaction(&transaction);
+ net::HttpResponseInfo response_info;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), transaction,
+ &response_info);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_FALSE(response_info.server_data_unavailable);
+ EXPECT_TRUE(response_info.network_accessed);
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that LOAD_FROM_CACHE_IF_OFFLINE returns proper response on
+// offline failure
+TEST(HttpCache, SimpleGET_CacheOverride_Offline) {
+ MockHttpCache cache;
+
+ // Prime cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_FROM_CACHE_IF_OFFLINE;
+ transaction.response_headers = "Cache-Control: no-cache\n";
+
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+
+ // Network failure with offline error; should return cache entry above +
+ // flag signalling stale data.
+ transaction.return_code = net::ERR_NAME_NOT_RESOLVED;
+ AddMockTransaction(&transaction);
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, callback.GetResult(rv));
+
+ const net::HttpResponseInfo* response_info = trans->GetResponseInfo();
+ ASSERT_TRUE(response_info);
+ EXPECT_TRUE(response_info->server_data_unavailable);
+ EXPECT_TRUE(response_info->was_cached);
+ EXPECT_FALSE(response_info->network_accessed);
+ ReadAndVerifyTransaction(trans.get(), transaction);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that LOAD_FROM_CACHE_IF_OFFLINE returns proper response on
+// non-offline failure.
+TEST(HttpCache, SimpleGET_CacheOverride_NonOffline) {
+ MockHttpCache cache;
+
+ // Prime cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_FROM_CACHE_IF_OFFLINE;
+ transaction.response_headers = "Cache-Control: no-cache\n";
+
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+
+ // Network failure with non-offline error; should fail with that error.
+ transaction.return_code = net::ERR_PROXY_CONNECTION_FAILED;
+ AddMockTransaction(&transaction);
+
+ net::HttpResponseInfo response_info2;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), transaction,
+ &response_info2);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_FALSE(response_info2.server_data_unavailable);
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Confirm if we have an empty cache, a read is marked as network verified.
+TEST(HttpCache, SimpleGET_NetworkAccessed_Network) {
+ MockHttpCache cache;
+
+ // write to the cache
+ net::HttpResponseInfo response_info;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response_info);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ EXPECT_TRUE(response_info.network_accessed);
+}
+
+// Confirm if we have a fresh entry in cache, it isn't marked as
+// network verified.
+TEST(HttpCache, SimpleGET_NetworkAccessed_Cache) {
+ MockHttpCache cache;
+
+ // Prime cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Re-run transaction; make sure we don't mark the network as accessed.
+ net::HttpResponseInfo response_info;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), transaction,
+ &response_info);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_FALSE(response_info.server_data_unavailable);
+ EXPECT_FALSE(response_info.network_accessed);
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // Force this transaction to write to the cache again.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_BYPASS_CACHE;
+
+ net::CapturingBoundNetLog log;
+
+ // This prevents a number of write events from being logged.
+ log.SetLogLevel(net::NetLog::LOG_BASIC);
+ net::LoadTimingInfo load_timing_info;
+
+ // Write to the cache.
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ // Check that the NetLog was filled as expected.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(8u, entries.size());
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 0, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 1, net::NetLog::TYPE_HTTP_CACHE_GET_BACKEND));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 2, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 3, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ entries, 6, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ entries, 7, net::NetLog::TYPE_HTTP_CACHE_ADD_TO_ENTRY));
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "pragma: no-cache\r\n";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit2) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "cache-control: no-cache\r\n";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimpleGET_LoadValidateCache) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // Read from the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // Force this transaction to validate the cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+
+ net::HttpResponseInfo response_info;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseInfoAndGetTiming(
+ cache.http_cache(), transaction, &response_info, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ EXPECT_TRUE(response_info.network_accessed);
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+TEST(HttpCache, SimpleGET_LoadValidateCache_Implicit) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // read from the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // force this transaction to validate the cache
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "cache-control: max-age=0\r\n";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+static void PreserveRequestHeaders_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
+}
+
+// Tests that we don't remove extra headers for simple requests.
+TEST(HttpCache, SimpleGET_PreserveRequestHeaders) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.handler = PreserveRequestHeaders_Handler;
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.response_headers = "Cache-Control: max-age=0\n";
+ AddMockTransaction(&transaction);
+
+ // Write, then revalidate the entry.
+ RunTransactionTest(cache.http_cache(), transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't remove extra headers for conditionalized requests.
+TEST(HttpCache, ConditionalizedGET_PreserveRequestHeaders) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
+
+ MockTransaction transaction(kETagGET_Transaction);
+ transaction.handler = PreserveRequestHeaders_Handler;
+ transaction.request_headers = "If-None-Match: \"foopy\"\r\n"
+ EXTRA_HEADER;
+ AddMockTransaction(&transaction);
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+TEST(HttpCache, SimpleGET_ManyReaders) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+ EXPECT_EQ(net::LOAD_STATE_IDLE, c->trans->GetLoadState());
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // All requests are waiting for the active entry.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ EXPECT_EQ(net::LOAD_STATE_WAITING_FOR_CACHE, c->trans->GetLoadState());
+ }
+
+ // Allow all requests to move from the Create queue to the active entry.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // All requests depend on the writer, and the writer is between Start and
+ // Read, i.e. idle.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ EXPECT_EQ(net::LOAD_STATE_IDLE, c->trans->GetLoadState());
+ }
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+
+ // We should not have had to re-open the disk entry
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ delete c;
+ }
+}
+
+// This is a test for http://code.google.com/p/chromium/issues/detail?id=4769.
+// If cancelling a request is racing with another request for the same resource
+// finishing, we have to make sure that we remove both transactions from the
+// entry.
+TEST(HttpCache, SimpleGET_RacingReaders) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ MockHttpRequest reader_request(kSimpleGET_Transaction);
+ reader_request.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ MockHttpRequest* this_request = &request;
+ if (i == 1 || i == 2)
+ this_request = &reader_request;
+
+ c->result = c->trans->Start(
+ this_request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // Allow all requests to move from the Create queue to the active entry.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ Context* c = context_list[0];
+ ASSERT_EQ(net::ERR_IO_PENDING, c->result);
+ c->result = c->callback.WaitForResult();
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+
+ // Now we have 2 active readers and two queued transactions.
+
+ EXPECT_EQ(net::LOAD_STATE_IDLE,
+ context_list[2]->trans->GetLoadState());
+ EXPECT_EQ(net::LOAD_STATE_WAITING_FOR_CACHE,
+ context_list[3]->trans->GetLoadState());
+
+ c = context_list[1];
+ ASSERT_EQ(net::ERR_IO_PENDING, c->result);
+ c->result = c->callback.WaitForResult();
+ if (c->result == net::OK)
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+
+ // At this point we have one reader, two pending transactions and a task on
+ // the queue to move to the next transaction. Now we cancel the request that
+ // is the current reader, and expect the queued task to be able to start the
+ // next request.
+
+ c = context_list[2];
+ c->trans.reset();
+
+ for (int i = 3; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ if (c->result == net::OK)
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+
+ // We should not have had to re-open the disk entry.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ delete c;
+ }
+}
+
+// Tests that we can doom an entry with pending transactions and delete one of
+// the pending transactions before the first one completes.
+// See http://code.google.com/p/chromium/issues/detail?id=25588
+TEST(HttpCache, SimpleGET_DoomWithPending) {
+ // We need simultaneous doomed / not_doomed entries so let's use a real cache.
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(1024 * 1024));
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ MockHttpRequest writer_request(kSimpleGET_Transaction);
+ writer_request.load_flags = net::LOAD_BYPASS_CACHE;
+
+ ScopedVector<Context> context_list;
+ const int kNumTransactions = 4;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ MockHttpRequest* this_request = &request;
+ if (i == 3)
+ this_request = &writer_request;
+
+ c->result = c->trans->Start(
+ this_request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // The first request should be a writer at this point, and the two subsequent
+ // requests should be pending. The last request doomed the first entry.
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+
+ // Cancel the first queued transaction.
+ delete context_list[1];
+ context_list.get()[1] = NULL;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ if (i == 1)
+ continue;
+ Context* c = context_list[i];
+ ASSERT_EQ(net::ERR_IO_PENDING, c->result);
+ c->result = c->callback.WaitForResult();
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+}
+
+// This is a test for http://code.google.com/p/chromium/issues/detail?id=4731.
+// We may attempt to delete an entry synchronously with the act of adding a new
+// transaction to said entry.
+TEST(HttpCache, FastNoStoreGET_DoneWithPending) {
+ MockHttpCache cache;
+
+ // The headers will be served right from the call to Start() the request.
+ MockHttpRequest request(kFastNoStoreGET_Transaction);
+ FastTransactionServer request_handler;
+ AddMockTransaction(&kFastNoStoreGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 3;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // Allow all requests to move from the Create queue to the active entry.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Now, make sure that the second request asks for the entry not to be stored.
+ request_handler.set_no_store(true);
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ ReadAndVerifyTransaction(c->trans.get(), kFastNoStoreGET_Transaction);
+ delete c;
+ }
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kFastNoStoreGET_Transaction);
+}
+
+TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 2;
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // Allow all requests to move from the Create queue to the active entry.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be a writer at this point, and the subsequent
+ // requests should be pending.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ if (c->result == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+ // Destroy only the first transaction.
+ if (i == 0) {
+ delete c;
+ context_list[i] = NULL;
+ }
+ }
+
+ // Complete the rest of the transactions.
+ for (int i = 1; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+
+ // We should have had to re-open the disk entry.
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ for (int i = 1; i < kNumTransactions; ++i) {
+ Context* c = context_list[i];
+ delete c;
+ }
+}
+
+// Tests that we can cancel requests that are queued waiting to open the disk
+// cache entry.
+TEST(HttpCache, SimpleGET_ManyWriters_CancelCreate) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // The first request should be creating the disk cache entry and the others
+ // should be pending.
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Cancel a request from the pending queue.
+ delete context_list[3];
+ context_list[3] = NULL;
+
+ // Cancel the request that is creating the entry. This will force the pending
+ // operations to restart.
+ delete context_list[0];
+ context_list[0] = NULL;
+
+ // Complete the rest of the transactions.
+ for (int i = 1; i < kNumTransactions; i++) {
+ Context* c = context_list[i];
+ if (c) {
+ c->result = c->callback.GetResult(c->result);
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+ }
+
+ // We should have had to re-create the disk entry.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ for (int i = 1; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
+// Tests that we can cancel a single request to open a disk cache entry.
+TEST(HttpCache, SimpleGET_CancelCreate) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ Context* c = new Context();
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING, c->result);
+
+ // Release the reference that the mock disk cache keeps for this entry, so
+ // that we test that the http cache handles the cancellation correctly.
+ cache.disk_cache()->ReleaseAll();
+ delete c;
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we delete/create entries even if multiple requests are queued.
+TEST(HttpCache, SimpleGET_ManyWriters_BypassCache) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ request.load_flags = net::LOAD_BYPASS_CACHE;
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // The first request should be deleting the disk cache entry and the others
+ // should be pending.
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ // Complete the transactions.
+ for (int i = 0; i < kNumTransactions; i++) {
+ Context* c = context_list[i];
+ c->result = c->callback.GetResult(c->result);
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+
+ // We should have had to re-create the disk entry multiple times.
+
+ EXPECT_EQ(5, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(5, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
+TEST(HttpCache, SimpleGET_AbandonedCacheRead) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ net::TestCompletionCallback callback;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Test that destroying the transaction while it is reading from the cache
+ // works properly.
+ trans.reset();
+
+ // Make sure we pump any pending events, which should include a call to
+ // HttpCache::Transaction::OnCacheReadCompleted.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Tests that we can delete the HttpCache and deal with queued transactions
+// ("waiting for the backend" as opposed to Active or Doomed entries).
+TEST(HttpCache, SimpleGET_ManyWriters_DeleteCache) {
+ scoped_ptr<MockHttpCache> cache(new MockHttpCache(
+ new MockBackendNoCbFactory()));
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 5;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache->http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->result = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ }
+
+ // The first request should be creating the disk cache entry and the others
+ // should be pending.
+
+ EXPECT_EQ(0, cache->network_layer()->transaction_count());
+ EXPECT_EQ(0, cache->disk_cache()->open_count());
+ EXPECT_EQ(0, cache->disk_cache()->create_count());
+
+ cache.reset();
+
+ // There is not much to do with the transactions at this point... they are
+ // waiting for a callback that will not fire.
+ for (int i = 0; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
+// Tests that we queue requests when initializing the backend.
+TEST(HttpCache, SimpleGET_WaitForBackend) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ MockHttpCache cache(factory);
+
+ MockHttpRequest request0(kSimpleGET_Transaction);
+ MockHttpRequest request1(kTypicalGET_Transaction);
+ MockHttpRequest request2(kETagGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 3;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+ }
+
+ context_list[0]->result = context_list[0]->trans->Start(
+ &request0, context_list[0]->callback.callback(), net::BoundNetLog());
+ context_list[1]->result = context_list[1]->trans->Start(
+ &request1, context_list[1]->callback.callback(), net::BoundNetLog());
+ context_list[2]->result = context_list[2]->trans->Start(
+ &request2, context_list[2]->callback.callback(), net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be creating the disk cache.
+ EXPECT_FALSE(context_list[0]->callback.have_result());
+
+ factory->FinishCreation();
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ EXPECT_TRUE(context_list[i]->callback.have_result());
+ delete context_list[i];
+ }
+}
+
+// Tests that we can cancel requests that are queued waiting for the backend
+// to be initialized.
+TEST(HttpCache, SimpleGET_WaitForBackend_CancelCreate) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ MockHttpCache cache(factory);
+
+ MockHttpRequest request0(kSimpleGET_Transaction);
+ MockHttpRequest request1(kTypicalGET_Transaction);
+ MockHttpRequest request2(kETagGET_Transaction);
+
+ std::vector<Context*> context_list;
+ const int kNumTransactions = 3;
+
+ for (int i = 0; i < kNumTransactions; i++) {
+ context_list.push_back(new Context());
+ Context* c = context_list[i];
+
+ c->result = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+ }
+
+ context_list[0]->result = context_list[0]->trans->Start(
+ &request0, context_list[0]->callback.callback(), net::BoundNetLog());
+ context_list[1]->result = context_list[1]->trans->Start(
+ &request1, context_list[1]->callback.callback(), net::BoundNetLog());
+ context_list[2]->result = context_list[2]->trans->Start(
+ &request2, context_list[2]->callback.callback(), net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The first request should be creating the disk cache.
+ EXPECT_FALSE(context_list[0]->callback.have_result());
+
+ // Cancel a request from the pending queue.
+ delete context_list[1];
+ context_list[1] = NULL;
+
+ // Cancel the request that is creating the entry.
+ delete context_list[0];
+ context_list[0] = NULL;
+
+ // Complete the last transaction.
+ factory->FinishCreation();
+
+ context_list[2]->result =
+ context_list[2]->callback.GetResult(context_list[2]->result);
+ ReadAndVerifyTransaction(context_list[2]->trans.get(), kETagGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ delete context_list[2];
+}
+
+// Tests that we can delete the cache while creating the backend.
+TEST(HttpCache, DeleteCacheWaitingForBackend) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ scoped_ptr<MockHttpCache> cache(new MockHttpCache(factory));
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ scoped_ptr<Context> c(new Context());
+ c->result = cache->http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+
+ // Just to make sure that everything is still pending.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The request should be creating the disk cache.
+ EXPECT_FALSE(c->callback.have_result());
+
+ // We cannot call FinishCreation because the factory itself will go away with
+ // the cache, so grab the callback and attempt to use it.
+ net::CompletionCallback callback = factory->callback();
+ scoped_ptr<disk_cache::Backend>* backend = factory->backend();
+
+ cache.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ backend->reset();
+ callback.Run(net::ERR_ABORTED);
+}
+
+// Tests that we can delete the cache while creating the backend, from within
+// one of the callbacks.
+TEST(HttpCache, DeleteCacheWaitingForBackend2) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ MockHttpCache* cache = new MockHttpCache(factory);
+
+ DeleteCacheCompletionCallback cb(cache);
+ disk_cache::Backend* backend;
+ int rv = cache->http_cache()->GetBackend(&backend, cb.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Now let's queue a regular transaction
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ scoped_ptr<Context> c(new Context());
+ c->result = cache->http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, c->result);
+
+ c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+
+ // And another direct backend request.
+ net::TestCompletionCallback cb2;
+ rv = cache->http_cache()->GetBackend(&backend, cb2.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Just to make sure that everything is still pending.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The request should be queued.
+ EXPECT_FALSE(c->callback.have_result());
+
+ // Generate the callback.
+ factory->FinishCreation();
+ rv = cb.WaitForResult();
+
+ // The cache should be gone by now.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(net::OK, c->callback.GetResult(c->result));
+ EXPECT_FALSE(cb2.have_result());
+}
+
+TEST(HttpCache, TypicalGET_ConditionalRequest) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Get the same URL again, but this time we expect it to result
+ // in a conditional request.
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), kTypicalGET_Transaction,
+ log.bound(), &load_timing_info);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+static void ETagGet_ConditionalRequest_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(
+ request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch));
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_headers->assign(kETagGET_Transaction.response_headers);
+ response_data->clear();
+}
+
+TEST(HttpCache, ETagGET_ConditionalRequest_304) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Get the same URL again, but this time we expect it to result
+ // in a conditional request.
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.handler = ETagGet_ConditionalRequest_Handler;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+class RevalidationServer {
+ public:
+ RevalidationServer() {
+ s_etag_used_ = false;
+ s_last_modified_used_ = false;
+ }
+
+ bool EtagUsed() { return s_etag_used_; }
+ bool LastModifiedUsed() { return s_last_modified_used_; }
+
+ static void Handler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data);
+
+ private:
+ static bool s_etag_used_;
+ static bool s_last_modified_used_;
+};
+bool RevalidationServer::s_etag_used_ = false;
+bool RevalidationServer::s_last_modified_used_ = false;
+
+void RevalidationServer::Handler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ if (request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch))
+ s_etag_used_ = true;
+
+ if (request->extra_headers.HasHeader(
+ net::HttpRequestHeaders::kIfModifiedSince)) {
+ s_last_modified_used_ = true;
+ }
+
+ if (s_etag_used_ || s_last_modified_used_) {
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_headers->assign(kTypicalGET_Transaction.response_headers);
+ response_data->clear();
+ } else {
+ response_status->assign(kTypicalGET_Transaction.status);
+ response_headers->assign(kTypicalGET_Transaction.response_headers);
+ response_data->assign(kTypicalGET_Transaction.data);
+ }
+}
+
+// Tests revalidation after a vary match.
+TEST(HttpCache, SimpleGET_LoadValidateCache_VaryMatch) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kTypicalGET_Transaction);
+ transaction.request_headers = "Foo: bar\r\n";
+ transaction.response_headers =
+ "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Etag: \"foopy\"\n"
+ "Cache-Control: max-age=0\n"
+ "Vary: Foo\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Read from the cache.
+ RevalidationServer server;
+ transaction.handler = server.Handler;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ EXPECT_TRUE(server.EtagUsed());
+ EXPECT_TRUE(server.LastModifiedUsed());
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests revalidation after a vary mismatch if etag is present.
+TEST(HttpCache, SimpleGET_LoadValidateCache_VaryMismatch) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kTypicalGET_Transaction);
+ transaction.request_headers = "Foo: bar\r\n";
+ transaction.response_headers =
+ "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Etag: \"foopy\"\n"
+ "Cache-Control: max-age=0\n"
+ "Vary: Foo\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Read from the cache and revalidate the entry.
+ RevalidationServer server;
+ transaction.handler = server.Handler;
+ transaction.request_headers = "Foo: none\r\n";
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ EXPECT_TRUE(server.EtagUsed());
+ EXPECT_FALSE(server.LastModifiedUsed());
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests lack of revalidation after a vary mismatch and no etag.
+TEST(HttpCache, SimpleGET_LoadDontValidateCache_VaryMismatch) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kTypicalGET_Transaction);
+ transaction.request_headers = "Foo: bar\r\n";
+ transaction.response_headers =
+ "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Cache-Control: max-age=0\n"
+ "Vary: Foo\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Read from the cache and don't revalidate the entry.
+ RevalidationServer server;
+ transaction.handler = server.Handler;
+ transaction.request_headers = "Foo: none\r\n";
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
+ &load_timing_info);
+
+ EXPECT_FALSE(server.EtagUsed());
+ EXPECT_FALSE(server.LastModifiedUsed());
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+ RemoveMockTransaction(&transaction);
+}
+
+static void ETagGet_UnconditionalRequest_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_FALSE(
+ request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch));
+}
+
+TEST(HttpCache, ETagGET_Http10) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+ transaction.status = "HTTP/1.0 200 OK";
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Get the same URL again, without generating a conditional request.
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.handler = ETagGet_UnconditionalRequest_Handler;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, ETagGET_Http10_Range) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+ transaction.status = "HTTP/1.0 200 OK";
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Get the same URL again, but use a byte range request.
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.handler = ETagGet_UnconditionalRequest_Handler;
+ transaction.request_headers = "Range: bytes = 5-\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+static void ETagGet_ConditionalRequest_NoStore_Handler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(
+ request->extra_headers.HasHeader(net::HttpRequestHeaders::kIfNoneMatch));
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_headers->assign("Cache-Control: no-store\n");
+ response_data->clear();
+}
+
+TEST(HttpCache, ETagGET_ConditionalRequest_304_NoStore) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Get the same URL again, but this time we expect it to result
+ // in a conditional request.
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.handler = ETagGet_ConditionalRequest_NoStore_Handler;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedMockTransaction transaction2(kETagGET_Transaction);
+
+ // Write to the cache again. This should create a new entry.
+ RunTransactionTest(cache.http_cache(), transaction2);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Helper that does 4 requests using HttpCache:
+//
+// (1) loads |kUrl| -- expects |net_response_1| to be returned.
+// (2) loads |kUrl| from cache only -- expects |net_response_1| to be returned.
+// (3) loads |kUrl| using |extra_request_headers| -- expects |net_response_2| to
+// be returned.
+// (4) loads |kUrl| from cache only -- expects |cached_response_2| to be
+// returned.
+static void ConditionalizedRequestUpdatesCacheHelper(
+ const Response& net_response_1,
+ const Response& net_response_2,
+ const Response& cached_response_2,
+ const char* extra_request_headers) {
+ MockHttpCache cache;
+
+ // The URL we will be requesting.
+ const char* kUrl = "http://foobar.com/main.css";
+
+ // Junk network response.
+ static const Response kUnexpectedResponse = {
+ "HTTP/1.1 500 Unexpected",
+ "Server: unexpected_header",
+ "unexpected body"
+ };
+
+ // We will control the network layer's responses for |kUrl| using
+ // |mock_network_response|.
+ MockTransaction mock_network_response = { 0 };
+ mock_network_response.url = kUrl;
+ AddMockTransaction(&mock_network_response);
+
+ // Request |kUrl| for the first time. It should hit the network and
+ // receive |kNetResponse1|, which it saves into the HTTP cache.
+
+ MockTransaction request = { 0 };
+ request.url = kUrl;
+ request.method = "GET";
+ request.request_headers = "";
+
+ net_response_1.AssignTo(&mock_network_response); // Network mock.
+ net_response_1.AssignTo(&request); // Expected result.
+
+ std::string response_headers;
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(net_response_1.status_and_headers(), response_headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Request |kUrl| a second time. Now |kNetResponse1| it is in the HTTP
+ // cache, so we don't hit the network.
+
+ request.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ kUnexpectedResponse.AssignTo(&mock_network_response); // Network mock.
+ net_response_1.AssignTo(&request); // Expected result.
+
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(net_response_1.status_and_headers(), response_headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Request |kUrl| yet again, but this time give the request an
+ // "If-Modified-Since" header. This will cause the request to re-hit the
+ // network. However now the network response is going to be
+ // different -- this simulates a change made to the CSS file.
+
+ request.request_headers = extra_request_headers;
+ request.load_flags = net::LOAD_NORMAL;
+
+ net_response_2.AssignTo(&mock_network_response); // Network mock.
+ net_response_2.AssignTo(&request); // Expected result.
+
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(net_response_2.status_and_headers(), response_headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Finally, request |kUrl| again. This request should be serviced from
+ // the cache. Moreover, the value in the cache should be |kNetResponse2|
+ // and NOT |kNetResponse1|. The previous step should have replaced the
+ // value in the cache with the modified response.
+
+ request.request_headers = "";
+ request.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ kUnexpectedResponse.AssignTo(&mock_network_response); // Network mock.
+ cached_response_2.AssignTo(&request); // Expected result.
+
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(cached_response_2.status_and_headers(), response_headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&mock_network_response);
+}
+
+// Check that when an "if-modified-since" header is attached
+// to the request, the result still updates the cached entry.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache1) {
+ // First network response for |kUrl|.
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
+ "body2"
+ };
+
+ const char* extra_headers =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse2, extra_headers);
+}
+
+// Check that when an "if-none-match" header is attached
+// to the request, the result updates the cached entry.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache2) {
+ // First network response for |kUrl|.
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Etag: \"ETAG1\"\n"
+ "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n", // Should never expire.
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Etag: \"ETAG2\"\n"
+ "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n", // Should never expire.
+ "body2"
+ };
+
+ const char* extra_headers = "If-None-Match: \"ETAG1\"\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse2, extra_headers);
+}
+
+// Check that when an "if-modified-since" header is attached
+// to a request, the 304 (not modified result) result updates the cached
+// headers, and the 304 response is returned rather than the cached response.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache3) {
+ // First network response for |kUrl|.
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Server: server1\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 304 Not Modified",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Server: server2\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ ""
+ };
+
+ static const Response kCachedResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Server: server2\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ const char* extra_headers =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kCachedResponse2, extra_headers);
+}
+
+// Test that when doing an externally conditionalized if-modified-since
+// and there is no corresponding cache entry, a new cache entry is NOT
+// created (304 response).
+TEST(HttpCache, ConditionalizedRequestUpdatesCache4) {
+ MockHttpCache cache;
+
+ const char* kUrl = "http://foobar.com/main.css";
+
+ static const Response kNetResponse = {
+ "HTTP/1.1 304 Not Modified",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ ""
+ };
+
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n";
+
+ // We will control the network layer's responses for |kUrl| using
+ // |mock_network_response|.
+ MockTransaction mock_network_response = { 0 };
+ mock_network_response.url = kUrl;
+ AddMockTransaction(&mock_network_response);
+
+ MockTransaction request = { 0 };
+ request.url = kUrl;
+ request.method = "GET";
+ request.request_headers = kExtraRequestHeaders;
+
+ kNetResponse.AssignTo(&mock_network_response); // Network mock.
+ kNetResponse.AssignTo(&request); // Expected result.
+
+ std::string response_headers;
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(kNetResponse.status_and_headers(), response_headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&mock_network_response);
+}
+
+// Test that when doing an externally conditionalized if-modified-since
+// and there is no corresponding cache entry, a new cache entry is NOT
+// created (200 response).
+TEST(HttpCache, ConditionalizedRequestUpdatesCache5) {
+ MockHttpCache cache;
+
+ const char* kUrl = "http://foobar.com/main.css";
+
+ static const Response kNetResponse = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "foobar!!!"
+ };
+
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n";
+
+ // We will control the network layer's responses for |kUrl| using
+ // |mock_network_response|.
+ MockTransaction mock_network_response = { 0 };
+ mock_network_response.url = kUrl;
+ AddMockTransaction(&mock_network_response);
+
+ MockTransaction request = { 0 };
+ request.url = kUrl;
+ request.method = "GET";
+ request.request_headers = kExtraRequestHeaders;
+
+ kNetResponse.AssignTo(&mock_network_response); // Network mock.
+ kNetResponse.AssignTo(&request); // Expected result.
+
+ std::string response_headers;
+ RunTransactionTestWithResponse(
+ cache.http_cache(), request, &response_headers);
+
+ EXPECT_EQ(kNetResponse.status_and_headers(), response_headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&mock_network_response);
+}
+
+// Test that when doing an externally conditionalized if-modified-since
+// if the date does not match the cache entry's last-modified date,
+// then we do NOT use the response (304) to update the cache.
+// (the if-modified-since date is 2 days AFTER the cache's modification date).
+TEST(HttpCache, ConditionalizedRequestUpdatesCache6) {
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Server: server1\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 304 Not Modified",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Server: server2\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ ""
+ };
+
+ // This is two days in the future from the original response's last-modified
+ // date!
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Fri, 08 Feb 2008 22:38:21 GMT\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
+}
+
+// Test that when doing an externally conditionalized if-none-match
+// if the etag does not match the cache entry's etag, then we do not use the
+// response (304) to update the cache.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache7) {
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Etag: \"Foo1\"\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 304 Not Modified",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Etag: \"Foo2\"\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ ""
+ };
+
+ // Different etag from original response.
+ const char* kExtraRequestHeaders = "If-None-Match: \"Foo2\"\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
+}
+
+// Test that doing an externally conditionalized request with both if-none-match
+// and if-modified-since updates the cache.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache8) {
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Etag: \"Foo1\"\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Etag: \"Foo2\"\n"
+ "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
+ "body2"
+ };
+
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n"
+ "If-None-Match: \"Foo1\"\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse2, kExtraRequestHeaders);
+}
+
+// Test that doing an externally conditionalized request with both if-none-match
+// and if-modified-since does not update the cache with only one match.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache9) {
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Etag: \"Foo1\"\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Etag: \"Foo2\"\n"
+ "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
+ "body2"
+ };
+
+ // The etag doesn't match what we have stored.
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Wed, 06 Feb 2008 22:38:21 GMT\r\n"
+ "If-None-Match: \"Foo2\"\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
+}
+
+// Test that doing an externally conditionalized request with both if-none-match
+// and if-modified-since does not update the cache with only one match.
+TEST(HttpCache, ConditionalizedRequestUpdatesCache10) {
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Etag: \"Foo1\"\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ "body1"
+ };
+
+ // Second network response for |kUrl|.
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Etag: \"Foo2\"\n"
+ "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
+ "body2"
+ };
+
+ // The modification date doesn't match what we have stored.
+ const char* kExtraRequestHeaders =
+ "If-Modified-Since: Fri, 08 Feb 2008 22:38:21 GMT\r\n"
+ "If-None-Match: \"Foo1\"\r\n";
+
+ ConditionalizedRequestUpdatesCacheHelper(
+ kNetResponse1, kNetResponse2, kNetResponse1, kExtraRequestHeaders);
+}
+
+TEST(HttpCache, UrlContainingHash) {
+ MockHttpCache cache;
+
+ // Do a typical GET request -- should write an entry into our cache.
+ MockTransaction trans(kTypicalGET_Transaction);
+ RunTransactionTest(cache.http_cache(), trans);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Request the same URL, but this time with a reference section (hash).
+ // Since the cache key strips the hash sections, this should be a cache hit.
+ std::string url_with_hash = std::string(trans.url) + "#multiple#hashes";
+ trans.url = url_with_hash.c_str();
+ trans.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ RunTransactionTest(cache.http_cache(), trans);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we skip the cache for POST requests that do not have an upload
+// identifier.
+TEST(HttpCache, SimplePOST_SkipsCache) {
+ MockHttpCache cache;
+
+ RunTransactionTest(cache.http_cache(), kSimplePOST_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Miss) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ ASSERT_EQ(net::ERR_CACHE_MISS, callback.GetResult(rv));
+
+ trans.reset();
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Hit) {
+ MockHttpCache cache;
+
+ // Test that we hit the cache for POST requests.
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+
+ const int64 kUploadId = 1; // Just a dummy value.
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, kUploadId);
+ MockHttpRequest request(transaction);
+ request.upload_data_stream = &upload_data_stream;
+
+ // Populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, request, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Load from cache.
+ request.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, request, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Test that we don't hit the cache for POST requests if there is a byte range.
+TEST(HttpCache, SimplePOST_WithRanges) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.request_headers = "Range: bytes = 0-4\r\n";
+
+ const int64 kUploadId = 1; // Just a dummy value.
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, kUploadId);
+
+ MockHttpRequest request(transaction);
+ request.upload_data_stream = &upload_data_stream;
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, request, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+// Tests that a POST is cached separately from a previously cached GET.
+TEST(HttpCache, SimplePOST_SeparateCache) {
+ MockHttpCache cache;
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 1);
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ MockHttpRequest req1(transaction);
+ req1.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ transaction.method = "GET";
+ MockHttpRequest req2(transaction);
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that a successful POST invalidates a previously cached GET.
+TEST(HttpCache, SimplePOST_Invalidate_205) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 1);
+
+ transaction.method = "POST";
+ transaction.status = "HTTP/1.1 205 No Content";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(3, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't invalidate entries as a result of a failed POST.
+TEST(HttpCache, SimplePOST_DontInvalidate_100) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 1);
+
+ transaction.method = "POST";
+ transaction.status = "HTTP/1.1 100 Continue";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we do not cache the response of a PUT.
+TEST(HttpCache, SimplePUT_Miss) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.method = "PUT";
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ MockHttpRequest request(transaction);
+ request.upload_data_stream = &upload_data_stream;
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, request, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+// Tests that we invalidate entries as a result of a PUT.
+TEST(HttpCache, SimplePUT_Invalidate) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ transaction.method = "PUT";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we invalidate entries as a result of a PUT.
+TEST(HttpCache, SimplePUT_Invalidate_305) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ transaction.method = "PUT";
+ transaction.status = "HTTP/1.1 305 Use Proxy";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't invalidate entries as a result of a failed PUT.
+TEST(HttpCache, SimplePUT_DontInvalidate_404) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ transaction.method = "PUT";
+ transaction.status = "HTTP/1.1 404 Not Found";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we do not cache the response of a DELETE.
+TEST(HttpCache, SimpleDELETE_Miss) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimplePOST_Transaction);
+ transaction.method = "DELETE";
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ MockHttpRequest request(transaction);
+ request.upload_data_stream = &upload_data_stream;
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, request, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+// Tests that we invalidate entries as a result of a DELETE.
+TEST(HttpCache, SimpleDELETE_Invalidate) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ MockHttpRequest req1(transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ ScopedVector<net::UploadElementReader> element_readers;
+ element_readers.push_back(new net::UploadBytesElementReader("hello", 5));
+ net::UploadDataStream upload_data_stream(&element_readers, 0);
+
+ transaction.method = "DELETE";
+ MockHttpRequest req2(transaction);
+ req2.upload_data_stream = &upload_data_stream;
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req2, NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTestWithRequest(cache.http_cache(), transaction, req1, NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we invalidate entries as a result of a DELETE.
+TEST(HttpCache, SimpleDELETE_Invalidate_301) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ transaction.method = "DELETE";
+ transaction.status = "HTTP/1.1 301 Moved Permanently ";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ transaction.method = "GET";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't invalidate entries as a result of a failed DELETE.
+TEST(HttpCache, SimpleDELETE_DontInvalidate_416) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+
+ // Attempt to populate the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ transaction.method = "DELETE";
+ transaction.status = "HTTP/1.1 416 Requested Range Not Satisfiable";
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ transaction.method = "GET";
+ transaction.status = "HTTP/1.1 200 OK";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't invalidate entries after a failed network transaction.
+TEST(HttpCache, SimpleGET_DontInvalidateOnFailure) {
+ MockHttpCache cache;
+
+ // Populate the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ // Fail the network request.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.return_code = net::ERR_FAILED;
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ RemoveMockTransaction(&transaction);
+
+ transaction.load_flags = net::LOAD_ONLY_FROM_CACHE;
+ transaction.return_code = net::OK;
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Make sure the transaction didn't reach the network.
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ RemoveMockTransaction(&transaction);
+}
+
+TEST(HttpCache, RangeGET_SkipsCache) {
+ MockHttpCache cache;
+
+ // Test that we skip the cache for range GET requests. Eventually, we will
+ // want to cache these, but we'll still have cases where skipping the cache
+ // makes sense, so we want to make sure that it works properly.
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.request_headers = "If-None-Match: foo\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ transaction.request_headers =
+ "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+// Test that we skip the cache for range requests that include a validation
+// header.
+TEST(HttpCache, RangeGET_SkipsCache2) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kRangeGET_Transaction);
+ transaction.request_headers = "If-None-Match: foo\r\n"
+ EXTRA_HEADER
+ "Range: bytes = 40-49\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ transaction.request_headers =
+ "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT\r\n"
+ EXTRA_HEADER
+ "Range: bytes = 40-49\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ transaction.request_headers = "If-Range: bla\r\n"
+ EXTRA_HEADER
+ "Range: bytes = 40-49\r\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+// Tests that receiving 206 for a regular request is handled correctly.
+TEST(HttpCache, GET_Crazy206) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ AddMockTransaction(&transaction);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.handler = NULL;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // This should read again from the net.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't cache partial responses that can't be validated.
+TEST(HttpCache, RangeGET_NoStrongValidators) {
+ MockHttpCache cache;
+ std::string headers;
+
+ // Attempt to write to the cache (40-49).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ AddMockTransaction(&transaction);
+ transaction.response_headers = "Content-Length: 10\n"
+ "ETag: w/\"foo\"\n";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Now verify that there's no cached data.
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can cache range requests and fetch random blocks from the
+// cache and the network.
+TEST(HttpCache, RangeGET_OK) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write to the cache (30-39).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 30-39 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 30, 39);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write and read from the cache (20-59).
+ transaction.request_headers = "Range: bytes = 20-59\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 20-29 rg: 30-39 rg: 40-49 rg: 50-59 ";
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ Verify206Response(headers, 20, 59);
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can cache range requests and fetch random blocks from the
+// cache and the network, with synchronous responses.
+TEST(HttpCache, RangeGET_SyncOK) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.test_mode = TEST_MODE_SYNC_ALL;
+ AddMockTransaction(&transaction);
+
+ // Write to the cache (40-49).
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write to the cache (30-39).
+ transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 30-39 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 30, 39);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write and read from the cache (20-59).
+ transaction.request_headers = "Range: bytes = 20-59\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 20-29 rg: 30-39 rg: 40-49 rg: 50-59 ";
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ Verify206Response(headers, 20, 59);
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't revalidate an entry unless we are required to do so.
+TEST(HttpCache, RangeGET_Revalidate1) {
+ MockHttpCache cache;
+ std::string headers;
+
+ // Write to the cache (40-49).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n" // Should never expire.
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingCachedResponse(load_timing_info);
+
+ // Read again forcing the revalidation.
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Checks that we revalidate an entry when the headers say so.
+TEST(HttpCache, RangeGET_Revalidate2) {
+ MockHttpCache cache;
+ std::string headers;
+
+ // Write to the cache (40-49).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "Expires: Sat, 18 Apr 2009 01:10:43 GMT\n" // Expired.
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+ Verify206Response(headers, 40, 49);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we deal with 304s for range requests.
+TEST(HttpCache, RangeGET_304) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RangeTransactionServer handler;
+ handler.set_not_modified(true);
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we deal with 206s when revalidating range requests.
+TEST(HttpCache, RangeGET_ModifiedResult) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Attempt to read from the cache (40-49).
+ RangeTransactionServer handler;
+ handler.set_modified(true);
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // And the entry should be gone.
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we cache 301s for range requests.
+TEST(HttpCache, RangeGET_301) {
+ MockHttpCache cache;
+ ScopedMockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.status = "HTTP/1.1 301 Moved Permanently";
+ transaction.response_headers = "Location: http://www.bar.com/\n";
+ transaction.data = "";
+ transaction.handler = NULL;
+ AddMockTransaction(&transaction);
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can cache range requests when the start or end is unknown.
+// We start with one suffix request, followed by a request from a given point.
+TEST(HttpCache, UnknownRangeGET_1) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (70-79).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = -10\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 70, 79);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write and read from the cache (60-79).
+ transaction.request_headers = "Range: bytes = 60-\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 60-69 rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 60, 79);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can cache range requests when the start or end is unknown.
+// We start with one request from a given point, followed by a suffix request.
+// We'll also verify that synchronous cache responses work as intended.
+TEST(HttpCache, UnknownRangeGET_2) {
+ MockHttpCache cache;
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.test_mode = TEST_MODE_SYNC_CACHE_START |
+ TEST_MODE_SYNC_CACHE_READ |
+ TEST_MODE_SYNC_CACHE_WRITE;
+ AddMockTransaction(&transaction);
+
+ // Write to the cache (70-79).
+ transaction.request_headers = "Range: bytes = 70-\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 70, 79);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Write and read from the cache (60-79).
+ transaction.request_headers = "Range: bytes = -20\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 60-69 rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 60, 79);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that receiving Not Modified when asking for an open range doesn't mess
+// up things.
+TEST(HttpCache, UnknownRangeGET_304) {
+ MockHttpCache cache;
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ AddMockTransaction(&transaction);
+
+ RangeTransactionServer handler;
+ handler.set_not_modified(true);
+
+ // Ask for the end of the file, without knowing the length.
+ transaction.request_headers = "Range: bytes = 70-\r\n" EXTRA_HEADER;
+ transaction.data = "";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ // We just bypass the cache.
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 304 Not Modified\n"));
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can handle non-range requests when we have cached a range.
+TEST(HttpCache, GET_Previous206) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+
+ // Write to the cache (40-49).
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), kRangeGET_TransactionOK, &headers, log.bound(),
+ &load_timing_info);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ // Write and read from the cache (0-79), when not asked for a range.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 200 OK\n"));
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can handle non-range requests when we have cached the first
+// part of the object and the server replies with 304 (Not Modified).
+TEST(HttpCache, GET_Previous206_NotModified) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ AddMockTransaction(&transaction);
+ std::string headers;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+
+ // Write to the cache (0-9).
+ transaction.request_headers = "Range: bytes = 0-9\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 00-09 ";
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+ Verify206Response(headers, 0, 9);
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ // Write to the cache (70-79).
+ transaction.request_headers = "Range: bytes = 70-79\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 70-79 ";
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+ Verify206Response(headers, 70, 79);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ // Read from the cache (0-9), write and read from cache (10 - 79).
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ transaction.request_headers = "Foo: bar\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 200 OK\n"));
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can handle a regular request to a sparse entry, that results in
+// new content provided by the server (206).
+TEST(HttpCache, GET_Previous206_NewContent) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (0-9).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 0-9\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 00-09 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 0, 9);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Now we'll issue a request without any range that should result first in a
+ // 206 (when revalidating), and then in a weird standard answer: the test
+ // server will not modify the response so we'll get the default range... a
+ // real server will answer with 200.
+ MockTransaction transaction2(kRangeGET_TransactionOK);
+ transaction2.request_headers = EXTRA_HEADER;
+ transaction2.load_flags |= net::LOAD_VALIDATE_CACHE;
+ transaction2.data = "Not a range";
+ RangeTransactionServer handler;
+ handler.set_modified(true);
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), transaction2, &headers, log.bound(),
+ &load_timing_info);
+
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 200 OK\n"));
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+
+ // Verify that the previous request deleted the entry.
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can handle cached 206 responses that are not sparse.
+TEST(HttpCache, GET_Previous206_NotSparse) {
+ MockHttpCache cache;
+
+ // Create a disk cache entry that stores 206 headers while not being sparse.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.CreateBackendEntry(kSimpleGET_Transaction.url, &entry,
+ NULL));
+
+ std::string raw_headers(kRangeGET_TransactionOK.status);
+ raw_headers.append("\n");
+ raw_headers.append(kRangeGET_TransactionOK.response_headers);
+ raw_headers = net::HttpUtil::AssembleRawHeaders(raw_headers.data(),
+ raw_headers.size());
+
+ net::HttpResponseInfo response;
+ response.headers = new net::HttpResponseHeaders(raw_headers);
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(500));
+ int len = static_cast<int>(base::strlcpy(buf->data(),
+ kRangeGET_TransactionOK.data, 500));
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf.get(), len, cb.callback(), true);
+ EXPECT_EQ(len, cb.GetResult(rv));
+ entry->Close();
+
+ // Now see that we don't use the stored entry.
+ std::string headers;
+ net::CapturingBoundNetLog log;
+ net::LoadTimingInfo load_timing_info;
+ RunTransactionTestWithResponseAndGetTiming(
+ cache.http_cache(), kSimpleGET_Transaction, &headers, log.bound(),
+ &load_timing_info);
+
+ // We are expecting a 200.
+ std::string expected_headers(kSimpleGET_Transaction.status);
+ expected_headers.append("\n");
+ expected_headers.append(kSimpleGET_Transaction.response_headers);
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+ TestLoadTimingNetworkRequest(load_timing_info);
+}
+
+// Tests that we can handle cached 206 responses that are not sparse. This time
+// we issue a range request and expect to receive a range.
+TEST(HttpCache, RangeGET_Previous206_NotSparse_2) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Create a disk cache entry that stores 206 headers while not being sparse.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.CreateBackendEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
+
+ std::string raw_headers(kRangeGET_TransactionOK.status);
+ raw_headers.append("\n");
+ raw_headers.append(kRangeGET_TransactionOK.response_headers);
+ raw_headers = net::HttpUtil::AssembleRawHeaders(raw_headers.data(),
+ raw_headers.size());
+
+ net::HttpResponseInfo response;
+ response.headers = new net::HttpResponseHeaders(raw_headers);
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(500));
+ int len = static_cast<int>(base::strlcpy(buf->data(),
+ kRangeGET_TransactionOK.data, 500));
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf.get(), len, cb.callback(), true);
+ EXPECT_EQ(len, cb.GetResult(rv));
+ entry->Close();
+
+ // Now see that we don't use the stored entry.
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ // We are expecting a 206.
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can handle cached 206 responses that can't be validated.
+TEST(HttpCache, GET_Previous206_NotValidation) {
+ MockHttpCache cache;
+
+ // Create a disk cache entry that stores 206 headers.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.CreateBackendEntry(kSimpleGET_Transaction.url, &entry,
+ NULL));
+
+ // Make sure that the headers cannot be validated with the server.
+ std::string raw_headers(kRangeGET_TransactionOK.status);
+ raw_headers.append("\n");
+ raw_headers.append("Content-Length: 80\n");
+ raw_headers = net::HttpUtil::AssembleRawHeaders(raw_headers.data(),
+ raw_headers.size());
+
+ net::HttpResponseInfo response;
+ response.headers = new net::HttpResponseHeaders(raw_headers);
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(500));
+ int len = static_cast<int>(base::strlcpy(buf->data(),
+ kRangeGET_TransactionOK.data, 500));
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(1, 0, buf.get(), len, cb.callback(), true);
+ EXPECT_EQ(len, cb.GetResult(rv));
+ entry->Close();
+
+ // Now see that we don't use the stored entry.
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), kSimpleGET_Transaction,
+ &headers);
+
+ // We are expecting a 200.
+ std::string expected_headers(kSimpleGET_Transaction.status);
+ expected_headers.append("\n");
+ expected_headers.append(kSimpleGET_Transaction.response_headers);
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we can handle range requests with cached 200 responses.
+TEST(HttpCache, RangeGET_Previous200) {
+ MockHttpCache cache;
+
+ // Store the whole thing with status 200.
+ MockTransaction transaction(kTypicalGET_Transaction);
+ transaction.url = kRangeGET_TransactionOK.url;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Now see that we use the stored entry.
+ std::string headers;
+ MockTransaction transaction2(kRangeGET_TransactionOK);
+ RangeTransactionServer handler;
+ handler.set_not_modified(true);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
+
+ // We are expecting a 206.
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // The last transaction has finished so make sure the entry is deactivated.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Make a request for an invalid range.
+ MockTransaction transaction3(kRangeGET_TransactionOK);
+ transaction3.request_headers = "Range: bytes = 80-90\r\n" EXTRA_HEADER;
+ transaction3.data = transaction.data;
+ transaction3.load_flags = net::LOAD_PREFERRING_CACHE;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction3, &headers);
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 200 "));
+ EXPECT_EQ(std::string::npos, headers.find("Content-Range:"));
+ EXPECT_EQ(std::string::npos, headers.find("Content-Length: 80"));
+
+ // Make sure the entry is deactivated.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Even though the request was invalid, we should have the entry.
+ RunTransactionTest(cache.http_cache(), transaction2);
+ EXPECT_EQ(3, cache.disk_cache()->open_count());
+
+ // Make sure the entry is deactivated.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now we should receive a range from the server and drop the stored entry.
+ handler.set_not_modified(false);
+ transaction2.request_headers = kRangeGET_TransactionOK.request_headers;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
+ EXPECT_EQ(4, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTest(cache.http_cache(), transaction2);
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can handle a 200 response when dealing with sparse entries.
+TEST(HttpCache, RangeRequestResultsIn200) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (70-79).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = -10\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 70, 79);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Now we'll issue a request that results in a plain 200 response, but to
+ // the to the same URL that we used to store sparse data, and making sure
+ // that we ask for a range.
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+ MockTransaction transaction2(kSimpleGET_Transaction);
+ transaction2.url = kRangeGET_TransactionOK.url;
+ transaction2.request_headers = kRangeGET_TransactionOK.request_headers;
+ AddMockTransaction(&transaction2);
+
+ RunTransactionTestWithResponse(cache.http_cache(), transaction2, &headers);
+
+ std::string expected_headers(kSimpleGET_Transaction.status);
+ expected_headers.append("\n");
+ expected_headers.append(kSimpleGET_Transaction.response_headers);
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction2);
+}
+
+// Tests that a range request that falls outside of the size that we know about
+// only deletes the entry if the resource has indeed changed.
+TEST(HttpCache, RangeGET_MoreThanCurrentSize) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+ std::string headers;
+
+ // Write to the cache (40-49).
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // A weird request should not delete this entry. Ask for bytes 120-.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 120-\r\n" EXTRA_HEADER;
+ transaction.data = "";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ EXPECT_EQ(0U, headers.find("HTTP/1.1 416 "));
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we don't delete a sparse entry when we cancel a request.
+TEST(HttpCache, RangeGET_Cancel) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ MockHttpRequest request(kRangeGET_TransactionOK);
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that the entry has some data stored.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(10));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+ EXPECT_EQ(buf->size(), rv);
+
+ // Destroy the transaction.
+ delete c;
+
+ // Verify that the entry has not been deleted.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ entry->Close();
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we don't delete a sparse entry when we start a new request after
+// cancelling the previous one.
+TEST(HttpCache, RangeGET_Cancel2) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ MockHttpRequest request(kRangeGET_TransactionOK);
+ request.load_flags |= net::LOAD_VALIDATE_CACHE;
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that we revalidate the entry and read from the cache (a single
+ // read will return while waiting for the network).
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(5));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(5, c->callback.GetResult(rv));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Destroy the transaction before completing the read.
+ delete c;
+
+ // We have the read and the delete (OnProcessPendingQueue) waiting on the
+ // message loop. This means that a new transaction will just reuse the same
+ // active entry (no open or create).
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// A slight variation of the previous test, this time we cancel two requests in
+// a row, making sure that the second is waiting for the entry to be ready.
+TEST(HttpCache, RangeGET_Cancel3) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ MockHttpRequest request(kRangeGET_TransactionOK);
+ request.load_flags |= net::LOAD_VALIDATE_CACHE;
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that we revalidate the entry and read from the cache (a single
+ // read will return while waiting for the network).
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(5));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(5, c->callback.GetResult(rv));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Destroy the transaction before completing the read.
+ delete c;
+
+ // We have the read and the delete (OnProcessPendingQueue) waiting on the
+ // message loop. This means that a new transaction will just reuse the same
+ // active entry (no open or create).
+
+ c = new Context();
+ rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ MockDiskEntry::IgnoreCallbacks(true);
+ base::MessageLoop::current()->RunUntilIdle();
+ MockDiskEntry::IgnoreCallbacks(false);
+
+ // The new transaction is waiting for the query range callback.
+ delete c;
+
+ // And we should not crash when the callback is delivered.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that an invalid range response results in no cached entry.
+TEST(HttpCache, RangeGET_InvalidResponse1) {
+ MockHttpCache cache;
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = NULL;
+ transaction.response_headers = "Content-Range: bytes 40-49/45\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ std::string expected(transaction.status);
+ expected.append("\n");
+ expected.append(transaction.response_headers);
+ EXPECT_EQ(expected, headers);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that we don't have a cached entry.
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we reject a range that doesn't match the content-length.
+TEST(HttpCache, RangeGET_InvalidResponse2) {
+ MockHttpCache cache;
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = NULL;
+ transaction.response_headers = "Content-Range: bytes 40-49/80\n"
+ "Content-Length: 20\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ std::string expected(transaction.status);
+ expected.append("\n");
+ expected.append(transaction.response_headers);
+ EXPECT_EQ(expected, headers);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that we don't have a cached entry.
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that if a server tells us conflicting information about a resource we
+// ignore the response.
+TEST(HttpCache, RangeGET_InvalidResponse3) {
+ MockHttpCache cache;
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = NULL;
+ transaction.request_headers = "Range: bytes = 50-59\r\n" EXTRA_HEADER;
+ std::string response_headers(transaction.response_headers);
+ response_headers.append("Content-Range: bytes 50-59/160\n");
+ transaction.response_headers = response_headers.c_str();
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 50, 59);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // This transaction will report a resource size of 80 bytes, and we think it's
+ // 160 so we should ignore the response.
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that we cached the first response but not the second one.
+ disk_cache::Entry* en;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &en));
+
+ int64 cached_start = 0;
+ net::TestCompletionCallback cb;
+ int rv = en->GetAvailableRange(40, 20, &cached_start, cb.callback());
+ EXPECT_EQ(10, cb.GetResult(rv));
+ EXPECT_EQ(50, cached_start);
+ en->Close();
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we handle large range values properly.
+TEST(HttpCache, RangeGET_LargeValues) {
+ // We need a real sparse cache for this test.
+ MockHttpCache cache(net::HttpCache::DefaultBackend::InMemory(1024 * 1024));
+ std::string headers;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = NULL;
+ transaction.request_headers = "Range: bytes = 4294967288-4294967297\r\n"
+ EXTRA_HEADER;
+ transaction.response_headers =
+ "ETag: \"foo\"\n"
+ "Content-Range: bytes 4294967288-4294967297/4294967299\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ std::string expected(transaction.status);
+ expected.append("\n");
+ expected.append(transaction.response_headers);
+ EXPECT_EQ(expected, headers);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ // Verify that we have a cached entry.
+ disk_cache::Entry* en;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &en));
+ en->Close();
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we don't crash with a range request if the disk cache was not
+// initialized properly.
+TEST(HttpCache, RangeGET_NoDiskCache) {
+ MockBlockingBackendFactory* factory = new MockBlockingBackendFactory();
+ factory->set_fail(true);
+ factory->FinishCreation(); // We'll complete synchronously.
+ MockHttpCache cache(factory);
+
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we handle byte range requests that skip the cache.
+TEST(HttpCache, RangeHEAD) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = -10\r\n" EXTRA_HEADER;
+ transaction.method = "HEAD";
+ transaction.data = "rg: 70-79 ";
+
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ Verify206Response(headers, 70, 79);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we don't crash when after reading from the cache we issue a
+// request for the next range and the server gives us a 200 synchronously.
+TEST(HttpCache, RangeGET_FastFlakyServer) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 40-\r\n" EXTRA_HEADER;
+ transaction.test_mode = TEST_MODE_SYNC_NET_START;
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ AddMockTransaction(&transaction);
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+
+ // And now read from the cache and the network.
+ RangeTransactionServer handler;
+ handler.set_bad_200(true);
+ transaction.data = "Not a range";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that when the server gives us less data than expected, we don't keep
+// asking for more data.
+TEST(HttpCache, RangeGET_FastFlakyServer2) {
+ MockHttpCache cache;
+
+ // First, check with an empty cache (WRITE mode).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 40-49\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 40-"; // Less than expected.
+ transaction.handler = NULL;
+ std::string headers(transaction.response_headers);
+ headers.append("Content-Range: bytes 40-49/80\n");
+ transaction.response_headers = headers.c_str();
+
+ AddMockTransaction(&transaction);
+
+ // Write to the cache.
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Now verify that even in READ_WRITE mode, we forward the bad response to
+ // the caller.
+ transaction.request_headers = "Range: bytes = 60-69\r\n" EXTRA_HEADER;
+ transaction.data = "rg: 60-"; // Less than expected.
+ headers = kRangeGET_TransactionOK.response_headers;
+ headers.append("Content-Range: bytes 60-69/80\n");
+ transaction.response_headers = headers.c_str();
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+// This test hits a NOTREACHED so it is a release mode only test.
+TEST(HttpCache, RangeGET_OK_LoadOnlyFromCache) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Write to the cache (40-49).
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Force this transaction to read from the cache.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+
+ trans.reset();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+#endif
+
+// Tests the handling of the "truncation" flag.
+TEST(HttpCache, WriteResponseInfo_Truncated) {
+ MockHttpCache cache;
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.CreateBackendEntry("http://www.google.com", &entry,
+ NULL));
+
+ std::string headers("HTTP/1.1 200 OK");
+ headers = net::HttpUtil::AssembleRawHeaders(headers.data(), headers.size());
+ net::HttpResponseInfo response;
+ response.headers = new net::HttpResponseHeaders(headers);
+
+ // Set the last argument for this to be an incomplete request.
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true));
+ bool truncated = false;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(truncated);
+
+ // And now test the opposite case.
+ EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, false));
+ truncated = true;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_FALSE(truncated);
+ entry->Close();
+}
+
+// Tests basic pickling/unpickling of HttpResponseInfo.
+TEST(HttpCache, PersistHttpResponseInfo) {
+ // Set some fields (add more if needed.)
+ net::HttpResponseInfo response1;
+ response1.was_cached = false;
+ response1.socket_address = net::HostPortPair("1.2.3.4", 80);
+ response1.headers = new net::HttpResponseHeaders("HTTP/1.1 200 OK");
+
+ // Pickle.
+ Pickle pickle;
+ response1.Persist(&pickle, false, false);
+
+ // Unpickle.
+ net::HttpResponseInfo response2;
+ bool response_truncated;
+ EXPECT_TRUE(response2.InitFromPickle(pickle, &response_truncated));
+ EXPECT_FALSE(response_truncated);
+
+ // Verify fields.
+ EXPECT_TRUE(response2.was_cached); // InitFromPickle sets this flag.
+ EXPECT_EQ("1.2.3.4", response2.socket_address.host());
+ EXPECT_EQ(80, response2.socket_address.port());
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+// Tests that we delete an entry when the request is cancelled before starting
+// to read from the network.
+TEST(HttpCache, DoomOnDestruction) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ c->result = c->callback.WaitForResult();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Destroy the transaction. We only have the headers so we should delete this
+ // entry.
+ delete c;
+
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we delete an entry when the request is cancelled if the response
+// does not have content-length and strong validators.
+TEST(HttpCache, DoomOnDestruction2) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that the entry has some data stored.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(10));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+ EXPECT_EQ(buf->size(), rv);
+
+ // Destroy the transaction.
+ delete c;
+
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we delete an entry when the request is cancelled if the response
+// has an "Accept-Ranges: none" header.
+TEST(HttpCache, DoomOnDestruction3) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.response_headers =
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Content-Length: 22\n"
+ "Accept-Ranges: none\n"
+ "Etag: \"foopy\"\n";
+ AddMockTransaction(&transaction);
+ MockHttpRequest request(transaction);
+
+ Context* c = new Context();
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that the entry has some data stored.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(10));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+ EXPECT_EQ(buf->size(), rv);
+
+ // Destroy the transaction.
+ delete c;
+
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we mark an entry as incomplete when the request is cancelled.
+TEST(HttpCache, SetTruncatedFlag) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.response_headers =
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Content-Length: 22\n"
+ "Etag: \"foopy\"\n";
+ AddMockTransaction(&transaction);
+ MockHttpRequest request(transaction);
+
+ scoped_ptr<Context> c(new Context());
+ // We use a test delegate to ensure that after initiating destruction
+ // of the transaction, no further delegate callbacks happen.
+ // We initialize the TestHttpTransactionDelegate with the correct number of
+ // cache actions and network actions to be reported.
+ scoped_ptr<TestHttpTransactionDelegate> delegate(
+ new TestHttpTransactionDelegate(7, 3));
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, delegate.get());
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure that the entry has some data stored.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(10));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = c->callback.WaitForResult();
+ EXPECT_EQ(buf->size(), rv);
+
+ // We want to cancel the request when the transaction is busy.
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ EXPECT_FALSE(c->callback.have_result());
+
+ MockHttpCache::SetTestMode(TEST_MODE_SYNC_ALL);
+ int num_delegate_callbacks_before_destruction =
+ delegate->num_callbacks_observed();
+
+ // Destroy the transaction.
+ c->trans.reset();
+ MockHttpCache::SetTestMode(0);
+
+ // Ensure the delegate received no callbacks during destruction.
+ EXPECT_EQ(num_delegate_callbacks_before_destruction,
+ delegate->num_callbacks_observed());
+
+ // Since the transaction was aborted in the middle of network I/O, we will
+ // manually call the delegate so that its pending I/O operation will be
+ // closed (which is what the test delegate is expecting).
+ delegate->OnNetworkActionFinish();
+
+ // Make sure that we don't invoke the callback. We may have an issue if the
+ // UrlRequestJob is killed directly (without cancelling the UrlRequest) so we
+ // could end up with the transaction being deleted twice if we send any
+ // notification from the transaction destructor (see http://crbug.com/31723).
+ EXPECT_FALSE(c->callback.have_result());
+
+ // Verify that the entry is marked as incomplete.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &entry));
+ net::HttpResponseInfo response;
+ bool truncated = false;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(truncated);
+ entry->Close();
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we don't mark an entry as truncated when we read everything.
+TEST(HttpCache, DontSetTruncatedFlag) {
+ MockHttpCache cache;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.response_headers =
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
+ "Content-Length: 22\n"
+ "Etag: \"foopy\"\n";
+ AddMockTransaction(&transaction);
+ MockHttpRequest request(transaction);
+
+ scoped_ptr<Context> c(new Context());
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, c->callback.GetResult(rv));
+
+ // Read everything.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(22));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(buf->size(), c->callback.GetResult(rv));
+
+ // Destroy the transaction.
+ c->trans.reset();
+
+ // Verify that the entry is not marked as truncated.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &entry));
+ net::HttpResponseInfo response;
+ bool truncated = true;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_FALSE(truncated);
+ entry->Close();
+
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we can continue with a request that was interrupted.
+TEST(HttpCache, GET_IncompleteResource) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request.
+ std::string headers;
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ // We update the headers with the ones received while revalidating.
+ std::string expected_headers(
+ "HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Accept-Ranges: bytes\n"
+ "ETag: \"foo\"\n"
+ "Content-Length: 80\n");
+
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that the disk entry was updated.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ EXPECT_EQ(80, entry->GetDataSize(1));
+ bool truncated = true;
+ net::HttpResponseInfo response;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_FALSE(truncated);
+ entry->Close();
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests the handling of no-store when revalidating a truncated entry.
+TEST(HttpCache, GET_IncompleteResource_NoStore) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+
+ // Now make a regular request.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ std::string response_headers(transaction.response_headers);
+ response_headers += ("Cache-Control: no-store\n");
+ transaction.response_headers = response_headers.c_str();
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ AddMockTransaction(&transaction);
+
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ // We update the headers with the ones received while revalidating.
+ std::string expected_headers(
+ "HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Accept-Ranges: bytes\n"
+ "Cache-Control: no-store\n"
+ "ETag: \"foo\"\n"
+ "Content-Length: 80\n");
+
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that the disk entry was deleted.
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests cancelling a request after the server sent no-store.
+TEST(HttpCache, GET_IncompleteResource_Cancel) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+
+ // Now make a regular request.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ std::string response_headers(transaction.response_headers);
+ response_headers += ("Cache-Control: no-store\n");
+ transaction.response_headers = response_headers.c_str();
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+ AddMockTransaction(&transaction);
+
+ MockHttpRequest request(transaction);
+ Context* c = new Context();
+
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ // Queue another request to this transaction. We have to start this request
+ // before the first one gets the response from the server and dooms the entry,
+ // otherwise it will just create a new entry without being queued to the first
+ // request.
+ Context* pending = new Context();
+ EXPECT_EQ(net::OK,
+ cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &pending->trans, NULL));
+
+ rv = c->trans->Start(&request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ pending->trans->Start(&request, pending->callback.callback(),
+ net::BoundNetLog()));
+ EXPECT_EQ(net::OK, c->callback.GetResult(rv));
+
+ // Make sure that the entry has some data stored.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(5));
+ rv = c->trans->Read(buf.get(), buf->size(), c->callback.callback());
+ EXPECT_EQ(5, c->callback.GetResult(rv));
+
+ // Cancel the requests.
+ delete c;
+ delete pending;
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ RemoveMockTransaction(&transaction);
+}
+
+// Tests that we delete truncated entries if the server changes its mind midway.
+TEST(HttpCache, GET_IncompleteResource2) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Content-length will be intentionally bad.
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 50\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request. We expect the code to fail the validation and
+ // retry the request without using byte ranges.
+ std::string headers;
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "Not a range";
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ // The server will return 200 instead of a byte range.
+ std::string expected_headers(
+ "HTTP/1.1 200 OK\n"
+ "Date: Wed, 28 Nov 2007 09:40:09 GMT\n");
+
+ EXPECT_EQ(expected_headers, headers);
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that the disk entry was deleted.
+ disk_cache::Entry* entry;
+ ASSERT_FALSE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we always validate a truncated request.
+TEST(HttpCache, GET_IncompleteResource3) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // This should not require validation for 10 hours.
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Cache-Control: max-age= 36000\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request.
+ std::string headers;
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+
+ scoped_ptr<Context> c(new Context);
+ EXPECT_EQ(net::OK, cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL));
+
+ MockHttpRequest request(transaction);
+ int rv = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, c->callback.GetResult(rv));
+
+ // We should have checked with the server before finishing Start().
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we cache a 200 response to the validation request.
+TEST(HttpCache, GET_IncompleteResource4) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request.
+ std::string headers;
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "Not a range";
+ RangeTransactionServer handler;
+ handler.set_bad_200(true);
+ RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that the disk entry was updated.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ EXPECT_EQ(11, entry->GetDataSize(1));
+ bool truncated = true;
+ net::HttpResponseInfo response;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_FALSE(truncated);
+ entry->Close();
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that when we cancel a request that was interrupted, we mark it again
+// as truncated.
+TEST(HttpCache, GET_CancelIncompleteResource) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request.
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+
+ MockHttpRequest request(transaction);
+ Context* c = new Context();
+ EXPECT_EQ(net::OK, cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &c->trans, NULL));
+
+ int rv = c->trans->Start(
+ &request, c->callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, c->callback.GetResult(rv));
+
+ // Read 20 bytes from the cache, and 10 from the net.
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(100));
+ rv = c->trans->Read(buf.get(), 20, c->callback.callback());
+ EXPECT_EQ(20, c->callback.GetResult(rv));
+ rv = c->trans->Read(buf.get(), 10, c->callback.callback());
+ EXPECT_EQ(10, c->callback.GetResult(rv));
+
+ // At this point, we are already reading so canceling the request should leave
+ // a truncated one.
+ delete c;
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Verify that the disk entry was updated: now we have 30 bytes.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ EXPECT_EQ(30, entry->GetDataSize(1));
+ bool truncated = false;
+ net::HttpResponseInfo response;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(truncated);
+ entry->Close();
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we can handle range requests when we have a truncated entry.
+TEST(HttpCache, RangeGET_IncompleteResource) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Content-length will be intentionally bogus.
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: something\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a range request.
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
+ &headers);
+
+ Verify206Response(headers, 40, 49);
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+TEST(HttpCache, SyncRead) {
+ MockHttpCache cache;
+
+ // This test ensures that a read that completes synchronously does not cause
+ // any problems.
+
+ ScopedMockTransaction transaction(kSimpleGET_Transaction);
+ transaction.test_mode |= (TEST_MODE_SYNC_CACHE_START |
+ TEST_MODE_SYNC_CACHE_READ |
+ TEST_MODE_SYNC_CACHE_WRITE);
+
+ MockHttpRequest r1(transaction),
+ r2(transaction),
+ r3(transaction);
+
+ TestTransactionConsumer c1(net::DEFAULT_PRIORITY, cache.http_cache()),
+ c2(net::DEFAULT_PRIORITY, cache.http_cache()),
+ c3(net::DEFAULT_PRIORITY, cache.http_cache());
+
+ c1.Start(&r1, net::BoundNetLog());
+
+ r2.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c2.Start(&r2, net::BoundNetLog());
+
+ r3.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ c3.Start(&r3, net::BoundNetLog());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(c1.is_done());
+ EXPECT_TRUE(c2.is_done());
+ EXPECT_TRUE(c3.is_done());
+
+ EXPECT_EQ(net::OK, c1.error());
+ EXPECT_EQ(net::OK, c2.error());
+ EXPECT_EQ(net::OK, c3.error());
+}
+
+TEST(HttpCache, ValidationResultsIn200) {
+ MockHttpCache cache;
+
+ // This test ensures that a conditional request, which results in a 200
+ // instead of a 304, properly truncates the existing response data.
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
+
+ // force this transaction to validate the cache
+ MockTransaction transaction(kETagGET_Transaction);
+ transaction.load_flags |= net::LOAD_VALIDATE_CACHE;
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // read from the cache
+ RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
+}
+
+TEST(HttpCache, CachedRedirect) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction kTestTransaction(kSimpleGET_Transaction);
+ kTestTransaction.status = "HTTP/1.1 301 Moved Permanently";
+ kTestTransaction.response_headers = "Location: http://www.bar.com/\n";
+
+ MockHttpRequest request(kTestTransaction);
+ net::TestCompletionCallback callback;
+
+ // write to the cache
+ {
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* info = trans->GetResponseInfo();
+ ASSERT_TRUE(info);
+
+ EXPECT_EQ(info->headers->response_code(), 301);
+
+ std::string location;
+ info->headers->EnumerateHeader(NULL, "Location", &location);
+ EXPECT_EQ(location, "http://www.bar.com/");
+
+ // Destroy transaction when going out of scope. We have not actually
+ // read the response body -- want to test that it is still getting cached.
+ }
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // read from the cache
+ {
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* info = trans->GetResponseInfo();
+ ASSERT_TRUE(info);
+
+ EXPECT_EQ(info->headers->response_code(), 301);
+
+ std::string location;
+ info->headers->EnumerateHeader(NULL, "Location", &location);
+ EXPECT_EQ(location, "http://www.bar.com/");
+
+ // Destroy transaction when going out of scope. We have not actually
+ // read the response body -- want to test that it is still getting cached.
+ }
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+TEST(HttpCache, CacheControlNoStore) {
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kSimpleGET_Transaction);
+ transaction.response_headers = "cache-control: no-store\n";
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
+}
+
+TEST(HttpCache, CacheControlNoStore2) {
+ // this test is similar to the above test, except that the initial response
+ // is cachable, but when it is validated, no-store is received causing the
+ // cached document to be deleted.
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.response_headers = "cache-control: no-store\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
+}
+
+TEST(HttpCache, CacheControlNoStore3) {
+ // this test is similar to the above test, except that the response is a 304
+ // instead of a 200. this should never happen in practice, but it seems like
+ // a good thing to verify that we still destroy the cache entry.
+ MockHttpCache cache;
+
+ ScopedMockTransaction transaction(kETagGET_Transaction);
+
+ // initial load
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // try loading again; it should result in a network fetch
+ transaction.load_flags = net::LOAD_VALIDATE_CACHE;
+ transaction.response_headers = "cache-control: no-store\n";
+ transaction.status = "HTTP/1.1 304 Not Modified";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ disk_cache::Entry* entry;
+ EXPECT_FALSE(cache.OpenBackendEntry(transaction.url, &entry));
+}
+
+// Ensure that we don't cache requests served over bad HTTPS.
+TEST(HttpCache, SimpleGET_SSLError) {
+ MockHttpCache cache;
+
+ MockTransaction transaction = kSimpleGET_Transaction;
+ transaction.cert_status = net::CERT_STATUS_REVOKED;
+ ScopedMockTransaction scoped_transaction(transaction);
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ // Test that it was not cached.
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_CACHE_MISS, rv);
+}
+
+// Ensure that we don't crash by if left-behind transactions.
+TEST(HttpCache, OutlivedTransactions) {
+ MockHttpCache* cache = new MockHttpCache;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache->http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ delete cache;
+ trans.reset();
+}
+
+// Test that the disabled mode works.
+TEST(HttpCache, CacheDisabledMode) {
+ MockHttpCache cache;
+
+ // write to the cache
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ // go into disabled mode
+ cache.http_cache()->set_mode(net::HttpCache::DISABLE);
+
+ // force this transaction to write to the cache again
+ MockTransaction transaction(kSimpleGET_Transaction);
+
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Other tests check that the response headers of the cached response
+// get updated on 304. Here we specifically check that the
+// HttpResponseHeaders::request_time and HttpResponseHeaders::response_time
+// fields also gets updated.
+// http://crbug.com/20594.
+TEST(HttpCache, UpdatesRequestResponseTimeOn304) {
+ MockHttpCache cache;
+
+ const char* kUrl = "http://foobar";
+ const char* kData = "body";
+
+ MockTransaction mock_network_response = { 0 };
+ mock_network_response.url = kUrl;
+
+ AddMockTransaction(&mock_network_response);
+
+ // Request |kUrl|, causing |kNetResponse1| to be written to the cache.
+
+ MockTransaction request = { 0 };
+ request.url = kUrl;
+ request.method = "GET";
+ request.request_headers = "\r\n";
+ request.data = kData;
+
+ static const Response kNetResponse1 = {
+ "HTTP/1.1 200 OK",
+ "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ kData
+ };
+
+ kNetResponse1.AssignTo(&mock_network_response);
+
+ RunTransactionTest(cache.http_cache(), request);
+
+ // Request |kUrl| again, this time validating the cache and getting
+ // a 304 back.
+
+ request.load_flags = net::LOAD_VALIDATE_CACHE;
+
+ static const Response kNetResponse2 = {
+ "HTTP/1.1 304 Not Modified",
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n",
+ ""
+ };
+
+ kNetResponse2.AssignTo(&mock_network_response);
+
+ base::Time request_time = base::Time() + base::TimeDelta::FromHours(1234);
+ base::Time response_time = base::Time() + base::TimeDelta::FromHours(1235);
+
+ mock_network_response.request_time = request_time;
+ mock_network_response.response_time = response_time;
+
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), request, &response);
+
+ // The request and response times should have been updated.
+ EXPECT_EQ(request_time.ToInternalValue(),
+ response.request_time.ToInternalValue());
+ EXPECT_EQ(response_time.ToInternalValue(),
+ response.response_time.ToInternalValue());
+
+ std::string headers;
+ response.headers->GetNormalizedHeaders(&headers);
+
+ EXPECT_EQ("HTTP/1.1 200 OK\n"
+ "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+ "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+ headers);
+
+ RemoveMockTransaction(&mock_network_response);
+}
+
+// Tests that we can write metadata to an entry.
+TEST(HttpCache, WriteMetadata_OK) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Trivial call.
+ cache.http_cache()->WriteMetadata(GURL("foo"), net::DEFAULT_PRIORITY,
+ Time::Now(), NULL, 0);
+
+ // Write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ cache.http_cache()->WriteMetadata(GURL(kSimpleGET_Transaction.url),
+ net::DEFAULT_PRIORITY,
+ response.response_time,
+ buf.get(),
+ buf->size());
+
+ // Release the buffer before the operation takes place.
+ buf = NULL;
+
+ // Makes sure we finish pending operations.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ ASSERT_TRUE(response.metadata.get() != NULL);
+ EXPECT_EQ(50, response.metadata->size());
+ EXPECT_EQ(0, strcmp(response.metadata->data(), "Hi there"));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we only write metadata to an entry if the time stamp matches.
+TEST(HttpCache, WriteMetadata_Fail) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Attempt to write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ base::Time expected_time = response.response_time -
+ base::TimeDelta::FromMilliseconds(20);
+ cache.http_cache()->WriteMetadata(GURL(kSimpleGET_Transaction.url),
+ net::DEFAULT_PRIORITY,
+ expected_time,
+ buf.get(),
+ buf->size());
+
+ // Makes sure we finish pending operations.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction,
+ &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we can read metadata after validating the entry and with READ mode
+// transactions.
+TEST(HttpCache, ReadMetadata) {
+ MockHttpCache cache;
+
+ // Write to the cache
+ net::HttpResponseInfo response;
+ RunTransactionTestWithResponseInfo(cache.http_cache(),
+ kTypicalGET_Transaction, &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ // Write meta data to the same entry.
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(50));
+ memset(buf->data(), 0, buf->size());
+ base::strlcpy(buf->data(), "Hi there", buf->size());
+ cache.http_cache()->WriteMetadata(GURL(kTypicalGET_Transaction.url),
+ net::DEFAULT_PRIORITY,
+ response.response_time,
+ buf.get(),
+ buf->size());
+
+ // Makes sure we finish pending operations.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Start with a READ mode transaction.
+ MockTransaction trans1(kTypicalGET_Transaction);
+ trans1.load_flags = net::LOAD_ONLY_FROM_CACHE;
+
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans1, &response);
+ ASSERT_TRUE(response.metadata.get() != NULL);
+ EXPECT_EQ(50, response.metadata->size());
+ EXPECT_EQ(0, strcmp(response.metadata->data(), "Hi there"));
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now make sure that the entry is re-validated with the server.
+ trans1.load_flags = net::LOAD_VALIDATE_CACHE;
+ trans1.status = "HTTP/1.1 304 Not Modified";
+ AddMockTransaction(&trans1);
+
+ response.metadata = NULL;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans1, &response);
+ EXPECT_TRUE(response.metadata.get() != NULL);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ base::MessageLoop::current()->RunUntilIdle();
+ RemoveMockTransaction(&trans1);
+
+ // Now return 200 when validating the entry so the metadata will be lost.
+ MockTransaction trans2(kTypicalGET_Transaction);
+ trans2.load_flags = net::LOAD_VALIDATE_CACHE;
+ RunTransactionTestWithResponseInfo(cache.http_cache(), trans2, &response);
+ EXPECT_TRUE(response.metadata.get() == NULL);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(4, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we don't mark entries as truncated when a filter detects the end
+// of the stream.
+TEST(HttpCache, FilterCompletion) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+
+ {
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, callback.GetResult(rv));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_GT(callback.GetResult(rv), 0);
+
+ // Now make sure that the entry is preserved.
+ trans->DoneReading();
+ }
+
+ // Make sure that the ActiveEntry is gone.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Read from the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+}
+
+// Tests that we stop cachining when told.
+TEST(HttpCache, StopCachingDeletesEntry) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ {
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, callback.GetResult(rv));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 10);
+
+ trans->StopCaching();
+
+ // We should be able to keep reading.
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_GT(callback.GetResult(rv), 0);
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 0);
+ }
+
+ // Make sure that the ActiveEntry is gone.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify that the entry is gone.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that when we are told to stop caching we don't throw away valid data.
+TEST(HttpCache, StopCachingSavesEntry) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+ MockHttpRequest request(kSimpleGET_Transaction);
+
+ {
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ // Force a response that can be resumed.
+ MockTransaction mock_transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&mock_transaction);
+ mock_transaction.response_headers = "Cache-Control: max-age=10000\n"
+ "Content-Length: 42\n"
+ "Etag: \"foo\"\n";
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, callback.GetResult(rv));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 10);
+
+ trans->StopCaching();
+
+ // We should be able to keep reading.
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_GT(callback.GetResult(rv), 0);
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 0);
+
+ RemoveMockTransaction(&mock_transaction);
+ }
+
+ // Verify that the entry is marked as incomplete.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &entry));
+ net::HttpResponseInfo response;
+ bool truncated = false;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(truncated);
+ entry->Close();
+}
+
+// Tests that we handle truncated enries when StopCaching is called.
+TEST(HttpCache, StopCachingTruncatedEntry) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+ MockHttpRequest request(kRangeGET_TransactionOK);
+ request.extra_headers.Clear();
+ request.extra_headers.AddHeaderFromString(EXTRA_HEADER_LINE);
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ {
+ // Now make a regular request.
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv = cache.http_cache()->CreateTransaction(
+ net::DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(net::OK, rv);
+
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ EXPECT_EQ(net::OK, callback.GetResult(rv));
+
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 10);
+
+ // This is actually going to do nothing.
+ trans->StopCaching();
+
+ // We should be able to keep reading.
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_GT(callback.GetResult(rv), 0);
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_GT(callback.GetResult(rv), 0);
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ EXPECT_EQ(callback.GetResult(rv), 0);
+ }
+
+ // Verify that the disk entry was updated.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kRangeGET_TransactionOK.url, &entry));
+ EXPECT_EQ(80, entry->GetDataSize(1));
+ bool truncated = true;
+ net::HttpResponseInfo response;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_FALSE(truncated);
+ entry->Close();
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
+// Tests that we detect truncated resources from the net when there is
+// a Content-Length header.
+TEST(HttpCache, TruncatedByContentLength) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ transaction.response_headers = "Cache-Control: max-age=10000\n"
+ "Content-Length: 100\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+ RemoveMockTransaction(&transaction);
+
+ // Read from the cache.
+ RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+}
+
+// Tests that we actually flag entries as truncated when we detect an error
+// from the net.
+TEST(HttpCache, TruncatedByContentLength2) {
+ MockHttpCache cache;
+ net::TestCompletionCallback callback;
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ AddMockTransaction(&transaction);
+ transaction.response_headers = "Cache-Control: max-age=10000\n"
+ "Content-Length: 100\n"
+ "Etag: \"foo\"\n";
+ RunTransactionTest(cache.http_cache(), transaction);
+ RemoveMockTransaction(&transaction);
+
+ // Verify that the entry is marked as incomplete.
+ disk_cache::Entry* entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &entry));
+ net::HttpResponseInfo response;
+ bool truncated = false;
+ EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated));
+ EXPECT_TRUE(truncated);
+ entry->Close();
+}
+
+TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit_TransactionDelegate) {
+ MockHttpCache cache;
+
+ // Write to the cache.
+ RunTransactionTestWithDelegate(cache.http_cache(),
+ kSimpleGET_Transaction,
+ 8,
+ 3);
+
+ // Force this transaction to read from the cache.
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+
+ RunTransactionTestWithDelegate(cache.http_cache(),
+ kSimpleGET_Transaction,
+ 5,
+ 0);
+}
+
+// Make sure that calling SetPriority on a cache transaction passes on
+// its priority updates to its underlying network transaction.
+TEST(HttpCache, SetPriority) {
+ MockHttpCache cache;
+
+ scoped_ptr<net::HttpTransaction> trans;
+ EXPECT_EQ(net::OK, cache.http_cache()->CreateTransaction(
+ net::IDLE, &trans, NULL));
+ ASSERT_TRUE(trans.get());
+
+ // Shouldn't crash, but doesn't do anything either.
+ trans->SetPriority(net::LOW);
+
+ EXPECT_FALSE(cache.network_layer()->last_transaction());
+ EXPECT_EQ(net::DEFAULT_PRIORITY,
+ cache.network_layer()->last_create_transaction_priority());
+
+ net::HttpRequestInfo info;
+ info.url = GURL(kSimpleGET_Transaction.url);
+ net::TestCompletionCallback callback;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ trans->Start(&info, callback.callback(), net::BoundNetLog()));
+
+ EXPECT_TRUE(cache.network_layer()->last_transaction());
+ if (cache.network_layer()->last_transaction()) {
+ EXPECT_EQ(net::LOW,
+ cache.network_layer()->last_create_transaction_priority());
+ EXPECT_EQ(net::LOW,
+ cache.network_layer()->last_transaction()->priority());
+ }
+
+ trans->SetPriority(net::HIGHEST);
+
+ if (cache.network_layer()->last_transaction()) {
+ EXPECT_EQ(net::LOW,
+ cache.network_layer()->last_create_transaction_priority());
+ EXPECT_EQ(net::HIGHEST,
+ cache.network_layer()->last_transaction()->priority());
+ }
+
+ EXPECT_EQ(net::OK, callback.WaitForResult());
+}
+
+// Make sure that a cache transaction passes on its priority to
+// newly-created network transactions.
+TEST(HttpCache, SetPriorityNewTransaction) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ std::string raw_headers("HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+
+ // Now make a regular request.
+ std::string headers;
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = EXTRA_HEADER;
+ transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 rg: 40-49 "
+ "rg: 50-59 rg: 60-69 rg: 70-79 ";
+
+ scoped_ptr<net::HttpTransaction> trans;
+ EXPECT_EQ(net::OK, cache.http_cache()->CreateTransaction(
+ net::MEDIUM, &trans, NULL));
+ ASSERT_TRUE(trans.get());
+ EXPECT_EQ(net::DEFAULT_PRIORITY,
+ cache.network_layer()->last_create_transaction_priority());
+
+ MockHttpRequest info(transaction);
+ net::TestCompletionCallback callback;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ trans->Start(&info, callback.callback(), net::BoundNetLog()));
+ EXPECT_EQ(net::OK, callback.WaitForResult());
+
+ EXPECT_EQ(net::MEDIUM,
+ cache.network_layer()->last_create_transaction_priority());
+
+ trans->SetPriority(net::HIGHEST);
+ // Should trigger a new network transaction and pick up the new
+ // priority.
+ ReadAndVerifyTransaction(trans.get(), transaction);
+
+ EXPECT_EQ(net::HIGHEST,
+ cache.network_layer()->last_create_transaction_priority());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
diff --git a/chromium/net/http/http_chunked_decoder.cc b/chromium/net/http/http_chunked_decoder.cc
new file mode 100644
index 00000000000..ca07f24de06
--- /dev/null
+++ b/chromium/net/http/http_chunked_decoder.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 2011 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.
+
+// Derived from:
+// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp
+// The license block is:
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@netscape.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/http/http_chunked_decoder.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// Absurdly long size to avoid imposing a constraint on chunked encoding
+// extensions.
+const size_t HttpChunkedDecoder::kMaxLineBufLen = 16384;
+
+HttpChunkedDecoder::HttpChunkedDecoder()
+ : chunk_remaining_(0),
+ chunk_terminator_remaining_(false),
+ reached_last_chunk_(false),
+ reached_eof_(false),
+ bytes_after_eof_(0) {
+}
+
+int HttpChunkedDecoder::FilterBuf(char* buf, int buf_len) {
+ int result = 0;
+
+ while (buf_len) {
+ if (chunk_remaining_) {
+ int num = std::min(chunk_remaining_, buf_len);
+
+ buf_len -= num;
+ chunk_remaining_ -= num;
+
+ result += num;
+ buf += num;
+
+ // After each chunk's data there should be a CRLF
+ if (!chunk_remaining_)
+ chunk_terminator_remaining_ = true;
+ continue;
+ } else if (reached_eof_) {
+ bytes_after_eof_ += buf_len;
+ break; // Done!
+ }
+
+ int bytes_consumed = ScanForChunkRemaining(buf, buf_len);
+ if (bytes_consumed < 0)
+ return bytes_consumed; // Error
+
+ buf_len -= bytes_consumed;
+ if (buf_len)
+ memmove(buf, buf + bytes_consumed, buf_len);
+ }
+
+ return result;
+}
+
+int HttpChunkedDecoder::ScanForChunkRemaining(const char* buf, int buf_len) {
+ DCHECK_EQ(0, chunk_remaining_);
+ DCHECK_GT(buf_len, 0);
+
+ int bytes_consumed = 0;
+
+ size_t index_of_lf = base::StringPiece(buf, buf_len).find('\n');
+ if (index_of_lf != base::StringPiece::npos) {
+ buf_len = static_cast<int>(index_of_lf);
+ if (buf_len && buf[buf_len - 1] == '\r') // Eliminate a preceding CR.
+ buf_len--;
+ bytes_consumed = static_cast<int>(index_of_lf) + 1;
+
+ // Make buf point to the full line buffer to parse.
+ if (!line_buf_.empty()) {
+ line_buf_.append(buf, buf_len);
+ buf = line_buf_.data();
+ buf_len = static_cast<int>(line_buf_.size());
+ }
+
+ if (reached_last_chunk_) {
+ if (buf_len)
+ DVLOG(1) << "ignoring http trailer";
+ else
+ reached_eof_ = true;
+ } else if (chunk_terminator_remaining_) {
+ if (buf_len) {
+ DLOG(ERROR) << "chunk data not terminated properly";
+ return ERR_INVALID_CHUNKED_ENCODING;
+ }
+ chunk_terminator_remaining_ = false;
+ } else if (buf_len) {
+ // Ignore any chunk-extensions.
+ size_t index_of_semicolon = base::StringPiece(buf, buf_len).find(';');
+ if (index_of_semicolon != base::StringPiece::npos)
+ buf_len = static_cast<int>(index_of_semicolon);
+
+ if (!ParseChunkSize(buf, buf_len, &chunk_remaining_)) {
+ DLOG(ERROR) << "Failed parsing HEX from: " <<
+ std::string(buf, buf_len);
+ return ERR_INVALID_CHUNKED_ENCODING;
+ }
+
+ if (chunk_remaining_ == 0)
+ reached_last_chunk_ = true;
+ } else {
+ DLOG(ERROR) << "missing chunk-size";
+ return ERR_INVALID_CHUNKED_ENCODING;
+ }
+ line_buf_.clear();
+ } else {
+ // Save the partial line; wait for more data.
+ bytes_consumed = buf_len;
+
+ // Ignore a trailing CR
+ if (buf[buf_len - 1] == '\r')
+ buf_len--;
+
+ if (line_buf_.length() + buf_len > kMaxLineBufLen) {
+ DLOG(ERROR) << "Chunked line length too long";
+ return ERR_INVALID_CHUNKED_ENCODING;
+ }
+
+ line_buf_.append(buf, buf_len);
+ }
+ return bytes_consumed;
+}
+
+
+// While the HTTP 1.1 specification defines chunk-size as 1*HEX
+// some sites rely on more lenient parsing.
+// http://www.yahoo.com/, for example, pads chunk-size with trailing spaces
+// (0x20) to be 7 characters long, such as "819b ".
+//
+// A comparison of browsers running on WindowsXP shows that
+// they will parse the following inputs (egrep syntax):
+//
+// Let \X be the character class for a hex digit: [0-9a-fA-F]
+//
+// RFC 2616: ^\X+$
+// IE7: ^\X+[^\X]*$
+// Safari 3.1: ^[\t\r ]*\X+[\t ]*$
+// Firefox 3: ^[\t\f\v\r ]*[+]?(0x)?\X+[^\X]*$
+// Opera 9.51: ^[\t\f\v ]*[+]?(0x)?\X+[^\X]*$
+//
+// Our strategy is to be as strict as possible, while not breaking
+// known sites.
+//
+// Us: ^\X+[ ]*$
+bool HttpChunkedDecoder::ParseChunkSize(const char* start, int len, int* out) {
+ DCHECK_GE(len, 0);
+
+ // Strip trailing spaces
+ while (len && start[len - 1] == ' ')
+ len--;
+
+ // Be more restrictive than HexStringToInt;
+ // don't allow inputs with leading "-", "+", "0x", "0X"
+ base::StringPiece chunk_size(start, len);
+ if (chunk_size.find_first_not_of("0123456789abcdefABCDEF")
+ != base::StringPiece::npos) {
+ return false;
+ }
+
+ int parsed_number;
+ bool ok = base::HexStringToInt(chunk_size, &parsed_number);
+ if (ok && parsed_number >= 0) {
+ *out = parsed_number;
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_chunked_decoder.h b/chromium/net/http/http_chunked_decoder.h
new file mode 100644
index 00000000000..9ee7400ad7d
--- /dev/null
+++ b/chromium/net/http/http_chunked_decoder.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2011 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.
+
+// Derived from:
+// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.h
+// The license block is:
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@netscape.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_HTTP_HTTP_CHUNKED_DECODER_H_
+#define NET_HTTP_HTTP_CHUNKED_DECODER_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// From RFC2617 section 3.6.1, the chunked transfer coding is defined as:
+//
+// Chunked-Body = *chunk
+// last-chunk
+// trailer
+// CRLF
+// chunk = chunk-size [ chunk-extension ] CRLF
+// chunk-data CRLF
+// chunk-size = 1*HEX
+// last-chunk = 1*("0") [ chunk-extension ] CRLF
+//
+// chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+// chunk-ext-name = token
+// chunk-ext-val = token | quoted-string
+// chunk-data = chunk-size(OCTET)
+// trailer = *(entity-header CRLF)
+//
+// The chunk-size field is a string of hex digits indicating the size of the
+// chunk. The chunked encoding is ended by any chunk whose size is zero,
+// followed by the trailer, which is terminated by an empty line.
+//
+// NOTE: This implementation does not bother to parse trailers since they are
+// not used on the web.
+//
+class NET_EXPORT_PRIVATE HttpChunkedDecoder {
+ public:
+ // The maximum length of |line_buf_| between calls to FilterBuff().
+ // Exposed for tests.
+ static const size_t kMaxLineBufLen;
+
+ HttpChunkedDecoder();
+
+ // Indicates that a previous call to FilterBuf encountered the final CRLF.
+ bool reached_eof() const { return reached_eof_; }
+
+ // Returns the number of bytes after the final CRLF.
+ int bytes_after_eof() const { return bytes_after_eof_; }
+
+ // Called to filter out the chunk markers from buf and to check for end-of-
+ // file. This method modifies |buf| inline if necessary to remove chunk
+ // markers. The return value indicates the final size of decoded data stored
+ // in |buf|. Call reached_eof() after this method to check if end-of-file
+ // was encountered.
+ int FilterBuf(char* buf, int buf_len);
+
+ private:
+ // Scans |buf| for the next chunk delimiter. This method returns the number
+ // of bytes consumed from |buf|. If found, |chunk_remaining_| holds the
+ // value for the next chunk size.
+ int ScanForChunkRemaining(const char* buf, int buf_len);
+
+ // Converts string |start| of length |len| to a numeric value.
+ // |start| is a string of type "chunk-size" (hex string).
+ // If the conversion succeeds, returns true and places the result in |out|.
+ static bool ParseChunkSize(const char* start, int len, int* out);
+
+ // Indicates the number of bytes remaining for the current chunk.
+ int chunk_remaining_;
+
+ // A small buffer used to store a partial chunk marker.
+ std::string line_buf_;
+
+ // True if waiting for the terminal CRLF of a chunk's data.
+ bool chunk_terminator_remaining_;
+
+ // Set to true when FilterBuf encounters the last-chunk.
+ bool reached_last_chunk_;
+
+ // Set to true when FilterBuf encounters the final CRLF.
+ bool reached_eof_;
+
+ // The number of unfiltered bytes after the final CRLF, either extraneous
+ // data or the first part of the next response in a pipelined stream.
+ int bytes_after_eof_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CHUNKED_DECODER_H_
diff --git a/chromium/net/http/http_chunked_decoder_unittest.cc b/chromium/net/http/http_chunked_decoder_unittest.cc
new file mode 100644
index 00000000000..f3e83000180
--- /dev/null
+++ b/chromium/net/http/http_chunked_decoder_unittest.cc
@@ -0,0 +1,356 @@
+// Copyright (c) 2006-2008 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/http/http_chunked_decoder.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+typedef testing::Test HttpChunkedDecoderTest;
+
+void RunTest(const char* inputs[], size_t num_inputs,
+ const char* expected_output,
+ bool expected_eof,
+ int bytes_after_eof) {
+ HttpChunkedDecoder decoder;
+ EXPECT_FALSE(decoder.reached_eof());
+
+ std::string result;
+
+ for (size_t i = 0; i < num_inputs; ++i) {
+ std::string input = inputs[i];
+ int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
+ EXPECT_GE(n, 0);
+ if (n > 0)
+ result.append(input.data(), n);
+ }
+
+ EXPECT_EQ(expected_output, result);
+ EXPECT_EQ(expected_eof, decoder.reached_eof());
+ EXPECT_EQ(bytes_after_eof, decoder.bytes_after_eof());
+}
+
+// Feed the inputs to the decoder, until it returns an error.
+void RunTestUntilFailure(const char* inputs[],
+ size_t num_inputs,
+ size_t fail_index) {
+ HttpChunkedDecoder decoder;
+ EXPECT_FALSE(decoder.reached_eof());
+
+ for (size_t i = 0; i < num_inputs; ++i) {
+ std::string input = inputs[i];
+ int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size()));
+ if (n < 0) {
+ EXPECT_EQ(ERR_INVALID_CHUNKED_ENCODING, n);
+ EXPECT_EQ(fail_index, i);
+ return;
+ }
+ }
+ FAIL(); // We should have failed on the |fail_index| iteration of the loop.
+}
+
+TEST(HttpChunkedDecoderTest, Basic) {
+ const char* inputs[] = {
+ "B\r\nhello hello\r\n0\r\n\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello hello", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, OneChunk) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", false, 0);
+}
+
+TEST(HttpChunkedDecoderTest, Typical) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n",
+ "1\r\n \r\n",
+ "5\r\nworld\r\n",
+ "0\r\n\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello world", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, Incremental) {
+ const char* inputs[] = {
+ "5",
+ "\r",
+ "\n",
+ "hello",
+ "\r",
+ "\n",
+ "0",
+ "\r",
+ "\n",
+ "\r",
+ "\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 0);
+}
+
+// Same as above, but group carriage returns with previous input.
+TEST(HttpChunkedDecoderTest, Incremental2) {
+ const char* inputs[] = {
+ "5\r",
+ "\n",
+ "hello\r",
+ "\n",
+ "0\r",
+ "\n\r",
+ "\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, LF_InsteadOf_CRLF) {
+ // Compatibility: [RFC 2616 - Invalid]
+ // {Firefox3} - Valid
+ // {IE7, Safari3.1, Opera9.51} - Invalid
+ const char* inputs[] = {
+ "5\nhello\n",
+ "1\n \n",
+ "5\nworld\n",
+ "0\n\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello world", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, Extensions) {
+ const char* inputs[] = {
+ "5;x=0\r\nhello\r\n",
+ "0;y=\"2 \"\r\n\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, Trailers) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n",
+ "0\r\n",
+ "Foo: 1\r\n",
+ "Bar: 2\r\n",
+ "\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, TrailersUnfinished) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n",
+ "0\r\n",
+ "Foo: 1\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", false, 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_TooBig) {
+ const char* inputs[] = {
+ // This chunked body is not terminated.
+ // However we will fail decoding because the chunk-size
+ // number is larger than we can handle.
+ "48469410265455838241\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_0X) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {Safari3.1, IE7} - Invalid
+ // {Firefox3, Opera 9.51} - Valid
+ "0x5\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, ChunkSize_TrailingSpace) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
+ //
+ // At least yahoo.com depends on this being valid.
+ "5 \r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingTab) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {IE7, Safari3.1, Firefox3, Opera 9.51} - Valid
+ "5\t\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingFormFeed) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616- Invalid]:
+ // {Safari3.1} - Invalid
+ // {IE7, Firefox3, Opera 9.51} - Valid
+ "5\f\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingVerticalTab) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {Safari 3.1} - Invalid
+ // {IE7, Firefox3, Opera 9.51} - Valid
+ "5\v\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_TrailingNonHexDigit) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {Safari 3.1} - Invalid
+ // {IE7, Firefox3, Opera 9.51} - Valid
+ "5H\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_LeadingSpace) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {IE7} - Invalid
+ // {Safari 3.1, Firefox3, Opera 9.51} - Valid
+ " 5\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidLeadingSeparator) {
+ const char* inputs[] = {
+ "\r\n5\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_NoSeparator) {
+ const char* inputs[] = {
+ "5\r\nhello",
+ "1\r\n \r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 1);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_Negative) {
+ const char* inputs[] = {
+ "8\r\n12345678\r\n-5\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidChunkSize_Plus) {
+ const char* inputs[] = {
+ // Compatibility [RFC 2616 - Invalid]:
+ // {IE7, Safari 3.1} - Invalid
+ // {Firefox3, Opera 9.51} - Valid
+ "+5\r\nhello\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, InvalidConsecutiveCRLFs) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n",
+ "\r\n\r\n\r\n\r\n",
+ "0\r\n\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 1);
+}
+
+TEST(HttpChunkedDecoderTest, ExcessiveChunkLen) {
+ const char* inputs[] = {
+ "c0000000\r\nhello\r\n"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 0);
+}
+
+TEST(HttpChunkedDecoderTest, BasicExtraData) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n0\r\n\r\nextra bytes"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 11);
+}
+
+TEST(HttpChunkedDecoderTest, IncrementalExtraData) {
+ const char* inputs[] = {
+ "5",
+ "\r",
+ "\n",
+ "hello",
+ "\r",
+ "\n",
+ "0",
+ "\r",
+ "\n",
+ "\r",
+ "\nextra bytes"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 11);
+}
+
+TEST(HttpChunkedDecoderTest, MultipleExtraDataBlocks) {
+ const char* inputs[] = {
+ "5\r\nhello\r\n0\r\n\r\nextra",
+ " bytes"
+ };
+ RunTest(inputs, arraysize(inputs), "hello", true, 11);
+}
+
+// Test when the line with the chunk length is too long.
+TEST(HttpChunkedDecoderTest, LongChunkLengthLine) {
+ int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
+ scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
+ memset(big_chunk.get(), '0', big_chunk_length);
+ big_chunk[big_chunk_length] = 0;
+ const char* inputs[] = {
+ big_chunk.get(),
+ "5"
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 1);
+}
+
+// Test when the extension portion of the line with the chunk length is too
+// long.
+TEST(HttpChunkedDecoderTest, LongLengthLengthLine) {
+ int big_chunk_length = HttpChunkedDecoder::kMaxLineBufLen;
+ scoped_ptr<char[]> big_chunk(new char[big_chunk_length + 1]);
+ memset(big_chunk.get(), '0', big_chunk_length);
+ big_chunk[big_chunk_length] = 0;
+ const char* inputs[] = {
+ "5;",
+ big_chunk.get()
+ };
+ RunTestUntilFailure(inputs, arraysize(inputs), 1);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_content_disposition.cc b/chromium/net/http/http_content_disposition.cc
new file mode 100644
index 00000000000..3dbf234b943
--- /dev/null
+++ b/chromium/net/http/http_content_disposition.cc
@@ -0,0 +1,456 @@
+// Copyright (c) 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.
+
+#include "net/http/http_content_disposition.h"
+
+#include "base/base64.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_util.h"
+#include "net/http/http_util.h"
+#include "third_party/icu/source/common/unicode/ucnv.h"
+
+namespace {
+
+enum RFC2047EncodingType {
+ Q_ENCODING,
+ B_ENCODING
+};
+
+// Decodes a "Q" encoded string as described in RFC 2047 section 4.2. Similar to
+// decoding a quoted-printable string. Returns true if the input was valid.
+bool DecodeQEncoding(const std::string& input, std::string* output) {
+ std::string temp;
+ temp.reserve(input.size());
+ for (std::string::const_iterator it = input.begin(); it != input.end();
+ ++it) {
+ if (*it == '_') {
+ temp.push_back(' ');
+ } else if (*it == '=') {
+ if ((input.end() - it < 3) ||
+ !IsHexDigit(static_cast<unsigned char>(*(it + 1))) ||
+ !IsHexDigit(static_cast<unsigned char>(*(it + 2))))
+ return false;
+ unsigned char ch = HexDigitToInt(*(it + 1)) * 16 +
+ HexDigitToInt(*(it + 2));
+ temp.push_back(static_cast<char>(ch));
+ ++it;
+ ++it;
+ } else if (0x20 < *it && *it < 0x7F && *it != '?') {
+ // In a Q-encoded word, only printable ASCII characters
+ // represent themselves. Besides, space, '=', '_' and '?' are
+ // not allowed, but they're already filtered out.
+ DCHECK_NE('=', *it);
+ DCHECK_NE('?', *it);
+ DCHECK_NE('_', *it);
+ temp.push_back(*it);
+ } else {
+ return false;
+ }
+ }
+ output->swap(temp);
+ return true;
+}
+
+// Decodes a "Q" or "B" encoded string as per RFC 2047 section 4. The encoding
+// type is specified in |enc_type|.
+bool DecodeBQEncoding(const std::string& part,
+ RFC2047EncodingType enc_type,
+ const std::string& charset,
+ std::string* output) {
+ std::string decoded;
+ if (!((enc_type == B_ENCODING) ?
+ base::Base64Decode(part, &decoded) : DecodeQEncoding(part, &decoded)))
+ return false;
+
+ if (decoded.empty()) {
+ output->clear();
+ return true;
+ }
+
+ UErrorCode err = U_ZERO_ERROR;
+ UConverter* converter(ucnv_open(charset.c_str(), &err));
+ if (U_FAILURE(err))
+ return false;
+
+ // A single byte in a legacy encoding can be expanded to 3 bytes in UTF-8.
+ // A 'two-byte character' in a legacy encoding can be expanded to 4 bytes
+ // in UTF-8. Therefore, the expansion ratio is 3 at most. Add one for a
+ // trailing '\0'.
+ size_t output_length = decoded.length() * 3 + 1;
+ char* buf = WriteInto(output, output_length);
+ output_length = ucnv_toAlgorithmic(UCNV_UTF8, converter, buf, output_length,
+ decoded.data(), decoded.length(), &err);
+ ucnv_close(converter);
+ if (U_FAILURE(err))
+ return false;
+ output->resize(output_length);
+ return true;
+}
+
+bool DecodeWord(const std::string& encoded_word,
+ const std::string& referrer_charset,
+ bool* is_rfc2047,
+ std::string* output,
+ int* parse_result_flags) {
+ *is_rfc2047 = false;
+ output->clear();
+ if (encoded_word.empty())
+ return true;
+
+ if (!IsStringASCII(encoded_word)) {
+ // Try UTF-8, referrer_charset and the native OS default charset in turn.
+ if (IsStringUTF8(encoded_word)) {
+ *output = encoded_word;
+ } else {
+ base::string16 utf16_output;
+ if (!referrer_charset.empty() &&
+ base::CodepageToUTF16(encoded_word, referrer_charset.c_str(),
+ base::OnStringConversionError::FAIL,
+ &utf16_output)) {
+ *output = UTF16ToUTF8(utf16_output);
+ } else {
+ *output = WideToUTF8(base::SysNativeMBToWide(encoded_word));
+ }
+ }
+
+ *parse_result_flags |= net::HttpContentDisposition::HAS_NON_ASCII_STRINGS;
+ return true;
+ }
+
+ // RFC 2047 : one of encoding methods supported by Firefox and relatively
+ // widely used by web servers.
+ // =?charset?<E>?<encoded string>?= where '<E>' is either 'B' or 'Q'.
+ // We don't care about the length restriction (72 bytes) because
+ // many web servers generate encoded words longer than the limit.
+ std::string decoded_word;
+ *is_rfc2047 = true;
+ int part_index = 0;
+ std::string charset;
+ base::StringTokenizer t(encoded_word, "?");
+ RFC2047EncodingType enc_type = Q_ENCODING;
+ while (*is_rfc2047 && t.GetNext()) {
+ std::string part = t.token();
+ switch (part_index) {
+ case 0:
+ if (part != "=") {
+ *is_rfc2047 = false;
+ break;
+ }
+ ++part_index;
+ break;
+ case 1:
+ // Do we need charset validity check here?
+ charset = part;
+ ++part_index;
+ break;
+ case 2:
+ if (part.size() > 1 ||
+ part.find_first_of("bBqQ") == std::string::npos) {
+ *is_rfc2047 = false;
+ break;
+ }
+ if (part[0] == 'b' || part[0] == 'B') {
+ enc_type = B_ENCODING;
+ }
+ ++part_index;
+ break;
+ case 3:
+ *is_rfc2047 = DecodeBQEncoding(part, enc_type, charset, &decoded_word);
+ if (!*is_rfc2047) {
+ // Last minute failure. Invalid B/Q encoding. Rather than
+ // passing it through, return now.
+ return false;
+ }
+ ++part_index;
+ break;
+ case 4:
+ if (part != "=") {
+ // Another last minute failure !
+ // Likely to be a case of two encoded-words in a row or
+ // an encoded word followed by a non-encoded word. We can be
+ // generous, but it does not help much in terms of compatibility,
+ // I believe. Return immediately.
+ *is_rfc2047 = false;
+ return false;
+ }
+ ++part_index;
+ break;
+ default:
+ *is_rfc2047 = false;
+ return false;
+ }
+ }
+
+ if (*is_rfc2047) {
+ if (*(encoded_word.end() - 1) == '=') {
+ output->swap(decoded_word);
+ *parse_result_flags |=
+ net::HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS;
+ return true;
+ }
+ // encoded_word ending prematurelly with '?' or extra '?'
+ *is_rfc2047 = false;
+ return false;
+ }
+
+ // We're not handling 'especial' characters quoted with '\', but
+ // it should be Ok because we're not an email client but a
+ // web browser.
+
+ // What IE6/7 does: %-escaped UTF-8.
+ decoded_word = net::UnescapeURLComponent(encoded_word,
+ net::UnescapeRule::SPACES);
+ if (decoded_word != encoded_word)
+ *parse_result_flags |=
+ net::HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS;
+ if (IsStringUTF8(decoded_word)) {
+ output->swap(decoded_word);
+ return true;
+ // We can try either the OS default charset or 'origin charset' here,
+ // As far as I can tell, IE does not support it. However, I've seen
+ // web servers emit %-escaped string in a legacy encoding (usually
+ // origin charset).
+ // TODO(jungshik) : Test IE further and consider adding a fallback here.
+ }
+ return false;
+}
+
+// Decodes the value of a 'filename' or 'name' parameter given as |input|. The
+// value is supposed to be of the form:
+//
+// value = token | quoted-string
+//
+// However we currently also allow RFC 2047 encoding and non-ASCII
+// strings. Non-ASCII strings are interpreted based on |referrer_charset|.
+bool DecodeFilenameValue(const std::string& input,
+ const std::string& referrer_charset,
+ std::string* output,
+ int* parse_result_flags) {
+ int current_parse_result_flags = 0;
+ std::string decoded_value;
+ bool is_previous_token_rfc2047 = true;
+
+ // Tokenize with whitespace characters.
+ base::StringTokenizer t(input, " \t\n\r");
+ t.set_options(base::StringTokenizer::RETURN_DELIMS);
+ while (t.GetNext()) {
+ if (t.token_is_delim()) {
+ // If the previous non-delimeter token is not RFC2047-encoded,
+ // put in a space in its place. Otheriwse, skip over it.
+ if (!is_previous_token_rfc2047)
+ decoded_value.push_back(' ');
+ continue;
+ }
+ // We don't support a single multibyte character split into
+ // adjacent encoded words. Some broken mail clients emit headers
+ // with that problem, but most web servers usually encode a filename
+ // in a single encoded-word. Firefox/Thunderbird do not support
+ // it, either.
+ std::string decoded;
+ if (!DecodeWord(t.token(), referrer_charset, &is_previous_token_rfc2047,
+ &decoded, &current_parse_result_flags))
+ return false;
+ decoded_value.append(decoded);
+ }
+ output->swap(decoded_value);
+ if (parse_result_flags && !output->empty())
+ *parse_result_flags |= current_parse_result_flags;
+ return true;
+}
+
+// Parses the charset and value-chars out of an ext-value string.
+//
+// ext-value = charset "'" [ language ] "'" value-chars
+bool ParseExtValueComponents(const std::string& input,
+ std::string* charset,
+ std::string* value_chars) {
+ base::StringTokenizer t(input, "'");
+ t.set_options(base::StringTokenizer::RETURN_DELIMS);
+ std::string temp_charset;
+ std::string temp_value;
+ int numDelimsSeen = 0;
+ while (t.GetNext()) {
+ if (t.token_is_delim()) {
+ ++numDelimsSeen;
+ continue;
+ } else {
+ switch (numDelimsSeen) {
+ case 0:
+ temp_charset = t.token();
+ break;
+ case 1:
+ // Language is ignored.
+ break;
+ case 2:
+ temp_value = t.token();
+ break;
+ default:
+ return false;
+ }
+ }
+ }
+ if (numDelimsSeen != 2)
+ return false;
+ if (temp_charset.empty() || temp_value.empty())
+ return false;
+ charset->swap(temp_charset);
+ value_chars->swap(temp_value);
+ return true;
+}
+
+// http://tools.ietf.org/html/rfc5987#section-3.2
+//
+// ext-value = charset "'" [ language ] "'" value-chars
+//
+// charset = "UTF-8" / "ISO-8859-1" / mime-charset
+//
+// mime-charset = 1*mime-charsetc
+// mime-charsetc = ALPHA / DIGIT
+// / "!" / "#" / "$" / "%" / "&"
+// / "+" / "-" / "^" / "_" / "`"
+// / "{" / "}" / "~"
+//
+// language = <Language-Tag, defined in [RFC5646], Section 2.1>
+//
+// value-chars = *( pct-encoded / attr-char )
+//
+// pct-encoded = "%" HEXDIG HEXDIG
+//
+// attr-char = ALPHA / DIGIT
+// / "!" / "#" / "$" / "&" / "+" / "-" / "."
+// / "^" / "_" / "`" / "|" / "~"
+bool DecodeExtValue(const std::string& param_value, std::string* decoded) {
+ if (param_value.find('"') != std::string::npos)
+ return false;
+
+ std::string charset;
+ std::string value;
+ if (!ParseExtValueComponents(param_value, &charset, &value))
+ return false;
+
+ // RFC 5987 value should be ASCII-only.
+ if (!IsStringASCII(value)) {
+ decoded->clear();
+ return true;
+ }
+
+ std::string unescaped = net::UnescapeURLComponent(
+ value, net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS);
+
+ return base::ConvertToUtf8AndNormalize(unescaped, charset, decoded);
+}
+
+} // namespace
+
+namespace net {
+
+HttpContentDisposition::HttpContentDisposition(
+ const std::string& header, const std::string& referrer_charset)
+ : type_(INLINE),
+ parse_result_flags_(INVALID) {
+ Parse(header, referrer_charset);
+}
+
+HttpContentDisposition::~HttpContentDisposition() {
+}
+
+std::string::const_iterator HttpContentDisposition::ConsumeDispositionType(
+ std::string::const_iterator begin, std::string::const_iterator end) {
+ DCHECK(type_ == INLINE);
+ std::string::const_iterator delimiter = std::find(begin, end, ';');
+
+ std::string::const_iterator type_begin = begin;
+ std::string::const_iterator type_end = delimiter;
+ HttpUtil::TrimLWS(&type_begin, &type_end);
+
+ // If the disposition-type isn't a valid token the then the
+ // Content-Disposition header is malformed, and we treat the first bytes as
+ // a parameter rather than a disposition-type.
+ if (!HttpUtil::IsToken(type_begin, type_end))
+ return begin;
+
+ parse_result_flags_ |= HAS_DISPOSITION_TYPE;
+
+ DCHECK(std::find(type_begin, type_end, '=') == type_end);
+
+ if (LowerCaseEqualsASCII(type_begin, type_end, "inline")) {
+ type_ = INLINE;
+ } else if (LowerCaseEqualsASCII(type_begin, type_end, "attachment")) {
+ type_ = ATTACHMENT;
+ } else {
+ parse_result_flags_ |= HAS_UNKNOWN_DISPOSITION_TYPE;
+ type_ = ATTACHMENT;
+ }
+ return delimiter;
+}
+
+// http://tools.ietf.org/html/rfc6266
+//
+// content-disposition = "Content-Disposition" ":"
+// disposition-type *( ";" disposition-parm )
+//
+// disposition-type = "inline" | "attachment" | disp-ext-type
+// ; case-insensitive
+// disp-ext-type = token
+//
+// disposition-parm = filename-parm | disp-ext-parm
+//
+// filename-parm = "filename" "=" value
+// | "filename*" "=" ext-value
+//
+// disp-ext-parm = token "=" value
+// | ext-token "=" ext-value
+// ext-token = <the characters in token, followed by "*">
+//
+void HttpContentDisposition::Parse(const std::string& header,
+ const std::string& referrer_charset) {
+ DCHECK(type_ == INLINE);
+ DCHECK(filename_.empty());
+
+ std::string::const_iterator pos = header.begin();
+ std::string::const_iterator end = header.end();
+ pos = ConsumeDispositionType(pos, end);
+
+ std::string name;
+ std::string filename;
+ std::string ext_filename;
+
+ HttpUtil::NameValuePairsIterator iter(pos, end, ';');
+ while (iter.GetNext()) {
+ if (filename.empty() && LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "filename")) {
+ DecodeFilenameValue(iter.value(), referrer_charset, &filename,
+ &parse_result_flags_);
+ if (!filename.empty())
+ parse_result_flags_ |= HAS_FILENAME;
+ } else if (name.empty() && LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "name")) {
+ DecodeFilenameValue(iter.value(), referrer_charset, &name, NULL);
+ if (!name.empty())
+ parse_result_flags_ |= HAS_NAME;
+ } else if (ext_filename.empty() && LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "filename*")) {
+ DecodeExtValue(iter.raw_value(), &ext_filename);
+ if (!ext_filename.empty())
+ parse_result_flags_ |= HAS_EXT_FILENAME;
+ }
+ }
+
+ if (!ext_filename.empty())
+ filename_ = ext_filename;
+ else if (!filename.empty())
+ filename_ = filename;
+ else
+ filename_ = name;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_content_disposition.h b/chromium/net/http/http_content_disposition.h
new file mode 100644
index 00000000000..2b4ca709eb3
--- /dev/null
+++ b/chromium/net/http/http_content_disposition.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_CONTENT_DISPOSITION_H_
+#define NET_HTTP_HTTP_CONTENT_DISPOSITION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT HttpContentDisposition {
+ public:
+ enum Type {
+ INLINE,
+ ATTACHMENT,
+ };
+
+ // Properties of the Content-Disposition header. Used for UMA.
+ enum ParseResultFlags {
+ INVALID = 0,
+
+ // A valid disposition-type is present.
+ HAS_DISPOSITION_TYPE = 1 << 0,
+
+ // The disposition-type is not 'inline' or 'attachment'.
+ HAS_UNKNOWN_DISPOSITION_TYPE = 1 << 1,
+
+ // Has a valid non-empty 'name' attribute.
+ HAS_NAME = 1 << 2,
+
+ // Has a valid non-empty 'filename' attribute.
+ HAS_FILENAME = 1 << 3,
+
+ // Has a valid non-empty 'filename*' attribute.
+ HAS_EXT_FILENAME = 1 << 4,
+
+ // The following fields are properties of the 'filename' attribute:
+
+ // Quoted-string contains non-ASCII characters.
+ HAS_NON_ASCII_STRINGS = 1 << 5,
+
+ // Quoted-string contains percent-encoding.
+ HAS_PERCENT_ENCODED_STRINGS = 1 << 6,
+
+ // Quoted-string contains RFC 2047 encoded words.
+ HAS_RFC2047_ENCODED_STRINGS = 1 << 7
+ };
+
+ HttpContentDisposition(const std::string& header,
+ const std::string& referrer_charset);
+ ~HttpContentDisposition();
+
+ bool is_attachment() const { return type() == ATTACHMENT; }
+
+ Type type() const { return type_; }
+ const std::string& filename() const { return filename_; }
+
+ // A combination of ParseResultFlags values.
+ int parse_result_flags() const { return parse_result_flags_; }
+
+ private:
+ void Parse(const std::string& header, const std::string& referrer_charset);
+ std::string::const_iterator ConsumeDispositionType(
+ std::string::const_iterator begin, std::string::const_iterator end);
+
+ Type type_;
+ std::string filename_;
+ int parse_result_flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpContentDisposition);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_CONTENT_DISPOSITION_H_
diff --git a/chromium/net/http/http_content_disposition_unittest.cc b/chromium/net/http/http_content_disposition_unittest.cc
new file mode 100644
index 00000000000..62d95778c0b
--- /dev/null
+++ b/chromium/net/http/http_content_disposition_unittest.cc
@@ -0,0 +1,590 @@
+// Copyright (c) 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.
+
+#include "net/http/http_content_disposition.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+struct FileNameCDCase {
+ const char* header;
+ const char* referrer_charset;
+ const wchar_t* expected;
+};
+
+} // anonymous namespace
+
+TEST(HttpContentDispositionTest, Filename) {
+ const FileNameCDCase tests[] = {
+ // Test various forms of C-D header fields emitted by web servers.
+ {"inline; filename=\"abcde.pdf\"", "", L"abcde.pdf"},
+ {"inline; name=\"abcde.pdf\"", "", L"abcde.pdf"},
+ {"attachment; filename=abcde.pdf", "", L"abcde.pdf"},
+ {"attachment; name=abcde.pdf", "", L"abcde.pdf"},
+ {"attachment; filename=abc,de.pdf", "", L"abc,de.pdf"},
+ {"filename=abcde.pdf", "", L"abcde.pdf"},
+ {"filename= abcde.pdf", "", L"abcde.pdf"},
+ {"filename =abcde.pdf", "", L"abcde.pdf"},
+ {"filename = abcde.pdf", "", L"abcde.pdf"},
+ {"filename\t=abcde.pdf", "", L"abcde.pdf"},
+ {"filename \t\t =abcde.pdf", "", L"abcde.pdf"},
+ {"name=abcde.pdf", "", L"abcde.pdf"},
+ {"inline; filename=\"abc%20de.pdf\"", "",
+ L"abc de.pdf"},
+ // Unbalanced quotation mark
+ {"filename=\"abcdef.pdf", "", L"abcdef.pdf"},
+ // Whitespaces are converted to a space.
+ {"inline; filename=\"abc \t\nde.pdf\"", "",
+ L"abc de.pdf"},
+ // %-escaped UTF-8
+ {"attachment; filename=\"%EC%98%88%EC%88%A0%20"
+ "%EC%98%88%EC%88%A0.jpg\"", "", L"\xc608\xc220 \xc608\xc220.jpg"},
+ {"attachment; filename=\"%F0%90%8C%B0%F0%90%8C%B1"
+ "abc.jpg\"", "", L"\U00010330\U00010331abc.jpg"},
+ {"attachment; filename=\"%EC%98%88%EC%88%A0 \n"
+ "%EC%98%88%EC%88%A0.jpg\"", "", L"\xc608\xc220 \xc608\xc220.jpg"},
+ // RFC 2047 with various charsets and Q/B encodings
+ {"attachment; filename=\"=?EUC-JP?Q?=B7=DD=BD="
+ "D13=2Epng?=\"", "", L"\x82b8\x8853" L"3.png"},
+ {"attachment; filename==?eUc-Kr?b?v7m8+iAzLnBuZw==?=",
+ "", L"\xc608\xc220 3.png"},
+ {"attachment; filename==?utf-8?Q?=E8=8A=B8=E8"
+ "=A1=93_3=2Epng?=", "", L"\x82b8\x8853 3.png"},
+ {"attachment; filename==?utf-8?Q?=F0=90=8C=B0"
+ "_3=2Epng?=", "", L"\U00010330 3.png"},
+ {"inline; filename=\"=?iso88591?Q?caf=e9_=2epng?=\"",
+ "", L"caf\x00e9 .png"},
+ // Space after an encoded word should be removed.
+ {"inline; filename=\"=?iso88591?Q?caf=E9_?= .png\"",
+ "", L"caf\x00e9 .png"},
+ // Two encoded words with different charsets (not very likely to be emitted
+ // by web servers in the wild). Spaces between them are removed.
+ {"inline; filename=\"=?euc-kr?b?v7m8+iAz?="
+ " =?ksc5601?q?=BF=B9=BC=FA=2Epng?=\"", "",
+ L"\xc608\xc220 3\xc608\xc220.png"},
+ {"attachment; filename=\"=?windows-1252?Q?caf=E9?="
+ " =?iso-8859-7?b?4eI=?= .png\"", "", L"caf\x00e9\x03b1\x03b2.png"},
+ // Non-ASCII string is passed through and treated as UTF-8 as long as
+ // it's valid as UTF-8 and regardless of |referrer_charset|.
+ {"attachment; filename=caf\xc3\xa9.png",
+ "iso-8859-1", L"caf\x00e9.png"},
+ {"attachment; filename=caf\xc3\xa9.png",
+ "", L"caf\x00e9.png"},
+ // Non-ASCII/Non-UTF-8 string. Fall back to the referrer charset.
+ {"attachment; filename=caf\xe5.png",
+ "windows-1253", L"caf\x03b5.png"},
+#if 0
+ // Non-ASCII/Non-UTF-8 string. Fall back to the native codepage.
+ // TODO(jungshik): We need to set the OS default codepage
+ // to a specific value before testing. On Windows, we can use
+ // SetThreadLocale().
+ {"attachment; filename=\xb0\xa1\xb0\xa2.png",
+ "", L"\xac00\xac01.png"},
+#endif
+ // Failure cases
+ // Invalid hex-digit "G"
+ {"attachment; filename==?iiso88591?Q?caf=EG?=", "",
+ L""},
+ // Incomplete RFC 2047 encoded-word (missing '='' at the end)
+ {"attachment; filename==?iso88591?Q?caf=E3?", "", L""},
+ // Extra character at the end of an encoded word
+ {"attachment; filename==?iso88591?Q?caf=E3?==",
+ "", L""},
+ // Extra token at the end of an encoded word
+ {"attachment; filename==?iso88591?Q?caf=E3?=?",
+ "", L""},
+ {"attachment; filename==?iso88591?Q?caf=E3?=?=",
+ "", L""},
+ // Incomplete hex-escaped chars
+ {"attachment; filename==?windows-1252?Q?=63=61=E?=",
+ "", L""},
+ {"attachment; filename=%EC%98%88%EC%88%A", "", L""},
+ // %-escaped non-UTF-8 encoding is an "error"
+ {"attachment; filename=%B7%DD%BD%D1.png", "", L""},
+ // Two RFC 2047 encoded words in a row without a space is an error.
+ {"attachment; filename==?windows-1252?Q?caf=E3?="
+ "=?iso-8859-7?b?4eIucG5nCg==?=", "", L""},
+
+ // RFC 5987 tests with Filename* : see http://tools.ietf.org/html/rfc5987
+ {"attachment; filename*=foo.html", "", L""},
+ {"attachment; filename*=foo'.html", "", L""},
+ {"attachment; filename*=''foo'.html", "", L""},
+ {"attachment; filename*=''foo.html'", "", L""},
+ {"attachment; filename*=''f\"oo\".html'", "", L""},
+ {"attachment; filename*=bogus_charset''foo.html'",
+ "", L""},
+ {"attachment; filename*='en'foo.html'", "", L""},
+ {"attachment; filename*=iso-8859-1'en'foo.html", "",
+ L"foo.html"},
+ {"attachment; filename*=utf-8'en'foo.html", "",
+ L"foo.html"},
+ // charset cannot be omitted.
+ {"attachment; filename*='es'f\xfa.html'", "", L""},
+ // Non-ASCII bytes are not allowed.
+ {"attachment; filename*=iso-8859-1'es'f\xfa.html", "",
+ L""},
+ {"attachment; filename*=utf-8'es'f\xce\xba.html", "",
+ L""},
+ // TODO(jshin): Space should be %-encoded, but currently, we allow
+ // spaces.
+ {"inline; filename*=iso88591''cafe foo.png", "",
+ L"cafe foo.png"},
+
+ // Filename* tests converted from Q-encoded tests above.
+ {"attachment; filename*=EUC-JP''%B7%DD%BD%D13%2Epng",
+ "", L"\x82b8\x8853" L"3.png"},
+ {"attachment; filename*=utf-8''"
+ "%E8%8A%B8%E8%A1%93%203%2Epng", "", L"\x82b8\x8853 3.png"},
+ {"attachment; filename*=utf-8''%F0%90%8C%B0 3.png", "",
+ L"\U00010330 3.png"},
+ {"inline; filename*=Euc-Kr'ko'%BF%B9%BC%FA%2Epng", "",
+ L"\xc608\xc220.png"},
+ {"attachment; filename*=windows-1252''caf%E9.png", "",
+ L"caf\x00e9.png"},
+
+ // Multiple filename, filename*, name parameters specified.
+ {"attachment; name=\"foo\"; filename=\"bar\"", "", L"bar"},
+ {"attachment; filename=\"bar\"; name=\"foo\"", "", L"bar"},
+ {"attachment; filename=\"bar\"; filename*=utf-8''baz", "", L"baz"},
+
+ // http://greenbytes.de/tech/tc2231/ filename* test cases.
+ // attwithisofn2231iso
+ {"attachment; filename*=iso-8859-1''foo-%E4.html", "",
+ L"foo-\xe4.html"},
+ // attwithfn2231utf8
+ {"attachment; filename*="
+ "UTF-8''foo-%c3%a4-%e2%82%ac.html", "", L"foo-\xe4-\x20ac.html"},
+ // attwithfn2231noc : no encoding specified but UTF-8 is used.
+ {"attachment; filename*=''foo-%c3%a4-%e2%82%ac.html",
+ "", L""},
+ // attwithfn2231utf8comp
+ {"attachment; filename*=UTF-8''foo-a%cc%88.html", "",
+ L"foo-\xe4.html"},
+#ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
+ // This does not work because we treat ISO-8859-1 synonymous with
+ // Windows-1252 per HTML5. For HTTP, in theory, we're not
+ // supposed to.
+ // attwithfn2231utf8-bad
+ {"attachment; filename*="
+ "iso-8859-1''foo-%c3%a4-%e2%82%ac.html", "", L""},
+#endif
+ // attwithfn2231ws1
+ {"attachment; filename *=UTF-8''foo-%c3%a4.html", "",
+ L""},
+ // attwithfn2231ws2
+ {"attachment; filename*= UTF-8''foo-%c3%a4.html", "",
+ L"foo-\xe4.html"},
+ // attwithfn2231ws3
+ {"attachment; filename* =UTF-8''foo-%c3%a4.html", "",
+ L"foo-\xe4.html"},
+ // attwithfn2231quot
+ {"attachment; filename*=\"UTF-8''foo-%c3%a4.html\"",
+ "", L""},
+ // attfnboth
+ {"attachment; filename=\"foo-ae.html\"; "
+ "filename*=UTF-8''foo-%c3%a4.html", "", L"foo-\xe4.html"},
+ // attfnboth2
+ {"attachment; filename*=UTF-8''foo-%c3%a4.html; "
+ "filename=\"foo-ae.html\"", "", L"foo-\xe4.html"},
+ // attnewandfn
+ {"attachment; foobar=x; filename=\"foo.html\"", "",
+ L"foo.html"},
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ HttpContentDisposition header(tests[i].header, tests[i].referrer_charset);
+ EXPECT_EQ(tests[i].expected,
+ UTF8ToWide(header.filename()))
+ << "Failed on input: " << tests[i].header;
+ }
+}
+
+// Test cases from http://greenbytes.de/tech/tc2231/
+TEST(HttpContentDispositionTest, tc2231) {
+ const struct FileNameCDCase {
+ const char* header;
+ net::HttpContentDisposition::Type expected_type;
+ const wchar_t* expected_filename;
+ } tests[] = {
+ // http://greenbytes.de/tech/tc2231/#inlonly
+ { "inline",
+ net::HttpContentDisposition::INLINE,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#inlonlyquoted
+ { "\"inline\"",
+ net::HttpContentDisposition::INLINE,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#inlwithasciifilename
+ { "inline; filename=\"foo.html\"",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#inlwithfnattach
+ { "inline; filename=\"Not an attachment!\"",
+ net::HttpContentDisposition::INLINE,
+ L"Not an attachment!"
+ },
+ // http://greenbytes.de/tech/tc2231/#inlwithasciifilenamepdf
+ { "inline; filename=\"foo.pdf\"",
+ net::HttpContentDisposition::INLINE,
+ L"foo.pdf"
+ },
+ // http://greenbytes.de/tech/tc2231/#attonly
+ { "attachment",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attonlyquoted
+ { "\"attachment\"",
+ net::HttpContentDisposition::INLINE,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attonly403
+ // TODO(abarth): This isn't testable in this unit test.
+ // http://greenbytes.de/tech/tc2231/#attonlyucase
+ { "ATTACHMENT",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilename
+ { "attachment; filename=\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifnescapedchar
+ { "attachment; filename=\"f\\oo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifnescapedquote
+ { "attachment; filename=\"\\\"quoting\\\" tested.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"\"quoting\" tested.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithquotedsemicolon
+ { "attachment; filename=\"Here's a semicolon;.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"Here's a semicolon;.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfilenameandextparam
+ { "attachment; foo=\"bar\"; filename=\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfilenameandextparamescaped
+ { "attachment; foo=\"\\\"\\\\\";filename=\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilenameucase
+ { "attachment; FILENAME=\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenq
+ { "attachment; filename=foo.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenqs
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo.html ;",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attemptyparam
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; ;filename=foo",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilenamenqws
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo bar.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo bar.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfntokensq
+ { "attachment; filename='foo.bar'",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.bar" // Should be L"'foo.bar'"
+ },
+#ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
+ // http://greenbytes.de/tech/tc2231/#attwithisofnplain
+ { "attachment; filename=\"foo-\xE4html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"" // Should be L"foo-\xE4.html"
+ },
+#endif
+ // http://greenbytes.de/tech/tc2231/#attwithutf8fnplain
+ // Note: We'll UTF-8 decode the file name, even though tc2231 says not to.
+ { "attachment; filename=\"foo-\xC3\xA4.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-\xE4.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfnrawpctenca
+ { "attachment; filename=\"foo-%41.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-A.html" // Should be L"foo-%41.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfnusingpct
+ { "attachment; filename=\"50%.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"50%.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithfnrawpctencaq
+ { "attachment; filename=\"foo-%\\41.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-A.html" // Should be L"foo-%41.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithnamepct
+ { "attachment; name=\"foo-%41.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-A.html" // Should be L"foo-%41.html"
+ },
+#ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
+ // http://greenbytes.de/tech/tc2231/#attwithfilenamepctandiso
+ { "attachment; filename=\"\xE4-%41.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"" // Should be L"\xE4-%41.htm"
+ },
+#endif
+ // http://greenbytes.de/tech/tc2231/#attwithfnrawpctenclong
+ { "attachment; filename=\"foo-%c3%a4-%e2%82%ac.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-\xE4-\u20AC.html" // Should be L"foo-%c3%a4-%e2%82%ac.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwithasciifilenamews1
+ { "attachment; filename =\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attwith2filenames
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=\"foo.html\"; filename=\"bar.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attfnbrokentoken
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo[1](2).html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo[1](2).html"
+ },
+#ifdef ICU_SHOULD_FAIL_CONVERSION_ON_INVALID_CHARACTER
+ // http://greenbytes.de/tech/tc2231/#attfnbrokentokeniso
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo-\xE4.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+#endif
+ // http://greenbytes.de/tech/tc2231/#attfnbrokentokenutf
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo-\xC3\xA4.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo-\xE4.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attmissingdisposition
+ // Note: tc2231 says we should fail to parse this header.
+ { "filename=foo.html",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attmissingdisposition2
+ // Note: tc2231 says we should fail to parse this header.
+ { "x=y; filename=foo.html",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attmissingdisposition3
+ // Note: tc2231 says we should fail to parse this header.
+ { "\"foo; filename=bar;baz\"; filename=qux",
+ net::HttpContentDisposition::INLINE,
+ L"" // Firefox gets qux
+ },
+ // http://greenbytes.de/tech/tc2231/#attmissingdisposition4
+ // Note: tc2231 says we should fail to parse this header.
+ { "filename=foo.html, filename=bar.html",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html, filename=bar.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#emptydisposition
+ // Note: tc2231 says we should fail to parse this header.
+ { "; filename=foo.html",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attandinline
+ // Note: tc2231 says we should fail to parse this header.
+ { "inline; attachment; filename=foo.html",
+ net::HttpContentDisposition::INLINE,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attandinline2
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; inline; filename=foo.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=\"foo.html\".txt",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html\".txt"
+ },
+ // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn2
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=\"bar",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"bar"
+ },
+ // http://greenbytes.de/tech/tc2231/#attbrokenquotedfn3
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo\"bar;baz\"qux",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo\"bar;baz\"qux"
+ },
+ // http://greenbytes.de/tech/tc2231/#attmultinstances
+ // Note: tc2231 says we should fail to parse this header.
+ { "attachment; filename=foo.html, attachment; filename=bar.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html, attachment"
+ },
+ // http://greenbytes.de/tech/tc2231/#attmissingdelim
+ { "attachment; foo=foo filename=bar",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attreversed
+ // Note: tc2231 says we should fail to parse this header.
+ { "filename=foo.html; attachment",
+ net::HttpContentDisposition::INLINE,
+ L"foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attconfusedparam
+ { "attachment; xfilename=foo.html",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attabspath
+ { "attachment; filename=\"/foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"/foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#attabspathwin
+ { "attachment; filename=\"\\\\foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"\\foo.html"
+ },
+ // http://greenbytes.de/tech/tc2231/#dispext
+ { "foobar",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#dispextbadfn
+ { "attachment; example=\"filename=example.txt\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L""
+ },
+ // http://greenbytes.de/tech/tc2231/#attnewandfn
+ { "attachment; foobar=x; filename=\"foo.html\"",
+ net::HttpContentDisposition::ATTACHMENT,
+ L"foo.html"
+ },
+ // TODO(abarth): Add the filename* tests, but check
+ // HttpContentDispositionTest.Filename for overlap.
+ // TODO(abarth): http://greenbytes.de/tech/tc2231/#attrfc2047token
+ // TODO(abarth): http://greenbytes.de/tech/tc2231/#attrfc2047quoted
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ HttpContentDisposition header(tests[i].header, std::string());
+ EXPECT_EQ(tests[i].expected_type, header.type())
+ << "Failed on input: " << tests[i].header;
+ EXPECT_EQ(tests[i].expected_filename, UTF8ToWide(header.filename()))
+ << "Failed on input: " << tests[i].header;
+ }
+}
+
+TEST(HttpContentDispositionTest, ParseResult) {
+ const struct ParseResultTestCase {
+ const char* header;
+ int expected_flags;
+ } kTestCases[] = {
+ // Basic feature tests
+ { "", HttpContentDisposition::INVALID },
+ { "example=x", HttpContentDisposition::INVALID },
+ { "attachment; filename=", HttpContentDisposition::HAS_DISPOSITION_TYPE },
+ { "attachment; name=", HttpContentDisposition::HAS_DISPOSITION_TYPE },
+ { "attachment; filename*=", HttpContentDisposition::HAS_DISPOSITION_TYPE },
+ { "attachment; filename==?utf-8?Q?\?=",
+ HttpContentDisposition::HAS_DISPOSITION_TYPE },
+ { "filename=x", HttpContentDisposition::HAS_FILENAME },
+ { "example; filename=x",
+ HttpContentDisposition::HAS_DISPOSITION_TYPE |
+ HttpContentDisposition::HAS_UNKNOWN_DISPOSITION_TYPE |
+ HttpContentDisposition::HAS_FILENAME},
+ { "attachment; filename=x",
+ HttpContentDisposition::HAS_DISPOSITION_TYPE |
+ HttpContentDisposition::HAS_FILENAME },
+ { "attachment; filename=x; name=y",
+ HttpContentDisposition::HAS_DISPOSITION_TYPE |
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_NAME },
+ { "attachment; name=y; filename*=utf-8''foo; name=x",
+ HttpContentDisposition::HAS_DISPOSITION_TYPE |
+ HttpContentDisposition::HAS_EXT_FILENAME |
+ HttpContentDisposition::HAS_NAME },
+
+ // Feature tests for 'filename' attribute.
+ { "filename=foo\xcc\x88",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_NON_ASCII_STRINGS },
+ { "filename=foo%cc%88",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS },
+ { "filename==?utf-8?Q?foo?=",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS },
+ { "filename=\"=?utf-8?Q?foo?=\"",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS },
+ { "filename==?utf-8?Q?foo?", HttpContentDisposition::INVALID },
+ { "name=foo\xcc\x88",
+ HttpContentDisposition::HAS_NAME },
+
+ // Shouldn't set |has_non_ascii_strings| based on 'name' attribute.
+ { "filename=x; name=foo\xcc\x88",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_NAME },
+ { "filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?=",
+ HttpContentDisposition::HAS_FILENAME |
+ HttpContentDisposition::HAS_NON_ASCII_STRINGS |
+ HttpContentDisposition::HAS_PERCENT_ENCODED_STRINGS |
+ HttpContentDisposition::HAS_RFC2047_ENCODED_STRINGS },
+
+ // If 'filename' attribute is invalid, should set any flags based on it.
+ { "filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?",
+ HttpContentDisposition::INVALID },
+ { "filename=foo\xcc\x88 foo%cc%88 =?utf-8?Q?foo?; name=x",
+ HttpContentDisposition::HAS_NAME },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ const ParseResultTestCase& test_case = kTestCases[i];
+ HttpContentDisposition content_disposition(test_case.header, "utf-8");
+ int result = content_disposition.parse_result_flags();
+
+ SCOPED_TRACE(testing::Message() << "Test case " << i
+ << " with header " << test_case.header);
+ EXPECT_EQ(test_case.expected_flags, result);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_network_layer.cc b/chromium/net/http/http_network_layer.cc
new file mode 100644
index 00000000000..e4d082eb0fd
--- /dev/null
+++ b/chromium/net/http/http_network_layer.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 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.
+
+#include "net/http/http_network_layer.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream_factory_impl_job.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+HttpNetworkLayer::HttpNetworkLayer(HttpNetworkSession* session)
+ : session_(session),
+ suspended_(false) {
+ DCHECK(session_.get());
+}
+
+HttpNetworkLayer::~HttpNetworkLayer() {
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+HttpTransactionFactory* HttpNetworkLayer::CreateFactory(
+ HttpNetworkSession* session) {
+ DCHECK(session);
+
+ return new HttpNetworkLayer(session);
+}
+
+// static
+void HttpNetworkLayer::ForceAlternateProtocol() {
+ PortAlternateProtocolPair pair;
+ pair.port = 443;
+ pair.protocol = NPN_SPDY_2;
+ HttpServerPropertiesImpl::ForceAlternateProtocol(pair);
+}
+
+//-----------------------------------------------------------------------------
+
+int HttpNetworkLayer::CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) {
+ if (suspended_)
+ return ERR_NETWORK_IO_SUSPENDED;
+
+ trans->reset(new HttpNetworkTransaction(priority, GetSession()));
+ return OK;
+}
+
+HttpCache* HttpNetworkLayer::GetCache() {
+ return NULL;
+}
+
+HttpNetworkSession* HttpNetworkLayer::GetSession() { return session_.get(); }
+
+void HttpNetworkLayer::OnSuspend() {
+ suspended_ = true;
+
+ if (session_.get())
+ session_->CloseIdleConnections();
+}
+
+void HttpNetworkLayer::OnResume() {
+ suspended_ = false;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_network_layer.h b/chromium/net/http/http_network_layer.h
new file mode 100644
index 00000000000..4b41b7190e8
--- /dev/null
+++ b/chromium/net/http/http_network_layer.h
@@ -0,0 +1,61 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_NETWORK_LAYER_H_
+#define NET_HTTP_HTTP_NETWORK_LAYER_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/power_monitor/power_observer.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+#include "net/http/http_transaction_factory.h"
+
+namespace net {
+
+class HttpNetworkSession;
+
+class NET_EXPORT HttpNetworkLayer
+ : public HttpTransactionFactory,
+ public base::PowerObserver,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Construct a HttpNetworkLayer with an existing HttpNetworkSession which
+ // contains a valid ProxyService.
+ explicit HttpNetworkLayer(HttpNetworkSession* session);
+ virtual ~HttpNetworkLayer();
+
+ // Create a transaction factory that instantiate a network layer over an
+ // existing network session. Network session contains some valuable
+ // information (e.g. authentication data) that we want to share across
+ // multiple network layers. This method exposes the implementation details
+ // of a network layer, use this method with an existing network layer only
+ // when network session is shared.
+ static HttpTransactionFactory* CreateFactory(HttpNetworkSession* session);
+
+ // Forces an alternate protocol of SPDY/2 on port 443.
+ // TODO(rch): eliminate this method.
+ static void ForceAlternateProtocol();
+
+ // HttpTransactionFactory methods:
+ virtual int CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) OVERRIDE;
+ virtual HttpCache* GetCache() OVERRIDE;
+ virtual HttpNetworkSession* GetSession() OVERRIDE;
+
+ // base::PowerObserver methods:
+ virtual void OnSuspend() OVERRIDE;
+ virtual void OnResume() OVERRIDE;
+
+ private:
+ const scoped_refptr<HttpNetworkSession> session_;
+ bool suspended_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_LAYER_H_
diff --git a/chromium/net/http/http_network_layer_unittest.cc b/chromium/net/http/http_network_layer_unittest.cc
new file mode 100644
index 00000000000..c939d844ba2
--- /dev/null
+++ b/chromium/net/http/http_network_layer_unittest.cc
@@ -0,0 +1,447 @@
+// Copyright (c) 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.
+
+#include "net/http/http_network_layer.h"
+
+#include "base/strings/stringprintf.h"
+#include "net/base/net_log.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+class HttpNetworkLayerTest : public PlatformTest {
+ protected:
+ HttpNetworkLayerTest() : ssl_config_service_(new SSLConfigServiceDefaults) {}
+
+ virtual void SetUp() {
+ ConfigureTestDependencies(ProxyService::CreateDirect());
+ }
+
+ void ConfigureTestDependencies(ProxyService* proxy_service) {
+ cert_verifier_.reset(new MockCertVerifier);
+ transport_security_state_.reset(new TransportSecurityState);
+ proxy_service_.reset(proxy_service);
+ HttpNetworkSession::Params session_params;
+ session_params.client_socket_factory = &mock_socket_factory_;
+ session_params.host_resolver = &host_resolver_;
+ session_params.cert_verifier = cert_verifier_.get();
+ session_params.transport_security_state = transport_security_state_.get();
+ session_params.proxy_service = proxy_service_.get();
+ session_params.ssl_config_service = ssl_config_service_.get();
+ session_params.http_server_properties =
+ http_server_properties_.GetWeakPtr();
+ network_session_ = new HttpNetworkSession(session_params);
+ factory_.reset(new HttpNetworkLayer(network_session_.get()));
+ }
+
+ MockClientSocketFactory mock_socket_factory_;
+ MockHostResolver host_resolver_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+ scoped_ptr<ProxyService> proxy_service_;
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+ scoped_refptr<HttpNetworkSession> network_session_;
+ scoped_ptr<HttpNetworkLayer> factory_;
+ HttpServerPropertiesImpl http_server_properties_;
+};
+
+TEST_F(HttpNetworkLayerTest, CreateAndDestroy) {
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(trans.get() != NULL);
+}
+
+TEST_F(HttpNetworkLayerTest, Suspend) {
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ trans.reset();
+
+ factory_->OnSuspend();
+
+ rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(ERR_NETWORK_IO_SUSPENDED, rv);
+
+ ASSERT_TRUE(trans == NULL);
+
+ factory_->OnResume();
+
+ rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_F(HttpNetworkLayerTest, GET) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: Foo/1.0\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Foo/1.0");
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans.get(), &contents);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", contents);
+}
+
+TEST_F(HttpNetworkLayerTest, ServerFallback) {
+ // Verify that a Connection: Proxy-Bypass header induces proxy fallback to
+ // a second proxy, if configured.
+
+ // To configure this test, we need to wire up a custom proxy service to use
+ // a pair of proxies. We'll induce fallback via the first and return
+ // the expected data via the second.
+ ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult(
+ "PROXY bad:8080; PROXY good:8080"));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Connection: proxy-bypass\r\n\r\n"),
+ MockRead("Bypass message"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // Second data provider returns the expected content.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"
+ "Server: not-proxy\r\n\r\n"),
+ MockRead("content"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes2[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans.get(), &contents);
+ EXPECT_EQ(OK, rv);
+
+ // We should obtain content from the second socket provider write
+ // corresponding to the fallback proxy.
+ EXPECT_EQ("content", contents);
+ // We also have a server header here that isn't set by the proxy.
+ EXPECT_TRUE(trans->GetResponseInfo()->headers->HasHeaderValue(
+ "server", "not-proxy"));
+ // We should also observe the bad proxy in the retry list.
+ ASSERT_TRUE(1u == proxy_service_->proxy_retry_info().size());
+ EXPECT_EQ("bad:8080", (*proxy_service_->proxy_retry_info().begin()).first);
+}
+
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+TEST_F(HttpNetworkLayerTest, ServerFallbackOnInternalServerError) {
+ // Verify that "500 Internal Server Error" via the data reduction proxy
+ // induces proxy fallback to a second proxy, if configured.
+
+ // To configure this test, we need to wire up a custom proxy service to use
+ // a pair of proxies. We'll induce fallback via the first and return
+ // the expected data via the second.
+ std::string data_reduction_proxy(
+ HostPortPair::FromURL(GURL(SPDY_PROXY_AUTH_ORIGIN)).ToString());
+ std::string pac_string = base::StringPrintf(
+ "PROXY %s; PROXY good:8080", data_reduction_proxy.data());
+ ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult(pac_string));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 500 Internal Server Error\r\n\r\n"),
+ MockRead("Bypass message"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // Second data provider returns the expected content.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"
+ "Server: not-proxy\r\n\r\n"),
+ MockRead("content"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes2[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans.get(), &contents);
+ EXPECT_EQ(OK, rv);
+
+ // We should obtain content from the second socket provider write
+ // corresponding to the fallback proxy.
+ EXPECT_EQ("content", contents);
+ // We also have a server header here that isn't set by the proxy.
+ EXPECT_TRUE(trans->GetResponseInfo()->headers->HasHeaderValue(
+ "server", "not-proxy"));
+ // We should also observe the data reduction proxy in the retry list.
+ ASSERT_TRUE(1u == proxy_service_->proxy_retry_info().size());
+ EXPECT_EQ(data_reduction_proxy,
+ (*proxy_service_->proxy_retry_info().begin()).first);
+}
+#endif // defined(SPDY_PROXY_AUTH_ORIGIN)
+
+TEST_F(HttpNetworkLayerTest, ServerFallbackDoesntLoop) {
+ // Verify that a Connection: Proxy-Bypass header will display the original
+ // proxy's error page content if a fallback option is not configured.
+ ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult(
+ "PROXY bad:8080; PROXY alsobad:8080"));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Connection: proxy-bypass\r\n\r\n"),
+ MockRead("Bypass message"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ StaticSocketDataProvider data2(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+
+ std::string contents;
+ rv = ReadTransaction(trans.get(), &contents);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Bypass message", contents);
+
+ // Despite not falling back to anything, we should still observe the proxies
+ // in the bad proxies list.
+ const ProxyRetryInfoMap& retry_info = proxy_service_->proxy_retry_info();
+ ASSERT_EQ(2u, retry_info.size());
+ ASSERT_TRUE(retry_info.find("bad:8080") != retry_info.end());
+ ASSERT_TRUE(retry_info.find("alsobad:8080") != retry_info.end());
+}
+
+TEST_F(HttpNetworkLayerTest, ProxyBypassIgnoredOnDirectConnection) {
+ // Verify that a Connection: proxy-bypass header is ignored when returned
+ // from a directly connected origin server.
+ ConfigureTestDependencies(ProxyService::CreateFixedFromPacResult("DIRECT"));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Connection: proxy-bypass\r\n\r\n"),
+ MockRead("Bypass message"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ StaticSocketDataProvider data1(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+
+ // We should have read the original page data.
+ std::string contents;
+ rv = ReadTransaction(trans.get(), &contents);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Bypass message", contents);
+
+ // We should have no entries in our bad proxy list.
+ ASSERT_EQ(0u, proxy_service_->proxy_retry_info().size());
+}
+
+TEST_F(HttpNetworkLayerTest, NetworkVerified) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: Foo/1.0\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Foo/1.0");
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ EXPECT_TRUE(trans->GetResponseInfo()->network_accessed);
+}
+
+TEST_F(HttpNetworkLayerTest, NetworkUnVerified) {
+ MockRead data_reads[] = {
+ MockRead(ASYNC, ERR_CONNECTION_RESET),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: Foo/1.0\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory_.AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ HttpRequestInfo request_info;
+ request_info.url = GURL("http://www.google.com/");
+ request_info.method = "GET";
+ request_info.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Foo/1.0");
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans;
+ int rv = factory_->CreateTransaction(DEFAULT_PRIORITY, &trans, NULL);
+ EXPECT_EQ(OK, rv);
+
+ rv = trans->Start(&request_info, callback.callback(), BoundNetLog());
+ ASSERT_EQ(ERR_CONNECTION_RESET, callback.GetResult(rv));
+
+ // If the response info is null, that means that any consumer won't
+ // see the network accessed bit set.
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_network_session.cc b/chromium/net/http/http_network_session.cc
new file mode 100644
index 00000000000..32659629e90
--- /dev/null
+++ b/chromium/net/http/http_network_session.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 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.
+
+#include "net/http/http_network_session.h"
+
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_response_body_drainer.h"
+#include "net/http/http_stream_factory_impl.h"
+#include "net/http/url_security_manager.h"
+#include "net/proxy/proxy_service.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_stream_factory.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_pool_manager_impl.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_session_pool.h"
+
+namespace {
+
+net::ClientSocketPoolManager* CreateSocketPoolManager(
+ net::HttpNetworkSession::SocketPoolType pool_type,
+ const net::HttpNetworkSession::Params& params) {
+ // TODO(yutak): Differentiate WebSocket pool manager and allow more
+ // simultaneous connections for WebSockets.
+ return new net::ClientSocketPoolManagerImpl(
+ params.net_log,
+ params.client_socket_factory ?
+ params.client_socket_factory :
+ net::ClientSocketFactory::GetDefaultFactory(),
+ params.host_resolver,
+ params.cert_verifier,
+ params.server_bound_cert_service,
+ params.transport_security_state,
+ params.ssl_session_cache_shard,
+ params.proxy_service,
+ params.ssl_config_service,
+ pool_type);
+}
+
+} // unnamed namespace
+
+namespace net {
+
+HttpNetworkSession::Params::Params()
+ : client_socket_factory(NULL),
+ host_resolver(NULL),
+ cert_verifier(NULL),
+ server_bound_cert_service(NULL),
+ transport_security_state(NULL),
+ proxy_service(NULL),
+ ssl_config_service(NULL),
+ http_auth_handler_factory(NULL),
+ network_delegate(NULL),
+ net_log(NULL),
+ host_mapping_rules(NULL),
+ force_http_pipelining(false),
+ ignore_certificate_errors(false),
+ http_pipelining_enabled(false),
+ testing_fixed_http_port(0),
+ testing_fixed_https_port(0),
+ force_spdy_single_domain(false),
+ enable_spdy_ip_pooling(true),
+ enable_spdy_credential_frames(false),
+ enable_spdy_compression(true),
+ enable_spdy_ping_based_connection_checking(true),
+ spdy_default_protocol(kProtoUnknown),
+ spdy_stream_initial_recv_window_size(0),
+ spdy_initial_max_concurrent_streams(0),
+ spdy_max_concurrent_streams_limit(0),
+ time_func(&base::TimeTicks::Now),
+ enable_quic(false),
+ enable_quic_https(false),
+ quic_clock(NULL),
+ quic_random(NULL),
+ enable_user_alternate_protocol_ports(false),
+ quic_crypto_client_stream_factory(NULL) {
+}
+
+HttpNetworkSession::Params::~Params() {}
+
+// TODO(mbelshe): Move the socket factories into HttpStreamFactory.
+HttpNetworkSession::HttpNetworkSession(const Params& params)
+ : net_log_(params.net_log),
+ network_delegate_(params.network_delegate),
+ http_server_properties_(params.http_server_properties),
+ cert_verifier_(params.cert_verifier),
+ http_auth_handler_factory_(params.http_auth_handler_factory),
+ force_http_pipelining_(params.force_http_pipelining),
+ proxy_service_(params.proxy_service),
+ ssl_config_service_(params.ssl_config_service),
+ normal_socket_pool_manager_(
+ CreateSocketPoolManager(NORMAL_SOCKET_POOL, params)),
+ websocket_socket_pool_manager_(
+ CreateSocketPoolManager(WEBSOCKET_SOCKET_POOL, params)),
+ quic_stream_factory_(params.host_resolver,
+ params.client_socket_factory ?
+ params.client_socket_factory :
+ net::ClientSocketFactory::GetDefaultFactory(),
+ params.quic_crypto_client_stream_factory,
+ params.quic_random ? params.quic_random :
+ QuicRandom::GetInstance(),
+ params.quic_clock ? params. quic_clock :
+ new QuicClock()),
+ spdy_session_pool_(params.host_resolver,
+ params.ssl_config_service,
+ params.http_server_properties,
+ params.force_spdy_single_domain,
+ params.enable_spdy_ip_pooling,
+ params.enable_spdy_credential_frames,
+ params.enable_spdy_compression,
+ params.enable_spdy_ping_based_connection_checking,
+ params.spdy_default_protocol,
+ params.spdy_stream_initial_recv_window_size,
+ params.spdy_initial_max_concurrent_streams,
+ params.spdy_max_concurrent_streams_limit,
+ params.time_func,
+ params.trusted_spdy_proxy),
+ http_stream_factory_(new HttpStreamFactoryImpl(this, false)),
+ websocket_stream_factory_(new HttpStreamFactoryImpl(this, true)),
+ params_(params) {
+ DCHECK(proxy_service_);
+ DCHECK(ssl_config_service_.get());
+ CHECK(http_server_properties_);
+}
+
+HttpNetworkSession::~HttpNetworkSession() {
+ STLDeleteElements(&response_drainers_);
+ spdy_session_pool_.CloseAllSessions();
+}
+
+void HttpNetworkSession::AddResponseDrainer(HttpResponseBodyDrainer* drainer) {
+ DCHECK(!ContainsKey(response_drainers_, drainer));
+ response_drainers_.insert(drainer);
+}
+
+void HttpNetworkSession::RemoveResponseDrainer(
+ HttpResponseBodyDrainer* drainer) {
+ DCHECK(ContainsKey(response_drainers_, drainer));
+ response_drainers_.erase(drainer);
+}
+
+TransportClientSocketPool* HttpNetworkSession::GetTransportSocketPool(
+ SocketPoolType pool_type) {
+ return GetSocketPoolManager(pool_type)->GetTransportSocketPool();
+}
+
+SSLClientSocketPool* HttpNetworkSession::GetSSLSocketPool(
+ SocketPoolType pool_type) {
+ return GetSocketPoolManager(pool_type)->GetSSLSocketPool();
+}
+
+SOCKSClientSocketPool* HttpNetworkSession::GetSocketPoolForSOCKSProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& socks_proxy) {
+ return GetSocketPoolManager(pool_type)->GetSocketPoolForSOCKSProxy(
+ socks_proxy);
+}
+
+HttpProxyClientSocketPool* HttpNetworkSession::GetSocketPoolForHTTPProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& http_proxy) {
+ return GetSocketPoolManager(pool_type)->GetSocketPoolForHTTPProxy(http_proxy);
+}
+
+SSLClientSocketPool* HttpNetworkSession::GetSocketPoolForSSLWithProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& proxy_server) {
+ return GetSocketPoolManager(pool_type)->GetSocketPoolForSSLWithProxy(
+ proxy_server);
+}
+
+base::Value* HttpNetworkSession::SocketPoolInfoToValue() const {
+ // TODO(yutak): Should merge values from normal pools and WebSocket pools.
+ return normal_socket_pool_manager_->SocketPoolInfoToValue();
+}
+
+base::Value* HttpNetworkSession::SpdySessionPoolInfoToValue() const {
+ return spdy_session_pool_.SpdySessionPoolInfoToValue();
+}
+
+base::Value* HttpNetworkSession::QuicInfoToValue() const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->Set("sessions", quic_stream_factory_.QuicStreamFactoryInfoToValue());
+ dict->SetBoolean("quic_enabled", params_.enable_quic);
+ dict->SetBoolean("quic_enabled_https", params_.enable_quic_https);
+ dict->SetString("origin_to_force_quic_on",
+ params_.origin_to_force_quic_on.ToString());
+ return dict;
+}
+
+void HttpNetworkSession::CloseAllConnections() {
+ normal_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
+ websocket_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
+ spdy_session_pool_.CloseCurrentSessions(ERR_ABORTED);
+ quic_stream_factory_.CloseAllSessions(ERR_ABORTED);
+}
+
+void HttpNetworkSession::CloseIdleConnections() {
+ normal_socket_pool_manager_->CloseIdleSockets();
+ websocket_socket_pool_manager_->CloseIdleSockets();
+ spdy_session_pool_.CloseCurrentIdleSessions();
+}
+
+ClientSocketPoolManager* HttpNetworkSession::GetSocketPoolManager(
+ SocketPoolType pool_type) {
+ switch (pool_type) {
+ case NORMAL_SOCKET_POOL:
+ return normal_socket_pool_manager_.get();
+ case WEBSOCKET_SOCKET_POOL:
+ return websocket_socket_pool_manager_.get();
+ default:
+ NOTREACHED();
+ break;
+ }
+ return NULL;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_network_session.h b/chromium/net/http/http_network_session.h
new file mode 100644
index 00000000000..c10bb1446f4
--- /dev/null
+++ b/chromium/net/http/http_network_session.h
@@ -0,0 +1,209 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_NETWORK_SESSION_H_
+#define NET_HTTP_HTTP_NETWORK_SESSION_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_stream_factory.h"
+#include "net/quic/quic_stream_factory.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/ssl/ssl_client_auth_cache.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class CertVerifier;
+class ClientSocketFactory;
+class ClientSocketPoolManager;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpNetworkSessionPeer;
+class HttpProxyClientSocketPool;
+class HttpResponseBodyDrainer;
+class HttpServerProperties;
+class NetLog;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class QuicClock;
+class QuicCryptoClientStreamFactory;
+class SOCKSClientSocketPool;
+class SSLClientSocketPool;
+class SSLConfigService;
+class TransportClientSocketPool;
+class TransportSecurityState;
+
+// This class holds session objects used by HttpNetworkTransaction objects.
+class NET_EXPORT HttpNetworkSession
+ : public base::RefCounted<HttpNetworkSession>,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ struct NET_EXPORT Params {
+ Params();
+ ~Params();
+
+ ClientSocketFactory* client_socket_factory;
+ HostResolver* host_resolver;
+ CertVerifier* cert_verifier;
+ ServerBoundCertService* server_bound_cert_service;
+ TransportSecurityState* transport_security_state;
+ ProxyService* proxy_service;
+ std::string ssl_session_cache_shard;
+ SSLConfigService* ssl_config_service;
+ HttpAuthHandlerFactory* http_auth_handler_factory;
+ NetworkDelegate* network_delegate;
+ base::WeakPtr<HttpServerProperties> http_server_properties;
+ NetLog* net_log;
+ HostMappingRules* host_mapping_rules;
+ bool force_http_pipelining;
+ bool ignore_certificate_errors;
+ bool http_pipelining_enabled;
+ uint16 testing_fixed_http_port;
+ uint16 testing_fixed_https_port;
+ bool force_spdy_single_domain;
+ bool enable_spdy_ip_pooling;
+ bool enable_spdy_credential_frames;
+ bool enable_spdy_compression;
+ bool enable_spdy_ping_based_connection_checking;
+ NextProto spdy_default_protocol;
+ size_t spdy_stream_initial_recv_window_size;
+ size_t spdy_initial_max_concurrent_streams;
+ size_t spdy_max_concurrent_streams_limit;
+ SpdySessionPool::TimeFunc time_func;
+ std::string trusted_spdy_proxy;
+ bool enable_quic;
+ bool enable_quic_https;
+ HostPortPair origin_to_force_quic_on;
+ QuicClock* quic_clock; // Will be owned by QuicStreamFactory.
+ QuicRandom* quic_random;
+ bool enable_user_alternate_protocol_ports;
+ QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory;
+ };
+
+ enum SocketPoolType {
+ NORMAL_SOCKET_POOL,
+ WEBSOCKET_SOCKET_POOL,
+ NUM_SOCKET_POOL_TYPES
+ };
+
+ explicit HttpNetworkSession(const Params& params);
+
+ HttpAuthCache* http_auth_cache() { return &http_auth_cache_; }
+ SSLClientAuthCache* ssl_client_auth_cache() {
+ return &ssl_client_auth_cache_;
+ }
+
+ void AddResponseDrainer(HttpResponseBodyDrainer* drainer);
+
+ void RemoveResponseDrainer(HttpResponseBodyDrainer* drainer);
+
+ TransportClientSocketPool* GetTransportSocketPool(SocketPoolType pool_type);
+ SSLClientSocketPool* GetSSLSocketPool(SocketPoolType pool_type);
+ SOCKSClientSocketPool* GetSocketPoolForSOCKSProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& socks_proxy);
+ HttpProxyClientSocketPool* GetSocketPoolForHTTPProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& http_proxy);
+ SSLClientSocketPool* GetSocketPoolForSSLWithProxy(
+ SocketPoolType pool_type,
+ const HostPortPair& proxy_server);
+
+ CertVerifier* cert_verifier() { return cert_verifier_; }
+ ProxyService* proxy_service() { return proxy_service_; }
+ SSLConfigService* ssl_config_service() { return ssl_config_service_.get(); }
+ SpdySessionPool* spdy_session_pool() { return &spdy_session_pool_; }
+ QuicStreamFactory* quic_stream_factory() { return &quic_stream_factory_; }
+ HttpAuthHandlerFactory* http_auth_handler_factory() {
+ return http_auth_handler_factory_;
+ }
+ NetworkDelegate* network_delegate() {
+ return network_delegate_;
+ }
+ base::WeakPtr<HttpServerProperties> http_server_properties() {
+ return http_server_properties_;
+ }
+ HttpStreamFactory* http_stream_factory() {
+ return http_stream_factory_.get();
+ }
+ HttpStreamFactory* websocket_stream_factory() {
+ return websocket_stream_factory_.get();
+ }
+ NetLog* net_log() {
+ return net_log_;
+ }
+
+ // Creates a Value summary of the state of the socket pools. The caller is
+ // responsible for deleting the returned value.
+ base::Value* SocketPoolInfoToValue() const;
+
+ // Creates a Value summary of the state of the SPDY sessions. The caller is
+ // responsible for deleting the returned value.
+ base::Value* SpdySessionPoolInfoToValue() const;
+
+ // Creates a Value summary of the state of the QUIC sessions and
+ // configuration. The caller is responsible for deleting the returned value.
+ base::Value* QuicInfoToValue() const;
+
+ void CloseAllConnections();
+ void CloseIdleConnections();
+
+ bool force_http_pipelining() const { return force_http_pipelining_; }
+
+ // Returns the original Params used to construct this session.
+ const Params& params() const { return params_; }
+
+ void set_http_pipelining_enabled(bool enable) {
+ params_.http_pipelining_enabled = enable;
+ }
+
+ private:
+ friend class base::RefCounted<HttpNetworkSession>;
+ friend class HttpNetworkSessionPeer;
+
+ ~HttpNetworkSession();
+
+ ClientSocketPoolManager* GetSocketPoolManager(SocketPoolType pool_type);
+
+ NetLog* const net_log_;
+ NetworkDelegate* const network_delegate_;
+ const base::WeakPtr<HttpServerProperties> http_server_properties_;
+ CertVerifier* const cert_verifier_;
+ HttpAuthHandlerFactory* const http_auth_handler_factory_;
+ bool force_http_pipelining_;
+
+ // Not const since it's modified by HttpNetworkSessionPeer for testing.
+ ProxyService* proxy_service_;
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+
+ HttpAuthCache http_auth_cache_;
+ SSLClientAuthCache ssl_client_auth_cache_;
+ scoped_ptr<ClientSocketPoolManager> normal_socket_pool_manager_;
+ scoped_ptr<ClientSocketPoolManager> websocket_socket_pool_manager_;
+ QuicStreamFactory quic_stream_factory_;
+ SpdySessionPool spdy_session_pool_;
+ scoped_ptr<HttpStreamFactory> http_stream_factory_;
+ scoped_ptr<HttpStreamFactory> websocket_stream_factory_;
+ std::set<HttpResponseBodyDrainer*> response_drainers_;
+
+ Params params_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_SESSION_H_
diff --git a/chromium/net/http/http_network_session_peer.cc b/chromium/net/http/http_network_session_peer.cc
new file mode 100644
index 00000000000..74b0d415310
--- /dev/null
+++ b/chromium/net/http/http_network_session_peer.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 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.
+
+#include "net/http/http_network_session_peer.h"
+
+#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+
+namespace net {
+
+HttpNetworkSessionPeer::HttpNetworkSessionPeer(
+ const scoped_refptr<HttpNetworkSession>& session)
+ : session_(session) {}
+
+HttpNetworkSessionPeer::~HttpNetworkSessionPeer() {}
+
+void HttpNetworkSessionPeer::SetClientSocketPoolManager(
+ ClientSocketPoolManager* socket_pool_manager) {
+ session_->normal_socket_pool_manager_.reset(socket_pool_manager);
+}
+
+void HttpNetworkSessionPeer::SetProxyService(ProxyService* proxy_service) {
+ session_->proxy_service_ = proxy_service;
+}
+
+void HttpNetworkSessionPeer::SetHttpStreamFactory(
+ HttpStreamFactory* http_stream_factory) {
+ session_->http_stream_factory_.reset(http_stream_factory);
+}
+
+void HttpNetworkSessionPeer::SetWebSocketStreamFactory(
+ HttpStreamFactory* http_stream_factory) {
+ session_->websocket_stream_factory_.reset(http_stream_factory);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_network_session_peer.h b/chromium/net/http/http_network_session_peer.h
new file mode 100644
index 00000000000..eeccfefafd4
--- /dev/null
+++ b/chromium/net/http/http_network_session_peer.h
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_NETWORK_SESSION_PEER_H_
+#define NET_HTTP_HTTP_NETWORK_SESSION_PEER_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class ClientSocketPoolManager;
+class HttpNetworkSession;
+class HttpStreamFactory;
+class ProxyService;
+
+class NET_EXPORT_PRIVATE HttpNetworkSessionPeer {
+ public:
+ explicit HttpNetworkSessionPeer(
+ const scoped_refptr<HttpNetworkSession>& session);
+ ~HttpNetworkSessionPeer();
+
+ void SetClientSocketPoolManager(
+ ClientSocketPoolManager* socket_pool_manager);
+
+ void SetProxyService(ProxyService* proxy_service);
+
+ void SetHttpStreamFactory(HttpStreamFactory* http_stream_factory);
+ void SetWebSocketStreamFactory(HttpStreamFactory* websocket_stream_factory);
+
+ private:
+ const scoped_refptr<HttpNetworkSession> session_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpNetworkSessionPeer);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_SESSION_PEER_H_
diff --git a/chromium/net/http/http_network_transaction.cc b/chromium/net/http/http_network_transaction.cc
new file mode 100644
index 00000000000..a63a2aa43b9
--- /dev/null
+++ b/chromium/net/http/http_network_transaction.cc
@@ -0,0 +1,1488 @@
+// Copyright (c) 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.
+
+#include "net/http/http_network_transaction.h"
+
+#include <set>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_handler.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_chunked_decoder.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_stream_base.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/http/url_security_manager.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "url/gurl.h"
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+void ProcessAlternateProtocol(
+ HttpStreamFactory* factory,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ const HttpResponseHeaders& headers,
+ const HostPortPair& http_host_port_pair) {
+ std::string alternate_protocol_str;
+
+ if (!headers.EnumerateHeader(NULL, kAlternateProtocolHeader,
+ &alternate_protocol_str)) {
+ // Header is not present.
+ return;
+ }
+
+ factory->ProcessAlternateProtocol(http_server_properties,
+ alternate_protocol_str,
+ http_host_port_pair);
+}
+
+// Returns true if |error| is a client certificate authentication error.
+bool IsClientCertificateError(int error) {
+ switch (error) {
+ case ERR_BAD_SSL_CLIENT_AUTH_CERT:
+ case ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED:
+ case ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY:
+ case ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+base::Value* NetLogSSLVersionFallbackCallback(
+ const GURL* url,
+ int net_error,
+ uint16 version_before,
+ uint16 version_after,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("host_and_port", GetHostAndPort(*url));
+ dict->SetInteger("net_error", net_error);
+ dict->SetInteger("version_before", version_before);
+ dict->SetInteger("version_after", version_after);
+ return dict;
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+HttpNetworkTransaction::HttpNetworkTransaction(RequestPriority priority,
+ HttpNetworkSession* session)
+ : pending_auth_target_(HttpAuth::AUTH_NONE),
+ io_callback_(base::Bind(&HttpNetworkTransaction::OnIOComplete,
+ base::Unretained(this))),
+ session_(session),
+ request_(NULL),
+ priority_(priority),
+ headers_valid_(false),
+ logged_response_time_(false),
+ request_headers_(),
+ read_buf_len_(0),
+ next_state_(STATE_NONE),
+ establishing_tunnel_(false) {
+ session->ssl_config_service()->GetSSLConfig(&server_ssl_config_);
+ if (session->http_stream_factory()->has_next_protos()) {
+ server_ssl_config_.next_protos =
+ session->http_stream_factory()->next_protos();
+ }
+ proxy_ssl_config_ = server_ssl_config_;
+}
+
+HttpNetworkTransaction::~HttpNetworkTransaction() {
+ if (stream_.get()) {
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ // TODO(mbelshe): The stream_ should be able to compute whether or not the
+ // stream should be kept alive. No reason to compute here
+ // and pass it in.
+ bool try_to_keep_alive =
+ next_state_ == STATE_NONE &&
+ stream_->CanFindEndOfResponse() &&
+ (!headers || headers->IsKeepAlive());
+ if (!try_to_keep_alive) {
+ stream_->Close(true /* not reusable */);
+ } else {
+ if (stream_->IsResponseBodyComplete()) {
+ // If the response body is complete, we can just reuse the socket.
+ stream_->Close(false /* reusable */);
+ } else if (stream_->IsSpdyHttpStream()) {
+ // Doesn't really matter for SpdyHttpStream. Just close it.
+ stream_->Close(true /* not reusable */);
+ } else {
+ // Otherwise, we try to drain the response body.
+ HttpStreamBase* stream = stream_.release();
+ stream->Drain(session_.get());
+ }
+ }
+ }
+}
+
+int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ SIMPLE_STATS_COUNTER("HttpNetworkTransaction.Count");
+
+ net_log_ = net_log;
+ request_ = request_info;
+ start_time_ = base::Time::Now();
+
+ if (request_->load_flags & LOAD_DISABLE_CERT_REVOCATION_CHECKING) {
+ server_ssl_config_.rev_checking_enabled = false;
+ proxy_ssl_config_.rev_checking_enabled = false;
+ }
+
+ // Channel ID is enabled unless --disable-tls-channel-id flag is set,
+ // or if privacy mode is enabled.
+ bool channel_id_enabled = server_ssl_config_.channel_id_enabled &&
+ (request_->privacy_mode == kPrivacyModeDisabled);
+ server_ssl_config_.channel_id_enabled = channel_id_enabled;
+
+ next_state_ = STATE_CREATE_STREAM;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+int HttpNetworkTransaction::RestartIgnoringLastError(
+ const CompletionCallback& callback) {
+ DCHECK(!stream_.get());
+ DCHECK(!stream_request_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ next_state_ = STATE_CREATE_STREAM;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+int HttpNetworkTransaction::RestartWithCertificate(
+ X509Certificate* client_cert, const CompletionCallback& callback) {
+ // In HandleCertificateRequest(), we always tear down existing stream
+ // requests to force a new connection. So we shouldn't have one here.
+ DCHECK(!stream_request_.get());
+ DCHECK(!stream_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ SSLConfig* ssl_config = response_.cert_request_info->is_proxy ?
+ &proxy_ssl_config_ : &server_ssl_config_;
+ ssl_config->send_client_cert = true;
+ ssl_config->client_cert = client_cert;
+ session_->ssl_client_auth_cache()->Add(
+ response_.cert_request_info->host_and_port, client_cert);
+ // Reset the other member variables.
+ // Note: this is necessary only with SSL renegotiation.
+ ResetStateForRestart();
+ next_state_ = STATE_CREATE_STREAM;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+int HttpNetworkTransaction::RestartWithAuth(
+ const AuthCredentials& credentials, const CompletionCallback& callback) {
+ HttpAuth::Target target = pending_auth_target_;
+ if (target == HttpAuth::AUTH_NONE) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ pending_auth_target_ = HttpAuth::AUTH_NONE;
+
+ auth_controllers_[target]->ResetAuth(credentials);
+
+ DCHECK(callback_.is_null());
+
+ int rv = OK;
+ if (target == HttpAuth::AUTH_PROXY && establishing_tunnel_) {
+ // In this case, we've gathered credentials for use with proxy
+ // authentication of a tunnel.
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+ DCHECK(stream_request_ != NULL);
+ auth_controllers_[target] = NULL;
+ ResetStateForRestart();
+ rv = stream_request_->RestartTunnelWithProxyAuth(credentials);
+ } else {
+ // In this case, we've gathered credentials for the server or the proxy
+ // but it is not during the tunneling phase.
+ DCHECK(stream_request_ == NULL);
+ PrepareForAuthRestart(target);
+ rv = DoLoop(OK);
+ }
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
+ DCHECK(HaveAuth(target));
+ DCHECK(!stream_request_.get());
+
+ bool keep_alive = false;
+ // Even if the server says the connection is keep-alive, we have to be
+ // able to find the end of each response in order to reuse the connection.
+ if (GetResponseHeaders()->IsKeepAlive() &&
+ stream_->CanFindEndOfResponse()) {
+ // If the response body hasn't been completely read, we need to drain
+ // it first.
+ if (!stream_->IsResponseBodyComplete()) {
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
+ read_buf_ = new IOBuffer(kDrainBodyBufferSize); // A bit bucket.
+ read_buf_len_ = kDrainBodyBufferSize;
+ return;
+ }
+ keep_alive = true;
+ }
+
+ // We don't need to drain the response body, so we act as if we had drained
+ // the response body.
+ DidDrainBodyForAuthRestart(keep_alive);
+}
+
+void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) {
+ DCHECK(!stream_request_.get());
+
+ if (stream_.get()) {
+ HttpStream* new_stream = NULL;
+ if (keep_alive && stream_->IsConnectionReusable()) {
+ // We should call connection_->set_idle_time(), but this doesn't occur
+ // often enough to be worth the trouble.
+ stream_->SetConnectionReused();
+ new_stream =
+ static_cast<HttpStream*>(stream_.get())->RenewStreamForAuth();
+ }
+
+ if (!new_stream) {
+ // Close the stream and mark it as not_reusable. Even in the
+ // keep_alive case, we've determined that the stream_ is not
+ // reusable if new_stream is NULL.
+ stream_->Close(true);
+ next_state_ = STATE_CREATE_STREAM;
+ } else {
+ next_state_ = STATE_INIT_STREAM;
+ }
+ stream_.reset(new_stream);
+ }
+
+ // Reset the other member variables.
+ ResetStateForAuthRestart();
+}
+
+bool HttpNetworkTransaction::IsReadyToRestartForAuth() {
+ return pending_auth_target_ != HttpAuth::AUTH_NONE &&
+ HaveAuth(pending_auth_target_);
+}
+
+int HttpNetworkTransaction::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_LT(0, buf_len);
+
+ State next_state = STATE_NONE;
+
+ scoped_refptr<HttpResponseHeaders> headers(GetResponseHeaders());
+ if (headers_valid_ && headers.get() && stream_request_.get()) {
+ // We're trying to read the body of the response but we're still trying
+ // to establish an SSL tunnel through an HTTP proxy. We can't read these
+ // bytes when establishing a tunnel because they might be controlled by
+ // an active network attacker. We don't worry about this for HTTP
+ // because an active network attacker can already control HTTP sessions.
+ // We reach this case when the user cancels a 407 proxy auth prompt. We
+ // also don't worry about this for an HTTPS Proxy, because the
+ // communication with the proxy is secure.
+ // See http://crbug.com/8473.
+ DCHECK(proxy_info_.is_http() || proxy_info_.is_https());
+ DCHECK_EQ(headers->response_code(), HTTP_PROXY_AUTHENTICATION_REQUIRED);
+ LOG(WARNING) << "Blocked proxy response with status "
+ << headers->response_code() << " to CONNECT request for "
+ << GetHostAndPort(request_->url) << ".";
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ // Are we using SPDY or HTTP?
+ next_state = STATE_READ_BODY;
+
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+
+ next_state_ = next_state;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ return rv;
+}
+
+bool HttpNetworkTransaction::GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ // TODO(ttuttle): Make sure we've populated request_headers_.
+ *headers = request_headers_;
+ return true;
+}
+
+const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const {
+ return ((headers_valid_ && response_.headers.get()) ||
+ response_.ssl_info.cert.get() || response_.cert_request_info.get())
+ ? &response_
+ : NULL;
+}
+
+LoadState HttpNetworkTransaction::GetLoadState() const {
+ // TODO(wtc): Define a new LoadState value for the
+ // STATE_INIT_CONNECTION_COMPLETE state, which delays the HTTP request.
+ switch (next_state_) {
+ case STATE_CREATE_STREAM_COMPLETE:
+ return stream_request_->GetLoadState();
+ case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
+ case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
+ case STATE_SEND_REQUEST_COMPLETE:
+ return LOAD_STATE_SENDING_REQUEST;
+ case STATE_READ_HEADERS_COMPLETE:
+ return LOAD_STATE_WAITING_FOR_RESPONSE;
+ case STATE_READ_BODY_COMPLETE:
+ return LOAD_STATE_READING_RESPONSE;
+ default:
+ return LOAD_STATE_IDLE;
+ }
+}
+
+UploadProgress HttpNetworkTransaction::GetUploadProgress() const {
+ if (!stream_.get())
+ return UploadProgress();
+
+ // TODO(bashi): This cast is temporary. Remove later.
+ return static_cast<HttpStream*>(stream_.get())->GetUploadProgress();
+}
+
+bool HttpNetworkTransaction::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ if (!stream_ || !stream_->GetLoadTimingInfo(load_timing_info))
+ return false;
+
+ load_timing_info->proxy_resolve_start =
+ proxy_info_.proxy_resolve_start_time();
+ load_timing_info->proxy_resolve_end = proxy_info_.proxy_resolve_end_time();
+ load_timing_info->send_start = send_start_time_;
+ load_timing_info->send_end = send_end_time_;
+ return true;
+}
+
+void HttpNetworkTransaction::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+ if (stream_request_)
+ stream_request_->SetPriority(priority);
+ if (stream_)
+ stream_->SetPriority(priority);
+}
+
+void HttpNetworkTransaction::OnStreamReady(const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) {
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+ DCHECK(stream_request_.get());
+
+ stream_.reset(stream);
+ server_ssl_config_ = used_ssl_config;
+ proxy_info_ = used_proxy_info;
+ response_.was_npn_negotiated = stream_request_->was_npn_negotiated();
+ response_.npn_negotiated_protocol = SSLClientSocket::NextProtoToString(
+ stream_request_->protocol_negotiated());
+ response_.was_fetched_via_spdy = stream_request_->using_spdy();
+ response_.was_fetched_via_proxy = !proxy_info_.is_direct();
+
+ OnIOComplete(OK);
+}
+
+void HttpNetworkTransaction::OnWebSocketStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) {
+ NOTREACHED() << "This function should never be called.";
+}
+
+void HttpNetworkTransaction::OnStreamFailed(int result,
+ const SSLConfig& used_ssl_config) {
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+ DCHECK_NE(OK, result);
+ DCHECK(stream_request_.get());
+ DCHECK(!stream_.get());
+ server_ssl_config_ = used_ssl_config;
+
+ OnIOComplete(result);
+}
+
+void HttpNetworkTransaction::OnCertificateError(
+ int result,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) {
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+ DCHECK_NE(OK, result);
+ DCHECK(stream_request_.get());
+ DCHECK(!stream_.get());
+
+ response_.ssl_info = ssl_info;
+ server_ssl_config_ = used_ssl_config;
+
+ // TODO(mbelshe): For now, we're going to pass the error through, and that
+ // will close the stream_request in all cases. This means that we're always
+ // going to restart an entire STATE_CREATE_STREAM, even if the connection is
+ // good and the user chooses to ignore the error. This is not ideal, but not
+ // the end of the world either.
+
+ OnIOComplete(result);
+}
+
+void HttpNetworkTransaction::OnNeedsProxyAuth(
+ const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) {
+ DCHECK(stream_request_.get());
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+
+ establishing_tunnel_ = true;
+ response_.headers = proxy_response.headers;
+ response_.auth_challenge = proxy_response.auth_challenge;
+ headers_valid_ = true;
+ server_ssl_config_ = used_ssl_config;
+ proxy_info_ = used_proxy_info;
+
+ auth_controllers_[HttpAuth::AUTH_PROXY] = auth_controller;
+ pending_auth_target_ = HttpAuth::AUTH_PROXY;
+
+ DoCallback(OK);
+}
+
+void HttpNetworkTransaction::OnNeedsClientAuth(
+ const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) {
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+
+ server_ssl_config_ = used_ssl_config;
+ response_.cert_request_info = cert_info;
+ OnIOComplete(ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
+}
+
+void HttpNetworkTransaction::OnHttpsProxyTunnelResponse(
+ const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) {
+ DCHECK_EQ(STATE_CREATE_STREAM_COMPLETE, next_state_);
+
+ headers_valid_ = true;
+ response_ = response_info;
+ server_ssl_config_ = used_ssl_config;
+ proxy_info_ = used_proxy_info;
+ stream_.reset(stream);
+ stream_request_.reset(); // we're done with the stream request
+ OnIOComplete(ERR_HTTPS_PROXY_TUNNEL_RESPONSE);
+}
+
+bool HttpNetworkTransaction::is_https_request() const {
+ return request_->url.SchemeIs("https");
+}
+
+void HttpNetworkTransaction::DoCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!callback_.is_null());
+
+ // Since Run may result in Read being called, clear user_callback_ up front.
+ CompletionCallback c = callback_;
+ callback_.Reset();
+ c.Run(rv);
+}
+
+void HttpNetworkTransaction::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int HttpNetworkTransaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CREATE_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoCreateStream();
+ break;
+ case STATE_CREATE_STREAM_COMPLETE:
+ rv = DoCreateStreamComplete(rv);
+ break;
+ case STATE_INIT_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoInitStream();
+ break;
+ case STATE_INIT_STREAM_COMPLETE:
+ rv = DoInitStreamComplete(rv);
+ break;
+ case STATE_GENERATE_PROXY_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateProxyAuthToken();
+ break;
+ case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateProxyAuthTokenComplete(rv);
+ break;
+ case STATE_GENERATE_SERVER_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateServerAuthToken();
+ break;
+ case STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateServerAuthTokenComplete(rv);
+ break;
+ case STATE_INIT_REQUEST_BODY:
+ DCHECK_EQ(OK, rv);
+ rv = DoInitRequestBody();
+ break;
+ case STATE_INIT_REQUEST_BODY_COMPLETE:
+ rv = DoInitRequestBodyComplete(rv);
+ break;
+ case STATE_BUILD_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST);
+ rv = DoBuildRequest();
+ break;
+ case STATE_BUILD_REQUEST_COMPLETE:
+ rv = DoBuildRequestComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, rv);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, rv);
+ break;
+ case STATE_READ_BODY:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_BODY);
+ rv = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ rv = DoReadBodyComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, rv);
+ break;
+ case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART);
+ rv = DoDrainBodyForAuthRestart();
+ break;
+ case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
+ rv = DoDrainBodyForAuthRestartComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_DRAIN_BODY_FOR_AUTH_RESTART, rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpNetworkTransaction::DoCreateStream() {
+ next_state_ = STATE_CREATE_STREAM_COMPLETE;
+
+ stream_request_.reset(
+ session_->http_stream_factory()->RequestStream(
+ *request_,
+ priority_,
+ server_ssl_config_,
+ proxy_ssl_config_,
+ this,
+ net_log_));
+ DCHECK(stream_request_.get());
+ return ERR_IO_PENDING;
+}
+
+int HttpNetworkTransaction::DoCreateStreamComplete(int result) {
+ if (result == OK) {
+ next_state_ = STATE_INIT_STREAM;
+ DCHECK(stream_.get());
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ result = HandleCertificateRequest(result);
+ } else if (result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
+ // Return OK and let the caller read the proxy's error page
+ next_state_ = STATE_NONE;
+ return OK;
+ }
+
+ // Handle possible handshake errors that may have occurred if the stream
+ // used SSL for one or more of the layers.
+ result = HandleSSLHandshakeError(result);
+
+ // At this point we are done with the stream_request_.
+ stream_request_.reset();
+ return result;
+}
+
+int HttpNetworkTransaction::DoInitStream() {
+ DCHECK(stream_.get());
+ next_state_ = STATE_INIT_STREAM_COMPLETE;
+ return stream_->InitializeStream(request_, priority_, net_log_, io_callback_);
+}
+
+int HttpNetworkTransaction::DoInitStreamComplete(int result) {
+ if (result == OK) {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ } else {
+ if (result < 0)
+ result = HandleIOError(result);
+
+ // The stream initialization failed, so this stream will never be useful.
+ stream_.reset();
+ }
+
+ return result;
+}
+
+int HttpNetworkTransaction::DoGenerateProxyAuthToken() {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE;
+ if (!ShouldApplyProxyAuth())
+ return OK;
+ HttpAuth::Target target = HttpAuth::AUTH_PROXY;
+ if (!auth_controllers_[target].get())
+ auth_controllers_[target] =
+ new HttpAuthController(target,
+ AuthURL(target),
+ session_->http_auth_cache(),
+ session_->http_auth_handler_factory());
+ return auth_controllers_[target]->MaybeGenerateAuthToken(request_,
+ io_callback_,
+ net_log_);
+}
+
+int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv == OK)
+ next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN;
+ return rv;
+}
+
+int HttpNetworkTransaction::DoGenerateServerAuthToken() {
+ next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE;
+ HttpAuth::Target target = HttpAuth::AUTH_SERVER;
+ if (!auth_controllers_[target].get())
+ auth_controllers_[target] =
+ new HttpAuthController(target,
+ AuthURL(target),
+ session_->http_auth_cache(),
+ session_->http_auth_handler_factory());
+ if (!ShouldApplyServerAuth())
+ return OK;
+ return auth_controllers_[target]->MaybeGenerateAuthToken(request_,
+ io_callback_,
+ net_log_);
+}
+
+int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ if (rv == OK)
+ next_state_ = STATE_INIT_REQUEST_BODY;
+ return rv;
+}
+
+void HttpNetworkTransaction::BuildRequestHeaders(bool using_proxy) {
+ request_headers_.SetHeader(HttpRequestHeaders::kHost,
+ GetHostAndOptionalPort(request_->url));
+
+ // For compat with HTTP/1.0 servers and proxies:
+ if (using_proxy) {
+ request_headers_.SetHeader(HttpRequestHeaders::kProxyConnection,
+ "keep-alive");
+ } else {
+ request_headers_.SetHeader(HttpRequestHeaders::kConnection, "keep-alive");
+ }
+
+ // Add a content length header?
+ if (request_->upload_data_stream) {
+ if (request_->upload_data_stream->is_chunked()) {
+ request_headers_.SetHeader(
+ HttpRequestHeaders::kTransferEncoding, "chunked");
+ } else {
+ request_headers_.SetHeader(
+ HttpRequestHeaders::kContentLength,
+ base::Uint64ToString(request_->upload_data_stream->size()));
+ }
+ } else if (request_->method == "POST" || request_->method == "PUT" ||
+ request_->method == "HEAD") {
+ // An empty POST/PUT request still needs a content length. As for HEAD,
+ // IE and Safari also add a content length header. Presumably it is to
+ // support sending a HEAD request to an URL that only expects to be sent a
+ // POST or some other method that normally would have a message body.
+ request_headers_.SetHeader(HttpRequestHeaders::kContentLength, "0");
+ }
+
+ // Honor load flags that impact proxy caches.
+ if (request_->load_flags & LOAD_BYPASS_CACHE) {
+ request_headers_.SetHeader(HttpRequestHeaders::kPragma, "no-cache");
+ request_headers_.SetHeader(HttpRequestHeaders::kCacheControl, "no-cache");
+ } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
+ request_headers_.SetHeader(HttpRequestHeaders::kCacheControl, "max-age=0");
+ }
+
+ if (ShouldApplyProxyAuth() && HaveAuth(HttpAuth::AUTH_PROXY))
+ auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader(
+ &request_headers_);
+ if (ShouldApplyServerAuth() && HaveAuth(HttpAuth::AUTH_SERVER))
+ auth_controllers_[HttpAuth::AUTH_SERVER]->AddAuthorizationHeader(
+ &request_headers_);
+
+ request_headers_.MergeFrom(request_->extra_headers);
+ response_.did_use_http_auth =
+ request_headers_.HasHeader(HttpRequestHeaders::kAuthorization) ||
+ request_headers_.HasHeader(HttpRequestHeaders::kProxyAuthorization);
+}
+
+int HttpNetworkTransaction::DoInitRequestBody() {
+ next_state_ = STATE_INIT_REQUEST_BODY_COMPLETE;
+ int rv = OK;
+ if (request_->upload_data_stream)
+ rv = request_->upload_data_stream->Init(io_callback_);
+ return rv;
+}
+
+int HttpNetworkTransaction::DoInitRequestBodyComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_BUILD_REQUEST;
+ return result;
+}
+
+int HttpNetworkTransaction::DoBuildRequest() {
+ next_state_ = STATE_BUILD_REQUEST_COMPLETE;
+ headers_valid_ = false;
+
+ // This is constructed lazily (instead of within our Start method), so that
+ // we have proxy info available.
+ if (request_headers_.IsEmpty()) {
+ bool using_proxy = (proxy_info_.is_http() || proxy_info_.is_https()) &&
+ !is_https_request();
+ BuildRequestHeaders(using_proxy);
+ }
+
+ return OK;
+}
+
+int HttpNetworkTransaction::DoBuildRequestComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return result;
+}
+
+int HttpNetworkTransaction::DoSendRequest() {
+ send_start_time_ = base::TimeTicks::Now();
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ return stream_->SendRequest(request_headers_, &response_, io_callback_);
+}
+
+int HttpNetworkTransaction::DoSendRequestComplete(int result) {
+ send_end_time_ = base::TimeTicks::Now();
+ if (result < 0)
+ return HandleIOError(result);
+ response_.network_accessed = true;
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int HttpNetworkTransaction::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return stream_->ReadResponseHeaders(io_callback_);
+}
+
+int HttpNetworkTransaction::HandleConnectionClosedBeforeEndOfHeaders() {
+ if (!response_.headers.get() && !stream_->IsConnectionReused()) {
+ // The connection was closed before any data was sent. Likely an error
+ // rather than empty HTTP/0.9 response.
+ return ERR_EMPTY_RESPONSE;
+ }
+
+ return OK;
+}
+
+int HttpNetworkTransaction::DoReadHeadersComplete(int result) {
+ // We can get a certificate error or ERR_SSL_CLIENT_AUTH_CERT_NEEDED here
+ // due to SSL renegotiation.
+ if (IsCertificateError(result)) {
+ // We don't handle a certificate error during SSL renegotiation, so we
+ // have to return an error that's not in the certificate error range
+ // (-2xx).
+ LOG(ERROR) << "Got a server certificate with error " << result
+ << " during SSL renegotiation";
+ result = ERR_CERT_ERROR_IN_SSL_RENEGOTIATION;
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ // TODO(wtc): Need a test case for this code path!
+ DCHECK(stream_.get());
+ DCHECK(is_https_request());
+ response_.cert_request_info = new SSLCertRequestInfo;
+ stream_->GetSSLCertRequestInfo(response_.cert_request_info.get());
+ result = HandleCertificateRequest(result);
+ if (result == OK)
+ return result;
+ }
+
+ if (result < 0 && result != ERR_CONNECTION_CLOSED)
+ return HandleIOError(result);
+
+ if (result == ERR_CONNECTION_CLOSED && ShouldResendRequest(result)) {
+ ResetConnectionAndRequestForResend();
+ return OK;
+ }
+
+ // After we call RestartWithAuth a new response_time will be recorded, and
+ // we need to be cautious about incorrectly logging the duration across the
+ // authentication activity.
+ if (result == OK)
+ LogTransactionConnectedMetrics();
+
+ if (result == ERR_CONNECTION_CLOSED) {
+ // For now, if we get at least some data, we do the best we can to make
+ // sense of it and send it back up the stack.
+ int rv = HandleConnectionClosedBeforeEndOfHeaders();
+ if (rv != OK)
+ return rv;
+ }
+ DCHECK(response_.headers.get());
+
+ // Server-induced fallback is supported only if this is a PAC configured
+ // proxy. See: http://crbug.com/143712
+ if (response_.was_fetched_via_proxy && proxy_info_.did_use_pac_script() &&
+ response_.headers.get() != NULL) {
+ bool should_fallback =
+ response_.headers->HasHeaderValue("connection", "proxy-bypass");
+ // Additionally, fallback if a 500 is returned via the data reduction proxy.
+ // This is conservative, as the 500 might have been generated by the origin,
+ // and not the proxy.
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+ if (!should_fallback) {
+ should_fallback =
+ response_.headers->response_code() == HTTP_INTERNAL_SERVER_ERROR &&
+ proxy_info_.proxy_server().host_port_pair().Equals(
+ HostPortPair::FromURL(GURL(SPDY_PROXY_AUTH_ORIGIN)));
+ }
+#endif
+ if (should_fallback) {
+ ProxyService* proxy_service = session_->proxy_service();
+ if (proxy_service->MarkProxyAsBad(proxy_info_, net_log_)) {
+ // Only retry in the case of GETs. We don't want to resubmit a POST
+ // if the proxy took some action.
+ if (request_->method == "GET") {
+ ResetConnectionAndRequestForResend();
+ return OK;
+ }
+ }
+ }
+ }
+
+ // Like Net.HttpResponseCode, but only for MAIN_FRAME loads.
+ if (request_->load_flags & LOAD_MAIN_FRAME) {
+ const int response_code = response_.headers->response_code();
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.HttpResponseCode_Nxx_MainFrame", response_code/100, 10);
+ }
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+ base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));
+
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) {
+ // HTTP/0.9 doesn't support the PUT method, so lack of response headers
+ // indicates a buggy server. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=193921
+ if (request_->method == "PUT")
+ return ERR_METHOD_NOT_SUPPORTED;
+ }
+
+ // Check for an intermediate 100 Continue response. An origin server is
+ // allowed to send this response even if we didn't ask for it, so we just
+ // need to skip over it.
+ // We treat any other 1xx in this same way (although in practice getting
+ // a 1xx that isn't a 100 is rare).
+ if (response_.headers->response_code() / 100 == 1) {
+ response_.headers = new HttpResponseHeaders(std::string());
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+ }
+
+ HostPortPair endpoint = HostPortPair(request_->url.HostNoBrackets(),
+ request_->url.EffectiveIntPort());
+ ProcessAlternateProtocol(session_->http_stream_factory(),
+ session_->http_server_properties(),
+ *response_.headers.get(),
+ endpoint);
+
+ int rv = HandleAuthChallenge();
+ if (rv != OK)
+ return rv;
+
+ if (is_https_request())
+ stream_->GetSSLInfo(&response_.ssl_info);
+
+ headers_valid_ = true;
+ return OK;
+}
+
+int HttpNetworkTransaction::DoReadBody() {
+ DCHECK(read_buf_.get());
+ DCHECK_GT(read_buf_len_, 0);
+ DCHECK(stream_ != NULL);
+
+ next_state_ = STATE_READ_BODY_COMPLETE;
+ return stream_->ReadResponseBody(
+ read_buf_.get(), read_buf_len_, io_callback_);
+}
+
+int HttpNetworkTransaction::DoReadBodyComplete(int result) {
+ // We are done with the Read call.
+ bool done = false;
+ if (result <= 0) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ done = true;
+ }
+
+ bool keep_alive = false;
+ if (stream_->IsResponseBodyComplete()) {
+ // Note: Just because IsResponseBodyComplete is true, we're not
+ // necessarily "done". We're only "done" when it is the last
+ // read on this HttpNetworkTransaction, which will be signified
+ // by a zero-length read.
+ // TODO(mbelshe): The keepalive property is really a property of
+ // the stream. No need to compute it here just to pass back
+ // to the stream's Close function.
+ // TODO(rtenneti): CanFindEndOfResponse should return false if there are no
+ // ResponseHeaders.
+ if (stream_->CanFindEndOfResponse()) {
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ if (headers)
+ keep_alive = headers->IsKeepAlive();
+ }
+ }
+
+ // Clean up connection if we are done.
+ if (done) {
+ LogTransactionMetrics();
+ stream_->Close(!keep_alive);
+ // Note: we don't reset the stream here. We've closed it, but we still
+ // need it around so that callers can call methods such as
+ // GetUploadProgress() and have them be meaningful.
+ // TODO(mbelshe): This means we closed the stream here, and we close it
+ // again in ~HttpNetworkTransaction. Clean that up.
+
+ // The next Read call will return 0 (EOF).
+ }
+
+ // Clear these to avoid leaving around old state.
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+
+ return result;
+}
+
+int HttpNetworkTransaction::DoDrainBodyForAuthRestart() {
+ // This method differs from DoReadBody only in the next_state_. So we just
+ // call DoReadBody and override the next_state_. Perhaps there is a more
+ // elegant way for these two methods to share code.
+ int rv = DoReadBody();
+ DCHECK(next_state_ == STATE_READ_BODY_COMPLETE);
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE;
+ return rv;
+}
+
+// TODO(wtc): This method and the DoReadBodyComplete method are almost
+// the same. Figure out a good way for these two methods to share code.
+int HttpNetworkTransaction::DoDrainBodyForAuthRestartComplete(int result) {
+ // keep_alive defaults to true because the very reason we're draining the
+ // response body is to reuse the connection for auth restart.
+ bool done = false, keep_alive = true;
+ if (result < 0) {
+ // Error or closed connection while reading the socket.
+ done = true;
+ keep_alive = false;
+ } else if (stream_->IsResponseBodyComplete()) {
+ done = true;
+ }
+
+ if (done) {
+ DidDrainBodyForAuthRestart(keep_alive);
+ } else {
+ // Keep draining.
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
+ }
+
+ return OK;
+}
+
+void HttpNetworkTransaction::LogTransactionConnectedMetrics() {
+ if (logged_response_time_)
+ return;
+
+ logged_response_time_ = true;
+
+ base::TimeDelta total_duration = response_.response_time - start_time_;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Transaction_Connected",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
+ 100);
+
+ bool reused_socket = stream_->IsConnectionReused();
+ if (!reused_socket) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Transaction_Connected_New_b",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+
+ // Currently, non-HIGHEST priority requests are frame or sub-frame resource
+ // types. This will change when we also prioritize certain subresources like
+ // css, js, etc.
+ if (priority_ != HIGHEST) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Priority_High_Latency_b",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
+ 100);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Priority_Low_Latency_b",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+}
+
+void HttpNetworkTransaction::LogTransactionMetrics() const {
+ base::TimeDelta duration = base::Time::Now() -
+ response_.request_time;
+ if (60 < duration.InMinutes())
+ return;
+
+ base::TimeDelta total_duration = base::Time::Now() - start_time_;
+
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.Transaction_Latency_b", duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.Transaction_Latency_Total",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10), 100);
+
+ if (!stream_->IsConnectionReused()) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.Transaction_Latency_Total_New_Connection",
+ total_duration, base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10), 100);
+ }
+}
+
+int HttpNetworkTransaction::HandleCertificateRequest(int error) {
+ // There are two paths through which the server can request a certificate
+ // from us. The first is during the initial handshake, the second is
+ // during SSL renegotiation.
+ //
+ // In both cases, we want to close the connection before proceeding.
+ // We do this for two reasons:
+ // First, we don't want to keep the connection to the server hung for a
+ // long time while the user selects a certificate.
+ // Second, even if we did keep the connection open, NSS has a bug where
+ // restarting the handshake for ClientAuth is currently broken.
+ DCHECK_EQ(error, ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
+
+ if (stream_.get()) {
+ // Since we already have a stream, we're being called as part of SSL
+ // renegotiation.
+ DCHECK(!stream_request_.get());
+ stream_->Close(true);
+ stream_.reset();
+ }
+
+ // The server is asking for a client certificate during the initial
+ // handshake.
+ stream_request_.reset();
+
+ // If the user selected one of the certificates in client_certs or declined
+ // to provide one for this server before, use the past decision
+ // automatically.
+ scoped_refptr<X509Certificate> client_cert;
+ bool found_cached_cert = session_->ssl_client_auth_cache()->Lookup(
+ response_.cert_request_info->host_and_port, &client_cert);
+ if (!found_cached_cert)
+ return error;
+
+ // Check that the certificate selected is still a certificate the server
+ // is likely to accept, based on the criteria supplied in the
+ // CertificateRequest message.
+ if (client_cert.get()) {
+ const std::vector<std::string>& cert_authorities =
+ response_.cert_request_info->cert_authorities;
+
+ bool cert_still_valid = cert_authorities.empty() ||
+ client_cert->IsIssuedByEncoded(cert_authorities);
+ if (!cert_still_valid)
+ return error;
+ }
+
+ // TODO(davidben): Add a unit test which covers this path; we need to be
+ // able to send a legitimate certificate and also bypass/clear the
+ // SSL session cache.
+ SSLConfig* ssl_config = response_.cert_request_info->is_proxy ?
+ &proxy_ssl_config_ : &server_ssl_config_;
+ ssl_config->send_client_cert = true;
+ ssl_config->client_cert = client_cert;
+ next_state_ = STATE_CREATE_STREAM;
+ // Reset the other member variables.
+ // Note: this is necessary only with SSL renegotiation.
+ ResetStateForRestart();
+ return OK;
+}
+
+// TODO(rch): This does not correctly handle errors when an SSL proxy is
+// being used, as all of the errors are handled as if they were generated
+// by the endpoint host, request_->url, rather than considering if they were
+// generated by the SSL proxy. http://crbug.com/69329
+int HttpNetworkTransaction::HandleSSLHandshakeError(int error) {
+ DCHECK(request_);
+ if (server_ssl_config_.send_client_cert &&
+ (error == ERR_SSL_PROTOCOL_ERROR || IsClientCertificateError(error))) {
+ session_->ssl_client_auth_cache()->Remove(
+ GetHostAndPort(request_->url));
+ }
+
+ bool should_fallback = false;
+ uint16 version_max = server_ssl_config_.version_max;
+
+ switch (error) {
+ case ERR_SSL_PROTOCOL_ERROR:
+ case ERR_SSL_VERSION_OR_CIPHER_MISMATCH:
+ if (version_max >= SSL_PROTOCOL_VERSION_TLS1 &&
+ version_max > server_ssl_config_.version_min) {
+ // This could be a TLS-intolerant server or a server that chose a
+ // cipher suite defined only for higher protocol versions (such as
+ // an SSL 3.0 server that chose a TLS-only cipher suite). Fall
+ // back to the next lower version and retry.
+ // NOTE: if the SSLClientSocket class doesn't support TLS 1.1,
+ // specifying TLS 1.1 in version_max will result in a TLS 1.0
+ // handshake, so falling back from TLS 1.1 to TLS 1.0 will simply
+ // repeat the TLS 1.0 handshake. To avoid this problem, the default
+ // version_max should match the maximum protocol version supported
+ // by the SSLClientSocket class.
+ version_max--;
+
+ // Fallback to the lower SSL version.
+ // While SSL 3.0 fallback should be eliminated because of security
+ // reasons, there is a high risk of breaking the servers if this is
+ // done in general.
+ // For now SSL 3.0 fallback is disabled for Google servers first,
+ // and will be expanded to other servers after enough experiences
+ // have been gained showing that this experiment works well with
+ // today's Internet.
+ if (version_max > SSL_PROTOCOL_VERSION_SSL3 ||
+ (server_ssl_config_.unrestricted_ssl3_fallback_enabled ||
+ !TransportSecurityState::IsGooglePinnedProperty(
+ request_->url.host(), true /* include SNI */))) {
+ should_fallback = true;
+ }
+ }
+ break;
+ case ERR_SSL_BAD_RECORD_MAC_ALERT:
+ if (version_max >= SSL_PROTOCOL_VERSION_TLS1_1 &&
+ version_max > server_ssl_config_.version_min) {
+ // Some broken SSL devices negotiate TLS 1.0 when sent a TLS 1.1 or
+ // 1.2 ClientHello, but then return a bad_record_mac alert. See
+ // crbug.com/260358. In order to make the fallback as minimal as
+ // possible, this fallback is only triggered for >= TLS 1.1.
+ version_max--;
+ should_fallback = true;
+ }
+ break;
+ }
+
+ if (should_fallback) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SSL_VERSION_FALLBACK,
+ base::Bind(&NetLogSSLVersionFallbackCallback,
+ &request_->url, error, server_ssl_config_.version_max,
+ version_max));
+ server_ssl_config_.version_max = version_max;
+ server_ssl_config_.version_fallback = true;
+ ResetConnectionAndRequestForResend();
+ error = OK;
+ }
+
+ return error;
+}
+
+// This method determines whether it is safe to resend the request after an
+// IO error. It can only be called in response to request header or body
+// write errors or response header read errors. It should not be used in
+// other cases, such as a Connect error.
+int HttpNetworkTransaction::HandleIOError(int error) {
+ // SSL errors may happen at any time during the stream and indicate issues
+ // with the underlying connection. Because the peer may request
+ // renegotiation at any time, check and handle any possible SSL handshake
+ // related errors. In addition to renegotiation, TLS False Start may cause
+ // SSL handshake errors (specifically servers with buggy DEFLATE support)
+ // to be delayed until the first Read on the underlying connection.
+ error = HandleSSLHandshakeError(error);
+
+ switch (error) {
+ // If we try to reuse a connection that the server is in the process of
+ // closing, we may end up successfully writing out our request (or a
+ // portion of our request) only to find a connection error when we try to
+ // read from (or finish writing to) the socket.
+ case ERR_CONNECTION_RESET:
+ case ERR_CONNECTION_CLOSED:
+ case ERR_CONNECTION_ABORTED:
+ // There can be a race between the socket pool checking checking whether a
+ // socket is still connected, receiving the FIN, and sending/reading data
+ // on a reused socket. If we receive the FIN between the connectedness
+ // check and writing/reading from the socket, we may first learn the socket
+ // is disconnected when we get a ERR_SOCKET_NOT_CONNECTED. This will most
+ // likely happen when trying to retrieve its IP address.
+ // See http://crbug.com/105824 for more details.
+ case ERR_SOCKET_NOT_CONNECTED:
+ if (ShouldResendRequest(error)) {
+ net_log_.AddEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error);
+ ResetConnectionAndRequestForResend();
+ error = OK;
+ }
+ break;
+ case ERR_PIPELINE_EVICTION:
+ if (!session_->force_http_pipelining()) {
+ net_log_.AddEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error);
+ ResetConnectionAndRequestForResend();
+ error = OK;
+ }
+ break;
+ case ERR_SPDY_PING_FAILED:
+ case ERR_SPDY_SERVER_REFUSED_STREAM:
+ net_log_.AddEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_RESTART_AFTER_ERROR, error);
+ ResetConnectionAndRequestForResend();
+ error = OK;
+ break;
+ }
+ return error;
+}
+
+void HttpNetworkTransaction::ResetStateForRestart() {
+ ResetStateForAuthRestart();
+ stream_.reset();
+}
+
+void HttpNetworkTransaction::ResetStateForAuthRestart() {
+ send_start_time_ = base::TimeTicks();
+ send_end_time_ = base::TimeTicks();
+
+ pending_auth_target_ = HttpAuth::AUTH_NONE;
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ headers_valid_ = false;
+ request_headers_.Clear();
+ response_ = HttpResponseInfo();
+ establishing_tunnel_ = false;
+}
+
+HttpResponseHeaders* HttpNetworkTransaction::GetResponseHeaders() const {
+ return response_.headers.get();
+}
+
+bool HttpNetworkTransaction::ShouldResendRequest(int error) const {
+ bool connection_is_proven = stream_->IsConnectionReused();
+ bool has_received_headers = GetResponseHeaders() != NULL;
+
+ // NOTE: we resend a request only if we reused a keep-alive connection.
+ // This automatically prevents an infinite resend loop because we'll run
+ // out of the cached keep-alive connections eventually.
+ if (connection_is_proven && !has_received_headers)
+ return true;
+ return false;
+}
+
+void HttpNetworkTransaction::ResetConnectionAndRequestForResend() {
+ if (stream_.get()) {
+ stream_->Close(true);
+ stream_.reset();
+ }
+
+ // We need to clear request_headers_ because it contains the real request
+ // headers, but we may need to resend the CONNECT request first to recreate
+ // the SSL tunnel.
+ request_headers_.Clear();
+ next_state_ = STATE_CREATE_STREAM; // Resend the request.
+}
+
+bool HttpNetworkTransaction::ShouldApplyProxyAuth() const {
+ return !is_https_request() &&
+ (proxy_info_.is_https() || proxy_info_.is_http());
+}
+
+bool HttpNetworkTransaction::ShouldApplyServerAuth() const {
+ return !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA);
+}
+
+int HttpNetworkTransaction::HandleAuthChallenge() {
+ scoped_refptr<HttpResponseHeaders> headers(GetResponseHeaders());
+ DCHECK(headers.get());
+
+ int status = headers->response_code();
+ if (status != HTTP_UNAUTHORIZED &&
+ status != HTTP_PROXY_AUTHENTICATION_REQUIRED)
+ return OK;
+ HttpAuth::Target target = status == HTTP_PROXY_AUTHENTICATION_REQUIRED ?
+ HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+ if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct())
+ return ERR_UNEXPECTED_PROXY_AUTH;
+
+ // This case can trigger when an HTTPS server responds with a "Proxy
+ // authentication required" status code through a non-authenticating
+ // proxy.
+ if (!auth_controllers_[target].get())
+ return ERR_UNEXPECTED_PROXY_AUTH;
+
+ int rv = auth_controllers_[target]->HandleAuthChallenge(
+ headers, (request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA) != 0, false,
+ net_log_);
+ if (auth_controllers_[target]->HaveAuthHandler())
+ pending_auth_target_ = target;
+
+ scoped_refptr<AuthChallengeInfo> auth_info =
+ auth_controllers_[target]->auth_info();
+ if (auth_info.get())
+ response_.auth_challenge = auth_info;
+
+ return rv;
+}
+
+bool HttpNetworkTransaction::HaveAuth(HttpAuth::Target target) const {
+ return auth_controllers_[target].get() &&
+ auth_controllers_[target]->HaveAuth();
+}
+
+GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const {
+ switch (target) {
+ case HttpAuth::AUTH_PROXY: {
+ if (!proxy_info_.proxy_server().is_valid() ||
+ proxy_info_.proxy_server().is_direct()) {
+ return GURL(); // There is no proxy server.
+ }
+ const char* scheme = proxy_info_.is_https() ? "https://" : "http://";
+ return GURL(scheme +
+ proxy_info_.proxy_server().host_port_pair().ToString());
+ }
+ case HttpAuth::AUTH_SERVER:
+ return request_->url;
+ default:
+ return GURL();
+ }
+}
+
+#define STATE_CASE(s) \
+ case s: \
+ description = base::StringPrintf("%s (0x%08X)", #s, s); \
+ break
+
+std::string HttpNetworkTransaction::DescribeState(State state) {
+ std::string description;
+ switch (state) {
+ STATE_CASE(STATE_CREATE_STREAM);
+ STATE_CASE(STATE_CREATE_STREAM_COMPLETE);
+ STATE_CASE(STATE_INIT_REQUEST_BODY);
+ STATE_CASE(STATE_INIT_REQUEST_BODY_COMPLETE);
+ STATE_CASE(STATE_BUILD_REQUEST);
+ STATE_CASE(STATE_BUILD_REQUEST_COMPLETE);
+ STATE_CASE(STATE_SEND_REQUEST);
+ STATE_CASE(STATE_SEND_REQUEST_COMPLETE);
+ STATE_CASE(STATE_READ_HEADERS);
+ STATE_CASE(STATE_READ_HEADERS_COMPLETE);
+ STATE_CASE(STATE_READ_BODY);
+ STATE_CASE(STATE_READ_BODY_COMPLETE);
+ STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART);
+ STATE_CASE(STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE);
+ STATE_CASE(STATE_NONE);
+ default:
+ description = base::StringPrintf("Unknown state 0x%08X (%u)", state,
+ state);
+ break;
+ }
+ return description;
+}
+
+#undef STATE_CASE
+
+} // namespace net
diff --git a/chromium/net/http/http_network_transaction.h b/chromium/net/http/http_network_transaction.h
new file mode 100644
index 00000000000..6228487e615
--- /dev/null
+++ b/chromium/net/http/http_network_transaction.h
@@ -0,0 +1,314 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
+#define NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class HttpAuthController;
+class HttpNetworkSession;
+class HttpStreamBase;
+class HttpStreamRequest;
+class IOBuffer;
+class SpdySession;
+struct HttpRequestInfo;
+
+class NET_EXPORT_PRIVATE HttpNetworkTransaction
+ : public HttpTransaction,
+ public HttpStreamRequest::Delegate {
+ public:
+ HttpNetworkTransaction(RequestPriority priority,
+ HttpNetworkSession* session);
+
+ virtual ~HttpNetworkTransaction();
+
+ // HttpTransaction methods:
+ virtual int Start(const HttpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual int RestartIgnoringLastError(
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int RestartWithCertificate(
+ X509Certificate* client_cert,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool IsReadyToRestartForAuth() OVERRIDE;
+
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void StopCaching() OVERRIDE {}
+ virtual bool GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void DoneReading() OVERRIDE {}
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ // HttpStreamRequest::Delegate methods:
+ virtual void OnStreamReady(const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE;
+ virtual void OnWebSocketStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) OVERRIDE;
+ virtual void OnStreamFailed(int status,
+ const SSLConfig& used_ssl_config) OVERRIDE;
+ virtual void OnCertificateError(int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) OVERRIDE;
+ virtual void OnNeedsProxyAuth(
+ const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) OVERRIDE;
+ virtual void OnNeedsClientAuth(const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) OVERRIDE;
+ virtual void OnHttpsProxyTunnelResponse(const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE;
+
+ private:
+ friend class HttpNetworkTransactionSSLTest;
+
+ FRIEND_TEST_ALL_PREFIXES(HttpNetworkTransactionTest,
+ ResetStateForRestart);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ WindowUpdateReceived);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ WindowUpdateSent);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ WindowUpdateOverflow);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ FlowControlStallResume);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ FlowControlStallResumeAfterSettings);
+ FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionTest,
+ FlowControlNegativeSendWindowSize);
+
+ enum State {
+ STATE_CREATE_STREAM,
+ STATE_CREATE_STREAM_COMPLETE,
+ STATE_INIT_STREAM,
+ STATE_INIT_STREAM_COMPLETE,
+ STATE_GENERATE_PROXY_AUTH_TOKEN,
+ STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE,
+ STATE_GENERATE_SERVER_AUTH_TOKEN,
+ STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE,
+ STATE_INIT_REQUEST_BODY,
+ STATE_INIT_REQUEST_BODY_COMPLETE,
+ STATE_BUILD_REQUEST,
+ STATE_BUILD_REQUEST_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE,
+ STATE_NONE
+ };
+
+ bool is_https_request() const;
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoCreateStream();
+ int DoCreateStreamComplete(int result);
+ int DoInitStream();
+ int DoInitStreamComplete(int result);
+ int DoGenerateProxyAuthToken();
+ int DoGenerateProxyAuthTokenComplete(int result);
+ int DoGenerateServerAuthToken();
+ int DoGenerateServerAuthTokenComplete(int result);
+ int DoInitRequestBody();
+ int DoInitRequestBodyComplete(int result);
+ int DoBuildRequest();
+ int DoBuildRequestComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+ int DoDrainBodyForAuthRestart();
+ int DoDrainBodyForAuthRestartComplete(int result);
+
+ void BuildRequestHeaders(bool using_proxy);
+
+ // Record histogram of time until first byte of header is received.
+ void LogTransactionConnectedMetrics();
+
+ // Record histogram of latency (durations until last byte received).
+ void LogTransactionMetrics() const;
+
+ // Writes a log message to help debugging in the field when we block a proxy
+ // response to a CONNECT request.
+ void LogBlockedTunnelResponse(int response_code) const;
+
+ // Called to handle a client certificate request.
+ int HandleCertificateRequest(int error);
+
+ // Called to possibly recover from an SSL handshake error. Sets next_state_
+ // and returns OK if recovering from the error. Otherwise, the same error
+ // code is returned.
+ int HandleSSLHandshakeError(int error);
+
+ // Called to possibly recover from the given error. Sets next_state_ and
+ // returns OK if recovering from the error. Otherwise, the same error code
+ // is returned.
+ int HandleIOError(int error);
+
+ // Gets the response headers from the HttpStream.
+ HttpResponseHeaders* GetResponseHeaders() const;
+
+ // Called when we reached EOF or got an error. Returns true if we should
+ // resend the request. |error| is OK when we reached EOF.
+ bool ShouldResendRequest(int error) const;
+
+ // Resets the connection and the request headers for resend. Called when
+ // ShouldResendRequest() is true.
+ void ResetConnectionAndRequestForResend();
+
+ // Decides the policy when the connection is closed before the end of headers
+ // has been read. This only applies to reading responses, and not writing
+ // requests.
+ int HandleConnectionClosedBeforeEndOfHeaders();
+
+ // Sets up the state machine to restart the transaction with auth.
+ void PrepareForAuthRestart(HttpAuth::Target target);
+
+ // Called when we don't need to drain the response body or have drained it.
+ // Resets |connection_| unless |keep_alive| is true, then calls
+ // ResetStateForRestart. Sets |next_state_| appropriately.
+ void DidDrainBodyForAuthRestart(bool keep_alive);
+
+ // Resets the members of the transaction so it can be restarted.
+ void ResetStateForRestart();
+
+ // Resets the members of the transaction, except |stream_|, which needs
+ // to be maintained for multi-round auth.
+ void ResetStateForAuthRestart();
+
+ // Returns true if we should try to add a Proxy-Authorization header
+ bool ShouldApplyProxyAuth() const;
+
+ // Returns true if we should try to add an Authorization header.
+ bool ShouldApplyServerAuth() const;
+
+ // Handles HTTP status code 401 or 407.
+ // HandleAuthChallenge() returns a network error code, or OK on success.
+ // May update |pending_auth_target_| or |response_.auth_challenge|.
+ int HandleAuthChallenge();
+
+ // Returns true if we have auth credentials for the given target.
+ bool HaveAuth(HttpAuth::Target target) const;
+
+ // Get the {scheme, host, path, port} for the authentication target
+ GURL AuthURL(HttpAuth::Target target) const;
+
+ // Debug helper.
+ static std::string DescribeState(State state);
+
+ scoped_refptr<HttpAuthController>
+ auth_controllers_[HttpAuth::AUTH_NUM_TARGETS];
+
+ // Whether this transaction is waiting for proxy auth, server auth, or is
+ // not waiting for any auth at all. |pending_auth_target_| is read and
+ // cleared by RestartWithAuth().
+ HttpAuth::Target pending_auth_target_;
+
+ CompletionCallback io_callback_;
+ CompletionCallback callback_;
+
+ scoped_refptr<HttpNetworkSession> session_;
+
+ BoundNetLog net_log_;
+ const HttpRequestInfo* request_;
+ RequestPriority priority_;
+ HttpResponseInfo response_;
+
+ // |proxy_info_| is the ProxyInfo used by the HttpStreamRequest.
+ ProxyInfo proxy_info_;
+
+ scoped_ptr<HttpStreamRequest> stream_request_;
+ scoped_ptr<HttpStreamBase> stream_;
+
+ // True if we've validated the headers that the stream parser has returned.
+ bool headers_valid_;
+
+ // True if we've logged the time of the first response byte. Used to
+ // prevent logging across authentication activity where we see multiple
+ // responses.
+ bool logged_response_time_;
+
+ SSLConfig server_ssl_config_;
+ SSLConfig proxy_ssl_config_;
+
+ HttpRequestHeaders request_headers_;
+
+ // The size in bytes of the buffer we use to drain the response body that
+ // we want to throw away. The response body is typically a small error
+ // page just a few hundred bytes long.
+ static const int kDrainBodyBufferSize = 1024;
+
+ // User buffer and length passed to the Read method.
+ scoped_refptr<IOBuffer> read_buf_;
+ int read_buf_len_;
+
+ // The time the Start method was called.
+ base::Time start_time_;
+
+ // When the transaction started / finished sending the request, including
+ // the body, if present.
+ base::TimeTicks send_start_time_;
+ base::TimeTicks send_end_time_;
+
+ // The next state in the state machine.
+ State next_state_;
+
+ // True when the tunnel is in the process of being established - we can't
+ // read from the socket until the tunnel is done.
+ bool establishing_tunnel_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpNetworkTransaction);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_NETWORK_TRANSACTION_H_
diff --git a/chromium/net/http/http_network_transaction_ssl_unittest.cc b/chromium/net/http/http_network_transaction_ssl_unittest.cc
new file mode 100644
index 00000000000..bb218498961
--- /dev/null
+++ b/chromium/net/http/http_network_transaction_ssl_unittest.cc
@@ -0,0 +1,302 @@
+// Copyright (c) 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.
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/net_util.h"
+#include "net/base/request_priority.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class TLS10SSLConfigService : public SSLConfigService {
+ public:
+ TLS10SSLConfigService() {
+ ssl_config_.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ ssl_config_.version_max = SSL_PROTOCOL_VERSION_TLS1;
+ }
+
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {
+ *config = ssl_config_;
+ }
+
+ private:
+ virtual ~TLS10SSLConfigService() {}
+
+ SSLConfig ssl_config_;
+};
+
+class TLS11SSLConfigService : public SSLConfigService {
+ public:
+ TLS11SSLConfigService() {
+ ssl_config_.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ ssl_config_.version_max = SSL_PROTOCOL_VERSION_TLS1_1;
+ }
+
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {
+ *config = ssl_config_;
+ }
+
+ private:
+ virtual ~TLS11SSLConfigService() {}
+
+ SSLConfig ssl_config_;
+};
+
+} // namespace
+
+class HttpNetworkTransactionSSLTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ssl_config_service_ = new TLS10SSLConfigService;
+ session_params_.ssl_config_service = ssl_config_service_.get();
+
+ auth_handler_factory_.reset(new HttpAuthHandlerMock::Factory());
+ session_params_.http_auth_handler_factory = auth_handler_factory_.get();
+
+ proxy_service_.reset(ProxyService::CreateDirect());
+ session_params_.proxy_service = proxy_service_.get();
+
+ session_params_.client_socket_factory = &mock_socket_factory_;
+ session_params_.host_resolver = &mock_resolver_;
+ session_params_.http_server_properties =
+ http_server_properties_.GetWeakPtr();
+ session_params_.transport_security_state = &transport_security_state_;
+ }
+
+ HttpRequestInfo* GetRequestInfo(const std::string& url) {
+ HttpRequestInfo* request_info = new HttpRequestInfo;
+ request_info->url = GURL(url);
+ request_info->method = "GET";
+ request_info_vector_.push_back(request_info);
+ return request_info;
+ }
+
+ SSLConfig& GetServerSSLConfig(HttpNetworkTransaction* trans) {
+ return trans->server_ssl_config_;
+ }
+
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory_;
+ scoped_ptr<ProxyService> proxy_service_;
+
+ MockClientSocketFactory mock_socket_factory_;
+ MockHostResolver mock_resolver_;
+ HttpServerPropertiesImpl http_server_properties_;
+ TransportSecurityState transport_security_state_;
+ HttpNetworkSession::Params session_params_;
+ ScopedVector<HttpRequestInfo> request_info_vector_;
+};
+
+// Tests that HttpNetworkTransaction does not attempt to
+// fallback to SSL 3.0 when a TLS 1.0 handshake fails and:
+// * the site is pinned to the Google pin list (indicating that
+// it is a Google site);
+// * unrestricted SSL 3.0 fallback is disabled.
+TEST_F(HttpNetworkTransactionSSLTest, SSL3FallbackDisabled_Google) {
+ // |ssl_data1| is for the first handshake (TLS 1.0), which will fail for
+ // protocol reasons (e.g., simulating a version rollback attack).
+ // Because unrestricted SSL 3.0 fallback is disabled, only this simulated
+ // SSL handshake is consumed.
+ SSLSocketDataProvider ssl_data1(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data1);
+ StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // This extra handshake, which should be unconsumed, is provided to ensure
+ // that even if the behaviour being tested here ever breaks (and Google
+ // properties begin SSL 3.0 fallbacks), this test will not crash (and bring
+ // down all of net_unittests), but it will fail gracefully.
+ SSLSocketDataProvider ssl_data2(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data2);
+ StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ scoped_refptr<HttpNetworkSession> session(
+ new HttpNetworkSession(session_params_));
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ SSLConfig& ssl_config = GetServerSSLConfig(trans.get());
+ ssl_config.unrestricted_ssl3_fallback_enabled = false;
+
+ TestCompletionCallback callback;
+ // This will consume only |ssl_data1|. |ssl_data2| will not be consumed.
+ int rv = callback.GetResult(
+ trans->Start(GetRequestInfo("https://www.google.com/"),
+ callback.callback(), BoundNetLog()));
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, rv);
+
+ SocketDataProviderArray<SocketDataProvider>& mock_data =
+ mock_socket_factory_.mock_data();
+ // Confirms that only |ssl_data1| is consumed.
+ EXPECT_EQ(1u, mock_data.next_index());
+
+ // |version_max| never fallbacks to SSLv3 for Google properties.
+ EXPECT_EQ(SSL_PROTOCOL_VERSION_TLS1, ssl_config.version_max);
+ EXPECT_FALSE(ssl_config.version_fallback);
+}
+
+// Tests that HttpNetworkTransaction attempts to fallback to SSL 3.0
+// when a TLS 1.0 handshake fails and:
+// * the site is pinned to the Google pin list (indicating that
+// it is a Google site);
+// * unrestricted SSL 3.0 fallback is enabled.
+TEST_F(HttpNetworkTransactionSSLTest, SSL3FallbackEnabled_Google) {
+ // |ssl_data1| is for the first handshake (TLS 1.0), which will fail
+ // for protocol reasons (e.g., simulating a version rollback attack).
+ SSLSocketDataProvider ssl_data1(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data1);
+ StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // |ssl_data2| contains the handshake result for a SSL 3.0
+ // handshake which will be attempted after the TLS 1.0
+ // handshake fails.
+ SSLSocketDataProvider ssl_data2(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data2);
+ StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ scoped_refptr<HttpNetworkSession> session(
+ new HttpNetworkSession(session_params_));
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ SSLConfig& ssl_config = GetServerSSLConfig(trans.get());
+ ssl_config.unrestricted_ssl3_fallback_enabled = true;
+
+ TestCompletionCallback callback;
+ // This will consume |ssl_data1| and |ssl_data2|.
+ int rv = callback.GetResult(
+ trans->Start(GetRequestInfo("https://www.google.com/"),
+ callback.callback(), BoundNetLog()));
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, rv);
+
+ SocketDataProviderArray<SocketDataProvider>& mock_data =
+ mock_socket_factory_.mock_data();
+ // Confirms that both |ssl_data1| and |ssl_data2| are consumed.
+ EXPECT_EQ(2u, mock_data.next_index());
+
+ // |version_max| fallbacks to SSL 3.0 for Google properties when
+ // |unrestricted_ssl3_fallback_enabled| is true.
+ EXPECT_EQ(SSL_PROTOCOL_VERSION_SSL3, ssl_config.version_max);
+ EXPECT_TRUE(ssl_config.version_fallback);
+}
+
+// Tests that HttpNetworkTransaction attempts to fallback to SSL 3.0
+// when a TLS 1.0 handshake fails and the site is not a Google domain,
+// even if unrestricted SSL 3.0 fallback is disabled.
+// TODO(thaidn): revise the above comment and this test when the
+// SSL 3.0 fallback experiment is applied for non-Google domains.
+TEST_F(HttpNetworkTransactionSSLTest, SSL3FallbackDisabled_Paypal) {
+ // |ssl_data1| is for the first handshake (TLS 1.0), which will fail
+ // for protocol reasons (e.g., simulating a version rollback attack).
+ SSLSocketDataProvider ssl_data1(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data1);
+ StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // |ssl_data2| contains the handshake result for a SSL 3.0
+ // handshake which will be attempted after the TLS 1.0
+ // handshake fails.
+ SSLSocketDataProvider ssl_data2(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data2);
+ StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ scoped_refptr<HttpNetworkSession> session(
+ new HttpNetworkSession(session_params_));
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ SSLConfig& ssl_config = GetServerSSLConfig(trans.get());
+ ssl_config.unrestricted_ssl3_fallback_enabled = false;
+
+ TestCompletionCallback callback;
+ // This will consume |ssl_data1| and |ssl_data2|.
+ int rv = callback.GetResult(
+ trans->Start(GetRequestInfo("https://www.paypal.com/"),
+ callback.callback(), BoundNetLog()));
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, rv);
+
+ SocketDataProviderArray<SocketDataProvider>& mock_data =
+ mock_socket_factory_.mock_data();
+ // Confirms that both |ssl_data1| and |ssl_data2| are consumed.
+ EXPECT_EQ(2u, mock_data.next_index());
+
+ // |version_max| fallbacks to SSL 3.0.
+ EXPECT_EQ(SSL_PROTOCOL_VERSION_SSL3, ssl_config.version_max);
+ EXPECT_TRUE(ssl_config.version_fallback);
+}
+
+// Tests that HttpNetworkTransaction attempts to fallback from
+// TLS 1.1 to TLS 1.0, then from TLS 1.0 to SSL 3.0.
+TEST_F(HttpNetworkTransactionSSLTest, SSLFallback) {
+ ssl_config_service_ = new TLS11SSLConfigService;
+ session_params_.ssl_config_service = ssl_config_service_.get();
+ // |ssl_data1| is for the first handshake (TLS 1.1), which will fail
+ // for protocol reasons (e.g., simulating a version rollback attack).
+ SSLSocketDataProvider ssl_data1(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data1);
+ StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data1);
+
+ // |ssl_data2| contains the handshake result for a TLS 1.0
+ // handshake which will be attempted after the TLS 1.1
+ // handshake fails.
+ SSLSocketDataProvider ssl_data2(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data2);
+ StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data2);
+
+ // |ssl_data3| contains the handshake result for a SSL 3.0
+ // handshake which will be attempted after the TLS 1.0
+ // handshake fails.
+ SSLSocketDataProvider ssl_data3(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory_.AddSSLSocketDataProvider(&ssl_data3);
+ StaticSocketDataProvider data3(NULL, 0, NULL, 0);
+ mock_socket_factory_.AddSocketDataProvider(&data3);
+
+ scoped_refptr<HttpNetworkSession> session(
+ new HttpNetworkSession(session_params_));
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ SSLConfig& ssl_config = GetServerSSLConfig(trans.get());
+ ssl_config.unrestricted_ssl3_fallback_enabled = true;
+
+ TestCompletionCallback callback;
+ // This will consume |ssl_data1|, |ssl_data2| and |ssl_data3|.
+ int rv = callback.GetResult(
+ trans->Start(GetRequestInfo("https://www.paypal.com/"),
+ callback.callback(), BoundNetLog()));
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, rv);
+
+ SocketDataProviderArray<SocketDataProvider>& mock_data =
+ mock_socket_factory_.mock_data();
+ // Confirms that |ssl_data1|, |ssl_data2| and |ssl_data3| are consumed.
+ EXPECT_EQ(3u, mock_data.next_index());
+
+ // |version_max| fallbacks to SSL 3.0.
+ EXPECT_EQ(SSL_PROTOCOL_VERSION_SSL3, ssl_config.version_max);
+ EXPECT_TRUE(ssl_config.version_fallback);
+}
+
+} // namespace net
+
diff --git a/chromium/net/http/http_network_transaction_unittest.cc b/chromium/net/http/http_network_transaction_unittest.cc
new file mode 100644
index 00000000000..d89ab546afd
--- /dev/null
+++ b/chromium/net/http/http_network_transaction_unittest.cc
@@ -0,0 +1,11981 @@
+// Copyright 2013 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/http/http_network_transaction.h"
+
+#include <math.h> // ceil
+#include <stdarg.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_file_util.h"
+#include "net/base/auth.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_digest.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_auth_handler_ntlm.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_session_peer.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/mock_client_socket_pool_manager.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/ssl/ssl_info.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+const base::string16 kBar(ASCIIToUTF16("bar"));
+const base::string16 kBar2(ASCIIToUTF16("bar2"));
+const base::string16 kBar3(ASCIIToUTF16("bar3"));
+const base::string16 kBaz(ASCIIToUTF16("baz"));
+const base::string16 kFirst(ASCIIToUTF16("first"));
+const base::string16 kFoo(ASCIIToUTF16("foo"));
+const base::string16 kFoo2(ASCIIToUTF16("foo2"));
+const base::string16 kFoo3(ASCIIToUTF16("foo3"));
+const base::string16 kFou(ASCIIToUTF16("fou"));
+const base::string16 kSecond(ASCIIToUTF16("second"));
+const base::string16 kTestingNTLM(ASCIIToUTF16("testing-ntlm"));
+const base::string16 kWrongPassword(ASCIIToUTF16("wrongpassword"));
+
+int GetIdleSocketCountInTransportSocketPool(net::HttpNetworkSession* session) {
+ return session->GetTransportSocketPool(
+ net::HttpNetworkSession::NORMAL_SOCKET_POOL)->IdleSocketCount();
+}
+
+int GetIdleSocketCountInSSLSocketPool(net::HttpNetworkSession* session) {
+ return session->GetSSLSocketPool(
+ net::HttpNetworkSession::NORMAL_SOCKET_POOL)->IdleSocketCount();
+}
+
+// Takes in a Value created from a NetLogHttpResponseParameter, and returns
+// a JSONified list of headers as a single string. Uses single quotes instead
+// of double quotes for easier comparison. Returns false on failure.
+bool GetHeaders(base::DictionaryValue* params, std::string* headers) {
+ if (!params)
+ return false;
+ base::ListValue* header_list;
+ if (!params->GetList("headers", &header_list))
+ return false;
+ std::string double_quote_headers;
+ base::JSONWriter::Write(header_list, &double_quote_headers);
+ ReplaceChars(double_quote_headers, "\"", "'", headers);
+ return true;
+}
+
+// Tests LoadTimingInfo in the case a socket is reused and no PAC script is
+// used.
+void TestLoadTimingReused(const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+
+ net::ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ EXPECT_FALSE(load_timing_info.send_start.is_null());
+
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+// Tests LoadTimingInfo in the case a new socket is used and no PAC script is
+// used.
+void TestLoadTimingNotReused(const net::LoadTimingInfo& load_timing_info,
+ int connect_timing_flags) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+
+ net::ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ connect_timing_flags);
+ EXPECT_LE(load_timing_info.connect_timing.connect_end,
+ load_timing_info.send_start);
+
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+// Tests LoadTimingInfo in the case a socket is reused and a PAC script is
+// used.
+void TestLoadTimingReusedWithPac(const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ net::ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+
+ EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_LE(load_timing_info.proxy_resolve_start,
+ load_timing_info.proxy_resolve_end);
+ EXPECT_LE(load_timing_info.proxy_resolve_end,
+ load_timing_info.send_start);
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+// Tests LoadTimingInfo in the case a new socket is used and a PAC script is
+// used.
+void TestLoadTimingNotReusedWithPac(const net::LoadTimingInfo& load_timing_info,
+ int connect_timing_flags) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_LE(load_timing_info.proxy_resolve_start,
+ load_timing_info.proxy_resolve_end);
+ EXPECT_LE(load_timing_info.proxy_resolve_end,
+ load_timing_info.connect_timing.connect_start);
+ net::ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ connect_timing_flags);
+ EXPECT_LE(load_timing_info.connect_timing.connect_end,
+ load_timing_info.send_start);
+
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+
+ // Set at a higher level.
+ EXPECT_TRUE(load_timing_info.request_start_time.is_null());
+ EXPECT_TRUE(load_timing_info.request_start.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+} // namespace
+
+namespace net {
+
+namespace {
+
+HttpNetworkSession* CreateSession(SpdySessionDependencies* session_deps) {
+ return SpdySessionDependencies::SpdyCreateSession(session_deps);
+}
+
+} // namespace
+
+class HttpNetworkTransactionTest
+ : public PlatformTest,
+ public ::testing::WithParamInterface<NextProto> {
+ public:
+ virtual ~HttpNetworkTransactionTest() {
+ // Important to restore the per-pool limit first, since the pool limit must
+ // always be greater than group limit, and the tests reduce both limits.
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_);
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_);
+ }
+
+ protected:
+ HttpNetworkTransactionTest()
+ : spdy_util_(GetParam()),
+ session_deps_(GetParam()),
+ old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL)),
+ old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL)) {
+ }
+
+ struct SimpleGetHelperResult {
+ int rv;
+ std::string status_line;
+ std::string response_data;
+ LoadTimingInfo load_timing_info;
+ };
+
+ virtual void SetUp() {
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ virtual void TearDown() {
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ HttpStreamFactory::set_use_alternate_protocols(false);
+ HttpStreamFactory::SetNextProtos(std::vector<NextProto>());
+ }
+
+ // This is the expected return from a current server advertising SPDY.
+ std::string GetAlternateProtocolHttpHeader() {
+ return
+ std::string("Alternate-Protocol: 443:") +
+ AlternateProtocolToString(AlternateProtocolFromNextProto(GetParam())) +
+ "\r\n\r\n";
+ }
+
+ // Either |write_failure| specifies a write failure or |read_failure|
+ // specifies a read failure when using a reused socket. In either case, the
+ // failure should cause the network transaction to resend the request, and the
+ // other argument should be NULL.
+ void KeepAliveConnectionResendRequestTest(const MockWrite* write_failure,
+ const MockRead* read_failure);
+
+ SimpleGetHelperResult SimpleGetHelperForData(StaticSocketDataProvider* data[],
+ size_t data_count) {
+ SimpleGetHelperResult out;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ for (size_t i = 0; i < data_count; ++i) {
+ session_deps_.socket_factory->AddSocketDataProvider(data[i]);
+ }
+
+ TestCompletionCallback callback;
+
+ EXPECT_TRUE(log.bound().IsLoggingAllEvents());
+ int rv = trans->Start(&request, callback.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ out.rv = callback.WaitForResult();
+
+ // Even in the failure cases that use this function, connections are always
+ // successfully established before the error.
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&out.load_timing_info));
+ TestLoadTimingNotReused(out.load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+
+ if (out.rv != OK)
+ return out;
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ // Can't use ASSERT_* inside helper functions like this, so
+ // return an error.
+ if (response == NULL || response->headers.get() == NULL) {
+ out.rv = ERR_UNEXPECTED;
+ return out;
+ }
+ out.status_line = response->headers->GetStatusLine();
+
+ EXPECT_EQ("127.0.0.1", response->socket_address.host());
+ EXPECT_EQ(80, response->socket_address.port());
+
+ rv = ReadTransaction(trans.get(), &out.response_data);
+ EXPECT_EQ(OK, rv);
+
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+
+ std::string line;
+ EXPECT_TRUE(entries[pos].GetStringValue("line", &line));
+ EXPECT_EQ("GET / HTTP/1.1\r\n", line);
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ std::string value;
+ EXPECT_TRUE(request_headers.GetHeader("Host", &value));
+ EXPECT_EQ("www.google.com", value);
+ EXPECT_TRUE(request_headers.GetHeader("Connection", &value));
+ EXPECT_EQ("keep-alive", value);
+
+ std::string response_headers;
+ EXPECT_TRUE(GetHeaders(entries[pos].params.get(), &response_headers));
+ EXPECT_EQ("['Host: www.google.com','Connection: keep-alive']",
+ response_headers);
+
+ return out;
+ }
+
+ SimpleGetHelperResult SimpleGetHelper(MockRead data_reads[],
+ size_t reads_count) {
+ StaticSocketDataProvider reads(data_reads, reads_count, NULL, 0);
+ StaticSocketDataProvider* data[] = { &reads };
+ return SimpleGetHelperForData(data, 1);
+ }
+
+ void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+ int expected_status);
+
+ void ConnectStatusHelper(const MockRead& status);
+
+ void BypassHostCacheOnRefreshHelper(int load_flags);
+
+ void CheckErrorIsPassedBack(int error, IoMode mode);
+
+ SpdyTestUtil spdy_util_;
+ SpdySessionDependencies session_deps_;
+
+ // Original socket limits. Some tests set these. Safest to always restore
+ // them once each test has been run.
+ int old_max_group_sockets_;
+ int old_max_pool_sockets_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ HttpNetworkTransactionTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+namespace {
+
+// Fill |str| with a long header list that consumes >= |size| bytes.
+void FillLargeHeadersString(std::string* str, int size) {
+ const char* row =
+ "SomeHeaderName: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n";
+ const int sizeof_row = strlen(row);
+ const int num_rows = static_cast<int>(
+ ceil(static_cast<float>(size) / sizeof_row));
+ const int sizeof_data = num_rows * sizeof_row;
+ DCHECK(sizeof_data >= size);
+ str->reserve(sizeof_data);
+
+ for (int i = 0; i < num_rows; ++i)
+ str->append(row, sizeof_row);
+}
+
+// Alternative functions that eliminate randomness and dependency on the local
+// host name so that the generated NTLM messages are reproducible.
+void MockGenerateRandom1(uint8* output, size_t n) {
+ static const uint8 bytes[] = {
+ 0x55, 0x29, 0x66, 0x26, 0x6b, 0x9c, 0x73, 0x54
+ };
+ static size_t current_byte = 0;
+ for (size_t i = 0; i < n; ++i) {
+ output[i] = bytes[current_byte++];
+ current_byte %= arraysize(bytes);
+ }
+}
+
+void MockGenerateRandom2(uint8* output, size_t n) {
+ static const uint8 bytes[] = {
+ 0x96, 0x79, 0x85, 0xe7, 0x49, 0x93, 0x70, 0xa1,
+ 0x4e, 0xe7, 0x87, 0x45, 0x31, 0x5b, 0xd3, 0x1f
+ };
+ static size_t current_byte = 0;
+ for (size_t i = 0; i < n; ++i) {
+ output[i] = bytes[current_byte++];
+ current_byte %= arraysize(bytes);
+ }
+}
+
+std::string MockGetHostName() {
+ return "WTC-WIN7";
+}
+
+template<typename ParentPool>
+class CaptureGroupNameSocketPool : public ParentPool {
+ public:
+ CaptureGroupNameSocketPool(HostResolver* host_resolver,
+ CertVerifier* cert_verifier);
+
+ const std::string last_group_name_received() const {
+ return last_group_name_;
+ }
+
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ last_group_name_ = group_name;
+ return ERR_IO_PENDING;
+ }
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {}
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {}
+ virtual void CloseIdleSockets() {}
+ virtual int IdleSocketCount() const {
+ return 0;
+ }
+ virtual int IdleSocketCountInGroup(const std::string& group_name) const {
+ return 0;
+ }
+ virtual LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const {
+ return LOAD_STATE_IDLE;
+ }
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return base::TimeDelta();
+ }
+
+ private:
+ std::string last_group_name_;
+};
+
+typedef CaptureGroupNameSocketPool<TransportClientSocketPool>
+CaptureGroupNameTransportSocketPool;
+typedef CaptureGroupNameSocketPool<HttpProxyClientSocketPool>
+CaptureGroupNameHttpProxySocketPool;
+typedef CaptureGroupNameSocketPool<SOCKSClientSocketPool>
+CaptureGroupNameSOCKSSocketPool;
+typedef CaptureGroupNameSocketPool<SSLClientSocketPool>
+CaptureGroupNameSSLSocketPool;
+
+template<typename ParentPool>
+CaptureGroupNameSocketPool<ParentPool>::CaptureGroupNameSocketPool(
+ HostResolver* host_resolver,
+ CertVerifier* /* cert_verifier */)
+ : ParentPool(0, 0, NULL, host_resolver, NULL, NULL) {}
+
+template<>
+CaptureGroupNameHttpProxySocketPool::CaptureGroupNameSocketPool(
+ HostResolver* host_resolver,
+ CertVerifier* /* cert_verifier */)
+ : HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL) {}
+
+template <>
+CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool(
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier)
+ : SSLClientSocketPool(0,
+ 0,
+ NULL,
+ host_resolver,
+ cert_verifier,
+ NULL,
+ NULL,
+ std::string(),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL) {}
+
+//-----------------------------------------------------------------------------
+
+// Helper functions for validating that AuthChallengeInfo's are correctly
+// configured for common cases.
+bool CheckBasicServerAuth(const AuthChallengeInfo* auth_challenge) {
+ if (!auth_challenge)
+ return false;
+ EXPECT_FALSE(auth_challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString());
+ EXPECT_EQ("MyRealm1", auth_challenge->realm);
+ EXPECT_EQ("basic", auth_challenge->scheme);
+ return true;
+}
+
+bool CheckBasicProxyAuth(const AuthChallengeInfo* auth_challenge) {
+ if (!auth_challenge)
+ return false;
+ EXPECT_TRUE(auth_challenge->is_proxy);
+ EXPECT_EQ("myproxy:70", auth_challenge->challenger.ToString());
+ EXPECT_EQ("MyRealm1", auth_challenge->realm);
+ EXPECT_EQ("basic", auth_challenge->scheme);
+ return true;
+}
+
+bool CheckDigestServerAuth(const AuthChallengeInfo* auth_challenge) {
+ if (!auth_challenge)
+ return false;
+ EXPECT_FALSE(auth_challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80", auth_challenge->challenger.ToString());
+ EXPECT_EQ("digestive", auth_challenge->realm);
+ EXPECT_EQ("digest", auth_challenge->scheme);
+ return true;
+}
+
+bool CheckNTLMServerAuth(const AuthChallengeInfo* auth_challenge) {
+ if (!auth_challenge)
+ return false;
+ EXPECT_FALSE(auth_challenge->is_proxy);
+ EXPECT_EQ("172.22.68.17:80", auth_challenge->challenger.ToString());
+ EXPECT_EQ(std::string(), auth_challenge->realm);
+ EXPECT_EQ("ntlm", auth_challenge->scheme);
+ return true;
+}
+
+} // namespace
+
+TEST_P(HttpNetworkTransactionTest, Basic) {
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+}
+
+TEST_P(HttpNetworkTransactionTest, SimpleGET) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
+ EXPECT_EQ("hello world", out.response_data);
+}
+
+// Response with no status line.
+TEST_P(HttpNetworkTransactionTest, SimpleGETNoHeaders) {
+ MockRead data_reads[] = {
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
+ EXPECT_EQ("hello world", out.response_data);
+}
+
+// Allow up to 4 bytes of junk to precede status line.
+TEST_P(HttpNetworkTransactionTest, StatusLineJunk3Bytes) {
+ MockRead data_reads[] = {
+ MockRead("xxxHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
+ EXPECT_EQ("DATA", out.response_data);
+}
+
+// Allow up to 4 bytes of junk to precede status line.
+TEST_P(HttpNetworkTransactionTest, StatusLineJunk4Bytes) {
+ MockRead data_reads[] = {
+ MockRead("\n\nQJHTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
+ EXPECT_EQ("DATA", out.response_data);
+}
+
+// Beyond 4 bytes of slop and it should fail to find a status line.
+TEST_P(HttpNetworkTransactionTest, StatusLineJunk5Bytes) {
+ MockRead data_reads[] = {
+ MockRead("xxxxxHTTP/1.1 404 Not Found\nServer: blah"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
+ EXPECT_EQ("xxxxxHTTP/1.1 404 Not Found\nServer: blah", out.response_data);
+}
+
+// Same as StatusLineJunk4Bytes, except the read chunks are smaller.
+TEST_P(HttpNetworkTransactionTest, StatusLineJunk4Bytes_Slow) {
+ MockRead data_reads[] = {
+ MockRead("\n"),
+ MockRead("\n"),
+ MockRead("Q"),
+ MockRead("J"),
+ MockRead("HTTP/1.0 404 Not Found\nServer: blah\n\nDATA"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 404 Not Found", out.status_line);
+ EXPECT_EQ("DATA", out.response_data);
+}
+
+// Close the connection before enough bytes to have a status line.
+TEST_P(HttpNetworkTransactionTest, StatusLinePartial) {
+ MockRead data_reads[] = {
+ MockRead("HTT"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/0.9 200 OK", out.status_line);
+ EXPECT_EQ("HTT", out.response_data);
+}
+
+// Simulate a 204 response, lacking a Content-Length header, sent over a
+// persistent connection. The response should still terminate since a 204
+// cannot have a response body.
+TEST_P(HttpNetworkTransactionTest, StopsReading204) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 204 No Content\r\n\r\n"),
+ MockRead("junk"), // Should not be read!!
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 204 No Content", out.status_line);
+ EXPECT_EQ("", out.response_data);
+}
+
+// A simple request using chunked encoding with some extra data after.
+// (Like might be seen in a pipelined response.)
+TEST_P(HttpNetworkTransactionTest, ChunkedEncoding) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"),
+ MockRead("5\r\nHello\r\n"),
+ MockRead("1\r\n"),
+ MockRead(" \r\n"),
+ MockRead("5\r\nworld\r\n"),
+ MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello world", out.response_data);
+}
+
+// Next tests deal with http://crbug.com/56344.
+
+TEST_P(HttpNetworkTransactionTest,
+ MultipleContentLengthHeadersNoTransferEncoding) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 10\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv);
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ DuplicateContentLengthHeadersNoTransferEncoding) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 5\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello", out.response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ ComplexContentLengthHeadersNoTransferEncoding) {
+ // More than 2 dupes.
+ {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 5\r\n"),
+ MockRead("Content-Length: 5\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello", out.response_data);
+ }
+ // HTTP/1.0
+ {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 5\r\n"),
+ MockRead("Content-Length: 5\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
+ EXPECT_EQ("Hello", out.response_data);
+ }
+ // 2 dupes and one mismatched.
+ {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 10\r\n"),
+ MockRead("Content-Length: 10\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, out.rv);
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ MultipleContentLengthHeadersTransferEncoding) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 666\r\n"),
+ MockRead("Content-Length: 1337\r\n"),
+ MockRead("Transfer-Encoding: chunked\r\n\r\n"),
+ MockRead("5\r\nHello\r\n"),
+ MockRead("1\r\n"),
+ MockRead(" \r\n"),
+ MockRead("5\r\nworld\r\n"),
+ MockRead("0\r\n\r\nHTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello world", out.response_data);
+}
+
+// Next tests deal with http://crbug.com/98895.
+
+// Checks that a single Content-Disposition header results in no error.
+TEST_P(HttpNetworkTransactionTest, SingleContentDispositionHeader) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Disposition: attachment;filename=\"salutations.txt\"r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello", out.response_data);
+}
+
+// Checks that two identical Content-Disposition headers result in no error.
+TEST_P(HttpNetworkTransactionTest,
+ TwoIdenticalContentDispositionHeaders) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
+ MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("Hello", out.response_data);
+}
+
+// Checks that two distinct Content-Disposition headers result in an error.
+TEST_P(HttpNetworkTransactionTest, TwoDistinctContentDispositionHeaders) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Disposition: attachment;filename=\"greetings.txt\"r\n"),
+ MockRead("Content-Disposition: attachment;filename=\"hi.txt\"r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, out.rv);
+}
+
+// Checks that two identical Location headers result in no error.
+// Also tests Location header behavior.
+TEST_P(HttpNetworkTransactionTest, TwoIdenticalLocationHeaders) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 302 Redirect\r\n"),
+ MockRead("Location: http://good.com/\r\n"),
+ MockRead("Location: http://good.com/\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://redirect.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL && response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 302 Redirect", response->headers->GetStatusLine());
+ std::string url;
+ EXPECT_TRUE(response->headers->IsRedirect(&url));
+ EXPECT_EQ("http://good.com/", url);
+}
+
+// Checks that two distinct Location headers result in an error.
+TEST_P(HttpNetworkTransactionTest, TwoDistinctLocationHeaders) {
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 302 Redirect\r\n"),
+ MockRead("Location: http://good.com/\r\n"),
+ MockRead("Location: http://evil.com/\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION, out.rv);
+}
+
+// Do a request using the HEAD method. Verify that we don't try to read the
+// message body (since HEAD has none).
+TEST_P(HttpNetworkTransactionTest, Head) {
+ HttpRequestInfo request;
+ request.method = "HEAD";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes1[] = {
+ MockWrite("HEAD / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ };
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 404 Not Found\r\n"),
+ MockRead("Server: Blah\r\n"),
+ MockRead("Content-Length: 1234\r\n\r\n"),
+
+ // No response body because the test stops reading here.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ // Check that the headers got parsed.
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ(1234, response->headers->GetContentLength());
+ EXPECT_EQ("HTTP/1.1 404 Not Found", response->headers->GetStatusLine());
+
+ std::string server_header;
+ void* iter = NULL;
+ bool has_server_header = response->headers->EnumerateHeader(
+ &iter, "Server", &server_header);
+ EXPECT_TRUE(has_server_header);
+ EXPECT_EQ("Blah", server_header);
+
+ // Reading should give EOF right away, since there is no message body
+ // (despite non-zero content-length).
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, ReuseConnection) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ const char* const kExpectedResponseData[] = {
+ "hello", "world"
+ };
+
+ for (int i = 0; i < 2; ++i) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(kExpectedResponseData[i], response_data);
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest, Ignores100) {
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(new UploadBytesElementReader("foo", 3));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.foo.com/");
+ request.upload_data_stream = &upload_data_stream;
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 100 Continue\r\n\r\n"),
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+}
+
+// This test is almost the same as Ignores100 above, but the response contains
+// a 102 instead of a 100. Also, instead of HTTP/1.0 the response is
+// HTTP/1.1 and the two status headers are read in one read.
+TEST_P(HttpNetworkTransactionTest, Ignores1xx) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 102 Unspecified status code\r\n\r\n"
+ "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, Incomplete100ThenEOF) {
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"),
+ MockRead(ASYNC, 0),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, EmptyResponse) {
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, 0),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, rv);
+}
+
+void HttpNetworkTransactionTest::KeepAliveConnectionResendRequestTest(
+ const MockWrite* write_failure,
+ const MockRead* read_failure) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Written data for successfully sending both requests.
+ MockWrite data1_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.foo.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.foo.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ // Read results for the first request.
+ MockRead data1_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ MockRead(ASYNC, OK),
+ };
+
+ if (write_failure) {
+ ASSERT_TRUE(!read_failure);
+ data1_writes[1] = *write_failure;
+ } else {
+ ASSERT_TRUE(read_failure);
+ data1_reads[2] = *read_failure;
+ }
+
+ StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads),
+ data1_writes, arraysize(data1_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ MockRead data2_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ const char* kExpectedResponseData[] = {
+ "hello", "world"
+ };
+
+ uint32 first_socket_log_id = NetLog::Source::kInvalidId;
+ for (int i = 0; i < 2; ++i) {
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ if (i == 0) {
+ first_socket_log_id = load_timing_info.socket_log_id;
+ } else {
+ // The second request should be using a new socket.
+ EXPECT_NE(first_socket_log_id, load_timing_info.socket_log_id);
+ }
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(kExpectedResponseData[i], response_data);
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ KeepAliveConnectionNotConnectedOnWrite) {
+ MockWrite write_failure(ASYNC, ERR_SOCKET_NOT_CONNECTED);
+ KeepAliveConnectionResendRequestTest(&write_failure, NULL);
+}
+
+TEST_P(HttpNetworkTransactionTest, KeepAliveConnectionReset) {
+ MockRead read_failure(ASYNC, ERR_CONNECTION_RESET);
+ KeepAliveConnectionResendRequestTest(NULL, &read_failure);
+}
+
+TEST_P(HttpNetworkTransactionTest, KeepAliveConnectionEOF) {
+ MockRead read_failure(SYNCHRONOUS, OK); // EOF
+ KeepAliveConnectionResendRequestTest(NULL, &read_failure);
+}
+
+TEST_P(HttpNetworkTransactionTest, NonKeepAliveConnectionReset) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, ERR_CONNECTION_RESET),
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response == NULL);
+}
+
+// What do various browsers do when the server closes a non-keepalive
+// connection without sending any response header or body?
+//
+// IE7: error page
+// Safari 3.1.2 (Windows): error page
+// Firefox 3.0.1: blank page
+// Opera 9.52: after five attempts, blank page
+// Us with WinHTTP: error page (ERR_INVALID_RESPONSE)
+// Us: error page (EMPTY_RESPONSE)
+TEST_P(HttpNetworkTransactionTest, NonKeepAliveConnectionEOF) {
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, OK), // EOF
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"), // Should not be used
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ SimpleGetHelperResult out = SimpleGetHelper(data_reads,
+ arraysize(data_reads));
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv);
+}
+
+// Next 2 cases (KeepAliveEarlyClose and KeepAliveEarlyClose2) are regression
+// tests. There was a bug causing HttpNetworkTransaction to hang in the
+// destructor in such situations.
+// See http://crbug.com/154712 and http://crbug.com/156609.
+TEST_P(HttpNetworkTransactionTest, KeepAliveEarlyClose) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Connection: keep-alive\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead("hello"),
+ MockRead(SYNCHRONOUS, 0),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
+ rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(5, rv);
+ rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
+ EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
+
+ trans.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+}
+
+TEST_P(HttpNetworkTransactionTest, KeepAliveEarlyClose2) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Connection: keep-alive\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 0),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(100));
+ rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
+
+ trans.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+}
+
+// Test that we correctly reuse a keep-alive connection after not explicitly
+// reading the body.
+TEST_P(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Note that because all these reads happen in the same
+ // StaticSocketDataProvider, it shows that the same socket is being reused for
+ // all transactions.
+ MockRead data1_reads[] = {
+ MockRead("HTTP/1.1 204 No Content\r\n\r\n"),
+ MockRead("HTTP/1.1 205 Reset Content\r\n\r\n"),
+ MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"),
+ MockRead("HTTP/1.1 302 Found\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ MockRead("HTTP/1.1 302 Found\r\n"
+ "Content-Length: 5\r\n\r\n"
+ "hello"),
+ MockRead("HTTP/1.1 301 Moved Permanently\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ MockRead("HTTP/1.1 301 Moved Permanently\r\n"
+ "Content-Length: 5\r\n\r\n"
+ "hello"),
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ };
+ StaticSocketDataProvider data1(data1_reads, arraysize(data1_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ MockRead data2_reads[] = {
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+ StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ const int kNumUnreadBodies = arraysize(data1_reads) - 2;
+ std::string response_lines[kNumUnreadBodies];
+
+ uint32 first_socket_log_id = NetLog::Source::kInvalidId;
+ for (size_t i = 0; i < arraysize(data1_reads) - 2; ++i) {
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ if (i == 0) {
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ first_socket_log_id = load_timing_info.socket_log_id;
+ } else {
+ TestLoadTimingReused(load_timing_info);
+ EXPECT_EQ(first_socket_log_id, load_timing_info.socket_log_id);
+ }
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ ASSERT_TRUE(response->headers.get() != NULL);
+ response_lines[i] = response->headers->GetStatusLine();
+
+ // We intentionally don't read the response bodies.
+ }
+
+ const char* const kStatusLines[] = {
+ "HTTP/1.1 204 No Content",
+ "HTTP/1.1 205 Reset Content",
+ "HTTP/1.1 304 Not Modified",
+ "HTTP/1.1 302 Found",
+ "HTTP/1.1 302 Found",
+ "HTTP/1.1 301 Moved Permanently",
+ "HTTP/1.1 301 Moved Permanently",
+ };
+
+ COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines),
+ forgot_to_update_kStatusLines);
+
+ for (int i = 0; i < kNumUnreadBodies; ++i)
+ EXPECT_EQ(kStatusLines[i], response_lines[i]);
+
+ TestCompletionCallback callback;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello", response_data);
+}
+
+// Test the request-challenge-retry sequence for basic auth.
+// (basic auth is the easiest to mock, because it has no randomness).
+TEST_P(HttpNetworkTransactionTest, BasicAuth) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ CapturingNetLog log;
+ session_deps_.net_log = &log;
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ // Give a couple authenticate options (only the middle one is actually
+ // supported).
+ MockRead("WWW-Authenticate: Basic invalid\r\n"), // Malformed.
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info1));
+ TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_DNS_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingNotReused(load_timing_info2, CONNECT_TIMING_HAS_DNS_TIMES);
+ // The load timing after restart should have a new socket ID, and times after
+ // those of the first load timing.
+ EXPECT_LE(load_timing_info1.receive_headers_end,
+ load_timing_info2.connect_timing.connect_start);
+ EXPECT_NE(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+TEST_P(HttpNetworkTransactionTest, DoNotSendAuth) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection.
+TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAlive) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ CapturingNetLog log;
+ session_deps_.net_log = &log;
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 14\r\n\r\n"),
+ MockRead("Unauthorized\r\n"),
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("Hello"),
+ };
+
+ // If there is a regression where we disconnect a Keep-Alive
+ // connection during an auth roundtrip, we'll end up reading this.
+ MockRead data_reads2[] = {
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info1));
+ TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_DNS_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingReused(load_timing_info2);
+ // The load timing after restart should have the same socket ID, and times
+ // those of the first load timing.
+ EXPECT_LE(load_timing_info1.receive_headers_end,
+ load_timing_info2.send_start);
+ EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(5, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection and with no response body to drain.
+TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"), // No response body.
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ };
+
+ // An incorrect reconnect would cause this to be read.
+ MockRead data_reads2[] = {
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(5, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection and with a large response body to drain.
+TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Respond with 5 kb of response body.
+ std::string large_body_string("Unauthorized");
+ large_body_string.append(5 * 1024, ' ');
+ large_body_string.append("\r\n");
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // 5134 = 12 + 5 * 1024 + 2
+ MockRead("Content-Length: 5134\r\n\r\n"),
+ MockRead(ASYNC, large_body_string.data(), large_body_string.size()),
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ };
+
+ // An incorrect reconnect would cause this to be read.
+ MockRead data_reads2[] = {
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(5, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection, but the server gets impatient and closes the connection.
+TEST_P(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ // This simulates the seemingly successful write to a closed connection
+ // if the bug is not fixed.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 14\r\n\r\n"),
+ // Tell MockTCPClientSocket to simulate the server closing the connection.
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead("Unauthorized\r\n"),
+ MockRead(SYNCHRONOUS, OK), // The server closes the connection.
+ };
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead("hello"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(5, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a connection
+// that requires a restart when setting up an SSL tunnel.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ // when the no authentication data flag is set.
+ request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ // No credentials.
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Connection: close\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 5\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "hello"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ LoadTimingInfo load_timing_info;
+ // CONNECT requests and responses are handled at the connect job level, so
+ // the transaction does not yet have a connection.
+ EXPECT_FALSE(trans->GetLoadTimingInfo(&load_timing_info));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(5, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should not be set.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ trans.reset();
+ session->CloseAllConnections();
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// proxy connection, when setting up an SSL tunnel.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ // Ensure that proxy authentication is attempted even
+ // when the no authentication data flag is set.
+ request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ // No credentials.
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+
+ // Wrong credentials (wrong password).
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ // No response body because the test stops reading here.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ // Wrong password (should be "bar").
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBaz), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ // Flush the idle socket before the NetLog and HttpNetworkTransaction go
+ // out of scope.
+ session->CloseAllConnections();
+}
+
+// Test that we don't read the response body when we fail to establish a tunnel,
+// even if the user cancels the proxy's auth attempt.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyCancelTunnel) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ // Flush the idle socket before the HttpNetworkTransaction goes out of scope.
+ session->CloseAllConnections();
+}
+
+// Test when a server (non-proxy) returns a 407 (proxy-authenticate).
+// The request should fail with ERR_UNEXPECTED_PROXY_AUTH.
+TEST_P(HttpNetworkTransactionTest, UnexpectedProxyAuth) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // We are using a DIRECT connection (i.e. no proxy) for this session.
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 407 Proxy Auth required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv);
+}
+
+// Tests when an HTTPS server (non-proxy) returns a 407 (proxy-authentication)
+// through a non-authenticating proxy. The request should fail with
+// ERR_UNEXPECTED_PROXY_AUTH.
+// Note that it is impossible to detect if an HTTP server returns a 407 through
+// a non-authenticating proxy - there is nothing to indicate whether the
+// response came from the proxy or the server, so it is treated as if the proxy
+// issued the challenge.
+TEST_P(HttpNetworkTransactionTest,
+ HttpsServerRequestsProxyAuthThroughProxy) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+
+ MockRead("HTTP/1.1 407 Unauthorized\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(ERR_UNEXPECTED_PROXY_AUTH, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+}
+
+// Test the load timing for HTTPS requests with an HTTP proxy.
+TEST_P(HttpNetworkTransactionTest, HttpProxyLoadTimingNoPacTwoRequests) {
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/1");
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.google.com/2");
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET /1 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET /2 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 1\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "1"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 2\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "22"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+ scoped_ptr<HttpTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans1->Start(&request1, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ ASSERT_TRUE(response1 != NULL);
+ ASSERT_TRUE(response1->headers.get() != NULL);
+ EXPECT_EQ(1, response1->headers->GetContentLength());
+
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(trans1->GetLoadTimingInfo(&load_timing_info1));
+ TestLoadTimingNotReused(load_timing_info1, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ trans1.reset();
+
+ TestCompletionCallback callback2;
+ scoped_ptr<HttpTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans2->Start(&request2, callback2.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ ASSERT_TRUE(response2 != NULL);
+ ASSERT_TRUE(response2->headers.get() != NULL);
+ EXPECT_EQ(2, response2->headers->GetContentLength());
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingReused(load_timing_info2);
+
+ EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+
+ trans2.reset();
+ session->CloseAllConnections();
+}
+
+// Test the load timing for HTTPS requests with an HTTP proxy and a PAC script.
+TEST_P(HttpNetworkTransactionTest, HttpProxyLoadTimingWithPacTwoRequests) {
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/1");
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.google.com/2");
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET /1 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET /2 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 1\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "1"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 2\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "22"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+ scoped_ptr<HttpTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans1->Start(&request1, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ ASSERT_TRUE(response1 != NULL);
+ ASSERT_TRUE(response1->headers.get() != NULL);
+ EXPECT_EQ(1, response1->headers->GetContentLength());
+
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(trans1->GetLoadTimingInfo(&load_timing_info1));
+ TestLoadTimingNotReusedWithPac(load_timing_info1,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ trans1.reset();
+
+ TestCompletionCallback callback2;
+ scoped_ptr<HttpTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans2->Start(&request2, callback2.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ ASSERT_TRUE(response2 != NULL);
+ ASSERT_TRUE(response2->headers.get() != NULL);
+ EXPECT_EQ(2, response2->headers->GetContentLength());
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingReusedWithPac(load_timing_info2);
+
+ EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+
+ trans2.reset();
+ session->CloseAllConnections();
+}
+
+// Test a simple get through an HTTPS Proxy.
+TEST_P(HttpNetworkTransactionTest, HttpsProxyGet) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should use full url
+ MockWrite data_writes1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should not be set.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+// Test a SPDY get through an HTTPS Proxy.
+TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyGet) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // fetch http://www.google.com/ via SPDY
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(ASYNC, 0, 0),
+ };
+
+ DelayedSocketData spdy_data(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ(kUploadData, response_data);
+}
+
+// Test a SPDY get through an HTTPS Proxy.
+TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyGetWithProxyAuth) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against https proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // The first request will be a bare GET, the second request will be a
+ // GET with a Proxy-Authorization header.
+ scoped_ptr<SpdyFrame> req_get(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false));
+ const char* const kExtraAuthorizationHeaders[] = {
+ "proxy-authorization", "Basic Zm9vOmJhcg=="
+ };
+ scoped_ptr<SpdyFrame> req_get_authorization(
+ spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders,
+ arraysize(kExtraAuthorizationHeaders) / 2,
+ false,
+ 3,
+ LOWEST,
+ false));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req_get, 1),
+ CreateMockWrite(*req_get_authorization, 4),
+ };
+
+ // The first response is a 407 proxy authentication challenge, and the second
+ // response will be a 200 response since the second request includes a valid
+ // Authorization header.
+ const char* const kExtraAuthenticationHeaders[] = {
+ "proxy-authenticate", "Basic realm=\"MyRealm1\""
+ };
+ scoped_ptr<SpdyFrame> resp_authentication(
+ spdy_util_.ConstructSpdySynReplyError(
+ "407 Proxy Authentication Required",
+ kExtraAuthenticationHeaders, arraysize(kExtraAuthenticationHeaders)/2,
+ 1));
+ scoped_ptr<SpdyFrame> body_authentication(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp_data(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp_authentication, 2),
+ CreateMockRead(*body_authentication, 3),
+ CreateMockRead(*resp_data, 5),
+ CreateMockRead(*body_data, 6),
+ MockRead(ASYNC, 0, 7),
+ };
+
+ OrderedSocketData data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* const response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* const response_restart = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response_restart != NULL);
+ ASSERT_TRUE(response_restart->headers.get() != NULL);
+ EXPECT_EQ(200, response_restart->headers->response_code());
+ // The password prompt info should not be set.
+ EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+// Test a SPDY CONNECT through an HTTPS Proxy to an HTTPS (non-SPDY) Server.
+TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectHttps) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // CONNECT to www.google.com:443 via SPDY
+ scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ // fetch https://www.google.com/ via HTTP
+
+ const char get[] = "GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get(
+ spdy_util_.ConstructSpdyBodyFrame(1, get, strlen(get), false));
+ scoped_ptr<SpdyFrame> conn_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char resp[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 10\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get_resp(
+ spdy_util_.ConstructSpdyBodyFrame(1, resp, strlen(resp), false));
+ scoped_ptr<SpdyFrame> wrapped_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, "1234567890", 10, false));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp->size()));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*connect, 1),
+ CreateMockWrite(*wrapped_get, 3),
+ CreateMockWrite(*window_update, 5),
+ };
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*conn_resp, 2, ASYNC),
+ CreateMockRead(*wrapped_get_resp, 4, ASYNC),
+ CreateMockRead(*wrapped_body, 6, ASYNC),
+ CreateMockRead(*wrapped_body, 7, ASYNC),
+ MockRead(ASYNC, 0, 8),
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.was_npn_negotiated = false;
+ ssl2.protocol_negotiated = kProtoUnknown;
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("1234567890", response_data);
+}
+
+// Test a SPDY CONNECT through an HTTPS Proxy to a SPDY server.
+TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectSpdy) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // CONNECT to www.google.com:443 via SPDY
+ scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ // fetch https://www.google.com/ via SPDY
+ const char* const kMyUrl = "https://www.google.com/";
+ scoped_ptr<SpdyFrame> get(
+ spdy_util_.ConstructSpdyGet(kMyUrl, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> wrapped_get(
+ spdy_util_.ConstructWrappedSpdyFrame(get, 1));
+ scoped_ptr<SpdyFrame> conn_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> get_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> wrapped_get_resp(
+ spdy_util_.ConstructWrappedSpdyFrame(get_resp, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> wrapped_body(
+ spdy_util_.ConstructWrappedSpdyFrame(body, 1));
+ scoped_ptr<SpdyFrame> window_update_get_resp(
+ spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp->size()));
+ scoped_ptr<SpdyFrame> window_update_body(
+ spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_body->size()));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*connect, 1),
+ CreateMockWrite(*wrapped_get, 3),
+ CreateMockWrite(*window_update_get_resp, 5),
+ CreateMockWrite(*window_update_body, 7),
+ };
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*conn_resp, 2, ASYNC),
+ CreateMockRead(*wrapped_get_resp, 4, ASYNC),
+ CreateMockRead(*wrapped_body, 6, ASYNC),
+ MockRead(ASYNC, 0, 8),
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.SetNextProto(GetParam());
+ ssl2.protocol_negotiated = GetParam();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ(kUploadData, response_data);
+}
+
+// Test a SPDY CONNECT failure through an HTTPS Proxy.
+TEST_P(HttpNetworkTransactionTest, HttpsProxySpdyConnectFailure) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // CONNECT to www.google.com:443 via SPDY
+ scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> get(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*connect, 1),
+ CreateMockWrite(*get, 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdySynReplyError(1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 2, ASYNC),
+ MockRead(ASYNC, 0, 4),
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ // TODO(ttuttle): Anything else to check here?
+}
+
+// Test load timing in the case of two HTTPS (non-SPDY) requests through a SPDY
+// HTTPS Proxy to different servers.
+TEST_P(HttpNetworkTransactionTest,
+ HttpsProxySpdyConnectHttpsLoadTimingTwoRequestsTwoServers) {
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/");
+ request1.load_flags = 0;
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://news.google.com/");
+ request2.load_flags = 0;
+
+ // CONNECT to www.google.com:443 via SPDY.
+ scoped_ptr<SpdyFrame> connect1(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> conn_resp1(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ // Fetch https://www.google.com/ via HTTP.
+ const char get1[] = "GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get1(
+ spdy_util_.ConstructSpdyBodyFrame(1, get1, strlen(get1), false));
+ const char resp1[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 1\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get_resp1(
+ spdy_util_.ConstructSpdyBodyFrame(1, resp1, strlen(resp1), false));
+ scoped_ptr<SpdyFrame> wrapped_body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, false));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp1->size()));
+
+ // CONNECT to news.google.com:443 via SPDY.
+ const char* const kConnectHeaders2[] = {
+ spdy_util_.GetMethodKey(), "CONNECT",
+ spdy_util_.GetPathKey(), "news.google.com:443",
+ spdy_util_.GetHostKey(), "news.google.com",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ };
+ scoped_ptr<SpdyFrame> connect2(
+ spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ /*compressed*/ false,
+ 3,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kConnectHeaders2,
+ arraysize(kConnectHeaders2),
+ 0));
+ scoped_ptr<SpdyFrame> conn_resp2(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+
+ // Fetch https://news.google.com/ via HTTP.
+ const char get2[] = "GET / HTTP/1.1\r\n"
+ "Host: news.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get2(
+ spdy_util_.ConstructSpdyBodyFrame(3, get2, strlen(get2), false));
+ const char resp2[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 2\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get_resp2(
+ spdy_util_.ConstructSpdyBodyFrame(3, resp2, strlen(resp2), false));
+ scoped_ptr<SpdyFrame> wrapped_body2(
+ spdy_util_.ConstructSpdyBodyFrame(3, "22", 2, false));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*connect1, 0),
+ CreateMockWrite(*wrapped_get1, 2),
+ CreateMockWrite(*connect2, 5),
+ CreateMockWrite(*wrapped_get2, 7),
+ };
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*conn_resp1, 1, ASYNC),
+ CreateMockRead(*wrapped_get_resp1, 3, ASYNC),
+ CreateMockRead(*wrapped_body1, 4, ASYNC),
+ CreateMockRead(*conn_resp2, 6, ASYNC),
+ CreateMockRead(*wrapped_get_resp2, 8, ASYNC),
+ CreateMockRead(*wrapped_body2, 9, ASYNC),
+ MockRead(ASYNC, 0, 10),
+ };
+
+ DeterministicSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.was_npn_negotiated = false;
+ ssl2.protocol_negotiated = kProtoUnknown;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl2);
+ SSLSocketDataProvider ssl3(ASYNC, OK);
+ ssl3.was_npn_negotiated = false;
+ ssl3.protocol_negotiated = kProtoUnknown;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl3);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // The first connect and request, each of their responses, and the body.
+ spdy_data.RunFor(5);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ EXPECT_EQ(1, trans->Read(buf.get(), 256, callback.callback()));
+
+ scoped_ptr<HttpTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The second connect and request, each of their responses, and the body.
+ spdy_data.RunFor(5);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
+ // Even though the SPDY connection is reused, a new tunnelled connection has
+ // to be created, so the socket's load timing looks like a fresh connection.
+ TestLoadTimingNotReused(load_timing_info2, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ // The requests should have different IDs, since they each are using their own
+ // separate stream.
+ EXPECT_NE(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
+
+ EXPECT_EQ(2, trans2->Read(buf.get(), 256, callback.callback()));
+}
+
+// Test load timing in the case of two HTTPS (non-SPDY) requests through a SPDY
+// HTTPS Proxy to the same server.
+TEST_P(HttpNetworkTransactionTest,
+ HttpsProxySpdyConnectHttpsLoadTimingTwoRequestsSameServer) {
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/");
+ request1.load_flags = 0;
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.google.com/2");
+ request2.load_flags = 0;
+
+ // CONNECT to www.google.com:443 via SPDY.
+ scoped_ptr<SpdyFrame> connect1(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> conn_resp1(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ // Fetch https://www.google.com/ via HTTP.
+ const char get1[] = "GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get1(
+ spdy_util_.ConstructSpdyBodyFrame(1, get1, strlen(get1), false));
+ const char resp1[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 1\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get_resp1(
+ spdy_util_.ConstructSpdyBodyFrame(1, resp1, strlen(resp1), false));
+ scoped_ptr<SpdyFrame> wrapped_body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, false));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, wrapped_get_resp1->size()));
+
+ // Fetch https://www.google.com/2 via HTTP.
+ const char get2[] = "GET /2 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get2(
+ spdy_util_.ConstructSpdyBodyFrame(1, get2, strlen(get2), false));
+ const char resp2[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 2\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get_resp2(
+ spdy_util_.ConstructSpdyBodyFrame(1, resp2, strlen(resp2), false));
+ scoped_ptr<SpdyFrame> wrapped_body2(
+ spdy_util_.ConstructSpdyBodyFrame(1, "22", 2, false));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*connect1, 0),
+ CreateMockWrite(*wrapped_get1, 2),
+ CreateMockWrite(*wrapped_get2, 5),
+ };
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*conn_resp1, 1, ASYNC),
+ CreateMockRead(*wrapped_get_resp1, 3, ASYNC),
+ CreateMockRead(*wrapped_body1, 4, ASYNC),
+ CreateMockRead(*wrapped_get_resp2, 6, ASYNC),
+ CreateMockRead(*wrapped_body2, 7, ASYNC),
+ MockRead(ASYNC, 0, 8),
+ };
+
+ DeterministicSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.was_npn_negotiated = false;
+ ssl2.protocol_negotiated = kProtoUnknown;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // The first connect and request, each of their responses, and the body.
+ spdy_data.RunFor(5);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_SSL_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ EXPECT_EQ(1, trans->Read(buf.get(), 256, callback.callback()));
+ trans.reset();
+
+ scoped_ptr<HttpTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The second request, response, and body. There should not be a second
+ // connect.
+ spdy_data.RunFor(3);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingReused(load_timing_info2);
+
+ // The requests should have the same ID.
+ EXPECT_EQ(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
+
+ EXPECT_EQ(2, trans2->Read(buf.get(), 256, callback.callback()));
+}
+
+// Test load timing in the case of of two HTTP requests through a SPDY HTTPS
+// Proxy to different servers.
+TEST_P(HttpNetworkTransactionTest,
+ HttpsProxySpdyLoadTimingTwoHttpRequests) {
+ // Configure against https proxy server "proxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("http://www.google.com/");
+ request1.load_flags = 0;
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("http://news.google.com/");
+ request2.load_flags = 0;
+
+ // http://www.google.com/
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlockForProxy("http://www.google.com/"));
+ scoped_ptr<SpdyFrame> get1(spdy_util_.ConstructSpdyControlFrame(
+ headers.Pass(), false, 1, LOWEST, SYN_STREAM, CONTROL_FLAG_FIN, 0));
+ scoped_ptr<SpdyFrame> get_resp1(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, "1", 1, true));
+
+ // http://news.google.com/
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlockForProxy("http://news.google.com/"));
+ scoped_ptr<SpdyFrame> get2(spdy_util_.ConstructSpdyControlFrame(
+ headers2.Pass(), false, 3, LOWEST, SYN_STREAM, CONTROL_FLAG_FIN, 0));
+ scoped_ptr<SpdyFrame> get_resp2(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(3, "22", 2, true));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*get1, 0),
+ CreateMockWrite(*get2, 3),
+ };
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*get_resp1, 1, ASYNC),
+ CreateMockRead(*body1, 2, ASYNC),
+ CreateMockRead(*get_resp2, 4, ASYNC),
+ CreateMockRead(*body2, 5, ASYNC),
+ MockRead(ASYNC, 0, 6),
+ };
+
+ DeterministicSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ spdy_data.RunFor(2);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ EXPECT_EQ(ERR_IO_PENDING, trans->Read(buf.get(), 256, callback.callback()));
+ spdy_data.RunFor(1);
+ EXPECT_EQ(1, callback.WaitForResult());
+ // Delete the first request, so the second one can reuse the socket.
+ trans.reset();
+
+ scoped_ptr<HttpTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ rv = trans2->Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ spdy_data.RunFor(2);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2->GetLoadTimingInfo(&load_timing_info2));
+ TestLoadTimingReused(load_timing_info2);
+
+ // The requests should have the same ID.
+ EXPECT_EQ(load_timing_info.socket_log_id, load_timing_info2.socket_log_id);
+
+ EXPECT_EQ(ERR_IO_PENDING, trans2->Read(buf.get(), 256, callback.callback()));
+ spdy_data.RunFor(1);
+ EXPECT_EQ(2, callback.WaitForResult());
+}
+
+// Test the challenge-response-retry sequence through an HTTPS Proxy
+TEST_P(HttpNetworkTransactionTest, HttpsProxyAuthRetry) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ // when the no authentication data flag is set.
+ request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
+
+ // Configure against https proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should use full url
+ MockWrite data_writes1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // The proxy responds to the GET with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ // No credentials.
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Connection: keep-alive\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ load_timing_info = LoadTimingInfo();
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ // Retrying with HTTP AUTH is considered to be reusing a socket.
+ TestLoadTimingReused(load_timing_info);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should not be set.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus(
+ const MockRead& status, int expected_status) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ status,
+ MockRead("Content-Length: 10\r\n\r\n"),
+ // No response body because the test stops reading here.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(expected_status, rv);
+}
+
+void HttpNetworkTransactionTest::ConnectStatusHelper(
+ const MockRead& status) {
+ ConnectStatusHelperWithExpectedStatus(
+ status, ERR_TUNNEL_CONNECTION_FAILED);
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus100) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 100 Continue\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus101) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 101 Switching Protocols\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus201) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 201 Created\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus202) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 202 Accepted\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus203) {
+ ConnectStatusHelper(
+ MockRead("HTTP/1.1 203 Non-Authoritative Information\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus204) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 204 No Content\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus205) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 205 Reset Content\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus206) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 206 Partial Content\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus300) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 300 Multiple Choices\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus301) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 301 Moved Permanently\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus302) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 302 Found\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus303) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 303 See Other\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus304) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 304 Not Modified\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus305) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 305 Use Proxy\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus306) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 306\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus307) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 307 Temporary Redirect\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus400) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 400 Bad Request\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus401) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 401 Unauthorized\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus402) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 402 Payment Required\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus403) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 403 Forbidden\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus404) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 404 Not Found\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus405) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 405 Method Not Allowed\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus406) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 406 Not Acceptable\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus407) {
+ ConnectStatusHelperWithExpectedStatus(
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ ERR_PROXY_AUTH_UNSUPPORTED);
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus408) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 408 Request Timeout\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus409) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 409 Conflict\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus410) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 410 Gone\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus411) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 411 Length Required\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus412) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 412 Precondition Failed\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus413) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 413 Request Entity Too Large\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus414) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 414 Request-URI Too Long\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus415) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 415 Unsupported Media Type\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus416) {
+ ConnectStatusHelper(
+ MockRead("HTTP/1.1 416 Requested Range Not Satisfiable\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus417) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 417 Expectation Failed\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus500) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 500 Internal Server Error\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus501) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 501 Not Implemented\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus502) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 502 Bad Gateway\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus503) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 503 Service Unavailable\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus504) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 504 Gateway Timeout\r\n"));
+}
+
+TEST_P(HttpNetworkTransactionTest, ConnectStatus505) {
+ ConnectStatusHelper(MockRead("HTTP/1.1 505 HTTP Version Not Supported\r\n"));
+}
+
+// Test the flow when both the proxy server AND origin server require
+// authentication. Again, this uses basic auth for both since that is
+// the simplest to mock.
+TEST_P(HttpNetworkTransactionTest, BasicAuthProxyThenServer) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ // Configure against proxy server "myproxy:70".
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 407 Unauthorized\r\n"),
+ // Give a couple authenticate options (only the middle one is actually
+ // supported).
+ MockRead("Proxy-Authenticate: Basic invalid\r\n"), // Malformed.
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After calling trans->RestartWithAuth() the first time, this is the
+ // request we should be issuing -- the final header line contains the
+ // proxy's credentials.
+ MockWrite data_writes2[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Now the proxy server lets the request pass through to origin server.
+ // The origin server responds with a 401.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ // Note: We are using the same realm-name as the proxy server. This is
+ // completely valid, as realms are unique across hosts.
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 2000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED), // Won't be reached.
+ };
+
+ // After calling trans->RestartWithAuth() the second time, we should send
+ // the credentials for both the proxy and origin server.
+ MockWrite data_writes3[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n"
+ "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"),
+ };
+
+ // Lastly we get the desired content.
+ MockRead data_reads3[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback3;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo2, kBar2), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// For the NTLM implementation using SSPI, we skip the NTLM tests since we
+// can't hook into its internals to cause it to generate predictable NTLM
+// authorization headers.
+#if defined(NTLM_PORTABLE)
+// The NTLM authentication unit tests were generated by capturing the HTTP
+// requests and responses using Fiddler 2 and inspecting the generated random
+// bytes in the debugger.
+
+// Enter the correct password and authenticate successfully.
+TEST_P(HttpNetworkTransactionTest, NTLMAuth1) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://172.22.68.17/kids/login.aspx");
+ request.load_flags = 0;
+
+ HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1,
+ MockGetHostName);
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ // Negotiate and NTLM are often requested together. However, we only want
+ // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip
+ // the header that requests Negotiate for this test.
+ MockRead("WWW-Authenticate: NTLM\r\n"),
+ MockRead("Connection: close\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ // Missing content -- won't matter, as connection will be reset.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED),
+ };
+
+ MockWrite data_writes2[] = {
+ // After restarting with a null identity, this is the
+ // request we should be issuing -- the final header line contains a Type
+ // 1 message.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM "
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message
+ // (the credentials for the origin server). The second request continues
+ // on the same connection.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBVKW"
+ "Yma5xzVAAAAAAAAAAAAAAAAAAAAACH+gWcm+YsP9Tqb9zCR3WAeZZX"
+ "ahlhx5I=\r\n\r\n"),
+ };
+
+ MockRead data_reads2[] = {
+ // The origin server responds with a Type 2 message.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCjGpMpPGlYKkAAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ MockRead("You are not authorized to view this page\r\n"),
+
+ // Lastly we get the desired content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=utf-8\r\n"),
+ MockRead("Content-Length: 13\r\n\r\n"),
+ MockRead("Please Login\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM),
+ callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ TestCompletionCallback callback3;
+
+ rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(13, response->headers->GetContentLength());
+}
+
+// Enter a wrong password, and then the correct one.
+TEST_P(HttpNetworkTransactionTest, NTLMAuth2) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://172.22.68.17/kids/login.aspx");
+ request.load_flags = 0;
+
+ HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom2,
+ MockGetHostName);
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ // Negotiate and NTLM are often requested together. However, we only want
+ // to test NTLM. Since Negotiate is preferred over NTLM, we have to skip
+ // the header that requests Negotiate for this test.
+ MockRead("WWW-Authenticate: NTLM\r\n"),
+ MockRead("Connection: close\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ // Missing content -- won't matter, as connection will be reset.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED),
+ };
+
+ MockWrite data_writes2[] = {
+ // After restarting with a null identity, this is the
+ // request we should be issuing -- the final header line contains a Type
+ // 1 message.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM "
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message
+ // (the credentials for the origin server). The second request continues
+ // on the same connection.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwCWeY"
+ "XnSZNwoQAAAAAAAAAAAAAAAAAAAADLa34/phTTKzNTWdub+uyFleOj"
+ "4Ww7b7E=\r\n\r\n"),
+ };
+
+ MockRead data_reads2[] = {
+ // The origin server responds with a Type 2 message.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCbVWUZezVGpAAAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ MockRead("You are not authorized to view this page\r\n"),
+
+ // Wrong password.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM\r\n"),
+ MockRead("Connection: close\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ // Missing content -- won't matter, as connection will be reset.
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED),
+ };
+
+ MockWrite data_writes3[] = {
+ // After restarting with a null identity, this is the
+ // request we should be issuing -- the final header line contains a Type
+ // 1 message.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM "
+ "TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA=\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), we should send a Type 3 message
+ // (the credentials for the origin server). The second request continues
+ // on the same connection.
+ MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
+ "Host: 172.22.68.17\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: NTLM TlRMTVNTUAADAAAAGAAYAGgAAAAYABgAgA"
+ "AAAAAAAABAAAAAGAAYAEAAAAAQABAAWAAAAAAAAAAAAAAABYIIAHQA"
+ "ZQBzAHQAaQBuAGcALQBuAHQAbABtAFcAVABDAC0AVwBJAE4ANwBO54"
+ "dFMVvTHwAAAAAAAAAAAAAAAAAAAACS7sT6Uzw7L0L//WUqlIaVWpbI"
+ "+4MUm7c=\r\n\r\n"),
+ };
+
+ MockRead data_reads3[] = {
+ // The origin server responds with a Type 2 message.
+ MockRead("HTTP/1.1 401 Access Denied\r\n"),
+ MockRead("WWW-Authenticate: NTLM "
+ "TlRMTVNTUAACAAAADAAMADgAAAAFgokCL24VN8dgOR8AAAAAAAAAALo"
+ "AugBEAAAABQEoCgAAAA9HAE8ATwBHAEwARQACAAwARwBPAE8ARwBMAE"
+ "UAAQAaAEEASwBFAEUAUwBBAFIAQQAtAEMATwBSAFAABAAeAGMAbwByA"
+ "HAALgBnAG8AbwBnAGwAZQAuAGMAbwBtAAMAQABhAGsAZQBlAHMAYQBy"
+ "AGEALQBjAG8AcgBwAC4AYQBkAC4AYwBvAHIAcAAuAGcAbwBvAGcAbAB"
+ "lAC4AYwBvAG0ABQAeAGMAbwByAHAALgBnAG8AbwBnAGwAZQAuAGMAbw"
+ "BtAAAAAAA=\r\n"),
+ MockRead("Content-Length: 42\r\n"),
+ MockRead("Content-Type: text/html\r\n\r\n"),
+ MockRead("You are not authorized to view this page\r\n"),
+
+ // Lastly we get the desired content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=utf-8\r\n"),
+ MockRead("Content-Length: 13\r\n\r\n"),
+ MockRead("Please Login\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ // Enter the wrong password.
+ rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kWrongPassword),
+ callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback3;
+ rv = trans->RestartWithAuth(AuthCredentials(), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ response = trans->GetResponseInfo();
+ ASSERT_FALSE(response == NULL);
+ EXPECT_TRUE(CheckNTLMServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback4;
+
+ // Now enter the right password.
+ rv = trans->RestartWithAuth(AuthCredentials(kTestingNTLM, kTestingNTLM),
+ callback4.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback4.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+
+ TestCompletionCallback callback5;
+
+ // One more roundtrip
+ rv = trans->RestartWithAuth(AuthCredentials(), callback5.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback5.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(13, response->headers->GetContentLength());
+}
+#endif // NTLM_PORTABLE
+
+// Test reading a server response which has only headers, and no body.
+// After some maximum number of bytes is consumed, the transaction should
+// fail with ERR_RESPONSE_HEADERS_TOO_BIG.
+TEST_P(HttpNetworkTransactionTest, LargeHeadersNoBody) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ // Respond with 300 kb of headers (we should fail after 256 kb).
+ std::string large_headers_string;
+ FillLargeHeadersString(&large_headers_string, 300 * 1024);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead(ASYNC, large_headers_string.data(), large_headers_string.size()),
+ MockRead("\r\nBODY"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_TOO_BIG, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response == NULL);
+}
+
+// Make sure that we don't try to reuse a TCPClientSocket when failing to
+// establish tunnel.
+// http://code.google.com/p/chromium/issues/detail?id=3772
+TEST_P(HttpNetworkTransactionTest,
+ DontRecycleTransportSocketForSSLTunnel) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Configure against proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 404, using a persistent
+ // connection. Usually a proxy would return 501 (not implemented),
+ // or 200 (tunnel established).
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 404 Not Found\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response == NULL);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the TCPClientSocket was not added back to
+ // the pool.
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+ trans.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ // Make sure that the socket didn't get recycled after calling the destructor.
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+}
+
+// Make sure that we recycle a socket after reading all of the response body.
+TEST_P(HttpNetworkTransactionTest, RecycleSocket) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockRead data_reads[] = {
+ // A part of the response body is received with the response headers.
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nhel"),
+ // The rest of the response body is received in two parts.
+ MockRead("lo"),
+ MockRead(" world"),
+ MockRead("junk"), // Should not be read!!
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ std::string status_line = response->headers->GetStatusLine();
+ EXPECT_EQ("HTTP/1.1 200 OK", status_line);
+
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the socket was added back to the pool.
+ EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
+}
+
+// Make sure that we recycle a SSL socket after reading all of the response
+// body.
+TEST_P(HttpNetworkTransactionTest, RecycleSSLSocket) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 11\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the socket was added back to the pool.
+ EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
+}
+
+// Grab a SSL socket, use it, and put it back into the pool. Then, reuse it
+// from the pool and make sure that we recover okay.
+TEST_P(HttpNetworkTransactionTest, RecycleDeadSSLSocket) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 11\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead("hello world"),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ StaticSocketDataProvider data2(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the socket was added back to the pool.
+ EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
+
+ // Now start the second transaction, which should reuse the previous socket.
+
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the socket was added back to the pool.
+ EXPECT_EQ(1, GetIdleSocketCountInSSLSocketPool(session.get()));
+}
+
+// Make sure that we recycle a socket after a zero-length response.
+// http://crbug.com/9880
+TEST_P(HttpNetworkTransactionTest, RecycleSocketAfterZeroContentLength) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/csi?v=3&s=web&action=&"
+ "tran=undefined&ei=mAXcSeegAo-SMurloeUN&"
+ "e=17259,18167,19592,19773,19981,20133,20173,20233&"
+ "rt=prt.2642,ol.2649,xjs.2951");
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 204 No Content\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: text/html\r\n\r\n"),
+ MockRead("junk"), // Should not be read!!
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ std::string status_line = response->headers->GetStatusLine();
+ EXPECT_EQ("HTTP/1.1 204 No Content", status_line);
+
+ EXPECT_EQ(0, GetIdleSocketCountInTransportSocketPool(session.get()));
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("", response_data);
+
+ // Empty the current queue. This is necessary because idle sockets are
+ // added to the connection pool asynchronously with a PostTask.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now check to make sure the socket was added back to the pool.
+ EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session.get()));
+}
+
+TEST_P(HttpNetworkTransactionTest, ResendRequestOnWriteBodyError) {
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(new UploadBytesElementReader("foo", 3));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request[2];
+ // Transaction 1: a GET request that succeeds. The socket is recycled
+ // after use.
+ request[0].method = "GET";
+ request[0].url = GURL("http://www.google.com/");
+ request[0].load_flags = 0;
+ // Transaction 2: a POST request. Reuses the socket kept alive from
+ // transaction 1. The first attempts fails when writing the POST data.
+ // This causes the transaction to retry with a new socket. The second
+ // attempt succeeds.
+ request[1].method = "POST";
+ request[1].url = GURL("http://www.google.com/login.cgi");
+ request[1].upload_data_stream = &upload_data_stream;
+ request[1].load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // The first socket is used for transaction 1 and the first attempt of
+ // transaction 2.
+
+ // The response of transaction 1.
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ // The mock write results of transaction 1 and the first attempt of
+ // transaction 2.
+ MockWrite data_writes1[] = {
+ MockWrite(SYNCHRONOUS, 64), // GET
+ MockWrite(SYNCHRONOUS, 93), // POST
+ MockWrite(SYNCHRONOUS, ERR_CONNECTION_ABORTED), // POST data
+ };
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+
+ // The second socket is used for the second attempt of transaction 2.
+
+ // The response of transaction 2.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 200 OK\r\nContent-Length: 7\r\n\r\n"),
+ MockRead("welcome"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ // The mock write results of the second attempt of transaction 2.
+ MockWrite data_writes2[] = {
+ MockWrite(SYNCHRONOUS, 93), // POST
+ MockWrite(SYNCHRONOUS, 3), // POST data
+ };
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ const char* kExpectedResponseData[] = {
+ "hello world", "welcome"
+ };
+
+ for (int i = 0; i < 2; ++i) {
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request[i], callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(kExpectedResponseData[i], response_data);
+ }
+}
+
+// Test the request-challenge-retry sequence for basic auth when there is
+// an identity in the URL. The request should be sent as normal, but when
+// it fails the identity from the URL is used to answer the challenge.
+TEST_P(HttpNetworkTransactionTest, AuthIdentityInURL) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://foo:b@r@www.google.com/");
+ request.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ // The password contains an escaped character -- for this test to pass it
+ // will need to be unescaped by HttpNetworkTransaction.
+ EXPECT_EQ("b%40r", request.url.password());
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After the challenge above, the transaction will be restarted using the
+ // identity from the url (foo, b@r) to answer the challenge.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJAcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ // There is no challenge info, since the identity in URL worked.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ EXPECT_EQ(100, response->headers->GetContentLength());
+
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test the request-challenge-retry sequence for basic auth when there is an
+// incorrect identity in the URL. The identity from the URL should be used only
+// once.
+TEST_P(HttpNetworkTransactionTest, WrongAuthIdentityInURL) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ // Note: the URL has a username:password in it. The password "baz" is
+ // wrong (should be "bar").
+ request.url = GURL("http://foo:baz@www.google.com/");
+
+ request.load_flags = LOAD_NORMAL;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After the challenge above, the transaction will be restarted using the
+ // identity from the url (foo, baz) to answer the challenge.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After the challenge above, the transaction will be restarted using the
+ // identity supplied by the user (foo, bar) to answer the challenge.
+ MockWrite data_writes3[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads3[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback3;
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ // There is no challenge info, since the identity worked.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ EXPECT_EQ(100, response->headers->GetContentLength());
+
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test that previously tried username/passwords for a realm get re-used.
+TEST_P(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Transaction 1: authenticate (foo, bar) on MyRealm1
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/x/y/z");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // Resend with authorization (username=foo, password=bar)
+ MockWrite data_writes2[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 2: authenticate (foo2, bar2) on MyRealm2
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ // Note that Transaction 1 was at /x/y/z, so this is in the same
+ // protection space as MyRealm1.
+ request.url = GURL("http://www.google.com/x/y/a/b");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/a/b HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ // Send preemptive authorization for MyRealm1
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // The server didn't like the preemptive authorization, and
+ // challenges us for a different realm (MyRealm2).
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm2\"\r\n"),
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // Resend with authorization for MyRealm2 (username=foo2, password=bar2)
+ MockWrite data_writes2[] = {
+ MockWrite("GET /x/y/a/b HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vMjpiYXIy\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->auth_challenge.get());
+ EXPECT_FALSE(response->auth_challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80",
+ response->auth_challenge->challenger.ToString());
+ EXPECT_EQ("MyRealm2", response->auth_challenge->realm);
+ EXPECT_EQ("basic", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo2, kBar2), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 3: Resend a request in MyRealm's protection space --
+ // succeed with preemptive authorization.
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/x/y/z2");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/z2 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ // The authorization for MyRealm1 gets sent preemptively
+ // (since the url is in the same protection space)
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Sever accepts the preemptive authorization
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 4: request another URL in MyRealm (however the
+ // url is not known to belong to the protection space, so no pre-auth).
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/x/1");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/1 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // Resend with authorization from MyRealm's cache.
+ MockWrite data_writes2[] = {
+ MockWrite("GET /x/1 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 5: request a URL in MyRealm, but the server rejects the
+ // cached identity. Should invalidate and re-prompt.
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/p/q/t");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /p/q/t HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // Resend with authorization from cache for MyRealm.
+ MockWrite data_writes2[] = {
+ MockWrite("GET /p/q/t HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Sever rejects the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // At this point we should prompt for new credentials for MyRealm.
+ // Restart with username=foo3, password=foo4.
+ MockWrite data_writes3[] = {
+ MockWrite("GET /p/q/t HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vMzpiYXIz\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads3[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(AuthCredentials(), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback3;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo3, kBar3), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+}
+
+// Tests that nonce count increments when multiple auth attempts
+// are started with the same nonce.
+TEST_P(HttpNetworkTransactionTest, DigestPreAuthNonceCount) {
+ HttpAuthHandlerDigest::Factory* digest_factory =
+ new HttpAuthHandlerDigest::Factory();
+ HttpAuthHandlerDigest::FixedNonceGenerator* nonce_generator =
+ new HttpAuthHandlerDigest::FixedNonceGenerator("0123456789abcdef");
+ digest_factory->set_nonce_generator(nonce_generator);
+ session_deps_.http_auth_handler_factory.reset(digest_factory);
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Transaction 1: authenticate (foo, bar) on MyRealm1
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/x/y/z");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Digest realm=\"digestive\", nonce=\"OU812\", "
+ "algorithm=MD5, qop=\"auth\"\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ // Resend with authorization (username=foo, password=bar)
+ MockWrite data_writes2[] = {
+ MockWrite("GET /x/y/z HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Digest username=\"foo\", realm=\"digestive\", "
+ "nonce=\"OU812\", uri=\"/x/y/z\", algorithm=MD5, "
+ "response=\"03ffbcd30add722589c1de345d7a927f\", qop=auth, "
+ "nc=00000001, cnonce=\"0123456789abcdef\"\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckDigestServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+
+ // ------------------------------------------------------------------------
+
+ // Transaction 2: Request another resource in digestive's protection space.
+ // This will preemptively add an Authorization header which should have an
+ // "nc" value of 2 (as compared to 1 in the first use.
+ {
+ HttpRequestInfo request;
+ request.method = "GET";
+ // Note that Transaction 1 was at /x/y/z, so this is in the same
+ // protection space as digest.
+ request.url = GURL("http://www.google.com/x/y/a/b");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET /x/y/a/b HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Digest username=\"foo\", realm=\"digestive\", "
+ "nonce=\"OU812\", uri=\"/x/y/a/b\", algorithm=MD5, "
+ "response=\"d6f9a2c07d1c5df7b89379dca1269b35\", qop=auth, "
+ "nc=00000002, cnonce=\"0123456789abcdef\"\r\n\r\n"),
+ };
+
+ // Sever accepts the authorization.
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+}
+
+// Test the ResetStateForRestart() private method.
+TEST_P(HttpNetworkTransactionTest, ResetStateForRestart) {
+ // Create a transaction (the dependencies aren't important).
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ // Setup some state (which we expect ResetStateForRestart() will clear).
+ trans->read_buf_ = new IOBuffer(15);
+ trans->read_buf_len_ = 15;
+ trans->request_headers_.SetHeader("Authorization", "NTLM");
+
+ // Setup state in response_
+ HttpResponseInfo* response = &trans->response_;
+ response->auth_challenge = new AuthChallengeInfo();
+ response->ssl_info.cert_status = static_cast<CertStatus>(-1); // Nonsensical.
+ response->response_time = base::Time::Now();
+ response->was_cached = true; // (Wouldn't ever actually be true...)
+
+ { // Setup state for response_.vary_data
+ HttpRequestInfo request;
+ std::string temp("HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ std::replace(temp.begin(), temp.end(), '\n', '\0');
+ scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(temp));
+ request.extra_headers.SetHeader("Foo", "1");
+ request.extra_headers.SetHeader("bar", "23");
+ EXPECT_TRUE(response->vary_data.Init(request, *headers.get()));
+ }
+
+ // Cause the above state to be reset.
+ trans->ResetStateForRestart();
+
+ // Verify that the state that needed to be reset, has been reset.
+ EXPECT_TRUE(trans->read_buf_.get() == NULL);
+ EXPECT_EQ(0, trans->read_buf_len_);
+ EXPECT_TRUE(trans->request_headers_.IsEmpty());
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_TRUE(response->headers.get() == NULL);
+ EXPECT_FALSE(response->was_cached);
+ EXPECT_EQ(0U, response->ssl_info.cert_status);
+ EXPECT_FALSE(response->vary_data.is_valid());
+}
+
+// Test HTTPS connections to a site with a bad certificate
+TEST_P(HttpNetworkTransactionTest, HTTPSBadCertificate) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider ssl_bad_certificate;
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&ssl_bad_certificate);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_bad);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv);
+
+ rv = trans->RestartIgnoringLastError(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// Test HTTPS connections to a site with a bad certificate, going through a
+// proxy
+TEST_P(HttpNetworkTransactionTest, HTTPSBadCertificateViaProxy) {
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite proxy_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead proxy_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\n"),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider ssl_bad_certificate(
+ proxy_reads, arraysize(proxy_reads),
+ proxy_writes, arraysize(proxy_writes));
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&ssl_bad_certificate);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_bad);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ for (int i = 0; i < 2; i++) {
+ session_deps_.socket_factory->ResetNextMockIndexes();
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv);
+
+ rv = trans->RestartIgnoringLastError(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ }
+}
+
+
+// Test HTTPS connections to a site, going through an HTTPS proxy
+TEST_P(HttpNetworkTransactionTest, HTTPSViaHttpsProxy) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("HTTPS proxy:70"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\n"),
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
+ SSLSocketDataProvider tunnel_ssl(ASYNC, OK); // SSL through the tunnel
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&tunnel_ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+// Test an HTTPS Proxy's ability to redirect a CONNECT request
+TEST_P(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaHttpsProxy) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("HTTPS proxy:70"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 302 Redirect\r\n"),
+ MockRead("Location: http://login.example.com/\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_EQ(302, response->headers->response_code());
+ std::string url;
+ EXPECT_TRUE(response->headers->IsRedirect(&url));
+ EXPECT_EQ("http://login.example.com/", url);
+
+ // In the case of redirects from proxies, HttpNetworkTransaction returns
+ // timing for the proxy connection instead of the connection to the host,
+ // and no send / receive times.
+ // See HttpNetworkTransaction::OnHttpsProxyTunnelResponse.
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_LE(load_timing_info.proxy_resolve_start,
+ load_timing_info.proxy_resolve_end);
+ EXPECT_LE(load_timing_info.proxy_resolve_end,
+ load_timing_info.connect_timing.connect_start);
+ ExpectConnectTimingHasTimes(
+ load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_DNS_TIMES | CONNECT_TIMING_HAS_SSL_TIMES);
+
+ EXPECT_TRUE(load_timing_info.send_start.is_null());
+ EXPECT_TRUE(load_timing_info.send_end.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+// Test an HTTPS (SPDY) Proxy's ability to redirect a CONNECT request
+TEST_P(HttpNetworkTransactionTest, RedirectOfHttpsConnectViaSpdyProxy) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://proxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<SpdyFrame> conn(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> goaway(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite data_writes[] = {
+ CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
+ CreateMockWrite(*goaway.get(), 3, SYNCHRONOUS),
+ };
+
+ static const char* const kExtraHeaders[] = {
+ "location",
+ "http://login.example.com/",
+ };
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdySynReplyError("302 Redirect", kExtraHeaders,
+ arraysize(kExtraHeaders)/2, 1));
+ MockRead data_reads[] = {
+ CreateMockRead(*resp.get(), 1, SYNCHRONOUS),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ DelayedSocketData data(
+ 1, // wait for one write to finish before reading.
+ data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
+ proxy_ssl.SetNextProto(GetParam());
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_EQ(302, response->headers->response_code());
+ std::string url;
+ EXPECT_TRUE(response->headers->IsRedirect(&url));
+ EXPECT_EQ("http://login.example.com/", url);
+}
+
+// Test that an HTTPS proxy's response to a CONNECT request is filtered.
+TEST_P(HttpNetworkTransactionTest,
+ ErrorResponseToHttpsConnectViaHttpsProxy) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://proxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 404 Not Found\r\n"),
+ MockRead("Content-Length: 23\r\n\r\n"),
+ MockRead("The host does not exist"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ // TODO(ttuttle): Anything else to check here?
+}
+
+// Test that a SPDY proxy's response to a CONNECT request is filtered.
+TEST_P(HttpNetworkTransactionTest,
+ ErrorResponseToHttpsConnectViaSpdyProxy) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://proxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<SpdyFrame> conn(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite data_writes[] = {
+ CreateMockWrite(*conn.get(), 0, SYNCHRONOUS),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+ };
+
+ static const char* const kExtraHeaders[] = {
+ "location",
+ "http://login.example.com/",
+ };
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdySynReplyError("404 Not Found", kExtraHeaders,
+ arraysize(kExtraHeaders)/2, 1));
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, "The host does not exist", 23, true));
+ MockRead data_reads[] = {
+ CreateMockRead(*resp.get(), 1, SYNCHRONOUS),
+ CreateMockRead(*body.get(), 2, SYNCHRONOUS),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ DelayedSocketData data(
+ 1, // wait for one write to finish before reading.
+ data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ SSLSocketDataProvider proxy_ssl(ASYNC, OK); // SSL to the proxy
+ proxy_ssl.SetNextProto(GetParam());
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy_ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+
+ // TODO(ttuttle): Anything else to check here?
+}
+
+// Test the request-challenge-retry sequence for basic auth, through
+// a SPDY proxy over a single SPDY session.
+TEST_P(HttpNetworkTransactionTest, BasicAuthSpdyProxy) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ // when the no authentication data flag is set.
+ request.load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA;
+
+ // Configure against https proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("HTTPS myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Since we have proxy, should try to establish tunnel.
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ const char* const kAuthCredentials[] = {
+ "proxy-authorization", "Basic Zm9vOmJhcg==",
+ };
+ scoped_ptr<SpdyFrame> connect2(spdy_util_.ConstructSpdyConnect(
+ kAuthCredentials, arraysize(kAuthCredentials) / 2, 3));
+ // fetch https://www.google.com/ via HTTP
+ const char get[] = "GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n";
+ scoped_ptr<SpdyFrame> wrapped_get(
+ spdy_util_.ConstructSpdyBodyFrame(3, get, strlen(get), false));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 1, ASYNC),
+ CreateMockWrite(*rst, 4, ASYNC),
+ CreateMockWrite(*connect2, 5),
+ CreateMockWrite(*wrapped_get, 8),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ const char* const kAuthChallenge[] = {
+ spdy_util_.GetStatusKey(), "407 Proxy Authentication Required",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+ };
+
+ scoped_ptr<SpdyFrame> conn_auth_resp(
+ spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kAuthChallenge,
+ arraysize(kAuthChallenge),
+ 0));
+
+ scoped_ptr<SpdyFrame> conn_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ const char resp[] = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 5\r\n\r\n";
+
+ scoped_ptr<SpdyFrame> wrapped_get_resp(
+ spdy_util_.ConstructSpdyBodyFrame(3, resp, strlen(resp), false));
+ scoped_ptr<SpdyFrame> wrapped_body(
+ spdy_util_.ConstructSpdyBodyFrame(3, "hello", 5, false));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*conn_auth_resp, 2, ASYNC),
+ CreateMockRead(*conn_resp, 6, ASYNC),
+ CreateMockRead(*wrapped_get_resp, 9, ASYNC),
+ CreateMockRead(*wrapped_body, 10, ASYNC),
+ MockRead(ASYNC, OK, 11), // EOF. May or may not be read.
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+ // Negotiate SPDY to the proxy
+ SSLSocketDataProvider proxy(ASYNC, OK);
+ proxy.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
+ // Vanilla SSL to the server
+ SSLSocketDataProvider server(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&server);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->headers.get() == NULL);
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(response->auth_challenge.get() != NULL);
+ EXPECT_TRUE(CheckBasicProxyAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar),
+ callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(5, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should not be set.
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ trans.reset();
+ session->CloseAllConnections();
+}
+
+// Test that an explicitly trusted SPDY proxy can push a resource from an
+// origin that is different from that of its associated resource.
+TEST_P(HttpNetworkTransactionTest, CrossOriginProxyPush) {
+ HttpRequestInfo request;
+ HttpRequestInfo push_request;
+
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ push_request.method = "GET";
+ push_request.url = GURL("http://www.another-origin.com/foo.dat");
+
+ // Configure against https proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("HTTPS myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+
+ // Enable cross-origin push.
+ session_deps_.trusted_spdy_proxy = "myproxy:70";
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*stream1_syn, 1, ASYNC),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ scoped_ptr<SpdyFrame>
+ stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.another-origin.com/foo.dat"));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*stream1_reply, 2, ASYNC),
+ CreateMockRead(*stream2_syn, 3, ASYNC),
+ CreateMockRead(*stream1_body, 4, ASYNC),
+ CreateMockRead(*stream2_body, 5, ASYNC),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+ // Negotiate SPDY to the proxy
+ SSLSocketDataProvider proxy(ASYNC, OK);
+ proxy.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ scoped_ptr<HttpTransaction> push_trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ rv = push_trans->Start(&push_request, callback.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* push_response = push_trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello!", response_data);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(push_response->headers.get() != NULL);
+ EXPECT_EQ(200, push_response->headers->response_code());
+
+ rv = ReadTransaction(push_trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("pushed", response_data);
+
+ LoadTimingInfo push_load_timing_info;
+ EXPECT_TRUE(push_trans->GetLoadTimingInfo(&push_load_timing_info));
+ TestLoadTimingReusedWithPac(push_load_timing_info);
+ // The transactions should share a socket ID, despite being for different
+ // origins.
+ EXPECT_EQ(load_timing_info.socket_log_id,
+ push_load_timing_info.socket_log_id);
+
+ trans.reset();
+ push_trans.reset();
+ session->CloseAllConnections();
+}
+
+// Test that an explicitly trusted SPDY proxy cannot push HTTPS content.
+TEST_P(HttpNetworkTransactionTest, CrossOriginProxyPushCorrectness) {
+ HttpRequestInfo request;
+
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+
+ // Configure against https proxy server "myproxy:70".
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+
+ // Enable cross-origin push.
+ session_deps_.trusted_spdy_proxy = "myproxy:70";
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, false));
+
+ scoped_ptr<SpdyFrame> push_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*stream1_syn, 1, ASYNC),
+ CreateMockWrite(*push_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ scoped_ptr<SpdyFrame>
+ stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "https://www.another-origin.com/foo.dat"));
+
+ MockRead spdy_reads[] = {
+ CreateMockRead(*stream1_reply, 2, ASYNC),
+ CreateMockRead(*stream2_syn, 3, ASYNC),
+ CreateMockRead(*stream1_body, 5, ASYNC),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+ // Negotiate SPDY to the proxy
+ SSLSocketDataProvider proxy(ASYNC, OK);
+ proxy.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&proxy);
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello!", response_data);
+
+ trans.reset();
+ session->CloseAllConnections();
+}
+
+// Test HTTPS connections to a site with a bad certificate, going through an
+// HTTPS proxy
+TEST_P(HttpNetworkTransactionTest, HTTPSBadCertificateViaHttpsProxy) {
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed(
+ "https://proxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Attempt to fetch the URL from a server with a bad cert
+ MockWrite bad_cert_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead bad_cert_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ // Attempt to fetch the URL with a good cert
+ MockWrite good_data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead good_cert_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\n"),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider ssl_bad_certificate(
+ bad_cert_reads, arraysize(bad_cert_reads),
+ bad_cert_writes, arraysize(bad_cert_writes));
+ StaticSocketDataProvider data(good_cert_reads, arraysize(good_cert_reads),
+ good_data_writes, arraysize(good_data_writes));
+ SSLSocketDataProvider ssl_bad(ASYNC, ERR_CERT_AUTHORITY_INVALID);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+
+ // SSL to the proxy, then CONNECT request, then SSL with bad certificate
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSocketDataProvider(&ssl_bad_certificate);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_bad);
+
+ // SSL to the proxy, then CONNECT request, then valid SSL certificate
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, rv);
+
+ rv = trans->RestartIgnoringLastError(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+
+ ASSERT_TRUE(response != NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_UserAgent) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Chromium Ultra Awesome X Edition");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_UserAgentOverTunnel) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ "Chromium Ultra Awesome X Edition");
+
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "User-Agent: Chromium Ultra Awesome X Edition\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ // Return an error, so the transaction stops here (this test isn't
+ // interested in the rest).
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Proxy-Connection: close\r\n\r\n"),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_Referer) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ request.extra_headers.SetHeader(HttpRequestHeaders::kReferer,
+ "http://the.previous.site.com/");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Referer: http://the.previous.site.com/\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_PostContentLengthZero) {
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("POST / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_PutContentLengthZero) {
+ HttpRequestInfo request;
+ request.method = "PUT";
+ request.url = GURL("http://www.google.com/");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("PUT / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_HeadContentLengthZero) {
+ HttpRequestInfo request;
+ request.method = "HEAD";
+ request.url = GURL("http://www.google.com/");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("HEAD / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_CacheControlNoCache) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = LOAD_BYPASS_CACHE;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ BuildRequest_CacheControlValidateCache) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = LOAD_VALIDATE_CACHE;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Cache-Control: max-age=0\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_ExtraHeaders) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.extra_headers.SetHeader("FooHeader", "Bar");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "FooHeader: Bar\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, BuildRequest_ExtraHeadersStripped) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.extra_headers.SetHeader("referer", "www.foo.com");
+ request.extra_headers.SetHeader("hEllo", "Kitty");
+ request.extra_headers.SetHeader("FoO", "bar");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "referer: www.foo.com\r\n"
+ "hEllo: Kitty\r\n"
+ "FoO: bar\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, SOCKS4_HTTP_GET) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("SOCKS myproxy:1080"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ char write_buffer[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 };
+ char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, write_buffer, arraysize(write_buffer)),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, read_buffer, arraysize(read_buffer)),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+TEST_P(HttpNetworkTransactionTest, SOCKS4_SSL_GET) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("SOCKS myproxy:1080"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ unsigned char write_buffer[] = { 0x04, 0x01, 0x01, 0xBB, 127, 0, 0, 1, 0 };
+ unsigned char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, reinterpret_cast<char*>(write_buffer),
+ arraysize(write_buffer)),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, reinterpret_cast<char*>(read_buffer),
+ arraysize(read_buffer)),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+TEST_P(HttpNetworkTransactionTest, SOCKS4_HTTP_GET_no_PAC) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("socks4://myproxy:1080"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ char write_buffer[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 };
+ char read_buffer[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, write_buffer, arraysize(write_buffer)),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, read_buffer, arraysize(read_buffer)),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReused(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+TEST_P(HttpNetworkTransactionTest, SOCKS5_HTTP_GET) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("SOCKS5 myproxy:1080"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 };
+ const char kSOCKS5GreetResponse[] = { 0x05, 0x00 };
+ const char kSOCKS5OkRequest[] = {
+ 0x05, // Version
+ 0x01, // Command (CONNECT)
+ 0x00, // Reserved.
+ 0x03, // Address type (DOMAINNAME).
+ 0x0E, // Length of domain (14)
+ // Domain string:
+ 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',
+ 0x00, 0x50, // 16-bit port (80)
+ };
+ const char kSOCKS5OkResponse[] =
+ { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
+ MockWrite(ASYNC, kSOCKS5OkRequest, arraysize(kSOCKS5OkRequest)),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
+ MockRead(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+TEST_P(HttpNetworkTransactionTest, SOCKS5_SSL_GET) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("SOCKS5 myproxy:1080"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 };
+ const char kSOCKS5GreetResponse[] = { 0x05, 0x00 };
+ const unsigned char kSOCKS5OkRequest[] = {
+ 0x05, // Version
+ 0x01, // Command (CONNECT)
+ 0x00, // Reserved.
+ 0x03, // Address type (DOMAINNAME).
+ 0x0E, // Length of domain (14)
+ // Domain string:
+ 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',
+ 0x01, 0xBB, // 16-bit port (443)
+ };
+
+ const char kSOCKS5OkResponse[] =
+ { 0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0x00, 0x00 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, arraysize(kSOCKS5GreetRequest)),
+ MockWrite(ASYNC, reinterpret_cast<const char*>(kSOCKS5OkRequest),
+ arraysize(kSOCKS5OkRequest)),
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, arraysize(kSOCKS5GreetResponse)),
+ MockRead(ASYNC, kSOCKS5OkResponse, arraysize(kSOCKS5OkResponse)),
+ MockRead("HTTP/1.0 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n\r\n"),
+ MockRead("Payload"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ std::string response_text;
+ rv = ReadTransaction(trans.get(), &response_text);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("Payload", response_text);
+}
+
+namespace {
+
+// Tests that for connection endpoints the group names are correctly set.
+
+struct GroupNameTest {
+ std::string proxy_server;
+ std::string url;
+ std::string expected_group_name;
+ bool ssl;
+};
+
+scoped_refptr<HttpNetworkSession> SetupSessionForGroupNameTests(
+ NextProto next_proto,
+ SpdySessionDependencies* session_deps_) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair("host.with.alternate", 80), 443,
+ AlternateProtocolFromNextProto(next_proto));
+
+ return session;
+}
+
+int GroupNameTransactionHelper(
+ const std::string& url,
+ const scoped_refptr<HttpNetworkSession>& session) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(url);
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+
+ // We do not complete this request, the dtor will clean the transaction up.
+ return trans->Start(&request, callback.callback(), BoundNetLog());
+}
+
+} // namespace
+
+TEST_P(HttpNetworkTransactionTest, GroupNameForDirectConnections) {
+ const GroupNameTest tests[] = {
+ {
+ "", // unused
+ "http://www.google.com/direct",
+ "www.google.com:80",
+ false,
+ },
+ {
+ "", // unused
+ "http://[2001:1418:13:1::25]/direct",
+ "[2001:1418:13:1::25]:80",
+ false,
+ },
+
+ // SSL Tests
+ {
+ "", // unused
+ "https://www.google.com/direct_ssl",
+ "ssl/www.google.com:443",
+ true,
+ },
+ {
+ "", // unused
+ "https://[2001:1418:13:1::25]/direct",
+ "ssl/[2001:1418:13:1::25]:443",
+ true,
+ },
+ {
+ "", // unused
+ "http://host.with.alternate/direct",
+ "ssl/host.with.alternate:443",
+ true,
+ },
+ };
+
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed(tests[i].proxy_server));
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(GetParam(), &session_deps_));
+
+ HttpNetworkSessionPeer peer(session);
+ CaptureGroupNameTransportSocketPool* transport_conn_pool =
+ new CaptureGroupNameTransportSocketPool(NULL, NULL);
+ CaptureGroupNameSSLSocketPool* ssl_conn_pool =
+ new CaptureGroupNameSSLSocketPool(NULL, NULL);
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetTransportSocketPool(transport_conn_pool);
+ mock_pool_manager->SetSSLSocketPool(ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ transport_conn_pool->last_group_name_received());
+ }
+
+}
+
+TEST_P(HttpNetworkTransactionTest, GroupNameForHTTPProxyConnections) {
+ const GroupNameTest tests[] = {
+ {
+ "http_proxy",
+ "http://www.google.com/http_proxy_normal",
+ "www.google.com:80",
+ false,
+ },
+
+ // SSL Tests
+ {
+ "http_proxy",
+ "https://www.google.com/http_connect_ssl",
+ "ssl/www.google.com:443",
+ true,
+ },
+
+ {
+ "http_proxy",
+ "http://host.with.alternate/direct",
+ "ssl/host.with.alternate:443",
+ true,
+ },
+
+ {
+ "http_proxy",
+ "ftp://ftp.google.com/http_proxy_normal",
+ "ftp/ftp.google.com:21",
+ false,
+ },
+ };
+
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed(tests[i].proxy_server));
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(GetParam(), &session_deps_));
+
+ HttpNetworkSessionPeer peer(session);
+
+ HostPortPair proxy_host("http_proxy", 80);
+ CaptureGroupNameHttpProxySocketPool* http_proxy_pool =
+ new CaptureGroupNameHttpProxySocketPool(NULL, NULL);
+ CaptureGroupNameSSLSocketPool* ssl_conn_pool =
+ new CaptureGroupNameSSLSocketPool(NULL, NULL);
+
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetSocketPoolForHTTPProxy(proxy_host, http_proxy_pool);
+ mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ http_proxy_pool->last_group_name_received());
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest, GroupNameForSOCKSConnections) {
+ const GroupNameTest tests[] = {
+ {
+ "socks4://socks_proxy:1080",
+ "http://www.google.com/socks4_direct",
+ "socks4/www.google.com:80",
+ false,
+ },
+ {
+ "socks5://socks_proxy:1080",
+ "http://www.google.com/socks5_direct",
+ "socks5/www.google.com:80",
+ false,
+ },
+
+ // SSL Tests
+ {
+ "socks4://socks_proxy:1080",
+ "https://www.google.com/socks4_ssl",
+ "socks4/ssl/www.google.com:443",
+ true,
+ },
+ {
+ "socks5://socks_proxy:1080",
+ "https://www.google.com/socks5_ssl",
+ "socks5/ssl/www.google.com:443",
+ true,
+ },
+
+ {
+ "socks4://socks_proxy:1080",
+ "http://host.with.alternate/direct",
+ "socks4/ssl/host.with.alternate:443",
+ true,
+ },
+ };
+
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed(tests[i].proxy_server));
+ scoped_refptr<HttpNetworkSession> session(
+ SetupSessionForGroupNameTests(GetParam(), &session_deps_));
+
+ HttpNetworkSessionPeer peer(session);
+
+ HostPortPair proxy_host("socks_proxy", 1080);
+ CaptureGroupNameSOCKSSocketPool* socks_conn_pool =
+ new CaptureGroupNameSOCKSSocketPool(NULL, NULL);
+ CaptureGroupNameSSLSocketPool* ssl_conn_pool =
+ new CaptureGroupNameSSLSocketPool(NULL, NULL);
+
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetSocketPoolForSOCKSProxy(proxy_host, socks_conn_pool);
+ mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ GroupNameTransactionHelper(tests[i].url, session));
+ if (tests[i].ssl)
+ EXPECT_EQ(tests[i].expected_group_name,
+ ssl_conn_pool->last_group_name_received());
+ else
+ EXPECT_EQ(tests[i].expected_group_name,
+ socks_conn_pool->last_group_name_received());
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest, ReconsiderProxyAfterFailedConnection) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("myproxy:70;foobar:80"));
+
+ // This simulates failure resolving all hostnames; that means we will fail
+ // connecting to both proxies (myproxy:70 and foobar:80).
+ session_deps_.host_resolver->rules()->AddSimulatedFailure("*");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv);
+}
+
+// Base test to make sure that when the load flags for a request specify to
+// bypass the cache, the DNS cache is not used.
+void HttpNetworkTransactionTest::BypassHostCacheOnRefreshHelper(
+ int load_flags) {
+ // Issue a request, asking to bypass the cache(s).
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.load_flags = load_flags;
+ request.url = GURL("http://www.google.com/");
+
+ // Select a host resolver that does caching.
+ session_deps_.host_resolver.reset(new MockCachingHostResolver);
+
+ scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ // Warm up the host cache so it has an entry for "www.google.com".
+ AddressList addrlist;
+ TestCompletionCallback callback;
+ int rv = session_deps_.host_resolver->Resolve(
+ HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist,
+ callback.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ // Verify that it was added to host cache, by doing a subsequent async lookup
+ // and confirming it completes synchronously.
+ rv = session_deps_.host_resolver->Resolve(
+ HostResolver::RequestInfo(HostPortPair("www.google.com", 80)), &addrlist,
+ callback.callback(), NULL, BoundNetLog());
+ ASSERT_EQ(OK, rv);
+
+ // Inject a failure the next time that "www.google.com" is resolved. This way
+ // we can tell if the next lookup hit the cache, or the "network".
+ // (cache --> success, "network" --> failure).
+ session_deps_.host_resolver->rules()->AddSimulatedFailure("www.google.com");
+
+ // Connect up a mock socket which will fail with ERR_UNEXPECTED during the
+ // first read -- this won't be reached as the host resolution will fail first.
+ MockRead data_reads[] = { MockRead(SYNCHRONOUS, ERR_UNEXPECTED) };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ // Run the request.
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // If we bypassed the cache, we would have gotten a failure while resolving
+ // "www.google.com".
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
+}
+
+// There are multiple load flags that should trigger the host cache bypass.
+// Test each in isolation:
+TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh1) {
+ BypassHostCacheOnRefreshHelper(LOAD_BYPASS_CACHE);
+}
+
+TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh2) {
+ BypassHostCacheOnRefreshHelper(LOAD_VALIDATE_CACHE);
+}
+
+TEST_P(HttpNetworkTransactionTest, BypassHostCacheOnRefresh3) {
+ BypassHostCacheOnRefreshHelper(LOAD_DISABLE_CACHE);
+}
+
+// Make sure we can handle an error when writing the request.
+TEST_P(HttpNetworkTransactionTest, RequestWriteError) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ MockWrite write_failure[] = {
+ MockWrite(ASYNC, ERR_CONNECTION_RESET),
+ };
+ StaticSocketDataProvider data(NULL, 0,
+ write_failure, arraysize(write_failure));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+}
+
+// Check that a connection closed after the start of the headers finishes ok.
+TEST_P(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.foo.com/");
+ request.load_flags = 0;
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1."),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("", response_data);
+}
+
+// Make sure that a dropped connection while draining the body for auth
+// restart does the right thing.
+TEST_P(HttpNetworkTransactionTest, DrainResetOK) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 14\r\n\r\n"),
+ MockRead("Unauth"),
+ MockRead(ASYNC, ERR_CONNECTION_RESET),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Lastly, the server responds with the actual content.
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// Test HTTPS connections going through a proxy that sends extra data.
+TEST_P(HttpNetworkTransactionTest, HTTPSViaProxyWithExtraData) {
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockRead proxy_reads[] = {
+ MockRead("HTTP/1.0 200 Connected\r\n\r\nExtra data"),
+ MockRead(SYNCHRONOUS, OK)
+ };
+
+ StaticSocketDataProvider data(proxy_reads, arraysize(proxy_reads), NULL, 0);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback;
+
+ session_deps_.socket_factory->ResetNextMockIndexes();
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, LargeContentLengthThenClose) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\nContent-Length:6719476739\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, UploadFileSmallerThanLength) {
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file_path));
+ const uint64 kFakeSize = 100000; // file is actually blank
+ UploadFileElementReader::ScopedOverridingContentLengthForTests
+ overriding_content_length(kFakeSize);
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data_stream = &upload_data_stream;
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("hello world", response_data);
+
+ base::DeleteFile(temp_file_path, false);
+}
+
+TEST_P(HttpNetworkTransactionTest, UploadUnreadableFile) {
+ base::FilePath temp_file;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file));
+ std::string temp_file_content("Unreadable file.");
+ ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_content.c_str(),
+ temp_file_content.length()));
+ ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file));
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data_stream = &upload_data_stream;
+ request.load_flags = 0;
+
+ // If we try to upload an unreadable file, the network stack should report
+ // the file size as zero and upload zero bytes for that file.
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.0 200 OK", response->headers->GetStatusLine());
+
+ base::DeleteFile(temp_file, false);
+}
+
+TEST_P(HttpNetworkTransactionTest, UnreadableUploadFileAfterAuthRestart) {
+ base::FilePath temp_file;
+ ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file));
+ std::string temp_file_contents("Unreadable file.");
+ std::string unreadable_contents(temp_file_contents.length(), '\0');
+ ASSERT_TRUE(file_util::WriteFile(temp_file, temp_file_contents.c_str(),
+ temp_file_contents.length()));
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file,
+ 0,
+ kuint64max,
+ base::Time()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/upload");
+ request.upload_data_stream = &upload_data_stream;
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"), // No response body.
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ MockWrite data_writes[] = {
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 16\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, temp_file_contents.c_str()),
+
+ MockWrite("POST /upload HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Content-Length: 0\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, unreadable_contents.c_str(),
+ temp_file_contents.length()),
+ MockWrite(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->headers->GetStatusLine());
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ // Now make the file unreadable and try again.
+ ASSERT_TRUE(file_util::MakeFileUnreadable(temp_file));
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ base::DeleteFile(temp_file, false);
+}
+
+// Tests that changes to Auth realms are treated like auth rejections.
+TEST_P(HttpNetworkTransactionTest, ChangeAuthRealms) {
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // First transaction will request a resource and receive a Basic challenge
+ // with realm="first_realm".
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"first_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // After calling trans->RestartWithAuth(), provide an Authentication header
+ // for first_realm. The server will reject and provide a challenge with
+ // second_realm.
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zmlyc3Q6YmF6\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"second_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // This again fails, and goes back to first_realm. Make sure that the
+ // entry is removed from cache.
+ MockWrite data_writes3[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic c2Vjb25kOmZvdQ==\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads3[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Basic realm=\"first_realm\"\r\n"
+ "\r\n"),
+ };
+
+ // Try one last time (with the correct password) and get the resource.
+ MockWrite data_writes4[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zmlyc3Q6YmFy\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads4[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 5\r\n"
+ "\r\n"
+ "hello"),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider data3(data_reads3, arraysize(data_reads3),
+ data_writes3, arraysize(data_writes3));
+ StaticSocketDataProvider data4(data_reads4, arraysize(data_reads4),
+ data_writes4, arraysize(data_writes4));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+ session_deps_.socket_factory->AddSocketDataProvider(&data4);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ // Issue the first request with Authorize headers. There should be a
+ // password prompt for first_realm waiting to be filled in after the
+ // transaction completes.
+ int rv = trans->Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ const AuthChallengeInfo* challenge = response->auth_challenge.get();
+ ASSERT_FALSE(challenge == NULL);
+ EXPECT_FALSE(challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80", challenge->challenger.ToString());
+ EXPECT_EQ("first_realm", challenge->realm);
+ EXPECT_EQ("basic", challenge->scheme);
+
+ // Issue the second request with an incorrect password. There should be a
+ // password prompt for second_realm waiting to be filled in after the
+ // transaction completes.
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFirst, kBaz), callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ challenge = response->auth_challenge.get();
+ ASSERT_FALSE(challenge == NULL);
+ EXPECT_FALSE(challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80", challenge->challenger.ToString());
+ EXPECT_EQ("second_realm", challenge->realm);
+ EXPECT_EQ("basic", challenge->scheme);
+
+ // Issue the third request with another incorrect password. There should be
+ // a password prompt for first_realm waiting to be filled in. If the password
+ // prompt is not present, it indicates that the HttpAuthCacheEntry for
+ // first_realm was not correctly removed.
+ TestCompletionCallback callback3;
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kSecond, kFou), callback3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ challenge = response->auth_challenge.get();
+ ASSERT_FALSE(challenge == NULL);
+ EXPECT_FALSE(challenge->is_proxy);
+ EXPECT_EQ("www.google.com:80", challenge->challenger.ToString());
+ EXPECT_EQ("first_realm", challenge->realm);
+ EXPECT_EQ("basic", challenge->scheme);
+
+ // Issue the fourth request with the correct password and username.
+ TestCompletionCallback callback4;
+ rv = trans->RestartWithAuth(
+ AuthCredentials(kFirst, kBar), callback4.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback4.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+}
+
+TEST_P(HttpNetworkTransactionTest, HonorAlternateProtocolHeader) {
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ HostPortPair http_host_port_pair("www.google.com", 80);
+ const HttpServerProperties& http_server_properties =
+ *session->http_server_properties();
+ EXPECT_FALSE(
+ http_server_properties.HasAlternateProtocol(http_host_port_pair));
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ ASSERT_TRUE(http_server_properties.HasAlternateProtocol(http_host_port_pair));
+ const PortAlternateProtocolPair alternate =
+ http_server_properties.GetAlternateProtocol(http_host_port_pair);
+ PortAlternateProtocolPair expected_alternate;
+ expected_alternate.port = 443;
+ expected_alternate.protocol = AlternateProtocolFromNextProto(GetParam());
+ EXPECT_TRUE(expected_alternate.Equals(alternate));
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ MarkBrokenAlternateProtocolAndFallback) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ // Port must be < 1024, or the header will be ignored (since initial port was
+ // port 80 (another restricted port).
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(request.url),
+ 666 /* port is ignored by MockConnect anyway */,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ ASSERT_TRUE(http_server_properties->HasAlternateProtocol(
+ HostPortPair::FromURL(request.url)));
+ const PortAlternateProtocolPair alternate =
+ http_server_properties->GetAlternateProtocol(
+ HostPortPair::FromURL(request.url));
+ EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol);
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolPortRestrictedBlocked) {
+ // Ensure that we're not allowed to redirect traffic via an alternate
+ // protocol to an unrestricted (port >= 1024) when the original traffic was
+ // on a restricted port (port < 1024). Ensure that we can redirect in all
+ // other cases.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo restricted_port_request;
+ restricted_port_request.method = "GET";
+ restricted_port_request.url = GURL("http://www.google.com:1023/");
+ restricted_port_request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kUnrestrictedAlternatePort = 1024;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(restricted_port_request.url),
+ kUnrestrictedAlternatePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &restricted_port_request,
+ callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // Invalid change to unrestricted port should fail.
+ EXPECT_EQ(ERR_CONNECTION_REFUSED, callback.WaitForResult());
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolPortRestrictedPermitted) {
+ // Ensure that we're allowed to redirect traffic via an alternate
+ // protocol to an unrestricted (port >= 1024) when the original traffic was
+ // on a restricted port (port < 1024) if we set
+ // enable_user_alternate_protocol_ports.
+
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ session_deps_.enable_user_alternate_protocol_ports = true;
+
+ HttpRequestInfo restricted_port_request;
+ restricted_port_request.method = "GET";
+ restricted_port_request.url = GURL("http://www.google.com:1023/");
+ restricted_port_request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kUnrestrictedAlternatePort = 1024;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(restricted_port_request.url),
+ kUnrestrictedAlternatePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ EXPECT_EQ(ERR_IO_PENDING, trans->Start(
+ &restricted_port_request,
+ callback.callback(), BoundNetLog()));
+ // Change to unrestricted port should succeed.
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolPortRestrictedAllowed) {
+ // Ensure that we're not allowed to redirect traffic via an alternate
+ // protocol to an unrestricted (port >= 1024) when the original traffic was
+ // on a restricted port (port < 1024). Ensure that we can redirect in all
+ // other cases.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo restricted_port_request;
+ restricted_port_request.method = "GET";
+ restricted_port_request.url = GURL("http://www.google.com:1023/");
+ restricted_port_request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kRestrictedAlternatePort = 80;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(restricted_port_request.url),
+ kRestrictedAlternatePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &restricted_port_request,
+ callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // Valid change to restricted port should pass.
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolPortUnrestrictedAllowed1) {
+ // Ensure that we're not allowed to redirect traffic via an alternate
+ // protocol to an unrestricted (port >= 1024) when the original traffic was
+ // on a restricted port (port < 1024). Ensure that we can redirect in all
+ // other cases.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo unrestricted_port_request;
+ unrestricted_port_request.method = "GET";
+ unrestricted_port_request.url = GURL("http://www.google.com:1024/");
+ unrestricted_port_request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kRestrictedAlternatePort = 80;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(unrestricted_port_request.url),
+ kRestrictedAlternatePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &unrestricted_port_request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // Valid change to restricted port should pass.
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolPortUnrestrictedAllowed2) {
+ // Ensure that we're not allowed to redirect traffic via an alternate
+ // protocol to an unrestricted (port >= 1024) when the original traffic was
+ // on a restricted port (port < 1024). Ensure that we can redirect in all
+ // other cases.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo unrestricted_port_request;
+ unrestricted_port_request.method = "GET";
+ unrestricted_port_request.url = GURL("http://www.google.com:1024/");
+ unrestricted_port_request.load_flags = 0;
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider first_data;
+ first_data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_data);
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider second_data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&second_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kUnrestrictedAlternatePort = 1024;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(unrestricted_port_request.url),
+ kUnrestrictedAlternatePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &unrestricted_port_request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // Valid change to an unrestricted port should pass.
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ AlternateProtocolUnsafeBlocked) {
+ // Ensure that we're not allowed to redirect traffic via an alternate
+ // protocol to an unsafe port, and that we resume the second
+ // HttpStreamFactoryImpl::Job once the alternate protocol request fails.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ // The alternate protocol request will error out before we attempt to connect,
+ // so only the standard HTTP request will try to connect.
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+ StaticSocketDataProvider data(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session->http_server_properties();
+ const int kUnsafePort = 7;
+ http_server_properties->SetAlternateProtocol(
+ HostPortPair::FromURL(request.url),
+ kUnsafePort,
+ AlternateProtocolFromNextProto(GetParam()));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // The HTTP request should succeed.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Disable alternate protocol before the asserts.
+ HttpStreamFactory::set_use_alternate_protocols(false);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, UseAlternateProtocolForNpnSpdy) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(ASYNC, 0, 0),
+ };
+
+ DelayedSocketData spdy_data(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider hanging_non_alternate_protocol_socket(
+ NULL, 0, NULL, 0);
+ hanging_non_alternate_protocol_socket.set_connect_data(
+ never_finishing_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(
+ &hanging_non_alternate_protocol_socket);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, AlternateProtocolWithSpdyLateBinding) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ // Socket 1 is the HTTP transaction with the Alternate-Protocol header.
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider hanging_socket(
+ NULL, 0, NULL, 0);
+ hanging_socket.set_connect_data(never_finishing_connect);
+ // Socket 2 and 3 are the hanging Alternate-Protocol and
+ // non-Alternate-Protocol jobs from the 2nd transaction.
+ session_deps_.socket_factory->AddSocketDataProvider(&hanging_socket);
+ session_deps_.socket_factory->AddSocketDataProvider(&hanging_socket);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req1),
+ CreateMockWrite(*req2),
+ };
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> data2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp1),
+ CreateMockRead(*data1),
+ CreateMockRead(*resp2),
+ CreateMockRead(*data2),
+ MockRead(ASYNC, 0, 0),
+ };
+
+ DelayedSocketData spdy_data(
+ 2, // wait for writes to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ // Socket 4 is the successful Alternate-Protocol for transaction 3.
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ // Socket 5 is the unsuccessful non-Alternate-Protocol for transaction 3.
+ session_deps_.socket_factory->AddSocketDataProvider(&hanging_socket);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ TestCompletionCallback callback1;
+ HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
+
+ int rv = trans1.Start(&request, callback1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ const HttpResponseInfo* response = trans1.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ TestCompletionCallback callback2;
+ HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
+ rv = trans2.Start(&request, callback2.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback3;
+ HttpNetworkTransaction trans3(DEFAULT_PRIORITY, session.get());
+ rv = trans3.Start(&request, callback3.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(OK, callback3.WaitForResult());
+
+ response = trans2.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ response = trans3.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(&trans3, &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, StallAlternateProtocolForNpnSpdy) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider hanging_alternate_protocol_socket(
+ NULL, 0, NULL, 0);
+ hanging_alternate_protocol_socket.set_connect_data(
+ never_finishing_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(
+ &hanging_alternate_protocol_socket);
+
+ // 2nd request is just a copy of the first one, over HTTP again.
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+}
+
+class CapturingProxyResolver : public ProxyResolver {
+ public:
+ CapturingProxyResolver() : ProxyResolver(false /* expects_pac_bytes */) {}
+ virtual ~CapturingProxyResolver() {}
+
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ ProxyServer proxy_server(ProxyServer::SCHEME_HTTP,
+ HostPortPair("myproxy", 80));
+ results->UseProxyServer(proxy_server);
+ resolved_.push_back(url);
+ return OK;
+ }
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+
+ virtual void CancelSetPacScript() OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(const scoped_refptr<ProxyResolverScriptData>&,
+ const CompletionCallback& /*callback*/) OVERRIDE {
+ return OK;
+ }
+
+ const std::vector<GURL>& resolved() const { return resolved_; }
+
+ private:
+ std::vector<GURL> resolved_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapturingProxyResolver);
+};
+
+TEST_P(HttpNetworkTransactionTest,
+ UseAlternateProtocolForTunneledNpnSpdy) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ ProxyConfig proxy_config;
+ proxy_config.set_auto_detect(true);
+ proxy_config.set_pac_url(GURL("http://fooproxyurl"));
+
+ CapturingProxyResolver* capturing_proxy_resolver =
+ new CapturingProxyResolver();
+ session_deps_.proxy_service.reset(new ProxyService(
+ new ProxyConfigServiceFixed(proxy_config), capturing_proxy_resolver,
+ NULL));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite spdy_writes[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"), // 0
+ CreateMockWrite(*req), // 3
+ };
+
+ const char kCONNECTResponse[] = "HTTP/1.1 200 Connected\r\n\r\n";
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ MockRead(ASYNC, kCONNECTResponse, arraysize(kCONNECTResponse) - 1, 1), // 1
+ CreateMockRead(*resp.get(), 4), // 2, 4
+ CreateMockRead(*data.get(), 4), // 5
+ MockRead(ASYNC, 0, 0, 4), // 6
+ };
+
+ OrderedSocketData spdy_data(
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider hanging_non_alternate_protocol_socket(
+ NULL, 0, NULL, 0);
+ hanging_non_alternate_protocol_socket.set_connect_data(
+ never_finishing_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(
+ &hanging_non_alternate_protocol_socket);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+ ASSERT_EQ(3u, capturing_proxy_resolver->resolved().size());
+ EXPECT_EQ("http://www.google.com/",
+ capturing_proxy_resolver->resolved()[0].spec());
+ EXPECT_EQ("https://www.google.com/",
+ capturing_proxy_resolver->resolved()[1].spec());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+TEST_P(HttpNetworkTransactionTest,
+ UseAlternateProtocolForNpnSpdyWithExistingSpdySession) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(ASYNC, OK),
+ };
+
+ StaticSocketDataProvider first_transaction(
+ data_reads, arraysize(data_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&first_transaction);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(ASYNC, 0, 0),
+ };
+
+ DelayedSocketData spdy_data(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ // Set up an initial SpdySession in the pool to reuse.
+ HostPortPair host_port_pair("www.google.com", 443);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> spdy_session =
+ CreateSecureSpdySession(session, key, BoundNetLog());
+
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+
+// GenerateAuthToken is a mighty big test.
+// It tests all permutation of GenerateAuthToken behavior:
+// - Synchronous and Asynchronous completion.
+// - OK or error on completion.
+// - Direct connection, non-authenticating proxy, and authenticating proxy.
+// - HTTP or HTTPS backend (to include proxy tunneling).
+// - Non-authenticating and authenticating backend.
+//
+// In all, there are 44 reasonable permuations (for example, if there are
+// problems generating an auth token for an authenticating proxy, we don't
+// need to test all permutations of the backend server).
+//
+// The test proceeds by going over each of the configuration cases, and
+// potentially running up to three rounds in each of the tests. The TestConfig
+// specifies both the configuration for the test as well as the expectations
+// for the results.
+TEST_P(HttpNetworkTransactionTest, GenerateAuthToken) {
+ static const char kServer[] = "http://www.example.com";
+ static const char kSecureServer[] = "https://www.example.com";
+ static const char kProxy[] = "myproxy:70";
+ const int kAuthErr = ERR_INVALID_AUTH_CREDENTIALS;
+
+ enum AuthTiming {
+ AUTH_NONE,
+ AUTH_SYNC,
+ AUTH_ASYNC,
+ };
+
+ const MockWrite kGet(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetProxy(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetAuth(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetProxyAuth(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetAuthThroughProxy(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kGetAuthWithProxyAuth(
+ "GET http://www.example.com/ HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n"
+ "Authorization: auth_token\r\n\r\n");
+ const MockWrite kConnect(
+ "CONNECT www.example.com:443 HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n");
+ const MockWrite kConnectProxyAuth(
+ "CONNECT www.example.com:443 HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n\r\n");
+
+ const MockRead kSuccess(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "Yes");
+ const MockRead kFailure(
+ "Should not be called.");
+ const MockRead kServerChallenge(
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Mock realm=server\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kProxyChallenge(
+ "HTTP/1.1 407 Unauthorized\r\n"
+ "Proxy-Authenticate: Mock realm=proxy\r\n"
+ "Proxy-Connection: close\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kProxyConnected(
+ "HTTP/1.1 200 Connection Established\r\n\r\n");
+
+ // NOTE(cbentzel): I wanted TestReadWriteRound to be a simple struct with
+ // no constructors, but the C++ compiler on Windows warns about
+ // unspecified data in compound literals. So, moved to using constructors,
+ // and TestRound's created with the default constructor should not be used.
+ struct TestRound {
+ TestRound()
+ : expected_rv(ERR_UNEXPECTED),
+ extra_write(NULL),
+ extra_read(NULL) {
+ }
+ TestRound(const MockWrite& write_arg, const MockRead& read_arg,
+ int expected_rv_arg)
+ : write(write_arg),
+ read(read_arg),
+ expected_rv(expected_rv_arg),
+ extra_write(NULL),
+ extra_read(NULL) {
+ }
+ TestRound(const MockWrite& write_arg, const MockRead& read_arg,
+ int expected_rv_arg, const MockWrite* extra_write_arg,
+ const MockRead* extra_read_arg)
+ : write(write_arg),
+ read(read_arg),
+ expected_rv(expected_rv_arg),
+ extra_write(extra_write_arg),
+ extra_read(extra_read_arg) {
+ }
+ MockWrite write;
+ MockRead read;
+ int expected_rv;
+ const MockWrite* extra_write;
+ const MockRead* extra_read;
+ };
+
+ static const int kNoSSL = 500;
+
+ struct TestConfig {
+ const char* proxy_url;
+ AuthTiming proxy_auth_timing;
+ int proxy_auth_rv;
+ const char* server_url;
+ AuthTiming server_auth_timing;
+ int server_auth_rv;
+ int num_auth_rounds;
+ int first_ssl_round;
+ TestRound rounds[3];
+ } test_configs[] = {
+ // Non-authenticating HTTP server with a direct connection.
+ { NULL, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL,
+ { TestRound(kGet, kSuccess, OK)}},
+ // Authenticating HTTP server with a direct connection.
+ { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTP server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_NONE, OK, 1, kNoSSL,
+ { TestRound(kGetProxy, kSuccess, OK)}},
+ // Authenticating HTTP server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_SYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kServer, AUTH_ASYNC, kAuthErr, 2, kNoSSL,
+ { TestRound(kGetProxy, kServerChallenge, OK),
+ TestRound(kGetAuthThroughProxy, kFailure, kAuthErr)}},
+ // Non-authenticating HTTP server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, kAuthErr, kServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kFailure, kAuthErr)}},
+ // Authenticating HTTP server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_SYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, OK, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kServer, AUTH_ASYNC, kAuthErr, 3, kNoSSL,
+ { TestRound(kGetProxy, kProxyChallenge, OK),
+ TestRound(kGetProxyAuth, kServerChallenge, OK),
+ TestRound(kGetAuthWithProxyAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTPS server with a direct connection.
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0,
+ { TestRound(kGet, kSuccess, OK)}},
+ // Authenticating HTTPS server with a direct connection.
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { NULL, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0,
+ { TestRound(kGet, kServerChallenge, OK),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-authenticating HTTPS server with a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_NONE, OK, 1, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kSuccess)}},
+ // Authenticating HTTPS server through a non-authenticating proxy.
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, OK, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_SYNC, kAuthErr, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, OK, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_NONE, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 2, 0,
+ { TestRound(kConnect, kProxyConnected, OK, &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ // Non-Authenticating HTTPS server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}},
+ { kProxy, AUTH_SYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_NONE, OK, 2, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK, &kGet, &kSuccess)}},
+ { kProxy, AUTH_ASYNC, kAuthErr, kSecureServer, AUTH_NONE, OK, 2, kNoSSL,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kFailure, kAuthErr)}},
+ // Authenticating HTTPS server through an authenticating proxy.
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_SYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_SYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, OK, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kSuccess, OK)}},
+ { kProxy, AUTH_ASYNC, OK, kSecureServer, AUTH_ASYNC, kAuthErr, 3, 1,
+ { TestRound(kConnect, kProxyChallenge, OK),
+ TestRound(kConnectProxyAuth, kProxyConnected, OK,
+ &kGet, &kServerChallenge),
+ TestRound(kGetAuth, kFailure, kAuthErr)}},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_configs); ++i) {
+ HttpAuthHandlerMock::Factory* auth_factory(
+ new HttpAuthHandlerMock::Factory());
+ session_deps_.http_auth_handler_factory.reset(auth_factory);
+ const TestConfig& test_config = test_configs[i];
+
+ // Set up authentication handlers as necessary.
+ if (test_config.proxy_auth_timing != AUTH_NONE) {
+ for (int n = 0; n < 2; n++) {
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ std::string auth_challenge = "Mock realm=proxy";
+ GURL origin(test_config.proxy_url);
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_PROXY,
+ origin, BoundNetLog());
+ auth_handler->SetGenerateExpectation(
+ test_config.proxy_auth_timing == AUTH_ASYNC,
+ test_config.proxy_auth_rv);
+ auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY);
+ }
+ }
+ if (test_config.server_auth_timing != AUTH_NONE) {
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ std::string auth_challenge = "Mock realm=server";
+ GURL origin(test_config.server_url);
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
+ origin, BoundNetLog());
+ auth_handler->SetGenerateExpectation(
+ test_config.server_auth_timing == AUTH_ASYNC,
+ test_config.server_auth_rv);
+ auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER);
+ }
+ if (test_config.proxy_url) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed(test_config.proxy_url));
+ } else {
+ session_deps_.proxy_service.reset(ProxyService::CreateDirect());
+ }
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(test_config.server_url);
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ HttpNetworkTransaction trans(
+ DEFAULT_PRIORITY, CreateSession(&session_deps_));
+
+ for (int round = 0; round < test_config.num_auth_rounds; ++round) {
+ const TestRound& read_write_round = test_config.rounds[round];
+
+ // Set up expected reads and writes.
+ MockRead reads[2];
+ reads[0] = read_write_round.read;
+ size_t length_reads = 1;
+ if (read_write_round.extra_read) {
+ reads[1] = *read_write_round.extra_read;
+ length_reads = 2;
+ }
+
+ MockWrite writes[2];
+ writes[0] = read_write_round.write;
+ size_t length_writes = 1;
+ if (read_write_round.extra_write) {
+ writes[1] = *read_write_round.extra_write;
+ length_writes = 2;
+ }
+ StaticSocketDataProvider data_provider(
+ reads, length_reads, writes, length_writes);
+ session_deps_.socket_factory->AddSocketDataProvider(&data_provider);
+
+ // Add an SSL sequence if necessary.
+ SSLSocketDataProvider ssl_socket_data_provider(SYNCHRONOUS, OK);
+ if (round >= test_config.first_ssl_round)
+ session_deps_.socket_factory->AddSSLSocketDataProvider(
+ &ssl_socket_data_provider);
+
+ // Start or restart the transaction.
+ TestCompletionCallback callback;
+ int rv;
+ if (round == 0) {
+ rv = trans.Start(&request, callback.callback(), BoundNetLog());
+ } else {
+ rv = trans.RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback.callback());
+ }
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ // Compare results with expected data.
+ EXPECT_EQ(read_write_round.expected_rv, rv);
+ const HttpResponseInfo* response = trans.GetResponseInfo();
+ if (read_write_round.expected_rv == OK) {
+ ASSERT_TRUE(response != NULL);
+ } else {
+ EXPECT_TRUE(response == NULL);
+ EXPECT_EQ(round + 1, test_config.num_auth_rounds);
+ continue;
+ }
+ if (round + 1 < test_config.num_auth_rounds) {
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+ } else {
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ }
+ }
+ }
+}
+
+TEST_P(HttpNetworkTransactionTest, MultiRoundAuth) {
+ // Do multi-round authentication and make sure it works correctly.
+ HttpAuthHandlerMock::Factory* auth_factory(
+ new HttpAuthHandlerMock::Factory());
+ session_deps_.http_auth_handler_factory.reset(auth_factory);
+ session_deps_.proxy_service.reset(ProxyService::CreateDirect());
+ session_deps_.host_resolver->rules()->AddRule("www.example.com", "10.0.0.1");
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ HttpAuthHandlerMock* auth_handler(new HttpAuthHandlerMock());
+ auth_handler->set_connection_based(true);
+ std::string auth_challenge = "Mock realm=server";
+ GURL origin("http://www.example.com");
+ HttpAuth::ChallengeTokenizer tokenizer(auth_challenge.begin(),
+ auth_challenge.end());
+ auth_handler->InitFromChallenge(&tokenizer, HttpAuth::AUTH_SERVER,
+ origin, BoundNetLog());
+ auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_SERVER);
+
+ int rv = OK;
+ const HttpResponseInfo* response = NULL;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = origin;
+ request.load_flags = 0;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Use a TCP Socket Pool with only one connection per group. This is used
+ // to validate that the TCP socket is not released to the pool between
+ // each round of multi-round authentication.
+ HttpNetworkSessionPeer session_peer(session);
+ ClientSocketPoolHistograms transport_pool_histograms("SmallTCP");
+ TransportClientSocketPool* transport_pool = new TransportClientSocketPool(
+ 50, // Max sockets for pool
+ 1, // Max sockets per group
+ &transport_pool_histograms,
+ session_deps_.host_resolver.get(),
+ session_deps_.socket_factory.get(),
+ session_deps_.net_log);
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetTransportSocketPool(transport_pool);
+ session_peer.SetClientSocketPoolManager(mock_pool_manager);
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+
+ const MockWrite kGet(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n\r\n");
+ const MockWrite kGetAuth(
+ "GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n");
+
+ const MockRead kServerChallenge(
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Mock realm=server\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "Unauthorized\r\n");
+ const MockRead kSuccess(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=iso-8859-1\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "Yes");
+
+ MockWrite writes[] = {
+ // First round
+ kGet,
+ // Second round
+ kGetAuth,
+ // Third round
+ kGetAuth,
+ // Fourth round
+ kGetAuth,
+ // Competing request
+ kGet,
+ };
+ MockRead reads[] = {
+ // First round
+ kServerChallenge,
+ // Second round
+ kServerChallenge,
+ // Third round
+ kServerChallenge,
+ // Fourth round
+ kSuccess,
+ // Competing response
+ kSuccess,
+ };
+ StaticSocketDataProvider data_provider(reads, arraysize(reads),
+ writes, arraysize(writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data_provider);
+
+ const char* const kSocketGroup = "www.example.com:80";
+
+ // First round of authentication.
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+
+ // In between rounds, another request comes in for the same domain.
+ // It should not be able to grab the TCP socket that trans has already
+ // claimed.
+ scoped_ptr<HttpTransaction> trans_compete(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback_compete;
+ rv = trans_compete->Start(
+ &request, callback_compete.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // callback_compete.WaitForResult at this point would stall forever,
+ // since the HttpNetworkTransaction does not release the request back to
+ // the pool until after authentication completes.
+
+ // Second round of authentication.
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(AuthCredentials(kFoo, kBar), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+
+ // Third round of authentication.
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+
+ // Fourth round of authentication, which completes successfully.
+ auth_handler->SetGenerateExpectation(false, OK);
+ rv = trans->RestartWithAuth(AuthCredentials(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+
+ // Read the body since the fourth round was successful. This will also
+ // release the socket back to the pool.
+ scoped_refptr<IOBufferWithSize> io_buf(new IOBufferWithSize(50));
+ rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(3, rv);
+ rv = trans->Read(io_buf.get(), io_buf->size(), callback.callback());
+ EXPECT_EQ(0, rv);
+ // There are still 0 idle sockets, since the trans_compete transaction
+ // will be handed it immediately after trans releases it to the group.
+ EXPECT_EQ(0, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+
+ // The competing request can now finish. Wait for the headers and then
+ // read the body.
+ rv = callback_compete.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ rv = trans_compete->Read(io_buf.get(), io_buf->size(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(3, rv);
+ rv = trans_compete->Read(io_buf.get(), io_buf->size(), callback.callback());
+ EXPECT_EQ(0, rv);
+
+ // Finally, the socket is released to the group.
+ EXPECT_EQ(1, transport_pool->IdleSocketCountInGroup(kSocketGroup));
+}
+
+// This tests the case that a request is issued via http instead of spdy after
+// npn is negotiated.
+TEST_P(HttpNetworkTransactionTest, NpnWithHttpOverSSL) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ HttpStreamFactory::SetNextProtos(next_protos);
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ std::string alternate_protocol_http_header =
+ GetAlternateProtocolHttpHeader();
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(alternate_protocol_http_header.c_str()),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ ssl.next_proto = "http/1.1";
+ ssl.protocol_negotiated = kProtoHTTP11;
+
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello world", response_data);
+
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+}
+
+TEST_P(HttpNetworkTransactionTest, SpdyPostNPNServerHangup) {
+ // Simulate the SSL handshake completing with an NPN negotiation
+ // followed by an immediate server closing of the socket.
+ // Fix crash: http://crbug.com/46369
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ MockRead spdy_reads[] = {
+ MockRead(SYNCHRONOUS, 0, 0) // Not async - return 0 immediately.
+ };
+
+ DelayedSocketData spdy_data(
+ 0, // don't wait in this case, immediate hangup.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ TestCompletionCallback callback;
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+}
+
+// A subclass of HttpAuthHandlerMock that records the request URL when
+// it gets it. This is needed since the auth handler may get destroyed
+// before we get a chance to query it.
+class UrlRecordingHttpAuthHandlerMock : public HttpAuthHandlerMock {
+ public:
+ explicit UrlRecordingHttpAuthHandlerMock(GURL* url) : url_(url) {}
+
+ virtual ~UrlRecordingHttpAuthHandlerMock() {}
+
+ protected:
+ virtual int GenerateAuthTokenImpl(const AuthCredentials* credentials,
+ const HttpRequestInfo* request,
+ const CompletionCallback& callback,
+ std::string* auth_token) OVERRIDE {
+ *url_ = request->url;
+ return HttpAuthHandlerMock::GenerateAuthTokenImpl(
+ credentials, request, callback, auth_token);
+ }
+
+ private:
+ GURL* url_;
+};
+
+TEST_P(HttpNetworkTransactionTest, SpdyAlternateProtocolThroughProxy) {
+ // This test ensures that the URL passed into the proxy is upgraded
+ // to https when doing an Alternate Protocol upgrade.
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingNetLog net_log;
+ session_deps_.net_log = &net_log;
+ GURL request_url;
+ {
+ HttpAuthHandlerMock::Factory* auth_factory =
+ new HttpAuthHandlerMock::Factory();
+ UrlRecordingHttpAuthHandlerMock* auth_handler =
+ new UrlRecordingHttpAuthHandlerMock(&request_url);
+ auth_factory->AddMockHandler(auth_handler, HttpAuth::AUTH_PROXY);
+ auth_factory->set_do_init_from_challenge(true);
+ session_deps_.http_auth_handler_factory.reset(auth_factory);
+ }
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com");
+ request.load_flags = 0;
+
+ // First round goes unauthenticated through the proxy.
+ MockWrite data_writes_1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "\r\n"),
+ };
+ MockRead data_reads_1[] = {
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Alternate-Protocol: 443:npn-spdy/2\r\n"
+ "Proxy-Connection: close\r\n"
+ "\r\n"),
+ };
+ StaticSocketDataProvider data_1(data_reads_1, arraysize(data_reads_1),
+ data_writes_1, arraysize(data_writes_1));
+
+ // Second round tries to tunnel to www.google.com due to the
+ // Alternate-Protocol announcement in the first round. It fails due
+ // to a proxy authentication challenge.
+ // After the failure, a tunnel is established to www.google.com using
+ // Proxy-Authorization headers. There is then a SPDY request round.
+ //
+ // NOTE: Despite the "Proxy-Connection: Close", these are done on the
+ // same MockTCPClientSocket since the underlying HttpNetworkClientSocket
+ // does a Disconnect and Connect on the same socket, rather than trying
+ // to obtain a new one.
+ //
+ // NOTE: Originally, the proxy response to the second CONNECT request
+ // simply returned another 407 so the unit test could skip the SSL connection
+ // establishment and SPDY framing issues. Alas, the
+ // retry-http-when-alternate-protocol fails logic kicks in, which was more
+ // complicated to set up expectations for than the SPDY session.
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ MockWrite data_writes_2[] = {
+ // First connection attempt without Proxy-Authorization.
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "\r\n"),
+
+ // Second connection attempt with Proxy-Authorization.
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: auth_token\r\n"
+ "\r\n"),
+
+ // SPDY request
+ CreateMockWrite(*req),
+ };
+ const char kRejectConnectResponse[] = ("HTTP/1.1 407 Unauthorized\r\n"
+ "Proxy-Authenticate: Mock\r\n"
+ "Proxy-Connection: close\r\n"
+ "\r\n");
+ const char kAcceptConnectResponse[] = "HTTP/1.1 200 Connected\r\n\r\n";
+ MockRead data_reads_2[] = {
+ // First connection attempt fails
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, 1),
+ MockRead(ASYNC, kRejectConnectResponse,
+ arraysize(kRejectConnectResponse) - 1, 1),
+
+ // Second connection attempt passes
+ MockRead(ASYNC, kAcceptConnectResponse,
+ arraysize(kAcceptConnectResponse) -1, 4),
+
+ // SPDY response
+ CreateMockRead(*resp.get(), 6),
+ CreateMockRead(*data.get(), 6),
+ MockRead(ASYNC, 0, 0, 6),
+ };
+ OrderedSocketData data_2(
+ data_reads_2, arraysize(data_reads_2),
+ data_writes_2, arraysize(data_writes_2));
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider hanging_non_alternate_protocol_socket(
+ NULL, 0, NULL, 0);
+ hanging_non_alternate_protocol_socket.set_connect_data(
+ never_finishing_connect);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data_1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data_2);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSocketDataProvider(
+ &hanging_non_alternate_protocol_socket);
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // First round should work and provide the Alternate-Protocol state.
+ TestCompletionCallback callback_1;
+ scoped_ptr<HttpTransaction> trans_1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ int rv = trans_1->Start(&request, callback_1.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_1.WaitForResult());
+
+ // Second round should attempt a tunnel connect and get an auth challenge.
+ TestCompletionCallback callback_2;
+ scoped_ptr<HttpTransaction> trans_2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ rv = trans_2->Start(&request, callback_2.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_2.WaitForResult());
+ const HttpResponseInfo* response = trans_2->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_FALSE(response->auth_challenge.get() == NULL);
+
+ // Restart with auth. Tunnel should work and response received.
+ TestCompletionCallback callback_3;
+ rv = trans_2->RestartWithAuth(
+ AuthCredentials(kFoo, kBar), callback_3.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_3.WaitForResult());
+
+ // After all that work, these two lines (or actually, just the scheme) are
+ // what this test is all about. Make sure it happens correctly.
+ EXPECT_EQ("https", request_url.scheme());
+ EXPECT_EQ("www.google.com", request_url.host());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans_2->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+// Test that if we cancel the transaction as the connection is completing, that
+// everything tears down correctly.
+TEST_P(HttpNetworkTransactionTest, SimpleCancel) {
+ // Setup everything about the connection to complete synchronously, so that
+ // after calling HttpNetworkTransaction::Start, the only thing we're waiting
+ // for is the callback from the HttpStreamRequest.
+ // Then cancel the transaction.
+ // Verify that we don't crash.
+ MockConnect mock_connect(SYNCHRONOUS, OK);
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, "HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, "hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+ data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ CapturingBoundNetLog log;
+ int rv = trans->Start(&request, callback.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ trans.reset(); // Cancel the transaction here.
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test a basic GET request through a proxy.
+TEST_P(HttpNetworkTransactionTest, ProxyGet) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET http://www.google.com/ HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ EXPECT_TRUE(response->was_fetched_via_proxy);
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+}
+
+// Test a basic HTTPS GET request through a proxy.
+TEST_P(HttpNetworkTransactionTest, ProxyTunnelGet) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(200, response->headers->response_code());
+ EXPECT_EQ(100, response->headers->GetContentLength());
+ EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion());
+ EXPECT_TRUE(response->was_fetched_via_proxy);
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(trans->GetLoadTimingInfo(&load_timing_info));
+ TestLoadTimingNotReusedWithPac(load_timing_info,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+// Test a basic HTTPS GET request through a proxy, but the server hangs up
+// while establishing the tunnel.
+TEST_P(HttpNetworkTransactionTest, ProxyTunnelGetHangup) {
+ session_deps_.proxy_service.reset(ProxyService::CreateFixed("myproxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback callback1;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request, callback1.callback(), log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, rv);
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t pos = ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE);
+ ExpectLogContainsSomewhere(
+ entries, pos,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE);
+}
+
+// Test for crbug.com/55424.
+TEST_P(HttpNetworkTransactionTest, PreconnectWithExistingSpdySession) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet("https://www.google.com", false, 1, LOWEST));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> data(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*data),
+ MockRead(ASYNC, 0, 0),
+ };
+
+ DelayedSocketData spdy_data(
+ 1, // wait for one write to finish before reading.
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Set up an initial SpdySession in the pool to reuse.
+ HostPortPair host_port_pair("www.google.com", 443);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> spdy_session =
+ CreateInsecureSpdySession(session, key, BoundNetLog());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // This is the important line that marks this as a preconnect.
+ request.motivation = HttpRequestInfo::PRECONNECT_MOTIVATED;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Given a net error, cause that error to be returned from the first Write()
+// call and verify that the HttpTransaction fails with that error.
+void HttpNetworkTransactionTest::CheckErrorIsPassedBack(
+ int error, IoMode mode) {
+ net::HttpRequestInfo request_info;
+ request_info.url = GURL("https://www.example.com/");
+ request_info.method = "GET";
+ request_info.load_flags = net::LOAD_NORMAL;
+
+ SSLSocketDataProvider ssl_data(mode, OK);
+ net::MockWrite data_writes[] = {
+ net::MockWrite(mode, error),
+ };
+ net::StaticSocketDataProvider data(NULL, 0,
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ ASSERT_EQ(error, rv);
+}
+
+TEST_P(HttpNetworkTransactionTest, SSLWriteCertError) {
+ // Just check a grab bag of cert errors.
+ static const int kErrors[] = {
+ ERR_CERT_COMMON_NAME_INVALID,
+ ERR_CERT_AUTHORITY_INVALID,
+ ERR_CERT_DATE_INVALID,
+ };
+ for (size_t i = 0; i < arraysize(kErrors); i++) {
+ CheckErrorIsPassedBack(kErrors[i], ASYNC);
+ CheckErrorIsPassedBack(kErrors[i], SYNCHRONOUS);
+ }
+}
+
+// Ensure that a client certificate is removed from the SSL client auth
+// cache when:
+// 1) No proxy is involved.
+// 2) TLS False Start is disabled.
+// 3) The initial TLS handshake requests a client certificate.
+// 4) The client supplies an invalid/unacceptable certificate.
+TEST_P(HttpNetworkTransactionTest,
+ ClientAuthCertCache_Direct_NoFalseStart) {
+ net::HttpRequestInfo request_info;
+ request_info.url = GURL("https://www.example.com/");
+ request_info.method = "GET";
+ request_info.load_flags = net::LOAD_NORMAL;
+
+ scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
+ cert_request->host_and_port = "www.example.com:443";
+
+ // [ssl_]data1 contains the data for the first SSL handshake. When a
+ // CertificateRequest is received for the first time, the handshake will
+ // be aborted to allow the caller to provide a certificate.
+ SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
+ ssl_data1.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
+ net::StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ // [ssl_]data2 contains the data for the second SSL handshake. When TLS
+ // False Start is not being used, the result of the SSL handshake will be
+ // returned as part of the SSLClientSocket::Connect() call. This test
+ // matches the result of a server sending a handshake_failure alert,
+ // rather than a Finished message, because it requires a client
+ // certificate and none was supplied.
+ SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data2.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
+ net::StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ // [ssl_]data3 contains the data for the third SSL handshake. When a
+ // connection to a server fails during an SSL handshake,
+ // HttpNetworkTransaction will attempt to fallback to TLSv1 if the previous
+ // connection was attempted with TLSv1.1. This is transparent to the caller
+ // of the HttpNetworkTransaction. Because this test failure is due to
+ // requiring a client certificate, this fallback handshake should also
+ // fail.
+ SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data3.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
+ net::StaticSocketDataProvider data3(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ // [ssl_]data4 contains the data for the fourth SSL handshake. When a
+ // connection to a server fails during an SSL handshake,
+ // HttpNetworkTransaction will attempt to fallback to SSLv3 if the previous
+ // connection was attempted with TLSv1. This is transparent to the caller
+ // of the HttpNetworkTransaction. Because this test failure is due to
+ // requiring a client certificate, this fallback handshake should also
+ // fail.
+ SSLSocketDataProvider ssl_data4(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data4.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data4);
+ net::StaticSocketDataProvider data4(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data4);
+
+ // Need one more if TLSv1.2 is enabled.
+ SSLSocketDataProvider ssl_data5(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data5.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data5);
+ net::StaticSocketDataProvider data5(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data5);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Begin the SSL handshake with the peer. This consumes ssl_data1.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Complete the SSL handshake, which should abort due to requiring a
+ // client certificate.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
+
+ // Indicate that no certificate should be supplied. From the perspective
+ // of SSLClientCertCache, NULL is just as meaningful as a real
+ // certificate, so this is the same as supply a
+ // legitimate-but-unacceptable certificate.
+ rv = trans->RestartWithCertificate(NULL, callback.callback());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Ensure the certificate was added to the client auth cache before
+ // allowing the connection to continue restarting.
+ scoped_refptr<X509Certificate> client_cert;
+ ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+ ASSERT_EQ(NULL, client_cert.get());
+
+ // Restart the handshake. This will consume ssl_data2, which fails, and
+ // then consume ssl_data3 and ssl_data4, both of which should also fail.
+ // The result code is checked against what ssl_data4 should return.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv);
+
+ // Ensure that the client certificate is removed from the cache on a
+ // handshake failure.
+ ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+}
+
+// Ensure that a client certificate is removed from the SSL client auth
+// cache when:
+// 1) No proxy is involved.
+// 2) TLS False Start is enabled.
+// 3) The initial TLS handshake requests a client certificate.
+// 4) The client supplies an invalid/unacceptable certificate.
+TEST_P(HttpNetworkTransactionTest,
+ ClientAuthCertCache_Direct_FalseStart) {
+ net::HttpRequestInfo request_info;
+ request_info.url = GURL("https://www.example.com/");
+ request_info.method = "GET";
+ request_info.load_flags = net::LOAD_NORMAL;
+
+ scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
+ cert_request->host_and_port = "www.example.com:443";
+
+ // When TLS False Start is used, SSLClientSocket::Connect() calls will
+ // return successfully after reading up to the peer's Certificate message.
+ // This is to allow the caller to call SSLClientSocket::Write(), which can
+ // enqueue application data to be sent in the same packet as the
+ // ChangeCipherSpec and Finished messages.
+ // The actual handshake will be finished when SSLClientSocket::Read() is
+ // called, which expects to process the peer's ChangeCipherSpec and
+ // Finished messages. If there was an error negotiating with the peer,
+ // such as due to the peer requiring a client certificate when none was
+ // supplied, the alert sent by the peer won't be processed until Read() is
+ // called.
+
+ // Like the non-False Start case, when a client certificate is requested by
+ // the peer, the handshake is aborted during the Connect() call.
+ // [ssl_]data1 represents the initial SSL handshake with the peer.
+ SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
+ ssl_data1.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
+ net::StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ // When a client certificate is supplied, Connect() will not be aborted
+ // when the peer requests the certificate. Instead, the handshake will
+ // artificially succeed, allowing the caller to write the HTTP request to
+ // the socket. The handshake messages are not processed until Read() is
+ // called, which then detects that the handshake was aborted, due to the
+ // peer sending a handshake_failure because it requires a client
+ // certificate.
+ SSLSocketDataProvider ssl_data2(ASYNC, net::OK);
+ ssl_data2.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
+ net::MockRead data2_reads[] = {
+ net::MockRead(ASYNC /* async */, net::ERR_SSL_PROTOCOL_ERROR),
+ };
+ net::StaticSocketDataProvider data2(
+ data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ // As described in ClientAuthCertCache_Direct_NoFalseStart, [ssl_]data3 is
+ // the data for the SSL handshake once the TLSv1.1 connection falls back to
+ // TLSv1. It has the same behaviour as [ssl_]data2.
+ SSLSocketDataProvider ssl_data3(ASYNC, net::OK);
+ ssl_data3.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
+ net::StaticSocketDataProvider data3(
+ data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+
+ // [ssl_]data4 is the data for the SSL handshake once the TLSv1 connection
+ // falls back to SSLv3. It has the same behaviour as [ssl_]data2.
+ SSLSocketDataProvider ssl_data4(ASYNC, net::OK);
+ ssl_data4.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data4);
+ net::StaticSocketDataProvider data4(
+ data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data4);
+
+ // Need one more if TLSv1.2 is enabled.
+ SSLSocketDataProvider ssl_data5(ASYNC, net::OK);
+ ssl_data5.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data5);
+ net::StaticSocketDataProvider data5(
+ data2_reads, arraysize(data2_reads), NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data5);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Begin the initial SSL handshake.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request_info, callback.callback(), net::BoundNetLog());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Complete the SSL handshake, which should abort due to requiring a
+ // client certificate.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
+
+ // Indicate that no certificate should be supplied. From the perspective
+ // of SSLClientCertCache, NULL is just as meaningful as a real
+ // certificate, so this is the same as supply a
+ // legitimate-but-unacceptable certificate.
+ rv = trans->RestartWithCertificate(NULL, callback.callback());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Ensure the certificate was added to the client auth cache before
+ // allowing the connection to continue restarting.
+ scoped_refptr<X509Certificate> client_cert;
+ ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+ ASSERT_EQ(NULL, client_cert.get());
+
+ // Restart the handshake. This will consume ssl_data2, which fails, and
+ // then consume ssl_data3 and ssl_data4, both of which should also fail.
+ // The result code is checked against what ssl_data4 should return.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_SSL_PROTOCOL_ERROR, rv);
+
+ // Ensure that the client certificate is removed from the cache on a
+ // handshake failure.
+ ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+}
+
+// Ensure that a client certificate is removed from the SSL client auth
+// cache when:
+// 1) An HTTPS proxy is involved.
+// 3) The HTTPS proxy requests a client certificate.
+// 4) The client supplies an invalid/unacceptable certificate for the
+// proxy.
+// The test is repeated twice, first for connecting to an HTTPS endpoint,
+// then for connecting to an HTTP endpoint.
+TEST_P(HttpNetworkTransactionTest, ClientAuthCertCache_Proxy_Fail) {
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixed("https://proxy:70"));
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+
+ scoped_refptr<SSLCertRequestInfo> cert_request(new SSLCertRequestInfo());
+ cert_request->host_and_port = "proxy:70";
+
+ // See ClientAuthCertCache_Direct_NoFalseStart for the explanation of
+ // [ssl_]data[1-3]. Rather than represending the endpoint
+ // (www.example.com:443), they represent failures with the HTTPS proxy
+ // (proxy:70).
+ SSLSocketDataProvider ssl_data1(ASYNC, net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
+ ssl_data1.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
+ net::StaticSocketDataProvider data1(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+
+ SSLSocketDataProvider ssl_data2(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data2.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data2);
+ net::StaticSocketDataProvider data2(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ // TODO(wtc): find out why this unit test doesn't need [ssl_]data3.
+#if 0
+ SSLSocketDataProvider ssl_data3(ASYNC, net::ERR_SSL_PROTOCOL_ERROR);
+ ssl_data3.cert_request_info = cert_request.get();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data3);
+ net::StaticSocketDataProvider data3(NULL, 0, NULL, 0);
+ session_deps_.socket_factory->AddSocketDataProvider(&data3);
+#endif
+
+ net::HttpRequestInfo requests[2];
+ requests[0].url = GURL("https://www.example.com/");
+ requests[0].method = "GET";
+ requests[0].load_flags = net::LOAD_NORMAL;
+
+ requests[1].url = GURL("http://www.example.com/");
+ requests[1].method = "GET";
+ requests[1].load_flags = net::LOAD_NORMAL;
+
+ for (size_t i = 0; i < arraysize(requests); ++i) {
+ session_deps_.socket_factory->ResetNextMockIndexes();
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ // Begin the SSL handshake with the proxy.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &requests[i], callback.callback(), net::BoundNetLog());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Complete the SSL handshake, which should abort due to requiring a
+ // client certificate.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
+
+ // Indicate that no certificate should be supplied. From the perspective
+ // of SSLClientCertCache, NULL is just as meaningful as a real
+ // certificate, so this is the same as supply a
+ // legitimate-but-unacceptable certificate.
+ rv = trans->RestartWithCertificate(NULL, callback.callback());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+
+ // Ensure the certificate was added to the client auth cache before
+ // allowing the connection to continue restarting.
+ scoped_refptr<X509Certificate> client_cert;
+ ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("proxy:70",
+ &client_cert));
+ ASSERT_EQ(NULL, client_cert.get());
+ // Ensure the certificate was NOT cached for the endpoint. This only
+ // applies to HTTPS requests, but is fine to check for HTTP requests.
+ ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+
+ // Restart the handshake. This will consume ssl_data2, which fails, and
+ // then consume ssl_data3, which should also fail. The result code is
+ // checked against what ssl_data3 should return.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_PROXY_CONNECTION_FAILED, rv);
+
+ // Now that the new handshake has failed, ensure that the client
+ // certificate was removed from the client auth cache.
+ ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("proxy:70",
+ &client_cert));
+ ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
+ &client_cert));
+ }
+}
+
+// Unlike TEST/TEST_F, which are macros that expand to further macros,
+// TEST_P is a macro that expands directly to code that stringizes the
+// arguments. As a result, macros passed as parameters (such as prefix
+// or test_case_name) will not be expanded by the preprocessor. To
+// work around this, indirect the macro for TEST_P, so that the
+// pre-processor will expand macros such as MAYBE_test_name before
+// instantiating the test.
+#define WRAPPED_TEST_P(test_case_name, test_name) \
+ TEST_P(test_case_name, test_name)
+
+// Times out on Win7 dbg(2) bot. http://crbug.com/124776
+#if defined(OS_WIN)
+#define MAYBE_UseIPConnectionPooling DISABLED_UseIPConnectionPooling
+#else
+#define MAYBE_UseIPConnectionPooling UseIPConnectionPooling
+#endif
+WRAPPED_TEST_P(HttpNetworkTransactionTest, MAYBE_UseIPConnectionPooling) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ // Set up a special HttpNetworkSession with a MockCachingHostResolver.
+ session_deps_.host_resolver.reset(new MockCachingHostResolver());
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
+ pool_peer.DisableDomainAuthenticationVerification();
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> host1_req(
+ spdy_util_.ConstructSpdyGet("https://www.google.com", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> host2_req(
+ spdy_util_.ConstructSpdyGet("https://www.gmail.com", false, 3, LOWEST));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*host1_req, 1),
+ CreateMockWrite(*host2_req, 4),
+ };
+ scoped_ptr<SpdyFrame> host1_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> host1_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> host2_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> host2_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*host1_resp, 2),
+ CreateMockRead(*host1_resp_body, 3),
+ CreateMockRead(*host2_resp, 5),
+ CreateMockRead(*host2_resp_body, 6),
+ MockRead(ASYNC, 0, 7),
+ };
+
+ IPAddressNumber ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &ip));
+ IPEndPoint peer_addr = IPEndPoint(ip, 443);
+ MockConnect connect(ASYNC, OK, peer_addr);
+ OrderedSocketData spdy_data(
+ connect,
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ TestCompletionCallback callback;
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/");
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
+
+ int rv = trans1.Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans1.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ // Preload www.gmail.com into HostCache.
+ HostPortPair host_port("www.gmail.com", 443);
+ HostResolver::RequestInfo resolve_info(host_port);
+ AddressList ignored;
+ rv = session_deps_.host_resolver->Resolve(resolve_info, &ignored,
+ callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.gmail.com/");
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
+
+ rv = trans2.Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans2.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+#undef MAYBE_UseIPConnectionPooling
+
+TEST_P(HttpNetworkTransactionTest, UseIPConnectionPoolingAfterResolution) {
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ // Set up a special HttpNetworkSession with a MockCachingHostResolver.
+ session_deps_.host_resolver.reset(new MockCachingHostResolver());
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
+ pool_peer.DisableDomainAuthenticationVerification();
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> host1_req(
+ spdy_util_.ConstructSpdyGet("https://www.google.com", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> host2_req(
+ spdy_util_.ConstructSpdyGet("https://www.gmail.com", false, 3, LOWEST));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*host1_req, 1),
+ CreateMockWrite(*host2_req, 4),
+ };
+ scoped_ptr<SpdyFrame> host1_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> host1_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> host2_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> host2_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*host1_resp, 2),
+ CreateMockRead(*host1_resp_body, 3),
+ CreateMockRead(*host2_resp, 5),
+ CreateMockRead(*host2_resp_body, 6),
+ MockRead(ASYNC, 0, 7),
+ };
+
+ IPAddressNumber ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &ip));
+ IPEndPoint peer_addr = IPEndPoint(ip, 443);
+ MockConnect connect(ASYNC, OK, peer_addr);
+ OrderedSocketData spdy_data(
+ connect,
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ TestCompletionCallback callback;
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/");
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
+
+ int rv = trans1.Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans1.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.gmail.com/");
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
+
+ rv = trans2.Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans2.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+
+class OneTimeCachingHostResolver : public net::HostResolver {
+ public:
+ explicit OneTimeCachingHostResolver(const HostPortPair& host_port)
+ : host_port_(host_port) {}
+ virtual ~OneTimeCachingHostResolver() {}
+
+ RuleBasedHostResolverProc* rules() { return host_resolver_.rules(); }
+
+ // HostResolver methods:
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE {
+ return host_resolver_.Resolve(
+ info, addresses, callback, out_req, net_log);
+ }
+
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE {
+ int rv = host_resolver_.ResolveFromCache(info, addresses, net_log);
+ if (rv == OK && info.host_port_pair().Equals(host_port_))
+ host_resolver_.GetHostCache()->clear();
+ return rv;
+ }
+
+ virtual void CancelRequest(RequestHandle req) OVERRIDE {
+ host_resolver_.CancelRequest(req);
+ }
+
+ MockCachingHostResolver* GetMockHostResolver() {
+ return &host_resolver_;
+ }
+
+ private:
+ MockCachingHostResolver host_resolver_;
+ const HostPortPair host_port_;
+};
+
+// Times out on Win7 dbg(2) bot. http://crbug.com/124776
+#if defined(OS_WIN)
+#define MAYBE_UseIPConnectionPoolingWithHostCacheExpiration \
+ DISABLED_UseIPConnectionPoolingWithHostCacheExpiration
+#else
+#define MAYBE_UseIPConnectionPoolingWithHostCacheExpiration \
+ UseIPConnectionPoolingWithHostCacheExpiration
+#endif
+WRAPPED_TEST_P(HttpNetworkTransactionTest,
+ MAYBE_UseIPConnectionPoolingWithHostCacheExpiration) {
+// Times out on Win7 dbg(2) bot. http://crbug.com/124776 . (MAYBE_
+// prefix doesn't work with parametrized tests).
+#if defined(OS_WIN)
+ return;
+#endif
+
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+
+ // Set up a special HttpNetworkSession with a OneTimeCachingHostResolver.
+ OneTimeCachingHostResolver host_resolver(HostPortPair("www.gmail.com", 443));
+ HttpNetworkSession::Params params =
+ SpdySessionDependencies::CreateSessionParams(&session_deps_);
+ params.host_resolver = &host_resolver;
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ SpdySessionPoolPeer pool_peer(session->spdy_session_pool());
+ pool_peer.DisableDomainAuthenticationVerification();
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_ptr<SpdyFrame> host1_req(
+ spdy_util_.ConstructSpdyGet("https://www.google.com", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> host2_req(
+ spdy_util_.ConstructSpdyGet("https://www.gmail.com", false, 3, LOWEST));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*host1_req, 1),
+ CreateMockWrite(*host2_req, 4),
+ };
+ scoped_ptr<SpdyFrame> host1_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> host1_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> host2_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> host2_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*host1_resp, 2),
+ CreateMockRead(*host1_resp_body, 3),
+ CreateMockRead(*host2_resp, 5),
+ CreateMockRead(*host2_resp_body, 6),
+ MockRead(ASYNC, 0, 7),
+ };
+
+ IPAddressNumber ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &ip));
+ IPEndPoint peer_addr = IPEndPoint(ip, 443);
+ MockConnect connect(ASYNC, OK, peer_addr);
+ OrderedSocketData spdy_data(
+ connect,
+ spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&spdy_data);
+
+ TestCompletionCallback callback;
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.google.com/");
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(DEFAULT_PRIORITY, session.get());
+
+ int rv = trans1.Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans1.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(&trans1, &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ // Preload cache entries into HostCache.
+ HostResolver::RequestInfo resolve_info(HostPortPair("www.gmail.com", 443));
+ AddressList ignored;
+ rv = host_resolver.Resolve(resolve_info, &ignored, callback.callback(),
+ NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.gmail.com/");
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(DEFAULT_PRIORITY, session.get());
+
+ rv = trans2.Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans2.GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(&trans2, &response_data));
+ EXPECT_EQ("hello!", response_data);
+}
+#undef MAYBE_UseIPConnectionPoolingWithHostCacheExpiration
+
+TEST_P(HttpNetworkTransactionTest, ReadPipelineEvictionFallback) {
+ MockRead data_reads1[] = {
+ MockRead(SYNCHRONOUS, ERR_PIPELINE_EVICTION),
+ };
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), NULL, 0);
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), NULL, 0);
+ StaticSocketDataProvider* data[] = { &data1, &data2 };
+
+ SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data));
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
+ EXPECT_EQ("hello world", out.response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, SendPipelineEvictionFallback) {
+ MockWrite data_writes1[] = {
+ MockWrite(SYNCHRONOUS, ERR_PIPELINE_EVICTION),
+ };
+ MockWrite data_writes2[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.0 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ StaticSocketDataProvider data1(NULL, 0,
+ data_writes1, arraysize(data_writes1));
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ StaticSocketDataProvider* data[] = { &data1, &data2 };
+
+ SimpleGetHelperResult out = SimpleGetHelperForData(data, arraysize(data));
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.0 200 OK", out.status_line);
+ EXPECT_EQ("hello world", out.response_data);
+}
+
+TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttp) {
+ const std::string https_url = "https://www.google.com/";
+ const std::string http_url = "http://www.google.com:443/";
+
+ // SPDY GET for HTTPS URL
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(https_url.c_str(), false, 1, LOWEST));
+
+ MockWrite writes1[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads1[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*body1, 2),
+ MockRead(ASYNC, ERR_IO_PENDING, 3)
+ };
+
+ DelayedSocketData data1(
+ 1, reads1, arraysize(reads1),
+ writes1, arraysize(writes1));
+ MockConnect connect_data1(ASYNC, OK);
+ data1.set_connect_data(connect_data1);
+
+ // HTTP GET for the HTTP URL
+ MockWrite writes2[] = {
+ MockWrite(ASYNC, 4,
+ "GET / HTTP/1.1\r\n"
+ "Host: www.google.com:443\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead reads2[] = {
+ MockRead(ASYNC, 5, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"),
+ MockRead(ASYNC, 6, "hello"),
+ MockRead(ASYNC, 7, OK),
+ };
+
+ DelayedSocketData data2(
+ 1, reads2, arraysize(reads2),
+ writes2, arraysize(writes2));
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSocketDataProvider(&data1);
+ session_deps_.socket_factory->AddSocketDataProvider(&data2);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Start the first transaction to set up the SpdySession
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL(https_url);
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(LOWEST, session.get());
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans1.Start(&request1, callback1.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
+
+ // Now, start the HTTP request
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL(http_url);
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(MEDIUM, session.get());
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans2.Start(&request2, callback2.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(trans2.GetResponseInfo()->was_fetched_via_spdy);
+}
+
+TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttpOverTunnel) {
+ const std::string https_url = "https://www.google.com/";
+ const std::string http_url = "http://www.google.com:443/";
+
+ // SPDY GET for HTTPS URL (through CONNECT tunnel)
+ scoped_ptr<SpdyFrame> connect(spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(https_url.c_str(), false, 1, LOWEST));
+
+ // SPDY GET for HTTP URL (through the proxy, but not the tunnel)
+ scoped_ptr<SpdyFrame> wrapped_req1(
+ spdy_util_.ConstructWrappedSpdyFrame(req1, 1));
+ const char* const headers[] = {
+ spdy_util_.GetMethodKey(), "GET",
+ spdy_util_.GetPathKey(), spdy_util_.is_spdy2() ? http_url.c_str() : "/",
+ spdy_util_.GetHostKey(), "www.google.com:443",
+ spdy_util_.GetSchemeKey(), "http",
+ spdy_util_.GetVersionKey(), "HTTP/1.1"
+ };
+ scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyControlFrame(
+ NULL, 0, false, 3, MEDIUM, SYN_STREAM, CONTROL_FLAG_FIN,
+ headers, arraysize(headers), 0));
+
+ MockWrite writes1[] = {
+ CreateMockWrite(*connect, 0),
+ CreateMockWrite(*wrapped_req1, 2),
+ CreateMockWrite(*req2, 5),
+ };
+
+ scoped_ptr<SpdyFrame> conn_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> wrapped_resp1(
+ spdy_util_.ConstructWrappedSpdyFrame(resp1, 1));
+ scoped_ptr<SpdyFrame> wrapped_body1(
+ spdy_util_.ConstructWrappedSpdyFrame(body1, 1));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead reads1[] = {
+ CreateMockRead(*conn_resp, 1),
+ CreateMockRead(*wrapped_resp1, 3),
+ CreateMockRead(*wrapped_body1, 4),
+ CreateMockRead(*resp2, 6),
+ CreateMockRead(*body2, 7),
+ MockRead(ASYNC, ERR_IO_PENDING, 8)
+ };
+
+ DeterministicSocketData data1(reads1, arraysize(reads1),
+ writes1, arraysize(writes1));
+ MockConnect connect_data1(ASYNC, OK);
+ data1.set_connect_data(connect_data1);
+
+ session_deps_.proxy_service.reset(
+ ProxyService::CreateFixedFromPacResult("HTTPS proxy:70"));
+ CapturingNetLog log;
+ session_deps_.net_log = &log;
+ SSLSocketDataProvider ssl1(ASYNC, OK); // to the proxy
+ ssl1.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl1);
+ SSLSocketDataProvider ssl2(ASYNC, OK); // to the server
+ ssl2.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl2);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data1);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ // Start the first transaction to set up the SpdySession
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL(https_url);
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(LOWEST, session.get());
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans1.Start(&request1, callback1.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+ data1.RunFor(4);
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
+
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(trans1.GetLoadTimingInfo(&load_timing_info1));
+ TestLoadTimingNotReusedWithPac(load_timing_info1,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+
+ // Now, start the HTTP request
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL(http_url);
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(MEDIUM, session.get());
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans2.Start(&request2, callback2.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+ data1.RunFor(3);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
+
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(trans2.GetLoadTimingInfo(&load_timing_info2));
+ // The established SPDY sessions is considered reused by the HTTP request.
+ TestLoadTimingReusedWithPac(load_timing_info2);
+ // HTTP requests over a SPDY session should have a different connection
+ // socket_log_id than requests over a tunnel.
+ EXPECT_NE(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+}
+
+TEST_P(HttpNetworkTransactionTest, UseSpdySessionForHttpWhenForced) {
+ HttpStreamFactory::set_force_spdy_always(true);
+ const std::string https_url = "https://www.google.com/";
+ const std::string http_url = "http://www.google.com:443/";
+
+ // SPDY GET for HTTPS URL
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(https_url.c_str(), false, 1, LOWEST));
+ // SPDY GET for the HTTP URL
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(http_url.c_str(), false, 3, MEDIUM));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 1),
+ CreateMockWrite(*req2, 4),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 2),
+ CreateMockRead(*body1, 3),
+ CreateMockRead(*resp2, 5),
+ CreateMockRead(*body2, 6),
+ MockRead(ASYNC, ERR_IO_PENDING, 7)
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ // Start the first transaction to set up the SpdySession
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL(https_url);
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(LOWEST, session.get());
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans1.Start(&request1, callback1.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
+
+ // Now, start the HTTP request
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL(http_url);
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(MEDIUM, session.get());
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans2.Start(&request2, callback2.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
+}
+
+// Test that in the case where we have a SPDY session to a SPDY proxy
+// that we do not pool other origins that resolve to the same IP when
+// the certificate does not match the new origin.
+// http://crbug.com/134690
+TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionIfCertDoesNotMatch) {
+ const std::string url1 = "http://www.google.com/";
+ const std::string url2 = "https://mail.google.com/";
+ const std::string ip_addr = "1.2.3.4";
+
+ // SPDY GET for HTTP URL (through SPDY proxy)
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlockForProxy("http://www.google.com/"));
+ scoped_ptr<SpdyFrame> req1(spdy_util_.ConstructSpdyControlFrame(
+ headers.Pass(), false, 1, LOWEST, SYN_STREAM, CONTROL_FLAG_FIN, 0));
+
+ MockWrite writes1[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads1[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*body1, 2),
+ MockRead(ASYNC, OK, 3) // EOF
+ };
+
+ scoped_ptr<DeterministicSocketData> data1(
+ new DeterministicSocketData(reads1, arraysize(reads1),
+ writes1, arraysize(writes1)));
+ IPAddressNumber ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber(ip_addr, &ip));
+ IPEndPoint peer_addr = IPEndPoint(ip, 443);
+ MockConnect connect_data1(ASYNC, OK, peer_addr);
+ data1->set_connect_data(connect_data1);
+
+ // SPDY GET for HTTPS URL (direct)
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(url2.c_str(), false, 1, MEDIUM));
+
+ MockWrite writes2[] = {
+ CreateMockWrite(*req2, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads2[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, OK, 3) // EOF
+ };
+
+ scoped_ptr<DeterministicSocketData> data2(
+ new DeterministicSocketData(reads2, arraysize(reads2),
+ writes2, arraysize(writes2)));
+ MockConnect connect_data2(ASYNC, OK);
+ data2->set_connect_data(connect_data2);
+
+ // Set up a proxy config that sends HTTP requests to a proxy, and
+ // all others direct.
+ ProxyConfig proxy_config;
+ proxy_config.proxy_rules().ParseFromString("http=https://proxy:443");
+ CapturingProxyResolver* capturing_proxy_resolver =
+ new CapturingProxyResolver();
+ session_deps_.proxy_service.reset(new ProxyService(
+ new ProxyConfigServiceFixed(proxy_config), capturing_proxy_resolver,
+ NULL));
+
+ // Load a valid cert. Note, that this does not need to
+ // be valid for proxy because the MockSSLClientSocket does
+ // not actually verify it. But SpdySession will use this
+ // to see if it is valid for the new origin
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> server_cert(
+ ImportCertFromFile(certs_dir, "ok_cert.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert);
+
+ SSLSocketDataProvider ssl1(ASYNC, OK); // to the proxy
+ ssl1.SetNextProto(GetParam());
+ ssl1.cert = server_cert;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl1);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ data1.get());
+
+ SSLSocketDataProvider ssl2(ASYNC, OK); // to the server
+ ssl2.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl2);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ data2.get());
+
+ session_deps_.host_resolver.reset(new MockCachingHostResolver());
+ session_deps_.host_resolver->rules()->AddRule("mail.google.com", ip_addr);
+ session_deps_.host_resolver->rules()->AddRule("proxy", ip_addr);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ // Start the first transaction to set up the SpdySession
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL(url1);
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(LOWEST, session.get());
+ TestCompletionCallback callback1;
+ ASSERT_EQ(ERR_IO_PENDING,
+ trans1.Start(&request1, callback1.callback(), BoundNetLog()));
+ data1->RunFor(3);
+
+ ASSERT_TRUE(callback1.have_result());
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(trans1.GetResponseInfo()->was_fetched_via_spdy);
+
+ // Now, start the HTTP request
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL(url2);
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(MEDIUM, session.get());
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans2.Start(&request2, callback2.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+ data2->RunFor(3);
+
+ ASSERT_TRUE(callback2.have_result());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
+}
+
+// Test to verify that a failed socket read (due to an ERR_CONNECTION_CLOSED
+// error) in SPDY session, removes the socket from pool and closes the SPDY
+// session. Verify that new url's from the same HttpNetworkSession (and a new
+// SpdySession) do work. http://crbug.com/224701
+TEST_P(HttpNetworkTransactionTest, ErrorSocketNotConnected) {
+ const std::string https_url = "https://www.google.com/";
+
+ MockRead reads1[] = {
+ MockRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED, 0)
+ };
+
+ scoped_ptr<DeterministicSocketData> data1(
+ new DeterministicSocketData(reads1, arraysize(reads1), NULL, 0));
+ data1->SetStop(1);
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(https_url.c_str(), false, 1, MEDIUM));
+ MockWrite writes2[] = {
+ CreateMockWrite(*req2, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads2[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, OK, 3) // EOF
+ };
+
+ scoped_ptr<DeterministicSocketData> data2(
+ new DeterministicSocketData(reads2, arraysize(reads2),
+ writes2, arraysize(writes2)));
+
+ SSLSocketDataProvider ssl1(ASYNC, OK);
+ ssl1.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl1);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ data1.get());
+
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.SetNextProto(GetParam());
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl2);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ data2.get());
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_));
+
+ // Start the first transaction to set up the SpdySession and verify that
+ // connection was closed.
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL(https_url);
+ request1.load_flags = 0;
+ HttpNetworkTransaction trans1(MEDIUM, session.get());
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans1.Start(&request1, callback1.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback1.WaitForResult());
+
+ // Now, start the second request and make sure it succeeds.
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL(https_url);
+ request2.load_flags = 0;
+ HttpNetworkTransaction trans2(MEDIUM, session.get());
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans2.Start(&request2, callback2.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+ data2->RunFor(3);
+
+ ASSERT_TRUE(callback2.have_result());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_TRUE(trans2.GetResponseInfo()->was_fetched_via_spdy);
+}
+
+TEST_P(HttpNetworkTransactionTest, CloseIdleSpdySessionToOpenNewOne) {
+ HttpStreamFactory::SetNextProtos(SpdyNextProtos());
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+
+ // Use two different hosts with different IPs so they don't get pooled.
+ session_deps_.host_resolver->rules()->AddRule("www.a.com", "10.0.0.1");
+ session_deps_.host_resolver->rules()->AddRule("www.b.com", "10.0.0.2");
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+
+ SSLSocketDataProvider ssl1(ASYNC, OK);
+ ssl1.SetNextProto(GetParam());
+ SSLSocketDataProvider ssl2(ASYNC, OK);
+ ssl2.SetNextProto(GetParam());
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl1);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl2);
+
+ scoped_ptr<SpdyFrame> host1_req(spdy_util_.ConstructSpdyGet(
+ "https://www.a.com", false, 1, DEFAULT_PRIORITY));
+ MockWrite spdy1_writes[] = {
+ CreateMockWrite(*host1_req, 1),
+ };
+ scoped_ptr<SpdyFrame> host1_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> host1_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy1_reads[] = {
+ CreateMockRead(*host1_resp, 2),
+ CreateMockRead(*host1_resp_body, 3),
+ MockRead(ASYNC, ERR_IO_PENDING, 4),
+ };
+
+ scoped_ptr<OrderedSocketData> spdy1_data(
+ new OrderedSocketData(
+ spdy1_reads, arraysize(spdy1_reads),
+ spdy1_writes, arraysize(spdy1_writes)));
+ session_deps_.socket_factory->AddSocketDataProvider(spdy1_data.get());
+
+ scoped_ptr<SpdyFrame> host2_req(spdy_util_.ConstructSpdyGet(
+ "https://www.b.com", false, 1, DEFAULT_PRIORITY));
+ MockWrite spdy2_writes[] = {
+ CreateMockWrite(*host2_req, 1),
+ };
+ scoped_ptr<SpdyFrame> host2_resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> host2_resp_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy2_reads[] = {
+ CreateMockRead(*host2_resp, 2),
+ CreateMockRead(*host2_resp_body, 3),
+ MockRead(ASYNC, ERR_IO_PENDING, 4),
+ };
+
+ scoped_ptr<OrderedSocketData> spdy2_data(
+ new OrderedSocketData(
+ spdy2_reads, arraysize(spdy2_reads),
+ spdy2_writes, arraysize(spdy2_writes)));
+ session_deps_.socket_factory->AddSocketDataProvider(spdy2_data.get());
+
+ MockWrite http_write[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.a.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead http_read[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 6\r\n\r\n"),
+ MockRead("hello!"),
+ };
+ StaticSocketDataProvider http_data(http_read, arraysize(http_read),
+ http_write, arraysize(http_write));
+ session_deps_.socket_factory->AddSocketDataProvider(&http_data);
+
+ HostPortPair host_port_pair_a("www.a.com", 443);
+ SpdySessionKey spdy_session_key_a(
+ host_port_pair_a, ProxyServer::Direct(), kPrivacyModeDisabled);
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
+
+ TestCompletionCallback callback;
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("https://www.a.com/");
+ request1.load_flags = 0;
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ int rv = trans->Start(&request1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+ trans.reset();
+ EXPECT_TRUE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
+
+ HostPortPair host_port_pair_b("www.b.com", 443);
+ SpdySessionKey spdy_session_key_b(
+ host_port_pair_b, ProxyServer::Direct(), kPrivacyModeDisabled);
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("https://www.b.com/");
+ request2.load_flags = 0;
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request2, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
+ EXPECT_TRUE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
+
+ HostPortPair host_port_pair_a1("www.a.com", 80);
+ SpdySessionKey spdy_session_key_a1(
+ host_port_pair_a1, ProxyServer::Direct(), kPrivacyModeDisabled);
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_a1));
+ HttpRequestInfo request3;
+ request3.method = "GET";
+ request3.url = GURL("http://www.a.com/");
+ request3.load_flags = 0;
+ trans.reset(new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+
+ rv = trans->Start(&request3, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+ ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data));
+ EXPECT_EQ("hello!", response_data);
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_a));
+ EXPECT_FALSE(
+ HasSpdySession(session->spdy_session_pool(), spdy_session_key_b));
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpSyncConnectError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockConnect mock_connect(SYNCHRONOUS, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider data;
+ data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_REFUSED, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ // We don't care whether this succeeds or fails, but it shouldn't crash.
+ HttpRequestHeaders request_headers;
+ trans->GetFullRequestHeaders(&request_headers);
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpAsyncConnectError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider data;
+ data.set_connect_data(mock_connect);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_REFUSED, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ // We don't care whether this succeeds or fails, but it shouldn't crash.
+ HttpRequestHeaders request_headers;
+ trans->GetFullRequestHeaders(&request_headers);
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpSyncWriteError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET),
+ };
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ EXPECT_TRUE(request_headers.HasHeader("Host"));
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpAsyncWriteError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, ERR_CONNECTION_RESET),
+ };
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ EXPECT_TRUE(request_headers.HasHeader("Host"));
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpSyncReadError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, ERR_CONNECTION_RESET),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ EXPECT_TRUE(request_headers.HasHeader("Host"));
+}
+
+TEST_P(HttpNetworkTransactionTest, HttpAsyncReadError) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, ERR_CONNECTION_RESET),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+
+ EXPECT_EQ(NULL, trans->GetResponseInfo());
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ EXPECT_TRUE(request_headers.HasHeader("Host"));
+}
+
+TEST_P(HttpNetworkTransactionTest, GetFullRequestHeadersIncludesExtraHeader) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ request.extra_headers.SetHeader("X-Foo", "bar");
+
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY,
+ CreateSession(&session_deps_)));
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "X-Foo: bar\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Content-Length: 5\r\n\r\n"
+ "hello"),
+ MockRead(ASYNC, ERR_UNEXPECTED),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ HttpRequestHeaders request_headers;
+ EXPECT_TRUE(trans->GetFullRequestHeaders(&request_headers));
+ std::string foo;
+ EXPECT_TRUE(request_headers.GetHeader("X-Foo", &foo));
+ EXPECT_EQ("bar", foo);
+}
+
+namespace {
+
+// Fake HttpStreamBase that simply records calls to SetPriority().
+class FakeStream : public HttpStreamBase,
+ public base::SupportsWeakPtr<FakeStream> {
+ public:
+ explicit FakeStream(RequestPriority priority) : priority_(priority) {}
+ virtual ~FakeStream() {}
+
+ RequestPriority priority() const { return priority_; }
+
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual void Close(bool not_reusable) OVERRIDE {}
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool CanFindEndOfResponse() const OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsConnectionReused() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual void SetConnectionReused() OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual bool IsConnectionReusable() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual bool IsSpdyHttpStream() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual void SetPriority(RequestPriority priority) OVERRIDE {
+ priority_ = priority;
+ }
+
+ private:
+ RequestPriority priority_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeStream);
+};
+
+// Fake HttpStreamRequest that simply records calls to SetPriority()
+// and vends FakeStreams with its current priority.
+class FakeStreamRequest : public HttpStreamRequest,
+ public base::SupportsWeakPtr<FakeStreamRequest> {
+ public:
+ FakeStreamRequest(RequestPriority priority,
+ HttpStreamRequest::Delegate* delegate)
+ : priority_(priority),
+ delegate_(delegate) {}
+
+ virtual ~FakeStreamRequest() {}
+
+ RequestPriority priority() const { return priority_; }
+
+ // Create a new FakeStream and pass it to the request's
+ // delegate. Returns a weak pointer to the FakeStream.
+ base::WeakPtr<FakeStream> FinishStreamRequest() {
+ FakeStream* fake_stream = new FakeStream(priority_);
+ // Do this before calling OnStreamReady() as OnStreamReady() may
+ // immediately delete |fake_stream|.
+ base::WeakPtr<FakeStream> weak_stream = fake_stream->AsWeakPtr();
+ delegate_->OnStreamReady(SSLConfig(), ProxyInfo(), fake_stream);
+ return weak_stream;
+ }
+
+ virtual int RestartTunnelWithProxyAuth(
+ const AuthCredentials& credentials) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual LoadState GetLoadState() const OVERRIDE {
+ ADD_FAILURE();
+ return LoadState();
+ }
+
+ virtual void SetPriority(RequestPriority priority) OVERRIDE {
+ priority_ = priority;
+ }
+
+ virtual bool was_npn_negotiated() const OVERRIDE {
+ return false;
+ }
+
+ virtual NextProto protocol_negotiated() const OVERRIDE {
+ return kProtoUnknown;
+ }
+
+ virtual bool using_spdy() const OVERRIDE {
+ return false;
+ }
+
+ private:
+ RequestPriority priority_;
+ HttpStreamRequest::Delegate* const delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeStreamRequest);
+};
+
+// Fake HttpStreamFactory that vends FakeStreamRequests.
+class FakeStreamFactory : public HttpStreamFactory {
+ public:
+ FakeStreamFactory() {}
+ virtual ~FakeStreamFactory() {}
+
+ // Returns a WeakPtr<> to the last HttpStreamRequest returned by
+ // RequestStream() (which may be NULL if it was destroyed already).
+ base::WeakPtr<FakeStreamRequest> last_stream_request() {
+ return last_stream_request_;
+ }
+
+ virtual HttpStreamRequest* RequestStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ const BoundNetLog& net_log) OVERRIDE {
+ FakeStreamRequest* fake_request = new FakeStreamRequest(priority, delegate);
+ last_stream_request_ = fake_request->AsWeakPtr();
+ return fake_request;
+ }
+
+ virtual HttpStreamRequest* RequestWebSocketStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* factory,
+ const BoundNetLog& net_log) OVERRIDE {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ virtual void PreconnectStreams(int num_streams,
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual base::Value* PipelineInfoToValue() const OVERRIDE {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ virtual const HostMappingRules* GetHostMappingRules() const OVERRIDE {
+ ADD_FAILURE();
+ return NULL;
+ }
+
+ private:
+ base::WeakPtr<FakeStreamRequest> last_stream_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeStreamFactory);
+};
+
+} // namespace
+
+// Make sure that HttpNetworkTransaction passes on its priority to its
+// stream request on start.
+TEST_P(HttpNetworkTransactionTest, SetStreamRequestPriorityOnStart) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ HttpNetworkSessionPeer peer(session);
+ FakeStreamFactory* fake_factory = new FakeStreamFactory();
+ peer.SetHttpStreamFactory(fake_factory);
+
+ HttpNetworkTransaction trans(LOW, session);
+
+ ASSERT_TRUE(fake_factory->last_stream_request() == NULL);
+
+ HttpRequestInfo request;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans.Start(&request, callback.callback(), BoundNetLog()));
+
+ base::WeakPtr<FakeStreamRequest> fake_request =
+ fake_factory->last_stream_request();
+ ASSERT_TRUE(fake_request != NULL);
+ EXPECT_EQ(LOW, fake_request->priority());
+}
+
+// Make sure that HttpNetworkTransaction passes on its priority
+// updates to its stream request.
+TEST_P(HttpNetworkTransactionTest, SetStreamRequestPriority) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ HttpNetworkSessionPeer peer(session);
+ FakeStreamFactory* fake_factory = new FakeStreamFactory();
+ peer.SetHttpStreamFactory(fake_factory);
+
+ HttpNetworkTransaction trans(LOW, session);
+
+ HttpRequestInfo request;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans.Start(&request, callback.callback(), BoundNetLog()));
+
+ base::WeakPtr<FakeStreamRequest> fake_request =
+ fake_factory->last_stream_request();
+ ASSERT_TRUE(fake_request != NULL);
+ EXPECT_EQ(LOW, fake_request->priority());
+
+ trans.SetPriority(LOWEST);
+ ASSERT_TRUE(fake_request != NULL);
+ EXPECT_EQ(LOWEST, fake_request->priority());
+}
+
+// Make sure that HttpNetworkTransaction passes on its priority
+// updates to its stream.
+TEST_P(HttpNetworkTransactionTest, SetStreamPriority) {
+ scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ HttpNetworkSessionPeer peer(session);
+ FakeStreamFactory* fake_factory = new FakeStreamFactory();
+ peer.SetHttpStreamFactory(fake_factory);
+
+ HttpNetworkTransaction trans(LOW, session);
+
+ HttpRequestInfo request;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ trans.Start(&request, callback.callback(), BoundNetLog()));
+
+ base::WeakPtr<FakeStreamRequest> fake_request =
+ fake_factory->last_stream_request();
+ ASSERT_TRUE(fake_request != NULL);
+ base::WeakPtr<FakeStream> fake_stream = fake_request->FinishStreamRequest();
+ ASSERT_TRUE(fake_stream != NULL);
+ EXPECT_EQ(LOW, fake_stream->priority());
+
+ trans.SetPriority(LOWEST);
+ EXPECT_EQ(LOWEST, fake_stream->priority());
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_connection.h b/chromium/net/http/http_pipelined_connection.h
new file mode 100644
index 00000000000..d0d3599e3f1
--- /dev/null
+++ b/chromium/net/http/http_pipelined_connection.h
@@ -0,0 +1,93 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_CONNECTION_H_
+#define NET_HTTP_HTTP_PIPELINED_CONNECTION_H_
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/socket/ssl_client_socket.h"
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HostPortPair;
+class HttpPipelinedStream;
+class ProxyInfo;
+struct SSLConfig;
+
+class NET_EXPORT_PRIVATE HttpPipelinedConnection {
+ public:
+ enum Feedback {
+ OK,
+ PIPELINE_SOCKET_ERROR,
+ OLD_HTTP_VERSION,
+ MUST_CLOSE_CONNECTION,
+ AUTHENTICATION_REQUIRED,
+ };
+
+ class Delegate {
+ public:
+ // Called when a pipeline has newly available capacity. This may be because
+ // the first request has been sent and the pipeline is now active. Or, it
+ // may be because a request successfully completed.
+ virtual void OnPipelineHasCapacity(HttpPipelinedConnection* pipeline) = 0;
+
+ // Called every time a pipeline receives headers. Lets the delegate know if
+ // the headers indicate that pipelining can be used.
+ virtual void OnPipelineFeedback(HttpPipelinedConnection* pipeline,
+ Feedback feedback) = 0;
+ };
+
+ class Factory {
+ public:
+ virtual ~Factory() {}
+
+ virtual HttpPipelinedConnection* CreateNewPipeline(
+ ClientSocketHandle* connection,
+ Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) = 0;
+ };
+
+ virtual ~HttpPipelinedConnection() {}
+
+ // Returns a new stream that uses this pipeline.
+ virtual HttpPipelinedStream* CreateNewStream() = 0;
+
+ // The number of streams currently associated with this pipeline.
+ virtual int depth() const = 0;
+
+ // True if this pipeline can accept new HTTP requests. False if a fatal error
+ // has occurred.
+ virtual bool usable() const = 0;
+
+ // True if this pipeline has bound one request and is ready for additional
+ // requests.
+ virtual bool active() const = 0;
+
+ // The SSLConfig used to establish this connection.
+ virtual const SSLConfig& used_ssl_config() const = 0;
+
+ // The ProxyInfo used to establish this connection.
+ virtual const ProxyInfo& used_proxy_info() const = 0;
+
+ // The BoundNetLog of this pipelined connection.
+ virtual const BoundNetLog& net_log() const = 0;
+
+ // True if this connection was NPN negotiated.
+ virtual bool was_npn_negotiated() const = 0;
+
+ // Protocol negotiated with the server.
+ virtual NextProto protocol_negotiated() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_CONNECTION_H_
diff --git a/chromium/net/http/http_pipelined_connection_impl.cc b/chromium/net/http/http_pipelined_connection_impl.cc
new file mode 100644
index 00000000000..e2e53de47ba
--- /dev/null
+++ b/chromium/net/http/http_pipelined_connection_impl.cc
@@ -0,0 +1,838 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_connection_impl.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_pipelined_stream.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_body_drainer.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_stream_parser.h"
+#include "net/http/http_version.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogReceivedHeadersCallback(const NetLog::Source& source,
+ const std::string* feedback,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue;
+ source.AddToEventParameters(dict);
+ dict->SetString("feedback", *feedback);
+ return dict;
+}
+
+base::Value* NetLogStreamClosedCallback(const NetLog::Source& source,
+ bool not_reusable,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue;
+ source.AddToEventParameters(dict);
+ dict->SetBoolean("not_reusable", not_reusable);
+ return dict;
+}
+
+base::Value* NetLogHostPortPairCallback(const HostPortPair* host_port_pair,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue;
+ dict->SetString("host_and_port", host_port_pair->ToString());
+ return dict;
+}
+
+} // anonymous namespace
+
+HttpPipelinedConnection*
+HttpPipelinedConnectionImpl::Factory::CreateNewPipeline(
+ ClientSocketHandle* connection,
+ HttpPipelinedConnection::Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) {
+ return new HttpPipelinedConnectionImpl(connection, delegate, origin,
+ used_ssl_config, used_proxy_info,
+ net_log, was_npn_negotiated,
+ protocol_negotiated);
+}
+
+HttpPipelinedConnectionImpl::HttpPipelinedConnectionImpl(
+ ClientSocketHandle* connection,
+ HttpPipelinedConnection::Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated)
+ : delegate_(delegate),
+ connection_(connection),
+ used_ssl_config_(used_ssl_config),
+ used_proxy_info_(used_proxy_info),
+ net_log_(BoundNetLog::Make(net_log.net_log(),
+ NetLog::SOURCE_HTTP_PIPELINED_CONNECTION)),
+ was_npn_negotiated_(was_npn_negotiated),
+ protocol_negotiated_(protocol_negotiated),
+ read_buf_(new GrowableIOBuffer()),
+ next_pipeline_id_(1),
+ active_(false),
+ usable_(true),
+ completed_one_request_(false),
+ weak_factory_(this),
+ send_next_state_(SEND_STATE_NONE),
+ send_still_on_call_stack_(false),
+ read_next_state_(READ_STATE_NONE),
+ active_read_id_(0),
+ read_still_on_call_stack_(false) {
+ CHECK(connection_.get());
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_PIPELINED_CONNECTION,
+ base::Bind(&NetLogHostPortPairCallback, &origin));
+}
+
+HttpPipelinedConnectionImpl::~HttpPipelinedConnectionImpl() {
+ CHECK_EQ(depth(), 0);
+ CHECK(stream_info_map_.empty());
+ CHECK(pending_send_request_queue_.empty());
+ CHECK(request_order_.empty());
+ CHECK_EQ(send_next_state_, SEND_STATE_NONE);
+ CHECK_EQ(read_next_state_, READ_STATE_NONE);
+ CHECK(!active_send_request_.get());
+ CHECK(!active_read_id_);
+ if (!usable_) {
+ connection_->socket()->Disconnect();
+ }
+ connection_->Reset();
+ net_log_.EndEvent(NetLog::TYPE_HTTP_PIPELINED_CONNECTION);
+}
+
+HttpPipelinedStream* HttpPipelinedConnectionImpl::CreateNewStream() {
+ int pipeline_id = next_pipeline_id_++;
+ CHECK(pipeline_id);
+ HttpPipelinedStream* stream = new HttpPipelinedStream(this, pipeline_id);
+ stream_info_map_.insert(std::make_pair(pipeline_id, StreamInfo()));
+ return stream;
+}
+
+void HttpPipelinedConnectionImpl::InitializeParser(
+ int pipeline_id,
+ const HttpRequestInfo* request,
+ const BoundNetLog& net_log) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(!stream_info_map_[pipeline_id].parser.get());
+ stream_info_map_[pipeline_id].state = STREAM_BOUND;
+ stream_info_map_[pipeline_id].parser.reset(new HttpStreamParser(
+ connection_.get(), request, read_buf_.get(), net_log));
+ stream_info_map_[pipeline_id].source = net_log.source();
+
+ // In case our first stream doesn't SendRequest() immediately, we should still
+ // allow others to use this pipeline.
+ if (pipeline_id == 1) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpPipelinedConnectionImpl::ActivatePipeline,
+ weak_factory_.GetWeakPtr()));
+ }
+}
+
+void HttpPipelinedConnectionImpl::ActivatePipeline() {
+ if (!active_) {
+ active_ = true;
+ delegate_->OnPipelineHasCapacity(this);
+ }
+}
+
+void HttpPipelinedConnectionImpl::OnStreamDeleted(int pipeline_id) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ Close(pipeline_id, false);
+
+ if (stream_info_map_[pipeline_id].state != STREAM_CREATED &&
+ stream_info_map_[pipeline_id].state != STREAM_UNUSED) {
+ CHECK_EQ(stream_info_map_[pipeline_id].state, STREAM_CLOSED);
+ CHECK(stream_info_map_[pipeline_id].parser.get());
+ stream_info_map_[pipeline_id].parser.reset();
+ }
+ CHECK(!stream_info_map_[pipeline_id].parser.get());
+ stream_info_map_.erase(pipeline_id);
+
+ delegate_->OnPipelineHasCapacity(this);
+}
+
+int HttpPipelinedConnectionImpl::SendRequest(
+ int pipeline_id,
+ const std::string& request_line,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK_EQ(stream_info_map_[pipeline_id].state, STREAM_BOUND);
+ if (!usable_) {
+ return ERR_PIPELINE_EVICTION;
+ }
+
+ PendingSendRequest* send_request = new PendingSendRequest;
+ send_request->pipeline_id = pipeline_id;
+ send_request->request_line = request_line;
+ send_request->headers = headers;
+ send_request->response = response;
+ send_request->callback = callback;
+ pending_send_request_queue_.push(send_request);
+
+ int rv;
+ if (send_next_state_ == SEND_STATE_NONE) {
+ send_next_state_ = SEND_STATE_START_IMMEDIATELY;
+ rv = DoSendRequestLoop(OK);
+ } else {
+ rv = ERR_IO_PENDING;
+ }
+ ActivatePipeline();
+ return rv;
+}
+
+int HttpPipelinedConnectionImpl::DoSendRequestLoop(int result) {
+ int rv = result;
+ do {
+ SendRequestState state = send_next_state_;
+ send_next_state_ = SEND_STATE_NONE;
+ switch (state) {
+ case SEND_STATE_START_IMMEDIATELY:
+ rv = DoStartRequestImmediately(rv);
+ break;
+ case SEND_STATE_START_NEXT_DEFERRED_REQUEST:
+ rv = DoStartNextDeferredRequest(rv);
+ break;
+ case SEND_STATE_SEND_ACTIVE_REQUEST:
+ rv = DoSendActiveRequest(rv);
+ break;
+ case SEND_STATE_COMPLETE:
+ rv = DoSendComplete(rv);
+ break;
+ case SEND_STATE_EVICT_PENDING_REQUESTS:
+ rv = DoEvictPendingSendRequests(rv);
+ break;
+ default:
+ CHECK(false) << "bad send state: " << state;
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && send_next_state_ != SEND_STATE_NONE);
+ send_still_on_call_stack_ = false;
+ return rv;
+}
+
+void HttpPipelinedConnectionImpl::OnSendIOCallback(int result) {
+ CHECK(active_send_request_.get());
+ DoSendRequestLoop(result);
+}
+
+int HttpPipelinedConnectionImpl::DoStartRequestImmediately(int result) {
+ CHECK(!active_send_request_.get());
+ CHECK_EQ(static_cast<size_t>(1), pending_send_request_queue_.size());
+ // If SendRequest() completes synchronously, then we need to return the value
+ // directly to the caller. |send_still_on_call_stack_| will track this.
+ // Otherwise, asynchronous completions will notify the caller via callback.
+ send_still_on_call_stack_ = true;
+ active_send_request_.reset(pending_send_request_queue_.front());
+ pending_send_request_queue_.pop();
+ send_next_state_ = SEND_STATE_SEND_ACTIVE_REQUEST;
+ return OK;
+}
+
+int HttpPipelinedConnectionImpl::DoStartNextDeferredRequest(int result) {
+ CHECK(!send_still_on_call_stack_);
+ CHECK(!active_send_request_.get());
+
+ while (!pending_send_request_queue_.empty()) {
+ scoped_ptr<PendingSendRequest> next_request(
+ pending_send_request_queue_.front());
+ pending_send_request_queue_.pop();
+ CHECK(ContainsKey(stream_info_map_, next_request->pipeline_id));
+ if (stream_info_map_[next_request->pipeline_id].state != STREAM_CLOSED) {
+ active_send_request_.reset(next_request.release());
+ send_next_state_ = SEND_STATE_SEND_ACTIVE_REQUEST;
+ return OK;
+ }
+ }
+
+ send_next_state_ = SEND_STATE_NONE;
+ return OK;
+}
+
+int HttpPipelinedConnectionImpl::DoSendActiveRequest(int result) {
+ CHECK(stream_info_map_[active_send_request_->pipeline_id].parser.get());
+ int rv = stream_info_map_[active_send_request_->pipeline_id].parser->
+ SendRequest(active_send_request_->request_line,
+ active_send_request_->headers,
+ active_send_request_->response,
+ base::Bind(&HttpPipelinedConnectionImpl::OnSendIOCallback,
+ base::Unretained(this)));
+ stream_info_map_[active_send_request_->pipeline_id].state = STREAM_SENDING;
+ send_next_state_ = SEND_STATE_COMPLETE;
+ return rv;
+}
+
+int HttpPipelinedConnectionImpl::DoSendComplete(int result) {
+ CHECK(active_send_request_.get());
+ CHECK_EQ(STREAM_SENDING,
+ stream_info_map_[active_send_request_->pipeline_id].state);
+
+ request_order_.push(active_send_request_->pipeline_id);
+ stream_info_map_[active_send_request_->pipeline_id].state = STREAM_SENT;
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_PIPELINED_CONNECTION_SENT_REQUEST,
+ stream_info_map_[active_send_request_->pipeline_id].source.
+ ToEventParametersCallback());
+
+ if (result == ERR_SOCKET_NOT_CONNECTED && completed_one_request_) {
+ result = ERR_PIPELINE_EVICTION;
+ }
+ if (result < OK) {
+ usable_ = false;
+ }
+
+ if (!send_still_on_call_stack_) {
+ QueueUserCallback(active_send_request_->pipeline_id,
+ active_send_request_->callback, result, FROM_HERE);
+ }
+
+ active_send_request_.reset();
+
+ if (send_still_on_call_stack_) {
+ // It should be impossible for another request to appear on the queue while
+ // this send was on the call stack.
+ CHECK(pending_send_request_queue_.empty());
+ send_next_state_ = SEND_STATE_NONE;
+ } else if (!usable_) {
+ send_next_state_ = SEND_STATE_EVICT_PENDING_REQUESTS;
+ } else {
+ send_next_state_ = SEND_STATE_START_NEXT_DEFERRED_REQUEST;
+ }
+
+ return result;
+}
+
+int HttpPipelinedConnectionImpl::DoEvictPendingSendRequests(int result) {
+ while (!pending_send_request_queue_.empty()) {
+ scoped_ptr<PendingSendRequest> evicted_send(
+ pending_send_request_queue_.front());
+ pending_send_request_queue_.pop();
+ if (ContainsKey(stream_info_map_, evicted_send->pipeline_id) &&
+ stream_info_map_[evicted_send->pipeline_id].state != STREAM_CLOSED) {
+ evicted_send->callback.Run(ERR_PIPELINE_EVICTION);
+ }
+ }
+ send_next_state_ = SEND_STATE_NONE;
+ return result;
+}
+
+int HttpPipelinedConnectionImpl::ReadResponseHeaders(
+ int pipeline_id, const CompletionCallback& callback) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK_EQ(STREAM_SENT, stream_info_map_[pipeline_id].state);
+ CHECK(stream_info_map_[pipeline_id].read_headers_callback.is_null());
+
+ if (!usable_)
+ return ERR_PIPELINE_EVICTION;
+
+ stream_info_map_[pipeline_id].state = STREAM_READ_PENDING;
+ stream_info_map_[pipeline_id].read_headers_callback = callback;
+ if (read_next_state_ == READ_STATE_NONE &&
+ pipeline_id == request_order_.front()) {
+ read_next_state_ = READ_STATE_START_IMMEDIATELY;
+ return DoReadHeadersLoop(OK);
+ }
+ return ERR_IO_PENDING;
+}
+
+void HttpPipelinedConnectionImpl::StartNextDeferredRead() {
+ if (read_next_state_ == READ_STATE_NONE) {
+ read_next_state_ = READ_STATE_START_NEXT_DEFERRED_READ;
+ DoReadHeadersLoop(OK);
+ }
+}
+
+int HttpPipelinedConnectionImpl::DoReadHeadersLoop(int result) {
+ int rv = result;
+ do {
+ ReadHeadersState state = read_next_state_;
+ read_next_state_ = READ_STATE_NONE;
+ switch (state) {
+ case READ_STATE_START_IMMEDIATELY:
+ rv = DoStartReadImmediately(rv);
+ break;
+ case READ_STATE_START_NEXT_DEFERRED_READ:
+ rv = DoStartNextDeferredRead(rv);
+ break;
+ case READ_STATE_READ_HEADERS:
+ rv = DoReadHeaders(rv);
+ break;
+ case READ_STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ break;
+ case READ_STATE_WAITING_FOR_CLOSE:
+ // This is a holding state. We return instead of continuing to run hte
+ // loop. The state will advance when the stream calls Close().
+ rv = DoReadWaitForClose(rv);
+ read_still_on_call_stack_ = false;
+ return rv;
+ case READ_STATE_STREAM_CLOSED:
+ rv = DoReadStreamClosed();
+ break;
+ case READ_STATE_EVICT_PENDING_READS:
+ rv = DoEvictPendingReadHeaders(rv);
+ break;
+ case READ_STATE_NONE:
+ break;
+ default:
+ CHECK(false) << "bad read state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && read_next_state_ != READ_STATE_NONE);
+ read_still_on_call_stack_ = false;
+ return rv;
+}
+
+void HttpPipelinedConnectionImpl::OnReadIOCallback(int result) {
+ DoReadHeadersLoop(result);
+}
+
+int HttpPipelinedConnectionImpl::DoStartReadImmediately(int result) {
+ CHECK(!active_read_id_);
+ CHECK(!read_still_on_call_stack_);
+ CHECK(!request_order_.empty());
+ // If ReadResponseHeaders() completes synchronously, then we need to return
+ // the value directly to the caller. |read_still_on_call_stack_| will track
+ // this. Otherwise, asynchronous completions will notify the caller via
+ // callback.
+ read_still_on_call_stack_ = true;
+ read_next_state_ = READ_STATE_READ_HEADERS;
+ active_read_id_ = request_order_.front();
+ request_order_.pop();
+ return OK;
+}
+
+int HttpPipelinedConnectionImpl::DoStartNextDeferredRead(int result) {
+ CHECK(!active_read_id_);
+ CHECK(!read_still_on_call_stack_);
+
+ if (request_order_.empty()) {
+ read_next_state_ = READ_STATE_NONE;
+ return OK;
+ }
+
+ int next_id = request_order_.front();
+ CHECK(ContainsKey(stream_info_map_, next_id));
+ switch (stream_info_map_[next_id].state) {
+ case STREAM_READ_PENDING:
+ read_next_state_ = READ_STATE_READ_HEADERS;
+ active_read_id_ = next_id;
+ request_order_.pop();
+ break;
+
+ case STREAM_CLOSED:
+ // Since nobody will read whatever data is on the pipeline associated with
+ // this closed request, we must shut down the rest of the pipeline.
+ read_next_state_ = READ_STATE_EVICT_PENDING_READS;
+ break;
+
+ case STREAM_SENT:
+ read_next_state_ = READ_STATE_NONE;
+ break;
+
+ default:
+ CHECK(false) << "Unexpected read state: "
+ << stream_info_map_[next_id].state;
+ }
+
+ return OK;
+}
+
+int HttpPipelinedConnectionImpl::DoReadHeaders(int result) {
+ CHECK(active_read_id_);
+ CHECK(ContainsKey(stream_info_map_, active_read_id_));
+ CHECK_EQ(STREAM_READ_PENDING, stream_info_map_[active_read_id_].state);
+ stream_info_map_[active_read_id_].state = STREAM_ACTIVE;
+ int rv = stream_info_map_[active_read_id_].parser->ReadResponseHeaders(
+ base::Bind(&HttpPipelinedConnectionImpl::OnReadIOCallback,
+ base::Unretained(this)));
+ read_next_state_ = READ_STATE_READ_HEADERS_COMPLETE;
+ return rv;
+}
+
+int HttpPipelinedConnectionImpl::DoReadHeadersComplete(int result) {
+ CHECK(active_read_id_);
+ CHECK(ContainsKey(stream_info_map_, active_read_id_));
+ CHECK_EQ(STREAM_ACTIVE, stream_info_map_[active_read_id_].state);
+
+ read_next_state_ = READ_STATE_WAITING_FOR_CLOSE;
+ if (result < OK) {
+ if (completed_one_request_ &&
+ (result == ERR_CONNECTION_CLOSED ||
+ result == ERR_EMPTY_RESPONSE ||
+ result == ERR_SOCKET_NOT_CONNECTED)) {
+ // These usually indicate that pipelining failed on the server side. In
+ // that case, we should retry without pipelining.
+ result = ERR_PIPELINE_EVICTION;
+ }
+ usable_ = false;
+ }
+
+ CheckHeadersForPipelineCompatibility(active_read_id_, result);
+
+ if (!read_still_on_call_stack_) {
+ QueueUserCallback(active_read_id_,
+ stream_info_map_[active_read_id_].read_headers_callback,
+ result, FROM_HERE);
+ }
+
+ return result;
+}
+
+int HttpPipelinedConnectionImpl::DoReadWaitForClose(int result) {
+ read_next_state_ = READ_STATE_WAITING_FOR_CLOSE;
+ return result;
+}
+
+int HttpPipelinedConnectionImpl::DoReadStreamClosed() {
+ CHECK(active_read_id_);
+ CHECK(ContainsKey(stream_info_map_, active_read_id_));
+ CHECK_EQ(stream_info_map_[active_read_id_].state, STREAM_CLOSED);
+ active_read_id_ = 0;
+ if (!usable_) {
+ // TODO(simonjam): Don't wait this long to evict.
+ read_next_state_ = READ_STATE_EVICT_PENDING_READS;
+ return OK;
+ }
+ completed_one_request_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpPipelinedConnectionImpl::StartNextDeferredRead,
+ weak_factory_.GetWeakPtr()));
+ read_next_state_ = READ_STATE_NONE;
+ return OK;
+}
+
+int HttpPipelinedConnectionImpl::DoEvictPendingReadHeaders(int result) {
+ while (!request_order_.empty()) {
+ int evicted_id = request_order_.front();
+ request_order_.pop();
+ if (!ContainsKey(stream_info_map_, evicted_id)) {
+ continue;
+ }
+ if (stream_info_map_[evicted_id].state == STREAM_READ_PENDING) {
+ stream_info_map_[evicted_id].state = STREAM_READ_EVICTED;
+ stream_info_map_[evicted_id].read_headers_callback.Run(
+ ERR_PIPELINE_EVICTION);
+ }
+ }
+ read_next_state_ = READ_STATE_NONE;
+ return result;
+}
+
+void HttpPipelinedConnectionImpl::Close(int pipeline_id,
+ bool not_reusable) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_PIPELINED_CONNECTION_STREAM_CLOSED,
+ base::Bind(&NetLogStreamClosedCallback,
+ stream_info_map_[pipeline_id].source, not_reusable));
+ switch (stream_info_map_[pipeline_id].state) {
+ case STREAM_CREATED:
+ stream_info_map_[pipeline_id].state = STREAM_UNUSED;
+ break;
+
+ case STREAM_BOUND:
+ stream_info_map_[pipeline_id].state = STREAM_CLOSED;
+ break;
+
+ case STREAM_SENDING:
+ usable_ = false;
+ stream_info_map_[pipeline_id].state = STREAM_CLOSED;
+ active_send_request_.reset();
+ send_next_state_ = SEND_STATE_EVICT_PENDING_REQUESTS;
+ DoSendRequestLoop(OK);
+ break;
+
+ case STREAM_SENT:
+ case STREAM_READ_PENDING:
+ usable_ = false;
+ stream_info_map_[pipeline_id].state = STREAM_CLOSED;
+ if (!request_order_.empty() &&
+ pipeline_id == request_order_.front() &&
+ read_next_state_ == READ_STATE_NONE) {
+ read_next_state_ = READ_STATE_EVICT_PENDING_READS;
+ DoReadHeadersLoop(OK);
+ }
+ break;
+
+ case STREAM_ACTIVE:
+ stream_info_map_[pipeline_id].state = STREAM_CLOSED;
+ if (not_reusable) {
+ usable_ = false;
+ }
+ read_next_state_ = READ_STATE_STREAM_CLOSED;
+ DoReadHeadersLoop(OK);
+ break;
+
+ case STREAM_READ_EVICTED:
+ stream_info_map_[pipeline_id].state = STREAM_CLOSED;
+ break;
+
+ case STREAM_CLOSED:
+ case STREAM_UNUSED:
+ // TODO(simonjam): Why is Close() sometimes called twice?
+ break;
+
+ default:
+ CHECK(false);
+ break;
+ }
+}
+
+int HttpPipelinedConnectionImpl::ReadResponseBody(
+ int pipeline_id, IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK_EQ(active_read_id_, pipeline_id);
+ CHECK(stream_info_map_[pipeline_id].parser.get());
+ return stream_info_map_[pipeline_id].parser->ReadResponseBody(
+ buf, buf_len, callback);
+}
+
+UploadProgress HttpPipelinedConnectionImpl::GetUploadProgress(
+ int pipeline_id) const {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_.find(pipeline_id)->second.parser.get());
+ return stream_info_map_.find(pipeline_id)->second.parser->GetUploadProgress();
+}
+
+HttpResponseInfo* HttpPipelinedConnectionImpl::GetResponseInfo(
+ int pipeline_id) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_.find(pipeline_id)->second.parser.get());
+ return stream_info_map_.find(pipeline_id)->second.parser->GetResponseInfo();
+}
+
+bool HttpPipelinedConnectionImpl::IsResponseBodyComplete(
+ int pipeline_id) const {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_.find(pipeline_id)->second.parser.get());
+ return stream_info_map_.find(pipeline_id)->second.parser->
+ IsResponseBodyComplete();
+}
+
+bool HttpPipelinedConnectionImpl::CanFindEndOfResponse(int pipeline_id) const {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_.find(pipeline_id)->second.parser.get());
+ return stream_info_map_.find(pipeline_id)->second.parser->
+ CanFindEndOfResponse();
+}
+
+bool HttpPipelinedConnectionImpl::IsConnectionReused(int pipeline_id) const {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ if (pipeline_id > 1) {
+ return true;
+ }
+ ClientSocketHandle::SocketReuseType reuse_type = connection_->reuse_type();
+ return connection_->is_reused() ||
+ reuse_type == ClientSocketHandle::UNUSED_IDLE;
+}
+
+void HttpPipelinedConnectionImpl::SetConnectionReused(int pipeline_id) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ connection_->set_is_reused(true);
+}
+
+bool HttpPipelinedConnectionImpl::GetLoadTimingInfo(
+ int pipeline_id, LoadTimingInfo* load_timing_info) const {
+ return connection_->GetLoadTimingInfo(IsConnectionReused(pipeline_id),
+ load_timing_info);
+}
+
+void HttpPipelinedConnectionImpl::GetSSLInfo(int pipeline_id,
+ SSLInfo* ssl_info) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_[pipeline_id].parser.get());
+ stream_info_map_[pipeline_id].parser->GetSSLInfo(ssl_info);
+}
+
+void HttpPipelinedConnectionImpl::GetSSLCertRequestInfo(
+ int pipeline_id,
+ SSLCertRequestInfo* cert_request_info) {
+ CHECK(ContainsKey(stream_info_map_, pipeline_id));
+ CHECK(stream_info_map_[pipeline_id].parser.get());
+ stream_info_map_[pipeline_id].parser->GetSSLCertRequestInfo(
+ cert_request_info);
+}
+
+void HttpPipelinedConnectionImpl::Drain(HttpPipelinedStream* stream,
+ HttpNetworkSession* session) {
+ HttpResponseHeaders* headers = stream->GetResponseInfo()->headers.get();
+ if (!stream->CanFindEndOfResponse() || headers->IsChunkEncoded() ||
+ !usable_) {
+ // TODO(simonjam): Drain chunk-encoded responses if they're relatively
+ // common.
+ stream->Close(true);
+ delete stream;
+ return;
+ }
+ HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(stream);
+ drainer->StartWithSize(session, headers->GetContentLength());
+ // |drainer| will delete itself when done.
+}
+
+void HttpPipelinedConnectionImpl::CheckHeadersForPipelineCompatibility(
+ int pipeline_id,
+ int result) {
+ if (result < OK) {
+ switch (result) {
+ // TODO(simonjam): Ignoring specific errors like this may not work.
+ // Collect metrics to see if this code is useful.
+ case ERR_ABORTED:
+ case ERR_INTERNET_DISCONNECTED:
+ case ERR_NETWORK_CHANGED:
+ // These errors are no fault of the server.
+ break;
+
+ default:
+ ReportPipelineFeedback(pipeline_id, PIPELINE_SOCKET_ERROR);
+ break;
+ }
+ return;
+ }
+ HttpResponseInfo* info = GetResponseInfo(pipeline_id);
+ const HttpVersion required_version(1, 1);
+ if (info->headers->GetParsedHttpVersion() < required_version) {
+ ReportPipelineFeedback(pipeline_id, OLD_HTTP_VERSION);
+ return;
+ }
+ if (!info->headers->IsKeepAlive() || !CanFindEndOfResponse(pipeline_id)) {
+ usable_ = false;
+ ReportPipelineFeedback(pipeline_id, MUST_CLOSE_CONNECTION);
+ return;
+ }
+ if (info->headers->HasHeader(
+ HttpAuth::GetChallengeHeaderName(HttpAuth::AUTH_SERVER))) {
+ ReportPipelineFeedback(pipeline_id, AUTHENTICATION_REQUIRED);
+ return;
+ }
+ ReportPipelineFeedback(pipeline_id, OK);
+}
+
+void HttpPipelinedConnectionImpl::ReportPipelineFeedback(int pipeline_id,
+ Feedback feedback) {
+ std::string feedback_str;
+ switch (feedback) {
+ case OK:
+ feedback_str = "OK";
+ break;
+
+ case PIPELINE_SOCKET_ERROR:
+ feedback_str = "PIPELINE_SOCKET_ERROR";
+ break;
+
+ case OLD_HTTP_VERSION:
+ feedback_str = "OLD_HTTP_VERSION";
+ break;
+
+ case MUST_CLOSE_CONNECTION:
+ feedback_str = "MUST_CLOSE_CONNECTION";
+ break;
+
+ case AUTHENTICATION_REQUIRED:
+ feedback_str = "AUTHENTICATION_REQUIRED";
+ break;
+
+ default:
+ NOTREACHED();
+ feedback_str = "UNKNOWN";
+ break;
+ }
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_PIPELINED_CONNECTION_RECEIVED_HEADERS,
+ base::Bind(&NetLogReceivedHeadersCallback,
+ stream_info_map_[pipeline_id].source, &feedback_str));
+ delegate_->OnPipelineFeedback(this, feedback);
+}
+
+void HttpPipelinedConnectionImpl::QueueUserCallback(
+ int pipeline_id, const CompletionCallback& callback, int rv,
+ const tracked_objects::Location& from_here) {
+ CHECK(stream_info_map_[pipeline_id].pending_user_callback.is_null());
+ stream_info_map_[pipeline_id].pending_user_callback = callback;
+ base::MessageLoop::current()->PostTask(
+ from_here,
+ base::Bind(&HttpPipelinedConnectionImpl::FireUserCallback,
+ weak_factory_.GetWeakPtr(), pipeline_id, rv));
+}
+
+void HttpPipelinedConnectionImpl::FireUserCallback(int pipeline_id,
+ int result) {
+ if (ContainsKey(stream_info_map_, pipeline_id)) {
+ CHECK(!stream_info_map_[pipeline_id].pending_user_callback.is_null());
+ CompletionCallback callback =
+ stream_info_map_[pipeline_id].pending_user_callback;
+ stream_info_map_[pipeline_id].pending_user_callback.Reset();
+ callback.Run(result);
+ }
+}
+
+int HttpPipelinedConnectionImpl::depth() const {
+ return stream_info_map_.size();
+}
+
+bool HttpPipelinedConnectionImpl::usable() const {
+ return usable_;
+}
+
+bool HttpPipelinedConnectionImpl::active() const {
+ return active_;
+}
+
+const SSLConfig& HttpPipelinedConnectionImpl::used_ssl_config() const {
+ return used_ssl_config_;
+}
+
+const ProxyInfo& HttpPipelinedConnectionImpl::used_proxy_info() const {
+ return used_proxy_info_;
+}
+
+const BoundNetLog& HttpPipelinedConnectionImpl::net_log() const {
+ return net_log_;
+}
+
+bool HttpPipelinedConnectionImpl::was_npn_negotiated() const {
+ return was_npn_negotiated_;
+}
+
+NextProto HttpPipelinedConnectionImpl::protocol_negotiated()
+ const {
+ return protocol_negotiated_;
+}
+
+HttpPipelinedConnectionImpl::PendingSendRequest::PendingSendRequest()
+ : pipeline_id(0),
+ response(NULL) {
+}
+
+HttpPipelinedConnectionImpl::PendingSendRequest::~PendingSendRequest() {
+}
+
+HttpPipelinedConnectionImpl::StreamInfo::StreamInfo()
+ : state(STREAM_CREATED) {
+}
+
+HttpPipelinedConnectionImpl::StreamInfo::~StreamInfo() {
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_connection_impl.h b/chromium/net/http/http_pipelined_connection_impl.h
new file mode 100644
index 00000000000..f8246a06f9b
--- /dev/null
+++ b/chromium/net/http/http_pipelined_connection_impl.h
@@ -0,0 +1,328 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_CONNECTION_IMPL_H_
+#define NET_HTTP_HTTP_PIPELINED_CONNECTION_IMPL_H_
+
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/location.h"
+#include "base/memory/linked_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_stream_parser.h"
+#include "net/proxy/proxy_info.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class GrowableIOBuffer;
+class HostPortPair;
+class HttpNetworkSession;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class IOBuffer;
+struct LoadTimingInfo;
+class SSLCertRequestInfo;
+class SSLInfo;
+
+// This class manages all of the state for a single pipelined connection. It
+// tracks the order that HTTP requests are sent and enforces that the
+// subsequent reads occur in the appropriate order.
+//
+// If an error occurs related to pipelining, ERR_PIPELINE_EVICTION will be
+// returned to the client. This indicates the client should retry the request
+// without pipelining.
+class NET_EXPORT_PRIVATE HttpPipelinedConnectionImpl
+ : public HttpPipelinedConnection {
+ public:
+ class Factory : public HttpPipelinedConnection::Factory {
+ public:
+ virtual HttpPipelinedConnection* CreateNewPipeline(
+ ClientSocketHandle* connection,
+ HttpPipelinedConnection::Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) OVERRIDE;
+ };
+
+ HttpPipelinedConnectionImpl(ClientSocketHandle* connection,
+ Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated);
+ virtual ~HttpPipelinedConnectionImpl();
+
+ // HttpPipelinedConnection interface.
+
+ // Used by HttpStreamFactoryImpl and friends.
+ virtual HttpPipelinedStream* CreateNewStream() OVERRIDE;
+
+ // Used by HttpPipelinedHost.
+ virtual int depth() const OVERRIDE;
+ virtual bool usable() const OVERRIDE;
+ virtual bool active() const OVERRIDE;
+
+ // Used by HttpStreamFactoryImpl.
+ virtual const SSLConfig& used_ssl_config() const OVERRIDE;
+ virtual const ProxyInfo& used_proxy_info() const OVERRIDE;
+ virtual const BoundNetLog& net_log() const OVERRIDE;
+ virtual bool was_npn_negotiated() const OVERRIDE;
+ virtual NextProto protocol_negotiated() const OVERRIDE;
+
+ // Used by HttpPipelinedStream.
+
+ // Notifies this pipeline that a stream is no longer using it.
+ void OnStreamDeleted(int pipeline_id);
+
+ // Effective implementation of HttpStream. Note that we don't directly
+ // implement that interface. Instead, these functions will be called by the
+ // pass-through methods in HttpPipelinedStream.
+ void InitializeParser(int pipeline_id,
+ const HttpRequestInfo* request,
+ const BoundNetLog& net_log);
+
+ int SendRequest(int pipeline_id,
+ const std::string& request_line,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback);
+
+ int ReadResponseHeaders(int pipeline_id,
+ const CompletionCallback& callback);
+
+ int ReadResponseBody(int pipeline_id,
+ IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ void Close(int pipeline_id,
+ bool not_reusable);
+
+ UploadProgress GetUploadProgress(int pipeline_id) const;
+
+ HttpResponseInfo* GetResponseInfo(int pipeline_id);
+
+ bool IsResponseBodyComplete(int pipeline_id) const;
+
+ bool CanFindEndOfResponse(int pipeline_id) const;
+
+ bool IsConnectionReused(int pipeline_id) const;
+
+ void SetConnectionReused(int pipeline_id);
+
+ bool GetLoadTimingInfo(int pipeline_id,
+ LoadTimingInfo* load_timing_info) const;
+
+ void GetSSLInfo(int pipeline_id, SSLInfo* ssl_info);
+
+ void GetSSLCertRequestInfo(int pipeline_id,
+ SSLCertRequestInfo* cert_request_info);
+
+ // Attempts to drain the response body for |stream| so that the pipeline may
+ // be reused.
+ void Drain(HttpPipelinedStream* stream, HttpNetworkSession* session);
+
+ private:
+ enum StreamState {
+ STREAM_CREATED,
+ STREAM_BOUND,
+ STREAM_SENDING,
+ STREAM_SENT,
+ STREAM_READ_PENDING,
+ STREAM_ACTIVE,
+ STREAM_CLOSED,
+ STREAM_READ_EVICTED,
+ STREAM_UNUSED,
+ };
+ enum SendRequestState {
+ SEND_STATE_START_IMMEDIATELY,
+ SEND_STATE_START_NEXT_DEFERRED_REQUEST,
+ SEND_STATE_SEND_ACTIVE_REQUEST,
+ SEND_STATE_COMPLETE,
+ SEND_STATE_EVICT_PENDING_REQUESTS,
+ SEND_STATE_NONE,
+ };
+ enum ReadHeadersState {
+ READ_STATE_START_IMMEDIATELY,
+ READ_STATE_START_NEXT_DEFERRED_READ,
+ READ_STATE_READ_HEADERS,
+ READ_STATE_READ_HEADERS_COMPLETE,
+ READ_STATE_WAITING_FOR_CLOSE,
+ READ_STATE_STREAM_CLOSED,
+ READ_STATE_NONE,
+ READ_STATE_EVICT_PENDING_READS,
+ };
+
+ struct PendingSendRequest {
+ PendingSendRequest();
+ ~PendingSendRequest();
+
+ int pipeline_id;
+ std::string request_line;
+ HttpRequestHeaders headers;
+ HttpResponseInfo* response;
+ CompletionCallback callback;
+ };
+
+ struct StreamInfo {
+ StreamInfo();
+ ~StreamInfo();
+
+ linked_ptr<HttpStreamParser> parser;
+ CompletionCallback read_headers_callback;
+ CompletionCallback pending_user_callback;
+ StreamState state;
+ NetLog::Source source;
+ };
+
+ typedef std::map<int, StreamInfo> StreamInfoMap;
+
+ // Called after the first request is sent or in a task sometime after the
+ // first stream is added to this pipeline. This gives the first request
+ // priority to send, but doesn't hold up other requests if it doesn't.
+ // When called the first time, notifies the |delegate_| that we can accept new
+ // requests.
+ void ActivatePipeline();
+
+ // Responsible for sending one request at a time and waiting until each
+ // comepletes.
+ int DoSendRequestLoop(int result);
+
+ // Called when an asynchronous Send() completes.
+ void OnSendIOCallback(int result);
+
+ // Activates the only request in |pending_send_request_queue_|. This should
+ // only be called via SendRequest() when the send loop is idle.
+ int DoStartRequestImmediately(int result);
+
+ // Activates the first request in |pending_send_request_queue_| that hasn't
+ // been closed, if any. This is called via DoSendComplete() after a prior
+ // request complets.
+ int DoStartNextDeferredRequest(int result);
+
+ // Sends the active request.
+ int DoSendActiveRequest(int result);
+
+ // Notifies the user that the send has completed. This may be called directly
+ // after SendRequest() for a synchronous request, or it may be called in
+ // response to OnSendIOCallback for an asynchronous request.
+ int DoSendComplete(int result);
+
+ // Evicts all unsent deferred requests. This is called if there is a Send()
+ // error or one of our streams informs us the connection is no longer
+ // reusable.
+ int DoEvictPendingSendRequests(int result);
+
+ // Ensures that only the active request's HttpPipelinedSocket can read from
+ // the underlying socket until it completes. A HttpPipelinedSocket informs us
+ // that it's done by calling Close().
+ int DoReadHeadersLoop(int result);
+
+ // Called when the pending asynchronous ReadResponseHeaders() completes.
+ void OnReadIOCallback(int result);
+
+ // Invokes DoStartNextDeferredRead() if the read loop is idle. This is called
+ // via a task queued when the previous |active_read_id_| closes its stream
+ // after a succesful response.
+ void StartNextDeferredRead();
+
+ // Activates the next read request immediately. This is called via
+ // ReadResponseHeaders() if that stream is at the front of |request_order_|
+ // and the read loop is idle.
+ int DoStartReadImmediately(int result);
+
+ // Activates the next read request in |request_order_| if it's ready to go.
+ // This is called via StartNextDeferredRead().
+ int DoStartNextDeferredRead(int result);
+
+ // Calls ReadResponseHeaders() on the active request's parser.
+ int DoReadHeaders(int result);
+
+ // Notifies the user that reading the headers has completed. This may happen
+ // directly after DoReadNextHeaders() if the response is already available.
+ // Otherwise, it is called in response to OnReadIOCallback().
+ int DoReadHeadersComplete(int result);
+
+ // Halts the read loop until Close() is called by the active stream.
+ int DoReadWaitForClose(int result);
+
+ // Cleans up the state associated with the active request. Invokes
+ // DoReadNextHeaders() in a new task to start the next response. This is
+ // called after the active request's HttpPipelinedSocket calls Close().
+ int DoReadStreamClosed();
+
+ // Removes all pending ReadResponseHeaders() requests from the queue. This may
+ // happen if there is an error with the pipeline or one of our
+ // HttpPipelinedSockets indicates the connection was suddenly closed.
+ int DoEvictPendingReadHeaders(int result);
+
+ // Determines if the response headers indicate pipelining will work. This is
+ // called every time we receive headers.
+ void CheckHeadersForPipelineCompatibility(int pipeline_id, int result);
+
+ // Reports back to |delegate_| whether pipelining will work.
+ void ReportPipelineFeedback(int pipeline_id, Feedback feedback);
+
+ // Posts a task to fire the user's callback in response to SendRequest() or
+ // ReadResponseHeaders() completing on an underlying parser. This might be
+ // invoked in response to our own IO callbacks, or it may be invoked if the
+ // underlying parser completes SendRequest() or ReadResponseHeaders()
+ // synchronously, but we've already returned ERR_IO_PENDING to the user's
+ // SendRequest() or ReadResponseHeaders() call into us.
+ void QueueUserCallback(int pipeline_id,
+ const CompletionCallback& callback,
+ int rv,
+ const tracked_objects::Location& from_here);
+
+ // Invokes the callback queued in QueueUserCallback().
+ void FireUserCallback(int pipeline_id, int result);
+
+ Delegate* delegate_;
+ scoped_ptr<ClientSocketHandle> connection_;
+ SSLConfig used_ssl_config_;
+ ProxyInfo used_proxy_info_;
+ BoundNetLog net_log_;
+ bool was_npn_negotiated_;
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated_;
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+ int next_pipeline_id_;
+ bool active_;
+ bool usable_;
+ bool completed_one_request_;
+ base::WeakPtrFactory<HttpPipelinedConnectionImpl> weak_factory_;
+
+ StreamInfoMap stream_info_map_;
+
+ std::queue<int> request_order_;
+
+ std::queue<PendingSendRequest*> pending_send_request_queue_;
+ scoped_ptr<PendingSendRequest> active_send_request_;
+ SendRequestState send_next_state_;
+ bool send_still_on_call_stack_;
+
+ ReadHeadersState read_next_state_;
+ int active_read_id_;
+ bool read_still_on_call_stack_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedConnectionImpl);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_CONNECTION_IMPL_H_
diff --git a/chromium/net/http/http_pipelined_connection_impl_unittest.cc b/chromium/net/http/http_pipelined_connection_impl_unittest.cc
new file mode 100644
index 00000000000..1bf7597e33d
--- /dev/null
+++ b/chromium/net/http/http_pipelined_connection_impl_unittest.cc
@@ -0,0 +1,1606 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_connection_impl.h"
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_pipelined_stream.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::StrEq;
+
+namespace net {
+
+class DummySocketParams : public base::RefCounted<DummySocketParams> {
+ private:
+ friend class base::RefCounted<DummySocketParams>;
+ ~DummySocketParams() {}
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(MockTransportClientSocketPool,
+ DummySocketParams);
+
+namespace {
+
+// Tests the load timing of a stream that's connected and is not the first
+// request sent on a connection.
+void TestLoadTimingReused(const HttpStream& stream) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+// Tests the load timing of a stream that's connected and using a fresh
+// connection.
+void TestLoadTimingNotReused(const HttpStream& stream) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+class MockPipelineDelegate : public HttpPipelinedConnection::Delegate {
+ public:
+ MOCK_METHOD1(OnPipelineHasCapacity, void(HttpPipelinedConnection* pipeline));
+ MOCK_METHOD2(OnPipelineFeedback, void(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback));
+};
+
+class SuddenCloseObserver : public base::MessageLoop::TaskObserver {
+ public:
+ SuddenCloseObserver(HttpStream* stream, int close_before_task)
+ : stream_(stream),
+ close_before_task_(close_before_task),
+ current_task_(0) { }
+
+ virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE {
+ ++current_task_;
+ if (current_task_ == close_before_task_) {
+ stream_->Close(false);
+ base::MessageLoop::current()->RemoveTaskObserver(this);
+ }
+ }
+
+ virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {}
+
+ private:
+ HttpStream* stream_;
+ int close_before_task_;
+ int current_task_;
+};
+
+class HttpPipelinedConnectionImplTest : public testing::Test {
+ public:
+ HttpPipelinedConnectionImplTest()
+ : histograms_("a"),
+ pool_(1, 1, &histograms_, &factory_),
+ origin_("host", 123) {
+ }
+
+ void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void Initialize(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count) {
+ data_.reset(new DeterministicSocketData(reads, reads_count,
+ writes, writes_count));
+ data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ if (reads_count || writes_count) {
+ data_->StopAfter(reads_count + writes_count);
+ }
+ factory_.AddSocketDataProvider(data_.get());
+ scoped_refptr<DummySocketParams> params;
+ ClientSocketHandle* connection = new ClientSocketHandle;
+ // Only give the connection a real NetLog to make sure that LoadTiming uses
+ // the connection's ID, rather than the pipeline's. Since pipelines are
+ // destroyed when they've responded to all requests, but the connection
+ // lives on, this is an important behavior.
+ connection->Init("a", params, MEDIUM, CompletionCallback(), &pool_,
+ net_log_.bound());
+ pipeline_.reset(new HttpPipelinedConnectionImpl(
+ connection, &delegate_, origin_, ssl_config_, proxy_info_,
+ BoundNetLog(), false, kProtoUnknown));
+ }
+
+ HttpRequestInfo* GetRequestInfo(const std::string& filename) {
+ HttpRequestInfo* request_info = new HttpRequestInfo;
+ request_info->url = GURL("http://localhost/" + filename);
+ request_info->method = "GET";
+ request_info_vector_.push_back(request_info);
+ return request_info;
+ }
+
+ HttpStream* NewTestStream(const std::string& filename) {
+ HttpStream* stream = pipeline_->CreateNewStream();
+ HttpRequestInfo* request_info = GetRequestInfo(filename);
+ int rv = stream->InitializeStream(
+ request_info, DEFAULT_PRIORITY, BoundNetLog(), CompletionCallback());
+ DCHECK_EQ(OK, rv);
+ return stream;
+ }
+
+ void ExpectResponse(const std::string& expected,
+ scoped_ptr<HttpStream>& stream, bool async) {
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(expected.size()));
+
+ if (async) {
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->ReadResponseBody(buffer.get(), expected.size(),
+ callback_.callback()));
+ data_->RunFor(1);
+ EXPECT_EQ(static_cast<int>(expected.size()), callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(static_cast<int>(expected.size()),
+ stream->ReadResponseBody(buffer.get(), expected.size(),
+ callback_.callback()));
+ }
+ std::string actual(buffer->data(), expected.size());
+ EXPECT_THAT(actual, StrEq(expected));
+ }
+
+ void TestSyncRequest(scoped_ptr<HttpStream>& stream,
+ const std::string& filename) {
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK, stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse(filename, stream, false);
+
+ stream->Close(false);
+ }
+
+ CapturingBoundNetLog net_log_;
+ DeterministicMockClientSocketFactory factory_;
+ ClientSocketPoolHistograms histograms_;
+ MockTransportClientSocketPool pool_;
+ scoped_ptr<DeterministicSocketData> data_;
+
+ HostPortPair origin_;
+ SSLConfig ssl_config_;
+ ProxyInfo proxy_info_;
+ NiceMock<MockPipelineDelegate> delegate_;
+ TestCompletionCallback callback_;
+ scoped_ptr<HttpPipelinedConnectionImpl> pipeline_;
+ ScopedVector<HttpRequestInfo> request_info_vector_;
+};
+
+TEST_F(HttpPipelinedConnectionImplTest, PipelineNotUsed) {
+ Initialize(NULL, 0, NULL, 0);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, StreamNotUsed) {
+ Initialize(NULL, 0, NULL, 0);
+
+ scoped_ptr<HttpStream> stream(pipeline_->CreateNewStream());
+
+ stream->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, StreamBoundButNotUsed) {
+ Initialize(NULL, 0, NULL, 0);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+
+ TestLoadTimingNotReused(*stream);
+ stream->Close(false);
+ TestLoadTimingNotReused(*stream);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, SyncSingleRequest) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestLoadTimingNotReused(*stream);
+ TestSyncRequest(stream, "ok.html");
+ TestLoadTimingNotReused(*stream);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AsyncSingleRequest) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(ASYNC, 3, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(headers, &response,
+ callback_.callback()));
+ data_->RunFor(1);
+ EXPECT_LE(OK, callback_.WaitForResult());
+ TestLoadTimingNotReused(*stream);
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
+ data_->RunFor(2);
+ EXPECT_LE(OK, callback_.WaitForResult());
+ TestLoadTimingNotReused(*stream);
+
+ ExpectResponse("ok.html", stream, true);
+ TestLoadTimingNotReused(*stream);
+
+ stream->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, LockStepAsyncRequests) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, 1, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(ASYNC, 4, "ok.html"),
+ MockRead(ASYNC, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(ASYNC, 7, "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequest(headers1, &response1,
+ callback_.callback()));
+ TestLoadTimingNotReused(*stream1);
+
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequest(headers2, &response2,
+ callback_.callback()));
+ TestLoadTimingReused(*stream2);
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, callback_.WaitForResult());
+ data_->RunFor(1);
+ EXPECT_LE(OK, callback_.WaitForResult());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream1->ReadResponseHeaders(callback_.callback()));
+ EXPECT_EQ(ERR_IO_PENDING, stream2->ReadResponseHeaders(callback_.callback()));
+
+ data_->RunFor(2);
+ EXPECT_LE(OK, callback_.WaitForResult());
+
+ ExpectResponse("ok.html", stream1, true);
+
+ TestLoadTimingNotReused(*stream1);
+ LoadTimingInfo load_timing_info1;
+ EXPECT_TRUE(stream1->GetLoadTimingInfo(&load_timing_info1));
+ stream1->Close(false);
+
+ data_->RunFor(2);
+ EXPECT_LE(OK, callback_.WaitForResult());
+
+ ExpectResponse("ko.html", stream2, true);
+
+ TestLoadTimingReused(*stream2);
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(stream2->GetLoadTimingInfo(&load_timing_info2));
+ EXPECT_EQ(load_timing_info1.socket_log_id,
+ load_timing_info2.socket_log_id);
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, TwoResponsesInOnePacket) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 7\r\n\r\n"
+ "ok.html"
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 7\r\n\r\n"
+ "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", stream1, false);
+ stream1->Close(false);
+
+ EXPECT_EQ(OK, stream2->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ko.html", stream2, false);
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, SendOrderSwapped) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 4, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "ko.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ TestSyncRequest(stream2, "ko.html");
+ TestSyncRequest(stream1, "ok.html");
+ TestLoadTimingNotReused(*stream1);
+ TestLoadTimingReused(*stream2);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, ReadOrderSwapped) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, stream2->ReadResponseHeaders(callback_.callback()));
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", stream1, false);
+
+ stream1->Close(false);
+
+ EXPECT_LE(OK, callback_.WaitForResult());
+ ExpectResponse("ko.html", stream2, false);
+
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, SendWhileReading) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+ ExpectResponse("ok.html", stream1, false);
+ stream1->Close(false);
+
+ EXPECT_EQ(OK, stream2->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ko.html", stream2, false);
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AsyncSendWhileAsyncReadBlocked) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, 3, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(ASYNC, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ TestCompletionCallback callback1;
+ std::string expected = "ok.html";
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(expected.size()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream1->ReadResponseBody(buffer.get(), expected.size(),
+ callback1.callback()));
+
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequest(headers2, &response2,
+ callback2.callback()));
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, callback2.WaitForResult());
+ EXPECT_EQ(ERR_IO_PENDING, stream2->ReadResponseHeaders(callback2.callback()));
+
+ data_->RunFor(1);
+ EXPECT_EQ(static_cast<int>(expected.size()), callback1.WaitForResult());
+ std::string actual(buffer->data(), expected.size());
+ EXPECT_THAT(actual, StrEq(expected));
+ stream1->Close(false);
+
+ data_->StopAfter(8);
+ EXPECT_LE(OK, callback2.WaitForResult());
+ ExpectResponse("ko.html", stream2, false);
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, UnusedStreamAllowsLaterUse) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> unused_stream(NewTestStream("unused.html"));
+ unused_stream->Close(false);
+
+ scoped_ptr<HttpStream> later_stream(NewTestStream("ok.html"));
+ TestSyncRequest(later_stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, UnsentStreamAllowsLaterUse) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 4, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 7\r\n\r\n"),
+ MockRead(ASYNC, 3, "ok.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "ko.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ scoped_ptr<HttpStream> unsent_stream(NewTestStream("unsent.html"));
+ HttpRequestHeaders unsent_headers;
+ HttpResponseInfo unsent_response;
+ EXPECT_EQ(ERR_IO_PENDING, unsent_stream->SendRequest(unsent_headers,
+ &unsent_response,
+ callback_.callback()));
+ unsent_stream->Close(false);
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, callback_.WaitForResult());
+
+ EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(callback_.callback()));
+ data_->RunFor(2);
+ EXPECT_LE(OK, callback_.WaitForResult());
+
+ ExpectResponse("ok.html", stream, true);
+
+ stream->Close(false);
+
+ data_->StopAfter(8);
+ scoped_ptr<HttpStream> later_stream(NewTestStream("ko.html"));
+ TestSyncRequest(later_stream, "ko.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FailedSend) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, ERR_FAILED),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> failed_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+ scoped_ptr<HttpStream> closed_stream(NewTestStream("closed.html"));
+ scoped_ptr<HttpStream> rejected_stream(NewTestStream("rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ TestCompletionCallback failed_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ failed_stream->SendRequest(headers, &response,
+ failed_callback.callback()));
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->SendRequest(headers, &response,
+ evicted_callback.callback()));
+ EXPECT_EQ(ERR_IO_PENDING, closed_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ closed_stream->Close(false);
+
+ data_->RunFor(1);
+ EXPECT_EQ(ERR_FAILED, failed_callback.WaitForResult());
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ failed_stream->Close(true);
+ evicted_stream->Close(true);
+ rejected_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, ConnectionSuddenlyClosedAfterResponse) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /read_evicted.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 2, "GET /read_rejected.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, ERR_SOCKET_NOT_CONNECTED, 5),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(ASYNC, OK, 6), // Connection closed message. Not read before the
+ // ERR_SOCKET_NOT_CONNECTED.
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> closed_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> read_evicted_stream(
+ NewTestStream("read_evicted.html"));
+ scoped_ptr<HttpStream> read_rejected_stream(
+ NewTestStream("read_rejected.html"));
+ scoped_ptr<HttpStream> send_closed_stream(
+ NewTestStream("send_closed.html"));
+ scoped_ptr<HttpStream> send_evicted_stream(
+ NewTestStream("send_evicted.html"));
+ scoped_ptr<HttpStream> send_rejected_stream(
+ NewTestStream("send_rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, closed_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, read_evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK, read_rejected_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ TestCompletionCallback send_closed_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ send_closed_stream->SendRequest(headers, &response,
+ send_closed_callback.callback()));
+ TestCompletionCallback send_evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ send_evicted_stream->SendRequest(headers, &response,
+ send_evicted_callback.callback()));
+
+ TestCompletionCallback read_evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ read_evicted_stream->ReadResponseHeaders(
+ read_evicted_callback.callback()));
+
+ EXPECT_EQ(OK, closed_stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", closed_stream, false);
+ closed_stream->Close(true);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, read_evicted_callback.WaitForResult());
+ read_evicted_stream->Close(true);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ read_rejected_stream->ReadResponseHeaders(callback_.callback()));
+ read_rejected_stream->Close(true);
+
+ data_->RunFor(1);
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, send_closed_callback.WaitForResult());
+ send_closed_stream->Close(true);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, send_evicted_callback.WaitForResult());
+ send_evicted_stream->Close(true);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ send_rejected_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ send_rejected_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AbortWhileSending) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /aborts.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> aborted_stream(NewTestStream("aborts.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ TestCompletionCallback aborted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ aborted_stream->SendRequest(headers, &response,
+ aborted_callback.callback()));
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->SendRequest(headers, &response,
+ evicted_callback.callback()));
+
+ aborted_stream->Close(true);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+ EXPECT_FALSE(aborted_callback.have_result());
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AbortWhileSendingSecondRequest) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, 1, "GET /aborts.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> aborted_stream(NewTestStream("aborts.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ TestCompletionCallback ok_callback;
+ EXPECT_EQ(ERR_IO_PENDING, ok_stream->SendRequest(headers, &response,
+ ok_callback.callback()));
+ TestCompletionCallback aborted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ aborted_stream->SendRequest(headers, &response,
+ aborted_callback.callback()));
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->SendRequest(headers, &response,
+ evicted_callback.callback()));
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, ok_callback.WaitForResult());
+ base::MessageLoop::current()->RunUntilIdle();
+ aborted_stream->Close(true);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+ EXPECT_FALSE(aborted_callback.have_result());
+ ok_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AbortWhileReadingHeaders) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /aborts.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_FAILED, 2),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> aborted_stream(NewTestStream("aborts.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+ scoped_ptr<HttpStream> rejected_stream(NewTestStream("rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK,
+ aborted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK,
+ evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ aborted_stream->ReadResponseHeaders(callback_.callback()));
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ aborted_stream->Close(true);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ rejected_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, PendingResponseAbandoned) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /abandoned.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 2, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 4, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 5, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> abandoned_stream(NewTestStream("abandoned.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK, abandoned_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, ok_stream->ReadResponseHeaders(callback_.callback()));
+ TestCompletionCallback abandoned_callback;
+ EXPECT_EQ(ERR_IO_PENDING, abandoned_stream->ReadResponseHeaders(
+ abandoned_callback.callback()));
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ abandoned_stream->Close(false);
+
+ ExpectResponse("ok.html", ok_stream, false);
+ ok_stream->Close(false);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+ EXPECT_FALSE(evicted_stream->IsConnectionReusable());
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, DisconnectedAfterOneRequestRecovery) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /rejected.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, ERR_SOCKET_NOT_CONNECTED, 5),
+ MockWrite(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 7),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 6),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> rejected_read_stream(NewTestStream("rejected.html"));
+ scoped_ptr<HttpStream> evicted_send_stream(NewTestStream("evicted.html"));
+ scoped_ptr<HttpStream> rejected_send_stream(NewTestStream("rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, rejected_read_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, ok_stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", ok_stream, false);
+ ok_stream->Close(false);
+
+ TestCompletionCallback read_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_send_stream->SendRequest(headers, &response,
+ read_callback.callback()));
+ data_->RunFor(1);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, read_callback.WaitForResult());
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_read_stream->ReadResponseHeaders(callback_.callback()));
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_send_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ rejected_read_stream->Close(true);
+ rejected_send_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, DisconnectedPendingReadRecovery) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 5),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, ok_stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", ok_stream, false);
+
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ ok_stream->Close(false);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseCalledBeforeNextReadLoop) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 5),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, ok_stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", ok_stream, false);
+
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ ok_stream->Close(false);
+ evicted_stream->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseCalledBeforeReadCallback) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 5),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, ok_stream->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", ok_stream, false);
+
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ ok_stream->Close(false);
+
+ // The posted tasks should be:
+ // 1. DoReadHeadersLoop, which will post:
+ // 2. InvokeUserCallback
+ SuddenCloseObserver observer(evicted_stream.get(), 2);
+ base::MessageLoop::current()->AddTaskObserver(&observer);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(evicted_callback.have_result());
+}
+
+class StreamDeleter {
+ public:
+ StreamDeleter(HttpStream* stream)
+ : stream_(stream),
+ callback_(base::Bind(&StreamDeleter::OnIOComplete,
+ base::Unretained(this))) {
+ }
+
+ ~StreamDeleter() {
+ EXPECT_FALSE(stream_);
+ }
+
+ const CompletionCallback& callback() { return callback_; }
+
+ private:
+ void OnIOComplete(int result) {
+ stream_->Close(true);
+ stream_.reset();
+ }
+
+ scoped_ptr<HttpStream> stream_;
+ CompletionCallback callback_;
+};
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseCalledDuringSendCallback) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ HttpStream* stream(NewTestStream("ok.html"));
+
+ StreamDeleter deleter(stream);
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(headers, &response,
+ deleter.callback()));
+ data_->RunFor(1);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseCalledDuringReadCallback) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 7\r\n\r\n"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ HttpStream* stream(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers,
+ &response, callback_.callback()));
+
+ StreamDeleter deleter(stream);
+ EXPECT_EQ(ERR_IO_PENDING, stream->ReadResponseHeaders(deleter.callback()));
+ data_->RunFor(1);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest,
+ CloseCalledDuringReadCallbackWithPendingRead) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /failed.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 7\r\n\r\n"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ HttpStream* failed_stream(NewTestStream("failed.html"));
+ HttpStream* evicted_stream(NewTestStream("evicted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, failed_stream->SendRequest(headers, &response,
+ callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ StreamDeleter failed_deleter(failed_stream);
+ EXPECT_EQ(ERR_IO_PENDING,
+ failed_stream->ReadResponseHeaders(failed_deleter.callback()));
+ StreamDeleter evicted_deleter(evicted_stream);
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_deleter.callback()));
+ data_->RunFor(1);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseOtherDuringReadCallback) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /deleter.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /deleted.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 7\r\n\r\n"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> deleter_stream(NewTestStream("deleter.html"));
+ HttpStream* deleted_stream(NewTestStream("deleted.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, deleter_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, deleted_stream->SendRequest(headers,
+ &response, callback_.callback()));
+
+ StreamDeleter deleter(deleted_stream);
+ EXPECT_EQ(ERR_IO_PENDING,
+ deleter_stream->ReadResponseHeaders(deleter.callback()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ deleted_stream->ReadResponseHeaders(callback_.callback()));
+ data_->RunFor(1);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseBeforeSendCallbackRuns) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /close.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, 1, "GET /dummy.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> close_stream(NewTestStream("close.html"));
+ scoped_ptr<HttpStream> dummy_stream(NewTestStream("dummy.html"));
+
+ scoped_ptr<TestCompletionCallback> close_callback(
+ new TestCompletionCallback);
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(ERR_IO_PENDING,
+ close_stream->SendRequest(headers,
+ &response, close_callback->callback()));
+
+ data_->RunFor(1);
+ EXPECT_FALSE(close_callback->have_result());
+
+ close_stream->Close(false);
+ close_stream.reset();
+ close_callback.reset();
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, CloseBeforeReadCallbackRuns) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /close.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /dummy.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 7\r\n\r\n"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> close_stream(NewTestStream("close.html"));
+ scoped_ptr<HttpStream> dummy_stream(NewTestStream("dummy.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, close_stream->SendRequest(headers,
+ &response, callback_.callback()));
+
+ scoped_ptr<TestCompletionCallback> close_callback(
+ new TestCompletionCallback);
+ EXPECT_EQ(ERR_IO_PENDING,
+ close_stream->ReadResponseHeaders(close_callback->callback()));
+
+ data_->RunFor(1);
+ EXPECT_FALSE(close_callback->have_result());
+
+ close_stream->Close(false);
+ close_stream.reset();
+ close_callback.reset();
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, AbortWhileSendQueued) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(ASYNC, 1, "GET /ko.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ko.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, stream1->SendRequest(headers1, &response1,
+ callback1.callback()));
+
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, stream2->SendRequest(headers2, &response2,
+ callback2.callback()));
+
+ stream2.reset();
+ stream1->Close(true);
+
+ EXPECT_FALSE(callback2.have_result());
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, NoGapBetweenCloseAndEviction) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /close.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 2, "GET /dummy.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 7\r\n\r\n"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> close_stream(NewTestStream("close.html"));
+ scoped_ptr<HttpStream> dummy_stream(NewTestStream("dummy.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, close_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ TestCompletionCallback close_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ close_stream->ReadResponseHeaders(close_callback.callback()));
+
+ EXPECT_EQ(OK, dummy_stream->SendRequest(headers, &response,
+ callback_.callback()));
+
+ TestCompletionCallback dummy_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ dummy_stream->ReadResponseHeaders(dummy_callback.callback()));
+
+ close_stream->Close(true);
+ close_stream.reset();
+
+ EXPECT_TRUE(dummy_callback.have_result());
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, dummy_callback.WaitForResult());
+ dummy_stream->Close(true);
+ dummy_stream.reset();
+ pipeline_.reset();
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, RecoverFromDrainOnRedirect) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2,
+ "HTTP/1.1 302 OK\r\n"
+ "Content-Length: 8\r\n\r\n"
+ "redirect"),
+ MockRead(SYNCHRONOUS, 3,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 7\r\n\r\n"
+ "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("redirect.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ stream1.release()->Drain(NULL);
+
+ EXPECT_EQ(OK, stream2->ReadResponseHeaders(callback_.callback()));
+ ExpectResponse("ok.html", stream2, false);
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, EvictAfterDrainOfUnknownSize) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2,
+ "HTTP/1.1 302 OK\r\n\r\n"
+ "redirect"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("redirect.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ stream1.release()->Drain(NULL);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ stream2->ReadResponseHeaders(callback_.callback()));
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, EvictAfterFailedDrain) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2,
+ "HTTP/1.1 302 OK\r\n"
+ "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 3),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("redirect.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ stream1.release()->Drain(NULL);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ stream2->ReadResponseHeaders(callback_.callback()));
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, EvictIfDrainingChunkedEncoding) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 2,
+ "HTTP/1.1 302 OK\r\n"
+ "Transfer-Encoding: chunked\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3,
+ "jibberish"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> stream1(NewTestStream("redirect.html"));
+ scoped_ptr<HttpStream> stream2(NewTestStream("ok.html"));
+
+ HttpRequestHeaders headers1;
+ HttpResponseInfo response1;
+ EXPECT_EQ(OK, stream1->SendRequest(headers1,
+ &response1, callback_.callback()));
+ HttpRequestHeaders headers2;
+ HttpResponseInfo response2;
+ EXPECT_EQ(OK, stream2->SendRequest(headers2,
+ &response2, callback_.callback()));
+
+
+ EXPECT_EQ(OK, stream1->ReadResponseHeaders(callback_.callback()));
+ stream1.release()->Drain(NULL);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ stream2->ReadResponseHeaders(callback_.callback()));
+ stream2->Close(false);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, EvictionDueToMissingContentLength) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "GET /evicted.html HTTP/1.1\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 2, "GET /rejected.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 3, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ MockRead(SYNCHRONOUS, OK, 5),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpStream> ok_stream(NewTestStream("ok.html"));
+ scoped_ptr<HttpStream> evicted_stream(NewTestStream("evicted.html"));
+ scoped_ptr<HttpStream> rejected_stream(NewTestStream("rejected.html"));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, ok_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, evicted_stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(OK, rejected_stream->SendRequest(headers,
+ &response, callback_.callback()));
+
+ TestCompletionCallback ok_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ ok_stream->ReadResponseHeaders(ok_callback.callback()));
+
+ TestCompletionCallback evicted_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ evicted_stream->ReadResponseHeaders(evicted_callback.callback()));
+
+ data_->RunFor(1);
+ EXPECT_LE(OK, ok_callback.WaitForResult());
+ data_->StopAfter(10);
+
+ ExpectResponse("ok.html", ok_stream, false);
+ ok_stream->Close(false);
+
+ EXPECT_EQ(ERR_PIPELINE_EVICTION,
+ rejected_stream->ReadResponseHeaders(callback_.callback()));
+ rejected_stream->Close(true);
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, evicted_callback.WaitForResult());
+ evicted_stream->Close(true);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnSocketError) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_FAILED, 1),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::PIPELINE_SOCKET_ERROR))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(ERR_FAILED, stream->ReadResponseHeaders(callback_.callback()));
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnNoInternetConnection) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_INTERNET_DISCONNECTED, 1),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_, OnPipelineFeedback(_, _))
+ .Times(0);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers,
+ &response, callback_.callback()));
+ EXPECT_EQ(ERR_INTERNET_DISCONNECTED,
+ stream->ReadResponseHeaders(callback_.callback()));
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnHttp10) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.0 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Connection: keep-alive\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(pipeline_.get(),
+ HttpPipelinedConnection::OLD_HTTP_VERSION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnMustClose) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 7\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Connection: close\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnNoContentLength) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 2, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, FeedbackOnAuthenticationRequired) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead(SYNCHRONOUS, 2, "WWW-Authenticate: NTLM\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 7\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "ok.html"),
+ };
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_,
+ OnPipelineFeedback(
+ pipeline_.get(),
+ HttpPipelinedConnection::AUTHENTICATION_REQUIRED))
+ .Times(1);
+
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+ TestSyncRequest(stream, "ok.html");
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, OnPipelineHasCapacity) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(0);
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(1);
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ EXPECT_EQ(OK, stream->SendRequest(headers,
+ &response, callback_.callback()));
+
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(0);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ stream->Close(false);
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(1);
+ stream.reset(NULL);
+}
+
+TEST_F(HttpPipelinedConnectionImplTest, OnPipelineHasCapacityWithoutSend) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /ok.html HTTP/1.1\r\n\r\n"),
+ };
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(0);
+ scoped_ptr<HttpStream> stream(NewTestStream("ok.html"));
+
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(1);
+ base::MessageLoop::current()->RunUntilIdle();
+
+ stream->Close(false);
+ EXPECT_CALL(delegate_, OnPipelineHasCapacity(pipeline_.get())).Times(1);
+ stream.reset(NULL);
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host.cc b/chromium/net/http/http_pipelined_host.cc
new file mode 100644
index 00000000000..ca477808e9b
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host.h"
+
+namespace net {
+
+HttpPipelinedHost::Key::Key(const HostPortPair& origin)
+ : origin_(origin) {
+}
+
+bool HttpPipelinedHost::Key::operator<(const Key& rhs) const {
+ return origin_ < rhs.origin_;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host.h b/chromium/net/http/http_pipelined_host.h
new file mode 100644
index 00000000000..b7732e60a8b
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host.h
@@ -0,0 +1,102 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_HOST_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_H_
+
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host_capability.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HostPortPair;
+class HttpPipelinedStream;
+class ProxyInfo;
+struct SSLConfig;
+
+// Manages all of the pipelining state for specific host with active pipelined
+// HTTP requests. Manages connection jobs, constructs pipelined streams, and
+// assigns requests to the least loaded pipelined connection.
+class NET_EXPORT_PRIVATE HttpPipelinedHost {
+ public:
+ class NET_EXPORT_PRIVATE Key {
+ public:
+ Key(const HostPortPair& origin);
+
+ // The host and port associated with this key.
+ const HostPortPair& origin() const { return origin_; }
+
+ bool operator<(const Key& rhs) const;
+
+ private:
+ const HostPortPair origin_;
+ };
+
+ class Delegate {
+ public:
+ // Called when a pipelined host has no outstanding requests on any of its
+ // pipelined connections.
+ virtual void OnHostIdle(HttpPipelinedHost* host) = 0;
+
+ // Called when a pipelined host has newly available pipeline capacity, like
+ // when a request completes.
+ virtual void OnHostHasAdditionalCapacity(HttpPipelinedHost* host) = 0;
+
+ // Called when a host determines if pipelining can be used.
+ virtual void OnHostDeterminedCapability(
+ HttpPipelinedHost* host,
+ HttpPipelinedHostCapability capability) = 0;
+ };
+
+ class Factory {
+ public:
+ virtual ~Factory() {}
+
+ // Returns a new HttpPipelinedHost.
+ virtual HttpPipelinedHost* CreateNewHost(
+ Delegate* delegate, const Key& key,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHostCapability capability,
+ bool force_pipelining) = 0;
+ };
+
+ virtual ~HttpPipelinedHost() {}
+
+ // Constructs a new pipeline on |connection| and returns a new
+ // HttpPipelinedStream that uses it.
+ virtual HttpPipelinedStream* CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) = 0;
+
+ // Tries to find an existing pipeline with capacity for a new request. If
+ // successful, returns a new stream on that pipeline. Otherwise, returns NULL.
+ virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() = 0;
+
+ // Returns true if we have a pipelined connection that can accept new
+ // requests.
+ virtual bool IsExistingPipelineAvailable() const = 0;
+
+ // Returns a Key that uniquely identifies this host.
+ virtual const Key& GetKey() const = 0;
+
+ // Creates a Value summary of this host's pipelines. Caller assumes
+ // ownership of the returned Value.
+ virtual base::Value* PipelineInfoToValue() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_H_
diff --git a/chromium/net/http/http_pipelined_host_capability.h b/chromium/net/http/http_pipelined_host_capability.h
new file mode 100644
index 00000000000..d03a20893ea
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_capability.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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_HTTP_HTTP_PIPELINED_HOST_CAPABILITY_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_CAPABILITY_H_
+
+namespace net {
+
+// These values are serialized in Preferences. Do not change these values and
+// only add new ones at the end.
+enum HttpPipelinedHostCapability {
+ PIPELINE_UNKNOWN = 0,
+ PIPELINE_INCAPABLE = 1,
+ PIPELINE_CAPABLE = 2,
+ PIPELINE_PROBABLY_CAPABLE = 3, // We are using pipelining, but haven't
+ // processed enough requests to record this
+ // host as known to be capable.
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_CAPABILITY_H_
diff --git a/chromium/net/http/http_pipelined_host_forced.cc b/chromium/net/http/http_pipelined_host_forced.cc
new file mode 100644
index 00000000000..8059d848d73
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_forced.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_forced.h"
+
+#include "base/values.h"
+#include "net/http/http_pipelined_connection_impl.h"
+#include "net/http/http_pipelined_stream.h"
+#include "net/socket/buffered_write_stream_socket.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+HttpPipelinedHostForced::HttpPipelinedHostForced(
+ HttpPipelinedHost::Delegate* delegate,
+ const Key& key,
+ HttpPipelinedConnection::Factory* factory)
+ : delegate_(delegate),
+ key_(key),
+ factory_(factory) {
+ if (!factory) {
+ factory_.reset(new HttpPipelinedConnectionImpl::Factory());
+ }
+}
+
+HttpPipelinedHostForced::~HttpPipelinedHostForced() {
+ CHECK(!pipeline_.get());
+}
+
+HttpPipelinedStream* HttpPipelinedHostForced::CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) {
+ CHECK(!pipeline_.get());
+ scoped_ptr<BufferedWriteStreamSocket> buffered_socket(
+ new BufferedWriteStreamSocket(connection->PassSocket()));
+ connection->SetSocket(buffered_socket.PassAs<StreamSocket>());
+ pipeline_.reset(factory_->CreateNewPipeline(
+ connection, this, key_.origin(), used_ssl_config, used_proxy_info,
+ net_log, was_npn_negotiated, protocol_negotiated));
+ return pipeline_->CreateNewStream();
+}
+
+HttpPipelinedStream* HttpPipelinedHostForced::CreateStreamOnExistingPipeline() {
+ if (!pipeline_.get()) {
+ return NULL;
+ }
+ return pipeline_->CreateNewStream();
+}
+
+bool HttpPipelinedHostForced::IsExistingPipelineAvailable() const {
+ return pipeline_.get() != NULL;
+}
+
+const HttpPipelinedHost::Key& HttpPipelinedHostForced::GetKey() const {
+ return key_;
+}
+
+void HttpPipelinedHostForced::OnPipelineEmpty(
+ HttpPipelinedConnection* pipeline) {
+ CHECK_EQ(pipeline_.get(), pipeline);
+ pipeline_.reset();
+ delegate_->OnHostIdle(this);
+ // WARNING: We'll probably be deleted here.
+}
+
+void HttpPipelinedHostForced::OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) {
+ CHECK_EQ(pipeline_.get(), pipeline);
+ delegate_->OnHostHasAdditionalCapacity(this);
+ if (!pipeline->depth()) {
+ OnPipelineEmpty(pipeline);
+ // WARNING: We might be deleted here.
+ }
+}
+
+void HttpPipelinedHostForced::OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) {
+ // We don't care. We always pipeline.
+}
+
+base::Value* HttpPipelinedHostForced::PipelineInfoToValue() const {
+ base::ListValue* list_value = new base::ListValue();
+ if (pipeline_.get()) {
+ base::DictionaryValue* pipeline_dict = new base::DictionaryValue;
+ pipeline_dict->SetString("host", key_.origin().ToString());
+ pipeline_dict->SetBoolean("forced", true);
+ pipeline_dict->SetInteger("depth", pipeline_->depth());
+ pipeline_dict->SetInteger("capacity", 1000);
+ pipeline_dict->SetBoolean("usable", pipeline_->usable());
+ pipeline_dict->SetBoolean("active", pipeline_->active());
+ pipeline_dict->SetInteger("source_id", pipeline_->net_log().source().id);
+ list_value->Append(pipeline_dict);
+ }
+ return list_value;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_forced.h b/chromium/net/http/http_pipelined_host_forced.h
new file mode 100644
index 00000000000..2c3c9159342
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_forced.h
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_host_capability.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HttpPipelinedStream;
+class ProxyInfo;
+struct SSLConfig;
+
+// Manages a single pipelined connection for requests to a host that are forced
+// to use pipelining. Note that this is normally not used. It is intended to
+// test the user's connection for pipelining compatibility.
+class NET_EXPORT_PRIVATE HttpPipelinedHostForced
+ : public HttpPipelinedHost,
+ public HttpPipelinedConnection::Delegate {
+ public:
+ HttpPipelinedHostForced(HttpPipelinedHost::Delegate* delegate,
+ const Key& key,
+ HttpPipelinedConnection::Factory* factory);
+ virtual ~HttpPipelinedHostForced();
+
+ // HttpPipelinedHost interface
+ virtual HttpPipelinedStream* CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) OVERRIDE;
+
+ virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() OVERRIDE;
+
+ virtual bool IsExistingPipelineAvailable() const OVERRIDE;
+
+ virtual const Key& GetKey() const OVERRIDE;
+
+ virtual base::Value* PipelineInfoToValue() const OVERRIDE;
+
+ // HttpPipelinedConnection::Delegate interface
+
+ virtual void OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) OVERRIDE;
+
+ virtual void OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) OVERRIDE;
+
+ private:
+ // Called when a pipeline is empty and there are no pending requests. Closes
+ // the connection.
+ void OnPipelineEmpty(HttpPipelinedConnection* pipeline);
+
+ HttpPipelinedHost::Delegate* delegate_;
+ const Key key_;
+ scoped_ptr<HttpPipelinedConnection> pipeline_;
+ scoped_ptr<HttpPipelinedConnection::Factory> factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostForced);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_FORCED_H_
diff --git a/chromium/net/http/http_pipelined_host_forced_unittest.cc b/chromium/net/http/http_pipelined_host_forced_unittest.cc
new file mode 100644
index 00000000000..37732aa800f
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_forced_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_forced.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/http/http_pipelined_host_test_util.h"
+#include "net/proxy/proxy_info.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/ssl/ssl_config_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::NiceMock;
+using testing::Ref;
+using testing::Return;
+
+namespace net {
+
+namespace {
+
+HttpPipelinedStream* kDummyStream =
+ reinterpret_cast<HttpPipelinedStream*>(24);
+
+class HttpPipelinedHostForcedTest : public testing::Test {
+ public:
+ HttpPipelinedHostForcedTest()
+ : key_(HostPortPair("host", 123)),
+ factory_(new MockPipelineFactory), // Owned by |host_|.
+ host_(new HttpPipelinedHostForced(&delegate_, key_, factory_)) {
+ }
+
+ MockPipeline* AddTestPipeline() {
+ MockPipeline* pipeline = new MockPipeline(0, true, true);
+ EXPECT_CALL(*factory_, CreateNewPipeline(&connection_, host_.get(),
+ MatchesOrigin(key_.origin()),
+ Ref(ssl_config_), Ref(proxy_info_),
+ Ref(net_log_), true,
+ kProtoSPDY2))
+ .Times(1)
+ .WillOnce(Return(pipeline));
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnNewPipeline(
+ &connection_, ssl_config_, proxy_info_, net_log_, true,
+ kProtoSPDY2));
+ return pipeline;
+ }
+
+ ClientSocketHandle connection_;
+ NiceMock<MockHostDelegate> delegate_;
+ HttpPipelinedHost::Key key_;
+ MockPipelineFactory* factory_;
+ scoped_ptr<HttpPipelinedHostForced> host_;
+
+ SSLConfig ssl_config_;
+ ProxyInfo proxy_info_;
+ BoundNetLog net_log_;
+};
+
+TEST_F(HttpPipelinedHostForcedTest, Delegate) {
+ EXPECT_TRUE(key_.origin().Equals(host_->GetKey().origin()));
+}
+
+TEST_F(HttpPipelinedHostForcedTest, SingleUser) {
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+
+ MockPipeline* pipeline = AddTestPipeline();
+ EXPECT_TRUE(host_->IsExistingPipelineAvailable());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ host_->OnPipelineHasCapacity(pipeline);
+}
+
+TEST_F(HttpPipelinedHostForcedTest, ReuseExisting) {
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ MockPipeline* pipeline = AddTestPipeline();
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline());
+
+ pipeline->SetState(1, true, true);
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(0);
+ host_->OnPipelineHasCapacity(pipeline);
+
+ pipeline->SetState(0, true, true);
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ host_->OnPipelineHasCapacity(pipeline);
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_impl.cc b/chromium/net/http/http_pipelined_host_impl.cc
new file mode 100644
index 00000000000..2a41ca41a85
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_impl.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_impl.h"
+
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "net/http/http_pipelined_connection_impl.h"
+#include "net/http/http_pipelined_stream.h"
+
+namespace net {
+
+// TODO(simonjam): Run experiments to see what value minimizes evictions without
+// costing too much performance. Until then, this is just a bad guess.
+static const int kNumKnownSuccessesThreshold = 3;
+
+HttpPipelinedHostImpl::HttpPipelinedHostImpl(
+ HttpPipelinedHost::Delegate* delegate,
+ const HttpPipelinedHost::Key& key,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHostCapability capability)
+ : delegate_(delegate),
+ key_(key),
+ factory_(factory),
+ capability_(capability) {
+ if (!factory) {
+ factory_.reset(new HttpPipelinedConnectionImpl::Factory());
+ }
+}
+
+HttpPipelinedHostImpl::~HttpPipelinedHostImpl() {
+ CHECK(pipelines_.empty());
+}
+
+HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) {
+ if (capability_ == PIPELINE_INCAPABLE) {
+ return NULL;
+ }
+ HttpPipelinedConnection* pipeline = factory_->CreateNewPipeline(
+ connection, this, key_.origin(), used_ssl_config, used_proxy_info,
+ net_log, was_npn_negotiated, protocol_negotiated);
+ PipelineInfo info;
+ pipelines_.insert(std::make_pair(pipeline, info));
+ return pipeline->CreateNewStream();
+}
+
+HttpPipelinedStream* HttpPipelinedHostImpl::CreateStreamOnExistingPipeline() {
+ HttpPipelinedConnection* available_pipeline = NULL;
+ for (PipelineInfoMap::iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ if (CanPipelineAcceptRequests(it->first) &&
+ (!available_pipeline ||
+ it->first->depth() < available_pipeline->depth())) {
+ available_pipeline = it->first;
+ }
+ }
+ if (!available_pipeline) {
+ return NULL;
+ }
+ return available_pipeline->CreateNewStream();
+}
+
+bool HttpPipelinedHostImpl::IsExistingPipelineAvailable() const {
+ for (PipelineInfoMap::const_iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ if (CanPipelineAcceptRequests(it->first)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const HttpPipelinedHost::Key& HttpPipelinedHostImpl::GetKey() const {
+ return key_;
+}
+
+void HttpPipelinedHostImpl::OnPipelineEmpty(HttpPipelinedConnection* pipeline) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ pipelines_.erase(pipeline);
+ delete pipeline;
+ if (pipelines_.empty()) {
+ delegate_->OnHostIdle(this);
+ // WARNING: We'll probably be deleted here.
+ }
+}
+
+void HttpPipelinedHostImpl::OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ if (CanPipelineAcceptRequests(pipeline)) {
+ delegate_->OnHostHasAdditionalCapacity(this);
+ }
+ if (!pipeline->depth()) {
+ OnPipelineEmpty(pipeline);
+ // WARNING: We might be deleted here.
+ }
+}
+
+void HttpPipelinedHostImpl::OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) {
+ CHECK(ContainsKey(pipelines_, pipeline));
+ switch (feedback) {
+ case HttpPipelinedConnection::OK:
+ ++pipelines_[pipeline].num_successes;
+ if (capability_ == PIPELINE_UNKNOWN) {
+ capability_ = PIPELINE_PROBABLY_CAPABLE;
+ NotifyAllPipelinesHaveCapacity();
+ } else if (capability_ == PIPELINE_PROBABLY_CAPABLE &&
+ pipelines_[pipeline].num_successes >=
+ kNumKnownSuccessesThreshold) {
+ capability_ = PIPELINE_CAPABLE;
+ delegate_->OnHostDeterminedCapability(this, PIPELINE_CAPABLE);
+ }
+ break;
+
+ case HttpPipelinedConnection::PIPELINE_SOCKET_ERROR:
+ // Socket errors on the initial request - when no other requests are
+ // pipelined - can't be due to pipelining.
+ if (pipelines_[pipeline].num_successes > 0 || pipeline->depth() > 1) {
+ // TODO(simonjam): This may be needlessly harsh. For example, pogo.com
+ // only returns a socket error once after the root document, but is
+ // otherwise able to pipeline just fine. Consider being more persistent
+ // and only give up on pipelining if we get a couple of failures.
+ capability_ = PIPELINE_INCAPABLE;
+ delegate_->OnHostDeterminedCapability(this, PIPELINE_INCAPABLE);
+ }
+ break;
+
+ case HttpPipelinedConnection::OLD_HTTP_VERSION:
+ case HttpPipelinedConnection::AUTHENTICATION_REQUIRED:
+ capability_ = PIPELINE_INCAPABLE;
+ delegate_->OnHostDeterminedCapability(this, PIPELINE_INCAPABLE);
+ break;
+
+ case HttpPipelinedConnection::MUST_CLOSE_CONNECTION:
+ break;
+ }
+}
+
+int HttpPipelinedHostImpl::GetPipelineCapacity() const {
+ int capacity = 0;
+ switch (capability_) {
+ case PIPELINE_CAPABLE:
+ case PIPELINE_PROBABLY_CAPABLE:
+ capacity = max_pipeline_depth();
+ break;
+
+ case PIPELINE_INCAPABLE:
+ CHECK(false);
+
+ case PIPELINE_UNKNOWN:
+ capacity = 1;
+ break;
+
+ default:
+ CHECK(false) << "Unkown pipeline capability: " << capability_;
+ }
+ return capacity;
+}
+
+bool HttpPipelinedHostImpl::CanPipelineAcceptRequests(
+ HttpPipelinedConnection* pipeline) const {
+ return capability_ != PIPELINE_INCAPABLE &&
+ pipeline->usable() &&
+ pipeline->active() &&
+ pipeline->depth() < GetPipelineCapacity();
+}
+
+void HttpPipelinedHostImpl::NotifyAllPipelinesHaveCapacity() {
+ // Calling OnPipelineHasCapacity() can have side effects that include
+ // deleting and removing entries from |pipelines_|.
+ PipelineInfoMap pipelines_to_notify = pipelines_;
+ for (PipelineInfoMap::iterator it = pipelines_to_notify.begin();
+ it != pipelines_to_notify.end(); ++it) {
+ if (pipelines_.find(it->first) != pipelines_.end()) {
+ OnPipelineHasCapacity(it->first);
+ }
+ }
+}
+
+base::Value* HttpPipelinedHostImpl::PipelineInfoToValue() const {
+ base::ListValue* list_value = new base::ListValue();
+ for (PipelineInfoMap::const_iterator it = pipelines_.begin();
+ it != pipelines_.end(); ++it) {
+ base::DictionaryValue* pipeline_dict = new base::DictionaryValue;
+ pipeline_dict->SetString("host", key_.origin().ToString());
+ pipeline_dict->SetBoolean("forced", false);
+ pipeline_dict->SetInteger("depth", it->first->depth());
+ pipeline_dict->SetInteger("capacity", GetPipelineCapacity());
+ pipeline_dict->SetBoolean("usable", it->first->usable());
+ pipeline_dict->SetBoolean("active", it->first->active());
+ pipeline_dict->SetInteger("source_id", it->first->net_log().source().id);
+ list_value->Append(pipeline_dict);
+ }
+ return list_value;
+}
+
+HttpPipelinedHostImpl::PipelineInfo::PipelineInfo()
+ : num_successes(0) {
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_impl.h b/chromium/net/http/http_pipelined_host_impl.h
new file mode 100644
index 00000000000..e2e53c93c37
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_impl.h
@@ -0,0 +1,117 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_HOST_IMPL_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_IMPL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_host_capability.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HttpPipelinedStream;
+class ProxyInfo;
+struct SSLConfig;
+
+// Manages all of the pipelining state for specific host with active pipelined
+// HTTP requests. Manages connection jobs, constructs pipelined streams, and
+// assigns requests to the least loaded pipelined connection.
+class NET_EXPORT_PRIVATE HttpPipelinedHostImpl
+ : public HttpPipelinedHost,
+ public HttpPipelinedConnection::Delegate {
+ public:
+ HttpPipelinedHostImpl(HttpPipelinedHost::Delegate* delegate,
+ const HttpPipelinedHost::Key& key,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHostCapability capability);
+ virtual ~HttpPipelinedHostImpl();
+
+ // HttpPipelinedHost interface
+ virtual HttpPipelinedStream* CreateStreamOnNewPipeline(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) OVERRIDE;
+
+ virtual HttpPipelinedStream* CreateStreamOnExistingPipeline() OVERRIDE;
+
+ virtual bool IsExistingPipelineAvailable() const OVERRIDE;
+
+ // HttpPipelinedConnection::Delegate interface
+
+ // Called when a pipelined connection completes a request. Adds a pending
+ // request to the pipeline if the pipeline is still usable.
+ virtual void OnPipelineHasCapacity(
+ HttpPipelinedConnection* pipeline) OVERRIDE;
+
+ virtual void OnPipelineFeedback(
+ HttpPipelinedConnection* pipeline,
+ HttpPipelinedConnection::Feedback feedback) OVERRIDE;
+
+ virtual const Key& GetKey() const OVERRIDE;
+
+ // Creates a Value summary of this host's |pipelines_|. Caller assumes
+ // ownership of the returned Value.
+ virtual base::Value* PipelineInfoToValue() const OVERRIDE;
+
+ // Returns the maximum number of in-flight pipelined requests we'll allow on a
+ // single connection.
+ static int max_pipeline_depth() { return 3; }
+
+ private:
+ struct PipelineInfo {
+ PipelineInfo();
+
+ int num_successes;
+ };
+ typedef std::map<HttpPipelinedConnection*, PipelineInfo> PipelineInfoMap;
+
+ // Called when a pipeline is empty and there are no pending requests. Closes
+ // the connection.
+ void OnPipelineEmpty(HttpPipelinedConnection* pipeline);
+
+ // Adds the next pending request to the pipeline if it's still usuable.
+ void AddRequestToPipeline(HttpPipelinedConnection* pipeline);
+
+ // Returns the current pipeline capacity based on |capability_|. This should
+ // not be called if |capability_| is INCAPABLE.
+ int GetPipelineCapacity() const;
+
+ // Returns true if |pipeline| can handle a new request. This is true if the
+ // |pipeline| is active, usable, has capacity, and |capability_| is
+ // sufficient.
+ bool CanPipelineAcceptRequests(HttpPipelinedConnection* pipeline) const;
+
+ // Called when |this| moves from UNKNOWN |capability_| to PROBABLY_CAPABLE.
+ // Causes all pipelines to increase capacity to start pipelining.
+ void NotifyAllPipelinesHaveCapacity();
+
+ HttpPipelinedHost::Delegate* delegate_;
+ const Key key_;
+ PipelineInfoMap pipelines_;
+ scoped_ptr<HttpPipelinedConnection::Factory> factory_;
+ HttpPipelinedHostCapability capability_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostImpl);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_IMPL_H_
diff --git a/chromium/net/http/http_pipelined_host_impl_unittest.cc b/chromium/net/http/http_pipelined_host_impl_unittest.cc
new file mode 100644
index 00000000000..ea49e12c105
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_impl_unittest.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_impl.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host_test_util.h"
+#include "net/proxy/proxy_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::NiceMock;
+using testing::Ref;
+using testing::Return;
+using testing::ReturnNull;
+
+namespace net {
+
+namespace {
+
+ClientSocketHandle* kDummyConnection =
+ reinterpret_cast<ClientSocketHandle*>(84);
+HttpPipelinedStream* kDummyStream =
+ reinterpret_cast<HttpPipelinedStream*>(42);
+
+class HttpPipelinedHostImplTest : public testing::Test {
+ public:
+ HttpPipelinedHostImplTest()
+ : key_(HostPortPair("host", 123)),
+ factory_(new MockPipelineFactory), // Owned by host_.
+ host_(new HttpPipelinedHostImpl(&delegate_, key_, factory_,
+ PIPELINE_CAPABLE)) {
+ }
+
+ void SetCapability(HttpPipelinedHostCapability capability) {
+ factory_ = new MockPipelineFactory;
+ host_.reset(new HttpPipelinedHostImpl(
+ &delegate_, key_, factory_, capability));
+ }
+
+ MockPipeline* AddTestPipeline(int depth, bool usable, bool active) {
+ MockPipeline* pipeline = new MockPipeline(depth, usable, active);
+ EXPECT_CALL(*factory_, CreateNewPipeline(kDummyConnection, host_.get(),
+ MatchesOrigin(key_.origin()),
+ Ref(ssl_config_), Ref(proxy_info_),
+ Ref(net_log_), true,
+ kProtoSPDY2))
+ .Times(1)
+ .WillOnce(Return(pipeline));
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnNewPipeline(
+ kDummyConnection, ssl_config_, proxy_info_, net_log_, true,
+ kProtoSPDY2));
+ return pipeline;
+ }
+
+ void ClearTestPipeline(MockPipeline* pipeline) {
+ pipeline->SetState(0, true, true);
+ host_->OnPipelineHasCapacity(pipeline);
+ }
+
+ NiceMock<MockHostDelegate> delegate_;
+ HttpPipelinedHost::Key key_;
+ MockPipelineFactory* factory_;
+ scoped_ptr<HttpPipelinedHostImpl> host_;
+
+ SSLConfig ssl_config_;
+ ProxyInfo proxy_info_;
+ BoundNetLog net_log_;
+};
+
+TEST_F(HttpPipelinedHostImplTest, Delegate) {
+ EXPECT_TRUE(key_.origin().Equals(host_->GetKey().origin()));
+}
+
+TEST_F(HttpPipelinedHostImplTest, OnUnusablePipelineHasCapacity) {
+ MockPipeline* pipeline = AddTestPipeline(0, false, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ host_->OnPipelineHasCapacity(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OnUsablePipelineHasCapacity) {
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(0);
+
+ host_->OnPipelineHasCapacity(pipeline);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_, OnHostIdle(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresUnusablePipeline) {
+ MockPipeline* pipeline = AddTestPipeline(1, false, true);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresInactivePipeline) {
+ MockPipeline* pipeline = AddTestPipeline(1, true, false);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresFullPipeline) {
+ MockPipeline* pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth(), true, true);
+
+ EXPECT_FALSE(host_->IsExistingPipelineAvailable());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, PicksLeastLoadedPipeline) {
+ MockPipeline* full_pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth(), true, true);
+ MockPipeline* usable_pipeline = AddTestPipeline(
+ HttpPipelinedHostImpl::max_pipeline_depth() - 1, true, true);
+ MockPipeline* empty_pipeline = AddTestPipeline(0, true, true);
+
+ EXPECT_TRUE(host_->IsExistingPipelineAvailable());
+ EXPECT_CALL(*empty_pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(ReturnNull());
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ ClearTestPipeline(full_pipeline);
+ ClearTestPipeline(usable_pipeline);
+ ClearTestPipeline(empty_pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OpensUpOnPipelineSuccess) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(*pipeline, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, OpensAllPipelinesOnPipelineSuccess) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline1 = AddTestPipeline(1, false, true);
+ MockPipeline* pipeline2 = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline1, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(*pipeline2, CreateNewStream())
+ .Times(1)
+ .WillOnce(Return(kDummyStream));
+ EXPECT_EQ(kDummyStream, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(2);
+ ClearTestPipeline(pipeline1);
+ ClearTestPipeline(pipeline2);
+}
+
+TEST_F(HttpPipelinedHostImplTest, ShutsDownOnOldVersion) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(), PIPELINE_INCAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::OLD_HTTP_VERSION);
+
+ ClearTestPipeline(pipeline);
+ EXPECT_EQ(NULL, host_->CreateStreamOnNewPipeline(
+ kDummyConnection, ssl_config_, proxy_info_, net_log_, true,
+ kProtoSPDY2));
+}
+
+TEST_F(HttpPipelinedHostImplTest, ShutsDownOnAuthenticationRequired) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(), PIPELINE_INCAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::AUTHENTICATION_REQUIRED);
+
+ ClearTestPipeline(pipeline);
+ EXPECT_EQ(NULL, host_->CreateStreamOnNewPipeline(
+ kDummyConnection, ssl_config_, proxy_info_, net_log_, true,
+ kProtoSPDY2));
+}
+
+TEST_F(HttpPipelinedHostImplTest, ConnectionCloseHasNoEffect) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(0);
+ EXPECT_CALL(delegate_, OnHostDeterminedCapability(host_.get(), _))
+ .Times(0);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::MUST_CLOSE_CONNECTION);
+ EXPECT_EQ(NULL, host_->CreateStreamOnExistingPipeline());
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, SuccessesLeadToCapable) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(), PIPELINE_CAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ pipeline->SetState(3, true, true);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+ host_->OnPipelineFeedback(pipeline, HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, IgnoresSocketErrorOnFirstRequest) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(1, true, true);
+
+ EXPECT_CALL(delegate_, OnHostDeterminedCapability(host_.get(), _))
+ .Times(0);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::PIPELINE_SOCKET_ERROR);
+
+ EXPECT_CALL(delegate_, OnHostHasAdditionalCapacity(host_.get()))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::OK);
+
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(), PIPELINE_INCAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::PIPELINE_SOCKET_ERROR);
+
+ ClearTestPipeline(pipeline);
+}
+
+TEST_F(HttpPipelinedHostImplTest, HeedsSocketErrorOnFirstRequestWithPipeline) {
+ SetCapability(PIPELINE_UNKNOWN);
+ MockPipeline* pipeline = AddTestPipeline(2, true, true);
+
+ EXPECT_CALL(delegate_,
+ OnHostDeterminedCapability(host_.get(), PIPELINE_INCAPABLE))
+ .Times(1);
+ host_->OnPipelineFeedback(pipeline,
+ HttpPipelinedConnection::PIPELINE_SOCKET_ERROR);
+
+ ClearTestPipeline(pipeline);
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_pool.cc b/chromium/net/http/http_pipelined_host_pool.cc
new file mode 100644
index 00000000000..ee37e74c29d
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_pool.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_pool.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "net/http/http_pipelined_host_capability.h"
+#include "net/http/http_pipelined_host_forced.h"
+#include "net/http/http_pipelined_host_impl.h"
+#include "net/http/http_server_properties.h"
+
+namespace net {
+
+class HttpPipelinedHostImplFactory : public HttpPipelinedHost::Factory {
+ public:
+ virtual HttpPipelinedHost* CreateNewHost(
+ HttpPipelinedHost::Delegate* delegate,
+ const HttpPipelinedHost::Key& key,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHostCapability capability,
+ bool force_pipelining) OVERRIDE {
+ if (force_pipelining) {
+ return new HttpPipelinedHostForced(delegate, key, factory);
+ } else {
+ return new HttpPipelinedHostImpl(delegate, key, factory, capability);
+ }
+ }
+};
+
+HttpPipelinedHostPool::HttpPipelinedHostPool(
+ Delegate* delegate,
+ HttpPipelinedHost::Factory* factory,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool force_pipelining)
+ : delegate_(delegate),
+ factory_(factory),
+ http_server_properties_(http_server_properties),
+ force_pipelining_(force_pipelining) {
+ if (!factory) {
+ factory_.reset(new HttpPipelinedHostImplFactory);
+ }
+}
+
+HttpPipelinedHostPool::~HttpPipelinedHostPool() {
+ CHECK(host_map_.empty());
+}
+
+bool HttpPipelinedHostPool::IsKeyEligibleForPipelining(
+ const HttpPipelinedHost::Key& key) {
+ HttpPipelinedHostCapability capability =
+ http_server_properties_->GetPipelineCapability(key.origin());
+ return capability != PIPELINE_INCAPABLE;
+}
+
+HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnNewPipeline(
+ const HttpPipelinedHost::Key& key,
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated) {
+ HttpPipelinedHost* host = GetPipelinedHost(key, true);
+ if (!host) {
+ return NULL;
+ }
+ return host->CreateStreamOnNewPipeline(connection, used_ssl_config,
+ used_proxy_info, net_log,
+ was_npn_negotiated,
+ protocol_negotiated);
+}
+
+HttpPipelinedStream* HttpPipelinedHostPool::CreateStreamOnExistingPipeline(
+ const HttpPipelinedHost::Key& key) {
+ HttpPipelinedHost* host = GetPipelinedHost(key, false);
+ if (!host) {
+ return NULL;
+ }
+ return host->CreateStreamOnExistingPipeline();
+}
+
+bool HttpPipelinedHostPool::IsExistingPipelineAvailableForKey(
+ const HttpPipelinedHost::Key& key) {
+ HttpPipelinedHost* host = GetPipelinedHost(key, false);
+ if (!host) {
+ return false;
+ }
+ return host->IsExistingPipelineAvailable();
+}
+
+HttpPipelinedHost* HttpPipelinedHostPool::GetPipelinedHost(
+ const HttpPipelinedHost::Key& key, bool create_if_not_found) {
+ HostMap::iterator host_it = host_map_.find(key);
+ if (host_it != host_map_.end()) {
+ CHECK(host_it->second);
+ return host_it->second;
+ } else if (!create_if_not_found) {
+ return NULL;
+ }
+
+ HttpPipelinedHostCapability capability =
+ http_server_properties_->GetPipelineCapability(key.origin());
+ if (capability == PIPELINE_INCAPABLE) {
+ return NULL;
+ }
+
+ HttpPipelinedHost* host = factory_->CreateNewHost(
+ this, key, NULL, capability, force_pipelining_);
+ host_map_[key] = host;
+ return host;
+}
+
+void HttpPipelinedHostPool::OnHostIdle(HttpPipelinedHost* host) {
+ const HttpPipelinedHost::Key& key = host->GetKey();
+ CHECK(ContainsKey(host_map_, key));
+ host_map_.erase(key);
+ delete host;
+}
+
+void HttpPipelinedHostPool::OnHostHasAdditionalCapacity(
+ HttpPipelinedHost* host) {
+ delegate_->OnHttpPipelinedHostHasAdditionalCapacity(host);
+}
+
+void HttpPipelinedHostPool::OnHostDeterminedCapability(
+ HttpPipelinedHost* host,
+ HttpPipelinedHostCapability capability) {
+ http_server_properties_->SetPipelineCapability(host->GetKey().origin(),
+ capability);
+}
+
+base::Value* HttpPipelinedHostPool::PipelineInfoToValue() const {
+ base::ListValue* list = new base::ListValue();
+ for (HostMap::const_iterator it = host_map_.begin();
+ it != host_map_.end(); ++it) {
+ base::Value* value = it->second->PipelineInfoToValue();
+ list->Append(value);
+ }
+ return list;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_pool.h b/chromium/net/http/http_pipelined_host_pool.h
new file mode 100644
index 00000000000..45cb38cedf6
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_pool.h
@@ -0,0 +1,102 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_HOST_POOL_H_
+#define NET_HTTP_HTTP_PIPELINED_HOST_POOL_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_host_capability.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class HostPortPair;
+class HttpPipelinedStream;
+class HttpServerProperties;
+
+// Manages all of the pipelining state for specific host with active pipelined
+// HTTP requests. Manages connection jobs, constructs pipelined streams, and
+// assigns requests to the least loaded pipelined connection.
+class NET_EXPORT_PRIVATE HttpPipelinedHostPool
+ : public HttpPipelinedHost::Delegate {
+ public:
+ class Delegate {
+ public:
+ // Called when a HttpPipelinedHost has new capacity. Attempts to allocate
+ // any pending pipeline-capable requests to pipelines.
+ virtual void OnHttpPipelinedHostHasAdditionalCapacity(
+ HttpPipelinedHost* host) = 0;
+ };
+
+ HttpPipelinedHostPool(
+ Delegate* delegate,
+ HttpPipelinedHost::Factory* factory,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool force_pipelining);
+ virtual ~HttpPipelinedHostPool();
+
+ // Returns true if pipelining might work for |key|. Generally, this returns
+ // true, unless |key| is known to have failed pipelining recently.
+ bool IsKeyEligibleForPipelining(const HttpPipelinedHost::Key& key);
+
+ // Constructs a new pipeline on |connection| and returns a new
+ // HttpPipelinedStream that uses it.
+ HttpPipelinedStream* CreateStreamOnNewPipeline(
+ const HttpPipelinedHost::Key& key,
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated);
+
+ // Tries to find an existing pipeline with capacity for a new request. If
+ // successful, returns a new stream on that pipeline. Otherwise, returns NULL.
+ HttpPipelinedStream* CreateStreamOnExistingPipeline(
+ const HttpPipelinedHost::Key& key);
+
+ // Returns true if a pipelined connection already exists for |key| and
+ // can accept new requests.
+ bool IsExistingPipelineAvailableForKey(const HttpPipelinedHost::Key& key);
+
+ // Callbacks for HttpPipelinedHost.
+ virtual void OnHostIdle(HttpPipelinedHost* host) OVERRIDE;
+
+ virtual void OnHostHasAdditionalCapacity(HttpPipelinedHost* host) OVERRIDE;
+
+ virtual void OnHostDeterminedCapability(
+ HttpPipelinedHost* host,
+ HttpPipelinedHostCapability capability) OVERRIDE;
+
+ // Creates a Value summary of this pool's |host_map_|. Caller assumes
+ // ownership of the returned Value.
+ base::Value* PipelineInfoToValue() const;
+
+ private:
+ typedef std::map<HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap;
+
+ HttpPipelinedHost* GetPipelinedHost(const HttpPipelinedHost::Key& key,
+ bool create_if_not_found);
+
+ Delegate* delegate_;
+ scoped_ptr<HttpPipelinedHost::Factory> factory_;
+ HostMap host_map_;
+ const base::WeakPtr<HttpServerProperties> http_server_properties_;
+ bool force_pipelining_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedHostPool);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_HOST_POOL_H_
diff --git a/chromium/net/http/http_pipelined_host_pool_unittest.cc b/chromium/net/http/http_pipelined_host_pool_unittest.cc
new file mode 100644
index 00000000000..fa8d93facb1
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_pool_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_pool.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_host_capability.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/proxy/proxy_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Ref;
+using testing::Return;
+using testing::ReturnNull;
+
+namespace net {
+
+namespace {
+
+ClientSocketHandle* kDummyConnection =
+ reinterpret_cast<ClientSocketHandle*>(188);
+HttpPipelinedStream* kDummyStream =
+ reinterpret_cast<HttpPipelinedStream*>(99);
+
+class MockPoolDelegate : public HttpPipelinedHostPool::Delegate {
+ public:
+ MOCK_METHOD1(OnHttpPipelinedHostHasAdditionalCapacity,
+ void(HttpPipelinedHost* host));
+};
+
+class MockHostFactory : public HttpPipelinedHost::Factory {
+ public:
+ MOCK_METHOD5(CreateNewHost, HttpPipelinedHost*(
+ HttpPipelinedHost::Delegate* delegate,
+ const HttpPipelinedHost::Key& key,
+ HttpPipelinedConnection::Factory* factory,
+ HttpPipelinedHostCapability capability,
+ bool force_pipelining));
+};
+
+class MockHost : public HttpPipelinedHost {
+ public:
+ MockHost(const Key& key)
+ : key_(key) {
+ }
+
+ MOCK_METHOD6(CreateStreamOnNewPipeline, HttpPipelinedStream*(
+ ClientSocketHandle* connection,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated));
+ MOCK_METHOD0(CreateStreamOnExistingPipeline, HttpPipelinedStream*());
+ MOCK_CONST_METHOD0(IsExistingPipelineAvailable, bool());
+ MOCK_CONST_METHOD0(PipelineInfoToValue, base::Value*());
+
+ virtual const Key& GetKey() const OVERRIDE { return key_; }
+
+ private:
+ Key key_;
+};
+
+class HttpPipelinedHostPoolTest : public testing::Test {
+ public:
+ HttpPipelinedHostPoolTest()
+ : key_(HostPortPair("host", 123)),
+ factory_(new MockHostFactory), // Owned by pool_.
+ host_(new MockHost(key_)), // Owned by pool_.
+ http_server_properties_(new HttpServerPropertiesImpl()),
+ pool_(new HttpPipelinedHostPool(
+ &delegate_, factory_,
+ http_server_properties_->GetWeakPtr(), false)),
+ was_npn_negotiated_(false),
+ protocol_negotiated_(kProtoUnknown) {
+ }
+
+ void CreateDummyStream(const HttpPipelinedHost::Key& key,
+ ClientSocketHandle* connection,
+ HttpPipelinedStream* stream,
+ MockHost* host) {
+ EXPECT_CALL(*host, CreateStreamOnNewPipeline(connection,
+ Ref(ssl_config_),
+ Ref(proxy_info_),
+ Ref(net_log_),
+ was_npn_negotiated_,
+ protocol_negotiated_))
+ .Times(1)
+ .WillOnce(Return(stream));
+ EXPECT_EQ(stream,
+ pool_->CreateStreamOnNewPipeline(key, connection,
+ ssl_config_, proxy_info_,
+ net_log_, was_npn_negotiated_,
+ protocol_negotiated_));
+ }
+
+ MockHost* CreateDummyHost(const HttpPipelinedHost::Key& key) {
+ MockHost* mock_host = new MockHost(key);
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(mock_host));
+ ClientSocketHandle* dummy_connection =
+ reinterpret_cast<ClientSocketHandle*>(base::RandUint64());
+ HttpPipelinedStream* dummy_stream =
+ reinterpret_cast<HttpPipelinedStream*>(base::RandUint64());
+ CreateDummyStream(key, dummy_connection, dummy_stream, mock_host);
+ return mock_host;
+ }
+
+ HttpPipelinedHost::Key key_;
+ MockPoolDelegate delegate_;
+ MockHostFactory* factory_;
+ MockHost* host_;
+ scoped_ptr<HttpServerPropertiesImpl> http_server_properties_;
+ scoped_ptr<HttpPipelinedHostPool> pool_;
+
+ const SSLConfig ssl_config_;
+ const ProxyInfo proxy_info_;
+ const BoundNetLog net_log_;
+ bool was_npn_negotiated_;
+ NextProto protocol_negotiated_;
+};
+
+TEST_F(HttpPipelinedHostPoolTest, DefaultUnknown) {
+ EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_));
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostIdle(host_);
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemembersIncapable) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_INCAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_FALSE(pool_->IsKeyEligibleForPipelining(key_));
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_INCAPABLE, false))
+ .Times(0);
+ EXPECT_EQ(NULL,
+ pool_->CreateStreamOnNewPipeline(key_, kDummyConnection,
+ ssl_config_, proxy_info_, net_log_,
+ was_npn_negotiated_,
+ protocol_negotiated_));
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemembersCapable) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_));
+
+ host_ = new MockHost(key_);
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_CAPABLE, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostIdle(host_);
+}
+
+TEST_F(HttpPipelinedHostPoolTest, IncapableIsSticky) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE);
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_INCAPABLE);
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE);
+ pool_->OnHostIdle(host_);
+ EXPECT_FALSE(pool_->IsKeyEligibleForPipelining(key_));
+}
+
+TEST_F(HttpPipelinedHostPoolTest, RemainsUnknownWithoutFeedback) {
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostIdle(host_);
+ EXPECT_TRUE(pool_->IsKeyEligibleForPipelining(key_));
+
+ host_ = new MockHost(key_);
+ EXPECT_CALL(*factory_, CreateNewHost(pool_.get(), Ref(key_), _,
+ PIPELINE_UNKNOWN, false))
+ .Times(1)
+ .WillOnce(Return(host_));
+
+ CreateDummyStream(key_, kDummyConnection, kDummyStream, host_);
+ pool_->OnHostIdle(host_);
+}
+
+TEST_F(HttpPipelinedHostPoolTest, PopulatesServerProperties) {
+ EXPECT_EQ(PIPELINE_UNKNOWN,
+ http_server_properties_->GetPipelineCapability(
+ host_->GetKey().origin()));
+ pool_->OnHostDeterminedCapability(host_, PIPELINE_CAPABLE);
+ EXPECT_EQ(PIPELINE_CAPABLE,
+ http_server_properties_->GetPipelineCapability(
+ host_->GetKey().origin()));
+ delete host_; // Must manually delete, because it's never added to |pool_|.
+}
+
+TEST_F(HttpPipelinedHostPoolTest, MultipleKeys) {
+ HttpPipelinedHost::Key key1(HostPortPair("host", 123));
+ HttpPipelinedHost::Key key2(HostPortPair("host", 456));
+ HttpPipelinedHost::Key key3(HostPortPair("other", 456));
+ HttpPipelinedHost::Key key4(HostPortPair("other", 789));
+ MockHost* host1 = CreateDummyHost(key1);
+ MockHost* host2 = CreateDummyHost(key2);
+ MockHost* host3 = CreateDummyHost(key3);
+
+ EXPECT_CALL(*host1, IsExistingPipelineAvailable())
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_TRUE(pool_->IsExistingPipelineAvailableForKey(key1));
+
+ EXPECT_CALL(*host2, IsExistingPipelineAvailable())
+ .Times(1)
+ .WillOnce(Return(false));
+ EXPECT_FALSE(pool_->IsExistingPipelineAvailableForKey(key2));
+
+ EXPECT_CALL(*host3, IsExistingPipelineAvailable())
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_TRUE(pool_->IsExistingPipelineAvailableForKey(key3));
+
+ EXPECT_FALSE(pool_->IsExistingPipelineAvailableForKey(key4));
+
+ pool_->OnHostIdle(host1);
+ pool_->OnHostIdle(host2);
+ pool_->OnHostIdle(host3);
+
+ delete host_; // Must manually delete, because it's never added to |pool_|.
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_test_util.cc b/chromium/net/http/http_pipelined_host_test_util.cc
new file mode 100644
index 00000000000..5162e983708
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_test_util.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_host_test_util.h"
+
+#include "net/proxy/proxy_info.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+MockHostDelegate::MockHostDelegate() {
+}
+
+MockHostDelegate::~MockHostDelegate() {
+}
+
+MockPipelineFactory::MockPipelineFactory() {
+}
+
+MockPipelineFactory::~MockPipelineFactory() {
+}
+
+MockPipeline::MockPipeline(int depth, bool usable, bool active)
+ : depth_(depth),
+ usable_(usable),
+ active_(active) {
+}
+
+MockPipeline::~MockPipeline() {
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_host_test_util.h b/chromium/net/http/http_pipelined_host_test_util.h
new file mode 100644
index 00000000000..245ff02c4d5
--- /dev/null
+++ b/chromium/net/http/http_pipelined_host_test_util.h
@@ -0,0 +1,70 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+
+class MockHostDelegate : public HttpPipelinedHost::Delegate {
+ public:
+ MockHostDelegate();
+ virtual ~MockHostDelegate();
+
+ MOCK_METHOD1(OnHostIdle, void(HttpPipelinedHost* host));
+ MOCK_METHOD1(OnHostHasAdditionalCapacity, void(HttpPipelinedHost* host));
+ MOCK_METHOD2(OnHostDeterminedCapability,
+ void(HttpPipelinedHost* host,
+ HttpPipelinedHostCapability capability));
+};
+
+class MockPipelineFactory : public HttpPipelinedConnection::Factory {
+ public:
+ MockPipelineFactory();
+ virtual ~MockPipelineFactory();
+
+ MOCK_METHOD8(CreateNewPipeline, HttpPipelinedConnection*(
+ ClientSocketHandle* connection,
+ HttpPipelinedConnection::Delegate* delegate,
+ const HostPortPair& origin,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ const BoundNetLog& net_log,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated));
+};
+
+class MockPipeline : public HttpPipelinedConnection {
+ public:
+ MockPipeline(int depth, bool usable, bool active);
+ virtual ~MockPipeline();
+
+ void SetState(int depth, bool usable, bool active) {
+ depth_ = depth;
+ usable_ = usable;
+ active_ = active;
+ }
+
+ virtual int depth() const OVERRIDE { return depth_; }
+ virtual bool usable() const OVERRIDE { return usable_; }
+ virtual bool active() const OVERRIDE { return active_; }
+
+ MOCK_METHOD0(CreateNewStream, HttpPipelinedStream*());
+ MOCK_METHOD1(OnStreamDeleted, void(int pipeline_id));
+ MOCK_CONST_METHOD0(used_ssl_config, const SSLConfig&());
+ MOCK_CONST_METHOD0(used_proxy_info, const ProxyInfo&());
+ MOCK_CONST_METHOD0(net_log, const BoundNetLog&());
+ MOCK_CONST_METHOD0(was_npn_negotiated, bool());
+ MOCK_CONST_METHOD0(protocol_negotiated, NextProto());
+
+ private:
+ int depth_;
+ bool usable_;
+ bool active_;
+};
+
+MATCHER_P(MatchesOrigin, expected, "") { return expected.Equals(arg); }
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_network_transaction_unittest.cc b/chromium/net/http/http_pipelined_network_transaction_unittest.cc
new file mode 100644
index 00000000000..80b74fd554b
--- /dev/null
+++ b/chromium/net/http/http_pipelined_network_transaction_unittest.cc
@@ -0,0 +1,1035 @@
+// Copyright (c) 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.
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/request_priority.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_mock.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/socket_test_util.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::StrEq;
+
+namespace net {
+
+namespace {
+
+class SimpleProxyConfigService : public ProxyConfigService {
+ public:
+ virtual void AddObserver(Observer* observer) OVERRIDE {
+ observer_ = observer;
+ }
+
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {
+ if (observer_ == observer) {
+ observer_ = NULL;
+ }
+ }
+
+ virtual ConfigAvailability GetLatestProxyConfig(
+ ProxyConfig* config) OVERRIDE {
+ *config = config_;
+ return CONFIG_VALID;
+ }
+
+ void IncrementConfigId() {
+ config_.set_id(config_.id() + 1);
+ observer_->OnProxyConfigChanged(config_, ProxyConfigService::CONFIG_VALID);
+ }
+
+ private:
+ ProxyConfig config_;
+ Observer* observer_;
+};
+
+class HttpPipelinedNetworkTransactionTest : public testing::Test {
+ public:
+ HttpPipelinedNetworkTransactionTest()
+ : histograms_("a"),
+ pool_(1, 1, &histograms_, &factory_) {
+ }
+
+ void Initialize(bool force_http_pipelining) {
+ // Normally, this code could just go in SetUp(). For a few of these tests,
+ // we change the default number of sockets per group. That needs to be done
+ // before we construct the HttpNetworkSession.
+ proxy_config_service_ = new SimpleProxyConfigService();
+ proxy_service_.reset(new ProxyService(proxy_config_service_, NULL, NULL));
+ ssl_config_ = new SSLConfigServiceDefaults;
+ auth_handler_factory_.reset(new HttpAuthHandlerMock::Factory());
+
+ HttpNetworkSession::Params session_params;
+ session_params.client_socket_factory = &factory_;
+ session_params.proxy_service = proxy_service_.get();
+ session_params.host_resolver = &mock_resolver_;
+ session_params.ssl_config_service = ssl_config_.get();
+ session_params.http_auth_handler_factory = auth_handler_factory_.get();
+ session_params.http_server_properties =
+ http_server_properties_.GetWeakPtr();
+ session_params.force_http_pipelining = force_http_pipelining;
+ session_params.http_pipelining_enabled = true;
+ session_ = new HttpNetworkSession(session_params);
+ }
+
+ void AddExpectedConnection(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count) {
+ DeterministicSocketData* data = new DeterministicSocketData(
+ reads, reads_count, writes, writes_count);
+ data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ if (reads_count || writes_count) {
+ data->StopAfter(reads_count + writes_count);
+ }
+ factory_.AddSocketDataProvider(data);
+ data_vector_.push_back(data);
+ }
+
+ enum RequestInfoOptions {
+ REQUEST_DEFAULT,
+ REQUEST_MAIN_RESOURCE,
+ };
+
+ HttpRequestInfo* GetRequestInfo(
+ const char* filename, RequestInfoOptions options = REQUEST_DEFAULT) {
+ std::string url = base::StringPrintf("http://localhost/%s", filename);
+ HttpRequestInfo* request_info = new HttpRequestInfo;
+ request_info->url = GURL(url);
+ request_info->method = "GET";
+ if (options == REQUEST_MAIN_RESOURCE) {
+ request_info->load_flags = LOAD_MAIN_FRAME;
+ }
+ request_info_vector_.push_back(request_info);
+ return request_info;
+ }
+
+ void ExpectResponse(const std::string& expected,
+ HttpNetworkTransaction& transaction,
+ IoMode io_mode) {
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(expected.size()));
+ if (io_mode == ASYNC) {
+ EXPECT_EQ(ERR_IO_PENDING, transaction.Read(buffer.get(), expected.size(),
+ callback_.callback()));
+ data_vector_[0]->RunFor(1);
+ EXPECT_EQ(static_cast<int>(expected.length()), callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(static_cast<int>(expected.size()),
+ transaction.Read(buffer.get(), expected.size(),
+ callback_.callback()));
+ }
+ std::string actual(buffer->data(), expected.size());
+ EXPECT_THAT(actual, StrEq(expected));
+ EXPECT_EQ(OK, transaction.Read(buffer.get(), expected.size(),
+ callback_.callback()));
+ }
+
+ void CompleteTwoRequests(int data_index, int stop_at_step) {
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ TestCompletionCallback one_read_callback;
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(8));
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Read(buffer.get(), 8,
+ one_read_callback.callback()));
+
+ data_vector_[data_index]->SetStop(stop_at_step);
+ data_vector_[data_index]->Run();
+ EXPECT_EQ(8, one_read_callback.WaitForResult());
+ data_vector_[data_index]->SetStop(10);
+ std::string actual(buffer->data(), 8);
+ EXPECT_THAT(actual, StrEq("one.html"));
+ EXPECT_EQ(OK, one_transaction->Read(buffer.get(), 8,
+ one_read_callback.callback()));
+
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+ }
+
+ void CompleteFourRequests(RequestInfoOptions options) {
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html", options),
+ one_callback.callback(), BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html", options),
+ two_callback.callback(), BoundNetLog()));
+
+ HttpNetworkTransaction three_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback three_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ three_transaction.Start(GetRequestInfo("three.html", options),
+ three_callback.callback(),
+ BoundNetLog()));
+
+ HttpNetworkTransaction four_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback four_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ four_transaction.Start(GetRequestInfo("four.html", options),
+ four_callback.callback(), BoundNetLog()));
+
+ ExpectResponse("one.html", *one_transaction.get(), SYNCHRONOUS);
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+ EXPECT_EQ(OK, three_callback.WaitForResult());
+ ExpectResponse("three.html", three_transaction, SYNCHRONOUS);
+
+ one_transaction.reset();
+ EXPECT_EQ(OK, four_callback.WaitForResult());
+ ExpectResponse("four.html", four_transaction, SYNCHRONOUS);
+ }
+
+ DeterministicMockClientSocketFactory factory_;
+ ClientSocketPoolHistograms histograms_;
+ MockTransportClientSocketPool pool_;
+ ScopedVector<DeterministicSocketData> data_vector_;
+ TestCompletionCallback callback_;
+ ScopedVector<HttpRequestInfo> request_info_vector_;
+
+ SimpleProxyConfigService* proxy_config_service_;
+ scoped_ptr<ProxyService> proxy_service_;
+ MockHostResolver mock_resolver_;
+ scoped_refptr<SSLConfigService> ssl_config_;
+ scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory_;
+ HttpServerPropertiesImpl http_server_properties_;
+ scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(HttpPipelinedNetworkTransactionTest, OneRequest) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /test.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 9\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "test.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ transaction.Start(GetRequestInfo("test.html"), callback_.callback(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ ExpectResponse("test.html", transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ReusePipeline) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 4, "one.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ CompleteTwoRequests(0, 5);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) {
+ int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 7, "GET /three.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "one.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 8, "two.html"),
+ MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 11, "three.html"),
+ MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 15, "four.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ CompleteFourRequests(REQUEST_DEFAULT);
+
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, WontPipelineMainResource) {
+ int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 8, "GET /three.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "one.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "two.html"),
+ MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 11, "three.html"),
+ MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 15, "four.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ CompleteFourRequests(REQUEST_MAIN_RESOURCE);
+
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(ASYNC, 2, "one.html"),
+ MockRead(SYNCHRONOUS, OK, 3),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "two.html"),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ CompleteTwoRequests(0, 3);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 4, "one.html"),
+ MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 5),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "two.html"),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ CompleteTwoRequests(0, 5);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ErrorEvictsToNewPipeline) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED, 2),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "two.html"),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction.Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(1));
+ EXPECT_EQ(ERR_FAILED,
+ one_transaction.Read(buffer.get(), 1, callback_.callback()));
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, SendErrorEvictsToNewPipeline) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, ERR_FAILED, 0),
+ };
+ AddExpectedConnection(NULL, 0, writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "two.html"),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction.Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ data_vector_[0]->RunFor(1);
+ EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult());
+
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, RedirectDrained) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 302 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 4, "redirect"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("redirect.html"),
+ one_callback.callback(), BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ one_transaction.reset();
+ data_vector_[0]->RunFor(2);
+ data_vector_[0]->SetStop(10);
+
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, BasicHttpAuthentication) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 5, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: auth_token\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 401 Authentication Required\r\n"),
+ MockRead(SYNCHRONOUS, 2,
+ "WWW-Authenticate: Basic realm=\"Secure Area\"\r\n"),
+ MockRead(SYNCHRONOUS, 3, "Content-Length: 20\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "needs authentication"),
+ MockRead(SYNCHRONOUS, 6, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 7, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 8, "one.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ HttpAuthHandlerMock* mock_auth = new HttpAuthHandlerMock;
+ std::string challenge_text = "Basic";
+ HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(),
+ challenge_text.end());
+ GURL origin("localhost");
+ EXPECT_TRUE(mock_auth->InitFromChallenge(&challenge,
+ HttpAuth::AUTH_SERVER,
+ origin,
+ BoundNetLog()));
+ auth_handler_factory_->AddMockHandler(mock_auth, HttpAuth::AUTH_SERVER);
+
+ HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ transaction.Start(GetRequestInfo("one.html"),
+ callback_.callback(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback_.WaitForResult());
+
+ AuthCredentials credentials(ASCIIToUTF16("user"), ASCIIToUTF16("pass"));
+ EXPECT_EQ(OK, transaction.RestartWithAuth(credentials, callback_.callback()));
+
+ ExpectResponse("one.html", transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, OldVersionDisablesPipelining) {
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /pipelined.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.0 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 14\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "pipelined.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ MockWrite writes2[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads2[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 3, "one.html"),
+ MockRead(SYNCHRONOUS, OK, 4),
+ };
+ AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ MockWrite writes3[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads3[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "two.html"),
+ MockRead(SYNCHRONOUS, OK, 4),
+ };
+ AddExpectedConnection(reads3, arraysize(reads3), writes3, arraysize(writes3));
+
+ HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction.Start(GetRequestInfo("pipelined.html"),
+ one_callback.callback(), BoundNetLog()));
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+ ExpectResponse("pipelined.html", one_transaction, SYNCHRONOUS);
+
+ CompleteTwoRequests(1, 4);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, PipelinesImmediatelyIfKnownGood) {
+ // The first request gets us an HTTP/1.1. The next 3 test pipelining. When the
+ // 3rd request completes, we know pipelining is safe. After the first 4
+ // complete, the 5th and 6th should then be immediately sent pipelined on a
+ // new HttpPipelinedConnection.
+ int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 7, "GET /three.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 16, "GET /second-pipeline-one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(SYNCHRONOUS, 17, "GET /second-pipeline-two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 3, "one.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 8, "two.html"),
+ MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 11, "three.html"),
+ MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 15, "four.html"),
+ MockRead(ASYNC, 18, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 19, "Content-Length: 24\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 20, "second-pipeline-one.html"),
+ MockRead(SYNCHRONOUS, 21, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 22, "Content-Length: 24\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 23, "second-pipeline-two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ CompleteFourRequests(REQUEST_DEFAULT);
+
+ HttpNetworkTransaction second_one_transaction(
+ DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback second_one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ second_one_transaction.Start(
+ GetRequestInfo("second-pipeline-one.html"),
+ second_one_callback.callback(), BoundNetLog()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ HttpNetworkTransaction second_two_transaction(
+ DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback second_two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ second_two_transaction.Start(
+ GetRequestInfo("second-pipeline-two.html"),
+ second_two_callback.callback(), BoundNetLog()));
+
+ data_vector_[0]->RunFor(3);
+ EXPECT_EQ(OK, second_one_callback.WaitForResult());
+ data_vector_[0]->StopAfter(100);
+ ExpectResponse("second-pipeline-one.html", second_one_transaction,
+ SYNCHRONOUS);
+ EXPECT_EQ(OK, second_two_callback.WaitForResult());
+ ExpectResponse("second-pipeline-two.html", second_two_transaction,
+ SYNCHRONOUS);
+
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets);
+}
+
+class DataRunnerObserver : public base::MessageLoop::TaskObserver {
+ public:
+ DataRunnerObserver(DeterministicSocketData* data, int run_before_task)
+ : data_(data),
+ run_before_task_(run_before_task),
+ current_task_(0) { }
+
+ virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE {
+ ++current_task_;
+ if (current_task_ == run_before_task_) {
+ data_->Run();
+ base::MessageLoop::current()->RemoveTaskObserver(this);
+ }
+ }
+
+ virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {}
+
+ private:
+ DeterministicSocketData* data_;
+ int run_before_task_;
+ int current_task_;
+};
+
+TEST_F(HttpPipelinedNetworkTransactionTest, OpenPipelinesWhileBinding) {
+ // There was a racy crash in the pipelining code. This test recreates that
+ // race. The steps are:
+ // 1. The first request starts a pipeline and requests headers.
+ // 2. HttpStreamFactoryImpl::Job tries to bind a pending request to a new
+ // pipeline and queues a task to do so.
+ // 3. Before that task runs, the first request receives its headers and
+ // determines this host is probably capable of pipelining.
+ // 4. All of the hosts' pipelines are notified they have capacity in a loop.
+ // 5. On the first iteration, the first pipeline is opened up to accept new
+ // requests and steals the request from step #2.
+ // 6. The pipeline from #2 is deleted because it has no streams.
+ // 7. On the second iteration, the host tries to notify the pipeline from step
+ // #2 that it has capacity. This is a use-after-free.
+ Initialize(false);
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 3, "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 4, "one.html"),
+ MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 7, "two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ AddExpectedConnection(NULL, 0, NULL, 0);
+
+ HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction.Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ data_vector_[0]->SetStop(2);
+ data_vector_[0]->Run();
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+ // Posted tasks should be:
+ // 1. MockHostResolverBase::ResolveNow
+ // 2. HttpStreamFactoryImpl::Job::OnStreamReadyCallback for job 1
+ // 3. HttpStreamFactoryImpl::Job::OnStreamReadyCallback for job 2
+ //
+ // We need to make sure that the response that triggers OnPipelineFeedback(OK)
+ // is called in between when task #3 is scheduled and when it runs. The
+ // DataRunnerObserver does that.
+ DataRunnerObserver observer(data_vector_[0], 3);
+ base::MessageLoop::current()->AddTaskObserver(&observer);
+ data_vector_[0]->SetStop(4);
+ base::MessageLoop::current()->RunUntilIdle();
+ data_vector_[0]->SetStop(10);
+
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+ ExpectResponse("one.html", one_transaction, SYNCHRONOUS);
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, SYNCHRONOUS);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ProxyChangesWhileConnecting) {
+ Initialize(false);
+
+ DeterministicSocketData data(NULL, 0, NULL, 0);
+ data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
+ factory_.AddSocketDataProvider(&data);
+
+ DeterministicSocketData data2(NULL, 0, NULL, 0);
+ data2.set_connect_data(MockConnect(ASYNC, ERR_FAILED));
+ factory_.AddSocketDataProvider(&data2);
+
+ HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get());
+ EXPECT_EQ(ERR_IO_PENDING,
+ transaction.Start(GetRequestInfo("test.html"), callback_.callback(),
+ BoundNetLog()));
+
+ proxy_config_service_->IncrementConfigId();
+
+ EXPECT_EQ(ERR_FAILED, callback_.WaitForResult());
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineSharesConnection) {
+ Initialize(true);
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 3, "one.html"),
+ MockRead(ASYNC, 4, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 5, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 6, "two.html"),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ data_vector_[0]->RunFor(3); // Send + 2 lines of headers.
+ EXPECT_EQ(OK, one_callback.WaitForResult());
+ ExpectResponse("one.html", *one_transaction.get(), ASYNC);
+ one_transaction.reset();
+
+ data_vector_[0]->RunFor(2); // 2 lines of headers.
+ EXPECT_EQ(OK, two_callback.WaitForResult());
+ ExpectResponse("two.html", two_transaction, ASYNC);
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest,
+ ForcedPipelineConnectionErrorFailsBoth) {
+ Initialize(true);
+
+ DeterministicSocketData data(NULL, 0, NULL, 0);
+ data.set_connect_data(MockConnect(ASYNC, ERR_FAILED));
+ factory_.AddSocketDataProvider(&data);
+
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ data.Run();
+ EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult());
+ EXPECT_EQ(ERR_FAILED, two_callback.WaitForResult());
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineEvictionIsFatal) {
+ Initialize(true);
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_FAILED, 1),
+ };
+ AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes));
+
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get());
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction.Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ data_vector_[0]->RunFor(2);
+ EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult());
+ one_transaction.reset();
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, two_callback.WaitForResult());
+}
+
+TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineOrder) {
+ Initialize(true);
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ "GET /two.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ "GET /three.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ "GET /four.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Connection: keep-alive\r\n\r\n"
+ ),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_FAILED, 1),
+ };
+ DeterministicSocketData data(
+ reads, arraysize(reads), writes, arraysize(writes));
+ data.set_connect_data(MockConnect(ASYNC, OK));
+ factory_.AddSocketDataProvider(&data);
+
+ scoped_ptr<HttpNetworkTransaction> one_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback one_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ one_transaction->Start(GetRequestInfo("one.html"),
+ one_callback.callback(), BoundNetLog()));
+
+ scoped_ptr<HttpNetworkTransaction> two_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback two_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ two_transaction->Start(GetRequestInfo("two.html"),
+ two_callback.callback(), BoundNetLog()));
+
+ scoped_ptr<HttpNetworkTransaction> three_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback three_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ three_transaction->Start(GetRequestInfo("three.html"),
+ three_callback.callback(), BoundNetLog()));
+
+ scoped_ptr<HttpNetworkTransaction> four_transaction(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback four_callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ four_transaction->Start(GetRequestInfo("four.html"),
+ four_callback.callback(), BoundNetLog()));
+
+ data.RunFor(3);
+ EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult());
+ one_transaction.reset();
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, two_callback.WaitForResult());
+ two_transaction.reset();
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, three_callback.WaitForResult());
+ three_transaction.reset();
+ EXPECT_EQ(ERR_PIPELINE_EVICTION, four_callback.WaitForResult());
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_stream.cc b/chromium/net/http/http_pipelined_stream.cc
new file mode 100644
index 00000000000..df5743556d1
--- /dev/null
+++ b/chromium/net/http/http_pipelined_stream.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 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.
+
+#include "net/http/http_pipelined_stream.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_pipelined_connection_impl.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+HttpPipelinedStream::HttpPipelinedStream(HttpPipelinedConnectionImpl* pipeline,
+ int pipeline_id)
+ : pipeline_(pipeline),
+ pipeline_id_(pipeline_id),
+ request_info_(NULL) {
+}
+
+HttpPipelinedStream::~HttpPipelinedStream() {
+ pipeline_->OnStreamDeleted(pipeline_id_);
+}
+
+int HttpPipelinedStream::InitializeStream(
+ const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ request_info_ = request_info;
+ pipeline_->InitializeParser(pipeline_id_, request_info, net_log);
+ return OK;
+}
+
+
+int HttpPipelinedStream::SendRequest(const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ CHECK(pipeline_id_);
+ CHECK(request_info_);
+ // TODO(simonjam): Proxy support will be needed here.
+ const std::string path = HttpUtil::PathForRequest(request_info_->url);
+ std::string request_line_ = base::StringPrintf("%s %s HTTP/1.1\r\n",
+ request_info_->method.c_str(),
+ path.c_str());
+ return pipeline_->SendRequest(pipeline_id_, request_line_, headers, response,
+ callback);
+}
+
+UploadProgress HttpPipelinedStream::GetUploadProgress() const {
+ return pipeline_->GetUploadProgress(pipeline_id_);
+}
+
+int HttpPipelinedStream::ReadResponseHeaders(
+ const CompletionCallback& callback) {
+ return pipeline_->ReadResponseHeaders(pipeline_id_, callback);
+}
+
+const HttpResponseInfo* HttpPipelinedStream::GetResponseInfo() const {
+ return pipeline_->GetResponseInfo(pipeline_id_);
+}
+
+int HttpPipelinedStream::ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return pipeline_->ReadResponseBody(pipeline_id_, buf, buf_len, callback);
+}
+
+void HttpPipelinedStream::Close(bool not_reusable) {
+ pipeline_->Close(pipeline_id_, not_reusable);
+}
+
+HttpStream* HttpPipelinedStream::RenewStreamForAuth() {
+ if (pipeline_->usable()) {
+ return pipeline_->CreateNewStream();
+ }
+ return NULL;
+}
+
+bool HttpPipelinedStream::IsResponseBodyComplete() const {
+ return pipeline_->IsResponseBodyComplete(pipeline_id_);
+}
+
+bool HttpPipelinedStream::CanFindEndOfResponse() const {
+ return pipeline_->CanFindEndOfResponse(pipeline_id_);
+}
+
+bool HttpPipelinedStream::IsConnectionReused() const {
+ return pipeline_->IsConnectionReused(pipeline_id_);
+}
+
+void HttpPipelinedStream::SetConnectionReused() {
+ pipeline_->SetConnectionReused(pipeline_id_);
+}
+
+bool HttpPipelinedStream::IsConnectionReusable() const {
+ return pipeline_->usable();
+}
+
+bool HttpPipelinedStream::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ return pipeline_->GetLoadTimingInfo(pipeline_id_, load_timing_info);
+}
+
+void HttpPipelinedStream::GetSSLInfo(SSLInfo* ssl_info) {
+ pipeline_->GetSSLInfo(pipeline_id_, ssl_info);
+}
+
+void HttpPipelinedStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ pipeline_->GetSSLCertRequestInfo(pipeline_id_, cert_request_info);
+}
+
+bool HttpPipelinedStream::IsSpdyHttpStream() const {
+ return false;
+}
+
+void HttpPipelinedStream::Drain(HttpNetworkSession* session) {
+ pipeline_->Drain(this, session);
+}
+
+void HttpPipelinedStream::SetPriority(RequestPriority priority) {
+ // TODO(akalin): Plumb this through to |pipeline_| and its
+ // underlying ClientSocketHandle.
+}
+
+const SSLConfig& HttpPipelinedStream::used_ssl_config() const {
+ return pipeline_->used_ssl_config();
+}
+
+const ProxyInfo& HttpPipelinedStream::used_proxy_info() const {
+ return pipeline_->used_proxy_info();
+}
+
+const BoundNetLog& HttpPipelinedStream::net_log() const {
+ return pipeline_->net_log();
+}
+
+bool HttpPipelinedStream::was_npn_negotiated() const {
+ return pipeline_->was_npn_negotiated();
+}
+
+NextProto HttpPipelinedStream::protocol_negotiated() const {
+ return pipeline_->protocol_negotiated();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_pipelined_stream.h b/chromium/net/http/http_pipelined_stream.h
new file mode 100644
index 00000000000..d3a7991e5ca
--- /dev/null
+++ b/chromium/net/http/http_pipelined_stream.h
@@ -0,0 +1,113 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PIPELINED_STREAM_H_
+#define NET_HTTP_HTTP_PIPELINED_STREAM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_log.h"
+#include "net/http/http_stream.h"
+#include "net/socket/ssl_client_socket.h"
+
+namespace net {
+
+class BoundNetLog;
+class HttpPipelinedConnectionImpl;
+class HttpResponseInfo;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+class IOBuffer;
+class ProxyInfo;
+struct SSLConfig;
+
+// HttpPipelinedStream is the pipelined implementation of HttpStream. It has
+// very little code in it. Instead, it serves as the client's interface to the
+// pipelined connection, where all the work happens.
+//
+// In the case of pipelining failures, these functions may return
+// ERR_PIPELINE_EVICTION. In that case, the client should retry the HTTP
+// request without pipelining.
+class HttpPipelinedStream : public HttpStream {
+ public:
+ HttpPipelinedStream(HttpPipelinedConnectionImpl* pipeline,
+ int pipeline_id);
+ virtual ~HttpPipelinedStream();
+
+ // HttpStream methods:
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual int SendRequest(const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+
+ virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual void Close(bool not_reusable) OVERRIDE;
+
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+
+ virtual bool IsConnectionReused() const OVERRIDE;
+
+ virtual void SetConnectionReused() OVERRIDE;
+
+ virtual bool IsConnectionReusable() const OVERRIDE;
+
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ // The SSLConfig used to establish this stream's pipeline.
+ const SSLConfig& used_ssl_config() const;
+
+ // The ProxyInfo used to establish this this stream's pipeline.
+ const ProxyInfo& used_proxy_info() const;
+
+ // The BoundNetLog of this stream's pipelined connection.
+ const BoundNetLog& net_log() const;
+
+ // True if this stream's pipeline was NPN negotiated.
+ bool was_npn_negotiated() const;
+
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated() const;
+
+ private:
+ HttpPipelinedConnectionImpl* pipeline_;
+
+ int pipeline_id_;
+
+ const HttpRequestInfo* request_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpPipelinedStream);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PIPELINED_STREAM_H_
diff --git a/chromium/net/http/http_proxy_client_socket.cc b/chromium/net/http/http_proxy_client_socket.cc
new file mode 100644
index 00000000000..7037616ae5a
--- /dev/null
+++ b/chromium/net/http/http_proxy_client_socket.cc
@@ -0,0 +1,538 @@
+// Copyright (c) 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.
+
+#include "net/http/http_proxy_client_socket.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_stream_parser.h"
+#include "net/http/proxy_connect_redirect_http_stream.h"
+#include "net/socket/client_socket_handle.h"
+#include "url/gurl.h"
+
+namespace net {
+
+HttpProxyClientSocket::HttpProxyClientSocket(
+ ClientSocketHandle* transport_socket,
+ const GURL& request_url,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const HostPortPair& proxy_server,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ bool tunnel,
+ bool using_spdy,
+ NextProto protocol_negotiated,
+ bool is_https_proxy)
+ : io_callback_(base::Bind(&HttpProxyClientSocket::OnIOComplete,
+ base::Unretained(this))),
+ next_state_(STATE_NONE),
+ transport_(transport_socket),
+ endpoint_(endpoint),
+ auth_(tunnel ?
+ new HttpAuthController(HttpAuth::AUTH_PROXY,
+ GURL((is_https_proxy ? "https://" : "http://")
+ + proxy_server.ToString()),
+ http_auth_cache,
+ http_auth_handler_factory)
+ : NULL),
+ tunnel_(tunnel),
+ using_spdy_(using_spdy),
+ protocol_negotiated_(protocol_negotiated),
+ is_https_proxy_(is_https_proxy),
+ redirect_has_load_timing_info_(false),
+ net_log_(transport_socket->socket()->NetLog()) {
+ // Synthesize the bits of a request that we actually use.
+ request_.url = request_url;
+ request_.method = "GET";
+ if (!user_agent.empty())
+ request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ user_agent);
+}
+
+HttpProxyClientSocket::~HttpProxyClientSocket() {
+ Disconnect();
+}
+
+int HttpProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ int rv = PrepareForAuthRestart();
+ if (rv != OK)
+ return rv;
+
+ rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ if (!callback.is_null())
+ user_callback_ = callback;
+ }
+
+ return rv;
+}
+
+const scoped_refptr<HttpAuthController>&
+HttpProxyClientSocket::GetAuthController() const {
+ return auth_;
+}
+
+bool HttpProxyClientSocket::IsUsingSpdy() const {
+ return using_spdy_;
+}
+
+NextProto HttpProxyClientSocket::GetProtocolNegotiated() const {
+ return protocol_negotiated_;
+}
+
+const HttpResponseInfo* HttpProxyClientSocket::GetConnectResponseInfo() const {
+ return response_.headers.get() ? &response_ : NULL;
+}
+
+HttpStream* HttpProxyClientSocket::CreateConnectResponseStream() {
+ return new ProxyConnectRedirectHttpStream(
+ redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL);
+}
+
+
+int HttpProxyClientSocket::Connect(const CompletionCallback& callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->socket());
+ DCHECK(user_callback_.is_null());
+
+ // TODO(rch): figure out the right way to set up a tunnel with SPDY.
+ // This approach sends the complete HTTPS request to the proxy
+ // which allows the proxy to see "private" data. Instead, we should
+ // create an SSL tunnel to the origin server using the CONNECT method
+ // inside a single SPDY stream.
+ if (using_spdy_ || !tunnel_)
+ next_state_ = STATE_DONE;
+ if (next_state_ == STATE_DONE)
+ return OK;
+
+ DCHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ user_callback_ = callback;
+ return rv;
+}
+
+void HttpProxyClientSocket::Disconnect() {
+ if (transport_.get())
+ transport_->socket()->Disconnect();
+
+ // Reset other states to make sure they aren't mistakenly used later.
+ // These are the states initialized by Connect().
+ next_state_ = STATE_NONE;
+ user_callback_.Reset();
+}
+
+bool HttpProxyClientSocket::IsConnected() const {
+ return next_state_ == STATE_DONE && transport_->socket()->IsConnected();
+}
+
+bool HttpProxyClientSocket::IsConnectedAndIdle() const {
+ return next_state_ == STATE_DONE &&
+ transport_->socket()->IsConnectedAndIdle();
+}
+
+const BoundNetLog& HttpProxyClientSocket::NetLog() const {
+ return net_log_;
+}
+
+void HttpProxyClientSocket::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void HttpProxyClientSocket::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool HttpProxyClientSocket::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool HttpProxyClientSocket::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool HttpProxyClientSocket::WasNpnNegotiated() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasNpnNegotiated();
+ }
+ NOTREACHED();
+ return false;
+}
+
+NextProto HttpProxyClientSocket::GetNegotiatedProtocol() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetNegotiatedProtocol();
+ }
+ NOTREACHED();
+ return kProtoUnknown;
+}
+
+bool HttpProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetSSLInfo(ssl_info);
+ }
+ NOTREACHED();
+ return false;
+}
+
+int HttpProxyClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(user_callback_.is_null());
+ if (next_state_ != STATE_DONE) {
+ // We're trying to read the body of the response but we're still trying
+ // to establish an SSL tunnel through the proxy. We can't read these
+ // bytes when establishing a tunnel because they might be controlled by
+ // an active network attacker. We don't worry about this for HTTP
+ // because an active network attacker can already control HTTP sessions.
+ // We reach this case when the user cancels a 407 proxy auth prompt.
+ // See http://crbug.com/8473.
+ DCHECK_EQ(407, response_.headers->response_code());
+ LogBlockedTunnelResponse();
+
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+int HttpProxyClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_DONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+bool HttpProxyClientSocket::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool HttpProxyClientSocket::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+int HttpProxyClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+int HttpProxyClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetLocalAddress(address);
+}
+
+int HttpProxyClientSocket::PrepareForAuthRestart() {
+ if (!response_.headers.get())
+ return ERR_CONNECTION_RESET;
+
+ bool keep_alive = false;
+ if (response_.headers->IsKeepAlive() &&
+ http_stream_parser_->CanFindEndOfResponse()) {
+ if (!http_stream_parser_->IsResponseBodyComplete()) {
+ next_state_ = STATE_DRAIN_BODY;
+ drain_buf_ = new IOBuffer(kDrainBodyBufferSize);
+ return OK;
+ }
+ keep_alive = true;
+ }
+
+ // We don't need to drain the response body, so we act as if we had drained
+ // the response body.
+ return DidDrainBodyForAuthRestart(keep_alive);
+}
+
+int HttpProxyClientSocket::DidDrainBodyForAuthRestart(bool keep_alive) {
+ if (keep_alive && transport_->socket()->IsConnectedAndIdle()) {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ transport_->set_is_reused(true);
+ } else {
+ // This assumes that the underlying transport socket is a TCP socket,
+ // since only TCP sockets are restartable.
+ next_state_ = STATE_TCP_RESTART;
+ transport_->socket()->Disconnect();
+ }
+
+ // Reset the other member variables.
+ drain_buf_ = NULL;
+ parser_buf_ = NULL;
+ http_stream_parser_.reset();
+ request_line_.clear();
+ request_headers_.Clear();
+ response_ = HttpResponseInfo();
+ return OK;
+}
+
+void HttpProxyClientSocket::LogBlockedTunnelResponse() const {
+ ProxyClientSocket::LogBlockedTunnelResponse(
+ response_.headers->response_code(),
+ request_.url,
+ is_https_proxy_);
+}
+
+void HttpProxyClientSocket::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(!user_callback_.is_null());
+
+ // Since Run() may result in Read being called,
+ // clear user_callback_ up front.
+ CompletionCallback c = user_callback_;
+ user_callback_.Reset();
+ c.Run(result);
+}
+
+void HttpProxyClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ DCHECK_NE(STATE_DONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+}
+
+int HttpProxyClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ DCHECK_NE(next_state_, STATE_DONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
+ break;
+ case STATE_READ_HEADERS:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
+ break;
+ case STATE_DRAIN_BODY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDrainBody();
+ break;
+ case STATE_DRAIN_BODY_COMPLETE:
+ rv = DoDrainBodyComplete(rv);
+ break;
+ case STATE_TCP_RESTART:
+ DCHECK_EQ(OK, rv);
+ rv = DoTCPRestart();
+ break;
+ case STATE_TCP_RESTART_COMPLETE:
+ rv = DoTCPRestartComplete(rv);
+ break;
+ case STATE_DONE:
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE &&
+ next_state_ != STATE_DONE);
+ return rv;
+}
+
+int HttpProxyClientSocket::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ return auth_->MaybeGenerateAuthToken(&request_, io_callback_, net_log_);
+}
+
+int HttpProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ if (result == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return result;
+}
+
+int HttpProxyClientSocket::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ // This is constructed lazily (instead of within our Start method), so that
+ // we have proxy info available.
+ if (request_line_.empty()) {
+ DCHECK(request_headers_.IsEmpty());
+ HttpRequestHeaders authorization_headers;
+ if (auth_->HaveAuth())
+ auth_->AddAuthorizationHeader(&authorization_headers);
+ BuildTunnelRequest(request_, authorization_headers, endpoint_,
+ &request_line_, &request_headers_);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ base::Bind(&HttpRequestHeaders::NetLogCallback,
+ base::Unretained(&request_headers_),
+ &request_line_));
+ }
+
+ parser_buf_ = new GrowableIOBuffer();
+ http_stream_parser_.reset(new HttpStreamParser(
+ transport_.get(), &request_, parser_buf_.get(), net_log_));
+ return http_stream_parser_->SendRequest(
+ request_line_, request_headers_, &response_, io_callback_);
+}
+
+int HttpProxyClientSocket::DoSendRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int HttpProxyClientSocket::DoReadHeaders() {
+ next_state_ = STATE_READ_HEADERS_COMPLETE;
+ return http_stream_parser_->ReadResponseHeaders(io_callback_);
+}
+
+int HttpProxyClientSocket::DoReadHeadersComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // Require the "HTTP/1.x" status line for SSL CONNECT.
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0))
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));
+
+ switch (response_.headers->response_code()) {
+ case 200: // OK
+ if (http_stream_parser_->IsMoreDataBuffered())
+ // The proxy sent extraneous data after the headers.
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ next_state_ = STATE_DONE;
+ return OK;
+
+ // We aren't able to CONNECT to the remote host through the proxy. We
+ // need to be very suspicious about the response because an active network
+ // attacker can force us into this state by masquerading as the proxy.
+ // The only safe thing to do here is to fail the connection because our
+ // client is expecting an SSL protected response.
+ // See http://crbug.com/7338.
+
+ case 302: // Found / Moved Temporarily
+ // Attempt to follow redirects from HTTPS proxies, but only if we can
+ // sanitize the response. This still allows a rogue HTTPS proxy to
+ // redirect an HTTPS site load to a similar-looking site, but no longer
+ // allows it to impersonate the site the user requested.
+ if (is_https_proxy_ && SanitizeProxyRedirect(&response_, request_.url)) {
+ bool is_connection_reused = http_stream_parser_->IsConnectionReused();
+ redirect_has_load_timing_info_ =
+ transport_->GetLoadTimingInfo(
+ is_connection_reused, &redirect_load_timing_info_);
+ transport_.reset();
+ http_stream_parser_.reset();
+ return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
+ }
+
+ // We're not using an HTTPS proxy, or we couldn't sanitize the redirect.
+ LogBlockedTunnelResponse();
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ case 407: // Proxy Authentication Required
+ // We need this status code to allow proxy authentication. Our
+ // authentication code is smart enough to avoid being tricked by an
+ // active network attacker.
+ // The next state is intentionally not set as it should be STATE_NONE;
+ return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_);
+
+ default:
+ // Ignore response to avoid letting the proxy impersonate the target
+ // server. (See http://crbug.com/137891.)
+ // We lose something by doing this. We have seen proxy 403, 404, and
+ // 501 response bodies that contain a useful error message. For
+ // example, Squid uses a 404 response to report the DNS error: "The
+ // domain name does not exist."
+ LogBlockedTunnelResponse();
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+}
+
+int HttpProxyClientSocket::DoDrainBody() {
+ DCHECK(drain_buf_.get());
+ DCHECK(transport_->is_initialized());
+ next_state_ = STATE_DRAIN_BODY_COMPLETE;
+ return http_stream_parser_->ReadResponseBody(
+ drain_buf_.get(), kDrainBodyBufferSize, io_callback_);
+}
+
+int HttpProxyClientSocket::DoDrainBodyComplete(int result) {
+ if (result < 0)
+ return result;
+
+ if (http_stream_parser_->IsResponseBodyComplete())
+ return DidDrainBodyForAuthRestart(true);
+
+ // Keep draining.
+ next_state_ = STATE_DRAIN_BODY;
+ return OK;
+}
+
+int HttpProxyClientSocket::DoTCPRestart() {
+ next_state_ = STATE_TCP_RESTART_COMPLETE;
+ return transport_->socket()->Connect(
+ base::Bind(&HttpProxyClientSocket::OnIOComplete, base::Unretained(this)));
+}
+
+int HttpProxyClientSocket::DoTCPRestartComplete(int result) {
+ if (result != OK)
+ return result;
+
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+ return result;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_proxy_client_socket.h b/chromium/net/http/http_proxy_client_socket.h
new file mode 100644
index 00000000000..fca054f9c44
--- /dev/null
+++ b/chromium/net/http/http_proxy_client_socket.h
@@ -0,0 +1,173 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
+#define NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/proxy_client_socket.h"
+#include "net/socket/ssl_client_socket.h"
+
+class GURL;
+
+namespace net {
+
+class AddressList;
+class ClientSocketHandle;
+class GrowableIOBuffer;
+class HttpAuthCache;
+class HttpStream;
+class HttpStreamParser;
+class IOBuffer;
+
+class HttpProxyClientSocket : public ProxyClientSocket {
+ public:
+ // Takes ownership of |transport_socket|, which should already be connected
+ // by the time Connect() is called. If tunnel is true then on Connect()
+ // this socket will establish an Http tunnel.
+ HttpProxyClientSocket(ClientSocketHandle* transport_socket,
+ const GURL& request_url,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const HostPortPair& proxy_server,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ bool tunnel,
+ bool using_spdy,
+ NextProto protocol_negotiated,
+ bool is_https_proxy);
+
+ // On destruction Disconnect() is called.
+ virtual ~HttpProxyClientSocket();
+
+ // ProxyClientSocket implementation.
+ virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE;
+ virtual HttpStream* CreateConnectResponseStream() OVERRIDE;
+ virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE;
+ virtual const scoped_refptr<HttpAuthController>& GetAuthController() const
+ OVERRIDE;
+ virtual bool IsUsingSpdy() const OVERRIDE;
+ virtual NextProto GetProtocolNegotiated() const OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_GENERATE_AUTH_TOKEN,
+ STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_DRAIN_BODY,
+ STATE_DRAIN_BODY_COMPLETE,
+ STATE_TCP_RESTART,
+ STATE_TCP_RESTART_COMPLETE,
+ STATE_DONE,
+ };
+
+ // The size in bytes of the buffer we use to drain the response body that
+ // we want to throw away. The response body is typically a small error
+ // page just a few hundred bytes long.
+ static const int kDrainBodyBufferSize = 1024;
+
+ int PrepareForAuthRestart();
+ int DidDrainBodyForAuthRestart(bool keep_alive);
+
+ void LogBlockedTunnelResponse() const;
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoGenerateAuthToken();
+ int DoGenerateAuthTokenComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoDrainBody();
+ int DoDrainBodyComplete(int result);
+ int DoTCPRestart();
+ int DoTCPRestartComplete(int result);
+
+ CompletionCallback io_callback_;
+ State next_state_;
+
+ // Stores the callback to the layer above, called on completing Connect().
+ CompletionCallback user_callback_;
+
+ HttpRequestInfo request_;
+ HttpResponseInfo response_;
+
+ scoped_refptr<GrowableIOBuffer> parser_buf_;
+ scoped_ptr<HttpStreamParser> http_stream_parser_;
+ scoped_refptr<IOBuffer> drain_buf_;
+
+ // Stores the underlying socket.
+ scoped_ptr<ClientSocketHandle> transport_;
+
+ // The hostname and port of the endpoint. This is not necessarily the one
+ // specified by the URL, due to Alternate-Protocol or fixed testing ports.
+ const HostPortPair endpoint_;
+ scoped_refptr<HttpAuthController> auth_;
+ const bool tunnel_;
+ // If true, then the connection to the proxy is a SPDY connection.
+ const bool using_spdy_;
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated_;
+ // If true, then SSL is used to communicate with this proxy
+ const bool is_https_proxy_;
+
+ std::string request_line_;
+ HttpRequestHeaders request_headers_;
+
+ // Used only for redirects.
+ bool redirect_has_load_timing_info_;
+ LoadTimingInfo redirect_load_timing_info_;
+
+ const BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_H_
diff --git a/chromium/net/http/http_proxy_client_socket_pool.cc b/chromium/net/http/http_proxy_client_socket_pool.cc
new file mode 100644
index 00000000000..c75df6f0d2f
--- /dev/null
+++ b/chromium/net/http/http_proxy_client_socket_pool.cc
@@ -0,0 +1,542 @@
+// Copyright (c) 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.
+
+#include "net/http/http_proxy_client_socket_pool.h"
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/spdy/spdy_proxy_client_socket.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "url/gurl.h"
+
+namespace net {
+
+HttpProxySocketParams::HttpProxySocketParams(
+ const scoped_refptr<TransportSocketParams>& transport_params,
+ const scoped_refptr<SSLSocketParams>& ssl_params,
+ const GURL& request_url,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ SpdySessionPool* spdy_session_pool,
+ bool tunnel)
+ : transport_params_(transport_params),
+ ssl_params_(ssl_params),
+ spdy_session_pool_(spdy_session_pool),
+ request_url_(request_url),
+ user_agent_(user_agent),
+ endpoint_(endpoint),
+ http_auth_cache_(tunnel ? http_auth_cache : NULL),
+ http_auth_handler_factory_(tunnel ? http_auth_handler_factory : NULL),
+ tunnel_(tunnel) {
+ DCHECK((transport_params.get() == NULL && ssl_params.get() != NULL) ||
+ (transport_params.get() != NULL && ssl_params.get() == NULL));
+ if (transport_params_.get()) {
+ ignore_limits_ = transport_params->ignore_limits();
+ } else {
+ ignore_limits_ = ssl_params->ignore_limits();
+ }
+}
+
+const HostResolver::RequestInfo& HttpProxySocketParams::destination() const {
+ if (transport_params_.get() == NULL) {
+ return ssl_params_->transport_params()->destination();
+ } else {
+ return transport_params_->destination();
+ }
+}
+
+HttpProxySocketParams::~HttpProxySocketParams() {}
+
+// HttpProxyConnectJobs will time out after this many seconds. Note this is on
+// top of the timeout for the transport socket.
+#if (defined(OS_ANDROID) || defined(OS_IOS)) && defined(SPDY_PROXY_AUTH_ORIGIN)
+static const int kHttpProxyConnectJobTimeoutInSeconds = 10;
+#else
+static const int kHttpProxyConnectJobTimeoutInSeconds = 30;
+#endif
+
+
+HttpProxyConnectJob::HttpProxyConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<HttpProxySocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ weak_ptr_factory_(this),
+ params_(params),
+ transport_pool_(transport_pool),
+ ssl_pool_(ssl_pool),
+ resolver_(host_resolver),
+ callback_(base::Bind(&HttpProxyConnectJob::OnIOComplete,
+ weak_ptr_factory_.GetWeakPtr())),
+ using_spdy_(false),
+ protocol_negotiated_(kProtoUnknown) {
+}
+
+HttpProxyConnectJob::~HttpProxyConnectJob() {}
+
+LoadState HttpProxyConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_TCP_CONNECT:
+ case STATE_TCP_CONNECT_COMPLETE:
+ case STATE_SSL_CONNECT:
+ case STATE_SSL_CONNECT_COMPLETE:
+ return transport_socket_handle_->GetLoadState();
+ case STATE_HTTP_PROXY_CONNECT:
+ case STATE_HTTP_PROXY_CONNECT_COMPLETE:
+ case STATE_SPDY_PROXY_CREATE_STREAM:
+ case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
+ return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+void HttpProxyConnectJob::GetAdditionalErrorState(ClientSocketHandle * handle) {
+ if (error_response_info_.cert_request_info.get()) {
+ handle->set_ssl_error_response_info(error_response_info_);
+ handle->set_is_ssl_error(true);
+ }
+}
+
+void HttpProxyConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|
+}
+
+int HttpProxyConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_TCP_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTransportConnect();
+ break;
+ case STATE_TCP_CONNECT_COMPLETE:
+ rv = DoTransportConnectComplete(rv);
+ break;
+ case STATE_SSL_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSSLConnect();
+ break;
+ case STATE_SSL_CONNECT_COMPLETE:
+ rv = DoSSLConnectComplete(rv);
+ break;
+ case STATE_HTTP_PROXY_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoHttpProxyConnect();
+ break;
+ case STATE_HTTP_PROXY_CONNECT_COMPLETE:
+ rv = DoHttpProxyConnectComplete(rv);
+ break;
+ case STATE_SPDY_PROXY_CREATE_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoSpdyProxyCreateStream();
+ break;
+ case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
+ rv = DoSpdyProxyCreateStreamComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpProxyConnectJob::DoTransportConnect() {
+ next_state_ = STATE_TCP_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ return transport_socket_handle_->Init(
+ group_name(), params_->transport_params(),
+ params_->transport_params()->destination().priority(), callback_,
+ transport_pool_, net_log());
+}
+
+int HttpProxyConnectJob::DoTransportConnectComplete(int result) {
+ if (result != OK)
+ return ERR_PROXY_CONNECTION_FAILED;
+
+ // Reset the timer to just the length of time allowed for HttpProxy handshake
+ // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
+ // longer to timeout than it should.
+ ResetTimer(base::TimeDelta::FromSeconds(
+ kHttpProxyConnectJobTimeoutInSeconds));
+
+ next_state_ = STATE_HTTP_PROXY_CONNECT;
+ return result;
+}
+
+int HttpProxyConnectJob::DoSSLConnect() {
+ if (params_->tunnel()) {
+ SpdySessionKey key(params_->destination().host_port_pair(),
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ if (params_->spdy_session_pool()->FindAvailableSession(key, net_log())) {
+ using_spdy_ = true;
+ next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
+ return OK;
+ }
+ }
+ next_state_ = STATE_SSL_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ return transport_socket_handle_->Init(
+ group_name(), params_->ssl_params(),
+ params_->ssl_params()->transport_params()->destination().priority(),
+ callback_, ssl_pool_, net_log());
+}
+
+int HttpProxyConnectJob::DoSSLConnectComplete(int result) {
+ if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ error_response_info_ = transport_socket_handle_->ssl_error_response_info();
+ DCHECK(error_response_info_.cert_request_info.get());
+ error_response_info_.cert_request_info->is_proxy = true;
+ return result;
+ }
+ if (IsCertificateError(result)) {
+ if (params_->ssl_params()->load_flags() & LOAD_IGNORE_ALL_CERT_ERRORS) {
+ result = OK;
+ } else {
+ // TODO(rch): allow the user to deal with proxy cert errors in the
+ // same way as server cert errors.
+ transport_socket_handle_->socket()->Disconnect();
+ return ERR_PROXY_CERTIFICATE_INVALID;
+ }
+ }
+ if (result < 0) {
+ if (transport_socket_handle_->socket())
+ transport_socket_handle_->socket()->Disconnect();
+ return ERR_PROXY_CONNECTION_FAILED;
+ }
+
+ SSLClientSocket* ssl =
+ static_cast<SSLClientSocket*>(transport_socket_handle_->socket());
+ using_spdy_ = ssl->was_spdy_negotiated();
+ protocol_negotiated_ = ssl->GetNegotiatedProtocol();
+
+ // Reset the timer to just the length of time allowed for HttpProxy handshake
+ // so that a fast SSL connection plus a slow HttpProxy failure doesn't take
+ // longer to timeout than it should.
+ ResetTimer(base::TimeDelta::FromSeconds(
+ kHttpProxyConnectJobTimeoutInSeconds));
+ // TODO(rch): If we ever decide to implement a "trusted" SPDY proxy
+ // (one that we speak SPDY over SSL to, but to which we send HTTPS
+ // request directly instead of through CONNECT tunnels, then we
+ // need to add a predicate to this if statement so we fall through
+ // to the else case. (HttpProxyClientSocket currently acts as
+ // a "trusted" SPDY proxy).
+ if (using_spdy_ && params_->tunnel()) {
+ next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
+ } else {
+ next_state_ = STATE_HTTP_PROXY_CONNECT;
+ }
+ return result;
+}
+
+int HttpProxyConnectJob::DoHttpProxyConnect() {
+ next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
+ const HostResolver::RequestInfo& tcp_destination = params_->destination();
+ const HostPortPair& proxy_server = tcp_destination.host_port_pair();
+
+ // Add a HttpProxy connection on top of the tcp socket.
+ transport_socket_.reset(
+ new HttpProxyClientSocket(transport_socket_handle_.release(),
+ params_->request_url(),
+ params_->user_agent(),
+ params_->endpoint(),
+ proxy_server,
+ params_->http_auth_cache(),
+ params_->http_auth_handler_factory(),
+ params_->tunnel(),
+ using_spdy_,
+ protocol_negotiated_,
+ params_->ssl_params().get() != NULL));
+ return transport_socket_->Connect(callback_);
+}
+
+int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
+ if (result == OK || result == ERR_PROXY_AUTH_REQUESTED ||
+ result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
+ SetSocket(transport_socket_.PassAs<StreamSocket>());
+ }
+
+ return result;
+}
+
+int HttpProxyConnectJob::DoSpdyProxyCreateStream() {
+ DCHECK(using_spdy_);
+ DCHECK(params_->tunnel());
+ SpdySessionKey key(params_->destination().host_port_pair(),
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ SpdySessionPool* spdy_pool = params_->spdy_session_pool();
+ base::WeakPtr<SpdySession> spdy_session =
+ spdy_pool->FindAvailableSession(key, net_log());
+ // It's possible that a session to the proxy has recently been created
+ if (spdy_session) {
+ if (transport_socket_handle_.get()) {
+ if (transport_socket_handle_->socket())
+ transport_socket_handle_->socket()->Disconnect();
+ transport_socket_handle_->Reset();
+ }
+ } else {
+ // Create a session direct to the proxy itself
+ int rv = spdy_pool->CreateAvailableSessionFromSocket(
+ key, transport_socket_handle_.Pass(),
+ net_log(), OK, &spdy_session, /*using_ssl_*/ true);
+ if (rv < 0)
+ return rv;
+ }
+
+ next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
+ return spdy_stream_request_.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, spdy_session, params_->request_url(),
+ params_->destination().priority(), spdy_session->net_log(), callback_);
+}
+
+int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) {
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
+ base::WeakPtr<SpdyStream> stream = spdy_stream_request_.ReleaseStream();
+ DCHECK(stream.get());
+ // |transport_socket_| will set itself as |stream|'s delegate.
+ transport_socket_.reset(
+ new SpdyProxyClientSocket(stream,
+ params_->user_agent(),
+ params_->endpoint(),
+ params_->request_url(),
+ params_->destination().host_port_pair(),
+ net_log(),
+ params_->http_auth_cache(),
+ params_->http_auth_handler_factory()));
+ return transport_socket_->Connect(callback_);
+}
+
+int HttpProxyConnectJob::ConnectInternal() {
+ if (params_->transport_params().get()) {
+ next_state_ = STATE_TCP_CONNECT;
+ } else {
+ next_state_ = STATE_SSL_CONNECT;
+ }
+ return DoLoop(OK);
+}
+
+HttpProxyClientSocketPool::
+HttpProxyConnectJobFactory::HttpProxyConnectJobFactory(
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ ssl_pool_(ssl_pool),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {
+ base::TimeDelta max_pool_timeout = base::TimeDelta();
+
+#if (defined(OS_ANDROID) || defined(OS_IOS)) && defined(SPDY_PROXY_AUTH_ORIGIN)
+#else
+ if (transport_pool_)
+ max_pool_timeout = transport_pool_->ConnectionTimeout();
+ if (ssl_pool_)
+ max_pool_timeout = std::max(max_pool_timeout,
+ ssl_pool_->ConnectionTimeout());
+#endif
+ timeout_ = max_pool_timeout +
+ base::TimeDelta::FromSeconds(kHttpProxyConnectJobTimeoutInSeconds);
+}
+
+
+scoped_ptr<ConnectJob>
+HttpProxyClientSocketPool::HttpProxyConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return scoped_ptr<ConnectJob>(new HttpProxyConnectJob(group_name,
+ request.params(),
+ ConnectionTimeout(),
+ transport_pool_,
+ ssl_pool_,
+ host_resolver_,
+ delegate,
+ net_log_));
+}
+
+base::TimeDelta
+HttpProxyClientSocketPool::HttpProxyConnectJobFactory::ConnectionTimeout(
+ ) const {
+ return timeout_;
+}
+
+HttpProxyClientSocketPool::HttpProxyClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ ssl_pool_(ssl_pool),
+ base_(max_sockets, max_sockets_per_group, histograms,
+ ClientSocketPool::unused_idle_socket_timeout(),
+ ClientSocketPool::used_idle_socket_timeout(),
+ new HttpProxyConnectJobFactory(transport_pool,
+ ssl_pool,
+ host_resolver,
+ net_log)) {
+ // We should always have a |transport_pool_| except in unit tests.
+ if (transport_pool_)
+ transport_pool_->AddLayeredPool(this);
+ if (ssl_pool_)
+ ssl_pool_->AddLayeredPool(this);
+}
+
+HttpProxyClientSocketPool::~HttpProxyClientSocketPool() {
+ if (ssl_pool_)
+ ssl_pool_->RemoveLayeredPool(this);
+ // We should always have a |transport_pool_| except in unit tests.
+ if (transport_pool_)
+ transport_pool_->RemoveLayeredPool(this);
+}
+
+int HttpProxyClientSocketPool::RequestSocket(
+ const std::string& group_name, const void* socket_params,
+ RequestPriority priority, ClientSocketHandle* handle,
+ const CompletionCallback& callback, const BoundNetLog& net_log) {
+ const scoped_refptr<HttpProxySocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<HttpProxySocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void HttpProxyClientSocketPool::RequestSockets(
+ const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<HttpProxySocketParams>* casted_params =
+ static_cast<const scoped_refptr<HttpProxySocketParams>*>(params);
+
+ base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
+}
+
+void HttpProxyClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void HttpProxyClientSocketPool::ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket.Pass(), id);
+}
+
+void HttpProxyClientSocketPool::FlushWithError(int error) {
+ base_.FlushWithError(error);
+}
+
+bool HttpProxyClientSocketPool::IsStalled() const {
+ return base_.IsStalled() ||
+ (transport_pool_ && transport_pool_->IsStalled()) ||
+ (ssl_pool_ && ssl_pool_->IsStalled());
+}
+
+void HttpProxyClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int HttpProxyClientSocketPool::IdleSocketCount() const {
+ return base_.idle_socket_count();
+}
+
+int HttpProxyClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState HttpProxyClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+void HttpProxyClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
+ base_.AddLayeredPool(layered_pool);
+}
+
+void HttpProxyClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
+ base_.RemoveLayeredPool(layered_pool);
+}
+
+base::DictionaryValue* HttpProxyClientSocketPool::GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const {
+ base::DictionaryValue* dict = base_.GetInfoAsValue(name, type);
+ if (include_nested_pools) {
+ base::ListValue* list = new base::ListValue();
+ if (transport_pool_) {
+ list->Append(transport_pool_->GetInfoAsValue("transport_socket_pool",
+ "transport_socket_pool",
+ true));
+ }
+ if (ssl_pool_) {
+ list->Append(ssl_pool_->GetInfoAsValue("ssl_socket_pool",
+ "ssl_socket_pool",
+ true));
+ }
+ dict->Set("nested_pools", list);
+ }
+ return dict;
+}
+
+base::TimeDelta HttpProxyClientSocketPool::ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+}
+
+ClientSocketPoolHistograms* HttpProxyClientSocketPool::histograms() const {
+ return base_.histograms();
+}
+
+bool HttpProxyClientSocketPool::CloseOneIdleConnection() {
+ if (base_.CloseOneIdleSocket())
+ return true;
+ return base_.CloseOneIdleConnectionInLayeredPool();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_proxy_client_socket_pool.h b/chromium/net/http/http_proxy_client_socket_pool.h
new file mode 100644
index 00000000000..b77b5ae3571
--- /dev/null
+++ b/chromium/net/http/http_proxy_client_socket_pool.h
@@ -0,0 +1,282 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
+#define NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_response_info.h"
+#include "net/http/proxy_client_socket.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+class HostResolver;
+class HttpAuthCache;
+class HttpAuthHandlerFactory;
+class SSLClientSocketPool;
+class SSLSocketParams;
+class SpdySessionPool;
+class SpdyStream;
+class TransportClientSocketPool;
+class TransportSocketParams;
+
+// HttpProxySocketParams only needs the socket params for one of the proxy
+// types. The other param must be NULL. When using an HTTP Proxy,
+// |transport_params| must be set. When using an HTTPS Proxy, |ssl_params|
+// must be set.
+class NET_EXPORT_PRIVATE HttpProxySocketParams
+ : public base::RefCounted<HttpProxySocketParams> {
+ public:
+ HttpProxySocketParams(
+ const scoped_refptr<TransportSocketParams>& transport_params,
+ const scoped_refptr<SSLSocketParams>& ssl_params,
+ const GURL& request_url,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ HttpAuthCache* http_auth_cache,
+ HttpAuthHandlerFactory* http_auth_handler_factory,
+ SpdySessionPool* spdy_session_pool,
+ bool tunnel);
+
+ const scoped_refptr<TransportSocketParams>& transport_params() const {
+ return transport_params_;
+ }
+ const scoped_refptr<SSLSocketParams>& ssl_params() const {
+ return ssl_params_;
+ }
+ const GURL& request_url() const { return request_url_; }
+ const std::string& user_agent() const { return user_agent_; }
+ const HostPortPair& endpoint() const { return endpoint_; }
+ HttpAuthCache* http_auth_cache() const { return http_auth_cache_; }
+ HttpAuthHandlerFactory* http_auth_handler_factory() const {
+ return http_auth_handler_factory_;
+ }
+ SpdySessionPool* spdy_session_pool() {
+ return spdy_session_pool_;
+ }
+ const HostResolver::RequestInfo& destination() const;
+ bool tunnel() const { return tunnel_; }
+ bool ignore_limits() const { return ignore_limits_; }
+
+ private:
+ friend class base::RefCounted<HttpProxySocketParams>;
+ ~HttpProxySocketParams();
+
+ const scoped_refptr<TransportSocketParams> transport_params_;
+ const scoped_refptr<SSLSocketParams> ssl_params_;
+ SpdySessionPool* spdy_session_pool_;
+ const GURL request_url_;
+ const std::string user_agent_;
+ const HostPortPair endpoint_;
+ HttpAuthCache* const http_auth_cache_;
+ HttpAuthHandlerFactory* const http_auth_handler_factory_;
+ const bool tunnel_;
+ bool ignore_limits_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxySocketParams);
+};
+
+// HttpProxyConnectJob optionally establishes a tunnel through the proxy
+// server after connecting the underlying transport socket.
+class HttpProxyConnectJob : public ConnectJob {
+ public:
+ HttpProxyConnectJob(const std::string& group_name,
+ const scoped_refptr<HttpProxySocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~HttpProxyConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const OVERRIDE;
+
+ virtual void GetAdditionalErrorState(ClientSocketHandle* handle) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_TCP_CONNECT,
+ STATE_TCP_CONNECT_COMPLETE,
+ STATE_SSL_CONNECT,
+ STATE_SSL_CONNECT_COMPLETE,
+ STATE_HTTP_PROXY_CONNECT,
+ STATE_HTTP_PROXY_CONNECT_COMPLETE,
+ STATE_SPDY_PROXY_CREATE_STREAM,
+ STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE,
+ STATE_SPDY_PROXY_CONNECT_COMPLETE,
+ STATE_NONE,
+ };
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Connecting to HTTP Proxy
+ int DoTransportConnect();
+ int DoTransportConnectComplete(int result);
+ // Connecting to HTTPS Proxy
+ int DoSSLConnect();
+ int DoSSLConnectComplete(int result);
+
+ int DoHttpProxyConnect();
+ int DoHttpProxyConnectComplete(int result);
+
+ int DoSpdyProxyCreateStream();
+ int DoSpdyProxyCreateStreamComplete(int result);
+
+ // Begins the tcp connection and the optional Http proxy tunnel. If the
+ // request is not immediately servicable (likely), the request will return
+ // ERR_IO_PENDING. An OK return from this function or the callback means
+ // that the connection is established; ERR_PROXY_AUTH_REQUESTED means
+ // that the tunnel needs authentication credentials, the socket will be
+ // returned in this case, and must be release back to the pool; or
+ // a standard net error code will be returned.
+ virtual int ConnectInternal() OVERRIDE;
+
+ base::WeakPtrFactory<HttpProxyConnectJob> weak_ptr_factory_;
+ scoped_refptr<HttpProxySocketParams> params_;
+ TransportClientSocketPool* const transport_pool_;
+ SSLClientSocketPool* const ssl_pool_;
+ HostResolver* const resolver_;
+
+ State next_state_;
+ CompletionCallback callback_;
+ scoped_ptr<ClientSocketHandle> transport_socket_handle_;
+ scoped_ptr<ProxyClientSocket> transport_socket_;
+ bool using_spdy_;
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated_;
+
+ HttpResponseInfo error_response_info_;
+
+ SpdyStreamRequest spdy_stream_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJob);
+};
+
+class NET_EXPORT_PRIVATE HttpProxyClientSocketPool
+ : public ClientSocketPool,
+ public LayeredPool {
+ public:
+ HttpProxyClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ NetLog* net_log);
+
+ virtual ~HttpProxyClientSocketPool();
+
+ // ClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+
+ virtual void FlushWithError(int error) OVERRIDE;
+
+ virtual bool IsStalled() const OVERRIDE;
+
+ virtual void CloseIdleSockets() OVERRIDE;
+
+ virtual int IdleSocketCount() const OVERRIDE;
+
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE;
+
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE;
+
+ virtual void AddLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual void RemoveLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ virtual ClientSocketPoolHistograms* histograms() const OVERRIDE;
+
+ // LayeredPool implementation.
+ virtual bool CloseOneIdleConnection() OVERRIDE;
+
+ private:
+ typedef ClientSocketPoolBase<HttpProxySocketParams> PoolBase;
+
+ class HttpProxyConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ HttpProxyConnectJobFactory(
+ TransportClientSocketPool* transport_pool,
+ SSLClientSocketPool* ssl_pool,
+ HostResolver* host_resolver,
+ NetLog* net_log);
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ private:
+ TransportClientSocketPool* const transport_pool_;
+ SSLClientSocketPool* const ssl_pool_;
+ HostResolver* const host_resolver_;
+ NetLog* net_log_;
+ base::TimeDelta timeout_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJobFactory);
+ };
+
+ TransportClientSocketPool* const transport_pool_;
+ SSLClientSocketPool* const ssl_pool_;
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpProxyClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(HttpProxyClientSocketPool,
+ HttpProxySocketParams);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_PROXY_CLIENT_SOCKET_POOL_H_
diff --git a/chromium/net/http/http_proxy_client_socket_pool_unittest.cc b/chromium/net/http/http_proxy_client_socket_pool_unittest.cc
new file mode 100644
index 00000000000..2274f250ff1
--- /dev/null
+++ b/chromium/net/http/http_proxy_client_socket_pool_unittest.cc
@@ -0,0 +1,656 @@
+// Copyright (c) 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.
+
+#include "net/http/http_proxy_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+const char * const kAuthHeaders[] = {
+ "proxy-authorization", "Basic Zm9vOmJhcg=="
+};
+const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2;
+
+enum HttpProxyType {
+ HTTP,
+ HTTPS,
+ SPDY
+};
+
+struct HttpProxyClientSocketPoolTestParams {
+ HttpProxyClientSocketPoolTestParams()
+ : proxy_type(HTTP),
+ protocol(kProtoSPDY2) {}
+
+ HttpProxyClientSocketPoolTestParams(
+ HttpProxyType proxy_type,
+ NextProto protocol)
+ : proxy_type(proxy_type),
+ protocol(protocol) {}
+
+ HttpProxyType proxy_type;
+ NextProto protocol;
+};
+
+typedef ::testing::TestWithParam<HttpProxyType> TestWithHttpParam;
+
+} // namespace
+
+class HttpProxyClientSocketPoolTest
+ : public ::testing::TestWithParam<HttpProxyClientSocketPoolTestParams> {
+ protected:
+ HttpProxyClientSocketPoolTest()
+ : session_deps_(GetParam().protocol),
+ ssl_config_(),
+ ignored_transport_socket_params_(
+ new TransportSocketParams(HostPortPair("proxy", 80),
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback())),
+ ignored_ssl_socket_params_(
+ new SSLSocketParams(ignored_transport_socket_params_,
+ NULL,
+ NULL,
+ ProxyServer::SCHEME_DIRECT,
+ HostPortPair("www.google.com", 443),
+ ssl_config_,
+ kPrivacyModeDisabled,
+ 0,
+ false,
+ false)),
+ tcp_histograms_("MockTCP"),
+ transport_socket_pool_(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ &tcp_histograms_,
+ session_deps_.deterministic_socket_factory.get()),
+ ssl_histograms_("MockSSL"),
+ ssl_socket_pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ &ssl_histograms_,
+ session_deps_.host_resolver.get(),
+ session_deps_.cert_verifier.get(),
+ NULL /* server_bound_cert_store */,
+ NULL /* transport_security_state */,
+ std::string() /* ssl_session_cache_shard */,
+ session_deps_.deterministic_socket_factory.get(),
+ &transport_socket_pool_,
+ NULL,
+ NULL,
+ session_deps_.ssl_config_service.get(),
+ BoundNetLog().net_log()),
+ session_(CreateNetworkSession()),
+ http_proxy_histograms_("HttpProxyUnitTest"),
+ spdy_util_(GetParam().protocol),
+ pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ &http_proxy_histograms_,
+ NULL,
+ &transport_socket_pool_,
+ &ssl_socket_pool_,
+ NULL) {}
+
+ virtual ~HttpProxyClientSocketPoolTest() {
+ }
+
+ void AddAuthToCache() {
+ const base::string16 kFoo(ASCIIToUTF16("foo"));
+ const base::string16 kBar(ASCIIToUTF16("bar"));
+ GURL proxy_url(GetParam().proxy_type == HTTP ? "http://proxy" : "https://proxy:80");
+ session_->http_auth_cache()->Add(proxy_url,
+ "MyRealm1",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "Basic realm=MyRealm1",
+ AuthCredentials(kFoo, kBar),
+ "/");
+ }
+
+ scoped_refptr<TransportSocketParams> GetTcpParams() {
+ if (GetParam().proxy_type != HTTP)
+ return scoped_refptr<TransportSocketParams>();
+ return ignored_transport_socket_params_;
+ }
+
+ scoped_refptr<SSLSocketParams> GetSslParams() {
+ if (GetParam().proxy_type == HTTP)
+ return scoped_refptr<SSLSocketParams>();
+ return ignored_ssl_socket_params_;
+ }
+
+ // Returns the a correctly constructed HttpProxyParms
+ // for the HTTP or HTTPS proxy.
+ scoped_refptr<HttpProxySocketParams> GetParams(bool tunnel) {
+ return scoped_refptr<HttpProxySocketParams>(new HttpProxySocketParams(
+ GetTcpParams(),
+ GetSslParams(),
+ GURL(tunnel ? "https://www.google.com/" : "http://www.google.com"),
+ std::string(),
+ HostPortPair("www.google.com", tunnel ? 443 : 80),
+ session_->http_auth_cache(),
+ session_->http_auth_handler_factory(),
+ session_->spdy_session_pool(),
+ tunnel));
+ }
+
+ scoped_refptr<HttpProxySocketParams> GetTunnelParams() {
+ return GetParams(true);
+ }
+
+ scoped_refptr<HttpProxySocketParams> GetNoTunnelParams() {
+ return GetParams(false);
+ }
+
+ DeterministicMockClientSocketFactory& socket_factory() {
+ return *session_deps_.deterministic_socket_factory.get();
+ }
+
+ void Initialize(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ MockRead* spdy_reads, size_t spdy_reads_count,
+ MockWrite* spdy_writes, size_t spdy_writes_count) {
+ if (GetParam().proxy_type == SPDY) {
+ data_.reset(new DeterministicSocketData(spdy_reads, spdy_reads_count,
+ spdy_writes, spdy_writes_count));
+ } else {
+ data_.reset(new DeterministicSocketData(reads, reads_count, writes,
+ writes_count));
+ }
+
+ data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ data_->StopAfter(2); // Request / Response
+
+ socket_factory().AddSocketDataProvider(data_.get());
+
+ if (GetParam().proxy_type != HTTP) {
+ ssl_data_.reset(new SSLSocketDataProvider(SYNCHRONOUS, OK));
+ if (GetParam().proxy_type == SPDY) {
+ InitializeSpdySsl();
+ }
+ socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
+ }
+ }
+
+ void InitializeSpdySsl() {
+ ssl_data_->SetNextProto(GetParam().protocol);
+ }
+
+ HttpNetworkSession* CreateNetworkSession() {
+ return SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ }
+
+ private:
+ SpdySessionDependencies session_deps_;
+ SSLConfig ssl_config_;
+
+ scoped_refptr<TransportSocketParams> ignored_transport_socket_params_;
+ scoped_refptr<SSLSocketParams> ignored_ssl_socket_params_;
+ ClientSocketPoolHistograms tcp_histograms_;
+ MockTransportClientSocketPool transport_socket_pool_;
+ ClientSocketPoolHistograms ssl_histograms_;
+ MockHostResolver host_resolver_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ SSLClientSocketPool ssl_socket_pool_;
+
+ const scoped_refptr<HttpNetworkSession> session_;
+ ClientSocketPoolHistograms http_proxy_histograms_;
+
+ protected:
+ SpdyTestUtil spdy_util_;
+ scoped_ptr<SSLSocketDataProvider> ssl_data_;
+ scoped_ptr<DeterministicSocketData> data_;
+ HttpProxyClientSocketPool pool_;
+ ClientSocketHandle handle_;
+ TestCompletionCallback callback_;
+};
+
+//-----------------------------------------------------------------------------
+// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
+// and SPDY.
+//
+// TODO(akalin): Use ::testing::Combine() when we are able to use
+// <tr1/tuple>.
+INSTANTIATE_TEST_CASE_P(
+ HttpProxyClientSocketPoolTests,
+ HttpProxyClientSocketPoolTest,
+ ::testing::Values(
+ HttpProxyClientSocketPoolTestParams(HTTP, kProtoSPDY2),
+ HttpProxyClientSocketPoolTestParams(HTTPS, kProtoSPDY2),
+ HttpProxyClientSocketPoolTestParams(SPDY, kProtoSPDY2),
+ HttpProxyClientSocketPoolTestParams(HTTP, kProtoSPDY3),
+ HttpProxyClientSocketPoolTestParams(HTTPS, kProtoSPDY3),
+ HttpProxyClientSocketPoolTestParams(SPDY, kProtoSPDY3),
+ HttpProxyClientSocketPoolTestParams(HTTP, kProtoSPDY31),
+ HttpProxyClientSocketPoolTestParams(HTTPS, kProtoSPDY31),
+ HttpProxyClientSocketPoolTestParams(SPDY, kProtoSPDY31),
+ HttpProxyClientSocketPoolTestParams(HTTP, kProtoSPDY4a2),
+ HttpProxyClientSocketPoolTestParams(HTTPS, kProtoSPDY4a2),
+ HttpProxyClientSocketPoolTestParams(SPDY, kProtoSPDY4a2),
+ HttpProxyClientSocketPoolTestParams(HTTP, kProtoHTTP2Draft04),
+ HttpProxyClientSocketPoolTestParams(HTTPS, kProtoHTTP2Draft04),
+ HttpProxyClientSocketPoolTestParams(SPDY, kProtoHTTP2Draft04)));
+
+TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) {
+ Initialize(NULL, 0, NULL, 0, NULL, 0, NULL, 0);
+
+ int rv = handle_.Init("a", GetNoTunnelParams(), LOW, CompletionCallback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle_.is_initialized());
+ ASSERT_TRUE(handle_.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle_.socket());
+ EXPECT_TRUE(tunnel_socket->IsConnected());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead(ASYNC, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 4, "0123456789"),
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyConnect(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC),
+ CreateMockWrite(*rst, 2, ASYNC),
+ };
+ const char* const kAuthChallenge[] = {
+ spdy_util_.GetStatusKey(), "407 Proxy Authentication Required",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+ };
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kAuthChallenge,
+ arraysize(kAuthChallenge),
+ 0));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 3)
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+
+ data_->StopAfter(4);
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(GetParam().proxy_type == SPDY ? 2 : 4);
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, rv);
+ EXPECT_TRUE(handle_.is_initialized());
+ ASSERT_TRUE(handle_.socket());
+ ProxyClientSocket* tunnel_socket =
+ static_cast<ProxyClientSocket*>(handle_.socket());
+ if (GetParam().proxy_type == SPDY) {
+ EXPECT_TRUE(tunnel_socket->IsConnected());
+ EXPECT_TRUE(tunnel_socket->IsUsingSpdy());
+ } else {
+ EXPECT_FALSE(tunnel_socket->IsConnected());
+ EXPECT_FALSE(tunnel_socket->IsUsingSpdy());
+ EXPECT_FALSE(tunnel_socket->IsUsingSpdy());
+ }
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) {
+ // It's pretty much impossible to make the SPDY case behave synchronously
+ // so we skip this test for SPDY
+ if (GetParam().proxy_type == SPDY)
+ return;
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes), NULL, 0,
+ NULL, 0);
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle_.is_initialized());
+ ASSERT_TRUE(handle_.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle_.socket());
+ EXPECT_TRUE(tunnel_socket->IsConnected());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC)
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2)
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(2);
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ EXPECT_TRUE(handle_.is_initialized());
+ ASSERT_TRUE(handle_.socket());
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(handle_.socket());
+ EXPECT_TRUE(tunnel_socket->IsConnected());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, TCPError) {
+ if (GetParam().proxy_type == SPDY) return;
+ data_.reset(new DeterministicSocketData(NULL, 0, NULL, 0));
+ data_->set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_CLOSED));
+
+ socket_factory().AddSocketDataProvider(data_.get());
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback_.WaitForResult());
+
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, SSLError) {
+ if (GetParam().proxy_type == HTTP) return;
+ data_.reset(new DeterministicSocketData(NULL, 0, NULL, 0));
+ data_->set_connect_data(MockConnect(ASYNC, OK));
+ socket_factory().AddSocketDataProvider(data_.get());
+
+ ssl_data_.reset(new SSLSocketDataProvider(ASYNC,
+ ERR_CERT_AUTHORITY_INVALID));
+ if (GetParam().proxy_type == SPDY) {
+ InitializeSpdySsl();
+ }
+ socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ EXPECT_EQ(ERR_PROXY_CERTIFICATE_INVALID, callback_.WaitForResult());
+
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) {
+ if (GetParam().proxy_type == HTTP) return;
+ data_.reset(new DeterministicSocketData(NULL, 0, NULL, 0));
+ data_->set_connect_data(MockConnect(ASYNC, OK));
+ socket_factory().AddSocketDataProvider(data_.get());
+
+ ssl_data_.reset(new SSLSocketDataProvider(ASYNC,
+ ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
+ if (GetParam().proxy_type == SPDY) {
+ InitializeSpdySsl();
+ }
+ socket_factory().AddSSLSocketDataProvider(ssl_data_.get());
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, callback_.WaitForResult());
+
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 Conn"),
+ MockRead(ASYNC, ERR_CONNECTION_CLOSED, 2),
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC)
+ };
+ MockRead spdy_reads[] = {
+ MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(3);
+ if (GetParam().proxy_type == SPDY) {
+ // SPDY cannot process a headers block unless it's complete and so it
+ // returns ERR_CONNECTION_CLOSED in this case.
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_TRUNCATED, callback_.WaitForResult());
+ }
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"),
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC),
+ CreateMockWrite(*rst, 2, ASYNC),
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdySynReplyError(1));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 3),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(2);
+
+ rv = callback_.WaitForResult();
+ // All Proxy CONNECT responses are not trustworthy
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+}
+
+TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupRedirect) {
+ const std::string redirectTarget = "https://foo.google.com/";
+
+ const std::string responseText = "HTTP/1.1 302 Found\r\n"
+ "Location: " + redirectTarget + "\r\n"
+ "Set-Cookie: foo=bar\r\n"
+ "\r\n";
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, responseText.c_str()),
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req, 0, ASYNC),
+ CreateMockWrite(*rst, 3, ASYNC),
+ };
+
+ const char* const responseHeaders[] = {
+ "location", redirectTarget.c_str(),
+ "set-cookie", "foo=bar",
+ };
+ const int responseHeadersSize = arraysize(responseHeaders) / 2;
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdySynReplyError(
+ "302 Found",
+ responseHeaders, responseHeadersSize,
+ 1));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2),
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes),
+ spdy_reads, arraysize(spdy_reads), spdy_writes,
+ arraysize(spdy_writes));
+ AddAuthToCache();
+
+ int rv = handle_.Init("a", GetTunnelParams(), LOW, callback_.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+
+ data_->RunFor(2);
+
+ rv = callback_.WaitForResult();
+
+ if (GetParam().proxy_type == HTTP) {
+ // We don't trust 302 responses to CONNECT from HTTP proxies.
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle_.is_initialized());
+ EXPECT_FALSE(handle_.socket());
+ } else {
+ // Expect ProxyClientSocket to return the proxy's response, sanitized.
+ EXPECT_EQ(ERR_HTTPS_PROXY_TUNNEL_RESPONSE, rv);
+ EXPECT_TRUE(handle_.is_initialized());
+ ASSERT_TRUE(handle_.socket());
+
+ const ProxyClientSocket* tunnel_socket =
+ static_cast<ProxyClientSocket*>(handle_.socket());
+ const HttpResponseInfo* response = tunnel_socket->GetConnectResponseInfo();
+ const HttpResponseHeaders* headers = response->headers.get();
+
+ // Make sure Set-Cookie header was stripped.
+ EXPECT_FALSE(headers->HasHeader("set-cookie"));
+
+ // Make sure Content-Length: 0 header was added.
+ EXPECT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+ // Make sure Location header was included and correct.
+ std::string location;
+ EXPECT_TRUE(headers->IsRedirect(&location));
+ EXPECT_EQ(location, redirectTarget);
+ }
+}
+
+// It would be nice to also test the timeouts in HttpProxyClientSocketPool.
+
+} // namespace net
diff --git a/chromium/net/http/http_request_headers.cc b/chromium/net/http/http_request_headers.cc
new file mode 100644
index 00000000000..bf557df37b8
--- /dev/null
+++ b/chromium/net/http/http_request_headers.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 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.
+
+#include "net/http/http_request_headers.h"
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+const char HttpRequestHeaders::kGetMethod[] = "GET";
+const char HttpRequestHeaders::kAcceptCharset[] = "Accept-Charset";
+const char HttpRequestHeaders::kAcceptEncoding[] = "Accept-Encoding";
+const char HttpRequestHeaders::kAcceptLanguage[] = "Accept-Language";
+const char HttpRequestHeaders::kAuthorization[] = "Authorization";
+const char HttpRequestHeaders::kCacheControl[] = "Cache-Control";
+const char HttpRequestHeaders::kConnection[] = "Connection";
+const char HttpRequestHeaders::kContentLength[] = "Content-Length";
+const char HttpRequestHeaders::kContentType[] = "Content-Type";
+const char HttpRequestHeaders::kCookie[] = "Cookie";
+const char HttpRequestHeaders::kHost[] = "Host";
+const char HttpRequestHeaders::kIfModifiedSince[] = "If-Modified-Since";
+const char HttpRequestHeaders::kIfNoneMatch[] = "If-None-Match";
+const char HttpRequestHeaders::kIfRange[] = "If-Range";
+const char HttpRequestHeaders::kOrigin[] = "Origin";
+const char HttpRequestHeaders::kPragma[] = "Pragma";
+const char HttpRequestHeaders::kProxyAuthorization[] = "Proxy-Authorization";
+const char HttpRequestHeaders::kProxyConnection[] = "Proxy-Connection";
+const char HttpRequestHeaders::kRange[] = "Range";
+const char HttpRequestHeaders::kReferer[] = "Referer";
+const char HttpRequestHeaders::kUserAgent[] = "User-Agent";
+const char HttpRequestHeaders::kTransferEncoding[] = "Transfer-Encoding";
+
+HttpRequestHeaders::HeaderKeyValuePair::HeaderKeyValuePair() {
+}
+
+HttpRequestHeaders::HeaderKeyValuePair::HeaderKeyValuePair(
+ const base::StringPiece& key, const base::StringPiece& value)
+ : key(key.data(), key.size()), value(value.data(), value.size()) {
+}
+
+
+HttpRequestHeaders::Iterator::Iterator(const HttpRequestHeaders& headers)
+ : started_(false),
+ curr_(headers.headers_.begin()),
+ end_(headers.headers_.end()) {}
+
+HttpRequestHeaders::Iterator::~Iterator() {}
+
+bool HttpRequestHeaders::Iterator::GetNext() {
+ if (!started_) {
+ started_ = true;
+ return curr_ != end_;
+ }
+
+ if (curr_ == end_)
+ return false;
+
+ ++curr_;
+ return curr_ != end_;
+}
+
+HttpRequestHeaders::HttpRequestHeaders() {}
+HttpRequestHeaders::~HttpRequestHeaders() {}
+
+bool HttpRequestHeaders::GetHeader(const base::StringPiece& key,
+ std::string* out) const {
+ HeaderVector::const_iterator it = FindHeader(key);
+ if (it == headers_.end())
+ return false;
+ out->assign(it->value);
+ return true;
+}
+
+void HttpRequestHeaders::Clear() {
+ headers_.clear();
+}
+
+void HttpRequestHeaders::SetHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ HeaderVector::iterator it = FindHeader(key);
+ if (it != headers_.end())
+ it->value.assign(value.data(), value.size());
+ else
+ headers_.push_back(HeaderKeyValuePair(key, value));
+}
+
+void HttpRequestHeaders::SetHeaderIfMissing(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ HeaderVector::iterator it = FindHeader(key);
+ if (it == headers_.end())
+ headers_.push_back(HeaderKeyValuePair(key, value));
+}
+
+void HttpRequestHeaders::RemoveHeader(const base::StringPiece& key) {
+ HeaderVector::iterator it = FindHeader(key);
+ if (it != headers_.end())
+ headers_.erase(it);
+}
+
+void HttpRequestHeaders::AddHeaderFromString(
+ const base::StringPiece& header_line) {
+ DCHECK_EQ(std::string::npos, header_line.find("\r\n"))
+ << "\"" << header_line << "\" contains CRLF.";
+
+ const std::string::size_type key_end_index = header_line.find(":");
+ if (key_end_index == std::string::npos) {
+ LOG(DFATAL) << "\"" << header_line << "\" is missing colon delimiter.";
+ return;
+ }
+
+ if (key_end_index == 0) {
+ LOG(DFATAL) << "\"" << header_line << "\" is missing header key.";
+ return;
+ }
+
+ const base::StringPiece header_key(header_line.data(), key_end_index);
+
+ const std::string::size_type value_index = key_end_index + 1;
+
+ if (value_index < header_line.size()) {
+ std::string header_value(header_line.data() + value_index,
+ header_line.size() - value_index);
+ std::string::const_iterator header_value_begin =
+ header_value.begin();
+ std::string::const_iterator header_value_end =
+ header_value.end();
+ HttpUtil::TrimLWS(&header_value_begin, &header_value_end);
+
+ if (header_value_begin == header_value_end) {
+ // Value was all LWS.
+ SetHeader(header_key, "");
+ } else {
+ SetHeader(header_key,
+ base::StringPiece(&*header_value_begin,
+ header_value_end - header_value_begin));
+ }
+ } else if (value_index == header_line.size()) {
+ SetHeader(header_key, "");
+ } else {
+ NOTREACHED();
+ }
+}
+
+void HttpRequestHeaders::AddHeadersFromString(
+ const base::StringPiece& headers) {
+ // TODO(willchan): Consider adding more StringPiece support in string_util.h
+ // to eliminate copies.
+ std::vector<std::string> header_line_vector;
+ base::SplitStringUsingSubstr(headers.as_string(), "\r\n",
+ &header_line_vector);
+ for (std::vector<std::string>::const_iterator it = header_line_vector.begin();
+ it != header_line_vector.end(); ++it) {
+ if (!it->empty())
+ AddHeaderFromString(*it);
+ }
+}
+
+void HttpRequestHeaders::MergeFrom(const HttpRequestHeaders& other) {
+ for (HeaderVector::const_iterator it = other.headers_.begin();
+ it != other.headers_.end(); ++it ) {
+ SetHeader(it->key, it->value);
+ }
+}
+
+std::string HttpRequestHeaders::ToString() const {
+ std::string output;
+ for (HeaderVector::const_iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (!it->value.empty()) {
+ base::StringAppendF(&output, "%s: %s\r\n",
+ it->key.c_str(), it->value.c_str());
+ } else {
+ base::StringAppendF(&output, "%s:\r\n", it->key.c_str());
+ }
+ }
+ output.append("\r\n");
+ return output;
+}
+
+base::Value* HttpRequestHeaders::NetLogCallback(
+ const std::string* request_line,
+ NetLog::LogLevel /* log_level */) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("line", *request_line);
+ base::ListValue* headers = new base::ListValue();
+ for (HeaderVector::const_iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ headers->Append(
+ new base::StringValue(base::StringPrintf("%s: %s",
+ it->key.c_str(),
+ it->value.c_str())));
+ }
+ dict->Set("headers", headers);
+ return dict;
+}
+
+// static
+bool HttpRequestHeaders::FromNetLogParam(const base::Value* event_param,
+ HttpRequestHeaders* headers,
+ std::string* request_line) {
+ headers->Clear();
+ *request_line = "";
+
+ const base::DictionaryValue* dict = NULL;
+ const base::ListValue* header_list = NULL;
+
+ if (!event_param ||
+ !event_param->GetAsDictionary(&dict) ||
+ !dict->GetList("headers", &header_list) ||
+ !dict->GetString("line", request_line)) {
+ return false;
+ }
+
+ for (base::ListValue::const_iterator it = header_list->begin();
+ it != header_list->end();
+ ++it) {
+ std::string header_line;
+ if (!(*it)->GetAsString(&header_line)) {
+ headers->Clear();
+ *request_line = "";
+ return false;
+ }
+ headers->AddHeaderFromString(header_line);
+ }
+ return true;
+}
+
+HttpRequestHeaders::HeaderVector::iterator
+HttpRequestHeaders::FindHeader(const base::StringPiece& key) {
+ for (HeaderVector::iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (key.length() == it->key.length() &&
+ !base::strncasecmp(key.data(), it->key.data(), key.length()))
+ return it;
+ }
+
+ return headers_.end();
+}
+
+HttpRequestHeaders::HeaderVector::const_iterator
+HttpRequestHeaders::FindHeader(const base::StringPiece& key) const {
+ for (HeaderVector::const_iterator it = headers_.begin();
+ it != headers_.end(); ++it) {
+ if (key.length() == it->key.length() &&
+ !base::strncasecmp(key.data(), it->key.data(), key.length()))
+ return it;
+ }
+
+ return headers_.end();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_request_headers.h b/chromium/net/http/http_request_headers.h
new file mode 100644
index 00000000000..617811741ab
--- /dev/null
+++ b/chromium/net/http/http_request_headers.h
@@ -0,0 +1,177 @@
+// Copyright (c) 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.
+//
+// HttpRequestHeaders manages the request headers.
+// It maintains these in a vector of header key/value pairs, thereby maintaining
+// the order of the headers. This means that any lookups are linear time
+// operations.
+
+#ifndef NET_HTTP_HTTP_REQUEST_HEADERS_H_
+#define NET_HTTP_HTTP_REQUEST_HEADERS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+class NET_EXPORT HttpRequestHeaders {
+ public:
+ struct HeaderKeyValuePair {
+ HeaderKeyValuePair();
+ HeaderKeyValuePair(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ std::string key;
+ std::string value;
+ };
+
+ typedef std::vector<HeaderKeyValuePair> HeaderVector;
+
+ class NET_EXPORT Iterator {
+ public:
+ explicit Iterator(const HttpRequestHeaders& headers);
+ ~Iterator();
+
+ // Advances the iterator to the next header, if any. Returns true if there
+ // is a next header. Use name() and value() methods to access the resultant
+ // header name and value.
+ bool GetNext();
+
+ // These two accessors are only valid if GetNext() returned true.
+ const std::string& name() const { return curr_->key; }
+ const std::string& value() const { return curr_->value; }
+
+ private:
+ bool started_;
+ HttpRequestHeaders::HeaderVector::const_iterator curr_;
+ const HttpRequestHeaders::HeaderVector::const_iterator end_;
+
+ DISALLOW_COPY_AND_ASSIGN(Iterator);
+ };
+
+ static const char kGetMethod[];
+
+ static const char kAcceptCharset[];
+ static const char kAcceptEncoding[];
+ static const char kAcceptLanguage[];
+ static const char kAuthorization[];
+ static const char kCacheControl[];
+ static const char kConnection[];
+ static const char kContentType[];
+ static const char kCookie[];
+ static const char kContentLength[];
+ static const char kHost[];
+ static const char kIfModifiedSince[];
+ static const char kIfNoneMatch[];
+ static const char kIfRange[];
+ static const char kOrigin[];
+ static const char kPragma[];
+ static const char kProxyAuthorization[];
+ static const char kProxyConnection[];
+ static const char kRange[];
+ static const char kReferer[];
+ static const char kUserAgent[];
+ static const char kTransferEncoding[];
+
+ HttpRequestHeaders();
+ ~HttpRequestHeaders();
+
+ bool IsEmpty() const { return headers_.empty(); }
+
+ bool HasHeader(const base::StringPiece& key) const {
+ return FindHeader(key) != headers_.end();
+ }
+
+ // Gets the first header that matches |key|. If found, returns true and
+ // writes the value to |out|.
+ bool GetHeader(const base::StringPiece& key, std::string* out) const;
+
+ // Clears all the headers.
+ void Clear();
+
+ // Sets the header value pair for |key| and |value|. If |key| already exists,
+ // then the header value is modified, but the key is untouched, and the order
+ // in the vector remains the same. When comparing |key|, case is ignored.
+ void SetHeader(const base::StringPiece& key, const base::StringPiece& value);
+
+ // Sets the header value pair for |key| and |value|, if |key| does not exist.
+ // If |key| already exists, the call is a no-op.
+ // When comparing |key|, case is ignored.
+ void SetHeaderIfMissing(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ // Removes the first header that matches (case insensitive) |key|.
+ void RemoveHeader(const base::StringPiece& key);
+
+ // Parses the header from a string and calls SetHeader() with it. This string
+ // should not contain any CRLF. As per RFC2616, the format is:
+ //
+ // message-header = field-name ":" [ field-value ]
+ // field-name = token
+ // field-value = *( field-content | LWS )
+ // field-content = <the OCTETs making up the field-value
+ // and consisting of either *TEXT or combinations
+ // of token, separators, and quoted-string>
+ //
+ // AddHeaderFromString() will trim any LWS surrounding the
+ // field-content.
+ void AddHeaderFromString(const base::StringPiece& header_line);
+
+ // Same thing as AddHeaderFromString() except that |headers| is a "\r\n"
+ // delimited string of header lines. It will split up the string by "\r\n"
+ // and call AddHeaderFromString() on each.
+ void AddHeadersFromString(const base::StringPiece& headers);
+
+ // Calls SetHeader() on each header from |other|, maintaining order.
+ void MergeFrom(const HttpRequestHeaders& other);
+
+ // Copies from |other| to |this|.
+ void CopyFrom(const HttpRequestHeaders& other) {
+ *this = other;
+ }
+
+ void Swap(HttpRequestHeaders* other) {
+ headers_.swap(other->headers_);
+ }
+
+ // Serializes HttpRequestHeaders to a string representation. Joins all the
+ // header keys and values with ": ", and inserts "\r\n" between each header
+ // line, and adds the trailing "\r\n".
+ std::string ToString() const;
+
+ // Takes in the request line and returns a Value for use with the NetLog
+ // containing both the request line and all headers fields.
+ base::Value* NetLogCallback(const std::string* request_line,
+ NetLog::LogLevel log_level) const;
+
+ // Takes in a Value created by the above function, and attempts to extract the
+ // request line and create a copy of the original headers. Returns true on
+ // success. On failure, clears |headers| and |request_line|.
+ // TODO(mmenke): Long term, we want to remove this, and migrate external
+ // consumers to be NetworkDelegates.
+ static bool FromNetLogParam(const base::Value* event_param,
+ HttpRequestHeaders* headers,
+ std::string* request_line);
+
+ private:
+ HeaderVector::iterator FindHeader(const base::StringPiece& key);
+ HeaderVector::const_iterator FindHeader(const base::StringPiece& key) const;
+
+ HeaderVector headers_;
+
+ // Allow the copy construction and operator= to facilitate copying in
+ // HttpRequestHeaders.
+ // TODO(willchan): Investigate to see if we can remove the need to copy
+ // HttpRequestHeaders.
+ // DISALLOW_COPY_AND_ASSIGN(HttpRequestHeaders);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_REQUEST_HEADERS_H_
diff --git a/chromium/net/http/http_request_headers_unittest.cc b/chromium/net/http/http_request_headers_unittest.cc
new file mode 100644
index 00000000000..d33b4731011
--- /dev/null
+++ b/chromium/net/http/http_request_headers_unittest.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 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.
+
+#include "net/http/http_request_headers.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HttpRequestHeaders, HasHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ EXPECT_TRUE(headers.HasHeader("foo"));
+ EXPECT_TRUE(headers.HasHeader("Foo"));
+ EXPECT_FALSE(headers.HasHeader("Fo"));
+
+ const HttpRequestHeaders& headers_ref = headers;
+ EXPECT_TRUE(headers_ref.HasHeader("foo"));
+ EXPECT_TRUE(headers_ref.HasHeader("Foo"));
+ EXPECT_FALSE(headers_ref.HasHeader("Fo"));
+}
+
+TEST(HttpRequestHeaders, SetHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetMultipleHeaders) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Cookie-Monster", "Nom nom nom");
+ headers.SetHeader("Domo-Kun", "Loves Chrome");
+ EXPECT_EQ("Cookie-Monster: Nom nom nom\r\nDomo-Kun: Loves Chrome\r\n\r\n",
+ headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwice) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("Foo", "bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwiceCaseInsensitive) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("FoO", "Bar");
+ EXPECT_EQ("Foo: Bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderTwiceSamePrefix) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("FooBar", "smokes");
+ headers.SetHeader("Foo", "crack");
+ EXPECT_EQ("FooBar: smokes\r\nFoo: crack\r\n\r\n", headers.ToString());
+ const HttpRequestHeaders& headers_ref = headers;
+ EXPECT_EQ("FooBar: smokes\r\nFoo: crack\r\n\r\n", headers_ref.ToString());
+}
+
+TEST(HttpRequestHeaders, SetEmptyHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "Bar");
+ headers.SetHeader("Bar", "");
+ EXPECT_EQ("Foo: Bar\r\nBar:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, SetHeaderIfMissing) {
+ HttpRequestHeaders headers;
+ headers.SetHeaderIfMissing("Foo", "Bar");
+ EXPECT_EQ("Foo: Bar\r\n\r\n", headers.ToString());
+ headers.SetHeaderIfMissing("Foo", "Baz");
+ EXPECT_EQ("Foo: Bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.RemoveHeader("Foo");
+ EXPECT_EQ("\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeaderMissingHeader) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.RemoveHeader("Bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, RemoveHeaderCaseInsensitive) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("Foo", "bar");
+ headers.SetHeader("All-Your-Base", "Belongs To Chrome");
+ headers.RemoveHeader("foo");
+ EXPECT_EQ("All-Your-Base: Belongs To Chrome\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromString) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringNoLeadingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo:bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringMoreLeadingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: \t \t bar");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringTrailingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: bar \t \t ");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringLeadingTrailingWhitespace) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: \t bar\t ");
+ EXPECT_EQ("Foo: bar\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringWithEmptyValue) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo:");
+ EXPECT_EQ("Foo:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, AddHeaderFromStringWithWhitespaceValue) {
+ HttpRequestHeaders headers;
+ headers.AddHeaderFromString("Foo: ");
+ EXPECT_EQ("Foo:\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, MergeFrom) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("A", "A");
+ headers.SetHeader("B", "B");
+
+ HttpRequestHeaders headers2;
+ headers2.SetHeader("B", "b");
+ headers2.SetHeader("C", "c");
+ headers.MergeFrom(headers2);
+ EXPECT_EQ("A: A\r\nB: b\r\nC: c\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, CopyFrom) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("A", "A");
+ headers.SetHeader("B", "B");
+
+ HttpRequestHeaders headers2;
+ headers2.SetHeader("B", "b");
+ headers2.SetHeader("C", "c");
+ headers.CopyFrom(headers2);
+ EXPECT_EQ("B: b\r\nC: c\r\n\r\n", headers.ToString());
+}
+
+TEST(HttpRequestHeaders, ToNetLogParamAndBackAgain) {
+ HttpRequestHeaders headers;
+ headers.SetHeader("B", "b");
+ headers.SetHeader("A", "a");
+ std::string request_line("GET /stuff");
+
+ scoped_ptr<base::Value> event_param(
+ headers.NetLogCallback(&request_line, NetLog::LOG_ALL_BUT_BYTES));
+ HttpRequestHeaders headers2;
+ std::string request_line2;
+
+ ASSERT_TRUE(HttpRequestHeaders::FromNetLogParam(event_param.get(),
+ &headers2,
+ &request_line2));
+ EXPECT_EQ(request_line, request_line2);
+ EXPECT_EQ("B: b\r\nA: a\r\n\r\n", headers2.ToString());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_request_info.cc b/chromium/net/http/http_request_info.cc
new file mode 100644
index 00000000000..7feb4ac3d7f
--- /dev/null
+++ b/chromium/net/http/http_request_info.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2010 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/http/http_request_info.h"
+
+namespace net {
+
+HttpRequestInfo::HttpRequestInfo()
+ : upload_data_stream(NULL),
+ load_flags(0),
+ motivation(NORMAL_MOTIVATION),
+ request_id(0),
+ privacy_mode(kPrivacyModeDisabled) {
+}
+
+HttpRequestInfo::~HttpRequestInfo() {}
+
+} // namespace net
diff --git a/chromium/net/http/http_request_info.h b/chromium/net/http/http_request_info.h
new file mode 100644
index 00000000000..607bba5806d
--- /dev/null
+++ b/chromium/net/http/http_request_info.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 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_HTTP_HTTP_REQUEST_INFO_H__
+#define NET_HTTP_HTTP_REQUEST_INFO_H__
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/base/privacy_mode.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class UploadDataStream;
+
+struct NET_EXPORT HttpRequestInfo {
+ enum RequestMotivation{
+ // TODO(mbelshe): move these into Client Socket.
+ PRECONNECT_MOTIVATED, // Request was motivated by a prefetch.
+ OMNIBOX_MOTIVATED, // Request was motivated by the omnibox.
+ NORMAL_MOTIVATION, // No special motivation associated with the request.
+ EARLY_LOAD_MOTIVATED, // When browser asks a tab to open an URL, this short
+ // circuits that path (of waiting for the renderer to
+ // do the URL request), and starts loading ASAP.
+ };
+
+ HttpRequestInfo();
+ ~HttpRequestInfo();
+
+ // The requested URL.
+ GURL url;
+
+ // The method to use (GET, POST, etc.).
+ std::string method;
+
+ // Any extra request headers (including User-Agent).
+ HttpRequestHeaders extra_headers;
+
+ // Any upload data.
+ UploadDataStream* upload_data_stream;
+
+ // Any load flags (see load_flags.h).
+ int load_flags;
+
+ // The motivation behind this request.
+ RequestMotivation motivation;
+
+ // An optional globally unique identifier for this request for use by the
+ // consumer. 0 is invalid.
+ uint64 request_id;
+
+ // If enabled, then request must be sent over connection that cannot be
+ // tracked by the server (e.g. without channel id).
+ PrivacyMode privacy_mode;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_REQUEST_INFO_H__
diff --git a/chromium/net/http/http_response_body_drainer.cc b/chromium/net/http/http_response_body_drainer.cc
new file mode 100644
index 00000000000..d8f00853509
--- /dev/null
+++ b/chromium/net/http/http_response_body_drainer.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 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.
+
+#include "net/http/http_response_body_drainer.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_stream.h"
+
+namespace net {
+
+HttpResponseBodyDrainer::HttpResponseBodyDrainer(HttpStream* stream)
+ : read_size_(0),
+ stream_(stream),
+ next_state_(STATE_NONE),
+ total_read_(0),
+ session_(NULL) {}
+
+HttpResponseBodyDrainer::~HttpResponseBodyDrainer() {}
+
+void HttpResponseBodyDrainer::Start(HttpNetworkSession* session) {
+ StartWithSize(session, kDrainBodyBufferSize);
+}
+
+void HttpResponseBodyDrainer::StartWithSize(HttpNetworkSession* session,
+ int num_bytes_to_drain) {
+ DCHECK_LE(0, num_bytes_to_drain);
+ // TODO(simonjam): Consider raising this limit if we're pipelining. If we have
+ // a bunch of responses in the pipeline, we should be less willing to give up
+ // while draining.
+ if (num_bytes_to_drain > kDrainBodyBufferSize) {
+ Finish(ERR_RESPONSE_BODY_TOO_BIG_TO_DRAIN);
+ return;
+ } else if (num_bytes_to_drain == 0) {
+ Finish(OK);
+ return;
+ }
+
+ read_size_ = num_bytes_to_drain;
+ read_buf_ = new IOBuffer(read_size_);
+ next_state_ = STATE_DRAIN_RESPONSE_BODY;
+ int rv = DoLoop(OK);
+
+ if (rv == ERR_IO_PENDING) {
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kTimeoutInSeconds),
+ this,
+ &HttpResponseBodyDrainer::OnTimerFired);
+ session_ = session;
+ session->AddResponseDrainer(this);
+ return;
+ }
+
+ Finish(rv);
+}
+
+int HttpResponseBodyDrainer::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_DRAIN_RESPONSE_BODY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDrainResponseBody();
+ break;
+ case STATE_DRAIN_RESPONSE_BODY_COMPLETE:
+ rv = DoDrainResponseBodyComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int HttpResponseBodyDrainer::DoDrainResponseBody() {
+ next_state_ = STATE_DRAIN_RESPONSE_BODY_COMPLETE;
+
+ return stream_->ReadResponseBody(
+ read_buf_.get(),
+ read_size_ - total_read_,
+ base::Bind(&HttpResponseBodyDrainer::OnIOComplete,
+ base::Unretained(this)));
+}
+
+int HttpResponseBodyDrainer::DoDrainResponseBodyComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ if (result < 0)
+ return result;
+
+ total_read_ += result;
+ if (stream_->IsResponseBodyComplete())
+ return OK;
+
+ DCHECK_LE(total_read_, kDrainBodyBufferSize);
+ if (total_read_ >= kDrainBodyBufferSize)
+ return ERR_RESPONSE_BODY_TOO_BIG_TO_DRAIN;
+
+ if (result == 0)
+ return ERR_CONNECTION_CLOSED;
+
+ next_state_ = STATE_DRAIN_RESPONSE_BODY;
+ return OK;
+}
+
+void HttpResponseBodyDrainer::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ timer_.Stop();
+ Finish(rv);
+ }
+}
+
+void HttpResponseBodyDrainer::OnTimerFired() {
+ Finish(ERR_TIMED_OUT);
+}
+
+void HttpResponseBodyDrainer::Finish(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ if (session_)
+ session_->RemoveResponseDrainer(this);
+
+ if (result < 0) {
+ stream_->Close(true /* no keep-alive */);
+ } else {
+ DCHECK_EQ(OK, result);
+ stream_->Close(false /* keep-alive */);
+ }
+
+ delete this;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_response_body_drainer.h b/chromium/net/http/http_response_body_drainer.h
new file mode 100644
index 00000000000..915305cda46
--- /dev/null
+++ b/chromium/net/http/http_response_body_drainer.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 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_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_
+#define NET_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/http/http_network_session.h"
+
+namespace net {
+
+class HttpStream;
+class IOBuffer;
+
+class NET_EXPORT_PRIVATE HttpResponseBodyDrainer {
+ public:
+ // The size in bytes of the buffer we use to drain the response body that
+ // we want to throw away. The response body is typically a small page just a
+ // few hundred bytes long. We set a limit to prevent it from taking too long,
+ // since we may as well just create a new socket then.
+ static const int kDrainBodyBufferSize = 16384;
+ static const int kTimeoutInSeconds = 5;
+
+ explicit HttpResponseBodyDrainer(HttpStream* stream);
+ ~HttpResponseBodyDrainer();
+
+ // Starts reading the body until completion, or we hit the buffer limit, or we
+ // timeout. After Start(), |this| will eventually delete itself. If it
+ // doesn't complete immediately, it will add itself to |session|.
+ void Start(HttpNetworkSession* session);
+
+ // As above, but stop reading once |num_bytes_to_drain| has been reached.
+ void StartWithSize(HttpNetworkSession* session, int num_bytes_to_drain);
+
+ private:
+ enum State {
+ STATE_DRAIN_RESPONSE_BODY,
+ STATE_DRAIN_RESPONSE_BODY_COMPLETE,
+ STATE_NONE,
+ };
+
+ int DoLoop(int result);
+
+ int DoDrainResponseBody();
+ int DoDrainResponseBodyComplete(int result);
+
+ void OnIOComplete(int result);
+ void OnTimerFired();
+ void Finish(int result);
+
+ int read_size_;
+ scoped_refptr<IOBuffer> read_buf_;
+ const scoped_ptr<HttpStream> stream_;
+ State next_state_;
+ int total_read_;
+ CompletionCallback user_callback_;
+ base::OneShotTimer<HttpResponseBodyDrainer> timer_;
+ HttpNetworkSession* session_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseBodyDrainer);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_
diff --git a/chromium/net/http/http_response_body_drainer_unittest.cc b/chromium/net/http/http_response_body_drainer_unittest.cc
new file mode 100644
index 00000000000..70134cce1ea
--- /dev/null
+++ b/chromium/net/http/http_response_body_drainer_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 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.
+
+#include "net/http/http_response_body_drainer.h"
+
+#include <cstring>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMagicChunkSize = 1024;
+COMPILE_ASSERT(
+ (HttpResponseBodyDrainer::kDrainBodyBufferSize % kMagicChunkSize) == 0,
+ chunk_size_needs_to_divide_evenly_into_buffer_size);
+
+class CloseResultWaiter {
+ public:
+ CloseResultWaiter()
+ : result_(false),
+ have_result_(false),
+ waiting_for_result_(false) {}
+
+ int WaitForResult() {
+ CHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ return result_;
+ }
+
+ void set_result(bool result) {
+ result_ = result;
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ private:
+ int result_;
+ bool have_result_;
+ bool waiting_for_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(CloseResultWaiter);
+};
+
+class MockHttpStream : public HttpStream {
+ public:
+ MockHttpStream(CloseResultWaiter* result_waiter)
+ : result_waiter_(result_waiter),
+ buf_len_(0),
+ closed_(false),
+ stall_reads_forever_(false),
+ num_chunks_(0),
+ is_sync_(false),
+ is_last_chunk_zero_size_(false),
+ is_complete_(false),
+ weak_factory_(this) {}
+ virtual ~MockHttpStream() {}
+
+ // HttpStream implementation.
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual UploadProgress GetUploadProgress() const OVERRIDE {
+ return UploadProgress();
+ }
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool CanFindEndOfResponse() const OVERRIDE { return true; }
+ virtual bool IsConnectionReused() const OVERRIDE { return false; }
+ virtual void SetConnectionReused() OVERRIDE {}
+ virtual bool IsConnectionReusable() const OVERRIDE { return false; }
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {}
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE {}
+
+ // Mocked API
+ virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Close(bool not_reusable) OVERRIDE {
+ CHECK(!closed_);
+ closed_ = true;
+ result_waiter_->set_result(not_reusable);
+ }
+
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE { return is_complete_; }
+
+ virtual bool IsSpdyHttpStream() const OVERRIDE { return false; }
+
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE { return false; }
+
+ virtual void Drain(HttpNetworkSession*) OVERRIDE {}
+
+ virtual void SetPriority(RequestPriority priority) OVERRIDE {}
+
+ // Methods to tweak/observer mock behavior:
+ void set_stall_reads_forever() { stall_reads_forever_ = true; }
+
+ void set_num_chunks(int num_chunks) { num_chunks_ = num_chunks; }
+
+ void set_sync() { is_sync_ = true; }
+
+ void set_is_last_chunk_zero_size() { is_last_chunk_zero_size_ = true; }
+
+ private:
+ int ReadResponseBodyImpl(IOBuffer* buf, int buf_len);
+ void CompleteRead();
+
+ bool closed() const { return closed_; }
+
+ CloseResultWaiter* const result_waiter_;
+ scoped_refptr<IOBuffer> user_buf_;
+ CompletionCallback callback_;
+ int buf_len_;
+ bool closed_;
+ bool stall_reads_forever_;
+ int num_chunks_;
+ bool is_sync_;
+ bool is_last_chunk_zero_size_;
+ bool is_complete_;
+ base::WeakPtrFactory<MockHttpStream> weak_factory_;
+};
+
+int MockHttpStream::ReadResponseBody(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ CHECK(!callback.is_null());
+ CHECK(callback_.is_null());
+ CHECK(buf);
+
+ if (stall_reads_forever_)
+ return ERR_IO_PENDING;
+
+ if (is_complete_)
+ return ERR_UNEXPECTED;
+
+ if (!is_sync_) {
+ user_buf_ = buf;
+ buf_len_ = buf_len;
+ callback_ = callback;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockHttpStream::CompleteRead, weak_factory_.GetWeakPtr()));
+ return ERR_IO_PENDING;
+ } else {
+ return ReadResponseBodyImpl(buf, buf_len);
+ }
+}
+
+int MockHttpStream::ReadResponseBodyImpl(IOBuffer* buf, int buf_len) {
+ if (is_last_chunk_zero_size_ && num_chunks_ == 1) {
+ buf_len = 0;
+ } else {
+ if (buf_len > kMagicChunkSize)
+ buf_len = kMagicChunkSize;
+ std::memset(buf->data(), 1, buf_len);
+ }
+ num_chunks_--;
+ if (!num_chunks_)
+ is_complete_ = true;
+
+ return buf_len;
+}
+
+void MockHttpStream::CompleteRead() {
+ int result = ReadResponseBodyImpl(user_buf_.get(), buf_len_);
+ user_buf_ = NULL;
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(result);
+}
+
+class HttpResponseBodyDrainerTest : public testing::Test {
+ protected:
+ HttpResponseBodyDrainerTest()
+ : proxy_service_(ProxyService::CreateDirect()),
+ ssl_config_service_(new SSLConfigServiceDefaults),
+ http_server_properties_(new HttpServerPropertiesImpl()),
+ session_(CreateNetworkSession()),
+ mock_stream_(new MockHttpStream(&result_waiter_)),
+ drainer_(new HttpResponseBodyDrainer(mock_stream_)) {}
+
+ virtual ~HttpResponseBodyDrainerTest() {}
+
+ HttpNetworkSession* CreateNetworkSession() const {
+ HttpNetworkSession::Params params;
+ params.proxy_service = proxy_service_.get();
+ params.ssl_config_service = ssl_config_service_.get();
+ params.http_server_properties = http_server_properties_->GetWeakPtr();
+ return new HttpNetworkSession(params);
+ }
+
+ scoped_ptr<ProxyService> proxy_service_;
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ scoped_ptr<HttpServerPropertiesImpl> http_server_properties_;
+ const scoped_refptr<HttpNetworkSession> session_;
+ CloseResultWaiter result_waiter_;
+ MockHttpStream* const mock_stream_; // Owned by |drainer_|.
+ HttpResponseBodyDrainer* const drainer_; // Deletes itself.
+};
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncSingleOK) {
+ mock_stream_->set_num_chunks(1);
+ mock_stream_->set_sync();
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncOK) {
+ mock_stream_->set_num_chunks(3);
+ mock_stream_->set_sync();
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncOK) {
+ mock_stream_->set_num_chunks(3);
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+// Test the case when the final chunk is 0 bytes. This can happen when
+// the final 0-byte chunk of a chunk-encoded http response is read in a last
+// call to ReadResponseBody, after all data were returned from HttpStream.
+TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncEmptyChunk) {
+ mock_stream_->set_num_chunks(4);
+ mock_stream_->set_is_last_chunk_zero_size();
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncEmptyChunk) {
+ mock_stream_->set_num_chunks(4);
+ mock_stream_->set_sync();
+ mock_stream_->set_is_last_chunk_zero_size();
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodySizeEqualsDrainBuffer) {
+ mock_stream_->set_num_chunks(
+ HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize);
+ drainer_->Start(session_.get());
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodyTimeOut) {
+ mock_stream_->set_num_chunks(2);
+ mock_stream_->set_stall_reads_forever();
+ drainer_->Start(session_.get());
+ EXPECT_TRUE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, CancelledBySession) {
+ mock_stream_->set_num_chunks(2);
+ mock_stream_->set_stall_reads_forever();
+ drainer_->Start(session_.get());
+ // HttpNetworkSession should delete |drainer_|.
+}
+
+TEST_F(HttpResponseBodyDrainerTest, DrainBodyTooLarge) {
+ int too_many_chunks =
+ HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize;
+ too_many_chunks += 1; // Now it's too large.
+
+ mock_stream_->set_num_chunks(too_many_chunks);
+ drainer_->Start(session_.get());
+ EXPECT_TRUE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, StartBodyTooLarge) {
+ int too_many_chunks =
+ HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize;
+ too_many_chunks += 1; // Now it's too large.
+
+ mock_stream_->set_num_chunks(0);
+ drainer_->StartWithSize(session_.get(), too_many_chunks * kMagicChunkSize);
+ EXPECT_TRUE(result_waiter_.WaitForResult());
+}
+
+TEST_F(HttpResponseBodyDrainerTest, StartWithNothingToDo) {
+ mock_stream_->set_num_chunks(0);
+ drainer_->StartWithSize(session_.get(), 0);
+ EXPECT_FALSE(result_waiter_.WaitForResult());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_response_headers.cc b/chromium/net/http/http_response_headers.cc
new file mode 100644
index 00000000000..6047aa12ac8
--- /dev/null
+++ b/chromium/net/http/http_response_headers.cc
@@ -0,0 +1,1357 @@
+// Copyright (c) 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.
+
+// The rules for header parsing were borrowed from Firefox:
+// http://lxr.mozilla.org/seamonkey/source/netwerk/protocol/http/src/nsHttpResponseHead.cpp
+// The rules for parsing content-types were also borrowed from Firefox:
+// http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
+
+#include "net/http/http_response_headers.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/escape.h"
+#include "net/http/http_util.h"
+
+using base::StringPiece;
+using base::Time;
+using base::TimeDelta;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+// These headers are RFC 2616 hop-by-hop headers;
+// not to be stored by caches.
+const char* const kHopByHopResponseHeaders[] = {
+ "connection",
+ "proxy-connection",
+ "keep-alive",
+ "trailer",
+ "transfer-encoding",
+ "upgrade"
+};
+
+// These headers are challenge response headers;
+// not to be stored by caches.
+const char* const kChallengeResponseHeaders[] = {
+ "www-authenticate",
+ "proxy-authenticate"
+};
+
+// These headers are cookie setting headers;
+// not to be stored by caches or disclosed otherwise.
+const char* const kCookieResponseHeaders[] = {
+ "set-cookie",
+ "set-cookie2"
+};
+
+// By default, do not cache Strict-Transport-Security or Public-Key-Pins.
+// This avoids erroneously re-processing them on page loads from cache ---
+// they are defined to be valid only on live and error-free HTTPS
+// connections.
+const char* const kSecurityStateHeaders[] = {
+ "strict-transport-security",
+ "public-key-pins"
+};
+
+// These response headers are not copied from a 304/206 response to the cached
+// response headers. This list is based on Mozilla's nsHttpResponseHead.cpp.
+const char* const kNonUpdatedHeaders[] = {
+ "connection",
+ "proxy-connection",
+ "keep-alive",
+ "www-authenticate",
+ "proxy-authenticate",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "etag",
+ "x-frame-options",
+ "x-xss-protection",
+};
+
+// Some header prefixes mean "Don't copy this header from a 304 response.".
+// Rather than listing all the relevant headers, we can consolidate them into
+// this list:
+const char* const kNonUpdatedHeaderPrefixes[] = {
+ "content-",
+ "x-content-",
+ "x-webkit-"
+};
+
+bool ShouldUpdateHeader(const std::string::const_iterator& name_begin,
+ const std::string::const_iterator& name_end) {
+ for (size_t i = 0; i < arraysize(kNonUpdatedHeaders); ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, kNonUpdatedHeaders[i]))
+ return false;
+ }
+ for (size_t i = 0; i < arraysize(kNonUpdatedHeaderPrefixes); ++i) {
+ if (StartsWithASCII(std::string(name_begin, name_end),
+ kNonUpdatedHeaderPrefixes[i], false))
+ return false;
+ }
+ return true;
+}
+
+void CheckDoesNotHaveEmbededNulls(const std::string& str) {
+ // Care needs to be taken when adding values to the raw headers string to
+ // make sure it does not contain embeded NULLs. Any embeded '\0' may be
+ // understood as line terminators and change how header lines get tokenized.
+ CHECK(str.find('\0') == std::string::npos);
+}
+
+} // namespace
+
+struct HttpResponseHeaders::ParsedHeader {
+ // A header "continuation" contains only a subsequent value for the
+ // preceding header. (Header values are comma separated.)
+ bool is_continuation() const { return name_begin == name_end; }
+
+ std::string::const_iterator name_begin;
+ std::string::const_iterator name_end;
+ std::string::const_iterator value_begin;
+ std::string::const_iterator value_end;
+};
+
+//-----------------------------------------------------------------------------
+
+HttpResponseHeaders::HttpResponseHeaders(const std::string& raw_input)
+ : response_code_(-1) {
+ Parse(raw_input);
+
+ // The most important thing to do with this histogram is find out
+ // the existence of unusual HTTP status codes. As it happens
+ // right now, there aren't double-constructions of response headers
+ // using this constructor, so our counts should also be accurate,
+ // without instantiating the histogram in two places. It is also
+ // important that this histogram not collect data in the other
+ // constructor, which rebuilds an histogram from a pickle, since
+ // that would actually create a double call between the original
+ // HttpResponseHeader that was serialized, and initialization of the
+ // new object from that pickle.
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.HttpResponseCode",
+ HttpUtil::MapStatusCodeForHistogram(
+ response_code_),
+ // Note the third argument is only
+ // evaluated once, see macro
+ // definition for details.
+ HttpUtil::GetStatusCodesForHistogram());
+}
+
+HttpResponseHeaders::HttpResponseHeaders(const Pickle& pickle,
+ PickleIterator* iter)
+ : response_code_(-1) {
+ std::string raw_input;
+ if (pickle.ReadString(iter, &raw_input))
+ Parse(raw_input);
+}
+
+void HttpResponseHeaders::Persist(Pickle* pickle, PersistOptions options) {
+ if (options == PERSIST_RAW) {
+ pickle->WriteString(raw_headers_);
+ return; // Done.
+ }
+
+ HeaderSet filter_headers;
+
+ // Construct set of headers to filter out based on options.
+ if ((options & PERSIST_SANS_NON_CACHEABLE) == PERSIST_SANS_NON_CACHEABLE)
+ AddNonCacheableHeaders(&filter_headers);
+
+ if ((options & PERSIST_SANS_COOKIES) == PERSIST_SANS_COOKIES)
+ AddCookieHeaders(&filter_headers);
+
+ if ((options & PERSIST_SANS_CHALLENGES) == PERSIST_SANS_CHALLENGES)
+ AddChallengeHeaders(&filter_headers);
+
+ if ((options & PERSIST_SANS_HOP_BY_HOP) == PERSIST_SANS_HOP_BY_HOP)
+ AddHopByHopHeaders(&filter_headers);
+
+ if ((options & PERSIST_SANS_RANGES) == PERSIST_SANS_RANGES)
+ AddHopContentRangeHeaders(&filter_headers);
+
+ if ((options & PERSIST_SANS_SECURITY_STATE) == PERSIST_SANS_SECURITY_STATE)
+ AddSecurityStateHeaders(&filter_headers);
+
+ std::string blob;
+ blob.reserve(raw_headers_.size());
+
+ // This copies the status line w/ terminator null.
+ // Note raw_headers_ has embedded nulls instead of \n,
+ // so this just copies the first header line.
+ blob.assign(raw_headers_.c_str(), strlen(raw_headers_.c_str()) + 1);
+
+ for (size_t i = 0; i < parsed_.size(); ++i) {
+ DCHECK(!parsed_[i].is_continuation());
+
+ // Locate the start of the next header.
+ size_t k = i;
+ while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
+ --k;
+
+ std::string header_name(parsed_[i].name_begin, parsed_[i].name_end);
+ StringToLowerASCII(&header_name);
+
+ if (filter_headers.find(header_name) == filter_headers.end()) {
+ // Make sure there is a null after the value.
+ blob.append(parsed_[i].name_begin, parsed_[k].value_end);
+ blob.push_back('\0');
+ }
+
+ i = k;
+ }
+ blob.push_back('\0');
+
+ pickle->WriteString(blob);
+}
+
+void HttpResponseHeaders::Update(const HttpResponseHeaders& new_headers) {
+ DCHECK(new_headers.response_code() == 304 ||
+ new_headers.response_code() == 206);
+
+ // Copy up to the null byte. This just copies the status line.
+ std::string new_raw_headers(raw_headers_.c_str());
+ new_raw_headers.push_back('\0');
+
+ HeaderSet updated_headers;
+
+ // NOTE: we write the new headers then the old headers for convenience. The
+ // order should not matter.
+
+ // Figure out which headers we want to take from new_headers:
+ for (size_t i = 0; i < new_headers.parsed_.size(); ++i) {
+ const HeaderList& new_parsed = new_headers.parsed_;
+
+ DCHECK(!new_parsed[i].is_continuation());
+
+ // Locate the start of the next header.
+ size_t k = i;
+ while (++k < new_parsed.size() && new_parsed[k].is_continuation()) {}
+ --k;
+
+ const std::string::const_iterator& name_begin = new_parsed[i].name_begin;
+ const std::string::const_iterator& name_end = new_parsed[i].name_end;
+ if (ShouldUpdateHeader(name_begin, name_end)) {
+ std::string name(name_begin, name_end);
+ StringToLowerASCII(&name);
+ updated_headers.insert(name);
+
+ // Preserve this header line in the merged result, making sure there is
+ // a null after the value.
+ new_raw_headers.append(name_begin, new_parsed[k].value_end);
+ new_raw_headers.push_back('\0');
+ }
+
+ i = k;
+ }
+
+ // Now, build the new raw headers.
+ MergeWithHeaders(new_raw_headers, updated_headers);
+}
+
+void HttpResponseHeaders::MergeWithHeaders(const std::string& raw_headers,
+ const HeaderSet& headers_to_remove) {
+ std::string new_raw_headers(raw_headers);
+ for (size_t i = 0; i < parsed_.size(); ++i) {
+ DCHECK(!parsed_[i].is_continuation());
+
+ // Locate the start of the next header.
+ size_t k = i;
+ while (++k < parsed_.size() && parsed_[k].is_continuation()) {}
+ --k;
+
+ std::string name(parsed_[i].name_begin, parsed_[i].name_end);
+ StringToLowerASCII(&name);
+ if (headers_to_remove.find(name) == headers_to_remove.end()) {
+ // It's ok to preserve this header in the final result.
+ new_raw_headers.append(parsed_[i].name_begin, parsed_[k].value_end);
+ new_raw_headers.push_back('\0');
+ }
+
+ i = k;
+ }
+ new_raw_headers.push_back('\0');
+
+ // Make this object hold the new data.
+ raw_headers_.clear();
+ parsed_.clear();
+ Parse(new_raw_headers);
+}
+
+void HttpResponseHeaders::RemoveHeader(const std::string& name) {
+ // Copy up to the null byte. This just copies the status line.
+ std::string new_raw_headers(raw_headers_.c_str());
+ new_raw_headers.push_back('\0');
+
+ std::string lowercase_name(name);
+ StringToLowerASCII(&lowercase_name);
+ HeaderSet to_remove;
+ to_remove.insert(lowercase_name);
+ MergeWithHeaders(new_raw_headers, to_remove);
+}
+
+void HttpResponseHeaders::RemoveHeaderLine(const std::string& name,
+ const std::string& value) {
+ std::string name_lowercase(name);
+ StringToLowerASCII(&name_lowercase);
+
+ std::string new_raw_headers(GetStatusLine());
+ new_raw_headers.push_back('\0');
+
+ new_raw_headers.reserve(raw_headers_.size());
+
+ void* iter = NULL;
+ std::string old_header_name;
+ std::string old_header_value;
+ while (EnumerateHeaderLines(&iter, &old_header_name, &old_header_value)) {
+ std::string old_header_name_lowercase(name);
+ StringToLowerASCII(&old_header_name_lowercase);
+
+ if (name_lowercase == old_header_name_lowercase &&
+ value == old_header_value)
+ continue;
+
+ new_raw_headers.append(old_header_name);
+ new_raw_headers.push_back(':');
+ new_raw_headers.push_back(' ');
+ new_raw_headers.append(old_header_value);
+ new_raw_headers.push_back('\0');
+ }
+ new_raw_headers.push_back('\0');
+
+ // Make this object hold the new data.
+ raw_headers_.clear();
+ parsed_.clear();
+ Parse(new_raw_headers);
+}
+
+void HttpResponseHeaders::AddHeader(const std::string& header) {
+ CheckDoesNotHaveEmbededNulls(header);
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 1]);
+ // Don't copy the last null.
+ std::string new_raw_headers(raw_headers_, 0, raw_headers_.size() - 1);
+ new_raw_headers.append(header);
+ new_raw_headers.push_back('\0');
+ new_raw_headers.push_back('\0');
+
+ // Make this object hold the new data.
+ raw_headers_.clear();
+ parsed_.clear();
+ Parse(new_raw_headers);
+}
+
+void HttpResponseHeaders::ReplaceStatusLine(const std::string& new_status) {
+ CheckDoesNotHaveEmbededNulls(new_status);
+ // Copy up to the null byte. This just copies the status line.
+ std::string new_raw_headers(new_status);
+ new_raw_headers.push_back('\0');
+
+ HeaderSet empty_to_remove;
+ MergeWithHeaders(new_raw_headers, empty_to_remove);
+}
+
+void HttpResponseHeaders::Parse(const std::string& raw_input) {
+ raw_headers_.reserve(raw_input.size());
+
+ // ParseStatusLine adds a normalized status line to raw_headers_
+ std::string::const_iterator line_begin = raw_input.begin();
+ std::string::const_iterator line_end =
+ std::find(line_begin, raw_input.end(), '\0');
+ // has_headers = true, if there is any data following the status line.
+ // Used by ParseStatusLine() to decide if a HTTP/0.9 is really a HTTP/1.0.
+ bool has_headers = (line_end != raw_input.end() &&
+ (line_end + 1) != raw_input.end() &&
+ *(line_end + 1) != '\0');
+ ParseStatusLine(line_begin, line_end, has_headers);
+ raw_headers_.push_back('\0'); // Terminate status line with a null.
+
+ if (line_end == raw_input.end()) {
+ raw_headers_.push_back('\0'); // Ensure the headers end with a double null.
+
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 1]);
+ return;
+ }
+
+ // Including a terminating null byte.
+ size_t status_line_len = raw_headers_.size();
+
+ // Now, we add the rest of the raw headers to raw_headers_, and begin parsing
+ // it (to populate our parsed_ vector).
+ raw_headers_.append(line_end + 1, raw_input.end());
+
+ // Ensure the headers end with a double null.
+ while (raw_headers_.size() < 2 ||
+ raw_headers_[raw_headers_.size() - 2] != '\0' ||
+ raw_headers_[raw_headers_.size() - 1] != '\0') {
+ raw_headers_.push_back('\0');
+ }
+
+ // Adjust to point at the null byte following the status line
+ line_end = raw_headers_.begin() + status_line_len - 1;
+
+ HttpUtil::HeadersIterator headers(line_end + 1, raw_headers_.end(),
+ std::string(1, '\0'));
+ while (headers.GetNext()) {
+ AddHeader(headers.name_begin(),
+ headers.name_end(),
+ headers.values_begin(),
+ headers.values_end());
+ }
+
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 2]);
+ DCHECK_EQ('\0', raw_headers_[raw_headers_.size() - 1]);
+}
+
+// Append all of our headers to the final output string.
+void HttpResponseHeaders::GetNormalizedHeaders(std::string* output) const {
+ // copy up to the null byte. this just copies the status line.
+ output->assign(raw_headers_.c_str());
+
+ // headers may appear multiple times (not necessarily in succession) in the
+ // header data, so we build a map from header name to generated header lines.
+ // to preserve the order of the original headers, the actual values are kept
+ // in a separate list. finally, the list of headers is flattened to form
+ // the normalized block of headers.
+ //
+ // NOTE: We take special care to preserve the whitespace around any commas
+ // that may occur in the original response headers. Because our consumer may
+ // be a web app, we cannot be certain of the semantics of commas despite the
+ // fact that RFC 2616 says that they should be regarded as value separators.
+ //
+ typedef base::hash_map<std::string, size_t> HeadersMap;
+ HeadersMap headers_map;
+ HeadersMap::iterator iter = headers_map.end();
+
+ std::vector<std::string> headers;
+
+ for (size_t i = 0; i < parsed_.size(); ++i) {
+ DCHECK(!parsed_[i].is_continuation());
+
+ std::string name(parsed_[i].name_begin, parsed_[i].name_end);
+ std::string lower_name = StringToLowerASCII(name);
+
+ iter = headers_map.find(lower_name);
+ if (iter == headers_map.end()) {
+ iter = headers_map.insert(
+ HeadersMap::value_type(lower_name, headers.size())).first;
+ headers.push_back(name + ": ");
+ } else {
+ headers[iter->second].append(", ");
+ }
+
+ std::string::const_iterator value_begin = parsed_[i].value_begin;
+ std::string::const_iterator value_end = parsed_[i].value_end;
+ while (++i < parsed_.size() && parsed_[i].is_continuation())
+ value_end = parsed_[i].value_end;
+ --i;
+
+ headers[iter->second].append(value_begin, value_end);
+ }
+
+ for (size_t i = 0; i < headers.size(); ++i) {
+ output->push_back('\n');
+ output->append(headers[i]);
+ }
+
+ output->push_back('\n');
+}
+
+bool HttpResponseHeaders::GetNormalizedHeader(const std::string& name,
+ std::string* value) const {
+ // If you hit this assertion, please use EnumerateHeader instead!
+ DCHECK(!HttpUtil::IsNonCoalescingHeader(name));
+
+ value->clear();
+
+ bool found = false;
+ size_t i = 0;
+ while (i < parsed_.size()) {
+ i = FindHeader(i, name);
+ if (i == std::string::npos)
+ break;
+
+ found = true;
+
+ if (!value->empty())
+ value->append(", ");
+
+ std::string::const_iterator value_begin = parsed_[i].value_begin;
+ std::string::const_iterator value_end = parsed_[i].value_end;
+ while (++i < parsed_.size() && parsed_[i].is_continuation())
+ value_end = parsed_[i].value_end;
+ value->append(value_begin, value_end);
+ }
+
+ return found;
+}
+
+std::string HttpResponseHeaders::GetStatusLine() const {
+ // copy up to the null byte.
+ return std::string(raw_headers_.c_str());
+}
+
+std::string HttpResponseHeaders::GetStatusText() const {
+ // GetStatusLine() is already normalized, so it has the format:
+ // <http_version> SP <response_code> SP <status_text>
+ std::string status_text = GetStatusLine();
+ std::string::const_iterator begin = status_text.begin();
+ std::string::const_iterator end = status_text.end();
+ for (int i = 0; i < 2; ++i)
+ begin = std::find(begin, end, ' ') + 1;
+ return std::string(begin, end);
+}
+
+bool HttpResponseHeaders::EnumerateHeaderLines(void** iter,
+ std::string* name,
+ std::string* value) const {
+ size_t i = reinterpret_cast<size_t>(*iter);
+ if (i == parsed_.size())
+ return false;
+
+ DCHECK(!parsed_[i].is_continuation());
+
+ name->assign(parsed_[i].name_begin, parsed_[i].name_end);
+
+ std::string::const_iterator value_begin = parsed_[i].value_begin;
+ std::string::const_iterator value_end = parsed_[i].value_end;
+ while (++i < parsed_.size() && parsed_[i].is_continuation())
+ value_end = parsed_[i].value_end;
+
+ value->assign(value_begin, value_end);
+
+ *iter = reinterpret_cast<void*>(i);
+ return true;
+}
+
+bool HttpResponseHeaders::EnumerateHeader(void** iter,
+ const base::StringPiece& name,
+ std::string* value) const {
+ size_t i;
+ if (!iter || !*iter) {
+ i = FindHeader(0, name);
+ } else {
+ i = reinterpret_cast<size_t>(*iter);
+ if (i >= parsed_.size()) {
+ i = std::string::npos;
+ } else if (!parsed_[i].is_continuation()) {
+ i = FindHeader(i, name);
+ }
+ }
+
+ if (i == std::string::npos) {
+ value->clear();
+ return false;
+ }
+
+ if (iter)
+ *iter = reinterpret_cast<void*>(i + 1);
+ value->assign(parsed_[i].value_begin, parsed_[i].value_end);
+ return true;
+}
+
+bool HttpResponseHeaders::HasHeaderValue(const base::StringPiece& name,
+ const base::StringPiece& value) const {
+ // The value has to be an exact match. This is important since
+ // 'cache-control: no-cache' != 'cache-control: no-cache="foo"'
+ void* iter = NULL;
+ std::string temp;
+ while (EnumerateHeader(&iter, name, &temp)) {
+ if (value.size() == temp.size() &&
+ std::equal(temp.begin(), temp.end(), value.begin(),
+ base::CaseInsensitiveCompare<char>()))
+ return true;
+ }
+ return false;
+}
+
+bool HttpResponseHeaders::HasHeader(const base::StringPiece& name) const {
+ return FindHeader(0, name) != std::string::npos;
+}
+
+HttpResponseHeaders::HttpResponseHeaders() : response_code_(-1) {
+}
+
+HttpResponseHeaders::~HttpResponseHeaders() {
+}
+
+// Note: this implementation implicitly assumes that line_end points at a valid
+// sentinel character (such as '\0').
+// static
+HttpVersion HttpResponseHeaders::ParseVersion(
+ std::string::const_iterator line_begin,
+ std::string::const_iterator line_end) {
+ std::string::const_iterator p = line_begin;
+
+ // RFC2616 sec 3.1: HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
+ // TODO: (1*DIGIT apparently means one or more digits, but we only handle 1).
+ // TODO: handle leading zeros, which is allowed by the rfc1616 sec 3.1.
+
+ if ((line_end - p < 4) || !LowerCaseEqualsASCII(p, p + 4, "http")) {
+ DVLOG(1) << "missing status line";
+ return HttpVersion();
+ }
+
+ p += 4;
+
+ if (p >= line_end || *p != '/') {
+ DVLOG(1) << "missing version";
+ return HttpVersion();
+ }
+
+ std::string::const_iterator dot = std::find(p, line_end, '.');
+ if (dot == line_end) {
+ DVLOG(1) << "malformed version";
+ return HttpVersion();
+ }
+
+ ++p; // from / to first digit.
+ ++dot; // from . to second digit.
+
+ if (!(*p >= '0' && *p <= '9' && *dot >= '0' && *dot <= '9')) {
+ DVLOG(1) << "malformed version number";
+ return HttpVersion();
+ }
+
+ uint16 major = *p - '0';
+ uint16 minor = *dot - '0';
+
+ return HttpVersion(major, minor);
+}
+
+// Note: this implementation implicitly assumes that line_end points at a valid
+// sentinel character (such as '\0').
+void HttpResponseHeaders::ParseStatusLine(
+ std::string::const_iterator line_begin,
+ std::string::const_iterator line_end,
+ bool has_headers) {
+ // Extract the version number
+ parsed_http_version_ = ParseVersion(line_begin, line_end);
+
+ // Clamp the version number to one of: {0.9, 1.0, 1.1}
+ if (parsed_http_version_ == HttpVersion(0, 9) && !has_headers) {
+ http_version_ = HttpVersion(0, 9);
+ raw_headers_ = "HTTP/0.9";
+ } else if (parsed_http_version_ >= HttpVersion(1, 1)) {
+ http_version_ = HttpVersion(1, 1);
+ raw_headers_ = "HTTP/1.1";
+ } else {
+ // Treat everything else like HTTP 1.0
+ http_version_ = HttpVersion(1, 0);
+ raw_headers_ = "HTTP/1.0";
+ }
+ if (parsed_http_version_ != http_version_) {
+ DVLOG(1) << "assuming HTTP/" << http_version_.major_value() << "."
+ << http_version_.minor_value();
+ }
+
+ // TODO(eroman): this doesn't make sense if ParseVersion failed.
+ std::string::const_iterator p = std::find(line_begin, line_end, ' ');
+
+ if (p == line_end) {
+ DVLOG(1) << "missing response status; assuming 200 OK";
+ raw_headers_.append(" 200 OK");
+ response_code_ = 200;
+ return;
+ }
+
+ // Skip whitespace.
+ while (*p == ' ')
+ ++p;
+
+ std::string::const_iterator code = p;
+ while (*p >= '0' && *p <= '9')
+ ++p;
+
+ if (p == code) {
+ DVLOG(1) << "missing response status number; assuming 200";
+ raw_headers_.append(" 200 OK");
+ response_code_ = 200;
+ return;
+ }
+ raw_headers_.push_back(' ');
+ raw_headers_.append(code, p);
+ raw_headers_.push_back(' ');
+ base::StringToInt(StringPiece(code, p), &response_code_);
+
+ // Skip whitespace.
+ while (*p == ' ')
+ ++p;
+
+ // Trim trailing whitespace.
+ while (line_end > p && line_end[-1] == ' ')
+ --line_end;
+
+ if (p == line_end) {
+ DVLOG(1) << "missing response status text; assuming OK";
+ // Not super critical what we put here. Just use "OK"
+ // even if it isn't descriptive of response_code_.
+ raw_headers_.append("OK");
+ } else {
+ raw_headers_.append(p, line_end);
+ }
+}
+
+size_t HttpResponseHeaders::FindHeader(size_t from,
+ const base::StringPiece& search) const {
+ for (size_t i = from; i < parsed_.size(); ++i) {
+ if (parsed_[i].is_continuation())
+ continue;
+ const std::string::const_iterator& name_begin = parsed_[i].name_begin;
+ const std::string::const_iterator& name_end = parsed_[i].name_end;
+ if (static_cast<size_t>(name_end - name_begin) == search.size() &&
+ std::equal(name_begin, name_end, search.begin(),
+ base::CaseInsensitiveCompare<char>()))
+ return i;
+ }
+
+ return std::string::npos;
+}
+
+void HttpResponseHeaders::AddHeader(std::string::const_iterator name_begin,
+ std::string::const_iterator name_end,
+ std::string::const_iterator values_begin,
+ std::string::const_iterator values_end) {
+ // If the header can be coalesced, then we should split it up.
+ if (values_begin == values_end ||
+ HttpUtil::IsNonCoalescingHeader(name_begin, name_end)) {
+ AddToParsed(name_begin, name_end, values_begin, values_end);
+ } else {
+ HttpUtil::ValuesIterator it(values_begin, values_end, ',');
+ while (it.GetNext()) {
+ AddToParsed(name_begin, name_end, it.value_begin(), it.value_end());
+ // clobber these so that subsequent values are treated as continuations
+ name_begin = name_end = raw_headers_.end();
+ }
+ }
+}
+
+void HttpResponseHeaders::AddToParsed(std::string::const_iterator name_begin,
+ std::string::const_iterator name_end,
+ std::string::const_iterator value_begin,
+ std::string::const_iterator value_end) {
+ ParsedHeader header;
+ header.name_begin = name_begin;
+ header.name_end = name_end;
+ header.value_begin = value_begin;
+ header.value_end = value_end;
+ parsed_.push_back(header);
+}
+
+void HttpResponseHeaders::AddNonCacheableHeaders(HeaderSet* result) const {
+ // Add server specified transients. Any 'cache-control: no-cache="foo,bar"'
+ // headers present in the response specify additional headers that we should
+ // not store in the cache.
+ const char kCacheControl[] = "cache-control";
+ const char kPrefix[] = "no-cache=\"";
+ const size_t kPrefixLen = sizeof(kPrefix) - 1;
+
+ std::string value;
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, kCacheControl, &value)) {
+ // If the value is smaller than the prefix and a terminal quote, skip
+ // it.
+ if (value.size() <= kPrefixLen ||
+ value.compare(0, kPrefixLen, kPrefix) != 0) {
+ continue;
+ }
+ // if it doesn't end with a quote, then treat as malformed
+ if (value[value.size()-1] != '\"')
+ continue;
+
+ // process the value as a comma-separated list of items. Each
+ // item can be wrapped by linear white space.
+ std::string::const_iterator item = value.begin() + kPrefixLen;
+ std::string::const_iterator end = value.end() - 1;
+ while (item != end) {
+ // Find the comma to compute the length of the current item,
+ // and the position of the next one.
+ std::string::const_iterator item_next = std::find(item, end, ',');
+ std::string::const_iterator item_end = end;
+ if (item_next != end) {
+ // Skip over comma for next position.
+ item_end = item_next;
+ item_next++;
+ }
+ // trim off leading and trailing whitespace in this item.
+ HttpUtil::TrimLWS(&item, &item_end);
+
+ // assuming the header is not empty, lowercase and insert into set
+ if (item_end > item) {
+ std::string name(&*item, item_end - item);
+ StringToLowerASCII(&name);
+ result->insert(name);
+ }
+
+ // Continue to next item.
+ item = item_next;
+ }
+ }
+}
+
+void HttpResponseHeaders::AddHopByHopHeaders(HeaderSet* result) {
+ for (size_t i = 0; i < arraysize(kHopByHopResponseHeaders); ++i)
+ result->insert(std::string(kHopByHopResponseHeaders[i]));
+}
+
+void HttpResponseHeaders::AddCookieHeaders(HeaderSet* result) {
+ for (size_t i = 0; i < arraysize(kCookieResponseHeaders); ++i)
+ result->insert(std::string(kCookieResponseHeaders[i]));
+}
+
+void HttpResponseHeaders::AddChallengeHeaders(HeaderSet* result) {
+ for (size_t i = 0; i < arraysize(kChallengeResponseHeaders); ++i)
+ result->insert(std::string(kChallengeResponseHeaders[i]));
+}
+
+void HttpResponseHeaders::AddHopContentRangeHeaders(HeaderSet* result) {
+ result->insert("content-range");
+}
+
+void HttpResponseHeaders::AddSecurityStateHeaders(HeaderSet* result) {
+ for (size_t i = 0; i < arraysize(kSecurityStateHeaders); ++i)
+ result->insert(std::string(kSecurityStateHeaders[i]));
+}
+
+void HttpResponseHeaders::GetMimeTypeAndCharset(std::string* mime_type,
+ std::string* charset) const {
+ mime_type->clear();
+ charset->clear();
+
+ std::string name = "content-type";
+ std::string value;
+
+ bool had_charset = false;
+
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, name, &value))
+ HttpUtil::ParseContentType(value, mime_type, charset, &had_charset, NULL);
+}
+
+bool HttpResponseHeaders::GetMimeType(std::string* mime_type) const {
+ std::string unused;
+ GetMimeTypeAndCharset(mime_type, &unused);
+ return !mime_type->empty();
+}
+
+bool HttpResponseHeaders::GetCharset(std::string* charset) const {
+ std::string unused;
+ GetMimeTypeAndCharset(&unused, charset);
+ return !charset->empty();
+}
+
+bool HttpResponseHeaders::IsRedirect(std::string* location) const {
+ if (!IsRedirectResponseCode(response_code_))
+ return false;
+
+ // If we lack a Location header, then we can't treat this as a redirect.
+ // We assume that the first non-empty location value is the target URL that
+ // we want to follow. TODO(darin): Is this consistent with other browsers?
+ size_t i = std::string::npos;
+ do {
+ i = FindHeader(++i, "location");
+ if (i == std::string::npos)
+ return false;
+ // If the location value is empty, then it doesn't count.
+ } while (parsed_[i].value_begin == parsed_[i].value_end);
+
+ if (location) {
+ // Escape any non-ASCII characters to preserve them. The server should
+ // only be returning ASCII here, but for compat we need to do this.
+ *location = EscapeNonASCII(
+ std::string(parsed_[i].value_begin, parsed_[i].value_end));
+ }
+
+ return true;
+}
+
+// static
+bool HttpResponseHeaders::IsRedirectResponseCode(int response_code) {
+ // Users probably want to see 300 (multiple choice) pages, so we don't count
+ // them as redirects that need to be followed.
+ return (response_code == 301 ||
+ response_code == 302 ||
+ response_code == 303 ||
+ response_code == 307);
+}
+
+// From RFC 2616 section 13.2.4:
+//
+// The calculation to determine if a response has expired is quite simple:
+//
+// response_is_fresh = (freshness_lifetime > current_age)
+//
+// Of course, there are other factors that can force a response to always be
+// validated or re-fetched.
+//
+bool HttpResponseHeaders::RequiresValidation(const Time& request_time,
+ const Time& response_time,
+ const Time& current_time) const {
+ TimeDelta lifetime =
+ GetFreshnessLifetime(response_time);
+ if (lifetime == TimeDelta())
+ return true;
+
+ return lifetime <= GetCurrentAge(request_time, response_time, current_time);
+}
+
+// From RFC 2616 section 13.2.4:
+//
+// The max-age directive takes priority over Expires, so if max-age is present
+// in a response, the calculation is simply:
+//
+// freshness_lifetime = max_age_value
+//
+// Otherwise, if Expires is present in the response, the calculation is:
+//
+// freshness_lifetime = expires_value - date_value
+//
+// Note that neither of these calculations is vulnerable to clock skew, since
+// all of the information comes from the origin server.
+//
+// Also, if the response does have a Last-Modified time, the heuristic
+// expiration value SHOULD be no more than some fraction of the interval since
+// that time. A typical setting of this fraction might be 10%:
+//
+// freshness_lifetime = (date_value - last_modified_value) * 0.10
+//
+TimeDelta HttpResponseHeaders::GetFreshnessLifetime(
+ const Time& response_time) const {
+ // Check for headers that force a response to never be fresh. For backwards
+ // compat, we treat "Pragma: no-cache" as a synonym for "Cache-Control:
+ // no-cache" even though RFC 2616 does not specify it.
+ if (HasHeaderValue("cache-control", "no-cache") ||
+ HasHeaderValue("cache-control", "no-store") ||
+ HasHeaderValue("pragma", "no-cache") ||
+ HasHeaderValue("vary", "*")) // see RFC 2616 section 13.6
+ return TimeDelta(); // not fresh
+
+ // NOTE: "Cache-Control: max-age" overrides Expires, so we only check the
+ // Expires header after checking for max-age in GetFreshnessLifetime. This
+ // is important since "Expires: <date in the past>" means not fresh, but
+ // it should not trump a max-age value.
+
+ TimeDelta max_age_value;
+ if (GetMaxAgeValue(&max_age_value))
+ return max_age_value;
+
+ // If there is no Date header, then assume that the server response was
+ // generated at the time when we received the response.
+ Time date_value;
+ if (!GetDateValue(&date_value))
+ date_value = response_time;
+
+ Time expires_value;
+ if (GetExpiresValue(&expires_value)) {
+ // The expires value can be a date in the past!
+ if (expires_value > date_value)
+ return expires_value - date_value;
+
+ return TimeDelta(); // not fresh
+ }
+
+ // From RFC 2616 section 13.4:
+ //
+ // A response received with a status code of 200, 203, 206, 300, 301 or 410
+ // MAY be stored by a cache and used in reply to a subsequent request,
+ // subject to the expiration mechanism, unless a cache-control directive
+ // prohibits caching.
+ // ...
+ // A response received with any other status code (e.g. status codes 302
+ // and 307) MUST NOT be returned in a reply to a subsequent request unless
+ // there are cache-control directives or another header(s) that explicitly
+ // allow it.
+ //
+ // From RFC 2616 section 14.9.4:
+ //
+ // When the must-revalidate directive is present in a response received by
+ // a cache, that cache MUST NOT use the entry after it becomes stale to
+ // respond to a subsequent request without first revalidating it with the
+ // origin server. (I.e., the cache MUST do an end-to-end revalidation every
+ // time, if, based solely on the origin server's Expires or max-age value,
+ // the cached response is stale.)
+ //
+ if ((response_code_ == 200 || response_code_ == 203 ||
+ response_code_ == 206) &&
+ !HasHeaderValue("cache-control", "must-revalidate")) {
+ // TODO(darin): Implement a smarter heuristic.
+ Time last_modified_value;
+ if (GetLastModifiedValue(&last_modified_value)) {
+ // The last-modified value can be a date in the past!
+ if (last_modified_value <= date_value)
+ return (date_value - last_modified_value) / 10;
+ }
+ }
+
+ // These responses are implicitly fresh (unless otherwise overruled):
+ if (response_code_ == 300 || response_code_ == 301 || response_code_ == 410)
+ return TimeDelta::FromMicroseconds(kint64max);
+
+ return TimeDelta(); // not fresh
+}
+
+// From RFC 2616 section 13.2.3:
+//
+// Summary of age calculation algorithm, when a cache receives a response:
+//
+// /*
+// * age_value
+// * is the value of Age: header received by the cache with
+// * this response.
+// * date_value
+// * is the value of the origin server's Date: header
+// * request_time
+// * is the (local) time when the cache made the request
+// * that resulted in this cached response
+// * response_time
+// * is the (local) time when the cache received the
+// * response
+// * now
+// * is the current (local) time
+// */
+// apparent_age = max(0, response_time - date_value);
+// corrected_received_age = max(apparent_age, age_value);
+// response_delay = response_time - request_time;
+// corrected_initial_age = corrected_received_age + response_delay;
+// resident_time = now - response_time;
+// current_age = corrected_initial_age + resident_time;
+//
+TimeDelta HttpResponseHeaders::GetCurrentAge(const Time& request_time,
+ const Time& response_time,
+ const Time& current_time) const {
+ // If there is no Date header, then assume that the server response was
+ // generated at the time when we received the response.
+ Time date_value;
+ if (!GetDateValue(&date_value))
+ date_value = response_time;
+
+ // If there is no Age header, then assume age is zero. GetAgeValue does not
+ // modify its out param if the value does not exist.
+ TimeDelta age_value;
+ GetAgeValue(&age_value);
+
+ TimeDelta apparent_age = std::max(TimeDelta(), response_time - date_value);
+ TimeDelta corrected_received_age = std::max(apparent_age, age_value);
+ TimeDelta response_delay = response_time - request_time;
+ TimeDelta corrected_initial_age = corrected_received_age + response_delay;
+ TimeDelta resident_time = current_time - response_time;
+ TimeDelta current_age = corrected_initial_age + resident_time;
+
+ return current_age;
+}
+
+bool HttpResponseHeaders::GetMaxAgeValue(TimeDelta* result) const {
+ std::string name = "cache-control";
+ std::string value;
+
+ const char kMaxAgePrefix[] = "max-age=";
+ const size_t kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1;
+
+ void* iter = NULL;
+ while (EnumerateHeader(&iter, name, &value)) {
+ if (value.size() > kMaxAgePrefixLen) {
+ if (LowerCaseEqualsASCII(value.begin(),
+ value.begin() + kMaxAgePrefixLen,
+ kMaxAgePrefix)) {
+ int64 seconds;
+ base::StringToInt64(StringPiece(value.begin() + kMaxAgePrefixLen,
+ value.end()),
+ &seconds);
+ *result = TimeDelta::FromSeconds(seconds);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool HttpResponseHeaders::GetAgeValue(TimeDelta* result) const {
+ std::string value;
+ if (!EnumerateHeader(NULL, "Age", &value))
+ return false;
+
+ int64 seconds;
+ base::StringToInt64(value, &seconds);
+ *result = TimeDelta::FromSeconds(seconds);
+ return true;
+}
+
+bool HttpResponseHeaders::GetDateValue(Time* result) const {
+ return GetTimeValuedHeader("Date", result);
+}
+
+bool HttpResponseHeaders::GetLastModifiedValue(Time* result) const {
+ return GetTimeValuedHeader("Last-Modified", result);
+}
+
+bool HttpResponseHeaders::GetExpiresValue(Time* result) const {
+ return GetTimeValuedHeader("Expires", result);
+}
+
+bool HttpResponseHeaders::GetTimeValuedHeader(const std::string& name,
+ Time* result) const {
+ std::string value;
+ if (!EnumerateHeader(NULL, name, &value))
+ return false;
+
+ // When parsing HTTP dates it's beneficial to default to GMT because:
+ // 1. RFC2616 3.3.1 says times should always be specified in GMT
+ // 2. Only counter-example incorrectly appended "UTC" (crbug.com/153759)
+ // 3. When adjusting cookie expiration times for clock skew
+ // (crbug.com/135131) this better matches our cookie expiration
+ // time parser which ignores timezone specifiers and assumes GMT.
+ // 4. This is exactly what Firefox does.
+ // TODO(pauljensen): The ideal solution would be to return false if the
+ // timezone could not be understood so as to avoid makeing other calculations
+ // based on an incorrect time. This would require modifying the time
+ // library or duplicating the code. (http://crbug.com/158327)
+ return Time::FromUTCString(value.c_str(), result);
+}
+
+bool HttpResponseHeaders::IsKeepAlive() const {
+ if (http_version_ < HttpVersion(1, 0))
+ return false;
+
+ // NOTE: It is perhaps risky to assume that a Proxy-Connection header is
+ // meaningful when we don't know that this response was from a proxy, but
+ // Mozilla also does this, so we'll do the same.
+ std::string connection_val;
+ if (!EnumerateHeader(NULL, "connection", &connection_val))
+ EnumerateHeader(NULL, "proxy-connection", &connection_val);
+
+ bool keep_alive;
+
+ if (http_version_ == HttpVersion(1, 0)) {
+ // HTTP/1.0 responses default to NOT keep-alive
+ keep_alive = LowerCaseEqualsASCII(connection_val, "keep-alive");
+ } else {
+ // HTTP/1.1 responses default to keep-alive
+ keep_alive = !LowerCaseEqualsASCII(connection_val, "close");
+ }
+
+ return keep_alive;
+}
+
+bool HttpResponseHeaders::HasStrongValidators() const {
+ std::string etag_header;
+ EnumerateHeader(NULL, "etag", &etag_header);
+ std::string last_modified_header;
+ EnumerateHeader(NULL, "Last-Modified", &last_modified_header);
+ std::string date_header;
+ EnumerateHeader(NULL, "Date", &date_header);
+ return HttpUtil::HasStrongValidators(GetHttpVersion(),
+ etag_header,
+ last_modified_header,
+ date_header);
+}
+
+// From RFC 2616:
+// Content-Length = "Content-Length" ":" 1*DIGIT
+int64 HttpResponseHeaders::GetContentLength() const {
+ return GetInt64HeaderValue("content-length");
+}
+
+int64 HttpResponseHeaders::GetInt64HeaderValue(
+ const std::string& header) const {
+ void* iter = NULL;
+ std::string content_length_val;
+ if (!EnumerateHeader(&iter, header, &content_length_val))
+ return -1;
+
+ if (content_length_val.empty())
+ return -1;
+
+ if (content_length_val[0] == '+')
+ return -1;
+
+ int64 result;
+ bool ok = base::StringToInt64(content_length_val, &result);
+ if (!ok || result < 0)
+ return -1;
+
+ return result;
+}
+
+// From RFC 2616 14.16:
+// content-range-spec =
+// bytes-unit SP byte-range-resp-spec "/" ( instance-length | "*" )
+// byte-range-resp-spec = (first-byte-pos "-" last-byte-pos) | "*"
+// instance-length = 1*DIGIT
+// bytes-unit = "bytes"
+bool HttpResponseHeaders::GetContentRange(int64* first_byte_position,
+ int64* last_byte_position,
+ int64* instance_length) const {
+ void* iter = NULL;
+ std::string content_range_spec;
+ *first_byte_position = *last_byte_position = *instance_length = -1;
+ if (!EnumerateHeader(&iter, "content-range", &content_range_spec))
+ return false;
+
+ // If the header value is empty, we have an invalid header.
+ if (content_range_spec.empty())
+ return false;
+
+ size_t space_position = content_range_spec.find(' ');
+ if (space_position == std::string::npos)
+ return false;
+
+ // Invalid header if it doesn't contain "bytes-unit".
+ std::string::const_iterator content_range_spec_begin =
+ content_range_spec.begin();
+ std::string::const_iterator content_range_spec_end =
+ content_range_spec.begin() + space_position;
+ HttpUtil::TrimLWS(&content_range_spec_begin, &content_range_spec_end);
+ if (!LowerCaseEqualsASCII(content_range_spec_begin,
+ content_range_spec_end,
+ "bytes")) {
+ return false;
+ }
+
+ size_t slash_position = content_range_spec.find('/', space_position + 1);
+ if (slash_position == std::string::npos)
+ return false;
+
+ // Obtain the part behind the space and before slash.
+ std::string::const_iterator byte_range_resp_spec_begin =
+ content_range_spec.begin() + space_position + 1;
+ std::string::const_iterator byte_range_resp_spec_end =
+ content_range_spec.begin() + slash_position;
+ HttpUtil::TrimLWS(&byte_range_resp_spec_begin, &byte_range_resp_spec_end);
+
+ // Parse the byte-range-resp-spec part.
+ std::string byte_range_resp_spec(byte_range_resp_spec_begin,
+ byte_range_resp_spec_end);
+ // If byte-range-resp-spec != "*".
+ if (!LowerCaseEqualsASCII(byte_range_resp_spec, "*")) {
+ size_t minus_position = byte_range_resp_spec.find('-');
+ if (minus_position != std::string::npos) {
+ // Obtain first-byte-pos.
+ std::string::const_iterator first_byte_pos_begin =
+ byte_range_resp_spec.begin();
+ std::string::const_iterator first_byte_pos_end =
+ byte_range_resp_spec.begin() + minus_position;
+ HttpUtil::TrimLWS(&first_byte_pos_begin, &first_byte_pos_end);
+
+ bool ok = base::StringToInt64(StringPiece(first_byte_pos_begin,
+ first_byte_pos_end),
+ first_byte_position);
+
+ // Obtain last-byte-pos.
+ std::string::const_iterator last_byte_pos_begin =
+ byte_range_resp_spec.begin() + minus_position + 1;
+ std::string::const_iterator last_byte_pos_end =
+ byte_range_resp_spec.end();
+ HttpUtil::TrimLWS(&last_byte_pos_begin, &last_byte_pos_end);
+
+ ok &= base::StringToInt64(StringPiece(last_byte_pos_begin,
+ last_byte_pos_end),
+ last_byte_position);
+ if (!ok) {
+ *first_byte_position = *last_byte_position = -1;
+ return false;
+ }
+ if (*first_byte_position < 0 || *last_byte_position < 0 ||
+ *first_byte_position > *last_byte_position)
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ // Parse the instance-length part.
+ // If instance-length == "*".
+ std::string::const_iterator instance_length_begin =
+ content_range_spec.begin() + slash_position + 1;
+ std::string::const_iterator instance_length_end =
+ content_range_spec.end();
+ HttpUtil::TrimLWS(&instance_length_begin, &instance_length_end);
+
+ if (LowerCaseEqualsASCII(instance_length_begin, instance_length_end, "*")) {
+ return false;
+ } else if (!base::StringToInt64(StringPiece(instance_length_begin,
+ instance_length_end),
+ instance_length)) {
+ *instance_length = -1;
+ return false;
+ }
+
+ // We have all the values; let's verify that they make sense for a 206
+ // response.
+ if (*first_byte_position < 0 || *last_byte_position < 0 ||
+ *instance_length < 0 || *instance_length - 1 < *last_byte_position)
+ return false;
+
+ return true;
+}
+
+base::Value* HttpResponseHeaders::NetLogCallback(
+ NetLog::LogLevel /* log_level */) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* headers = new base::ListValue();
+ headers->Append(new base::StringValue(GetStatusLine()));
+ void* iterator = NULL;
+ std::string name;
+ std::string value;
+ while (EnumerateHeaderLines(&iterator, &name, &value)) {
+ headers->Append(
+ new base::StringValue(base::StringPrintf("%s: %s",
+ name.c_str(),
+ value.c_str())));
+ }
+ dict->Set("headers", headers);
+ return dict;
+}
+
+// static
+bool HttpResponseHeaders::FromNetLogParam(
+ const base::Value* event_param,
+ scoped_refptr<HttpResponseHeaders>* http_response_headers) {
+ *http_response_headers = NULL;
+
+ const base::DictionaryValue* dict = NULL;
+ const base::ListValue* header_list = NULL;
+
+ if (!event_param ||
+ !event_param->GetAsDictionary(&dict) ||
+ !dict->GetList("headers", &header_list)) {
+ return false;
+ }
+
+ std::string raw_headers;
+ for (base::ListValue::const_iterator it = header_list->begin();
+ it != header_list->end();
+ ++it) {
+ std::string header_line;
+ if (!(*it)->GetAsString(&header_line))
+ return false;
+
+ raw_headers.append(header_line);
+ raw_headers.push_back('\0');
+ }
+ raw_headers.push_back('\0');
+ *http_response_headers = new HttpResponseHeaders(raw_headers);
+ return true;
+}
+
+bool HttpResponseHeaders::IsChunkEncoded() const {
+ // Ignore spurious chunked responses from HTTP/1.0 servers and proxies.
+ return GetHttpVersion() >= HttpVersion(1, 1) &&
+ HasHeaderValue("Transfer-Encoding", "chunked");
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_response_headers.h b/chromium/net/http/http_response_headers.h
new file mode 100644
index 00000000000..61075979535
--- /dev/null
+++ b/chromium/net/http/http_response_headers.h
@@ -0,0 +1,377 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_RESPONSE_HEADERS_H_
+#define NET_HTTP_HTTP_RESPONSE_HEADERS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/http/http_version.h"
+
+class Pickle;
+class PickleIterator;
+
+namespace base {
+class Time;
+class TimeDelta;
+}
+
+namespace net {
+
+// HttpResponseHeaders: parses and holds HTTP response headers.
+class NET_EXPORT HttpResponseHeaders
+ : public base::RefCountedThreadSafe<HttpResponseHeaders> {
+ public:
+ // Persist options.
+ typedef int PersistOptions;
+ static const PersistOptions PERSIST_RAW = -1; // Raw, unparsed headers.
+ static const PersistOptions PERSIST_ALL = 0; // Parsed headers.
+ static const PersistOptions PERSIST_SANS_COOKIES = 1 << 0;
+ static const PersistOptions PERSIST_SANS_CHALLENGES = 1 << 1;
+ static const PersistOptions PERSIST_SANS_HOP_BY_HOP = 1 << 2;
+ static const PersistOptions PERSIST_SANS_NON_CACHEABLE = 1 << 3;
+ static const PersistOptions PERSIST_SANS_RANGES = 1 << 4;
+ static const PersistOptions PERSIST_SANS_SECURITY_STATE = 1 << 5;
+
+ // Parses the given raw_headers. raw_headers should be formatted thus:
+ // includes the http status response line, each line is \0-terminated, and
+ // it's terminated by an empty line (ie, 2 \0s in a row).
+ // (Note that line continuations should have already been joined;
+ // see HttpUtil::AssembleRawHeaders)
+ //
+ // HttpResponseHeaders does not perform any encoding changes on the input.
+ //
+ explicit HttpResponseHeaders(const std::string& raw_headers);
+
+ // Initializes from the representation stored in the given pickle. The data
+ // for this object is found relative to the given pickle_iter, which should
+ // be passed to the pickle's various Read* methods.
+ HttpResponseHeaders(const Pickle& pickle, PickleIterator* pickle_iter);
+
+ // Appends a representation of this object to the given pickle.
+ // The options argument can be a combination of PersistOptions.
+ void Persist(Pickle* pickle, PersistOptions options);
+
+ // Performs header merging as described in 13.5.3 of RFC 2616.
+ void Update(const HttpResponseHeaders& new_headers);
+
+ // Removes all instances of a particular header.
+ void RemoveHeader(const std::string& name);
+
+ // Removes a particular header line. The header name is compared
+ // case-insensitively.
+ void RemoveHeaderLine(const std::string& name, const std::string& value);
+
+ // Adds a particular header. |header| has to be a single header without any
+ // EOL termination, just [<header-name>: <header-values>]
+ // If a header with the same name is already stored, the two headers are not
+ // merged together by this method; the one provided is simply put at the
+ // end of the list.
+ void AddHeader(const std::string& header);
+
+ // Replaces the current status line with the provided one (|new_status| should
+ // not have any EOL).
+ void ReplaceStatusLine(const std::string& new_status);
+
+ // Creates a normalized header string. The output will be formatted exactly
+ // like so:
+ // HTTP/<version> <status_code> <status_text>\n
+ // [<header-name>: <header-values>\n]*
+ // meaning, each line is \n-terminated, and there is no extra whitespace
+ // beyond the single space separators shown (of course, values can contain
+ // whitespace within them). If a given header-name appears more than once
+ // in the set of headers, they are combined into a single line like so:
+ // <header-name>: <header-value1>, <header-value2>, ...<header-valueN>\n
+ //
+ // DANGER: For some headers (e.g., "Set-Cookie"), the normalized form can be
+ // a lossy format. This is due to the fact that some servers generate
+ // Set-Cookie headers that contain unquoted commas (usually as part of the
+ // value of an "expires" attribute). So, use this function with caution. Do
+ // not expect to be able to re-parse Set-Cookie headers from this output.
+ //
+ // NOTE: Do not make any assumptions about the encoding of this output
+ // string. It may be non-ASCII, and the encoding used by the server is not
+ // necessarily known to us. Do not assume that this output is UTF-8!
+ //
+ // TODO(darin): remove this method
+ //
+ void GetNormalizedHeaders(std::string* output) const;
+
+ // Fetch the "normalized" value of a single header, where all values for the
+ // header name are separated by commas. See the GetNormalizedHeaders for
+ // format details. Returns false if this header wasn't found.
+ //
+ // NOTE: Do not make any assumptions about the encoding of this output
+ // string. It may be non-ASCII, and the encoding used by the server is not
+ // necessarily known to us. Do not assume that this output is UTF-8!
+ //
+ // TODO(darin): remove this method
+ //
+ bool GetNormalizedHeader(const std::string& name, std::string* value) const;
+
+ // Returns the normalized status line. For HTTP/0.9 responses (i.e.,
+ // responses that lack a status line), this is the manufactured string
+ // "HTTP/0.9 200 OK".
+ std::string GetStatusLine() const;
+
+ // Get the HTTP version of the normalized status line.
+ HttpVersion GetHttpVersion() const {
+ return http_version_;
+ }
+
+ // Get the HTTP version determined while parsing; or (0,0) if parsing failed
+ HttpVersion GetParsedHttpVersion() const {
+ return parsed_http_version_;
+ }
+
+ // Get the HTTP status text of the normalized status line.
+ std::string GetStatusText() const;
+
+ // Enumerate the "lines" of the response headers. This skips over the status
+ // line. Use GetStatusLine if you are interested in that. Note that this
+ // method returns the un-coalesced response header lines, so if a response
+ // header appears on multiple lines, then it will appear multiple times in
+ // this enumeration (in the order the header lines were received from the
+ // server). Also, a given header might have an empty value. Initialize a
+ // 'void*' variable to NULL and pass it by address to EnumerateHeaderLines.
+ // Call EnumerateHeaderLines repeatedly until it returns false. The
+ // out-params 'name' and 'value' are set upon success.
+ bool EnumerateHeaderLines(void** iter,
+ std::string* name,
+ std::string* value) const;
+
+ // Enumerate the values of the specified header. If you are only interested
+ // in the first header, then you can pass NULL for the 'iter' parameter.
+ // Otherwise, to iterate across all values for the specified header,
+ // initialize a 'void*' variable to NULL and pass it by address to
+ // EnumerateHeader. Note that a header might have an empty value. Call
+ // EnumerateHeader repeatedly until it returns false.
+ bool EnumerateHeader(void** iter,
+ const base::StringPiece& name,
+ std::string* value) const;
+
+ // Returns true if the response contains the specified header-value pair.
+ // Both name and value are compared case insensitively.
+ bool HasHeaderValue(const base::StringPiece& name,
+ const base::StringPiece& value) const;
+
+ // Returns true if the response contains the specified header.
+ // The name is compared case insensitively.
+ bool HasHeader(const base::StringPiece& name) const;
+
+ // Get the mime type and charset values in lower case form from the headers.
+ // Empty strings are returned if the values are not present.
+ void GetMimeTypeAndCharset(std::string* mime_type,
+ std::string* charset) const;
+
+ // Get the mime type in lower case from the headers. If there's no mime
+ // type, returns false.
+ bool GetMimeType(std::string* mime_type) const;
+
+ // Get the charset in lower case from the headers. If there's no charset,
+ // returns false.
+ bool GetCharset(std::string* charset) const;
+
+ // Returns true if this response corresponds to a redirect. The target
+ // location of the redirect is optionally returned if location is non-null.
+ bool IsRedirect(std::string* location) const;
+
+ // Returns true if the HTTP response code passed in corresponds to a
+ // redirect.
+ static bool IsRedirectResponseCode(int response_code);
+
+ // Returns true if the response cannot be reused without validation. The
+ // result is relative to the current_time parameter, which is a parameter to
+ // support unit testing. The request_time parameter indicates the time at
+ // which the request was made that resulted in this response, which was
+ // received at response_time.
+ bool RequiresValidation(const base::Time& request_time,
+ const base::Time& response_time,
+ const base::Time& current_time) const;
+
+ // Returns the amount of time the server claims the response is fresh from
+ // the time the response was generated. See section 13.2.4 of RFC 2616. See
+ // RequiresValidation for a description of the response_time parameter.
+ base::TimeDelta GetFreshnessLifetime(const base::Time& response_time) const;
+
+ // Returns the age of the response. See section 13.2.3 of RFC 2616.
+ // See RequiresValidation for a description of this method's parameters.
+ base::TimeDelta GetCurrentAge(const base::Time& request_time,
+ const base::Time& response_time,
+ const base::Time& current_time) const;
+
+ // The following methods extract values from the response headers. If a
+ // value is not present, then false is returned. Otherwise, true is returned
+ // and the out param is assigned to the corresponding value.
+ bool GetMaxAgeValue(base::TimeDelta* value) const;
+ bool GetAgeValue(base::TimeDelta* value) const;
+ bool GetDateValue(base::Time* value) const;
+ bool GetLastModifiedValue(base::Time* value) const;
+ bool GetExpiresValue(base::Time* value) const;
+
+ // Extracts the time value of a particular header. This method looks for the
+ // first matching header value and parses its value as a HTTP-date.
+ bool GetTimeValuedHeader(const std::string& name, base::Time* result) const;
+
+ // Determines if this response indicates a keep-alive connection.
+ bool IsKeepAlive() const;
+
+ // Returns true if this response has a strong etag or last-modified header.
+ // See section 13.3.3 of RFC 2616.
+ bool HasStrongValidators() const;
+
+ // Extracts the value of the Content-Length header or returns -1 if there is
+ // no such header in the response.
+ int64 GetContentLength() const;
+
+ // Extracts the value of the specified header or returns -1 if there is no
+ // such header in the response.
+ int64 GetInt64HeaderValue(const std::string& header) const;
+
+ // Extracts the values in a Content-Range header and returns true if they are
+ // valid for a 206 response; otherwise returns false.
+ // The following values will be outputted:
+ // |*first_byte_position| = inclusive position of the first byte of the range
+ // |*last_byte_position| = inclusive position of the last byte of the range
+ // |*instance_length| = size in bytes of the object requested
+ // If any of the above values is unknown, its value will be -1.
+ bool GetContentRange(int64* first_byte_position,
+ int64* last_byte_position,
+ int64* instance_length) const;
+
+ // Returns true if the response is chunk-encoded.
+ bool IsChunkEncoded() const;
+
+ // Creates a Value for use with the NetLog containing the response headers.
+ base::Value* NetLogCallback(NetLog::LogLevel log_level) const;
+
+ // Takes in a Value created by the above function, and attempts to create a
+ // copy of the original headers. Returns true on success. On failure,
+ // clears |http_response_headers|.
+ // TODO(mmenke): Long term, we want to remove this, and migrate external
+ // consumers to be NetworkDelegates.
+ static bool FromNetLogParam(
+ const base::Value* event_param,
+ scoped_refptr<HttpResponseHeaders>* http_response_headers);
+
+ // Returns the HTTP response code. This is 0 if the response code text seems
+ // to exist but could not be parsed. Otherwise, it defaults to 200 if the
+ // response code is not found in the raw headers.
+ int response_code() const { return response_code_; }
+
+ // Returns the raw header string.
+ const std::string& raw_headers() const { return raw_headers_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<HttpResponseHeaders>;
+
+ typedef base::hash_set<std::string> HeaderSet;
+
+ // The members of this structure point into raw_headers_.
+ struct ParsedHeader;
+ typedef std::vector<ParsedHeader> HeaderList;
+
+ HttpResponseHeaders();
+ ~HttpResponseHeaders();
+
+ // Initializes from the given raw headers.
+ void Parse(const std::string& raw_input);
+
+ // Helper function for ParseStatusLine.
+ // Tries to extract the "HTTP/X.Y" from a status line formatted like:
+ // HTTP/1.1 200 OK
+ // with line_begin and end pointing at the begin and end of this line. If the
+ // status line is malformed, returns HttpVersion(0,0).
+ static HttpVersion ParseVersion(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end);
+
+ // Tries to extract the status line from a header block, given the first
+ // line of said header block. If the status line is malformed, we'll
+ // construct a valid one. Example input:
+ // HTTP/1.1 200 OK
+ // with line_begin and end pointing at the begin and end of this line.
+ // Output will be a normalized version of this.
+ void ParseStatusLine(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end,
+ bool has_headers);
+
+ // Find the header in our list (case-insensitive) starting with parsed_ at
+ // index |from|. Returns string::npos if not found.
+ size_t FindHeader(size_t from, const base::StringPiece& name) const;
+
+ // Add a header->value pair to our list. If we already have header in our
+ // list, append the value to it.
+ void AddHeader(std::string::const_iterator name_begin,
+ std::string::const_iterator name_end,
+ std::string::const_iterator value_begin,
+ std::string::const_iterator value_end);
+
+ // Add to parsed_ given the fields of a ParsedHeader object.
+ void AddToParsed(std::string::const_iterator name_begin,
+ std::string::const_iterator name_end,
+ std::string::const_iterator value_begin,
+ std::string::const_iterator value_end);
+
+ // Replaces the current headers with the merged version of |raw_headers| and
+ // the current headers without the headers in |headers_to_remove|. Note that
+ // |headers_to_remove| are removed from the current headers (before the
+ // merge), not after the merge.
+ void MergeWithHeaders(const std::string& raw_headers,
+ const HeaderSet& headers_to_remove);
+
+ // Adds the values from any 'cache-control: no-cache="foo,bar"' headers.
+ void AddNonCacheableHeaders(HeaderSet* header_names) const;
+
+ // Adds the set of header names that contain cookie values.
+ static void AddSensitiveHeaders(HeaderSet* header_names);
+
+ // Adds the set of rfc2616 hop-by-hop response headers.
+ static void AddHopByHopHeaders(HeaderSet* header_names);
+
+ // Adds the set of challenge response headers.
+ static void AddChallengeHeaders(HeaderSet* header_names);
+
+ // Adds the set of cookie response headers.
+ static void AddCookieHeaders(HeaderSet* header_names);
+
+ // Adds the set of content range response headers.
+ static void AddHopContentRangeHeaders(HeaderSet* header_names);
+
+ // Adds the set of transport security state headers.
+ static void AddSecurityStateHeaders(HeaderSet* header_names);
+
+ // We keep a list of ParsedHeader objects. These tell us where to locate the
+ // header-value pairs within raw_headers_.
+ HeaderList parsed_;
+
+ // The raw_headers_ consists of the normalized status line (terminated with a
+ // null byte) and then followed by the raw null-terminated headers from the
+ // input that was passed to our constructor. We preserve the input [*] to
+ // maintain as much ancillary fidelity as possible (since it is sometimes
+ // hard to tell what may matter down-stream to a consumer of XMLHttpRequest).
+ // [*] The status line may be modified.
+ std::string raw_headers_;
+
+ // This is the parsed HTTP response code.
+ int response_code_;
+
+ // The normalized http version (consistent with what GetStatusLine() returns).
+ HttpVersion http_version_;
+
+ // The parsed http version number (not normalized).
+ HttpVersion parsed_http_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponseHeaders);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_RESPONSE_HEADERS_H_
diff --git a/chromium/net/http/http_response_headers_unittest.cc b/chromium/net/http/http_response_headers_unittest.cc
new file mode 100644
index 00000000000..8bde289ed84
--- /dev/null
+++ b/chromium/net/http/http_response_headers_unittest.cc
@@ -0,0 +1,1879 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/pickle.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/http/http_response_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct TestData {
+ const char* raw_headers;
+ const char* expected_headers;
+ int expected_response_code;
+ net::HttpVersion expected_parsed_version;
+ net::HttpVersion expected_version;
+};
+
+struct ContentTypeTestData {
+ const std::string raw_headers;
+ const std::string mime_type;
+ const bool has_mimetype;
+ const std::string charset;
+ const bool has_charset;
+ const std::string all_content_type;
+};
+
+class HttpResponseHeadersTest : public testing::Test {
+};
+
+// Transform "normal"-looking headers (\n-separated) to the appropriate
+// input format for ParseRawHeaders (\0-separated).
+void HeadersToRaw(std::string* headers) {
+ std::replace(headers->begin(), headers->end(), '\n', '\0');
+ if (!headers->empty())
+ *headers += '\0';
+}
+
+void TestCommon(const TestData& test) {
+ std::string raw_headers(test.raw_headers);
+ HeadersToRaw(&raw_headers);
+ std::string expected_headers(test.expected_headers);
+
+ std::string headers;
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(raw_headers));
+ parsed->GetNormalizedHeaders(&headers);
+
+ // Transform to readable output format (so it's easier to see diffs).
+ std::replace(headers.begin(), headers.end(), ' ', '_');
+ std::replace(headers.begin(), headers.end(), '\n', '\\');
+ std::replace(expected_headers.begin(), expected_headers.end(), ' ', '_');
+ std::replace(expected_headers.begin(), expected_headers.end(), '\n', '\\');
+
+ EXPECT_EQ(expected_headers, headers);
+
+ EXPECT_EQ(test.expected_response_code, parsed->response_code());
+
+ EXPECT_TRUE(test.expected_parsed_version == parsed->GetParsedHttpVersion());
+ EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion());
+}
+
+} // end namespace
+
+// Check that we normalize headers properly.
+TEST(HttpResponseHeadersTest, NormalizeHeadersWhitespace) {
+ TestData test = {
+ "HTTP/1.1 202 Accepted \n"
+ "Content-TYPE : text/html; charset=utf-8 \n"
+ "Set-Cookie: a \n"
+ "Set-Cookie: b \n",
+
+ "HTTP/1.1 202 Accepted\n"
+ "Content-TYPE: text/html; charset=utf-8\n"
+ "Set-Cookie: a, b\n",
+
+ 202,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+// Check that we normalize headers properly (header name is invalid if starts
+// with LWS).
+TEST(HttpResponseHeadersTest, NormalizeHeadersLeadingWhitespace) {
+ TestData test = {
+ "HTTP/1.1 202 Accepted \n"
+ // Starts with space -- will be skipped as invalid.
+ " Content-TYPE : text/html; charset=utf-8 \n"
+ "Set-Cookie: a \n"
+ "Set-Cookie: b \n",
+
+ "HTTP/1.1 202 Accepted\n"
+ "Set-Cookie: a, b\n",
+
+ 202,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, BlankHeaders) {
+ TestData test = {
+ "HTTP/1.1 200 OK\n"
+ "Header1 : \n"
+ "Header2: \n"
+ "Header3:\n"
+ "Header4\n"
+ "Header5 :\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Header1: \n"
+ "Header2: \n"
+ "Header3: \n"
+ "Header5: \n",
+
+ 200,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) {
+ // Don't believe the http/0.9 version if there are headers!
+ TestData test = {
+ "hTtP/0.9 201\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ "HTTP/1.0 201 OK\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ 201,
+ net::HttpVersion(0,9),
+ net::HttpVersion(1,0)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, PreserveHttp09) {
+ // Accept the HTTP/0.9 version number if there are no headers.
+ // This is how HTTP/0.9 responses get constructed from HttpNetworkTransaction.
+ TestData test = {
+ "hTtP/0.9 200 OK\n",
+
+ "HTTP/0.9 200 OK\n",
+
+ 200,
+ net::HttpVersion(0,9),
+ net::HttpVersion(0,9)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersMissingOK) {
+ TestData test = {
+ "HTTP/1.1 201\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ "HTTP/1.1 201 OK\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ 201,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersBadStatus) {
+ TestData test = {
+ "SCREWED_UP_STATUS_LINE\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ "HTTP/1.0 200 OK\n"
+ "Content-TYPE: text/html; charset=utf-8\n",
+
+ 200,
+ net::HttpVersion(0,0), // Parse error
+ net::HttpVersion(1,0)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersInvalidStatusCode) {
+ TestData test = {
+ "HTTP/1.1 -1 Unknown\n",
+
+ "HTTP/1.1 200 OK\n",
+
+ 200,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) {
+ TestData test = {
+ "",
+
+ "HTTP/1.0 200 OK\n",
+
+ 200,
+ net::HttpVersion(0,0), // Parse Error
+ net::HttpVersion(1,0)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColon) {
+ TestData test = {
+ "HTTP/1.1 202 Accepted \n"
+ "foo: bar\n"
+ ": a \n"
+ " : b\n"
+ "baz: blat \n",
+
+ "HTTP/1.1 202 Accepted\n"
+ "foo: bar\n"
+ "baz: blat\n",
+
+ 202,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColonAtEOL) {
+ TestData test = {
+ "HTTP/1.1 202 Accepted \n"
+ "foo: \n"
+ "bar:\n"
+ "baz: blat \n"
+ "zip:\n",
+
+ "HTTP/1.1 202 Accepted\n"
+ "foo: \n"
+ "bar: \n"
+ "baz: blat\n"
+ "zip: \n",
+
+ 202,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) {
+ TestData test = {
+ "\n \n",
+
+ "HTTP/1.0 200 OK\n",
+
+ 200,
+ net::HttpVersion(0,0), // Parse error
+ net::HttpVersion(1,0)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, RepeatedSetCookie) {
+ TestData test = {
+ "HTTP/1.1 200 OK\n"
+ "Set-Cookie: x=1\n"
+ "Set-Cookie: y=2\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Set-Cookie: x=1, y=2\n",
+
+ 200,
+ net::HttpVersion(1,1),
+ net::HttpVersion(1,1)
+ };
+ TestCommon(test);
+}
+
+TEST(HttpResponseHeadersTest, GetNormalizedHeader) {
+ std::string headers =
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: private\n"
+ "cache-Control: no-store\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ std::string value;
+ EXPECT_TRUE(parsed->GetNormalizedHeader("cache-control", &value));
+ EXPECT_EQ("private, no-store", value);
+}
+
+TEST(HttpResponseHeadersTest, Persist) {
+ const struct {
+ net::HttpResponseHeaders::PersistOptions options;
+ const char* raw_headers;
+ const char* expected_headers;
+ } tests[] = {
+ { net::HttpResponseHeaders::PERSIST_ALL,
+ "HTTP/1.1 200 OK\n"
+ "Cache-control:private\n"
+ "cache-Control:no-store\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: private, no-store\n"
+ },
+ { net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "server: blah\n",
+
+ "HTTP/1.1 200 OK\n"
+ "server: blah\n"
+ },
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
+ net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
+ "HTTP/1.1 200 OK\n"
+ "fOo: 1\n"
+ "Foo: 2\n"
+ "Transfer-Encoding: chunked\n"
+ "CoNnection: keep-alive\n"
+ "cache-control: private, no-cache=\"foo\"\n",
+
+ "HTTP/1.1 200 OK\n"
+ "cache-control: private, no-cache=\"foo\"\n"
+ },
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private,no-cache=\"foo, bar\"\n"
+ "bar",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-Control: private,no-cache=\"foo, bar\"\n"
+ },
+ // ignore bogus no-cache value
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private,no-cache=foo\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private,no-cache=foo\n"
+ },
+ // ignore bogus no-cache value
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\n"
+ },
+ // ignore empty no-cache value
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\"\"\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\"\"\n"
+ },
+ // ignore wrong quotes no-cache value
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\'foo\'\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\'foo\'\n"
+ },
+ // ignore unterminated quotes no-cache value
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\"foo\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\"foo\n"
+ },
+ // accept sloppy LWS
+ { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 2\n"
+ "Cache-Control: private, no-cache=\" foo\t, bar\"\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-Control: private, no-cache=\" foo\t, bar\"\n"
+ },
+ // header name appears twice, separated by another header
+ { net::HttpResponseHeaders::PERSIST_ALL,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 1\n"
+ "Bar: 2\n"
+ "Foo: 3\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 1, 3\n"
+ "Bar: 2\n"
+ },
+ // header name appears twice, separated by another header (type 2)
+ { net::HttpResponseHeaders::PERSIST_ALL,
+ "HTTP/1.1 200 OK\n"
+ "Foo: 1, 3\n"
+ "Bar: 2\n"
+ "Foo: 4\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Foo: 1, 3, 4\n"
+ "Bar: 2\n"
+ },
+ // Test filtering of cookie headers.
+ { net::HttpResponseHeaders::PERSIST_SANS_COOKIES,
+ "HTTP/1.1 200 OK\n"
+ "Set-Cookie: foo=bar; httponly\n"
+ "Set-Cookie: bar=foo\n"
+ "Bar: 1\n"
+ "Set-Cookie2: bar2=foo2\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Bar: 1\n"
+ },
+ // Test LWS at the end of a header.
+ { net::HttpResponseHeaders::PERSIST_ALL,
+ "HTTP/1.1 200 OK\n"
+ "Content-Length: 450 \n"
+ "Content-Encoding: gzip\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Content-Length: 450\n"
+ "Content-Encoding: gzip\n"
+ },
+ // Test LWS at the end of a header.
+ { net::HttpResponseHeaders::PERSIST_RAW,
+ "HTTP/1.1 200 OK\n"
+ "Content-Length: 450 \n"
+ "Content-Encoding: gzip\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Content-Length: 450\n"
+ "Content-Encoding: gzip\n"
+ },
+ // Test filtering of transport security state headers.
+ { net::HttpResponseHeaders::PERSIST_SANS_SECURITY_STATE,
+ "HTTP/1.1 200 OK\n"
+ "Strict-Transport-Security: max-age=1576800\n"
+ "Bar: 1\n"
+ "Public-Key-Pins: max-age=100000; "
+ "pin-sha1=\"ObT42aoSpAqWdY9WfRfL7i0HsVk=\";"
+ "pin-sha1=\"7kW49EVwZG0hSNx41ZO/fUPN0ek=\"",
+
+ "HTTP/1.1 200 OK\n"
+ "Bar: 1\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers = tests[i].raw_headers;
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed1(
+ new net::HttpResponseHeaders(headers));
+
+ Pickle pickle;
+ parsed1->Persist(&pickle, tests[i].options);
+
+ PickleIterator iter(pickle);
+ scoped_refptr<net::HttpResponseHeaders> parsed2(
+ new net::HttpResponseHeaders(pickle, &iter));
+
+ std::string h2;
+ parsed2->GetNormalizedHeaders(&h2);
+ EXPECT_EQ(std::string(tests[i].expected_headers), h2);
+ }
+}
+
+TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) {
+ // Ensure that commas in quoted strings are not regarded as value separators.
+ // Ensure that whitespace following a value is trimmed properly
+ std::string headers =
+ "HTTP/1.1 200 OK\n"
+ "Cache-control:private , no-cache=\"set-cookie,server\" \n"
+ "cache-Control: no-store\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ void* iter = NULL;
+ std::string value;
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
+ EXPECT_EQ("private", value);
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
+ EXPECT_EQ("no-cache=\"set-cookie,server\"", value);
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
+ EXPECT_EQ("no-store", value);
+ EXPECT_FALSE(parsed->EnumerateHeader(&iter, "cache-control", &value));
+}
+
+TEST(HttpResponseHeadersTest, EnumerateHeader_Challenge) {
+ // Even though WWW-Authenticate has commas, it should not be treated as
+ // coalesced values.
+ std::string headers =
+ "HTTP/1.1 401 OK\n"
+ "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
+ "WWW-Authenticate:Basic realm=quatar\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ void* iter = NULL;
+ std::string value;
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
+ EXPECT_EQ("Digest realm=foobar, nonce=x, domain=y", value);
+ EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
+ EXPECT_EQ("Basic realm=quatar", value);
+ EXPECT_FALSE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
+}
+
+TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) {
+ // The comma in a date valued header should not be treated as a
+ // field-value separator
+ std::string headers =
+ "HTTP/1.1 200 OK\n"
+ "Date: Tue, 07 Aug 2007 23:10:55 GMT\n"
+ "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ std::string value;
+ EXPECT_TRUE(parsed->EnumerateHeader(NULL, "date", &value));
+ EXPECT_EQ("Tue, 07 Aug 2007 23:10:55 GMT", value);
+ EXPECT_TRUE(parsed->EnumerateHeader(NULL, "last-modified", &value));
+ EXPECT_EQ("Wed, 01 Aug 2007 23:23:45 GMT", value);
+}
+
+TEST(HttpResponseHeadersTest, DefaultDateToGMT) {
+ // Verify we make the best interpretation when parsing dates that incorrectly
+ // do not end in "GMT" as RFC2616 requires.
+ std::string headers =
+ "HTTP/1.1 200 OK\n"
+ "Date: Tue, 07 Aug 2007 23:10:55\n"
+ "Last-Modified: Tue, 07 Aug 2007 19:10:55 EDT\n"
+ "Expires: Tue, 07 Aug 2007 23:10:55 UTC\n";
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+ base::Time expected_value;
+ ASSERT_TRUE(base::Time::FromString("Tue, 07 Aug 2007 23:10:55 GMT",
+ &expected_value));
+
+ base::Time value;
+ // When the timezone is missing, GMT is a good guess as its what RFC2616
+ // requires.
+ EXPECT_TRUE(parsed->GetDateValue(&value));
+ EXPECT_EQ(expected_value, value);
+ // If GMT is missing but an RFC822-conforming one is present, use that.
+ EXPECT_TRUE(parsed->GetLastModifiedValue(&value));
+ EXPECT_EQ(expected_value, value);
+ // If an unknown timezone is present, treat like a missing timezone and
+ // default to GMT. The only example of a web server not specifying "GMT"
+ // used "UTC" which is equivalent to GMT.
+ if (parsed->GetExpiresValue(&value))
+ EXPECT_EQ(expected_value, value);
+}
+
+TEST(HttpResponseHeadersTest, GetMimeType) {
+ const ContentTypeTestData tests[] = {
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "", false,
+ "text/html" },
+ // Multiple content-type headers should give us the last one.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "", false,
+ "text/html, text/html" },
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/plain\n"
+ "Content-type: text/html\n"
+ "Content-type: text/plain\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "", false,
+ "text/plain, text/html, text/plain, text/html" },
+ // Test charset parsing.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html\n"
+ "Content-type: text/html; charset=ISO-8859-1\n",
+ "text/html", true,
+ "iso-8859-1", true,
+ "text/html, text/html; charset=ISO-8859-1" },
+ // Test charset in double quotes.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html\n"
+ "Content-type: text/html; charset=\"ISO-8859-1\"\n",
+ "text/html", true,
+ "iso-8859-1", true,
+ "text/html, text/html; charset=\"ISO-8859-1\"" },
+ // If there are multiple matching content-type headers, we carry
+ // over the charset value.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html;charset=utf-8\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "utf-8", true,
+ "text/html;charset=utf-8, text/html" },
+ // Test single quotes.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html;charset='utf-8'\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "utf-8", true,
+ "text/html;charset='utf-8', text/html" },
+ // Last charset wins if matching content-type.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html;charset=utf-8\n"
+ "Content-type: text/html;charset=iso-8859-1\n",
+ "text/html", true,
+ "iso-8859-1", true,
+ "text/html;charset=utf-8, text/html;charset=iso-8859-1" },
+ // Charset is ignored if the content types change.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/plain;charset=utf-8\n"
+ "Content-type: text/html\n",
+ "text/html", true,
+ "", false,
+ "text/plain;charset=utf-8, text/html" },
+ // Empty content-type
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: \n",
+ "", false,
+ "", false,
+ "" },
+ // Emtpy charset
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html;charset=\n",
+ "text/html", true,
+ "", false,
+ "text/html;charset=" },
+ // Multiple charsets, last one wins.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
+ "text/html", true,
+ "iso-8859-1", true,
+ "text/html;charset=utf-8; charset=iso-8859-1" },
+ // Multiple params.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n",
+ "text/html", true,
+ "iso-8859-1", true,
+ "text/html; foo=utf-8; charset=iso-8859-1" },
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n",
+ "text/html", true,
+ "utf-8", true,
+ "text/html ; charset=utf-8 ; bar=iso-8859-1" },
+ // Comma embeded in quotes.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html ; charset='utf-8,text/plain' ;\n",
+ "text/html", true,
+ "utf-8,text/plain", true,
+ "text/html ; charset='utf-8,text/plain' ;" },
+ // Charset with leading spaces.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html ; charset= 'utf-8' ;\n",
+ "text/html", true,
+ "utf-8", true,
+ "text/html ; charset= 'utf-8' ;" },
+ // Media type comments in mime-type.
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html (html)\n",
+ "text/html", true,
+ "", false,
+ "text/html (html)" },
+ // Incomplete charset= param
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: text/html; char=\n",
+ "text/html", true,
+ "", false,
+ "text/html; char=" },
+ // Invalid media type: no slash
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: texthtml\n",
+ "", false,
+ "", false,
+ "texthtml" },
+ // Invalid media type: */*
+ { "HTTP/1.1 200 OK\n"
+ "Content-type: */*\n",
+ "", false,
+ "", false,
+ "*/*" },
+ };
+
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ std::string headers(tests[i].raw_headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ std::string value;
+ EXPECT_EQ(tests[i].has_mimetype, parsed->GetMimeType(&value));
+ EXPECT_EQ(tests[i].mime_type, value);
+ value.clear();
+ EXPECT_EQ(tests[i].has_charset, parsed->GetCharset(&value));
+ EXPECT_EQ(tests[i].charset, value);
+ EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value));
+ EXPECT_EQ(tests[i].all_content_type, value);
+ }
+}
+
+TEST(HttpResponseHeadersTest, RequiresValidation) {
+ const struct {
+ const char* headers;
+ bool requires_validation;
+ } tests[] = {
+ // no expiry info: expires immediately
+ { "HTTP/1.1 200 OK\n"
+ "\n",
+ true
+ },
+ // valid for a little while
+ { "HTTP/1.1 200 OK\n"
+ "cache-control: max-age=10000\n"
+ "\n",
+ false
+ },
+ // expires in the future
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "expires: Wed, 28 Nov 2007 01:00:00 GMT\n"
+ "\n",
+ false
+ },
+ // expired already
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
+ "\n",
+ true
+ },
+ // max-age trumps expires
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
+ "cache-control: max-age=10000\n"
+ "\n",
+ false
+ },
+ // last-modified heuristic: modified a while ago
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
+ "\n",
+ false
+ },
+ { "HTTP/1.1 203 Non-Authoritative Information\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
+ "\n",
+ false
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
+ "\n",
+ false
+ },
+ // last-modified heuristic: modified recently
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
+ "\n",
+ true
+ },
+ { "HTTP/1.1 203 Non-Authoritative Information\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
+ "\n",
+ true
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
+ "\n",
+ true
+ },
+ // cached permanent redirect
+ { "HTTP/1.1 301 Moved Permanently\n"
+ "\n",
+ false
+ },
+ // cached redirect: not reusable even though by default it would be
+ { "HTTP/1.1 300 Multiple Choices\n"
+ "Cache-Control: no-cache\n"
+ "\n",
+ true
+ },
+ // cached forever by default
+ { "HTTP/1.1 410 Gone\n"
+ "\n",
+ false
+ },
+ // cached temporary redirect: not reusable
+ { "HTTP/1.1 302 Found\n"
+ "\n",
+ true
+ },
+ // cached temporary redirect: reusable
+ { "HTTP/1.1 302 Found\n"
+ "cache-control: max-age=10000\n"
+ "\n",
+ false
+ },
+ // cache-control: max-age=N overrides expires: date in the past
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "expires: Wed, 28 Nov 2007 00:20:11 GMT\n"
+ "cache-control: max-age=10000\n"
+ "\n",
+ false
+ },
+ // cache-control: no-store overrides expires: in the future
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "expires: Wed, 29 Nov 2007 00:40:11 GMT\n"
+ "cache-control: no-store,private,no-cache=\"foo\"\n"
+ "\n",
+ true
+ },
+ // pragma: no-cache overrides last-modified heuristic
+ { "HTTP/1.1 200 OK\n"
+ "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
+ "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
+ "pragma: no-cache\n"
+ "\n",
+ true
+ },
+ // TODO(darin): add many many more tests here
+ };
+ base::Time request_time, response_time, current_time;
+ base::Time::FromString("Wed, 28 Nov 2007 00:40:09 GMT", &request_time);
+ base::Time::FromString("Wed, 28 Nov 2007 00:40:12 GMT", &response_time);
+ base::Time::FromString("Wed, 28 Nov 2007 00:45:20 GMT", &current_time);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ bool requires_validation =
+ parsed->RequiresValidation(request_time, response_time, current_time);
+ EXPECT_EQ(tests[i].requires_validation, requires_validation);
+ }
+}
+
+TEST(HttpResponseHeadersTest, Update) {
+ const struct {
+ const char* orig_headers;
+ const char* new_headers;
+ const char* expected_headers;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Foo: 1\n"
+ "Cache-control: private\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: max-age=10000\n"
+ "Foo: 1\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Foo: 1\n"
+ "Cache-control: private\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "connection: keep-alive\n"
+ "Cache-CONTROL: max-age=10000\n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-CONTROL: max-age=10000\n"
+ "Foo: 1\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 450\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10001 \n",
+
+ "HTTP/1.1 200 OK\n"
+ "Cache-control: max-age=10001\n"
+ "Content-Length: 450\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "X-Frame-Options: DENY\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "X-Frame-Options: ALLOW\n",
+
+ "HTTP/1.1 200 OK\n"
+ "X-Frame-Options: DENY\n",
+ },
+ { "HTTP/1.1 200 OK\n"
+ "X-WebKit-CSP: default-src 'none'\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "X-WebKit-CSP: default-src *\n",
+
+ "HTTP/1.1 200 OK\n"
+ "X-WebKit-CSP: default-src 'none'\n",
+ },
+ { "HTTP/1.1 200 OK\n"
+ "X-XSS-Protection: 1\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "X-XSS-Protection: 0\n",
+
+ "HTTP/1.1 200 OK\n"
+ "X-XSS-Protection: 1\n",
+ },
+ { "HTTP/1.1 200 OK\n",
+
+ "HTTP/1/1 304 Not Modified\n"
+ "X-Content-Type-Options: nosniff\n",
+
+ "HTTP/1.1 200 OK\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(orig_headers));
+
+ std::string new_headers(tests[i].new_headers);
+ HeadersToRaw(&new_headers);
+ scoped_refptr<net::HttpResponseHeaders> new_parsed(
+ new net::HttpResponseHeaders(new_headers));
+
+ parsed->Update(*new_parsed.get());
+
+ std::string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
+ }
+}
+
+TEST(HttpResponseHeadersTest, EnumerateHeaderLines) {
+ const struct {
+ const char* headers;
+ const char* expected_lines;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n",
+
+ ""
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Foo: 1\n",
+
+ "Foo: 1\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Foo: 1\n"
+ "Bar: 2\n"
+ "Foo: 3\n",
+
+ "Foo: 1\nBar: 2\nFoo: 3\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Foo: 1, 2, 3\n",
+
+ "Foo: 1, 2, 3\n"
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ std::string name, value, lines;
+
+ void* iter = NULL;
+ while (parsed->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+
+ EXPECT_EQ(std::string(tests[i].expected_lines), lines);
+ }
+}
+
+TEST(HttpResponseHeadersTest, IsRedirect) {
+ const struct {
+ const char* headers;
+ const char* location;
+ bool is_redirect;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n",
+ "",
+ false
+ },
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foopy/\n",
+ "http://foopy/",
+ true
+ },
+ { "HTTP/1.1 301 Moved\n"
+ "Location: \t \n",
+ "",
+ false
+ },
+ // we use the first location header as the target of the redirect
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/\n"
+ "Location: http://bar/\n",
+ "http://foo/",
+ true
+ },
+ // we use the first _valid_ location header as the target of the redirect
+ { "HTTP/1.1 301 Moved\n"
+ "Location: \n"
+ "Location: http://bar/\n",
+ "http://bar/",
+ true
+ },
+ // bug 1050541 (location header w/ an unescaped comma)
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/bar,baz.html\n",
+ "http://foo/bar,baz.html",
+ true
+ },
+ // bug 1224617 (location header w/ non-ASCII bytes)
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/bar?key=\xE4\xF6\xFC\n",
+ "http://foo/bar?key=%E4%F6%FC",
+ true
+ },
+ // Shift_JIS, Big5, and GBK contain multibyte characters with the trailing
+ // byte falling in the ASCII range.
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/bar?key=\x81\x5E\xD8\xBF\n",
+ "http://foo/bar?key=%81^%D8%BF",
+ true
+ },
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/bar?key=\x82\x40\xBD\xC4\n",
+ "http://foo/bar?key=%82@%BD%C4",
+ true
+ },
+ { "HTTP/1.1 301 Moved\n"
+ "Location: http://foo/bar?key=\x83\x5C\x82\x5D\xCB\xD7\n",
+ "http://foo/bar?key=%83\\%82]%CB%D7",
+ true
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ std::string location;
+ EXPECT_EQ(parsed->IsRedirect(&location), tests[i].is_redirect);
+ EXPECT_EQ(location, tests[i].location);
+ }
+}
+
+TEST(HttpResponseHeadersTest, GetContentLength) {
+ const struct {
+ const char* headers;
+ int64 expected_len;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 10\n",
+ 10
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: \n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: abc\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: -10\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: +10\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 23xb5\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 0xA\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 010\n",
+ 10
+ },
+ // Content-Length too big, will overflow an int64
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 40000000000000000000\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 10\n",
+ 10
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 10 \n",
+ 10
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: \t10\n",
+ 10
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: \v10\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: \f10\n",
+ -1
+ },
+ { "HTTP/1.1 200 OK\n"
+ "cOnTeNt-LENgth: 33\n",
+ 33
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Content-Length: 34\r\n",
+ -1
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ EXPECT_EQ(tests[i].expected_len, parsed->GetContentLength());
+ }
+}
+
+TEST(HttpResponseHeaders, GetContentRange) {
+ const struct {
+ const char* headers;
+ bool expected_return_value;
+ int64 expected_first_byte_position;
+ int64 expected_last_byte_position;
+ int64 expected_instance_size;
+ } tests[] = {
+ { "HTTP/1.1 206 Partial Content",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range:",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: megabytes 0-10/50",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: 0-10/50",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: Bytes 0-50/51",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50/51",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes\t0-50/51",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50/51",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0 - 50 \t / \t51",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0\t-\t50\t/\t51\t",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: \tbytes\t\t\t 0\t-\t50\t/\t51\t",
+ true,
+ 0,
+ 50,
+ 51
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: \t bytes \t 0 - 50 / 5 1",
+ false,
+ 0,
+ 50,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: \t bytes \t 0 - 5 0 / 51",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 50-0/51",
+ false,
+ 50,
+ 0,
+ -1
+ },
+ { "HTTP/1.1 416 Requested range not satisfiable\n"
+ "Content-Range: bytes * /*",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 416 Requested range not satisfiable\n"
+ "Content-Range: bytes * / * ",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50/*",
+ false,
+ 0,
+ 50,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50 / * ",
+ false,
+ 0,
+ 50,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-10000000000/10000000001",
+ true,
+ 0,
+ 10000000000ll,
+ 10000000001ll
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-10000000000/10000000000",
+ false,
+ 0,
+ 10000000000ll,
+ 10000000000ll
+ },
+ // 64 bits wraparound.
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0 - 9223372036854775807 / 100",
+ false,
+ 0,
+ kint64max,
+ 100
+ },
+ // 64 bits wraparound.
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0 - 100 / -9223372036854775808",
+ false,
+ 0,
+ 100,
+ kint64min
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes */50",
+ false,
+ -1,
+ -1,
+ 50
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50/10",
+ false,
+ 0,
+ 50,
+ 10
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 40-50/45",
+ false,
+ 40,
+ 50,
+ 45
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-50/-10",
+ false,
+ 0,
+ 50,
+ -10
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-0/1",
+ true,
+ 0,
+ 0,
+ 1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-40000000000000000000/40000000000000000001",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 1-/100",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes -/100",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes -1/100",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes 0-1233/*",
+ false,
+ 0,
+ 1233,
+ -1
+ },
+ { "HTTP/1.1 206 Partial Content\n"
+ "Content-Range: bytes -123 - -1/100",
+ false,
+ -1,
+ -1,
+ -1
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ int64 first_byte_position;
+ int64 last_byte_position;
+ int64 instance_size;
+ bool return_value = parsed->GetContentRange(&first_byte_position,
+ &last_byte_position,
+ &instance_size);
+ EXPECT_EQ(tests[i].expected_return_value, return_value);
+ EXPECT_EQ(tests[i].expected_first_byte_position, first_byte_position);
+ EXPECT_EQ(tests[i].expected_last_byte_position, last_byte_position);
+ EXPECT_EQ(tests[i].expected_instance_size, instance_size);
+ }
+}
+
+TEST(HttpResponseHeadersTest, IsKeepAlive) {
+ const struct {
+ const char* headers;
+ bool expected_keep_alive;
+ } tests[] = {
+ // The status line fabricated by HttpNetworkTransaction for a 0.9 response.
+ // Treated as 0.9.
+ { "HTTP/0.9 200 OK",
+ false
+ },
+ // This could come from a broken server. Treated as 1.0 because it has a
+ // header.
+ { "HTTP/0.9 200 OK\n"
+ "connection: keep-alive\n",
+ true
+ },
+ { "HTTP/1.1 200 OK\n",
+ true
+ },
+ { "HTTP/1.0 200 OK\n",
+ false
+ },
+ { "HTTP/1.0 200 OK\n"
+ "connection: close\n",
+ false
+ },
+ { "HTTP/1.0 200 OK\n"
+ "connection: keep-alive\n",
+ true
+ },
+ { "HTTP/1.0 200 OK\n"
+ "connection: kEeP-AliVe\n",
+ true
+ },
+ { "HTTP/1.0 200 OK\n"
+ "connection: keep-aliveX\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: close\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n",
+ true
+ },
+ { "HTTP/1.0 200 OK\n"
+ "proxy-connection: close\n",
+ false
+ },
+ { "HTTP/1.0 200 OK\n"
+ "proxy-connection: keep-alive\n",
+ true
+ },
+ { "HTTP/1.1 200 OK\n"
+ "proxy-connection: close\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "proxy-connection: keep-alive\n",
+ true
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ EXPECT_EQ(tests[i].expected_keep_alive, parsed->IsKeepAlive());
+ }
+}
+
+TEST(HttpResponseHeadersTest, HasStrongValidators) {
+ const struct {
+ const char* headers;
+ bool expected_result;
+ } tests[] = {
+ { "HTTP/0.9 200 OK",
+ false
+ },
+ { "HTTP/1.0 200 OK\n"
+ "Date: Wed, 28 Nov 2007 01:40:10 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
+ "ETag: \"foo\"\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Date: Wed, 28 Nov 2007 01:40:10 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
+ "ETag: \"foo\"\n",
+ true
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Date: Wed, 28 Nov 2007 00:41:10 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
+ true
+ },
+ { "HTTP/1.1 200 OK\n"
+ "Date: Wed, 28 Nov 2007 00:41:09 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "ETag: \"foo\"\n",
+ true
+ },
+ // This is not really a weak etag:
+ { "HTTP/1.1 200 OK\n"
+ "etag: \"w/foo\"\n",
+ true
+ },
+ // This is a weak etag:
+ { "HTTP/1.1 200 OK\n"
+ "etag: w/\"foo\"\n",
+ false
+ },
+ { "HTTP/1.1 200 OK\n"
+ "etag: W / \"foo\"\n",
+ false
+ }
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string headers(tests[i].headers);
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ EXPECT_EQ(tests[i].expected_result, parsed->HasStrongValidators()) <<
+ "Failed test case " << i;
+ }
+}
+
+TEST(HttpResponseHeadersTest, GetStatusText) {
+ std::string headers("HTTP/1.1 404 Not Found");
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+ EXPECT_EQ(std::string("Not Found"), parsed->GetStatusText());
+}
+
+TEST(HttpResponseHeadersTest, GetStatusTextMissing) {
+ std::string headers("HTTP/1.1 404");
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+ // Since the status line gets normalized, we have OK
+ EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
+}
+
+TEST(HttpResponseHeadersTest, GetStatusTextMultiSpace) {
+ std::string headers("HTTP/1.0 404 Not Found");
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+ EXPECT_EQ(std::string("Not Found"), parsed->GetStatusText());
+}
+
+TEST(HttpResponseHeadersTest, GetStatusBadStatusLine) {
+ std::string headers("Foo bar.");
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+ // The bad status line would have gotten rewritten as
+ // HTTP/1.0 200 OK.
+ EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
+}
+
+TEST(HttpResponseHeadersTest, AddHeader) {
+ const struct {
+ const char* orig_headers;
+ const char* new_header;
+ const char* expected_headers;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n",
+
+ "Content-Length: 450",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000 \n",
+
+ "Content-Length: 450 ",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(orig_headers));
+
+ std::string new_header(tests[i].new_header);
+ parsed->AddHeader(new_header);
+
+ std::string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
+ }
+}
+
+TEST(HttpResponseHeadersTest, RemoveHeader) {
+ const struct {
+ const char* orig_headers;
+ const char* to_remove;
+ const char* expected_headers;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n",
+
+ "Content-Length",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Content-Length : 450 \n"
+ "Cache-control: max-age=10000\n",
+
+ "Content-Length",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(orig_headers));
+
+ std::string name(tests[i].to_remove);
+ parsed->RemoveHeader(name);
+
+ std::string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
+ }
+}
+
+TEST(HttpResponseHeadersTest, RemoveIndividualHeader) {
+ const struct {
+ const char* orig_headers;
+ const char* to_remove_name;
+ const char* to_remove_value;
+ const char* expected_headers;
+ } tests[] = {
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n",
+
+ "Content-Length",
+
+ "450",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Content-Length : 450 \n"
+ "Cache-control: max-age=10000\n",
+
+ "Content-Length",
+
+ "450",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Content-Length: 450\n"
+ "Cache-control: max-age=10000\n",
+
+ "Content-Length", // Matching name.
+
+ "999", // Mismatching value.
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Content-Length: 450\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Foo: bar, baz\n"
+ "Foo: bar\n"
+ "Cache-control: max-age=10000\n",
+
+ "Foo",
+
+ "bar, baz", // Space in value.
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Foo: bar\n"
+ "Cache-control: max-age=10000\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Foo: bar, baz\n"
+ "Cache-control: max-age=10000\n",
+
+ "Foo",
+
+ "baz", // Only partial match -> ignored.
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Foo: bar, baz\n"
+ "Cache-control: max-age=10000\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(orig_headers));
+
+ std::string name(tests[i].to_remove_name);
+ std::string value(tests[i].to_remove_value);
+ parsed->RemoveHeaderLine(name, value);
+
+ std::string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
+ }
+}
+
+TEST(HttpResponseHeadersTest, ReplaceStatus) {
+ const struct {
+ const char* orig_headers;
+ const char* new_status;
+ const char* expected_headers;
+ } tests[] = {
+ { "HTTP/1.1 206 Partial Content\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n",
+
+ "HTTP/1.1 200 OK",
+
+ "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n"
+ "Cache-control: max-age=10000\n"
+ "Content-Length: 450\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive\n",
+
+ "HTTP/1.1 304 Not Modified",
+
+ "HTTP/1.1 304 Not Modified\n"
+ "connection: keep-alive\n"
+ },
+ { "HTTP/1.1 200 OK\n"
+ "connection: keep-alive \n"
+ "Content-Length : 450 \n"
+ "Cache-control: max-age=10000\n",
+
+ "HTTP/1//1 304 Not Modified",
+
+ "HTTP/1.0 304 Not Modified\n"
+ "connection: keep-alive\n"
+ "Content-Length: 450\n"
+ "Cache-control: max-age=10000\n"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string orig_headers(tests[i].orig_headers);
+ HeadersToRaw(&orig_headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(orig_headers));
+
+ std::string name(tests[i].new_status);
+ parsed->ReplaceStatusLine(name);
+
+ std::string resulting_headers;
+ parsed->GetNormalizedHeaders(&resulting_headers);
+ EXPECT_EQ(std::string(tests[i].expected_headers), resulting_headers);
+ }
+}
+
+TEST(HttpResponseHeadersTest, ToNetLogParamAndBackAgain) {
+ std::string headers("HTTP/1.1 404\n"
+ "Content-Length: 450\n"
+ "Connection: keep-alive\n");
+ HeadersToRaw(&headers);
+ scoped_refptr<net::HttpResponseHeaders> parsed(
+ new net::HttpResponseHeaders(headers));
+
+ scoped_ptr<base::Value> event_param(
+ parsed->NetLogCallback(net::NetLog::LOG_ALL_BUT_BYTES));
+ scoped_refptr<net::HttpResponseHeaders> recreated;
+
+ ASSERT_TRUE(net::HttpResponseHeaders::FromNetLogParam(event_param.get(),
+ &recreated));
+ ASSERT_TRUE(recreated.get());
+ EXPECT_EQ(parsed->GetHttpVersion(), recreated->GetHttpVersion());
+ EXPECT_EQ(parsed->response_code(), recreated->response_code());
+ EXPECT_EQ(parsed->GetContentLength(), recreated->GetContentLength());
+ EXPECT_EQ(parsed->IsKeepAlive(), recreated->IsKeepAlive());
+
+ std::string normalized_parsed;
+ parsed->GetNormalizedHeaders(&normalized_parsed);
+ std::string normalized_recreated;
+ parsed->GetNormalizedHeaders(&normalized_recreated);
+ EXPECT_EQ(normalized_parsed, normalized_recreated);
+}
diff --git a/chromium/net/http/http_response_info.cc b/chromium/net/http/http_response_info.cc
new file mode 100644
index 00000000000..0b57a4e774b
--- /dev/null
+++ b/chromium/net/http/http_response_info.cc
@@ -0,0 +1,382 @@
+// Copyright (c) 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.
+
+#include "net/http/http_response_info.h"
+
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "base/time/time.h"
+#include "net/base/auth.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/http_response_headers.h"
+#include "net/ssl/ssl_cert_request_info.h"
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+X509Certificate::PickleType GetPickleTypeForVersion(int version) {
+ switch (version) {
+ case 1:
+ return X509Certificate::PICKLETYPE_SINGLE_CERTIFICATE;
+ case 2:
+ return X509Certificate::PICKLETYPE_CERTIFICATE_CHAIN_V2;
+ case 3:
+ default:
+ return X509Certificate::PICKLETYPE_CERTIFICATE_CHAIN_V3;
+ }
+}
+
+} // namespace
+
+// These values can be bit-wise combined to form the flags field of the
+// serialized HttpResponseInfo.
+enum {
+ // The version of the response info used when persisting response info.
+ RESPONSE_INFO_VERSION = 3,
+
+ // The minimum version supported for deserializing response info.
+ RESPONSE_INFO_MINIMUM_VERSION = 1,
+
+ // We reserve up to 8 bits for the version number.
+ RESPONSE_INFO_VERSION_MASK = 0xFF,
+
+ // This bit is set if the response info has a cert at the end.
+ // Version 1 serialized only the end-entity certificate, while subsequent
+ // versions include the available certificate chain.
+ RESPONSE_INFO_HAS_CERT = 1 << 8,
+
+ // This bit is set if the response info has a security-bits field (security
+ // strength, in bits, of the SSL connection) at the end.
+ RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9,
+
+ // This bit is set if the response info has a cert status at the end.
+ RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10,
+
+ // This bit is set if the response info has vary header data.
+ RESPONSE_INFO_HAS_VARY_DATA = 1 << 11,
+
+ // This bit is set if the request was cancelled before completion.
+ RESPONSE_INFO_TRUNCATED = 1 << 12,
+
+ // This bit is set if the response was received via SPDY.
+ RESPONSE_INFO_WAS_SPDY = 1 << 13,
+
+ // This bit is set if the request has NPN negotiated.
+ RESPONSE_INFO_WAS_NPN = 1 << 14,
+
+ // This bit is set if the request was fetched via an explicit proxy.
+ RESPONSE_INFO_WAS_PROXY = 1 << 15,
+
+ // This bit is set if the response info has an SSL connection status field.
+ // This contains the ciphersuite used to fetch the resource as well as the
+ // protocol version, compression method and whether SSLv3 fallback was used.
+ RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS = 1 << 16,
+
+ // This bit is set if the response info has protocol version.
+ RESPONSE_INFO_HAS_NPN_NEGOTIATED_PROTOCOL = 1 << 17,
+
+ // This bit is set if the response info has connection info.
+ RESPONSE_INFO_HAS_CONNECTION_INFO = 1 << 18,
+
+ // This bit is set if the request has http authentication.
+ RESPONSE_INFO_USE_HTTP_AUTHENTICATION = 1 << 19,
+
+ // TODO(darin): Add other bits to indicate alternate request methods.
+ // For now, we don't support storing those.
+};
+
+HttpResponseInfo::HttpResponseInfo()
+ : was_cached(false),
+ server_data_unavailable(false),
+ network_accessed(false),
+ was_fetched_via_spdy(false),
+ was_npn_negotiated(false),
+ was_fetched_via_proxy(false),
+ did_use_http_auth(false),
+ connection_info(CONNECTION_INFO_UNKNOWN) {
+}
+
+HttpResponseInfo::HttpResponseInfo(const HttpResponseInfo& rhs)
+ : was_cached(rhs.was_cached),
+ server_data_unavailable(rhs.server_data_unavailable),
+ network_accessed(rhs.network_accessed),
+ was_fetched_via_spdy(rhs.was_fetched_via_spdy),
+ was_npn_negotiated(rhs.was_npn_negotiated),
+ was_fetched_via_proxy(rhs.was_fetched_via_proxy),
+ did_use_http_auth(rhs.did_use_http_auth),
+ socket_address(rhs.socket_address),
+ npn_negotiated_protocol(rhs.npn_negotiated_protocol),
+ connection_info(rhs.connection_info),
+ request_time(rhs.request_time),
+ response_time(rhs.response_time),
+ auth_challenge(rhs.auth_challenge),
+ cert_request_info(rhs.cert_request_info),
+ ssl_info(rhs.ssl_info),
+ headers(rhs.headers),
+ vary_data(rhs.vary_data),
+ metadata(rhs.metadata) {
+}
+
+HttpResponseInfo::~HttpResponseInfo() {
+}
+
+HttpResponseInfo& HttpResponseInfo::operator=(const HttpResponseInfo& rhs) {
+ was_cached = rhs.was_cached;
+ server_data_unavailable = rhs.server_data_unavailable;
+ network_accessed = rhs.network_accessed;
+ was_fetched_via_spdy = rhs.was_fetched_via_spdy;
+ was_npn_negotiated = rhs.was_npn_negotiated;
+ was_fetched_via_proxy = rhs.was_fetched_via_proxy;
+ did_use_http_auth = rhs.did_use_http_auth;
+ socket_address = rhs.socket_address;
+ npn_negotiated_protocol = rhs.npn_negotiated_protocol;
+ connection_info = rhs.connection_info;
+ request_time = rhs.request_time;
+ response_time = rhs.response_time;
+ auth_challenge = rhs.auth_challenge;
+ cert_request_info = rhs.cert_request_info;
+ ssl_info = rhs.ssl_info;
+ headers = rhs.headers;
+ vary_data = rhs.vary_data;
+ metadata = rhs.metadata;
+ return *this;
+}
+
+bool HttpResponseInfo::InitFromPickle(const Pickle& pickle,
+ bool* response_truncated) {
+ PickleIterator iter(pickle);
+
+ // Read flags and verify version
+ int flags;
+ if (!pickle.ReadInt(&iter, &flags))
+ return false;
+ int version = flags & RESPONSE_INFO_VERSION_MASK;
+ if (version < RESPONSE_INFO_MINIMUM_VERSION ||
+ version > RESPONSE_INFO_VERSION) {
+ DLOG(ERROR) << "unexpected response info version: " << version;
+ return false;
+ }
+
+ // Read request-time
+ int64 time_val;
+ if (!pickle.ReadInt64(&iter, &time_val))
+ return false;
+ request_time = Time::FromInternalValue(time_val);
+ was_cached = true; // Set status to show cache resurrection.
+
+ // Read response-time
+ if (!pickle.ReadInt64(&iter, &time_val))
+ return false;
+ response_time = Time::FromInternalValue(time_val);
+
+ // Read response-headers
+ headers = new HttpResponseHeaders(pickle, &iter);
+ if (headers->response_code() == -1)
+ return false;
+
+ // Read ssl-info
+ if (flags & RESPONSE_INFO_HAS_CERT) {
+ X509Certificate::PickleType type = GetPickleTypeForVersion(version);
+ ssl_info.cert = X509Certificate::CreateFromPickle(pickle, &iter, type);
+ if (!ssl_info.cert.get())
+ return false;
+ }
+ if (flags & RESPONSE_INFO_HAS_CERT_STATUS) {
+ CertStatus cert_status;
+ if (!pickle.ReadUInt32(&iter, &cert_status))
+ return false;
+ ssl_info.cert_status = cert_status;
+ }
+ if (flags & RESPONSE_INFO_HAS_SECURITY_BITS) {
+ int security_bits;
+ if (!pickle.ReadInt(&iter, &security_bits))
+ return false;
+ ssl_info.security_bits = security_bits;
+ }
+
+ if (flags & RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS) {
+ int connection_status;
+ if (!pickle.ReadInt(&iter, &connection_status))
+ return false;
+ ssl_info.connection_status = connection_status;
+ }
+
+ // Read vary-data
+ if (flags & RESPONSE_INFO_HAS_VARY_DATA) {
+ if (!vary_data.InitFromPickle(pickle, &iter))
+ return false;
+ }
+
+ // Read socket_address.
+ std::string socket_address_host;
+ if (pickle.ReadString(&iter, &socket_address_host)) {
+ // If the host was written, we always expect the port to follow.
+ uint16 socket_address_port;
+ if (!pickle.ReadUInt16(&iter, &socket_address_port))
+ return false;
+ socket_address = HostPortPair(socket_address_host, socket_address_port);
+ } else if (version > 1) {
+ // socket_address was not always present in version 1 of the response
+ // info, so we don't fail if it can't be read.
+ return false;
+ }
+
+ // Read protocol-version.
+ if (flags & RESPONSE_INFO_HAS_NPN_NEGOTIATED_PROTOCOL) {
+ if (!pickle.ReadString(&iter, &npn_negotiated_protocol))
+ return false;
+ }
+
+ // Read connection info.
+ if (flags & RESPONSE_INFO_HAS_CONNECTION_INFO) {
+ int value;
+ if (!pickle.ReadInt(&iter, &value))
+ return false;
+
+ if (value > static_cast<int>(CONNECTION_INFO_UNKNOWN) &&
+ value < static_cast<int>(NUM_OF_CONNECTION_INFOS)) {
+ connection_info = static_cast<ConnectionInfo>(value);
+ }
+ }
+
+ was_fetched_via_spdy = (flags & RESPONSE_INFO_WAS_SPDY) != 0;
+
+ was_npn_negotiated = (flags & RESPONSE_INFO_WAS_NPN) != 0;
+
+ was_fetched_via_proxy = (flags & RESPONSE_INFO_WAS_PROXY) != 0;
+
+ *response_truncated = (flags & RESPONSE_INFO_TRUNCATED) != 0;
+
+ did_use_http_auth = (flags & RESPONSE_INFO_USE_HTTP_AUTHENTICATION) != 0;
+
+ return true;
+}
+
+void HttpResponseInfo::Persist(Pickle* pickle,
+ bool skip_transient_headers,
+ bool response_truncated) const {
+ int flags = RESPONSE_INFO_VERSION;
+ if (ssl_info.is_valid()) {
+ flags |= RESPONSE_INFO_HAS_CERT;
+ flags |= RESPONSE_INFO_HAS_CERT_STATUS;
+ if (ssl_info.security_bits != -1)
+ flags |= RESPONSE_INFO_HAS_SECURITY_BITS;
+ if (ssl_info.connection_status != 0)
+ flags |= RESPONSE_INFO_HAS_SSL_CONNECTION_STATUS;
+ }
+ if (vary_data.is_valid())
+ flags |= RESPONSE_INFO_HAS_VARY_DATA;
+ if (response_truncated)
+ flags |= RESPONSE_INFO_TRUNCATED;
+ if (was_fetched_via_spdy)
+ flags |= RESPONSE_INFO_WAS_SPDY;
+ if (was_npn_negotiated) {
+ flags |= RESPONSE_INFO_WAS_NPN;
+ flags |= RESPONSE_INFO_HAS_NPN_NEGOTIATED_PROTOCOL;
+ }
+ if (was_fetched_via_proxy)
+ flags |= RESPONSE_INFO_WAS_PROXY;
+ if (connection_info != CONNECTION_INFO_UNKNOWN)
+ flags |= RESPONSE_INFO_HAS_CONNECTION_INFO;
+ if (did_use_http_auth)
+ flags |= RESPONSE_INFO_USE_HTTP_AUTHENTICATION;
+
+ pickle->WriteInt(flags);
+ pickle->WriteInt64(request_time.ToInternalValue());
+ pickle->WriteInt64(response_time.ToInternalValue());
+
+ net::HttpResponseHeaders::PersistOptions persist_options =
+ net::HttpResponseHeaders::PERSIST_RAW;
+
+ if (skip_transient_headers) {
+ persist_options =
+ net::HttpResponseHeaders::PERSIST_SANS_COOKIES |
+ net::HttpResponseHeaders::PERSIST_SANS_CHALLENGES |
+ net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP |
+ net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
+ net::HttpResponseHeaders::PERSIST_SANS_RANGES |
+ net::HttpResponseHeaders::PERSIST_SANS_SECURITY_STATE;
+ }
+
+ headers->Persist(pickle, persist_options);
+
+ if (ssl_info.is_valid()) {
+ ssl_info.cert->Persist(pickle);
+ pickle->WriteUInt32(ssl_info.cert_status);
+ if (ssl_info.security_bits != -1)
+ pickle->WriteInt(ssl_info.security_bits);
+ if (ssl_info.connection_status != 0)
+ pickle->WriteInt(ssl_info.connection_status);
+ }
+
+ if (vary_data.is_valid())
+ vary_data.Persist(pickle);
+
+ pickle->WriteString(socket_address.host());
+ pickle->WriteUInt16(socket_address.port());
+
+ if (was_npn_negotiated)
+ pickle->WriteString(npn_negotiated_protocol);
+
+ if (connection_info != CONNECTION_INFO_UNKNOWN)
+ pickle->WriteInt(static_cast<int>(connection_info));
+}
+
+HttpResponseInfo::ConnectionInfo HttpResponseInfo::ConnectionInfoFromNextProto(
+ NextProto next_proto) {
+ switch (next_proto) {
+ case kProtoSPDY2:
+ return CONNECTION_INFO_SPDY2;
+ case kProtoSPDY3:
+ case kProtoSPDY31:
+ return CONNECTION_INFO_SPDY3;
+ case kProtoSPDY4a2:
+ return CONNECTION_INFO_SPDY4A2;
+ case kProtoHTTP2Draft04:
+ return CONNECTION_INFO_HTTP2_DRAFT_04;
+ case kProtoQUIC1SPDY3:
+ return CONNECTION_INFO_QUIC1_SPDY3;
+
+ case kProtoUnknown:
+ case kProtoHTTP11:
+ case kProtoSPDY1:
+ case kProtoSPDY21:
+ break;
+ }
+
+ NOTREACHED();
+ return CONNECTION_INFO_UNKNOWN;
+}
+
+// static
+std::string HttpResponseInfo::ConnectionInfoToString(
+ ConnectionInfo connection_info) {
+ switch (connection_info) {
+ case CONNECTION_INFO_UNKNOWN:
+ return "unknown";
+ case CONNECTION_INFO_HTTP1:
+ return "http/1";
+ case CONNECTION_INFO_SPDY2:
+ return "spdy/2";
+ case CONNECTION_INFO_SPDY3:
+ return "spdy/3";
+ case CONNECTION_INFO_SPDY4A2:
+ return "spdy/4a2";
+ case CONNECTION_INFO_HTTP2_DRAFT_04:
+ return "HTTP-draft-04/2.0";
+ case CONNECTION_INFO_QUIC1_SPDY3:
+ return "quic/1+spdy/3";
+ case NUM_OF_CONNECTION_INFOS:
+ break;
+ }
+ NOTREACHED();
+ return "";
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_response_info.h b/chromium/net/http/http_response_info.h
new file mode 100644
index 00000000000..907ec96a570
--- /dev/null
+++ b/chromium/net/http/http_response_info.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2011 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_HTTP_HTTP_RESPONSE_INFO_H_
+#define NET_HTTP_HTTP_RESPONSE_INFO_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_vary_data.h"
+#include "net/socket/next_proto.h"
+#include "net/ssl/ssl_info.h"
+
+class Pickle;
+
+namespace net {
+
+class AuthChallengeInfo;
+class HttpResponseHeaders;
+class IOBufferWithSize;
+class SSLCertRequestInfo;
+
+class NET_EXPORT HttpResponseInfo {
+ public:
+ // Describes the kind of connection used to fetch this response.
+ //
+ // NOTE: This is persisted to the cache, so make sure not to reorder
+ // these values.
+ //
+ // TODO(akalin): Better yet, just use a string instead of an enum,
+ // like |npn_negotiated_protocol|.
+ enum ConnectionInfo {
+ CONNECTION_INFO_UNKNOWN = 0,
+ CONNECTION_INFO_HTTP1 = 1,
+ CONNECTION_INFO_SPDY2 = 2,
+ CONNECTION_INFO_SPDY3 = 3,
+ CONNECTION_INFO_SPDY4A2 = 4,
+ CONNECTION_INFO_QUIC1_SPDY3 = 5,
+ CONNECTION_INFO_HTTP2_DRAFT_04 = 6,
+ NUM_OF_CONNECTION_INFOS,
+ };
+
+ HttpResponseInfo();
+ HttpResponseInfo(const HttpResponseInfo& rhs);
+ ~HttpResponseInfo();
+ HttpResponseInfo& operator=(const HttpResponseInfo& rhs);
+ // Even though we could get away with the copy ctor and default operator=,
+ // that would prevent us from doing a bunch of forward declaration.
+
+ // Initializes from the representation stored in the given pickle.
+ bool InitFromPickle(const Pickle& pickle, bool* response_truncated);
+
+ // Call this method to persist the response info.
+ void Persist(Pickle* pickle,
+ bool skip_transient_headers,
+ bool response_truncated) const;
+
+ // The following is only defined if the request_time member is set.
+ // If this response was resurrected from cache, then this bool is set, and
+ // request_time may corresponds to a time "far" in the past. Note that
+ // stale content (perhaps un-cacheable) may be fetched from cache subject to
+ // the load flags specified on the request info. For example, this is done
+ // when a user presses the back button to re-render pages, or at startup,
+ // when reloading previously visited pages (without going over the network).
+ bool was_cached;
+
+ // True if the request was fetched from cache rather than the network
+ // because of a LOAD_FROM_CACHE_IF_OFFLINE flag when the system
+ // was unable to contact the server.
+ bool server_data_unavailable;
+
+ // True if the request accessed the network in the process of retrieving
+ // data.
+ bool network_accessed;
+
+ // True if the request was fetched over a SPDY channel.
+ bool was_fetched_via_spdy;
+
+ // True if the npn was negotiated for this request.
+ bool was_npn_negotiated;
+
+ // True if the request was fetched via an explicit proxy. The proxy could
+ // be any type of proxy, HTTP or SOCKS. Note, we do not know if a
+ // transparent proxy may have been involved.
+ bool was_fetched_via_proxy;
+
+ // Whether the request use http proxy or server authentication.
+ bool did_use_http_auth;
+
+ // Remote address of the socket which fetched this resource.
+ //
+ // NOTE: If the response was served from the cache (was_cached is true),
+ // the socket address will be set to the address that the content came from
+ // originally. This is true even if the response was re-validated using a
+ // different remote address, or if some of the content came from a byte-range
+ // request to a different address.
+ HostPortPair socket_address;
+
+ // Protocol negotiated with the server.
+ std::string npn_negotiated_protocol;
+
+ // The type of connection used for this response.
+ ConnectionInfo connection_info;
+
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this is the last time the cache entry was validated.
+ base::Time request_time;
+
+ // The time at which the response headers were received. For cached
+ // this is the last time the cache entry was validated.
+ base::Time response_time;
+
+ // If the response headers indicate a 401 or 407 failure, then this structure
+ // will contain additional information about the authentication challenge.
+ scoped_refptr<AuthChallengeInfo> auth_challenge;
+
+ // The SSL client certificate request info.
+ // TODO(wtc): does this really belong in HttpResponseInfo? I put it here
+ // because it is similar to |auth_challenge|, but unlike HTTP authentication
+ // challenge, client certificate request is not part of an HTTP response.
+ scoped_refptr<SSLCertRequestInfo> cert_request_info;
+
+ // The SSL connection info (if HTTPS).
+ SSLInfo ssl_info;
+
+ // The parsed response headers and status line.
+ scoped_refptr<HttpResponseHeaders> headers;
+
+ // The "Vary" header data for this response.
+ HttpVaryData vary_data;
+
+ // Any metadata asociated with this resource's cached data.
+ scoped_refptr<IOBufferWithSize> metadata;
+
+ static ConnectionInfo ConnectionInfoFromNextProto(NextProto next_proto);
+
+ static std::string ConnectionInfoToString(ConnectionInfo connection_info);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_RESPONSE_INFO_H_
diff --git a/chromium/net/http/http_security_headers.cc b/chromium/net/http/http_security_headers.cc
new file mode 100644
index 00000000000..9fc7627cc5e
--- /dev/null
+++ b/chromium/net/http/http_security_headers.cc
@@ -0,0 +1,334 @@
+// Copyright (c) 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.
+
+#include "base/base64.h"
+#include "base/basictypes.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_security_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+namespace {
+
+COMPILE_ASSERT(kMaxHSTSAgeSecs <= kuint32max, kMaxHSTSAgeSecsTooLarge);
+
+// MaxAgeToInt converts a string representation of a "whole number" of
+// seconds into a uint32. The string may contain an arbitrarily large number,
+// which will be clipped to kMaxHSTSAgeSecs and which is guaranteed to fit
+// within a 32-bit unsigned integer. False is returned on any parse error.
+bool MaxAgeToInt(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ uint32* result) {
+ const std::string s(begin, end);
+ int64 i = 0;
+
+ // Return false on any StringToInt64 parse errors *except* for
+ // int64 overflow. StringToInt64 is used, rather than StringToUint64,
+ // in order to properly handle and reject negative numbers
+ // (StringToUint64 does not return false on negative numbers).
+ // For values too large to be stored in an int64, StringToInt64 will
+ // return false with i set to kint64max, so this case is detected
+ // by the immediately following if-statement and allowed to fall
+ // through so that i gets clipped to kMaxHSTSAgeSecs.
+ if (!base::StringToInt64(s, &i) && i != kint64max)
+ return false;
+ if (i < 0)
+ return false;
+ if (i > kMaxHSTSAgeSecs)
+ i = kMaxHSTSAgeSecs;
+ *result = (uint32)i;
+ return true;
+}
+
+// Returns true iff there is an item in |pins| which is not present in
+// |from_cert_chain|. Such an SPKI hash is called a "backup pin".
+bool IsBackupPinPresent(const HashValueVector& pins,
+ const HashValueVector& from_cert_chain) {
+ for (HashValueVector::const_iterator i = pins.begin(); i != pins.end();
+ ++i) {
+ HashValueVector::const_iterator j =
+ std::find_if(from_cert_chain.begin(), from_cert_chain.end(),
+ HashValuesEqual(*i));
+ if (j == from_cert_chain.end())
+ return true;
+ }
+
+ return false;
+}
+
+// Returns true if the intersection of |a| and |b| is not empty. If either
+// |a| or |b| is empty, returns false.
+bool HashesIntersect(const HashValueVector& a,
+ const HashValueVector& b) {
+ for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) {
+ HashValueVector::const_iterator j =
+ std::find_if(b.begin(), b.end(), HashValuesEqual(*i));
+ if (j != b.end())
+ return true;
+ }
+ return false;
+}
+
+// Returns true iff |pins| contains both a live and a backup pin. A live pin
+// is a pin whose SPKI is present in the certificate chain in |ssl_info|. A
+// backup pin is a pin intended for disaster recovery, not day-to-day use, and
+// thus must be absent from the certificate chain. The Public-Key-Pins header
+// specification requires both.
+bool IsPinListValid(const HashValueVector& pins,
+ const HashValueVector& from_cert_chain) {
+ // Fast fail: 1 live + 1 backup = at least 2 pins. (Check for actual
+ // liveness and backupness below.)
+ if (pins.size() < 2)
+ return false;
+
+ if (from_cert_chain.empty())
+ return false;
+
+ return IsBackupPinPresent(pins, from_cert_chain) &&
+ HashesIntersect(pins, from_cert_chain);
+}
+
+std::string Strip(const std::string& source) {
+ if (source.empty())
+ return source;
+
+ std::string::const_iterator start = source.begin();
+ std::string::const_iterator end = source.end();
+ HttpUtil::TrimLWS(&start, &end);
+ return std::string(start, end);
+}
+
+typedef std::pair<std::string, std::string> StringPair;
+
+StringPair Split(const std::string& source, char delimiter) {
+ StringPair pair;
+ size_t point = source.find(delimiter);
+
+ pair.first = source.substr(0, point);
+ if (std::string::npos != point)
+ pair.second = source.substr(point + 1);
+
+ return pair;
+}
+
+bool ParseAndAppendPin(const std::string& value,
+ HashValueTag tag,
+ HashValueVector* hashes) {
+ std::string unquoted = HttpUtil::Unquote(value);
+ std::string decoded;
+
+ if (unquoted.empty())
+ return false;
+
+ if (!base::Base64Decode(unquoted, &decoded))
+ return false;
+
+ HashValue hash(tag);
+ if (decoded.size() != hash.size())
+ return false;
+
+ memcpy(hash.data(), decoded.data(), hash.size());
+ hashes->push_back(hash);
+ return true;
+}
+
+} // namespace
+
+// Parse the Strict-Transport-Security header, as currently defined in
+// http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14:
+//
+// Strict-Transport-Security = "Strict-Transport-Security" ":"
+// [ directive ] *( ";" [ directive ] )
+//
+// directive = directive-name [ "=" directive-value ]
+// directive-name = token
+// directive-value = token | quoted-string
+//
+// 1. The order of appearance of directives is not significant.
+//
+// 2. All directives MUST appear only once in an STS header field.
+// Directives are either optional or required, as stipulated in
+// their definitions.
+//
+// 3. Directive names are case-insensitive.
+//
+// 4. UAs MUST ignore any STS header fields containing directives, or
+// other header field value data, that does not conform to the
+// syntax defined in this specification.
+//
+// 5. If an STS header field contains directive(s) not recognized by
+// the UA, the UA MUST ignore the unrecognized directives and if the
+// STS header field otherwise satisfies the above requirements (1
+// through 4), the UA MUST process the recognized directives.
+bool ParseHSTSHeader(const std::string& value,
+ base::TimeDelta* max_age,
+ bool* include_subdomains) {
+ uint32 max_age_candidate = 0;
+ bool include_subdomains_candidate = false;
+
+ // We must see max-age exactly once.
+ int max_age_observed = 0;
+ // We must see includeSubdomains exactly 0 or 1 times.
+ int include_subdomains_observed = 0;
+
+ enum ParserState {
+ START,
+ AFTER_MAX_AGE_LABEL,
+ AFTER_MAX_AGE_EQUALS,
+ AFTER_MAX_AGE,
+ AFTER_INCLUDE_SUBDOMAINS,
+ AFTER_UNKNOWN_LABEL,
+ DIRECTIVE_END
+ } state = START;
+
+ base::StringTokenizer tokenizer(value, " \t=;");
+ tokenizer.set_options(base::StringTokenizer::RETURN_DELIMS);
+ tokenizer.set_quote_chars("\"");
+ std::string unquoted;
+ while (tokenizer.GetNext()) {
+ DCHECK(!tokenizer.token_is_delim() || tokenizer.token().length() == 1);
+ switch (state) {
+ case START:
+ case DIRECTIVE_END:
+ if (IsAsciiWhitespace(*tokenizer.token_begin()))
+ continue;
+ if (LowerCaseEqualsASCII(tokenizer.token(), "max-age")) {
+ state = AFTER_MAX_AGE_LABEL;
+ max_age_observed++;
+ } else if (LowerCaseEqualsASCII(tokenizer.token(),
+ "includesubdomains")) {
+ state = AFTER_INCLUDE_SUBDOMAINS;
+ include_subdomains_observed++;
+ include_subdomains_candidate = true;
+ } else {
+ state = AFTER_UNKNOWN_LABEL;
+ }
+ break;
+
+ case AFTER_MAX_AGE_LABEL:
+ if (IsAsciiWhitespace(*tokenizer.token_begin()))
+ continue;
+ if (*tokenizer.token_begin() != '=')
+ return false;
+ DCHECK_EQ(tokenizer.token().length(), 1U);
+ state = AFTER_MAX_AGE_EQUALS;
+ break;
+
+ case AFTER_MAX_AGE_EQUALS:
+ if (IsAsciiWhitespace(*tokenizer.token_begin()))
+ continue;
+ unquoted = HttpUtil::Unquote(tokenizer.token());
+ if (!MaxAgeToInt(unquoted.begin(), unquoted.end(), &max_age_candidate))
+ return false;
+ state = AFTER_MAX_AGE;
+ break;
+
+ case AFTER_MAX_AGE:
+ case AFTER_INCLUDE_SUBDOMAINS:
+ if (IsAsciiWhitespace(*tokenizer.token_begin()))
+ continue;
+ else if (*tokenizer.token_begin() == ';')
+ state = DIRECTIVE_END;
+ else
+ return false;
+ break;
+
+ case AFTER_UNKNOWN_LABEL:
+ // Consume and ignore the post-label contents (if any).
+ if (*tokenizer.token_begin() != ';')
+ continue;
+ state = DIRECTIVE_END;
+ break;
+ }
+ }
+
+ // We've consumed all the input. Let's see what state we ended up in.
+ if (max_age_observed != 1 ||
+ (include_subdomains_observed != 0 && include_subdomains_observed != 1)) {
+ return false;
+ }
+
+ switch (state) {
+ case AFTER_MAX_AGE:
+ case AFTER_INCLUDE_SUBDOMAINS:
+ case AFTER_UNKNOWN_LABEL:
+ *max_age = base::TimeDelta::FromSeconds(max_age_candidate);
+ *include_subdomains = include_subdomains_candidate;
+ return true;
+ case START:
+ case DIRECTIVE_END:
+ case AFTER_MAX_AGE_LABEL:
+ case AFTER_MAX_AGE_EQUALS:
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+// "Public-Key-Pins" ":"
+// "max-age" "=" delta-seconds ";"
+// "pin-" algo "=" base64 [ ";" ... ]
+bool ParseHPKPHeader(const std::string& value,
+ const HashValueVector& chain_hashes,
+ base::TimeDelta* max_age,
+ bool* include_subdomains,
+ HashValueVector* hashes) {
+ bool parsed_max_age = false;
+ bool include_subdomains_candidate = false;
+ uint32 max_age_candidate = 0;
+ HashValueVector pins;
+
+ std::string source = value;
+
+ while (!source.empty()) {
+ StringPair semicolon = Split(source, ';');
+ semicolon.first = Strip(semicolon.first);
+ semicolon.second = Strip(semicolon.second);
+ StringPair equals = Split(semicolon.first, '=');
+ equals.first = Strip(equals.first);
+ equals.second = Strip(equals.second);
+
+ if (LowerCaseEqualsASCII(equals.first, "max-age")) {
+ if (equals.second.empty() ||
+ !MaxAgeToInt(equals.second.begin(), equals.second.end(),
+ &max_age_candidate)) {
+ return false;
+ }
+ parsed_max_age = true;
+ } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) {
+ if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA1, &pins))
+ return false;
+ } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
+ if (!ParseAndAppendPin(equals.second, HASH_VALUE_SHA256, &pins))
+ return false;
+ } else if (LowerCaseEqualsASCII(equals.first, "includesubdomains")) {
+ include_subdomains_candidate = true;
+ } else {
+ // Silently ignore unknown directives for forward compatibility.
+ }
+
+ source = semicolon.second;
+ }
+
+ if (!parsed_max_age)
+ return false;
+
+ if (!IsPinListValid(pins, chain_hashes))
+ return false;
+
+ *max_age = base::TimeDelta::FromSeconds(max_age_candidate);
+ *include_subdomains = include_subdomains_candidate;
+ for (HashValueVector::const_iterator i = pins.begin();
+ i != pins.end(); ++i) {
+ hashes->push_back(*i);
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_security_headers.h b/chromium/net/http/http_security_headers.h
new file mode 100644
index 00000000000..12e6be9000a
--- /dev/null
+++ b/chromium/net/http/http_security_headers.h
@@ -0,0 +1,59 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_SECURITY_HEADERS_H_
+#define NET_HTTP_HTTP_SECURITY_HEADERS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/hash_value.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+const int64 kMaxHSTSAgeSecs = 86400 * 365; // 1 year
+
+// Parses |value| as a Strict-Transport-Security header value. If successful,
+// returns true and sets |*max_age| and |*include_subdomains|.
+// Otherwise returns false and leaves the output parameters unchanged.
+//
+// value is the right-hand side of:
+//
+// "Strict-Transport-Security" ":"
+// [ directive ] *( ";" [ directive ] )
+bool NET_EXPORT_PRIVATE ParseHSTSHeader(const std::string& value,
+ base::TimeDelta* max_age,
+ bool* include_subdomains);
+
+// Parses |value| as a Public-Key-Pins header value. If successful, returns
+// true and populates the |*max_age|, |*include_subdomains|, and |*hashes|
+// values. Otherwise returns false and leaves the output parameters
+// unchanged.
+//
+// value is the right-hand side of:
+//
+// "Public-Key-Pins" ":"
+// "max-age" "=" delta-seconds ";"
+// "pin-" algo "=" base64 [ ";" ... ]
+// [ ";" "includeSubdomains" ]
+//
+// For this function to return true, the key hashes specified by the HPKP
+// header must pass two additional checks. There MUST be at least one key
+// hash which matches the SSL certificate chain of the current site (as
+// specified by the chain_hashes) parameter. In addition, there MUST be at
+// least one key hash which does NOT match the site's SSL certificate chain
+// (this is the "backup pin").
+bool NET_EXPORT_PRIVATE ParseHPKPHeader(const std::string& value,
+ const HashValueVector& chain_hashes,
+ base::TimeDelta* max_age,
+ bool* include_subdomains,
+ HashValueVector* hashes);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_SECURITY_HEADERS_H_
diff --git a/chromium/net/http/http_security_headers_unittest.cc b/chromium/net/http/http_security_headers_unittest.cc
new file mode 100644
index 00000000000..0cc81b57eb8
--- /dev/null
+++ b/chromium/net/http/http_security_headers_unittest.cc
@@ -0,0 +1,505 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+
+#include "base/base64.h"
+#include "base/sha1.h"
+#include "base/strings/string_piece.h"
+#include "crypto/sha2.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_security_headers.h"
+#include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/ssl/ssl_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+HashValue GetTestHashValue(uint8 label, HashValueTag tag) {
+ HashValue hash_value(tag);
+ memset(hash_value.data(), label, hash_value.size());
+ return hash_value;
+}
+
+std::string GetTestPin(uint8 label, HashValueTag tag) {
+ HashValue hash_value = GetTestHashValue(label, tag);
+ std::string base64;
+ base::Base64Encode(base::StringPiece(
+ reinterpret_cast<char*>(hash_value.data()), hash_value.size()), &base64);
+
+ switch (hash_value.tag) {
+ case HASH_VALUE_SHA1:
+ return std::string("pin-sha1=\"") + base64 + "\"";
+ case HASH_VALUE_SHA256:
+ return std::string("pin-sha256=\"") + base64 + "\"";
+ default:
+ NOTREACHED() << "Unknown HashValueTag " << hash_value.tag;
+ return std::string("ERROR");
+ }
+}
+
+};
+
+
+class HttpSecurityHeadersTest : public testing::Test {
+};
+
+
+TEST_F(HttpSecurityHeadersTest, BogusHeaders) {
+ base::TimeDelta max_age;
+ bool include_subdomains = false;
+
+ EXPECT_FALSE(
+ ParseHSTSHeader(std::string(), &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" ", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("abc", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" abc", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" abc ", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age ", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=", &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age=", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age =", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age= ", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age = ", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age = xy", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader(" max-age = 3488a923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488a923 ", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-ag=3488923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-aged=3488923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age==3488923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("amax-age=3488923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=-3488923", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923;", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 e", &max_age,
+ &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomain",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923includesubdomains",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923=includesubdomains",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomainx",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomain=",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomain=true",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomainsx",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=3488923 includesubdomains x",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=34889.23 includesubdomains",
+ &max_age, &include_subdomains));
+ EXPECT_FALSE(ParseHSTSHeader("max-age=34889 includesubdomains",
+ &max_age, &include_subdomains));
+
+ // Check the out args were not updated by checking the default
+ // values for its predictable fields.
+ EXPECT_EQ(0, max_age.InSeconds());
+ EXPECT_FALSE(include_subdomains);
+}
+
+static void TestBogusPinsHeaders(HashValueTag tag) {
+ base::TimeDelta max_age;
+ bool include_subdomains;
+ HashValueVector hashes;
+ HashValueVector chain_hashes;
+
+ // Set some fake "chain" hashes
+ chain_hashes.push_back(GetTestHashValue(1, tag));
+ chain_hashes.push_back(GetTestHashValue(2, tag));
+ chain_hashes.push_back(GetTestHashValue(3, tag));
+
+ // The good pin must be in the chain, the backup pin must not be
+ std::string good_pin = GetTestPin(2, tag);
+ std::string backup_pin = GetTestPin(4, tag);
+
+ EXPECT_FALSE(ParseHPKPHeader(std::string(), chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" ", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("abc", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" abc", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" abc ", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age ", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age=", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age =", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age= ", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age = ", chain_hashes,
+ &max_age, &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age = xy", chain_hashes,
+ &max_age, &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader(" max-age = 3488a923",
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=3488a923 ", chain_hashes,
+ &max_age, &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-ag=3488923pins=" + good_pin + "," +
+ backup_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923" + backup_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + backup_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + backup_pin + ";" +
+ backup_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + good_pin + ";" +
+ good_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-aged=3488923; " + good_pin,
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age==3488923", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("amax-age=3488923", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=-3488923", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=3488923;", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=3488923 e", chain_hashes,
+ &max_age, &include_subdomains, &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=3488923 includesubdomain",
+ chain_hashes, &max_age, &include_subdomains,
+ &hashes));
+ EXPECT_FALSE(ParseHPKPHeader("max-age=34889.23", chain_hashes, &max_age,
+ &include_subdomains, &hashes));
+
+ // Check the out args were not updated by checking the default
+ // values for its predictable fields.
+ EXPECT_EQ(0, max_age.InSeconds());
+ EXPECT_EQ(hashes.size(), (size_t)0);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidSTSHeaders) {
+ base::TimeDelta max_age;
+ base::TimeDelta expect_max_age;
+ bool include_subdomains = false;
+
+ EXPECT_TRUE(ParseHSTSHeader("max-age=243", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(243);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(" Max-agE = 567", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(567);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(" mAx-aGe = 890 ", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(890);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader("max-age=123;incLudesUbdOmains", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader("incLudesUbdOmains; max-age=123", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(" incLudesUbdOmains; max-age=123",
+ &max_age, &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " incLudesUbdOmains; max-age=123; pumpkin=kitten", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " pumpkin=894; incLudesUbdOmains; max-age=123 ", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " pumpkin; incLudesUbdOmains; max-age=123 ", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " pumpkin; incLudesUbdOmains; max-age=\"123\" ", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ "animal=\"squirrel; distinguished\"; incLudesUbdOmains; max-age=123",
+ &max_age, &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader("max-age=394082; incLudesUbdOmains",
+ &max_age, &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(394082);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ "max-age=39408299 ;incLudesUbdOmains", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(39408299))));
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ "max-age=394082038 ; incLudesUbdOmains", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(394082038))));
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " max-age=0 ; incLudesUbdOmains ", &max_age,
+ &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHSTSHeader(
+ " max-age=999999999999999999999999999999999999999999999 ;"
+ " incLudesUbdOmains ", &max_age, &include_subdomains));
+ expect_max_age = base::TimeDelta::FromSeconds(
+ kMaxHSTSAgeSecs);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+}
+
+static void TestValidPKPHeaders(HashValueTag tag) {
+ base::TimeDelta max_age;
+ base::TimeDelta expect_max_age;
+ bool include_subdomains;
+ HashValueVector hashes;
+ HashValueVector chain_hashes;
+
+ // Set some fake "chain" hashes into chain_hashes
+ chain_hashes.push_back(GetTestHashValue(1, tag));
+ chain_hashes.push_back(GetTestHashValue(2, tag));
+ chain_hashes.push_back(GetTestHashValue(3, tag));
+
+ // The good pin must be in the chain, the backup pin must not be
+ std::string good_pin = GetTestPin(2, tag);
+ std::string backup_pin = GetTestPin(4, tag);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ "max-age=243; " + good_pin + ";" + backup_pin,
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(243);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ " " + good_pin + "; " + backup_pin + " ; Max-agE = 567",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(567);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ "includeSubDOMAINS;" + good_pin + ";" + backup_pin +
+ " ; mAx-aGe = 890 ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(890);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ good_pin + ";" + backup_pin + "; max-age=123;IGNORED;",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(123);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ "max-age=394082;" + backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(394082);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ "max-age=39408299 ;" + backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(39408299))));
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ "max-age=39408038 ; cybers=39408038 ; includeSubdomains; " +
+ good_pin + ";" + backup_pin + "; ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(
+ std::min(kMaxHSTSAgeSecs, static_cast<int64>(GG_INT64_C(394082038))));
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ " max-age=0 ; " + good_pin + ";" + backup_pin,
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ " max-age=0 ; includeSubdomains; " + good_pin + ";" + backup_pin,
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(0);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_TRUE(include_subdomains);
+
+ EXPECT_TRUE(ParseHPKPHeader(
+ " max-age=999999999999999999999999999999999999999999999 ; " +
+ backup_pin + ";" + good_pin + "; ",
+ chain_hashes, &max_age, &include_subdomains, &hashes));
+ expect_max_age = base::TimeDelta::FromSeconds(kMaxHSTSAgeSecs);
+ EXPECT_EQ(expect_max_age, max_age);
+ EXPECT_FALSE(include_subdomains);
+}
+
+TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA1) {
+ TestBogusPinsHeaders(HASH_VALUE_SHA1);
+}
+
+TEST_F(HttpSecurityHeadersTest, BogusPinsHeadersSHA256) {
+ TestBogusPinsHeaders(HASH_VALUE_SHA256);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidPKPHeadersSHA1) {
+ TestValidPKPHeaders(HASH_VALUE_SHA1);
+}
+
+TEST_F(HttpSecurityHeadersTest, ValidPKPHeadersSHA256) {
+ TestValidPKPHeaders(HASH_VALUE_SHA256);
+}
+
+TEST_F(HttpSecurityHeadersTest, UpdateDynamicPKPOnly) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ // docs.google.com has preloaded pins.
+ std::string domain = "docs.google.com";
+ EXPECT_TRUE(state.GetDomainState(domain, true, &domain_state));
+ EXPECT_GT(domain_state.static_spki_hashes.size(), 1UL);
+ HashValueVector saved_hashes = domain_state.static_spki_hashes;
+
+ // Add a header, which should only update the dynamic state.
+ HashValue good_hash = GetTestHashValue(1, HASH_VALUE_SHA1);
+ HashValue backup_hash = GetTestHashValue(2, HASH_VALUE_SHA1);
+ std::string good_pin = GetTestPin(1, HASH_VALUE_SHA1);
+ std::string backup_pin = GetTestPin(2, HASH_VALUE_SHA1);
+ std::string header = "max-age = 10000; " + good_pin + "; " + backup_pin;
+
+ // Construct a fake SSLInfo that will pass AddHPKPHeader's checks.
+ SSLInfo ssl_info;
+ ssl_info.public_key_hashes.push_back(good_hash);
+ ssl_info.public_key_hashes.push_back(saved_hashes[0]);
+ EXPECT_TRUE(state.AddHPKPHeader(domain, header, ssl_info));
+
+ // Expect the preloaded state to remain unchanged.
+ std::string canonicalized_host = TransportSecurityState::CanonicalizeHost(
+ domain);
+ TransportSecurityState::DomainState static_domain_state;
+ EXPECT_TRUE(state.GetStaticDomainState(canonicalized_host,
+ true,
+ &static_domain_state));
+ for (size_t i = 0; i < saved_hashes.size(); ++i) {
+ EXPECT_TRUE(HashValuesEqual(
+ saved_hashes[i])(static_domain_state.static_spki_hashes[i]));
+ }
+
+ // Expect the dynamic state to reflect the header.
+ TransportSecurityState::DomainState dynamic_domain_state;
+ EXPECT_TRUE(state.GetDynamicDomainState(domain, &dynamic_domain_state));
+ EXPECT_EQ(2UL, dynamic_domain_state.dynamic_spki_hashes.size());
+
+ HashValueVector::const_iterator hash = std::find_if(
+ dynamic_domain_state.dynamic_spki_hashes.begin(),
+ dynamic_domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(good_hash));
+ EXPECT_NE(dynamic_domain_state.dynamic_spki_hashes.end(), hash);
+
+ hash = std::find_if(
+ dynamic_domain_state.dynamic_spki_hashes.begin(),
+ dynamic_domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(backup_hash));
+ EXPECT_NE(dynamic_domain_state.dynamic_spki_hashes.end(), hash);
+
+ // Expect the overall state to reflect the header, too.
+ EXPECT_TRUE(state.GetDomainState(domain, true, &domain_state));
+ EXPECT_EQ(2UL, domain_state.dynamic_spki_hashes.size());
+
+ hash = std::find_if(domain_state.dynamic_spki_hashes.begin(),
+ domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(good_hash));
+ EXPECT_NE(domain_state.dynamic_spki_hashes.end(), hash);
+
+ hash = std::find_if(
+ domain_state.dynamic_spki_hashes.begin(),
+ domain_state.dynamic_spki_hashes.end(),
+ HashValuesEqual(backup_hash));
+ EXPECT_NE(domain_state.dynamic_spki_hashes.end(), hash);
+}
+
+}; // namespace net
diff --git a/chromium/net/http/http_server_properties.cc b/chromium/net/http/http_server_properties.cc
new file mode 100644
index 00000000000..bff262ade45
--- /dev/null
+++ b/chromium/net/http/http_server_properties.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 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.
+
+#include "net/http/http_server_properties.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace net {
+
+const char kAlternateProtocolHeader[] = "Alternate-Protocol";
+
+namespace {
+
+// The order of these strings much match the order of the enum definition
+// for AlternateProtocol.
+const char* const kAlternateProtocolStrings[] = {
+ "npn-spdy/1",
+ "npn-spdy/2",
+ "npn-spdy/3",
+ "npn-spdy/3.1",
+ "npn-spdy/4a2",
+ "npn-HTTP-draft-04/2.0",
+ "quic"
+};
+const char kBrokenAlternateProtocol[] = "Broken";
+
+COMPILE_ASSERT(arraysize(kAlternateProtocolStrings) == NUM_ALTERNATE_PROTOCOLS,
+ kAlternateProtocolStringsSize_NUM_ALTERNATE_PROTOCOLS_nut_equal);
+
+} // namespace
+
+const char* AlternateProtocolToString(AlternateProtocol protocol) {
+ switch (protocol) {
+ case NPN_SPDY_1:
+ case NPN_SPDY_2:
+ case NPN_SPDY_3:
+ case NPN_SPDY_3_1:
+ case NPN_SPDY_4A2:
+ case NPN_HTTP2_DRAFT_04:
+ case QUIC:
+ DCHECK_LT(static_cast<size_t>(protocol),
+ arraysize(kAlternateProtocolStrings));
+ return kAlternateProtocolStrings[protocol];
+ case NUM_ALTERNATE_PROTOCOLS:
+ break;
+ case ALTERNATE_PROTOCOL_BROKEN:
+ return kBrokenAlternateProtocol;
+ case UNINITIALIZED_ALTERNATE_PROTOCOL:
+ return "Uninitialized";
+ }
+ NOTREACHED();
+ return "";
+}
+
+AlternateProtocol AlternateProtocolFromString(const std::string& protocol) {
+ for (int i = NPN_SPDY_1; i < NUM_ALTERNATE_PROTOCOLS; ++i)
+ if (protocol == kAlternateProtocolStrings[i])
+ return static_cast<AlternateProtocol>(i);
+ if (protocol == kBrokenAlternateProtocol)
+ return ALTERNATE_PROTOCOL_BROKEN;
+ return UNINITIALIZED_ALTERNATE_PROTOCOL;
+}
+
+AlternateProtocol AlternateProtocolFromNextProto(NextProto next_proto) {
+ switch (next_proto) {
+ case kProtoSPDY2:
+ return NPN_SPDY_2;
+ case kProtoSPDY3:
+ return NPN_SPDY_3;
+ case kProtoSPDY31:
+ return NPN_SPDY_3_1;
+ case kProtoSPDY4a2:
+ return NPN_SPDY_4A2;
+ case kProtoHTTP2Draft04:
+ return NPN_HTTP2_DRAFT_04;
+ case kProtoQUIC1SPDY3:
+ return QUIC;
+
+ case kProtoUnknown:
+ case kProtoHTTP11:
+ case kProtoSPDY1:
+ case kProtoSPDY21:
+ break;
+ }
+
+ NOTREACHED() << "Invalid NextProto: " << next_proto;
+ return UNINITIALIZED_ALTERNATE_PROTOCOL;
+}
+
+std::string PortAlternateProtocolPair::ToString() const {
+ return base::StringPrintf("%d:%s", port,
+ AlternateProtocolToString(protocol));
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_server_properties.h b/chromium/net/http/http_server_properties.h
new file mode 100644
index 00000000000..654d262024a
--- /dev/null
+++ b/chromium/net/http/http_server_properties.h
@@ -0,0 +1,141 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_SERVER_PROPERTIES_H_
+#define NET_HTTP_HTTP_SERVER_PROPERTIES_H_
+
+#include <map>
+#include <string>
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_pipelined_host_capability.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_framer.h" // TODO(willchan): Reconsider this.
+
+namespace net {
+
+enum AlternateProtocol {
+ NPN_SPDY_1 = 0,
+ NPN_SPDY_MINIMUM_VERSION = NPN_SPDY_1,
+ NPN_SPDY_2,
+ NPN_SPDY_3,
+ NPN_SPDY_3_1,
+ NPN_SPDY_4A2,
+ // We lump in HTTP/2 with the SPDY protocols for now.
+ NPN_HTTP2_DRAFT_04,
+ NPN_SPDY_MAXIMUM_VERSION = NPN_HTTP2_DRAFT_04,
+ QUIC,
+ NUM_ALTERNATE_PROTOCOLS,
+ ALTERNATE_PROTOCOL_BROKEN, // The alternate protocol is known to be broken.
+ UNINITIALIZED_ALTERNATE_PROTOCOL,
+};
+
+NET_EXPORT const char* AlternateProtocolToString(AlternateProtocol protocol);
+NET_EXPORT AlternateProtocol AlternateProtocolFromString(
+ const std::string& protocol);
+NET_EXPORT_PRIVATE AlternateProtocol AlternateProtocolFromNextProto(
+ NextProto next_proto);
+
+struct NET_EXPORT PortAlternateProtocolPair {
+ bool Equals(const PortAlternateProtocolPair& other) const {
+ return port == other.port && protocol == other.protocol;
+ }
+
+ std::string ToString() const;
+
+ uint16 port;
+ AlternateProtocol protocol;
+};
+
+typedef std::map<HostPortPair, PortAlternateProtocolPair> AlternateProtocolMap;
+typedef std::map<HostPortPair, SettingsMap> SpdySettingsMap;
+typedef std::map<HostPortPair,
+ HttpPipelinedHostCapability> PipelineCapabilityMap;
+
+extern const char kAlternateProtocolHeader[];
+
+// The interface for setting/retrieving the HTTP server properties.
+// Currently, this class manages servers':
+// * SPDY support (based on NPN results)
+// * Alternate-Protocol support
+// * Spdy Settings (like CWND ID field)
+class NET_EXPORT HttpServerProperties {
+ public:
+ HttpServerProperties() {}
+ virtual ~HttpServerProperties() {}
+
+ // Gets a weak pointer for this object.
+ virtual base::WeakPtr<HttpServerProperties> GetWeakPtr() = 0;
+
+ // Deletes all data.
+ virtual void Clear() = 0;
+
+ // Returns true if |server| supports SPDY.
+ virtual bool SupportsSpdy(const HostPortPair& server) const = 0;
+
+ // Add |server| into the persistent store. Should only be called from IO
+ // thread.
+ virtual void SetSupportsSpdy(const HostPortPair& server,
+ bool support_spdy) = 0;
+
+ // Returns true if |server| has an Alternate-Protocol header.
+ virtual bool HasAlternateProtocol(const HostPortPair& server) const = 0;
+
+ // Returns the Alternate-Protocol and port for |server|.
+ // HasAlternateProtocol(server) must be true.
+ virtual PortAlternateProtocolPair GetAlternateProtocol(
+ const HostPortPair& server) const = 0;
+
+ // Sets the Alternate-Protocol for |server|.
+ virtual void SetAlternateProtocol(const HostPortPair& server,
+ uint16 alternate_port,
+ AlternateProtocol alternate_protocol) = 0;
+
+ // Sets the Alternate-Protocol for |server| to be BROKEN.
+ virtual void SetBrokenAlternateProtocol(const HostPortPair& server) = 0;
+
+ // Returns all Alternate-Protocol mappings.
+ virtual const AlternateProtocolMap& alternate_protocol_map() const = 0;
+
+ // Gets a reference to the SettingsMap stored for a host.
+ // If no settings are stored, returns an empty SettingsMap.
+ virtual const SettingsMap& GetSpdySettings(
+ const HostPortPair& host_port_pair) const = 0;
+
+ // Saves an individual SPDY setting for a host. Returns true if SPDY setting
+ // is to be persisted.
+ virtual bool SetSpdySetting(const HostPortPair& host_port_pair,
+ SpdySettingsIds id,
+ SpdySettingsFlags flags,
+ uint32 value) = 0;
+
+ // Clears all SPDY settings for a host.
+ virtual void ClearSpdySettings(const HostPortPair& host_port_pair) = 0;
+
+ // Clears all SPDY settings for all hosts.
+ virtual void ClearAllSpdySettings() = 0;
+
+ // Returns all persistent SPDY settings.
+ virtual const SpdySettingsMap& spdy_settings_map() const = 0;
+
+ virtual HttpPipelinedHostCapability GetPipelineCapability(
+ const HostPortPair& origin) = 0;
+
+ virtual void SetPipelineCapability(
+ const HostPortPair& origin,
+ HttpPipelinedHostCapability capability) = 0;
+
+ virtual void ClearPipelineCapabilities() = 0;
+
+ virtual PipelineCapabilityMap GetPipelineCapabilityMap() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpServerProperties);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_SERVER_PROPERTIES_H_
diff --git a/chromium/net/http/http_server_properties_impl.cc b/chromium/net/http/http_server_properties_impl.cc
new file mode 100644
index 00000000000..a0b287de588
--- /dev/null
+++ b/chromium/net/http/http_server_properties_impl.cc
@@ -0,0 +1,303 @@
+// Copyright (c) 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.
+
+#include "net/http/http_server_properties_impl.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_pipelined_host_capability.h"
+
+namespace net {
+
+// TODO(simonjam): Run experiments with different values of this to see what
+// value is good at avoiding evictions without eating too much memory. Until
+// then, this is just a bad guess.
+static const int kDefaultNumHostsToRemember = 200;
+
+HttpServerPropertiesImpl::HttpServerPropertiesImpl()
+ : weak_ptr_factory_(this),
+ pipeline_capability_map_(
+ new CachedPipelineCapabilityMap(kDefaultNumHostsToRemember)) {
+}
+
+HttpServerPropertiesImpl::~HttpServerPropertiesImpl() {
+}
+
+void HttpServerPropertiesImpl::InitializeSpdyServers(
+ std::vector<std::string>* spdy_servers,
+ bool support_spdy) {
+ DCHECK(CalledOnValidThread());
+ spdy_servers_table_.clear();
+ if (!spdy_servers)
+ return;
+ for (std::vector<std::string>::iterator it = spdy_servers->begin();
+ it != spdy_servers->end(); ++it) {
+ spdy_servers_table_[*it] = support_spdy;
+ }
+}
+
+void HttpServerPropertiesImpl::InitializeAlternateProtocolServers(
+ AlternateProtocolMap* alternate_protocol_map) {
+ // First swap, and then add back all the ALTERNATE_PROTOCOL_BROKEN ones since
+ // those don't get persisted.
+ alternate_protocol_map_.swap(*alternate_protocol_map);
+ for (AlternateProtocolMap::const_iterator it =
+ alternate_protocol_map->begin();
+ it != alternate_protocol_map->end(); ++it) {
+ if (it->second.protocol == ALTERNATE_PROTOCOL_BROKEN)
+ alternate_protocol_map_[it->first] = it->second;
+ }
+}
+
+void HttpServerPropertiesImpl::InitializeSpdySettingsServers(
+ SpdySettingsMap* spdy_settings_map) {
+ spdy_settings_map_.swap(*spdy_settings_map);
+}
+
+void HttpServerPropertiesImpl::InitializePipelineCapabilities(
+ const PipelineCapabilityMap* pipeline_capability_map) {
+ PipelineCapabilityMap::const_iterator it;
+ pipeline_capability_map_->Clear();
+ for (it = pipeline_capability_map->begin();
+ it != pipeline_capability_map->end(); ++it) {
+ pipeline_capability_map_->Put(it->first, it->second);
+ }
+}
+
+void HttpServerPropertiesImpl::SetNumPipelinedHostsToRemember(int max_size) {
+ DCHECK(pipeline_capability_map_->empty());
+ pipeline_capability_map_.reset(new CachedPipelineCapabilityMap(max_size));
+}
+
+void HttpServerPropertiesImpl::GetSpdyServerList(
+ base::ListValue* spdy_server_list) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(spdy_server_list);
+ spdy_server_list->Clear();
+ // Get the list of servers (host/port) that support SPDY.
+ for (SpdyServerHostPortTable::const_iterator it = spdy_servers_table_.begin();
+ it != spdy_servers_table_.end(); ++it) {
+ const std::string spdy_server_host_port = it->first;
+ if (it->second)
+ spdy_server_list->Append(new base::StringValue(spdy_server_host_port));
+ }
+}
+
+// static
+std::string HttpServerPropertiesImpl::GetFlattenedSpdyServer(
+ const net::HostPortPair& host_port_pair) {
+ std::string spdy_server;
+ spdy_server.append(host_port_pair.host());
+ spdy_server.append(":");
+ base::StringAppendF(&spdy_server, "%d", host_port_pair.port());
+ return spdy_server;
+}
+
+static const PortAlternateProtocolPair* g_forced_alternate_protocol = NULL;
+
+// static
+void HttpServerPropertiesImpl::ForceAlternateProtocol(
+ const PortAlternateProtocolPair& pair) {
+ // Note: we're going to leak this.
+ if (g_forced_alternate_protocol)
+ delete g_forced_alternate_protocol;
+ g_forced_alternate_protocol = new PortAlternateProtocolPair(pair);
+}
+
+// static
+void HttpServerPropertiesImpl::DisableForcedAlternateProtocol() {
+ delete g_forced_alternate_protocol;
+ g_forced_alternate_protocol = NULL;
+}
+
+base::WeakPtr<HttpServerProperties> HttpServerPropertiesImpl::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void HttpServerPropertiesImpl::Clear() {
+ DCHECK(CalledOnValidThread());
+ spdy_servers_table_.clear();
+ alternate_protocol_map_.clear();
+ spdy_settings_map_.clear();
+ pipeline_capability_map_->Clear();
+}
+
+bool HttpServerPropertiesImpl::SupportsSpdy(
+ const net::HostPortPair& host_port_pair) const {
+ DCHECK(CalledOnValidThread());
+ if (host_port_pair.host().empty())
+ return false;
+ std::string spdy_server = GetFlattenedSpdyServer(host_port_pair);
+
+ SpdyServerHostPortTable::const_iterator spdy_host_port =
+ spdy_servers_table_.find(spdy_server);
+ if (spdy_host_port != spdy_servers_table_.end())
+ return spdy_host_port->second;
+ return false;
+}
+
+void HttpServerPropertiesImpl::SetSupportsSpdy(
+ const net::HostPortPair& host_port_pair,
+ bool support_spdy) {
+ DCHECK(CalledOnValidThread());
+ if (host_port_pair.host().empty())
+ return;
+ std::string spdy_server = GetFlattenedSpdyServer(host_port_pair);
+
+ SpdyServerHostPortTable::iterator spdy_host_port =
+ spdy_servers_table_.find(spdy_server);
+ if ((spdy_host_port != spdy_servers_table_.end()) &&
+ (spdy_host_port->second == support_spdy)) {
+ return;
+ }
+ // Cache the data.
+ spdy_servers_table_[spdy_server] = support_spdy;
+}
+
+bool HttpServerPropertiesImpl::HasAlternateProtocol(
+ const HostPortPair& server) const {
+ return ContainsKey(alternate_protocol_map_, server) ||
+ g_forced_alternate_protocol;
+}
+
+PortAlternateProtocolPair
+HttpServerPropertiesImpl::GetAlternateProtocol(
+ const HostPortPair& server) const {
+ DCHECK(HasAlternateProtocol(server));
+
+ // First check the map.
+ AlternateProtocolMap::const_iterator it =
+ alternate_protocol_map_.find(server);
+ if (it != alternate_protocol_map_.end())
+ return it->second;
+
+ // We must be forcing an alternate.
+ DCHECK(g_forced_alternate_protocol);
+ return *g_forced_alternate_protocol;
+}
+
+void HttpServerPropertiesImpl::SetAlternateProtocol(
+ const HostPortPair& server,
+ uint16 alternate_port,
+ AlternateProtocol alternate_protocol) {
+ if (alternate_protocol == ALTERNATE_PROTOCOL_BROKEN) {
+ LOG(DFATAL) << "Call SetBrokenAlternateProtocol() instead.";
+ return;
+ }
+
+ PortAlternateProtocolPair alternate;
+ alternate.port = alternate_port;
+ alternate.protocol = alternate_protocol;
+ if (HasAlternateProtocol(server)) {
+ const PortAlternateProtocolPair existing_alternate =
+ GetAlternateProtocol(server);
+
+ if (existing_alternate.protocol == ALTERNATE_PROTOCOL_BROKEN) {
+ DVLOG(1) << "Ignore alternate protocol since it's known to be broken.";
+ return;
+ }
+
+ if (alternate_protocol != ALTERNATE_PROTOCOL_BROKEN &&
+ !existing_alternate.Equals(alternate)) {
+ LOG(WARNING) << "Changing the alternate protocol for: "
+ << server.ToString()
+ << " from [Port: " << existing_alternate.port
+ << ", Protocol: " << existing_alternate.protocol
+ << "] to [Port: " << alternate_port
+ << ", Protocol: " << alternate_protocol
+ << "].";
+ }
+ }
+
+ alternate_protocol_map_[server] = alternate;
+}
+
+void HttpServerPropertiesImpl::SetBrokenAlternateProtocol(
+ const HostPortPair& server) {
+ alternate_protocol_map_[server].protocol = ALTERNATE_PROTOCOL_BROKEN;
+}
+
+const AlternateProtocolMap&
+HttpServerPropertiesImpl::alternate_protocol_map() const {
+ return alternate_protocol_map_;
+}
+
+const SettingsMap& HttpServerPropertiesImpl::GetSpdySettings(
+ const HostPortPair& host_port_pair) const {
+ SpdySettingsMap::const_iterator it = spdy_settings_map_.find(host_port_pair);
+ if (it == spdy_settings_map_.end()) {
+ CR_DEFINE_STATIC_LOCAL(SettingsMap, kEmptySettingsMap, ());
+ return kEmptySettingsMap;
+ }
+ return it->second;
+}
+
+bool HttpServerPropertiesImpl::SetSpdySetting(
+ const HostPortPair& host_port_pair,
+ SpdySettingsIds id,
+ SpdySettingsFlags flags,
+ uint32 value) {
+ if (!(flags & SETTINGS_FLAG_PLEASE_PERSIST))
+ return false;
+
+ SettingsMap& settings_map = spdy_settings_map_[host_port_pair];
+ SettingsFlagsAndValue flags_and_value(SETTINGS_FLAG_PERSISTED, value);
+ settings_map[id] = flags_and_value;
+ return true;
+}
+
+void HttpServerPropertiesImpl::ClearSpdySettings(
+ const HostPortPair& host_port_pair) {
+ spdy_settings_map_.erase(host_port_pair);
+}
+
+void HttpServerPropertiesImpl::ClearAllSpdySettings() {
+ spdy_settings_map_.clear();
+}
+
+const SpdySettingsMap&
+HttpServerPropertiesImpl::spdy_settings_map() const {
+ return spdy_settings_map_;
+}
+
+HttpPipelinedHostCapability HttpServerPropertiesImpl::GetPipelineCapability(
+ const HostPortPair& origin) {
+ HttpPipelinedHostCapability capability = PIPELINE_UNKNOWN;
+ CachedPipelineCapabilityMap::const_iterator it =
+ pipeline_capability_map_->Get(origin);
+ if (it != pipeline_capability_map_->end()) {
+ capability = it->second;
+ }
+ return capability;
+}
+
+void HttpServerPropertiesImpl::SetPipelineCapability(
+ const HostPortPair& origin,
+ HttpPipelinedHostCapability capability) {
+ CachedPipelineCapabilityMap::iterator it =
+ pipeline_capability_map_->Peek(origin);
+ if (it == pipeline_capability_map_->end() ||
+ it->second != PIPELINE_INCAPABLE) {
+ pipeline_capability_map_->Put(origin, capability);
+ }
+}
+
+void HttpServerPropertiesImpl::ClearPipelineCapabilities() {
+ pipeline_capability_map_->Clear();
+}
+
+PipelineCapabilityMap
+HttpServerPropertiesImpl::GetPipelineCapabilityMap() const {
+ PipelineCapabilityMap result;
+ CachedPipelineCapabilityMap::const_iterator it;
+ for (it = pipeline_capability_map_->begin();
+ it != pipeline_capability_map_->end(); ++it) {
+ result[it->first] = it->second;
+ }
+ return result;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_server_properties_impl.h b/chromium/net/http/http_server_properties_impl.h
new file mode 100644
index 00000000000..c1e2d4ba20b
--- /dev/null
+++ b/chromium/net/http/http_server_properties_impl.h
@@ -0,0 +1,161 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_SERVER_PROPERTIES_IMPL_H_
+#define NET_HTTP_HTTP_SERVER_PROPERTIES_IMPL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/containers/mru_cache.h"
+#include "base/gtest_prod_util.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/http/http_pipelined_host_capability.h"
+#include "net/http/http_server_properties.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace net {
+
+// The implementation for setting/retrieving the HTTP server properties.
+class NET_EXPORT HttpServerPropertiesImpl
+ : public HttpServerProperties,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ HttpServerPropertiesImpl();
+ virtual ~HttpServerPropertiesImpl();
+
+ // Initializes |spdy_servers_table_| with the servers (host/port) from
+ // |spdy_servers| that either support SPDY or not.
+ void InitializeSpdyServers(std::vector<std::string>* spdy_servers,
+ bool support_spdy);
+
+ void InitializeAlternateProtocolServers(
+ AlternateProtocolMap* alternate_protocol_servers);
+
+ void InitializeSpdySettingsServers(SpdySettingsMap* spdy_settings_map);
+
+ // Initializes |pipeline_capability_map_| with the servers (host/port) from
+ // |pipeline_capability_map| that either support HTTP pipelining or not.
+ void InitializePipelineCapabilities(
+ const PipelineCapabilityMap* pipeline_capability_map);
+
+ // Get the list of servers (host/port) that support SPDY.
+ void GetSpdyServerList(base::ListValue* spdy_server_list) const;
+
+ // Returns flattened string representation of the |host_port_pair|. Used by
+ // unittests.
+ static std::string GetFlattenedSpdyServer(
+ const net::HostPortPair& host_port_pair);
+
+ // Debugging to simulate presence of an AlternateProtocol.
+ // If we don't have an alternate protocol in the map for any given host/port
+ // pair, force this ProtocolPortPair.
+ static void ForceAlternateProtocol(const PortAlternateProtocolPair& pair);
+ static void DisableForcedAlternateProtocol();
+
+ // Changes the number of host/port pairs we remember pipelining capability
+ // for. A larger number means we're more likely to be able to pipeline
+ // immediately if a host is known good, but uses more memory. This function
+ // can only be called if |pipeline_capability_map_| is empty.
+ void SetNumPipelinedHostsToRemember(int max_size);
+
+ // -----------------------------
+ // HttpServerProperties methods:
+ // -----------------------------
+
+ // Gets a weak pointer for this object.
+ virtual base::WeakPtr<HttpServerProperties> GetWeakPtr() OVERRIDE;
+
+ // Deletes all data.
+ virtual void Clear() OVERRIDE;
+
+ // Returns true if |server| supports SPDY.
+ virtual bool SupportsSpdy(const HostPortPair& server) const OVERRIDE;
+
+ // Add |server| into the persistent store.
+ virtual void SetSupportsSpdy(const HostPortPair& server,
+ bool support_spdy) OVERRIDE;
+
+ // Returns true if |server| has an Alternate-Protocol header.
+ virtual bool HasAlternateProtocol(const HostPortPair& server) const OVERRIDE;
+
+ // Returns the Alternate-Protocol and port for |server|.
+ // HasAlternateProtocol(server) must be true.
+ virtual PortAlternateProtocolPair GetAlternateProtocol(
+ const HostPortPair& server) const OVERRIDE;
+
+ // Sets the Alternate-Protocol for |server|.
+ virtual void SetAlternateProtocol(
+ const HostPortPair& server,
+ uint16 alternate_port,
+ AlternateProtocol alternate_protocol) OVERRIDE;
+
+ // Sets the Alternate-Protocol for |server| to be BROKEN.
+ virtual void SetBrokenAlternateProtocol(const HostPortPair& server) OVERRIDE;
+
+ // Returns all Alternate-Protocol mappings.
+ virtual const AlternateProtocolMap& alternate_protocol_map() const OVERRIDE;
+
+ // Gets a reference to the SettingsMap stored for a host.
+ // If no settings are stored, returns an empty SettingsMap.
+ virtual const SettingsMap& GetSpdySettings(
+ const HostPortPair& host_port_pair) const OVERRIDE;
+
+ // Saves an individual SPDY setting for a host. Returns true if SPDY setting
+ // is to be persisted.
+ virtual bool SetSpdySetting(const HostPortPair& host_port_pair,
+ SpdySettingsIds id,
+ SpdySettingsFlags flags,
+ uint32 value) OVERRIDE;
+
+ // Clears all entries in |spdy_settings_map_| for a host.
+ virtual void ClearSpdySettings(const HostPortPair& host_port_pair) OVERRIDE;
+
+ // Clears all entries in |spdy_settings_map_|.
+ virtual void ClearAllSpdySettings() OVERRIDE;
+
+ // Returns all persistent SPDY settings.
+ virtual const SpdySettingsMap& spdy_settings_map() const OVERRIDE;
+
+ virtual HttpPipelinedHostCapability GetPipelineCapability(
+ const HostPortPair& origin) OVERRIDE;
+
+ virtual void SetPipelineCapability(
+ const HostPortPair& origin,
+ HttpPipelinedHostCapability capability) OVERRIDE;
+
+ virtual void ClearPipelineCapabilities() OVERRIDE;
+
+ virtual PipelineCapabilityMap GetPipelineCapabilityMap() const OVERRIDE;
+
+ private:
+ typedef base::MRUCache<
+ HostPortPair, HttpPipelinedHostCapability> CachedPipelineCapabilityMap;
+ // |spdy_servers_table_| has flattened representation of servers (host/port
+ // pair) that either support or not support SPDY protocol.
+ typedef base::hash_map<std::string, bool> SpdyServerHostPortTable;
+
+ base::WeakPtrFactory<HttpServerPropertiesImpl> weak_ptr_factory_;
+
+ SpdyServerHostPortTable spdy_servers_table_;
+
+ AlternateProtocolMap alternate_protocol_map_;
+ SpdySettingsMap spdy_settings_map_;
+ scoped_ptr<CachedPipelineCapabilityMap> pipeline_capability_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpServerPropertiesImpl);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_SERVER_PROPERTIES_IMPL_H_
diff --git a/chromium/net/http/http_server_properties_impl_unittest.cc b/chromium/net/http/http_server_properties_impl_unittest.cc
new file mode 100644
index 00000000000..d125adcc40c
--- /dev/null
+++ b/chromium/net/http/http_server_properties_impl_unittest.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 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.
+
+#include "net/http/http_server_properties_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace net {
+
+namespace {
+
+class HttpServerPropertiesImplTest : public testing::Test {
+ protected:
+ HttpServerPropertiesImpl impl_;
+};
+
+typedef HttpServerPropertiesImplTest SpdyServerPropertiesTest;
+
+TEST_F(SpdyServerPropertiesTest, Initialize) {
+ HostPortPair spdy_server_google("www.google.com", 443);
+ std::string spdy_server_g =
+ HttpServerPropertiesImpl::GetFlattenedSpdyServer(spdy_server_google);
+
+ HostPortPair spdy_server_docs("docs.google.com", 443);
+ std::string spdy_server_d =
+ HttpServerPropertiesImpl::GetFlattenedSpdyServer(spdy_server_docs);
+
+ // Check by initializing NULL spdy servers.
+ impl_.InitializeSpdyServers(NULL, true);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Check by initializing empty spdy servers.
+ std::vector<std::string> spdy_servers;
+ impl_.InitializeSpdyServers(&spdy_servers, true);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Check by initializing with www.google.com:443 spdy server.
+ std::vector<std::string> spdy_servers1;
+ spdy_servers1.push_back(spdy_server_g);
+ impl_.InitializeSpdyServers(&spdy_servers1, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Check by initializing with www.google.com:443 and docs.google.com:443 spdy
+ // servers.
+ std::vector<std::string> spdy_servers2;
+ spdy_servers2.push_back(spdy_server_g);
+ spdy_servers2.push_back(spdy_server_d);
+ impl_.InitializeSpdyServers(&spdy_servers2, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_docs));
+}
+
+TEST_F(SpdyServerPropertiesTest, SupportsSpdyTest) {
+ HostPortPair spdy_server_empty(std::string(), 443);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_empty));
+
+ // Add www.google.com:443 as supporting SPDY.
+ HostPortPair spdy_server_google("www.google.com", 443);
+ impl_.SetSupportsSpdy(spdy_server_google, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Add mail.google.com:443 as not supporting SPDY.
+ HostPortPair spdy_server_mail("mail.google.com", 443);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_mail));
+
+ // Add docs.google.com:443 as supporting SPDY.
+ HostPortPair spdy_server_docs("docs.google.com", 443);
+ impl_.SetSupportsSpdy(spdy_server_docs, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_docs));
+
+ // Verify all the entries are the same after additions.
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_mail));
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_docs));
+}
+
+TEST_F(SpdyServerPropertiesTest, SetSupportsSpdy) {
+ HostPortPair spdy_server_empty(std::string(), 443);
+ impl_.SetSupportsSpdy(spdy_server_empty, true);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_empty));
+
+ // Add www.google.com:443 as supporting SPDY.
+ HostPortPair spdy_server_google("www.google.com", 443);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+ impl_.SetSupportsSpdy(spdy_server_google, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Make www.google.com:443 as not supporting SPDY.
+ impl_.SetSupportsSpdy(spdy_server_google, false);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+
+ // Add mail.google.com:443 as supporting SPDY.
+ HostPortPair spdy_server_mail("mail.google.com", 443);
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_mail));
+ impl_.SetSupportsSpdy(spdy_server_mail, true);
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_mail));
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+}
+
+TEST_F(SpdyServerPropertiesTest, Clear) {
+ // Add www.google.com:443 and mail.google.com:443 as supporting SPDY.
+ HostPortPair spdy_server_google("www.google.com", 443);
+ impl_.SetSupportsSpdy(spdy_server_google, true);
+ HostPortPair spdy_server_mail("mail.google.com", 443);
+ impl_.SetSupportsSpdy(spdy_server_mail, true);
+
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_google));
+ EXPECT_TRUE(impl_.SupportsSpdy(spdy_server_mail));
+
+ impl_.Clear();
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_google));
+ EXPECT_FALSE(impl_.SupportsSpdy(spdy_server_mail));
+}
+
+TEST_F(SpdyServerPropertiesTest, GetSpdyServerList) {
+ base::ListValue spdy_server_list;
+
+ // Check there are no spdy_servers.
+ impl_.GetSpdyServerList(&spdy_server_list);
+ EXPECT_EQ(0U, spdy_server_list.GetSize());
+
+ // Check empty server is not added.
+ HostPortPair spdy_server_empty(std::string(), 443);
+ impl_.SetSupportsSpdy(spdy_server_empty, true);
+ impl_.GetSpdyServerList(&spdy_server_list);
+ EXPECT_EQ(0U, spdy_server_list.GetSize());
+
+ std::string string_value_g;
+ std::string string_value_m;
+ HostPortPair spdy_server_google("www.google.com", 443);
+ std::string spdy_server_g =
+ HttpServerPropertiesImpl::GetFlattenedSpdyServer(spdy_server_google);
+ HostPortPair spdy_server_mail("mail.google.com", 443);
+ std::string spdy_server_m =
+ HttpServerPropertiesImpl::GetFlattenedSpdyServer(spdy_server_mail);
+
+ // Add www.google.com:443 as not supporting SPDY.
+ impl_.SetSupportsSpdy(spdy_server_google, false);
+ impl_.GetSpdyServerList(&spdy_server_list);
+ EXPECT_EQ(0U, spdy_server_list.GetSize());
+
+ // Add www.google.com:443 as supporting SPDY.
+ impl_.SetSupportsSpdy(spdy_server_google, true);
+ impl_.GetSpdyServerList(&spdy_server_list);
+ ASSERT_EQ(1U, spdy_server_list.GetSize());
+ ASSERT_TRUE(spdy_server_list.GetString(0, &string_value_g));
+ ASSERT_EQ(spdy_server_g, string_value_g);
+
+ // Add mail.google.com:443 as not supporting SPDY.
+ impl_.SetSupportsSpdy(spdy_server_mail, false);
+ impl_.GetSpdyServerList(&spdy_server_list);
+ ASSERT_EQ(1U, spdy_server_list.GetSize());
+ ASSERT_TRUE(spdy_server_list.GetString(0, &string_value_g));
+ ASSERT_EQ(spdy_server_g, string_value_g);
+
+ // Add mail.google.com:443 as supporting SPDY.
+ impl_.SetSupportsSpdy(spdy_server_mail, true);
+ impl_.GetSpdyServerList(&spdy_server_list);
+ ASSERT_EQ(2U, spdy_server_list.GetSize());
+
+ // Verify www.google.com:443 and mail.google.com:443 are in the list.
+ ASSERT_TRUE(spdy_server_list.GetString(0, &string_value_g));
+ ASSERT_TRUE(spdy_server_list.GetString(1, &string_value_m));
+ if (string_value_g.compare(spdy_server_g) == 0) {
+ ASSERT_EQ(spdy_server_g, string_value_g);
+ ASSERT_EQ(spdy_server_m, string_value_m);
+ } else {
+ ASSERT_EQ(spdy_server_g, string_value_m);
+ ASSERT_EQ(spdy_server_m, string_value_g);
+ }
+}
+
+typedef HttpServerPropertiesImplTest AlternateProtocolServerPropertiesTest;
+
+TEST_F(AlternateProtocolServerPropertiesTest, Basic) {
+ HostPortPair test_host_port_pair("foo", 80);
+ EXPECT_FALSE(impl_.HasAlternateProtocol(test_host_port_pair));
+ impl_.SetAlternateProtocol(test_host_port_pair, 443, NPN_SPDY_1);
+ ASSERT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair));
+ const PortAlternateProtocolPair alternate =
+ impl_.GetAlternateProtocol(test_host_port_pair);
+ EXPECT_EQ(443, alternate.port);
+ EXPECT_EQ(NPN_SPDY_1, alternate.protocol);
+
+ impl_.Clear();
+ EXPECT_FALSE(impl_.HasAlternateProtocol(test_host_port_pair));
+}
+
+TEST_F(AlternateProtocolServerPropertiesTest, Initialize) {
+ HostPortPair test_host_port_pair1("foo1", 80);
+ impl_.SetBrokenAlternateProtocol(test_host_port_pair1);
+ HostPortPair test_host_port_pair2("foo2", 80);
+ impl_.SetAlternateProtocol(test_host_port_pair2, 443, NPN_SPDY_1);
+
+ AlternateProtocolMap alternate_protocol_map;
+ PortAlternateProtocolPair port_alternate_protocol_pair;
+ port_alternate_protocol_pair.port = 123;
+ port_alternate_protocol_pair.protocol = NPN_SPDY_2;
+ alternate_protocol_map[test_host_port_pair2] = port_alternate_protocol_pair;
+ impl_.InitializeAlternateProtocolServers(&alternate_protocol_map);
+
+ ASSERT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair1));
+ ASSERT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair2));
+ port_alternate_protocol_pair =
+ impl_.GetAlternateProtocol(test_host_port_pair1);
+ EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, port_alternate_protocol_pair.protocol);
+ port_alternate_protocol_pair =
+ impl_.GetAlternateProtocol(test_host_port_pair2);
+ EXPECT_EQ(123, port_alternate_protocol_pair.port);
+ EXPECT_EQ(NPN_SPDY_2, port_alternate_protocol_pair.protocol);
+}
+
+TEST_F(AlternateProtocolServerPropertiesTest, SetBroken) {
+ HostPortPair test_host_port_pair("foo", 80);
+ impl_.SetBrokenAlternateProtocol(test_host_port_pair);
+ ASSERT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair));
+ PortAlternateProtocolPair alternate =
+ impl_.GetAlternateProtocol(test_host_port_pair);
+ EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol);
+
+ impl_.SetAlternateProtocol(
+ test_host_port_pair,
+ 1234,
+ NPN_SPDY_1);
+ alternate = impl_.GetAlternateProtocol(test_host_port_pair);
+ EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol)
+ << "Second attempt should be ignored.";
+}
+
+TEST_F(AlternateProtocolServerPropertiesTest, Forced) {
+ // Test forced alternate protocols.
+
+ PortAlternateProtocolPair default_protocol;
+ default_protocol.port = 1234;
+ default_protocol.protocol = NPN_SPDY_2;
+ HttpServerPropertiesImpl::ForceAlternateProtocol(default_protocol);
+
+ // Verify the forced protocol.
+ HostPortPair test_host_port_pair("foo", 80);
+ EXPECT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair));
+ PortAlternateProtocolPair alternate =
+ impl_.GetAlternateProtocol(test_host_port_pair);
+ EXPECT_EQ(default_protocol.port, alternate.port);
+ EXPECT_EQ(default_protocol.protocol, alternate.protocol);
+
+ // Verify the real protocol overrides the forced protocol.
+ impl_.SetAlternateProtocol(test_host_port_pair, 443, NPN_SPDY_1);
+ ASSERT_TRUE(impl_.HasAlternateProtocol(test_host_port_pair));
+ alternate = impl_.GetAlternateProtocol(test_host_port_pair);
+ EXPECT_EQ(443, alternate.port);
+ EXPECT_EQ(NPN_SPDY_1, alternate.protocol);
+
+ // Turn off the static, forced alternate protocol so that tests don't
+ // have this state.
+ HttpServerPropertiesImpl::DisableForcedAlternateProtocol();
+
+ // Verify the forced protocol is off.
+ HostPortPair test_host_port_pair2("bar", 80);
+ EXPECT_FALSE(impl_.HasAlternateProtocol(test_host_port_pair2));
+}
+
+typedef HttpServerPropertiesImplTest SpdySettingsServerPropertiesTest;
+
+TEST_F(SpdySettingsServerPropertiesTest, Initialize) {
+ HostPortPair spdy_server_google("www.google.com", 443);
+
+ // Check by initializing empty spdy settings.
+ SpdySettingsMap spdy_settings_map;
+ impl_.InitializeSpdySettingsServers(&spdy_settings_map);
+ EXPECT_TRUE(impl_.GetSpdySettings(spdy_server_google).empty());
+
+ // Check by initializing with www.google.com:443 spdy server settings.
+ SettingsMap settings_map;
+ const SpdySettingsIds id = SETTINGS_UPLOAD_BANDWIDTH;
+ const SpdySettingsFlags flags = SETTINGS_FLAG_PERSISTED;
+ const uint32 value = 31337;
+ SettingsFlagsAndValue flags_and_value(flags, value);
+ settings_map[id] = flags_and_value;
+ spdy_settings_map[spdy_server_google] = settings_map;
+ impl_.InitializeSpdySettingsServers(&spdy_settings_map);
+
+ const SettingsMap& settings_map2 = impl_.GetSpdySettings(spdy_server_google);
+ ASSERT_EQ(1U, settings_map2.size());
+ SettingsMap::const_iterator it = settings_map2.find(id);
+ EXPECT_TRUE(it != settings_map2.end());
+ SettingsFlagsAndValue flags_and_value2 = it->second;
+ EXPECT_EQ(flags, flags_and_value2.first);
+ EXPECT_EQ(value, flags_and_value2.second);
+}
+
+TEST_F(SpdySettingsServerPropertiesTest, SetSpdySetting) {
+ HostPortPair spdy_server_empty(std::string(), 443);
+ const SettingsMap& settings_map0 = impl_.GetSpdySettings(spdy_server_empty);
+ EXPECT_EQ(0U, settings_map0.size()); // Returns kEmptySettingsMap.
+
+ // Add www.google.com:443 as persisting.
+ HostPortPair spdy_server_google("www.google.com", 443);
+ const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
+ const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
+ const uint32 value1 = 31337;
+ EXPECT_TRUE(impl_.SetSpdySetting(spdy_server_google, id1, flags1, value1));
+ // Check the values.
+ const SettingsMap& settings_map1_ret =
+ impl_.GetSpdySettings(spdy_server_google);
+ ASSERT_EQ(1U, settings_map1_ret.size());
+ SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
+ EXPECT_TRUE(it1_ret != settings_map1_ret.end());
+ SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
+ EXPECT_EQ(value1, flags_and_value1_ret.second);
+
+ // Add mail.google.com:443 as not persisting.
+ HostPortPair spdy_server_mail("mail.google.com", 443);
+ const SpdySettingsIds id2 = SETTINGS_DOWNLOAD_BANDWIDTH;
+ const SpdySettingsFlags flags2 = SETTINGS_FLAG_NONE;
+ const uint32 value2 = 62667;
+ EXPECT_FALSE(impl_.SetSpdySetting(spdy_server_mail, id2, flags2, value2));
+ const SettingsMap& settings_map2_ret =
+ impl_.GetSpdySettings(spdy_server_mail);
+ EXPECT_EQ(0U, settings_map2_ret.size()); // Returns kEmptySettingsMap.
+
+ // Add docs.google.com:443 as persisting
+ HostPortPair spdy_server_docs("docs.google.com", 443);
+ const SpdySettingsIds id3 = SETTINGS_ROUND_TRIP_TIME;
+ const SpdySettingsFlags flags3 = SETTINGS_FLAG_PLEASE_PERSIST;
+ const uint32 value3 = 93997;
+ SettingsFlagsAndValue flags_and_value3(flags3, value3);
+ EXPECT_TRUE(impl_.SetSpdySetting(spdy_server_docs, id3, flags3, value3));
+ // Check the values.
+ const SettingsMap& settings_map3_ret =
+ impl_.GetSpdySettings(spdy_server_docs);
+ ASSERT_EQ(1U, settings_map3_ret.size());
+ SettingsMap::const_iterator it3_ret = settings_map3_ret.find(id3);
+ EXPECT_TRUE(it3_ret != settings_map3_ret.end());
+ SettingsFlagsAndValue flags_and_value3_ret = it3_ret->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3_ret.first);
+ EXPECT_EQ(value3, flags_and_value3_ret.second);
+
+ // Check data for www.google.com:443 (id1).
+ const SettingsMap& settings_map4_ret =
+ impl_.GetSpdySettings(spdy_server_google);
+ ASSERT_EQ(1U, settings_map4_ret.size());
+ SettingsMap::const_iterator it4_ret = settings_map4_ret.find(id1);
+ EXPECT_TRUE(it4_ret != settings_map4_ret.end());
+ SettingsFlagsAndValue flags_and_value4_ret = it4_ret->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value4_ret.first);
+ EXPECT_EQ(value1, flags_and_value1_ret.second);
+
+ // Clear www.google.com:443 as persisting.
+ impl_.ClearSpdySettings(spdy_server_google);
+ // Check the values.
+ const SettingsMap& settings_map5_ret =
+ impl_.GetSpdySettings(spdy_server_google);
+ ASSERT_EQ(0U, settings_map5_ret.size());
+
+ // Clear all settings.
+ ASSERT_GT(impl_.spdy_settings_map().size(), 0U);
+ impl_.ClearAllSpdySettings();
+ ASSERT_EQ(0U, impl_.spdy_settings_map().size());
+}
+
+TEST_F(SpdySettingsServerPropertiesTest, Clear) {
+ // Add www.google.com:443 as persisting.
+ HostPortPair spdy_server_google("www.google.com", 443);
+ const SpdySettingsIds id1 = SETTINGS_UPLOAD_BANDWIDTH;
+ const SpdySettingsFlags flags1 = SETTINGS_FLAG_PLEASE_PERSIST;
+ const uint32 value1 = 31337;
+ EXPECT_TRUE(impl_.SetSpdySetting(spdy_server_google, id1, flags1, value1));
+ // Check the values.
+ const SettingsMap& settings_map1_ret =
+ impl_.GetSpdySettings(spdy_server_google);
+ ASSERT_EQ(1U, settings_map1_ret.size());
+ SettingsMap::const_iterator it1_ret = settings_map1_ret.find(id1);
+ EXPECT_TRUE(it1_ret != settings_map1_ret.end());
+ SettingsFlagsAndValue flags_and_value1_ret = it1_ret->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1_ret.first);
+ EXPECT_EQ(value1, flags_and_value1_ret.second);
+
+ // Add docs.google.com:443 as persisting
+ HostPortPair spdy_server_docs("docs.google.com", 443);
+ const SpdySettingsIds id3 = SETTINGS_ROUND_TRIP_TIME;
+ const SpdySettingsFlags flags3 = SETTINGS_FLAG_PLEASE_PERSIST;
+ const uint32 value3 = 93997;
+ EXPECT_TRUE(impl_.SetSpdySetting(spdy_server_docs, id3, flags3, value3));
+ // Check the values.
+ const SettingsMap& settings_map3_ret =
+ impl_.GetSpdySettings(spdy_server_docs);
+ ASSERT_EQ(1U, settings_map3_ret.size());
+ SettingsMap::const_iterator it3_ret = settings_map3_ret.find(id3);
+ EXPECT_TRUE(it3_ret != settings_map3_ret.end());
+ SettingsFlagsAndValue flags_and_value3_ret = it3_ret->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3_ret.first);
+ EXPECT_EQ(value3, flags_and_value3_ret.second);
+
+ impl_.Clear();
+ EXPECT_EQ(0U, impl_.GetSpdySettings(spdy_server_google).size());
+ EXPECT_EQ(0U, impl_.GetSpdySettings(spdy_server_docs).size());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_status_code.cc b/chromium/net/http/http_status_code.cc
new file mode 100644
index 00000000000..fb7261b3001
--- /dev/null
+++ b/chromium/net/http/http_status_code.cc
@@ -0,0 +1,25 @@
+// Copyright 2013 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/http/http_status_code.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+const char* GetHttpReasonPhrase(HttpStatusCode code) {
+ switch (code) {
+
+#define HTTP_STATUS(label, code, reason) case HTTP_ ## label: return reason;
+#include "net/http/http_status_code_list.h"
+#undef HTTP_STATUS
+
+ default:
+ NOTREACHED() << "unknown HTTP status code " << code;
+ }
+
+ return "";
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_status_code.h b/chromium/net/http/http_status_code.h
new file mode 100644
index 00000000000..a4b398b5038
--- /dev/null
+++ b/chromium/net/http/http_status_code.h
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STATUS_CODE_H_
+#define NET_HTTP_HTTP_STATUS_CODE_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+// HTTP status codes.
+enum HttpStatusCode {
+
+#define HTTP_STATUS(label, code, reason) HTTP_ ## label = code,
+#include "net/http/http_status_code_list.h"
+#undef HTTP_STATUS
+
+};
+
+// Returns the corresponding HTTP status description to use in the Reason-Phrase
+// field in an HTTP response for given |code|. It's based on the IANA HTTP
+// Status Code Registry.
+// http://www.iana.org/assignments/http-status-codes/http-status-codes.xml
+//
+// This function may not cover all codes defined in the IANA registry. It
+// returns an empty string (or crash in debug build) for status codes which are
+// not yet covered or just invalid. Please extend it when needed.
+NET_EXPORT const char* GetHttpReasonPhrase(HttpStatusCode code);
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STATUS_CODE_H_
diff --git a/chromium/net/http/http_status_code_list.h b/chromium/net/http/http_status_code_list.h
new file mode 100644
index 00000000000..75c2304c5bd
--- /dev/null
+++ b/chromium/net/http/http_status_code_list.h
@@ -0,0 +1,67 @@
+// Copyright 2013 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.
+
+// This file intentionally does not have header guards, it's included
+// inside a macro to generate enum.
+//
+// This file contains the list of HTTP status codes. Taken from IANA HTTP Status
+// Code Registry.
+// http://www.iana.org/assignments/http-status-codes/http-status-codes.xml
+
+#ifndef HTTP_STATUS
+#error "HTTP_STATUS should be defined before including this file"
+#endif
+
+// Informational 1xx
+HTTP_STATUS(CONTINUE, 100, "Continue")
+HTTP_STATUS(SWITCHING_PROTOCOLS, 101, "Switching Protocols")
+
+// Successful 2xx
+HTTP_STATUS(OK, 200, "OK")
+HTTP_STATUS(CREATED, 201, "Created")
+HTTP_STATUS(ACCEPTED, 202, "Accepted")
+HTTP_STATUS(NON_AUTHORITATIVE_INFORMATION, 203, "Non-Authoritative Information")
+HTTP_STATUS(NO_CONTENT, 204, "No Content")
+HTTP_STATUS(RESET_CONTENT, 205, "Reset Content")
+HTTP_STATUS(PARTIAL_CONTENT, 206, "Partial Content")
+
+// Redirection 3xx
+HTTP_STATUS(MULTIPLE_CHOICES, 300, "Multiple Choices")
+HTTP_STATUS(MOVED_PERMANENTLY, 301, "Moved Permanently")
+HTTP_STATUS(FOUND, 302, "Found")
+HTTP_STATUS(SEE_OTHER, 303, "See Other")
+HTTP_STATUS(NOT_MODIFIED, 304, "Not Modified")
+HTTP_STATUS(USE_PROXY, 305, "Use Proxy")
+// 306 is no longer used.
+HTTP_STATUS(TEMPORARY_REDIRECT, 307, "Temporary Redirect")
+HTTP_STATUS(PERMANENT_REDIRECT, 308, "Permanent Redirect")
+
+// Client error 4xx
+HTTP_STATUS(BAD_REQUEST, 400, "Bad Request")
+HTTP_STATUS(UNAUTHORIZED, 401, "Unauthorized")
+HTTP_STATUS(PAYMENT_REQUIRED, 402, "Payment Required")
+HTTP_STATUS(FORBIDDEN, 403, "Forbidden")
+HTTP_STATUS(NOT_FOUND, 404, "Not Found")
+HTTP_STATUS(METHOD_NOT_ALLOWED, 405, "Method Not Allowed")
+HTTP_STATUS(NOT_ACCEPTABLE, 406, "Not Acceptable")
+HTTP_STATUS(PROXY_AUTHENTICATION_REQUIRED, 407, "Proxy Authentication Required")
+HTTP_STATUS(REQUEST_TIMEOUT, 408, "Request Timeout")
+HTTP_STATUS(CONFLICT, 409, "Conflict")
+HTTP_STATUS(GONE, 410, "Gone")
+HTTP_STATUS(LENGTH_REQUIRED, 411, "Length Required")
+HTTP_STATUS(PRECONDITION_FAILED, 412, "Precondition Failed")
+HTTP_STATUS(REQUEST_ENTITY_TOO_LARGE, 413, "Request Entity Too Large")
+HTTP_STATUS(REQUEST_URI_TOO_LONG, 414, "Request-URI Too Long")
+HTTP_STATUS(UNSUPPORTED_MEDIA_TYPE, 415, "Unsupported Media Type")
+HTTP_STATUS(REQUESTED_RANGE_NOT_SATISFIABLE, 416,
+ "Requested Range Not Satisfiable")
+HTTP_STATUS(EXPECTATION_FAILED, 417, "Expectation Failed")
+
+// Server error 5xx
+HTTP_STATUS(INTERNAL_SERVER_ERROR, 500, "Internal Server Error")
+HTTP_STATUS(NOT_IMPLEMENTED, 501, "Not Implemented")
+HTTP_STATUS(BAD_GATEWAY, 502, "Bad Gateway")
+HTTP_STATUS(SERVICE_UNAVAILABLE, 503, "Service Unavailable")
+HTTP_STATUS(GATEWAY_TIMEOUT, 504, "Gateway Timeout")
+HTTP_STATUS(VERSION_NOT_SUPPORTED, 505, "HTTP Version Not Supported")
diff --git a/chromium/net/http/http_status_code_unittest.cc b/chromium/net/http/http_status_code_unittest.cc
new file mode 100644
index 00000000000..962b89d3acf
--- /dev/null
+++ b/chromium/net/http/http_status_code_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2013 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/http/http_status_code.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(HttpStatusCode, OK) {
+ EXPECT_EQ(200, HTTP_OK);
+ EXPECT_STREQ("OK", GetHttpReasonPhrase(HTTP_OK));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_stream.h b/chromium/net/http/http_stream.h
new file mode 100644
index 00000000000..3680db3f862
--- /dev/null
+++ b/chromium/net/http/http_stream.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+//
+// HttpStream provides an abstraction for a basic http streams, http pipelining
+// implementations, and SPDY. The HttpStream subtype is expected to manage the
+// underlying transport appropriately. For example, a non-pipelined HttpStream
+// would return the transport socket to the pool for reuse. SPDY streams on the
+// other hand leave the transport socket management to the SpdySession.
+
+#ifndef NET_HTTP_HTTP_STREAM_H_
+#define NET_HTTP_HTTP_STREAM_H_
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/upload_progress.h"
+#include "net/http/http_stream_base.h"
+
+namespace net {
+
+class IOBuffer;
+
+class NET_EXPORT_PRIVATE HttpStream : public HttpStreamBase {
+ public:
+ HttpStream() {}
+ virtual ~HttpStream() {}
+
+ // Queries the UploadDataStream for its progress (bytes sent).
+ virtual UploadProgress GetUploadProgress() const = 0;
+
+ // Returns a new (not initialized) stream using the same underlying
+ // connection and invalidates the old stream - no further methods should be
+ // called on the old stream. The caller should ensure that the response body
+ // from the previous request is drained before calling this method. If the
+ // subclass does not support renewing the stream, NULL is returned.
+ virtual HttpStream* RenewStreamForAuth() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpStream);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_H_
diff --git a/chromium/net/http/http_stream_base.h b/chromium/net/http/http_stream_base.h
new file mode 100644
index 00000000000..596ed75dff1
--- /dev/null
+++ b/chromium/net/http/http_stream_base.h
@@ -0,0 +1,153 @@
+// Copyright (c) 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.
+//
+// HttpStreamBase is an interface for reading and writing data to an
+// HTTP-like stream that keeps the client agnostic of the actual underlying
+// transport layer. This provides an abstraction for HttpStream and
+// WebSocketStream.
+
+#ifndef NET_HTTP_HTTP_STREAM_BASE_H_
+#define NET_HTTP_HTTP_STREAM_BASE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_progress.h"
+
+namespace net {
+
+class BoundNetLog;
+class HttpNetworkSession;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+class HttpResponseInfo;
+class IOBuffer;
+struct LoadTimingInfo;
+class SSLCertRequestInfo;
+class SSLInfo;
+
+class NET_EXPORT_PRIVATE HttpStreamBase {
+ public:
+ HttpStreamBase() {}
+ virtual ~HttpStreamBase() {}
+
+ // Initialize stream. Must be called before calling SendRequest().
+ // |request_info| must outlive the HttpStreamBase.
+ // Returns a net error code, possibly ERR_IO_PENDING.
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) = 0;
+
+ // Writes the headers and uploads body data to the underlying socket.
+ // ERR_IO_PENDING is returned if the operation could not be completed
+ // synchronously, in which case the result will be passed to the callback
+ // when available. Returns OK on success.
+ // |response| must outlive the HttpStreamBase.
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) = 0;
+
+ // Reads from the underlying socket until the response headers have been
+ // completely received. ERR_IO_PENDING is returned if the operation could
+ // not be completed synchronously, in which case the result will be passed
+ // to the callback when available. Returns OK on success. The response
+ // headers are available in the HttpResponseInfo returned by GetResponseInfo
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) = 0;
+
+ // Provides access to HttpResponseInfo (owned by HttpStream).
+ virtual const HttpResponseInfo* GetResponseInfo() const = 0;
+
+ // Reads response body data, up to |buf_len| bytes. |buf_len| should be a
+ // reasonable size (<2MB). The number of bytes read is returned, or an
+ // error is returned upon failure. 0 indicates that the request has been
+ // fully satisfied and there is no more data to read.
+ // ERR_CONNECTION_CLOSED is returned when the connection has been closed
+ // prematurely. ERR_IO_PENDING is returned if the operation could not be
+ // completed synchronously, in which case the result will be passed to the
+ // callback when available. If the operation is not completed immediately,
+ // the socket acquires a reference to the provided buffer until the callback
+ // is invoked or the socket is destroyed.
+ virtual int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Closes the stream.
+ // |not_reusable| indicates if the stream can be used for further requests.
+ // In the case of HTTP, where we re-use the byte-stream (e.g. the connection)
+ // this means we need to close the connection; in the case of SPDY, where the
+ // underlying stream is never reused, it has no effect.
+ // TODO(mbelshe): We should figure out how to fold the not_reusable flag
+ // into the stream implementation itself so that the caller
+ // does not need to pass it at all. We might also be able to
+ // eliminate the SetConnectionReused() below.
+ virtual void Close(bool not_reusable) = 0;
+
+ // Indicates if the response body has been completely read.
+ virtual bool IsResponseBodyComplete() const = 0;
+
+ // Indicates that the end of the response is detectable. This means that
+ // the response headers indicate either chunked encoding or content length.
+ // If neither is sent, the server must close the connection for us to detect
+ // the end of the response.
+ // TODO(rch): Rename this method, so that it is clear why it exists
+ // particularly as it applies to QUIC and SPDY for which the end of the
+ // response is always findable.
+ virtual bool CanFindEndOfResponse() const = 0;
+
+ // A stream exists on top of a connection. If the connection has been used
+ // to successfully exchange data in the past, error handling for the
+ // stream is done differently. This method returns true if the underlying
+ // connection is reused or has been connected and idle for some time.
+ virtual bool IsConnectionReused() const = 0;
+ virtual void SetConnectionReused() = 0;
+
+ // Checks whether the current state of the underlying connection
+ // allows it to be reused.
+ virtual bool IsConnectionReusable() const = 0;
+
+ // Populates the connection establishment part of |load_timing_info|, and
+ // socket ID. |load_timing_info| must have all null times when called.
+ // Returns false and does nothing if there is no underlying connection, either
+ // because one has yet to be assigned to the stream, or because the underlying
+ // socket has been closed.
+ //
+ // In practice, this means that this function will always succeed any time
+ // between when the full headers have been received and the stream has been
+ // closed.
+ virtual bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const = 0;
+
+ // Get the SSLInfo associated with this stream's connection. This should
+ // only be called for streams over SSL sockets, otherwise the behavior is
+ // undefined.
+ virtual void GetSSLInfo(SSLInfo* ssl_info) = 0;
+
+ // Get the SSLCertRequestInfo associated with this stream's connection.
+ // This should only be called for streams over SSL sockets, otherwise the
+ // behavior is undefined.
+ virtual void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) = 0;
+
+ // HACK(willchan): Really, we should move the HttpResponseDrainer logic into
+ // the HttpStream implementation. This is just a quick hack.
+ virtual bool IsSpdyHttpStream() const = 0;
+
+ // In the case of an HTTP error or redirect, flush the response body (usually
+ // a simple error or "this page has moved") so that we can re-use the
+ // underlying connection. This stream is responsible for deleting itself when
+ // draining is complete.
+ virtual void Drain(HttpNetworkSession* session) = 0;
+
+ // Called when the priority of the parent transaction changes.
+ virtual void SetPriority(RequestPriority priority) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpStreamBase);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_BASE_H_
diff --git a/chromium/net/http/http_stream_factory.cc b/chromium/net/http/http_stream_factory.cc
new file mode 100644
index 00000000000..a55ed075bb5
--- /dev/null
+++ b/chromium/net/http/http_stream_factory.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_factory.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "net/base/host_mapping_rules.h"
+#include "net/base/host_port_pair.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// WARNING: If you modify or add any static flags, you must keep them in sync
+// with |ResetStaticSettingsToInit|. This is critical for unit test isolation.
+
+// static
+std::vector<std::string>* HttpStreamFactory::next_protos_ = NULL;
+// static
+bool HttpStreamFactory::enabled_protocols_[NUM_ALTERNATE_PROTOCOLS];
+// static
+bool HttpStreamFactory::spdy_enabled_ = true;
+// static
+bool HttpStreamFactory::use_alternate_protocols_ = false;
+// static
+bool HttpStreamFactory::force_spdy_over_ssl_ = true;
+// static
+bool HttpStreamFactory::force_spdy_always_ = false;
+// static
+std::list<HostPortPair>* HttpStreamFactory::forced_spdy_exclusions_ = NULL;
+
+HttpStreamFactory::~HttpStreamFactory() {}
+
+// static
+void HttpStreamFactory::ResetStaticSettingsToInit() {
+ // WARNING: These must match the initializers above.
+ delete next_protos_;
+ delete forced_spdy_exclusions_;
+ next_protos_ = NULL;
+ spdy_enabled_ = true;
+ use_alternate_protocols_ = false;
+ force_spdy_over_ssl_ = true;
+ force_spdy_always_ = false;
+ forced_spdy_exclusions_ = NULL;
+ for (int i = 0; i < NUM_ALTERNATE_PROTOCOLS; ++i)
+ enabled_protocols_[i] = false;
+}
+
+void HttpStreamFactory::ProcessAlternateProtocol(
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ const std::string& alternate_protocol_str,
+ const HostPortPair& http_host_port_pair) {
+ std::vector<std::string> port_protocol_vector;
+ base::SplitString(alternate_protocol_str, ':', &port_protocol_vector);
+ if (port_protocol_vector.size() != 2) {
+ DLOG(WARNING) << kAlternateProtocolHeader
+ << " header has too many tokens: "
+ << alternate_protocol_str;
+ return;
+ }
+
+ int port;
+ if (!base::StringToInt(port_protocol_vector[0], &port) ||
+ port <= 0 || port >= 1 << 16) {
+ DLOG(WARNING) << kAlternateProtocolHeader
+ << " header has unrecognizable port: "
+ << port_protocol_vector[0];
+ return;
+ }
+
+ AlternateProtocol protocol =
+ AlternateProtocolFromString(port_protocol_vector[1]);
+ if (protocol < NUM_ALTERNATE_PROTOCOLS && !enabled_protocols_[protocol])
+ protocol = ALTERNATE_PROTOCOL_BROKEN;
+
+ if (protocol == ALTERNATE_PROTOCOL_BROKEN) {
+ // Currently, we only recognize the npn-spdy protocol.
+ DLOG(WARNING) << kAlternateProtocolHeader
+ << " header has unrecognized protocol: "
+ << port_protocol_vector[1];
+ return;
+ }
+
+ HostPortPair host_port(http_host_port_pair);
+ const HostMappingRules* mapping_rules = GetHostMappingRules();
+ if (mapping_rules)
+ mapping_rules->RewriteHost(&host_port);
+
+ if (http_server_properties->HasAlternateProtocol(host_port)) {
+ const PortAlternateProtocolPair existing_alternate =
+ http_server_properties->GetAlternateProtocol(host_port);
+ // If we think the alternate protocol is broken, don't change it.
+ if (existing_alternate.protocol == ALTERNATE_PROTOCOL_BROKEN)
+ return;
+ }
+
+ http_server_properties->SetAlternateProtocol(host_port, port, protocol);
+}
+
+GURL HttpStreamFactory::ApplyHostMappingRules(const GURL& url,
+ HostPortPair* endpoint) {
+ const HostMappingRules* mapping_rules = GetHostMappingRules();
+ if (mapping_rules && mapping_rules->RewriteHost(endpoint)) {
+ url_canon::Replacements<char> replacements;
+ const std::string port_str = base::IntToString(endpoint->port());
+ replacements.SetPort(port_str.c_str(),
+ url_parse::Component(0, port_str.size()));
+ replacements.SetHost(endpoint->host().c_str(),
+ url_parse::Component(0, endpoint->host().size()));
+ return url.ReplaceComponents(replacements);
+ }
+ return url;
+}
+
+// static
+void HttpStreamFactory::add_forced_spdy_exclusion(const std::string& value) {
+ HostPortPair pair = HostPortPair::FromURL(GURL(value));
+ if (!forced_spdy_exclusions_)
+ forced_spdy_exclusions_ = new std::list<HostPortPair>();
+ forced_spdy_exclusions_->push_back(pair);
+}
+
+// static
+bool HttpStreamFactory::HasSpdyExclusion(const HostPortPair& endpoint) {
+ std::list<HostPortPair>* exclusions = forced_spdy_exclusions_;
+ if (!exclusions)
+ return false;
+
+ std::list<HostPortPair>::const_iterator it;
+ for (it = exclusions->begin(); it != exclusions->end(); ++it)
+ if (it->Equals(endpoint))
+ return true;
+ return false;
+}
+
+// static
+void HttpStreamFactory::EnableNpnSpdy() {
+ set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ next_protos.push_back(kProtoQUIC1SPDY3);
+ next_protos.push_back(kProtoSPDY2);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::EnableNpnHttpOnly() {
+ // Avoid alternate protocol in this case. Otherwise, browser will try SSL
+ // and then fallback to http. This introduces extra load.
+ set_use_alternate_protocols(false);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::EnableNpnSpdy3() {
+ set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ next_protos.push_back(kProtoQUIC1SPDY3);
+ next_protos.push_back(kProtoSPDY2);
+ next_protos.push_back(kProtoSPDY3);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::EnableNpnSpdy31() {
+ set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ next_protos.push_back(kProtoQUIC1SPDY3);
+ next_protos.push_back(kProtoSPDY2);
+ next_protos.push_back(kProtoSPDY3);
+ next_protos.push_back(kProtoSPDY31);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::EnableNpnSpdy4a2() {
+ set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ next_protos.push_back(kProtoQUIC1SPDY3);
+ next_protos.push_back(kProtoSPDY2);
+ next_protos.push_back(kProtoSPDY3);
+ next_protos.push_back(kProtoSPDY31);
+ next_protos.push_back(kProtoSPDY4a2);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::EnableNpnHttp2Draft04() {
+ set_use_alternate_protocols(true);
+ std::vector<NextProto> next_protos;
+ next_protos.push_back(kProtoHTTP11);
+ next_protos.push_back(kProtoQUIC1SPDY3);
+ next_protos.push_back(kProtoSPDY2);
+ next_protos.push_back(kProtoSPDY3);
+ next_protos.push_back(kProtoSPDY31);
+ next_protos.push_back(kProtoSPDY4a2);
+ next_protos.push_back(kProtoHTTP2Draft04);
+ SetNextProtos(next_protos);
+}
+
+// static
+void HttpStreamFactory::SetNextProtos(const std::vector<NextProto>& value) {
+ if (!next_protos_)
+ next_protos_ = new std::vector<std::string>;
+
+ next_protos_->clear();
+
+ for (uint32 i = 0; i < NUM_ALTERNATE_PROTOCOLS; ++i)
+ enabled_protocols_[i] = false;
+
+ // TODO(rtenneti): bug 116575 - consider combining the NextProto and
+ // AlternateProtocol.
+ for (uint32 i = 0; i < value.size(); ++i) {
+ NextProto proto = value[i];
+ // Add the protocol to the TLS next protocol list, except for QUIC
+ // since it uses UDP.
+ if (proto != kProtoQUIC1SPDY3) {
+ next_protos_->push_back(SSLClientSocket::NextProtoToString(proto));
+ }
+
+ // Enable the corresponding alternate protocol, except for HTTP
+ // which has not corresponding alternative.
+ if (proto != kProtoHTTP11) {
+ AlternateProtocol alternate = AlternateProtocolFromNextProto(proto);
+ if (alternate == UNINITIALIZED_ALTERNATE_PROTOCOL) {
+ NOTREACHED() << "Invalid next proto: " << proto;
+ continue;
+ }
+ enabled_protocols_[alternate] = true;
+ }
+ }
+}
+
+HttpStreamFactory::HttpStreamFactory() {}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_factory.h b/chromium/net/http/http_stream_factory.h
new file mode 100644
index 00000000000..0de3b65bc57
--- /dev/null
+++ b/chromium/net/http/http_stream_factory.h
@@ -0,0 +1,314 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STREAM_FACTORY_H_
+#define NET_HTTP_HTTP_STREAM_FACTORY_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_server_properties.h"
+#include "net/socket/ssl_client_socket.h"
+// This file can be included from net/http even though
+// it is in net/websockets because it doesn't
+// introduce any link dependency to net/websockets.
+#include "net/websockets/websocket_stream_base.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class AuthCredentials;
+class BoundNetLog;
+class HostMappingRules;
+class HostPortPair;
+class HttpAuthController;
+class HttpResponseInfo;
+class HttpServerProperties;
+class HttpStreamBase;
+class ProxyInfo;
+class SSLCertRequestInfo;
+class SSLInfo;
+struct HttpRequestInfo;
+struct SSLConfig;
+
+// The HttpStreamRequest is the client's handle to the worker object which
+// handles the creation of an HttpStream. While the HttpStream is being
+// created, this object is the creator's handle for interacting with the
+// HttpStream creation process. The request is cancelled by deleting it, after
+// which no callbacks will be invoked.
+class NET_EXPORT_PRIVATE HttpStreamRequest {
+ public:
+ // The HttpStreamRequest::Delegate is a set of callback methods for a
+ // HttpStreamRequestJob. Generally, only one of these methods will be
+ // called as a result of a stream request.
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // This is the success case for RequestStream.
+ // |stream| is now owned by the delegate.
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ // |used_proxy_info| indicates the actual ProxyInfo used for this stream,
+ // since the HttpStreamRequest performs the proxy resolution.
+ virtual void OnStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) = 0;
+
+ // This is the success case for RequestWebSocketStream.
+ // |stream| is now owned by the delegate.
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ // |used_proxy_info| indicates the actual ProxyInfo used for this stream,
+ // since the HttpStreamRequest performs the proxy resolution.
+ virtual void OnWebSocketStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) = 0;
+
+ // This is the failure to create a stream case.
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ virtual void OnStreamFailed(int status,
+ const SSLConfig& used_ssl_config) = 0;
+
+ // Called when we have a certificate error for the request.
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ virtual void OnCertificateError(int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) = 0;
+
+ // This is the failure case where we need proxy authentication during
+ // proxy tunnel establishment. For the tunnel case, we were unable to
+ // create the HttpStream, so the caller provides the auth and then resumes
+ // the HttpStreamRequest.
+ //
+ // For the non-tunnel case, the caller will discover the authentication
+ // failure when reading response headers. At that point, he will handle the
+ // authentication failure and restart the HttpStreamRequest entirely.
+ //
+ // Ownership of |auth_controller| and |proxy_response| are owned
+ // by the HttpStreamRequest. |proxy_response| is not guaranteed to be usable
+ // after the lifetime of this callback. The delegate may take a reference
+ // to |auth_controller| if it is needed beyond the lifetime of this
+ // callback.
+ //
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ virtual void OnNeedsProxyAuth(const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) = 0;
+
+ // This is the failure for SSL Client Auth
+ // Ownership of |cert_info| is retained by the HttpStreamRequest. The
+ // delegate may take a reference if it needs the cert_info beyond the
+ // lifetime of this callback.
+ virtual void OnNeedsClientAuth(const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) = 0;
+
+ // This is the failure of the CONNECT request through an HTTPS proxy.
+ // Headers can be read from |response_info|, while the body can be read
+ // from |stream|.
+ //
+ // |used_ssl_config| indicates the actual SSL configuration used for this
+ // stream, since the HttpStreamRequest may have modified the configuration
+ // during stream processing.
+ //
+ // |used_proxy_info| indicates the actual ProxyInfo used for this stream,
+ // since the HttpStreamRequest performs the proxy resolution.
+ //
+ // Ownership of |stream| is transferred to the delegate.
+ virtual void OnHttpsProxyTunnelResponse(
+ const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) = 0;
+ };
+
+ virtual ~HttpStreamRequest() {}
+
+ // When a HttpStream creation process is stalled due to necessity
+ // of Proxy authentication credentials, the delegate OnNeedsProxyAuth
+ // will have been called. It now becomes the delegate's responsibility
+ // to collect the necessary credentials, and then call this method to
+ // resume the HttpStream creation process.
+ virtual int RestartTunnelWithProxyAuth(
+ const AuthCredentials& credentials) = 0;
+
+ // Called when the priority of the parent transaction changes.
+ virtual void SetPriority(RequestPriority priority) = 0;
+
+ // Returns the LoadState for the request.
+ virtual LoadState GetLoadState() const = 0;
+
+ // Returns true if TLS/NPN was negotiated for this stream.
+ virtual bool was_npn_negotiated() const = 0;
+
+ // Protocol negotiated with the server.
+ virtual NextProto protocol_negotiated() const = 0;
+
+ // Returns true if this stream is being fetched over SPDY.
+ virtual bool using_spdy() const = 0;
+};
+
+// The HttpStreamFactory defines an interface for creating usable HttpStreams.
+class NET_EXPORT HttpStreamFactory {
+ public:
+ virtual ~HttpStreamFactory();
+
+ void ProcessAlternateProtocol(
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ const std::string& alternate_protocol_str,
+ const HostPortPair& http_host_port_pair);
+
+ GURL ApplyHostMappingRules(const GURL& url, HostPortPair* endpoint);
+
+ // Virtual interface methods.
+
+ // Request a stream.
+ // Will call delegate->OnStreamReady on successful completion.
+ virtual HttpStreamRequest* RequestStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ const BoundNetLog& net_log) = 0;
+
+ // Request a WebSocket stream.
+ // Will call delegate->OnWebSocketStreamReady on successful completion.
+ virtual HttpStreamRequest* RequestWebSocketStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* factory,
+ const BoundNetLog& net_log) = 0;
+
+ // Requests that enough connections for |num_streams| be opened.
+ virtual void PreconnectStreams(int num_streams,
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config) = 0;
+
+ // If pipelining is supported, creates a Value summary of the currently active
+ // pipelines. Caller assumes ownership of the returned value. Otherwise,
+ // returns an empty Value.
+ virtual base::Value* PipelineInfoToValue() const = 0;
+
+ virtual const HostMappingRules* GetHostMappingRules() const = 0;
+
+ // Static settings
+
+ // Reset all static settings to initialized values. Used to init test suite.
+ static void ResetStaticSettingsToInit();
+
+ // Turns spdy on or off.
+ static void set_spdy_enabled(bool value) {
+ spdy_enabled_ = value;
+ if (!spdy_enabled_) {
+ delete next_protos_;
+ next_protos_ = NULL;
+ }
+ }
+ static bool spdy_enabled() { return spdy_enabled_; }
+
+ // Controls whether or not we use the Alternate-Protocol header.
+ static void set_use_alternate_protocols(bool value) {
+ use_alternate_protocols_ = value;
+ }
+ static bool use_alternate_protocols() { return use_alternate_protocols_; }
+
+ // Controls whether or not we use ssl when in spdy mode.
+ static void set_force_spdy_over_ssl(bool value) {
+ force_spdy_over_ssl_ = value;
+ }
+ static bool force_spdy_over_ssl() {
+ return force_spdy_over_ssl_;
+ }
+
+ // Controls whether or not we use spdy without npn.
+ static void set_force_spdy_always(bool value) {
+ force_spdy_always_ = value;
+ }
+ static bool force_spdy_always() { return force_spdy_always_; }
+
+ // Add a URL to exclude from forced SPDY.
+ static void add_forced_spdy_exclusion(const std::string& value);
+ // Check if a HostPortPair is excluded from using spdy.
+ static bool HasSpdyExclusion(const HostPortPair& endpoint);
+
+ // Sets http/1.1 as the only protocol supported via NPN or Alternate-Protocol.
+ static void EnableNpnHttpOnly();
+
+ // Sets http/1.1, quic and spdy/2 (the default spdy protocol) as the protocols
+ // supported via NPN or Alternate-Protocol.
+ static void EnableNpnSpdy();
+
+ // Sets http/1.1, quic, spdy/2, and spdy/3 as the protocols supported via NPN
+ // or Alternate-Protocol.
+ static void EnableNpnSpdy3();
+
+ // Sets http/1.1, quic, spdy/2, spdy/3, and spdy/3.1 as the protocols
+ // supported via NPN or Alternate-Protocol.
+ static void EnableNpnSpdy31();
+
+ // Sets http/1.1, quic, spdy/2, spdy/3, spdy/3.1, and spdy/4a2 as
+ // the protocols supported via NPN or Alternate-Protocol.
+ static void EnableNpnSpdy4a2();
+
+ // Sets http/1.1, quic, spdy/2, spdy/3, spdy/3.1, spdy/4a2, and
+ // http/2 draft 04 as the protocols supported via NPN or
+ // Alternate-Protocol.
+ static void EnableNpnHttp2Draft04();
+
+ // Sets the protocols supported by NPN (next protocol negotiation) during the
+ // SSL handshake as well as by HTTP Alternate-Protocol.
+ static void SetNextProtos(const std::vector<NextProto>& value);
+ static bool has_next_protos() { return next_protos_ != NULL; }
+ static const std::vector<std::string>& next_protos() {
+ return *next_protos_;
+ }
+
+ protected:
+ HttpStreamFactory();
+
+ private:
+ static std::vector<std::string>* next_protos_;
+ static bool enabled_protocols_[NUM_ALTERNATE_PROTOCOLS];
+ static bool spdy_enabled_;
+ static bool use_alternate_protocols_;
+ static bool force_spdy_over_ssl_;
+ static bool force_spdy_always_;
+ static std::list<HostPortPair>* forced_spdy_exclusions_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpStreamFactory);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_FACTORY_H_
diff --git a/chromium/net/http/http_stream_factory_impl.cc b/chromium/net/http/http_stream_factory_impl.cc
new file mode 100644
index 00000000000..35d94d779f8
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_factory_impl.h"
+
+#include <string>
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_stream.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_stream_factory_impl_job.h"
+#include "net/http/http_stream_factory_impl_request.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+const PortAlternateProtocolPair kNoAlternateProtocol = {
+ 0, UNINITIALIZED_ALTERNATE_PROTOCOL
+};
+
+GURL UpgradeUrlToHttps(const GURL& original_url, int port) {
+ GURL::Replacements replacements;
+ // new_sheme and new_port need to be in scope here because GURL::Replacements
+ // references the memory contained by them directly.
+ const std::string new_scheme = "https";
+ const std::string new_port = base::IntToString(port);
+ replacements.SetSchemeStr(new_scheme);
+ replacements.SetPortStr(new_port);
+ return original_url.ReplaceComponents(replacements);
+}
+
+} // namespace
+
+HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session,
+ bool for_websockets)
+ : session_(session),
+ http_pipelined_host_pool_(this, NULL,
+ session_->http_server_properties(),
+ session_->force_http_pipelining()),
+ for_websockets_(for_websockets) {}
+
+HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
+ DCHECK(request_map_.empty());
+ DCHECK(spdy_session_request_map_.empty());
+ DCHECK(http_pipelining_request_map_.empty());
+
+ std::set<const Job*> tmp_job_set;
+ tmp_job_set.swap(orphaned_job_set_);
+ STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
+ DCHECK(orphaned_job_set_.empty());
+
+ tmp_job_set.clear();
+ tmp_job_set.swap(preconnect_job_set_);
+ STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
+ DCHECK(preconnect_job_set_.empty());
+}
+
+HttpStreamRequest* HttpStreamFactoryImpl::RequestStream(
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ const BoundNetLog& net_log) {
+ DCHECK(!for_websockets_);
+ return RequestStreamInternal(request_info,
+ priority,
+ server_ssl_config,
+ proxy_ssl_config,
+ delegate,
+ NULL,
+ net_log);
+}
+
+HttpStreamRequest* HttpStreamFactoryImpl::RequestWebSocketStream(
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* factory,
+ const BoundNetLog& net_log) {
+ DCHECK(for_websockets_);
+ DCHECK(factory);
+ return RequestStreamInternal(request_info,
+ priority,
+ server_ssl_config,
+ proxy_ssl_config,
+ delegate,
+ factory,
+ net_log);
+}
+
+HttpStreamRequest* HttpStreamFactoryImpl::RequestStreamInternal(
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* websocket_stream_factory,
+ const BoundNetLog& net_log) {
+ Request* request = new Request(request_info.url,
+ this,
+ delegate,
+ websocket_stream_factory,
+ net_log);
+
+ GURL alternate_url;
+ PortAlternateProtocolPair alternate =
+ GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
+ Job* alternate_job = NULL;
+ if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
+ // Never share connection with other jobs for FTP requests.
+ DCHECK(!request_info.url.SchemeIs("ftp"));
+
+ HttpRequestInfo alternate_request_info = request_info;
+ alternate_request_info.url = alternate_url;
+ alternate_job =
+ new Job(this, session_, alternate_request_info, priority,
+ server_ssl_config, proxy_ssl_config, net_log.net_log());
+ request->AttachJob(alternate_job);
+ alternate_job->MarkAsAlternate(request_info.url, alternate);
+ }
+
+ Job* job = new Job(this, session_, request_info, priority,
+ server_ssl_config, proxy_ssl_config, net_log.net_log());
+ request->AttachJob(job);
+ if (alternate_job) {
+ // Never share connection with other jobs for FTP requests.
+ DCHECK(!request_info.url.SchemeIs("ftp"));
+
+ job->WaitFor(alternate_job);
+ // Make sure to wait until we call WaitFor(), before starting
+ // |alternate_job|, otherwise |alternate_job| will not notify |job|
+ // appropriately.
+ alternate_job->Start(request);
+ }
+ // Even if |alternate_job| has already finished, it won't have notified the
+ // request yet, since we defer that to the next iteration of the MessageLoop,
+ // so starting |job| is always safe.
+ job->Start(request);
+ return request;
+}
+
+void HttpStreamFactoryImpl::PreconnectStreams(
+ int num_streams,
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config) {
+ DCHECK(!for_websockets_);
+ GURL alternate_url;
+ PortAlternateProtocolPair alternate =
+ GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
+ Job* job = NULL;
+ if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) {
+ HttpRequestInfo alternate_request_info = request_info;
+ alternate_request_info.url = alternate_url;
+ job = new Job(this, session_, alternate_request_info, priority,
+ server_ssl_config, proxy_ssl_config, session_->net_log());
+ job->MarkAsAlternate(request_info.url, alternate);
+ } else {
+ job = new Job(this, session_, request_info, priority,
+ server_ssl_config, proxy_ssl_config, session_->net_log());
+ }
+ preconnect_job_set_.insert(job);
+ job->Preconnect(num_streams);
+}
+
+base::Value* HttpStreamFactoryImpl::PipelineInfoToValue() const {
+ return http_pipelined_host_pool_.PipelineInfoToValue();
+}
+
+const HostMappingRules* HttpStreamFactoryImpl::GetHostMappingRules() const {
+ return session_->params().host_mapping_rules;
+}
+
+PortAlternateProtocolPair HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
+ const GURL& original_url,
+ GURL* alternate_url) const {
+ if (!use_alternate_protocols())
+ return kNoAlternateProtocol;
+
+ if (original_url.SchemeIs("ftp"))
+ return kNoAlternateProtocol;
+
+ HostPortPair origin = HostPortPair(original_url.HostNoBrackets(),
+ original_url.EffectiveIntPort());
+
+ const HttpServerProperties& http_server_properties =
+ *session_->http_server_properties();
+ if (!http_server_properties.HasAlternateProtocol(origin))
+ return kNoAlternateProtocol;
+
+ PortAlternateProtocolPair alternate =
+ http_server_properties.GetAlternateProtocol(origin);
+ if (alternate.protocol == ALTERNATE_PROTOCOL_BROKEN)
+ return kNoAlternateProtocol;
+
+ DCHECK_LE(NPN_SPDY_1, alternate.protocol);
+ DCHECK_GT(NUM_ALTERNATE_PROTOCOLS, alternate.protocol);
+
+ if (alternate.protocol < NPN_SPDY_2)
+ return kNoAlternateProtocol;
+
+ // Some shared unix systems may have user home directories (like
+ // http://foo.com/~mike) which allow users to emit headers. This is a bad
+ // idea already, but with Alternate-Protocol, it provides the ability for a
+ // single user on a multi-user system to hijack the alternate protocol.
+ // These systems also enforce ports <1024 as restricted ports. So don't
+ // allow protocol upgrades to user-controllable ports.
+ const int kUnrestrictedPort = 1024;
+ if (!session_->params().enable_user_alternate_protocol_ports &&
+ (alternate.port >= kUnrestrictedPort &&
+ origin.port() < kUnrestrictedPort))
+ return kNoAlternateProtocol;
+
+ origin.set_port(alternate.port);
+ if (alternate.protocol >= NPN_SPDY_MINIMUM_VERSION &&
+ alternate.protocol <= NPN_SPDY_MAXIMUM_VERSION) {
+ if (!spdy_enabled())
+ return kNoAlternateProtocol;
+
+ if (HttpStreamFactory::HasSpdyExclusion(origin))
+ return kNoAlternateProtocol;
+
+ *alternate_url = UpgradeUrlToHttps(original_url, alternate.port);
+ } else {
+ DCHECK_EQ(QUIC, alternate.protocol);
+ if (!session_->params().enable_quic ||
+ !(original_url.SchemeIs("http") ||
+ session_->params().enable_quic_https)) {
+ return kNoAlternateProtocol;
+ }
+ // TODO(rch): Figure out how to make QUIC iteract with PAC
+ // scripts. By not re-writing the URL, we will query the PAC script
+ // for the proxy to use to reach the original URL via TCP. But
+ // the alternate request will be going via UDP to a different port.
+ *alternate_url = original_url;
+ }
+ return alternate;
+}
+
+void HttpStreamFactoryImpl::OrphanJob(Job* job, const Request* request) {
+ DCHECK(ContainsKey(request_map_, job));
+ DCHECK_EQ(request_map_[job], request);
+ DCHECK(!ContainsKey(orphaned_job_set_, job));
+
+ request_map_.erase(job);
+
+ orphaned_job_set_.insert(job);
+ job->Orphan(request);
+}
+
+void HttpStreamFactoryImpl::OnNewSpdySessionReady(
+ const base::WeakPtr<SpdySession>& spdy_session,
+ bool direct,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated,
+ bool using_spdy,
+ const BoundNetLog& net_log) {
+ while (true) {
+ if (!spdy_session)
+ break;
+ const SpdySessionKey& spdy_session_key = spdy_session->spdy_session_key();
+ // Each iteration may empty out the RequestSet for |spdy_session_key| in
+ // |spdy_session_request_map_|. So each time, check for RequestSet and use
+ // the first one.
+ //
+ // TODO(willchan): If it's important, switch RequestSet out for a FIFO
+ // queue (Order by priority first, then FIFO within same priority). Unclear
+ // that it matters here.
+ if (!ContainsKey(spdy_session_request_map_, spdy_session_key))
+ break;
+ Request* request = *spdy_session_request_map_[spdy_session_key].begin();
+ request->Complete(was_npn_negotiated,
+ protocol_negotiated,
+ using_spdy,
+ net_log);
+ if (for_websockets_) {
+ WebSocketStreamBase::Factory* factory =
+ request->websocket_stream_factory();
+ DCHECK(factory);
+ bool use_relative_url = direct || request->url().SchemeIs("wss");
+ request->OnWebSocketStreamReady(
+ NULL,
+ used_ssl_config,
+ used_proxy_info,
+ factory->CreateSpdyStream(spdy_session, use_relative_url));
+ } else {
+ bool use_relative_url = direct || request->url().SchemeIs("https");
+ request->OnStreamReady(
+ NULL,
+ used_ssl_config,
+ used_proxy_info,
+ new SpdyHttpStream(spdy_session, use_relative_url));
+ }
+ }
+ // TODO(mbelshe): Alert other valid requests.
+}
+
+void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) {
+ orphaned_job_set_.erase(job);
+ delete job;
+}
+
+void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) {
+ preconnect_job_set_.erase(job);
+ delete job;
+ OnPreconnectsCompleteInternal();
+}
+
+void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity(
+ HttpPipelinedHost* host) {
+ while (ContainsKey(http_pipelining_request_map_, host->GetKey())) {
+ HttpPipelinedStream* stream =
+ http_pipelined_host_pool_.CreateStreamOnExistingPipeline(
+ host->GetKey());
+ if (!stream) {
+ break;
+ }
+
+ Request* request = *http_pipelining_request_map_[host->GetKey()].begin();
+ request->Complete(stream->was_npn_negotiated(),
+ stream->protocol_negotiated(),
+ false, // not using_spdy
+ stream->net_log());
+ request->OnStreamReady(NULL,
+ stream->used_ssl_config(),
+ stream->used_proxy_info(),
+ stream);
+ }
+}
+
+void HttpStreamFactoryImpl::AbortPipelinedRequestsWithKey(
+ const Job* job, const HttpPipelinedHost::Key& key, int status,
+ const SSLConfig& used_ssl_config) {
+ RequestVector requests_to_fail = http_pipelining_request_map_[key];
+ for (RequestVector::const_iterator it = requests_to_fail.begin();
+ it != requests_to_fail.end(); ++it) {
+ Request* request = *it;
+ if (request == request_map_[job]) {
+ continue;
+ }
+ request->OnStreamFailed(NULL, status, used_ssl_config);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_factory_impl.h b/chromium/net/http/http_stream_factory_impl.h
new file mode 100644
index 00000000000..4339fd350d7
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl.h
@@ -0,0 +1,158 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STREAM_FACTORY_IMPL_H_
+#define NET_HTTP_HTTP_STREAM_FACTORY_IMPL_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/http/http_pipelined_host_pool.h"
+#include "net/http/http_stream_factory.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_session_key.h"
+
+namespace net {
+
+class HttpNetworkSession;
+class HttpPipelinedHost;
+class SpdySession;
+
+class NET_EXPORT_PRIVATE HttpStreamFactoryImpl :
+ public HttpStreamFactory,
+ public HttpPipelinedHostPool::Delegate {
+ public:
+ // RequestStream may only be called if |for_websockets| is false.
+ // RequestWebSocketStream may only be called if |for_websockets| is true.
+ HttpStreamFactoryImpl(HttpNetworkSession* session, bool for_websockets);
+ virtual ~HttpStreamFactoryImpl();
+
+ // HttpStreamFactory interface
+ virtual HttpStreamRequest* RequestStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual HttpStreamRequest* RequestWebSocketStream(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* factory,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void PreconnectStreams(int num_streams,
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config) OVERRIDE;
+ virtual base::Value* PipelineInfoToValue() const OVERRIDE;
+ virtual const HostMappingRules* GetHostMappingRules() const OVERRIDE;
+
+ // HttpPipelinedHostPool::Delegate interface
+ virtual void OnHttpPipelinedHostHasAdditionalCapacity(
+ HttpPipelinedHost* host) OVERRIDE;
+
+ size_t num_orphaned_jobs() const { return orphaned_job_set_.size(); }
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HttpStreamFactoryImplRequestTest, SetPriority);
+
+ class NET_EXPORT_PRIVATE Request;
+ class NET_EXPORT_PRIVATE Job;
+
+ typedef std::set<Request*> RequestSet;
+ typedef std::vector<Request*> RequestVector;
+ typedef std::map<SpdySessionKey, RequestSet> SpdySessionRequestMap;
+ typedef std::map<HttpPipelinedHost::Key,
+ RequestVector> HttpPipeliningRequestMap;
+
+ HttpStreamRequest* RequestStreamInternal(
+ const HttpRequestInfo& info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* factory,
+ const BoundNetLog& net_log);
+
+ PortAlternateProtocolPair GetAlternateProtocolRequestFor(
+ const GURL& original_url,
+ GURL* alternate_url) const;
+
+ // Detaches |job| from |request|.
+ void OrphanJob(Job* job, const Request* request);
+
+ // Called when a SpdySession is ready. It will find appropriate Requests and
+ // fulfill them. |direct| indicates whether or not |spdy_session| uses a
+ // proxy.
+ void OnNewSpdySessionReady(const base::WeakPtr<SpdySession>& spdy_session,
+ bool direct,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated,
+ bool using_spdy,
+ const BoundNetLog& net_log);
+
+ // Called when the Job detects that the endpoint indicated by the
+ // Alternate-Protocol does not work. Lets the factory update
+ // HttpAlternateProtocols with the failure and resets the SPDY session key.
+ void OnBrokenAlternateProtocol(const Job*, const HostPortPair& origin);
+
+ // Invoked when an orphaned Job finishes.
+ void OnOrphanedJobComplete(const Job* job);
+
+ // Invoked when the Job finishes preconnecting sockets.
+ void OnPreconnectsComplete(const Job* job);
+
+ // Called when the Preconnect completes. Used for testing.
+ virtual void OnPreconnectsCompleteInternal() {}
+
+ void AbortPipelinedRequestsWithKey(const Job* job,
+ const HttpPipelinedHost::Key& key,
+ int status,
+ const SSLConfig& used_ssl_config);
+
+ HttpNetworkSession* const session_;
+
+ // All Requests are handed out to clients. By the time HttpStreamFactoryImpl
+ // is destroyed, all Requests should be deleted (which should remove them from
+ // |request_map_|. The Requests will delete the corresponding job.
+ std::map<const Job*, Request*> request_map_;
+
+ SpdySessionRequestMap spdy_session_request_map_;
+ HttpPipeliningRequestMap http_pipelining_request_map_;
+
+ HttpPipelinedHostPool http_pipelined_host_pool_;
+
+ // These jobs correspond to jobs orphaned by Requests and now owned by
+ // HttpStreamFactoryImpl. Since they are no longer tied to Requests, they will
+ // not be canceled when Requests are canceled. Therefore, in
+ // ~HttpStreamFactoryImpl, it is possible for some jobs to still exist in this
+ // set. Leftover jobs will be deleted when the factory is destroyed.
+ std::set<const Job*> orphaned_job_set_;
+
+ // These jobs correspond to preconnect requests and have no associated Request
+ // object. They're owned by HttpStreamFactoryImpl. Leftover jobs will be
+ // deleted when the factory is destroyed.
+ std::set<const Job*> preconnect_job_set_;
+
+ const bool for_websockets_;
+ DISALLOW_COPY_AND_ASSIGN(HttpStreamFactoryImpl);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_FACTORY_IMPL_H_
diff --git a/chromium/net/http/http_stream_factory_impl_job.cc b/chromium/net/http/http_stream_factory_impl_job.cc
new file mode 100644
index 00000000000..c0383f4772d
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_job.cc
@@ -0,0 +1,1480 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_factory_impl_job.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/http/http_basic_stream.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_pipelined_connection.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_pipelined_host_pool.h"
+#include "net/http/http_pipelined_stream.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_stream_factory_impl_request.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/ssl/ssl_cert_request_info.h"
+
+namespace net {
+
+// Returns parameters associated with the start of a HTTP stream job.
+base::Value* NetLogHttpStreamJobCallback(const GURL* original_url,
+ const GURL* url,
+ RequestPriority priority,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("original_url", original_url->GetOrigin().spec());
+ dict->SetString("url", url->GetOrigin().spec());
+ dict->SetInteger("priority", priority);
+ return dict;
+}
+
+// Returns parameters associated with the Proto (with NPN negotiation) of a HTTP
+// stream.
+base::Value* NetLogHttpStreamProtoCallback(
+ const SSLClientSocket::NextProtoStatus status,
+ const std::string* proto,
+ const std::string* server_protos,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetString("next_proto_status",
+ SSLClientSocket::NextProtoStatusToString(status));
+ dict->SetString("proto", *proto);
+ dict->SetString("server_protos",
+ SSLClientSocket::ServerProtosToString(*server_protos));
+ return dict;
+}
+
+HttpStreamFactoryImpl::Job::Job(HttpStreamFactoryImpl* stream_factory,
+ HttpNetworkSession* session,
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ NetLog* net_log)
+ : request_(NULL),
+ request_info_(request_info),
+ priority_(priority),
+ server_ssl_config_(server_ssl_config),
+ proxy_ssl_config_(proxy_ssl_config),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_HTTP_STREAM_JOB)),
+ io_callback_(base::Bind(&Job::OnIOComplete, base::Unretained(this))),
+ connection_(new ClientSocketHandle),
+ session_(session),
+ stream_factory_(stream_factory),
+ next_state_(STATE_NONE),
+ pac_request_(NULL),
+ blocking_job_(NULL),
+ waiting_job_(NULL),
+ using_ssl_(false),
+ using_spdy_(false),
+ using_quic_(false),
+ quic_request_(session_->quic_stream_factory()),
+ force_spdy_always_(HttpStreamFactory::force_spdy_always()),
+ force_spdy_over_ssl_(HttpStreamFactory::force_spdy_over_ssl()),
+ spdy_certificate_error_(OK),
+ establishing_tunnel_(false),
+ was_npn_negotiated_(false),
+ protocol_negotiated_(kProtoUnknown),
+ num_streams_(0),
+ spdy_session_direct_(false),
+ existing_available_pipeline_(false),
+ ptr_factory_(this) {
+ DCHECK(stream_factory);
+ DCHECK(session);
+}
+
+HttpStreamFactoryImpl::Job::~Job() {
+ net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_JOB);
+
+ // When we're in a partially constructed state, waiting for the user to
+ // provide certificate handling information or authentication, we can't reuse
+ // this stream at all.
+ if (next_state_ == STATE_WAITING_USER_ACTION) {
+ connection_->socket()->Disconnect();
+ connection_.reset();
+ }
+
+ if (pac_request_)
+ session_->proxy_service()->CancelPacRequest(pac_request_);
+
+ // The stream could be in a partial state. It is not reusable.
+ if (stream_.get() && next_state_ != STATE_DONE)
+ stream_->Close(true /* not reusable */);
+}
+
+void HttpStreamFactoryImpl::Job::Start(Request* request) {
+ DCHECK(request);
+ request_ = request;
+ StartInternal();
+}
+
+int HttpStreamFactoryImpl::Job::Preconnect(int num_streams) {
+ DCHECK_GT(num_streams, 0);
+ HostPortPair origin_server =
+ HostPortPair(request_info_.url.HostNoBrackets(),
+ request_info_.url.EffectiveIntPort());
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session_->http_server_properties();
+ if (http_server_properties &&
+ http_server_properties->SupportsSpdy(origin_server)) {
+ num_streams_ = 1;
+ } else {
+ num_streams_ = num_streams;
+ }
+ return StartInternal();
+}
+
+int HttpStreamFactoryImpl::Job::RestartTunnelWithProxyAuth(
+ const AuthCredentials& credentials) {
+ DCHECK(establishing_tunnel_);
+ next_state_ = STATE_RESTART_TUNNEL_AUTH;
+ stream_.reset();
+ return RunLoop(OK);
+}
+
+LoadState HttpStreamFactoryImpl::Job::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_RESOLVE_PROXY_COMPLETE:
+ return session_->proxy_service()->GetLoadState(pac_request_);
+ case STATE_INIT_CONNECTION_COMPLETE:
+ case STATE_CREATE_STREAM_COMPLETE:
+ return using_quic_ ? LOAD_STATE_CONNECTING : connection_->GetLoadState();
+ default:
+ return LOAD_STATE_IDLE;
+ }
+}
+
+void HttpStreamFactoryImpl::Job::MarkAsAlternate(
+ const GURL& original_url,
+ PortAlternateProtocolPair alternate) {
+ DCHECK(!original_url_.get());
+ original_url_.reset(new GURL(original_url));
+ if (alternate.protocol == QUIC) {
+ DCHECK(session_->params().enable_quic);
+ using_quic_ = true;
+ }
+}
+
+void HttpStreamFactoryImpl::Job::WaitFor(Job* job) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK_EQ(STATE_NONE, job->next_state_);
+ DCHECK(!blocking_job_);
+ DCHECK(!job->waiting_job_);
+ blocking_job_ = job;
+ job->waiting_job_ = this;
+}
+
+void HttpStreamFactoryImpl::Job::Resume(Job* job) {
+ DCHECK_EQ(blocking_job_, job);
+ blocking_job_ = NULL;
+
+ // We know we're blocked if the next_state_ is STATE_WAIT_FOR_JOB_COMPLETE.
+ // Unblock |this|.
+ if (next_state_ == STATE_WAIT_FOR_JOB_COMPLETE) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpStreamFactoryImpl::Job::OnIOComplete,
+ ptr_factory_.GetWeakPtr(), OK));
+ }
+}
+
+void HttpStreamFactoryImpl::Job::Orphan(const Request* request) {
+ DCHECK_EQ(request_, request);
+ request_ = NULL;
+ if (blocking_job_) {
+ // We've been orphaned, but there's a job we're blocked on. Don't bother
+ // racing, just cancel ourself.
+ DCHECK(blocking_job_->waiting_job_);
+ blocking_job_->waiting_job_ = NULL;
+ blocking_job_ = NULL;
+ if (stream_factory_->for_websockets_ &&
+ connection_ && connection_->socket())
+ connection_->socket()->Disconnect();
+ stream_factory_->OnOrphanedJobComplete(this);
+ } else if (stream_factory_->for_websockets_) {
+ // We cancel this job because WebSocketStream can't be created
+ // without a WebSocketStreamBase::Factory which is stored in Request class
+ // and isn't accessible from this job.
+ if (connection_ && connection_->socket())
+ connection_->socket()->Disconnect();
+ stream_factory_->OnOrphanedJobComplete(this);
+ }
+}
+
+void HttpStreamFactoryImpl::Job::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+ // TODO(akalin): Propagate this to |connection_| and maybe the
+ // preconnect state.
+}
+
+bool HttpStreamFactoryImpl::Job::was_npn_negotiated() const {
+ return was_npn_negotiated_;
+}
+
+NextProto HttpStreamFactoryImpl::Job::protocol_negotiated() const {
+ return protocol_negotiated_;
+}
+
+bool HttpStreamFactoryImpl::Job::using_spdy() const {
+ return using_spdy_;
+}
+
+const SSLConfig& HttpStreamFactoryImpl::Job::server_ssl_config() const {
+ return server_ssl_config_;
+}
+
+const SSLConfig& HttpStreamFactoryImpl::Job::proxy_ssl_config() const {
+ return proxy_ssl_config_;
+}
+
+const ProxyInfo& HttpStreamFactoryImpl::Job::proxy_info() const {
+ return proxy_info_;
+}
+
+void HttpStreamFactoryImpl::Job::GetSSLInfo() {
+ DCHECK(using_ssl_);
+ DCHECK(!establishing_tunnel_);
+ DCHECK(connection_.get() && connection_->socket());
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(&ssl_info_);
+}
+
+SpdySessionKey HttpStreamFactoryImpl::Job::GetSpdySessionKey() const {
+ // In the case that we're using an HTTPS proxy for an HTTP url,
+ // we look for a SPDY session *to* the proxy, instead of to the
+ // origin server.
+ PrivacyMode privacy_mode = request_info_.privacy_mode;
+ if (IsHttpsProxyAndHttpUrl()) {
+ return SpdySessionKey(proxy_info_.proxy_server().host_port_pair(),
+ ProxyServer::Direct(),
+ privacy_mode);
+ } else {
+ return SpdySessionKey(origin_,
+ proxy_info_.proxy_server(),
+ privacy_mode);
+ }
+}
+
+bool HttpStreamFactoryImpl::Job::CanUseExistingSpdySession() const {
+ // We need to make sure that if a spdy session was created for
+ // https://somehost/ that we don't use that session for http://somehost:443/.
+ // The only time we can use an existing session is if the request URL is
+ // https (the normal case) or if we're connection to a SPDY proxy, or
+ // if we're running with force_spdy_always_. crbug.com/133176
+ return request_info_.url.SchemeIs("https") ||
+ request_info_.url.SchemeIs("wss") ||
+ proxy_info_.proxy_server().is_https() ||
+ force_spdy_always_;
+}
+
+void HttpStreamFactoryImpl::Job::OnStreamReadyCallback() {
+ DCHECK(stream_.get());
+ DCHECK(!IsPreconnecting());
+ DCHECK(!stream_factory_->for_websockets_);
+ if (IsOrphaned()) {
+ stream_factory_->OnOrphanedJobComplete(this);
+ } else {
+ request_->Complete(was_npn_negotiated(),
+ protocol_negotiated(),
+ using_spdy(),
+ net_log_);
+ request_->OnStreamReady(this, server_ssl_config_, proxy_info_,
+ stream_.release());
+ }
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnWebSocketStreamReadyCallback() {
+ DCHECK(websocket_stream_);
+ DCHECK(!IsPreconnecting());
+ DCHECK(stream_factory_->for_websockets_);
+ // An orphaned WebSocket job will be closed immediately and
+ // never be ready.
+ DCHECK(!IsOrphaned());
+ request_->Complete(was_npn_negotiated(),
+ protocol_negotiated(),
+ using_spdy(),
+ net_log_);
+ request_->OnWebSocketStreamReady(this,
+ server_ssl_config_,
+ proxy_info_,
+ websocket_stream_.release());
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnNewSpdySessionReadyCallback() {
+ DCHECK(!stream_.get());
+ DCHECK(!IsPreconnecting());
+ DCHECK(using_spdy());
+ if (!new_spdy_session_)
+ return;
+ base::WeakPtr<SpdySession> spdy_session = new_spdy_session_;
+ new_spdy_session_.reset();
+ if (IsOrphaned()) {
+ stream_factory_->OnNewSpdySessionReady(
+ spdy_session, spdy_session_direct_, server_ssl_config_, proxy_info_,
+ was_npn_negotiated(), protocol_negotiated(), using_spdy(), net_log_);
+ stream_factory_->OnOrphanedJobComplete(this);
+ } else {
+ request_->OnNewSpdySessionReady(this, spdy_session, spdy_session_direct_);
+ }
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnStreamFailedCallback(int result) {
+ DCHECK(!IsPreconnecting());
+ if (IsOrphaned())
+ stream_factory_->OnOrphanedJobComplete(this);
+ else
+ request_->OnStreamFailed(this, result, server_ssl_config_);
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnCertificateErrorCallback(
+ int result, const SSLInfo& ssl_info) {
+ DCHECK(!IsPreconnecting());
+ if (IsOrphaned())
+ stream_factory_->OnOrphanedJobComplete(this);
+ else
+ request_->OnCertificateError(this, result, server_ssl_config_, ssl_info);
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnNeedsProxyAuthCallback(
+ const HttpResponseInfo& response,
+ HttpAuthController* auth_controller) {
+ DCHECK(!IsPreconnecting());
+ if (IsOrphaned())
+ stream_factory_->OnOrphanedJobComplete(this);
+ else
+ request_->OnNeedsProxyAuth(
+ this, response, server_ssl_config_, proxy_info_, auth_controller);
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnNeedsClientAuthCallback(
+ SSLCertRequestInfo* cert_info) {
+ DCHECK(!IsPreconnecting());
+ if (IsOrphaned())
+ stream_factory_->OnOrphanedJobComplete(this);
+ else
+ request_->OnNeedsClientAuth(this, server_ssl_config_, cert_info);
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnHttpsProxyTunnelResponseCallback(
+ const HttpResponseInfo& response_info,
+ HttpStream* stream) {
+ DCHECK(!IsPreconnecting());
+ if (IsOrphaned())
+ stream_factory_->OnOrphanedJobComplete(this);
+ else
+ request_->OnHttpsProxyTunnelResponse(
+ this, response_info, server_ssl_config_, proxy_info_, stream);
+ // |this| may be deleted after this call.
+}
+
+void HttpStreamFactoryImpl::Job::OnPreconnectsComplete() {
+ DCHECK(!request_);
+ if (new_spdy_session_.get()) {
+ stream_factory_->OnNewSpdySessionReady(new_spdy_session_,
+ spdy_session_direct_,
+ server_ssl_config_,
+ proxy_info_,
+ was_npn_negotiated(),
+ protocol_negotiated(),
+ using_spdy(),
+ net_log_);
+ }
+ stream_factory_->OnPreconnectsComplete(this);
+ // |this| may be deleted after this call.
+}
+
+// static
+int HttpStreamFactoryImpl::Job::OnHostResolution(
+ SpdySessionPool* spdy_session_pool,
+ const SpdySessionKey& spdy_session_key,
+ const AddressList& addresses,
+ const BoundNetLog& net_log) {
+ // It is OK to dereference spdy_session_pool, because the
+ // ClientSocketPoolManager will be destroyed in the same callback that
+ // destroys the SpdySessionPool.
+ return
+ spdy_session_pool->FindAvailableSession(spdy_session_key, net_log) ?
+ ERR_SPDY_SESSION_ALREADY_EXISTS : OK;
+}
+
+void HttpStreamFactoryImpl::Job::OnIOComplete(int result) {
+ RunLoop(result);
+}
+
+int HttpStreamFactoryImpl::Job::RunLoop(int result) {
+ result = DoLoop(result);
+
+ if (result == ERR_IO_PENDING)
+ return result;
+
+ // If there was an error, we should have already resumed the |waiting_job_|,
+ // if there was one.
+ DCHECK(result == OK || waiting_job_ == NULL);
+
+ if (IsPreconnecting()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &HttpStreamFactoryImpl::Job::OnPreconnectsComplete,
+ ptr_factory_.GetWeakPtr()));
+ return ERR_IO_PENDING;
+ }
+
+ if (IsCertificateError(result)) {
+ // Retrieve SSL information from the socket.
+ GetSSLInfo();
+
+ next_state_ = STATE_WAITING_USER_ACTION;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &HttpStreamFactoryImpl::Job::OnCertificateErrorCallback,
+ ptr_factory_.GetWeakPtr(),
+ result, ssl_info_));
+ return ERR_IO_PENDING;
+ }
+
+ switch (result) {
+ case ERR_PROXY_AUTH_REQUESTED:
+ {
+ DCHECK(connection_.get());
+ DCHECK(connection_->socket());
+ DCHECK(establishing_tunnel_);
+
+ ProxyClientSocket* proxy_socket =
+ static_cast<ProxyClientSocket*>(connection_->socket());
+ const HttpResponseInfo* tunnel_auth_response =
+ proxy_socket->GetConnectResponseInfo();
+
+ next_state_ = STATE_WAITING_USER_ACTION;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnNeedsProxyAuthCallback,
+ ptr_factory_.GetWeakPtr(),
+ *tunnel_auth_response,
+ proxy_socket->GetAuthController()));
+ }
+ return ERR_IO_PENDING;
+
+ case ERR_SSL_CLIENT_AUTH_CERT_NEEDED:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnNeedsClientAuthCallback,
+ ptr_factory_.GetWeakPtr(),
+ connection_->ssl_error_response_info().cert_request_info));
+ return ERR_IO_PENDING;
+
+ case ERR_HTTPS_PROXY_TUNNEL_RESPONSE:
+ {
+ DCHECK(connection_.get());
+ DCHECK(connection_->socket());
+ DCHECK(establishing_tunnel_);
+
+ ProxyClientSocket* proxy_socket =
+ static_cast<ProxyClientSocket*>(connection_->socket());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnHttpsProxyTunnelResponseCallback,
+ ptr_factory_.GetWeakPtr(),
+ *proxy_socket->GetConnectResponseInfo(),
+ proxy_socket->CreateConnectResponseStream()));
+ return ERR_IO_PENDING;
+ }
+
+ case OK:
+ next_state_ = STATE_DONE;
+ if (new_spdy_session_.get()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&Job::OnNewSpdySessionReadyCallback,
+ ptr_factory_.GetWeakPtr()));
+ } else if (stream_factory_->for_websockets_) {
+ DCHECK(websocket_stream_);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnWebSocketStreamReadyCallback,
+ ptr_factory_.GetWeakPtr()));
+ } else {
+ DCHECK(stream_.get());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnStreamReadyCallback,
+ ptr_factory_.GetWeakPtr()));
+ }
+ return ERR_IO_PENDING;
+
+ default:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Job::OnStreamFailedCallback,
+ ptr_factory_.GetWeakPtr(),
+ result));
+ return ERR_IO_PENDING;
+ }
+ return result;
+}
+
+int HttpStreamFactoryImpl::Job::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_START:
+ DCHECK_EQ(OK, rv);
+ rv = DoStart();
+ break;
+ case STATE_RESOLVE_PROXY:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveProxy();
+ break;
+ case STATE_RESOLVE_PROXY_COMPLETE:
+ rv = DoResolveProxyComplete(rv);
+ break;
+ case STATE_WAIT_FOR_JOB:
+ DCHECK_EQ(OK, rv);
+ rv = DoWaitForJob();
+ break;
+ case STATE_WAIT_FOR_JOB_COMPLETE:
+ rv = DoWaitForJobComplete(rv);
+ break;
+ case STATE_INIT_CONNECTION:
+ DCHECK_EQ(OK, rv);
+ rv = DoInitConnection();
+ break;
+ case STATE_INIT_CONNECTION_COMPLETE:
+ rv = DoInitConnectionComplete(rv);
+ break;
+ case STATE_WAITING_USER_ACTION:
+ rv = DoWaitingUserAction(rv);
+ break;
+ case STATE_RESTART_TUNNEL_AUTH:
+ DCHECK_EQ(OK, rv);
+ rv = DoRestartTunnelAuth();
+ break;
+ case STATE_RESTART_TUNNEL_AUTH_COMPLETE:
+ rv = DoRestartTunnelAuthComplete(rv);
+ break;
+ case STATE_CREATE_STREAM:
+ DCHECK_EQ(OK, rv);
+ rv = DoCreateStream();
+ break;
+ case STATE_CREATE_STREAM_COMPLETE:
+ rv = DoCreateStreamComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int HttpStreamFactoryImpl::Job::StartInternal() {
+ CHECK_EQ(STATE_NONE, next_state_);
+ next_state_ = STATE_START;
+ int rv = RunLoop(OK);
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ return rv;
+}
+
+int HttpStreamFactoryImpl::Job::DoStart() {
+ int port = request_info_.url.EffectiveIntPort();
+ origin_ = HostPortPair(request_info_.url.HostNoBrackets(), port);
+ origin_url_ = stream_factory_->ApplyHostMappingRules(
+ request_info_.url, &origin_);
+ http_pipelining_key_.reset(new HttpPipelinedHost::Key(origin_));
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_JOB,
+ base::Bind(&NetLogHttpStreamJobCallback,
+ &request_info_.url, &origin_url_,
+ priority_));
+
+ // Don't connect to restricted ports.
+ bool is_port_allowed = IsPortAllowedByDefault(port);
+ if (request_info_.url.SchemeIs("ftp")) {
+ // Never share connection with other jobs for FTP requests.
+ DCHECK(!waiting_job_);
+
+ is_port_allowed = IsPortAllowedByFtp(port);
+ }
+ if (!is_port_allowed && !IsPortAllowedByOverride(port)) {
+ if (waiting_job_) {
+ waiting_job_->Resume(this);
+ waiting_job_ = NULL;
+ }
+ return ERR_UNSAFE_PORT;
+ }
+
+ next_state_ = STATE_RESOLVE_PROXY;
+ return OK;
+}
+
+int HttpStreamFactoryImpl::Job::DoResolveProxy() {
+ DCHECK(!pac_request_);
+
+ next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
+
+ if (request_info_.load_flags & LOAD_BYPASS_PROXY) {
+ proxy_info_.UseDirect();
+ return OK;
+ }
+
+ return session_->proxy_service()->ResolveProxy(
+ request_info_.url, &proxy_info_, io_callback_, &pac_request_, net_log_);
+}
+
+int HttpStreamFactoryImpl::Job::DoResolveProxyComplete(int result) {
+ pac_request_ = NULL;
+
+ if (result == OK) {
+ // Remove unsupported proxies from the list.
+ proxy_info_.RemoveProxiesWithoutScheme(
+ ProxyServer::SCHEME_DIRECT |
+ ProxyServer::SCHEME_HTTP | ProxyServer::SCHEME_HTTPS |
+ ProxyServer::SCHEME_SOCKS4 | ProxyServer::SCHEME_SOCKS5);
+
+ if (proxy_info_.is_empty()) {
+ // No proxies/direct to choose from. This happens when we don't support
+ // any of the proxies in the returned list.
+ result = ERR_NO_SUPPORTED_PROXIES;
+ }
+ }
+
+ if (result != OK) {
+ if (waiting_job_) {
+ waiting_job_->Resume(this);
+ waiting_job_ = NULL;
+ }
+ return result;
+ }
+
+ if (blocking_job_)
+ next_state_ = STATE_WAIT_FOR_JOB;
+ else
+ next_state_ = STATE_INIT_CONNECTION;
+ return OK;
+}
+
+bool HttpStreamFactoryImpl::Job::ShouldForceSpdySSL() const {
+ bool rv = force_spdy_always_ && force_spdy_over_ssl_;
+ return rv && !HttpStreamFactory::HasSpdyExclusion(origin_);
+}
+
+bool HttpStreamFactoryImpl::Job::ShouldForceSpdyWithoutSSL() const {
+ bool rv = force_spdy_always_ && !force_spdy_over_ssl_;
+ return rv && !HttpStreamFactory::HasSpdyExclusion(origin_);
+}
+
+bool HttpStreamFactoryImpl::Job::ShouldForceQuic() const {
+ return session_->params().enable_quic &&
+ session_->params().origin_to_force_quic_on.Equals(origin_) &&
+ proxy_info_.is_direct();
+}
+
+int HttpStreamFactoryImpl::Job::DoWaitForJob() {
+ DCHECK(blocking_job_);
+ next_state_ = STATE_WAIT_FOR_JOB_COMPLETE;
+ return ERR_IO_PENDING;
+}
+
+int HttpStreamFactoryImpl::Job::DoWaitForJobComplete(int result) {
+ DCHECK(!blocking_job_);
+ DCHECK_EQ(OK, result);
+ next_state_ = STATE_INIT_CONNECTION;
+ return OK;
+}
+
+int HttpStreamFactoryImpl::Job::DoInitConnection() {
+ DCHECK(!blocking_job_);
+ DCHECK(!connection_->is_initialized());
+ DCHECK(proxy_info_.proxy_server().is_valid());
+ next_state_ = STATE_INIT_CONNECTION_COMPLETE;
+
+ using_ssl_ = request_info_.url.SchemeIs("https") ||
+ request_info_.url.SchemeIs("wss") || ShouldForceSpdySSL();
+ using_spdy_ = false;
+
+ if (ShouldForceQuic())
+ using_quic_ = true;
+
+ if (using_quic_) {
+ DCHECK(session_->params().enable_quic);
+ if (!proxy_info_.is_direct()) {
+ NOTREACHED();
+ // TODO(rch): support QUIC proxies.
+ return ERR_NOT_IMPLEMENTED;
+ }
+ next_state_ = STATE_INIT_CONNECTION_COMPLETE;
+ const ProxyServer& proxy_server = proxy_info_.proxy_server();
+ int rv = quic_request_.Request(HostPortProxyPair(origin_, proxy_server),
+ using_ssl_, session_->cert_verifier(),
+ net_log_, io_callback_);
+ if (rv != OK) {
+ // OK, there's no available QUIC session. Let |waiting_job_| resume
+ // if it's paused.
+ if (waiting_job_) {
+ waiting_job_->Resume(this);
+ waiting_job_ = NULL;
+ }
+ }
+ return rv;
+ }
+
+ // Check first if we have a spdy session for this group. If so, then go
+ // straight to using that.
+ SpdySessionKey spdy_session_key = GetSpdySessionKey();
+ base::WeakPtr<SpdySession> spdy_session =
+ session_->spdy_session_pool()->FindAvailableSession(
+ spdy_session_key, net_log_);
+ if (spdy_session && CanUseExistingSpdySession()) {
+ // If we're preconnecting, but we already have a SpdySession, we don't
+ // actually need to preconnect any sockets, so we're done.
+ if (IsPreconnecting())
+ return OK;
+ using_spdy_ = true;
+ next_state_ = STATE_CREATE_STREAM;
+ existing_spdy_session_ = spdy_session;
+ return OK;
+ } else if (request_ && (using_ssl_ || ShouldForceSpdyWithoutSSL())) {
+ // Update the spdy session key for the request that launched this job.
+ request_->SetSpdySessionKey(spdy_session_key);
+ } else if (IsRequestEligibleForPipelining()) {
+ // TODO(simonjam): With pipelining, we might be better off using fewer
+ // connections and thus should make fewer preconnections. Explore
+ // preconnecting fewer than the requested num_connections.
+ //
+ // Separate note: A forced pipeline is always available if one exists for
+ // this key. This is different than normal pipelines, which may be
+ // unavailable or unusable. So, there is no need to worry about a race
+ // between when a pipeline becomes available and when this job blocks.
+ existing_available_pipeline_ = stream_factory_->http_pipelined_host_pool_.
+ IsExistingPipelineAvailableForKey(*http_pipelining_key_.get());
+ if (existing_available_pipeline_) {
+ return OK;
+ } else {
+ bool was_new_key = request_->SetHttpPipeliningKey(
+ *http_pipelining_key_.get());
+ if (!was_new_key && session_->force_http_pipelining()) {
+ return ERR_IO_PENDING;
+ }
+ }
+ }
+
+ // OK, there's no available SPDY session. Let |waiting_job_| resume if it's
+ // paused.
+
+ if (waiting_job_) {
+ waiting_job_->Resume(this);
+ waiting_job_ = NULL;
+ }
+
+ if (proxy_info_.is_http() || proxy_info_.is_https())
+ establishing_tunnel_ = using_ssl_;
+
+ bool want_spdy_over_npn = original_url_ != NULL;
+
+ if (proxy_info_.is_https()) {
+ InitSSLConfig(proxy_info_.proxy_server().host_port_pair(),
+ &proxy_ssl_config_,
+ true /* is a proxy server */);
+ // Disable revocation checking for HTTPS proxies since the revocation
+ // requests are probably going to need to go through the proxy too.
+ proxy_ssl_config_.rev_checking_enabled = false;
+ }
+ if (using_ssl_) {
+ InitSSLConfig(origin_, &server_ssl_config_,
+ false /* not a proxy server */);
+ }
+
+ if (IsPreconnecting()) {
+ DCHECK(!stream_factory_->for_websockets_);
+ return PreconnectSocketsForHttpRequest(
+ origin_url_,
+ request_info_.extra_headers,
+ request_info_.load_flags,
+ priority_,
+ session_,
+ proxy_info_,
+ ShouldForceSpdySSL(),
+ want_spdy_over_npn,
+ server_ssl_config_,
+ proxy_ssl_config_,
+ request_info_.privacy_mode,
+ net_log_,
+ num_streams_);
+ } else {
+ // If we can't use a SPDY session, don't both checking for one after
+ // the hostname is resolved.
+ OnHostResolutionCallback resolution_callback = CanUseExistingSpdySession() ?
+ base::Bind(&Job::OnHostResolution, session_->spdy_session_pool(),
+ GetSpdySessionKey()) :
+ OnHostResolutionCallback();
+ if (stream_factory_->for_websockets_) {
+ return InitSocketHandleForWebSocketRequest(
+ origin_url_, request_info_.extra_headers, request_info_.load_flags,
+ priority_, session_, proxy_info_, ShouldForceSpdySSL(),
+ want_spdy_over_npn, server_ssl_config_, proxy_ssl_config_,
+ request_info_.privacy_mode, net_log_,
+ connection_.get(), resolution_callback, io_callback_);
+ }
+ return InitSocketHandleForHttpRequest(
+ origin_url_, request_info_.extra_headers, request_info_.load_flags,
+ priority_, session_, proxy_info_, ShouldForceSpdySSL(),
+ want_spdy_over_npn, server_ssl_config_, proxy_ssl_config_,
+ request_info_.privacy_mode, net_log_,
+ connection_.get(), resolution_callback, io_callback_);
+ }
+}
+
+int HttpStreamFactoryImpl::Job::DoInitConnectionComplete(int result) {
+ if (IsPreconnecting()) {
+ if (using_quic_)
+ return result;
+ DCHECK_EQ(OK, result);
+ return OK;
+ }
+
+ if (result == ERR_SPDY_SESSION_ALREADY_EXISTS) {
+ // We found a SPDY connection after resolving the host. This is
+ // probably an IP pooled connection.
+ SpdySessionKey spdy_session_key = GetSpdySessionKey();
+ existing_spdy_session_ =
+ session_->spdy_session_pool()->FindAvailableSession(
+ spdy_session_key, net_log_);
+ if (existing_spdy_session_) {
+ using_spdy_ = true;
+ next_state_ = STATE_CREATE_STREAM;
+ } else {
+ // It is possible that the spdy session no longer exists.
+ ReturnToStateInitConnection(true /* close connection */);
+ }
+ return OK;
+ }
+
+ // TODO(willchan): Make this a bit more exact. Maybe there are recoverable
+ // errors, such as ignoring certificate errors for Alternate-Protocol.
+ if (result < 0 && waiting_job_) {
+ waiting_job_->Resume(this);
+ waiting_job_ = NULL;
+ }
+
+ if (result < 0 && session_->force_http_pipelining()) {
+ stream_factory_->AbortPipelinedRequestsWithKey(
+ this, *http_pipelining_key_.get(), result, server_ssl_config_);
+ }
+
+ // |result| may be the result of any of the stacked pools. The following
+ // logic is used when determining how to interpret an error.
+ // If |result| < 0:
+ // and connection_->socket() != NULL, then the SSL handshake ran and it
+ // is a potentially recoverable error.
+ // and connection_->socket == NULL and connection_->is_ssl_error() is true,
+ // then the SSL handshake ran with an unrecoverable error.
+ // otherwise, the error came from one of the other pools.
+ bool ssl_started = using_ssl_ && (result == OK || connection_->socket() ||
+ connection_->is_ssl_error());
+
+ if (ssl_started && (result == OK || IsCertificateError(result))) {
+ if (using_quic_ && result == OK) {
+ was_npn_negotiated_ = true;
+ NextProto protocol_negotiated =
+ SSLClientSocket::NextProtoFromString("quic/1+spdy/3");
+ protocol_negotiated_ = protocol_negotiated;
+ } else {
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ if (ssl_socket->WasNpnNegotiated()) {
+ was_npn_negotiated_ = true;
+ std::string proto;
+ std::string server_protos;
+ SSLClientSocket::NextProtoStatus status =
+ ssl_socket->GetNextProto(&proto, &server_protos);
+ NextProto protocol_negotiated =
+ SSLClientSocket::NextProtoFromString(proto);
+ protocol_negotiated_ = protocol_negotiated;
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_STREAM_REQUEST_PROTO,
+ base::Bind(&NetLogHttpStreamProtoCallback,
+ status, &proto, &server_protos));
+ if (ssl_socket->was_spdy_negotiated())
+ SwitchToSpdyMode();
+ }
+ if (ShouldForceSpdySSL())
+ SwitchToSpdyMode();
+ }
+ } else if (proxy_info_.is_https() && connection_->socket() &&
+ result == OK) {
+ ProxyClientSocket* proxy_socket =
+ static_cast<ProxyClientSocket*>(connection_->socket());
+ if (proxy_socket->IsUsingSpdy()) {
+ was_npn_negotiated_ = true;
+ protocol_negotiated_ = proxy_socket->GetProtocolNegotiated();
+ SwitchToSpdyMode();
+ }
+ }
+
+ // We may be using spdy without SSL
+ if (ShouldForceSpdyWithoutSSL())
+ SwitchToSpdyMode();
+
+ if (result == ERR_PROXY_AUTH_REQUESTED ||
+ result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
+ DCHECK(!ssl_started);
+ // Other state (i.e. |using_ssl_|) suggests that |connection_| will have an
+ // SSL socket, but there was an error before that could happen. This
+ // puts the in progress HttpProxy socket into |connection_| in order to
+ // complete the auth (or read the response body). The tunnel restart code
+ // is careful to remove it before returning control to the rest of this
+ // class.
+ connection_.reset(connection_->release_pending_http_proxy_connection());
+ return result;
+ }
+
+ if (!ssl_started && result < 0 && original_url_.get()) {
+ // Mark the alternate protocol as broken and fallback.
+ session_->http_server_properties()->SetBrokenAlternateProtocol(
+ HostPortPair::FromURL(*original_url_));
+ return result;
+ }
+
+ if (using_quic_) {
+ if (result < 0)
+ return result;
+ stream_ = quic_request_.ReleaseStream();
+ next_state_ = STATE_NONE;
+ return OK;
+ }
+
+ if (result < 0 && !ssl_started)
+ return ReconsiderProxyAfterError(result);
+ establishing_tunnel_ = false;
+
+ if (connection_->socket()) {
+ LogHttpConnectedMetrics(*connection_);
+
+ // We officially have a new connection. Record the type.
+ if (!connection_->is_reused()) {
+ ConnectionType type = using_spdy_ ? CONNECTION_SPDY : CONNECTION_HTTP;
+ UpdateConnectionTypeHistograms(type);
+ }
+ }
+
+ // Handle SSL errors below.
+ if (using_ssl_) {
+ DCHECK(ssl_started);
+ if (IsCertificateError(result)) {
+ if (using_spdy_ && original_url_.get() &&
+ original_url_->SchemeIs("http")) {
+ // We ignore certificate errors for http over spdy.
+ spdy_certificate_error_ = result;
+ result = OK;
+ } else {
+ result = HandleCertificateError(result);
+ if (result == OK && !connection_->socket()->IsConnectedAndIdle()) {
+ ReturnToStateInitConnection(true /* close connection */);
+ return result;
+ }
+ }
+ }
+ if (result < 0)
+ return result;
+ }
+
+ next_state_ = STATE_CREATE_STREAM;
+ return OK;
+}
+
+int HttpStreamFactoryImpl::Job::DoWaitingUserAction(int result) {
+ // This state indicates that the stream request is in a partially
+ // completed state, and we've called back to the delegate for more
+ // information.
+
+ // We're always waiting here for the delegate to call us back.
+ return ERR_IO_PENDING;
+}
+
+int HttpStreamFactoryImpl::Job::DoCreateStream() {
+ DCHECK(connection_->socket() || existing_spdy_session_.get() ||
+ existing_available_pipeline_ || using_quic_);
+
+ next_state_ = STATE_CREATE_STREAM_COMPLETE;
+
+ // We only set the socket motivation if we're the first to use
+ // this socket. Is there a race for two SPDY requests? We really
+ // need to plumb this through to the connect level.
+ if (connection_->socket() && !connection_->is_reused())
+ SetSocketMotivation();
+
+ if (!using_spdy_) {
+ // We may get ftp scheme when fetching ftp resources through proxy.
+ bool using_proxy = (proxy_info_.is_http() || proxy_info_.is_https()) &&
+ (request_info_.url.SchemeIs("http") ||
+ request_info_.url.SchemeIs("ftp"));
+ if (stream_factory_->http_pipelined_host_pool_.
+ IsExistingPipelineAvailableForKey(*http_pipelining_key_.get())) {
+ DCHECK(!stream_factory_->for_websockets_);
+ stream_.reset(stream_factory_->http_pipelined_host_pool_.
+ CreateStreamOnExistingPipeline(
+ *http_pipelining_key_.get()));
+ CHECK(stream_.get());
+ } else if (stream_factory_->for_websockets_) {
+ DCHECK(request_);
+ DCHECK(request_->websocket_stream_factory());
+ websocket_stream_.reset(
+ request_->websocket_stream_factory()->CreateBasicStream(
+ connection_.release(), using_proxy));
+ } else if (!using_proxy && IsRequestEligibleForPipelining()) {
+ // TODO(simonjam): Support proxies.
+ stream_.reset(
+ stream_factory_->http_pipelined_host_pool_.CreateStreamOnNewPipeline(
+ *http_pipelining_key_.get(),
+ connection_.release(),
+ server_ssl_config_,
+ proxy_info_,
+ net_log_,
+ was_npn_negotiated_,
+ protocol_negotiated_));
+ CHECK(stream_.get());
+ } else {
+ stream_.reset(new HttpBasicStream(connection_.release(), NULL,
+ using_proxy));
+ }
+ return OK;
+ }
+
+ CHECK(!stream_.get());
+
+ bool direct = true;
+ const ProxyServer& proxy_server = proxy_info_.proxy_server();
+ PrivacyMode privacy_mode = request_info_.privacy_mode;
+ SpdySessionKey spdy_session_key(origin_, proxy_server, privacy_mode);
+ if (IsHttpsProxyAndHttpUrl()) {
+ // If we don't have a direct SPDY session, and we're using an HTTPS
+ // proxy, then we might have a SPDY session to the proxy.
+ // We never use privacy mode for connection to proxy server.
+ spdy_session_key = SpdySessionKey(proxy_server.host_port_pair(),
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ direct = false;
+ }
+
+ base::WeakPtr<SpdySession> spdy_session;
+ if (existing_spdy_session_.get()) {
+ // We picked up an existing session, so we don't need our socket.
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+ std::swap(spdy_session, existing_spdy_session_);
+ } else {
+ SpdySessionPool* spdy_pool = session_->spdy_session_pool();
+ spdy_session = spdy_pool->FindAvailableSession(spdy_session_key, net_log_);
+ if (!spdy_session) {
+ int error =
+ spdy_pool->CreateAvailableSessionFromSocket(spdy_session_key,
+ connection_.Pass(),
+ net_log_,
+ spdy_certificate_error_,
+ &new_spdy_session_,
+ using_ssl_);
+ if (error != OK)
+ return error;
+ const HostPortPair& host_port_pair = spdy_session_key.host_port_pair();
+ base::WeakPtr<HttpServerProperties> http_server_properties =
+ session_->http_server_properties();
+ if (http_server_properties)
+ http_server_properties->SetSupportsSpdy(host_port_pair, true);
+ spdy_session_direct_ = direct;
+ return OK;
+ }
+ }
+
+ if (!spdy_session)
+ return ERR_CONNECTION_CLOSED;
+
+ // TODO(willchan): Delete this code, because eventually, the
+ // HttpStreamFactoryImpl will be creating all the SpdyHttpStreams, since it
+ // will know when SpdySessions become available.
+
+ if (stream_factory_->for_websockets_) {
+ DCHECK(request_);
+ DCHECK(request_->websocket_stream_factory());
+ bool use_relative_url = direct || request_info_.url.SchemeIs("wss");
+ websocket_stream_.reset(
+ request_->websocket_stream_factory()->CreateSpdyStream(
+ spdy_session, use_relative_url));
+ } else {
+ bool use_relative_url = direct || request_info_.url.SchemeIs("https");
+ stream_.reset(new SpdyHttpStream(spdy_session, use_relative_url));
+ }
+ return OK;
+}
+
+int HttpStreamFactoryImpl::Job::DoCreateStreamComplete(int result) {
+ if (result < 0)
+ return result;
+
+ session_->proxy_service()->ReportSuccess(proxy_info_);
+ next_state_ = STATE_NONE;
+ return OK;
+}
+
+int HttpStreamFactoryImpl::Job::DoRestartTunnelAuth() {
+ next_state_ = STATE_RESTART_TUNNEL_AUTH_COMPLETE;
+ ProxyClientSocket* proxy_socket =
+ static_cast<ProxyClientSocket*>(connection_->socket());
+ return proxy_socket->RestartWithAuth(io_callback_);
+}
+
+int HttpStreamFactoryImpl::Job::DoRestartTunnelAuthComplete(int result) {
+ if (result == ERR_PROXY_AUTH_REQUESTED)
+ return result;
+
+ if (result == OK) {
+ // Now that we've got the HttpProxyClientSocket connected. We have
+ // to release it as an idle socket into the pool and start the connection
+ // process from the beginning. Trying to pass it in with the
+ // SSLSocketParams might cause a deadlock since params are dispatched
+ // interchangeably. This request won't necessarily get this http proxy
+ // socket, but there will be forward progress.
+ establishing_tunnel_ = false;
+ ReturnToStateInitConnection(false /* do not close connection */);
+ return OK;
+ }
+
+ return ReconsiderProxyAfterError(result);
+}
+
+void HttpStreamFactoryImpl::Job::ReturnToStateInitConnection(
+ bool close_connection) {
+ if (close_connection && connection_->socket())
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+
+ if (request_) {
+ request_->RemoveRequestFromSpdySessionRequestMap();
+ request_->RemoveRequestFromHttpPipeliningRequestMap();
+ }
+
+ next_state_ = STATE_INIT_CONNECTION;
+}
+
+void HttpStreamFactoryImpl::Job::SetSocketMotivation() {
+ if (request_info_.motivation == HttpRequestInfo::PRECONNECT_MOTIVATED)
+ connection_->socket()->SetSubresourceSpeculation();
+ else if (request_info_.motivation == HttpRequestInfo::OMNIBOX_MOTIVATED)
+ connection_->socket()->SetOmniboxSpeculation();
+ // TODO(mbelshe): Add other motivations (like EARLY_LOAD_MOTIVATED).
+}
+
+bool HttpStreamFactoryImpl::Job::IsHttpsProxyAndHttpUrl() const {
+ if (!proxy_info_.is_https())
+ return false;
+ if (original_url_.get()) {
+ // We currently only support Alternate-Protocol where the original scheme
+ // is http.
+ DCHECK(original_url_->SchemeIs("http"));
+ return original_url_->SchemeIs("http");
+ }
+ return request_info_.url.SchemeIs("http");
+}
+
+// Sets several fields of ssl_config for the given origin_server based on the
+// proxy info and other factors.
+void HttpStreamFactoryImpl::Job::InitSSLConfig(
+ const HostPortPair& origin_server,
+ SSLConfig* ssl_config,
+ bool is_proxy) const {
+ if (proxy_info_.is_https() && ssl_config->send_client_cert) {
+ // When connecting through an HTTPS proxy, disable TLS False Start so
+ // that client authentication errors can be distinguished between those
+ // originating from the proxy server (ERR_PROXY_CONNECTION_FAILED) and
+ // those originating from the endpoint (ERR_SSL_PROTOCOL_ERROR /
+ // ERR_BAD_SSL_CLIENT_AUTH_CERT).
+ // TODO(rch): This assumes that the HTTPS proxy will only request a
+ // client certificate during the initial handshake.
+ // http://crbug.com/59292
+ ssl_config->false_start_enabled = false;
+ }
+
+ enum {
+ FALLBACK_NONE = 0, // SSL version fallback did not occur.
+ FALLBACK_SSL3 = 1, // Fell back to SSL 3.0.
+ FALLBACK_TLS1 = 2, // Fell back to TLS 1.0.
+ FALLBACK_TLS1_1 = 3, // Fell back to TLS 1.1.
+ FALLBACK_MAX
+ };
+
+ int fallback = FALLBACK_NONE;
+ if (ssl_config->version_fallback) {
+ switch (ssl_config->version_max) {
+ case SSL_PROTOCOL_VERSION_SSL3:
+ fallback = FALLBACK_SSL3;
+ break;
+ case SSL_PROTOCOL_VERSION_TLS1:
+ fallback = FALLBACK_TLS1;
+ break;
+ case SSL_PROTOCOL_VERSION_TLS1_1:
+ fallback = FALLBACK_TLS1_1;
+ break;
+ }
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.ConnectionUsedSSLVersionFallback",
+ fallback, FALLBACK_MAX);
+
+ // We also wish to measure the amount of fallback connections for a host that
+ // we know implements TLS up to 1.2. Ideally there would be no fallback here
+ // but high numbers of SSLv3 would suggest that SSLv3 fallback is being
+ // caused by network middleware rather than buggy HTTPS servers.
+ const std::string& host = origin_server.host();
+ if (!is_proxy &&
+ host.size() >= 10 &&
+ host.compare(host.size() - 10, 10, "google.com") == 0 &&
+ (host.size() == 10 || host[host.size()-11] == '.')) {
+ UMA_HISTOGRAM_ENUMERATION("Net.GoogleConnectionUsedSSLVersionFallback",
+ fallback, FALLBACK_MAX);
+ }
+
+ if (request_info_.load_flags & LOAD_VERIFY_EV_CERT)
+ ssl_config->verify_ev_cert = true;
+
+ // Disable Channel ID if privacy mode is enabled.
+ if (request_info_.privacy_mode == kPrivacyModeEnabled)
+ ssl_config->channel_id_enabled = false;
+}
+
+
+int HttpStreamFactoryImpl::Job::ReconsiderProxyAfterError(int error) {
+ DCHECK(!pac_request_);
+
+ // A failure to resolve the hostname or any error related to establishing a
+ // TCP connection could be grounds for trying a new proxy configuration.
+ //
+ // Why do this when a hostname cannot be resolved? Some URLs only make sense
+ // to proxy servers. The hostname in those URLs might fail to resolve if we
+ // are still using a non-proxy config. We need to check if a proxy config
+ // now exists that corresponds to a proxy server that could load the URL.
+ //
+ switch (error) {
+ case ERR_PROXY_CONNECTION_FAILED:
+ case ERR_NAME_NOT_RESOLVED:
+ case ERR_INTERNET_DISCONNECTED:
+ case ERR_ADDRESS_UNREACHABLE:
+ case ERR_CONNECTION_CLOSED:
+ case ERR_CONNECTION_TIMED_OUT:
+ case ERR_CONNECTION_RESET:
+ case ERR_CONNECTION_REFUSED:
+ case ERR_CONNECTION_ABORTED:
+ case ERR_TIMED_OUT:
+ case ERR_TUNNEL_CONNECTION_FAILED:
+ case ERR_SOCKS_CONNECTION_FAILED:
+ // This can happen in the case of trying to talk to a proxy using SSL, and
+ // ending up talking to a captive portal that supports SSL instead.
+ case ERR_PROXY_CERTIFICATE_INVALID:
+ // This can happen when trying to talk SSL to a non-SSL server (Like a
+ // captive portal).
+ case ERR_SSL_PROTOCOL_ERROR:
+ break;
+ case ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
+ // Remap the SOCKS-specific "host unreachable" error to a more
+ // generic error code (this way consumers like the link doctor
+ // know to substitute their error page).
+ //
+ // Note that if the host resolving was done by the SOCKS5 proxy, we can't
+ // differentiate between a proxy-side "host not found" versus a proxy-side
+ // "address unreachable" error, and will report both of these failures as
+ // ERR_ADDRESS_UNREACHABLE.
+ return ERR_ADDRESS_UNREACHABLE;
+ default:
+ return error;
+ }
+
+ if (request_info_.load_flags & LOAD_BYPASS_PROXY) {
+ return error;
+ }
+
+ if (proxy_info_.is_https() && proxy_ssl_config_.send_client_cert) {
+ session_->ssl_client_auth_cache()->Remove(
+ proxy_info_.proxy_server().host_port_pair().ToString());
+ }
+
+ int rv = session_->proxy_service()->ReconsiderProxyAfterError(
+ request_info_.url, &proxy_info_, io_callback_, &pac_request_, net_log_);
+ if (rv == OK || rv == ERR_IO_PENDING) {
+ // If the error was during connection setup, there is no socket to
+ // disconnect.
+ if (connection_->socket())
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+ if (request_) {
+ request_->RemoveRequestFromSpdySessionRequestMap();
+ request_->RemoveRequestFromHttpPipeliningRequestMap();
+ }
+ next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
+ } else {
+ // If ReconsiderProxyAfterError() failed synchronously, it means
+ // there was nothing left to fall-back to, so fail the transaction
+ // with the last connection error we got.
+ // TODO(eroman): This is a confusing contract, make it more obvious.
+ rv = error;
+ }
+
+ return rv;
+}
+
+int HttpStreamFactoryImpl::Job::HandleCertificateError(int error) {
+ DCHECK(using_ssl_);
+ DCHECK(IsCertificateError(error));
+
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(&ssl_info_);
+
+ // Add the bad certificate to the set of allowed certificates in the
+ // SSL config object. This data structure will be consulted after calling
+ // RestartIgnoringLastError(). And the user will be asked interactively
+ // before RestartIgnoringLastError() is ever called.
+ SSLConfig::CertAndStatus bad_cert;
+
+ // |ssl_info_.cert| may be NULL if we failed to create
+ // X509Certificate for whatever reason, but normally it shouldn't
+ // happen, unless this code is used inside sandbox.
+ if (ssl_info_.cert.get() == NULL ||
+ !X509Certificate::GetDEREncoded(ssl_info_.cert->os_cert_handle(),
+ &bad_cert.der_cert)) {
+ return error;
+ }
+ bad_cert.cert_status = ssl_info_.cert_status;
+ server_ssl_config_.allowed_bad_certs.push_back(bad_cert);
+
+ int load_flags = request_info_.load_flags;
+ if (session_->params().ignore_certificate_errors)
+ load_flags |= LOAD_IGNORE_ALL_CERT_ERRORS;
+ if (ssl_socket->IgnoreCertError(error, load_flags))
+ return OK;
+ return error;
+}
+
+void HttpStreamFactoryImpl::Job::SwitchToSpdyMode() {
+ if (HttpStreamFactory::spdy_enabled())
+ using_spdy_ = true;
+}
+
+// static
+void HttpStreamFactoryImpl::Job::LogHttpConnectedMetrics(
+ const ClientSocketHandle& handle) {
+ UMA_HISTOGRAM_ENUMERATION("Net.HttpSocketType", handle.reuse_type(),
+ ClientSocketHandle::NUM_TYPES);
+
+ switch (handle.reuse_type()) {
+ case ClientSocketHandle::UNUSED:
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.HttpConnectionLatency",
+ handle.setup_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ break;
+ case ClientSocketHandle::UNUSED_IDLE:
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SocketIdleTimeBeforeNextUse_UnusedSocket",
+ handle.idle_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100);
+ break;
+ case ClientSocketHandle::REUSED_IDLE:
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SocketIdleTimeBeforeNextUse_ReusedSocket",
+ handle.idle_time(),
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool HttpStreamFactoryImpl::Job::IsPreconnecting() const {
+ DCHECK_GE(num_streams_, 0);
+ return num_streams_ > 0;
+}
+
+bool HttpStreamFactoryImpl::Job::IsOrphaned() const {
+ return !IsPreconnecting() && !request_;
+}
+
+bool HttpStreamFactoryImpl::Job::IsRequestEligibleForPipelining() {
+ if (IsPreconnecting() || !request_) {
+ return false;
+ }
+ if (stream_factory_->for_websockets_) {
+ return false;
+ }
+ if (session_->force_http_pipelining()) {
+ return true;
+ }
+ if (!session_->params().http_pipelining_enabled) {
+ return false;
+ }
+ if (using_ssl_) {
+ return false;
+ }
+ if (request_info_.method != "GET" && request_info_.method != "HEAD") {
+ return false;
+ }
+ if (request_info_.load_flags &
+ (net::LOAD_MAIN_FRAME | net::LOAD_SUB_FRAME | net::LOAD_PREFETCH |
+ net::LOAD_IS_DOWNLOAD)) {
+ // Avoid pipelining resources that may be streamed for a long time.
+ return false;
+ }
+ return stream_factory_->http_pipelined_host_pool_.IsKeyEligibleForPipelining(
+ *http_pipelining_key_.get());
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_factory_impl_job.h b/chromium/net/http/http_stream_factory_impl_job.h
new file mode 100644
index 00000000000..01a794a1bc7
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_job.h
@@ -0,0 +1,334 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STREAM_FACTORY_IMPL_JOB_H_
+#define NET_HTTP_HTTP_STREAM_FACTORY_IMPL_JOB_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_pipelined_host.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_stream_factory_impl.h"
+#include "net/proxy/proxy_service.h"
+#include "net/quic/quic_stream_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_session_key.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class HttpAuthController;
+class HttpNetworkSession;
+class HttpStream;
+class SpdySessionPool;
+class QuicHttpStream;
+
+// An HttpStreamRequestImpl exists for each stream which is in progress of being
+// created for the StreamFactory.
+class HttpStreamFactoryImpl::Job {
+ public:
+ Job(HttpStreamFactoryImpl* stream_factory,
+ HttpNetworkSession* session,
+ const HttpRequestInfo& request_info,
+ RequestPriority priority,
+ const SSLConfig& server_ssl_config,
+ const SSLConfig& proxy_ssl_config,
+ NetLog* net_log);
+ ~Job();
+
+ // Start initiates the process of creating a new HttpStream. |request| will be
+ // notified upon completion if the Job has not been Orphan()'d.
+ void Start(Request* request);
+
+ // Preconnect will attempt to request |num_streams| sockets from the
+ // appropriate ClientSocketPool.
+ int Preconnect(int num_streams);
+
+ int RestartTunnelWithProxyAuth(const AuthCredentials& credentials);
+ LoadState GetLoadState() const;
+
+ // Marks this Job as the "alternate" job, from Alternate-Protocol. Tracks the
+ // original url so we can mark the Alternate-Protocol as broken if
+ // we fail to connect. |alternate| specifies the alternate protocol to use
+ // and alternate port to connect to.
+ void MarkAsAlternate(const GURL& original_url,
+ PortAlternateProtocolPair alternate);
+
+ // Tells |this| to wait for |job| to resume it.
+ void WaitFor(Job* job);
+
+ // Tells |this| that |job| has determined it still needs to continue
+ // connecting, so allow |this| to continue. If this is not called, then
+ // |request_| is expected to cancel |this| by deleting it.
+ void Resume(Job* job);
+
+ // Used to detach the Job from |request|.
+ void Orphan(const Request* request);
+
+ void SetPriority(RequestPriority priority);
+
+ RequestPriority priority() const { return priority_; }
+ bool was_npn_negotiated() const;
+ NextProto protocol_negotiated() const;
+ bool using_spdy() const;
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ const SSLConfig& server_ssl_config() const;
+ const SSLConfig& proxy_ssl_config() const;
+ const ProxyInfo& proxy_info() const;
+
+ // Indicates whether or not this job is performing a preconnect.
+ bool IsPreconnecting() const;
+
+ // Indicates whether or not this Job has been orphaned by a Request.
+ bool IsOrphaned() const;
+
+ private:
+ enum State {
+ STATE_START,
+ STATE_RESOLVE_PROXY,
+ STATE_RESOLVE_PROXY_COMPLETE,
+
+ // Note that when Alternate-Protocol says we can connect to an alternate
+ // port using a different protocol, we have the choice of communicating over
+ // the original protocol, or speaking the alternate protocol (currently,
+ // only npn-spdy) over an alternate port. For a cold page load, the http
+ // connection that delivers the http response that has the
+ // Alternate-Protocol header will already be warm. So, blocking the next
+ // http request on establishing a new npn-spdy connection would incur extra
+ // latency. Even if the http connection was not reused, establishing a new
+ // http connection is typically faster than npn-spdy, since npn-spdy
+ // requires a SSL handshake. Therefore, we start both the http and the
+ // npn-spdy jobs in parallel. In order not to unnecessarily waste sockets,
+ // we have the http job block on the npn-spdy job after proxy resolution.
+ // The npn-spdy job will Resume() the http job if, in
+ // STATE_INIT_CONNECTION_COMPLETE, it detects an error or does not find an
+ // existing SpdySession. In that case, the http and npn-spdy jobs will race.
+ STATE_WAIT_FOR_JOB,
+ STATE_WAIT_FOR_JOB_COMPLETE,
+
+ STATE_INIT_CONNECTION,
+ STATE_INIT_CONNECTION_COMPLETE,
+ STATE_WAITING_USER_ACTION,
+ STATE_RESTART_TUNNEL_AUTH,
+ STATE_RESTART_TUNNEL_AUTH_COMPLETE,
+ STATE_CREATE_STREAM,
+ STATE_CREATE_STREAM_COMPLETE,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE,
+ STATE_DONE,
+ STATE_NONE
+ };
+
+ void OnStreamReadyCallback();
+ void OnWebSocketStreamReadyCallback();
+ // This callback function is called when a new SPDY session is created.
+ void OnNewSpdySessionReadyCallback();
+ void OnStreamFailedCallback(int result);
+ void OnCertificateErrorCallback(int result, const SSLInfo& ssl_info);
+ void OnNeedsProxyAuthCallback(const HttpResponseInfo& response_info,
+ HttpAuthController* auth_controller);
+ void OnNeedsClientAuthCallback(SSLCertRequestInfo* cert_info);
+ void OnHttpsProxyTunnelResponseCallback(const HttpResponseInfo& response_info,
+ HttpStream* stream);
+ void OnPreconnectsComplete();
+
+ void OnIOComplete(int result);
+ int RunLoop(int result);
+ int DoLoop(int result);
+ int StartInternal();
+
+ // Each of these methods corresponds to a State value. Those with an input
+ // argument receive the result from the previous state. If a method returns
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
+ // next state method as the result arg.
+ int DoStart();
+ int DoResolveProxy();
+ int DoResolveProxyComplete(int result);
+ int DoWaitForJob();
+ int DoWaitForJobComplete(int result);
+ int DoInitConnection();
+ int DoInitConnectionComplete(int result);
+ int DoWaitingUserAction(int result);
+ int DoCreateStream();
+ int DoCreateStreamComplete(int result);
+ int DoRestartTunnelAuth();
+ int DoRestartTunnelAuthComplete(int result);
+
+ // Returns to STATE_INIT_CONNECTION and resets some state.
+ void ReturnToStateInitConnection(bool close_connection);
+
+ // Set the motivation for this request onto the underlying socket.
+ void SetSocketMotivation();
+
+ bool IsHttpsProxyAndHttpUrl() const;
+
+ // Sets several fields of ssl_config for the given origin_server based on the
+ // proxy info and other factors.
+ void InitSSLConfig(const HostPortPair& origin_server,
+ SSLConfig* ssl_config,
+ bool is_proxy) const;
+
+ // Retrieve SSLInfo from our SSL Socket.
+ // This must only be called when we are using an SSLSocket.
+ // After calling, the caller can use ssl_info_.
+ void GetSSLInfo();
+
+ SpdySessionKey GetSpdySessionKey() const;
+
+ // Returns true if the current request can use an existing spdy session.
+ bool CanUseExistingSpdySession() const;
+
+ // Called when we encounter a network error that could be resolved by trying
+ // a new proxy configuration. If there is another proxy configuration to try
+ // then this method sets next_state_ appropriately and returns either OK or
+ // ERR_IO_PENDING depending on whether or not the new proxy configuration is
+ // available synchronously or asynchronously. Otherwise, the given error
+ // code is simply returned.
+ int ReconsiderProxyAfterError(int error);
+
+ // Called to handle a certificate error. Stores the certificate in the
+ // allowed_bad_certs list, and checks if the error can be ignored. Returns
+ // OK if it can be ignored, or the error code otherwise.
+ int HandleCertificateError(int error);
+
+ // Called to handle a client certificate request.
+ int HandleCertificateRequest(int error);
+
+ // Moves this stream request into SPDY mode.
+ void SwitchToSpdyMode();
+
+ // Should we force SPDY to run over SSL for this stream request.
+ bool ShouldForceSpdySSL() const;
+
+ // Should we force SPDY to run without SSL for this stream request.
+ bool ShouldForceSpdyWithoutSSL() const;
+
+ // Should we force QUIC for this stream request.
+ bool ShouldForceQuic() const;
+
+ bool IsRequestEligibleForPipelining();
+
+ // Record histograms of latency until Connect() completes.
+ static void LogHttpConnectedMetrics(const ClientSocketHandle& handle);
+
+ // Invoked by the transport socket pool after host resolution is complete
+ // to allow the connection to be aborted, if a matching SPDY session can
+ // be found. Will return ERR_SPDY_SESSION_ALREADY_EXISTS if such a
+ // session is found, and OK otherwise.
+ static int OnHostResolution(SpdySessionPool* spdy_session_pool,
+ const SpdySessionKey& spdy_session_key,
+ const AddressList& addresses,
+ const BoundNetLog& net_log);
+
+ Request* request_;
+
+ const HttpRequestInfo request_info_;
+ RequestPriority priority_;
+ ProxyInfo proxy_info_;
+ SSLConfig server_ssl_config_;
+ SSLConfig proxy_ssl_config_;
+ const BoundNetLog net_log_;
+
+ CompletionCallback io_callback_;
+ scoped_ptr<ClientSocketHandle> connection_;
+ HttpNetworkSession* const session_;
+ HttpStreamFactoryImpl* const stream_factory_;
+ State next_state_;
+ ProxyService::PacRequest* pac_request_;
+ SSLInfo ssl_info_;
+
+ // The origin server we're trying to reach.
+ HostPortPair origin_;
+
+ // The origin url we're trying to reach. This url may be different from the
+ // original request when host mapping rules are set-up.
+ GURL origin_url_;
+
+ // If this is a Job for an "Alternate-Protocol", then this will be non-NULL
+ // and will specify the original URL.
+ scoped_ptr<GURL> original_url_;
+
+ // This is the Job we're dependent on. It will notify us if/when it's OK to
+ // proceed.
+ Job* blocking_job_;
+
+ // |waiting_job_| is a Job waiting to see if |this| can reuse a connection.
+ // If |this| is unable to do so, we'll notify |waiting_job_| that it's ok to
+ // proceed and then race the two Jobs.
+ Job* waiting_job_;
+
+ // True if handling a HTTPS request, or using SPDY with SSL
+ bool using_ssl_;
+
+ // True if this network transaction is using SPDY instead of HTTP.
+ bool using_spdy_;
+
+ // True if this network transaction is using QUIC instead of HTTP.
+ bool using_quic_;
+ QuicStreamRequest quic_request_;
+
+ // Force spdy for all connections.
+ bool force_spdy_always_;
+
+ // Force spdy only for SSL connections.
+ bool force_spdy_over_ssl_;
+
+ // Force quic for a specific port.
+ int force_quic_port_;
+
+ // The certificate error while using SPDY over SSL for insecure URLs.
+ int spdy_certificate_error_;
+
+ scoped_refptr<HttpAuthController>
+ auth_controllers_[HttpAuth::AUTH_NUM_TARGETS];
+
+ // True when the tunnel is in the process of being established - we can't
+ // read from the socket until the tunnel is done.
+ bool establishing_tunnel_;
+
+ scoped_ptr<HttpStream> stream_;
+ scoped_ptr<WebSocketStreamBase> websocket_stream_;
+
+ // True if we negotiated NPN.
+ bool was_npn_negotiated_;
+
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated_;
+
+ // 0 if we're not preconnecting. Otherwise, the number of streams to
+ // preconnect.
+ int num_streams_;
+
+ // Initialized when we create a new SpdySession.
+ base::WeakPtr<SpdySession> new_spdy_session_;
+
+ // Initialized when we have an existing SpdySession.
+ base::WeakPtr<SpdySession> existing_spdy_session_;
+
+ // Only used if |new_spdy_session_| is non-NULL.
+ bool spdy_session_direct_;
+
+ // Key used to identify the HttpPipelinedHost for |request_|.
+ scoped_ptr<HttpPipelinedHost::Key> http_pipelining_key_;
+
+ // True if an existing pipeline can handle this job's request.
+ bool existing_available_pipeline_;
+
+ base::WeakPtrFactory<Job> ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_FACTORY_IMPL_JOB_H_
diff --git a/chromium/net/http/http_stream_factory_impl_request.cc b/chromium/net/http/http_stream_factory_impl_request.cc
new file mode 100644
index 00000000000..57190ed72e7
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_request.cc
@@ -0,0 +1,389 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_factory_impl_request.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/http/http_stream_factory_impl_job.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+HttpStreamFactoryImpl::Request::Request(
+ const GURL& url,
+ HttpStreamFactoryImpl* factory,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* websocket_stream_factory,
+ const BoundNetLog& net_log)
+ : url_(url),
+ factory_(factory),
+ websocket_stream_factory_(websocket_stream_factory),
+ delegate_(delegate),
+ net_log_(net_log),
+ completed_(false),
+ was_npn_negotiated_(false),
+ protocol_negotiated_(kProtoUnknown),
+ using_spdy_(false) {
+ DCHECK(factory_);
+ DCHECK(delegate_);
+
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);
+}
+
+HttpStreamFactoryImpl::Request::~Request() {
+ if (bound_job_.get())
+ DCHECK(jobs_.empty());
+ else
+ DCHECK(!jobs_.empty());
+
+ net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);
+
+ for (std::set<Job*>::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
+ factory_->request_map_.erase(*it);
+
+ RemoveRequestFromSpdySessionRequestMap();
+ RemoveRequestFromHttpPipeliningRequestMap();
+
+ STLDeleteElements(&jobs_);
+}
+
+void HttpStreamFactoryImpl::Request::SetSpdySessionKey(
+ const SpdySessionKey& spdy_session_key) {
+ DCHECK(!spdy_session_key_.get());
+ spdy_session_key_.reset(new SpdySessionKey(spdy_session_key));
+ RequestSet& request_set =
+ factory_->spdy_session_request_map_[spdy_session_key];
+ DCHECK(!ContainsKey(request_set, this));
+ request_set.insert(this);
+}
+
+bool HttpStreamFactoryImpl::Request::SetHttpPipeliningKey(
+ const HttpPipelinedHost::Key& http_pipelining_key) {
+ CHECK(!http_pipelining_key_.get());
+ http_pipelining_key_.reset(new HttpPipelinedHost::Key(http_pipelining_key));
+ bool was_new_key = !ContainsKey(factory_->http_pipelining_request_map_,
+ http_pipelining_key);
+ RequestVector& request_vector =
+ factory_->http_pipelining_request_map_[http_pipelining_key];
+ request_vector.push_back(this);
+ return was_new_key;
+}
+
+void HttpStreamFactoryImpl::Request::AttachJob(Job* job) {
+ DCHECK(job);
+ jobs_.insert(job);
+ factory_->request_map_[job] = this;
+}
+
+void HttpStreamFactoryImpl::Request::Complete(
+ bool was_npn_negotiated,
+ NextProto protocol_negotiated,
+ bool using_spdy,
+ const BoundNetLog& job_net_log) {
+ DCHECK(!completed_);
+ completed_ = true;
+ was_npn_negotiated_ = was_npn_negotiated;
+ protocol_negotiated_ = protocol_negotiated;
+ using_spdy_ = using_spdy;
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB,
+ job_net_log.source().ToEventParametersCallback());
+ job_net_log.AddEvent(
+ NetLog::TYPE_HTTP_STREAM_JOB_BOUND_TO_REQUEST,
+ net_log_.source().ToEventParametersCallback());
+}
+
+void HttpStreamFactoryImpl::Request::OnStreamReady(
+ Job* job,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) {
+ DCHECK(!factory_->for_websockets_);
+ DCHECK(stream);
+ DCHECK(completed_);
+
+ OnJobSucceeded(job);
+ delegate_->OnStreamReady(used_ssl_config, used_proxy_info, stream);
+}
+
+void HttpStreamFactoryImpl::Request::OnWebSocketStreamReady(
+ Job* job,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) {
+ DCHECK(factory_->for_websockets_);
+ DCHECK(stream);
+ DCHECK(completed_);
+
+ OnJobSucceeded(job);
+ delegate_->OnWebSocketStreamReady(used_ssl_config, used_proxy_info, stream);
+}
+
+void HttpStreamFactoryImpl::Request::OnStreamFailed(
+ Job* job,
+ int status,
+ const SSLConfig& used_ssl_config) {
+ DCHECK_NE(OK, status);
+ // |job| should only be NULL if we're being canceled by a late bound
+ // HttpPipelinedConnection (one that was not created by a job in our |jobs_|
+ // set).
+ if (!job) {
+ DCHECK(!bound_job_.get());
+ DCHECK(!jobs_.empty());
+ // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
+ // we *WANT* to cancel the unnecessary Jobs from other requests if another
+ // Job completes first.
+ } else if (!bound_job_.get()) {
+ // Hey, we've got other jobs! Maybe one of them will succeed, let's just
+ // ignore this failure.
+ if (jobs_.size() > 1) {
+ jobs_.erase(job);
+ factory_->request_map_.erase(job);
+ delete job;
+ return;
+ } else {
+ bound_job_.reset(job);
+ jobs_.erase(job);
+ DCHECK(jobs_.empty());
+ factory_->request_map_.erase(job);
+ }
+ } else {
+ DCHECK(jobs_.empty());
+ }
+ delegate_->OnStreamFailed(status, used_ssl_config);
+}
+
+void HttpStreamFactoryImpl::Request::OnCertificateError(
+ Job* job,
+ int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) {
+ DCHECK_NE(OK, status);
+ if (!bound_job_.get())
+ OrphanJobsExcept(job);
+ else
+ DCHECK(jobs_.empty());
+ delegate_->OnCertificateError(status, used_ssl_config, ssl_info);
+}
+
+void HttpStreamFactoryImpl::Request::OnNeedsProxyAuth(
+ Job* job,
+ const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) {
+ if (!bound_job_.get())
+ OrphanJobsExcept(job);
+ else
+ DCHECK(jobs_.empty());
+ delegate_->OnNeedsProxyAuth(
+ proxy_response, used_ssl_config, used_proxy_info, auth_controller);
+}
+
+void HttpStreamFactoryImpl::Request::OnNeedsClientAuth(
+ Job* job,
+ const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) {
+ if (!bound_job_.get())
+ OrphanJobsExcept(job);
+ else
+ DCHECK(jobs_.empty());
+ delegate_->OnNeedsClientAuth(used_ssl_config, cert_info);
+}
+
+void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse(
+ Job *job,
+ const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) {
+ if (!bound_job_.get())
+ OrphanJobsExcept(job);
+ else
+ DCHECK(jobs_.empty());
+ delegate_->OnHttpsProxyTunnelResponse(
+ response_info, used_ssl_config, used_proxy_info, stream);
+}
+
+int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth(
+ const AuthCredentials& credentials) {
+ DCHECK(bound_job_.get());
+ return bound_job_->RestartTunnelWithProxyAuth(credentials);
+}
+
+void HttpStreamFactoryImpl::Request::SetPriority(RequestPriority priority) {
+ for (std::set<HttpStreamFactoryImpl::Job*>::const_iterator it = jobs_.begin();
+ it != jobs_.end(); ++it) {
+ (*it)->SetPriority(priority);
+ }
+ if (bound_job_)
+ bound_job_->SetPriority(priority);
+}
+
+LoadState HttpStreamFactoryImpl::Request::GetLoadState() const {
+ if (bound_job_.get())
+ return bound_job_->GetLoadState();
+ DCHECK(!jobs_.empty());
+
+ // Just pick the first one.
+ return (*jobs_.begin())->GetLoadState();
+}
+
+bool HttpStreamFactoryImpl::Request::was_npn_negotiated() const {
+ DCHECK(completed_);
+ return was_npn_negotiated_;
+}
+
+NextProto HttpStreamFactoryImpl::Request::protocol_negotiated()
+ const {
+ DCHECK(completed_);
+ return protocol_negotiated_;
+}
+
+bool HttpStreamFactoryImpl::Request::using_spdy() const {
+ DCHECK(completed_);
+ return using_spdy_;
+}
+
+void
+HttpStreamFactoryImpl::Request::RemoveRequestFromSpdySessionRequestMap() {
+ if (spdy_session_key_.get()) {
+ SpdySessionRequestMap& spdy_session_request_map =
+ factory_->spdy_session_request_map_;
+ DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key_));
+ RequestSet& request_set =
+ spdy_session_request_map[*spdy_session_key_];
+ DCHECK(ContainsKey(request_set, this));
+ request_set.erase(this);
+ if (request_set.empty())
+ spdy_session_request_map.erase(*spdy_session_key_);
+ spdy_session_key_.reset();
+ }
+}
+
+void
+HttpStreamFactoryImpl::Request::RemoveRequestFromHttpPipeliningRequestMap() {
+ if (http_pipelining_key_.get()) {
+ HttpPipeliningRequestMap& http_pipelining_request_map =
+ factory_->http_pipelining_request_map_;
+ DCHECK(ContainsKey(http_pipelining_request_map, *http_pipelining_key_));
+ RequestVector& request_vector =
+ http_pipelining_request_map[*http_pipelining_key_];
+ for (RequestVector::iterator it = request_vector.begin();
+ it != request_vector.end(); ++it) {
+ if (*it == this) {
+ request_vector.erase(it);
+ break;
+ }
+ }
+ if (request_vector.empty())
+ http_pipelining_request_map.erase(*http_pipelining_key_);
+ http_pipelining_key_.reset();
+ }
+}
+
+void HttpStreamFactoryImpl::Request::OnNewSpdySessionReady(
+ Job* job,
+ const base::WeakPtr<SpdySession>& spdy_session,
+ bool direct) {
+ DCHECK(job);
+ DCHECK(job->using_spdy());
+
+ // The first case is the usual case.
+ if (!bound_job_.get()) {
+ OrphanJobsExcept(job);
+ } else { // This is the case for HTTPS proxy tunneling.
+ DCHECK_EQ(bound_job_.get(), job);
+ DCHECK(jobs_.empty());
+ }
+
+ // Cache these values in case the job gets deleted.
+ const SSLConfig used_ssl_config = job->server_ssl_config();
+ const ProxyInfo used_proxy_info = job->proxy_info();
+ const bool was_npn_negotiated = job->was_npn_negotiated();
+ const NextProto protocol_negotiated =
+ job->protocol_negotiated();
+ const bool using_spdy = job->using_spdy();
+ const BoundNetLog net_log = job->net_log();
+
+ Complete(was_npn_negotiated, protocol_negotiated, using_spdy, net_log);
+
+ // Cache this so we can still use it if the request is deleted.
+ HttpStreamFactoryImpl* factory = factory_;
+ if (factory->for_websockets_) {
+ DCHECK(websocket_stream_factory_);
+ bool use_relative_url = direct || url().SchemeIs("wss");
+ delegate_->OnWebSocketStreamReady(
+ job->server_ssl_config(),
+ job->proxy_info(),
+ websocket_stream_factory_->CreateSpdyStream(spdy_session,
+ use_relative_url));
+ } else {
+ bool use_relative_url = direct || url().SchemeIs("https");
+ delegate_->OnStreamReady(
+ job->server_ssl_config(),
+ job->proxy_info(),
+ new SpdyHttpStream(spdy_session, use_relative_url));
+ }
+ // |this| may be deleted after this point.
+ factory->OnNewSpdySessionReady(spdy_session,
+ direct,
+ used_ssl_config,
+ used_proxy_info,
+ was_npn_negotiated,
+ protocol_negotiated,
+ using_spdy,
+ net_log);
+}
+
+void HttpStreamFactoryImpl::Request::OrphanJobsExcept(Job* job) {
+ DCHECK(job);
+ DCHECK(!bound_job_.get());
+ DCHECK(ContainsKey(jobs_, job));
+ bound_job_.reset(job);
+ jobs_.erase(job);
+ factory_->request_map_.erase(job);
+
+ OrphanJobs();
+}
+
+void HttpStreamFactoryImpl::Request::OrphanJobs() {
+ RemoveRequestFromSpdySessionRequestMap();
+ RemoveRequestFromHttpPipeliningRequestMap();
+
+ std::set<Job*> tmp;
+ tmp.swap(jobs_);
+
+ for (std::set<Job*>::iterator it = tmp.begin(); it != tmp.end(); ++it)
+ factory_->OrphanJob(*it, this);
+}
+
+void HttpStreamFactoryImpl::Request::OnJobSucceeded(Job* job) {
+ // |job| should only be NULL if we're being serviced by a late bound
+ // SpdySession or HttpPipelinedConnection (one that was not created by a job
+ // in our |jobs_| set).
+ if (!job) {
+ DCHECK(!bound_job_.get());
+ DCHECK(!jobs_.empty());
+ // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
+ // we *WANT* to cancel the unnecessary Jobs from other requests if another
+ // Job completes first.
+ // TODO(mbelshe): Revisit this when we implement ip connection pooling of
+ // SpdySessions. Do we want to orphan the jobs for a different hostname so
+ // they complete? Or do we want to prevent connecting a new SpdySession if
+ // we've already got one available for a different hostname where the ip
+ // address matches up?
+ } else if (!bound_job_.get()) {
+ // We may have other jobs in |jobs_|. For example, if we start multiple jobs
+ // for Alternate-Protocol.
+ OrphanJobsExcept(job);
+ } else {
+ DCHECK(jobs_.empty());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_factory_impl_request.h b/chromium/net/http/http_stream_factory_impl_request.h
new file mode 100644
index 00000000000..d6f9b02cbef
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_request.h
@@ -0,0 +1,148 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STREAM_FACTORY_IMPL_REQUEST_H_
+#define NET_HTTP_HTTP_STREAM_FACTORY_IMPL_REQUEST_H_
+
+#include <set>
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_log.h"
+#include "net/http/http_stream_factory_impl.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_session_key.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class SpdySession;
+
+class HttpStreamFactoryImpl::Request : public HttpStreamRequest {
+ public:
+ Request(const GURL& url,
+ HttpStreamFactoryImpl* factory,
+ HttpStreamRequest::Delegate* delegate,
+ WebSocketStreamBase::Factory* websocket_stream_factory,
+ const BoundNetLog& net_log);
+ virtual ~Request();
+
+ // The GURL from the HttpRequestInfo the started the Request.
+ const GURL& url() const { return url_; }
+
+ // Called when the Job determines the appropriate |spdy_session_key| for the
+ // Request. Note that this does not mean that SPDY is necessarily supported
+ // for this SpdySessionKey, since we may need to wait for NPN to complete
+ // before knowing if SPDY is available.
+ void SetSpdySessionKey(const SpdySessionKey& spdy_session_key);
+
+ // Called when the Job determines the appropriate |http_pipelining_key| for
+ // the Request. Registers this Request with the factory, so that if an
+ // existing pipeline becomes available, this Request can be late bound to it.
+ // Returns true if this is this key was new to the factory.
+ bool SetHttpPipeliningKey(const HttpPipelinedHost::Key& http_pipelining_key);
+
+ // Attaches |job| to this request. Does not mean that Request will use |job|,
+ // but Request will own |job|.
+ void AttachJob(HttpStreamFactoryImpl::Job* job);
+
+ // Marks completion of the request. Must be called before OnStreamReady().
+ // |job_net_log| is the BoundNetLog of the Job that fulfilled this request.
+ void Complete(bool was_npn_negotiated,
+ NextProto protocol_negotiated,
+ bool using_spdy,
+ const BoundNetLog& job_net_log);
+
+ // If this Request has a |spdy_session_key_|, remove this session from the
+ // SpdySessionRequestMap.
+ void RemoveRequestFromSpdySessionRequestMap();
+
+ // If this Request has a |http_pipelining_key_|, remove this session from the
+ // HttpPipeliningRequestMap.
+ void RemoveRequestFromHttpPipeliningRequestMap();
+
+ // Called by an attached Job if it sets up a SpdySession.
+ void OnNewSpdySessionReady(Job* job,
+ const base::WeakPtr<SpdySession>& spdy_session,
+ bool direct);
+
+ WebSocketStreamBase::Factory* websocket_stream_factory() {
+ return websocket_stream_factory_;
+ }
+
+ // HttpStreamRequest::Delegate methods which we implement. Note we don't
+ // actually subclass HttpStreamRequest::Delegate.
+
+ void OnStreamReady(Job* job,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream);
+ void OnWebSocketStreamReady(Job* job,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream);
+ void OnStreamFailed(Job* job, int status, const SSLConfig& used_ssl_config);
+ void OnCertificateError(Job* job,
+ int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info);
+ void OnNeedsProxyAuth(Job* job,
+ const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller);
+ void OnNeedsClientAuth(Job* job,
+ const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info);
+ void OnHttpsProxyTunnelResponse(
+ Job *job,
+ const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream);
+
+ // HttpStreamRequest methods.
+
+ virtual int RestartTunnelWithProxyAuth(
+ const AuthCredentials& credentials) OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual bool was_npn_negotiated() const OVERRIDE;
+ virtual NextProto protocol_negotiated() const OVERRIDE;
+ virtual bool using_spdy() const OVERRIDE;
+
+ private:
+ // Used to orphan all jobs in |jobs_| other than |job| which becomes "bound"
+ // to the request.
+ void OrphanJobsExcept(Job* job);
+
+ // Used to orphan all jobs in |jobs_|.
+ void OrphanJobs();
+
+ // Called when a Job succeeds.
+ void OnJobSucceeded(Job* job);
+
+ const GURL url_;
+ HttpStreamFactoryImpl* const factory_;
+ WebSocketStreamBase::Factory* const websocket_stream_factory_;
+ HttpStreamRequest::Delegate* const delegate_;
+ const BoundNetLog net_log_;
+
+ // At the point where Job is irrevocably tied to the Request, we set this.
+ scoped_ptr<Job> bound_job_;
+ std::set<HttpStreamFactoryImpl::Job*> jobs_;
+ scoped_ptr<const SpdySessionKey> spdy_session_key_;
+ scoped_ptr<const HttpPipelinedHost::Key> http_pipelining_key_;
+
+ bool completed_;
+ bool was_npn_negotiated_;
+ // Protocol negotiated with the server.
+ NextProto protocol_negotiated_;
+ bool using_spdy_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_FACTORY_IMPL_REQUEST_H_
diff --git a/chromium/net/http/http_stream_factory_impl_request_unittest.cc b/chromium/net/http/http_stream_factory_impl_request_unittest.cc
new file mode 100644
index 00000000000..1f38a2e56f6
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_request_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2013 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/http/http_stream_factory_impl_request.h"
+
+#include "net/http/http_stream_factory_impl_job.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_service.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class HttpStreamFactoryImplRequestTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ HttpStreamFactoryImplRequestTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+namespace {
+
+class DoNothingRequestDelegate : public HttpStreamRequest::Delegate {
+ public:
+ DoNothingRequestDelegate() {}
+
+ virtual ~DoNothingRequestDelegate() {}
+
+ // HttpStreamRequest::Delegate
+ virtual void OnStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE {}
+ virtual void OnWebSocketStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) OVERRIDE {}
+ virtual void OnStreamFailed(
+ int status,
+ const SSLConfig& used_ssl_config) OVERRIDE {}
+ virtual void OnCertificateError(
+ int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) OVERRIDE {}
+ virtual void OnNeedsProxyAuth(const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) OVERRIDE {}
+ virtual void OnNeedsClientAuth(const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) OVERRIDE {}
+ virtual void OnHttpsProxyTunnelResponse(const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE {}
+};
+
+} // namespace
+
+// Make sure that Request passes on its priority updates to its jobs.
+TEST_P(HttpStreamFactoryImplRequestTest, SetPriority) {
+ SpdySessionDependencies session_deps(GetParam(),
+ ProxyService::CreateDirect());
+
+ scoped_refptr<HttpNetworkSession>
+ session(SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpStreamFactoryImpl* factory =
+ static_cast<HttpStreamFactoryImpl*>(session->http_stream_factory());
+
+ DoNothingRequestDelegate request_delegate;
+ HttpStreamFactoryImpl::Request request(
+ GURL(), factory, &request_delegate, NULL, BoundNetLog());
+
+ HttpStreamFactoryImpl::Job* job =
+ new HttpStreamFactoryImpl::Job(factory,
+ session,
+ HttpRequestInfo(),
+ DEFAULT_PRIORITY,
+ SSLConfig(),
+ SSLConfig(),
+ NULL);
+ request.AttachJob(job);
+ EXPECT_EQ(DEFAULT_PRIORITY, job->priority());
+
+ request.SetPriority(MEDIUM);
+ EXPECT_EQ(MEDIUM, job->priority());
+
+ // Make |job| the bound job.
+ request.OnStreamFailed(job, ERR_FAILED, SSLConfig());
+
+ request.SetPriority(IDLE);
+ EXPECT_EQ(IDLE, job->priority());
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_factory_impl_unittest.cc b/chromium/net/http/http_stream_factory_impl_unittest.cc
new file mode 100644
index 00000000000..f378c93ea18
--- /dev/null
+++ b/chromium/net/http/http_stream_factory_impl_unittest.cc
@@ -0,0 +1,1226 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_factory_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_session_peer.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/mock_client_socket_pool_manager.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+// This file can be included from net/http even though
+// it is in net/websockets because it doesn't
+// introduce any link dependency to net/websockets.
+#include "net/websockets/websocket_stream_base.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class UseAlternateProtocolsScopedSetter {
+ public:
+ explicit UseAlternateProtocolsScopedSetter(bool use_alternate_protocols)
+ : use_alternate_protocols_(HttpStreamFactory::use_alternate_protocols()) {
+ HttpStreamFactory::set_use_alternate_protocols(use_alternate_protocols);
+ }
+ ~UseAlternateProtocolsScopedSetter() {
+ HttpStreamFactory::set_use_alternate_protocols(use_alternate_protocols_);
+ }
+
+ private:
+ bool use_alternate_protocols_;
+};
+
+class MockWebSocketStream : public WebSocketStreamBase {
+ public:
+ enum StreamType {
+ kStreamTypeBasic,
+ kStreamTypeSpdy,
+ };
+
+ explicit MockWebSocketStream(StreamType type) : type_(type) {}
+
+ virtual ~MockWebSocketStream() {}
+
+ virtual WebSocketStream* AsWebSocketStream() OVERRIDE { return NULL; }
+
+ StreamType type() const {
+ return type_;
+ }
+
+ private:
+ const StreamType type_;
+};
+
+// HttpStreamFactoryImpl subclass that can wait until a preconnect is complete.
+class MockHttpStreamFactoryImplForPreconnect : public HttpStreamFactoryImpl {
+ public:
+ MockHttpStreamFactoryImplForPreconnect(HttpNetworkSession* session,
+ bool for_websockets)
+ : HttpStreamFactoryImpl(session, for_websockets),
+ preconnect_done_(false),
+ waiting_for_preconnect_(false) {}
+
+
+ void WaitForPreconnects() {
+ while (!preconnect_done_) {
+ waiting_for_preconnect_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_preconnect_ = false;
+ }
+ }
+
+ private:
+ // HttpStreamFactoryImpl methods.
+ virtual void OnPreconnectsCompleteInternal() OVERRIDE {
+ preconnect_done_ = true;
+ if (waiting_for_preconnect_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ bool preconnect_done_;
+ bool waiting_for_preconnect_;
+};
+
+class StreamRequestWaiter : public HttpStreamRequest::Delegate {
+ public:
+ StreamRequestWaiter()
+ : waiting_for_stream_(false),
+ stream_done_(false) {}
+
+ // HttpStreamRequest::Delegate
+
+ virtual void OnStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE {
+ stream_done_ = true;
+ if (waiting_for_stream_)
+ base::MessageLoop::current()->Quit();
+ stream_.reset(stream);
+ used_ssl_config_ = used_ssl_config;
+ used_proxy_info_ = used_proxy_info;
+ }
+
+ virtual void OnWebSocketStreamReady(
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ WebSocketStreamBase* stream) OVERRIDE {
+ stream_done_ = true;
+ if (waiting_for_stream_)
+ base::MessageLoop::current()->Quit();
+ websocket_stream_.reset(stream);
+ used_ssl_config_ = used_ssl_config;
+ used_proxy_info_ = used_proxy_info;
+ }
+
+ virtual void OnStreamFailed(
+ int status,
+ const SSLConfig& used_ssl_config) OVERRIDE {}
+
+ virtual void OnCertificateError(
+ int status,
+ const SSLConfig& used_ssl_config,
+ const SSLInfo& ssl_info) OVERRIDE {}
+
+ virtual void OnNeedsProxyAuth(const HttpResponseInfo& proxy_response,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpAuthController* auth_controller) OVERRIDE {}
+
+ virtual void OnNeedsClientAuth(const SSLConfig& used_ssl_config,
+ SSLCertRequestInfo* cert_info) OVERRIDE {}
+
+ virtual void OnHttpsProxyTunnelResponse(const HttpResponseInfo& response_info,
+ const SSLConfig& used_ssl_config,
+ const ProxyInfo& used_proxy_info,
+ HttpStreamBase* stream) OVERRIDE {}
+
+ void WaitForStream() {
+ while (!stream_done_) {
+ waiting_for_stream_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_stream_ = false;
+ }
+ }
+
+ const SSLConfig& used_ssl_config() const {
+ return used_ssl_config_;
+ }
+
+ const ProxyInfo& used_proxy_info() const {
+ return used_proxy_info_;
+ }
+
+ HttpStreamBase* stream() {
+ return stream_.get();
+ }
+
+ MockWebSocketStream* websocket_stream() {
+ return static_cast<MockWebSocketStream*>(websocket_stream_.get());
+ }
+
+ bool stream_done() const { return stream_done_; }
+
+ private:
+ bool waiting_for_stream_;
+ bool stream_done_;
+ scoped_ptr<HttpStreamBase> stream_;
+ scoped_ptr<WebSocketStreamBase> websocket_stream_;
+ SSLConfig used_ssl_config_;
+ ProxyInfo used_proxy_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamRequestWaiter);
+};
+
+class WebSocketSpdyStream : public MockWebSocketStream {
+ public:
+ explicit WebSocketSpdyStream(const base::WeakPtr<SpdySession>& spdy_session)
+ : MockWebSocketStream(kStreamTypeSpdy), spdy_session_(spdy_session) {}
+
+ virtual ~WebSocketSpdyStream() {}
+
+ SpdySession* spdy_session() { return spdy_session_.get(); }
+
+ private:
+ base::WeakPtr<SpdySession> spdy_session_;
+};
+
+class WebSocketBasicStream : public MockWebSocketStream {
+ public:
+ explicit WebSocketBasicStream(ClientSocketHandle* connection)
+ : MockWebSocketStream(kStreamTypeBasic), connection_(connection) {}
+
+ virtual ~WebSocketBasicStream() {
+ connection_->socket()->Disconnect();
+ }
+
+ ClientSocketHandle* connection() { return connection_.get(); }
+
+ private:
+ scoped_ptr<ClientSocketHandle> connection_;
+};
+
+class WebSocketStreamFactory : public WebSocketStreamBase::Factory {
+ public:
+ virtual ~WebSocketStreamFactory() {}
+
+ virtual WebSocketStreamBase* CreateBasicStream(ClientSocketHandle* connection,
+ bool using_proxy) OVERRIDE {
+ return new WebSocketBasicStream(connection);
+ }
+
+ virtual WebSocketStreamBase* CreateSpdyStream(
+ const base::WeakPtr<SpdySession>& spdy_session,
+ bool use_relative_url) OVERRIDE {
+ return new WebSocketSpdyStream(spdy_session);
+ }
+};
+
+struct TestCase {
+ int num_streams;
+ bool ssl;
+};
+
+TestCase kTests[] = {
+ { 1, false },
+ { 2, false },
+ { 1, true},
+ { 2, true},
+};
+
+void PreconnectHelperForURL(int num_streams,
+ const GURL& url,
+ HttpNetworkSession* session) {
+ HttpNetworkSessionPeer peer(session);
+ MockHttpStreamFactoryImplForPreconnect* mock_factory =
+ new MockHttpStreamFactoryImplForPreconnect(session, false);
+ peer.SetHttpStreamFactory(mock_factory);
+ SSLConfig ssl_config;
+ session->ssl_config_service()->GetSSLConfig(&ssl_config);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = url;
+ request.load_flags = 0;
+
+ session->http_stream_factory()->PreconnectStreams(
+ num_streams, request, DEFAULT_PRIORITY, ssl_config, ssl_config);
+ mock_factory->WaitForPreconnects();
+};
+
+void PreconnectHelper(const TestCase& test,
+ HttpNetworkSession* session) {
+ GURL url = test.ssl ? GURL("https://www.google.com") :
+ GURL("http://www.google.com");
+ PreconnectHelperForURL(test.num_streams, url, session);
+};
+
+template<typename ParentPool>
+class CapturePreconnectsSocketPool : public ParentPool {
+ public:
+ CapturePreconnectsSocketPool(HostResolver* host_resolver,
+ CertVerifier* cert_verifier);
+
+ int last_num_streams() const {
+ return last_num_streams_;
+ }
+
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual void RequestSockets(const std::string& group_name,
+ const void* socket_params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE {
+ last_num_streams_ = num_sockets;
+ }
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE {
+ ADD_FAILURE();
+ }
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE {
+ ADD_FAILURE();
+ }
+ virtual void CloseIdleSockets() OVERRIDE {
+ ADD_FAILURE();
+ }
+ virtual int IdleSocketCount() const OVERRIDE {
+ ADD_FAILURE();
+ return 0;
+ }
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE {
+ ADD_FAILURE();
+ return 0;
+ }
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE {
+ ADD_FAILURE();
+ return LOAD_STATE_IDLE;
+ }
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE {
+ return base::TimeDelta();
+ }
+
+ private:
+ int last_num_streams_;
+};
+
+typedef CapturePreconnectsSocketPool<TransportClientSocketPool>
+CapturePreconnectsTransportSocketPool;
+typedef CapturePreconnectsSocketPool<HttpProxyClientSocketPool>
+CapturePreconnectsHttpProxySocketPool;
+typedef CapturePreconnectsSocketPool<SOCKSClientSocketPool>
+CapturePreconnectsSOCKSSocketPool;
+typedef CapturePreconnectsSocketPool<SSLClientSocketPool>
+CapturePreconnectsSSLSocketPool;
+
+template<typename ParentPool>
+CapturePreconnectsSocketPool<ParentPool>::CapturePreconnectsSocketPool(
+ HostResolver* host_resolver, CertVerifier* /* cert_verifier */)
+ : ParentPool(0, 0, NULL, host_resolver, NULL, NULL),
+ last_num_streams_(-1) {}
+
+template<>
+CapturePreconnectsHttpProxySocketPool::CapturePreconnectsSocketPool(
+ HostResolver* host_resolver, CertVerifier* /* cert_verifier */)
+ : HttpProxyClientSocketPool(0, 0, NULL, host_resolver, NULL, NULL, NULL),
+ last_num_streams_(-1) {}
+
+template <>
+CapturePreconnectsSSLSocketPool::CapturePreconnectsSocketPool(
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier)
+ : SSLClientSocketPool(0,
+ 0,
+ NULL,
+ host_resolver,
+ cert_verifier,
+ NULL,
+ NULL,
+ std::string(),
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL),
+ last_num_streams_(-1) {}
+
+class HttpStreamFactoryTest : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ HttpStreamFactoryTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(HttpStreamFactoryTest, PreconnectDirect) {
+ for (size_t i = 0; i < arraysize(kTests); ++i) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpNetworkSessionPeer peer(session);
+ CapturePreconnectsTransportSocketPool* transport_conn_pool =
+ new CapturePreconnectsTransportSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ CapturePreconnectsSSLSocketPool* ssl_conn_pool =
+ new CapturePreconnectsSSLSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetTransportSocketPool(transport_conn_pool);
+ mock_pool_manager->SetSSLSocketPool(ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+ PreconnectHelper(kTests[i], session.get());
+ if (kTests[i].ssl)
+ EXPECT_EQ(kTests[i].num_streams, ssl_conn_pool->last_num_streams());
+ else
+ EXPECT_EQ(kTests[i].num_streams, transport_conn_pool->last_num_streams());
+ }
+}
+
+TEST_P(HttpStreamFactoryTest, PreconnectHttpProxy) {
+ for (size_t i = 0; i < arraysize(kTests); ++i) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateFixed("http_proxy"));
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpNetworkSessionPeer peer(session);
+ HostPortPair proxy_host("http_proxy", 80);
+ CapturePreconnectsHttpProxySocketPool* http_proxy_pool =
+ new CapturePreconnectsHttpProxySocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ CapturePreconnectsSSLSocketPool* ssl_conn_pool =
+ new CapturePreconnectsSSLSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetSocketPoolForHTTPProxy(proxy_host, http_proxy_pool);
+ mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+ PreconnectHelper(kTests[i], session.get());
+ if (kTests[i].ssl)
+ EXPECT_EQ(kTests[i].num_streams, ssl_conn_pool->last_num_streams());
+ else
+ EXPECT_EQ(kTests[i].num_streams, http_proxy_pool->last_num_streams());
+ }
+}
+
+TEST_P(HttpStreamFactoryTest, PreconnectSocksProxy) {
+ for (size_t i = 0; i < arraysize(kTests); ++i) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateFixed("socks4://socks_proxy:1080"));
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpNetworkSessionPeer peer(session);
+ HostPortPair proxy_host("socks_proxy", 1080);
+ CapturePreconnectsSOCKSSocketPool* socks_proxy_pool =
+ new CapturePreconnectsSOCKSSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ CapturePreconnectsSSLSocketPool* ssl_conn_pool =
+ new CapturePreconnectsSSLSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetSocketPoolForSOCKSProxy(proxy_host, socks_proxy_pool);
+ mock_pool_manager->SetSocketPoolForSSLWithProxy(proxy_host, ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+ PreconnectHelper(kTests[i], session.get());
+ if (kTests[i].ssl)
+ EXPECT_EQ(kTests[i].num_streams, ssl_conn_pool->last_num_streams());
+ else
+ EXPECT_EQ(kTests[i].num_streams, socks_proxy_pool->last_num_streams());
+ }
+}
+
+TEST_P(HttpStreamFactoryTest, PreconnectDirectWithExistingSpdySession) {
+ for (size_t i = 0; i < arraysize(kTests); ++i) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpNetworkSessionPeer peer(session);
+
+ // Put a SpdySession in the pool.
+ HostPortPair host_port_pair("www.google.com", 443);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ ignore_result(CreateFakeSpdySession(session->spdy_session_pool(), key));
+
+ CapturePreconnectsTransportSocketPool* transport_conn_pool =
+ new CapturePreconnectsTransportSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ CapturePreconnectsSSLSocketPool* ssl_conn_pool =
+ new CapturePreconnectsSSLSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetTransportSocketPool(transport_conn_pool);
+ mock_pool_manager->SetSSLSocketPool(ssl_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+ PreconnectHelper(kTests[i], session.get());
+ // We shouldn't be preconnecting if we have an existing session, which is
+ // the case for https://www.google.com.
+ if (kTests[i].ssl)
+ EXPECT_EQ(-1, ssl_conn_pool->last_num_streams());
+ else
+ EXPECT_EQ(kTests[i].num_streams,
+ transport_conn_pool->last_num_streams());
+ }
+}
+
+// Verify that preconnects to unsafe ports are cancelled before they reach
+// the SocketPool.
+TEST_P(HttpStreamFactoryTest, PreconnectUnsafePort) {
+ ASSERT_FALSE(IsPortAllowedByDefault(7));
+ ASSERT_FALSE(IsPortAllowedByOverride(7));
+
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ HttpNetworkSessionPeer peer(session);
+ CapturePreconnectsTransportSocketPool* transport_conn_pool =
+ new CapturePreconnectsTransportSocketPool(
+ session_deps.host_resolver.get(),
+ session_deps.cert_verifier.get());
+ MockClientSocketPoolManager* mock_pool_manager =
+ new MockClientSocketPoolManager;
+ mock_pool_manager->SetTransportSocketPool(transport_conn_pool);
+ peer.SetClientSocketPoolManager(mock_pool_manager);
+
+ PreconnectHelperForURL(1, GURL("http://www.google.com:7"), session.get());
+
+ EXPECT_EQ(-1, transport_conn_pool->last_num_streams());
+}
+
+TEST_P(HttpStreamFactoryTest, JobNotifiesProxy) {
+ const char* kProxyString = "PROXY bad:99; PROXY maybe:80; DIRECT";
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateFixedFromPacResult(kProxyString));
+
+ // First connection attempt fails
+ StaticSocketDataProvider socket_data1;
+ socket_data1.set_connect_data(MockConnect(ASYNC, ERR_ADDRESS_UNREACHABLE));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data1);
+
+ // Second connection attempt succeeds
+ StaticSocketDataProvider socket_data2;
+ socket_data2.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data2);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream. It should succeed using the second proxy in the
+ // list.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://www.google.com");
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+ waiter.WaitForStream();
+
+ // The proxy that failed should now be known to the proxy_service as bad.
+ const ProxyRetryInfoMap& retry_info =
+ session->proxy_service()->proxy_retry_info();
+ EXPECT_EQ(1u, retry_info.size());
+ ProxyRetryInfoMap::const_iterator iter = retry_info.find("bad:99");
+ EXPECT_TRUE(iter != retry_info.end());
+}
+
+TEST_P(HttpStreamFactoryTest, PrivacyModeDisablesChannelId) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Set an existing SpdySession in the pool.
+ HostPortPair host_port_pair("www.google.com", 443);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeEnabled);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.google.com");
+ request_info.load_flags = 0;
+ request_info.privacy_mode = kPrivacyModeDisabled;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+ waiter.WaitForStream();
+
+ // The stream shouldn't come from spdy as we are using different privacy mode
+ EXPECT_FALSE(request->using_spdy());
+
+ SSLConfig used_ssl_config = waiter.used_ssl_config();
+ EXPECT_EQ(used_ssl_config.channel_id_enabled, ssl_config.channel_id_enabled);
+}
+
+namespace {
+// Return count of distinct groups in given socket pool.
+int GetSocketPoolGroupCount(ClientSocketPool* pool) {
+ int count = 0;
+ scoped_ptr<base::DictionaryValue> dict(pool->GetInfoAsValue("", "", false));
+ EXPECT_TRUE(dict != NULL);
+ base::DictionaryValue* groups = NULL;
+ if (dict->GetDictionary("groups", &groups) && (groups != NULL)) {
+ count = static_cast<int>(groups->size());
+ }
+ return count;
+}
+} // namespace
+
+TEST_P(HttpStreamFactoryTest, PrivacyModeUsesDifferentSocketPoolGroup) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+ SSLClientSocketPool* ssl_pool = session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+
+ EXPECT_EQ(GetSocketPoolGroupCount(ssl_pool), 0);
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.google.com");
+ request_info.load_flags = 0;
+ request_info.privacy_mode = kPrivacyModeDisabled;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+
+ scoped_ptr<HttpStreamRequest> request1(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+ waiter.WaitForStream();
+
+ EXPECT_EQ(GetSocketPoolGroupCount(ssl_pool), 1);
+
+ scoped_ptr<HttpStreamRequest> request2(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+ waiter.WaitForStream();
+
+ EXPECT_EQ(GetSocketPoolGroupCount(ssl_pool), 1);
+
+ request_info.privacy_mode = kPrivacyModeEnabled;
+ scoped_ptr<HttpStreamRequest> request3(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+ waiter.WaitForStream();
+
+ EXPECT_EQ(GetSocketPoolGroupCount(ssl_pool), 2);
+}
+
+TEST_P(HttpStreamFactoryTest, GetLoadState) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://www.google.com");
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info, DEFAULT_PRIORITY, ssl_config, ssl_config,
+ &waiter, BoundNetLog()));
+
+ EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, request->GetLoadState());
+
+ waiter.WaitForStream();
+}
+
+TEST_P(HttpStreamFactoryTest, RequestHttpStream) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream. It should succeed using the second proxy in the
+ // list.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ ASSERT_TRUE(NULL != waiter.stream());
+ EXPECT_TRUE(NULL == waiter.websocket_stream());
+ EXPECT_FALSE(waiter.stream()->IsSpdyHttpStream());
+
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSSLSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestHttpStreamOverSSL) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ MockRead mock_read(ASYNC, OK);
+ StaticSocketDataProvider socket_data(&mock_read, 1, NULL, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ ASSERT_TRUE(NULL != waiter.stream());
+ EXPECT_TRUE(NULL == waiter.websocket_stream());
+ EXPECT_FALSE(waiter.stream()->IsSpdyHttpStream());
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSSLSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestHttpStreamOverProxy) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateFixed("myproxy:8888"));
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream. It should succeed using the second proxy in the
+ // list.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ ASSERT_TRUE(NULL != waiter.stream());
+ EXPECT_TRUE(NULL == waiter.websocket_stream());
+ EXPECT_FALSE(waiter.stream()->IsSpdyHttpStream());
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(session->GetSocketPoolForHTTPProxy(
+ HttpNetworkSession::NORMAL_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForSSLWithProxy(
+ HttpNetworkSession::NORMAL_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForHTTPProxy(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForSSLWithProxy(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_FALSE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestWebSocketBasicStream) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("ws://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ WebSocketStreamFactory factory;
+ scoped_ptr<HttpStreamRequest> request(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ &factory,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ EXPECT_TRUE(NULL == waiter.stream());
+ ASSERT_TRUE(NULL != waiter.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeBasic,
+ waiter.websocket_stream()->type());
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestWebSocketBasicStreamOverSSL) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateDirect());
+
+ MockRead mock_read(ASYNC, OK);
+ StaticSocketDataProvider socket_data(&mock_read, 1, NULL, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("wss://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ WebSocketStreamFactory factory;
+ scoped_ptr<HttpStreamRequest> request(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ &factory,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ EXPECT_TRUE(NULL == waiter.stream());
+ ASSERT_TRUE(NULL != waiter.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeBasic,
+ waiter.websocket_stream()->type());
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestWebSocketBasicStreamOverProxy) {
+ SpdySessionDependencies session_deps(
+ GetParam(), ProxyService::CreateFixed("myproxy:8888"));
+
+ MockRead read(SYNCHRONOUS, "HTTP/1.0 200 Connection established\r\n\r\n");
+ StaticSocketDataProvider socket_data(&read, 1, 0, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("ws://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ WebSocketStreamFactory factory;
+ scoped_ptr<HttpStreamRequest> request(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ &factory,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ EXPECT_TRUE(NULL == waiter.stream());
+ ASSERT_TRUE(NULL != waiter.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeBasic,
+ waiter.websocket_stream()->type());
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForHTTPProxy(
+ HttpNetworkSession::NORMAL_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForSSLWithProxy(
+ HttpNetworkSession::NORMAL_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(session->GetSocketPoolForHTTPProxy(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(session->GetSocketPoolForSSLWithProxy(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL,
+ HostPortPair("myproxy", 8888))));
+ EXPECT_FALSE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestSpdyHttpStream) {
+ SpdySessionDependencies session_deps(GetParam(),
+ ProxyService::CreateDirect());
+
+ MockRead mock_read(ASYNC, OK);
+ DeterministicSocketData socket_data(&mock_read, 1, NULL, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.deterministic_socket_factory->AddSocketDataProvider(
+ &socket_data);
+
+ SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
+ ssl_socket_data.SetNextProto(GetParam());
+ session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(
+ &ssl_socket_data);
+
+ HostPortPair host_port_pair("www.google.com", 443);
+ scoped_refptr<HttpNetworkSession>
+ session(SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("https://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ scoped_ptr<HttpStreamRequest> request(
+ session->http_stream_factory()->RequestStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ EXPECT_TRUE(NULL == waiter.websocket_stream());
+ ASSERT_TRUE(NULL != waiter.stream());
+ EXPECT_TRUE(waiter.stream()->IsSpdyHttpStream());
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, RequestWebSocketSpdyStream) {
+ SpdySessionDependencies session_deps(GetParam(),
+ ProxyService::CreateDirect());
+
+ MockRead mock_read(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider socket_data(&mock_read, 1, NULL, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.socket_factory->AddSocketDataProvider(&socket_data);
+
+ SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
+ ssl_socket_data.SetNextProto(GetParam());
+ session_deps.socket_factory->AddSSLSocketDataProvider(&ssl_socket_data);
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ scoped_refptr<HttpNetworkSession>
+ session(SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("wss://www.google.com");
+ request_info.load_flags = 0;
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter1;
+ WebSocketStreamFactory factory;
+ scoped_ptr<HttpStreamRequest> request1(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter1,
+ &factory,
+ BoundNetLog()));
+ waiter1.WaitForStream();
+ EXPECT_TRUE(waiter1.stream_done());
+ ASSERT_TRUE(NULL != waiter1.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeSpdy,
+ waiter1.websocket_stream()->type());
+ EXPECT_TRUE(NULL == waiter1.stream());
+
+ StreamRequestWaiter waiter2;
+ scoped_ptr<HttpStreamRequest> request2(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter2,
+ &factory,
+ BoundNetLog()));
+ waiter2.WaitForStream();
+ EXPECT_TRUE(waiter2.stream_done());
+ ASSERT_TRUE(NULL != waiter2.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeSpdy,
+ waiter2.websocket_stream()->type());
+ EXPECT_TRUE(NULL == waiter2.stream());
+ EXPECT_NE(waiter2.websocket_stream(), waiter1.websocket_stream());
+ EXPECT_EQ(static_cast<WebSocketSpdyStream*>(waiter2.websocket_stream())->
+ spdy_session(),
+ static_cast<WebSocketSpdyStream*>(waiter1.websocket_stream())->
+ spdy_session());
+
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter1.used_proxy_info().is_direct());
+}
+
+TEST_P(HttpStreamFactoryTest, OrphanedWebSocketStream) {
+ UseAlternateProtocolsScopedSetter use_alternate_protocols(true);
+ SpdySessionDependencies session_deps(GetParam(),
+ ProxyService::CreateDirect());
+
+ MockRead mock_read(ASYNC, OK);
+ DeterministicSocketData socket_data(&mock_read, 1, NULL, 0);
+ socket_data.set_connect_data(MockConnect(ASYNC, OK));
+ session_deps.deterministic_socket_factory->AddSocketDataProvider(
+ &socket_data);
+
+ MockRead mock_read2(ASYNC, OK);
+ DeterministicSocketData socket_data2(&mock_read2, 1, NULL, 0);
+ socket_data2.set_connect_data(MockConnect(ASYNC, ERR_IO_PENDING));
+ session_deps.deterministic_socket_factory->AddSocketDataProvider(
+ &socket_data2);
+
+ SSLSocketDataProvider ssl_socket_data(ASYNC, OK);
+ ssl_socket_data.SetNextProto(GetParam());
+ session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(
+ &ssl_socket_data);
+
+ scoped_refptr<HttpNetworkSession>
+ session(SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps));
+
+ // Now request a stream.
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("ws://www.google.com:8888");
+ request_info.load_flags = 0;
+
+ session->http_server_properties()->SetAlternateProtocol(
+ HostPortPair("www.google.com", 8888),
+ 9999,
+ NPN_SPDY_3);
+
+ SSLConfig ssl_config;
+ StreamRequestWaiter waiter;
+ WebSocketStreamFactory factory;
+ scoped_ptr<HttpStreamRequest> request(
+ session->websocket_stream_factory()->RequestWebSocketStream(
+ request_info,
+ DEFAULT_PRIORITY,
+ ssl_config,
+ ssl_config,
+ &waiter,
+ &factory,
+ BoundNetLog()));
+ waiter.WaitForStream();
+ EXPECT_TRUE(waiter.stream_done());
+ EXPECT_TRUE(NULL == waiter.stream());
+ ASSERT_TRUE(NULL != waiter.websocket_stream());
+ EXPECT_EQ(MockWebSocketStream::kStreamTypeSpdy,
+ waiter.websocket_stream()->type());
+
+ // Make sure that there was an alternative connection
+ // which consumes extra connections.
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(0, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL)));
+ EXPECT_EQ(2, GetSocketPoolGroupCount(
+ session->GetTransportSocketPool(
+ HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_EQ(1, GetSocketPoolGroupCount(
+ session->GetSSLSocketPool(HttpNetworkSession::WEBSOCKET_SOCKET_POOL)));
+ EXPECT_TRUE(waiter.used_proxy_info().is_direct());
+
+ // Make sure there is no orphaned job. it is already canceled.
+ ASSERT_EQ(0u, static_cast<HttpStreamFactoryImpl*>(
+ session->websocket_stream_factory())->num_orphaned_jobs());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_parser.cc b/chromium/net/http/http_stream_parser.cc
new file mode 100644
index 00000000000..e53aafdac1c
--- /dev/null
+++ b/chromium/net/http/http_stream_parser.cc
@@ -0,0 +1,955 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_parser.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_chunked_decoder.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+
+namespace {
+
+const size_t kMaxMergedHeaderAndBodySize = 1400;
+const size_t kRequestBodyBufferSize = 1 << 14; // 16KB
+
+std::string GetResponseHeaderLines(const net::HttpResponseHeaders& headers) {
+ std::string raw_headers = headers.raw_headers();
+ const char* null_separated_headers = raw_headers.c_str();
+ const char* header_line = null_separated_headers;
+ std::string cr_separated_headers;
+ while (header_line[0] != 0) {
+ cr_separated_headers += header_line;
+ cr_separated_headers += "\n";
+ header_line += strlen(header_line) + 1;
+ }
+ return cr_separated_headers;
+}
+
+// Return true if |headers| contain multiple |field_name| fields with different
+// values.
+bool HeadersContainMultipleCopiesOfField(
+ const net::HttpResponseHeaders& headers,
+ const std::string& field_name) {
+ void* it = NULL;
+ std::string field_value;
+ if (!headers.EnumerateHeader(&it, field_name, &field_value))
+ return false;
+ // There's at least one |field_name| header. Check if there are any more
+ // such headers, and if so, return true if they have different values.
+ std::string field_value2;
+ while (headers.EnumerateHeader(&it, field_name, &field_value2)) {
+ if (field_value != field_value2)
+ return true;
+ }
+ return false;
+}
+
+base::Value* NetLogSendRequestBodyCallback(
+ int length,
+ bool is_chunked,
+ bool did_merge,
+ net::NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("length", length);
+ dict->SetBoolean("is_chunked", is_chunked);
+ dict->SetBoolean("did_merge", did_merge);
+ return dict;
+}
+
+} // namespace
+
+namespace net {
+
+// Similar to DrainableIOBuffer(), but this version comes with its own
+// storage. The motivation is to avoid repeated allocations of
+// DrainableIOBuffer.
+//
+// Example:
+//
+// scoped_refptr<SeekableIOBuffer> buf = new SeekableIOBuffer(1024);
+// // capacity() == 1024. size() == BytesRemaining == BytesConsumed() == 0.
+// // data() points to the beginning of the buffer.
+//
+// // Read() takes an IOBuffer.
+// int bytes_read = some_reader->Read(buf, buf->capacity());
+// buf->DidAppend(bytes_read);
+// // size() == BytesRemaining() == bytes_read. data() is unaffected.
+//
+// while (buf->BytesRemaining() > 0) {
+// // Write() takes an IOBuffer. If it takes const char*, we could
+/// // simply use the regular IOBuffer like buf->data() + offset.
+// int bytes_written = Write(buf, buf->BytesRemaining());
+// buf->DidConsume(bytes_written);
+// }
+// // BytesRemaining() == 0. BytesConsumed() == size().
+// // data() points to the end of the comsumed bytes (exclusive).
+//
+// // If you want to reuse the buffer, be sure to clear the buffer.
+// buf->Clear();
+// // size() == BytesRemaining() == BytesConsumed() == 0.
+// // data() points to the beginning of the buffer.
+//
+class HttpStreamParser::SeekableIOBuffer : public net::IOBuffer {
+ public:
+ explicit SeekableIOBuffer(int capacity)
+ : IOBuffer(capacity),
+ real_data_(data_),
+ capacity_(capacity),
+ size_(0),
+ used_(0) {
+ }
+
+ // DidConsume() changes the |data_| pointer so that |data_| always points
+ // to the first unconsumed byte.
+ void DidConsume(int bytes) {
+ SetOffset(used_ + bytes);
+ }
+
+ // Returns the number of unconsumed bytes.
+ int BytesRemaining() const {
+ return size_ - used_;
+ }
+
+ // Seeks to an arbitrary point in the buffer. The notion of bytes consumed
+ // and remaining are updated appropriately.
+ void SetOffset(int bytes) {
+ DCHECK_GE(bytes, 0);
+ DCHECK_LE(bytes, size_);
+ used_ = bytes;
+ data_ = real_data_ + used_;
+ }
+
+ // Called after data is added to the buffer. Adds |bytes| added to
+ // |size_|. data() is unaffected.
+ void DidAppend(int bytes) {
+ DCHECK_GE(bytes, 0);
+ DCHECK_GE(size_ + bytes, 0);
+ DCHECK_LE(size_ + bytes, capacity_);
+ size_ += bytes;
+ }
+
+ // Changes the logical size to 0, and the offset to 0.
+ void Clear() {
+ size_ = 0;
+ SetOffset(0);
+ }
+
+ // Returns the logical size of the buffer (i.e the number of bytes of data
+ // in the buffer).
+ int size() const { return size_; }
+
+ // Returns the capacity of the buffer. The capacity is the size used when
+ // the object is created.
+ int capacity() const { return capacity_; };
+
+ private:
+ virtual ~SeekableIOBuffer() {
+ // data_ will be deleted in IOBuffer::~IOBuffer().
+ data_ = real_data_;
+ }
+
+ char* real_data_;
+ int capacity_;
+ int size_;
+ int used_;
+};
+
+// 2 CRLFs + max of 8 hex chars.
+const size_t HttpStreamParser::kChunkHeaderFooterSize = 12;
+
+HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection,
+ const HttpRequestInfo* request,
+ GrowableIOBuffer* read_buffer,
+ const BoundNetLog& net_log)
+ : io_state_(STATE_NONE),
+ request_(request),
+ request_headers_(NULL),
+ read_buf_(read_buffer),
+ read_buf_unused_offset_(0),
+ response_header_start_offset_(-1),
+ response_body_length_(-1),
+ response_body_read_(0),
+ user_read_buf_(NULL),
+ user_read_buf_len_(0),
+ connection_(connection),
+ net_log_(net_log),
+ sent_last_chunk_(false),
+ weak_ptr_factory_(this) {
+ io_callback_ = base::Bind(&HttpStreamParser::OnIOComplete,
+ weak_ptr_factory_.GetWeakPtr());
+}
+
+HttpStreamParser::~HttpStreamParser() {
+}
+
+int HttpStreamParser::SendRequest(const std::string& request_line,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_NONE, io_state_);
+ DCHECK(callback_.is_null());
+ DCHECK(!callback.is_null());
+ DCHECK(response);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS,
+ base::Bind(&HttpRequestHeaders::NetLogCallback,
+ base::Unretained(&headers),
+ &request_line));
+
+ DVLOG(1) << __FUNCTION__ << "()"
+ << " request_line = \"" << request_line << "\""
+ << " headers = \"" << headers.ToString() << "\"";
+ response_ = response;
+
+ // Put the peer's IP address and port into the response.
+ IPEndPoint ip_endpoint;
+ int result = connection_->socket()->GetPeerAddress(&ip_endpoint);
+ if (result != OK)
+ return result;
+ response_->socket_address = HostPortPair::FromIPEndPoint(ip_endpoint);
+
+ std::string request = request_line + headers.ToString();
+
+ if (request_->upload_data_stream != NULL) {
+ request_body_send_buf_ = new SeekableIOBuffer(kRequestBodyBufferSize);
+ if (request_->upload_data_stream->is_chunked()) {
+ // Read buffer is adjusted to guarantee that |request_body_send_buf_| is
+ // large enough to hold the encoded chunk.
+ request_body_read_buf_ =
+ new SeekableIOBuffer(kRequestBodyBufferSize - kChunkHeaderFooterSize);
+ } else {
+ // No need to encode request body, just send the raw data.
+ request_body_read_buf_ = request_body_send_buf_;
+ }
+ }
+
+ io_state_ = STATE_SENDING_HEADERS;
+
+ // If we have a small request body, then we'll merge with the headers into a
+ // single write.
+ bool did_merge = false;
+ if (ShouldMergeRequestHeadersAndBody(request, request_->upload_data_stream)) {
+ size_t merged_size = request.size() + request_->upload_data_stream->size();
+ scoped_refptr<IOBuffer> merged_request_headers_and_body(
+ new IOBuffer(merged_size));
+ // We'll repurpose |request_headers_| to store the merged headers and
+ // body.
+ request_headers_ = new DrainableIOBuffer(
+ merged_request_headers_and_body.get(), merged_size);
+
+ memcpy(request_headers_->data(), request.data(), request.size());
+ request_headers_->DidConsume(request.size());
+
+ size_t todo = request_->upload_data_stream->size();
+ while (todo) {
+ int consumed = request_->upload_data_stream
+ ->Read(request_headers_.get(), todo, CompletionCallback());
+ DCHECK_GT(consumed, 0); // Read() won't fail if not chunked.
+ request_headers_->DidConsume(consumed);
+ todo -= consumed;
+ }
+ DCHECK(request_->upload_data_stream->IsEOF());
+ // Reset the offset, so the buffer can be read from the beginning.
+ request_headers_->SetOffset(0);
+ did_merge = true;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_BODY,
+ base::Bind(&NetLogSendRequestBodyCallback,
+ request_->upload_data_stream->size(),
+ false, /* not chunked */
+ true /* merged */));
+ }
+
+ if (!did_merge) {
+ // If we didn't merge the body with the headers, then |request_headers_|
+ // contains just the HTTP headers.
+ scoped_refptr<StringIOBuffer> headers_io_buf(new StringIOBuffer(request));
+ request_headers_ =
+ new DrainableIOBuffer(headers_io_buf.get(), headers_io_buf->size());
+ }
+
+ result = DoLoop(OK);
+ if (result == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return result > 0 ? OK : result;
+}
+
+int HttpStreamParser::ReadResponseHeaders(const CompletionCallback& callback) {
+ DCHECK(io_state_ == STATE_REQUEST_SENT || io_state_ == STATE_DONE);
+ DCHECK(callback_.is_null());
+ DCHECK(!callback.is_null());
+
+ // This function can be called with io_state_ == STATE_DONE if the
+ // connection is closed after seeing just a 1xx response code.
+ if (io_state_ == STATE_DONE)
+ return ERR_CONNECTION_CLOSED;
+
+ int result = OK;
+ io_state_ = STATE_READ_HEADERS;
+
+ if (read_buf_->offset() > 0) {
+ // Simulate the state where the data was just read from the socket.
+ result = read_buf_->offset() - read_buf_unused_offset_;
+ read_buf_->set_offset(read_buf_unused_offset_);
+ }
+ if (result > 0)
+ io_state_ = STATE_READ_HEADERS_COMPLETE;
+
+ result = DoLoop(result);
+ if (result == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return result > 0 ? OK : result;
+}
+
+void HttpStreamParser::Close(bool not_reusable) {
+ if (not_reusable && connection_->socket())
+ connection_->socket()->Disconnect();
+ connection_->Reset();
+}
+
+int HttpStreamParser::ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(io_state_ == STATE_BODY_PENDING || io_state_ == STATE_DONE);
+ DCHECK(callback_.is_null());
+ DCHECK(!callback.is_null());
+ DCHECK_LE(buf_len, kMaxBufSize);
+
+ if (io_state_ == STATE_DONE)
+ return OK;
+
+ user_read_buf_ = buf;
+ user_read_buf_len_ = buf_len;
+ io_state_ = STATE_READ_BODY;
+
+ int result = DoLoop(OK);
+ if (result == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return result;
+}
+
+void HttpStreamParser::OnIOComplete(int result) {
+ result = DoLoop(result);
+
+ // The client callback can do anything, including destroying this class,
+ // so any pending callback must be issued after everything else is done.
+ if (result != ERR_IO_PENDING && !callback_.is_null()) {
+ CompletionCallback c = callback_;
+ callback_.Reset();
+ c.Run(result);
+ }
+}
+
+int HttpStreamParser::DoLoop(int result) {
+ bool can_do_more = true;
+ do {
+ switch (io_state_) {
+ case STATE_SENDING_HEADERS:
+ if (result < 0)
+ can_do_more = false;
+ else
+ result = DoSendHeaders(result);
+ break;
+ case STATE_SENDING_BODY:
+ if (result < 0)
+ can_do_more = false;
+ else
+ result = DoSendBody(result);
+ break;
+ case STATE_SEND_REQUEST_READING_BODY:
+ result = DoSendRequestReadingBody(result);
+ break;
+ case STATE_REQUEST_SENT:
+ DCHECK(result != ERR_IO_PENDING);
+ can_do_more = false;
+ break;
+ case STATE_READ_HEADERS:
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS);
+ result = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ result = DoReadHeadersComplete(result);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_STREAM_PARSER_READ_HEADERS, result);
+ break;
+ case STATE_BODY_PENDING:
+ DCHECK(result != ERR_IO_PENDING);
+ can_do_more = false;
+ break;
+ case STATE_READ_BODY:
+ result = DoReadBody();
+ // DoReadBodyComplete handles error conditions.
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ result = DoReadBodyComplete(result);
+ break;
+ case STATE_DONE:
+ DCHECK(result != ERR_IO_PENDING);
+ can_do_more = false;
+ break;
+ default:
+ NOTREACHED();
+ can_do_more = false;
+ break;
+ }
+ } while (result != ERR_IO_PENDING && can_do_more);
+
+ return result;
+}
+
+int HttpStreamParser::DoSendHeaders(int result) {
+ request_headers_->DidConsume(result);
+ int bytes_remaining = request_headers_->BytesRemaining();
+ if (bytes_remaining > 0) {
+ // Record our best estimate of the 'request time' as the time when we send
+ // out the first bytes of the request headers.
+ if (bytes_remaining == request_headers_->size()) {
+ response_->request_time = base::Time::Now();
+ }
+ result = connection_->socket()
+ ->Write(request_headers_.get(), bytes_remaining, io_callback_);
+ } else if (request_->upload_data_stream != NULL &&
+ (request_->upload_data_stream->is_chunked() ||
+ // !IsEOF() indicates that the body wasn't merged.
+ (request_->upload_data_stream->size() > 0 &&
+ !request_->upload_data_stream->IsEOF()))) {
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_BODY,
+ base::Bind(&NetLogSendRequestBodyCallback,
+ request_->upload_data_stream->size(),
+ request_->upload_data_stream->is_chunked(),
+ false /* not merged */));
+ io_state_ = STATE_SENDING_BODY;
+ result = OK;
+ } else {
+ io_state_ = STATE_REQUEST_SENT;
+ }
+ return result;
+}
+
+int HttpStreamParser::DoSendBody(int result) {
+ // |result| is the number of bytes sent from the last call to
+ // DoSendBody(), or 0 (i.e. OK).
+
+ // Send the remaining data in the request body buffer.
+ request_body_send_buf_->DidConsume(result);
+ if (request_body_send_buf_->BytesRemaining() > 0) {
+ return connection_->socket()
+ ->Write(request_body_send_buf_.get(),
+ request_body_send_buf_->BytesRemaining(),
+ io_callback_);
+ }
+
+ if (request_->upload_data_stream->is_chunked() && sent_last_chunk_) {
+ io_state_ = STATE_REQUEST_SENT;
+ return OK;
+ }
+
+ request_body_read_buf_->Clear();
+ io_state_ = STATE_SEND_REQUEST_READING_BODY;
+ return request_->upload_data_stream->Read(request_body_read_buf_.get(),
+ request_body_read_buf_->capacity(),
+ io_callback_);
+}
+
+int HttpStreamParser::DoSendRequestReadingBody(int result) {
+ // |result| is the result of read from the request body from the last call to
+ // DoSendBody().
+ DCHECK_GE(result, 0); // There won't be errors.
+
+ // Chunked data needs to be encoded.
+ if (request_->upload_data_stream->is_chunked()) {
+ if (result == 0) { // Reached the end.
+ DCHECK(request_->upload_data_stream->IsEOF());
+ sent_last_chunk_ = true;
+ }
+ // Encode the buffer as 1 chunk.
+ const base::StringPiece payload(request_body_read_buf_->data(), result);
+ request_body_send_buf_->Clear();
+ result = EncodeChunk(payload,
+ request_body_send_buf_->data(),
+ request_body_send_buf_->capacity());
+ }
+
+ if (result == 0) { // Reached the end.
+ // Reaching EOF means we can finish sending request body unless the data is
+ // chunked. (i.e. No need to send the terminal chunk.)
+ DCHECK(request_->upload_data_stream->IsEOF());
+ DCHECK(!request_->upload_data_stream->is_chunked());
+ io_state_ = STATE_REQUEST_SENT;
+ } else if (result > 0) {
+ request_body_send_buf_->DidAppend(result);
+ result = 0;
+ io_state_ = STATE_SENDING_BODY;
+ }
+ return result;
+}
+
+int HttpStreamParser::DoReadHeaders() {
+ io_state_ = STATE_READ_HEADERS_COMPLETE;
+
+ // Grow the read buffer if necessary.
+ if (read_buf_->RemainingCapacity() == 0)
+ read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize);
+
+ // http://crbug.com/16371: We're seeing |user_buf_->data()| return NULL.
+ // See if the user is passing in an IOBuffer with a NULL |data_|.
+ CHECK(read_buf_->data());
+
+ return connection_->socket()
+ ->Read(read_buf_.get(), read_buf_->RemainingCapacity(), io_callback_);
+}
+
+int HttpStreamParser::DoReadHeadersComplete(int result) {
+ if (result == 0)
+ result = ERR_CONNECTION_CLOSED;
+
+ if (result < 0 && result != ERR_CONNECTION_CLOSED) {
+ io_state_ = STATE_DONE;
+ return result;
+ }
+ // If we've used the connection before, then we know it is not a HTTP/0.9
+ // response and return ERR_CONNECTION_CLOSED.
+ if (result == ERR_CONNECTION_CLOSED && read_buf_->offset() == 0 &&
+ connection_->is_reused()) {
+ io_state_ = STATE_DONE;
+ return result;
+ }
+
+ // Record our best estimate of the 'response time' as the time when we read
+ // the first bytes of the response headers.
+ if (read_buf_->offset() == 0 && result != ERR_CONNECTION_CLOSED)
+ response_->response_time = base::Time::Now();
+
+ if (result == ERR_CONNECTION_CLOSED) {
+ // The connection closed before we detected the end of the headers.
+ if (read_buf_->offset() == 0) {
+ // The connection was closed before any data was sent. Likely an error
+ // rather than empty HTTP/0.9 response.
+ io_state_ = STATE_DONE;
+ return ERR_EMPTY_RESPONSE;
+ } else if (request_->url.SchemeIsSecure()) {
+ // The connection was closed in the middle of the headers. For HTTPS we
+ // don't parse partial headers. Return a different error code so that we
+ // know that we shouldn't attempt to retry the request.
+ io_state_ = STATE_DONE;
+ return ERR_RESPONSE_HEADERS_TRUNCATED;
+ }
+ // Parse things as well as we can and let the caller decide what to do.
+ int end_offset;
+ if (response_header_start_offset_ >= 0) {
+ io_state_ = STATE_READ_BODY_COMPLETE;
+ end_offset = read_buf_->offset();
+ } else {
+ io_state_ = STATE_BODY_PENDING;
+ end_offset = 0;
+ }
+ int rv = DoParseResponseHeaders(end_offset);
+ if (rv < 0)
+ return rv;
+ return result;
+ }
+
+ read_buf_->set_offset(read_buf_->offset() + result);
+ DCHECK_LE(read_buf_->offset(), read_buf_->capacity());
+ DCHECK_GE(result, 0);
+
+ int end_of_header_offset = ParseResponseHeaders();
+
+ // Note: -1 is special, it indicates we haven't found the end of headers.
+ // Anything less than -1 is a net::Error, so we bail out.
+ if (end_of_header_offset < -1)
+ return end_of_header_offset;
+
+ if (end_of_header_offset == -1) {
+ io_state_ = STATE_READ_HEADERS;
+ // Prevent growing the headers buffer indefinitely.
+ if (read_buf_->offset() - read_buf_unused_offset_ >= kMaxHeaderBufSize) {
+ io_state_ = STATE_DONE;
+ return ERR_RESPONSE_HEADERS_TOO_BIG;
+ }
+ } else {
+ // Note where the headers stop.
+ read_buf_unused_offset_ = end_of_header_offset;
+
+ if (response_->headers->response_code() / 100 == 1) {
+ // After processing a 1xx response, the caller will ask for the next
+ // header, so reset state to support that. We don't just skip these
+ // completely because 1xx codes aren't acceptable when establishing a
+ // tunnel.
+ io_state_ = STATE_REQUEST_SENT;
+ response_header_start_offset_ = -1;
+ } else {
+ io_state_ = STATE_BODY_PENDING;
+ CalculateResponseBodySize();
+ // If the body is 0, the caller may not call ReadResponseBody, which
+ // is where any extra data is copied to read_buf_, so we move the
+ // data here and transition to DONE.
+ if (response_body_length_ == 0) {
+ io_state_ = STATE_DONE;
+ int extra_bytes = read_buf_->offset() - read_buf_unused_offset_;
+ if (extra_bytes) {
+ CHECK_GT(extra_bytes, 0);
+ memmove(read_buf_->StartOfBuffer(),
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_,
+ extra_bytes);
+ }
+ read_buf_->SetCapacity(extra_bytes);
+ read_buf_unused_offset_ = 0;
+ return OK;
+ }
+ }
+ }
+ return result;
+}
+
+int HttpStreamParser::DoReadBody() {
+ io_state_ = STATE_READ_BODY_COMPLETE;
+
+ // There may be some data left over from reading the response headers.
+ if (read_buf_->offset()) {
+ int available = read_buf_->offset() - read_buf_unused_offset_;
+ if (available) {
+ CHECK_GT(available, 0);
+ int bytes_from_buffer = std::min(available, user_read_buf_len_);
+ memcpy(user_read_buf_->data(),
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_,
+ bytes_from_buffer);
+ read_buf_unused_offset_ += bytes_from_buffer;
+ if (bytes_from_buffer == available) {
+ read_buf_->SetCapacity(0);
+ read_buf_unused_offset_ = 0;
+ }
+ return bytes_from_buffer;
+ } else {
+ read_buf_->SetCapacity(0);
+ read_buf_unused_offset_ = 0;
+ }
+ }
+
+ // Check to see if we're done reading.
+ if (IsResponseBodyComplete())
+ return 0;
+
+ DCHECK_EQ(0, read_buf_->offset());
+ return connection_->socket()
+ ->Read(user_read_buf_.get(), user_read_buf_len_, io_callback_);
+}
+
+int HttpStreamParser::DoReadBodyComplete(int result) {
+ // When the connection is closed, there are numerous ways to interpret it.
+ //
+ // - If a Content-Length header is present and the body contains exactly that
+ // number of bytes at connection close, the response is successful.
+ //
+ // - If a Content-Length header is present and the body contains fewer bytes
+ // than promised by the header at connection close, it may indicate that
+ // the connection was closed prematurely, or it may indicate that the
+ // server sent an invalid Content-Length header. Unfortunately, the invalid
+ // Content-Length header case does occur in practice and other browsers are
+ // tolerant of it. We choose to treat it as an error for now, but the
+ // download system treats it as a non-error, and URLRequestHttpJob also
+ // treats it as OK if the Content-Length is the post-decoded body content
+ // length.
+ //
+ // - If chunked encoding is used and the terminating chunk has been processed
+ // when the connection is closed, the response is successful.
+ //
+ // - If chunked encoding is used and the terminating chunk has not been
+ // processed when the connection is closed, it may indicate that the
+ // connection was closed prematurely or it may indicate that the server
+ // sent an invalid chunked encoding. We choose to treat it as
+ // an invalid chunked encoding.
+ //
+ // - If a Content-Length is not present and chunked encoding is not used,
+ // connection close is the only way to signal that the response is
+ // complete. Unfortunately, this also means that there is no way to detect
+ // early close of a connection. No error is returned.
+ if (result == 0 && !IsResponseBodyComplete() && CanFindEndOfResponse()) {
+ if (chunked_decoder_.get())
+ result = ERR_INCOMPLETE_CHUNKED_ENCODING;
+ else
+ result = ERR_CONTENT_LENGTH_MISMATCH;
+ }
+
+ // Filter incoming data if appropriate. FilterBuf may return an error.
+ if (result > 0 && chunked_decoder_.get()) {
+ result = chunked_decoder_->FilterBuf(user_read_buf_->data(), result);
+ if (result == 0 && !chunked_decoder_->reached_eof()) {
+ // Don't signal completion of the Read call yet or else it'll look like
+ // we received end-of-file. Wait for more data.
+ io_state_ = STATE_READ_BODY;
+ return OK;
+ }
+ }
+
+ if (result > 0)
+ response_body_read_ += result;
+
+ if (result <= 0 || IsResponseBodyComplete()) {
+ io_state_ = STATE_DONE;
+
+ // Save the overflow data, which can be in two places. There may be
+ // some left over in |user_read_buf_|, plus there may be more
+ // in |read_buf_|. But the part left over in |user_read_buf_| must have
+ // come from the |read_buf_|, so there's room to put it back at the
+ // start first.
+ int additional_save_amount = read_buf_->offset() - read_buf_unused_offset_;
+ int save_amount = 0;
+ if (chunked_decoder_.get()) {
+ save_amount = chunked_decoder_->bytes_after_eof();
+ } else if (response_body_length_ >= 0) {
+ int64 extra_data_read = response_body_read_ - response_body_length_;
+ if (extra_data_read > 0) {
+ save_amount = static_cast<int>(extra_data_read);
+ if (result > 0)
+ result -= save_amount;
+ }
+ }
+
+ CHECK_LE(save_amount + additional_save_amount, kMaxBufSize);
+ if (read_buf_->capacity() < save_amount + additional_save_amount) {
+ read_buf_->SetCapacity(save_amount + additional_save_amount);
+ }
+
+ if (save_amount) {
+ memcpy(read_buf_->StartOfBuffer(), user_read_buf_->data() + result,
+ save_amount);
+ }
+ read_buf_->set_offset(save_amount);
+ if (additional_save_amount) {
+ memmove(read_buf_->data(),
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_,
+ additional_save_amount);
+ read_buf_->set_offset(save_amount + additional_save_amount);
+ }
+ read_buf_unused_offset_ = 0;
+ } else {
+ io_state_ = STATE_BODY_PENDING;
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ }
+
+ return result;
+}
+
+int HttpStreamParser::ParseResponseHeaders() {
+ int end_offset = -1;
+
+ // Look for the start of the status line, if it hasn't been found yet.
+ if (response_header_start_offset_ < 0) {
+ response_header_start_offset_ = HttpUtil::LocateStartOfStatusLine(
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_,
+ read_buf_->offset() - read_buf_unused_offset_);
+ }
+
+ if (response_header_start_offset_ >= 0) {
+ end_offset = HttpUtil::LocateEndOfHeaders(
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_,
+ read_buf_->offset() - read_buf_unused_offset_,
+ response_header_start_offset_);
+ } else if (read_buf_->offset() - read_buf_unused_offset_ >= 8) {
+ // Enough data to decide that this is an HTTP/0.9 response.
+ // 8 bytes = (4 bytes of junk) + "http".length()
+ end_offset = 0;
+ }
+
+ if (end_offset == -1)
+ return -1;
+
+ int rv = DoParseResponseHeaders(end_offset);
+ if (rv < 0)
+ return rv;
+ return end_offset + read_buf_unused_offset_;
+}
+
+int HttpStreamParser::DoParseResponseHeaders(int end_offset) {
+ scoped_refptr<HttpResponseHeaders> headers;
+ if (response_header_start_offset_ >= 0) {
+ headers = new HttpResponseHeaders(HttpUtil::AssembleRawHeaders(
+ read_buf_->StartOfBuffer() + read_buf_unused_offset_, end_offset));
+ } else {
+ // Enough data was read -- there is no status line.
+ headers = new HttpResponseHeaders(std::string("HTTP/0.9 200 OK"));
+ }
+
+ // Check for multiple Content-Length headers with no Transfer-Encoding header.
+ // If they exist, and have distinct values, it's a potential response
+ // smuggling attack.
+ if (!headers->HasHeader("Transfer-Encoding")) {
+ if (HeadersContainMultipleCopiesOfField(*headers.get(), "Content-Length"))
+ return ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH;
+ }
+
+ // Check for multiple Content-Disposition or Location headers. If they exist,
+ // it's also a potential response smuggling attack.
+ if (HeadersContainMultipleCopiesOfField(*headers.get(),
+ "Content-Disposition"))
+ return ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION;
+ if (HeadersContainMultipleCopiesOfField(*headers.get(), "Location"))
+ return ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION;
+
+ response_->headers = headers;
+ response_->connection_info = HttpResponseInfo::CONNECTION_INFO_HTTP1;
+ response_->vary_data.Init(*request_, *response_->headers.get());
+ DVLOG(1) << __FUNCTION__ << "()"
+ << " content_length = \"" << response_->headers->GetContentLength()
+ << "\n\""
+ << " headers = \""
+ << GetResponseHeaderLines(*response_->headers.get()) << "\"";
+ return OK;
+}
+
+void HttpStreamParser::CalculateResponseBodySize() {
+ // Figure how to determine EOF:
+
+ // For certain responses, we know the content length is always 0. From
+ // RFC 2616 Section 4.3 Message Body:
+ //
+ // For response messages, whether or not a message-body is included with
+ // a message is dependent on both the request method and the response
+ // status code (section 6.1.1). All responses to the HEAD request method
+ // MUST NOT include a message-body, even though the presence of entity-
+ // header fields might lead one to believe they do. All 1xx
+ // (informational), 204 (no content), and 304 (not modified) responses
+ // MUST NOT include a message-body. All other responses do include a
+ // message-body, although it MAY be of zero length.
+ switch (response_->headers->response_code()) {
+ // Note that 1xx was already handled earlier.
+ case 204: // No Content
+ case 205: // Reset Content
+ case 304: // Not Modified
+ response_body_length_ = 0;
+ break;
+ }
+ if (request_->method == "HEAD")
+ response_body_length_ = 0;
+
+ if (response_body_length_ == -1) {
+ // "Transfer-Encoding: chunked" trumps "Content-Length: N"
+ if (response_->headers->IsChunkEncoded()) {
+ chunked_decoder_.reset(new HttpChunkedDecoder());
+ } else {
+ response_body_length_ = response_->headers->GetContentLength();
+ // If response_body_length_ is still -1, then we have to wait
+ // for the server to close the connection.
+ }
+ }
+}
+
+UploadProgress HttpStreamParser::GetUploadProgress() const {
+ if (!request_->upload_data_stream)
+ return UploadProgress();
+
+ return UploadProgress(request_->upload_data_stream->position(),
+ request_->upload_data_stream->size());
+}
+
+HttpResponseInfo* HttpStreamParser::GetResponseInfo() {
+ return response_;
+}
+
+bool HttpStreamParser::IsResponseBodyComplete() const {
+ if (chunked_decoder_.get())
+ return chunked_decoder_->reached_eof();
+ if (response_body_length_ != -1)
+ return response_body_read_ >= response_body_length_;
+
+ return false; // Must read to EOF.
+}
+
+bool HttpStreamParser::CanFindEndOfResponse() const {
+ return chunked_decoder_.get() || response_body_length_ >= 0;
+}
+
+bool HttpStreamParser::IsMoreDataBuffered() const {
+ return read_buf_->offset() > read_buf_unused_offset_;
+}
+
+bool HttpStreamParser::IsConnectionReused() const {
+ ClientSocketHandle::SocketReuseType reuse_type = connection_->reuse_type();
+ return connection_->is_reused() ||
+ reuse_type == ClientSocketHandle::UNUSED_IDLE;
+}
+
+void HttpStreamParser::SetConnectionReused() {
+ connection_->set_is_reused(true);
+}
+
+bool HttpStreamParser::IsConnectionReusable() const {
+ return connection_->socket() && connection_->socket()->IsConnectedAndIdle();
+}
+
+void HttpStreamParser::GetSSLInfo(SSLInfo* ssl_info) {
+ if (request_->url.SchemeIsSecure() && connection_->socket()) {
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLInfo(ssl_info);
+ }
+}
+
+void HttpStreamParser::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ if (request_->url.SchemeIsSecure() && connection_->socket()) {
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLCertRequestInfo(cert_request_info);
+ }
+}
+
+int HttpStreamParser::EncodeChunk(const base::StringPiece& payload,
+ char* output,
+ size_t output_size) {
+ if (output_size < payload.size() + kChunkHeaderFooterSize)
+ return ERR_INVALID_ARGUMENT;
+
+ char* cursor = output;
+ // Add the header.
+ const int num_chars = base::snprintf(output, output_size,
+ "%X\r\n",
+ static_cast<int>(payload.size()));
+ cursor += num_chars;
+ // Add the payload if any.
+ if (payload.size() > 0) {
+ memcpy(cursor, payload.data(), payload.size());
+ cursor += payload.size();
+ }
+ // Add the trailing CRLF.
+ memcpy(cursor, "\r\n", 2);
+ cursor += 2;
+
+ return cursor - output;
+}
+
+// static
+bool HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ const std::string& request_headers,
+ const UploadDataStream* request_body) {
+ if (request_body != NULL &&
+ // IsInMemory() ensures that the request body is not chunked.
+ request_body->IsInMemory() &&
+ request_body->size() > 0) {
+ size_t merged_size = request_headers.size() + request_body->size();
+ if (merged_size <= kMaxMergedHeaderAndBodySize)
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_stream_parser.h b/chromium/net/http/http_stream_parser.h
new file mode 100644
index 00000000000..a41e393b485
--- /dev/null
+++ b/chromium/net/http/http_stream_parser.h
@@ -0,0 +1,238 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_STREAM_PARSER_H_
+#define NET_HTTP_HTTP_STREAM_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/upload_progress.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class DrainableIOBuffer;
+class GrowableIOBuffer;
+class HttpChunkedDecoder;
+struct HttpRequestInfo;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class IOBuffer;
+class IOBufferWithSize;
+class SSLCertRequestInfo;
+class SSLInfo;
+class UploadDataStream;
+
+class NET_EXPORT_PRIVATE HttpStreamParser {
+ public:
+ // Any data in |read_buffer| will be used before reading from the socket
+ // and any data left over after parsing the stream will be put into
+ // |read_buffer|. The left over data will start at offset 0 and the
+ // buffer's offset will be set to the first free byte. |read_buffer| may
+ // have its capacity changed.
+ HttpStreamParser(ClientSocketHandle* connection,
+ const HttpRequestInfo* request,
+ GrowableIOBuffer* read_buffer,
+ const BoundNetLog& net_log);
+ virtual ~HttpStreamParser();
+
+ // These functions implement the interface described in HttpStream with
+ // some additional functionality
+ int SendRequest(const std::string& request_line,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback);
+
+ int ReadResponseHeaders(const CompletionCallback& callback);
+
+ int ReadResponseBody(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ void Close(bool not_reusable);
+
+ // Returns the progress of uploading. When data is chunked, size is set to
+ // zero, but position will not be.
+ UploadProgress GetUploadProgress() const;
+
+ HttpResponseInfo* GetResponseInfo();
+
+ bool IsResponseBodyComplete() const;
+
+ bool CanFindEndOfResponse() const;
+
+ bool IsMoreDataBuffered() const;
+
+ bool IsConnectionReused() const;
+
+ void SetConnectionReused();
+
+ bool IsConnectionReusable() const;
+
+ void GetSSLInfo(SSLInfo* ssl_info);
+
+ void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
+
+ // Encodes the given |payload| in the chunked format to |output|.
+ // Returns the number of bytes written to |output|. |output_size| should
+ // be large enough to store the encoded chunk, which is payload.size() +
+ // kChunkHeaderFooterSize. Returns ERR_INVALID_ARGUMENT if |output_size|
+ // is not large enough.
+ //
+ // The output will look like: "HEX\r\n[payload]\r\n"
+ // where HEX is a length in hexdecimal (without the "0x" prefix).
+ static int EncodeChunk(const base::StringPiece& payload,
+ char* output,
+ size_t output_size);
+
+ // Returns true if request headers and body should be merged (i.e. the
+ // sum is small enough and the body is in memory, and not chunked).
+ static bool ShouldMergeRequestHeadersAndBody(
+ const std::string& request_headers,
+ const UploadDataStream* request_body);
+
+ // The number of extra bytes required to encode a chunk.
+ static const size_t kChunkHeaderFooterSize;
+
+ private:
+ class SeekableIOBuffer;
+
+ // FOO_COMPLETE states implement the second half of potentially asynchronous
+ // operations and don't necessarily mean that FOO is complete.
+ enum State {
+ STATE_NONE,
+ STATE_SENDING_HEADERS,
+ // If the request comes with a body, either of the following two
+ // states will be executed, depending on whether the body is chunked
+ // or not.
+ STATE_SENDING_BODY,
+ STATE_SEND_REQUEST_READING_BODY,
+ STATE_REQUEST_SENT,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_BODY_PENDING,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_DONE
+ };
+
+ // The number of bytes by which the header buffer is grown when it reaches
+ // capacity.
+ static const int kHeaderBufInitialSize = 4 * 1024; // 4K
+
+ // |kMaxHeaderBufSize| is the number of bytes that the response headers can
+ // grow to. If the body start is not found within this range of the
+ // response, the transaction will fail with ERR_RESPONSE_HEADERS_TOO_BIG.
+ // Note: |kMaxHeaderBufSize| should be a multiple of |kHeaderBufInitialSize|.
+ static const int kMaxHeaderBufSize = kHeaderBufInitialSize * 64; // 256K
+
+ // The maximum sane buffer size.
+ static const int kMaxBufSize = 2 * 1024 * 1024; // 2M
+
+ // Handle callbacks.
+ void OnIOComplete(int result);
+
+ // Try to make progress sending/receiving the request/response.
+ int DoLoop(int result);
+
+ // The implementations of each state of the state machine.
+ int DoSendHeaders(int result);
+ int DoSendBody(int result);
+ int DoSendRequestReadingBody(int result);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoReadBody();
+ int DoReadBodyComplete(int result);
+
+ // Examines |read_buf_| to find the start and end of the headers. If they are
+ // found, parse them with DoParseResponseHeaders(). Return the offset for
+ // the end of the headers, or -1 if the complete headers were not found, or
+ // with a net::Error if we encountered an error during parsing.
+ int ParseResponseHeaders();
+
+ // Parse the headers into response_. Returns OK on success or a net::Error on
+ // failure.
+ int DoParseResponseHeaders(int end_of_header_offset);
+
+ // Examine the parsed headers to try to determine the response body size.
+ void CalculateResponseBodySize();
+
+ // Current state of the request.
+ State io_state_;
+
+ // The request to send.
+ const HttpRequestInfo* request_;
+
+ // The request header data.
+ scoped_refptr<DrainableIOBuffer> request_headers_;
+
+ // Temporary buffer for reading.
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+
+ // Offset of the first unused byte in |read_buf_|. May be nonzero due to
+ // a 1xx header, or body data in the same packet as header data.
+ int read_buf_unused_offset_;
+
+ // The amount beyond |read_buf_unused_offset_| where the status line starts;
+ // -1 if not found yet.
+ int response_header_start_offset_;
+
+ // The parsed response headers. Owned by the caller.
+ HttpResponseInfo* response_;
+
+ // Indicates the content length. If this value is less than zero
+ // (and chunked_decoder_ is null), then we must read until the server
+ // closes the connection.
+ int64 response_body_length_;
+
+ // Keep track of the number of response body bytes read so far.
+ int64 response_body_read_;
+
+ // Helper if the data is chunked.
+ scoped_ptr<HttpChunkedDecoder> chunked_decoder_;
+
+ // Where the caller wants the body data.
+ scoped_refptr<IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ // The callback to notify a user that their request or response is
+ // complete or there was an error
+ CompletionCallback callback_;
+
+ // In the client callback, the client can do anything, including
+ // destroying this class, so any pending callback must be issued
+ // after everything else is done. When it is time to issue the client
+ // callback, move it from |callback_| to |scheduled_callback_|.
+ CompletionCallback scheduled_callback_;
+
+ // The underlying socket.
+ ClientSocketHandle* const connection_;
+
+ BoundNetLog net_log_;
+
+ // Callback to be used when doing IO.
+ CompletionCallback io_callback_;
+
+ // Buffer used to read the request body from UploadDataStream.
+ scoped_refptr<SeekableIOBuffer> request_body_read_buf_;
+ // Buffer used to send the request body. This points the same buffer as
+ // |request_body_read_buf_| unless the data is chunked.
+ scoped_refptr<SeekableIOBuffer> request_body_send_buf_;
+ bool sent_last_chunk_;
+
+ base::WeakPtrFactory<HttpStreamParser> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpStreamParser);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_STREAM_PARSER_H_
diff --git a/chromium/net/http/http_stream_parser_unittest.cc b/chromium/net/http/http_stream_parser_unittest.cc
new file mode 100644
index 00000000000..84775945ec1
--- /dev/null
+++ b/chromium/net/http/http_stream_parser_unittest.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 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.
+
+#include "net/http/http_stream_parser.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+const size_t kOutputSize = 1024; // Just large enough for this test.
+// The number of bytes that can fit in a buffer of kOutputSize.
+const size_t kMaxPayloadSize =
+ kOutputSize - HttpStreamParser::kChunkHeaderFooterSize;
+
+// The empty payload is how the last chunk is encoded.
+TEST(HttpStreamParser, EncodeChunk_EmptyPayload) {
+ char output[kOutputSize];
+
+ const base::StringPiece kPayload = "";
+ const base::StringPiece kExpected = "0\r\n\r\n";
+ const int num_bytes_written =
+ HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
+ ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
+ EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
+}
+
+TEST(HttpStreamParser, EncodeChunk_ShortPayload) {
+ char output[kOutputSize];
+
+ const std::string kPayload("foo\x00\x11\x22", 6);
+ // 11 = payload size + sizeof("6") + CRLF x 2.
+ const std::string kExpected("6\r\nfoo\x00\x11\x22\r\n", 11);
+ const int num_bytes_written =
+ HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
+ ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
+ EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
+}
+
+TEST(HttpStreamParser, EncodeChunk_LargePayload) {
+ char output[kOutputSize];
+
+ const std::string kPayload(1000, '\xff'); // '\xff' x 1000.
+ // 3E8 = 1000 in hex.
+ const std::string kExpected = "3E8\r\n" + kPayload + "\r\n";
+ const int num_bytes_written =
+ HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
+ ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
+ EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
+}
+
+TEST(HttpStreamParser, EncodeChunk_FullPayload) {
+ char output[kOutputSize];
+
+ const std::string kPayload(kMaxPayloadSize, '\xff');
+ // 3F4 = 1012 in hex.
+ const std::string kExpected = "3F4\r\n" + kPayload + "\r\n";
+ const int num_bytes_written =
+ HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
+ ASSERT_EQ(kExpected.size(), static_cast<size_t>(num_bytes_written));
+ EXPECT_EQ(kExpected, base::StringPiece(output, num_bytes_written));
+}
+
+TEST(HttpStreamParser, EncodeChunk_TooLargePayload) {
+ char output[kOutputSize];
+
+ // The payload is one byte larger the output buffer size.
+ const std::string kPayload(kMaxPayloadSize + 1, '\xff');
+ const int num_bytes_written =
+ HttpStreamParser::EncodeChunk(kPayload, output, sizeof(output));
+ ASSERT_EQ(ERR_INVALID_ARGUMENT, num_bytes_written);
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_NoBody) {
+ // Shouldn't be merged if upload data is non-existent.
+ ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", NULL));
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_EmptyBody) {
+ ScopedVector<UploadElementReader> element_readers;
+ scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
+ ASSERT_EQ(OK, body->Init(CompletionCallback()));
+ // Shouldn't be merged if upload data is empty.
+ ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", body.get()));
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_ChunkedBody) {
+ const std::string payload = "123";
+ scoped_ptr<UploadDataStream> body(
+ new UploadDataStream(UploadDataStream::CHUNKED, 0));
+ body->AppendChunk(payload.data(), payload.size(), true);
+ ASSERT_EQ(OK, body->Init(CompletionCallback()));
+ // Shouldn't be merged if upload data carries chunked data.
+ ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", body.get()));
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_FileBody) {
+ ScopedVector<UploadElementReader> element_readers;
+
+ // Create an empty temporary file.
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath temp_file_path;
+ ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(),
+ &temp_file_path));
+
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ temp_file_path,
+ 0,
+ 0,
+ base::Time()));
+
+ scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING, body->Init(callback.callback()));
+ ASSERT_EQ(OK, callback.WaitForResult());
+ // Shouldn't be merged if upload data carries a file, as it's not in-memory.
+ ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", body.get()));
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_SmallBodyInMemory) {
+ ScopedVector<UploadElementReader> element_readers;
+ const std::string payload = "123";
+ element_readers.push_back(new UploadBytesElementReader(
+ payload.data(), payload.size()));
+
+ scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
+ ASSERT_EQ(OK, body->Init(CompletionCallback()));
+ // Yes, should be merged if the in-memory body is small here.
+ ASSERT_TRUE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", body.get()));
+}
+
+TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_LargeBodyInMemory) {
+ ScopedVector<UploadElementReader> element_readers;
+ const std::string payload(10000, 'a'); // 'a' x 10000.
+ element_readers.push_back(new UploadBytesElementReader(
+ payload.data(), payload.size()));
+
+ scoped_ptr<UploadDataStream> body(new UploadDataStream(&element_readers, 0));
+ ASSERT_EQ(OK, body->Init(CompletionCallback()));
+ // Shouldn't be merged if the in-memory body is large here.
+ ASSERT_FALSE(HttpStreamParser::ShouldMergeRequestHeadersAndBody(
+ "some header", body.get()));
+}
+
+// Test to ensure the HttpStreamParser state machine does not get confused
+// when sending a request with a chunked body, where chunks become available
+// asynchronously, over a socket where writes may also complete
+// asynchronously.
+// This is a regression test for http://crbug.com/132243
+TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) {
+ // The chunks that will be written in the request, as reflected in the
+ // MockWrites below.
+ static const char kChunk1[] = "Chunk 1";
+ static const char kChunk2[] = "Chunky 2";
+ static const char kChunk3[] = "Test 3";
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0,
+ "GET /one.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"),
+ MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"),
+ MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"),
+ MockWrite(ASYNC, 4, "0\r\n\r\n"),
+ };
+
+ // The size of the response body, as reflected in the Content-Length of the
+ // MockRead below.
+ static const int kBodySize = 8;
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 6, "Content-Length: 8\r\n\r\n"),
+ MockRead(ASYNC, 7, "one.html"),
+ MockRead(SYNCHRONOUS, 0, 8), // EOF
+ };
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+ upload_stream.AppendChunk(kChunk1, arraysize(kChunk1) - 1, false);
+ ASSERT_EQ(OK, upload_stream.Init(CompletionCallback()));
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+
+ scoped_ptr<DeterministicMockTCPClientSocket> transport(
+ new DeterministicMockTCPClientSocket(NULL, &data));
+ data.set_delegate(transport->AsWeakPtr());
+
+ TestCompletionCallback callback;
+ int rv = transport->Connect(callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(OK, rv);
+
+ scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
+ socket_handle->SetSocket(transport.PassAs<StreamSocket>());
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ request_info.url = GURL("http://localhost");
+ request_info.load_flags = LOAD_NORMAL;
+ request_info.upload_data_stream = &upload_stream;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(
+ socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Host", "localhost");
+ request_headers.SetHeader("Transfer-Encoding", "chunked");
+ request_headers.SetHeader("Connection", "keep-alive");
+
+ HttpResponseInfo response_info;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ rv = parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ // Complete the initial request write. Additionally, this should enqueue the
+ // first chunk.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Now append another chunk (while the first write is still pending), which
+ // should not confuse the state machine.
+ upload_stream.AppendChunk(kChunk2, arraysize(kChunk2) - 1, false);
+ ASSERT_FALSE(callback.have_result());
+
+ // Complete writing the first chunk, which should then enqueue the second
+ // chunk for writing and return, because it is set to complete
+ // asynchronously.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Complete writing the second chunk. However, because no chunks are
+ // available yet, no further writes should be called until a new chunk is
+ // added.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Add the final chunk. This will enqueue another write, but it will not
+ // complete due to the async nature.
+ upload_stream.AppendChunk(kChunk3, arraysize(kChunk3) - 1, true);
+ ASSERT_FALSE(callback.have_result());
+
+ // Finalize writing the last chunk, which will enqueue the trailer.
+ data.RunFor(1);
+ ASSERT_FALSE(callback.have_result());
+
+ // Finalize writing the trailer.
+ data.RunFor(1);
+ ASSERT_TRUE(callback.have_result());
+
+ // Warning: This will hang if the callback doesn't already have a result,
+ // due to the deterministic socket provider. Do not remove the above
+ // ASSERT_TRUE, which will avoid this hang.
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+
+ // Attempt to read the response status and the response headers.
+ rv = parser.ReadResponseHeaders(callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(2);
+
+ ASSERT_TRUE(callback.have_result());
+ rv = callback.WaitForResult();
+ ASSERT_GT(rv, 0);
+
+ // Finally, attempt to read the response body.
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize));
+ rv = parser.ReadResponseBody(
+ body_buffer.get(), kBodySize, callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(1);
+
+ ASSERT_TRUE(callback.have_result());
+ rv = callback.WaitForResult();
+ ASSERT_EQ(kBodySize, rv);
+}
+
+TEST(HttpStreamParser, TruncatedHeaders) {
+ MockRead truncated_status_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 20"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead truncated_after_status_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\n"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead truncated_in_header_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHead"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead truncated_after_header_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead truncated_after_final_newline_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n\r"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead not_truncated_reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Ok\r\nHeader: foo\r\n\r\n"),
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockRead* reads[] = {
+ truncated_status_reads,
+ truncated_after_status_reads,
+ truncated_in_header_reads,
+ truncated_after_header_reads,
+ truncated_after_final_newline_reads,
+ not_truncated_reads,
+ };
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n\r\n"),
+ };
+
+ enum {
+ HTTP = 0,
+ HTTPS,
+ NUM_PROTOCOLS,
+ };
+
+ for (size_t protocol = 0; protocol < NUM_PROTOCOLS; protocol++) {
+ SCOPED_TRACE(protocol);
+
+ for (size_t i = 0; i < arraysize(reads); i++) {
+ SCOPED_TRACE(i);
+ DeterministicSocketData data(reads[i], 2, writes, arraysize(writes));
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ data.SetStop(3);
+
+ scoped_ptr<DeterministicMockTCPClientSocket> transport(
+ new DeterministicMockTCPClientSocket(NULL, &data));
+ data.set_delegate(transport->AsWeakPtr());
+
+ TestCompletionCallback callback;
+ int rv = transport->Connect(callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(OK, rv);
+
+ scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle);
+ socket_handle->SetSocket(transport.PassAs<StreamSocket>());
+
+ HttpRequestInfo request_info;
+ request_info.method = "GET";
+ if (protocol == HTTP) {
+ request_info.url = GURL("http://localhost");
+ } else {
+ request_info.url = GURL("https://localhost");
+ }
+ request_info.load_flags = LOAD_NORMAL;
+
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer);
+ HttpStreamParser parser(
+ socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog());
+
+ HttpRequestHeaders request_headers;
+ HttpResponseInfo response_info;
+ rv = parser.SendRequest("GET / HTTP/1.1\r\n", request_headers,
+ &response_info, callback.callback());
+ ASSERT_EQ(OK, rv);
+
+ rv = parser.ReadResponseHeaders(callback.callback());
+ if (i == arraysize(reads) - 1) {
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(response_info.headers.get());
+ } else {
+ if (protocol == HTTP) {
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+ EXPECT_TRUE(response_info.headers.get());
+ } else {
+ EXPECT_EQ(ERR_RESPONSE_HEADERS_TRUNCATED, rv);
+ EXPECT_FALSE(response_info.headers.get());
+ }
+ }
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_transaction.h b/chromium/net/http/http_transaction.h
new file mode 100644
index 00000000000..ec3fc088a03
--- /dev/null
+++ b/chromium/net/http/http_transaction.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2011 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_HTTP_HTTP_TRANSACTION_H_
+#define NET_HTTP_HTTP_TRANSACTION_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_progress.h"
+
+namespace net {
+
+class AuthCredentials;
+class BoundNetLog;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+class HttpResponseInfo;
+class IOBuffer;
+struct LoadTimingInfo;
+class X509Certificate;
+
+// Represents a single HTTP transaction (i.e., a single request/response pair).
+// HTTP redirects are not followed and authentication challenges are not
+// answered. Cookies are assumed to be managed by the caller.
+class NET_EXPORT_PRIVATE HttpTransaction {
+ public:
+ // Stops any pending IO and destroys the transaction object.
+ virtual ~HttpTransaction() {}
+
+ // Starts the HTTP transaction (i.e., sends the HTTP request).
+ //
+ // Returns OK if the transaction could be started synchronously, which means
+ // that the request was served from the cache. ERR_IO_PENDING is returned to
+ // indicate that the CompletionCallback will be notified once response info is
+ // available or if an IO error occurs. Any other return value indicates that
+ // the transaction could not be started.
+ //
+ // Regardless of the return value, the caller is expected to keep the
+ // request_info object alive until Destroy is called on the transaction.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ virtual int Start(const HttpRequestInfo* request_info,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) = 0;
+
+ // Restarts the HTTP transaction, ignoring the last error. This call can
+ // only be made after a call to Start (or RestartIgnoringLastError) failed.
+ // Once Read has been called, this method cannot be called. This method is
+ // used, for example, to continue past various SSL related errors.
+ //
+ // Not all errors can be ignored using this method. See error code
+ // descriptions for details about errors that can be ignored.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ //
+ virtual int RestartIgnoringLastError(const CompletionCallback& callback) = 0;
+
+ // Restarts the HTTP transaction with a client certificate.
+ virtual int RestartWithCertificate(X509Certificate* client_cert,
+ const CompletionCallback& callback) = 0;
+
+ // Restarts the HTTP transaction with authentication credentials.
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
+ const CompletionCallback& callback) = 0;
+
+ // Returns true if auth is ready to be continued. Callers should check
+ // this value anytime Start() completes: if it is true, the transaction
+ // can be resumed with RestartWithAuth(L"", L"", callback) to resume
+ // the automatic auth exchange. This notification gives the caller a
+ // chance to process the response headers from all of the intermediate
+ // restarts needed for authentication.
+ virtual bool IsReadyToRestartForAuth() = 0;
+
+ // Once response info is available for the transaction, response data may be
+ // read by calling this method.
+ //
+ // Response data is copied into the given buffer and the number of bytes
+ // copied is returned. ERR_IO_PENDING is returned if response data is not
+ // yet available. The CompletionCallback is notified when the data copy
+ // completes, and it is passed the number of bytes that were successfully
+ // copied. Or, if a read error occurs, the CompletionCallback is notified of
+ // the error. Any other negative return value indicates that the transaction
+ // could not be read.
+ //
+ // NOTE: The transaction is not responsible for deleting the callback object.
+ // If the operation is not completed immediately, the transaction must acquire
+ // a reference to the provided buffer.
+ //
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Stops further caching of this request by the HTTP cache, if there is any.
+ virtual void StopCaching() = 0;
+
+ // Gets the full request headers sent to the server. This is guaranteed to
+ // work only if Start returns success and the underlying transaction supports
+ // it. (Right now, this is only network transactions, not cache ones.)
+ //
+ // Returns true and overwrites headers if it can get the request headers;
+ // otherwise, returns false and does not modify headers.
+ virtual bool GetFullRequestHeaders(HttpRequestHeaders* headers) const = 0;
+
+ // Called to tell the transaction that we have successfully reached the end
+ // of the stream. This is equivalent to performing an extra Read() at the end
+ // that should return 0 bytes. This method should not be called if the
+ // transaction is busy processing a previous operation (like a pending Read).
+ virtual void DoneReading() = 0;
+
+ // Returns the response info for this transaction or NULL if the response
+ // info is not available.
+ virtual const HttpResponseInfo* GetResponseInfo() const = 0;
+
+ // Returns the load state for this transaction.
+ virtual LoadState GetLoadState() const = 0;
+
+ // Returns the upload progress in bytes. If there is no upload data,
+ // zero will be returned. This does not include the request headers.
+ virtual UploadProgress GetUploadProgress() const = 0;
+
+ // Populates all of load timing, except for request start times and receive
+ // headers time.
+ // |load_timing_info| must have all null times when called. Returns false and
+ // does not modify |load_timing_info| if there's no timing information to
+ // provide.
+ virtual bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const = 0;
+
+ // Called when the priority of the parent job changes.
+ virtual void SetPriority(RequestPriority priority) = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_TRANSACTION_H_
diff --git a/chromium/net/http/http_transaction_delegate.h b/chromium/net/http/http_transaction_delegate.h
new file mode 100644
index 00000000000..18493733bff
--- /dev/null
+++ b/chromium/net/http/http_transaction_delegate.h
@@ -0,0 +1,26 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_DELEGATE_H_
+#define NET_HTTP_HTTP_TRANSACTION_DELEGATE_H_
+
+namespace net {
+
+// Delegate class receiving notifications when cache or network actions start
+// and finish, i.e. when the object starts and finishes waiting on an
+// underlying cache or the network. The owner of a HttpTransaction can use
+// this to register a delegate to receive notifications when these events
+// happen.
+class HttpTransactionDelegate {
+ public:
+ virtual ~HttpTransactionDelegate() {}
+ virtual void OnCacheActionStart() = 0;
+ virtual void OnCacheActionFinish() = 0;
+ virtual void OnNetworkActionStart() = 0;
+ virtual void OnNetworkActionFinish() = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_TRANSACTION_DELEGATE_H_
diff --git a/chromium/net/http/http_transaction_factory.h b/chromium/net/http/http_transaction_factory.h
new file mode 100644
index 00000000000..d9870be914b
--- /dev/null
+++ b/chromium/net/http/http_transaction_factory.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_FACTORY_H__
+#define NET_HTTP_HTTP_TRANSACTION_FACTORY_H__
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+
+namespace net {
+
+class HttpCache;
+class HttpNetworkSession;
+class HttpTransaction;
+class HttpTransactionDelegate;
+
+// An interface to a class that can create HttpTransaction objects.
+class NET_EXPORT HttpTransactionFactory {
+ public:
+ virtual ~HttpTransactionFactory() {}
+
+ // Creates a HttpTransaction object. On success, saves the new
+ // transaction to |*trans| and returns OK.
+ virtual int CreateTransaction(RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) = 0;
+
+ // Returns the associated cache if any (may be NULL).
+ virtual HttpCache* GetCache() = 0;
+
+ // Returns the associated HttpNetworkSession used by new transactions.
+ virtual HttpNetworkSession* GetSession() = 0;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_TRANSACTION_FACTORY_H__
diff --git a/chromium/net/http/http_transaction_unittest.cc b/chromium/net/http/http_transaction_unittest.cc
new file mode 100644
index 00000000000..9aa81cf608e
--- /dev/null
+++ b/chromium/net/http/http_transaction_unittest.cc
@@ -0,0 +1,445 @@
+// Copyright (c) 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.
+
+#include "net/http/http_transaction_unittest.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+typedef base::hash_map<std::string, const MockTransaction*> MockTransactionMap;
+static MockTransactionMap mock_transactions;
+} // namespace
+
+//-----------------------------------------------------------------------------
+// mock transaction data
+
+const MockTransaction kSimpleGET_Transaction = {
+ "http://www.google.com/",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0,
+ net::OK
+};
+
+const MockTransaction kSimplePOST_Transaction = {
+ "http://bugdatabase.com/edit",
+ "POST",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0,
+ net::OK
+};
+
+const MockTransaction kTypicalGET_Transaction = {
+ "http://www.example.com/~foo/bar.html",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+ "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0,
+ net::OK
+};
+
+const MockTransaction kETagGET_Transaction = {
+ "http://www.google.com/foopy",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n"
+ "Etag: \"foopy\"\n",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0,
+ net::OK
+};
+
+const MockTransaction kRangeGET_Transaction = {
+ "http://www.google.com/",
+ "GET",
+ base::Time(),
+ "Range: 0-100\r\n",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n",
+ base::Time(),
+ "<html><body>Google Blah Blah</body></html>",
+ TEST_MODE_NORMAL,
+ NULL,
+ 0,
+ net::OK
+};
+
+static const MockTransaction* const kBuiltinMockTransactions[] = {
+ &kSimpleGET_Transaction,
+ &kSimplePOST_Transaction,
+ &kTypicalGET_Transaction,
+ &kETagGET_Transaction,
+ &kRangeGET_Transaction
+};
+
+const MockTransaction* FindMockTransaction(const GURL& url) {
+ // look for overrides:
+ MockTransactionMap::const_iterator it = mock_transactions.find(url.spec());
+ if (it != mock_transactions.end())
+ return it->second;
+
+ // look for builtins:
+ for (size_t i = 0; i < arraysize(kBuiltinMockTransactions); ++i) {
+ if (url == GURL(kBuiltinMockTransactions[i]->url))
+ return kBuiltinMockTransactions[i];
+ }
+ return NULL;
+}
+
+void AddMockTransaction(const MockTransaction* trans) {
+ mock_transactions[GURL(trans->url).spec()] = trans;
+}
+
+void RemoveMockTransaction(const MockTransaction* trans) {
+ mock_transactions.erase(GURL(trans->url).spec());
+}
+
+MockHttpRequest::MockHttpRequest(const MockTransaction& t) {
+ url = GURL(t.url);
+ method = t.method;
+ extra_headers.AddHeadersFromString(t.request_headers);
+ load_flags = t.load_flags;
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+int TestTransactionConsumer::quit_counter_ = 0;
+
+TestTransactionConsumer::TestTransactionConsumer(
+ net::RequestPriority priority,
+ net::HttpTransactionFactory* factory)
+ : state_(IDLE), error_(net::OK) {
+ // Disregard the error code.
+ factory->CreateTransaction(priority, &trans_, NULL);
+ ++quit_counter_;
+}
+
+TestTransactionConsumer::~TestTransactionConsumer() {
+}
+
+void TestTransactionConsumer::Start(const net::HttpRequestInfo* request,
+ const net::BoundNetLog& net_log) {
+ state_ = STARTING;
+ int result = trans_->Start(
+ request, base::Bind(&TestTransactionConsumer::OnIOComplete,
+ base::Unretained(this)), net_log);
+ if (result != net::ERR_IO_PENDING)
+ DidStart(result);
+}
+
+void TestTransactionConsumer::DidStart(int result) {
+ if (result != net::OK) {
+ DidFinish(result);
+ } else {
+ Read();
+ }
+}
+
+void TestTransactionConsumer::DidRead(int result) {
+ if (result <= 0) {
+ DidFinish(result);
+ } else {
+ content_.append(read_buf_->data(), result);
+ Read();
+ }
+}
+
+void TestTransactionConsumer::DidFinish(int result) {
+ state_ = DONE;
+ error_ = result;
+ if (--quit_counter_ == 0)
+ base::MessageLoop::current()->Quit();
+}
+
+void TestTransactionConsumer::Read() {
+ state_ = READING;
+ read_buf_ = new net::IOBuffer(1024);
+ int result = trans_->Read(read_buf_.get(),
+ 1024,
+ base::Bind(&TestTransactionConsumer::OnIOComplete,
+ base::Unretained(this)));
+ if (result != net::ERR_IO_PENDING)
+ DidRead(result);
+}
+
+void TestTransactionConsumer::OnIOComplete(int result) {
+ switch (state_) {
+ case STARTING:
+ DidStart(result);
+ break;
+ case READING:
+ DidRead(result);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+MockNetworkTransaction::MockNetworkTransaction(
+ net::RequestPriority priority,
+ MockNetworkLayer* factory)
+ : weak_factory_(this),
+ data_cursor_(0),
+ priority_(priority),
+ transaction_factory_(factory->AsWeakPtr()),
+ socket_log_id_(net::NetLog::Source::kInvalidId) {
+}
+
+MockNetworkTransaction::~MockNetworkTransaction() {}
+
+int MockNetworkTransaction::Start(const net::HttpRequestInfo* request,
+ const net::CompletionCallback& callback,
+ const net::BoundNetLog& net_log) {
+ const MockTransaction* t = FindMockTransaction(request->url);
+ if (!t)
+ return net::ERR_FAILED;
+
+ test_mode_ = t->test_mode;
+
+ // Return immediately if we're returning an error.
+ if (net::OK != t->return_code) {
+ if (test_mode_ & TEST_MODE_SYNC_NET_START)
+ return t->return_code;
+ CallbackLater(callback, t->return_code);
+ return net::ERR_IO_PENDING;
+ }
+
+ std::string resp_status = t->status;
+ std::string resp_headers = t->response_headers;
+ std::string resp_data = t->data;
+ if (t->handler)
+ (t->handler)(request, &resp_status, &resp_headers, &resp_data);
+
+ std::string header_data = base::StringPrintf(
+ "%s\n%s\n", resp_status.c_str(), resp_headers.c_str());
+ std::replace(header_data.begin(), header_data.end(), '\n', '\0');
+
+ response_.request_time = base::Time::Now();
+ if (!t->request_time.is_null())
+ response_.request_time = t->request_time;
+
+ response_.was_cached = false;
+ response_.network_accessed = true;
+
+ response_.response_time = base::Time::Now();
+ if (!t->response_time.is_null())
+ response_.response_time = t->response_time;
+
+ response_.headers = new net::HttpResponseHeaders(header_data);
+ response_.vary_data.Init(*request, *response_.headers.get());
+ response_.ssl_info.cert_status = t->cert_status;
+ data_ = resp_data;
+
+ if (net_log.net_log())
+ socket_log_id_ = net_log.net_log()->NextID();
+
+ if (test_mode_ & TEST_MODE_SYNC_NET_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
+}
+
+int MockNetworkTransaction::RestartIgnoringLastError(
+ const net::CompletionCallback& callback) {
+ return net::ERR_FAILED;
+}
+
+int MockNetworkTransaction::RestartWithCertificate(
+ net::X509Certificate* client_cert,
+ const net::CompletionCallback& callback) {
+ return net::ERR_FAILED;
+}
+
+int MockNetworkTransaction::RestartWithAuth(
+ const net::AuthCredentials& credentials,
+ const net::CompletionCallback& callback) {
+ return net::ERR_FAILED;
+}
+
+bool MockNetworkTransaction::IsReadyToRestartForAuth() {
+ return false;
+}
+
+int MockNetworkTransaction::Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ int data_len = static_cast<int>(data_.size());
+ int num = std::min(buf_len, data_len - data_cursor_);
+ if (num) {
+ memcpy(buf->data(), data_.data() + data_cursor_, num);
+ data_cursor_ += num;
+ }
+ if (test_mode_ & TEST_MODE_SYNC_NET_READ)
+ return num;
+
+ CallbackLater(callback, num);
+ return net::ERR_IO_PENDING;
+}
+
+void MockNetworkTransaction::StopCaching() {}
+
+bool MockNetworkTransaction::GetFullRequestHeaders(
+ net::HttpRequestHeaders* headers) const {
+ return false;
+}
+
+void MockNetworkTransaction::DoneReading() {
+ if (transaction_factory_.get())
+ transaction_factory_->TransactionDoneReading();
+}
+
+const net::HttpResponseInfo* MockNetworkTransaction::GetResponseInfo() const {
+ return &response_;
+}
+
+net::LoadState MockNetworkTransaction::GetLoadState() const {
+ if (data_cursor_)
+ return net::LOAD_STATE_READING_RESPONSE;
+ return net::LOAD_STATE_IDLE;
+}
+
+net::UploadProgress MockNetworkTransaction::GetUploadProgress() const {
+ return net::UploadProgress();
+}
+
+bool MockNetworkTransaction::GetLoadTimingInfo(
+ net::LoadTimingInfo* load_timing_info) const {
+ if (socket_log_id_ != net::NetLog::Source::kInvalidId) {
+ // The minimal set of times for a request that gets a response, assuming it
+ // gets a new socket.
+ load_timing_info->socket_reused = false;
+ load_timing_info->socket_log_id = socket_log_id_;
+ load_timing_info->connect_timing.connect_start = base::TimeTicks::Now();
+ load_timing_info->connect_timing.connect_end = base::TimeTicks::Now();
+ load_timing_info->send_start = base::TimeTicks::Now();
+ load_timing_info->send_end = base::TimeTicks::Now();
+ } else {
+ // If there's no valid socket ID, just use the generic socket reused values.
+ // No tests currently depend on this, just should not match the values set
+ // by a cache hit.
+ load_timing_info->socket_reused = true;
+ load_timing_info->send_start = base::TimeTicks::Now();
+ load_timing_info->send_end = base::TimeTicks::Now();
+ }
+ return true;
+}
+
+void MockNetworkTransaction::SetPriority(net::RequestPriority priority) {
+ priority_ = priority;
+}
+
+void MockNetworkTransaction::CallbackLater(
+ const net::CompletionCallback& callback, int result) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&MockNetworkTransaction::RunCallback,
+ weak_factory_.GetWeakPtr(), callback, result));
+}
+
+void MockNetworkTransaction::RunCallback(
+ const net::CompletionCallback& callback, int result) {
+ callback.Run(result);
+}
+
+MockNetworkLayer::MockNetworkLayer()
+ : transaction_count_(0),
+ done_reading_called_(false),
+ last_create_transaction_priority_(net::DEFAULT_PRIORITY) {}
+
+MockNetworkLayer::~MockNetworkLayer() {}
+
+void MockNetworkLayer::TransactionDoneReading() {
+ done_reading_called_ = true;
+}
+
+int MockNetworkLayer::CreateTransaction(
+ net::RequestPriority priority,
+ scoped_ptr<net::HttpTransaction>* trans,
+ net::HttpTransactionDelegate* delegate) {
+ transaction_count_++;
+ last_create_transaction_priority_ = priority;
+ scoped_ptr<MockNetworkTransaction> mock_transaction(
+ new MockNetworkTransaction(priority, this));
+ last_transaction_ = mock_transaction->AsWeakPtr();
+ *trans = mock_transaction.Pass();
+ return net::OK;
+}
+
+net::HttpCache* MockNetworkLayer::GetCache() {
+ return NULL;
+}
+
+net::HttpNetworkSession* MockNetworkLayer::GetSession() {
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// helpers
+
+int ReadTransaction(net::HttpTransaction* trans, std::string* result) {
+ int rv;
+
+ net::TestCompletionCallback callback;
+
+ std::string content;
+ do {
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(256));
+ rv = trans->Read(buf.get(), 256, callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ if (rv > 0)
+ content.append(buf->data(), rv);
+ else if (rv < 0)
+ return rv;
+ } while (rv > 0);
+
+ result->swap(content);
+ return net::OK;
+}
diff --git a/chromium/net/http/http_transaction_unittest.h b/chromium/net/http/http_transaction_unittest.h
new file mode 100644
index 00000000000..407fc9fff9a
--- /dev/null
+++ b/chromium/net/http/http_transaction_unittest.h
@@ -0,0 +1,280 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
+#define NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
+
+#include "net/http/http_transaction.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+
+namespace net {
+class HttpRequestHeaders;
+class IOBuffer;
+}
+
+//-----------------------------------------------------------------------------
+// mock transaction data
+
+// these flags may be combined to form the test_mode field
+enum {
+ TEST_MODE_NORMAL = 0,
+ TEST_MODE_SYNC_NET_START = 1 << 0,
+ TEST_MODE_SYNC_NET_READ = 1 << 1,
+ TEST_MODE_SYNC_CACHE_START = 1 << 2,
+ TEST_MODE_SYNC_CACHE_READ = 1 << 3,
+ TEST_MODE_SYNC_CACHE_WRITE = 1 << 4,
+ TEST_MODE_SYNC_ALL = (TEST_MODE_SYNC_NET_START | TEST_MODE_SYNC_NET_READ |
+ TEST_MODE_SYNC_CACHE_START | TEST_MODE_SYNC_CACHE_READ |
+ TEST_MODE_SYNC_CACHE_WRITE)
+};
+
+typedef void (*MockTransactionHandler)(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data);
+
+struct MockTransaction {
+ const char* url;
+ const char* method;
+ // If |request_time| is unspecified, the current time will be used.
+ base::Time request_time;
+ const char* request_headers;
+ int load_flags;
+ const char* status;
+ const char* response_headers;
+ // If |response_time| is unspecified, the current time will be used.
+ base::Time response_time;
+ const char* data;
+ int test_mode;
+ MockTransactionHandler handler;
+ net::CertStatus cert_status;
+ // Value returned by MockNetworkTransaction::Start (potentially
+ // asynchronously if |!(test_mode & TEST_MODE_SYNC_NET_START)|.)
+ net::Error return_code;
+};
+
+extern const MockTransaction kSimpleGET_Transaction;
+extern const MockTransaction kSimplePOST_Transaction;
+extern const MockTransaction kTypicalGET_Transaction;
+extern const MockTransaction kETagGET_Transaction;
+extern const MockTransaction kRangeGET_Transaction;
+
+// returns the mock transaction for the given URL
+const MockTransaction* FindMockTransaction(const GURL& url);
+
+// Add/Remove a mock transaction that can be accessed via FindMockTransaction.
+// There can be only one MockTransaction associated with a given URL.
+void AddMockTransaction(const MockTransaction* trans);
+void RemoveMockTransaction(const MockTransaction* trans);
+
+struct ScopedMockTransaction : MockTransaction {
+ ScopedMockTransaction() {
+ AddMockTransaction(this);
+ }
+ explicit ScopedMockTransaction(const MockTransaction& t)
+ : MockTransaction(t) {
+ AddMockTransaction(this);
+ }
+ ~ScopedMockTransaction() {
+ RemoveMockTransaction(this);
+ }
+};
+
+//-----------------------------------------------------------------------------
+// mock http request
+
+class MockHttpRequest : public net::HttpRequestInfo {
+ public:
+ explicit MockHttpRequest(const MockTransaction& t);
+};
+
+//-----------------------------------------------------------------------------
+// use this class to test completely consuming a transaction
+
+class TestTransactionConsumer {
+ public:
+ TestTransactionConsumer(net::RequestPriority priority,
+ net::HttpTransactionFactory* factory);
+ virtual ~TestTransactionConsumer();
+
+ void Start(const net::HttpRequestInfo* request,
+ const net::BoundNetLog& net_log);
+
+ bool is_done() const { return state_ == DONE; }
+ int error() const { return error_; }
+
+ const net::HttpResponseInfo* response_info() const {
+ return trans_->GetResponseInfo();
+ }
+ const std::string& content() const { return content_; }
+
+ private:
+ enum State {
+ IDLE,
+ STARTING,
+ READING,
+ DONE
+ };
+
+ void DidStart(int result);
+ void DidRead(int result);
+ void DidFinish(int result);
+ void Read();
+
+ void OnIOComplete(int result);
+
+ State state_;
+ scoped_ptr<net::HttpTransaction> trans_;
+ std::string content_;
+ scoped_refptr<net::IOBuffer> read_buf_;
+ int error_;
+
+ static int quit_counter_;
+};
+
+//-----------------------------------------------------------------------------
+// mock network layer
+
+class MockNetworkLayer;
+
+// This transaction class inspects the available set of mock transactions to
+// find data for the request URL. It supports IO operations that complete
+// synchronously or asynchronously to help exercise different code paths in the
+// HttpCache implementation.
+class MockNetworkTransaction
+ : public net::HttpTransaction,
+ public base::SupportsWeakPtr<MockNetworkTransaction> {
+ public:
+ MockNetworkTransaction(net::RequestPriority priority,
+ MockNetworkLayer* factory);
+ virtual ~MockNetworkTransaction();
+
+ virtual int Start(const net::HttpRequestInfo* request,
+ const net::CompletionCallback& callback,
+ const net::BoundNetLog& net_log) OVERRIDE;
+
+ virtual int RestartIgnoringLastError(
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ virtual int RestartWithCertificate(
+ net::X509Certificate* client_cert,
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ virtual int RestartWithAuth(
+ const net::AuthCredentials& credentials,
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ virtual bool IsReadyToRestartForAuth() OVERRIDE;
+
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ virtual void StopCaching() OVERRIDE;
+
+ virtual bool GetFullRequestHeaders(
+ net::HttpRequestHeaders* headers) const OVERRIDE;
+
+ virtual void DoneReading() OVERRIDE;
+
+ virtual const net::HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+
+ virtual net::LoadState GetLoadState() const OVERRIDE;
+
+ virtual net::UploadProgress GetUploadProgress() const OVERRIDE;
+
+ virtual bool GetLoadTimingInfo(
+ net::LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ virtual void SetPriority(net::RequestPriority priority) OVERRIDE;
+
+ net::RequestPriority priority() const { return priority_; }
+
+ private:
+ void CallbackLater(const net::CompletionCallback& callback, int result);
+ void RunCallback(const net::CompletionCallback& callback, int result);
+
+ base::WeakPtrFactory<MockNetworkTransaction> weak_factory_;
+ net::HttpResponseInfo response_;
+ std::string data_;
+ int data_cursor_;
+ int test_mode_;
+ net::RequestPriority priority_;
+ base::WeakPtr<MockNetworkLayer> transaction_factory_;
+
+ // NetLog ID of the fake / non-existent underlying socket used by the
+ // connection. Requires Start() be passed a BoundNetLog with a real NetLog to
+ // be initialized.
+ unsigned int socket_log_id_;
+};
+
+class MockNetworkLayer : public net::HttpTransactionFactory,
+ public base::SupportsWeakPtr<MockNetworkLayer> {
+ public:
+ MockNetworkLayer();
+ virtual ~MockNetworkLayer();
+
+ int transaction_count() const { return transaction_count_; }
+ bool done_reading_called() const { return done_reading_called_; }
+ void TransactionDoneReading();
+
+ // Returns the last priority passed to CreateTransaction, or
+ // DEFAULT_PRIORITY if it hasn't been called yet.
+ net::RequestPriority last_create_transaction_priority() const {
+ return last_create_transaction_priority_;
+ }
+
+ // Returns the last transaction created by
+ // CreateTransaction. Returns a NULL WeakPtr if one has not been
+ // created yet, or the last transaction has been destroyed, or
+ // ClearLastTransaction() has been called and a new transaction
+ // hasn't been created yet.
+ base::WeakPtr<MockNetworkTransaction> last_transaction() {
+ return last_transaction_;
+ }
+
+ // Makes last_transaction() return NULL until the next transaction
+ // is created.
+ void ClearLastTransaction() {
+ last_transaction_.reset();
+ }
+
+ // net::HttpTransactionFactory:
+ virtual int CreateTransaction(
+ net::RequestPriority priority,
+ scoped_ptr<net::HttpTransaction>* trans,
+ net::HttpTransactionDelegate* delegate) OVERRIDE;
+ virtual net::HttpCache* GetCache() OVERRIDE;
+ virtual net::HttpNetworkSession* GetSession() OVERRIDE;
+
+ private:
+ int transaction_count_;
+ bool done_reading_called_;
+ net::RequestPriority last_create_transaction_priority_;
+ base::WeakPtr<MockNetworkTransaction> last_transaction_;
+};
+
+//-----------------------------------------------------------------------------
+// helpers
+
+// read the transaction completely
+int ReadTransaction(net::HttpTransaction* trans, std::string* result);
+
+#endif // NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_
diff --git a/chromium/net/http/http_util.cc b/chromium/net/http/http_util.cc
new file mode 100644
index 00000000000..77f498133bc
--- /dev/null
+++ b/chromium/net/http/http_util.cc
@@ -0,0 +1,920 @@
+// Copyright (c) 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.
+
+// The rules for parsing content-types were borrowed from Firefox:
+// http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
+
+#include "net/http/http_util.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+
+using std::string;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+// Return the index of the closing quote of the string, if any.
+static size_t FindStringEnd(const string& line, size_t start, char delim) {
+ DCHECK(start < line.length() && line[start] == delim &&
+ (delim == '"' || delim == '\''));
+
+ const char set[] = { delim, '\\', '\0' };
+ for (;;) {
+ // start points to either the start quote or the last
+ // escaped char (the char following a '\\')
+
+ size_t end = line.find_first_of(set, start + 1);
+ if (end == string::npos)
+ return line.length();
+
+ if (line[end] == '\\') {
+ // Hit a backslash-escaped char. Need to skip over it.
+ start = end + 1;
+ if (start == line.length())
+ return start;
+
+ // Go back to looking for the next escape or the string end
+ continue;
+ }
+
+ return end;
+ }
+
+ NOTREACHED();
+ return line.length();
+}
+
+//-----------------------------------------------------------------------------
+
+// static
+size_t HttpUtil::FindDelimiter(const string& line, size_t search_start,
+ char delimiter) {
+ do {
+ // search_start points to the spot from which we should start looking
+ // for the delimiter.
+ const char delim_str[] = { delimiter, '"', '\'', '\0' };
+ size_t cur_delim_pos = line.find_first_of(delim_str, search_start);
+ if (cur_delim_pos == string::npos)
+ return line.length();
+
+ char ch = line[cur_delim_pos];
+ if (ch == delimiter) {
+ // Found delimiter
+ return cur_delim_pos;
+ }
+
+ // We hit the start of a quoted string. Look for its end.
+ search_start = FindStringEnd(line, cur_delim_pos, ch);
+ if (search_start == line.length())
+ return search_start;
+
+ ++search_start;
+
+ // search_start now points to the first char after the end of the
+ // string, so just go back to the top of the loop and look for
+ // |delimiter| again.
+ } while (true);
+
+ NOTREACHED();
+ return line.length();
+}
+
+// static
+void HttpUtil::ParseContentType(const string& content_type_str,
+ string* mime_type,
+ string* charset,
+ bool* had_charset,
+ string* boundary) {
+ const string::const_iterator begin = content_type_str.begin();
+
+ // Trim leading and trailing whitespace from type. We include '(' in
+ // the trailing trim set to catch media-type comments, which are not at all
+ // standard, but may occur in rare cases.
+ size_t type_val = content_type_str.find_first_not_of(HTTP_LWS);
+ type_val = std::min(type_val, content_type_str.length());
+ size_t type_end = content_type_str.find_first_of(HTTP_LWS ";(", type_val);
+ if (string::npos == type_end)
+ type_end = content_type_str.length();
+
+ size_t charset_val = 0;
+ size_t charset_end = 0;
+ bool type_has_charset = false;
+
+ // Iterate over parameters
+ size_t param_start = content_type_str.find_first_of(';', type_end);
+ if (param_start != string::npos) {
+ base::StringTokenizer tokenizer(begin + param_start, content_type_str.end(),
+ ";");
+ tokenizer.set_quote_chars("\"");
+ while (tokenizer.GetNext()) {
+ string::const_iterator equals_sign =
+ std::find(tokenizer.token_begin(), tokenizer.token_end(), '=');
+ if (equals_sign == tokenizer.token_end())
+ continue;
+
+ string::const_iterator param_name_begin = tokenizer.token_begin();
+ string::const_iterator param_name_end = equals_sign;
+ TrimLWS(&param_name_begin, &param_name_end);
+
+ string::const_iterator param_value_begin = equals_sign + 1;
+ string::const_iterator param_value_end = tokenizer.token_end();
+ DCHECK(param_value_begin <= tokenizer.token_end());
+ TrimLWS(&param_value_begin, &param_value_end);
+
+ if (LowerCaseEqualsASCII(param_name_begin, param_name_end, "charset")) {
+ // TODO(abarth): Refactor this function to consistently use iterators.
+ charset_val = param_value_begin - begin;
+ charset_end = param_value_end - begin;
+ type_has_charset = true;
+ } else if (LowerCaseEqualsASCII(param_name_begin, param_name_end,
+ "boundary")) {
+ if (boundary)
+ boundary->assign(param_value_begin, param_value_end);
+ }
+ }
+ }
+
+ if (type_has_charset) {
+ // Trim leading and trailing whitespace from charset_val. We include
+ // '(' in the trailing trim set to catch media-type comments, which are
+ // not at all standard, but may occur in rare cases.
+ charset_val = content_type_str.find_first_not_of(HTTP_LWS, charset_val);
+ charset_val = std::min(charset_val, charset_end);
+ char first_char = content_type_str[charset_val];
+ if (first_char == '"' || first_char == '\'') {
+ charset_end = FindStringEnd(content_type_str, charset_val, first_char);
+ ++charset_val;
+ DCHECK(charset_end >= charset_val);
+ } else {
+ charset_end = std::min(content_type_str.find_first_of(HTTP_LWS ";(",
+ charset_val),
+ charset_end);
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type_val is the same as mime_type, then just update the
+ // charset. however, if charset is empty and mime_type hasn't
+ // changed, then don't wipe-out an existing charset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+ if (content_type_str.length() != 0 &&
+ content_type_str != "*/*" &&
+ content_type_str.find_first_of('/') != string::npos) {
+ // Common case here is that mime_type is empty
+ bool eq = !mime_type->empty() && LowerCaseEqualsASCII(begin + type_val,
+ begin + type_end,
+ mime_type->data());
+ if (!eq) {
+ mime_type->assign(begin + type_val, begin + type_end);
+ StringToLowerASCII(mime_type);
+ }
+ if ((!eq && *had_charset) || type_has_charset) {
+ *had_charset = true;
+ charset->assign(begin + charset_val, begin + charset_end);
+ StringToLowerASCII(charset);
+ }
+ }
+}
+
+// static
+// Parse the Range header according to RFC 2616 14.35.1
+// ranges-specifier = byte-ranges-specifier
+// byte-ranges-specifier = bytes-unit "=" byte-range-set
+// byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
+// byte-range-spec = first-byte-pos "-" [last-byte-pos]
+// first-byte-pos = 1*DIGIT
+// last-byte-pos = 1*DIGIT
+bool HttpUtil::ParseRanges(const std::string& headers,
+ std::vector<HttpByteRange>* ranges) {
+ std::string ranges_specifier;
+ HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+
+ while (it.GetNext()) {
+ // Look for "Range" header.
+ if (!LowerCaseEqualsASCII(it.name(), "range"))
+ continue;
+ ranges_specifier = it.values();
+ // We just care about the first "Range" header, so break here.
+ break;
+ }
+
+ if (ranges_specifier.empty())
+ return false;
+
+ return ParseRangeHeader(ranges_specifier, ranges);
+}
+
+// static
+bool HttpUtil::ParseRangeHeader(const std::string& ranges_specifier,
+ std::vector<HttpByteRange>* ranges) {
+ size_t equal_char_offset = ranges_specifier.find('=');
+ if (equal_char_offset == std::string::npos)
+ return false;
+
+ // Try to extract bytes-unit part.
+ std::string::const_iterator bytes_unit_begin = ranges_specifier.begin();
+ std::string::const_iterator bytes_unit_end = bytes_unit_begin +
+ equal_char_offset;
+ std::string::const_iterator byte_range_set_begin = bytes_unit_end + 1;
+ std::string::const_iterator byte_range_set_end = ranges_specifier.end();
+
+ TrimLWS(&bytes_unit_begin, &bytes_unit_end);
+ // "bytes" unit identifier is not found.
+ if (!LowerCaseEqualsASCII(bytes_unit_begin, bytes_unit_end, "bytes"))
+ return false;
+
+ ValuesIterator byte_range_set_iterator(byte_range_set_begin,
+ byte_range_set_end, ',');
+ while (byte_range_set_iterator.GetNext()) {
+ size_t minus_char_offset = byte_range_set_iterator.value().find('-');
+ // If '-' character is not found, reports failure.
+ if (minus_char_offset == std::string::npos)
+ return false;
+
+ std::string::const_iterator first_byte_pos_begin =
+ byte_range_set_iterator.value_begin();
+ std::string::const_iterator first_byte_pos_end =
+ first_byte_pos_begin + minus_char_offset;
+ TrimLWS(&first_byte_pos_begin, &first_byte_pos_end);
+ std::string first_byte_pos(first_byte_pos_begin, first_byte_pos_end);
+
+ HttpByteRange range;
+ // Try to obtain first-byte-pos.
+ if (!first_byte_pos.empty()) {
+ int64 first_byte_position = -1;
+ if (!base::StringToInt64(first_byte_pos, &first_byte_position))
+ return false;
+ range.set_first_byte_position(first_byte_position);
+ }
+
+ std::string::const_iterator last_byte_pos_begin =
+ byte_range_set_iterator.value_begin() + minus_char_offset + 1;
+ std::string::const_iterator last_byte_pos_end =
+ byte_range_set_iterator.value_end();
+ TrimLWS(&last_byte_pos_begin, &last_byte_pos_end);
+ std::string last_byte_pos(last_byte_pos_begin, last_byte_pos_end);
+
+ // We have last-byte-pos or suffix-byte-range-spec in this case.
+ if (!last_byte_pos.empty()) {
+ int64 last_byte_position;
+ if (!base::StringToInt64(last_byte_pos, &last_byte_position))
+ return false;
+ if (range.HasFirstBytePosition())
+ range.set_last_byte_position(last_byte_position);
+ else
+ range.set_suffix_length(last_byte_position);
+ } else if (!range.HasFirstBytePosition()) {
+ return false;
+ }
+
+ // Do a final check on the HttpByteRange object.
+ if (!range.IsValid())
+ return false;
+ ranges->push_back(range);
+ }
+ return !ranges->empty();
+}
+
+// static
+bool HttpUtil::HasHeader(const std::string& headers, const char* name) {
+ size_t name_len = strlen(name);
+ string::const_iterator it =
+ std::search(headers.begin(),
+ headers.end(),
+ name,
+ name + name_len,
+ base::CaseInsensitiveCompareASCII<char>());
+ if (it == headers.end())
+ return false;
+
+ // ensure match is prefixed by newline
+ if (it != headers.begin() && it[-1] != '\n')
+ return false;
+
+ // ensure match is suffixed by colon
+ if (it + name_len >= headers.end() || it[name_len] != ':')
+ return false;
+
+ return true;
+}
+
+namespace {
+// A header string containing any of the following fields will cause
+// an error. The list comes from the XMLHttpRequest standard.
+// http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader-method
+const char* const kForbiddenHeaderFields[] = {
+ "accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "content-transfer-encoding",
+ "date",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "user-agent",
+ "via",
+};
+} // anonymous namespace
+
+// static
+bool HttpUtil::IsSafeHeader(const std::string& name) {
+ std::string lower_name(StringToLowerASCII(name));
+ if (StartsWithASCII(lower_name, "proxy-", true) ||
+ StartsWithASCII(lower_name, "sec-", true))
+ return false;
+ for (size_t i = 0; i < arraysize(kForbiddenHeaderFields); ++i) {
+ if (lower_name == kForbiddenHeaderFields[i])
+ return false;
+ }
+ return true;
+}
+
+// static
+std::string HttpUtil::StripHeaders(const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ std::string stripped_headers;
+ net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+
+ while (it.GetNext()) {
+ bool should_remove = false;
+ for (size_t i = 0; i < headers_to_remove_len; ++i) {
+ if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
+ headers_to_remove[i])) {
+ should_remove = true;
+ break;
+ }
+ }
+ if (!should_remove) {
+ // Assume that name and values are on the same line.
+ stripped_headers.append(it.name_begin(), it.values_end());
+ stripped_headers.append("\r\n");
+ }
+ }
+ return stripped_headers;
+}
+
+// static
+bool HttpUtil::IsNonCoalescingHeader(string::const_iterator name_begin,
+ string::const_iterator name_end) {
+ // NOTE: "set-cookie2" headers do not support expires attributes, so we don't
+ // have to list them here.
+ const char* kNonCoalescingHeaders[] = {
+ "date",
+ "expires",
+ "last-modified",
+ "location", // See bug 1050541 for details
+ "retry-after",
+ "set-cookie",
+ // The format of auth-challenges mixes both space separated tokens and
+ // comma separated properties, so coalescing on comma won't work.
+ "www-authenticate",
+ "proxy-authenticate",
+ // STS specifies that UAs must not process any STS headers after the first
+ // one.
+ "strict-transport-security"
+ };
+ for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, kNonCoalescingHeaders[i]))
+ return true;
+ }
+ return false;
+}
+
+bool HttpUtil::IsLWS(char c) {
+ return strchr(HTTP_LWS, c) != NULL;
+}
+
+void HttpUtil::TrimLWS(string::const_iterator* begin,
+ string::const_iterator* end) {
+ // leading whitespace
+ while (*begin < *end && IsLWS((*begin)[0]))
+ ++(*begin);
+
+ // trailing whitespace
+ while (*begin < *end && IsLWS((*end)[-1]))
+ --(*end);
+}
+
+bool HttpUtil::IsQuote(char c) {
+ // Single quote mark isn't actually part of quoted-text production,
+ // but apparently some servers rely on this.
+ return c == '"' || c == '\'';
+}
+
+// See RFC 2616 Sec 2.2 for the definition of |token|.
+bool HttpUtil::IsToken(string::const_iterator begin,
+ string::const_iterator end) {
+ if (begin == end)
+ return false;
+ for (std::string::const_iterator iter = begin; iter != end; ++iter) {
+ unsigned char c = *iter;
+ if (c >= 0x80 || c <= 0x1F || c == 0x7F ||
+ c == '(' || c == ')' || c == '<' || c == '>' || c == '@' ||
+ c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' ||
+ c == '/' || c == '[' || c == ']' || c == '?' || c == '=' ||
+ c == '{' || c == '}' || c == ' ' || c == '\t')
+ return false;
+ }
+ return true;
+}
+
+std::string HttpUtil::Unquote(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ // Empty string
+ if (begin == end)
+ return std::string();
+
+ // Nothing to unquote.
+ if (!IsQuote(*begin))
+ return std::string(begin, end);
+
+ // No terminal quote mark.
+ if (end - begin < 2 || *begin != *(end - 1))
+ return std::string(begin, end);
+
+ // Strip quotemarks
+ ++begin;
+ --end;
+
+ // Unescape quoted-pair (defined in RFC 2616 section 2.2)
+ std::string unescaped;
+ bool prev_escape = false;
+ for (; begin != end; ++begin) {
+ char c = *begin;
+ if (c == '\\' && !prev_escape) {
+ prev_escape = true;
+ continue;
+ }
+ prev_escape = false;
+ unescaped.push_back(c);
+ }
+ return unescaped;
+}
+
+// static
+std::string HttpUtil::Unquote(const std::string& str) {
+ return Unquote(str.begin(), str.end());
+}
+
+// static
+std::string HttpUtil::Quote(const std::string& str) {
+ std::string escaped;
+ escaped.reserve(2 + str.size());
+
+ std::string::const_iterator begin = str.begin();
+ std::string::const_iterator end = str.end();
+
+ // Esape any backslashes or quotemarks within the string, and
+ // then surround with quotes.
+ escaped.push_back('"');
+ for (; begin != end; ++begin) {
+ char c = *begin;
+ if (c == '"' || c == '\\')
+ escaped.push_back('\\');
+ escaped.push_back(c);
+ }
+ escaped.push_back('"');
+ return escaped;
+}
+
+// Find the "http" substring in a status line. This allows for
+// some slop at the start. If the "http" string could not be found
+// then returns -1.
+// static
+int HttpUtil::LocateStartOfStatusLine(const char* buf, int buf_len) {
+ const int slop = 4;
+ const int http_len = 4;
+
+ if (buf_len >= http_len) {
+ int i_max = std::min(buf_len - http_len, slop);
+ for (int i = 0; i <= i_max; ++i) {
+ if (LowerCaseEqualsASCII(buf + i, buf + i + http_len, "http"))
+ return i;
+ }
+ }
+ return -1; // Not found
+}
+
+int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len, int i) {
+ bool was_lf = false;
+ char last_c = '\0';
+ for (; i < buf_len; ++i) {
+ char c = buf[i];
+ if (c == '\n') {
+ if (was_lf)
+ return i + 1;
+ was_lf = true;
+ } else if (c != '\r' || last_c != '\n') {
+ was_lf = false;
+ }
+ last_c = c;
+ }
+ return -1;
+}
+
+// In order for a line to be continuable, it must specify a
+// non-blank header-name. Line continuations are specifically for
+// header values -- do not allow headers names to span lines.
+static bool IsLineSegmentContinuable(const char* begin, const char* end) {
+ if (begin == end)
+ return false;
+
+ const char* colon = std::find(begin, end, ':');
+ if (colon == end)
+ return false;
+
+ const char* name_begin = begin;
+ const char* name_end = colon;
+
+ // Name can't be empty.
+ if (name_begin == name_end)
+ return false;
+
+ // Can't start with LWS (this would imply the segment is a continuation)
+ if (HttpUtil::IsLWS(*name_begin))
+ return false;
+
+ return true;
+}
+
+// Helper used by AssembleRawHeaders, to find the end of the status line.
+static const char* FindStatusLineEnd(const char* begin, const char* end) {
+ size_t i = base::StringPiece(begin, end - begin).find_first_of("\r\n");
+ if (i == base::StringPiece::npos)
+ return end;
+ return begin + i;
+}
+
+// Helper used by AssembleRawHeaders, to skip past leading LWS.
+static const char* FindFirstNonLWS(const char* begin, const char* end) {
+ for (const char* cur = begin; cur != end; ++cur) {
+ if (!HttpUtil::IsLWS(*cur))
+ return cur;
+ }
+ return end; // Not found.
+}
+
+std::string HttpUtil::AssembleRawHeaders(const char* input_begin,
+ int input_len) {
+ std::string raw_headers;
+ raw_headers.reserve(input_len);
+
+ const char* input_end = input_begin + input_len;
+
+ // Skip any leading slop, since the consumers of this output
+ // (HttpResponseHeaders) don't deal with it.
+ int status_begin_offset = LocateStartOfStatusLine(input_begin, input_len);
+ if (status_begin_offset != -1)
+ input_begin += status_begin_offset;
+
+ // Copy the status line.
+ const char* status_line_end = FindStatusLineEnd(input_begin, input_end);
+ raw_headers.append(input_begin, status_line_end);
+
+ // After the status line, every subsequent line is a header line segment.
+ // Should a segment start with LWS, it is a continuation of the previous
+ // line's field-value.
+
+ // TODO(ericroman): is this too permissive? (delimits on [\r\n]+)
+ base::CStringTokenizer lines(status_line_end, input_end, "\r\n");
+
+ // This variable is true when the previous line was continuable.
+ bool prev_line_continuable = false;
+
+ while (lines.GetNext()) {
+ const char* line_begin = lines.token_begin();
+ const char* line_end = lines.token_end();
+
+ if (prev_line_continuable && IsLWS(*line_begin)) {
+ // Join continuation; reduce the leading LWS to a single SP.
+ raw_headers.push_back(' ');
+ raw_headers.append(FindFirstNonLWS(line_begin, line_end), line_end);
+ } else {
+ // Terminate the previous line.
+ raw_headers.push_back('\n');
+
+ // Copy the raw data to output.
+ raw_headers.append(line_begin, line_end);
+
+ // Check if the current line can be continued.
+ prev_line_continuable = IsLineSegmentContinuable(line_begin, line_end);
+ }
+ }
+
+ raw_headers.append("\n\n", 2);
+
+ // Use '\0' as the canonical line terminator. If the input already contained
+ // any embeded '\0' characters we will strip them first to avoid interpreting
+ // them as line breaks.
+ raw_headers.erase(std::remove(raw_headers.begin(), raw_headers.end(), '\0'),
+ raw_headers.end());
+ std::replace(raw_headers.begin(), raw_headers.end(), '\n', '\0');
+
+ return raw_headers;
+}
+
+std::string HttpUtil::ConvertHeadersBackToHTTPResponse(const std::string& str) {
+ std::string disassembled_headers;
+ base::StringTokenizer tokenizer(str, std::string(1, '\0'));
+ while (tokenizer.GetNext()) {
+ disassembled_headers.append(tokenizer.token_begin(), tokenizer.token_end());
+ disassembled_headers.append("\r\n");
+ }
+ disassembled_headers.append("\r\n");
+
+ return disassembled_headers;
+}
+
+// TODO(jungshik): 1. If the list is 'fr-CA,fr-FR,en,de', we have to add
+// 'fr' after 'fr-CA' with the same q-value as 'fr-CA' because
+// web servers, in general, do not fall back to 'fr' and may end up picking
+// 'en' which has a lower preference than 'fr-CA' and 'fr-FR'.
+// 2. This function assumes that the input is a comma separated list
+// without any whitespace. As long as it comes from the preference and
+// a user does not manually edit the preference file, it's the case. Still,
+// we may have to make it more robust.
+std::string HttpUtil::GenerateAcceptLanguageHeader(
+ const std::string& raw_language_list) {
+ // We use integers for qvalue and qvalue decrement that are 10 times
+ // larger than actual values to avoid a problem with comparing
+ // two floating point numbers.
+ const unsigned int kQvalueDecrement10 = 2;
+ unsigned int qvalue10 = 10;
+ base::StringTokenizer t(raw_language_list, ",");
+ std::string lang_list_with_q;
+ while (t.GetNext()) {
+ std::string language = t.token();
+ if (qvalue10 == 10) {
+ // q=1.0 is implicit.
+ lang_list_with_q = language;
+ } else {
+ DCHECK_LT(qvalue10, 10U);
+ base::StringAppendF(&lang_list_with_q, ",%s;q=0.%d", language.c_str(),
+ qvalue10);
+ }
+ // It does not make sense to have 'q=0'.
+ if (qvalue10 > kQvalueDecrement10)
+ qvalue10 -= kQvalueDecrement10;
+ }
+ return lang_list_with_q;
+}
+
+void HttpUtil::AppendHeaderIfMissing(const char* header_name,
+ const std::string& header_value,
+ std::string* headers) {
+ if (header_value.empty())
+ return;
+ if (net::HttpUtil::HasHeader(*headers, header_name))
+ return;
+ *headers += std::string(header_name) + ": " + header_value + "\r\n";
+}
+
+bool HttpUtil::HasStrongValidators(HttpVersion version,
+ const std::string& etag_header,
+ const std::string& last_modified_header,
+ const std::string& date_header) {
+ if (version < HttpVersion(1, 1))
+ return false;
+
+ if (!etag_header.empty()) {
+ size_t slash = etag_header.find('/');
+ if (slash == std::string::npos || slash == 0)
+ return true;
+
+ std::string::const_iterator i = etag_header.begin();
+ std::string::const_iterator j = etag_header.begin() + slash;
+ TrimLWS(&i, &j);
+ if (!LowerCaseEqualsASCII(i, j, "w"))
+ return true;
+ }
+
+ base::Time last_modified;
+ if (!base::Time::FromString(last_modified_header.c_str(), &last_modified))
+ return false;
+
+ base::Time date;
+ if (!base::Time::FromString(date_header.c_str(), &date))
+ return false;
+
+ return ((date - last_modified).InSeconds() >= 60);
+}
+
+// Functions for histogram initialization. The code 0 is put in the map to
+// track status codes that are invalid.
+// TODO(gavinp): Greatly prune the collected codes once we learn which
+// ones are not sent in practice, to reduce upload size & memory use.
+
+enum {
+ HISTOGRAM_MIN_HTTP_STATUS_CODE = 100,
+ HISTOGRAM_MAX_HTTP_STATUS_CODE = 599,
+};
+
+// static
+std::vector<int> HttpUtil::GetStatusCodesForHistogram() {
+ std::vector<int> codes;
+ codes.reserve(
+ HISTOGRAM_MAX_HTTP_STATUS_CODE - HISTOGRAM_MIN_HTTP_STATUS_CODE + 2);
+ codes.push_back(0);
+ for (int i = HISTOGRAM_MIN_HTTP_STATUS_CODE;
+ i <= HISTOGRAM_MAX_HTTP_STATUS_CODE; ++i)
+ codes.push_back(i);
+ return codes;
+}
+
+// static
+int HttpUtil::MapStatusCodeForHistogram(int code) {
+ if (HISTOGRAM_MIN_HTTP_STATUS_CODE <= code &&
+ code <= HISTOGRAM_MAX_HTTP_STATUS_CODE)
+ return code;
+ return 0;
+}
+
+// BNF from section 4.2 of RFC 2616:
+//
+// message-header = field-name ":" [ field-value ]
+// field-name = token
+// field-value = *( field-content | LWS )
+// field-content = <the OCTETs making up the field-value
+// and consisting of either *TEXT or combinations
+// of token, separators, and quoted-string>
+//
+
+HttpUtil::HeadersIterator::HeadersIterator(string::const_iterator headers_begin,
+ string::const_iterator headers_end,
+ const std::string& line_delimiter)
+ : lines_(headers_begin, headers_end, line_delimiter) {
+}
+
+HttpUtil::HeadersIterator::~HeadersIterator() {
+}
+
+bool HttpUtil::HeadersIterator::GetNext() {
+ while (lines_.GetNext()) {
+ name_begin_ = lines_.token_begin();
+ values_end_ = lines_.token_end();
+
+ string::const_iterator colon = std::find(name_begin_, values_end_, ':');
+ if (colon == values_end_)
+ continue; // skip malformed header
+
+ name_end_ = colon;
+
+ // If the name starts with LWS, it is an invalid line.
+ // Leading LWS implies a line continuation, and these should have
+ // already been joined by AssembleRawHeaders().
+ if (name_begin_ == name_end_ || IsLWS(*name_begin_))
+ continue;
+
+ TrimLWS(&name_begin_, &name_end_);
+ if (name_begin_ == name_end_)
+ continue; // skip malformed header
+
+ values_begin_ = colon + 1;
+ TrimLWS(&values_begin_, &values_end_);
+
+ // if we got a header name, then we are done.
+ return true;
+ }
+ return false;
+}
+
+bool HttpUtil::HeadersIterator::AdvanceTo(const char* name) {
+ DCHECK(name != NULL);
+ DCHECK_EQ(0, StringToLowerASCII<std::string>(name).compare(name))
+ << "the header name must be in all lower case";
+
+ while (GetNext()) {
+ if (LowerCaseEqualsASCII(name_begin_, name_end_, name)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+HttpUtil::ValuesIterator::ValuesIterator(
+ string::const_iterator values_begin,
+ string::const_iterator values_end,
+ char delimiter)
+ : values_(values_begin, values_end, string(1, delimiter)) {
+ values_.set_quote_chars("\'\"");
+}
+
+HttpUtil::ValuesIterator::~ValuesIterator() {
+}
+
+bool HttpUtil::ValuesIterator::GetNext() {
+ while (values_.GetNext()) {
+ value_begin_ = values_.token_begin();
+ value_end_ = values_.token_end();
+ TrimLWS(&value_begin_, &value_end_);
+
+ // bypass empty values.
+ if (value_begin_ != value_end_)
+ return true;
+ }
+ return false;
+}
+
+HttpUtil::NameValuePairsIterator::NameValuePairsIterator(
+ string::const_iterator begin,
+ string::const_iterator end,
+ char delimiter)
+ : props_(begin, end, delimiter),
+ valid_(true),
+ name_begin_(end),
+ name_end_(end),
+ value_begin_(end),
+ value_end_(end),
+ value_is_quoted_(false) {
+}
+
+HttpUtil::NameValuePairsIterator::~NameValuePairsIterator() {}
+
+// We expect properties to be formatted as one of:
+// name="value"
+// name='value'
+// name='\'value\''
+// name=value
+// name = value
+// name=
+// Due to buggy implementations found in some embedded devices, we also
+// accept values with missing close quotemark (http://crbug.com/39836):
+// name="value
+bool HttpUtil::NameValuePairsIterator::GetNext() {
+ if (!props_.GetNext())
+ return false;
+
+ // Set the value as everything. Next we will split out the name.
+ value_begin_ = props_.value_begin();
+ value_end_ = props_.value_end();
+ name_begin_ = name_end_ = value_end_;
+
+ // Scan for the equals sign.
+ std::string::const_iterator equals = std::find(value_begin_, value_end_, '=');
+ if (equals == value_end_ || equals == value_begin_)
+ return valid_ = false; // Malformed, no equals sign
+
+ // Verify that the equals sign we found wasn't inside of quote marks.
+ for (std::string::const_iterator it = value_begin_; it != equals; ++it) {
+ if (HttpUtil::IsQuote(*it))
+ return valid_ = false; // Malformed, quote appears before equals sign
+ }
+
+ name_begin_ = value_begin_;
+ name_end_ = equals;
+ value_begin_ = equals + 1;
+
+ TrimLWS(&name_begin_, &name_end_);
+ TrimLWS(&value_begin_, &value_end_);
+ value_is_quoted_ = false;
+ unquoted_value_.clear();
+
+ if (value_begin_ == value_end_)
+ return valid_ = false; // Malformed, value is empty
+
+ if (HttpUtil::IsQuote(*value_begin_)) {
+ // Trim surrounding quotemarks off the value
+ if (*value_begin_ != *(value_end_ - 1) || value_begin_ + 1 == value_end_) {
+ // NOTE: This is not as graceful as it sounds:
+ // * quoted-pairs will no longer be unquoted
+ // (["\"hello] should give ["hello]).
+ // * Does not detect when the final quote is escaped
+ // (["value\"] should give [value"])
+ ++value_begin_; // Gracefully recover from mismatching quotes.
+ } else {
+ value_is_quoted_ = true;
+ // Do not store iterators into this. See declaration of unquoted_value_.
+ unquoted_value_ = HttpUtil::Unquote(value_begin_, value_end_);
+ }
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_util.h b/chromium/net/http/http_util.h
new file mode 100644
index 00000000000..ae65146e6e4
--- /dev/null
+++ b/chromium/net/http/http_util.h
@@ -0,0 +1,359 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_UTIL_H_
+#define NET_HTTP_HTTP_UTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_tokenizer.h"
+#include "net/base/net_export.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_version.h"
+#include "url/gurl.h"
+
+// This is a macro to support extending this string literal at compile time.
+// Please excuse me polluting your global namespace!
+#define HTTP_LWS " \t"
+
+namespace net {
+
+class NET_EXPORT HttpUtil {
+ public:
+ // Returns the absolute path of the URL, to be used for the http request.
+ // The absolute path starts with a '/' and may contain a query.
+ static std::string PathForRequest(const GURL& url);
+
+ // Returns the absolute URL, to be used for the http request. This url is
+ // made up of the protocol, host, [port], path, [query]. Everything else
+ // is stripped (username, password, reference).
+ static std::string SpecForRequest(const GURL& url);
+
+ // Locates the next occurance of delimiter in line, skipping over quoted
+ // strings (e.g., commas will not be treated as delimiters if they appear
+ // within a quoted string). Returns the offset of the found delimiter or
+ // line.size() if no delimiter was found.
+ static size_t FindDelimiter(const std::string& line,
+ size_t search_start,
+ char delimiter);
+
+ // Parses the value of a Content-Type header. The resulting mime_type and
+ // charset values are normalized to lowercase. The mime_type and charset
+ // output values are only modified if the content_type_str contains a mime
+ // type and charset value, respectively. The boundary output value is
+ // optional and will be assigned the (quoted) value of the boundary
+ // paramter, if any.
+ static void ParseContentType(const std::string& content_type_str,
+ std::string* mime_type,
+ std::string* charset,
+ bool* had_charset,
+ std::string* boundary);
+
+ // Scans the headers and look for the first "Range" header in |headers|,
+ // if "Range" exists and the first one of it is well formatted then returns
+ // true, |ranges| will contain a list of valid ranges. If return
+ // value is false then values in |ranges| should not be used. The format of
+ // "Range" header is defined in RFC 2616 Section 14.35.1.
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
+ static bool ParseRanges(const std::string& headers,
+ std::vector<HttpByteRange>* ranges);
+
+ // Same thing as ParseRanges except the Range header is known and its value
+ // is directly passed in, rather than requiring searching through a string.
+ static bool ParseRangeHeader(const std::string& range_specifier,
+ std::vector<HttpByteRange>* ranges);
+
+ // Scans the '\r\n'-delimited headers for the given header name. Returns
+ // true if a match is found. Input is assumed to be well-formed.
+ // TODO(darin): kill this
+ static bool HasHeader(const std::string& headers, const char* name);
+
+ // Returns true if it is safe to allow users and scripts to specify the header
+ // named |name|.
+ static bool IsSafeHeader(const std::string& name);
+
+ // Strips all header lines from |headers| whose name matches
+ // |headers_to_remove|. |headers_to_remove| is a list of null-terminated
+ // lower-case header names, with array length |headers_to_remove_len|.
+ // Returns the stripped header lines list, separated by "\r\n".
+ static std::string StripHeaders(const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
+ // Multiple occurances of some headers cannot be coalesced into a comma-
+ // separated list since their values are (or contain) unquoted HTTP-date
+ // values, which may contain a comma (see RFC 2616 section 3.3.1).
+ static bool IsNonCoalescingHeader(std::string::const_iterator name_begin,
+ std::string::const_iterator name_end);
+ static bool IsNonCoalescingHeader(const std::string& name) {
+ return IsNonCoalescingHeader(name.begin(), name.end());
+ }
+
+ // Return true if the character is HTTP "linear white space" (SP | HT).
+ // This definition corresponds with the HTTP_LWS macro, and does not match
+ // newlines.
+ static bool IsLWS(char c);
+
+ // Trim HTTP_LWS chars from the beginning and end of the string.
+ static void TrimLWS(std::string::const_iterator* begin,
+ std::string::const_iterator* end);
+
+ // Whether the character is the start of a quotation mark.
+ static bool IsQuote(char c);
+
+ // Whether the string is a valid |token| as defined in RFC 2616 Sec 2.2.
+ static bool IsToken(std::string::const_iterator begin,
+ std::string::const_iterator end);
+ static bool IsToken(const std::string& str) {
+ return IsToken(str.begin(), str.end());
+ }
+
+ // RFC 2616 Sec 2.2:
+ // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ // Unquote() strips the surrounding quotemarks off a string, and unescapes
+ // any quoted-pair to obtain the value contained by the quoted-string.
+ // If the input is not quoted, then it works like the identity function.
+ static std::string Unquote(std::string::const_iterator begin,
+ std::string::const_iterator end);
+
+ // Same as above.
+ static std::string Unquote(const std::string& str);
+
+ // The reverse of Unquote() -- escapes and surrounds with "
+ static std::string Quote(const std::string& str);
+
+ // Returns the start of the status line, or -1 if no status line was found.
+ // This allows for 4 bytes of junk to precede the status line (which is what
+ // mozilla does too).
+ static int LocateStartOfStatusLine(const char* buf, int buf_len);
+
+ // Returns index beyond the end-of-headers marker or -1 if not found. RFC
+ // 2616 defines the end-of-headers marker as a double CRLF; however, some
+ // servers only send back LFs (e.g., Unix-based CGI scripts written using the
+ // ASIS Apache module). This function therefore accepts the pattern LF[CR]LF
+ // as end-of-headers (just like Mozilla).
+ // The parameter |i| is the offset within |buf| to begin searching from.
+ static int LocateEndOfHeaders(const char* buf, int buf_len, int i = 0);
+
+ // Assemble "raw headers" in the format required by HttpResponseHeaders.
+ // This involves normalizing line terminators, converting [CR]LF to \0 and
+ // handling HTTP line continuations (i.e., lines starting with LWS are
+ // continuations of the previous line). |buf_len| indicates the position of
+ // the end-of-headers marker as defined by LocateEndOfHeaders.
+ // If a \0 appears within the headers themselves, it will be stripped. This
+ // is a workaround to avoid later code from incorrectly interpreting it as
+ // a line terminator.
+ //
+ // TODO(eroman): we should use \n as the canonical line separator rather than
+ // \0 to avoid this problem. Unfortunately the persistence layer
+ // is already dependent on newlines being replaced by NULL so
+ // this is hard to change without breaking things.
+ static std::string AssembleRawHeaders(const char* buf, int buf_len);
+
+ // Converts assembled "raw headers" back to the HTTP response format. That is
+ // convert each \0 occurence to CRLF. This is used by DevTools.
+ // Since all line continuations info is already lost at this point, the result
+ // consists of status line and then one line for each header.
+ static std::string ConvertHeadersBackToHTTPResponse(const std::string& str);
+
+ // Given a comma separated ordered list of language codes, return
+ // the list with a qvalue appended to each language.
+ // The way qvalues are assigned is rather simple. The qvalue
+ // starts with 1.0 and is decremented by 0.2 for each successive entry
+ // in the list until it reaches 0.2. All the entries after that are
+ // assigned the same qvalue of 0.2. Also, note that the 1st language
+ // will not have a qvalue added because the absence of a qvalue implicitly
+ // means q=1.0.
+ //
+ // When making a http request, this should be used to determine what
+ // to put in Accept-Language header. If a comma separated list of language
+ // codes *without* qvalue is sent, web servers regard all
+ // of them as having q=1.0 and pick one of them even though it may not
+ // be at the beginning of the list (see http://crbug.com/5899).
+ static std::string GenerateAcceptLanguageHeader(
+ const std::string& raw_language_list);
+
+ // Helper. If |*headers| already contains |header_name| do nothing,
+ // otherwise add <header_name> ": " <header_value> to the end of the list.
+ static void AppendHeaderIfMissing(const char* header_name,
+ const std::string& header_value,
+ std::string* headers);
+
+ // Returns true if the parameters describe a response with a strong etag or
+ // last-modified header. See section 13.3.3 of RFC 2616.
+ static bool HasStrongValidators(HttpVersion version,
+ const std::string& etag_header,
+ const std::string& last_modified_header,
+ const std::string& date_header);
+
+ // Gets a vector of common HTTP status codes for histograms of status
+ // codes. Currently returns everything in the range [100, 600), plus 0
+ // (for invalid responses/status codes).
+ static std::vector<int> GetStatusCodesForHistogram();
+
+ // Maps an HTTP status code to one of the status codes in the vector
+ // returned by GetStatusCodesForHistogram.
+ static int MapStatusCodeForHistogram(int code);
+
+ // Used to iterate over the name/value pairs of HTTP headers. To iterate
+ // over the values in a multi-value header, use ValuesIterator.
+ // See AssembleRawHeaders for joining line continuations (this iterator
+ // does not expect any).
+ class NET_EXPORT HeadersIterator {
+ public:
+ HeadersIterator(std::string::const_iterator headers_begin,
+ std::string::const_iterator headers_end,
+ const std::string& line_delimiter);
+ ~HeadersIterator();
+
+ // Advances the iterator to the next header, if any. Returns true if there
+ // is a next header. Use name* and values* methods to access the resultant
+ // header name and values.
+ bool GetNext();
+
+ // Iterates through the list of headers, starting with the current position
+ // and looks for the specified header. Note that the name _must_ be
+ // lower cased.
+ // If the header was found, the return value will be true and the current
+ // position points to the header. If the return value is false, the
+ // current position will be at the end of the headers.
+ bool AdvanceTo(const char* lowercase_name);
+
+ void Reset() {
+ lines_.Reset();
+ }
+
+ std::string::const_iterator name_begin() const {
+ return name_begin_;
+ }
+ std::string::const_iterator name_end() const {
+ return name_end_;
+ }
+ std::string name() const {
+ return std::string(name_begin_, name_end_);
+ }
+
+ std::string::const_iterator values_begin() const {
+ return values_begin_;
+ }
+ std::string::const_iterator values_end() const {
+ return values_end_;
+ }
+ std::string values() const {
+ return std::string(values_begin_, values_end_);
+ }
+
+ private:
+ base::StringTokenizer lines_;
+ std::string::const_iterator name_begin_;
+ std::string::const_iterator name_end_;
+ std::string::const_iterator values_begin_;
+ std::string::const_iterator values_end_;
+ };
+
+ // Iterates over delimited values in an HTTP header. HTTP LWS is
+ // automatically trimmed from the resulting values.
+ //
+ // When using this class to iterate over response header values, be aware that
+ // for some headers (e.g., Last-Modified), commas are not used as delimiters.
+ // This iterator should be avoided for headers like that which are considered
+ // non-coalescing (see IsNonCoalescingHeader).
+ //
+ // This iterator is careful to skip over delimiters found inside an HTTP
+ // quoted string.
+ //
+ class NET_EXPORT_PRIVATE ValuesIterator {
+ public:
+ ValuesIterator(std::string::const_iterator values_begin,
+ std::string::const_iterator values_end,
+ char delimiter);
+ ~ValuesIterator();
+
+ // Advances the iterator to the next value, if any. Returns true if there
+ // is a next value. Use value* methods to access the resultant value.
+ bool GetNext();
+
+ std::string::const_iterator value_begin() const {
+ return value_begin_;
+ }
+ std::string::const_iterator value_end() const {
+ return value_end_;
+ }
+ std::string value() const {
+ return std::string(value_begin_, value_end_);
+ }
+
+ private:
+ base::StringTokenizer values_;
+ std::string::const_iterator value_begin_;
+ std::string::const_iterator value_end_;
+ };
+
+ // Iterates over a delimited sequence of name-value pairs in an HTTP header.
+ // Each pair consists of a token (the name), an equals sign, and either a
+ // token or quoted-string (the value). Arbitrary HTTP LWS is permitted outside
+ // of and between names, values, and delimiters.
+ //
+ // String iterators returned from this class' methods may be invalidated upon
+ // calls to GetNext() or after the NameValuePairsIterator is destroyed.
+ class NET_EXPORT NameValuePairsIterator {
+ public:
+ NameValuePairsIterator(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ char delimiter);
+ ~NameValuePairsIterator();
+
+ // Advances the iterator to the next pair, if any. Returns true if there
+ // is a next pair. Use name* and value* methods to access the resultant
+ // value.
+ bool GetNext();
+
+ // Returns false if there was a parse error.
+ bool valid() const { return valid_; }
+
+ // The name of the current name-value pair.
+ std::string::const_iterator name_begin() const { return name_begin_; }
+ std::string::const_iterator name_end() const { return name_end_; }
+ std::string name() const { return std::string(name_begin_, name_end_); }
+
+ // The value of the current name-value pair.
+ std::string::const_iterator value_begin() const {
+ return value_is_quoted_ ? unquoted_value_.begin() : value_begin_;
+ }
+ std::string::const_iterator value_end() const {
+ return value_is_quoted_ ? unquoted_value_.end() : value_end_;
+ }
+ std::string value() const {
+ return value_is_quoted_ ? unquoted_value_ : std::string(value_begin_,
+ value_end_);
+ }
+
+ // The value before unquoting (if any).
+ std::string raw_value() const { return std::string(value_begin_,
+ value_end_); }
+
+ private:
+ HttpUtil::ValuesIterator props_;
+ bool valid_;
+
+ std::string::const_iterator name_begin_;
+ std::string::const_iterator name_end_;
+
+ std::string::const_iterator value_begin_;
+ std::string::const_iterator value_end_;
+
+ // Do not store iterators into this string. The NameValuePairsIterator
+ // is copyable/assignable, and if copied the copy's iterators would point
+ // into the original's unquoted_value_ member.
+ std::string unquoted_value_;
+
+ bool value_is_quoted_;
+ };
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_UTIL_H_
diff --git a/chromium/net/http/http_util_icu.cc b/chromium/net/http/http_util_icu.cc
new file mode 100644
index 00000000000..64e7424df14
--- /dev/null
+++ b/chromium/net/http/http_util_icu.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2009 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.
+
+// The rules for parsing content-types were borrowed from Firefox:
+// http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834
+
+#include "net/http/http_util.h"
+
+#include "base/logging.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+// static
+std::string HttpUtil::PathForRequest(const GURL& url) {
+ DCHECK(url.is_valid() && (url.SchemeIs("http") || url.SchemeIs("https")));
+ if (url.has_query())
+ return url.path() + "?" + url.query();
+ return url.path();
+}
+
+// static
+std::string HttpUtil::SpecForRequest(const GURL& url) {
+ // We may get ftp scheme when fetching ftp resources through proxy.
+ DCHECK(url.is_valid() && (url.SchemeIs("http") ||
+ url.SchemeIs("https") ||
+ url.SchemeIs("ftp")));
+ return SimplifyUrlForRequest(url).spec();
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_util_unittest.cc b/chromium/net/http/http_util_unittest.cc
new file mode 100644
index 00000000000..fe2d8b47496
--- /dev/null
+++ b/chromium/net/http/http_util_unittest.cc
@@ -0,0 +1,1051 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::HttpUtil;
+
+namespace {
+class HttpUtilTest : public testing::Test {};
+}
+
+TEST(HttpUtilTest, IsSafeHeader) {
+ static const char* unsafe_headers[] = {
+ "sec-",
+ "sEc-",
+ "sec-foo",
+ "sEc-FoO",
+ "proxy-",
+ "pRoXy-",
+ "proxy-foo",
+ "pRoXy-FoO",
+ "accept-charset",
+ "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection",
+ "content-length",
+ "cookie",
+ "cookie2",
+ "content-transfer-encoding",
+ "date",
+ "expect",
+ "host",
+ "keep-alive",
+ "origin",
+ "referer",
+ "te",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "user-agent",
+ "via",
+ };
+ for (size_t i = 0; i < arraysize(unsafe_headers); ++i) {
+ EXPECT_FALSE(HttpUtil::IsSafeHeader(unsafe_headers[i]))
+ << unsafe_headers[i];
+ EXPECT_FALSE(HttpUtil::IsSafeHeader(StringToUpperASCII(std::string(
+ unsafe_headers[i])))) << unsafe_headers[i];
+ }
+ static const char* safe_headers[] = {
+ "foo",
+ "x-",
+ "x-foo",
+ "content-disposition",
+ "update",
+ "accept-charseta",
+ "accept_charset",
+ "accept-encodinga",
+ "accept_encoding",
+ "access-control-request-headersa",
+ "access-control-request-header",
+ "access_control_request_header",
+ "access-control-request-methoda",
+ "access_control_request_method",
+ "connectiona",
+ "content-lengtha",
+ "content_length",
+ "cookiea",
+ "cookie2a",
+ "cookie3",
+ "content-transfer-encodinga",
+ "content_transfer_encoding",
+ "datea",
+ "expecta",
+ "hosta",
+ "keep-alivea",
+ "keep_alive",
+ "origina",
+ "referera",
+ "referrer",
+ "tea",
+ "trailera",
+ "transfer-encodinga",
+ "transfer_encoding",
+ "upgradea",
+ "user-agenta",
+ "user_agent",
+ "viaa",
+ };
+ for (size_t i = 0; i < arraysize(safe_headers); ++i) {
+ EXPECT_TRUE(HttpUtil::IsSafeHeader(safe_headers[i])) << safe_headers[i];
+ EXPECT_TRUE(HttpUtil::IsSafeHeader(StringToUpperASCII(std::string(
+ safe_headers[i])))) << safe_headers[i];
+ }
+}
+
+TEST(HttpUtilTest, HasHeader) {
+ static const struct {
+ const char* headers;
+ const char* name;
+ bool expected_result;
+ } tests[] = {
+ { "", "foo", false },
+ { "foo\r\nbar", "foo", false },
+ { "ffoo: 1", "foo", false },
+ { "foo: 1", "foo", true },
+ { "foo: 1\r\nbar: 2", "foo", true },
+ { "fOO: 1\r\nbar: 2", "foo", true },
+ { "g: 0\r\nfoo: 1\r\nbar: 2", "foo", true },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ bool result = HttpUtil::HasHeader(tests[i].headers, tests[i].name);
+ EXPECT_EQ(tests[i].expected_result, result);
+ }
+}
+
+TEST(HttpUtilTest, StripHeaders) {
+ static const char* headers =
+ "Origin: origin\r\n"
+ "Content-Type: text/plain\r\n"
+ "Cookies: foo1\r\n"
+ "Custom: baz\r\n"
+ "COOKIES: foo2\r\n"
+ "Server: Apache\r\n"
+ "OrIGin: origin2\r\n";
+
+ static const char* header_names[] = {
+ "origin", "content-type", "cookies"
+ };
+
+ static const char* expected_stripped_headers =
+ "Custom: baz\r\n"
+ "Server: Apache\r\n";
+
+ EXPECT_EQ(expected_stripped_headers,
+ HttpUtil::StripHeaders(headers, header_names,
+ arraysize(header_names)));
+}
+
+TEST(HttpUtilTest, HeadersIterator) {
+ std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n";
+
+ HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("foo"), it.name());
+ EXPECT_EQ(std::string("1"), it.values());
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("bar"), it.name());
+ EXPECT_EQ(std::string("hello world"), it.values());
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("baz"), it.name());
+ EXPECT_EQ(std::string("3"), it.values());
+
+ EXPECT_FALSE(it.GetNext());
+}
+
+TEST(HttpUtilTest, HeadersIterator_MalformedLine) {
+ std::string headers = "foo: 1\n: 2\n3\nbar: 4";
+
+ HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("foo"), it.name());
+ EXPECT_EQ(std::string("1"), it.values());
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("bar"), it.name());
+ EXPECT_EQ(std::string("4"), it.values());
+
+ EXPECT_FALSE(it.GetNext());
+}
+
+TEST(HttpUtilTest, HeadersIterator_AdvanceTo) {
+ std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4";
+
+ HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+ EXPECT_TRUE(it.AdvanceTo("foo"));
+ EXPECT_EQ("foo", it.name());
+ EXPECT_TRUE(it.AdvanceTo("bar"));
+ EXPECT_EQ("bar", it.name());
+ EXPECT_FALSE(it.AdvanceTo("blat"));
+ EXPECT_FALSE(it.GetNext()); // should be at end of headers
+}
+
+TEST(HttpUtilTest, HeadersIterator_Reset) {
+ std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4";
+ HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+ // Search past "foo".
+ EXPECT_TRUE(it.AdvanceTo("bar"));
+ // Now try advancing to "foo". This time it should fail since the iterator
+ // position is past it.
+ EXPECT_FALSE(it.AdvanceTo("foo"));
+ it.Reset();
+ // Now that we reset the iterator position, we should find 'foo'
+ EXPECT_TRUE(it.AdvanceTo("foo"));
+}
+
+TEST(HttpUtilTest, ValuesIterator) {
+ std::string values = " must-revalidate, no-cache=\"foo, bar\"\t, private ";
+
+ HttpUtil::ValuesIterator it(values.begin(), values.end(), ',');
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("must-revalidate"), it.value());
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("no-cache=\"foo, bar\""), it.value());
+
+ ASSERT_TRUE(it.GetNext());
+ EXPECT_EQ(std::string("private"), it.value());
+
+ EXPECT_FALSE(it.GetNext());
+}
+
+TEST(HttpUtilTest, ValuesIterator_Blanks) {
+ std::string values = " \t ";
+
+ HttpUtil::ValuesIterator it(values.begin(), values.end(), ',');
+
+ EXPECT_FALSE(it.GetNext());
+}
+
+TEST(HttpUtilTest, Unquote) {
+ // Replace <backslash> " with ".
+ EXPECT_STREQ("xyz\"abc", HttpUtil::Unquote("\"xyz\\\"abc\"").c_str());
+
+ // Replace <backslash> <backslash> with <backslash>
+ EXPECT_STREQ("xyz\\abc", HttpUtil::Unquote("\"xyz\\\\abc\"").c_str());
+ EXPECT_STREQ("xyz\\\\\\abc",
+ HttpUtil::Unquote("\"xyz\\\\\\\\\\\\abc\"").c_str());
+
+ // Replace <backslash> X with X
+ EXPECT_STREQ("xyzXabc", HttpUtil::Unquote("\"xyz\\Xabc\"").c_str());
+
+ // Act as identity function on unquoted inputs.
+ EXPECT_STREQ("X", HttpUtil::Unquote("X").c_str());
+ EXPECT_STREQ("\"", HttpUtil::Unquote("\"").c_str());
+
+ // Allow single quotes to act as quote marks.
+ // Not part of RFC 2616.
+ EXPECT_STREQ("x\"", HttpUtil::Unquote("'x\"'").c_str());
+}
+
+TEST(HttpUtilTest, Quote) {
+ EXPECT_STREQ("\"xyz\\\"abc\"", HttpUtil::Quote("xyz\"abc").c_str());
+
+ // Replace <backslash> <backslash> with <backslash>
+ EXPECT_STREQ("\"xyz\\\\abc\"", HttpUtil::Quote("xyz\\abc").c_str());
+
+ // Replace <backslash> X with X
+ EXPECT_STREQ("\"xyzXabc\"", HttpUtil::Quote("xyzXabc").c_str());
+}
+
+TEST(HttpUtilTest, LocateEndOfHeaders) {
+ struct {
+ const char* input;
+ int expected_result;
+ } tests[] = {
+ { "foo\r\nbar\r\n\r\n", 12 },
+ { "foo\nbar\n\n", 9 },
+ { "foo\r\nbar\r\n\r\njunk", 12 },
+ { "foo\nbar\n\njunk", 9 },
+ { "foo\nbar\n\r\njunk", 10 },
+ { "foo\nbar\r\n\njunk", 10 },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ int input_len = static_cast<int>(strlen(tests[i].input));
+ int eoh = HttpUtil::LocateEndOfHeaders(tests[i].input, input_len);
+ EXPECT_EQ(tests[i].expected_result, eoh);
+ }
+}
+
+TEST(HttpUtilTest, AssembleRawHeaders) {
+ struct {
+ const char* input; // with '|' representing '\0'
+ const char* expected_result; // with '\0' changed to '|'
+ } tests[] = {
+ { "HTTP/1.0 200 OK\r\nFoo: 1\r\nBar: 2\r\n\r\n",
+ "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" },
+
+ { "HTTP/1.0 200 OK\nFoo: 1\nBar: 2\n\n",
+ "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" },
+
+ // Valid line continuation (single SP).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " continuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid line continuation (single HT).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "\tcontinuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid line continuation (multiple SP).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " continuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid line continuation (multiple HT).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "\t\t\tcontinuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid line continuation (mixed HT, SP).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " \t \t continuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid multi-line continuation
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " continuation1\n"
+ "\tcontinuation2\n"
+ " continuation3\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation1 continuation2 continuation3|"
+ "Bar: 2||"
+ },
+
+ // Continuation of quoted value.
+ // This is different from what Firefox does, since it
+ // will preserve the LWS.
+ {
+ "HTTP/1.0 200 OK\n"
+ "Etag: \"34534-d3\n"
+ " 134q\"\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Etag: \"34534-d3 134q\"|"
+ "Bar: 2||"
+ },
+
+ // Valid multi-line continuation, full LWS lines
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " \n"
+ "\t\t\t\t\n"
+ "\t continuation\n"
+ "Bar: 2\n\n",
+
+ // One SP per continued line = 3.
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 continuation|"
+ "Bar: 2||"
+ },
+
+ // Valid multi-line continuation, all LWS
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ " \n"
+ "\t\t\t\t\n"
+ "\t \n"
+ "Bar: 2\n\n",
+
+ // One SP per continued line = 3.
+ "HTTP/1.0 200 OK|"
+ "Foo: 1 |"
+ "Bar: 2||"
+ },
+
+ // Valid line continuation (No value bytes in first line).
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo:\n"
+ " value\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: value|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation (can't continue status line).
+ {
+ "HTTP/1.0 200 OK\n"
+ " Foo: 1\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ " Foo: 1|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation (can't continue status line).
+ {
+ "HTTP/1.0\n"
+ " 200 OK\n"
+ "Foo: 1\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0|"
+ " 200 OK|"
+ "Foo: 1|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation (can't continue status line).
+ {
+ "HTTP/1.0 404\n"
+ " Not Found\n"
+ "Foo: 1\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 404|"
+ " Not Found|"
+ "Foo: 1|"
+ "Bar: 2||"
+ },
+
+ // Unterminated status line.
+ {
+ "HTTP/1.0 200 OK",
+
+ "HTTP/1.0 200 OK||"
+ },
+
+ // Single terminated, with headers
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "Bar: 2\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1|"
+ "Bar: 2||"
+ },
+
+ // Not terminated, with headers
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "Bar: 2",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation (VT)
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "\vInvalidContinuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1|"
+ "\vInvalidContinuation|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation (formfeed)
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "\fInvalidContinuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1|"
+ "\fInvalidContinuation|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation -- can't continue header names.
+ {
+ "HTTP/1.0 200 OK\n"
+ "Serv\n"
+ " er: Apache\n"
+ "\tInvalidContinuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Serv|"
+ " er: Apache|"
+ "\tInvalidContinuation|"
+ "Bar: 2||"
+ },
+
+ // Not a line continuation -- no value to continue.
+ {
+ "HTTP/1.0 200 OK\n"
+ "Foo: 1\n"
+ "garbage\n"
+ " not-a-continuation\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ "Foo: 1|"
+ "garbage|"
+ " not-a-continuation|"
+ "Bar: 2||",
+ },
+
+ // Not a line continuation -- no valid name.
+ {
+ "HTTP/1.0 200 OK\n"
+ ": 1\n"
+ " garbage\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ ": 1|"
+ " garbage|"
+ "Bar: 2||",
+ },
+
+ // Not a line continuation -- no valid name (whitespace)
+ {
+ "HTTP/1.0 200 OK\n"
+ " : 1\n"
+ " garbage\n"
+ "Bar: 2\n\n",
+
+ "HTTP/1.0 200 OK|"
+ " : 1|"
+ " garbage|"
+ "Bar: 2||",
+ },
+
+ // Embed NULLs in the status line. They should not be understood
+ // as line separators.
+ {
+ "HTTP/1.0 200 OK|Bar2:0|Baz2:1\r\nFoo: 1\r\nBar: 2\r\n\r\n",
+ "HTTP/1.0 200 OKBar2:0Baz2:1|Foo: 1|Bar: 2||"
+ },
+
+ // Embed NULLs in a header line. They should not be understood as
+ // line separators.
+ {
+ "HTTP/1.0 200 OK\nFoo: 1|Foo2: 3\nBar: 2\n\n",
+ "HTTP/1.0 200 OK|Foo: 1Foo2: 3|Bar: 2||"
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string input = tests[i].input;
+ std::replace(input.begin(), input.end(), '|', '\0');
+ std::string raw = HttpUtil::AssembleRawHeaders(input.data(), input.size());
+ std::replace(raw.begin(), raw.end(), '\0', '|');
+ EXPECT_EQ(tests[i].expected_result, raw);
+ }
+}
+
+// Test SpecForRequest() and PathForRequest().
+TEST(HttpUtilTest, RequestUrlSanitize) {
+ struct {
+ const char* url;
+ const char* expected_spec;
+ const char* expected_path;
+ } tests[] = {
+ { // Check that #hash is removed.
+ "http://www.google.com:78/foobar?query=1#hash",
+ "http://www.google.com:78/foobar?query=1",
+ "/foobar?query=1"
+ },
+ { // The reference may itself contain # -- strip all of it.
+ "http://192.168.0.1?query=1#hash#10#11#13#14",
+ "http://192.168.0.1/?query=1",
+ "/?query=1"
+ },
+ { // Strip username/password.
+ "http://user:pass@google.com",
+ "http://google.com/",
+ "/"
+ }
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ GURL url(GURL(tests[i].url));
+ std::string expected_spec(tests[i].expected_spec);
+ std::string expected_path(tests[i].expected_path);
+
+ EXPECT_EQ(expected_spec, HttpUtil::SpecForRequest(url));
+ EXPECT_EQ(expected_path, HttpUtil::PathForRequest(url));
+ }
+}
+
+TEST(HttpUtilTest, GenerateAcceptLanguageHeader) {
+ EXPECT_EQ(std::string("en-US,fr;q=0.8,de;q=0.6"),
+ HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de"));
+ EXPECT_EQ(std::string("en-US,fr;q=0.8,de;q=0.6,ko;q=0.4,zh-CN;q=0.2,"
+ "ja;q=0.2"),
+ HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de,ko,zh-CN,ja"));
+}
+
+// HttpResponseHeadersTest.GetMimeType also tests ParseContentType.
+TEST(HttpUtilTest, ParseContentType) {
+ const struct {
+ const char* content_type;
+ const char* expected_mime_type;
+ const char* expected_charset;
+ const bool expected_had_charset;
+ const char* expected_boundary;
+ } tests[] = {
+ { "text/html; charset=utf-8",
+ "text/html",
+ "utf-8",
+ true,
+ ""
+ },
+ { "text/html; charset =utf-8",
+ "text/html",
+ "utf-8",
+ true,
+ ""
+ },
+ { "text/html; charset= utf-8",
+ "text/html",
+ "utf-8",
+ true,
+ ""
+ },
+ { "text/html; charset=utf-8 ",
+ "text/html",
+ "utf-8",
+ true,
+ ""
+ },
+ { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs\"",
+ "text/html",
+ "",
+ false,
+ "\"WebKit-ada-df-dsf-adsfadsfs\""
+ },
+ { "text/html; boundary =\"WebKit-ada-df-dsf-adsfadsfs\"",
+ "text/html",
+ "",
+ false,
+ "\"WebKit-ada-df-dsf-adsfadsfs\""
+ },
+ { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\"",
+ "text/html",
+ "",
+ false,
+ "\"WebKit-ada-df-dsf-adsfadsfs\""
+ },
+ { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\" ",
+ "text/html",
+ "",
+ false,
+ "\"WebKit-ada-df-dsf-adsfadsfs\""
+ },
+ { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs \"",
+ "text/html",
+ "",
+ false,
+ "\"WebKit-ada-df-dsf-adsfadsfs \""
+ },
+ { "text/html; boundary=WebKit-ada-df-dsf-adsfadsfs",
+ "text/html",
+ "",
+ false,
+ "WebKit-ada-df-dsf-adsfadsfs"
+ },
+ // TODO(abarth): Add more interesting test cases.
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::string mime_type;
+ std::string charset;
+ bool had_charset = false;
+ std::string boundary;
+ net::HttpUtil::ParseContentType(tests[i].content_type, &mime_type,
+ &charset, &had_charset, &boundary);
+ EXPECT_EQ(tests[i].expected_mime_type, mime_type) << "i=" << i;
+ EXPECT_EQ(tests[i].expected_charset, charset) << "i=" << i;
+ EXPECT_EQ(tests[i].expected_had_charset, had_charset) << "i=" << i;
+ EXPECT_EQ(tests[i].expected_boundary, boundary) << "i=" << i;
+ }
+}
+
+TEST(HttpUtilTest, ParseRanges) {
+ const struct {
+ const char* headers;
+ bool expected_return_value;
+ size_t expected_ranges_size;
+ const struct {
+ int64 expected_first_byte_position;
+ int64 expected_last_byte_position;
+ int64 expected_suffix_length;
+ } expected_ranges[10];
+ } tests[] = {
+ { "Range: bytes=0-10",
+ true,
+ 1,
+ { {0, 10, -1}, }
+ },
+ { "Range: bytes=10-0",
+ false,
+ 0,
+ {}
+ },
+ { "Range: BytES=0-10",
+ true,
+ 1,
+ { {0, 10, -1}, }
+ },
+ { "Range: megabytes=0-10",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes0-10",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes=0-0,0-10,10-20,100-200,100-,-200",
+ true,
+ 6,
+ { {0, 0, -1},
+ {0, 10, -1},
+ {10, 20, -1},
+ {100, 200, -1},
+ {100, -1, -1},
+ {-1, -1, 200},
+ }
+ },
+ { "Range: bytes=0-10\r\n"
+ "Range: bytes=0-10,10-20,100-200,100-,-200",
+ true,
+ 1,
+ { {0, 10, -1}
+ }
+ },
+ { "Range: bytes=",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes=-",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes=0-10-",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes=-0-10",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes =0-10\r\n",
+ true,
+ 1,
+ { {0, 10, -1}
+ }
+ },
+ { "Range: bytes= 0-10 \r\n",
+ true,
+ 1,
+ { {0, 10, -1}
+ }
+ },
+ { "Range: bytes = 0 - 10 \r\n",
+ true,
+ 1,
+ { {0, 10, -1}
+ }
+ },
+ { "Range: bytes= 0-1 0\r\n",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes= 0- -10\r\n",
+ false,
+ 0,
+ {}
+ },
+ { "Range: bytes= 0 - 1 , 10 -20, 100- 200 , 100-, -200 \r\n",
+ true,
+ 5,
+ { {0, 1, -1},
+ {10, 20, -1},
+ {100, 200, -1},
+ {100, -1, -1},
+ {-1, -1, 200},
+ }
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ std::vector<net::HttpByteRange> ranges;
+ bool return_value = HttpUtil::ParseRanges(std::string(tests[i].headers),
+ &ranges);
+ EXPECT_EQ(tests[i].expected_return_value, return_value);
+ if (return_value) {
+ EXPECT_EQ(tests[i].expected_ranges_size, ranges.size());
+ for (size_t j = 0; j < ranges.size(); ++j) {
+ EXPECT_EQ(tests[i].expected_ranges[j].expected_first_byte_position,
+ ranges[j].first_byte_position());
+ EXPECT_EQ(tests[i].expected_ranges[j].expected_last_byte_position,
+ ranges[j].last_byte_position());
+ EXPECT_EQ(tests[i].expected_ranges[j].expected_suffix_length,
+ ranges[j].suffix_length());
+ }
+ }
+ }
+}
+
+namespace {
+void CheckCurrentNameValuePair(HttpUtil::NameValuePairsIterator* parser,
+ bool expect_valid,
+ std::string expected_name,
+ std::string expected_value) {
+ ASSERT_EQ(expect_valid, parser->valid());
+ if (!expect_valid) {
+ return;
+ }
+
+ // Let's make sure that these never change (i.e., when a quoted value is
+ // unquoted, it should be cached on the first calls and not regenerated
+ // later).
+ std::string::const_iterator first_value_begin = parser->value_begin();
+ std::string::const_iterator first_value_end = parser->value_end();
+
+ ASSERT_EQ(expected_name, std::string(parser->name_begin(),
+ parser->name_end()));
+ ASSERT_EQ(expected_name, parser->name());
+ ASSERT_EQ(expected_value, std::string(parser->value_begin(),
+ parser->value_end()));
+ ASSERT_EQ(expected_value, parser->value());
+
+ // Make sure they didn't/don't change.
+ ASSERT_TRUE(first_value_begin == parser->value_begin());
+ ASSERT_TRUE(first_value_end == parser->value_end());
+}
+
+void CheckNextNameValuePair(HttpUtil::NameValuePairsIterator* parser,
+ bool expect_next,
+ bool expect_valid,
+ std::string expected_name,
+ std::string expected_value) {
+ ASSERT_EQ(expect_next, parser->GetNext());
+ ASSERT_EQ(expect_valid, parser->valid());
+ if (!expect_next || !expect_valid) {
+ return;
+ }
+
+ CheckCurrentNameValuePair(parser,
+ expect_valid,
+ expected_name,
+ expected_value);
+}
+
+void CheckInvalidNameValuePair(std::string valid_part,
+ std::string invalid_part) {
+ std::string whole_string = valid_part + invalid_part;
+
+ HttpUtil::NameValuePairsIterator valid_parser(valid_part.begin(),
+ valid_part.end(),
+ ';');
+ HttpUtil::NameValuePairsIterator invalid_parser(whole_string.begin(),
+ whole_string.end(),
+ ';');
+
+ ASSERT_TRUE(valid_parser.valid());
+ ASSERT_TRUE(invalid_parser.valid());
+
+ // Both parsers should return all the same values until "valid_parser" is
+ // exhausted.
+ while (valid_parser.GetNext()) {
+ ASSERT_TRUE(invalid_parser.GetNext());
+ ASSERT_TRUE(valid_parser.valid());
+ ASSERT_TRUE(invalid_parser.valid());
+ ASSERT_EQ(valid_parser.name(), invalid_parser.name());
+ ASSERT_EQ(valid_parser.value(), invalid_parser.value());
+ }
+
+ // valid_parser is exhausted and remains 'valid'
+ ASSERT_TRUE(valid_parser.valid());
+
+ // invalid_parser's corresponding call to GetNext also returns false...
+ ASSERT_FALSE(invalid_parser.GetNext());
+ // ...but the parser is in an invalid state.
+ ASSERT_FALSE(invalid_parser.valid());
+}
+
+} // anonymous namespace
+
+TEST(HttpUtilTest, NameValuePairsIteratorCopyAndAssign) {
+ std::string data = "alpha='\\'a\\''; beta=\" b \"; cappa='c;'; delta=\"d\"";
+ HttpUtil::NameValuePairsIterator parser_a(data.begin(), data.end(), ';');
+
+ EXPECT_TRUE(parser_a.valid());
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser_a, true, true, "alpha", "'a'"));
+
+ HttpUtil::NameValuePairsIterator parser_b(parser_a);
+ // a and b now point to same location
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_b, true, "alpha", "'a'"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_a, true, "alpha", "'a'"));
+
+ // advance a, no effect on b
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser_a, true, true, "beta", " b "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_b, true, "alpha", "'a'"));
+
+ // assign b the current state of a, no effect on a
+ parser_b = parser_a;
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_b, true, "beta", " b "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_a, true, "beta", " b "));
+
+ // advance b, no effect on a
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser_b, true, true, "cappa", "c;"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckCurrentNameValuePair(&parser_a, true, "beta", " b "));
+}
+
+TEST(HttpUtilTest, NameValuePairsIteratorEmptyInput) {
+ std::string data;
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+
+ EXPECT_TRUE(parser.valid());
+ ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
+ &parser, false, true, std::string(), std::string()));
+}
+
+TEST(HttpUtilTest, NameValuePairsIterator) {
+ std::string data = "alpha=1; beta= 2 ;cappa =' 3; ';"
+ "delta= \" \\\"4\\\" \"; e= \" '5'\"; e=6;"
+ "f='\\'\\h\\e\\l\\l\\o\\ \\w\\o\\r\\l\\d\\'';"
+ "g=''; h='hello'";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "alpha", "1"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "beta", "2"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "cappa", " 3; "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "delta", " \"4\" "));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "e", " '5'"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "e", "6"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "f", "'hello world'"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "g", std::string()));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "h", "hello"));
+ ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
+ &parser, false, true, std::string(), std::string()));
+}
+
+TEST(HttpUtilTest, NameValuePairsIteratorIllegalInputs) {
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; beta"));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair(std::string(), "beta"));
+
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; 'beta'=2"));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair(std::string(), "'beta'=2"));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";beta="));
+ ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1",
+ ";beta=;cappa=2"));
+
+ // According to the spec this is an error, but it doesn't seem appropriate to
+ // change our behaviour to be less permissive at this time.
+ // See NameValuePairsIteratorExtraSeparators test
+ // ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";; beta=2"));
+}
+
+// If we are going to support extra separators against the spec, let's just make
+// sure they work rationally.
+TEST(HttpUtilTest, NameValuePairsIteratorExtraSeparators) {
+ std::string data = " ; ;;alpha=1; ;; ; beta= 2;cappa=3;;; ; ";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "alpha", "1"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "beta", "2"));
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "cappa", "3"));
+ ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
+ &parser, false, true, std::string(), std::string()));
+}
+
+// See comments on the implementation of NameValuePairsIterator::GetNext
+// regarding this derogation from the spec.
+TEST(HttpUtilTest, NameValuePairsIteratorMissingEndQuote) {
+ std::string data = "name='value";
+ HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
+ EXPECT_TRUE(parser.valid());
+
+ ASSERT_NO_FATAL_FAILURE(
+ CheckNextNameValuePair(&parser, true, true, "name", "value"));
+ ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
+ &parser, false, true, std::string(), std::string()));
+}
diff --git a/chromium/net/http/http_vary_data.cc b/chromium/net/http/http_vary_data.cc
new file mode 100644
index 00000000000..f102058cdef
--- /dev/null
+++ b/chromium/net/http/http_vary_data.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 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.
+
+#include "net/http/http_vary_data.h"
+
+#include <stdlib.h>
+
+#include "base/pickle.h"
+#include "base/strings/string_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+HttpVaryData::HttpVaryData() : is_valid_(false) {
+}
+
+bool HttpVaryData::Init(const HttpRequestInfo& request_info,
+ const HttpResponseHeaders& response_headers) {
+ base::MD5Context ctx;
+ base::MD5Init(&ctx);
+
+ is_valid_ = false;
+ bool processed_header = false;
+
+ // Feed the MD5 context in the order of the Vary header enumeration. If the
+ // Vary header repeats a header name, then that's OK.
+ //
+ // If the Vary header contains '*' then we should not construct any vary data
+ // since it is all usurped by a '*'. See section 13.6 of RFC 2616.
+ //
+ void* iter = NULL;
+ std::string name = "vary", request_header;
+ while (response_headers.EnumerateHeader(&iter, name, &request_header)) {
+ if (request_header == "*")
+ return false;
+ AddField(request_info, request_header, &ctx);
+ processed_header = true;
+ }
+
+ // Add an implicit 'Vary: cookie' header to any redirect to avoid redirect
+ // loops which may result from redirects that are incorrectly marked as
+ // cachable by the server. Unfortunately, other browsers do not cache
+ // redirects that result from requests containing a cookie header. We are
+ // treading on untested waters here, so we want to be extra careful to make
+ // sure we do not end up with a redirect loop served from cache.
+ //
+ // If there is an explicit 'Vary: cookie' header, then we will just end up
+ // digesting the cookie header twice. Not a problem.
+ //
+ std::string location;
+ if (response_headers.IsRedirect(&location)) {
+ AddField(request_info, "cookie", &ctx);
+ processed_header = true;
+ }
+
+ if (!processed_header)
+ return false;
+
+ base::MD5Final(&request_digest_, &ctx);
+ return is_valid_ = true;
+}
+
+bool HttpVaryData::InitFromPickle(const Pickle& pickle, PickleIterator* iter) {
+ is_valid_ = false;
+ const char* data;
+ if (pickle.ReadBytes(iter, &data, sizeof(request_digest_))) {
+ memcpy(&request_digest_, data, sizeof(request_digest_));
+ return is_valid_ = true;
+ }
+ return false;
+}
+
+void HttpVaryData::Persist(Pickle* pickle) const {
+ DCHECK(is_valid());
+ pickle->WriteBytes(&request_digest_, sizeof(request_digest_));
+}
+
+bool HttpVaryData::MatchesRequest(
+ const HttpRequestInfo& request_info,
+ const HttpResponseHeaders& cached_response_headers) const {
+ HttpVaryData new_vary_data;
+ if (!new_vary_data.Init(request_info, cached_response_headers)) {
+ // This shouldn't happen provided the same response headers passed here
+ // were also used when initializing |this|.
+ NOTREACHED();
+ return false;
+ }
+ return memcmp(&new_vary_data.request_digest_, &request_digest_,
+ sizeof(request_digest_)) == 0;
+}
+
+// static
+std::string HttpVaryData::GetRequestValue(
+ const HttpRequestInfo& request_info,
+ const std::string& request_header) {
+ // Unfortunately, we do not have access to all of the request headers at this
+ // point. Most notably, we do not have access to an Authorization header if
+ // one will be added to the request.
+
+ std::string result;
+ if (request_info.extra_headers.GetHeader(request_header, &result))
+ return result;
+
+ return std::string();
+}
+
+// static
+void HttpVaryData::AddField(const HttpRequestInfo& request_info,
+ const std::string& request_header,
+ base::MD5Context* ctx) {
+ std::string request_value = GetRequestValue(request_info, request_header);
+
+ // Append a character that cannot appear in the request header line so that we
+ // protect against case where the concatenation of two request headers could
+ // look the same for a variety of values for the individual request headers.
+ // For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise.
+ request_value.append(1, '\n');
+
+ base::MD5Update(ctx, request_value);
+}
+
+} // namespace net
diff --git a/chromium/net/http/http_vary_data.h b/chromium/net/http/http_vary_data.h
new file mode 100644
index 00000000000..ec97956e676
--- /dev/null
+++ b/chromium/net/http/http_vary_data.h
@@ -0,0 +1,86 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_HTTP_VARY_DATA_H_
+#define NET_HTTP_HTTP_VARY_DATA_H_
+
+#include "base/md5.h"
+#include "net/base/net_export.h"
+
+class Pickle;
+class PickleIterator;
+
+namespace net {
+
+struct HttpRequestInfo;
+class HttpResponseHeaders;
+
+// Used to implement the HTTP/1.1 Vary header. This class contains a MD5 hash
+// over the request headers indicated by a Vary header.
+//
+// While RFC 2616 requires strict request header comparisons, it is much
+// cheaper to store a MD5 sum, which should be sufficient. Storing a hash also
+// avoids messy privacy issues as some of the request headers could hold
+// sensitive data (e.g., cookies).
+//
+// NOTE: This class does not hold onto the contents of the Vary header.
+// Instead, it relies on the consumer to store that and to supply it again to
+// the MatchesRequest function for comparing against future HTTP requests.
+//
+class NET_EXPORT_PRIVATE HttpVaryData {
+ public:
+ HttpVaryData();
+
+ bool is_valid() const { return is_valid_; }
+
+ // Initialize from a request and its corresponding response headers.
+ //
+ // Returns true if a Vary header was found in the response headers and that
+ // Vary header was not empty and did not contain the '*' value. Upon
+ // success, the object is also marked as valid such that is_valid() will
+ // return true. Otherwise, false is returned to indicate that this object
+ // is marked as invalid.
+ //
+ bool Init(const HttpRequestInfo& request_info,
+ const HttpResponseHeaders& response_headers);
+
+ // Initialize from a pickle that contains data generated by a call to the
+ // vary data's Persist method.
+ //
+ // Upon success, true is returned and the object is marked as valid such that
+ // is_valid() will return true. Otherwise, false is returned to indicate
+ // that this object is marked as invalid.
+ //
+ bool InitFromPickle(const Pickle& pickle, PickleIterator* pickle_iter);
+
+ // Call this method to persist the vary data. Illegal to call this on an
+ // invalid object.
+ void Persist(Pickle* pickle) const;
+
+ // Call this method to test if the given request matches the previous request
+ // with which this vary data corresponds. The |cached_response_headers| must
+ // be the same response headers used to generate this vary data.
+ bool MatchesRequest(const HttpRequestInfo& request_info,
+ const HttpResponseHeaders& cached_response_headers) const;
+
+ private:
+ // Returns the corresponding request header value.
+ static std::string GetRequestValue(const HttpRequestInfo& request_info,
+ const std::string& request_header);
+
+ // Append to the MD5 context for the given request header.
+ static void AddField(const HttpRequestInfo& request_info,
+ const std::string& request_header,
+ base::MD5Context* context);
+
+ // A digested version of the request headers corresponding to the Vary header.
+ base::MD5Digest request_digest_;
+
+ // True when request_digest_ contains meaningful data.
+ bool is_valid_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_VARY_DATA_H_
diff --git a/chromium/net/http/http_vary_data_unittest.cc b/chromium/net/http/http_vary_data_unittest.cc
new file mode 100644
index 00000000000..cffa2d299ac
--- /dev/null
+++ b/chromium/net/http/http_vary_data_unittest.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2006-2008 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 <algorithm>
+
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_vary_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+typedef testing::Test HttpVaryDataTest;
+
+struct TestTransaction {
+ net::HttpRequestInfo request;
+ scoped_refptr<net::HttpResponseHeaders> response;
+
+ void Init(const std::string& request_headers,
+ const std::string& response_headers) {
+ std::string temp(response_headers);
+ std::replace(temp.begin(), temp.end(), '\n', '\0');
+ response = new net::HttpResponseHeaders(temp);
+
+ request.extra_headers.Clear();
+ request.extra_headers.AddHeadersFromString(request_headers);
+ }
+};
+
+} // namespace
+
+TEST(HttpVaryDataTest, IsInvalid) {
+ // All of these responses should result in an invalid vary data object.
+ const char* kTestResponses[] = {
+ "HTTP/1.1 200 OK\n\n",
+ "HTTP/1.1 200 OK\nVary: *\n\n",
+ "HTTP/1.1 200 OK\nVary: cookie, *, bar\n\n",
+ "HTTP/1.1 200 OK\nVary: cookie\nFoo: 1\nVary: *\n\n",
+ };
+
+ for (size_t i = 0; i < arraysize(kTestResponses); ++i) {
+ TestTransaction t;
+ t.Init(std::string(), kTestResponses[i]);
+
+ net::HttpVaryData v;
+ EXPECT_FALSE(v.is_valid());
+ EXPECT_FALSE(v.Init(t.request, *t.response.get()));
+ EXPECT_FALSE(v.is_valid());
+ }
+}
+
+TEST(HttpVaryDataTest, MultipleInit) {
+ net::HttpVaryData v;
+
+ // Init to something valid.
+ TestTransaction t1;
+ t1.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+ EXPECT_TRUE(v.Init(t1.request, *t1.response.get()));
+ EXPECT_TRUE(v.is_valid());
+
+ // Now overwrite by initializing to something invalid.
+ TestTransaction t2;
+ t2.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: *\n\n");
+ EXPECT_FALSE(v.Init(t2.request, *t2.response.get()));
+ EXPECT_FALSE(v.is_valid());
+}
+
+TEST(HttpVaryDataTest, DoesVary) {
+ TestTransaction a;
+ a.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 2", "HTTP/1.1 200 OK\nVary: foo\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response.get()));
+}
+
+TEST(HttpVaryDataTest, DoesVary2) {
+ TestTransaction a;
+ a.Init("Foo: 1\r\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 12\r\nbar: 3", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response.get()));
+}
+
+TEST(HttpVaryDataTest, DoesntVary) {
+ TestTransaction a;
+ a.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_TRUE(v.MatchesRequest(b.request, *b.response.get()));
+}
+
+TEST(HttpVaryDataTest, DoesntVary2) {
+ TestTransaction a;
+ a.Init("Foo: 1\r\nbAr: 2", "HTTP/1.1 200 OK\nVary: foo, bar\n\n");
+
+ TestTransaction b;
+ b.Init("Foo: 1\r\nbaR: 2", "HTTP/1.1 200 OK\nVary: foo\nVary: bar\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_TRUE(v.MatchesRequest(b.request, *b.response.get()));
+}
+
+TEST(HttpVaryDataTest, ImplicitCookieForRedirect) {
+ TestTransaction a;
+ a.Init("Cookie: 1", "HTTP/1.1 301 Moved\nLocation: x\n\n");
+
+ TestTransaction b;
+ b.Init("Cookie: 2", "HTTP/1.1 301 Moved\nLocation: x\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response.get()));
+}
+
+TEST(HttpVaryDataTest, ImplicitCookieForRedirect2) {
+ // This should be no different than the test above
+
+ TestTransaction a;
+ a.Init("Cookie: 1", "HTTP/1.1 301 Moved\nLocation: x\nVary: coOkie\n\n");
+
+ TestTransaction b;
+ b.Init("Cookie: 2", "HTTP/1.1 301 Moved\nLocation: x\nVary: cooKie\n\n");
+
+ net::HttpVaryData v;
+ EXPECT_TRUE(v.Init(a.request, *a.response.get()));
+
+ EXPECT_FALSE(v.MatchesRequest(b.request, *b.response.get()));
+}
diff --git a/chromium/net/http/http_version.h b/chromium/net/http/http_version.h
new file mode 100644
index 00000000000..127e7115bf9
--- /dev/null
+++ b/chromium/net/http/http_version.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2006-2008 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_HTTP_HTTP_VERSION_H_
+#define NET_HTTP_HTTP_VERSION_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+
+// Wrapper for an HTTP (major,minor) version pair.
+class HttpVersion {
+ public:
+ // Default constructor (major=0, minor=0).
+ HttpVersion() : value_(0) { }
+
+ // Build from unsigned major/minor pair.
+ HttpVersion(uint16 major, uint16 minor) : value_(major << 16 | minor) { }
+
+ // Major version number.
+ uint16 major_value() const {
+ return value_ >> 16;
+ }
+
+ // Minor version number.
+ uint16 minor_value() const {
+ return value_ & 0xffff;
+ }
+
+ // Overloaded operators:
+
+ bool operator==(const HttpVersion& v) const {
+ return value_ == v.value_;
+ }
+ bool operator!=(const HttpVersion& v) const {
+ return value_ != v.value_;
+ }
+ bool operator>(const HttpVersion& v) const {
+ return value_ > v.value_;
+ }
+ bool operator>=(const HttpVersion& v) const {
+ return value_ >= v.value_;
+ }
+ bool operator<(const HttpVersion& v) const {
+ return value_ < v.value_;
+ }
+ bool operator<=(const HttpVersion& v) const {
+ return value_ <= v.value_;
+ }
+
+ private:
+ uint32 value_; // Packed as <major>:<minor>
+};
+
+} // namespace net
+
+#endif // NET_HTTP_HTTP_VERSION_H_
diff --git a/chromium/net/http/md4.cc b/chromium/net/http/md4.cc
new file mode 100644
index 00000000000..da1e8d3b202
--- /dev/null
+++ b/chromium/net/http/md4.cc
@@ -0,0 +1,184 @@
+// This is mozilla/security/manager/ssl/src/md4.c, CVS rev. 1.1, with trivial
+// changes to port it to our source tree.
+//
+// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM
+// authentication.
+
+/* vim:set ts=2 sw=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * "clean room" MD4 implementation (see RFC 1320)
+ */
+
+#include "net/http/md4.h"
+
+#include <string.h>
+
+typedef uint32 Uint32;
+typedef uint8 Uint8;
+
+/* the "conditional" function */
+#define F(x,y,z) (((x) & (y)) | (~(x) & (z)))
+
+/* the "majority" function */
+#define G(x,y,z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
+
+/* the "parity" function */
+#define H(x,y,z) ((x) ^ (y) ^ (z))
+
+/* rotate n-bits to the left */
+#define ROTL(x,n) (((x) << (n)) | ((x) >> (0x20 - n)))
+
+/* round 1: [abcd k s]: a = (a + F(b,c,d) + X[k]) <<< s */
+#define RD1(a,b,c,d,k,s) a += F(b,c,d) + X[k]; a = ROTL(a,s)
+
+/* round 2: [abcd k s]: a = (a + G(b,c,d) + X[k] + MAGIC) <<< s */
+#define RD2(a,b,c,d,k,s) a += G(b,c,d) + X[k] + 0x5A827999; a = ROTL(a,s)
+
+/* round 3: [abcd k s]: a = (a + H(b,c,d) + X[k] + MAGIC) <<< s */
+#define RD3(a,b,c,d,k,s) a += H(b,c,d) + X[k] + 0x6ED9EBA1; a = ROTL(a,s)
+
+/* converts from word array to byte array, len is number of bytes */
+static void w2b(Uint8 *out, const Uint32 *in, Uint32 len)
+{
+ Uint8 *bp; const Uint32 *wp, *wpend;
+
+ bp = out;
+ wp = in;
+ wpend = wp + (len >> 2);
+
+ for (; wp != wpend; ++wp, bp += 4)
+ {
+ bp[0] = (Uint8) ((*wp ) & 0xFF);
+ bp[1] = (Uint8) ((*wp >> 8) & 0xFF);
+ bp[2] = (Uint8) ((*wp >> 16) & 0xFF);
+ bp[3] = (Uint8) ((*wp >> 24) & 0xFF);
+ }
+}
+
+/* converts from byte array to word array, len is number of bytes */
+static void b2w(Uint32 *out, const Uint8 *in, Uint32 len)
+{
+ Uint32 *wp; const Uint8 *bp, *bpend;
+
+ wp = out;
+ bp = in;
+ bpend = in + len;
+
+ for (; bp != bpend; bp += 4, ++wp)
+ {
+ *wp = (Uint32) (bp[0] ) |
+ (Uint32) (bp[1] << 8) |
+ (Uint32) (bp[2] << 16) |
+ (Uint32) (bp[3] << 24);
+ }
+}
+
+/* update state: data is 64 bytes in length */
+static void md4step(Uint32 state[4], const Uint8 *data)
+{
+ Uint32 A, B, C, D, X[16];
+
+ b2w(X, data, 64);
+
+ A = state[0];
+ B = state[1];
+ C = state[2];
+ D = state[3];
+
+ RD1(A,B,C,D, 0,3); RD1(D,A,B,C, 1,7); RD1(C,D,A,B, 2,11); RD1(B,C,D,A, 3,19);
+ RD1(A,B,C,D, 4,3); RD1(D,A,B,C, 5,7); RD1(C,D,A,B, 6,11); RD1(B,C,D,A, 7,19);
+ RD1(A,B,C,D, 8,3); RD1(D,A,B,C, 9,7); RD1(C,D,A,B,10,11); RD1(B,C,D,A,11,19);
+ RD1(A,B,C,D,12,3); RD1(D,A,B,C,13,7); RD1(C,D,A,B,14,11); RD1(B,C,D,A,15,19);
+
+ RD2(A,B,C,D, 0,3); RD2(D,A,B,C, 4,5); RD2(C,D,A,B, 8, 9); RD2(B,C,D,A,12,13);
+ RD2(A,B,C,D, 1,3); RD2(D,A,B,C, 5,5); RD2(C,D,A,B, 9, 9); RD2(B,C,D,A,13,13);
+ RD2(A,B,C,D, 2,3); RD2(D,A,B,C, 6,5); RD2(C,D,A,B,10, 9); RD2(B,C,D,A,14,13);
+ RD2(A,B,C,D, 3,3); RD2(D,A,B,C, 7,5); RD2(C,D,A,B,11, 9); RD2(B,C,D,A,15,13);
+
+ RD3(A,B,C,D, 0,3); RD3(D,A,B,C, 8,9); RD3(C,D,A,B, 4,11); RD3(B,C,D,A,12,15);
+ RD3(A,B,C,D, 2,3); RD3(D,A,B,C,10,9); RD3(C,D,A,B, 6,11); RD3(B,C,D,A,14,15);
+ RD3(A,B,C,D, 1,3); RD3(D,A,B,C, 9,9); RD3(C,D,A,B, 5,11); RD3(B,C,D,A,13,15);
+ RD3(A,B,C,D, 3,3); RD3(D,A,B,C,11,9); RD3(C,D,A,B, 7,11); RD3(B,C,D,A,15,15);
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+}
+
+namespace net {
+namespace weak_crypto {
+
+void MD4Sum(const Uint8 *input, Uint32 inputLen, Uint8 *result)
+{
+ Uint8 final[128];
+ Uint32 i, n, m, state[4];
+
+ /* magic initial states */
+ state[0] = 0x67452301;
+ state[1] = 0xEFCDAB89;
+ state[2] = 0x98BADCFE;
+ state[3] = 0x10325476;
+
+ /* compute number of complete 64-byte segments contained in input */
+ m = inputLen >> 6;
+
+ /* digest first m segments */
+ for (i=0; i<m; ++i)
+ md4step(state, (input + (i << 6)));
+
+ /* build final buffer */
+ n = inputLen % 64;
+ memcpy(final, input + (m << 6), n);
+ final[n] = 0x80;
+ memset(final + n + 1, 0, 120 - (n + 1));
+
+ inputLen = inputLen << 3;
+ w2b(final + (n >= 56 ? 120 : 56), &inputLen, 4);
+
+ md4step(state, final);
+ if (n >= 56)
+ md4step(state, final + 64);
+
+ /* copy state to result */
+ w2b(result, state, 16);
+}
+
+} // namespace net::weak_crypto
+} // namespace net
diff --git a/chromium/net/http/md4.h b/chromium/net/http/md4.h
new file mode 100644
index 00000000000..b416e261de0
--- /dev/null
+++ b/chromium/net/http/md4.h
@@ -0,0 +1,74 @@
+// This is mozilla/security/manager/ssl/src/md4.h, CVS rev. 1.1, with trivial
+// changes to port it to our source tree.
+//
+// WARNING: MD4 is cryptographically weak. Do not use MD4 except in NTLM
+// authentication.
+
+/* vim:set ts=2 sw=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla.
+ *
+ * The Initial Developer of the Original Code is IBM Corporation.
+ * Portions created by IBM Corporation are Copyright (C) 2003
+ * IBM Corporation. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_HTTP_MD4_H_
+#define NET_HTTP_MD4_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+namespace weak_crypto {
+
+/**
+ * MD4Sum - computes the MD4 sum over the input buffer per RFC 1320
+ *
+ * @param input
+ * buffer containing input data
+ * @param inputLen
+ * length of input buffer (number of bytes)
+ * @param result
+ * 16-byte buffer that will contain the MD4 sum upon return
+ *
+ * NOTE: MD4 is superceded by MD5. do not use MD4 unless required by the
+ * protocol you are implementing (e.g., NTLM requires MD4).
+ *
+ * NOTE: this interface is designed for relatively small buffers. A streaming
+ * interface would make more sense if that were a requirement. Currently, this
+ * is good enough for the applications we care about.
+ */
+void MD4Sum(const uint8 *input, uint32 inputLen, uint8 *result);
+
+} // namespace net::weak_crypto
+} // namespace net
+
+#endif // NET_HTTP_MD4_H_
diff --git a/chromium/net/http/mock_allow_url_security_manager.cc b/chromium/net/http/mock_allow_url_security_manager.cc
new file mode 100644
index 00000000000..e8b2c4ffe2b
--- /dev/null
+++ b/chromium/net/http/mock_allow_url_security_manager.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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/http/mock_allow_url_security_manager.h"
+
+namespace net {
+
+MockAllowURLSecurityManager::MockAllowURLSecurityManager() {}
+
+MockAllowURLSecurityManager::~MockAllowURLSecurityManager() {}
+
+bool MockAllowURLSecurityManager::CanUseDefaultCredentials(
+ const GURL& auth_origin) const {
+ return true;
+}
+
+bool MockAllowURLSecurityManager::CanDelegate(const GURL& auth_origin) const {
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/http/mock_allow_url_security_manager.h b/chromium/net/http/mock_allow_url_security_manager.h
new file mode 100644
index 00000000000..8fbe2c54204
--- /dev/null
+++ b/chromium/net/http/mock_allow_url_security_manager.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011 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_HTTP_MOCK_ALLOW_URL_SECURITY_MANAGER_H_
+#define NET_HTTP_MOCK_ALLOW_URL_SECURITY_MANAGER_H_
+
+#include "net/http/url_security_manager.h"
+
+namespace net {
+
+// An URLSecurityManager which is very permissive and which should only be used
+// in unit testing.
+class MockAllowURLSecurityManager : public URLSecurityManager {
+ public:
+ MockAllowURLSecurityManager();
+ virtual ~MockAllowURLSecurityManager();
+
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) const OVERRIDE;
+ virtual bool CanDelegate(const GURL& auth_origin) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockAllowURLSecurityManager);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_MOCK_ALLOW_URL_SECURITY_MANAGER_H_
diff --git a/chromium/net/http/mock_gssapi_library_posix.cc b/chromium/net/http/mock_gssapi_library_posix.cc
new file mode 100644
index 00000000000..b4d651461ec
--- /dev/null
+++ b/chromium/net/http/mock_gssapi_library_posix.cc
@@ -0,0 +1,480 @@
+// Copyright (c) 2010 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/http/mock_gssapi_library_posix.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+struct GssNameMockImpl {
+ std::string name;
+ gss_OID_desc name_type;
+};
+
+} // namespace test
+
+namespace {
+
+// gss_OID helpers.
+// NOTE: gss_OID's do not own the data they point to, which should be static.
+void ClearOid(gss_OID dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ dest->elements = NULL;
+}
+
+void SetOid(gss_OID dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length)
+ dest->elements = const_cast<void*>(src);
+}
+
+void CopyOid(gss_OID dest, const gss_OID_desc* src) {
+ if (!dest)
+ return;
+ ClearOid(dest);
+ if (!src)
+ return;
+ SetOid(dest, src->elements, src->length);
+}
+
+// gss_buffer_t helpers.
+void ClearBuffer(gss_buffer_t dest) {
+ if (!dest)
+ return;
+ dest->length = 0;
+ delete [] reinterpret_cast<char*>(dest->value);
+ dest->value = NULL;
+}
+
+void SetBuffer(gss_buffer_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ dest->length = length;
+ if (length) {
+ dest->value = new char[length];
+ memcpy(dest->value, src, length);
+ }
+}
+
+void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) {
+ if (!dest)
+ return;
+ ClearBuffer(dest);
+ if (!src)
+ return;
+ SetBuffer(dest, src->value, src->length);
+}
+
+std::string BufferToString(const gss_buffer_t src) {
+ std::string dest;
+ if (!src)
+ return dest;
+ const char* string = reinterpret_cast<char*>(src->value);
+ dest.assign(string, src->length);
+ return dest;
+}
+
+void BufferFromString(const std::string& src, gss_buffer_t dest) {
+ if (!dest)
+ return;
+ SetBuffer(dest, src.c_str(), src.length());
+}
+
+// gss_name_t helpers.
+void ClearName(gss_name_t dest) {
+ if (!dest)
+ return;
+ test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
+ name->name.clear();
+ ClearOid(&name->name_type);
+}
+
+void SetName(gss_name_t dest, const void* src, size_t length) {
+ if (!dest)
+ return;
+ ClearName(dest);
+ if (!src)
+ return;
+ test::GssNameMockImpl* name = reinterpret_cast<test::GssNameMockImpl*>(dest);
+ name->name.assign(reinterpret_cast<const char*>(src), length);
+}
+
+std::string NameToString(const gss_name_t& src) {
+ std::string dest;
+ if (!src)
+ return dest;
+ test::GssNameMockImpl* string =
+ reinterpret_cast<test::GssNameMockImpl*>(src);
+ dest = string->name;
+ return dest;
+}
+
+void NameFromString(const std::string& src, gss_name_t dest) {
+ if (!dest)
+ return;
+ SetName(dest, src.c_str(), src.length());
+}
+
+} // namespace
+
+namespace test {
+
+GssContextMockImpl::GssContextMockImpl()
+ : lifetime_rec(0),
+ ctx_flags(0),
+ locally_initiated(0),
+ open(0) {
+ ClearOid(&mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const GssContextMockImpl& other)
+ : src_name(other.src_name),
+ targ_name(other.targ_name),
+ lifetime_rec(other.lifetime_rec),
+ ctx_flags(other.ctx_flags),
+ locally_initiated(other.locally_initiated),
+ open(other.open) {
+ CopyOid(&mech_type, &other.mech_type);
+}
+
+GssContextMockImpl::GssContextMockImpl(const char* src_name_in,
+ const char* targ_name_in,
+ OM_uint32 lifetime_rec_in,
+ const gss_OID_desc& mech_type_in,
+ OM_uint32 ctx_flags_in,
+ int locally_initiated_in,
+ int open_in)
+ : src_name(src_name_in ? src_name_in : ""),
+ targ_name(targ_name_in ? targ_name_in : ""),
+ lifetime_rec(lifetime_rec_in),
+ ctx_flags(ctx_flags_in),
+ locally_initiated(locally_initiated_in),
+ open(open_in) {
+ CopyOid(&mech_type, &mech_type_in);
+}
+
+GssContextMockImpl::~GssContextMockImpl() {
+ ClearOid(&mech_type);
+}
+
+void GssContextMockImpl::Assign(
+ const GssContextMockImpl& other) {
+ if (&other == this)
+ return;
+ src_name = other.src_name;
+ targ_name = other.targ_name;
+ lifetime_rec = other.lifetime_rec;
+ CopyOid(&mech_type, &other.mech_type);
+ ctx_flags = other.ctx_flags;
+ locally_initiated = other.locally_initiated;
+ open = other.open;
+}
+
+MockGSSAPILibrary::SecurityContextQuery::SecurityContextQuery()
+ : expected_package(),
+ response_code(0),
+ minor_response_code(0),
+ context_info() {
+ expected_input_token.length = 0;
+ expected_input_token.value = NULL;
+ output_token.length = 0;
+ output_token.value = NULL;
+}
+
+MockGSSAPILibrary::SecurityContextQuery::SecurityContextQuery(
+ const std::string& in_expected_package,
+ OM_uint32 in_response_code,
+ OM_uint32 in_minor_response_code,
+ const test::GssContextMockImpl& in_context_info,
+ const char* in_expected_input_token,
+ const char* in_output_token)
+ : expected_package(in_expected_package),
+ response_code(in_response_code),
+ minor_response_code(in_minor_response_code),
+ context_info(in_context_info) {
+ if (in_expected_input_token) {
+ expected_input_token.length = strlen(in_expected_input_token);
+ expected_input_token.value = const_cast<char*>(in_expected_input_token);
+ } else {
+ expected_input_token.length = 0;
+ expected_input_token.value = NULL;
+ }
+
+ if (in_output_token) {
+ output_token.length = strlen(in_output_token);
+ output_token.value = const_cast<char*>(in_output_token);
+ } else {
+ output_token.length = 0;
+ output_token.value = NULL;
+ }
+}
+
+MockGSSAPILibrary::SecurityContextQuery::~SecurityContextQuery() {}
+
+MockGSSAPILibrary::MockGSSAPILibrary() {
+}
+
+MockGSSAPILibrary::~MockGSSAPILibrary() {
+}
+
+void MockGSSAPILibrary::ExpectSecurityContext(
+ const std::string& expected_package,
+ OM_uint32 response_code,
+ OM_uint32 minor_response_code,
+ const GssContextMockImpl& context_info,
+ const gss_buffer_desc& expected_input_token,
+ const gss_buffer_desc& output_token) {
+ SecurityContextQuery security_query;
+ security_query.expected_package = expected_package;
+ security_query.response_code = response_code;
+ security_query.minor_response_code = minor_response_code;
+ security_query.context_info.Assign(context_info);
+ security_query.expected_input_token = expected_input_token;
+ security_query.output_token = output_token;
+ expected_security_queries_.push_back(security_query);
+}
+
+bool MockGSSAPILibrary::Init() {
+ return true;
+}
+
+// These methods match the ones in the GSSAPI library.
+OM_uint32 MockGSSAPILibrary::import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!output_name)
+ return GSS_S_BAD_NAME;
+ if (!input_name_buffer)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ if (!input_name_type)
+ return GSS_S_BAD_NAMETYPE;
+ GssNameMockImpl* output = new GssNameMockImpl;
+ if (output == NULL)
+ return GSS_S_FAILURE;
+ output->name_type.length = 0;
+ output->name_type.elements = NULL;
+
+ // Save the data.
+ output->name = BufferToString(input_name_buffer);
+ CopyOid(&output->name_type, input_name_type);
+ *output_name = reinterpret_cast<gss_name_t>(output);
+
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!input_name)
+ return GSS_S_BAD_NAME;
+ if (!*input_name)
+ return GSS_S_COMPLETE;
+ GssNameMockImpl* name = *reinterpret_cast<GssNameMockImpl**>(input_name);
+ ClearName(*input_name);
+ delete name;
+ *input_name = NULL;
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!buffer)
+ return GSS_S_BAD_NAME;
+ ClearBuffer(buffer);
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!input_name)
+ return GSS_S_BAD_NAME;
+ if (!output_name_buffer)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ if (!output_name_type)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ std::string name(NameToString(input_name));
+ BufferFromString(name, output_name_buffer);
+ GssNameMockImpl* internal_name =
+ *reinterpret_cast<GssNameMockImpl**>(input_name);
+ if (output_name_type)
+ *output_name_type = internal_name ? &internal_name->name_type : NULL;
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_context,
+ gss_buffer_t status_string) {
+ if (minor_status)
+ *minor_status = 0;
+ std::string msg = base::StringPrintf("Value: %u, Type %u",
+ status_value,
+ status_type);
+ if (message_context)
+ *message_context = 0;
+ BufferFromString(msg, status_string);
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl** internal_context_handle =
+ reinterpret_cast<test::GssContextMockImpl**>(context_handle);
+ // Create it if necessary.
+ if (!*internal_context_handle) {
+ *internal_context_handle = new GssContextMockImpl;
+ }
+ EXPECT_TRUE(*internal_context_handle);
+ GssContextMockImpl& context = **internal_context_handle;
+ if (expected_security_queries_.empty()) {
+ return GSS_S_UNAVAILABLE;
+ }
+ SecurityContextQuery security_query = expected_security_queries_.front();
+ expected_security_queries_.pop_front();
+ EXPECT_EQ(std::string("Negotiate"), security_query.expected_package);
+ OM_uint32 major_status = security_query.response_code;
+ if (minor_status)
+ *minor_status = security_query.minor_response_code;
+ context.src_name = security_query.context_info.src_name;
+ context.targ_name = security_query.context_info.targ_name;
+ context.lifetime_rec = security_query.context_info.lifetime_rec;
+ CopyOid(&context.mech_type, &security_query.context_info.mech_type);
+ context.ctx_flags = security_query.context_info.ctx_flags;
+ context.locally_initiated = security_query.context_info.locally_initiated;
+ context.open = security_query.context_info.open;
+ if (!input_token) {
+ EXPECT_FALSE(security_query.expected_input_token.length);
+ } else {
+ EXPECT_EQ(input_token->length, security_query.expected_input_token.length);
+ if (input_token->length) {
+ EXPECT_EQ(0, memcmp(input_token->value,
+ security_query.expected_input_token.value,
+ input_token->length));
+ }
+ }
+ CopyBuffer(output_token, &security_query.output_token);
+ if (actual_mech_type)
+ CopyOid(*actual_mech_type, mech_type);
+ if (ret_flags)
+ *ret_flags = req_flags;
+ return major_status;
+}
+
+OM_uint32 MockGSSAPILibrary::wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) {
+ if (minor_status)
+ *minor_status = 0;
+ ADD_FAILURE();
+ return GSS_S_UNAVAILABLE;
+}
+
+OM_uint32 MockGSSAPILibrary::delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl** internal_context_handle =
+ reinterpret_cast<GssContextMockImpl**>(context_handle);
+ if (*internal_context_handle) {
+ delete *internal_context_handle;
+ *internal_context_handle = NULL;
+ }
+ return GSS_S_COMPLETE;
+}
+
+OM_uint32 MockGSSAPILibrary::inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) {
+ if (minor_status)
+ *minor_status = 0;
+ if (!context_handle)
+ return GSS_S_CALL_BAD_STRUCTURE;
+ GssContextMockImpl* internal_context_ptr =
+ reinterpret_cast<GssContextMockImpl*>(context_handle);
+ GssContextMockImpl& context = *internal_context_ptr;
+ if (src_name)
+ NameFromString(context.src_name, *src_name);
+ if (targ_name)
+ NameFromString(context.targ_name, *targ_name);
+ if (lifetime_rec)
+ *lifetime_rec = context.lifetime_rec;
+ if (mech_type)
+ CopyOid(*mech_type, &context.mech_type);
+ if (ctx_flags)
+ *ctx_flags = context.ctx_flags;
+ if (locally_initiated)
+ *locally_initiated = context.locally_initiated;
+ if (open)
+ *open = context.open;
+ return GSS_S_COMPLETE;
+}
+
+} // namespace test
+
+} // namespace net
+
diff --git a/chromium/net/http/mock_gssapi_library_posix.h b/chromium/net/http/mock_gssapi_library_posix.h
new file mode 100644
index 00000000000..d35f7f5fcdb
--- /dev/null
+++ b/chromium/net/http/mock_gssapi_library_posix.h
@@ -0,0 +1,199 @@
+// Copyright (c) 2011 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_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+#define NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+
+#include <list>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "net/http/http_auth_gssapi_posix.h"
+
+#if defined(OS_MACOSX) && defined(MAC_OS_X_VERSION_10_9) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_9
+// Including gssapi.h directly is deprecated in the 10.9 SDK.
+#include <GSS/gssapi.h>
+#else
+#include <gssapi.h>
+#endif
+
+namespace net {
+
+namespace test {
+
+class GssContextMockImpl {
+ public:
+ GssContextMockImpl();
+ GssContextMockImpl(const GssContextMockImpl& other);
+ GssContextMockImpl(const char* src_name,
+ const char* targ_name,
+ OM_uint32 lifetime_rec,
+ const gss_OID_desc& mech_type,
+ OM_uint32 ctx_flags,
+ int locally_initiated,
+ int open);
+ ~GssContextMockImpl();
+
+ void Assign(const GssContextMockImpl& other);
+
+ std::string src_name;
+ std::string targ_name;
+ OM_uint32 lifetime_rec;
+ gss_OID_desc mech_type;
+ OM_uint32 ctx_flags;
+ int locally_initiated;
+ int open;
+};
+
+// The MockGSSAPILibrary class is intended for unit tests which want to bypass
+// the system GSSAPI library calls.
+class MockGSSAPILibrary : public GSSAPILibrary {
+ public:
+ // Unit tests need access to this. "Friend"ing didn't help.
+ struct SecurityContextQuery {
+ SecurityContextQuery();
+ SecurityContextQuery(const std::string& expected_package,
+ OM_uint32 response_code,
+ OM_uint32 minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const char* expected_input_token,
+ const char* output_token);
+ ~SecurityContextQuery();
+
+ std::string expected_package;
+ OM_uint32 response_code;
+ OM_uint32 minor_response_code;
+ test::GssContextMockImpl context_info;
+ gss_buffer_desc expected_input_token;
+ gss_buffer_desc output_token;
+ };
+
+ MockGSSAPILibrary();
+ virtual ~MockGSSAPILibrary();
+
+ // Establishes an expectation for a |init_sec_context()| call.
+ //
+ // Each expectation established by |ExpectSecurityContext()| must be
+ // matched by a call to |init_sec_context()| during the lifetime of
+ // the MockGSSAPILibrary. The |expected_package| argument must equal the
+ // value associated with the |target_name| argument to |init_sec_context()|
+ // for there to be a match. The expectations also establish an explicit
+ // ordering.
+ //
+ // For example, this sequence will be successful.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ // lib.init_sec_context("Negotiate", ...)
+ //
+ // This sequence will fail since the queries do not occur in the order
+ // established by the expectations.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ //
+ // This sequence will fail because there were not enough queries.
+ // MockGSSAPILibrary lib;
+ // lib.ExpectSecurityContext("NTLM", ...)
+ // lib.ExpectSecurityContext("Negotiate", ...)
+ // lib.init_sec_context("NTLM", ...)
+ //
+ // |response_code| is used as the return value for |init_sec_context()|.
+ // If |response_code| is GSS_S_COMPLETE,
+ //
+ // |context_info| is the expected value of the |**context_handle| in after
+ // |init_sec_context()| returns.
+ void ExpectSecurityContext(const std::string& expected_package,
+ OM_uint32 response_code,
+ OM_uint32 minor_response_code,
+ const test::GssContextMockImpl& context_info,
+ const gss_buffer_desc& expected_input_token,
+ const gss_buffer_desc& output_token);
+
+ // GSSAPILibrary methods:
+
+ // Initializes the library, including any necessary dynamic libraries.
+ // This is done separately from construction (which happens at startup time)
+ // in order to delay work until the class is actually needed.
+ virtual bool Init() OVERRIDE;
+
+ // These methods match the ones in the GSSAPI library.
+ virtual OM_uint32 import_name(
+ OM_uint32* minor_status,
+ const gss_buffer_t input_name_buffer,
+ const gss_OID input_name_type,
+ gss_name_t* output_name) OVERRIDE;
+ virtual OM_uint32 release_name(
+ OM_uint32* minor_status,
+ gss_name_t* input_name) OVERRIDE;
+ virtual OM_uint32 release_buffer(
+ OM_uint32* minor_status,
+ gss_buffer_t buffer) OVERRIDE;
+ virtual OM_uint32 display_name(
+ OM_uint32* minor_status,
+ const gss_name_t input_name,
+ gss_buffer_t output_name_buffer,
+ gss_OID* output_name_type) OVERRIDE;
+ virtual OM_uint32 display_status(
+ OM_uint32* minor_status,
+ OM_uint32 status_value,
+ int status_type,
+ const gss_OID mech_type,
+ OM_uint32* message_contex,
+ gss_buffer_t status_string) OVERRIDE;
+ virtual OM_uint32 init_sec_context(
+ OM_uint32* minor_status,
+ const gss_cred_id_t initiator_cred_handle,
+ gss_ctx_id_t* context_handle,
+ const gss_name_t target_name,
+ const gss_OID mech_type,
+ OM_uint32 req_flags,
+ OM_uint32 time_req,
+ const gss_channel_bindings_t input_chan_bindings,
+ const gss_buffer_t input_token,
+ gss_OID* actual_mech_type,
+ gss_buffer_t output_token,
+ OM_uint32* ret_flags,
+ OM_uint32* time_rec) OVERRIDE;
+ virtual OM_uint32 wrap_size_limit(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ int conf_req_flag,
+ gss_qop_t qop_req,
+ OM_uint32 req_output_size,
+ OM_uint32* max_input_size) OVERRIDE;
+ virtual OM_uint32 delete_sec_context(
+ OM_uint32* minor_status,
+ gss_ctx_id_t* context_handle,
+ gss_buffer_t output_token) OVERRIDE;
+ virtual OM_uint32 inquire_context(
+ OM_uint32* minor_status,
+ const gss_ctx_id_t context_handle,
+ gss_name_t* src_name,
+ gss_name_t* targ_name,
+ OM_uint32* lifetime_rec,
+ gss_OID* mech_type,
+ OM_uint32* ctx_flags,
+ int* locally_initiated,
+ int* open) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPICycle);
+
+ // |expected_security_queries| contains an ordered list of expected
+ // |init_sec_context()| calls and the return values for those
+ // calls.
+ std::list<SecurityContextQuery> expected_security_queries_;
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
+
diff --git a/chromium/net/http/mock_http_cache.cc b/chromium/net/http/mock_http_cache.cc
new file mode 100644
index 00000000000..85ffa74584e
--- /dev/null
+++ b/chromium/net/http/mock_http_cache.cc
@@ -0,0 +1,619 @@
+// Copyright (c) 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.
+
+#include "net/http/mock_http_cache.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// We can override the test mode for a given operation by setting this global
+// variable.
+int g_test_mode = 0;
+
+int GetTestModeForEntry(const std::string& key) {
+ // 'key' is prefixed with an identifier if it corresponds to a cached POST.
+ // Skip past that to locate the actual URL.
+ //
+ // TODO(darin): It breaks the abstraction a bit that we assume 'key' is an
+ // URL corresponding to a registered MockTransaction. It would be good to
+ // have another way to access the test_mode.
+ GURL url;
+ if (isdigit(key[0])) {
+ size_t slash = key.find('/');
+ DCHECK(slash != std::string::npos);
+ url = GURL(key.substr(slash + 1));
+ } else {
+ url = GURL(key);
+ }
+ const MockTransaction* t = FindMockTransaction(url);
+ DCHECK(t);
+ return t->test_mode;
+}
+
+void CallbackForwader(const net::CompletionCallback& callback, int result) {
+ callback.Run(result);
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+struct MockDiskEntry::CallbackInfo {
+ scoped_refptr<MockDiskEntry> entry;
+ net::CompletionCallback callback;
+ int result;
+};
+
+MockDiskEntry::MockDiskEntry()
+ : test_mode_(0), doomed_(false), sparse_(false),
+ fail_requests_(false), busy_(false), delayed_(false) {
+}
+
+MockDiskEntry::MockDiskEntry(const std::string& key)
+ : key_(key), doomed_(false), sparse_(false),
+ fail_requests_(false), busy_(false), delayed_(false) {
+ test_mode_ = GetTestModeForEntry(key);
+}
+
+void MockDiskEntry::Doom() {
+ doomed_ = true;
+}
+
+void MockDiskEntry::Close() {
+ Release();
+}
+
+std::string MockDiskEntry::GetKey() const {
+ return key_;
+}
+
+base::Time MockDiskEntry::GetLastUsed() const {
+ return base::Time::FromInternalValue(0);
+}
+
+base::Time MockDiskEntry::GetLastModified() const {
+ return base::Time::FromInternalValue(0);
+}
+
+int32 MockDiskEntry::GetDataSize(int index) const {
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
+ return static_cast<int32>(data_[index].size());
+}
+
+int MockDiskEntry::ReadData(
+ int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
+ DCHECK(!callback.is_null());
+
+ if (fail_requests_)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ if (offset < 0 || offset > static_cast<int>(data_[index].size()))
+ return net::ERR_FAILED;
+ if (static_cast<size_t>(offset) == data_[index].size())
+ return 0;
+
+ int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset);
+ memcpy(buf->data(), &data_[index][offset], num);
+
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
+ return num;
+
+ CallbackLater(callback, num);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskEntry::WriteData(
+ int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback, bool truncate) {
+ DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
+ DCHECK(!callback.is_null());
+ DCHECK(truncate);
+
+ if (fail_requests_) {
+ CallbackLater(callback, net::ERR_CACHE_READ_FAILURE);
+ return net::ERR_IO_PENDING;
+ }
+
+ if (offset < 0 || offset > static_cast<int>(data_[index].size()))
+ return net::ERR_FAILED;
+
+ data_[index].resize(offset + buf_len);
+ if (buf_len)
+ memcpy(&data_[index][offset], buf->data(), buf_len);
+
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
+ return buf_len;
+
+ CallbackLater(callback, buf_len);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskEntry::ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (!sparse_ || busy_)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ if (offset < 0)
+ return net::ERR_FAILED;
+
+ if (fail_requests_)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ DCHECK(offset < kint32max);
+ int real_offset = static_cast<int>(offset);
+ if (!buf_len)
+ return 0;
+
+ int num = std::min(static_cast<int>(data_[1].size()) - real_offset,
+ buf_len);
+ memcpy(buf->data(), &data_[1][real_offset], num);
+
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
+ return num;
+
+ CallbackLater(callback, num);
+ busy_ = true;
+ delayed_ = false;
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskEntry::WriteSparseData(int64 offset, net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (busy_)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ if (!sparse_) {
+ if (data_[1].size())
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ sparse_ = true;
+ }
+ if (offset < 0)
+ return net::ERR_FAILED;
+ if (!buf_len)
+ return 0;
+
+ if (fail_requests_)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ DCHECK(offset < kint32max);
+ int real_offset = static_cast<int>(offset);
+
+ if (static_cast<int>(data_[1].size()) < real_offset + buf_len)
+ data_[1].resize(real_offset + buf_len);
+
+ memcpy(&data_[1][real_offset], buf->data(), buf_len);
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
+ return buf_len;
+
+ CallbackLater(callback, buf_len);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskEntry::GetAvailableRange(int64 offset, int len, int64* start,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (!sparse_ || busy_)
+ return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
+ if (offset < 0)
+ return net::ERR_FAILED;
+
+ if (fail_requests_)
+ return net::ERR_CACHE_READ_FAILURE;
+
+ *start = offset;
+ DCHECK(offset < kint32max);
+ int real_offset = static_cast<int>(offset);
+ if (static_cast<int>(data_[1].size()) < real_offset)
+ return 0;
+
+ int num = std::min(static_cast<int>(data_[1].size()) - real_offset, len);
+ int count = 0;
+ for (; num > 0; num--, real_offset++) {
+ if (!count) {
+ if (data_[1][real_offset]) {
+ count++;
+ *start = real_offset;
+ }
+ } else {
+ if (!data_[1][real_offset])
+ break;
+ count++;
+ }
+ }
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
+ return count;
+
+ CallbackLater(callback, count);
+ return net::ERR_IO_PENDING;
+}
+
+bool MockDiskEntry::CouldBeSparse() const {
+ return sparse_;
+}
+
+void MockDiskEntry::CancelSparseIO() {
+ cancel_ = true;
+}
+
+int MockDiskEntry::ReadyForSparseIO(const net::CompletionCallback& callback) {
+ if (!cancel_)
+ return net::OK;
+
+ cancel_ = false;
+ DCHECK(!callback.is_null());
+ if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
+ return net::OK;
+
+ // The pending operation is already in the message loop (and hopefully
+ // already in the second pass). Just notify the caller that it finished.
+ CallbackLater(callback, 0);
+ return net::ERR_IO_PENDING;
+}
+
+// If |value| is true, don't deliver any completion callbacks until called
+// again with |value| set to false. Caution: remember to enable callbacks
+// again or all subsequent tests will fail.
+// Static.
+void MockDiskEntry::IgnoreCallbacks(bool value) {
+ if (ignore_callbacks_ == value)
+ return;
+ ignore_callbacks_ = value;
+ if (!value)
+ StoreAndDeliverCallbacks(false, NULL, net::CompletionCallback(), 0);
+}
+
+MockDiskEntry::~MockDiskEntry() {
+}
+
+// Unlike the callbacks for MockHttpTransaction, we want this one to run even
+// if the consumer called Close on the MockDiskEntry. We achieve that by
+// leveraging the fact that this class is reference counted.
+void MockDiskEntry::CallbackLater(const net::CompletionCallback& callback,
+ int result) {
+ if (ignore_callbacks_)
+ return StoreAndDeliverCallbacks(true, this, callback, result);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockDiskEntry::RunCallback, this, callback, result));
+}
+
+void MockDiskEntry::RunCallback(
+ const net::CompletionCallback& callback, int result) {
+ if (busy_) {
+ // This is kind of hacky, but controlling the behavior of just this entry
+ // from a test is sort of complicated. What we really want to do is
+ // delay the delivery of a sparse IO operation a little more so that the
+ // request start operation (async) will finish without seeing the end of
+ // this operation (already posted to the message loop)... and without
+ // just delaying for n mS (which may cause trouble with slow bots). So
+ // we re-post this operation (all async sparse IO operations will take two
+ // trips through the message loop instead of one).
+ if (!delayed_) {
+ delayed_ = true;
+ return CallbackLater(callback, result);
+ }
+ }
+ busy_ = false;
+ callback.Run(result);
+}
+
+// When |store| is true, stores the callback to be delivered later; otherwise
+// delivers any callback previously stored.
+// Static.
+void MockDiskEntry::StoreAndDeliverCallbacks(
+ bool store, MockDiskEntry* entry, const net::CompletionCallback& callback,
+ int result) {
+ static std::vector<CallbackInfo> callback_list;
+ if (store) {
+ CallbackInfo c = {entry, callback, result};
+ callback_list.push_back(c);
+ } else {
+ for (size_t i = 0; i < callback_list.size(); i++) {
+ CallbackInfo& c = callback_list[i];
+ c.entry->CallbackLater(c.callback, c.result);
+ }
+ callback_list.clear();
+ }
+}
+
+// Statics.
+bool MockDiskEntry::cancel_ = false;
+bool MockDiskEntry::ignore_callbacks_ = false;
+
+//-----------------------------------------------------------------------------
+
+MockDiskCache::MockDiskCache()
+ : open_count_(0), create_count_(0), fail_requests_(false),
+ soft_failures_(false), double_create_check_(true) {
+}
+
+MockDiskCache::~MockDiskCache() {
+ ReleaseAll();
+}
+
+net::CacheType MockDiskCache::GetCacheType() const {
+ return net::DISK_CACHE;
+}
+
+int32 MockDiskCache::GetEntryCount() const {
+ return static_cast<int32>(entries_.size());
+}
+
+int MockDiskCache::OpenEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (fail_requests_)
+ return net::ERR_CACHE_OPEN_FAILURE;
+
+ EntryMap::iterator it = entries_.find(key);
+ if (it == entries_.end())
+ return net::ERR_CACHE_OPEN_FAILURE;
+
+ if (it->second->is_doomed()) {
+ it->second->Release();
+ entries_.erase(it);
+ return net::ERR_CACHE_OPEN_FAILURE;
+ }
+
+ open_count_++;
+
+ it->second->AddRef();
+ *entry = it->second;
+
+ if (soft_failures_)
+ it->second->set_fail_requests();
+
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskCache::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ if (fail_requests_)
+ return net::ERR_CACHE_CREATE_FAILURE;
+
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end()) {
+ if (!it->second->is_doomed()) {
+ if (double_create_check_)
+ NOTREACHED();
+ else
+ return net::ERR_CACHE_CREATE_FAILURE;
+ }
+ it->second->Release();
+ entries_.erase(it);
+ }
+
+ create_count_++;
+
+ MockDiskEntry* new_entry = new MockDiskEntry(key);
+
+ new_entry->AddRef();
+ entries_[key] = new_entry;
+
+ new_entry->AddRef();
+ *entry = new_entry;
+
+ if (soft_failures_)
+ new_entry->set_fail_requests();
+
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskCache::DoomEntry(const std::string& key,
+ const net::CompletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ EntryMap::iterator it = entries_.find(key);
+ if (it != entries_.end()) {
+ it->second->Release();
+ entries_.erase(it);
+ }
+
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
+}
+
+int MockDiskCache::DoomAllEntries(const net::CompletionCallback& callback) {
+ return net::ERR_NOT_IMPLEMENTED;
+}
+
+int MockDiskCache::DoomEntriesBetween(const base::Time initial_time,
+ const base::Time end_time,
+ const net::CompletionCallback& callback) {
+ return net::ERR_NOT_IMPLEMENTED;
+}
+
+int MockDiskCache::DoomEntriesSince(const base::Time initial_time,
+ const net::CompletionCallback& callback) {
+ return net::ERR_NOT_IMPLEMENTED;
+}
+
+int MockDiskCache::OpenNextEntry(void** iter, disk_cache::Entry** next_entry,
+ const net::CompletionCallback& callback) {
+ return net::ERR_NOT_IMPLEMENTED;
+}
+
+void MockDiskCache::EndEnumeration(void** iter) {
+}
+
+void MockDiskCache::GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) {
+}
+
+void MockDiskCache::OnExternalCacheHit(const std::string& key) {
+}
+
+void MockDiskCache::ReleaseAll() {
+ EntryMap::iterator it = entries_.begin();
+ for (; it != entries_.end(); ++it)
+ it->second->Release();
+ entries_.clear();
+}
+
+void MockDiskCache::CallbackLater(const net::CompletionCallback& callback,
+ int result) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&CallbackForwader, callback, result));
+}
+
+//-----------------------------------------------------------------------------
+
+int MockBackendFactory::CreateBackend(net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) {
+ backend->reset(new MockDiskCache());
+ return net::OK;
+}
+
+//-----------------------------------------------------------------------------
+
+MockHttpCache::MockHttpCache()
+ : http_cache_(new MockNetworkLayer(), NULL, new MockBackendFactory()) {
+}
+
+MockHttpCache::MockHttpCache(net::HttpCache::BackendFactory* disk_cache_factory)
+ : http_cache_(new MockNetworkLayer(), NULL, disk_cache_factory) {
+}
+
+MockDiskCache* MockHttpCache::disk_cache() {
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* backend;
+ int rv = http_cache_.GetBackend(&backend, cb.callback());
+ rv = cb.GetResult(rv);
+ return (rv == net::OK) ? static_cast<MockDiskCache*>(backend) : NULL;
+}
+
+bool MockHttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
+ net::HttpResponseInfo* response_info,
+ bool* response_truncated) {
+ int size = disk_entry->GetDataSize(0);
+
+ net::TestCompletionCallback cb;
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size));
+ int rv = disk_entry->ReadData(0, 0, buffer.get(), size, cb.callback());
+ rv = cb.GetResult(rv);
+ EXPECT_EQ(size, rv);
+
+ return net::HttpCache::ParseResponseInfo(buffer->data(), size,
+ response_info,
+ response_truncated);
+}
+
+bool MockHttpCache::WriteResponseInfo(
+ disk_cache::Entry* disk_entry, const net::HttpResponseInfo* response_info,
+ bool skip_transient_headers, bool response_truncated) {
+ Pickle pickle;
+ response_info->Persist(
+ &pickle, skip_transient_headers, response_truncated);
+
+ net::TestCompletionCallback cb;
+ scoped_refptr<net::WrappedIOBuffer> data(new net::WrappedIOBuffer(
+ reinterpret_cast<const char*>(pickle.data())));
+ int len = static_cast<int>(pickle.size());
+
+ int rv = disk_entry->WriteData(0, 0, data.get(), len, cb.callback(), true);
+ rv = cb.GetResult(rv);
+ return (rv == len);
+}
+
+bool MockHttpCache::OpenBackendEntry(const std::string& key,
+ disk_cache::Entry** entry) {
+ net::TestCompletionCallback cb;
+ int rv = disk_cache()->OpenEntry(key, entry, cb.callback());
+ return (cb.GetResult(rv) == net::OK);
+}
+
+bool MockHttpCache::CreateBackendEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ net::NetLog* net_log) {
+ net::TestCompletionCallback cb;
+ int rv = disk_cache()->CreateEntry(key, entry, cb.callback());
+ return (cb.GetResult(rv) == net::OK);
+}
+
+// Static.
+int MockHttpCache::GetTestMode(int test_mode) {
+ if (!g_test_mode)
+ return test_mode;
+
+ return g_test_mode;
+}
+
+// Static.
+void MockHttpCache::SetTestMode(int test_mode) {
+ g_test_mode = test_mode;
+}
+
+//-----------------------------------------------------------------------------
+
+int MockDiskCacheNoCB::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) {
+ return net::ERR_IO_PENDING;
+}
+
+//-----------------------------------------------------------------------------
+
+int MockBackendNoCbFactory::CreateBackend(
+ net::NetLog* net_log, scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) {
+ backend->reset(new MockDiskCacheNoCB());
+ return net::OK;
+}
+
+//-----------------------------------------------------------------------------
+
+MockBlockingBackendFactory::MockBlockingBackendFactory()
+ : backend_(NULL),
+ block_(true),
+ fail_(false) {
+}
+
+MockBlockingBackendFactory::~MockBlockingBackendFactory() {
+}
+
+int MockBlockingBackendFactory::CreateBackend(
+ net::NetLog* net_log, scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) {
+ if (!block_) {
+ if (!fail_)
+ backend->reset(new MockDiskCache());
+ return Result();
+ }
+
+ backend_ = backend;
+ callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+void MockBlockingBackendFactory::FinishCreation() {
+ block_ = false;
+ if (!callback_.is_null()) {
+ if (!fail_)
+ backend_->reset(new MockDiskCache());
+ net::CompletionCallback cb = callback_;
+ callback_.Reset();
+ cb.Run(Result()); // This object can be deleted here.
+ }
+}
diff --git a/chromium/net/http/mock_http_cache.h b/chromium/net/http/mock_http_cache.h
new file mode 100644
index 00000000000..6cb0e50f562
--- /dev/null
+++ b/chromium/net/http/mock_http_cache.h
@@ -0,0 +1,241 @@
+// Copyright (c) 2011 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.
+
+// This is a mock of the http cache and related testing classes. To be fair, it
+// is not really a mock http cache given that it uses the real implementation of
+// the http cache, but it has fake implementations of all required components,
+// so it is useful for unit tests at the http layer.
+
+#ifndef NET_HTTP_MOCK_HTTP_CACHE_H_
+#define NET_HTTP_MOCK_HTTP_CACHE_H_
+
+#include "base/containers/hash_tables.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_transaction_unittest.h"
+
+//-----------------------------------------------------------------------------
+// Mock disk cache (a very basic memory cache implementation).
+
+class MockDiskEntry : public disk_cache::Entry,
+ public base::RefCounted<MockDiskEntry> {
+ public:
+ MockDiskEntry();
+ explicit MockDiskEntry(const std::string& key);
+
+ bool is_doomed() const { return doomed_; }
+
+ virtual void Doom() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual std::string GetKey() const OVERRIDE;
+ virtual base::Time GetLastUsed() const OVERRIDE;
+ virtual base::Time GetLastModified() const OVERRIDE;
+ virtual int32 GetDataSize(int index) const OVERRIDE;
+ virtual int ReadData(int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback,
+ bool truncate) OVERRIDE;
+ virtual int ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int WriteSparseData(
+ int64 offset, net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int GetAvailableRange(
+ int64 offset, int len, int64* start,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual bool CouldBeSparse() const OVERRIDE;
+ virtual void CancelSparseIO() OVERRIDE;
+ virtual int ReadyForSparseIO(
+ const net::CompletionCallback& completion_callback) OVERRIDE;
+
+ // Fail most subsequent requests.
+ void set_fail_requests() { fail_requests_ = true; }
+
+ // If |value| is true, don't deliver any completion callbacks until called
+ // again with |value| set to false. Caution: remember to enable callbacks
+ // again or all subsequent tests will fail.
+ static void IgnoreCallbacks(bool value);
+
+ private:
+ friend class base::RefCounted<MockDiskEntry>;
+ struct CallbackInfo;
+
+ virtual ~MockDiskEntry();
+
+ // Unlike the callbacks for MockHttpTransaction, we want this one to run even
+ // if the consumer called Close on the MockDiskEntry. We achieve that by
+ // leveraging the fact that this class is reference counted.
+ void CallbackLater(const net::CompletionCallback& callback, int result);
+
+ void RunCallback(const net::CompletionCallback& callback, int result);
+
+ // When |store| is true, stores the callback to be delivered later; otherwise
+ // delivers any callback previously stored.
+ static void StoreAndDeliverCallbacks(bool store, MockDiskEntry* entry,
+ const net::CompletionCallback& callback,
+ int result);
+
+ static const int kNumCacheEntryDataIndices = 3;
+
+ std::string key_;
+ std::vector<char> data_[kNumCacheEntryDataIndices];
+ int test_mode_;
+ bool doomed_;
+ bool sparse_;
+ bool fail_requests_;
+ bool busy_;
+ bool delayed_;
+ static bool cancel_;
+ static bool ignore_callbacks_;
+};
+
+class MockDiskCache : public disk_cache::Backend {
+ public:
+ MockDiskCache();
+ virtual ~MockDiskCache();
+
+ virtual net::CacheType GetCacheType() const OVERRIDE;
+ virtual int32 GetEntryCount() const OVERRIDE;
+ virtual int OpenEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntry(const std::string& key,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int DoomAllEntries(const net::CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesBetween(
+ base::Time initial_time,
+ base::Time end_time,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int DoomEntriesSince(
+ base::Time initial_time,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int OpenNextEntry(void** iter, disk_cache::Entry** next_entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual void EndEnumeration(void** iter) OVERRIDE;
+ virtual void GetStats(
+ std::vector<std::pair<std::string, std::string> >* stats) OVERRIDE;
+ virtual void OnExternalCacheHit(const std::string& key) OVERRIDE;
+
+ // Returns number of times a cache entry was successfully opened.
+ int open_count() const { return open_count_; }
+
+ // Returns number of times a cache entry was successfully created.
+ int create_count() const { return create_count_; }
+
+ // Fail any subsequent CreateEntry and OpenEntry.
+ void set_fail_requests() { fail_requests_ = true; }
+
+ // Return entries that fail some of their requests.
+ void set_soft_failures(bool value) { soft_failures_ = value; }
+
+ // Makes sure that CreateEntry is not called twice for a given key.
+ void set_double_create_check(bool value) { double_create_check_ = value; }
+
+ void ReleaseAll();
+
+ private:
+ typedef base::hash_map<std::string, MockDiskEntry*> EntryMap;
+
+ void CallbackLater(const net::CompletionCallback& callback, int result);
+
+ EntryMap entries_;
+ int open_count_;
+ int create_count_;
+ bool fail_requests_;
+ bool soft_failures_;
+ bool double_create_check_;
+};
+
+class MockBackendFactory : public net::HttpCache::BackendFactory {
+ public:
+ virtual int CreateBackend(net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) OVERRIDE;
+};
+
+class MockHttpCache {
+ public:
+ MockHttpCache();
+ explicit MockHttpCache(net::HttpCache::BackendFactory* disk_cache_factory);
+
+ net::HttpCache* http_cache() { return &http_cache_; }
+
+ MockNetworkLayer* network_layer() {
+ return static_cast<MockNetworkLayer*>(http_cache_.network_layer());
+ }
+ MockDiskCache* disk_cache();
+
+ // Helper function for reading response info from the disk cache.
+ static bool ReadResponseInfo(disk_cache::Entry* disk_entry,
+ net::HttpResponseInfo* response_info,
+ bool* response_truncated);
+
+ // Helper function for writing response info into the disk cache.
+ static bool WriteResponseInfo(disk_cache::Entry* disk_entry,
+ const net::HttpResponseInfo* response_info,
+ bool skip_transient_headers,
+ bool response_truncated);
+
+ // Helper function to synchronously open a backend entry.
+ bool OpenBackendEntry(const std::string& key, disk_cache::Entry** entry);
+
+ // Helper function to synchronously create a backend entry.
+ bool CreateBackendEntry(const std::string& key, disk_cache::Entry** entry,
+ net::NetLog* net_log);
+
+ // Returns the test mode after considering the global override.
+ static int GetTestMode(int test_mode);
+
+ // Overrides the test mode for a given operation. Remember to reset it after
+ // the test! (by setting test_mode to zero).
+ static void SetTestMode(int test_mode);
+
+ private:
+ net::HttpCache http_cache_;
+};
+
+// This version of the disk cache doesn't invoke CreateEntry callbacks.
+class MockDiskCacheNoCB : public MockDiskCache {
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+};
+
+class MockBackendNoCbFactory : public net::HttpCache::BackendFactory {
+ public:
+ virtual int CreateBackend(net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) OVERRIDE;
+};
+
+// This backend factory allows us to control the backend instantiation.
+class MockBlockingBackendFactory : public net::HttpCache::BackendFactory {
+ public:
+ MockBlockingBackendFactory();
+ virtual ~MockBlockingBackendFactory();
+
+ virtual int CreateBackend(net::NetLog* net_log,
+ scoped_ptr<disk_cache::Backend>* backend,
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ // Completes the backend creation. Any blocked call will be notified via the
+ // provided callback.
+ void FinishCreation();
+
+ scoped_ptr<disk_cache::Backend>* backend() { return backend_; }
+ void set_fail(bool fail) { fail_ = fail; }
+
+ const net::CompletionCallback& callback() { return callback_; }
+
+ private:
+ int Result() { return fail_ ? net::ERR_FAILED : net::OK; }
+
+ scoped_ptr<disk_cache::Backend>* backend_;
+ net::CompletionCallback callback_;
+ bool block_;
+ bool fail_;
+};
+
+#endif // NET_HTTP_MOCK_HTTP_CACHE_H_
diff --git a/chromium/net/http/mock_sspi_library_win.cc b/chromium/net/http/mock_sspi_library_win.cc
new file mode 100644
index 00000000000..c3d875c0170
--- /dev/null
+++ b/chromium/net/http/mock_sspi_library_win.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2010 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/http/mock_sspi_library_win.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+MockSSPILibrary::MockSSPILibrary() {
+}
+
+MockSSPILibrary::~MockSSPILibrary() {
+ EXPECT_TRUE(expected_package_queries_.empty());
+ EXPECT_TRUE(expected_freed_packages_.empty());
+}
+
+SECURITY_STATUS MockSSPILibrary::AcquireCredentialsHandle(
+ LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry) {
+ // Fill in phCredential with arbitrary value.
+ phCredential->dwLower = phCredential->dwUpper = ((ULONG_PTR) ((INT_PTR)0));
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::InitializeSecurityContext(
+ PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry) {
+ // Fill in the outbound buffer with garbage data.
+ PSecBuffer out_buffer = pOutput->pBuffers;
+ out_buffer->cbBuffer = 2;
+ uint8* buf = reinterpret_cast<uint8 *>(out_buffer->pvBuffer);
+ buf[0] = 0xAB;
+ buf[1] = 0xBA;
+
+ // Fill in phNewContext with arbitrary value if it's invalid.
+ if (phNewContext != phContext)
+ phNewContext->dwLower = phNewContext->dwUpper = ((ULONG_PTR) ((INT_PTR)0));
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::QuerySecurityPackageInfo(
+ LPWSTR pszPackageName, PSecPkgInfoW *pkgInfo) {
+ EXPECT_TRUE(!expected_package_queries_.empty());
+ PackageQuery package_query = expected_package_queries_.front();
+ expected_package_queries_.pop_front();
+ std::wstring actual_package(pszPackageName);
+ EXPECT_EQ(package_query.expected_package, actual_package);
+ *pkgInfo = package_query.package_info;
+ if (package_query.response_code == SEC_E_OK)
+ expected_freed_packages_.insert(package_query.package_info);
+ return package_query.response_code;
+}
+
+SECURITY_STATUS MockSSPILibrary::FreeCredentialsHandle(
+ PCredHandle phCredential) {
+ EXPECT_TRUE(phCredential->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ EXPECT_TRUE(phCredential->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ SecInvalidateHandle(phCredential);
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::DeleteSecurityContext(PCtxtHandle phContext) {
+ EXPECT_TRUE(phContext->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ EXPECT_TRUE(phContext->dwLower == ((ULONG_PTR) ((INT_PTR) 0)));
+ SecInvalidateHandle(phContext);
+ return SEC_E_OK;
+}
+
+SECURITY_STATUS MockSSPILibrary::FreeContextBuffer(PVOID pvContextBuffer) {
+ PSecPkgInfoW package_info = static_cast<PSecPkgInfoW>(pvContextBuffer);
+ std::set<PSecPkgInfoW>::iterator it = expected_freed_packages_.find(
+ package_info);
+ EXPECT_TRUE(it != expected_freed_packages_.end());
+ expected_freed_packages_.erase(it);
+ return SEC_E_OK;
+}
+
+void MockSSPILibrary::ExpectQuerySecurityPackageInfo(
+ const std::wstring& expected_package,
+ SECURITY_STATUS response_code,
+ PSecPkgInfoW package_info) {
+ PackageQuery package_query = {expected_package, response_code,
+ package_info};
+ expected_package_queries_.push_back(package_query);
+}
+
+} // namespace net
diff --git a/chromium/net/http/mock_sspi_library_win.h b/chromium/net/http/mock_sspi_library_win.h
new file mode 100644
index 00000000000..eca3eb55751
--- /dev/null
+++ b/chromium/net/http/mock_sspi_library_win.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2010 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_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
+#define NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
+
+#include <list>
+#include <set>
+
+#include "net/http/http_auth_sspi_win.h"
+
+namespace net {
+
+// The MockSSPILibrary class is intended for unit tests which want to bypass
+// the system SSPI library calls.
+class MockSSPILibrary : public SSPILibrary {
+ public:
+ MockSSPILibrary();
+ virtual ~MockSSPILibrary();
+
+ // TODO(cbentzel): Only QuerySecurityPackageInfo and FreeContextBuffer
+ // are properly handled currently.
+ // SSPILibrary methods:
+ virtual SECURITY_STATUS AcquireCredentialsHandle(LPWSTR pszPrincipal,
+ LPWSTR pszPackage,
+ unsigned long fCredentialUse,
+ void* pvLogonId,
+ void* pvAuthData,
+ SEC_GET_KEY_FN pGetKeyFn,
+ void* pvGetKeyArgument,
+ PCredHandle phCredential,
+ PTimeStamp ptsExpiry);
+ virtual SECURITY_STATUS InitializeSecurityContext(PCredHandle phCredential,
+ PCtxtHandle phContext,
+ SEC_WCHAR* pszTargetName,
+ unsigned long fContextReq,
+ unsigned long Reserved1,
+ unsigned long TargetDataRep,
+ PSecBufferDesc pInput,
+ unsigned long Reserved2,
+ PCtxtHandle phNewContext,
+ PSecBufferDesc pOutput,
+ unsigned long* contextAttr,
+ PTimeStamp ptsExpiry);
+ virtual SECURITY_STATUS QuerySecurityPackageInfo(LPWSTR pszPackageName,
+ PSecPkgInfoW *pkgInfo);
+ virtual SECURITY_STATUS FreeCredentialsHandle(PCredHandle phCredential);
+ virtual SECURITY_STATUS DeleteSecurityContext(PCtxtHandle phContext);
+ virtual SECURITY_STATUS FreeContextBuffer(PVOID pvContextBuffer);
+
+ // Establishes an expectation for a |QuerySecurityPackageInfo()| call.
+ //
+ // Each expectation established by |ExpectSecurityQueryPackageInfo()| must be
+ // matched by a call to |QuerySecurityPackageInfo()| during the lifetime of
+ // the MockSSPILibrary. The |expected_package| argument must equal the
+ // |*pszPackageName| argument to |QuerySecurityPackageInfo()| for there to be
+ // a match. The expectations also establish an explicit ordering.
+ //
+ // For example, this sequence will be successful.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.QuerySecurityPackageInfo(L"Negotiate", ...)
+ //
+ // This sequence will fail since the queries do not occur in the order
+ // established by the expectations.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ //
+ // This sequence will fail because there were not enough queries.
+ // MockSSPILibrary lib;
+ // lib.ExpectQuerySecurityPackageInfo(L"NTLM", ...)
+ // lib.ExpectQuerySecurityPackageInfo(L"Negotiate", ...)
+ // lib.QuerySecurityPackageInfo(L"NTLM", ...)
+ //
+ // |response_code| is used as the return value for
+ // |QuerySecurityPackageInfo()|. If |response_code| is SEC_E_OK,
+ // an expectation is also set for a call to |FreeContextBuffer()| after
+ // the matching |QuerySecurityPackageInfo()| is called.
+ //
+ // |package_info| is assigned to |*pkgInfo| in |QuerySecurityPackageInfo|.
+ // The lifetime of |*package_info| should last at least until the matching
+ // |QuerySecurityPackageInfo()| is called.
+ void ExpectQuerySecurityPackageInfo(const std::wstring& expected_package,
+ SECURITY_STATUS response_code,
+ PSecPkgInfoW package_info);
+
+ private:
+ struct PackageQuery {
+ std::wstring expected_package;
+ SECURITY_STATUS response_code;
+ PSecPkgInfoW package_info;
+ };
+
+ // expected_package_queries contains an ordered list of expected
+ // |QuerySecurityPackageInfo()| calls and the return values for those
+ // calls.
+ std::list<PackageQuery> expected_package_queries_;
+
+ // Set of packages which should be freed.
+ std::set<PSecPkgInfoW> expected_freed_packages_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
diff --git a/chromium/net/http/partial_data.cc b/chromium/net/http/partial_data.cc
new file mode 100644
index 00000000000..02fda6cc9b1
--- /dev/null
+++ b/chromium/net/http/partial_data.cc
@@ -0,0 +1,496 @@
+// Copyright (c) 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.
+
+#include "net/http/partial_data.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+namespace {
+
+// The headers that we have to process.
+const char kLengthHeader[] = "Content-Length";
+const char kRangeHeader[] = "Content-Range";
+const int kDataStream = 1;
+
+void AddRangeHeader(int64 start, int64 end, HttpRequestHeaders* headers) {
+ DCHECK(start >= 0 || end >= 0);
+ std::string my_start, my_end;
+ if (start >= 0)
+ my_start = base::Int64ToString(start);
+ if (end >= 0)
+ my_end = base::Int64ToString(end);
+
+ headers->SetHeader(
+ HttpRequestHeaders::kRange,
+ base::StringPrintf("bytes=%s-%s", my_start.c_str(), my_end.c_str()));
+}
+
+} // namespace
+
+// A core object that can be detached from the Partialdata object at destruction
+// so that asynchronous operations cleanup can be performed.
+class PartialData::Core {
+ public:
+ // Build a new core object. Lifetime management is automatic.
+ static Core* CreateCore(PartialData* owner) {
+ return new Core(owner);
+ }
+
+ // Wrapper for Entry::GetAvailableRange. If this method returns ERR_IO_PENDING
+ // PartialData::GetAvailableRangeCompleted() will be invoked on the owner
+ // object when finished (unless Cancel() is called first).
+ int GetAvailableRange(disk_cache::Entry* entry, int64 offset, int len,
+ int64* start);
+
+ // Cancels a pending operation. It is a mistake to call this method if there
+ // is no operation in progress; in fact, there will be no object to do so.
+ void Cancel();
+
+ private:
+ explicit Core(PartialData* owner);
+ ~Core();
+
+ // Pending io completion routine.
+ void OnIOComplete(int result);
+
+ PartialData* owner_;
+ int64 start_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+PartialData::Core::Core(PartialData* owner)
+ : owner_(owner), start_(0) {
+ DCHECK(!owner_->core_);
+ owner_->core_ = this;
+}
+
+PartialData::Core::~Core() {
+ if (owner_)
+ owner_->core_ = NULL;
+}
+
+void PartialData::Core::Cancel() {
+ DCHECK(owner_);
+ owner_ = NULL;
+}
+
+int PartialData::Core::GetAvailableRange(disk_cache::Entry* entry, int64 offset,
+ int len, int64* start) {
+ int rv = entry->GetAvailableRange(
+ offset, len, &start_, base::Bind(&PartialData::Core::OnIOComplete,
+ base::Unretained(this)));
+ if (rv != net::ERR_IO_PENDING) {
+ // The callback will not be invoked. Lets cleanup.
+ *start = start_;
+ delete this;
+ }
+ return rv;
+}
+
+void PartialData::Core::OnIOComplete(int result) {
+ if (owner_)
+ owner_->GetAvailableRangeCompleted(result, start_);
+ delete this;
+}
+
+// -----------------------------------------------------------------------------
+
+PartialData::PartialData()
+ : range_present_(false),
+ final_range_(false),
+ sparse_entry_(true),
+ truncated_(false),
+ initial_validation_(false),
+ core_(NULL) {
+}
+
+PartialData::~PartialData() {
+ if (core_)
+ core_->Cancel();
+}
+
+bool PartialData::Init(const HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (!headers.GetHeader(HttpRequestHeaders::kRange, &range_header))
+ return false;
+
+ std::vector<HttpByteRange> ranges;
+ if (!HttpUtil::ParseRangeHeader(range_header, &ranges) || ranges.size() != 1)
+ return false;
+
+ // We can handle this range request.
+ byte_range_ = ranges[0];
+ if (!byte_range_.IsValid())
+ return false;
+
+ resource_size_ = 0;
+ current_range_start_ = byte_range_.first_byte_position();
+
+ DVLOG(1) << "Range start: " << current_range_start_ << " end: " <<
+ byte_range_.last_byte_position();
+ return true;
+}
+
+void PartialData::SetHeaders(const HttpRequestHeaders& headers) {
+ DCHECK(extra_headers_.IsEmpty());
+ extra_headers_.CopyFrom(headers);
+}
+
+void PartialData::RestoreHeaders(HttpRequestHeaders* headers) const {
+ DCHECK(current_range_start_ >= 0 || byte_range_.IsSuffixByteRange());
+ int64 end = byte_range_.IsSuffixByteRange() ?
+ byte_range_.suffix_length() : byte_range_.last_byte_position();
+
+ headers->CopyFrom(extra_headers_);
+ if (!truncated_ && byte_range_.IsValid())
+ AddRangeHeader(current_range_start_, end, headers);
+}
+
+int PartialData::ShouldValidateCache(disk_cache::Entry* entry,
+ const CompletionCallback& callback) {
+ DCHECK_GE(current_range_start_, 0);
+
+ // Scan the disk cache for the first cached portion within this range.
+ int len = GetNextRangeLen();
+ if (!len)
+ return 0;
+
+ DVLOG(3) << "ShouldValidateCache len: " << len;
+
+ if (sparse_entry_) {
+ DCHECK(callback_.is_null());
+ Core* core = Core::CreateCore(this);
+ cached_min_len_ = core->GetAvailableRange(entry, current_range_start_, len,
+ &cached_start_);
+
+ if (cached_min_len_ == ERR_IO_PENDING) {
+ callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+ } else if (!truncated_) {
+ if (byte_range_.HasFirstBytePosition() &&
+ byte_range_.first_byte_position() >= resource_size_) {
+ // The caller should take care of this condition because we should have
+ // failed IsRequestedRangeOK(), but it's better to be consistent here.
+ len = 0;
+ }
+ cached_min_len_ = len;
+ cached_start_ = current_range_start_;
+ }
+
+ if (cached_min_len_ < 0)
+ return cached_min_len_;
+
+ // Return a positive number to indicate success (versus error or finished).
+ return 1;
+}
+
+void PartialData::PrepareCacheValidation(disk_cache::Entry* entry,
+ HttpRequestHeaders* headers) {
+ DCHECK_GE(current_range_start_, 0);
+ DCHECK_GE(cached_min_len_, 0);
+
+ int len = GetNextRangeLen();
+ DCHECK_NE(0, len);
+ range_present_ = false;
+
+ headers->CopyFrom(extra_headers_);
+
+ if (!cached_min_len_) {
+ // We don't have anything else stored.
+ final_range_ = true;
+ cached_start_ =
+ byte_range_.HasLastBytePosition() ? current_range_start_ + len : 0;
+ }
+
+ if (current_range_start_ == cached_start_) {
+ // The data lives in the cache.
+ range_present_ = true;
+ if (len == cached_min_len_)
+ final_range_ = true;
+ AddRangeHeader(current_range_start_, cached_start_ + cached_min_len_ - 1,
+ headers);
+ } else {
+ // This range is not in the cache.
+ AddRangeHeader(current_range_start_, cached_start_ - 1, headers);
+ }
+}
+
+bool PartialData::IsCurrentRangeCached() const {
+ return range_present_;
+}
+
+bool PartialData::IsLastRange() const {
+ return final_range_;
+}
+
+bool PartialData::UpdateFromStoredHeaders(const HttpResponseHeaders* headers,
+ disk_cache::Entry* entry,
+ bool truncated) {
+ resource_size_ = 0;
+ if (truncated) {
+ DCHECK_EQ(headers->response_code(), 200);
+ // We don't have the real length and the user may be trying to create a
+ // sparse entry so let's not write to this entry.
+ if (byte_range_.IsValid())
+ return false;
+
+ if (!headers->HasStrongValidators())
+ return false;
+
+ // Now we avoid resume if there is no content length, but that was not
+ // always the case so double check here.
+ int64 total_length = headers->GetContentLength();
+ if (total_length <= 0)
+ return false;
+
+ truncated_ = true;
+ initial_validation_ = true;
+ sparse_entry_ = false;
+ int current_len = entry->GetDataSize(kDataStream);
+ byte_range_.set_first_byte_position(current_len);
+ resource_size_ = total_length;
+ current_range_start_ = current_len;
+ cached_min_len_ = current_len;
+ cached_start_ = current_len + 1;
+ return true;
+ }
+
+ if (headers->response_code() != 206) {
+ DCHECK(byte_range_.IsValid());
+ sparse_entry_ = false;
+ resource_size_ = entry->GetDataSize(kDataStream);
+ DVLOG(2) << "UpdateFromStoredHeaders size: " << resource_size_;
+ return true;
+ }
+
+ if (!headers->HasStrongValidators())
+ return false;
+
+ int64 length_value = headers->GetContentLength();
+ if (length_value <= 0)
+ return false; // We must have stored the resource length.
+
+ resource_size_ = length_value;
+
+ // Make sure that this is really a sparse entry.
+ return entry->CouldBeSparse();
+}
+
+void PartialData::SetRangeToStartDownload() {
+ DCHECK(truncated_);
+ DCHECK(!sparse_entry_);
+ current_range_start_ = 0;
+ cached_start_ = 0;
+ initial_validation_ = false;
+}
+
+bool PartialData::IsRequestedRangeOK() {
+ if (byte_range_.IsValid()) {
+ if (!byte_range_.ComputeBounds(resource_size_))
+ return false;
+ if (truncated_)
+ return true;
+
+ if (current_range_start_ < 0)
+ current_range_start_ = byte_range_.first_byte_position();
+ } else {
+ // This is not a range request but we have partial data stored.
+ current_range_start_ = 0;
+ byte_range_.set_last_byte_position(resource_size_ - 1);
+ }
+
+ bool rv = current_range_start_ >= 0;
+ if (!rv)
+ current_range_start_ = 0;
+
+ return rv;
+}
+
+bool PartialData::ResponseHeadersOK(const HttpResponseHeaders* headers) {
+ if (headers->response_code() == 304) {
+ if (!byte_range_.IsValid() || truncated_)
+ return true;
+
+ // We must have a complete range here.
+ return byte_range_.HasFirstBytePosition() &&
+ byte_range_.HasLastBytePosition();
+ }
+
+ int64 start, end, total_length;
+ if (!headers->GetContentRange(&start, &end, &total_length))
+ return false;
+ if (total_length <= 0)
+ return false;
+
+ int64 content_length = headers->GetContentLength();
+ if (content_length < 0 || content_length != end - start + 1)
+ return false;
+
+ if (!resource_size_) {
+ // First response. Update our values with the ones provided by the server.
+ resource_size_ = total_length;
+ if (!byte_range_.HasFirstBytePosition()) {
+ byte_range_.set_first_byte_position(start);
+ current_range_start_ = start;
+ }
+ if (!byte_range_.HasLastBytePosition())
+ byte_range_.set_last_byte_position(end);
+ } else if (resource_size_ != total_length) {
+ return false;
+ }
+
+ if (truncated_) {
+ if (!byte_range_.HasLastBytePosition())
+ byte_range_.set_last_byte_position(end);
+ }
+
+ if (start != current_range_start_)
+ return false;
+
+ if (byte_range_.IsValid() && end > byte_range_.last_byte_position())
+ return false;
+
+ return true;
+}
+
+// We are making multiple requests to complete the range requested by the user.
+// Just assume that everything is fine and say that we are returning what was
+// requested.
+void PartialData::FixResponseHeaders(HttpResponseHeaders* headers,
+ bool success) {
+ if (truncated_)
+ return;
+
+ headers->RemoveHeader(kLengthHeader);
+ headers->RemoveHeader(kRangeHeader);
+
+ int64 range_len, start, end;
+ if (byte_range_.IsValid()) {
+ if (success) {
+ if (!sparse_entry_)
+ headers->ReplaceStatusLine("HTTP/1.1 206 Partial Content");
+
+ DCHECK(byte_range_.HasFirstBytePosition());
+ DCHECK(byte_range_.HasLastBytePosition());
+ start = byte_range_.first_byte_position();
+ end = byte_range_.last_byte_position();
+ range_len = end - start + 1;
+ } else {
+ headers->ReplaceStatusLine(
+ "HTTP/1.1 416 Requested Range Not Satisfiable");
+ start = 0;
+ end = 0;
+ range_len = 0;
+ }
+
+ headers->AddHeader(
+ base::StringPrintf("%s: bytes %" PRId64 "-%" PRId64 "/%" PRId64,
+ kRangeHeader, start, end, resource_size_));
+ } else {
+ // TODO(rvargas): Is it safe to change the protocol version?
+ headers->ReplaceStatusLine("HTTP/1.1 200 OK");
+ DCHECK_NE(resource_size_, 0);
+ range_len = resource_size_;
+ }
+
+ headers->AddHeader(base::StringPrintf("%s: %" PRId64, kLengthHeader,
+ range_len));
+}
+
+void PartialData::FixContentLength(HttpResponseHeaders* headers) {
+ headers->RemoveHeader(kLengthHeader);
+ headers->AddHeader(base::StringPrintf("%s: %" PRId64, kLengthHeader,
+ resource_size_));
+}
+
+int PartialData::CacheRead(
+ disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ const net::CompletionCallback& callback) {
+ int read_len = std::min(data_len, cached_min_len_);
+ if (!read_len)
+ return 0;
+
+ int rv = 0;
+ if (sparse_entry_) {
+ rv = entry->ReadSparseData(current_range_start_, data, read_len,
+ callback);
+ } else {
+ if (current_range_start_ > kint32max)
+ return ERR_INVALID_ARGUMENT;
+
+ rv = entry->ReadData(kDataStream, static_cast<int>(current_range_start_),
+ data, read_len, callback);
+ }
+ return rv;
+}
+
+int PartialData::CacheWrite(
+ disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ const net::CompletionCallback& callback) {
+ DVLOG(3) << "To write: " << data_len;
+ if (sparse_entry_) {
+ return entry->WriteSparseData(
+ current_range_start_, data, data_len, callback);
+ } else {
+ if (current_range_start_ > kint32max)
+ return ERR_INVALID_ARGUMENT;
+
+ return entry->WriteData(kDataStream, static_cast<int>(current_range_start_),
+ data, data_len, callback, true);
+ }
+}
+
+void PartialData::OnCacheReadCompleted(int result) {
+ DVLOG(3) << "Read: " << result;
+ if (result > 0) {
+ current_range_start_ += result;
+ cached_min_len_ -= result;
+ DCHECK_GE(cached_min_len_, 0);
+ }
+}
+
+void PartialData::OnNetworkReadCompleted(int result) {
+ if (result > 0)
+ current_range_start_ += result;
+}
+
+int PartialData::GetNextRangeLen() {
+ int64 range_len =
+ byte_range_.HasLastBytePosition() ?
+ byte_range_.last_byte_position() - current_range_start_ + 1 :
+ kint32max;
+ if (range_len > kint32max)
+ range_len = kint32max;
+ return static_cast<int32>(range_len);
+}
+
+void PartialData::GetAvailableRangeCompleted(int result, int64 start) {
+ DCHECK(!callback_.is_null());
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ cached_start_ = start;
+ cached_min_len_ = result;
+ if (result >= 0)
+ result = 1; // Return success, go ahead and validate the entry.
+
+ CompletionCallback cb = callback_;
+ callback_.Reset();
+ cb.Run(result);
+}
+
+} // namespace net
diff --git a/chromium/net/http/partial_data.h b/chromium/net/http/partial_data.h
new file mode 100644
index 00000000000..45bda39b43e
--- /dev/null
+++ b/chromium/net/http/partial_data.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2011 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_HTTP_PARTIAL_DATA_H_
+#define NET_HTTP_PARTIAL_DATA_H_
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_request_headers.h"
+
+namespace disk_cache {
+class Entry;
+}
+
+namespace net {
+
+class HttpResponseHeaders;
+class IOBuffer;
+
+// This class provides support for dealing with range requests and the
+// subsequent partial-content responses. We use sparse cache entries to store
+// these requests. This class is tightly integrated with HttpCache::Transaction
+// and it is intended to allow a cleaner implementation of that class.
+//
+// In order to fulfill range requests, we may have to perform a sequence of
+// reads from the cache, interleaved with reads from the network / writes to the
+// cache. This class basically keeps track of the data required to perform each
+// of those individual network / cache requests.
+class PartialData {
+ public:
+ PartialData();
+ ~PartialData();
+
+ // Performs initialization of the object by examining the request |headers|
+ // and verifying that we can process the requested range. Returns true if
+ // we can process the requested range, and false otherwise.
+ bool Init(const HttpRequestHeaders& headers);
+
+ // Sets the headers that we should use to make byte range requests. This is a
+ // subset of the request extra headers, with byte-range related headers
+ // removed.
+ void SetHeaders(const HttpRequestHeaders& headers);
+
+ // Restores the byte-range headers, by appending the byte range to the headers
+ // provided to SetHeaders().
+ void RestoreHeaders(HttpRequestHeaders* headers) const;
+
+ // Starts the checks to perform a cache validation. Returns 0 when there is no
+ // need to perform more operations because we reached the end of the request
+ // (so 0 bytes should be actually returned to the user), a positive number to
+ // indicate that PrepareCacheValidation should be called, or an appropriate
+ // error code. If this method returns ERR_IO_PENDING, the |callback| will be
+ // notified when the result is ready.
+ int ShouldValidateCache(disk_cache::Entry* entry,
+ const CompletionCallback& callback);
+
+ // Builds the required |headers| to perform the proper cache validation for
+ // the next range to be fetched.
+ void PrepareCacheValidation(disk_cache::Entry* entry,
+ HttpRequestHeaders* headers);
+
+ // Returns true if the current range is stored in the cache.
+ bool IsCurrentRangeCached() const;
+
+ // Returns true if the current range is the last one needed to fulfill the
+ // user's request.
+ bool IsLastRange() const;
+
+ // Extracts info from headers already stored in the cache. Returns false if
+ // there is any problem with the headers. |truncated| should be true if we
+ // have an incomplete 200 entry.
+ bool UpdateFromStoredHeaders(const HttpResponseHeaders* headers,
+ disk_cache::Entry* entry, bool truncated);
+
+ // Sets the byte current range to start again at zero (for a truncated entry).
+ void SetRangeToStartDownload();
+
+ // Returns true if the requested range is valid given the stored data.
+ bool IsRequestedRangeOK();
+
+ // Returns true if the response headers match what we expect, false otherwise.
+ bool ResponseHeadersOK(const HttpResponseHeaders* headers);
+
+ // Fixes the response headers to include the right content length and range.
+ // |success| is the result of the whole request so if it's false, we'll change
+ // the result code to be 416.
+ void FixResponseHeaders(HttpResponseHeaders* headers, bool success);
+
+ // Fixes the content length that we want to store in the cache.
+ void FixContentLength(HttpResponseHeaders* headers);
+
+ // Reads up to |data_len| bytes from the cache and stores them in the provided
+ // buffer (|data|). Basically, this is just a wrapper around the API of the
+ // cache that provides the right arguments for the current range. When the IO
+ // operation completes, OnCacheReadCompleted() must be called with the result
+ // of the operation.
+ int CacheRead(disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ const net::CompletionCallback& callback);
+
+ // Writes |data_len| bytes to cache. This is basically a wrapper around the
+ // API of the cache that provides the right arguments for the current range.
+ int CacheWrite(disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ const net::CompletionCallback& callback);
+
+ // This method should be called when CacheRead() finishes the read, to update
+ // the internal state about the current range.
+ void OnCacheReadCompleted(int result);
+
+ // This method should be called after receiving data from the network, to
+ // update the internal state about the current range.
+ void OnNetworkReadCompleted(int result);
+
+ bool initial_validation() const { return initial_validation_; }
+
+ private:
+ class Core;
+ // Returns the length to use when scanning the cache.
+ int GetNextRangeLen();
+
+ // Completion routine for our callback.
+ void GetAvailableRangeCompleted(int result, int64 start);
+
+ int64 current_range_start_;
+ int64 cached_start_;
+ int64 resource_size_;
+ int cached_min_len_;
+ HttpByteRange byte_range_; // The range requested by the user.
+ // The clean set of extra headers (no ranges).
+ HttpRequestHeaders extra_headers_;
+ bool range_present_; // True if next range entry is already stored.
+ bool final_range_;
+ bool sparse_entry_;
+ bool truncated_; // We have an incomplete 200 stored.
+ bool initial_validation_; // Only used for truncated entries.
+ Core* core_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PartialData);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_PARTIAL_DATA_H_
diff --git a/chromium/net/http/proxy_client_socket.cc b/chromium/net/http/proxy_client_socket.cc
new file mode 100644
index 00000000000..dcfae037ce7
--- /dev/null
+++ b/chromium/net/http/proxy_client_socket.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 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.
+
+#include "net/http/proxy_client_socket.h"
+
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// static
+void ProxyClientSocket::BuildTunnelRequest(
+ const HttpRequestInfo& request_info,
+ const HttpRequestHeaders& auth_headers,
+ const HostPortPair& endpoint,
+ std::string* request_line,
+ HttpRequestHeaders* request_headers) {
+ // RFC 2616 Section 9 says the Host request-header field MUST accompany all
+ // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with
+ // HTTP/1.0 proxies such as Squid (required for NTLM authentication).
+ *request_line = base::StringPrintf(
+ "CONNECT %s HTTP/1.1\r\n", endpoint.ToString().c_str());
+ request_headers->SetHeader(HttpRequestHeaders::kHost,
+ GetHostAndOptionalPort(request_info.url));
+ request_headers->SetHeader(HttpRequestHeaders::kProxyConnection,
+ "keep-alive");
+
+ std::string user_agent;
+ if (request_info.extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
+ &user_agent))
+ request_headers->SetHeader(HttpRequestHeaders::kUserAgent, user_agent);
+
+ request_headers->MergeFrom(auth_headers);
+}
+
+// static
+int ProxyClientSocket::HandleProxyAuthChallenge(HttpAuthController* auth,
+ HttpResponseInfo* response,
+ const BoundNetLog& net_log) {
+ DCHECK(response->headers.get());
+ int rv = auth->HandleAuthChallenge(response->headers, false, true, net_log);
+ response->auth_challenge = auth->auth_info();
+ if (rv == OK)
+ return ERR_PROXY_AUTH_REQUESTED;
+ return rv;
+}
+
+// static
+void ProxyClientSocket::LogBlockedTunnelResponse(int http_status_code,
+ const GURL& url,
+ bool is_https_proxy) {
+ if (is_https_proxy) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.BlockedTunnelResponse.HttpsProxy",
+ HttpUtil::MapStatusCodeForHistogram(http_status_code),
+ HttpUtil::GetStatusCodesForHistogram());
+ } else {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.BlockedTunnelResponse.HttpProxy",
+ HttpUtil::MapStatusCodeForHistogram(http_status_code),
+ HttpUtil::GetStatusCodesForHistogram());
+ }
+}
+
+// static
+bool ProxyClientSocket::SanitizeProxyRedirect(HttpResponseInfo* response,
+ const GURL& url) {
+ DCHECK(response && response->headers.get());
+
+ std::string location;
+ if (!response->headers->IsRedirect(&location))
+ return false;
+
+ // Return minimal headers; set "Content-length: 0" to ignore response body.
+ std::string fake_response_headers =
+ base::StringPrintf("HTTP/1.0 302 Found\n"
+ "Location: %s\n"
+ "Content-length: 0\n"
+ "Connection: close\n"
+ "\n",
+ location.c_str());
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(fake_response_headers.data(),
+ fake_response_headers.length());
+ response->headers = new HttpResponseHeaders(raw_headers);
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/http/proxy_client_socket.h b/chromium/net/http/proxy_client_socket.h
new file mode 100644
index 00000000000..da255f39440
--- /dev/null
+++ b/chromium/net/http/proxy_client_socket.h
@@ -0,0 +1,91 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_PROXY_CLIENT_SOCKET_H_
+#define NET_HTTP_PROXY_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+
+class GURL;
+
+namespace net {
+
+class HostPortPair;
+class HttpAuthController;
+class HttpStream;
+class HttpResponseInfo;
+class HttpRequestHeaders;
+struct HttpRequestInfo;
+class HttpAuthController;
+
+class NET_EXPORT_PRIVATE ProxyClientSocket : public StreamSocket {
+ public:
+ ProxyClientSocket() {}
+ virtual ~ProxyClientSocket() {}
+
+ // Returns the HttpResponseInfo (including HTTP Headers) from
+ // the response to the CONNECT request.
+ virtual const HttpResponseInfo* GetConnectResponseInfo() const = 0;
+
+ // Transfers ownership of a newly created HttpStream to the caller
+ // which can be used to read the response body.
+ virtual HttpStream* CreateConnectResponseStream() = 0;
+
+ // Returns the HttpAuthController which can be used
+ // to interact with an HTTP Proxy Authorization Required (407) request.
+ virtual const scoped_refptr<HttpAuthController>& GetAuthController() const
+ = 0;
+
+ // If Connect (or its callback) returns PROXY_AUTH_REQUESTED, then
+ // credentials should be added to the HttpAuthController before calling
+ // RestartWithAuth. Not all ProxyClientSocket implementations will be
+ // restartable. Such implementations should disconnect themselves and
+ // return OK.
+ virtual int RestartWithAuth(const CompletionCallback& callback) = 0;
+
+ // Returns true of the connection to the proxy is using SPDY.
+ virtual bool IsUsingSpdy() const = 0;
+
+ // Returns the protocol negotiated with the proxy.
+ virtual NextProto GetProtocolNegotiated() const = 0;
+
+ protected:
+ // The HTTP CONNECT method for establishing a tunnel connection is documented
+ // in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2
+ // and 5.3.
+ static void BuildTunnelRequest(const HttpRequestInfo& request_info,
+ const HttpRequestHeaders& auth_headers,
+ const HostPortPair& endpoint,
+ std::string* request_line,
+ HttpRequestHeaders* request_headers);
+
+ // When an auth challenge (407 response) is received during tunnel
+ // construction/ this method should be called.
+ static int HandleProxyAuthChallenge(HttpAuthController* auth,
+ HttpResponseInfo* response,
+ const BoundNetLog& net_log);
+
+ // Logs (to the log and in a histogram) a blocked CONNECT response.
+ static void LogBlockedTunnelResponse(int http_response_code,
+ const GURL& url,
+ bool is_https_proxy);
+
+ // When a redirect (e.g. 302 response) is received during tunnel
+ // construction, this method should be called to strip everything
+ // but the Location header from the redirect response. If it returns
+ // false, the response should be discarded and tunnel construction should
+ // fail. |url| is for logging purposes.
+ static bool SanitizeProxyRedirect(HttpResponseInfo* response,
+ const GURL& url);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_PROXY_CLIENT_SOCKET_H_
diff --git a/chromium/net/http/proxy_connect_redirect_http_stream.cc b/chromium/net/http/proxy_connect_redirect_http_stream.cc
new file mode 100644
index 00000000000..59bb0146953
--- /dev/null
+++ b/chromium/net/http/proxy_connect_redirect_http_stream.cc
@@ -0,0 +1,126 @@
+// Copyright 2013 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/http/proxy_connect_redirect_http_stream.h"
+
+#include <cstddef>
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+ProxyConnectRedirectHttpStream::ProxyConnectRedirectHttpStream(
+ LoadTimingInfo* load_timing_info)
+ : has_load_timing_info_(load_timing_info != NULL) {
+ if (has_load_timing_info_)
+ load_timing_info_ = *load_timing_info;
+}
+
+ProxyConnectRedirectHttpStream::~ProxyConnectRedirectHttpStream() {}
+
+int ProxyConnectRedirectHttpStream::InitializeStream(
+ const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+int ProxyConnectRedirectHttpStream::SendRequest(
+ const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+int ProxyConnectRedirectHttpStream::ReadResponseHeaders(
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+const HttpResponseInfo*
+ProxyConnectRedirectHttpStream::GetResponseInfo() const {
+ NOTREACHED();
+ return NULL;
+}
+
+int ProxyConnectRedirectHttpStream::ReadResponseBody(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ NOTREACHED();
+ return OK;
+}
+
+void ProxyConnectRedirectHttpStream::Close(bool not_reusable) {}
+
+bool ProxyConnectRedirectHttpStream::IsResponseBodyComplete() const {
+ NOTREACHED();
+ return true;
+}
+
+bool ProxyConnectRedirectHttpStream::CanFindEndOfResponse() const {
+ return true;
+}
+
+bool ProxyConnectRedirectHttpStream::IsConnectionReused() const {
+ NOTREACHED();
+ return false;
+}
+
+void ProxyConnectRedirectHttpStream::SetConnectionReused() {
+ NOTREACHED();
+}
+
+bool ProxyConnectRedirectHttpStream::IsConnectionReusable() const {
+ NOTREACHED();
+ return false;
+}
+
+bool ProxyConnectRedirectHttpStream::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ if (!has_load_timing_info_)
+ return false;
+
+ *load_timing_info = load_timing_info_;
+ return true;
+}
+
+void ProxyConnectRedirectHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+ NOTREACHED();
+}
+
+void ProxyConnectRedirectHttpStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ NOTREACHED();
+}
+
+bool ProxyConnectRedirectHttpStream::IsSpdyHttpStream() const {
+ NOTREACHED();
+ return false;
+}
+
+void ProxyConnectRedirectHttpStream::Drain(HttpNetworkSession* session) {
+ NOTREACHED();
+}
+
+void ProxyConnectRedirectHttpStream::SetPriority(RequestPriority priority) {
+ // Nothing to do.
+}
+
+UploadProgress ProxyConnectRedirectHttpStream::GetUploadProgress() const {
+ NOTREACHED();
+ return UploadProgress();
+}
+
+HttpStream* ProxyConnectRedirectHttpStream::RenewStreamForAuth() {
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace
diff --git a/chromium/net/http/proxy_connect_redirect_http_stream.h b/chromium/net/http/proxy_connect_redirect_http_stream.h
new file mode 100644
index 00000000000..c335c218c71
--- /dev/null
+++ b/chromium/net/http/proxy_connect_redirect_http_stream.h
@@ -0,0 +1,76 @@
+// Copyright 2013 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_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
+#define NET_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/load_timing_info.h"
+#include "net/http/http_stream.h"
+
+namespace net {
+
+// A dummy HttpStream with no body used when a redirect is returned
+// from a proxy.
+class ProxyConnectRedirectHttpStream : public HttpStream {
+ public:
+ // |load_timing_info| is the info that should be returned by
+ // GetLoadTimingInfo(), or NULL if there is none. Does not take
+ // ownership of |load_timing_info|.
+ explicit ProxyConnectRedirectHttpStream(LoadTimingInfo* load_timing_info);
+ virtual ~ProxyConnectRedirectHttpStream();
+
+ // All functions below are expected not to be called except for the
+ // marked one.
+
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual int ReadResponseBody(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // This function may be called.
+ virtual void Close(bool not_reusable) OVERRIDE;
+
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+
+ // This function may be called.
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+
+ virtual bool IsConnectionReused() const OVERRIDE;
+ virtual void SetConnectionReused() OVERRIDE;
+ virtual bool IsConnectionReusable() const OVERRIDE;
+
+ // This function may be called.
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+
+ // This function may be called.
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+
+ private:
+ bool has_load_timing_info_;
+ LoadTimingInfo load_timing_info_;
+};
+
+} // namespace net
+
+#endif // NET_HTTP_PROXY_CONNECT_REDIRECT_HTTP_STREAM_H_
diff --git a/chromium/net/http/transport_security_state.cc b/chromium/net/http/transport_security_state.cc
new file mode 100644
index 00000000000..d238e995568
--- /dev/null
+++ b/chromium/net/http/transport_security_state.cc
@@ -0,0 +1,894 @@
+// Copyright (c) 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.
+
+#include "net/http/transport_security_state.h"
+
+#if defined(USE_OPENSSL)
+#include <openssl/ecdsa.h>
+#include <openssl/ssl.h>
+#else // !defined(USE_OPENSSL)
+#include <cryptohi.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <nspr.h>
+#include <pk11pub.h>
+#endif
+
+#include <algorithm>
+
+#include "base/base64.h"
+#include "base/build_time.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "crypto/sha2.h"
+#include "net/base/dns_util.h"
+#include "net/cert/x509_cert_types.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/http_security_headers.h"
+#include "net/ssl/ssl_info.h"
+#include "url/gurl.h"
+
+#if defined(USE_OPENSSL)
+#include "crypto/openssl_util.h"
+#endif
+
+namespace net {
+
+namespace {
+
+std::string HashesToBase64String(const HashValueVector& hashes) {
+ std::string str;
+ for (size_t i = 0; i != hashes.size(); ++i) {
+ if (i != 0)
+ str += ",";
+ str += hashes[i].ToString();
+ }
+ return str;
+}
+
+std::string HashHost(const std::string& canonicalized_host) {
+ char hashed[crypto::kSHA256Length];
+ crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed));
+ return std::string(hashed, sizeof(hashed));
+}
+
+// Returns true if the intersection of |a| and |b| is not empty. If either
+// |a| or |b| is empty, returns false.
+bool HashesIntersect(const HashValueVector& a,
+ const HashValueVector& b) {
+ for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) {
+ HashValueVector::const_iterator j =
+ std::find_if(b.begin(), b.end(), HashValuesEqual(*i));
+ if (j != b.end())
+ return true;
+ }
+ return false;
+}
+
+bool AddHash(const char* sha1_hash,
+ HashValueVector* out) {
+ HashValue hash(HASH_VALUE_SHA1);
+ memcpy(hash.data(), sha1_hash, hash.size());
+ out->push_back(hash);
+ return true;
+}
+
+} // namespace
+
+TransportSecurityState::TransportSecurityState()
+ : delegate_(NULL) {
+ DCHECK(CalledOnValidThread());
+}
+
+TransportSecurityState::Iterator::Iterator(const TransportSecurityState& state)
+ : iterator_(state.enabled_hosts_.begin()),
+ end_(state.enabled_hosts_.end()) {
+}
+
+TransportSecurityState::Iterator::~Iterator() {}
+
+void TransportSecurityState::SetDelegate(
+ TransportSecurityState::Delegate* delegate) {
+ DCHECK(CalledOnValidThread());
+ delegate_ = delegate;
+}
+
+void TransportSecurityState::EnableHost(const std::string& host,
+ const DomainState& state) {
+ DCHECK(CalledOnValidThread());
+
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ if (canonicalized_host.empty())
+ return;
+
+ DomainState state_copy(state);
+ // No need to store this value since it is redundant. (|canonicalized_host|
+ // is the map key.)
+ state_copy.domain.clear();
+
+ enabled_hosts_[HashHost(canonicalized_host)] = state_copy;
+ DirtyNotify();
+}
+
+bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) {
+ DCHECK(CalledOnValidThread());
+
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ if (canonicalized_host.empty())
+ return false;
+
+ DomainStateMap::iterator i = enabled_hosts_.find(
+ HashHost(canonicalized_host));
+ if (i != enabled_hosts_.end()) {
+ enabled_hosts_.erase(i);
+ DirtyNotify();
+ return true;
+ }
+ return false;
+}
+
+bool TransportSecurityState::GetDomainState(const std::string& host,
+ bool sni_enabled,
+ DomainState* result) {
+ DCHECK(CalledOnValidThread());
+
+ DomainState state;
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ if (canonicalized_host.empty())
+ return false;
+
+ bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled,
+ &state);
+ std::string canonicalized_preload = CanonicalizeHost(state.domain);
+ GetDynamicDomainState(host, &state);
+
+ base::Time current_time(base::Time::Now());
+
+ for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
+ std::string host_sub_chunk(&canonicalized_host[i],
+ canonicalized_host.size() - i);
+ // Exact match of a preload always wins.
+ if (has_preload && host_sub_chunk == canonicalized_preload) {
+ *result = state;
+ return true;
+ }
+
+ DomainStateMap::iterator j =
+ enabled_hosts_.find(HashHost(host_sub_chunk));
+ if (j == enabled_hosts_.end())
+ continue;
+
+ if (current_time > j->second.upgrade_expiry &&
+ current_time > j->second.dynamic_spki_hashes_expiry) {
+ enabled_hosts_.erase(j);
+ DirtyNotify();
+ continue;
+ }
+
+ state = j->second;
+ state.domain = DNSDomainToString(host_sub_chunk);
+
+ // Succeed if we matched the domain exactly or if subdomain matches are
+ // allowed.
+ if (i == 0 || j->second.sts_include_subdomains ||
+ j->second.pkp_include_subdomains) {
+ *result = state;
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+void TransportSecurityState::ClearDynamicData() {
+ DCHECK(CalledOnValidThread());
+ enabled_hosts_.clear();
+}
+
+void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) {
+ DCHECK(CalledOnValidThread());
+
+ bool dirtied = false;
+
+ DomainStateMap::iterator i = enabled_hosts_.begin();
+ while (i != enabled_hosts_.end()) {
+ if (i->second.created >= time) {
+ dirtied = true;
+ enabled_hosts_.erase(i++);
+ } else {
+ i++;
+ }
+ }
+
+ if (dirtied)
+ DirtyNotify();
+}
+
+TransportSecurityState::~TransportSecurityState() {
+ DCHECK(CalledOnValidThread());
+}
+
+void TransportSecurityState::DirtyNotify() {
+ DCHECK(CalledOnValidThread());
+
+ if (delegate_)
+ delegate_->StateIsDirty(this);
+}
+
+// static
+std::string TransportSecurityState::CanonicalizeHost(const std::string& host) {
+ // We cannot perform the operations as detailed in the spec here as |host|
+ // has already undergone IDN processing before it reached us. Thus, we check
+ // that there are no invalid characters in the host and lowercase the result.
+
+ std::string new_host;
+ if (!DNSDomainFromDot(host, &new_host)) {
+ // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole
+ // name is >255 bytes. However, search terms can have those properties.
+ return std::string();
+ }
+
+ for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
+ const unsigned label_length = static_cast<unsigned>(new_host[i]);
+ if (!label_length)
+ break;
+
+ for (size_t j = 0; j < label_length; ++j) {
+ // RFC 3490, 4.1, step 3
+ if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j]))
+ return std::string();
+
+ new_host[i + 1 + j] = tolower(new_host[i + 1 + j]);
+ }
+
+ // step 3(b)
+ if (new_host[i + 1] == '-' ||
+ new_host[i + label_length] == '-') {
+ return std::string();
+ }
+ }
+
+ return new_host;
+}
+
+// |ReportUMAOnPinFailure| uses these to report which domain was associated
+// with the public key pinning failure.
+//
+// DO NOT CHANGE THE ORDERING OF THESE NAMES OR REMOVE ANY OF THEM. Add new
+// domains at the END of the listing (but before DOMAIN_NUM_EVENTS).
+enum SecondLevelDomainName {
+ DOMAIN_NOT_PINNED,
+
+ DOMAIN_GOOGLE_COM,
+ DOMAIN_ANDROID_COM,
+ DOMAIN_GOOGLE_ANALYTICS_COM,
+ DOMAIN_GOOGLEPLEX_COM,
+ DOMAIN_YTIMG_COM,
+ DOMAIN_GOOGLEUSERCONTENT_COM,
+ DOMAIN_YOUTUBE_COM,
+ DOMAIN_GOOGLEAPIS_COM,
+ DOMAIN_GOOGLEADSERVICES_COM,
+ DOMAIN_GOOGLECODE_COM,
+ DOMAIN_APPSPOT_COM,
+ DOMAIN_GOOGLESYNDICATION_COM,
+ DOMAIN_DOUBLECLICK_NET,
+ DOMAIN_GSTATIC_COM,
+ DOMAIN_GMAIL_COM,
+ DOMAIN_GOOGLEMAIL_COM,
+ DOMAIN_GOOGLEGROUPS_COM,
+
+ DOMAIN_TORPROJECT_ORG,
+
+ DOMAIN_TWITTER_COM,
+ DOMAIN_TWIMG_COM,
+
+ DOMAIN_AKAMAIHD_NET,
+
+ DOMAIN_TOR2WEB_ORG,
+
+ DOMAIN_YOUTU_BE,
+ DOMAIN_GOOGLECOMMERCE_COM,
+ DOMAIN_URCHIN_COM,
+ DOMAIN_GOO_GL,
+ DOMAIN_G_CO,
+ DOMAIN_GOOGLE_AC,
+ DOMAIN_GOOGLE_AD,
+ DOMAIN_GOOGLE_AE,
+ DOMAIN_GOOGLE_AF,
+ DOMAIN_GOOGLE_AG,
+ DOMAIN_GOOGLE_AM,
+ DOMAIN_GOOGLE_AS,
+ DOMAIN_GOOGLE_AT,
+ DOMAIN_GOOGLE_AZ,
+ DOMAIN_GOOGLE_BA,
+ DOMAIN_GOOGLE_BE,
+ DOMAIN_GOOGLE_BF,
+ DOMAIN_GOOGLE_BG,
+ DOMAIN_GOOGLE_BI,
+ DOMAIN_GOOGLE_BJ,
+ DOMAIN_GOOGLE_BS,
+ DOMAIN_GOOGLE_BY,
+ DOMAIN_GOOGLE_CA,
+ DOMAIN_GOOGLE_CAT,
+ DOMAIN_GOOGLE_CC,
+ DOMAIN_GOOGLE_CD,
+ DOMAIN_GOOGLE_CF,
+ DOMAIN_GOOGLE_CG,
+ DOMAIN_GOOGLE_CH,
+ DOMAIN_GOOGLE_CI,
+ DOMAIN_GOOGLE_CL,
+ DOMAIN_GOOGLE_CM,
+ DOMAIN_GOOGLE_CN,
+ DOMAIN_CO_AO,
+ DOMAIN_CO_BW,
+ DOMAIN_CO_CK,
+ DOMAIN_CO_CR,
+ DOMAIN_CO_HU,
+ DOMAIN_CO_ID,
+ DOMAIN_CO_IL,
+ DOMAIN_CO_IM,
+ DOMAIN_CO_IN,
+ DOMAIN_CO_JE,
+ DOMAIN_CO_JP,
+ DOMAIN_CO_KE,
+ DOMAIN_CO_KR,
+ DOMAIN_CO_LS,
+ DOMAIN_CO_MA,
+ DOMAIN_CO_MZ,
+ DOMAIN_CO_NZ,
+ DOMAIN_CO_TH,
+ DOMAIN_CO_TZ,
+ DOMAIN_CO_UG,
+ DOMAIN_CO_UK,
+ DOMAIN_CO_UZ,
+ DOMAIN_CO_VE,
+ DOMAIN_CO_VI,
+ DOMAIN_CO_ZA,
+ DOMAIN_CO_ZM,
+ DOMAIN_CO_ZW,
+ DOMAIN_COM_AF,
+ DOMAIN_COM_AG,
+ DOMAIN_COM_AI,
+ DOMAIN_COM_AR,
+ DOMAIN_COM_AU,
+ DOMAIN_COM_BD,
+ DOMAIN_COM_BH,
+ DOMAIN_COM_BN,
+ DOMAIN_COM_BO,
+ DOMAIN_COM_BR,
+ DOMAIN_COM_BY,
+ DOMAIN_COM_BZ,
+ DOMAIN_COM_CN,
+ DOMAIN_COM_CO,
+ DOMAIN_COM_CU,
+ DOMAIN_COM_CY,
+ DOMAIN_COM_DO,
+ DOMAIN_COM_EC,
+ DOMAIN_COM_EG,
+ DOMAIN_COM_ET,
+ DOMAIN_COM_FJ,
+ DOMAIN_COM_GE,
+ DOMAIN_COM_GH,
+ DOMAIN_COM_GI,
+ DOMAIN_COM_GR,
+ DOMAIN_COM_GT,
+ DOMAIN_COM_HK,
+ DOMAIN_COM_IQ,
+ DOMAIN_COM_JM,
+ DOMAIN_COM_JO,
+ DOMAIN_COM_KH,
+ DOMAIN_COM_KW,
+ DOMAIN_COM_LB,
+ DOMAIN_COM_LY,
+ DOMAIN_COM_MT,
+ DOMAIN_COM_MX,
+ DOMAIN_COM_MY,
+ DOMAIN_COM_NA,
+ DOMAIN_COM_NF,
+ DOMAIN_COM_NG,
+ DOMAIN_COM_NI,
+ DOMAIN_COM_NP,
+ DOMAIN_COM_NR,
+ DOMAIN_COM_OM,
+ DOMAIN_COM_PA,
+ DOMAIN_COM_PE,
+ DOMAIN_COM_PH,
+ DOMAIN_COM_PK,
+ DOMAIN_COM_PL,
+ DOMAIN_COM_PR,
+ DOMAIN_COM_PY,
+ DOMAIN_COM_QA,
+ DOMAIN_COM_RU,
+ DOMAIN_COM_SA,
+ DOMAIN_COM_SB,
+ DOMAIN_COM_SG,
+ DOMAIN_COM_SL,
+ DOMAIN_COM_SV,
+ DOMAIN_COM_TJ,
+ DOMAIN_COM_TN,
+ DOMAIN_COM_TR,
+ DOMAIN_COM_TW,
+ DOMAIN_COM_UA,
+ DOMAIN_COM_UY,
+ DOMAIN_COM_VC,
+ DOMAIN_COM_VE,
+ DOMAIN_COM_VN,
+ DOMAIN_GOOGLE_CV,
+ DOMAIN_GOOGLE_CZ,
+ DOMAIN_GOOGLE_DE,
+ DOMAIN_GOOGLE_DJ,
+ DOMAIN_GOOGLE_DK,
+ DOMAIN_GOOGLE_DM,
+ DOMAIN_GOOGLE_DZ,
+ DOMAIN_GOOGLE_EE,
+ DOMAIN_GOOGLE_ES,
+ DOMAIN_GOOGLE_FI,
+ DOMAIN_GOOGLE_FM,
+ DOMAIN_GOOGLE_FR,
+ DOMAIN_GOOGLE_GA,
+ DOMAIN_GOOGLE_GE,
+ DOMAIN_GOOGLE_GG,
+ DOMAIN_GOOGLE_GL,
+ DOMAIN_GOOGLE_GM,
+ DOMAIN_GOOGLE_GP,
+ DOMAIN_GOOGLE_GR,
+ DOMAIN_GOOGLE_GY,
+ DOMAIN_GOOGLE_HK,
+ DOMAIN_GOOGLE_HN,
+ DOMAIN_GOOGLE_HR,
+ DOMAIN_GOOGLE_HT,
+ DOMAIN_GOOGLE_HU,
+ DOMAIN_GOOGLE_IE,
+ DOMAIN_GOOGLE_IM,
+ DOMAIN_GOOGLE_INFO,
+ DOMAIN_GOOGLE_IQ,
+ DOMAIN_GOOGLE_IS,
+ DOMAIN_GOOGLE_IT,
+ DOMAIN_IT_AO,
+ DOMAIN_GOOGLE_JE,
+ DOMAIN_GOOGLE_JO,
+ DOMAIN_GOOGLE_JOBS,
+ DOMAIN_GOOGLE_JP,
+ DOMAIN_GOOGLE_KG,
+ DOMAIN_GOOGLE_KI,
+ DOMAIN_GOOGLE_KZ,
+ DOMAIN_GOOGLE_LA,
+ DOMAIN_GOOGLE_LI,
+ DOMAIN_GOOGLE_LK,
+ DOMAIN_GOOGLE_LT,
+ DOMAIN_GOOGLE_LU,
+ DOMAIN_GOOGLE_LV,
+ DOMAIN_GOOGLE_MD,
+ DOMAIN_GOOGLE_ME,
+ DOMAIN_GOOGLE_MG,
+ DOMAIN_GOOGLE_MK,
+ DOMAIN_GOOGLE_ML,
+ DOMAIN_GOOGLE_MN,
+ DOMAIN_GOOGLE_MS,
+ DOMAIN_GOOGLE_MU,
+ DOMAIN_GOOGLE_MV,
+ DOMAIN_GOOGLE_MW,
+ DOMAIN_GOOGLE_NE,
+ DOMAIN_NE_JP,
+ DOMAIN_GOOGLE_NET,
+ DOMAIN_GOOGLE_NL,
+ DOMAIN_GOOGLE_NO,
+ DOMAIN_GOOGLE_NR,
+ DOMAIN_GOOGLE_NU,
+ DOMAIN_OFF_AI,
+ DOMAIN_GOOGLE_PK,
+ DOMAIN_GOOGLE_PL,
+ DOMAIN_GOOGLE_PN,
+ DOMAIN_GOOGLE_PS,
+ DOMAIN_GOOGLE_PT,
+ DOMAIN_GOOGLE_RO,
+ DOMAIN_GOOGLE_RS,
+ DOMAIN_GOOGLE_RU,
+ DOMAIN_GOOGLE_RW,
+ DOMAIN_GOOGLE_SC,
+ DOMAIN_GOOGLE_SE,
+ DOMAIN_GOOGLE_SH,
+ DOMAIN_GOOGLE_SI,
+ DOMAIN_GOOGLE_SK,
+ DOMAIN_GOOGLE_SM,
+ DOMAIN_GOOGLE_SN,
+ DOMAIN_GOOGLE_SO,
+ DOMAIN_GOOGLE_ST,
+ DOMAIN_GOOGLE_TD,
+ DOMAIN_GOOGLE_TG,
+ DOMAIN_GOOGLE_TK,
+ DOMAIN_GOOGLE_TL,
+ DOMAIN_GOOGLE_TM,
+ DOMAIN_GOOGLE_TN,
+ DOMAIN_GOOGLE_TO,
+ DOMAIN_GOOGLE_TP,
+ DOMAIN_GOOGLE_TT,
+ DOMAIN_GOOGLE_US,
+ DOMAIN_GOOGLE_UZ,
+ DOMAIN_GOOGLE_VG,
+ DOMAIN_GOOGLE_VU,
+ DOMAIN_GOOGLE_WS,
+
+ DOMAIN_CHROMIUM_ORG,
+
+ DOMAIN_CRYPTO_CAT,
+
+ // Boundary value for UMA_HISTOGRAM_ENUMERATION:
+ DOMAIN_NUM_EVENTS
+};
+
+// PublicKeyPins contains a number of SubjectPublicKeyInfo hashes for a site.
+// The validated certificate chain for the site must not include any of
+// |excluded_hashes| and must include one or more of |required_hashes|.
+struct PublicKeyPins {
+ const char* const* required_hashes;
+ const char* const* excluded_hashes;
+};
+
+struct HSTSPreload {
+ uint8 length;
+ bool include_subdomains;
+ char dns_name[38];
+ bool https_required;
+ PublicKeyPins pins;
+ SecondLevelDomainName second_level_domain_name;
+};
+
+static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries,
+ const std::string& canonicalized_host, size_t i,
+ TransportSecurityState::DomainState* out, bool* ret) {
+ for (size_t j = 0; j < num_entries; j++) {
+ if (entries[j].length == canonicalized_host.size() - i &&
+ memcmp(entries[j].dns_name, &canonicalized_host[i],
+ entries[j].length) == 0) {
+ if (!entries[j].include_subdomains && i != 0) {
+ *ret = false;
+ } else {
+ out->sts_include_subdomains = entries[j].include_subdomains;
+ out->pkp_include_subdomains = entries[j].include_subdomains;
+ *ret = true;
+ if (!entries[j].https_required)
+ out->upgrade_mode = TransportSecurityState::DomainState::MODE_DEFAULT;
+ if (entries[j].pins.required_hashes) {
+ const char* const* sha1_hash = entries[j].pins.required_hashes;
+ while (*sha1_hash) {
+ AddHash(*sha1_hash, &out->static_spki_hashes);
+ sha1_hash++;
+ }
+ }
+ if (entries[j].pins.excluded_hashes) {
+ const char* const* sha1_hash = entries[j].pins.excluded_hashes;
+ while (*sha1_hash) {
+ AddHash(*sha1_hash, &out->bad_static_spki_hashes);
+ sha1_hash++;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+#include "net/http/transport_security_state_static.h"
+
+// Returns the HSTSPreload entry for the |canonicalized_host| in |entries|,
+// or NULL if there is none. Prefers exact hostname matches to those that
+// match only because HSTSPreload.include_subdomains is true.
+//
+// |canonicalized_host| should be the hostname as canonicalized by
+// CanonicalizeHost.
+static const struct HSTSPreload* GetHSTSPreload(
+ const std::string& canonicalized_host,
+ const struct HSTSPreload* entries,
+ size_t num_entries) {
+ for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
+ for (size_t j = 0; j < num_entries; j++) {
+ const struct HSTSPreload* entry = entries + j;
+
+ if (i != 0 && !entry->include_subdomains)
+ continue;
+
+ if (entry->length == canonicalized_host.size() - i &&
+ memcmp(entry->dns_name, &canonicalized_host[i], entry->length) == 0) {
+ return entry;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+bool TransportSecurityState::AddHSTSHeader(const std::string& host,
+ const std::string& value) {
+ DCHECK(CalledOnValidThread());
+
+ base::Time now = base::Time::Now();
+ base::TimeDelta max_age;
+ TransportSecurityState::DomainState domain_state;
+ GetDynamicDomainState(host, &domain_state);
+ if (ParseHSTSHeader(value, &max_age, &domain_state.sts_include_subdomains)) {
+ // Handle max-age == 0
+ if (max_age.InSeconds() == 0)
+ domain_state.upgrade_mode = DomainState::MODE_DEFAULT;
+ else
+ domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS;
+ domain_state.created = now;
+ domain_state.upgrade_expiry = now + max_age;
+ EnableHost(host, domain_state);
+ return true;
+ }
+ return false;
+}
+
+bool TransportSecurityState::AddHPKPHeader(const std::string& host,
+ const std::string& value,
+ const SSLInfo& ssl_info) {
+ DCHECK(CalledOnValidThread());
+
+ base::Time now = base::Time::Now();
+ base::TimeDelta max_age;
+ TransportSecurityState::DomainState domain_state;
+ GetDynamicDomainState(host, &domain_state);
+ if (ParseHPKPHeader(value, ssl_info.public_key_hashes,
+ &max_age, &domain_state.pkp_include_subdomains,
+ &domain_state.dynamic_spki_hashes)) {
+ // TODO(palmer): http://crbug.com/243865 handle max-age == 0.
+ domain_state.created = now;
+ domain_state.dynamic_spki_hashes_expiry = now + max_age;
+ EnableHost(host, domain_state);
+ return true;
+ }
+ return false;
+}
+
+bool TransportSecurityState::AddHSTS(const std::string& host,
+ const base::Time& expiry,
+ bool include_subdomains) {
+ DCHECK(CalledOnValidThread());
+
+ // Copy-and-modify the existing DomainState for this host (if any).
+ TransportSecurityState::DomainState domain_state;
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ const std::string hashed_host = HashHost(canonicalized_host);
+ DomainStateMap::const_iterator i = enabled_hosts_.find(
+ hashed_host);
+ if (i != enabled_hosts_.end())
+ domain_state = i->second;
+
+ domain_state.created = base::Time::Now();
+ domain_state.sts_include_subdomains = include_subdomains;
+ domain_state.upgrade_expiry = expiry;
+ domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS;
+ EnableHost(host, domain_state);
+ return true;
+}
+
+bool TransportSecurityState::AddHPKP(const std::string& host,
+ const base::Time& expiry,
+ bool include_subdomains,
+ const HashValueVector& hashes) {
+ DCHECK(CalledOnValidThread());
+
+ // Copy-and-modify the existing DomainState for this host (if any).
+ TransportSecurityState::DomainState domain_state;
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ const std::string hashed_host = HashHost(canonicalized_host);
+ DomainStateMap::const_iterator i = enabled_hosts_.find(
+ hashed_host);
+ if (i != enabled_hosts_.end())
+ domain_state = i->second;
+
+ domain_state.created = base::Time::Now();
+ domain_state.pkp_include_subdomains = include_subdomains;
+ domain_state.dynamic_spki_hashes_expiry = expiry;
+ domain_state.dynamic_spki_hashes = hashes;
+ EnableHost(host, domain_state);
+ return true;
+}
+
+// static
+bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host,
+ bool sni_enabled) {
+ std::string canonicalized_host = CanonicalizeHost(host);
+ const struct HSTSPreload* entry =
+ GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
+
+ if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
+ return true;
+
+ if (sni_enabled) {
+ entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
+ kNumPreloadedSNISTS);
+ if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts)
+ return true;
+ }
+
+ return false;
+}
+
+// static
+void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) {
+ std::string canonicalized_host = CanonicalizeHost(host);
+
+ const struct HSTSPreload* entry =
+ GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS);
+
+ if (!entry) {
+ entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS,
+ kNumPreloadedSNISTS);
+ }
+
+ if (!entry) {
+ // We don't care to report pin failures for dynamic pins.
+ return;
+ }
+
+ DCHECK(entry);
+ DCHECK(entry->pins.required_hashes);
+ DCHECK(entry->second_level_domain_name != DOMAIN_NOT_PINNED);
+
+ UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain",
+ entry->second_level_domain_name, DOMAIN_NUM_EVENTS);
+}
+
+// static
+bool TransportSecurityState::IsBuildTimely() {
+ const base::Time build_time = base::GetBuildTime();
+ // We consider built-in information to be timely for 10 weeks.
+ return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */;
+}
+
+bool TransportSecurityState::GetStaticDomainState(
+ const std::string& canonicalized_host,
+ bool sni_enabled,
+ DomainState* out) {
+ DCHECK(CalledOnValidThread());
+
+ out->upgrade_mode = DomainState::MODE_FORCE_HTTPS;
+ out->sts_include_subdomains = false;
+ out->pkp_include_subdomains = false;
+
+ const bool is_build_timely = IsBuildTimely();
+
+ for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
+ std::string host_sub_chunk(&canonicalized_host[i],
+ canonicalized_host.size() - i);
+ out->domain = DNSDomainToString(host_sub_chunk);
+ bool ret;
+ if (is_build_timely &&
+ HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out,
+ &ret)) {
+ return ret;
+ }
+ if (sni_enabled &&
+ is_build_timely &&
+ HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i,
+ out, &ret)) {
+ return ret;
+ }
+ }
+
+ return false;
+}
+
+bool TransportSecurityState::GetDynamicDomainState(const std::string& host,
+ DomainState* result) {
+ DCHECK(CalledOnValidThread());
+
+ DomainState state;
+ const std::string canonicalized_host = CanonicalizeHost(host);
+ if (canonicalized_host.empty())
+ return false;
+
+ base::Time current_time(base::Time::Now());
+
+ for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
+ std::string host_sub_chunk(&canonicalized_host[i],
+ canonicalized_host.size() - i);
+ DomainStateMap::iterator j =
+ enabled_hosts_.find(HashHost(host_sub_chunk));
+ if (j == enabled_hosts_.end())
+ continue;
+
+ if (current_time > j->second.upgrade_expiry &&
+ current_time > j->second.dynamic_spki_hashes_expiry) {
+ enabled_hosts_.erase(j);
+ DirtyNotify();
+ continue;
+ }
+
+ state = j->second;
+ state.domain = DNSDomainToString(host_sub_chunk);
+
+ // Succeed if we matched the domain exactly or if subdomain matches are
+ // allowed.
+ if (i == 0 || j->second.sts_include_subdomains ||
+ j->second.pkp_include_subdomains) {
+ *result = state;
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+
+void TransportSecurityState::AddOrUpdateEnabledHosts(
+ const std::string& hashed_host, const DomainState& state) {
+ DCHECK(CalledOnValidThread());
+ enabled_hosts_[hashed_host] = state;
+}
+
+TransportSecurityState::DomainState::DomainState()
+ : upgrade_mode(MODE_DEFAULT),
+ created(base::Time::Now()),
+ sts_include_subdomains(false),
+ pkp_include_subdomains(false) {
+}
+
+TransportSecurityState::DomainState::~DomainState() {
+}
+
+bool TransportSecurityState::DomainState::CheckPublicKeyPins(
+ const HashValueVector& hashes) const {
+ // Validate that hashes is not empty. By the time this code is called (in
+ // production), that should never happen, but it's good to be defensive.
+ // And, hashes *can* be empty in some test scenarios.
+ if (hashes.empty()) {
+ LOG(ERROR) << "Rejecting empty public key chain for public-key-pinned "
+ "domain " << domain;
+ return false;
+ }
+
+ if (HashesIntersect(bad_static_spki_hashes, hashes)) {
+ LOG(ERROR) << "Rejecting public key chain for domain " << domain
+ << ". Validated chain: " << HashesToBase64String(hashes)
+ << ", matches one or more bad hashes: "
+ << HashesToBase64String(bad_static_spki_hashes);
+ return false;
+ }
+
+ // If there are no pins, then any valid chain is acceptable.
+ if (dynamic_spki_hashes.empty() && static_spki_hashes.empty())
+ return true;
+
+ if (HashesIntersect(dynamic_spki_hashes, hashes) ||
+ HashesIntersect(static_spki_hashes, hashes)) {
+ return true;
+ }
+
+ LOG(ERROR) << "Rejecting public key chain for domain " << domain
+ << ". Validated chain: " << HashesToBase64String(hashes)
+ << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
+ << " or: " << HashesToBase64String(static_spki_hashes);
+ return false;
+}
+
+bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const {
+ return upgrade_mode == MODE_FORCE_HTTPS;
+}
+
+bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const {
+ return true;
+}
+
+bool TransportSecurityState::DomainState::HasPublicKeyPins() const {
+ return static_spki_hashes.size() > 0 ||
+ bad_static_spki_hashes.size() > 0 ||
+ dynamic_spki_hashes.size() > 0;
+}
+
+} // namespace
diff --git a/chromium/net/http/transport_security_state.h b/chromium/net/http/transport_security_state.h
new file mode 100644
index 00000000000..ccbc53a087d
--- /dev/null
+++ b/chromium/net/http/transport_security_state.h
@@ -0,0 +1,328 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_TRANSPORT_SECURITY_STATE_H_
+#define NET_HTTP_TRANSPORT_SECURITY_STATE_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_cert_types.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+class SSLInfo;
+
+// Tracks which hosts have enabled strict transport security and/or public
+// key pins.
+//
+// This object manages the in-memory store. Register a Delegate with
+// |SetDelegate| to persist the state to disk.
+//
+// HTTP strict transport security (HSTS) is defined in
+// http://tools.ietf.org/html/ietf-websec-strict-transport-sec, and
+// HTTP-based dynamic public key pinning (HPKP) is defined in
+// http://tools.ietf.org/html/ietf-websec-key-pinning.
+class NET_EXPORT TransportSecurityState
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ class Delegate {
+ public:
+ // This function may not block and may be called with internal locks held.
+ // Thus it must not reenter the TransportSecurityState object.
+ virtual void StateIsDirty(TransportSecurityState* state) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ TransportSecurityState();
+ ~TransportSecurityState();
+
+ // A DomainState describes the transport security state (required upgrade
+ // to HTTPS, and/or any public key pins).
+ class NET_EXPORT DomainState {
+ public:
+ enum UpgradeMode {
+ // These numbers must match those in hsts_view.js, function modeToString.
+ MODE_FORCE_HTTPS = 0,
+ MODE_DEFAULT = 1,
+ };
+
+ DomainState();
+ ~DomainState();
+
+ // Takes a set of SubjectPublicKeyInfo |hashes| and returns true if:
+ // 1) |bad_static_spki_hashes| does not intersect |hashes|; AND
+ // 2) Both |static_spki_hashes| and |dynamic_spki_hashes| are empty
+ // or at least one of them intersects |hashes|.
+ //
+ // |{dynamic,static}_spki_hashes| contain trustworthy public key hashes,
+ // any one of which is sufficient to validate the certificate chain in
+ // question. The public keys could be of a root CA, intermediate CA, or
+ // leaf certificate, depending on the security vs. disaster recovery
+ // tradeoff selected. (Pinning only to leaf certifiates increases
+ // security because you no longer trust any CAs, but it hampers disaster
+ // recovery because you can't just get a new certificate signed by the
+ // CA.)
+ //
+ // |bad_static_spki_hashes| contains public keys that we don't want to
+ // trust.
+ bool CheckPublicKeyPins(const HashValueVector& hashes) const;
+
+ // Returns true if any of the HashValueVectors |static_spki_hashes|,
+ // |bad_static_spki_hashes|, or |dynamic_spki_hashes| contains any
+ // items.
+ bool HasPublicKeyPins() const;
+
+ // ShouldUpgradeToSSL returns true iff, given the |mode| of this
+ // DomainState, HTTP requests should be internally redirected to HTTPS
+ // (also if the "ws" WebSocket request should be upgraded to "wss")
+ bool ShouldUpgradeToSSL() const;
+
+ // ShouldSSLErrorsBeFatal returns true iff HTTPS errors should cause
+ // hard-fail behavior (e.g. if HSTS is set for the domain)
+ bool ShouldSSLErrorsBeFatal() const;
+
+ UpgradeMode upgrade_mode;
+
+ // The absolute time (UTC) when this DomainState was first created.
+ //
+ // Static entries do not have a created time.
+ base::Time created;
+
+ // The absolute time (UTC) when the |upgrade_mode|, if set to
+ // UPGRADE_ALWAYS, downgrades to UPGRADE_NEVER.
+ base::Time upgrade_expiry;
+
+ // Are subdomains subject to this DomainState, for the purposes of
+ // upgrading to HTTPS?
+ bool sts_include_subdomains;
+
+ // Are subdomains subject to this DomainState, for the purposes of
+ // Pin Validation?
+ bool pkp_include_subdomains;
+
+ // Optional; hashes of static pinned SubjectPublicKeyInfos. Unless both
+ // are empty, at least one of |static_spki_hashes| and
+ // |dynamic_spki_hashes| MUST intersect with the set of SPKIs in the TLS
+ // server's certificate chain.
+ //
+ // |dynamic_spki_hashes| take precedence over |static_spki_hashes|.
+ // That is, |IsChainOfPublicKeysPermitted| first checks dynamic pins and
+ // then checks static pins.
+ HashValueVector static_spki_hashes;
+
+ // Optional; hashes of dynamically pinned SubjectPublicKeyInfos.
+ HashValueVector dynamic_spki_hashes;
+
+ // The absolute time (UTC) when the |dynamic_spki_hashes| expire.
+ base::Time dynamic_spki_hashes_expiry;
+
+ // Optional; hashes of static known-bad SubjectPublicKeyInfos which
+ // MUST NOT intersect with the set of SPKIs in the TLS server's
+ // certificate chain.
+ HashValueVector bad_static_spki_hashes;
+
+ // The following members are not valid when stored in |enabled_hosts_|:
+
+ // The domain which matched during a search for this DomainState entry.
+ // Updated by |GetDomainState|, |GetDynamicDomainState|, and
+ // |GetStaticDomainState|.
+ std::string domain;
+ };
+
+ class NET_EXPORT Iterator {
+ public:
+ explicit Iterator(const TransportSecurityState& state);
+ ~Iterator();
+
+ bool HasNext() const { return iterator_ != end_; }
+ void Advance() { ++iterator_; }
+ const std::string& hostname() const { return iterator_->first; }
+ const DomainState& domain_state() const { return iterator_->second; }
+
+ private:
+ std::map<std::string, DomainState>::const_iterator iterator_;
+ std::map<std::string, DomainState>::const_iterator end_;
+ };
+
+ // Assign a |Delegate| for persisting the transport security state. If
+ // |NULL|, state will not be persisted. The caller retains
+ // ownership of |delegate|.
+ // Note: This is only used for serializing/deserializing the
+ // TransportSecurityState.
+ void SetDelegate(Delegate* delegate);
+
+ // Clears all dynamic data (e.g. HSTS and HPKP data).
+ //
+ // Does NOT persist changes using the Delegate, as this function is only
+ // used to clear any dynamic data prior to re-loading it from a file.
+ // Note: This is only used for serializing/deserializing the
+ // TransportSecurityState.
+ void ClearDynamicData();
+
+ // Inserts |state| into |enabled_hosts_| under the key |hashed_host|.
+ // |hashed_host| is already in the internal representation
+ // HashHost(CanonicalizeHost(host)).
+ // Note: This is only used for serializing/deserializing the
+ // TransportSecurityState.
+ void AddOrUpdateEnabledHosts(const std::string& hashed_host,
+ const DomainState& state);
+
+ // Deletes all dynamic data (e.g. HSTS or HPKP data) created since a given
+ // time.
+ //
+ // If any entries are deleted, the new state will be persisted through
+ // the Delegate (if any).
+ void DeleteAllDynamicDataSince(const base::Time& time);
+
+ // Deletes any dynamic data stored for |host| (e.g. HSTS or HPKP data).
+ // If |host| doesn't have an exact entry then no action is taken. Does
+ // not delete static (i.e. preloaded) data. Returns true iff an entry
+ // was deleted.
+ //
+ // If an entry is deleted, the new state will be persisted through
+ // the Delegate (if any).
+ bool DeleteDynamicDataForHost(const std::string& host);
+
+ // Returns true and updates |*result| iff there is a DomainState for
+ // |host|.
+ //
+ // If |sni_enabled| is true, searches the static pins defined for
+ // SNI-using hosts as well as the rest of the pins.
+ //
+ // If |host| matches both an exact entry and is a subdomain of another
+ // entry, the exact match determines the return value.
+ //
+ // Note that this method is not const because it opportunistically removes
+ // entries that have expired.
+ bool GetDomainState(const std::string& host,
+ bool sni_enabled,
+ DomainState* result);
+
+ // Processes an HSTS header value from the host, adding entries to
+ // dynamic state if necessary.
+ bool AddHSTSHeader(const std::string& host, const std::string& value);
+
+ // Processes an HPKP header value from the host, adding entries to
+ // dynamic state if necessary. ssl_info is used to check that
+ // the specified pins overlap with the certificate chain.
+ bool AddHPKPHeader(const std::string& host, const std::string& value,
+ const SSLInfo& ssl_info);
+
+ // Adds explicitly-specified data as if it was processed from an
+ // HSTS header (used for net-internals and unit tests).
+ bool AddHSTS(const std::string& host, const base::Time& expiry,
+ bool include_subdomains);
+
+ // Adds explicitly-specified data as if it was processed from an
+ // HPKP header (used for net-internals and unit tests).
+ bool AddHPKP(const std::string& host, const base::Time& expiry,
+ bool include_subdomains, const HashValueVector& hashes);
+
+ // Returns true iff we have any static public key pins for the |host| and
+ // iff its set of required pins is the set we expect for Google
+ // properties.
+ //
+ // If |sni_enabled| is true, searches the static pins defined for
+ // SNI-using hosts as well as the rest of the pins.
+ //
+ // If |host| matches both an exact entry and is a subdomain of another
+ // entry, the exact match determines the return value.
+ static bool IsGooglePinnedProperty(const std::string& host,
+ bool sni_enabled);
+
+ // The maximum number of seconds for which we'll cache an HSTS request.
+ static const long int kMaxHSTSAgeSecs;
+
+ // Send an UMA report on pin validation failure, if the host is in a
+ // statically-defined list of domains.
+ //
+ // TODO(palmer): This doesn't really belong here, and should be moved into
+ // the exactly one call site. This requires unifying |struct HSTSPreload|
+ // (an implementation detail of this class) with a more generic
+ // representation of first-class DomainStates, and exposing the preloads
+ // to the caller with |GetStaticDomainState|.
+ static void ReportUMAOnPinFailure(const std::string& host);
+
+ // IsBuildTimely returns true if the current build is new enough ensure that
+ // built in security information (i.e. HSTS preloading and pinning
+ // information) is timely.
+ static bool IsBuildTimely();
+
+ private:
+ friend class TransportSecurityStateTest;
+ FRIEND_TEST_ALL_PREFIXES(HttpSecurityHeadersTest,
+ UpdateDynamicPKPOnly);
+
+ typedef std::map<std::string, DomainState> DomainStateMap;
+
+ // If a Delegate is present, notify it that the internal state has
+ // changed.
+ void DirtyNotify();
+
+ // Enable TransportSecurity for |host|. |state| supercedes any previous
+ // state for the |host|, including static entries.
+ //
+ // The new state for |host| is persisted using the Delegate (if any).
+ void EnableHost(const std::string& host, const DomainState& state);
+
+ // Converts |hostname| from dotted form ("www.google.com") to the form
+ // used in DNS: "\x03www\x06google\x03com", lowercases that, and returns
+ // the result.
+ static std::string CanonicalizeHost(const std::string& hostname);
+
+ // Returns true and updates |*result| iff there is a static DomainState for
+ // |host|.
+ //
+ // |GetStaticDomainState| is identical to |GetDomainState| except that it
+ // searches only the statically-defined transport security state, ignoring
+ // all dynamically-added DomainStates.
+ //
+ // If |sni_enabled| is true, searches the static pins defined for
+ // SNI-using hosts as well as the rest of the pins.
+ //
+ // If |host| matches both an exact entry and is a subdomain of another
+ // entry, the exact match determines the return value.
+ //
+ // Note that this method is not const because it opportunistically removes
+ // entries that have expired.
+ bool GetStaticDomainState(const std::string& host,
+ bool sni_enabled,
+ DomainState* result);
+
+ // Returns true and updates |*result| iff there is a dynamic DomainState for
+ // |host|.
+ //
+ // |GetDynamicDomainState| is identical to |GetDomainState| except that it
+ // searches only the dynamically-added transport security state, ignoring
+ // all statically-defined DomainStates.
+ //
+ // If |host| matches both an exact entry and is a subdomain of another
+ // entry, the exact match determines the return value.
+ //
+ // Note that this method is not const because it opportunistically removes
+ // entries that have expired.
+ bool GetDynamicDomainState(const std::string& host, DomainState* result);
+
+ // The set of hosts that have enabled TransportSecurity.
+ DomainStateMap enabled_hosts_;
+
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportSecurityState);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_TRANSPORT_SECURITY_STATE_H_
diff --git a/chromium/net/http/transport_security_state_static.certs b/chromium/net/http/transport_security_state_static.certs
new file mode 100644
index 00000000000..610e56a103e
--- /dev/null
+++ b/chromium/net/http/transport_security_state_static.certs
@@ -0,0 +1,1218 @@
+# Copyright (c) 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.
+
+# This file contains pinned certificates to be used in conjunction with
+# hsts_preloaded.json. See the comments at the beginning of that file for
+# details.
+
+# Each entry consists of a line containing the name of the pin followed either
+# by a hash in the format "sha1/" + base64(hash), or a PEM encoded certificate.
+
+TestSPKI
+sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=
+
+VeriSignClass3
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
+2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
+2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+-----END CERTIFICATE-----
+
+VeriSignClass3_G3
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
+N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
+KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
+kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
+CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
+Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
+imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
+2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
+DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
+F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
+TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+
+Google1024
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3
+WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
+R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
+NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
+qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
+oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk
+MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
+Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde
+BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN
+0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml
+UUIuOss4jHg7y/j7lYe8vJD5UDI=
+-----END CERTIFICATE-----
+
+Google2048
+sha1/AbkhxY0L343gKf+cki7NVWp+ozk=
+
+GoogleBackup1024
+sha1/fVujyo43ZR18ccPjt3TN6XsbWUM=
+
+GoogleBackup2048
+sha1/vq7OyjSnqOco9nyMCDGdy77eijM=
+
+GoogleG2
+sha1/Q9rWMO5T+KmAym79hfRqo3mQ4Oo=
+
+EquifaxSecureCA
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+
+Aetna
+-----BEGIN CERTIFICATE-----
+MIICsjCCAhugAwIBAgIDBe3YMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDUwODMxMjA0MDM3WhcNMTIwODMxMjA0MDM3
+WjBIMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQWV0bmEgSW5jLjEkMCIGA1UEAxMb
+QWV0bmEgSW5jLiBTZWN1cmUgU2VydmVyIENBMIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQCnB2yrm4i44DG5epPu0fbe/pOZDWOvAS7qCcy6YbSkPfOHfH9Blmf3
+8L6D5yY1pzmTXaU7cDQu4qmj21toEIGwBziMmW6NsiV8nHtmtfXfHP6xrmyPUdN2
+DdTj937fnrYOoyMhGgBYEjiemeHFQxZSpKZdolFEFXbUa2/yWQafrQIDAQABo4Gj
+MIGgMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU2S4/xnaeitmFkzoxLnZeo33n
+H4owHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9QwEgYDVR0TAQH/BAgw
+BgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNv
+bS9jcmxzL3NlY3VyZWNhLmNybDANBgkqhkiG9w0BAQUFAAOBgQBMSoZHIrD1rq8v
+UG3UYbN76xiF9FDRzWTs5Mvv4Psvf2kk426slzNO0ukFAsmwqN1mA/P9Nc4FlMMC
+YtcnLNwC/syEYdQBOJjxfTVGTqh5q6jDs7S3rPJv8mrFk8ldC8PxU1ZJVfSlFCDn
+6diMDgvOAJfUeJlIRLGu2k/ksI0Y1w==
+-----END CERTIFICATE-----
+
+GeoTrustGlobal
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+
+GeoTrustPrimary
+-----BEGIN CERTIFICATE-----
+MIIDizCCAvSgAwIBAgIDDW5iMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMTI3MDAwMDAwWhcNMTgwODIxMTYxNTAw
+WjBYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UE
+AxMoR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64FXv/1Hx9Z62DZHvIQlMt3/aE
+CCBh1gFZapxEEa/vdv2Vfs5hMLt6g18CvQFmyu4VjW+hMJy9oYWelDrzVogAMc/Y
+7mqWAtntA4z7dW3n6rhVFgUWmvTgXrGIwGSFXBVNiMe3uuB16a0FPZ3HiUjguyjI
+A+Ewk2ReUsBZcCI1V4iK8ZUKg9e8MXMBNO3vRnHgawKoNXJrl5tm4MsceV/YGgRo
+HkcC5p1g4jaXAd/ONZLfvmfHbXdZO4+d1pAVlLxCNBDBOfmxJz5+1op1xbKvltOi
+3pvkmL594emBrbZv/NcO2uA0sA0ad+fjCJjvWPqchLc2r8LfrNL0EAZwcTUCAwEA
+AaOB6DCB5TAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCzVUEGXFYvwjzZhW0r7
+a9mZyTOSMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMA8GA1UdEwEB
+/wQFMAMBAf8wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9zZWN1cmVjYS5jcmwwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJ
+KoZIhvcNAQEFBQADgYEAr/MO1nKrx6mXyiprhDneeanwgeUIZ6vXLyACAXEMBCLJ
+HoiVA8lJOq9nCEmw1Qj1ID2AkaDFh6P7yaMXkfmoL67pD9+Wcg91F4BdeAFNnx9t
+e9j1QjgjGpmT9IO+OzV05zcTNXqstLaQgmwnpODsnjW9v+UpoUefWzL86Zl9Kzk=
+-----END CERTIFICATE-----
+
+Intel
+-----BEGIN CERTIFICATE-----
+MIIFijCCBHKgAwIBAgIKYSCKYgAAAAAACDANBgkqhkiG9w0BAQUFADBSMQswCQYD
+VQQGEwJVUzEaMBgGA1UEChMRSW50ZWwgQ29ycG9yYXRpb24xJzAlBgNVBAMTHklu
+dGVsIEV4dGVybmFsIEJhc2ljIFBvbGljeSBDQTAeFw0wOTA1MTUxOTI3MjZaFw0x
+NTA1MTUxOTM3MjZaMFYxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFJbnRlbCBDb3Jw
+b3JhdGlvbjErMCkGA1UEAxMiSW50ZWwgRXh0ZXJuYWwgQmFzaWMgSXNzdWluZyBD
+QSAzQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKQEM1Wn9TU9vc9C
++/Tc7KB+eiYElmrcEWE32WUdHvWG+IcQHVQsikTmMyKKojNLw2B5s6Iekc8ivDo/
+wCfjZzX9JyftMnc+AArc0la87Olybzm8K9jXEfTBvTnUSFSiI9ZYefITdiUgqlAF
+uljFZEHYKYtLuhrRacpmQfP4mV63NKdc2bT804HRf6YptZFa4k6YN94zlrGNrBuQ
+Q74WFzz/jLBusbUpEkro6Mu/ZYFOFWQrV9lBhF9Ruk8yN+3N6n9fUo/qBigiF2kE
+n9xVh1ykl7SCGL2jBUkXx4qgV27a6Si8lRRdgrHGtN/HWnSWlLXTH5l575H4Lq++
+77OFv38CAwEAAaOCAlwwggJYMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFA7G
+KvdZsggQkCVvw939imYxMCvFMAsGA1UdDwQEAwIBhjASBgkrBgEEAYI3FQEEBQID
+AQABMCMGCSsGAQQBgjcVAgQWBBQ5oFY2ekKQ/5Ktim+VdMeSWb4QWTAZBgkrBgEE
+AYI3FAIEDB4KAFMAdQBiAEMAQTAfBgNVHSMEGDAWgBQaxgxKxEdvqNutK/D0Vgaj
+7TdUDDCBvQYDVR0fBIG1MIGyMIGvoIGsoIGphk5odHRwOi8vd3d3LmludGVsLmNv
+bS9yZXBvc2l0b3J5L0NSTC9JbnRlbCUyMEV4dGVybmFsJTIwQmFzaWMlMjBQb2xp
+Y3klMjBDQS5jcmyGV2h0dHA6Ly9jZXJ0aWZpY2F0ZXMuaW50ZWwuY29tL3JlcG9z
+aXRvcnkvQ1JML0ludGVsJTIwRXh0ZXJuYWwlMjBCYXNpYyUyMFBvbGljeSUyMENB
+LmNybDCB4wYIKwYBBQUHAQEEgdYwgdMwYwYIKwYBBQUHMAKGV2h0dHA6Ly93d3cu
+aW50ZWwuY29tL3JlcG9zaXRvcnkvY2VydGlmaWNhdGVzL0ludGVsJTIwRXh0ZXJu
+YWwlMjBCYXNpYyUyMFBvbGljeSUyMENBLmNydDBsBggrBgEFBQcwAoZgaHR0cDov
+L2NlcnRpZmljYXRlcy5pbnRlbC5jb20vcmVwb3NpdG9yeS9jZXJ0aWZpY2F0ZXMv
+SW50ZWwlMjBFeHRlcm5hbCUyMEJhc2ljJTIwUG9saWN5JTIwQ0EuY3J0MA0GCSqG
+SIb3DQEBBQUAA4IBAQCxtQEHchVQhXyjEqtMVUMe6gkmPsIczHxSeqNbo9dsD+6x
+bT65JT+oYgpIAtfEsYXeUJu1cChqpb22U5bMAz7eaQcW5bzefufWvA6lg2048B8o
+czBj/q+5P5NpYrUO8jOmN4jTjfJq3ElZ7yFWpy7rB3Vm/aN6ATYqWfMbS/xfh+JC
+xmH3droUmMJI0/aZJHsLtjbjFnNsHDNrJZX1vxlM78Lb1hjskTENPmhbVbfTj5i/
+ZGnhv4tmI8QZPCNtcegXJrfhRl2D9bWpdTOPrWiLDUqzy1Z6KL7TcOS/PCl8RHCJ
+XkPau/thTQCpIoDa2+c+3XA++gRTfAQ4svTO260N
+-----END CERTIFICATE-----
+
+TCTrustCenter
+-----BEGIN CERTIFICATE-----
+MIIDWzCCAsSgAwIBAgIDCaxIMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDgwODE1MTY0NTE1WhcNMTMwMjE0MTc0NTE1
+WjBtMQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21iSDEe
+MBwGA1UECxMVVEMgVHJ1c3RDZW50ZXIgU1NMIENBMSAwHgYDVQQDExdUQyBUcnVz
+dENlbnRlciBTU0wgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AOkCoJoNbJw33wSxNWbDdmIfDIedR8Zmr/mjOhMkXdxRYb6qrl/WfMEuo4PBcysJ
+kF81LaDMkBH0zc7Hs1eYixrMVObkCmEUjxYylgOk4ExGwhmIWDJUWGslNBUIIhFf
++ucDWuGZNfILQrwCWRHYBG0n/6lZPylCqopCMYhBK5sTI/PyuHEAzDL7+buep/Na
+zn+oy/a6x1nobsuL9X2oFaWZb7Z6ty5kZ/U56JHa7vnsLrg4ePwiQb8jtyUdz0fD
+uMHkNzK0gWxr4hm0v92otYFuOTZqNLEJneeiILxUCCMop2chr1obpq2zGVNxJ/rP
+StWmcu75KBGMpT+mzFgIyf0CAwEAAaOBozCBoDAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFOe/bKlImXeG4tD/MKCQHQtk0IU6MB8GA1UdIwQYMBaAFEjmaPkr0rKV
+10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2g
+K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9zZWN1cmVjYS5jcmwwDQYJ
+KoZIhvcNAQEFBQADgYEAVKyJLbJha83PggEit8+dzh50wIsKXpTV2K6K4HnUI1kh
+xqocLVfQORluC+LS7L78D2EKTWLZ8WNujiP6DbbIPSTsMasuiBMQMBUlJMUqsp/M
+XmQJgIGAbxsr19MY6mmB30oWuo4cjHnkMzSCfhcON6Rxvbjijk2qCWXkk2T2HAk=
+-----END CERTIFICATE-----
+
+Vodafone
+-----BEGIN CERTIFICATE-----
+MIIDJDCCAo2gAwIBAgIDBfw3MA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYwNzIxMTUwNTA2WhcNMTEwNzEyMTUwNTA2
+WjA5MQswCQYDVQQGEwJVSzEXMBUGA1UEChMOVm9kYWZvbmUgR3JvdXAxETAPBgNV
+BAMTCFZvZGFmb25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs61K
+wbMcB+GGGbjyo1dYEiVNGRYKRsDXfeOgeq03Vebf7D5Xq6a0Qs4Rvp6CuRTSNDPi
+M+0vuQRW5sib9UD8UB2x4znc6FriRV4FUpAyKNVqQ9NB0MOBpQekVlX9DzcXkn+p
+zWRi6tt3CtPsaDyHo06oAwX5qu3tW3pjtf0vnQqJWwwA6Mp4YJ/acHD/vVtt67hz
+a0Upz0O2DEJetb3OaqI5yaNZ91y6i7sK0KTvBQxZHeJs+y5UjluHv3ptMUZvmsf0
+SiKysXnkg5mtsZSFlfM+U7dADq1zNb764NV5sSlmbDLEkvohQyg1p9gh2HX9Jk4A
+e9nnF4hjw2U33HLBXwIDAQABo4GgMIGdMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
+FgQUR+YiAaq+68BPLD6l0UcvzlkcgvswHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwDwYDVR0TAQH/BAUwAwEB/zA6BgNVHR8EMzAxMC+gLaArhilodHRw
+Oi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDANBgkqhkiG9w0B
+AQUFAAOBgQCs37zuSY/KkPigCvJevu+ewWy9GP2bFZi5EaxKuHGF+tYFZUNkyc06
+ACYMM3ADPM6dVUYeXIDZnPfV8BJFCpdoAHkSNlg341AVjabCOWtzOYolBn0ua8Wi
+BM471XfzzXD7yMliek9J4fUn2vQU7MYgEkSAA53ZkMScGDkA/c1wMQ==
+-----END CERTIFICATE-----
+
+RapidSSL
+-----BEGIN CERTIFICATE-----
+MIID1TCCAr2gAwIBAgIDAjbRMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjE5MjI0NTA1WhcNMjAwMjE4MjI0NTA1WjA8MQswCQYDVQQG
+EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xFDASBgNVBAMTC1JhcGlkU1NM
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3H4Vsce2cy1rfa0
+l6P7oeYLUF9QqjraD/w9KSRDxhApwfxVQHLuverfn7ZB9EhLyG7+T1cSi1v6kt1e
+6K3z8Buxe037z/3R5fjj3Of1c3/fAUnPjFbBvTfjW761T4uL8NpPx+PdVUdp3/Jb
+ewdPPeWsIcHIHXro5/YPoar1b96oZU8QiZwD84l6pV4BcjPtqelaHnnzh8jfyMX8
+N8iamte4dsywPuf95lTq319SQXhZV63xEtZ/vNWfcNMFbPqjfWdY3SZiHTGSDHl5
+HI7PynvBZq+odEj7joLCniyZXHstXZu8W1eefDp6E63yoxhbK1kPzVw662gzxigd
+gtFQiwIDAQABo4HZMIHWMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUa2k9ahhC
+St2PAmU5/TUkhniRFjAwHwYDVR0jBBgwFoAUwHqYaI2J+6sFZAwRfap9ZbjKzE4w
+EgYDVR0TAQH/BAgwBgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
+Lmdlb3RydXN0LmNvbS9jcmxzL2d0Z2xvYmFsLmNybDA0BggrBgEFBQcBAQQoMCYw
+JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdlb3RydXN0LmNvbTANBgkqhkiG9w0B
+AQUFAAOCAQEAq7y8Cl0YlOPBscOoTFXWvrSY8e48HM3P8yQkXJYDJ1j8Nq6iL4/x
+/torAsMzvcjdSCIrYA+lAxD9d/jQ7ZZnT/3qRyBwVNypDFV+4ZYlitm12ldKvo2O
+SUNjpWxOJ4cl61tt/qJ/OCjgNqutOaWlYsS3XFgsql0BYKZiZ6PAx2Ij9OdsRu61
+04BqIhPSLT90T+qvjF+0OJzbrs6vhB6m9jRRWXnT43XcvNfzc9+S7NIgWW+c+5X4
+knYYCnwPLKbK3opie9jzzl9ovY8+wXS7FXI6FoOpC+ZNmZzYV+yoAVHHb1c0XqtK
+LEL2TxyJeN4mTvVvk0wVaydWTQBUbHq3tw==
+-----END CERTIFICATE-----
+
+DigiCertEVRoot
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Tor1
+sha1/juNxSTv9UANmpC9kF5GKpmWNx3Y=
+Tor2
+sha1/lia43lPolzSPVIq34Dw57uYcLD8=
+Tor3
+sha1/rzEyQIKOh77j87n5bjWUNguXF8Y=
+
+VeriSignClass1
+-----BEGIN CERTIFICATE-----
+MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh
+c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05
+NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD
+VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp
+bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N
+H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR
+4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN
+BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo
+EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5
+FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx
+lA==
+-----END CERTIFICATE-----
+
+VeriSignClass3_G4
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
+U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
+SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
+biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
+GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
+fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
+aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
+aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
+kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
+4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
+FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+VeriSignClass4_G3
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
+GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
+U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
+NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
+ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
+ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
+CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
+g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
+2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
+bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+
+VeriSignClass1_G3
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
+cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
+LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
+aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
+VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
+aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
+bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
+IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
+nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
+8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
+ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
+PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
+6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
+n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
+qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
+wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
+ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
+pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
+E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
+-----END CERTIFICATE-----
+
+VeriSignClass2_G3
+-----BEGIN CERTIFICATE-----
+MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
+Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
+BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
+Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
+Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
+IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
+J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
+JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
+wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
+koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
+qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
+Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
+xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
+7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
+sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
+sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
+cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
+-----END CERTIFICATE-----
+
+VeriSignClass3_G2
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
+BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
+c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
+MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
+emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
+DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
+FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
+UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
+YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
+MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
+AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
+pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
+13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
+AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
+U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
+F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
+oJ2daZH9
+-----END CERTIFICATE-----
+
+VeriSignClass2_G2
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
+YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
+aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
+Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
+IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
+KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
+HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
+DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
+AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
+nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
+rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
+jBJ7xUS0rg==
+-----END CERTIFICATE-----
+
+VeriSignClass3_G5
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
+U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
+nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
+t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
+SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
+BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
+rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
+NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
+BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
+MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
+p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
+5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
+WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
+4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
+hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+
+VeriSignUniversal
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
+vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
+ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
+MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
+IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
+IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
+bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
+9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
+H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
+LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
+/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
+rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
+WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
+exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
+sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
+seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
+4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
+lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
+7M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+Twitter1
+sha1/Vv7zwhR9TtOIN/29MFI4cgHld40=
+
+GeoTrustGlobal2
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
+IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
+R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
+PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
+Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
+TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
+5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
+S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
+2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
+EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
+EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
+/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
+A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
+abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
+I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
+4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+
+GeoTrustUniversal
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
+IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
+VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
+cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
+QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
+F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
+c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
+mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
+VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
+teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
+f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
+Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
+nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
+/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
+MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
+9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
+IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
+ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
+uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
+Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
+QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
+koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
+ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
+DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
+bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+
+GeoTrustUniversal2
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
+MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
+c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
+VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
+c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
+WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
+FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
+XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
+se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
+KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
+IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
+y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
+hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
+QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
+Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
+HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
+KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
+L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
+Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
+ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
+T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
+GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
+1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
+OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
+6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
+QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+
+GeoTrustPrimary_G2
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
+MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
+KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
+MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
+NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
+BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
+MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
+So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
+tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
+CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
+qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
+rD6ogRLQy7rQkgu2npaqBA+K
+-----END CERTIFICATE-----
+
+GeoTrustPrimary_G3
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
+mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
+MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
+eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
+BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
+BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
+hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
+5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
+JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
+DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
+huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
+HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
+zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
+kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
+SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
+spki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+
+Entrust_2048
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
+NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
+KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
+T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
+J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
+nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+
+Entrust_EV
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
+NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
+BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
+Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
+4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
+KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
+rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
+94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
+sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
+gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
+kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
+vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
+O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
+AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
+9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
+eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
+0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+Entrust_G2
+-----BEGIN CERTIFICATE-----
+MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
+cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
+IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
+dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
+NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
+dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
+dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
+aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
+RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
+cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
+wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
+U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
+jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
+BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
+jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
+Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
+1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
+nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
+VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
+-----END CERTIFICATE-----
+
+Entrust_SSL
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
+MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
+ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
+b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
+U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
+A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
+I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
+wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
+AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
+oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
+BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
+dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
+MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
+b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
+MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
+E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
+MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
+hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
+95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
+2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+
+AAACertificateServices
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
+YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
+GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
+BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
+3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
+YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
+rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
+ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
+oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
+QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
+b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
+AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
+GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
+G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
+l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
+smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+
+AddTrustClass1CARoot
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
+MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
+VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
+CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
+tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
+dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
+PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
+BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
+ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
+7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
+43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
+pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
+WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+
+AddTrustExternalCARoot
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+
+AddTrustPublicCARoot
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
+MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
+ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
+BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
+6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
+GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
+dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
+1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
+62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
+BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
+MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
+cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
+b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
+IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
+iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
+4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
+XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+
+AddTrustQualifiedCARoot
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
+b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
+MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
+EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
+BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
+xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
+87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
+2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
+WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
+0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
+A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
+pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
+ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
+aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
+hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
+hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
+P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
+iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
+xqE=
+-----END CERTIFICATE-----
+
+COMODOCertificationAuthority
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
+UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
+2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
+Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
+nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
+/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
+SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
+IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
+zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
+BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
+ZQ==
+-----END CERTIFICATE-----
+
+SecureCertificateServices
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
+ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
+fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
+BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
+cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
+HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
+CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
+3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
+6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
+HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
+EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
+Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
+DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
+5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
+gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
+aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
+izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+
+TrustedCertificateServices
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
+aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
+MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
+VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
+fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
+TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
+fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
+1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
+kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
+A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
+ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
+dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
+Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
+HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
+jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
+xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
+dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+
+UTNDATACorpSGC
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
+VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
+dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
+E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
+D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
+4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
+lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
+bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
+o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
+MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
+LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
+BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
+AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
+j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
+KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
+2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
+mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+
+UTNUSERFirstClientAuthenticationandEmail
+-----BEGIN CERTIFICATE-----
+MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB
+rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt
+Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa
+Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV
+BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
+dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE
+AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B
+YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9
+hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l
+L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm
+SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM
+1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws
+6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw
+Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50
+aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
+AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u
+7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0
+xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ
+rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim
+eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk
+USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
+-----END CERTIFICATE-----
+
+UTNUSERFirstHardware
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
+-----END CERTIFICATE-----
+
+UTNUSERFirstObject
+-----BEGIN CERTIFICATE-----
+MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB
+lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt
+T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc
+BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3
+dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP
+HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO
+KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo
+5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+
+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb
+kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC
+AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov
+L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV
+HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN
+AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw
+NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB
+mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU
+4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5
+81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR
+Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g=
+-----END CERTIFICATE-----
+
+GTECyberTrustGlobalRoot
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
+VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
+bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
+b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
+iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
+r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
+04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
+GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
+3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
+lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+
+Tor2web
+-----BEGIN CERTIFICATE-----
+MIIEgjCCA2qgAwIBAgISESHiIwbyj8tbXjvCF3lADzOxMA0GCSqGSIb3DQEBBQUA
+MC4xETAPBgNVBAoTCEFscGhhU1NMMRkwFwYDVQQDExBBbHBoYVNTTCBDQSAtIEcy
+MB4XDTExMTIwNTEyMzYzMVoXDTE2MTIwNTA0NTk1OFowSDELMAkGA1UEBhMCREUx
+ITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDEWMBQGA1UEAxQNKi50
+b3Iyd2ViLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJZ/olAy
+7o+W0soGoxD5xWXGVKa3cQdv/daqwDyFhGINhVgsm3GS3Oo2XLAYvyvlUFceuy2v
+fRecb431lh7xtLhPpr5nZL/T0cjUxffstxSt5HI5BQ5Q/TFLA4iJQDzJgiNld0DJ
+RYd8gGADwh5cVBjvAtRouUbFw75b1/4hR3kJnQsHutvglLjWHmZtf/ZoZ39CbR1a
+LBJpEPoWkVqJ9LrvgA+aJ1wmi+oKLfSYQkDEn30DBeVxBZBp6tRc93eGqK1skzpG
+2Sof9cmlRNIXp8plYBvtsV3LKrFlBXvQRr+hhpjrqGNib02ynyJdRij7tOCLHfqW
+UitjVQVOWoGs49MCAwEAAaOCAX4wggF6MA4GA1UdDwEB/wQEAwIFoDBNBgNVHSAE
+RjBEMEIGCisGAQQBoDIBCgowNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv
+YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wJQYDVR0RBB4wHIINKi50b3Iyd2ViLm9y
+Z4ILdG9yMndlYi5vcmcwCQYDVR0TBAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+KwYBBQUHAwIwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybDIuYWxwaGFzc2wu
+Y29tL2dzL2dzYWxwaGFnMi5jcmwwTAYIKwYBBQUHAQEEQDA+MDwGCCsGAQUFBzAC
+hjBodHRwOi8vc2VjdXJlMi5hbHBoYXNzbC5jb20vY2FjZXJ0L2dzYWxwaGFnMi5j
+cnQwHQYDVR0OBBYEFLE3Bo2XTl90LORxYwgr2pPD06tSMB8GA1UdIwQYMBaAFBTq
+GVXwDg0yxh90M7eOZhpMEjEeMA0GCSqGSIb3DQEBBQUAA4IBAQAyOUFr9R7EKzPP
+B8UsWT5ckA/TNlOqbdo6fvqshQfH/FHUQja28IbYcpBiC2XsMov+r7WNiH3lh1CF
+WKT1SwfO6a0I/58CL36pL/asWv/onlDYgAsCwr1j7qcSiROZlpLD+tehiCE70afa
++3VlyoGsbKVZ2A7MrXnxIaYhmhe4Y+238PwyBT74fpBvwoFIcbccwWEST8J2y2YW
+4+SWm4pJtcJxJH/uJ8qzvZLwjzcgFKQbBLVtl+SRAblFSj64YuO9Xu97+nta1HuL
+fmLvlwIO/yvONapjePASH6prPdmWvj3Clqz381mkU1pLpxTgHQqeoP87DYi8z084
++maO9AY4
+-----END CERTIFICATE-----
+
+AlphaSSL_G2
+-----BEGIN CERTIFICATE-----
+MIIELzCCAxegAwIBAgILBAAAAAABL07hNwIwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMC4xETAPBgNVBAoTCEFscGhhU1NMMRkwFwYDVQQD
+ExBBbHBoYVNTTCBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAw/BliN8b3caChy/JC7pUxmM/RnWsSxQfmHKLHBD/CalSbi9l32WEP1+Bstjx
+T9fwWrvJr9Ax3SZGKpme2KmjtrgHxMlx95WE79LqH1Sg5b7kQSFWMRBkfR5jjpxx
+XDygLt5n3MiaIPB1yLC2J4Hrlw3uIkWlwi80J+zgWRJRsx4F5Tgg0mlZelkXvhpL
+OQgSeTObZGj+WIHdiAxqulm0ryRPYeDK/Bda0jxyq6dMt7nqLeP0P5miTcgdWPh/
+UzWO1yKIt2F2CBMTaWawV1kTMQpwgiuT1/biQBXQHQFyxxNYalrsGYkWPODIjYYq
++jfwNTLd7OX+gI73BWe0i0J1NQIDAQABo4IBIzCCAR8wDgYDVR0PAQH/BAQDAgEG
+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFBTqGVXwDg0yxh90M7eOZhpM
+EjEeMEUGA1UdIAQ+MDwwOgYEVR0gADAyMDAGCCsGAQUFBwIBFiRodHRwczovL3d3
+dy5hbHBoYXNzbC5jb20vcmVwb3NpdG9yeS8wMwYDVR0fBCwwKjAooCagJIYiaHR0
+cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNybDA9BggrBgEFBQcBAQQxMC8w
+LQYIKwYBBQUHMAGGIWh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL3Jvb3RyMTAf
+BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOC
+AQEABjBCm89JAn6J6fWDWj0C87yyRt5KUO65mpBz2qBcJsqCrA6ts5T6KC6y5kk/
+UHcOlS9o82U8nxTyaGCStvwEDfakGKFpYA3jnWhbvJ4LOFmNIdoj+pmKCbkfpy61
+VWxH50Hs5uJ/r1VEOeCsdO5l0/qrUUgw8T53be3kD0CY7kd/jbZYJ82Sb2AjzAKb
+WSh4olGd0Eqc5ZNemI/L7z/K/uCvpMlbbkBYpZItvV1lVcW/fARB2aS1gOmUYAIQ
+OGoICNdTHC2Tr8kTe9RsxDrE+4CsuzpOVHrNTrM+7fH8EU6f9fMUvLmxMc72qi+l
++MPpZqmyIJ3E+LgDYqeF0RhjWw==
+-----END CERTIFICATE-----
+
+CryptoCat1
+-----BEGIN CERTIFICATE-----
+MIIHtTCCBp2gAwIBAgIQBQJvUvveB1Ep7CH9FkuAMjANBgkqhkiG9w0BAQUFADBm
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBDQS0zMB4XDTEyMTEwOTAwMDAwMFoXDTE1MDExMjEyMDAwMFowczELMAkGA1UE
+BhMCQ0ExDzANBgNVBAgTBlF1ZWJlYzERMA8GA1UEBxMITW9udHJlYWwxFzAVBgNV
+BAoTDk5hZGltIEtvYmVpc3NpMRIwEAYDVQQLEwlDcnlwdG9jYXQxEzARBgNVBAMT
+CmNyeXB0by5jYXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDS1vdN
+oZ+gNyjs3RnyQ0ZfQ8aGeRvnzYBZsZawwmRN/aSzdYhwxENAKr1wUnJk7aiXhYWq
+Z+ME/GUI8/89LHyHImQtVxTVRgfDSQSeciP7fplJGDNTz9+IOS849doom2sTsolH
+WJtJ/ggSDX4igDc+6/c3D1rz34gAGspQ1XK2TZEcPvN6+IW9a+BUqYMfyRZmoft6
+YfDDf8S36weyyIASoRc9nB02wvOJ/7ME8sbExXbUEjQBr/HFcI3IwElft32zLsVu
+NWdYz0TsFHG5tPhHSGQ/QI85uON4TJthlu3T0lV//T5nME2hijRcMo4C/yD+JK5Y
+4h0tLCHrce4xx4au4EPF2YoGO8IEPVlQLJb5Yz92vQaAA15NnDSCVlb4Tfe6zwsf
+7qBBobM9A1ZTKDtQiZZLHIACmJ4EfENtSFE+On3y31vvkaWXRROgo1Z0ECNBynb4
+ubPTlR2ZlAeE7Qp3b3GdMmnIP8QPn6tSxqYEveLwqOhXD/Upa5qxjnbbwQk1SRpA
+Pbvz5waGzA/9UXLwcgJguqh3cLIuKmOysCG6qiSkpfzSdeCDa4vheLJl+rUbS0DV
+abcRkRUmzRvNCeYYlTe6I0gHRC1UPef8aj2ppinw+9dWiQVxhjjIhw8JyGjM5Qg9
+NH5lDyXEuoS7f/VOTOOhAHvHSl5Ec/iXYU7PpQIDAQABo4IDUDCCA0wwHwYDVR0j
+BBgwFoAUUOpzidsp+xCPnuUBINTeeZlIg/cwHQYDVR0OBBYEFPtM/u402qRHQsv4
+C3aUc3BKnofhMCUGA1UdEQQeMByCCmNyeXB0by5jYXSCDnd3dy5jcnlwdG8uY2F0
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+YQYDVR0fBFowWDAqoCigJoYkaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL2NhMy1n
+MTYuY3JsMCqgKKAmhiRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vY2EzLWcxNi5j
+cmwwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwBATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wewYIKwYBBQUHAQEEbzBtMCQGCCsG
+AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRQYIKwYBBQUHMAKGOWh0
+dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VD
+QS0zLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQBlJ+AAe5Il
+9Tj9ZD7Equ9JjdbKi5srhjEDtDXtvl/2ga8Bjbh2qs0Qb18DtfpdTdIPuDF2KKjW
+kIQabl4h8s5NcQ/U7+AssAyKZGjiHs40G0iAlHpMeo/YzzEVqfoG+AT6c8caL4b0
+M2FnRee23OuKUdPixln3iShViViKt29NqDliIu4/IaeB7WkgJgljtIPNPZVoNqXa
+TvwjIDhO+wtc3qXjtO1zej3+GBmGz7RcZckturc2pZe3NRWQ7wO8ZzWShWU/ii3z
+2PftKlqZo3WAeJoUCPtQNsLnBFGvdUx2rUZwMhdgPuGeV4kEULAtu8M74xR5/Opz
+nRGP22zr1K4q
+-----END CERTIFICATE-----
diff --git a/chromium/net/http/transport_security_state_static.h b/chromium/net/http/transport_security_state_static.h
new file mode 100644
index 00000000000..22b5cf957d0
--- /dev/null
+++ b/chromium/net/http/transport_security_state_static.h
@@ -0,0 +1,850 @@
+// Copyright (c) 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.
+
+// This file is automatically generated by transport_security_state_static_generate.go
+
+#ifndef NET_HTTP_TRANSPORT_SECURITY_STATE_STATIC_H_
+#define NET_HTTP_TRANSPORT_SECURITY_STATE_STATIC_H_
+
+// These are SubjectPublicKeyInfo hashes for public key pinning. The
+// hashes are SHA1 digests.
+
+static const char kSPKIHash_TestSPKI[] =
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+
+static const char kSPKIHash_VeriSignClass3[] =
+ "\xe2\x7f\x7b\xd8\x77\xd5\xdf\x9e\x0a\x3f"
+ "\x9e\xb4\xcb\x0e\x2e\xa9\xef\xdb\x69\x77";
+
+static const char kSPKIHash_VeriSignClass3_G3[] =
+ "\x22\xf1\x9e\x2e\xc6\xea\xcc\xfc\x5d\x23"
+ "\x46\xf4\xc2\xe8\xf6\xc5\x54\xdd\x5e\x07";
+
+static const char kSPKIHash_Google1024[] =
+ "\x40\xc5\x40\x1d\x6f\x8c\xba\xf0\x8b\x00"
+ "\xed\xef\xb1\xee\x87\xd0\x05\xb3\xb9\xcd";
+
+static const char kSPKIHash_Google2048[] =
+ "\x01\xb9\x21\xc5\x8d\x0b\xdf\x8d\xe0\x29"
+ "\xff\x9c\x92\x2e\xcd\x55\x6a\x7e\xa3\x39";
+
+static const char kSPKIHash_GoogleBackup1024[] =
+ "\x7d\x5b\xa3\xca\x8e\x37\x65\x1d\x7c\x71"
+ "\xc3\xe3\xb7\x74\xcd\xe9\x7b\x1b\x59\x43";
+
+static const char kSPKIHash_GoogleBackup2048[] =
+ "\xbe\xae\xce\xca\x34\xa7\xa8\xe7\x28\xf6"
+ "\x7c\x8c\x08\x31\x9d\xcb\xbe\xde\x8a\x33";
+
+static const char kSPKIHash_GoogleG2[] =
+ "\x43\xda\xd6\x30\xee\x53\xf8\xa9\x80\xca"
+ "\x6e\xfd\x85\xf4\x6a\xa3\x79\x90\xe0\xea";
+
+static const char kSPKIHash_EquifaxSecureCA[] =
+ "\x48\xe6\x68\xf9\x2b\xd2\xb2\x95\xd7\x47"
+ "\xd8\x23\x20\x10\x4f\x33\x98\x90\x9f\xd4";
+
+static const char kSPKIHash_Aetna[] =
+ "\x92\x52\xaa\x14\xde\xbf\x80\xae\x30\xaa"
+ "\xd9\x4e\x60\x38\x70\x24\xa5\x43\x2f\x1a";
+
+static const char kSPKIHash_GeoTrustGlobal[] =
+ "\xc0\x7a\x98\x68\x8d\x89\xfb\xab\x05\x64"
+ "\x0c\x11\x7d\xaa\x7d\x65\xb8\xca\xcc\x4e";
+
+static const char kSPKIHash_GeoTrustPrimary[] =
+ "\xb0\x19\x89\xe7\xef\xfb\x4a\xaf\xcb\x14"
+ "\x8f\x58\x46\x39\x76\x22\x41\x50\xe1\xba";
+
+static const char kSPKIHash_Intel[] =
+ "\x0e\xc6\x2a\xf7\x59\xb2\x08\x10\x90\x25"
+ "\x6f\xc3\xdd\xfd\x8a\x66\x31\x30\x2b\xc5";
+
+static const char kSPKIHash_TCTrustCenter[] =
+ "\x83\x3b\x84\x10\x00\x7f\x6e\x4a\x9d\x41"
+ "\x2d\xc4\x22\x39\x36\x6f\x2e\xe5\x5b\xe9";
+
+static const char kSPKIHash_Vodafone[] =
+ "\x0d\x7f\xe1\x5c\x55\x14\x36\x68\x99\xfc"
+ "\x40\xd6\x22\x08\xef\x22\xeb\xd1\x15\x1c";
+
+static const char kSPKIHash_RapidSSL[] =
+ "\xa3\x93\x99\xc4\x04\xc3\xb2\x09\xb0\x81"
+ "\xc2\x1f\x21\x62\x27\x78\xc2\x74\x8e\x4c";
+
+static const char kSPKIHash_DigiCertEVRoot[] =
+ "\x83\x31\x7e\x62\x85\x42\x53\xd6\xd7\x78"
+ "\x31\x90\xec\x91\x90\x56\xe9\x91\xb9\xe3";
+
+static const char kSPKIHash_Tor1[] =
+ "\x8e\xe3\x71\x49\x3b\xfd\x50\x03\x66\xa4"
+ "\x2f\x64\x17\x91\x8a\xa6\x65\x8d\xc7\x76";
+
+static const char kSPKIHash_Tor2[] =
+ "\x96\x26\xb8\xde\x53\xe8\x97\x34\x8f\x54"
+ "\x8a\xb7\xe0\x3c\x39\xee\xe6\x1c\x2c\x3f";
+
+static const char kSPKIHash_Tor3[] =
+ "\xaf\x31\x32\x40\x82\x8e\x87\xbe\xe3\xf3"
+ "\xb9\xf9\x6e\x35\x94\x36\x0b\x97\x17\xc6";
+
+static const char kSPKIHash_VeriSignClass1[] =
+ "\x23\x43\xd1\x48\xa2\x55\x89\x9b\x94\x7d"
+ "\x46\x1a\x79\x7e\xc0\x4c\xfe\xd1\x70\xb7";
+
+static const char kSPKIHash_VeriSignClass3_G4[] =
+ "\xed\x66\x31\x35\xd3\x1b\xd4\xec\xa6\x14"
+ "\xc4\x29\xe3\x19\x06\x9f\x94\xc1\x26\x50";
+
+static const char kSPKIHash_VeriSignClass4_G3[] =
+ "\x3c\x03\x43\x68\x68\x95\x1c\xf3\x69\x2a"
+ "\xb8\xb4\x26\xda\xba\x8f\xe9\x22\xe5\xbd";
+
+static const char kSPKIHash_VeriSignClass1_G3[] =
+ "\x55\x19\xb2\x78\xac\xb2\x81\xd7\xed\xa7"
+ "\xab\xc1\x83\x99\xc3\xbb\x69\x04\x24\xb5";
+
+static const char kSPKIHash_VeriSignClass2_G3[] =
+ "\x5a\xbe\xc5\x75\xdc\xae\xf3\xb0\x8e\x27"
+ "\x19\x43\xfc\x7f\x25\x0c\x3d\xf6\x61\xe3";
+
+static const char kSPKIHash_VeriSignClass3_G2[] =
+ "\x1a\x21\xb4\x95\x2b\x62\x93\xce\x18\xb3"
+ "\x65\xec\x9c\x0e\x93\x4c\xb3\x81\xe6\xd4";
+
+static const char kSPKIHash_VeriSignClass2_G2[] =
+ "\x12\x37\xba\x45\x17\xee\xad\x29\x26\xfd"
+ "\xc1\xcd\xfe\xbe\xed\xf2\xde\xd9\x14\x5c";
+
+static const char kSPKIHash_VeriSignClass3_G5[] =
+ "\xb1\x81\x08\x1a\x19\xa4\xc0\x94\x1f\xfa"
+ "\xe8\x95\x28\xc1\x24\xc9\x9b\x34\xac\xc7";
+
+static const char kSPKIHash_VeriSignUniversal[] =
+ "\xbb\xc2\x3e\x29\x0b\xb3\x28\x77\x1d\xad"
+ "\x3e\xa2\x4d\xbd\xf4\x23\xbd\x06\xb0\x3d";
+
+static const char kSPKIHash_Twitter1[] =
+ "\x56\xfe\xf3\xc2\x14\x7d\x4e\xd3\x88\x37"
+ "\xfd\xbd\x30\x52\x38\x72\x01\xe5\x77\x8d";
+
+static const char kSPKIHash_GeoTrustGlobal2[] =
+ "\x71\x38\x36\xf2\x02\x31\x53\x47\x2b\x6e"
+ "\xba\x65\x46\xa9\x10\x15\x58\x20\x05\x09";
+
+static const char kSPKIHash_GeoTrustUniversal[] =
+ "\x87\xe8\x5b\x63\x53\xc6\x23\xa3\x12\x8c"
+ "\xb0\xff\xbb\xf5\x51\xfe\x59\x80\x0e\x22";
+
+static const char kSPKIHash_GeoTrustUniversal2[] =
+ "\x5e\x4f\x53\x86\x85\xdd\x4f\x9e\xca\x5f"
+ "\xdc\x0d\x45\x6f\x7d\x51\xb1\xdc\x9b\x7b";
+
+static const char kSPKIHash_GeoTrustPrimary_G2[] =
+ "\xbd\xbe\xa7\x1b\xab\x71\x57\xf9\xe4\x75"
+ "\xd9\x54\xd2\xb7\x27\x80\x1a\x82\x26\x82";
+
+static const char kSPKIHash_GeoTrustPrimary_G3[] =
+ "\x9c\xa9\x8d\x00\xaf\x74\x0d\xdd\x81\x80"
+ "\xd2\x13\x45\xa5\x8b\x8f\x2e\x94\x38\xd6";
+
+static const char kSPKIHash_Entrust_2048[] =
+ "\x55\xe4\x81\xd1\x11\x80\xbe\xd8\x89\xb9"
+ "\x08\xa3\x31\xf9\xa1\x24\x09\x16\xb9\x70";
+
+static const char kSPKIHash_Entrust_EV[] =
+ "\xba\x42\xb0\x81\x88\x53\x88\x1d\x86\x63"
+ "\xbd\x4c\xc0\x5e\x08\xfe\xea\x6e\xbb\x77";
+
+static const char kSPKIHash_Entrust_G2[] =
+ "\xab\x30\xd3\xaf\x4b\xd8\xf1\x6b\x58\x69"
+ "\xee\x45\x69\x29\xda\x84\xb8\x73\x94\x88";
+
+static const char kSPKIHash_Entrust_SSL[] =
+ "\xf0\x17\x62\x13\x55\x3d\xb3\xff\x0a\x00"
+ "\x6b\xfb\x50\x84\x97\xf3\xed\x62\xd0\x1a";
+
+static const char kSPKIHash_AAACertificateServices[] =
+ "\xc4\x30\x28\xc5\xd3\xe3\x08\x0c\x10\x44"
+ "\x8b\x2c\x77\xba\x24\x53\x97\x60\xbb\xf9";
+
+static const char kSPKIHash_AddTrustClass1CARoot[] =
+ "\x8b\xdb\xd7\xcc\xa0\x68\x53\x42\x16\xf4"
+ "\xc1\x2b\x25\x44\xfc\x02\x9c\xa5\x8b\x47";
+
+static const char kSPKIHash_AddTrustExternalCARoot[] =
+ "\x4f\x9c\x7d\x21\x79\x9c\xad\x0e\xd8\xb9"
+ "\x0c\x57\x9f\x1a\x02\x99\xe7\x90\xf3\x87";
+
+static const char kSPKIHash_AddTrustPublicCARoot[] =
+ "\xa8\x57\x65\xd6\xe8\x32\xc8\xc5\x19\x63"
+ "\x73\x5a\x9a\x17\x74\x3a\x81\xdf\xee\x2e";
+
+static const char kSPKIHash_AddTrustQualifiedCARoot[] =
+ "\xbc\xe4\xb7\x23\x12\x55\x98\xe5\x63\x41"
+ "\x19\x1c\x50\xe4\xb6\x47\xc2\x76\x05\xd7";
+
+static const char kSPKIHash_COMODOCertificationAuthority[] =
+ "\x11\xe4\x91\xd1\xc9\xe4\xc0\xeb\x9a\xce"
+ "\xcf\x73\x54\x5d\xe1\xf1\xa8\x30\x3e\xc3";
+
+static const char kSPKIHash_SecureCertificateServices[] =
+ "\x3c\xb4\x1a\x84\x2e\xf5\x5c\xf2\x1a\x3d"
+ "\xa5\x4a\xc8\xd1\xbe\x39\x08\x76\x37\xbc";
+
+static const char kSPKIHash_TrustedCertificateServices[] =
+ "\xfe\x72\xc8\xeb\xbf\x0c\x2f\xbb\x0e\x26"
+ "\x13\x93\x93\x3c\x2c\xa9\x8d\xdc\x24\x94";
+
+static const char kSPKIHash_UTNDATACorpSGC[] =
+ "\x53\x32\xd1\xb3\xcf\x7f\xfa\xe0\xf1\xa0"
+ "\x5d\x85\x4e\x92\xd2\x9e\x45\x1d\xb4\x4f";
+
+static const char kSPKIHash_UTNUSERFirstClientAuthenticationandEmail[] =
+ "\x89\x82\x67\x7d\xc4\x9d\x26\x70\x00\x4b"
+ "\xb4\x50\x48\x7c\xde\x3d\xae\x04\x6e\x7d";
+
+static const char kSPKIHash_UTNUSERFirstHardware[] =
+ "\xa1\x72\x5f\x26\x1b\x28\x98\x43\x95\x5d"
+ "\x07\x37\xd5\x85\x96\x9d\x4b\xd2\xc3\x45";
+
+static const char kSPKIHash_UTNUSERFirstObject[] =
+ "\xda\xed\x64\x74\x14\x9c\x14\x3c\xab\xdd"
+ "\x99\xa9\xbd\x5b\x28\x4d\x8b\x3c\xc9\xd8";
+
+static const char kSPKIHash_GTECyberTrustGlobalRoot[] =
+ "\x59\x79\x12\xde\x61\x75\xd6\x6f\xc4\x23"
+ "\xb7\x77\x13\x74\xc7\x96\xde\x6f\x88\x72";
+
+static const char kSPKIHash_Tor2web[] =
+ "\x19\xe5\xb5\x87\x1b\xd4\x83\x2e\xc8\xf5"
+ "\x94\x97\xfe\xc6\x5e\xfb\x48\xe3\x33\xb1";
+
+static const char kSPKIHash_AlphaSSL_G2[] =
+ "\xe5\x24\xe9\x8e\x31\x7d\xc8\xfc\xad\x90"
+ "\x53\x7c\x91\xe7\x0d\xa4\x70\x93\x90\x5f";
+
+static const char kSPKIHash_CryptoCat1[] =
+ "\x4c\x87\xce\x85\x2c\xf4\xc0\x4d\x67\xa9"
+ "\xe0\xec\x51\x0c\x7f\x3b\x14\xb3\xe9\xc9";
+
+// The following is static data describing the hosts that are hardcoded with
+// certificate pins or HSTS information.
+
+// kNoRejectedPublicKeys is a placeholder for when no public keys are rejected.
+static const char* const kNoRejectedPublicKeys[] = {
+ NULL,
+};
+
+static const char* const kTestAcceptableCerts[] = {
+ kSPKIHash_TestSPKI,
+ NULL,
+};
+#define kTestPins { \
+ kTestAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+static const char* const kGoogleAcceptableCerts[] = {
+ kSPKIHash_VeriSignClass3,
+ kSPKIHash_VeriSignClass3_G3,
+ kSPKIHash_Google1024,
+ kSPKIHash_Google2048,
+ kSPKIHash_GoogleBackup1024,
+ kSPKIHash_GoogleBackup2048,
+ kSPKIHash_GoogleG2,
+ kSPKIHash_EquifaxSecureCA,
+ kSPKIHash_GeoTrustGlobal,
+ NULL,
+};
+static const char* const kGoogleRejectedCerts[] = {
+ kSPKIHash_Aetna,
+ kSPKIHash_Intel,
+ kSPKIHash_TCTrustCenter,
+ kSPKIHash_Vodafone,
+ NULL,
+};
+#define kGooglePins { \
+ kGoogleAcceptableCerts, \
+ kGoogleRejectedCerts, \
+}
+
+static const char* const kTorAcceptableCerts[] = {
+ kSPKIHash_RapidSSL,
+ kSPKIHash_DigiCertEVRoot,
+ kSPKIHash_Tor1,
+ kSPKIHash_Tor2,
+ kSPKIHash_Tor3,
+ NULL,
+};
+#define kTorPins { \
+ kTorAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+static const char* const kTwitterComAcceptableCerts[] = {
+ kSPKIHash_VeriSignClass1,
+ kSPKIHash_VeriSignClass3,
+ kSPKIHash_VeriSignClass3_G4,
+ kSPKIHash_VeriSignClass4_G3,
+ kSPKIHash_VeriSignClass3_G3,
+ kSPKIHash_VeriSignClass1_G3,
+ kSPKIHash_VeriSignClass2_G3,
+ kSPKIHash_VeriSignClass3_G2,
+ kSPKIHash_VeriSignClass2_G2,
+ kSPKIHash_VeriSignClass3_G5,
+ kSPKIHash_VeriSignUniversal,
+ kSPKIHash_GeoTrustGlobal,
+ kSPKIHash_GeoTrustGlobal2,
+ kSPKIHash_GeoTrustUniversal,
+ kSPKIHash_GeoTrustUniversal2,
+ kSPKIHash_GeoTrustPrimary,
+ kSPKIHash_GeoTrustPrimary_G2,
+ kSPKIHash_GeoTrustPrimary_G3,
+ kSPKIHash_Twitter1,
+ NULL,
+};
+#define kTwitterComPins { \
+ kTwitterComAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+static const char* const kTwitterCDNAcceptableCerts[] = {
+ kSPKIHash_VeriSignClass1,
+ kSPKIHash_VeriSignClass3,
+ kSPKIHash_VeriSignClass3_G4,
+ kSPKIHash_VeriSignClass4_G3,
+ kSPKIHash_VeriSignClass3_G3,
+ kSPKIHash_VeriSignClass1_G3,
+ kSPKIHash_VeriSignClass2_G3,
+ kSPKIHash_VeriSignClass3_G2,
+ kSPKIHash_VeriSignClass2_G2,
+ kSPKIHash_VeriSignClass3_G5,
+ kSPKIHash_VeriSignUniversal,
+ kSPKIHash_GeoTrustGlobal,
+ kSPKIHash_GeoTrustGlobal2,
+ kSPKIHash_GeoTrustUniversal,
+ kSPKIHash_GeoTrustUniversal2,
+ kSPKIHash_GeoTrustPrimary,
+ kSPKIHash_GeoTrustPrimary_G2,
+ kSPKIHash_GeoTrustPrimary_G3,
+ kSPKIHash_Twitter1,
+ kSPKIHash_Entrust_2048,
+ kSPKIHash_Entrust_EV,
+ kSPKIHash_Entrust_G2,
+ kSPKIHash_Entrust_SSL,
+ kSPKIHash_AAACertificateServices,
+ kSPKIHash_AddTrustClass1CARoot,
+ kSPKIHash_AddTrustExternalCARoot,
+ kSPKIHash_AddTrustPublicCARoot,
+ kSPKIHash_AddTrustQualifiedCARoot,
+ kSPKIHash_COMODOCertificationAuthority,
+ kSPKIHash_SecureCertificateServices,
+ kSPKIHash_TrustedCertificateServices,
+ kSPKIHash_UTNDATACorpSGC,
+ kSPKIHash_UTNUSERFirstClientAuthenticationandEmail,
+ kSPKIHash_UTNUSERFirstHardware,
+ kSPKIHash_UTNUSERFirstObject,
+ kSPKIHash_GTECyberTrustGlobalRoot,
+ NULL,
+};
+#define kTwitterCDNPins { \
+ kTwitterCDNAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+static const char* const kTor2webAcceptableCerts[] = {
+ kSPKIHash_AlphaSSL_G2,
+ kSPKIHash_Tor2web,
+ NULL,
+};
+#define kTor2webPins { \
+ kTor2webAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+static const char* const kCryptoCatAcceptableCerts[] = {
+ kSPKIHash_DigiCertEVRoot,
+ kSPKIHash_CryptoCat1,
+ NULL,
+};
+#define kCryptoCatPins { \
+ kCryptoCatAcceptableCerts, \
+ kNoRejectedPublicKeys, \
+}
+
+#define kNoPins {\
+ NULL, NULL, \
+}
+
+static const struct HSTSPreload kPreloadedSTS[] = {
+ {25, true, "\013pinningtest\007appspot\003com", false, kTestPins, DOMAIN_APPSPOT_COM },
+ {12, true, "\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
+ {19, true, "\006health\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {21, true, "\010checkout\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {19, true, "\006chrome\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {17, true, "\004docs\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {18, true, "\005sites\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {25, true, "\014spreadsheets\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {22, false, "\011appengine\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {22, true, "\011encrypted\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {21, true, "\010accounts\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {21, true, "\010profiles\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {17, true, "\004mail\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {23, true, "\012talkgadget\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {17, true, "\004talk\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {29, true, "\020hostedtalkgadget\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {17, true, "\004plus\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {25, true, "\004plus\007sandbox\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {19, true, "\006script\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {20, true, "\007history\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {21, true, "\010security\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {20, true, "\006market\007android\003com", true, kGooglePins, DOMAIN_ANDROID_COM },
+ {26, true, "\003ssl\020google-analytics\003com", true, kGooglePins, DOMAIN_GOOGLE_ANALYTICS_COM },
+ {18, true, "\005drive\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {16, true, "\012googleplex\003com", true, kGooglePins, DOMAIN_GOOGLEPLEX_COM },
+ {19, true, "\006groups\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {17, true, "\004apis\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {32, true, "\022chromiumcodereview\007appspot\003com", true, kGooglePins, DOMAIN_APPSPOT_COM },
+ {38, true, "\030chrome-devtools-frontend\007appspot\003com", true, kGooglePins, DOMAIN_APPSPOT_COM },
+ {24, true, "\012codereview\007appspot\003com", true, kGooglePins, DOMAIN_APPSPOT_COM },
+ {25, true, "\012codereview\010chromium\003org", true, kGooglePins, DOMAIN_CHROMIUM_ORG },
+ {17, true, "\004code\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {16, true, "\012googlecode\003com", false, kGooglePins, DOMAIN_GOOGLECODE_COM },
+ {15, true, "\002dl\006google\003com", true, kGooglePins, DOMAIN_GOOGLE_COM },
+ {23, true, "\005chart\004apis\006google\003com", false, kGooglePins, DOMAIN_GOOGLE_COM },
+ {11, true, "\005ytimg\003com", false, kGooglePins, DOMAIN_YTIMG_COM },
+ {23, true, "\021googleusercontent\003com", false, kGooglePins, DOMAIN_GOOGLEUSERCONTENT_COM },
+ {13, true, "\007youtube\003com", false, kGooglePins, DOMAIN_YOUTUBE_COM },
+ {16, true, "\012googleapis\003com", false, kGooglePins, DOMAIN_GOOGLEAPIS_COM },
+ {22, true, "\020googleadservices\003com", false, kGooglePins, DOMAIN_GOOGLEADSERVICES_COM },
+ {13, true, "\007appspot\003com", false, kGooglePins, DOMAIN_APPSPOT_COM },
+ {23, true, "\021googlesyndication\003com", false, kGooglePins, DOMAIN_GOOGLESYNDICATION_COM },
+ {17, true, "\013doubleclick\003net", false, kGooglePins, DOMAIN_DOUBLECLICK_NET },
+ {17, true, "\003ssl\007gstatic\003com", false, kGooglePins, DOMAIN_GSTATIC_COM },
+ {10, true, "\005youtu\002be", false, kGooglePins, DOMAIN_YOUTU_BE },
+ {13, true, "\007android\003com", false, kGooglePins, DOMAIN_ANDROID_COM },
+ {20, true, "\016googlecommerce\003com", false, kGooglePins, DOMAIN_GOOGLECOMMERCE_COM },
+ {12, true, "\006urchin\003com", false, kGooglePins, DOMAIN_URCHIN_COM },
+ {8, true, "\003goo\002gl", false, kGooglePins, DOMAIN_GOO_GL },
+ {6, true, "\001g\002co", false, kGooglePins, DOMAIN_G_CO },
+ {11, true, "\006google\002ac", false, kGooglePins, DOMAIN_GOOGLE_AC },
+ {11, true, "\006google\002ad", false, kGooglePins, DOMAIN_GOOGLE_AD },
+ {11, true, "\006google\002ae", false, kGooglePins, DOMAIN_GOOGLE_AE },
+ {11, true, "\006google\002af", false, kGooglePins, DOMAIN_GOOGLE_AF },
+ {11, true, "\006google\002ag", false, kGooglePins, DOMAIN_GOOGLE_AG },
+ {11, true, "\006google\002am", false, kGooglePins, DOMAIN_GOOGLE_AM },
+ {11, true, "\006google\002as", false, kGooglePins, DOMAIN_GOOGLE_AS },
+ {11, true, "\006google\002at", false, kGooglePins, DOMAIN_GOOGLE_AT },
+ {11, true, "\006google\002az", false, kGooglePins, DOMAIN_GOOGLE_AZ },
+ {11, true, "\006google\002ba", false, kGooglePins, DOMAIN_GOOGLE_BA },
+ {11, true, "\006google\002be", false, kGooglePins, DOMAIN_GOOGLE_BE },
+ {11, true, "\006google\002bf", false, kGooglePins, DOMAIN_GOOGLE_BF },
+ {11, true, "\006google\002bg", false, kGooglePins, DOMAIN_GOOGLE_BG },
+ {11, true, "\006google\002bi", false, kGooglePins, DOMAIN_GOOGLE_BI },
+ {11, true, "\006google\002bj", false, kGooglePins, DOMAIN_GOOGLE_BJ },
+ {11, true, "\006google\002bs", false, kGooglePins, DOMAIN_GOOGLE_BS },
+ {11, true, "\006google\002by", false, kGooglePins, DOMAIN_GOOGLE_BY },
+ {11, true, "\006google\002ca", false, kGooglePins, DOMAIN_GOOGLE_CA },
+ {12, true, "\006google\003cat", false, kGooglePins, DOMAIN_GOOGLE_CAT },
+ {11, true, "\006google\002cc", false, kGooglePins, DOMAIN_GOOGLE_CC },
+ {11, true, "\006google\002cd", false, kGooglePins, DOMAIN_GOOGLE_CD },
+ {11, true, "\006google\002cf", false, kGooglePins, DOMAIN_GOOGLE_CF },
+ {11, true, "\006google\002cg", false, kGooglePins, DOMAIN_GOOGLE_CG },
+ {11, true, "\006google\002ch", false, kGooglePins, DOMAIN_GOOGLE_CH },
+ {11, true, "\006google\002ci", false, kGooglePins, DOMAIN_GOOGLE_CI },
+ {11, true, "\006google\002cl", false, kGooglePins, DOMAIN_GOOGLE_CL },
+ {11, true, "\006google\002cm", false, kGooglePins, DOMAIN_GOOGLE_CM },
+ {11, true, "\006google\002cn", false, kGooglePins, DOMAIN_GOOGLE_CN },
+ {14, true, "\006google\002co\002ao", false, kGooglePins, DOMAIN_CO_AO },
+ {14, true, "\006google\002co\002bw", false, kGooglePins, DOMAIN_CO_BW },
+ {14, true, "\006google\002co\002ck", false, kGooglePins, DOMAIN_CO_CK },
+ {14, true, "\006google\002co\002cr", false, kGooglePins, DOMAIN_CO_CR },
+ {14, true, "\006google\002co\002hu", false, kGooglePins, DOMAIN_CO_HU },
+ {14, true, "\006google\002co\002id", false, kGooglePins, DOMAIN_CO_ID },
+ {14, true, "\006google\002co\002il", false, kGooglePins, DOMAIN_CO_IL },
+ {14, true, "\006google\002co\002im", false, kGooglePins, DOMAIN_CO_IM },
+ {14, true, "\006google\002co\002in", false, kGooglePins, DOMAIN_CO_IN },
+ {14, true, "\006google\002co\002je", false, kGooglePins, DOMAIN_CO_JE },
+ {14, true, "\006google\002co\002jp", false, kGooglePins, DOMAIN_CO_JP },
+ {14, true, "\006google\002co\002ke", false, kGooglePins, DOMAIN_CO_KE },
+ {14, true, "\006google\002co\002kr", false, kGooglePins, DOMAIN_CO_KR },
+ {14, true, "\006google\002co\002ls", false, kGooglePins, DOMAIN_CO_LS },
+ {14, true, "\006google\002co\002ma", false, kGooglePins, DOMAIN_CO_MA },
+ {14, true, "\006google\002co\002mz", false, kGooglePins, DOMAIN_CO_MZ },
+ {14, true, "\006google\002co\002nz", false, kGooglePins, DOMAIN_CO_NZ },
+ {14, true, "\006google\002co\002th", false, kGooglePins, DOMAIN_CO_TH },
+ {14, true, "\006google\002co\002tz", false, kGooglePins, DOMAIN_CO_TZ },
+ {14, true, "\006google\002co\002ug", false, kGooglePins, DOMAIN_CO_UG },
+ {14, true, "\006google\002co\002uk", false, kGooglePins, DOMAIN_CO_UK },
+ {14, true, "\006google\002co\002uz", false, kGooglePins, DOMAIN_CO_UZ },
+ {14, true, "\006google\002co\002ve", false, kGooglePins, DOMAIN_CO_VE },
+ {14, true, "\006google\002co\002vi", false, kGooglePins, DOMAIN_CO_VI },
+ {14, true, "\006google\002co\002za", false, kGooglePins, DOMAIN_CO_ZA },
+ {14, true, "\006google\002co\002zm", false, kGooglePins, DOMAIN_CO_ZM },
+ {14, true, "\006google\002co\002zw", false, kGooglePins, DOMAIN_CO_ZW },
+ {15, true, "\006google\003com\002af", false, kGooglePins, DOMAIN_COM_AF },
+ {15, true, "\006google\003com\002ag", false, kGooglePins, DOMAIN_COM_AG },
+ {15, true, "\006google\003com\002ai", false, kGooglePins, DOMAIN_COM_AI },
+ {15, true, "\006google\003com\002ar", false, kGooglePins, DOMAIN_COM_AR },
+ {15, true, "\006google\003com\002au", false, kGooglePins, DOMAIN_COM_AU },
+ {15, true, "\006google\003com\002bd", false, kGooglePins, DOMAIN_COM_BD },
+ {15, true, "\006google\003com\002bh", false, kGooglePins, DOMAIN_COM_BH },
+ {15, true, "\006google\003com\002bn", false, kGooglePins, DOMAIN_COM_BN },
+ {15, true, "\006google\003com\002bo", false, kGooglePins, DOMAIN_COM_BO },
+ {15, true, "\006google\003com\002br", false, kGooglePins, DOMAIN_COM_BR },
+ {15, true, "\006google\003com\002by", false, kGooglePins, DOMAIN_COM_BY },
+ {15, true, "\006google\003com\002bz", false, kGooglePins, DOMAIN_COM_BZ },
+ {15, true, "\006google\003com\002cn", false, kGooglePins, DOMAIN_COM_CN },
+ {15, true, "\006google\003com\002co", false, kGooglePins, DOMAIN_COM_CO },
+ {15, true, "\006google\003com\002cu", false, kGooglePins, DOMAIN_COM_CU },
+ {15, true, "\006google\003com\002cy", false, kGooglePins, DOMAIN_COM_CY },
+ {15, true, "\006google\003com\002do", false, kGooglePins, DOMAIN_COM_DO },
+ {15, true, "\006google\003com\002ec", false, kGooglePins, DOMAIN_COM_EC },
+ {15, true, "\006google\003com\002eg", false, kGooglePins, DOMAIN_COM_EG },
+ {15, true, "\006google\003com\002et", false, kGooglePins, DOMAIN_COM_ET },
+ {15, true, "\006google\003com\002fj", false, kGooglePins, DOMAIN_COM_FJ },
+ {15, true, "\006google\003com\002ge", false, kGooglePins, DOMAIN_COM_GE },
+ {15, true, "\006google\003com\002gh", false, kGooglePins, DOMAIN_COM_GH },
+ {15, true, "\006google\003com\002gi", false, kGooglePins, DOMAIN_COM_GI },
+ {15, true, "\006google\003com\002gr", false, kGooglePins, DOMAIN_COM_GR },
+ {15, true, "\006google\003com\002gt", false, kGooglePins, DOMAIN_COM_GT },
+ {15, true, "\006google\003com\002hk", false, kGooglePins, DOMAIN_COM_HK },
+ {15, true, "\006google\003com\002iq", false, kGooglePins, DOMAIN_COM_IQ },
+ {15, true, "\006google\003com\002jm", false, kGooglePins, DOMAIN_COM_JM },
+ {15, true, "\006google\003com\002jo", false, kGooglePins, DOMAIN_COM_JO },
+ {15, true, "\006google\003com\002kh", false, kGooglePins, DOMAIN_COM_KH },
+ {15, true, "\006google\003com\002kw", false, kGooglePins, DOMAIN_COM_KW },
+ {15, true, "\006google\003com\002lb", false, kGooglePins, DOMAIN_COM_LB },
+ {15, true, "\006google\003com\002ly", false, kGooglePins, DOMAIN_COM_LY },
+ {15, true, "\006google\003com\002mt", false, kGooglePins, DOMAIN_COM_MT },
+ {15, true, "\006google\003com\002mx", false, kGooglePins, DOMAIN_COM_MX },
+ {15, true, "\006google\003com\002my", false, kGooglePins, DOMAIN_COM_MY },
+ {15, true, "\006google\003com\002na", false, kGooglePins, DOMAIN_COM_NA },
+ {15, true, "\006google\003com\002nf", false, kGooglePins, DOMAIN_COM_NF },
+ {15, true, "\006google\003com\002ng", false, kGooglePins, DOMAIN_COM_NG },
+ {15, true, "\006google\003com\002ni", false, kGooglePins, DOMAIN_COM_NI },
+ {15, true, "\006google\003com\002np", false, kGooglePins, DOMAIN_COM_NP },
+ {15, true, "\006google\003com\002nr", false, kGooglePins, DOMAIN_COM_NR },
+ {15, true, "\006google\003com\002om", false, kGooglePins, DOMAIN_COM_OM },
+ {15, true, "\006google\003com\002pa", false, kGooglePins, DOMAIN_COM_PA },
+ {15, true, "\006google\003com\002pe", false, kGooglePins, DOMAIN_COM_PE },
+ {15, true, "\006google\003com\002ph", false, kGooglePins, DOMAIN_COM_PH },
+ {15, true, "\006google\003com\002pk", false, kGooglePins, DOMAIN_COM_PK },
+ {15, true, "\006google\003com\002pl", false, kGooglePins, DOMAIN_COM_PL },
+ {15, true, "\006google\003com\002pr", false, kGooglePins, DOMAIN_COM_PR },
+ {15, true, "\006google\003com\002py", false, kGooglePins, DOMAIN_COM_PY },
+ {15, true, "\006google\003com\002qa", false, kGooglePins, DOMAIN_COM_QA },
+ {15, true, "\006google\003com\002ru", false, kGooglePins, DOMAIN_COM_RU },
+ {15, true, "\006google\003com\002sa", false, kGooglePins, DOMAIN_COM_SA },
+ {15, true, "\006google\003com\002sb", false, kGooglePins, DOMAIN_COM_SB },
+ {15, true, "\006google\003com\002sg", false, kGooglePins, DOMAIN_COM_SG },
+ {15, true, "\006google\003com\002sl", false, kGooglePins, DOMAIN_COM_SL },
+ {15, true, "\006google\003com\002sv", false, kGooglePins, DOMAIN_COM_SV },
+ {15, true, "\006google\003com\002tj", false, kGooglePins, DOMAIN_COM_TJ },
+ {15, true, "\006google\003com\002tn", false, kGooglePins, DOMAIN_COM_TN },
+ {15, true, "\006google\003com\002tr", false, kGooglePins, DOMAIN_COM_TR },
+ {15, true, "\006google\003com\002tw", false, kGooglePins, DOMAIN_COM_TW },
+ {15, true, "\006google\003com\002ua", false, kGooglePins, DOMAIN_COM_UA },
+ {15, true, "\006google\003com\002uy", false, kGooglePins, DOMAIN_COM_UY },
+ {15, true, "\006google\003com\002vc", false, kGooglePins, DOMAIN_COM_VC },
+ {15, true, "\006google\003com\002ve", false, kGooglePins, DOMAIN_COM_VE },
+ {15, true, "\006google\003com\002vn", false, kGooglePins, DOMAIN_COM_VN },
+ {11, true, "\006google\002cv", false, kGooglePins, DOMAIN_GOOGLE_CV },
+ {11, true, "\006google\002cz", false, kGooglePins, DOMAIN_GOOGLE_CZ },
+ {11, true, "\006google\002de", false, kGooglePins, DOMAIN_GOOGLE_DE },
+ {11, true, "\006google\002dj", false, kGooglePins, DOMAIN_GOOGLE_DJ },
+ {11, true, "\006google\002dk", false, kGooglePins, DOMAIN_GOOGLE_DK },
+ {11, true, "\006google\002dm", false, kGooglePins, DOMAIN_GOOGLE_DM },
+ {11, true, "\006google\002dz", false, kGooglePins, DOMAIN_GOOGLE_DZ },
+ {11, true, "\006google\002ee", false, kGooglePins, DOMAIN_GOOGLE_EE },
+ {11, true, "\006google\002es", false, kGooglePins, DOMAIN_GOOGLE_ES },
+ {11, true, "\006google\002fi", false, kGooglePins, DOMAIN_GOOGLE_FI },
+ {11, true, "\006google\002fm", false, kGooglePins, DOMAIN_GOOGLE_FM },
+ {11, true, "\006google\002fr", false, kGooglePins, DOMAIN_GOOGLE_FR },
+ {11, true, "\006google\002ga", false, kGooglePins, DOMAIN_GOOGLE_GA },
+ {11, true, "\006google\002ge", false, kGooglePins, DOMAIN_GOOGLE_GE },
+ {11, true, "\006google\002gg", false, kGooglePins, DOMAIN_GOOGLE_GG },
+ {11, true, "\006google\002gl", false, kGooglePins, DOMAIN_GOOGLE_GL },
+ {11, true, "\006google\002gm", false, kGooglePins, DOMAIN_GOOGLE_GM },
+ {11, true, "\006google\002gp", false, kGooglePins, DOMAIN_GOOGLE_GP },
+ {11, true, "\006google\002gr", false, kGooglePins, DOMAIN_GOOGLE_GR },
+ {11, true, "\006google\002gy", false, kGooglePins, DOMAIN_GOOGLE_GY },
+ {11, true, "\006google\002hk", false, kGooglePins, DOMAIN_GOOGLE_HK },
+ {11, true, "\006google\002hn", false, kGooglePins, DOMAIN_GOOGLE_HN },
+ {11, true, "\006google\002hr", false, kGooglePins, DOMAIN_GOOGLE_HR },
+ {11, true, "\006google\002ht", false, kGooglePins, DOMAIN_GOOGLE_HT },
+ {11, true, "\006google\002hu", false, kGooglePins, DOMAIN_GOOGLE_HU },
+ {11, true, "\006google\002ie", false, kGooglePins, DOMAIN_GOOGLE_IE },
+ {11, true, "\006google\002im", false, kGooglePins, DOMAIN_GOOGLE_IM },
+ {13, true, "\006google\004info", false, kGooglePins, DOMAIN_GOOGLE_INFO },
+ {11, true, "\006google\002iq", false, kGooglePins, DOMAIN_GOOGLE_IQ },
+ {11, true, "\006google\002is", false, kGooglePins, DOMAIN_GOOGLE_IS },
+ {11, true, "\006google\002it", false, kGooglePins, DOMAIN_GOOGLE_IT },
+ {14, true, "\006google\002it\002ao", false, kGooglePins, DOMAIN_IT_AO },
+ {11, true, "\006google\002je", false, kGooglePins, DOMAIN_GOOGLE_JE },
+ {11, true, "\006google\002jo", false, kGooglePins, DOMAIN_GOOGLE_JO },
+ {13, true, "\006google\004jobs", false, kGooglePins, DOMAIN_GOOGLE_JOBS },
+ {11, true, "\006google\002jp", false, kGooglePins, DOMAIN_GOOGLE_JP },
+ {11, true, "\006google\002kg", false, kGooglePins, DOMAIN_GOOGLE_KG },
+ {11, true, "\006google\002ki", false, kGooglePins, DOMAIN_GOOGLE_KI },
+ {11, true, "\006google\002kz", false, kGooglePins, DOMAIN_GOOGLE_KZ },
+ {11, true, "\006google\002la", false, kGooglePins, DOMAIN_GOOGLE_LA },
+ {11, true, "\006google\002li", false, kGooglePins, DOMAIN_GOOGLE_LI },
+ {11, true, "\006google\002lk", false, kGooglePins, DOMAIN_GOOGLE_LK },
+ {11, true, "\006google\002lt", false, kGooglePins, DOMAIN_GOOGLE_LT },
+ {11, true, "\006google\002lu", false, kGooglePins, DOMAIN_GOOGLE_LU },
+ {11, true, "\006google\002lv", false, kGooglePins, DOMAIN_GOOGLE_LV },
+ {11, true, "\006google\002md", false, kGooglePins, DOMAIN_GOOGLE_MD },
+ {11, true, "\006google\002me", false, kGooglePins, DOMAIN_GOOGLE_ME },
+ {11, true, "\006google\002mg", false, kGooglePins, DOMAIN_GOOGLE_MG },
+ {11, true, "\006google\002mk", false, kGooglePins, DOMAIN_GOOGLE_MK },
+ {11, true, "\006google\002ml", false, kGooglePins, DOMAIN_GOOGLE_ML },
+ {11, true, "\006google\002mn", false, kGooglePins, DOMAIN_GOOGLE_MN },
+ {11, true, "\006google\002ms", false, kGooglePins, DOMAIN_GOOGLE_MS },
+ {11, true, "\006google\002mu", false, kGooglePins, DOMAIN_GOOGLE_MU },
+ {11, true, "\006google\002mv", false, kGooglePins, DOMAIN_GOOGLE_MV },
+ {11, true, "\006google\002mw", false, kGooglePins, DOMAIN_GOOGLE_MW },
+ {11, true, "\006google\002ne", false, kGooglePins, DOMAIN_GOOGLE_NE },
+ {14, true, "\006google\002ne\002jp", false, kGooglePins, DOMAIN_NE_JP },
+ {12, true, "\006google\003net", false, kGooglePins, DOMAIN_GOOGLE_NET },
+ {11, true, "\006google\002nl", false, kGooglePins, DOMAIN_GOOGLE_NL },
+ {11, true, "\006google\002no", false, kGooglePins, DOMAIN_GOOGLE_NO },
+ {11, true, "\006google\002nr", false, kGooglePins, DOMAIN_GOOGLE_NR },
+ {11, true, "\006google\002nu", false, kGooglePins, DOMAIN_GOOGLE_NU },
+ {15, true, "\006google\003off\002ai", false, kGooglePins, DOMAIN_OFF_AI },
+ {11, true, "\006google\002pk", false, kGooglePins, DOMAIN_GOOGLE_PK },
+ {11, true, "\006google\002pl", false, kGooglePins, DOMAIN_GOOGLE_PL },
+ {11, true, "\006google\002pn", false, kGooglePins, DOMAIN_GOOGLE_PN },
+ {11, true, "\006google\002ps", false, kGooglePins, DOMAIN_GOOGLE_PS },
+ {11, true, "\006google\002pt", false, kGooglePins, DOMAIN_GOOGLE_PT },
+ {11, true, "\006google\002ro", false, kGooglePins, DOMAIN_GOOGLE_RO },
+ {11, true, "\006google\002rs", false, kGooglePins, DOMAIN_GOOGLE_RS },
+ {11, true, "\006google\002ru", false, kGooglePins, DOMAIN_GOOGLE_RU },
+ {11, true, "\006google\002rw", false, kGooglePins, DOMAIN_GOOGLE_RW },
+ {11, true, "\006google\002sc", false, kGooglePins, DOMAIN_GOOGLE_SC },
+ {11, true, "\006google\002se", false, kGooglePins, DOMAIN_GOOGLE_SE },
+ {11, true, "\006google\002sh", false, kGooglePins, DOMAIN_GOOGLE_SH },
+ {11, true, "\006google\002si", false, kGooglePins, DOMAIN_GOOGLE_SI },
+ {11, true, "\006google\002sk", false, kGooglePins, DOMAIN_GOOGLE_SK },
+ {11, true, "\006google\002sm", false, kGooglePins, DOMAIN_GOOGLE_SM },
+ {11, true, "\006google\002sn", false, kGooglePins, DOMAIN_GOOGLE_SN },
+ {11, true, "\006google\002so", false, kGooglePins, DOMAIN_GOOGLE_SO },
+ {11, true, "\006google\002st", false, kGooglePins, DOMAIN_GOOGLE_ST },
+ {11, true, "\006google\002td", false, kGooglePins, DOMAIN_GOOGLE_TD },
+ {11, true, "\006google\002tg", false, kGooglePins, DOMAIN_GOOGLE_TG },
+ {11, true, "\006google\002tk", false, kGooglePins, DOMAIN_GOOGLE_TK },
+ {11, true, "\006google\002tl", false, kGooglePins, DOMAIN_GOOGLE_TL },
+ {11, true, "\006google\002tm", false, kGooglePins, DOMAIN_GOOGLE_TM },
+ {11, true, "\006google\002tn", false, kGooglePins, DOMAIN_GOOGLE_TN },
+ {11, true, "\006google\002to", false, kGooglePins, DOMAIN_GOOGLE_TO },
+ {11, true, "\006google\002tp", false, kGooglePins, DOMAIN_GOOGLE_TP },
+ {11, true, "\006google\002tt", false, kGooglePins, DOMAIN_GOOGLE_TT },
+ {11, true, "\006google\002us", false, kGooglePins, DOMAIN_GOOGLE_US },
+ {11, true, "\006google\002uz", false, kGooglePins, DOMAIN_GOOGLE_UZ },
+ {11, true, "\006google\002vg", false, kGooglePins, DOMAIN_GOOGLE_VG },
+ {11, true, "\006google\002vu", false, kGooglePins, DOMAIN_GOOGLE_VU },
+ {11, true, "\006google\002ws", false, kGooglePins, DOMAIN_GOOGLE_WS },
+ {23, true, "\005learn\013doubleclick\003net", false, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\003www\006paypal\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, false, "\006paypal\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\003www\006elanex\003biz", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006jottit\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\015sunshinepress\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, false, "\003www\013noisebridge\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, false, "\004neg9\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006riseup\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, false, "\006factor\002cc", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\007members\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\007support\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\002id\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, true, "\005lists\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\007webmail\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {24, true, "\011roundcube\010mayfirst\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {28, false, "\016aladdinschools\007appspot\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\011ottospora\002nl", true, kNoPins, DOMAIN_NOT_PINNED },
+ {25, false, "\003www\017paycheckrecords\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\010lastpass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\003www\010lastpass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\010keyerror\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\010entropia\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003www\010entropia\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\005romab\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\012logentries\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\003www\012logentries\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006stripe\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {27, true, "\025cloudsecurityalliance\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\005login\004sapo\002pt", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\015mattmccutchen\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\006betnet\002fr", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\010uprotect\002it", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\010squareup\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006square\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {9, true, "\004cert\002se", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\006crypto\002is", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, true, "\005simon\007butcher\004name", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\004linx\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007dropcam\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003www\007dropcam\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {30, true, "\010ebanking\014indovinabank\003com\002vn", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007epoxate\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\012torproject\003org", true, kTorPins, DOMAIN_TORPROJECT_ORG },
+ {21, true, "\004blog\012torproject\003org", true, kTorPins, DOMAIN_TORPROJECT_ORG },
+ {22, true, "\005check\012torproject\003org", true, kTorPins, DOMAIN_TORPROJECT_ORG },
+ {20, true, "\003www\012torproject\003org", true, kTorPins, DOMAIN_TORPROJECT_ORG },
+ {21, true, "\004dist\012torproject\003org", true, kTorPins, DOMAIN_TORPROJECT_ORG },
+ {22, true, "\003www\014moneybookers\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\013ledgerscope\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, false, "\003www\013ledgerscope\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\003app\007recurly\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\003api\007recurly\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007greplin\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003www\007greplin\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {27, true, "\006luneta\016nearbuysystems\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006ubertt\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {9, true, "\004pixi\002me", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\010grepular\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\003www\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {26, false, "\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {30, false, "\003www\011developer\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {24, false, "\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {28, false, "\003www\007sandbox\012mydigipass\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, false, "\006crypto\003cat", true, kCryptoCatPins, DOMAIN_CRYPTO_CAT },
+ {25, true, "\014bigshinylock\006minazo\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\005crate\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007twitter\003com", true, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003www\007twitter\003com", true, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003api\007twitter\003com", false, kTwitterCDNPins, DOMAIN_TWITTER_COM },
+ {19, true, "\005oauth\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {20, true, "\006mobile\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {17, true, "\003dev\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {22, true, "\010business\007twitter\003com", false, kTwitterComPins, DOMAIN_TWITTER_COM },
+ {22, true, "\010platform\007twitter\003com", false, kTwitterCDNPins, DOMAIN_TWITTER_COM },
+ {15, true, "\003si0\005twimg\003com", false, kTwitterCDNPins, DOMAIN_TWIMG_COM },
+ {23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNPins, DOMAIN_AKAMAIHD_NET },
+ {22, true, "\020braintreegateway\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {23, false, "\021braintreepayments\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {27, false, "\003www\021braintreepayments\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {24, false, "\022emailprivacytester\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\007tor2web\003org", false, kTor2webPins, DOMAIN_TOR2WEB_ORG },
+ {25, true, "\010business\007medbank\003com\002mt", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\005arivo\003com\002br", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, true, "\003www\013apollo-auto\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\003www\005cueup\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, false, "\005jitsi\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, false, "\003www\005jitsi\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\010download\005jitsi\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {8, true, "\003sol\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\010irccloud\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\003www\010irccloud\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\005alpha\010irccloud\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\006passwd\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\011browserid\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\005login\007persona\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007neonisi\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\003www\007neonisi\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\005shops\007neonisi\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\014piratenlogin\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\011howrandom\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\010intercom\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003api\010intercom\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003www\010intercom\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\010fatzebra\003com\002au", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, true, "\007csawctf\004poly\003edu", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\014makeyourlaws\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, false, "\003www\014makeyourlaws\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, true, "\003iop\006intuit\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\010surfeasy\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\003www\010surfeasy\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, false, "\011packagist\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007lookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\003www\007lookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, false, "\011mylookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, false, "\003www\011mylookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\002dm\007lookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\002dm\011mylookout\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\011itriskltd\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\012stocktrade\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\011openshift\006redhat\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\014therapynotes\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, false, "\003www\014therapynotes\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {9, true, "\003wiz\003biz", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\002my\006onlime\002ch", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, true, "\007webmail\006onlime\002ch", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\003crm\006onlime\002ch", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\003www\003gov\002uk", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, true, "\014silentcircle\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, true, "\014silentcircle\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, true, "\015serverdensity\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\002my\010alfresco\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, true, "\007webmail\010gigahost\002dk", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\007paymill\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\007paymill\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, true, "\012gocardless\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\005espra\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\005zoo24\002de", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, false, "\004mega\002co\002nz", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, true, "\003api\004mega\002co\002nz", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\007lockify\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\010writeapp\002me", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\010bugzilla\007mozilla\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {30, true, "\007members\020nearlyfreespeech\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {19, false, "\003ssl\011panoramio\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\007kiwiirc\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\003pay\010gigahost\002dk", true, kNoPins, DOMAIN_NOT_PINNED },
+ {27, true, "\015controlcenter\010gigahost\002dk", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, false, "\006simple\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\003www\006simple\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, false, "\002fj\006simple\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\003api\006simple\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, true, "\004bank\006simple\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\005bassh\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\004sah3\003net", true, kNoPins, DOMAIN_NOT_PINNED },
+ {9, false, "\003grc\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, false, "\003www\003grc\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, false, "\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, false, "\003www\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\007manager\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {17, false, "\004blog\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {20, false, "\007library\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\005forum\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\001p\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, false, "\005paste\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, false, "\010pastebin\006linode\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, true, "\017inertianetworks\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, false, "\010carezone\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\011conformal\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, true, "\012cyphertite\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\010logotype\002se", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\004bccx\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {15, true, "\011launchkey\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {16, true, "\010carlolly\002co\002uk", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, true, "\003www\013cyveillance\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {22, true, "\004blog\013cyveillance\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\006whonix\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\010blueseed\002co", true, kNoPins, DOMAIN_NOT_PINNED },
+ {26, true, "\005forum\016quantifiedself\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {11, true, "\006shodan\002io", true, kNoPins, DOMAIN_NOT_PINNED },
+ {18, true, "\015rapidresearch\002me", true, kNoPins, DOMAIN_NOT_PINNED },
+ {14, true, "\010surkatty\003org", true, kNoPins, DOMAIN_NOT_PINNED },
+ {21, true, "\017securityheaders\003com", true, kNoPins, DOMAIN_NOT_PINNED },
+ {10, true, "\005haste\002ch", true, kNoPins, DOMAIN_NOT_PINNED },
+ {12, true, "\007mudcrab\002us", true, kNoPins, DOMAIN_NOT_PINNED },
+ {13, true, "\010mediacru\002sh", true, kNoPins, DOMAIN_NOT_PINNED },
+};
+static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS);
+
+static const struct HSTSPreload kPreloadedSNISTS[] = {
+ {11, false, "\005gmail\003com", true, kGooglePins, DOMAIN_GMAIL_COM },
+ {16, false, "\012googlemail\003com", true, kGooglePins, DOMAIN_GOOGLEMAIL_COM },
+ {15, false, "\003www\005gmail\003com", true, kGooglePins, DOMAIN_GMAIL_COM },
+ {20, false, "\003www\012googlemail\003com", true, kGooglePins, DOMAIN_GOOGLEMAIL_COM },
+ {22, true, "\020google-analytics\003com", false, kGooglePins, DOMAIN_GOOGLE_ANALYTICS_COM },
+ {18, true, "\014googlegroups\003com", false, kGooglePins, DOMAIN_GOOGLEGROUPS_COM },
+};
+static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS);
+
+#endif // NET_HTTP_TRANSPORT_SECURITY_STATE_STATIC_H_
diff --git a/chromium/net/http/transport_security_state_static.json b/chromium/net/http/transport_security_state_static.json
new file mode 100644
index 00000000000..1106187da57
--- /dev/null
+++ b/chromium/net/http/transport_security_state_static.json
@@ -0,0 +1,636 @@
+// Copyright (c) 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.
+
+// This file contains the HSTS preloaded list in a machine readable format.
+
+// The top-level element is a dictionary with two keys: "pinsets" maps details
+// of certificate pinning to a name and "entries" contains the HSTS details for
+// each host.
+//
+// "pinsets" is a list of objects. Each object has the following members:
+// name: (string) the name of the pinset
+// static_spki_hashes: (list of strings) the set of allowed SPKIs hashes
+// bad_static_spki_hashes: (optional list of strings) the set of forbidden SPKIs hashes
+//
+// For a given pinset, a certifiacte is accepted if at least one of the
+// "static_spki_hashes" SPKIs is found in the chain and none of the "bad_static_spki_hashes" SPKIs are.
+// SPKIs are specified as names, which must match up with the file of
+// certificates.
+//
+// "entries" is a list of objects. Each object has the following members:
+// name: (string) the DNS name of the host in question
+// include_subdomains: (optional bool) whether subdomains of |name| are also covered
+// mode: (optional string) "force-https" iff covered names should require HTTPS
+// pins: (optional string) the |name| member of an object in |pinsets|
+// snionly: (optional bool) if true then this entry is only enforced if TLS is
+// enabled because the site in question only serves the correct
+// certificate if SNI is sent. Note that this only covers the case where
+// TLS has been disabled by explicit configuration. If TLS was disabled
+// because of SSLv3 fallback, then the entry is still in force and a
+// fatal certificate error will result. Spurious certificate errors are
+// an unfortunate result of SSLv3 fallback.
+
+{
+ "pinsets": [
+ {
+ "name": "test",
+ "static_spki_hashes": [
+ "TestSPKI"
+ ]
+ },
+ {
+ "name": "google",
+ "static_spki_hashes": [
+ "VeriSignClass3",
+ "VeriSignClass3_G3",
+ "Google1024",
+ "Google2048",
+ "GoogleBackup1024",
+ "GoogleBackup2048",
+ "GoogleG2",
+ "EquifaxSecureCA",
+ "GeoTrustGlobal"
+ ],
+ "bad_static_spki_hashes": [
+ "Aetna",
+ "Intel",
+ "TCTrustCenter",
+ "Vodafone"
+ ]
+ },
+ {
+ "name": "tor",
+ "static_spki_hashes": [
+ "RapidSSL",
+ "DigiCertEVRoot",
+ "Tor1",
+ "Tor2",
+ "Tor3"
+ ]
+ },
+ {
+ "name": "twitterCom",
+ "static_spki_hashes": [
+ "VeriSignClass1",
+ "VeriSignClass3",
+ "VeriSignClass3_G4",
+ "VeriSignClass4_G3",
+ "VeriSignClass3_G3",
+ "VeriSignClass1_G3",
+ "VeriSignClass2_G3",
+ "VeriSignClass3_G2",
+ "VeriSignClass2_G2",
+ "VeriSignClass3_G5",
+ "VeriSignUniversal",
+ "GeoTrustGlobal",
+ "GeoTrustGlobal2",
+ "GeoTrustUniversal",
+ "GeoTrustUniversal2",
+ "GeoTrustPrimary",
+ "GeoTrustPrimary_G2",
+ "GeoTrustPrimary_G3",
+ "Twitter1"
+ ]
+ },
+ {
+ "name": "twitterCDN",
+ "static_spki_hashes": [
+ "VeriSignClass1",
+ "VeriSignClass3",
+ "VeriSignClass3_G4",
+ "VeriSignClass4_G3",
+ "VeriSignClass3_G3",
+ "VeriSignClass1_G3",
+ "VeriSignClass2_G3",
+ "VeriSignClass3_G2",
+ "VeriSignClass2_G2",
+ "VeriSignClass3_G5",
+ "VeriSignUniversal",
+ "GeoTrustGlobal",
+ "GeoTrustGlobal2",
+ "GeoTrustUniversal",
+ "GeoTrustUniversal2",
+ "GeoTrustPrimary",
+ "GeoTrustPrimary_G2",
+ "GeoTrustPrimary_G3",
+ "Twitter1",
+
+ "Entrust_2048",
+ "Entrust_EV",
+ "Entrust_G2",
+ "Entrust_SSL",
+ "AAACertificateServices",
+ "AddTrustClass1CARoot",
+ "AddTrustExternalCARoot",
+ "AddTrustPublicCARoot",
+ "AddTrustQualifiedCARoot",
+ "COMODOCertificationAuthority",
+ "SecureCertificateServices",
+ "TrustedCertificateServices",
+ "UTNDATACorpSGC",
+ "UTNUSERFirstClientAuthenticationandEmail",
+ "UTNUSERFirstHardware",
+ "UTNUSERFirstObject",
+ "GTECyberTrustGlobalRoot"
+ ]
+ },
+ {
+ "name": "tor2web",
+ "static_spki_hashes": [
+ "AlphaSSL_G2",
+ "Tor2web"
+ ]
+ },
+ {
+ "name": "cryptoCat",
+ "static_spki_hashes": [
+ "DigiCertEVRoot",
+ "CryptoCat1"
+ ]
+ }
+ ],
+
+ "entries": [
+ // Dummy entry to test certificate pinning.
+ { "name": "pinningtest.appspot.com", "include_subdomains": true, "pins": "test" },
+
+ // (*.)google.com, iff using SSL, must use an acceptable certificate.
+ { "name": "google.com", "include_subdomains": true, "pins": "google" },
+
+ // Now we force HTTPS for subtrees of google.com.
+ { "name": "health.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "checkout.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "chrome.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "docs.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "sites.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "spreadsheets.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "appengine.google.com", "mode": "force-https", "pins": "google" },
+ { "name": "encrypted.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "accounts.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "profiles.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "mail.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "talkgadget.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "talk.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "hostedtalkgadget.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "plus.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "plus.sandbox.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "script.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "history.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "security.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+
+ // Other Google-related domains that must use HTTPS.
+ { "name": "market.android.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "ssl.google-analytics.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "drive.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "googleplex.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "groups.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "apis.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "chromiumcodereview.appspot.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "chrome-devtools-frontend.appspot.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "codereview.appspot.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "codereview.chromium.org", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "code.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+ { "name": "googlecode.com", "include_subdomains": true, "pins": "google" },
+ { "name": "dl.google.com", "include_subdomains": true, "mode": "force-https", "pins": "google" },
+
+ // chart.apis.google.com is *not* HSTS because the certificate doesn't match
+ // and there are lots of links out there that still use the name. The correct
+ // hostname for this is chart.googleapis.com.
+ { "name": "chart.apis.google.com", "include_subdomains": true, "pins": "google" },
+
+ // Other Google-related domains that must use an acceptable certificate
+ // iff using SSL.
+ { "name": "ytimg.com", "include_subdomains": true, "pins": "google" },
+ { "name": "googleusercontent.com", "include_subdomains": true, "pins": "google" },
+ { "name": "youtube.com", "include_subdomains": true, "pins": "google" },
+ { "name": "googleapis.com", "include_subdomains": true, "pins": "google" },
+ { "name": "googleadservices.com", "include_subdomains": true, "pins": "google" },
+ { "name": "appspot.com", "include_subdomains": true, "pins": "google" },
+ { "name": "googlesyndication.com", "include_subdomains": true, "pins": "google" },
+ { "name": "doubleclick.net", "include_subdomains": true, "pins": "google" },
+ { "name": "ssl.gstatic.com", "include_subdomains": true, "pins": "google" },
+ { "name": "youtu.be", "include_subdomains": true, "pins": "google" },
+ { "name": "android.com", "include_subdomains": true, "pins": "google" },
+ { "name": "googlecommerce.com", "include_subdomains": true, "pins": "google" },
+ { "name": "urchin.com", "include_subdomains": true, "pins": "google" },
+ { "name": "goo.gl", "include_subdomains": true, "pins": "google" },
+ { "name": "g.co", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ac", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ad", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ae", "include_subdomains": true, "pins": "google" },
+ { "name": "google.af", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ag", "include_subdomains": true, "pins": "google" },
+ { "name": "google.am", "include_subdomains": true, "pins": "google" },
+ { "name": "google.as", "include_subdomains": true, "pins": "google" },
+ { "name": "google.at", "include_subdomains": true, "pins": "google" },
+ { "name": "google.az", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ba", "include_subdomains": true, "pins": "google" },
+ { "name": "google.be", "include_subdomains": true, "pins": "google" },
+ { "name": "google.bf", "include_subdomains": true, "pins": "google" },
+ { "name": "google.bg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.bi", "include_subdomains": true, "pins": "google" },
+ { "name": "google.bj", "include_subdomains": true, "pins": "google" },
+ { "name": "google.bs", "include_subdomains": true, "pins": "google" },
+ { "name": "google.by", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ca", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cat", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cc", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cd", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cf", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ch", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ci", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ao", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.bw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ck", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.cr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.hu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.id", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.il", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.im", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.in", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.je", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.jp", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ke", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.kr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ls", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ma", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.mz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.nz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.th", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.tz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ug", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.uk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.uz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.ve", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.vi", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.za", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.zm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.co.zw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.af", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ag", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ai", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ar", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.au", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.bd", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.bh", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.bn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.bo", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.br", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.by", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.bz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.cn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.co", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.cu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.cy", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.do", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ec", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.eg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.et", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.fj", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ge", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.gh", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.gi", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.gr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.gt", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.hk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.iq", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.jm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.jo", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.kh", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.kw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.lb", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ly", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.mt", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.mx", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.my", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.na", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.nf", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ng", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ni", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.np", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.nr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.om", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.pa", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.pe", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ph", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.pk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.pl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.pr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.py", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.qa", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ru", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.sa", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.sb", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.sg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.sl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.sv", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.tj", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.tn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.tr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.tw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ua", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.uy", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.vc", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.ve", "include_subdomains": true, "pins": "google" },
+ { "name": "google.com.vn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cv", "include_subdomains": true, "pins": "google" },
+ { "name": "google.cz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.de", "include_subdomains": true, "pins": "google" },
+ { "name": "google.dj", "include_subdomains": true, "pins": "google" },
+ { "name": "google.dk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.dm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.dz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ee", "include_subdomains": true, "pins": "google" },
+ { "name": "google.es", "include_subdomains": true, "pins": "google" },
+ { "name": "google.fi", "include_subdomains": true, "pins": "google" },
+ { "name": "google.fm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.fr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ga", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ge", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gp", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.gy", "include_subdomains": true, "pins": "google" },
+ { "name": "google.hk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.hn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.hr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ht", "include_subdomains": true, "pins": "google" },
+ { "name": "google.hu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ie", "include_subdomains": true, "pins": "google" },
+ { "name": "google.im", "include_subdomains": true, "pins": "google" },
+ { "name": "google.info", "include_subdomains": true, "pins": "google" },
+ { "name": "google.iq", "include_subdomains": true, "pins": "google" },
+ { "name": "google.is", "include_subdomains": true, "pins": "google" },
+ { "name": "google.it", "include_subdomains": true, "pins": "google" },
+ { "name": "google.it.ao", "include_subdomains": true, "pins": "google" },
+ { "name": "google.je", "include_subdomains": true, "pins": "google" },
+ { "name": "google.jo", "include_subdomains": true, "pins": "google" },
+ { "name": "google.jobs", "include_subdomains": true, "pins": "google" },
+ { "name": "google.jp", "include_subdomains": true, "pins": "google" },
+ { "name": "google.kg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ki", "include_subdomains": true, "pins": "google" },
+ { "name": "google.kz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.la", "include_subdomains": true, "pins": "google" },
+ { "name": "google.li", "include_subdomains": true, "pins": "google" },
+ { "name": "google.lk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.lt", "include_subdomains": true, "pins": "google" },
+ { "name": "google.lu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.lv", "include_subdomains": true, "pins": "google" },
+ { "name": "google.md", "include_subdomains": true, "pins": "google" },
+ { "name": "google.me", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ml", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ms", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mv", "include_subdomains": true, "pins": "google" },
+ { "name": "google.mw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ne", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ne.jp", "include_subdomains": true, "pins": "google" },
+ { "name": "google.net", "include_subdomains": true, "pins": "google" },
+ { "name": "google.nl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.no", "include_subdomains": true, "pins": "google" },
+ { "name": "google.nr", "include_subdomains": true, "pins": "google" },
+ { "name": "google.nu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.off.ai", "include_subdomains": true, "pins": "google" },
+ { "name": "google.pk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.pl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.pn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ps", "include_subdomains": true, "pins": "google" },
+ { "name": "google.pt", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ro", "include_subdomains": true, "pins": "google" },
+ { "name": "google.rs", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ru", "include_subdomains": true, "pins": "google" },
+ { "name": "google.rw", "include_subdomains": true, "pins": "google" },
+ { "name": "google.sc", "include_subdomains": true, "pins": "google" },
+ { "name": "google.se", "include_subdomains": true, "pins": "google" },
+ { "name": "google.sh", "include_subdomains": true, "pins": "google" },
+ { "name": "google.si", "include_subdomains": true, "pins": "google" },
+ { "name": "google.sk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.sm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.sn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.so", "include_subdomains": true, "pins": "google" },
+ { "name": "google.st", "include_subdomains": true, "pins": "google" },
+ { "name": "google.td", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tk", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tl", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tm", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tn", "include_subdomains": true, "pins": "google" },
+ { "name": "google.to", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tp", "include_subdomains": true, "pins": "google" },
+ { "name": "google.tt", "include_subdomains": true, "pins": "google" },
+ { "name": "google.us", "include_subdomains": true, "pins": "google" },
+ { "name": "google.uz", "include_subdomains": true, "pins": "google" },
+ { "name": "google.vg", "include_subdomains": true, "pins": "google" },
+ { "name": "google.vu", "include_subdomains": true, "pins": "google" },
+ { "name": "google.ws", "include_subdomains": true, "pins": "google" },
+ // Exclude the learn.doubleclick.net subdomain because it uses a different
+ // CA.
+ { "name": "learn.doubleclick.net", "include_subdomains": true },
+
+ // Force HTTPS for sites that have requested it.
+ { "name": "www.paypal.com", "mode": "force-https" },
+ { "name": "paypal.com", "mode": "force-https" },
+ { "name": "www.elanex.biz", "mode": "force-https" },
+ { "name": "jottit.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "sunshinepress.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.noisebridge.net", "mode": "force-https" },
+ { "name": "neg9.org", "mode": "force-https" },
+ { "name": "riseup.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "factor.cc", "mode": "force-https" },
+ { "name": "members.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "support.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "id.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "lists.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "webmail.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "roundcube.mayfirst.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "aladdinschools.appspot.com", "mode": "force-https" },
+ { "name": "ottospora.nl", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.paycheckrecords.com", "mode": "force-https" },
+ { "name": "lastpass.com", "mode": "force-https" },
+ { "name": "www.lastpass.com", "mode": "force-https" },
+ { "name": "keyerror.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "entropia.de", "mode": "force-https" },
+ { "name": "www.entropia.de", "mode": "force-https" },
+ { "name": "romab.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "logentries.com", "mode": "force-https" },
+ { "name": "www.logentries.com", "mode": "force-https" },
+ { "name": "stripe.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "cloudsecurityalliance.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "login.sapo.pt", "include_subdomains": true, "mode": "force-https" },
+ { "name": "mattmccutchen.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "betnet.fr", "include_subdomains": true, "mode": "force-https" },
+ { "name": "uprotect.it", "include_subdomains": true, "mode": "force-https" },
+ { "name": "squareup.com", "mode": "force-https" },
+ { "name": "square.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "cert.se", "include_subdomains": true, "mode": "force-https" },
+ { "name": "crypto.is", "include_subdomains": true, "mode": "force-https" },
+ { "name": "simon.butcher.name", "include_subdomains": true, "mode": "force-https" },
+ { "name": "linx.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "dropcam.com", "mode": "force-https" },
+ { "name": "www.dropcam.com", "mode": "force-https" },
+ { "name": "ebanking.indovinabank.com.vn", "include_subdomains": true, "mode": "force-https" },
+ { "name": "epoxate.com", "mode": "force-https" },
+ { "name": "torproject.org", "mode": "force-https", "pins": "tor" },
+ { "name": "blog.torproject.org", "include_subdomains": true, "mode": "force-https", "pins": "tor" },
+ { "name": "check.torproject.org", "include_subdomains": true, "mode": "force-https", "pins": "tor" },
+ { "name": "www.torproject.org", "include_subdomains": true, "mode": "force-https", "pins": "tor" },
+ { "name": "dist.torproject.org", "include_subdomains": true, "mode": "force-https", "pins": "tor" },
+ { "name": "www.moneybookers.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "ledgerscope.net", "mode": "force-https" },
+ { "name": "www.ledgerscope.net", "mode": "force-https" },
+ { "name": "app.recurly.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "api.recurly.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "greplin.com", "mode": "force-https" },
+ { "name": "www.greplin.com", "mode": "force-https" },
+ { "name": "luneta.nearbuysystems.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "ubertt.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "pixi.me", "include_subdomains": true, "mode": "force-https" },
+ { "name": "grepular.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "mydigipass.com", "mode": "force-https" },
+ { "name": "www.mydigipass.com", "mode": "force-https" },
+ { "name": "developer.mydigipass.com", "mode": "force-https" },
+ { "name": "www.developer.mydigipass.com", "mode": "force-https" },
+ { "name": "sandbox.mydigipass.com", "mode": "force-https" },
+ { "name": "www.sandbox.mydigipass.com", "mode": "force-https" },
+ { "name": "crypto.cat", "mode": "force-https", "pins": "cryptoCat" },
+ { "name": "bigshinylock.minazo.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "crate.io", "include_subdomains": true, "mode": "force-https" },
+ { "name": "twitter.com", "mode": "force-https", "pins": "twitterCom" },
+ { "name": "www.twitter.com", "include_subdomains": true, "mode": "force-https", "pins": "twitterCom" },
+ { "name": "api.twitter.com", "include_subdomains": true, "pins": "twitterCDN" },
+ { "name": "oauth.twitter.com", "include_subdomains": true, "pins": "twitterCom" },
+ { "name": "mobile.twitter.com", "include_subdomains": true, "pins": "twitterCom" },
+ { "name": "dev.twitter.com", "include_subdomains": true, "pins": "twitterCom" },
+ { "name": "business.twitter.com", "include_subdomains": true, "pins": "twitterCom" },
+ { "name": "platform.twitter.com", "include_subdomains": true, "pins": "twitterCDN" },
+ { "name": "si0.twimg.com", "include_subdomains": true, "pins": "twitterCDN" },
+ { "name": "twimg0-a.akamaihd.net", "include_subdomains": true, "pins": "twitterCDN" },
+ { "name": "braintreegateway.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "braintreepayments.com", "mode": "force-https" },
+ { "name": "www.braintreepayments.com", "mode": "force-https" },
+ { "name": "emailprivacytester.com", "mode": "force-https" },
+ { "name": "tor2web.org", "include_subdomains": true, "pins": "tor2web" },
+ { "name": "business.medbank.com.mt", "include_subdomains": true, "mode": "force-https" },
+ { "name": "arivo.com.br", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.apollo-auto.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.cueup.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "jitsi.org", "mode": "force-https" },
+ { "name": "www.jitsi.org", "mode": "force-https" },
+ { "name": "download.jitsi.org", "mode": "force-https" },
+ { "name": "sol.io", "include_subdomains": true, "mode": "force-https" },
+ { "name": "irccloud.com", "mode": "force-https" },
+ { "name": "www.irccloud.com", "mode": "force-https" },
+ { "name": "alpha.irccloud.com", "mode": "force-https" },
+ { "name": "passwd.io", "include_subdomains": true, "mode": "force-https" },
+ { "name": "browserid.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "login.persona.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "neonisi.com", "mode": "force-https" },
+ { "name": "www.neonisi.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "shops.neonisi.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "piratenlogin.de", "include_subdomains": true, "mode": "force-https" },
+ { "name": "howrandom.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "intercom.io", "mode": "force-https" },
+ { "name": "api.intercom.io", "mode": "force-https" },
+ { "name": "www.intercom.io", "mode": "force-https" },
+ { "name": "fatzebra.com.au", "include_subdomains": true, "mode": "force-https" },
+ { "name": "csawctf.poly.edu", "include_subdomains": true, "mode": "force-https" },
+ { "name": "makeyourlaws.org", "mode": "force-https" },
+ { "name": "www.makeyourlaws.org", "mode": "force-https" },
+ { "name": "iop.intuit.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "surfeasy.com", "mode": "force-https" },
+ { "name": "www.surfeasy.com", "mode": "force-https" },
+ { "name": "packagist.org", "mode": "force-https" },
+ { "name": "lookout.com", "mode": "force-https" },
+ { "name": "www.lookout.com", "mode": "force-https" },
+ { "name": "mylookout.com", "mode": "force-https" },
+ { "name": "www.mylookout.com", "mode": "force-https" },
+ { "name": "dm.lookout.com", "mode": "force-https" },
+ { "name": "dm.mylookout.com", "mode": "force-https" },
+ { "name": "itriskltd.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "stocktrade.de", "include_subdomains": true, "mode": "force-https" },
+ { "name": "openshift.redhat.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "therapynotes.com", "mode": "force-https" },
+ { "name": "www.therapynotes.com", "mode": "force-https" },
+ { "name": "wiz.biz", "include_subdomains": true, "mode": "force-https" },
+ { "name": "my.onlime.ch", "include_subdomains": true, "mode": "force-https" },
+ { "name": "webmail.onlime.ch", "include_subdomains": true, "mode": "force-https" },
+ { "name": "crm.onlime.ch", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.gov.uk", "include_subdomains": true, "mode": "force-https" },
+ { "name": "silentcircle.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "silentcircle.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "serverdensity.io", "include_subdomains": true, "mode": "force-https" },
+ { "name": "my.alfresco.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "webmail.gigahost.dk", "include_subdomains": true, "mode": "force-https" },
+ { "name": "paymill.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "paymill.de", "include_subdomains": true, "mode": "force-https" },
+ { "name": "gocardless.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "espra.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "zoo24.de", "include_subdomains": true, "mode": "force-https" },
+ { "name": "mega.co.nz", "mode": "force-https" },
+ { "name": "api.mega.co.nz", "include_subdomains": true, "mode": "force-https" },
+ { "name": "lockify.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "writeapp.me", "mode": "force-https" },
+ { "name": "bugzilla.mozilla.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "members.nearlyfreespeech.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "ssl.panoramio.com", "mode": "force-https" },
+ { "name": "kiwiirc.com", "mode": "force-https" },
+ { "name": "pay.gigahost.dk", "include_subdomains": true, "mode": "force-https" },
+ { "name": "controlcenter.gigahost.dk", "include_subdomains": true, "mode": "force-https" },
+ { "name": "simple.com", "mode": "force-https" },
+ { "name": "www.simple.com", "mode": "force-https" },
+ { "name": "fj.simple.com", "mode": "force-https" },
+ { "name": "api.simple.com", "mode": "force-https" },
+ { "name": "bank.simple.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "bassh.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "sah3.net", "include_subdomains": true, "mode": "force-https" },
+ { "name": "grc.com", "mode": "force-https" },
+ { "name": "www.grc.com", "mode": "force-https" },
+ { "name": "linode.com", "mode": "force-https" },
+ { "name": "www.linode.com", "mode": "force-https" },
+ { "name": "manager.linode.com", "mode": "force-https" },
+ { "name": "blog.linode.com", "mode": "force-https" },
+ { "name": "library.linode.com", "mode": "force-https" },
+ { "name": "forum.linode.com", "mode": "force-https" },
+ { "name": "p.linode.com", "mode": "force-https" },
+ { "name": "paste.linode.com", "mode": "force-https" },
+ { "name": "pastebin.linode.com", "mode": "force-https" },
+ { "name": "inertianetworks.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "carezone.com", "mode": "force-https" },
+ { "name": "conformal.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "cyphertite.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "logotype.se", "include_subdomains": true, "mode": "force-https" },
+ { "name": "bccx.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "launchkey.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "carlolly.co.uk", "include_subdomains": true, "mode": "force-https" },
+ { "name": "www.cyveillance.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "blog.cyveillance.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "whonix.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "blueseed.co", "include_subdomains": true, "mode": "force-https" },
+ { "name": "forum.quantifiedself.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "shodan.io", "include_subdomains": true, "mode": "force-https" },
+ { "name": "rapidresearch.me", "include_subdomains": true, "mode": "force-https" },
+ { "name": "surkatty.org", "include_subdomains": true, "mode": "force-https" },
+ { "name": "securityheaders.com", "include_subdomains": true, "mode": "force-https" },
+ { "name": "haste.ch", "include_subdomains": true, "mode": "force-https" },
+ { "name": "mudcrab.us", "include_subdomains": true, "mode": "force-https" },
+ { "name": "mediacru.sh", "include_subdomains": true, "mode": "force-https" },
+
+ // Entries that are only valid if the client supports SNI.
+ { "name": "gmail.com", "mode": "force-https", "pins": "google", "snionly": true },
+ { "name": "googlemail.com", "mode": "force-https", "pins": "google", "snionly": true },
+ { "name": "www.gmail.com", "mode": "force-https", "pins": "google", "snionly": true },
+ { "name": "www.googlemail.com", "mode": "force-https", "pins": "google", "snionly": true },
+ { "name": "google-analytics.com", "include_subdomains": true, "pins": "google", "snionly": true },
+ { "name": "googlegroups.com", "include_subdomains": true, "pins": "google", "snionly": true }
+ ]
+}
diff --git a/chromium/net/http/transport_security_state_unittest.cc b/chromium/net/http/transport_security_state_unittest.cc
new file mode 100644
index 00000000000..99d8c39f042
--- /dev/null
+++ b/chromium/net/http/transport_security_state_unittest.cc
@@ -0,0 +1,842 @@
+// Copyright (c) 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.
+
+#include "net/http/transport_security_state.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/sha1.h"
+#include "base/strings/string_piece.h"
+#include "crypto/sha2.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_cert_types.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/http_util.h"
+#include "net/ssl/ssl_info.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_OPENSSL)
+#include "crypto/openssl_util.h"
+#else
+#include "crypto/nss_util.h"
+#endif
+
+namespace net {
+
+class TransportSecurityStateTest : public testing::Test {
+ virtual void SetUp() {
+#if defined(USE_OPENSSL)
+ crypto::EnsureOpenSSLInit();
+#else
+ crypto::EnsureNSSInit();
+#endif
+ }
+
+ protected:
+ std::string CanonicalizeHost(const std::string& host) {
+ return TransportSecurityState::CanonicalizeHost(host);
+ }
+
+ bool GetStaticDomainState(TransportSecurityState* state,
+ const std::string& host,
+ bool sni_enabled,
+ TransportSecurityState::DomainState* result) {
+ return state->GetStaticDomainState(host, sni_enabled, result);
+ }
+
+ void EnableHost(TransportSecurityState* state,
+ const std::string& host,
+ const TransportSecurityState::DomainState& domain_state) {
+ return state->EnableHost(host, domain_state);
+ }
+};
+
+TEST_F(TransportSecurityStateTest, SimpleMatches) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+ bool include_subdomains = false;
+ state.AddHSTS("yahoo.com", expiry, include_subdomains);
+ EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, MatchesCase1) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+ bool include_subdomains = false;
+ state.AddHSTS("YAhoo.coM", expiry, include_subdomains);
+ EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, MatchesCase2) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state.GetDomainState("YAhoo.coM", true, &domain_state));
+ bool include_subdomains = false;
+ state.AddHSTS("yahoo.com", expiry, include_subdomains);
+ EXPECT_TRUE(state.GetDomainState("YAhoo.coM", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, SubdomainMatches) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+ bool include_subdomains = true;
+ state.AddHSTS("yahoo.com", expiry, include_subdomains);
+ EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state));
+ EXPECT_TRUE(state.GetDomainState("foo.yahoo.com", true, &domain_state));
+ EXPECT_TRUE(state.GetDomainState("foo.bar.yahoo.com", true, &domain_state));
+ EXPECT_TRUE(state.GetDomainState("foo.bar.baz.yahoo.com", true,
+ &domain_state));
+ EXPECT_FALSE(state.GetDomainState("com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, DeleteAllDynamicDataSince) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ const base::Time older = current_time - base::TimeDelta::FromSeconds(1000);
+
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+ bool include_subdomains = false;
+ state.AddHSTS("yahoo.com", expiry, include_subdomains);
+
+ state.DeleteAllDynamicDataSince(expiry);
+ EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state));
+ state.DeleteAllDynamicDataSince(older);
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, DeleteDynamicDataForHost) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ bool include_subdomains = false;
+ state.AddHSTS("yahoo.com", expiry, include_subdomains);
+
+ EXPECT_TRUE(state.GetDomainState("yahoo.com", true, &domain_state));
+ EXPECT_FALSE(state.GetDomainState("example.com", true, &domain_state));
+ EXPECT_TRUE(state.DeleteDynamicDataForHost("yahoo.com"));
+ EXPECT_FALSE(state.GetDomainState("yahoo.com", true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, IsPreloaded) {
+ const std::string paypal = CanonicalizeHost("paypal.com");
+ const std::string www_paypal = CanonicalizeHost("www.paypal.com");
+ const std::string foo_paypal = CanonicalizeHost("foo.paypal.com");
+ const std::string a_www_paypal = CanonicalizeHost("a.www.paypal.com");
+ const std::string abc_paypal = CanonicalizeHost("a.b.c.paypal.com");
+ const std::string example = CanonicalizeHost("example.com");
+ const std::string aypal = CanonicalizeHost("aypal.com");
+
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ EXPECT_TRUE(GetStaticDomainState(&state, paypal, true, &domain_state));
+ EXPECT_TRUE(GetStaticDomainState(&state, www_paypal, true, &domain_state));
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+ EXPECT_FALSE(GetStaticDomainState(&state, a_www_paypal, true, &domain_state));
+ EXPECT_FALSE(GetStaticDomainState(&state, abc_paypal, true, &domain_state));
+ EXPECT_FALSE(GetStaticDomainState(&state, example, true, &domain_state));
+ EXPECT_FALSE(GetStaticDomainState(&state, aypal, true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, PreloadedDomainSet) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ // The domain wasn't being set, leading to a blank string in the
+ // chrome://net-internals/#hsts UI. So test that.
+ EXPECT_TRUE(state.GetDomainState("market.android.com", true, &domain_state));
+ EXPECT_EQ(domain_state.domain, "market.android.com");
+ EXPECT_TRUE(state.GetDomainState("sub.market.android.com", true,
+ &domain_state));
+ EXPECT_EQ(domain_state.domain, "market.android.com");
+}
+
+static bool ShouldRedirect(const char* hostname) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ return state.GetDomainState(hostname, true /* SNI ok */, &domain_state) &&
+ domain_state.ShouldUpgradeToSSL();
+}
+
+static bool HasState(const char* hostname) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ return state.GetDomainState(hostname, true /* SNI ok */, &domain_state);
+}
+
+static bool HasPublicKeyPins(const char* hostname, bool sni_enabled) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ if (!state.GetDomainState(hostname, sni_enabled, &domain_state))
+ return false;
+
+ return domain_state.HasPublicKeyPins();
+}
+
+static bool HasPublicKeyPins(const char* hostname) {
+ return HasPublicKeyPins(hostname, true);
+}
+
+static bool OnlyPinning(const char *hostname) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ if (!state.GetDomainState(hostname, true /* SNI ok */, &domain_state))
+ return false;
+
+ return (domain_state.static_spki_hashes.size() > 0 ||
+ domain_state.bad_static_spki_hashes.size() > 0 ||
+ domain_state.dynamic_spki_hashes.size() > 0) &&
+ !domain_state.ShouldUpgradeToSSL();
+}
+
+TEST_F(TransportSecurityStateTest, Preloaded) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ // We do more extensive checks for the first domain.
+ EXPECT_TRUE(state.GetDomainState("www.paypal.com", true, &domain_state));
+ EXPECT_EQ(domain_state.upgrade_mode,
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS);
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+
+ EXPECT_TRUE(HasState("paypal.com"));
+ EXPECT_FALSE(HasState("www2.paypal.com"));
+ EXPECT_FALSE(HasState("www2.paypal.com"));
+
+ // Google hosts:
+
+ EXPECT_TRUE(ShouldRedirect("chrome.google.com"));
+ EXPECT_TRUE(ShouldRedirect("checkout.google.com"));
+ EXPECT_TRUE(ShouldRedirect("health.google.com"));
+ EXPECT_TRUE(ShouldRedirect("docs.google.com"));
+ EXPECT_TRUE(ShouldRedirect("sites.google.com"));
+ EXPECT_TRUE(ShouldRedirect("drive.google.com"));
+ EXPECT_TRUE(ShouldRedirect("spreadsheets.google.com"));
+ EXPECT_TRUE(ShouldRedirect("appengine.google.com"));
+ EXPECT_TRUE(ShouldRedirect("market.android.com"));
+ EXPECT_TRUE(ShouldRedirect("encrypted.google.com"));
+ EXPECT_TRUE(ShouldRedirect("accounts.google.com"));
+ EXPECT_TRUE(ShouldRedirect("profiles.google.com"));
+ EXPECT_TRUE(ShouldRedirect("mail.google.com"));
+ EXPECT_TRUE(ShouldRedirect("chatenabled.mail.google.com"));
+ EXPECT_TRUE(ShouldRedirect("talkgadget.google.com"));
+ EXPECT_TRUE(ShouldRedirect("hostedtalkgadget.google.com"));
+ EXPECT_TRUE(ShouldRedirect("talk.google.com"));
+ EXPECT_TRUE(ShouldRedirect("plus.google.com"));
+ EXPECT_TRUE(ShouldRedirect("groups.google.com"));
+ EXPECT_TRUE(ShouldRedirect("apis.google.com"));
+ EXPECT_FALSE(ShouldRedirect("chart.apis.google.com"));
+ EXPECT_TRUE(ShouldRedirect("ssl.google-analytics.com"));
+ EXPECT_TRUE(ShouldRedirect("gmail.com"));
+ EXPECT_TRUE(ShouldRedirect("www.gmail.com"));
+ EXPECT_TRUE(ShouldRedirect("googlemail.com"));
+ EXPECT_TRUE(ShouldRedirect("www.googlemail.com"));
+ EXPECT_TRUE(ShouldRedirect("googleplex.com"));
+ EXPECT_TRUE(ShouldRedirect("www.googleplex.com"));
+ EXPECT_FALSE(HasState("m.gmail.com"));
+ EXPECT_FALSE(HasState("m.googlemail.com"));
+
+ EXPECT_TRUE(OnlyPinning("www.google.com"));
+ EXPECT_TRUE(OnlyPinning("foo.google.com"));
+ EXPECT_TRUE(OnlyPinning("google.com"));
+ EXPECT_TRUE(OnlyPinning("www.youtube.com"));
+ EXPECT_TRUE(OnlyPinning("youtube.com"));
+ EXPECT_TRUE(OnlyPinning("i.ytimg.com"));
+ EXPECT_TRUE(OnlyPinning("ytimg.com"));
+ EXPECT_TRUE(OnlyPinning("googleusercontent.com"));
+ EXPECT_TRUE(OnlyPinning("www.googleusercontent.com"));
+ EXPECT_TRUE(OnlyPinning("www.google-analytics.com"));
+ EXPECT_TRUE(OnlyPinning("googleapis.com"));
+ EXPECT_TRUE(OnlyPinning("googleadservices.com"));
+ EXPECT_TRUE(OnlyPinning("googlecode.com"));
+ EXPECT_TRUE(OnlyPinning("appspot.com"));
+ EXPECT_TRUE(OnlyPinning("googlesyndication.com"));
+ EXPECT_TRUE(OnlyPinning("doubleclick.net"));
+ EXPECT_TRUE(OnlyPinning("googlegroups.com"));
+
+ // Tests for domains that don't work without SNI.
+ EXPECT_FALSE(state.GetDomainState("gmail.com", false, &domain_state));
+ EXPECT_FALSE(state.GetDomainState("www.gmail.com", false, &domain_state));
+ EXPECT_FALSE(state.GetDomainState("m.gmail.com", false, &domain_state));
+ EXPECT_FALSE(state.GetDomainState("googlemail.com", false, &domain_state));
+ EXPECT_FALSE(state.GetDomainState("www.googlemail.com", false,
+ &domain_state));
+ EXPECT_FALSE(state.GetDomainState("m.googlemail.com", false, &domain_state));
+
+ // Other hosts:
+
+ EXPECT_TRUE(ShouldRedirect("aladdinschools.appspot.com"));
+
+ EXPECT_TRUE(ShouldRedirect("ottospora.nl"));
+ EXPECT_TRUE(ShouldRedirect("www.ottospora.nl"));
+
+ EXPECT_TRUE(ShouldRedirect("www.paycheckrecords.com"));
+
+ EXPECT_TRUE(ShouldRedirect("lastpass.com"));
+ EXPECT_TRUE(ShouldRedirect("www.lastpass.com"));
+ EXPECT_FALSE(HasState("blog.lastpass.com"));
+
+ EXPECT_TRUE(ShouldRedirect("keyerror.com"));
+ EXPECT_TRUE(ShouldRedirect("www.keyerror.com"));
+
+ EXPECT_TRUE(ShouldRedirect("entropia.de"));
+ EXPECT_TRUE(ShouldRedirect("www.entropia.de"));
+ EXPECT_FALSE(HasState("foo.entropia.de"));
+
+ EXPECT_TRUE(ShouldRedirect("www.elanex.biz"));
+ EXPECT_FALSE(HasState("elanex.biz"));
+ EXPECT_FALSE(HasState("foo.elanex.biz"));
+
+ EXPECT_TRUE(ShouldRedirect("sunshinepress.org"));
+ EXPECT_TRUE(ShouldRedirect("www.sunshinepress.org"));
+ EXPECT_TRUE(ShouldRedirect("a.b.sunshinepress.org"));
+
+ EXPECT_TRUE(ShouldRedirect("www.noisebridge.net"));
+ EXPECT_FALSE(HasState("noisebridge.net"));
+ EXPECT_FALSE(HasState("foo.noisebridge.net"));
+
+ EXPECT_TRUE(ShouldRedirect("neg9.org"));
+ EXPECT_FALSE(HasState("www.neg9.org"));
+
+ EXPECT_TRUE(ShouldRedirect("riseup.net"));
+ EXPECT_TRUE(ShouldRedirect("foo.riseup.net"));
+
+ EXPECT_TRUE(ShouldRedirect("factor.cc"));
+ EXPECT_FALSE(HasState("www.factor.cc"));
+
+ EXPECT_TRUE(ShouldRedirect("members.mayfirst.org"));
+ EXPECT_TRUE(ShouldRedirect("support.mayfirst.org"));
+ EXPECT_TRUE(ShouldRedirect("id.mayfirst.org"));
+ EXPECT_TRUE(ShouldRedirect("lists.mayfirst.org"));
+ EXPECT_FALSE(HasState("www.mayfirst.org"));
+
+ EXPECT_TRUE(ShouldRedirect("romab.com"));
+ EXPECT_TRUE(ShouldRedirect("www.romab.com"));
+ EXPECT_TRUE(ShouldRedirect("foo.romab.com"));
+
+ EXPECT_TRUE(ShouldRedirect("logentries.com"));
+ EXPECT_TRUE(ShouldRedirect("www.logentries.com"));
+ EXPECT_FALSE(HasState("foo.logentries.com"));
+
+ EXPECT_TRUE(ShouldRedirect("stripe.com"));
+ EXPECT_TRUE(ShouldRedirect("foo.stripe.com"));
+
+ EXPECT_TRUE(ShouldRedirect("cloudsecurityalliance.org"));
+ EXPECT_TRUE(ShouldRedirect("foo.cloudsecurityalliance.org"));
+
+ EXPECT_TRUE(ShouldRedirect("login.sapo.pt"));
+ EXPECT_TRUE(ShouldRedirect("foo.login.sapo.pt"));
+
+ EXPECT_TRUE(ShouldRedirect("mattmccutchen.net"));
+ EXPECT_TRUE(ShouldRedirect("foo.mattmccutchen.net"));
+
+ EXPECT_TRUE(ShouldRedirect("betnet.fr"));
+ EXPECT_TRUE(ShouldRedirect("foo.betnet.fr"));
+
+ EXPECT_TRUE(ShouldRedirect("uprotect.it"));
+ EXPECT_TRUE(ShouldRedirect("foo.uprotect.it"));
+
+ EXPECT_TRUE(ShouldRedirect("squareup.com"));
+ EXPECT_FALSE(HasState("foo.squareup.com"));
+
+ EXPECT_TRUE(ShouldRedirect("cert.se"));
+ EXPECT_TRUE(ShouldRedirect("foo.cert.se"));
+
+ EXPECT_TRUE(ShouldRedirect("crypto.is"));
+ EXPECT_TRUE(ShouldRedirect("foo.crypto.is"));
+
+ EXPECT_TRUE(ShouldRedirect("simon.butcher.name"));
+ EXPECT_TRUE(ShouldRedirect("foo.simon.butcher.name"));
+
+ EXPECT_TRUE(ShouldRedirect("linx.net"));
+ EXPECT_TRUE(ShouldRedirect("foo.linx.net"));
+
+ EXPECT_TRUE(ShouldRedirect("dropcam.com"));
+ EXPECT_TRUE(ShouldRedirect("www.dropcam.com"));
+ EXPECT_FALSE(HasState("foo.dropcam.com"));
+
+ EXPECT_TRUE(state.GetDomainState("torproject.org", false, &domain_state));
+ EXPECT_FALSE(domain_state.static_spki_hashes.empty());
+ EXPECT_TRUE(state.GetDomainState("www.torproject.org", false,
+ &domain_state));
+ EXPECT_FALSE(domain_state.static_spki_hashes.empty());
+ EXPECT_TRUE(state.GetDomainState("check.torproject.org", false,
+ &domain_state));
+ EXPECT_FALSE(domain_state.static_spki_hashes.empty());
+ EXPECT_TRUE(state.GetDomainState("blog.torproject.org", false,
+ &domain_state));
+ EXPECT_FALSE(domain_state.static_spki_hashes.empty());
+ EXPECT_TRUE(ShouldRedirect("ebanking.indovinabank.com.vn"));
+ EXPECT_TRUE(ShouldRedirect("foo.ebanking.indovinabank.com.vn"));
+
+ EXPECT_TRUE(ShouldRedirect("epoxate.com"));
+ EXPECT_FALSE(HasState("foo.epoxate.com"));
+
+ EXPECT_TRUE(HasPublicKeyPins("torproject.org"));
+ EXPECT_TRUE(HasPublicKeyPins("www.torproject.org"));
+ EXPECT_TRUE(HasPublicKeyPins("check.torproject.org"));
+ EXPECT_TRUE(HasPublicKeyPins("blog.torproject.org"));
+ EXPECT_FALSE(HasState("foo.torproject.org"));
+
+ EXPECT_TRUE(ShouldRedirect("www.moneybookers.com"));
+ EXPECT_FALSE(HasState("moneybookers.com"));
+
+ EXPECT_TRUE(ShouldRedirect("ledgerscope.net"));
+ EXPECT_TRUE(ShouldRedirect("www.ledgerscope.net"));
+ EXPECT_FALSE(HasState("status.ledgerscope.net"));
+
+ EXPECT_TRUE(ShouldRedirect("foo.app.recurly.com"));
+ EXPECT_TRUE(ShouldRedirect("foo.api.recurly.com"));
+
+ EXPECT_TRUE(ShouldRedirect("greplin.com"));
+ EXPECT_TRUE(ShouldRedirect("www.greplin.com"));
+ EXPECT_FALSE(HasState("foo.greplin.com"));
+
+ EXPECT_TRUE(ShouldRedirect("luneta.nearbuysystems.com"));
+ EXPECT_TRUE(ShouldRedirect("foo.luneta.nearbuysystems.com"));
+
+ EXPECT_TRUE(ShouldRedirect("ubertt.org"));
+ EXPECT_TRUE(ShouldRedirect("foo.ubertt.org"));
+
+ EXPECT_TRUE(ShouldRedirect("pixi.me"));
+ EXPECT_TRUE(ShouldRedirect("www.pixi.me"));
+
+ EXPECT_TRUE(ShouldRedirect("grepular.com"));
+ EXPECT_TRUE(ShouldRedirect("www.grepular.com"));
+
+ EXPECT_TRUE(ShouldRedirect("mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.mydigipass.com"));
+ EXPECT_TRUE(ShouldRedirect("www.mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.www.mydigipass.com"));
+ EXPECT_TRUE(ShouldRedirect("developer.mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.developer.mydigipass.com"));
+ EXPECT_TRUE(ShouldRedirect("www.developer.mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.www.developer.mydigipass.com"));
+ EXPECT_TRUE(ShouldRedirect("sandbox.mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.sandbox.mydigipass.com"));
+ EXPECT_TRUE(ShouldRedirect("www.sandbox.mydigipass.com"));
+ EXPECT_FALSE(ShouldRedirect("foo.www.sandbox.mydigipass.com"));
+
+ EXPECT_TRUE(ShouldRedirect("crypto.cat"));
+ EXPECT_FALSE(ShouldRedirect("foo.crypto.cat"));
+
+ EXPECT_TRUE(ShouldRedirect("bigshinylock.minazo.net"));
+ EXPECT_TRUE(ShouldRedirect("foo.bigshinylock.minazo.net"));
+
+ EXPECT_TRUE(ShouldRedirect("crate.io"));
+ EXPECT_TRUE(ShouldRedirect("foo.crate.io"));
+
+ EXPECT_TRUE(HasPublicKeyPins("www.twitter.com"));
+}
+
+TEST_F(TransportSecurityStateTest, LongNames) {
+ TransportSecurityState state;
+ const char kLongName[] =
+ "lookupByWaveIdHashAndWaveIdIdAndWaveIdDomainAndWaveletIdIdAnd"
+ "WaveletIdDomainAndBlipBlipid";
+ TransportSecurityState::DomainState domain_state;
+ // Just checks that we don't hit a NOTREACHED.
+ EXPECT_FALSE(state.GetDomainState(kLongName, true, &domain_state));
+}
+
+TEST_F(TransportSecurityStateTest, BuiltinCertPins) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ EXPECT_TRUE(state.GetDomainState("chrome.google.com", true, &domain_state));
+ EXPECT_TRUE(HasPublicKeyPins("chrome.google.com"));
+
+ HashValueVector hashes;
+ // Checks that a built-in list does exist.
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(hashes));
+ EXPECT_FALSE(HasPublicKeyPins("www.paypal.com"));
+
+ EXPECT_TRUE(HasPublicKeyPins("docs.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("1.docs.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("sites.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("drive.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("spreadsheets.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("health.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("checkout.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("appengine.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("market.android.com"));
+ EXPECT_TRUE(HasPublicKeyPins("encrypted.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("accounts.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("profiles.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("mail.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("chatenabled.mail.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("talkgadget.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("hostedtalkgadget.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("talk.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("plus.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("groups.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("apis.google.com"));
+
+ EXPECT_TRUE(HasPublicKeyPins("ssl.gstatic.com"));
+ EXPECT_FALSE(HasPublicKeyPins("www.gstatic.com"));
+ EXPECT_TRUE(HasPublicKeyPins("ssl.google-analytics.com"));
+ EXPECT_TRUE(HasPublicKeyPins("www.googleplex.com"));
+
+ // Disabled in order to help track down pinning failures --agl
+ EXPECT_TRUE(HasPublicKeyPins("twitter.com"));
+ EXPECT_FALSE(HasPublicKeyPins("foo.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("www.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("api.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("oauth.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("mobile.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("dev.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("business.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("platform.twitter.com"));
+ EXPECT_TRUE(HasPublicKeyPins("si0.twimg.com"));
+ EXPECT_TRUE(HasPublicKeyPins("twimg0-a.akamaihd.net"));
+}
+
+static bool AddHash(const std::string& type_and_base64,
+ HashValueVector* out) {
+ HashValue hash;
+ if (!hash.FromString(type_and_base64))
+ return false;
+
+ out->push_back(hash);
+ return true;
+}
+
+TEST_F(TransportSecurityStateTest, PinValidationWithRejectedCerts) {
+ // kGoodPath is plus.google.com via Google Internet Authority.
+ static const char* kGoodPath[] = {
+ "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=",
+ "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=",
+ "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=",
+ NULL,
+ };
+
+ // kBadPath is plus.google.com via Trustcenter, which contains a required
+ // certificate (Equifax root), but also an excluded certificate
+ // (Trustcenter).
+ static const char* kBadPath[] = {
+ "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=",
+ "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=",
+ "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=",
+ NULL,
+ };
+
+ HashValueVector good_hashes, bad_hashes;
+
+ for (size_t i = 0; kGoodPath[i]; i++) {
+ EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes));
+ }
+ for (size_t i = 0; kBadPath[i]; i++) {
+ EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes));
+ }
+
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(state.GetDomainState("plus.google.com", true, &domain_state));
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(good_hashes));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(bad_hashes));
+}
+
+TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) {
+ // kGoodPath is blog.torproject.org.
+ static const char* kGoodPath[] = {
+ "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI=",
+ "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=",
+ "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=",
+ NULL,
+ };
+
+ // kBadPath is plus.google.com via Trustcenter, which is utterly wrong for
+ // torproject.org.
+ static const char* kBadPath[] = {
+ "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=",
+ "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=",
+ "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=",
+ NULL,
+ };
+
+ HashValueVector good_hashes, bad_hashes;
+
+ for (size_t i = 0; kGoodPath[i]; i++) {
+ EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes));
+ }
+ for (size_t i = 0; kBadPath[i]; i++) {
+ EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes));
+ }
+
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(state.GetDomainState("blog.torproject.org", true, &domain_state));
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(good_hashes));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(bad_hashes));
+}
+
+TEST_F(TransportSecurityStateTest, PinValidationWithRejectedCertsMixedHashes) {
+ static const char* ee_sha1 = "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=";
+ static const char* ee_sha256 =
+ "sha256/sRJBQqWhpaKIGcc1NA7/jJ4vgWj+47oYfyU7waOS1+I=";
+ static const char* google_1024_sha1 = "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=";
+ static const char* google_1024_sha256 =
+ "sha256/trlUMquuV/4CDLK3T0+fkXPIxwivyecyrOIyeQR8bQU=";
+ static const char* equifax_sha1 = "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=";
+ static const char* equifax_sha256 =
+ "sha256//1aAzXOlcD2gSBegdf1GJQanNQbEuBoVg+9UlHjSZHY=";
+ static const char* trustcenter_sha1 = "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=";
+ static const char* trustcenter_sha256 =
+ "sha256/Dq58KIA4NMLsboWMLU8/aTREzaAGEFW+EtUule8dd/M=";
+
+ // Good chains for plus.google.com chain up through google_1024_sha{1,256}
+ // to equifax_sha{1,256}. Bad chains chain up to Equifax through
+ // trustcenter_sha{1,256}, which is a blacklisted key. Even though Equifax
+ // and Google1024 are known-good, the blacklistedness of Trustcenter
+ // should override and cause pin validation failure.
+
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(state.GetDomainState("plus.google.com", true, &domain_state));
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+
+ // The statically-defined pins are all SHA-1, so we add some SHA-256 pins
+ // manually:
+ EXPECT_TRUE(AddHash(google_1024_sha256, &domain_state.static_spki_hashes));
+ EXPECT_TRUE(AddHash(trustcenter_sha256,
+ &domain_state.bad_static_spki_hashes));
+
+ // Try an all-good SHA1 chain.
+ HashValueVector validated_chain;
+ EXPECT_TRUE(AddHash(ee_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(google_1024_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha1, &validated_chain));
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try an all-bad SHA1 chain.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(trustcenter_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha1, &validated_chain));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try an all-good SHA-256 chain.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(google_1024_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha256, &validated_chain));
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try an all-bad SHA-256 chain.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(trustcenter_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha256, &validated_chain));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try a mixed-hash good chain.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(google_1024_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha256, &validated_chain));
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try a mixed-hash bad chain.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(trustcenter_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha1, &validated_chain));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try a chain with all good hashes.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(google_1024_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(ee_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(google_1024_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha256, &validated_chain));
+ EXPECT_TRUE(domain_state.CheckPublicKeyPins(validated_chain));
+
+ // Try a chain with all bad hashes.
+ validated_chain.clear();
+ EXPECT_TRUE(AddHash(ee_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(trustcenter_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha1, &validated_chain));
+ EXPECT_TRUE(AddHash(ee_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(trustcenter_sha256, &validated_chain));
+ EXPECT_TRUE(AddHash(equifax_sha256, &validated_chain));
+ EXPECT_FALSE(domain_state.CheckPublicKeyPins(validated_chain));
+}
+
+TEST_F(TransportSecurityStateTest, OptionalHSTSCertPins) {
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+
+ EXPECT_FALSE(ShouldRedirect("www.google-analytics.com"));
+
+ EXPECT_FALSE(HasPublicKeyPins("www.google-analytics.com", false));
+ EXPECT_TRUE(HasPublicKeyPins("www.google-analytics.com"));
+ EXPECT_TRUE(HasPublicKeyPins("google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("www.google.com"));
+ EXPECT_TRUE(HasPublicKeyPins("mail-attachment.googleusercontent.com"));
+ EXPECT_TRUE(HasPublicKeyPins("www.youtube.com"));
+ EXPECT_TRUE(HasPublicKeyPins("i.ytimg.com"));
+ EXPECT_TRUE(HasPublicKeyPins("googleapis.com"));
+ EXPECT_TRUE(HasPublicKeyPins("ajax.googleapis.com"));
+ EXPECT_TRUE(HasPublicKeyPins("googleadservices.com"));
+ EXPECT_TRUE(HasPublicKeyPins("pagead2.googleadservices.com"));
+ EXPECT_TRUE(HasPublicKeyPins("googlecode.com"));
+ EXPECT_TRUE(HasPublicKeyPins("kibbles.googlecode.com"));
+ EXPECT_TRUE(HasPublicKeyPins("appspot.com"));
+ EXPECT_TRUE(HasPublicKeyPins("googlesyndication.com"));
+ EXPECT_TRUE(HasPublicKeyPins("doubleclick.net"));
+ EXPECT_TRUE(HasPublicKeyPins("ad.doubleclick.net"));
+ EXPECT_FALSE(HasPublicKeyPins("learn.doubleclick.net"));
+ EXPECT_TRUE(HasPublicKeyPins("a.googlegroups.com"));
+ EXPECT_FALSE(HasPublicKeyPins("a.googlegroups.com", false));
+}
+
+TEST_F(TransportSecurityStateTest, OverrideBuiltins) {
+ EXPECT_TRUE(HasPublicKeyPins("google.com"));
+ EXPECT_FALSE(ShouldRedirect("google.com"));
+ EXPECT_FALSE(ShouldRedirect("www.google.com"));
+
+ TransportSecurityState state;
+ TransportSecurityState::DomainState domain_state;
+ const base::Time current_time(base::Time::Now());
+ const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000);
+ domain_state.upgrade_expiry = expiry;
+ EnableHost(&state, "www.google.com", domain_state);
+
+ EXPECT_TRUE(state.GetDomainState("www.google.com", true, &domain_state));
+}
+
+static const uint8 kSidePinLeafSPKI[] = {
+ 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xe4,
+ 0x1d, 0xcc, 0xf2, 0x92, 0xe7, 0x7a, 0xc6, 0x36, 0xf7, 0x1a, 0x62, 0x31, 0x7d,
+ 0x37, 0xea, 0x0d, 0xa2, 0xa8, 0x12, 0x2b, 0xc2, 0x1c, 0x82, 0x3e, 0xa5, 0x70,
+ 0x4a, 0x83, 0x5d, 0x9b, 0x84, 0x82, 0x70, 0xa4, 0x88, 0x98, 0x98, 0x41, 0x29,
+ 0x31, 0xcb, 0x6e, 0x2a, 0x54, 0x65, 0x14, 0x60, 0xcc, 0x00, 0xe8, 0x10, 0x30,
+ 0x0a, 0x4a, 0xd1, 0xa7, 0x52, 0xfe, 0x2d, 0x31, 0x2a, 0x1d, 0x0d, 0x02, 0x03,
+ 0x01, 0x00, 0x01,
+};
+
+static const uint8 kSidePinInfo[] = {
+ 0x01, 0x00, 0x53, 0x50, 0x49, 0x4e, 0xa0, 0x00, 0x03, 0x00, 0x53, 0x49, 0x47,
+ 0x00, 0x50, 0x55, 0x42, 0x4b, 0x41, 0x4c, 0x47, 0x4f, 0x47, 0x00, 0x41, 0x00,
+ 0x04, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xfb, 0x26, 0xd5, 0xe8, 0x76, 0x35,
+ 0x96, 0x6d, 0x91, 0x9b, 0x5b, 0x27, 0xe6, 0x09, 0x1c, 0x7b, 0x6c, 0xcd, 0xc8,
+ 0x10, 0x25, 0x95, 0xc0, 0xa5, 0xf6, 0x6c, 0x6f, 0xfb, 0x59, 0x1e, 0x2d, 0xf4,
+ 0x02, 0x20, 0x33, 0x0a, 0xf8, 0x8b, 0x3e, 0xc4, 0xca, 0x75, 0x28, 0xdf, 0x5f,
+ 0xab, 0xe4, 0x46, 0xa0, 0xdd, 0x2d, 0xe5, 0xad, 0xc3, 0x81, 0x44, 0x70, 0xb2,
+ 0x10, 0x87, 0xe8, 0xc3, 0xd6, 0x6e, 0x12, 0x5d, 0x04, 0x67, 0x0b, 0x7d, 0xf2,
+ 0x99, 0x75, 0x57, 0x99, 0x3a, 0x98, 0xf8, 0xe4, 0xdf, 0x79, 0xdf, 0x8e, 0x02,
+ 0x2c, 0xbe, 0xd8, 0xfd, 0x75, 0x80, 0x18, 0xb1, 0x6f, 0x43, 0xd9, 0x8a, 0x79,
+ 0xc3, 0x6e, 0x18, 0xdf, 0x79, 0xc0, 0x59, 0xab, 0xd6, 0x77, 0x37, 0x6a, 0x94,
+ 0x5a, 0x7e, 0xfb, 0xa9, 0xc5, 0x54, 0x14, 0x3a, 0x7b, 0x97, 0x17, 0x2a, 0xb6,
+ 0x1e, 0x59, 0x4f, 0x2f, 0xb1, 0x15, 0x1a, 0x34, 0x50, 0x32, 0x35, 0x36,
+};
+
+static const uint8 kSidePinExpectedHash[20] = {
+ 0xb5, 0x91, 0x66, 0x47, 0x43, 0x16, 0x62, 0x86, 0xd4, 0x1e, 0x5d, 0x36, 0xe1,
+ 0xc4, 0x09, 0x3d, 0x2d, 0x1d, 0xea, 0x1e,
+};
+
+TEST_F(TransportSecurityStateTest, GooglePinnedProperties) {
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.example.com", true));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.paypal.com", true));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "mail.twitter.com", true));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.google.com.int", true));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "jottit.com", true));
+ // learn.doubleclick.net has a more specific match than
+ // *.doubleclick.com, and has 0 or NULL for its required certs.
+ // This test ensures that the exact-match-preferred behavior
+ // works.
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "learn.doubleclick.net", true));
+
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "encrypted.google.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "mail.google.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "accounts.google.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "doubleclick.net", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "ad.doubleclick.net", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "youtube.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.profiles.google.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "checkout.google.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "googleadservices.com", true));
+
+ // Test with sni_enabled false:
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.example.com", false));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.paypal.com", false));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "checkout.google.com", false));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "googleadservices.com", false));
+
+ // Test some SNI hosts:
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "gmail.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "googlegroups.com", true));
+ EXPECT_TRUE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.googlegroups.com", true));
+ // Expect to fail for SNI hosts when not searching the SNI list:
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "gmail.com", false));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "googlegroups.com", false));
+ EXPECT_FALSE(TransportSecurityState::IsGooglePinnedProperty(
+ "www.googlegroups.com", false));
+}
+
+} // namespace net
diff --git a/chromium/net/http/url_security_manager.cc b/chromium/net/http/url_security_manager.cc
new file mode 100644
index 00000000000..4ff0f654dac
--- /dev/null
+++ b/chromium/net/http/url_security_manager.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 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/http/url_security_manager.h"
+
+#include "net/http/http_auth_filter.h"
+
+namespace net {
+
+URLSecurityManagerWhitelist::URLSecurityManagerWhitelist(
+ const HttpAuthFilter* whitelist_default,
+ const HttpAuthFilter* whitelist_delegate)
+ : whitelist_default_(whitelist_default),
+ whitelist_delegate_(whitelist_delegate) {
+}
+
+URLSecurityManagerWhitelist::~URLSecurityManagerWhitelist() {}
+
+bool URLSecurityManagerWhitelist::CanUseDefaultCredentials(
+ const GURL& auth_origin) const {
+ if (whitelist_default_.get())
+ return whitelist_default_->IsValid(auth_origin, HttpAuth::AUTH_SERVER);
+ return false;
+}
+
+bool URLSecurityManagerWhitelist::CanDelegate(const GURL& auth_origin) const {
+ if (whitelist_delegate_.get())
+ return whitelist_delegate_->IsValid(auth_origin, HttpAuth::AUTH_SERVER);
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/http/url_security_manager.h b/chromium/net/http/url_security_manager.h
new file mode 100644
index 00000000000..4015fc26766
--- /dev/null
+++ b/chromium/net/http/url_security_manager.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2011 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_HTTP_URL_SECURITY_MANAGER_H_
+#define NET_HTTP_URL_SECURITY_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class HttpAuthFilter;
+
+// The URL security manager controls the policies (allow, deny, prompt user)
+// regarding URL actions (e.g., sending the default credentials to a server).
+class NET_EXPORT URLSecurityManager {
+ public:
+ URLSecurityManager() {}
+ virtual ~URLSecurityManager() {}
+
+ // Creates a platform-dependent instance of URLSecurityManager.
+ //
+ // |whitelist_default| is the whitelist of servers that default credentials
+ // can be used with during NTLM or Negotiate authentication. If
+ // |whitelist_default| is NULL and the platform is Windows, it indicates
+ // that security zone mapping should be used to determine whether default
+ // credentials sxhould be used. If |whitelist_default| is NULL and the
+ // platform is non-Windows, it indicates that no servers should be
+ // whitelisted.
+ //
+ // |whitelist_delegate| is the whitelist of servers that are allowed
+ // to have Delegated Kerberos tickets. If |whitelist_delegate| is NULL,
+ // no servers can have delegated Kerberos tickets.
+ //
+ // Both |whitelist_default| and |whitelist_delegate| will be owned by
+ // the created URLSecurityManager.
+ //
+ // TODO(cbentzel): Perhaps it's better to make a non-abstract HttpAuthFilter
+ // and just copy into the URLSecurityManager?
+ static URLSecurityManager* Create(const HttpAuthFilter* whitelist_default,
+ const HttpAuthFilter* whitelist_delegate);
+
+ // Returns true if we can send the default credentials to the server at
+ // |auth_origin| for HTTP NTLM or Negotiate authentication.
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) const = 0;
+
+ // Returns true if Kerberos delegation is allowed for the server at
+ // |auth_origin| for HTTP Negotiate authentication.
+ virtual bool CanDelegate(const GURL& auth_origin) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManager);
+};
+
+class URLSecurityManagerWhitelist : public URLSecurityManager {
+ public:
+ // The URLSecurityManagerWhitelist takes ownership of the whitelists.
+ URLSecurityManagerWhitelist(const HttpAuthFilter* whitelist_default,
+ const HttpAuthFilter* whitelist_delegation);
+ virtual ~URLSecurityManagerWhitelist();
+
+ // URLSecurityManager methods.
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) const OVERRIDE;
+ virtual bool CanDelegate(const GURL& auth_origin) const OVERRIDE;
+
+ private:
+ scoped_ptr<const HttpAuthFilter> whitelist_default_;
+ scoped_ptr<const HttpAuthFilter> whitelist_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManagerWhitelist);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_URL_SECURITY_MANAGER_H_
diff --git a/chromium/net/http/url_security_manager_posix.cc b/chromium/net/http/url_security_manager_posix.cc
new file mode 100644
index 00000000000..d3b42fb743f
--- /dev/null
+++ b/chromium/net/http/url_security_manager_posix.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2010 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/http/url_security_manager.h"
+
+#include "net/http/http_auth_filter.h"
+
+namespace net {
+
+// static
+URLSecurityManager* URLSecurityManager::Create(
+ const HttpAuthFilter* whitelist_default,
+ const HttpAuthFilter* whitelist_delegate) {
+ return new URLSecurityManagerWhitelist(whitelist_default, whitelist_delegate);
+}
+
+} // namespace net
diff --git a/chromium/net/http/url_security_manager_unittest.cc b/chromium/net/http/url_security_manager_unittest.cc
new file mode 100644
index 00000000000..cf072e587cd
--- /dev/null
+++ b/chromium/net/http/url_security_manager_unittest.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2010 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/http/url_security_manager.h"
+
+#include "base/basictypes.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_auth_filter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+struct TestData {
+ const char* url;
+ bool succeds_in_windows_default;
+ bool succeeds_in_whitelist;
+};
+
+const char* kTestAuthWhitelist = "*example.com,*foobar.com,baz";
+
+// Under Windows the following will be allowed by default:
+// localhost
+// host names without a period.
+// In Posix systems (or on Windows if a whitelist is specified explicitly),
+// everything depends on the whitelist.
+const TestData kTestDataList[] = {
+ { "http://localhost", true, false },
+ { "http://bat", true, false },
+ { "http://www.example.com", false, true },
+ { "http://example.com", false, true },
+ { "http://foobar.com", false, true },
+ { "http://boo.foobar.com", false, true },
+ { "http://baz", true, true },
+ { "http://www.exampl.com", false, false },
+ { "http://example.org", false, false },
+ { "http://foobar.net", false, false },
+ { "http://boo.fubar.com", false, false },
+};
+
+} // namespace
+
+TEST(URLSecurityManager, UseDefaultCredentials) {
+ HttpAuthFilterWhitelist* auth_filter = new HttpAuthFilterWhitelist(
+ kTestAuthWhitelist);
+ ASSERT_TRUE(auth_filter);
+ // The URL security manager takes ownership of |auth_filter|.
+ scoped_ptr<URLSecurityManager> url_security_manager(
+ URLSecurityManager::Create(auth_filter, NULL));
+ ASSERT_TRUE(url_security_manager.get());
+
+ for (size_t i = 0; i < arraysize(kTestDataList); ++i) {
+ GURL gurl(kTestDataList[i].url);
+ bool can_use_default =
+ url_security_manager->CanUseDefaultCredentials(gurl);
+
+ EXPECT_EQ(kTestDataList[i].succeeds_in_whitelist, can_use_default)
+ << " Run: " << i << " URL: '" << gurl << "'";
+ }
+}
+
+TEST(URLSecurityManager, CanDelegate) {
+ HttpAuthFilterWhitelist* auth_filter = new HttpAuthFilterWhitelist(
+ kTestAuthWhitelist);
+ ASSERT_TRUE(auth_filter);
+ // The URL security manager takes ownership of |auth_filter|.
+ scoped_ptr<URLSecurityManager> url_security_manager(
+ URLSecurityManager::Create(NULL, auth_filter));
+ ASSERT_TRUE(url_security_manager.get());
+
+ for (size_t i = 0; i < arraysize(kTestDataList); ++i) {
+ GURL gurl(kTestDataList[i].url);
+ bool can_delegate = url_security_manager->CanDelegate(gurl);
+ EXPECT_EQ(kTestDataList[i].succeeds_in_whitelist, can_delegate)
+ << " Run: " << i << " URL: '" << gurl << "'";
+ }
+}
+
+TEST(URLSecurityManager, CanDelegate_NoWhitelist) {
+ // Nothing can delegate in this case.
+ scoped_ptr<URLSecurityManager> url_security_manager(
+ URLSecurityManager::Create(NULL, NULL));
+ ASSERT_TRUE(url_security_manager.get());
+
+ for (size_t i = 0; i < arraysize(kTestDataList); ++i) {
+ GURL gurl(kTestDataList[i].url);
+ bool can_delegate = url_security_manager->CanDelegate(gurl);
+ EXPECT_FALSE(can_delegate);
+ }
+}
+
+
+} // namespace net
diff --git a/chromium/net/http/url_security_manager_win.cc b/chromium/net/http/url_security_manager_win.cc
new file mode 100644
index 00000000000..cb3c66ef0f0
--- /dev/null
+++ b/chromium/net/http/url_security_manager_win.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 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/http/url_security_manager.h"
+
+#include <urlmon.h>
+#pragma comment(lib, "urlmon.lib")
+
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_comptr.h"
+#include "net/http/http_auth_filter.h"
+#include "url/gurl.h"
+
+// The Windows implementation of URLSecurityManager uses WinINet/IE's
+// URL security zone manager. See the MSDN page "URL Security Zones" at
+// http://msdn.microsoft.com/en-us/library/ms537021(VS.85).aspx for more
+// info on the Internet Security Manager and Internet Zone Manager objects.
+//
+// On Windows, we honor the WinINet/IE settings and group policy related to
+// URL Security Zones. See the Microsoft Knowledge Base article 182569
+// "Internet Explorer security zones registry entries for advanced users"
+// (http://support.microsoft.com/kb/182569) for more info on these registry
+// keys.
+
+namespace net {
+
+class URLSecurityManagerWin : public URLSecurityManager {
+ public:
+ explicit URLSecurityManagerWin(const HttpAuthFilter* whitelist_delegate);
+
+ // URLSecurityManager methods:
+ virtual bool CanUseDefaultCredentials(const GURL& auth_origin) const;
+ virtual bool CanDelegate(const GURL& auth_origin) const;
+
+ private:
+ bool EnsureSystemSecurityManager();
+
+ base::win::ScopedComPtr<IInternetSecurityManager> security_manager_;
+ scoped_ptr<const HttpAuthFilter> whitelist_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLSecurityManagerWin);
+};
+
+URLSecurityManagerWin::URLSecurityManagerWin(
+ const HttpAuthFilter* whitelist_delegate)
+ : whitelist_delegate_(whitelist_delegate) {
+}
+
+bool URLSecurityManagerWin::CanUseDefaultCredentials(
+ const GURL& auth_origin) const {
+ if (!const_cast<URLSecurityManagerWin*>(this)->EnsureSystemSecurityManager())
+ return false;
+
+ std::wstring url_w = ASCIIToWide(auth_origin.spec());
+ DWORD policy = 0;
+ HRESULT hr;
+ hr = security_manager_->ProcessUrlAction(url_w.c_str(),
+ URLACTION_CREDENTIALS_USE,
+ reinterpret_cast<BYTE*>(&policy),
+ sizeof(policy), NULL, 0,
+ PUAF_NOUI, 0);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "IInternetSecurityManager::ProcessUrlAction failed: " << hr;
+ return false;
+ }
+
+ // Four possible policies for URLACTION_CREDENTIALS_USE. See the MSDN page
+ // "About URL Security Zones" at
+ // http://msdn.microsoft.com/en-us/library/ms537183(VS.85).aspx
+ switch (policy) {
+ case URLPOLICY_CREDENTIALS_SILENT_LOGON_OK:
+ return true;
+ case URLPOLICY_CREDENTIALS_CONDITIONAL_PROMPT: {
+ // This policy means "prompt the user for permission if the resource is
+ // not located in the Intranet zone". TODO(wtc): Note that it's
+ // prompting for permission (to use the default credentials), as opposed
+ // to prompting the user to enter a user name and password.
+
+ // URLZONE_LOCAL_MACHINE 0
+ // URLZONE_INTRANET 1
+ // URLZONE_TRUSTED 2
+ // URLZONE_INTERNET 3
+ // URLZONE_UNTRUSTED 4
+ DWORD zone = 0;
+ hr = security_manager_->MapUrlToZone(url_w.c_str(), &zone, 0);
+ if (FAILED(hr)) {
+ LOG(ERROR) << "IInternetSecurityManager::MapUrlToZone failed: " << hr;
+ return false;
+ }
+ return zone <= URLZONE_INTRANET;
+ }
+ case URLPOLICY_CREDENTIALS_MUST_PROMPT_USER:
+ return false;
+ case URLPOLICY_CREDENTIALS_ANONYMOUS_ONLY:
+ // TODO(wtc): we should fail the authentication.
+ return false;
+ default:
+ NOTREACHED();
+ return false;
+ }
+}
+
+bool URLSecurityManagerWin::CanDelegate(const GURL& auth_origin) const {
+ // TODO(cbentzel): Could this just use the security zone as well? Apparently
+ // this is what IE does as well.
+ if (whitelist_delegate_.get())
+ return whitelist_delegate_->IsValid(auth_origin, HttpAuth::AUTH_SERVER);
+ return false;
+}
+
+bool URLSecurityManagerWin::EnsureSystemSecurityManager() {
+ if (!security_manager_) {
+ HRESULT hr = CoInternetCreateSecurityManager(NULL,
+ security_manager_.Receive(),
+ NULL);
+ if (FAILED(hr) || !security_manager_) {
+ LOG(ERROR) << "Unable to create the Windows Security Manager instance";
+ return false;
+ }
+ }
+ return true;
+}
+
+// static
+URLSecurityManager* URLSecurityManager::Create(
+ const HttpAuthFilter* whitelist_default,
+ const HttpAuthFilter* whitelist_delegate) {
+ // If we have a whitelist, just use that.
+ if (whitelist_default)
+ return new URLSecurityManagerWhitelist(whitelist_default,
+ whitelist_delegate);
+ return new URLSecurityManagerWin(whitelist_delegate);
+}
+
+} // namespace net
diff --git a/chromium/net/net.gyp b/chromium/net/net.gyp
new file mode 100644
index 00000000000..7086e3d362a
--- /dev/null
+++ b/chromium/net/net.gyp
@@ -0,0 +1,2927 @@
+# Copyright 2013 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+
+ 'linux_link_kerberos%': 0,
+ 'use_tracing_cache_backend%': 0,
+ 'conditions': [
+ ['chromeos==1 or OS=="android" or OS=="ios"', {
+ # Disable Kerberos on ChromeOS, Android and iOS, at least for now.
+ # It needs configuration (krb5.conf and so on).
+ 'use_kerberos%': 0,
+ }, { # chromeos == 0
+ 'use_kerberos%': 1,
+ }],
+ ['OS=="android" and target_arch != "ia32"', {
+ # The way the cache uses mmap() is inefficient on some Android devices.
+ # If this flag is set, we hackily avoid using mmap() in the disk cache.
+ # We are pretty confident that mmap-ing the index would not hurt any
+ # existing x86 android devices, but we cannot be so sure about the
+ # variety of ARM devices. So enable it for x86 only for now.
+ 'posix_avoid_mmap%': 1,
+ }, {
+ 'posix_avoid_mmap%': 0,
+ }],
+ ['OS=="ios"', {
+ # Websockets and socket stream are not used on iOS.
+ 'enable_websockets%': 0,
+ # iOS does not use V8.
+ 'use_v8_in_net%': 0,
+ 'enable_built_in_dns%': 0,
+ }, {
+ 'enable_websockets%': 1,
+ 'use_v8_in_net%': 1,
+ 'enable_built_in_dns%': 1,
+ }],
+ ],
+ },
+ 'includes': [
+ '../build/win_precompile.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'net',
+ 'type': '<(component)',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../crypto/crypto.gyp:crypto',
+ '../sdch/sdch.gyp:sdch',
+ '../third_party/icu/icu.gyp:icui18n',
+ '../third_party/icu/icu.gyp:icuuc',
+ '../third_party/zlib/zlib.gyp:zlib',
+ '../url/url.gyp:url_lib',
+ 'net_resources',
+ ],
+ 'sources': [
+ 'android/cert_verify_result_android.h',
+ 'android/cert_verify_result_android_list.h',
+ 'android/gurl_utils.cc',
+ 'android/gurl_utils.h',
+ 'android/keystore.cc',
+ 'android/keystore.h',
+ 'android/keystore_openssl.cc',
+ 'android/keystore_openssl.h',
+ 'android/net_jni_registrar.cc',
+ 'android/net_jni_registrar.h',
+ 'android/network_change_notifier_android.cc',
+ 'android/network_change_notifier_android.h',
+ 'android/network_change_notifier_delegate_android.cc',
+ 'android/network_change_notifier_delegate_android.h',
+ 'android/network_change_notifier_factory_android.cc',
+ 'android/network_change_notifier_factory_android.h',
+ 'android/network_library.cc',
+ 'android/network_library.h',
+ 'base/address_family.h',
+ 'base/address_list.cc',
+ 'base/address_list.h',
+ 'base/address_tracker_linux.cc',
+ 'base/address_tracker_linux.h',
+ 'base/auth.cc',
+ 'base/auth.h',
+ 'base/backoff_entry.cc',
+ 'base/backoff_entry.h',
+ 'base/bandwidth_metrics.cc',
+ 'base/bandwidth_metrics.h',
+ 'base/big_endian.cc',
+ 'base/big_endian.h',
+ 'base/cache_type.h',
+ 'base/completion_callback.h',
+ 'base/connection_type_histograms.cc',
+ 'base/connection_type_histograms.h',
+ 'base/crypto_module.h',
+ 'base/crypto_module_nss.cc',
+ 'base/crypto_module_openssl.cc',
+ 'base/data_url.cc',
+ 'base/data_url.h',
+ 'base/directory_lister.cc',
+ 'base/directory_lister.h',
+ 'base/dns_reloader.cc',
+ 'base/dns_reloader.h',
+ 'base/dns_util.cc',
+ 'base/dns_util.h',
+ 'base/escape.cc',
+ 'base/escape.h',
+ 'base/expiring_cache.h',
+ 'base/file_stream.cc',
+ 'base/file_stream.h',
+ 'base/file_stream_context.cc',
+ 'base/file_stream_context.h',
+ 'base/file_stream_context_posix.cc',
+ 'base/file_stream_context_win.cc',
+ 'base/file_stream_metrics.cc',
+ 'base/file_stream_metrics.h',
+ 'base/file_stream_metrics_posix.cc',
+ 'base/file_stream_metrics_win.cc',
+ 'base/file_stream_net_log_parameters.cc',
+ 'base/file_stream_net_log_parameters.h',
+ 'base/file_stream_whence.h',
+ 'base/filter.cc',
+ 'base/filter.h',
+ 'base/int128.cc',
+ 'base/int128.h',
+ 'base/gzip_filter.cc',
+ 'base/gzip_filter.h',
+ 'base/gzip_header.cc',
+ 'base/gzip_header.h',
+ 'base/hash_value.cc',
+ 'base/hash_value.h',
+ 'base/host_mapping_rules.cc',
+ 'base/host_mapping_rules.h',
+ 'base/host_port_pair.cc',
+ 'base/host_port_pair.h',
+ 'base/io_buffer.cc',
+ 'base/io_buffer.h',
+ 'base/iovec.h',
+ 'base/ip_endpoint.cc',
+ 'base/ip_endpoint.h',
+ 'base/keygen_handler.cc',
+ 'base/keygen_handler.h',
+ 'base/keygen_handler_mac.cc',
+ 'base/keygen_handler_nss.cc',
+ 'base/keygen_handler_openssl.cc',
+ 'base/keygen_handler_win.cc',
+ 'base/linked_hash_map.h',
+ 'base/load_flags.h',
+ 'base/load_flags_list.h',
+ 'base/load_states.h',
+ 'base/load_states_list.h',
+ 'base/load_timing_info.cc',
+ 'base/load_timing_info.h',
+ 'base/mime_sniffer.cc',
+ 'base/mime_sniffer.h',
+ 'base/mime_util.cc',
+ 'base/mime_util.h',
+ 'base/net_error_list.h',
+ 'base/net_errors.cc',
+ 'base/net_errors.h',
+ 'base/net_errors_posix.cc',
+ 'base/net_errors_win.cc',
+ 'base/net_export.h',
+ 'base/net_log.cc',
+ 'base/net_log.h',
+ 'base/net_log_logger.cc',
+ 'base/net_log_logger.h',
+ 'base/net_log_event_type_list.h',
+ 'base/net_log_source_type_list.h',
+ 'base/net_module.cc',
+ 'base/net_module.h',
+ 'base/net_util.cc',
+ 'base/net_util.h',
+ 'base/net_util_posix.cc',
+ 'base/net_util_win.cc',
+ 'base/network_change_notifier.cc',
+ 'base/network_change_notifier.h',
+ 'base/network_change_notifier_factory.h',
+ 'base/network_change_notifier_linux.cc',
+ 'base/network_change_notifier_linux.h',
+ 'base/network_change_notifier_mac.cc',
+ 'base/network_change_notifier_mac.h',
+ 'base/network_change_notifier_win.cc',
+ 'base/network_change_notifier_win.h',
+ 'base/network_config_watcher_mac.cc',
+ 'base/network_config_watcher_mac.h',
+ 'base/network_delegate.cc',
+ 'base/network_delegate.h',
+ 'base/network_time_notifier.cc',
+ 'base/network_time_notifier.h',
+ 'base/nss_memio.c',
+ 'base/nss_memio.h',
+ 'base/openssl_private_key_store.h',
+ 'base/openssl_private_key_store_android.cc',
+ 'base/openssl_private_key_store_memory.cc',
+ 'base/platform_mime_util.h',
+ # TODO(tc): gnome-vfs? xdgmime? /etc/mime.types?
+ 'base/platform_mime_util_linux.cc',
+ 'base/platform_mime_util_mac.mm',
+ 'base/platform_mime_util_win.cc',
+ 'base/prioritized_dispatcher.cc',
+ 'base/prioritized_dispatcher.h',
+ 'base/priority_queue.h',
+ 'base/rand_callback.h',
+ 'base/registry_controlled_domains/registry_controlled_domain.cc',
+ 'base/registry_controlled_domains/registry_controlled_domain.h',
+ 'base/request_priority.h',
+ 'base/sdch_filter.cc',
+ 'base/sdch_filter.h',
+ 'base/sdch_manager.cc',
+ 'base/sdch_manager.h',
+ 'base/static_cookie_policy.cc',
+ 'base/static_cookie_policy.h',
+ 'base/sys_addrinfo.h',
+ 'base/test_data_stream.cc',
+ 'base/test_data_stream.h',
+ 'base/upload_bytes_element_reader.cc',
+ 'base/upload_bytes_element_reader.h',
+ 'base/upload_data.cc',
+ 'base/upload_data.h',
+ 'base/upload_data_stream.cc',
+ 'base/upload_data_stream.h',
+ 'base/upload_element.cc',
+ 'base/upload_element.h',
+ 'base/upload_element_reader.cc',
+ 'base/upload_element_reader.h',
+ 'base/upload_file_element_reader.cc',
+ 'base/upload_file_element_reader.h',
+ 'base/upload_progress.h',
+ 'base/url_util.cc',
+ 'base/url_util.h',
+ 'base/winsock_init.cc',
+ 'base/winsock_init.h',
+ 'base/winsock_util.cc',
+ 'base/winsock_util.h',
+ 'base/zap.cc',
+ 'base/zap.h',
+ 'cert/asn1_util.cc',
+ 'cert/asn1_util.h',
+ 'cert/cert_database.cc',
+ 'cert/cert_database.h',
+ 'cert/cert_database_android.cc',
+ 'cert/cert_database_ios.cc',
+ 'cert/cert_database_mac.cc',
+ 'cert/cert_database_nss.cc',
+ 'cert/cert_database_openssl.cc',
+ 'cert/cert_database_win.cc',
+ 'cert/cert_status_flags.cc',
+ 'cert/cert_status_flags.h',
+ 'cert/cert_trust_anchor_provider.h',
+ 'cert/cert_verifier.cc',
+ 'cert/cert_verifier.h',
+ 'cert/cert_verify_proc.cc',
+ 'cert/cert_verify_proc.h',
+ 'cert/cert_verify_proc_android.cc',
+ 'cert/cert_verify_proc_android.h',
+ 'cert/cert_verify_proc_mac.cc',
+ 'cert/cert_verify_proc_mac.h',
+ 'cert/cert_verify_proc_nss.cc',
+ 'cert/cert_verify_proc_nss.h',
+ 'cert/cert_verify_proc_openssl.cc',
+ 'cert/cert_verify_proc_openssl.h',
+ 'cert/cert_verify_proc_win.cc',
+ 'cert/cert_verify_proc_win.h',
+ 'cert/cert_verify_result.cc',
+ 'cert/cert_verify_result.h',
+ 'cert/crl_set.cc',
+ 'cert/crl_set.h',
+ 'cert/ev_root_ca_metadata.cc',
+ 'cert/ev_root_ca_metadata.h',
+ 'cert/jwk_serializer_nss.cc',
+ 'cert/jwk_serializer_openssl.cc',
+ 'cert/jwk_serializer.h',
+ 'cert/multi_threaded_cert_verifier.cc',
+ 'cert/multi_threaded_cert_verifier.h',
+ 'cert/nss_cert_database.cc',
+ 'cert/nss_cert_database.h',
+ 'cert/pem_tokenizer.cc',
+ 'cert/pem_tokenizer.h',
+ 'cert/single_request_cert_verifier.cc',
+ 'cert/single_request_cert_verifier.h',
+ 'cert/test_root_certs.cc',
+ 'cert/test_root_certs.h',
+ 'cert/test_root_certs_mac.cc',
+ 'cert/test_root_certs_nss.cc',
+ 'cert/test_root_certs_openssl.cc',
+ 'cert/test_root_certs_android.cc',
+ 'cert/test_root_certs_win.cc',
+ 'cert/x509_cert_types.cc',
+ 'cert/x509_cert_types.h',
+ 'cert/x509_cert_types_mac.cc',
+ 'cert/x509_cert_types_win.cc',
+ 'cert/x509_certificate.cc',
+ 'cert/x509_certificate.h',
+ 'cert/x509_certificate_ios.cc',
+ 'cert/x509_certificate_mac.cc',
+ 'cert/x509_certificate_net_log_param.cc',
+ 'cert/x509_certificate_net_log_param.h',
+ 'cert/x509_certificate_nss.cc',
+ 'cert/x509_certificate_openssl.cc',
+ 'cert/x509_certificate_win.cc',
+ 'cert/x509_util.h',
+ 'cert/x509_util.cc',
+ 'cert/x509_util_ios.cc',
+ 'cert/x509_util_ios.h',
+ 'cert/x509_util_mac.cc',
+ 'cert/x509_util_mac.h',
+ 'cert/x509_util_nss.cc',
+ 'cert/x509_util_nss.h',
+ 'cert/x509_util_openssl.cc',
+ 'cert/x509_util_openssl.h',
+ 'cookies/canonical_cookie.cc',
+ 'cookies/canonical_cookie.h',
+ 'cookies/cookie_constants.cc',
+ 'cookies/cookie_constants.h',
+ 'cookies/cookie_monster.cc',
+ 'cookies/cookie_monster.h',
+ 'cookies/cookie_options.h',
+ 'cookies/cookie_store.cc',
+ 'cookies/cookie_store.h',
+ 'cookies/cookie_util.cc',
+ 'cookies/cookie_util.h',
+ 'cookies/parsed_cookie.cc',
+ 'cookies/parsed_cookie.h',
+ 'disk_cache/addr.cc',
+ 'disk_cache/addr.h',
+ 'disk_cache/backend_impl.cc',
+ 'disk_cache/backend_impl.h',
+ 'disk_cache/bitmap.cc',
+ 'disk_cache/bitmap.h',
+ 'disk_cache/block_files.cc',
+ 'disk_cache/block_files.h',
+ 'disk_cache/cache_creator.cc',
+ 'disk_cache/cache_util.h',
+ 'disk_cache/cache_util.cc',
+ 'disk_cache/cache_util_posix.cc',
+ 'disk_cache/cache_util_win.cc',
+ 'disk_cache/disk_cache.h',
+ 'disk_cache/disk_format.cc',
+ 'disk_cache/disk_format.h',
+ 'disk_cache/disk_format_base.h',
+ 'disk_cache/entry_impl.cc',
+ 'disk_cache/entry_impl.h',
+ 'disk_cache/errors.h',
+ 'disk_cache/eviction.cc',
+ 'disk_cache/eviction.h',
+ 'disk_cache/experiments.h',
+ 'disk_cache/file.cc',
+ 'disk_cache/file.h',
+ 'disk_cache/file_block.h',
+ 'disk_cache/file_lock.cc',
+ 'disk_cache/file_lock.h',
+ 'disk_cache/file_posix.cc',
+ 'disk_cache/file_win.cc',
+ 'disk_cache/histogram_macros.h',
+ 'disk_cache/in_flight_backend_io.cc',
+ 'disk_cache/in_flight_backend_io.h',
+ 'disk_cache/in_flight_io.cc',
+ 'disk_cache/in_flight_io.h',
+ 'disk_cache/mapped_file.h',
+ 'disk_cache/mapped_file_posix.cc',
+ 'disk_cache/mapped_file_avoid_mmap_posix.cc',
+ 'disk_cache/mapped_file_win.cc',
+ 'disk_cache/mem_backend_impl.cc',
+ 'disk_cache/mem_backend_impl.h',
+ 'disk_cache/mem_entry_impl.cc',
+ 'disk_cache/mem_entry_impl.h',
+ 'disk_cache/mem_rankings.cc',
+ 'disk_cache/mem_rankings.h',
+ 'disk_cache/net_log_parameters.cc',
+ 'disk_cache/net_log_parameters.h',
+ 'disk_cache/rankings.cc',
+ 'disk_cache/rankings.h',
+ 'disk_cache/sparse_control.cc',
+ 'disk_cache/sparse_control.h',
+ 'disk_cache/stats.cc',
+ 'disk_cache/stats.h',
+ 'disk_cache/stats_histogram.cc',
+ 'disk_cache/stats_histogram.h',
+ 'disk_cache/storage_block-inl.h',
+ 'disk_cache/storage_block.h',
+ 'disk_cache/stress_support.h',
+ 'disk_cache/trace.cc',
+ 'disk_cache/trace.h',
+ 'disk_cache/tracing_cache_backend.cc',
+ 'disk_cache/tracing_cache_backend.h',
+ 'disk_cache/simple/simple_backend_impl.cc',
+ 'disk_cache/simple/simple_backend_impl.h',
+ 'disk_cache/simple/simple_entry_format.cc',
+ 'disk_cache/simple/simple_entry_format.h',
+ 'disk_cache/simple/simple_entry_impl.cc',
+ 'disk_cache/simple/simple_entry_impl.h',
+ 'disk_cache/simple/simple_entry_operation.cc',
+ 'disk_cache/simple/simple_entry_operation.h',
+ 'disk_cache/simple/simple_index.cc',
+ 'disk_cache/simple/simple_index.h',
+ 'disk_cache/simple/simple_index_file.cc',
+ 'disk_cache/simple/simple_index_file.h',
+ 'disk_cache/simple/simple_net_log_parameters.cc',
+ 'disk_cache/simple/simple_net_log_parameters.h',
+ 'disk_cache/simple/simple_synchronous_entry.cc',
+ 'disk_cache/simple/simple_synchronous_entry.h',
+ 'disk_cache/simple/simple_util.cc',
+ 'disk_cache/simple/simple_util.h',
+ 'disk_cache/flash/flash_entry_impl.cc',
+ 'disk_cache/flash/flash_entry_impl.h',
+ 'disk_cache/flash/format.h',
+ 'disk_cache/flash/internal_entry.cc',
+ 'disk_cache/flash/internal_entry.h',
+ 'disk_cache/flash/log_store.cc',
+ 'disk_cache/flash/log_store.h',
+ 'disk_cache/flash/log_store_entry.cc',
+ 'disk_cache/flash/log_store_entry.h',
+ 'disk_cache/flash/segment.cc',
+ 'disk_cache/flash/segment.h',
+ 'disk_cache/flash/storage.cc',
+ 'disk_cache/flash/storage.h',
+ 'disk_cache/v3/disk_format_v3.h',
+ 'dns/address_sorter.h',
+ 'dns/address_sorter_posix.cc',
+ 'dns/address_sorter_posix.h',
+ 'dns/address_sorter_win.cc',
+ 'dns/dns_client.cc',
+ 'dns/dns_client.h',
+ 'dns/dns_config_service.cc',
+ 'dns/dns_config_service.h',
+ 'dns/dns_config_service_posix.cc',
+ 'dns/dns_config_service_posix.h',
+ 'dns/dns_config_service_win.cc',
+ 'dns/dns_config_service_win.h',
+ 'dns/dns_hosts.cc',
+ 'dns/dns_hosts.h',
+ 'dns/dns_protocol.h',
+ 'dns/dns_query.cc',
+ 'dns/dns_query.h',
+ 'dns/dns_response.cc',
+ 'dns/dns_response.h',
+ 'dns/dns_session.cc',
+ 'dns/dns_session.h',
+ 'dns/dns_socket_pool.cc',
+ 'dns/dns_socket_pool.h',
+ 'dns/dns_transaction.cc',
+ 'dns/dns_transaction.h',
+ 'dns/host_cache.cc',
+ 'dns/host_cache.h',
+ 'dns/host_resolver.cc',
+ 'dns/host_resolver.h',
+ 'dns/host_resolver_impl.cc',
+ 'dns/host_resolver_impl.h',
+ 'dns/host_resolver_proc.cc',
+ 'dns/host_resolver_proc.h',
+ 'dns/mapped_host_resolver.cc',
+ 'dns/mapped_host_resolver.h',
+ 'dns/mdns_cache.cc',
+ 'dns/mdns_cache.h',
+ 'dns/mdns_client.cc',
+ 'dns/mdns_client.h',
+ 'dns/mdns_client_impl.cc',
+ 'dns/mdns_client_impl.h',
+ 'dns/notify_watcher_mac.cc',
+ 'dns/notify_watcher_mac.h',
+ 'dns/record_parsed.cc',
+ 'dns/record_parsed.h',
+ 'dns/record_rdata.cc',
+ 'dns/record_rdata.h',
+ 'dns/serial_worker.cc',
+ 'dns/serial_worker.h',
+ 'dns/single_request_host_resolver.cc',
+ 'dns/single_request_host_resolver.h',
+ 'ftp/ftp_auth_cache.cc',
+ 'ftp/ftp_auth_cache.h',
+ 'ftp/ftp_ctrl_response_buffer.cc',
+ 'ftp/ftp_ctrl_response_buffer.h',
+ 'ftp/ftp_directory_listing_parser.cc',
+ 'ftp/ftp_directory_listing_parser.h',
+ 'ftp/ftp_directory_listing_parser_ls.cc',
+ 'ftp/ftp_directory_listing_parser_ls.h',
+ 'ftp/ftp_directory_listing_parser_netware.cc',
+ 'ftp/ftp_directory_listing_parser_netware.h',
+ 'ftp/ftp_directory_listing_parser_os2.cc',
+ 'ftp/ftp_directory_listing_parser_os2.h',
+ 'ftp/ftp_directory_listing_parser_vms.cc',
+ 'ftp/ftp_directory_listing_parser_vms.h',
+ 'ftp/ftp_directory_listing_parser_windows.cc',
+ 'ftp/ftp_directory_listing_parser_windows.h',
+ 'ftp/ftp_network_layer.cc',
+ 'ftp/ftp_network_layer.h',
+ 'ftp/ftp_network_session.cc',
+ 'ftp/ftp_network_session.h',
+ 'ftp/ftp_network_transaction.cc',
+ 'ftp/ftp_network_transaction.h',
+ 'ftp/ftp_request_info.h',
+ 'ftp/ftp_response_info.cc',
+ 'ftp/ftp_response_info.h',
+ 'ftp/ftp_server_type_histograms.cc',
+ 'ftp/ftp_server_type_histograms.h',
+ 'ftp/ftp_transaction.h',
+ 'ftp/ftp_transaction_factory.h',
+ 'ftp/ftp_util.cc',
+ 'ftp/ftp_util.h',
+ 'http/des.cc',
+ 'http/des.h',
+ 'http/http_atom_list.h',
+ 'http/http_auth.cc',
+ 'http/http_auth.h',
+ 'http/http_auth_cache.cc',
+ 'http/http_auth_cache.h',
+ 'http/http_auth_controller.cc',
+ 'http/http_auth_controller.h',
+ 'http/http_auth_filter.cc',
+ 'http/http_auth_filter.h',
+ 'http/http_auth_filter_win.h',
+ 'http/http_auth_gssapi_posix.cc',
+ 'http/http_auth_gssapi_posix.h',
+ 'http/http_auth_handler.cc',
+ 'http/http_auth_handler.h',
+ 'http/http_auth_handler_basic.cc',
+ 'http/http_auth_handler_basic.h',
+ 'http/http_auth_handler_digest.cc',
+ 'http/http_auth_handler_digest.h',
+ 'http/http_auth_handler_factory.cc',
+ 'http/http_auth_handler_factory.h',
+ 'http/http_auth_handler_negotiate.cc',
+ 'http/http_auth_handler_negotiate.h',
+ 'http/http_auth_handler_ntlm.cc',
+ 'http/http_auth_handler_ntlm.h',
+ 'http/http_auth_handler_ntlm_portable.cc',
+ 'http/http_auth_handler_ntlm_win.cc',
+ 'http/http_auth_sspi_win.cc',
+ 'http/http_auth_sspi_win.h',
+ 'http/http_basic_stream.cc',
+ 'http/http_basic_stream.h',
+ 'http/http_byte_range.cc',
+ 'http/http_byte_range.h',
+ 'http/http_cache.cc',
+ 'http/http_cache.h',
+ 'http/http_cache_transaction.cc',
+ 'http/http_cache_transaction.h',
+ 'http/http_content_disposition.cc',
+ 'http/http_content_disposition.h',
+ 'http/http_chunked_decoder.cc',
+ 'http/http_chunked_decoder.h',
+ 'http/http_network_layer.cc',
+ 'http/http_network_layer.h',
+ 'http/http_network_session.cc',
+ 'http/http_network_session.h',
+ 'http/http_network_session_peer.cc',
+ 'http/http_network_session_peer.h',
+ 'http/http_network_transaction.cc',
+ 'http/http_network_transaction.h',
+ 'http/http_pipelined_connection.h',
+ 'http/http_pipelined_connection_impl.cc',
+ 'http/http_pipelined_connection_impl.h',
+ 'http/http_pipelined_host.cc',
+ 'http/http_pipelined_host.h',
+ 'http/http_pipelined_host_capability.h',
+ 'http/http_pipelined_host_forced.cc',
+ 'http/http_pipelined_host_forced.h',
+ 'http/http_pipelined_host_impl.cc',
+ 'http/http_pipelined_host_impl.h',
+ 'http/http_pipelined_host_pool.cc',
+ 'http/http_pipelined_host_pool.h',
+ 'http/http_pipelined_stream.cc',
+ 'http/http_pipelined_stream.h',
+ 'http/http_proxy_client_socket.cc',
+ 'http/http_proxy_client_socket.h',
+ 'http/http_proxy_client_socket_pool.cc',
+ 'http/http_proxy_client_socket_pool.h',
+ 'http/http_request_headers.cc',
+ 'http/http_request_headers.h',
+ 'http/http_request_info.cc',
+ 'http/http_request_info.h',
+ 'http/http_response_body_drainer.cc',
+ 'http/http_response_body_drainer.h',
+ 'http/http_response_headers.cc',
+ 'http/http_response_headers.h',
+ 'http/http_response_info.cc',
+ 'http/http_response_info.h',
+ 'http/http_security_headers.cc',
+ 'http/http_security_headers.h',
+ 'http/http_server_properties.cc',
+ 'http/http_server_properties.h',
+ 'http/http_server_properties_impl.cc',
+ 'http/http_server_properties_impl.h',
+ 'http/http_status_code.cc',
+ 'http/http_status_code.h',
+ 'http/http_stream.h',
+ 'http/http_stream_base.h',
+ 'http/http_stream_factory.cc',
+ 'http/http_stream_factory.h',
+ 'http/http_stream_factory_impl.cc',
+ 'http/http_stream_factory_impl.h',
+ 'http/http_stream_factory_impl_job.cc',
+ 'http/http_stream_factory_impl_job.h',
+ 'http/http_stream_factory_impl_request.cc',
+ 'http/http_stream_factory_impl_request.h',
+ 'http/http_stream_parser.cc',
+ 'http/http_stream_parser.h',
+ 'http/http_transaction.h',
+ 'http/http_transaction_delegate.h',
+ 'http/http_transaction_factory.h',
+ 'http/http_util.cc',
+ 'http/http_util.h',
+ 'http/http_util_icu.cc',
+ 'http/http_vary_data.cc',
+ 'http/http_vary_data.h',
+ 'http/http_version.h',
+ 'http/md4.cc',
+ 'http/md4.h',
+ 'http/partial_data.cc',
+ 'http/partial_data.h',
+ 'http/proxy_client_socket.h',
+ 'http/proxy_client_socket.cc',
+ 'http/proxy_connect_redirect_http_stream.h',
+ 'http/proxy_connect_redirect_http_stream.cc',
+ 'http/transport_security_state.cc',
+ 'http/transport_security_state.h',
+ 'http/transport_security_state_static.h',
+ 'http/url_security_manager.cc',
+ 'http/url_security_manager.h',
+ 'http/url_security_manager_posix.cc',
+ 'http/url_security_manager_win.cc',
+ 'ocsp/nss_ocsp.cc',
+ 'ocsp/nss_ocsp.h',
+ 'proxy/dhcp_proxy_script_adapter_fetcher_win.cc',
+ 'proxy/dhcp_proxy_script_adapter_fetcher_win.h',
+ 'proxy/dhcp_proxy_script_fetcher.cc',
+ 'proxy/dhcp_proxy_script_fetcher.h',
+ 'proxy/dhcp_proxy_script_fetcher_factory.cc',
+ 'proxy/dhcp_proxy_script_fetcher_factory.h',
+ 'proxy/dhcp_proxy_script_fetcher_win.cc',
+ 'proxy/dhcp_proxy_script_fetcher_win.h',
+ 'proxy/dhcpcsvc_init_win.cc',
+ 'proxy/dhcpcsvc_init_win.h',
+ 'proxy/multi_threaded_proxy_resolver.cc',
+ 'proxy/multi_threaded_proxy_resolver.h',
+ 'proxy/network_delegate_error_observer.cc',
+ 'proxy/network_delegate_error_observer.h',
+ 'proxy/polling_proxy_config_service.cc',
+ 'proxy/polling_proxy_config_service.h',
+ 'proxy/proxy_bypass_rules.cc',
+ 'proxy/proxy_bypass_rules.h',
+ 'proxy/proxy_config.cc',
+ 'proxy/proxy_config.h',
+ 'proxy/proxy_config_service.h',
+ 'proxy/proxy_config_service_android.cc',
+ 'proxy/proxy_config_service_android.h',
+ 'proxy/proxy_config_service_fixed.cc',
+ 'proxy/proxy_config_service_fixed.h',
+ 'proxy/proxy_config_service_ios.cc',
+ 'proxy/proxy_config_service_ios.h',
+ 'proxy/proxy_config_service_linux.cc',
+ 'proxy/proxy_config_service_linux.h',
+ 'proxy/proxy_config_service_mac.cc',
+ 'proxy/proxy_config_service_mac.h',
+ 'proxy/proxy_config_service_win.cc',
+ 'proxy/proxy_config_service_win.h',
+ 'proxy/proxy_config_source.cc',
+ 'proxy/proxy_config_source.h',
+ 'proxy/proxy_info.cc',
+ 'proxy/proxy_info.h',
+ 'proxy/proxy_list.cc',
+ 'proxy/proxy_list.h',
+ 'proxy/proxy_resolver.h',
+ 'proxy/proxy_resolver_error_observer.h',
+ 'proxy/proxy_resolver_mac.cc',
+ 'proxy/proxy_resolver_mac.h',
+ 'proxy/proxy_resolver_script.h',
+ 'proxy/proxy_resolver_script_data.cc',
+ 'proxy/proxy_resolver_script_data.h',
+ 'proxy/proxy_resolver_winhttp.cc',
+ 'proxy/proxy_resolver_winhttp.h',
+ 'proxy/proxy_retry_info.h',
+ 'proxy/proxy_script_decider.cc',
+ 'proxy/proxy_script_decider.h',
+ 'proxy/proxy_script_fetcher.h',
+ 'proxy/proxy_script_fetcher_impl.cc',
+ 'proxy/proxy_script_fetcher_impl.h',
+ 'proxy/proxy_server.cc',
+ 'proxy/proxy_server.h',
+ 'proxy/proxy_server_mac.cc',
+ 'proxy/proxy_service.cc',
+ 'proxy/proxy_service.h',
+ 'quic/blocked_list.h',
+ 'quic/congestion_control/available_channel_estimator.cc',
+ 'quic/congestion_control/available_channel_estimator.h',
+ 'quic/congestion_control/channel_estimator.cc',
+ 'quic/congestion_control/channel_estimator.h',
+ 'quic/congestion_control/cube_root.cc',
+ 'quic/congestion_control/cube_root.h',
+ 'quic/congestion_control/cubic.cc',
+ 'quic/congestion_control/cubic.h',
+ 'quic/congestion_control/fix_rate_receiver.cc',
+ 'quic/congestion_control/fix_rate_receiver.h',
+ 'quic/congestion_control/fix_rate_sender.cc',
+ 'quic/congestion_control/fix_rate_sender.h',
+ 'quic/congestion_control/hybrid_slow_start.cc',
+ 'quic/congestion_control/hybrid_slow_start.h',
+ 'quic/congestion_control/inter_arrival_bitrate_ramp_up.cc',
+ 'quic/congestion_control/inter_arrival_bitrate_ramp_up.h',
+ 'quic/congestion_control/inter_arrival_overuse_detector.cc',
+ 'quic/congestion_control/inter_arrival_overuse_detector.h',
+ 'quic/congestion_control/inter_arrival_probe.cc',
+ 'quic/congestion_control/inter_arrival_probe.h',
+ 'quic/congestion_control/inter_arrival_receiver.cc',
+ 'quic/congestion_control/inter_arrival_receiver.h',
+ 'quic/congestion_control/inter_arrival_sender.cc',
+ 'quic/congestion_control/inter_arrival_sender.h',
+ 'quic/congestion_control/inter_arrival_state_machine.cc',
+ 'quic/congestion_control/inter_arrival_state_machine.h',
+ 'quic/congestion_control/leaky_bucket.cc',
+ 'quic/congestion_control/leaky_bucket.h',
+ 'quic/congestion_control/paced_sender.cc',
+ 'quic/congestion_control/paced_sender.h',
+ 'quic/congestion_control/quic_congestion_manager.cc',
+ 'quic/congestion_control/quic_congestion_manager.h',
+ 'quic/congestion_control/quic_max_sized_map.h',
+ 'quic/congestion_control/receive_algorithm_interface.cc',
+ 'quic/congestion_control/receive_algorithm_interface.h',
+ 'quic/congestion_control/send_algorithm_interface.cc',
+ 'quic/congestion_control/send_algorithm_interface.h',
+ 'quic/congestion_control/tcp_cubic_sender.cc',
+ 'quic/congestion_control/tcp_cubic_sender.h',
+ 'quic/congestion_control/tcp_receiver.cc',
+ 'quic/congestion_control/tcp_receiver.h',
+ 'quic/crypto/aes_128_gcm_12_decrypter.h',
+ 'quic/crypto/aes_128_gcm_12_decrypter_nss.cc',
+ 'quic/crypto/aes_128_gcm_12_decrypter_openssl.cc',
+ 'quic/crypto/aes_128_gcm_12_encrypter.h',
+ 'quic/crypto/aes_128_gcm_12_encrypter_nss.cc',
+ 'quic/crypto/aes_128_gcm_12_encrypter_openssl.cc',
+ 'quic/crypto/cert_compressor.cc',
+ 'quic/crypto/cert_compressor.h',
+ 'quic/crypto/channel_id.cc',
+ 'quic/crypto/channel_id.h',
+ 'quic/crypto/channel_id_nss.cc',
+ 'quic/crypto/channel_id_openssl.cc',
+ 'quic/crypto/common_cert_set.cc',
+ 'quic/crypto/common_cert_set.h',
+ 'quic/crypto/crypto_framer.cc',
+ 'quic/crypto/crypto_framer.h',
+ 'quic/crypto/crypto_handshake.cc',
+ 'quic/crypto/crypto_handshake.h',
+ 'quic/crypto/crypto_protocol.h',
+ 'quic/crypto/crypto_secret_boxer.cc',
+ 'quic/crypto/crypto_secret_boxer.h',
+ 'quic/crypto/crypto_server_config.cc',
+ 'quic/crypto/crypto_server_config.h',
+ 'quic/crypto/crypto_server_config_protobuf.cc',
+ 'quic/crypto/crypto_server_config_protobuf.h',
+ 'quic/crypto/crypto_utils.cc',
+ 'quic/crypto/crypto_utils.h',
+ 'quic/crypto/curve25519_key_exchange.cc',
+ 'quic/crypto/curve25519_key_exchange.h',
+ 'quic/crypto/ephemeral_key_source.h',
+ 'quic/crypto/key_exchange.h',
+ 'quic/crypto/null_decrypter.cc',
+ 'quic/crypto/null_decrypter.h',
+ 'quic/crypto/null_encrypter.cc',
+ 'quic/crypto/null_encrypter.h',
+ 'quic/crypto/p256_key_exchange.h',
+ 'quic/crypto/p256_key_exchange_nss.cc',
+ 'quic/crypto/p256_key_exchange_openssl.cc',
+ 'quic/crypto/proof_source.h',
+ 'quic/crypto/proof_source_chromium.cc',
+ 'quic/crypto/proof_source_chromium.h',
+ 'quic/crypto/proof_verifier.cc',
+ 'quic/crypto/proof_verifier_chromium.cc',
+ 'quic/crypto/proof_verifier_chromium.h',
+ 'quic/crypto/quic_decrypter.cc',
+ 'quic/crypto/quic_decrypter.h',
+ 'quic/crypto/quic_encrypter.cc',
+ 'quic/crypto/quic_encrypter.h',
+ 'quic/crypto/quic_random.cc',
+ 'quic/crypto/quic_random.h',
+ 'quic/crypto/scoped_evp_cipher_ctx.cc',
+ 'quic/crypto/scoped_evp_cipher_ctx.h',
+ 'quic/crypto/strike_register.cc',
+ 'quic/crypto/strike_register.h',
+ 'quic/crypto/source_address_token.cc',
+ 'quic/crypto/source_address_token.h',
+ 'quic/quic_alarm.cc',
+ 'quic/quic_alarm.h',
+ 'quic/quic_bandwidth.cc',
+ 'quic/quic_bandwidth.h',
+ 'quic/quic_blocked_writer_interface.h',
+ 'quic/quic_client_session.cc',
+ 'quic/quic_client_session.h',
+ 'quic/quic_config.cc',
+ 'quic/quic_config.h',
+ 'quic/quic_crypto_client_stream.cc',
+ 'quic/quic_crypto_client_stream.h',
+ 'quic/quic_crypto_client_stream_factory.h',
+ 'quic/quic_crypto_server_stream.cc',
+ 'quic/quic_crypto_server_stream.h',
+ 'quic/quic_crypto_stream.cc',
+ 'quic/quic_crypto_stream.h',
+ 'quic/quic_clock.cc',
+ 'quic/quic_clock.h',
+ 'quic/quic_connection.cc',
+ 'quic/quic_connection.h',
+ 'quic/quic_connection_helper.cc',
+ 'quic/quic_connection_helper.h',
+ 'quic/quic_connection_logger.cc',
+ 'quic/quic_connection_logger.h',
+ 'quic/quic_data_reader.cc',
+ 'quic/quic_data_reader.h',
+ 'quic/quic_data_writer.cc',
+ 'quic/quic_data_writer.h',
+ 'quic/quic_fec_group.cc',
+ 'quic/quic_fec_group.h',
+ 'quic/quic_framer.cc',
+ 'quic/quic_framer.h',
+ 'quic/quic_http_stream.cc',
+ 'quic/quic_http_stream.h',
+ 'quic/quic_packet_creator.cc',
+ 'quic/quic_packet_creator.h',
+ 'quic/quic_packet_generator.cc',
+ 'quic/quic_packet_generator.h',
+ 'quic/quic_protocol.cc',
+ 'quic/quic_protocol.h',
+ 'quic/quic_received_packet_manager.cc',
+ 'quic/quic_received_packet_manager.h',
+ 'quic/quic_reliable_client_stream.cc',
+ 'quic/quic_reliable_client_stream.h',
+ 'quic/quic_sent_entropy_manager.cc',
+ 'quic/quic_sent_entropy_manager.h',
+ 'quic/quic_session.cc',
+ 'quic/quic_session.h',
+ 'quic/quic_spdy_compressor.cc',
+ 'quic/quic_spdy_compressor.h',
+ 'quic/quic_spdy_decompressor.cc',
+ 'quic/quic_spdy_decompressor.h',
+ 'quic/quic_stats.cc',
+ 'quic/quic_stats.h',
+ 'quic/quic_stream_factory.cc',
+ 'quic/quic_stream_factory.h',
+ 'quic/quic_stream_sequencer.cc',
+ 'quic/quic_stream_sequencer.h',
+ 'quic/quic_time.cc',
+ 'quic/quic_time.h',
+ 'quic/quic_utils.cc',
+ 'quic/quic_utils.h',
+ 'quic/reliable_quic_stream.cc',
+ 'quic/reliable_quic_stream.h',
+ 'quic/spdy_utils.cc',
+ 'quic/spdy_utils.h',
+ 'socket/buffered_write_stream_socket.cc',
+ 'socket/buffered_write_stream_socket.h',
+ 'socket/client_socket_factory.cc',
+ 'socket/client_socket_factory.h',
+ 'socket/client_socket_handle.cc',
+ 'socket/client_socket_handle.h',
+ 'socket/client_socket_pool.cc',
+ 'socket/client_socket_pool.h',
+ 'socket/client_socket_pool_base.cc',
+ 'socket/client_socket_pool_base.h',
+ 'socket/client_socket_pool_histograms.cc',
+ 'socket/client_socket_pool_histograms.h',
+ 'socket/client_socket_pool_manager.cc',
+ 'socket/client_socket_pool_manager.h',
+ 'socket/client_socket_pool_manager_impl.cc',
+ 'socket/client_socket_pool_manager_impl.h',
+ 'socket/next_proto.h',
+ 'socket/nss_ssl_util.cc',
+ 'socket/nss_ssl_util.h',
+ 'socket/server_socket.h',
+ 'socket/socket_net_log_params.cc',
+ 'socket/socket_net_log_params.h',
+ 'socket/socket.h',
+ 'socket/socks5_client_socket.cc',
+ 'socket/socks5_client_socket.h',
+ 'socket/socks_client_socket.cc',
+ 'socket/socks_client_socket.h',
+ 'socket/socks_client_socket_pool.cc',
+ 'socket/socks_client_socket_pool.h',
+ 'socket/ssl_client_socket.cc',
+ 'socket/ssl_client_socket.h',
+ 'socket/ssl_client_socket_nss.cc',
+ 'socket/ssl_client_socket_nss.h',
+ 'socket/ssl_client_socket_openssl.cc',
+ 'socket/ssl_client_socket_openssl.h',
+ 'socket/ssl_client_socket_pool.cc',
+ 'socket/ssl_client_socket_pool.h',
+ 'socket/ssl_error_params.cc',
+ 'socket/ssl_error_params.h',
+ 'socket/ssl_server_socket.h',
+ 'socket/ssl_server_socket_nss.cc',
+ 'socket/ssl_server_socket_nss.h',
+ 'socket/ssl_server_socket_openssl.cc',
+ 'socket/ssl_socket.h',
+ 'socket/stream_listen_socket.cc',
+ 'socket/stream_listen_socket.h',
+ 'socket/stream_socket.cc',
+ 'socket/stream_socket.h',
+ 'socket/tcp_client_socket.cc',
+ 'socket/tcp_client_socket.h',
+ 'socket/tcp_client_socket_libevent.cc',
+ 'socket/tcp_client_socket_libevent.h',
+ 'socket/tcp_client_socket_win.cc',
+ 'socket/tcp_client_socket_win.h',
+ 'socket/tcp_listen_socket.cc',
+ 'socket/tcp_listen_socket.h',
+ 'socket/tcp_server_socket.h',
+ 'socket/tcp_server_socket_libevent.cc',
+ 'socket/tcp_server_socket_libevent.h',
+ 'socket/tcp_server_socket_win.cc',
+ 'socket/tcp_server_socket_win.h',
+ 'socket/transport_client_socket_pool.cc',
+ 'socket/transport_client_socket_pool.h',
+ 'socket/unix_domain_socket_posix.cc',
+ 'socket/unix_domain_socket_posix.h',
+ 'socket_stream/socket_stream.cc',
+ 'socket_stream/socket_stream.h',
+ 'socket_stream/socket_stream_job.cc',
+ 'socket_stream/socket_stream_job.h',
+ 'socket_stream/socket_stream_job_manager.cc',
+ 'socket_stream/socket_stream_job_manager.h',
+ 'socket_stream/socket_stream_metrics.cc',
+ 'socket_stream/socket_stream_metrics.h',
+ 'spdy/buffered_spdy_framer.cc',
+ 'spdy/buffered_spdy_framer.h',
+ 'spdy/spdy_bitmasks.h',
+ 'spdy/spdy_buffer.cc',
+ 'spdy/spdy_buffer.h',
+ 'spdy/spdy_buffer_producer.cc',
+ 'spdy/spdy_buffer_producer.h',
+ 'spdy/spdy_credential_builder.cc',
+ 'spdy/spdy_credential_builder.h',
+ 'spdy/spdy_credential_state.cc',
+ 'spdy/spdy_credential_state.h',
+ 'spdy/spdy_frame_builder.cc',
+ 'spdy/spdy_frame_builder.h',
+ 'spdy/spdy_frame_reader.cc',
+ 'spdy/spdy_frame_reader.h',
+ 'spdy/spdy_framer.cc',
+ 'spdy/spdy_framer.h',
+ 'spdy/spdy_header_block.cc',
+ 'spdy/spdy_header_block.h',
+ 'spdy/spdy_http_stream.cc',
+ 'spdy/spdy_http_stream.h',
+ 'spdy/spdy_http_utils.cc',
+ 'spdy/spdy_http_utils.h',
+ 'spdy/spdy_priority_forest.h',
+ 'spdy/spdy_protocol.cc',
+ 'spdy/spdy_protocol.h',
+ 'spdy/spdy_proxy_client_socket.cc',
+ 'spdy/spdy_proxy_client_socket.h',
+ 'spdy/spdy_read_queue.cc',
+ 'spdy/spdy_read_queue.h',
+ 'spdy/spdy_session.cc',
+ 'spdy/spdy_session.h',
+ 'spdy/spdy_session_key.cc',
+ 'spdy/spdy_session_key.h',
+ 'spdy/spdy_session_pool.cc',
+ 'spdy/spdy_session_pool.h',
+ 'spdy/spdy_stream.cc',
+ 'spdy/spdy_stream.h',
+ 'spdy/spdy_websocket_stream.cc',
+ 'spdy/spdy_websocket_stream.h',
+ 'spdy/spdy_write_queue.cc',
+ 'spdy/spdy_write_queue.h',
+ 'ssl/client_cert_store.h',
+ 'ssl/client_cert_store_impl.h',
+ 'ssl/client_cert_store_impl_mac.cc',
+ 'ssl/client_cert_store_impl_nss.cc',
+ 'ssl/client_cert_store_impl_win.cc',
+ 'ssl/default_server_bound_cert_store.cc',
+ 'ssl/default_server_bound_cert_store.h',
+ 'ssl/openssl_client_key_store.cc',
+ 'ssl/openssl_client_key_store.h',
+ 'ssl/server_bound_cert_service.cc',
+ 'ssl/server_bound_cert_service.h',
+ 'ssl/server_bound_cert_store.cc',
+ 'ssl/server_bound_cert_store.h',
+ 'ssl/ssl_cert_request_info.cc',
+ 'ssl/ssl_cert_request_info.h',
+ 'ssl/ssl_cipher_suite_names.cc',
+ 'ssl/ssl_cipher_suite_names.h',
+ 'ssl/ssl_client_auth_cache.cc',
+ 'ssl/ssl_client_auth_cache.h',
+ 'ssl/ssl_client_cert_type.h',
+ 'ssl/ssl_config_service.cc',
+ 'ssl/ssl_config_service.h',
+ 'ssl/ssl_config_service_defaults.cc',
+ 'ssl/ssl_config_service_defaults.h',
+ 'ssl/ssl_info.cc',
+ 'ssl/ssl_info.h',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.cpp',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.h',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.cpp',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.h',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.cpp',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.h',
+ 'udp/datagram_client_socket.h',
+ 'udp/datagram_server_socket.h',
+ 'udp/datagram_socket.h',
+ 'udp/udp_client_socket.cc',
+ 'udp/udp_client_socket.h',
+ 'udp/udp_net_log_parameters.cc',
+ 'udp/udp_net_log_parameters.h',
+ 'udp/udp_server_socket.cc',
+ 'udp/udp_server_socket.h',
+ 'udp/udp_socket.h',
+ 'udp/udp_socket_libevent.cc',
+ 'udp/udp_socket_libevent.h',
+ 'udp/udp_socket_win.cc',
+ 'udp/udp_socket_win.h',
+ 'url_request/data_protocol_handler.cc',
+ 'url_request/data_protocol_handler.h',
+ 'url_request/file_protocol_handler.cc',
+ 'url_request/file_protocol_handler.h',
+ 'url_request/fraudulent_certificate_reporter.h',
+ 'url_request/ftp_protocol_handler.cc',
+ 'url_request/ftp_protocol_handler.h',
+ 'url_request/http_user_agent_settings.h',
+ 'url_request/protocol_intercept_job_factory.cc',
+ 'url_request/protocol_intercept_job_factory.h',
+ 'url_request/static_http_user_agent_settings.cc',
+ 'url_request/static_http_user_agent_settings.h',
+ 'url_request/url_fetcher.cc',
+ 'url_request/url_fetcher.h',
+ 'url_request/url_fetcher_core.cc',
+ 'url_request/url_fetcher_core.h',
+ 'url_request/url_fetcher_delegate.cc',
+ 'url_request/url_fetcher_delegate.h',
+ 'url_request/url_fetcher_factory.h',
+ 'url_request/url_fetcher_impl.cc',
+ 'url_request/url_fetcher_impl.h',
+ 'url_request/url_fetcher_response_writer.cc',
+ 'url_request/url_fetcher_response_writer.h',
+ 'url_request/url_request.cc',
+ 'url_request/url_request.h',
+ 'url_request/url_request_about_job.cc',
+ 'url_request/url_request_about_job.h',
+ 'url_request/url_request_context.cc',
+ 'url_request/url_request_context.h',
+ 'url_request/url_request_context_builder.cc',
+ 'url_request/url_request_context_builder.h',
+ 'url_request/url_request_context_getter.cc',
+ 'url_request/url_request_context_getter.h',
+ 'url_request/url_request_context_storage.cc',
+ 'url_request/url_request_context_storage.h',
+ 'url_request/url_request_data_job.cc',
+ 'url_request/url_request_data_job.h',
+ 'url_request/url_request_error_job.cc',
+ 'url_request/url_request_error_job.h',
+ 'url_request/url_request_file_dir_job.cc',
+ 'url_request/url_request_file_dir_job.h',
+ 'url_request/url_request_file_job.cc',
+ 'url_request/url_request_file_job.h',
+ 'url_request/url_request_filter.cc',
+ 'url_request/url_request_filter.h',
+ 'url_request/url_request_ftp_job.cc',
+ 'url_request/url_request_ftp_job.h',
+ 'url_request/url_request_http_job.cc',
+ 'url_request/url_request_http_job.h',
+ 'url_request/url_request_job.cc',
+ 'url_request/url_request_job.h',
+ 'url_request/url_request_job_factory.cc',
+ 'url_request/url_request_job_factory.h',
+ 'url_request/url_request_job_factory_impl.cc',
+ 'url_request/url_request_job_factory_impl.h',
+ 'url_request/url_request_job_manager.cc',
+ 'url_request/url_request_job_manager.h',
+ 'url_request/url_request_netlog_params.cc',
+ 'url_request/url_request_netlog_params.h',
+ 'url_request/url_request_redirect_job.cc',
+ 'url_request/url_request_redirect_job.h',
+ 'url_request/url_request_simple_job.cc',
+ 'url_request/url_request_simple_job.h',
+ 'url_request/url_request_status.h',
+ 'url_request/url_request_test_job.cc',
+ 'url_request/url_request_test_job.h',
+ 'url_request/url_request_throttler_entry.cc',
+ 'url_request/url_request_throttler_entry.h',
+ 'url_request/url_request_throttler_entry_interface.h',
+ 'url_request/url_request_throttler_header_adapter.cc',
+ 'url_request/url_request_throttler_header_adapter.h',
+ 'url_request/url_request_throttler_header_interface.h',
+ 'url_request/url_request_throttler_manager.cc',
+ 'url_request/url_request_throttler_manager.h',
+ 'url_request/view_cache_helper.cc',
+ 'url_request/view_cache_helper.h',
+ 'websockets/websocket_channel.cc',
+ 'websockets/websocket_channel.h',
+ 'websockets/websocket_errors.cc',
+ 'websockets/websocket_errors.h',
+ 'websockets/websocket_frame.cc',
+ 'websockets/websocket_frame.h',
+ 'websockets/websocket_frame_parser.cc',
+ 'websockets/websocket_frame_parser.h',
+ 'websockets/websocket_handshake_handler.cc',
+ 'websockets/websocket_handshake_handler.h',
+ 'websockets/websocket_job.cc',
+ 'websockets/websocket_job.h',
+ 'websockets/websocket_mux.h',
+ 'websockets/websocket_net_log_params.cc',
+ 'websockets/websocket_net_log_params.h',
+ 'websockets/websocket_stream.cc',
+ 'websockets/websocket_stream.h',
+ 'websockets/websocket_stream_base.h',
+ 'websockets/websocket_throttle.cc',
+ 'websockets/websocket_throttle.h',
+ ],
+ 'defines': [
+ 'NET_IMPLEMENTATION',
+ ],
+ 'export_dependent_settings': [
+ '../base/base.gyp:base',
+ ],
+ 'conditions': [
+ ['chromeos==1', {
+ 'sources!': [
+ 'base/network_change_notifier_linux.cc',
+ 'base/network_change_notifier_linux.h',
+ 'base/network_change_notifier_netlink_linux.cc',
+ 'base/network_change_notifier_netlink_linux.h',
+ 'proxy/proxy_config_service_linux.cc',
+ 'proxy/proxy_config_service_linux.h',
+ ],
+ }],
+ ['use_kerberos==1', {
+ 'defines': [
+ 'USE_KERBEROS',
+ ],
+ 'conditions': [
+ ['OS=="openbsd"', {
+ 'include_dirs': [
+ '/usr/include/kerberosV'
+ ],
+ }],
+ ['linux_link_kerberos==1', {
+ 'link_settings': {
+ 'ldflags': [
+ '<!@(krb5-config --libs gssapi)',
+ ],
+ },
+ }, { # linux_link_kerberos==0
+ 'defines': [
+ 'DLOPEN_KERBEROS',
+ ],
+ }],
+ ],
+ }, { # use_kerberos == 0
+ 'sources!': [
+ 'http/http_auth_gssapi_posix.cc',
+ 'http/http_auth_gssapi_posix.h',
+ 'http/http_auth_handler_negotiate.h',
+ 'http/http_auth_handler_negotiate.cc',
+ ],
+ }],
+ ['posix_avoid_mmap==1', {
+ 'defines': [
+ 'POSIX_AVOID_MMAP',
+ ],
+ 'direct_dependent_settings': {
+ 'defines': [
+ 'POSIX_AVOID_MMAP',
+ ],
+ },
+ 'sources!': [
+ 'disk_cache/mapped_file_posix.cc',
+ ],
+ }, { # else
+ 'sources!': [
+ 'disk_cache/mapped_file_avoid_mmap_posix.cc',
+ ],
+ }],
+ ['disable_ftp_support==1', {
+ 'sources/': [
+ ['exclude', '^ftp/'],
+ ],
+ 'sources!': [
+ 'url_request/ftp_protocol_handler.cc',
+ 'url_request/ftp_protocol_handler.h',
+ 'url_request/url_request_ftp_job.cc',
+ 'url_request/url_request_ftp_job.h',
+ ],
+ }],
+ ['enable_built_in_dns==1', {
+ 'defines': [
+ 'ENABLE_BUILT_IN_DNS',
+ ]
+ }, { # else
+ 'sources!': [
+ 'dns/address_sorter_posix.cc',
+ 'dns/address_sorter_posix.h',
+ 'dns/dns_client.cc',
+ ],
+ }],
+ ['use_tracing_cache_backend==1', {
+ 'defines': [
+ 'USE_TRACING_CACHE_BACKEND'
+ ],
+ }],
+ ['use_openssl==1', {
+ 'sources!': [
+ 'base/crypto_module_nss.cc',
+ 'base/keygen_handler_nss.cc',
+ 'base/nss_memio.c',
+ 'base/nss_memio.h',
+ 'cert/cert_database_nss.cc',
+ 'cert/cert_verify_proc_nss.cc',
+ 'cert/cert_verify_proc_nss.h',
+ 'cert/jwk_serializer_nss.cc',
+ 'cert/nss_cert_database.cc',
+ 'cert/nss_cert_database.h',
+ 'cert/test_root_certs_nss.cc',
+ 'cert/x509_certificate_nss.cc',
+ 'cert/x509_util_nss.cc',
+ 'cert/x509_util_nss.h',
+ 'ocsp/nss_ocsp.cc',
+ 'ocsp/nss_ocsp.h',
+ 'quic/crypto/aes_128_gcm_12_decrypter_nss.cc',
+ 'quic/crypto/aes_128_gcm_12_encrypter_nss.cc',
+ 'quic/crypto/channel_id_nss.cc',
+ 'quic/crypto/p256_key_exchange_nss.cc',
+ 'socket/nss_ssl_util.cc',
+ 'socket/nss_ssl_util.h',
+ 'socket/ssl_client_socket_nss.cc',
+ 'socket/ssl_client_socket_nss.h',
+ 'socket/ssl_server_socket_nss.cc',
+ 'socket/ssl_server_socket_nss.h',
+ 'ssl/client_cert_store_impl_nss.cc',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.cpp',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.h',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.cpp',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.h',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.cpp',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.h',
+ ],
+ },
+ { # else !use_openssl: remove the unneeded files
+ 'sources!': [
+ 'base/crypto_module_openssl.cc',
+ 'base/keygen_handler_openssl.cc',
+ 'base/openssl_private_key_store.h',
+ 'base/openssl_private_key_store_android.cc',
+ 'base/openssl_private_key_store_memory.cc',
+ 'cert/cert_database_openssl.cc',
+ 'cert/cert_verify_proc_openssl.cc',
+ 'cert/cert_verify_proc_openssl.h',
+ 'cert/jwk_serializer_openssl.cc',
+ 'cert/test_root_certs_openssl.cc',
+ 'cert/x509_certificate_openssl.cc',
+ 'cert/x509_util_openssl.cc',
+ 'cert/x509_util_openssl.h',
+ 'quic/crypto/aes_128_gcm_12_decrypter_openssl.cc',
+ 'quic/crypto/aes_128_gcm_12_encrypter_openssl.cc',
+ 'quic/crypto/channel_id_openssl.cc',
+ 'quic/crypto/p256_key_exchange_openssl.cc',
+ 'quic/crypto/scoped_evp_cipher_ctx.cc',
+ 'quic/crypto/scoped_evp_cipher_ctx.h',
+ 'socket/ssl_client_socket_openssl.cc',
+ 'socket/ssl_client_socket_openssl.h',
+ 'socket/ssl_server_socket_openssl.cc',
+ 'ssl/openssl_client_key_store.cc',
+ 'ssl/openssl_client_key_store.h',
+ ],
+ },
+ ],
+ [ 'use_glib == 1', {
+ 'dependencies': [
+ '../build/linux/system.gyp:gconf',
+ '../build/linux/system.gyp:gio',
+ ],
+ 'conditions': [
+ ['use_openssl==1', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ ],
+ },
+ { # else use_openssl==0, use NSS
+ 'dependencies': [
+ '../build/linux/system.gyp:ssl',
+ ],
+ }],
+ ['os_bsd==1', {
+ 'sources!': [
+ 'base/network_change_notifier_linux.cc',
+ 'base/network_change_notifier_netlink_linux.cc',
+ 'proxy/proxy_config_service_linux.cc',
+ ],
+ },{
+ 'dependencies': [
+ '../build/linux/system.gyp:libresolv',
+ ],
+ }],
+ ['OS=="solaris"', {
+ 'link_settings': {
+ 'ldflags': [
+ '-R/usr/lib/mps',
+ ],
+ },
+ }],
+ ],
+ },
+ { # else: OS is not in the above list
+ 'sources!': [
+ 'base/crypto_module_nss.cc',
+ 'base/keygen_handler_nss.cc',
+ 'cert/cert_database_nss.cc',
+ 'cert/nss_cert_database.cc',
+ 'cert/nss_cert_database.h',
+ 'cert/test_root_certs_nss.cc',
+ 'cert/x509_certificate_nss.cc',
+ 'ocsp/nss_ocsp.cc',
+ 'ocsp/nss_ocsp.h',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.cpp',
+ 'third_party/mozilla_security_manager/nsKeygenHandler.h',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.cpp',
+ 'third_party/mozilla_security_manager/nsNSSCertificateDB.h',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.cpp',
+ 'third_party/mozilla_security_manager/nsPKCS12Blob.h',
+ ],
+ },
+ ],
+ [ 'toolkit_uses_gtk == 1', {
+ 'dependencies': [
+ '../build/linux/system.gyp:gdk',
+ ],
+ }],
+ [ 'use_nss != 1', {
+ 'sources!': [
+ 'cert/cert_verify_proc_nss.cc',
+ 'cert/cert_verify_proc_nss.h',
+ 'ssl/client_cert_store_impl_nss.cc',
+ ],
+ }],
+ [ 'enable_websockets != 1', {
+ 'sources/': [
+ ['exclude', '^socket_stream/'],
+ ['exclude', '^websockets/'],
+ ],
+ 'sources!': [
+ 'spdy/spdy_websocket_stream.cc',
+ 'spdy/spdy_websocket_stream.h',
+ ],
+ }],
+ [ 'enable_mdns != 1', {
+ 'sources!' : [
+ 'dns/mdns_cache.cc',
+ 'dns/mdns_cache.h',
+ 'dns/mdns_client.cc',
+ 'dns/mdns_client.h',
+ 'dns/mdns_client_impl.cc',
+ 'dns/mdns_client_impl.h',
+ 'dns/record_parsed.cc',
+ 'dns/record_parsed.h',
+ 'dns/record_rdata.cc',
+ 'dns/record_rdata.h',
+ ]
+ }],
+ [ 'OS == "win"', {
+ 'sources!': [
+ 'http/http_auth_handler_ntlm_portable.cc',
+ 'socket/tcp_client_socket_libevent.cc',
+ 'socket/tcp_client_socket_libevent.h',
+ 'socket/tcp_server_socket_libevent.cc',
+ 'socket/tcp_server_socket_libevent.h',
+ 'ssl/client_cert_store_impl_nss.cc',
+ 'udp/udp_socket_libevent.cc',
+ 'udp/udp_socket_libevent.h',
+ ],
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nspr',
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:libssl',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ }, { # else: OS != "win"
+ 'sources!': [
+ 'base/winsock_init.cc',
+ 'base/winsock_init.h',
+ 'base/winsock_util.cc',
+ 'base/winsock_util.h',
+ 'proxy/proxy_resolver_winhttp.cc',
+ 'proxy/proxy_resolver_winhttp.h',
+ ],
+ },
+ ],
+ [ 'OS == "mac"', {
+ 'sources!': [
+ 'ssl/client_cert_store_impl_nss.cc',
+ ],
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nspr',
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:libssl',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
+ '$(SDKROOT)/System/Library/Frameworks/Security.framework',
+ '$(SDKROOT)/System/Library/Frameworks/SystemConfiguration.framework',
+ '$(SDKROOT)/usr/lib/libresolv.dylib',
+ ]
+ },
+ },
+ ],
+ [ 'OS == "ios"', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:libssl',
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/CFNetwork.framework',
+ '$(SDKROOT)/System/Library/Frameworks/MobileCoreServices.framework',
+ '$(SDKROOT)/System/Library/Frameworks/Security.framework',
+ '$(SDKROOT)/System/Library/Frameworks/SystemConfiguration.framework',
+ '$(SDKROOT)/usr/lib/libresolv.dylib',
+ ],
+ },
+ },
+ ],
+ ['OS=="android" and _toolset=="target" and android_webview_build == 0', {
+ 'dependencies': [
+ 'net_java',
+ ],
+ }],
+ [ 'OS == "android"', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ 'net_jni_headers',
+ ],
+ 'sources!': [
+ 'base/openssl_private_key_store_memory.cc',
+ 'cert/cert_database_openssl.cc',
+ 'cert/cert_verify_proc_openssl.cc',
+ 'cert/test_root_certs_openssl.cc',
+ ],
+ # The net/android/keystore_openssl.cc source file needs to
+ # access an OpenSSL-internal header.
+ 'include_dirs': [
+ '../third_party/openssl',
+ ],
+ },
+ ],
+ ],
+ 'target_conditions': [
+ # These source files are excluded by default platform rules, but they
+ # are needed in specific cases on other platforms. Re-including them can
+ # only be done in target_conditions as it is evaluated after the
+ # platform rules.
+ ['OS == "android"', {
+ 'sources/': [
+ ['include', '^base/platform_mime_util_linux\\.cc$'],
+ ],
+ }],
+ ['OS == "ios"', {
+ 'sources/': [
+ ['include', '^base/network_change_notifier_mac\\.cc$'],
+ ['include', '^base/network_config_watcher_mac\\.cc$'],
+ ['include', '^base/platform_mime_util_mac\\.mm$'],
+ # The iOS implementation only partially uses NSS and thus does not
+ # defines |use_nss|. In particular the |USE_NSS| preprocessor
+ # definition is not used. The following files are needed though:
+ ['include', '^cert/cert_verify_proc_nss\\.cc$'],
+ ['include', '^cert/cert_verify_proc_nss\\.h$'],
+ ['include', '^cert/test_root_certs_nss\\.cc$'],
+ ['include', '^cert/x509_util_nss\\.cc$'],
+ ['include', '^cert/x509_util_nss\\.h$'],
+ ['include', '^dns/notify_watcher_mac\\.cc$'],
+ ['include', '^proxy/proxy_resolver_mac\\.cc$'],
+ ['include', '^proxy/proxy_server_mac\\.cc$'],
+ ['include', '^ocsp/nss_ocsp\\.cc$'],
+ ['include', '^ocsp/nss_ocsp\\.h$'],
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'net_unittests',
+ 'type': '<(gtest_target_type)',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../crypto/crypto.gyp:crypto',
+ '../testing/gmock.gyp:gmock',
+ '../testing/gtest.gyp:gtest',
+ '../third_party/zlib/zlib.gyp:zlib',
+ '../url/url.gyp:url_lib',
+ 'http_server',
+ 'net',
+ 'net_test_support'
+ ],
+ 'sources': [
+ 'android/keystore_unittest.cc',
+ 'android/network_change_notifier_android_unittest.cc',
+ 'base/address_list_unittest.cc',
+ 'base/address_tracker_linux_unittest.cc',
+ 'base/backoff_entry_unittest.cc',
+ 'base/big_endian_unittest.cc',
+ 'base/data_url_unittest.cc',
+ 'base/directory_lister_unittest.cc',
+ 'base/dns_util_unittest.cc',
+ 'base/escape_unittest.cc',
+ 'base/expiring_cache_unittest.cc',
+ 'base/file_stream_unittest.cc',
+ 'base/filter_unittest.cc',
+ 'base/int128_unittest.cc',
+ 'base/gzip_filter_unittest.cc',
+ 'base/host_mapping_rules_unittest.cc',
+ 'base/host_port_pair_unittest.cc',
+ 'base/ip_endpoint_unittest.cc',
+ 'base/keygen_handler_unittest.cc',
+ 'base/mime_sniffer_unittest.cc',
+ 'base/mime_util_unittest.cc',
+ 'base/mock_filter_context.cc',
+ 'base/mock_filter_context.h',
+ 'base/net_log_logger_unittest.cc',
+ 'base/net_log_unittest.cc',
+ 'base/net_log_unittest.h',
+ 'base/net_util_unittest.cc',
+ 'base/network_change_notifier_win_unittest.cc',
+ 'base/prioritized_dispatcher_unittest.cc',
+ 'base/priority_queue_unittest.cc',
+ 'base/registry_controlled_domains/registry_controlled_domain_unittest.cc',
+ 'base/sdch_filter_unittest.cc',
+ 'base/static_cookie_policy_unittest.cc',
+ 'base/test_completion_callback_unittest.cc',
+ 'base/upload_bytes_element_reader_unittest.cc',
+ 'base/upload_data_stream_unittest.cc',
+ 'base/upload_file_element_reader_unittest.cc',
+ 'base/url_util_unittest.cc',
+ 'cert/cert_verify_proc_unittest.cc',
+ 'cert/crl_set_unittest.cc',
+ 'cert/ev_root_ca_metadata_unittest.cc',
+ 'cert/jwk_serializer_unittest.cc',
+ 'cert/multi_threaded_cert_verifier_unittest.cc',
+ 'cert/nss_cert_database_unittest.cc',
+ 'cert/pem_tokenizer_unittest.cc',
+ 'cert/test_root_certs_unittest.cc',
+ 'cert/x509_certificate_unittest.cc',
+ 'cert/x509_cert_types_unittest.cc',
+ 'cert/x509_util_unittest.cc',
+ 'cert/x509_util_nss_unittest.cc',
+ 'cert/x509_util_openssl_unittest.cc',
+ 'cookies/canonical_cookie_unittest.cc',
+ 'cookies/cookie_constants_unittest.cc',
+ 'cookies/cookie_monster_unittest.cc',
+ 'cookies/cookie_store_unittest.h',
+ 'cookies/cookie_util_unittest.cc',
+ 'cookies/parsed_cookie_unittest.cc',
+ 'disk_cache/addr_unittest.cc',
+ 'disk_cache/backend_unittest.cc',
+ 'disk_cache/bitmap_unittest.cc',
+ 'disk_cache/block_files_unittest.cc',
+ 'disk_cache/cache_util_unittest.cc',
+ 'disk_cache/entry_unittest.cc',
+ 'disk_cache/mapped_file_unittest.cc',
+ 'disk_cache/simple/simple_index_file_unittest.cc',
+ 'disk_cache/simple/simple_index_unittest.cc',
+ 'disk_cache/simple/simple_test_util.h',
+ 'disk_cache/simple/simple_test_util.cc',
+ 'disk_cache/simple/simple_util_unittest.cc',
+ 'disk_cache/storage_block_unittest.cc',
+ 'disk_cache/flash/log_store_entry_unittest.cc',
+ 'disk_cache/flash/log_store_unittest.cc',
+ 'disk_cache/flash/segment_unittest.cc',
+ 'disk_cache/flash/storage_unittest.cc',
+ 'dns/address_sorter_posix_unittest.cc',
+ 'dns/address_sorter_unittest.cc',
+ 'dns/dns_config_service_posix_unittest.cc',
+ 'dns/dns_config_service_unittest.cc',
+ 'dns/dns_config_service_win_unittest.cc',
+ 'dns/dns_hosts_unittest.cc',
+ 'dns/dns_query_unittest.cc',
+ 'dns/dns_response_unittest.cc',
+ 'dns/dns_session_unittest.cc',
+ 'dns/dns_transaction_unittest.cc',
+ 'dns/host_cache_unittest.cc',
+ 'dns/host_resolver_impl_unittest.cc',
+ 'dns/mapped_host_resolver_unittest.cc',
+ 'dns/mdns_cache_unittest.cc',
+ 'dns/mdns_client_unittest.cc',
+ 'dns/serial_worker_unittest.cc',
+ 'dns/record_parsed_unittest.cc',
+ 'dns/record_rdata_unittest.cc',
+ 'dns/single_request_host_resolver_unittest.cc',
+ 'ftp/ftp_auth_cache_unittest.cc',
+ 'ftp/ftp_ctrl_response_buffer_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_ls_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_netware_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_os2_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_unittest.h',
+ 'ftp/ftp_directory_listing_parser_vms_unittest.cc',
+ 'ftp/ftp_directory_listing_parser_windows_unittest.cc',
+ 'ftp/ftp_network_transaction_unittest.cc',
+ 'ftp/ftp_util_unittest.cc',
+ 'http/des_unittest.cc',
+ 'http/http_auth_cache_unittest.cc',
+ 'http/http_auth_controller_unittest.cc',
+ 'http/http_auth_filter_unittest.cc',
+ 'http/http_auth_gssapi_posix_unittest.cc',
+ 'http/http_auth_handler_basic_unittest.cc',
+ 'http/http_auth_handler_digest_unittest.cc',
+ 'http/http_auth_handler_factory_unittest.cc',
+ 'http/http_auth_handler_mock.cc',
+ 'http/http_auth_handler_mock.h',
+ 'http/http_auth_handler_negotiate_unittest.cc',
+ 'http/http_auth_handler_unittest.cc',
+ 'http/http_auth_sspi_win_unittest.cc',
+ 'http/http_auth_unittest.cc',
+ 'http/http_byte_range_unittest.cc',
+ 'http/http_cache_unittest.cc',
+ 'http/http_chunked_decoder_unittest.cc',
+ 'http/http_content_disposition_unittest.cc',
+ 'http/http_network_layer_unittest.cc',
+ 'http/http_network_transaction_unittest.cc',
+ 'http/http_network_transaction_ssl_unittest.cc',
+ 'http/http_pipelined_connection_impl_unittest.cc',
+ 'http/http_pipelined_host_forced_unittest.cc',
+ 'http/http_pipelined_host_impl_unittest.cc',
+ 'http/http_pipelined_host_pool_unittest.cc',
+ 'http/http_pipelined_host_test_util.cc',
+ 'http/http_pipelined_host_test_util.h',
+ 'http/http_pipelined_network_transaction_unittest.cc',
+ 'http/http_proxy_client_socket_pool_unittest.cc',
+ 'http/http_request_headers_unittest.cc',
+ 'http/http_response_body_drainer_unittest.cc',
+ 'http/http_response_headers_unittest.cc',
+ 'http/http_security_headers_unittest.cc',
+ 'http/http_server_properties_impl_unittest.cc',
+ 'http/http_status_code_unittest.cc',
+ 'http/http_stream_factory_impl_request_unittest.cc',
+ 'http/http_stream_factory_impl_unittest.cc',
+ 'http/http_stream_parser_unittest.cc',
+ 'http/http_transaction_unittest.cc',
+ 'http/http_transaction_unittest.h',
+ 'http/http_util_unittest.cc',
+ 'http/http_vary_data_unittest.cc',
+ 'http/mock_allow_url_security_manager.cc',
+ 'http/mock_allow_url_security_manager.h',
+ 'http/mock_gssapi_library_posix.cc',
+ 'http/mock_gssapi_library_posix.h',
+ 'http/mock_http_cache.cc',
+ 'http/mock_http_cache.h',
+ 'http/mock_sspi_library_win.cc',
+ 'http/mock_sspi_library_win.h',
+ 'http/transport_security_state_unittest.cc',
+ 'http/url_security_manager_unittest.cc',
+ 'ocsp/nss_ocsp_unittest.cc',
+ 'proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc',
+ 'proxy/dhcp_proxy_script_fetcher_factory_unittest.cc',
+ 'proxy/dhcp_proxy_script_fetcher_win_unittest.cc',
+ 'proxy/multi_threaded_proxy_resolver_unittest.cc',
+ 'proxy/network_delegate_error_observer_unittest.cc',
+ 'proxy/proxy_bypass_rules_unittest.cc',
+ 'proxy/proxy_config_service_android_unittest.cc',
+ 'proxy/proxy_config_service_linux_unittest.cc',
+ 'proxy/proxy_config_service_win_unittest.cc',
+ 'proxy/proxy_config_unittest.cc',
+ 'proxy/proxy_info_unittest.cc',
+ 'proxy/proxy_list_unittest.cc',
+ 'proxy/proxy_resolver_v8_tracing_unittest.cc',
+ 'proxy/proxy_resolver_v8_unittest.cc',
+ 'proxy/proxy_script_decider_unittest.cc',
+ 'proxy/proxy_script_fetcher_impl_unittest.cc',
+ 'proxy/proxy_server_unittest.cc',
+ 'proxy/proxy_service_unittest.cc',
+ 'quic/blocked_list_test.cc',
+ 'quic/congestion_control/available_channel_estimator_test.cc',
+ 'quic/congestion_control/channel_estimator_test.cc',
+ 'quic/congestion_control/cube_root_test.cc',
+ 'quic/congestion_control/cubic_test.cc',
+ 'quic/congestion_control/fix_rate_test.cc',
+ 'quic/congestion_control/hybrid_slow_start_test.cc',
+ 'quic/congestion_control/inter_arrival_bitrate_ramp_up_test.cc',
+ 'quic/congestion_control/inter_arrival_overuse_detector_test.cc',
+ 'quic/congestion_control/inter_arrival_probe_test.cc',
+ 'quic/congestion_control/inter_arrival_receiver_test.cc',
+ 'quic/congestion_control/inter_arrival_state_machine_test.cc',
+ 'quic/congestion_control/inter_arrival_sender_test.cc',
+ 'quic/congestion_control/leaky_bucket_test.cc',
+ 'quic/congestion_control/paced_sender_test.cc',
+ 'quic/congestion_control/quic_congestion_control_test.cc',
+ 'quic/congestion_control/quic_congestion_manager_test.cc',
+ 'quic/congestion_control/quic_max_sized_map_test.cc',
+ 'quic/congestion_control/tcp_cubic_sender_test.cc',
+ 'quic/congestion_control/tcp_receiver_test.cc',
+ 'quic/crypto/aes_128_gcm_12_decrypter_test.cc',
+ 'quic/crypto/aes_128_gcm_12_encrypter_test.cc',
+ 'quic/crypto/cert_compressor_test.cc',
+ 'quic/crypto/channel_id_test.cc',
+ 'quic/crypto/common_cert_set_test.cc',
+ 'quic/crypto/crypto_framer_test.cc',
+ 'quic/crypto/crypto_handshake_test.cc',
+ 'quic/crypto/crypto_secret_boxer_test.cc',
+ 'quic/crypto/crypto_server_test.cc',
+ 'quic/crypto/crypto_utils_test.cc',
+ 'quic/crypto/curve25519_key_exchange_test.cc',
+ 'quic/crypto/null_decrypter_test.cc',
+ 'quic/crypto/null_encrypter_test.cc',
+ 'quic/crypto/p256_key_exchange_test.cc',
+ 'quic/crypto/proof_test.cc',
+ 'quic/crypto/quic_random_test.cc',
+ 'quic/crypto/strike_register_test.cc',
+ 'quic/test_tools/crypto_test_utils.cc',
+ 'quic/test_tools/crypto_test_utils.h',
+ 'quic/test_tools/crypto_test_utils_chromium.cc',
+ 'quic/test_tools/crypto_test_utils_nss.cc',
+ 'quic/test_tools/crypto_test_utils_openssl.cc',
+ 'quic/test_tools/mock_clock.cc',
+ 'quic/test_tools/mock_clock.h',
+ 'quic/test_tools/mock_crypto_client_stream.cc',
+ 'quic/test_tools/mock_crypto_client_stream.h',
+ 'quic/test_tools/mock_crypto_client_stream_factory.cc',
+ 'quic/test_tools/mock_crypto_client_stream_factory.h',
+ 'quic/test_tools/mock_random.cc',
+ 'quic/test_tools/mock_random.h',
+ 'quic/test_tools/quic_client_session_peer.cc',
+ 'quic/test_tools/quic_client_session_peer.h',
+ 'quic/test_tools/quic_connection_peer.cc',
+ 'quic/test_tools/quic_connection_peer.h',
+ 'quic/test_tools/quic_framer_peer.cc',
+ 'quic/test_tools/quic_framer_peer.h',
+ 'quic/test_tools/quic_packet_creator_peer.cc',
+ 'quic/test_tools/quic_packet_creator_peer.h',
+ 'quic/test_tools/quic_received_packet_manager_peer.cc',
+ 'quic/test_tools/quic_received_packet_manager_peer.h',
+ 'quic/test_tools/quic_session_peer.cc',
+ 'quic/test_tools/quic_session_peer.h',
+ 'quic/test_tools/quic_test_utils.cc',
+ 'quic/test_tools/quic_test_utils.h',
+ 'quic/test_tools/reliable_quic_stream_peer.cc',
+ 'quic/test_tools/reliable_quic_stream_peer.h',
+ 'quic/test_tools/simple_quic_framer.cc',
+ 'quic/test_tools/simple_quic_framer.h',
+ 'quic/test_tools/test_task_runner.cc',
+ 'quic/test_tools/test_task_runner.h',
+ 'quic/quic_alarm_test.cc',
+ 'quic/quic_bandwidth_test.cc',
+ 'quic/quic_client_session_test.cc',
+ 'quic/quic_clock_test.cc',
+ 'quic/quic_config_test.cc',
+ 'quic/quic_connection_helper_test.cc',
+ 'quic/quic_connection_test.cc',
+ 'quic/quic_crypto_client_stream_test.cc',
+ 'quic/quic_crypto_server_stream_test.cc',
+ 'quic/quic_crypto_stream_test.cc',
+ 'quic/quic_data_writer_test.cc',
+ 'quic/quic_fec_group_test.cc',
+ 'quic/quic_framer_test.cc',
+ 'quic/quic_http_stream_test.cc',
+ 'quic/quic_network_transaction_unittest.cc',
+ 'quic/quic_packet_creator_test.cc',
+ 'quic/quic_packet_generator_test.cc',
+ 'quic/quic_protocol_test.cc',
+ 'quic/quic_received_packet_manager_test.cc',
+ 'quic/quic_reliable_client_stream_test.cc',
+ 'quic/quic_sent_entropy_manager_test.cc',
+ 'quic/quic_session_test.cc',
+ 'quic/quic_spdy_compressor_test.cc',
+ 'quic/quic_spdy_decompressor_test.cc',
+ 'quic/quic_stream_factory_test.cc',
+ 'quic/quic_stream_sequencer_test.cc',
+ 'quic/quic_time_test.cc',
+ 'quic/quic_utils_test.cc',
+ 'quic/reliable_quic_stream_test.cc',
+ 'server/http_server_response_info_unittest.cc',
+ 'server/http_server_unittest.cc',
+ 'socket/buffered_write_stream_socket_unittest.cc',
+ 'socket/client_socket_pool_base_unittest.cc',
+ 'socket/deterministic_socket_data_unittest.cc',
+ 'socket/mock_client_socket_pool_manager.cc',
+ 'socket/mock_client_socket_pool_manager.h',
+ 'socket/socks5_client_socket_unittest.cc',
+ 'socket/socks_client_socket_pool_unittest.cc',
+ 'socket/socks_client_socket_unittest.cc',
+ 'socket/ssl_client_socket_openssl_unittest.cc',
+ 'socket/ssl_client_socket_pool_unittest.cc',
+ 'socket/ssl_client_socket_unittest.cc',
+ 'socket/ssl_server_socket_unittest.cc',
+ 'socket/tcp_client_socket_unittest.cc',
+ 'socket/tcp_listen_socket_unittest.cc',
+ 'socket/tcp_listen_socket_unittest.h',
+ 'socket/tcp_server_socket_unittest.cc',
+ 'socket/transport_client_socket_pool_unittest.cc',
+ 'socket/transport_client_socket_unittest.cc',
+ 'socket/unix_domain_socket_posix_unittest.cc',
+ 'socket_stream/socket_stream_metrics_unittest.cc',
+ 'socket_stream/socket_stream_unittest.cc',
+ 'spdy/buffered_spdy_framer_unittest.cc',
+ 'spdy/spdy_credential_builder_unittest.cc',
+ 'spdy/spdy_buffer_unittest.cc',
+ 'spdy/spdy_credential_state_unittest.cc',
+ 'spdy/spdy_frame_builder_test.cc',
+ 'spdy/spdy_frame_reader_test.cc',
+ 'spdy/spdy_framer_test.cc',
+ 'spdy/spdy_header_block_unittest.cc',
+ 'spdy/spdy_http_stream_unittest.cc',
+ 'spdy/spdy_http_utils_unittest.cc',
+ 'spdy/spdy_network_transaction_unittest.cc',
+ 'spdy/spdy_priority_forest_test.cc',
+ 'spdy/spdy_protocol_test.cc',
+ 'spdy/spdy_proxy_client_socket_unittest.cc',
+ 'spdy/spdy_read_queue_unittest.cc',
+ 'spdy/spdy_session_pool_unittest.cc',
+ 'spdy/spdy_session_test_util.cc',
+ 'spdy/spdy_session_test_util.h',
+ 'spdy/spdy_session_unittest.cc',
+ 'spdy/spdy_stream_test_util.cc',
+ 'spdy/spdy_stream_test_util.h',
+ 'spdy/spdy_stream_unittest.cc',
+ 'spdy/spdy_test_util_common.cc',
+ 'spdy/spdy_test_util_common.h',
+ 'spdy/spdy_test_utils.cc',
+ 'spdy/spdy_test_utils.h',
+ 'spdy/spdy_websocket_stream_unittest.cc',
+ 'spdy/spdy_websocket_test_util.cc',
+ 'spdy/spdy_websocket_test_util.h',
+ 'spdy/spdy_write_queue_unittest.cc',
+ 'ssl/client_cert_store_impl_unittest.cc',
+ 'ssl/default_server_bound_cert_store_unittest.cc',
+ 'ssl/openssl_client_key_store_unittest.cc',
+ 'ssl/server_bound_cert_service_unittest.cc',
+ 'ssl/ssl_cipher_suite_names_unittest.cc',
+ 'ssl/ssl_client_auth_cache_unittest.cc',
+ 'ssl/ssl_config_service_unittest.cc',
+ 'test/embedded_test_server/embedded_test_server_unittest.cc',
+ 'test/embedded_test_server/http_request_unittest.cc',
+ 'test/embedded_test_server/http_response_unittest.cc',
+ 'test/python_utils_unittest.cc',
+ 'test/run_all_unittests.cc',
+ 'test/test_certificate_data.h',
+ 'tools/dump_cache/url_to_filename_encoder.cc',
+ 'tools/dump_cache/url_to_filename_encoder.h',
+ 'tools/dump_cache/url_to_filename_encoder_unittest.cc',
+ 'tools/dump_cache/url_utilities.h',
+ 'tools/dump_cache/url_utilities.cc',
+ 'tools/dump_cache/url_utilities_unittest.cc',
+ 'tools/tld_cleanup/tld_cleanup_util_unittest.cc',
+ 'udp/udp_socket_unittest.cc',
+ 'url_request/url_fetcher_impl_unittest.cc',
+ 'url_request/url_request_context_builder_unittest.cc',
+ 'url_request/url_request_filter_unittest.cc',
+ 'url_request/url_request_ftp_job_unittest.cc',
+ 'url_request/url_request_http_job_unittest.cc',
+ 'url_request/url_request_job_factory_impl_unittest.cc',
+ 'url_request/url_request_job_unittest.cc',
+ 'url_request/url_request_throttler_simulation_unittest.cc',
+ 'url_request/url_request_throttler_test_support.cc',
+ 'url_request/url_request_throttler_test_support.h',
+ 'url_request/url_request_throttler_unittest.cc',
+ 'url_request/url_request_unittest.cc',
+ 'url_request/view_cache_helper_unittest.cc',
+ 'websockets/websocket_channel_test.cc',
+ 'websockets/websocket_errors_unittest.cc',
+ 'websockets/websocket_frame_parser_unittest.cc',
+ 'websockets/websocket_frame_unittest.cc',
+ 'websockets/websocket_handshake_handler_unittest.cc',
+ 'websockets/websocket_handshake_handler_spdy_unittest.cc',
+ 'websockets/websocket_job_unittest.cc',
+ 'websockets/websocket_net_log_params_unittest.cc',
+ 'websockets/websocket_throttle_unittest.cc',
+ ],
+ 'conditions': [
+ ['os_posix == 1 and OS != "mac" and OS != "ios" and OS != "android"', {
+ 'dependencies': [
+ 'quic_library',
+ 'flip_in_mem_edsm_server_library',
+ 'flip_balsa_and_epoll_library',
+ ],
+ 'sources': [
+ 'tools/flip_server/balsa_frame_test.cc',
+ 'tools/flip_server/balsa_headers_test.cc',
+ 'tools/flip_server/mem_cache_test.cc',
+ 'tools/flip_server/simple_buffer.cc',
+ 'tools/flip_server/simple_buffer.h',
+ 'tools/quic/end_to_end_test.cc',
+ 'tools/quic/quic_client_session_test.cc',
+ 'tools/quic/quic_dispatcher_test.cc',
+ 'tools/quic/quic_epoll_clock_test.cc',
+ 'tools/quic/quic_epoll_connection_helper_test.cc',
+ 'tools/quic/quic_in_memory_cache_test.cc',
+ 'tools/quic/quic_reliable_client_stream_test.cc',
+ 'tools/quic/quic_reliable_server_stream_test.cc',
+ 'tools/quic/quic_server_test.cc',
+ 'tools/quic/quic_spdy_server_stream_test.cc',
+ 'tools/quic/test_tools/http_message_test_utils.cc',
+ 'tools/quic/test_tools/http_message_test_utils.h',
+ 'tools/quic/test_tools/mock_epoll_server.cc',
+ 'tools/quic/test_tools/mock_epoll_server.h',
+ 'tools/quic/test_tools/mock_quic_dispatcher.cc',
+ 'tools/quic/test_tools/mock_quic_dispatcher.h',
+ 'tools/quic/test_tools/quic_client_peer.cc',
+ 'tools/quic/test_tools/quic_client_peer.h',
+ 'tools/quic/test_tools/quic_epoll_connection_helper_peer.cc',
+ 'tools/quic/test_tools/quic_epoll_connection_helper_peer.h',
+ 'tools/quic/test_tools/quic_test_client.cc',
+ 'tools/quic/test_tools/quic_test_client.h',
+ 'tools/quic/test_tools/quic_test_utils.cc',
+ 'tools/quic/test_tools/quic_test_utils.h',
+ ],
+ }],
+ ['chromeos==1', {
+ 'sources!': [
+ 'base/network_change_notifier_linux_unittest.cc',
+ 'proxy/proxy_config_service_linux_unittest.cc',
+ ],
+ }],
+ [ 'OS == "android"', {
+ 'sources!': [
+ # No res_ninit() et al on Android, so this doesn't make a lot of
+ # sense.
+ 'dns/dns_config_service_posix_unittest.cc',
+ 'ssl/client_cert_store_impl_unittest.cc',
+ ],
+ 'dependencies': [
+ 'net_javatests',
+ 'net_test_jni_headers',
+ ],
+ }],
+ [ 'use_glib == 1', {
+ 'dependencies': [
+ '../build/linux/system.gyp:ssl',
+ ],
+ }, { # else use_glib == 0: !posix || mac
+ 'sources!': [
+ 'cert/nss_cert_database_unittest.cc',
+ ],
+ },
+ ],
+ [ 'toolkit_uses_gtk == 1', {
+ 'dependencies': [
+ '../build/linux/system.gyp:gtk',
+ ],
+ },
+ ],
+ [ 'os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"', {
+ 'conditions': [
+ ['linux_use_tcmalloc==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }],
+ ],
+ }],
+ [ 'use_kerberos==1', {
+ 'defines': [
+ 'USE_KERBEROS',
+ ],
+ }, { # use_kerberos == 0
+ 'sources!': [
+ 'http/http_auth_gssapi_posix_unittest.cc',
+ 'http/http_auth_handler_negotiate_unittest.cc',
+ 'http/mock_gssapi_library_posix.cc',
+ 'http/mock_gssapi_library_posix.h',
+ ],
+ }],
+ [ 'use_openssl == 1 or (use_glib == 0 and OS != "ios")', {
+ # Only include this test when on Posix and using NSS for
+ # cert verification or on iOS (which also uses NSS for certs).
+ 'sources!': [
+ 'ocsp/nss_ocsp_unittest.cc',
+ ],
+ }],
+ [ 'use_openssl==1', {
+ # When building for OpenSSL, we need to exclude NSS specific tests.
+ # TODO(bulach): Add equivalent tests when the underlying
+ # functionality is ported to OpenSSL.
+ 'sources!': [
+ 'cert/nss_cert_database_unittest.cc',
+ 'cert/x509_util_nss_unittest.cc',
+ 'quic/test_tools/crypto_test_utils_nss.cc',
+ 'ssl/client_cert_store_impl_unittest.cc',
+ ],
+ }, { # else !use_openssl: remove the unneeded files
+ 'sources!': [
+ 'cert/x509_util_openssl_unittest.cc',
+ 'quic/test_tools/crypto_test_utils_openssl.cc',
+ 'socket/ssl_client_socket_openssl_unittest.cc',
+ 'ssl/openssl_client_key_store_unittest.cc',
+ ],
+ },
+ ],
+ [ 'enable_websockets != 1', {
+ 'sources/': [
+ ['exclude', '^socket_stream/'],
+ ['exclude', '^websockets/'],
+ ['exclude', '^spdy/spdy_websocket_stream_unittest\\.cc$'],
+ ],
+ }],
+ [ 'disable_ftp_support==1', {
+ 'sources/': [
+ ['exclude', '^ftp/'],
+ ],
+ 'sources!': [
+ 'url_request/url_request_ftp_job_unittest.cc',
+ ],
+ },
+ ],
+ [ 'enable_built_in_dns!=1', {
+ 'sources!': [
+ 'dns/address_sorter_posix_unittest.cc',
+ 'dns/address_sorter_unittest.cc',
+ ],
+ },
+ ],
+ [ 'use_v8_in_net==1', {
+ 'dependencies': [
+ 'net_with_v8',
+ ],
+ }, { # else: !use_v8_in_net
+ 'sources!': [
+ 'proxy/proxy_resolver_v8_unittest.cc',
+ 'proxy/proxy_resolver_v8_tracing_unittest.cc',
+ ],
+ },
+ ],
+
+ [ 'enable_mdns != 1', {
+ 'sources!' : [
+ 'dns/mdns_cache_unittest.cc',
+ 'dns/mdns_client_unittest.cc',
+ 'dns/mdns_query_unittest.cc',
+ 'dns/record_parsed_unittest.cc',
+ 'dns/record_rdata_unittest.cc',
+ ],
+ }],
+ [ 'OS == "win"', {
+ 'sources!': [
+ 'dns/dns_config_service_posix_unittest.cc',
+ 'http/http_auth_gssapi_posix_unittest.cc',
+ ],
+ # This is needed to trigger the dll copy step on windows.
+ # TODO(mark): Specifying this here shouldn't be necessary.
+ 'dependencies': [
+ '../third_party/icu/icu.gyp:icudata',
+ '../third_party/nss/nss.gyp:nspr',
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:libssl',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+ [ 'OS == "mac"', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nspr',
+ '../third_party/nss/nss.gyp:nss',
+ 'third_party/nss/ssl.gyp:libssl',
+ ],
+ },
+ ],
+ [ 'OS == "ios"', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nss',
+ ],
+ 'actions': [
+ {
+ 'action_name': 'copy_test_data',
+ 'variables': {
+ 'test_data_files': [
+ 'data/ssl/certificates/',
+ 'data/test.html',
+ 'data/url_request_unittest/',
+ ],
+ 'test_data_prefix': 'net',
+ },
+ 'includes': [ '../build/copy_test_data_ios.gypi' ],
+ },
+ ],
+ 'sources!': [
+ # TODO(droger): The following tests are disabled because the
+ # implementation is missing or incomplete.
+ # KeygenHandler::GenKeyAndSignChallenge() is not ported to iOS.
+ 'base/keygen_handler_unittest.cc',
+ # Need to read input data files.
+ 'base/gzip_filter_unittest.cc',
+ 'disk_cache/backend_unittest.cc',
+ 'disk_cache/block_files_unittest.cc',
+ 'socket/ssl_server_socket_unittest.cc',
+ # Need TestServer.
+ 'proxy/proxy_script_fetcher_impl_unittest.cc',
+ 'socket/ssl_client_socket_unittest.cc',
+ 'ssl/client_cert_store_impl_unittest.cc',
+ 'url_request/url_fetcher_impl_unittest.cc',
+ 'url_request/url_request_context_builder_unittest.cc',
+ # Needs GetAppOutput().
+ 'test/python_utils_unittest.cc',
+
+ # The following tests are disabled because they don't apply to
+ # iOS.
+ # OS is not "linux" or "freebsd" or "openbsd".
+ 'socket/unix_domain_socket_posix_unittest.cc',
+ ],
+ 'conditions': [
+ ['coverage != 0', {
+ 'sources!': [
+ # These sources can't be built with coverage due to a
+ # toolchain bug: http://openradar.appspot.com/radar?id=1499403
+ 'http/transport_security_state_unittest.cc',
+
+ # These tests crash when run with coverage turned on due to an
+ # issue with llvm_gcda_increment_indirect_counter:
+ # http://crbug.com/156058
+ 'cookies/cookie_monster_unittest.cc',
+ 'cookies/cookie_store_unittest.h',
+ 'http/http_auth_controller_unittest.cc',
+ 'http/http_network_layer_unittest.cc',
+ 'http/http_network_transaction_unittest.cc',
+ 'spdy/spdy_http_stream_unittest.cc',
+ 'spdy/spdy_proxy_client_socket_unittest.cc',
+ 'spdy/spdy_session_unittest.cc',
+
+ # These tests crash when run with coverage turned on:
+ # http://crbug.com/177203
+ 'proxy/proxy_service_unittest.cc',
+ ],
+ }],
+ ],
+ }],
+ [ 'OS == "android"', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ ],
+ 'sources!': [
+ 'dns/dns_config_service_posix_unittest.cc',
+ ],
+ },
+ ],
+ ['OS == "android" and gtest_target_type == "shared_library"', {
+ 'dependencies': [
+ '../testing/android/native_test.gyp:native_test_native_code',
+ ]
+ }],
+ ],
+ },
+ {
+ 'target_name': 'net_perftests',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../base/base.gyp:test_support_perf',
+ '../testing/gtest.gyp:gtest',
+ '../url/url.gyp:url_lib',
+ 'net',
+ 'net_test_support',
+ ],
+ 'sources': [
+ 'cookies/cookie_monster_perftest.cc',
+ 'disk_cache/disk_cache_perftest.cc',
+ 'proxy/proxy_resolver_perftest.cc',
+ ],
+ 'conditions': [
+ [ 'use_v8_in_net==1', {
+ 'dependencies': [
+ 'net_with_v8',
+ ],
+ }, { # else: !use_v8_in_net
+ 'sources!': [
+ 'proxy/proxy_resolver_perftest.cc',
+ ],
+ },
+ ],
+ # This is needed to trigger the dll copy step on windows.
+ # TODO(mark): Specifying this here shouldn't be necessary.
+ [ 'OS == "win"', {
+ 'dependencies': [
+ '../third_party/icu/icu.gyp:icudata',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+ ],
+ },
+ {
+ 'target_name': 'net_test_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../net/tools/tld_cleanup/tld_cleanup.gyp:tld_cleanup_util',
+ '../testing/gtest.gyp:gtest',
+ '../testing/gmock.gyp:gmock',
+ '../url/url.gyp:url_lib',
+ 'net',
+ ],
+ 'export_dependent_settings': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ '../testing/gmock.gyp:gmock',
+ ],
+ 'sources': [
+ 'base/capturing_net_log.cc',
+ 'base/capturing_net_log.h',
+ 'base/load_timing_info_test_util.cc',
+ 'base/load_timing_info_test_util.h',
+ 'base/mock_file_stream.cc',
+ 'base/mock_file_stream.h',
+ 'base/test_completion_callback.cc',
+ 'base/test_completion_callback.h',
+ 'base/test_data_directory.cc',
+ 'base/test_data_directory.h',
+ 'cert/mock_cert_verifier.cc',
+ 'cert/mock_cert_verifier.h',
+ 'cookies/cookie_monster_store_test.cc',
+ 'cookies/cookie_monster_store_test.h',
+ 'cookies/cookie_store_test_callbacks.cc',
+ 'cookies/cookie_store_test_callbacks.h',
+ 'cookies/cookie_store_test_helpers.cc',
+ 'cookies/cookie_store_test_helpers.h',
+ 'disk_cache/disk_cache_test_base.cc',
+ 'disk_cache/disk_cache_test_base.h',
+ 'disk_cache/disk_cache_test_util.cc',
+ 'disk_cache/disk_cache_test_util.h',
+ 'disk_cache/flash/flash_cache_test_base.h',
+ 'disk_cache/flash/flash_cache_test_base.cc',
+ 'dns/dns_test_util.cc',
+ 'dns/dns_test_util.h',
+ 'dns/mock_host_resolver.cc',
+ 'dns/mock_host_resolver.h',
+ 'dns/mock_mdns_socket_factory.cc',
+ 'dns/mock_mdns_socket_factory.h',
+ 'proxy/mock_proxy_resolver.cc',
+ 'proxy/mock_proxy_resolver.h',
+ 'proxy/mock_proxy_script_fetcher.cc',
+ 'proxy/mock_proxy_script_fetcher.h',
+ 'proxy/proxy_config_service_common_unittest.cc',
+ 'proxy/proxy_config_service_common_unittest.h',
+ 'socket/socket_test_util.cc',
+ 'socket/socket_test_util.h',
+ 'test/cert_test_util.cc',
+ 'test/cert_test_util.h',
+ 'test/embedded_test_server/embedded_test_server.cc',
+ 'test/embedded_test_server/embedded_test_server.h',
+ 'test/embedded_test_server/http_connection.cc',
+ 'test/embedded_test_server/http_connection.h',
+ 'test/embedded_test_server/http_request.cc',
+ 'test/embedded_test_server/http_request.h',
+ 'test/embedded_test_server/http_response.cc',
+ 'test/embedded_test_server/http_response.h',
+ 'test/net_test_suite.cc',
+ 'test/net_test_suite.h',
+ 'test/python_utils.cc',
+ 'test/python_utils.h',
+ 'test/spawned_test_server/base_test_server.cc',
+ 'test/spawned_test_server/base_test_server.h',
+ 'test/spawned_test_server/local_test_server_posix.cc',
+ 'test/spawned_test_server/local_test_server_win.cc',
+ 'test/spawned_test_server/local_test_server.cc',
+ 'test/spawned_test_server/local_test_server.h',
+ 'test/spawned_test_server/remote_test_server.cc',
+ 'test/spawned_test_server/remote_test_server.h',
+ 'test/spawned_test_server/spawned_test_server.h',
+ 'test/spawned_test_server/spawner_communicator.cc',
+ 'test/spawned_test_server/spawner_communicator.h',
+ 'url_request/test_url_fetcher_factory.cc',
+ 'url_request/test_url_fetcher_factory.h',
+ 'url_request/url_request_test_util.cc',
+ 'url_request/url_request_test_util.h',
+ ],
+ 'conditions': [
+ ['OS != "ios"', {
+ 'dependencies': [
+ '../third_party/protobuf/protobuf.gyp:py_proto',
+ ],
+ }],
+ ['os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"', {
+ 'conditions': [
+ ['use_openssl==1', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ ],
+ }, {
+ 'dependencies': [
+ '../build/linux/system.gyp:ssl',
+ ],
+ }],
+ ],
+ }],
+ ['os_posix == 1 and OS != "mac" and OS != "android" and OS != "ios"', {
+ 'conditions': [
+ ['linux_use_tcmalloc==1', {
+ 'dependencies': [
+ '../base/allocator/allocator.gyp:allocator',
+ ],
+ }],
+ ],
+ }],
+ ['OS != "android"', {
+ 'sources!': [
+ 'test/spawned_test_server/remote_test_server.cc',
+ 'test/spawned_test_server/remote_test_server.h',
+ 'test/spawned_test_server/spawner_communicator.cc',
+ 'test/spawned_test_server/spawner_communicator.h',
+ ],
+ }],
+ ['OS == "ios"', {
+ 'dependencies': [
+ '../third_party/nss/nss.gyp:nss',
+ ],
+ }],
+ [ 'use_v8_in_net==1', {
+ 'dependencies': [
+ 'net_with_v8',
+ ],
+ },
+ ],
+ [ 'enable_mdns != 1', {
+ 'sources!' : [
+ 'dns/mock_mdns_socket_factory.cc',
+ 'dns/mock_mdns_socket_factory.h'
+ ]
+ }],
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'net_resources',
+ 'type': 'none',
+ 'variables': {
+ 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/net',
+ },
+ 'actions': [
+ {
+ 'action_name': 'net_resources',
+ 'variables': {
+ 'grit_grd_file': 'base/net_resources.grd',
+ },
+ 'includes': [ '../build/grit_action.gypi' ],
+ },
+ ],
+ 'includes': [ '../build/grit_target.gypi' ],
+ },
+ {
+ 'target_name': 'http_server',
+ 'type': 'static_library',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ ],
+ 'sources': [
+ 'server/http_connection.cc',
+ 'server/http_connection.h',
+ 'server/http_server.cc',
+ 'server/http_server.h',
+ 'server/http_server_request_info.cc',
+ 'server/http_server_request_info.h',
+ 'server/http_server_response_info.cc',
+ 'server/http_server_response_info.h',
+ 'server/web_socket.cc',
+ 'server/web_socket.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'dump_cache',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ 'net_test_support',
+ ],
+ 'sources': [
+ 'tools/dump_cache/cache_dumper.cc',
+ 'tools/dump_cache/cache_dumper.h',
+ 'tools/dump_cache/dump_cache.cc',
+ 'tools/dump_cache/dump_files.cc',
+ 'tools/dump_cache/dump_files.h',
+ 'tools/dump_cache/simple_cache_dumper.cc',
+ 'tools/dump_cache/simple_cache_dumper.h',
+ 'tools/dump_cache/upgrade_win.cc',
+ 'tools/dump_cache/upgrade_win.h',
+ 'tools/dump_cache/url_to_filename_encoder.cc',
+ 'tools/dump_cache/url_to_filename_encoder.h',
+ 'tools/dump_cache/url_utilities.h',
+ 'tools/dump_cache/url_utilities.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+ 'conditions': [
+ ['use_v8_in_net == 1', {
+ 'targets': [
+ {
+ 'target_name': 'net_with_v8',
+ 'type': '<(component)',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../url/url.gyp:url_lib',
+ '../v8/tools/gyp/v8.gyp:v8',
+ 'net'
+ ],
+ 'defines': [
+ 'NET_IMPLEMENTATION',
+ ],
+ 'sources': [
+ 'proxy/proxy_resolver_v8.cc',
+ 'proxy/proxy_resolver_v8.h',
+ 'proxy/proxy_resolver_v8_tracing.cc',
+ 'proxy/proxy_resolver_v8_tracing.h',
+ 'proxy/proxy_service_v8.cc',
+ 'proxy/proxy_service_v8.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+ }],
+ ['OS != "ios"', {
+ 'targets': [
+ # iOS doesn't have the concept of simple executables, these targets
+ # can't be compiled on the platform.
+ {
+ 'target_name': 'crash_cache',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ 'net_test_support',
+ ],
+ 'sources': [
+ 'tools/crash_cache/crash_cache.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'crl_set_dump',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/crl_set_dump/crl_set_dump.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'dns_fuzz_stub',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/dns_fuzz_stub/dns_fuzz_stub.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'fetch_client',
+ 'type': 'executable',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../testing/gtest.gyp:gtest',
+ '../url/url.gyp:url_lib',
+ 'net',
+ 'net_with_v8',
+ ],
+ 'sources': [
+ 'tools/fetch/fetch_client.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'fetch_server',
+ 'type': 'executable',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../url/url.gyp:url_lib',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/fetch/fetch_server.cc',
+ 'tools/fetch/http_listen_socket.cc',
+ 'tools/fetch/http_listen_socket.h',
+ 'tools/fetch/http_server.cc',
+ 'tools/fetch/http_server.h',
+ 'tools/fetch/http_server_request_info.cc',
+ 'tools/fetch/http_server_request_info.h',
+ 'tools/fetch/http_server_response_info.cc',
+ 'tools/fetch/http_server_response_info.h',
+ 'tools/fetch/http_session.cc',
+ 'tools/fetch/http_session.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'gdig',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/gdig/file_net_log.cc',
+ 'tools/gdig/gdig.cc',
+ ],
+ },
+ {
+ 'target_name': 'get_server_time',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../url/url.gyp:url_lib',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/get_server_time/get_server_time.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'net_watcher',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ 'net_with_v8',
+ ],
+ 'conditions': [
+ [ 'use_glib == 1', {
+ 'dependencies': [
+ '../build/linux/system.gyp:gconf',
+ '../build/linux/system.gyp:gio',
+ ],
+ },
+ ],
+ ],
+ 'sources': [
+ 'tools/net_watcher/net_watcher.cc',
+ ],
+ },
+ {
+ 'target_name': 'run_testserver',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
+ '../testing/gtest.gyp:gtest',
+ 'net_test_support',
+ ],
+ 'sources': [
+ 'tools/testserver/run_testserver.cc',
+ ],
+ },
+ {
+ 'target_name': 'stress_cache',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ 'net_test_support',
+ ],
+ 'sources': [
+ 'disk_cache/stress_cache.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ {
+ 'target_name': 'tld_cleanup',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_i18n',
+ '../net/tools/tld_cleanup/tld_cleanup.gyp:tld_cleanup_util',
+ ],
+ 'sources': [
+ 'tools/tld_cleanup/tld_cleanup.cc',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+ }],
+ ['os_posix == 1 and OS != "mac" and OS != "ios" and OS != "android"', {
+ 'targets': [
+ {
+ 'target_name': 'flip_balsa_and_epoll_library',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/flip_server/balsa_enums.h',
+ 'tools/flip_server/balsa_frame.cc',
+ 'tools/flip_server/balsa_frame.h',
+ 'tools/flip_server/balsa_headers.cc',
+ 'tools/flip_server/balsa_headers.h',
+ 'tools/flip_server/balsa_headers_token_utils.cc',
+ 'tools/flip_server/balsa_headers_token_utils.h',
+ 'tools/flip_server/balsa_visitor_interface.h',
+ 'tools/flip_server/constants.h',
+ 'tools/flip_server/epoll_server.cc',
+ 'tools/flip_server/epoll_server.h',
+ 'tools/flip_server/http_message_constants.cc',
+ 'tools/flip_server/http_message_constants.h',
+ 'tools/flip_server/split.h',
+ 'tools/flip_server/split.cc',
+ ],
+ },
+ {
+ 'target_name': 'flip_in_mem_edsm_server_library',
+ 'type': 'static_library',
+ 'cflags': [
+ '-Wno-deprecated',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../third_party/openssl/openssl.gyp:openssl',
+ 'flip_balsa_and_epoll_library',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/dump_cache/url_to_filename_encoder.cc',
+ 'tools/dump_cache/url_to_filename_encoder.h',
+ 'tools/dump_cache/url_utilities.h',
+ 'tools/dump_cache/url_utilities.cc',
+ 'tools/flip_server/acceptor_thread.h',
+ 'tools/flip_server/acceptor_thread.cc',
+ 'tools/flip_server/buffer_interface.h',
+ 'tools/flip_server/create_listener.cc',
+ 'tools/flip_server/create_listener.h',
+ 'tools/flip_server/flip_config.cc',
+ 'tools/flip_server/flip_config.h',
+ 'tools/flip_server/http_interface.cc',
+ 'tools/flip_server/http_interface.h',
+ 'tools/flip_server/loadtime_measurement.h',
+ 'tools/flip_server/mem_cache.h',
+ 'tools/flip_server/mem_cache.cc',
+ 'tools/flip_server/output_ordering.cc',
+ 'tools/flip_server/output_ordering.h',
+ 'tools/flip_server/ring_buffer.cc',
+ 'tools/flip_server/ring_buffer.h',
+ 'tools/flip_server/simple_buffer.cc',
+ 'tools/flip_server/simple_buffer.h',
+ 'tools/flip_server/sm_connection.cc',
+ 'tools/flip_server/sm_connection.h',
+ 'tools/flip_server/sm_interface.h',
+ 'tools/flip_server/spdy_ssl.cc',
+ 'tools/flip_server/spdy_ssl.h',
+ 'tools/flip_server/spdy_interface.cc',
+ 'tools/flip_server/spdy_interface.h',
+ 'tools/flip_server/spdy_util.cc',
+ 'tools/flip_server/spdy_util.h',
+ 'tools/flip_server/streamer_interface.cc',
+ 'tools/flip_server/streamer_interface.h',
+ 'tools/flip_server/string_piece_utils.h',
+ ],
+ },
+ {
+ 'target_name': 'flip_in_mem_edsm_server',
+ 'type': 'executable',
+ 'cflags': [
+ '-Wno-deprecated',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'flip_balsa_and_epoll_library',
+ 'flip_in_mem_edsm_server_library',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/flip_server/flip_in_mem_edsm_server.cc',
+ ],
+ },
+ {
+ 'target_name': 'quic_library',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '../crypto/crypto.gyp:crypto',
+ '../third_party/openssl/openssl.gyp:openssl',
+ '../url/url.gyp:url_lib',
+ 'flip_balsa_and_epoll_library',
+ 'net',
+ ],
+ 'sources': [
+ 'tools/quic/quic_client.cc',
+ 'tools/quic/quic_client.h',
+ 'tools/quic/quic_client_session.cc',
+ 'tools/quic/quic_client_session.h',
+ 'tools/quic/quic_dispatcher.h',
+ 'tools/quic/quic_dispatcher.cc',
+ 'tools/quic/quic_epoll_clock.cc',
+ 'tools/quic/quic_epoll_clock.h',
+ 'tools/quic/quic_epoll_connection_helper.cc',
+ 'tools/quic/quic_epoll_connection_helper.h',
+ 'tools/quic/quic_in_memory_cache.cc',
+ 'tools/quic/quic_in_memory_cache.h',
+ 'tools/quic/quic_packet_writer.h',
+ 'tools/quic/quic_reliable_client_stream.cc',
+ 'tools/quic/quic_reliable_client_stream.h',
+ 'tools/quic/quic_reliable_server_stream.cc',
+ 'tools/quic/quic_reliable_server_stream.h',
+ 'tools/quic/quic_server.cc',
+ 'tools/quic/quic_server.h',
+ 'tools/quic/quic_server_session.cc',
+ 'tools/quic/quic_server_session.h',
+ 'tools/quic/quic_socket_utils.cc',
+ 'tools/quic/quic_socket_utils.h',
+ 'tools/quic/quic_spdy_client_stream.cc',
+ 'tools/quic/quic_spdy_client_stream.h',
+ 'tools/quic/quic_spdy_server_stream.cc',
+ 'tools/quic/quic_spdy_server_stream.h',
+ 'tools/quic/quic_time_wait_list_manager.h',
+ 'tools/quic/quic_time_wait_list_manager.cc',
+ 'tools/quic/spdy_utils.cc',
+ 'tools/quic/spdy_utils.h',
+ ],
+ },
+ {
+ 'target_name': 'quic_client',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../third_party/openssl/openssl.gyp:openssl',
+ 'net',
+ 'quic_library',
+ ],
+ 'sources': [
+ 'tools/quic/quic_client_bin.cc',
+ ],
+ },
+ {
+ 'target_name': 'quic_server',
+ 'type': 'executable',
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../third_party/openssl/openssl.gyp:openssl',
+ 'net',
+ 'quic_library',
+ ],
+ 'sources': [
+ 'tools/quic/quic_server_bin.cc',
+ ],
+ }
+ ]
+ }],
+ ['OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'net_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/src/org/chromium/net/AndroidKeyStore.java',
+ 'android/java/src/org/chromium/net/AndroidNetworkLibrary.java',
+ 'android/java/src/org/chromium/net/GURLUtils.java',
+ 'android/java/src/org/chromium/net/NetworkChangeNotifier.java',
+ 'android/java/src/org/chromium/net/ProxyChangeListener.java',
+ ],
+ 'variables': {
+ 'jni_gen_package': 'net',
+ },
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/net',
+ ],
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
+ {
+ 'target_name': 'net_test_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java',
+ ],
+ 'variables': {
+ 'jni_gen_package': 'net',
+ },
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/net',
+ ],
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
+ {
+ 'target_name': 'net_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../net/android/java',
+ },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ 'cert_verify_result_android_java',
+ 'certificate_mime_types_java',
+ 'net_errors_java',
+ 'private_key_types_java',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ 'target_name': 'net_java_test_support',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../net/test/android/javatests',
+ },
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ 'target_name': 'net_javatests',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../net/android/javatests',
+ },
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../base/base.gyp:base_java_test_support',
+ 'net_java',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ 'target_name': 'net_errors_java',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/NetError.template',
+ ],
+ 'variables': {
+ 'package_name': 'org/chromium/net',
+ 'template_deps': ['base/net_error_list.h'],
+ },
+ 'includes': [ '../build/android/java_cpp_template.gypi' ],
+ },
+ {
+ 'target_name': 'certificate_mime_types_java',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/CertificateMimeType.template',
+ ],
+ 'variables': {
+ 'package_name': 'org/chromium/net',
+ 'template_deps': ['base/mime_util_certificate_type_list.h'],
+ },
+ 'includes': [ '../build/android/java_cpp_template.gypi' ],
+ },
+ {
+ 'target_name': 'cert_verify_result_android_java',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/CertVerifyResultAndroid.template',
+ ],
+ 'variables': {
+ 'package_name': 'org/chromium/net',
+ 'template_deps': ['android/cert_verify_result_android_list.h'],
+ },
+ 'includes': [ '../build/android/java_cpp_template.gypi' ],
+ },
+ {
+ 'target_name': 'private_key_types_java',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/PrivateKeyType.template',
+ ],
+ 'variables': {
+ 'package_name': 'org/chromium/net',
+ 'template_deps': ['android/private_key_type_list.h'],
+ },
+ 'includes': [ '../build/android/java_cpp_template.gypi' ],
+ },
+ ],
+ }],
+ # Special target to wrap a gtest_target_type==shared_library
+ # net_unittests into an android apk for execution.
+ # See base.gyp for TODO(jrg)s about this strategy.
+ ['OS == "android" and gtest_target_type == "shared_library"', {
+ 'targets': [
+ {
+ 'target_name': 'net_unittests_apk',
+ 'type': 'none',
+ 'dependencies': [
+ 'net_java',
+ 'net_javatests',
+ 'net_unittests',
+ ],
+ 'variables': {
+ 'test_suite_name': 'net_unittests',
+ 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)net_unittests<(SHARED_LIB_SUFFIX)',
+ },
+ 'includes': [ '../build/apk_test.gypi' ],
+ },
+ ],
+ }],
+ ['test_isolation_mode != "noop"', {
+ 'targets': [
+ {
+ 'target_name': 'net_unittests_run',
+ 'type': 'none',
+ 'dependencies': [
+ 'net_unittests',
+ ],
+ 'includes': [
+ '../build/isolate.gypi',
+ 'net_unittests.isolate',
+ ],
+ 'sources': [
+ 'net_unittests.isolate',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/chromium/net/net_unittests.isolate b/chromium/net/net_unittests.isolate
new file mode 100644
index 00000000000..71a2dc05db3
--- /dev/null
+++ b/chromium/net/net_unittests.isolate
@@ -0,0 +1,70 @@
+# Copyright (c) 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.
+{
+ 'conditions': [
+ ['OS=="android" or OS=="linux" or OS=="mac" or OS=="win"', {
+ 'variables': {
+ 'isolate_dependency_tracked': [
+ '../chrome/test/data/animate1.gif',
+ '../chrome/test/data/server-unavailable.html',
+ '../chrome/test/data/server-unavailable.html.mock-http-headers',
+ '../chrome/test/data/simple.html',
+ ],
+ 'isolate_dependency_untracked': [
+ 'data/',
+ ],
+ },
+ }],
+ ['OS=="linux"', {
+ 'variables': {
+ 'command': [
+ '../testing/xvfb.py',
+ '<(PRODUCT_DIR)',
+ '../tools/swarm_client/googletest/run_test_cases.py',
+ '<(PRODUCT_DIR)/net_unittests<(EXECUTABLE_SUFFIX)',
+ ],
+ 'isolate_dependency_tracked': [
+ '../testing/xvfb.py',
+ '<(PRODUCT_DIR)/xdisplaycheck<(EXECUTABLE_SUFFIX)',
+ ],
+ },
+ }],
+ ['OS=="linux" or OS=="mac" or OS=="win"', {
+ 'variables': {
+ 'isolate_dependency_tracked': [
+ '../testing/test_env.py',
+ '../tools/swarm_client/run_isolated.py',
+ '../tools/swarm_client/googletest/run_test_cases.py',
+ '<(PRODUCT_DIR)/net_unittests<(EXECUTABLE_SUFFIX)',
+ ],
+ 'isolate_dependency_untracked': [
+ '../third_party/pyftpdlib/',
+ '../third_party/pywebsocket/',
+ '../third_party/tlslite/',
+ '<(PRODUCT_DIR)/pyproto/',
+ 'tools/testserver/',
+ ],
+ },
+ }],
+ ['OS=="mac" or OS=="win"', {
+ 'variables': {
+ 'command': [
+ '../testing/test_env.py',
+ '../tools/swarm_client/googletest/run_test_cases.py',
+ '<(PRODUCT_DIR)/net_unittests<(EXECUTABLE_SUFFIX)',
+ ],
+ },
+ }],
+ ['OS=="win"', {
+ 'variables': {
+ 'isolate_dependency_tracked': [
+ '<(PRODUCT_DIR)/icudt.dll',
+ ],
+ 'isolate_dependency_untracked': [
+ '../third_party/python_26/',
+ ],
+ },
+ }],
+ ],
+}
diff --git a/chromium/net/ocsp/nss_ocsp.cc b/chromium/net/ocsp/nss_ocsp.cc
new file mode 100644
index 00000000000..aea8fc2ad41
--- /dev/null
+++ b/chromium/net/ocsp/nss_ocsp.cc
@@ -0,0 +1,974 @@
+// Copyright (c) 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.
+
+#include "net/ocsp/nss_ocsp.h"
+
+#include <certt.h>
+#include <certdb.h>
+#include <ocsp.h>
+#include <nspr.h>
+#include <nss.h>
+#include <pthread.h>
+#include <secerr.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+// Protects |g_request_context|.
+pthread_mutex_t g_request_context_lock = PTHREAD_MUTEX_INITIALIZER;
+URLRequestContext* g_request_context = NULL;
+
+// The default timeout for network fetches in NSS is 60 seconds. Choose a
+// saner upper limit for OCSP/CRL/AIA fetches.
+const int kNetworkFetchTimeoutInSecs = 15;
+
+class OCSPRequestSession;
+
+class OCSPIOLoop {
+ public:
+ void StartUsing() {
+ base::AutoLock autolock(lock_);
+ used_ = true;
+ io_loop_ = base::MessageLoopForIO::current();
+ DCHECK(io_loop_);
+ }
+
+ // Called on IO loop.
+ void Shutdown();
+
+ bool used() const {
+ base::AutoLock autolock(lock_);
+ return used_;
+ }
+
+ // Called from worker thread.
+ void PostTaskToIOLoop(const tracked_objects::Location& from_here,
+ const base::Closure& task);
+
+ void EnsureIOLoop();
+
+ void AddRequest(OCSPRequestSession* request);
+ void RemoveRequest(OCSPRequestSession* request);
+
+ // Clears internal state and calls |StartUsing()|. Should be called only in
+ // the context of testing.
+ void ReuseForTesting() {
+ {
+ base::AutoLock autolock(lock_);
+ DCHECK(base::MessageLoopForIO::current());
+ thread_checker_.DetachFromThread();
+ thread_checker_.CalledOnValidThread();
+ shutdown_ = false;
+ used_ = false;
+ }
+ StartUsing();
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<OCSPIOLoop>;
+
+ OCSPIOLoop();
+ ~OCSPIOLoop();
+
+ void CancelAllRequests();
+
+ mutable base::Lock lock_;
+ bool shutdown_; // Protected by |lock_|.
+ std::set<OCSPRequestSession*> requests_; // Protected by |lock_|.
+ bool used_; // Protected by |lock_|.
+ // This should not be modified after |used_|.
+ base::MessageLoopForIO* io_loop_; // Protected by |lock_|.
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPIOLoop);
+};
+
+base::LazyInstance<OCSPIOLoop>::Leaky
+ g_ocsp_io_loop = LAZY_INSTANCE_INITIALIZER;
+
+const int kRecvBufferSize = 4096;
+
+// All OCSP handlers should be called in the context of
+// CertVerifier's thread (i.e. worker pool, not on the I/O thread).
+// It supports blocking mode only.
+
+SECStatus OCSPCreateSession(const char* host, PRUint16 portnum,
+ SEC_HTTP_SERVER_SESSION* pSession);
+SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session,
+ PRPollDesc **pPollDesc);
+SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session);
+
+SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session,
+ const char* http_protocol_variant,
+ const char* path_and_query_string,
+ const char* http_request_method,
+ const PRIntervalTime timeout,
+ SEC_HTTP_REQUEST_SESSION* pRequest);
+SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request,
+ const char* http_data,
+ const PRUint32 http_data_len,
+ const char* http_content_type);
+SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request,
+ const char* http_header_name,
+ const char* http_header_value);
+SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request,
+ PRPollDesc** pPollDesc,
+ PRUint16* http_response_code,
+ const char** http_response_content_type,
+ const char** http_response_headers,
+ const char** http_response_data,
+ PRUint32* http_response_data_len);
+SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request);
+
+char* GetAlternateOCSPAIAInfo(CERTCertificate *cert);
+
+class OCSPNSSInitialization {
+ private:
+ friend struct base::DefaultLazyInstanceTraits<OCSPNSSInitialization>;
+
+ OCSPNSSInitialization();
+ ~OCSPNSSInitialization();
+
+ SEC_HttpClientFcn client_fcn_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPNSSInitialization);
+};
+
+base::LazyInstance<OCSPNSSInitialization> g_ocsp_nss_initialization =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Concrete class for SEC_HTTP_REQUEST_SESSION.
+// Public methods except virtual methods of URLRequest::Delegate
+// (On* methods) run on certificate verifier thread (worker thread).
+// Virtual methods of URLRequest::Delegate and private methods run
+// on IO thread.
+class OCSPRequestSession
+ : public base::RefCountedThreadSafe<OCSPRequestSession>,
+ public URLRequest::Delegate {
+ public:
+ OCSPRequestSession(const GURL& url,
+ const char* http_request_method,
+ base::TimeDelta timeout)
+ : url_(url),
+ http_request_method_(http_request_method),
+ timeout_(timeout),
+ request_(NULL),
+ buffer_(new IOBuffer(kRecvBufferSize)),
+ response_code_(-1),
+ cv_(&lock_),
+ io_loop_(NULL),
+ finished_(false) {}
+
+ void SetPostData(const char* http_data, PRUint32 http_data_len,
+ const char* http_content_type) {
+ // |upload_content_| should not be modified if |request_| is active.
+ DCHECK(!request_);
+ upload_content_.assign(http_data, http_data_len);
+ upload_content_type_.assign(http_content_type);
+ }
+
+ void AddHeader(const char* http_header_name, const char* http_header_value) {
+ extra_request_headers_.SetHeader(http_header_name,
+ http_header_value);
+ }
+
+ void Start() {
+ // At this point, it runs on worker thread.
+ // |io_loop_| was initialized to be NULL in constructor, and
+ // set only in StartURLRequest, so no need to lock |lock_| here.
+ DCHECK(!io_loop_);
+ g_ocsp_io_loop.Get().PostTaskToIOLoop(
+ FROM_HERE,
+ base::Bind(&OCSPRequestSession::StartURLRequest, this));
+ }
+
+ bool Started() const {
+ return request_ != NULL;
+ }
+
+ void Cancel() {
+ // IO thread may set |io_loop_| to NULL, so protect by |lock_|.
+ base::AutoLock autolock(lock_);
+ CancelLocked();
+ }
+
+ bool Finished() const {
+ base::AutoLock autolock(lock_);
+ return finished_;
+ }
+
+ bool Wait() {
+ base::TimeDelta timeout = timeout_;
+ base::AutoLock autolock(lock_);
+ while (!finished_) {
+ base::TimeTicks last_time = base::TimeTicks::Now();
+ cv_.TimedWait(timeout);
+ // Check elapsed time
+ base::TimeDelta elapsed_time = base::TimeTicks::Now() - last_time;
+ timeout -= elapsed_time;
+ if (timeout < base::TimeDelta()) {
+ VLOG(1) << "OCSP Timed out";
+ if (!finished_)
+ CancelLocked();
+ break;
+ }
+ }
+ return finished_;
+ }
+
+ const GURL& url() const {
+ return url_;
+ }
+
+ const std::string& http_request_method() const {
+ return http_request_method_;
+ }
+
+ base::TimeDelta timeout() const {
+ return timeout_;
+ }
+
+ PRUint16 http_response_code() const {
+ DCHECK(finished_);
+ return response_code_;
+ }
+
+ const std::string& http_response_content_type() const {
+ DCHECK(finished_);
+ return response_content_type_;
+ }
+
+ const std::string& http_response_headers() const {
+ DCHECK(finished_);
+ return response_headers_->raw_headers();
+ }
+
+ const std::string& http_response_data() const {
+ DCHECK(finished_);
+ return data_;
+ }
+
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE {
+ DCHECK_EQ(request, request_);
+ DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_);
+
+ if (!new_url.SchemeIs("http")) {
+ // Prevent redirects to non-HTTP schemes, including HTTPS. This matches
+ // the initial check in OCSPServerSession::CreateRequest().
+ CancelURLRequest();
+ }
+ }
+
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE {
+ DCHECK_EQ(request, request_);
+ DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_);
+
+ int bytes_read = 0;
+ if (request->status().is_success()) {
+ response_code_ = request_->GetResponseCode();
+ response_headers_ = request_->response_headers();
+ response_headers_->GetMimeType(&response_content_type_);
+ request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read);
+ }
+ OnReadCompleted(request_, bytes_read);
+ }
+
+ virtual void OnReadCompleted(URLRequest* request,
+ int bytes_read) OVERRIDE {
+ DCHECK_EQ(request, request_);
+ DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_);
+
+ do {
+ if (!request_->status().is_success() || bytes_read <= 0)
+ break;
+ data_.append(buffer_->data(), bytes_read);
+ } while (request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read));
+
+ if (!request_->status().is_io_pending()) {
+ delete request_;
+ request_ = NULL;
+ g_ocsp_io_loop.Get().RemoveRequest(this);
+ {
+ base::AutoLock autolock(lock_);
+ finished_ = true;
+ io_loop_ = NULL;
+ }
+ cv_.Signal();
+ Release(); // Balanced with StartURLRequest().
+ }
+ }
+
+ // Must be called on the IO loop thread.
+ void CancelURLRequest() {
+#ifndef NDEBUG
+ {
+ base::AutoLock autolock(lock_);
+ if (io_loop_)
+ DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_);
+ }
+#endif
+ if (request_) {
+ request_->Cancel();
+ delete request_;
+ request_ = NULL;
+ g_ocsp_io_loop.Get().RemoveRequest(this);
+ {
+ base::AutoLock autolock(lock_);
+ finished_ = true;
+ io_loop_ = NULL;
+ }
+ cv_.Signal();
+ Release(); // Balanced with StartURLRequest().
+ }
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<OCSPRequestSession>;
+
+ virtual ~OCSPRequestSession() {
+ // When this destructor is called, there should be only one thread that has
+ // a reference to this object, and so that thread doesn't need to lock
+ // |lock_| here.
+ DCHECK(!request_);
+ DCHECK(!io_loop_);
+ }
+
+ // Must call this method while holding |lock_|.
+ void CancelLocked() {
+ lock_.AssertAcquired();
+ if (io_loop_) {
+ io_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&OCSPRequestSession::CancelURLRequest, this));
+ }
+ }
+
+ // Runs on |g_ocsp_io_loop|'s IO loop.
+ void StartURLRequest() {
+ DCHECK(!request_);
+
+ pthread_mutex_lock(&g_request_context_lock);
+ URLRequestContext* url_request_context = g_request_context;
+ pthread_mutex_unlock(&g_request_context_lock);
+
+ if (url_request_context == NULL)
+ return;
+
+ {
+ base::AutoLock autolock(lock_);
+ DCHECK(!io_loop_);
+ io_loop_ = base::MessageLoopForIO::current();
+ g_ocsp_io_loop.Get().AddRequest(this);
+ }
+
+ request_ = new URLRequest(url_, this, url_request_context);
+ // To meet the privacy requirements of incognito mode.
+ request_->set_load_flags(LOAD_DISABLE_CACHE | LOAD_DO_NOT_SAVE_COOKIES |
+ LOAD_DO_NOT_SEND_COOKIES);
+
+ if (http_request_method_ == "POST") {
+ DCHECK(!upload_content_.empty());
+ DCHECK(!upload_content_type_.empty());
+
+ request_->set_method("POST");
+ extra_request_headers_.SetHeader(
+ HttpRequestHeaders::kContentType, upload_content_type_);
+
+ scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader(
+ upload_content_.data(), upload_content_.size()));
+ request_->set_upload(make_scoped_ptr(
+ UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ }
+ if (!extra_request_headers_.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_request_headers_);
+
+ request_->Start();
+ AddRef(); // Release after |request_| deleted.
+ }
+
+ GURL url_; // The URL we eventually wound up at
+ std::string http_request_method_;
+ base::TimeDelta timeout_; // The timeout for OCSP
+ URLRequest* request_; // The actual request this wraps
+ scoped_refptr<IOBuffer> buffer_; // Read buffer
+ HttpRequestHeaders extra_request_headers_;
+
+ // HTTP POST payload. |request_| reads bytes from this.
+ std::string upload_content_;
+ std::string upload_content_type_; // MIME type of POST payload
+
+ int response_code_; // HTTP status code for the request
+ std::string response_content_type_;
+ scoped_refptr<HttpResponseHeaders> response_headers_;
+ std::string data_; // Results of the request
+
+ // |lock_| protects |finished_| and |io_loop_|.
+ mutable base::Lock lock_;
+ base::ConditionVariable cv_;
+
+ base::MessageLoop* io_loop_; // Message loop of the IO thread
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession);
+};
+
+// Concrete class for SEC_HTTP_SERVER_SESSION.
+class OCSPServerSession {
+ public:
+ OCSPServerSession(const char* host, PRUint16 port)
+ : host_and_port_(host, port) {}
+ ~OCSPServerSession() {}
+
+ OCSPRequestSession* CreateRequest(const char* http_protocol_variant,
+ const char* path_and_query_string,
+ const char* http_request_method,
+ const PRIntervalTime timeout) {
+ // We dont' support "https" because we haven't thought about
+ // whether it's safe to re-enter this code from talking to an OCSP
+ // responder over SSL.
+ if (strcmp(http_protocol_variant, "http") != 0) {
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return NULL;
+ }
+
+ std::string url_string(base::StringPrintf(
+ "%s://%s%s",
+ http_protocol_variant,
+ host_and_port_.ToString().c_str(),
+ path_and_query_string));
+ VLOG(1) << "URL [" << url_string << "]";
+ GURL url(url_string);
+
+ // NSS does not expose public functions to adjust the fetch timeout when
+ // using libpkix, so hardcode the upper limit for network fetches.
+ base::TimeDelta actual_timeout = std::min(
+ base::TimeDelta::FromSeconds(kNetworkFetchTimeoutInSecs),
+ base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout)));
+
+ return new OCSPRequestSession(url, http_request_method, actual_timeout);
+ }
+
+
+ private:
+ HostPortPair host_and_port_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPServerSession);
+};
+
+OCSPIOLoop::OCSPIOLoop()
+ : shutdown_(false),
+ used_(false),
+ io_loop_(NULL) {
+}
+
+OCSPIOLoop::~OCSPIOLoop() {
+ // IO thread was already deleted before the singleton is deleted
+ // in AtExitManager.
+ {
+ base::AutoLock autolock(lock_);
+ DCHECK(!io_loop_);
+ DCHECK(!used_);
+ DCHECK(shutdown_);
+ }
+
+ pthread_mutex_lock(&g_request_context_lock);
+ DCHECK(!g_request_context);
+ pthread_mutex_unlock(&g_request_context_lock);
+}
+
+void OCSPIOLoop::Shutdown() {
+ // Safe to read outside lock since we only write on IO thread anyway.
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Prevent the worker thread from trying to access |io_loop_|.
+ {
+ base::AutoLock autolock(lock_);
+ io_loop_ = NULL;
+ used_ = false;
+ shutdown_ = true;
+ }
+
+ CancelAllRequests();
+
+ pthread_mutex_lock(&g_request_context_lock);
+ g_request_context = NULL;
+ pthread_mutex_unlock(&g_request_context_lock);
+}
+
+void OCSPIOLoop::PostTaskToIOLoop(
+ const tracked_objects::Location& from_here, const base::Closure& task) {
+ base::AutoLock autolock(lock_);
+ if (io_loop_)
+ io_loop_->PostTask(from_here, task);
+}
+
+void OCSPIOLoop::EnsureIOLoop() {
+ base::AutoLock autolock(lock_);
+ DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_);
+}
+
+void OCSPIOLoop::AddRequest(OCSPRequestSession* request) {
+ DCHECK(!ContainsKey(requests_, request));
+ requests_.insert(request);
+}
+
+void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) {
+ DCHECK(ContainsKey(requests_, request));
+ requests_.erase(request);
+}
+
+void OCSPIOLoop::CancelAllRequests() {
+ // CancelURLRequest() always removes the request from the requests_
+ // set synchronously.
+ while (!requests_.empty())
+ (*requests_.begin())->CancelURLRequest();
+}
+
+OCSPNSSInitialization::OCSPNSSInitialization() {
+ // NSS calls the functions in the function table to download certificates
+ // or CRLs or talk to OCSP responders over HTTP. These functions must
+ // set an NSS/NSPR error code when they fail. Otherwise NSS will get the
+ // residual error code from an earlier failed function call.
+ client_fcn_.version = 1;
+ SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1;
+ ft->createSessionFcn = OCSPCreateSession;
+ ft->keepAliveSessionFcn = OCSPKeepAliveSession;
+ ft->freeSessionFcn = OCSPFreeSession;
+ ft->createFcn = OCSPCreate;
+ ft->setPostDataFcn = OCSPSetPostData;
+ ft->addHeaderFcn = OCSPAddHeader;
+ ft->trySendAndReceiveFcn = OCSPTrySendAndReceive;
+ ft->cancelFcn = NULL;
+ ft->freeFcn = OCSPFree;
+ SECStatus status = SEC_RegisterDefaultHttpClient(&client_fcn_);
+ if (status != SECSuccess) {
+ NOTREACHED() << "Error initializing OCSP: " << PR_GetError();
+ }
+
+ // Work around NSS bugs 524013 and 564334. NSS incorrectly thinks the
+ // CRLs for Network Solutions Certificate Authority have bad signatures,
+ // which causes certificates issued by that CA to be reported as revoked.
+ // By using OCSP for those certificates, which don't have AIA extensions,
+ // we can work around these bugs. See http://crbug.com/41730.
+ CERT_StringFromCertFcn old_callback = NULL;
+ status = CERT_RegisterAlternateOCSPAIAInfoCallBack(
+ GetAlternateOCSPAIAInfo, &old_callback);
+ if (status == SECSuccess) {
+ DCHECK(!old_callback);
+ } else {
+ NOTREACHED() << "Error initializing OCSP: " << PR_GetError();
+ }
+}
+
+OCSPNSSInitialization::~OCSPNSSInitialization() {
+ SECStatus status = CERT_RegisterAlternateOCSPAIAInfoCallBack(NULL, NULL);
+ if (status != SECSuccess) {
+ LOG(ERROR) << "Error unregistering OCSP: " << PR_GetError();
+ }
+}
+
+
+// OCSP Http Client functions.
+// Our Http Client functions operate in blocking mode.
+SECStatus OCSPCreateSession(const char* host, PRUint16 portnum,
+ SEC_HTTP_SERVER_SESSION* pSession) {
+ VLOG(1) << "OCSP create session: host=" << host << " port=" << portnum;
+ pthread_mutex_lock(&g_request_context_lock);
+ URLRequestContext* request_context = g_request_context;
+ pthread_mutex_unlock(&g_request_context_lock);
+ if (request_context == NULL) {
+ LOG(ERROR) << "No URLRequestContext for NSS HTTP handler. host: " << host;
+ // The application failed to call SetURLRequestContextForNSSHttpIO or
+ // has already called ShutdownNSSHttpIO, so we can't create and use
+ // URLRequest. PR_NOT_IMPLEMENTED_ERROR is not an accurate error
+ // code for these error conditions, but is close enough.
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+ }
+ *pSession = new OCSPServerSession(host, portnum);
+ return SECSuccess;
+}
+
+SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session,
+ PRPollDesc **pPollDesc) {
+ VLOG(1) << "OCSP keep alive";
+ if (pPollDesc)
+ *pPollDesc = NULL;
+ return SECSuccess;
+}
+
+SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) {
+ VLOG(1) << "OCSP free session";
+ delete reinterpret_cast<OCSPServerSession*>(session);
+ return SECSuccess;
+}
+
+SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session,
+ const char* http_protocol_variant,
+ const char* path_and_query_string,
+ const char* http_request_method,
+ const PRIntervalTime timeout,
+ SEC_HTTP_REQUEST_SESSION* pRequest) {
+ VLOG(1) << "OCSP create protocol=" << http_protocol_variant
+ << " path_and_query=" << path_and_query_string
+ << " http_request_method=" << http_request_method
+ << " timeout=" << timeout;
+ OCSPServerSession* ocsp_session =
+ reinterpret_cast<OCSPServerSession*>(session);
+
+ OCSPRequestSession* req = ocsp_session->CreateRequest(http_protocol_variant,
+ path_and_query_string,
+ http_request_method,
+ timeout);
+ SECStatus rv = SECFailure;
+ if (req) {
+ req->AddRef(); // Release in OCSPFree().
+ rv = SECSuccess;
+ }
+ *pRequest = req;
+ return rv;
+}
+
+SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request,
+ const char* http_data,
+ const PRUint32 http_data_len,
+ const char* http_content_type) {
+ VLOG(1) << "OCSP set post data len=" << http_data_len;
+ OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
+
+ req->SetPostData(http_data, http_data_len, http_content_type);
+ return SECSuccess;
+}
+
+SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request,
+ const char* http_header_name,
+ const char* http_header_value) {
+ VLOG(1) << "OCSP add header name=" << http_header_name
+ << " value=" << http_header_value;
+ OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
+
+ req->AddHeader(http_header_name, http_header_value);
+ return SECSuccess;
+}
+
+// Sets response of |req| in the output parameters.
+// It is helper routine for OCSP trySendAndReceiveFcn.
+// |http_response_data_len| could be used as input parameter. If it has
+// non-zero value, it is considered as maximum size of |http_response_data|.
+SECStatus OCSPSetResponse(OCSPRequestSession* req,
+ PRUint16* http_response_code,
+ const char** http_response_content_type,
+ const char** http_response_headers,
+ const char** http_response_data,
+ PRUint32* http_response_data_len) {
+ DCHECK(req->Finished());
+ const std::string& data = req->http_response_data();
+ if (http_response_data_len && *http_response_data_len) {
+ if (*http_response_data_len < data.size()) {
+ LOG(ERROR) << "response body too large: " << *http_response_data_len
+ << " < " << data.size();
+ *http_response_data_len = data.size();
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE);
+ return SECFailure;
+ }
+ }
+ VLOG(1) << "OCSP response "
+ << " response_code=" << req->http_response_code()
+ << " content_type=" << req->http_response_content_type()
+ << " header=" << req->http_response_headers()
+ << " data_len=" << data.size();
+ if (http_response_code)
+ *http_response_code = req->http_response_code();
+ if (http_response_content_type)
+ *http_response_content_type = req->http_response_content_type().c_str();
+ if (http_response_headers)
+ *http_response_headers = req->http_response_headers().c_str();
+ if (http_response_data)
+ *http_response_data = data.data();
+ if (http_response_data_len)
+ *http_response_data_len = data.size();
+ return SECSuccess;
+}
+
+SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request,
+ PRPollDesc** pPollDesc,
+ PRUint16* http_response_code,
+ const char** http_response_content_type,
+ const char** http_response_headers,
+ const char** http_response_data,
+ PRUint32* http_response_data_len) {
+ if (http_response_data_len) {
+ // We must always set an output value, even on failure. The output value 0
+ // means the failure was unrelated to the acceptable response data length.
+ *http_response_data_len = 0;
+ }
+
+ VLOG(1) << "OCSP try send and receive";
+ OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
+ // We support blocking mode only.
+ if (pPollDesc)
+ *pPollDesc = NULL;
+
+ if (req->Started() || req->Finished()) {
+ // We support blocking mode only, so this function shouldn't be called
+ // again when req has stareted or finished.
+ NOTREACHED();
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation.
+ return SECFailure;
+ }
+
+ const base::Time start_time = base::Time::Now();
+ bool request_ok = true;
+ req->Start();
+ if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) {
+ // If the response code is -1, the request failed and there is no response.
+ request_ok = false;
+ }
+ const base::TimeDelta duration = base::Time::Now() - start_time;
+
+ // For metrics, we want to know if the request was 'successful' or not.
+ // |request_ok| determines if we'll pass the response back to NSS and |ok|
+ // keep track of if we think the response was good.
+ bool ok = true;
+ if (!request_ok ||
+ (req->http_response_code() >= 400 && req->http_response_code() < 600) ||
+ req->http_response_data().size() == 0 ||
+ // 0x30 is the ASN.1 DER encoding of a SEQUENCE. All valid OCSP/CRL/CRT
+ // responses must start with this. If we didn't check for this then a
+ // captive portal could provide an HTML reply that we would count as a
+ // 'success' (although it wouldn't count in NSS, of course).
+ req->http_response_data().data()[0] != 0x30) {
+ ok = false;
+ }
+
+ // We want to know if this was:
+ // 1) An OCSP request
+ // 2) A CRL request
+ // 3) A request for a missing intermediate certificate
+ // There's no sure way to do this, so we use heuristics like MIME type and
+ // URL.
+ const char* mime_type = "";
+ if (ok)
+ mime_type = req->http_response_content_type().c_str();
+ bool is_ocsp =
+ strcasecmp(mime_type, "application/ocsp-response") == 0;
+ bool is_crl = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 ||
+ strcasecmp(mime_type, "application/x-x509-crl") == 0 ||
+ strcasecmp(mime_type, "application/pkix-crl") == 0;
+ bool is_cert =
+ strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 ||
+ strcasecmp(mime_type, "application/x-x509-server-cert") == 0 ||
+ strcasecmp(mime_type, "application/pkix-cert") == 0 ||
+ strcasecmp(mime_type, "application/pkcs7-mime") == 0;
+
+ if (!is_cert && !is_crl && !is_ocsp) {
+ // We didn't get a hint from the MIME type, so do the best that we can.
+ const std::string path = req->url().path();
+ const std::string host = req->url().host();
+ is_crl = strcasestr(path.c_str(), ".crl") != NULL;
+ is_cert = strcasestr(path.c_str(), ".crt") != NULL ||
+ strcasestr(path.c_str(), ".p7c") != NULL ||
+ strcasestr(path.c_str(), ".cer") != NULL;
+ is_ocsp = strcasestr(host.c_str(), "ocsp") != NULL ||
+ req->http_request_method() == "POST";
+ }
+
+ if (is_ocsp) {
+ if (ok) {
+ UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration);
+ UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", true);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.OCSPRequestFailedTimeMs", duration);
+ UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", false);
+ }
+ } else if (is_crl) {
+ if (ok) {
+ UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration);
+ UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", true);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.CRLRequestFailedTimeMs", duration);
+ UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", false);
+ }
+ } else if (is_cert) {
+ if (ok)
+ UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration);
+ } else {
+ if (ok)
+ UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration);
+ }
+
+ if (!request_ok) {
+ PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation.
+ return SECFailure;
+ }
+
+ return OCSPSetResponse(
+ req, http_response_code,
+ http_response_content_type,
+ http_response_headers,
+ http_response_data,
+ http_response_data_len);
+}
+
+SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) {
+ VLOG(1) << "OCSP free";
+ OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
+ req->Cancel();
+ req->Release();
+ return SECSuccess;
+}
+
+// Data for GetAlternateOCSPAIAInfo.
+
+// CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US
+//
+// There are two CAs with this name. Their key IDs are listed next.
+const unsigned char network_solutions_ca_name[] = {
+ 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69,
+ 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+ 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53,
+ 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79
+};
+const unsigned int network_solutions_ca_name_len = 100;
+
+// This CA is an intermediate CA, subordinate to UTN-USERFirst-Hardware.
+const unsigned char network_solutions_ca_key_id[] = {
+ 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08, 0xa9, 0x4c, 0x25, 0x89,
+ 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c, 0x62, 0x17
+};
+const unsigned int network_solutions_ca_key_id_len = 20;
+
+// This CA is a root CA. It is also cross-certified by
+// UTN-USERFirst-Hardware.
+const unsigned char network_solutions_ca_key_id2[] = {
+ 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87,
+ 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c
+};
+const unsigned int network_solutions_ca_key_id2_len = 20;
+
+// An entry in our OCSP responder table. |issuer| and |issuer_key_id| are
+// the key. |ocsp_url| is the value.
+struct OCSPResponderTableEntry {
+ SECItem issuer;
+ SECItem issuer_key_id;
+ const char *ocsp_url;
+};
+
+const OCSPResponderTableEntry g_ocsp_responder_table[] = {
+ {
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_name),
+ network_solutions_ca_name_len
+ },
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_key_id),
+ network_solutions_ca_key_id_len
+ },
+ "http://ocsp.netsolssl.com"
+ },
+ {
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_name),
+ network_solutions_ca_name_len
+ },
+ {
+ siBuffer,
+ const_cast<unsigned char*>(network_solutions_ca_key_id2),
+ network_solutions_ca_key_id2_len
+ },
+ "http://ocsp.netsolssl.com"
+ }
+};
+
+char* GetAlternateOCSPAIAInfo(CERTCertificate *cert) {
+ if (cert && !cert->isRoot && cert->authKeyID) {
+ for (unsigned int i=0; i < arraysize(g_ocsp_responder_table); i++) {
+ if (SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer,
+ &cert->derIssuer) == SECEqual &&
+ SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer_key_id,
+ &cert->authKeyID->keyID) == SECEqual) {
+ return PORT_Strdup(g_ocsp_responder_table[i].ocsp_url);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+} // anonymous namespace
+
+void SetMessageLoopForNSSHttpIO() {
+ // Must have a MessageLoopForIO.
+ DCHECK(base::MessageLoopForIO::current());
+
+ bool used = g_ocsp_io_loop.Get().used();
+
+ // Should not be called when g_ocsp_io_loop has already been used.
+ DCHECK(!used);
+}
+
+void EnsureNSSHttpIOInit() {
+ g_ocsp_io_loop.Get().StartUsing();
+ g_ocsp_nss_initialization.Get();
+}
+
+void ShutdownNSSHttpIO() {
+ g_ocsp_io_loop.Get().Shutdown();
+}
+
+void ResetNSSHttpIOForTesting() {
+ g_ocsp_io_loop.Get().ReuseForTesting();
+}
+
+// This function would be called before NSS initialization.
+void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) {
+ pthread_mutex_lock(&g_request_context_lock);
+ if (request_context) {
+ DCHECK(!g_request_context);
+ }
+ g_request_context = request_context;
+ pthread_mutex_unlock(&g_request_context_lock);
+}
+
+} // namespace net
diff --git a/chromium/net/ocsp/nss_ocsp.h b/chromium/net/ocsp/nss_ocsp.h
new file mode 100644
index 00000000000..fd840056d07
--- /dev/null
+++ b/chromium/net/ocsp/nss_ocsp.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_OCSP_NSS_OCSP_H_
+#define NET_OCSP_NSS_OCSP_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class URLRequestContext;
+
+// Sets the MessageLoop for NSS's HTTP client functions (i.e. OCSP, CA
+// certificate and CRL fetches) to the current message loop. This should be
+// called before EnsureNSSHttpIOInit() if you want to control the message loop.
+NET_EXPORT void SetMessageLoopForNSSHttpIO();
+
+// Initializes HTTP client functions for NSS. This must be called before any
+// certificate verification functions. This function is thread-safe, and HTTP
+// handlers will only ever be initialized once. ShutdownNSSHttpIO() must be
+// called on shutdown.
+NET_EXPORT void EnsureNSSHttpIOInit();
+
+// This should be called once on shutdown to stop issuing URLRequests for NSS
+// related HTTP fetches.
+NET_EXPORT void ShutdownNSSHttpIO();
+
+// Can be called after a call to |ShutdownNSSHttpIO()| to reset internal state
+// and associate it with the current thread.
+NET_EXPORT void ResetNSSHttpIOForTesting();
+
+// Sets the URLRequestContext for HTTP requests issued by NSS.
+NET_EXPORT void SetURLRequestContextForNSSHttpIO(
+ URLRequestContext* request_context);
+
+} // namespace net
+
+#endif // NET_OCSP_NSS_OCSP_H_
diff --git a/chromium/net/ocsp/nss_ocsp_unittest.cc b/chromium/net/ocsp/nss_ocsp_unittest.cc
new file mode 100644
index 00000000000..be29d5f7931
--- /dev/null
+++ b/chromium/net/ocsp/nss_ocsp_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2013 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/ocsp/nss_ocsp.h"
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_proc.h"
+#include "net/cert/cert_verify_proc_nss.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/multi_threaded_cert_verifier.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/test/cert_test_util.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Matches the caIssuers hostname from the generated certificate.
+const char kAiaHost[] = "aia-test.invalid";
+// Returning a single DER-encoded cert, so the mime-type must be
+// application/pkix-cert per RFC 5280.
+const char kAiaHeaders[] = "HTTP/1.1 200 OK\0"
+ "Content-type: application/pkix-cert\0"
+ "\0";
+
+class AiaResponseHandler : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ AiaResponseHandler(const std::string& headers, const std::string& cert_data)
+ : headers_(headers), cert_data_(cert_data), request_count_(0) {}
+ virtual ~AiaResponseHandler() {}
+
+ // net::URLRequestJobFactory::ProtocolHandler implementation:
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ ++const_cast<AiaResponseHandler*>(this)->request_count_;
+
+ return new net::URLRequestTestJob(
+ request, network_delegate, headers_, cert_data_, true);
+ }
+
+ int request_count() const { return request_count_; }
+
+ private:
+ std::string headers_;
+ std::string cert_data_;
+ int request_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(AiaResponseHandler);
+};
+
+} // namespace
+
+class NssHttpTest : public ::testing::Test {
+ public:
+ NssHttpTest()
+ : context_(false),
+ handler_(NULL),
+ verify_proc_(new CertVerifyProcNSS),
+ verifier_(new MultiThreadedCertVerifier(verify_proc_.get())) {}
+ virtual ~NssHttpTest() {}
+
+ virtual void SetUp() {
+ std::string file_contents;
+ ASSERT_TRUE(file_util::ReadFileToString(
+ GetTestCertsDirectory().AppendASCII("aia-intermediate.der"),
+ &file_contents));
+ ASSERT_FALSE(file_contents.empty());
+
+ // Ownership of |handler| is transferred to the URLRequestFilter, but
+ // hold onto the original pointer in order to access |request_count()|.
+ scoped_ptr<AiaResponseHandler> handler(
+ new AiaResponseHandler(kAiaHeaders, file_contents));
+ handler_ = handler.get();
+
+ URLRequestFilter::GetInstance()->AddHostnameProtocolHandler(
+ "http",
+ kAiaHost,
+ handler.PassAs<URLRequestJobFactory::ProtocolHandler>());
+
+ SetURLRequestContextForNSSHttpIO(&context_);
+ EnsureNSSHttpIOInit();
+ }
+
+ virtual void TearDown() {
+ ShutdownNSSHttpIO();
+
+ if (handler_)
+ URLRequestFilter::GetInstance()->RemoveHostnameHandler("http", kAiaHost);
+ }
+
+ CertVerifier* verifier() const {
+ return verifier_.get();
+ }
+
+ int request_count() const {
+ return handler_->request_count();
+ }
+
+ protected:
+ const CertificateList empty_cert_list_;
+
+ private:
+ TestURLRequestContext context_;
+ AiaResponseHandler* handler_;
+ scoped_refptr<CertVerifyProc> verify_proc_;
+ scoped_ptr<CertVerifier> verifier_;
+};
+
+// Tests that when using NSS to verify certificates, and IO is enabled,
+// that a request to fetch missing intermediate certificates is
+// made successfully.
+TEST_F(NssHttpTest, TestAia) {
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(GetTestCertsDirectory(), "aia-cert.pem"));
+ ASSERT_TRUE(test_cert.get());
+
+ scoped_refptr<X509Certificate> test_root(
+ ImportCertFromFile(GetTestCertsDirectory(), "aia-root.pem"));
+ ASSERT_TRUE(test_root.get());
+
+ ScopedTestRoot scoped_root(test_root.get());
+
+ CertVerifyResult verify_result;
+ TestCompletionCallback test_callback;
+ CertVerifier::RequestHandle request_handle;
+
+ int flags = CertVerifier::VERIFY_CERT_IO_ENABLED;
+ int error = verifier()->Verify(test_cert.get(),
+ "aia-host.invalid",
+ flags,
+ NULL,
+ &verify_result,
+ test_callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+
+ error = test_callback.WaitForResult();
+
+ EXPECT_EQ(OK, error);
+
+ // Ensure that NSS made an AIA request for the missing intermediate.
+ EXPECT_LT(0, request_count());
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc
new file mode 100644
index 00000000000..56e47471bf0
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/threading/worker_pool.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/dhcpcsvc_init_win.h"
+#include "net/proxy/proxy_script_fetcher_impl.h"
+#include "net/url_request/url_request_context.h"
+
+#include <windows.h>
+#include <winsock2.h>
+#include <dhcpcsdk.h>
+#pragma comment(lib, "dhcpcsvc.lib")
+
+namespace {
+
+// Maximum amount of time to wait for response from the Win32 DHCP API.
+const int kTimeoutMs = 2000;
+
+} // namespace
+
+namespace net {
+
+DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher(
+ URLRequestContext* url_request_context)
+ : state_(STATE_START),
+ result_(ERR_IO_PENDING),
+ url_request_context_(url_request_context) {
+ DCHECK(url_request_context_);
+}
+
+DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() {
+ Cancel();
+}
+
+void DhcpProxyScriptAdapterFetcher::Fetch(
+ const std::string& adapter_name, const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(state_, STATE_START);
+ result_ = ERR_IO_PENDING;
+ pac_script_ = base::string16();
+ state_ = STATE_WAIT_DHCP;
+ callback_ = callback;
+
+ wait_timer_.Start(FROM_HERE, ImplGetTimeout(),
+ this, &DhcpProxyScriptAdapterFetcher::OnTimeout);
+ scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(
+ &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
+ dhcp_query.get(),
+ adapter_name),
+ base::Bind(
+ &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone,
+ AsWeakPtr(),
+ dhcp_query),
+ true);
+}
+
+void DhcpProxyScriptAdapterFetcher::Cancel() {
+ DCHECK(CalledOnValidThread());
+ callback_.Reset();
+ wait_timer_.Stop();
+ script_fetcher_.reset();
+
+ switch (state_) {
+ case STATE_WAIT_DHCP:
+ // Nothing to do here, we let the worker thread run to completion,
+ // the task it posts back when it completes will check the state.
+ break;
+ case STATE_WAIT_URL:
+ break;
+ case STATE_START:
+ case STATE_FINISH:
+ case STATE_CANCEL:
+ break;
+ }
+
+ if (state_ != STATE_FINISH) {
+ result_ = ERR_ABORTED;
+ state_ = STATE_CANCEL;
+ }
+}
+
+bool DhcpProxyScriptAdapterFetcher::DidFinish() const {
+ DCHECK(CalledOnValidThread());
+ return state_ == STATE_FINISH;
+}
+
+int DhcpProxyScriptAdapterFetcher::GetResult() const {
+ DCHECK(CalledOnValidThread());
+ return result_;
+}
+
+base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const {
+ DCHECK(CalledOnValidThread());
+ return pac_script_;
+}
+
+GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const {
+ DCHECK(CalledOnValidThread());
+ return pac_url_;
+}
+
+DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() {
+}
+
+DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() {
+}
+
+void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
+ const std::string& adapter_name) {
+ url_ = ImplGetPacURLFromDhcp(adapter_name);
+}
+
+const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const {
+ return url_;
+}
+
+std::string
+ DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
+ const std::string& adapter_name) {
+ return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name);
+}
+
+void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone(
+ scoped_refptr<DhcpQuery> dhcp_query) {
+ DCHECK(CalledOnValidThread());
+ // Because we can't cancel the call to the Win32 API, we can expect
+ // it to finish while we are in a few different states. The expected
+ // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
+ // or FINISH if timeout occurred.
+ DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
+ state_ == STATE_FINISH);
+ if (state_ != STATE_WAIT_DHCP)
+ return;
+
+ wait_timer_.Stop();
+
+ pac_url_ = GURL(dhcp_query->url());
+ if (pac_url_.is_empty() || !pac_url_.is_valid()) {
+ result_ = ERR_PAC_NOT_IN_DHCP;
+ TransitionToFinish();
+ } else {
+ state_ = STATE_WAIT_URL;
+ script_fetcher_.reset(ImplCreateScriptFetcher());
+ script_fetcher_->Fetch(
+ pac_url_, &pac_script_,
+ base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone,
+ base::Unretained(this)));
+ }
+}
+
+void DhcpProxyScriptAdapterFetcher::OnTimeout() {
+ DCHECK_EQ(state_, STATE_WAIT_DHCP);
+ result_ = ERR_TIMED_OUT;
+ TransitionToFinish();
+}
+
+void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
+ if (state_ == STATE_CANCEL)
+ return;
+
+ // At this point, pac_script_ has already been written to.
+ script_fetcher_.reset();
+ result_ = result;
+ TransitionToFinish();
+}
+
+void DhcpProxyScriptAdapterFetcher::TransitionToFinish() {
+ DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
+ state_ = STATE_FINISH;
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+
+ // Be careful not to touch any member state after this, as the client
+ // may delete us during this callback.
+ callback.Run(result_);
+}
+
+DhcpProxyScriptAdapterFetcher::State
+ DhcpProxyScriptAdapterFetcher::state() const {
+ return state_;
+}
+
+ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() {
+ return new ProxyScriptFetcherImpl(url_request_context_);
+}
+
+DhcpProxyScriptAdapterFetcher::DhcpQuery*
+ DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() {
+ return new DhcpQuery();
+}
+
+base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const {
+ return base::TimeDelta::FromMilliseconds(kTimeoutMs);
+}
+
+// static
+std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(
+ const std::string& adapter_name) {
+ EnsureDhcpcsvcInit();
+
+ std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
+ CP_ACP);
+
+ DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL };
+
+ BYTE option_data[] = { 1, 252 };
+ DHCPCAPI_PARAMS wpad_params = { 0 };
+ wpad_params.OptionId = 252;
+ wpad_params.IsVendor = FALSE; // Surprising, but intentional.
+
+ DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
+ request_params.nParams = 1;
+ request_params.Params = &wpad_params;
+
+ // The maximum message size is typically 4096 bytes on Windows per
+ // http://support.microsoft.com/kb/321592
+ DWORD result_buffer_size = 4096;
+ scoped_ptr_malloc<BYTE> result_buffer;
+ int retry_count = 0;
+ DWORD res = NO_ERROR;
+ do {
+ result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size)));
+
+ // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
+ // there might be an asynchronous mode, there seems to be (at least in
+ // terms of well-documented use of this API) only a synchronous mode, with
+ // an optional "async notifications later if the option changes" mode.
+ // Even IE9, which we hope to emulate as IE is the most widely deployed
+ // previous implementation of the DHCP aspect of WPAD and the only one
+ // on Windows (Konqueror is the other, on Linux), uses this API with the
+ // synchronous flag. There seem to be several Microsoft Knowledge Base
+ // articles about calls to this function failing when other flags are used
+ // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
+ // chances on non-standard, poorly documented usage.
+ res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS,
+ NULL,
+ const_cast<LPWSTR>(adapter_name_wide.c_str()),
+ NULL,
+ send_params, request_params,
+ result_buffer.get(), &result_buffer_size,
+ NULL);
+ ++retry_count;
+ } while (res == ERROR_MORE_DATA && retry_count <= 3);
+
+ if (res != NO_ERROR) {
+ LOG(INFO) << "Error fetching PAC URL from DHCP: " << res;
+ UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1);
+ } else if (wpad_params.nBytesData) {
+#ifndef NDEBUG
+ // The result should be ASCII, not wide character. Some DHCP
+ // servers appear to count the trailing NULL in nBytesData, others
+ // do not.
+ size_t count_without_null =
+ strlen(reinterpret_cast<const char*>(wpad_params.Data));
+ DCHECK(count_without_null == wpad_params.nBytesData ||
+ count_without_null + 1 == wpad_params.nBytesData);
+#endif
+ // Belt and suspenders: First, ensure we NULL-terminate after
+ // nBytesData; this is the inner constructor with nBytesData as a
+ // parameter. Then, return only up to the first null in case of
+ // embedded NULLs; this is the outer constructor that takes the
+ // result of c_str() on the inner. If the server is giving us
+ // back a buffer with embedded NULLs, something is broken anyway.
+ return std::string(
+ std::string(reinterpret_cast<const char *>(wpad_params.Data),
+ wpad_params.nBytesData).c_str());
+ }
+
+ return "";
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h
new file mode 100644
index 00000000000..fadf2344656
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win.h
@@ -0,0 +1,176 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_DHCP_PROXY_SCRIPT_ADAPTER_FETCHER_WIN_H_
+#define NET_PROXY_DHCP_PROXY_SCRIPT_ADAPTER_FETCHER_WIN_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class ProxyScriptFetcher;
+class URLRequestContext;
+
+// For a given adapter, this class takes care of first doing a DHCP lookup
+// to get the PAC URL, then if there is one, trying to fetch it.
+class NET_EXPORT_PRIVATE DhcpProxyScriptAdapterFetcher
+ : public base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher>,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // |url_request_context| must outlive DhcpProxyScriptAdapterFetcher.
+ explicit DhcpProxyScriptAdapterFetcher(
+ URLRequestContext* url_request_context);
+ virtual ~DhcpProxyScriptAdapterFetcher();
+
+ // Starts a fetch. On completion (but not cancellation), |callback|
+ // will be invoked with the network error indicating success or failure
+ // of fetching a DHCP-configured PAC file on this adapter.
+ //
+ // On completion, results can be obtained via |GetPacScript()|, |GetPacURL()|.
+ //
+ // You may only call Fetch() once on a given instance of
+ // DhcpProxyScriptAdapterFetcher.
+ virtual void Fetch(const std::string& adapter_name,
+ const net::CompletionCallback& callback);
+
+ // Cancels the fetch on this adapter.
+ virtual void Cancel();
+
+ // Returns true if in the FINISH state (not CANCEL).
+ virtual bool DidFinish() const;
+
+ // Returns the network error indicating the result of the fetch. Will
+ // return IO_PENDING until the fetch is complete or cancelled. This is
+ // the same network error passed to the |callback| provided to |Fetch()|.
+ virtual int GetResult() const;
+
+ // Returns the contents of the PAC file retrieved. Only valid if
+ // |IsComplete()| is true. Returns the empty string if |GetResult()|
+ // returns anything other than OK.
+ virtual base::string16 GetPacScript() const;
+
+ // Returns the PAC URL retrieved from DHCP. Only guaranteed to be
+ // valid if |IsComplete()| is true. Returns an empty URL if no URL was
+ // configured in DHCP. May return a valid URL even if |result()| does
+ // not return OK (this would indicate that we found a URL configured in
+ // DHCP but failed to download it).
+ virtual GURL GetPacURL() const;
+
+ // Returns the PAC URL configured in DHCP for the given |adapter_name|, or
+ // the empty string if none is configured.
+ //
+ // This function executes synchronously due to limitations of the Windows
+ // DHCP client API.
+ static std::string GetPacURLFromDhcp(const std::string& adapter_name);
+
+ protected:
+ // This is the state machine for fetching from a given adapter.
+ //
+ // The state machine goes from START->WAIT_DHCP when it starts
+ // a worker thread to fetch the PAC URL from DHCP.
+ //
+ // In state WAIT_DHCP, if the DHCP query finishes and has no URL, it
+ // moves to state FINISH. If there is a URL, it starts a
+ // ProxyScriptFetcher to fetch it and moves to state WAIT_URL.
+ //
+ // It goes from WAIT_URL->FINISH when the ProxyScriptFetcher completes.
+ //
+ // In state FINISH, completion is indicated to the outer class, with
+ // the results of the fetch if a PAC script was successfully fetched.
+ //
+ // In state WAIT_DHCP, our timeout occurring can push us to FINISH.
+ //
+ // In any state except FINISH, a call to Cancel() will move to state
+ // CANCEL and cause all outstanding work to be cancelled or its
+ // results ignored when available.
+ enum State {
+ STATE_START,
+ STATE_WAIT_DHCP,
+ STATE_WAIT_URL,
+ STATE_FINISH,
+ STATE_CANCEL,
+ };
+
+ State state() const;
+
+ // This inner class encapsulates work done on a worker pool thread.
+ // By using a separate object, we can keep the main object completely
+ // thread safe and let it be non-refcounted.
+ class NET_EXPORT_PRIVATE DhcpQuery
+ : public base::RefCountedThreadSafe<DhcpQuery> {
+ public:
+ DhcpQuery();
+ virtual ~DhcpQuery();
+
+ // This method should run on a worker pool thread, via PostTaskAndReply.
+ // After it has run, the |url()| method on this object will return the
+ // URL retrieved.
+ void GetPacURLForAdapter(const std::string& adapter_name);
+
+ // Returns the URL retrieved for the given adapter, once the task has run.
+ const std::string& url() const;
+
+ protected:
+ // Virtual method introduced to allow unit testing.
+ virtual std::string ImplGetPacURLFromDhcp(const std::string& adapter_name);
+
+ private:
+ // The URL retrieved for the given adapter.
+ std::string url_;
+
+ DISALLOW_COPY_AND_ASSIGN(DhcpQuery);
+ };
+
+ // Virtual methods introduced to allow unit testing.
+ virtual ProxyScriptFetcher* ImplCreateScriptFetcher();
+ virtual DhcpQuery* ImplCreateDhcpQuery();
+ virtual base::TimeDelta ImplGetTimeout() const;
+
+ private:
+ // Event/state transition handlers
+ void OnDhcpQueryDone(scoped_refptr<DhcpQuery> dhcp_query);
+ void OnTimeout();
+ void OnFetcherDone(int result);
+ void TransitionToFinish();
+
+ // Current state of this state machine.
+ State state_;
+
+ // A network error indicating result of operation.
+ int result_;
+
+ // Empty string or the PAC script downloaded.
+ base::string16 pac_script_;
+
+ // Empty URL or the PAC URL configured in DHCP.
+ GURL pac_url_;
+
+ // Callback to let our client know we're done. Invalid in states
+ // START, FINISH and CANCEL.
+ net::CompletionCallback callback_;
+
+ // Fetcher to retrieve PAC files once URL is known.
+ scoped_ptr<ProxyScriptFetcher> script_fetcher_;
+
+ // Implements a timeout on the call to the Win32 DHCP API.
+ base::OneShotTimer<DhcpProxyScriptAdapterFetcher> wait_timer_;
+
+ URLRequestContext* const url_request_context_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptAdapterFetcher);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_DHCP_PROXY_SCRIPT_ADAPTER_FETCHER_WIN_H_
diff --git a/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc
new file mode 100644
index 00000000000..be177fa8ab8
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc
@@ -0,0 +1,299 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
+
+#include "base/perftimer.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/mock_proxy_script_fetcher.h"
+#include "net/proxy/proxy_script_fetcher_impl.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const char* const kPacUrl = "http://pacserver/script.pac";
+
+// In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few
+// tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with
+// DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32
+// APIs and the network. In this file we test only by stubbing out
+// functionality.
+
+// Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies
+// to allow unit testing.
+class MockDhcpProxyScriptAdapterFetcher
+ : public DhcpProxyScriptAdapterFetcher {
+ public:
+ explicit MockDhcpProxyScriptAdapterFetcher(URLRequestContext* context)
+ : DhcpProxyScriptAdapterFetcher(context),
+ dhcp_delay_(base::TimeDelta::FromMilliseconds(1)),
+ timeout_(TestTimeouts::action_timeout()),
+ configured_url_(kPacUrl),
+ fetcher_delay_ms_(1),
+ fetcher_result_(OK),
+ pac_script_("bingo") {
+ }
+
+ void Cancel() {
+ DhcpProxyScriptAdapterFetcher::Cancel();
+ fetcher_ = NULL;
+ }
+
+ virtual ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE {
+ // We don't maintain ownership of the fetcher, it is transferred to
+ // the caller.
+ fetcher_ = new MockProxyScriptFetcher();
+ if (fetcher_delay_ms_ != -1) {
+ fetcher_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(fetcher_delay_ms_),
+ this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer);
+ }
+ return fetcher_;
+ }
+
+ class DelayingDhcpQuery : public DhcpQuery {
+ public:
+ explicit DelayingDhcpQuery()
+ : DhcpQuery(),
+ test_finished_event_(true, false) {
+ }
+
+ std::string ImplGetPacURLFromDhcp(
+ const std::string& adapter_name) OVERRIDE {
+ PerfTimer timer;
+ test_finished_event_.TimedWait(dhcp_delay_);
+ return configured_url_;
+ }
+
+ base::WaitableEvent test_finished_event_;
+ base::TimeDelta dhcp_delay_;
+ std::string configured_url_;
+ };
+
+ virtual DhcpQuery* ImplCreateDhcpQuery() OVERRIDE {
+ dhcp_query_ = new DelayingDhcpQuery();
+ dhcp_query_->dhcp_delay_ = dhcp_delay_;
+ dhcp_query_->configured_url_ = configured_url_;
+ return dhcp_query_;
+ }
+
+ // Use a shorter timeout so tests can finish more quickly.
+ virtual base::TimeDelta ImplGetTimeout() const OVERRIDE {
+ return timeout_;
+ }
+
+ void OnFetcherTimer() {
+ // Note that there is an assumption by this mock implementation that
+ // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher
+ // and call Fetch on the fetcher before the message loop is re-entered.
+ // This holds true today, but if you hit this DCHECK the problem can
+ // possibly be resolved by having a separate subclass of
+ // MockProxyScriptFetcher that adds the delay internally (instead of
+ // the simple approach currently used in ImplCreateScriptFetcher above).
+ DCHECK(fetcher_ && fetcher_->has_pending_request());
+ fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_);
+ fetcher_ = NULL;
+ }
+
+ bool IsWaitingForFetcher() const {
+ return state() == STATE_WAIT_URL;
+ }
+
+ bool WasCancelled() const {
+ return state() == STATE_CANCEL;
+ }
+
+ void FinishTest() {
+ DCHECK(dhcp_query_);
+ dhcp_query_->test_finished_event_.Signal();
+ }
+
+ base::TimeDelta dhcp_delay_;
+ base::TimeDelta timeout_;
+ std::string configured_url_;
+ int fetcher_delay_ms_;
+ int fetcher_result_;
+ std::string pac_script_;
+ MockProxyScriptFetcher* fetcher_;
+ base::OneShotTimer<MockDhcpProxyScriptAdapterFetcher> fetcher_timer_;
+ scoped_refptr<DelayingDhcpQuery> dhcp_query_;
+};
+
+class FetcherClient {
+ public:
+ FetcherClient()
+ : url_request_context_(new TestURLRequestContext()),
+ fetcher_(
+ new MockDhcpProxyScriptAdapterFetcher(url_request_context_.get())) {
+ }
+
+ void WaitForResult(int expected_error) {
+ EXPECT_EQ(expected_error, callback_.WaitForResult());
+ }
+
+ void RunTest() {
+ fetcher_->Fetch("adapter name", callback_.callback());
+ }
+
+ void FinishTestAllowCleanup() {
+ fetcher_->FinishTest();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ TestCompletionCallback callback_;
+ scoped_ptr<URLRequestContext> url_request_context_;
+ scoped_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_;
+ base::string16 pac_text_;
+};
+
+TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) {
+ FetcherClient client;
+ client.fetcher_->configured_url_ = "";
+ client.RunTest();
+ client.WaitForResult(ERR_PAC_NOT_IN_DHCP);
+ ASSERT_TRUE(client.fetcher_->DidFinish());
+ EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
+}
+
+TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) {
+ FetcherClient client;
+ client.RunTest();
+ client.WaitForResult(OK);
+ ASSERT_TRUE(client.fetcher_->DidFinish());
+ EXPECT_EQ(OK, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript());
+ EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
+}
+
+TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) {
+ // Does a Fetch() with a long enough delay on accessing DHCP that the
+ // fetcher should time out. This is to test a case manual testing found,
+ // where under certain circumstances (e.g. adapter enabled for DHCP and
+ // needs to retrieve its configuration from DHCP, but no DHCP server
+ // present on the network) accessing DHCP can take on the order of tens
+ // of seconds.
+ FetcherClient client;
+ client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout();
+ client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25);
+
+ PerfTimer timer;
+ client.RunTest();
+ // An error different from this would be received if the timeout didn't
+ // kick in.
+ client.WaitForResult(ERR_TIMED_OUT);
+
+ ASSERT_TRUE(client.fetcher_->DidFinish());
+ EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
+ EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
+ client.FinishTestAllowCleanup();
+}
+
+TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) {
+ FetcherClient client;
+ client.RunTest();
+ client.fetcher_->Cancel();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_FALSE(client.fetcher_->DidFinish());
+ ASSERT_TRUE(client.fetcher_->WasCancelled());
+ EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
+ EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
+ client.FinishTestAllowCleanup();
+}
+
+TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) {
+ FetcherClient client;
+ // This causes the mock fetcher not to pretend the
+ // fetcher finishes after a timeout.
+ client.fetcher_->fetcher_delay_ms_ = -1;
+ client.RunTest();
+ int max_loops = 4;
+ while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ client.fetcher_->Cancel();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_FALSE(client.fetcher_->DidFinish());
+ ASSERT_TRUE(client.fetcher_->WasCancelled());
+ EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
+ // GetPacURL() still returns the URL fetched in this case.
+ EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
+ client.FinishTestAllowCleanup();
+}
+
+TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) {
+ FetcherClient client;
+ client.RunTest();
+ client.WaitForResult(OK);
+ client.fetcher_->Cancel();
+ // Canceling after you're done should have no effect, so these
+ // are identical expectations to the NormalCaseURLInDhcp test.
+ ASSERT_TRUE(client.fetcher_->DidFinish());
+ EXPECT_EQ(OK, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript());
+ EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
+ client.FinishTestAllowCleanup();
+}
+
+// Does a real fetch on a mock DHCP configuration.
+class MockDhcpRealFetchProxyScriptAdapterFetcher
+ : public MockDhcpProxyScriptAdapterFetcher {
+ public:
+ explicit MockDhcpRealFetchProxyScriptAdapterFetcher(
+ URLRequestContext* context)
+ : MockDhcpProxyScriptAdapterFetcher(context),
+ url_request_context_(context) {
+ }
+
+ // Returns a real proxy script fetcher.
+ ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE {
+ ProxyScriptFetcher* fetcher =
+ new ProxyScriptFetcherImpl(url_request_context_);
+ return fetcher;
+ }
+
+ URLRequestContext* url_request_context_;
+};
+
+TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) {
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(
+ FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest")));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL configured_url = test_server.GetURL("files/downloadable.pac");
+
+ FetcherClient client;
+ TestURLRequestContext url_request_context;
+ client.fetcher_.reset(
+ new MockDhcpRealFetchProxyScriptAdapterFetcher(
+ &url_request_context));
+ client.fetcher_->configured_url_ = configured_url.spec();
+ client.RunTest();
+ client.WaitForResult(OK);
+ ASSERT_TRUE(client.fetcher_->DidFinish());
+ EXPECT_EQ(OK, client.fetcher_->GetResult());
+ EXPECT_EQ(base::string16(L"-downloadable.pac-\n"),
+ client.fetcher_->GetPacScript());
+ EXPECT_EQ(configured_url,
+ client.fetcher_->GetPacURL());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher.cc b/chromium/net/proxy/dhcp_proxy_script_fetcher.cc
new file mode 100644
index 00000000000..1771be05d64
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+
+#include "net/base/net_errors.h"
+
+namespace net {
+
+std::string DhcpProxyScriptFetcher::GetFetcherName() const {
+ return std::string();
+}
+
+DhcpProxyScriptFetcher::DhcpProxyScriptFetcher() {}
+
+DhcpProxyScriptFetcher::~DhcpProxyScriptFetcher() {}
+
+DoNothingDhcpProxyScriptFetcher::DoNothingDhcpProxyScriptFetcher() {}
+
+DoNothingDhcpProxyScriptFetcher::~DoNothingDhcpProxyScriptFetcher() {}
+
+int DoNothingDhcpProxyScriptFetcher::Fetch(
+ base::string16* utf16_text, const CompletionCallback& callback) {
+ return ERR_NOT_IMPLEMENTED;
+}
+
+void DoNothingDhcpProxyScriptFetcher::Cancel() {}
+
+const GURL& DoNothingDhcpProxyScriptFetcher::GetPacURL() const {
+ return gurl_;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher.h b/chromium/net/proxy/dhcp_proxy_script_fetcher.h
new file mode 100644
index 00000000000..0ec2ed4463c
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher.h
@@ -0,0 +1,99 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_DHCP_SCRIPT_FETCHER_H_
+#define NET_PROXY_DHCP_SCRIPT_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// Interface for classes that can fetch a proxy script as configured via DHCP.
+//
+// The Fetch method on this interface tries to retrieve the most appropriate
+// PAC script configured via DHCP.
+//
+// Normally there are zero or one DHCP scripts configured, but in the
+// presence of multiple adapters with DHCP enabled, the fetcher resolves
+// which PAC script to use if one or more are available.
+class NET_EXPORT_PRIVATE DhcpProxyScriptFetcher {
+ public:
+ // Destruction should cancel any outstanding requests.
+ virtual ~DhcpProxyScriptFetcher();
+
+ // Attempts to retrieve the most appropriate PAC script configured via DHCP,
+ // and invokes |callback| on completion.
+ //
+ // Returns OK on success, otherwise the error code. If the return code is
+ // ERR_IO_PENDING, then the request completes asynchronously, and |callback|
+ // will be invoked later with the final error code.
+ //
+ // After synchronous or asynchronous completion with a result code of OK,
+ // |*utf16_text| is filled with the response. On failure, the result text is
+ // an empty string, and the result code is a network error. Some special
+ // network errors that may occur are:
+ //
+ // ERR_PAC_NOT_IN_DHCP -- no script configured in DHCP.
+ //
+ // The following all indicate there was one or more script configured
+ // in DHCP but all failed to download, and the error for the most
+ // preferred adapter that had a script configured was what the error
+ // code says:
+ //
+ // ERR_TIMED_OUT -- fetch took too long to complete.
+ // ERR_FILE_TOO_BIG -- response body was too large.
+ // ERR_PAC_STATUS_NOT_OK -- script failed to download.
+ // ERR_NOT_IMPLEMENTED -- script required authentication.
+ //
+ // If the request is cancelled (either using the "Cancel()" method or by
+ // deleting |this|), then no callback is invoked.
+ //
+ // Only one fetch is allowed to be outstanding at a time.
+ virtual int Fetch(base::string16* utf16_text,
+ const CompletionCallback& callback) = 0;
+
+ // Aborts the in-progress fetch (if any).
+ virtual void Cancel() = 0;
+
+ // After successful completion of |Fetch()|, this will return the URL
+ // retrieved from DHCP. It is reset if/when |Fetch()| is called again.
+ virtual const GURL& GetPacURL() const = 0;
+
+ // Intended for unit tests only, so they can test that factories return
+ // the right types under given circumstances.
+ virtual std::string GetFetcherName() const;
+
+ protected:
+ DhcpProxyScriptFetcher();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DhcpProxyScriptFetcher);
+};
+
+// A do-nothing retriever, always returns synchronously with
+// ERR_NOT_IMPLEMENTED result and empty text.
+class NET_EXPORT_PRIVATE DoNothingDhcpProxyScriptFetcher
+ : public DhcpProxyScriptFetcher {
+ public:
+ DoNothingDhcpProxyScriptFetcher();
+ virtual ~DoNothingDhcpProxyScriptFetcher();
+
+ virtual int Fetch(base::string16* utf16_text,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual const GURL& GetPacURL() const OVERRIDE;
+ private:
+ GURL gurl_;
+ DISALLOW_COPY_AND_ASSIGN(DoNothingDhcpProxyScriptFetcher);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_DHCP_SCRIPT_FETCHER_H_
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.cc b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.cc
new file mode 100644
index 00000000000..01ede05b093
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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/proxy/dhcp_proxy_script_fetcher_factory.h"
+
+#include "net/base/net_errors.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+
+#if defined(OS_WIN)
+#include "net/proxy/dhcp_proxy_script_fetcher_win.h"
+#endif
+
+namespace net {
+
+DhcpProxyScriptFetcherFactory::DhcpProxyScriptFetcherFactory()
+ : feature_enabled_(false) {
+ set_enabled(true);
+}
+
+DhcpProxyScriptFetcher* DhcpProxyScriptFetcherFactory::Create(
+ URLRequestContext* context) {
+ if (!feature_enabled_) {
+ return new DoNothingDhcpProxyScriptFetcher();
+ } else {
+ DCHECK(IsSupported());
+ DhcpProxyScriptFetcher* ret = NULL;
+#if defined(OS_WIN)
+ ret = new DhcpProxyScriptFetcherWin(context);
+#endif
+ DCHECK(ret);
+ return ret;
+ }
+}
+
+void DhcpProxyScriptFetcherFactory::set_enabled(bool enabled) {
+ if (IsSupported()) {
+ feature_enabled_ = enabled;
+ }
+}
+
+bool DhcpProxyScriptFetcherFactory::enabled() const {
+ return feature_enabled_;
+}
+
+// static
+bool DhcpProxyScriptFetcherFactory::IsSupported() {
+#if defined(OS_WIN)
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.h b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.h
new file mode 100644
index 00000000000..147435d6f0b
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory.h
@@ -0,0 +1,68 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_DHCP_SCRIPT_FETCHER_FACTORY_H_
+#define NET_PROXY_DHCP_SCRIPT_FETCHER_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class DhcpProxyScriptFetcher;
+class URLRequestContext;
+
+// Factory object for creating the appropriate concrete base class of
+// DhcpProxyScriptFetcher for your operating system and settings.
+//
+// You might think we could just implement a DHCP client at the protocol
+// level and have cross-platform support for retrieving PAC configuration
+// from DHCP, but unfortunately the DHCP protocol assumes there is a single
+// client per machine (specifically per network interface card), and there
+// is an implicit state machine between the client and server, so adding a
+// second client to the machine would not be advisable (see e.g. some
+// discussion of what can happen at this URL:
+// http://www.net.princeton.edu/multi-dhcp-one-interface-handling.html).
+//
+// Therefore, we have platform-specific implementations, and so we use
+// this factory to select the right one.
+class NET_EXPORT DhcpProxyScriptFetcherFactory {
+ public:
+ // Creates a new factory object with default settings.
+ DhcpProxyScriptFetcherFactory();
+
+ // Ownership is transferred to the caller. url_request_context must be valid
+ // and its lifetime must exceed that of the returned DhcpProxyScriptFetcher.
+ //
+ // Note that while a request is in progress, the fetcher may be holding a
+ // reference to |url_request_context|. Be careful not to create cycles
+ // between the fetcher and the context; you can break such cycles by calling
+ // Cancel().
+ DhcpProxyScriptFetcher* Create(URLRequestContext* url_request_context);
+
+ // Attempts to enable/disable the DHCP WPAD feature. Does nothing
+ // if |IsSupported()| returns false.
+ //
+ // The default is |enabled() == true|.
+ void set_enabled(bool enabled);
+
+ // Returns true if the DHCP WPAD feature is enabled. Always returns
+ // false if |IsSupported()| is false.
+ bool enabled() const;
+
+ // Returns true if the DHCP WPAD feature is supported on the current
+ // operating system.
+ static bool IsSupported();
+
+ private:
+ bool feature_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(DhcpProxyScriptFetcherFactory);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_DHCP_SCRIPT_FETCHER_FACTORY_H_
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc
new file mode 100644
index 00000000000..9eb7c67f1f1
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_factory_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+#include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+TEST(DhcpProxyScriptFetcherFactoryTest, DoNothingWhenDisabled) {
+ DhcpProxyScriptFetcherFactory factory;
+ factory.set_enabled(false);
+ scoped_ptr<DhcpProxyScriptFetcher> fetcher(factory.Create(NULL));
+ EXPECT_EQ("", fetcher->GetFetcherName());
+}
+
+#if defined(OS_WIN)
+TEST(DhcpProxyScriptFetcherFactoryTest, WindowsFetcherOnWindows) {
+ DhcpProxyScriptFetcherFactory factory;
+ factory.set_enabled(true);
+
+ scoped_ptr<TestURLRequestContext> context(new TestURLRequestContext());
+ scoped_ptr<DhcpProxyScriptFetcher> fetcher(factory.Create(context.get()));
+ EXPECT_EQ("win", fetcher->GetFetcherName());
+}
+#endif // defined(OS_WIN)
+
+TEST(DhcpProxyScriptFetcherFactoryTest, IsSupported) {
+#if defined(OS_WIN)
+ ASSERT_TRUE(DhcpProxyScriptFetcherFactory::IsSupported());
+#else
+ ASSERT_FALSE(DhcpProxyScriptFetcherFactory::IsSupported());
+#endif // defined(OS_WIN)
+}
+
+TEST(DhcpProxyScriptFetcherFactoryTest, SetEnabled) {
+ DhcpProxyScriptFetcherFactory factory;
+#if defined(OS_WIN)
+ EXPECT_TRUE(factory.enabled());
+#else
+ EXPECT_FALSE(factory.enabled());
+#endif // defined(OS_WIN)
+
+ factory.set_enabled(false);
+ EXPECT_FALSE(factory.enabled());
+
+ factory.set_enabled(true);
+#if defined(OS_WIN)
+ EXPECT_TRUE(factory.enabled());
+#else
+ EXPECT_FALSE(factory.enabled());
+#endif // defined(OS_WIN)
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_win.cc b/chromium/net/proxy/dhcp_proxy_script_fetcher_win.cc
new file mode 100644
index 00000000000..9e34f5122e4
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_win.cc
@@ -0,0 +1,370 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_fetcher_win.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/perftimer.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
+
+#include <winsock2.h>
+#include <iphlpapi.h>
+#pragma comment(lib, "iphlpapi.lib")
+
+namespace {
+
+// How long to wait at maximum after we get results (a PAC file or
+// knowledge that no PAC file is configured) from whichever network
+// adapter finishes first.
+const int kMaxWaitAfterFirstResultMs = 400;
+
+const int kGetAdaptersAddressesErrors[] = {
+ ERROR_ADDRESS_NOT_ASSOCIATED,
+ ERROR_BUFFER_OVERFLOW,
+ ERROR_INVALID_PARAMETER,
+ ERROR_NOT_ENOUGH_MEMORY,
+ ERROR_NO_DATA,
+};
+
+} // namespace
+
+namespace net {
+
+DhcpProxyScriptFetcherWin::DhcpProxyScriptFetcherWin(
+ URLRequestContext* url_request_context)
+ : state_(STATE_START),
+ num_pending_fetchers_(0),
+ destination_string_(NULL),
+ url_request_context_(url_request_context) {
+ DCHECK(url_request_context_);
+}
+
+DhcpProxyScriptFetcherWin::~DhcpProxyScriptFetcherWin() {
+ // Count as user-initiated if we are not yet in STATE_DONE.
+ Cancel();
+}
+
+int DhcpProxyScriptFetcherWin::Fetch(base::string16* utf16_text,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (state_ != STATE_START && state_ != STATE_DONE) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ fetch_start_time_ = base::TimeTicks::Now();
+
+ state_ = STATE_WAIT_ADAPTERS;
+ callback_ = callback;
+ destination_string_ = utf16_text;
+
+ last_query_ = ImplCreateAdapterQuery();
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(
+ &DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames,
+ last_query_.get()),
+ base::Bind(
+ &DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone,
+ AsWeakPtr(),
+ last_query_),
+ true);
+
+ return ERR_IO_PENDING;
+}
+
+void DhcpProxyScriptFetcherWin::Cancel() {
+ DCHECK(CalledOnValidThread());
+
+ if (state_ != STATE_DONE) {
+ // We only count this stat if the cancel was explicitly initiated by
+ // our client, and if we weren't already in STATE_DONE.
+ UMA_HISTOGRAM_TIMES("Net.DhcpWpadCancelTime",
+ base::TimeTicks::Now() - fetch_start_time_);
+ }
+
+ CancelImpl();
+}
+
+void DhcpProxyScriptFetcherWin::CancelImpl() {
+ DCHECK(CalledOnValidThread());
+
+ if (state_ != STATE_DONE) {
+ callback_.Reset();
+ wait_timer_.Stop();
+ state_ = STATE_DONE;
+
+ for (FetcherVector::iterator it = fetchers_.begin();
+ it != fetchers_.end();
+ ++it) {
+ (*it)->Cancel();
+ }
+
+ fetchers_.clear();
+ }
+}
+
+void DhcpProxyScriptFetcherWin::OnGetCandidateAdapterNamesDone(
+ scoped_refptr<AdapterQuery> query) {
+ DCHECK(CalledOnValidThread());
+
+ // This can happen if this object is reused for multiple queries,
+ // and a previous query was cancelled before it completed.
+ if (query.get() != last_query_.get())
+ return;
+ last_query_ = NULL;
+
+ // Enable unit tests to wait for this to happen; in production this function
+ // call is a no-op.
+ ImplOnGetCandidateAdapterNamesDone();
+
+ // We may have been cancelled.
+ if (state_ != STATE_WAIT_ADAPTERS)
+ return;
+
+ state_ = STATE_NO_RESULTS;
+
+ const std::set<std::string>& adapter_names = query->adapter_names();
+
+ if (adapter_names.empty()) {
+ TransitionToDone();
+ return;
+ }
+
+ for (std::set<std::string>::const_iterator it = adapter_names.begin();
+ it != adapter_names.end();
+ ++it) {
+ DhcpProxyScriptAdapterFetcher* fetcher(ImplCreateAdapterFetcher());
+ fetcher->Fetch(
+ *it, base::Bind(&DhcpProxyScriptFetcherWin::OnFetcherDone,
+ base::Unretained(this)));
+ fetchers_.push_back(fetcher);
+ }
+ num_pending_fetchers_ = fetchers_.size();
+}
+
+std::string DhcpProxyScriptFetcherWin::GetFetcherName() const {
+ DCHECK(CalledOnValidThread());
+ return "win";
+}
+
+const GURL& DhcpProxyScriptFetcherWin::GetPacURL() const {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(state_, STATE_DONE);
+
+ return pac_url_;
+}
+
+void DhcpProxyScriptFetcherWin::OnFetcherDone(int result) {
+ DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
+
+ if (--num_pending_fetchers_ == 0) {
+ TransitionToDone();
+ return;
+ }
+
+ // If the only pending adapters are those less preferred than one
+ // with a valid PAC script, we do not need to wait any longer.
+ for (FetcherVector::iterator it = fetchers_.begin();
+ it != fetchers_.end();
+ ++it) {
+ bool did_finish = (*it)->DidFinish();
+ int result = (*it)->GetResult();
+ if (did_finish && result == OK) {
+ TransitionToDone();
+ return;
+ }
+ if (!did_finish || result != ERR_PAC_NOT_IN_DHCP) {
+ break;
+ }
+ }
+
+ // Once we have a single result, we set a maximum on how long to wait
+ // for the rest of the results.
+ if (state_ == STATE_NO_RESULTS) {
+ state_ = STATE_SOME_RESULTS;
+ wait_timer_.Start(FROM_HERE,
+ ImplGetMaxWait(), this, &DhcpProxyScriptFetcherWin::OnWaitTimer);
+ }
+}
+
+void DhcpProxyScriptFetcherWin::OnWaitTimer() {
+ DCHECK_EQ(state_, STATE_SOME_RESULTS);
+
+ // These are intended to help us understand whether our timeout may
+ // be too aggressive or not aggressive enough.
+ UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumAdaptersAtWaitTimer",
+ fetchers_.size());
+ UMA_HISTOGRAM_COUNTS_100("Net.DhcpWpadNumPendingAdaptersAtWaitTimer",
+ num_pending_fetchers_);
+
+ TransitionToDone();
+}
+
+void DhcpProxyScriptFetcherWin::TransitionToDone() {
+ DCHECK(state_ == STATE_NO_RESULTS || state_ == STATE_SOME_RESULTS);
+
+ int result = ERR_PAC_NOT_IN_DHCP; // Default if no fetchers.
+ if (!fetchers_.empty()) {
+ // Scan twice for the result; once through the whole list for success,
+ // then if no success, return result for most preferred network adapter,
+ // preferring "real" network errors to the ERR_PAC_NOT_IN_DHCP error.
+ // Default to ERR_ABORTED if no fetcher completed.
+ result = ERR_ABORTED;
+ for (FetcherVector::iterator it = fetchers_.begin();
+ it != fetchers_.end();
+ ++it) {
+ if ((*it)->DidFinish() && (*it)->GetResult() == OK) {
+ result = OK;
+ *destination_string_ = (*it)->GetPacScript();
+ pac_url_ = (*it)->GetPacURL();
+ break;
+ }
+ }
+ if (result != OK) {
+ destination_string_->clear();
+ for (FetcherVector::iterator it = fetchers_.begin();
+ it != fetchers_.end();
+ ++it) {
+ if ((*it)->DidFinish()) {
+ result = (*it)->GetResult();
+ if (result != ERR_PAC_NOT_IN_DHCP) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ CompletionCallback callback = callback_;
+ CancelImpl();
+ DCHECK_EQ(state_, STATE_DONE);
+ DCHECK(fetchers_.empty());
+ DCHECK(callback_.is_null()); // Invariant of data.
+
+ UMA_HISTOGRAM_TIMES("Net.DhcpWpadCompletionTime",
+ base::TimeTicks::Now() - fetch_start_time_);
+
+ if (result != OK) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.DhcpWpadFetchError", std::abs(result), GetAllErrorCodesForUma());
+ }
+
+ // We may be deleted re-entrantly within this outcall.
+ callback.Run(result);
+}
+
+int DhcpProxyScriptFetcherWin::num_pending_fetchers() const {
+ return num_pending_fetchers_;
+}
+
+URLRequestContext* DhcpProxyScriptFetcherWin::url_request_context() const {
+ return url_request_context_;
+}
+
+DhcpProxyScriptAdapterFetcher*
+ DhcpProxyScriptFetcherWin::ImplCreateAdapterFetcher() {
+ return new DhcpProxyScriptAdapterFetcher(url_request_context_);
+}
+
+DhcpProxyScriptFetcherWin::AdapterQuery*
+ DhcpProxyScriptFetcherWin::ImplCreateAdapterQuery() {
+ return new AdapterQuery();
+}
+
+base::TimeDelta DhcpProxyScriptFetcherWin::ImplGetMaxWait() {
+ return base::TimeDelta::FromMilliseconds(kMaxWaitAfterFirstResultMs);
+}
+
+bool DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(
+ std::set<std::string>* adapter_names) {
+ DCHECK(adapter_names);
+ adapter_names->clear();
+
+ // The GetAdaptersAddresses MSDN page recommends using a size of 15000 to
+ // avoid reallocation.
+ ULONG adapters_size = 15000;
+ scoped_ptr_malloc<IP_ADAPTER_ADDRESSES> adapters;
+ ULONG error = ERROR_SUCCESS;
+ int num_tries = 0;
+
+ PerfTimer time_api_access;
+ do {
+ adapters.reset(
+ reinterpret_cast<IP_ADAPTER_ADDRESSES*>(malloc(adapters_size)));
+ // Return only unicast addresses, and skip information we do not need.
+ error = GetAdaptersAddresses(AF_UNSPEC,
+ GAA_FLAG_SKIP_ANYCAST |
+ GAA_FLAG_SKIP_MULTICAST |
+ GAA_FLAG_SKIP_DNS_SERVER |
+ GAA_FLAG_SKIP_FRIENDLY_NAME,
+ NULL,
+ adapters.get(),
+ &adapters_size);
+ ++num_tries;
+ } while (error == ERROR_BUFFER_OVERFLOW && num_tries <= 3);
+
+ // This is primarily to validate our belief that the GetAdaptersAddresses API
+ // function is fast enough to call synchronously from the network thread.
+ UMA_HISTOGRAM_TIMES("Net.DhcpWpadGetAdaptersAddressesTime",
+ time_api_access.Elapsed());
+
+ if (error != ERROR_SUCCESS) {
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION(
+ "Net.DhcpWpadGetAdaptersAddressesError",
+ error,
+ base::CustomHistogram::ArrayToCustomRanges(
+ kGetAdaptersAddressesErrors,
+ arraysize(kGetAdaptersAddressesErrors)));
+ }
+
+ if (error == ERROR_NO_DATA) {
+ // There are no adapters that we care about.
+ return true;
+ }
+
+ if (error != ERROR_SUCCESS) {
+ LOG(WARNING) << "Unexpected error retrieving WPAD configuration from DHCP.";
+ return false;
+ }
+
+ IP_ADAPTER_ADDRESSES* adapter = NULL;
+ for (adapter = adapters.get(); adapter; adapter = adapter->Next) {
+ if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
+ continue;
+ if ((adapter->Flags & IP_ADAPTER_DHCP_ENABLED) == 0)
+ continue;
+
+ DCHECK(adapter->AdapterName);
+ adapter_names->insert(adapter->AdapterName);
+ }
+
+ return true;
+}
+
+DhcpProxyScriptFetcherWin::AdapterQuery::AdapterQuery() {
+}
+
+DhcpProxyScriptFetcherWin::AdapterQuery::~AdapterQuery() {
+}
+
+void DhcpProxyScriptFetcherWin::AdapterQuery::GetCandidateAdapterNames() {
+ ImplGetCandidateAdapterNames(&adapter_names_);
+}
+
+const std::set<std::string>&
+ DhcpProxyScriptFetcherWin::AdapterQuery::adapter_names() const {
+ return adapter_names_;
+}
+
+bool DhcpProxyScriptFetcherWin::AdapterQuery::ImplGetCandidateAdapterNames(
+ std::set<std::string>* adapter_names) {
+ return DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(adapter_names);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_win.h b/chromium/net/proxy/dhcp_proxy_script_fetcher_win.h
new file mode 100644
index 00000000000..79fc4b348ef
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_win.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_
+#define NET_PROXY_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+
+namespace net {
+
+class DhcpProxyScriptAdapterFetcher;
+class URLRequestContext;
+
+// Windows-specific implementation.
+class NET_EXPORT_PRIVATE DhcpProxyScriptFetcherWin
+ : public DhcpProxyScriptFetcher,
+ public base::SupportsWeakPtr<DhcpProxyScriptFetcherWin>,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Creates a DhcpProxyScriptFetcherWin that issues requests through
+ // |url_request_context|. |url_request_context| must remain valid for
+ // the lifetime of DhcpProxyScriptFetcherWin.
+ explicit DhcpProxyScriptFetcherWin(URLRequestContext* url_request_context);
+ virtual ~DhcpProxyScriptFetcherWin();
+
+ // DhcpProxyScriptFetcher implementation.
+ int Fetch(base::string16* utf16_text,
+ const net::CompletionCallback& callback) OVERRIDE;
+ void Cancel() OVERRIDE;
+ const GURL& GetPacURL() const OVERRIDE;
+ std::string GetFetcherName() const OVERRIDE;
+
+ // Sets |adapter_names| to contain the name of each network adapter on
+ // this machine that has DHCP enabled and is not a loop-back adapter. Returns
+ // false on error.
+ static bool GetCandidateAdapterNames(std::set<std::string>* adapter_names);
+
+ protected:
+ int num_pending_fetchers() const;
+
+ URLRequestContext* url_request_context() const;
+
+ // This inner class encapsulate work done on a worker pool thread.
+ // The class calls GetCandidateAdapterNames, which can take a couple of
+ // hundred milliseconds.
+ class NET_EXPORT_PRIVATE AdapterQuery
+ : public base::RefCountedThreadSafe<AdapterQuery> {
+ public:
+ AdapterQuery();
+ virtual ~AdapterQuery();
+
+ // This is the method that runs on the worker pool thread.
+ void GetCandidateAdapterNames();
+
+ // This set is valid after GetCandidateAdapterNames has
+ // been run. Its lifetime is scoped by this object.
+ const std::set<std::string>& adapter_names() const;
+
+ protected:
+ // Virtual method introduced to allow unit testing.
+ virtual bool ImplGetCandidateAdapterNames(
+ std::set<std::string>* adapter_names);
+
+ private:
+ // This is constructed on the originating thread, then used on the
+ // worker thread, then used again on the originating thread only when
+ // the task has completed on the worker thread. No locking required.
+ std::set<std::string> adapter_names_;
+
+ DISALLOW_COPY_AND_ASSIGN(AdapterQuery);
+ };
+
+ // Virtual methods introduced to allow unit testing.
+ virtual DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher();
+ virtual AdapterQuery* ImplCreateAdapterQuery();
+ virtual base::TimeDelta ImplGetMaxWait();
+ virtual void ImplOnGetCandidateAdapterNamesDone() {}
+
+ private:
+ // Event/state transition handlers
+ void CancelImpl();
+ void OnGetCandidateAdapterNamesDone(scoped_refptr<AdapterQuery> query);
+ void OnFetcherDone(int result);
+ void OnWaitTimer();
+ void TransitionToDone();
+
+ // This is the outer state machine for fetching PAC configuration from
+ // DHCP. It relies for sub-states on the state machine of the
+ // DhcpProxyScriptAdapterFetcher class.
+ //
+ // The goal of the implementation is to the following work in parallel
+ // for all network adapters that are using DHCP:
+ // a) Try to get the PAC URL configured in DHCP;
+ // b) If one is configured, try to fetch the PAC URL.
+ // c) Once this is done for all adapters, or a timeout has passed after
+ // it has completed for the fastest adapter, return the PAC file
+ // available for the most preferred network adapter, if any.
+ //
+ // The state machine goes from START->WAIT_ADAPTERS when it starts a
+ // worker thread to get the list of adapters with DHCP enabled.
+ // It then goes from WAIT_ADAPTERS->NO_RESULTS when it creates
+ // and starts an DhcpProxyScriptAdapterFetcher for each adapter. It goes
+ // from NO_RESULTS->SOME_RESULTS when it gets the first result; at this
+ // point a wait timer is started. It goes from SOME_RESULTS->DONE in
+ // two cases: All results are known, or the wait timer expired. A call
+ // to Cancel() will also go straight to DONE from any state. Any
+ // way the DONE state is entered, we will at that point cancel any
+ // outstanding work and return the best known PAC script or the empty
+ // string.
+ //
+ // The state machine is reset for each Fetch(), a call to which is
+ // only valid in states START and DONE, as only one Fetch() is
+ // allowed to be outstanding at any given time.
+ enum State {
+ STATE_START,
+ STATE_WAIT_ADAPTERS,
+ STATE_NO_RESULTS,
+ STATE_SOME_RESULTS,
+ STATE_DONE,
+ };
+
+ // Current state of this state machine.
+ State state_;
+
+ // Vector, in Windows' network adapter preference order, of
+ // DhcpProxyScriptAdapterFetcher objects that are or were attempting
+ // to fetch a PAC file based on DHCP configuration.
+ typedef ScopedVector<DhcpProxyScriptAdapterFetcher> FetcherVector;
+ FetcherVector fetchers_;
+
+ // Number of fetchers we are waiting for.
+ int num_pending_fetchers_;
+
+ // Lets our client know we're done. Not valid in states START or DONE.
+ net::CompletionCallback callback_;
+
+ // Pointer to string we will write results to. Not valid in states
+ // START and DONE.
+ base::string16* destination_string_;
+
+ // PAC URL retrieved from DHCP, if any. Valid only in state STATE_DONE.
+ GURL pac_url_;
+
+ base::OneShotTimer<DhcpProxyScriptFetcherWin> wait_timer_;
+
+ URLRequestContext* const url_request_context_;
+
+ // NULL or the AdapterQuery currently in flight.
+ scoped_refptr<AdapterQuery> last_query_;
+
+ // Time |Fetch()| was last called, 0 if never.
+ base::TimeTicks fetch_start_time_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(DhcpProxyScriptFetcherWin);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_DHCP_PROXY_SCRIPT_FETCHER_WIN_H_
diff --git a/chromium/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc b/chromium/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc
new file mode 100644
index 00000000000..919787a435a
--- /dev/null
+++ b/chromium/net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc
@@ -0,0 +1,644 @@
+// Copyright (c) 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.
+
+#include "net/proxy/dhcp_proxy_script_fetcher_win.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/perftimer.h"
+#include "base/rand_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(DhcpProxyScriptFetcherWin, AdapterNamesAndPacURLFromDhcp) {
+ // This tests our core Win32 implementation without any of the wrappers
+ // we layer on top to achieve asynchronous and parallel operations.
+ //
+ // We don't make assumptions about the environment this unit test is
+ // running in, so it just exercises the code to make sure there
+ // is no crash and no error returned, but does not assert on the number
+ // of interfaces or the information returned via DHCP.
+ std::set<std::string> adapter_names;
+ DhcpProxyScriptFetcherWin::GetCandidateAdapterNames(&adapter_names);
+ for (std::set<std::string>::const_iterator it = adapter_names.begin();
+ it != adapter_names.end();
+ ++it) {
+ const std::string& adapter_name = *it;
+ std::string pac_url =
+ DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name);
+ printf("Adapter '%s' has PAC URL '%s' configured in DHCP.\n",
+ adapter_name.c_str(),
+ pac_url.c_str());
+ }
+}
+
+// Helper for RealFetch* tests below.
+class RealFetchTester {
+ public:
+ RealFetchTester()
+ : context_(new TestURLRequestContext),
+ fetcher_(new DhcpProxyScriptFetcherWin(context_.get())),
+ finished_(false),
+ on_completion_is_error_(false) {
+ // Make sure the test ends.
+ timeout_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(5), this, &RealFetchTester::OnTimeout);
+ }
+
+ void RunTest() {
+ int result = fetcher_->Fetch(
+ &pac_text_,
+ base::Bind(&RealFetchTester::OnCompletion, base::Unretained(this)));
+ if (result != ERR_IO_PENDING)
+ finished_ = true;
+ }
+
+ void RunTestWithCancel() {
+ RunTest();
+ fetcher_->Cancel();
+ }
+
+ void RunTestWithDeferredCancel() {
+ // Put the cancellation into the queue before even running the
+ // test to avoid the chance of one of the adapter fetcher worker
+ // threads completing before cancellation. See http://crbug.com/86756.
+ cancel_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(0),
+ this, &RealFetchTester::OnCancelTimer);
+ RunTest();
+ }
+
+ void OnCompletion(int result) {
+ if (on_completion_is_error_) {
+ FAIL() << "Received completion for test in which this is error.";
+ }
+ finished_ = true;
+ printf("Result code %d PAC data length %d\n", result, pac_text_.size());
+ }
+
+ void OnTimeout() {
+ printf("Timeout!");
+ OnCompletion(0);
+ }
+
+ void OnCancelTimer() {
+ fetcher_->Cancel();
+ finished_ = true;
+ }
+
+ void WaitUntilDone() {
+ while (!finished_) {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Attempts to give worker threads time to finish. This is currently
+ // very simplistic as completion (via completion callback or cancellation)
+ // immediately "detaches" any worker threads, so the best we can do is give
+ // them a little time. If we start running into Valgrind leaks, we can
+ // do something a bit more clever to track worker threads even when the
+ // DhcpProxyScriptFetcherWin state machine has finished.
+ void FinishTestAllowCleanup() {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30));
+ }
+
+ scoped_ptr<URLRequestContext> context_;
+ scoped_ptr<DhcpProxyScriptFetcherWin> fetcher_;
+ bool finished_;
+ base::string16 pac_text_;
+ base::OneShotTimer<RealFetchTester> timeout_;
+ base::OneShotTimer<RealFetchTester> cancel_timer_;
+ bool on_completion_is_error_;
+};
+
+TEST(DhcpProxyScriptFetcherWin, RealFetch) {
+ // This tests a call to Fetch() with no stubbing out of dependencies.
+ //
+ // We don't make assumptions about the environment this unit test is
+ // running in, so it just exercises the code to make sure there
+ // is no crash and no unexpected error returned, but does not assert on
+ // results beyond that.
+ RealFetchTester fetcher;
+ fetcher.RunTest();
+
+ fetcher.WaitUntilDone();
+ printf("PAC URL was %s\n",
+ fetcher.fetcher_->GetPacURL().possibly_invalid_spec().c_str());
+
+ fetcher.FinishTestAllowCleanup();
+}
+
+TEST(DhcpProxyScriptFetcherWin, RealFetchWithCancel) {
+ // Does a Fetch() with an immediate cancel. As before, just
+ // exercises the code without stubbing out dependencies.
+ RealFetchTester fetcher;
+ fetcher.RunTestWithCancel();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Attempt to avoid Valgrind leak reports in case worker thread is
+ // still running.
+ fetcher.FinishTestAllowCleanup();
+}
+
+// For RealFetchWithDeferredCancel, below.
+class DelayingDhcpProxyScriptAdapterFetcher
+ : public DhcpProxyScriptAdapterFetcher {
+ public:
+ explicit DelayingDhcpProxyScriptAdapterFetcher(
+ URLRequestContext* url_request_context)
+ : DhcpProxyScriptAdapterFetcher(url_request_context) {
+ }
+
+ class DelayingDhcpQuery : public DhcpQuery {
+ public:
+ explicit DelayingDhcpQuery()
+ : DhcpQuery() {
+ }
+
+ std::string ImplGetPacURLFromDhcp(
+ const std::string& adapter_name) OVERRIDE {
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+ return DhcpQuery::ImplGetPacURLFromDhcp(adapter_name);
+ }
+ };
+
+ DhcpQuery* ImplCreateDhcpQuery() OVERRIDE {
+ return new DelayingDhcpQuery();
+ }
+};
+
+// For RealFetchWithDeferredCancel, below.
+class DelayingDhcpProxyScriptFetcherWin
+ : public DhcpProxyScriptFetcherWin {
+ public:
+ explicit DelayingDhcpProxyScriptFetcherWin(
+ URLRequestContext* context)
+ : DhcpProxyScriptFetcherWin(context) {
+ }
+
+ DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE {
+ return new DelayingDhcpProxyScriptAdapterFetcher(url_request_context());
+ }
+};
+
+TEST(DhcpProxyScriptFetcherWin, RealFetchWithDeferredCancel) {
+ // Does a Fetch() with a slightly delayed cancel. As before, just
+ // exercises the code without stubbing out dependencies, but
+ // introduces a guaranteed 20 ms delay on the worker threads so that
+ // the cancel is called before they complete.
+ RealFetchTester fetcher;
+ fetcher.fetcher_.reset(
+ new DelayingDhcpProxyScriptFetcherWin(fetcher.context_.get()));
+ fetcher.on_completion_is_error_ = true;
+ fetcher.RunTestWithDeferredCancel();
+ fetcher.WaitUntilDone();
+}
+
+// The remaining tests are to exercise our state machine in various
+// situations, with actual network access fully stubbed out.
+
+class DummyDhcpProxyScriptAdapterFetcher
+ : public DhcpProxyScriptAdapterFetcher {
+ public:
+ explicit DummyDhcpProxyScriptAdapterFetcher(URLRequestContext* context)
+ : DhcpProxyScriptAdapterFetcher(context),
+ did_finish_(false),
+ result_(OK),
+ pac_script_(L"bingo"),
+ fetch_delay_ms_(1) {
+ }
+
+ void Fetch(const std::string& adapter_name,
+ const CompletionCallback& callback) OVERRIDE {
+ callback_ = callback;
+ timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(fetch_delay_ms_),
+ this, &DummyDhcpProxyScriptAdapterFetcher::OnTimer);
+ }
+
+ void Cancel() OVERRIDE {
+ timer_.Stop();
+ }
+
+ bool DidFinish() const OVERRIDE {
+ return did_finish_;
+ }
+
+ int GetResult() const OVERRIDE {
+ return result_;
+ }
+
+ base::string16 GetPacScript() const OVERRIDE {
+ return pac_script_;
+ }
+
+ void OnTimer() {
+ callback_.Run(result_);
+ }
+
+ void Configure(bool did_finish,
+ int result,
+ base::string16 pac_script,
+ int fetch_delay_ms) {
+ did_finish_ = did_finish;
+ result_ = result;
+ pac_script_ = pac_script;
+ fetch_delay_ms_ = fetch_delay_ms;
+ }
+
+ private:
+ bool did_finish_;
+ int result_;
+ base::string16 pac_script_;
+ int fetch_delay_ms_;
+ CompletionCallback callback_;
+ base::OneShotTimer<DummyDhcpProxyScriptAdapterFetcher> timer_;
+};
+
+class MockDhcpProxyScriptFetcherWin : public DhcpProxyScriptFetcherWin {
+ public:
+ class MockAdapterQuery : public AdapterQuery {
+ public:
+ MockAdapterQuery() {
+ }
+
+ virtual ~MockAdapterQuery() {
+ }
+
+ virtual bool ImplGetCandidateAdapterNames(
+ std::set<std::string>* adapter_names) OVERRIDE {
+ adapter_names->insert(
+ mock_adapter_names_.begin(), mock_adapter_names_.end());
+ return true;
+ }
+
+ std::vector<std::string> mock_adapter_names_;
+ };
+
+ MockDhcpProxyScriptFetcherWin(URLRequestContext* context)
+ : DhcpProxyScriptFetcherWin(context),
+ num_fetchers_created_(0),
+ worker_finished_event_(true, false) {
+ ResetTestState();
+ }
+
+ virtual ~MockDhcpProxyScriptFetcherWin() {
+ ResetTestState();
+ }
+
+ // Adds a fetcher object to the queue of fetchers used by
+ // |ImplCreateAdapterFetcher()|, and its name to the list of adapters
+ // returned by ImplGetCandidateAdapterNames.
+ void PushBackAdapter(const std::string& adapter_name,
+ DhcpProxyScriptAdapterFetcher* fetcher) {
+ adapter_query_->mock_adapter_names_.push_back(adapter_name);
+ adapter_fetchers_.push_back(fetcher);
+ }
+
+ void ConfigureAndPushBackAdapter(const std::string& adapter_name,
+ bool did_finish,
+ int result,
+ base::string16 pac_script,
+ base::TimeDelta fetch_delay) {
+ scoped_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher(
+ new DummyDhcpProxyScriptAdapterFetcher(url_request_context()));
+ adapter_fetcher->Configure(
+ did_finish, result, pac_script, fetch_delay.InMilliseconds());
+ PushBackAdapter(adapter_name, adapter_fetcher.release());
+ }
+
+ DhcpProxyScriptAdapterFetcher* ImplCreateAdapterFetcher() OVERRIDE {
+ ++num_fetchers_created_;
+ return adapter_fetchers_[next_adapter_fetcher_index_++];
+ }
+
+ virtual AdapterQuery* ImplCreateAdapterQuery() OVERRIDE {
+ DCHECK(adapter_query_);
+ return adapter_query_.get();
+ }
+
+ base::TimeDelta ImplGetMaxWait() OVERRIDE {
+ return max_wait_;
+ }
+
+ void ImplOnGetCandidateAdapterNamesDone() OVERRIDE {
+ worker_finished_event_.Signal();
+ }
+
+ void ResetTestState() {
+ // Delete any adapter fetcher objects we didn't hand out.
+ std::vector<DhcpProxyScriptAdapterFetcher*>::const_iterator it
+ = adapter_fetchers_.begin();
+ for (; it != adapter_fetchers_.end(); ++it) {
+ if (num_fetchers_created_-- <= 0) {
+ delete (*it);
+ }
+ }
+
+ next_adapter_fetcher_index_ = 0;
+ num_fetchers_created_ = 0;
+ adapter_fetchers_.clear();
+ adapter_query_ = new MockAdapterQuery();
+ max_wait_ = TestTimeouts::tiny_timeout();
+ }
+
+ bool HasPendingFetchers() {
+ return num_pending_fetchers() > 0;
+ }
+
+ int next_adapter_fetcher_index_;
+
+ // Ownership gets transferred to the implementation class via
+ // ImplCreateAdapterFetcher, but any objects not handed out are
+ // deleted on destruction.
+ std::vector<DhcpProxyScriptAdapterFetcher*> adapter_fetchers_;
+
+ scoped_refptr<MockAdapterQuery> adapter_query_;
+
+ base::TimeDelta max_wait_;
+ int num_fetchers_created_;
+ base::WaitableEvent worker_finished_event_;
+};
+
+class FetcherClient {
+public:
+ FetcherClient()
+ : context_(new TestURLRequestContext),
+ fetcher_(context_.get()),
+ finished_(false),
+ result_(ERR_UNEXPECTED) {
+ }
+
+ void RunTest() {
+ int result = fetcher_.Fetch(
+ &pac_text_,
+ base::Bind(&FetcherClient::OnCompletion, base::Unretained(this)));
+ ASSERT_EQ(ERR_IO_PENDING, result);
+ }
+
+ void RunMessageLoopUntilComplete() {
+ while (!finished_) {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void RunMessageLoopUntilWorkerDone() {
+ DCHECK(fetcher_.adapter_query_.get());
+ while (!fetcher_.worker_finished_event_.TimedWait(
+ base::TimeDelta::FromMilliseconds(10))) {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ }
+
+ void OnCompletion(int result) {
+ finished_ = true;
+ result_ = result;
+ }
+
+ void ResetTestState() {
+ finished_ = false;
+ result_ = ERR_UNEXPECTED;
+ pac_text_ = L"";
+ fetcher_.ResetTestState();
+ }
+
+ scoped_ptr<URLRequestContext> context_;
+ MockDhcpProxyScriptFetcherWin fetcher_;
+ bool finished_;
+ int result_;
+ base::string16 pac_text_;
+};
+
+// We separate out each test's logic so that we can easily implement
+// the ReuseFetcher test at the bottom.
+void TestNormalCaseURLConfiguredOneAdapter(FetcherClient* client) {
+ TestURLRequestContext context;
+ scoped_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher(
+ new DummyDhcpProxyScriptAdapterFetcher(&context));
+ adapter_fetcher->Configure(true, OK, L"bingo", 1);
+ client->fetcher_.PushBackAdapter("a", adapter_fetcher.release());
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(OK, client->result_);
+ ASSERT_EQ(L"bingo", client->pac_text_);
+}
+
+TEST(DhcpProxyScriptFetcherWin, NormalCaseURLConfiguredOneAdapter) {
+ FetcherClient client;
+ TestNormalCaseURLConfiguredOneAdapter(&client);
+}
+
+void TestNormalCaseURLConfiguredMultipleAdapters(FetcherClient* client) {
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "second", true, OK, L"bingo", base::TimeDelta::FromMilliseconds(50));
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "third", true, OK, L"rocko", base::TimeDelta::FromMilliseconds(1));
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(OK, client->result_);
+ ASSERT_EQ(L"bingo", client->pac_text_);
+}
+
+TEST(DhcpProxyScriptFetcherWin, NormalCaseURLConfiguredMultipleAdapters) {
+ FetcherClient client;
+ TestNormalCaseURLConfiguredMultipleAdapters(&client);
+}
+
+void TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout(
+ FetcherClient* client) {
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ // This will time out.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "second", false, ERR_IO_PENDING, L"bingo",
+ TestTimeouts::action_timeout());
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "third", true, OK, L"rocko", base::TimeDelta::FromMilliseconds(1));
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(OK, client->result_);
+ ASSERT_EQ(L"rocko", client->pac_text_);
+}
+
+TEST(DhcpProxyScriptFetcherWin,
+ NormalCaseURLConfiguredMultipleAdaptersWithTimeout) {
+ FetcherClient client;
+ TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout(&client);
+}
+
+void TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout(
+ FetcherClient* client) {
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ // This will time out.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "second", false, ERR_IO_PENDING, L"bingo",
+ TestTimeouts::action_timeout());
+ // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such
+ // should be chosen.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "third", true, ERR_PAC_STATUS_NOT_OK, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "fourth", true, ERR_NOT_IMPLEMENTED, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(ERR_PAC_STATUS_NOT_OK, client->result_);
+ ASSERT_EQ(L"", client->pac_text_);
+}
+
+TEST(DhcpProxyScriptFetcherWin,
+ FailureCaseURLConfiguredMultipleAdaptersWithTimeout) {
+ FetcherClient client;
+ TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout(&client);
+}
+
+void TestFailureCaseNoURLConfigured(FetcherClient* client) {
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "most_preferred", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ // This will time out.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "second", false, ERR_IO_PENDING, L"bingo",
+ TestTimeouts::action_timeout());
+ // This is the first non-ERR_PAC_NOT_IN_DHCP error and as such
+ // should be chosen.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "third", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, client->result_);
+ ASSERT_EQ(L"", client->pac_text_);
+}
+
+TEST(DhcpProxyScriptFetcherWin, FailureCaseNoURLConfigured) {
+ FetcherClient client;
+ TestFailureCaseNoURLConfigured(&client);
+}
+
+void TestFailureCaseNoDhcpAdapters(FetcherClient* client) {
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_EQ(ERR_PAC_NOT_IN_DHCP, client->result_);
+ ASSERT_EQ(L"", client->pac_text_);
+ ASSERT_EQ(0, client->fetcher_.num_fetchers_created_);
+}
+
+TEST(DhcpProxyScriptFetcherWin, FailureCaseNoDhcpAdapters) {
+ FetcherClient client;
+ TestFailureCaseNoDhcpAdapters(&client);
+}
+
+void TestShortCircuitLessPreferredAdapters(FetcherClient* client) {
+ // Here we have a bunch of adapters; the first reports no PAC in DHCP,
+ // the second responds quickly with a PAC file, the rest take a long
+ // time. Verify that we complete quickly and do not wait for the slow
+ // adapters, i.e. we finish before timeout.
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "1", true, ERR_PAC_NOT_IN_DHCP, L"",
+ base::TimeDelta::FromMilliseconds(1));
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "2", true, OK, L"bingo",
+ base::TimeDelta::FromMilliseconds(1));
+ client->fetcher_.ConfigureAndPushBackAdapter(
+ "3", true, OK, L"wrongo", TestTimeouts::action_max_timeout());
+
+ // Increase the timeout to ensure the short circuit mechanism has
+ // time to kick in before the timeout waiting for more adapters kicks in.
+ client->fetcher_.max_wait_ = TestTimeouts::action_timeout();
+
+ PerfTimer timer;
+ client->RunTest();
+ client->RunMessageLoopUntilComplete();
+ ASSERT_TRUE(client->fetcher_.HasPendingFetchers());
+ // Assert that the time passed is definitely less than the wait timer
+ // timeout, to get a second signal that it was the shortcut mechanism
+ // (in OnFetcherDone) that kicked in, and not the timeout waiting for
+ // more adapters.
+ ASSERT_GT(client->fetcher_.max_wait_ - (client->fetcher_.max_wait_ / 10),
+ timer.Elapsed());
+}
+
+TEST(DhcpProxyScriptFetcherWin, ShortCircuitLessPreferredAdapters) {
+ FetcherClient client;
+ TestShortCircuitLessPreferredAdapters(&client);
+}
+
+void TestImmediateCancel(FetcherClient* client) {
+ TestURLRequestContext context;
+ scoped_ptr<DummyDhcpProxyScriptAdapterFetcher> adapter_fetcher(
+ new DummyDhcpProxyScriptAdapterFetcher(&context));
+ adapter_fetcher->Configure(true, OK, L"bingo", 1);
+ client->fetcher_.PushBackAdapter("a", adapter_fetcher.release());
+ client->RunTest();
+ client->fetcher_.Cancel();
+ client->RunMessageLoopUntilWorkerDone();
+ ASSERT_EQ(0, client->fetcher_.num_fetchers_created_);
+}
+
+// Regression test to check that when we cancel immediately, no
+// adapter fetchers get created.
+TEST(DhcpProxyScriptFetcherWin, ImmediateCancel) {
+ FetcherClient client;
+ TestImmediateCancel(&client);
+}
+
+TEST(DhcpProxyScriptFetcherWin, ReuseFetcher) {
+ FetcherClient client;
+
+ // The ProxyScriptFetcher interface stipulates that only a single
+ // |Fetch()| may be in flight at once, but allows reuse, so test
+ // that the state transitions correctly from done to start in all
+ // cases we're testing.
+
+ typedef void (*FetcherClientTestFunction)(FetcherClient*);
+ typedef std::vector<FetcherClientTestFunction> TestVector;
+ TestVector test_functions;
+ test_functions.push_back(TestNormalCaseURLConfiguredOneAdapter);
+ test_functions.push_back(TestNormalCaseURLConfiguredMultipleAdapters);
+ test_functions.push_back(
+ TestNormalCaseURLConfiguredMultipleAdaptersWithTimeout);
+ test_functions.push_back(
+ TestFailureCaseURLConfiguredMultipleAdaptersWithTimeout);
+ test_functions.push_back(TestFailureCaseNoURLConfigured);
+ test_functions.push_back(TestFailureCaseNoDhcpAdapters);
+ test_functions.push_back(TestShortCircuitLessPreferredAdapters);
+ test_functions.push_back(TestImmediateCancel);
+
+ std::random_shuffle(test_functions.begin(),
+ test_functions.end(),
+ base::RandGenerator);
+ for (TestVector::const_iterator it = test_functions.begin();
+ it != test_functions.end();
+ ++it) {
+ (*it)(&client);
+ client.ResetTestState();
+ }
+
+ // Re-do the first test to make sure the last test that was run did
+ // not leave things in a bad state.
+ (*test_functions.begin())(&client);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcpcsvc_init_win.cc b/chromium/net/proxy/dhcpcsvc_init_win.cc
new file mode 100644
index 00000000000..7e32aeae9cd
--- /dev/null
+++ b/chromium/net/proxy/dhcpcsvc_init_win.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 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/proxy/dhcpcsvc_init_win.h"
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+
+#include <dhcpcsdk.h>
+#include <dhcpv6csdk.h>
+
+namespace {
+
+class DhcpcsvcInitSingleton {
+ public:
+ DhcpcsvcInitSingleton() {
+ DWORD version = 0;
+ DWORD err = DhcpCApiInitialize(&version);
+ DCHECK(err == ERROR_SUCCESS); // DCHECK_EQ complains of unsigned mismatch.
+ }
+
+ ~DhcpcsvcInitSingleton() {
+ // Worker pool threads that use the DHCP API may still be running, so skip
+ // cleanup.
+ }
+};
+
+static base::LazyInstance<DhcpcsvcInitSingleton> g_dhcpcsvc_init_singleton =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace net {
+
+void EnsureDhcpcsvcInit() {
+ g_dhcpcsvc_init_singleton.Get();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/dhcpcsvc_init_win.h b/chromium/net/proxy/dhcpcsvc_init_win.h
new file mode 100644
index 00000000000..39bdf24c10c
--- /dev/null
+++ b/chromium/net/proxy/dhcpcsvc_init_win.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2011 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_PROXY_DHCPCSVC_INIT_WIN_H
+#define NET_PROXY_DHCPCSVC_INIT_WIN_H
+
+namespace net {
+
+// Initialization of the Dhcpcsvc library must happen before any of its
+// calls are made. This function will make sure that the appropriate
+// initialization has been done, and that uninitialization is also
+// performed at static uninitialization time.
+//
+// Note: This initializes only for DHCP, not DHCPv6.
+void EnsureDhcpcsvcInit();
+
+} // namespace net
+
+#endif // NET_PROXY_DHCPCSVC_INIT_WIN_H
diff --git a/chromium/net/proxy/mock_proxy_resolver.cc b/chromium/net/proxy/mock_proxy_resolver.cc
new file mode 100644
index 00000000000..fb4a914ccbd
--- /dev/null
+++ b/chromium/net/proxy/mock_proxy_resolver.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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/proxy/mock_proxy_resolver.h"
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+
+namespace net {
+
+MockAsyncProxyResolverBase::Request::Request(
+ MockAsyncProxyResolverBase* resolver,
+ const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback)
+ : resolver_(resolver),
+ url_(url),
+ results_(results),
+ callback_(callback),
+ origin_loop_(base::MessageLoop::current()) {}
+
+ void MockAsyncProxyResolverBase::Request::CompleteNow(int rv) {
+ CompletionCallback callback = callback_;
+
+ // May delete |this|.
+ resolver_->RemovePendingRequest(this);
+
+ callback.Run(rv);
+ }
+
+MockAsyncProxyResolverBase::Request::~Request() {}
+
+MockAsyncProxyResolverBase::SetPacScriptRequest::SetPacScriptRequest(
+ MockAsyncProxyResolverBase* resolver,
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback)
+ : resolver_(resolver),
+ script_data_(script_data),
+ callback_(callback),
+ origin_loop_(base::MessageLoop::current()) {}
+
+MockAsyncProxyResolverBase::SetPacScriptRequest::~SetPacScriptRequest() {}
+
+ void MockAsyncProxyResolverBase::SetPacScriptRequest::CompleteNow(int rv) {
+ CompletionCallback callback = callback_;
+
+ // Will delete |this|.
+ resolver_->RemovePendingSetPacScriptRequest(this);
+
+ callback.Run(rv);
+ }
+
+MockAsyncProxyResolverBase::~MockAsyncProxyResolverBase() {}
+
+int MockAsyncProxyResolverBase::GetProxyForURL(
+ const GURL& url, ProxyInfo* results, const CompletionCallback& callback,
+ RequestHandle* request_handle, const BoundNetLog& /*net_log*/) {
+ scoped_refptr<Request> request = new Request(this, url, results, callback);
+ pending_requests_.push_back(request);
+
+ if (request_handle)
+ *request_handle = reinterpret_cast<RequestHandle>(request.get());
+
+ // Test code completes the request by calling request->CompleteNow().
+ return ERR_IO_PENDING;
+}
+
+void MockAsyncProxyResolverBase::CancelRequest(RequestHandle request_handle) {
+ scoped_refptr<Request> request = reinterpret_cast<Request*>(request_handle);
+ cancelled_requests_.push_back(request);
+ RemovePendingRequest(request.get());
+}
+
+LoadState MockAsyncProxyResolverBase::GetLoadState(
+ RequestHandle request_handle) const {
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+}
+
+int MockAsyncProxyResolverBase::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) {
+ DCHECK(!pending_set_pac_script_request_.get());
+ pending_set_pac_script_request_.reset(
+ new SetPacScriptRequest(this, script_data, callback));
+ // Finished when user calls SetPacScriptRequest::CompleteNow().
+ return ERR_IO_PENDING;
+}
+
+void MockAsyncProxyResolverBase::CancelSetPacScript() {
+ // Do nothing (caller was responsible for completing the request).
+}
+
+MockAsyncProxyResolverBase::SetPacScriptRequest*
+MockAsyncProxyResolverBase::pending_set_pac_script_request() const {
+ return pending_set_pac_script_request_.get();
+}
+
+void MockAsyncProxyResolverBase::RemovePendingRequest(Request* request) {
+ RequestsList::iterator it = std::find(
+ pending_requests_.begin(), pending_requests_.end(), request);
+ DCHECK(it != pending_requests_.end());
+ pending_requests_.erase(it);
+}
+
+void MockAsyncProxyResolverBase::RemovePendingSetPacScriptRequest(
+ SetPacScriptRequest* request) {
+ DCHECK_EQ(request, pending_set_pac_script_request());
+ pending_set_pac_script_request_.reset();
+}
+
+MockAsyncProxyResolverBase::MockAsyncProxyResolverBase(bool expects_pac_bytes)
+ : ProxyResolver(expects_pac_bytes) {}
+
+} // namespace net
diff --git a/chromium/net/proxy/mock_proxy_resolver.h b/chromium/net/proxy/mock_proxy_resolver.h
new file mode 100644
index 00000000000..3864d530de9
--- /dev/null
+++ b/chromium/net/proxy/mock_proxy_resolver.h
@@ -0,0 +1,129 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_MOCK_PROXY_RESOLVER_H_
+#define NET_PROXY_MOCK_PROXY_RESOLVER_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_resolver.h"
+#include "url/gurl.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace net {
+
+// Asynchronous mock proxy resolver. All requests complete asynchronously,
+// user must call Request::CompleteNow() on a pending request to signal it.
+class MockAsyncProxyResolverBase : public ProxyResolver {
+ public:
+ class Request : public base::RefCounted<Request> {
+ public:
+ Request(MockAsyncProxyResolverBase* resolver,
+ const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& callback);
+
+ const GURL& url() const { return url_; }
+ ProxyInfo* results() const { return results_; }
+ const net::CompletionCallback& callback() const { return callback_; }
+
+ void CompleteNow(int rv);
+
+ private:
+ friend class base::RefCounted<Request>;
+
+ virtual ~Request();
+
+ MockAsyncProxyResolverBase* resolver_;
+ const GURL url_;
+ ProxyInfo* results_;
+ net::CompletionCallback callback_;
+ base::MessageLoop* origin_loop_;
+ };
+
+ class SetPacScriptRequest {
+ public:
+ SetPacScriptRequest(
+ MockAsyncProxyResolverBase* resolver,
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const net::CompletionCallback& callback);
+ ~SetPacScriptRequest();
+
+ const ProxyResolverScriptData* script_data() const {
+ return script_data_.get();
+ }
+
+ void CompleteNow(int rv);
+
+ private:
+ MockAsyncProxyResolverBase* resolver_;
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
+ net::CompletionCallback callback_;
+ base::MessageLoop* origin_loop_;
+ };
+
+ typedef std::vector<scoped_refptr<Request> > RequestsList;
+
+ virtual ~MockAsyncProxyResolverBase();
+
+ // ProxyResolver implementation.
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& callback,
+ RequestHandle* request_handle,
+ const BoundNetLog& /*net_log*/) OVERRIDE;
+ virtual void CancelRequest(RequestHandle request_handle) OVERRIDE;
+ virtual LoadState GetLoadState(RequestHandle request_handle) const OVERRIDE;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual void CancelSetPacScript() OVERRIDE;
+
+ const RequestsList& pending_requests() const {
+ return pending_requests_;
+ }
+
+ const RequestsList& cancelled_requests() const {
+ return cancelled_requests_;
+ }
+
+ SetPacScriptRequest* pending_set_pac_script_request() const;
+
+ bool has_pending_set_pac_script_request() const {
+ return pending_set_pac_script_request_.get() != NULL;
+ }
+
+ void RemovePendingRequest(Request* request);
+
+ void RemovePendingSetPacScriptRequest(SetPacScriptRequest* request);
+
+ protected:
+ explicit MockAsyncProxyResolverBase(bool expects_pac_bytes);
+
+ private:
+ RequestsList pending_requests_;
+ RequestsList cancelled_requests_;
+ scoped_ptr<SetPacScriptRequest> pending_set_pac_script_request_;
+};
+
+class MockAsyncProxyResolver : public MockAsyncProxyResolverBase {
+ public:
+ MockAsyncProxyResolver()
+ : MockAsyncProxyResolverBase(false /*expects_pac_bytes*/) {}
+};
+
+class MockAsyncProxyResolverExpectsBytes : public MockAsyncProxyResolverBase {
+ public:
+ MockAsyncProxyResolverExpectsBytes()
+ : MockAsyncProxyResolverBase(true /*expects_pac_bytes*/) {}
+};
+
+} // namespace net
+
+#endif // NET_PROXY_MOCK_PROXY_RESOLVER_H_
diff --git a/chromium/net/proxy/mock_proxy_script_fetcher.cc b/chromium/net/proxy/mock_proxy_script_fetcher.cc
new file mode 100644
index 00000000000..5d66e6c6147
--- /dev/null
+++ b/chromium/net/proxy/mock_proxy_script_fetcher.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 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.
+
+#include "net/proxy/mock_proxy_script_fetcher.h"
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+MockProxyScriptFetcher::MockProxyScriptFetcher()
+ : pending_request_text_(NULL),
+ waiting_for_fetch_(false) {
+}
+
+MockProxyScriptFetcher::~MockProxyScriptFetcher() {}
+
+// ProxyScriptFetcher implementation.
+int MockProxyScriptFetcher::Fetch(const GURL& url, base::string16* text,
+ const CompletionCallback& callback) {
+ DCHECK(!has_pending_request());
+
+ // Save the caller's information, and have them wait.
+ pending_request_url_ = url;
+ pending_request_callback_ = callback;
+ pending_request_text_ = text;
+
+ if (waiting_for_fetch_)
+ base::MessageLoop::current()->Quit();
+
+ return ERR_IO_PENDING;
+}
+
+void MockProxyScriptFetcher::NotifyFetchCompletion(
+ int result, const std::string& ascii_text) {
+ DCHECK(has_pending_request());
+ *pending_request_text_ = ASCIIToUTF16(ascii_text);
+ CompletionCallback callback = pending_request_callback_;
+ pending_request_callback_.Reset();
+ callback.Run(result);
+}
+
+void MockProxyScriptFetcher::Cancel() {
+}
+
+URLRequestContext* MockProxyScriptFetcher::GetRequestContext() const {
+ return NULL;
+}
+
+const GURL& MockProxyScriptFetcher::pending_request_url() const {
+ return pending_request_url_;
+}
+
+bool MockProxyScriptFetcher::has_pending_request() const {
+ return !pending_request_callback_.is_null();
+}
+
+void MockProxyScriptFetcher::WaitUntilFetch() {
+ DCHECK(!has_pending_request());
+ waiting_for_fetch_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_fetch_ = false;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/mock_proxy_script_fetcher.h b/chromium/net/proxy/mock_proxy_script_fetcher.h
new file mode 100644
index 00000000000..81e221f0a9b
--- /dev/null
+++ b/chromium/net/proxy/mock_proxy_script_fetcher.h
@@ -0,0 +1,48 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_MOCK_PROXY_SCRIPT_FETCHER_H_
+#define NET_PROXY_MOCK_PROXY_SCRIPT_FETCHER_H_
+
+#include "base/compiler_specific.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "url/gurl.h"
+
+#include <string>
+
+namespace net {
+
+class URLRequestContext;
+
+// A mock ProxyScriptFetcher. No result will be returned to the fetch client
+// until we call NotifyFetchCompletion() to set the results.
+class MockProxyScriptFetcher : public ProxyScriptFetcher {
+ public:
+ MockProxyScriptFetcher();
+ virtual ~MockProxyScriptFetcher();
+
+ // ProxyScriptFetcher implementation.
+ virtual int Fetch(const GURL& url,
+ base::string16* text,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual URLRequestContext* GetRequestContext() const OVERRIDE;
+
+ void NotifyFetchCompletion(int result, const std::string& ascii_text);
+ const GURL& pending_request_url() const;
+ bool has_pending_request() const;
+
+ // Spins the message loop until this->Fetch() is invoked.
+ void WaitUntilFetch();
+
+ private:
+ GURL pending_request_url_;
+ CompletionCallback pending_request_callback_;
+ base::string16* pending_request_text_;
+ bool waiting_for_fetch_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_MOCK_PROXY_SCRIPT_FETCHER_H_
diff --git a/chromium/net/proxy/multi_threaded_proxy_resolver.cc b/chromium/net/proxy/multi_threaded_proxy_resolver.cc
new file mode 100644
index 00000000000..b54cf1fbac7
--- /dev/null
+++ b/chromium/net/proxy/multi_threaded_proxy_resolver.cc
@@ -0,0 +1,583 @@
+// Copyright (c) 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.
+
+#include "net/proxy/multi_threaded_proxy_resolver.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/proxy/proxy_info.h"
+
+// TODO(eroman): Have the MultiThreadedProxyResolver clear its PAC script
+// data when SetPacScript fails. That will reclaim memory when
+// testing bogus scripts.
+
+namespace net {
+
+// An "executor" is a job-runner for PAC requests. It encapsulates a worker
+// thread and a synchronous ProxyResolver (which will be operated on said
+// thread.)
+class MultiThreadedProxyResolver::Executor
+ : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Executor > {
+ public:
+ // |coordinator| must remain valid throughout our lifetime. It is used to
+ // signal when the executor is ready to receive work by calling
+ // |coordinator->OnExecutorReady()|.
+ // The constructor takes ownership of |resolver|.
+ // |thread_number| is an identifier used when naming the worker thread.
+ Executor(MultiThreadedProxyResolver* coordinator,
+ ProxyResolver* resolver,
+ int thread_number);
+
+ // Submit a job to this executor.
+ void StartJob(Job* job);
+
+ // Callback for when a job has completed running on the executor's thread.
+ void OnJobCompleted(Job* job);
+
+ // Cleanup the executor. Cancels all outstanding work, and frees the thread
+ // and resolver.
+ void Destroy();
+
+ void PurgeMemory();
+
+ // Returns the outstanding job, or NULL.
+ Job* outstanding_job() const { return outstanding_job_.get(); }
+
+ ProxyResolver* resolver() { return resolver_.get(); }
+
+ int thread_number() const { return thread_number_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<Executor>;
+ ~Executor();
+
+ MultiThreadedProxyResolver* coordinator_;
+ const int thread_number_;
+
+ // The currently active job for this executor (either a SetPacScript or
+ // GetProxyForURL task).
+ scoped_refptr<Job> outstanding_job_;
+
+ // The synchronous resolver implementation.
+ scoped_ptr<ProxyResolver> resolver_;
+
+ // The thread where |resolver_| is run on.
+ // Note that declaration ordering is important here. |thread_| needs to be
+ // destroyed *before* |resolver_|, in case |resolver_| is currently
+ // executing on |thread_|.
+ scoped_ptr<base::Thread> thread_;
+};
+
+// MultiThreadedProxyResolver::Job ---------------------------------------------
+
+class MultiThreadedProxyResolver::Job
+ : public base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job> {
+ public:
+ // Identifies the subclass of Job (only being used for debugging purposes).
+ enum Type {
+ TYPE_GET_PROXY_FOR_URL,
+ TYPE_SET_PAC_SCRIPT,
+ TYPE_SET_PAC_SCRIPT_INTERNAL,
+ };
+
+ Job(Type type, const CompletionCallback& callback)
+ : type_(type),
+ callback_(callback),
+ executor_(NULL),
+ was_cancelled_(false) {
+ }
+
+ void set_executor(Executor* executor) {
+ executor_ = executor;
+ }
+
+ // The "executor" is the job runner that is scheduling this job. If
+ // this job has not been submitted to an executor yet, this will be
+ // NULL (and we know it hasn't started yet).
+ Executor* executor() {
+ return executor_;
+ }
+
+ // Mark the job as having been cancelled.
+ void Cancel() {
+ was_cancelled_ = true;
+ }
+
+ // Returns true if Cancel() has been called.
+ bool was_cancelled() const { return was_cancelled_; }
+
+ Type type() const { return type_; }
+
+ // Returns true if this job still has a user callback. Some jobs
+ // do not have a user callback, because they were helper jobs
+ // scheduled internally (for example TYPE_SET_PAC_SCRIPT_INTERNAL).
+ //
+ // Otherwise jobs that correspond with user-initiated work will
+ // have a non-null callback up until the callback is run.
+ bool has_user_callback() const { return !callback_.is_null(); }
+
+ // This method is called when the job is inserted into a wait queue
+ // because no executors were ready to accept it.
+ virtual void WaitingForThread() {}
+
+ // This method is called just before the job is posted to the work thread.
+ virtual void FinishedWaitingForThread() {}
+
+ // This method is called on the worker thread to do the job's work. On
+ // completion, implementors are expected to call OnJobCompleted() on
+ // |origin_loop|.
+ virtual void Run(scoped_refptr<base::MessageLoopProxy> origin_loop) = 0;
+
+ protected:
+ void OnJobCompleted() {
+ // |executor_| will be NULL if the executor has already been deleted.
+ if (executor_)
+ executor_->OnJobCompleted(this);
+ }
+
+ void RunUserCallback(int result) {
+ DCHECK(has_user_callback());
+ CompletionCallback callback = callback_;
+ // Reset the callback so has_user_callback() will now return false.
+ callback_.Reset();
+ callback.Run(result);
+ }
+
+ friend class base::RefCountedThreadSafe<MultiThreadedProxyResolver::Job>;
+
+ virtual ~Job() {}
+
+ private:
+ const Type type_;
+ CompletionCallback callback_;
+ Executor* executor_;
+ bool was_cancelled_;
+};
+
+// MultiThreadedProxyResolver::SetPacScriptJob ---------------------------------
+
+// Runs on the worker thread to call ProxyResolver::SetPacScript.
+class MultiThreadedProxyResolver::SetPacScriptJob
+ : public MultiThreadedProxyResolver::Job {
+ public:
+ SetPacScriptJob(const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback)
+ : Job(!callback.is_null() ? TYPE_SET_PAC_SCRIPT :
+ TYPE_SET_PAC_SCRIPT_INTERNAL,
+ callback),
+ script_data_(script_data) {
+ }
+
+ // Runs on the worker thread.
+ virtual void Run(scoped_refptr<base::MessageLoopProxy> origin_loop) OVERRIDE {
+ ProxyResolver* resolver = executor()->resolver();
+ int rv = resolver->SetPacScript(script_data_, CompletionCallback());
+
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ origin_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&SetPacScriptJob::RequestComplete, this, rv));
+ }
+
+ protected:
+ virtual ~SetPacScriptJob() {}
+
+ private:
+ // Runs the completion callback on the origin thread.
+ void RequestComplete(int result_code) {
+ // The task may have been cancelled after it was started.
+ if (!was_cancelled() && has_user_callback()) {
+ RunUserCallback(result_code);
+ }
+ OnJobCompleted();
+ }
+
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
+};
+
+// MultiThreadedProxyResolver::GetProxyForURLJob ------------------------------
+
+class MultiThreadedProxyResolver::GetProxyForURLJob
+ : public MultiThreadedProxyResolver::Job {
+ public:
+ // |url| -- the URL of the query.
+ // |results| -- the structure to fill with proxy resolve results.
+ GetProxyForURLJob(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log)
+ : Job(TYPE_GET_PROXY_FOR_URL, callback),
+ results_(results),
+ net_log_(net_log),
+ url_(url),
+ was_waiting_for_thread_(false) {
+ DCHECK(!callback.is_null());
+ start_time_ = base::TimeTicks::Now();
+ }
+
+ BoundNetLog* net_log() { return &net_log_; }
+
+ virtual void WaitingForThread() OVERRIDE {
+ was_waiting_for_thread_ = true;
+ net_log_.BeginEvent(NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD);
+ }
+
+ virtual void FinishedWaitingForThread() OVERRIDE {
+ DCHECK(executor());
+
+ submitted_to_thread_time_ = base::TimeTicks::Now();
+
+ if (was_waiting_for_thread_) {
+ net_log_.EndEvent(NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD);
+ }
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD,
+ NetLog::IntegerCallback("thread_number", executor()->thread_number()));
+ }
+
+ // Runs on the worker thread.
+ virtual void Run(scoped_refptr<base::MessageLoopProxy> origin_loop) OVERRIDE {
+ ProxyResolver* resolver = executor()->resolver();
+ int rv = resolver->GetProxyForURL(
+ url_, &results_buf_, CompletionCallback(), NULL, net_log_);
+ DCHECK_NE(rv, ERR_IO_PENDING);
+
+ origin_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&GetProxyForURLJob::QueryComplete, this, rv));
+ }
+
+ protected:
+ virtual ~GetProxyForURLJob() {}
+
+ private:
+ // Runs the completion callback on the origin thread.
+ void QueryComplete(int result_code) {
+ // The Job may have been cancelled after it was started.
+ if (!was_cancelled()) {
+ RecordPerformanceMetrics();
+ if (result_code >= OK) { // Note: unit-tests use values > 0.
+ results_->Use(results_buf_);
+ }
+ RunUserCallback(result_code);
+ }
+ OnJobCompleted();
+ }
+
+ void RecordPerformanceMetrics() {
+ DCHECK(!was_cancelled());
+
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ // Log the total time the request took to complete.
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.MTPR_GetProxyForUrl_Time",
+ now - start_time_);
+
+ // Log the time the request was stalled waiting for a thread to free up.
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.MTPR_GetProxyForUrl_Thread_Wait_Time",
+ submitted_to_thread_time_ - start_time_);
+ }
+
+ // Must only be used on the "origin" thread.
+ ProxyInfo* results_;
+
+ // Can be used on either "origin" or worker thread.
+ BoundNetLog net_log_;
+ const GURL url_;
+
+ // Usable from within DoQuery on the worker thread.
+ ProxyInfo results_buf_;
+
+ base::TimeTicks start_time_;
+ base::TimeTicks submitted_to_thread_time_;
+
+ bool was_waiting_for_thread_;
+};
+
+// MultiThreadedProxyResolver::Executor ----------------------------------------
+
+MultiThreadedProxyResolver::Executor::Executor(
+ MultiThreadedProxyResolver* coordinator,
+ ProxyResolver* resolver,
+ int thread_number)
+ : coordinator_(coordinator),
+ thread_number_(thread_number),
+ resolver_(resolver) {
+ DCHECK(coordinator);
+ DCHECK(resolver);
+ // Start up the thread.
+ // Note that it is safe to pass a temporary C-String to Thread(), as it will
+ // make a copy.
+ std::string thread_name =
+ base::StringPrintf("PAC thread #%d", thread_number);
+ thread_.reset(new base::Thread(thread_name.c_str()));
+ CHECK(thread_->Start());
+}
+
+void MultiThreadedProxyResolver::Executor::StartJob(Job* job) {
+ DCHECK(!outstanding_job_.get());
+ outstanding_job_ = job;
+
+ // Run the job. Once it has completed (regardless of whether it was
+ // cancelled), it will invoke OnJobCompleted() on this thread.
+ job->set_executor(this);
+ job->FinishedWaitingForThread();
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&Job::Run, job, base::MessageLoopProxy::current()));
+}
+
+void MultiThreadedProxyResolver::Executor::OnJobCompleted(Job* job) {
+ DCHECK_EQ(job, outstanding_job_.get());
+ outstanding_job_ = NULL;
+ coordinator_->OnExecutorReady(this);
+}
+
+void MultiThreadedProxyResolver::Executor::Destroy() {
+ DCHECK(coordinator_);
+
+ {
+ // See http://crbug.com/69710.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Join the worker thread.
+ thread_.reset();
+ }
+
+ // Cancel any outstanding job.
+ if (outstanding_job_.get()) {
+ outstanding_job_->Cancel();
+ // Orphan the job (since this executor may be deleted soon).
+ outstanding_job_->set_executor(NULL);
+ }
+
+ // It is now safe to free the ProxyResolver, since all the tasks that
+ // were using it on the resolver thread have completed.
+ resolver_.reset();
+
+ // Null some stuff as a precaution.
+ coordinator_ = NULL;
+ outstanding_job_ = NULL;
+}
+
+void MultiThreadedProxyResolver::Executor::PurgeMemory() {
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProxyResolver::PurgeMemory,
+ base::Unretained(resolver_.get())));
+}
+
+MultiThreadedProxyResolver::Executor::~Executor() {
+ // The important cleanup happens as part of Destroy(), which should always be
+ // called first.
+ DCHECK(!coordinator_) << "Destroy() was not called";
+ DCHECK(!thread_.get());
+ DCHECK(!resolver_.get());
+ DCHECK(!outstanding_job_.get());
+}
+
+// MultiThreadedProxyResolver --------------------------------------------------
+
+MultiThreadedProxyResolver::MultiThreadedProxyResolver(
+ ProxyResolverFactory* resolver_factory,
+ size_t max_num_threads)
+ : ProxyResolver(resolver_factory->resolvers_expect_pac_bytes()),
+ resolver_factory_(resolver_factory),
+ max_num_threads_(max_num_threads) {
+ DCHECK_GE(max_num_threads, 1u);
+}
+
+MultiThreadedProxyResolver::~MultiThreadedProxyResolver() {
+ // We will cancel all outstanding requests.
+ pending_jobs_.clear();
+ ReleaseAllExecutors();
+}
+
+int MultiThreadedProxyResolver::GetProxyForURL(
+ const GURL& url, ProxyInfo* results, const CompletionCallback& callback,
+ RequestHandle* request, const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(current_script_data_.get())
+ << "Resolver is un-initialized. Must call SetPacScript() first!";
+
+ scoped_refptr<GetProxyForURLJob> job(
+ new GetProxyForURLJob(url, results, callback, net_log));
+
+ // Completion will be notified through |callback|, unless the caller cancels
+ // the request using |request|.
+ if (request)
+ *request = reinterpret_cast<RequestHandle>(job.get());
+
+ // If there is an executor that is ready to run this request, submit it!
+ Executor* executor = FindIdleExecutor();
+ if (executor) {
+ DCHECK_EQ(0u, pending_jobs_.size());
+ executor->StartJob(job.get());
+ return ERR_IO_PENDING;
+ }
+
+ // Otherwise queue this request. (We will schedule it to a thread once one
+ // becomes available).
+ job->WaitingForThread();
+ pending_jobs_.push_back(job);
+
+ // If we haven't already reached the thread limit, provision a new thread to
+ // drain the requests more quickly.
+ if (executors_.size() < max_num_threads_) {
+ executor = AddNewExecutor();
+ executor->StartJob(
+ new SetPacScriptJob(current_script_data_, CompletionCallback()));
+ }
+
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedProxyResolver::CancelRequest(RequestHandle req) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(req);
+
+ Job* job = reinterpret_cast<Job*>(req);
+ DCHECK_EQ(Job::TYPE_GET_PROXY_FOR_URL, job->type());
+
+ if (job->executor()) {
+ // If the job was already submitted to the executor, just mark it
+ // as cancelled so the user callback isn't run on completion.
+ job->Cancel();
+ } else {
+ // Otherwise the job is just sitting in a queue.
+ PendingJobsQueue::iterator it =
+ std::find(pending_jobs_.begin(), pending_jobs_.end(), job);
+ DCHECK(it != pending_jobs_.end());
+ pending_jobs_.erase(it);
+ }
+}
+
+LoadState MultiThreadedProxyResolver::GetLoadState(RequestHandle req) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(req);
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+}
+
+void MultiThreadedProxyResolver::CancelSetPacScript() {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(0u, pending_jobs_.size());
+ DCHECK_EQ(1u, executors_.size());
+ DCHECK_EQ(Job::TYPE_SET_PAC_SCRIPT,
+ executors_[0]->outstanding_job()->type());
+
+ // Defensively clear some data which shouldn't be getting used
+ // anymore.
+ current_script_data_ = NULL;
+
+ ReleaseAllExecutors();
+}
+
+void MultiThreadedProxyResolver::PurgeMemory() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = it->get();
+ executor->PurgeMemory();
+ }
+}
+
+int MultiThreadedProxyResolver::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback&callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ // Save the script details, so we can provision new executors later.
+ current_script_data_ = script_data;
+
+ // The user should not have any outstanding requests when they call
+ // SetPacScript().
+ CheckNoOutstandingUserRequests();
+
+ // Destroy all of the current threads and their proxy resolvers.
+ ReleaseAllExecutors();
+
+ // Provision a new executor, and run the SetPacScript request. On completion
+ // notification will be sent through |callback|.
+ Executor* executor = AddNewExecutor();
+ executor->StartJob(new SetPacScriptJob(script_data, callback));
+ return ERR_IO_PENDING;
+}
+
+void MultiThreadedProxyResolver::CheckNoOutstandingUserRequests() const {
+ DCHECK(CalledOnValidThread());
+ CHECK_EQ(0u, pending_jobs_.size());
+
+ for (ExecutorList::const_iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ const Executor* executor = it->get();
+ Job* job = executor->outstanding_job();
+ // The "has_user_callback()" is to exclude jobs for which the callback
+ // has already been invoked, or was not user-initiated (as in the case of
+ // lazy thread provisions). User-initiated jobs may !has_user_callback()
+ // when the callback has already been run. (Since we only clear the
+ // outstanding job AFTER the callback has been invoked, it is possible
+ // for a new request to be started from within the callback).
+ CHECK(!job || job->was_cancelled() || !job->has_user_callback());
+ }
+}
+
+void MultiThreadedProxyResolver::ReleaseAllExecutors() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = it->get();
+ executor->Destroy();
+ }
+ executors_.clear();
+}
+
+MultiThreadedProxyResolver::Executor*
+MultiThreadedProxyResolver::FindIdleExecutor() {
+ DCHECK(CalledOnValidThread());
+ for (ExecutorList::iterator it = executors_.begin();
+ it != executors_.end(); ++it) {
+ Executor* executor = it->get();
+ if (!executor->outstanding_job())
+ return executor;
+ }
+ return NULL;
+}
+
+MultiThreadedProxyResolver::Executor*
+MultiThreadedProxyResolver::AddNewExecutor() {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LT(executors_.size(), max_num_threads_);
+ // The "thread number" is used to give the thread a unique name.
+ int thread_number = executors_.size();
+ ProxyResolver* resolver = resolver_factory_->CreateProxyResolver();
+ Executor* executor = new Executor(
+ this, resolver, thread_number);
+ executors_.push_back(make_scoped_refptr(executor));
+ return executor;
+}
+
+void MultiThreadedProxyResolver::OnExecutorReady(Executor* executor) {
+ DCHECK(CalledOnValidThread());
+ if (pending_jobs_.empty())
+ return;
+
+ // Get the next job to process (FIFO). Transfer it from the pending queue
+ // to the executor.
+ scoped_refptr<Job> job = pending_jobs_.front();
+ pending_jobs_.pop_front();
+ executor->StartJob(job.get());
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/multi_threaded_proxy_resolver.h b/chromium/net/proxy/multi_threaded_proxy_resolver.h
new file mode 100644
index 00000000000..3076c36a55f
--- /dev/null
+++ b/chromium/net/proxy/multi_threaded_proxy_resolver.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2011 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_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
+#define NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_resolver.h"
+
+namespace base {
+class Thread;
+} // namespace base
+
+namespace net {
+
+// ProxyResolverFactory is an interface for creating ProxyResolver instances.
+class ProxyResolverFactory {
+ public:
+ explicit ProxyResolverFactory(bool resolvers_expect_pac_bytes)
+ : resolvers_expect_pac_bytes_(resolvers_expect_pac_bytes) {}
+
+ virtual ~ProxyResolverFactory() {}
+
+ // Creates a new ProxyResolver. The caller is responsible for freeing this
+ // object.
+ virtual ProxyResolver* CreateProxyResolver() = 0;
+
+ bool resolvers_expect_pac_bytes() const {
+ return resolvers_expect_pac_bytes_;
+ }
+
+ private:
+ bool resolvers_expect_pac_bytes_;
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverFactory);
+};
+
+// MultiThreadedProxyResolver is a ProxyResolver implementation that runs
+// synchronous ProxyResolver implementations on worker threads.
+//
+// Threads are created lazily on demand, up to a maximum total. The advantage
+// of having a pool of threads, is faster performance. In particular, being
+// able to keep servicing PAC requests even if one blocks its execution.
+//
+// During initialization (SetPacScript), a single thread is spun up to test
+// the script. If this succeeds, we cache the input script, and will re-use
+// this to lazily provision any new threads as needed.
+//
+// For each new thread that we spawn, a corresponding new ProxyResolver is
+// created using ProxyResolverFactory.
+//
+// Because we are creating multiple ProxyResolver instances, this means we
+// are duplicating script contexts for what is ordinarily seen as being a
+// single script. This can affect compatibility on some classes of PAC
+// script:
+//
+// (a) Scripts whose initialization has external dependencies on network or
+// time may end up successfully initializing on some threads, but not
+// others. So depending on what thread services the request, the result
+// may jump between several possibilities.
+//
+// (b) Scripts whose FindProxyForURL() depends on side-effects may now
+// work differently. For example, a PAC script which was incrementing
+// a global counter and using that to make a decision. In the
+// multi-threaded model, each thread may have a different value for this
+// counter, so it won't globally be seen as monotonically increasing!
+class NET_EXPORT_PRIVATE MultiThreadedProxyResolver
+ : public ProxyResolver,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Creates an asynchronous ProxyResolver that runs requests on up to
+ // |max_num_threads|.
+ //
+ // For each thread that is created, an accompanying synchronous ProxyResolver
+ // will be provisioned using |resolver_factory|. All methods on these
+ // ProxyResolvers will be called on the one thread, with the exception of
+ // ProxyResolver::Shutdown() which will be called from the origin thread
+ // prior to destruction.
+ //
+ // The constructor takes ownership of |resolver_factory|.
+ MultiThreadedProxyResolver(ProxyResolverFactory* resolver_factory,
+ size_t max_num_threads);
+
+ virtual ~MultiThreadedProxyResolver();
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle request) OVERRIDE;
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE;
+ virtual void CancelSetPacScript() OVERRIDE;
+ virtual void PurgeMemory() OVERRIDE;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ class Executor;
+ class Job;
+ class SetPacScriptJob;
+ class GetProxyForURLJob;
+ // FIFO queue of pending jobs waiting to be started.
+ // TODO(eroman): Make this priority queue.
+ typedef std::deque<scoped_refptr<Job> > PendingJobsQueue;
+ typedef std::vector<scoped_refptr<Executor> > ExecutorList;
+
+ // Asserts that there are no outstanding user-initiated jobs on any of the
+ // worker threads.
+ void CheckNoOutstandingUserRequests() const;
+
+ // Stops and deletes all of the worker threads.
+ void ReleaseAllExecutors();
+
+ // Returns an idle worker thread which is ready to receive GetProxyForURL()
+ // requests. If all threads are occupied, returns NULL.
+ Executor* FindIdleExecutor();
+
+ // Creates a new worker thread, and appends it to |executors_|.
+ Executor* AddNewExecutor();
+
+ // Starts the next job from |pending_jobs_| if possible.
+ void OnExecutorReady(Executor* executor);
+
+ const scoped_ptr<ProxyResolverFactory> resolver_factory_;
+ const size_t max_num_threads_;
+ PendingJobsQueue pending_jobs_;
+ ExecutorList executors_;
+ scoped_refptr<ProxyResolverScriptData> current_script_data_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_MULTI_THREADED_PROXY_RESOLVER_H_
diff --git a/chromium/net/proxy/multi_threaded_proxy_resolver_unittest.cc b/chromium/net/proxy/multi_threaded_proxy_resolver_unittest.cc
new file mode 100644
index 00000000000..79c0acc1ded
--- /dev/null
+++ b/chromium/net/proxy/multi_threaded_proxy_resolver_unittest.cc
@@ -0,0 +1,786 @@
+// Copyright (c) 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.
+
+#include "net/proxy/multi_threaded_proxy_resolver.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+// A synchronous mock ProxyResolver implementation, which can be used in
+// conjunction with MultiThreadedProxyResolver.
+// - returns a single-item proxy list with the query's host.
+class MockProxyResolver : public ProxyResolver {
+ public:
+ MockProxyResolver()
+ : ProxyResolver(true /*expects_pac_bytes*/),
+ wrong_loop_(base::MessageLoop::current()),
+ request_count_(0),
+ purge_count_(0) {}
+
+ // ProxyResolver implementation.
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ if (resolve_latency_ != base::TimeDelta())
+ base::PlatformThread::Sleep(resolve_latency_);
+
+ CheckIsOnWorkerThread();
+
+ EXPECT_TRUE(callback.is_null());
+ EXPECT_TRUE(request == NULL);
+
+ // Write something into |net_log| (doesn't really have any meaning.)
+ net_log.BeginEvent(NetLog::TYPE_PAC_JAVASCRIPT_ALERT);
+
+ results->UseNamedProxy(query_url.host());
+
+ // Return a success code which represents the request's order.
+ return request_count_++;
+ }
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+
+ virtual void CancelSetPacScript() OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) OVERRIDE {
+ CheckIsOnWorkerThread();
+ last_script_data_ = script_data;
+ return OK;
+ }
+
+ virtual void PurgeMemory() OVERRIDE {
+ CheckIsOnWorkerThread();
+ ++purge_count_;
+ }
+
+ int purge_count() const { return purge_count_; }
+ int request_count() const { return request_count_; }
+
+ const ProxyResolverScriptData* last_script_data() const {
+ return last_script_data_.get();
+ }
+
+ void SetResolveLatency(base::TimeDelta latency) {
+ resolve_latency_ = latency;
+ }
+
+ private:
+ void CheckIsOnWorkerThread() {
+ // We should be running on the worker thread -- while we don't know the
+ // message loop of MultiThreadedProxyResolver's worker thread, we do
+ // know that it is going to be distinct from the loop running the
+ // test, so at least make sure it isn't the main loop.
+ EXPECT_NE(base::MessageLoop::current(), wrong_loop_);
+ }
+
+ base::MessageLoop* wrong_loop_;
+ int request_count_;
+ int purge_count_;
+ scoped_refptr<ProxyResolverScriptData> last_script_data_;
+ base::TimeDelta resolve_latency_;
+};
+
+
+// A mock synchronous ProxyResolver which can be set to block upon reaching
+// GetProxyForURL().
+// TODO(eroman): WaitUntilBlocked() *must* be called before calling Unblock(),
+// otherwise there will be a race on |should_block_| since it is
+// read without any synchronization.
+class BlockableProxyResolver : public MockProxyResolver {
+ public:
+ BlockableProxyResolver()
+ : should_block_(false),
+ unblocked_(true, true),
+ blocked_(true, false) {
+ }
+
+ void Block() {
+ should_block_ = true;
+ unblocked_.Reset();
+ }
+
+ void Unblock() {
+ should_block_ = false;
+ blocked_.Reset();
+ unblocked_.Signal();
+ }
+
+ void WaitUntilBlocked() {
+ blocked_.Wait();
+ }
+
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ if (should_block_) {
+ blocked_.Signal();
+ unblocked_.Wait();
+ }
+
+ return MockProxyResolver::GetProxyForURL(
+ query_url, results, callback, request, net_log);
+ }
+
+ private:
+ bool should_block_;
+ base::WaitableEvent unblocked_;
+ base::WaitableEvent blocked_;
+};
+
+// ForwardingProxyResolver forwards all requests to |impl|.
+class ForwardingProxyResolver : public ProxyResolver {
+ public:
+ explicit ForwardingProxyResolver(ProxyResolver* impl)
+ : ProxyResolver(impl->expects_pac_bytes()),
+ impl_(impl) {}
+
+ virtual int GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ return impl_->GetProxyForURL(
+ query_url, results, callback, request, net_log);
+ }
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE {
+ impl_->CancelRequest(request);
+ }
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+
+ virtual void CancelSetPacScript() OVERRIDE {
+ impl_->CancelSetPacScript();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) OVERRIDE {
+ return impl_->SetPacScript(script_data, callback);
+ }
+
+ virtual void PurgeMemory() OVERRIDE {
+ impl_->PurgeMemory();
+ }
+
+ private:
+ ProxyResolver* impl_;
+};
+
+// This factory returns ProxyResolvers that forward all requests to
+// |resolver|.
+class ForwardingProxyResolverFactory : public ProxyResolverFactory {
+ public:
+ explicit ForwardingProxyResolverFactory(ProxyResolver* resolver)
+ : ProxyResolverFactory(resolver->expects_pac_bytes()),
+ resolver_(resolver) {}
+
+ virtual ProxyResolver* CreateProxyResolver() OVERRIDE {
+ return new ForwardingProxyResolver(resolver_);
+ }
+
+ private:
+ ProxyResolver* resolver_;
+};
+
+// This factory returns new instances of BlockableProxyResolver.
+class BlockableProxyResolverFactory : public ProxyResolverFactory {
+ public:
+ BlockableProxyResolverFactory() : ProxyResolverFactory(true) {}
+
+ virtual ~BlockableProxyResolverFactory() {
+ STLDeleteElements(&resolvers_);
+ }
+
+ virtual ProxyResolver* CreateProxyResolver() OVERRIDE {
+ BlockableProxyResolver* resolver = new BlockableProxyResolver;
+ resolvers_.push_back(resolver);
+ return new ForwardingProxyResolver(resolver);
+ }
+
+ std::vector<BlockableProxyResolver*> resolvers() {
+ return resolvers_;
+ }
+
+ private:
+ std::vector<BlockableProxyResolver*> resolvers_;
+};
+
+TEST(MultiThreadedProxyResolverTest, SingleThread_Basic) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<MockProxyResolver> mock(new MockProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Call SetPacScriptByData() -- verify that it reaches the synchronous
+ // resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ set_script_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ mock->last_script_data()->utf16());
+
+ // Start request 0.
+ TestCompletionCallback callback0;
+ CapturingBoundNetLog log0;
+ ProxyInfo results0;
+ rv = resolver.GetProxyForURL(GURL("http://request0"), &results0,
+ callback0.callback(), NULL, log0.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for request 0 to finish.
+ rv = callback0.WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ("PROXY request0:80", results0.ToPacString());
+
+ // The mock proxy resolver should have written 1 log entry. And
+ // on completion, this should have been copied into |log0|.
+ // We also have 1 log entry that was emitted by the
+ // MultiThreadedProxyResolver.
+ CapturingNetLog::CapturedEntryList entries0;
+ log0.GetEntries(&entries0);
+
+ ASSERT_EQ(2u, entries0.size());
+ EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD, entries0[0].type);
+
+ // Start 3 more requests (request1 to request3).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver.GetProxyForURL(GURL("http://request1"), &results1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver.GetProxyForURL(GURL("http://request2"), &results2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback3;
+ ProxyInfo results3;
+ rv = resolver.GetProxyForURL(GURL("http://request3"), &results3,
+ callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for the requests to finish (they must finish in the order they were
+ // started, which is what we check for from their magic return value)
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(1, rv);
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(2, rv);
+ EXPECT_EQ("PROXY request2:80", results2.ToPacString());
+
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(3, rv);
+ EXPECT_EQ("PROXY request3:80", results3.ToPacString());
+
+ // Ensure that PurgeMemory() reaches the wrapped resolver and happens on the
+ // right thread.
+ EXPECT_EQ(0, mock->purge_count());
+ resolver.PurgeMemory();
+ // There is no way to get a callback directly when PurgeMemory() completes, so
+ // we queue up a dummy request after the PurgeMemory() call and wait until it
+ // finishes to ensure PurgeMemory() has had a chance to run.
+ TestCompletionCallback dummy_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("dummy"),
+ dummy_callback.callback());
+ EXPECT_EQ(OK, dummy_callback.WaitForResult());
+ EXPECT_EQ(1, mock->purge_count());
+}
+
+// Tests that the NetLog is updated to include the time the request was waiting
+// to be scheduled to a thread.
+TEST(MultiThreadedProxyResolverTest,
+ SingleThread_UpdatesNetLogWithThreadWait) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ init_callback.callback());
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start request 0.
+ ProxyResolver::RequestHandle request0;
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ CapturingBoundNetLog log0;
+ rv = resolver.GetProxyForURL(GURL("http://request0"), &results0,
+ callback0.callback(), &request0, log0.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Start 2 more requests (request1 and request2).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ CapturingBoundNetLog log1;
+ rv = resolver.GetProxyForURL(GURL("http://request1"), &results1,
+ callback1.callback(), NULL, log1.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyResolver::RequestHandle request2;
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ CapturingBoundNetLog log2;
+ rv = resolver.GetProxyForURL(GURL("http://request2"), &results2,
+ callback2.callback(), &request2, log2.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Unblock the worker thread so the requests can continue running.
+ mock->WaitUntilBlocked();
+ mock->Unblock();
+
+ // Check that request 0 completed as expected.
+ // The NetLog has 1 entry that came from the MultiThreadedProxyResolver, and
+ // 1 entry from the mock proxy resolver.
+ EXPECT_EQ(0, callback0.WaitForResult());
+ EXPECT_EQ("PROXY request0:80", results0.ToPacString());
+
+ CapturingNetLog::CapturedEntryList entries0;
+ log0.GetEntries(&entries0);
+
+ ASSERT_EQ(2u, entries0.size());
+ EXPECT_EQ(NetLog::TYPE_SUBMITTED_TO_RESOLVER_THREAD,
+ entries0[0].type);
+
+ // Check that request 1 completed as expected.
+ EXPECT_EQ(1, callback1.WaitForResult());
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+
+ CapturingNetLog::CapturedEntryList entries1;
+ log1.GetEntries(&entries1);
+
+ ASSERT_EQ(4u, entries1.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries1, 0,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries1, 1,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+
+ // Check that request 2 completed as expected.
+ EXPECT_EQ(2, callback2.WaitForResult());
+ EXPECT_EQ("PROXY request2:80", results2.ToPacString());
+
+ CapturingNetLog::CapturedEntryList entries2;
+ log2.GetEntries(&entries2);
+
+ ASSERT_EQ(4u, entries2.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries2, 0,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries2, 1,
+ NetLog::TYPE_WAITING_FOR_PROXY_RESOLVER_THREAD));
+}
+
+// Cancel a request which is in progress, and then cancel a request which
+// is pending.
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequest) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()),
+ kNumThreads);
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ init_callback.callback());
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start request 0.
+ ProxyResolver::RequestHandle request0;
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ rv = resolver.GetProxyForURL(GURL("http://request0"), &results0,
+ callback0.callback(), &request0, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait until requests 0 reaches the worker thread.
+ mock->WaitUntilBlocked();
+
+ // Start 3 more requests (request1 : request3).
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver.GetProxyForURL(GURL("http://request1"), &results1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyResolver::RequestHandle request2;
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver.GetProxyForURL(GURL("http://request2"), &results2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback3;
+ ProxyInfo results3;
+ rv = resolver.GetProxyForURL(GURL("http://request3"), &results3,
+ callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Cancel request0 (inprogress) and request2 (pending).
+ resolver.CancelRequest(request0);
+ resolver.CancelRequest(request2);
+
+ // Unblock the worker thread so the requests can continue running.
+ mock->Unblock();
+
+ // Wait for requests 1 and 3 to finish.
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(1, rv);
+ EXPECT_EQ("PROXY request1:80", results1.ToPacString());
+
+ rv = callback3.WaitForResult();
+ // Note that since request2 was cancelled before reaching the resolver,
+ // the request count is 2 and not 3 here.
+ EXPECT_EQ(2, rv);
+ EXPECT_EQ("PROXY request3:80", results3.ToPacString());
+
+ // Requests 0 and 2 which were cancelled, hence their completion callbacks
+ // were never summoned.
+ EXPECT_FALSE(callback0.have_result());
+ EXPECT_FALSE(callback2.have_result());
+}
+
+// Test that deleting MultiThreadedProxyResolver while requests are
+// outstanding cancels them (and doesn't leak anything).
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelRequestByDeleting) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ scoped_ptr<MultiThreadedProxyResolver> resolver(
+ new MultiThreadedProxyResolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads));
+
+ int rv;
+
+ // Initialize the resolver.
+ TestCompletionCallback init_callback;
+ rv = resolver->SetPacScript(ProxyResolverScriptData::FromUTF8("foo"),
+ init_callback.callback());
+ EXPECT_EQ(OK, init_callback.WaitForResult());
+
+ // Block the proxy resolver, so no request can complete.
+ mock->Block();
+
+ // Start 3 requests.
+
+ TestCompletionCallback callback0;
+ ProxyInfo results0;
+ rv = resolver->GetProxyForURL(GURL("http://request0"), &results0,
+ callback0.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback1;
+ ProxyInfo results1;
+ rv = resolver->GetProxyForURL(GURL("http://request1"), &results1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestCompletionCallback callback2;
+ ProxyInfo results2;
+ rv = resolver->GetProxyForURL(GURL("http://request2"), &results2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait until request 0 reaches the worker thread.
+ mock->WaitUntilBlocked();
+
+ // Add some latency, to improve the chance that when
+ // MultiThreadedProxyResolver is deleted below we are still running inside
+ // of the worker thread. The test will pass regardless, so this race doesn't
+ // cause flakiness. However the destruction during execution is a more
+ // interesting case to test.
+ mock->SetResolveLatency(base::TimeDelta::FromMilliseconds(100));
+
+ // Unblock the worker thread and delete the underlying
+ // MultiThreadedProxyResolver immediately.
+ mock->Unblock();
+ resolver.reset();
+
+ // Give any posted tasks a chance to run (in case there is badness).
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Check that none of the outstanding requests were completed.
+ EXPECT_FALSE(callback0.have_result());
+ EXPECT_FALSE(callback1.have_result());
+ EXPECT_FALSE(callback2.have_result());
+}
+
+// Cancel an outstanding call to SetPacScriptByData().
+TEST(MultiThreadedProxyResolverTest, SingleThread_CancelSetPacScript) {
+ const size_t kNumThreads = 1u;
+ scoped_ptr<BlockableProxyResolver> mock(new BlockableProxyResolver);
+ MultiThreadedProxyResolver resolver(
+ new ForwardingProxyResolverFactory(mock.get()), kNumThreads);
+
+ int rv;
+
+ TestCompletionCallback set_pac_script_callback;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data"),
+ set_pac_script_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Cancel the SetPacScriptByData request.
+ resolver.CancelSetPacScript();
+
+ // Start another SetPacScript request
+ TestCompletionCallback set_pac_script_callback2;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("data2"),
+ set_pac_script_callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for the initialization to complete.
+
+ rv = set_pac_script_callback2.WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ(ASCIIToUTF16("data2"), mock->last_script_data()->utf16());
+
+ // The first SetPacScript callback should never have been completed.
+ EXPECT_FALSE(set_pac_script_callback.have_result());
+}
+
+// Tests setting the PAC script once, lazily creating new threads, and
+// cancelling requests.
+TEST(MultiThreadedProxyResolverTest, ThreeThreads_Basic) {
+ const size_t kNumThreads = 3u;
+ BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory;
+ MultiThreadedProxyResolver resolver(factory, kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Call SetPacScriptByData() -- verify that it reaches the synchronous
+ // resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ set_script_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ // One thread has been provisioned (i.e. one ProxyResolver was created).
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[0]->last_script_data()->utf16());
+
+ const int kNumRequests = 9;
+ TestCompletionCallback callback[kNumRequests];
+ ProxyInfo results[kNumRequests];
+ ProxyResolver::RequestHandle request[kNumRequests];
+
+ // Start request 0 -- this should run on thread 0 as there is nothing else
+ // going on right now.
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results[0], callback[0].callback(), &request[0],
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait for request 0 to finish.
+ rv = callback[0].WaitForResult();
+ EXPECT_EQ(0, rv);
+ EXPECT_EQ("PROXY request0:80", results[0].ToPacString());
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(1, factory->resolvers()[0]->request_count());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // We now start 8 requests in parallel -- this will cause the maximum of
+ // three threads to be provisioned (an additional two from what we already
+ // have).
+
+ for (int i = 1; i < kNumRequests; ++i) {
+ rv = resolver.GetProxyForURL(
+ GURL(base::StringPrintf("http://request%d", i)), &results[i],
+ callback[i].callback(), &request[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // We should now have a total of 3 threads, each with its own ProxyResolver
+ // that will get initialized with the same data. (We check this later since
+ // the assignment happens on the worker threads and may not have occurred
+ // yet.)
+ ASSERT_EQ(3u, factory->resolvers().size());
+
+ // Cancel 3 of the 8 oustanding requests.
+ resolver.CancelRequest(request[1]);
+ resolver.CancelRequest(request[3]);
+ resolver.CancelRequest(request[6]);
+
+ // Wait for the remaining requests to complete.
+ int kNonCancelledRequests[] = {2, 4, 5, 7, 8};
+ for (size_t i = 0; i < arraysize(kNonCancelledRequests); ++i) {
+ int request_index = kNonCancelledRequests[i];
+ EXPECT_GE(callback[request_index].WaitForResult(), 0);
+ }
+
+ // Check that the cancelled requests never invoked their callback.
+ EXPECT_FALSE(callback[1].have_result());
+ EXPECT_FALSE(callback[3].have_result());
+ EXPECT_FALSE(callback[6].have_result());
+
+ // We call SetPacScript again, solely to stop the current worker threads.
+ // (That way we can test to see the values observed by the synchronous
+ // resolvers in a non-racy manner).
+ TestCompletionCallback set_script_callback2;
+ rv = resolver.SetPacScript(ProxyResolverScriptData::FromUTF8("xyz"),
+ set_script_callback2.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback2.WaitForResult());
+ ASSERT_EQ(4u, factory->resolvers().size());
+
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(
+ ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[i]->last_script_data()->utf16()) << "i=" << i;
+ }
+
+ EXPECT_EQ(ASCIIToUTF16("xyz"),
+ factory->resolvers()[3]->last_script_data()->utf16());
+
+ // We don't know the exact ordering that requests ran on threads with,
+ // but we do know the total count that should have reached the threads.
+ // 8 total were submitted, and three were cancelled. Of the three that
+ // were cancelled, one of them (request 1) was cancelled after it had
+ // already been posted to the worker thread. So the resolvers will
+ // have seen 6 total (and 1 from the run prior).
+ ASSERT_EQ(4u, factory->resolvers().size());
+ int total_count = 0;
+ for (int i = 0; i < 3; ++i) {
+ total_count += factory->resolvers()[i]->request_count();
+ }
+ EXPECT_EQ(7, total_count);
+}
+
+// Tests using two threads. The first request hangs the first thread. Checks
+// that other requests are able to complete while this first request remains
+// stalled.
+TEST(MultiThreadedProxyResolverTest, OneThreadBlocked) {
+ const size_t kNumThreads = 2u;
+ BlockableProxyResolverFactory* factory = new BlockableProxyResolverFactory;
+ MultiThreadedProxyResolver resolver(factory, kNumThreads);
+
+ int rv;
+
+ EXPECT_TRUE(resolver.expects_pac_bytes());
+
+ // Initialize the resolver.
+ TestCompletionCallback set_script_callback;
+ rv = resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF8("pac script bytes"),
+ set_script_callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, set_script_callback.WaitForResult());
+ // One thread has been provisioned (i.e. one ProxyResolver was created).
+ ASSERT_EQ(1u, factory->resolvers().size());
+ EXPECT_EQ(ASCIIToUTF16("pac script bytes"),
+ factory->resolvers()[0]->last_script_data()->utf16());
+
+ const int kNumRequests = 4;
+ TestCompletionCallback callback[kNumRequests];
+ ProxyInfo results[kNumRequests];
+ ProxyResolver::RequestHandle request[kNumRequests];
+
+ // Start a request that will block the first thread.
+
+ factory->resolvers()[0]->Block();
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://request0"), &results[0], callback[0].callback(), &request[0],
+ BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ factory->resolvers()[0]->WaitUntilBlocked();
+
+ // Start 3 more requests -- they should all be serviced by thread #2
+ // since thread #1 is blocked.
+
+ for (int i = 1; i < kNumRequests; ++i) {
+ rv = resolver.GetProxyForURL(
+ GURL(base::StringPrintf("http://request%d", i)),
+ &results[i], callback[i].callback(), &request[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // Wait for the three requests to complete (they should complete in FIFO
+ // order).
+ for (int i = 1; i < kNumRequests; ++i) {
+ EXPECT_EQ(i - 1, callback[i].WaitForResult());
+ }
+
+ // Unblock the first thread.
+ factory->resolvers()[0]->Unblock();
+ EXPECT_EQ(0, callback[0].WaitForResult());
+
+ // All in all, the first thread should have seen just 1 request. And the
+ // second thread 3 requests.
+ ASSERT_EQ(2u, factory->resolvers().size());
+ EXPECT_EQ(1, factory->resolvers()[0]->request_count());
+ EXPECT_EQ(3, factory->resolvers()[1]->request_count());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/proxy/network_delegate_error_observer.cc b/chromium/net/proxy/network_delegate_error_observer.cc
new file mode 100644
index 00000000000..8029141a0a4
--- /dev/null
+++ b/chromium/net/proxy/network_delegate_error_observer.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 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/proxy/network_delegate_error_observer.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+
+namespace net {
+
+// NetworkDelegateErrorObserver::Core -----------------------------------------
+
+class NetworkDelegateErrorObserver::Core
+ : public base::RefCountedThreadSafe<NetworkDelegateErrorObserver::Core> {
+ public:
+ Core(NetworkDelegate* network_delegate, base::MessageLoopProxy* origin_loop);
+
+ void NotifyPACScriptError(int line_number, const base::string16& error);
+
+ void Shutdown();
+
+ private:
+ friend class base::RefCountedThreadSafe<NetworkDelegateErrorObserver::Core>;
+
+ virtual ~Core();
+
+ NetworkDelegate* network_delegate_;
+ scoped_refptr<base::MessageLoopProxy> origin_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+NetworkDelegateErrorObserver::Core::Core(NetworkDelegate* network_delegate,
+ base::MessageLoopProxy* origin_loop)
+ : network_delegate_(network_delegate),
+ origin_loop_(origin_loop) {
+ DCHECK(origin_loop);
+}
+
+NetworkDelegateErrorObserver::Core::~Core() {}
+
+
+void NetworkDelegateErrorObserver::Core::NotifyPACScriptError(
+ int line_number,
+ const base::string16& error) {
+ if (!origin_loop_->BelongsToCurrentThread()) {
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::NotifyPACScriptError, this, line_number, error));
+ return;
+ }
+ if (network_delegate_)
+ network_delegate_->NotifyPACScriptError(line_number, error);
+}
+
+void NetworkDelegateErrorObserver::Core::Shutdown() {
+ CHECK(origin_loop_->BelongsToCurrentThread());
+ network_delegate_ = NULL;
+}
+
+// NetworkDelegateErrorObserver -----------------------------------------------
+
+NetworkDelegateErrorObserver::NetworkDelegateErrorObserver(
+ NetworkDelegate* network_delegate,
+ base::MessageLoopProxy* origin_loop)
+ : core_(new Core(network_delegate, origin_loop)) {}
+
+NetworkDelegateErrorObserver::~NetworkDelegateErrorObserver() {
+ core_->Shutdown();
+}
+
+void NetworkDelegateErrorObserver::OnPACScriptError(
+ int line_number,
+ const base::string16& error) {
+ core_->NotifyPACScriptError(line_number, error);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/network_delegate_error_observer.h b/chromium/net/proxy/network_delegate_error_observer.h
new file mode 100644
index 00000000000..e4b03aa4437
--- /dev/null
+++ b/chromium/net/proxy/network_delegate_error_observer.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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_PROXY_NETWORK_DELEGATE_ERROR_OBSERVER_H_
+#define NET_PROXY_NETWORK_DELEGATE_ERROR_OBSERVER_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/proxy/proxy_resolver_error_observer.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace net {
+
+class NetworkDelegate;
+
+// An implementation of ProxyResolverErrorObserver that forwards PAC script
+// errors to a NetworkDelegate object on the thread it lives on.
+class NET_EXPORT_PRIVATE NetworkDelegateErrorObserver
+ : public ProxyResolverErrorObserver {
+ public:
+ NetworkDelegateErrorObserver(NetworkDelegate* network_delegate,
+ base::MessageLoopProxy* origin_loop);
+ virtual ~NetworkDelegateErrorObserver();
+
+ // ProxyResolverErrorObserver implementation.
+ virtual void OnPACScriptError(int line_number, const base::string16& error)
+ OVERRIDE;
+
+ private:
+ class Core;
+
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkDelegateErrorObserver);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_NETWORK_DELEGATE_ERROR_OBSERVER_H_
diff --git a/chromium/net/proxy/network_delegate_error_observer_unittest.cc b/chromium/net/proxy/network_delegate_error_observer_unittest.cc
new file mode 100644
index 00000000000..cb32760cafd
--- /dev/null
+++ b/chromium/net/proxy/network_delegate_error_observer_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 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.
+
+#include "net/proxy/network_delegate_error_observer.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class TestNetworkDelegate : public net::NetworkDelegate {
+ public:
+ TestNetworkDelegate() : got_pac_error_(false) {}
+ virtual ~TestNetworkDelegate() {}
+
+ bool got_pac_error() const { return got_pac_error_; }
+
+ private:
+ // net::NetworkDelegate implementation.
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE {
+ return OK;
+ }
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE {
+ return OK;
+ }
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE {}
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE {
+ return net::OK;
+ }
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE {}
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE {}
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE {}
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE {}
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE {}
+
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) OVERRIDE {
+ got_pac_error_ = true;
+ }
+ virtual AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE {
+ return AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE {
+ return true;
+ }
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE {
+ return true;
+ }
+ virtual bool OnCanAccessFile(const net::URLRequest& request,
+ const base::FilePath& path) const OVERRIDE {
+ return true;
+ }
+ virtual bool OnCanThrottleRequest(const URLRequest& request) const OVERRIDE {
+ return false;
+ }
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+ virtual void OnRequestWaitStateChange(const net::URLRequest& request,
+ RequestWaitState state) OVERRIDE {
+ }
+
+ bool got_pac_error_;
+};
+
+} // namespace
+
+// Check that the OnPACScriptError method can be called from an arbitrary
+// thread.
+TEST(NetworkDelegateErrorObserverTest, CallOnThread) {
+ base::Thread thread("test_thread");
+ thread.Start();
+ TestNetworkDelegate network_delegate;
+ NetworkDelegateErrorObserver observer(
+ &network_delegate, base::MessageLoopProxy::current().get());
+ thread.message_loop()
+ ->PostTask(FROM_HERE,
+ base::Bind(&NetworkDelegateErrorObserver::OnPACScriptError,
+ base::Unretained(&observer),
+ 42,
+ base::string16()));
+ thread.Stop();
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(network_delegate.got_pac_error());
+}
+
+// Check that passing a NULL network delegate works.
+TEST(NetworkDelegateErrorObserverTest, NoDelegate) {
+ base::Thread thread("test_thread");
+ thread.Start();
+ NetworkDelegateErrorObserver observer(
+ NULL, base::MessageLoopProxy::current().get());
+ thread.message_loop()
+ ->PostTask(FROM_HERE,
+ base::Bind(&NetworkDelegateErrorObserver::OnPACScriptError,
+ base::Unretained(&observer),
+ 42,
+ base::string16()));
+ thread.Stop();
+ base::MessageLoop::current()->RunUntilIdle();
+ // Shouldn't have crashed until here...
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/polling_proxy_config_service.cc b/chromium/net/proxy/polling_proxy_config_service.cc
new file mode 100644
index 00000000000..611ea5975c0
--- /dev/null
+++ b/chromium/net/proxy/polling_proxy_config_service.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 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.
+
+#include "net/proxy/polling_proxy_config_service.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/observer_list.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/worker_pool.h"
+#include "net/proxy/proxy_config.h"
+
+namespace net {
+
+// Reference-counted wrapper that does all the work (needs to be
+// reference-counted since we post tasks between threads; may outlive
+// the parent PollingProxyConfigService).
+class PollingProxyConfigService::Core
+ : public base::RefCountedThreadSafe<PollingProxyConfigService::Core> {
+ public:
+ Core(base::TimeDelta poll_interval,
+ GetConfigFunction get_config_func)
+ : get_config_func_(get_config_func),
+ poll_interval_(poll_interval),
+ have_initialized_origin_loop_(false),
+ has_config_(false),
+ poll_task_outstanding_(false),
+ poll_task_queued_(false) {
+ }
+
+ // Called when the parent PollingProxyConfigService is destroyed
+ // (observers should not be called past this point).
+ void Orphan() {
+ base::AutoLock l(lock_);
+ origin_loop_proxy_ = NULL;
+ }
+
+ bool GetLatestProxyConfig(ProxyConfig* config) {
+ LazyInitializeOriginLoop();
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+
+ OnLazyPoll();
+
+ // If we have already retrieved the proxy settings (on worker thread)
+ // then return what we last saw.
+ if (has_config_) {
+ *config = last_config_;
+ return true;
+ }
+ return false;
+ }
+
+ void AddObserver(Observer* observer) {
+ LazyInitializeOriginLoop();
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+ observers_.AddObserver(observer);
+ }
+
+ void RemoveObserver(Observer* observer) {
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+ observers_.RemoveObserver(observer);
+ }
+
+ // Check for a new configuration if enough time has elapsed.
+ void OnLazyPoll() {
+ LazyInitializeOriginLoop();
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+
+ if (last_poll_time_.is_null() ||
+ (base::TimeTicks::Now() - last_poll_time_) > poll_interval_) {
+ CheckForChangesNow();
+ }
+ }
+
+ void CheckForChangesNow() {
+ LazyInitializeOriginLoop();
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+
+ if (poll_task_outstanding_) {
+ // Only allow one task to be outstanding at a time. If we get a poll
+ // request while we are busy, we will defer it until the current poll
+ // completes.
+ poll_task_queued_ = true;
+ return;
+ }
+
+ last_poll_time_ = base::TimeTicks::Now();
+ poll_task_outstanding_ = true;
+ poll_task_queued_ = false;
+ base::WorkerPool::PostTask(
+ FROM_HERE,
+ base::Bind(&Core::PollOnWorkerThread, this, get_config_func_),
+ true);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ ~Core() {}
+
+ void PollOnWorkerThread(GetConfigFunction func) {
+ ProxyConfig config;
+ func(&config);
+
+ base::AutoLock l(lock_);
+ if (origin_loop_proxy_.get()) {
+ origin_loop_proxy_->PostTask(
+ FROM_HERE, base::Bind(&Core::GetConfigCompleted, this, config));
+ }
+ }
+
+ // Called after the worker thread has finished retrieving a configuration.
+ void GetConfigCompleted(const ProxyConfig& config) {
+ DCHECK(poll_task_outstanding_);
+ poll_task_outstanding_ = false;
+
+ if (!origin_loop_proxy_.get())
+ return; // Was orphaned (parent has already been destroyed).
+
+ DCHECK(origin_loop_proxy_->BelongsToCurrentThread());
+
+ if (!has_config_ || !last_config_.Equals(config)) {
+ // If the configuration has changed, notify the observers.
+ has_config_ = true;
+ last_config_ = config;
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnProxyConfigChanged(config,
+ ProxyConfigService::CONFIG_VALID));
+ }
+
+ if (poll_task_queued_)
+ CheckForChangesNow();
+ }
+
+ void LazyInitializeOriginLoop() {
+ // TODO(eroman): Really this should be done in the constructor, but right
+ // now chrome is constructing the ProxyConfigService on the
+ // UI thread so we can't cache the IO thread for the purpose
+ // of DCHECKs until the first call is made.
+ if (!have_initialized_origin_loop_) {
+ origin_loop_proxy_ = base::MessageLoopProxy::current();
+ have_initialized_origin_loop_ = true;
+ }
+ }
+
+ GetConfigFunction get_config_func_;
+ ObserverList<Observer> observers_;
+ ProxyConfig last_config_;
+ base::TimeTicks last_poll_time_;
+ base::TimeDelta poll_interval_;
+
+ base::Lock lock_;
+ scoped_refptr<base::MessageLoopProxy> origin_loop_proxy_;
+
+ bool have_initialized_origin_loop_;
+ bool has_config_;
+ bool poll_task_outstanding_;
+ bool poll_task_queued_;
+};
+
+void PollingProxyConfigService::AddObserver(Observer* observer) {
+ core_->AddObserver(observer);
+}
+
+void PollingProxyConfigService::RemoveObserver(Observer* observer) {
+ core_->RemoveObserver(observer);
+}
+
+ProxyConfigService::ConfigAvailability
+ PollingProxyConfigService::GetLatestProxyConfig(ProxyConfig* config) {
+ return core_->GetLatestProxyConfig(config) ? CONFIG_VALID : CONFIG_PENDING;
+}
+
+void PollingProxyConfigService::OnLazyPoll() {
+ core_->OnLazyPoll();
+}
+
+PollingProxyConfigService::PollingProxyConfigService(
+ base::TimeDelta poll_interval,
+ GetConfigFunction get_config_func)
+ : core_(new Core(poll_interval, get_config_func)) {
+}
+
+PollingProxyConfigService::~PollingProxyConfigService() {
+ core_->Orphan();
+}
+
+void PollingProxyConfigService::CheckForChangesNow() {
+ core_->CheckForChangesNow();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/polling_proxy_config_service.h b/chromium/net/proxy/polling_proxy_config_service.h
new file mode 100644
index 00000000000..f5f5117238a
--- /dev/null
+++ b/chromium/net/proxy/polling_proxy_config_service.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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_PROXY_POLLING_PROXY_CONFIG_SERVICE_H_
+#define NET_PROXY_POLLING_PROXY_CONFIG_SERVICE_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/proxy/proxy_config_service.h"
+
+namespace net {
+
+// PollingProxyConfigService is a base class for creating ProxyConfigService
+// implementations that use polling to notice when settings have change.
+//
+// It runs code to get the current proxy settings on a background worker
+// thread, and notifies registered observers when the value changes.
+class NET_EXPORT_PRIVATE PollingProxyConfigService : public ProxyConfigService {
+ public:
+ // ProxyConfigService implementation:
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE;
+ virtual void OnLazyPoll() OVERRIDE;
+
+ protected:
+ // Function for retrieving the current proxy configuration.
+ // Implementors must be threadsafe as the function will be invoked from
+ // worker threads.
+ typedef void (*GetConfigFunction)(ProxyConfig*);
+
+ // Creates a polling-based ProxyConfigService which will test for new
+ // settings at most every |poll_interval| time by calling |get_config_func|
+ // on a worker thread.
+ PollingProxyConfigService(
+ base::TimeDelta poll_interval,
+ GetConfigFunction get_config_func);
+
+ virtual ~PollingProxyConfigService();
+
+ // Polls for changes by posting a task to the worker pool.
+ void CheckForChangesNow();
+
+ private:
+ class Core;
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(PollingProxyConfigService);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_POLLING_PROXY_CONFIG_SERVICE_H_
diff --git a/chromium/net/proxy/proxy_bypass_rules.cc b/chromium/net/proxy/proxy_bypass_rules.cc
new file mode 100644
index 00000000000..a60adc352ff
--- /dev/null
+++ b/chromium/net/proxy/proxy_bypass_rules.cc
@@ -0,0 +1,347 @@
+// Copyright (c) 2011 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/proxy/proxy_bypass_rules.h"
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_tokenizer.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+namespace {
+
+class HostnamePatternRule : public ProxyBypassRules::Rule {
+ public:
+ HostnamePatternRule(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port)
+ : optional_scheme_(StringToLowerASCII(optional_scheme)),
+ hostname_pattern_(StringToLowerASCII(hostname_pattern)),
+ optional_port_(optional_port) {
+ }
+
+ virtual bool Matches(const GURL& url) const OVERRIDE {
+ if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_)
+ return false; // Didn't match port expectation.
+
+ if (!optional_scheme_.empty() && url.scheme() != optional_scheme_)
+ return false; // Didn't match scheme expectation.
+
+ // Note it is necessary to lower-case the host, since GURL uses capital
+ // letters for percent-escaped characters.
+ return MatchPattern(StringToLowerASCII(url.host()), hostname_pattern_);
+ }
+
+ virtual std::string ToString() const OVERRIDE {
+ std::string str;
+ if (!optional_scheme_.empty())
+ base::StringAppendF(&str, "%s://", optional_scheme_.c_str());
+ str += hostname_pattern_;
+ if (optional_port_ != -1)
+ base::StringAppendF(&str, ":%d", optional_port_);
+ return str;
+ }
+
+ virtual Rule* Clone() const OVERRIDE {
+ return new HostnamePatternRule(optional_scheme_,
+ hostname_pattern_,
+ optional_port_);
+ }
+
+ private:
+ const std::string optional_scheme_;
+ const std::string hostname_pattern_;
+ const int optional_port_;
+};
+
+class BypassLocalRule : public ProxyBypassRules::Rule {
+ public:
+ virtual bool Matches(const GURL& url) const OVERRIDE {
+ const std::string& host = url.host();
+ if (host == "127.0.0.1" || host == "[::1]")
+ return true;
+ return host.find('.') == std::string::npos;
+ }
+
+ virtual std::string ToString() const OVERRIDE {
+ return "<local>";
+ }
+
+ virtual Rule* Clone() const OVERRIDE {
+ return new BypassLocalRule();
+ }
+};
+
+// Rule for matching a URL that is an IP address, if that IP address falls
+// within a certain numeric range. For example, you could use this rule to
+// match all the IPs in the CIDR block 10.10.3.4/24.
+class BypassIPBlockRule : public ProxyBypassRules::Rule {
+ public:
+ // |ip_prefix| + |prefix_length| define the IP block to match.
+ BypassIPBlockRule(const std::string& description,
+ const std::string& optional_scheme,
+ const IPAddressNumber& ip_prefix,
+ size_t prefix_length_in_bits)
+ : description_(description),
+ optional_scheme_(optional_scheme),
+ ip_prefix_(ip_prefix),
+ prefix_length_in_bits_(prefix_length_in_bits) {
+ }
+
+ virtual bool Matches(const GURL& url) const OVERRIDE {
+ if (!url.HostIsIPAddress())
+ return false;
+
+ if (!optional_scheme_.empty() && url.scheme() != optional_scheme_)
+ return false; // Didn't match scheme expectation.
+
+ // Parse the input IP literal to a number.
+ IPAddressNumber ip_number;
+ if (!ParseIPLiteralToNumber(url.HostNoBrackets(), &ip_number))
+ return false;
+
+ // Test if it has the expected prefix.
+ return IPNumberMatchesPrefix(ip_number, ip_prefix_,
+ prefix_length_in_bits_);
+ }
+
+ virtual std::string ToString() const OVERRIDE {
+ return description_;
+ }
+
+ virtual Rule* Clone() const OVERRIDE {
+ return new BypassIPBlockRule(description_,
+ optional_scheme_,
+ ip_prefix_,
+ prefix_length_in_bits_);
+ }
+
+ private:
+ const std::string description_;
+ const std::string optional_scheme_;
+ const IPAddressNumber ip_prefix_;
+ const size_t prefix_length_in_bits_;
+};
+
+// Returns true if the given string represents an IP address.
+bool IsIPAddress(const std::string& domain) {
+ // From GURL::HostIsIPAddress()
+ url_canon::RawCanonOutputT<char, 128> ignored_output;
+ url_canon::CanonHostInfo host_info;
+ url_parse::Component domain_comp(0, domain.size());
+ url_canon::CanonicalizeIPAddress(domain.c_str(), domain_comp,
+ &ignored_output, &host_info);
+ return host_info.IsIPAddress();
+}
+
+} // namespace
+
+ProxyBypassRules::Rule::Rule() {
+}
+
+ProxyBypassRules::Rule::~Rule() {
+}
+
+bool ProxyBypassRules::Rule::Equals(const Rule& rule) const {
+ return ToString() == rule.ToString();
+}
+
+ProxyBypassRules::ProxyBypassRules() {
+}
+
+ProxyBypassRules::ProxyBypassRules(const ProxyBypassRules& rhs) {
+ AssignFrom(rhs);
+}
+
+ProxyBypassRules::~ProxyBypassRules() {
+ Clear();
+}
+
+ProxyBypassRules& ProxyBypassRules::operator=(const ProxyBypassRules& rhs) {
+ AssignFrom(rhs);
+ return *this;
+}
+
+bool ProxyBypassRules::Matches(const GURL& url) const {
+ for (RuleList::const_iterator it = rules_.begin(); it != rules_.end(); ++it) {
+ if ((*it)->Matches(url))
+ return true;
+ }
+ return false;
+}
+
+bool ProxyBypassRules::Equals(const ProxyBypassRules& other) const {
+ if (rules_.size() != other.rules_.size())
+ return false;
+
+ for (size_t i = 0; i < rules_.size(); ++i) {
+ if (!rules_[i]->Equals(*other.rules_[i]))
+ return false;
+ }
+ return true;
+}
+
+void ProxyBypassRules::ParseFromString(const std::string& raw) {
+ ParseFromStringInternal(raw, false);
+}
+
+void ProxyBypassRules::ParseFromStringUsingSuffixMatching(
+ const std::string& raw) {
+ ParseFromStringInternal(raw, true);
+}
+
+bool ProxyBypassRules::AddRuleForHostname(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port) {
+ if (hostname_pattern.empty())
+ return false;
+
+ rules_.push_back(new HostnamePatternRule(optional_scheme,
+ hostname_pattern,
+ optional_port));
+ return true;
+}
+
+void ProxyBypassRules::AddRuleToBypassLocal() {
+ rules_.push_back(new BypassLocalRule);
+}
+
+bool ProxyBypassRules::AddRuleFromString(const std::string& raw) {
+ return AddRuleFromStringInternalWithLogging(raw, false);
+}
+
+bool ProxyBypassRules::AddRuleFromStringUsingSuffixMatching(
+ const std::string& raw) {
+ return AddRuleFromStringInternalWithLogging(raw, true);
+}
+
+std::string ProxyBypassRules::ToString() const {
+ std::string result;
+ for (RuleList::const_iterator rule(rules_.begin());
+ rule != rules_.end();
+ ++rule) {
+ result += (*rule)->ToString();
+ result += ";";
+ }
+ return result;
+}
+
+void ProxyBypassRules::Clear() {
+ STLDeleteElements(&rules_);
+}
+
+void ProxyBypassRules::AssignFrom(const ProxyBypassRules& other) {
+ Clear();
+
+ // Make a copy of the rules list.
+ for (RuleList::const_iterator it = other.rules_.begin();
+ it != other.rules_.end(); ++it) {
+ rules_.push_back((*it)->Clone());
+ }
+}
+
+void ProxyBypassRules::ParseFromStringInternal(
+ const std::string& raw,
+ bool use_hostname_suffix_matching) {
+ Clear();
+
+ base::StringTokenizer entries(raw, ",;");
+ while (entries.GetNext()) {
+ AddRuleFromStringInternalWithLogging(entries.token(),
+ use_hostname_suffix_matching);
+ }
+}
+
+bool ProxyBypassRules::AddRuleFromStringInternal(
+ const std::string& raw_untrimmed,
+ bool use_hostname_suffix_matching) {
+ std::string raw;
+ TrimWhitespaceASCII(raw_untrimmed, TRIM_ALL, &raw);
+
+ // This is the special syntax used by WinInet's bypass list -- we allow it
+ // on all platforms and interpret it the same way.
+ if (LowerCaseEqualsASCII(raw, "<local>")) {
+ AddRuleToBypassLocal();
+ return true;
+ }
+
+ // Extract any scheme-restriction.
+ std::string::size_type scheme_pos = raw.find("://");
+ std::string scheme;
+ if (scheme_pos != std::string::npos) {
+ scheme = raw.substr(0, scheme_pos);
+ raw = raw.substr(scheme_pos + 3);
+ if (scheme.empty())
+ return false;
+ }
+
+ if (raw.empty())
+ return false;
+
+ // If there is a forward slash in the input, it is probably a CIDR style
+ // mask.
+ if (raw.find('/') != std::string::npos) {
+ IPAddressNumber ip_prefix;
+ size_t prefix_length_in_bits;
+
+ if (!ParseCIDRBlock(raw, &ip_prefix, &prefix_length_in_bits))
+ return false;
+
+ rules_.push_back(
+ new BypassIPBlockRule(raw, scheme, ip_prefix, prefix_length_in_bits));
+
+ return true;
+ }
+
+ // Check if we have an <ip-address>[:port] input. We need to treat this
+ // separately since the IP literal may not be in a canonical form.
+ std::string host;
+ int port;
+ if (ParseHostAndPort(raw, &host, &port)) {
+ if (IsIPAddress(host)) {
+ // Canonicalize the IP literal before adding it as a string pattern.
+ GURL tmp_url("http://" + host);
+ return AddRuleForHostname(scheme, tmp_url.host(), port);
+ }
+ }
+
+ // Otherwise assume we have <hostname-pattern>[:port].
+ std::string::size_type pos_colon = raw.rfind(':');
+ host = raw;
+ port = -1;
+ if (pos_colon != std::string::npos) {
+ if (!base::StringToInt(base::StringPiece(raw.begin() + pos_colon + 1,
+ raw.end()),
+ &port) ||
+ (port < 0 || port > 0xFFFF)) {
+ return false; // Port was invalid.
+ }
+ raw = raw.substr(0, pos_colon);
+ }
+
+ // Special-case hostnames that begin with a period.
+ // For example, we remap ".google.com" --> "*.google.com".
+ if (StartsWithASCII(raw, ".", false))
+ raw = "*" + raw;
+
+ // If suffix matching was asked for, make sure the pattern starts with a
+ // wildcard.
+ if (use_hostname_suffix_matching && !StartsWithASCII(raw, "*", false))
+ raw = "*" + raw;
+
+ return AddRuleForHostname(scheme, raw, port);
+}
+
+bool ProxyBypassRules::AddRuleFromStringInternalWithLogging(
+ const std::string& raw,
+ bool use_hostname_suffix_matching) {
+ return AddRuleFromStringInternal(raw, use_hostname_suffix_matching);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_bypass_rules.h b/chromium/net/proxy/proxy_bypass_rules.h
new file mode 100644
index 00000000000..6afeeee88c1
--- /dev/null
+++ b/chromium/net/proxy/proxy_bypass_rules.h
@@ -0,0 +1,182 @@
+// Copyright (c) 2011 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_PROXY_PROXY_BYPASS_RULES_H_
+#define NET_PROXY_PROXY_BYPASS_RULES_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// ProxyBypassRules describes the set of URLs that should bypass the proxy
+// settings, as a list of rules. A URL is said to match the bypass rules
+// if it matches any one of these rules.
+class NET_EXPORT ProxyBypassRules {
+ public:
+ // Interface for an individual proxy bypass rule.
+ class NET_EXPORT Rule {
+ public:
+ Rule();
+ virtual ~Rule();
+
+ // Returns true if |url| matches the rule.
+ virtual bool Matches(const GURL& url) const = 0;
+
+ // Returns a string representation of this rule. This is used both for
+ // visualizing the rules, and also to test equality of a rules list.
+ virtual std::string ToString() const = 0;
+
+ // Creates a copy of this rule. (Caller is responsible for deleting it)
+ virtual Rule* Clone() const = 0;
+
+ bool Equals(const Rule& rule) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Rule);
+ };
+
+ typedef std::vector<const Rule*> RuleList;
+
+ // Note: This class supports copy constructor and assignment.
+ ProxyBypassRules();
+ ProxyBypassRules(const ProxyBypassRules& rhs);
+ ~ProxyBypassRules();
+ ProxyBypassRules& operator=(const ProxyBypassRules& rhs);
+
+ // Returns the current list of rules. The rules list contains pointers
+ // which are owned by this class, callers should NOT keep references
+ // or delete them.
+ const RuleList& rules() const { return rules_; }
+
+ // Returns true if |url| matches any of the proxy bypass rules.
+ bool Matches(const GURL& url) const;
+
+ // Returns true if |*this| is equal to |other|; in other words, whether they
+ // describe the same set of rules.
+ bool Equals(const ProxyBypassRules& other) const;
+
+ // Initializes the list of rules by parsing the string |raw|. |raw| is a
+ // comma separated list of rules. See AddRuleFromString() to see the list
+ // of supported formats.
+ void ParseFromString(const std::string& raw);
+
+ // This is a variant of ParseFromString, which interprets hostname patterns
+ // as suffix tests rather than hostname tests (so "google.com" would actually
+ // match "*google.com"). This is only currently used for the linux no_proxy
+ // evironment variable. It is less flexible, since with the suffix matching
+ // format you can't match an individual host.
+ // NOTE: Use ParseFromString() unless you truly need this behavior.
+ void ParseFromStringUsingSuffixMatching(const std::string& raw);
+
+ // Adds a rule that matches a URL when all of the following are true:
+ // (a) The URL's scheme matches |optional_scheme|, if
+ // |!optional_scheme.empty()|
+ // (b) The URL's hostname matches |hostname_pattern|.
+ // (c) The URL's (effective) port number matches |optional_port| if
+ // |optional_port != -1|
+ // Returns true if the rule was successfully added.
+ bool AddRuleForHostname(const std::string& optional_scheme,
+ const std::string& hostname_pattern,
+ int optional_port);
+
+ // Adds a rule that bypasses all "local" hostnames.
+ // This matches IE's interpretation of the
+ // "Bypass proxy server for local addresses" settings checkbox. Fully
+ // qualified domain names or IP addresses are considered non-local,
+ // regardless of what they map to (except for the loopback addresses).
+ void AddRuleToBypassLocal();
+
+ // Adds a rule given by the string |raw|. The format of |raw| can be any of
+ // the following:
+ //
+ // (1) [ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]
+ //
+ // Match all hostnames that match the pattern HOSTNAME_PATTERN.
+ //
+ // Examples:
+ // "foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
+ // "https://x.*.y.com:99"
+ //
+ // (2) "." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]
+ //
+ // Match a particular domain suffix.
+ //
+ // Examples:
+ // ".google.com", ".com", "http://.google.com"
+ //
+ // (3) [ SCHEME "://" ] IP_LITERAL [ ":" PORT ]
+ //
+ // Match URLs which are IP address literals.
+ //
+ // Conceptually this is the similar to (1), but with special cases
+ // to handle IP literal canonicalization. For example matching
+ // on "[0:0:0::1]" would be the same as matching on "[::1]" since
+ // the IPv6 canonicalization is done internally.
+ //
+ // Examples:
+ // "127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
+ //
+ // (4) IP_LITERAL "/" PREFIX_LENGHT_IN_BITS
+ //
+ // Match any URL that is to an IP literal that falls between the
+ // given range. IP range is specified using CIDR notation.
+ //
+ // Examples:
+ // "192.168.1.1/16", "fefe:13::abc/33".
+ //
+ // (5) "<local>"
+ //
+ // Match local addresses. The meaning of "<local>" is whether the
+ // host matches one of: "127.0.0.1", "::1", "localhost".
+ //
+ // See the unit-tests for more examples.
+ //
+ // Returns true if the rule was successfully added.
+ //
+ // TODO(eroman): support IPv6 literals without brackets.
+ //
+ bool AddRuleFromString(const std::string& raw);
+
+ // This is a variant of AddFromString, which interprets hostname patterns as
+ // suffix tests rather than hostname tests (so "google.com" would actually
+ // match "*google.com"). This is used for KDE which interprets every rule as
+ // a suffix test. It is less flexible, since with the suffix matching format
+ // you can't match an individual host.
+ //
+ // Returns true if the rule was successfully added.
+ //
+ // NOTE: Use AddRuleFromString() unless you truly need this behavior.
+ bool AddRuleFromStringUsingSuffixMatching(const std::string& raw);
+
+ // Converts the rules to string representation. Inverse operation to
+ // ParseFromString().
+ std::string ToString() const;
+
+ // Removes all the rules.
+ void Clear();
+
+ // Sets |*this| to |other|.
+ void AssignFrom(const ProxyBypassRules& other);
+
+ private:
+ // The following are variants of ParseFromString() and AddRuleFromString(),
+ // which additionally prefix hostname patterns with a wildcard if
+ // |use_hostname_suffix_matching| was true.
+ void ParseFromStringInternal(const std::string& raw,
+ bool use_hostname_suffix_matching);
+ bool AddRuleFromStringInternal(const std::string& raw,
+ bool use_hostname_suffix_matching);
+ bool AddRuleFromStringInternalWithLogging(const std::string& raw,
+ bool use_hostname_suffix_matching);
+
+ RuleList rules_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_BYPASS_RULES_H_
diff --git a/chromium/net/proxy/proxy_bypass_rules_unittest.cc b/chromium/net/proxy/proxy_bypass_rules_unittest.cc
new file mode 100644
index 00000000000..40758fd4ad7
--- /dev/null
+++ b/chromium/net/proxy/proxy_bypass_rules_unittest.cc
@@ -0,0 +1,315 @@
+// Copyright (c) 2010 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/proxy/proxy_bypass_rules.h"
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicHost) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("wWw.gOogle.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("www.google.com", rules.rules()[0]->ToString());
+
+ // All of these match; port, scheme, and non-hostname components don't
+ // matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://www.google.com:81")));
+
+ // Must be a strict host match to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://xxx.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.baz.org")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomain) {
+ ProxyBypassRules rules;
+ rules.ParseFromString(".gOOgle.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ // Note that we inferred this was an "ends with" test.
+ EXPECT_EQ("*.google.com", rules.rules()[0]->ToString());
+
+ // All of these match; port, scheme, and non-hostname components don't
+ // matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:81")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo.google.com/x/y?q")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo:bar@baz.google.com#x")));
+
+ // Must be a strict "ends with" to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchBasicDomainWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*.GOOGLE.com:80");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*.google.com:80", rules.rules()[0]->ToString());
+
+ // All of these match; scheme, and non-hostname components don't matter.
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.google.com:80")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x")));
+
+ // Must be a strict "ends with" to work.
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.google.com.baz.org")));
+
+ // The ports must match.
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com:90")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, MatchAll) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://www.foobar.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("https://a.google.com:80?x")));
+}
+
+TEST(ProxyBypassRulesTest, WildcardAtStart) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("*.org:443");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("*.org:443", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.org:443")));
+ EXPECT_TRUE(rules.Matches(GURL("https://www.google.org")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.org.com")));
+}
+
+TEST(ProxyBypassRulesTest, IPV4Address) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_TRUE(rules.Matches(GURL("https://192.168.1.1:90")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1")));
+}
+
+TEST(ProxyBypassRulesTest, IPV4AddressWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1:33");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1:33", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33")));
+}
+
+TEST(ProxyBypassRulesTest, IPV6Address) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("[3ffe:2a00:100:7031:0:0::1]");
+ ASSERT_EQ(1u, rules.rules().size());
+ // Note that we canonicalized the IP address.
+ EXPECT_EQ("[3ffe:2a00:100:7031::1]", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://sup.192.168.1.1:33")));
+}
+
+TEST(ProxyBypassRulesTest, IPV6AddressWithPort) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("[3ffe:2a00:100:7031::1]:33");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("[3ffe:2a00:100:7031::1]:33", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]:33")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, HTTPOnly) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("http://www.google.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("http://www.google.com", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo")));
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99")));
+
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foo.www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, HTTPOnlyWithWildcard) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("http://*www.google.com");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("http://*www.google.com", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com/foo")));
+ EXPECT_TRUE(rules.Matches(GURL("http://www.google.com:99")));
+ EXPECT_TRUE(rules.Matches(GURL("http://foo.www.google.com")));
+
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("ftp://www.google.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://www.google.com.org")));
+ EXPECT_FALSE(rules.Matches(GURL("https://www.google.com")));
+}
+
+TEST(ProxyBypassRulesTest, UseSuffixMatching) {
+ ProxyBypassRules rules;
+ rules.ParseFromStringUsingSuffixMatching(
+ "foo1.com, .foo2.com, 192.168.1.1, "
+ "*foobar.com:80, *.foo, http://baz, <local>");
+ ASSERT_EQ(7u, rules.rules().size());
+ EXPECT_EQ("*foo1.com", rules.rules()[0]->ToString());
+ EXPECT_EQ("*.foo2.com", rules.rules()[1]->ToString());
+ EXPECT_EQ("192.168.1.1", rules.rules()[2]->ToString());
+ EXPECT_EQ("*foobar.com:80", rules.rules()[3]->ToString());
+ EXPECT_EQ("*.foo", rules.rules()[4]->ToString());
+ EXPECT_EQ("http://*baz", rules.rules()[5]->ToString());
+ EXPECT_EQ("<local>", rules.rules()[6]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://foo1.com")));
+ EXPECT_TRUE(rules.Matches(GURL("http://aaafoo1.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://aaafoo1.com.net")));
+}
+
+TEST(ProxyBypassRulesTest, MultipleRules) {
+ ProxyBypassRules rules;
+ rules.ParseFromString(".google.com , .foobar.com:30");
+ ASSERT_EQ(2u, rules.rules().size());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://baz.google.com:40")));
+ EXPECT_FALSE(rules.Matches(GURL("http://google.com:40")));
+ EXPECT_TRUE(rules.Matches(GURL("http://bar.foobar.com:30")));
+ EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://bar.foobar.com:33")));
+}
+
+TEST(ProxyBypassRulesTest, BadInputs) {
+ ProxyBypassRules rules;
+ EXPECT_FALSE(rules.AddRuleFromString("://"));
+ EXPECT_FALSE(rules.AddRuleFromString(" "));
+ EXPECT_FALSE(rules.AddRuleFromString("http://"));
+ EXPECT_FALSE(rules.AddRuleFromString("*.foo.com:-34"));
+ EXPECT_EQ(0u, rules.rules().size());
+}
+
+TEST(ProxyBypassRulesTest, Equals) {
+ ProxyBypassRules rules1;
+ ProxyBypassRules rules2;
+
+ rules1.ParseFromString("foo1.com, .foo2.com");
+ rules2.ParseFromString("foo1.com,.FOo2.com");
+
+ EXPECT_TRUE(rules1.Equals(rules2));
+ EXPECT_TRUE(rules2.Equals(rules1));
+
+ rules1.ParseFromString(".foo2.com");
+ rules2.ParseFromString("foo1.com,.FOo2.com");
+
+ EXPECT_FALSE(rules1.Equals(rules2));
+ EXPECT_FALSE(rules2.Equals(rules1));
+}
+
+TEST(ProxyBypassRulesTest, BypassLocalNames) {
+ const struct {
+ const char* url;
+ bool expected_is_local;
+ } tests[] = {
+ // Single-component hostnames are considered local.
+ {"http://localhost/x", true},
+ {"http://www", true},
+
+ // IPv4 loopback interface.
+ {"http://127.0.0.1/x", true},
+ {"http://127.0.0.1:80/x", true},
+
+ // IPv6 loopback interface.
+ {"http://[::1]:80/x", true},
+ {"http://[0:0::1]:6233/x", true},
+ {"http://[0:0:0:0:0:0:0:1]/x", true},
+
+ // Non-local URLs.
+ {"http://foo.com/", false},
+ {"http://localhost.i/", false},
+ {"http://www.google.com/", false},
+ {"http://192.168.0.1/", false},
+
+ // Try with different protocols.
+ {"ftp://127.0.0.1/x", true},
+ {"ftp://foobar.com/x", false},
+
+ // This is a bit of a gray-area, but GURL does not strip trailing dots
+ // in host-names, so the following are considered non-local.
+ {"http://www./x", false},
+ {"http://localhost./x", false},
+ };
+
+ ProxyBypassRules rules;
+ rules.ParseFromString("<local>");
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf(
+ "Test[%d]: %s", static_cast<int>(i), tests[i].url));
+ EXPECT_EQ(tests[i].expected_is_local, rules.Matches(GURL(tests[i].url)));
+ }
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv4) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("192.168.1.1/16");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("192.168.1.1/16", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://192.168.1.1")));
+ EXPECT_TRUE(rules.Matches(GURL("ftp://192.168.4.4")));
+ EXPECT_TRUE(rules.Matches(GURL("https://192.168.0.0:81")));
+ EXPECT_TRUE(rules.Matches(GURL("http://[::ffff:192.168.11.11]")));
+
+ EXPECT_FALSE(rules.Matches(GURL("http://foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://xxx.192.168.1.1")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.168.1.1.xx")));
+}
+
+TEST(ProxyBypassRulesTest, ParseAndMatchCIDR_IPv6) {
+ ProxyBypassRules rules;
+ rules.ParseFromString("a:b:c:d::/48");
+ ASSERT_EQ(1u, rules.rules().size());
+ EXPECT_EQ("a:b:c:d::/48", rules.rules()[0]->ToString());
+
+ EXPECT_TRUE(rules.Matches(GURL("http://[A:b:C:9::]")));
+ EXPECT_FALSE(rules.Matches(GURL("http://foobar.com")));
+ EXPECT_FALSE(rules.Matches(GURL("http://192.169.1.1")));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config.cc b/chromium/net/proxy/proxy_config.cc
new file mode 100644
index 00000000000..c1fe0f5ceb4
--- /dev/null
+++ b/chromium/net/proxy/proxy_config.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/values.h"
+#include "net/proxy/proxy_info.h"
+
+namespace net {
+
+namespace {
+
+// If |proxies| is non-empty, sets it in |dict| under the key |name|.
+void AddProxyListToValue(const char* name,
+ const ProxyList& proxies,
+ base::DictionaryValue* dict) {
+ if (!proxies.IsEmpty())
+ dict->Set(name, proxies.ToValue());
+}
+
+// Split the |uri_list| on commas and add each entry to |proxy_list| in turn.
+void AddProxyURIListToProxyList(std::string uri_list,
+ ProxyList* proxy_list,
+ ProxyServer::Scheme default_scheme) {
+ base::StringTokenizer proxy_uri_list(uri_list, ",");
+ while (proxy_uri_list.GetNext()) {
+ proxy_list->AddProxyServer(
+ ProxyServer::FromURI(proxy_uri_list.token(), default_scheme));
+ }
+}
+
+} // namespace
+
+ProxyConfig::ProxyRules::ProxyRules()
+ : reverse_bypass(false),
+ type(TYPE_NO_RULES) {
+}
+
+ProxyConfig::ProxyRules::~ProxyRules() {
+}
+
+void ProxyConfig::ProxyRules::Apply(const GURL& url, ProxyInfo* result) const {
+ if (empty()) {
+ result->UseDirect();
+ return;
+ }
+
+ bool bypass_proxy = bypass_rules.Matches(url);
+ if (reverse_bypass)
+ bypass_proxy = !bypass_proxy;
+ if (bypass_proxy) {
+ result->UseDirectWithBypassedProxy();
+ return;
+ }
+
+ switch (type) {
+ case ProxyRules::TYPE_SINGLE_PROXY: {
+ result->UseProxyList(single_proxies);
+ return;
+ }
+ case ProxyRules::TYPE_PROXY_PER_SCHEME: {
+ const ProxyList* entry = MapUrlSchemeToProxyList(url.scheme());
+ if (entry) {
+ result->UseProxyList(*entry);
+ } else {
+ // We failed to find a matching proxy server for the current URL
+ // scheme. Default to direct.
+ result->UseDirect();
+ }
+ return;
+ }
+ default: {
+ result->UseDirect();
+ NOTREACHED();
+ return;
+ }
+ }
+}
+
+void ProxyConfig::ProxyRules::ParseFromString(const std::string& proxy_rules) {
+ // Reset.
+ type = TYPE_NO_RULES;
+ single_proxies = ProxyList();
+ proxies_for_http = ProxyList();
+ proxies_for_https = ProxyList();
+ proxies_for_ftp = ProxyList();
+ fallback_proxies = ProxyList();
+
+ base::StringTokenizer proxy_server_list(proxy_rules, ";");
+ while (proxy_server_list.GetNext()) {
+ base::StringTokenizer proxy_server_for_scheme(
+ proxy_server_list.token_begin(), proxy_server_list.token_end(), "=");
+
+ while (proxy_server_for_scheme.GetNext()) {
+ std::string url_scheme = proxy_server_for_scheme.token();
+
+ // If we fail to get the proxy server here, it means that
+ // this is a regular proxy server configuration, i.e. proxies
+ // are not configured per protocol.
+ if (!proxy_server_for_scheme.GetNext()) {
+ if (type == TYPE_PROXY_PER_SCHEME)
+ continue; // Unexpected.
+ AddProxyURIListToProxyList(url_scheme,
+ &single_proxies,
+ ProxyServer::SCHEME_HTTP);
+ type = TYPE_SINGLE_PROXY;
+ return;
+ }
+
+ // Trim whitespace off the url scheme.
+ TrimWhitespaceASCII(url_scheme, TRIM_ALL, &url_scheme);
+
+ // Add it to the per-scheme mappings (if supported scheme).
+ type = TYPE_PROXY_PER_SCHEME;
+ ProxyList* entry = MapUrlSchemeToProxyListNoFallback(url_scheme);
+ ProxyServer::Scheme default_scheme = ProxyServer::SCHEME_HTTP;
+
+ // socks=XXX is inconsistent with the other formats, since "socks"
+ // is not a URL scheme. Rather this means "for everything else, send
+ // it to the SOCKS proxy server XXX".
+ if (url_scheme == "socks") {
+ DCHECK(!entry);
+ entry = &fallback_proxies;
+ // Note that here 'socks' is understood to be SOCKS4, even though
+ // 'socks' maps to SOCKS5 in ProxyServer::GetSchemeFromURIInternal.
+ default_scheme = ProxyServer::SCHEME_SOCKS4;
+ }
+
+ if (entry) {
+ AddProxyURIListToProxyList(proxy_server_for_scheme.token(),
+ entry,
+ default_scheme);
+ }
+ }
+ }
+}
+
+const ProxyList* ProxyConfig::ProxyRules::MapUrlSchemeToProxyList(
+ const std::string& url_scheme) const {
+ const ProxyList* proxy_server_list = const_cast<ProxyRules*>(this)->
+ MapUrlSchemeToProxyListNoFallback(url_scheme);
+ if (proxy_server_list && !proxy_server_list->IsEmpty())
+ return proxy_server_list;
+ if (!fallback_proxies.IsEmpty())
+ return &fallback_proxies;
+ return NULL; // No mapping for this scheme. Use direct.
+}
+
+bool ProxyConfig::ProxyRules::Equals(const ProxyRules& other) const {
+ return type == other.type &&
+ single_proxies.Equals(other.single_proxies) &&
+ proxies_for_http.Equals(other.proxies_for_http) &&
+ proxies_for_https.Equals(other.proxies_for_https) &&
+ proxies_for_ftp.Equals(other.proxies_for_ftp) &&
+ fallback_proxies.Equals(other.fallback_proxies) &&
+ bypass_rules.Equals(other.bypass_rules) &&
+ reverse_bypass == other.reverse_bypass;
+}
+
+ProxyList* ProxyConfig::ProxyRules::MapUrlSchemeToProxyListNoFallback(
+ const std::string& scheme) {
+ DCHECK_EQ(TYPE_PROXY_PER_SCHEME, type);
+ if (scheme == "http")
+ return &proxies_for_http;
+ if (scheme == "https")
+ return &proxies_for_https;
+ if (scheme == "ftp")
+ return &proxies_for_ftp;
+ return NULL; // No mapping for this scheme.
+}
+
+ProxyConfig::ProxyConfig()
+ : auto_detect_(false), pac_mandatory_(false),
+ source_(PROXY_CONFIG_SOURCE_UNKNOWN), id_(kInvalidConfigID) {
+}
+
+ProxyConfig::ProxyConfig(const ProxyConfig& config)
+ : auto_detect_(config.auto_detect_),
+ pac_url_(config.pac_url_),
+ pac_mandatory_(config.pac_mandatory_),
+ proxy_rules_(config.proxy_rules_),
+ source_(config.source_),
+ id_(config.id_) {
+}
+
+ProxyConfig::~ProxyConfig() {
+}
+
+ProxyConfig& ProxyConfig::operator=(const ProxyConfig& config) {
+ auto_detect_ = config.auto_detect_;
+ pac_url_ = config.pac_url_;
+ pac_mandatory_ = config.pac_mandatory_;
+ proxy_rules_ = config.proxy_rules_;
+ source_ = config.source_;
+ id_ = config.id_;
+ return *this;
+}
+
+bool ProxyConfig::Equals(const ProxyConfig& other) const {
+ // The two configs can have different IDs and sources. We are just interested
+ // in if they have the same settings.
+ return auto_detect_ == other.auto_detect_ &&
+ pac_url_ == other.pac_url_ &&
+ pac_mandatory_ == other.pac_mandatory_ &&
+ proxy_rules_.Equals(other.proxy_rules());
+}
+
+bool ProxyConfig::HasAutomaticSettings() const {
+ return auto_detect_ || has_pac_url();
+}
+
+void ProxyConfig::ClearAutomaticSettings() {
+ auto_detect_ = false;
+ pac_url_ = GURL();
+}
+
+base::DictionaryValue* ProxyConfig::ToValue() const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ // Output the automatic settings.
+ if (auto_detect_)
+ dict->SetBoolean("auto_detect", auto_detect_);
+ if (has_pac_url()) {
+ dict->SetString("pac_url", pac_url_.possibly_invalid_spec());
+ if (pac_mandatory_)
+ dict->SetBoolean("pac_mandatory", pac_mandatory_);
+ }
+
+ // Output the manual settings.
+ if (proxy_rules_.type != ProxyRules::TYPE_NO_RULES) {
+ switch (proxy_rules_.type) {
+ case ProxyRules::TYPE_SINGLE_PROXY:
+ AddProxyListToValue("single_proxy",
+ proxy_rules_.single_proxies, dict);
+ break;
+ case ProxyRules::TYPE_PROXY_PER_SCHEME: {
+ base::DictionaryValue* dict2 = new base::DictionaryValue();
+ AddProxyListToValue("http", proxy_rules_.proxies_for_http, dict2);
+ AddProxyListToValue("https", proxy_rules_.proxies_for_https, dict2);
+ AddProxyListToValue("ftp", proxy_rules_.proxies_for_ftp, dict2);
+ AddProxyListToValue("fallback", proxy_rules_.fallback_proxies, dict2);
+ dict->Set("proxy_per_scheme", dict2);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ // Output the bypass rules.
+ const ProxyBypassRules& bypass = proxy_rules_.bypass_rules;
+ if (!bypass.rules().empty()) {
+ if (proxy_rules_.reverse_bypass)
+ dict->SetBoolean("reverse_bypass", true);
+
+ base::ListValue* list = new base::ListValue();
+
+ for (ProxyBypassRules::RuleList::const_iterator it =
+ bypass.rules().begin();
+ it != bypass.rules().end(); ++it) {
+ list->Append(new base::StringValue((*it)->ToString()));
+ }
+
+ dict->Set("bypass_list", list);
+ }
+ }
+
+ // Output the source.
+ dict->SetString("source", ProxyConfigSourceToString(source_));
+
+ return dict;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config.h b/chromium/net/proxy/proxy_config.h
new file mode 100644
index 00000000000..e558174a465
--- /dev/null
+++ b/chromium/net/proxy/proxy_config.h
@@ -0,0 +1,263 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_H_
+#define NET_PROXY_PROXY_CONFIG_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_bypass_rules.h"
+#include "net/proxy/proxy_config_source.h"
+#include "net/proxy/proxy_list.h"
+#include "net/proxy/proxy_server.h"
+#include "url/gurl.h"
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+class ProxyInfo;
+
+// ProxyConfig describes a user's proxy settings.
+//
+// There are two categories of proxy settings:
+// (1) Automatic (indicates the methods to obtain a PAC script)
+// (2) Manual (simple set of proxy servers per scheme, and bypass patterns)
+//
+// When both automatic and manual settings are specified, the Automatic ones
+// take precedence over the manual ones.
+//
+// For more details see:
+// http://www.chromium.org/developers/design-documents/proxy-settings-fallback
+class NET_EXPORT ProxyConfig {
+ public:
+ // ProxyRules describes the "manual" proxy settings.
+ // TODO(eroman): Turn this into a class.
+ // TODO(marq): Update the enum names; "TYPE_SINGLE_PROXY" really means
+ // the same set of proxies are used for all requests.
+ struct NET_EXPORT ProxyRules {
+ enum Type {
+ TYPE_NO_RULES,
+ TYPE_SINGLE_PROXY,
+ TYPE_PROXY_PER_SCHEME,
+ };
+
+ // Note that the default of TYPE_NO_RULES results in direct connections
+ // being made when using this ProxyConfig.
+ ProxyRules();
+ ~ProxyRules();
+
+ bool empty() const {
+ return type == TYPE_NO_RULES;
+ }
+
+ // Sets |result| with the proxies to use for |url| based on the current
+ // rules.
+ void Apply(const GURL& url, ProxyInfo* result) const;
+
+ // Parses the rules from a string, indicating which proxies to use.
+ //
+ // proxy-uri = [<proxy-scheme>"://"]<proxy-host>[":"<proxy-port>]
+ //
+ // proxy-uri-list = <proxy-uri>[","<proxy-uri-list>]
+ //
+ // url-scheme = "http" | "https" | "ftp" | "socks"
+ //
+ // scheme-proxies = [<url-scheme>"="]<proxy-uri-list>
+ //
+ // proxy-rules = scheme-proxies[";"<scheme-proxies>]
+ //
+ // Thus, the proxy-rules string should be a semicolon-separated list of
+ // ordered proxies that apply to a particular URL scheme. Unless specified,
+ // the proxy scheme for proxy-uris is assumed to be http.
+ //
+ // Some special cases:
+ // * If the scheme is omitted from the first proxy list, that list applies
+ // to all URL schemes and subsequent lists are ignored.
+ // * If a scheme is omitted from any proxy list after a list where a scheme
+ // has been provided, the list without a scheme is ignored.
+ // * If the url-scheme is set to 'socks', that sets a fallback list that
+ // to all otherwise unspecified url-schemes, however the default proxy-
+ // scheme for proxy urls in the 'socks' list is understood to be
+ // socks4:// if unspecified.
+ //
+ // For example:
+ // "http=foopy:80;ftp=foopy2" -- use HTTP proxy "foopy:80" for http://
+ // URLs, and HTTP proxy "foopy2:80" for
+ // ftp:// URLs.
+ // "foopy:80" -- use HTTP proxy "foopy:80" for all URLs.
+ // "foopy:80,bar,direct://" -- use HTTP proxy "foopy:80" for all URLs,
+ // failing over to "bar" if "foopy:80" is
+ // unavailable, and after that using no
+ // proxy.
+ // "socks4://foopy" -- use SOCKS v4 proxy "foopy:1080" for all
+ // URLs.
+ // "http=foop,socks5://bar.com -- use HTTP proxy "foopy" for http URLs,
+ // and fail over to the SOCKS5 proxy
+ // "bar.com" if "foop" is unavailable.
+ // "http=foopy,direct:// -- use HTTP proxy "foopy" for http URLs,
+ // and use no proxy if "foopy" is
+ // unavailable.
+ // "http=foopy;socks=foopy2 -- use HTTP proxy "foopy" for http URLs,
+ // and use socks4://foopy2 for all other
+ // URLs.
+ void ParseFromString(const std::string& proxy_rules);
+
+ // Returns one of {&proxies_for_http, &proxies_for_https, &proxies_for_ftp,
+ // &fallback_proxies}, or NULL if there is no proxy to use.
+ // Should only call this if the type is TYPE_PROXY_PER_SCHEME.
+ const ProxyList* MapUrlSchemeToProxyList(
+ const std::string& url_scheme) const;
+
+ // Returns true if |*this| describes the same configuration as |other|.
+ bool Equals(const ProxyRules& other) const;
+
+ // Exceptions for when not to use a proxy.
+ ProxyBypassRules bypass_rules;
+
+ // Reverse the meaning of |bypass_rules|.
+ bool reverse_bypass;
+
+ Type type;
+
+ // Set if |type| is TYPE_SINGLE_PROXY.
+ ProxyList single_proxies;
+
+ // Set if |type| is TYPE_PROXY_PER_SCHEME.
+ ProxyList proxies_for_http;
+ ProxyList proxies_for_https;
+ ProxyList proxies_for_ftp;
+
+ // Used when a fallback has been defined and the url to be proxied doesn't
+ // match any of the standard schemes.
+ ProxyList fallback_proxies;
+
+ private:
+ // Returns one of {&proxies_for_http, &proxies_for_https, &proxies_for_ftp}
+ // or NULL if it is a scheme that we don't have a mapping
+ // for. Should only call this if the type is TYPE_PROXY_PER_SCHEME.
+ ProxyList* MapUrlSchemeToProxyListNoFallback(const std::string& scheme);
+ };
+
+ typedef int ID;
+
+ // Indicates an invalid proxy config.
+ static const ID kInvalidConfigID = 0;
+
+ ProxyConfig();
+ ProxyConfig(const ProxyConfig& config);
+ ~ProxyConfig();
+ ProxyConfig& operator=(const ProxyConfig& config);
+
+ // Used to numerically identify this configuration.
+ ID id() const { return id_; }
+ void set_id(ID id) { id_ = id; }
+ bool is_valid() const { return id_ != kInvalidConfigID; }
+
+ // Returns true if the given config is equivalent to this config. The
+ // comparison ignores differences in |id()| and |source()|.
+ bool Equals(const ProxyConfig& other) const;
+
+ // Returns true if this config contains any "automatic" settings. See the
+ // class description for what that means.
+ bool HasAutomaticSettings() const;
+
+ void ClearAutomaticSettings();
+
+ // Creates a Value dump of this configuration. The caller is responsible for
+ // deleting the returned value.
+ base::DictionaryValue* ToValue() const;
+
+ ProxyRules& proxy_rules() {
+ return proxy_rules_;
+ }
+
+ const ProxyRules& proxy_rules() const {
+ return proxy_rules_;
+ }
+
+ void set_pac_url(const GURL& url) {
+ pac_url_ = url;
+ }
+
+ const GURL& pac_url() const {
+ return pac_url_;
+ }
+
+ void set_pac_mandatory(bool enable_pac_mandatory) {
+ pac_mandatory_ = enable_pac_mandatory;
+ }
+
+ bool pac_mandatory() const {
+ return pac_mandatory_;
+ }
+
+ bool has_pac_url() const {
+ return pac_url_.is_valid();
+ }
+
+ void set_auto_detect(bool enable_auto_detect) {
+ auto_detect_ = enable_auto_detect;
+ }
+
+ bool auto_detect() const {
+ return auto_detect_;
+ }
+
+ void set_source(ProxyConfigSource source) {
+ source_ = source;
+ }
+
+ ProxyConfigSource source() const {
+ return source_;
+ }
+
+ // Helpers to construct some common proxy configurations.
+
+ static ProxyConfig CreateDirect() {
+ return ProxyConfig();
+ }
+
+ static ProxyConfig CreateAutoDetect() {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ return config;
+ }
+
+ static ProxyConfig CreateFromCustomPacURL(const GURL& pac_url) {
+ ProxyConfig config;
+ config.set_pac_url(pac_url);
+ // By default fall back to direct connection in case PAC script fails.
+ config.set_pac_mandatory(false);
+ return config;
+ }
+
+ private:
+ // True if the proxy configuration should be auto-detected.
+ bool auto_detect_;
+
+ // If non-empty, indicates the URL of the proxy auto-config file to use.
+ GURL pac_url_;
+
+ // If true, blocks all traffic in case fetching the pac script from |pac_url_|
+ // fails. Only valid if |pac_url_| is non-empty.
+ bool pac_mandatory_;
+
+ // Manual proxy settings.
+ ProxyRules proxy_rules_;
+
+ // Source of proxy settings.
+ ProxyConfigSource source_;
+
+ ID id_;
+};
+
+} // namespace net
+
+
+
+#endif // NET_PROXY_PROXY_CONFIG_H_
diff --git a/chromium/net/proxy/proxy_config_service.h b/chromium/net/proxy/proxy_config_service.h
new file mode 100644
index 00000000000..5e14995d093
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 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_PROXY_PROXY_CONFIG_SERVICE_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class ProxyConfig;
+
+// Service for watching when the proxy settings have changed.
+class NET_EXPORT ProxyConfigService {
+ public:
+ // Indicates whether proxy configuration is valid, and if not, why.
+ enum ConfigAvailability {
+ // Configuration is pending, observers will be notified later.
+ CONFIG_PENDING,
+ // Configuration is present and valid.
+ CONFIG_VALID,
+ // No configuration is set.
+ CONFIG_UNSET
+ };
+
+ // Observer for being notified when the proxy settings have changed.
+ class NET_EXPORT Observer {
+ public:
+ virtual ~Observer() {}
+ // Notification callback that should be invoked by ProxyConfigService
+ // implementors whenever the configuration changes. |availability| indicates
+ // the new availability status and can be CONFIG_UNSET or CONFIG_VALID (in
+ // which case |config| contains the configuration). Implementors must not
+ // pass CONFIG_PENDING.
+ virtual void OnProxyConfigChanged(const ProxyConfig& config,
+ ConfigAvailability availability) = 0;
+ };
+
+ virtual ~ProxyConfigService() {}
+
+ // Adds/Removes an observer that will be called whenever the proxy
+ // configuration has changed.
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // Gets the most recent availability status. If a configuration is present,
+ // the proxy configuration is written to |config| and CONFIG_VALID is
+ // returned. Returns CONFIG_PENDING if it is not available yet. In this case,
+ // it is guaranteed that subscribed observers will be notified of a change at
+ // some point in the future once the configuration is available.
+ // Note that to avoid re-entrancy problems, implementations should not
+ // dispatch any change notifications from within this function.
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) = 0;
+
+ // ProxyService will call this periodically during periods of activity.
+ // It can be used as a signal for polling-based implementations.
+ //
+ // Note that this is purely used as an optimization -- polling
+ // implementations could simply set a global timer that goes off every
+ // X seconds at which point they check for changes. However that has
+ // the disadvantage of doing continuous work even during idle periods.
+ virtual void OnLazyPoll() {}
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_H_
diff --git a/chromium/net/proxy/proxy_config_service_android.cc b/chromium/net/proxy/proxy_config_service_android.cc
new file mode 100644
index 00000000000..41bef8a4cd8
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_android.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_android.h"
+
+#include <sys/system_properties.h>
+
+#include "base/android/jni_string.h"
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "jni/ProxyChangeListener_jni.h"
+#include "net/base/host_port_pair.h"
+#include "net/proxy/proxy_config.h"
+#include "url/url_parse.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::CheckException;
+using base::android::ClearException;
+using base::android::ScopedJavaGlobalRef;
+
+namespace net {
+
+namespace {
+
+typedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback;
+
+// Returns whether the provided string was successfully converted to a port.
+bool ConvertStringToPort(const std::string& port, int* output) {
+ url_parse::Component component(0, port.size());
+ int result = url_parse::ParsePort(port.c_str(), component);
+ if (result == url_parse::PORT_INVALID ||
+ result == url_parse::PORT_UNSPECIFIED)
+ return false;
+ *output = result;
+ return true;
+}
+
+ProxyServer ConstructProxyServer(ProxyServer::Scheme scheme,
+ const std::string& proxy_host,
+ const std::string& proxy_port) {
+ DCHECK(!proxy_host.empty());
+ int port_as_int = 0;
+ if (proxy_port.empty())
+ port_as_int = ProxyServer::GetDefaultPortForScheme(scheme);
+ else if (!ConvertStringToPort(proxy_port, &port_as_int))
+ return ProxyServer();
+ DCHECK(port_as_int > 0);
+ return ProxyServer(
+ scheme,
+ HostPortPair(proxy_host, static_cast<uint16>(port_as_int)));
+}
+
+ProxyServer LookupProxy(const std::string& prefix,
+ const GetPropertyCallback& get_property,
+ ProxyServer::Scheme scheme) {
+ DCHECK(!prefix.empty());
+ std::string proxy_host = get_property.Run(prefix + ".proxyHost");
+ if (!proxy_host.empty()) {
+ std::string proxy_port = get_property.Run(prefix + ".proxyPort");
+ return ConstructProxyServer(scheme, proxy_host, proxy_port);
+ }
+ // Fall back to default proxy, if any.
+ proxy_host = get_property.Run("proxyHost");
+ if (!proxy_host.empty()) {
+ std::string proxy_port = get_property.Run("proxyPort");
+ return ConstructProxyServer(scheme, proxy_host, proxy_port);
+ }
+ return ProxyServer();
+}
+
+ProxyServer LookupSocksProxy(const GetPropertyCallback& get_property) {
+ std::string proxy_host = get_property.Run("socksProxyHost");
+ if (!proxy_host.empty()) {
+ std::string proxy_port = get_property.Run("socksProxyPort");
+ return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host,
+ proxy_port);
+ }
+ return ProxyServer();
+}
+
+void AddBypassRules(const std::string& scheme,
+ const GetPropertyCallback& get_property,
+ ProxyBypassRules* bypass_rules) {
+ // The format of a hostname pattern is a list of hostnames that are separated
+ // by | and that use * as a wildcard. For example, setting the
+ // http.nonProxyHosts property to *.android.com|*.kernel.org will cause
+ // requests to http://developer.android.com to be made without a proxy.
+ std::string non_proxy_hosts =
+ get_property.Run(scheme + ".nonProxyHosts");
+ if (non_proxy_hosts.empty())
+ return;
+ base::StringTokenizer tokenizer(non_proxy_hosts, "|");
+ while (tokenizer.GetNext()) {
+ std::string token = tokenizer.token();
+ std::string pattern;
+ TrimWhitespaceASCII(token, TRIM_ALL, &pattern);
+ if (pattern.empty())
+ continue;
+ // '?' is not one of the specified pattern characters above.
+ DCHECK_EQ(std::string::npos, pattern.find('?'));
+ bypass_rules->AddRuleForHostname(scheme, pattern, -1);
+ }
+}
+
+// Returns true if a valid proxy was found.
+bool GetProxyRules(const GetPropertyCallback& get_property,
+ ProxyConfig::ProxyRules* rules) {
+ // See libcore/luni/src/main/java/java/net/ProxySelectorImpl.java for the
+ // mostly equivalent Android implementation. There is one intentional
+ // difference: by default Chromium uses the HTTP port (80) for HTTPS
+ // connections via proxy. This default is identical on other platforms.
+ // On the opposite, Java spec suggests to use HTTPS port (443) by default (the
+ // default value of https.proxyPort).
+ rules->type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ rules->proxies_for_http.SetSingleProxyServer(
+ LookupProxy("http", get_property, ProxyServer::SCHEME_HTTP));
+ rules->proxies_for_https.SetSingleProxyServer(
+ LookupProxy("https", get_property, ProxyServer::SCHEME_HTTP));
+ rules->proxies_for_ftp.SetSingleProxyServer(
+ LookupProxy("ftp", get_property, ProxyServer::SCHEME_HTTP));
+ rules->fallback_proxies.SetSingleProxyServer(LookupSocksProxy(get_property));
+ rules->bypass_rules.Clear();
+ AddBypassRules("ftp", get_property, &rules->bypass_rules);
+ AddBypassRules("http", get_property, &rules->bypass_rules);
+ AddBypassRules("https", get_property, &rules->bypass_rules);
+ // We know a proxy was found if not all of the proxy lists are empty.
+ return !(rules->proxies_for_http.IsEmpty() &&
+ rules->proxies_for_https.IsEmpty() &&
+ rules->proxies_for_ftp.IsEmpty() &&
+ rules->fallback_proxies.IsEmpty());
+};
+
+void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property,
+ ProxyConfig* config) {
+ if (!GetProxyRules(get_property, &config->proxy_rules()))
+ *config = ProxyConfig::CreateDirect();
+}
+
+std::string GetJavaProperty(const std::string& property) {
+ // Use Java System.getProperty to get configuration information.
+ // TODO(pliard): Conversion to/from UTF8 ok here?
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, property);
+ ScopedJavaLocalRef<jstring> result =
+ Java_ProxyChangeListener_getProperty(env, str.obj());
+ return result.is_null() ?
+ std::string() : ConvertJavaStringToUTF8(env, result.obj());
+}
+
+} // namespace
+
+class ProxyConfigServiceAndroid::Delegate
+ : public base::RefCountedThreadSafe<Delegate> {
+ public:
+ Delegate(base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* jni_task_runner,
+ const GetPropertyCallback& get_property_callback)
+ : jni_delegate_(this),
+ network_task_runner_(network_task_runner),
+ jni_task_runner_(jni_task_runner),
+ get_property_callback_(get_property_callback) {
+ }
+
+ void SetupJNI() {
+ DCHECK(OnJNIThread());
+ JNIEnv* env = AttachCurrentThread();
+ if (java_proxy_change_listener_.is_null()) {
+ java_proxy_change_listener_.Reset(
+ Java_ProxyChangeListener_create(
+ env, base::android::GetApplicationContext()));
+ CHECK(!java_proxy_change_listener_.is_null());
+ }
+ Java_ProxyChangeListener_start(
+ env,
+ java_proxy_change_listener_.obj(),
+ reinterpret_cast<jint>(&jni_delegate_));
+ }
+
+ void FetchInitialConfig() {
+ DCHECK(OnJNIThread());
+ ProxyConfig proxy_config;
+ GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Delegate::SetNewConfigOnNetworkThread, this, proxy_config));
+ }
+
+ void Shutdown() {
+ if (OnJNIThread()) {
+ ShutdownOnJNIThread();
+ } else {
+ jni_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Delegate::ShutdownOnJNIThread, this));
+ }
+ }
+
+ // Called only on the network thread.
+ void AddObserver(Observer* observer) {
+ DCHECK(OnNetworkThread());
+ observers_.AddObserver(observer);
+ }
+
+ void RemoveObserver(Observer* observer) {
+ DCHECK(OnNetworkThread());
+ observers_.RemoveObserver(observer);
+ }
+
+ ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) {
+ DCHECK(OnNetworkThread());
+ if (!config)
+ return ProxyConfigService::CONFIG_UNSET;
+ *config = proxy_config_;
+ return ProxyConfigService::CONFIG_VALID;
+ }
+
+ // Called on the JNI thread.
+ void ProxySettingsChanged() {
+ DCHECK(OnJNIThread());
+ ProxyConfig proxy_config;
+ GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &Delegate::SetNewConfigOnNetworkThread, this, proxy_config));
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+
+ class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate {
+ public:
+ explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {}
+
+ // ProxyConfigServiceAndroid::JNIDelegate overrides.
+ virtual void ProxySettingsChanged(JNIEnv*, jobject) OVERRIDE {
+ delegate_->ProxySettingsChanged();
+ }
+
+ private:
+ Delegate* const delegate_;
+ };
+
+ virtual ~Delegate() {}
+
+ void ShutdownOnJNIThread() {
+ if (java_proxy_change_listener_.is_null())
+ return;
+ JNIEnv* env = AttachCurrentThread();
+ Java_ProxyChangeListener_stop(env, java_proxy_change_listener_.obj());
+ }
+
+ // Called on the network thread.
+ void SetNewConfigOnNetworkThread(const ProxyConfig& proxy_config) {
+ DCHECK(OnNetworkThread());
+ proxy_config_ = proxy_config;
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnProxyConfigChanged(proxy_config,
+ ProxyConfigService::CONFIG_VALID));
+ }
+
+ bool OnJNIThread() const {
+ return jni_task_runner_->RunsTasksOnCurrentThread();
+ }
+
+ bool OnNetworkThread() const {
+ return network_task_runner_->RunsTasksOnCurrentThread();
+ }
+
+ ScopedJavaGlobalRef<jobject> java_proxy_change_listener_;
+
+ JNIDelegateImpl jni_delegate_;
+ ObserverList<Observer> observers_;
+ scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> jni_task_runner_;
+ GetPropertyCallback get_property_callback_;
+ ProxyConfig proxy_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+};
+
+ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
+ base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* jni_task_runner)
+ : delegate_(new Delegate(
+ network_task_runner, jni_task_runner, base::Bind(&GetJavaProperty))) {
+ delegate_->SetupJNI();
+ delegate_->FetchInitialConfig();
+}
+
+ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() {
+ delegate_->Shutdown();
+}
+
+// static
+bool ProxyConfigServiceAndroid::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+void ProxyConfigServiceAndroid::AddObserver(Observer* observer) {
+ delegate_->AddObserver(observer);
+}
+
+void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) {
+ delegate_->RemoveObserver(observer);
+}
+
+ProxyConfigService::ConfigAvailability
+ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) {
+ return delegate_->GetLatestProxyConfig(config);
+}
+
+ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
+ base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* jni_task_runner,
+ GetPropertyCallback get_property_callback)
+ : delegate_(new Delegate(
+ network_task_runner, jni_task_runner, get_property_callback)) {
+ delegate_->SetupJNI();
+ delegate_->FetchInitialConfig();
+}
+
+void ProxyConfigServiceAndroid::ProxySettingsChanged() {
+ delegate_->ProxySettingsChanged();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_android.h b/chromium/net/proxy/proxy_config_service_android.h
new file mode 100644
index 00000000000..3ddbaebe78e
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_android.h
@@ -0,0 +1,79 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_
+
+#include <string>
+
+#include "base/android/jni_android.h"
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_config_service.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace net {
+
+class ProxyConfig;
+
+class NET_EXPORT ProxyConfigServiceAndroid : public ProxyConfigService {
+ public:
+ // Callback that returns the value of the property identified by the provided
+ // key. If it was not found, an empty string is returned. Note that this
+ // interface does not let you distinguish an empty property from a
+ // non-existing property. This callback is invoked on the JNI thread.
+ typedef base::Callback<std::string (const std::string& property)>
+ GetPropertyCallback;
+
+ // Separate class whose instance is owned by the Delegate class implemented in
+ // the .cc file.
+ class JNIDelegate {
+ public:
+ virtual ~JNIDelegate() {}
+
+ // Called from Java (on JNI thread) to signal that the proxy settings have
+ // changed.
+ virtual void ProxySettingsChanged(JNIEnv*, jobject) = 0;
+ };
+
+ ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* jni_task_runner);
+
+ virtual ~ProxyConfigServiceAndroid();
+
+ // Register JNI bindings.
+ static bool Register(JNIEnv* env);
+
+ // ProxyConfigService:
+ // Called only on the network thread.
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE;
+
+ private:
+ friend class ProxyConfigServiceAndroidTestBase;
+ class Delegate;
+
+ // For tests.
+ ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* jni_task_runner,
+ GetPropertyCallback get_property_callback);
+
+ // For tests.
+ void ProxySettingsChanged();
+
+ scoped_refptr<Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceAndroid);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_
diff --git a/chromium/net/proxy/proxy_config_service_android_unittest.cc b/chromium/net/proxy/proxy_config_service_android_unittest.cc
new file mode 100644
index 00000000000..03324b9ed77
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_android_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (c) 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.
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_android.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class TestObserver : public ProxyConfigService::Observer {
+ public:
+ TestObserver() : availability_(ProxyConfigService::CONFIG_UNSET) {}
+
+ // ProxyConfigService::Observer:
+ virtual void OnProxyConfigChanged(
+ const ProxyConfig& config,
+ ProxyConfigService::ConfigAvailability availability) OVERRIDE {
+ config_ = config;
+ availability_ = availability;
+ }
+
+ ProxyConfigService::ConfigAvailability availability() const {
+ return availability_;
+ }
+
+ const ProxyConfig& config() const {
+ return config_;
+ }
+
+ private:
+ ProxyConfig config_;
+ ProxyConfigService::ConfigAvailability availability_;
+};
+
+} // namespace
+
+typedef std::map<std::string, std::string> StringMap;
+
+class ProxyConfigServiceAndroidTestBase : public testing::Test {
+ protected:
+ // Note that the current thread's message loop is initialized by the test
+ // suite (see net/test/net_test_suite.cc).
+ ProxyConfigServiceAndroidTestBase(const StringMap& initial_configuration)
+ : configuration_(initial_configuration),
+ message_loop_(base::MessageLoop::current()),
+ service_(message_loop_->message_loop_proxy(),
+ message_loop_->message_loop_proxy(),
+ base::Bind(&ProxyConfigServiceAndroidTestBase::GetProperty,
+ base::Unretained(this))) {}
+
+ virtual ~ProxyConfigServiceAndroidTestBase() {}
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE {
+ message_loop_->RunUntilIdle();
+ service_.AddObserver(&observer_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ service_.RemoveObserver(&observer_);
+ }
+
+ void ClearConfiguration() {
+ configuration_.clear();
+ }
+
+ void AddProperty(const std::string& key, const std::string& value) {
+ configuration_[key] = value;
+ }
+
+ std::string GetProperty(const std::string& key) {
+ StringMap::const_iterator it = configuration_.find(key);
+ if (it == configuration_.end())
+ return std::string();
+ return it->second;
+ }
+
+ void ProxySettingsChanged() {
+ service_.ProxySettingsChanged();
+ message_loop_->RunUntilIdle();
+ }
+
+ void TestMapping(const std::string& url, const std::string& expected) {
+ ProxyConfigService::ConfigAvailability availability;
+ ProxyConfig proxy_config;
+ availability = service_.GetLatestProxyConfig(&proxy_config);
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID, availability);
+ ProxyInfo proxy_info;
+ proxy_config.proxy_rules().Apply(GURL(url), &proxy_info);
+ EXPECT_EQ(expected, proxy_info.ToPacString());
+ }
+
+ StringMap configuration_;
+ TestObserver observer_;
+ base::MessageLoop* const message_loop_;
+ ProxyConfigServiceAndroid service_;
+};
+
+class ProxyConfigServiceAndroidTest : public ProxyConfigServiceAndroidTestBase {
+ public:
+ ProxyConfigServiceAndroidTest()
+ : ProxyConfigServiceAndroidTestBase(StringMap()) {}
+};
+
+class ProxyConfigServiceAndroidWithInitialConfigTest
+ : public ProxyConfigServiceAndroidTestBase {
+ public:
+ ProxyConfigServiceAndroidWithInitialConfigTest()
+ : ProxyConfigServiceAndroidTestBase(MakeInitialConfiguration()) {}
+
+ private:
+ StringMap MakeInitialConfiguration() {
+ StringMap initial_configuration;
+ initial_configuration["http.proxyHost"] = "httpproxy.com";
+ initial_configuration["http.proxyPort"] = "8080";
+ return initial_configuration;
+ }
+};
+
+TEST_F(ProxyConfigServiceAndroidTest, TestChangePropertiesNotification) {
+ // Set up a non-empty configuration
+ AddProperty("http.proxyHost", "localhost");
+ ProxySettingsChanged();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability());
+ EXPECT_FALSE(observer_.config().proxy_rules().empty());
+
+ // Set up an empty configuration
+ ClearConfiguration();
+ ProxySettingsChanged();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability());
+ EXPECT_TRUE(observer_.config().proxy_rules().empty());
+}
+
+TEST_F(ProxyConfigServiceAndroidWithInitialConfigTest, TestInitialConfig) {
+ // Make sure that the initial config is set.
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "PROXY httpproxy.com:8080");
+
+ // Override the initial configuration.
+ ClearConfiguration();
+ AddProperty("http.proxyHost", "httpproxy.com");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "PROXY httpproxy.com:80");
+}
+
+// !! The following test cases are automatically generated from
+// !! net/android/tools/proxy_test_cases.py.
+// !! Please edit that file instead of editing the test cases below and
+// !! update also the corresponding Java unit tests in
+// !! AndroidProxySelectorTest.java
+
+TEST_F(ProxyConfigServiceAndroidTest, NoProxy) {
+ // Test direct mapping when no proxy defined.
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPort) {
+ // Test http.proxyHost and http.proxyPort works.
+ AddProperty("http.proxyHost", "httpproxy.com");
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostOnly) {
+ // We should get the default port (80) for proxied hosts.
+ AddProperty("http.proxyHost", "httpproxy.com");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "PROXY httpproxy.com:80");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyPortOnly) {
+ // http.proxyPort only should not result in any hosts being proxied.
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts1) {
+ // Test that HTTP non proxy hosts are mapped correctly
+ AddProperty("http.nonProxyHosts", "slashdot.org");
+ AddProperty("http.proxyHost", "httpproxy.com");
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("http://slashdot.org/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts2) {
+ // Test that | pattern works.
+ AddProperty("http.nonProxyHosts", "slashdot.org|freecode.net");
+ AddProperty("http.proxyHost", "httpproxy.com");
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("http://freecode.net/", "DIRECT");
+ TestMapping("http://slashdot.org/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts3) {
+ // Test that * pattern works.
+ AddProperty("http.nonProxyHosts", "*example.com");
+ AddProperty("http.proxyHost", "httpproxy.com");
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("http://slashdot.org/", "PROXY httpproxy.com:8080");
+ TestMapping("http://www.example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, FtpNonProxyHosts) {
+ // Test that FTP non proxy hosts are mapped correctly
+ AddProperty("ftp.nonProxyHosts", "slashdot.org");
+ AddProperty("ftp.proxyHost", "httpproxy.com");
+ AddProperty("ftp.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("http://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostAndPort) {
+ // Test ftp.proxyHost and ftp.proxyPort works.
+ AddProperty("ftp.proxyHost", "httpproxy.com");
+ AddProperty("ftp.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostOnly) {
+ // Test ftp.proxyHost and default port.
+ AddProperty("ftp.proxyHost", "httpproxy.com");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "PROXY httpproxy.com:80");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostAndPort) {
+ // Test https.proxyHost and https.proxyPort works.
+ AddProperty("https.proxyHost", "httpproxy.com");
+ AddProperty("https.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "PROXY httpproxy.com:8080");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostOnly) {
+ // Test https.proxyHost and default port.
+ AddProperty("https.proxyHost", "httpproxy.com");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "DIRECT");
+ TestMapping("https://example.com/", "PROXY httpproxy.com:80");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostIPv6) {
+ // Test IPv6 https.proxyHost and default port.
+ AddProperty("http.proxyHost", "a:b:c::d:1");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:80");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPortIPv6) {
+ // Test IPv6 http.proxyHost and http.proxyPort works.
+ AddProperty("http.proxyHost", "a:b:c::d:1");
+ AddProperty("http.proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:8080");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndInvalidPort) {
+ // Test invalid http.proxyPort does not crash.
+ AddProperty("http.proxyHost", "a:b:c::d:1");
+ AddProperty("http.proxyPort", "65536");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "DIRECT");
+ TestMapping("http://example.com/", "DIRECT");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyExplictPort) {
+ // Default http proxy is used if a scheme-specific one is not found.
+ AddProperty("ftp.proxyHost", "httpproxy.com");
+ AddProperty("ftp.proxyPort", "8080");
+ AddProperty("proxyHost", "defaultproxy.com");
+ AddProperty("proxyPort", "8080");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080");
+ TestMapping("http://example.com/", "PROXY defaultproxy.com:8080");
+ TestMapping("https://example.com/", "PROXY defaultproxy.com:8080");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyDefaultPort) {
+ // Check that the default proxy port is as expected.
+ AddProperty("proxyHost", "defaultproxy.com");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "PROXY defaultproxy.com:80");
+ TestMapping("https://example.com/", "PROXY defaultproxy.com:80");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, FallbackToSocks) {
+ // SOCKS proxy is used if scheme-specific one is not found.
+ AddProperty("http.proxyHost", "defaultproxy.com");
+ AddProperty("socksProxyHost", "socksproxy.com");
+ ProxySettingsChanged();
+ TestMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080");
+ TestMapping("http://example.com/", "PROXY defaultproxy.com:80");
+ TestMapping("https://example.com/", "SOCKS5 socksproxy.com:1080");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, SocksExplicitPort) {
+ // SOCKS proxy port is used if specified
+ AddProperty("socksProxyHost", "socksproxy.com");
+ AddProperty("socksProxyPort", "9000");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "SOCKS5 socksproxy.com:9000");
+}
+
+TEST_F(ProxyConfigServiceAndroidTest, HttpProxySupercedesSocks) {
+ // SOCKS proxy is ignored if default HTTP proxy defined.
+ AddProperty("proxyHost", "defaultproxy.com");
+ AddProperty("socksProxyHost", "socksproxy.com");
+ AddProperty("socksProxyPort", "9000");
+ ProxySettingsChanged();
+ TestMapping("http://example.com/", "PROXY defaultproxy.com:80");
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_common_unittest.cc b/chromium/net/proxy/proxy_config_service_common_unittest.cc
new file mode 100644
index 00000000000..3835bb1ad20
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_common_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2009 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/proxy/proxy_config_service_common_unittest.h"
+
+#include <string>
+#include <vector>
+
+#include "net/proxy/proxy_config.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Helper to verify that |expected_proxy| matches the first proxy conatined in
+// |actual_proxies|, and that |actual_proxies| contains exactly one proxy. If
+// either condition is untrue, then |*did_fail| is set to true, and
+// |*failure_details| is filled with a description of the failure.
+void MatchesProxyServerHelper(const char* failure_message,
+ const char* expected_proxy,
+ const ProxyList& actual_proxies,
+ ::testing::AssertionResult* failure_details,
+ bool* did_fail) {
+ // If |expected_proxy| is empty, then we expect |actual_proxies| to be so as
+ // well.
+ if (strlen(expected_proxy) == 0) {
+ if (!actual_proxies.IsEmpty()) {
+ *did_fail = true;
+ *failure_details
+ << failure_message << ". Was expecting no proxies but got "
+ << actual_proxies.size() << ".";
+ }
+ return;
+ }
+
+ // Otherwise we check that |actual_proxies| holds a single matching proxy.
+ if (actual_proxies.size() != 1) {
+ *did_fail = true;
+ *failure_details
+ << failure_message << ". Was expecting exactly one proxy but got "
+ << actual_proxies.size() << ".";
+ return;
+ }
+
+ ProxyServer actual_proxy = actual_proxies.Get();
+ std::string actual_proxy_string;
+ if (actual_proxy.is_valid())
+ actual_proxy_string = actual_proxy.ToURI();
+
+ if (std::string(expected_proxy) != actual_proxy_string) {
+ *failure_details
+ << failure_message << ". Was expecting: \"" << expected_proxy
+ << "\" but got: \"" << actual_proxy_string << "\"";
+ *did_fail = true;
+ }
+}
+
+std::string FlattenProxyBypass(const ProxyBypassRules& bypass_rules) {
+ std::string flattened_proxy_bypass;
+ for (ProxyBypassRules::RuleList::const_iterator it =
+ bypass_rules.rules().begin();
+ it != bypass_rules.rules().end(); ++it) {
+ if (!flattened_proxy_bypass.empty())
+ flattened_proxy_bypass += ",";
+ flattened_proxy_bypass += (*it)->ToString();
+ }
+ return flattened_proxy_bypass;
+}
+
+} // namespace
+
+ProxyRulesExpectation::ProxyRulesExpectation(
+ ProxyConfig::ProxyRules::Type type,
+ const char* single_proxy,
+ const char* proxy_for_http,
+ const char* proxy_for_https,
+ const char* proxy_for_ftp,
+ const char* fallback_proxy,
+ const char* flattened_bypass_rules,
+ bool reverse_bypass)
+ : type(type),
+ single_proxy(single_proxy),
+ proxy_for_http(proxy_for_http),
+ proxy_for_https(proxy_for_https),
+ proxy_for_ftp(proxy_for_ftp),
+ fallback_proxy(fallback_proxy),
+ flattened_bypass_rules(flattened_bypass_rules),
+ reverse_bypass(reverse_bypass) {
+}
+
+
+::testing::AssertionResult ProxyRulesExpectation::Matches(
+ const ProxyConfig::ProxyRules& rules) const {
+ ::testing::AssertionResult failure_details = ::testing::AssertionFailure();
+ bool failed = false;
+
+ if (rules.type != type) {
+ failure_details << "Type mismatch. Expected: "
+ << type << " but was: " << rules.type;
+ failed = true;
+ }
+
+ MatchesProxyServerHelper("Bad single_proxy", single_proxy,
+ rules.single_proxies, &failure_details, &failed);
+ MatchesProxyServerHelper("Bad proxy_for_http", proxy_for_http,
+ rules.proxies_for_http, &failure_details,
+ &failed);
+ MatchesProxyServerHelper("Bad proxy_for_https", proxy_for_https,
+ rules.proxies_for_https, &failure_details,
+ &failed);
+ MatchesProxyServerHelper("Bad fallback_proxy", fallback_proxy,
+ rules.fallback_proxies, &failure_details, &failed);
+
+ std::string actual_flattened_bypass = FlattenProxyBypass(rules.bypass_rules);
+ if (std::string(flattened_bypass_rules) != actual_flattened_bypass) {
+ failure_details
+ << "Bad bypass rules. Expected: \"" << flattened_bypass_rules
+ << "\" but got: \"" << actual_flattened_bypass << "\"";
+ failed = true;
+ }
+
+ if (rules.reverse_bypass != reverse_bypass) {
+ failure_details << "Bad reverse_bypass. Expected: " << reverse_bypass
+ << " but got: " << rules.reverse_bypass;
+ failed = true;
+ }
+
+ return failed ? failure_details : ::testing::AssertionSuccess();
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::Empty() {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_NO_RULES,
+ "", "", "", "", "", "", false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::EmptyWithBypass(
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_NO_RULES,
+ "", "", "", "", "", flattened_bypass_rules,
+ false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::Single(
+ const char* single_proxy,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ single_proxy, "", "", "", "",
+ flattened_bypass_rules, false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerScheme(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp, "",
+ flattened_bypass_rules, false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithSocks(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* socks_proxy,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp,
+ socks_proxy, flattened_bypass_rules, false);
+}
+
+// static
+ProxyRulesExpectation ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules) {
+ return ProxyRulesExpectation(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ "", proxy_http, proxy_https, proxy_ftp, "",
+ flattened_bypass_rules, true);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_common_unittest.h b/chromium/net/proxy/proxy_config_service_common_unittest.h
new file mode 100644
index 00000000000..dbacbab79b8
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_common_unittest.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2010 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_PROXY_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_
+
+#include "net/proxy/proxy_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Helper functions to describe the expected value of a
+// ProxyConfig::ProxyRules, and to check for a match.
+
+namespace net {
+
+// This structure contains our expectations on what values the ProxyRules
+// should have.
+struct ProxyRulesExpectation {
+ ProxyRulesExpectation(ProxyConfig::ProxyRules::Type type,
+ const char* single_proxy,
+ const char* proxy_for_http,
+ const char* proxy_for_https,
+ const char* proxy_for_ftp,
+ const char* fallback_proxy,
+ const char* flattened_bypass_rules,
+ bool reverse_bypass);
+
+ // Call this within an EXPECT_TRUE(), to assert that |rules| matches
+ // our expected values |*this|.
+ ::testing::AssertionResult Matches(
+ const ProxyConfig::ProxyRules& rules) const;
+
+ // Creates an expectation that the ProxyRules has no rules.
+ static ProxyRulesExpectation Empty();
+
+ // Creates an expectation that the ProxyRules has nothing other than
+ // the specified bypass rules.
+ static ProxyRulesExpectation EmptyWithBypass(
+ const char* flattened_bypass_rules);
+
+ // Creates an expectation that the ProxyRules is for a single proxy
+ // server for all schemes.
+ static ProxyRulesExpectation Single(const char* single_proxy,
+ const char* flattened_bypass_rules);
+
+ // Creates an expectation that the ProxyRules specifies a different
+ // proxy server for each URL scheme.
+ static ProxyRulesExpectation PerScheme(const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules);
+
+ // Same as above, but additionally with a SOCKS fallback.
+ static ProxyRulesExpectation PerSchemeWithSocks(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* fallback_proxy,
+ const char* flattened_bypass_rules);
+
+ // Same as PerScheme, but with the bypass rules reversed
+ static ProxyRulesExpectation PerSchemeWithBypassReversed(
+ const char* proxy_http,
+ const char* proxy_https,
+ const char* proxy_ftp,
+ const char* flattened_bypass_rules);
+
+ ProxyConfig::ProxyRules::Type type;
+ const char* single_proxy;
+ const char* proxy_for_http;
+ const char* proxy_for_https;
+ const char* proxy_for_ftp;
+ const char* fallback_proxy;
+ const char* flattened_bypass_rules;
+ bool reverse_bypass;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_COMMON_UNITTEST_H_
diff --git a/chromium/net/proxy/proxy_config_service_fixed.cc b/chromium/net/proxy/proxy_config_service_fixed.cc
new file mode 100644
index 00000000000..3081ea5afa1
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_fixed.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2011 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/proxy/proxy_config_service_fixed.h"
+
+namespace net {
+
+ProxyConfigServiceFixed::ProxyConfigServiceFixed(const ProxyConfig& pc)
+ : pc_(pc) {
+}
+
+ProxyConfigServiceFixed::~ProxyConfigServiceFixed() {}
+
+ProxyConfigService::ConfigAvailability
+ ProxyConfigServiceFixed::GetLatestProxyConfig(ProxyConfig* config) {
+ *config = pc_;
+ return CONFIG_VALID;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_fixed.h b/chromium/net/proxy/proxy_config_service_fixed.h
new file mode 100644
index 00000000000..b5f30e3654b
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_fixed.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 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_PROXY_PROXY_CONFIG_SERVICE_FIXED_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_FIXED_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service.h"
+
+namespace net {
+
+// Implementation of ProxyConfigService that returns a fixed result.
+class NET_EXPORT ProxyConfigServiceFixed : public ProxyConfigService {
+ public:
+ explicit ProxyConfigServiceFixed(const ProxyConfig& pc);
+ virtual ~ProxyConfigServiceFixed();
+
+ // ProxyConfigService methods:
+ virtual void AddObserver(Observer* observer) OVERRIDE {}
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {}
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE;
+
+ private:
+ ProxyConfig pc_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_FIXED_H_
diff --git a/chromium/net/proxy/proxy_config_service_ios.cc b/chromium/net/proxy/proxy_config_service_ios.cc
new file mode 100644
index 00000000000..84a5adcedf5
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_ios.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_ios.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CFNetwork/CFProxySupport.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "net/proxy/proxy_config.h"
+
+namespace net {
+
+namespace {
+
+const int kPollIntervalSec = 10;
+
+// Utility function to pull out a boolean value from a dictionary and return it,
+// returning a default value if the key is not present.
+bool GetBoolFromDictionary(CFDictionaryRef dict,
+ CFStringRef key,
+ bool default_value) {
+ CFNumberRef number =
+ base::mac::GetValueFromDictionary<CFNumberRef>(dict, key);
+ if (!number)
+ return default_value;
+
+ int int_value;
+ if (CFNumberGetValue(number, kCFNumberIntType, &int_value))
+ return int_value;
+ else
+ return default_value;
+}
+
+void GetCurrentProxyConfig(ProxyConfig* config) {
+ base::ScopedCFTypeRef<CFDictionaryRef> config_dict(
+ CFNetworkCopySystemProxySettings());
+ DCHECK(config_dict);
+
+ // Auto-detect is not supported.
+ // The kCFNetworkProxiesProxyAutoDiscoveryEnable key is not available on iOS.
+
+ // PAC file
+
+ if (GetBoolFromDictionary(config_dict.get(),
+ kCFNetworkProxiesProxyAutoConfigEnable,
+ false)) {
+ CFStringRef pac_url_ref = base::mac::GetValueFromDictionary<CFStringRef>(
+ config_dict.get(), kCFNetworkProxiesProxyAutoConfigURLString);
+ if (pac_url_ref)
+ config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
+ }
+
+ // Proxies (for now http).
+
+ // The following keys are not available on iOS:
+ // kCFNetworkProxiesFTPEnable
+ // kCFNetworkProxiesFTPProxy
+ // kCFNetworkProxiesFTPPort
+ // kCFNetworkProxiesHTTPSEnable
+ // kCFNetworkProxiesHTTPSProxy
+ // kCFNetworkProxiesHTTPSPort
+ // kCFNetworkProxiesSOCKSEnable
+ // kCFNetworkProxiesSOCKSProxy
+ // kCFNetworkProxiesSOCKSPort
+ if (GetBoolFromDictionary(config_dict.get(),
+ kCFNetworkProxiesHTTPEnable,
+ false)) {
+ ProxyServer proxy_server =
+ ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
+ config_dict.get(),
+ kCFNetworkProxiesHTTPProxy,
+ kCFNetworkProxiesHTTPPort);
+ if (proxy_server.is_valid()) {
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server);
+ // Desktop Safari applies the HTTP proxy to http:// URLs only, but
+ // Mobile Safari applies the HTTP proxy to https:// URLs as well.
+ config->proxy_rules().proxies_for_https.SetSingleProxyServer(
+ proxy_server);
+ }
+ }
+
+ // Proxy bypass list is not supported.
+ // The kCFNetworkProxiesExceptionsList key is not available on iOS.
+
+ // Proxy bypass boolean is not supported.
+ // The kCFNetworkProxiesExcludeSimpleHostnames key is not available on iOS.
+
+ // Source
+ config->set_source(PROXY_CONFIG_SOURCE_SYSTEM);
+}
+
+} // namespace
+
+ProxyConfigServiceIOS::ProxyConfigServiceIOS()
+ : PollingProxyConfigService(base::TimeDelta::FromSeconds(kPollIntervalSec),
+ GetCurrentProxyConfig) {
+}
+
+ProxyConfigServiceIOS::~ProxyConfigServiceIOS() {
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_ios.h b/chromium/net/proxy/proxy_config_service_ios.h
new file mode 100644
index 00000000000..bf8f76b6b67
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_ios.h
@@ -0,0 +1,24 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_IOS_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_IOS_H_
+
+#include "net/proxy/polling_proxy_config_service.h"
+
+namespace net {
+
+class ProxyConfigServiceIOS : public PollingProxyConfigService {
+ public:
+ // Constructs a ProxyConfigService that watches the iOS system proxy settings.
+ explicit ProxyConfigServiceIOS();
+ virtual ~ProxyConfigServiceIOS();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceIOS);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_IOS_H_
diff --git a/chromium/net/proxy/proxy_config_service_linux.cc b/chromium/net/proxy/proxy_config_service_linux.cc
new file mode 100644
index 00000000000..2c21283ea75
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_linux.cc
@@ -0,0 +1,1766 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_linux.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#if defined(USE_GCONF)
+#include <gconf/gconf-client.h>
+#endif // defined(USE_GCONF)
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/environment.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/nix/xdg_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/timer/timer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_util.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_server.h"
+#include "url/url_canon.h"
+
+#if defined(USE_GIO)
+#include "library_loaders/libgio.h"
+#endif // defined(USE_GIO)
+
+namespace net {
+
+namespace {
+
+// Given a proxy hostname from a setting, returns that hostname with
+// an appropriate proxy server scheme prefix.
+// scheme indicates the desired proxy scheme: usually http, with
+// socks 4 or 5 as special cases.
+// TODO(arindam): Remove URI string manipulation by using MapUrlSchemeToProxy.
+std::string FixupProxyHostScheme(ProxyServer::Scheme scheme,
+ std::string host) {
+ if (scheme == ProxyServer::SCHEME_SOCKS5 &&
+ StartsWithASCII(host, "socks4://", false)) {
+ // We default to socks 5, but if the user specifically set it to
+ // socks4://, then use that.
+ scheme = ProxyServer::SCHEME_SOCKS4;
+ }
+ // Strip the scheme if any.
+ std::string::size_type colon = host.find("://");
+ if (colon != std::string::npos)
+ host = host.substr(colon + 3);
+ // If a username and perhaps password are specified, give a warning.
+ std::string::size_type at_sign = host.find("@");
+ // Should this be supported?
+ if (at_sign != std::string::npos) {
+ // ProxyConfig does not support authentication parameters, but Chrome
+ // will prompt for the password later. Disregard the
+ // authentication parameters and continue with this hostname.
+ LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
+ host = host.substr(at_sign + 1);
+ }
+ // If this is a socks proxy, prepend a scheme so as to tell
+ // ProxyServer. This also allows ProxyServer to choose the right
+ // default port.
+ if (scheme == ProxyServer::SCHEME_SOCKS4)
+ host = "socks4://" + host;
+ else if (scheme == ProxyServer::SCHEME_SOCKS5)
+ host = "socks5://" + host;
+ // If there is a trailing slash, remove it so |host| will parse correctly
+ // even if it includes a port number (since the slash is not numeric).
+ if (host.length() && host[host.length() - 1] == '/')
+ host.resize(host.length() - 1);
+ return host;
+}
+
+} // namespace
+
+ProxyConfigServiceLinux::Delegate::~Delegate() {
+}
+
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme(
+ const char* variable, ProxyServer::Scheme scheme,
+ ProxyServer* result_server) {
+ std::string env_value;
+ if (env_var_getter_->GetVar(variable, &env_value)) {
+ if (!env_value.empty()) {
+ env_value = FixupProxyHostScheme(scheme, env_value);
+ ProxyServer proxy_server =
+ ProxyServer::FromURI(env_value, ProxyServer::SCHEME_HTTP);
+ if (proxy_server.is_valid() && !proxy_server.is_direct()) {
+ *result_server = proxy_server;
+ return true;
+ } else {
+ LOG(ERROR) << "Failed to parse environment variable " << variable;
+ }
+ }
+ }
+ return false;
+}
+
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar(
+ const char* variable, ProxyServer* result_server) {
+ return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
+ result_server);
+}
+
+bool ProxyConfigServiceLinux::Delegate::GetConfigFromEnv(ProxyConfig* config) {
+ // Check for automatic configuration first, in
+ // "auto_proxy". Possibly only the "environment_proxy" firefox
+ // extension has ever used this, but it still sounds like a good
+ // idea.
+ std::string auto_proxy;
+ if (env_var_getter_->GetVar("auto_proxy", &auto_proxy)) {
+ if (auto_proxy.empty()) {
+ // Defined and empty => autodetect
+ config->set_auto_detect(true);
+ } else {
+ // specified autoconfig URL
+ config->set_pac_url(GURL(auto_proxy));
+ }
+ return true;
+ }
+ // "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy.
+ ProxyServer proxy_server;
+ if (GetProxyFromEnvVar("all_proxy", &proxy_server)) {
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_server);
+ } else {
+ bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_server);
+ if (have_http)
+ config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server);
+ // It would be tempting to let http_proxy apply for all protocols
+ // if https_proxy and ftp_proxy are not defined. Googling turns up
+ // several documents that mention only http_proxy. But then the
+ // user really might not want to proxy https. And it doesn't seem
+ // like other apps do this. So we will refrain.
+ bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_server);
+ if (have_https)
+ config->proxy_rules().proxies_for_https.
+ SetSingleProxyServer(proxy_server);
+ bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_server);
+ if (have_ftp)
+ config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server);
+ if (have_http || have_https || have_ftp) {
+ // mustn't change type unless some rules are actually set.
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ }
+ }
+ if (config->proxy_rules().empty()) {
+ // If the above were not defined, try for socks.
+ // For environment variables, we default to version 5, per the gnome
+ // documentation: http://library.gnome.org/devel/gnet/stable/gnet-socks.html
+ ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS5;
+ std::string env_version;
+ if (env_var_getter_->GetVar("SOCKS_VERSION", &env_version)
+ && env_version == "4")
+ scheme = ProxyServer::SCHEME_SOCKS4;
+ if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_server);
+ }
+ }
+ // Look for the proxy bypass list.
+ std::string no_proxy;
+ env_var_getter_->GetVar("no_proxy", &no_proxy);
+ if (config->proxy_rules().empty()) {
+ // Having only "no_proxy" set, presumably to "*", makes it
+ // explicit that env vars do specify a configuration: having no
+ // rules specified only means the user explicitly asks for direct
+ // connections.
+ return !no_proxy.empty();
+ }
+ // Note that this uses "suffix" matching. So a bypass of "google.com"
+ // is understood to mean a bypass of "*google.com".
+ config->proxy_rules().bypass_rules.ParseFromStringUsingSuffixMatching(
+ no_proxy);
+ return true;
+}
+
+namespace {
+
+const int kDebounceTimeoutMilliseconds = 250;
+
+#if defined(USE_GCONF)
+// This setting getter uses gconf, as used in GNOME 2 and some GNOME 3 desktops.
+class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter {
+ public:
+ SettingGetterImplGConf()
+ : client_(NULL), system_proxy_id_(0), system_http_proxy_id_(0),
+ notify_delegate_(NULL) {
+ }
+
+ virtual ~SettingGetterImplGConf() {
+ // client_ should have been released before now, from
+ // Delegate::OnDestroy(), while running on the UI thread. However
+ // on exiting the process, it may happen that Delegate::OnDestroy()
+ // task is left pending on the glib loop after the loop was quit,
+ // and pending tasks may then be deleted without being run.
+ if (client_) {
+ // gconf client was not cleaned up.
+ if (task_runner_->BelongsToCurrentThread()) {
+ // We are on the UI thread so we can clean it safely. This is
+ // the case at least for ui_tests running under Valgrind in
+ // bug 16076.
+ VLOG(1) << "~SettingGetterImplGConf: releasing gconf client";
+ ShutDown();
+ } else {
+ // This is very bad! We are deleting the setting getter but we're not on
+ // the UI thread. This is not supposed to happen: the setting getter is
+ // owned by the proxy config service's delegate, which is supposed to be
+ // destroyed on the UI thread only. We will get change notifications to
+ // a deleted object if we continue here, so fail now.
+ LOG(FATAL) << "~SettingGetterImplGConf: deleting on wrong thread!";
+ }
+ }
+ DCHECK(!client_);
+ }
+
+ virtual bool Init(base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::MessageLoopForIO* file_loop) OVERRIDE {
+ DCHECK(glib_thread_task_runner->BelongsToCurrentThread());
+ DCHECK(!client_);
+ DCHECK(!task_runner_.get());
+ task_runner_ = glib_thread_task_runner;
+ client_ = gconf_client_get_default();
+ if (!client_) {
+ // It's not clear whether/when this can return NULL.
+ LOG(ERROR) << "Unable to create a gconf client";
+ task_runner_ = NULL;
+ return false;
+ }
+ GError* error = NULL;
+ bool added_system_proxy = false;
+ // We need to add the directories for which we'll be asking
+ // for notifications, and we might as well ask to preload them.
+ // These need to be removed again in ShutDown(); we are careful
+ // here to only leave client_ non-NULL if both have been added.
+ gconf_client_add_dir(client_, "/system/proxy",
+ GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
+ if (error == NULL) {
+ added_system_proxy = true;
+ gconf_client_add_dir(client_, "/system/http_proxy",
+ GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
+ }
+ if (error != NULL) {
+ LOG(ERROR) << "Error requesting gconf directory: " << error->message;
+ g_error_free(error);
+ if (added_system_proxy)
+ gconf_client_remove_dir(client_, "/system/proxy", NULL);
+ g_object_unref(client_);
+ client_ = NULL;
+ task_runner_ = NULL;
+ return false;
+ }
+ return true;
+ }
+
+ virtual void ShutDown() OVERRIDE {
+ if (client_) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ // We must explicitly disable gconf notifications here, because the gconf
+ // client will be shared between all setting getters, and they do not all
+ // have the same lifetimes. (For instance, incognito sessions get their
+ // own, which is destroyed when the session ends.)
+ gconf_client_notify_remove(client_, system_http_proxy_id_);
+ gconf_client_notify_remove(client_, system_proxy_id_);
+ gconf_client_remove_dir(client_, "/system/http_proxy", NULL);
+ gconf_client_remove_dir(client_, "/system/proxy", NULL);
+ g_object_unref(client_);
+ client_ = NULL;
+ task_runner_ = NULL;
+ }
+ }
+
+ virtual bool SetUpNotifications(
+ ProxyConfigServiceLinux::Delegate* delegate) OVERRIDE {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ GError* error = NULL;
+ notify_delegate_ = delegate;
+ // We have to keep track of the IDs returned by gconf_client_notify_add() so
+ // that we can remove them in ShutDown(). (Otherwise, notifications will be
+ // delivered to this object after it is deleted, which is bad, m'kay?)
+ system_proxy_id_ = gconf_client_notify_add(
+ client_, "/system/proxy",
+ OnGConfChangeNotification, this,
+ NULL, &error);
+ if (error == NULL) {
+ system_http_proxy_id_ = gconf_client_notify_add(
+ client_, "/system/http_proxy",
+ OnGConfChangeNotification, this,
+ NULL, &error);
+ }
+ if (error != NULL) {
+ LOG(ERROR) << "Error requesting gconf notifications: " << error->message;
+ g_error_free(error);
+ ShutDown();
+ return false;
+ }
+ // Simulate a change to avoid possibly losing updates before this point.
+ OnChangeNotification();
+ return true;
+ }
+
+ virtual base::SingleThreadTaskRunner* GetNotificationTaskRunner() OVERRIDE {
+ return task_runner_.get();
+ }
+
+ virtual ProxyConfigSource GetConfigSource() OVERRIDE {
+ return PROXY_CONFIG_SOURCE_GCONF;
+ }
+
+ virtual bool GetString(StringSetting key, std::string* result) OVERRIDE {
+ switch (key) {
+ case PROXY_MODE:
+ return GetStringByPath("/system/proxy/mode", result);
+ case PROXY_AUTOCONF_URL:
+ return GetStringByPath("/system/proxy/autoconfig_url", result);
+ case PROXY_HTTP_HOST:
+ return GetStringByPath("/system/http_proxy/host", result);
+ case PROXY_HTTPS_HOST:
+ return GetStringByPath("/system/proxy/secure_host", result);
+ case PROXY_FTP_HOST:
+ return GetStringByPath("/system/proxy/ftp_host", result);
+ case PROXY_SOCKS_HOST:
+ return GetStringByPath("/system/proxy/socks_host", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetBool(BoolSetting key, bool* result) OVERRIDE {
+ switch (key) {
+ case PROXY_USE_HTTP_PROXY:
+ return GetBoolByPath("/system/http_proxy/use_http_proxy", result);
+ case PROXY_USE_SAME_PROXY:
+ return GetBoolByPath("/system/http_proxy/use_same_proxy", result);
+ case PROXY_USE_AUTHENTICATION:
+ return GetBoolByPath("/system/http_proxy/use_authentication", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetInt(IntSetting key, int* result) OVERRIDE {
+ switch (key) {
+ case PROXY_HTTP_PORT:
+ return GetIntByPath("/system/http_proxy/port", result);
+ case PROXY_HTTPS_PORT:
+ return GetIntByPath("/system/proxy/secure_port", result);
+ case PROXY_FTP_PORT:
+ return GetIntByPath("/system/proxy/ftp_port", result);
+ case PROXY_SOCKS_PORT:
+ return GetIntByPath("/system/proxy/socks_port", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetStringList(StringListSetting key,
+ std::vector<std::string>* result) OVERRIDE {
+ switch (key) {
+ case PROXY_IGNORE_HOSTS:
+ return GetStringListByPath("/system/http_proxy/ignore_hosts", result);
+ }
+ return false; // Placate compiler.
+ }
+
+ virtual bool BypassListIsReversed() OVERRIDE {
+ // This is a KDE-specific setting.
+ return false;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() OVERRIDE {
+ return false;
+ }
+
+ private:
+ bool GetStringByPath(const char* key, std::string* result) {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ GError* error = NULL;
+ gchar* value = gconf_client_get_string(client_, key, &error);
+ if (HandleGError(error, key))
+ return false;
+ if (!value)
+ return false;
+ *result = value;
+ g_free(value);
+ return true;
+ }
+ bool GetBoolByPath(const char* key, bool* result) {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ GError* error = NULL;
+ // We want to distinguish unset values from values defaulting to
+ // false. For that we need to use the type-generic
+ // gconf_client_get() rather than gconf_client_get_bool().
+ GConfValue* gconf_value = gconf_client_get(client_, key, &error);
+ if (HandleGError(error, key))
+ return false;
+ if (!gconf_value) {
+ // Unset.
+ return false;
+ }
+ if (gconf_value->type != GCONF_VALUE_BOOL) {
+ gconf_value_free(gconf_value);
+ return false;
+ }
+ gboolean bool_value = gconf_value_get_bool(gconf_value);
+ *result = static_cast<bool>(bool_value);
+ gconf_value_free(gconf_value);
+ return true;
+ }
+ bool GetIntByPath(const char* key, int* result) {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ GError* error = NULL;
+ int value = gconf_client_get_int(client_, key, &error);
+ if (HandleGError(error, key))
+ return false;
+ // We don't bother to distinguish an unset value because callers
+ // don't care. 0 is returned if unset.
+ *result = value;
+ return true;
+ }
+ bool GetStringListByPath(const char* key, std::vector<std::string>* result) {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ GError* error = NULL;
+ GSList* list = gconf_client_get_list(client_, key,
+ GCONF_VALUE_STRING, &error);
+ if (HandleGError(error, key))
+ return false;
+ if (!list)
+ return false;
+ for (GSList *it = list; it; it = it->next) {
+ result->push_back(static_cast<char*>(it->data));
+ g_free(it->data);
+ }
+ g_slist_free(list);
+ return true;
+ }
+
+ // Logs and frees a glib error. Returns false if there was no error
+ // (error is NULL).
+ bool HandleGError(GError* error, const char* key) {
+ if (error != NULL) {
+ LOG(ERROR) << "Error getting gconf value for " << key
+ << ": " << error->message;
+ g_error_free(error);
+ return true;
+ }
+ return false;
+ }
+
+ // This is the callback from the debounce timer.
+ void OnDebouncedNotification() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ CHECK(notify_delegate_);
+ // Forward to a method on the proxy config service delegate object.
+ notify_delegate_->OnCheckProxyConfigSettings();
+ }
+
+ void OnChangeNotification() {
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ debounce_timer_.Stop();
+ debounce_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds),
+ this, &SettingGetterImplGConf::OnDebouncedNotification);
+ }
+
+ // gconf notification callback, dispatched on the default glib main loop.
+ static void OnGConfChangeNotification(GConfClient* client, guint cnxn_id,
+ GConfEntry* entry, gpointer user_data) {
+ VLOG(1) << "gconf change notification for key "
+ << gconf_entry_get_key(entry);
+ // We don't track which key has changed, just that something did change.
+ SettingGetterImplGConf* setting_getter =
+ reinterpret_cast<SettingGetterImplGConf*>(user_data);
+ setting_getter->OnChangeNotification();
+ }
+
+ GConfClient* client_;
+ // These ids are the values returned from gconf_client_notify_add(), which we
+ // will need in order to later call gconf_client_notify_remove().
+ guint system_proxy_id_;
+ guint system_http_proxy_id_;
+
+ ProxyConfigServiceLinux::Delegate* notify_delegate_;
+ base::OneShotTimer<SettingGetterImplGConf> debounce_timer_;
+
+ // Task runner for the thread that we make gconf calls on. It should
+ // be the UI thread and all our methods should be called on this
+ // thread. Only for assertions.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingGetterImplGConf);
+};
+#endif // defined(USE_GCONF)
+
+#if defined(USE_GIO)
+// This setting getter uses gsettings, as used in most GNOME 3 desktops.
+class SettingGetterImplGSettings
+ : public ProxyConfigServiceLinux::SettingGetter {
+ public:
+ SettingGetterImplGSettings() :
+ client_(NULL),
+ http_client_(NULL),
+ https_client_(NULL),
+ ftp_client_(NULL),
+ socks_client_(NULL),
+ notify_delegate_(NULL) {
+ }
+
+ virtual ~SettingGetterImplGSettings() {
+ // client_ should have been released before now, from
+ // Delegate::OnDestroy(), while running on the UI thread. However
+ // on exiting the process, it may happen that
+ // Delegate::OnDestroy() task is left pending on the glib loop
+ // after the loop was quit, and pending tasks may then be deleted
+ // without being run.
+ if (client_) {
+ // gconf client was not cleaned up.
+ if (task_runner_->BelongsToCurrentThread()) {
+ // We are on the UI thread so we can clean it safely. This is
+ // the case at least for ui_tests running under Valgrind in
+ // bug 16076.
+ VLOG(1) << "~SettingGetterImplGSettings: releasing gsettings client";
+ ShutDown();
+ } else {
+ LOG(WARNING) << "~SettingGetterImplGSettings: leaking gsettings client";
+ client_ = NULL;
+ }
+ }
+ DCHECK(!client_);
+ }
+
+ bool SchemaExists(const char* schema_name) {
+ const gchar* const* schemas = libgio_loader_.g_settings_list_schemas();
+ while (*schemas) {
+ if (strcmp(schema_name, static_cast<const char*>(*schemas)) == 0)
+ return true;
+ schemas++;
+ }
+ return false;
+ }
+
+ // LoadAndCheckVersion() must be called *before* Init()!
+ bool LoadAndCheckVersion(base::Environment* env);
+
+ virtual bool Init(base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::MessageLoopForIO* file_loop) OVERRIDE {
+ DCHECK(glib_thread_task_runner->BelongsToCurrentThread());
+ DCHECK(!client_);
+ DCHECK(!task_runner_.get());
+
+ if (!SchemaExists("org.gnome.system.proxy") ||
+ !(client_ = libgio_loader_.g_settings_new("org.gnome.system.proxy"))) {
+ // It's not clear whether/when this can return NULL.
+ LOG(ERROR) << "Unable to create a gsettings client";
+ return false;
+ }
+ task_runner_ = glib_thread_task_runner;
+ // We assume these all work if the above call worked.
+ http_client_ = libgio_loader_.g_settings_get_child(client_, "http");
+ https_client_ = libgio_loader_.g_settings_get_child(client_, "https");
+ ftp_client_ = libgio_loader_.g_settings_get_child(client_, "ftp");
+ socks_client_ = libgio_loader_.g_settings_get_child(client_, "socks");
+ DCHECK(http_client_ && https_client_ && ftp_client_ && socks_client_);
+ return true;
+ }
+
+ virtual void ShutDown() OVERRIDE {
+ if (client_) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ // This also disables gsettings notifications.
+ g_object_unref(socks_client_);
+ g_object_unref(ftp_client_);
+ g_object_unref(https_client_);
+ g_object_unref(http_client_);
+ g_object_unref(client_);
+ // We only need to null client_ because it's the only one that we check.
+ client_ = NULL;
+ task_runner_ = NULL;
+ }
+ }
+
+ virtual bool SetUpNotifications(
+ ProxyConfigServiceLinux::Delegate* delegate) OVERRIDE {
+ DCHECK(client_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ notify_delegate_ = delegate;
+ // We could watch for the change-event signal instead of changed, but
+ // since we have to watch more than one object, we'd still have to
+ // debounce change notifications. This is conceptually simpler.
+ g_signal_connect(G_OBJECT(client_), "changed",
+ G_CALLBACK(OnGSettingsChangeNotification), this);
+ g_signal_connect(G_OBJECT(http_client_), "changed",
+ G_CALLBACK(OnGSettingsChangeNotification), this);
+ g_signal_connect(G_OBJECT(https_client_), "changed",
+ G_CALLBACK(OnGSettingsChangeNotification), this);
+ g_signal_connect(G_OBJECT(ftp_client_), "changed",
+ G_CALLBACK(OnGSettingsChangeNotification), this);
+ g_signal_connect(G_OBJECT(socks_client_), "changed",
+ G_CALLBACK(OnGSettingsChangeNotification), this);
+ // Simulate a change to avoid possibly losing updates before this point.
+ OnChangeNotification();
+ return true;
+ }
+
+ virtual base::SingleThreadTaskRunner* GetNotificationTaskRunner() OVERRIDE {
+ return task_runner_.get();
+ }
+
+ virtual ProxyConfigSource GetConfigSource() OVERRIDE {
+ return PROXY_CONFIG_SOURCE_GSETTINGS;
+ }
+
+ virtual bool GetString(StringSetting key, std::string* result) OVERRIDE {
+ DCHECK(client_);
+ switch (key) {
+ case PROXY_MODE:
+ return GetStringByPath(client_, "mode", result);
+ case PROXY_AUTOCONF_URL:
+ return GetStringByPath(client_, "autoconfig-url", result);
+ case PROXY_HTTP_HOST:
+ return GetStringByPath(http_client_, "host", result);
+ case PROXY_HTTPS_HOST:
+ return GetStringByPath(https_client_, "host", result);
+ case PROXY_FTP_HOST:
+ return GetStringByPath(ftp_client_, "host", result);
+ case PROXY_SOCKS_HOST:
+ return GetStringByPath(socks_client_, "host", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetBool(BoolSetting key, bool* result) OVERRIDE {
+ DCHECK(client_);
+ switch (key) {
+ case PROXY_USE_HTTP_PROXY:
+ // Although there is an "enabled" boolean in http_client_, it is not set
+ // to true by the proxy config utility. We ignore it and return false.
+ return false;
+ case PROXY_USE_SAME_PROXY:
+ // Similarly, although there is a "use-same-proxy" boolean in client_,
+ // it is never set to false by the proxy config utility. We ignore it.
+ return false;
+ case PROXY_USE_AUTHENTICATION:
+ // There is also no way to set this in the proxy config utility, but it
+ // doesn't hurt us to get the actual setting (unlike the two above).
+ return GetBoolByPath(http_client_, "use-authentication", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetInt(IntSetting key, int* result) OVERRIDE {
+ DCHECK(client_);
+ switch (key) {
+ case PROXY_HTTP_PORT:
+ return GetIntByPath(http_client_, "port", result);
+ case PROXY_HTTPS_PORT:
+ return GetIntByPath(https_client_, "port", result);
+ case PROXY_FTP_PORT:
+ return GetIntByPath(ftp_client_, "port", result);
+ case PROXY_SOCKS_PORT:
+ return GetIntByPath(socks_client_, "port", result);
+ }
+ return false; // Placate compiler.
+ }
+ virtual bool GetStringList(StringListSetting key,
+ std::vector<std::string>* result) OVERRIDE {
+ DCHECK(client_);
+ switch (key) {
+ case PROXY_IGNORE_HOSTS:
+ return GetStringListByPath(client_, "ignore-hosts", result);
+ }
+ return false; // Placate compiler.
+ }
+
+ virtual bool BypassListIsReversed() OVERRIDE {
+ // This is a KDE-specific setting.
+ return false;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() OVERRIDE {
+ return false;
+ }
+
+ private:
+ bool GetStringByPath(GSettings* client, const char* key,
+ std::string* result) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ gchar* value = libgio_loader_.g_settings_get_string(client, key);
+ if (!value)
+ return false;
+ *result = value;
+ g_free(value);
+ return true;
+ }
+ bool GetBoolByPath(GSettings* client, const char* key, bool* result) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ *result = static_cast<bool>(
+ libgio_loader_.g_settings_get_boolean(client, key));
+ return true;
+ }
+ bool GetIntByPath(GSettings* client, const char* key, int* result) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ *result = libgio_loader_.g_settings_get_int(client, key);
+ return true;
+ }
+ bool GetStringListByPath(GSettings* client, const char* key,
+ std::vector<std::string>* result) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ gchar** list = libgio_loader_.g_settings_get_strv(client, key);
+ if (!list)
+ return false;
+ for (size_t i = 0; list[i]; ++i) {
+ result->push_back(static_cast<char*>(list[i]));
+ g_free(list[i]);
+ }
+ g_free(list);
+ return true;
+ }
+
+ // This is the callback from the debounce timer.
+ void OnDebouncedNotification() {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+ CHECK(notify_delegate_);
+ // Forward to a method on the proxy config service delegate object.
+ notify_delegate_->OnCheckProxyConfigSettings();
+ }
+
+ void OnChangeNotification() {
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ debounce_timer_.Stop();
+ debounce_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds),
+ this, &SettingGetterImplGSettings::OnDebouncedNotification);
+ }
+
+ // gsettings notification callback, dispatched on the default glib main loop.
+ static void OnGSettingsChangeNotification(GSettings* client, gchar* key,
+ gpointer user_data) {
+ VLOG(1) << "gsettings change notification for key " << key;
+ // We don't track which key has changed, just that something did change.
+ SettingGetterImplGSettings* setting_getter =
+ reinterpret_cast<SettingGetterImplGSettings*>(user_data);
+ setting_getter->OnChangeNotification();
+ }
+
+ GSettings* client_;
+ GSettings* http_client_;
+ GSettings* https_client_;
+ GSettings* ftp_client_;
+ GSettings* socks_client_;
+ ProxyConfigServiceLinux::Delegate* notify_delegate_;
+ base::OneShotTimer<SettingGetterImplGSettings> debounce_timer_;
+
+ // Task runner for the thread that we make gsettings calls on. It should
+ // be the UI thread and all our methods should be called on this
+ // thread. Only for assertions.
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+ LibGioLoader libgio_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingGetterImplGSettings);
+};
+
+bool SettingGetterImplGSettings::LoadAndCheckVersion(
+ base::Environment* env) {
+ // LoadAndCheckVersion() must be called *before* Init()!
+ DCHECK(!client_);
+
+ // The APIs to query gsettings were introduced after the minimum glib
+ // version we target, so we can't link directly against them. We load them
+ // dynamically at runtime, and if they don't exist, return false here. (We
+ // support linking directly via gyp flags though.) Additionally, even when
+ // they are present, we do two additional checks to make sure we should use
+ // them and not gconf. First, we attempt to load the schema for proxy
+ // settings. Second, we check for the program that was used in older
+ // versions of GNOME to configure proxy settings, and return false if it
+ // exists. Some distributions (e.g. Ubuntu 11.04) have the API and schema
+ // but don't use gsettings for proxy settings, but they do have the old
+ // binary, so we detect these systems that way.
+
+ {
+ // TODO(phajdan.jr): Redesign the code to load library on different thread.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Try also without .0 at the end; on some systems this may be required.
+ if (!libgio_loader_.Load("libgio-2.0.so.0") &&
+ !libgio_loader_.Load("libgio-2.0.so")) {
+ VLOG(1) << "Cannot load gio library. Will fall back to gconf.";
+ return false;
+ }
+ }
+
+ GSettings* client;
+ if (!SchemaExists("org.gnome.system.proxy") ||
+ !(client = libgio_loader_.g_settings_new("org.gnome.system.proxy"))) {
+ VLOG(1) << "Cannot create gsettings client. Will fall back to gconf.";
+ return false;
+ }
+ g_object_unref(client);
+
+ std::string path;
+ if (!env->GetVar("PATH", &path)) {
+ LOG(ERROR) << "No $PATH variable. Assuming no gnome-network-properties.";
+ } else {
+ // Yes, we're on the UI thread. Yes, we're accessing the file system.
+ // Sadly, we don't have much choice. We need the proxy settings and we
+ // need them now, and to figure out where to get them, we have to check
+ // for this binary. See http://crbug.com/69057 for additional details.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ std::vector<std::string> paths;
+ Tokenize(path, ":", &paths);
+ for (size_t i = 0; i < paths.size(); ++i) {
+ base::FilePath file(paths[i]);
+ if (base::PathExists(file.Append("gnome-network-properties"))) {
+ VLOG(1) << "Found gnome-network-properties. Will fall back to gconf.";
+ return false;
+ }
+ }
+ }
+
+ VLOG(1) << "All gsettings tests OK. Will get proxy config from gsettings.";
+ return true;
+}
+#endif // defined(USE_GIO)
+
+// This is the KDE version that reads kioslaverc and simulates gconf.
+// Doing this allows the main Delegate code, as well as the unit tests
+// for it, to stay the same - and the settings map fairly well besides.
+class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter,
+ public base::MessagePumpLibevent::Watcher {
+ public:
+ explicit SettingGetterImplKDE(base::Environment* env_var_getter)
+ : inotify_fd_(-1), notify_delegate_(NULL), indirect_manual_(false),
+ auto_no_pac_(false), reversed_bypass_list_(false),
+ env_var_getter_(env_var_getter), file_loop_(NULL) {
+ // This has to be called on the UI thread (http://crbug.com/69057).
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Derive the location of the kde config dir from the environment.
+ std::string home;
+ if (env_var_getter->GetVar("KDEHOME", &home) && !home.empty()) {
+ // $KDEHOME is set. Use it unconditionally.
+ kde_config_dir_ = KDEHomeToConfigPath(base::FilePath(home));
+ } else {
+ // $KDEHOME is unset. Try to figure out what to use. This seems to be
+ // the common case on most distributions.
+ if (!env_var_getter->GetVar(base::env_vars::kHome, &home))
+ // User has no $HOME? Give up. Later we'll report the failure.
+ return;
+ if (base::nix::GetDesktopEnvironment(env_var_getter) ==
+ base::nix::DESKTOP_ENVIRONMENT_KDE3) {
+ // KDE3 always uses .kde for its configuration.
+ base::FilePath kde_path = base::FilePath(home).Append(".kde");
+ kde_config_dir_ = KDEHomeToConfigPath(kde_path);
+ } else {
+ // Some distributions patch KDE4 to use .kde4 instead of .kde, so that
+ // both can be installed side-by-side. Sadly they don't all do this, and
+ // they don't always do this: some distributions have started switching
+ // back as well. So if there is a .kde4 directory, check the timestamps
+ // of the config directories within and use the newest one.
+ // Note that we should currently be running in the UI thread, because in
+ // the gconf version, that is the only thread that can access the proxy
+ // settings (a gconf restriction). As noted below, the initial read of
+ // the proxy settings will be done in this thread anyway, so we check
+ // for .kde4 here in this thread as well.
+ base::FilePath kde3_path = base::FilePath(home).Append(".kde");
+ base::FilePath kde3_config = KDEHomeToConfigPath(kde3_path);
+ base::FilePath kde4_path = base::FilePath(home).Append(".kde4");
+ base::FilePath kde4_config = KDEHomeToConfigPath(kde4_path);
+ bool use_kde4 = false;
+ if (base::DirectoryExists(kde4_path)) {
+ base::PlatformFileInfo kde3_info;
+ base::PlatformFileInfo kde4_info;
+ if (file_util::GetFileInfo(kde4_config, &kde4_info)) {
+ if (file_util::GetFileInfo(kde3_config, &kde3_info)) {
+ use_kde4 = kde4_info.last_modified >= kde3_info.last_modified;
+ } else {
+ use_kde4 = true;
+ }
+ }
+ }
+ if (use_kde4) {
+ kde_config_dir_ = KDEHomeToConfigPath(kde4_path);
+ } else {
+ kde_config_dir_ = KDEHomeToConfigPath(kde3_path);
+ }
+ }
+ }
+ }
+
+ virtual ~SettingGetterImplKDE() {
+ // inotify_fd_ should have been closed before now, from
+ // Delegate::OnDestroy(), while running on the file thread. However
+ // on exiting the process, it may happen that Delegate::OnDestroy()
+ // task is left pending on the file loop after the loop was quit,
+ // and pending tasks may then be deleted without being run.
+ // Here in the KDE version, we can safely close the file descriptor
+ // anyway. (Not that it really matters; the process is exiting.)
+ if (inotify_fd_ >= 0)
+ ShutDown();
+ DCHECK(inotify_fd_ < 0);
+ }
+
+ virtual bool Init(base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::MessageLoopForIO* file_loop) OVERRIDE {
+ // This has to be called on the UI thread (http://crbug.com/69057).
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ DCHECK(inotify_fd_ < 0);
+ inotify_fd_ = inotify_init();
+ if (inotify_fd_ < 0) {
+ PLOG(ERROR) << "inotify_init failed";
+ return false;
+ }
+ int flags = fcntl(inotify_fd_, F_GETFL);
+ if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
+ PLOG(ERROR) << "fcntl failed";
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ return false;
+ }
+ file_loop_ = file_loop;
+ // The initial read is done on the current thread, not |file_loop_|,
+ // since we will need to have it for SetUpAndFetchInitialConfig().
+ UpdateCachedSettings();
+ return true;
+ }
+
+ virtual void ShutDown() OVERRIDE {
+ if (inotify_fd_ >= 0) {
+ ResetCachedSettings();
+ inotify_watcher_.StopWatchingFileDescriptor();
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ }
+ }
+
+ virtual bool SetUpNotifications(
+ ProxyConfigServiceLinux::Delegate* delegate) OVERRIDE {
+ DCHECK(inotify_fd_ >= 0);
+ DCHECK(base::MessageLoop::current() == file_loop_);
+ // We can't just watch the kioslaverc file directly, since KDE will write
+ // a new copy of it and then rename it whenever settings are changed and
+ // inotify watches inodes (so we'll be watching the old deleted file after
+ // the first change, and it will never change again). So, we watch the
+ // directory instead. We then act only on changes to the kioslaverc entry.
+ if (inotify_add_watch(inotify_fd_, kde_config_dir_.value().c_str(),
+ IN_MODIFY | IN_MOVED_TO) < 0)
+ return false;
+ notify_delegate_ = delegate;
+ if (!file_loop_->WatchFileDescriptor(inotify_fd_,
+ true,
+ base::MessageLoopForIO::WATCH_READ,
+ &inotify_watcher_,
+ this))
+ return false;
+ // Simulate a change to avoid possibly losing updates before this point.
+ OnChangeNotification();
+ return true;
+ }
+
+ virtual base::SingleThreadTaskRunner* GetNotificationTaskRunner() OVERRIDE {
+ return file_loop_ ? file_loop_->message_loop_proxy().get() : NULL;
+ }
+
+ // Implement base::MessagePumpLibevent::Watcher.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+ DCHECK_EQ(fd, inotify_fd_);
+ DCHECK(base::MessageLoop::current() == file_loop_);
+ OnChangeNotification();
+ }
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual ProxyConfigSource GetConfigSource() OVERRIDE {
+ return PROXY_CONFIG_SOURCE_KDE;
+ }
+
+ virtual bool GetString(StringSetting key, std::string* result) OVERRIDE {
+ string_map_type::iterator it = string_table_.find(key);
+ if (it == string_table_.end())
+ return false;
+ *result = it->second;
+ return true;
+ }
+ virtual bool GetBool(BoolSetting key, bool* result) OVERRIDE {
+ // We don't ever have any booleans.
+ return false;
+ }
+ virtual bool GetInt(IntSetting key, int* result) OVERRIDE {
+ // We don't ever have any integers. (See AddProxy() below about ports.)
+ return false;
+ }
+ virtual bool GetStringList(StringListSetting key,
+ std::vector<std::string>* result) OVERRIDE {
+ strings_map_type::iterator it = strings_table_.find(key);
+ if (it == strings_table_.end())
+ return false;
+ *result = it->second;
+ return true;
+ }
+
+ virtual bool BypassListIsReversed() OVERRIDE {
+ return reversed_bypass_list_;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() OVERRIDE {
+ return true;
+ }
+
+ private:
+ void ResetCachedSettings() {
+ string_table_.clear();
+ strings_table_.clear();
+ indirect_manual_ = false;
+ auto_no_pac_ = false;
+ reversed_bypass_list_ = false;
+ }
+
+ base::FilePath KDEHomeToConfigPath(const base::FilePath& kde_home) {
+ return kde_home.Append("share").Append("config");
+ }
+
+ void AddProxy(StringSetting host_key, const std::string& value) {
+ if (value.empty() || value.substr(0, 3) == "//:")
+ // No proxy.
+ return;
+ size_t space = value.find(' ');
+ if (space != std::string::npos) {
+ // Newer versions of KDE use a space rather than a colon to separate the
+ // port number from the hostname. If we find this, we need to convert it.
+ std::string fixed = value;
+ fixed[space] = ':';
+ string_table_[host_key] = fixed;
+ } else {
+ // We don't need to parse the port number out; GetProxyFromSettings()
+ // would only append it right back again. So we just leave the port
+ // number right in the host string.
+ string_table_[host_key] = value;
+ }
+ }
+
+ void AddHostList(StringListSetting key, const std::string& value) {
+ std::vector<std::string> tokens;
+ base::StringTokenizer tk(value, ", ");
+ while (tk.GetNext()) {
+ std::string token = tk.token();
+ if (!token.empty())
+ tokens.push_back(token);
+ }
+ strings_table_[key] = tokens;
+ }
+
+ void AddKDESetting(const std::string& key, const std::string& value) {
+ if (key == "ProxyType") {
+ const char* mode = "none";
+ indirect_manual_ = false;
+ auto_no_pac_ = false;
+ int int_value;
+ base::StringToInt(value, &int_value);
+ switch (int_value) {
+ case 0: // No proxy, or maybe kioslaverc syntax error.
+ break;
+ case 1: // Manual configuration.
+ mode = "manual";
+ break;
+ case 2: // PAC URL.
+ mode = "auto";
+ break;
+ case 3: // WPAD.
+ mode = "auto";
+ auto_no_pac_ = true;
+ break;
+ case 4: // Indirect manual via environment variables.
+ mode = "manual";
+ indirect_manual_ = true;
+ break;
+ }
+ string_table_[PROXY_MODE] = mode;
+ } else if (key == "Proxy Config Script") {
+ string_table_[PROXY_AUTOCONF_URL] = value;
+ } else if (key == "httpProxy") {
+ AddProxy(PROXY_HTTP_HOST, value);
+ } else if (key == "httpsProxy") {
+ AddProxy(PROXY_HTTPS_HOST, value);
+ } else if (key == "ftpProxy") {
+ AddProxy(PROXY_FTP_HOST, value);
+ } else if (key == "socksProxy") {
+ // Older versions of KDE configure SOCKS in a weird way involving
+ // LD_PRELOAD and a library that intercepts network calls to SOCKSify
+ // them. We don't support it. KDE 4.8 added a proper SOCKS setting.
+ AddProxy(PROXY_SOCKS_HOST, value);
+ } else if (key == "ReversedException") {
+ // We count "true" or any nonzero number as true, otherwise false.
+ // Note that if the value is not actually numeric StringToInt()
+ // will return 0, which we count as false.
+ int int_value;
+ base::StringToInt(value, &int_value);
+ reversed_bypass_list_ = (value == "true" || int_value);
+ } else if (key == "NoProxyFor") {
+ AddHostList(PROXY_IGNORE_HOSTS, value);
+ } else if (key == "AuthMode") {
+ // Check for authentication, just so we can warn.
+ int mode;
+ base::StringToInt(value, &mode);
+ if (mode) {
+ // ProxyConfig does not support authentication parameters, but
+ // Chrome will prompt for the password later. So we ignore this.
+ LOG(WARNING) <<
+ "Proxy authentication parameters ignored, see bug 16709";
+ }
+ }
+ }
+
+ void ResolveIndirect(StringSetting key) {
+ string_map_type::iterator it = string_table_.find(key);
+ if (it != string_table_.end()) {
+ std::string value;
+ if (env_var_getter_->GetVar(it->second.c_str(), &value))
+ it->second = value;
+ else
+ string_table_.erase(it);
+ }
+ }
+
+ void ResolveIndirectList(StringListSetting key) {
+ strings_map_type::iterator it = strings_table_.find(key);
+ if (it != strings_table_.end()) {
+ std::string value;
+ if (!it->second.empty() &&
+ env_var_getter_->GetVar(it->second[0].c_str(), &value))
+ AddHostList(key, value);
+ else
+ strings_table_.erase(it);
+ }
+ }
+
+ // The settings in kioslaverc could occur in any order, but some affect
+ // others. Rather than read the whole file in and then query them in an
+ // order that allows us to handle that, we read the settings in whatever
+ // order they occur and do any necessary tweaking after we finish.
+ void ResolveModeEffects() {
+ if (indirect_manual_) {
+ ResolveIndirect(PROXY_HTTP_HOST);
+ ResolveIndirect(PROXY_HTTPS_HOST);
+ ResolveIndirect(PROXY_FTP_HOST);
+ ResolveIndirectList(PROXY_IGNORE_HOSTS);
+ }
+ if (auto_no_pac_) {
+ // Remove the PAC URL; we're not supposed to use it.
+ string_table_.erase(PROXY_AUTOCONF_URL);
+ }
+ }
+
+ // Reads kioslaverc one line at a time and calls AddKDESetting() to add
+ // each relevant name-value pair to the appropriate value table.
+ void UpdateCachedSettings() {
+ base::FilePath kioslaverc = kde_config_dir_.Append("kioslaverc");
+ file_util::ScopedFILE input(file_util::OpenFile(kioslaverc, "r"));
+ if (!input.get())
+ return;
+ ResetCachedSettings();
+ bool in_proxy_settings = false;
+ bool line_too_long = false;
+ char line[BUFFER_SIZE];
+ // fgets() will return NULL on EOF or error.
+ while (fgets(line, sizeof(line), input.get())) {
+ // fgets() guarantees the line will be properly terminated.
+ size_t length = strlen(line);
+ if (!length)
+ continue;
+ // This should be true even with CRLF endings.
+ if (line[length - 1] != '\n') {
+ line_too_long = true;
+ continue;
+ }
+ if (line_too_long) {
+ // The previous line had no line ending, but this done does. This is
+ // the end of the line that was too long, so warn here and skip it.
+ LOG(WARNING) << "skipped very long line in " << kioslaverc.value();
+ line_too_long = false;
+ continue;
+ }
+ // Remove the LF at the end, and the CR if there is one.
+ line[--length] = '\0';
+ if (length && line[length - 1] == '\r')
+ line[--length] = '\0';
+ // Now parse the line.
+ if (line[0] == '[') {
+ // Switching sections. All we care about is whether this is
+ // the (a?) proxy settings section, for both KDE3 and KDE4.
+ in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16);
+ } else if (in_proxy_settings) {
+ // A regular line, in the (a?) proxy settings section.
+ char* split = strchr(line, '=');
+ // Skip this line if it does not contain an = sign.
+ if (!split)
+ continue;
+ // Split the line on the = and advance |split|.
+ *(split++) = 0;
+ std::string key = line;
+ std::string value = split;
+ TrimWhitespaceASCII(key, TRIM_ALL, &key);
+ TrimWhitespaceASCII(value, TRIM_ALL, &value);
+ // Skip this line if the key name is empty.
+ if (key.empty())
+ continue;
+ // Is the value name localized?
+ if (key[key.length() - 1] == ']') {
+ // Find the matching bracket.
+ length = key.rfind('[');
+ // Skip this line if the localization indicator is malformed.
+ if (length == std::string::npos)
+ continue;
+ // Trim the localization indicator off.
+ key.resize(length);
+ // Remove any resulting trailing whitespace.
+ TrimWhitespaceASCII(key, TRIM_TRAILING, &key);
+ // Skip this line if the key name is now empty.
+ if (key.empty())
+ continue;
+ }
+ // Now fill in the tables.
+ AddKDESetting(key, value);
+ }
+ }
+ if (ferror(input.get()))
+ LOG(ERROR) << "error reading " << kioslaverc.value();
+ ResolveModeEffects();
+ }
+
+ // This is the callback from the debounce timer.
+ void OnDebouncedNotification() {
+ DCHECK(base::MessageLoop::current() == file_loop_);
+ VLOG(1) << "inotify change notification for kioslaverc";
+ UpdateCachedSettings();
+ CHECK(notify_delegate_);
+ // Forward to a method on the proxy config service delegate object.
+ notify_delegate_->OnCheckProxyConfigSettings();
+ }
+
+ // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads
+ // from the inotify file descriptor and starts up a debounce timer if
+ // an event for kioslaverc is seen.
+ void OnChangeNotification() {
+ DCHECK_GE(inotify_fd_, 0);
+ DCHECK(base::MessageLoop::current() == file_loop_);
+ char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4];
+ bool kioslaverc_touched = false;
+ ssize_t r;
+ while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) {
+ // inotify returns variable-length structures, which is why we have
+ // this strange-looking loop instead of iterating through an array.
+ char* event_ptr = event_buf;
+ while (event_ptr < event_buf + r) {
+ inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr);
+ // The kernel always feeds us whole events.
+ CHECK_LE(event_ptr + sizeof(inotify_event), event_buf + r);
+ CHECK_LE(event->name + event->len, event_buf + r);
+ if (!strcmp(event->name, "kioslaverc"))
+ kioslaverc_touched = true;
+ // Advance the pointer just past the end of the filename.
+ event_ptr = event->name + event->len;
+ }
+ // We keep reading even if |kioslaverc_touched| is true to drain the
+ // inotify event queue.
+ }
+ if (!r)
+ // Instead of returning -1 and setting errno to EINVAL if there is not
+ // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the
+ // new behavior (EINVAL) so we can reuse the code below.
+ errno = EINVAL;
+ if (errno != EAGAIN) {
+ PLOG(WARNING) << "error reading inotify file descriptor";
+ if (errno == EINVAL) {
+ // Our buffer is not large enough to read the next event. This should
+ // not happen (because its size is calculated to always be sufficiently
+ // large), but if it does we'd warn continuously since |inotify_fd_|
+ // would be forever ready to read. Close it and stop watching instead.
+ LOG(ERROR) << "inotify failure; no longer watching kioslaverc!";
+ inotify_watcher_.StopWatchingFileDescriptor();
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ }
+ }
+ if (kioslaverc_touched) {
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ debounce_timer_.Stop();
+ debounce_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(
+ kDebounceTimeoutMilliseconds), this,
+ &SettingGetterImplKDE::OnDebouncedNotification);
+ }
+ }
+
+ typedef std::map<StringSetting, std::string> string_map_type;
+ typedef std::map<StringListSetting,
+ std::vector<std::string> > strings_map_type;
+
+ int inotify_fd_;
+ base::MessagePumpLibevent::FileDescriptorWatcher inotify_watcher_;
+ ProxyConfigServiceLinux::Delegate* notify_delegate_;
+ base::OneShotTimer<SettingGetterImplKDE> debounce_timer_;
+ base::FilePath kde_config_dir_;
+ bool indirect_manual_;
+ bool auto_no_pac_;
+ bool reversed_bypass_list_;
+ // We don't own |env_var_getter_|. It's safe to hold a pointer to it, since
+ // both it and us are owned by ProxyConfigServiceLinux::Delegate, and have the
+ // same lifetime.
+ base::Environment* env_var_getter_;
+
+ // We cache these settings whenever we re-read the kioslaverc file.
+ string_map_type string_table_;
+ strings_map_type strings_table_;
+
+ // Message loop of the file thread, for reading kioslaverc. If NULL,
+ // just read it directly (for testing). We also handle inotify events
+ // on this thread.
+ base::MessageLoopForIO* file_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(SettingGetterImplKDE);
+};
+
+} // namespace
+
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromSettings(
+ SettingGetter::StringSetting host_key,
+ ProxyServer* result_server) {
+ std::string host;
+ if (!setting_getter_->GetString(host_key, &host) || host.empty()) {
+ // Unset or empty.
+ return false;
+ }
+ // Check for an optional port.
+ int port = 0;
+ SettingGetter::IntSetting port_key =
+ SettingGetter::HostSettingToPortSetting(host_key);
+ setting_getter_->GetInt(port_key, &port);
+ if (port != 0) {
+ // If a port is set and non-zero:
+ host += ":" + base::IntToString(port);
+ }
+
+ // gconf settings do not appear to distinguish between SOCKS version. We
+ // default to version 5. For more information on this policy decision, see:
+ // http://code.google.com/p/chromium/issues/detail?id=55912#c2
+ ProxyServer::Scheme scheme = (host_key == SettingGetter::PROXY_SOCKS_HOST) ?
+ ProxyServer::SCHEME_SOCKS5 : ProxyServer::SCHEME_HTTP;
+ host = FixupProxyHostScheme(scheme, host);
+ ProxyServer proxy_server = ProxyServer::FromURI(host,
+ ProxyServer::SCHEME_HTTP);
+ if (proxy_server.is_valid()) {
+ *result_server = proxy_server;
+ return true;
+ }
+ return false;
+}
+
+bool ProxyConfigServiceLinux::Delegate::GetConfigFromSettings(
+ ProxyConfig* config) {
+ std::string mode;
+ if (!setting_getter_->GetString(SettingGetter::PROXY_MODE, &mode)) {
+ // We expect this to always be set, so if we don't see it then we
+ // probably have a gconf/gsettings problem, and so we don't have a valid
+ // proxy config.
+ return false;
+ }
+ if (mode == "none") {
+ // Specifically specifies no proxy.
+ return true;
+ }
+
+ if (mode == "auto") {
+ // Automatic proxy config.
+ std::string pac_url_str;
+ if (setting_getter_->GetString(SettingGetter::PROXY_AUTOCONF_URL,
+ &pac_url_str)) {
+ if (!pac_url_str.empty()) {
+ // If the PAC URL is actually a file path, then put file:// in front.
+ if (pac_url_str[0] == '/')
+ pac_url_str = "file://" + pac_url_str;
+ GURL pac_url(pac_url_str);
+ if (!pac_url.is_valid())
+ return false;
+ config->set_pac_url(pac_url);
+ return true;
+ }
+ }
+ config->set_auto_detect(true);
+ return true;
+ }
+
+ if (mode != "manual") {
+ // Mode is unrecognized.
+ return false;
+ }
+ bool use_http_proxy;
+ if (setting_getter_->GetBool(SettingGetter::PROXY_USE_HTTP_PROXY,
+ &use_http_proxy)
+ && !use_http_proxy) {
+ // Another master switch for some reason. If set to false, then no
+ // proxy. But we don't panic if the key doesn't exist.
+ return true;
+ }
+
+ bool same_proxy = false;
+ // Indicates to use the http proxy for all protocols. This one may
+ // not exist (presumably on older versions); we assume false in that
+ // case.
+ setting_getter_->GetBool(SettingGetter::PROXY_USE_SAME_PROXY,
+ &same_proxy);
+
+ ProxyServer proxy_for_http;
+ ProxyServer proxy_for_https;
+ ProxyServer proxy_for_ftp;
+ ProxyServer socks_proxy; // (socks)
+
+ // This counts how many of the above ProxyServers were defined and valid.
+ size_t num_proxies_specified = 0;
+
+ // Extract the per-scheme proxies. If we failed to parse it, or no proxy was
+ // specified for the scheme, then the resulting ProxyServer will be invalid.
+ if (GetProxyFromSettings(SettingGetter::PROXY_HTTP_HOST, &proxy_for_http))
+ num_proxies_specified++;
+ if (GetProxyFromSettings(SettingGetter::PROXY_HTTPS_HOST, &proxy_for_https))
+ num_proxies_specified++;
+ if (GetProxyFromSettings(SettingGetter::PROXY_FTP_HOST, &proxy_for_ftp))
+ num_proxies_specified++;
+ if (GetProxyFromSettings(SettingGetter::PROXY_SOCKS_HOST, &socks_proxy))
+ num_proxies_specified++;
+
+ if (same_proxy) {
+ if (proxy_for_http.is_valid()) {
+ // Use the http proxy for all schemes.
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxies.SetSingleProxyServer(proxy_for_http);
+ }
+ } else if (num_proxies_specified > 0) {
+ if (socks_proxy.is_valid() && num_proxies_specified == 1) {
+ // If the only proxy specified was for SOCKS, use it for all schemes.
+ config->proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config->proxy_rules().single_proxies.SetSingleProxyServer(socks_proxy);
+ } else {
+ // Otherwise use the indicated proxies per-scheme.
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxies_for_http.
+ SetSingleProxyServer(proxy_for_http);
+ config->proxy_rules().proxies_for_https.
+ SetSingleProxyServer(proxy_for_https);
+ config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_for_ftp);
+ config->proxy_rules().fallback_proxies.SetSingleProxyServer(socks_proxy);
+ }
+ }
+
+ if (config->proxy_rules().empty()) {
+ // Manual mode but we couldn't parse any rules.
+ return false;
+ }
+
+ // Check for authentication, just so we can warn.
+ bool use_auth = false;
+ setting_getter_->GetBool(SettingGetter::PROXY_USE_AUTHENTICATION,
+ &use_auth);
+ if (use_auth) {
+ // ProxyConfig does not support authentication parameters, but
+ // Chrome will prompt for the password later. So we ignore
+ // /system/http_proxy/*auth* settings.
+ LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
+ }
+
+ // Now the bypass list.
+ std::vector<std::string> ignore_hosts_list;
+ config->proxy_rules().bypass_rules.Clear();
+ if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS,
+ &ignore_hosts_list)) {
+ std::vector<std::string>::const_iterator it(ignore_hosts_list.begin());
+ for (; it != ignore_hosts_list.end(); ++it) {
+ if (setting_getter_->MatchHostsUsingSuffixMatching()) {
+ config->proxy_rules().bypass_rules.
+ AddRuleFromStringUsingSuffixMatching(*it);
+ } else {
+ config->proxy_rules().bypass_rules.AddRuleFromString(*it);
+ }
+ }
+ }
+ // Note that there are no settings with semantics corresponding to
+ // bypass of local names in GNOME. In KDE, "<local>" is supported
+ // as a hostname rule.
+
+ // KDE allows one to reverse the bypass rules.
+ config->proxy_rules().reverse_bypass =
+ setting_getter_->BypassListIsReversed();
+
+ return true;
+}
+
+ProxyConfigServiceLinux::Delegate::Delegate(base::Environment* env_var_getter)
+ : env_var_getter_(env_var_getter) {
+ // Figure out which SettingGetterImpl to use, if any.
+ switch (base::nix::GetDesktopEnvironment(env_var_getter)) {
+ case base::nix::DESKTOP_ENVIRONMENT_GNOME:
+ case base::nix::DESKTOP_ENVIRONMENT_UNITY:
+#if defined(USE_GIO)
+ {
+ scoped_ptr<SettingGetterImplGSettings> gs_getter(
+ new SettingGetterImplGSettings());
+ // We have to load symbols and check the GNOME version in use to decide
+ // if we should use the gsettings getter. See LoadAndCheckVersion().
+ if (gs_getter->LoadAndCheckVersion(env_var_getter))
+ setting_getter_.reset(gs_getter.release());
+ }
+#endif
+#if defined(USE_GCONF)
+ // Fall back on gconf if gsettings is unavailable or incorrect.
+ if (!setting_getter_.get())
+ setting_getter_.reset(new SettingGetterImplGConf());
+#endif
+ break;
+ case base::nix::DESKTOP_ENVIRONMENT_KDE3:
+ case base::nix::DESKTOP_ENVIRONMENT_KDE4:
+ setting_getter_.reset(new SettingGetterImplKDE(env_var_getter));
+ break;
+ case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+ case base::nix::DESKTOP_ENVIRONMENT_OTHER:
+ break;
+ }
+}
+
+ProxyConfigServiceLinux::Delegate::Delegate(
+ base::Environment* env_var_getter, SettingGetter* setting_getter)
+ : env_var_getter_(env_var_getter), setting_getter_(setting_getter) {
+}
+
+void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig(
+ base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::SingleThreadTaskRunner* io_thread_task_runner,
+ base::MessageLoopForIO* file_loop) {
+ // We should be running on the default glib main loop thread right
+ // now. gconf can only be accessed from this thread.
+ DCHECK(glib_thread_task_runner->BelongsToCurrentThread());
+ glib_thread_task_runner_ = glib_thread_task_runner;
+ io_thread_task_runner_ = io_thread_task_runner;
+
+ // If we are passed a NULL |io_thread_task_runner| or |file_loop|,
+ // then we don't set up proxy setting change notifications. This
+ // should not be the usual case but is intended to simplify test
+ // setups.
+ if (!io_thread_task_runner_.get() || !file_loop)
+ VLOG(1) << "Monitoring of proxy setting changes is disabled";
+
+ // Fetch and cache the current proxy config. The config is left in
+ // cached_config_, where GetLatestProxyConfig() running on the IO thread
+ // will expect to find it. This is safe to do because we return
+ // before this ProxyConfigServiceLinux is passed on to
+ // the ProxyService.
+
+ // Note: It would be nice to prioritize environment variables
+ // and only fall back to gconf if env vars were unset. But
+ // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
+ // does so even if the proxy mode is set to auto, which would
+ // mislead us.
+
+ bool got_config = false;
+ if (setting_getter_.get() &&
+ setting_getter_->Init(glib_thread_task_runner, file_loop) &&
+ GetConfigFromSettings(&cached_config_)) {
+ cached_config_.set_id(1); // Mark it as valid.
+ cached_config_.set_source(setting_getter_->GetConfigSource());
+ VLOG(1) << "Obtained proxy settings from "
+ << ProxyConfigSourceToString(cached_config_.source());
+
+ // If gconf proxy mode is "none", meaning direct, then we take
+ // that to be a valid config and will not check environment
+ // variables. The alternative would have been to look for a proxy
+ // whereever we can find one.
+ got_config = true;
+
+ // Keep a copy of the config for use from this thread for
+ // comparison with updated settings when we get notifications.
+ reference_config_ = cached_config_;
+ reference_config_.set_id(1); // Mark it as valid.
+
+ // We only set up notifications if we have IO and file loops available.
+ // We do this after getting the initial configuration so that we don't have
+ // to worry about cancelling it if the initial fetch above fails. Note that
+ // setting up notifications has the side effect of simulating a change, so
+ // that we won't lose any updates that may have happened after the initial
+ // fetch and before setting up notifications. We'll detect the common case
+ // of no changes in OnCheckProxyConfigSettings() (or sooner) and ignore it.
+ if (io_thread_task_runner && file_loop) {
+ scoped_refptr<base::SingleThreadTaskRunner> required_loop =
+ setting_getter_->GetNotificationTaskRunner();
+ if (!required_loop.get() || required_loop->BelongsToCurrentThread()) {
+ // In this case we are already on an acceptable thread.
+ SetUpNotifications();
+ } else {
+ // Post a task to set up notifications. We don't wait for success.
+ required_loop->PostTask(FROM_HERE, base::Bind(
+ &ProxyConfigServiceLinux::Delegate::SetUpNotifications, this));
+ }
+ }
+ }
+
+ if (!got_config) {
+ // We fall back on environment variables.
+ //
+ // Consulting environment variables doesn't need to be done from the
+ // default glib main loop, but it's a tiny enough amount of work.
+ if (GetConfigFromEnv(&cached_config_)) {
+ cached_config_.set_source(PROXY_CONFIG_SOURCE_ENV);
+ cached_config_.set_id(1); // Mark it as valid.
+ VLOG(1) << "Obtained proxy settings from environment variables";
+ }
+ }
+}
+
+// Depending on the SettingGetter in use, this method will be called
+// on either the UI thread (GConf) or the file thread (KDE).
+void ProxyConfigServiceLinux::Delegate::SetUpNotifications() {
+ scoped_refptr<base::SingleThreadTaskRunner> required_loop =
+ setting_getter_->GetNotificationTaskRunner();
+ DCHECK(!required_loop.get() || required_loop->BelongsToCurrentThread());
+ if (!setting_getter_->SetUpNotifications(this))
+ LOG(ERROR) << "Unable to set up proxy configuration change notifications";
+}
+
+void ProxyConfigServiceLinux::Delegate::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ProxyConfigServiceLinux::Delegate::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+ProxyConfigService::ConfigAvailability
+ ProxyConfigServiceLinux::Delegate::GetLatestProxyConfig(
+ ProxyConfig* config) {
+ // This is called from the IO thread.
+ DCHECK(!io_thread_task_runner_.get() ||
+ io_thread_task_runner_->BelongsToCurrentThread());
+
+ // Simply return the last proxy configuration that glib_default_loop
+ // notified us of.
+ if (cached_config_.is_valid()) {
+ *config = cached_config_;
+ } else {
+ *config = ProxyConfig::CreateDirect();
+ config->set_source(PROXY_CONFIG_SOURCE_SYSTEM_FAILED);
+ }
+
+ // We return CONFIG_VALID to indicate that *config was filled in. It is always
+ // going to be available since we initialized eagerly on the UI thread.
+ // TODO(eroman): do lazy initialization instead, so we no longer need
+ // to construct ProxyConfigServiceLinux on the UI thread.
+ // In which case, we may return false here.
+ return CONFIG_VALID;
+}
+
+// Depending on the SettingGetter in use, this method will be called
+// on either the UI thread (GConf) or the file thread (KDE).
+void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() {
+ scoped_refptr<base::SingleThreadTaskRunner> required_loop =
+ setting_getter_->GetNotificationTaskRunner();
+ DCHECK(!required_loop.get() || required_loop->BelongsToCurrentThread());
+ ProxyConfig new_config;
+ bool valid = GetConfigFromSettings(&new_config);
+ if (valid)
+ new_config.set_id(1); // mark it as valid
+
+ // See if it is different from what we had before.
+ if (new_config.is_valid() != reference_config_.is_valid() ||
+ !new_config.Equals(reference_config_)) {
+ // Post a task to the IO thread with the new configuration, so it can
+ // update |cached_config_|.
+ io_thread_task_runner_->PostTask(FROM_HERE, base::Bind(
+ &ProxyConfigServiceLinux::Delegate::SetNewProxyConfig,
+ this, new_config));
+ // Update the thread-private copy in |reference_config_| as well.
+ reference_config_ = new_config;
+ } else {
+ VLOG(1) << "Detected no-op change to proxy settings. Doing nothing.";
+ }
+}
+
+void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
+ const ProxyConfig& new_config) {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+ VLOG(1) << "Proxy configuration changed";
+ cached_config_ = new_config;
+ FOR_EACH_OBSERVER(
+ Observer, observers_,
+ OnProxyConfigChanged(new_config, ProxyConfigService::CONFIG_VALID));
+}
+
+void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
+ if (!setting_getter_.get())
+ return;
+ scoped_refptr<base::SingleThreadTaskRunner> shutdown_loop =
+ setting_getter_->GetNotificationTaskRunner();
+ if (!shutdown_loop.get() || shutdown_loop->BelongsToCurrentThread()) {
+ // Already on the right thread, call directly.
+ // This is the case for the unittests.
+ OnDestroy();
+ } else {
+ // Post to shutdown thread. Note that on browser shutdown, we may quit
+ // this MessageLoop and exit the program before ever running this.
+ shutdown_loop->PostTask(FROM_HERE, base::Bind(
+ &ProxyConfigServiceLinux::Delegate::OnDestroy, this));
+ }
+}
+void ProxyConfigServiceLinux::Delegate::OnDestroy() {
+ scoped_refptr<base::SingleThreadTaskRunner> shutdown_loop =
+ setting_getter_->GetNotificationTaskRunner();
+ DCHECK(!shutdown_loop.get() || shutdown_loop->BelongsToCurrentThread());
+ setting_getter_->ShutDown();
+}
+
+ProxyConfigServiceLinux::ProxyConfigServiceLinux()
+ : delegate_(new Delegate(base::Environment::Create())) {
+}
+
+ProxyConfigServiceLinux::~ProxyConfigServiceLinux() {
+ delegate_->PostDestroyTask();
+}
+
+ProxyConfigServiceLinux::ProxyConfigServiceLinux(
+ base::Environment* env_var_getter)
+ : delegate_(new Delegate(env_var_getter)) {
+}
+
+ProxyConfigServiceLinux::ProxyConfigServiceLinux(
+ base::Environment* env_var_getter, SettingGetter* setting_getter)
+ : delegate_(new Delegate(env_var_getter, setting_getter)) {
+}
+
+void ProxyConfigServiceLinux::AddObserver(Observer* observer) {
+ delegate_->AddObserver(observer);
+}
+
+void ProxyConfigServiceLinux::RemoveObserver(Observer* observer) {
+ delegate_->RemoveObserver(observer);
+}
+
+ProxyConfigService::ConfigAvailability
+ ProxyConfigServiceLinux::GetLatestProxyConfig(ProxyConfig* config) {
+ return delegate_->GetLatestProxyConfig(config);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_linux.h b/chromium/net/proxy/proxy_config_service_linux.h
new file mode 100644
index 00000000000..5dccd9deb77
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_linux.h
@@ -0,0 +1,309 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_LINUX_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_LINUX_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/environment.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_server.h"
+
+namespace base {
+class MessageLoopForIO;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+
+// Implementation of ProxyConfigService that retrieves the system proxy
+// settings from environment variables, gconf, gsettings, or kioslaverc (KDE).
+class NET_EXPORT_PRIVATE ProxyConfigServiceLinux : public ProxyConfigService {
+ public:
+
+ // Forward declaration of Delegate.
+ class Delegate;
+
+ class SettingGetter {
+ public:
+ // Buffer size used in some implementations of this class when reading
+ // files. Defined here so unit tests can construct worst-case inputs.
+ static const size_t BUFFER_SIZE = 512;
+
+ SettingGetter() {}
+ virtual ~SettingGetter() {}
+
+ // Initializes the class: obtains a gconf/gsettings client, or simulates
+ // one, in the concrete implementations. Returns true on success. Must be
+ // called before using other methods, and should be called on the thread
+ // running the glib main loop.
+ // One of |glib_thread_task_runner| and |file_loop| will be used for
+ // gconf/gsettings calls or reading necessary files, depending on the
+ // implementation.
+ virtual bool Init(base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::MessageLoopForIO* file_loop) = 0;
+
+ // Releases the gconf/gsettings client, which clears cached directories and
+ // stops notifications.
+ virtual void ShutDown() = 0;
+
+ // Requests notification of gconf/gsettings changes for proxy
+ // settings. Returns true on success.
+ virtual bool SetUpNotifications(Delegate* delegate) = 0;
+
+ // Returns the message loop for the thread on which this object
+ // handles notifications, and also on which it must be destroyed.
+ // Returns NULL if it does not matter.
+ virtual base::SingleThreadTaskRunner* GetNotificationTaskRunner() = 0;
+
+ // Returns the source of proxy settings.
+ virtual ProxyConfigSource GetConfigSource() = 0;
+
+ // These are all the values that can be fetched. We used to just use the
+ // corresponding paths in gconf for these, but gconf is now obsolete and
+ // in the future we'll be using mostly gsettings/kioslaverc so we
+ // enumerate them instead to avoid unnecessary string operations.
+ enum StringSetting {
+ PROXY_MODE,
+ PROXY_AUTOCONF_URL,
+ PROXY_HTTP_HOST,
+ PROXY_HTTPS_HOST,
+ PROXY_FTP_HOST,
+ PROXY_SOCKS_HOST,
+ };
+ enum BoolSetting {
+ PROXY_USE_HTTP_PROXY,
+ PROXY_USE_SAME_PROXY,
+ PROXY_USE_AUTHENTICATION,
+ };
+ enum IntSetting {
+ PROXY_HTTP_PORT,
+ PROXY_HTTPS_PORT,
+ PROXY_FTP_PORT,
+ PROXY_SOCKS_PORT,
+ };
+ enum StringListSetting {
+ PROXY_IGNORE_HOSTS,
+ };
+
+ // Given a PROXY_*_HOST value, return the corresponding PROXY_*_PORT value.
+ static IntSetting HostSettingToPortSetting(StringSetting host) {
+ switch (host) {
+ case PROXY_HTTP_HOST:
+ return PROXY_HTTP_PORT;
+ case PROXY_HTTPS_HOST:
+ return PROXY_HTTPS_PORT;
+ case PROXY_FTP_HOST:
+ return PROXY_FTP_PORT;
+ case PROXY_SOCKS_HOST:
+ return PROXY_SOCKS_PORT;
+ default:
+ NOTREACHED();
+ return PROXY_HTTP_PORT; // Placate compiler.
+ }
+ }
+
+ // Gets a string type value from the data source and stores it in
+ // |*result|. Returns false if the key is unset or on error. Must only be
+ // called after a successful call to Init(), and not after a failed call
+ // to SetUpNotifications() or after calling Release().
+ virtual bool GetString(StringSetting key, std::string* result) = 0;
+ // Same thing for a bool typed value.
+ virtual bool GetBool(BoolSetting key, bool* result) = 0;
+ // Same for an int typed value.
+ virtual bool GetInt(IntSetting key, int* result) = 0;
+ // And for a string list.
+ virtual bool GetStringList(StringListSetting key,
+ std::vector<std::string>* result) = 0;
+
+ // Returns true if the bypass list should be interpreted as a proxy
+ // whitelist rather than blacklist. (This is KDE-specific.)
+ virtual bool BypassListIsReversed() = 0;
+
+ // Returns true if the bypass rules should be interpreted as
+ // suffix-matching rules.
+ virtual bool MatchHostsUsingSuffixMatching() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SettingGetter);
+ };
+
+ // ProxyConfigServiceLinux is created on the UI thread, and
+ // SetUpAndFetchInitialConfig() is immediately called to synchronously
+ // fetch the original configuration and set up change notifications on
+ // the UI thread.
+ //
+ // Past that point, it is accessed periodically through the
+ // ProxyConfigService interface (GetLatestProxyConfig, AddObserver,
+ // RemoveObserver) from the IO thread.
+ //
+ // Setting change notification callbacks can occur at any time and are
+ // run on either the UI thread (gconf/gsettings) or the file thread
+ // (KDE). The new settings are fetched on that thread, and the resulting
+ // proxy config is posted to the IO thread through
+ // Delegate::SetNewProxyConfig(). We then notify observers on the IO
+ // thread of the configuration change.
+ //
+ // ProxyConfigServiceLinux is deleted from the IO thread.
+ //
+ // The substance of the ProxyConfigServiceLinux implementation is
+ // wrapped in the Delegate ref counted class. On deleting the
+ // ProxyConfigServiceLinux, Delegate::OnDestroy() is posted to either
+ // the UI thread (gconf/gsettings) or the file thread (KDE) where change
+ // notifications will be safely stopped before releasing Delegate.
+
+ class Delegate : public base::RefCountedThreadSafe<Delegate> {
+ public:
+ // Constructor receives env var getter implementation to use, and
+ // takes ownership of it. This is the normal constructor.
+ explicit Delegate(base::Environment* env_var_getter);
+ // Constructor receives setting and env var getter implementations
+ // to use, and takes ownership of them. Used for testing.
+ Delegate(base::Environment* env_var_getter, SettingGetter* setting_getter);
+
+ // Synchronously obtains the proxy configuration. If gconf,
+ // gsettings, or kioslaverc are used, also enables notifications for
+ // setting changes. gconf/gsettings must only be accessed from the
+ // thread running the default glib main loop, and so this method
+ // must be called from the UI thread. The message loop for the IO
+ // thread is specified so that notifications can post tasks to it
+ // (and for assertions). The message loop for the file thread is
+ // used to read any files needed to determine proxy settings.
+ void SetUpAndFetchInitialConfig(
+ base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::SingleThreadTaskRunner* io_thread_task_runner,
+ base::MessageLoopForIO* file_loop);
+
+ // Handler for setting change notifications: fetches a new proxy
+ // configuration from settings, and if this config is different
+ // than what we had before, posts a task to have it stored in
+ // cached_config_.
+ // Left public for simplicity.
+ void OnCheckProxyConfigSettings();
+
+ // Called from IO thread.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+ ProxyConfigService::ConfigAvailability GetLatestProxyConfig(
+ ProxyConfig* config);
+
+ // Posts a call to OnDestroy() to the UI or FILE thread, depending on the
+ // setting getter in use. Called from ProxyConfigServiceLinux's destructor.
+ void PostDestroyTask();
+ // Safely stops change notifications. Posted to either the UI or FILE
+ // thread, depending on the setting getter in use.
+ void OnDestroy();
+
+ private:
+ friend class base::RefCountedThreadSafe<Delegate>;
+
+ ~Delegate();
+
+ // Obtains an environment variable's value. Parses a proxy server
+ // specification from it and puts it in result. Returns true if the
+ // requested variable is defined and the value valid.
+ bool GetProxyFromEnvVarForScheme(const char* variable,
+ ProxyServer::Scheme scheme,
+ ProxyServer* result_server);
+ // As above but with scheme set to HTTP, for convenience.
+ bool GetProxyFromEnvVar(const char* variable, ProxyServer* result_server);
+ // Fills proxy config from environment variables. Returns true if
+ // variables were found and the configuration is valid.
+ bool GetConfigFromEnv(ProxyConfig* config);
+
+ // Obtains host and port config settings and parses a proxy server
+ // specification from it and puts it in result. Returns true if the
+ // requested variable is defined and the value valid.
+ bool GetProxyFromSettings(SettingGetter::StringSetting host_key,
+ ProxyServer* result_server);
+ // Fills proxy config from settings. Returns true if settings were found
+ // and the configuration is valid.
+ bool GetConfigFromSettings(ProxyConfig* config);
+
+ // This method is posted from the UI thread to the IO thread to
+ // carry the new config information.
+ void SetNewProxyConfig(const ProxyConfig& new_config);
+
+ // This method is run on the getter's notification thread.
+ void SetUpNotifications();
+
+ scoped_ptr<base::Environment> env_var_getter_;
+ scoped_ptr<SettingGetter> setting_getter_;
+
+ // Cached proxy configuration, to be returned by
+ // GetLatestProxyConfig. Initially populated from the UI thread, but
+ // afterwards only accessed from the IO thread.
+ ProxyConfig cached_config_;
+
+ // A copy kept on the UI thread of the last seen proxy config, so as
+ // to avoid posting a call to SetNewProxyConfig when we get a
+ // notification but the config has not actually changed.
+ ProxyConfig reference_config_;
+
+ // The task runner for the glib thread, aka main browser thread. This thread
+ // is where we run the glib main loop (see
+ // base/message_loop/message_pump_glib.h). It is the glib default loop in
+ // the sense that it runs the glib default context: as in the context where
+ // sources are added by g_timeout_add and g_idle_add, and returned by
+ // g_main_context_default. gconf uses glib timeouts and idles and possibly
+ // other callbacks that will all be dispatched on this thread. Since gconf
+ // is not thread safe, any use of gconf must be done on the thread running
+ // this loop.
+ scoped_refptr<base::SingleThreadTaskRunner> glib_thread_task_runner_;
+ // Task runner for the IO thread. GetLatestProxyConfig() is called from
+ // the thread running this loop.
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
+
+ ObserverList<Observer> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // Thin wrapper shell around Delegate.
+
+ // Usual constructor
+ ProxyConfigServiceLinux();
+ // For testing: take alternate setting and env var getter implementations.
+ explicit ProxyConfigServiceLinux(base::Environment* env_var_getter);
+ ProxyConfigServiceLinux(base::Environment* env_var_getter,
+ SettingGetter* setting_getter);
+
+ virtual ~ProxyConfigServiceLinux();
+
+ void SetupAndFetchInitialConfig(
+ base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::SingleThreadTaskRunner* io_thread_task_runner,
+ base::MessageLoopForIO* file_loop) {
+ delegate_->SetUpAndFetchInitialConfig(glib_thread_task_runner,
+ io_thread_task_runner, file_loop);
+ }
+ void OnCheckProxyConfigSettings() {
+ delegate_->OnCheckProxyConfigSettings();
+ }
+
+ // ProxyConfigService methods:
+ // Called from IO thread.
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual ProxyConfigService::ConfigAvailability GetLatestProxyConfig(
+ ProxyConfig* config) OVERRIDE;
+
+ private:
+ scoped_refptr<Delegate> delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceLinux);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_LINUX_H_
diff --git a/chromium/net/proxy/proxy_config_service_linux_unittest.cc b/chromium/net/proxy/proxy_config_service_linux_unittest.cc
new file mode 100644
index 00000000000..03bba04d994
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_linux_unittest.cc
@@ -0,0 +1,1617 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_linux.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+namespace {
+
+// Set of values for all environment variables that we might
+// query. NULL represents an unset variable.
+struct EnvVarValues {
+ // The strange capitalization is so that the field matches the
+ // environment variable name exactly.
+ const char *DESKTOP_SESSION, *HOME,
+ *KDEHOME, *KDE_SESSION_VERSION,
+ *auto_proxy, *all_proxy,
+ *http_proxy, *https_proxy, *ftp_proxy,
+ *SOCKS_SERVER, *SOCKS_VERSION,
+ *no_proxy;
+};
+
+// Undo macro pollution from GDK includes (from message_loop.h).
+#undef TRUE
+#undef FALSE
+
+// So as to distinguish between an unset gconf boolean variable and
+// one that is false.
+enum BoolSettingValue {
+ UNSET = 0, TRUE, FALSE
+};
+
+// Set of values for all gconf settings that we might query.
+struct GConfValues {
+ // strings
+ const char *mode, *autoconfig_url,
+ *http_host, *secure_host, *ftp_host, *socks_host;
+ // integers
+ int http_port, secure_port, ftp_port, socks_port;
+ // booleans
+ BoolSettingValue use_proxy, same_proxy, use_auth;
+ // string list
+ std::vector<std::string> ignore_hosts;
+};
+
+// Mapping from a setting name to the location of the corresponding
+// value (inside a EnvVarValues or GConfValues struct).
+template<typename key_type, typename value_type>
+struct SettingsTable {
+ typedef std::map<key_type, value_type*> map_type;
+
+ // Gets the value from its location
+ value_type Get(key_type key) {
+ typename map_type::const_iterator it = settings.find(key);
+ // In case there's a typo or the unittest becomes out of sync.
+ CHECK(it != settings.end()) << "key " << key << " not found";
+ value_type* value_ptr = it->second;
+ return *value_ptr;
+ }
+
+ map_type settings;
+};
+
+class MockEnvironment : public base::Environment {
+ public:
+ MockEnvironment() {
+#define ENTRY(x) table[#x] = &values.x
+ ENTRY(DESKTOP_SESSION);
+ ENTRY(HOME);
+ ENTRY(KDEHOME);
+ ENTRY(KDE_SESSION_VERSION);
+ ENTRY(auto_proxy);
+ ENTRY(all_proxy);
+ ENTRY(http_proxy);
+ ENTRY(https_proxy);
+ ENTRY(ftp_proxy);
+ ENTRY(no_proxy);
+ ENTRY(SOCKS_SERVER);
+ ENTRY(SOCKS_VERSION);
+#undef ENTRY
+ Reset();
+ }
+
+ // Zeroes all environment values.
+ void Reset() {
+ EnvVarValues zero_values = { 0 };
+ values = zero_values;
+ }
+
+ // Begin base::Environment implementation.
+ virtual bool GetVar(const char* variable_name, std::string* result) OVERRIDE {
+ std::map<std::string, const char**>::iterator it =
+ table.find(variable_name);
+ if (it != table.end() && *(it->second) != NULL) {
+ // Note that the variable may be defined but empty.
+ *result = *(it->second);
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool SetVar(const char* variable_name, const std::string& new_value)
+ OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool UnSetVar(const char* variable_name) OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+ // End base::Environment implementation.
+
+ // Intentionally public, for convenience when setting up a test.
+ EnvVarValues values;
+
+ private:
+ std::map<std::string, const char**> table;
+};
+
+class MockSettingGetter
+ : public ProxyConfigServiceLinux::SettingGetter {
+ public:
+ typedef ProxyConfigServiceLinux::SettingGetter SettingGetter;
+ MockSettingGetter() {
+#define ENTRY(key, field) \
+ strings_table.settings[SettingGetter::key] = &values.field
+ ENTRY(PROXY_MODE, mode);
+ ENTRY(PROXY_AUTOCONF_URL, autoconfig_url);
+ ENTRY(PROXY_HTTP_HOST, http_host);
+ ENTRY(PROXY_HTTPS_HOST, secure_host);
+ ENTRY(PROXY_FTP_HOST, ftp_host);
+ ENTRY(PROXY_SOCKS_HOST, socks_host);
+#undef ENTRY
+#define ENTRY(key, field) \
+ ints_table.settings[SettingGetter::key] = &values.field
+ ENTRY(PROXY_HTTP_PORT, http_port);
+ ENTRY(PROXY_HTTPS_PORT, secure_port);
+ ENTRY(PROXY_FTP_PORT, ftp_port);
+ ENTRY(PROXY_SOCKS_PORT, socks_port);
+#undef ENTRY
+#define ENTRY(key, field) \
+ bools_table.settings[SettingGetter::key] = &values.field
+ ENTRY(PROXY_USE_HTTP_PROXY, use_proxy);
+ ENTRY(PROXY_USE_SAME_PROXY, same_proxy);
+ ENTRY(PROXY_USE_AUTHENTICATION, use_auth);
+#undef ENTRY
+ string_lists_table.settings[SettingGetter::PROXY_IGNORE_HOSTS] =
+ &values.ignore_hosts;
+ Reset();
+ }
+
+ // Zeros all environment values.
+ void Reset() {
+ GConfValues zero_values = { 0 };
+ values = zero_values;
+ }
+
+ virtual bool Init(base::SingleThreadTaskRunner* glib_thread_task_runner,
+ base::MessageLoopForIO* file_loop) OVERRIDE {
+ return true;
+ }
+
+ virtual void ShutDown() OVERRIDE {}
+
+ virtual bool SetUpNotifications(ProxyConfigServiceLinux::Delegate* delegate)
+ OVERRIDE {
+ return true;
+ }
+
+ virtual base::SingleThreadTaskRunner* GetNotificationTaskRunner() OVERRIDE {
+ return NULL;
+ }
+
+ virtual ProxyConfigSource GetConfigSource() OVERRIDE {
+ return PROXY_CONFIG_SOURCE_TEST;
+ }
+
+ virtual bool GetString(StringSetting key, std::string* result) OVERRIDE {
+ const char* value = strings_table.Get(key);
+ if (value) {
+ *result = value;
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool GetBool(BoolSetting key, bool* result) OVERRIDE {
+ BoolSettingValue value = bools_table.Get(key);
+ switch (value) {
+ case UNSET:
+ return false;
+ case TRUE:
+ *result = true;
+ break;
+ case FALSE:
+ *result = false;
+ }
+ return true;
+ }
+
+ virtual bool GetInt(IntSetting key, int* result) OVERRIDE {
+ // We don't bother to distinguish unset keys from 0 values.
+ *result = ints_table.Get(key);
+ return true;
+ }
+
+ virtual bool GetStringList(StringListSetting key,
+ std::vector<std::string>* result) OVERRIDE {
+ *result = string_lists_table.Get(key);
+ // We don't bother to distinguish unset keys from empty lists.
+ return !result->empty();
+ }
+
+ virtual bool BypassListIsReversed() OVERRIDE {
+ return false;
+ }
+
+ virtual bool MatchHostsUsingSuffixMatching() OVERRIDE {
+ return false;
+ }
+
+ // Intentionally public, for convenience when setting up a test.
+ GConfValues values;
+
+ private:
+ SettingsTable<StringSetting, const char*> strings_table;
+ SettingsTable<BoolSetting, BoolSettingValue> bools_table;
+ SettingsTable<IntSetting, int> ints_table;
+ SettingsTable<StringListSetting,
+ std::vector<std::string> > string_lists_table;
+};
+
+} // namespace
+} // namespace net
+
+// This helper class runs ProxyConfigServiceLinux::GetLatestProxyConfig() on
+// the IO thread and synchronously waits for the result.
+// Some code duplicated from proxy_script_fetcher_unittest.cc.
+class SynchConfigGetter {
+ public:
+ // Takes ownership of |config_service|.
+ explicit SynchConfigGetter(net::ProxyConfigServiceLinux* config_service)
+ : event_(false, false),
+ io_thread_("IO_Thread"),
+ config_service_(config_service) {
+ // Start an IO thread.
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ io_thread_.StartWithOptions(options);
+
+ // Make sure the thread started.
+ io_thread_.message_loop()->PostTask(FROM_HERE,
+ base::Bind(&SynchConfigGetter::Init, base::Unretained(this)));
+ Wait();
+ }
+
+ ~SynchConfigGetter() {
+ // Let the config service post a destroy message to the IO thread
+ // before cleaning up that thread.
+ delete config_service_;
+ // Clean up the IO thread.
+ io_thread_.message_loop()->PostTask(FROM_HERE,
+ base::Bind(&SynchConfigGetter::CleanUp, base::Unretained(this)));
+ Wait();
+ }
+
+ // Does gconf setup and initial fetch of the proxy config,
+ // all on the calling thread (meant to be the thread with the
+ // default glib main loop, which is the UI thread).
+ void SetupAndInitialFetch() {
+ base::MessageLoop* file_loop = io_thread_.message_loop();
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, file_loop->type());
+ // We pass the mock IO thread as both the IO and file threads.
+ config_service_->SetupAndFetchInitialConfig(
+ base::MessageLoopProxy::current().get(),
+ io_thread_.message_loop_proxy().get(),
+ static_cast<base::MessageLoopForIO*>(file_loop));
+ }
+ // Synchronously gets the proxy config.
+ net::ProxyConfigService::ConfigAvailability SyncGetLatestProxyConfig(
+ net::ProxyConfig* config) {
+ io_thread_.message_loop()->PostTask(FROM_HERE,
+ base::Bind(&SynchConfigGetter::GetLatestConfigOnIOThread,
+ base::Unretained(this)));
+ Wait();
+ *config = proxy_config_;
+ return get_latest_config_result_;
+ }
+
+ private:
+ // [Runs on |io_thread_|]
+ void Init() {
+ event_.Signal();
+ }
+
+ // Calls GetLatestProxyConfig, running on |io_thread_| Signals |event_|
+ // on completion.
+ void GetLatestConfigOnIOThread() {
+ get_latest_config_result_ =
+ config_service_->GetLatestProxyConfig(&proxy_config_);
+ event_.Signal();
+ }
+
+ // [Runs on |io_thread_|] Signals |event_| on cleanup completion.
+ void CleanUp() {
+ base::MessageLoop::current()->RunUntilIdle();
+ event_.Signal();
+ }
+
+ void Wait() {
+ event_.Wait();
+ event_.Reset();
+ }
+
+ base::WaitableEvent event_;
+ base::Thread io_thread_;
+
+ net::ProxyConfigServiceLinux* config_service_;
+
+ // The config obtained by |io_thread_| and read back by the main
+ // thread.
+ net::ProxyConfig proxy_config_;
+
+ // Return value from GetLatestProxyConfig().
+ net::ProxyConfigService::ConfigAvailability get_latest_config_result_;
+};
+
+namespace net {
+
+// This test fixture is only really needed for the KDEConfigParser test case,
+// but all the test cases with the same prefix ("ProxyConfigServiceLinuxTest")
+// must use the same test fixture class (also "ProxyConfigServiceLinuxTest").
+class ProxyConfigServiceLinuxTest : public PlatformTest {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ PlatformTest::SetUp();
+ // Set up a temporary KDE home directory.
+ std::string prefix("ProxyConfigServiceLinuxTest_user_home");
+ file_util::CreateNewTempDirectory(prefix, &user_home_);
+ kde_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde"));
+ base::FilePath path = kde_home_.Append(FILE_PATH_LITERAL("share"));
+ path = path.Append(FILE_PATH_LITERAL("config"));
+ file_util::CreateDirectory(path);
+ kioslaverc_ = path.Append(FILE_PATH_LITERAL("kioslaverc"));
+ // Set up paths but do not create the directory for .kde4.
+ kde4_home_ = user_home_.Append(FILE_PATH_LITERAL(".kde4"));
+ path = kde4_home_.Append(FILE_PATH_LITERAL("share"));
+ kde4_config_ = path.Append(FILE_PATH_LITERAL("config"));
+ kioslaverc4_ = kde4_config_.Append(FILE_PATH_LITERAL("kioslaverc"));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Delete the temporary KDE home directory.
+ base::DeleteFile(user_home_, true);
+ PlatformTest::TearDown();
+ }
+
+ base::FilePath user_home_;
+ // KDE3 paths.
+ base::FilePath kde_home_;
+ base::FilePath kioslaverc_;
+ // KDE4 paths.
+ base::FilePath kde4_home_;
+ base::FilePath kde4_config_;
+ base::FilePath kioslaverc4_;
+};
+
+// Builds an identifier for each test in an array.
+#define TEST_DESC(desc) base::StringPrintf("at line %d <%s>", __LINE__, desc)
+
+TEST_F(ProxyConfigServiceLinuxTest, BasicGConfTest) {
+ std::vector<std::string> empty_ignores;
+
+ std::vector<std::string> google_ignores;
+ google_ignores.push_back("*.google.com");
+
+ // Inspired from proxy_config_service_win_unittest.cc.
+ // Very neat, but harder to track down failures though.
+ const struct {
+ // Short description to identify the test
+ std::string description;
+
+ // Input.
+ GConfValues values;
+
+ // Expected outputs (availability and fields of ProxyConfig).
+ ProxyConfigService::ConfigAvailability availability;
+ bool auto_detect;
+ GURL pac_url;
+ ProxyRulesExpectation proxy_rules;
+ } tests[] = {
+ {
+ TEST_DESC("No proxying"),
+ { // Input.
+ "none", // mode
+ "", // autoconfig_url
+ "", "", "", "", // hosts
+ 0, 0, 0, 0, // ports
+ FALSE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Auto detect"),
+ { // Input.
+ "auto", // mode
+ "", // autoconfig_url
+ "", "", "", "", // hosts
+ 0, 0, 0, 0, // ports
+ FALSE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Valid PAC URL"),
+ { // Input.
+ "auto", // mode
+ "http://wpad/wpad.dat", // autoconfig_url
+ "", "", "", "", // hosts
+ 0, 0, 0, 0, // ports
+ FALSE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL("http://wpad/wpad.dat"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Invalid PAC URL"),
+ { // Input.
+ "auto", // mode
+ "wpad.dat", // autoconfig_url
+ "", "", "", "", // hosts
+ 0, 0, 0, 0, // ports
+ FALSE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Single-host in proxy list"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", "", "", "", // hosts
+ 80, 0, 0, 0, // ports
+ TRUE, TRUE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("use_http_proxy is honored"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", "", "", "", // hosts
+ 80, 0, 0, 0, // ports
+ FALSE, TRUE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("use_http_proxy and use_same_proxy are optional"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", "", "", "", // hosts
+ 80, 0, 0, 0, // ports
+ UNSET, UNSET, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Single-host, different port"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", "", "", "", // hosts
+ 88, 0, 0, 0, // ports
+ TRUE, TRUE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:88", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Per-scheme proxy rules"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", // http_host
+ "www.foo.com", // secure_host
+ "ftp.foo.com", // ftp
+ "", // socks
+ 88, 110, 121, 0, // ports
+ TRUE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:88", // http
+ "www.foo.com:110", // https
+ "ftp.foo.com:121", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "", "", "", "socks.com", // hosts
+ 0, 0, 0, 99, // ports
+ TRUE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks5://socks.com:99", // single proxy
+ "") // bypass rules
+ },
+
+ {
+ TEST_DESC("Per-scheme proxy rules with fallback to SOCKS"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", // http_host
+ "www.foo.com", // secure_host
+ "ftp.foo.com", // ftp
+ "foobar.net", // socks
+ 88, 110, 121, 99, // ports
+ TRUE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerSchemeWithSocks(
+ "www.google.com:88", // http
+ "www.foo.com:110", // https
+ "ftp.foo.com:121", // ftp
+ "socks5://foobar.net:99", // socks
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Per-scheme proxy rules (just HTTP) with fallback to SOCKS"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", // http_host
+ "", // secure_host
+ "", // ftp
+ "foobar.net", // socks
+ 88, 0, 0, 99, // ports
+ TRUE, FALSE, FALSE, // use, same, auth
+ empty_ignores, // ignore_hosts
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerSchemeWithSocks(
+ "www.google.com:88", // http
+ "", // https
+ "", // ftp
+ "socks5://foobar.net:99", // socks
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Bypass *.google.com"),
+ { // Input.
+ "manual", // mode
+ "", // autoconfig_url
+ "www.google.com", "", "", "", // hosts
+ 80, 0, 0, 0, // ports
+ TRUE, TRUE, FALSE, // use, same, auth
+ google_ignores, // ignore_hosts
+ },
+
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ "*.google.com"), // bypass rules
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
+ tests[i].description.c_str()));
+ MockEnvironment* env = new MockEnvironment;
+ MockSettingGetter* setting_getter = new MockSettingGetter;
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env, setting_getter));
+ ProxyConfig config;
+ setting_getter->values = tests[i].values;
+ sync_config_getter.SetupAndInitialFetch();
+ ProxyConfigService::ConfigAvailability availability =
+ sync_config_getter.SyncGetLatestProxyConfig(&config);
+ EXPECT_EQ(tests[i].availability, availability);
+
+ if (availability == ProxyConfigService::CONFIG_VALID) {
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
+ }
+ }
+}
+
+TEST_F(ProxyConfigServiceLinuxTest, BasicEnvTest) {
+ // Inspired from proxy_config_service_win_unittest.cc.
+ const struct {
+ // Short description to identify the test
+ std::string description;
+
+ // Input.
+ EnvVarValues values;
+
+ // Expected outputs (availability and fields of ProxyConfig).
+ ProxyConfigService::ConfigAvailability availability;
+ bool auto_detect;
+ GURL pac_url;
+ ProxyRulesExpectation proxy_rules;
+ } tests[] = {
+ {
+ TEST_DESC("No proxying"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ NULL, // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ "*", // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Auto detect"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ "", // auto_proxy
+ NULL, // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Valid PAC URL"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ "http://wpad/wpad.dat", // auto_proxy
+ NULL, // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL("http://wpad/wpad.dat"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Invalid PAC URL"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ "wpad.dat", // auto_proxy
+ NULL, // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Single-host in proxy list"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "www.google.com", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Single-host, different port"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "www.google.com:99", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:99", // single
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Tolerate a scheme"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "http://www.google.com:99", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:99", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Per-scheme proxy rules"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ NULL, // all_proxy
+ "www.google.com:80", "www.foo.com:110", "ftp.foo.com:121", // per-proto
+ NULL, NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "ftp.foo.com:121", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ "socks.com:888", NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks5://socks.com:888", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks4"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ "socks.com:888", "4", // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks4://socks.com:888", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks default port"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "", // all_proxy
+ NULL, NULL, NULL, // per-proto proxies
+ "socks.com", NULL, // SOCKS
+ NULL, // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks5://socks.com:1080", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("bypass"),
+ { // Input.
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ "www.google.com", // all_proxy
+ NULL, NULL, NULL, // per-proto
+ NULL, NULL, // SOCKS
+ ".google.com, foo.com:99, 1.2.3.4:22, 127.0.0.1/8", // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:80",
+ "*.google.com,*foo.com:99,1.2.3.4:22,127.0.0.1/8"),
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
+ tests[i].description.c_str()));
+ MockEnvironment* env = new MockEnvironment;
+ MockSettingGetter* setting_getter = new MockSettingGetter;
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env, setting_getter));
+ ProxyConfig config;
+ env->values = tests[i].values;
+ sync_config_getter.SetupAndInitialFetch();
+ ProxyConfigService::ConfigAvailability availability =
+ sync_config_getter.SyncGetLatestProxyConfig(&config);
+ EXPECT_EQ(tests[i].availability, availability);
+
+ if (availability == ProxyConfigService::CONFIG_VALID) {
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
+ }
+ }
+}
+
+TEST_F(ProxyConfigServiceLinuxTest, GconfNotification) {
+ MockEnvironment* env = new MockEnvironment;
+ MockSettingGetter* setting_getter = new MockSettingGetter;
+ ProxyConfigServiceLinux* service =
+ new ProxyConfigServiceLinux(env, setting_getter);
+ SynchConfigGetter sync_config_getter(service);
+ ProxyConfig config;
+
+ // Start with no proxy.
+ setting_getter->values.mode = "none";
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_FALSE(config.auto_detect());
+
+ // Now set to auto-detect.
+ setting_getter->values.mode = "auto";
+ // Simulate setting change notification callback.
+ service->OnCheckProxyConfigSettings();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_TRUE(config.auto_detect());
+}
+
+TEST_F(ProxyConfigServiceLinuxTest, KDEConfigParser) {
+ // One of the tests below needs a worst-case long line prefix. We build it
+ // programmatically so that it will always be the right size.
+ std::string long_line;
+ size_t limit = ProxyConfigServiceLinux::SettingGetter::BUFFER_SIZE - 1;
+ for (size_t i = 0; i < limit; ++i)
+ long_line += "-";
+
+ // Inspired from proxy_config_service_win_unittest.cc.
+ const struct {
+ // Short description to identify the test
+ std::string description;
+
+ // Input.
+ std::string kioslaverc;
+ EnvVarValues env_values;
+
+ // Expected outputs (availability and fields of ProxyConfig).
+ ProxyConfigService::ConfigAvailability availability;
+ bool auto_detect;
+ GURL pac_url;
+ ProxyRulesExpectation proxy_rules;
+ } tests[] = {
+ {
+ TEST_DESC("No proxying"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=0\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Auto detect"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=3\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Valid PAC URL"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=2\n"
+ "Proxy Config Script=http://wpad/wpad.dat\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL("http://wpad/wpad.dat"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Valid PAC file without file://"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=2\n"
+ "Proxy Config Script=/wpad/wpad.dat\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL("file:///wpad/wpad.dat"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Per-scheme proxy rules"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "httpsProxy=www.foo.com\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:80", // https
+ "ftp.foo.com:80", // http
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Only HTTP proxy specified"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\n"
+ "httpProxy=www.google.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Only HTTP proxy specified, different port"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\n"
+ "httpProxy=www.google.com:88\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:88", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Only HTTP proxy specified, different port, space-delimited"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\n"
+ "httpProxy=www.google.com 88\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:88", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Bypass *.google.com"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=.google.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
+ },
+
+ {
+ TEST_DESC("Bypass *.google.com and *.kde.org"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=.google.com,.kde.org\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com,*.kde.org"), // bypass rules
+ },
+
+ {
+ TEST_DESC("Correctly parse bypass list with ReversedException"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=.google.com\nReversedException=true\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nsocksProxy=socks.com 888\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks5://socks.com:888", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("socks4"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nsocksProxy=socks4://socks.com 888\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "socks4://socks.com:888", // single proxy
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Treat all hostname patterns as wildcard patterns"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=google.com,kde.org,<local>\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*google.com,*kde.org,<local>"), // bypass rules
+ },
+
+ {
+ TEST_DESC("Allow trailing whitespace after boolean value"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "NoProxyFor=.google.com\nReversedException=true \n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerSchemeWithBypassReversed(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ "*.google.com"), // bypass rules
+ },
+
+ {
+ TEST_DESC("Ignore settings outside [Proxy Settings]"),
+
+ // Input.
+ "httpsProxy=www.foo.com\n[Proxy Settings]\nProxyType=1\n"
+ "httpProxy=www.google.com\n[Other Section]\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Handle CRLF line endings"),
+
+ // Input.
+ "[Proxy Settings]\r\nProxyType=1\r\nhttpProxy=www.google.com\r\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Handle blank lines and mixed line endings"),
+
+ // Input.
+ "[Proxy Settings]\r\n\nProxyType=1\n\r\nhttpProxy=www.google.com\n\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Handle localized settings"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType[$e]=1\nhttpProxy[$e]=www.google.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Ignore malformed localized settings"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=1\nhttpProxy=www.google.com\n"
+ "httpsProxy$e]=www.foo.com\nftpProxy=ftp.foo.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "ftp.foo.com:80", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Handle strange whitespace"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType [$e] =2\n"
+ " Proxy Config Script = http:// foo\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL("http:// foo"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Ignore all of a line which is too long"),
+
+ // Input.
+ std::string("[Proxy Settings]\nProxyType=1\nftpProxy=ftp.foo.com\n") +
+ long_line + "httpsProxy=www.foo.com\nhttpProxy=www.google.com\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "", // https
+ "ftp.foo.com:80", // ftp
+ ""), // bypass rules
+ },
+
+ {
+ TEST_DESC("Indirect Proxy - no env vars set"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
+ "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
+ {}, // env_values
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ {
+ TEST_DESC("Indirect Proxy - with env vars set"),
+
+ // Input.
+ "[Proxy Settings]\nProxyType=4\nhttpProxy=http_proxy\n"
+ "httpsProxy=https_proxy\nftpProxy=ftp_proxy\nNoProxyFor=no_proxy\n",
+ { // env_values
+ NULL, // DESKTOP_SESSION
+ NULL, // HOME
+ NULL, // KDEHOME
+ NULL, // KDE_SESSION_VERSION
+ NULL, // auto_proxy
+ NULL, // all_proxy
+ "www.normal.com", // http_proxy
+ "www.secure.com", // https_proxy
+ "ftp.foo.com", // ftp_proxy
+ NULL, NULL, // SOCKS
+ ".google.com, .kde.org", // no_proxy
+ },
+
+ // Expected result.
+ ProxyConfigService::CONFIG_VALID,
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.normal.com:80", // http
+ "www.secure.com:80", // https
+ "ftp.foo.com:80", // ftp
+ "*.google.com,*.kde.org"), // bypass rules
+ },
+
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "] %s", i,
+ tests[i].description.c_str()));
+ MockEnvironment* env = new MockEnvironment;
+ env->values = tests[i].env_values;
+ // Force the KDE getter to be used and tell it where the test is.
+ env->values.DESKTOP_SESSION = "kde4";
+ env->values.KDEHOME = kde_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ // Overwrite the kioslaverc file.
+ file_util::WriteFile(kioslaverc_, tests[i].kioslaverc.c_str(),
+ tests[i].kioslaverc.length());
+ sync_config_getter.SetupAndInitialFetch();
+ ProxyConfigService::ConfigAvailability availability =
+ sync_config_getter.SyncGetLatestProxyConfig(&config);
+ EXPECT_EQ(tests[i].availability, availability);
+
+ if (availability == ProxyConfigService::CONFIG_VALID) {
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
+ }
+ }
+}
+
+TEST_F(ProxyConfigServiceLinuxTest, KDEHomePicker) {
+ // Auto detect proxy settings.
+ std::string slaverc3 = "[Proxy Settings]\nProxyType=3\n";
+ // Valid PAC URL.
+ std::string slaverc4 = "[Proxy Settings]\nProxyType=2\n"
+ "Proxy Config Script=http://wpad/wpad.dat\n";
+ GURL slaverc4_pac_url("http://wpad/wpad.dat");
+
+ // Overwrite the .kde kioslaverc file.
+ file_util::WriteFile(kioslaverc_, slaverc3.c_str(), slaverc3.length());
+
+ // If .kde4 exists it will mess up the first test. It should not, as
+ // we created the directory for $HOME in the test setup.
+ CHECK(!base::DirectoryExists(kde4_home_));
+
+ { SCOPED_TRACE("KDE4, no .kde4 directory, verify fallback");
+ MockEnvironment* env = new MockEnvironment;
+ env->values.DESKTOP_SESSION = "kde4";
+ env->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ // Now create .kde4 and put a kioslaverc in the config directory.
+ // Note that its timestamp will be at least as new as the .kde one.
+ file_util::CreateDirectory(kde4_config_);
+ file_util::WriteFile(kioslaverc4_, slaverc4.c_str(), slaverc4.length());
+ CHECK(base::PathExists(kioslaverc4_));
+
+ { SCOPED_TRACE("KDE4, .kde4 directory present, use it");
+ MockEnvironment* env = new MockEnvironment;
+ env->values.DESKTOP_SESSION = "kde4";
+ env->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_FALSE(config.auto_detect());
+ EXPECT_EQ(slaverc4_pac_url, config.pac_url());
+ }
+
+ { SCOPED_TRACE("KDE3, .kde4 directory present, ignore it");
+ MockEnvironment* env = new MockEnvironment;
+ env->values.DESKTOP_SESSION = "kde";
+ env->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ { SCOPED_TRACE("KDE4, .kde4 directory present, KDEHOME set to .kde");
+ MockEnvironment* env = new MockEnvironment;
+ env->values.DESKTOP_SESSION = "kde4";
+ env->values.HOME = user_home_.value().c_str();
+ env->values.KDEHOME = kde_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+
+ // Finally, make the .kde4 config directory older than the .kde directory
+ // and make sure we then use .kde instead of .kde4 since it's newer.
+ file_util::SetLastModifiedTime(kde4_config_, base::Time());
+
+ { SCOPED_TRACE("KDE4, very old .kde4 directory present, use .kde");
+ MockEnvironment* env = new MockEnvironment;
+ env->values.DESKTOP_SESSION = "kde4";
+ env->values.HOME = user_home_.value().c_str();
+ SynchConfigGetter sync_config_getter(
+ new ProxyConfigServiceLinux(env));
+ ProxyConfig config;
+ sync_config_getter.SetupAndInitialFetch();
+ EXPECT_EQ(ProxyConfigService::CONFIG_VALID,
+ sync_config_getter.SyncGetLatestProxyConfig(&config));
+ EXPECT_TRUE(config.auto_detect());
+ EXPECT_EQ(GURL(), config.pac_url());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_mac.cc b/chromium/net/proxy/proxy_config_service_mac.cc
new file mode 100644
index 00000000000..c1bc5337e5d
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_mac.cc
@@ -0,0 +1,286 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_server.h"
+
+namespace net {
+
+namespace {
+
+const int kPollIntervalSec = 5;
+
+// Utility function to pull out a boolean value from a dictionary and return it,
+// returning a default value if the key is not present.
+bool GetBoolFromDictionary(CFDictionaryRef dict,
+ CFStringRef key,
+ bool default_value) {
+ CFNumberRef number = base::mac::GetValueFromDictionary<CFNumberRef>(dict,
+ key);
+ if (!number)
+ return default_value;
+
+ int int_value;
+ if (CFNumberGetValue(number, kCFNumberIntType, &int_value))
+ return int_value;
+ else
+ return default_value;
+}
+
+void GetCurrentProxyConfig(ProxyConfig* config) {
+ base::ScopedCFTypeRef<CFDictionaryRef> config_dict(
+ SCDynamicStoreCopyProxies(NULL));
+ DCHECK(config_dict);
+
+ // auto-detect
+
+ // There appears to be no UI for this configuration option, and we're not sure
+ // if Apple's proxy code even takes it into account. But the constant is in
+ // the header file so we'll use it.
+ config->set_auto_detect(
+ GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesProxyAutoDiscoveryEnable,
+ false));
+
+ // PAC file
+
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesProxyAutoConfigEnable,
+ false)) {
+ CFStringRef pac_url_ref = base::mac::GetValueFromDictionary<CFStringRef>(
+ config_dict.get(), kSCPropNetProxiesProxyAutoConfigURLString);
+ if (pac_url_ref)
+ config->set_pac_url(GURL(base::SysCFStringRefToUTF8(pac_url_ref)));
+ }
+
+ // proxies (for now ftp, http, https, and SOCKS)
+
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesFTPEnable,
+ false)) {
+ ProxyServer proxy_server =
+ ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
+ config_dict.get(),
+ kSCPropNetProxiesFTPProxy,
+ kSCPropNetProxiesFTPPort);
+ if (proxy_server.is_valid()) {
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_server);
+ }
+ }
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesHTTPEnable,
+ false)) {
+ ProxyServer proxy_server =
+ ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
+ config_dict.get(),
+ kSCPropNetProxiesHTTPProxy,
+ kSCPropNetProxiesHTTPPort);
+ if (proxy_server.is_valid()) {
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxies_for_http.SetSingleProxyServer(proxy_server);
+ }
+ }
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesHTTPSEnable,
+ false)) {
+ ProxyServer proxy_server =
+ ProxyServer::FromDictionary(ProxyServer::SCHEME_HTTP,
+ config_dict.get(),
+ kSCPropNetProxiesHTTPSProxy,
+ kSCPropNetProxiesHTTPSPort);
+ if (proxy_server.is_valid()) {
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().proxies_for_https.
+ SetSingleProxyServer(proxy_server);
+ }
+ }
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesSOCKSEnable,
+ false)) {
+ ProxyServer proxy_server =
+ ProxyServer::FromDictionary(ProxyServer::SCHEME_SOCKS5,
+ config_dict.get(),
+ kSCPropNetProxiesSOCKSProxy,
+ kSCPropNetProxiesSOCKSPort);
+ if (proxy_server.is_valid()) {
+ config->proxy_rules().type =
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
+ config->proxy_rules().fallback_proxies.SetSingleProxyServer(proxy_server);
+ }
+ }
+
+ // proxy bypass list
+
+ CFArrayRef bypass_array_ref = base::mac::GetValueFromDictionary<CFArrayRef>(
+ config_dict.get(), kSCPropNetProxiesExceptionsList);
+ if (bypass_array_ref) {
+ CFIndex bypass_array_count = CFArrayGetCount(bypass_array_ref);
+ for (CFIndex i = 0; i < bypass_array_count; ++i) {
+ CFStringRef bypass_item_ref = base::mac::CFCast<CFStringRef>(
+ CFArrayGetValueAtIndex(bypass_array_ref, i));
+ if (!bypass_item_ref) {
+ LOG(WARNING) << "Expected value for item " << i
+ << " in the kSCPropNetProxiesExceptionsList"
+ " to be a CFStringRef but it was not";
+
+ } else {
+ config->proxy_rules().bypass_rules.AddRuleFromString(
+ base::SysCFStringRefToUTF8(bypass_item_ref));
+ }
+ }
+ }
+
+ // proxy bypass boolean
+
+ if (GetBoolFromDictionary(config_dict.get(),
+ kSCPropNetProxiesExcludeSimpleHostnames,
+ false)) {
+ config->proxy_rules().bypass_rules.AddRuleToBypassLocal();
+ }
+
+ // Source
+ config->set_source(PROXY_CONFIG_SOURCE_SYSTEM);
+}
+
+} // namespace
+
+// Reference-counted helper for posting a task to
+// ProxyConfigServiceMac::OnProxyConfigChanged between the notifier and IO
+// thread. This helper object may outlive the ProxyConfigServiceMac.
+class ProxyConfigServiceMac::Helper
+ : public base::RefCountedThreadSafe<ProxyConfigServiceMac::Helper> {
+ public:
+ explicit Helper(ProxyConfigServiceMac* parent) : parent_(parent) {
+ DCHECK(parent);
+ }
+
+ // Called when the parent is destroyed.
+ void Orphan() {
+ parent_ = NULL;
+ }
+
+ void OnProxyConfigChanged(const ProxyConfig& new_config) {
+ if (parent_)
+ parent_->OnProxyConfigChanged(new_config);
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<Helper>;
+ ~Helper() {}
+
+ ProxyConfigServiceMac* parent_;
+};
+
+void ProxyConfigServiceMac::Forwarder::SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) {
+ proxy_config_service_->SetDynamicStoreNotificationKeys(store);
+}
+
+void ProxyConfigServiceMac::Forwarder::OnNetworkConfigChange(
+ CFArrayRef changed_keys) {
+ proxy_config_service_->OnNetworkConfigChange(changed_keys);
+}
+
+ProxyConfigServiceMac::ProxyConfigServiceMac(
+ base::SingleThreadTaskRunner* io_thread_task_runner)
+ : forwarder_(this),
+ has_fetched_config_(false),
+ helper_(new Helper(this)),
+ io_thread_task_runner_(io_thread_task_runner) {
+ DCHECK(io_thread_task_runner_.get());
+ config_watcher_.reset(new NetworkConfigWatcherMac(&forwarder_));
+}
+
+ProxyConfigServiceMac::~ProxyConfigServiceMac() {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+ // Delete the config_watcher_ to ensure the notifier thread finishes before
+ // this object is destroyed.
+ config_watcher_.reset();
+ helper_->Orphan();
+}
+
+void ProxyConfigServiceMac::AddObserver(Observer* observer) {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+ observers_.AddObserver(observer);
+}
+
+void ProxyConfigServiceMac::RemoveObserver(Observer* observer) {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+ observers_.RemoveObserver(observer);
+}
+
+net::ProxyConfigService::ConfigAvailability
+ ProxyConfigServiceMac::GetLatestProxyConfig(ProxyConfig* config) {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+
+ // Lazy-initialize by fetching the proxy setting from this thread.
+ if (!has_fetched_config_) {
+ GetCurrentProxyConfig(&last_config_fetched_);
+ has_fetched_config_ = true;
+ }
+
+ *config = last_config_fetched_;
+ return has_fetched_config_ ? CONFIG_VALID : CONFIG_PENDING;
+}
+
+void ProxyConfigServiceMac::SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) {
+ // Called on notifier thread.
+
+ CFStringRef proxies_key = SCDynamicStoreKeyCreateProxies(NULL);
+ CFArrayRef key_array = CFArrayCreate(
+ NULL, (const void **)(&proxies_key), 1, &kCFTypeArrayCallBacks);
+
+ bool ret = SCDynamicStoreSetNotificationKeys(store, key_array, NULL);
+ // TODO(willchan): Figure out a proper way to handle this rather than crash.
+ CHECK(ret);
+
+ CFRelease(key_array);
+ CFRelease(proxies_key);
+}
+
+void ProxyConfigServiceMac::OnNetworkConfigChange(CFArrayRef changed_keys) {
+ // Called on notifier thread.
+
+ // Fetch the new system proxy configuration.
+ ProxyConfig new_config;
+ GetCurrentProxyConfig(&new_config);
+
+ // Call OnProxyConfigChanged() on the IO thread to notify our observers.
+ io_thread_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Helper::OnProxyConfigChanged, helper_.get(), new_config));
+}
+
+void ProxyConfigServiceMac::OnProxyConfigChanged(
+ const ProxyConfig& new_config) {
+ DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
+
+ // Keep track of the last value we have seen.
+ has_fetched_config_ = true;
+ last_config_fetched_ = new_config;
+
+ // Notify all the observers.
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnProxyConfigChanged(new_config, CONFIG_VALID));
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_mac.h b/chromium/net/proxy/proxy_config_service_mac.h
new file mode 100644
index 00000000000..cf513a0aeef
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_mac.h
@@ -0,0 +1,88 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_MAC_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_MAC_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "net/base/network_config_watcher_mac.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+
+// TODO(sergeyu): This class needs to be exported because remoting code
+// creates it directly. Fix that and remove NET_EXPORT here.
+// crbug.com/125104
+class NET_EXPORT ProxyConfigServiceMac : public ProxyConfigService {
+ public:
+ // Constructs a ProxyConfigService that watches the Mac OS system settings.
+ // This instance is expected to be operated and deleted on the same thread
+ // (however it may be constructed from a different thread).
+ explicit ProxyConfigServiceMac(
+ base::SingleThreadTaskRunner* io_thread_task_runner);
+ virtual ~ProxyConfigServiceMac();
+
+ public:
+ // ProxyConfigService implementation:
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+ virtual void RemoveObserver(Observer* observer) OVERRIDE;
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE;
+
+ private:
+ class Helper;
+
+ // Forwarder just exists to keep the NetworkConfigWatcherMac API out of
+ // ProxyConfigServiceMac's public API.
+ class Forwarder : public NetworkConfigWatcherMac::Delegate {
+ public:
+ explicit Forwarder(ProxyConfigServiceMac* proxy_config_service)
+ : proxy_config_service_(proxy_config_service) {}
+
+ // NetworkConfigWatcherMac::Delegate implementation:
+ virtual void StartReachabilityNotifications() OVERRIDE {}
+ virtual void SetDynamicStoreNotificationKeys(
+ SCDynamicStoreRef store) OVERRIDE;
+ virtual void OnNetworkConfigChange(CFArrayRef changed_keys) OVERRIDE;
+
+ private:
+ ProxyConfigServiceMac* const proxy_config_service_;
+ DISALLOW_COPY_AND_ASSIGN(Forwarder);
+ };
+
+ // Methods directly called by the NetworkConfigWatcherMac::Delegate:
+ void SetDynamicStoreNotificationKeys(SCDynamicStoreRef store);
+ void OnNetworkConfigChange(CFArrayRef changed_keys);
+
+ // Called when the proxy configuration has changed, to notify the observers.
+ void OnProxyConfigChanged(const ProxyConfig& new_config);
+
+ Forwarder forwarder_;
+ scoped_ptr<const NetworkConfigWatcherMac> config_watcher_;
+
+ ObserverList<Observer> observers_;
+
+ // Holds the last system proxy settings that we fetched.
+ bool has_fetched_config_;
+ ProxyConfig last_config_fetched_;
+
+ scoped_refptr<Helper> helper_;
+
+ // The thread that we expect to be operated on.
+ const scoped_refptr<base::SingleThreadTaskRunner> io_thread_task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceMac);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_MAC_H_
diff --git a/chromium/net/proxy/proxy_config_service_win.cc b/chromium/net/proxy/proxy_config_service_win.cc
new file mode 100644
index 00000000000..a1295f33de8
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_win.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_win.h"
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/win/registry.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
+
+#pragma comment(lib, "winhttp.lib")
+
+namespace net {
+
+namespace {
+
+const int kPollIntervalSec = 10;
+
+void FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* ie_config) {
+ if (ie_config->lpszAutoConfigUrl)
+ GlobalFree(ie_config->lpszAutoConfigUrl);
+ if (ie_config->lpszProxy)
+ GlobalFree(ie_config->lpszProxy);
+ if (ie_config->lpszProxyBypass)
+ GlobalFree(ie_config->lpszProxyBypass);
+}
+
+} // namespace
+
+// RegKey and ObjectWatcher pair.
+class ProxyConfigServiceWin::KeyEntry {
+ public:
+ bool StartWatching(base::win::ObjectWatcher::Delegate* delegate) {
+ // Try to create a watch event for the registry key (which watches the
+ // sibling tree as well).
+ if (key_.StartWatching() != ERROR_SUCCESS)
+ return false;
+
+ // Now setup an ObjectWatcher for this event, so we get OnObjectSignaled()
+ // invoked on this message loop once it is signalled.
+ if (!watcher_.StartWatching(key_.watch_event(), delegate))
+ return false;
+
+ return true;
+ }
+
+ bool CreateRegKey(HKEY rootkey, const wchar_t* subkey) {
+ return key_.Create(rootkey, subkey, KEY_NOTIFY) == ERROR_SUCCESS;
+ }
+
+ HANDLE watch_event() const {
+ return key_.watch_event();
+ }
+
+ private:
+ base::win::RegKey key_;
+ base::win::ObjectWatcher watcher_;
+};
+
+ProxyConfigServiceWin::ProxyConfigServiceWin()
+ : PollingProxyConfigService(
+ base::TimeDelta::FromSeconds(kPollIntervalSec),
+ &ProxyConfigServiceWin::GetCurrentProxyConfig) {
+}
+
+ProxyConfigServiceWin::~ProxyConfigServiceWin() {
+ // The registry functions below will end up going to disk. Do this on another
+ // thread to avoid slowing the IO thread. http://crbug.com/61453
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ STLDeleteElements(&keys_to_watch_);
+}
+
+void ProxyConfigServiceWin::AddObserver(Observer* observer) {
+ // Lazily-initialize our registry watcher.
+ StartWatchingRegistryForChanges();
+
+ // Let the super-class do its work now.
+ PollingProxyConfigService::AddObserver(observer);
+}
+
+void ProxyConfigServiceWin::StartWatchingRegistryForChanges() {
+ if (!keys_to_watch_.empty())
+ return; // Already initialized.
+
+ // The registry functions below will end up going to disk. Do this on another
+ // thread to avoid slowing the IO thread. http://crbug.com/61453
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // There are a number of different places where proxy settings can live
+ // in the registry. In some cases it appears in a binary value, in other
+ // cases string values. Furthermore winhttp and wininet appear to have
+ // separate stores, and proxy settings can be configured per-machine
+ // or per-user.
+ //
+ // This function is probably not exhaustive in the registry locations it
+ // watches for changes, however it should catch the majority of the
+ // cases. In case we have missed some less common triggers (likely), we
+ // will catch them during the periodic (10 second) polling, so things
+ // will recover.
+
+ AddKeyToWatchList(
+ HKEY_CURRENT_USER,
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
+
+ AddKeyToWatchList(
+ HKEY_LOCAL_MACHINE,
+ L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
+
+ AddKeyToWatchList(
+ HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\"
+ L"Internet Settings");
+}
+
+bool ProxyConfigServiceWin::AddKeyToWatchList(HKEY rootkey,
+ const wchar_t* subkey) {
+ scoped_ptr<KeyEntry> entry(new KeyEntry);
+ if (!entry->CreateRegKey(rootkey, subkey))
+ return false;
+
+ if (!entry->StartWatching(this))
+ return false;
+
+ keys_to_watch_.push_back(entry.release());
+ return true;
+}
+
+void ProxyConfigServiceWin::OnObjectSignaled(HANDLE object) {
+ // Figure out which registry key signalled this change.
+ KeyEntryList::iterator it;
+ for (it = keys_to_watch_.begin(); it != keys_to_watch_.end(); ++it) {
+ if ((*it)->watch_event() == object)
+ break;
+ }
+
+ DCHECK(it != keys_to_watch_.end());
+
+ // Keep watching the registry key.
+ if (!(*it)->StartWatching(this))
+ keys_to_watch_.erase(it);
+
+ // Have the PollingProxyConfigService test for changes.
+ CheckForChangesNow();
+}
+
+// static
+void ProxyConfigServiceWin::GetCurrentProxyConfig(ProxyConfig* config) {
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0};
+ if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) {
+ LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " <<
+ GetLastError();
+ *config = ProxyConfig::CreateDirect();
+ config->set_source(PROXY_CONFIG_SOURCE_SYSTEM_FAILED);
+ return;
+ }
+ SetFromIEConfig(config, ie_config);
+ FreeIEConfig(&ie_config);
+}
+
+// static
+void ProxyConfigServiceWin::SetFromIEConfig(
+ ProxyConfig* config,
+ const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config) {
+ if (ie_config.fAutoDetect)
+ config->set_auto_detect(true);
+ if (ie_config.lpszProxy) {
+ // lpszProxy may be a single proxy, or a proxy per scheme. The format
+ // is compatible with ProxyConfig::ProxyRules's string format.
+ config->proxy_rules().ParseFromString(WideToASCII(ie_config.lpszProxy));
+ }
+ if (ie_config.lpszProxyBypass) {
+ std::string proxy_bypass = WideToASCII(ie_config.lpszProxyBypass);
+
+ base::StringTokenizer proxy_server_bypass_list(proxy_bypass, ";, \t\n\r");
+ while (proxy_server_bypass_list.GetNext()) {
+ std::string bypass_url_domain = proxy_server_bypass_list.token();
+ config->proxy_rules().bypass_rules.AddRuleFromString(bypass_url_domain);
+ }
+ }
+ if (ie_config.lpszAutoConfigUrl)
+ config->set_pac_url(GURL(ie_config.lpszAutoConfigUrl));
+ config->set_source(PROXY_CONFIG_SOURCE_SYSTEM);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_service_win.h b/chromium/net/proxy/proxy_config_service_win.h
new file mode 100644
index 00000000000..aa91b686c4e
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_win.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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_PROXY_PROXY_CONFIG_SERVICE_WIN_H_
+#define NET_PROXY_PROXY_CONFIG_SERVICE_WIN_H_
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/win/object_watcher.h"
+#include "net/proxy/polling_proxy_config_service.h"
+
+namespace net {
+
+// Implementation of ProxyConfigService that retrieves the system proxy
+// settings.
+//
+// It works by calling WinHttpGetIEProxyConfigForCurrentUser() to fetch the
+// Internet Explorer proxy settings.
+//
+// We use two different strategies to notice when the configuration has
+// changed:
+//
+// (1) Watch the internet explorer settings registry keys for changes. When
+// one of the registry keys pertaining to proxy settings has changed, we
+// call WinHttpGetIEProxyConfigForCurrentUser() again to read the
+// configuration's new value.
+//
+// (2) Do regular polling every 10 seconds during network activity to see if
+// WinHttpGetIEProxyConfigForCurrentUser() returns something different.
+//
+// Ideally strategy (1) should be sufficient to pick up all of the changes.
+// However we still do the regular polling as a precaution in case the
+// implementation details of WinHttpGetIEProxyConfigForCurrentUser() ever
+// change, or in case we got it wrong (and are not checking all possible
+// registry dependencies).
+class NET_EXPORT_PRIVATE ProxyConfigServiceWin
+ : public PollingProxyConfigService,
+ public base::win::ObjectWatcher::Delegate {
+ public:
+ ProxyConfigServiceWin();
+ virtual ~ProxyConfigServiceWin();
+
+ // Overrides a function from PollingProxyConfigService.
+ virtual void AddObserver(Observer* observer) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ProxyConfigServiceWinTest, SetFromIEConfig);
+ class KeyEntry;
+ typedef std::vector<KeyEntry*> KeyEntryList;
+
+ // Registers change observers on the registry keys relating to proxy settings.
+ void StartWatchingRegistryForChanges();
+
+ // Creates a new KeyEntry and appends it to |keys_to_watch_|. If the key
+ // fails to be created, it is not appended to the list and we return false.
+ bool AddKeyToWatchList(HKEY rootkey, const wchar_t* subkey);
+
+ // ObjectWatcher::Delegate methods:
+ // This is called whenever one of the registry keys we are watching change.
+ virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
+
+ static void GetCurrentProxyConfig(ProxyConfig* config);
+
+ // Set |config| using the proxy configuration values of |ie_config|.
+ static void SetFromIEConfig(
+ ProxyConfig* config,
+ const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config);
+
+ KeyEntryList keys_to_watch_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SERVICE_WIN_H_
diff --git a/chromium/net/proxy/proxy_config_service_win_unittest.cc b/chromium/net/proxy/proxy_config_service_win_unittest.cc
new file mode 100644
index 00000000000..911949d5994
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_service_win_unittest.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_service_win.h"
+
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(ProxyConfigServiceWinTest, SetFromIEConfig) {
+ const struct {
+ // Input.
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config;
+
+ // Expected outputs (fields of the ProxyConfig).
+ bool auto_detect;
+ GURL pac_url;
+ ProxyRulesExpectation proxy_rules;
+ const char* proxy_bypass_list; // newline separated
+ } tests[] = {
+ // Auto detect.
+ {
+ { // Input.
+ TRUE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ NULL, // lpszProxyBypass
+ },
+
+ // Expected result.
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ // Valid PAC url
+ {
+ { // Input.
+ FALSE, // fAutoDetect
+ L"http://wpad/wpad.dat", // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ NULL, // lpszProxy_bypass
+ },
+
+ // Expected result.
+ false, // auto_detect
+ GURL("http://wpad/wpad.dat"), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ // Invalid PAC url string.
+ {
+ { // Input.
+ FALSE, // fAutoDetect
+ L"wpad.dat", // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ NULL, // lpszProxy_bypass
+ },
+
+ // Expected result.
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Empty(),
+ },
+
+ // Single-host in proxy list.
+ {
+ { // Input.
+ FALSE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ L"www.google.com", // lpszProxy
+ NULL, // lpszProxy_bypass
+ },
+
+ // Expected result.
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::Single(
+ "www.google.com:80", // single proxy
+ ""), // bypass rules
+ },
+
+ // Per-scheme proxy rules.
+ {
+ { // Input.
+ FALSE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ L"http=www.google.com:80;https=www.foo.com:110", // lpszProxy
+ NULL, // lpszProxy_bypass
+ },
+
+ // Expected result.
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerScheme(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "", // ftp
+ ""), // bypass rules
+ },
+
+ // SOCKS proxy configuration.
+ {
+ { // Input.
+ FALSE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ L"http=www.google.com:80;https=www.foo.com:110;"
+ L"ftp=ftpproxy:20;socks=foopy:130", // lpszProxy
+ NULL, // lpszProxy_bypass
+ },
+
+ // Expected result.
+ // Note that "socks" is interprted as meaning "socks4", since that is how
+ // Internet Explorer applies the settings. For more details on this
+ // policy, see:
+ // http://code.google.com/p/chromium/issues/detail?id=55912#c2
+ false, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::PerSchemeWithSocks(
+ "www.google.com:80", // http
+ "www.foo.com:110", // https
+ "ftpproxy:20", // ftp
+ "socks4://foopy:130", // socks
+ ""), // bypass rules
+ },
+
+ // Bypass local names.
+ {
+ { // Input.
+ TRUE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ L"<local>", // lpszProxy_bypass
+ },
+
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::EmptyWithBypass("<local>"),
+ },
+
+ // Bypass "google.com" and local names, using semicolon as delimiter
+ // (ignoring white space).
+ {
+ { // Input.
+ TRUE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ L"<local> ; google.com", // lpszProxy_bypass
+ },
+
+ // Expected result.
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::EmptyWithBypass("<local>,google.com"),
+ },
+
+ // Bypass "foo.com" and "google.com", using lines as delimiter.
+ {
+ { // Input.
+ TRUE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ L"foo.com\r\ngoogle.com", // lpszProxy_bypass
+ },
+
+ // Expected result.
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::EmptyWithBypass("foo.com,google.com"),
+ },
+
+ // Bypass "foo.com" and "google.com", using commas as delimiter.
+ {
+ { // Input.
+ TRUE, // fAutoDetect
+ NULL, // lpszAutoConfigUrl
+ NULL, // lpszProxy
+ L"foo.com, google.com", // lpszProxy_bypass
+ },
+
+ // Expected result.
+ true, // auto_detect
+ GURL(), // pac_url
+ ProxyRulesExpectation::EmptyWithBypass("foo.com,google.com"),
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ ProxyConfig config;
+ ProxyConfigServiceWin::SetFromIEConfig(&config, tests[i].ie_config);
+
+ EXPECT_EQ(tests[i].auto_detect, config.auto_detect());
+ EXPECT_EQ(tests[i].pac_url, config.pac_url());
+ EXPECT_TRUE(tests[i].proxy_rules.Matches(config.proxy_rules()));
+ EXPECT_EQ(PROXY_CONFIG_SOURCE_SYSTEM, config.source());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_source.cc b/chromium/net/proxy/proxy_config_source.cc
new file mode 100644
index 00000000000..5695b9bf6e6
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_source.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config_source.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace net {
+
+namespace {
+
+const char* kSourceNames[] = {
+ "UNKNOWN",
+ "SYSTEM",
+ "SYSTEM FAILED",
+ "GCONF",
+ "GSETTINGS",
+ "KDE",
+ "ENV",
+ "CUSTOM",
+ "TEST"
+};
+COMPILE_ASSERT(ARRAYSIZE_UNSAFE(kSourceNames) == NUM_PROXY_CONFIG_SOURCES,
+ source_names_incorrect_size);
+
+} // namespace
+
+const char* ProxyConfigSourceToString(ProxyConfigSource source) {
+ DCHECK_GT(NUM_PROXY_CONFIG_SOURCES, source);
+ return kSourceNames[source];
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_config_source.h b/chromium/net/proxy/proxy_config_source.h
new file mode 100644
index 00000000000..a7e375ab749
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_source.h
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_CONFIG_SOURCE_H_
+#define NET_PROXY_PROXY_CONFIG_SOURCE_H_
+
+namespace net {
+
+// Source of the configuration settings encapsulated in a ProxyConfig object.
+
+// The source information is used for determining how credentials are used and
+// for logging. When adding new values, remember to add a string to
+// kSourceNames[] in proxy_config_source.cc.
+enum ProxyConfigSource {
+ PROXY_CONFIG_SOURCE_UNKNOWN, // The source hasn't been set.
+ PROXY_CONFIG_SOURCE_SYSTEM, // System settings (Win/Mac).
+ PROXY_CONFIG_SOURCE_SYSTEM_FAILED, // Default settings after failure to
+ // determine system settings.
+ PROXY_CONFIG_SOURCE_GCONF, // GConf (Linux)
+ PROXY_CONFIG_SOURCE_GSETTINGS, // GSettings (Linux).
+ PROXY_CONFIG_SOURCE_KDE, // KDE (Linux).
+ PROXY_CONFIG_SOURCE_ENV, // Environment variables.
+ PROXY_CONFIG_SOURCE_CUSTOM, // Custom settings local to the
+ // application (command line,
+ // extensions, application
+ // specific preferences, etc.)
+ PROXY_CONFIG_SOURCE_TEST, // Test settings.
+ NUM_PROXY_CONFIG_SOURCES
+};
+
+// Returns a textual representation of the source.
+const char* ProxyConfigSourceToString(ProxyConfigSource source);
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_CONFIG_SOURCE_H_
diff --git a/chromium/net/proxy/proxy_config_unittest.cc b/chromium/net/proxy/proxy_config_unittest.cc
new file mode 100644
index 00000000000..4b041b34340
--- /dev/null
+++ b/chromium/net/proxy/proxy_config_unittest.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_common_unittest.h"
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+void ExpectProxyServerEquals(const char* expectation,
+ const ProxyList& proxy_servers) {
+ if (expectation == NULL) {
+ EXPECT_TRUE(proxy_servers.IsEmpty());
+ } else {
+ EXPECT_EQ(expectation, proxy_servers.ToPacString());
+ }
+}
+
+TEST(ProxyConfigTest, Equals) {
+ // Test |ProxyConfig::auto_detect|.
+
+ ProxyConfig config1;
+ config1.set_auto_detect(true);
+
+ ProxyConfig config2;
+ config2.set_auto_detect(false);
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config2.set_auto_detect(true);
+
+ EXPECT_TRUE(config1.Equals(config2));
+ EXPECT_TRUE(config2.Equals(config1));
+
+ // Test |ProxyConfig::pac_url|.
+
+ config2.set_pac_url(GURL("http://wpad/wpad.dat"));
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config1.set_pac_url(GURL("http://wpad/wpad.dat"));
+
+ EXPECT_TRUE(config1.Equals(config2));
+ EXPECT_TRUE(config2.Equals(config1));
+
+ // Test |ProxyConfig::proxy_rules|.
+
+ config2.proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config2.proxy_rules().single_proxies.SetSingleProxyServer(
+ ProxyServer::FromURI("myproxy:80", ProxyServer::SCHEME_HTTP));
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config1.proxy_rules().type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
+ config1.proxy_rules().single_proxies.SetSingleProxyServer(
+ ProxyServer::FromURI("myproxy:100", ProxyServer::SCHEME_HTTP));
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config1.proxy_rules().single_proxies.SetSingleProxyServer(
+ ProxyServer::FromURI("myproxy", ProxyServer::SCHEME_HTTP));
+
+ EXPECT_TRUE(config1.Equals(config2));
+ EXPECT_TRUE(config2.Equals(config1));
+
+ // Test |ProxyConfig::bypass_rules|.
+
+ config2.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config1.proxy_rules().bypass_rules.AddRuleFromString("*.google.com");
+
+ EXPECT_TRUE(config1.Equals(config2));
+ EXPECT_TRUE(config2.Equals(config1));
+
+ // Test |ProxyConfig::proxy_rules.reverse_bypass|.
+
+ config2.proxy_rules().reverse_bypass = true;
+
+ EXPECT_FALSE(config1.Equals(config2));
+ EXPECT_FALSE(config2.Equals(config1));
+
+ config1.proxy_rules().reverse_bypass = true;
+
+ EXPECT_TRUE(config1.Equals(config2));
+ EXPECT_TRUE(config2.Equals(config1));
+}
+
+TEST(ProxyConfigTest, ParseProxyRules) {
+ const struct {
+ const char* proxy_rules;
+
+ ProxyConfig::ProxyRules::Type type;
+ // These will be PAC-stle strings, eg 'PROXY foo.com'
+ const char* single_proxy;
+ const char* proxy_for_http;
+ const char* proxy_for_https;
+ const char* proxy_for_ftp;
+ const char* fallback_proxy;
+ } tests[] = {
+ // One HTTP proxy for all schemes.
+ {
+ "myproxy:80",
+
+ ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ "PROXY myproxy:80",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ // Multiple HTTP proxies for all schemes.
+ {
+ "myproxy:80,https://myotherproxy",
+
+ ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ "PROXY myproxy:80;HTTPS myotherproxy:443",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ // Only specify a proxy server for "http://" urls.
+ {
+ "http=myproxy:80",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY myproxy:80",
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ // Specify an HTTP proxy for "ftp://" and a SOCKS proxy for "https://" urls.
+ {
+ "ftp=ftp-proxy ; https=socks4://foopy",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ NULL,
+ "SOCKS foopy:1080",
+ "PROXY ftp-proxy:80",
+ NULL,
+ },
+
+ // Give a scheme-specific proxy as well as a non-scheme specific.
+ // The first entry "foopy" takes precedance marking this list as
+ // TYPE_SINGLE_PROXY.
+ {
+ "foopy ; ftp=ftp-proxy",
+
+ ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY,
+ "PROXY foopy:80",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ // Give a scheme-specific proxy as well as a non-scheme specific.
+ // The first entry "ftp=ftp-proxy" takes precedance marking this list as
+ // TYPE_PROXY_PER_SCHEME.
+ {
+ "ftp=ftp-proxy ; foopy",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ NULL,
+ NULL,
+ "PROXY ftp-proxy:80",
+ NULL,
+ },
+
+ // Include a list of entries for a single scheme.
+ {
+ "ftp=ftp1,ftp2,ftp3",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ NULL,
+ NULL,
+ "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
+ NULL,
+ },
+
+ // Include multiple entries for the same scheme -- they accumulate.
+ {
+ "http=http1,http2; http=http3",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY http1:80;PROXY http2:80;PROXY http3:80",
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ // Include lists of entries for multiple schemes.
+ {
+ "ftp=ftp1,ftp2,ftp3 ; http=http1,http2; ",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY http1:80;PROXY http2:80",
+ NULL,
+ "PROXY ftp1:80;PROXY ftp2:80;PROXY ftp3:80",
+ NULL,
+ },
+
+ // Include non-default proxy schemes.
+ {
+ "http=https://secure_proxy; ftp=socks4://socks_proxy; https=socks://foo",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "HTTPS secure_proxy:443",
+ "SOCKS5 foo:1080",
+ "SOCKS socks_proxy:1080",
+ NULL,
+ },
+
+ // Only SOCKS proxy present, others being blank.
+ {
+ "socks=foopy",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "SOCKS foopy:1080",
+ },
+
+ // SOCKS proxy present along with other proxies too
+ {
+ "http=httpproxy ; https=httpsproxy ; ftp=ftpproxy ; socks=foopy ",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY httpproxy:80",
+ "PROXY httpsproxy:80",
+ "PROXY ftpproxy:80",
+ "SOCKS foopy:1080",
+ },
+
+ // SOCKS proxy (with modifier) present along with some proxies
+ // (FTP being blank)
+ {
+ "http=httpproxy ; https=httpsproxy ; socks=socks5://foopy ",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY httpproxy:80",
+ "PROXY httpsproxy:80",
+ NULL,
+ "SOCKS5 foopy:1080",
+ },
+
+ // Include unsupported schemes -- they are discarded.
+ {
+ "crazy=foopy ; foo=bar ; https=myhttpsproxy",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ NULL,
+ "PROXY myhttpsproxy:80",
+ NULL,
+ NULL,
+ },
+
+ // direct:// as first option for a scheme.
+ {
+ "http=direct://,myhttpproxy; https=direct://",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "DIRECT;PROXY myhttpproxy:80",
+ "DIRECT",
+ NULL,
+ NULL,
+ },
+
+ // direct:// as a second option for a scheme.
+ {
+ "http=myhttpproxy,direct://",
+
+ ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ NULL,
+ "PROXY myhttpproxy:80;DIRECT",
+ NULL,
+ NULL,
+ NULL,
+ },
+
+ };
+
+ ProxyConfig config;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ config.proxy_rules().ParseFromString(tests[i].proxy_rules);
+
+ EXPECT_EQ(tests[i].type, config.proxy_rules().type);
+ ExpectProxyServerEquals(tests[i].single_proxy,
+ config.proxy_rules().single_proxies);
+ ExpectProxyServerEquals(tests[i].proxy_for_http,
+ config.proxy_rules().proxies_for_http);
+ ExpectProxyServerEquals(tests[i].proxy_for_https,
+ config.proxy_rules().proxies_for_https);
+ ExpectProxyServerEquals(tests[i].proxy_for_ftp,
+ config.proxy_rules().proxies_for_ftp);
+ ExpectProxyServerEquals(tests[i].fallback_proxy,
+ config.proxy_rules().fallback_proxies);
+ }
+}
+
+TEST(ProxyConfigTest, ProxyRulesSetBypassFlag) {
+ // Test whether the did_bypass_proxy() flag is set in proxy info correctly.
+ ProxyConfig::ProxyRules rules;
+ ProxyInfo result;
+
+ rules.ParseFromString("http=httpproxy:80");
+ rules.bypass_rules.AddRuleFromString(".com");
+
+ rules.Apply(GURL("http://example.com"), &result);
+ EXPECT_TRUE(result.is_direct_only());
+ EXPECT_TRUE(result.did_bypass_proxy());
+
+ rules.Apply(GURL("http://example.org"), &result);
+ EXPECT_FALSE(result.is_direct());
+ EXPECT_FALSE(result.did_bypass_proxy());
+
+ // Try with reversed bypass rules.
+ rules.reverse_bypass = true;
+
+ rules.Apply(GURL("http://example.org"), &result);
+ EXPECT_TRUE(result.is_direct_only());
+ EXPECT_TRUE(result.did_bypass_proxy());
+
+ rules.Apply(GURL("http://example.com"), &result);
+ EXPECT_FALSE(result.is_direct());
+ EXPECT_FALSE(result.did_bypass_proxy());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/proxy/proxy_info.cc b/chromium/net/proxy/proxy_info.cc
new file mode 100644
index 00000000000..b6fcc86767f
--- /dev/null
+++ b/chromium/net/proxy/proxy_info.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_info.h"
+
+#include "net/proxy/proxy_retry_info.h"
+
+namespace net {
+
+ProxyInfo::ProxyInfo()
+ : config_id_(ProxyConfig::kInvalidConfigID),
+ config_source_(PROXY_CONFIG_SOURCE_UNKNOWN),
+ did_bypass_proxy_(false),
+ did_use_pac_script_(false) {
+}
+
+ProxyInfo::~ProxyInfo() {
+}
+
+void ProxyInfo::Use(const ProxyInfo& other) {
+ proxy_resolve_start_time_ = other.proxy_resolve_start_time_;
+ proxy_resolve_end_time_ = other.proxy_resolve_end_time_;
+ proxy_list_ = other.proxy_list_;
+ proxy_retry_info_ = other.proxy_retry_info_;
+ config_id_ = other.config_id_;
+ config_source_ = other.config_source_;
+ did_bypass_proxy_ = other.did_bypass_proxy_;
+ did_use_pac_script_ = other.did_use_pac_script_;
+}
+
+void ProxyInfo::UseDirect() {
+ Reset();
+ proxy_list_.SetSingleProxyServer(ProxyServer::Direct());
+}
+
+void ProxyInfo::UseDirectWithBypassedProxy() {
+ UseDirect();
+ did_bypass_proxy_ = true;
+}
+
+void ProxyInfo::UseNamedProxy(const std::string& proxy_uri_list) {
+ Reset();
+ proxy_list_.Set(proxy_uri_list);
+}
+
+void ProxyInfo::UseProxyServer(const ProxyServer& proxy_server) {
+ Reset();
+ proxy_list_.SetSingleProxyServer(proxy_server);
+}
+
+void ProxyInfo::UsePacString(const std::string& pac_string) {
+ Reset();
+ proxy_list_.SetFromPacString(pac_string);
+}
+
+void ProxyInfo::UseProxyList(const ProxyList& proxy_list) {
+ Reset();
+ proxy_list_ = proxy_list;
+}
+
+std::string ProxyInfo::ToPacString() const {
+ return proxy_list_.ToPacString();
+}
+
+bool ProxyInfo::Fallback(const BoundNetLog& net_log) {
+ return proxy_list_.Fallback(&proxy_retry_info_, net_log);
+}
+
+void ProxyInfo::DeprioritizeBadProxies(
+ const ProxyRetryInfoMap& proxy_retry_info) {
+ proxy_list_.DeprioritizeBadProxies(proxy_retry_info);
+}
+
+void ProxyInfo::RemoveProxiesWithoutScheme(int scheme_bit_field) {
+ proxy_list_.RemoveProxiesWithoutScheme(scheme_bit_field);
+}
+
+void ProxyInfo::Reset() {
+ proxy_resolve_start_time_ = base::TimeTicks();
+ proxy_resolve_end_time_ = base::TimeTicks();
+ proxy_list_.Clear();
+ proxy_retry_info_.clear();
+ config_id_ = ProxyConfig::kInvalidConfigID;
+ config_source_ = PROXY_CONFIG_SOURCE_UNKNOWN;
+ did_bypass_proxy_ = false;
+ did_use_pac_script_ = false;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_info.h b/chromium/net/proxy/proxy_info.h
new file mode 100644
index 00000000000..243cbba80fd
--- /dev/null
+++ b/chromium/net/proxy/proxy_info.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_INFO_H_
+#define NET_PROXY_PROXY_INFO_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_list.h"
+#include "net/proxy/proxy_retry_info.h"
+#include "net/proxy/proxy_server.h"
+
+namespace net {
+
+// This object holds proxy information returned by ResolveProxy.
+class NET_EXPORT ProxyInfo {
+ public:
+ ProxyInfo();
+ ~ProxyInfo();
+ // Default copy-constructor and assignment operator are OK!
+
+ // Uses the same proxy server as the given |proxy_info|.
+ void Use(const ProxyInfo& proxy_info);
+
+ // Uses a direct connection.
+ void UseDirect();
+
+ // Uses a direct connection. did_bypass_proxy() will return true to indicate
+ // that the direct connection is the result of configured proxy bypass rules.
+ void UseDirectWithBypassedProxy();
+
+ // Uses a specific proxy server, of the form:
+ // proxy-uri = [<scheme> "://"] <hostname> [":" <port>]
+ // This may optionally be a semi-colon delimited list of <proxy-uri>.
+ // It is OK to have LWS between entries.
+ void UseNamedProxy(const std::string& proxy_uri_list);
+
+ // Sets the proxy list to a single entry, |proxy_server|.
+ void UseProxyServer(const ProxyServer& proxy_server);
+
+ // Parses from the given PAC result.
+ void UsePacString(const std::string& pac_string);
+
+ // Use the proxies from the given list.
+ void UseProxyList(const ProxyList& proxy_list);
+
+ // Returns true if this proxy info specifies a direct connection.
+ bool is_direct() const {
+ // We don't implicitly fallback to DIRECT unless it was added to the list.
+ if (is_empty())
+ return false;
+ return proxy_list_.Get().is_direct();
+ }
+
+ bool is_direct_only() const {
+ return is_direct() && proxy_list_.size() == 1 && proxy_retry_info_.empty();
+ }
+
+ // Returns true if the first valid proxy server is an https proxy.
+ bool is_https() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_https();
+ }
+
+ // Returns true if the first valid proxy server is an http proxy.
+ bool is_http() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_http();
+ }
+
+ // Returns true if the first valid proxy server is a socks server.
+ bool is_socks() const {
+ if (is_empty())
+ return false;
+ return proxy_server().is_socks();
+ }
+
+ // Returns true if this proxy info has no proxies left to try.
+ bool is_empty() const {
+ return proxy_list_.IsEmpty();
+ }
+
+ // Returns true if this proxy resolution is using a direct connection due to
+ // proxy bypass rules.
+ bool did_bypass_proxy() const {
+ return did_bypass_proxy_;
+ }
+
+ // Returns true if the proxy resolution was done using a PAC script.
+ bool did_use_pac_script() const {
+ return did_use_pac_script_;
+ }
+
+ // Returns the first valid proxy server. is_empty() must be false to be able
+ // to call this function.
+ const ProxyServer& proxy_server() const { return proxy_list_.Get(); }
+
+ // Returns the source for configuration settings used for proxy resolution.
+ ProxyConfigSource config_source() const { return config_source_; }
+
+ // See description in ProxyList::ToPacString().
+ std::string ToPacString() const;
+
+ // Marks the current proxy as bad. Returns true if there is another proxy
+ // available to try in proxy list_.
+ bool Fallback(const BoundNetLog& net_log);
+
+ // De-prioritizes the proxies that we have cached as not working, by moving
+ // them to the end of the proxy list.
+ void DeprioritizeBadProxies(const ProxyRetryInfoMap& proxy_retry_info);
+
+ // Deletes any entry which doesn't have one of the specified proxy schemes.
+ void RemoveProxiesWithoutScheme(int scheme_bit_field);
+
+ ProxyConfig::ID config_id() const { return config_id_; }
+
+ base::TimeTicks proxy_resolve_start_time() const {
+ return proxy_resolve_start_time_;
+ }
+
+ base::TimeTicks proxy_resolve_end_time() const {
+ return proxy_resolve_end_time_;
+ }
+
+ private:
+ friend class ProxyService;
+
+ const ProxyRetryInfoMap& proxy_retry_info() const {
+ return proxy_retry_info_;
+ }
+
+ // Reset proxy and config settings.
+ void Reset();
+
+ // The ordered list of proxy servers (including DIRECT attempts) remaining to
+ // try. If proxy_list_ is empty, then there is nothing left to fall back to.
+ ProxyList proxy_list_;
+
+ // List of proxies that have been tried already.
+ ProxyRetryInfoMap proxy_retry_info_;
+
+ // This value identifies the proxy config used to initialize this object.
+ ProxyConfig::ID config_id_;
+
+ // The source of the proxy settings used,
+ ProxyConfigSource config_source_;
+
+ // Whether the proxy result represent a proxy bypass.
+ bool did_bypass_proxy_;
+
+ // Whether we used a PAC script for resolving the proxy.
+ bool did_use_pac_script_;
+
+ // How long it took to resolve the proxy. Times are both null if proxy was
+ // determined synchronously without running a PAC.
+ base::TimeTicks proxy_resolve_start_time_;
+ base::TimeTicks proxy_resolve_end_time_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_INFO_H_
diff --git a/chromium/net/proxy/proxy_info_unittest.cc b/chromium/net/proxy/proxy_info_unittest.cc
new file mode 100644
index 00000000000..377cff3438a
--- /dev/null
+++ b/chromium/net/proxy/proxy_info_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+TEST(ProxyInfoTest, ProxyInfoIsDirectOnly) {
+ // Test the is_direct_only() predicate.
+ ProxyInfo info;
+
+ // An empty ProxyInfo is not considered direct.
+ EXPECT_FALSE(info.is_direct_only());
+
+ info.UseDirect();
+ EXPECT_TRUE(info.is_direct_only());
+
+ info.UsePacString("DIRECT");
+ EXPECT_TRUE(info.is_direct_only());
+
+ info.UsePacString("PROXY myproxy:80");
+ EXPECT_FALSE(info.is_direct_only());
+
+ info.UsePacString("DIRECT; PROXY myproxy:80");
+ EXPECT_TRUE(info.is_direct());
+ EXPECT_FALSE(info.is_direct_only());
+
+ info.UsePacString("PROXY myproxy:80; DIRECT");
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_FALSE(info.is_direct_only());
+ // After falling back to direct, we shouldn't consider it DIRECT only.
+ EXPECT_TRUE(info.Fallback(BoundNetLog()));
+ EXPECT_TRUE(info.is_direct());
+ EXPECT_FALSE(info.is_direct_only());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/proxy/proxy_list.cc b/chromium/net/proxy/proxy_list.cc
new file mode 100644
index 00000000000..baa638f6c7d
--- /dev/null
+++ b/chromium/net/proxy/proxy_list.cc
@@ -0,0 +1,230 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_list.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/proxy/proxy_server.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+
+ProxyList::ProxyList() {
+}
+
+ProxyList::~ProxyList() {
+}
+
+void ProxyList::Set(const std::string& proxy_uri_list) {
+ proxies_.clear();
+ base::StringTokenizer str_tok(proxy_uri_list, ";");
+ while (str_tok.GetNext()) {
+ ProxyServer uri = ProxyServer::FromURI(
+ str_tok.token_begin(), str_tok.token_end(), ProxyServer::SCHEME_HTTP);
+ // Silently discard malformed inputs.
+ if (uri.is_valid())
+ proxies_.push_back(uri);
+ }
+}
+
+void ProxyList::SetSingleProxyServer(const ProxyServer& proxy_server) {
+ proxies_.clear();
+ AddProxyServer(proxy_server);
+}
+
+void ProxyList::AddProxyServer(const ProxyServer& proxy_server) {
+ if (proxy_server.is_valid())
+ proxies_.push_back(proxy_server);
+}
+
+void ProxyList::DeprioritizeBadProxies(
+ const ProxyRetryInfoMap& proxy_retry_info) {
+ // Partition the proxy list in two:
+ // (1) the known bad proxies
+ // (2) everything else
+ std::vector<ProxyServer> good_proxies;
+ std::vector<ProxyServer> bad_proxies;
+
+ std::vector<ProxyServer>::const_iterator iter = proxies_.begin();
+ for (; iter != proxies_.end(); ++iter) {
+ ProxyRetryInfoMap::const_iterator bad_proxy =
+ proxy_retry_info.find(iter->ToURI());
+ if (bad_proxy != proxy_retry_info.end()) {
+ // This proxy is bad. Check if it's time to retry.
+ if (bad_proxy->second.bad_until >= TimeTicks::Now()) {
+ // still invalid.
+ bad_proxies.push_back(*iter);
+ continue;
+ }
+ }
+ good_proxies.push_back(*iter);
+ }
+
+ // "proxies_ = good_proxies + bad_proxies"
+ proxies_.swap(good_proxies);
+ proxies_.insert(proxies_.end(), bad_proxies.begin(), bad_proxies.end());
+}
+
+bool ProxyList::HasUntriedProxies(
+ const ProxyRetryInfoMap& proxy_retry_info) const {
+ std::vector<ProxyServer>::const_iterator iter = proxies_.begin();
+ for (; iter != proxies_.end(); ++iter) {
+ ProxyRetryInfoMap::const_iterator bad_proxy =
+ proxy_retry_info.find(iter->ToURI());
+ if (bad_proxy != proxy_retry_info.end()) {
+ // This proxy is bad. Check if it's time to retry.
+ if (bad_proxy->second.bad_until >= TimeTicks::Now()) {
+ continue;
+ }
+ }
+ // Either we've found the entry in the retry map and it's expired or we
+ // didn't find a corresponding entry in the retry map. In either case, we
+ // have a proxy to try.
+ return true;
+ }
+ return false;
+}
+
+void ProxyList::RemoveProxiesWithoutScheme(int scheme_bit_field) {
+ for (std::vector<ProxyServer>::iterator it = proxies_.begin();
+ it != proxies_.end(); ) {
+ if (!(scheme_bit_field & it->scheme())) {
+ it = proxies_.erase(it);
+ continue;
+ }
+ ++it;
+ }
+}
+
+void ProxyList::Clear() {
+ proxies_.clear();
+}
+
+bool ProxyList::IsEmpty() const {
+ return proxies_.empty();
+}
+
+size_t ProxyList::size() const {
+ return proxies_.size();
+}
+
+// Returns true if |*this| lists the same proxies as |other|.
+bool ProxyList::Equals(const ProxyList& other) const {
+ if (size() != other.size())
+ return false;
+ return proxies_ == other.proxies_;
+}
+
+const ProxyServer& ProxyList::Get() const {
+ DCHECK(!proxies_.empty());
+ return proxies_[0];
+}
+
+void ProxyList::SetFromPacString(const std::string& pac_string) {
+ base::StringTokenizer entry_tok(pac_string, ";");
+ proxies_.clear();
+ while (entry_tok.GetNext()) {
+ ProxyServer uri = ProxyServer::FromPacString(
+ entry_tok.token_begin(), entry_tok.token_end());
+ // Silently discard malformed inputs.
+ if (uri.is_valid())
+ proxies_.push_back(uri);
+ }
+
+ // If we failed to parse anything from the PAC results list, fallback to
+ // DIRECT (this basically means an error in the PAC script).
+ if (proxies_.empty()) {
+ proxies_.push_back(ProxyServer::Direct());
+ }
+}
+
+std::string ProxyList::ToPacString() const {
+ std::string proxy_list;
+ std::vector<ProxyServer>::const_iterator iter = proxies_.begin();
+ for (; iter != proxies_.end(); ++iter) {
+ if (!proxy_list.empty())
+ proxy_list += ";";
+ proxy_list += iter->ToPacString();
+ }
+ return proxy_list.empty() ? std::string() : proxy_list;
+}
+
+base::ListValue* ProxyList::ToValue() const {
+ base::ListValue* list = new base::ListValue();
+ for (size_t i = 0; i < proxies_.size(); ++i)
+ list->AppendString(proxies_[i].ToURI());
+ return list;
+}
+
+bool ProxyList::Fallback(ProxyRetryInfoMap* proxy_retry_info,
+ const BoundNetLog& net_log) {
+
+ // TODO(eroman): It would be good if instead of removing failed proxies
+ // from the list, we simply annotated them with the error code they failed
+ // with. Of course, ProxyService::ReconsiderProxyAfterError() would need to
+ // be given this information by the network transaction.
+ //
+ // The advantage of this approach is when the network transaction
+ // fails, we could output the full list of proxies that were attempted, and
+ // why each one of those failed (as opposed to just the last failure).
+ //
+ // And also, before failing the transaction wholesale, we could go back and
+ // retry the "bad proxies" which we never tried to begin with.
+ // (RemoveBadProxies would annotate them as 'expected bad' rather then delete
+ // them from the list, so we would know what they were).
+
+ if (proxies_.empty()) {
+ NOTREACHED();
+ return false;
+ }
+ UpdateRetryInfoOnFallback(proxy_retry_info, net_log);
+
+ // Remove this proxy from our list.
+ proxies_.erase(proxies_.begin());
+ return !proxies_.empty();
+}
+
+void ProxyList::UpdateRetryInfoOnFallback(
+ ProxyRetryInfoMap* proxy_retry_info, const BoundNetLog& net_log) const {
+ // Time to wait before retrying a bad proxy server.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+ // Randomize the timeout over a range from one to five minutes.
+ const TimeDelta proxy_retry_delay =
+ TimeDelta::FromMilliseconds(base::RandInt(1 * 60 * 1000, 5 * 60 * 1000));
+#else
+ const TimeDelta proxy_retry_delay = TimeDelta::FromMinutes(5);
+#endif
+
+ if (proxies_.empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ if (!proxies_[0].is_direct()) {
+ std::string key = proxies_[0].ToURI();
+ // Mark this proxy as bad.
+ ProxyRetryInfoMap::iterator iter = proxy_retry_info->find(key);
+ if (iter != proxy_retry_info->end()) {
+ // TODO(nsylvain): This is not the first time we get this. We should
+ // double the retry time. Bug 997660.
+ iter->second.bad_until = TimeTicks::Now() + iter->second.current_delay;
+ } else {
+ ProxyRetryInfo retry_info;
+ retry_info.current_delay = proxy_retry_delay;
+ retry_info.bad_until = TimeTicks().Now() + retry_info.current_delay;
+ (*proxy_retry_info)[key] = retry_info;
+ }
+ net_log.AddEvent(NetLog::TYPE_PROXY_LIST_FALLBACK,
+ NetLog::StringCallback("bad_proxy", &key));
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_list.h b/chromium/net/proxy/proxy_list.h
new file mode 100644
index 00000000000..9f5fa59db06
--- /dev/null
+++ b/chromium/net/proxy/proxy_list.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_LIST_H_
+#define NET_PROXY_PROXY_LIST_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/proxy/proxy_retry_info.h"
+
+namespace base {
+class ListValue;
+}
+
+namespace net {
+
+class ProxyServer;
+
+// This class is used to hold a list of proxies returned by GetProxyForUrl or
+// manually configured. It handles proxy fallback if multiple servers are
+// specified.
+class NET_EXPORT_PRIVATE ProxyList {
+ public:
+ ProxyList();
+ ~ProxyList();
+
+ // Initializes the proxy list to a string containing one or more proxy servers
+ // delimited by a semicolon.
+ void Set(const std::string& proxy_uri_list);
+
+ // Set the proxy list to a single entry, |proxy_server|.
+ void SetSingleProxyServer(const ProxyServer& proxy_server);
+
+ // Append a single proxy server to the end of the proxy list.
+ void AddProxyServer(const ProxyServer& proxy_server);
+
+ // De-prioritizes the proxies that we have cached as not working, by moving
+ // them to the end of the fallback list.
+ void DeprioritizeBadProxies(const ProxyRetryInfoMap& proxy_retry_info);
+
+ // Returns true if this proxy list contains at least one proxy that is
+ // not currently present in |proxy_retry_info|.
+ bool HasUntriedProxies(const ProxyRetryInfoMap& proxy_retry_info) const;
+
+ // Delete any entry which doesn't have one of the specified proxy schemes.
+ // |scheme_bit_field| is a bunch of ProxyServer::Scheme bitwise ORed together.
+ void RemoveProxiesWithoutScheme(int scheme_bit_field);
+
+ // Clear the proxy list.
+ void Clear();
+
+ // Returns true if there is nothing left in the ProxyList.
+ bool IsEmpty() const;
+
+ // Returns the number of proxy servers in this list.
+ size_t size() const;
+
+ // Returns true if |*this| lists the same proxies as |other|.
+ bool Equals(const ProxyList& other) const;
+
+ // Returns the first proxy server in the list. It is only valid to call
+ // this if !IsEmpty().
+ const ProxyServer& Get() const;
+
+ // Sets the list by parsing the pac result |pac_string|.
+ // Some examples for |pac_string|:
+ // "DIRECT"
+ // "PROXY foopy1"
+ // "PROXY foopy1; SOCKS4 foopy2:1188"
+ // Does a best-effort parse, and silently discards any errors.
+ void SetFromPacString(const std::string& pac_string);
+
+ // Returns a PAC-style semicolon-separated list of valid proxy servers.
+ // For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+ std::string ToPacString() const;
+
+ // Returns a serialized value for the list. The caller takes ownership of it.
+ base::ListValue* ToValue() const;
+
+ // Marks the current proxy server as bad and deletes it from the list. The
+ // list of known bad proxies is given by proxy_retry_info. Returns true if
+ // there is another server available in the list.
+ bool Fallback(ProxyRetryInfoMap* proxy_retry_info,
+ const BoundNetLog& net_log);
+
+ // Updates |proxy_retry_info| to indicate that the first proxy in the list
+ // is bad. This is distinct from Fallback(), above, to allow updating proxy
+ // retry information without modifying a given transction's proxy list.
+ void UpdateRetryInfoOnFallback(ProxyRetryInfoMap* proxy_retry_info,
+ const BoundNetLog& net_log) const;
+
+ private:
+ // List of proxies.
+ std::vector<ProxyServer> proxies_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_LIST_H_
diff --git a/chromium/net/proxy/proxy_list_unittest.cc b/chromium/net/proxy/proxy_list_unittest.cc
new file mode 100644
index 00000000000..470a9000882
--- /dev/null
+++ b/chromium/net/proxy/proxy_list_unittest.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2006-2008 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/proxy/proxy_list.h"
+
+#include "net/proxy/proxy_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Test parsing from a PAC string.
+TEST(ProxyListTest, SetFromPacString) {
+ const struct {
+ const char* pac_input;
+ const char* pac_output;
+ } tests[] = {
+ // Valid inputs:
+ { "PROXY foopy:10",
+ "PROXY foopy:10",
+ },
+ { " DIRECT", // leading space.
+ "DIRECT",
+ },
+ { "PROXY foopy1 ; proxy foopy2;\t DIRECT",
+ "PROXY foopy1:80;PROXY foopy2:80;DIRECT",
+ },
+ { "proxy foopy1 ; SOCKS foopy2",
+ "PROXY foopy1:80;SOCKS foopy2:1080",
+ },
+ // Try putting DIRECT first.
+ { "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
+ "DIRECT;PROXY foopy1:80;DIRECT;SOCKS5 foopy2:1080;DIRECT",
+ },
+ // Try putting DIRECT consecutively.
+ { "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
+ "DIRECT;PROXY foopy1:80;DIRECT;DIRECT",
+ },
+
+ // Invalid inputs (parts which aren't understood get
+ // silently discarded):
+ //
+ // If the proxy list string parsed to empty, automatically fall-back to
+ // DIRECT.
+ { "PROXY-foopy:10",
+ "DIRECT",
+ },
+ { "PROXY",
+ "DIRECT",
+ },
+ { "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
+ "PROXY foopy1:80;SOCKS5 foopy2:1080",
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ ProxyList list;
+ list.SetFromPacString(tests[i].pac_input);
+ EXPECT_EQ(tests[i].pac_output, list.ToPacString());
+ EXPECT_FALSE(list.IsEmpty());
+ }
+}
+
+TEST(ProxyListTest, RemoveProxiesWithoutScheme) {
+ const struct {
+ const char* pac_input;
+ int filter;
+ const char* filtered_pac_output;
+ } tests[] = {
+ { "PROXY foopy:10 ; SOCKS5 foopy2 ; SOCKS foopy11 ; PROXY foopy3 ; DIRECT",
+ // Remove anything that isn't HTTP or DIRECT.
+ ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_HTTP,
+ "PROXY foopy:10;PROXY foopy3:80;DIRECT",
+ },
+ { "PROXY foopy:10 ; SOCKS5 foopy2",
+ // Remove anything that isn't HTTP or SOCKS5.
+ ProxyServer::SCHEME_DIRECT | ProxyServer::SCHEME_SOCKS4,
+ "",
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ ProxyList list;
+ list.SetFromPacString(tests[i].pac_input);
+ list.RemoveProxiesWithoutScheme(tests[i].filter);
+ EXPECT_EQ(tests[i].filtered_pac_output, list.ToPacString());
+ }
+}
+
+TEST(ProxyListTest, HasUntriedProxies) {
+ // As in DeprioritizeBadProxies, we use a lengthy timeout to avoid depending
+ // on the current time.
+ ProxyRetryInfo proxy_retry_info;
+ proxy_retry_info.bad_until =
+ base::TimeTicks::Now() + base::TimeDelta::FromDays(1);
+
+ // An empty list has nothing to try.
+ {
+ ProxyList list;
+ ProxyRetryInfoMap proxy_retry_info;
+ EXPECT_FALSE(list.HasUntriedProxies(proxy_retry_info));
+ }
+
+ // A list with one bad proxy has something to try. With two bad proxies,
+ // there's nothing to try.
+ {
+ ProxyList list;
+ list.SetFromPacString("PROXY bad1:80; PROXY bad2:80");
+ ProxyRetryInfoMap retry_info_map;
+ retry_info_map["bad1:80"] = proxy_retry_info;
+ EXPECT_TRUE(list.HasUntriedProxies(retry_info_map));
+ retry_info_map["bad2:80"] = proxy_retry_info;
+ EXPECT_FALSE(list.HasUntriedProxies(retry_info_map));
+ }
+
+ // A list with one bad proxy and a DIRECT entry has something to try.
+ {
+ ProxyList list;
+ list.SetFromPacString("PROXY bad1:80; DIRECT");
+ ProxyRetryInfoMap retry_info_map;
+ retry_info_map["bad1:80"] = proxy_retry_info;
+ EXPECT_TRUE(list.HasUntriedProxies(retry_info_map));
+ }
+}
+
+TEST(ProxyListTest, DeprioritizeBadProxies) {
+ // Retry info that marks a proxy as being bad for a *very* long time (to avoid
+ // the test depending on the current time.)
+ ProxyRetryInfo proxy_retry_info;
+ proxy_retry_info.bad_until =
+ base::TimeTicks::Now() + base::TimeDelta::FromDays(1);
+
+ // Call DeprioritizeBadProxies with an empty map -- should have no effect.
+ {
+ ProxyList list;
+ list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
+
+ ProxyRetryInfoMap retry_info_map;
+ list.DeprioritizeBadProxies(retry_info_map);
+ EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80",
+ list.ToPacString());
+ }
+
+ // Call DeprioritizeBadProxies with 2 of the three proxies marked as bad.
+ // These proxies should be retried last.
+ {
+ ProxyList list;
+ list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
+
+ ProxyRetryInfoMap retry_info_map;
+ retry_info_map["foopy1:80"] = proxy_retry_info;
+ retry_info_map["foopy3:80"] = proxy_retry_info;
+ retry_info_map["socks5://localhost:1080"] = proxy_retry_info;
+
+ list.DeprioritizeBadProxies(retry_info_map);
+
+ EXPECT_EQ("PROXY foopy2:80;PROXY foopy1:80;PROXY foopy3:80",
+ list.ToPacString());
+ }
+
+ // Call DeprioritizeBadProxies where ALL of the proxies are marked as bad.
+ // This should have no effect on the order.
+ {
+ ProxyList list;
+ list.SetFromPacString("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80");
+
+ ProxyRetryInfoMap retry_info_map;
+ retry_info_map["foopy1:80"] = proxy_retry_info;
+ retry_info_map["foopy2:80"] = proxy_retry_info;
+ retry_info_map["foopy3:80"] = proxy_retry_info;
+
+ list.DeprioritizeBadProxies(retry_info_map);
+
+ EXPECT_EQ("PROXY foopy1:80;PROXY foopy2:80;PROXY foopy3:80",
+ list.ToPacString());
+ }
+}
+
+} // namesapce
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver.h b/chromium/net/proxy/proxy_resolver.h
new file mode 100644
index 00000000000..47ec31af5c6
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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_PROXY_PROXY_RESOLVER_H_
+#define NET_PROXY_PROXY_RESOLVER_H_
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_resolver_script_data.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class BoundNetLog;
+class ProxyInfo;
+
+// Interface for "proxy resolvers". A ProxyResolver fills in a list of proxies
+// to use for a particular URL. Generally the backend for a ProxyResolver is
+// a PAC script, but it doesn't need to be. ProxyResolver can service multiple
+// requests at a time.
+class NET_EXPORT_PRIVATE ProxyResolver {
+ public:
+ // Opaque pointer type, to return a handle to cancel outstanding requests.
+ typedef void* RequestHandle;
+
+ // See |expects_pac_bytes()| for the meaning of |expects_pac_bytes|.
+ explicit ProxyResolver(bool expects_pac_bytes)
+ : expects_pac_bytes_(expects_pac_bytes) {}
+
+ virtual ~ProxyResolver() {}
+
+ // Gets a list of proxy servers to use for |url|. If the request will
+ // complete asynchronously returns ERR_IO_PENDING and notifies the result
+ // by running |callback|. If the result code is OK then
+ // the request was successful and |results| contains the proxy
+ // resolution information. In the case of asynchronous completion
+ // |*request| is written to, and can be passed to CancelRequest().
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) = 0;
+
+ // Cancels |request|.
+ virtual void CancelRequest(RequestHandle request) = 0;
+
+ // Gets the LoadState for |request|.
+ virtual LoadState GetLoadState(RequestHandle request) const = 0;
+
+ // The PAC script backend can be specified to the ProxyResolver either via
+ // URL, or via the javascript text itself. If |expects_pac_bytes| is true,
+ // then the ProxyResolverScriptData passed to SetPacScript() should
+ // contain the actual script bytes rather than just the URL.
+ bool expects_pac_bytes() const { return expects_pac_bytes_; }
+
+ virtual void CancelSetPacScript() = 0;
+
+ // Frees any unneeded memory held by the resolver, e.g. garbage in the JS
+ // engine. Most subclasses don't need to do anything, so we provide a default
+ // no-op implementation.
+ virtual void PurgeMemory() {}
+
+ // Called to set the PAC script backend to use.
+ // Returns ERR_IO_PENDING in the case of asynchronous completion, and notifies
+ // the result through |callback|.
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& pac_script,
+ const net::CompletionCallback& callback) = 0;
+
+ private:
+ const bool expects_pac_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_H_
diff --git a/chromium/net/proxy/proxy_resolver_error_observer.h b/chromium/net/proxy/proxy_resolver_error_observer.h
new file mode 100644
index 00000000000..190b78759ba
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_error_observer.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 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_PROXY_PROXY_RESOLVER_ERROR_OBSERVER_H_
+#define NET_PROXY_PROXY_RESOLVER_ERROR_OBSERVER_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Interface for observing JavaScript error messages from PAC scripts.
+class NET_EXPORT_PRIVATE ProxyResolverErrorObserver {
+ public:
+ ProxyResolverErrorObserver() {}
+ virtual ~ProxyResolverErrorObserver() {}
+
+ // Handler for when an error is encountered. |line_number| may be -1
+ // if a line number is not applicable to this error. |error| is a message
+ // describing the error.
+ //
+ // Note on threading: This may get called from a worker thread. If the
+ // backing proxy resolver is ProxyResolverV8Tracing, then it will not
+ // be called concurrently, however it will be called from a different
+ // thread than the proxy resolver's origin thread.
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverErrorObserver);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_ERROR_OBSERVER_H_
diff --git a/chromium/net/proxy/proxy_resolver_mac.cc b/chromium/net/proxy/proxy_resolver_mac.cc
new file mode 100644
index 00000000000..8ddf81fffe4
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_mac.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2011 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/proxy/proxy_resolver_mac.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_server.h"
+
+#if defined(OS_IOS)
+#include <CFNetwork/CFProxySupport.h>
+#else
+#include <CoreServices/CoreServices.h>
+#endif
+
+namespace {
+
+// Utility function to map a CFProxyType to a ProxyServer::Scheme.
+// If the type is unknown, returns ProxyServer::SCHEME_INVALID.
+net::ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) {
+ if (CFEqual(proxy_type, kCFProxyTypeNone))
+ return net::ProxyServer::SCHEME_DIRECT;
+ if (CFEqual(proxy_type, kCFProxyTypeHTTP))
+ return net::ProxyServer::SCHEME_HTTP;
+ if (CFEqual(proxy_type, kCFProxyTypeHTTPS)) {
+ // The "HTTPS" on the Mac side here means "proxy applies to https://" URLs;
+ // the proxy itself is still expected to be an HTTP proxy.
+ return net::ProxyServer::SCHEME_HTTP;
+ }
+ if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) {
+ // We can't tell whether this was v4 or v5. We will assume it is
+ // v5 since that is the only version OS X supports.
+ return net::ProxyServer::SCHEME_SOCKS5;
+ }
+ return net::ProxyServer::SCHEME_INVALID;
+}
+
+// Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
+// to a CFTypeRef. This stashes either |error| or |proxies| in that location.
+void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
+ DCHECK((proxies != NULL) == (error == NULL));
+
+ CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
+ DCHECK(result_ptr != NULL);
+ DCHECK(*result_ptr == NULL);
+
+ if (error != NULL) {
+ *result_ptr = CFRetain(error);
+ } else {
+ *result_ptr = CFRetain(proxies);
+ }
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+} // namespace
+
+namespace net {
+
+ProxyResolverMac::ProxyResolverMac()
+ : ProxyResolver(false /*expects_pac_bytes*/) {
+}
+
+ProxyResolverMac::~ProxyResolverMac() {}
+
+// Gets the proxy information for a query URL from a PAC. Implementation
+// inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
+int ProxyResolverMac::GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ const CompletionCallback& /*callback*/,
+ RequestHandle* /*request*/,
+ const BoundNetLog& net_log) {
+ base::ScopedCFTypeRef<CFStringRef> query_ref(
+ base::SysUTF8ToCFStringRef(query_url.spec()));
+ base::ScopedCFTypeRef<CFURLRef> query_url_ref(
+ CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), NULL));
+ if (!query_url_ref.get())
+ return ERR_FAILED;
+ base::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef(
+ script_data_->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT
+ ? std::string()
+ : script_data_->url().spec()));
+ base::ScopedCFTypeRef<CFURLRef> pac_url_ref(
+ CFURLCreateWithString(kCFAllocatorDefault, pac_ref.get(), NULL));
+ if (!pac_url_ref.get())
+ return ERR_FAILED;
+
+ // Work around <rdar://problem/5530166>. This dummy call to
+ // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
+ // required by CFNetworkExecuteProxyAutoConfigurationURL.
+
+ CFArrayRef dummy_result = CFNetworkCopyProxiesForURL(query_url_ref.get(),
+ NULL);
+ if (dummy_result)
+ CFRelease(dummy_result);
+
+ // We cheat here. We need to act as if we were synchronous, so we pump the
+ // runloop ourselves. Our caller moved us to a new thread anyway, so this is
+ // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
+ // runloop source we need to release despite its name.)
+
+ CFTypeRef result = NULL;
+ CFStreamClientContext context = { 0, &result, NULL, NULL, NULL };
+ base::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
+ CFNetworkExecuteProxyAutoConfigurationURL(
+ pac_url_ref.get(), query_url_ref.get(), ResultCallback, &context));
+ if (!runloop_source)
+ return ERR_FAILED;
+
+ const CFStringRef private_runloop_mode =
+ CFSTR("org.chromium.ProxyResolverMac");
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
+ private_runloop_mode);
+ CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
+ private_runloop_mode);
+ DCHECK(result != NULL);
+
+ if (CFGetTypeID(result) == CFErrorGetTypeID()) {
+ // TODO(avi): do something better than this
+ CFRelease(result);
+ return ERR_FAILED;
+ }
+ base::ScopedCFTypeRef<CFArrayRef> proxy_array_ref(
+ base::mac::CFCastStrict<CFArrayRef>(result));
+ DCHECK(proxy_array_ref != NULL);
+
+ // This string will be an ordered list of <proxy-uri> entries, separated by
+ // semi-colons. It is the format that ProxyInfo::UseNamedProxy() expects.
+ // proxy-uri = [<proxy-scheme>"://"]<proxy-host>":"<proxy-port>
+ // (This also includes entries for direct connection, as "direct://").
+ std::string proxy_uri_list;
+
+ CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
+ for (CFIndex i = 0; i < proxy_array_count; ++i) {
+ CFDictionaryRef proxy_dictionary = base::mac::CFCastStrict<CFDictionaryRef>(
+ CFArrayGetValueAtIndex(proxy_array_ref.get(), i));
+ DCHECK(proxy_dictionary != NULL);
+
+ // The dictionary may have the following keys:
+ // - kCFProxyTypeKey : The type of the proxy
+ // - kCFProxyHostNameKey
+ // - kCFProxyPortNumberKey : The meat we're after.
+ // - kCFProxyUsernameKey
+ // - kCFProxyPasswordKey : Despite the existence of these keys in the
+ // documentation, they're never populated. Even if a
+ // username/password were to be set in the network
+ // proxy system preferences, we'd need to fetch it
+ // from the Keychain ourselves. CFProxy is such a
+ // tease.
+ // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
+ // PAC file, I'm going home.
+
+ CFStringRef proxy_type = base::mac::GetValueFromDictionary<CFStringRef>(
+ proxy_dictionary, kCFProxyTypeKey);
+ ProxyServer proxy_server = ProxyServer::FromDictionary(
+ GetProxyServerScheme(proxy_type),
+ proxy_dictionary,
+ kCFProxyHostNameKey,
+ kCFProxyPortNumberKey);
+ if (!proxy_server.is_valid())
+ continue;
+
+ if (!proxy_uri_list.empty())
+ proxy_uri_list += ";";
+ proxy_uri_list += proxy_server.ToURI();
+ }
+
+ if (!proxy_uri_list.empty())
+ results->UseNamedProxy(proxy_uri_list);
+ // Else do nothing (results is already guaranteed to be in the default state).
+
+ return OK;
+}
+
+void ProxyResolverMac::CancelRequest(RequestHandle request) {
+ NOTREACHED();
+}
+
+LoadState ProxyResolverMac::GetLoadState(RequestHandle request) const {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+}
+
+void ProxyResolverMac::CancelSetPacScript() {
+ NOTREACHED();
+}
+
+int ProxyResolverMac::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& /*callback*/) {
+ script_data_ = script_data;
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_mac.h b/chromium/net/proxy/proxy_resolver_mac.h
new file mode 100644
index 00000000000..24eb10dac86
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_mac.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_PROXY_PROXY_RESOLVER_MAC_H_
+#define NET_PROXY_PROXY_RESOLVER_MAC_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_resolver.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// Implementation of ProxyResolver that uses the Mac CFProxySupport to implement
+// proxies.
+class NET_EXPORT ProxyResolverMac : public ProxyResolver {
+ public:
+ ProxyResolverMac();
+ virtual ~ProxyResolverMac();
+
+ // ProxyResolver methods:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE;
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE;
+
+ virtual void CancelSetPacScript() OVERRIDE;
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const net::CompletionCallback& /*callback*/) OVERRIDE;
+
+ private:
+ scoped_refptr<ProxyResolverScriptData> script_data_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_MAC_H_
diff --git a/chromium/net/proxy/proxy_resolver_perftest.cc b/chromium/net/proxy/proxy_resolver_perftest.cc
new file mode 100644
index 00000000000..3faf3961b81
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_perftest.cc
@@ -0,0 +1,229 @@
+// Copyright (c) 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.
+
+#include "base/base_paths.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/perftimer.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_v8.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "net/proxy/proxy_resolver_winhttp.h"
+#elif defined(OS_MACOSX)
+#include "net/proxy/proxy_resolver_mac.h"
+#endif
+
+// This class holds the URL to use for resolving, and the expected result.
+// We track the expected result in order to make sure the performance
+// test is actually resolving URLs properly, otherwise the perf numbers
+// are meaningless :-)
+struct PacQuery {
+ const char* query_url;
+ const char* expected_result;
+};
+
+// Entry listing which PAC scripts to load, and which URLs to try resolving.
+// |queries| should be terminated by {NULL, NULL}. A sentinel is used
+// rather than a length, to simplify using initializer lists.
+struct PacPerfTest {
+ const char* pac_name;
+ PacQuery queries[100];
+
+ // Returns the actual number of entries in |queries| (assumes NULL sentinel).
+ int NumQueries() const;
+};
+
+// List of performance tests.
+static PacPerfTest kPerfTests[] = {
+ // This test uses an ad-blocker PAC script. This script is very heavily
+ // regular expression oriented, and has no dependencies on the current
+ // IP address, or DNS resolving of hosts.
+ { "no-ads.pac",
+ { // queries:
+ {"http://www.google.com", "DIRECT"},
+ {"http://www.imdb.com/photos/cmsicons/x", "PROXY 0.0.0.0:3421"},
+ {"http://www.imdb.com/x", "DIRECT"},
+ {"http://www.staples.com/", "DIRECT"},
+ {"http://www.staples.com/pixeltracker/x", "PROXY 0.0.0.0:3421"},
+ {"http://www.staples.com/pixel/x", "DIRECT"},
+ {"http://www.foobar.com", "DIRECT"},
+ {"http://www.foobarbaz.com/x/y/z", "DIRECT"},
+ {"http://www.testurl1.com/index.html", "DIRECT"},
+ {"http://www.testurl2.com", "DIRECT"},
+ {"https://www.sample/pirate/arrrrrr", "DIRECT"},
+ {NULL, NULL}
+ },
+ },
+};
+
+int PacPerfTest::NumQueries() const {
+ for (size_t i = 0; i < arraysize(queries); ++i) {
+ if (queries[i].query_url == NULL)
+ return i;
+ }
+ NOTREACHED(); // Bad definition.
+ return 0;
+}
+
+// The number of URLs to resolve when testing a PAC script.
+const int kNumIterations = 500;
+
+// Helper class to run through all the performance tests using the specified
+// proxy resolver implementation.
+class PacPerfSuiteRunner {
+ public:
+ // |resolver_name| is the label used when logging the results.
+ PacPerfSuiteRunner(net::ProxyResolver* resolver,
+ const std::string& resolver_name)
+ : resolver_(resolver),
+ resolver_name_(resolver_name),
+ test_server_(
+ net::SpawnedTestServer::TYPE_HTTP,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(
+ FILE_PATH_LITERAL("net/data/proxy_resolver_perftest"))) {
+ }
+
+ void RunAllTests() {
+ ASSERT_TRUE(test_server_.Start());
+ for (size_t i = 0; i < arraysize(kPerfTests); ++i) {
+ const PacPerfTest& test_data = kPerfTests[i];
+ RunTest(test_data.pac_name,
+ test_data.queries,
+ test_data.NumQueries());
+ }
+ }
+
+ private:
+ void RunTest(const std::string& script_name,
+ const PacQuery* queries,
+ int queries_len) {
+ if (!resolver_->expects_pac_bytes()) {
+ GURL pac_url =
+ test_server_.GetURL(std::string("files/") + script_name);
+ int rv = resolver_->SetPacScript(
+ net::ProxyResolverScriptData::FromURL(pac_url),
+ net::CompletionCallback());
+ EXPECT_EQ(net::OK, rv);
+ } else {
+ LoadPacScriptIntoResolver(script_name);
+ }
+
+ // Do a query to warm things up. In the case of internal-fetch proxy
+ // resolvers, the first resolve will be slow since it has to download
+ // the PAC script.
+ {
+ net::ProxyInfo proxy_info;
+ int result = resolver_->GetProxyForURL(
+ GURL("http://www.warmup.com"), &proxy_info, net::CompletionCallback(),
+ NULL, net::BoundNetLog());
+ ASSERT_EQ(net::OK, result);
+ }
+
+ // Start the perf timer.
+ std::string perf_test_name = resolver_name_ + "_" + script_name;
+ PerfTimeLogger timer(perf_test_name.c_str());
+
+ for (int i = 0; i < kNumIterations; ++i) {
+ // Round-robin between URLs to resolve.
+ const PacQuery& query = queries[i % queries_len];
+
+ // Resolve.
+ net::ProxyInfo proxy_info;
+ int result = resolver_->GetProxyForURL(
+ GURL(query.query_url), &proxy_info, net::CompletionCallback(), NULL,
+ net::BoundNetLog());
+
+ // Check that the result was correct. Note that ToPacString() and
+ // ASSERT_EQ() are fast, so they won't skew the results.
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(query.expected_result, proxy_info.ToPacString());
+ }
+
+ // Print how long the test ran for.
+ timer.Done();
+ }
+
+ // Read the PAC script from disk and initialize the proxy resolver with it.
+ void LoadPacScriptIntoResolver(const std::string& script_name) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_resolver_perftest");
+ path = path.AppendASCII(script_name);
+
+ // Try to read the file from disk.
+ std::string file_contents;
+ bool ok = file_util::ReadFileToString(path, &file_contents);
+
+ // If we can't load the file from disk, something is misconfigured.
+ LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value();
+ ASSERT_TRUE(ok);
+
+ // Load the PAC script into the ProxyResolver.
+ int rv = resolver_->SetPacScript(
+ net::ProxyResolverScriptData::FromUTF8(file_contents),
+ net::CompletionCallback());
+ EXPECT_EQ(net::OK, rv);
+ }
+
+ net::ProxyResolver* resolver_;
+ std::string resolver_name_;
+ net::SpawnedTestServer test_server_;
+};
+
+#if defined(OS_WIN)
+TEST(ProxyResolverPerfTest, ProxyResolverWinHttp) {
+ net::ProxyResolverWinHttp resolver;
+ PacPerfSuiteRunner runner(&resolver, "ProxyResolverWinHttp");
+ runner.RunAllTests();
+}
+#elif defined(OS_MACOSX)
+TEST(ProxyResolverPerfTest, ProxyResolverMac) {
+ net::ProxyResolverMac resolver;
+ PacPerfSuiteRunner runner(&resolver, "ProxyResolverMac");
+ runner.RunAllTests();
+}
+#endif
+
+class MockJSBindings : public net::ProxyResolverV8::JSBindings {
+ public:
+ MockJSBindings() {}
+
+ virtual void Alert(const base::string16& message) OVERRIDE {
+ CHECK(false);
+ }
+
+ virtual bool ResolveDns(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) OVERRIDE {
+ CHECK(false);
+ return false;
+ }
+
+ virtual void OnError(int line_number,
+ const base::string16& message) OVERRIDE {
+ CHECK(false);
+ }
+};
+
+TEST(ProxyResolverPerfTest, ProxyResolverV8) {
+ // This has to be done on the main thread.
+ net::ProxyResolverV8::RememberDefaultIsolate();
+
+ MockJSBindings js_bindings;
+ net::ProxyResolverV8 resolver;
+ resolver.set_js_bindings(&js_bindings);
+ PacPerfSuiteRunner runner(&resolver, "ProxyResolverV8");
+ runner.RunAllTests();
+}
diff --git a/chromium/net/proxy/proxy_resolver_script.h b/chromium/net/proxy/proxy_resolver_script.h
new file mode 100644
index 00000000000..283eff91454
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_script.h
@@ -0,0 +1,276 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Akhil Arora <akhil.arora@sun.com>
+ * Tomi Leppikangas <Tomi.Leppikangas@oulu.fi>
+ * Darin Fisher <darin@meer.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
+#define NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
+
+// The following code was formatted from:
+// 'mozilla/netwerk/base/src/nsProxyAutoConfig.js' (1.55)
+//
+// Using the command:
+// $ cat nsProxyAutoConfig.js |
+// awk '/var pacUtils/,/EOF/' |
+// sed -e 's/^\s*$/""/g' |
+// sed -e 's/"\s*[+]\s*$/"/g' |
+// sed -e 's/"$/" \\/g' |
+// sed -e 's/\/(ipaddr);/\/.exec(ipaddr);/g' |
+// grep -v '^var pacUtils ='
+#define PROXY_RESOLVER_SCRIPT \
+ "function dnsDomainIs(host, domain) {\n" \
+ " return (host.length >= domain.length &&\n" \
+ " host.substring(host.length - domain.length) == domain);\n" \
+ "}\n" \
+ "" \
+ "function dnsDomainLevels(host) {\n" \
+ " return host.split('.').length-1;\n" \
+ "}\n" \
+ "" \
+ "function convert_addr(ipchars) {\n" \
+ " var bytes = ipchars.split('.');\n" \
+ " var result = ((bytes[0] & 0xff) << 24) |\n" \
+ " ((bytes[1] & 0xff) << 16) |\n" \
+ " ((bytes[2] & 0xff) << 8) |\n" \
+ " (bytes[3] & 0xff);\n" \
+ " return result;\n" \
+ "}\n" \
+ "" \
+ "function isInNet(ipaddr, pattern, maskstr) {\n" \
+ " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" \
+ " if (test == null) {\n" \
+ " ipaddr = dnsResolve(ipaddr);\n" \
+ " if (ipaddr == null)\n" \
+ " return false;\n" \
+ " } else if (test[1] > 255 || test[2] > 255 || \n" \
+ " test[3] > 255 || test[4] > 255) {\n" \
+ " return false; // not an IP address\n" \
+ " }\n" \
+ " var host = convert_addr(ipaddr);\n" \
+ " var pat = convert_addr(pattern);\n" \
+ " var mask = convert_addr(maskstr);\n" \
+ " return ((host & mask) == (pat & mask));\n" \
+ " \n" \
+ "}\n" \
+ "" \
+ "function isPlainHostName(host) {\n" \
+ " return (host.search('\\\\.') == -1);\n" \
+ "}\n" \
+ "" \
+ "function isResolvable(host) {\n" \
+ " var ip = dnsResolve(host);\n" \
+ " return (ip != null);\n" \
+ "}\n" \
+ "" \
+ "function localHostOrDomainIs(host, hostdom) {\n" \
+ " return (host == hostdom) ||\n" \
+ " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" \
+ "}\n" \
+ "" \
+ "function shExpMatch(url, pattern) {\n" \
+ " pattern = pattern.replace(/\\./g, '\\\\.');\n" \
+ " pattern = pattern.replace(/\\*/g, '.*');\n" \
+ " pattern = pattern.replace(/\\?/g, '.');\n" \
+ " var newRe = new RegExp('^'+pattern+'$');\n" \
+ " return newRe.test(url);\n" \
+ "}\n" \
+ "" \
+ "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \
+ "" \
+ "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" \
+ "" \
+ "function weekdayRange() {\n" \
+ " function getDay(weekday) {\n" \
+ " if (weekday in wdays) {\n" \
+ " return wdays[weekday];\n" \
+ " }\n" \
+ " return -1;\n" \
+ " }\n" \
+ " var date = new Date();\n" \
+ " var argc = arguments.length;\n" \
+ " var wday;\n" \
+ " if (argc < 1)\n" \
+ " return false;\n" \
+ " if (arguments[argc - 1] == 'GMT') {\n" \
+ " argc--;\n" \
+ " wday = date.getUTCDay();\n" \
+ " } else {\n" \
+ " wday = date.getDay();\n" \
+ " }\n" \
+ " var wd1 = getDay(arguments[0]);\n" \
+ " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \
+ " return (wd1 == -1 || wd2 == -1) ? false\n" \
+ " : (wd1 <= wday && wday <= wd2);\n" \
+ "}\n" \
+ "" \
+ "function dateRange() {\n" \
+ " function getMonth(name) {\n" \
+ " if (name in months) {\n" \
+ " return months[name];\n" \
+ " }\n" \
+ " return -1;\n" \
+ " }\n" \
+ " var date = new Date();\n" \
+ " var argc = arguments.length;\n" \
+ " if (argc < 1) {\n" \
+ " return false;\n" \
+ " }\n" \
+ " var isGMT = (arguments[argc - 1] == 'GMT');\n" \
+ "\n" \
+ " if (isGMT) {\n" \
+ " argc--;\n" \
+ " }\n" \
+ " // function will work even without explict handling of this case\n" \
+ " if (argc == 1) {\n" \
+ " var tmp = parseInt(arguments[0]);\n" \
+ " if (isNaN(tmp)) {\n" \
+ " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" \
+ "getMonth(arguments[0]));\n" \
+ " } else if (tmp < 32) {\n" \
+ " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" \
+ " } else { \n" \
+ " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" \
+ "tmp);\n" \
+ " }\n" \
+ " }\n" \
+ " var year = date.getFullYear();\n" \
+ " var date1, date2;\n" \
+ " date1 = new Date(year, 0, 1, 0, 0, 0);\n" \
+ " date2 = new Date(year, 11, 31, 23, 59, 59);\n" \
+ " var adjustMonth = false;\n" \
+ " for (var i = 0; i < (argc >> 1); i++) {\n" \
+ " var tmp = parseInt(arguments[i]);\n" \
+ " if (isNaN(tmp)) {\n" \
+ " var mon = getMonth(arguments[i]);\n" \
+ " date1.setMonth(mon);\n" \
+ " } else if (tmp < 32) {\n" \
+ " adjustMonth = (argc <= 2);\n" \
+ " date1.setDate(tmp);\n" \
+ " } else {\n" \
+ " date1.setFullYear(tmp);\n" \
+ " }\n" \
+ " }\n" \
+ " for (var i = (argc >> 1); i < argc; i++) {\n" \
+ " var tmp = parseInt(arguments[i]);\n" \
+ " if (isNaN(tmp)) {\n" \
+ " var mon = getMonth(arguments[i]);\n" \
+ " date2.setMonth(mon);\n" \
+ " } else if (tmp < 32) {\n" \
+ " date2.setDate(tmp);\n" \
+ " } else {\n" \
+ " date2.setFullYear(tmp);\n" \
+ " }\n" \
+ " }\n" \
+ " if (adjustMonth) {\n" \
+ " date1.setMonth(date.getMonth());\n" \
+ " date2.setMonth(date.getMonth());\n" \
+ " }\n" \
+ " if (isGMT) {\n" \
+ " var tmp = date;\n" \
+ " tmp.setFullYear(date.getUTCFullYear());\n" \
+ " tmp.setMonth(date.getUTCMonth());\n" \
+ " tmp.setDate(date.getUTCDate());\n" \
+ " tmp.setHours(date.getUTCHours());\n" \
+ " tmp.setMinutes(date.getUTCMinutes());\n" \
+ " tmp.setSeconds(date.getUTCSeconds());\n" \
+ " date = tmp;\n" \
+ " }\n" \
+ " return ((date1 <= date) && (date <= date2));\n" \
+ "}\n" \
+ "" \
+ "function timeRange() {\n" \
+ " var argc = arguments.length;\n" \
+ " var date = new Date();\n" \
+ " var isGMT= false;\n" \
+ "\n" \
+ " if (argc < 1) {\n" \
+ " return false;\n" \
+ " }\n" \
+ " if (arguments[argc - 1] == 'GMT') {\n" \
+ " isGMT = true;\n" \
+ " argc--;\n" \
+ " }\n" \
+ "\n" \
+ " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \
+ " var date1, date2;\n" \
+ " date1 = new Date();\n" \
+ " date2 = new Date();\n" \
+ "\n" \
+ " if (argc == 1) {\n" \
+ " return (hour == arguments[0]);\n" \
+ " } else if (argc == 2) {\n" \
+ " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \
+ " } else {\n" \
+ " switch (argc) {\n" \
+ " case 6:\n" \
+ " date1.setSeconds(arguments[2]);\n" \
+ " date2.setSeconds(arguments[5]);\n" \
+ " case 4:\n" \
+ " var middle = argc >> 1;\n" \
+ " date1.setHours(arguments[0]);\n" \
+ " date1.setMinutes(arguments[1]);\n" \
+ " date2.setHours(arguments[middle]);\n" \
+ " date2.setMinutes(arguments[middle + 1]);\n" \
+ " if (middle == 2) {\n" \
+ " date2.setSeconds(59);\n" \
+ " }\n" \
+ " break;\n" \
+ " default:\n" \
+ " throw 'timeRange: bad number of arguments'\n" \
+ " }\n" \
+ " }\n" \
+ "\n" \
+ " if (isGMT) {\n" \
+ " date.setFullYear(date.getUTCFullYear());\n" \
+ " date.setMonth(date.getUTCMonth());\n" \
+ " date.setDate(date.getUTCDate());\n" \
+ " date.setHours(date.getUTCHours());\n" \
+ " date.setMinutes(date.getUTCMinutes());\n" \
+ " date.setSeconds(date.getUTCSeconds());\n" \
+ " }\n" \
+ " return ((date1 <= date) && (date <= date2));\n" \
+ "}\n"
+
+// This is a Microsoft extension to PAC for IPv6, see:
+// http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx
+#define PROXY_RESOLVER_SCRIPT_EX \
+ "function isResolvableEx(host) {\n" \
+ " var ipList = dnsResolveEx(host);\n" \
+ " return (ipList != '');\n" \
+ "}\n"
+
+#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
diff --git a/chromium/net/proxy/proxy_resolver_script_data.cc b/chromium/net/proxy/proxy_resolver_script_data.cc
new file mode 100644
index 00000000000..7ee07a5d794
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_script_data.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_resolver_script_data.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+
+namespace net {
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF8(
+ const std::string& utf8) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS,
+ GURL(),
+ UTF8ToUTF16(utf8));
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromUTF16(
+ const base::string16& utf16) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_CONTENTS, GURL(), utf16);
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData> ProxyResolverScriptData::FromURL(
+ const GURL& url) {
+ return new ProxyResolverScriptData(TYPE_SCRIPT_URL, url, base::string16());
+}
+
+// static
+scoped_refptr<ProxyResolverScriptData>
+ProxyResolverScriptData::ForAutoDetect() {
+ return new ProxyResolverScriptData(TYPE_AUTO_DETECT, GURL(),
+ base::string16());
+}
+
+const base::string16& ProxyResolverScriptData::utf16() const {
+ DCHECK_EQ(TYPE_SCRIPT_CONTENTS, type_);
+ return utf16_;
+}
+
+const GURL& ProxyResolverScriptData::url() const {
+ DCHECK_EQ(TYPE_SCRIPT_URL, type_);
+ return url_;
+}
+
+bool ProxyResolverScriptData::Equals(
+ const ProxyResolverScriptData* other) const {
+ if (type() != other->type())
+ return false;
+
+ switch (type()) {
+ case TYPE_SCRIPT_CONTENTS:
+ return utf16() == other->utf16();
+ case TYPE_SCRIPT_URL:
+ return url() == other->url();
+ case TYPE_AUTO_DETECT:
+ return true;
+ }
+
+ return false; // Shouldn't be reached.
+}
+
+ProxyResolverScriptData::ProxyResolverScriptData(Type type,
+ const GURL& url,
+ const base::string16& utf16)
+ : type_(type),
+ url_(url),
+ utf16_(utf16) {
+}
+
+ProxyResolverScriptData::~ProxyResolverScriptData() {}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_script_data.h b/chromium/net/proxy/proxy_resolver_script_data.h
new file mode 100644
index 00000000000..16e17fd5293
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_script_data.h
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
+#define NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// Reference-counted wrapper for passing around a PAC script specification.
+// The PAC script can be either specified via a URL, a deferred URL for
+// auto-detect, or the actual javascript program text.
+//
+// This is thread-safe so it can be used by multi-threaded implementations of
+// ProxyResolver to share the data between threads.
+class NET_EXPORT_PRIVATE ProxyResolverScriptData
+ : public base::RefCountedThreadSafe<ProxyResolverScriptData> {
+ public:
+ enum Type {
+ TYPE_SCRIPT_CONTENTS,
+ TYPE_SCRIPT_URL,
+ TYPE_AUTO_DETECT,
+ };
+
+ // Creates a script data given the UTF8 bytes of the content.
+ static scoped_refptr<ProxyResolverScriptData> FromUTF8(
+ const std::string& utf8);
+
+ // Creates a script data given the UTF16 bytes of the content.
+ static scoped_refptr<ProxyResolverScriptData> FromUTF16(
+ const base::string16& utf16);
+
+ // Creates a script data given a URL to the PAC script.
+ static scoped_refptr<ProxyResolverScriptData> FromURL(const GURL& url);
+
+ // Creates a script data for using an automatically detected PAC URL.
+ static scoped_refptr<ProxyResolverScriptData> ForAutoDetect();
+
+ Type type() const {
+ return type_;
+ }
+
+ // Returns the contents of the script as UTF16.
+ // (only valid for type() == TYPE_SCRIPT_CONTENTS).
+ const base::string16& utf16() const;
+
+ // Returns the URL of the script.
+ // (only valid for type() == TYPE_SCRIPT_URL).
+ const GURL& url() const;
+
+ // Returns true if |this| matches |other|.
+ bool Equals(const ProxyResolverScriptData* other) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<ProxyResolverScriptData>;
+ ProxyResolverScriptData(Type type,
+ const GURL& url,
+ const base::string16& utf16);
+ virtual ~ProxyResolverScriptData();
+
+
+ const Type type_;
+ const GURL url_;
+ const base::string16 utf16_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_DATA_H_
diff --git a/chromium/net/proxy/proxy_resolver_v8.cc b/chromium/net/proxy/proxy_resolver_v8.cc
new file mode 100644
index 00000000000..87f61028039
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8.cc
@@ -0,0 +1,808 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_resolver_v8.h"
+
+#include <algorithm>
+#include <cstdio>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_script.h"
+#include "url/gurl.h"
+#include "url/url_canon.h"
+#include "v8/include/v8.h"
+
+// Notes on the javascript environment:
+//
+// For the majority of the PAC utility functions, we use the same code
+// as Firefox. See the javascript library that proxy_resolver_scipt.h
+// pulls in.
+//
+// In addition, we implement a subset of Microsoft's extensions to PAC.
+// - myIpAddressEx()
+// - dnsResolveEx()
+// - isResolvableEx()
+// - isInNetEx()
+// - sortIpAddressList()
+//
+// It is worth noting that the original PAC specification does not describe
+// the return values on failure. Consequently, there are compatibility
+// differences between browsers on what to return on failure, which are
+// illustrated below:
+//
+// --------------------+-------------+-------------------+--------------
+// | Firefox3 | InternetExplorer8 | --> Us <---
+// --------------------+-------------+-------------------+--------------
+// myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1"
+// dnsResolve() | null | false | null
+// myIpAddressEx() | N/A | "" | ""
+// sortIpAddressList() | N/A | false | false
+// dnsResolveEx() | N/A | "" | ""
+// isInNetEx() | N/A | false | false
+// --------------------+-------------+-------------------+--------------
+//
+// TODO(eroman): The cell above reading ??? means I didn't test it.
+//
+// Another difference is in how dnsResolve() and myIpAddress() are
+// implemented -- whether they should restrict to IPv4 results, or
+// include both IPv4 and IPv6. The following table illustrates the
+// differences:
+//
+// --------------------+-------------+-------------------+--------------
+// | Firefox3 | InternetExplorer8 | --> Us <---
+// --------------------+-------------+-------------------+--------------
+// myIpAddress() | IPv4/IPv6 | IPv4 | IPv4
+// dnsResolve() | IPv4/IPv6 | IPv4 | IPv4
+// isResolvable() | IPv4/IPv6 | IPv4 | IPv4
+// myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6
+// dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6
+// sortIpAddressList() | N/A | IPv4/IPv6 | IPv4/IPv6
+// isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6
+// isInNetEx() | N/A | IPv4/IPv6 | IPv4/IPv6
+// -----------------+-------------+-------------------+--------------
+
+namespace net {
+
+namespace {
+
+// Pseudo-name for the PAC script.
+const char kPacResourceName[] = "proxy-pac-script.js";
+// Pseudo-name for the PAC utility script.
+const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
+
+// External string wrapper so V8 can access the UTF16 string wrapped by
+// ProxyResolverScriptData.
+class V8ExternalStringFromScriptData
+ : public v8::String::ExternalStringResource {
+ public:
+ explicit V8ExternalStringFromScriptData(
+ const scoped_refptr<ProxyResolverScriptData>& script_data)
+ : script_data_(script_data) {}
+
+ virtual const uint16_t* data() const OVERRIDE {
+ return reinterpret_cast<const uint16*>(script_data_->utf16().data());
+ }
+
+ virtual size_t length() const OVERRIDE {
+ return script_data_->utf16().size();
+ }
+
+ private:
+ const scoped_refptr<ProxyResolverScriptData> script_data_;
+ DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
+};
+
+// External string wrapper so V8 can access a string literal.
+class V8ExternalASCIILiteral : public v8::String::ExternalAsciiStringResource {
+ public:
+ // |ascii| must be a NULL-terminated C string, and must remain valid
+ // throughout this object's lifetime.
+ V8ExternalASCIILiteral(const char* ascii, size_t length)
+ : ascii_(ascii), length_(length) {
+ DCHECK(IsStringASCII(ascii));
+ }
+
+ virtual const char* data() const OVERRIDE {
+ return ascii_;
+ }
+
+ virtual size_t length() const OVERRIDE {
+ return length_;
+ }
+
+ private:
+ const char* ascii_;
+ size_t length_;
+ DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
+};
+
+// When creating a v8::String from a C++ string we have two choices: create
+// a copy, or create a wrapper that shares the same underlying storage.
+// For small strings it is better to just make a copy, whereas for large
+// strings there are savings by sharing the storage. This number identifies
+// the cutoff length for when to start wrapping rather than creating copies.
+const size_t kMaxStringBytesForCopy = 256;
+
+// Converts a V8 String to a UTF8 std::string.
+std::string V8StringToUTF8(v8::Handle<v8::String> s) {
+ int len = s->Length();
+ std::string result;
+ if (len > 0)
+ s->WriteUtf8(WriteInto(&result, len + 1));
+ return result;
+}
+
+// Converts a V8 String to a UTF16 base::string16.
+base::string16 V8StringToUTF16(v8::Handle<v8::String> s) {
+ int len = s->Length();
+ base::string16 result;
+ // Note that the reinterpret cast is because on Windows string16 is an alias
+ // to wstring, and hence has character type wchar_t not uint16_t.
+ if (len > 0)
+ s->Write(reinterpret_cast<uint16_t*>(WriteInto(&result, len + 1)), 0, len);
+ return result;
+}
+
+// Converts an ASCII std::string to a V8 string.
+v8::Local<v8::String> ASCIIStringToV8String(const std::string& s) {
+ DCHECK(IsStringASCII(s));
+ return v8::String::New(s.data(), s.size());
+}
+
+// Converts a UTF16 base::string16 (warpped by a ProxyResolverScriptData) to a
+// V8 string.
+v8::Local<v8::String> ScriptDataToV8String(
+ const scoped_refptr<ProxyResolverScriptData>& s) {
+ if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
+ return v8::String::New(
+ reinterpret_cast<const uint16_t*>(s->utf16().data()),
+ s->utf16().size());
+ }
+ return v8::String::NewExternal(new V8ExternalStringFromScriptData(s));
+}
+
+// Converts an ASCII string literal to a V8 string.
+v8::Local<v8::String> ASCIILiteralToV8String(const char* ascii) {
+ DCHECK(IsStringASCII(ascii));
+ size_t length = strlen(ascii);
+ if (length <= kMaxStringBytesForCopy)
+ return v8::String::New(ascii, length);
+ return v8::String::NewExternal(new V8ExternalASCIILiteral(ascii, length));
+}
+
+// Stringizes a V8 object by calling its toString() method. Returns true
+// on success. This may fail if the toString() throws an exception.
+bool V8ObjectToUTF16String(v8::Handle<v8::Value> object,
+ base::string16* utf16_result) {
+ if (object.IsEmpty())
+ return false;
+
+ v8::HandleScope scope;
+ v8::Local<v8::String> str_object = object->ToString();
+ if (str_object.IsEmpty())
+ return false;
+ *utf16_result = V8StringToUTF16(str_object);
+ return true;
+}
+
+// Extracts an hostname argument from |args|. On success returns true
+// and fills |*hostname| with the result.
+bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
+ std::string* hostname) {
+ // The first argument should be a string.
+ if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
+ return false;
+
+ const base::string16 hostname_utf16 = V8StringToUTF16(args[0]->ToString());
+
+ // If the hostname is already in ASCII, simply return it as is.
+ if (IsStringASCII(hostname_utf16)) {
+ *hostname = UTF16ToASCII(hostname_utf16);
+ return true;
+ }
+
+ // Otherwise try to convert it from IDN to punycode.
+ const int kInitialBufferSize = 256;
+ url_canon::RawCanonOutputT<char16, kInitialBufferSize> punycode_output;
+ if (!url_canon::IDNToASCII(hostname_utf16.data(),
+ hostname_utf16.length(),
+ &punycode_output)) {
+ return false;
+ }
+
+ // |punycode_output| should now be ASCII; convert it to a std::string.
+ // (We could use UTF16ToASCII() instead, but that requires an extra string
+ // copy. Since ASCII is a subset of UTF8 the following is equivalent).
+ bool success = UTF16ToUTF8(punycode_output.data(),
+ punycode_output.length(),
+ hostname);
+ DCHECK(success);
+ DCHECK(IsStringASCII(*hostname));
+ return success;
+}
+
+// Wrapper for passing around IP address strings and IPAddressNumber objects.
+struct IPAddress {
+ IPAddress(const std::string& ip_string, const IPAddressNumber& ip_number)
+ : string_value(ip_string),
+ ip_address_number(ip_number) {
+ }
+
+ // Used for sorting IP addresses in ascending order in SortIpAddressList().
+ // IP6 addresses are placed ahead of IPv4 addresses.
+ bool operator<(const IPAddress& rhs) const {
+ const IPAddressNumber& ip1 = this->ip_address_number;
+ const IPAddressNumber& ip2 = rhs.ip_address_number;
+ if (ip1.size() != ip2.size())
+ return ip1.size() > ip2.size(); // IPv6 before IPv4.
+ DCHECK(ip1.size() == ip2.size());
+ return memcmp(&ip1[0], &ip2[0], ip1.size()) < 0; // Ascending order.
+ }
+
+ std::string string_value;
+ IPAddressNumber ip_address_number;
+};
+
+// Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
+// semi-colon delimited string containing IP addresses.
+// |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
+// IP addresses or an empty string if unable to sort the IP address list.
+// Returns 'true' if the sorting was successful, and 'false' if the input was an
+// empty string, a string of separators (";" in this case), or if any of the IP
+// addresses in the input list failed to parse.
+bool SortIpAddressList(const std::string& ip_address_list,
+ std::string* sorted_ip_address_list) {
+ sorted_ip_address_list->clear();
+
+ // Strip all whitespace (mimics IE behavior).
+ std::string cleaned_ip_address_list;
+ RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
+ if (cleaned_ip_address_list.empty())
+ return false;
+
+ // Split-up IP addresses and store them in a vector.
+ std::vector<IPAddress> ip_vector;
+ IPAddressNumber ip_num;
+ base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
+ while (str_tok.GetNext()) {
+ if (!ParseIPLiteralToNumber(str_tok.token(), &ip_num))
+ return false;
+ ip_vector.push_back(IPAddress(str_tok.token(), ip_num));
+ }
+
+ if (ip_vector.empty()) // Can happen if we have something like
+ return false; // sortIpAddressList(";") or sortIpAddressList("; ;")
+
+ DCHECK(!ip_vector.empty());
+
+ // Sort lists according to ascending numeric value.
+ if (ip_vector.size() > 1)
+ std::stable_sort(ip_vector.begin(), ip_vector.end());
+
+ // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
+ // IPv4).
+ for (size_t i = 0; i < ip_vector.size(); ++i) {
+ if (i > 0)
+ *sorted_ip_address_list += ";";
+ *sorted_ip_address_list += ip_vector[i].string_value;
+ }
+ return true;
+}
+
+// Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
+// containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
+// slash-delimited IP prefix with the top 'n' bits specified in the bit
+// field. This returns 'true' if the address is in the same subnet, and
+// 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
+// format, or if an address and prefix of different types are used (e.g. IPv6
+// address and IPv4 prefix).
+bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
+ IPAddressNumber address;
+ if (!ParseIPLiteralToNumber(ip_address, &address))
+ return false;
+
+ IPAddressNumber prefix;
+ size_t prefix_length_in_bits;
+ if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
+ return false;
+
+ // Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
+ if (address.size() != prefix.size())
+ return false;
+
+ DCHECK((address.size() == 4 && prefix.size() == 4) ||
+ (address.size() == 16 && prefix.size() == 16));
+
+ return IPNumberMatchesPrefix(address, prefix, prefix_length_in_bits);
+}
+
+} // namespace
+
+// ProxyResolverV8::Context ---------------------------------------------------
+
+class ProxyResolverV8::Context {
+ public:
+ Context(ProxyResolverV8* parent, v8::Isolate* isolate)
+ : parent_(parent),
+ isolate_(isolate) {
+ DCHECK(isolate);
+ }
+
+ ~Context() {
+ v8::Locker locked(isolate_);
+
+ v8_this_.Dispose(isolate_);
+ v8_context_.Dispose(isolate_);
+ }
+
+ JSBindings* js_bindings() {
+ return parent_->js_bindings_;
+ }
+
+ int ResolveProxy(const GURL& query_url, ProxyInfo* results) {
+ v8::Locker locked(isolate_);
+ v8::HandleScope scope(isolate_);
+
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate_, v8_context_);
+ v8::Context::Scope function_scope(context);
+
+ v8::Local<v8::Value> function;
+ if (!GetFindProxyForURL(&function)) {
+ js_bindings()->OnError(
+ -1, ASCIIToUTF16("FindProxyForURL() is undefined."));
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ v8::Handle<v8::Value> argv[] = {
+ ASCIIStringToV8String(query_url.spec()),
+ ASCIIStringToV8String(query_url.HostNoBrackets()),
+ };
+
+ v8::TryCatch try_catch;
+ v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
+ context->Global(), arraysize(argv), argv);
+
+ if (try_catch.HasCaught()) {
+ HandleError(try_catch.Message());
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ if (!ret->IsString()) {
+ js_bindings()->OnError(
+ -1, ASCIIToUTF16("FindProxyForURL() did not return a string."));
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ base::string16 ret_str = V8StringToUTF16(ret->ToString());
+
+ if (!IsStringASCII(ret_str)) {
+ // TODO(eroman): Rather than failing when a wide string is returned, we
+ // could extend the parsing to handle IDNA hostnames by
+ // converting them to ASCII punycode.
+ // crbug.com/47234
+ base::string16 error_message =
+ ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
+ "(crbug.com/47234): ") + ret_str;
+ js_bindings()->OnError(-1, error_message);
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ results->UsePacString(UTF16ToASCII(ret_str));
+ return OK;
+ }
+
+ int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script) {
+ v8::Locker locked(isolate_);
+ v8::HandleScope scope(isolate_);
+
+ v8_this_.Reset(isolate_, v8::External::New(this));
+ v8::Local<v8::External> v8_this =
+ v8::Local<v8::External>::New(isolate_, v8_this_);
+ v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
+
+ // Attach the javascript bindings.
+ v8::Local<v8::FunctionTemplate> alert_template =
+ v8::FunctionTemplate::New(&AlertCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("alert"), alert_template);
+
+ v8::Local<v8::FunctionTemplate> my_ip_address_template =
+ v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("myIpAddress"),
+ my_ip_address_template);
+
+ v8::Local<v8::FunctionTemplate> dns_resolve_template =
+ v8::FunctionTemplate::New(&DnsResolveCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("dnsResolve"),
+ dns_resolve_template);
+
+ // Microsoft's PAC extensions:
+
+ v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
+ v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("dnsResolveEx"),
+ dns_resolve_ex_template);
+
+ v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
+ v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("myIpAddressEx"),
+ my_ip_address_ex_template);
+
+ v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
+ v8::FunctionTemplate::New(&SortIpAddressListCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("sortIpAddressList"),
+ sort_ip_address_list_template);
+
+ v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
+ v8::FunctionTemplate::New(&IsInNetExCallback, v8_this);
+ global_template->Set(ASCIILiteralToV8String("isInNetEx"),
+ is_in_net_ex_template);
+
+ v8_context_.Reset(
+ isolate_, v8::Context::New(isolate_, NULL, global_template));
+
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(isolate_, v8_context_);
+ v8::Context::Scope ctx(context);
+
+ // Add the PAC utility functions to the environment.
+ // (This script should never fail, as it is a string literal!)
+ // Note that the two string literals are concatenated.
+ int rv = RunScript(
+ ASCIILiteralToV8String(
+ PROXY_RESOLVER_SCRIPT
+ PROXY_RESOLVER_SCRIPT_EX),
+ kPacUtilityResourceName);
+ if (rv != OK) {
+ NOTREACHED();
+ return rv;
+ }
+
+ // Add the user's PAC code to the environment.
+ rv = RunScript(ScriptDataToV8String(pac_script), kPacResourceName);
+ if (rv != OK)
+ return rv;
+
+ // At a minimum, the FindProxyForURL() function must be defined for this
+ // to be a legitimiate PAC script.
+ v8::Local<v8::Value> function;
+ if (!GetFindProxyForURL(&function)) {
+ js_bindings()->OnError(
+ -1, ASCIIToUTF16("FindProxyForURL() is undefined."));
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ return OK;
+ }
+
+ void PurgeMemory() {
+ v8::Locker locked(isolate_);
+ v8::V8::LowMemoryNotification();
+ }
+
+ private:
+ bool GetFindProxyForURL(v8::Local<v8::Value>* function) {
+ v8::Local<v8::Context> context =
+ v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_);
+ *function =
+ context->Global()->Get(ASCIILiteralToV8String("FindProxyForURL"));
+ return (*function)->IsFunction();
+ }
+
+ // Handle an exception thrown by V8.
+ void HandleError(v8::Handle<v8::Message> message) {
+ base::string16 error_message;
+ int line_number = -1;
+
+ if (!message.IsEmpty()) {
+ line_number = message->GetLineNumber();
+ V8ObjectToUTF16String(message->Get(), &error_message);
+ }
+
+ js_bindings()->OnError(line_number, error_message);
+ }
+
+ // Compiles and runs |script| in the current V8 context.
+ // Returns OK on success, otherwise an error code.
+ int RunScript(v8::Handle<v8::String> script, const char* script_name) {
+ v8::TryCatch try_catch;
+
+ // Compile the script.
+ v8::ScriptOrigin origin =
+ v8::ScriptOrigin(ASCIILiteralToV8String(script_name));
+ v8::Local<v8::Script> code = v8::Script::Compile(script, &origin);
+
+ // Execute.
+ if (!code.IsEmpty())
+ code->Run();
+
+ // Check for errors.
+ if (try_catch.HasCaught()) {
+ HandleError(try_catch.Message());
+ return ERR_PAC_SCRIPT_FAILED;
+ }
+
+ return OK;
+ }
+
+ // V8 callback for when "alert()" is invoked by the PAC script.
+ static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ Context* context =
+ static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+ // Like firefox we assume "undefined" if no argument was specified, and
+ // disregard any arguments beyond the first.
+ base::string16 message;
+ if (args.Length() == 0) {
+ message = ASCIIToUTF16("undefined");
+ } else {
+ if (!V8ObjectToUTF16String(args[0], &message))
+ return; // toString() threw an exception.
+ }
+
+ context->js_bindings()->Alert(message);
+ }
+
+ // V8 callback for when "myIpAddress()" is invoked by the PAC script.
+ static void MyIpAddressCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS);
+ }
+
+ // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
+ static void MyIpAddressExCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS_EX);
+ }
+
+ // V8 callback for when "dnsResolve()" is invoked by the PAC script.
+ static void DnsResolveCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE);
+ }
+
+ // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
+ static void DnsResolveExCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX);
+ }
+
+ // Shared code for implementing:
+ // - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
+ static void DnsResolveCallbackHelper(
+ const v8::FunctionCallbackInfo<v8::Value>& args,
+ JSBindings::ResolveDnsOperation op) {
+ Context* context =
+ static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
+
+ std::string hostname;
+
+ // dnsResolve() and dnsResolveEx() need at least 1 argument.
+ if (op == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) {
+ if (!GetHostnameArgument(args, &hostname)) {
+ if (op == JSBindings::DNS_RESOLVE)
+ args.GetReturnValue().SetNull();
+ return;
+ }
+ }
+
+ std::string result;
+ bool success;
+ bool terminate = false;
+
+ {
+ v8::Unlocker unlocker(args.GetIsolate());
+ success = context->js_bindings()->ResolveDns(
+ hostname, op, &result, &terminate);
+ }
+
+ if (terminate)
+ v8::V8::TerminateExecution(args.GetIsolate());
+
+ if (success) {
+ args.GetReturnValue().Set(ASCIIStringToV8String(result));
+ return;
+ }
+
+ // Each function handles resolution errors differently.
+ switch (op) {
+ case JSBindings::DNS_RESOLVE:
+ args.GetReturnValue().SetNull();
+ return;
+ case JSBindings::DNS_RESOLVE_EX:
+ args.GetReturnValue().SetEmptyString();
+ return;
+ case JSBindings::MY_IP_ADDRESS:
+ args.GetReturnValue().Set(ASCIILiteralToV8String("127.0.0.1"));
+ return;
+ case JSBindings::MY_IP_ADDRESS_EX:
+ args.GetReturnValue().SetEmptyString();
+ return;
+ }
+
+ NOTREACHED();
+ }
+
+ // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
+ static void SortIpAddressListCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // We need at least one string argument.
+ if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
+ args.GetReturnValue().SetNull();
+ return;
+ }
+
+ std::string ip_address_list = V8StringToUTF8(args[0]->ToString());
+ if (!IsStringASCII(ip_address_list)) {
+ args.GetReturnValue().SetNull();
+ return;
+ }
+ std::string sorted_ip_address_list;
+ bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
+ if (!success) {
+ args.GetReturnValue().Set(false);
+ return;
+ }
+ args.GetReturnValue().Set(ASCIIStringToV8String(sorted_ip_address_list));
+ }
+
+ // V8 callback for when "isInNetEx()" is invoked by the PAC script.
+ static void IsInNetExCallback(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // We need at least 2 string arguments.
+ if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
+ args[1].IsEmpty() || !args[1]->IsString()) {
+ args.GetReturnValue().SetNull();
+ return;
+ }
+
+ std::string ip_address = V8StringToUTF8(args[0]->ToString());
+ if (!IsStringASCII(ip_address)) {
+ args.GetReturnValue().Set(false);
+ return;
+ }
+ std::string ip_prefix = V8StringToUTF8(args[1]->ToString());
+ if (!IsStringASCII(ip_prefix)) {
+ args.GetReturnValue().Set(false);
+ return;
+ }
+ args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
+ }
+
+ mutable base::Lock lock_;
+ ProxyResolverV8* parent_;
+ v8::Isolate* isolate_;
+ v8::Persistent<v8::External> v8_this_;
+ v8::Persistent<v8::Context> v8_context_;
+};
+
+// ProxyResolverV8 ------------------------------------------------------------
+
+ProxyResolverV8::ProxyResolverV8()
+ : ProxyResolver(true /*expects_pac_bytes*/),
+ js_bindings_(NULL) {
+}
+
+ProxyResolverV8::~ProxyResolverV8() {}
+
+int ProxyResolverV8::GetProxyForURL(
+ const GURL& query_url, ProxyInfo* results,
+ const CompletionCallback& /*callback*/,
+ RequestHandle* /*request*/,
+ const BoundNetLog& net_log) {
+ DCHECK(js_bindings_);
+
+ // If the V8 instance has not been initialized (either because
+ // SetPacScript() wasn't called yet, or because it failed.
+ if (!context_)
+ return ERR_FAILED;
+
+ // Otherwise call into V8.
+ int rv = context_->ResolveProxy(query_url, results);
+
+ return rv;
+}
+
+void ProxyResolverV8::CancelRequest(RequestHandle request) {
+ // This is a synchronous ProxyResolver; no possibility for async requests.
+ NOTREACHED();
+}
+
+LoadState ProxyResolverV8::GetLoadState(RequestHandle request) const {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+}
+
+void ProxyResolverV8::CancelSetPacScript() {
+ NOTREACHED();
+}
+
+void ProxyResolverV8::PurgeMemory() {
+ context_->PurgeMemory();
+}
+
+int ProxyResolverV8::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& /*callback*/) {
+ DCHECK(script_data.get());
+ DCHECK(js_bindings_);
+
+ context_.reset();
+ if (script_data->utf16().empty())
+ return ERR_PAC_SCRIPT_FAILED;
+
+ // Try parsing the PAC script.
+ scoped_ptr<Context> context(new Context(this, GetDefaultIsolate()));
+ int rv = context->InitV8(script_data);
+ if (rv == OK)
+ context_.reset(context.release());
+ return rv;
+}
+
+// static
+void ProxyResolverV8::RememberDefaultIsolate() {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ DCHECK(isolate)
+ << "ProxyResolverV8::RememberDefaultIsolate called on wrong thread";
+ DCHECK(g_default_isolate_ == NULL || g_default_isolate_ == isolate)
+ << "Default Isolate can not be changed";
+ g_default_isolate_ = isolate;
+}
+
+#if defined(OS_WIN)
+// static
+void ProxyResolverV8::CreateIsolate() {
+ v8::Isolate* isolate = v8::Isolate::New();
+ DCHECK(isolate);
+ DCHECK(g_default_isolate_ == NULL) << "Default Isolate can not be set twice";
+
+ isolate->Enter();
+ v8::V8::Initialize();
+
+ g_default_isolate_ = isolate;
+}
+#endif // defined(OS_WIN)
+
+// static
+v8::Isolate* ProxyResolverV8::GetDefaultIsolate() {
+ DCHECK(g_default_isolate_)
+ << "Must call ProxyResolverV8::RememberDefaultIsolate() first";
+ return g_default_isolate_;
+}
+
+v8::Isolate* ProxyResolverV8::g_default_isolate_ = NULL;
+
+// static
+size_t ProxyResolverV8::GetTotalHeapSize() {
+ if (!g_default_isolate_)
+ return 0;
+
+ v8::Locker locked(g_default_isolate_);
+ v8::HeapStatistics heap_statistics;
+ g_default_isolate_->GetHeapStatistics(&heap_statistics);
+ return heap_statistics.total_heap_size();
+}
+
+// static
+size_t ProxyResolverV8::GetUsedHeapSize() {
+ if (!g_default_isolate_)
+ return 0;
+
+ v8::Locker locked(g_default_isolate_);
+ v8::HeapStatistics heap_statistics;
+ g_default_isolate_->GetHeapStatistics(&heap_statistics);
+ return heap_statistics.used_heap_size();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_v8.h b/chromium/net/proxy/proxy_resolver_v8.h
new file mode 100644
index 00000000000..76ec7252700
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2011 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_PROXY_PROXY_RESOLVER_V8_H_
+#define NET_PROXY_PROXY_RESOLVER_V8_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_resolver.h"
+
+namespace v8 {
+class HeapStatistics;
+class Isolate;
+} // namespace v8
+
+namespace net {
+
+// Implementation of ProxyResolver that uses V8 to evaluate PAC scripts.
+//
+// ----------------------------------------------------------------------------
+// !!! Important note on threading model:
+// ----------------------------------------------------------------------------
+// There can be only one instance of V8 running at a time. To enforce this
+// constraint, ProxyResolverV8 holds a v8::Locker during execution. Therefore
+// it is OK to run multiple instances of ProxyResolverV8 on different threads,
+// since only one will be running inside V8 at a time.
+//
+// It is important that *ALL* instances of V8 in the process be using
+// v8::Locker. If not there can be race conditions between the non-locked V8
+// instances and the locked V8 instances used by ProxyResolverV8 (assuming they
+// run on different threads).
+//
+// This is the case with the V8 instance used by chromium's renderer -- it runs
+// on a different thread from ProxyResolver (renderer thread vs PAC thread),
+// and does not use locking since it expects to be alone.
+class NET_EXPORT_PRIVATE ProxyResolverV8 : public ProxyResolver {
+ public:
+ // Interface for the javascript bindings.
+ class NET_EXPORT_PRIVATE JSBindings {
+ public:
+ enum ResolveDnsOperation {
+ DNS_RESOLVE,
+ DNS_RESOLVE_EX,
+ MY_IP_ADDRESS,
+ MY_IP_ADDRESS_EX,
+ };
+
+ JSBindings() {}
+
+ // Handler for "dnsResolve()", "dnsResolveEx()", "myIpAddress()",
+ // "myIpAddressEx()". Returns true on success and fills |*output| with the
+ // result. If |*terminate| is set to true, then the script execution will
+ // be aborted. Note that termination may not happen right away.
+ virtual bool ResolveDns(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) = 0;
+
+ // Handler for "alert(message)"
+ virtual void Alert(const base::string16& message) = 0;
+
+ // Handler for when an error is encountered. |line_number| may be -1
+ // if a line number is not applicable to this error.
+ virtual void OnError(int line_number, const base::string16& error) = 0;
+
+ protected:
+ virtual ~JSBindings() {}
+ };
+
+ // Constructs a ProxyResolverV8.
+ ProxyResolverV8();
+
+ virtual ~ProxyResolverV8();
+
+ JSBindings* js_bindings() const { return js_bindings_; }
+ void set_js_bindings(JSBindings* js_bindings) { js_bindings_ = js_bindings; }
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& /*callback*/,
+ RequestHandle* /*request*/,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle request) OVERRIDE;
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE;
+ virtual void CancelSetPacScript() OVERRIDE;
+ virtual void PurgeMemory() OVERRIDE;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const net::CompletionCallback& /*callback*/) OVERRIDE;
+
+ // Remember the default Isolate, must be called from the main thread. This
+ // hack can be removed when the "default Isolate" concept is gone.
+ static void RememberDefaultIsolate();
+
+#if defined(OS_WIN)
+ // Create an isolate to use for the proxy resolver. Until the "default
+ // Isolate" concept is gone, it is preferable to invoke
+ // RememberDefaultIsolate() as creating a new Isolate in additional to the
+ // default Isolate will waste a few MB of memory and the runtime it took to
+ // create the default Isolate.
+ static void CreateIsolate();
+#endif
+
+ static v8::Isolate* GetDefaultIsolate();
+
+ // Get total/ued heap memory usage of all v8 instances used by the proxy
+ // resolver.
+ static size_t GetTotalHeapSize();
+ static size_t GetUsedHeapSize();
+
+ private:
+ static v8::Isolate* g_default_isolate_;
+
+ // Context holds the Javascript state for the most recently loaded PAC
+ // script. It corresponds with the data from the last call to
+ // SetPacScript().
+ class Context;
+
+ scoped_ptr<Context> context_;
+
+ JSBindings* js_bindings_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_V8_H_
diff --git a/chromium/net/proxy/proxy_resolver_v8_tracing.cc b/chromium/net/proxy/proxy_resolver_v8_tracing.cc
new file mode 100644
index 00000000000..4f6f5fc17d9
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8_tracing.cc
@@ -0,0 +1,1181 @@
+// Copyright (c) 2013 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/proxy/proxy_resolver_v8_tracing.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/dns/host_resolver.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_error_observer.h"
+#include "net/proxy/proxy_resolver_v8.h"
+
+// The intent of this class is explained in the design document:
+// https://docs.google.com/a/chromium.org/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit
+//
+// In a nutshell, PAC scripts are Javascript programs and may depend on
+// network I/O, by calling functions like dnsResolve().
+//
+// This is problematic since functions such as dnsResolve() will block the
+// Javascript execution until the DNS result is availble, thereby stalling the
+// PAC thread, which hurts the ability to process parallel proxy resolves.
+// An obvious solution is to simply start more PAC threads, however this scales
+// poorly, which hurts the ability to process parallel proxy resolves.
+//
+// The solution in ProxyResolverV8Tracing is to model PAC scripts as being
+// deterministic, and depending only on the inputted URL. When the script
+// issues a dnsResolve() for a yet unresolved hostname, the Javascript
+// execution is "aborted", and then re-started once the DNS result is
+// known.
+namespace net {
+
+namespace {
+
+// Upper bound on how many *unique* DNS resolves a PAC script is allowed
+// to make. This is a failsafe both for scripts that do a ridiculous
+// number of DNS resolves, as well as scripts which are misbehaving
+// under the tracing optimization. It is not expected to hit this normally.
+const size_t kMaxUniqueResolveDnsPerExec = 20;
+
+// Approximate number of bytes to use for buffering alerts() and errors.
+// This is a failsafe in case repeated executions of the script causes
+// too much memory bloat. It is not expected for well behaved scripts to
+// hit this. (In fact normal scripts should not even have alerts() or errors).
+const size_t kMaxAlertsAndErrorsBytes = 2048;
+
+// Returns event parameters for a PAC error message (line number + message).
+base::Value* NetLogErrorCallback(int line_number,
+ const base::string16* message,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("line_number", line_number);
+ dict->SetString("message", *message);
+ return dict;
+}
+
+void IncrementWithoutOverflow(uint8* x) {
+ if (*x != 0xFF)
+ *x += 1;
+}
+
+} // namespace
+
+// The Job class is responsible for executing GetProxyForURL() and
+// SetPacScript(), since both of these operations share similar code.
+//
+// The DNS for these operations can operate in either blocking or
+// non-blocking mode. Blocking mode is used as a fallback when the PAC script
+// seems to be misbehaving under the tracing optimization.
+//
+// Note that this class runs on both the origin thread and a worker
+// thread. Most methods are expected to be used exclusively on one thread
+// or the other.
+//
+// The lifetime of Jobs does not exceed that of the ProxyResolverV8Tracing that
+// spawned it. Destruction might happen on either the origin thread or the
+// worker thread.
+class ProxyResolverV8Tracing::Job
+ : public base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>,
+ public ProxyResolverV8::JSBindings {
+ public:
+ // |parent| is non-owned. It is the ProxyResolverV8Tracing that spawned this
+ // Job, and must oulive it.
+ explicit Job(ProxyResolverV8Tracing* parent);
+
+ // Called from origin thread.
+ void StartSetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback);
+
+ // Called from origin thread.
+ void StartGetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback);
+
+ // Called from origin thread.
+ void Cancel();
+
+ // Called from origin thread.
+ LoadState GetLoadState() const;
+
+ private:
+ typedef std::map<std::string, std::string> DnsCache;
+ friend class base::RefCountedThreadSafe<ProxyResolverV8Tracing::Job>;
+
+ enum Operation {
+ SET_PAC_SCRIPT,
+ GET_PROXY_FOR_URL,
+ };
+
+ struct AlertOrError {
+ bool is_alert;
+ int line_number;
+ base::string16 message;
+ };
+
+ virtual ~Job();
+
+ void CheckIsOnWorkerThread() const;
+ void CheckIsOnOriginThread() const;
+
+ void SetCallback(const CompletionCallback& callback);
+ void ReleaseCallback();
+
+ ProxyResolverV8* v8_resolver();
+ base::MessageLoop* worker_loop();
+ HostResolver* host_resolver();
+ ProxyResolverErrorObserver* error_observer();
+ NetLog* net_log();
+
+ // Invokes the user's callback.
+ void NotifyCaller(int result);
+ void NotifyCallerOnOriginLoop(int result);
+
+ void RecordMetrics() const;
+
+ void Start(Operation op, bool blocking_dns,
+ const CompletionCallback& callback);
+
+ void ExecuteBlocking();
+ void ExecuteNonBlocking();
+ int ExecuteProxyResolver();
+
+ // Implementation of ProxyResolverv8::JSBindings
+ virtual bool ResolveDns(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) OVERRIDE;
+ virtual void Alert(const base::string16& message) OVERRIDE;
+ virtual void OnError(int line_number, const base::string16& error) OVERRIDE;
+
+ bool ResolveDnsBlocking(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output);
+
+ bool ResolveDnsNonBlocking(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate);
+
+ bool PostDnsOperationAndWait(const std::string& host,
+ ResolveDnsOperation op,
+ bool* completed_synchronously)
+ WARN_UNUSED_RESULT;
+
+ void DoDnsOperation();
+ void OnDnsOperationComplete(int result);
+
+ void ScheduleRestartWithBlockingDns();
+
+ bool GetDnsFromLocalCache(const std::string& host, ResolveDnsOperation op,
+ std::string* output, bool* return_value);
+
+ void SaveDnsToLocalCache(const std::string& host, ResolveDnsOperation op,
+ int net_error, const net::AddressList& addresses);
+
+ // Builds a RequestInfo to service the specified PAC DNS operation.
+ static HostResolver::RequestInfo MakeDnsRequestInfo(const std::string& host,
+ ResolveDnsOperation op);
+
+ // Makes a key for looking up |host, op| in |dns_cache_|. Strings are used for
+ // convenience, to avoid defining custom comparators.
+ static std::string MakeDnsCacheKey(const std::string& host,
+ ResolveDnsOperation op);
+
+ void HandleAlertOrError(bool is_alert, int line_number,
+ const base::string16& message);
+ void DispatchBufferedAlertsAndErrors();
+ void DispatchAlertOrError(bool is_alert, int line_number,
+ const base::string16& message);
+
+ void LogEventToCurrentRequestAndGlobally(
+ NetLog::EventType type,
+ const NetLog::ParametersCallback& parameters_callback);
+
+ // The thread which called into ProxyResolverV8Tracing, and on which the
+ // completion callback is expected to run.
+ scoped_refptr<base::MessageLoopProxy> origin_loop_;
+
+ // The ProxyResolverV8Tracing which spawned this Job.
+ // Initialized on origin thread and then accessed from both threads.
+ ProxyResolverV8Tracing* parent_;
+
+ // The callback to run (on the origin thread) when the Job finishes.
+ // Should only be accessed from origin thread.
+ CompletionCallback callback_;
+
+ // Flag to indicate whether the request has been cancelled.
+ base::CancellationFlag cancelled_;
+
+ // The operation that this Job is running.
+ // Initialized on origin thread and then accessed from both threads.
+ Operation operation_;
+
+ // The DNS mode for this Job.
+ // Initialized on origin thread, mutated on worker thread, and accessed
+ // by both the origin thread and worker thread.
+ bool blocking_dns_;
+
+ // Used to block the worker thread on a DNS operation taking place on the
+ // origin thread.
+ base::WaitableEvent event_;
+
+ // Map of DNS operations completed so far. Written into on the origin thread
+ // and read on the worker thread.
+ DnsCache dns_cache_;
+
+ // The job holds a reference to itself to ensure that it remains alive until
+ // either completion or cancellation.
+ scoped_refptr<Job> owned_self_reference_;
+
+ // -------------------------------------------------------
+ // State specific to SET_PAC_SCRIPT.
+ // -------------------------------------------------------
+
+ scoped_refptr<ProxyResolverScriptData> script_data_;
+
+ // -------------------------------------------------------
+ // State specific to GET_PROXY_FOR_URL.
+ // -------------------------------------------------------
+
+ ProxyInfo* user_results_; // Owned by caller, lives on origin thread.
+ GURL url_;
+ ProxyInfo results_;
+ BoundNetLog bound_net_log_;
+
+ // ---------------------------------------------------------------------------
+ // State for ExecuteNonBlocking()
+ // ---------------------------------------------------------------------------
+ // These variables are used exclusively on the worker thread and are only
+ // meaningful when executing inside of ExecuteNonBlocking().
+
+ // Whether this execution was abandoned due to a missing DNS dependency.
+ bool abandoned_;
+
+ // Number of calls made to ResolveDns() by this execution.
+ int num_dns_;
+
+ // Sequence of calls made to Alert() or OnError() by this execution.
+ std::vector<AlertOrError> alerts_and_errors_;
+ size_t alerts_and_errors_byte_cost_; // Approximate byte cost of the above.
+
+ // Number of calls made to ResolveDns() by the PREVIOUS execution.
+ int last_num_dns_;
+
+ // Whether the current execution needs to be restarted in blocking mode.
+ bool should_restart_with_blocking_dns_;
+
+ // ---------------------------------------------------------------------------
+ // State for pending DNS request.
+ // ---------------------------------------------------------------------------
+
+ // Handle to the outstanding request in the HostResolver, or NULL.
+ // This is mutated and used on the origin thread, however it may be read by
+ // the worker thread for some DCHECKS().
+ HostResolver::RequestHandle pending_dns_;
+
+ // Indicates if the outstanding DNS request completed synchronously. Written
+ // on the origin thread, and read by the worker thread.
+ bool pending_dns_completed_synchronously_;
+
+ // These are the inputs to DoDnsOperation(). Written on the worker thread,
+ // read by the origin thread.
+ std::string pending_dns_host_;
+ ResolveDnsOperation pending_dns_op_;
+
+ // This contains the resolved address list that DoDnsOperation() fills in.
+ // Used exclusively on the origin thread.
+ AddressList pending_dns_addresses_;
+
+ // ---------------------------------------------------------------------------
+ // Metrics for histograms
+ // ---------------------------------------------------------------------------
+ // These values are used solely for logging histograms. They do not affect
+ // the execution flow of requests.
+
+ // The time when the proxy resolve request started. Used exclusively on the
+ // origin thread.
+ base::TimeTicks metrics_start_time_;
+
+ // The time when the proxy resolve request completes on the worker thread.
+ // Written on the worker thread, read on the origin thread.
+ base::TimeTicks metrics_end_time_;
+
+ // The time when PostDnsOperationAndWait() was called. Written on the worker
+ // thread, read by the origin thread.
+ base::TimeTicks metrics_pending_dns_start_;
+
+ // The total amount of time that has been spent by the script waiting for
+ // DNS dependencies. This includes the time spent posting the task to
+ // the origin thread, up until the DNS result is found on the origin
+ // thread. It does not include any time spent waiting in the message loop
+ // for the worker thread, nor any time restarting or executing the
+ // script. Used exclusively on the origin thread.
+ base::TimeDelta metrics_dns_total_time_;
+
+ // The following variables are initialized on the origin thread,
+ // incremented on the worker thread, and then read upon completion on the
+ // origin thread. The values are not expected to exceed the range of a uint8.
+ // If they do, then they will be clamped to 0xFF.
+ uint8 metrics_num_executions_;
+ uint8 metrics_num_unique_dns_;
+ uint8 metrics_num_alerts_;
+ uint8 metrics_num_errors_;
+
+ // The time that the latest execution took (time spent inside of
+ // ExecuteProxyResolver(), which includes time spent in bindings too).
+ // Written on the worker thread, read on the origin thread.
+ base::TimeDelta metrics_execution_time_;
+
+ // The cumulative time spent in ExecuteProxyResolver() that was ultimately
+ // discarded work.
+ // Written on the worker thread, read on the origin thread.
+ base::TimeDelta metrics_abandoned_execution_total_time_;
+
+ // The duration that the worker thread was blocked waiting on DNS results from
+ // the origin thread, when operating in nonblocking mode.
+ // Written on the worker thread, read on the origin thread.
+ base::TimeDelta metrics_nonblocking_dns_wait_total_time_;
+};
+
+ProxyResolverV8Tracing::Job::Job(ProxyResolverV8Tracing* parent)
+ : origin_loop_(base::MessageLoopProxy::current()),
+ parent_(parent),
+ event_(true, false),
+ last_num_dns_(0),
+ pending_dns_(NULL),
+ metrics_num_executions_(0),
+ metrics_num_unique_dns_(0),
+ metrics_num_alerts_(0),
+ metrics_num_errors_(0) {
+ CheckIsOnOriginThread();
+}
+
+void ProxyResolverV8Tracing::Job::StartSetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) {
+ CheckIsOnOriginThread();
+
+ script_data_ = script_data;
+
+ // Script initialization uses blocking DNS since there isn't any
+ // advantage to using non-blocking mode here. That is because the
+ // parent ProxyService can't submit any ProxyResolve requests until
+ // initialization has completed successfully!
+ Start(SET_PAC_SCRIPT, true /*blocking*/, callback);
+}
+
+void ProxyResolverV8Tracing::Job::StartGetProxyForURL(
+ const GURL& url,
+ ProxyInfo* results,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ CheckIsOnOriginThread();
+
+ url_ = url;
+ user_results_ = results;
+ bound_net_log_ = net_log;
+
+ Start(GET_PROXY_FOR_URL, false /*non-blocking*/, callback);
+}
+
+void ProxyResolverV8Tracing::Job::Cancel() {
+ CheckIsOnOriginThread();
+
+ // There are several possibilities to consider for cancellation:
+ // (a) The job has been posted to the worker thread, however script execution
+ // has not yet started.
+ // (b) The script is executing on the worker thread.
+ // (c) The script is executing on the worker thread, however is blocked inside
+ // of dnsResolve() waiting for a response from the origin thread.
+ // (d) Nothing is running on the worker thread, however the host resolver has
+ // a pending DNS request which upon completion will restart the script
+ // execution.
+ // (e) The worker thread has a pending task to restart execution, which was
+ // posted after the DNS dependency was resolved and saved to local cache.
+ // (f) The script execution completed entirely, and posted a task to the
+ // origin thread to notify the caller.
+ //
+ // |cancelled_| is read on both the origin thread and worker thread. The
+ // code that runs on the worker thread is littered with checks on
+ // |cancelled_| to break out early.
+ cancelled_.Set();
+
+ ReleaseCallback();
+
+ if (pending_dns_) {
+ host_resolver()->CancelRequest(pending_dns_);
+ pending_dns_ = NULL;
+ }
+
+ // The worker thread might be blocked waiting for DNS.
+ event_.Signal();
+
+ owned_self_reference_ = NULL;
+}
+
+LoadState ProxyResolverV8Tracing::Job::GetLoadState() const {
+ CheckIsOnOriginThread();
+
+ if (pending_dns_)
+ return LOAD_STATE_RESOLVING_HOST_IN_PROXY_SCRIPT;
+
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+}
+
+ProxyResolverV8Tracing::Job::~Job() {
+ DCHECK(!pending_dns_);
+ DCHECK(callback_.is_null());
+}
+
+void ProxyResolverV8Tracing::Job::CheckIsOnWorkerThread() const {
+ DCHECK_EQ(base::MessageLoop::current(), parent_->thread_->message_loop());
+}
+
+void ProxyResolverV8Tracing::Job::CheckIsOnOriginThread() const {
+ DCHECK(origin_loop_->BelongsToCurrentThread());
+}
+
+void ProxyResolverV8Tracing::Job::SetCallback(
+ const CompletionCallback& callback) {
+ CheckIsOnOriginThread();
+ DCHECK(callback_.is_null());
+ parent_->num_outstanding_callbacks_++;
+ callback_ = callback;
+}
+
+void ProxyResolverV8Tracing::Job::ReleaseCallback() {
+ CheckIsOnOriginThread();
+ DCHECK(!callback_.is_null());
+ CHECK_GT(parent_->num_outstanding_callbacks_, 0);
+ parent_->num_outstanding_callbacks_--;
+ callback_.Reset();
+
+ // For good measure, clear this other user-owned pointer.
+ user_results_ = NULL;
+}
+
+ProxyResolverV8* ProxyResolverV8Tracing::Job::v8_resolver() {
+ return parent_->v8_resolver_.get();
+}
+
+base::MessageLoop* ProxyResolverV8Tracing::Job::worker_loop() {
+ return parent_->thread_->message_loop();
+}
+
+HostResolver* ProxyResolverV8Tracing::Job::host_resolver() {
+ return parent_->host_resolver_;
+}
+
+ProxyResolverErrorObserver* ProxyResolverV8Tracing::Job::error_observer() {
+ return parent_->error_observer_.get();
+}
+
+NetLog* ProxyResolverV8Tracing::Job::net_log() {
+ return parent_->net_log_;
+}
+
+void ProxyResolverV8Tracing::Job::NotifyCaller(int result) {
+ CheckIsOnWorkerThread();
+
+ metrics_end_time_ = base::TimeTicks::Now();
+
+ origin_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&Job::NotifyCallerOnOriginLoop, this, result));
+}
+
+void ProxyResolverV8Tracing::Job::NotifyCallerOnOriginLoop(int result) {
+ CheckIsOnOriginThread();
+
+ if (cancelled_.IsSet())
+ return;
+
+ DCHECK(!callback_.is_null());
+ DCHECK(!pending_dns_);
+
+ if (operation_ == GET_PROXY_FOR_URL) {
+ RecordMetrics();
+ *user_results_ = results_;
+ }
+
+ // There is only ever 1 outstanding SET_PAC_SCRIPT job. It needs to be
+ // tracked to support cancellation.
+ if (operation_ == SET_PAC_SCRIPT) {
+ DCHECK_EQ(parent_->set_pac_script_job_.get(), this);
+ parent_->set_pac_script_job_ = NULL;
+ }
+
+ CompletionCallback callback = callback_;
+ ReleaseCallback();
+ callback.Run(result);
+
+ owned_self_reference_ = NULL;
+}
+
+void ProxyResolverV8Tracing::Job::RecordMetrics() const {
+ CheckIsOnOriginThread();
+ DCHECK_EQ(GET_PROXY_FOR_URL, operation_);
+
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ // Metrics are output for each completed request to GetProxyForURL()).
+ //
+ // Note that a different set of histograms is used to record the metrics for
+ // requests that completed in non-blocking mode versus blocking mode. The
+ // expectation is for requests to complete in non-blocking mode each time.
+ // If they don't then something strange is happening, and the purpose of the
+ // seprate statistics is to better understand that trend.
+#define UPDATE_HISTOGRAMS(base_name) \
+ do {\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTime", now - metrics_start_time_);\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTimeWorkerThread",\
+ metrics_end_time_ - metrics_start_time_);\
+ UMA_HISTOGRAM_TIMES(base_name "OriginThreadLatency",\
+ now - metrics_end_time_);\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "TotalTimeDNS",\
+ metrics_dns_total_time_);\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "ExecutionTime",\
+ metrics_execution_time_);\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "AbandonedExecutionTotalTime",\
+ metrics_abandoned_execution_total_time_);\
+ UMA_HISTOGRAM_MEDIUM_TIMES(base_name "DnsWaitTotalTime",\
+ metrics_nonblocking_dns_wait_total_time_);\
+ UMA_HISTOGRAM_CUSTOM_COUNTS(\
+ base_name "NumRestarts", metrics_num_executions_ - 1,\
+ 1, kMaxUniqueResolveDnsPerExec, kMaxUniqueResolveDnsPerExec);\
+ UMA_HISTOGRAM_CUSTOM_COUNTS(\
+ base_name "UniqueDNS", metrics_num_unique_dns_,\
+ 1, kMaxUniqueResolveDnsPerExec, kMaxUniqueResolveDnsPerExec);\
+ UMA_HISTOGRAM_COUNTS_100(base_name "NumAlerts", metrics_num_alerts_);\
+ UMA_HISTOGRAM_CUSTOM_COUNTS(\
+ base_name "NumErrors", metrics_num_errors_, 1, 10, 10);\
+ } while (false)
+
+ if (!blocking_dns_)
+ UPDATE_HISTOGRAMS("Net.ProxyResolver.");
+ else
+ UPDATE_HISTOGRAMS("Net.ProxyResolver.BlockingDNSMode.");
+
+#undef UPDATE_HISTOGRAMS
+
+ // Histograms to better understand http://crbug.com/240536 -- long
+ // URLs can cause a significant slowdown in PAC execution. Figure out how
+ // severe this is in the wild.
+ if (!blocking_dns_) {
+ size_t url_size = url_.spec().size();
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.ProxyResolver.URLSize", url_size, 1, 200000, 50);
+
+ if (url_size > 2048) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver2K",
+ metrics_execution_time_);
+ }
+
+ if (url_size > 4096) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver4K",
+ metrics_execution_time_);
+ }
+
+ if (url_size > 8192) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver8K",
+ metrics_execution_time_);
+ }
+
+ if (url_size > 131072) {
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.ProxyResolver.ExecutionTime_UrlOver128K",
+ metrics_execution_time_);
+ }
+ }
+}
+
+
+void ProxyResolverV8Tracing::Job::Start(Operation op, bool blocking_dns,
+ const CompletionCallback& callback) {
+ CheckIsOnOriginThread();
+
+ metrics_start_time_ = base::TimeTicks::Now();
+ operation_ = op;
+ blocking_dns_ = blocking_dns;
+ SetCallback(callback);
+
+ owned_self_reference_ = this;
+
+ worker_loop()->PostTask(FROM_HERE,
+ blocking_dns_ ? base::Bind(&Job::ExecuteBlocking, this) :
+ base::Bind(&Job::ExecuteNonBlocking, this));
+}
+
+void ProxyResolverV8Tracing::Job::ExecuteBlocking() {
+ CheckIsOnWorkerThread();
+ DCHECK(blocking_dns_);
+
+ if (cancelled_.IsSet())
+ return;
+
+ NotifyCaller(ExecuteProxyResolver());
+}
+
+void ProxyResolverV8Tracing::Job::ExecuteNonBlocking() {
+ CheckIsOnWorkerThread();
+ DCHECK(!blocking_dns_);
+
+ if (cancelled_.IsSet())
+ return;
+
+ // Reset state for the current execution.
+ abandoned_ = false;
+ num_dns_ = 0;
+ alerts_and_errors_.clear();
+ alerts_and_errors_byte_cost_ = 0;
+ should_restart_with_blocking_dns_ = false;
+
+ int result = ExecuteProxyResolver();
+
+ if (abandoned_)
+ metrics_abandoned_execution_total_time_ += metrics_execution_time_;
+
+ if (should_restart_with_blocking_dns_) {
+ DCHECK(!blocking_dns_);
+ DCHECK(abandoned_);
+ blocking_dns_ = true;
+ ExecuteBlocking();
+ return;
+ }
+
+ if (abandoned_)
+ return;
+
+ DispatchBufferedAlertsAndErrors();
+ NotifyCaller(result);
+}
+
+int ProxyResolverV8Tracing::Job::ExecuteProxyResolver() {
+ IncrementWithoutOverflow(&metrics_num_executions_);
+
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ JSBindings* prev_bindings = v8_resolver()->js_bindings();
+ v8_resolver()->set_js_bindings(this);
+
+ int result = ERR_UNEXPECTED; // Initialized to silence warnings.
+
+ switch (operation_) {
+ case SET_PAC_SCRIPT:
+ result = v8_resolver()->SetPacScript(
+ script_data_, CompletionCallback());
+ break;
+ case GET_PROXY_FOR_URL:
+ result = v8_resolver()->GetProxyForURL(
+ url_,
+ // Important: Do not write directly into |user_results_|, since if the
+ // request were to be cancelled from the origin thread, must guarantee
+ // that |user_results_| is not accessed anymore.
+ &results_,
+ CompletionCallback(),
+ NULL,
+ bound_net_log_);
+ break;
+ }
+
+ v8_resolver()->set_js_bindings(prev_bindings);
+
+ metrics_execution_time_ = base::TimeTicks::Now() - start;
+
+ return result;
+}
+
+bool ProxyResolverV8Tracing::Job::ResolveDns(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) {
+ if (cancelled_.IsSet()) {
+ *terminate = true;
+ return false;
+ }
+
+ if ((op == DNS_RESOLVE || op == DNS_RESOLVE_EX) && host.empty()) {
+ // a DNS resolve with an empty hostname is considered an error.
+ return false;
+ }
+
+ return blocking_dns_ ?
+ ResolveDnsBlocking(host, op, output) :
+ ResolveDnsNonBlocking(host, op, output, terminate);
+}
+
+void ProxyResolverV8Tracing::Job::Alert(const base::string16& message) {
+ HandleAlertOrError(true, -1, message);
+}
+
+void ProxyResolverV8Tracing::Job::OnError(int line_number,
+ const base::string16& error) {
+ HandleAlertOrError(false, line_number, error);
+}
+
+bool ProxyResolverV8Tracing::Job::ResolveDnsBlocking(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output) {
+ CheckIsOnWorkerThread();
+
+ // Check if the DNS result for this host has already been cached.
+ bool rv;
+ if (GetDnsFromLocalCache(host, op, output, &rv)) {
+ // Yay, cache hit!
+ return rv;
+ }
+
+ // If the host was not in the local cache, this is a new hostname.
+ IncrementWithoutOverflow(&metrics_num_unique_dns_);
+
+ if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
+ // Safety net for scripts with unexpectedly many DNS calls.
+ // We will continue running to completion, but will fail every
+ // subsequent DNS request.
+ return false;
+ }
+
+ if (!PostDnsOperationAndWait(host, op, NULL))
+ return false; // Was cancelled.
+
+ CHECK(GetDnsFromLocalCache(host, op, output, &rv));
+ return rv;
+}
+
+bool ProxyResolverV8Tracing::Job::ResolveDnsNonBlocking(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) {
+ CheckIsOnWorkerThread();
+
+ if (abandoned_) {
+ // If this execution was already abandoned can fail right away. Only 1 DNS
+ // dependency will be traced at a time (for more predictable outcomes).
+ return false;
+ }
+
+ num_dns_ += 1;
+
+ // Check if the DNS result for this host has already been cached.
+ bool rv;
+ if (GetDnsFromLocalCache(host, op, output, &rv)) {
+ // Yay, cache hit!
+ return rv;
+ }
+
+ // If the host was not in the local cache, then this is a new hostname.
+ IncrementWithoutOverflow(&metrics_num_unique_dns_);
+
+ if (num_dns_ <= last_num_dns_) {
+ // The sequence of DNS operations is different from last time!
+ ScheduleRestartWithBlockingDns();
+ *terminate = true;
+ return false;
+ }
+
+ if (dns_cache_.size() >= kMaxUniqueResolveDnsPerExec) {
+ // Safety net for scripts with unexpectedly many DNS calls.
+ return false;
+ }
+
+ DCHECK(!should_restart_with_blocking_dns_);
+
+ bool completed_synchronously;
+ if (!PostDnsOperationAndWait(host, op, &completed_synchronously))
+ return false; // Was cancelled.
+
+ if (completed_synchronously) {
+ CHECK(GetDnsFromLocalCache(host, op, output, &rv));
+ return rv;
+ }
+
+ // Otherwise if the result was not in the cache, then a DNS request has
+ // been started. Abandon this invocation of FindProxyForURL(), it will be
+ // restarted once the DNS request completes.
+ abandoned_ = true;
+ *terminate = true;
+ last_num_dns_ = num_dns_;
+ return false;
+}
+
+bool ProxyResolverV8Tracing::Job::PostDnsOperationAndWait(
+ const std::string& host, ResolveDnsOperation op,
+ bool* completed_synchronously) {
+
+ base::TimeTicks start = base::TimeTicks::Now();
+
+ // Post the DNS request to the origin thread.
+ DCHECK(!pending_dns_);
+ metrics_pending_dns_start_ = base::TimeTicks::Now();
+ pending_dns_host_ = host;
+ pending_dns_op_ = op;
+ origin_loop_->PostTask(FROM_HERE, base::Bind(&Job::DoDnsOperation, this));
+
+ event_.Wait();
+ event_.Reset();
+
+ if (cancelled_.IsSet())
+ return false;
+
+ if (completed_synchronously)
+ *completed_synchronously = pending_dns_completed_synchronously_;
+
+ if (!blocking_dns_)
+ metrics_nonblocking_dns_wait_total_time_ += base::TimeTicks::Now() - start;
+
+ return true;
+}
+
+void ProxyResolverV8Tracing::Job::DoDnsOperation() {
+ CheckIsOnOriginThread();
+ DCHECK(!pending_dns_);
+
+ if (cancelled_.IsSet())
+ return;
+
+ HostResolver::RequestHandle dns_request = NULL;
+ int result = host_resolver()->Resolve(
+ MakeDnsRequestInfo(pending_dns_host_, pending_dns_op_),
+ &pending_dns_addresses_,
+ base::Bind(&Job::OnDnsOperationComplete, this),
+ &dns_request,
+ bound_net_log_);
+
+ pending_dns_completed_synchronously_ = result != ERR_IO_PENDING;
+
+ // Check if the request was cancelled as a side-effect of calling into the
+ // HostResolver. This isn't the ordinary execution flow, however it is
+ // exercised by unit-tests.
+ if (cancelled_.IsSet()) {
+ if (!pending_dns_completed_synchronously_)
+ host_resolver()->CancelRequest(dns_request);
+ return;
+ }
+
+ if (pending_dns_completed_synchronously_) {
+ OnDnsOperationComplete(result);
+ } else {
+ DCHECK(dns_request);
+ pending_dns_ = dns_request;
+ // OnDnsOperationComplete() will be called by host resolver on completion.
+ }
+
+ if (!blocking_dns_) {
+ // The worker thread always blocks waiting to see if the result can be
+ // serviced from cache before restarting.
+ event_.Signal();
+ }
+}
+
+void ProxyResolverV8Tracing::Job::OnDnsOperationComplete(int result) {
+ CheckIsOnOriginThread();
+
+ DCHECK(!cancelled_.IsSet());
+ DCHECK(pending_dns_completed_synchronously_ == (pending_dns_ == NULL));
+
+ SaveDnsToLocalCache(pending_dns_host_, pending_dns_op_, result,
+ pending_dns_addresses_);
+ pending_dns_ = NULL;
+
+ metrics_dns_total_time_ +=
+ base::TimeTicks::Now() - metrics_pending_dns_start_;
+
+ if (blocking_dns_) {
+ event_.Signal();
+ return;
+ }
+
+ if (!blocking_dns_ && !pending_dns_completed_synchronously_) {
+ // Restart. This time it should make more progress due to having
+ // cached items.
+ worker_loop()->PostTask(FROM_HERE,
+ base::Bind(&Job::ExecuteNonBlocking, this));
+ }
+}
+
+void ProxyResolverV8Tracing::Job::ScheduleRestartWithBlockingDns() {
+ CheckIsOnWorkerThread();
+
+ DCHECK(!should_restart_with_blocking_dns_);
+ DCHECK(!abandoned_);
+ DCHECK(!blocking_dns_);
+
+ abandoned_ = true;
+
+ // The restart will happen after ExecuteNonBlocking() finishes.
+ should_restart_with_blocking_dns_ = true;
+}
+
+bool ProxyResolverV8Tracing::Job::GetDnsFromLocalCache(
+ const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* return_value) {
+ CheckIsOnWorkerThread();
+
+ DnsCache::const_iterator it = dns_cache_.find(MakeDnsCacheKey(host, op));
+ if (it == dns_cache_.end())
+ return false;
+
+ *output = it->second;
+ *return_value = !it->second.empty();
+ return true;
+}
+
+void ProxyResolverV8Tracing::Job::SaveDnsToLocalCache(
+ const std::string& host,
+ ResolveDnsOperation op,
+ int net_error,
+ const net::AddressList& addresses) {
+ CheckIsOnOriginThread();
+
+ // Serialize the result into a string to save to the cache.
+ std::string cache_value;
+ if (net_error != OK) {
+ cache_value = std::string();
+ } else if (op == DNS_RESOLVE || op == MY_IP_ADDRESS) {
+ // dnsResolve() and myIpAddress() are expected to return a single IP
+ // address.
+ cache_value = addresses.front().ToStringWithoutPort();
+ } else {
+ // The *Ex versions are expected to return a semi-colon separated list.
+ for (AddressList::const_iterator iter = addresses.begin();
+ iter != addresses.end(); ++iter) {
+ if (!cache_value.empty())
+ cache_value += ";";
+ cache_value += iter->ToStringWithoutPort();
+ }
+ }
+
+ dns_cache_[MakeDnsCacheKey(host, op)] = cache_value;
+}
+
+// static
+HostResolver::RequestInfo ProxyResolverV8Tracing::Job::MakeDnsRequestInfo(
+ const std::string& host, ResolveDnsOperation op) {
+ HostPortPair host_port = HostPortPair(host, 80);
+ if (op == MY_IP_ADDRESS || op == MY_IP_ADDRESS_EX) {
+ host_port.set_host(GetHostName());
+ }
+
+ HostResolver::RequestInfo info(host_port);
+
+ // The non-ex flavors are limited to IPv4 results.
+ if (op == MY_IP_ADDRESS || op == DNS_RESOLVE) {
+ info.set_address_family(ADDRESS_FAMILY_IPV4);
+ }
+
+ return info;
+}
+
+std::string ProxyResolverV8Tracing::Job::MakeDnsCacheKey(
+ const std::string& host, ResolveDnsOperation op) {
+ return base::StringPrintf("%d:%s", op, host.c_str());
+}
+
+void ProxyResolverV8Tracing::Job::HandleAlertOrError(
+ bool is_alert,
+ int line_number,
+ const base::string16& message) {
+ CheckIsOnWorkerThread();
+
+ if (cancelled_.IsSet())
+ return;
+
+ if (blocking_dns_) {
+ // In blocking DNS mode the events can be dispatched immediately.
+ DispatchAlertOrError(is_alert, line_number, message);
+ return;
+ }
+
+ // Otherwise in nonblocking mode, buffer all the messages until
+ // the end.
+
+ if (abandoned_)
+ return;
+
+ alerts_and_errors_byte_cost_ += sizeof(AlertOrError) + message.size() * 2;
+
+ // If there have been lots of messages, enqueing could be expensive on
+ // memory. Consider a script which does megabytes worth of alerts().
+ // Avoid this by falling back to blocking mode.
+ if (alerts_and_errors_byte_cost_ > kMaxAlertsAndErrorsBytes) {
+ ScheduleRestartWithBlockingDns();
+ return;
+ }
+
+ AlertOrError entry = {is_alert, line_number, message};
+ alerts_and_errors_.push_back(entry);
+}
+
+void ProxyResolverV8Tracing::Job::DispatchBufferedAlertsAndErrors() {
+ CheckIsOnWorkerThread();
+ DCHECK(!blocking_dns_);
+ DCHECK(!abandoned_);
+
+ for (size_t i = 0; i < alerts_and_errors_.size(); ++i) {
+ const AlertOrError& x = alerts_and_errors_[i];
+ DispatchAlertOrError(x.is_alert, x.line_number, x.message);
+ }
+}
+
+void ProxyResolverV8Tracing::Job::DispatchAlertOrError(
+ bool is_alert, int line_number, const base::string16& message) {
+ CheckIsOnWorkerThread();
+
+ // Note that the handling of cancellation is racy with regard to
+ // alerts/errors. The request might get cancelled shortly after this
+ // check! (There is no lock being held to guarantee otherwise).
+ //
+ // If this happens, then some information will get written to the NetLog
+ // needlessly, however the NetLog will still be alive so it shouldn't cause
+ // problems.
+ if (cancelled_.IsSet())
+ return;
+
+ if (is_alert) {
+ // -------------------
+ // alert
+ // -------------------
+ IncrementWithoutOverflow(&metrics_num_alerts_);
+ VLOG(1) << "PAC-alert: " << message;
+
+ // Send to the NetLog.
+ LogEventToCurrentRequestAndGlobally(
+ NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::StringCallback("message", &message));
+ } else {
+ // -------------------
+ // error
+ // -------------------
+ IncrementWithoutOverflow(&metrics_num_errors_);
+ if (line_number == -1)
+ VLOG(1) << "PAC-error: " << message;
+ else
+ VLOG(1) << "PAC-error: " << "line: " << line_number << ": " << message;
+
+ // Send the error to the NetLog.
+ LogEventToCurrentRequestAndGlobally(
+ NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
+ base::Bind(&NetLogErrorCallback, line_number, &message));
+
+ if (error_observer())
+ error_observer()->OnPACScriptError(line_number, message);
+ }
+}
+
+void ProxyResolverV8Tracing::Job::LogEventToCurrentRequestAndGlobally(
+ NetLog::EventType type,
+ const NetLog::ParametersCallback& parameters_callback) {
+ CheckIsOnWorkerThread();
+ bound_net_log_.AddEvent(type, parameters_callback);
+
+ // Emit to the global NetLog event stream.
+ if (net_log())
+ net_log()->AddGlobalEntry(type, parameters_callback);
+}
+
+ProxyResolverV8Tracing::ProxyResolverV8Tracing(
+ HostResolver* host_resolver,
+ ProxyResolverErrorObserver* error_observer,
+ NetLog* net_log)
+ : ProxyResolver(true /*expects_pac_bytes*/),
+ host_resolver_(host_resolver),
+ error_observer_(error_observer),
+ net_log_(net_log),
+ num_outstanding_callbacks_(0) {
+ DCHECK(host_resolver);
+ // Start up the thread.
+ thread_.reset(new base::Thread("Proxy resolver"));
+ CHECK(thread_->Start());
+
+ v8_resolver_.reset(new ProxyResolverV8);
+}
+
+ProxyResolverV8Tracing::~ProxyResolverV8Tracing() {
+ // Note, all requests should have been cancelled.
+ CHECK(!set_pac_script_job_.get());
+ CHECK_EQ(0, num_outstanding_callbacks_);
+
+ // Join the worker thread. See http://crbug.com/69710. Note that we call
+ // Stop() here instead of simply clearing thread_ since there may be pending
+ // callbacks on the worker thread which want to dereference thread_.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ thread_->Stop();
+}
+
+int ProxyResolverV8Tracing::GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(!set_pac_script_job_.get());
+
+ scoped_refptr<Job> job = new Job(this);
+
+ if (request)
+ *request = job.get();
+
+ job->StartGetProxyForURL(url, results, net_log, callback);
+ return ERR_IO_PENDING;
+}
+
+void ProxyResolverV8Tracing::CancelRequest(RequestHandle request) {
+ Job* job = reinterpret_cast<Job*>(request);
+ job->Cancel();
+}
+
+LoadState ProxyResolverV8Tracing::GetLoadState(RequestHandle request) const {
+ Job* job = reinterpret_cast<Job*>(request);
+ return job->GetLoadState();
+}
+
+void ProxyResolverV8Tracing::CancelSetPacScript() {
+ DCHECK(set_pac_script_job_.get());
+ set_pac_script_job_->Cancel();
+ set_pac_script_job_ = NULL;
+}
+
+void ProxyResolverV8Tracing::PurgeMemory() {
+ thread_->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProxyResolverV8::PurgeMemory,
+ // The use of unretained is safe, since the worker thread
+ // cannot outlive |this|.
+ base::Unretained(v8_resolver_.get())));
+}
+
+int ProxyResolverV8Tracing::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ // Note that there should not be any outstanding (non-cancelled) Jobs when
+ // setting the PAC script (ProxyService should guarantee this). If there are,
+ // then they might complete in strange ways after the new script is set.
+ DCHECK(!set_pac_script_job_.get());
+ CHECK_EQ(0, num_outstanding_callbacks_);
+
+ set_pac_script_job_ = new Job(this);
+ set_pac_script_job_->StartSetPacScript(script_data, callback);
+
+ return ERR_IO_PENDING;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_v8_tracing.h b/chromium/net/proxy/proxy_resolver_v8_tracing.h
new file mode 100644
index 00000000000..5877aa2088d
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8_tracing.h
@@ -0,0 +1,85 @@
+// Copyright (c) 2013 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_PROXY_PROXY_RESOLVER_V8_TRACING_H_
+#define NET_PROXY_PROXY_RESOLVER_V8_TRACING_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+#include "net/proxy/proxy_resolver.h"
+
+namespace base {
+class Thread;
+class MessageLoopProxy;
+} // namespace base
+
+namespace net {
+
+class HostResolver;
+class NetLog;
+class ProxyResolverErrorObserver;
+class ProxyResolverV8;
+
+// ProxyResolverV8Tracing is a non-blocking ProxyResolver. It executes
+// ProxyResolverV8 on a single helper thread, and does some magic to avoid
+// blocking in DNS. For more details see the design document:
+// https://docs.google.com/a/google.com/document/d/16Ij5OcVnR3s0MH4Z5XkhI9VTPoMJdaBn9rKreAmGOdE/edit?pli=1
+class NET_EXPORT_PRIVATE ProxyResolverV8Tracing
+ : public ProxyResolver,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // Constructs a ProxyResolver that will issue DNS requests through
+ // |host_resolver|, forward Javascript errors through |error_observer|, and
+ // log Javascript errors and alerts to |net_log|.
+ //
+ // Note that the constructor takes ownership of |error_observer|, whereas
+ // |host_resolver| and |net_log| are expected to outlive |this|.
+ ProxyResolverV8Tracing(HostResolver* host_resolver,
+ ProxyResolverErrorObserver* error_observer,
+ NetLog* net_log);
+
+ virtual ~ProxyResolverV8Tracing();
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(RequestHandle request) OVERRIDE;
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE;
+ virtual void CancelSetPacScript() OVERRIDE;
+ virtual void PurgeMemory() OVERRIDE;
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ class Job;
+
+ // The worker thread on which the ProxyResolverV8 will be run.
+ scoped_ptr<base::Thread> thread_;
+ scoped_ptr<ProxyResolverV8> v8_resolver_;
+
+ // Non-owned host resolver, which is to be operated on the origin thread.
+ HostResolver* host_resolver_;
+
+ scoped_ptr<ProxyResolverErrorObserver> error_observer_;
+ NetLog* net_log_;
+
+ // The outstanding SetPacScript operation, or NULL.
+ scoped_refptr<Job> set_pac_script_job_;
+
+ // The number of outstanding (non-cancelled) jobs.
+ int num_outstanding_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8Tracing);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_V8_TRACING_H_
diff --git a/chromium/net/proxy/proxy_resolver_v8_tracing_unittest.cc b/chromium/net/proxy/proxy_resolver_v8_tracing_unittest.cc
new file mode 100644
index 00000000000..e597402c89c
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8_tracing_unittest.cc
@@ -0,0 +1,1098 @@
+// Copyright (c) 2013 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/proxy/proxy_resolver_v8_tracing.h"
+
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_error_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+class ProxyResolverV8TracingTest : public testing::Test {
+ public:
+ virtual void TearDown() OVERRIDE {
+ // Drain any pending messages, which may be left over from cancellation.
+ // This way they get reliably run as part of the current test, rather than
+ // spilling into the next test's execution.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+};
+
+scoped_refptr<ProxyResolverScriptData> LoadScriptData(const char* filename) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_resolver_v8_tracing_unittest");
+ path = path.AppendASCII(filename);
+
+ // Try to read the file from disk.
+ std::string file_contents;
+ bool ok = file_util::ReadFileToString(path, &file_contents);
+
+ // If we can't load the file from disk, something is misconfigured.
+ EXPECT_TRUE(ok) << "Failed to read file: " << path.value();
+
+ // Load the PAC script into the ProxyResolver.
+ return ProxyResolverScriptData::FromUTF8(file_contents);
+}
+
+void InitResolver(ProxyResolverV8Tracing* resolver, const char* filename) {
+ TestCompletionCallback callback;
+ int rv =
+ resolver->SetPacScript(LoadScriptData(filename), callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+class MockErrorObserver : public ProxyResolverErrorObserver {
+ public:
+ MockErrorObserver() : event_(true, false) {}
+
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) OVERRIDE {
+ {
+ base::AutoLock l(lock_);
+ output += base::StringPrintf("Error: line %d: %s\n", line_number,
+ UTF16ToASCII(error).c_str());
+ }
+ event_.Signal();
+ }
+
+ std::string GetOutput() {
+ base::AutoLock l(lock_);
+ return output;
+ }
+
+ void WaitForOutput() {
+ event_.Wait();
+ }
+
+ private:
+ base::Lock lock_;
+ std::string output;
+
+ base::WaitableEvent event_;
+};
+
+TEST_F(ProxyResolverV8TracingTest, Simple) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ InitResolver(&resolver, "simple.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(),
+ NULL, request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ("foo:99", proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(0u, host_resolver.num_resolve());
+
+ // There were no errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- nothing was logged.
+ EXPECT_EQ(0u, log.GetSize());
+ EXPECT_EQ(0u, request_log.GetSize());
+}
+
+TEST_F(ProxyResolverV8TracingTest, JavascriptError) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ InitResolver(&resolver, "error.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://throw-an-error/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult());
+
+ EXPECT_EQ(0u, host_resolver.num_resolve());
+
+ EXPECT_EQ("Error: line 5: Uncaught TypeError: Cannot call method 'split' "
+ "of null\n", error_observer->GetOutput());
+
+ // Check the NetLogs -- there was 1 alert and 1 javascript error, and they
+ // were output to both the global log, and per-request log.
+ CapturingNetLog::CapturedEntryList entries_list[2];
+ log.GetEntries(&entries_list[0]);
+ request_log.GetEntries(&entries_list[1]);
+
+ for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
+ const CapturingNetLog::CapturedEntryList& entries = entries_list[list_i];
+ EXPECT_EQ(2u, entries.size());
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_ERROR,
+ NetLog::PHASE_NONE));
+
+ EXPECT_EQ("{\"message\":\"Prepare to DIE!\"}", entries[0].GetParamsJson());
+ EXPECT_EQ("{\"line_number\":5,\"message\":\"Uncaught TypeError: Cannot "
+ "call method 'split' of null\"}", entries[1].GetParamsJson());
+ }
+}
+
+TEST_F(ProxyResolverV8TracingTest, TooManyAlerts) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ InitResolver(&resolver, "too_many_alerts.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"),
+ &proxy_info,
+ callback.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Iteration1 does a DNS resolve
+ // Iteration2 exceeds the alert buffer
+ // Iteration3 runs in blocking mode and completes
+ EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(1u, host_resolver.num_resolve());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- the script generated 50 alerts, which were mirrored
+ // to both the global and per-request logs.
+ CapturingNetLog::CapturedEntryList entries_list[2];
+ log.GetEntries(&entries_list[0]);
+ request_log.GetEntries(&entries_list[1]);
+
+ for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
+ const CapturingNetLog::CapturedEntryList& entries = entries_list[list_i];
+ EXPECT_EQ(50u, entries.size());
+ for (size_t i = 0; i < entries.size(); ++i) {
+ ASSERT_TRUE(
+ LogContainsEvent(entries, i, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ }
+ }
+}
+
+// Verify that buffered alerts cannot grow unboundedly, even when the message is
+// empty string.
+TEST_F(ProxyResolverV8TracingTest, TooManyEmptyAlerts) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ InitResolver(&resolver, "too_many_empty_alerts.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"),
+ &proxy_info,
+ callback.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ("foo:3", proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(1u, host_resolver.num_resolve());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- the script generated 50 alerts, which were mirrored
+ // to both the global and per-request logs.
+ CapturingNetLog::CapturedEntryList entries_list[2];
+ log.GetEntries(&entries_list[0]);
+ request_log.GetEntries(&entries_list[1]);
+
+ for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
+ const CapturingNetLog::CapturedEntryList& entries = entries_list[list_i];
+ EXPECT_EQ(1000u, entries.size());
+ for (size_t i = 0; i < entries.size(); ++i) {
+ ASSERT_TRUE(
+ LogContainsEvent(entries, i, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ }
+ }
+}
+
+// This test runs a PAC script that issues a sequence of DNS resolves. The test
+// verifies the final result, and that the underlying DNS resolver received
+// the correct set of queries.
+TEST_F(ProxyResolverV8TracingTest, Dns) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRuleForAddressFamily(
+ "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44");
+ host_resolver.rules()
+ ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string());
+ host_resolver.rules()->AddSimulatedFailure("host2");
+ host_resolver.rules()->AddRule("host3", "166.155.144.33");
+ host_resolver.rules()->AddRule("host5", "166.155.144.55");
+ host_resolver.rules()->AddSimulatedFailure("host6");
+ host_resolver.rules()->AddRuleForAddressFamily(
+ "*", ADDRESS_FAMILY_IPV4, "122.133.144.155");
+ host_resolver.rules()->AddRule("*", "133.122.100.200");
+
+ InitResolver(&resolver, "dns.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"),
+ &proxy_info,
+ callback.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // The test does 13 DNS resolution, however only 7 of them are unique.
+ EXPECT_EQ(7u, host_resolver.num_resolve());
+
+ const char* kExpectedResult =
+ "122.133.144.155-" // myIpAddress()
+ "null-" // dnsResolve('')
+ "__1_192.168.1.1-" // dnsResolveEx('host1')
+ "null-" // dnsResolve('host2')
+ "166.155.144.33-" // dnsResolve('host3')
+ "122.133.144.155-" // myIpAddress()
+ "166.155.144.33-" // dnsResolve('host3')
+ "__1_192.168.1.1-" // dnsResolveEx('host1')
+ "122.133.144.155-" // myIpAddress()
+ "null-" // dnsResolve('host2')
+ "-" // dnsResolveEx('host6')
+ "133.122.100.200-" // myIpAddressEx()
+ "166.155.144.44" // dnsResolve('host1')
+ ":99";
+
+ EXPECT_EQ(kExpectedResult, proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- the script generated 1 alert, mirrored to both
+ // the per-request and global logs.
+ CapturingNetLog::CapturedEntryList entries_list[2];
+ log.GetEntries(&entries_list[0]);
+ request_log.GetEntries(&entries_list[1]);
+
+ for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
+ const CapturingNetLog::CapturedEntryList& entries = entries_list[list_i];
+ EXPECT_EQ(1u, entries.size());
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ EXPECT_EQ("{\"message\":\"iteration: 7\"}", entries[0].GetParamsJson());
+ }
+}
+
+// This test runs a PAC script that does "myIpAddress()" followed by
+// "dnsResolve()". This requires 2 restarts. However once the HostResolver's
+// cache is warmed, subsequent calls should take 0 restarts.
+TEST_F(ProxyResolverV8TracingTest, DnsChecksCache) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("foopy", "166.155.144.11");
+ host_resolver.rules()->AddRule("*", "122.133.144.155");
+
+ InitResolver(&resolver, "simple_dns.js");
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foopy/req1"),
+ &proxy_info,
+ callback1.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ // The test does 2 DNS resolutions.
+ EXPECT_EQ(2u, host_resolver.num_resolve());
+
+ // The first request took 2 restarts, hence on g_iteration=3.
+ EXPECT_EQ("166.155.144.11:3", proxy_info.proxy_server().ToURI());
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://foopy/req2"),
+ &proxy_info,
+ callback2.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback2.WaitForResult());
+
+ EXPECT_EQ(4u, host_resolver.num_resolve());
+
+ // This time no restarts were required, so g_iteration incremented by 1.
+ EXPECT_EQ("166.155.144.11:4", proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ EXPECT_EQ(0u, log.GetSize());
+ EXPECT_EQ(0u, request_log.GetSize());
+}
+
+// This test runs a weird PAC script that was designed to defeat the DNS tracing
+// optimization. The proxy resolver should detect the inconsistency and
+// fall-back to synchronous mode execution.
+TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous1) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host1", "166.155.144.11");
+ host_resolver.rules()->AddRule("crazy4", "133.199.111.4");
+ host_resolver.rules()->AddRule("*", "122.133.144.155");
+
+ InitResolver(&resolver, "global_sideffects1.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // The script itself only does 2 DNS resolves per execution, however it
+ // constructs the hostname using a global counter which changes on each
+ // invocation.
+ EXPECT_EQ(3u, host_resolver.num_resolve());
+
+ EXPECT_EQ("166.155.144.11-133.199.111.4:100",
+ proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- the script generated 1 alert, mirrored to both
+ // the per-request and global logs.
+ CapturingNetLog::CapturedEntryList entries_list[2];
+ log.GetEntries(&entries_list[0]);
+ request_log.GetEntries(&entries_list[1]);
+
+ for (size_t list_i = 0; list_i < arraysize(entries_list); list_i++) {
+ const CapturingNetLog::CapturedEntryList& entries = entries_list[list_i];
+ EXPECT_EQ(1u, entries.size());
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ EXPECT_EQ("{\"message\":\"iteration: 4\"}", entries[0].GetParamsJson());
+ }
+}
+
+// This test runs a weird PAC script that was designed to defeat the DNS tracing
+// optimization. The proxy resolver should detect the inconsistency and
+// fall-back to synchronous mode execution.
+TEST_F(ProxyResolverV8TracingTest, FallBackToSynchronous2) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host1", "166.155.144.11");
+ host_resolver.rules()->AddRule("host2", "166.155.144.22");
+ host_resolver.rules()->AddRule("host3", "166.155.144.33");
+ host_resolver.rules()->AddRule("host4", "166.155.144.44");
+ host_resolver.rules()->AddRule("*", "122.133.144.155");
+
+ InitResolver(&resolver, "global_sideffects2.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(3u, host_resolver.num_resolve());
+
+ EXPECT_EQ("166.155.144.44:100", proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- nothing was logged.
+ EXPECT_EQ(0u, log.GetSize());
+ EXPECT_EQ(0u, request_log.GetSize());
+}
+
+// This test runs a weird PAC script that yields a never ending sequence
+// of DNS resolves when restarting. Running it will hit the maximum
+// DNS resolves per request limit (20) after which every DNS resolve will
+// fail.
+TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host*", "166.155.144.11");
+ host_resolver.rules()->AddRule("*", "122.133.144.155");
+
+ InitResolver(&resolver, "global_sideffects3.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(20u, host_resolver.num_resolve());
+
+ EXPECT_EQ(
+ "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
+ "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
+ "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
+ "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
+ "166.155.144.11-166.155.144.11-166.155.144.11-166.155.144.11-"
+ "null:21", proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- 1 alert was logged.
+ EXPECT_EQ(1u, log.GetSize());
+ EXPECT_EQ(1u, request_log.GetSize());
+}
+
+// This test runs a weird PAC script that yields a never ending sequence
+// of DNS resolves when restarting. Running it will hit the maximum
+// DNS resolves per request limit (20) after which every DNS resolve will
+// fail.
+TEST_F(ProxyResolverV8TracingTest, InfiniteDNSSequence2) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host*", "166.155.144.11");
+ host_resolver.rules()->AddRule("*", "122.133.144.155");
+
+ InitResolver(&resolver, "global_sideffects4.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(20u, host_resolver.num_resolve());
+
+ EXPECT_EQ("null21:34", proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ // Check the NetLogs -- 1 alert was logged.
+ EXPECT_EQ(1u, log.GetSize());
+ EXPECT_EQ(1u, request_log.GetSize());
+}
+
+void DnsDuringInitHelper(bool synchronous_host_resolver) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ host_resolver.set_synchronous_mode(synchronous_host_resolver);
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host1", "91.13.12.1");
+ host_resolver.rules()->AddRule("host2", "91.13.12.2");
+
+ InitResolver(&resolver, "dns_during_init.js");
+
+ // Initialization did 2 dnsResolves.
+ EXPECT_EQ(2u, host_resolver.num_resolve());
+
+ host_resolver.rules()->ClearRules();
+ host_resolver.GetHostCache()->clear();
+
+ host_resolver.rules()->AddRule("host1", "145.88.13.3");
+ host_resolver.rules()->AddRule("host2", "137.89.8.45");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info, callback.callback(), NULL,
+ request_log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Fetched host1 and host2 again, since the ones done during initialization
+ // should not have been cached.
+ EXPECT_EQ(4u, host_resolver.num_resolve());
+
+ EXPECT_EQ("91.13.12.1-91.13.12.2-145.88.13.3-137.89.8.45:99",
+ proxy_info.proxy_server().ToURI());
+
+ // Check the NetLogs -- the script generated 2 alerts during initialization.
+ EXPECT_EQ(0u, request_log.GetSize());
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ ASSERT_EQ(2u, entries.size());
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 0, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(
+ LogContainsEvent(entries, 1, NetLog::TYPE_PAC_JAVASCRIPT_ALERT,
+ NetLog::PHASE_NONE));
+
+ EXPECT_EQ("{\"message\":\"Watsup\"}", entries[0].GetParamsJson());
+ EXPECT_EQ("{\"message\":\"Watsup2\"}", entries[1].GetParamsJson());
+}
+
+// Tests a PAC script which does DNS resolves during initialization.
+TEST_F(ProxyResolverV8TracingTest, DnsDuringInit) {
+ // Test with both both a host resolver that always completes asynchronously,
+ // and then again with one that completes synchronously.
+ DnsDuringInitHelper(false);
+ DnsDuringInitHelper(true);
+}
+
+void CrashCallback(int) {
+ // Be extra sure that if the callback ever gets invoked, the test will fail.
+ CHECK(false);
+}
+
+// Start some requests, cancel them all, and then destroy the resolver.
+// Note the execution order for this test can vary. Since multiple
+// threads are involved, the cancellation may be received a different
+// times.
+TEST_F(ProxyResolverV8TracingTest, CancelAll) {
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ host_resolver.rules()->AddSimulatedFailure("*");
+
+ InitResolver(&resolver, "dns.js");
+
+ const size_t kNumRequests = 5;
+ ProxyInfo proxy_info[kNumRequests];
+ ProxyResolver::RequestHandle request[kNumRequests];
+
+ for (size_t i = 0; i < kNumRequests; ++i) {
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info[i],
+ base::Bind(&CrashCallback), &request[i], BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ for (size_t i = 0; i < kNumRequests; ++i) {
+ resolver.CancelRequest(request[i]);
+ }
+}
+
+// Note the execution order for this test can vary. Since multiple
+// threads are involved, the cancellation may be received a different
+// times.
+TEST_F(ProxyResolverV8TracingTest, CancelSome) {
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ host_resolver.rules()->AddSimulatedFailure("*");
+
+ InitResolver(&resolver, "dns.js");
+
+ ProxyInfo proxy_info1;
+ ProxyInfo proxy_info2;
+ ProxyResolver::RequestHandle request1;
+ ProxyResolver::RequestHandle request2;
+ TestCompletionCallback callback;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info1,
+ base::Bind(&CrashCallback), &request1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info2,
+ callback.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ resolver.CancelRequest(request1);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Cancel a request after it has finished running on the worker thread, and has
+// posted a task the completion task back to origin thread.
+TEST_F(ProxyResolverV8TracingTest, CancelWhilePendingCompletionTask) {
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ host_resolver.rules()->AddSimulatedFailure("*");
+
+ InitResolver(&resolver, "error.js");
+
+ ProxyInfo proxy_info1;
+ ProxyInfo proxy_info2;
+ ProxyInfo proxy_info3;
+ ProxyResolver::RequestHandle request1;
+ ProxyResolver::RequestHandle request2;
+ ProxyResolver::RequestHandle request3;
+ TestCompletionCallback callback;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info1,
+ base::Bind(&CrashCallback), &request1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://throw-an-error/"), &proxy_info2,
+ callback.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait until the first request has finished running on the worker thread.
+ // (The second request will output an error).
+ error_observer->WaitForOutput();
+
+ // Cancel the first request, while it has a pending completion task on
+ // the origin thread.
+ resolver.CancelRequest(request1);
+
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, callback.WaitForResult());
+
+ // Start another request, to make sure it is able to complete.
+ rv = resolver.GetProxyForURL(
+ GURL("http://i-have-no-idea-what-im-doing/"), &proxy_info3,
+ callback.callback(), &request3, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ("i-approve-this-message:42",
+ proxy_info3.proxy_server().ToURI());
+}
+
+// This implementation of HostResolver allows blocking until a resolve request
+// has been received. The resolve requests it receives will never be completed.
+class BlockableHostResolver : public HostResolver {
+ public:
+ BlockableHostResolver()
+ : num_cancelled_requests_(0), waiting_for_resolve_(false) {}
+
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE {
+ EXPECT_FALSE(callback.is_null());
+ EXPECT_TRUE(out_req);
+
+ if (!action_.is_null())
+ action_.Run();
+
+ // Indicate to the caller that a request was received.
+ EXPECT_TRUE(waiting_for_resolve_);
+ base::MessageLoop::current()->Quit();
+
+ // This line is intentionally after action_.Run(), since one of the
+ // tests does a cancellation inside of Resolve(), and it is more
+ // interesting if *out_req hasn't been written yet at that point.
+ *out_req = reinterpret_cast<RequestHandle*>(1); // Magic value.
+
+ // Return ERR_IO_PENDING as this request will NEVER be completed.
+ // Expectation is for the caller to later cancel the request.
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE {
+ NOTREACHED();
+ return ERR_DNS_CACHE_MISS;
+ }
+
+ virtual void CancelRequest(RequestHandle req) OVERRIDE {
+ EXPECT_EQ(reinterpret_cast<RequestHandle*>(1), req);
+ num_cancelled_requests_++;
+ }
+
+ void SetAction(const base::Callback<void(void)>& action) {
+ action_ = action;
+ }
+
+ // Waits until Resolve() has been called.
+ void WaitUntilRequestIsReceived() {
+ waiting_for_resolve_ = true;
+ base::MessageLoop::current()->Run();
+ DCHECK(waiting_for_resolve_);
+ waiting_for_resolve_ = false;
+ }
+
+ int num_cancelled_requests() const {
+ return num_cancelled_requests_;
+ }
+
+ private:
+ int num_cancelled_requests_;
+ bool waiting_for_resolve_;
+ base::Callback<void(void)> action_;
+};
+
+// This cancellation test exercises a more predictable cancellation codepath --
+// when the request has an outstanding DNS request in flight.
+TEST_F(ProxyResolverV8TracingTest, CancelWhileOutstandingNonBlockingDns) {
+ BlockableHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ InitResolver(&resolver, "dns.js");
+
+ ProxyInfo proxy_info1;
+ ProxyInfo proxy_info2;
+ ProxyResolver::RequestHandle request1;
+ ProxyResolver::RequestHandle request2;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/req1"), &proxy_info1,
+ base::Bind(&CrashCallback), &request1, BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ host_resolver.WaitUntilRequestIsReceived();
+
+ rv = resolver.GetProxyForURL(
+ GURL("http://foo/req2"), &proxy_info2,
+ base::Bind(&CrashCallback), &request2, BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ host_resolver.WaitUntilRequestIsReceived();
+
+ resolver.CancelRequest(request1);
+ resolver.CancelRequest(request2);
+
+ EXPECT_EQ(2, host_resolver.num_cancelled_requests());
+
+ // After leaving this scope, the ProxyResolver is destroyed.
+ // This should not cause any problems, as the outstanding work
+ // should have been cancelled.
+}
+
+void CancelRequestAndPause(ProxyResolverV8Tracing* resolver,
+ ProxyResolver::RequestHandle request) {
+ resolver->CancelRequest(request);
+
+ // Sleep for a little bit. This makes it more likely for the worker
+ // thread to have returned from its call, and serves as a regression
+ // test for http://crbug.com/173373.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(30));
+}
+
+// In non-blocking mode, the worker thread actually does block for
+// a short time to see if the result is in the DNS cache. Test
+// cancellation while the worker thread is waiting on this event.
+TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns) {
+ BlockableHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ InitResolver(&resolver, "dns.js");
+
+ ProxyInfo proxy_info;
+ ProxyResolver::RequestHandle request;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info,
+ base::Bind(&CrashCallback), &request, BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ host_resolver.SetAction(
+ base::Bind(CancelRequestAndPause, &resolver, request));
+
+ host_resolver.WaitUntilRequestIsReceived();
+
+ // At this point the host resolver ran Resolve(), and should have cancelled
+ // the request.
+
+ EXPECT_EQ(1, host_resolver.num_cancelled_requests());
+}
+
+// Cancel the request while there is a pending DNS request, however before
+// the request is sent to the host resolver.
+TEST_F(ProxyResolverV8TracingTest, CancelWhileBlockedInNonBlockingDns2) {
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ InitResolver(&resolver, "dns.js");
+
+ ProxyInfo proxy_info;
+ ProxyResolver::RequestHandle request;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foo/"), &proxy_info,
+ base::Bind(&CrashCallback), &request, BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Wait a bit, so the DNS task has hopefully been posted. The test will
+ // work whatever the delay is here, but it is most useful if the delay
+ // is large enough to allow a task to be posted back.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ resolver.CancelRequest(request);
+
+ EXPECT_EQ(0u, host_resolver.num_resolve());
+}
+
+TEST_F(ProxyResolverV8TracingTest, CancelSetPacWhileOutstandingBlockingDns) {
+ BlockableHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, NULL);
+
+ int rv =
+ resolver.SetPacScript(LoadScriptData("dns_during_init.js"),
+ base::Bind(&CrashCallback));
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ host_resolver.WaitUntilRequestIsReceived();
+
+ resolver.CancelSetPacScript();
+ EXPECT_EQ(1, host_resolver.num_cancelled_requests());
+}
+
+// This tests that the execution of a PAC script is terminated when the DNS
+// dependencies are missing. If the test fails, then it will hang.
+TEST_F(ProxyResolverV8TracingTest, Terminate) {
+ CapturingNetLog log;
+ CapturingBoundNetLog request_log;
+ MockCachingHostResolver host_resolver;
+ MockErrorObserver* error_observer = new MockErrorObserver;
+ ProxyResolverV8Tracing resolver(&host_resolver, error_observer, &log);
+
+ host_resolver.rules()->AddRule("host1", "182.111.0.222");
+ host_resolver.rules()->AddRule("host2", "111.33.44.55");
+
+ InitResolver(&resolver, "terminate.js");
+
+ TestCompletionCallback callback;
+ ProxyInfo proxy_info;
+
+ int rv = resolver.GetProxyForURL(
+ GURL("http://foopy/req1"),
+ &proxy_info,
+ callback.callback(),
+ NULL,
+ request_log.bound());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // The test does 2 DNS resolutions.
+ EXPECT_EQ(2u, host_resolver.num_resolve());
+
+ EXPECT_EQ("foopy:3", proxy_info.proxy_server().ToURI());
+
+ // No errors.
+ EXPECT_EQ("", error_observer->GetOutput());
+
+ EXPECT_EQ(0u, log.GetSize());
+ EXPECT_EQ(0u, request_log.GetSize());
+}
+
+// Tests that multiple instances of ProxyResolverV8Tracing can coexist and run
+// correctly at the same time. This is relevant because at the moment (time
+// this test was written) each ProxyResolverV8Tracing creates its own thread to
+// run V8 on, however each thread is operating on the same v8::Isolate.
+TEST_F(ProxyResolverV8TracingTest, MultipleResolvers) {
+ // ------------------------
+ // Setup resolver0
+ // ------------------------
+ MockHostResolver host_resolver0;
+ host_resolver0.rules()->AddRuleForAddressFamily(
+ "host1", ADDRESS_FAMILY_IPV4, "166.155.144.44");
+ host_resolver0.rules()
+ ->AddIPLiteralRule("host1", "::1,192.168.1.1", std::string());
+ host_resolver0.rules()->AddSimulatedFailure("host2");
+ host_resolver0.rules()->AddRule("host3", "166.155.144.33");
+ host_resolver0.rules()->AddRule("host5", "166.155.144.55");
+ host_resolver0.rules()->AddSimulatedFailure("host6");
+ host_resolver0.rules()->AddRuleForAddressFamily(
+ "*", ADDRESS_FAMILY_IPV4, "122.133.144.155");
+ host_resolver0.rules()->AddRule("*", "133.122.100.200");
+ ProxyResolverV8Tracing resolver0(
+ &host_resolver0, new MockErrorObserver, NULL);
+ InitResolver(&resolver0, "dns.js");
+
+ // ------------------------
+ // Setup resolver1
+ // ------------------------
+ ProxyResolverV8Tracing resolver1(
+ &host_resolver0, new MockErrorObserver, NULL);
+ InitResolver(&resolver1, "dns.js");
+
+ // ------------------------
+ // Setup resolver2
+ // ------------------------
+ ProxyResolverV8Tracing resolver2(
+ &host_resolver0, new MockErrorObserver, NULL);
+ InitResolver(&resolver2, "simple.js");
+
+ // ------------------------
+ // Setup resolver3
+ // ------------------------
+ MockHostResolver host_resolver3;
+ host_resolver3.rules()->AddRule("foo", "166.155.144.33");
+ ProxyResolverV8Tracing resolver3(
+ &host_resolver3, new MockErrorObserver, NULL);
+ InitResolver(&resolver3, "simple_dns.js");
+
+ // ------------------------
+ // Queue up work for each resolver (which will be running in parallel).
+ // ------------------------
+
+ ProxyResolverV8Tracing* resolver[] = {
+ &resolver0, &resolver1, &resolver2, &resolver3,
+ };
+
+ const size_t kNumResolvers = arraysize(resolver);
+ const size_t kNumIterations = 20;
+ const size_t kNumResults = kNumResolvers * kNumIterations;
+ TestCompletionCallback callback[kNumResults];
+ ProxyInfo proxy_info[kNumResults];
+
+ for (size_t i = 0; i < kNumResults; ++i) {
+ size_t resolver_i = i % kNumResolvers;
+ int rv = resolver[resolver_i]->GetProxyForURL(
+ GURL("http://foo/"), &proxy_info[i], callback[i].callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ }
+
+ // ------------------------
+ // Verify all of the results.
+ // ------------------------
+
+ const char* kExpectedForDnsJs =
+ "122.133.144.155-" // myIpAddress()
+ "null-" // dnsResolve('')
+ "__1_192.168.1.1-" // dnsResolveEx('host1')
+ "null-" // dnsResolve('host2')
+ "166.155.144.33-" // dnsResolve('host3')
+ "122.133.144.155-" // myIpAddress()
+ "166.155.144.33-" // dnsResolve('host3')
+ "__1_192.168.1.1-" // dnsResolveEx('host1')
+ "122.133.144.155-" // myIpAddress()
+ "null-" // dnsResolve('host2')
+ "-" // dnsResolveEx('host6')
+ "133.122.100.200-" // myIpAddressEx()
+ "166.155.144.44" // dnsResolve('host1')
+ ":99";
+
+ for (size_t i = 0; i < kNumResults; ++i) {
+ size_t resolver_i = i % kNumResolvers;
+ EXPECT_EQ(OK, callback[i].WaitForResult());
+
+ std::string proxy_uri = proxy_info[i].proxy_server().ToURI();
+
+ if (resolver_i == 0 || resolver_i == 1) {
+ EXPECT_EQ(kExpectedForDnsJs, proxy_uri);
+ } else if (resolver_i == 2) {
+ EXPECT_EQ("foo:99", proxy_uri);
+ } else if (resolver_i == 3) {
+ EXPECT_EQ("166.155.144.33:",
+ proxy_uri.substr(0, proxy_uri.find(':') + 1));
+ } else {
+ NOTREACHED();
+ }
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_v8_unittest.cc b/chromium/net/proxy/proxy_resolver_v8_unittest.cc
new file mode 100644
index 00000000000..67e77397a8c
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_v8_unittest.cc
@@ -0,0 +1,631 @@
+// Copyright (c) 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.
+
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_resolver_v8.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace {
+
+// Javascript bindings for ProxyResolverV8, which returns mock values.
+// Each time one of the bindings is called into, we push the input into a
+// list, for later verification.
+class MockJSBindings : public ProxyResolverV8::JSBindings {
+ public:
+ MockJSBindings() : my_ip_address_count(0), my_ip_address_ex_count(0),
+ should_terminate(false) {}
+
+ virtual void Alert(const base::string16& message) OVERRIDE {
+ VLOG(1) << "PAC-alert: " << message; // Helpful when debugging.
+ alerts.push_back(UTF16ToUTF8(message));
+ }
+
+ virtual bool ResolveDns(const std::string& host,
+ ResolveDnsOperation op,
+ std::string* output,
+ bool* terminate) OVERRIDE {
+ *terminate = should_terminate;
+
+ if (op == MY_IP_ADDRESS) {
+ my_ip_address_count++;
+ *output = my_ip_address_result;
+ return !my_ip_address_result.empty();
+ }
+
+ if (op == MY_IP_ADDRESS_EX) {
+ my_ip_address_ex_count++;
+ *output = my_ip_address_ex_result;
+ return !my_ip_address_ex_result.empty();
+ }
+
+ if (op == DNS_RESOLVE) {
+ dns_resolves.push_back(host);
+ *output = dns_resolve_result;
+ return !dns_resolve_result.empty();
+ }
+
+ if (op == DNS_RESOLVE_EX) {
+ dns_resolves_ex.push_back(host);
+ *output = dns_resolve_ex_result;
+ return !dns_resolve_ex_result.empty();
+ }
+
+ CHECK(false);
+ return false;
+ }
+
+ virtual void OnError(int line_number,
+ const base::string16& message) OVERRIDE {
+ // Helpful when debugging.
+ VLOG(1) << "PAC-error: [" << line_number << "] " << message;
+
+ errors.push_back(UTF16ToUTF8(message));
+ errors_line_number.push_back(line_number);
+ }
+
+ // Mock values to return.
+ std::string my_ip_address_result;
+ std::string my_ip_address_ex_result;
+ std::string dns_resolve_result;
+ std::string dns_resolve_ex_result;
+
+ // Inputs we got called with.
+ std::vector<std::string> alerts;
+ std::vector<std::string> errors;
+ std::vector<int> errors_line_number;
+ std::vector<std::string> dns_resolves;
+ std::vector<std::string> dns_resolves_ex;
+ int my_ip_address_count;
+ int my_ip_address_ex_count;
+
+ // Whether ResolveDns() should terminate script execution.
+ bool should_terminate;
+};
+
+// This is the same as ProxyResolverV8, but it uses mock bindings in place of
+// the default bindings, and has a helper function to load PAC scripts from
+// disk.
+class ProxyResolverV8WithMockBindings : public ProxyResolverV8 {
+ public:
+ ProxyResolverV8WithMockBindings() {
+ set_js_bindings(&mock_js_bindings_);
+ }
+
+ MockJSBindings* mock_js_bindings() {
+ return &mock_js_bindings_;
+ }
+
+ // Initialize with the PAC script data at |filename|.
+ int SetPacScriptFromDisk(const char* filename) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_resolver_v8_unittest");
+ path = path.AppendASCII(filename);
+
+ // Try to read the file from disk.
+ std::string file_contents;
+ bool ok = file_util::ReadFileToString(path, &file_contents);
+
+ // If we can't load the file from disk, something is misconfigured.
+ if (!ok) {
+ LOG(ERROR) << "Failed to read file: " << path.value();
+ return ERR_UNEXPECTED;
+ }
+
+ // Load the PAC script into the ProxyResolver.
+ return SetPacScript(ProxyResolverScriptData::FromUTF8(file_contents),
+ CompletionCallback());
+ }
+
+ private:
+ MockJSBindings mock_js_bindings_;
+};
+
+// Doesn't really matter what these values are for many of the tests.
+const GURL kQueryUrl("http://www.google.com");
+const GURL kPacUrl;
+
+TEST(ProxyResolverV8Test, Direct) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("direct.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ CapturingBoundNetLog log;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, log.bound());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ // No bindings were called, so no log entries.
+ EXPECT_EQ(0u, entries.size());
+}
+
+TEST(ProxyResolverV8Test, ReturnEmptyString) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("return_empty_string.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+TEST(ProxyResolverV8Test, Basic) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("passthrough.js");
+ EXPECT_EQ(OK, result);
+
+ // The "FindProxyForURL" of this PAC script simply concatenates all of the
+ // arguments into a pseudo-host. The purpose of this test is to verify that
+ // the correct arguments are being passed to FindProxyForURL().
+ {
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(GURL("http://query.com/path"), &proxy_info,
+ CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ("http.query.com.path.query.com:80",
+ proxy_info.proxy_server().ToURI());
+ }
+ {
+ ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(
+ GURL("ftp://query.com:90/path"), &proxy_info, CompletionCallback(),
+ NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+ // Note that FindProxyForURL(url, host) does not expect |host| to contain
+ // the port number.
+ EXPECT_EQ("ftp.query.com.90.path.query.com:80",
+ proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+ }
+
+ // We call this so we'll have code coverage of the function and valgrind will
+ // make sure nothing bad happens.
+ //
+ // NOTE: This is here instead of in its own test so that we'll be calling it
+ // after having done something, in hopes it won't be a no-op.
+ resolver.PurgeMemory();
+}
+
+TEST(ProxyResolverV8Test, BadReturnType) {
+ // These are the filenames of PAC scripts which each return a non-string
+ // types for FindProxyForURL(). They should all fail with
+ // ERR_PAC_SCRIPT_FAILED.
+ static const char* const filenames[] = {
+ "return_undefined.js",
+ "return_integer.js",
+ "return_function.js",
+ "return_object.js",
+ // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ?
+ "return_null.js"
+ };
+
+ for (size_t i = 0; i < arraysize(filenames); ++i) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk(filenames[i]);
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(1U, bindings->errors.size());
+ EXPECT_EQ("FindProxyForURL() did not return a string.",
+ bindings->errors[0]);
+ EXPECT_EQ(-1, bindings->errors_line_number[0]);
+ }
+}
+
+// Try using a PAC script which defines no "FindProxyForURL" function.
+TEST(ProxyResolverV8Test, NoEntryPoint) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("no_entrypoint.js");
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(ERR_FAILED, result);
+}
+
+// Try loading a malformed PAC script.
+TEST(ProxyResolverV8Test, ParseError) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("missing_close_brace.js");
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(ERR_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+
+ // We get one error during compilation.
+ ASSERT_EQ(1U, bindings->errors.size());
+
+ EXPECT_EQ("Uncaught SyntaxError: Unexpected end of input",
+ bindings->errors[0]);
+ EXPECT_EQ(0, bindings->errors_line_number[0]);
+}
+
+// Run a PAC script several times, which has side-effects.
+TEST(ProxyResolverV8Test, SideEffects) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("side_effects.js");
+
+ // The PAC script increments a counter each time we invoke it.
+ for (int i = 0; i < 3; ++i) {
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i),
+ proxy_info.proxy_server().ToURI());
+ }
+
+ // Reload the script -- the javascript environment should be reset, hence
+ // the counter starts over.
+ result = resolver.SetPacScriptFromDisk("side_effects.js");
+ EXPECT_EQ(OK, result);
+
+ for (int i = 0; i < 3; ++i) {
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ(base::StringPrintf("sideffect_%d:80", i),
+ proxy_info.proxy_server().ToURI());
+ }
+}
+
+// Execute a PAC script which throws an exception in FindProxyForURL.
+TEST(ProxyResolverV8Test, UnhandledException) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("unhandled_exception.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ EXPECT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(1U, bindings->errors.size());
+ EXPECT_EQ("Uncaught ReferenceError: undefined_variable is not defined",
+ bindings->errors[0]);
+ EXPECT_EQ(3, bindings->errors_line_number[0]);
+}
+
+TEST(ProxyResolverV8Test, ReturnUnicode) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("return_unicode.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ // The result from this resolve was unparseable, because it
+ // wasn't ASCII.
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// Test the PAC library functions that we expose in the JS environment.
+TEST(ProxyResolverV8Test, JavascriptLibrary) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("pac_library_unittest.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ // If the javascript side of this unit-test fails, it will throw a javascript
+ // exception. Otherwise it will return "PROXY success:80".
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+// Try resolving when SetPacScriptByData() has not been called.
+TEST(ProxyResolverV8Test, NoSetPacScript) {
+ ProxyResolverV8WithMockBindings resolver;
+
+ ProxyInfo proxy_info;
+
+ // Resolve should fail, as we are not yet initialized with a script.
+ int result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_FAILED, result);
+
+ // Initialize it.
+ result = resolver.SetPacScriptFromDisk("direct.js");
+ EXPECT_EQ(OK, result);
+
+ // Resolve should now succeed.
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+
+ // Clear it, by initializing with an empty string.
+ resolver.SetPacScript(
+ ProxyResolverScriptData::FromUTF16(base::string16()),
+ CompletionCallback());
+
+ // Resolve should fail again now.
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_FAILED, result);
+
+ // Load a good script once more.
+ result = resolver.SetPacScriptFromDisk("direct.js");
+ EXPECT_EQ(OK, result);
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, result);
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->alerts.size());
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+}
+
+// Test marshalling/un-marshalling of values between C++/V8.
+TEST(ProxyResolverV8Test, V8Bindings) {
+ ProxyResolverV8WithMockBindings resolver;
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+ bindings->dns_resolve_result = "127.0.0.1";
+ int result = resolver.SetPacScriptFromDisk("bindings.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ EXPECT_EQ(0U, resolver.mock_js_bindings()->errors.size());
+
+ // Alert was called 5 times.
+ ASSERT_EQ(5U, bindings->alerts.size());
+ EXPECT_EQ("undefined", bindings->alerts[0]);
+ EXPECT_EQ("null", bindings->alerts[1]);
+ EXPECT_EQ("undefined", bindings->alerts[2]);
+ EXPECT_EQ("[object Object]", bindings->alerts[3]);
+ EXPECT_EQ("exception from calling toString()", bindings->alerts[4]);
+
+ // DnsResolve was called 8 times, however only 2 of those were string
+ // parameters. (so 6 of them failed immediately).
+ ASSERT_EQ(2U, bindings->dns_resolves.size());
+ EXPECT_EQ("", bindings->dns_resolves[0]);
+ EXPECT_EQ("arg1", bindings->dns_resolves[1]);
+
+ // MyIpAddress was called two times.
+ EXPECT_EQ(2, bindings->my_ip_address_count);
+
+ // MyIpAddressEx was called once.
+ EXPECT_EQ(1, bindings->my_ip_address_ex_count);
+
+ // DnsResolveEx was called 2 times.
+ ASSERT_EQ(2U, bindings->dns_resolves_ex.size());
+ EXPECT_EQ("is_resolvable", bindings->dns_resolves_ex[0]);
+ EXPECT_EQ("foobar", bindings->dns_resolves_ex[1]);
+}
+
+// Test calling a binding (myIpAddress()) from the script's global scope.
+// http://crbug.com/40026
+TEST(ProxyResolverV8Test, BindingCalledDuringInitialization) {
+ ProxyResolverV8WithMockBindings resolver;
+
+ int result = resolver.SetPacScriptFromDisk("binding_from_global.js");
+ EXPECT_EQ(OK, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+
+ // myIpAddress() got called during initialization of the script.
+ EXPECT_EQ(1, bindings->my_ip_address_count);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_FALSE(proxy_info.is_direct());
+ EXPECT_EQ("127.0.0.1:80", proxy_info.proxy_server().ToURI());
+
+ // Check that no other bindings were called.
+ EXPECT_EQ(0U, bindings->errors.size());
+ ASSERT_EQ(0U, bindings->alerts.size());
+ ASSERT_EQ(0U, bindings->dns_resolves.size());
+ EXPECT_EQ(0, bindings->my_ip_address_ex_count);
+ ASSERT_EQ(0U, bindings->dns_resolves_ex.size());
+}
+
+// Try loading a PAC script that ends with a comment and has no terminal
+// newline. This should not cause problems with the PAC utility functions
+// that we add to the script's environment.
+// http://crbug.com/22864
+TEST(ProxyResolverV8Test, EndsWithCommentNoNewline) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("ends_with_comment.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_FALSE(proxy_info.is_direct());
+ EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
+}
+
+// Try loading a PAC script that ends with a statement and has no terminal
+// newline. This should not cause problems with the PAC utility functions
+// that we add to the script's environment.
+// http://crbug.com/22864
+TEST(ProxyResolverV8Test, EndsWithStatementNoNewline) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk(
+ "ends_with_statement_no_semicolon.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_FALSE(proxy_info.is_direct());
+ EXPECT_EQ("success:3", proxy_info.proxy_server().ToURI());
+}
+
+// Test the return values from myIpAddress(), myIpAddressEx(), dnsResolve(),
+// dnsResolveEx(), isResolvable(), isResolvableEx(), when the the binding
+// returns empty string (failure). This simulates the return values from
+// those functions when the underlying DNS resolution fails.
+TEST(ProxyResolverV8Test, DNSResolutionFailure) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("dns_fail.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_FALSE(proxy_info.is_direct());
+ EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
+}
+
+TEST(ProxyResolverV8Test, DNSResolutionOfInternationDomainName) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("international_domain_names.js");
+ EXPECT_EQ(OK, result);
+
+ // Execute FindProxyForURL().
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ kQueryUrl, &proxy_info, CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ // Check that the international domain name was converted to punycode
+ // before passing it onto the bindings layer.
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+
+ ASSERT_EQ(1u, bindings->dns_resolves.size());
+ EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves[0]);
+
+ ASSERT_EQ(1u, bindings->dns_resolves_ex.size());
+ EXPECT_EQ("xn--bcher-kva.ch", bindings->dns_resolves_ex[0]);
+}
+
+// Test that when resolving a URL which contains an IPv6 string literal, the
+// brackets are removed from the host before passing it down to the PAC script.
+// If we don't do this, then subsequent calls to dnsResolveEx(host) will be
+// doomed to fail since it won't correspond with a valid name.
+TEST(ProxyResolverV8Test, IPv6HostnamesNotBracketed) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("resolve_host.js");
+ EXPECT_EQ(OK, result);
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ GURL("http://[abcd::efff]:99/watsupdawg"), &proxy_info,
+ CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+
+ // We called dnsResolveEx() exactly once, by passing through the "host"
+ // argument to FindProxyForURL(). The brackets should have been stripped.
+ ASSERT_EQ(1U, resolver.mock_js_bindings()->dns_resolves_ex.size());
+ EXPECT_EQ("abcd::efff", resolver.mock_js_bindings()->dns_resolves_ex[0]);
+}
+
+// Test that terminating a script within DnsResolve() leads to eventual
+// termination of the script. Also test that repeatedly calling terminate is
+// safe, and running the script again after termination still works.
+TEST(ProxyResolverV8Test, Terminate) {
+ ProxyResolverV8WithMockBindings resolver;
+ int result = resolver.SetPacScriptFromDisk("terminate.js");
+ EXPECT_EQ(OK, result);
+
+ MockJSBindings* bindings = resolver.mock_js_bindings();
+
+ // Terminate script execution upon reaching dnsResolve(). Note that
+ // termination may not take effect right away (so the subsequent dnsResolve()
+ // and alert() may be run).
+ bindings->should_terminate = true;
+
+ ProxyInfo proxy_info;
+ result = resolver.GetProxyForURL(
+ GURL("http://hang/"), &proxy_info,
+ CompletionCallback(), NULL, BoundNetLog());
+
+ // The script execution was terminated.
+ EXPECT_EQ(ERR_PAC_SCRIPT_FAILED, result);
+
+ EXPECT_EQ(1U, resolver.mock_js_bindings()->dns_resolves.size());
+ EXPECT_GE(2U, resolver.mock_js_bindings()->dns_resolves_ex.size());
+ EXPECT_GE(1U, bindings->alerts.size());
+
+ EXPECT_EQ(1U, bindings->errors.size());
+
+ // Termination shows up as an uncaught exception without any message.
+ EXPECT_EQ("", bindings->errors[0]);
+
+ bindings->errors.clear();
+
+ // Try running the script again, this time with a different input which won't
+ // cause a termination+hang.
+ result = resolver.GetProxyForURL(
+ GURL("http://kittens/"), &proxy_info,
+ CompletionCallback(), NULL, BoundNetLog());
+
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ(0u, bindings->errors.size());
+ EXPECT_EQ("kittens:88", proxy_info.proxy_server().ToURI());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_winhttp.cc b/chromium/net/proxy/proxy_resolver_winhttp.cc
new file mode 100644
index 00000000000..32737875e3e
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_winhttp.cc
@@ -0,0 +1,173 @@
+// Copyright (c) 2011 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/proxy/proxy_resolver_winhttp.h"
+
+#include <windows.h>
+#include <winhttp.h>
+
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/proxy_info.h"
+#include "url/gurl.h"
+
+#pragma comment(lib, "winhttp.lib")
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+
+static void FreeInfo(WINHTTP_PROXY_INFO* info) {
+ if (info->lpszProxy)
+ GlobalFree(info->lpszProxy);
+ if (info->lpszProxyBypass)
+ GlobalFree(info->lpszProxyBypass);
+}
+
+ProxyResolverWinHttp::ProxyResolverWinHttp()
+ : ProxyResolver(false /*expects_pac_bytes*/), session_handle_(NULL) {
+}
+
+ProxyResolverWinHttp::~ProxyResolverWinHttp() {
+ CloseWinHttpSession();
+}
+
+int ProxyResolverWinHttp::GetProxyForURL(const GURL& query_url,
+ ProxyInfo* results,
+ const CompletionCallback& /*callback*/,
+ RequestHandle* /*request*/,
+ const BoundNetLog& /*net_log*/) {
+ // If we don't have a WinHTTP session, then create a new one.
+ if (!session_handle_ && !OpenWinHttpSession())
+ return ERR_FAILED;
+
+ // If we have been given an empty PAC url, then use auto-detection.
+ //
+ // NOTE: We just use DNS-based auto-detection here like Firefox. We do this
+ // to avoid WinHTTP's auto-detection code, which while more featureful (it
+ // supports DHCP based auto-detection) also appears to have issues.
+ //
+ WINHTTP_AUTOPROXY_OPTIONS options = {0};
+ options.fAutoLogonIfChallenged = FALSE;
+ options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
+ std::wstring pac_url_wide = ASCIIToWide(pac_url_.spec());
+ options.lpszAutoConfigUrl = pac_url_wide.c_str();
+
+ WINHTTP_PROXY_INFO info = {0};
+ DCHECK(session_handle_);
+
+ // Per http://msdn.microsoft.com/en-us/library/aa383153(VS.85).aspx, it is
+ // necessary to first try resolving with fAutoLogonIfChallenged set to false.
+ // Otherwise, we fail over to trying it with a value of true. This way we
+ // get good performance in the case where WinHTTP uses an out-of-process
+ // resolver. This is important for Vista and Win2k3.
+ BOOL ok = WinHttpGetProxyForUrl(
+ session_handle_, ASCIIToWide(query_url.spec()).c_str(), &options, &info);
+ if (!ok) {
+ if (ERROR_WINHTTP_LOGIN_FAILURE == GetLastError()) {
+ options.fAutoLogonIfChallenged = TRUE;
+ ok = WinHttpGetProxyForUrl(
+ session_handle_, ASCIIToWide(query_url.spec()).c_str(),
+ &options, &info);
+ }
+ if (!ok) {
+ DWORD error = GetLastError();
+ // If we got here because of RPC timeout during out of process PAC
+ // resolution, no further requests on this session are going to work.
+ if (ERROR_WINHTTP_TIMEOUT == error ||
+ ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR == error) {
+ CloseWinHttpSession();
+ }
+ return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code.
+ }
+ }
+
+ int rv = OK;
+
+ switch (info.dwAccessType) {
+ case WINHTTP_ACCESS_TYPE_NO_PROXY:
+ results->UseDirect();
+ break;
+ case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
+ // According to MSDN:
+ //
+ // The proxy server list contains one or more of the following strings
+ // separated by semicolons or whitespace.
+ //
+ // ([<scheme>=][<scheme>"://"]<server>[":"<port>])
+ //
+ // Based on this description, ProxyInfo::UseNamedProxy() isn't
+ // going to handle all the variations (in particular <scheme>=).
+ //
+ // However in practice, it seems that WinHTTP is simply returning
+ // things like "foopy1:80;foopy2:80". It strips out the non-HTTP
+ // proxy types, and stops the list when PAC encounters a "DIRECT".
+ // So UseNamedProxy() should work OK.
+ results->UseNamedProxy(WideToASCII(info.lpszProxy));
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+
+ FreeInfo(&info);
+ return rv;
+}
+
+void ProxyResolverWinHttp::CancelRequest(RequestHandle request) {
+ // This is a synchronous ProxyResolver; no possibility for async requests.
+ NOTREACHED();
+}
+
+LoadState ProxyResolverWinHttp::GetLoadState(RequestHandle request) const {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+}
+
+void ProxyResolverWinHttp::CancelSetPacScript() {
+ NOTREACHED();
+}
+
+int ProxyResolverWinHttp::SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const CompletionCallback& /*callback*/) {
+ if (script_data->type() == ProxyResolverScriptData::TYPE_AUTO_DETECT) {
+ pac_url_ = GURL("http://wpad/wpad.dat");
+ } else {
+ pac_url_ = script_data->url();
+ }
+ return OK;
+}
+
+bool ProxyResolverWinHttp::OpenWinHttpSession() {
+ DCHECK(!session_handle_);
+ session_handle_ = WinHttpOpen(NULL,
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+ if (!session_handle_)
+ return false;
+
+ // Since this session handle will never be used for WinHTTP connections,
+ // these timeouts don't really mean much individually. However, WinHTTP's
+ // out of process PAC resolution will use a combined (sum of all timeouts)
+ // value to wait for an RPC reply.
+ BOOL rv = WinHttpSetTimeouts(session_handle_, 10000, 10000, 5000, 5000);
+ DCHECK(rv);
+
+ return true;
+}
+
+void ProxyResolverWinHttp::CloseWinHttpSession() {
+ if (session_handle_) {
+ WinHttpCloseHandle(session_handle_);
+ session_handle_ = NULL;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_resolver_winhttp.h b/chromium/net/proxy/proxy_resolver_winhttp.h
new file mode 100644
index 00000000000..62b56436ba0
--- /dev/null
+++ b/chromium/net/proxy/proxy_resolver_winhttp.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 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_PROXY_PROXY_RESOLVER_WINHTTP_H_
+#define NET_PROXY_PROXY_RESOLVER_WINHTTP_H_
+
+#include "base/compiler_specific.h"
+#include "net/proxy/proxy_resolver.h"
+#include "url/gurl.h"
+
+typedef void* HINTERNET; // From winhttp.h
+
+namespace net {
+
+// An implementation of ProxyResolver that uses WinHTTP and the system
+// proxy settings.
+class NET_EXPORT_PRIVATE ProxyResolverWinHttp : public ProxyResolver {
+ public:
+ ProxyResolverWinHttp();
+ virtual ~ProxyResolverWinHttp();
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& /*callback*/,
+ RequestHandle* /*request*/,
+ const BoundNetLog& /*net_log*/) OVERRIDE;
+ virtual void CancelRequest(RequestHandle request) OVERRIDE;
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE;
+
+ virtual void CancelSetPacScript() OVERRIDE;
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const net::CompletionCallback& /*callback*/) OVERRIDE;
+
+ private:
+ bool OpenWinHttpSession();
+ void CloseWinHttpSession();
+
+ // Proxy configuration is cached on the session handle.
+ HINTERNET session_handle_;
+
+ GURL pac_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverWinHttp);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_WINHTTP_H_
diff --git a/chromium/net/proxy/proxy_retry_info.h b/chromium/net/proxy/proxy_retry_info.h
new file mode 100644
index 00000000000..8825289a352
--- /dev/null
+++ b/chromium/net/proxy/proxy_retry_info.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2006-2008 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_PROXY_PROXY_RETRY_INFO_H_
+#define NET_PROXY_PROXY_RETRY_INFO_H_
+
+#include <map>
+
+#include "base/time/time.h"
+
+namespace net {
+
+// Contains the information about when to retry a proxy server.
+struct ProxyRetryInfo {
+ // We should not retry until this time.
+ base::TimeTicks bad_until;
+
+ // This is the current delay. If the proxy is still bad, we need to increase
+ // this delay.
+ base::TimeDelta current_delay;
+};
+
+// Map of proxy servers with the associated RetryInfo structures.
+// The key is a proxy URI string [<scheme>"://"]<host>":"<port>.
+typedef std::map<std::string, ProxyRetryInfo> ProxyRetryInfoMap;
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RETRY_INFO_H_
diff --git a/chromium/net/proxy/proxy_script_decider.cc b/chromium/net/proxy/proxy_script_decider.cc
new file mode 100644
index 00000000000..38bf751cd4d
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_decider.cc
@@ -0,0 +1,414 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_script_decider.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+#include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
+#include "net/proxy/proxy_script_fetcher.h"
+
+namespace net {
+
+namespace {
+
+bool LooksLikePacScript(const base::string16& script) {
+ // Note: this is only an approximation! It may not always work correctly,
+ // however it is very likely that legitimate scripts have this exact string,
+ // since they must minimally define a function of this name. Conversely, a
+ // file not containing the string is not likely to be a PAC script.
+ //
+ // An exact test would have to load the script in a javascript evaluator.
+ return script.find(ASCIIToUTF16("FindProxyForURL")) != base::string16::npos;
+}
+
+}
+
+// This is the hard-coded location used by the DNS portion of web proxy
+// auto-discovery.
+//
+// Note that we not use DNS devolution to find the WPAD host, since that could
+// be dangerous should our top level domain registry become out of date.
+//
+// Instead we directly resolve "wpad", and let the operating system apply the
+// DNS suffix search paths. This is the same approach taken by Firefox, and
+// compatibility hasn't been an issue.
+//
+// For more details, also check out this comment:
+// http://code.google.com/p/chromium/issues/detail?id=18575#c20
+static const char kWpadUrl[] = "http://wpad/wpad.dat";
+
+base::Value* ProxyScriptDecider::PacSource::NetLogCallback(
+ const GURL* effective_pac_url,
+ NetLog::LogLevel /* log_level */) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ std::string source;
+ switch (type) {
+ case PacSource::WPAD_DHCP:
+ source = "WPAD DHCP";
+ break;
+ case PacSource::WPAD_DNS:
+ source = "WPAD DNS: ";
+ source += effective_pac_url->possibly_invalid_spec();
+ break;
+ case PacSource::CUSTOM:
+ source = "Custom PAC URL: ";
+ source += effective_pac_url->possibly_invalid_spec();
+ break;
+ }
+ dict->SetString("source", source);
+ return dict;
+}
+
+ProxyScriptDecider::ProxyScriptDecider(
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ NetLog* net_log)
+ : resolver_(NULL),
+ proxy_script_fetcher_(proxy_script_fetcher),
+ dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher),
+ current_pac_source_index_(0u),
+ pac_mandatory_(false),
+ next_state_(STATE_NONE),
+ net_log_(BoundNetLog::Make(
+ net_log, NetLog::SOURCE_PROXY_SCRIPT_DECIDER)),
+ fetch_pac_bytes_(false) {
+}
+
+ProxyScriptDecider::~ProxyScriptDecider() {
+ if (next_state_ != STATE_NONE)
+ Cancel();
+}
+
+int ProxyScriptDecider::Start(
+ const ProxyConfig& config, const base::TimeDelta wait_delay,
+ bool fetch_pac_bytes, const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(!callback.is_null());
+ DCHECK(config.HasAutomaticSettings());
+
+ net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
+
+ fetch_pac_bytes_ = fetch_pac_bytes;
+
+ // Save the |wait_delay| as a non-negative value.
+ wait_delay_ = wait_delay;
+ if (wait_delay_ < base::TimeDelta())
+ wait_delay_ = base::TimeDelta();
+
+ pac_mandatory_ = config.pac_mandatory();
+
+ pac_sources_ = BuildPacSourcesFallbackList(config);
+ DCHECK(!pac_sources_.empty());
+
+ next_state_ = STATE_WAIT;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+ else
+ DidComplete();
+
+ return rv;
+}
+
+const ProxyConfig& ProxyScriptDecider::effective_config() const {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return effective_config_;
+}
+
+// TODO(eroman): Return a const-pointer.
+ProxyResolverScriptData* ProxyScriptDecider::script_data() const {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return script_data_.get();
+}
+
+// Initialize the fallback rules.
+// (1) WPAD (DHCP).
+// (2) WPAD (DNS).
+// (3) Custom PAC URL.
+ProxyScriptDecider::PacSourceList ProxyScriptDecider::
+ BuildPacSourcesFallbackList(
+ const ProxyConfig& config) const {
+ PacSourceList pac_sources;
+ if (config.auto_detect()) {
+ pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL()));
+ pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL()));
+ }
+ if (config.has_pac_url())
+ pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
+ return pac_sources;
+}
+
+void ProxyScriptDecider::OnIOCompletion(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ DidComplete();
+ DoCallback(rv);
+ }
+}
+
+int ProxyScriptDecider::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_WAIT:
+ DCHECK_EQ(OK, rv);
+ rv = DoWait();
+ break;
+ case STATE_WAIT_COMPLETE:
+ rv = DoWaitComplete(rv);
+ break;
+ case STATE_FETCH_PAC_SCRIPT:
+ DCHECK_EQ(OK, rv);
+ rv = DoFetchPacScript();
+ break;
+ case STATE_FETCH_PAC_SCRIPT_COMPLETE:
+ rv = DoFetchPacScriptComplete(rv);
+ break;
+ case STATE_VERIFY_PAC_SCRIPT:
+ DCHECK_EQ(OK, rv);
+ rv = DoVerifyPacScript();
+ break;
+ case STATE_VERIFY_PAC_SCRIPT_COMPLETE:
+ rv = DoVerifyPacScriptComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+void ProxyScriptDecider::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(!callback_.is_null());
+ callback_.Run(result);
+}
+
+int ProxyScriptDecider::DoWait() {
+ next_state_ = STATE_WAIT_COMPLETE;
+
+ // If no waiting is required, continue on to the next state.
+ if (wait_delay_.ToInternalValue() == 0)
+ return OK;
+
+ // Otherwise wait the specified amount of time.
+ wait_timer_.Start(FROM_HERE, wait_delay_, this,
+ &ProxyScriptDecider::OnWaitTimerFired);
+ net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT);
+ return ERR_IO_PENDING;
+}
+
+int ProxyScriptDecider::DoWaitComplete(int result) {
+ DCHECK_EQ(OK, result);
+ if (wait_delay_.ToInternalValue() != 0) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT,
+ result);
+ }
+ next_state_ = GetStartState();
+ return OK;
+}
+
+int ProxyScriptDecider::DoFetchPacScript() {
+ DCHECK(fetch_pac_bytes_);
+
+ next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
+
+ const PacSource& pac_source = current_pac_source();
+
+ GURL effective_pac_url;
+ DetermineURL(pac_source, &effective_pac_url);
+
+ net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT,
+ base::Bind(&PacSource::NetLogCallback,
+ base::Unretained(&pac_source),
+ &effective_pac_url));
+
+ if (pac_source.type == PacSource::WPAD_DHCP) {
+ if (!dhcp_proxy_script_fetcher_) {
+ net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
+ return ERR_UNEXPECTED;
+ }
+
+ return dhcp_proxy_script_fetcher_->Fetch(
+ &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion,
+ base::Unretained(this)));
+ }
+
+ if (!proxy_script_fetcher_) {
+ net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
+ return ERR_UNEXPECTED;
+ }
+
+ return proxy_script_fetcher_->Fetch(
+ effective_pac_url, &pac_script_,
+ base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)));
+}
+
+int ProxyScriptDecider::DoFetchPacScriptComplete(int result) {
+ DCHECK(fetch_pac_bytes_);
+
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result);
+ if (result != OK)
+ return TryToFallbackPacSource(result);
+
+ next_state_ = STATE_VERIFY_PAC_SCRIPT;
+ return result;
+}
+
+int ProxyScriptDecider::DoVerifyPacScript() {
+ next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
+
+ // This is just a heuristic. Ideally we would try to parse the script.
+ if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
+ return ERR_PAC_SCRIPT_FAILED;
+
+ return OK;
+}
+
+int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) {
+ if (result != OK)
+ return TryToFallbackPacSource(result);
+
+ const PacSource& pac_source = current_pac_source();
+
+ // Extract the current script data.
+ if (fetch_pac_bytes_) {
+ script_data_ = ProxyResolverScriptData::FromUTF16(pac_script_);
+ } else {
+ script_data_ = pac_source.type == PacSource::CUSTOM ?
+ ProxyResolverScriptData::FromURL(pac_source.url) :
+ ProxyResolverScriptData::ForAutoDetect();
+ }
+
+ // Let the caller know which automatic setting we ended up initializing the
+ // resolver for (there may have been multiple fallbacks to choose from.)
+ if (current_pac_source().type == PacSource::CUSTOM) {
+ effective_config_ =
+ ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
+ effective_config_.set_pac_mandatory(pac_mandatory_);
+ } else {
+ if (fetch_pac_bytes_) {
+ GURL auto_detected_url;
+
+ switch (current_pac_source().type) {
+ case PacSource::WPAD_DHCP:
+ auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL();
+ break;
+
+ case PacSource::WPAD_DNS:
+ auto_detected_url = GURL(kWpadUrl);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ effective_config_ =
+ ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
+ } else {
+ // The resolver does its own resolution so we cannot know the
+ // URL. Just do the best we can and state that the configuration
+ // is to auto-detect proxy settings.
+ effective_config_ = ProxyConfig::CreateAutoDetect();
+ }
+ }
+
+ return OK;
+}
+
+int ProxyScriptDecider::TryToFallbackPacSource(int error) {
+ DCHECK_LT(error, 0);
+
+ if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
+ // Nothing left to fall back to.
+ return error;
+ }
+
+ // Advance to next URL in our list.
+ ++current_pac_source_index_;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
+
+ next_state_ = GetStartState();
+
+ return OK;
+}
+
+ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
+ return fetch_pac_bytes_ ? STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
+}
+
+void ProxyScriptDecider::DetermineURL(const PacSource& pac_source,
+ GURL* effective_pac_url) {
+ DCHECK(effective_pac_url);
+
+ switch (pac_source.type) {
+ case PacSource::WPAD_DHCP:
+ break;
+ case PacSource::WPAD_DNS:
+ *effective_pac_url = GURL(kWpadUrl);
+ break;
+ case PacSource::CUSTOM:
+ *effective_pac_url = pac_source.url;
+ break;
+ }
+}
+
+const ProxyScriptDecider::PacSource&
+ ProxyScriptDecider::current_pac_source() const {
+ DCHECK_LT(current_pac_source_index_, pac_sources_.size());
+ return pac_sources_[current_pac_source_index_];
+}
+
+void ProxyScriptDecider::OnWaitTimerFired() {
+ OnIOCompletion(OK);
+}
+
+void ProxyScriptDecider::DidComplete() {
+ net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
+}
+
+void ProxyScriptDecider::Cancel() {
+ DCHECK_NE(STATE_NONE, next_state_);
+
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+
+ switch (next_state_) {
+ case STATE_WAIT_COMPLETE:
+ wait_timer_.Stop();
+ break;
+ case STATE_FETCH_PAC_SCRIPT_COMPLETE:
+ proxy_script_fetcher_->Cancel();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // This is safe to call in any state.
+ if (dhcp_proxy_script_fetcher_)
+ dhcp_proxy_script_fetcher_->Cancel();
+
+ DidComplete();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_script_decider.h b/chromium/net/proxy/proxy_script_decider.h
new file mode 100644
index 00000000000..9a77938ec8e
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_decider.h
@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_SCRIPT_DECIDER_H_
+#define NET_PROXY_PROXY_SCRIPT_DECIDER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_resolver.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class DhcpProxyScriptFetcher;
+class NetLogParameter;
+class ProxyResolver;
+class ProxyScriptFetcher;
+
+// ProxyScriptDecider is a helper class used by ProxyService to determine which
+// PAC script to use given our proxy configuration.
+//
+// This involves trying to use PAC scripts in this order:
+//
+// (1) WPAD (DHCP) if auto-detect is on.
+// (2) WPAD (DNS) if auto-detect is on.
+// (3) Custom PAC script if a URL was given.
+//
+// If no PAC script was successfully selected, then it fails with either a
+// network error, or PAC_SCRIPT_FAILED (indicating it did not pass our
+// validation).
+//
+// On successful completion, the fetched PAC script data can be accessed using
+// script_data().
+//
+// Deleting ProxyScriptDecider while Init() is in progress, will
+// cancel the request.
+//
+class NET_EXPORT_PRIVATE ProxyScriptDecider {
+ public:
+ // |proxy_script_fetcher|, |dhcp_proxy_script_fetcher| and
+ // |net_log| must remain valid for the lifespan of ProxyScriptDecider.
+ ProxyScriptDecider(ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ NetLog* net_log);
+
+ // Aborts any in-progress request.
+ ~ProxyScriptDecider();
+
+ // Evaluates the effective proxy settings for |config|, and downloads the
+ // associated PAC script.
+ // If |wait_delay| is positive, the initialization will pause for this
+ // amount of time before getting started.
+ // On successful completion, the "effective" proxy settings we ended up
+ // deciding on will be available vial the effective_settings() accessor.
+ // Note that this may differ from |config| since we will have stripped any
+ // manual settings, and decided whether to use auto-detect or the custom PAC
+ // URL. Finally, if auto-detect was used we may now have resolved that to a
+ // specific script URL.
+ int Start(const ProxyConfig& config,
+ const base::TimeDelta wait_delay,
+ bool fetch_pac_bytes,
+ const net::CompletionCallback& callback);
+
+ const ProxyConfig& effective_config() const;
+
+ // TODO(eroman): Return a const-pointer.
+ ProxyResolverScriptData* script_data() const;
+
+ private:
+ // Represents the sources from which we can get PAC files; two types of
+ // auto-detect or a custom URL.
+ struct PacSource {
+ enum Type {
+ WPAD_DHCP,
+ WPAD_DNS,
+ CUSTOM
+ };
+
+ PacSource(Type type, const GURL& url)
+ : type(type), url(url) {}
+
+ // Returns a Value representing the PacSource. |effective_pac_url| must
+ // be non-NULL and point to the URL derived from information contained in
+ // |this|, if Type is not WPAD_DHCP.
+ base::Value* NetLogCallback(const GURL* effective_pac_url,
+ NetLog::LogLevel log_level) const;
+
+ Type type;
+ GURL url; // Empty unless |type == PAC_SOURCE_CUSTOM|.
+ };
+
+ typedef std::vector<PacSource> PacSourceList;
+
+ enum State {
+ STATE_NONE,
+ STATE_WAIT,
+ STATE_WAIT_COMPLETE,
+ STATE_FETCH_PAC_SCRIPT,
+ STATE_FETCH_PAC_SCRIPT_COMPLETE,
+ STATE_VERIFY_PAC_SCRIPT,
+ STATE_VERIFY_PAC_SCRIPT_COMPLETE,
+ };
+
+ // Returns ordered list of PAC urls to try for |config|.
+ PacSourceList BuildPacSourcesFallbackList(const ProxyConfig& config) const;
+
+ void OnIOCompletion(int result);
+ int DoLoop(int result);
+ void DoCallback(int result);
+
+ int DoWait();
+ int DoWaitComplete(int result);
+
+ int DoFetchPacScript();
+ int DoFetchPacScriptComplete(int result);
+
+ int DoVerifyPacScript();
+ int DoVerifyPacScriptComplete(int result);
+
+ // Tries restarting using the next fallback PAC URL:
+ // |pac_sources_[++current_pac_source_index]|.
+ // Returns OK and rewinds the state machine when there
+ // is something to try, otherwise returns |error|.
+ int TryToFallbackPacSource(int error);
+
+ // Gets the initial state (we skip fetching when the
+ // ProxyResolver doesn't |expect_pac_bytes()|.
+ State GetStartState() const;
+
+ void DetermineURL(const PacSource& pac_source, GURL* effective_pac_url);
+
+ // Returns the current PAC URL we are fetching/testing.
+ const PacSource& current_pac_source() const;
+
+ void OnWaitTimerFired();
+ void DidComplete();
+ void Cancel();
+
+ ProxyResolver* resolver_;
+ ProxyScriptFetcher* proxy_script_fetcher_;
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_;
+
+ net::CompletionCallback callback_;
+
+ size_t current_pac_source_index_;
+
+ // Filled when the PAC script fetch completes.
+ base::string16 pac_script_;
+
+ // Flag indicating whether the caller requested a mandatory pac script
+ // (i.e. fallback to direct connections are prohibited).
+ bool pac_mandatory_;
+
+ PacSourceList pac_sources_;
+ State next_state_;
+
+ BoundNetLog net_log_;
+
+ bool fetch_pac_bytes_;
+
+ base::TimeDelta wait_delay_;
+ base::OneShotTimer<ProxyScriptDecider> wait_timer_;
+
+ // Results.
+ ProxyConfig effective_config_;
+ scoped_refptr<ProxyResolverScriptData> script_data_;
+
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyScriptDecider);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SCRIPT_DECIDER_H_
diff --git a/chromium/net/proxy/proxy_script_decider_unittest.cc b/chromium/net/proxy/proxy_script_decider_unittest.cc
new file mode 100644
index 00000000000..977bd4df642
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_decider_unittest.cc
@@ -0,0 +1,599 @@
+// Copyright (c) 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.
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_script_decider.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+enum Error {
+ kFailedDownloading = -100,
+ kFailedParsing = ERR_PAC_SCRIPT_FAILED,
+};
+
+class Rules {
+ public:
+ struct Rule {
+ Rule(const GURL& url, int fetch_error, bool is_valid_script)
+ : url(url),
+ fetch_error(fetch_error),
+ is_valid_script(is_valid_script) {
+ }
+
+ base::string16 text() const {
+ if (is_valid_script)
+ return UTF8ToUTF16(url.spec() + "!FindProxyForURL");
+ if (fetch_error == OK)
+ return UTF8ToUTF16(url.spec() + "!invalid-script");
+ return base::string16();
+ }
+
+ GURL url;
+ int fetch_error;
+ bool is_valid_script;
+ };
+
+ Rule AddSuccessRule(const char* url) {
+ Rule rule(GURL(url), OK /*fetch_error*/, true);
+ rules_.push_back(rule);
+ return rule;
+ }
+
+ void AddFailDownloadRule(const char* url) {
+ rules_.push_back(Rule(GURL(url), kFailedDownloading /*fetch_error*/,
+ false));
+ }
+
+ void AddFailParsingRule(const char* url) {
+ rules_.push_back(Rule(GURL(url), OK /*fetch_error*/, false));
+ }
+
+ const Rule& GetRuleByUrl(const GURL& url) const {
+ for (RuleList::const_iterator it = rules_.begin(); it != rules_.end();
+ ++it) {
+ if (it->url == url)
+ return *it;
+ }
+ LOG(FATAL) << "Rule not found for " << url;
+ return rules_[0];
+ }
+
+ const Rule& GetRuleByText(const base::string16& text) const {
+ for (RuleList::const_iterator it = rules_.begin(); it != rules_.end();
+ ++it) {
+ if (it->text() == text)
+ return *it;
+ }
+ LOG(FATAL) << "Rule not found for " << text;
+ return rules_[0];
+ }
+
+ private:
+ typedef std::vector<Rule> RuleList;
+ RuleList rules_;
+};
+
+class RuleBasedProxyScriptFetcher : public ProxyScriptFetcher {
+ public:
+ explicit RuleBasedProxyScriptFetcher(const Rules* rules) : rules_(rules) {}
+
+ // ProxyScriptFetcher implementation.
+ virtual int Fetch(const GURL& url,
+ base::string16* text,
+ const CompletionCallback& callback) OVERRIDE {
+ const Rules::Rule& rule = rules_->GetRuleByUrl(url);
+ int rv = rule.fetch_error;
+ EXPECT_NE(ERR_UNEXPECTED, rv);
+ if (rv == OK)
+ *text = rule.text();
+ return rv;
+ }
+
+ virtual void Cancel() OVERRIDE {}
+
+ virtual URLRequestContext* GetRequestContext() const OVERRIDE { return NULL; }
+
+ private:
+ const Rules* rules_;
+};
+
+// Succeed using custom PAC script.
+TEST(ProxyScriptDeciderTest, CustomPacSucceeds) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log);
+ EXPECT_EQ(OK, decider.Start(
+ config, base::TimeDelta(), true, callback.callback()));
+ EXPECT_EQ(rule.text(), decider.script_data()->utf16());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(4u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 3, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+
+ EXPECT_TRUE(decider.effective_config().has_pac_url());
+ EXPECT_EQ(config.pac_url(), decider.effective_config().pac_url());
+}
+
+// Fail downloading the custom PAC script.
+TEST(ProxyScriptDeciderTest, CustomPacFails1) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log);
+ EXPECT_EQ(kFailedDownloading,
+ decider.Start(config, base::TimeDelta(), true,
+ callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(4u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 3, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+
+ EXPECT_FALSE(decider.effective_config().has_pac_url());
+}
+
+// Fail parsing the custom PAC script.
+TEST(ProxyScriptDeciderTest, CustomPacFails2) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailParsingRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(kFailedParsing,
+ decider.Start(config, base::TimeDelta(), true,
+ callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+}
+
+// Fail downloading the custom PAC script, because the fetcher was NULL.
+TEST(ProxyScriptDeciderTest, HasNullProxyScriptFetcher) {
+ Rules rules;
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(NULL, &dhcp_fetcher, NULL);
+ EXPECT_EQ(ERR_UNEXPECTED,
+ decider.Start(config, base::TimeDelta(), true,
+ callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+}
+
+// Succeeds in choosing autodetect (WPAD DNS).
+TEST(ProxyScriptDeciderTest, AutodetectSuccess) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+
+ Rules::Rule rule = rules.AddSuccessRule("http://wpad/wpad.dat");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(OK, decider.Start(
+ config, base::TimeDelta(), true, callback.callback()));
+ EXPECT_EQ(rule.text(), decider.script_data()->utf16());
+
+ EXPECT_TRUE(decider.effective_config().has_pac_url());
+ EXPECT_EQ(rule.url, decider.effective_config().pac_url());
+}
+
+// Fails at WPAD (downloading), but succeeds in choosing the custom PAC.
+TEST(ProxyScriptDeciderTest, AutodetectFailCustomSuccess1) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+ Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(OK, decider.Start(
+ config, base::TimeDelta(), true, callback.callback()));
+ EXPECT_EQ(rule.text(), decider.script_data()->utf16());
+
+ EXPECT_TRUE(decider.effective_config().has_pac_url());
+ EXPECT_EQ(rule.url, decider.effective_config().pac_url());
+}
+
+// Fails at WPAD (no DHCP config, DNS PAC fails parsing), but succeeds in
+// choosing the custom PAC.
+TEST(ProxyScriptDeciderTest, AutodetectFailCustomSuccess2) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+ config.proxy_rules().ParseFromString("unused-manual-proxy:99");
+
+ rules.AddFailParsingRule("http://wpad/wpad.dat");
+ Rules::Rule rule = rules.AddSuccessRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log);
+ EXPECT_EQ(OK, decider.Start(config, base::TimeDelta(),
+ true, callback.callback()));
+ EXPECT_EQ(rule.text(), decider.script_data()->utf16());
+
+ // Verify that the effective configuration no longer contains auto detect or
+ // any of the manual settings.
+ EXPECT_TRUE(decider.effective_config().Equals(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://custom/proxy.pac"))));
+
+ // Check the NetLog was filled correctly.
+ // (Note that various states are repeated since both WPAD and custom
+ // PAC scripts are tried).
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(10u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+ // This is the DHCP phase, which fails fetching rather than parsing, so
+ // there is no pair of SET_PAC_SCRIPT events.
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 3,
+ NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE,
+ NetLog::PHASE_NONE));
+ // This is the DNS phase, which attempts a fetch but fails.
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 4, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 5, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 6,
+ NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE,
+ NetLog::PHASE_NONE));
+ // Finally, the custom PAC URL phase.
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 7, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 8, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 9, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+}
+
+// Fails at WPAD (downloading), and fails at custom PAC (downloading).
+TEST(ProxyScriptDeciderTest, AutodetectFailCustomFails1) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+ rules.AddFailDownloadRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(kFailedDownloading,
+ decider.Start(config, base::TimeDelta(), true,
+ callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+}
+
+// Fails at WPAD (downloading), and fails at custom PAC (parsing).
+TEST(ProxyScriptDeciderTest, AutodetectFailCustomFails2) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+ rules.AddFailParsingRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(kFailedParsing,
+ decider.Start(config, base::TimeDelta(), true,
+ callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+}
+
+// This is a copy-paste of CustomPacFails1, with the exception that we give it
+// a 1 millisecond delay. This means it will now complete asynchronously.
+// Moreover, we test the NetLog to make sure it logged the pause.
+TEST(ProxyScriptDeciderTest, CustomPacFails1_WithPositiveDelay) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log);
+ EXPECT_EQ(ERR_IO_PENDING,
+ decider.Start(config, base::TimeDelta::FromMilliseconds(1),
+ true, callback.callback()));
+
+ EXPECT_EQ(kFailedDownloading, callback.WaitForResult());
+ EXPECT_EQ(NULL, decider.script_data());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(6u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 3, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 4, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 5, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+}
+
+// This is a copy-paste of CustomPacFails1, with the exception that we give it
+// a -5 second delay instead of a 0 ms delay. This change should have no effect
+// so the rest of the test is unchanged.
+TEST(ProxyScriptDeciderTest, CustomPacFails1_WithNegativeDelay) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ DoNothingDhcpProxyScriptFetcher dhcp_fetcher;
+
+ ProxyConfig config;
+ config.set_pac_url(GURL("http://custom/proxy.pac"));
+
+ rules.AddFailDownloadRule("http://custom/proxy.pac");
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, &log);
+ EXPECT_EQ(kFailedDownloading,
+ decider.Start(config, base::TimeDelta::FromSeconds(-5),
+ true, callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(4u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 3, NetLog::TYPE_PROXY_SCRIPT_DECIDER));
+}
+
+class SynchronousSuccessDhcpFetcher : public DhcpProxyScriptFetcher {
+ public:
+ explicit SynchronousSuccessDhcpFetcher(const base::string16& expected_text)
+ : gurl_("http://dhcppac/"), expected_text_(expected_text) {
+ }
+
+ virtual int Fetch(base::string16* utf16_text,
+ const CompletionCallback& callback) OVERRIDE {
+ *utf16_text = expected_text_;
+ return OK;
+ }
+
+ virtual void Cancel() OVERRIDE {
+ }
+
+ virtual const GURL& GetPacURL() const OVERRIDE {
+ return gurl_;
+ }
+
+ const base::string16& expected_text() const {
+ return expected_text_;
+ }
+
+ private:
+ GURL gurl_;
+ base::string16 expected_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(SynchronousSuccessDhcpFetcher);
+};
+
+// All of the tests above that use ProxyScriptDecider have tested
+// failure to fetch a PAC file via DHCP configuration, so we now test
+// success at downloading and parsing, and then success at downloading,
+// failure at parsing.
+
+TEST(ProxyScriptDeciderTest, AutodetectDhcpSuccess) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ SynchronousSuccessDhcpFetcher dhcp_fetcher(
+ WideToUTF16(L"http://bingo/!FindProxyForURL"));
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+
+ rules.AddSuccessRule("http://bingo/");
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ EXPECT_EQ(OK, decider.Start(
+ config, base::TimeDelta(), true, callback.callback()));
+ EXPECT_EQ(dhcp_fetcher.expected_text(),
+ decider.script_data()->utf16());
+
+ EXPECT_TRUE(decider.effective_config().has_pac_url());
+ EXPECT_EQ(GURL("http://dhcppac/"), decider.effective_config().pac_url());
+}
+
+TEST(ProxyScriptDeciderTest, AutodetectDhcpFailParse) {
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+ SynchronousSuccessDhcpFetcher dhcp_fetcher(
+ WideToUTF16(L"http://bingo/!invalid-script"));
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+
+ rules.AddFailParsingRule("http://bingo/");
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+
+ TestCompletionCallback callback;
+ ProxyScriptDecider decider(&fetcher, &dhcp_fetcher, NULL);
+ // Since there is fallback to DNS-based WPAD, the final error will be that
+ // it failed downloading, not that it failed parsing.
+ EXPECT_EQ(kFailedDownloading,
+ decider.Start(config, base::TimeDelta(), true, callback.callback()));
+ EXPECT_EQ(NULL, decider.script_data());
+
+ EXPECT_FALSE(decider.effective_config().has_pac_url());
+}
+
+class AsyncFailDhcpFetcher
+ : public DhcpProxyScriptFetcher,
+ public base::SupportsWeakPtr<AsyncFailDhcpFetcher> {
+ public:
+ AsyncFailDhcpFetcher() {}
+ virtual ~AsyncFailDhcpFetcher() {}
+
+ virtual int Fetch(base::string16* utf16_text,
+ const CompletionCallback& callback) OVERRIDE {
+ callback_ = callback;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&AsyncFailDhcpFetcher::CallbackWithFailure, AsWeakPtr()));
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Cancel() OVERRIDE {
+ callback_.Reset();
+ }
+
+ virtual const GURL& GetPacURL() const OVERRIDE {
+ return dummy_gurl_;
+ }
+
+ void CallbackWithFailure() {
+ if (!callback_.is_null())
+ callback_.Run(ERR_PAC_NOT_IN_DHCP);
+ }
+
+ private:
+ GURL dummy_gurl_;
+ CompletionCallback callback_;
+};
+
+TEST(ProxyScriptDeciderTest, DhcpCancelledByDestructor) {
+ // This regression test would crash before
+ // http://codereview.chromium.org/7044058/
+ // Thus, we don't care much about actual results (hence no EXPECT or ASSERT
+ // macros below), just that it doesn't crash.
+ Rules rules;
+ RuleBasedProxyScriptFetcher fetcher(&rules);
+
+ scoped_ptr<AsyncFailDhcpFetcher> dhcp_fetcher(new AsyncFailDhcpFetcher());
+
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ rules.AddFailDownloadRule("http://wpad/wpad.dat");
+
+ TestCompletionCallback callback;
+
+ // Scope so ProxyScriptDecider gets destroyed early.
+ {
+ ProxyScriptDecider decider(&fetcher, dhcp_fetcher.get(), NULL);
+ decider.Start(config, base::TimeDelta(), true, callback.callback());
+ }
+
+ // Run the message loop to let the DHCP fetch complete and post the results
+ // back. Before the fix linked to above, this would try to invoke on
+ // the callback object provided by ProxyScriptDecider after it was
+ // no longer valid.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/proxy/proxy_script_fetcher.h b/chromium/net/proxy/proxy_script_fetcher.h
new file mode 100644
index 00000000000..02f3195381e
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_fetcher.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 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.
+
+// ProxyScriptFetcher is an async interface for fetching a proxy auto config
+// script. It is specific to fetching a PAC script; enforces timeout, max-size,
+// status code.
+
+#ifndef NET_PROXY_PROXY_SCRIPT_FETCHER_H_
+#define NET_PROXY_PROXY_SCRIPT_FETCHER_H_
+
+#include "base/strings/string16.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class URLRequestContext;
+
+// Interface for downloading a PAC script. Implementations can enforce
+// timeouts, maximum size constraints, content encoding, etc..
+class NET_EXPORT_PRIVATE ProxyScriptFetcher {
+ public:
+ // Destruction should cancel any outstanding requests.
+ virtual ~ProxyScriptFetcher() {}
+
+ // Downloads the given PAC URL, and invokes |callback| on completion.
+ // Returns OK on success, otherwise the error code. If the return code is
+ // ERR_IO_PENDING, then the request completes asynchronously, and |callback|
+ // will be invoked later with the final error code.
+ // After synchronous or asynchronous completion with a result code of OK,
+ // |*utf16_text| is filled with the response. On failure, the result text is
+ // an empty string, and the result code is a network error. Some special
+ // network errors that may occur are:
+ //
+ // ERR_TIMED_OUT -- the fetch took too long to complete.
+ // ERR_FILE_TOO_BIG -- the response's body was too large.
+ // ERR_PAC_STATUS_NOT_OK -- non-200 HTTP status code.
+ // ERR_NOT_IMPLEMENTED -- the response required authentication.
+ //
+ // If the request is cancelled (either using the "Cancel()" method or by
+ // deleting |this|), then no callback is invoked.
+ //
+ // Only one fetch is allowed to be outstanding at a time.
+ virtual int Fetch(const GURL& url, base::string16* utf16_text,
+ const net::CompletionCallback& callback) = 0;
+
+ // Aborts the in-progress fetch (if any).
+ virtual void Cancel() = 0;
+
+ // Returns the request context that this fetcher uses to issue downloads,
+ // or NULL.
+ virtual URLRequestContext* GetRequestContext() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SCRIPT_FETCHER_H_
diff --git a/chromium/net/proxy/proxy_script_fetcher_impl.cc b/chromium/net/proxy/proxy_script_fetcher_impl.cc
new file mode 100644
index 00000000000..2bf9e667792
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_fetcher_impl.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_script_fetcher_impl.h"
+
+#include "base/compiler_specific.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "net/base/data_url.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_context.h"
+
+// TODO(eroman):
+// - Support auth-prompts (http://crbug.com/77366)
+
+namespace net {
+
+namespace {
+
+// The maximum size (in bytes) allowed for a PAC script. Responses exceeding
+// this will fail with ERR_FILE_TOO_BIG.
+const int kDefaultMaxResponseBytes = 1048576; // 1 megabyte
+
+// The maximum duration (in milliseconds) allowed for fetching the PAC script.
+// Responses exceeding this will fail with ERR_TIMED_OUT.
+const int kDefaultMaxDurationMs = 300000; // 5 minutes
+
+// Returns true if |mime_type| is one of the known PAC mime type.
+bool IsPacMimeType(const std::string& mime_type) {
+ static const char * const kSupportedPacMimeTypes[] = {
+ "application/x-ns-proxy-autoconfig",
+ "application/x-javascript-config",
+ };
+ for (size_t i = 0; i < arraysize(kSupportedPacMimeTypes); ++i) {
+ if (LowerCaseEqualsASCII(mime_type, kSupportedPacMimeTypes[i]))
+ return true;
+ }
+ return false;
+}
+
+// Converts |bytes| (which is encoded by |charset|) to UTF16, saving the resul
+// to |*utf16|.
+// If |charset| is empty, then we don't know what it was and guess.
+void ConvertResponseToUTF16(const std::string& charset,
+ const std::string& bytes,
+ base::string16* utf16) {
+ const char* codepage;
+
+ if (charset.empty()) {
+ // Assume ISO-8859-1 if no charset was specified.
+ codepage = base::kCodepageLatin1;
+ } else {
+ // Otherwise trust the charset that was provided.
+ codepage = charset.c_str();
+ }
+
+ // We will be generous in the conversion -- if any characters lie
+ // outside of |charset| (i.e. invalid), then substitute them with
+ // U+FFFD rather than failing.
+ base::CodepageToUTF16(bytes, codepage,
+ base::OnStringConversionError::SUBSTITUTE,
+ utf16);
+}
+
+} // namespace
+
+ProxyScriptFetcherImpl::ProxyScriptFetcherImpl(
+ URLRequestContext* url_request_context)
+ : weak_factory_(this),
+ url_request_context_(url_request_context),
+ buf_(new IOBuffer(kBufSize)),
+ next_id_(0),
+ cur_request_id_(0),
+ result_code_(OK),
+ result_text_(NULL),
+ max_response_bytes_(kDefaultMaxResponseBytes),
+ max_duration_(base::TimeDelta::FromMilliseconds(kDefaultMaxDurationMs)) {
+ DCHECK(url_request_context);
+}
+
+ProxyScriptFetcherImpl::~ProxyScriptFetcherImpl() {
+ // The URLRequest's destructor will cancel the outstanding request, and
+ // ensure that the delegate (this) is not called again.
+}
+
+base::TimeDelta ProxyScriptFetcherImpl::SetTimeoutConstraint(
+ base::TimeDelta timeout) {
+ base::TimeDelta prev = max_duration_;
+ max_duration_ = timeout;
+ return prev;
+}
+
+size_t ProxyScriptFetcherImpl::SetSizeConstraint(size_t size_bytes) {
+ size_t prev = max_response_bytes_;
+ max_response_bytes_ = size_bytes;
+ return prev;
+}
+
+void ProxyScriptFetcherImpl::OnResponseCompleted(URLRequest* request) {
+ DCHECK_EQ(request, cur_request_.get());
+
+ // Use |result_code_| as the request's error if we have already set it to
+ // something specific.
+ if (result_code_ == OK && !request->status().is_success())
+ result_code_ = request->status().error();
+
+ FetchCompleted();
+}
+
+int ProxyScriptFetcherImpl::Fetch(
+ const GURL& url, base::string16* text, const CompletionCallback& callback) {
+ // It is invalid to call Fetch() while a request is already in progress.
+ DCHECK(!cur_request_.get());
+ DCHECK(!callback.is_null());
+ DCHECK(text);
+
+ // Handle base-64 encoded data-urls that contain custom PAC scripts.
+ if (url.SchemeIs("data")) {
+ std::string mime_type;
+ std::string charset;
+ std::string data;
+ if (!DataURL::Parse(url, &mime_type, &charset, &data))
+ return ERR_FAILED;
+
+ ConvertResponseToUTF16(charset, data, text);
+ return OK;
+ }
+
+ cur_request_.reset(url_request_context_->CreateRequest(url, this));
+ cur_request_->set_method("GET");
+
+ // Make sure that the PAC script is downloaded using a direct connection,
+ // to avoid circular dependencies (fetching is a part of proxy resolution).
+ // Also disable the use of the disk cache. The cache is disabled so that if
+ // the user switches networks we don't potentially use the cached response
+ // from old network when we should in fact be re-fetching on the new network.
+ // If the PAC script is hosted on an HTTPS server we bypass revocation
+ // checking in order to avoid a circular dependency when attempting to fetch
+ // the OCSP response or CRL. We could make the revocation check go direct but
+ // the proxy might be the only way to the outside world.
+ cur_request_->set_load_flags(LOAD_BYPASS_PROXY | LOAD_DISABLE_CACHE |
+ LOAD_DISABLE_CERT_REVOCATION_CHECKING);
+
+ // Save the caller's info for notification on completion.
+ callback_ = callback;
+ result_text_ = text;
+
+ bytes_read_so_far_.clear();
+
+ // Post a task to timeout this request if it takes too long.
+ cur_request_id_ = ++next_id_;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProxyScriptFetcherImpl::OnTimeout,
+ weak_factory_.GetWeakPtr(),
+ cur_request_id_),
+ max_duration_);
+
+ // Start the request.
+ cur_request_->Start();
+ return ERR_IO_PENDING;
+}
+
+void ProxyScriptFetcherImpl::Cancel() {
+ // ResetCurRequestState will free the URLRequest, which will cause
+ // cancellation.
+ ResetCurRequestState();
+}
+
+URLRequestContext* ProxyScriptFetcherImpl::GetRequestContext() const {
+ return url_request_context_;
+}
+
+void ProxyScriptFetcherImpl::OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ DCHECK_EQ(request, cur_request_.get());
+ // TODO(eroman): http://crbug.com/77366
+ LOG(WARNING) << "Auth required to fetch PAC script, aborting.";
+ result_code_ = ERR_NOT_IMPLEMENTED;
+ request->CancelAuth();
+}
+
+void ProxyScriptFetcherImpl::OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) {
+ DCHECK_EQ(request, cur_request_.get());
+ // Revocation check failures are not fatal.
+ if (IsCertStatusMinorError(ssl_info.cert_status)) {
+ request->ContinueDespiteLastError();
+ return;
+ }
+ LOG(WARNING) << "SSL certificate error when fetching PAC script, aborting.";
+ // Certificate errors are in same space as net errors.
+ result_code_ = MapCertStatusToNetError(ssl_info.cert_status);
+ request->Cancel();
+}
+
+void ProxyScriptFetcherImpl::OnResponseStarted(URLRequest* request) {
+ DCHECK_EQ(request, cur_request_.get());
+
+ if (!request->status().is_success()) {
+ OnResponseCompleted(request);
+ return;
+ }
+
+ // Require HTTP responses to have a success status code.
+ if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) {
+ // NOTE about status codes: We are like Firefox 3 in this respect.
+ // {IE 7, Safari 3, Opera 9.5} do not care about the status code.
+ if (request->GetResponseCode() != 200) {
+ VLOG(1) << "Fetched PAC script had (bad) status line: "
+ << request->response_headers()->GetStatusLine();
+ result_code_ = ERR_PAC_STATUS_NOT_OK;
+ request->Cancel();
+ return;
+ }
+
+ // NOTE about mime types: We do not enforce mime types on PAC files.
+ // This is for compatibility with {IE 7, Firefox 3, Opera 9.5}. We will
+ // however log mismatches to help with debugging.
+ std::string mime_type;
+ cur_request_->GetMimeType(&mime_type);
+ if (!IsPacMimeType(mime_type)) {
+ VLOG(1) << "Fetched PAC script does not have a proper mime type: "
+ << mime_type;
+ }
+ }
+
+ ReadBody(request);
+}
+
+void ProxyScriptFetcherImpl::OnReadCompleted(URLRequest* request,
+ int num_bytes) {
+ DCHECK_EQ(request, cur_request_.get());
+ if (ConsumeBytesRead(request, num_bytes)) {
+ // Keep reading.
+ ReadBody(request);
+ }
+}
+
+void ProxyScriptFetcherImpl::ReadBody(URLRequest* request) {
+ // Read as many bytes as are available synchronously.
+ while (true) {
+ int num_bytes;
+ if (!request->Read(buf_.get(), kBufSize, &num_bytes)) {
+ // Check whether the read failed synchronously.
+ if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ return;
+ }
+ if (!ConsumeBytesRead(request, num_bytes))
+ return;
+ }
+}
+
+bool ProxyScriptFetcherImpl::ConsumeBytesRead(URLRequest* request,
+ int num_bytes) {
+ if (num_bytes <= 0) {
+ // Error while reading, or EOF.
+ OnResponseCompleted(request);
+ return false;
+ }
+
+ // Enforce maximum size bound.
+ if (num_bytes + bytes_read_so_far_.size() >
+ static_cast<size_t>(max_response_bytes_)) {
+ result_code_ = ERR_FILE_TOO_BIG;
+ request->Cancel();
+ return false;
+ }
+
+ bytes_read_so_far_.append(buf_->data(), num_bytes);
+ return true;
+}
+
+void ProxyScriptFetcherImpl::FetchCompleted() {
+ if (result_code_ == OK) {
+ // The caller expects the response to be encoded as UTF16.
+ std::string charset;
+ cur_request_->GetCharset(&charset);
+ ConvertResponseToUTF16(charset, bytes_read_so_far_, result_text_);
+ } else {
+ // On error, the caller expects empty string for bytes.
+ result_text_->clear();
+ }
+
+ int result_code = result_code_;
+ CompletionCallback callback = callback_;
+
+ ResetCurRequestState();
+
+ callback.Run(result_code);
+}
+
+void ProxyScriptFetcherImpl::ResetCurRequestState() {
+ cur_request_.reset();
+ cur_request_id_ = 0;
+ callback_.Reset();
+ result_code_ = OK;
+ result_text_ = NULL;
+}
+
+void ProxyScriptFetcherImpl::OnTimeout(int id) {
+ // Timeout tasks may outlive the URLRequest they reference. Make sure it
+ // is still applicable.
+ if (cur_request_id_ != id)
+ return;
+
+ DCHECK(cur_request_.get());
+ result_code_ = ERR_TIMED_OUT;
+ cur_request_->Cancel();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_script_fetcher_impl.h b/chromium/net/proxy/proxy_script_fetcher_impl.h
new file mode 100644
index 00000000000..2da866901f5
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_fetcher_impl.h
@@ -0,0 +1,127 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_SCRIPT_FETCHER_IMPL_H_
+#define NET_PROXY_PROXY_SCRIPT_FETCHER_IMPL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "net/url_request/url_request.h"
+
+class GURL;
+
+namespace net {
+
+class URLRequestContext;
+
+// Implementation of ProxyScriptFetcher that downloads scripts using the
+// specified request context.
+class NET_EXPORT ProxyScriptFetcherImpl : public ProxyScriptFetcher,
+ public URLRequest::Delegate {
+ public:
+ // Creates a ProxyScriptFetcher that issues requests through
+ // |url_request_context|. |url_request_context| must remain valid for the
+ // lifetime of ProxyScriptFetcherImpl.
+ // Note that while a request is in progress, we will be holding a reference
+ // to |url_request_context|. Be careful not to create cycles between the
+ // fetcher and the context; you can break such cycles by calling Cancel().
+ explicit ProxyScriptFetcherImpl(URLRequestContext* url_request_context);
+
+ virtual ~ProxyScriptFetcherImpl();
+
+ // Used by unit-tests to modify the default limits.
+ base::TimeDelta SetTimeoutConstraint(base::TimeDelta timeout);
+ size_t SetSizeConstraint(size_t size_bytes);
+
+ void OnResponseCompleted(URLRequest* request);
+
+ // ProxyScriptFetcher methods:
+ virtual int Fetch(const GURL& url, base::string16* text,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual void Cancel() OVERRIDE;
+ virtual URLRequestContext* GetRequestContext() const OVERRIDE;
+
+ // URLRequest::Delegate methods:
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) OVERRIDE;
+ virtual void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool is_hsts_ok) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request, int num_bytes) OVERRIDE;
+
+ private:
+ enum { kBufSize = 4096 };
+
+ // Read more bytes from the response.
+ void ReadBody(URLRequest* request);
+
+ // Handles a response from Read(). Returns true if we should continue trying
+ // to read. |num_bytes| is 0 for EOF, and < 0 on errors.
+ bool ConsumeBytesRead(URLRequest* request, int num_bytes);
+
+ // Called once the request has completed to notify the caller of
+ // |response_code_| and |response_text_|.
+ void FetchCompleted();
+
+ // Clear out the state for the current request.
+ void ResetCurRequestState();
+
+ // Callback for time-out task of request with id |id|.
+ void OnTimeout(int id);
+
+ // Factory for creating the time-out task. This takes care of revoking
+ // outstanding tasks when |this| is deleted.
+ base::WeakPtrFactory<ProxyScriptFetcherImpl> weak_factory_;
+
+ // The context used for making network requests.
+ URLRequestContext* const url_request_context_;
+
+ // Buffer that URLRequest writes into.
+ scoped_refptr<IOBuffer> buf_;
+
+ // The next ID to use for |cur_request_| (monotonically increasing).
+ int next_id_;
+
+ // The current (in progress) request, or NULL.
+ scoped_ptr<URLRequest> cur_request_;
+
+ // State for current request (only valid when |cur_request_| is not NULL):
+
+ // Unique ID for the current request.
+ int cur_request_id_;
+
+ // Callback to invoke on completion of the fetch.
+ net::CompletionCallback callback_;
+
+ // Holds the error condition that was hit on the current request, or OK.
+ int result_code_;
+
+ // Holds the bytes read so far. Will not exceed |max_response_bytes|.
+ std::string bytes_read_so_far_;
+
+ // This buffer is owned by the owner of |callback|, and will be filled with
+ // UTF16 response on completion.
+ base::string16* result_text_;
+
+ // The maximum number of bytes to allow in responses.
+ size_t max_response_bytes_;
+
+ // The maximum amount of time to wait for download to complete.
+ base::TimeDelta max_duration_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyScriptFetcherImpl);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SCRIPT_FETCHER_IMPL_H_
diff --git a/chromium/net/proxy/proxy_script_fetcher_impl_unittest.cc b/chromium/net/proxy/proxy_script_fetcher_impl_unittest.cc
new file mode 100644
index 00000000000..8d42514a957
--- /dev/null
+++ b/chromium/net/proxy/proxy_script_fetcher_impl_unittest.cc
@@ -0,0 +1,481 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_script_fetcher_impl.h"
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/transport_security_state.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/file_protocol_handler.h"
+#include "net/url_request/url_request_context_storage.h"
+#include "net/url_request/url_request_file_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+// TODO(eroman):
+// - Test canceling an outstanding request.
+// - Test deleting ProxyScriptFetcher while a request is in progress.
+
+namespace {
+
+const base::FilePath::CharType kDocRoot[] =
+ FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest");
+
+struct FetchResult {
+ int code;
+ base::string16 text;
+};
+
+// A non-mock URL request which can access http:// and file:// urls.
+class RequestContext : public URLRequestContext {
+ public:
+ RequestContext() : storage_(this) {
+ ProxyConfig no_proxy;
+ storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+ storage_.set_cert_verifier(new MockCertVerifier);
+ storage_.set_transport_security_state(new TransportSecurityState);
+ storage_.set_proxy_service(ProxyService::CreateFixed(no_proxy));
+ storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+ storage_.set_http_server_properties(
+ scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));
+
+ HttpNetworkSession::Params params;
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ params.transport_security_state = transport_security_state();
+ params.proxy_service = proxy_service();
+ params.ssl_config_service = ssl_config_service();
+ params.http_server_properties = http_server_properties();
+ scoped_refptr<HttpNetworkSession> network_session(
+ new HttpNetworkSession(params));
+ storage_.set_http_transaction_factory(new HttpCache(
+ network_session.get(), HttpCache::DefaultBackend::InMemory(0)));
+ URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl();
+ job_factory->SetProtocolHandler("file", new FileProtocolHandler());
+ storage_.set_job_factory(job_factory);
+ }
+
+ virtual ~RequestContext() {
+ }
+
+ private:
+ URLRequestContextStorage storage_;
+};
+
+// Get a file:// url relative to net/data/proxy/proxy_script_fetcher_unittest.
+GURL GetTestFileUrl(const std::string& relpath) {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_script_fetcher_unittest");
+ GURL base_url = FilePathToFileURL(path);
+ return GURL(base_url.spec() + "/" + relpath);
+}
+
+// Really simple NetworkDelegate so we can allow local file access on ChromeOS
+// without introducing layering violations. Also causes a test failure if a
+// request is seen that doesn't set a load flag to bypass revocation checking.
+
+class BasicNetworkDelegate : public NetworkDelegate {
+ public:
+ BasicNetworkDelegate() {}
+ virtual ~BasicNetworkDelegate() {}
+
+ private:
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE {
+ EXPECT_TRUE(request->load_flags() & LOAD_DISABLE_CERT_REVOCATION_CHECKING);
+ return OK;
+ }
+
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE {}
+
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers)
+ OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE {}
+
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE {}
+
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE {}
+
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE {}
+
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE {}
+
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) OVERRIDE {}
+
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE {
+ return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanAccessFile(const net::URLRequest& request,
+ const base::FilePath& path) const OVERRIDE {
+ return true;
+ }
+ virtual bool OnCanThrottleRequest(const URLRequest& request) const OVERRIDE {
+ return false;
+ }
+
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnRequestWaitStateChange(const net::URLRequest& request,
+ RequestWaitState state) OVERRIDE {
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BasicNetworkDelegate);
+};
+
+} // namespace
+
+class ProxyScriptFetcherImplTest : public PlatformTest {
+ public:
+ ProxyScriptFetcherImplTest()
+ : test_server_(SpawnedTestServer::TYPE_HTTP,
+ net::SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot)) {
+ context_.set_network_delegate(&network_delegate_);
+ }
+
+ protected:
+ SpawnedTestServer test_server_;
+ BasicNetworkDelegate network_delegate_;
+ RequestContext context_;
+};
+
+TEST_F(ProxyScriptFetcherImplTest, FileUrl) {
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ { // Fetch a non-existent file.
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(GetTestFileUrl("does-not-exist"),
+ &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(ERR_FILE_NOT_FOUND, callback.WaitForResult());
+ EXPECT_TRUE(text.empty());
+ }
+ { // Fetch a file that exists.
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(GetTestFileUrl("pac.txt"),
+ &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text);
+ }
+}
+
+// Note that all mime types are allowed for PAC file, to be consistent
+// with other browsers.
+TEST_F(ProxyScriptFetcherImplTest, HttpMimeType) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ { // Fetch a PAC with mime type "text/plain"
+ GURL url(test_server_.GetURL("files/pac.txt"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.txt-\n"), text);
+ }
+ { // Fetch a PAC with mime type "text/html"
+ GURL url(test_server_.GetURL("files/pac.html"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.html-\n"), text);
+ }
+ { // Fetch a PAC with mime type "application/x-ns-proxy-autoconfig"
+ GURL url(test_server_.GetURL("files/pac.nsproxy"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
+ }
+}
+
+TEST_F(ProxyScriptFetcherImplTest, HttpStatusCode) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ { // Fetch a PAC which gives a 500 -- FAIL
+ GURL url(test_server_.GetURL("files/500.pac"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult());
+ EXPECT_TRUE(text.empty());
+ }
+ { // Fetch a PAC which gives a 404 -- FAIL
+ GURL url(test_server_.GetURL("files/404.pac"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(ERR_PAC_STATUS_NOT_OK, callback.WaitForResult());
+ EXPECT_TRUE(text.empty());
+ }
+}
+
+TEST_F(ProxyScriptFetcherImplTest, ContentDisposition) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ // Fetch PAC scripts via HTTP with a Content-Disposition header -- should
+ // have no effect.
+ GURL url(test_server_.GetURL("files/downloadable.pac"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-downloadable.pac-\n"), text);
+}
+
+// Verifies that PAC scripts are not being cached.
+TEST_F(ProxyScriptFetcherImplTest, NoCache) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ // Fetch a PAC script whose HTTP headers make it cacheable for 1 hour.
+ GURL url(test_server_.GetURL("files/cacheable_1hr.pac"));
+ {
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-cacheable_1hr.pac-\n"), text);
+ }
+
+ // Kill the HTTP server.
+ ASSERT_TRUE(test_server_.Stop());
+
+ // Try to fetch the file again. Since the server is not running anymore, the
+ // call should fail, thus indicating that the file was not fetched from the
+ // local cache.
+ {
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+
+ // Expect any error. The exact error varies by platform.
+ EXPECT_NE(OK, callback.WaitForResult());
+ }
+}
+
+TEST_F(ProxyScriptFetcherImplTest, TooLarge) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ // Set the maximum response size to 50 bytes.
+ int prev_size = pac_fetcher.SetSizeConstraint(50);
+
+ // These two URLs are the same file, but are http:// vs file://
+ GURL urls[] = {
+ test_server_.GetURL("files/large-pac.nsproxy"),
+ GetTestFileUrl("large-pac.nsproxy")
+ };
+
+ // Try fetching URLs that are 101 bytes large. We should abort the request
+ // after 50 bytes have been read, and fail with a too large error.
+ for (size_t i = 0; i < arraysize(urls); ++i) {
+ const GURL& url = urls[i];
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(ERR_FILE_TOO_BIG, callback.WaitForResult());
+ EXPECT_TRUE(text.empty());
+ }
+
+ // Restore the original size bound.
+ pac_fetcher.SetSizeConstraint(prev_size);
+
+ { // Make sure we can still fetch regular URLs.
+ GURL url(test_server_.GetURL("files/pac.nsproxy"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
+ }
+}
+
+TEST_F(ProxyScriptFetcherImplTest, Hang) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ // Set the timeout period to 0.5 seconds.
+ base::TimeDelta prev_timeout = pac_fetcher.SetTimeoutConstraint(
+ base::TimeDelta::FromMilliseconds(500));
+
+ // Try fetching a URL which takes 1.2 seconds. We should abort the request
+ // after 500 ms, and fail with a timeout error.
+ {
+ GURL url(test_server_.GetURL("slow/proxy.pac?1.2"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(ERR_TIMED_OUT, callback.WaitForResult());
+ EXPECT_TRUE(text.empty());
+ }
+
+ // Restore the original timeout period.
+ pac_fetcher.SetTimeoutConstraint(prev_timeout);
+
+ { // Make sure we can still fetch regular URLs.
+ GURL url(test_server_.GetURL("files/pac.nsproxy"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("-pac.nsproxy-\n"), text);
+ }
+}
+
+// The ProxyScriptFetcher should decode any content-codings
+// (like gzip, bzip, etc.), and apply any charset conversions to yield
+// UTF8.
+TEST_F(ProxyScriptFetcherImplTest, Encodings) {
+ ASSERT_TRUE(test_server_.Start());
+
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ // Test a response that is gzip-encoded -- should get inflated.
+ {
+ GURL url(test_server_.GetURL("files/gzipped_pac"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("This data was gzipped.\n"), text);
+ }
+
+ // Test a response that was served as UTF-16 (BE). It should
+ // be converted to UTF8.
+ {
+ GURL url(test_server_.GetURL("files/utf16be_pac"));
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, result);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ASCIIToUTF16("This was encoded as UTF-16BE.\n"), text);
+ }
+}
+
+TEST_F(ProxyScriptFetcherImplTest, DataURLs) {
+ ProxyScriptFetcherImpl pac_fetcher(&context_);
+
+ const char kEncodedUrl[] =
+ "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R"
+ "m9yVVJMKHVybCwgaG9zdCkgewogIGlmIChob3N0ID09ICdmb29iYXIuY29tJykKICAgIHJl"
+ "dHVybiAnUFJPWFkgYmxhY2tob2xlOjgwJzsKICByZXR1cm4gJ0RJUkVDVCc7Cn0=";
+ const char kPacScript[] =
+ "function FindProxyForURL(url, host) {\n"
+ " if (host == 'foobar.com')\n"
+ " return 'PROXY blackhole:80';\n"
+ " return 'DIRECT';\n"
+ "}";
+
+ // Test fetching a "data:"-url containing a base64 encoded PAC script.
+ {
+ GURL url(kEncodedUrl);
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(OK, result);
+ EXPECT_EQ(ASCIIToUTF16(kPacScript), text);
+ }
+
+ const char kEncodedUrlBroken[] =
+ "data:application/x-ns-proxy-autoconfig;base64,ZnVuY3Rpb24gRmluZFByb3h5R";
+
+ // Test a broken "data:"-url containing a base64 encoded PAC script.
+ {
+ GURL url(kEncodedUrlBroken);
+ base::string16 text;
+ TestCompletionCallback callback;
+ int result = pac_fetcher.Fetch(url, &text, callback.callback());
+ EXPECT_EQ(ERR_FAILED, result);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_server.cc b/chromium/net/proxy/proxy_server.cc
new file mode 100644
index 00000000000..6875b4a329b
--- /dev/null
+++ b/chromium/net/proxy/proxy_server.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2010 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/proxy/proxy_server.h"
+
+#include <algorithm>
+
+#include "base/strings/string_util.h"
+#include "net/base/net_util.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+namespace {
+
+// Parses the proxy type from a PAC string, to a ProxyServer::Scheme.
+// This mapping is case-insensitive. If no type could be matched
+// returns SCHEME_INVALID.
+ProxyServer::Scheme GetSchemeFromPacTypeInternal(
+ std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ if (LowerCaseEqualsASCII(begin, end, "proxy"))
+ return ProxyServer::SCHEME_HTTP;
+ if (LowerCaseEqualsASCII(begin, end, "socks")) {
+ // Default to v4 for compatibility. This is because the SOCKS4 vs SOCKS5
+ // notation didn't originally exist, so if a client returns SOCKS they
+ // really meant SOCKS4.
+ return ProxyServer::SCHEME_SOCKS4;
+ }
+ if (LowerCaseEqualsASCII(begin, end, "socks4"))
+ return ProxyServer::SCHEME_SOCKS4;
+ if (LowerCaseEqualsASCII(begin, end, "socks5"))
+ return ProxyServer::SCHEME_SOCKS5;
+ if (LowerCaseEqualsASCII(begin, end, "direct"))
+ return ProxyServer::SCHEME_DIRECT;
+ if (LowerCaseEqualsASCII(begin, end, "https"))
+ return ProxyServer::SCHEME_HTTPS;
+
+ return ProxyServer::SCHEME_INVALID;
+}
+
+// Parses the proxy scheme from a URL-like representation, to a
+// ProxyServer::Scheme. This corresponds with the values used in
+// ProxyServer::ToURI(). If no type could be matched, returns SCHEME_INVALID.
+ProxyServer::Scheme GetSchemeFromURIInternal(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ if (LowerCaseEqualsASCII(begin, end, "http"))
+ return ProxyServer::SCHEME_HTTP;
+ if (LowerCaseEqualsASCII(begin, end, "socks4"))
+ return ProxyServer::SCHEME_SOCKS4;
+ if (LowerCaseEqualsASCII(begin, end, "socks"))
+ return ProxyServer::SCHEME_SOCKS5;
+ if (LowerCaseEqualsASCII(begin, end, "socks5"))
+ return ProxyServer::SCHEME_SOCKS5;
+ if (LowerCaseEqualsASCII(begin, end, "direct"))
+ return ProxyServer::SCHEME_DIRECT;
+ if (LowerCaseEqualsASCII(begin, end, "https"))
+ return ProxyServer::SCHEME_HTTPS;
+ return ProxyServer::SCHEME_INVALID;
+}
+
+std::string HostNoBrackets(const std::string& host) {
+ // Remove brackets from an RFC 2732-style IPv6 literal address.
+ const std::string::size_type len = host.size();
+ if (len >= 2 && host[0] == '[' && host[len - 1] == ']')
+ return host.substr(1, len - 2);
+ return host;
+}
+
+} // namespace
+
+ProxyServer::ProxyServer(Scheme scheme, const HostPortPair& host_port_pair)
+ : scheme_(scheme), host_port_pair_(host_port_pair) {
+ if (scheme_ == SCHEME_DIRECT || scheme_ == SCHEME_INVALID) {
+ // |host_port_pair| isn't relevant for these special schemes, so none should
+ // have been specified. It is important for this to be consistent since we
+ // do raw field comparisons in the equality and comparison functions.
+ DCHECK(host_port_pair.Equals(HostPortPair()));
+ host_port_pair_ = HostPortPair();
+ }
+}
+
+const HostPortPair& ProxyServer::host_port_pair() const {
+ // Doesn't make sense to call this if the URI scheme doesn't
+ // have concept of a host.
+ DCHECK(is_valid() && !is_direct());
+ return host_port_pair_;
+}
+
+// static
+ProxyServer ProxyServer::FromURI(const std::string& uri,
+ Scheme default_scheme) {
+ return FromURI(uri.begin(), uri.end(), default_scheme);
+}
+
+// static
+ProxyServer ProxyServer::FromURI(std::string::const_iterator begin,
+ std::string::const_iterator end,
+ Scheme default_scheme) {
+ // We will default to |default_scheme| if no scheme specifier was given.
+ Scheme scheme = default_scheme;
+
+ // Trim the leading/trailing whitespace.
+ HttpUtil::TrimLWS(&begin, &end);
+
+ // Check for [<scheme> "://"]
+ std::string::const_iterator colon = std::find(begin, end, ':');
+ if (colon != end &&
+ (end - colon) >= 3 &&
+ *(colon + 1) == '/' &&
+ *(colon + 2) == '/') {
+ scheme = GetSchemeFromURIInternal(begin, colon);
+ begin = colon + 3; // Skip past the "://"
+ }
+
+ // Now parse the <host>[":"<port>].
+ return FromSchemeHostAndPort(scheme, begin, end);
+}
+
+std::string ProxyServer::ToURI() const {
+ switch (scheme_) {
+ case SCHEME_DIRECT:
+ return "direct://";
+ case SCHEME_HTTP:
+ // Leave off "http://" since it is our default scheme.
+ return host_port_pair().ToString();
+ case SCHEME_SOCKS4:
+ return std::string("socks4://") + host_port_pair().ToString();
+ case SCHEME_SOCKS5:
+ return std::string("socks5://") + host_port_pair().ToString();
+ case SCHEME_HTTPS:
+ return std::string("https://") + host_port_pair().ToString();
+ default:
+ // Got called with an invalid scheme.
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+ProxyServer ProxyServer::FromPacString(const std::string& pac_string) {
+ return FromPacString(pac_string.begin(), pac_string.end());
+}
+
+// static
+ProxyServer ProxyServer::FromPacString(std::string::const_iterator begin,
+ std::string::const_iterator end) {
+ // Trim the leading/trailing whitespace.
+ HttpUtil::TrimLWS(&begin, &end);
+
+ // Input should match:
+ // "DIRECT" | ( <type> 1*(LWS) <host-and-port> )
+
+ // Start by finding the first space (if any).
+ std::string::const_iterator space;
+ for (space = begin; space != end; ++space) {
+ if (HttpUtil::IsLWS(*space)) {
+ break;
+ }
+ }
+
+ // Everything to the left of the space is the scheme.
+ Scheme scheme = GetSchemeFromPacTypeInternal(begin, space);
+
+ // And everything to the right of the space is the
+ // <host>[":" <port>].
+ return FromSchemeHostAndPort(scheme, space, end);
+}
+
+std::string ProxyServer::ToPacString() const {
+ switch (scheme_) {
+ case SCHEME_DIRECT:
+ return "DIRECT";
+ case SCHEME_HTTP:
+ return std::string("PROXY ") + host_port_pair().ToString();
+ case SCHEME_SOCKS4:
+ // For compatibility send SOCKS instead of SOCKS4.
+ return std::string("SOCKS ") + host_port_pair().ToString();
+ case SCHEME_SOCKS5:
+ return std::string("SOCKS5 ") + host_port_pair().ToString();
+ case SCHEME_HTTPS:
+ return std::string("HTTPS ") + host_port_pair().ToString();
+ default:
+ // Got called with an invalid scheme.
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+// static
+int ProxyServer::GetDefaultPortForScheme(Scheme scheme) {
+ switch (scheme) {
+ case SCHEME_HTTP:
+ return 80;
+ case SCHEME_SOCKS4:
+ case SCHEME_SOCKS5:
+ return 1080;
+ case SCHEME_HTTPS:
+ return 443;
+ default:
+ return -1;
+ }
+}
+
+// static
+ProxyServer::Scheme ProxyServer::GetSchemeFromURI(const std::string& scheme) {
+ return GetSchemeFromURIInternal(scheme.begin(), scheme.end());
+}
+
+// static
+ProxyServer ProxyServer::FromSchemeHostAndPort(
+ Scheme scheme,
+ std::string::const_iterator begin,
+ std::string::const_iterator end) {
+
+ // Trim leading/trailing space.
+ HttpUtil::TrimLWS(&begin, &end);
+
+ if (scheme == SCHEME_DIRECT && begin != end)
+ return ProxyServer(); // Invalid -- DIRECT cannot have a host/port.
+
+ HostPortPair host_port_pair;
+
+ if (scheme != SCHEME_INVALID && scheme != SCHEME_DIRECT) {
+ std::string host;
+ int port = -1;
+ // If the scheme has a host/port, parse it.
+ bool ok = net::ParseHostAndPort(begin, end, &host, &port);
+ if (!ok)
+ return ProxyServer(); // Invalid -- failed parsing <host>[":"<port>]
+
+ // Choose a default port number if none was given.
+ if (port == -1)
+ port = GetDefaultPortForScheme(scheme);
+
+ host_port_pair = HostPortPair(HostNoBrackets(host), port);
+ }
+
+ return ProxyServer(scheme, host_port_pair);
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_server.h b/chromium/net/proxy/proxy_server.h
new file mode 100644
index 00000000000..00cc9fddaf6
--- /dev/null
+++ b/chromium/net/proxy/proxy_server.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 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_PROXY_PROXY_SERVER_H_
+#define NET_PROXY_PROXY_SERVER_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#include <string>
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// ProxyServer encodes the {type, host, port} of a proxy server.
+// ProxyServer is immutable.
+class NET_EXPORT ProxyServer {
+ public:
+ // The type of proxy. These are defined as bit flags so they can be ORed
+ // together to pass as the |scheme_bit_field| argument to
+ // ProxyService::RemoveProxiesWithoutScheme().
+ enum Scheme {
+ SCHEME_INVALID = 1 << 0,
+ SCHEME_DIRECT = 1 << 1,
+ SCHEME_HTTP = 1 << 2,
+ SCHEME_SOCKS4 = 1 << 3,
+ SCHEME_SOCKS5 = 1 << 4,
+ SCHEME_HTTPS = 1 << 5,
+ };
+
+ // Default copy-constructor and assignment operator are OK!
+
+ // Constructs an invalid ProxyServer.
+ ProxyServer() : scheme_(SCHEME_INVALID) {}
+
+ ProxyServer(Scheme scheme, const HostPortPair& host_port_pair);
+
+ bool is_valid() const { return scheme_ != SCHEME_INVALID; }
+
+ // Gets the proxy's scheme (i.e. SOCKS4, SOCKS5, HTTP)
+ Scheme scheme() const { return scheme_; }
+
+ // Returns true if this ProxyServer is actually just a DIRECT connection.
+ bool is_direct() const { return scheme_ == SCHEME_DIRECT; }
+
+ // Returns true if this ProxyServer is an HTTP proxy.
+ bool is_http() const { return scheme_ == SCHEME_HTTP; }
+
+ // Returns true if this ProxyServer is an HTTPS proxy.
+ bool is_https() const { return scheme_ == SCHEME_HTTPS; }
+
+ // Returns true if this ProxyServer is a SOCKS proxy.
+ bool is_socks() const {
+ return scheme_ == SCHEME_SOCKS4 || scheme_ == SCHEME_SOCKS5;
+ }
+
+ const HostPortPair& host_port_pair() const;
+
+ // Parses from an input with format:
+ // [<scheme>"://"]<server>[":"<port>]
+ //
+ // Both <scheme> and <port> are optional. If <scheme> is omitted, it will be
+ // assumed as |default_scheme|. If <port> is omitted, it will be assumed as
+ // the default port for the chosen scheme (80 for "http", 1080 for "socks").
+ //
+ // If parsing fails the instance will be set to invalid.
+ //
+ // Examples (for |default_scheme| = SCHEME_HTTP ):
+ // "foopy" {scheme=HTTP, host="foopy", port=80}
+ // "socks://foopy" {scheme=SOCKS5, host="foopy", port=1080}
+ // "socks4://foopy" {scheme=SOCKS4, host="foopy", port=1080}
+ // "socks5://foopy" {scheme=SOCKS5, host="foopy", port=1080}
+ // "http://foopy:17" {scheme=HTTP, host="foopy", port=17}
+ // "https://foopy:17" {scheme=HTTPS, host="foopy", port=17}
+ // "direct://" {scheme=DIRECT}
+ // "foopy:X" INVALID -- bad port.
+ static ProxyServer FromURI(const std::string& uri, Scheme default_scheme);
+ static ProxyServer FromURI(std::string::const_iterator uri_begin,
+ std::string::const_iterator uri_end,
+ Scheme default_scheme);
+
+ // Formats as a URI string. This does the reverse of FromURI.
+ std::string ToURI() const;
+
+ // Parses from a PAC string result.
+ //
+ // If <port> is omitted, it will be assumed as the default port for the
+ // chosen scheme (80 for "http", 1080 for "socks").
+ //
+ // If parsing fails the instance will be set to invalid.
+ //
+ // Examples:
+ // "PROXY foopy:19" {scheme=HTTP, host="foopy", port=19}
+ // "DIRECT" {scheme=DIRECT}
+ // "SOCKS5 foopy" {scheme=SOCKS5, host="foopy", port=1080}
+ // "HTTPS foopy:123" {scheme=HTTPS, host="foopy", port=123}
+ // "BLAH xxx:xx" INVALID
+ static ProxyServer FromPacString(const std::string& pac_string);
+ static ProxyServer FromPacString(std::string::const_iterator pac_string_begin,
+ std::string::const_iterator pac_string_end);
+
+ // Returns a ProxyServer representing DIRECT connections.
+ static ProxyServer Direct() {
+ return ProxyServer(SCHEME_DIRECT, HostPortPair());
+ }
+
+#if defined(OS_MACOSX)
+ // Utility function to pull out a host/port pair from a dictionary and return
+ // it as a ProxyServer object. Pass in a dictionary that has a value for the
+ // host key and optionally a value for the port key. In the error condition
+ // where the host value is especially malformed, returns an invalid
+ // ProxyServer.
+ static ProxyServer FromDictionary(Scheme scheme,
+ CFDictionaryRef dict,
+ CFStringRef host_key,
+ CFStringRef port_key);
+#endif
+
+ // Formats as a PAC result entry. This does the reverse of FromPacString().
+ std::string ToPacString() const;
+
+ // Returns the default port number for a proxy server with the specified
+ // scheme. Returns -1 if unknown.
+ static int GetDefaultPortForScheme(Scheme scheme);
+
+ // Parses the proxy scheme from a URL-like representation, to a
+ // ProxyServer::Scheme. This corresponds with the values used in
+ // ProxyServer::ToURI(). If no type could be matched, returns SCHEME_INVALID.
+ // |scheme| can be one of http, https, socks, socks4, socks5, direct.
+ static Scheme GetSchemeFromURI(const std::string& scheme);
+
+ bool operator==(const ProxyServer& other) const {
+ return scheme_ == other.scheme_ &&
+ host_port_pair_.Equals(other.host_port_pair_);
+ }
+
+ // Comparator function so this can be placed in a std::map.
+ bool operator<(const ProxyServer& other) const {
+ if (scheme_ != other.scheme_)
+ return scheme_ < other.scheme_;
+ return host_port_pair_ < other.host_port_pair_;
+ }
+
+ private:
+ // Creates a ProxyServer given a scheme, and host/port string. If parsing the
+ // host/port string fails, the returned instance will be invalid.
+ static ProxyServer FromSchemeHostAndPort(
+ Scheme scheme,
+ std::string::const_iterator host_and_port_begin,
+ std::string::const_iterator host_and_port_end);
+
+ Scheme scheme_;
+ HostPortPair host_port_pair_;
+};
+
+typedef std::pair<HostPortPair, ProxyServer> HostPortProxyPair;
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SERVER_H_
diff --git a/chromium/net/proxy/proxy_server_mac.cc b/chromium/net/proxy/proxy_server_mac.cc
new file mode 100644
index 00000000000..fe3faa93e52
--- /dev/null
+++ b/chromium/net/proxy/proxy_server_mac.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 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/proxy/proxy_server.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+
+namespace net {
+
+// static
+ProxyServer ProxyServer::FromDictionary(Scheme scheme,
+ CFDictionaryRef dict,
+ CFStringRef host_key,
+ CFStringRef port_key) {
+ if (scheme == SCHEME_INVALID || scheme == SCHEME_DIRECT) {
+ // No hostname port to extract; we are done.
+ return ProxyServer(scheme, HostPortPair());
+ }
+
+ CFStringRef host_ref =
+ base::mac::GetValueFromDictionary<CFStringRef>(dict, host_key);
+ if (!host_ref) {
+ LOG(WARNING) << "Could not find expected key "
+ << base::SysCFStringRefToUTF8(host_key)
+ << " in the proxy dictionary";
+ return ProxyServer(); // Invalid.
+ }
+ std::string host = base::SysCFStringRefToUTF8(host_ref);
+
+ CFNumberRef port_ref =
+ base::mac::GetValueFromDictionary<CFNumberRef>(dict, port_key);
+ int port;
+ if (port_ref) {
+ CFNumberGetValue(port_ref, kCFNumberIntType, &port);
+ } else {
+ port = GetDefaultPortForScheme(scheme);
+ }
+
+ return ProxyServer(scheme, HostPortPair(host, port));
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_server_unittest.cc b/chromium/net/proxy/proxy_server_unittest.cc
new file mode 100644
index 00000000000..7646467538b
--- /dev/null
+++ b/chromium/net/proxy/proxy_server_unittest.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 2010 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 "base/basictypes.h"
+#include "net/proxy/proxy_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Test the creation of ProxyServer using ProxyServer::FromURI, which parses
+// inputs of the form [<scheme>"://"]<host>[":"<port>]. Verify that each part
+// was labelled correctly, and the accessors all give the right data.
+TEST(ProxyServerTest, FromURI) {
+ const struct {
+ const char* input_uri;
+ const char* expected_uri;
+ net::ProxyServer::Scheme expected_scheme;
+ const char* expected_host;
+ int expected_port;
+ const char* expected_pac_string;
+ } tests[] = {
+ // HTTP proxy URIs:
+ {
+ "foopy:10", // No scheme.
+ "foopy:10",
+ net::ProxyServer::SCHEME_HTTP,
+ "foopy",
+ 10,
+ "PROXY foopy:10"
+ },
+ {
+ "http://foopy", // No port.
+ "foopy:80",
+ net::ProxyServer::SCHEME_HTTP,
+ "foopy",
+ 80,
+ "PROXY foopy:80"
+ },
+ {
+ "http://foopy:10",
+ "foopy:10",
+ net::ProxyServer::SCHEME_HTTP,
+ "foopy",
+ 10,
+ "PROXY foopy:10"
+ },
+
+ // IPv6 HTTP proxy URIs:
+ {
+ "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10", // No scheme.
+ "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10",
+ net::ProxyServer::SCHEME_HTTP,
+ "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210",
+ 10,
+ "PROXY [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:10"
+ },
+ {
+ "http://[3ffe:2a00:100:7031::1]", // No port.
+ "[3ffe:2a00:100:7031::1]:80",
+ net::ProxyServer::SCHEME_HTTP,
+ "3ffe:2a00:100:7031::1",
+ 80,
+ "PROXY [3ffe:2a00:100:7031::1]:80"
+ },
+ {
+ "http://[::192.9.5.5]",
+ "[::192.9.5.5]:80",
+ net::ProxyServer::SCHEME_HTTP,
+ "::192.9.5.5",
+ 80,
+ "PROXY [::192.9.5.5]:80"
+ },
+ {
+ "http://[::FFFF:129.144.52.38]:80",
+ "[::FFFF:129.144.52.38]:80",
+ net::ProxyServer::SCHEME_HTTP,
+ "::FFFF:129.144.52.38",
+ 80,
+ "PROXY [::FFFF:129.144.52.38]:80"
+ },
+
+ // SOCKS4 proxy URIs:
+ {
+ "socks4://foopy", // No port.
+ "socks4://foopy:1080",
+ net::ProxyServer::SCHEME_SOCKS4,
+ "foopy",
+ 1080,
+ "SOCKS foopy:1080"
+ },
+ {
+ "socks4://foopy:10",
+ "socks4://foopy:10",
+ net::ProxyServer::SCHEME_SOCKS4,
+ "foopy",
+ 10,
+ "SOCKS foopy:10"
+ },
+
+ // SOCKS5 proxy URIs
+ {
+ "socks5://foopy", // No port.
+ "socks5://foopy:1080",
+ net::ProxyServer::SCHEME_SOCKS5,
+ "foopy",
+ 1080,
+ "SOCKS5 foopy:1080"
+ },
+ {
+ "socks5://foopy:10",
+ "socks5://foopy:10",
+ net::ProxyServer::SCHEME_SOCKS5,
+ "foopy",
+ 10,
+ "SOCKS5 foopy:10"
+ },
+
+ // SOCKS proxy URIs (should default to SOCKS5)
+ {
+ "socks://foopy", // No port.
+ "socks5://foopy:1080",
+ net::ProxyServer::SCHEME_SOCKS5,
+ "foopy",
+ 1080,
+ "SOCKS5 foopy:1080"
+ },
+ {
+ "socks://foopy:10",
+ "socks5://foopy:10",
+ net::ProxyServer::SCHEME_SOCKS5,
+ "foopy",
+ 10,
+ "SOCKS5 foopy:10"
+ },
+
+ // HTTPS proxy URIs:
+ {
+ "https://foopy", // No port
+ "https://foopy:443",
+ net::ProxyServer::SCHEME_HTTPS,
+ "foopy",
+ 443,
+ "HTTPS foopy:443"
+ },
+ {
+ "https://foopy:10", // Non-standard port
+ "https://foopy:10",
+ net::ProxyServer::SCHEME_HTTPS,
+ "foopy",
+ 10,
+ "HTTPS foopy:10"
+ },
+ {
+ "https://1.2.3.4:10", // IP Address
+ "https://1.2.3.4:10",
+ net::ProxyServer::SCHEME_HTTPS,
+ "1.2.3.4",
+ 10,
+ "HTTPS 1.2.3.4:10"
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::ProxyServer uri =
+ net::ProxyServer::FromURI(tests[i].input_uri,
+ net::ProxyServer::SCHEME_HTTP);
+ EXPECT_TRUE(uri.is_valid());
+ EXPECT_FALSE(uri.is_direct());
+ EXPECT_EQ(tests[i].expected_uri, uri.ToURI());
+ EXPECT_EQ(tests[i].expected_scheme, uri.scheme());
+ EXPECT_EQ(tests[i].expected_host, uri.host_port_pair().host());
+ EXPECT_EQ(tests[i].expected_port, uri.host_port_pair().port());
+ EXPECT_EQ(tests[i].expected_pac_string, uri.ToPacString());
+ }
+}
+
+TEST(ProxyServerTest, DefaultConstructor) {
+ net::ProxyServer proxy_server;
+ EXPECT_FALSE(proxy_server.is_valid());
+}
+
+// Test parsing of the special URI form "direct://". Analagous to the "DIRECT"
+// entry in a PAC result.
+TEST(ProxyServerTest, Direct) {
+ net::ProxyServer uri =
+ net::ProxyServer::FromURI("direct://", net::ProxyServer::SCHEME_HTTP);
+ EXPECT_TRUE(uri.is_valid());
+ EXPECT_TRUE(uri.is_direct());
+ EXPECT_EQ("direct://", uri.ToURI());
+ EXPECT_EQ("DIRECT", uri.ToPacString());
+}
+
+// Test parsing some invalid inputs.
+TEST(ProxyServerTest, Invalid) {
+ const char* tests[] = {
+ "",
+ " ",
+ "dddf:", // not a valid port
+ "dddd:d", // not a valid port
+ "http://", // not a valid host/port.
+ "direct://xyz", // direct is not allowed a host/port.
+ "http:/", // ambiguous, but will fail because of bad port.
+ "http:", // ambiguous, but will fail because of bad port.
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::ProxyServer uri =
+ net::ProxyServer::FromURI(tests[i], net::ProxyServer::SCHEME_HTTP);
+ EXPECT_FALSE(uri.is_valid());
+ EXPECT_FALSE(uri.is_direct());
+ EXPECT_FALSE(uri.is_http());
+ EXPECT_FALSE(uri.is_socks());
+ }
+}
+
+// Test that LWS (SP | HT) is disregarded from the ends.
+TEST(ProxyServerTest, Whitespace) {
+ const char* tests[] = {
+ " foopy:80",
+ "foopy:80 \t",
+ " \tfoopy:80 ",
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::ProxyServer uri =
+ net::ProxyServer::FromURI(tests[i], net::ProxyServer::SCHEME_HTTP);
+ EXPECT_EQ("foopy:80", uri.ToURI());
+ }
+}
+
+// Test parsing a ProxyServer from a PAC representation.
+TEST(ProxyServerTest, FromPACString) {
+ const struct {
+ const char* input_pac;
+ const char* expected_uri;
+ } tests[] = {
+ {
+ "PROXY foopy:10",
+ "foopy:10",
+ },
+ {
+ " PROXY foopy:10 ",
+ "foopy:10",
+ },
+ {
+ "pRoXy foopy:10",
+ "foopy:10",
+ },
+ {
+ "PROXY foopy", // No port.
+ "foopy:80",
+ },
+ {
+ "socks foopy",
+ "socks4://foopy:1080",
+ },
+ {
+ "socks4 foopy",
+ "socks4://foopy:1080",
+ },
+ {
+ "socks5 foopy",
+ "socks5://foopy:1080",
+ },
+ {
+ "socks5 foopy:11",
+ "socks5://foopy:11",
+ },
+ {
+ " direct ",
+ "direct://",
+ },
+ {
+ "https foopy",
+ "https://foopy:443",
+ },
+ {
+ "https foopy:10",
+ "https://foopy:10",
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::ProxyServer uri = net::ProxyServer::FromPacString(tests[i].input_pac);
+ EXPECT_TRUE(uri.is_valid());
+ EXPECT_EQ(tests[i].expected_uri, uri.ToURI());
+ }
+}
+
+// Test parsing a ProxyServer from an invalid PAC representation.
+TEST(ProxyServerTest, FromPACStringInvalid) {
+ const char* tests[] = {
+ "PROXY", // missing host/port.
+ "HTTPS", // missing host/port.
+ "SOCKS", // missing host/port.
+ "DIRECT foopy:10", // direct cannot have host/port.
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ net::ProxyServer uri = net::ProxyServer::FromPacString(tests[i]);
+ EXPECT_FALSE(uri.is_valid());
+ }
+}
+
+TEST(ProxyServerTest, ComparatorAndEquality) {
+ struct {
+ // Inputs.
+ const char* server1;
+ const char* server2;
+
+ // Expectation.
+ // -1 means server1 is less than server2
+ // 0 means server1 equals server2
+ // 1 means server1 is greater than server2
+ int expected_comparison;
+ } tests[] = {
+ { // Equal.
+ "foo:11",
+ "http://foo:11",
+ 0
+ },
+ { // Port is different.
+ "foo:333",
+ "foo:444",
+ -1
+ },
+ { // Host is different.
+ "foo:33",
+ "bar:33",
+ 1
+ },
+ { // Scheme is different.
+ "socks4://foo:33",
+ "http://foo:33",
+ 1
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ // Parse the expected inputs to ProxyServer instances.
+ const net::ProxyServer server1 =
+ net::ProxyServer::FromURI(
+ tests[i].server1, net::ProxyServer::SCHEME_HTTP);
+
+ const net::ProxyServer server2 =
+ net::ProxyServer::FromURI(
+ tests[i].server2, net::ProxyServer::SCHEME_HTTP);
+
+ switch (tests[i].expected_comparison) {
+ case -1:
+ EXPECT_TRUE(server1 < server2);
+ EXPECT_FALSE(server2 < server1);
+ EXPECT_FALSE(server2 == server1);
+ break;
+ case 0:
+ EXPECT_FALSE(server1 < server2);
+ EXPECT_FALSE(server2 < server1);
+ EXPECT_TRUE(server2 == server1);
+ break;
+ case 1:
+ EXPECT_FALSE(server1 < server2);
+ EXPECT_TRUE(server2 < server1);
+ EXPECT_FALSE(server2 == server1);
+ break;
+ default:
+ FAIL() << "Invalid expectation. Can be only -1, 0, 1";
+ }
+ }
+}
diff --git a/chromium/net/proxy/proxy_service.cc b/chromium/net/proxy/proxy_service.cc
new file mode 100644
index 00000000000..33bbd490842
--- /dev/null
+++ b/chromium/net/proxy/proxy_service.cc
@@ -0,0 +1,1576 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_service.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+#include "net/proxy/multi_threaded_proxy_resolver.h"
+#include "net/proxy/network_delegate_error_observer.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_script_decider.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "net/proxy/proxy_config_service_win.h"
+#include "net/proxy/proxy_resolver_winhttp.h"
+#elif defined(OS_IOS)
+#include "net/proxy/proxy_config_service_ios.h"
+#include "net/proxy/proxy_resolver_mac.h"
+#elif defined(OS_MACOSX)
+#include "net/proxy/proxy_config_service_mac.h"
+#include "net/proxy/proxy_resolver_mac.h"
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "net/proxy/proxy_config_service_linux.h"
+#elif defined(OS_ANDROID)
+#include "net/proxy/proxy_config_service_android.h"
+#endif
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+
+namespace {
+
+const size_t kMaxNumNetLogEntries = 100;
+
+// When the IP address changes we don't immediately re-run proxy auto-config.
+// Instead, we wait for |kDelayAfterNetworkChangesMs| before
+// attempting to re-valuate proxy auto-config.
+//
+// During this time window, any resolve requests sent to the ProxyService will
+// be queued. Once we have waited the required amount of them, the proxy
+// auto-config step will be run, and the queued requests resumed.
+//
+// The reason we play this game is that our signal for detecting network
+// changes (NetworkChangeNotifier) may fire *before* the system's networking
+// dependencies are fully configured. This is a problem since it means if
+// we were to run proxy auto-config right away, it could fail due to spurious
+// DNS failures. (see http://crbug.com/50779 for more details.)
+//
+// By adding the wait window, we give things a better chance to get properly
+// set up. Network failures can happen at any time though, so we additionally
+// poll the PAC script for changes, which will allow us to recover from these
+// sorts of problems.
+const int64 kDelayAfterNetworkChangesMs = 2000;
+
+// This is the default policy for polling the PAC script.
+//
+// In response to a failure, the poll intervals are:
+// 0: 8 seconds (scheduled on timer)
+// 1: 32 seconds
+// 2: 2 minutes
+// 3+: 4 hours
+//
+// In response to a success, the poll intervals are:
+// 0+: 12 hours
+//
+// Only the 8 second poll is scheduled on a timer, the rest happen in response
+// to network activity (and hence will take longer than the written time).
+//
+// Explanation for these values:
+//
+// TODO(eroman): These values are somewhat arbitrary, and need to be tuned
+// using some histograms data. Trying to be conservative so as not to break
+// existing setups when deployed. A simple exponential retry scheme would be
+// more elegant, but places more load on server.
+//
+// The motivation for trying quickly after failures (8 seconds) is to recover
+// from spurious network failures, which are common after the IP address has
+// just changed (like DNS failing to resolve). The next 32 second boundary is
+// to try and catch other VPN weirdness which anecdotally I have seen take
+// 10+ seconds for some users.
+//
+// The motivation for re-trying after a success is to check for possible
+// content changes to the script, or to the WPAD auto-discovery results. We are
+// not very aggressive with these checks so as to minimize the risk of
+// overloading existing PAC setups. Moreover it is unlikely that PAC scripts
+// change very frequently in existing setups. More research is needed to
+// motivate what safe values are here, and what other user agents do.
+//
+// Comparison to other browsers:
+//
+// In Firefox the PAC URL is re-tried on failures according to
+// network.proxy.autoconfig_retry_interval_min and
+// network.proxy.autoconfig_retry_interval_max. The defaults are 5 seconds and
+// 5 minutes respectively. It doubles the interval at each attempt.
+//
+// TODO(eroman): Figure out what Internet Explorer does.
+class DefaultPollPolicy : public ProxyService::PacPollPolicy {
+ public:
+ DefaultPollPolicy() {}
+
+ virtual Mode GetNextDelay(int initial_error,
+ TimeDelta current_delay,
+ TimeDelta* next_delay) const OVERRIDE {
+ if (initial_error != OK) {
+ // Re-try policy for failures.
+ const int kDelay1Seconds = 8;
+ const int kDelay2Seconds = 32;
+ const int kDelay3Seconds = 2 * 60; // 2 minutes
+ const int kDelay4Seconds = 4 * 60 * 60; // 4 Hours
+
+ // Initial poll.
+ if (current_delay < TimeDelta()) {
+ *next_delay = TimeDelta::FromSeconds(kDelay1Seconds);
+ return MODE_USE_TIMER;
+ }
+ switch (current_delay.InSeconds()) {
+ case kDelay1Seconds:
+ *next_delay = TimeDelta::FromSeconds(kDelay2Seconds);
+ return MODE_START_AFTER_ACTIVITY;
+ case kDelay2Seconds:
+ *next_delay = TimeDelta::FromSeconds(kDelay3Seconds);
+ return MODE_START_AFTER_ACTIVITY;
+ default:
+ *next_delay = TimeDelta::FromSeconds(kDelay4Seconds);
+ return MODE_START_AFTER_ACTIVITY;
+ }
+ } else {
+ // Re-try policy for succeses.
+ *next_delay = TimeDelta::FromHours(12);
+ return MODE_START_AFTER_ACTIVITY;
+ }
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultPollPolicy);
+};
+
+// Config getter that always returns direct settings.
+class ProxyConfigServiceDirect : public ProxyConfigService {
+ public:
+ // ProxyConfigService implementation:
+ virtual void AddObserver(Observer* observer) OVERRIDE {}
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {}
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config)
+ OVERRIDE {
+ *config = ProxyConfig::CreateDirect();
+ config->set_source(PROXY_CONFIG_SOURCE_UNKNOWN);
+ return CONFIG_VALID;
+ }
+};
+
+// Proxy resolver that fails every time.
+class ProxyResolverNull : public ProxyResolver {
+ public:
+ ProxyResolverNull() : ProxyResolver(false /*expects_pac_bytes*/) {}
+
+ // ProxyResolver implementation.
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ return ERR_NOT_IMPLEMENTED;
+ }
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+
+ virtual void CancelSetPacScript() OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& /*script_data*/,
+ const CompletionCallback& /*callback*/) OVERRIDE {
+ return ERR_NOT_IMPLEMENTED;
+ }
+};
+
+// ProxyResolver that simulates a PAC script which returns
+// |pac_string| for every single URL.
+class ProxyResolverFromPacString : public ProxyResolver {
+ public:
+ explicit ProxyResolverFromPacString(const std::string& pac_string)
+ : ProxyResolver(false /*expects_pac_bytes*/),
+ pac_string_(pac_string) {}
+
+ virtual int GetProxyForURL(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ RequestHandle* request,
+ const BoundNetLog& net_log) OVERRIDE {
+ results->UsePacString(pac_string_);
+ return OK;
+ }
+
+ virtual void CancelRequest(RequestHandle request) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual LoadState GetLoadState(RequestHandle request) const OVERRIDE {
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+
+ virtual void CancelSetPacScript() OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual int SetPacScript(
+ const scoped_refptr<ProxyResolverScriptData>& pac_script,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+
+ private:
+ const std::string pac_string_;
+};
+
+// Creates ProxyResolvers using a platform-specific implementation.
+class ProxyResolverFactoryForSystem : public ProxyResolverFactory {
+ public:
+ ProxyResolverFactoryForSystem()
+ : ProxyResolverFactory(false /*expects_pac_bytes*/) {}
+
+ virtual ProxyResolver* CreateProxyResolver() OVERRIDE {
+ DCHECK(IsSupported());
+#if defined(OS_WIN)
+ return new ProxyResolverWinHttp();
+#elif defined(OS_MACOSX)
+ return new ProxyResolverMac();
+#else
+ NOTREACHED();
+ return NULL;
+#endif
+ }
+
+ static bool IsSupported() {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ return true;
+#else
+ return false;
+#endif
+ }
+};
+
+// Returns NetLog parameters describing a proxy configuration change.
+base::Value* NetLogProxyConfigChangedCallback(
+ const ProxyConfig* old_config,
+ const ProxyConfig* new_config,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ // The "old_config" is optional -- the first notification will not have
+ // any "previous" configuration.
+ if (old_config->is_valid())
+ dict->Set("old_config", old_config->ToValue());
+ dict->Set("new_config", new_config->ToValue());
+ return dict;
+}
+
+base::Value* NetLogBadProxyListCallback(const ProxyRetryInfoMap* retry_info,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* list = new base::ListValue();
+
+ for (ProxyRetryInfoMap::const_iterator iter = retry_info->begin();
+ iter != retry_info->end(); ++iter) {
+ list->Append(new base::StringValue(iter->first));
+ }
+ dict->Set("bad_proxy_list", list);
+ return dict;
+}
+
+// Returns NetLog parameters on a successfuly proxy resolution.
+base::Value* NetLogFinishedResolvingProxyCallback(
+ ProxyInfo* result,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("pac_string", result->ToPacString());
+ return dict;
+}
+
+#if defined(OS_CHROMEOS)
+class UnsetProxyConfigService : public ProxyConfigService {
+ public:
+ UnsetProxyConfigService() {}
+ virtual ~UnsetProxyConfigService() {}
+
+ virtual void AddObserver(Observer* observer) OVERRIDE {}
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {}
+ virtual ConfigAvailability GetLatestProxyConfig(
+ ProxyConfig* config) OVERRIDE {
+ return CONFIG_UNSET;
+ }
+};
+#endif
+
+} // namespace
+
+// ProxyService::InitProxyResolver --------------------------------------------
+
+// This glues together two asynchronous steps:
+// (1) ProxyScriptDecider -- try to fetch/validate a sequence of PAC scripts
+// to figure out what we should configure against.
+// (2) Feed the fetched PAC script into the ProxyResolver.
+//
+// InitProxyResolver is a single-use class which encapsulates cancellation as
+// part of its destructor. Start() or StartSkipDecider() should be called just
+// once. The instance can be destroyed at any time, and the request will be
+// cancelled.
+
+class ProxyService::InitProxyResolver {
+ public:
+ InitProxyResolver()
+ : proxy_resolver_(NULL),
+ next_state_(STATE_NONE) {
+ }
+
+ ~InitProxyResolver() {
+ // Note that the destruction of ProxyScriptDecider will automatically cancel
+ // any outstanding work.
+ if (next_state_ == STATE_SET_PAC_SCRIPT_COMPLETE) {
+ proxy_resolver_->CancelSetPacScript();
+ }
+ }
+
+ // Begins initializing the proxy resolver; calls |callback| when done.
+ int Start(ProxyResolver* proxy_resolver,
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ NetLog* net_log,
+ const ProxyConfig& config,
+ TimeDelta wait_delay,
+ const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ proxy_resolver_ = proxy_resolver;
+
+ decider_.reset(new ProxyScriptDecider(
+ proxy_script_fetcher, dhcp_proxy_script_fetcher, net_log));
+ config_ = config;
+ wait_delay_ = wait_delay;
+ callback_ = callback;
+
+ next_state_ = STATE_DECIDE_PROXY_SCRIPT;
+ return DoLoop(OK);
+ }
+
+ // Similar to Start(), however it skips the ProxyScriptDecider stage. Instead
+ // |effective_config|, |decider_result| and |script_data| will be used as the
+ // inputs for initializing the ProxyResolver.
+ int StartSkipDecider(ProxyResolver* proxy_resolver,
+ const ProxyConfig& effective_config,
+ int decider_result,
+ ProxyResolverScriptData* script_data,
+ const CompletionCallback& callback) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ proxy_resolver_ = proxy_resolver;
+
+ effective_config_ = effective_config;
+ script_data_ = script_data;
+ callback_ = callback;
+
+ if (decider_result != OK)
+ return decider_result;
+
+ next_state_ = STATE_SET_PAC_SCRIPT;
+ return DoLoop(OK);
+ }
+
+ // Returns the proxy configuration that was selected by ProxyScriptDecider.
+ // Should only be called upon completion of the initialization.
+ const ProxyConfig& effective_config() const {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return effective_config_;
+ }
+
+ // Returns the PAC script data that was selected by ProxyScriptDecider.
+ // Should only be called upon completion of the initialization.
+ ProxyResolverScriptData* script_data() {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return script_data_.get();
+ }
+
+ LoadState GetLoadState() const {
+ if (next_state_ == STATE_DECIDE_PROXY_SCRIPT_COMPLETE) {
+ // In addition to downloading, this state may also include the stall time
+ // after network change events (kDelayAfterNetworkChangesMs).
+ return LOAD_STATE_DOWNLOADING_PROXY_SCRIPT;
+ }
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+ }
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_DECIDE_PROXY_SCRIPT,
+ STATE_DECIDE_PROXY_SCRIPT_COMPLETE,
+ STATE_SET_PAC_SCRIPT,
+ STATE_SET_PAC_SCRIPT_COMPLETE,
+ };
+
+ int DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_DECIDE_PROXY_SCRIPT:
+ DCHECK_EQ(OK, rv);
+ rv = DoDecideProxyScript();
+ break;
+ case STATE_DECIDE_PROXY_SCRIPT_COMPLETE:
+ rv = DoDecideProxyScriptComplete(rv);
+ break;
+ case STATE_SET_PAC_SCRIPT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSetPacScript();
+ break;
+ case STATE_SET_PAC_SCRIPT_COMPLETE:
+ rv = DoSetPacScriptComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state: " << state;
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+ }
+
+ int DoDecideProxyScript() {
+ next_state_ = STATE_DECIDE_PROXY_SCRIPT_COMPLETE;
+
+ return decider_->Start(
+ config_, wait_delay_, proxy_resolver_->expects_pac_bytes(),
+ base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this)));
+ }
+
+ int DoDecideProxyScriptComplete(int result) {
+ if (result != OK)
+ return result;
+
+ effective_config_ = decider_->effective_config();
+ script_data_ = decider_->script_data();
+
+ next_state_ = STATE_SET_PAC_SCRIPT;
+ return OK;
+ }
+
+ int DoSetPacScript() {
+ DCHECK(script_data_.get());
+ // TODO(eroman): Should log this latency to the NetLog.
+ next_state_ = STATE_SET_PAC_SCRIPT_COMPLETE;
+ return proxy_resolver_->SetPacScript(
+ script_data_,
+ base::Bind(&InitProxyResolver::OnIOCompletion, base::Unretained(this)));
+ }
+
+ int DoSetPacScriptComplete(int result) {
+ return result;
+ }
+
+ void OnIOCompletion(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoCallback(rv);
+ }
+
+ void DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ callback_.Run(result);
+ }
+
+ ProxyConfig config_;
+ ProxyConfig effective_config_;
+ scoped_refptr<ProxyResolverScriptData> script_data_;
+ TimeDelta wait_delay_;
+ scoped_ptr<ProxyScriptDecider> decider_;
+ ProxyResolver* proxy_resolver_;
+ CompletionCallback callback_;
+ State next_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(InitProxyResolver);
+};
+
+// ProxyService::ProxyScriptDeciderPoller -------------------------------------
+
+// This helper class encapsulates the logic to schedule and run periodic
+// background checks to see if the PAC script (or effective proxy configuration)
+// has changed. If a change is detected, then the caller will be notified via
+// the ChangeCallback.
+class ProxyService::ProxyScriptDeciderPoller {
+ public:
+ typedef base::Callback<void(int, ProxyResolverScriptData*,
+ const ProxyConfig&)> ChangeCallback;
+
+ // Builds a poller helper, and starts polling for updates. Whenever a change
+ // is observed, |callback| will be invoked with the details.
+ //
+ // |config| specifies the (unresolved) proxy configuration to poll.
+ // |proxy_resolver_expects_pac_bytes| the type of proxy resolver we expect
+ // to use the resulting script data with
+ // (so it can choose the right format).
+ // |proxy_script_fetcher| this pointer must remain alive throughout our
+ // lifetime. It is the dependency that will be used
+ // for downloading proxy scripts.
+ // |dhcp_proxy_script_fetcher| similar to |proxy_script_fetcher|, but for
+ // the DHCP dependency.
+ // |init_net_error| This is the initial network error (possibly success)
+ // encountered by the first PAC fetch attempt. We use it
+ // to schedule updates more aggressively if the initial
+ // fetch resulted in an error.
+ // |init_script_data| the initial script data from the PAC fetch attempt.
+ // This is the baseline used to determine when the
+ // script's contents have changed.
+ // |net_log| the NetLog to log progress into.
+ ProxyScriptDeciderPoller(ChangeCallback callback,
+ const ProxyConfig& config,
+ bool proxy_resolver_expects_pac_bytes,
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ int init_net_error,
+ ProxyResolverScriptData* init_script_data,
+ NetLog* net_log)
+ : weak_factory_(this),
+ change_callback_(callback),
+ config_(config),
+ proxy_resolver_expects_pac_bytes_(proxy_resolver_expects_pac_bytes),
+ proxy_script_fetcher_(proxy_script_fetcher),
+ dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher),
+ last_error_(init_net_error),
+ last_script_data_(init_script_data),
+ last_poll_time_(TimeTicks::Now()) {
+ // Set the initial poll delay.
+ next_poll_mode_ = poll_policy()->GetNextDelay(
+ last_error_, TimeDelta::FromSeconds(-1), &next_poll_delay_);
+ TryToStartNextPoll(false);
+ }
+
+ void OnLazyPoll() {
+ // We have just been notified of network activity. Use this opportunity to
+ // see if we can start our next poll.
+ TryToStartNextPoll(true);
+ }
+
+ static const PacPollPolicy* set_policy(const PacPollPolicy* policy) {
+ const PacPollPolicy* prev = poll_policy_;
+ poll_policy_ = policy;
+ return prev;
+ }
+
+ private:
+ // Returns the effective poll policy (the one injected by unit-tests, or the
+ // default).
+ const PacPollPolicy* poll_policy() {
+ if (poll_policy_)
+ return poll_policy_;
+ return &default_poll_policy_;
+ }
+
+ void StartPollTimer() {
+ DCHECK(!decider_.get());
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ProxyScriptDeciderPoller::DoPoll,
+ weak_factory_.GetWeakPtr()),
+ next_poll_delay_);
+ }
+
+ void TryToStartNextPoll(bool triggered_by_activity) {
+ switch (next_poll_mode_) {
+ case PacPollPolicy::MODE_USE_TIMER:
+ if (!triggered_by_activity)
+ StartPollTimer();
+ break;
+
+ case PacPollPolicy::MODE_START_AFTER_ACTIVITY:
+ if (triggered_by_activity && !decider_.get()) {
+ TimeDelta elapsed_time = TimeTicks::Now() - last_poll_time_;
+ if (elapsed_time >= next_poll_delay_)
+ DoPoll();
+ }
+ break;
+ }
+ }
+
+ void DoPoll() {
+ last_poll_time_ = TimeTicks::Now();
+
+ // Start the proxy script decider to see if anything has changed.
+ // TODO(eroman): Pass a proper NetLog rather than NULL.
+ decider_.reset(new ProxyScriptDecider(
+ proxy_script_fetcher_, dhcp_proxy_script_fetcher_, NULL));
+ int result = decider_->Start(
+ config_, TimeDelta(), proxy_resolver_expects_pac_bytes_,
+ base::Bind(&ProxyScriptDeciderPoller::OnProxyScriptDeciderCompleted,
+ base::Unretained(this)));
+
+ if (result != ERR_IO_PENDING)
+ OnProxyScriptDeciderCompleted(result);
+ }
+
+ void OnProxyScriptDeciderCompleted(int result) {
+ if (HasScriptDataChanged(result, decider_->script_data())) {
+ // Something has changed, we must notify the ProxyService so it can
+ // re-initialize its ProxyResolver. Note that we post a notification task
+ // rather than calling it directly -- this is done to avoid an ugly
+ // destruction sequence, since |this| might be destroyed as a result of
+ // the notification.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ProxyScriptDeciderPoller::NotifyProxyServiceOfChange,
+ weak_factory_.GetWeakPtr(),
+ result,
+ make_scoped_refptr(decider_->script_data()),
+ decider_->effective_config()));
+ return;
+ }
+
+ decider_.reset();
+
+ // Decide when the next poll should take place, and possibly start the
+ // next timer.
+ next_poll_mode_ = poll_policy()->GetNextDelay(
+ last_error_, next_poll_delay_, &next_poll_delay_);
+ TryToStartNextPoll(false);
+ }
+
+ bool HasScriptDataChanged(int result, ProxyResolverScriptData* script_data) {
+ if (result != last_error_) {
+ // Something changed -- it was failing before and now it succeeded, or
+ // conversely it succeeded before and now it failed. Or it failed in
+ // both cases, however the specific failure error codes differ.
+ return true;
+ }
+
+ if (result != OK) {
+ // If it failed last time and failed again with the same error code this
+ // time, then nothing has actually changed.
+ return false;
+ }
+
+ // Otherwise if it succeeded both this time and last time, we need to look
+ // closer and see if we ended up downloading different content for the PAC
+ // script.
+ return !script_data->Equals(last_script_data_.get());
+ }
+
+ void NotifyProxyServiceOfChange(
+ int result,
+ const scoped_refptr<ProxyResolverScriptData>& script_data,
+ const ProxyConfig& effective_config) {
+ // Note that |this| may be deleted after calling into the ProxyService.
+ change_callback_.Run(result, script_data.get(), effective_config);
+ }
+
+ base::WeakPtrFactory<ProxyScriptDeciderPoller> weak_factory_;
+
+ ChangeCallback change_callback_;
+ ProxyConfig config_;
+ bool proxy_resolver_expects_pac_bytes_;
+ ProxyScriptFetcher* proxy_script_fetcher_;
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher_;
+
+ int last_error_;
+ scoped_refptr<ProxyResolverScriptData> last_script_data_;
+
+ scoped_ptr<ProxyScriptDecider> decider_;
+ TimeDelta next_poll_delay_;
+ PacPollPolicy::Mode next_poll_mode_;
+
+ TimeTicks last_poll_time_;
+
+ // Polling policy injected by unit-tests. Otherwise this is NULL and the
+ // default policy will be used.
+ static const PacPollPolicy* poll_policy_;
+
+ const DefaultPollPolicy default_poll_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyScriptDeciderPoller);
+};
+
+// static
+const ProxyService::PacPollPolicy*
+ ProxyService::ProxyScriptDeciderPoller::poll_policy_ = NULL;
+
+// ProxyService::PacRequest ---------------------------------------------------
+
+class ProxyService::PacRequest
+ : public base::RefCounted<ProxyService::PacRequest> {
+ public:
+ PacRequest(ProxyService* service,
+ const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& user_callback,
+ const BoundNetLog& net_log)
+ : service_(service),
+ user_callback_(user_callback),
+ results_(results),
+ url_(url),
+ resolve_job_(NULL),
+ config_id_(ProxyConfig::kInvalidConfigID),
+ config_source_(PROXY_CONFIG_SOURCE_UNKNOWN),
+ net_log_(net_log) {
+ DCHECK(!user_callback.is_null());
+ }
+
+ // Starts the resolve proxy request.
+ int Start() {
+ DCHECK(!was_cancelled());
+ DCHECK(!is_started());
+
+ DCHECK(service_->config_.is_valid());
+
+ config_id_ = service_->config_.id();
+ config_source_ = service_->config_.source();
+ proxy_resolve_start_time_ = TimeTicks::Now();
+
+ return resolver()->GetProxyForURL(
+ url_, results_,
+ base::Bind(&PacRequest::QueryComplete, base::Unretained(this)),
+ &resolve_job_, net_log_);
+ }
+
+ bool is_started() const {
+ // Note that !! casts to bool. (VS gives a warning otherwise).
+ return !!resolve_job_;
+ }
+
+ void StartAndCompleteCheckingForSynchronous() {
+ int rv = service_->TryToCompleteSynchronously(url_, results_);
+ if (rv == ERR_IO_PENDING)
+ rv = Start();
+ if (rv != ERR_IO_PENDING)
+ QueryComplete(rv);
+ }
+
+ void CancelResolveJob() {
+ DCHECK(is_started());
+ // The request may already be running in the resolver.
+ resolver()->CancelRequest(resolve_job_);
+ resolve_job_ = NULL;
+ DCHECK(!is_started());
+ }
+
+ void Cancel() {
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+
+ if (is_started())
+ CancelResolveJob();
+
+ // Mark as cancelled, to prevent accessing this again later.
+ service_ = NULL;
+ user_callback_.Reset();
+ results_ = NULL;
+
+ net_log_.EndEvent(NetLog::TYPE_PROXY_SERVICE);
+ }
+
+ // Returns true if Cancel() has been called.
+ bool was_cancelled() const {
+ return user_callback_.is_null();
+ }
+
+ // Helper to call after ProxyResolver completion (both synchronous and
+ // asynchronous). Fixes up the result that is to be returned to user.
+ int QueryDidComplete(int result_code) {
+ DCHECK(!was_cancelled());
+
+ // Note that DidFinishResolvingProxy might modify |results_|.
+ int rv = service_->DidFinishResolvingProxy(results_, result_code, net_log_);
+
+ // Make a note in the results which configuration was in use at the
+ // time of the resolve.
+ results_->config_id_ = config_id_;
+ results_->config_source_ = config_source_;
+ results_->did_use_pac_script_ = true;
+ results_->proxy_resolve_start_time_ = proxy_resolve_start_time_;
+ results_->proxy_resolve_end_time_ = TimeTicks::Now();
+
+ // Reset the state associated with in-progress-resolve.
+ resolve_job_ = NULL;
+ config_id_ = ProxyConfig::kInvalidConfigID;
+ config_source_ = PROXY_CONFIG_SOURCE_UNKNOWN;
+
+ return rv;
+ }
+
+ BoundNetLog* net_log() { return &net_log_; }
+
+ LoadState GetLoadState() const {
+ if (is_started())
+ return resolver()->GetLoadState(resolve_job_);
+ return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
+ }
+
+ private:
+ friend class base::RefCounted<ProxyService::PacRequest>;
+
+ ~PacRequest() {}
+
+ // Callback for when the ProxyResolver request has completed.
+ void QueryComplete(int result_code) {
+ result_code = QueryDidComplete(result_code);
+
+ // Remove this completed PacRequest from the service's pending list.
+ /// (which will probably cause deletion of |this|).
+ if (!user_callback_.is_null()) {
+ net::CompletionCallback callback = user_callback_;
+ service_->RemovePendingRequest(this);
+ callback.Run(result_code);
+ }
+ }
+
+ ProxyResolver* resolver() const { return service_->resolver_.get(); }
+
+ // Note that we don't hold a reference to the ProxyService. Outstanding
+ // requests are cancelled during ~ProxyService, so this is guaranteed
+ // to be valid throughout our lifetime.
+ ProxyService* service_;
+ net::CompletionCallback user_callback_;
+ ProxyInfo* results_;
+ GURL url_;
+ ProxyResolver::RequestHandle resolve_job_;
+ ProxyConfig::ID config_id_; // The config id when the resolve was started.
+ ProxyConfigSource config_source_; // The source of proxy settings.
+ BoundNetLog net_log_;
+ // Time when the PAC is started. Cached here since resetting ProxyInfo also
+ // clears the proxy times.
+ TimeTicks proxy_resolve_start_time_;
+};
+
+// ProxyService ---------------------------------------------------------------
+
+ProxyService::ProxyService(ProxyConfigService* config_service,
+ ProxyResolver* resolver,
+ NetLog* net_log)
+ : resolver_(resolver),
+ next_config_id_(1),
+ current_state_(STATE_NONE) ,
+ net_log_(net_log),
+ stall_proxy_auto_config_delay_(TimeDelta::FromMilliseconds(
+ kDelayAfterNetworkChangesMs)) {
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ NetworkChangeNotifier::AddDNSObserver(this);
+ ResetConfigService(config_service);
+}
+
+// static
+ProxyService* ProxyService::CreateUsingSystemProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ size_t num_pac_threads,
+ NetLog* net_log) {
+ DCHECK(proxy_config_service);
+
+ if (!ProxyResolverFactoryForSystem::IsSupported()) {
+ LOG(WARNING) << "PAC support disabled because there is no "
+ "system implementation";
+ return CreateWithoutProxyResolver(proxy_config_service, net_log);
+ }
+
+ if (num_pac_threads == 0)
+ num_pac_threads = kDefaultNumPacThreads;
+
+ ProxyResolver* proxy_resolver = new MultiThreadedProxyResolver(
+ new ProxyResolverFactoryForSystem(), num_pac_threads);
+
+ return new ProxyService(proxy_config_service, proxy_resolver, net_log);
+}
+
+// static
+ProxyService* ProxyService::CreateWithoutProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ NetLog* net_log) {
+ return new ProxyService(proxy_config_service,
+ new ProxyResolverNull(),
+ net_log);
+}
+
+// static
+ProxyService* ProxyService::CreateFixed(const ProxyConfig& pc) {
+ // TODO(eroman): This isn't quite right, won't work if |pc| specifies
+ // a PAC script.
+ return CreateUsingSystemProxyResolver(new ProxyConfigServiceFixed(pc),
+ 0, NULL);
+}
+
+// static
+ProxyService* ProxyService::CreateFixed(const std::string& proxy) {
+ net::ProxyConfig proxy_config;
+ proxy_config.proxy_rules().ParseFromString(proxy);
+ return ProxyService::CreateFixed(proxy_config);
+}
+
+// static
+ProxyService* ProxyService::CreateDirect() {
+ return CreateDirectWithNetLog(NULL);
+}
+
+ProxyService* ProxyService::CreateDirectWithNetLog(NetLog* net_log) {
+ // Use direct connections.
+ return new ProxyService(new ProxyConfigServiceDirect, new ProxyResolverNull,
+ net_log);
+}
+
+// static
+ProxyService* ProxyService::CreateFixedFromPacResult(
+ const std::string& pac_string) {
+
+ // We need the settings to contain an "automatic" setting, otherwise the
+ // ProxyResolver dependency we give it will never be used.
+ scoped_ptr<ProxyConfigService> proxy_config_service(
+ new ProxyConfigServiceFixed(ProxyConfig::CreateAutoDetect()));
+
+ scoped_ptr<ProxyResolver> proxy_resolver(
+ new ProxyResolverFromPacString(pac_string));
+
+ return new ProxyService(proxy_config_service.release(),
+ proxy_resolver.release(),
+ NULL);
+}
+
+int ProxyService::ResolveProxy(const GURL& raw_url,
+ ProxyInfo* result,
+ const net::CompletionCallback& callback,
+ PacRequest** pac_request,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ net_log.BeginEvent(NetLog::TYPE_PROXY_SERVICE);
+
+ // Notify our polling-based dependencies that a resolve is taking place.
+ // This way they can schedule their polls in response to network activity.
+ config_service_->OnLazyPoll();
+ if (script_poller_.get())
+ script_poller_->OnLazyPoll();
+
+ if (current_state_ == STATE_NONE)
+ ApplyProxyConfigIfAvailable();
+
+ // Strip away any reference fragments and the username/password, as they
+ // are not relevant to proxy resolution.
+ GURL url = SimplifyUrlForRequest(raw_url);
+
+ // Check if the request can be completed right away. (This is the case when
+ // using a direct connection for example).
+ int rv = TryToCompleteSynchronously(url, result);
+ if (rv != ERR_IO_PENDING)
+ return DidFinishResolvingProxy(result, rv, net_log);
+
+ scoped_refptr<PacRequest> req(
+ new PacRequest(this, url, result, callback, net_log));
+
+ if (current_state_ == STATE_READY) {
+ // Start the resolve request.
+ rv = req->Start();
+ if (rv != ERR_IO_PENDING)
+ return req->QueryDidComplete(rv);
+ } else {
+ req->net_log()->BeginEvent(NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+ }
+
+ DCHECK_EQ(ERR_IO_PENDING, rv);
+ DCHECK(!ContainsPendingRequest(req.get()));
+ pending_requests_.push_back(req);
+
+ // Completion will be notified through |callback|, unless the caller cancels
+ // the request using |pac_request|.
+ if (pac_request)
+ *pac_request = req.get();
+ return rv; // ERR_IO_PENDING
+}
+
+int ProxyService::TryToCompleteSynchronously(const GURL& url,
+ ProxyInfo* result) {
+ DCHECK_NE(STATE_NONE, current_state_);
+
+ if (current_state_ != STATE_READY)
+ return ERR_IO_PENDING; // Still initializing.
+
+ DCHECK_NE(config_.id(), ProxyConfig::kInvalidConfigID);
+
+ // If it was impossible to fetch or parse the PAC script, we cannot complete
+ // the request here and bail out.
+ if (permanent_error_ != OK)
+ return permanent_error_;
+
+ if (config_.HasAutomaticSettings())
+ return ERR_IO_PENDING; // Must submit the request to the proxy resolver.
+
+ // Use the manual proxy settings.
+ config_.proxy_rules().Apply(url, result);
+ result->config_source_ = config_.source();
+ result->config_id_ = config_.id();
+ return OK;
+}
+
+ProxyService::~ProxyService() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ NetworkChangeNotifier::RemoveDNSObserver(this);
+ config_service_->RemoveObserver(this);
+
+ // Cancel any inprogress requests.
+ for (PendingRequests::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();
+ ++it) {
+ (*it)->Cancel();
+ }
+}
+
+void ProxyService::SuspendAllPendingRequests() {
+ for (PendingRequests::iterator it = pending_requests_.begin();
+ it != pending_requests_.end();
+ ++it) {
+ PacRequest* req = it->get();
+ if (req->is_started()) {
+ req->CancelResolveJob();
+
+ req->net_log()->BeginEvent(
+ NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+ }
+ }
+}
+
+void ProxyService::SetReady() {
+ DCHECK(!init_proxy_resolver_.get());
+ current_state_ = STATE_READY;
+
+ // Make a copy in case |this| is deleted during the synchronous completion
+ // of one of the requests. If |this| is deleted then all of the PacRequest
+ // instances will be Cancel()-ed.
+ PendingRequests pending_copy = pending_requests_;
+
+ for (PendingRequests::iterator it = pending_copy.begin();
+ it != pending_copy.end();
+ ++it) {
+ PacRequest* req = it->get();
+ if (!req->is_started() && !req->was_cancelled()) {
+ req->net_log()->EndEvent(NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC);
+
+ // Note that we re-check for synchronous completion, in case we are
+ // no longer using a ProxyResolver (can happen if we fell-back to manual).
+ req->StartAndCompleteCheckingForSynchronous();
+ }
+ }
+}
+
+void ProxyService::ApplyProxyConfigIfAvailable() {
+ DCHECK_EQ(STATE_NONE, current_state_);
+
+ config_service_->OnLazyPoll();
+
+ // If we have already fetched the configuration, start applying it.
+ if (fetched_config_.is_valid()) {
+ InitializeUsingLastFetchedConfig();
+ return;
+ }
+
+ // Otherwise we need to first fetch the configuration.
+ current_state_ = STATE_WAITING_FOR_PROXY_CONFIG;
+
+ // Retrieve the current proxy configuration from the ProxyConfigService.
+ // If a configuration is not available yet, we will get called back later
+ // by our ProxyConfigService::Observer once it changes.
+ ProxyConfig config;
+ ProxyConfigService::ConfigAvailability availability =
+ config_service_->GetLatestProxyConfig(&config);
+ if (availability != ProxyConfigService::CONFIG_PENDING)
+ OnProxyConfigChanged(config, availability);
+}
+
+void ProxyService::OnInitProxyResolverComplete(int result) {
+ DCHECK_EQ(STATE_WAITING_FOR_INIT_PROXY_RESOLVER, current_state_);
+ DCHECK(init_proxy_resolver_.get());
+ DCHECK(fetched_config_.HasAutomaticSettings());
+ config_ = init_proxy_resolver_->effective_config();
+
+ // At this point we have decided which proxy settings to use (i.e. which PAC
+ // script if any). We start up a background poller to periodically revisit
+ // this decision. If the contents of the PAC script change, or if the
+ // result of proxy auto-discovery changes, this poller will notice it and
+ // will trigger a re-initialization using the newly discovered PAC.
+ script_poller_.reset(new ProxyScriptDeciderPoller(
+ base::Bind(&ProxyService::InitializeUsingDecidedConfig,
+ base::Unretained(this)),
+ fetched_config_,
+ resolver_->expects_pac_bytes(),
+ proxy_script_fetcher_.get(),
+ dhcp_proxy_script_fetcher_.get(),
+ result,
+ init_proxy_resolver_->script_data(),
+ NULL));
+
+ init_proxy_resolver_.reset();
+
+ if (result != OK) {
+ if (fetched_config_.pac_mandatory()) {
+ VLOG(1) << "Failed configuring with mandatory PAC script, blocking all "
+ "traffic.";
+ config_ = fetched_config_;
+ result = ERR_MANDATORY_PROXY_CONFIGURATION_FAILED;
+ } else {
+ VLOG(1) << "Failed configuring with PAC script, falling-back to manual "
+ "proxy servers.";
+ config_ = fetched_config_;
+ config_.ClearAutomaticSettings();
+ result = OK;
+ }
+ }
+ permanent_error_ = result;
+
+ // TODO(eroman): Make this ID unique in the case where configuration changed
+ // due to ProxyScriptDeciderPoller.
+ config_.set_id(fetched_config_.id());
+ config_.set_source(fetched_config_.source());
+
+ // Resume any requests which we had to defer until the PAC script was
+ // downloaded.
+ SetReady();
+}
+
+int ProxyService::ReconsiderProxyAfterError(const GURL& url,
+ ProxyInfo* result,
+ const CompletionCallback& callback,
+ PacRequest** pac_request,
+ const BoundNetLog& net_log) {
+ DCHECK(CalledOnValidThread());
+
+ // Check to see if we have a new config since ResolveProxy was called. We
+ // want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a
+ // direct connection failed and we never tried the current config.
+
+ bool re_resolve = result->config_id_ != config_.id();
+
+ if (re_resolve) {
+ // If we have a new config or the config was never tried, we delete the
+ // list of bad proxies and we try again.
+ proxy_retry_info_.clear();
+ return ResolveProxy(url, result, callback, pac_request, net_log);
+ }
+
+ // We don't have new proxy settings to try, try to fallback to the next proxy
+ // in the list.
+ bool did_fallback = result->Fallback(net_log);
+
+ // Return synchronous failure if there is nothing left to fall-back to.
+ // TODO(eroman): This is a yucky API, clean it up.
+ return did_fallback ? OK : ERR_FAILED;
+}
+
+bool ProxyService::MarkProxyAsBad(const ProxyInfo& result,
+ const BoundNetLog& net_log) {
+ result.proxy_list_.UpdateRetryInfoOnFallback(&proxy_retry_info_, net_log);
+ return result.proxy_list_.HasUntriedProxies(proxy_retry_info_);
+}
+
+void ProxyService::ReportSuccess(const ProxyInfo& result) {
+ DCHECK(CalledOnValidThread());
+
+ const ProxyRetryInfoMap& new_retry_info = result.proxy_retry_info();
+ if (new_retry_info.empty())
+ return;
+
+ for (ProxyRetryInfoMap::const_iterator iter = new_retry_info.begin();
+ iter != new_retry_info.end(); ++iter) {
+ ProxyRetryInfoMap::iterator existing = proxy_retry_info_.find(iter->first);
+ if (existing == proxy_retry_info_.end())
+ proxy_retry_info_[iter->first] = iter->second;
+ else if (existing->second.bad_until < iter->second.bad_until)
+ existing->second.bad_until = iter->second.bad_until;
+ }
+ if (net_log_) {
+ net_log_->AddGlobalEntry(
+ NetLog::TYPE_BAD_PROXY_LIST_REPORTED,
+ base::Bind(&NetLogBadProxyListCallback, &new_retry_info));
+ }
+}
+
+void ProxyService::CancelPacRequest(PacRequest* req) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(req);
+ req->Cancel();
+ RemovePendingRequest(req);
+}
+
+LoadState ProxyService::GetLoadState(const PacRequest* req) const {
+ CHECK(req);
+ if (current_state_ == STATE_WAITING_FOR_INIT_PROXY_RESOLVER)
+ return init_proxy_resolver_->GetLoadState();
+ return req->GetLoadState();
+}
+
+bool ProxyService::ContainsPendingRequest(PacRequest* req) {
+ PendingRequests::iterator it = std::find(
+ pending_requests_.begin(), pending_requests_.end(), req);
+ return pending_requests_.end() != it;
+}
+
+void ProxyService::RemovePendingRequest(PacRequest* req) {
+ DCHECK(ContainsPendingRequest(req));
+ PendingRequests::iterator it = std::find(
+ pending_requests_.begin(), pending_requests_.end(), req);
+ pending_requests_.erase(it);
+}
+
+int ProxyService::DidFinishResolvingProxy(ProxyInfo* result,
+ int result_code,
+ const BoundNetLog& net_log) {
+ // Log the result of the proxy resolution.
+ if (result_code == OK) {
+ // When logging all events is enabled, dump the proxy list.
+ if (net_log.IsLoggingAllEvents()) {
+ net_log.AddEvent(
+ NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST,
+ base::Bind(&NetLogFinishedResolvingProxyCallback, result));
+ }
+ result->DeprioritizeBadProxies(proxy_retry_info_);
+ } else {
+ net_log.AddEventWithNetErrorCode(
+ NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST, result_code);
+
+ if (!config_.pac_mandatory()) {
+ // Fall-back to direct when the proxy resolver fails. This corresponds
+ // with a javascript runtime error in the PAC script.
+ //
+ // This implicit fall-back to direct matches Firefox 3.5 and
+ // Internet Explorer 8. For more information, see:
+ //
+ // http://www.chromium.org/developers/design-documents/proxy-settings-fallback
+ result->UseDirect();
+ result_code = OK;
+ } else {
+ result_code = ERR_MANDATORY_PROXY_CONFIGURATION_FAILED;
+ }
+ }
+
+ net_log.EndEvent(NetLog::TYPE_PROXY_SERVICE);
+ return result_code;
+}
+
+void ProxyService::SetProxyScriptFetchers(
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher) {
+ DCHECK(CalledOnValidThread());
+ State previous_state = ResetProxyConfig(false);
+ proxy_script_fetcher_.reset(proxy_script_fetcher);
+ dhcp_proxy_script_fetcher_.reset(dhcp_proxy_script_fetcher);
+ if (previous_state != STATE_NONE)
+ ApplyProxyConfigIfAvailable();
+}
+
+ProxyScriptFetcher* ProxyService::GetProxyScriptFetcher() const {
+ DCHECK(CalledOnValidThread());
+ return proxy_script_fetcher_.get();
+}
+
+ProxyService::State ProxyService::ResetProxyConfig(bool reset_fetched_config) {
+ DCHECK(CalledOnValidThread());
+ State previous_state = current_state_;
+
+ permanent_error_ = OK;
+ proxy_retry_info_.clear();
+ script_poller_.reset();
+ init_proxy_resolver_.reset();
+ SuspendAllPendingRequests();
+ config_ = ProxyConfig();
+ if (reset_fetched_config)
+ fetched_config_ = ProxyConfig();
+ current_state_ = STATE_NONE;
+
+ return previous_state;
+}
+
+void ProxyService::ResetConfigService(
+ ProxyConfigService* new_proxy_config_service) {
+ DCHECK(CalledOnValidThread());
+ State previous_state = ResetProxyConfig(true);
+
+ // Release the old configuration service.
+ if (config_service_.get())
+ config_service_->RemoveObserver(this);
+
+ // Set the new configuration service.
+ config_service_.reset(new_proxy_config_service);
+ config_service_->AddObserver(this);
+
+ if (previous_state != STATE_NONE)
+ ApplyProxyConfigIfAvailable();
+}
+
+void ProxyService::PurgeMemory() {
+ DCHECK(CalledOnValidThread());
+ if (resolver_.get())
+ resolver_->PurgeMemory();
+}
+
+void ProxyService::ForceReloadProxyConfig() {
+ DCHECK(CalledOnValidThread());
+ ResetProxyConfig(false);
+ ApplyProxyConfigIfAvailable();
+}
+
+// static
+ProxyConfigService* ProxyService::CreateSystemProxyConfigService(
+ base::SingleThreadTaskRunner* io_thread_task_runner,
+ base::MessageLoop* file_loop) {
+#if defined(OS_WIN)
+ return new ProxyConfigServiceWin();
+#elif defined(OS_IOS)
+ return new ProxyConfigServiceIOS();
+#elif defined(OS_MACOSX)
+ return new ProxyConfigServiceMac(io_thread_task_runner);
+#elif defined(OS_CHROMEOS)
+ LOG(ERROR) << "ProxyConfigService for ChromeOS should be created in "
+ << "profile_io_data.cc::CreateProxyConfigService and this should "
+ << "be used only for examples.";
+ return new UnsetProxyConfigService;
+#elif defined(OS_LINUX)
+ ProxyConfigServiceLinux* linux_config_service =
+ new ProxyConfigServiceLinux();
+
+ // Assume we got called on the thread that runs the default glib
+ // main loop, so the current thread is where we should be running
+ // gconf calls from.
+ scoped_refptr<base::SingleThreadTaskRunner> glib_thread_task_runner =
+ base::ThreadTaskRunnerHandle::Get();
+
+ // The file loop should be a MessageLoopForIO on Linux.
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, file_loop->type());
+
+ // Synchronously fetch the current proxy config (since we are
+ // running on glib_default_loop). Additionally register for
+ // notifications (delivered in either |glib_default_loop| or
+ // |file_loop|) to keep us updated when the proxy config changes.
+ linux_config_service->SetupAndFetchInitialConfig(
+ glib_thread_task_runner.get(),
+ io_thread_task_runner,
+ static_cast<base::MessageLoopForIO*>(file_loop));
+
+ return linux_config_service;
+#elif defined(OS_ANDROID)
+ return new ProxyConfigServiceAndroid(
+ io_thread_task_runner,
+ base::MessageLoop::current()->message_loop_proxy());
+#else
+ LOG(WARNING) << "Failed to choose a system proxy settings fetcher "
+ "for this platform.";
+ return new ProxyConfigServiceDirect();
+#endif
+}
+
+// static
+const ProxyService::PacPollPolicy* ProxyService::set_pac_script_poll_policy(
+ const PacPollPolicy* policy) {
+ return ProxyScriptDeciderPoller::set_policy(policy);
+}
+
+// static
+scoped_ptr<ProxyService::PacPollPolicy>
+ ProxyService::CreateDefaultPacPollPolicy() {
+ return scoped_ptr<PacPollPolicy>(new DefaultPollPolicy());
+}
+
+void ProxyService::OnProxyConfigChanged(
+ const ProxyConfig& config,
+ ProxyConfigService::ConfigAvailability availability) {
+ // Retrieve the current proxy configuration from the ProxyConfigService.
+ // If a configuration is not available yet, we will get called back later
+ // by our ProxyConfigService::Observer once it changes.
+ ProxyConfig effective_config;
+ switch (availability) {
+ case ProxyConfigService::CONFIG_PENDING:
+ // ProxyConfigService implementors should never pass CONFIG_PENDING.
+ NOTREACHED() << "Proxy config change with CONFIG_PENDING availability!";
+ return;
+ case ProxyConfigService::CONFIG_VALID:
+ effective_config = config;
+ break;
+ case ProxyConfigService::CONFIG_UNSET:
+ effective_config = ProxyConfig::CreateDirect();
+ break;
+ }
+
+ // Emit the proxy settings change to the NetLog stream.
+ if (net_log_) {
+ net_log_->AddGlobalEntry(
+ net::NetLog::TYPE_PROXY_CONFIG_CHANGED,
+ base::Bind(&NetLogProxyConfigChangedCallback,
+ &fetched_config_, &effective_config));
+ }
+
+ // Set the new configuration as the most recently fetched one.
+ fetched_config_ = effective_config;
+ fetched_config_.set_id(1); // Needed for a later DCHECK of is_valid().
+
+ InitializeUsingLastFetchedConfig();
+}
+
+void ProxyService::InitializeUsingLastFetchedConfig() {
+ ResetProxyConfig(false);
+
+ DCHECK(fetched_config_.is_valid());
+
+ // Increment the ID to reflect that the config has changed.
+ fetched_config_.set_id(next_config_id_++);
+
+ if (!fetched_config_.HasAutomaticSettings()) {
+ config_ = fetched_config_;
+ SetReady();
+ return;
+ }
+
+ // Start downloading + testing the PAC scripts for this new configuration.
+ current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER;
+
+ // If we changed networks recently, we should delay running proxy auto-config.
+ TimeDelta wait_delay =
+ stall_proxy_autoconfig_until_ - TimeTicks::Now();
+
+ init_proxy_resolver_.reset(new InitProxyResolver());
+ int rv = init_proxy_resolver_->Start(
+ resolver_.get(),
+ proxy_script_fetcher_.get(),
+ dhcp_proxy_script_fetcher_.get(),
+ net_log_,
+ fetched_config_,
+ wait_delay,
+ base::Bind(&ProxyService::OnInitProxyResolverComplete,
+ base::Unretained(this)));
+
+ if (rv != ERR_IO_PENDING)
+ OnInitProxyResolverComplete(rv);
+}
+
+void ProxyService::InitializeUsingDecidedConfig(
+ int decider_result,
+ ProxyResolverScriptData* script_data,
+ const ProxyConfig& effective_config) {
+ DCHECK(fetched_config_.is_valid());
+ DCHECK(fetched_config_.HasAutomaticSettings());
+
+ ResetProxyConfig(false);
+
+ current_state_ = STATE_WAITING_FOR_INIT_PROXY_RESOLVER;
+
+ init_proxy_resolver_.reset(new InitProxyResolver());
+ int rv = init_proxy_resolver_->StartSkipDecider(
+ resolver_.get(),
+ effective_config,
+ decider_result,
+ script_data,
+ base::Bind(&ProxyService::OnInitProxyResolverComplete,
+ base::Unretained(this)));
+
+ if (rv != ERR_IO_PENDING)
+ OnInitProxyResolverComplete(rv);
+}
+
+void ProxyService::OnIPAddressChanged() {
+ // See the comment block by |kDelayAfterNetworkChangesMs| for info.
+ stall_proxy_autoconfig_until_ =
+ TimeTicks::Now() + stall_proxy_auto_config_delay_;
+
+ State previous_state = ResetProxyConfig(false);
+ if (previous_state != STATE_NONE)
+ ApplyProxyConfigIfAvailable();
+}
+
+void ProxyService::OnDNSChanged() {
+ OnIPAddressChanged();
+}
+
+SyncProxyServiceHelper::SyncProxyServiceHelper(
+ base::MessageLoop* io_message_loop,
+ ProxyService* proxy_service)
+ : io_message_loop_(io_message_loop),
+ proxy_service_(proxy_service),
+ event_(false, false),
+ callback_(base::Bind(&SyncProxyServiceHelper::OnCompletion,
+ base::Unretained(this))) {
+ DCHECK(io_message_loop_ != base::MessageLoop::current());
+}
+
+int SyncProxyServiceHelper::ResolveProxy(const GURL& url,
+ ProxyInfo* proxy_info,
+ const BoundNetLog& net_log) {
+ DCHECK(io_message_loop_ != base::MessageLoop::current());
+
+ io_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncProxyServiceHelper::StartAsyncResolve, this, url,
+ net_log));
+
+ event_.Wait();
+
+ if (result_ == net::OK) {
+ *proxy_info = proxy_info_;
+ }
+ return result_;
+}
+
+int SyncProxyServiceHelper::ReconsiderProxyAfterError(
+ const GURL& url, ProxyInfo* proxy_info, const BoundNetLog& net_log) {
+ DCHECK(io_message_loop_ != base::MessageLoop::current());
+
+ io_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&SyncProxyServiceHelper::StartAsyncReconsider, this, url,
+ net_log));
+
+ event_.Wait();
+
+ if (result_ == net::OK) {
+ *proxy_info = proxy_info_;
+ }
+ return result_;
+}
+
+SyncProxyServiceHelper::~SyncProxyServiceHelper() {}
+
+void SyncProxyServiceHelper::StartAsyncResolve(const GURL& url,
+ const BoundNetLog& net_log) {
+ result_ = proxy_service_->ResolveProxy(
+ url, &proxy_info_, callback_, NULL, net_log);
+ if (result_ != net::ERR_IO_PENDING) {
+ OnCompletion(result_);
+ }
+}
+
+void SyncProxyServiceHelper::StartAsyncReconsider(const GURL& url,
+ const BoundNetLog& net_log) {
+ result_ = proxy_service_->ReconsiderProxyAfterError(
+ url, &proxy_info_, callback_, NULL, net_log);
+ if (result_ != net::ERR_IO_PENDING) {
+ OnCompletion(result_);
+ }
+}
+
+void SyncProxyServiceHelper::OnCompletion(int rv) {
+ result_ = rv;
+ event_.Signal();
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_service.h b/chromium/net/proxy/proxy_service.h
new file mode 100644
index 00000000000..663991133a0
--- /dev/null
+++ b/chromium/net/proxy/proxy_service.h
@@ -0,0 +1,441 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_SERVICE_H_
+#define NET_PROXY_PROXY_SERVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_server.h"
+
+class GURL;
+
+namespace base {
+class MessageLoop;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+
+class DhcpProxyScriptFetcher;
+class HostResolver;
+class NetworkDelegate;
+class ProxyResolver;
+class ProxyResolverScriptData;
+class ProxyScriptDecider;
+class ProxyScriptFetcher;
+
+// This class can be used to resolve the proxy server to use when loading a
+// HTTP(S) URL. It uses the given ProxyResolver to handle the actual proxy
+// resolution. See ProxyResolverV8 for example.
+class NET_EXPORT ProxyService : public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::DNSObserver,
+ public ProxyConfigService::Observer,
+ NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ static const size_t kDefaultNumPacThreads = 4;
+
+ // This interface defines the set of policies for when to poll the PAC
+ // script for changes.
+ //
+ // The polling policy decides what the next poll delay should be in
+ // milliseconds. It also decides how to wait for this delay -- either
+ // by starting a timer to do the poll at exactly |next_delay_ms|
+ // (MODE_USE_TIMER) or by waiting for the first network request issued after
+ // |next_delay_ms| (MODE_START_AFTER_ACTIVITY).
+ //
+ // The timer method is more precise and guarantees that polling happens when
+ // it was requested. However it has the disadvantage of causing spurious CPU
+ // and network activity. It is a reasonable choice to use for short poll
+ // intervals which only happen a couple times.
+ //
+ // However for repeated timers this will prevent the browser from going
+ // idle. MODE_START_AFTER_ACTIVITY solves this problem by only polling in
+ // direct response to network activity. The drawback to
+ // MODE_START_AFTER_ACTIVITY is since the poll is initiated only after the
+ // request is received, the first couple requests initiated after a long
+ // period of inactivity will likely see a stale version of the PAC script
+ // until the background polling gets a chance to update things.
+ class NET_EXPORT_PRIVATE PacPollPolicy {
+ public:
+ enum Mode {
+ MODE_USE_TIMER,
+ MODE_START_AFTER_ACTIVITY,
+ };
+
+ virtual ~PacPollPolicy() {}
+
+ // Decides the next poll delay. |current_delay| is the delay used
+ // by the preceding poll, or a negative TimeDelta value if determining
+ // the delay for the initial poll. |initial_error| is the network error
+ // code that the last PAC fetch (or WPAD initialization) failed with,
+ // or OK if it completed successfully. Implementations must set
+ // |next_delay| to a non-negative value.
+ virtual Mode GetNextDelay(int initial_error,
+ base::TimeDelta current_delay,
+ base::TimeDelta* next_delay) const = 0;
+ };
+
+ // The instance takes ownership of |config_service| and |resolver|.
+ // |net_log| is a possibly NULL destination to send log events to. It must
+ // remain alive for the lifetime of this ProxyService.
+ ProxyService(ProxyConfigService* config_service,
+ ProxyResolver* resolver,
+ NetLog* net_log);
+
+ virtual ~ProxyService();
+
+ // Used internally to handle PAC queries.
+ // TODO(eroman): consider naming this simply "Request".
+ class PacRequest;
+
+ // Returns ERR_IO_PENDING if the proxy information could not be provided
+ // synchronously, to indicate that the result will be available when the
+ // callback is run. The callback is run on the thread that calls
+ // ResolveProxy.
+ //
+ // The caller is responsible for ensuring that |results| and |callback|
+ // remain valid until the callback is run or until |pac_request| is cancelled
+ // via CancelPacRequest. |pac_request| is only valid while the completion
+ // callback is still pending. NULL can be passed for |pac_request| if
+ // the caller will not need to cancel the request.
+ //
+ // We use the three possible proxy access types in the following order,
+ // doing fallback if one doesn't work. See "pac_script_decider.h"
+ // for the specifics.
+ // 1. WPAD auto-detection
+ // 2. PAC URL
+ // 3. named proxy
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ int ResolveProxy(const GURL& url,
+ ProxyInfo* results,
+ const net::CompletionCallback& callback,
+ PacRequest** pac_request,
+ const BoundNetLog& net_log);
+
+ // This method is called after a failure to connect or resolve a host name.
+ // It gives the proxy service an opportunity to reconsider the proxy to use.
+ // The |results| parameter contains the results returned by an earlier call
+ // to ResolveProxy. The semantics of this call are otherwise similar to
+ // ResolveProxy.
+ //
+ // NULL can be passed for |pac_request| if the caller will not need to
+ // cancel the request.
+ //
+ // Returns ERR_FAILED if there is not another proxy config to try.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ int ReconsiderProxyAfterError(const GURL& url,
+ ProxyInfo* results,
+ const CompletionCallback& callback,
+ PacRequest** pac_request,
+ const BoundNetLog& net_log);
+
+ // Explicitly trigger proxy fallback for the given |results| by updating our
+ // list of bad proxies to include the first entry of |results|. Returns true
+ // if there will be at least one proxy remaining in the list after fallback
+ // and false otherwise.
+ bool MarkProxyAsBad(const ProxyInfo& results, const BoundNetLog& net_log);
+
+ // Called to report that the last proxy connection succeeded. If |proxy_info|
+ // has a non empty proxy_retry_info map, the proxies that have been tried (and
+ // failed) for this request will be marked as bad.
+ void ReportSuccess(const ProxyInfo& proxy_info);
+
+ // Call this method with a non-null |pac_request| to cancel the PAC request.
+ void CancelPacRequest(PacRequest* pac_request);
+
+ // Returns the LoadState for this |pac_request| which must be non-NULL.
+ LoadState GetLoadState(const PacRequest* pac_request) const;
+
+ // Sets the ProxyScriptFetcher and DhcpProxyScriptFetcher dependencies. This
+ // is needed if the ProxyResolver is of type ProxyResolverWithoutFetch.
+ // ProxyService takes ownership of both objects.
+ void SetProxyScriptFetchers(
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher);
+ ProxyScriptFetcher* GetProxyScriptFetcher() const;
+
+ // Tells this ProxyService to start using a new ProxyConfigService to
+ // retrieve its ProxyConfig from. The new ProxyConfigService will immediately
+ // be queried for new config info which will be used for all subsequent
+ // ResolveProxy calls. ProxyService takes ownership of
+ // |new_proxy_config_service|.
+ void ResetConfigService(ProxyConfigService* new_proxy_config_service);
+
+ // Tells the resolver to purge any memory it does not need.
+ void PurgeMemory();
+
+
+ // Returns the last configuration fetched from ProxyConfigService.
+ const ProxyConfig& fetched_config() {
+ return fetched_config_;
+ }
+
+ // Returns the current configuration being used by ProxyConfigService.
+ const ProxyConfig& config() {
+ return config_;
+ }
+
+ // Returns the map of proxies which have been marked as "bad".
+ const ProxyRetryInfoMap& proxy_retry_info() const {
+ return proxy_retry_info_;
+ }
+
+ // Clears the list of bad proxy servers that has been cached.
+ void ClearBadProxiesCache() {
+ proxy_retry_info_.clear();
+ }
+
+ // Forces refetching the proxy configuration, and applying it.
+ // This re-does everything from fetching the system configuration,
+ // to downloading and testing the PAC files.
+ void ForceReloadProxyConfig();
+
+ // Same as CreateProxyServiceUsingV8ProxyResolver, except it uses system
+ // libraries for evaluating the PAC script if available, otherwise skips
+ // proxy autoconfig.
+ static ProxyService* CreateUsingSystemProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ size_t num_pac_threads,
+ NetLog* net_log);
+
+ // Creates a ProxyService without support for proxy autoconfig.
+ static ProxyService* CreateWithoutProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ NetLog* net_log);
+
+ // Convenience methods that creates a proxy service using the
+ // specified fixed settings.
+ static ProxyService* CreateFixed(const ProxyConfig& pc);
+ static ProxyService* CreateFixed(const std::string& proxy);
+
+ // Creates a proxy service that uses a DIRECT connection for all requests.
+ static ProxyService* CreateDirect();
+ // |net_log|'s lifetime must exceed ProxyService.
+ static ProxyService* CreateDirectWithNetLog(NetLog* net_log);
+
+ // This method is used by tests to create a ProxyService that returns a
+ // hardcoded proxy fallback list (|pac_string|) for every URL.
+ //
+ // |pac_string| is a list of proxy servers, in the format that a PAC script
+ // would return it. For example, "PROXY foobar:99; SOCKS fml:2; DIRECT"
+ static ProxyService* CreateFixedFromPacResult(const std::string& pac_string);
+
+ // Creates a config service appropriate for this platform that fetches the
+ // system proxy settings.
+ static ProxyConfigService* CreateSystemProxyConfigService(
+ base::SingleThreadTaskRunner* io_thread_task_runner,
+ base::MessageLoop* file_loop);
+
+ // This method should only be used by unit tests.
+ void set_stall_proxy_auto_config_delay(base::TimeDelta delay) {
+ stall_proxy_auto_config_delay_ = delay;
+ }
+
+ // This method should only be used by unit tests. Returns the previously
+ // active policy.
+ static const PacPollPolicy* set_pac_script_poll_policy(
+ const PacPollPolicy* policy);
+
+ // This method should only be used by unit tests. Creates an instance
+ // of the default internal PacPollPolicy used by ProxyService.
+ static scoped_ptr<PacPollPolicy> CreateDefaultPacPollPolicy();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigAfterFailedAutodetect);
+ FRIEND_TEST_ALL_PREFIXES(ProxyServiceTest, UpdateConfigFromPACToDirect);
+ friend class PacRequest;
+ class InitProxyResolver;
+ class ProxyScriptDeciderPoller;
+
+ // TODO(eroman): change this to a std::set. Note that this requires updating
+ // some tests in proxy_service_unittest.cc such as:
+ // ProxyServiceTest.InitialPACScriptDownload
+ // which expects requests to finish in the order they were added.
+ typedef std::vector<scoped_refptr<PacRequest> > PendingRequests;
+
+ enum State {
+ STATE_NONE,
+ STATE_WAITING_FOR_PROXY_CONFIG,
+ STATE_WAITING_FOR_INIT_PROXY_RESOLVER,
+ STATE_READY,
+ };
+
+ // Resets all the variables associated with the current proxy configuration,
+ // and rewinds the current state to |STATE_NONE|. Returns the previous value
+ // of |current_state_|. If |reset_fetched_config| is true then
+ // |fetched_config_| will also be reset, otherwise it will be left as-is.
+ // Resetting it means that we will have to re-fetch the configuration from
+ // the ProxyConfigService later.
+ State ResetProxyConfig(bool reset_fetched_config);
+
+ // Retrieves the current proxy configuration from the ProxyConfigService, and
+ // starts initializing for it.
+ void ApplyProxyConfigIfAvailable();
+
+ // Callback for when the proxy resolver has been initialized with a
+ // PAC script.
+ void OnInitProxyResolverComplete(int result);
+
+ // Returns ERR_IO_PENDING if the request cannot be completed synchronously.
+ // Otherwise it fills |result| with the proxy information for |url|.
+ // Completing synchronously means we don't need to query ProxyResolver.
+ int TryToCompleteSynchronously(const GURL& url, ProxyInfo* result);
+
+ // Cancels all of the requests sent to the ProxyResolver. These will be
+ // restarted when calling SetReady().
+ void SuspendAllPendingRequests();
+
+ // Advances the current state to |STATE_READY|, and resumes any pending
+ // requests which had been stalled waiting for initialization to complete.
+ void SetReady();
+
+ // Returns true if |pending_requests_| contains |req|.
+ bool ContainsPendingRequest(PacRequest* req);
+
+ // Removes |req| from the list of pending requests.
+ void RemovePendingRequest(PacRequest* req);
+
+ // Called when proxy resolution has completed (either synchronously or
+ // asynchronously). Handles logging the result, and cleaning out
+ // bad entries from the results list.
+ int DidFinishResolvingProxy(ProxyInfo* result,
+ int result_code,
+ const BoundNetLog& net_log);
+
+ // Start initialization using |fetched_config_|.
+ void InitializeUsingLastFetchedConfig();
+
+ // Start the initialization skipping past the "decision" phase.
+ void InitializeUsingDecidedConfig(
+ int decider_result,
+ ProxyResolverScriptData* script_data,
+ const ProxyConfig& effective_config);
+
+ // NetworkChangeNotifier::IPAddressObserver
+ // When this is called, we re-fetch PAC scripts and re-run WPAD.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // NetworkChangeNotifier::DNSObserver
+ // We respond as above.
+ virtual void OnDNSChanged() OVERRIDE;
+
+ // ProxyConfigService::Observer
+ virtual void OnProxyConfigChanged(
+ const ProxyConfig& config,
+ ProxyConfigService::ConfigAvailability availability) OVERRIDE;
+
+ scoped_ptr<ProxyConfigService> config_service_;
+ scoped_ptr<ProxyResolver> resolver_;
+
+ // We store the proxy configuration that was last fetched from the
+ // ProxyConfigService, as well as the resulting "effective" configuration.
+ // The effective configuration is what we condense the original fetched
+ // settings to after testing the various automatic settings (auto-detect
+ // and custom PAC url).
+ ProxyConfig fetched_config_;
+ ProxyConfig config_;
+
+ // Increasing ID to give to the next ProxyConfig that we set.
+ int next_config_id_;
+
+ // The time when the proxy configuration was last read from the system.
+ base::TimeTicks config_last_update_time_;
+
+ // Map of the known bad proxies and the information about the retry time.
+ ProxyRetryInfoMap proxy_retry_info_;
+
+ // Set of pending/inprogress requests.
+ PendingRequests pending_requests_;
+
+ // The fetcher to use when downloading PAC scripts for the ProxyResolver.
+ // This dependency can be NULL if our ProxyResolver has no need for
+ // external PAC script fetching.
+ scoped_ptr<ProxyScriptFetcher> proxy_script_fetcher_;
+
+ // The fetcher to use when attempting to download the most appropriate PAC
+ // script configured in DHCP, if any. Can be NULL if the ProxyResolver has
+ // no need for DHCP PAC script fetching.
+ scoped_ptr<DhcpProxyScriptFetcher> dhcp_proxy_script_fetcher_;
+
+ // Helper to download the PAC script (wpad + custom) and apply fallback rules.
+ //
+ // Note that the declaration is important here: |proxy_script_fetcher_| and
+ // |proxy_resolver_| must outlive |init_proxy_resolver_|.
+ scoped_ptr<InitProxyResolver> init_proxy_resolver_;
+
+ // Helper to poll the PAC script for changes.
+ scoped_ptr<ProxyScriptDeciderPoller> script_poller_;
+
+ State current_state_;
+
+ // Either OK or an ERR_* value indicating that a permanent error (e.g.
+ // failed to fetch the PAC script) prevents proxy resolution.
+ int permanent_error_;
+
+ // This is the log where any events generated by |init_proxy_resolver_| are
+ // sent to.
+ NetLog* net_log_;
+
+ // The earliest time at which we should run any proxy auto-config. (Used to
+ // stall re-configuration following an IP address change).
+ base::TimeTicks stall_proxy_autoconfig_until_;
+
+ // The amount of time to stall requests following IP address changes.
+ base::TimeDelta stall_proxy_auto_config_delay_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyService);
+};
+
+// Wrapper for invoking methods on a ProxyService synchronously.
+class NET_EXPORT SyncProxyServiceHelper
+ : public base::RefCountedThreadSafe<SyncProxyServiceHelper> {
+ public:
+ SyncProxyServiceHelper(base::MessageLoop* io_message_loop,
+ ProxyService* proxy_service);
+
+ int ResolveProxy(const GURL& url,
+ ProxyInfo* proxy_info,
+ const BoundNetLog& net_log);
+ int ReconsiderProxyAfterError(const GURL& url,
+ ProxyInfo* proxy_info,
+ const BoundNetLog& net_log);
+
+ private:
+ friend class base::RefCountedThreadSafe<SyncProxyServiceHelper>;
+
+ virtual ~SyncProxyServiceHelper();
+
+ void StartAsyncResolve(const GURL& url, const BoundNetLog& net_log);
+ void StartAsyncReconsider(const GURL& url, const BoundNetLog& net_log);
+
+ void OnCompletion(int result);
+
+ base::MessageLoop* io_message_loop_;
+ ProxyService* proxy_service_;
+
+ base::WaitableEvent event_;
+ CompletionCallback callback_;
+ ProxyInfo proxy_info_;
+ int result_;
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SERVICE_H_
diff --git a/chromium/net/proxy/proxy_service_unittest.cc b/chromium/net/proxy/proxy_service_unittest.cc
new file mode 100644
index 00000000000..c8551b05e84
--- /dev/null
+++ b/chromium/net/proxy/proxy_service_unittest.cc
@@ -0,0 +1,2791 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_service.h"
+
+#include <vector>
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/proxy/dhcp_proxy_script_fetcher.h"
+#include "net/proxy/mock_proxy_resolver.h"
+#include "net/proxy/mock_proxy_script_fetcher.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_script_fetcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// TODO(eroman): Write a test which exercises
+// ProxyService::SuspendAllPendingRequests().
+namespace net {
+namespace {
+
+// This polling policy will decide to poll every 1 ms.
+class ImmediatePollPolicy : public ProxyService::PacPollPolicy {
+ public:
+ ImmediatePollPolicy() {}
+
+ virtual Mode GetNextDelay(int error, base::TimeDelta current_delay,
+ base::TimeDelta* next_delay) const OVERRIDE {
+ *next_delay = base::TimeDelta::FromMilliseconds(1);
+ return MODE_USE_TIMER;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ImmediatePollPolicy);
+};
+
+// This polling policy chooses a fantastically large delay. In other words, it
+// will never trigger a poll
+class NeverPollPolicy : public ProxyService::PacPollPolicy {
+ public:
+ NeverPollPolicy() {}
+
+ virtual Mode GetNextDelay(int error, base::TimeDelta current_delay,
+ base::TimeDelta* next_delay) const OVERRIDE {
+ *next_delay = base::TimeDelta::FromDays(60);
+ return MODE_USE_TIMER;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NeverPollPolicy);
+};
+
+// This polling policy starts a poll immediately after network activity.
+class ImmediateAfterActivityPollPolicy : public ProxyService::PacPollPolicy {
+ public:
+ ImmediateAfterActivityPollPolicy() {}
+
+ virtual Mode GetNextDelay(int error, base::TimeDelta current_delay,
+ base::TimeDelta* next_delay) const OVERRIDE {
+ *next_delay = base::TimeDelta();
+ return MODE_START_AFTER_ACTIVITY;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ImmediateAfterActivityPollPolicy);
+};
+
+// This test fixture is used to partially disable the background polling done by
+// the ProxyService (which it uses to detect whenever its PAC script contents or
+// WPAD results have changed).
+//
+// We disable the feature by setting the poll interval to something really
+// large, so it will never actually be reached even on the slowest bots that run
+// these tests.
+//
+// We disable the polling in order to avoid any timing dependencies in the
+// tests. If the bot were to run the tests very slowly and we hadn't disabled
+// polling, then it might start a background re-try in the middle of our test
+// and confuse our expectations leading to flaky failures.
+//
+// The tests which verify the polling code re-enable the polling behavior but
+// are careful to avoid timing problems.
+class ProxyServiceTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+ previous_policy_ =
+ ProxyService::set_pac_script_poll_policy(&never_poll_policy_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Restore the original policy.
+ ProxyService::set_pac_script_poll_policy(previous_policy_);
+ testing::Test::TearDown();
+ }
+
+ private:
+ NeverPollPolicy never_poll_policy_;
+ const ProxyService::PacPollPolicy* previous_policy_;
+};
+
+const char kValidPacScript1[] = "pac-script-v1-FindProxyForURL";
+const char kValidPacScript2[] = "pac-script-v2-FindProxyForURL";
+
+class MockProxyConfigService: public ProxyConfigService {
+ public:
+ explicit MockProxyConfigService(const ProxyConfig& config)
+ : availability_(CONFIG_VALID),
+ config_(config) {
+ }
+
+ explicit MockProxyConfigService(const std::string& pac_url)
+ : availability_(CONFIG_VALID),
+ config_(ProxyConfig::CreateFromCustomPacURL(GURL(pac_url))) {
+ }
+
+ virtual void AddObserver(Observer* observer) OVERRIDE {
+ observers_.AddObserver(observer);
+ }
+
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {
+ observers_.RemoveObserver(observer);
+ }
+
+ virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* results)
+ OVERRIDE {
+ if (availability_ == CONFIG_VALID)
+ *results = config_;
+ return availability_;
+ }
+
+ void SetConfig(const ProxyConfig& config) {
+ availability_ = CONFIG_VALID;
+ config_ = config;
+ FOR_EACH_OBSERVER(Observer, observers_,
+ OnProxyConfigChanged(config_, availability_));
+ }
+
+ private:
+ ConfigAvailability availability_;
+ ProxyConfig config_;
+ ObserverList<Observer, true> observers_;
+};
+
+} // namespace
+
+TEST_F(ProxyServiceTest, Direct) {
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+ ProxyService service(new MockProxyConfigService(
+ ProxyConfig::CreateDirect()), resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ CapturingBoundNetLog log;
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), NULL, log.bound());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ EXPECT_TRUE(info.is_direct());
+ EXPECT_TRUE(info.proxy_resolve_start_time().is_null());
+ EXPECT_TRUE(info.proxy_resolve_end_time().is_null());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(3u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 1, NetLog::TYPE_PROXY_SERVICE_RESOLVED_PROXY_LIST,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SERVICE));
+}
+
+TEST_F(ProxyServiceTest, PAC) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ ProxyService::PacRequest* request;
+ CapturingBoundNetLog log;
+
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), &request, log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request));
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("foopy");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy:80", info.proxy_server().ToURI());
+ EXPECT_TRUE(info.did_use_pac_script());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+
+ // Check the NetLog was filled correctly.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(5u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 4, NetLog::TYPE_PROXY_SERVICE));
+}
+
+// Test that the proxy resolver does not see the URL's username/password
+// or its reference section.
+TEST_F(ProxyServiceTest, PAC_NoIdentityOrHash) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://username:password@www.google.com/?ref#hash#hash");
+
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ // The URL should have been simplified, stripping the username/password/hash.
+ EXPECT_EQ(GURL("http://www.google.com/?ref"),
+ resolver->pending_requests()[0]->url());
+
+ // We end here without ever completing the request -- destruction of
+ // ProxyService will cancel the outstanding request.
+}
+
+TEST_F(ProxyServiceTest, PAC_FailoverWithoutDirect) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("foopy:8080");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy:8080", info.proxy_server().ToURI());
+ EXPECT_TRUE(info.did_use_pac_script());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+
+ // Now, imagine that connecting to foopy:8080 fails: there is nothing
+ // left to fallback to, since our proxy list was NOT terminated by
+ // DIRECT.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(
+ url, &info, callback2.callback(), NULL, BoundNetLog());
+ // ReconsiderProxyAfterError returns error indicating nothing left.
+ EXPECT_EQ(ERR_FAILED, rv);
+ EXPECT_TRUE(info.is_empty());
+}
+
+// Test that if the execution of the PAC script fails (i.e. javascript runtime
+// error), and the PAC settings are non-mandatory, that we fall-back to direct.
+TEST_F(ProxyServiceTest, PAC_RuntimeError) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://this-causes-js-error/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Simulate a failure in the PAC executor.
+ resolver->pending_requests()[0]->CompleteNow(ERR_PAC_SCRIPT_FAILED);
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+
+ // Since the PAC script was non-mandatory, we should have fallen-back to
+ // DIRECT.
+ EXPECT_TRUE(info.is_direct());
+ EXPECT_TRUE(info.did_use_pac_script());
+ EXPECT_EQ(1, info.config_id());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+}
+
+// The proxy list could potentially contain the DIRECT fallback choice
+// in a location other than the very end of the list, and could even
+// specify it multiple times.
+//
+// This is not a typical usage, but we will obey it.
+// (If we wanted to disallow this type of input, the right place to
+// enforce it would be in parsing the PAC result string).
+//
+// This test will use the PAC result string:
+//
+// "DIRECT ; PROXY foobar:10 ; DIRECT ; PROXY foobar:20"
+//
+// For which we expect it to try DIRECT, then foobar:10, then DIRECT again,
+// then foobar:20, and then give up and error.
+//
+// The important check of this test is to make sure that DIRECT is not somehow
+// cached as being a bad proxy.
+TEST_F(ProxyServiceTest, PAC_FailoverAfterDirect) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UsePacString(
+ "DIRECT ; PROXY foobar:10 ; DIRECT ; PROXY foobar:20");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(info.is_direct());
+
+ // Fallback 1.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foobar:10", info.proxy_server().ToURI());
+
+ // Fallback 2.
+ TestCompletionCallback callback3;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback3.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info.is_direct());
+
+ // Fallback 3.
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback4.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foobar:20", info.proxy_server().ToURI());
+
+ // Fallback 4 -- Nothing to fall back to!
+ TestCompletionCallback callback5;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback5.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_FAILED, rv);
+ EXPECT_TRUE(info.is_empty());
+}
+
+TEST_F(ProxyServiceTest, PAC_ConfigSourcePropagates) {
+ // Test whether the ProxyConfigSource set by the ProxyConfigService is applied
+ // to ProxyInfo after the proxy is resolved via a PAC script.
+ ProxyConfig config =
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"));
+ config.set_source(PROXY_CONFIG_SOURCE_TEST);
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+ ProxyService service(config_service, resolver, NULL);
+
+ // Resolve something.
+ GURL url("http://www.google.com/");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), NULL, BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("foopy");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source());
+ EXPECT_TRUE(info.did_use_pac_script());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+}
+
+TEST_F(ProxyServiceTest, ProxyResolverFails) {
+ // Test what happens when the ProxyResolver fails. The download and setting
+ // of the PAC script have already succeeded, so this corresponds with a
+ // javascript runtime error while calling FindProxyForURL().
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ // Start first resolve request.
+ GURL url("http://www.google.com/");
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Fail the first resolve request in MockAsyncProxyResolver.
+ resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
+
+ // Although the proxy resolver failed the request, ProxyService implicitly
+ // falls-back to DIRECT.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(info.is_direct());
+
+ // Failed PAC executions still have proxy resolution times.
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+
+ // The second resolve request will try to run through the proxy resolver,
+ // regardless of whether the first request failed in it.
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ url, &info, callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // This time we will have the resolver succeed (perhaps the PAC script has
+ // a dependency on the current time).
+ resolver->pending_requests()[0]->results()->UseNamedProxy("foopy_valid:8080");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI());
+}
+
+TEST_F(ProxyServiceTest, ProxyScriptFetcherFailsDownloadingMandatoryPac) {
+ // Test what happens when the ProxyScriptResolver fails to download a
+ // mandatory PAC script.
+
+ ProxyConfig config(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")));
+ config.set_pac_mandatory(true);
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ // Start first resolve request.
+ GURL url("http://www.google.com/");
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(ERR_FAILED);
+
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // As the proxy resolver failed the request and is configured for a mandatory
+ // PAC script, ProxyService must not implicitly fall-back to DIRECT.
+ EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED,
+ callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+
+ // As the proxy resolver failed the request and is configured for a mandatory
+ // PAC script, ProxyService must not implicitly fall-back to DIRECT.
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ url, &info, callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, rv);
+ EXPECT_FALSE(info.is_direct());
+}
+
+TEST_F(ProxyServiceTest, ProxyResolverFailsParsingJavaScriptMandatoryPac) {
+ // Test what happens when the ProxyResolver fails that is configured to use a
+ // mandatory PAC script. The download of the PAC script has already
+ // succeeded but the PAC script contains no valid javascript.
+
+ ProxyConfig config(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")));
+ config.set_pac_mandatory(true);
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ DhcpProxyScriptFetcher* dhcp_fetcher = new DoNothingDhcpProxyScriptFetcher();
+ service.SetProxyScriptFetchers(fetcher, dhcp_fetcher);
+
+ // Start resolve request.
+ GURL url("http://www.google.com/");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // Downloading the PAC script succeeds.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, "invalid-script-contents");
+
+ EXPECT_FALSE(fetcher->has_pending_request());
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // Since ProxyScriptDecider failed to identify a valid PAC and PAC was
+ // mandatory for this configuration, the ProxyService must not implicitly
+ // fall-back to DIRECT.
+ EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED,
+ callback.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+}
+
+TEST_F(ProxyServiceTest, ProxyResolverFailsInJavaScriptMandatoryPac) {
+ // Test what happens when the ProxyResolver fails that is configured to use a
+ // mandatory PAC script. The download and setting of the PAC script have
+ // already succeeded, so this corresponds with a javascript runtime error
+ // while calling FindProxyForURL().
+
+ ProxyConfig config(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")));
+ config.set_pac_mandatory(true);
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ // Start first resolve request.
+ GURL url("http://www.google.com/");
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Fail the first resolve request in MockAsyncProxyResolver.
+ resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
+
+ // As the proxy resolver failed the request and is configured for a mandatory
+ // PAC script, ProxyService must not implicitly fall-back to DIRECT.
+ EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED,
+ callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+
+ // The second resolve request will try to run through the proxy resolver,
+ // regardless of whether the first request failed in it.
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ url, &info, callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // This time we will have the resolver succeed (perhaps the PAC script has
+ // a dependency on the current time).
+ resolver->pending_requests()[0]->results()->UseNamedProxy("foopy_valid:8080");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy_valid:8080", info.proxy_server().ToURI());
+}
+
+TEST_F(ProxyServiceTest, ProxyFallback) {
+ // Test what happens when we specify multiple proxy servers and some of them
+ // are bad.
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first item is valid.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+ base::TimeTicks proxy_resolve_start_time = info.proxy_resolve_start_time();
+ base::TimeTicks proxy_resolve_end_time = info.proxy_resolve_end_time();
+
+ // Fake an error on the proxy.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ // Proxy times should not have been modified by fallback.
+ EXPECT_EQ(proxy_resolve_start_time, info.proxy_resolve_start_time());
+ EXPECT_EQ(proxy_resolve_end_time, info.proxy_resolve_end_time());
+
+ // The second proxy should be specified.
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+ // Report back that the second proxy worked. This will globally mark the
+ // first proxy as bad.
+ service.ReportSuccess(info);
+
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(
+ url, &info, callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver -- the second result is already known
+ // to be bad, so we will not try to use it initially.
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy3:7070;foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy3:7070", info.proxy_server().ToURI());
+
+ // Proxy times should have been updated, so get them again.
+ EXPECT_LE(proxy_resolve_end_time, info.proxy_resolve_start_time());
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+ proxy_resolve_start_time = info.proxy_resolve_start_time();
+ proxy_resolve_end_time = info.proxy_resolve_end_time();
+
+ // We fake another error. It should now try the third one.
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback4.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+
+ // We fake another error. At this point we have tried all of the
+ // proxy servers we thought were valid; next we try the proxy server
+ // that was in our bad proxies map (foopy1:8080).
+ TestCompletionCallback callback5;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback5.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // Fake another error, the last proxy is gone, the list should now be empty,
+ // so there is nothing left to try.
+ TestCompletionCallback callback6;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback6.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_FAILED, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_TRUE(info.is_empty());
+
+ // Proxy times should not have been modified by fallback.
+ EXPECT_EQ(proxy_resolve_start_time, info.proxy_resolve_start_time());
+ EXPECT_EQ(proxy_resolve_end_time, info.proxy_resolve_end_time());
+
+ // Look up proxies again
+ TestCompletionCallback callback7;
+ rv = service.ResolveProxy(url, &info, callback7.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // This time, the first 3 results have been found to be bad, but only the
+ // first proxy has been confirmed ...
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy3:7070;foopy2:9090;foopy4:9091");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // ... therefore, we should see the second proxy first.
+ EXPECT_EQ(OK, callback7.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy3:7070", info.proxy_server().ToURI());
+
+ EXPECT_LE(proxy_resolve_end_time, info.proxy_resolve_start_time());
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ // TODO(nsylvain): Test that the proxy can be retried after the delay.
+}
+
+// This test is similar to ProxyFallback, but this time we have an explicit
+// fallback choice to DIRECT.
+TEST_F(ProxyServiceTest, ProxyFallbackToDirect) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UsePacString(
+ "PROXY foopy1:8080; PROXY foopy2:9090; DIRECT");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Get the first result.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // Fake an error on the proxy.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ // Now we get back the second proxy.
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+
+ // Fake an error on this proxy as well.
+ TestCompletionCallback callback3;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback3.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ // Finally, we get back DIRECT.
+ EXPECT_TRUE(info.is_direct());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+
+ // Now we tell the proxy service that even DIRECT failed.
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback4.callback(), NULL,
+ BoundNetLog());
+ // There was nothing left to try after DIRECT, so we are out of
+ // choices.
+ EXPECT_EQ(ERR_FAILED, rv);
+}
+
+TEST_F(ProxyServiceTest, ProxyFallback_NewSettings) {
+ // Test proxy failover when new settings are available.
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // Set the result in proxy resolver.
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first item is valid.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // Fake an error on the proxy, and also a new configuration on the proxy.
+ config_service->SetConfig(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy-new/proxy.pac")));
+
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy-new/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first proxy is still there since the configuration changed.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // We fake another error. It should now ignore the first one.
+ TestCompletionCallback callback3;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback3.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+
+ // We simulate a new configuration.
+ config_service->SetConfig(
+ ProxyConfig::CreateFromCustomPacURL(
+ GURL("http://foopy-new2/proxy.pac")));
+
+ // We fake another error. It should go back to the first proxy.
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback4.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy-new2/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback4.WaitForResult());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+}
+
+TEST_F(ProxyServiceTest, ProxyFallback_BadConfig) {
+ // Test proxy failover when the configuration is bad.
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first item is valid.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // Fake a proxy error.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ // The first proxy is ignored, and the second one is selected.
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+
+ // Fake a PAC failure.
+ ProxyInfo info2;
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(
+ url, &info2, callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // This simulates a javascript runtime error in the PAC script.
+ resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
+
+ // Although the resolver failed, the ProxyService will implicitly fall-back
+ // to a DIRECT connection.
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_TRUE(info2.is_direct());
+ EXPECT_FALSE(info2.is_empty());
+
+ // The PAC script will work properly next time and successfully return a
+ // proxy list. Since we have not marked the configuration as bad, it should
+ // "just work" the next time we call it.
+ ProxyInfo info3;
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info3, callback4.callback(),
+ NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first proxy is not there since the it was added to the bad proxies
+ // list by the earlier ReconsiderProxyAfterError().
+ EXPECT_EQ(OK, callback4.WaitForResult());
+ EXPECT_FALSE(info3.is_direct());
+ EXPECT_EQ("foopy1:8080", info3.proxy_server().ToURI());
+
+ EXPECT_FALSE(info.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info.proxy_resolve_start_time(), info.proxy_resolve_end_time());
+}
+
+TEST_F(ProxyServiceTest, ProxyFallback_BadConfigMandatory) {
+ // Test proxy failover when the configuration is bad.
+
+ ProxyConfig config(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac")));
+
+ config.set_pac_mandatory(true);
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ // Get the proxy information.
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ url, &info, callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first item is valid.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ // Fake a proxy error.
+ TestCompletionCallback callback2;
+ rv = service.ReconsiderProxyAfterError(url, &info, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ // The first proxy is ignored, and the second one is selected.
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy2:9090", info.proxy_server().ToURI());
+
+ // Fake a PAC failure.
+ ProxyInfo info2;
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(
+ url, &info2, callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ // This simulates a javascript runtime error in the PAC script.
+ resolver->pending_requests()[0]->CompleteNow(ERR_FAILED);
+
+ // Although the resolver failed, the ProxyService will NOT fall-back
+ // to a DIRECT connection as it is configured as mandatory.
+ EXPECT_EQ(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED,
+ callback3.WaitForResult());
+ EXPECT_FALSE(info2.is_direct());
+ EXPECT_TRUE(info2.is_empty());
+
+ // The PAC script will work properly next time and successfully return a
+ // proxy list. Since we have not marked the configuration as bad, it should
+ // "just work" the next time we call it.
+ ProxyInfo info3;
+ TestCompletionCallback callback4;
+ rv = service.ReconsiderProxyAfterError(url, &info3, callback4.callback(),
+ NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(url, resolver->pending_requests()[0]->url());
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy(
+ "foopy1:8080;foopy2:9090");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // The first proxy is not there since the it was added to the bad proxies
+ // list by the earlier ReconsiderProxyAfterError().
+ EXPECT_EQ(OK, callback4.WaitForResult());
+ EXPECT_FALSE(info3.is_direct());
+ EXPECT_EQ("foopy1:8080", info3.proxy_server().ToURI());
+}
+
+TEST_F(ProxyServiceTest, ProxyBypassList) {
+ // Test that the proxy bypass rules are consulted.
+
+ TestCompletionCallback callback[2];
+ ProxyInfo info[2];
+ ProxyConfig config;
+ config.proxy_rules().ParseFromString("foopy1:8080;foopy2:9090");
+ config.set_auto_detect(false);
+ config.proxy_rules().bypass_rules.ParseFromString("*.org");
+
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+
+ int rv;
+ GURL url1("http://www.webkit.org");
+ GURL url2("http://www.webkit.com");
+
+ // Request for a .org domain should bypass proxy.
+ rv = service.ResolveProxy(
+ url1, &info[0], callback[0].callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info[0].is_direct());
+
+ // Request for a .com domain hits the proxy.
+ rv = service.ResolveProxy(
+ url2, &info[1], callback[1].callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy1:8080", info[1].proxy_server().ToURI());
+}
+
+
+TEST_F(ProxyServiceTest, PerProtocolProxyTests) {
+ ProxyConfig config;
+ config.proxy_rules().ParseFromString("http=foopy1:8080;https=foopy2:8080");
+ config.set_auto_detect(false);
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("http://www.msn.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+ }
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("ftp://ftp.google.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info.is_direct());
+ EXPECT_EQ("direct://", info.proxy_server().ToURI());
+ }
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("https://webbranch.techcu.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI());
+ }
+ {
+ config.proxy_rules().ParseFromString("foopy1:8080");
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("http://www.microsoft.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+ }
+}
+
+TEST_F(ProxyServiceTest, ProxyConfigSourcePropagates) {
+ // Test that the proxy config source is set correctly when resolving proxies
+ // using manual proxy rules. Namely, the config source should only be set if
+ // any of the rules were applied.
+ {
+ ProxyConfig config;
+ config.set_source(PROXY_CONFIG_SOURCE_TEST);
+ config.proxy_rules().ParseFromString("https=foopy2:8080");
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("http://www.google.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ ASSERT_EQ(OK, rv);
+ // Should be SOURCE_TEST, even if there are no HTTP proxies configured.
+ EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source());
+ }
+ {
+ ProxyConfig config;
+ config.set_source(PROXY_CONFIG_SOURCE_TEST);
+ config.proxy_rules().ParseFromString("https=foopy2:8080");
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("https://www.google.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ ASSERT_EQ(OK, rv);
+ // Used the HTTPS proxy. So source should be TEST.
+ EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source());
+ }
+ {
+ ProxyConfig config;
+ config.set_source(PROXY_CONFIG_SOURCE_TEST);
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("http://www.google.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ ASSERT_EQ(OK, rv);
+ // ProxyConfig is empty. Source should still be TEST.
+ EXPECT_EQ(PROXY_CONFIG_SOURCE_TEST, info.config_source());
+ }
+}
+
+// If only HTTP and a SOCKS proxy are specified, check if ftp/https queries
+// fall back to the SOCKS proxy.
+TEST_F(ProxyServiceTest, DefaultProxyFallbackToSOCKS) {
+ ProxyConfig config;
+ config.proxy_rules().ParseFromString("http=foopy1:8080;socks=foopy2:1080");
+ config.set_auto_detect(false);
+ EXPECT_EQ(ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME,
+ config.proxy_rules().type);
+
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("http://www.msn.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+ }
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("ftp://ftp.google.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
+ }
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("https://webbranch.techcu.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
+ }
+ {
+ ProxyService service(
+ new MockProxyConfigService(config), new MockAsyncProxyResolver, NULL);
+ GURL test_url("unknown://www.microsoft.com");
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(test_url, &info, callback.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_FALSE(info.is_direct());
+ EXPECT_EQ("socks4://foopy2:1080", info.proxy_server().ToURI());
+ }
+}
+
+// Test cancellation of an in-progress request.
+TEST_F(ProxyServiceTest, CancelInProgressRequest) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ // Start 3 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Nothing has been sent to the proxy resolver yet, since the proxy
+ // resolver has not been configured yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // Successfully initialize the PAC script.
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(2u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
+
+ ProxyInfo info3;
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(GURL("http://request3"), &info3,
+ callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(3u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[2]->url());
+
+ // Cancel the second request
+ service.CancelPacRequest(request2);
+
+ ASSERT_EQ(2u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+ EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[1]->url());
+
+ // Complete the two un-cancelled requests.
+ // We complete the last one first, just to mix it up a bit.
+ resolver->pending_requests()[1]->results()->UseNamedProxy("request3:80");
+ resolver->pending_requests()[1]->CompleteNow(OK);
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Complete and verify that requests ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ EXPECT_FALSE(callback2.have_result()); // Cancelled.
+ ASSERT_EQ(1u, resolver->cancelled_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->cancelled_requests()[0]->url());
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_EQ("request3:80", info3.proxy_server().ToURI());
+}
+
+// Test the initial PAC download for resolver that expects bytes.
+TEST_F(ProxyServiceTest, InitialPACScriptDownload) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 3 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ ProxyService::PacRequest* request1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), &request1, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyInfo info3;
+ TestCompletionCallback callback3;
+ ProxyService::PacRequest* request3;
+ rv = service.ResolveProxy(GURL("http://request3"), &info3,
+ callback3.callback(), &request3, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT,
+ service.GetLoadState(request1));
+ EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT,
+ service.GetLoadState(request2));
+ EXPECT_EQ(LOAD_STATE_DOWNLOADING_PROXY_SCRIPT,
+ service.GetLoadState(request3));
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, it will have been sent to the proxy
+ // resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(3u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
+ EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[2]->url());
+
+ EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request1));
+ EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request2));
+ EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, service.GetLoadState(request3));
+
+ // Complete all the requests (in some order).
+ // Note that as we complete requests, they shift up in |pending_requests()|.
+
+ resolver->pending_requests()[2]->results()->UseNamedProxy("request3:80");
+ resolver->pending_requests()[2]->CompleteNow(OK);
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Complete and verify that requests ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+ EXPECT_FALSE(info1.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info1.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info1.proxy_resolve_start_time(), info1.proxy_resolve_end_time());
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+ EXPECT_FALSE(info2.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info2.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info2.proxy_resolve_start_time(), info2.proxy_resolve_end_time());
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_EQ("request3:80", info3.proxy_server().ToURI());
+ EXPECT_FALSE(info3.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info3.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info3.proxy_resolve_start_time(), info3.proxy_resolve_end_time());
+}
+
+// Test changing the ProxyScriptFetcher while PAC download is in progress.
+TEST_F(ProxyServiceTest, ChangeScriptFetcherWhilePACDownloadInProgress) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 2 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+
+ // We now change out the ProxyService's script fetcher. We should restart
+ // the initialization with the new fetcher.
+
+ fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, it will have been sent to the proxy
+ // resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(2u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
+}
+
+// Test cancellation of a request, while the PAC script is being fetched.
+TEST_F(ProxyServiceTest, CancelWhilePACFetching) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 3 requests.
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ ProxyService::PacRequest* request1;
+ CapturingBoundNetLog log1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), &request1, log1.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyInfo info3;
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(GURL("http://request3"), &info3,
+ callback3.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // Cancel the first 2 requests.
+ service.CancelPacRequest(request1);
+ service.CancelPacRequest(request2);
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, it will have been sent to the
+ // proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request3"), resolver->pending_requests()[0]->url());
+
+ // Complete all the requests.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request3:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_EQ("request3:80", info3.proxy_server().ToURI());
+
+ EXPECT_TRUE(resolver->cancelled_requests().empty());
+
+ EXPECT_FALSE(callback1.have_result()); // Cancelled.
+ EXPECT_FALSE(callback2.have_result()); // Cancelled.
+
+ CapturingNetLog::CapturedEntryList entries1;
+ log1.GetEntries(&entries1);
+
+ // Check the NetLog for request 1 (which was cancelled) got filled properly.
+ EXPECT_EQ(4u, entries1.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries1, 0, NetLog::TYPE_PROXY_SERVICE));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries1, 1, NetLog::TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC));
+ // Note that TYPE_PROXY_SERVICE_WAITING_FOR_INIT_PAC is never completed before
+ // the cancellation occured.
+ EXPECT_TRUE(LogContainsEvent(
+ entries1, 2, NetLog::TYPE_CANCELLED, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries1, 3, NetLog::TYPE_PROXY_SERVICE));
+}
+
+// Test that if auto-detect fails, we fall-back to the custom pac.
+TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac) {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used.
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 2 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // It should be trying to auto-detect first -- FAIL the autodetect during
+ // the script download.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ // Next it should be trying the custom PAC url.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ // Now finally, the pending requests should have been sent to the resolver
+ // (which was initialized with custom PAC script).
+
+ ASSERT_EQ(2u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
+
+ // Complete the pending requests.
+ resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[1]->CompleteNow(OK);
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Verify that requests ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+ EXPECT_FALSE(info1.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info1.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info1.proxy_resolve_start_time(), info1.proxy_resolve_end_time());
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+ EXPECT_FALSE(info2.proxy_resolve_start_time().is_null());
+ EXPECT_FALSE(info2.proxy_resolve_end_time().is_null());
+ EXPECT_LE(info2.proxy_resolve_start_time(), info2.proxy_resolve_end_time());
+}
+
+// This is the same test as FallbackFromAutodetectToCustomPac, except
+// the auto-detect script fails parsing rather than downloading.
+TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomPac2) {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Won't be used.
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 2 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // It should be trying to auto-detect first -- succeed the download.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, "invalid-script-contents");
+
+ // The script contents passed failed basic verification step (since didn't
+ // contain token FindProxyForURL), so it was never passed to the resolver.
+
+ // Next it should be trying the custom PAC url.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ // Now finally, the pending requests should have been sent to the resolver
+ // (which was initialized with custom PAC script).
+
+ ASSERT_EQ(2u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[1]->url());
+
+ // Complete the pending requests.
+ resolver->pending_requests()[1]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[1]->CompleteNow(OK);
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Verify that requests ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+}
+
+// Test that if all of auto-detect, a custom PAC script, and manual settings
+// are given, then we will try them in that order.
+TEST_F(ProxyServiceTest, FallbackFromAutodetectToCustomToManual) {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80");
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 2 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ ProxyService::PacRequest* request2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), &request2, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // It should be trying to auto-detect first -- fail the download.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ // Next it should be trying the custom PAC url -- fail the download.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ // Since we never managed to initialize a ProxyResolver, nothing should have
+ // been sent to it.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // Verify that requests ran as expected -- they should have fallen back to
+ // the manual proxy configuration for HTTP urls.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("foopy:80", info1.proxy_server().ToURI());
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("foopy:80", info2.proxy_server().ToURI());
+}
+
+// Test that the bypass rules are NOT applied when using autodetect.
+TEST_F(ProxyServiceTest, BypassDoesntApplyToPac) {
+ ProxyConfig config;
+ config.set_auto_detect(true);
+ config.set_pac_url(GURL("http://foopy/proxy.pac"));
+ config.proxy_rules().ParseFromString("http=foopy:80"); // Not used.
+ config.proxy_rules().bypass_rules.ParseFromString("www.google.com");
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 requests.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://www.google.com"), &info1, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // It should be trying to auto-detect first -- succeed the download.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://wpad/wpad.dat"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://www.google.com"),
+ resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Verify that request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // Start another request, it should pickup the bypass item.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(GURL("http://www.google.com"), &info2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://www.google.com"),
+ resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+}
+
+// Delete the ProxyService while InitProxyResolver has an outstanding
+// request to the script fetcher. When run under valgrind, should not
+// have any memory errors (used to be that the ProxyScriptFetcher was
+// being deleted prior to the InitProxyResolver).
+TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingFetch) {
+ ProxyConfig config =
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foopy/proxy.pac"));
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://www.google.com"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // InitProxyResolver should have issued a request to the ProxyScriptFetcher
+ // and be waiting on that to complete.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+}
+
+// Delete the ProxyService while InitProxyResolver has an outstanding
+// request to the proxy resolver. When run under valgrind, should not
+// have any memory errors (used to be that the ProxyResolver was
+// being deleted prior to the InitProxyResolver).
+TEST_F(ProxyServiceTest, DeleteWhileInitProxyResolverHasOutstandingSet) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ GURL url("http://www.google.com/");
+
+ ProxyInfo info;
+ TestCompletionCallback callback;
+ int rv = service.ResolveProxy(
+ url, &info, callback.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"),
+ resolver->pending_set_pac_script_request()->script_data()->url());
+}
+
+TEST_F(ProxyServiceTest, ResetProxyConfigService) {
+ ProxyConfig config1;
+ config1.proxy_rules().ParseFromString("foopy1:8080");
+ config1.set_auto_detect(false);
+ ProxyService service(
+ new MockProxyConfigService(config1),
+ new MockAsyncProxyResolverExpectsBytes, NULL);
+
+ ProxyInfo info;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy1:8080", info.proxy_server().ToURI());
+
+ ProxyConfig config2;
+ config2.proxy_rules().ParseFromString("foopy2:8080");
+ config2.set_auto_detect(false);
+ service.ResetConfigService(new MockProxyConfigService(config2));
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("foopy2:8080", info.proxy_server().ToURI());
+}
+
+// Test that when going from a configuration that required PAC to one
+// that does NOT, we unset the variable |should_use_proxy_resolver_|.
+TEST_F(ProxyServiceTest, UpdateConfigFromPACToDirect) {
+ ProxyConfig config = ProxyConfig::CreateAutoDetect();
+
+ MockProxyConfigService* config_service = new MockProxyConfigService(config);
+ MockAsyncProxyResolver* resolver = new MockAsyncProxyResolver;
+ ProxyService service(config_service, resolver, NULL);
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://www.google.com"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that nothing has been sent to the proxy resolver yet.
+ ASSERT_EQ(0u, resolver->pending_requests().size());
+
+ // Successfully set the autodetect script.
+ EXPECT_EQ(ProxyResolverScriptData::TYPE_AUTO_DETECT,
+ resolver->pending_set_pac_script_request()->script_data()->type());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ // Complete the pending request.
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Verify that request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // Force the ProxyService to pull down a new proxy configuration.
+ // (Even though the configuration isn't old/bad).
+ //
+ // This new configuration no longer has auto_detect set, so
+ // requests should complete synchronously now as direct-connect.
+ config_service->SetConfig(ProxyConfig::CreateDirect());
+
+ // Start another request -- the effective configuration has changed.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(GURL("http://www.google.com"), &info2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_TRUE(info2.is_direct());
+}
+
+TEST_F(ProxyServiceTest, NetworkChangeTriggersPacRefetch) {
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ CapturingNetLog log;
+
+ ProxyService service(config_service, resolver, &log);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Disable the "wait after IP address changes" hack, so this unit-test can
+ // complete quickly.
+ service.set_stall_proxy_auto_config_delay(base::TimeDelta());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(GURL("http://request1"), &info1,
+ callback1.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, the request will have been sent to
+ // the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // Now simluate a change in the network. The ProxyConfigService is still
+ // going to return the same PAC URL as before, but this URL needs to be
+ // refetched on the new network.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(GURL("http://request2"), &info2,
+ callback2.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // This second request should have triggered the re-download of the PAC
+ // script (since we marked the network as having changed).
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // Simulate the PAC script fetch as having completed (this time with
+ // different data).
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript2);
+
+ // Now that the PAC script is downloaded, the second request will have been
+ // sent to the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript2),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending second request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+
+ // Check that the expected events were output to the log stream. In particular
+ // PROXY_CONFIG_CHANGED should have only been emitted once (for the initial
+ // setup), and NOT a second time when the IP address changed.
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_TRUE(LogContainsEntryWithType(entries, 0,
+ NetLog::TYPE_PROXY_CONFIG_CHANGED));
+ ASSERT_EQ(9u, entries.size());
+ for (size_t i = 1; i < entries.size(); ++i)
+ EXPECT_NE(NetLog::TYPE_PROXY_CONFIG_CHANGED, entries[i].type);
+}
+
+// This test verifies that the PAC script specified by the settings is
+// periodically polled for changes. Specifically, if the initial fetch fails due
+// to a network error, we will eventually re-configure the service to use the
+// script once it becomes available.
+TEST_F(ProxyServiceTest, PACScriptRefetchAfterFailure) {
+ // Change the retry policy to wait a mere 1 ms before retrying, so the test
+ // runs quickly.
+ ImmediatePollPolicy poll_policy;
+ ProxyService::set_pac_script_poll_policy(&poll_policy);
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://request1"), &info1, callback1.callback(),
+ NULL, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ //
+ // We simulate a failed download attempt, the proxy service should now
+ // fall-back to DIRECT connections.
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Wait for completion callback, and verify it used DIRECT.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_TRUE(info1.is_direct());
+
+ // At this point we have initialized the proxy service using a PAC script,
+ // however it failed and fell-back to DIRECT.
+ //
+ // A background task to periodically re-check the PAC script for validity will
+ // have been started. We will now wait for the next download attempt to start.
+ //
+ // Note that we shouldn't have to wait long here, since our test enables a
+ // special unit-test mode.
+ fetcher->WaitUntilFetch();
+
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Make sure that our background checker is trying to download the expected
+ // PAC script (same one as before). This time we will simulate a successful
+ // download of the script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now that the PAC script is downloaded, it should be used to initialize the
+ // ProxyResolver. Simulate a successful parse.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ // At this point the ProxyService should have re-configured itself to use the
+ // PAC script (thereby recovering from the initial fetch failure). We will
+ // verify that the next Resolve request uses the resolver rather than
+ // DIRECT.
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ GURL("http://request2"), &info2, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that it was sent to the resolver.
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending second request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+}
+
+// This test verifies that the PAC script specified by the settings is
+// periodically polled for changes. Specifically, if the initial fetch succeeds,
+// however at a later time its *contents* change, we will eventually
+// re-configure the service to use the new script.
+TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentChange) {
+ // Change the retry policy to wait a mere 1 ms before retrying, so the test
+ // runs quickly.
+ ImmediatePollPolicy poll_policy;
+ ProxyService::set_pac_script_poll_policy(&poll_policy);
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://request1"), &info1, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, the request will have been sent to
+ // the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // At this point we have initialized the proxy service using a PAC script.
+ //
+ // A background task to periodically re-check the PAC script for validity will
+ // have been started. We will now wait for the next download attempt to start.
+ //
+ // Note that we shouldn't have to wait long here, since our test enables a
+ // special unit-test mode.
+ fetcher->WaitUntilFetch();
+
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Make sure that our background checker is trying to download the expected
+ // PAC script (same one as before). This time we will simulate a successful
+ // download of a DIFFERENT script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript2);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now that the PAC script is downloaded, it should be used to initialize the
+ // ProxyResolver. Simulate a successful parse.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript2),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ // At this point the ProxyService should have re-configured itself to use the
+ // new PAC script.
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ GURL("http://request2"), &info2, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that it was sent to the resolver.
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending second request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+}
+
+// This test verifies that the PAC script specified by the settings is
+// periodically polled for changes. Specifically, if the initial fetch succeeds
+// and so does the next poll, however the contents of the downloaded script
+// have NOT changed, then we do not bother to re-initialize the proxy resolver.
+TEST_F(ProxyServiceTest, PACScriptRefetchAfterContentUnchanged) {
+ // Change the retry policy to wait a mere 1 ms before retrying, so the test
+ // runs quickly.
+ ImmediatePollPolicy poll_policy;
+ ProxyService::set_pac_script_poll_policy(&poll_policy);
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://request1"), &info1, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, the request will have been sent to
+ // the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // At this point we have initialized the proxy service using a PAC script.
+ //
+ // A background task to periodically re-check the PAC script for validity will
+ // have been started. We will now wait for the next download attempt to start.
+ //
+ // Note that we shouldn't have to wait long here, since our test enables a
+ // special unit-test mode.
+ fetcher->WaitUntilFetch();
+
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Make sure that our background checker is trying to download the expected
+ // PAC script (same one as before). We will simulate the same response as
+ // last time (i.e. the script is unchanged).
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_FALSE(resolver->has_pending_set_pac_script_request());
+
+ // At this point the ProxyService is still running the same PAC script as
+ // before.
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ GURL("http://request2"), &info2, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Check that it was sent to the resolver.
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending second request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+}
+
+// This test verifies that the PAC script specified by the settings is
+// periodically polled for changes. Specifically, if the initial fetch succeeds,
+// however at a later time it starts to fail, we should re-configure the
+// ProxyService to stop using that PAC script.
+TEST_F(ProxyServiceTest, PACScriptRefetchAfterSuccess) {
+ // Change the retry policy to wait a mere 1 ms before retrying, so the test
+ // runs quickly.
+ ImmediatePollPolicy poll_policy;
+ ProxyService::set_pac_script_poll_policy(&poll_policy);
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://request1"), &info1, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, the request will have been sent to
+ // the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // At this point we have initialized the proxy service using a PAC script.
+ //
+ // A background task to periodically re-check the PAC script for validity will
+ // have been started. We will now wait for the next download attempt to start.
+ //
+ // Note that we shouldn't have to wait long here, since our test enables a
+ // special unit-test mode.
+ fetcher->WaitUntilFetch();
+
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Make sure that our background checker is trying to download the expected
+ // PAC script (same one as before). This time we will simulate a failure
+ // to download the script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // At this point the ProxyService should have re-configured itself to use
+ // DIRECT connections rather than the given proxy resolver.
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ GURL("http://request2"), &info2, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info2.is_direct());
+}
+
+// Tests that the code which decides at what times to poll the PAC
+// script follows the expected policy.
+TEST_F(ProxyServiceTest, PACScriptPollingPolicy) {
+ // Retrieve the internal polling policy implementation used by ProxyService.
+ scoped_ptr<ProxyService::PacPollPolicy> policy =
+ ProxyService::CreateDefaultPacPollPolicy();
+
+ int error;
+ ProxyService::PacPollPolicy::Mode mode;
+ const base::TimeDelta initial_delay = base::TimeDelta::FromMilliseconds(-1);
+ base::TimeDelta delay = initial_delay;
+
+ // --------------------------------------------------
+ // Test the poll sequence in response to a failure.
+ // --------------------------------------------------
+ error = ERR_NAME_NOT_RESOLVED;
+
+ // Poll #0
+ mode = policy->GetNextDelay(error, initial_delay, &delay);
+ EXPECT_EQ(8, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_USE_TIMER, mode);
+
+ // Poll #1
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(32, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // Poll #2
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(120, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // Poll #3
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(14400, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // Poll #4
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(14400, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // --------------------------------------------------
+ // Test the poll sequence in response to a success.
+ // --------------------------------------------------
+ error = OK;
+
+ // Poll #0
+ mode = policy->GetNextDelay(error, initial_delay, &delay);
+ EXPECT_EQ(43200, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // Poll #1
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(43200, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+
+ // Poll #2
+ mode = policy->GetNextDelay(error, delay, &delay);
+ EXPECT_EQ(43200, delay.InSeconds());
+ EXPECT_EQ(ProxyService::PacPollPolicy::MODE_START_AFTER_ACTIVITY, mode);
+}
+
+// This tests the polling of the PAC script. Specifically, it tests that
+// polling occurs in response to user activity.
+TEST_F(ProxyServiceTest, PACScriptRefetchAfterActivity) {
+ ImmediateAfterActivityPollPolicy poll_policy;
+ ProxyService::set_pac_script_poll_policy(&poll_policy);
+
+ MockProxyConfigService* config_service =
+ new MockProxyConfigService("http://foopy/proxy.pac");
+
+ MockAsyncProxyResolverExpectsBytes* resolver =
+ new MockAsyncProxyResolverExpectsBytes;
+
+ ProxyService service(config_service, resolver, NULL);
+
+ MockProxyScriptFetcher* fetcher = new MockProxyScriptFetcher;
+ service.SetProxyScriptFetchers(fetcher,
+ new DoNothingDhcpProxyScriptFetcher());
+
+ // Start 1 request.
+
+ ProxyInfo info1;
+ TestCompletionCallback callback1;
+ int rv = service.ResolveProxy(
+ GURL("http://request1"), &info1, callback1.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The first request should have triggered initial download of PAC script.
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // Nothing has been sent to the resolver yet.
+ EXPECT_TRUE(resolver->pending_requests().empty());
+
+ // At this point the ProxyService should be waiting for the
+ // ProxyScriptFetcher to invoke its completion callback, notifying it of
+ // PAC script download completion.
+ fetcher->NotifyFetchCompletion(OK, kValidPacScript1);
+
+ // Now that the PAC script is downloaded, the request will have been sent to
+ // the proxy resolver.
+ EXPECT_EQ(ASCIIToUTF16(kValidPacScript1),
+ resolver->pending_set_pac_script_request()->script_data()->utf16());
+ resolver->pending_set_pac_script_request()->CompleteNow(OK);
+
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request1"), resolver->pending_requests()[0]->url());
+
+ // Complete the pending request.
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request1:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ // Wait for completion callback, and verify that the request ran as expected.
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ("request1:80", info1.proxy_server().ToURI());
+
+ // At this point we have initialized the proxy service using a PAC script.
+ // Our PAC poller is set to update ONLY in response to network activity,
+ // (i.e. another call to ResolveProxy()).
+
+ ASSERT_FALSE(fetcher->has_pending_request());
+ ASSERT_TRUE(resolver->pending_requests().empty());
+
+ // Start a second request.
+ ProxyInfo info2;
+ TestCompletionCallback callback2;
+ rv = service.ResolveProxy(
+ GURL("http://request2"), &info2, callback2.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // This request should have sent work to the resolver; complete it.
+ ASSERT_EQ(1u, resolver->pending_requests().size());
+ EXPECT_EQ(GURL("http://request2"), resolver->pending_requests()[0]->url());
+ resolver->pending_requests()[0]->results()->UseNamedProxy("request2:80");
+ resolver->pending_requests()[0]->CompleteNow(OK);
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ("request2:80", info2.proxy_server().ToURI());
+
+ // In response to getting that resolve request, the poller should have
+ // started the next poll, and made it as far as to request the download.
+
+ EXPECT_TRUE(fetcher->has_pending_request());
+ EXPECT_EQ(GURL("http://foopy/proxy.pac"), fetcher->pending_request_url());
+
+ // This time we will fail the download, to simulate a PAC script change.
+ fetcher->NotifyFetchCompletion(ERR_FAILED, std::string());
+
+ // Drain the message loop, so ProxyService is notified of the change
+ // and has a chance to re-configure itself.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Start a third request -- this time we expect to get a direct connection
+ // since the PAC script poller experienced a failure.
+ ProxyInfo info3;
+ TestCompletionCallback callback3;
+ rv = service.ResolveProxy(
+ GURL("http://request3"), &info3, callback3.callback(), NULL,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(info3.is_direct());
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_service_v8.cc b/chromium/net/proxy/proxy_service_v8.cc
new file mode 100644
index 00000000000..945719a1647
--- /dev/null
+++ b/chromium/net/proxy/proxy_service_v8.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 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.
+
+#include "net/proxy/proxy_service_v8.h"
+
+#include "base/logging.h"
+#include "net/proxy/network_delegate_error_observer.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_resolver_v8_tracing.h"
+#include "net/proxy/proxy_service.h"
+
+namespace net {
+
+// static
+ProxyService* CreateProxyServiceUsingV8ProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ HostResolver* host_resolver,
+ NetLog* net_log,
+ NetworkDelegate* network_delegate) {
+ DCHECK(proxy_config_service);
+ DCHECK(proxy_script_fetcher);
+ DCHECK(dhcp_proxy_script_fetcher);
+ DCHECK(host_resolver);
+
+ ProxyResolverErrorObserver* error_observer = new NetworkDelegateErrorObserver(
+ network_delegate, base::MessageLoopProxy::current().get());
+
+ ProxyResolver* proxy_resolver =
+ new ProxyResolverV8Tracing(host_resolver, error_observer, net_log);
+
+ ProxyService* proxy_service =
+ new ProxyService(proxy_config_service, proxy_resolver, net_log);
+
+ // Configure fetchers to use for PAC script downloads and auto-detect.
+ proxy_service->SetProxyScriptFetchers(proxy_script_fetcher,
+ dhcp_proxy_script_fetcher);
+
+ return proxy_service;
+}
+
+} // namespace net
diff --git a/chromium/net/proxy/proxy_service_v8.h b/chromium/net/proxy/proxy_service_v8.h
new file mode 100644
index 00000000000..0e339ebf3e7
--- /dev/null
+++ b/chromium/net/proxy/proxy_service_v8.h
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#ifndef NET_PROXY_PROXY_SERVICE_V8_H_
+#define NET_PROXY_PROXY_SERVICE_V8_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class DhcpProxyScriptFetcher;
+class HostResolver;
+class NetLog;
+class NetworkDelegate;
+class ProxyConfigService;
+class ProxyScriptFetcher;
+class ProxyService;
+
+// Creates a proxy service that polls |proxy_config_service| to notice when
+// the proxy settings change. We take ownership of |proxy_config_service|.
+//
+// |proxy_script_fetcher| specifies the dependency to use for downloading
+// any PAC scripts. The resulting ProxyService will take ownership of it.
+//
+// |dhcp_proxy_script_fetcher| specifies the dependency to use for attempting
+// to retrieve the most appropriate PAC script configured in DHCP. The
+// resulting ProxyService will take ownership of it.
+//
+// |host_resolver| points to the host resolving dependency the PAC script
+// should use for any DNS queries. It must remain valid throughout the
+// lifetime of the ProxyService.
+//
+// ##########################################################################
+// # See the warnings in net/proxy/proxy_resolver_v8.h describing the
+// # multi-threading model. In order for this to be safe to use, *ALL* the
+// # other V8's running in the process must use v8::Locker.
+// ##########################################################################
+NET_EXPORT ProxyService* CreateProxyServiceUsingV8ProxyResolver(
+ ProxyConfigService* proxy_config_service,
+ ProxyScriptFetcher* proxy_script_fetcher,
+ DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
+ HostResolver* host_resolver,
+ NetLog* net_log,
+ NetworkDelegate* network_delegate);
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_SERVICE_V8_H_
diff --git a/chromium/net/quic/blocked_list.h b/chromium/net/quic/blocked_list.h
new file mode 100644
index 00000000000..3a7f989eeec
--- /dev/null
+++ b/chromium/net/quic/blocked_list.h
@@ -0,0 +1,91 @@
+// Copyright (c) 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.
+//
+// A combined list/hash set for read or write-blocked entities.
+
+#ifndef NET_QUIC_BLOCKED_LIST_H_
+#define NET_QUIC_BLOCKED_LIST_H_
+
+#include <list>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+
+namespace net {
+
+template <typename Object>
+class BlockedList {
+ public:
+ // Called to add an object to the blocked list. This indicates
+ // the object should be notified when it can use the socket again.
+ //
+ // If this object is already on the list, it will not be added again.
+ void AddBlockedObject(Object object) {
+ // Only add the object to the list if we successfully add it to the set.
+ if (object_set_.insert(object).second) {
+ object_list_.push_back(object);
+ }
+ }
+
+ // Called to remove an object from a blocked list. This should be
+ // called in the event the object is being deleted before the list is.
+ void RemoveBlockedObject(Object object) {
+ // Remove the object from the set. We'll check the set before calling
+ // OnCanWrite on a object from the list.
+ //
+ // There is potentially ordering unfairness should a session be removed and
+ // then readded (as it keeps its position in the list) but it's not worth
+ // the overhead to walk the list and remove it.
+ object_set_.erase(object);
+ }
+
+ // Called when the socket is usable and some objects can access it. Returns
+ // the first object and removes it from the list.
+ Object GetNextBlockedObject() {
+ DCHECK(!IsEmpty());
+
+ // Walk the list to find the first object which was not removed from the
+ // set.
+ while (!object_list_.empty()) {
+ Object object = *object_list_.begin();
+ object_list_.pop_front();
+ int removed = object_set_.erase(object);
+ if (removed > 0) {
+ return object;
+ }
+ }
+
+ // This is a bit of a hack: It's illegal to call GetNextBlockedObject() if
+ // the list is empty (see DCHECK above) but we must return something. This
+ // compiles for ints (returns 0) and pointers in the case that someone has a
+ // bug in their call site.
+ return 0;
+ };
+
+ // Returns the number of objects in the blocked list.
+ int NumObjects() {
+ return object_set_.size();
+ };
+
+ // Returns true if there are no objects in the list, false otherwise.
+ bool IsEmpty() {
+ return object_set_.empty();
+ };
+
+ private:
+ // A set tracking the objects. This is the authoritative container for
+ // determining if an object is blocked. Objects in the list will always
+ // be in the set.
+ base::hash_set<Object> object_set_;
+ // A list tracking the order in which objects were added to the list.
+ // Objects are added to the back and pulled off the front, but only get
+ // resumption calls if they're still in the set.
+ // It's possible to be in the list twice, but only the first entry will get an
+ // OnCanWrite call.
+ std::list<Object> object_list_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_BLOCKED_LIST_H_
diff --git a/chromium/net/quic/blocked_list_test.cc b/chromium/net/quic/blocked_list_test.cc
new file mode 100644
index 00000000000..074b6f52782
--- /dev/null
+++ b/chromium/net/quic/blocked_list_test.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#include "net/quic/blocked_list.h"
+#include "net/quic/quic_connection.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template<>
+struct hash<const int*> {
+ std::size_t operator()(const int* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+}
+#endif
+
+namespace net {
+namespace test {
+namespace {
+
+class BlockedListTest : public ::testing::Test {
+ protected:
+ BlockedListTest() :
+ item1_(0),
+ item2_(0),
+ item3_(0) {
+ }
+
+ BlockedList<const int*> list_;
+ const int item1_;
+ const int item2_;
+ const int item3_;
+};
+
+TEST_F(BlockedListTest, BasicAdd) {
+ list_.AddBlockedObject(&item1_);
+ list_.AddBlockedObject(&item3_);
+ list_.AddBlockedObject(&item2_);
+ ASSERT_EQ(3, list_.NumObjects());
+ ASSERT_FALSE(list_.IsEmpty());
+
+ EXPECT_EQ(&item1_, list_.GetNextBlockedObject());
+ EXPECT_EQ(&item3_, list_.GetNextBlockedObject());
+ EXPECT_EQ(&item2_, list_.GetNextBlockedObject());
+}
+
+TEST_F(BlockedListTest, AddAndRemove) {
+ list_.AddBlockedObject(&item1_);
+ list_.AddBlockedObject(&item3_);
+ list_.AddBlockedObject(&item2_);
+ ASSERT_EQ(3, list_.NumObjects());
+
+ list_.RemoveBlockedObject(&item3_);
+ ASSERT_EQ(2, list_.NumObjects());
+
+ EXPECT_EQ(&item1_, list_.GetNextBlockedObject());
+ EXPECT_EQ(&item2_, list_.GetNextBlockedObject());
+}
+
+TEST_F(BlockedListTest, DuplicateAdd) {
+ list_.AddBlockedObject(&item1_);
+ list_.AddBlockedObject(&item3_);
+ list_.AddBlockedObject(&item2_);
+
+ list_.AddBlockedObject(&item3_);
+ list_.AddBlockedObject(&item2_);
+ list_.AddBlockedObject(&item1_);
+
+ ASSERT_EQ(3, list_.NumObjects());
+ ASSERT_FALSE(list_.IsEmpty());
+
+ // Call in the original insert order.
+ EXPECT_EQ(&item1_, list_.GetNextBlockedObject());
+ EXPECT_EQ(&item3_, list_.GetNextBlockedObject());
+ EXPECT_EQ(&item2_, list_.GetNextBlockedObject());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/available_channel_estimator.cc b/chromium/net/quic/congestion_control/available_channel_estimator.cc
new file mode 100644
index 00000000000..ef091abce43
--- /dev/null
+++ b/chromium/net/quic/congestion_control/available_channel_estimator.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2013 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/quic/congestion_control/available_channel_estimator.h"
+
+static const int kNumberOfSamples = 9;
+
+namespace net {
+
+AvailableChannelEstimator::AvailableChannelEstimator(
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime first_send_time,
+ QuicTime first_receive_time)
+ : first_sequence_number_(sequence_number),
+ first_send_time_(first_send_time),
+ first_receive_time_(first_receive_time),
+ last_incorporated_sequence_number_(sequence_number),
+ last_time_sent_(QuicTime::Zero()),
+ last_receive_time_(QuicTime::Zero()),
+ number_of_sequence_numbers_(0),
+ received_bytes_(0) {
+}
+
+void AvailableChannelEstimator::OnIncomingFeedback(
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount packet_size,
+ QuicTime sent_time,
+ QuicTime receive_time) {
+ if (sequence_number <= first_sequence_number_) {
+ // Ignore pre-probe feedback.
+ return;
+ }
+ if (sequence_number <= last_incorporated_sequence_number_) {
+ // Ignore old feedback; will remove duplicates.
+ return;
+ }
+ // Remember the highest received sequence number.
+ last_incorporated_sequence_number_ = sequence_number;
+ if (number_of_sequence_numbers_ < kNumberOfSamples) {
+ // We don't care how many sequence numbers we have after we pass
+ // kNumberOfSamples.
+ number_of_sequence_numbers_++;
+ }
+ last_receive_time_ = receive_time;
+ last_time_sent_ = sent_time;
+ received_bytes_ += packet_size;
+ // TODO(pwestin): the variance here should give us information about accuracy.
+}
+
+AvailableChannelEstimateState
+ AvailableChannelEstimator::GetAvailableChannelEstimate(
+ QuicBandwidth* bandwidth) const {
+ if (number_of_sequence_numbers_ < 2) {
+ return kAvailableChannelEstimateUnknown;
+ }
+ QuicTime::Delta send_delta = last_time_sent_.Subtract(first_send_time_);
+ QuicTime::Delta receive_delta =
+ last_receive_time_.Subtract(first_receive_time_);
+
+ // TODO(pwestin): room for improvement here. Keeping it simple for now.
+ *bandwidth = QuicBandwidth::FromBytesAndTimeDelta(received_bytes_,
+ receive_delta);
+
+ QuicTime::Delta diff = receive_delta.Subtract(send_delta);
+ QuicTime::Delta ten_percent_of_send_time =
+ QuicTime::Delta::FromMicroseconds(send_delta.ToMicroseconds() / 10);
+
+ if (diff < ten_percent_of_send_time) {
+ return kAvailableChannelEstimateSenderLimited;
+ }
+ if (number_of_sequence_numbers_ < kNumberOfSamples) {
+ return kAvailableChannelEstimateUncertain;
+ }
+ return kAvailableChannelEstimateGood;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/available_channel_estimator.h b/chromium/net/quic/congestion_control/available_channel_estimator.h
new file mode 100644
index 00000000000..e2ad19a8b66
--- /dev/null
+++ b/chromium/net/quic/congestion_control/available_channel_estimator.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2013 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.
+
+// Based on the inter arrival time of the received packets relative to the time
+// those packets where sent we can estimate the available capacity of the
+// channel.
+// We can only use packet trains that are sent out faster than the acctual
+// available channel capacity.
+// Note 1: this is intended to be a temporary class created when you send out a
+// channel probing burst. Once the last packet have arrived you ask it for an
+// estimate.
+// Note 2: During this phase we should not update the overuse detector.
+#ifndef NET_QUIC_CONGESTION_CONTROL_AVAILABLE_CHANNEL_ESTIMATOR_H_
+#define NET_QUIC_CONGESTION_CONTROL_AVAILABLE_CHANNEL_ESTIMATOR_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+enum NET_EXPORT_PRIVATE AvailableChannelEstimateState {
+ kAvailableChannelEstimateUnknown = 0,
+ kAvailableChannelEstimateUncertain = 1,
+ kAvailableChannelEstimateGood = 2,
+ kAvailableChannelEstimateSenderLimited = 3,
+};
+
+class NET_EXPORT_PRIVATE AvailableChannelEstimator {
+ public:
+ explicit AvailableChannelEstimator(
+ QuicPacketSequenceNumber first_sequence_number,
+ QuicTime first_send_time,
+ QuicTime first_receive_time);
+
+ // Update the statistics with each receive time, for every packet we get a
+ // feedback message for.
+ void OnIncomingFeedback(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount packet_size,
+ QuicTime sent_time,
+ QuicTime receive_time);
+
+ // Get the current estimated available channel capacity.
+ // bandwidth_estimate is invalid if kAvailableChannelEstimateUnknown
+ // is returned.
+ AvailableChannelEstimateState GetAvailableChannelEstimate(
+ QuicBandwidth* bandwidth_estimate) const;
+
+ private:
+ const QuicPacketSequenceNumber first_sequence_number_;
+ const QuicTime first_send_time_;
+ const QuicTime first_receive_time_;
+ QuicPacketSequenceNumber last_incorporated_sequence_number_;
+ QuicTime last_time_sent_;
+ QuicTime last_receive_time_;
+ int number_of_sequence_numbers_;
+ QuicByteCount received_bytes_;
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_AVAILABLE_CHANNEL_ESTIMATOR_H_
diff --git a/chromium/net/quic/congestion_control/available_channel_estimator_test.cc b/chromium/net/quic/congestion_control/available_channel_estimator_test.cc
new file mode 100644
index 00000000000..b4a4b9c341c
--- /dev/null
+++ b/chromium/net/quic/congestion_control/available_channel_estimator_test.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/available_channel_estimator.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class AvailableChannelEstimatorTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ srand(1234);
+ packet_size_ = 1200;
+ sequence_number_ = 1;
+ QuicTime receive_time = receive_clock_.Now();
+ QuicTime sent_time = send_clock_.Now();
+ estimator_.reset(new AvailableChannelEstimator(sequence_number_,
+ sent_time,
+ receive_time));
+ }
+
+ MockClock send_clock_;
+ MockClock receive_clock_;
+ QuicPacketSequenceNumber sequence_number_;
+ QuicByteCount packet_size_;
+ scoped_ptr<AvailableChannelEstimator> estimator_;
+};
+
+TEST_F(AvailableChannelEstimatorTest, SimpleBasic) {
+ QuicBandwidth bandwidth = QuicBandwidth::Zero();
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(1);
+ receive_clock_.AdvanceTime(received_delta);
+ send_clock_.AdvanceTime(send_delta);
+ QuicTime receive_time = receive_clock_.Now();
+ QuicTime sent_time = send_clock_.Now();
+ estimator_->OnIncomingFeedback(++sequence_number_,
+ packet_size_,
+ sent_time,
+ receive_time);
+ EXPECT_EQ(kAvailableChannelEstimateUnknown,
+ estimator_->GetAvailableChannelEstimate(&bandwidth));
+
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.Now();
+ send_clock_.AdvanceTime(send_delta);
+ sent_time = send_clock_.Now();
+
+ estimator_->OnIncomingFeedback(++sequence_number_,
+ packet_size_,
+ sent_time,
+ receive_time);
+ EXPECT_EQ(kAvailableChannelEstimateUncertain,
+ estimator_->GetAvailableChannelEstimate(&bandwidth));
+
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_, received_delta),
+ bandwidth);
+}
+
+// TODO(pwestin): simulate cross traffic.
+TEST_F(AvailableChannelEstimatorTest, SimpleUncertainEstimate) {
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(1);
+
+ for (int i = 0; i < 8; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.Now();
+ send_clock_.AdvanceTime(send_delta);
+ QuicTime sent_time = send_clock_.Now();
+ estimator_->OnIncomingFeedback(++sequence_number_,
+ packet_size_,
+ sent_time,
+ receive_time);
+ }
+ QuicBandwidth bandwidth = QuicBandwidth::Zero();
+ EXPECT_EQ(kAvailableChannelEstimateUncertain,
+ estimator_->GetAvailableChannelEstimate(&bandwidth));
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_, received_delta),
+ bandwidth);
+}
+
+TEST_F(AvailableChannelEstimatorTest, SimpleGoodEstimate) {
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(1);
+
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.Now();
+ send_clock_.AdvanceTime(send_delta);
+ QuicTime sent_time = send_clock_.Now();
+ estimator_->OnIncomingFeedback(++sequence_number_,
+ packet_size_,
+ sent_time,
+ receive_time);
+ }
+ QuicBandwidth bandwidth = QuicBandwidth::Zero();
+ EXPECT_EQ(kAvailableChannelEstimateGood,
+ estimator_->GetAvailableChannelEstimate(&bandwidth));
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_, received_delta),
+ bandwidth);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/channel_estimator.cc b/chromium/net/quic/congestion_control/channel_estimator.cc
new file mode 100644
index 00000000000..54b96ae4e18
--- /dev/null
+++ b/chromium/net/quic/congestion_control/channel_estimator.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2013 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/quic/congestion_control/channel_estimator.h"
+
+// To get information about bandwidth, our send rate for a pair of packets must
+// be much faster (ideally back to back) than the receive rate. In that
+// scenario, the arriving packet pair will tend to arrive at the max bandwidth
+// of the channel. Said another way, when our inter-departure time is a small
+// fraction of the inter-arrival time for the same pair of packets, then we can
+// get an estimate of bandwidth from that interarrival time. The following
+// constant is the threshold ratio for deriving bandwidth information.
+static const int kInterarrivalRatioThresholdForBandwidthEstimation = 5;
+static const size_t kMinNumberOfSamples = 10;
+static const size_t kMaxNumberOfSamples = 100;
+
+namespace net {
+
+ChannelEstimator::ChannelEstimator()
+ : last_sequence_number_(0),
+ last_send_time_(QuicTime::Zero()),
+ last_receive_time_(QuicTime::Zero()),
+ sorted_bitrate_estimates_(kMaxNumberOfSamples) {
+}
+
+ChannelEstimator::~ChannelEstimator() {
+}
+
+void ChannelEstimator::OnAcknowledgedPacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount packet_size,
+ QuicTime send_time,
+ QuicTime receive_time) {
+ if (last_sequence_number_ > sequence_number) {
+ // Old packet. The sequence_number use the full 64 bits even though it's
+ // less on the wire.
+ return;
+ }
+ if (last_sequence_number_ != sequence_number - 1) {
+ DLOG(INFO) << "Skip channel estimator due to lost packet(s)";
+ } else if (last_send_time_.IsInitialized()) {
+ QuicTime::Delta sent_delta = send_time.Subtract(last_send_time_);
+ QuicTime::Delta received_delta = receive_time.Subtract(last_receive_time_);
+ if (received_delta.ToMicroseconds() >
+ kInterarrivalRatioThresholdForBandwidthEstimation *
+ sent_delta.ToMicroseconds()) {
+ UpdateFilter(received_delta, packet_size, sequence_number);
+ }
+ }
+ last_sequence_number_ = sequence_number;
+ last_send_time_ = send_time;
+ last_receive_time_ = receive_time;
+}
+
+ChannelEstimateState ChannelEstimator::GetChannelEstimate(
+ QuicBandwidth* estimate) const {
+ if (sorted_bitrate_estimates_.Size() < kMinNumberOfSamples) {
+ // Not enough data to make an estimate.
+ return kChannelEstimateUnknown;
+ }
+ // Our data is stored in a sorted map, we need to iterate through our map to
+ // find the estimated bitrates at our targeted percentiles.
+
+ // Calculate 25th percentile.
+ size_t beginning_window = sorted_bitrate_estimates_.Size() / 4;
+ // Calculate 50th percentile.
+ size_t median = sorted_bitrate_estimates_.Size() / 2;
+ // Calculate 75th percentile.
+ size_t end_window = sorted_bitrate_estimates_.Size() - beginning_window;
+
+ QuicBandwidth bitrate_25th_percentile = QuicBandwidth::Zero();
+ QuicBandwidth median_bitrate = QuicBandwidth::Zero();
+ QuicBandwidth bitrate_75th_percentile = QuicBandwidth::Zero();
+ QuicMaxSizedMap<QuicBandwidth, QuicPacketSequenceNumber>::ConstIterator it =
+ sorted_bitrate_estimates_.Begin();
+
+ for (size_t i = 0; i <= end_window; ++i, ++it) {
+ DCHECK(it != sorted_bitrate_estimates_.End());
+ if (i == beginning_window) {
+ bitrate_25th_percentile = it->first;
+ }
+ if (i == median) {
+ median_bitrate = it->first;
+ }
+ if (i == end_window) {
+ bitrate_75th_percentile = it->first;
+ }
+ }
+ *estimate = median_bitrate;
+ DLOG(INFO) << "Channel estimate is:"
+ << median_bitrate.ToKBitsPerSecond() << " Kbit/s";
+ // If the bitrates in our 25th to 75th percentile window varies more than
+ // 25% of the median bitrate we consider the estimate to be uncertain.
+ if (bitrate_75th_percentile.Subtract(bitrate_25th_percentile) >
+ median_bitrate.Scale(0.25f)) {
+ return kChannelEstimateUncertain;
+ }
+ return kChannelEstimateGood;
+}
+
+void ChannelEstimator::UpdateFilter(QuicTime::Delta received_delta,
+ QuicByteCount size_delta,
+ QuicPacketSequenceNumber sequence_number) {
+ QuicBandwidth estimate =
+ QuicBandwidth::FromBytesAndTimeDelta(size_delta, received_delta);
+ sorted_bitrate_estimates_.Insert(estimate, sequence_number);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/channel_estimator.h b/chromium/net/quic/congestion_control/channel_estimator.h
new file mode 100644
index 00000000000..9be8031145b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/channel_estimator.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 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.
+
+// Based on the inter arrival time of the received packets relative to the time
+// those packets where sent we can estimate the max capacity of the channel.
+// We can only use packet pair that are sent out faster than the acctual
+// channel capacity.
+#ifndef NET_QUIC_CONGESTION_CONTROL_CHANNEL_ESTIMATOR_H_
+#define NET_QUIC_CONGESTION_CONTROL_CHANNEL_ESTIMATOR_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/quic_max_sized_map.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+enum ChannelEstimateState {
+ kChannelEstimateUnknown = 0,
+ kChannelEstimateUncertain = 1,
+ kChannelEstimateGood = 2
+};
+
+class NET_EXPORT_PRIVATE ChannelEstimator {
+ public:
+ ChannelEstimator();
+ ~ChannelEstimator();
+
+ // This method should be called each time we acquire a receive time for a
+ // packet we previously sent. It calculates deltas between consecutive
+ // receive times, and may use that to update the channel bandwidth estimate.
+ void OnAcknowledgedPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount packet_size,
+ QuicTime send_time,
+ QuicTime receive_time);
+
+ // Get the current estimated state and channel capacity.
+ // Note: estimate will not be valid when kChannelEstimateUnknown is returned.
+ ChannelEstimateState GetChannelEstimate(QuicBandwidth* estimate) const;
+
+ private:
+ void UpdateFilter(QuicTime::Delta received_delta, QuicByteCount size_delta,
+ QuicPacketSequenceNumber sequence_number);
+
+ QuicPacketSequenceNumber last_sequence_number_;
+ QuicTime last_send_time_;
+ QuicTime last_receive_time_;
+ QuicMaxSizedMap<QuicBandwidth, QuicPacketSequenceNumber>
+ sorted_bitrate_estimates_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChannelEstimator);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_CHANNEL_ESTIMATOR_H_
diff --git a/chromium/net/quic/congestion_control/channel_estimator_test.cc b/chromium/net/quic/congestion_control/channel_estimator_test.cc
new file mode 100644
index 00000000000..460e436589e
--- /dev/null
+++ b/chromium/net/quic/congestion_control/channel_estimator_test.cc
@@ -0,0 +1,224 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "net/quic/congestion_control/channel_estimator.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class ChannelEstimatorTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ srand(1234);
+ packet_size_ = 1200;
+ sequence_number_ = 1;
+ }
+
+ QuicPacketSequenceNumber sequence_number_;
+ QuicByteCount packet_size_;
+ MockClock send_clock_;
+ MockClock receive_clock_;
+ ChannelEstimator channel_estimator_;
+};
+
+TEST_F(ChannelEstimatorTest, SimpleNonDetect) {
+ // In this test, the send times differ by the same delta as the receive times,
+ // so we haven't sent packets closely enough to detect "spreading," or
+ // effective bandwidth.
+ QuicTime::Delta delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 1000; ++i) {
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(delta);
+ receive_clock_.AdvanceTime(delta);
+ }
+ QuicBandwidth estimate = QuicBandwidth::Zero();
+ EXPECT_EQ(kChannelEstimateUnknown,
+ channel_estimator_.GetChannelEstimate(&estimate));
+ EXPECT_TRUE(estimate.IsZero());
+}
+
+TEST_F(ChannelEstimatorTest, SimplePacketPairDetect) {
+ // In this test, we start by sending packet pairs back-to-back and
+ // add a receive side spreading that indicate an effective bandwidth.
+ // We do 2 testes with different effective bandwidth to make sure that we
+ // detect the new effective bandwidth.
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ QuicTime send_time = send_clock_.ApproximateNow();
+
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(send_delta);
+ }
+ QuicBandwidth estimate = QuicBandwidth::Zero();
+ EXPECT_EQ(kChannelEstimateGood,
+ channel_estimator_.GetChannelEstimate(&estimate));
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_, received_delta),
+ estimate);
+ received_delta = QuicTime::Delta::FromMilliseconds(1);
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ QuicTime send_time = send_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(send_delta);
+ }
+ EXPECT_EQ(kChannelEstimateGood,
+ channel_estimator_.GetChannelEstimate(&estimate));
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_, received_delta),
+ estimate);
+}
+
+TEST_F(ChannelEstimatorTest, SimpleFlatSlope) {
+ // In this test, we send packet pairs back-to-back and add a slowly increasing
+ // receive side spreading. We expect the estimate to be good and that our
+ // mean receive side spreading is returned as the estimate.
+ QuicTime::Delta initial_received_delta = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta received_delta = initial_received_delta;
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ QuicTime send_time = send_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(send_delta);
+ received_delta = received_delta.Add(QuicTime::Delta::FromMicroseconds(10));
+ }
+ QuicBandwidth estimate = QuicBandwidth::Zero();
+ EXPECT_EQ(kChannelEstimateGood,
+ channel_estimator_.GetChannelEstimate(&estimate));
+
+ // Calculate our mean receive delta.
+ QuicTime::Delta increased_received_delta =
+ received_delta.Subtract(initial_received_delta);
+ QuicTime::Delta mean_received_delta = initial_received_delta.Add(
+ QuicTime::Delta::FromMicroseconds(
+ increased_received_delta.ToMicroseconds() / 2));
+
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_,
+ mean_received_delta), estimate);
+}
+
+TEST_F(ChannelEstimatorTest, SimpleMediumSlope) {
+ // In this test, we send packet pairs back-to-back and add an increasing
+ // receive side spreading. We expect the estimate to be uncertaint and that
+ // our mean receive side spreading is returned as the estimate.
+ QuicTime::Delta initial_received_delta = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta received_delta = initial_received_delta;
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ QuicTime send_time = send_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(send_delta);
+ received_delta = received_delta.Add(QuicTime::Delta::FromMicroseconds(50));
+ }
+ QuicBandwidth estimate = QuicBandwidth::Zero();
+ EXPECT_EQ(kChannelEstimateUncertain,
+ channel_estimator_.GetChannelEstimate(&estimate));
+
+ // Calculate our mean receive delta.
+ QuicTime::Delta increased_received_delta =
+ received_delta.Subtract(initial_received_delta);
+ QuicTime::Delta mean_received_delta = initial_received_delta.Add(
+ QuicTime::Delta::FromMicroseconds(
+ increased_received_delta.ToMicroseconds() / 2));
+
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_,
+ mean_received_delta), estimate);
+}
+
+TEST_F(ChannelEstimatorTest, SimpleSteepSlope) {
+ // In this test, we send packet pairs back-to-back and add a rapidly
+ // increasing receive side spreading. We expect the estimate to be uncertain
+ // and that our mean receive side spreading is returned as the estimate.
+ QuicTime::Delta initial_received_delta = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta received_delta = initial_received_delta;
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 100; ++i) {
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ QuicTime send_time = send_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ channel_estimator_.OnAcknowledgedPacket(sequence_number_++,
+ packet_size_,
+ send_time,
+ receive_time);
+ send_clock_.AdvanceTime(send_delta);
+ received_delta = received_delta.Add(QuicTime::Delta::FromMicroseconds(100));
+ }
+ QuicBandwidth estimate = QuicBandwidth::Zero();
+ EXPECT_EQ(kChannelEstimateUncertain,
+ channel_estimator_.GetChannelEstimate(&estimate));
+
+ // Calculate our mean receive delta.
+ QuicTime::Delta increased_received_delta =
+ received_delta.Subtract(initial_received_delta);
+ QuicTime::Delta mean_received_delta = initial_received_delta.Add(
+ QuicTime::Delta::FromMicroseconds(
+ increased_received_delta.ToMicroseconds() / 2));
+
+ EXPECT_EQ(QuicBandwidth::FromBytesAndTimeDelta(packet_size_,
+ mean_received_delta), estimate);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/cube_root.cc b/chromium/net/quic/congestion_control/cube_root.cc
new file mode 100644
index 00000000000..c563ad9cf5e
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cube_root.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2013 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/quic/congestion_control/cube_root.h"
+
+#include "base/logging.h"
+
+namespace {
+
+// Find last bit in a 64-bit word.
+int FindMostSignificantBit(uint64 x) {
+ if (!x) {
+ return 0;
+ }
+ int r = 0;
+ if (x & 0xffffffff00000000ull) {
+ x >>= 32;
+ r += 32;
+ }
+ if (x & 0xffff0000u) {
+ x >>= 16;
+ r += 16;
+ }
+ if (x & 0xff00u) {
+ x >>= 8;
+ r += 8;
+ }
+ if (x & 0xf0u) {
+ x >>= 4;
+ r += 4;
+ }
+ if (x & 0xcu) {
+ x >>= 2;
+ r += 2;
+ }
+ if (x & 0x02u) {
+ x >>= 1;
+ r++;
+ }
+ if (x & 0x01u) {
+ r++;
+ }
+ return r;
+}
+
+// 6 bits table [0..63]
+const uint32 cube_root_table[] = {
+ 0, 54, 54, 54, 118, 118, 118, 118, 123, 129, 134, 138, 143, 147, 151,
+ 156, 157, 161, 164, 168, 170, 173, 176, 179, 181, 185, 187, 190, 192, 194,
+ 197, 199, 200, 202, 204, 206, 209, 211, 213, 215, 217, 219, 221, 222, 224,
+ 225, 227, 229, 231, 232, 234, 236, 237, 239, 240, 242, 244, 245, 246, 248,
+ 250, 251, 252, 254
+};
+} // namespace
+
+namespace net {
+
+// Calculate the cube root using a table lookup followed by one Newton-Raphson
+// iteration.
+uint32 CubeRoot::Root(uint64 a) {
+ uint32 msb = FindMostSignificantBit(a);
+ DCHECK_LE(msb, 64u);
+
+ if (msb < 7) {
+ // MSB in our table.
+ return ((cube_root_table[static_cast<uint32>(a)]) + 31) >> 6;
+ }
+ // MSB 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...
+ // cubic_shift 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, ...
+ uint32 cubic_shift = (msb - 4);
+ cubic_shift = ((cubic_shift * 342) >> 10); // Div by 3, biased high.
+
+ // 4 to 6 bits accuracy depending on MSB.
+ uint32 down_shifted_to_6bit = (a >> (cubic_shift * 3));
+ uint64 root = ((cube_root_table[down_shifted_to_6bit] + 10) << cubic_shift)
+ >> 6;
+
+ // Make one Newton-Raphson iteration.
+ // Since x has an error (inaccuracy due to the use of fix point) we get a
+ // more accurate result by doing x * (x - 1) instead of x * x.
+ root = 2 * root + (a / (root * (root - 1)));
+ root = ((root * 341) >> 10); // Div by 3, biased low.
+ return static_cast<uint32>(root);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/cube_root.h b/chromium/net/quic/congestion_control/cube_root.h
new file mode 100644
index 00000000000..3b3736c1c59
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cube_root.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 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_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
+#define NET_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE CubeRoot {
+ public:
+ // Calculates the cube root using a table lookup followed by one Newton-
+ // Raphson iteration.
+ static uint32 Root(uint64 a);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_CUBE_ROOT_H_
diff --git a/chromium/net/quic/congestion_control/cube_root_test.cc b/chromium/net/quic/congestion_control/cube_root_test.cc
new file mode 100644
index 00000000000..8f4729cb7a7
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cube_root_test.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 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 "base/basictypes.h"
+#include "net/quic/congestion_control/cube_root.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class CubeRootTest : public ::testing::Test {
+ protected:
+ CubeRootTest() {
+ }
+};
+
+TEST_F(CubeRootTest, LowRoot) {
+ for (uint32 i = 1; i < 256; ++i) {
+ uint64 cube = i * i * i;
+ uint8 cube_root = CubeRoot::Root(cube);
+ EXPECT_EQ(i, cube_root);
+ }
+}
+
+TEST_F(CubeRootTest, HighRoot) {
+ // Test the range we will opperate in, 1300 to 130 000.
+ // We expect some loss in accuracy, accepting +-0.2%.
+ for (uint64 i = 1300; i < 20000; i += 100) {
+ uint64 cube = i * i * i;
+ uint32 cube_root = CubeRoot::Root(cube);
+ uint32 margin = cube_root >> 9; // Calculate 0.2% roughly by
+ // dividing by 512.
+ EXPECT_LE(i - margin, cube_root);
+ EXPECT_GE(i + margin, cube_root);
+ }
+ for (uint64 i = 20000; i < 130000; i *= 2) {
+ uint64 cube = i * i * i;
+ uint32 cube_root = CubeRoot::Root(cube);
+ uint32 margin = cube_root >> 9;
+ EXPECT_LE(i - margin, cube_root);
+ EXPECT_GE(i + margin, cube_root);
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/cubic.cc b/chromium/net/quic/congestion_control/cubic.cc
new file mode 100644
index 00000000000..04b1e937cfd
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cubic.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/cubic.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace net {
+
+// Constants based on TCP defaults.
+// The following constants are in 2^10 fractions of a second instead of ms to
+// allow a 10 shift right to divide.
+const int kCubeScale = 40; // 1024*1024^3 (first 1024 is from 0.100^3)
+ // where 0.100 is 100 ms which is the scaling
+ // round trip time.
+const int kCubeCongestionWindowScale = 410;
+const uint64 kCubeFactor = (1ull << kCubeScale) / kCubeCongestionWindowScale;
+const uint32 kBeta = 717; // Back off factor after loss.
+const uint32 kBetaLastMax = 871; // Additional back off factor after loss for
+ // the stored max value.
+
+namespace {
+// Find last bit in a 64-bit word.
+int FindMostSignificantBit(uint64 x) {
+ if (!x) {
+ return 0;
+ }
+ int r = 0;
+ if (x & 0xffffffff00000000ull) {
+ x >>= 32;
+ r += 32;
+ }
+ if (x & 0xffff0000u) {
+ x >>= 16;
+ r += 16;
+ }
+ if (x & 0xff00u) {
+ x >>= 8;
+ r += 8;
+ }
+ if (x & 0xf0u) {
+ x >>= 4;
+ r += 4;
+ }
+ if (x & 0xcu) {
+ x >>= 2;
+ r += 2;
+ }
+ if (x & 0x02u) {
+ x >>= 1;
+ r++;
+ }
+ if (x & 0x01u) {
+ r++;
+ }
+ return r;
+}
+
+// 6 bits table [0..63]
+const uint32 cube_root_table[] = {
+ 0, 54, 54, 54, 118, 118, 118, 118, 123, 129, 134, 138, 143, 147, 151,
+ 156, 157, 161, 164, 168, 170, 173, 176, 179, 181, 185, 187, 190, 192, 194,
+ 197, 199, 200, 202, 204, 206, 209, 211, 213, 215, 217, 219, 221, 222, 224,
+ 225, 227, 229, 231, 232, 234, 236, 237, 239, 240, 242, 244, 245, 246, 248,
+ 250, 251, 252, 254
+};
+} // namespace
+
+Cubic::Cubic(const QuicClock* clock)
+ : clock_(clock),
+ epoch_(QuicTime::Zero()),
+ last_update_time_(QuicTime::Zero()) {
+ Reset();
+}
+
+// Calculate the cube root using a table lookup followed by one Newton-Raphson
+// iteration.
+uint32 Cubic::CubeRoot(uint64 a) {
+ uint32 msb = FindMostSignificantBit(a);
+ DCHECK_LE(msb, 64u);
+
+ if (msb < 7) {
+ // MSB in our table.
+ return ((cube_root_table[static_cast<uint32>(a)]) + 31) >> 6;
+ }
+ // MSB 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, ...
+ // cubic_shift 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, ...
+ uint32 cubic_shift = (msb - 4);
+ cubic_shift = ((cubic_shift * 342) >> 10); // Div by 3, biased high.
+
+ // 4 to 6 bits accuracy depending on MSB.
+ uint32 down_shifted_to_6bit = (a >> (cubic_shift * 3));
+ uint64 root = ((cube_root_table[down_shifted_to_6bit] + 10) << cubic_shift)
+ >> 6;
+
+ // Make one Newton-Raphson iteration.
+ // Since x has an error (inaccuracy due to the use of fix point) we get a
+ // more accurate result by doing x * (x - 1) instead of x * x.
+ root = 2 * root + (a / (root * (root - 1)));
+ root = ((root * 341) >> 10); // Div by 3, biased low.
+ return static_cast<uint32>(root);
+}
+
+void Cubic::Reset() {
+ epoch_ = QuicTime::Zero(); // Reset time.
+ last_update_time_ = QuicTime::Zero(); // Reset time.
+ last_congestion_window_ = 0;
+ last_max_congestion_window_ = 0;
+ acked_packets_count_ = 0;
+ estimated_tcp_congestion_window_ = 0;
+ origin_point_congestion_window_ = 0;
+ time_to_origin_point_ = 0;
+ last_target_congestion_window_ = 0;
+}
+
+QuicTcpCongestionWindow Cubic::CongestionWindowAfterPacketLoss(
+ QuicTcpCongestionWindow current_congestion_window) {
+ if (current_congestion_window < last_max_congestion_window_) {
+ // We never reached the old max, so assume we are competing with another
+ // flow. Use our extra back off factor to allow the other flow to go up.
+ last_max_congestion_window_ =
+ (kBetaLastMax * current_congestion_window) >> 10;
+ } else {
+ last_max_congestion_window_ = current_congestion_window;
+ }
+ epoch_ = QuicTime::Zero(); // Reset time.
+ return (current_congestion_window * kBeta) >> 10;
+}
+
+QuicTcpCongestionWindow Cubic::CongestionWindowAfterAck(
+ QuicTcpCongestionWindow current_congestion_window,
+ QuicTime::Delta delay_min) {
+ acked_packets_count_ += 1; // Packets acked.
+ QuicTime current_time = clock_->ApproximateNow();
+
+ // Cubic is "independent" of RTT, the update is limited by the time elapsed.
+ if (last_congestion_window_ == current_congestion_window &&
+ (current_time.Subtract(last_update_time_) <= MaxCubicTimeInterval())) {
+ return std::max(last_target_congestion_window_,
+ estimated_tcp_congestion_window_);
+ }
+ last_congestion_window_ = current_congestion_window;
+ last_update_time_ = current_time;
+
+ if (!epoch_.IsInitialized()) {
+ // First ACK after a loss event.
+ DLOG(INFO) << "Start of epoch";
+ epoch_ = current_time; // Start of epoch.
+ acked_packets_count_ = 1; // Reset count.
+ // Reset estimated_tcp_congestion_window_ to be in sync with cubic.
+ estimated_tcp_congestion_window_ = current_congestion_window;
+ if (last_max_congestion_window_ <= current_congestion_window) {
+ time_to_origin_point_ = 0;
+ origin_point_congestion_window_ = current_congestion_window;
+ } else {
+ time_to_origin_point_ = CubeRoot(kCubeFactor *
+ (last_max_congestion_window_ - current_congestion_window));
+ origin_point_congestion_window_ =
+ last_max_congestion_window_;
+ }
+ }
+ // Change the time unit from microseconds to 2^10 fractions per second. Take
+ // the round trip time in account. This is done to allow us to use shift as a
+ // divide operator.
+ int64 elapsed_time =
+ (current_time.Add(delay_min).Subtract(epoch_).ToMicroseconds() << 10) /
+ base::Time::kMicrosecondsPerSecond;
+
+ int64 offset = time_to_origin_point_ - elapsed_time;
+ QuicTcpCongestionWindow delta_congestion_window = (kCubeCongestionWindowScale
+ * offset * offset * offset) >> kCubeScale;
+
+ QuicTcpCongestionWindow target_congestion_window =
+ origin_point_congestion_window_ - delta_congestion_window;
+
+ // We have a new cubic congestion window.
+ last_target_congestion_window_ = target_congestion_window;
+
+ // Update estimated TCP congestion_window.
+ // Note: we do a normal Reno congestion avoidance calculation not the
+ // calculation described in section 3.3 TCP-friendly region of the document.
+ while (acked_packets_count_ >= estimated_tcp_congestion_window_) {
+ acked_packets_count_ -= estimated_tcp_congestion_window_;
+ estimated_tcp_congestion_window_++;
+ }
+ // Compute target congestion_window based on cubic target and estimated TCP
+ // congestion_window, use highest (fastest).
+ if (target_congestion_window < estimated_tcp_congestion_window_) {
+ target_congestion_window = estimated_tcp_congestion_window_;
+ }
+ DLOG(INFO) << "Target congestion_window:" << target_congestion_window;
+ return target_congestion_window;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/cubic.h b/chromium/net/quic/congestion_control/cubic.h
new file mode 100644
index 00000000000..e365bbe0ed7
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cubic.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+//
+// Cubic algorithm, helper class to TCP cubic.
+// For details see http://netsrv.csc.ncsu.edu/export/cubic_a_new_tcp_2008.pdf.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
+#define NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// TCP congestion window in QUIC is in packets, not bytes.
+typedef uint32 QuicTcpCongestionWindow;
+
+class NET_EXPORT_PRIVATE Cubic {
+ public:
+ explicit Cubic(const QuicClock* clock);
+
+ // Call after a timeout to reset the cubic state.
+ void Reset();
+
+ // Compute a new congestion window to use after a loss event.
+ // Returns the new congestion window in packets. The new congestion window is
+ // a multiplicative decrease of our current window.
+ QuicTcpCongestionWindow CongestionWindowAfterPacketLoss(
+ QuicTcpCongestionWindow current);
+
+ // Compute a new congestion window to use after a received ACK.
+ // Returns the new congestion window in packets. The new congestion window
+ // follows a cubic function that depends on the time passed since last
+ // packet loss.
+ QuicTcpCongestionWindow CongestionWindowAfterAck(
+ QuicTcpCongestionWindow current,
+ QuicTime::Delta delay_min);
+
+ protected:
+ // Calculates the cubic root using a table lookup followed by one Newton-
+ // Raphson iteration.
+ uint32 CubeRoot(uint64 a);
+
+ private:
+ static const QuicTime::Delta MaxCubicTimeInterval() {
+ return QuicTime::Delta::FromMilliseconds(30);
+ }
+
+ const QuicClock* clock_;
+
+ // Time when this cycle started, after last loss event.
+ QuicTime epoch_;
+
+ // Time when we updated last_congestion_window.
+ QuicTime last_update_time_;
+
+ // Last congestion window (in packets) used.
+ QuicTcpCongestionWindow last_congestion_window_;
+
+ // Max congestion window (in packets) used just before last loss event.
+ // Note: to improve fairness to other streams an additional back off is
+ // applied to this value if the new value is below our latest value.
+ QuicTcpCongestionWindow last_max_congestion_window_;
+
+ // Number of acked packets since the cycle started (epoch).
+ uint32 acked_packets_count_;
+
+ // TCP Reno equivalent congestion window in packets.
+ QuicTcpCongestionWindow estimated_tcp_congestion_window_;
+
+ // Origin point of cubic function.
+ QuicTcpCongestionWindow origin_point_congestion_window_;
+
+ // Time to origin point of cubic function in 2^10 fractions of a second.
+ uint32 time_to_origin_point_;
+
+ // Last congestion window in packets computed by cubic function.
+ QuicTcpCongestionWindow last_target_congestion_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(Cubic);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_CUBIC_H_
diff --git a/chromium/net/quic/congestion_control/cubic_test.cc b/chromium/net/quic/congestion_control/cubic_test.cc
new file mode 100644
index 00000000000..84f2a7bb53d
--- /dev/null
+++ b/chromium/net/quic/congestion_control/cubic_test.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/cubic.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class CubicPeer : public Cubic {
+ public:
+ explicit CubicPeer(QuicClock* clock)
+ : Cubic(clock) {
+ }
+ using Cubic::CubeRoot;
+};
+
+class CubicTest : public ::testing::Test {
+ protected:
+ CubicTest()
+ : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+ hundred_ms_(QuicTime::Delta::FromMilliseconds(100)) {
+ }
+ virtual void SetUp() {
+ cubic_.reset(new CubicPeer(&clock_));
+ }
+ const QuicTime::Delta one_ms_;
+ const QuicTime::Delta hundred_ms_;
+ MockClock clock_;
+ scoped_ptr<CubicPeer> cubic_;
+};
+
+TEST_F(CubicTest, CubeRootLow) {
+ for (uint32 i = 1; i < 256; ++i) {
+ uint64 cube = i * i * i;
+ uint8 cube_root = cubic_->CubeRoot(cube);
+ EXPECT_EQ(i, cube_root);
+ }
+}
+
+TEST_F(CubicTest, CubeRootHigh) {
+ // Test the range we will opperate in, 1300 to 130 000.
+ // We expect some loss in accuracy, accepting +-0.2%.
+ for (uint64 i = 1300; i < 20000; i += 100) {
+ uint64 cube = i * i * i;
+ uint32 cube_root = cubic_->CubeRoot(cube);
+ uint32 margin = cube_root >> 9; // Calculate 0.2% roughly by
+ // dividing by 512.
+ EXPECT_LE(i - margin, cube_root);
+ EXPECT_GE(i + margin, cube_root);
+ }
+ for (uint64 i = 20000; i < 130000; i *= 2) {
+ uint64 cube = i * i * i;
+ uint32 cube_root = cubic_->CubeRoot(cube);
+ uint32 margin = cube_root >> 9;
+ EXPECT_LE(i - margin, cube_root);
+ EXPECT_GE(i + margin, cube_root);
+ }
+}
+
+TEST_F(CubicTest, AboveOrgin) {
+ // Convex growth.
+ const QuicTime::Delta rtt_min = hundred_ms_;
+ uint32 current_cwnd = 10;
+ uint32 expected_cwnd = current_cwnd + 1;
+ // Initialize the state.
+ clock_.AdvanceTime(one_ms_);
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ current_cwnd = expected_cwnd;
+ // Normal TCP phase.
+ for (int i = 0; i < 48; ++i) {
+ for (uint32 n = 1; n < current_cwnd; ++n) {
+ // Call once per ACK.
+ EXPECT_EQ(current_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ current_cwnd = cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min);
+ EXPECT_EQ(expected_cwnd, current_cwnd);
+ expected_cwnd++;
+ }
+ // Cubic phase.
+ for (int j = 48; j < 100; ++j) {
+ for (uint32 n = 1; n < current_cwnd; ++n) {
+ // Call once per ACK.
+ EXPECT_EQ(current_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ current_cwnd = cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min);
+ }
+ float elapsed_time_s = 10.0f + 0.1f; // We need to add the RTT here.
+ expected_cwnd = 11 + (elapsed_time_s * elapsed_time_s * elapsed_time_s * 410)
+ / 1024;
+ EXPECT_EQ(expected_cwnd, current_cwnd);
+}
+
+TEST_F(CubicTest, LossEvents) {
+ const QuicTime::Delta rtt_min = hundred_ms_;
+ uint32 current_cwnd = 422;
+ uint32 expected_cwnd = current_cwnd + 1;
+ // Initialize the state.
+ clock_.AdvanceTime(one_ms_);
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ expected_cwnd = current_cwnd * 717 / 1024;
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterPacketLoss(current_cwnd));
+ expected_cwnd = current_cwnd * 717 / 1024;
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterPacketLoss(current_cwnd));
+}
+
+TEST_F(CubicTest, BelowOrgin) {
+ // Concave growth.
+ const QuicTime::Delta rtt_min = hundred_ms_;
+ uint32 current_cwnd = 422;
+ uint32 expected_cwnd = current_cwnd + 1;
+ // Initialize the state.
+ clock_.AdvanceTime(one_ms_);
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ expected_cwnd = current_cwnd * 717 / 1024;
+ EXPECT_EQ(expected_cwnd,
+ cubic_->CongestionWindowAfterPacketLoss(current_cwnd));
+ current_cwnd = expected_cwnd;
+ // First update after epoch.
+ current_cwnd = cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min);
+ // Cubic phase.
+ for (int i = 0; i < 54; ++i) {
+ for (uint32 n = 1; n < current_cwnd; ++n) {
+ // Call once per ACK.
+ EXPECT_EQ(current_cwnd,
+ cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min));
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ current_cwnd = cubic_->CongestionWindowAfterAck(current_cwnd, rtt_min);
+ }
+ expected_cwnd = 422;
+ EXPECT_EQ(expected_cwnd, current_cwnd);
+}
+
+} // namespace testing
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/fix_rate_receiver.cc b/chromium/net/quic/congestion_control/fix_rate_receiver.cc
new file mode 100644
index 00000000000..950b49c0d6b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/fix_rate_receiver.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/fix_rate_receiver.h"
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+
+namespace {
+ static const int kInitialBitrate = 100000; // In bytes per second.
+}
+
+namespace net {
+
+FixRateReceiver::FixRateReceiver()
+ : configured_rate_(QuicBandwidth::FromBytesPerSecond(kInitialBitrate)) {
+}
+
+bool FixRateReceiver::GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) {
+ feedback->type = kFixRate;
+ feedback->fix_rate.bitrate = configured_rate_;
+ return true;
+}
+
+void FixRateReceiver::RecordIncomingPacket(
+ QuicByteCount /*bytes*/,
+ QuicPacketSequenceNumber /*sequence_number*/,
+ QuicTime /*timestamp*/,
+ bool /*recovered*/) {
+ // Nothing to do for this simple implementation.
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/fix_rate_receiver.h b/chromium/net/quic/congestion_control/fix_rate_receiver.h
new file mode 100644
index 00000000000..690f13880f1
--- /dev/null
+++ b/chromium/net/quic/congestion_control/fix_rate_receiver.h
@@ -0,0 +1,44 @@
+// Copyright (c) 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.
+//
+// Fix rate receive side congestion control, used for initial testing.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_FIX_RATE_RECEIVER_H_
+#define NET_QUIC_CONGESTION_CONTROL_FIX_RATE_RECEIVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+
+namespace net {
+
+namespace test {
+class FixRateReceiverPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE FixRateReceiver : public ReceiveAlgorithmInterface {
+ public:
+ FixRateReceiver();
+
+ // Implements ReceiveAlgorithmInterface.
+ virtual bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) OVERRIDE;
+
+ // Implements ReceiveAlgorithmInterface.
+ virtual void RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool recovered) OVERRIDE;
+ private:
+ friend class test::FixRateReceiverPeer;
+
+ QuicBandwidth configured_rate_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixRateReceiver);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_FIX_RATE_RECEIVER_H_
diff --git a/chromium/net/quic/congestion_control/fix_rate_sender.cc b/chromium/net/quic/congestion_control/fix_rate_sender.cc
new file mode 100644
index 00000000000..dff52cf305d
--- /dev/null
+++ b/chromium/net/quic/congestion_control/fix_rate_sender.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/fix_rate_sender.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "net/quic/quic_protocol.h"
+
+namespace {
+ const int kInitialBitrate = 100000; // In bytes per second.
+ const uint64 kWindowSizeUs = 10000; // 10 ms.
+}
+
+namespace net {
+
+FixRateSender::FixRateSender(const QuicClock* clock)
+ : bitrate_(QuicBandwidth::FromBytesPerSecond(kInitialBitrate)),
+ fix_rate_leaky_bucket_(bitrate_),
+ paced_sender_(bitrate_),
+ data_in_flight_(0),
+ latest_rtt_(QuicTime::Delta::Zero()) {
+ DLOG(INFO) << "FixRateSender";
+}
+
+FixRateSender::~FixRateSender() {
+}
+
+void FixRateSender::OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& /*sent_packets*/) {
+ DCHECK(feedback.type == kFixRate) <<
+ "Invalid incoming CongestionFeedbackType:" << feedback.type;
+ if (feedback.type == kFixRate) {
+ bitrate_ = feedback.fix_rate.bitrate;
+ fix_rate_leaky_bucket_.SetDrainingRate(feedback_receive_time, bitrate_);
+ paced_sender_.UpdateBandwidthEstimate(feedback_receive_time, bitrate_);
+ }
+ // Silently ignore invalid messages in release mode.
+}
+
+void FixRateSender::OnIncomingAck(
+ QuicPacketSequenceNumber /*acked_sequence_number*/,
+ QuicByteCount bytes_acked,
+ QuicTime::Delta rtt) {
+ // RTT can't be negative.
+ DCHECK_LE(0, rtt.ToMicroseconds());
+
+ data_in_flight_ -= bytes_acked;
+ if (rtt.IsInfinite()) {
+ return;
+ }
+ latest_rtt_ = rtt;
+}
+
+void FixRateSender::OnIncomingLoss(QuicTime /*ack_receive_time*/) {
+ // Ignore losses for fix rate sender.
+}
+
+void FixRateSender::SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber /*sequence_number*/,
+ QuicByteCount bytes,
+ Retransmission is_retransmission) {
+ fix_rate_leaky_bucket_.Add(sent_time, bytes);
+ paced_sender_.SentPacket(sent_time, bytes);
+ if (is_retransmission == NOT_RETRANSMISSION) {
+ data_in_flight_ += bytes;
+ }
+}
+
+void FixRateSender::AbandoningPacket(
+ QuicPacketSequenceNumber /*sequence_number*/,
+ QuicByteCount /*abandoned_bytes*/) {
+}
+
+QuicTime::Delta FixRateSender::TimeUntilSend(
+ QuicTime now,
+ Retransmission /*is_retransmission*/,
+ HasRetransmittableData /*has_retransmittable_data*/,
+ IsHandshake /* handshake */) {
+ if (CongestionWindow() > fix_rate_leaky_bucket_.BytesPending(now)) {
+ if (CongestionWindow() <= data_in_flight_) {
+ // We need an ack before we send more.
+ return QuicTime::Delta::Infinite();
+ }
+ return paced_sender_.TimeUntilSend(now, QuicTime::Delta::Zero());
+ }
+ QuicTime::Delta time_remaining = fix_rate_leaky_bucket_.TimeRemaining(now);
+ if (time_remaining.IsZero()) {
+ // We need an ack before we send more.
+ return QuicTime::Delta::Infinite();
+ }
+ return paced_sender_.TimeUntilSend(now, time_remaining);
+}
+
+QuicByteCount FixRateSender::CongestionWindow() {
+ QuicByteCount window_size_bytes = bitrate_.ToBytesPerPeriod(
+ QuicTime::Delta::FromMicroseconds(kWindowSizeUs));
+ // Make sure window size is not less than a packet.
+ return std::max(kMaxPacketSize, window_size_bytes);
+}
+
+QuicBandwidth FixRateSender::BandwidthEstimate() {
+ return bitrate_;
+}
+
+QuicTime::Delta FixRateSender::SmoothedRtt() {
+ // TODO(satyamshekhar): Calculate and return smoothed rtt.
+ return latest_rtt_;
+}
+
+QuicTime::Delta FixRateSender::RetransmissionDelay() {
+ // TODO(pwestin): Calculate and return retransmission delay.
+ // Use 2 * the latest RTT for now.
+ return latest_rtt_.Add(latest_rtt_);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/fix_rate_sender.h b/chromium/net/quic/congestion_control/fix_rate_sender.h
new file mode 100644
index 00000000000..38cebad165f
--- /dev/null
+++ b/chromium/net/quic/congestion_control/fix_rate_sender.h
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+//
+// Fix rate send side congestion control, used for testing.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_FIX_RATE_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_FIX_RATE_SENDER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/congestion_control/leaky_bucket.h"
+#include "net/quic/congestion_control/paced_sender.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE FixRateSender : public SendAlgorithmInterface {
+ public:
+ explicit FixRateSender(const QuicClock* clock);
+ virtual ~FixRateSender();
+
+ // Start implementation of SendAlgorithmInterface.
+ virtual void OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& sent_packets) OVERRIDE;
+ virtual void OnIncomingAck(QuicPacketSequenceNumber acked_sequence_number,
+ QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) OVERRIDE;
+ virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE;
+ virtual void SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber equence_number,
+ QuicByteCount bytes,
+ Retransmission is_retransmission) OVERRIDE;
+ virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes) OVERRIDE;
+ virtual QuicTime::Delta TimeUntilSend(
+ QuicTime now,
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) OVERRIDE;
+ virtual QuicBandwidth BandwidthEstimate() OVERRIDE;
+ virtual QuicTime::Delta SmoothedRtt() OVERRIDE;
+ virtual QuicTime::Delta RetransmissionDelay() OVERRIDE;
+ // End implementation of SendAlgorithmInterface.
+
+ private:
+ QuicByteCount CongestionWindow();
+
+ QuicBandwidth bitrate_;
+ LeakyBucket fix_rate_leaky_bucket_;
+ PacedSender paced_sender_;
+ QuicByteCount data_in_flight_;
+ QuicTime::Delta latest_rtt_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixRateSender);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_FIX_RATE_SENDER_H_
diff --git a/chromium/net/quic/congestion_control/fix_rate_test.cc b/chromium/net/quic/congestion_control/fix_rate_test.cc
new file mode 100644
index 00000000000..f914ed671a6
--- /dev/null
+++ b/chromium/net/quic/congestion_control/fix_rate_test.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 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.
+
+// Test for FixRate sender and receiver.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/fix_rate_receiver.h"
+#include "net/quic/congestion_control/fix_rate_sender.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class FixRateReceiverPeer : public FixRateReceiver {
+ public:
+ FixRateReceiverPeer()
+ : FixRateReceiver() {
+ }
+ void SetBitrate(QuicBandwidth fix_rate) {
+ FixRateReceiver::configured_rate_ = fix_rate;
+ }
+};
+
+class FixRateTest : public ::testing::Test {
+ protected:
+ FixRateTest()
+ : rtt_(QuicTime::Delta::FromMilliseconds(30)),
+ sender_(new FixRateSender(&clock_)),
+ receiver_(new FixRateReceiverPeer()),
+ start_(clock_.Now()) {
+ // Make sure clock does not start at 0.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(2));
+ }
+ const QuicTime::Delta rtt_;
+ MockClock clock_;
+ SendAlgorithmInterface::SentPacketsMap unused_packet_map_;
+ scoped_ptr<FixRateSender> sender_;
+ scoped_ptr<FixRateReceiverPeer> receiver_;
+ const QuicTime start_;
+};
+
+TEST_F(FixRateTest, ReceiverAPI) {
+ QuicCongestionFeedbackFrame feedback;
+ QuicTime timestamp(QuicTime::Zero());
+ receiver_->SetBitrate(QuicBandwidth::FromKBytesPerSecond(300));
+ receiver_->RecordIncomingPacket(1, 1, timestamp, false);
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ EXPECT_EQ(kFixRate, feedback.type);
+ EXPECT_EQ(300000u, feedback.fix_rate.bitrate.ToBytesPerSecond());
+}
+
+TEST_F(FixRateTest, SenderAPI) {
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kFixRate;
+ feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(300);
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ unused_packet_map_);
+ EXPECT_EQ(300000, sender_->BandwidthEstimate().ToBytesPerSecond());
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ sender_->SentPacket(clock_.Now(), 1, kMaxPacketSize, NOT_RETRANSMISSION);
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ sender_->SentPacket(clock_.Now(), 2, kMaxPacketSize, NOT_RETRANSMISSION);
+ sender_->SentPacket(clock_.Now(), 3, 600, NOT_RETRANSMISSION);
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+ sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(2));
+ EXPECT_EQ(QuicTime::Delta::Infinite(), sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(8));
+ sender_->OnIncomingAck(1, kMaxPacketSize, rtt_);
+ sender_->OnIncomingAck(2, kMaxPacketSize, rtt_);
+ sender_->OnIncomingAck(3, 600, rtt_);
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+}
+
+TEST_F(FixRateTest, FixRatePacing) {
+ const QuicByteCount packet_size = 1200;
+ const QuicBandwidth bitrate = QuicBandwidth::FromKBytesPerSecond(240);
+ const int64 num_packets = 200;
+ QuicCongestionFeedbackFrame feedback;
+ receiver_->SetBitrate(QuicBandwidth::FromKBytesPerSecond(240));
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ unused_packet_map_);
+ QuicTime acc_advance_time(QuicTime::Zero());
+ QuicPacketSequenceNumber sequence_number = 0;
+ for (int i = 0; i < num_packets; i += 2) {
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+ NOT_HANDSHAKE).IsZero());
+ sender_->SentPacket(clock_.Now(), sequence_number++, packet_size,
+ NOT_RETRANSMISSION);
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+ NOT_HANDSHAKE).IsZero());
+ sender_->SentPacket(clock_.Now(), sequence_number++, packet_size,
+ NOT_RETRANSMISSION);
+ QuicTime::Delta advance_time = sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ clock_.AdvanceTime(advance_time);
+ sender_->OnIncomingAck(sequence_number - 1, packet_size, rtt_);
+ sender_->OnIncomingAck(sequence_number - 2, packet_size, rtt_);
+ acc_advance_time = acc_advance_time.Add(advance_time);
+ }
+ EXPECT_EQ(num_packets * packet_size * 1000000 / bitrate.ToBytesPerSecond(),
+ static_cast<uint64>(acc_advance_time.Subtract(start_)
+ .ToMicroseconds()));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/hybrid_slow_start.cc b/chromium/net/quic/congestion_control/hybrid_slow_start.cc
new file mode 100644
index 00000000000..8968dc94775
--- /dev/null
+++ b/chromium/net/quic/congestion_control/hybrid_slow_start.cc
@@ -0,0 +1,112 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+
+namespace net {
+
+// Note(pwestin): the magic clamping numbers come from the original code in
+// tcp_cubic.c.
+// Number of delay samples for detecting the increase of delay.
+const int kHybridStartMinSamples = 8;
+const int kHybridStartDelayFactorExp = 4; // 2^4 = 16
+const int kHybridStartDelayMinThresholdUs = 2000;
+const int kHybridStartDelayMaxThresholdUs = 16000;
+
+HybridSlowStart::HybridSlowStart(const QuicClock* clock)
+ : clock_(clock),
+ started_(false),
+ found_ack_train_(false),
+ found_delay_(false),
+ round_start_(QuicTime::Zero()),
+ end_sequence_number_(0),
+ last_time_(QuicTime::Zero()),
+ sample_count_(0),
+ current_rtt_(QuicTime::Delta::Zero()) {
+}
+
+void HybridSlowStart::Restart() {
+ found_ack_train_ = false;
+ found_delay_ = false;
+}
+
+void HybridSlowStart::Reset(QuicPacketSequenceNumber end_sequence_number) {
+ DLOG(INFO) << "Reset hybrid slow start @" << end_sequence_number;
+ round_start_ = last_time_ = clock_->ApproximateNow();
+ end_sequence_number_ = end_sequence_number;
+ current_rtt_ = QuicTime::Delta::Zero();
+ sample_count_ = 0;
+ started_ = true;
+}
+
+bool HybridSlowStart::EndOfRound(QuicPacketSequenceNumber ack) {
+ return end_sequence_number_ <= ack;
+}
+
+void HybridSlowStart::Update(QuicTime::Delta rtt, QuicTime::Delta delay_min) {
+ // The original code doesn't invoke this until we hit 16 packet per burst.
+ // Since the code handles lower than 16 grecefully and I removed that
+ // limit.
+ if (found_ack_train_ || found_delay_) {
+ return;
+ }
+ QuicTime current_time = clock_->ApproximateNow();
+
+ // First detection parameter - ack-train detection.
+ // Since slow start burst out packets we can indirectly estimate the inter-
+ // arrival time by looking at the arrival time of the ACKs if the ACKs are
+ // spread out more then half the minimum RTT packets are beeing spread out
+ // more than the capacity.
+ // This first trigger will not come into play until we hit roughly 4.8 Mbit/s.
+ // TODO(pwestin): we need to make sure our pacing don't trigger this detector.
+ if (current_time.Subtract(last_time_).ToMicroseconds() <=
+ kHybridStartDelayMinThresholdUs) {
+ last_time_ = current_time;
+ if (current_time.Subtract(round_start_).ToMicroseconds() >=
+ (delay_min.ToMicroseconds() >> 1)) {
+ found_ack_train_ = true;
+ }
+ }
+ // Second detection parameter - delay increase detection.
+ // Compare the minimum delay (current_rtt_) of the current
+ // burst of packets relative to the minimum delay during the session.
+ // Note: we only look at the first few(8) packets in each burst, since we
+ // only want to compare the lowest RTT of the burst relative to previous
+ // bursts.
+ sample_count_++;
+ if (sample_count_ <= kHybridStartMinSamples) {
+ if (current_rtt_.IsZero() || current_rtt_ > rtt) {
+ current_rtt_ = rtt;
+ }
+ }
+ // We only need to check this once.
+ if (sample_count_ == kHybridStartMinSamples) {
+ int accepted_variance_us = delay_min.ToMicroseconds() >>
+ kHybridStartDelayFactorExp;
+ accepted_variance_us = std::min(accepted_variance_us,
+ kHybridStartDelayMaxThresholdUs);
+ QuicTime::Delta accepted_variance = QuicTime::Delta::FromMicroseconds(
+ std::max(accepted_variance_us, kHybridStartDelayMinThresholdUs));
+
+ if (current_rtt_ > delay_min.Add(accepted_variance)) {
+ found_delay_ = true;
+ }
+ }
+}
+
+bool HybridSlowStart::Exit() {
+ // If either one of the two conditions are met we exit from slow start
+ // immediately.
+ if (found_ack_train_ || found_delay_) {
+ return true;
+ }
+ return false;
+}
+
+QuicTime::Delta HybridSlowStart::SmoothedRtt() {
+ // TODO(satyamshekhar): Calculate and return smooth average of rtt over time.
+ return current_rtt_;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/hybrid_slow_start.h b/chromium/net/quic/congestion_control/hybrid_slow_start.h
new file mode 100644
index 00000000000..b0e424883da
--- /dev/null
+++ b/chromium/net/quic/congestion_control/hybrid_slow_start.h
@@ -0,0 +1,67 @@
+// Copyright (c) 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.
+//
+// This class is a helper class to TcpCubicSender.
+// Slow start is the initial startup phase of TCP, it lasts until first packet
+// loss. This class implements hybrid slow start of the TCP cubic send side
+// congestion algorithm. The key feaure of hybrid slow start is that it tries to
+// avoid running into the wall too hard during the slow start phase, which
+// the traditional TCP implementation does.
+// http://netsrv.csc.ncsu.edu/export/hybridstart_pfldnet08.pdf
+// http://research.csc.ncsu.edu/netsrv/sites/default/files/hystart_techreport_2008.pdf
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
+#define NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE HybridSlowStart {
+ public:
+ explicit HybridSlowStart(const QuicClock* clock);
+
+ // Start a new slow start phase.
+ void Restart();
+
+ // Returns true if this ack the last sequence number of our current slow start
+ // round.
+ // Call Reset if this returns true.
+ bool EndOfRound(QuicPacketSequenceNumber ack);
+
+ // Call for each round (burst) in the slow start phase.
+ void Reset(QuicPacketSequenceNumber end_sequence_number);
+
+ // rtt: it the RTT for this ack packet.
+ // delay_min: is the lowest delay (RTT) we have seen during the session.
+ void Update(QuicTime::Delta rtt, QuicTime::Delta delay_min);
+
+ // Returns true when we should exit slow start.
+ bool Exit();
+
+ bool started() { return started_; }
+
+ QuicTime::Delta SmoothedRtt();
+
+ private:
+ const QuicClock* clock_;
+ bool started_;
+ bool found_ack_train_;
+ bool found_delay_;
+ QuicTime round_start_; // Beginning of each slow start round.
+ QuicPacketSequenceNumber end_sequence_number_; // End of slow start round.
+ QuicTime last_time_; // Last time when the ACK spacing was close.
+ uint8 sample_count_; // Number of samples to decide current RTT.
+ QuicTime::Delta current_rtt_; // The minimum rtt of current round.
+
+ DISALLOW_COPY_AND_ASSIGN(HybridSlowStart);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_HYBRID_SLOW_START_H_
diff --git a/chromium/net/quic/congestion_control/hybrid_slow_start_test.cc b/chromium/net/quic/congestion_control/hybrid_slow_start_test.cc
new file mode 100644
index 00000000000..0a9f91fd3f0
--- /dev/null
+++ b/chromium/net/quic/congestion_control/hybrid_slow_start_test.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class HybridSlowStartTest : public ::testing::Test {
+ protected:
+ HybridSlowStartTest()
+ : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+ rtt_(QuicTime::Delta::FromMilliseconds(60)) {
+ }
+ virtual void SetUp() {
+ slowStart_.reset(new HybridSlowStart(&clock_));
+ }
+ const QuicTime::Delta one_ms_;
+ const QuicTime::Delta rtt_;
+ MockClock clock_;
+ scoped_ptr<HybridSlowStart> slowStart_;
+};
+
+TEST_F(HybridSlowStartTest, Simple) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicPacketSequenceNumber end_sequence_number = 3;
+ slowStart_->Reset(end_sequence_number);
+
+ EXPECT_FALSE(slowStart_->EndOfRound(sequence_number++));
+
+ // Test duplicates.
+ EXPECT_FALSE(slowStart_->EndOfRound(sequence_number));
+
+ EXPECT_FALSE(slowStart_->EndOfRound(sequence_number++));
+ EXPECT_TRUE(slowStart_->EndOfRound(sequence_number++));
+
+ // Test without a new registered end_sequence_number;
+ EXPECT_TRUE(slowStart_->EndOfRound(sequence_number++));
+
+ end_sequence_number = 20;
+ slowStart_->Reset(end_sequence_number);
+ while (sequence_number < end_sequence_number) {
+ EXPECT_FALSE(slowStart_->EndOfRound(sequence_number++));
+ }
+ EXPECT_TRUE(slowStart_->EndOfRound(sequence_number++));
+}
+
+TEST_F(HybridSlowStartTest, AckTrain) {
+ // At a typical RTT 60 ms, assuming that the inter arrival is 1 ms,
+ // we expect to be able to send a burst of 30 packet before we trigger the
+ // ack train detection.
+ const int kMaxLoopCount = 5;
+ QuicPacketSequenceNumber sequence_number = 2;
+ QuicPacketSequenceNumber end_sequence_number = 2;
+ for (int burst = 0; burst < kMaxLoopCount; ++burst) {
+ slowStart_->Reset(end_sequence_number);
+ do {
+ clock_.AdvanceTime(one_ms_);
+ slowStart_->Update(rtt_, rtt_);
+ EXPECT_FALSE(slowStart_->Exit());
+ } while (!slowStart_->EndOfRound(sequence_number++));
+ end_sequence_number *= 2; // Exponential growth.
+ }
+ slowStart_->Reset(end_sequence_number);
+
+ for (int n = 0; n < 29 && !slowStart_->EndOfRound(sequence_number++); ++n) {
+ clock_.AdvanceTime(one_ms_);
+ slowStart_->Update(rtt_, rtt_);
+ EXPECT_FALSE(slowStart_->Exit());
+ }
+ clock_.AdvanceTime(one_ms_);
+ slowStart_->Update(rtt_, rtt_);
+ EXPECT_TRUE(slowStart_->Exit());
+}
+
+TEST_F(HybridSlowStartTest, Delay) {
+ // We expect to detect the increase at +1/16 of the RTT; hence at a typical
+ // RTT of 60ms the detection will happen at 63.75 ms.
+ const int kHybridStartMinSamples = 8; // Number of acks required to trigger.
+
+ QuicPacketSequenceNumber end_sequence_number = 1;
+ slowStart_->Reset(end_sequence_number++);
+
+ // Will not trigger since our lowest RTT in our burst is the same as the long
+ // term RTT provided.
+ for (int n = 0; n < kHybridStartMinSamples; ++n) {
+ slowStart_->Update(rtt_.Add(QuicTime::Delta::FromMilliseconds(n)), rtt_);
+ EXPECT_FALSE(slowStart_->Exit());
+ }
+ slowStart_->Reset(end_sequence_number++);
+ for (int n = 1; n < kHybridStartMinSamples; ++n) {
+ slowStart_->Update(rtt_.Add(QuicTime::Delta::FromMilliseconds(n + 4)),
+ rtt_);
+ EXPECT_FALSE(slowStart_->Exit());
+ }
+ // Expect to trigger since all packets in this burst was above the long term
+ // RTT provided.
+ slowStart_->Update(rtt_.Add(QuicTime::Delta::FromMilliseconds(4)), rtt_);
+ EXPECT_TRUE(slowStart_->Exit());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.cc b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.cc
new file mode 100644
index 00000000000..0a15a74dffc
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_bitrate_ramp_up.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "net/quic/congestion_control/cube_root.h"
+#include "net/quic/quic_protocol.h"
+
+namespace {
+// The following constants are in 2^10 fractions of a second instead of ms to
+// allow a 10 shift right to divide.
+const int kCubeScale = 40; // 1024*1024^3 (first 1024 is from 0.100^3)
+ // where 0.100 is 100 ms which is the scaling
+ // round trip time.
+// TODO(pwestin): Tuning parameter, currently close to TCP cubic at 100ms RTT.
+const int kPacedCubeScale = 6000;
+const uint64 kCubeFactor = (GG_UINT64_C(1) << kCubeScale) / kPacedCubeScale;
+} // namespace
+
+namespace net {
+
+InterArrivalBitrateRampUp::InterArrivalBitrateRampUp(const QuicClock* clock)
+ : clock_(clock),
+ current_rate_(QuicBandwidth::Zero()),
+ channel_estimate_(QuicBandwidth::Zero()),
+ available_channel_estimate_(QuicBandwidth::Zero()),
+ halfway_point_(QuicBandwidth::Zero()),
+ epoch_(QuicTime::Zero()),
+ last_update_time_(QuicTime::Zero()) {
+}
+
+void InterArrivalBitrateRampUp::Reset(QuicBandwidth new_rate,
+ QuicBandwidth available_channel_estimate,
+ QuicBandwidth channel_estimate) {
+ epoch_ = clock_->ApproximateNow();
+ last_update_time_ = epoch_;
+ available_channel_estimate_ = std::max(new_rate, available_channel_estimate);
+ channel_estimate_ = std::max(channel_estimate, available_channel_estimate_);
+
+ halfway_point_ = available_channel_estimate_.Add(
+ (channel_estimate_.Subtract(available_channel_estimate_)).Scale(0.5f));
+
+ if (new_rate < available_channel_estimate_) {
+ time_to_origin_point_ = CalcuateTimeToOriginPoint(
+ available_channel_estimate_.Subtract(new_rate));
+ } else if (new_rate >= channel_estimate_) {
+ time_to_origin_point_ = 0;
+ } else if (new_rate >= halfway_point_) {
+ time_to_origin_point_ =
+ CalcuateTimeToOriginPoint(channel_estimate_.Subtract(new_rate));
+ } else {
+ time_to_origin_point_ = CalcuateTimeToOriginPoint(
+ new_rate.Subtract(available_channel_estimate_));
+ }
+ current_rate_ = new_rate;
+ DLOG(INFO) << "Reset; time to origin point:" << time_to_origin_point_;
+}
+
+void InterArrivalBitrateRampUp::UpdateChannelEstimate(
+ QuicBandwidth channel_estimate) {
+ if (available_channel_estimate_ > channel_estimate ||
+ current_rate_ > channel_estimate ||
+ channel_estimate_ == channel_estimate) {
+ // Ignore, because one of the following reasons:
+ // 1) channel estimate is bellow our current available estimate which we
+ // value higher that this estimate.
+ // 2) channel estimate is bellow our current send rate.
+ // 3) channel estimate has not changed.
+ return;
+ }
+ if (available_channel_estimate_ == halfway_point_ &&
+ channel_estimate_ == halfway_point_) {
+ // First time we get a usable channel estimate.
+ channel_estimate_ = channel_estimate;
+ halfway_point_ = available_channel_estimate_.Add(
+ (channel_estimate_.Subtract(available_channel_estimate_).Scale(0.5f)));
+ DLOG(INFO) << "UpdateChannelEstimate; first usable value:"
+ << channel_estimate.ToKBitsPerSecond() << " Kbits/s";
+ return;
+ }
+ if (current_rate_ < halfway_point_) {
+ // Update channel estimate without recalculating if we are bellow the
+ // halfway point.
+ channel_estimate_ = channel_estimate;
+ return;
+ }
+ // We are between halfway point and our channel_estimate.
+ epoch_ = clock_->ApproximateNow();
+ last_update_time_ = epoch_;
+ channel_estimate_ = channel_estimate;
+
+ time_to_origin_point_ =
+ CalcuateTimeToOriginPoint(channel_estimate_.Subtract(current_rate_));
+
+ DLOG(INFO) << "UpdateChannelEstimate; time to origin point:"
+ << time_to_origin_point_;
+}
+
+QuicBandwidth InterArrivalBitrateRampUp::GetNewBitrate(
+ QuicBandwidth sent_bitrate) {
+ DCHECK(epoch_.IsInitialized());
+ QuicTime current_time = clock_->ApproximateNow();
+ // Cubic is "independent" of RTT, the update is limited by the time elapsed.
+ if (current_time.Subtract(last_update_time_) <= MaxCubicTimeInterval()) {
+ return current_rate_;
+ }
+ QuicTime::Delta time_from_last_update =
+ current_time.Subtract(last_update_time_);
+
+ last_update_time_ = current_time;
+
+ if (!sent_bitrate.IsZero() &&
+ sent_bitrate.Add(sent_bitrate) < current_rate_) {
+ // Don't go up in bitrate when we are not sending.
+ // We need to update the epoch to reflect this state.
+ epoch_ = epoch_.Add(time_from_last_update);
+ DLOG(INFO) << "Don't increase; our sent bitrate is:"
+ << sent_bitrate.ToKBitsPerSecond() << " Kbits/s"
+ << " current target rate is:"
+ << current_rate_.ToKBitsPerSecond() << " Kbits/s";
+ return current_rate_;
+ }
+ QuicTime::Delta time_from_epoch = current_time.Subtract(epoch_);
+
+ // Change the time unit from microseconds to 2^10 fractions per second. This
+ // is done to allow us to use shift as a divide operator.
+ int64 elapsed_time = (time_from_epoch.ToMicroseconds() << 10) /
+ kNumMicrosPerSecond;
+
+ int64 offset = time_to_origin_point_ - elapsed_time;
+ // Note: using int64 since QuicBandwidth can't be negative
+ int64 delta_pace_kbps = (kPacedCubeScale * offset * offset * offset) >>
+ kCubeScale;
+
+ bool start_bellow_halfway_point = false;
+ if (current_rate_ < halfway_point_) {
+ start_bellow_halfway_point = true;
+
+ // available_channel_estimate_ is the orgin of the cubic function.
+ QuicBandwidth current_rate = QuicBandwidth::FromBytesPerSecond(
+ available_channel_estimate_.ToBytesPerSecond() -
+ (delta_pace_kbps << 10));
+
+ if (start_bellow_halfway_point && current_rate >= halfway_point_) {
+ // We passed the halfway point, recalculate with new orgin.
+ epoch_ = clock_->ApproximateNow();
+ // channel_estimate_ is the new orgin of the cubic function.
+ if (current_rate >= channel_estimate_) {
+ time_to_origin_point_ = 0;
+ } else {
+ time_to_origin_point_ =
+ CalcuateTimeToOriginPoint(channel_estimate_.Subtract(current_rate));
+ }
+ DLOG(INFO) << "Passed the halfway point; time to origin point:"
+ << time_to_origin_point_;
+ }
+ current_rate_ = current_rate;
+ } else {
+ // channel_estimate_ is the orgin of the cubic function.
+ current_rate_ = QuicBandwidth::FromBytesPerSecond(
+ channel_estimate_.ToBytesPerSecond() - (delta_pace_kbps << 10));
+ }
+ return current_rate_;
+}
+
+uint32 InterArrivalBitrateRampUp::CalcuateTimeToOriginPoint(
+ QuicBandwidth rate_difference) const {
+ return CubeRoot::Root(kCubeFactor * rate_difference.ToKBytesPerSecond());
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h
new file mode 100644
index 00000000000..931992391a5
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 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.
+
+// Ramp up bitrate from a start point normally our "current_rate" as long as we
+// have no packet loss or delay events.
+// The first half of the ramp up curve follows a cubic function with its orgin
+// at the estimated available bandwidth, onece the bitrate pass the halfway
+// point between the estimated available bandwidth and the estimated max
+// bandwidth it will follw a new cubic function with its orgin at the estimated
+// max bandwidth.
+#ifndef NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_BITRATE_RAMP_UP_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_BITRATE_RAMP_UP_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE InterArrivalBitrateRampUp {
+ public:
+ explicit InterArrivalBitrateRampUp(const QuicClock* clock);
+
+ // Call after a decision to lower the bitrate and after a probe.
+ void Reset(QuicBandwidth current_rate,
+ QuicBandwidth available_channel_estimate,
+ QuicBandwidth channel_estimate);
+
+ // Call everytime we get a new channel estimate.
+ void UpdateChannelEstimate(QuicBandwidth channel_estimate);
+
+ // Compute a new send pace to use.
+ QuicBandwidth GetNewBitrate(QuicBandwidth sent_bitrate);
+
+ private:
+ uint32 CalcuateTimeToOriginPoint(QuicBandwidth rate_difference) const;
+
+ static const QuicTime::Delta MaxCubicTimeInterval() {
+ return QuicTime::Delta::FromMilliseconds(30);
+ }
+
+ const QuicClock* clock_;
+
+ QuicBandwidth current_rate_;
+ QuicBandwidth channel_estimate_;
+ QuicBandwidth available_channel_estimate_;
+ QuicBandwidth halfway_point_;
+
+ // Time when this cycle started, after a Reset.
+ QuicTime epoch_;
+
+ // Time when we updated current_rate_.
+ QuicTime last_update_time_;
+
+ // Time to origin point of cubic function in 2^10 fractions of a second.
+ uint32 time_to_origin_point_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalBitrateRampUp);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_BITRATE_RAMP_UP_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up_test.cc b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up_test.cc
new file mode 100644
index 00000000000..acae78d2056
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_bitrate_ramp_up_test.cc
@@ -0,0 +1,404 @@
+// Copyright (c) 2013 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class InterArrivalBitrateRampUpTest : public ::testing::Test {
+ protected:
+ InterArrivalBitrateRampUpTest()
+ : one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+ hundred_ms_(QuicTime::Delta::FromMilliseconds(100)),
+ bitrate_ramp_up_(&clock_) {
+ }
+ virtual void SetUp() {
+ clock_.AdvanceTime(one_ms_);
+ }
+ const QuicTime::Delta one_ms_;
+ const QuicTime::Delta hundred_ms_;
+ MockClock clock_;
+ InterArrivalBitrateRampUp bitrate_ramp_up_;
+};
+
+TEST_F(InterArrivalBitrateRampUpTest, GoodEstimates) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(100);
+ QuicBandwidth available_channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(400);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // First concave growth, towards available_channel_estimate.
+ for (int i = 0; i < 25; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(available_channel_estimate, sent_bitrate);
+ EXPECT_LE(start_rate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+
+ // First convex growth, from available_channel_estimate.
+ for (int j = 0; j < 25; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+
+ // Second concave growth, towards channel_estimate.
+ for (int i = 0; i < 24; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+
+ // Second convex growth, from channel_estimate.
+ for (int j = 0; j < 25; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 100000,
+ sent_bitrate.ToBytesPerSecond(), 10000);
+
+ // Verify that we increase cubic.
+ for (int j = 0; j < 23; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 750000,
+ sent_bitrate.ToBytesPerSecond(), 10000);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, GoodEstimatesLimitedSendRate) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(100);
+ QuicBandwidth available_channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth max_sent_rate =
+ QuicBandwidth::FromKBytesPerSecond(125);
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(400);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // First concave growth, towards available_channel_estimate.
+ // Should pass without being affected by the max_sent_rate.
+ for (int i = 0; i < 25; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ // Cap our previus sent rate.
+ sent_bitrate = std::min(sent_bitrate, max_sent_rate);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(available_channel_estimate, sent_bitrate);
+ EXPECT_LE(start_rate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ // Cap our previus sent rate.
+ sent_bitrate = std::min(sent_bitrate, max_sent_rate);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+
+ // First convex growth, from available_channel_estimate.
+ for (int j = 0; j < 25; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ // Cap our previus sent rate.
+ sent_bitrate = std::min(sent_bitrate, max_sent_rate);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = std::min(sent_bitrate, max_sent_rate);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ // We expect 2 * sent_bitrate to cap the rate.
+ EXPECT_LE(max_sent_rate.Add(max_sent_rate), sent_bitrate);
+ // Remove our sent cap.
+ // Expect bitrate to continue to ramp from its previous rate.
+ for (int j = 0; j < 5; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_LE(max_sent_rate.Add(max_sent_rate), sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, GoodEstimatesCloseToChannelEstimate) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(100);
+ QuicBandwidth available_channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(250);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // First concave growth, towards available_channel_estimate.
+ for (int i = 0; i < 25; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(available_channel_estimate, sent_bitrate);
+ EXPECT_LE(start_rate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+
+ // First convex growth, from available_channel_estimate.
+ for (int j = 0; j < 15; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+
+ // Second concave growth, towards channel_estimate.
+ for (int i = 0; i < 14; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+
+ // Second convex growth, from channel_estimate.
+ for (int j = 0; j < 25; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 100000,
+ sent_bitrate.ToBytesPerSecond(), 10000);
+
+ // Verify that we increase cubic.
+ for (int j = 0; j < 24; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 780000,
+ sent_bitrate.ToBytesPerSecond(), 20000);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, UncertainEstimates) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(100);
+ QuicBandwidth available_channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(400 * 0.7f);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // First concave growth, towards available_channel_estimate.
+ for (int i = 0; i < 20; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(available_channel_estimate, sent_bitrate);
+ EXPECT_LE(start_rate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+
+ // First convex growth, from available_channel_estimate.
+ for (int j = 0; j < 23; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+
+ // Second concave growth, towards channel_estimate.
+ for (int i = 0; i < 12; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+
+ // Second convex growth, from channel_estimate.
+ for (int j = 0; j < 30; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 100000,
+ sent_bitrate.ToBytesPerSecond(), 10000);
+
+ // Verify that we increase cubic.
+ for (int j = 0; j < 23; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond() + 750000,
+ sent_bitrate.ToBytesPerSecond(), 20000);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, UnknownEstimates) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(100);
+ QuicBandwidth available_channel_estimate =
+ QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(400);
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ available_channel_estimate);
+
+ // First convex growth, from start_rate.
+ for (int j = 0; j < 20; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(start_rate, sent_bitrate);
+ EXPECT_GE(available_channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(available_channel_estimate.ToBytesPerSecond(),
+ sent_bitrate.ToBytesPerSecond(), 10000);
+
+ // Verify that we increase cubic.
+ for (int j = 0; j < 31; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_NEAR(channel_estimate.ToBytesPerSecond(),
+ sent_bitrate.ToBytesPerSecond(), 10000);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, UpdatingChannelEstimateHigher) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth available_channel_estimate = start_rate;
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(250);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // Convex growth, from available_channel_estimate.
+ for (int j = 0; j < 16; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+
+ // Increse channel estimate.
+ channel_estimate = QuicBandwidth::FromKBytesPerSecond(300);
+ bitrate_ramp_up_.UpdateChannelEstimate(channel_estimate);
+
+ // Concave growth, towards channel_estimate.
+ for (int i = 0; i < 22; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+}
+
+TEST_F(InterArrivalBitrateRampUpTest, UpdatingChannelEstimateLower) {
+ QuicBandwidth start_rate = QuicBandwidth::FromKBytesPerSecond(200);
+ QuicBandwidth available_channel_estimate = start_rate;
+ QuicBandwidth channel_estimate = QuicBandwidth::FromKBytesPerSecond(250);
+ QuicBandwidth halfway_point = available_channel_estimate.Add(
+ channel_estimate.Subtract(available_channel_estimate).Scale(0.5f));
+ QuicBandwidth sent_bitrate = QuicBandwidth::Zero();
+ bitrate_ramp_up_.Reset(start_rate,
+ available_channel_estimate,
+ channel_estimate);
+
+ // Convex growth, from available_channel_estimate.
+ for (int j = 0; j < 16; ++j) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(available_channel_estimate, sent_bitrate);
+ EXPECT_GE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+
+ // Decrese channel estimate.
+ channel_estimate = QuicBandwidth::FromKBytesPerSecond(240);
+ bitrate_ramp_up_.UpdateChannelEstimate(channel_estimate);
+
+ // Concave growth, towards channel_estimate.
+ for (int i = 0; i < 11; ++i) {
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_GE(channel_estimate, sent_bitrate);
+ EXPECT_LE(halfway_point, sent_bitrate);
+ }
+ clock_.AdvanceTime(hundred_ms_);
+ sent_bitrate = bitrate_ramp_up_.GetNewBitrate(sent_bitrate);
+ EXPECT_LE(channel_estimate, sent_bitrate);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.cc b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.cc
new file mode 100644
index 00000000000..73e005d788a
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_overuse_detector.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+// Initial noise variance, equal to a standard deviation of 1 millisecond.
+static const float kInitialVarianceNoise = 1000000.0;
+
+// Minimum variance of the time delta.
+static const int kMinVarianceDelta = 10000;
+
+// Threshold for accumulated delta.
+static const int kThresholdAccumulatedDeltasUs = 1000;
+
+// The higher the beta parameter, the lower is the effect of the input and the
+// more damping of the noise. And the longer time for a detection.
+static const float kBeta = 0.98f;
+
+// Same as above, described as numerator and denominator.
+static const int kBetaNumerator = 49;
+static const int kBetaDenominator = 50;
+
+// Trigger a signal when the accumulated time drift is larger than
+// 5 x standard deviation.
+// A lower value triggers earlier with more false detect as a side effect.
+static const int kDetectDriftStandardDeviation = 5;
+
+// Trigger an overuse when the time difference between send time and receive
+// is larger than 7 x standard deviation.
+// A lower value triggers earlier with more false detect as a side effect.
+static const float kDetectTimeDiffStandardDeviation = 7;
+
+// Trigger an overuse when the mean of the time difference diverges too far
+// from 0.
+// A higher value trigger earlier with more false detect as a side effect.
+static const int kDetectSlopeFactor = 14;
+
+// We need to get some initial statistics before the detection can start.
+static const int kMinSamplesBeforeDetect = 10;
+
+namespace net {
+
+InterArrivalOveruseDetector::InterArrivalOveruseDetector()
+ : last_sequence_number_(0),
+ num_of_deltas_(0),
+ accumulated_deltas_(QuicTime::Delta::Zero()),
+ delta_mean_(0.0),
+ delta_variance_(kInitialVarianceNoise),
+ delta_overuse_counter_(0),
+ delta_estimate_(kBandwidthSteady),
+ slope_overuse_counter_(0),
+ slope_estimate_(kBandwidthSteady),
+ send_receive_offset_(QuicTime::Delta::Infinite()),
+ estimated_congestion_delay_(QuicTime::Delta::Zero()) {
+}
+
+void InterArrivalOveruseDetector::OnAcknowledgedPacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime send_time,
+ bool last_of_send_time,
+ QuicTime receive_time) {
+ if (last_sequence_number_ >= sequence_number) {
+ // This is an old packet and should be ignored. Note that we are called
+ // with a full 64 bit sequence number, even if the wire format may only
+ // convey some low-order bits of that number.
+ DLOG(INFO) << "Skip old packet";
+ return;
+ }
+
+ last_sequence_number_ = sequence_number;
+
+ if (current_packet_group_.send_time != send_time) {
+ // First value in this group. If the last packet of a group is lost we
+ // overwrite the old value and start over with a new measurement.
+ current_packet_group_.send_time = send_time;
+ // The receive_time value might not be first in a packet burst if that
+ // packet was lost, however we will still use it in this calculation.
+ UpdateSendReceiveTimeOffset(receive_time.Subtract(send_time));
+ }
+ if (!last_of_send_time) {
+ // We expect more packet with this send time.
+ return;
+ }
+ // First packet of a later group, the previous group sample is ready.
+ if (previous_packet_group_.send_time.IsInitialized()) {
+ QuicTime::Delta sent_delta = send_time.Subtract(
+ previous_packet_group_.send_time);
+ QuicTime::Delta receive_delta = receive_time.Subtract(
+ previous_packet_group_.last_receive_time);
+ // We assume that groups of packets are sent together as bursts (tagged
+ // with identical send times) because it is too computationally expensive
+ // to pace them out individually. The received_delta is then the total
+ // delta between the receipt of the last packet of the previous group, and
+ // the last packet of the current group. Assuming we are transmitting
+ // these bursts on average at the available bandwidth rate, there should be
+ // no change in overall spread (both deltas should be the same).
+ UpdateFilter(receive_delta, sent_delta);
+ }
+ // Save current as previous.
+ previous_packet_group_ = current_packet_group_;
+ previous_packet_group_.last_receive_time = receive_time;
+}
+
+void InterArrivalOveruseDetector::UpdateSendReceiveTimeOffset(
+ QuicTime::Delta offset) {
+ // Note the send and receive time can have a randomly large offset, however
+ // they are stable in relation to each other, hence no or extremely low clock
+ // drift relative to the duration of our stream.
+ if (offset.ToMicroseconds() < send_receive_offset_.ToMicroseconds()) {
+ send_receive_offset_ = offset;
+ }
+ estimated_congestion_delay_ = offset.Subtract(send_receive_offset_);
+}
+
+BandwidthUsage InterArrivalOveruseDetector::GetState(
+ QuicTime::Delta* estimated_congestion_delay) {
+ *estimated_congestion_delay = estimated_congestion_delay_;
+ int64 sigma_delta = sqrt(static_cast<double>(delta_variance_));
+ DetectSlope(sigma_delta);
+ DetectDrift(sigma_delta);
+ return std::max(slope_estimate_, delta_estimate_);
+}
+
+void InterArrivalOveruseDetector::UpdateFilter(QuicTime::Delta received_delta,
+ QuicTime::Delta sent_delta) {
+ ++num_of_deltas_;
+ QuicTime::Delta time_diff = received_delta.Subtract(sent_delta);
+ UpdateDeltaEstimate(time_diff);
+ accumulated_deltas_ = accumulated_deltas_.Add(time_diff);
+}
+
+void InterArrivalOveruseDetector::UpdateDeltaEstimate(
+ QuicTime::Delta residual) {
+ DCHECK_EQ(1, kBetaDenominator - kBetaNumerator);
+ int64 residual_us = residual.ToMicroseconds();
+ delta_mean_ =
+ (kBetaNumerator * delta_mean_ + residual_us) / kBetaDenominator;
+ delta_variance_ =
+ (kBetaNumerator * delta_variance_ +
+ (delta_mean_ - residual_us) * (delta_mean_ - residual_us)) /
+ kBetaDenominator;
+
+ if (delta_variance_ < kMinVarianceDelta) {
+ delta_variance_ = kMinVarianceDelta;
+ }
+}
+
+void InterArrivalOveruseDetector::DetectDrift(int64 sigma_delta) {
+ // We have 2 drift detectors. The accumulate of deltas and the absolute time
+ // differences.
+ if (num_of_deltas_ < kMinSamplesBeforeDetect) {
+ return;
+ }
+ if (delta_overuse_counter_ > 0 &&
+ accumulated_deltas_.ToMicroseconds() > kThresholdAccumulatedDeltasUs) {
+ if (delta_estimate_ != kBandwidthDraining) {
+ DLOG(INFO) << "Bandwidth estimate drift: Draining buffer(s) "
+ << accumulated_deltas_.ToMilliseconds() << " ms";
+ delta_estimate_ = kBandwidthDraining;
+ }
+ return;
+ }
+ if ((sigma_delta * kDetectTimeDiffStandardDeviation >
+ estimated_congestion_delay_.ToMicroseconds()) &&
+ (sigma_delta * kDetectDriftStandardDeviation >
+ abs(accumulated_deltas_.ToMicroseconds()))) {
+ if (delta_estimate_ != kBandwidthSteady) {
+ DLOG(INFO) << "Bandwidth estimate drift: Steady"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta
+ << " offset:" << send_receive_offset_.ToMicroseconds()
+ << " delta:" << estimated_congestion_delay_.ToMicroseconds()
+ << " drift:" << accumulated_deltas_.ToMicroseconds();
+ delta_estimate_ = kBandwidthSteady;
+ // Reset drift counter.
+ accumulated_deltas_ = QuicTime::Delta::Zero();
+ delta_overuse_counter_ = 0;
+ }
+ return;
+ }
+ if (accumulated_deltas_.ToMicroseconds() > 0) {
+ if (delta_estimate_ != kBandwidthOverUsing) {
+ ++delta_overuse_counter_;
+ DLOG(INFO) << "Bandwidth estimate drift: Over using"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta
+ << " offset:" << send_receive_offset_.ToMicroseconds()
+ << " delta:" << estimated_congestion_delay_.ToMicroseconds()
+ << " drift:" << accumulated_deltas_.ToMicroseconds();
+ delta_estimate_ = kBandwidthOverUsing;
+ }
+ } else {
+ if (delta_estimate_ != kBandwidthUnderUsing) {
+ --delta_overuse_counter_;
+ DLOG(INFO) << "Bandwidth estimate drift: Under using"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta
+ << " offset:" << send_receive_offset_.ToMicroseconds()
+ << " delta:" << estimated_congestion_delay_.ToMicroseconds()
+ << " drift:" << accumulated_deltas_.ToMicroseconds();
+ delta_estimate_ = kBandwidthUnderUsing;
+ }
+ // Adding decay of negative accumulated_deltas_ since it could be caused by
+ // a starting with full buffers. This way we will always converge to 0.
+ accumulated_deltas_ = accumulated_deltas_.Add(
+ QuicTime::Delta::FromMicroseconds(sigma_delta >> 3));
+ }
+}
+
+void InterArrivalOveruseDetector::DetectSlope(int64 sigma_delta) {
+ // We use the mean change since it has a constant expected mean 0
+ // regardless of number of packets and spread. It is also safe to use during
+ // packet loss, since a lost packet only results in a missed filter update
+ // not a drift.
+ if (num_of_deltas_ < kMinSamplesBeforeDetect) {
+ return;
+ }
+ if (slope_overuse_counter_ > 0 && delta_mean_ > 0) {
+ if (slope_estimate_ != kBandwidthDraining) {
+ DLOG(INFO) << "Bandwidth estimate slope: Draining buffer(s)";
+ }
+ slope_estimate_ = kBandwidthDraining;
+ return;
+ }
+ if (sigma_delta > abs(delta_mean_) * kDetectSlopeFactor) {
+ if (slope_estimate_ != kBandwidthSteady) {
+ DLOG(INFO) << "Bandwidth estimate slope: Steady"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta;
+ slope_overuse_counter_ = 0;
+ slope_estimate_ = kBandwidthSteady;
+ }
+ return;
+ }
+ if (delta_mean_ > 0) {
+ if (slope_estimate_ != kBandwidthOverUsing) {
+ ++slope_overuse_counter_;
+ DLOG(INFO) << "Bandwidth estimate slope: Over using"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta;
+ slope_estimate_ = kBandwidthOverUsing;
+ }
+ } else {
+ if (slope_estimate_ != kBandwidthUnderUsing) {
+ --slope_overuse_counter_;
+ DLOG(INFO) << "Bandwidth estimate slope: Under using"
+ << " mean:" << delta_mean_
+ << " sigma:" << sigma_delta;
+ slope_estimate_ = kBandwidthUnderUsing;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.h b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.h
new file mode 100644
index 00000000000..185236279bb
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector.h
@@ -0,0 +1,173 @@
+// Copyright (c) 2013 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.
+
+// This class is a helper class to the inter arrival congestion control. It
+// provide a signal to the inter arrival congestion control of the estimated
+// state of our transport channel. The estimate is based on the inter arrival
+// time of the received packets relative to the time those packets were sent;
+// we can estimate the build up of buffers on the network before packets are
+// lost.
+//
+// Note: this class is not thread-safe.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_OVERUSE_DETECTOR_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_OVERUSE_DETECTOR_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+enum NET_EXPORT_PRIVATE RateControlRegion {
+ kRateControlRegionUnknown = 0,
+ kRateControlRegionUnderMax = 1,
+ kRateControlRegionNearMax = 2
+};
+
+// Note: Order is important.
+enum NET_EXPORT_PRIVATE BandwidthUsage {
+ kBandwidthSteady = 0,
+ kBandwidthUnderUsing = 1,
+ kBandwidthDraining = 2,
+ kBandwidthOverUsing = 3,
+};
+
+// Normal state transition diagram
+//
+// kBandwidthUnderUsing
+// |
+// |
+// kBandwidthSteady
+// | ^
+// | |
+// kBandwidthOverUsing |
+// | |
+// | |
+// kBandwidthDraining
+//
+// The above transitions is in normal operation, with extreme values we don't
+// enforce the state transitions, hence you could in extreme scenarios go
+// between any states.
+//
+// kBandwidthSteady When the packets arrive in the same pace as we sent
+// them. In this state we can increase our send pace.
+//
+// kBandwidthOverUsing When the packets arrive slower than the pace we sent
+// them. In this state we should decrease our send pace.
+// When we enter into this state we will also get an
+// estimate on how much delay we have built up. The
+// reduction in send pace should be chosen to drain the
+// built up delay within reasonable time.
+//
+// kBandwidthUnderUsing When the packets arrive faster than the pace we sent
+// them. In this state another stream disappeared from
+// a shared link leaving us more available bandwidth.
+// In this state we should hold our pace to make sure we
+// fully drain the buffers before we start increasing
+// our send rate. We do this to avoid operating with
+// semi-full buffers.
+//
+// kBandwidthDraining We can only be in this state after we have been in a
+// overuse state. In this state we should hold our pace
+// to make sure we fully drain the buffers before we
+// start increasing our send rate. We do this to avoid
+// operating with semi-full buffers.
+
+class NET_EXPORT_PRIVATE InterArrivalOveruseDetector {
+ public:
+ InterArrivalOveruseDetector();
+
+ // Update the statistics with the received delta times, call for every
+ // received delta time. This function assumes that there is no re-orderings.
+ // If multiple packets are sent at the same time (identical send_time)
+ // last_of_send_time should be set to false for all but the last calls to
+ // this function. If there is only one packet sent at a given time
+ // last_of_send_time must be true.
+ // received_delta is the time difference between receiving this packet and the
+ // previously received packet.
+ void OnAcknowledgedPacket(QuicPacketSequenceNumber sequence_number,
+ QuicTime send_time,
+ bool last_of_send_time,
+ QuicTime receive_time);
+
+ // Get the current estimated state and update the estimated congestion delay.
+ // |estimated_congestion_delay| will be updated with the estimated built up
+ // buffer delay; it must not be NULL as it will be updated with the estimate.
+ // Note 1: estimated_buffer_delay will only be valid when kBandwidthOverUsing
+ // is returned.
+ // Note 2: it's assumed that the pacer lower its send pace to drain the
+ // built up buffer within reasonable time. The pacer should use the
+ // estimated_buffer_delay as a guidance on how much to back off.
+ // Note 3: The absolute value of estimated_congestion_delay is less reliable
+ // than the state itself. It is also biased to low since we can't know
+ // how full the buffers are when the flow starts.
+ BandwidthUsage GetState(QuicTime::Delta* estimated_congestion_delay);
+
+ private:
+ struct PacketGroup {
+ PacketGroup()
+ : send_time(QuicTime::Zero()),
+ last_receive_time(QuicTime::Zero()) {
+ }
+ QuicTime send_time;
+ QuicTime last_receive_time;
+ };
+
+ // Update the statistics with the absolute receive time relative to the
+ // absolute send time.
+ void UpdateSendReceiveTimeOffset(QuicTime::Delta offset);
+
+ // Update the filter with this new data point.
+ void UpdateFilter(QuicTime::Delta received_delta,
+ QuicTime::Delta sent_delta);
+
+ // Update the estimate with this residual.
+ void UpdateDeltaEstimate(QuicTime::Delta residual);
+
+ // Estimate the state based on the slope of the changes.
+ void DetectSlope(int64 sigma_delta);
+
+ // Estimate the state based on the accumulated drift of the changes.
+ void DetectDrift(int64 sigma_delta);
+
+ // Current grouping of packets that were sent at the same time.
+ PacketGroup current_packet_group_;
+ // Grouping of packets that were sent at the same time, just before the
+ // current_packet_group_ above.
+ PacketGroup previous_packet_group_;
+ // Sequence number of the last acknowledged packet.
+ QuicPacketSequenceNumber last_sequence_number_;
+ // Number of received delta times with unique send time.
+ int num_of_deltas_;
+ // Estimated accumulation of received delta times.
+ // Note: Can be negative and can drift over time which is why we bias it
+ // towards 0 and reset it given some triggers.
+ QuicTime::Delta accumulated_deltas_;
+ // Current running mean of our received delta times.
+ int delta_mean_;
+ // Current running variance of our received delta times.
+ int64 delta_variance_;
+ // Number of overuse signals currently triggered in this state.
+ // Note: negative represent underuse.
+ int delta_overuse_counter_;
+ // State estimated by the delta times.
+ BandwidthUsage delta_estimate_;
+ // Number of overuse signals currently triggered in this state.
+ // Note: negative represent underuse.
+ int slope_overuse_counter_;
+ // State estimated by the slope of the delta times.
+ BandwidthUsage slope_estimate_;
+ // Lowest offset between send and receive time ever received in this session.
+ QuicTime::Delta send_receive_offset_;
+ // Last received time difference between our normalized send and receive time.
+ QuicTime::Delta estimated_congestion_delay_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalOveruseDetector);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_OVERUSE_DETECTOR_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_overuse_detector_test.cc b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector_test.cc
new file mode 100644
index 00000000000..8d37749a577
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_overuse_detector_test.cc
@@ -0,0 +1,1114 @@
+// Copyright (c) 2013 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 <stdlib.h>
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "net/quic/congestion_control/inter_arrival_overuse_detector.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static const double kPi = 3.14159265;
+
+namespace net {
+namespace test {
+
+class InterArrivalOveruseDetectorTest : public ::testing::Test {
+ protected:
+ InterArrivalOveruseDetectorTest();
+
+ QuicTime::Delta GaussianRandom(QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation);
+
+ int Run100000Samples(int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation);
+
+ int RunUntilOveruse(int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst,
+ QuicTime::Delta *estimated_buffer_delay);
+
+ int RunUntilSteady(int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst);
+
+ int RunUntilNotDraining(int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst);
+
+ int RunUntilUnderusing(int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst);
+
+ void RunXBursts(int bursts,
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst);
+
+ QuicPacketSequenceNumber sequence_number_;
+ MockClock send_clock_;
+ MockClock receive_clock_;
+ QuicTime::Delta drift_from_mean_;
+ InterArrivalOveruseDetector overuse_detector_;
+ unsigned int seed_;
+};
+
+InterArrivalOveruseDetectorTest::InterArrivalOveruseDetectorTest()
+ : sequence_number_(1),
+ drift_from_mean_(QuicTime::Delta::Zero()),
+ seed_(1234) {
+}
+
+QuicTime::Delta InterArrivalOveruseDetectorTest::GaussianRandom(
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation) {
+ // Creating a Normal distribution variable from two independent uniform
+ // variables based on the Box-Muller transform.
+ double uniform1 = base::RandDouble();
+ double uniform2 = base::RandDouble();
+
+ QuicTime::Delta random = QuicTime::Delta::FromMicroseconds(
+ static_cast<int>(standard_deviation.ToMicroseconds() *
+ sqrt(-2 * log(uniform1)) * cos(2 * kPi * uniform2))).
+ Add(mean).Subtract(drift_from_mean_);
+ if (random < QuicTime::Delta::Zero()) {
+ // Don't do negative deltas.
+ drift_from_mean_ = drift_from_mean_.Subtract(mean);
+ return QuicTime::Delta::Zero();
+ }
+ drift_from_mean_ = drift_from_mean_.Add(random).Subtract(mean);
+ return random;
+}
+
+int InterArrivalOveruseDetectorTest::Run100000Samples(
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation) {
+ int unique_overuse = 0;
+ int last_overuse = -1;
+ for (int i = 0; i < 100000; ++i) {
+ // Assume that we send out the packets with perfect pacing.
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean, standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ // We expect to randomly hit a few false detects, count the unique
+ // overuse events, hence not multiple signals in a row.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ if (kBandwidthOverUsing == overuse_detector_.GetState(
+ &estimated_buffer_delay)) {
+ if (last_overuse + 1 != i) {
+ unique_overuse++;
+ }
+ last_overuse = i;
+ }
+ }
+ return unique_overuse;
+}
+
+int InterArrivalOveruseDetectorTest::RunUntilOveruse(
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst,
+ QuicTime::Delta *estimated_buffer_delay) {
+ // Simulate a higher send pace, that is too high.
+ for (int i = 0; i < 1000; ++i) {
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean.Add(drift_per_burst),
+ standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ if (kBandwidthOverUsing == overuse_detector_.GetState(
+ estimated_buffer_delay)) {
+ return i + 1;
+ }
+ }
+ return -1;
+}
+
+int InterArrivalOveruseDetectorTest::RunUntilSteady(
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst) {
+ // Simulate a lower send pace, that is lower than the capacity.
+ for (int i = 0; i < 1000; ++i) {
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean.Subtract(drift_per_burst),
+ standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ if (kBandwidthSteady ==
+ overuse_detector_.GetState(&estimated_buffer_delay)) {
+ return i + 1;
+ }
+ }
+ return -1;
+}
+
+int InterArrivalOveruseDetectorTest::RunUntilNotDraining(
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst) {
+ // Simulate a lower send pace, that is lower than the capacity.
+ for (int i = 0; i < 1000; ++i) {
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean.Subtract(drift_per_burst),
+ standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ if (kBandwidthDraining >
+ overuse_detector_.GetState(&estimated_buffer_delay)) {
+ return i + 1;
+ }
+ }
+ return -1;
+}
+
+int InterArrivalOveruseDetectorTest::RunUntilUnderusing(
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst) {
+ // Simulate a lower send pace, that is lower than the capacity.
+ for (int i = 0; i < 1000; ++i) {
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean.Subtract(drift_per_burst),
+ standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ if (kBandwidthUnderUsing == overuse_detector_.GetState(
+ &estimated_buffer_delay)) {
+ return i + 1;
+ }
+ }
+ return -1;
+}
+
+void InterArrivalOveruseDetectorTest::RunXBursts(
+ int bursts,
+ int packets_per_burst,
+ QuicTime::Delta mean,
+ QuicTime::Delta standard_deviation,
+ QuicTime::Delta drift_per_burst) {
+ for (int i = 0; i < bursts; ++i) {
+ send_clock_.AdvanceTime(mean);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ // Do only one random delta for all packets in a burst.
+ receive_clock_.AdvanceTime(GaussianRandom(mean.Add(drift_per_burst),
+ standard_deviation));
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ for (int j = 0; j <= packets_per_burst; ++j) {
+ overuse_detector_.OnAcknowledgedPacket(sequence_number_++,
+ send_time,
+ (j == packets_per_burst),
+ receive_time);
+ }
+ }
+}
+
+// TODO(pwestin): test packet loss impact on accuracy.
+// TODO(pwestin): test colored noise by dropping late frames.
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_TestNoise) {
+ int count[100];
+ memset(count, 0, sizeof(count));
+ for (int i = 0; i < 10000; ++i) {
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(30);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(10);
+ count[GaussianRandom(mean, standard_deviation).ToMilliseconds()]++;
+ }
+ for (int j = 0; j < 100; ++j) {
+ DLOG(INFO) << j << ":" << count[j];
+ }
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_SimpleNonOveruse) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta delta = QuicTime::Delta::FromMilliseconds(10);
+
+ for (int i = 0; i < 1000; ++i) {
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+ send_clock_.AdvanceTime(delta);
+ receive_clock_.AdvanceTime(delta);
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ EXPECT_EQ(kBandwidthSteady,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+ }
+}
+
+TEST_F(InterArrivalOveruseDetectorTest,
+ DISABLED_SimpleNonOveruseSendClockAhead) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta delta = QuicTime::Delta::FromMilliseconds(10);
+ send_clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1234));
+
+ for (int i = 0; i < 1000; ++i) {
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+ send_clock_.AdvanceTime(delta);
+ receive_clock_.AdvanceTime(delta);
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ EXPECT_EQ(kBandwidthSteady,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+ }
+}
+
+TEST_F(InterArrivalOveruseDetectorTest,
+ DISABLED_SimpleNonOveruseSendClockBehind) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta delta = QuicTime::Delta::FromMilliseconds(10);
+ receive_clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1234));
+
+ for (int i = 0; i < 1000; ++i) {
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+ send_clock_.AdvanceTime(delta);
+ receive_clock_.AdvanceTime(delta);
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ EXPECT_EQ(kBandwidthSteady,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+ }
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_SimpleNonOveruseWithVariance) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ for (int i = 0; i < 1000; ++i) {
+ if (i % 2) {
+ receive_clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ } else {
+ receive_clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(15));
+ }
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+ send_clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ EXPECT_EQ(kBandwidthSteady,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+ }
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_SimpleOveruse) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta send_delta = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+
+ for (int i = 0; i < 100000; ++i) {
+ send_clock_.AdvanceTime(send_delta);
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ // Sending 2 packets the same time as that is what we expect to do.
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ false,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+
+ EXPECT_EQ(kBandwidthSteady,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+ }
+ // Simulate a higher send pace, that is too high by receiving 1 millisecond
+ // late per packet.
+ received_delta = QuicTime::Delta::FromMilliseconds(6);
+ send_clock_.AdvanceTime(send_delta);
+ receive_clock_.AdvanceTime(received_delta);
+ QuicTime send_time = send_clock_.ApproximateNow();
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ false,
+ receive_time);
+ receive_clock_.AdvanceTime(received_delta);
+ receive_time = receive_clock_.ApproximateNow();
+ overuse_detector_.OnAcknowledgedPacket(sequence_number++,
+ send_time,
+ true,
+ receive_time);
+ EXPECT_EQ(kBandwidthOverUsing,
+ overuse_detector_.GetState(&estimated_buffer_delay));
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance10Kbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1000);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(5);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 6 updates with 5 milliseconds before
+ // detection.
+ // Resulting in a minimal buffer build up of 30 milliseconds.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(6, bursts_until_overuse);
+ EXPECT_NEAR(40, estimated_buffer_delay.ToMilliseconds(), 15);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(6, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(7, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance10Kbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1000);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(50);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(50);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 6 updates with 50 milliseconds before
+ // detection.
+ // Resulting in a minimal buffer build up of 300 milliseconds.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(6, bursts_until_overuse);
+ EXPECT_NEAR(400, estimated_buffer_delay.ToMilliseconds(), 150);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(6, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(7, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance100Kbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(100);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(5);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 6 updates with 5 milliseconds
+ // before detection.
+ // Resulting in a minimal buffer build up of 30 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(6, bursts_until_overuse);
+ EXPECT_NEAR(40, estimated_buffer_delay.ToMilliseconds(), 15);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(1,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(7, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance100Kbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(100);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(50);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(50);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 4 updates with 50 milliseconds
+ // before detection.
+ // Resulting in a minimal buffer build up of 200 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(4, bursts_until_overuse);
+ EXPECT_NEAR(300, estimated_buffer_delay.ToMilliseconds(), 150);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(1,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance1Mbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(5);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 4 updates with 5 millisecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 20 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(4, bursts_until_overuse);
+ EXPECT_NEAR(30, estimated_buffer_delay.ToMilliseconds(), 15);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(14, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(4, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance1Mbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(1);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 6 updates with 1 millisecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 6 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(6, bursts_until_overuse);
+ EXPECT_NEAR(8, estimated_buffer_delay.ToMilliseconds(), 3);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(16, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(4, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance20Mbit) {
+ int packets_per_burst = 2;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMicroseconds(500);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMicroseconds(500);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 4 updates with 500 microsecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 2 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(4, bursts_until_overuse);
+ EXPECT_NEAR(3, estimated_buffer_delay.ToMilliseconds(), 2);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(100,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_NEAR(100, bursts_until_not_draining, 10);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(1, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance100Mbit) {
+ int packets_per_burst = 10;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMicroseconds(500);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMicroseconds(500);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 4 updates with 500 microsecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 2 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(4, bursts_until_overuse);
+ EXPECT_NEAR(3, estimated_buffer_delay.ToMilliseconds(), 2);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(100,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_NEAR(100, bursts_until_not_draining, 10);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(1, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_VeryHighVariance100Mbit) {
+ int packets_per_burst = 10;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMicroseconds(500);
+
+ // We get false overuse in this scenario due to that the standard deviation is
+ // higher than our mean and the fact that a delta time can't be negative. This
+ // results in an under estimated standard deviation in the estimator causing
+ // false detects.
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(2000, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 25 updates with 500 microsecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 12.5 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(17, bursts_until_overuse);
+ EXPECT_NEAR(22, estimated_buffer_delay.ToMilliseconds(), 15);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(100,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(117, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(1, bursts_until_underusing);
+}
+
+//
+// Tests simulating big drop in bitrate.
+//
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance1MbitTo100Kbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(100);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 1 update with 100 millisecond
+ // before detection.
+ // Resulting in a minimal buffer build up of 100 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(1, bursts_until_overuse);
+ EXPECT_NEAR(104, estimated_buffer_delay.ToMilliseconds(), 3);
+
+ // Back off 20% lower than estimate to drain.
+ mean = QuicTime::Delta::FromMilliseconds(100);
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(20);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(3, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_HighVariance20MbitTo1Mbit) {
+ int packets_per_burst = 2;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMicroseconds(500);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(10);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 1 update with 10 milliseconds
+ // before detection.
+ // Resulting in a minimal buffer build up of 10 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(1, bursts_until_overuse);
+ EXPECT_NEAR(12, estimated_buffer_delay.ToMilliseconds(), 2);
+
+ // Back off 20% lower than estimate to drain.
+ mean = QuicTime::Delta::FromMilliseconds(10);
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(2);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(3, bursts_until_underusing);
+}
+
+//
+// Tests that we can detect slow drifts.
+//
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance1MbitSmallSteps) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMicroseconds(100);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 41 updates with 100 microseconds before
+ // detection.
+ // Resulting in a minimal buffer build up of 4.1 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(41, bursts_until_overuse);
+ EXPECT_NEAR(7, estimated_buffer_delay.ToMilliseconds(), 3);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(29, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(71, bursts_until_underusing);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest, DISABLED_LowVariance1MbitTinySteps) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMicroseconds(10);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a higher send pace, that is too high.
+ // With current tuning we require 345 updates with 10 microseconds before
+ // detection.
+ // Resulting in a minimal buffer build up of 3.45 ms.
+ QuicTime::Delta estimated_buffer_delay(QuicTime::Delta::Zero());
+ int bursts_until_overuse = RunUntilOveruse(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst,
+ &estimated_buffer_delay);
+ EXPECT_GE(345, bursts_until_overuse);
+ EXPECT_NEAR(7, estimated_buffer_delay.ToMilliseconds(), 3);
+
+ // Simulate an RTT of 100 ms. Hence overusing for additional 100 ms before
+ // detection.
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ int bursts_until_not_draining = RunUntilNotDraining(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(18, bursts_until_not_draining);
+
+ // After draining the buffer additionally we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(683, bursts_until_underusing);
+}
+
+//
+// Tests simulating starting with full buffers.
+//
+
+TEST_F(InterArrivalOveruseDetectorTest,
+ DISABLED_StartedWithFullBuffersHighVariance1Mbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(5);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a lower send pace.
+ // Draining the buffer until we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(6, bursts_until_underusing);
+
+ // After draining the buffers we are back in a normal state.
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(0);
+ int bursts_until_steady = RunUntilSteady(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(6, bursts_until_steady);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest,
+ DISABLED_StartedWithFullBuffersHighVariance1MbitSlowDrift) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(5);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(1);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a faster receive pace.
+ // Draining the buffer until we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(21, bursts_until_underusing);
+
+ // Simulate an RTT of 100 ms. Hence underusing for additional 100 ms before
+ // detection.
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(-1);
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(0);
+ int bursts_until_steady = RunUntilSteady(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(4, bursts_until_steady);
+}
+
+TEST_F(InterArrivalOveruseDetectorTest,
+ DISABLED_StartedWithFullBuffersLowVariance1Mbit) {
+ int packets_per_burst = 1;
+ QuicTime::Delta mean = QuicTime::Delta::FromMilliseconds(10);
+ QuicTime::Delta standard_deviation = QuicTime::Delta::FromMilliseconds(1);
+ QuicTime::Delta drift_per_burst = QuicTime::Delta::FromMilliseconds(1);
+
+ int overuse_signals = Run100000Samples(packets_per_burst,
+ mean,
+ standard_deviation);
+ EXPECT_GE(1, overuse_signals);
+
+ // Simulate a lower send pace.
+ // Draining the buffer until we detect an underuse.
+ int bursts_until_underusing = RunUntilUnderusing(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(5, bursts_until_underusing);
+
+ // Simulate an RTT of 100 ms. Hence underusing for additional 100 ms before
+ // detection.
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(-1);
+ RunXBursts(10,
+ packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+
+ // After draining the buffers we are back in a normal state.
+ drift_per_burst = QuicTime::Delta::FromMilliseconds(0);
+ int bursts_until_steady = RunUntilSteady(packets_per_burst,
+ mean,
+ standard_deviation,
+ drift_per_burst);
+ EXPECT_GE(41, bursts_until_steady);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_probe.cc b/chromium/net/quic/congestion_control/inter_arrival_probe.cc
new file mode 100644
index 00000000000..6d4c07315d0
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_probe.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_probe.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace {
+const int kProbeSizePackets = 10;
+const net::QuicByteCount kMinPacketSize = 500;
+const int64 kDefaultBytesPerSecond = 40000;
+const float kUncertainScaleFactor = 0.5; // TODO(pwestin): revisit this factor.
+}
+
+namespace net {
+
+InterArrivalProbe::InterArrivalProbe()
+ : estimate_available_(false),
+ available_channel_estimate_(QuicBandwidth::Zero()),
+ unacked_data_(0) {
+}
+
+InterArrivalProbe::~InterArrivalProbe() {
+}
+
+bool InterArrivalProbe::GetEstimate(QuicBandwidth* available_channel_estimate) {
+ if (!estimate_available_) {
+ return false;
+ }
+ *available_channel_estimate = available_channel_estimate_;
+ return true;
+}
+
+void InterArrivalProbe::OnSentPacket(QuicByteCount bytes) {
+ if (!estimate_available_) {
+ unacked_data_ += bytes;
+ }
+}
+
+void InterArrivalProbe::OnAcknowledgedPacket(QuicByteCount bytes) {
+ if (!estimate_available_) {
+ DCHECK_LE(bytes, unacked_data_);
+ unacked_data_ -= bytes;
+ }
+}
+
+QuicByteCount InterArrivalProbe::GetAvailableCongestionWindow() {
+ if (estimate_available_) {
+ return 0;
+ }
+ return (kProbeSizePackets * kMaxPacketSize) - unacked_data_;
+}
+
+void InterArrivalProbe::OnIncomingFeedback(
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes_sent,
+ QuicTime time_sent,
+ QuicTime time_received) {
+ if (estimate_available_) {
+ return;
+ }
+
+ if (available_channel_estimator_.get() == NULL) {
+ if (bytes_sent < kMinPacketSize) {
+ // Packet too small to start the probe phase.
+ return;
+ }
+ first_sequence_number_ = sequence_number;
+ available_channel_estimator_.reset(new AvailableChannelEstimator(
+ sequence_number, time_sent, time_received));
+ return;
+ }
+
+ available_channel_estimator_->OnIncomingFeedback(sequence_number,
+ bytes_sent,
+ time_sent,
+ time_received);
+ if (sequence_number < kProbeSizePackets - 1 + first_sequence_number_) {
+ // We need more feedback before we have a probe estimate.
+ return;
+ }
+ // Get the current estimated available channel capacity.
+ // available_channel_estimate is invalid if kAvailableChannelEstimateUnknown
+ // is returned.
+ QuicBandwidth available_channel_estimate = QuicBandwidth::Zero();
+ AvailableChannelEstimateState available_channel_estimate_state =
+ available_channel_estimator_->GetAvailableChannelEstimate(
+ &available_channel_estimate);
+ switch (available_channel_estimate_state) {
+ case kAvailableChannelEstimateUnknown:
+ // Backup when we miss our probe.
+ available_channel_estimate_ =
+ QuicBandwidth::FromBytesPerSecond(kDefaultBytesPerSecond);
+ break;
+ case kAvailableChannelEstimateUncertain:
+ available_channel_estimate_ =
+ available_channel_estimate.Scale(kUncertainScaleFactor);
+ break;
+ case kAvailableChannelEstimateGood:
+ available_channel_estimate_ = available_channel_estimate;
+ break;
+ case kAvailableChannelEstimateSenderLimited:
+ available_channel_estimate_ =
+ std::max(available_channel_estimate,
+ QuicBandwidth::FromBytesPerSecond(kDefaultBytesPerSecond));
+ break;
+ }
+ estimate_available_ = true;
+ available_channel_estimator_.reset(NULL);
+ DLOG(INFO) << "Probe estimate:"
+ << available_channel_estimate_.ToKBitsPerSecond()
+ << " Kbits/s";
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_probe.h b/chromium/net/quic/congestion_control/inter_arrival_probe.h
new file mode 100644
index 00000000000..5788a68a926
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_probe.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2013 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.
+
+// Class that handle the initial probing phase of inter arrival congestion
+// control.
+#ifndef NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_PROBE_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_PROBE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/available_channel_estimator.h"
+#include "net/quic/quic_bandwidth.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE InterArrivalProbe {
+ public:
+ InterArrivalProbe();
+ ~InterArrivalProbe();
+
+ // Call every time a packet is sent to the network.
+ void OnSentPacket(QuicByteCount bytes);
+
+ // Call once for each sent packet that we receive an acknowledgement from
+ // the peer for.
+ void OnAcknowledgedPacket(QuicByteCount bytes);
+
+ // Call to get the number of bytes that can be sent as part of this probe.
+ QuicByteCount GetAvailableCongestionWindow();
+
+ // Call once for each sent packet we receive a congestion feedback from the
+ // peer for.
+ // If a peer sends both and ack and feedback for a sent packet, both
+ // OnAcknowledgedPacket and OnIncomingFeedback should be called.
+ void OnIncomingFeedback(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes_sent,
+ QuicTime time_sent,
+ QuicTime time_received);
+
+ // Returns false as long as we are probing, available_channel_estimate is
+ // invalid during that time. When the probe is completed this function return
+ // true and available_channel_estimate contains the estimate.
+ bool GetEstimate(QuicBandwidth* available_channel_estimate);
+
+ private:
+ scoped_ptr<AvailableChannelEstimator> available_channel_estimator_;
+ QuicPacketSequenceNumber first_sequence_number_;
+ bool estimate_available_;
+ QuicBandwidth available_channel_estimate_;
+ QuicByteCount unacked_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalProbe);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_PROBE_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_probe_test.cc b/chromium/net/quic/congestion_control/inter_arrival_probe_test.cc
new file mode 100644
index 00000000000..18b97d33090
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_probe_test.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2013 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/inter_arrival_probe.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class InterArrivalProbeTest : public ::testing::Test {
+ protected:
+ InterArrivalProbeTest() : start_(QuicTime::Zero()) {
+ }
+
+ InterArrivalProbe probe_;
+ QuicTime start_;
+};
+
+TEST_F(InterArrivalProbeTest, CongestionWindow) {
+ for (size_t i = 0; i < 10; i++) {
+ probe_.OnSentPacket(kMaxPacketSize);
+ EXPECT_EQ((9 - i) * kMaxPacketSize, probe_.GetAvailableCongestionWindow());
+ }
+ probe_.OnAcknowledgedPacket(kMaxPacketSize);
+ EXPECT_EQ(kMaxPacketSize, probe_.GetAvailableCongestionWindow());
+
+ probe_.OnSentPacket(kMaxPacketSize);
+ EXPECT_EQ(0u, probe_.GetAvailableCongestionWindow());
+}
+
+TEST_F(InterArrivalProbeTest, Estimate) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicByteCount bytes_sent = kMaxPacketSize;
+ QuicTime time_received = start_.Add(QuicTime::Delta::FromMilliseconds(10));
+ QuicTime time_sent = start_.Add(QuicTime::Delta::FromMilliseconds(1));
+ QuicBandwidth available_channel_estimate = QuicBandwidth::Zero();
+
+ for (size_t i = 0; i < 10; ++i) {
+ EXPECT_FALSE(probe_.GetEstimate(&available_channel_estimate));
+
+ probe_.OnIncomingFeedback(sequence_number++,
+ bytes_sent,
+ time_sent,
+ time_received);
+ time_sent = time_sent.Add(QuicTime::Delta::FromMilliseconds(1));
+ time_received = time_received.Add(QuicTime::Delta::FromMilliseconds(10));
+ }
+ EXPECT_TRUE(probe_.GetEstimate(&available_channel_estimate));
+ EXPECT_EQ(kMaxPacketSize * 100,
+ static_cast<uint64>(available_channel_estimate.ToBytesPerSecond()));
+}
+
+TEST_F(InterArrivalProbeTest, EstimateWithLoss) {
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicByteCount bytes_sent = kMaxPacketSize;
+ QuicTime time_received = start_.Add(QuicTime::Delta::FromMilliseconds(10));
+ QuicTime time_sent = start_.Add(QuicTime::Delta::FromMilliseconds(1));
+ QuicBandwidth available_channel_estimate = QuicBandwidth::Zero();
+
+ for (size_t i = 0; i < 6; ++i) {
+ EXPECT_FALSE(probe_.GetEstimate(&available_channel_estimate));
+
+ probe_.OnIncomingFeedback(sequence_number,
+ bytes_sent,
+ time_sent,
+ time_received);
+ sequence_number += 2;
+ time_sent = time_sent.Add(QuicTime::Delta::FromMilliseconds(1));
+ time_received = time_received.Add(QuicTime::Delta::FromMilliseconds(10));
+ }
+ EXPECT_TRUE(probe_.GetEstimate(&available_channel_estimate));
+ EXPECT_EQ(kMaxPacketSize * 50,
+ static_cast<uint64>(available_channel_estimate.ToBytesPerSecond()));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_receiver.cc b/chromium/net/quic/congestion_control/inter_arrival_receiver.cc
new file mode 100644
index 00000000000..770b287db0b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_receiver.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_receiver.h"
+
+#include "base/basictypes.h"
+
+namespace net {
+
+InterArrivalReceiver::InterArrivalReceiver()
+ : accumulated_number_of_recoverd_lost_packets_(0) {
+}
+
+InterArrivalReceiver::~InterArrivalReceiver() {
+}
+
+bool InterArrivalReceiver::GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) {
+ if (received_packet_times_.size() <= 1) {
+ // Don't waste resources by sending a feedback frame for only one packet.
+ return false;
+ }
+ feedback->type = kInterArrival;
+ feedback->inter_arrival.accumulated_number_of_lost_packets =
+ accumulated_number_of_recoverd_lost_packets_;
+
+ // Copy our current receive set to our feedback message, we will not resend
+ // this data if it is lost.
+ feedback->inter_arrival.received_packet_times = received_packet_times_;
+
+ // Prepare for the next set of arriving packets by clearing our current set.
+ received_packet_times_.clear();
+ return true;
+}
+
+void InterArrivalReceiver::RecordIncomingPacket(
+ QuicByteCount /*bytes*/,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) {
+ if (revived) {
+ ++accumulated_number_of_recoverd_lost_packets_;
+ }
+ received_packet_times_.insert(std::make_pair(sequence_number, timestamp));
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_receiver.h b/chromium/net/quic/congestion_control/inter_arrival_receiver.h
new file mode 100644
index 00000000000..a9de62cb1ea
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_receiver.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 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_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_RECEIVER_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_RECEIVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE InterArrivalReceiver
+ : public ReceiveAlgorithmInterface {
+ public:
+ InterArrivalReceiver();
+ virtual ~InterArrivalReceiver();
+
+ // Start implementation of ReceiveAlgorithmInterface.
+ virtual bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) OVERRIDE;
+
+ virtual void RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) OVERRIDE;
+ // End implementation of ReceiveAlgorithmInterface.
+
+ private:
+ // We need to keep track of FEC recovered packets.
+ int accumulated_number_of_recoverd_lost_packets_;
+
+ // The set of received packets since the last feedback was sent, along with
+ // their arrival times.
+ TimeMap received_packet_times_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalReceiver);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_RECEIVER_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_receiver_test.cc b/chromium/net/quic/congestion_control/inter_arrival_receiver_test.cc
new file mode 100644
index 00000000000..927fb6d92f7
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_receiver_test.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "net/quic/congestion_control/inter_arrival_receiver.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class InterArrivalReceiverTest : public ::testing::Test {
+ protected:
+ InterArrivalReceiver receiver_;
+ MockClock clock_;
+};
+
+TEST_F(InterArrivalReceiverTest, SimpleReceiver) {
+ QuicTime start = clock_.ApproximateNow();
+ QuicTime::Delta received_delta = QuicTime::Delta::FromMilliseconds(10);
+ clock_.AdvanceTime(received_delta);
+ QuicTime receive_timestamp = clock_.ApproximateNow();
+ receiver_.RecordIncomingPacket(1, 1, receive_timestamp, false);
+
+ QuicCongestionFeedbackFrame feedback;
+ ASSERT_FALSE(receiver_.GenerateCongestionFeedback(&feedback));
+
+ clock_.AdvanceTime(received_delta);
+ receive_timestamp = clock_.ApproximateNow();
+ // Packet not received; but rather revived by FEC.
+ receiver_.RecordIncomingPacket(1, 2, receive_timestamp, true);
+ clock_.AdvanceTime(received_delta);
+ receive_timestamp = clock_.ApproximateNow();
+ receiver_.RecordIncomingPacket(1, 3, receive_timestamp, false);
+
+ ASSERT_TRUE(receiver_.GenerateCongestionFeedback(&feedback));
+
+ EXPECT_EQ(kInterArrival, feedback.type);
+ EXPECT_EQ(1, feedback.inter_arrival.accumulated_number_of_lost_packets);
+ EXPECT_EQ(3u, feedback.inter_arrival.received_packet_times.size());
+ TimeMap::iterator it = feedback.inter_arrival.received_packet_times.begin();
+ EXPECT_EQ(1u, it->first);
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10), it->second.Subtract(start));
+ it = feedback.inter_arrival.received_packet_times.begin();
+ it++;
+ EXPECT_EQ(2u, it->first);
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(20), it->second.Subtract(start));
+ it++;
+ EXPECT_EQ(3u, it->first);
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(30), it->second.Subtract(start));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_sender.cc b/chromium/net/quic/congestion_control/inter_arrival_sender.cc
new file mode 100644
index 00000000000..1aa7ab90e9b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_sender.cc
@@ -0,0 +1,505 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_sender.h"
+
+namespace net {
+
+namespace {
+const int64 kProbeBitrateKBytesPerSecond = 1200; // 9.6 Mbit/s
+const float kPacketLossBitrateReduction = 0.7f;
+const float kUncertainSafetyMargin = 0.7f;
+const float kMaxBitrateReduction = 0.9f;
+const float kMinBitrateReduction = 0.05f;
+const uint64 kMinBitrateKbit = 10;
+const int kInitialRttMs = 60; // At a typical RTT 60 ms.
+const float kAlpha = 0.125f;
+const float kOneMinusAlpha = 1 - kAlpha;
+
+static const int kBitrateSmoothingPeriodMs = 1000;
+static const int kMinBitrateSmoothingPeriodMs = 500;
+
+} // namespace
+
+InterArrivalSender::InterArrivalSender(const QuicClock* clock)
+ : probing_(true),
+ current_bandwidth_(QuicBandwidth::Zero()),
+ smoothed_rtt_(QuicTime::Delta::Zero()),
+ channel_estimator_(new ChannelEstimator()),
+ bitrate_ramp_up_(new InterArrivalBitrateRampUp(clock)),
+ overuse_detector_(new InterArrivalOveruseDetector()),
+ probe_(new InterArrivalProbe()),
+ state_machine_(new InterArrivalStateMachine(clock)),
+ paced_sender_(new PacedSender(QuicBandwidth::FromKBytesPerSecond(
+ kProbeBitrateKBytesPerSecond))),
+ accumulated_number_of_lost_packets_(0),
+ bandwidth_usage_state_(kBandwidthSteady),
+ back_down_time_(QuicTime::Zero()),
+ back_down_bandwidth_(QuicBandwidth::Zero()),
+ back_down_congestion_delay_(QuicTime::Delta::Zero()) {
+}
+
+InterArrivalSender::~InterArrivalSender() {
+}
+
+// TODO(pwestin): this is really inefficient (4% CPU on the GFE loadtest).
+// static
+QuicBandwidth InterArrivalSender::CalculateSentBandwidth(
+ const SendAlgorithmInterface::SentPacketsMap& sent_packets_map,
+ QuicTime feedback_receive_time) {
+ const QuicTime::Delta kBitrateSmoothingPeriod =
+ QuicTime::Delta::FromMilliseconds(kBitrateSmoothingPeriodMs);
+ const QuicTime::Delta kMinBitrateSmoothingPeriod =
+ QuicTime::Delta::FromMilliseconds(kMinBitrateSmoothingPeriodMs);
+
+ QuicByteCount sum_bytes_sent = 0;
+
+ // Sum packet from new until they are kBitrateSmoothingPeriod old.
+ SendAlgorithmInterface::SentPacketsMap::const_reverse_iterator history_rit =
+ sent_packets_map.rbegin();
+
+ QuicTime::Delta max_diff = QuicTime::Delta::Zero();
+ for (; history_rit != sent_packets_map.rend(); ++history_rit) {
+ QuicTime::Delta diff =
+ feedback_receive_time.Subtract(history_rit->second->SendTimestamp());
+ if (diff > kBitrateSmoothingPeriod) {
+ break;
+ }
+ sum_bytes_sent += history_rit->second->BytesSent();
+ max_diff = diff;
+ }
+ if (max_diff < kMinBitrateSmoothingPeriod) {
+ // No estimate.
+ return QuicBandwidth::Zero();
+ }
+ return QuicBandwidth::FromBytesAndTimeDelta(sum_bytes_sent, max_diff);
+}
+
+void InterArrivalSender::OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& sent_packets) {
+ DCHECK(feedback.type == kInterArrival);
+
+ if (feedback.type != kInterArrival) {
+ return;
+ }
+
+ QuicBandwidth sent_bandwidth = CalculateSentBandwidth(sent_packets,
+ feedback_receive_time);
+
+ TimeMap::const_iterator received_it;
+ for (received_it = feedback.inter_arrival.received_packet_times.begin();
+ received_it != feedback.inter_arrival.received_packet_times.end();
+ ++received_it) {
+ QuicPacketSequenceNumber sequence_number = received_it->first;
+
+ SentPacketsMap::const_iterator sent_it = sent_packets.find(sequence_number);
+ if (sent_it == sent_packets.end()) {
+ // Too old data; ignore and move forward.
+ DLOG(INFO) << "Too old feedback move forward, sequence_number:"
+ << sequence_number;
+ continue;
+ }
+ QuicTime time_received = received_it->second;
+ QuicTime time_sent = sent_it->second->SendTimestamp();
+ QuicByteCount bytes_sent = sent_it->second->BytesSent();
+
+ channel_estimator_->OnAcknowledgedPacket(
+ sequence_number, bytes_sent, time_sent, time_received);
+ if (probing_) {
+ probe_->OnIncomingFeedback(
+ sequence_number, bytes_sent, time_sent, time_received);
+ } else {
+ bool last_of_send_time = false;
+ SentPacketsMap::const_iterator next_sent_it = ++sent_it;
+ if (next_sent_it == sent_packets.end()) {
+ // No more sent packets; hence this must be the last.
+ last_of_send_time = true;
+ } else {
+ if (time_sent != next_sent_it->second->SendTimestamp()) {
+ // Next sent packet have a different send time.
+ last_of_send_time = true;
+ }
+ }
+ overuse_detector_->OnAcknowledgedPacket(
+ sequence_number, time_sent, last_of_send_time, time_received);
+ }
+ }
+ if (probing_) {
+ probing_ = ProbingPhase(feedback_receive_time);
+ return;
+ }
+
+ bool packet_loss_event = false;
+ if (accumulated_number_of_lost_packets_ !=
+ feedback.inter_arrival.accumulated_number_of_lost_packets) {
+ accumulated_number_of_lost_packets_ =
+ feedback.inter_arrival.accumulated_number_of_lost_packets;
+ packet_loss_event = true;
+ }
+ InterArrivalState state = state_machine_->GetInterArrivalState();
+
+ if (state == kInterArrivalStatePacketLoss ||
+ state == kInterArrivalStateCompetingTcpFLow) {
+ if (packet_loss_event) {
+ if (!state_machine_->PacketLossEvent()) {
+ // Less than one RTT since last PacketLossEvent.
+ return;
+ }
+ EstimateBandwidthAfterLossEvent(feedback_receive_time);
+ } else {
+ EstimateNewBandwidth(feedback_receive_time, sent_bandwidth);
+ }
+ return;
+ }
+ EstimateDelayBandwidth(feedback_receive_time, sent_bandwidth);
+}
+
+bool InterArrivalSender::ProbingPhase(QuicTime feedback_receive_time) {
+ QuicBandwidth available_channel_estimate = QuicBandwidth::Zero();
+ if (!probe_->GetEstimate(&available_channel_estimate)) {
+ // Continue probing phase.
+ return true;
+ }
+ QuicBandwidth channel_estimate = QuicBandwidth::Zero();
+ ChannelEstimateState channel_estimator_state =
+ channel_estimator_->GetChannelEstimate(&channel_estimate);
+
+ QuicBandwidth new_rate =
+ available_channel_estimate.Scale(kUncertainSafetyMargin);
+
+ switch (channel_estimator_state) {
+ case kChannelEstimateUnknown:
+ channel_estimate = available_channel_estimate;
+ break;
+ case kChannelEstimateUncertain:
+ channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin);
+ break;
+ case kChannelEstimateGood:
+ // Do nothing.
+ break;
+ }
+ new_rate = std::max(new_rate,
+ QuicBandwidth::FromKBitsPerSecond(kMinBitrateKbit));
+
+ bitrate_ramp_up_->Reset(new_rate, available_channel_estimate,
+ channel_estimate);
+
+ current_bandwidth_ = new_rate;
+ paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, new_rate);
+ DLOG(INFO) << "Probe result; new rate:"
+ << new_rate.ToKBitsPerSecond() << " Kbits/s "
+ << " available estimate:"
+ << available_channel_estimate.ToKBitsPerSecond() << " Kbits/s "
+ << " channel estimate:"
+ << channel_estimate.ToKBitsPerSecond() << " Kbits/s ";
+ return false;
+}
+
+void InterArrivalSender::OnIncomingAck(
+ QuicPacketSequenceNumber /*acked_sequence_number*/,
+ QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) {
+ // RTT can't be negative.
+ DCHECK_LE(0, rtt.ToMicroseconds());
+
+ if (probing_) {
+ probe_->OnAcknowledgedPacket(acked_bytes);
+ }
+
+ if (rtt.IsInfinite()) {
+ return;
+ }
+
+ if (smoothed_rtt_.IsZero()) {
+ smoothed_rtt_ = rtt;
+ } else {
+ smoothed_rtt_ = QuicTime::Delta::FromMicroseconds(
+ kOneMinusAlpha * smoothed_rtt_.ToMicroseconds() +
+ kAlpha * rtt.ToMicroseconds());
+ }
+ state_machine_->set_rtt(smoothed_rtt_);
+}
+
+void InterArrivalSender::OnIncomingLoss(QuicTime ack_receive_time) {
+ // Packet loss was reported.
+ if (!probing_) {
+ if (!state_machine_->PacketLossEvent()) {
+ // Less than one RTT since last PacketLossEvent.
+ return;
+ }
+ // Calculate new pace rate.
+ EstimateBandwidthAfterLossEvent(ack_receive_time);
+ }
+}
+
+void InterArrivalSender::SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes,
+ Retransmission /*retransmit*/) {
+ if (probing_) {
+ probe_->OnSentPacket(bytes);
+ }
+ paced_sender_->SentPacket(sent_time, bytes);
+}
+
+void InterArrivalSender::AbandoningPacket(
+ QuicPacketSequenceNumber /*sequence_number*/,
+ QuicByteCount abandoned_bytes) {
+ // TODO(pwestin): use for out outer_congestion_window_ logic.
+ if (probing_) {
+ probe_->OnAcknowledgedPacket(abandoned_bytes);
+ }
+}
+
+QuicTime::Delta InterArrivalSender::TimeUntilSend(
+ QuicTime now,
+ Retransmission /*retransmit*/,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake /* handshake */) {
+ // TODO(pwestin): implement outer_congestion_window_ logic.
+ QuicTime::Delta outer_window = QuicTime::Delta::Zero();
+
+ if (probing_) {
+ if (has_retransmittable_data == HAS_RETRANSMITTABLE_DATA &&
+ probe_->GetAvailableCongestionWindow() == 0) {
+ outer_window = QuicTime::Delta::Infinite();
+ }
+ }
+ return paced_sender_->TimeUntilSend(now, outer_window);
+}
+
+void InterArrivalSender::EstimateDelayBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth sent_bandwidth) {
+ QuicTime::Delta estimated_congestion_delay = QuicTime::Delta::Zero();
+ BandwidthUsage new_bandwidth_usage_state =
+ overuse_detector_->GetState(&estimated_congestion_delay);
+
+ switch (new_bandwidth_usage_state) {
+ case kBandwidthDraining:
+ case kBandwidthUnderUsing:
+ // Hold our current bitrate.
+ break;
+ case kBandwidthOverUsing:
+ if (!state_machine_->IncreasingDelayEvent()) {
+ // Less than one RTT since last IncreasingDelayEvent.
+ return;
+ }
+ EstimateBandwidthAfterDelayEvent(feedback_receive_time,
+ estimated_congestion_delay);
+ break;
+ case kBandwidthSteady:
+ // Calculate new pace rate.
+ if (bandwidth_usage_state_ == kBandwidthDraining ||
+ bandwidth_usage_state_ == kBandwidthOverUsing) {
+ EstimateNewBandwidthAfterDraining(feedback_receive_time,
+ estimated_congestion_delay);
+ } else {
+ EstimateNewBandwidth(feedback_receive_time, sent_bandwidth);
+ }
+ break;
+ }
+ bandwidth_usage_state_ = new_bandwidth_usage_state;
+}
+
+QuicBandwidth InterArrivalSender::BandwidthEstimate() {
+ return current_bandwidth_;
+}
+
+QuicTime::Delta InterArrivalSender::SmoothedRtt() {
+ if (smoothed_rtt_.IsZero()) {
+ return QuicTime::Delta::FromMilliseconds(kInitialRttMs);
+ }
+ return smoothed_rtt_;
+}
+
+QuicTime::Delta InterArrivalSender::RetransmissionDelay() {
+ // TODO(pwestin): Calculate and return retransmission delay.
+ // Use 2 * the smoothed RTT for now.
+ return smoothed_rtt_.Add(smoothed_rtt_);
+}
+
+void InterArrivalSender::EstimateNewBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth sent_bandwidth) {
+ QuicBandwidth new_bandwidth = bitrate_ramp_up_->GetNewBitrate(sent_bandwidth);
+ if (current_bandwidth_ == new_bandwidth) {
+ return;
+ }
+ current_bandwidth_ = new_bandwidth;
+ state_machine_->IncreaseBitrateDecision();
+
+ QuicBandwidth channel_estimate = QuicBandwidth::Zero();
+ ChannelEstimateState channel_estimator_state =
+ channel_estimator_->GetChannelEstimate(&channel_estimate);
+
+ if (channel_estimator_state == kChannelEstimateGood) {
+ bitrate_ramp_up_->UpdateChannelEstimate(channel_estimate);
+ }
+ paced_sender_->UpdateBandwidthEstimate(feedback_receive_time,
+ current_bandwidth_);
+ DLOG(INFO) << "New bandwidth estimate in steady state:"
+ << current_bandwidth_.ToKBitsPerSecond()
+ << " Kbits/s";
+}
+
+// Did we drain the network buffers in our expected pace?
+void InterArrivalSender::EstimateNewBandwidthAfterDraining(
+ QuicTime feedback_receive_time,
+ QuicTime::Delta estimated_congestion_delay) {
+ if (current_bandwidth_ > back_down_bandwidth_) {
+ // Do nothing, our current bandwidth is higher than our bandwidth at the
+ // previous back down.
+ DLOG(INFO) << "Current bandwidth estimate is higher than before draining";
+ return;
+ }
+ if (estimated_congestion_delay >= back_down_congestion_delay_) {
+ // Do nothing, our estimated delay have increased.
+ DLOG(INFO) << "Current delay estimate is higher than before draining";
+ return;
+ }
+ DCHECK(back_down_time_.IsInitialized());
+ QuicTime::Delta buffer_reduction =
+ back_down_congestion_delay_.Subtract(estimated_congestion_delay);
+ QuicTime::Delta elapsed_time =
+ feedback_receive_time.Subtract(back_down_time_).Subtract(SmoothedRtt());
+
+ QuicBandwidth new_estimate = QuicBandwidth::Zero();
+ if (buffer_reduction >= elapsed_time) {
+ // We have drained more than the elapsed time... go back to our old rate.
+ new_estimate = back_down_bandwidth_;
+ } else {
+ float fraction_of_rate =
+ static_cast<float>(buffer_reduction.ToMicroseconds()) /
+ elapsed_time.ToMicroseconds(); // < 1.0
+
+ QuicBandwidth draining_rate = back_down_bandwidth_.Scale(fraction_of_rate);
+ QuicBandwidth max_estimated_draining_rate =
+ back_down_bandwidth_.Subtract(current_bandwidth_);
+ if (draining_rate > max_estimated_draining_rate) {
+ // We drained faster than our old send rate, go back to our old rate.
+ new_estimate = back_down_bandwidth_;
+ } else {
+ // Use our drain rate and our kMinBitrateReduction to go to our
+ // new estimate.
+ new_estimate = std::max(current_bandwidth_,
+ current_bandwidth_.Add(draining_rate).Scale(
+ 1.0f - kMinBitrateReduction));
+ DLOG(INFO) << "Draining calculation; current rate:"
+ << current_bandwidth_.ToKBitsPerSecond() << " Kbits/s "
+ << "draining rate:"
+ << draining_rate.ToKBitsPerSecond() << " Kbits/s "
+ << "new estimate:"
+ << new_estimate.ToKBitsPerSecond() << " Kbits/s "
+ << " buffer reduction:"
+ << buffer_reduction.ToMicroseconds() << " us "
+ << " elapsed time:"
+ << elapsed_time.ToMicroseconds() << " us ";
+ }
+ }
+ if (new_estimate == current_bandwidth_) {
+ return;
+ }
+
+ QuicBandwidth channel_estimate = QuicBandwidth::Zero();
+ ChannelEstimateState channel_estimator_state =
+ channel_estimator_->GetChannelEstimate(&channel_estimate);
+
+ // TODO(pwestin): we need to analyze channel_estimate too.
+ switch (channel_estimator_state) {
+ case kChannelEstimateUnknown:
+ channel_estimate = current_bandwidth_;
+ break;
+ case kChannelEstimateUncertain:
+ channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin);
+ break;
+ case kChannelEstimateGood:
+ // Do nothing, estimate is accurate.
+ break;
+ }
+ bitrate_ramp_up_->Reset(new_estimate, back_down_bandwidth_, channel_estimate);
+ state_machine_->IncreaseBitrateDecision();
+ paced_sender_->UpdateBandwidthEstimate(feedback_receive_time, new_estimate);
+ current_bandwidth_ = new_estimate;
+ DLOG(INFO) << "New bandwidth estimate after draining:"
+ << new_estimate.ToKBitsPerSecond() << " Kbits/s";
+}
+
+void InterArrivalSender::EstimateBandwidthAfterDelayEvent(
+ QuicTime feedback_receive_time,
+ QuicTime::Delta estimated_congestion_delay) {
+ QuicByteCount estimated_byte_buildup =
+ current_bandwidth_.ToBytesPerPeriod(estimated_congestion_delay);
+
+ // To drain all build up buffer within one RTT we need to reduce the
+ // bitrate with the following.
+ // TODO(pwestin): this is a crude first implementation.
+ int64 draining_rate_per_rtt = (estimated_byte_buildup *
+ kNumMicrosPerSecond) / SmoothedRtt().ToMicroseconds();
+
+ float decrease_factor =
+ draining_rate_per_rtt / current_bandwidth_.ToBytesPerSecond();
+
+ decrease_factor = std::max(decrease_factor, kMinBitrateReduction);
+ decrease_factor = std::min(decrease_factor, kMaxBitrateReduction);
+ back_down_congestion_delay_ = estimated_congestion_delay;
+ QuicBandwidth new_target_bitrate =
+ current_bandwidth_.Scale(1.0f - decrease_factor);
+
+ // While in delay sensing mode send at least one packet per RTT.
+ QuicBandwidth min_delay_bitrate =
+ QuicBandwidth::FromBytesAndTimeDelta(kMaxPacketSize, SmoothedRtt());
+ new_target_bitrate = std::max(new_target_bitrate, min_delay_bitrate);
+
+ ResetCurrentBandwidth(feedback_receive_time, new_target_bitrate);
+
+ DLOG(INFO) << "New bandwidth estimate after delay event:"
+ << current_bandwidth_.ToKBitsPerSecond()
+ << " Kbits/s min delay bitrate:"
+ << min_delay_bitrate.ToKBitsPerSecond()
+ << " Kbits/s RTT:"
+ << SmoothedRtt().ToMicroseconds()
+ << " us";
+}
+
+void InterArrivalSender::EstimateBandwidthAfterLossEvent(
+ QuicTime feedback_receive_time) {
+ ResetCurrentBandwidth(feedback_receive_time,
+ current_bandwidth_.Scale(kPacketLossBitrateReduction));
+ DLOG(INFO) << "New bandwidth estimate after loss event:"
+ << current_bandwidth_.ToKBitsPerSecond()
+ << " Kbits/s";
+}
+
+void InterArrivalSender::ResetCurrentBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth new_rate) {
+ new_rate = std::max(new_rate,
+ QuicBandwidth::FromKBitsPerSecond(kMinBitrateKbit));
+ QuicBandwidth channel_estimate = QuicBandwidth::Zero();
+ ChannelEstimateState channel_estimator_state =
+ channel_estimator_->GetChannelEstimate(&channel_estimate);
+
+ switch (channel_estimator_state) {
+ case kChannelEstimateUnknown:
+ channel_estimate = current_bandwidth_;
+ break;
+ case kChannelEstimateUncertain:
+ channel_estimate = channel_estimate.Scale(kUncertainSafetyMargin);
+ break;
+ case kChannelEstimateGood:
+ // Do nothing.
+ break;
+ }
+ back_down_time_ = feedback_receive_time;
+ back_down_bandwidth_ = current_bandwidth_;
+ bitrate_ramp_up_->Reset(new_rate, current_bandwidth_, channel_estimate);
+ if (new_rate != current_bandwidth_) {
+ current_bandwidth_ = new_rate;
+ paced_sender_->UpdateBandwidthEstimate(feedback_receive_time,
+ current_bandwidth_);
+ state_machine_->DecreaseBitrateDecision();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_sender.h b/chromium/net/quic/congestion_control/inter_arrival_sender.h
new file mode 100644
index 00000000000..ad28ecd215a
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_sender.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 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_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_SENDER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/channel_estimator.h"
+#include "net/quic/congestion_control/inter_arrival_bitrate_ramp_up.h"
+#include "net/quic/congestion_control/inter_arrival_overuse_detector.h"
+#include "net/quic/congestion_control/inter_arrival_probe.h"
+#include "net/quic/congestion_control/inter_arrival_state_machine.h"
+#include "net/quic/congestion_control/paced_sender.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE InterArrivalSender : public SendAlgorithmInterface {
+ public:
+ explicit InterArrivalSender(const QuicClock* clock);
+ virtual ~InterArrivalSender();
+
+ static QuicBandwidth CalculateSentBandwidth(
+ const SendAlgorithmInterface::SentPacketsMap& sent_packets_map,
+ QuicTime feedback_receive_time);
+
+ // Start implementation of SendAlgorithmInterface.
+ virtual void OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& sent_packets) OVERRIDE;
+
+ virtual void OnIncomingAck(QuicPacketSequenceNumber acked_sequence_number,
+ QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) OVERRIDE;
+
+ virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE;
+
+ virtual void SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes,
+ Retransmission is_retransmit) OVERRIDE;
+
+ virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes) OVERRIDE;
+
+ virtual QuicTime::Delta TimeUntilSend(
+ QuicTime now,
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) OVERRIDE;
+
+ virtual QuicBandwidth BandwidthEstimate() OVERRIDE;
+ virtual QuicTime::Delta SmoothedRtt() OVERRIDE;
+ virtual QuicTime::Delta RetransmissionDelay() OVERRIDE;
+ // End implementation of SendAlgorithmInterface.
+
+ private:
+ void EstimateDelayBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth sent_bandwidth);
+ void EstimateNewBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth sent_bandwidth);
+ void EstimateNewBandwidthAfterDraining(
+ QuicTime feedback_receive_time,
+ QuicTime::Delta estimated_congestion_delay);
+ void EstimateBandwidthAfterLossEvent(QuicTime feedback_receive_time);
+ void EstimateBandwidthAfterDelayEvent(
+ QuicTime feedback_receive_time,
+ QuicTime::Delta estimated_congestion_delay);
+ void ResetCurrentBandwidth(QuicTime feedback_receive_time,
+ QuicBandwidth new_rate);
+ bool ProbingPhase(QuicTime feedback_receive_time);
+
+ bool probing_; // Are we currently in the probing phase?
+ QuicBandwidth current_bandwidth_;
+ QuicTime::Delta smoothed_rtt_;
+ scoped_ptr<ChannelEstimator> channel_estimator_;
+ scoped_ptr<InterArrivalBitrateRampUp> bitrate_ramp_up_;
+ scoped_ptr<InterArrivalOveruseDetector> overuse_detector_;
+ scoped_ptr<InterArrivalProbe> probe_;
+ scoped_ptr<InterArrivalStateMachine> state_machine_;
+ scoped_ptr<PacedSender> paced_sender_;
+ int accumulated_number_of_lost_packets_;
+ BandwidthUsage bandwidth_usage_state_;
+ QuicTime back_down_time_; // Time when we decided to back down.
+ QuicBandwidth back_down_bandwidth_; // Bandwidth before backing down.
+ QuicTime::Delta back_down_congestion_delay_; // Delay when backing down.
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalSender);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_SENDER_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_sender_test.cc b/chromium/net/quic/congestion_control/inter_arrival_sender_test.cc
new file mode 100644
index 00000000000..d0faca0f8f1
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_sender_test.cc
@@ -0,0 +1,565 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/inter_arrival_sender.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class InterArrivalSenderTest : public ::testing::Test {
+ protected:
+ InterArrivalSenderTest()
+ : rtt_(QuicTime::Delta::FromMilliseconds(60)),
+ one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+ one_s_(QuicTime::Delta::FromMilliseconds(1000)),
+ nine_ms_(QuicTime::Delta::FromMilliseconds(9)),
+ send_start_time_(send_clock_.Now()),
+ sender_(&send_clock_),
+ sequence_number_(1),
+ acked_sequence_number_(1),
+ feedback_sequence_number_(1) {
+ send_clock_.AdvanceTime(one_ms_);
+ receive_clock_.AdvanceTime(one_ms_);
+ }
+
+ virtual ~InterArrivalSenderTest() {
+ STLDeleteValues(&sent_packets_);
+ }
+
+ void SendAvailableCongestionWindow() {
+ while (sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero()) {
+ QuicByteCount bytes_in_packet = kMaxPacketSize;
+ sent_packets_[sequence_number_] =
+ new class SendAlgorithmInterface::SentPacket(
+ bytes_in_packet, send_clock_.Now());
+
+ sender_.SentPacket(send_clock_.Now(), sequence_number_, bytes_in_packet,
+ NOT_RETRANSMISSION);
+ sequence_number_++;
+ }
+ EXPECT_FALSE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+
+ void AckNPackets(int n) {
+ for (int i = 0; i < n; ++i) {
+ sender_.OnIncomingAck(acked_sequence_number_++, kMaxPacketSize, rtt_);
+ }
+ }
+
+ void SendDelaySpikeFeedbackMessage(QuicTime::Delta spike_time) {
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kInterArrival;
+ feedback.inter_arrival.accumulated_number_of_lost_packets = 0;
+ receive_clock_.AdvanceTime(spike_time);
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ feedback.inter_arrival.received_packet_times.insert(
+ std::pair<QuicPacketSequenceNumber, QuicTime>(
+ feedback_sequence_number_, receive_time));
+ feedback_sequence_number_++;
+
+ // We need to send feedback for 2 packets since they where sent at the
+ // same time.
+ feedback.inter_arrival.received_packet_times.insert(
+ std::pair<QuicPacketSequenceNumber, QuicTime>(
+ feedback_sequence_number_, receive_time));
+ feedback_sequence_number_++;
+
+ sender_.OnIncomingQuicCongestionFeedbackFrame(feedback, send_clock_.Now(),
+ sent_packets_);
+ }
+
+ void SendFeedbackMessageNPackets(int n,
+ QuicTime::Delta delta_odd,
+ QuicTime::Delta delta_even) {
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kInterArrival;
+ feedback.inter_arrival.accumulated_number_of_lost_packets = 0;
+ for (int i = 0; i < n; ++i) {
+ if (feedback_sequence_number_ % 2) {
+ receive_clock_.AdvanceTime(delta_even);
+ } else {
+ receive_clock_.AdvanceTime(delta_odd);
+ }
+ QuicTime receive_time = receive_clock_.ApproximateNow();
+ feedback.inter_arrival.received_packet_times.insert(
+ std::pair<QuicPacketSequenceNumber, QuicTime>(
+ feedback_sequence_number_, receive_time));
+ feedback_sequence_number_++;
+ }
+ sender_.OnIncomingQuicCongestionFeedbackFrame(feedback, send_clock_.Now(),
+ sent_packets_);
+ }
+
+ QuicTime::Delta SenderDeltaSinceStart() {
+ return send_clock_.ApproximateNow().Subtract(send_start_time_);
+ }
+
+ const QuicTime::Delta rtt_;
+ const QuicTime::Delta one_ms_;
+ const QuicTime::Delta one_s_;
+ const QuicTime::Delta nine_ms_;
+ MockClock send_clock_;
+ MockClock receive_clock_;
+ const QuicTime send_start_time_;
+ InterArrivalSender sender_;
+ QuicPacketSequenceNumber sequence_number_;
+ QuicPacketSequenceNumber acked_sequence_number_;
+ QuicPacketSequenceNumber feedback_sequence_number_;
+ SendAlgorithmInterface::SentPacketsMap sent_packets_;
+};
+
+TEST_F(InterArrivalSenderTest, ProbeFollowedByFullRampUpCycle) {
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // Send 5 bursts.
+ for (int i = 0; i < 4; ++i) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ SendAvailableCongestionWindow();
+
+ // We have now sent our probe.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(), NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsInfinite());
+
+ AckNPackets(10);
+ SendFeedbackMessageNPackets(10, one_ms_, nine_ms_);
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // We should now have our probe rate.
+ QuicTime::Delta acc_arrival_time = QuicTime::Delta::FromMilliseconds(41);
+ int64 probe_rate = kMaxPacketSize * 9 * kNumMicrosPerSecond /
+ acc_arrival_time.ToMicroseconds();
+ EXPECT_NEAR(0.7f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ DLOG(INFO) << "After probe";
+ // Send 50 bursts, make sure that we move fast in the beginning.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.875f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 600, 10);
+
+ // Send 50 bursts, make sure that we slow down towards the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.95f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 1100, 10);
+
+ // Send 50 bursts, make sure that we move very slow close to the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.99f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 1560, 10);
+ DLOG(INFO) << "Near available channel estimate";
+
+ // Send 50 bursts, make sure that we move very slow close to the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(1.00f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 2000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 2000, 100);
+ DLOG(INFO) << "At available channel estimate";
+
+ // Send 50 bursts, make sure that we move very slow close to the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(1.01f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 2000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 2500, 100);
+
+ // Send 50 bursts, make sure that we accelerate after the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(1.01f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 2000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 2900, 100);
+
+ // Send 50 bursts, make sure that we accelerate after the probe rate.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(1.03f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 2000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 3400, 100);
+
+ int64 max_rate = kMaxPacketSize * kNumMicrosPerSecond /
+ one_ms_.ToMicroseconds();
+
+ int64 halfway_rate = probe_rate + (max_rate - probe_rate) / 2;
+
+ // Send until we reach halfway point.
+ for (int i = 0; i < 570; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(halfway_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 5000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 6600, 100);
+ DLOG(INFO) << "Near halfway point";
+
+ // Send until we reach max channel capacity.
+ for (int i = 0; i < 1500; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(max_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 5000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 10000, 200);
+}
+
+TEST_F(InterArrivalSenderTest, DelaySpikeFollowedBySlowDrain) {
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // Send 5 bursts.
+ for (int i = 0; i < 4; ++i) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ SendAvailableCongestionWindow();
+
+ // We have now sent our probe.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(), NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsInfinite());
+
+ AckNPackets(10);
+ SendFeedbackMessageNPackets(10, one_ms_, nine_ms_);
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // We should now have our probe rate.
+ QuicTime::Delta acc_arrival_time = QuicTime::Delta::FromMilliseconds(41);
+ int64 probe_rate = kMaxPacketSize * 9 * kNumMicrosPerSecond /
+ acc_arrival_time.ToMicroseconds();
+ EXPECT_NEAR(0.7f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+
+ // Send 50 bursts, make sure that we move fast in the beginning.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.875f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 600, 10);
+
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+
+ int64 rate_at_introduced_delay_spike = 0.875f * probe_rate;
+ QuicTime::Delta spike_time = QuicTime::Delta::FromMilliseconds(100);
+ SendDelaySpikeFeedbackMessage(spike_time);
+
+ // Backing as much as we can, currently 90%.
+ EXPECT_NEAR(0.1f * rate_at_introduced_delay_spike,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 610, 10);
+
+ // Run until we are catched up after our introduced delay spike.
+ while (send_clock_.Now() < receive_clock_.Now()) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, one_ms_);
+ }
+ // Expect that we go back to 67% of the rate before the spike.
+ EXPECT_NEAR(0.67f * rate_at_introduced_delay_spike,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 820, 10);
+
+ // Send 100 bursts, make sure that we slow down towards the rate we had
+ // before the spike.
+ for (int i = 0; i < 100; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.97f * rate_at_introduced_delay_spike,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 2100, 10);
+}
+
+TEST_F(InterArrivalSenderTest, DelaySpikeFollowedByImmediateDrain) {
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // Send 5 bursts.
+ for (int i = 0; i < 4; ++i) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ SendAvailableCongestionWindow();
+
+ // We have now sent our probe.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(), NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsInfinite());
+
+ AckNPackets(10);
+ SendFeedbackMessageNPackets(10, one_ms_, nine_ms_);
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // We should now have our probe rate.
+ QuicTime::Delta acc_arrival_time = QuicTime::Delta::FromMilliseconds(41);
+ int64 probe_rate = kMaxPacketSize * 9 * kNumMicrosPerSecond /
+ acc_arrival_time.ToMicroseconds();
+ EXPECT_NEAR(0.7f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+
+ // Send 50 bursts, make sure that we move fast in the beginning.
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, time_until_send.Subtract(one_ms_));
+ }
+ EXPECT_NEAR(0.875f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 600, 10);
+
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ AckNPackets(2);
+
+ int64 rate_at_introduced_delay_spike = 0.875f * probe_rate;
+ QuicTime::Delta spike_time = QuicTime::Delta::FromMilliseconds(100);
+ SendDelaySpikeFeedbackMessage(spike_time);
+
+ // Backing as much as we can, currently 90%.
+ EXPECT_NEAR(0.1f * rate_at_introduced_delay_spike,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 610, 10);
+
+ // Move send time forward.
+ send_clock_.AdvanceTime(spike_time);
+ // Make sure our clocks are aligned again.
+ receive_clock_.AdvanceTime(send_clock_.Now().Subtract(receive_clock_.Now()));
+
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ SendFeedbackMessageNPackets(2, one_ms_, one_ms_);
+ // We should now be back where we introduced the delay spike.
+ EXPECT_NEAR(rate_at_introduced_delay_spike,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+ EXPECT_NEAR(SenderDeltaSinceStart().ToMilliseconds(), 710, 10);
+}
+
+TEST_F(InterArrivalSenderTest, MinBitrateDueToDelay) {
+ QuicBandwidth expected_min_bitrate = QuicBandwidth::FromKBitsPerSecond(10);
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // Send 5 bursts.
+ for (int i = 0; i < 4; ++i) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ SendAvailableCongestionWindow();
+
+ AckNPackets(10);
+
+ // One second spread per packet is expected to result in an estimate at
+ // our minimum bitrate.
+ SendFeedbackMessageNPackets(10, one_s_, one_s_);
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ EXPECT_EQ(expected_min_bitrate, sender_.BandwidthEstimate());
+}
+
+TEST_F(InterArrivalSenderTest, MinBitrateDueToLoss) {
+ QuicBandwidth expected_min_bitrate = QuicBandwidth::FromKBitsPerSecond(10);
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ // Send 5 bursts.
+ for (int i = 0; i < 4; ++i) {
+ SendAvailableCongestionWindow();
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ SendAvailableCongestionWindow();
+
+ AckNPackets(10);
+ SendFeedbackMessageNPackets(10, nine_ms_, nine_ms_);
+ send_clock_.AdvanceTime(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ QuicTime::Delta acc_arrival_time = QuicTime::Delta::FromMilliseconds(81);
+ int64 probe_rate = kMaxPacketSize * 9 * kNumMicrosPerSecond /
+ acc_arrival_time.ToMicroseconds();
+ EXPECT_NEAR(0.7f * probe_rate,
+ sender_.BandwidthEstimate().ToBytesPerSecond(), 1000);
+
+ for (int i = 0; i < 15; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ sender_.OnIncomingLoss(send_clock_.Now());
+ sender_.OnIncomingAck(acked_sequence_number_, kMaxPacketSize, rtt_);
+ acked_sequence_number_ += 2; // Create a loss by not acking both packets.
+ SendFeedbackMessageNPackets(2, nine_ms_, nine_ms_);
+ }
+ // Test that our exponentail back off stop at expected_min_bitrate.
+ EXPECT_EQ(expected_min_bitrate, sender_.BandwidthEstimate());
+
+ for (int i = 0; i < 50; ++i) {
+ SendAvailableCongestionWindow();
+ QuicTime::Delta time_until_send = sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ send_clock_.AdvanceTime(time_until_send);
+ EXPECT_TRUE(sender_.TimeUntilSend(send_clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ sender_.OnIncomingLoss(send_clock_.Now());
+ sender_.OnIncomingAck(acked_sequence_number_, kMaxPacketSize, rtt_);
+ acked_sequence_number_ += 2; // Create a loss by not acking both packets.
+ SendFeedbackMessageNPackets(2, nine_ms_, nine_ms_);
+
+ // Make sure our bitrate is fixed at the expected_min_bitrate.
+ EXPECT_EQ(expected_min_bitrate, sender_.BandwidthEstimate());
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_state_machine.cc b/chromium/net/quic/congestion_control/inter_arrival_state_machine.cc
new file mode 100644
index 00000000000..7095e60306b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_state_machine.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2013 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/quic/congestion_control/inter_arrival_state_machine.h"
+
+#include "base/logging.h"
+
+namespace {
+const int kIncreaseEventsBeforeDowngradingState = 5;
+const int kDecreaseEventsBeforeUpgradingState = 2;
+// Note: Can not be higher than kDecreaseEventsBeforeUpgradingState;
+const int kLossEventsBeforeUpgradingState = 2;
+// Timeout old loss and delay events after this time.
+const int kEventTimeoutMs = 10000;
+// A reasonable arbitrary chosen value for initial round trip time.
+const int kInitialRttMs = 80;
+}
+
+namespace net {
+
+InterArrivalStateMachine::InterArrivalStateMachine(const QuicClock* clock)
+ : clock_(clock),
+ current_state_(kInterArrivalStateStable),
+ smoothed_rtt_(QuicTime::Delta::FromMilliseconds(kInitialRttMs)),
+ decrease_event_count_(0),
+ last_decrease_event_(QuicTime::Zero()),
+ increase_event_count_(0),
+ last_increase_event_(QuicTime::Zero()),
+ loss_event_count_(0),
+ last_loss_event_(QuicTime::Zero()),
+ delay_event_count_(0),
+ last_delay_event_(QuicTime::Zero()) {
+}
+
+InterArrivalState InterArrivalStateMachine::GetInterArrivalState() {
+ return current_state_;
+}
+
+void InterArrivalStateMachine::IncreaseBitrateDecision() {
+ // Multiple increase event without packet loss or delay events will drive
+ // state back to stable.
+ QuicTime current_time = clock_->ApproximateNow();
+ if (current_time.Subtract(last_increase_event_) < smoothed_rtt_) {
+ // Less than one RTT have passed; ignore this event.
+ return;
+ }
+ last_increase_event_ = current_time;
+ increase_event_count_++;
+ decrease_event_count_ = 0; // Reset previous decrease events.
+
+ if (increase_event_count_ < kIncreaseEventsBeforeDowngradingState) {
+ // Not enough increase events to change state.
+ return;
+ }
+ increase_event_count_ = 0; // Reset increase events.
+
+ switch (current_state_) {
+ case kInterArrivalStateStable:
+ // Keep this state.
+ break;
+ case kInterArrivalStatePacketLoss:
+ current_state_ = kInterArrivalStateStable;
+ break;
+ case kInterArrivalStateDelay:
+ current_state_ = kInterArrivalStateStable;
+ break;
+ case kInterArrivalStateCompetingFlow:
+ current_state_ = kInterArrivalStateDelay;
+ break;
+ case kInterArrivalStateCompetingTcpFLow:
+ current_state_ = kInterArrivalStateDelay;
+ break;
+ }
+}
+
+void InterArrivalStateMachine::DecreaseBitrateDecision() {
+ DCHECK(kDecreaseEventsBeforeUpgradingState >=
+ kLossEventsBeforeUpgradingState);
+
+ QuicTime current_time = clock_->ApproximateNow();
+ if (current_time.Subtract(last_decrease_event_) < smoothed_rtt_) {
+ // Less than one RTT have passed; ignore this event.
+ return;
+ }
+ last_decrease_event_ = current_time;
+ decrease_event_count_++;
+ increase_event_count_ = 0; // Reset previous increase events.
+ if (decrease_event_count_ < kDecreaseEventsBeforeUpgradingState) {
+ // Not enough decrease events to change state.
+ return;
+ }
+ decrease_event_count_ = 0; // Reset decrease events.
+
+ switch (current_state_) {
+ case kInterArrivalStateStable:
+ if (delay_event_count_ == 0 && loss_event_count_ > 0) {
+ // No recent delay events; only packet loss events.
+ current_state_ = kInterArrivalStatePacketLoss;
+ } else {
+ current_state_ = kInterArrivalStateDelay;
+ }
+ break;
+ case kInterArrivalStatePacketLoss:
+ // Keep this state.
+ break;
+ case kInterArrivalStateDelay:
+ if (loss_event_count_ >= kLossEventsBeforeUpgradingState) {
+ // We have packet loss events. Assume fighting with TCP.
+ current_state_ = kInterArrivalStateCompetingTcpFLow;
+ } else {
+ current_state_ = kInterArrivalStateCompetingFlow;
+ }
+ break;
+ case kInterArrivalStateCompetingFlow:
+ if (loss_event_count_ >= kLossEventsBeforeUpgradingState) {
+ // We have packet loss events. Assume fighting with TCP.
+ current_state_ = kInterArrivalStateCompetingTcpFLow;
+ }
+ break;
+ case kInterArrivalStateCompetingTcpFLow:
+ // Keep this state.
+ break;
+ }
+}
+
+void InterArrivalStateMachine::set_rtt(QuicTime::Delta rtt) {
+ smoothed_rtt_ = rtt;
+}
+
+bool InterArrivalStateMachine::PacketLossEvent() {
+ QuicTime current_time = clock_->ApproximateNow();
+ if (current_time.Subtract(last_loss_event_) < smoothed_rtt_) {
+ // Less than one RTT have passed; ignore this event.
+ return false;
+ }
+ last_loss_event_ = current_time;
+ loss_event_count_++;
+ if (current_time.Subtract(last_delay_event_) >
+ QuicTime::Delta::FromMilliseconds(kEventTimeoutMs)) {
+ // Delay event have timed out.
+ delay_event_count_ = 0;
+ }
+ return true;
+}
+
+bool InterArrivalStateMachine::IncreasingDelayEvent() {
+ QuicTime current_time = clock_->ApproximateNow();
+ if (current_time.Subtract(last_delay_event_) < smoothed_rtt_) {
+ // Less than one RTT have passed; ignore this event.
+ return false;
+ }
+ last_delay_event_ = current_time;
+ delay_event_count_++;
+ if (current_time.Subtract(last_loss_event_) >
+ QuicTime::Delta::FromMilliseconds(kEventTimeoutMs)) {
+ // Loss event have timed out.
+ loss_event_count_ = 0;
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/inter_arrival_state_machine.h b/chromium/net/quic/congestion_control/inter_arrival_state_machine.h
new file mode 100644
index 00000000000..829aa29938a
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_state_machine.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2013 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.
+
+// State machine for congestion control. The state is updated by calls from
+// other modules as they detect events, or decide on taking specific actions.
+// Events include things like packet loss, or growing delay, while decisions
+// include decisions to increase or decrease bitrates.
+// This class should be called for every event and decision made by the
+// congestion control, this class will coalesce all calls relative to the
+// smoothed RTT.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_STATE_MACHINE_H_
+#define NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_STATE_MACHINE_H_
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// State transition diagram.
+//
+// kInterArrivalStatePacketLoss
+// |
+// kInterArrivalStateStable
+// |
+// kInterArrivalStateDelay
+// | |
+// kInterArrivalStateCompetingFlow -> kInterArrivalStateCompetingTcpFLow
+
+enum NET_EXPORT_PRIVATE InterArrivalState {
+ // We are on a network with a delay that is too small to be reliably detected,
+ // such as a local ethernet.
+ kInterArrivalStatePacketLoss = 1,
+ // We are on an underutilized network operating with low delay and low loss.
+ kInterArrivalStateStable = 2,
+ // We are on a network where we can detect delay changes and suffer only
+ // low loss. Nothing indicates that we are competing with another flow.
+ kInterArrivalStateDelay = 3,
+ // We are on a network where we can detect delay changes and suffer only
+ // low loss. We have indications that we are compete with another flow.
+ kInterArrivalStateCompetingFlow = 4,
+ // We are on a network where we can detect delay changes, however we suffer
+ // packet loss due to sharing the bottleneck link with another flow, which is
+ // most likely a TCP flow.
+ kInterArrivalStateCompetingTcpFLow = 5,
+};
+
+class NET_EXPORT_PRIVATE InterArrivalStateMachine {
+ public:
+ explicit InterArrivalStateMachine(const QuicClock* clock);
+
+ InterArrivalState GetInterArrivalState();
+
+ // Inter arrival congestion control decided to increase bitrate.
+ void IncreaseBitrateDecision();
+
+ // Inter arrival congestion control decided to decrease bitrate.
+ void DecreaseBitrateDecision();
+
+ // Estimated smoothed round trip time.
+ // This should be called whenever the smoothed RTT estimate is updated.
+ void set_rtt(QuicTime::Delta rtt);
+
+ // This method is called when a packet loss was reported.
+ bool PacketLossEvent();
+
+ // This method is called when we believe that packet transit delay is
+ // increasing, presumably due to a growing queue along the path.
+ bool IncreasingDelayEvent();
+
+ private:
+ const QuicClock* clock_;
+ InterArrivalState current_state_;
+ QuicTime::Delta smoothed_rtt_;
+
+ int decrease_event_count_;
+ QuicTime last_decrease_event_;
+
+ int increase_event_count_;
+ QuicTime last_increase_event_;
+
+ int loss_event_count_;
+ QuicTime last_loss_event_;
+
+ int delay_event_count_;
+ QuicTime last_delay_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterArrivalStateMachine);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_INTER_ARRIVAL_STATE_MACHINE_H_
diff --git a/chromium/net/quic/congestion_control/inter_arrival_state_machine_test.cc b/chromium/net/quic/congestion_control/inter_arrival_state_machine_test.cc
new file mode 100644
index 00000000000..1a4bc8523ea
--- /dev/null
+++ b/chromium/net/quic/congestion_control/inter_arrival_state_machine_test.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2013 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/inter_arrival_state_machine.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class InterArrivalStateMachineTest : public ::testing::Test {
+ protected:
+ InterArrivalStateMachineTest() {
+ }
+
+ virtual void SetUp() {
+ state_machine_.reset(new InterArrivalStateMachine(&clock_));
+ }
+
+ MockClock clock_;
+ scoped_ptr<InterArrivalStateMachine> state_machine_;
+};
+
+TEST_F(InterArrivalStateMachineTest, SimplePacketLoss) {
+ QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(100);
+ state_machine_->set_rtt(rtt);
+ state_machine_->IncreaseBitrateDecision();
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateStable,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we switch to state packet loss.
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStatePacketLoss,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we stay in state packet loss.
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStatePacketLoss,
+ state_machine_->GetInterArrivalState());
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStatePacketLoss,
+ state_machine_->GetInterArrivalState());
+}
+
+TEST_F(InterArrivalStateMachineTest, SimpleDelay) {
+ QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(100);
+ state_machine_->set_rtt(rtt);
+ state_machine_->IncreaseBitrateDecision();
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->IncreasingDelayEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateStable,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we switch to state delay.
+ clock_.AdvanceTime(rtt);
+ state_machine_->IncreasingDelayEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateDelay,
+ state_machine_->GetInterArrivalState());
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->IncreasingDelayEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateDelay,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we switch to state competing flow(s).
+ clock_.AdvanceTime(rtt);
+ state_machine_->IncreasingDelayEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingFlow,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we stay in state competing flow(s).
+ clock_.AdvanceTime(rtt);
+ state_machine_->IncreasingDelayEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingFlow,
+ state_machine_->GetInterArrivalState());
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingFlow,
+ state_machine_->GetInterArrivalState());
+
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingFlow,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we switch to state competing TCP flow(s).
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingTcpFLow,
+ state_machine_->GetInterArrivalState());
+
+ // Make sure we stay in state competing TCP flow(s).
+ clock_.AdvanceTime(rtt);
+ state_machine_->PacketLossEvent();
+ state_machine_->DecreaseBitrateDecision();
+ EXPECT_EQ(kInterArrivalStateCompetingTcpFLow,
+ state_machine_->GetInterArrivalState());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/leaky_bucket.cc b/chromium/net/quic/congestion_control/leaky_bucket.cc
new file mode 100644
index 00000000000..06c1f87f3ae
--- /dev/null
+++ b/chromium/net/quic/congestion_control/leaky_bucket.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/leaky_bucket.h"
+
+#include "base/time/time.h"
+
+namespace net {
+
+LeakyBucket::LeakyBucket(QuicBandwidth draining_rate)
+ : bytes_(0),
+ time_last_updated_(QuicTime::Zero()),
+ draining_rate_(draining_rate) {
+}
+
+void LeakyBucket::SetDrainingRate(QuicTime now, QuicBandwidth draining_rate) {
+ Update(now);
+ draining_rate_ = draining_rate;
+}
+
+void LeakyBucket::Add(QuicTime now, QuicByteCount bytes) {
+ Update(now);
+ bytes_ += bytes;
+}
+
+QuicTime::Delta LeakyBucket::TimeRemaining(QuicTime now) {
+ Update(now);
+ return QuicTime::Delta::FromMicroseconds(
+ (bytes_ * base::Time::kMicrosecondsPerSecond) /
+ draining_rate_.ToBytesPerSecond());
+}
+
+QuicByteCount LeakyBucket::BytesPending(QuicTime now) {
+ Update(now);
+ return bytes_;
+}
+
+void LeakyBucket::Update(QuicTime now) {
+ QuicTime::Delta elapsed_time = now.Subtract(time_last_updated_);
+ QuicByteCount bytes_cleared = draining_rate_.ToBytesPerPeriod(elapsed_time);
+ if (bytes_cleared >= bytes_) {
+ bytes_ = 0;
+ } else {
+ bytes_ -= bytes_cleared;
+ }
+ time_last_updated_ = now;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/leaky_bucket.h b/chromium/net/quic/congestion_control/leaky_bucket.h
new file mode 100644
index 00000000000..63ef8102a39
--- /dev/null
+++ b/chromium/net/quic/congestion_control/leaky_bucket.h
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+//
+// Helper class to track the rate data can leave the buffer for pacing.
+// A leaky bucket drains the data at a constant rate regardless of fullness of
+// the buffer.
+// See http://en.wikipedia.org/wiki/Leaky_bucket for more details.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
+#define NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE LeakyBucket {
+ public:
+ explicit LeakyBucket(QuicBandwidth draining_rate);
+
+ // Set the rate at which the bytes leave the buffer.
+ void SetDrainingRate(QuicTime now, QuicBandwidth draining_rate);
+
+ // Add data to the buffer.
+ void Add(QuicTime now, QuicByteCount bytes);
+
+ // Time until the buffer is empty.
+ QuicTime::Delta TimeRemaining(QuicTime now);
+
+ // Number of bytes in the buffer.
+ QuicByteCount BytesPending(QuicTime now);
+
+ private:
+ void Update(QuicTime now);
+
+ QuicByteCount bytes_;
+ QuicTime time_last_updated_;
+ QuicBandwidth draining_rate_;
+
+ DISALLOW_COPY_AND_ASSIGN(LeakyBucket);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_LEAKY_BUCKET_H_
diff --git a/chromium/net/quic/congestion_control/leaky_bucket_test.cc b/chromium/net/quic/congestion_control/leaky_bucket_test.cc
new file mode 100644
index 00000000000..977ef94b6c1
--- /dev/null
+++ b/chromium/net/quic/congestion_control/leaky_bucket_test.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/leaky_bucket.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class LeakyBucketTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ leaky_bucket_.reset(new LeakyBucket(QuicBandwidth::Zero()));
+ }
+ MockClock clock_;
+ scoped_ptr<LeakyBucket> leaky_bucket_;
+};
+
+TEST_F(LeakyBucketTest, Basic) {
+ QuicBandwidth draining_rate = QuicBandwidth::FromBytesPerSecond(200000);
+ leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+ leaky_bucket_->Add(clock_.Now(), 2000);
+ EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(5),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+ leaky_bucket_->Add(clock_.Now(), 2000);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(11));
+ EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+ leaky_bucket_->Add(clock_.Now(), 2000);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ leaky_bucket_->Add(clock_.Now(), 2000);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+ EXPECT_EQ(0u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_TRUE(leaky_bucket_->TimeRemaining(clock_.Now()).IsZero());
+}
+
+TEST_F(LeakyBucketTest, ChangeDrainRate) {
+ QuicBandwidth draining_rate = QuicBandwidth::FromBytesPerSecond(200000);
+ leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+ leaky_bucket_->Add(clock_.Now(), 2000);
+ EXPECT_EQ(2000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(5),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+ draining_rate = draining_rate.Scale(0.5f); // Cut drain rate in half.
+ leaky_bucket_->SetDrainingRate(clock_.Now(), draining_rate);
+ EXPECT_EQ(1000u, leaky_bucket_->BytesPending(clock_.Now()));
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(10),
+ leaky_bucket_->TimeRemaining(clock_.Now()));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/paced_sender.cc b/chromium/net/quic/congestion_control/paced_sender.cc
new file mode 100644
index 00000000000..dd116a80955
--- /dev/null
+++ b/chromium/net/quic/congestion_control/paced_sender.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/paced_sender.h"
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// To prevent too aggressive pacing we allow the following packet burst size.
+const int64 kMinPacketBurstSize = 2;
+// Max estimated time between calls to TimeUntilSend and
+// AvailableCongestionWindow.
+const int64 kMaxSchedulingDelayUs = 2000;
+
+PacedSender::PacedSender(QuicBandwidth estimate)
+ : leaky_bucket_(estimate),
+ pace_(estimate) {
+}
+
+void PacedSender::UpdateBandwidthEstimate(QuicTime now,
+ QuicBandwidth estimate) {
+ leaky_bucket_.SetDrainingRate(now, estimate);
+ pace_ = estimate;
+}
+
+void PacedSender::SentPacket(QuicTime now, QuicByteCount bytes) {
+ leaky_bucket_.Add(now, bytes);
+}
+
+QuicTime::Delta PacedSender::TimeUntilSend(QuicTime now,
+ QuicTime::Delta time_until_send) {
+ if (time_until_send.ToMicroseconds() >= kMaxSchedulingDelayUs) {
+ return time_until_send;
+ }
+ // Pace the data.
+ QuicByteCount pacing_window = pace_.ToBytesPerPeriod(
+ QuicTime::Delta::FromMicroseconds(kMaxSchedulingDelayUs));
+ QuicByteCount min_window_size = kMinPacketBurstSize * kMaxPacketSize;
+ pacing_window = std::max(pacing_window, min_window_size);
+
+ if (pacing_window > leaky_bucket_.BytesPending(now)) {
+ // We have not filled our pacing window yet.
+ return time_until_send;
+ }
+ return leaky_bucket_.TimeRemaining(now);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/paced_sender.h b/chromium/net/quic/congestion_control/paced_sender.h
new file mode 100644
index 00000000000..5f61b76f0ab
--- /dev/null
+++ b/chromium/net/quic/congestion_control/paced_sender.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+//
+// Helper class that limits the congestion window to pace the packets.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_PACED_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_PACED_SENDER_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/leaky_bucket.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE PacedSender {
+ public:
+ explicit PacedSender(QuicBandwidth bandwidth_estimate);
+
+ // The estimated bandidth from the congestion algorithm changed.
+ void UpdateBandwidthEstimate(QuicTime now, QuicBandwidth bandwidth_estimate);
+
+ // A packet of size bytes was sent.
+ void SentPacket(QuicTime now, QuicByteCount bytes);
+
+ // Return time until we can send based on the pacing.
+ QuicTime::Delta TimeUntilSend(QuicTime now, QuicTime::Delta time_until_send);
+
+ private:
+ // Helper object to track the rate data can leave the buffer for pacing.
+ LeakyBucket leaky_bucket_;
+ QuicBandwidth pace_;
+
+ DISALLOW_COPY_AND_ASSIGN(PacedSender);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_PACED_SENDER_H_
diff --git a/chromium/net/quic/congestion_control/paced_sender_test.cc b/chromium/net/quic/congestion_control/paced_sender_test.cc
new file mode 100644
index 00000000000..cc9297f8af3
--- /dev/null
+++ b/chromium/net/quic/congestion_control/paced_sender_test.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/paced_sender.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+const int kHundredKBytesPerS = 100;
+
+class PacedSenderTest : public ::testing::Test {
+ protected:
+ PacedSenderTest()
+ : zero_time_(QuicTime::Delta::Zero()),
+ paced_sender_(new PacedSender(
+ QuicBandwidth::FromKBytesPerSecond(kHundredKBytesPerS))) {
+ }
+
+ const QuicTime::Delta zero_time_;
+ MockClock clock_;
+ scoped_ptr<PacedSender> paced_sender_;
+};
+
+TEST_F(PacedSenderTest, Basic) {
+ paced_sender_->UpdateBandwidthEstimate(clock_.Now(),
+ QuicBandwidth::FromKBytesPerSecond(kHundredKBytesPerS * 10));
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_EQ(static_cast<int64>(kMaxPacketSize * 2),
+ paced_sender_->TimeUntilSend(
+ clock_.Now(), zero_time_).ToMicroseconds());
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(24));
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+}
+
+TEST_F(PacedSenderTest, LowRate) {
+ paced_sender_->UpdateBandwidthEstimate(clock_.Now(),
+ QuicBandwidth::FromKBytesPerSecond(kHundredKBytesPerS));
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_EQ(static_cast<int64>(kMaxPacketSize * 20),
+ paced_sender_->TimeUntilSend(
+ clock_.Now(), zero_time_).ToMicroseconds());
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(24));
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+}
+
+TEST_F(PacedSenderTest, HighRate) {
+ QuicBandwidth bandwidth_estimate = QuicBandwidth::FromKBytesPerSecond(
+ kHundredKBytesPerS * 100);
+ paced_sender_->UpdateBandwidthEstimate(clock_.Now(), bandwidth_estimate);
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+ for (int i = 0; i < 16; ++i) {
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(
+ clock_.Now(), zero_time_).IsZero());
+ }
+ paced_sender_->SentPacket(clock_.Now(), kMaxPacketSize);
+ EXPECT_EQ(2040, paced_sender_->TimeUntilSend(
+ clock_.Now(), zero_time_).ToMicroseconds());
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(20400));
+ EXPECT_TRUE(paced_sender_->TimeUntilSend(clock_.Now(), zero_time_).IsZero());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/quic_congestion_control_test.cc b/chromium/net/quic/congestion_control/quic_congestion_control_test.cc
new file mode 100644
index 00000000000..0051acab1f7
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_congestion_control_test.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2013 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.
+
+// Test of the full congestion control chain.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/quic_congestion_manager.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::max;
+
+namespace net {
+namespace test {
+
+class QuicCongestionManagerPeer : public QuicCongestionManager {
+ public:
+ explicit QuicCongestionManagerPeer(const QuicClock* clock,
+ CongestionFeedbackType congestion_type)
+ : QuicCongestionManager(clock, congestion_type) {
+ }
+ using QuicCongestionManager::BandwidthEstimate;
+};
+
+class QuicCongestionControlTest : public ::testing::Test {
+ protected:
+ QuicCongestionControlTest()
+ : start_(clock_.ApproximateNow()) {
+ }
+
+ void SetUpCongestionType(CongestionFeedbackType congestion_type) {
+ manager_.reset(new QuicCongestionManagerPeer(&clock_, congestion_type));
+ }
+
+ MockClock clock_;
+ QuicTime start_;
+ scoped_ptr<QuicCongestionManagerPeer> manager_;
+};
+
+TEST_F(QuicCongestionControlTest, FixedRateSenderAPI) {
+ SetUpCongestionType(kFixRate);
+ QuicCongestionFeedbackFrame congestion_feedback;
+ congestion_feedback.type = kFixRate;
+ congestion_feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(30);
+ manager_->OnIncomingQuicCongestionFeedbackFrame(congestion_feedback,
+ clock_.Now());
+ EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(1, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION);
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(40),
+ manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(35));
+ EXPECT_EQ(QuicTime::Delta::Infinite(),
+ manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(QuicTime::Delta::Infinite(),
+ manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE));
+}
+
+TEST_F(QuicCongestionControlTest, FixedRatePacing) {
+ SetUpCongestionType(kFixRate);
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = 0;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kFixRate;
+ feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(100);
+ manager_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now());
+
+ QuicTime acc_advance_time(QuicTime::Zero());
+ for (QuicPacketSequenceNumber i = 1; i <= 100; ++i) {
+ EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(i, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION);
+ QuicTime::Delta advance_time = manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ clock_.AdvanceTime(advance_time);
+ acc_advance_time = acc_advance_time.Add(advance_time);
+ // Ack the packet we sent.
+ ack.received_info.largest_observed = max(
+ i, ack.received_info.largest_observed);
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ }
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1200),
+ acc_advance_time.Subtract(start_));
+}
+
+TEST_F(QuicCongestionControlTest, Pacing) {
+ SetUpCongestionType(kFixRate);
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = 0;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kFixRate;
+ // Test a high bitrate (8Mbit/s) to trigger pacing.
+ feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(1000);
+ manager_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now());
+
+ QuicTime acc_advance_time(QuicTime::Zero());
+ for (QuicPacketSequenceNumber i = 1; i <= 100;) {
+ EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION);
+ EXPECT_TRUE(manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(i++, clock_.Now(), kMaxPacketSize, NOT_RETRANSMISSION);
+ QuicTime::Delta advance_time = manager_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ clock_.AdvanceTime(advance_time);
+ acc_advance_time = acc_advance_time.Add(advance_time);
+ // Ack the packets we sent.
+ ack.received_info.largest_observed = max(
+ i - 2, ack.received_info.largest_observed);
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ ack.received_info.largest_observed = max(
+ i - 1, ack.received_info.largest_observed);
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ }
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(120),
+ acc_advance_time.Subtract(start_));
+}
+
+// TODO(pwestin): add TCP tests.
+
+// TODO(pwestin): add InterArrival tests.
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/quic_congestion_manager.cc b/chromium/net/quic/congestion_control/quic_congestion_manager.cc
new file mode 100644
index 00000000000..ba6bab83ba3
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_congestion_manager.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/quic_congestion_manager.h"
+
+#include <algorithm>
+#include <map>
+
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+
+namespace {
+static const int kBitrateSmoothingPeriodMs = 1000;
+static const int kHistoryPeriodMs = 5000;
+
+static const int kDefaultRetransmissionTimeMs = 500;
+// TCP RFC calls for 1 second RTO however Linux differs from this default and
+// define the minimum RTO to 200ms, we will use the same until we have data to
+// support a higher or lower value.
+static const int kMinRetransmissionTimeMs = 200;
+static const int kMaxRetransmissionTimeMs = 60000;
+static const size_t kMaxRetransmissions = 10;
+static const size_t kTailDropWindowSize = 5;
+static const size_t kTailDropMaxRetransmissions = 4;
+
+COMPILE_ASSERT(kHistoryPeriodMs >= kBitrateSmoothingPeriodMs,
+ history_must_be_longer_or_equal_to_the_smoothing_period);
+} // namespace
+
+using std::map;
+using std::min;
+
+namespace net {
+
+QuicCongestionManager::QuicCongestionManager(
+ const QuicClock* clock,
+ CongestionFeedbackType type)
+ : clock_(clock),
+ receive_algorithm_(ReceiveAlgorithmInterface::Create(clock, type)),
+ send_algorithm_(SendAlgorithmInterface::Create(clock, type)),
+ largest_missing_(0),
+ current_rtt_(QuicTime::Delta::Infinite()) {
+}
+
+QuicCongestionManager::~QuicCongestionManager() {
+ STLDeleteValues(&packet_history_map_);
+}
+
+void QuicCongestionManager::SentPacket(QuicPacketSequenceNumber sequence_number,
+ QuicTime sent_time,
+ QuicByteCount bytes,
+ Retransmission retransmission) {
+ DCHECK(!ContainsKey(pending_packets_, sequence_number));
+ send_algorithm_->SentPacket(sent_time, sequence_number, bytes,
+ retransmission);
+
+ packet_history_map_[sequence_number] =
+ new class SendAlgorithmInterface::SentPacket(bytes, sent_time);
+ pending_packets_[sequence_number] = bytes;
+ CleanupPacketHistory();
+}
+
+// Called when a packet is timed out.
+void QuicCongestionManager::AbandoningPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ PendingPacketsMap::iterator it = pending_packets_.find(sequence_number);
+ if (it != pending_packets_.end()) {
+ // Shouldn't this report loss as well? (decrease cgst window).
+ send_algorithm_->AbandoningPacket(sequence_number, it->second);
+ pending_packets_.erase(it);
+ }
+}
+
+void QuicCongestionManager::OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame, QuicTime feedback_receive_time) {
+ send_algorithm_->OnIncomingQuicCongestionFeedbackFrame(
+ frame, feedback_receive_time, packet_history_map_);
+}
+
+void QuicCongestionManager::OnIncomingAckFrame(const QuicAckFrame& frame,
+ QuicTime ack_receive_time) {
+ // We calculate the RTT based on the highest ACKed sequence number, the lower
+ // sequence numbers will include the ACK aggregation delay.
+ SendAlgorithmInterface::SentPacketsMap::iterator history_it =
+ packet_history_map_.find(frame.received_info.largest_observed);
+ // TODO(satyamshekhar): largest_observed might be missing.
+ if (history_it != packet_history_map_.end() &&
+ !frame.received_info.delta_time_largest_observed.IsInfinite()) {
+ QuicTime::Delta send_delta = ack_receive_time.Subtract(
+ history_it->second->SendTimestamp());
+ if (send_delta > frame.received_info.delta_time_largest_observed) {
+ current_rtt_ = send_delta.Subtract(
+ frame.received_info.delta_time_largest_observed);
+ }
+ }
+ // We want to.
+ // * Get all packets lower(including) than largest_observed
+ // from pending_packets_.
+ // * Remove all missing packets.
+ // * Send each ACK in the list to send_algorithm_.
+ PendingPacketsMap::iterator it, it_upper;
+ it = pending_packets_.begin();
+ it_upper = pending_packets_.upper_bound(frame.received_info.largest_observed);
+
+ bool new_packet_loss_reported = false;
+ while (it != it_upper) {
+ QuicPacketSequenceNumber sequence_number = it->first;
+ if (!IsAwaitingPacket(frame.received_info, sequence_number)) {
+ // Not missing, hence implicitly acked.
+ send_algorithm_->OnIncomingAck(sequence_number, it->second, current_rtt_);
+ pending_packets_.erase(it++); // Must be incremented post to work.
+ } else {
+ if (sequence_number > largest_missing_) {
+ // We have a new loss reported.
+ new_packet_loss_reported = true;
+ largest_missing_ = sequence_number;
+ }
+ ++it;
+ }
+ }
+ if (new_packet_loss_reported) {
+ send_algorithm_->OnIncomingLoss(ack_receive_time);
+ }
+}
+
+QuicTime::Delta QuicCongestionManager::TimeUntilSend(
+ QuicTime now,
+ Retransmission retransmission,
+ HasRetransmittableData retransmittable,
+ IsHandshake handshake) {
+ return send_algorithm_->TimeUntilSend(now, retransmission, retransmittable,
+ handshake);
+}
+
+bool QuicCongestionManager::GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) {
+ return receive_algorithm_->GenerateCongestionFeedback(feedback);
+}
+
+void QuicCongestionManager::RecordIncomingPacket(
+ QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) {
+ receive_algorithm_->RecordIncomingPacket(bytes, sequence_number, timestamp,
+ revived);
+}
+
+const QuicTime::Delta QuicCongestionManager::rtt() {
+ return current_rtt_;
+}
+
+const QuicTime::Delta QuicCongestionManager::DefaultRetransmissionTime() {
+ return QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+}
+
+const QuicTime::Delta QuicCongestionManager::GetRetransmissionDelay(
+ size_t unacked_packets_count,
+ size_t number_retransmissions) {
+ QuicTime::Delta retransmission_delay = send_algorithm_->RetransmissionDelay();
+ if (retransmission_delay.IsZero()) {
+ // We are in the initial state, use default timeout values.
+ if (unacked_packets_count <= kTailDropWindowSize) {
+ if (number_retransmissions <= kTailDropMaxRetransmissions) {
+ return QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+ }
+ number_retransmissions -= kTailDropMaxRetransmissions;
+ }
+ retransmission_delay =
+ QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+ }
+ // Calcluate exponential back off.
+ retransmission_delay = QuicTime::Delta::FromMilliseconds(
+ retransmission_delay.ToMilliseconds() * static_cast<size_t>(
+ (1 << min<size_t>(number_retransmissions, kMaxRetransmissions))));
+
+ // TODO(rch): This code should move to |send_algorithm_|.
+ if (retransmission_delay.ToMilliseconds() < kMinRetransmissionTimeMs) {
+ return QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs);
+ }
+ if (retransmission_delay.ToMilliseconds() > kMaxRetransmissionTimeMs) {
+ return QuicTime::Delta::FromMilliseconds(kMaxRetransmissionTimeMs);
+ }
+ return retransmission_delay;
+}
+
+const QuicTime::Delta QuicCongestionManager::SmoothedRtt() {
+ return send_algorithm_->SmoothedRtt();
+}
+
+QuicBandwidth QuicCongestionManager::BandwidthEstimate() {
+ return send_algorithm_->BandwidthEstimate();
+}
+
+void QuicCongestionManager::CleanupPacketHistory() {
+ const QuicTime::Delta kHistoryPeriod =
+ QuicTime::Delta::FromMilliseconds(kHistoryPeriodMs);
+ QuicTime now = clock_->ApproximateNow();
+
+ SendAlgorithmInterface::SentPacketsMap::iterator history_it =
+ packet_history_map_.begin();
+ for (; history_it != packet_history_map_.end(); ++history_it) {
+ if (now.Subtract(history_it->second->SendTimestamp()) <= kHistoryPeriod) {
+ return;
+ }
+ delete history_it->second;
+ packet_history_map_.erase(history_it);
+ history_it = packet_history_map_.begin();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/quic_congestion_manager.h b/chromium/net/quic/congestion_control/quic_congestion_manager.h
new file mode 100644
index 00000000000..8bfa3c1d425
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_congestion_manager.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2013 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.
+
+// Copyright (c) 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.
+//
+// This is the interface from the QuicConnection into the QUIC
+// congestion control code. It wraps the SendAlgorithmInterface and
+// ReceiveAlgorithmInterface and provides a single interface
+// for consumers.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_QUIC_CONGESTION_MANAGER_H_
+#define NET_QUIC_CONGESTION_CONTROL_QUIC_CONGESTION_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicConnectionPeer;
+class QuicCongestionManagerPeer;
+} // namespace test
+
+class QuicClock;
+class ReceiveAlgorithmInterface;
+
+class NET_EXPORT_PRIVATE QuicCongestionManager {
+ public:
+ QuicCongestionManager(const QuicClock* clock,
+ CongestionFeedbackType congestion_type);
+ virtual ~QuicCongestionManager();
+
+ // Called when we have received an ack frame from peer.
+ virtual void OnIncomingAckFrame(const QuicAckFrame& frame,
+ QuicTime ack_receive_time);
+
+ // Called when a congestion feedback frame is received from peer.
+ virtual void OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame,
+ QuicTime feedback_receive_time);
+
+ // Called when we have sent bytes to the peer. This informs the manager both
+ // the number of bytes sent and if they were retransmitted.
+ virtual void SentPacket(QuicPacketSequenceNumber sequence_number,
+ QuicTime sent_time,
+ QuicByteCount bytes,
+ Retransmission retransmission);
+
+ // Called when a packet is timed out.
+ virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number);
+
+ // Calculate the time until we can send the next packet to the wire.
+ // Note 1: When kUnknownWaitTime is returned, there is no need to poll
+ // TimeUntilSend again until we receive an OnIncomingAckFrame event.
+ // Note 2: Send algorithms may or may not use |retransmit| in their
+ // calculations.
+ virtual QuicTime::Delta TimeUntilSend(QuicTime now,
+ Retransmission retransmission,
+ HasRetransmittableData retransmittable,
+ IsHandshake handshake);
+
+ // Should be called before sending an ACK packet, to decide if we need
+ // to attach a QuicCongestionFeedbackFrame block.
+ // Returns false if no QuicCongestionFeedbackFrame block is needed.
+ // Otherwise fills in feedback and returns true.
+ virtual bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback);
+
+ // Should be called for each incoming packet.
+ // bytes: the packet size in bytes including Quic Headers.
+ // sequence_number: the unique sequence number from the QUIC packet header.
+ // timestamp: the arrival time of the packet.
+ // revived: true if the packet was lost and then recovered with help of a
+ // FEC packet.
+ virtual void RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived);
+
+ const QuicTime::Delta DefaultRetransmissionTime();
+
+ const QuicTime::Delta GetRetransmissionDelay(
+ size_t unacked_packets_count,
+ size_t number_retransmissions);
+
+ // Returns the estimated smoothed RTT calculated by the congestion algorithm.
+ const QuicTime::Delta SmoothedRtt();
+
+ // Returns the estimated bandwidth calculated by the congestion algorithm.
+ QuicBandwidth BandwidthEstimate();
+
+ private:
+ friend class test::QuicConnectionPeer;
+ friend class test::QuicCongestionManagerPeer;
+ typedef std::map<QuicPacketSequenceNumber, size_t> PendingPacketsMap;
+
+ // Get the current(last) rtt. Infinite is returned if invalid.
+ const QuicTime::Delta rtt();
+
+ void CleanupPacketHistory();
+
+ const QuicClock* clock_;
+ scoped_ptr<ReceiveAlgorithmInterface> receive_algorithm_;
+ scoped_ptr<SendAlgorithmInterface> send_algorithm_;
+ SendAlgorithmInterface::SentPacketsMap packet_history_map_;
+ PendingPacketsMap pending_packets_;
+ QuicPacketSequenceNumber largest_missing_;
+ QuicTime::Delta current_rtt_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicCongestionManager);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_QUIC_CONGESTION_MANAGER_H_
diff --git a/chromium/net/quic/congestion_control/quic_congestion_manager_test.cc b/chromium/net/quic/congestion_control/quic_congestion_manager_test.cc
new file mode 100644
index 00000000000..1cf44a2bdf8
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_congestion_manager_test.cc
@@ -0,0 +1,236 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/inter_arrival_sender.h"
+#include "net/quic/congestion_control/quic_congestion_manager.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::StrictMock;
+
+namespace net {
+namespace test {
+
+class QuicCongestionManagerPeer : public QuicCongestionManager {
+ public:
+ explicit QuicCongestionManagerPeer(const QuicClock* clock,
+ CongestionFeedbackType congestion_type)
+ : QuicCongestionManager(clock, congestion_type) {
+ }
+ void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+ this->send_algorithm_.reset(send_algorithm);
+ }
+
+ using QuicCongestionManager::rtt;
+ const SendAlgorithmInterface::SentPacketsMap& packet_history_map() {
+ return packet_history_map_;
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicCongestionManagerPeer);
+};
+
+class QuicCongestionManagerTest : public ::testing::Test {
+ protected:
+ void SetUpCongestionType(CongestionFeedbackType congestion_type) {
+ manager_.reset(new QuicCongestionManagerPeer(&clock_, congestion_type));
+ }
+
+ static const HasRetransmittableData kIgnored = HAS_RETRANSMITTABLE_DATA;
+
+ MockClock clock_;
+ scoped_ptr<QuicCongestionManagerPeer> manager_;
+};
+
+TEST_F(QuicCongestionManagerTest, Bandwidth) {
+ SetUpCongestionType(kFixRate);
+ QuicAckFrame ack;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kFixRate;
+ feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(100);
+ manager_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now());
+
+ for (int i = 1; i <= 100; ++i) {
+ QuicTime::Delta advance_time = manager_->TimeUntilSend(
+ clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE);
+ clock_.AdvanceTime(advance_time);
+ EXPECT_TRUE(manager_->TimeUntilSend(
+ clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(i, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ // Ack the packet we sent.
+ ack.received_info.largest_observed = i;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ }
+ EXPECT_EQ(100, manager_->BandwidthEstimate().ToKBytesPerSecond());
+ EXPECT_NEAR(100,
+ InterArrivalSender::CalculateSentBandwidth(
+ manager_->packet_history_map(),
+ clock_.Now()).ToKBytesPerSecond(),
+ 4);
+}
+
+TEST_F(QuicCongestionManagerTest, BandwidthWith1SecondGap) {
+ SetUpCongestionType(kFixRate);
+ QuicAckFrame ack;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kFixRate;
+ feedback.fix_rate.bitrate = QuicBandwidth::FromKBytesPerSecond(100);
+ manager_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now());
+
+ for (QuicPacketSequenceNumber sequence_number = 1; sequence_number <= 100;
+ ++sequence_number) {
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+ EXPECT_TRUE(manager_->TimeUntilSend(
+ clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(
+ sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ // Ack the packet we sent.
+ ack.received_info.largest_observed = sequence_number;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ }
+ EXPECT_EQ(100000, manager_->BandwidthEstimate().ToBytesPerSecond());
+ EXPECT_NEAR(100000,
+ InterArrivalSender::CalculateSentBandwidth(
+ manager_->packet_history_map(),
+ clock_.Now()).ToBytesPerSecond(),
+ 2000);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(500));
+ EXPECT_NEAR(50000,
+ InterArrivalSender::CalculateSentBandwidth(
+ manager_->packet_history_map(),
+ clock_.Now()).ToBytesPerSecond(),
+ 1000);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(501));
+ EXPECT_NEAR(100000, manager_->BandwidthEstimate().ToBytesPerSecond(), 2000);
+ EXPECT_TRUE(InterArrivalSender::CalculateSentBandwidth(
+ manager_->packet_history_map(),
+ clock_.Now()).IsZero());
+ for (int i = 1; i <= 150; ++i) {
+ EXPECT_TRUE(manager_->TimeUntilSend(
+ clock_.Now(), NOT_RETRANSMISSION, kIgnored, NOT_HANDSHAKE).IsZero());
+ manager_->SentPacket(i + 100, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+ // Ack the packet we sent.
+ ack.received_info.largest_observed = i + 100;
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ }
+ EXPECT_EQ(100, manager_->BandwidthEstimate().ToKBytesPerSecond());
+ EXPECT_NEAR(100,
+ InterArrivalSender::CalculateSentBandwidth(
+ manager_->packet_history_map(),
+ clock_.Now()).ToKBytesPerSecond(),
+ 2);
+}
+
+TEST_F(QuicCongestionManagerTest, Rtt) {
+ SetUpCongestionType(kFixRate);
+
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ manager_->SetSendAlgorithm(send_algorithm);
+
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(15);
+
+ EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm,
+ OnIncomingAck(sequence_number, _, expected_rtt)).Times(1);
+
+ manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
+
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = sequence_number;
+ ack.received_info.delta_time_largest_observed =
+ QuicTime::Delta::FromMilliseconds(5);
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ EXPECT_EQ(manager_->rtt(), expected_rtt);
+}
+
+TEST_F(QuicCongestionManagerTest, RttWithInvalidDelta) {
+ // Expect that the RTT is infinite since the delta_time_largest_observed is
+ // larger than the local time elapsed aka invalid.
+ SetUpCongestionType(kFixRate);
+
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ manager_->SetSendAlgorithm(send_algorithm);
+
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta expected_rtt = QuicTime::Delta::Infinite();
+
+ EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm,
+ OnIncomingAck(sequence_number, _, expected_rtt)).Times(1);
+
+ manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = sequence_number;
+ ack.received_info.delta_time_largest_observed =
+ QuicTime::Delta::FromMilliseconds(11);
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ EXPECT_EQ(manager_->rtt(), expected_rtt);
+}
+
+TEST_F(QuicCongestionManagerTest, RttInfiniteDelta) {
+ // Expect that the RTT is infinite since the delta_time_largest_observed is
+ // infinite aka invalid.
+ SetUpCongestionType(kFixRate);
+
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ manager_->SetSendAlgorithm(send_algorithm);
+
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta expected_rtt = QuicTime::Delta::Infinite();
+
+ EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm,
+ OnIncomingAck(sequence_number, _, expected_rtt)).Times(1);
+
+ manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10));
+
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = sequence_number;
+ ack.received_info.delta_time_largest_observed = QuicTime::Delta::Infinite();
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ EXPECT_EQ(manager_->rtt(), expected_rtt);
+}
+
+TEST_F(QuicCongestionManagerTest, RttZeroDelta) {
+ // Expect that the RTT is the time between send and receive since the
+ // delta_time_largest_observed is zero.
+ SetUpCongestionType(kFixRate);
+
+ MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
+ manager_->SetSendAlgorithm(send_algorithm);
+
+ QuicPacketSequenceNumber sequence_number = 1;
+ QuicTime::Delta expected_rtt = QuicTime::Delta::FromMilliseconds(10);
+
+ EXPECT_CALL(*send_algorithm, SentPacket(_, _, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm,
+ OnIncomingAck(sequence_number, _, expected_rtt)).Times(1);
+
+ manager_->SentPacket(sequence_number, clock_.Now(), 1000, NOT_RETRANSMISSION);
+ clock_.AdvanceTime(expected_rtt);
+
+ QuicAckFrame ack;
+ ack.received_info.largest_observed = sequence_number;
+ ack.received_info.delta_time_largest_observed = QuicTime::Delta::Zero();
+ manager_->OnIncomingAckFrame(ack, clock_.Now());
+ EXPECT_EQ(manager_->rtt(), expected_rtt);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/quic_max_sized_map.h b/chromium/net/quic/congestion_control/quic_max_sized_map.h
new file mode 100644
index 00000000000..a4ed7769d61
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_max_sized_map.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2013 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.
+
+// Simple max sized map. Automatically deletes the oldest element when the
+// max limit is reached.
+// Note: the ConstIterator will NOT be valid after an Insert or RemoveAll.
+#ifndef NET_QUIC_CONGESTION_CONTROL_QUIC_MAX_SIZED_MAP_H_
+#define NET_QUIC_CONGESTION_CONTROL_QUIC_MAX_SIZED_MAP_H_
+
+#include <stdlib.h>
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+
+namespace net {
+
+template <class Key, class Value>
+class QuicMaxSizedMap {
+ public:
+ typedef typename std::multimap<Key, Value>::const_iterator ConstIterator;
+
+ explicit QuicMaxSizedMap(size_t max_numer_of_items)
+ : max_numer_of_items_(max_numer_of_items) {
+ }
+
+ size_t MaxSize() const {
+ return max_numer_of_items_;
+ }
+
+ size_t Size() const {
+ return table_.size();
+ }
+
+ void Insert(const Key& k, const Value& value) {
+ if (Size() == MaxSize()) {
+ ListIterator list_it = insert_order_.begin();
+ table_.erase(*list_it);
+ insert_order_.pop_front();
+ }
+ TableIterator it = table_.insert(std::pair<Key, Value>(k, value));
+ insert_order_.push_back(it);
+ }
+
+ void RemoveAll() {
+ table_.clear();
+ insert_order_.clear();
+ }
+
+ // STL style const_iterator support.
+ ConstIterator Find(const Key& k) const {
+ return table_.find(k);
+ }
+
+ ConstIterator Begin() const {
+ return ConstIterator(table_.begin());
+ }
+
+ ConstIterator End() const {
+ return ConstIterator(table_.end());
+ }
+
+ private:
+ typedef typename std::multimap<Key, Value>::iterator TableIterator;
+ typedef typename std::list<TableIterator>::iterator ListIterator;
+
+ const size_t max_numer_of_items_;
+ std::multimap<Key, Value> table_;
+ std::list<TableIterator> insert_order_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicMaxSizedMap);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_QUIC_MAX_SIZED_MAP_H_
diff --git a/chromium/net/quic/congestion_control/quic_max_sized_map_test.cc b/chromium/net/quic/congestion_control/quic_max_sized_map_test.cc
new file mode 100644
index 00000000000..89c05cccbaf
--- /dev/null
+++ b/chromium/net/quic/congestion_control/quic_max_sized_map_test.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "net/quic/congestion_control/quic_max_sized_map.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicMaxSizedMapTest : public ::testing::Test {
+};
+
+TEST_F(QuicMaxSizedMapTest, Basic) {
+ QuicMaxSizedMap<int, int> test_map(100);
+ EXPECT_EQ(100u, test_map.MaxSize());
+ EXPECT_EQ(0u, test_map.Size());
+ test_map.Insert(1, 2);
+ test_map.Insert(1, 3);
+ EXPECT_EQ(100u, test_map.MaxSize());
+ EXPECT_EQ(2u, test_map.Size());
+ test_map.RemoveAll();
+ EXPECT_EQ(100u, test_map.MaxSize());
+ EXPECT_EQ(0u, test_map.Size());
+}
+
+TEST_F(QuicMaxSizedMapTest, Find) {
+ QuicMaxSizedMap<int, int> test_map(100);
+ test_map.Insert(1, 2);
+ test_map.Insert(1, 3);
+ test_map.Insert(2, 4);
+ test_map.Insert(3, 5);
+ QuicMaxSizedMap<int, int>::ConstIterator it = test_map.Find(2);
+ EXPECT_TRUE(it != test_map.End());
+ EXPECT_EQ(4, it->second);
+ it = test_map.Find(1);
+ EXPECT_TRUE(it != test_map.End());
+ EXPECT_EQ(2, it->second);
+ ++it;
+ EXPECT_TRUE(it != test_map.End());
+ EXPECT_EQ(3, it->second);
+}
+
+TEST_F(QuicMaxSizedMapTest, Sort) {
+ QuicMaxSizedMap<int, int> test_map(100);
+ test_map.Insert(9, 9);
+ test_map.Insert(8, 8);
+ test_map.Insert(7, 7);
+ test_map.Insert(6, 6);
+ test_map.Insert(2, 2);
+ test_map.Insert(4, 4);
+ test_map.Insert(5, 5);
+ test_map.Insert(3, 3);
+ test_map.Insert(0, 0);
+ test_map.Insert(1, 1);
+ QuicMaxSizedMap<int, int>::ConstIterator it = test_map.Begin();
+ for (int i = 0; i < 10; ++i, ++it) {
+ EXPECT_TRUE(it != test_map.End());
+ EXPECT_EQ(i, it->first);
+ EXPECT_EQ(i, it->second);
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/receive_algorithm_interface.cc b/chromium/net/quic/congestion_control/receive_algorithm_interface.cc
new file mode 100644
index 00000000000..e8dacf0725b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/receive_algorithm_interface.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+
+#include "net/quic/congestion_control/fix_rate_receiver.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+
+namespace net {
+
+// Factory for receive side congestion control algorithm.
+ReceiveAlgorithmInterface* ReceiveAlgorithmInterface::Create(
+ const QuicClock* clock,
+ CongestionFeedbackType type) {
+ switch (type) {
+ case kTCP:
+ return new TcpReceiver();
+ case kInterArrival:
+ break; // TODO(pwestin) Implement.
+ case kFixRate:
+ return new FixRateReceiver();
+ }
+ return NULL;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/receive_algorithm_interface.h b/chromium/net/quic/congestion_control/receive_algorithm_interface.h
new file mode 100644
index 00000000000..e2bae4bb793
--- /dev/null
+++ b/chromium/net/quic/congestion_control/receive_algorithm_interface.h
@@ -0,0 +1,44 @@
+// Copyright (c) 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.
+//
+// The pure virtual class for receive side congestion algorithm.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
+#define NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE ReceiveAlgorithmInterface {
+ public:
+ static ReceiveAlgorithmInterface* Create(const QuicClock* clock,
+ CongestionFeedbackType type);
+
+ virtual ~ReceiveAlgorithmInterface() {}
+
+ // Returns false if no QuicCongestionFeedbackFrame block is needed.
+ // Otherwise fills in feedback and return true.
+ virtual bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) = 0;
+
+ // Should be called for each incoming packet.
+ // bytes: is the packet size in bytes including IP headers.
+ // sequence_number: is the unique sequence number from the QUIC packet header.
+ // timestamp: is the sent timestamp from the QUIC packet header.
+ // revived: is set if the packet is lost and then recovered with help of FEC
+ // (Forward Error Correction) packet(s).
+ virtual void RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_RECEIVE_ALGORITHM_INTERFACE_H_
diff --git a/chromium/net/quic/congestion_control/send_algorithm_interface.cc b/chromium/net/quic/congestion_control/send_algorithm_interface.cc
new file mode 100644
index 00000000000..ce24a00b1ac
--- /dev/null
+++ b/chromium/net/quic/congestion_control/send_algorithm_interface.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+
+#include "net/quic/congestion_control/cubic.h"
+#include "net/quic/congestion_control/fix_rate_sender.h"
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+
+namespace net {
+
+const bool kUseReno = false;
+// TODO(ianswett): Increase the max congestion window once the RTO logic is
+// improved, particularly in cases when RTT is larger than the RTO. b/10075719
+// Maximum number of outstanding packets for tcp.
+const QuicTcpCongestionWindow kMaxTcpCongestionWindow = 50;
+
+// Factory for send side congestion control algorithm.
+SendAlgorithmInterface* SendAlgorithmInterface::Create(
+ const QuicClock* clock,
+ CongestionFeedbackType type) {
+ switch (type) {
+ case kTCP:
+ return new TcpCubicSender(clock, kUseReno, kMaxTcpCongestionWindow);
+ case kInterArrival:
+ break; // TODO(pwestin) Implement.
+ case kFixRate:
+ return new FixRateSender(clock);
+ }
+ return NULL;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/send_algorithm_interface.h b/chromium/net/quic/congestion_control/send_algorithm_interface.h
new file mode 100644
index 00000000000..8896b2b06df
--- /dev/null
+++ b/chromium/net/quic/congestion_control/send_algorithm_interface.h
@@ -0,0 +1,90 @@
+// Copyright (c) 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.
+//
+// The pure virtual class for send side congestion control algorithm.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
+#define NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE SendAlgorithmInterface {
+ public:
+ class SentPacket {
+ public:
+ SentPacket(QuicByteCount bytes, QuicTime timestamp)
+ : bytes_sent_(bytes),
+ send_timestamp_(timestamp) {
+ }
+ QuicByteCount BytesSent() { return bytes_sent_; }
+ QuicTime& SendTimestamp() { return send_timestamp_; }
+
+ private:
+ QuicByteCount bytes_sent_;
+ QuicTime send_timestamp_;
+ };
+
+ typedef std::map<QuicPacketSequenceNumber, SentPacket*> SentPacketsMap;
+
+ static SendAlgorithmInterface* Create(const QuicClock* clock,
+ CongestionFeedbackType type);
+
+ virtual ~SendAlgorithmInterface() {}
+
+ // Called when we receive congestion feedback from remote peer.
+ virtual void OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& sent_packets) = 0;
+
+ // Called for each received ACK, with sequence number from remote peer.
+ virtual void OnIncomingAck(QuicPacketSequenceNumber acked_sequence_number,
+ QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) = 0;
+
+ virtual void OnIncomingLoss(QuicTime ack_receive_time) = 0;
+
+ // Inform that we sent x bytes to the wire, and if that was a retransmission.
+ // Note: this function must be called for every packet sent to the wire.
+ virtual void SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes,
+ Retransmission is_retransmission) = 0;
+
+ // Called when a packet is timed out.
+ virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes) = 0;
+
+ // Calculate the time until we can send the next packet.
+ virtual QuicTime::Delta TimeUntilSend(
+ QuicTime now,
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) = 0;
+
+ // What's the current estimated bandwidth in bytes per second.
+ // Returns 0 when it does not have an estimate.
+ virtual QuicBandwidth BandwidthEstimate() = 0;
+
+ // TODO(satyamshekhar): Monitor MinRtt.
+ virtual QuicTime::Delta SmoothedRtt() = 0;
+
+ // Get the send algorithm specific retransmission delay, called RTO in TCP,
+ // Note 1: the caller is responsible for sanity checking this value.
+ // Note 2: this will return zero if we don't have enough data for an estimate.
+ virtual QuicTime::Delta RetransmissionDelay() = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_SEND_ALGORITHM_INTERFACE_H_
diff --git a/chromium/net/quic/congestion_control/tcp_cubic_sender.cc b/chromium/net/quic/congestion_control/tcp_cubic_sender.cc
new file mode 100644
index 00000000000..1e98c12cbf3
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_cubic_sender.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 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.
+
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+
+namespace net {
+
+namespace {
+// Constants based on TCP defaults.
+const int64 kHybridStartLowWindow = 16;
+const QuicByteCount kMaxSegmentSize = kMaxPacketSize;
+const QuicByteCount kDefaultReceiveWindow = 64000;
+const int64 kInitialCongestionWindow = 10;
+const int kMaxBurstLength = 3;
+const int kInitialRttMs = 60; // At a typical RTT 60 ms.
+const float kAlpha = 0.125f;
+const float kOneMinusAlpha = (1 - kAlpha);
+const float kBeta = 0.25f;
+const float kOneMinusBeta = (1 - kBeta);
+}; // namespace
+
+TcpCubicSender::TcpCubicSender(
+ const QuicClock* clock,
+ bool reno,
+ QuicTcpCongestionWindow max_tcp_congestion_window)
+ : hybrid_slow_start_(clock),
+ cubic_(clock),
+ reno_(reno),
+ congestion_window_count_(0),
+ receiver_congestion_window_(kDefaultReceiveWindow),
+ last_received_accumulated_number_of_lost_packets_(0),
+ bytes_in_flight_(0),
+ update_end_sequence_number_(true),
+ end_sequence_number_(0),
+ congestion_window_(kInitialCongestionWindow),
+ slowstart_threshold_(max_tcp_congestion_window),
+ max_tcp_congestion_window_(max_tcp_congestion_window),
+ delay_min_(QuicTime::Delta::Zero()),
+ smoothed_rtt_(QuicTime::Delta::Zero()),
+ mean_deviation_(QuicTime::Delta::Zero()) {
+}
+
+TcpCubicSender::~TcpCubicSender() {
+}
+
+void TcpCubicSender::OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& /*sent_packets*/) {
+ if (last_received_accumulated_number_of_lost_packets_ !=
+ feedback.tcp.accumulated_number_of_lost_packets) {
+ int recovered_lost_packets =
+ last_received_accumulated_number_of_lost_packets_ -
+ feedback.tcp.accumulated_number_of_lost_packets;
+ last_received_accumulated_number_of_lost_packets_ =
+ feedback.tcp.accumulated_number_of_lost_packets;
+ if (recovered_lost_packets > 0) {
+ OnIncomingLoss(feedback_receive_time);
+ }
+ }
+ receiver_congestion_window_ = feedback.tcp.receive_window;
+}
+
+void TcpCubicSender::OnIncomingAck(
+ QuicPacketSequenceNumber acked_sequence_number, QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) {
+ bytes_in_flight_ -= acked_bytes;
+ CongestionAvoidance(acked_sequence_number);
+ AckAccounting(rtt);
+ if (end_sequence_number_ == acked_sequence_number) {
+ DLOG(INFO) << "Start update end sequence number @" << acked_sequence_number;
+ update_end_sequence_number_ = true;
+ }
+}
+
+void TcpCubicSender::OnIncomingLoss(QuicTime /*ack_receive_time*/) {
+ // In a normal TCP we would need to know the lowest missing packet to detect
+ // if we receive 3 missing packets. Here we get a missing packet for which we
+ // enter TCP Fast Retransmit immediately.
+ if (reno_) {
+ congestion_window_ = congestion_window_ >> 1;
+ slowstart_threshold_ = congestion_window_;
+ } else {
+ congestion_window_ =
+ cubic_.CongestionWindowAfterPacketLoss(congestion_window_);
+ slowstart_threshold_ = congestion_window_;
+ }
+ // Sanity, make sure that we don't end up with an empty window.
+ if (congestion_window_ == 0) {
+ congestion_window_ = 1;
+ }
+ DLOG(INFO) << "Incoming loss; congestion window:" << congestion_window_;
+}
+
+void TcpCubicSender::SentPacket(QuicTime /*sent_time*/,
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes,
+ Retransmission is_retransmission) {
+ bytes_in_flight_ += bytes;
+ if (is_retransmission == NOT_RETRANSMISSION && update_end_sequence_number_) {
+ end_sequence_number_ = sequence_number;
+ if (AvailableCongestionWindow() == 0) {
+ update_end_sequence_number_ = false;
+ DLOG(INFO) << "Stop update end sequence number @" << sequence_number;
+ }
+ }
+}
+
+void TcpCubicSender::AbandoningPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes) {
+ bytes_in_flight_ -= abandoned_bytes;
+}
+
+QuicTime::Delta TcpCubicSender::TimeUntilSend(
+ QuicTime now,
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) {
+ if (is_retransmission == IS_RETRANSMISSION ||
+ has_retransmittable_data == NO_RETRANSMITTABLE_DATA ||
+ handshake == IS_HANDSHAKE) {
+ // For TCP we can always send a retransmission, or an ACK immediately.
+ // We also immediately send any handshake packet (CHLO, etc.). We provide
+ // this special dispensation for handshake messages in QUIC, although the
+ // concept is not present in TCP.
+ return QuicTime::Delta::Zero();
+ }
+ if (AvailableCongestionWindow() == 0) {
+ return QuicTime::Delta::Infinite();
+ }
+ return QuicTime::Delta::Zero();
+}
+
+QuicByteCount TcpCubicSender::AvailableCongestionWindow() {
+ if (bytes_in_flight_ > CongestionWindow()) {
+ return 0;
+ }
+ return CongestionWindow() - bytes_in_flight_;
+}
+
+QuicByteCount TcpCubicSender::CongestionWindow() {
+ // What's the current congestion window in bytes.
+ return std::min(receiver_congestion_window_,
+ congestion_window_ * kMaxSegmentSize);
+}
+
+QuicBandwidth TcpCubicSender::BandwidthEstimate() {
+ // TODO(pwestin): make a long term estimate, based on RTT and loss rate? or
+ // instantaneous estimate?
+ // Throughput ~= (1/RTT)*sqrt(3/2p)
+ return QuicBandwidth::Zero();
+}
+
+QuicTime::Delta TcpCubicSender::SmoothedRtt() {
+ if (smoothed_rtt_.IsZero()) {
+ return QuicTime::Delta::FromMilliseconds(kInitialRttMs);
+ }
+ return smoothed_rtt_;
+}
+
+QuicTime::Delta TcpCubicSender::RetransmissionDelay() {
+ return QuicTime::Delta::FromMicroseconds(
+ smoothed_rtt_.ToMicroseconds() + 4 * mean_deviation_.ToMicroseconds());
+}
+
+void TcpCubicSender::Reset() {
+ delay_min_ = QuicTime::Delta::Zero();
+ hybrid_slow_start_.Restart();
+}
+
+bool TcpCubicSender::IsCwndLimited() const {
+ const QuicByteCount congestion_window_bytes = congestion_window_ *
+ kMaxSegmentSize;
+ if (bytes_in_flight_ >= congestion_window_bytes) {
+ return true;
+ }
+ const QuicByteCount tcp_max_burst = kMaxBurstLength * kMaxSegmentSize;
+ const QuicByteCount left = congestion_window_bytes - bytes_in_flight_;
+ return left <= tcp_max_burst;
+}
+
+// Called when we receive and ack. Normal TCP tracks how many packets one ack
+// represents, but quic has a separate ack for each packet.
+void TcpCubicSender::CongestionAvoidance(QuicPacketSequenceNumber ack) {
+ if (!IsCwndLimited()) {
+ // We don't update the congestion window unless we are close to using the
+ // window we have available.
+ return;
+ }
+ if (congestion_window_ < slowstart_threshold_) {
+ // Slow start.
+ if (hybrid_slow_start_.EndOfRound(ack)) {
+ hybrid_slow_start_.Reset(end_sequence_number_);
+ }
+ // congestion_window_cnt is the number of acks since last change of snd_cwnd
+ if (congestion_window_ < max_tcp_congestion_window_) {
+ // TCP slow start, exponentail growth, increase by one for each ACK.
+ congestion_window_++;
+ }
+ DLOG(INFO) << "Slow start; congestion window:" << congestion_window_;
+ } else {
+ if (congestion_window_ < max_tcp_congestion_window_) {
+ if (reno_) {
+ // Classic Reno congestion avoidance provided for testing.
+ if (congestion_window_count_ >= congestion_window_) {
+ congestion_window_++;
+ congestion_window_count_ = 0;
+ } else {
+ congestion_window_count_++;
+ }
+ DLOG(INFO) << "Reno; congestion window:" << congestion_window_;
+ } else {
+ congestion_window_ = cubic_.CongestionWindowAfterAck(congestion_window_,
+ delay_min_);
+ DLOG(INFO) << "Cubic; congestion window:" << congestion_window_;
+ }
+ }
+ }
+}
+
+// TODO(pwestin): what is the timout value?
+void TcpCubicSender::OnTimeOut() {
+ cubic_.Reset();
+ congestion_window_ = 1;
+}
+
+void TcpCubicSender::AckAccounting(QuicTime::Delta rtt) {
+ if (rtt.IsInfinite() || rtt.IsZero()) {
+ return;
+ }
+ // RTT can't be negative.
+ DCHECK_LT(0, rtt.ToMicroseconds());
+
+ // TODO(pwestin): Discard delay samples right after fast recovery,
+ // during 1 second?.
+
+ // First time call or link delay decreases.
+ if (delay_min_.IsZero() || delay_min_ > rtt) {
+ delay_min_ = rtt;
+ }
+ // First time call.
+ if (smoothed_rtt_.IsZero()) {
+ smoothed_rtt_ = rtt;
+ mean_deviation_ = QuicTime::Delta::FromMicroseconds(
+ rtt.ToMicroseconds() / 2);
+ } else {
+ mean_deviation_ = QuicTime::Delta::FromMicroseconds(
+ kOneMinusBeta * mean_deviation_.ToMicroseconds() +
+ kBeta * abs(smoothed_rtt_.ToMicroseconds() - rtt.ToMicroseconds()));
+ smoothed_rtt_ = QuicTime::Delta::FromMicroseconds(
+ kOneMinusAlpha * smoothed_rtt_.ToMicroseconds() +
+ kAlpha * rtt.ToMicroseconds());
+ DLOG(INFO) << "Cubic; mean_deviation_:" << mean_deviation_.ToMicroseconds();
+ }
+
+ // Hybrid start triggers when cwnd is larger than some threshold.
+ if (congestion_window_ <= slowstart_threshold_ &&
+ congestion_window_ >= kHybridStartLowWindow) {
+ if (!hybrid_slow_start_.started()) {
+ // Time to start the hybrid slow start.
+ hybrid_slow_start_.Reset(end_sequence_number_);
+ }
+ hybrid_slow_start_.Update(rtt, delay_min_);
+ if (hybrid_slow_start_.Exit()) {
+ slowstart_threshold_ = congestion_window_;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/tcp_cubic_sender.h b/chromium/net/quic/congestion_control/tcp_cubic_sender.h
new file mode 100644
index 00000000000..c22813a2cfd
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_cubic_sender.h
@@ -0,0 +1,118 @@
+// Copyright (c) 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.
+//
+// TCP cubic send side congestion algorithm, emulates the behaviour of
+// TCP cubic.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
+#define NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/cubic.h"
+#include "net/quic/congestion_control/hybrid_slow_start.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+namespace test {
+class TcpCubicSenderPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE TcpCubicSender : public SendAlgorithmInterface {
+ public:
+ // Reno option and max_tcp_congestion_window are provided for testing.
+ TcpCubicSender(const QuicClock* clock,
+ bool reno,
+ QuicTcpCongestionWindow max_tcp_congestion_window);
+ virtual ~TcpCubicSender();
+
+ // Start implementation of SendAlgorithmInterface.
+ virtual void OnIncomingQuicCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap& sent_packets) OVERRIDE;
+ virtual void OnIncomingAck(QuicPacketSequenceNumber acked_sequence_number,
+ QuicByteCount acked_bytes,
+ QuicTime::Delta rtt) OVERRIDE;
+ virtual void OnIncomingLoss(QuicTime ack_receive_time) OVERRIDE;
+ virtual void SentPacket(QuicTime sent_time,
+ QuicPacketSequenceNumber sequence_number,
+ QuicByteCount bytes,
+ Retransmission is_retransmission) OVERRIDE;
+ virtual void AbandoningPacket(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes) OVERRIDE;
+ virtual QuicTime::Delta TimeUntilSend(
+ QuicTime now,
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) OVERRIDE;
+ virtual QuicBandwidth BandwidthEstimate() OVERRIDE;
+ virtual QuicTime::Delta SmoothedRtt() OVERRIDE;
+ virtual QuicTime::Delta RetransmissionDelay() OVERRIDE;
+ // End implementation of SendAlgorithmInterface.
+
+ private:
+ friend class test::TcpCubicSenderPeer;
+
+ QuicByteCount AvailableCongestionWindow();
+ QuicByteCount CongestionWindow();
+ void Reset();
+ void AckAccounting(QuicTime::Delta rtt);
+ void CongestionAvoidance(QuicPacketSequenceNumber ack);
+ bool IsCwndLimited() const;
+ void OnTimeOut();
+
+ HybridSlowStart hybrid_slow_start_;
+ Cubic cubic_;
+
+ // Reno provided for testing.
+ const bool reno_;
+
+ // ACK counter for the Reno implementation.
+ int64 congestion_window_count_;
+
+ // Receiver side advertised window.
+ QuicByteCount receiver_congestion_window_;
+
+ // Receiver side advertised packet loss.
+ int last_received_accumulated_number_of_lost_packets_;
+
+ // Bytes in flight, aka bytes on the wire.
+ QuicByteCount bytes_in_flight_;
+
+ // We need to keep track of the end sequence number of each RTT "burst".
+ bool update_end_sequence_number_;
+ QuicPacketSequenceNumber end_sequence_number_;
+
+ // Congestion window in packets.
+ QuicTcpCongestionWindow congestion_window_;
+
+ // Slow start congestion window in packets.
+ QuicTcpCongestionWindow slowstart_threshold_;
+
+ // Maximum number of outstanding packets for tcp.
+ QuicTcpCongestionWindow max_tcp_congestion_window_;
+
+ // Min RTT during this session.
+ QuicTime::Delta delay_min_;
+
+ // Smoothed RTT during this session.
+ QuicTime::Delta smoothed_rtt_;
+
+ // Mean RTT deviation during this session.
+ // Approximation of standard deviation, the error is roughly 1.25 times
+ // larger than the standard deviation, for a normally distributed signal.
+ QuicTime::Delta mean_deviation_;
+
+ DISALLOW_COPY_AND_ASSIGN(TcpCubicSender);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CONGESTION_CONTROL_TCP_CUBIC_SENDER_H_
diff --git a/chromium/net/quic/congestion_control/tcp_cubic_sender_test.cc b/chromium/net/quic/congestion_control/tcp_cubic_sender_test.cc
new file mode 100644
index 00000000000..e9f88937426
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_cubic_sender_test.cc
@@ -0,0 +1,256 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/tcp_cubic_sender.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+const uint32 kDefaultWindowTCP = 10 * kMaxPacketSize;
+const QuicByteCount kNoNBytesInFlight = 0;
+
+class TcpCubicSenderPeer : public TcpCubicSender {
+ public:
+ // TODO(ianswett): Remove 10000 once b/10075719 is fixed.
+ TcpCubicSenderPeer(const QuicClock* clock, bool reno)
+ : TcpCubicSender(clock, reno, 10000) {
+ }
+ using TcpCubicSender::AvailableCongestionWindow;
+ using TcpCubicSender::CongestionWindow;
+ using TcpCubicSender::AckAccounting;
+};
+
+class TcpCubicSenderTest : public ::testing::Test {
+ protected:
+ TcpCubicSenderTest()
+ : rtt_(QuicTime::Delta::FromMilliseconds(60)),
+ one_ms_(QuicTime::Delta::FromMilliseconds(1)),
+ sender_(new TcpCubicSenderPeer(&clock_, true)),
+ receiver_(new TcpReceiver()),
+ sequence_number_(1),
+ acked_sequence_number_(0) {
+ }
+
+ void SendAvailableCongestionWindow() {
+ QuicByteCount bytes_to_send = sender_->AvailableCongestionWindow();
+ while (bytes_to_send > 0) {
+ QuicByteCount bytes_in_packet = std::min(kMaxPacketSize, bytes_to_send);
+ sender_->SentPacket(clock_.Now(), sequence_number_++, bytes_in_packet,
+ NOT_RETRANSMISSION);
+ bytes_to_send -= bytes_in_packet;
+ if (bytes_to_send > 0) {
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ }
+ }
+ }
+ // Normal is that TCP acks every other segment.
+ void AckNPackets(int n) {
+ for (int i = 0; i < n; ++i) {
+ acked_sequence_number_++;
+ sender_->OnIncomingAck(acked_sequence_number_, kMaxPacketSize, rtt_);
+ }
+ clock_.AdvanceTime(one_ms_); // 1 millisecond.
+ }
+
+ const QuicTime::Delta rtt_;
+ const QuicTime::Delta one_ms_;
+ MockClock clock_;
+ SendAlgorithmInterface::SentPacketsMap not_used_;
+ scoped_ptr<TcpCubicSenderPeer> sender_;
+ scoped_ptr<TcpReceiver> receiver_;
+ QuicPacketSequenceNumber sequence_number_;
+ QuicPacketSequenceNumber acked_sequence_number_;
+};
+
+TEST_F(TcpCubicSenderTest, SimpleSender) {
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we are at the default.
+ EXPECT_EQ(kDefaultWindowTCP,
+ sender_->AvailableCongestionWindow());
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ // Get default QuicCongestionFeedbackFrame from receiver.
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ not_used_);
+ // Make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ // And that window is un-affected.
+ EXPECT_EQ(kDefaultWindowTCP, sender_->AvailableCongestionWindow());
+
+ // A retransmitt should always retun 0.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ IS_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+}
+
+TEST_F(TcpCubicSenderTest, ExponentialSlowStart) {
+ const int kNumberOfAck = 20;
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ // Get default QuicCongestionFeedbackFrame from receiver.
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ not_used_);
+ // Make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ for (int n = 0; n < kNumberOfAck; ++n) {
+ // Send our full congestion window.
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ }
+ QuicByteCount bytes_to_send = sender_->CongestionWindow();
+ EXPECT_EQ(kDefaultWindowTCP + kMaxPacketSize * 2 * kNumberOfAck,
+ bytes_to_send);
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartAckTrain) {
+ // Make sure that we fall out of slow start when we send ACK train longer
+ // than half the RTT, in this test case 30ms, which is more than 30 calls to
+ // Ack2Packets in one round.
+ // Since we start at 10 packet first round will be 5 second round 10 etc
+ // Hence we should pass 30 at 65 = 5 + 10 + 20 + 30
+ const int kNumberOfAck = 65;
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ // Get default QuicCongestionFeedbackFrame from receiver.
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ not_used_);
+ // Make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ for (int n = 0; n < kNumberOfAck; ++n) {
+ // Send our full congestion window.
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ }
+ QuicByteCount expected_congestion_window =
+ kDefaultWindowTCP + (kMaxPacketSize * 2 * kNumberOfAck);
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+ // We should now have fallen out of slow start.
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ expected_congestion_window += kMaxPacketSize;
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+
+ // Testing Reno phase.
+ // We should need 141(65*2+1+10) ACK:ed packets before increasing window by
+ // one.
+ for (int m = 0; m < 70; ++m) {
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+ }
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ expected_congestion_window += kMaxPacketSize;
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, SlowStartPacketLoss) {
+ // Make sure that we fall out of slow start when we encounter a packet loss.
+ const int kNumberOfAck = 10;
+ QuicCongestionFeedbackFrame feedback;
+ // At startup make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+ // Get default QuicCongestionFeedbackFrame from receiver.
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ sender_->OnIncomingQuicCongestionFeedbackFrame(feedback, clock_.Now(),
+ not_used_);
+ // Make sure we can send.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(),
+ NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsZero());
+
+ for (int i = 0; i < kNumberOfAck; ++i) {
+ // Send our full congestion window.
+ SendAvailableCongestionWindow();
+ AckNPackets(2);
+ }
+ SendAvailableCongestionWindow();
+ QuicByteCount expected_congestion_window = kDefaultWindowTCP +
+ (kMaxPacketSize * 2 * kNumberOfAck);
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+
+ sender_->OnIncomingLoss(clock_.Now());
+
+ // Make sure that we should not send right now.
+ EXPECT_TRUE(sender_->TimeUntilSend(clock_.Now(), NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE).IsInfinite());
+
+ // We should now have fallen out of slow start.
+ // We expect window to be cut in half.
+ expected_congestion_window /= 2;
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+
+ // Testing Reno phase.
+ // We need to ack half of the pending packet before we can send agin.
+ int number_of_packets_in_window = expected_congestion_window / kMaxPacketSize;
+ AckNPackets(number_of_packets_in_window);
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+ EXPECT_EQ(0u, sender_->AvailableCongestionWindow());
+
+ AckNPackets(1);
+ expected_congestion_window += kMaxPacketSize;
+ number_of_packets_in_window++;
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+
+ // We should need number_of_packets_in_window ACK:ed packets before
+ // increasing window by one.
+ for (int k = 0; k < number_of_packets_in_window; ++k) {
+ SendAvailableCongestionWindow();
+ AckNPackets(1);
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+ }
+ SendAvailableCongestionWindow();
+ AckNPackets(1);
+ expected_congestion_window += kMaxPacketSize;
+ EXPECT_EQ(expected_congestion_window, sender_->CongestionWindow());
+}
+
+TEST_F(TcpCubicSenderTest, RetransmissionDelay) {
+ const int64 kRttMs = 10;
+ const int64 kDeviationMs = 3;
+ EXPECT_EQ(QuicTime::Delta::Zero(), sender_->RetransmissionDelay());
+
+ sender_->AckAccounting(QuicTime::Delta::FromMilliseconds(kRttMs));
+
+ // Initial value is to set the median deviation to half of the initial
+ // rtt, the median in then multiplied by a factor of 4 and finaly the
+ // smoothed rtt is added which is the inital rtt.
+ QuicTime::Delta expected_delay =
+ QuicTime::Delta::FromMilliseconds(kRttMs + kRttMs / 2 * 4);
+ EXPECT_EQ(expected_delay, sender_->RetransmissionDelay());
+
+ for (int i = 0; i < 100; ++i) {
+ // Run to make sure that we converge.
+ sender_->AckAccounting(
+ QuicTime::Delta::FromMilliseconds(kRttMs + kDeviationMs));
+ sender_->AckAccounting(
+ QuicTime::Delta::FromMilliseconds(kRttMs - kDeviationMs));
+ }
+ expected_delay = QuicTime::Delta::FromMilliseconds(kRttMs + kDeviationMs * 4);
+
+ EXPECT_NEAR(kRttMs, sender_->SmoothedRtt().ToMilliseconds(), 1);
+ EXPECT_NEAR(expected_delay.ToMilliseconds(),
+ sender_->RetransmissionDelay().ToMilliseconds(),
+ 1);
+}
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/tcp_receiver.cc b/chromium/net/quic/congestion_control/tcp_receiver.cc
new file mode 100644
index 00000000000..465e40f194a
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_receiver.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+
+namespace net {
+
+// Originally 64K bytes for TCP, setting it to 256K to support higher bitrates.
+const QuicByteCount kReceiveWindowTCP = 256000;
+
+TcpReceiver::TcpReceiver()
+ : accumulated_number_of_recoverd_lost_packets_(0),
+ receive_window_(kReceiveWindowTCP) {
+}
+
+bool TcpReceiver::GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) {
+ feedback->type = kTCP;
+ feedback->tcp.accumulated_number_of_lost_packets =
+ accumulated_number_of_recoverd_lost_packets_;
+ feedback->tcp.receive_window = receive_window_;
+ return true;
+}
+
+void TcpReceiver::RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) {
+ if (revived) {
+ ++accumulated_number_of_recoverd_lost_packets_;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/congestion_control/tcp_receiver.h b/chromium/net/quic/congestion_control/tcp_receiver.h
new file mode 100644
index 00000000000..695ffbbac41
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_receiver.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+//
+// TCP receiver side congestion algorithm, emulates the behaviour of TCP.
+
+#ifndef NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
+#define NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE TcpReceiver : public ReceiveAlgorithmInterface {
+ public:
+ TcpReceiver();
+
+ // Start implementation of SendAlgorithmInterface.
+ virtual bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* feedback) OVERRIDE;
+
+ virtual void RecordIncomingPacket(QuicByteCount bytes,
+ QuicPacketSequenceNumber sequence_number,
+ QuicTime timestamp,
+ bool revived) OVERRIDE;
+
+ private:
+ // We need to keep track of FEC recovered packets.
+ int accumulated_number_of_recoverd_lost_packets_;
+ QuicByteCount receive_window_;
+
+ DISALLOW_COPY_AND_ASSIGN(TcpReceiver);
+};
+
+} // namespace net
+#endif // NET_QUIC_CONGESTION_CONTROL_TCP_RECEIVER_H_
diff --git a/chromium/net/quic/congestion_control/tcp_receiver_test.cc b/chromium/net/quic/congestion_control/tcp_receiver_test.cc
new file mode 100644
index 00000000000..305ff75eb8b
--- /dev/null
+++ b/chromium/net/quic/congestion_control/tcp_receiver_test.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/congestion_control/tcp_receiver.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicTcpReceiverTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ receiver_.reset(new TcpReceiver());
+ }
+ scoped_ptr<TcpReceiver> receiver_;
+};
+
+TEST_F(QuicTcpReceiverTest, SimpleReceiver) {
+ QuicCongestionFeedbackFrame feedback;
+ QuicTime timestamp(QuicTime::Zero());
+ receiver_->RecordIncomingPacket(1, 1, timestamp, false);
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ EXPECT_EQ(kTCP, feedback.type);
+ EXPECT_EQ(256000u, feedback.tcp.receive_window);
+ EXPECT_EQ(0, feedback.tcp.accumulated_number_of_lost_packets);
+ receiver_->RecordIncomingPacket(1, 2, timestamp, true);
+ ASSERT_TRUE(receiver_->GenerateCongestionFeedback(&feedback));
+ EXPECT_EQ(kTCP, feedback.type);
+ EXPECT_EQ(256000u, feedback.tcp.receive_window);
+ EXPECT_EQ(1, feedback.tcp.accumulated_number_of_lost_packets);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_decrypter.h b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter.h
new file mode 100644
index 00000000000..5dc12c8f9c9
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_decrypter.h"
+
+#if defined(USE_OPENSSL)
+#include "net/quic/crypto/scoped_evp_cipher_ctx.h"
+#endif
+
+namespace net {
+
+namespace test {
+class Aes128Gcm12DecrypterPeer;
+} // namespace test
+
+// An Aes128Gcm12Decrypter is a QuicDecrypter that implements the
+// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by
+// calling QuicDecrypter::Create(kAESG).
+//
+// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix
+// of the nonce is four bytes.
+class NET_EXPORT_PRIVATE Aes128Gcm12Decrypter : public QuicDecrypter {
+ public:
+ enum {
+ // Authentication tags are truncated to 96 bits.
+ kAuthTagSize = 12,
+ };
+
+ Aes128Gcm12Decrypter();
+ virtual ~Aes128Gcm12Decrypter();
+
+ // Returns true if the underlying crypto library supports AES GCM.
+ static bool IsSupported();
+
+ // QuicDecrypter implementation
+ virtual bool SetKey(base::StringPiece key) OVERRIDE;
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+ virtual bool Decrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) OVERRIDE;
+ virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext) OVERRIDE;
+ virtual base::StringPiece GetKey() const OVERRIDE;
+ virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ private:
+ // The 128-bit AES key.
+ unsigned char key_[16];
+ // The nonce prefix.
+ unsigned char nonce_prefix_[4];
+
+#if defined(USE_OPENSSL)
+ ScopedEVPCipherCtx ctx_;
+#endif
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_AES_128_GCM_12_DECRYPTER_H_
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc
new file mode 100644
index 00000000000..e3fc1b6f0ae
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_nss.cc
@@ -0,0 +1,387 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include <nss.h>
+#include <pk11pub.h>
+#include <secerr.h>
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ghash.h"
+#include "crypto/scoped_nss_types.h"
+
+#if defined(USE_NSS)
+#include <dlfcn.h>
+#endif
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+// The pkcs11t.h header in NSS versions older than 3.14 does not have the CTR
+// and GCM types, so define them here.
+#if !defined(CKM_AES_CTR)
+#define CKM_AES_CTR 0x00001086
+#define CKM_AES_GCM 0x00001087
+
+struct CK_AES_CTR_PARAMS {
+ CK_ULONG ulCounterBits;
+ CK_BYTE cb[16];
+};
+
+struct CK_GCM_PARAMS {
+ CK_BYTE_PTR pIv;
+ CK_ULONG ulIvLen;
+ CK_BYTE_PTR pAAD;
+ CK_ULONG ulAADLen;
+ CK_ULONG ulTagBits;
+};
+#endif // CKM_AES_CTR
+
+typedef SECStatus
+(*PK11_DecryptFunction)(
+ PK11SymKey* symKey, CK_MECHANISM_TYPE mechanism, SECItem* param,
+ unsigned char* out, unsigned int* outLen, unsigned int maxLen,
+ const unsigned char* enc, unsigned encLen);
+
+// On Linux, dynamically link against the system version of libnss3.so. In
+// order to continue working on systems without up-to-date versions of NSS,
+// lookup PK11_Decrypt with dlsym.
+
+// GcmSupportChecker is a singleton which caches the results of runtime symbol
+// resolution of PK11_Decrypt.
+class GcmSupportChecker {
+ public:
+ static PK11_DecryptFunction pk11_decrypt_func() {
+ return pk11_decrypt_func_;
+ }
+
+ static CK_MECHANISM_TYPE aes_key_mechanism() {
+ return aes_key_mechanism_;
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>;
+
+ GcmSupportChecker() {
+#if !defined(USE_NSS)
+ // Using a bundled version of NSS that is guaranteed to have this symbol.
+ pk11_decrypt_func_ = PK11_Decrypt;
+#else
+ // Using system NSS libraries and PCKS #11 modules, which may not have the
+ // necessary function (PK11_Decrypt) or mechanism support (CKM_AES_GCM).
+
+ // If PK11_Decrypt() was successfully resolved, then NSS will support
+ // AES-GCM directly. This was introduced in NSS 3.15.
+ pk11_decrypt_func_ = (PK11_DecryptFunction)dlsym(RTLD_DEFAULT,
+ "PK11_Decrypt");
+ if (pk11_decrypt_func_ == NULL) {
+ aes_key_mechanism_ = CKM_AES_ECB;
+ }
+#endif
+ }
+
+ // |pk11_decrypt_func_| stores the runtime symbol resolution of PK11_Decrypt.
+ static PK11_DecryptFunction pk11_decrypt_func_;
+
+ // The correct value for |aes_key_mechanism_| is CKM_AES_GCM, but because of
+ // NSS bug https://bugzilla.mozilla.org/show_bug.cgi?id=853285 (to be fixed in
+ // NSS 3.15), use CKM_AES_ECB for NSS versions older than 3.15.
+ static CK_MECHANISM_TYPE aes_key_mechanism_;
+};
+
+// static
+PK11_DecryptFunction GcmSupportChecker::pk11_decrypt_func_ = NULL;
+
+// static
+CK_MECHANISM_TYPE GcmSupportChecker::aes_key_mechanism_ = CKM_AES_GCM;
+
+base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker =
+ LAZY_INSTANCE_INITIALIZER;
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+const size_t kAESNonceSize = 12;
+
+// Calls PK11_Decrypt if it's available. Otherwise, emulates CKM_AES_GCM using
+// CKM_AES_CTR and the GaloisHash class.
+SECStatus My_Decrypt(PK11SymKey* key,
+ CK_MECHANISM_TYPE mechanism,
+ SECItem* param,
+ unsigned char* out,
+ unsigned int* out_len,
+ unsigned int max_len,
+ const unsigned char* enc,
+ unsigned int enc_len) {
+ // If PK11_Decrypt() was successfully resolved or if bundled version of NSS is
+ // being used, then NSS will support AES-GCM directly.
+ PK11_DecryptFunction pk11_decrypt_func =
+ GcmSupportChecker::pk11_decrypt_func();
+ if (pk11_decrypt_func != NULL) {
+ return pk11_decrypt_func(key, mechanism, param, out, out_len, max_len, enc,
+ enc_len);
+ }
+
+ // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x
+ // has a bug in the AES GCM code
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing
+ // the PK11_Decrypt function
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are
+ // resolved in NSS 3.15.
+
+ DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM));
+ DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS));
+
+ const CK_GCM_PARAMS* gcm_params =
+ reinterpret_cast<CK_GCM_PARAMS*>(param->data);
+
+ DCHECK_EQ(gcm_params->ulTagBits,
+ static_cast<CK_ULONG>(Aes128Gcm12Decrypter::kAuthTagSize * 8));
+ if (gcm_params->ulIvLen != 12u) {
+ DLOG(INFO) << "ulIvLen is not equal to 12";
+ PORT_SetError(SEC_ERROR_INPUT_LEN);
+ return SECFailure;
+ }
+
+ SECItem my_param = { siBuffer, NULL, 0 };
+
+ // Step 2. Let H = CIPH_K(128 '0' bits).
+ unsigned char ghash_key[16] = {0};
+ crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey(
+ CKM_AES_ECB, CKA_ENCRYPT, key, &my_param));
+ if (!ctx) {
+ DLOG(INFO) << "PK11_CreateContextBySymKey failed";
+ return SECFailure;
+ }
+ int output_len;
+ if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key),
+ ghash_key, sizeof(ghash_key)) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+
+ PK11_Finalize(ctx.get());
+
+ if (output_len != sizeof(ghash_key)) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ // Step 3. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1.
+ CK_AES_CTR_PARAMS ctr_params = {0};
+ ctr_params.ulCounterBits = 32;
+ memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen);
+ ctr_params.cb[12] = 0;
+ ctr_params.cb[13] = 0;
+ ctr_params.cb[14] = 0;
+ ctr_params.cb[15] = 1;
+
+ my_param.type = siBuffer;
+ my_param.data = reinterpret_cast<unsigned char*>(&ctr_params);
+ my_param.len = sizeof(ctr_params);
+
+ ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key,
+ &my_param));
+ if (!ctx) {
+ DLOG(INFO) << "PK11_CreateContextBySymKey failed";
+ return SECFailure;
+ }
+
+ // Step 6. Calculate the encryption mask of GCTR_K(J0, ...).
+ unsigned char tag_mask[16] = {0};
+ if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask),
+ tag_mask, sizeof(tag_mask)) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+ if (output_len != sizeof(tag_mask)) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ if (enc_len < Aes128Gcm12Decrypter::kAuthTagSize) {
+ PORT_SetError(SEC_ERROR_INPUT_LEN);
+ return SECFailure;
+ }
+
+ // The const_cast for |enc| can be removed if system NSS libraries are
+ // NSS 3.14.1 or later (NSS bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=808218).
+ if (PK11_CipherOp(ctx.get(), out, &output_len, max_len,
+ const_cast<unsigned char*>(enc),
+ enc_len - Aes128Gcm12Decrypter::kAuthTagSize) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+
+ PK11_Finalize(ctx.get());
+
+ if (static_cast<unsigned int>(output_len) !=
+ enc_len - Aes128Gcm12Decrypter::kAuthTagSize) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ crypto::GaloisHash ghash(ghash_key);
+ ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen);
+ ghash.UpdateCiphertext(enc, output_len);
+ unsigned char auth_tag[Aes128Gcm12Decrypter::kAuthTagSize];
+ ghash.Finish(auth_tag, Aes128Gcm12Decrypter::kAuthTagSize);
+ for (unsigned int i = 0; i < Aes128Gcm12Decrypter::kAuthTagSize; i++) {
+ auth_tag[i] ^= tag_mask[i];
+ }
+
+ if (NSS_SecureMemcmp(auth_tag, enc + output_len,
+ Aes128Gcm12Decrypter::kAuthTagSize) != 0) {
+ PORT_SetError(SEC_ERROR_BAD_DATA);
+ return SECFailure;
+ }
+
+ *out_len = output_len;
+ return SECSuccess;
+}
+
+} // namespace
+
+Aes128Gcm12Decrypter::Aes128Gcm12Decrypter() {
+ ignore_result(g_gcm_support_checker.Get());
+}
+
+Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {}
+
+// static
+bool Aes128Gcm12Decrypter::IsSupported() {
+ // NSS 3.15 supports CKM_AES_GCM directly.
+ // NSS 3.14 supports CKM_AES_CTR, which can be used to emulate CKM_AES_GCM.
+ // Versions earlier than NSS 3.14 are not supported.
+ return NSS_VersionCheck("3.14") != PR_FALSE;
+}
+
+bool Aes128Gcm12Decrypter::SetKey(StringPiece key) {
+ DCHECK_EQ(key.size(), sizeof(key_));
+ if (key.size() != sizeof(key_)) {
+ return false;
+ }
+ memcpy(key_, key.data(), key.size());
+ return true;
+}
+
+bool Aes128Gcm12Decrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ DCHECK_EQ(nonce_prefix.size(), kNoncePrefixSize);
+ if (nonce_prefix.size() != kNoncePrefixSize) {
+ return false;
+ }
+ COMPILE_ASSERT(sizeof(nonce_prefix_) == kNoncePrefixSize, bad_nonce_length);
+ memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+ return true;
+}
+
+bool Aes128Gcm12Decrypter::Decrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece ciphertext,
+ uint8* output,
+ size_t* output_length) {
+ if (ciphertext.length() < kAuthTagSize ||
+ nonce.size() != kNoncePrefixSize + sizeof(QuicPacketSequenceNumber)) {
+ return false;
+ }
+ // NSS 3.14.x incorrectly requires an output buffer at least as long as
+ // the ciphertext (NSS bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id= 853674). Fortunately
+ // QuicDecrypter::Decrypt() specifies that |output| must be as long as
+ // |ciphertext| on entry.
+ size_t plaintext_size = ciphertext.length() - kAuthTagSize;
+
+ // Import key_ into NSS.
+ SECItem key_item;
+ key_item.type = siBuffer;
+ key_item.data = key_;
+ key_item.len = sizeof(key_);
+ PK11SlotInfo* slot = PK11_GetInternalSlot();
+ // The exact value of the |origin| argument doesn't matter to NSS as long as
+ // it's not PK11_OriginFortezzaHack, so pass PK11_OriginUnwrap as a
+ // placeholder.
+ crypto::ScopedPK11SymKey aes_key(PK11_ImportSymKey(
+ slot, GcmSupportChecker::aes_key_mechanism(), PK11_OriginUnwrap,
+ CKA_DECRYPT, &key_item, NULL));
+ PK11_FreeSlot(slot);
+ slot = NULL;
+ if (!aes_key) {
+ DLOG(INFO) << "PK11_ImportSymKey failed";
+ return false;
+ }
+
+ CK_GCM_PARAMS gcm_params = {0};
+ gcm_params.pIv =
+ reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+ gcm_params.ulIvLen = nonce.size();
+ gcm_params.pAAD =
+ reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+ gcm_params.ulAADLen = associated_data.size();
+ gcm_params.ulTagBits = kAuthTagSize * 8;
+
+ SECItem param;
+ param.type = siBuffer;
+ param.data = reinterpret_cast<unsigned char*>(&gcm_params);
+ param.len = sizeof(gcm_params);
+
+ unsigned int output_len;
+ // If an incorrect authentication tag causes a decryption failure, the NSS
+ // error is SEC_ERROR_BAD_DATA (-8190).
+ if (My_Decrypt(aes_key.get(), CKM_AES_GCM, &param,
+ output, &output_len, ciphertext.length(),
+ reinterpret_cast<const unsigned char*>(ciphertext.data()),
+ ciphertext.length()) != SECSuccess) {
+ DLOG(INFO) << "My_Decrypt failed: NSS error " << PORT_GetError();
+ return false;
+ }
+
+ if (output_len != plaintext_size) {
+ DLOG(INFO) << "Wrong output length";
+ return false;
+ }
+ *output_length = output_len;
+ return true;
+}
+
+QuicData* Aes128Gcm12Decrypter::DecryptPacket(
+ QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece ciphertext) {
+ if (ciphertext.length() < kAuthTagSize) {
+ return NULL;
+ }
+ size_t plaintext_size;
+ scoped_ptr<char[]> plaintext(new char[ciphertext.length()]);
+
+ uint8 nonce[kNoncePrefixSize + sizeof(sequence_number)];
+ COMPILE_ASSERT(sizeof(nonce) == kAESNonceSize, bad_sequence_number_size);
+ memcpy(nonce, nonce_prefix_, kNoncePrefixSize);
+ memcpy(nonce + kNoncePrefixSize, &sequence_number, sizeof(sequence_number));
+ if (!Decrypt(StringPiece(reinterpret_cast<char*>(nonce), sizeof(nonce)),
+ associated_data, ciphertext,
+ reinterpret_cast<uint8*>(plaintext.get()),
+ &plaintext_size)) {
+ return NULL;
+ }
+ return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+StringPiece Aes128Gcm12Decrypter::GetKey() const {
+ return StringPiece(reinterpret_cast<const char*>(key_), sizeof(key_));
+}
+
+StringPiece Aes128Gcm12Decrypter::GetNoncePrefix() const {
+ return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+ kNoncePrefixSize);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc
new file mode 100644
index 00000000000..cbc7a8426d3
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_openssl.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include <openssl/evp.h>
+
+#include "base/memory/scoped_ptr.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+const size_t kAESNonceSize = 12;
+
+} // namespace
+
+Aes128Gcm12Decrypter::Aes128Gcm12Decrypter() {}
+
+Aes128Gcm12Decrypter::~Aes128Gcm12Decrypter() {}
+
+// static
+bool Aes128Gcm12Decrypter::IsSupported() { return true; }
+
+bool Aes128Gcm12Decrypter::SetKey(StringPiece key) {
+ DCHECK_EQ(key.size(), sizeof(key_));
+ if (key.size() != sizeof(key_)) {
+ return false;
+ }
+ memcpy(key_, key.data(), key.size());
+
+ // Set the cipher type and the key.
+ if (EVP_EncryptInit_ex(ctx_.get(), EVP_aes_128_gcm(), NULL, key_,
+ NULL) == 0) {
+ return false;
+ }
+
+ // Set the IV (nonce) length.
+ if (EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_GCM_SET_IVLEN, kAESNonceSize,
+ NULL) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Aes128Gcm12Decrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ DCHECK_EQ(nonce_prefix.size(), kNoncePrefixSize);
+ if (nonce_prefix.size() != kNoncePrefixSize) {
+ return false;
+ }
+ COMPILE_ASSERT(sizeof(nonce_prefix_) == kNoncePrefixSize, bad_nonce_length);
+ memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+ return true;
+}
+
+bool Aes128Gcm12Decrypter::Decrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece ciphertext,
+ uint8* output,
+ size_t* output_length) {
+ if (ciphertext.length() < kAuthTagSize ||
+ nonce.size() != kNoncePrefixSize + sizeof(QuicPacketSequenceNumber)) {
+ return false;
+ }
+ const size_t plaintext_size = ciphertext.length() - kAuthTagSize;
+
+ // Set the IV (nonce).
+ if (EVP_DecryptInit_ex(
+ ctx_.get(), NULL, NULL, NULL,
+ reinterpret_cast<const uint8*>(nonce.data())) == 0) {
+ return false;
+ }
+
+ // Set the authentication tag.
+ if (EVP_CIPHER_CTX_ctrl(
+ ctx_.get(), EVP_CTRL_GCM_SET_TAG, kAuthTagSize,
+ const_cast<char*>(ciphertext.data()) + plaintext_size) == 0) {
+ return false;
+ }
+
+ // If we pass a NULL, zero-length associated data to OpenSSL then it breaks.
+ // Thus we only set non-empty associated data.
+ if (!associated_data.empty()) {
+ // Set the associated data. The second argument (output buffer) must be
+ // NULL.
+ int unused_len;
+ if (EVP_DecryptUpdate(
+ ctx_.get(), NULL, &unused_len,
+ reinterpret_cast<const uint8*>(associated_data.data()),
+ associated_data.size()) == 0) {
+ return false;
+ }
+ }
+
+ int len;
+ if (EVP_DecryptUpdate(
+ ctx_.get(), output, &len,
+ reinterpret_cast<const uint8*>(ciphertext.data()),
+ plaintext_size) == 0) {
+ return false;
+ }
+ output += len;
+
+ if (EVP_DecryptFinal_ex(ctx_.get(), output, &len) == 0) {
+ return false;
+ }
+ output += len;
+
+ *output_length = plaintext_size;
+
+ return true;
+}
+
+QuicData* Aes128Gcm12Decrypter::DecryptPacket(
+ QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece ciphertext) {
+ if (ciphertext.length() < kAuthTagSize) {
+ return NULL;
+ }
+ size_t plaintext_size;
+ scoped_ptr<char[]> plaintext(new char[ciphertext.length()]);
+
+ uint8 nonce[kNoncePrefixSize + sizeof(sequence_number)];
+ COMPILE_ASSERT(sizeof(nonce) == kAESNonceSize, bad_sequence_number_size);
+ memcpy(nonce, nonce_prefix_, kNoncePrefixSize);
+ memcpy(nonce + kNoncePrefixSize, &sequence_number, sizeof(sequence_number));
+ if (!Decrypt(StringPiece(reinterpret_cast<char*>(nonce), sizeof(nonce)),
+ associated_data, ciphertext,
+ reinterpret_cast<uint8*>(plaintext.get()),
+ &plaintext_size)) {
+ return NULL;
+ }
+ return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+StringPiece Aes128Gcm12Decrypter::GetKey() const {
+ return StringPiece(reinterpret_cast<const char*>(key_), sizeof(key_));
+}
+
+StringPiece Aes128Gcm12Decrypter::GetNoncePrefix() const {
+ return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+ kNoncePrefixSize);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc
new file mode 100644
index 00000000000..cea6a6e5b6c
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_decrypter_test.cc
@@ -0,0 +1,386 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_decrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The AES GCM test vectors come from the file gcmDecrypt128.rsp
+// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on
+// 2013-02-01. The test vectors in that file look like this:
+//
+// [Keylen = 128]
+// [IVlen = 96]
+// [PTlen = 0]
+// [AADlen = 0]
+// [Taglen = 128]
+//
+// Count = 0
+// Key = cf063a34d4a9a76c2c86787d3f96db71
+// IV = 113b9785971864c83b01c787
+// CT =
+// AAD =
+// Tag = 72ac8493e3a5228b5d130a69d2510e42
+// PT =
+//
+// Count = 1
+// Key = a49a5e26a2f8cb63d05546c2a62f5343
+// IV = 907763b19b9b4ab6bd4f0281
+// CT =
+// AAD =
+// Tag = a2be08210d8c470a8df6e8fbd79ec5cf
+// FAIL
+//
+// ...
+//
+// The gcmDecrypt128.rsp file is huge (2.6 MB), so I selected just a
+// few test vectors for this unit test.
+
+// Describes a group of test vectors that all have a given key length, IV
+// length, plaintext length, AAD length, and tag length.
+struct TestGroupInfo {
+ size_t key_len;
+ size_t iv_len;
+ size_t pt_len;
+ size_t aad_len;
+ size_t tag_len;
+};
+
+// Each test vector consists of six strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+ // Input:
+ const char* key;
+ const char* iv;
+ const char* ct;
+ const char* aad;
+ const char* tag;
+
+ // Expected output:
+ const char* pt; // An empty string "" means decryption succeeded and
+ // the plaintext is zero-length. NULL means decryption
+ // failed.
+};
+
+const TestGroupInfo test_group_info[] = {
+ { 128, 96, 0, 0, 128 },
+ { 128, 96, 0, 128, 128 },
+ { 128, 96, 128, 0, 128 },
+ { 128, 96, 408, 160, 128 },
+ { 128, 96, 408, 720, 128 },
+ { 128, 96, 104, 0, 128 },
+};
+
+const TestVector test_group_0[] = {
+ { "cf063a34d4a9a76c2c86787d3f96db71",
+ "113b9785971864c83b01c787",
+ "",
+ "",
+ "72ac8493e3a5228b5d130a69d2510e42",
+ ""
+ },
+ { "a49a5e26a2f8cb63d05546c2a62f5343",
+ "907763b19b9b4ab6bd4f0281",
+ "",
+ "",
+ "a2be08210d8c470a8df6e8fbd79ec5cf",
+ NULL // FAIL
+ },
+ { NULL }
+};
+
+const TestVector test_group_1[] = {
+ { "d1f6af919cde85661208bdce0c27cb22",
+ "898c6929b435017bf031c3c5",
+ "",
+ "7c5faa40e636bbc91107e68010c92b9f",
+ "ae45f11777540a2caeb128be8092468a",
+ NULL // FAIL
+ },
+ { "2370e320d4344208e0ff5683f243b213",
+ "04dbb82f044d30831c441228",
+ "",
+ "d43a8e5089eea0d026c03a85178b27da",
+ "2a049c049d25aa95969b451d93c31c6e",
+ ""
+ },
+ { NULL }
+};
+
+const TestVector test_group_2[] = {
+ { "e98b72a9881a84ca6b76e0f43e68647a",
+ "8b23299fde174053f3d652ba",
+ "5a3c1cf1985dbb8bed818036fdd5ab42",
+ "",
+ "23c7ab0f952b7091cd324835043b5eb5",
+ "28286a321293253c3e0aa2704a278032"
+ },
+ { "33240636cd3236165f1a553b773e728e",
+ "17c4d61493ecdc8f31700b12",
+ "47bb7e23f7bdfe05a8091ac90e4f8b2e",
+ "",
+ "b723c70e931d9785f40fd4ab1d612dc9",
+ "95695a5b12f2870b9cc5fdc8f218a97d"
+ },
+ { "5164df856f1e9cac04a79b808dc5be39",
+ "e76925d5355e0584ce871b2b",
+ "0216c899c88d6e32c958c7e553daa5bc",
+ "",
+ "a145319896329c96df291f64efbe0e3a",
+ NULL // FAIL
+ },
+ { NULL }
+};
+
+const TestVector test_group_3[] = {
+ { "af57f42c60c0fc5a09adb81ab86ca1c3",
+ "a2dc01871f37025dc0fc9a79",
+ "b9a535864f48ea7b6b1367914978f9bfa087d854bb0e269bed8d279d2eea1210e48947"
+ "338b22f9bad09093276a331e9c79c7f4",
+ "41dc38988945fcb44faf2ef72d0061289ef8efd8",
+ "4f71e72bde0018f555c5adcce062e005",
+ "3803a0727eeb0ade441e0ec107161ded2d425ec0d102f21f51bf2cf9947c7ec4aa7279"
+ "5b2f69b041596e8817d0a3c16f8fadeb"
+ },
+ { "ebc753e5422b377d3cb64b58ffa41b61",
+ "2e1821efaced9acf1f241c9b",
+ "069567190554e9ab2b50a4e1fbf9c147340a5025fdbd201929834eaf6532325899ccb9"
+ "f401823e04b05817243d2142a3589878",
+ "b9673412fd4f88ba0e920f46dd6438ff791d8eef",
+ "534d9234d2351cf30e565de47baece0b",
+ "39077edb35e9c5a4b1e4c2a6b9bb1fce77f00f5023af40333d6d699014c2bcf4209c18"
+ "353a18017f5b36bfc00b1f6dcb7ed485"
+ },
+ { "52bdbbf9cf477f187ec010589cb39d58",
+ "d3be36d3393134951d324b31",
+ "700188da144fa692cf46e4a8499510a53d90903c967f7f13e8a1bd8151a74adc4fe63e"
+ "32b992760b3a5f99e9a47838867000a9",
+ "93c4fc6a4135f54d640b0c976bf755a06a292c33",
+ "8ca4e38aa3dfa6b1d0297021ccf3ea5f",
+ NULL // FAIL
+ },
+ { NULL }
+};
+
+const TestVector test_group_4[] = {
+ { "da2bb7d581493d692380c77105590201",
+ "44aa3e7856ca279d2eb020c6",
+ "9290d430c9e89c37f0446dbd620c9a6b34b1274aeb6f911f75867efcf95b6feda69f1a"
+ "f4ee16c761b3c9aeac3da03aa9889c88",
+ "4cd171b23bddb3a53cdf959d5c1710b481eb3785a90eb20a2345ee00d0bb7868c367ab"
+ "12e6f4dd1dee72af4eee1d197777d1d6499cc541f34edbf45cda6ef90b3c024f9272d7"
+ "2ec1909fb8fba7db88a4d6f7d3d925980f9f9f72",
+ "9e3ac938d3eb0cadd6f5c9e35d22ba38",
+ "9bbf4c1a2742f6ac80cb4e8a052e4a8f4f07c43602361355b717381edf9fabd4cb7e3a"
+ "d65dbd1378b196ac270588dd0621f642"
+ },
+ { "d74e4958717a9d5c0e235b76a926cae8",
+ "0b7471141e0c70b1995fd7b1",
+ "e701c57d2330bf066f9ff8cf3ca4343cafe4894651cd199bdaaa681ba486b4a65c5a22"
+ "b0f1420be29ea547d42c713bc6af66aa",
+ "4a42b7aae8c245c6f1598a395316e4b8484dbd6e64648d5e302021b1d3fa0a38f46e22"
+ "bd9c8080b863dc0016482538a8562a4bd0ba84edbe2697c76fd039527ac179ec5506cf"
+ "34a6039312774cedebf4961f3978b14a26509f96",
+ "e192c23cb036f0b31592989119eed55d",
+ "840d9fb95e32559fb3602e48590280a172ca36d9b49ab69510f5bd552bfab7a306f85f"
+ "f0a34bc305b88b804c60b90add594a17"
+ },
+ { "1986310c725ac94ecfe6422e75fc3ee7",
+ "93ec4214fa8e6dc4e3afc775",
+ "b178ec72f85a311ac4168f42a4b2c23113fbea4b85f4b9dabb74e143eb1b8b0a361e02"
+ "43edfd365b90d5b325950df0ada058f9",
+ "e80b88e62c49c958b5e0b8b54f532d9ff6aa84c8a40132e93e55b59fc24e8decf28463"
+ "139f155d1e8ce4ee76aaeefcd245baa0fc519f83a5fb9ad9aa40c4b21126013f576c42"
+ "72c2cb136c8fd091cc4539877a5d1e72d607f960",
+ "8b347853f11d75e81e8a95010be81f17",
+ NULL // FAIL
+ },
+ { NULL }
+};
+
+const TestVector test_group_5[] = {
+ { "387218b246c1a8257748b56980e50c94",
+ "dd7e014198672be39f95b69d",
+ "cdba9e73eaf3d38eceb2b04a8d",
+ "",
+ "ecf90f4a47c9c626d6fb2c765d201556",
+ "48f5b426baca03064554cc2b30"
+ },
+ { "294de463721e359863887c820524b3d4",
+ "3338b35c9d57a5d28190e8c9",
+ "2f46634e74b8e4c89812ac83b9",
+ "",
+ "dabd506764e68b82a7e720aa18da0abe",
+ "46a2e55c8e264df211bd112685"
+ },
+ { "28ead7fd2179e0d12aa6d5d88c58c2dc",
+ "5055347f18b4d5add0ae5c41",
+ "142d8210c3fb84774cdbd0447a",
+ "",
+ "5fd321d9cdb01952dc85f034736c2a7d",
+ "3b95b981086ee73cc4d0cc1422"
+ },
+ { "7d7b6c988137b8d470c57bf674a09c87",
+ "9edf2aa970d016ac962e1fd8",
+ "a85b66c3cb5eab91d5bdc8bc0e",
+ "",
+ "dc054efc01f3afd21d9c2484819f569a",
+ NULL // FAIL
+ },
+ { NULL }
+};
+
+const TestVector* const test_group_array[] = {
+ test_group_0,
+ test_group_1,
+ test_group_2,
+ test_group_3,
+ test_group_4,
+ test_group_5,
+};
+
+// Returns true if |ch| is a lowercase hexadecimal digit.
+bool IsHexDigit(char ch) {
+ return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f');
+}
+
+// Converts a lowercase hexadecimal digit to its integer value.
+int HexDigitToInt(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ }
+ return ch - 'a' + 10;
+}
+
+// |in| is a string consisting of lowercase hexadecimal digits, where
+// every two digits represent one byte. |out| is a buffer of size |max_len|.
+// Converts |in| to bytes and stores the bytes in the |out| buffer. The
+// number of bytes converted is returned in |*out_len|. Returns true on
+// success, false on failure.
+bool DecodeHexString(const char* in,
+ char* out,
+ size_t* out_len,
+ size_t max_len) {
+ if (!in) {
+ *out_len = (size_t)-1;
+ return true;
+ }
+ *out_len = 0;
+ while (*in != '\0') {
+ if (!IsHexDigit(*in) || !IsHexDigit(*(in + 1))) {
+ return false;
+ }
+ if (*out_len >= max_len) {
+ return false;
+ }
+ out[*out_len] = HexDigitToInt(*in) * 16 + HexDigitToInt(*(in + 1));
+ (*out_len)++;
+ in += 2;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace net {
+namespace test {
+
+// DecryptWithNonce wraps the |Decrypt| method of |decrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the plaintext.
+QuicData* DecryptWithNonce(Aes128Gcm12Decrypter* decrypter,
+ StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece ciphertext) {
+ size_t plaintext_size = ciphertext.length();
+ scoped_ptr<char[]> plaintext(new char[plaintext_size]);
+
+ if (!decrypter->Decrypt(nonce, associated_data, ciphertext,
+ reinterpret_cast<unsigned char*>(plaintext.get()),
+ &plaintext_size)) {
+ return NULL;
+ }
+ return new QuicData(plaintext.release(), plaintext_size, true);
+}
+
+TEST(Aes128Gcm12DecrypterTest, Decrypt) {
+ if (!Aes128Gcm12Decrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ char key[1024];
+ size_t key_len;
+ char iv[1024];
+ size_t iv_len;
+ char ct[1024];
+ size_t ct_len;
+ char aad[1024];
+ size_t aad_len;
+ char tag[1024];
+ size_t tag_len;
+ char pt[1024];
+ size_t pt_len;
+
+ for (size_t i = 0; i < arraysize(test_group_array); i++) {
+ SCOPED_TRACE(i);
+ const TestVector* test_vector = test_group_array[i];
+ const TestGroupInfo& test_info = test_group_info[i];
+ for (size_t j = 0; test_vector[j].key != NULL; j++) {
+ // Decode the test vector.
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].key, key, &key_len, sizeof(key)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].iv, iv, &iv_len, sizeof(iv)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].ct, ct, &ct_len, sizeof(ct)));
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].aad, aad, &aad_len, sizeof(aad)));
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].tag, tag, &tag_len, sizeof(tag)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].pt, pt, &pt_len, sizeof(pt)));
+
+ // The test vector's lengths should look sane. Note that the lengths
+ // in |test_info| are in bits.
+ EXPECT_EQ(test_info.key_len, key_len * 8);
+ EXPECT_EQ(test_info.iv_len, iv_len * 8);
+ EXPECT_EQ(test_info.pt_len, ct_len * 8);
+ EXPECT_EQ(test_info.aad_len, aad_len * 8);
+ EXPECT_EQ(test_info.tag_len, tag_len * 8);
+ if (pt_len != static_cast<size_t>(-1)) {
+ EXPECT_EQ(test_info.pt_len, pt_len * 8);
+ }
+
+ // The test vectors have 16 byte authenticators but this code only uses
+ // the first 12.
+ ASSERT_LE(static_cast<size_t>(Aes128Gcm12Decrypter::kAuthTagSize),
+ tag_len);
+ tag_len = Aes128Gcm12Decrypter::kAuthTagSize;
+
+ Aes128Gcm12Decrypter decrypter;
+ ASSERT_TRUE(decrypter.SetKey(StringPiece(key, key_len)));
+ string ciphertext(ct, ct_len);
+ ciphertext.append(tag, tag_len);
+ scoped_ptr<QuicData> decrypted(DecryptWithNonce(
+ &decrypter, StringPiece(iv, iv_len),
+ // OpenSSL fails if NULL is set as the AAD, as opposed to a
+ // zero-length, non-NULL pointer.
+ StringPiece(aad_len ? aad : NULL, aad_len), ciphertext));
+ if (!decrypted.get()) {
+ EXPECT_EQ((size_t)-1, pt_len);
+ continue;
+ }
+ ASSERT_NE((size_t)-1, pt_len);
+
+ ASSERT_EQ(pt_len, decrypted->length());
+ test::CompareCharArraysWithHexError("plaintext", decrypted->data(),
+ pt_len, pt, pt_len);
+ }
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_encrypter.h b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter.h
new file mode 100644
index 00000000000..451f84df6f8
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+#if defined(USE_OPENSSL)
+#include "net/quic/crypto/scoped_evp_cipher_ctx.h"
+#endif
+
+namespace net {
+
+namespace test {
+class Aes128Gcm12EncrypterPeer;
+} // namespace test
+
+// An Aes128Gcm12Encrypter is a QuicEncrypter that implements the
+// AEAD_AES_128_GCM_12 algorithm specified in RFC 5282. Create an instance by
+// calling QuicEncrypter::Create(kAESG).
+//
+// It uses an authentication tag of 12 bytes (96 bits). The fixed prefix
+// of the nonce is four bytes.
+class NET_EXPORT_PRIVATE Aes128Gcm12Encrypter : public QuicEncrypter {
+ public:
+ enum {
+ // Authentication tags are truncated to 96 bits.
+ kAuthTagSize = 12,
+ };
+
+ Aes128Gcm12Encrypter();
+ virtual ~Aes128Gcm12Encrypter();
+
+ // Returns true if the underlying crypto library supports AES GCM.
+ static bool IsSupported();
+
+ // QuicEncrypter implementation
+ virtual bool SetKey(base::StringPiece key) OVERRIDE;
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+ virtual bool Encrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext,
+ unsigned char* output) OVERRIDE;
+ virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext) OVERRIDE;
+ virtual size_t GetKeySize() const OVERRIDE;
+ virtual size_t GetNoncePrefixSize() const OVERRIDE;
+ virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE;
+ virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE;
+ virtual base::StringPiece GetKey() const OVERRIDE;
+ virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+
+ private:
+ // The 128-bit AES key.
+ unsigned char key_[16];
+ // The nonce prefix.
+ unsigned char nonce_prefix_[4];
+ // last_seq_num_ is the last sequence number observed.
+ QuicPacketSequenceNumber last_seq_num_;
+
+#if defined(USE_OPENSSL)
+ ScopedEVPCipherCtx ctx_;
+#endif
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_AES_128_GCM_12_ENCRYPTER_H_
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc
new file mode 100644
index 00000000000..1cd3540c884
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_nss.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include <nss.h>
+#include <pk11pub.h>
+#include <secerr.h>
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ghash.h"
+#include "crypto/scoped_nss_types.h"
+
+#if defined(USE_NSS)
+#include <dlfcn.h>
+#endif
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+// The pkcs11t.h header in NSS versions older than 3.14 does not have the CTR
+// and GCM types, so define them here.
+#if !defined(CKM_AES_CTR)
+#define CKM_AES_CTR 0x00001086
+#define CKM_AES_GCM 0x00001087
+
+struct CK_AES_CTR_PARAMS {
+ CK_ULONG ulCounterBits;
+ CK_BYTE cb[16];
+};
+
+struct CK_GCM_PARAMS {
+ CK_BYTE_PTR pIv;
+ CK_ULONG ulIvLen;
+ CK_BYTE_PTR pAAD;
+ CK_ULONG ulAADLen;
+ CK_ULONG ulTagBits;
+};
+#endif // CKM_AES_CTR
+
+typedef SECStatus
+(*PK11_EncryptFunction)(
+ PK11SymKey* symKey, CK_MECHANISM_TYPE mechanism, SECItem* param,
+ unsigned char* out, unsigned int* outLen, unsigned int maxLen,
+ const unsigned char* data, unsigned int dataLen);
+
+// On Linux, dynamically link against the system version of libnss3.so. In
+// order to continue working on systems without up-to-date versions of NSS,
+// lookup PK11_Encrypt with dlsym.
+
+// GcmSupportChecker is a singleton which caches the results of runtime symbol
+// resolution of PK11_Encrypt.
+class GcmSupportChecker {
+ public:
+ static PK11_EncryptFunction pk11_encrypt_func() {
+ return pk11_encrypt_func_;
+ }
+
+ static CK_MECHANISM_TYPE aes_key_mechanism() {
+ return aes_key_mechanism_;
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<GcmSupportChecker>;
+
+ GcmSupportChecker() {
+#if !defined(USE_NSS)
+ // Using a bundled version of NSS that is guaranteed to have this symbol.
+ pk11_encrypt_func_ = PK11_Encrypt;
+#else
+ // Using system NSS libraries and PCKS #11 modules, which may not have the
+ // necessary function (PK11_Encrypt) or mechanism support (CKM_AES_GCM).
+
+ // If PK11_Encrypt() was successfully resolved, then NSS will support
+ // AES-GCM directly. This was introduced in NSS 3.15.
+ pk11_encrypt_func_ = (PK11_EncryptFunction)dlsym(RTLD_DEFAULT,
+ "PK11_Encrypt");
+ if (pk11_encrypt_func_ == NULL) {
+ aes_key_mechanism_ = CKM_AES_ECB;
+ }
+#endif
+ }
+
+ // |pk11_encrypt_func_| stores the runtime symbol resolution of PK11_Encrypt.
+ static PK11_EncryptFunction pk11_encrypt_func_;
+
+ // The correct value for |aes_key_mechanism_| is CKM_AES_GCM, but because of
+ // NSS bug https://bugzilla.mozilla.org/show_bug.cgi?id=853285 (to be fixed in
+ // NSS 3.15), use CKM_AES_ECB for NSS versions older than 3.15.
+ static CK_MECHANISM_TYPE aes_key_mechanism_;
+};
+
+// static
+PK11_EncryptFunction GcmSupportChecker::pk11_encrypt_func_ = NULL;
+
+// static
+CK_MECHANISM_TYPE GcmSupportChecker::aes_key_mechanism_ = CKM_AES_GCM;
+
+base::LazyInstance<GcmSupportChecker>::Leaky g_gcm_support_checker =
+ LAZY_INSTANCE_INITIALIZER;
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+const size_t kAESNonceSize = 12;
+
+// Calls PK11_Encrypt if it's available. Otherwise, emulates CKM_AES_GCM using
+// CKM_AES_CTR and the GaloisHash class.
+SECStatus My_Encrypt(PK11SymKey* key,
+ CK_MECHANISM_TYPE mechanism,
+ SECItem* param,
+ unsigned char* out,
+ unsigned int* out_len,
+ unsigned int max_len,
+ const unsigned char* data,
+ unsigned int data_len) {
+ // If PK11_Encrypt() was successfully resolved or if bundled version of NSS is
+ // being used, then NSS will support AES-GCM directly.
+ PK11_EncryptFunction pk11_encrypt_func =
+ GcmSupportChecker::pk11_encrypt_func();
+ if (pk11_encrypt_func != NULL) {
+ return pk11_encrypt_func(key, mechanism, param, out, out_len, max_len, data,
+ data_len);
+ }
+
+ // Otherwise, the user has an older version of NSS. Regrettably, NSS 3.14.x
+ // has a bug in the AES GCM code
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=853285), as well as missing
+ // the PK11_Encrypt function
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=854063), both of which are
+ // resolved in NSS 3.15.
+
+ DCHECK_EQ(mechanism, static_cast<CK_MECHANISM_TYPE>(CKM_AES_GCM));
+ DCHECK_EQ(param->len, sizeof(CK_GCM_PARAMS));
+
+ if (max_len < static_cast<unsigned int>(Aes128Gcm12Encrypter::kAuthTagSize)) {
+ DLOG(INFO) << "max_len is less than kAuthTagSize";
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+
+ const CK_GCM_PARAMS* gcm_params =
+ reinterpret_cast<CK_GCM_PARAMS*>(param->data);
+
+ DCHECK_EQ(gcm_params->ulTagBits,
+ static_cast<CK_ULONG>(Aes128Gcm12Encrypter::kAuthTagSize * 8));
+ if (gcm_params->ulIvLen != 12u) {
+ DLOG(INFO) << "ulIvLen is not equal to 12";
+ PORT_SetError(SEC_ERROR_INPUT_LEN);
+ return SECFailure;
+ }
+
+ SECItem my_param = { siBuffer, NULL, 0 };
+
+ // Step 1. Let H = CIPH_K(128 '0' bits).
+ unsigned char ghash_key[16] = {0};
+ crypto::ScopedPK11Context ctx(PK11_CreateContextBySymKey(
+ CKM_AES_ECB, CKA_ENCRYPT, key, &my_param));
+ if (!ctx) {
+ DLOG(INFO) << "PK11_CreateContextBySymKey failed";
+ return SECFailure;
+ }
+ int output_len;
+ if (PK11_CipherOp(ctx.get(), ghash_key, &output_len, sizeof(ghash_key),
+ ghash_key, sizeof(ghash_key)) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+
+ PK11_Finalize(ctx.get());
+
+ if (output_len != sizeof(ghash_key)) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ // Step 2. If len(IV)=96, then let J0 = IV || 31 '0' bits || 1.
+ CK_AES_CTR_PARAMS ctr_params = {0};
+ ctr_params.ulCounterBits = 32;
+ memcpy(ctr_params.cb, gcm_params->pIv, gcm_params->ulIvLen);
+ ctr_params.cb[12] = 0;
+ ctr_params.cb[13] = 0;
+ ctr_params.cb[14] = 0;
+ ctr_params.cb[15] = 1;
+
+ my_param.type = siBuffer;
+ my_param.data = reinterpret_cast<unsigned char*>(&ctr_params);
+ my_param.len = sizeof(ctr_params);
+
+ ctx.reset(PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, key,
+ &my_param));
+ if (!ctx) {
+ DLOG(INFO) << "PK11_CreateContextBySymKey failed";
+ return SECFailure;
+ }
+
+ // Step 6. Calculate the encryption mask of GCTR_K(J0, ...).
+ unsigned char tag_mask[16] = {0};
+ if (PK11_CipherOp(ctx.get(), tag_mask, &output_len, sizeof(tag_mask),
+ tag_mask, sizeof(tag_mask)) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+ if (output_len != sizeof(tag_mask)) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ // The const_cast for |data| can be removed if system NSS libraries are
+ // NSS 3.14.1 or later (NSS bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=808218).
+ if (PK11_CipherOp(ctx.get(), out, &output_len, max_len,
+ const_cast<unsigned char*>(data), data_len) != SECSuccess) {
+ DLOG(INFO) << "PK11_CipherOp failed";
+ return SECFailure;
+ }
+
+ PK11_Finalize(ctx.get());
+
+ if (static_cast<unsigned int>(output_len) != data_len) {
+ DLOG(INFO) << "Wrong output length";
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ if ((max_len - Aes128Gcm12Encrypter::kAuthTagSize) <
+ static_cast<unsigned int>(output_len)) {
+ DLOG(INFO) << "(max_len - kAuthTagSize) is less than output_len";
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+
+ crypto::GaloisHash ghash(ghash_key);
+ ghash.UpdateAdditional(gcm_params->pAAD, gcm_params->ulAADLen);
+ ghash.UpdateCiphertext(out, output_len);
+ ghash.Finish(out + output_len, Aes128Gcm12Encrypter::kAuthTagSize);
+ for (unsigned int i = 0; i < Aes128Gcm12Encrypter::kAuthTagSize; i++) {
+ out[output_len + i] ^= tag_mask[i];
+ }
+
+ *out_len = output_len + Aes128Gcm12Encrypter::kAuthTagSize;
+ return SECSuccess;
+}
+
+} // namespace
+
+Aes128Gcm12Encrypter::Aes128Gcm12Encrypter() : last_seq_num_(0) {
+ ignore_result(g_gcm_support_checker.Get());
+}
+
+Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {}
+
+// static
+bool Aes128Gcm12Encrypter::IsSupported() {
+ // NSS 3.15 supports CKM_AES_GCM directly.
+ // NSS 3.14 supports CKM_AES_CTR, which can be used to emulate CKM_AES_GCM.
+ // Versions earlier than NSS 3.14 are not supported.
+ return NSS_VersionCheck("3.14") != PR_FALSE;
+}
+
+bool Aes128Gcm12Encrypter::SetKey(StringPiece key) {
+ DCHECK_EQ(key.size(), sizeof(key_));
+ if (key.size() != sizeof(key_)) {
+ return false;
+ }
+ memcpy(key_, key.data(), key.size());
+ return true;
+}
+
+bool Aes128Gcm12Encrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ DCHECK_EQ(nonce_prefix.size(), kNoncePrefixSize);
+ if (nonce_prefix.size() != kNoncePrefixSize) {
+ return false;
+ }
+ COMPILE_ASSERT(sizeof(nonce_prefix_) == kNoncePrefixSize, bad_nonce_length);
+ memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+ return true;
+}
+
+bool Aes128Gcm12Encrypter::Encrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece plaintext,
+ unsigned char* output) {
+ if (nonce.size() != kNoncePrefixSize + sizeof(QuicPacketSequenceNumber)) {
+ return false;
+ }
+
+ size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+
+ // Import key_ into NSS.
+ SECItem key_item;
+ key_item.type = siBuffer;
+ key_item.data = key_;
+ key_item.len = sizeof(key_);
+ PK11SlotInfo* slot = PK11_GetInternalSlot();
+ // The exact value of the |origin| argument doesn't matter to NSS as long as
+ // it's not PK11_OriginFortezzaHack, so we pass PK11_OriginUnwrap as a
+ // placeholder.
+ crypto::ScopedPK11SymKey aes_key(PK11_ImportSymKey(
+ slot, GcmSupportChecker::aes_key_mechanism(), PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item, NULL));
+ PK11_FreeSlot(slot);
+ slot = NULL;
+ if (!aes_key) {
+ DLOG(INFO) << "PK11_ImportSymKey failed";
+ return false;
+ }
+
+ CK_GCM_PARAMS gcm_params = {0};
+ gcm_params.pIv =
+ reinterpret_cast<CK_BYTE*>(const_cast<char*>(nonce.data()));
+ gcm_params.ulIvLen = nonce.size();
+ gcm_params.pAAD =
+ reinterpret_cast<CK_BYTE*>(const_cast<char*>(associated_data.data()));
+ gcm_params.ulAADLen = associated_data.size();
+ gcm_params.ulTagBits = kAuthTagSize * 8;
+
+ SECItem param;
+ param.type = siBuffer;
+ param.data = reinterpret_cast<unsigned char*>(&gcm_params);
+ param.len = sizeof(gcm_params);
+
+ unsigned int output_len;
+ if (My_Encrypt(aes_key.get(), CKM_AES_GCM, &param,
+ output, &output_len, ciphertext_size,
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
+ plaintext.size()) != SECSuccess) {
+ DLOG(INFO) << "My_Encrypt failed";
+ return false;
+ }
+
+ if (output_len != ciphertext_size) {
+ DLOG(INFO) << "Wrong output length";
+ return false;
+ }
+
+ return true;
+}
+
+QuicData* Aes128Gcm12Encrypter::EncryptPacket(
+ QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece plaintext) {
+ size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+ scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+ if (last_seq_num_ != 0 && sequence_number <= last_seq_num_) {
+ DLOG(FATAL) << "Sequence numbers regressed";
+ return NULL;
+ }
+ last_seq_num_ = sequence_number;
+
+ uint8 nonce[kNoncePrefixSize + sizeof(sequence_number)];
+ COMPILE_ASSERT(sizeof(nonce) == kAESNonceSize, bad_sequence_number_size);
+ memcpy(nonce, nonce_prefix_, kNoncePrefixSize);
+ memcpy(nonce + kNoncePrefixSize, &sequence_number, sizeof(sequence_number));
+ if (!Encrypt(StringPiece(reinterpret_cast<char*>(nonce), sizeof(nonce)),
+ associated_data, plaintext,
+ reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+ return NULL;
+ }
+
+ return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+size_t Aes128Gcm12Encrypter::GetKeySize() const { return kKeySize; }
+
+size_t Aes128Gcm12Encrypter::GetNoncePrefixSize() const {
+ return kNoncePrefixSize;
+}
+
+size_t Aes128Gcm12Encrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+ return ciphertext_size - kAuthTagSize;
+}
+
+// An AEAD_AES_128_GCM_12 ciphertext is exactly 12 bytes longer than its
+// corresponding plaintext.
+size_t Aes128Gcm12Encrypter::GetCiphertextSize(size_t plaintext_size) const {
+ return plaintext_size + kAuthTagSize;
+}
+
+StringPiece Aes128Gcm12Encrypter::GetKey() const {
+ return StringPiece(reinterpret_cast<const char*>(key_), sizeof(key_));
+}
+
+StringPiece Aes128Gcm12Encrypter::GetNoncePrefix() const {
+ return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+ kNoncePrefixSize);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc
new file mode 100644
index 00000000000..79d0ec1a8a0
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_openssl.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include <openssl/evp.h>
+#include <string.h>
+
+#include "base/memory/scoped_ptr.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const size_t kKeySize = 16;
+const size_t kNoncePrefixSize = 4;
+const size_t kAESNonceSize = 12;
+
+} // namespace
+
+Aes128Gcm12Encrypter::Aes128Gcm12Encrypter() : last_seq_num_(0) {}
+
+Aes128Gcm12Encrypter::~Aes128Gcm12Encrypter() {}
+
+// static
+bool Aes128Gcm12Encrypter::IsSupported() { return true; }
+
+bool Aes128Gcm12Encrypter::SetKey(StringPiece key) {
+ DCHECK_EQ(key.size(), sizeof(key_));
+ if (key.size() != sizeof(key_)) {
+ return false;
+ }
+ memcpy(key_, key.data(), key.size());
+
+ // Set the cipher type and the key.
+ if (EVP_EncryptInit_ex(ctx_.get(), EVP_aes_128_gcm(), NULL, key_,
+ NULL) == 0) {
+ return false;
+ }
+
+ // Set the IV (nonce) length.
+ if (EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_GCM_SET_IVLEN, kAESNonceSize,
+ NULL) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Aes128Gcm12Encrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ DCHECK_EQ(nonce_prefix.size(), kNoncePrefixSize);
+ if (nonce_prefix.size() != kNoncePrefixSize) {
+ return false;
+ }
+ COMPILE_ASSERT(sizeof(nonce_prefix_) == kNoncePrefixSize, bad_nonce_length);
+ memcpy(nonce_prefix_, nonce_prefix.data(), nonce_prefix.size());
+ return true;
+}
+
+bool Aes128Gcm12Encrypter::Encrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece plaintext,
+ unsigned char* output) {
+ if (nonce.size() != kNoncePrefixSize + sizeof(QuicPacketSequenceNumber)) {
+ return false;
+ }
+
+ // Set the IV (nonce).
+ if (EVP_EncryptInit_ex(
+ ctx_.get(), NULL, NULL, NULL,
+ reinterpret_cast<const unsigned char*>(nonce.data())) == 0) {
+ return false;
+ }
+
+ // If we pass a NULL, zero-length associated data to OpenSSL then it breaks.
+ // Thus we only set non-empty associated data.
+ if (!associated_data.empty()) {
+ // Set the associated data. The second argument (output buffer) must be
+ // NULL.
+ int unused_len;
+ if (EVP_EncryptUpdate(
+ ctx_.get(), NULL, &unused_len,
+ reinterpret_cast<const unsigned char*>(associated_data.data()),
+ associated_data.size()) == 0) {
+ return false;
+ }
+ }
+
+ int len;
+ if (EVP_EncryptUpdate(
+ ctx_.get(), output, &len,
+ reinterpret_cast<const unsigned char*>(plaintext.data()),
+ plaintext.size()) == 0) {
+ return false;
+ }
+ output += len;
+
+ if (EVP_EncryptFinal_ex(ctx_.get(), output, &len) == 0) {
+ return false;
+ }
+ output += len;
+
+ if (EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_GCM_GET_TAG, kAuthTagSize,
+ output) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+QuicData* Aes128Gcm12Encrypter::EncryptPacket(
+ QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece plaintext) {
+ size_t ciphertext_size = GetCiphertextSize(plaintext.length());
+ scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+ if (last_seq_num_ != 0 && sequence_number <= last_seq_num_) {
+ DLOG(FATAL) << "Sequence numbers regressed";
+ return NULL;
+ }
+ last_seq_num_ = sequence_number;
+
+ uint8 nonce[kNoncePrefixSize + sizeof(sequence_number)];
+ COMPILE_ASSERT(sizeof(nonce) == kAESNonceSize, bad_sequence_number_size);
+ memcpy(nonce, nonce_prefix_, kNoncePrefixSize);
+ memcpy(nonce + kNoncePrefixSize, &sequence_number, sizeof(sequence_number));
+ if (!Encrypt(StringPiece(reinterpret_cast<char*>(nonce), sizeof(nonce)),
+ associated_data, plaintext,
+ reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+ return NULL;
+ }
+
+ return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+size_t Aes128Gcm12Encrypter::GetKeySize() const { return kKeySize; }
+
+size_t Aes128Gcm12Encrypter::GetNoncePrefixSize() const {
+ return kNoncePrefixSize;
+}
+
+size_t Aes128Gcm12Encrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+ return ciphertext_size - kAuthTagSize;
+}
+
+// An AEAD_AES_128_GCM_12 ciphertext is exactly 12 bytes longer than its
+// corresponding plaintext.
+size_t Aes128Gcm12Encrypter::GetCiphertextSize(size_t plaintext_size) const {
+ return plaintext_size + kAuthTagSize;
+}
+
+StringPiece Aes128Gcm12Encrypter::GetKey() const {
+ return StringPiece(reinterpret_cast<const char*>(key_), sizeof(key_));
+}
+
+StringPiece Aes128Gcm12Encrypter::GetNoncePrefix() const {
+ return StringPiece(reinterpret_cast<const char*>(nonce_prefix_),
+ kNoncePrefixSize);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc
new file mode 100644
index 00000000000..0c9928bc570
--- /dev/null
+++ b/chromium/net/quic/crypto/aes_128_gcm_12_encrypter_test.cc
@@ -0,0 +1,348 @@
+// Copyright (c) 2013 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/quic/crypto/aes_128_gcm_12_encrypter.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace {
+
+// The AES GCM test vectors come from the file gcmEncryptExtIV128.rsp
+// downloaded from http://csrc.nist.gov/groups/STM/cavp/index.html on
+// 2013-02-01. The test vectors in that file look like this:
+//
+// [Keylen = 128]
+// [IVlen = 96]
+// [PTlen = 0]
+// [AADlen = 0]
+// [Taglen = 128]
+//
+// Count = 0
+// Key = 11754cd72aec309bf52f7687212e8957
+// IV = 3c819d9a9bed087615030b65
+// PT =
+// AAD =
+// CT =
+// Tag = 250327c674aaf477aef2675748cf6971
+//
+// Count = 1
+// Key = ca47248ac0b6f8372a97ac43508308ed
+// IV = ffd2b598feabc9019262d2be
+// PT =
+// AAD =
+// CT =
+// Tag = 60d20404af527d248d893ae495707d1a
+//
+// ...
+//
+// The gcmEncryptExtIV128.rsp file is huge (2.8 MB), so I selected just a
+// few test vectors for this unit test.
+
+// Describes a group of test vectors that all have a given key length, IV
+// length, plaintext length, AAD length, and tag length.
+struct TestGroupInfo {
+ size_t key_len;
+ size_t iv_len;
+ size_t pt_len;
+ size_t aad_len;
+ size_t tag_len;
+};
+
+// Each test vector consists of six strings of lowercase hexadecimal digits.
+// The strings may be empty (zero length). A test vector with a NULL |key|
+// marks the end of an array of test vectors.
+struct TestVector {
+ const char* key;
+ const char* iv;
+ const char* pt;
+ const char* aad;
+ const char* ct;
+ const char* tag;
+};
+
+const TestGroupInfo test_group_info[] = {
+ { 128, 96, 0, 0, 128 },
+ { 128, 96, 0, 128, 128 },
+ { 128, 96, 128, 0, 128 },
+ { 128, 96, 408, 160, 128 },
+ { 128, 96, 408, 720, 128 },
+ { 128, 96, 104, 0, 128 },
+};
+
+const TestVector test_group_0[] = {
+ { "11754cd72aec309bf52f7687212e8957",
+ "3c819d9a9bed087615030b65",
+ "",
+ "",
+ "",
+ "250327c674aaf477aef2675748cf6971"
+ },
+ { "ca47248ac0b6f8372a97ac43508308ed",
+ "ffd2b598feabc9019262d2be",
+ "",
+ "",
+ "",
+ "60d20404af527d248d893ae495707d1a"
+ },
+ { NULL }
+};
+
+const TestVector test_group_1[] = {
+ { "77be63708971c4e240d1cb79e8d77feb",
+ "e0e00f19fed7ba0136a797f3",
+ "",
+ "7a43ec1d9c0a5a78a0b16533a6213cab",
+ "",
+ "209fcc8d3675ed938e9c7166709dd946"
+ },
+ { "7680c5d3ca6154758e510f4d25b98820",
+ "f8f105f9c3df4965780321f8",
+ "",
+ "c94c410194c765e3dcc7964379758ed3",
+ "",
+ "94dca8edfcf90bb74b153c8d48a17930"
+ },
+ { NULL }
+};
+
+const TestVector test_group_2[] = {
+ { "7fddb57453c241d03efbed3ac44e371c",
+ "ee283a3fc75575e33efd4887",
+ "d5de42b461646c255c87bd2962d3b9a2",
+ "",
+ "2ccda4a5415cb91e135c2a0f78c9b2fd",
+ "b36d1df9b9d5e596f83e8b7f52971cb3"
+ },
+ { "ab72c77b97cb5fe9a382d9fe81ffdbed",
+ "54cc7dc2c37ec006bcc6d1da",
+ "007c5e5b3e59df24a7c355584fc1518d",
+ "",
+ "0e1bde206a07a9c2c1b65300f8c64997",
+ "2b4401346697138c7a4891ee59867d0c"
+ },
+ { NULL }
+};
+
+const TestVector test_group_3[] = {
+ { "fe47fcce5fc32665d2ae399e4eec72ba",
+ "5adb9609dbaeb58cbd6e7275",
+ "7c0e88c88899a779228465074797cd4c2e1498d259b54390b85e3eef1c02df60e743f1"
+ "b840382c4bccaf3bafb4ca8429bea063",
+ "88319d6e1d3ffa5f987199166c8a9b56c2aeba5a",
+ "98f4826f05a265e6dd2be82db241c0fbbbf9ffb1c173aa83964b7cf539304373636525"
+ "3ddbc5db8778371495da76d269e5db3e",
+ "291ef1982e4defedaa2249f898556b47"
+ },
+ { "ec0c2ba17aa95cd6afffe949da9cc3a8",
+ "296bce5b50b7d66096d627ef",
+ "b85b3753535b825cbe5f632c0b843c741351f18aa484281aebec2f45bb9eea2d79d987"
+ "b764b9611f6c0f8641843d5d58f3a242",
+ "f8d00f05d22bf68599bcdeb131292ad6e2df5d14",
+ "a7443d31c26bdf2a1c945e29ee4bd344a99cfaf3aa71f8b3f191f83c2adfc7a0716299"
+ "5506fde6309ffc19e716eddf1a828c5a",
+ "890147971946b627c40016da1ecf3e77"
+ },
+ { NULL }
+};
+
+const TestVector test_group_4[] = {
+ { "2c1f21cf0f6fb3661943155c3e3d8492",
+ "23cb5ff362e22426984d1907",
+ "42f758836986954db44bf37c6ef5e4ac0adaf38f27252a1b82d02ea949c8a1a2dbc0d6"
+ "8b5615ba7c1220ff6510e259f06655d8",
+ "5d3624879d35e46849953e45a32a624d6a6c536ed9857c613b572b0333e701557a713e"
+ "3f010ecdf9a6bd6c9e3e44b065208645aff4aabee611b391528514170084ccf587177f"
+ "4488f33cfb5e979e42b6e1cfc0a60238982a7aec",
+ "81824f0e0d523db30d3da369fdc0d60894c7a0a20646dd015073ad2732bd989b14a222"
+ "b6ad57af43e1895df9dca2a5344a62cc",
+ "57a3ee28136e94c74838997ae9823f3a"
+ },
+ { "d9f7d2411091f947b4d6f1e2d1f0fb2e",
+ "e1934f5db57cc983e6b180e7",
+ "73ed042327f70fe9c572a61545eda8b2a0c6e1d6c291ef19248e973aee6c312012f490"
+ "c2c6f6166f4a59431e182663fcaea05a",
+ "0a8a18a7150e940c3d87b38e73baee9a5c049ee21795663e264b694a949822b639092d"
+ "0e67015e86363583fcf0ca645af9f43375f05fdb4ce84f411dcbca73c2220dea03a201"
+ "15d2e51398344b16bee1ed7c499b353d6c597af8",
+ "aaadbd5c92e9151ce3db7210b8714126b73e43436d242677afa50384f2149b831f1d57"
+ "3c7891c2a91fbc48db29967ec9542b23",
+ "21b51ca862cb637cdd03b99a0f93b134"
+ },
+ { NULL }
+};
+
+const TestVector test_group_5[] = {
+ { "fe9bb47deb3a61e423c2231841cfd1fb",
+ "4d328eb776f500a2f7fb47aa",
+ "f1cc3818e421876bb6b8bbd6c9",
+ "",
+ "b88c5c1977b35b517b0aeae967",
+ "43fd4727fe5cdb4b5b42818dea7ef8c9"
+ },
+ { "6703df3701a7f54911ca72e24dca046a",
+ "12823ab601c350ea4bc2488c",
+ "793cd125b0b84a043e3ac67717",
+ "",
+ "b2051c80014f42f08735a7b0cd",
+ "38e6bcd29962e5f2c13626b85a877101"
+ },
+ { NULL }
+};
+
+const TestVector* const test_group_array[] = {
+ test_group_0,
+ test_group_1,
+ test_group_2,
+ test_group_3,
+ test_group_4,
+ test_group_5,
+};
+
+// Returns true if |ch| is a lowercase hexadecimal digit.
+bool IsHexDigit(char ch) {
+ return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f');
+}
+
+// Converts a lowercase hexadecimal digit to its integer value.
+int HexDigitToInt(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ }
+ return ch - 'a' + 10;
+}
+
+// |in| is a string consisting of lowercase hexadecimal digits, where
+// every two digits represent one byte. |out| is a buffer of size |max_len|.
+// Converts |in| to bytes and stores the bytes in the |out| buffer. The
+// number of bytes converted is returned in |*out_len|. Returns true on
+// success, false on failure.
+bool DecodeHexString(const char* in,
+ char* out,
+ size_t* out_len,
+ size_t max_len) {
+ *out_len = 0;
+ while (*in != '\0') {
+ if (!IsHexDigit(*in) || !IsHexDigit(*(in + 1))) {
+ return false;
+ }
+ if (*out_len >= max_len) {
+ return false;
+ }
+ out[*out_len] = HexDigitToInt(*in) * 16 + HexDigitToInt(*(in + 1));
+ (*out_len)++;
+ in += 2;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace net {
+namespace test {
+
+// EncryptWithNonce wraps the |Encrypt| method of |encrypter| to allow passing
+// in an nonce and also to allocate the buffer needed for the ciphertext.
+QuicData* EncryptWithNonce(Aes128Gcm12Encrypter* encrypter,
+ StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece plaintext) {
+ size_t ciphertext_size = encrypter->GetCiphertextSize(plaintext.length());
+ scoped_ptr<char[]> ciphertext(new char[ciphertext_size]);
+
+ if (!encrypter->Encrypt(nonce, associated_data, plaintext,
+ reinterpret_cast<unsigned char*>(ciphertext.get()))) {
+ return NULL;
+ }
+
+ return new QuicData(ciphertext.release(), ciphertext_size, true);
+}
+
+TEST(Aes128Gcm12EncrypterTest, Encrypt) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ char key[1024];
+ size_t key_len;
+ char iv[1024];
+ size_t iv_len;
+ char pt[1024];
+ size_t pt_len;
+ char aad[1024];
+ size_t aad_len;
+ char ct[1024];
+ size_t ct_len;
+ char tag[1024];
+ size_t tag_len;
+
+ for (size_t i = 0; i < arraysize(test_group_array); i++) {
+ SCOPED_TRACE(i);
+ const TestVector* test_vector = test_group_array[i];
+ const TestGroupInfo& test_info = test_group_info[i];
+ for (size_t j = 0; test_vector[j].key != NULL; j++) {
+ // Decode the test vector.
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].key, key, &key_len, sizeof(key)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].iv, iv, &iv_len, sizeof(iv)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].pt, pt, &pt_len, sizeof(pt)));
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].aad, aad, &aad_len, sizeof(aad)));
+ ASSERT_TRUE(DecodeHexString(test_vector[j].ct, ct, &ct_len, sizeof(ct)));
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[j].tag, tag, &tag_len, sizeof(tag)));
+
+ // The test vector's lengths should look sane. Note that the lengths
+ // in |test_info| are in bits.
+ EXPECT_EQ(test_info.key_len, key_len * 8);
+ EXPECT_EQ(test_info.iv_len, iv_len * 8);
+ EXPECT_EQ(test_info.pt_len, pt_len * 8);
+ EXPECT_EQ(test_info.aad_len, aad_len * 8);
+ EXPECT_EQ(test_info.pt_len, ct_len * 8);
+ EXPECT_EQ(test_info.tag_len, tag_len * 8);
+
+ Aes128Gcm12Encrypter encrypter;
+ ASSERT_TRUE(encrypter.SetKey(StringPiece(key, key_len)));
+ scoped_ptr<QuicData> encrypted(EncryptWithNonce(
+ &encrypter, StringPiece(iv, iv_len),
+ // OpenSSL fails if NULL is set as the AAD, as opposed to a
+ // zero-length, non-NULL pointer. This deliberately tests that we
+ // handle this case.
+ StringPiece(aad_len ? aad : NULL, aad_len), StringPiece(pt, pt_len)));
+ ASSERT_TRUE(encrypted.get());
+
+ // The test vectors have 16 byte authenticators but this code only uses
+ // the first 12.
+ ASSERT_LE(static_cast<size_t>(Aes128Gcm12Encrypter::kAuthTagSize),
+ tag_len);
+ tag_len = Aes128Gcm12Encrypter::kAuthTagSize;
+
+ ASSERT_EQ(ct_len + tag_len, encrypted->length());
+ test::CompareCharArraysWithHexError("ciphertext", encrypted->data(),
+ ct_len, ct, ct_len);
+ test::CompareCharArraysWithHexError(
+ "authentication tag", encrypted->data() + ct_len, tag_len, tag,
+ tag_len);
+ }
+ }
+}
+
+TEST(Aes128Gcm12EncrypterTest, GetMaxPlaintextSize) {
+ Aes128Gcm12Encrypter encrypter;
+ EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1012));
+ EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(112));
+ EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(22));
+}
+
+TEST(Aes128Gcm12EncrypterTest, GetCiphertextSize) {
+ Aes128Gcm12Encrypter encrypter;
+ EXPECT_EQ(1012u, encrypter.GetCiphertextSize(1000));
+ EXPECT_EQ(112u, encrypter.GetCiphertextSize(100));
+ EXPECT_EQ(22u, encrypter.GetCiphertextSize(10));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/cert_compressor.cc b/chromium/net/quic/crypto/cert_compressor.cc
new file mode 100644
index 00000000000..023701bff95
--- /dev/null
+++ b/chromium/net/quic/crypto/cert_compressor.cc
@@ -0,0 +1,646 @@
+// Copyright (c) 2013 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/quic/crypto/cert_compressor.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_utils.h"
+#include "third_party/zlib/zlib.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// kCommonCertSubstrings contains ~1500 bytes of common certificate substrings
+// in order to help zlib. This was generated via a fairly dumb algorithm from
+// the Alexa Top 5000 set - we could probably do better.
+static const unsigned char kCommonCertSubstrings[] = {
+ 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+ 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03,
+ 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30,
+ 0x5f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01,
+ 0x06, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6d, 0x01, 0x07,
+ 0x17, 0x01, 0x30, 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+ 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x53, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x34,
+ 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31,
+ 0x32, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x2d, 0x61, 0x69, 0x61, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x45, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x2e, 0x63, 0x65,
+ 0x72, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4a, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
+ 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x1d, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x7b, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd2,
+ 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0xb4, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x30, 0x0b, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30,
+ 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74,
+ 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33,
+ 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53,
+ 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55,
+ 0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37,
+ 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x0c,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00,
+ 0x30, 0x1d, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86,
+ 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e,
+ 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x67, 0x64, 0x73, 0x31, 0x2d, 0x32, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74,
+ 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65,
+ 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+ 0x70, 0x73, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17,
+ 0x0d, 0x31, 0x33, 0x30, 0x35, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x73, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x45, 0x01, 0x07, 0x17, 0x06, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x53, 0x31, 0x17,
+ 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30, 0x39,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d, 0x73,
+ 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20, 0x68,
+ 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76,
+ 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x31, 0x13, 0x30, 0x11,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x47, 0x31, 0x13, 0x30, 0x11,
+ 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01,
+ 0x03, 0x13, 0x02, 0x55, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x14, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0f, 0x13, 0x14, 0x50,
+ 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e,
+ 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x12, 0x31, 0x21, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x44, 0x6f, 0x6d, 0x61,
+ 0x69, 0x6e, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x31, 0x14, 0x31, 0x31,
+ 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x53, 0x65, 0x65,
+ 0x20, 0x77, 0x77, 0x77, 0x2e, 0x72, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x2e, 0x67, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+ 0x69, 0x67, 0x6e, 0x31, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+ 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x45, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x64, 0x31, 0x1a,
+ 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x45, 0x56, 0x49, 0x6e, 0x74, 0x6c, 0x2d, 0x63, 0x63, 0x72,
+ 0x74, 0x2e, 0x67, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x69, 0x63, 0x65, 0x72,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x6f, 0x63, 0x73, 0x70, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x30, 0x39, 0x72, 0x61, 0x70, 0x69, 0x64, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x2f, 0x30, 0x81, 0x80, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x01, 0x01, 0x04, 0x74, 0x30, 0x72, 0x30, 0x24, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64,
+ 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x4a, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64,
+ 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+ 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74,
+ 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72,
+ 0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee,
+ 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x27,
+ 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x86, 0x30,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73,
+};
+
+// CertEntry represents a certificate in compressed form. Each entry is one of
+// the three types enumerated in |Type|.
+struct CertEntry {
+ public:
+ enum Type {
+ // Type 0 is reserved to mean "end of list" in the wire format.
+
+ // COMPRESSED means that the certificate is included in the trailing zlib
+ // data.
+ COMPRESSED = 1,
+ // CACHED means that the certificate is already known to the peer and will
+ // be replaced by its 64-bit hash (in |hash|).
+ CACHED = 2,
+ // COMMON means that the certificate is in a common certificate set known
+ // to the peer with hash |set_hash| and certificate index |index|.
+ COMMON = 3,
+ };
+
+ Type type;
+ uint64 hash;
+ uint64 set_hash;
+ uint32 index;
+};
+
+// MatchCerts returns a vector of CertEntries describing how to most
+// efficiently represent |certs| to a peer who has the common sets identified
+// by |client_common_set_hashes| and who has cached the certificates with the
+// 64-bit, FNV-1a hashes in |client_cached_cert_hashes|.
+vector<CertEntry> MatchCerts(const vector<string>& certs,
+ StringPiece client_common_set_hashes,
+ StringPiece client_cached_cert_hashes,
+ const CommonCertSets* common_sets) {
+ vector<CertEntry> entries;
+ entries.reserve(certs.size());
+
+ const bool cached_valid =
+ client_cached_cert_hashes.size() % sizeof(uint64) == 0 &&
+ !client_cached_cert_hashes.empty();
+
+ for (vector<string>::const_iterator i = certs.begin();
+ i != certs.end(); ++i) {
+ CertEntry entry;
+
+ if (cached_valid) {
+ bool cached = false;
+
+ uint64 hash = QuicUtils::FNV1a_64_Hash(i->data(), i->size());
+ // This assumes that the machine is little-endian.
+ for (size_t i = 0; i < client_cached_cert_hashes.size();
+ i += sizeof(uint64)) {
+ uint64 cached_hash;
+ memcpy(&cached_hash, client_cached_cert_hashes.data() + i,
+ sizeof(uint64));
+ if (hash != cached_hash) {
+ continue;
+ }
+
+ entry.type = CertEntry::CACHED;
+ entry.hash = hash;
+ entries.push_back(entry);
+ cached = true;
+ break;
+ }
+
+ if (cached) {
+ continue;
+ }
+ }
+
+ if (common_sets && common_sets->MatchCert(*i, client_common_set_hashes,
+ &entry.set_hash, &entry.index)) {
+ entry.type = CertEntry::COMMON;
+ entries.push_back(entry);
+ continue;
+ }
+
+ entry.type = CertEntry::COMPRESSED;
+ entries.push_back(entry);
+ }
+
+ return entries;
+}
+
+// CertEntriesSize returns the size, in bytes, of the serialised form of
+// |entries|.
+size_t CertEntriesSize(const vector<CertEntry>& entries) {
+ size_t entries_size = 0;
+
+ for (vector<CertEntry>::const_iterator i = entries.begin();
+ i != entries.end(); ++i) {
+ entries_size++;
+ switch (i->type) {
+ case CertEntry::COMPRESSED:
+ break;
+ case CertEntry::CACHED:
+ entries_size += sizeof(uint64);
+ break;
+ case CertEntry::COMMON:
+ entries_size += sizeof(uint64) + sizeof(uint32);
+ break;
+ }
+ }
+
+ entries_size++; // for end marker
+
+ return entries_size;
+}
+
+// SerializeCertEntries serialises |entries| to |out|, which must have enough
+// space to contain them.
+void SerializeCertEntries(uint8* out, const vector<CertEntry>& entries) {
+ for (vector<CertEntry>::const_iterator i = entries.begin();
+ i != entries.end(); ++i) {
+ *out++ = i->type;
+ switch (i->type) {
+ case CertEntry::COMPRESSED:
+ break;
+ case CertEntry::CACHED:
+ memcpy(out, &i->hash, sizeof(i->hash));
+ out += sizeof(uint64);
+ break;
+ case CertEntry::COMMON:
+ // Assumes a little-endian machine.
+ memcpy(out, &i->set_hash, sizeof(i->set_hash));
+ out += sizeof(i->set_hash);
+ memcpy(out, &i->index, sizeof(uint32));
+ out += sizeof(uint32);
+ break;
+ }
+ }
+
+ *out++ = 0; // end marker
+}
+
+// ZlibDictForEntries returns a string that contains the zlib pre-shared
+// dictionary to use in order to decompress a zlib block following |entries|.
+// |certs| is one-to-one with |entries| and contains the certificates for those
+// entries that are CACHED or COMMON.
+string ZlibDictForEntries(const vector<CertEntry>& entries,
+ const vector<string>& certs) {
+ string zlib_dict;
+
+ // The dictionary starts with the common and cached certs in reverse order.
+ size_t zlib_dict_size = 0;
+ for (size_t i = certs.size() - 1; i < certs.size(); i--) {
+ if (entries[i].type != CertEntry::COMPRESSED) {
+ zlib_dict_size += certs[i].size();
+ }
+ }
+
+ // At the end of the dictionary is a block of common certificate substrings.
+ zlib_dict_size += sizeof(kCommonCertSubstrings);
+
+ zlib_dict.reserve(zlib_dict_size);
+
+ for (size_t i = certs.size() - 1; i < certs.size(); i--) {
+ if (entries[i].type != CertEntry::COMPRESSED) {
+ zlib_dict += certs[i];
+ }
+ }
+
+ zlib_dict += string(reinterpret_cast<const char*>(kCommonCertSubstrings),
+ sizeof(kCommonCertSubstrings));
+
+ DCHECK_EQ(zlib_dict.size(), zlib_dict_size);
+
+ return zlib_dict;
+}
+
+// HashCerts returns the FNV-1a hashes of |certs|.
+vector<uint64> HashCerts(const vector<string>& certs) {
+ vector<uint64> ret;
+ ret.reserve(certs.size());
+
+ for (vector<string>::const_iterator i = certs.begin();
+ i != certs.end(); ++i) {
+ ret.push_back(QuicUtils::FNV1a_64_Hash(i->data(), i->size()));
+ }
+
+ return ret;
+}
+
+// ParseEntries parses the serialised form of a vector of CertEntries from
+// |in_out| and writes them to |out_entries|. CACHED and COMMON entries are
+// resolved using |cached_certs| and |common_sets| and written to |out_certs|.
+// |in_out| is updated to contain the trailing data.
+bool ParseEntries(StringPiece* in_out,
+ const vector<string>& cached_certs,
+ const CommonCertSets* common_sets,
+ vector<CertEntry>* out_entries,
+ vector<string>* out_certs) {
+ StringPiece in = *in_out;
+ vector<uint64> cached_hashes;
+
+ out_entries->clear();
+ out_certs->clear();
+
+ for (;;) {
+ if (in.empty()) {
+ return false;
+ }
+ CertEntry entry;
+ const uint8 type_byte = in[0];
+ in.remove_prefix(1);
+
+ if (type_byte == 0) {
+ break;
+ }
+
+ entry.type = static_cast<CertEntry::Type>(type_byte);
+
+ switch (entry.type) {
+ case CertEntry::COMPRESSED:
+ out_certs->push_back(string());
+ break;
+ case CertEntry::CACHED: {
+ if (in.size() < sizeof(uint64)) {
+ return false;
+ }
+ memcpy(&entry.hash, in.data(), sizeof(uint64));
+ in.remove_prefix(sizeof(uint64));
+
+ if (cached_hashes.size() != cached_certs.size()) {
+ cached_hashes = HashCerts(cached_certs);
+ }
+ bool found = false;
+ for (size_t i = 0; i < cached_hashes.size(); i++) {
+ if (cached_hashes[i] == entry.hash) {
+ out_certs->push_back(cached_certs[i]);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ break;
+ }
+ case CertEntry::COMMON: {
+ if (!common_sets) {
+ return false;
+ }
+ if (in.size() < sizeof(uint64) + sizeof(uint32)) {
+ return false;
+ }
+ memcpy(&entry.set_hash, in.data(), sizeof(uint64));
+ in.remove_prefix(sizeof(uint64));
+ memcpy(&entry.index, in.data(), sizeof(uint32));
+ in.remove_prefix(sizeof(uint32));
+
+ StringPiece cert = common_sets->GetCert(entry.set_hash, entry.index);
+ if (cert.empty()) {
+ return false;
+ }
+ out_certs->push_back(cert.as_string());
+ break;
+ }
+ default:
+ return false;
+ }
+ out_entries->push_back(entry);
+ }
+
+ *in_out = in;
+ return true;
+}
+
+// ScopedZLib deals with the automatic destruction of a zlib context.
+class ScopedZLib {
+ public:
+ enum Type {
+ INFLATE,
+ DEFLATE,
+ };
+
+ explicit ScopedZLib(Type type) : z_(NULL), type_(type) {}
+
+ void reset(z_stream* z) {
+ Clear();
+ z_ = z;
+ }
+
+ ~ScopedZLib() {
+ Clear();
+ }
+
+ private:
+ void Clear() {
+ if (!z_) {
+ return;
+ }
+
+ if (type_ == DEFLATE) {
+ deflateEnd(z_);
+ } else {
+ inflateEnd(z_);
+ }
+ z_ = NULL;
+ }
+
+ z_stream* z_;
+ const Type type_;
+};
+
+} // anonymous namespace
+
+
+// static
+string CertCompressor::CompressChain(const vector<string>& certs,
+ StringPiece client_common_set_hashes,
+ StringPiece client_cached_cert_hashes,
+ const CommonCertSets* common_sets) {
+ const vector<CertEntry> entries = MatchCerts(
+ certs, client_common_set_hashes, client_cached_cert_hashes, common_sets);
+ DCHECK_EQ(entries.size(), certs.size());
+
+ size_t uncompressed_size = 0;
+ for (size_t i = 0; i < entries.size(); i++) {
+ if (entries[i].type == CertEntry::COMPRESSED) {
+ uncompressed_size += 4 /* uint32 length */ + certs[i].size();
+ }
+ }
+
+ size_t compressed_size = 0;
+ z_stream z;
+ ScopedZLib scoped_z(ScopedZLib::DEFLATE);
+
+ if (uncompressed_size > 0) {
+ memset(&z, 0, sizeof(z));
+ int rv = deflateInit(&z, Z_DEFAULT_COMPRESSION);
+ DCHECK_EQ(Z_OK, rv);
+ if (rv != Z_OK) {
+ return "";
+ }
+ scoped_z.reset(&z);
+
+ string zlib_dict = ZlibDictForEntries(entries, certs);
+
+ rv = deflateSetDictionary(&z, reinterpret_cast<const uint8*>(&zlib_dict[0]),
+ zlib_dict.size());
+ DCHECK_EQ(Z_OK, rv);
+ if (rv != Z_OK) {
+ return "";
+ }
+
+ compressed_size = deflateBound(&z, uncompressed_size);
+ }
+
+ const size_t entries_size = CertEntriesSize(entries);
+
+ string result;
+ result.resize(entries_size + (uncompressed_size > 0 ? 4 : 0) +
+ compressed_size);
+
+ uint8* j = reinterpret_cast<uint8*>(&result[0]);
+ SerializeCertEntries(j, entries);
+ j += entries_size;
+
+ if (uncompressed_size == 0) {
+ return result;
+ }
+
+ uint32 uncompressed_size_32 = uncompressed_size;
+ memcpy(j, &uncompressed_size_32, sizeof(uint32));
+ j += sizeof(uint32);
+
+ int rv;
+
+ z.next_out = j;
+ z.avail_out = compressed_size;
+
+ for (size_t i = 0; i < certs.size(); i++) {
+ if (entries[i].type != CertEntry::COMPRESSED) {
+ continue;
+ }
+
+ uint32 length32 = certs[i].size();
+ z.next_in = reinterpret_cast<uint8*>(&length32);
+ z.avail_in = sizeof(length32);
+ rv = deflate(&z, Z_NO_FLUSH);
+ DCHECK_EQ(Z_OK, rv);
+ DCHECK_EQ(0u, z.avail_in);
+ if (rv != Z_OK || z.avail_in) {
+ return "";
+ }
+
+ z.next_in =
+ const_cast<uint8*>(reinterpret_cast<const uint8*>(certs[i].data()));
+ z.avail_in = certs[i].size();
+ rv = deflate(&z, Z_NO_FLUSH);
+ DCHECK_EQ(Z_OK, rv);
+ DCHECK_EQ(0u, z.avail_in);
+ if (rv != Z_OK || z.avail_in) {
+ return "";
+ }
+ }
+
+ z.avail_in = 0;
+ rv = deflate(&z, Z_FINISH);
+ DCHECK_EQ(Z_STREAM_END, rv);
+ if (rv != Z_STREAM_END) {
+ return "";
+ }
+
+ result.resize(result.size() - z.avail_out);
+ return result;
+}
+
+// static
+bool CertCompressor::DecompressChain(StringPiece in,
+ const vector<string>& cached_certs,
+ const CommonCertSets* common_sets,
+ vector<string>* out_certs) {
+ vector<CertEntry> entries;
+ if (!ParseEntries(&in, cached_certs, common_sets, &entries, out_certs)) {
+ return false;
+ }
+ DCHECK_EQ(entries.size(), out_certs->size());
+
+ scoped_ptr<uint8[]> uncompressed_data;
+ StringPiece uncompressed;
+
+ if (!in.empty()) {
+ if (in.size() < sizeof(uint32)) {
+ return false;
+ }
+
+ uint32 uncompressed_size;
+ memcpy(&uncompressed_size, in.data(), sizeof(uncompressed_size));
+ in.remove_prefix(sizeof(uint32));
+
+ if (uncompressed_size > 128 * 1024) {
+ return false;
+ }
+
+ uncompressed_data.reset(new uint8[uncompressed_size]);
+ z_stream z;
+ ScopedZLib scoped_z(ScopedZLib::INFLATE);
+
+ memset(&z, 0, sizeof(z));
+ z.next_out = uncompressed_data.get();
+ z.avail_out = uncompressed_size;
+ z.next_in = const_cast<uint8*>(reinterpret_cast<const uint8*>(in.data()));
+ z.avail_in = in.size();
+
+ if (Z_OK != inflateInit(&z)) {
+ return false;
+ }
+ scoped_z.reset(&z);
+
+ int rv = inflate(&z, Z_FINISH);
+ if (rv == Z_NEED_DICT) {
+ string zlib_dict = ZlibDictForEntries(entries, *out_certs);
+ const uint8* dict = reinterpret_cast<const uint8*>(zlib_dict.data());
+ if (Z_OK != inflateSetDictionary(&z, dict, zlib_dict.size())) {
+ return false;
+ }
+ rv = inflate(&z, Z_FINISH);
+ }
+
+ if (Z_STREAM_END != rv || z.avail_out > 0 || z.avail_in > 0) {
+ return false;
+ }
+
+ uncompressed = StringPiece(reinterpret_cast<char*>(uncompressed_data.get()),
+ uncompressed_size);
+ }
+
+ for (size_t i = 0; i < entries.size(); i++) {
+ switch (entries[i].type) {
+ case CertEntry::COMPRESSED:
+ if (uncompressed.size() < sizeof(uint32)) {
+ return false;
+ }
+ uint32 cert_len;
+ memcpy(&cert_len, uncompressed.data(), sizeof(cert_len));
+ uncompressed.remove_prefix(sizeof(uint32));
+ if (uncompressed.size() < cert_len) {
+ return false;
+ }
+ (*out_certs)[i] = uncompressed.substr(0, cert_len).as_string();
+ uncompressed.remove_prefix(cert_len);
+ break;
+ case CertEntry::CACHED:
+ case CertEntry::COMMON:
+ break;
+ }
+ }
+
+ if (!uncompressed.empty()) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/cert_compressor.h b/chromium/net/quic/crypto/cert_compressor.h
new file mode 100644
index 00000000000..7b7e2f0eb05
--- /dev/null
+++ b/chromium/net/quic/crypto/cert_compressor.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CERT_COMPRESSOR_H_
+#define NET_QUIC_CRYPTO_CERT_COMPRESSOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// CertCompressor provides functions for compressing and decompressing
+// certificate chains using three techniquies:
+// 1) The peer may provide a list of a 64-bit, FNV-1a hashes of certificates
+// that they already have. In the event that one of them is to be
+// compressed, it can be replaced with just the hash.
+// 2) The peer may provide a number of hashes that represent sets of
+// pre-shared certificates (CommonCertSets). If one of those certificates
+// is to be compressed, and it's known to the given CommonCertSets, then it
+// can be replaced with a set hash and certificate index.
+// 3) Otherwise the certificates are compressed with zlib using a pre-shared
+// dictionary that consists of the certificates handled with the above
+// methods and a small chunk of common substrings.
+class NET_EXPORT_PRIVATE CertCompressor {
+ public:
+ // CompressChain compresses the certificates in |certs| and returns a
+ // compressed representation. |common_sets| contains the common certificate
+ // sets known locally and |client_common_set_hashes| contains the hashes of
+ // the common sets known to the peer. |client_cached_cert_hashes| contains
+ // 64-bit, FNV-1a hashes of certificates that the peer already possesses.
+ static std::string CompressChain(const std::vector<std::string>& certs,
+ base::StringPiece client_common_set_hashes,
+ base::StringPiece client_cached_cert_hashes,
+ const CommonCertSets* common_sets);
+
+ // DecompressChain decompresses the result of |CompressChain|, given in |in|,
+ // into a series of certificates that are written to |out_certs|.
+ // |cached_certs| contains certificates that the peer may have omitted and
+ // |common_sets| contains the common certificate sets known locally.
+ static bool DecompressChain(base::StringPiece in,
+ const std::vector<std::string>& cached_certs,
+ const CommonCertSets* common_sets,
+ std::vector<std::string>* out_certs);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CERT_COMPRESSOR_H_
diff --git a/chromium/net/quic/crypto/cert_compressor_test.cc b/chromium/net/quic/crypto/cert_compressor_test.cc
new file mode 100644
index 00000000000..5bfef8f56ea
--- /dev/null
+++ b/chromium/net/quic/crypto/cert_compressor_test.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 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/quic/crypto/cert_compressor.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+TEST(CertCompressor, EmptyChain) {
+ vector<string> chain;
+ const string compressed =
+ CertCompressor::CompressChain(chain, StringPiece(), StringPiece(), NULL);
+ EXPECT_EQ("00", base::HexEncode(compressed.data(), compressed.size()));
+
+ vector<string> chain2, cached_certs;
+ ASSERT_TRUE(
+ CertCompressor::DecompressChain(compressed, cached_certs, NULL, &chain2));
+ EXPECT_EQ(chain.size(), chain2.size());
+}
+
+TEST(CertCompressor, Compressed) {
+ vector<string> chain;
+ chain.push_back("testcert");
+ const string compressed =
+ CertCompressor::CompressChain(chain, StringPiece(), StringPiece(), NULL);
+ ASSERT_GE(compressed.size(), 2u);
+ EXPECT_EQ("0100", base::HexEncode(compressed.substr(0, 2).data(), 2));
+
+ vector<string> chain2, cached_certs;
+ ASSERT_TRUE(
+ CertCompressor::DecompressChain(compressed, cached_certs, NULL, &chain2));
+ EXPECT_EQ(chain.size(), chain2.size());
+ EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, Common) {
+ vector<string> chain;
+ chain.push_back("testcert");
+ static const uint64 set_hash = 42;
+ scoped_ptr<CommonCertSets> common_sets(
+ CryptoTestUtils::MockCommonCertSets(chain[0], set_hash, 1));
+ const string compressed = CertCompressor::CompressChain(
+ chain,
+ StringPiece(reinterpret_cast<const char*>(&set_hash), sizeof(set_hash)),
+ StringPiece(), common_sets.get());
+ const string common("03" /* common */
+ "2A00000000000000" /* set hash 42 */
+ "01000000" /* index 1 */
+ "00" /* end of list */);
+ EXPECT_EQ(common.data(),
+ base::HexEncode(compressed.data(), compressed.size()));
+
+ vector<string> chain2, cached_certs;
+ ASSERT_TRUE(CertCompressor::DecompressChain(compressed, cached_certs,
+ common_sets.get(), &chain2));
+ EXPECT_EQ(chain.size(), chain2.size());
+ EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, Cached) {
+ vector<string> chain;
+ chain.push_back("testcert");
+ uint64 hash = QuicUtils::FNV1a_64_Hash(chain[0].data(), chain[0].size());
+ StringPiece hash_bytes(reinterpret_cast<char*>(&hash), sizeof(hash));
+ const string compressed =
+ CertCompressor::CompressChain(chain, StringPiece(), hash_bytes, NULL);
+
+ EXPECT_EQ("02" /* cached */ +
+ base::HexEncode(hash_bytes.data(), hash_bytes.size()) +
+ "00" /* end of list */,
+ base::HexEncode(compressed.data(), compressed.size()));
+
+ vector<string> cached_certs, chain2;
+ cached_certs.push_back(chain[0]);
+ ASSERT_TRUE(
+ CertCompressor::DecompressChain(compressed, cached_certs, NULL, &chain2));
+ EXPECT_EQ(chain.size(), chain2.size());
+ EXPECT_EQ(chain[0], chain2[0]);
+}
+
+TEST(CertCompressor, BadInputs) {
+ vector<string> cached_certs, chain;
+
+ /* bad entry type */
+ const string bad_entry("04");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(bad_entry.data(), bad_entry.size()),
+ cached_certs, NULL, &chain));
+
+ /* no terminator */
+ const string no_terminator("01");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(no_terminator.data(), no_terminator.size()),
+ cached_certs, NULL, &chain));
+
+ /* hash truncated */
+ const string hash_truncated("0200");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(hash_truncated.data(), hash_truncated.size()),
+ cached_certs, NULL, &chain));
+
+ /* hash and index truncated */
+ const string hash_and_index_truncated("0300");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(hash_and_index_truncated.data(),
+ hash_and_index_truncated.size()),
+ cached_certs, NULL, &chain));
+
+ /* without a CommonCertSets */
+ const string without_a_common_cert_set(
+ "03" "0000000000000000" "00000000");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(without_a_common_cert_set.data(),
+ without_a_common_cert_set.size()),
+ cached_certs, NULL, &chain));
+
+ scoped_ptr<CommonCertSets> common_sets(
+ CryptoTestUtils::MockCommonCertSets("foo", 42, 1));
+
+ /* incorrect hash and index */
+ const string incorrect_hash_and_index(
+ "03" "a200000000000000" "00000000");
+ EXPECT_FALSE(CertCompressor::DecompressChain(
+ base::HexEncode(incorrect_hash_and_index.data(),
+ incorrect_hash_and_index.size()),
+ cached_certs, NULL, &chain));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/channel_id.cc b/chromium/net/quic/crypto/channel_id.cc
new file mode 100644
index 00000000000..e707bf01cb3
--- /dev/null
+++ b/chromium/net/quic/crypto/channel_id.cc
@@ -0,0 +1,14 @@
+// Copyright 2013 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/quic/crypto/channel_id.h"
+
+namespace net {
+
+// static
+const char ChannelIDVerifier::kContextStr[] = "QUIC ChannelID";
+// static
+const char ChannelIDVerifier::kClientToServerStr[] = "client -> server";
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/channel_id.h b/chromium/net/quic/crypto/channel_id.h
new file mode 100644
index 00000000000..2d0c29de25a
--- /dev/null
+++ b/chromium/net/quic/crypto/channel_id.h
@@ -0,0 +1,64 @@
+// Copyright 2013 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_QUIC_CRYPTO_CHANNEL_ID_H_
+#define NET_QUIC_CRYPTO_CHANNEL_ID_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// ChannelIDSigner is an abstract interface that implements signing by
+// ChannelID keys.
+class NET_EXPORT_PRIVATE ChannelIDSigner {
+ public:
+ virtual ~ChannelIDSigner() { }
+
+ // Sign signs |signed_data| using the ChannelID key for |hostname| and puts
+ // the serialized public key into |out_key| and the signature into
+ // |out_signature|. It returns true on success.
+ virtual bool Sign(const std::string& hostname,
+ base::StringPiece signed_data,
+ std::string* out_key,
+ std::string* out_signature) = 0;
+
+ // GetKeyForHostname returns the ChannelID key that |ChannelIDSigner| will use
+ // for the given hostname.
+ virtual std::string GetKeyForHostname(const std::string& hostname) = 0;
+};
+
+// ChannelIDVerifier verifies ChannelID signatures.
+class NET_EXPORT_PRIVATE ChannelIDVerifier {
+ public:
+ // kContextStr is prepended to the data to be signed in order to ensure that
+ // a ChannelID signature cannot be used in a different context. (The
+ // terminating NUL byte is inclued.)
+ static const char kContextStr[];
+ // kClientToServerStr follows kContextStr to specify that the ChannelID is
+ // being used in the client to server direction. (The terminating NUL byte is
+ // included.)
+ static const char kClientToServerStr[];
+
+ // Verify returns true iff |signature| is a valid signature of |signed_data|
+ // by |key|.
+ static bool Verify(base::StringPiece key,
+ base::StringPiece signed_data,
+ base::StringPiece signature);
+
+ // FOR TESTING ONLY: VerifyRaw returns true iff |signature| is a valid
+ // signature of |signed_data| by |key|. |is_channel_id_signature| indicates
+ // whether |signature| is a ChannelID signature (with kContextStr prepended
+ // to the data to be signed).
+ static bool VerifyRaw(base::StringPiece key,
+ base::StringPiece signed_data,
+ base::StringPiece signature,
+ bool is_channel_id_signature);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CHANNEL_ID_H_
diff --git a/chromium/net/quic/crypto/channel_id_nss.cc b/chromium/net/quic/crypto/channel_id_nss.cc
new file mode 100644
index 00000000000..d9fc7f855f3
--- /dev/null
+++ b/chromium/net/quic/crypto/channel_id_nss.cc
@@ -0,0 +1,83 @@
+// Copyright 2013 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/quic/crypto/channel_id.h"
+
+#include <keythi.h>
+#include <pk11pub.h>
+#include <sechash.h>
+
+using base::StringPiece;
+
+namespace net {
+
+// static
+bool ChannelIDVerifier::Verify(StringPiece key,
+ StringPiece signed_data,
+ StringPiece signature) {
+ return VerifyRaw(key, signed_data, signature, true);
+}
+
+// static
+bool ChannelIDVerifier::VerifyRaw(StringPiece key,
+ StringPiece signed_data,
+ StringPiece signature,
+ bool is_channel_id_signature) {
+ if (key.size() != 32 * 2 ||
+ signature.size() != 32 * 2) {
+ return false;
+ }
+
+ SECKEYPublicKey public_key;
+ memset(&public_key, 0, sizeof(public_key));
+
+ // DER encoding of the object identifier (OID) of the named curve P-256
+ // (1.2.840.10045.3.1.7). See RFC 6637 Section 11.
+ static const unsigned char p256_oid[] = {
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+ };
+ public_key.keyType = ecKey;
+ public_key.u.ec.DEREncodedParams.type = siBuffer;
+ public_key.u.ec.DEREncodedParams.data = const_cast<unsigned char*>(p256_oid);
+ public_key.u.ec.DEREncodedParams.len = sizeof(p256_oid);
+
+ unsigned char key_buf[65];
+ key_buf[0] = 0x04;
+ memcpy(&key_buf[1], key.data(), key.size());
+ public_key.u.ec.publicValue.type = siBuffer;
+ public_key.u.ec.publicValue.data = key_buf;
+ public_key.u.ec.publicValue.len = sizeof(key_buf);
+
+ SECItem signature_item = {
+ siBuffer,
+ reinterpret_cast<unsigned char*>(const_cast<char*>(signature.data())),
+ static_cast<unsigned int>(signature.size())
+ };
+
+ unsigned char hash_buf[SHA256_LENGTH];
+ SECItem hash_item = { siBuffer, hash_buf, sizeof(hash_buf) };
+
+ HASHContext* sha256 = HASH_Create(HASH_AlgSHA256);
+ if (!sha256) {
+ return false;
+ }
+ HASH_Begin(sha256);
+ if (is_channel_id_signature) {
+ HASH_Update(sha256, reinterpret_cast<const unsigned char*>(kContextStr),
+ strlen(kContextStr) + 1);
+ HASH_Update(sha256,
+ reinterpret_cast<const unsigned char*>(kClientToServerStr),
+ strlen(kClientToServerStr) + 1);
+ }
+ HASH_Update(sha256,
+ reinterpret_cast<const unsigned char*>(signed_data.data()),
+ signed_data.size());
+ HASH_End(sha256, hash_buf, &hash_item.len, sizeof(hash_buf));
+ HASH_Destroy(sha256);
+
+ return PK11_Verify(&public_key, &signature_item, &hash_item, NULL) ==
+ SECSuccess;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/channel_id_openssl.cc b/chromium/net/quic/crypto/channel_id_openssl.cc
new file mode 100644
index 00000000000..241acae4941
--- /dev/null
+++ b/chromium/net/quic/crypto/channel_id_openssl.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 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/quic/crypto/channel_id.h"
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/obj_mac.h>
+#include <openssl/sha.h>
+
+#include "crypto/openssl_util.h"
+
+using base::StringPiece;
+
+namespace net {
+
+// static
+bool ChannelIDVerifier::Verify(StringPiece key,
+ StringPiece signed_data,
+ StringPiece signature) {
+ return VerifyRaw(key, signed_data, signature, true);
+}
+
+// static
+bool ChannelIDVerifier::VerifyRaw(StringPiece key,
+ StringPiece signed_data,
+ StringPiece signature,
+ bool is_channel_id_signature) {
+ if (key.size() != 32 * 2 ||
+ signature.size() != 32 * 2) {
+ return false;
+ }
+
+ crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> p256(
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ if (p256.get() == NULL) {
+ return false;
+ }
+
+ crypto::ScopedOpenSSL<BIGNUM, BN_free> x(BN_new()), y(BN_new()),
+ r(BN_new()), s(BN_new());
+
+ ECDSA_SIG sig;
+ sig.r = r.get();
+ sig.s = s.get();
+
+ const uint8* key_bytes = reinterpret_cast<const uint8*>(key.data());
+ const uint8* signature_bytes =
+ reinterpret_cast<const uint8*>(signature.data());
+
+ if (BN_bin2bn(key_bytes + 0, 32, x.get()) == NULL ||
+ BN_bin2bn(key_bytes + 32, 32, y.get()) == NULL ||
+ BN_bin2bn(signature_bytes + 0, 32, sig.r) == NULL ||
+ BN_bin2bn(signature_bytes + 32, 32, sig.s) == NULL) {
+ return false;
+ }
+
+ crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free> point(
+ EC_POINT_new(p256.get()));
+ if (point.get() == NULL ||
+ !EC_POINT_set_affine_coordinates_GFp(p256.get(), point.get(), x.get(),
+ y.get(), NULL)) {
+ return false;
+ }
+
+ crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ecdsa_key(EC_KEY_new());
+ if (ecdsa_key.get() == NULL ||
+ !EC_KEY_set_group(ecdsa_key.get(), p256.get()) ||
+ !EC_KEY_set_public_key(ecdsa_key.get(), point.get())) {
+ return false;
+ }
+
+ SHA256_CTX sha256;
+ SHA256_Init(&sha256);
+ if (is_channel_id_signature) {
+ SHA256_Update(&sha256, kContextStr, strlen(kContextStr) + 1);
+ SHA256_Update(&sha256, kClientToServerStr, strlen(kClientToServerStr) + 1);
+ }
+ SHA256_Update(&sha256, signed_data.data(), signed_data.size());
+
+ unsigned char digest[SHA256_DIGEST_LENGTH];
+ SHA256_Final(digest, &sha256);
+
+ return ECDSA_do_verify(digest, sizeof(digest), &sig, ecdsa_key.get()) == 1;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/channel_id_test.cc b/chromium/net/quic/crypto/channel_id_test.cc
new file mode 100644
index 00000000000..d29bfced059
--- /dev/null
+++ b/chromium/net/quic/crypto/channel_id_test.cc
@@ -0,0 +1,303 @@
+// Copyright 2013 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/quic/crypto/channel_id.h"
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+namespace {
+
+// The following ECDSA signature verification test vectors for P-256,SHA-256
+// come from the SigVer.rsp file in
+// http://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip
+// downloaded on 2013-06-11.
+struct TestVector {
+ // Input:
+ const char* msg;
+ const char* qx;
+ const char* qy;
+ const char* r;
+ const char* s;
+
+ // Expected output:
+ bool result; // true means "P", false means "F"
+};
+
+const TestVector test_vector[] = {
+ { "e4796db5f785f207aa30d311693b3702821dff1168fd2e04c0836825aefd850d"
+ "9aa60326d88cde1a23c7745351392ca2288d632c264f197d05cd424a30336c19"
+ "fd09bb229654f0222fcb881a4b35c290a093ac159ce13409111ff0358411133c"
+ "24f5b8e2090d6db6558afc36f06ca1f6ef779785adba68db27a409859fc4c4a0",
+ "87f8f2b218f49845f6f10eec3877136269f5c1a54736dbdf69f89940cad41555",
+ "e15f369036f49842fac7a86c8a2b0557609776814448b8f5e84aa9f4395205e9",
+ "d19ff48b324915576416097d2544f7cbdf8768b1454ad20e0baac50e211f23b0",
+ "a3e81e59311cdfff2d4784949f7a2cb50ba6c3a91fa54710568e61aca3e847c6",
+ false // F (3 - S changed)
+ },
+ { "069a6e6b93dfee6df6ef6997cd80dd2182c36653cef10c655d524585655462d6"
+ "83877f95ecc6d6c81623d8fac4e900ed0019964094e7de91f1481989ae187300"
+ "4565789cbf5dc56c62aedc63f62f3b894c9c6f7788c8ecaadc9bd0e81ad91b2b"
+ "3569ea12260e93924fdddd3972af5273198f5efda0746219475017557616170e",
+ "5cf02a00d205bdfee2016f7421807fc38ae69e6b7ccd064ee689fc1a94a9f7d2",
+ "ec530ce3cc5c9d1af463f264d685afe2b4db4b5828d7e61b748930f3ce622a85",
+ "dc23d130c6117fb5751201455e99f36f59aba1a6a21cf2d0e7481a97451d6693",
+ "d6ce7708c18dbf35d4f8aa7240922dc6823f2e7058cbc1484fcad1599db5018c",
+ false // F (2 - R changed)
+ },
+ { "df04a346cf4d0e331a6db78cca2d456d31b0a000aa51441defdb97bbeb20b94d"
+ "8d746429a393ba88840d661615e07def615a342abedfa4ce912e562af7149598"
+ "96858af817317a840dcff85a057bb91a3c2bf90105500362754a6dd321cdd861"
+ "28cfc5f04667b57aa78c112411e42da304f1012d48cd6a7052d7de44ebcc01de",
+ "2ddfd145767883ffbb0ac003ab4a44346d08fa2570b3120dcce94562422244cb",
+ "5f70c7d11ac2b7a435ccfbbae02c3df1ea6b532cc0e9db74f93fffca7c6f9a64",
+ "9913111cff6f20c5bf453a99cd2c2019a4e749a49724a08774d14e4c113edda8",
+ "9467cd4cd21ecb56b0cab0a9a453b43386845459127a952421f5c6382866c5cc",
+ false // F (4 - Q changed)
+ },
+ { "e1130af6a38ccb412a9c8d13e15dbfc9e69a16385af3c3f1e5da954fd5e7c45f"
+ "d75e2b8c36699228e92840c0562fbf3772f07e17f1add56588dd45f7450e1217"
+ "ad239922dd9c32695dc71ff2424ca0dec1321aa47064a044b7fe3c2b97d03ce4"
+ "70a592304c5ef21eed9f93da56bb232d1eeb0035f9bf0dfafdcc4606272b20a3",
+ "e424dc61d4bb3cb7ef4344a7f8957a0c5134e16f7a67c074f82e6e12f49abf3c",
+ "970eed7aa2bc48651545949de1dddaf0127e5965ac85d1243d6f60e7dfaee927",
+ "bf96b99aa49c705c910be33142017c642ff540c76349b9dab72f981fd9347f4f",
+ "17c55095819089c2e03b9cd415abdf12444e323075d98f31920b9e0f57ec871c",
+ true // P (0 )
+ },
+ { "73c5f6a67456ae48209b5f85d1e7de7758bf235300c6ae2bdceb1dcb27a7730f"
+ "b68c950b7fcada0ecc4661d3578230f225a875e69aaa17f1e71c6be5c831f226"
+ "63bac63d0c7a9635edb0043ff8c6f26470f02a7bc56556f1437f06dfa27b487a"
+ "6c4290d8bad38d4879b334e341ba092dde4e4ae694a9c09302e2dbf443581c08",
+ "e0fc6a6f50e1c57475673ee54e3a57f9a49f3328e743bf52f335e3eeaa3d2864",
+ "7f59d689c91e463607d9194d99faf316e25432870816dde63f5d4b373f12f22a",
+ "1d75830cd36f4c9aa181b2c4221e87f176b7f05b7c87824e82e396c88315c407",
+ "cb2acb01dac96efc53a32d4a0d85d0c2e48955214783ecf50a4f0414a319c05a",
+ true // P (0 )
+ },
+ { "666036d9b4a2426ed6585a4e0fd931a8761451d29ab04bd7dc6d0c5b9e38e6c2"
+ "b263ff6cb837bd04399de3d757c6c7005f6d7a987063cf6d7e8cb38a4bf0d74a"
+ "282572bd01d0f41e3fd066e3021575f0fa04f27b700d5b7ddddf50965993c3f9"
+ "c7118ed78888da7cb221849b3260592b8e632d7c51e935a0ceae15207bedd548",
+ "a849bef575cac3c6920fbce675c3b787136209f855de19ffe2e8d29b31a5ad86",
+ "bf5fe4f7858f9b805bd8dcc05ad5e7fb889de2f822f3d8b41694e6c55c16b471",
+ "25acc3aa9d9e84c7abf08f73fa4195acc506491d6fc37cb9074528a7db87b9d6",
+ "9b21d5b5259ed3f2ef07dfec6cc90d3a37855d1ce122a85ba6a333f307d31537",
+ false // F (2 - R changed)
+ },
+ { "7e80436bce57339ce8da1b5660149a20240b146d108deef3ec5da4ae256f8f89"
+ "4edcbbc57b34ce37089c0daa17f0c46cd82b5a1599314fd79d2fd2f446bd5a25"
+ "b8e32fcf05b76d644573a6df4ad1dfea707b479d97237a346f1ec632ea5660ef"
+ "b57e8717a8628d7f82af50a4e84b11f21bdff6839196a880ae20b2a0918d58cd",
+ "3dfb6f40f2471b29b77fdccba72d37c21bba019efa40c1c8f91ec405d7dcc5df",
+ "f22f953f1e395a52ead7f3ae3fc47451b438117b1e04d613bc8555b7d6e6d1bb",
+ "548886278e5ec26bed811dbb72db1e154b6f17be70deb1b210107decb1ec2a5a",
+ "e93bfebd2f14f3d827ca32b464be6e69187f5edbd52def4f96599c37d58eee75",
+ false // F (4 - Q changed)
+ },
+ { "1669bfb657fdc62c3ddd63269787fc1c969f1850fb04c933dda063ef74a56ce1"
+ "3e3a649700820f0061efabf849a85d474326c8a541d99830eea8131eaea584f2"
+ "2d88c353965dabcdc4bf6b55949fd529507dfb803ab6b480cd73ca0ba00ca19c"
+ "438849e2cea262a1c57d8f81cd257fb58e19dec7904da97d8386e87b84948169",
+ "69b7667056e1e11d6caf6e45643f8b21e7a4bebda463c7fdbc13bc98efbd0214",
+ "d3f9b12eb46c7c6fda0da3fc85bc1fd831557f9abc902a3be3cb3e8be7d1aa2f",
+ "288f7a1cd391842cce21f00e6f15471c04dc182fe4b14d92dc18910879799790",
+ "247b3c4e89a3bcadfea73c7bfd361def43715fa382b8c3edf4ae15d6e55e9979",
+ false // F (1 - Message changed)
+ },
+ { "3fe60dd9ad6caccf5a6f583b3ae65953563446c4510b70da115ffaa0ba04c076"
+ "115c7043ab8733403cd69c7d14c212c655c07b43a7c71b9a4cffe22c2684788e"
+ "c6870dc2013f269172c822256f9e7cc674791bf2d8486c0f5684283e1649576e"
+ "fc982ede17c7b74b214754d70402fb4bb45ad086cf2cf76b3d63f7fce39ac970",
+ "bf02cbcf6d8cc26e91766d8af0b164fc5968535e84c158eb3bc4e2d79c3cc682",
+ "069ba6cb06b49d60812066afa16ecf7b51352f2c03bd93ec220822b1f3dfba03",
+ "f5acb06c59c2b4927fb852faa07faf4b1852bbb5d06840935e849c4d293d1bad",
+ "049dab79c89cc02f1484c437f523e080a75f134917fda752f2d5ca397addfe5d",
+ false // F (3 - S changed)
+ },
+ { "983a71b9994d95e876d84d28946a041f8f0a3f544cfcc055496580f1dfd4e312"
+ "a2ad418fe69dbc61db230cc0c0ed97e360abab7d6ff4b81ee970a7e97466acfd"
+ "9644f828ffec538abc383d0e92326d1c88c55e1f46a668a039beaa1be631a891"
+ "29938c00a81a3ae46d4aecbf9707f764dbaccea3ef7665e4c4307fa0b0a3075c",
+ "224a4d65b958f6d6afb2904863efd2a734b31798884801fcab5a590f4d6da9de",
+ "178d51fddada62806f097aa615d33b8f2404e6b1479f5fd4859d595734d6d2b9",
+ "87b93ee2fecfda54deb8dff8e426f3c72c8864991f8ec2b3205bb3b416de93d2",
+ "4044a24df85be0cc76f21a4430b75b8e77b932a87f51e4eccbc45c263ebf8f66",
+ false // F (2 - R changed)
+ },
+ { "4a8c071ac4fd0d52faa407b0fe5dab759f7394a5832127f2a3498f34aac28733"
+ "9e043b4ffa79528faf199dc917f7b066ad65505dab0e11e6948515052ce20cfd"
+ "b892ffb8aa9bf3f1aa5be30a5bbe85823bddf70b39fd7ebd4a93a2f75472c1d4"
+ "f606247a9821f1a8c45a6cb80545de2e0c6c0174e2392088c754e9c8443eb5af",
+ "43691c7795a57ead8c5c68536fe934538d46f12889680a9cb6d055a066228369",
+ "f8790110b3c3b281aa1eae037d4f1234aff587d903d93ba3af225c27ddc9ccac",
+ "8acd62e8c262fa50dd9840480969f4ef70f218ebf8ef9584f199031132c6b1ce",
+ "cfca7ed3d4347fb2a29e526b43c348ae1ce6c60d44f3191b6d8ea3a2d9c92154",
+ false // F (3 - S changed)
+ },
+ { "0a3a12c3084c865daf1d302c78215d39bfe0b8bf28272b3c0b74beb4b7409db0"
+ "718239de700785581514321c6440a4bbaea4c76fa47401e151e68cb6c29017f0"
+ "bce4631290af5ea5e2bf3ed742ae110b04ade83a5dbd7358f29a85938e23d87a"
+ "c8233072b79c94670ff0959f9c7f4517862ff829452096c78f5f2e9a7e4e9216",
+ "9157dbfcf8cf385f5bb1568ad5c6e2a8652ba6dfc63bc1753edf5268cb7eb596",
+ "972570f4313d47fc96f7c02d5594d77d46f91e949808825b3d31f029e8296405",
+ "dfaea6f297fa320b707866125c2a7d5d515b51a503bee817de9faa343cc48eeb",
+ "8f780ad713f9c3e5a4f7fa4c519833dfefc6a7432389b1e4af463961f09764f2",
+ false // F (1 - Message changed)
+ },
+ { "785d07a3c54f63dca11f5d1a5f496ee2c2f9288e55007e666c78b007d95cc285"
+ "81dce51f490b30fa73dc9e2d45d075d7e3a95fb8a9e1465ad191904124160b7c"
+ "60fa720ef4ef1c5d2998f40570ae2a870ef3e894c2bc617d8a1dc85c3c557749"
+ "28c38789b4e661349d3f84d2441a3b856a76949b9f1f80bc161648a1cad5588e",
+ "072b10c081a4c1713a294f248aef850e297991aca47fa96a7470abe3b8acfdda",
+ "9581145cca04a0fb94cedce752c8f0370861916d2a94e7c647c5373ce6a4c8f5",
+ "09f5483eccec80f9d104815a1be9cc1a8e5b12b6eb482a65c6907b7480cf4f19",
+ "a4f90e560c5e4eb8696cb276e5165b6a9d486345dedfb094a76e8442d026378d",
+ false // F (4 - Q changed)
+ },
+ { "76f987ec5448dd72219bd30bf6b66b0775c80b394851a43ff1f537f140a6e722"
+ "9ef8cd72ad58b1d2d20298539d6347dd5598812bc65323aceaf05228f738b5ad"
+ "3e8d9fe4100fd767c2f098c77cb99c2992843ba3eed91d32444f3b6db6cd212d"
+ "d4e5609548f4bb62812a920f6e2bf1581be1ebeebdd06ec4e971862cc42055ca",
+ "09308ea5bfad6e5adf408634b3d5ce9240d35442f7fe116452aaec0d25be8c24",
+ "f40c93e023ef494b1c3079b2d10ef67f3170740495ce2cc57f8ee4b0618b8ee5",
+ "5cc8aa7c35743ec0c23dde88dabd5e4fcd0192d2116f6926fef788cddb754e73",
+ "9c9c045ebaa1b828c32f82ace0d18daebf5e156eb7cbfdc1eff4399a8a900ae7",
+ false // F (1 - Message changed)
+ },
+ { "60cd64b2cd2be6c33859b94875120361a24085f3765cb8b2bf11e026fa9d8855"
+ "dbe435acf7882e84f3c7857f96e2baab4d9afe4588e4a82e17a78827bfdb5ddb"
+ "d1c211fbc2e6d884cddd7cb9d90d5bf4a7311b83f352508033812c776a0e00c0"
+ "03c7e0d628e50736c7512df0acfa9f2320bd102229f46495ae6d0857cc452a84",
+ "2d98ea01f754d34bbc3003df5050200abf445ec728556d7ed7d5c54c55552b6d",
+ "9b52672742d637a32add056dfd6d8792f2a33c2e69dafabea09b960bc61e230a",
+ "06108e525f845d0155bf60193222b3219c98e3d49424c2fb2a0987f825c17959",
+ "62b5cdd591e5b507e560167ba8f6f7cda74673eb315680cb89ccbc4eec477dce",
+ true // P (0 )
+ },
+ { NULL }
+};
+
+// Returns true if |ch| is a lowercase hexadecimal digit.
+bool IsHexDigit(char ch) {
+ return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f');
+}
+
+// Converts a lowercase hexadecimal digit to its integer value.
+int HexDigitToInt(char ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ }
+ return ch - 'a' + 10;
+}
+
+// |in| is a string consisting of lowercase hexadecimal digits, where
+// every two digits represent one byte. |out| is a buffer of size |max_len|.
+// Converts |in| to bytes and stores the bytes in the |out| buffer. The
+// number of bytes converted is returned in |*out_len|. Returns true on
+// success, false on failure.
+bool DecodeHexString(const char* in,
+ char* out,
+ size_t* out_len,
+ size_t max_len) {
+ if (!in) {
+ *out_len = (size_t)-1;
+ return true;
+ }
+ *out_len = 0;
+ while (*in != '\0') {
+ if (!IsHexDigit(*in) || !IsHexDigit(*(in + 1))) {
+ return false;
+ }
+ if (*out_len >= max_len) {
+ return false;
+ }
+ out[*out_len] = HexDigitToInt(*in) * 16 + HexDigitToInt(*(in + 1));
+ (*out_len)++;
+ in += 2;
+ }
+ return true;
+}
+
+} // namespace
+
+// A known answer test for ChannelIDVerifier.
+TEST(ChannelIDTest, VerifyKnownAnswerTest) {
+ char msg[1024];
+ size_t msg_len;
+ char key[64];
+ size_t qx_len;
+ size_t qy_len;
+ char signature[64];
+ size_t r_len;
+ size_t s_len;
+
+ for (size_t i = 0; test_vector[i].msg != NULL; i++) {
+ SCOPED_TRACE(i);
+ // Decode the test vector.
+ ASSERT_TRUE(
+ DecodeHexString(test_vector[i].msg, msg, &msg_len, sizeof(msg)));
+ ASSERT_TRUE(DecodeHexString(test_vector[i].qx, key, &qx_len, sizeof(key)));
+ ASSERT_TRUE(DecodeHexString(test_vector[i].qy, key + qx_len, &qy_len,
+ sizeof(key) - qx_len));
+ ASSERT_TRUE(DecodeHexString(test_vector[i].r, signature, &r_len,
+ sizeof(signature)));
+ ASSERT_TRUE(DecodeHexString(test_vector[i].s, signature + r_len, &s_len,
+ sizeof(signature) - r_len));
+
+ // The test vector's lengths should look sane.
+ EXPECT_EQ(sizeof(key) / 2, qx_len);
+ EXPECT_EQ(sizeof(key) / 2, qy_len);
+ EXPECT_EQ(sizeof(signature) / 2, r_len);
+ EXPECT_EQ(sizeof(signature) / 2, s_len);
+
+ EXPECT_EQ(test_vector[i].result,
+ ChannelIDVerifier::VerifyRaw(
+ StringPiece(key, sizeof(key)),
+ StringPiece(msg, msg_len),
+ StringPiece(signature, sizeof(signature)),
+ false));
+ }
+}
+
+TEST(ChannelIDTest, SignAndVerify) {
+ scoped_ptr<ChannelIDSigner> signer(
+ CryptoTestUtils::ChannelIDSignerForTesting());
+
+ const string signed_data = "signed data";
+ const string hostname = "foo.example.com";
+ string key, signature;
+ ASSERT_TRUE(signer->Sign(hostname, signed_data, &key, &signature));
+
+ EXPECT_EQ(key, signer->GetKeyForHostname(hostname));
+
+ EXPECT_TRUE(ChannelIDVerifier::Verify(key, signed_data, signature));
+
+ EXPECT_FALSE(ChannelIDVerifier::Verify("a" + key, signed_data, signature));
+ EXPECT_FALSE(ChannelIDVerifier::Verify(key, "a" + signed_data, signature));
+
+ scoped_ptr<char[]> bad_key(new char[key.size()]);
+ memcpy(bad_key.get(), key.data(), key.size());
+ bad_key[1] ^= 0x80;
+ EXPECT_FALSE(ChannelIDVerifier::Verify(
+ string(bad_key.get(), key.size()), signed_data, signature));
+
+ scoped_ptr<char[]> bad_signature(new char[signature.size()]);
+ memcpy(bad_signature.get(), signature.data(), signature.size());
+ bad_signature[1] ^= 0x80;
+ EXPECT_FALSE(ChannelIDVerifier::Verify(
+ key, signed_data, string(bad_signature.get(), signature.size())));
+
+ EXPECT_FALSE(ChannelIDVerifier::Verify(
+ key, "wrong signed data", signature));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/common_cert_set.cc b/chromium/net/quic/crypto/common_cert_set.cc
new file mode 100644
index 00000000000..f631cd6fc46
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2013 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/quic/crypto/common_cert_set.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+
+namespace common_cert_set_0 {
+#include "net/quic/crypto/common_cert_set_0.c"
+}
+
+namespace {
+
+struct CertSet {
+ // num_certs contains the number of certificates in this set.
+ size_t num_certs;
+ // certs is an array of |num_certs| pointers to the DER encoded certificates.
+ const unsigned char* const* certs;
+ // lens is an array of |num_certs| integers describing the length, in bytes,
+ // of each certificate.
+ const size_t* lens;
+ // hash contains the 64-bit, FNV-1a hash of this set.
+ uint64 hash;
+};
+
+const CertSet kSets[] = {
+ {
+ common_cert_set_0::kNumCerts,
+ common_cert_set_0::kCerts,
+ common_cert_set_0::kLens,
+ common_cert_set_0::kHash,
+ },
+};
+
+const uint64 kSetHashes[] = {
+ common_cert_set_0::kHash,
+};
+
+// Compare returns a value less than, equal to or greater than zero if |a| is
+// lexicographically less than, equal to or greater than |b|, respectively.
+int Compare(StringPiece a, const unsigned char* b, size_t b_len) {
+ size_t len = a.size();
+ if (len > b_len) {
+ len = b_len;
+ }
+ int n = memcmp(a.data(), b, len);
+ if (n != 0) {
+ return n;
+ }
+
+ if (a.size() < b_len) {
+ return -1;
+ } else if (a.size() > b_len) {
+ return 1;
+ }
+ return 0;
+}
+
+// CommonCertSetsQUIC implements the CommonCertSets interface using the default
+// certificate sets.
+class CommonCertSetsQUIC : public CommonCertSets {
+ public:
+ // CommonCertSets interface.
+ virtual StringPiece GetCommonHashes() const OVERRIDE {
+ return StringPiece(reinterpret_cast<const char*>(kSetHashes),
+ sizeof(uint64) * arraysize(kSetHashes));
+ }
+
+ virtual StringPiece GetCert(uint64 hash, uint32 index) const OVERRIDE {
+ for (size_t i = 0; i < arraysize(kSets); i++) {
+ if (kSets[i].hash == hash) {
+ if (index < kSets[i].num_certs) {
+ return StringPiece(
+ reinterpret_cast<const char*>(kSets[i].certs[index]),
+ kSets[i].lens[index]);
+ }
+ break;
+ }
+ }
+
+ return StringPiece();
+ }
+
+ virtual bool MatchCert(StringPiece cert, StringPiece common_set_hashes,
+ uint64* out_hash, uint32* out_index) const OVERRIDE {
+ if (common_set_hashes.size() % sizeof(uint64) != 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < common_set_hashes.size() / sizeof(uint64); i++) {
+ uint64 hash;
+ memcpy(&hash, common_set_hashes.data() + i * sizeof(uint64),
+ sizeof(uint64));
+
+ for (size_t j = 0; j < arraysize(kSets); j++) {
+ if (kSets[j].hash != hash) {
+ continue;
+ }
+
+ if (kSets[j].num_certs == 0) {
+ continue;
+ }
+
+ // Binary search for a matching certificate.
+ size_t min = 0;
+ size_t max = kSets[j].num_certs - 1;
+ while (max >= min) {
+ size_t mid = min + ((max - min) / 2);
+ int n = Compare(cert, kSets[j].certs[mid], kSets[j].lens[mid]);
+ if (n < 0) {
+ if (mid == 0) {
+ break;
+ }
+ max = mid - 1;
+ } else if (n > 0) {
+ min = mid + 1;
+ } else {
+ *out_hash = hash;
+ *out_index = mid;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ static CommonCertSetsQUIC* GetInstance() {
+ return Singleton<CommonCertSetsQUIC>::get();
+ }
+
+ private:
+ CommonCertSetsQUIC() {}
+ virtual ~CommonCertSetsQUIC() {}
+
+ friend struct DefaultSingletonTraits<CommonCertSetsQUIC>;
+ DISALLOW_COPY_AND_ASSIGN(CommonCertSetsQUIC);
+};
+
+} // anonymous namespace
+
+CommonCertSets::~CommonCertSets() {}
+
+// static
+const CommonCertSets* CommonCertSets::GetInstanceQUIC() {
+ return CommonCertSetsQUIC::GetInstance();
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/common_cert_set.h b/chromium/net/quic/crypto/common_cert_set.h
new file mode 100644
index 00000000000..a9e93045f57
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_COMMON_CERT_SET_H_
+#define NET_QUIC_CRYPTO_COMMON_CERT_SET_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// CommonCertSets is an interface to an object that contains a number of common
+// certificate sets and can match against them.
+class NET_EXPORT_PRIVATE CommonCertSets {
+ public:
+ virtual ~CommonCertSets();
+
+ // GetInstanceQUIC returns the standard QUIC common certificate sets.
+ static const CommonCertSets* GetInstanceQUIC();
+
+ // GetCommonHashes returns a StringPiece containing the hashes of common sets
+ // supported by this object. The 64-bit hashes are concatenated in the
+ // StringPiece.
+ virtual base::StringPiece GetCommonHashes() const = 0;
+
+ // GetCert returns a specific certificate (at index |index|) in the common
+ // set identified by |hash|. If no such certificate is known, an empty
+ // StringPiece is returned.
+ virtual base::StringPiece GetCert(uint64 hash, uint32 index) const = 0;
+
+ // MatchCert tries to find |cert| in one of the common certificate sets
+ // identified by |common_set_hashes|. On success it puts the hash of the
+ // set in |out_hash|, the index of |cert| in the set in |out_index| and
+ // returns true. Otherwise it returns false.
+ virtual bool MatchCert(base::StringPiece cert,
+ base::StringPiece common_set_hashes,
+ uint64* out_hash,
+ uint32* out_index) const = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_COMMON_CERT_SET_H_
diff --git a/chromium/net/quic/crypto/common_cert_set_0.c b/chromium/net/quic/crypto/common_cert_set_0.c
new file mode 100644
index 00000000000..1133733cf7f
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set_0.c
@@ -0,0 +1,223 @@
+/* Copyright (c) 2013 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.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#include "net/quic/crypto/common_cert_set_1_50.inc"
+#include "net/quic/crypto/common_cert_set_51_100.inc"
+
+static const size_t kNumCerts = 102;
+static const unsigned char* const kCerts[] = {
+ kDERCert0,
+ kDERCert1,
+ kDERCert2,
+ kDERCert3,
+ kDERCert4,
+ kDERCert5,
+ kDERCert6,
+ kDERCert7,
+ kDERCert8,
+ kDERCert9,
+ kDERCert10,
+ kDERCert11,
+ kDERCert12,
+ kDERCert13,
+ kDERCert14,
+ kDERCert15,
+ kDERCert16,
+ kDERCert17,
+ kDERCert18,
+ kDERCert19,
+ kDERCert20,
+ kDERCert21,
+ kDERCert22,
+ kDERCert23,
+ kDERCert24,
+ kDERCert25,
+ kDERCert26,
+ kDERCert27,
+ kDERCert28,
+ kDERCert29,
+ kDERCert30,
+ kDERCert31,
+ kDERCert32,
+ kDERCert33,
+ kDERCert34,
+ kDERCert35,
+ kDERCert36,
+ kDERCert37,
+ kDERCert38,
+ kDERCert39,
+ kDERCert40,
+ kDERCert41,
+ kDERCert42,
+ kDERCert43,
+ kDERCert44,
+ kDERCert45,
+ kDERCert46,
+ kDERCert47,
+ kDERCert48,
+ kDERCert49,
+ kDERCert50,
+ kDERCert51,
+ kDERCert52,
+ kDERCert53,
+ kDERCert54,
+ kDERCert55,
+ kDERCert56,
+ kDERCert57,
+ kDERCert58,
+ kDERCert59,
+ kDERCert60,
+ kDERCert61,
+ kDERCert62,
+ kDERCert63,
+ kDERCert64,
+ kDERCert65,
+ kDERCert66,
+ kDERCert67,
+ kDERCert68,
+ kDERCert69,
+ kDERCert70,
+ kDERCert71,
+ kDERCert72,
+ kDERCert73,
+ kDERCert74,
+ kDERCert75,
+ kDERCert76,
+ kDERCert77,
+ kDERCert78,
+ kDERCert79,
+ kDERCert80,
+ kDERCert81,
+ kDERCert82,
+ kDERCert83,
+ kDERCert84,
+ kDERCert85,
+ kDERCert86,
+ kDERCert87,
+ kDERCert88,
+ kDERCert89,
+ kDERCert90,
+ kDERCert91,
+ kDERCert92,
+ kDERCert93,
+ kDERCert94,
+ kDERCert95,
+ kDERCert96,
+ kDERCert97,
+ kDERCert98,
+ kDERCert99,
+ kDERCert100,
+ kDERCert101,
+};
+
+static const size_t kLens[] = {
+ 692,
+ 897,
+ 903,
+ 911,
+ 911,
+ 957,
+ 971,
+ 985,
+ 989,
+ 1022,
+ 1032,
+ 1049,
+ 1055,
+ 1071,
+ 1071,
+ 1073,
+ 1075,
+ 1078,
+ 1080,
+ 1082,
+ 1082,
+ 1084,
+ 1088,
+ 1090,
+ 1094,
+ 1097,
+ 1098,
+ 1107,
+ 1118,
+ 1119,
+ 1122,
+ 1124,
+ 1131,
+ 1134,
+ 1136,
+ 1147,
+ 1161,
+ 1162,
+ 1162,
+ 1167,
+ 1167,
+ 1171,
+ 1172,
+ 1181,
+ 1183,
+ 1184,
+ 1187,
+ 1191,
+ 1194,
+ 1194,
+ 1199,
+ 1223,
+ 1226,
+ 1231,
+ 1236,
+ 1239,
+ 1250,
+ 1254,
+ 1256,
+ 1256,
+ 1257,
+ 1260,
+ 1268,
+ 1269,
+ 1269,
+ 1270,
+ 1273,
+ 1276,
+ 1279,
+ 1280,
+ 1282,
+ 1285,
+ 1286,
+ 1287,
+ 1287,
+ 1290,
+ 1291,
+ 1291,
+ 1294,
+ 1294,
+ 1297,
+ 1302,
+ 1302,
+ 1303,
+ 1311,
+ 1330,
+ 1365,
+ 1512,
+ 1520,
+ 1535,
+ 1548,
+ 1550,
+ 1559,
+ 1570,
+ 1581,
+ 1584,
+ 1592,
+ 1592,
+ 1625,
+ 1628,
+ 1767,
+ 1770,
+};
+
+static const uint64 kHash = GG_UINT64_C(0xc9fef74053f99f39);
diff --git a/chromium/net/quic/crypto/common_cert_set_1_50.inc b/chromium/net/quic/crypto/common_cert_set_1_50.inc
new file mode 100644
index 00000000000..5ee471ba76b
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set_1_50.inc
@@ -0,0 +1,9794 @@
+/* Copyright (c) 2013 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.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 747377 (0xb6771)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+ Validity
+ Not Before: Jun 8 20:43:27 2009 GMT
+ Not After : Jun 7 19:43:27 2013 GMT
+ Subject: C=US, O=Google Inc, CN=Google Internet Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:c9:ed:b7:a4:8b:9c:57:e7:84:3e:40:7d:84:f4:
+ 8f:d1:71:63:53:99:e7:79:74:14:af:44:99:33:20:
+ 92:8d:7b:e5:28:0c:ba:ad:6c:49:7e:83:5f:34:59:
+ 4e:0a:7a:30:cd:d0:d7:c4:57:45:ed:d5:aa:d6:73:
+ 26:ce:ad:32:13:b8:d7:0f:1d:3b:df:dd:dc:08:36:
+ a8:6f:51:44:9b:ca:d6:20:52:73:b7:26:87:35:6a:
+ db:a9:e5:d4:59:a5:2b:fc:67:19:39:fa:93:18:18:
+ 6c:de:dd:25:8a:0e:33:14:47:c2:ef:01:50:79:e4:
+ fd:69:d1:a7:c0:ac:e2:57:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ BF:C0:30:EB:F5:43:11:3E:67:BA:9E:91:FB:FC:6A:DA:E3:6B:12:24
+ X509v3 Authority Key Identifier:
+ keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/secureca.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ b8:8a:23:c6:48:96:b1:11:7c:60:77:5e:05:9a:ab:a1:c6:fa:
+ 82:1c:18:07:c4:eb:81:b0:a8:66:eb:49:a8:e9:0c:d3:29:ad:
+ f5:ef:24:4c:fd:e4:4b:ca:7f:5e:63:ab:99:27:cb:9f:36:21:
+ 2c:b9:10:60:67:cd:d2:b4:f0:f0:ab:71:e5:8b:5a:89:27:11:
+ 84:aa:8e:bf:99:f0:9d:09:21:0a:52:19:9a:5a:09:d2:90:b7:
+ fa:0c:f8:7e:78:a2:b0:85:af:5c:4c:99:d9:5c:55:29:f9:a5:
+ 51:42:2e:3a:cb:38:8c:78:3b:cb:f8:fb:95:87:bc:bc:90:f9:
+ 50:32
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIDC2dxMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDkwNjA4MjA0MzI3WhcNMTMwNjA3MTk0MzI3
+WjBGMQswCQYDVQQGEwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzEiMCAGA1UEAxMZ
+R29vZ2xlIEludGVybmV0IEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAye23pIucV+eEPkB9hPSP0XFjU5nneXQUr0SZMyCSjXvlKAy6rWxJfoNf
+NFlOCnowzdDXxFdF7dWq1nMmzq0yE7jXDx07393cCDaob1FEm8rWIFJztyaHNWrb
+qeXUWaUr/GcZOfqTGBhs3t0lig4zFEfC7wFQeeT9adGnwKziV28CAwEAAaOBozCB
+oDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFL/AMOv1QxE+Z7qekfv8atrjaxIk
+MB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20v
+Y3Jscy9zZWN1cmVjYS5jcmwwDQYJKoZIhvcNAQEFBQADgYEAuIojxkiWsRF8YHde
+BZqrocb6ghwYB8TrgbCoZutJqOkM0ymt9e8kTP3kS8p/XmOrmSfLnzYhLLkQYGfN
+0rTw8Ktx5YtaiScRhKqOv5nwnQkhClIZmloJ0pC3+gz4fniisIWvXEyZ2VxVKfml
+UUIuOss4jHg7y/j7lYe8vJD5UDI=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert0[] = {
+ 0x30, 0x82, 0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x0b, 0x67, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+ 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30,
+ 0x36, 0x30, 0x38, 0x32, 0x30, 0x34, 0x33, 0x32, 0x37, 0x5a, 0x17, 0x0d,
+ 0x31, 0x33, 0x30, 0x36, 0x30, 0x37, 0x31, 0x39, 0x34, 0x33, 0x32, 0x37,
+ 0x5a, 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+ 0x63, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+ 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+ 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+ 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xc9, 0xed, 0xb7, 0xa4, 0x8b, 0x9c,
+ 0x57, 0xe7, 0x84, 0x3e, 0x40, 0x7d, 0x84, 0xf4, 0x8f, 0xd1, 0x71, 0x63,
+ 0x53, 0x99, 0xe7, 0x79, 0x74, 0x14, 0xaf, 0x44, 0x99, 0x33, 0x20, 0x92,
+ 0x8d, 0x7b, 0xe5, 0x28, 0x0c, 0xba, 0xad, 0x6c, 0x49, 0x7e, 0x83, 0x5f,
+ 0x34, 0x59, 0x4e, 0x0a, 0x7a, 0x30, 0xcd, 0xd0, 0xd7, 0xc4, 0x57, 0x45,
+ 0xed, 0xd5, 0xaa, 0xd6, 0x73, 0x26, 0xce, 0xad, 0x32, 0x13, 0xb8, 0xd7,
+ 0x0f, 0x1d, 0x3b, 0xdf, 0xdd, 0xdc, 0x08, 0x36, 0xa8, 0x6f, 0x51, 0x44,
+ 0x9b, 0xca, 0xd6, 0x20, 0x52, 0x73, 0xb7, 0x26, 0x87, 0x35, 0x6a, 0xdb,
+ 0xa9, 0xe5, 0xd4, 0x59, 0xa5, 0x2b, 0xfc, 0x67, 0x19, 0x39, 0xfa, 0x93,
+ 0x18, 0x18, 0x6c, 0xde, 0xdd, 0x25, 0x8a, 0x0e, 0x33, 0x14, 0x47, 0xc2,
+ 0xef, 0x01, 0x50, 0x79, 0xe4, 0xfd, 0x69, 0xd1, 0xa7, 0xc0, 0xac, 0xe2,
+ 0x57, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa3, 0x30, 0x81,
+ 0xa0, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0xbf, 0xc0, 0x30, 0xeb, 0xf5, 0x43, 0x11, 0x3e,
+ 0x67, 0xba, 0x9e, 0x91, 0xfb, 0xfc, 0x6a, 0xda, 0xe3, 0x6b, 0x12, 0x24,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8,
+ 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+ 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67,
+ 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
+ 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+ 0xb8, 0x8a, 0x23, 0xc6, 0x48, 0x96, 0xb1, 0x11, 0x7c, 0x60, 0x77, 0x5e,
+ 0x05, 0x9a, 0xab, 0xa1, 0xc6, 0xfa, 0x82, 0x1c, 0x18, 0x07, 0xc4, 0xeb,
+ 0x81, 0xb0, 0xa8, 0x66, 0xeb, 0x49, 0xa8, 0xe9, 0x0c, 0xd3, 0x29, 0xad,
+ 0xf5, 0xef, 0x24, 0x4c, 0xfd, 0xe4, 0x4b, 0xca, 0x7f, 0x5e, 0x63, 0xab,
+ 0x99, 0x27, 0xcb, 0x9f, 0x36, 0x21, 0x2c, 0xb9, 0x10, 0x60, 0x67, 0xcd,
+ 0xd2, 0xb4, 0xf0, 0xf0, 0xab, 0x71, 0xe5, 0x8b, 0x5a, 0x89, 0x27, 0x11,
+ 0x84, 0xaa, 0x8e, 0xbf, 0x99, 0xf0, 0x9d, 0x09, 0x21, 0x0a, 0x52, 0x19,
+ 0x9a, 0x5a, 0x09, 0xd2, 0x90, 0xb7, 0xfa, 0x0c, 0xf8, 0x7e, 0x78, 0xa2,
+ 0xb0, 0x85, 0xaf, 0x5c, 0x4c, 0x99, 0xd9, 0x5c, 0x55, 0x29, 0xf9, 0xa5,
+ 0x51, 0x42, 0x2e, 0x3a, 0xcb, 0x38, 0x8c, 0x78, 0x3b, 0xcb, 0xf8, 0xfb,
+ 0x95, 0x87, 0xbc, 0xbc, 0x90, 0xf9, 0x50, 0x32,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1227750 (0x12bbe6)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+ Validity
+ Not Before: May 21 04:00:00 2002 GMT
+ Not After : Aug 21 04:00:00 2018 GMT
+ Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df:
+ 3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8:
+ 43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29:
+ bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4:
+ 60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3:
+ ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92:
+ 2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d:
+ 80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14:
+ 15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd:
+ d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6:
+ d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5:
+ 5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39:
+ 19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05:
+ 9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2:
+ fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32:
+ eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07:
+ 36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b:
+ e4:f9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+ X509v3 Subject Key Identifier:
+ C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/secureca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.geotrust.com/resources/repository
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 76:e1:12:6e:4e:4b:16:12:86:30:06:b2:81:08:cf:f0:08:c7:
+ c7:71:7e:66:ee:c2:ed:d4:3b:1f:ff:f0:f0:c8:4e:d6:43:38:
+ b0:b9:30:7d:18:d0:55:83:a2:6a:cb:36:11:9c:e8:48:66:a3:
+ 6d:7f:b8:13:d4:47:fe:8b:5a:5c:73:fc:ae:d9:1b:32:19:38:
+ ab:97:34:14:aa:96:d2:eb:a3:1c:14:08:49:b6:bb:e5:91:ef:
+ 83:36:eb:1d:56:6f:ca:da:bc:73:63:90:e4:7f:7b:3e:22:cb:
+ 3d:07:ed:5f:38:74:9c:e3:03:50:4e:a1:af:98:ee:61:f2:84:
+ 3f:12
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
+WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
+AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
+OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
+T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
+JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
+Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
+PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
+aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
+TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
+LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
+BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
+dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
+AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
+NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
+b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert1[] = {
+ 0x30, 0x82, 0x03, 0x7d, 0x30, 0x82, 0x02, 0xe6, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x12, 0xbb, 0xe6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+ 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x32, 0x30,
+ 0x35, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d,
+ 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x30, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x12, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xda, 0xcc, 0x18, 0x63, 0x30, 0xfd,
+ 0xf4, 0x17, 0x23, 0x1a, 0x56, 0x7e, 0x5b, 0xdf, 0x3c, 0x6c, 0x38, 0xe4,
+ 0x71, 0xb7, 0x78, 0x91, 0xd4, 0xbc, 0xa1, 0xd8, 0x4c, 0xf8, 0xa8, 0x43,
+ 0xb6, 0x03, 0xe9, 0x4d, 0x21, 0x07, 0x08, 0x88, 0xda, 0x58, 0x2f, 0x66,
+ 0x39, 0x29, 0xbd, 0x05, 0x78, 0x8b, 0x9d, 0x38, 0xe8, 0x05, 0xb7, 0x6a,
+ 0x7e, 0x71, 0xa4, 0xe6, 0xc4, 0x60, 0xa6, 0xb0, 0xef, 0x80, 0xe4, 0x89,
+ 0x28, 0x0f, 0x9e, 0x25, 0xd6, 0xed, 0x83, 0xf3, 0xad, 0xa6, 0x91, 0xc7,
+ 0x98, 0xc9, 0x42, 0x18, 0x35, 0x14, 0x9d, 0xad, 0x98, 0x46, 0x92, 0x2e,
+ 0x4f, 0xca, 0xf1, 0x87, 0x43, 0xc1, 0x16, 0x95, 0x57, 0x2d, 0x50, 0xef,
+ 0x89, 0x2d, 0x80, 0x7a, 0x57, 0xad, 0xf2, 0xee, 0x5f, 0x6b, 0xd2, 0x00,
+ 0x8d, 0xb9, 0x14, 0xf8, 0x14, 0x15, 0x35, 0xd9, 0xc0, 0x46, 0xa3, 0x7b,
+ 0x72, 0xc8, 0x91, 0xbf, 0xc9, 0x55, 0x2b, 0xcd, 0xd0, 0x97, 0x3e, 0x9c,
+ 0x26, 0x64, 0xcc, 0xdf, 0xce, 0x83, 0x19, 0x71, 0xca, 0x4e, 0xe6, 0xd4,
+ 0xd5, 0x7b, 0xa9, 0x19, 0xcd, 0x55, 0xde, 0xc8, 0xec, 0xd2, 0x5e, 0x38,
+ 0x53, 0xe5, 0x5c, 0x4f, 0x8c, 0x2d, 0xfe, 0x50, 0x23, 0x36, 0xfc, 0x66,
+ 0xe6, 0xcb, 0x8e, 0xa4, 0x39, 0x19, 0x00, 0xb7, 0x95, 0x02, 0x39, 0x91,
+ 0x0b, 0x0e, 0xfe, 0x38, 0x2e, 0xd1, 0x1d, 0x05, 0x9a, 0xf6, 0x4d, 0x3e,
+ 0x6f, 0x0f, 0x07, 0x1d, 0xaf, 0x2c, 0x1e, 0x8f, 0x60, 0x39, 0xe2, 0xfa,
+ 0x36, 0x53, 0x13, 0x39, 0xd4, 0x5e, 0x26, 0x2b, 0xdb, 0x3d, 0xa8, 0x14,
+ 0xbd, 0x32, 0xeb, 0x18, 0x03, 0x28, 0x52, 0x04, 0x71, 0xe5, 0xab, 0x33,
+ 0x3d, 0xe1, 0x38, 0xbb, 0x07, 0x36, 0x84, 0x62, 0x9c, 0x79, 0xea, 0x16,
+ 0x30, 0xf4, 0x5f, 0xc0, 0x2b, 0xe8, 0x71, 0x6b, 0xe4, 0xf9, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x81, 0xf0, 0x30, 0x81, 0xed, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6,
+ 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10,
+ 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb,
+ 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc,
+ 0x4e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0,
+ 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65,
+ 0x63, 0x75, 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4e,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+ 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+ 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+ 0x00, 0x76, 0xe1, 0x12, 0x6e, 0x4e, 0x4b, 0x16, 0x12, 0x86, 0x30, 0x06,
+ 0xb2, 0x81, 0x08, 0xcf, 0xf0, 0x08, 0xc7, 0xc7, 0x71, 0x7e, 0x66, 0xee,
+ 0xc2, 0xed, 0xd4, 0x3b, 0x1f, 0xff, 0xf0, 0xf0, 0xc8, 0x4e, 0xd6, 0x43,
+ 0x38, 0xb0, 0xb9, 0x30, 0x7d, 0x18, 0xd0, 0x55, 0x83, 0xa2, 0x6a, 0xcb,
+ 0x36, 0x11, 0x9c, 0xe8, 0x48, 0x66, 0xa3, 0x6d, 0x7f, 0xb8, 0x13, 0xd4,
+ 0x47, 0xfe, 0x8b, 0x5a, 0x5c, 0x73, 0xfc, 0xae, 0xd9, 0x1b, 0x32, 0x19,
+ 0x38, 0xab, 0x97, 0x34, 0x14, 0xaa, 0x96, 0xd2, 0xeb, 0xa3, 0x1c, 0x14,
+ 0x08, 0x49, 0xb6, 0xbb, 0xe5, 0x91, 0xef, 0x83, 0x36, 0xeb, 0x1d, 0x56,
+ 0x6f, 0xca, 0xda, 0xbc, 0x73, 0x63, 0x90, 0xe4, 0x7f, 0x7b, 0x3e, 0x22,
+ 0xcb, 0x3d, 0x07, 0xed, 0x5f, 0x38, 0x74, 0x9c, 0xe3, 0x03, 0x50, 0x4e,
+ 0xa1, 0xaf, 0x98, 0xee, 0x61, 0xf2, 0x84, 0x3f, 0x12,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 46:fc:eb:ba:b4:d0:2f:0f:92:60:98:23:3f:93:07:8f
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Apr 17 00:00:00 1997 GMT
+ Not After : Oct 24 23:59:59 2016 GMT
+ Subject: O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign International Server CA - Class 3, OU=www.verisign.com/CPS Incorp.by Ref. LIABILITY LTD.(c)97 VeriSign
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:d8:82:80:e8:d6:19:02:7d:1f:85:18:39:25:a2:
+ 65:2b:e1:bf:d4:05:d3:bc:e6:36:3b:aa:f0:4c:6c:
+ 5b:b6:e7:aa:3c:73:45:55:b2:f1:bd:ea:97:42:ed:
+ 9a:34:0a:15:d4:a9:5c:f5:40:25:dd:d9:07:c1:32:
+ b2:75:6c:c4:ca:bb:a3:fe:56:27:71:43:aa:63:f5:
+ 30:3e:93:28:e5:fa:f1:09:3b:f3:b7:4d:4e:39:f7:
+ 5c:49:5a:b8:c1:1d:d3:b2:8a:fe:70:30:95:42:cb:
+ fe:2b:51:8b:5a:3c:3a:f9:22:4f:90:b2:02:a7:53:
+ 9c:4f:34:e7:ab:04:b2:7b:6f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.1.1
+ CPS: https://www.verisign.com/CPS
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 40:8e:49:97:96:8a:73:dd:8e:4d:ef:3e:61:b7:ca:a0:62:ad:
+ f4:0e:0a:bb:75:3d:e2:6e:d8:2c:c7:bf:f4:b9:8c:36:9b:ca:
+ a2:d0:9c:72:46:39:f6:a6:82:03:65:11:c4:bc:bf:2d:a6:f5:
+ d9:3b:0a:b5:98:fa:b3:78:b9:1e:f2:2b:4c:62:d5:fd:b2:7a:
+ 1d:df:33:fd:73:f9:a5:d8:2d:8c:2a:ea:d1:fc:b0:28:b6:e9:
+ 49:48:13:4b:83:8a:1b:48:7b:24:f7:38:de:6f:41:54:b8:ab:
+ 57:6b:06:df:c7:a2:d4:a9:f6:f1:36:62:80:88:f2:8b:75:d6:
+ 80:71
+-----BEGIN CERTIFICATE-----
+MIIDgzCCAuygAwIBAgIQRvzrurTQLw+SYJgjP5MHjzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNOTcwNDE3MDAwMDAwWhcNMTYxMDI0MjM1OTU5WjCBujEfMB0GA1UEChMWVmVy
+aVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIEluYy4xMzAx
+BgNVBAsTKlZlcmlTaWduIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gQ2xhc3Mg
+MzFJMEcGA1UECxNAd3d3LnZlcmlzaWduLmNvbS9DUFMgSW5jb3JwLmJ5IFJlZi4g
+TElBQklMSVRZIExURC4oYyk5NyBWZXJpU2lnbjCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA2IKA6NYZAn0fhRg5JaJlK+G/1AXTvOY2O6rwTGxbtueqPHNFVbLx
+veqXQu2aNAoV1Klc9UAl3dkHwTKydWzEyruj/lYncUOqY/UwPpMo5frxCTvzt01O
+OfdcSVq4wR3Tsor+cDCVQsv+K1GLWjw6+SJPkLICp1OcTzTnqwSye28CAwEAAaOB
+4zCB4DAPBgNVHRMECDAGAQH/AgEAMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw
+KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL0NQUzA0BgNV
+HSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG+EIEAQYKYIZIAYb4RQEI
+ATALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMDEGA1UdHwQqMCgwJqAk
+oCKGIGh0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA0GCSqGSIb3DQEB
+BQUAA4GBAECOSZeWinPdjk3vPmG3yqBirfQOCrt1PeJu2CzHv/S5jDabyqLQnHJG
+OfamggNlEcS8vy2m9dk7CrWY+rN4uR7yK0xi1f2yeh3fM/1z+aXYLYwq6tH8sCi2
+6UlIE0uDihtIeyT3ON5vQVS4q1drBt/HotSp9vE2YoCI8ot11oBx
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert2[] = {
+ 0x30, 0x82, 0x03, 0x83, 0x30, 0x82, 0x02, 0xec, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x46, 0xfc, 0xeb, 0xba, 0xb4, 0xd0, 0x2f, 0x0f, 0x92,
+ 0x60, 0x98, 0x23, 0x3f, 0x93, 0x07, 0x8f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x39, 0x37, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x30, 0x32, 0x34,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x1f,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x16, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69,
+ 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x56, 0x65, 0x72, 0x69, 0x53,
+ 0x69, 0x67, 0x6e, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+ 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x31, 0x49, 0x30, 0x47, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x40,
+ 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x49, 0x6e, 0x63,
+ 0x6f, 0x72, 0x70, 0x2e, 0x62, 0x79, 0x20, 0x52, 0x65, 0x66, 0x2e, 0x20,
+ 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x4c, 0x54,
+ 0x44, 0x2e, 0x28, 0x63, 0x29, 0x39, 0x37, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81,
+ 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd8, 0x82, 0x80,
+ 0xe8, 0xd6, 0x19, 0x02, 0x7d, 0x1f, 0x85, 0x18, 0x39, 0x25, 0xa2, 0x65,
+ 0x2b, 0xe1, 0xbf, 0xd4, 0x05, 0xd3, 0xbc, 0xe6, 0x36, 0x3b, 0xaa, 0xf0,
+ 0x4c, 0x6c, 0x5b, 0xb6, 0xe7, 0xaa, 0x3c, 0x73, 0x45, 0x55, 0xb2, 0xf1,
+ 0xbd, 0xea, 0x97, 0x42, 0xed, 0x9a, 0x34, 0x0a, 0x15, 0xd4, 0xa9, 0x5c,
+ 0xf5, 0x40, 0x25, 0xdd, 0xd9, 0x07, 0xc1, 0x32, 0xb2, 0x75, 0x6c, 0xc4,
+ 0xca, 0xbb, 0xa3, 0xfe, 0x56, 0x27, 0x71, 0x43, 0xaa, 0x63, 0xf5, 0x30,
+ 0x3e, 0x93, 0x28, 0xe5, 0xfa, 0xf1, 0x09, 0x3b, 0xf3, 0xb7, 0x4d, 0x4e,
+ 0x39, 0xf7, 0x5c, 0x49, 0x5a, 0xb8, 0xc1, 0x1d, 0xd3, 0xb2, 0x8a, 0xfe,
+ 0x70, 0x30, 0x95, 0x42, 0xcb, 0xfe, 0x2b, 0x51, 0x8b, 0x5a, 0x3c, 0x3a,
+ 0xf9, 0x22, 0x4f, 0x90, 0xb2, 0x02, 0xa7, 0x53, 0x9c, 0x4f, 0x34, 0xe7,
+ 0xab, 0x04, 0xb2, 0x7b, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+ 0xe3, 0x30, 0x81, 0xe0, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04,
+ 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x44, 0x06,
+ 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x0b,
+ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x01, 0x01, 0x30,
+ 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+ 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x34, 0x06, 0x03, 0x55,
+ 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+ 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08,
+ 0x01, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x06, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+ 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x31, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a, 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24,
+ 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x40, 0x8e, 0x49, 0x97, 0x96,
+ 0x8a, 0x73, 0xdd, 0x8e, 0x4d, 0xef, 0x3e, 0x61, 0xb7, 0xca, 0xa0, 0x62,
+ 0xad, 0xf4, 0x0e, 0x0a, 0xbb, 0x75, 0x3d, 0xe2, 0x6e, 0xd8, 0x2c, 0xc7,
+ 0xbf, 0xf4, 0xb9, 0x8c, 0x36, 0x9b, 0xca, 0xa2, 0xd0, 0x9c, 0x72, 0x46,
+ 0x39, 0xf6, 0xa6, 0x82, 0x03, 0x65, 0x11, 0xc4, 0xbc, 0xbf, 0x2d, 0xa6,
+ 0xf5, 0xd9, 0x3b, 0x0a, 0xb5, 0x98, 0xfa, 0xb3, 0x78, 0xb9, 0x1e, 0xf2,
+ 0x2b, 0x4c, 0x62, 0xd5, 0xfd, 0xb2, 0x7a, 0x1d, 0xdf, 0x33, 0xfd, 0x73,
+ 0xf9, 0xa5, 0xd8, 0x2d, 0x8c, 0x2a, 0xea, 0xd1, 0xfc, 0xb0, 0x28, 0xb6,
+ 0xe9, 0x49, 0x48, 0x13, 0x4b, 0x83, 0x8a, 0x1b, 0x48, 0x7b, 0x24, 0xf7,
+ 0x38, 0xde, 0x6f, 0x41, 0x54, 0xb8, 0xab, 0x57, 0x6b, 0x06, 0xdf, 0xc7,
+ 0xa2, 0xd4, 0xa9, 0xf6, 0xf1, 0x36, 0x62, 0x80, 0x88, 0xf2, 0x8b, 0x75,
+ 0xd6, 0x80, 0x71,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 429597 (0x68e1d)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+ Validity
+ Not Before: Nov 28 16:08:31 2006 GMT
+ Not After : Aug 21 15:08:31 2018 GMT
+ Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:be:b8:15:7b:ff:d4:7c:7d:67:ad:83:64:7b:c8:
+ 42:53:2d:df:f6:84:08:20:61:d6:01:59:6a:9c:44:
+ 11:af:ef:76:fd:95:7e:ce:61:30:bb:7a:83:5f:02:
+ bd:01:66:ca:ee:15:8d:6f:a1:30:9c:bd:a1:85:9e:
+ 94:3a:f3:56:88:00:31:cf:d8:ee:6a:96:02:d9:ed:
+ 03:8c:fb:75:6d:e7:ea:b8:55:16:05:16:9a:f4:e0:
+ 5e:b1:88:c0:64:85:5c:15:4d:88:c7:b7:ba:e0:75:
+ e9:ad:05:3d:9d:c7:89:48:e0:bb:28:c8:03:e1:30:
+ 93:64:5e:52:c0:59:70:22:35:57:88:8a:f1:95:0a:
+ 83:d7:bc:31:73:01:34:ed:ef:46:71:e0:6b:02:a8:
+ 35:72:6b:97:9b:66:e0:cb:1c:79:5f:d8:1a:04:68:
+ 1e:47:02:e6:9d:60:e2:36:97:01:df:ce:35:92:df:
+ be:67:c7:6d:77:59:3b:8f:9d:d6:90:15:94:bc:42:
+ 34:10:c1:39:f9:b1:27:3e:7e:d6:8a:75:c5:b2:af:
+ 96:d3:a2:de:9b:e4:98:be:7d:e1:e9:81:ad:b6:6f:
+ fc:d7:0e:da:e0:34:b0:0d:1a:77:e7:e3:08:98:ef:
+ 58:fa:9c:84:b7:36:af:c2:df:ac:d2:f4:10:06:70:
+ 71:35
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+ X509v3 Authority Key Identifier:
+ keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/secureca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.geotrust.com/resources/cps
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 7b:60:06:e9:dd:a7:1d:29:08:ef:11:f9:d5:3b:3c:d2:2b:53:
+ cb:3e:ed:be:76:60:64:48:a0:e6:cb:e8:49:c3:1a:bf:dd:ad:
+ c5:4c:bd:53:48:55:41:db:18:b1:4e:3b:3a:68:2c:24:5a:41:
+ f5:c8:a9:44:a6:32:29:2d:75:f8:4d:f2:50:8e:f0:e2:9b:e9:
+ e1:e4:3b:70:b7:32:89:db:a8:39:c5:5b:68:56:bd:04:15:c3:
+ b6:cb:1b:24:4a:a7:fc:c4:d5:8d:b6:98:dd:03:f6:b1:b3:94:
+ da:3f:52:a0:a4:50:06:ca:45:67:4e:ff:f1:41:89:40:00:36:
+ 7e:79
+-----BEGIN CERTIFICATE-----
+MIIDizCCAvSgAwIBAgIDBo4dMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMTI4MTYwODMxWhcNMTgwODIxMTUwODMx
+WjBYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UE
+AxMoR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64FXv/1Hx9Z62DZHvIQlMt3/aE
+CCBh1gFZapxEEa/vdv2Vfs5hMLt6g18CvQFmyu4VjW+hMJy9oYWelDrzVogAMc/Y
+7mqWAtntA4z7dW3n6rhVFgUWmvTgXrGIwGSFXBVNiMe3uuB16a0FPZ3HiUjguyjI
+A+Ewk2ReUsBZcCI1V4iK8ZUKg9e8MXMBNO3vRnHgawKoNXJrl5tm4MsceV/YGgRo
+HkcC5p1g4jaXAd/ONZLfvmfHbXdZO4+d1pAVlLxCNBDBOfmxJz5+1op1xbKvltOi
+3pvkmL594emBrbZv/NcO2uA0sA0ad+fjCJjvWPqchLc2r8LfrNL0EAZwcTUCAwEA
+AaOB6DCB5TAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCzVUEGXFYvwjzZhW0r7
+a9mZyTOSMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMA8GA1UdEwEB
+/wQFMAMBAf8wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9zZWN1cmVjYS5jcmwwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJ
+KoZIhvcNAQEFBQADgYEAe2AG6d2nHSkI7xH51Ts80itTyz7tvnZgZEig5svoScMa
+v92txUy9U0hVQdsYsU47OmgsJFpB9cipRKYyKS11+E3yUI7w4pvp4eQ7cLcyiduo
+OcVbaFa9BBXDtssbJEqn/MTVjbaY3QP2sbOU2j9SoKRQBspFZ07/8UGJQAA2fnk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert3[] = {
+ 0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x06, 0x8e, 0x1d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+ 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31,
+ 0x31, 0x32, 0x38, 0x31, 0x36, 0x30, 0x38, 0x33, 0x31, 0x5a, 0x17, 0x0d,
+ 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x35, 0x30, 0x38, 0x33, 0x31,
+ 0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d,
+ 0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84,
+ 0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef,
+ 0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02,
+ 0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd,
+ 0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8,
+ 0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7,
+ 0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88,
+ 0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75,
+ 0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8,
+ 0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35,
+ 0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01,
+ 0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b,
+ 0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68,
+ 0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce,
+ 0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d,
+ 0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1,
+ 0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2,
+ 0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f,
+ 0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3,
+ 0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf,
+ 0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5,
+ 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb,
+ 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b,
+ 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98,
+ 0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+ 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75,
+ 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x81, 0x81, 0x00, 0x7b, 0x60, 0x06, 0xe9, 0xdd, 0xa7, 0x1d, 0x29, 0x08,
+ 0xef, 0x11, 0xf9, 0xd5, 0x3b, 0x3c, 0xd2, 0x2b, 0x53, 0xcb, 0x3e, 0xed,
+ 0xbe, 0x76, 0x60, 0x64, 0x48, 0xa0, 0xe6, 0xcb, 0xe8, 0x49, 0xc3, 0x1a,
+ 0xbf, 0xdd, 0xad, 0xc5, 0x4c, 0xbd, 0x53, 0x48, 0x55, 0x41, 0xdb, 0x18,
+ 0xb1, 0x4e, 0x3b, 0x3a, 0x68, 0x2c, 0x24, 0x5a, 0x41, 0xf5, 0xc8, 0xa9,
+ 0x44, 0xa6, 0x32, 0x29, 0x2d, 0x75, 0xf8, 0x4d, 0xf2, 0x50, 0x8e, 0xf0,
+ 0xe2, 0x9b, 0xe9, 0xe1, 0xe4, 0x3b, 0x70, 0xb7, 0x32, 0x89, 0xdb, 0xa8,
+ 0x39, 0xc5, 0x5b, 0x68, 0x56, 0xbd, 0x04, 0x15, 0xc3, 0xb6, 0xcb, 0x1b,
+ 0x24, 0x4a, 0xa7, 0xfc, 0xc4, 0xd5, 0x8d, 0xb6, 0x98, 0xdd, 0x03, 0xf6,
+ 0xb1, 0xb3, 0x94, 0xda, 0x3f, 0x52, 0xa0, 0xa4, 0x50, 0x06, 0xca, 0x45,
+ 0x67, 0x4e, 0xff, 0xf1, 0x41, 0x89, 0x40, 0x00, 0x36, 0x7e, 0x79,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 880226 (0xd6e62)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
+ Validity
+ Not Before: Nov 27 00:00:00 2006 GMT
+ Not After : Aug 21 16:15:00 2018 GMT
+ Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:be:b8:15:7b:ff:d4:7c:7d:67:ad:83:64:7b:c8:
+ 42:53:2d:df:f6:84:08:20:61:d6:01:59:6a:9c:44:
+ 11:af:ef:76:fd:95:7e:ce:61:30:bb:7a:83:5f:02:
+ bd:01:66:ca:ee:15:8d:6f:a1:30:9c:bd:a1:85:9e:
+ 94:3a:f3:56:88:00:31:cf:d8:ee:6a:96:02:d9:ed:
+ 03:8c:fb:75:6d:e7:ea:b8:55:16:05:16:9a:f4:e0:
+ 5e:b1:88:c0:64:85:5c:15:4d:88:c7:b7:ba:e0:75:
+ e9:ad:05:3d:9d:c7:89:48:e0:bb:28:c8:03:e1:30:
+ 93:64:5e:52:c0:59:70:22:35:57:88:8a:f1:95:0a:
+ 83:d7:bc:31:73:01:34:ed:ef:46:71:e0:6b:02:a8:
+ 35:72:6b:97:9b:66:e0:cb:1c:79:5f:d8:1a:04:68:
+ 1e:47:02:e6:9d:60:e2:36:97:01:df:ce:35:92:df:
+ be:67:c7:6d:77:59:3b:8f:9d:d6:90:15:94:bc:42:
+ 34:10:c1:39:f9:b1:27:3e:7e:d6:8a:75:c5:b2:af:
+ 96:d3:a2:de:9b:e4:98:be:7d:e1:e9:81:ad:b6:6f:
+ fc:d7:0e:da:e0:34:b0:0d:1a:77:e7:e3:08:98:ef:
+ 58:fa:9c:84:b7:36:af:c2:df:ac:d2:f4:10:06:70:
+ 71:35
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+ X509v3 Authority Key Identifier:
+ keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/secureca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.geotrust.com/resources/cps
+
+ Signature Algorithm: sha1WithRSAEncryption
+ af:f3:0e:d6:72:ab:c7:a9:97:ca:2a:6b:84:39:de:79:a9:f0:
+ 81:e5:08:67:ab:d7:2f:20:02:01:71:0c:04:22:c9:1e:88:95:
+ 03:c9:49:3a:af:67:08:49:b0:d5:08:f5:20:3d:80:91:a0:c5:
+ 87:a3:fb:c9:a3:17:91:f9:a8:2f:ae:e9:0f:df:96:72:0f:75:
+ 17:80:5d:78:01:4d:9f:1f:6d:7b:d8:f5:42:38:23:1a:99:93:
+ f4:83:be:3b:35:74:e7:37:13:35:7a:ac:b4:b6:90:82:6c:27:
+ a4:e0:ec:9e:35:bd:bf:e5:29:a1:47:9f:5b:32:fc:e9:99:7d:
+ 2b:39
+-----BEGIN CERTIFICATE-----
+MIIDizCCAvSgAwIBAgIDDW5iMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
+MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
+aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMTI3MDAwMDAwWhcNMTgwODIxMTYxNTAw
+WjBYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UE
+AxMoR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64FXv/1Hx9Z62DZHvIQlMt3/aE
+CCBh1gFZapxEEa/vdv2Vfs5hMLt6g18CvQFmyu4VjW+hMJy9oYWelDrzVogAMc/Y
+7mqWAtntA4z7dW3n6rhVFgUWmvTgXrGIwGSFXBVNiMe3uuB16a0FPZ3HiUjguyjI
+A+Ewk2ReUsBZcCI1V4iK8ZUKg9e8MXMBNO3vRnHgawKoNXJrl5tm4MsceV/YGgRo
+HkcC5p1g4jaXAd/ONZLfvmfHbXdZO4+d1pAVlLxCNBDBOfmxJz5+1op1xbKvltOi
+3pvkmL594emBrbZv/NcO2uA0sA0ad+fjCJjvWPqchLc2r8LfrNL0EAZwcTUCAwEA
+AaOB6DCB5TAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFCzVUEGXFYvwjzZhW0r7
+a9mZyTOSMB8GA1UdIwQYMBaAFEjmaPkr0rKV10fYIyAQTzOYkJ/UMA8GA1UdEwEB
+/wQFMAMBAf8wOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9zZWN1cmVjYS5jcmwwRgYDVR0gBD8wPTA7BgRVHSAAMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwDQYJ
+KoZIhvcNAQEFBQADgYEAr/MO1nKrx6mXyiprhDneeanwgeUIZ6vXLyACAXEMBCLJ
+HoiVA8lJOq9nCEmw1Qj1ID2AkaDFh6P7yaMXkfmoL67pD9+Wcg91F4BdeAFNnx9t
+e9j1QjgjGpmT9IO+OzV05zcTNXqstLaQgmwnpODsnjW9v+UpoUefWzL86Zl9Kzk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert4[] = {
+ 0x30, 0x82, 0x03, 0x8b, 0x30, 0x82, 0x02, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x0d, 0x6e, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+ 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31,
+ 0x31, 0x32, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d,
+ 0x31, 0x38, 0x30, 0x38, 0x32, 0x31, 0x31, 0x36, 0x31, 0x35, 0x30, 0x30,
+ 0x5a, 0x30, 0x58, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x28, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xbe, 0xb8, 0x15, 0x7b, 0xff, 0xd4, 0x7c, 0x7d,
+ 0x67, 0xad, 0x83, 0x64, 0x7b, 0xc8, 0x42, 0x53, 0x2d, 0xdf, 0xf6, 0x84,
+ 0x08, 0x20, 0x61, 0xd6, 0x01, 0x59, 0x6a, 0x9c, 0x44, 0x11, 0xaf, 0xef,
+ 0x76, 0xfd, 0x95, 0x7e, 0xce, 0x61, 0x30, 0xbb, 0x7a, 0x83, 0x5f, 0x02,
+ 0xbd, 0x01, 0x66, 0xca, 0xee, 0x15, 0x8d, 0x6f, 0xa1, 0x30, 0x9c, 0xbd,
+ 0xa1, 0x85, 0x9e, 0x94, 0x3a, 0xf3, 0x56, 0x88, 0x00, 0x31, 0xcf, 0xd8,
+ 0xee, 0x6a, 0x96, 0x02, 0xd9, 0xed, 0x03, 0x8c, 0xfb, 0x75, 0x6d, 0xe7,
+ 0xea, 0xb8, 0x55, 0x16, 0x05, 0x16, 0x9a, 0xf4, 0xe0, 0x5e, 0xb1, 0x88,
+ 0xc0, 0x64, 0x85, 0x5c, 0x15, 0x4d, 0x88, 0xc7, 0xb7, 0xba, 0xe0, 0x75,
+ 0xe9, 0xad, 0x05, 0x3d, 0x9d, 0xc7, 0x89, 0x48, 0xe0, 0xbb, 0x28, 0xc8,
+ 0x03, 0xe1, 0x30, 0x93, 0x64, 0x5e, 0x52, 0xc0, 0x59, 0x70, 0x22, 0x35,
+ 0x57, 0x88, 0x8a, 0xf1, 0x95, 0x0a, 0x83, 0xd7, 0xbc, 0x31, 0x73, 0x01,
+ 0x34, 0xed, 0xef, 0x46, 0x71, 0xe0, 0x6b, 0x02, 0xa8, 0x35, 0x72, 0x6b,
+ 0x97, 0x9b, 0x66, 0xe0, 0xcb, 0x1c, 0x79, 0x5f, 0xd8, 0x1a, 0x04, 0x68,
+ 0x1e, 0x47, 0x02, 0xe6, 0x9d, 0x60, 0xe2, 0x36, 0x97, 0x01, 0xdf, 0xce,
+ 0x35, 0x92, 0xdf, 0xbe, 0x67, 0xc7, 0x6d, 0x77, 0x59, 0x3b, 0x8f, 0x9d,
+ 0xd6, 0x90, 0x15, 0x94, 0xbc, 0x42, 0x34, 0x10, 0xc1, 0x39, 0xf9, 0xb1,
+ 0x27, 0x3e, 0x7e, 0xd6, 0x8a, 0x75, 0xc5, 0xb2, 0xaf, 0x96, 0xd3, 0xa2,
+ 0xde, 0x9b, 0xe4, 0x98, 0xbe, 0x7d, 0xe1, 0xe9, 0x81, 0xad, 0xb6, 0x6f,
+ 0xfc, 0xd7, 0x0e, 0xda, 0xe0, 0x34, 0xb0, 0x0d, 0x1a, 0x77, 0xe7, 0xe3,
+ 0x08, 0x98, 0xef, 0x58, 0xfa, 0x9c, 0x84, 0xb7, 0x36, 0xaf, 0xc2, 0xdf,
+ 0xac, 0xd2, 0xf4, 0x10, 0x06, 0x70, 0x71, 0x35, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x81, 0xe8, 0x30, 0x81, 0xe5, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0xd5,
+ 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b, 0x4a, 0xfb,
+ 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b,
+ 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8, 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98,
+ 0x90, 0x9f, 0xd4, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3a, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+ 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x46, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75,
+ 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x81, 0x81, 0x00, 0xaf, 0xf3, 0x0e, 0xd6, 0x72, 0xab, 0xc7, 0xa9, 0x97,
+ 0xca, 0x2a, 0x6b, 0x84, 0x39, 0xde, 0x79, 0xa9, 0xf0, 0x81, 0xe5, 0x08,
+ 0x67, 0xab, 0xd7, 0x2f, 0x20, 0x02, 0x01, 0x71, 0x0c, 0x04, 0x22, 0xc9,
+ 0x1e, 0x88, 0x95, 0x03, 0xc9, 0x49, 0x3a, 0xaf, 0x67, 0x08, 0x49, 0xb0,
+ 0xd5, 0x08, 0xf5, 0x20, 0x3d, 0x80, 0x91, 0xa0, 0xc5, 0x87, 0xa3, 0xfb,
+ 0xc9, 0xa3, 0x17, 0x91, 0xf9, 0xa8, 0x2f, 0xae, 0xe9, 0x0f, 0xdf, 0x96,
+ 0x72, 0x0f, 0x75, 0x17, 0x80, 0x5d, 0x78, 0x01, 0x4d, 0x9f, 0x1f, 0x6d,
+ 0x7b, 0xd8, 0xf5, 0x42, 0x38, 0x23, 0x1a, 0x99, 0x93, 0xf4, 0x83, 0xbe,
+ 0x3b, 0x35, 0x74, 0xe7, 0x37, 0x13, 0x35, 0x7a, 0xac, 0xb4, 0xb6, 0x90,
+ 0x82, 0x6c, 0x27, 0xa4, 0xe0, 0xec, 0x9e, 0x35, 0xbd, 0xbf, 0xe5, 0x29,
+ 0xa1, 0x47, 0x9f, 0x5b, 0x32, 0xfc, 0xe9, 0x99, 0x7d, 0x2b, 0x39,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120020005 (0x7275c25)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Feb 24 20:05:10 2010 GMT
+ Not After : Aug 13 23:59:00 2018 GMT
+ Subject: C=JP, O=Cybertrust Japan Co., Ltd., CN=Cybertrust Japan Public CA G1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:be:bf:b6:ed:e4:f1:4c:02:98:0b:5a:7c:c1:69:
+ 18:20:48:92:03:08:12:d2:52:39:cf:f4:cc:9f:6c:
+ ea:3a:ab:ff:46:db:f4:dd:e5:31:ed:01:89:90:ac:
+ 44:75:a6:31:5f:a3:41:89:36:97:ab:f4:43:7d:4c:
+ 18:15:5e:73:ca:aa:af:72:ff:4a:98:cc:95:24:5a:
+ 8d:9f:67:98:c6:ce:a1:e6:48:51:f9:8b:3c:b4:32:
+ 82:ec:15:a9:7b:35:a6:87:3e:84:2e:21:4b:6d:d1:
+ 7a:ed:8c:cb:a8:e1:88:0f:1c:75:77:79:45:2b:7b:
+ cb:6f:bb:08:94:75:fd:5a:c9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ 5A:84:4B:BB:97:58:E2:42:E0:8C:AA:9C:A9:BD:62:07:6C:E4:96:AB
+ Signature Algorithm: sha1WithRSAEncryption
+ 31:53:e8:7c:8b:09:f4:98:77:0b:07:05:e4:00:3e:f3:4a:af:
+ 3c:fe:ea:e0:99:aa:4e:f2:ce:c1:94:af:3d:c0:0b:68:a7:fd:
+ 6f:e8:2f:70:6d:22:59:b5:7e:f5:62:c5:36:af:e9:0b:4c:39:
+ 8f:80:4e:a4:37:49:69:78:35:32:d6:b5:f9:f9:48:bc:98:16:
+ fc:ff:3e:fa:df:f8:4b:3e:66:72:4f:02:1c:f8:d2:12:f4:bd:
+ 4c:ed:56:b0:a2:73:c8:4f:82:ce:0d:b4:c4:af:0e:43:70:6e:
+ 08:d4:ec:b0:c1:c4:7f:75:18:99:76:7d:68:d2:13:6c:37:52:
+ 34:56
+-----BEGIN CERTIFICATE-----
+MIIDuTCCAyKgAwIBAgIEBydcJTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDIyNDIwMDUxMFoXDTE4MDgxMzIzNTkwMFowWjELMAkG
+A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMSYw
+JAYDVQQDEx1DeWJlcnRydXN0IEphcGFuIFB1YmxpYyBDQSBHMTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAvr+27eTxTAKYC1p8wWkYIEiSAwgS0lI5z/TMn2zq
+Oqv/Rtv03eUx7QGJkKxEdaYxX6NBiTaXq/RDfUwYFV5zyqqvcv9KmMyVJFqNn2eY
+xs6h5khR+Ys8tDKC7BWpezWmhz6ELiFLbdF67YzLqOGIDxx1d3lFK3vLb7sIlHX9
+WskCAwEAAaOCAW8wggFrMBIGA1UdEwEB/wQIMAYBAf8CAQAwUwYDVR0gBEwwSjBI
+BgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYtaHR0cDovL2N5YmVydHJ1c3Qub21u
+aXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4GA1UdDwEB/wQEAwIBBjCBiQYDVR0j
+BIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRp
+b24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDww
+OqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8y
+MDE4L2NkcC5jcmwwHQYDVR0OBBYEFFqES7uXWOJC4IyqnKm9Ygds5JarMA0GCSqG
+SIb3DQEBBQUAA4GBADFT6HyLCfSYdwsHBeQAPvNKrzz+6uCZqk7yzsGUrz3AC2in
+/W/oL3BtIlm1fvVixTav6QtMOY+ATqQ3SWl4NTLWtfn5SLyYFvz/Pvrf+Es+ZnJP
+Ahz40hL0vUztVrCic8hPgs4NtMSvDkNwbgjU7LDBxH91GJl2fWjSE2w3UjRW
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert5[] = {
+ 0x30, 0x82, 0x03, 0xb9, 0x30, 0x82, 0x03, 0x22, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x5c, 0x25, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x30, 0x30, 0x32, 0x32, 0x34, 0x32, 0x30, 0x30, 0x35, 0x31,
+ 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x32, 0x33,
+ 0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1a, 0x43, 0x79, 0x62, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20,
+ 0x43, 0x6f, 0x2e, 0x2c, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x26, 0x30,
+ 0x24, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1d, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x47,
+ 0x31, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+ 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xbe, 0xbf, 0xb6, 0xed, 0xe4, 0xf1,
+ 0x4c, 0x02, 0x98, 0x0b, 0x5a, 0x7c, 0xc1, 0x69, 0x18, 0x20, 0x48, 0x92,
+ 0x03, 0x08, 0x12, 0xd2, 0x52, 0x39, 0xcf, 0xf4, 0xcc, 0x9f, 0x6c, 0xea,
+ 0x3a, 0xab, 0xff, 0x46, 0xdb, 0xf4, 0xdd, 0xe5, 0x31, 0xed, 0x01, 0x89,
+ 0x90, 0xac, 0x44, 0x75, 0xa6, 0x31, 0x5f, 0xa3, 0x41, 0x89, 0x36, 0x97,
+ 0xab, 0xf4, 0x43, 0x7d, 0x4c, 0x18, 0x15, 0x5e, 0x73, 0xca, 0xaa, 0xaf,
+ 0x72, 0xff, 0x4a, 0x98, 0xcc, 0x95, 0x24, 0x5a, 0x8d, 0x9f, 0x67, 0x98,
+ 0xc6, 0xce, 0xa1, 0xe6, 0x48, 0x51, 0xf9, 0x8b, 0x3c, 0xb4, 0x32, 0x82,
+ 0xec, 0x15, 0xa9, 0x7b, 0x35, 0xa6, 0x87, 0x3e, 0x84, 0x2e, 0x21, 0x4b,
+ 0x6d, 0xd1, 0x7a, 0xed, 0x8c, 0xcb, 0xa8, 0xe1, 0x88, 0x0f, 0x1c, 0x75,
+ 0x77, 0x79, 0x45, 0x2b, 0x7b, 0xcb, 0x6f, 0xbb, 0x08, 0x94, 0x75, 0xfd,
+ 0x5a, 0xc9, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6f, 0x30,
+ 0x82, 0x01, 0x6b, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48,
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30,
+ 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+ 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79,
+ 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e,
+ 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+ 0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+ 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+ 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79,
+ 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5,
+ 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30,
+ 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+ 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+ 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32,
+ 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5a, 0x84,
+ 0x4b, 0xbb, 0x97, 0x58, 0xe2, 0x42, 0xe0, 0x8c, 0xaa, 0x9c, 0xa9, 0xbd,
+ 0x62, 0x07, 0x6c, 0xe4, 0x96, 0xab, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+ 0x00, 0x31, 0x53, 0xe8, 0x7c, 0x8b, 0x09, 0xf4, 0x98, 0x77, 0x0b, 0x07,
+ 0x05, 0xe4, 0x00, 0x3e, 0xf3, 0x4a, 0xaf, 0x3c, 0xfe, 0xea, 0xe0, 0x99,
+ 0xaa, 0x4e, 0xf2, 0xce, 0xc1, 0x94, 0xaf, 0x3d, 0xc0, 0x0b, 0x68, 0xa7,
+ 0xfd, 0x6f, 0xe8, 0x2f, 0x70, 0x6d, 0x22, 0x59, 0xb5, 0x7e, 0xf5, 0x62,
+ 0xc5, 0x36, 0xaf, 0xe9, 0x0b, 0x4c, 0x39, 0x8f, 0x80, 0x4e, 0xa4, 0x37,
+ 0x49, 0x69, 0x78, 0x35, 0x32, 0xd6, 0xb5, 0xf9, 0xf9, 0x48, 0xbc, 0x98,
+ 0x16, 0xfc, 0xff, 0x3e, 0xfa, 0xdf, 0xf8, 0x4b, 0x3e, 0x66, 0x72, 0x4f,
+ 0x02, 0x1c, 0xf8, 0xd2, 0x12, 0xf4, 0xbd, 0x4c, 0xed, 0x56, 0xb0, 0xa2,
+ 0x73, 0xc8, 0x4f, 0x82, 0xce, 0x0d, 0xb4, 0xc4, 0xaf, 0x0e, 0x43, 0x70,
+ 0x6e, 0x08, 0xd4, 0xec, 0xb0, 0xc1, 0xc4, 0x7f, 0x75, 0x18, 0x99, 0x76,
+ 0x7d, 0x68, 0xd2, 0x13, 0x6c, 0x37, 0x52, 0x34, 0x56,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 67109891 (0x4000403)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: May 11 15:32:00 2006 GMT
+ Not After : May 11 23:59:00 2013 GMT
+ Subject: C=US, O=Akamai Technologies Inc, CN=Akamai Subordinate CA 3
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:9d:34:76:73:b3:26:44:c4:60:cc:76:5f:8f:d8:
+ 2f:4b:3a:12:56:8c:6d:d5:b4:e2:ac:0c:e1:47:8a:
+ 85:43:12:bc:03:66:85:20:1d:6b:8a:74:72:38:85:
+ 61:a9:73:0b:57:5b:db:c5:9e:b3:66:c5:51:f8:0a:
+ 90:7c:f8:74:14:72:12:80:f4:e8:5a:cd:c8:bb:11:
+ 14:c9:44:2f:ec:e1:af:33:c1:59:29:dd:4c:85:7b:
+ 1c:80:dd:46:a5:64:cf:60:ef:4f:55:93:3e:05:a9:
+ 16:24:2b:48:ff:9f:05:92:de:0c:e7:9f:60:df:54:
+ 6f:a7:16:ee:ff:af:61:a9:9d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ BE:39:BF:41:66:FA:D4:CE:8B:6E:78:A3:49:7E:DE:3D:C4:2E:2B:F6
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://www.public-trust.com/CPS/OmniRoot.html
+
+ X509v3 Authority Key Identifier:
+ keyid:A6:0C:1D:9F:61:FF:07:17:B5:BF:38:46:DB:43:30:D5:8E:B0:52:06
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 Key Usage: critical
+ Digital Signature, Non Repudiation, Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Signature Algorithm: sha1WithRSAEncryption
+ 76:87:d3:ae:4d:3d:c4:6b:28:e1:52:1f:79:81:1e:e9:62:1a:
+ f7:4f:d9:1a:c0:e5:05:11:fa:77:f9:ff:b1:25:17:5e:ca:19:
+ c8:ac:cc:dc:71:95:ce:cf:66:02:60:c1:7e:ff:ec:d9:b6:70:
+ e1:03:60:33:43:0c:36:55:8d:30:97:5d:5d:97:09:6d:9d:78:
+ 33:a5:56:84:a6:28:b8:a1:19:9d:a0:2c:48:27:be:5c:7b:05:
+ d2:16:94:7c:e9:f1:a6:3e:29:ec:26:63:fc:39:c6:65:50:7c:
+ 52:1f:76:39:16:b4:97:26:39:ab:8e:1d:fd:b5:7a:c0:3a:1d:
+ 3b:7f
+-----BEGIN CERTIFICATE-----
+MIIDxzCCAzCgAwIBAgIEBAAEAzANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA2MDUxMTE1MzIwMFoXDTEzMDUxMTIzNTkwMFowUTELMAkG
+A1UEBhMCVVMxIDAeBgNVBAoTF0FrYW1haSBUZWNobm9sb2dpZXMgSW5jMSAwHgYD
+VQQDExdBa2FtYWkgU3Vib3JkaW5hdGUgQ0EgMzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEAnTR2c7MmRMRgzHZfj9gvSzoSVoxt1bTirAzhR4qFQxK8A2aFIB1r
+inRyOIVhqXMLV1vbxZ6zZsVR+AqQfPh0FHISgPToWs3IuxEUyUQv7OGvM8FZKd1M
+hXscgN1GpWTPYO9PVZM+BakWJCtI/58Fkt4M559g31Rvpxbu/69hqZ0CAwEAAaOC
+AYYwggGCMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0
+LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFL45v0Fm+tTO
+i254o0l+3j3ELiv2MFMGA1UdIARMMEowSAYJKwYBBAGxPgEAMDswOQYIKwYBBQUH
+AgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMvT21uaVJvb3QuaHRt
+bDCBoAYDVR0jBIGYMIGVgBSmDB2fYf8HF7W/OEbbQzDVjrBSBqF5pHcwdTELMAkG
+A1UEBhMCVVMxGDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEnMCUGA1UECxMeR1RF
+IEN5YmVyVHJ1c3QgU29sdXRpb25zLCBJbmMuMSMwIQYDVQQDExpHVEUgQ3liZXJU
+cnVzdCBHbG9iYWwgUm9vdIICAaUwDgYDVR0PAQH/BAQDAgHGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwDQYJKoZIhvcNAQEFBQADgYEAdofTrk09xGso4VIfeYEe6WIa90/Z
+GsDlBRH6d/n/sSUXXsoZyKzM3HGVzs9mAmDBfv/s2bZw4QNgM0MMNlWNMJddXZcJ
+bZ14M6VWhKYouKEZnaAsSCe+XHsF0haUfOnxpj4p7CZj/DnGZVB8Uh92ORa0lyY5
+q44d/bV6wDodO38=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert6[] = {
+ 0x30, 0x82, 0x03, 0xc7, 0x30, 0x82, 0x03, 0x30, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x04, 0x00, 0x04, 0x03, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x30, 0x36, 0x30, 0x35, 0x31, 0x31, 0x31, 0x35, 0x33, 0x32, 0x30,
+ 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x33, 0x30, 0x35, 0x31, 0x31, 0x32, 0x33,
+ 0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x51, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x20, 0x30, 0x1e,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x17, 0x41, 0x6b, 0x61, 0x6d, 0x61,
+ 0x69, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69,
+ 0x65, 0x73, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x17, 0x41, 0x6b, 0x61, 0x6d, 0x61, 0x69, 0x20,
+ 0x53, 0x75, 0x62, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x20,
+ 0x43, 0x41, 0x20, 0x33, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81,
+ 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0x9d, 0x34, 0x76,
+ 0x73, 0xb3, 0x26, 0x44, 0xc4, 0x60, 0xcc, 0x76, 0x5f, 0x8f, 0xd8, 0x2f,
+ 0x4b, 0x3a, 0x12, 0x56, 0x8c, 0x6d, 0xd5, 0xb4, 0xe2, 0xac, 0x0c, 0xe1,
+ 0x47, 0x8a, 0x85, 0x43, 0x12, 0xbc, 0x03, 0x66, 0x85, 0x20, 0x1d, 0x6b,
+ 0x8a, 0x74, 0x72, 0x38, 0x85, 0x61, 0xa9, 0x73, 0x0b, 0x57, 0x5b, 0xdb,
+ 0xc5, 0x9e, 0xb3, 0x66, 0xc5, 0x51, 0xf8, 0x0a, 0x90, 0x7c, 0xf8, 0x74,
+ 0x14, 0x72, 0x12, 0x80, 0xf4, 0xe8, 0x5a, 0xcd, 0xc8, 0xbb, 0x11, 0x14,
+ 0xc9, 0x44, 0x2f, 0xec, 0xe1, 0xaf, 0x33, 0xc1, 0x59, 0x29, 0xdd, 0x4c,
+ 0x85, 0x7b, 0x1c, 0x80, 0xdd, 0x46, 0xa5, 0x64, 0xcf, 0x60, 0xef, 0x4f,
+ 0x55, 0x93, 0x3e, 0x05, 0xa9, 0x16, 0x24, 0x2b, 0x48, 0xff, 0x9f, 0x05,
+ 0x92, 0xde, 0x0c, 0xe7, 0x9f, 0x60, 0xdf, 0x54, 0x6f, 0xa7, 0x16, 0xee,
+ 0xff, 0xaf, 0x61, 0xa9, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0x86, 0x30, 0x82, 0x01, 0x82, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+ 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+ 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+ 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0xbe, 0x39, 0xbf, 0x41, 0x66, 0xfa, 0xd4, 0xce,
+ 0x8b, 0x6e, 0x78, 0xa3, 0x49, 0x7e, 0xde, 0x3d, 0xc4, 0x2e, 0x2b, 0xf6,
+ 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30,
+ 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+ 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x2f,
+ 0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x68, 0x74, 0x6d,
+ 0x6c, 0x30, 0x81, 0xa0, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x98,
+ 0x30, 0x81, 0x95, 0x80, 0x14, 0xa6, 0x0c, 0x1d, 0x9f, 0x61, 0xff, 0x07,
+ 0x17, 0xb5, 0xbf, 0x38, 0x46, 0xdb, 0x43, 0x30, 0xd5, 0x8e, 0xb0, 0x52,
+ 0x06, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27,
+ 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45,
+ 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20,
+ 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0xc6,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x81, 0x81, 0x00, 0x76, 0x87, 0xd3, 0xae, 0x4d, 0x3d, 0xc4, 0x6b, 0x28,
+ 0xe1, 0x52, 0x1f, 0x79, 0x81, 0x1e, 0xe9, 0x62, 0x1a, 0xf7, 0x4f, 0xd9,
+ 0x1a, 0xc0, 0xe5, 0x05, 0x11, 0xfa, 0x77, 0xf9, 0xff, 0xb1, 0x25, 0x17,
+ 0x5e, 0xca, 0x19, 0xc8, 0xac, 0xcc, 0xdc, 0x71, 0x95, 0xce, 0xcf, 0x66,
+ 0x02, 0x60, 0xc1, 0x7e, 0xff, 0xec, 0xd9, 0xb6, 0x70, 0xe1, 0x03, 0x60,
+ 0x33, 0x43, 0x0c, 0x36, 0x55, 0x8d, 0x30, 0x97, 0x5d, 0x5d, 0x97, 0x09,
+ 0x6d, 0x9d, 0x78, 0x33, 0xa5, 0x56, 0x84, 0xa6, 0x28, 0xb8, 0xa1, 0x19,
+ 0x9d, 0xa0, 0x2c, 0x48, 0x27, 0xbe, 0x5c, 0x7b, 0x05, 0xd2, 0x16, 0x94,
+ 0x7c, 0xe9, 0xf1, 0xa6, 0x3e, 0x29, 0xec, 0x26, 0x63, 0xfc, 0x39, 0xc6,
+ 0x65, 0x50, 0x7c, 0x52, 0x1f, 0x76, 0x39, 0x16, 0xb4, 0x97, 0x26, 0x39,
+ 0xab, 0x8e, 0x1d, 0xfd, 0xb5, 0x7a, 0xc0, 0x3a, 0x1d, 0x3b, 0x7f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 145105 (0x236d1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+ Validity
+ Not Before: Feb 19 22:45:05 2010 GMT
+ Not After : Feb 18 22:45:05 2020 GMT
+ Subject: C=US, O=GeoTrust, Inc., CN=RapidSSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c7:71:f8:56:c7:1e:d9:cc:b5:ad:f6:b4:97:a3:
+ fb:a1:e6:0b:50:5f:50:aa:3a:da:0f:fc:3d:29:24:
+ 43:c6:10:29:c1:fc:55:40:72:ee:bd:ea:df:9f:b6:
+ 41:f4:48:4b:c8:6e:fe:4f:57:12:8b:5b:fa:92:dd:
+ 5e:e8:ad:f3:f0:1b:b1:7b:4d:fb:cf:fd:d1:e5:f8:
+ e3:dc:e7:f5:73:7f:df:01:49:cf:8c:56:c1:bd:37:
+ e3:5b:be:b5:4f:8b:8b:f0:da:4f:c7:e3:dd:55:47:
+ 69:df:f2:5b:7b:07:4f:3d:e5:ac:21:c1:c8:1d:7a:
+ e8:e7:f6:0f:a1:aa:f5:6f:de:a8:65:4f:10:89:9c:
+ 03:f3:89:7a:a5:5e:01:72:33:ed:a9:e9:5a:1e:79:
+ f3:87:c8:df:c8:c5:fc:37:c8:9a:9a:d7:b8:76:cc:
+ b0:3e:e7:fd:e6:54:ea:df:5f:52:41:78:59:57:ad:
+ f1:12:d6:7f:bc:d5:9f:70:d3:05:6c:fa:a3:7d:67:
+ 58:dd:26:62:1d:31:92:0c:79:79:1c:8e:cf:ca:7b:
+ c1:66:af:a8:74:48:fb:8e:82:c2:9e:2c:99:5c:7b:
+ 2d:5d:9b:bc:5b:57:9e:7c:3a:7a:13:ad:f2:a3:18:
+ 5b:2b:59:0f:cd:5c:3a:eb:68:33:c6:28:1d:82:d1:
+ 50:8b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 6B:69:3D:6A:18:42:4A:DD:8F:02:65:39:FD:35:24:86:78:91:16:30
+ X509v3 Authority Key Identifier:
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.geotrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ ab:bc:bc:0a:5d:18:94:e3:c1:b1:c3:a8:4c:55:d6:be:b4:98:
+ f1:ee:3c:1c:cd:cf:f3:24:24:5c:96:03:27:58:fc:36:ae:a2:
+ 2f:8f:f1:fe:da:2b:02:c3:33:bd:c8:dd:48:22:2b:60:0f:a5:
+ 03:10:fd:77:f8:d0:ed:96:67:4f:fd:ea:47:20:70:54:dc:a9:
+ 0c:55:7e:e1:96:25:8a:d9:b5:da:57:4a:be:8d:8e:49:43:63:
+ a5:6c:4e:27:87:25:eb:5b:6d:fe:a2:7f:38:28:e0:36:ab:ad:
+ 39:a5:a5:62:c4:b7:5c:58:2c:aa:5d:01:60:a6:62:67:a3:c0:
+ c7:62:23:f4:e7:6c:46:ee:b5:d3:80:6a:22:13:d2:2d:3f:74:
+ 4f:ea:af:8c:5f:b4:38:9c:db:ae:ce:af:84:1e:a6:f6:34:51:
+ 59:79:d3:e3:75:dc:bc:d7:f3:73:df:92:ec:d2:20:59:6f:9c:
+ fb:95:f8:92:76:18:0a:7c:0f:2c:a6:ca:de:8a:62:7b:d8:f3:
+ ce:5f:68:bd:8f:3e:c1:74:bb:15:72:3a:16:83:a9:0b:e6:4d:
+ 99:9c:d8:57:ec:a8:01:51:c7:6f:57:34:5e:ab:4a:2c:42:f6:
+ 4f:1c:89:78:de:26:4e:f5:6f:93:4c:15:6b:27:56:4d:00:54:
+ 6c:7a:b7:b7
+-----BEGIN CERTIFICATE-----
+MIID1TCCAr2gAwIBAgIDAjbRMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjE5MjI0NTA1WhcNMjAwMjE4MjI0NTA1WjA8MQswCQYDVQQG
+EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xFDASBgNVBAMTC1JhcGlkU1NM
+IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3H4Vsce2cy1rfa0
+l6P7oeYLUF9QqjraD/w9KSRDxhApwfxVQHLuverfn7ZB9EhLyG7+T1cSi1v6kt1e
+6K3z8Buxe037z/3R5fjj3Of1c3/fAUnPjFbBvTfjW761T4uL8NpPx+PdVUdp3/Jb
+ewdPPeWsIcHIHXro5/YPoar1b96oZU8QiZwD84l6pV4BcjPtqelaHnnzh8jfyMX8
+N8iamte4dsywPuf95lTq319SQXhZV63xEtZ/vNWfcNMFbPqjfWdY3SZiHTGSDHl5
+HI7PynvBZq+odEj7joLCniyZXHstXZu8W1eefDp6E63yoxhbK1kPzVw662gzxigd
+gtFQiwIDAQABo4HZMIHWMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUa2k9ahhC
+St2PAmU5/TUkhniRFjAwHwYDVR0jBBgwFoAUwHqYaI2J+6sFZAwRfap9ZbjKzE4w
+EgYDVR0TAQH/BAgwBgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
+Lmdlb3RydXN0LmNvbS9jcmxzL2d0Z2xvYmFsLmNybDA0BggrBgEFBQcBAQQoMCYw
+JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdlb3RydXN0LmNvbTANBgkqhkiG9w0B
+AQUFAAOCAQEAq7y8Cl0YlOPBscOoTFXWvrSY8e48HM3P8yQkXJYDJ1j8Nq6iL4/x
+/torAsMzvcjdSCIrYA+lAxD9d/jQ7ZZnT/3qRyBwVNypDFV+4ZYlitm12ldKvo2O
+SUNjpWxOJ4cl61tt/qJ/OCjgNqutOaWlYsS3XFgsql0BYKZiZ6PAx2Ij9OdsRu61
+04BqIhPSLT90T+qvjF+0OJzbrs6vhB6m9jRRWXnT43XcvNfzc9+S7NIgWW+c+5X4
+knYYCnwPLKbK3opie9jzzl9ovY8+wXS7FXI6FoOpC+ZNmZzYV+yoAVHHb1c0XqtK
+LEL2TxyJeN4mTvVvk0wVaydWTQBUbHq3tw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert7[] = {
+ 0x30, 0x82, 0x03, 0xd5, 0x30, 0x82, 0x02, 0xbd, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x02, 0x36, 0xd1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+ 0x32, 0x31, 0x39, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35, 0x5a, 0x17, 0x0d,
+ 0x32, 0x30, 0x30, 0x32, 0x31, 0x38, 0x32, 0x32, 0x34, 0x35, 0x30, 0x35,
+ 0x5a, 0x30, 0x3c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2c,
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0b, 0x52, 0x61, 0x70, 0x69, 0x64, 0x53, 0x53, 0x4c,
+ 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xc7, 0x71, 0xf8, 0x56, 0xc7, 0x1e, 0xd9, 0xcc, 0xb5, 0xad, 0xf6, 0xb4,
+ 0x97, 0xa3, 0xfb, 0xa1, 0xe6, 0x0b, 0x50, 0x5f, 0x50, 0xaa, 0x3a, 0xda,
+ 0x0f, 0xfc, 0x3d, 0x29, 0x24, 0x43, 0xc6, 0x10, 0x29, 0xc1, 0xfc, 0x55,
+ 0x40, 0x72, 0xee, 0xbd, 0xea, 0xdf, 0x9f, 0xb6, 0x41, 0xf4, 0x48, 0x4b,
+ 0xc8, 0x6e, 0xfe, 0x4f, 0x57, 0x12, 0x8b, 0x5b, 0xfa, 0x92, 0xdd, 0x5e,
+ 0xe8, 0xad, 0xf3, 0xf0, 0x1b, 0xb1, 0x7b, 0x4d, 0xfb, 0xcf, 0xfd, 0xd1,
+ 0xe5, 0xf8, 0xe3, 0xdc, 0xe7, 0xf5, 0x73, 0x7f, 0xdf, 0x01, 0x49, 0xcf,
+ 0x8c, 0x56, 0xc1, 0xbd, 0x37, 0xe3, 0x5b, 0xbe, 0xb5, 0x4f, 0x8b, 0x8b,
+ 0xf0, 0xda, 0x4f, 0xc7, 0xe3, 0xdd, 0x55, 0x47, 0x69, 0xdf, 0xf2, 0x5b,
+ 0x7b, 0x07, 0x4f, 0x3d, 0xe5, 0xac, 0x21, 0xc1, 0xc8, 0x1d, 0x7a, 0xe8,
+ 0xe7, 0xf6, 0x0f, 0xa1, 0xaa, 0xf5, 0x6f, 0xde, 0xa8, 0x65, 0x4f, 0x10,
+ 0x89, 0x9c, 0x03, 0xf3, 0x89, 0x7a, 0xa5, 0x5e, 0x01, 0x72, 0x33, 0xed,
+ 0xa9, 0xe9, 0x5a, 0x1e, 0x79, 0xf3, 0x87, 0xc8, 0xdf, 0xc8, 0xc5, 0xfc,
+ 0x37, 0xc8, 0x9a, 0x9a, 0xd7, 0xb8, 0x76, 0xcc, 0xb0, 0x3e, 0xe7, 0xfd,
+ 0xe6, 0x54, 0xea, 0xdf, 0x5f, 0x52, 0x41, 0x78, 0x59, 0x57, 0xad, 0xf1,
+ 0x12, 0xd6, 0x7f, 0xbc, 0xd5, 0x9f, 0x70, 0xd3, 0x05, 0x6c, 0xfa, 0xa3,
+ 0x7d, 0x67, 0x58, 0xdd, 0x26, 0x62, 0x1d, 0x31, 0x92, 0x0c, 0x79, 0x79,
+ 0x1c, 0x8e, 0xcf, 0xca, 0x7b, 0xc1, 0x66, 0xaf, 0xa8, 0x74, 0x48, 0xfb,
+ 0x8e, 0x82, 0xc2, 0x9e, 0x2c, 0x99, 0x5c, 0x7b, 0x2d, 0x5d, 0x9b, 0xbc,
+ 0x5b, 0x57, 0x9e, 0x7c, 0x3a, 0x7a, 0x13, 0xad, 0xf2, 0xa3, 0x18, 0x5b,
+ 0x2b, 0x59, 0x0f, 0xcd, 0x5c, 0x3a, 0xeb, 0x68, 0x33, 0xc6, 0x28, 0x1d,
+ 0x82, 0xd1, 0x50, 0x8b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xd9,
+ 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x6b, 0x69, 0x3d, 0x6a, 0x18, 0x42,
+ 0x4a, 0xdd, 0x8f, 0x02, 0x65, 0x39, 0xfd, 0x35, 0x24, 0x86, 0x78, 0x91,
+ 0x16, 0x30, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+ 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab, 0x05,
+ 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b,
+ 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30,
+ 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+ 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+ 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xab, 0xbc, 0xbc,
+ 0x0a, 0x5d, 0x18, 0x94, 0xe3, 0xc1, 0xb1, 0xc3, 0xa8, 0x4c, 0x55, 0xd6,
+ 0xbe, 0xb4, 0x98, 0xf1, 0xee, 0x3c, 0x1c, 0xcd, 0xcf, 0xf3, 0x24, 0x24,
+ 0x5c, 0x96, 0x03, 0x27, 0x58, 0xfc, 0x36, 0xae, 0xa2, 0x2f, 0x8f, 0xf1,
+ 0xfe, 0xda, 0x2b, 0x02, 0xc3, 0x33, 0xbd, 0xc8, 0xdd, 0x48, 0x22, 0x2b,
+ 0x60, 0x0f, 0xa5, 0x03, 0x10, 0xfd, 0x77, 0xf8, 0xd0, 0xed, 0x96, 0x67,
+ 0x4f, 0xfd, 0xea, 0x47, 0x20, 0x70, 0x54, 0xdc, 0xa9, 0x0c, 0x55, 0x7e,
+ 0xe1, 0x96, 0x25, 0x8a, 0xd9, 0xb5, 0xda, 0x57, 0x4a, 0xbe, 0x8d, 0x8e,
+ 0x49, 0x43, 0x63, 0xa5, 0x6c, 0x4e, 0x27, 0x87, 0x25, 0xeb, 0x5b, 0x6d,
+ 0xfe, 0xa2, 0x7f, 0x38, 0x28, 0xe0, 0x36, 0xab, 0xad, 0x39, 0xa5, 0xa5,
+ 0x62, 0xc4, 0xb7, 0x5c, 0x58, 0x2c, 0xaa, 0x5d, 0x01, 0x60, 0xa6, 0x62,
+ 0x67, 0xa3, 0xc0, 0xc7, 0x62, 0x23, 0xf4, 0xe7, 0x6c, 0x46, 0xee, 0xb5,
+ 0xd3, 0x80, 0x6a, 0x22, 0x13, 0xd2, 0x2d, 0x3f, 0x74, 0x4f, 0xea, 0xaf,
+ 0x8c, 0x5f, 0xb4, 0x38, 0x9c, 0xdb, 0xae, 0xce, 0xaf, 0x84, 0x1e, 0xa6,
+ 0xf6, 0x34, 0x51, 0x59, 0x79, 0xd3, 0xe3, 0x75, 0xdc, 0xbc, 0xd7, 0xf3,
+ 0x73, 0xdf, 0x92, 0xec, 0xd2, 0x20, 0x59, 0x6f, 0x9c, 0xfb, 0x95, 0xf8,
+ 0x92, 0x76, 0x18, 0x0a, 0x7c, 0x0f, 0x2c, 0xa6, 0xca, 0xde, 0x8a, 0x62,
+ 0x7b, 0xd8, 0xf3, 0xce, 0x5f, 0x68, 0xbd, 0x8f, 0x3e, 0xc1, 0x74, 0xbb,
+ 0x15, 0x72, 0x3a, 0x16, 0x83, 0xa9, 0x0b, 0xe6, 0x4d, 0x99, 0x9c, 0xd8,
+ 0x57, 0xec, 0xa8, 0x01, 0x51, 0xc7, 0x6f, 0x57, 0x34, 0x5e, 0xab, 0x4a,
+ 0x2c, 0x42, 0xf6, 0x4f, 0x1c, 0x89, 0x78, 0xde, 0x26, 0x4e, 0xf5, 0x6f,
+ 0x93, 0x4c, 0x15, 0x6b, 0x27, 0x56, 0x4d, 0x00, 0x54, 0x6c, 0x7a, 0xb7,
+ 0xb7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 145104 (0x236d0)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+ Validity
+ Not Before: Feb 19 22:39:26 2010 GMT
+ Not After : Feb 18 22:39:26 2020 GMT
+ Subject: C=US, O=GeoTrust, Inc., CN=GeoTrust SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:90:b3:80:c1:e4:e5:46:ad:70:60:3d:ba:e5:14:
+ dd:9e:8a:5e:8b:75:5a:e6:ca:6d:41:a5:23:e8:39:
+ 85:26:7a:a7:55:77:9a:48:a1:92:7e:3a:1e:1a:f1:
+ 27:ab:a3:4c:39:cc:cb:3d:47:af:81:ae:16:6a:5c:
+ 37:ef:45:41:fd:fb:9a:97:3c:a0:43:9d:c6:df:17:
+ 21:d1:8a:a2:56:c2:03:49:84:12:81:3e:c9:0a:54:
+ 60:66:b9:8c:54:e4:f9:e6:f9:94:f1:e0:5f:75:11:
+ f2:29:b9:e4:86:a2:b1:89:ad:a6:1e:83:29:63:b2:
+ f0:54:1c:85:0b:7a:e7:e1:2e:0d:af:a4:bd:cd:e7:
+ b1:5a:d7:8c:05:5a:0e:4b:73:28:8b:75:5d:34:d8:
+ 77:0b:e1:74:62:e2:71:30:62:d8:bc:8a:05:e5:31:
+ 63:4a:54:89:6a:33:78:a7:4e:55:24:1d:97:ef:1a:
+ e4:12:c6:0f:30:18:b4:34:4d:e1:d8:23:3b:21:5b:
+ 2d:30:19:25:0e:74:f7:a4:21:4b:a0:a4:20:c9:6c:
+ cd:98:56:c0:f2:a8:5f:3e:26:75:a0:0d:f8:36:88:
+ 8a:2c:5a:7d:67:30:a9:0f:d1:99:70:2e:78:e1:51:
+ 26:af:55:7a:24:be:8c:39:0d:77:9d:de:02:c3:0c:
+ bd:1f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 42:79:54:1B:61:CD:55:2B:3E:63:D5:3C:48:57:F5:9F:FB:45:CE:4A
+ X509v3 Authority Key Identifier:
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.geotrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ d4:ef:53:84:e8:1a:bd:a1:8b:04:c0:a9:f5:5f:a1:10:78:45:
+ 5d:b2:57:6a:4e:24:cb:65:4e:31:97:91:9a:d4:24:f8:e2:27:
+ 66:70:31:9c:c1:62:54:06:e7:97:1d:3a:9a:c0:a4:29:48:0a:
+ af:24:c7:a8:c4:9a:54:c1:7c:4c:78:4c:2b:68:2c:5d:17:a6:
+ 54:78:4c:46:e2:80:c3:1f:38:71:12:d2:d7:53:e3:54:85:50:
+ b8:02:cb:ee:63:3a:f8:56:89:4d:55:bb:2e:c0:c8:18:77:86:
+ 31:0b:0b:70:f0:7e:35:83:a4:2a:13:64:56:67:34:5d:16:5f:
+ 73:ac:7b:06:24:da:4f:50:6d:2a:ab:d0:4d:53:41:c2:8e:bb:
+ 71:03:49:29:86:18:cf:21:42:4c:74:62:51:15:c5:6f:a8:ef:
+ c4:27:e5:1b:33:dd:5a:88:d7:7f:12:d1:a7:61:25:1f:d5:e0:
+ dc:1d:cf:1a:10:d8:a0:cb:5f:8c:fa:0c:e5:bf:71:ff:e5:5d:
+ 44:1d:a6:3e:87:47:fa:1a:4e:83:83:12:3f:88:66:95:98:79:
+ 9a:85:eb:02:47:cd:25:e3:f2:06:04:4e:99:ca:5c:a0:6e:7a:
+ bb:dd:a3:90:1a:45:33:ef:bf:3e:d2:04:c4:b6:e0:2a:85:65:
+ 41:3e:10:d4
+-----BEGIN CERTIFICATE-----
+MIID2TCCAsGgAwIBAgIDAjbQMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjE5MjIzOTI2WhcNMjAwMjE4MjIzOTI2WjBAMQswCQYDVQQG
+EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xGDAWBgNVBAMTD0dlb1RydXN0
+IFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCzgMHk5Uat
+cGA9uuUU3Z6KXot1WubKbUGlI+g5hSZ6p1V3mkihkn46HhrxJ6ujTDnMyz1Hr4Gu
+FmpcN+9FQf37mpc8oEOdxt8XIdGKolbCA0mEEoE+yQpUYGa5jFTk+eb5lPHgX3UR
+8im55IaisYmtph6DKWOy8FQchQt65+EuDa+kvc3nsVrXjAVaDktzKIt1XTTYdwvh
+dGLicTBi2LyKBeUxY0pUiWozeKdOVSQdl+8a5BLGDzAYtDRN4dgjOyFbLTAZJQ50
+96QhS6CkIMlszZhWwPKoXz4mdaAN+DaIiixafWcwqQ/RmXAueOFRJq9VeiS+jDkN
+d53eAsMMvR8CAwEAAaOB2TCB1jAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFEJ5
+VBthzVUrPmPVPEhX9Z/7Rc5KMB8GA1UdIwQYMBaAFMB6mGiNifurBWQMEX2qfWW4
+ysxOMBIGA1UdEwEB/wQIMAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDov
+L2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBBQUHAQEE
+KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20wDQYJKoZI
+hvcNAQEFBQADggEBANTvU4ToGr2hiwTAqfVfoRB4RV2yV2pOJMtlTjGXkZrUJPji
+J2ZwMZzBYlQG55cdOprApClICq8kx6jEmlTBfEx4TCtoLF0XplR4TEbigMMfOHES
+0tdT41SFULgCy+5jOvhWiU1Vuy7AyBh3hjELC3DwfjWDpCoTZFZnNF0WX3OsewYk
+2k9QbSqr0E1TQcKOu3EDSSmGGM8hQkx0YlEVxW+o78Qn5Rsz3VqI138S0adhJR/V
+4NwdzxoQ2KDLX4z6DOW/cf/lXUQdpj6HR/oaToODEj+IZpWYeZqF6wJHzSXj8gYE
+TpnKXKBuervdo5AaRTPvvz7SBMS24CqFZUE+ENQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert8[] = {
+ 0x30, 0x82, 0x03, 0xd9, 0x30, 0x82, 0x02, 0xc1, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x02, 0x36, 0xd0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+ 0x32, 0x31, 0x39, 0x32, 0x32, 0x33, 0x39, 0x32, 0x36, 0x5a, 0x17, 0x0d,
+ 0x32, 0x30, 0x30, 0x32, 0x31, 0x38, 0x32, 0x32, 0x33, 0x39, 0x32, 0x36,
+ 0x5a, 0x30, 0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0e, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2c,
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0f, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0x90, 0xb3, 0x80, 0xc1, 0xe4, 0xe5, 0x46, 0xad,
+ 0x70, 0x60, 0x3d, 0xba, 0xe5, 0x14, 0xdd, 0x9e, 0x8a, 0x5e, 0x8b, 0x75,
+ 0x5a, 0xe6, 0xca, 0x6d, 0x41, 0xa5, 0x23, 0xe8, 0x39, 0x85, 0x26, 0x7a,
+ 0xa7, 0x55, 0x77, 0x9a, 0x48, 0xa1, 0x92, 0x7e, 0x3a, 0x1e, 0x1a, 0xf1,
+ 0x27, 0xab, 0xa3, 0x4c, 0x39, 0xcc, 0xcb, 0x3d, 0x47, 0xaf, 0x81, 0xae,
+ 0x16, 0x6a, 0x5c, 0x37, 0xef, 0x45, 0x41, 0xfd, 0xfb, 0x9a, 0x97, 0x3c,
+ 0xa0, 0x43, 0x9d, 0xc6, 0xdf, 0x17, 0x21, 0xd1, 0x8a, 0xa2, 0x56, 0xc2,
+ 0x03, 0x49, 0x84, 0x12, 0x81, 0x3e, 0xc9, 0x0a, 0x54, 0x60, 0x66, 0xb9,
+ 0x8c, 0x54, 0xe4, 0xf9, 0xe6, 0xf9, 0x94, 0xf1, 0xe0, 0x5f, 0x75, 0x11,
+ 0xf2, 0x29, 0xb9, 0xe4, 0x86, 0xa2, 0xb1, 0x89, 0xad, 0xa6, 0x1e, 0x83,
+ 0x29, 0x63, 0xb2, 0xf0, 0x54, 0x1c, 0x85, 0x0b, 0x7a, 0xe7, 0xe1, 0x2e,
+ 0x0d, 0xaf, 0xa4, 0xbd, 0xcd, 0xe7, 0xb1, 0x5a, 0xd7, 0x8c, 0x05, 0x5a,
+ 0x0e, 0x4b, 0x73, 0x28, 0x8b, 0x75, 0x5d, 0x34, 0xd8, 0x77, 0x0b, 0xe1,
+ 0x74, 0x62, 0xe2, 0x71, 0x30, 0x62, 0xd8, 0xbc, 0x8a, 0x05, 0xe5, 0x31,
+ 0x63, 0x4a, 0x54, 0x89, 0x6a, 0x33, 0x78, 0xa7, 0x4e, 0x55, 0x24, 0x1d,
+ 0x97, 0xef, 0x1a, 0xe4, 0x12, 0xc6, 0x0f, 0x30, 0x18, 0xb4, 0x34, 0x4d,
+ 0xe1, 0xd8, 0x23, 0x3b, 0x21, 0x5b, 0x2d, 0x30, 0x19, 0x25, 0x0e, 0x74,
+ 0xf7, 0xa4, 0x21, 0x4b, 0xa0, 0xa4, 0x20, 0xc9, 0x6c, 0xcd, 0x98, 0x56,
+ 0xc0, 0xf2, 0xa8, 0x5f, 0x3e, 0x26, 0x75, 0xa0, 0x0d, 0xf8, 0x36, 0x88,
+ 0x8a, 0x2c, 0x5a, 0x7d, 0x67, 0x30, 0xa9, 0x0f, 0xd1, 0x99, 0x70, 0x2e,
+ 0x78, 0xe1, 0x51, 0x26, 0xaf, 0x55, 0x7a, 0x24, 0xbe, 0x8c, 0x39, 0x0d,
+ 0x77, 0x9d, 0xde, 0x02, 0xc3, 0x0c, 0xbd, 0x1f, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x81, 0xd9, 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x42, 0x79,
+ 0x54, 0x1b, 0x61, 0xcd, 0x55, 0x2b, 0x3e, 0x63, 0xd5, 0x3c, 0x48, 0x57,
+ 0xf5, 0x9f, 0xfb, 0x45, 0xce, 0x4a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d,
+ 0x89, 0xfb, 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8,
+ 0xca, 0xcc, 0x4e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f,
+ 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67,
+ 0x74, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+ 0x00, 0xd4, 0xef, 0x53, 0x84, 0xe8, 0x1a, 0xbd, 0xa1, 0x8b, 0x04, 0xc0,
+ 0xa9, 0xf5, 0x5f, 0xa1, 0x10, 0x78, 0x45, 0x5d, 0xb2, 0x57, 0x6a, 0x4e,
+ 0x24, 0xcb, 0x65, 0x4e, 0x31, 0x97, 0x91, 0x9a, 0xd4, 0x24, 0xf8, 0xe2,
+ 0x27, 0x66, 0x70, 0x31, 0x9c, 0xc1, 0x62, 0x54, 0x06, 0xe7, 0x97, 0x1d,
+ 0x3a, 0x9a, 0xc0, 0xa4, 0x29, 0x48, 0x0a, 0xaf, 0x24, 0xc7, 0xa8, 0xc4,
+ 0x9a, 0x54, 0xc1, 0x7c, 0x4c, 0x78, 0x4c, 0x2b, 0x68, 0x2c, 0x5d, 0x17,
+ 0xa6, 0x54, 0x78, 0x4c, 0x46, 0xe2, 0x80, 0xc3, 0x1f, 0x38, 0x71, 0x12,
+ 0xd2, 0xd7, 0x53, 0xe3, 0x54, 0x85, 0x50, 0xb8, 0x02, 0xcb, 0xee, 0x63,
+ 0x3a, 0xf8, 0x56, 0x89, 0x4d, 0x55, 0xbb, 0x2e, 0xc0, 0xc8, 0x18, 0x77,
+ 0x86, 0x31, 0x0b, 0x0b, 0x70, 0xf0, 0x7e, 0x35, 0x83, 0xa4, 0x2a, 0x13,
+ 0x64, 0x56, 0x67, 0x34, 0x5d, 0x16, 0x5f, 0x73, 0xac, 0x7b, 0x06, 0x24,
+ 0xda, 0x4f, 0x50, 0x6d, 0x2a, 0xab, 0xd0, 0x4d, 0x53, 0x41, 0xc2, 0x8e,
+ 0xbb, 0x71, 0x03, 0x49, 0x29, 0x86, 0x18, 0xcf, 0x21, 0x42, 0x4c, 0x74,
+ 0x62, 0x51, 0x15, 0xc5, 0x6f, 0xa8, 0xef, 0xc4, 0x27, 0xe5, 0x1b, 0x33,
+ 0xdd, 0x5a, 0x88, 0xd7, 0x7f, 0x12, 0xd1, 0xa7, 0x61, 0x25, 0x1f, 0xd5,
+ 0xe0, 0xdc, 0x1d, 0xcf, 0x1a, 0x10, 0xd8, 0xa0, 0xcb, 0x5f, 0x8c, 0xfa,
+ 0x0c, 0xe5, 0xbf, 0x71, 0xff, 0xe5, 0x5d, 0x44, 0x1d, 0xa6, 0x3e, 0x87,
+ 0x47, 0xfa, 0x1a, 0x4e, 0x83, 0x83, 0x12, 0x3f, 0x88, 0x66, 0x95, 0x98,
+ 0x79, 0x9a, 0x85, 0xeb, 0x02, 0x47, 0xcd, 0x25, 0xe3, 0xf2, 0x06, 0x04,
+ 0x4e, 0x99, 0xca, 0x5c, 0xa0, 0x6e, 0x7a, 0xbb, 0xdd, 0xa3, 0x90, 0x1a,
+ 0x45, 0x33, 0xef, 0xbf, 0x3e, 0xd2, 0x04, 0xc4, 0xb6, 0xe0, 0x2a, 0x85,
+ 0x65, 0x41, 0x3e, 0x10, 0xd4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 145106 (0x236d2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+ Validity
+ Not Before: Feb 26 21:32:31 2010 GMT
+ Not After : Feb 25 21:32:31 2020 GMT
+ Subject: C=US, O=GeoTrust Inc., OU=Domain Validated SSL, CN=GeoTrust DV SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a6:bb:8e:7a:cd:a4:9c:62:57:d4:51:30:42:7b:
+ 8b:1a:b2:d2:88:06:ad:3b:3c:29:13:0c:31:bc:69:
+ f9:9f:5a:94:da:06:ba:ac:24:04:9e:ce:d4:aa:c4:
+ 48:60:00:f8:34:ae:a1:93:af:de:04:7e:cd:f8:5c:
+ 22:52:0d:56:53:eb:a9:94:cf:fb:74:44:eb:43:94:
+ a4:97:7a:40:57:35:b6:a4:62:da:d5:48:f8:7a:f1:
+ ec:90:b5:5f:39:fe:63:72:70:c8:12:85:d0:a5:2e:
+ 86:13:40:6c:eb:6c:4d:d2:54:fd:5f:3e:26:1f:66:
+ 71:a8:c0:b8:85:9e:f5:f5:75:8f:da:91:4e:89:e3:
+ ca:78:74:30:5f:15:0a:99:a7:ca:83:3a:76:35:48:
+ d0:dc:8b:1a:22:4e:85:a4:4e:fa:49:6d:2b:70:be:
+ 8e:0c:21:c3:62:cc:a4:d1:ad:16:6b:9a:7b:cb:64:
+ ff:8d:ba:42:c3:26:aa:15:78:68:9c:ec:f6:6b:c8:
+ 0c:57:0d:e5:38:07:d3:6a:57:03:9d:20:0e:4b:c4:
+ 7b:81:b0:2a:1c:f5:4a:ea:4a:98:49:fe:02:5b:3d:
+ 03:14:90:28:7e:9a:f4:78:d0:31:84:57:e5:4c:38:
+ 7a:42:11:e2:f5:28:51:03:4b:20:15:bb:22:1a:b6:
+ f0:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 8C:F4:D9:93:0A:47:BC:00:A0:4A:CE:4B:75:6E:A0:B6:B0:B2:7E:FC
+ X509v3 Authority Key Identifier:
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.geotrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 33:91:37:11:db:40:f9:de:8c:b2:02:88:77:af:63:21:c1:ad:
+ b0:0d:fa:a0:78:56:a3:82:fd:bb:49:5f:14:6d:c8:dc:5f:94:
+ da:11:66:7c:1e:91:c5:b6:d8:6d:4f:aa:f2:bf:21:28:7e:52:
+ a2:92:78:08:61:69:21:fe:2d:ec:82:18:84:f4:d3:8d:c5:8a:
+ bb:8a:cc:5d:e6:a3:b6:cc:6e:ad:6f:b3:0e:61:ee:89:ce:13:
+ 34:4f:49:55:f5:39:bb:99:96:f0:f5:ea:5a:3c:9c:16:bd:02:
+ 53:f0:2a:0e:41:6e:eb:ef:9e:f7:70:36:cd:80:2a:76:c8:87:
+ e3:eb:23:b3:96:2c:e6:1d:94:5f:1c:a4:e2:cd:24:31:2b:06:
+ 38:32:61:61:39:5c:89:4c:48:1d:42:c9:67:9e:d2:bf:58:f7:
+ f9:37:31:b0:67:dd:8d:26:36:1a:78:1a:09:19:3c:93:07:70:
+ 2a:e1:7c:29:f5:de:66:57:0b:12:5e:16:ed:5e:bd:37:b3:30:
+ 69:c6:92:a5:f6:19:d8:1d:f8:36:12:b9:4b:95:95:9c:d0:ce:
+ 6c:30:a7:16:fb:f6:4d:64:b6:5f:2a:14:9c:a6:c8:55:8e:20:
+ f9:65:07:24:cc:38:05:4c:20:88:b4:b5:67:94:cf:5d:8e:62:
+ 37:fe:c4:b4
+-----BEGIN CERTIFICATE-----
+MIID+jCCAuKgAwIBAgIDAjbSMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTAwMjI2MjEzMjMxWhcNMjAwMjI1MjEzMjMxWjBhMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UECxMURG9tYWluIFZh
+bGlkYXRlZCBTU0wxGzAZBgNVBAMTEkdlb1RydXN0IERWIFNTTCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKa7jnrNpJxiV9RRMEJ7ixqy0ogGrTs8
+KRMMMbxp+Z9alNoGuqwkBJ7O1KrESGAA+DSuoZOv3gR+zfhcIlINVlPrqZTP+3RE
+60OUpJd6QFc1tqRi2tVI+Hrx7JC1Xzn+Y3JwyBKF0KUuhhNAbOtsTdJU/V8+Jh9m
+cajAuIWe9fV1j9qRTonjynh0MF8VCpmnyoM6djVI0NyLGiJOhaRO+kltK3C+jgwh
+w2LMpNGtFmuae8tk/426QsMmqhV4aJzs9mvIDFcN5TgH02pXA50gDkvEe4GwKhz1
+SupKmEn+Als9AxSQKH6a9HjQMYRX5Uw4ekIR4vUoUQNLIBW7Ihq28BUCAwEAAaOB
+2TCB1jAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFIz02ZMKR7wAoErOS3VuoLaw
+sn78MB8GA1UdIwQYMBaAFMB6mGiNifurBWQMEX2qfWW4ysxOMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5j
+b20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAB
+hhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBADOR
+NxHbQPnejLICiHevYyHBrbAN+qB4VqOC/btJXxRtyNxflNoRZnwekcW22G1PqvK/
+ISh+UqKSeAhhaSH+LeyCGIT0043FiruKzF3mo7bMbq1vsw5h7onOEzRPSVX1ObuZ
+lvD16lo8nBa9AlPwKg5BbuvvnvdwNs2AKnbIh+PrI7OWLOYdlF8cpOLNJDErBjgy
+YWE5XIlMSB1CyWee0r9Y9/k3MbBn3Y0mNhp4GgkZPJMHcCrhfCn13mZXCxJeFu1e
+vTezMGnGkqX2Gdgd+DYSuUuVlZzQzmwwpxb79k1ktl8qFJymyFWOIPllByTMOAVM
+IIi0tWeUz12OYjf+xLQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert9[] = {
+ 0x30, 0x82, 0x03, 0xfa, 0x30, 0x82, 0x02, 0xe2, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x02, 0x36, 0xd2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30,
+ 0x32, 0x32, 0x36, 0x32, 0x31, 0x33, 0x32, 0x33, 0x31, 0x5a, 0x17, 0x0d,
+ 0x32, 0x30, 0x30, 0x32, 0x32, 0x35, 0x32, 0x31, 0x33, 0x32, 0x33, 0x31,
+ 0x5a, 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0d, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x14, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61,
+ 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31,
+ 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47, 0x65,
+ 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x44, 0x56, 0x20, 0x53, 0x53,
+ 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xa6, 0xbb, 0x8e, 0x7a, 0xcd, 0xa4, 0x9c, 0x62, 0x57, 0xd4, 0x51,
+ 0x30, 0x42, 0x7b, 0x8b, 0x1a, 0xb2, 0xd2, 0x88, 0x06, 0xad, 0x3b, 0x3c,
+ 0x29, 0x13, 0x0c, 0x31, 0xbc, 0x69, 0xf9, 0x9f, 0x5a, 0x94, 0xda, 0x06,
+ 0xba, 0xac, 0x24, 0x04, 0x9e, 0xce, 0xd4, 0xaa, 0xc4, 0x48, 0x60, 0x00,
+ 0xf8, 0x34, 0xae, 0xa1, 0x93, 0xaf, 0xde, 0x04, 0x7e, 0xcd, 0xf8, 0x5c,
+ 0x22, 0x52, 0x0d, 0x56, 0x53, 0xeb, 0xa9, 0x94, 0xcf, 0xfb, 0x74, 0x44,
+ 0xeb, 0x43, 0x94, 0xa4, 0x97, 0x7a, 0x40, 0x57, 0x35, 0xb6, 0xa4, 0x62,
+ 0xda, 0xd5, 0x48, 0xf8, 0x7a, 0xf1, 0xec, 0x90, 0xb5, 0x5f, 0x39, 0xfe,
+ 0x63, 0x72, 0x70, 0xc8, 0x12, 0x85, 0xd0, 0xa5, 0x2e, 0x86, 0x13, 0x40,
+ 0x6c, 0xeb, 0x6c, 0x4d, 0xd2, 0x54, 0xfd, 0x5f, 0x3e, 0x26, 0x1f, 0x66,
+ 0x71, 0xa8, 0xc0, 0xb8, 0x85, 0x9e, 0xf5, 0xf5, 0x75, 0x8f, 0xda, 0x91,
+ 0x4e, 0x89, 0xe3, 0xca, 0x78, 0x74, 0x30, 0x5f, 0x15, 0x0a, 0x99, 0xa7,
+ 0xca, 0x83, 0x3a, 0x76, 0x35, 0x48, 0xd0, 0xdc, 0x8b, 0x1a, 0x22, 0x4e,
+ 0x85, 0xa4, 0x4e, 0xfa, 0x49, 0x6d, 0x2b, 0x70, 0xbe, 0x8e, 0x0c, 0x21,
+ 0xc3, 0x62, 0xcc, 0xa4, 0xd1, 0xad, 0x16, 0x6b, 0x9a, 0x7b, 0xcb, 0x64,
+ 0xff, 0x8d, 0xba, 0x42, 0xc3, 0x26, 0xaa, 0x15, 0x78, 0x68, 0x9c, 0xec,
+ 0xf6, 0x6b, 0xc8, 0x0c, 0x57, 0x0d, 0xe5, 0x38, 0x07, 0xd3, 0x6a, 0x57,
+ 0x03, 0x9d, 0x20, 0x0e, 0x4b, 0xc4, 0x7b, 0x81, 0xb0, 0x2a, 0x1c, 0xf5,
+ 0x4a, 0xea, 0x4a, 0x98, 0x49, 0xfe, 0x02, 0x5b, 0x3d, 0x03, 0x14, 0x90,
+ 0x28, 0x7e, 0x9a, 0xf4, 0x78, 0xd0, 0x31, 0x84, 0x57, 0xe5, 0x4c, 0x38,
+ 0x7a, 0x42, 0x11, 0xe2, 0xf5, 0x28, 0x51, 0x03, 0x4b, 0x20, 0x15, 0xbb,
+ 0x22, 0x1a, 0xb6, 0xf0, 0x15, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+ 0xd9, 0x30, 0x81, 0xd6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x8c, 0xf4, 0xd9, 0x93, 0x0a,
+ 0x47, 0xbc, 0x00, 0xa0, 0x4a, 0xce, 0x4b, 0x75, 0x6e, 0xa0, 0xb6, 0xb0,
+ 0xb2, 0x7e, 0xfc, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb, 0xab,
+ 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc, 0x4e,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+ 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26,
+ 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x33, 0x91,
+ 0x37, 0x11, 0xdb, 0x40, 0xf9, 0xde, 0x8c, 0xb2, 0x02, 0x88, 0x77, 0xaf,
+ 0x63, 0x21, 0xc1, 0xad, 0xb0, 0x0d, 0xfa, 0xa0, 0x78, 0x56, 0xa3, 0x82,
+ 0xfd, 0xbb, 0x49, 0x5f, 0x14, 0x6d, 0xc8, 0xdc, 0x5f, 0x94, 0xda, 0x11,
+ 0x66, 0x7c, 0x1e, 0x91, 0xc5, 0xb6, 0xd8, 0x6d, 0x4f, 0xaa, 0xf2, 0xbf,
+ 0x21, 0x28, 0x7e, 0x52, 0xa2, 0x92, 0x78, 0x08, 0x61, 0x69, 0x21, 0xfe,
+ 0x2d, 0xec, 0x82, 0x18, 0x84, 0xf4, 0xd3, 0x8d, 0xc5, 0x8a, 0xbb, 0x8a,
+ 0xcc, 0x5d, 0xe6, 0xa3, 0xb6, 0xcc, 0x6e, 0xad, 0x6f, 0xb3, 0x0e, 0x61,
+ 0xee, 0x89, 0xce, 0x13, 0x34, 0x4f, 0x49, 0x55, 0xf5, 0x39, 0xbb, 0x99,
+ 0x96, 0xf0, 0xf5, 0xea, 0x5a, 0x3c, 0x9c, 0x16, 0xbd, 0x02, 0x53, 0xf0,
+ 0x2a, 0x0e, 0x41, 0x6e, 0xeb, 0xef, 0x9e, 0xf7, 0x70, 0x36, 0xcd, 0x80,
+ 0x2a, 0x76, 0xc8, 0x87, 0xe3, 0xeb, 0x23, 0xb3, 0x96, 0x2c, 0xe6, 0x1d,
+ 0x94, 0x5f, 0x1c, 0xa4, 0xe2, 0xcd, 0x24, 0x31, 0x2b, 0x06, 0x38, 0x32,
+ 0x61, 0x61, 0x39, 0x5c, 0x89, 0x4c, 0x48, 0x1d, 0x42, 0xc9, 0x67, 0x9e,
+ 0xd2, 0xbf, 0x58, 0xf7, 0xf9, 0x37, 0x31, 0xb0, 0x67, 0xdd, 0x8d, 0x26,
+ 0x36, 0x1a, 0x78, 0x1a, 0x09, 0x19, 0x3c, 0x93, 0x07, 0x70, 0x2a, 0xe1,
+ 0x7c, 0x29, 0xf5, 0xde, 0x66, 0x57, 0x0b, 0x12, 0x5e, 0x16, 0xed, 0x5e,
+ 0xbd, 0x37, 0xb3, 0x30, 0x69, 0xc6, 0x92, 0xa5, 0xf6, 0x19, 0xd8, 0x1d,
+ 0xf8, 0x36, 0x12, 0xb9, 0x4b, 0x95, 0x95, 0x9c, 0xd0, 0xce, 0x6c, 0x30,
+ 0xa7, 0x16, 0xfb, 0xf6, 0x4d, 0x64, 0xb6, 0x5f, 0x2a, 0x14, 0x9c, 0xa6,
+ 0xc8, 0x55, 0x8e, 0x20, 0xf9, 0x65, 0x07, 0x24, 0xcc, 0x38, 0x05, 0x4c,
+ 0x20, 0x88, 0xb4, 0xb5, 0x67, 0x94, 0xcf, 0x5d, 0x8e, 0x62, 0x37, 0xfe,
+ 0xc4, 0xb4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 146025 (0x23a69)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
+ Validity
+ Not Before: Apr 5 15:15:55 2013 GMT
+ Not After : Apr 4 15:15:55 2015 GMT
+ Subject: C=US, O=Google Inc, CN=Google Internet Authority G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:9c:2a:04:77:5c:d8:50:91:3a:06:a3:82:e0:d8:
+ 50:48:bc:89:3f:f1:19:70:1a:88:46:7e:e0:8f:c5:
+ f1:89:ce:21:ee:5a:fe:61:0d:b7:32:44:89:a0:74:
+ 0b:53:4f:55:a4:ce:82:62:95:ee:eb:59:5f:c6:e1:
+ 05:80:12:c4:5e:94:3f:bc:5b:48:38:f4:53:f7:24:
+ e6:fb:91:e9:15:c4:cf:f4:53:0d:f4:4a:fc:9f:54:
+ de:7d:be:a0:6b:6f:87:c0:d0:50:1f:28:30:03:40:
+ da:08:73:51:6c:7f:ff:3a:3c:a7:37:06:8e:bd:4b:
+ 11:04:eb:7d:24:de:e6:f9:fc:31:71:fb:94:d5:60:
+ f3:2e:4a:af:42:d2:cb:ea:c4:6a:1a:b2:cc:53:dd:
+ 15:4b:8b:1f:c8:19:61:1f:cd:9d:a8:3e:63:2b:84:
+ 35:69:65:84:c8:19:c5:46:22:f8:53:95:be:e3:80:
+ 4a:10:c6:2a:ec:ba:97:20:11:c7:39:99:10:04:a0:
+ f0:61:7a:95:25:8c:4e:52:75:e2:b6:ed:08:ca:14:
+ fc:ce:22:6a:b3:4e:cf:46:03:97:97:03:7e:c0:b1:
+ de:7b:af:45:33:cf:ba:3e:71:b7:de:f4:25:25:c2:
+ 0d:35:89:9d:9d:fb:0e:11:79:89:1e:37:c5:af:8e:
+ 72:69
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
+
+ X509v3 Subject Key Identifier:
+ 4A:DD:06:16:1B:BC:F6:68:B5:76:F5:81:B6:BB:62:1A:BA:5A:81:2F
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.geotrust.com/crls/gtglobal.crl
+
+ Authority Information Access:
+ OCSP - URI:http://gtglobal-ocsp.geotrust.com
+
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.11129.2.5.1
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 36:d7:06:80:11:27:ad:2a:14:9b:38:77:b3:23:a0:75:58:bb:
+ b1:7e:83:42:ba:72:da:1e:d8:8e:36:06:97:e0:f0:95:3b:37:
+ fd:1b:42:58:fe:22:c8:6b:bd:38:5e:d1:3b:25:6e:12:eb:5e:
+ 67:76:46:40:90:da:14:c8:78:0d:ed:95:66:da:8e:86:6f:80:
+ a1:ba:56:32:95:86:dc:dc:6a:ca:04:8c:5b:7f:f6:bf:cc:6f:
+ 85:03:58:c3:68:51:13:cd:fd:c8:f7:79:3d:99:35:f0:56:a3:
+ bd:e0:59:ed:4f:44:09:a3:9e:38:7a:f6:46:d1:1d:12:9d:4f:
+ be:d0:40:fc:55:fe:06:5e:3c:da:1c:56:bd:96:51:7b:6f:57:
+ 2a:db:a2:aa:96:dc:8c:74:c2:95:be:f0:6e:95:13:ff:17:f0:
+ 3c:ac:b2:10:8d:cc:73:fb:e8:8f:02:c6:f0:fb:33:b3:95:3b:
+ e3:c2:cb:68:58:73:db:a8:24:62:3b:06:35:9d:0d:a9:33:bd:
+ 78:03:90:2e:4c:78:5d:50:3a:81:d4:ee:a0:c8:70:38:dc:b2:
+ f9:67:fa:87:40:5d:61:c0:51:8f:6b:83:6b:cd:05:3a:ca:e1:
+ a7:05:78:fc:ca:da:94:d0:2c:08:3d:7e:16:79:c8:a0:50:20:
+ 24:54:33:71
+-----BEGIN CERTIFICATE-----
+MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
+EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
+bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
+VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
+h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
+ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
+EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
+DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
+qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
+VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
+K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
+KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
+ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
+BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
+/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
+zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
+HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
+WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
+yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert10[] = {
+ 0x30, 0x82, 0x04, 0x04, 0x30, 0x82, 0x02, 0xec, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x02, 0x3a, 0x69, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x42, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x47,
+ 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x33, 0x30,
+ 0x34, 0x30, 0x35, 0x31, 0x35, 0x31, 0x35, 0x35, 0x35, 0x5a, 0x17, 0x0d,
+ 0x31, 0x35, 0x30, 0x34, 0x30, 0x34, 0x31, 0x35, 0x31, 0x35, 0x35, 0x35,
+ 0x5a, 0x30, 0x49, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+ 0x63, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+ 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+ 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0x9c, 0x2a, 0x04, 0x77, 0x5c, 0xd8, 0x50, 0x91, 0x3a, 0x06, 0xa3,
+ 0x82, 0xe0, 0xd8, 0x50, 0x48, 0xbc, 0x89, 0x3f, 0xf1, 0x19, 0x70, 0x1a,
+ 0x88, 0x46, 0x7e, 0xe0, 0x8f, 0xc5, 0xf1, 0x89, 0xce, 0x21, 0xee, 0x5a,
+ 0xfe, 0x61, 0x0d, 0xb7, 0x32, 0x44, 0x89, 0xa0, 0x74, 0x0b, 0x53, 0x4f,
+ 0x55, 0xa4, 0xce, 0x82, 0x62, 0x95, 0xee, 0xeb, 0x59, 0x5f, 0xc6, 0xe1,
+ 0x05, 0x80, 0x12, 0xc4, 0x5e, 0x94, 0x3f, 0xbc, 0x5b, 0x48, 0x38, 0xf4,
+ 0x53, 0xf7, 0x24, 0xe6, 0xfb, 0x91, 0xe9, 0x15, 0xc4, 0xcf, 0xf4, 0x53,
+ 0x0d, 0xf4, 0x4a, 0xfc, 0x9f, 0x54, 0xde, 0x7d, 0xbe, 0xa0, 0x6b, 0x6f,
+ 0x87, 0xc0, 0xd0, 0x50, 0x1f, 0x28, 0x30, 0x03, 0x40, 0xda, 0x08, 0x73,
+ 0x51, 0x6c, 0x7f, 0xff, 0x3a, 0x3c, 0xa7, 0x37, 0x06, 0x8e, 0xbd, 0x4b,
+ 0x11, 0x04, 0xeb, 0x7d, 0x24, 0xde, 0xe6, 0xf9, 0xfc, 0x31, 0x71, 0xfb,
+ 0x94, 0xd5, 0x60, 0xf3, 0x2e, 0x4a, 0xaf, 0x42, 0xd2, 0xcb, 0xea, 0xc4,
+ 0x6a, 0x1a, 0xb2, 0xcc, 0x53, 0xdd, 0x15, 0x4b, 0x8b, 0x1f, 0xc8, 0x19,
+ 0x61, 0x1f, 0xcd, 0x9d, 0xa8, 0x3e, 0x63, 0x2b, 0x84, 0x35, 0x69, 0x65,
+ 0x84, 0xc8, 0x19, 0xc5, 0x46, 0x22, 0xf8, 0x53, 0x95, 0xbe, 0xe3, 0x80,
+ 0x4a, 0x10, 0xc6, 0x2a, 0xec, 0xba, 0x97, 0x20, 0x11, 0xc7, 0x39, 0x99,
+ 0x10, 0x04, 0xa0, 0xf0, 0x61, 0x7a, 0x95, 0x25, 0x8c, 0x4e, 0x52, 0x75,
+ 0xe2, 0xb6, 0xed, 0x08, 0xca, 0x14, 0xfc, 0xce, 0x22, 0x6a, 0xb3, 0x4e,
+ 0xcf, 0x46, 0x03, 0x97, 0x97, 0x03, 0x7e, 0xc0, 0xb1, 0xde, 0x7b, 0xaf,
+ 0x45, 0x33, 0xcf, 0xba, 0x3e, 0x71, 0xb7, 0xde, 0xf4, 0x25, 0x25, 0xc2,
+ 0x0d, 0x35, 0x89, 0x9d, 0x9d, 0xfb, 0x0e, 0x11, 0x79, 0x89, 0x1e, 0x37,
+ 0xc5, 0xaf, 0x8e, 0x72, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+ 0xfb, 0x30, 0x81, 0xf8, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0xc0, 0x7a, 0x98, 0x68, 0x8d, 0x89, 0xfb,
+ 0xab, 0x05, 0x64, 0x0c, 0x11, 0x7d, 0xaa, 0x7d, 0x65, 0xb8, 0xca, 0xcc,
+ 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x4a, 0xdd, 0x06, 0x16, 0x1b, 0xbc, 0xf6, 0x68, 0xb5, 0x76, 0xf5, 0x81,
+ 0xb6, 0xbb, 0x62, 0x1a, 0xba, 0x5a, 0x81, 0x2f, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3a, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0,
+ 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x67, 0x74, 0x67, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+ 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x67, 0x74, 0x67,
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2d, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67,
+ 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x17, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x10, 0x30, 0x0e, 0x30, 0x0c,
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x05, 0x01,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x36, 0xd7, 0x06, 0x80,
+ 0x11, 0x27, 0xad, 0x2a, 0x14, 0x9b, 0x38, 0x77, 0xb3, 0x23, 0xa0, 0x75,
+ 0x58, 0xbb, 0xb1, 0x7e, 0x83, 0x42, 0xba, 0x72, 0xda, 0x1e, 0xd8, 0x8e,
+ 0x36, 0x06, 0x97, 0xe0, 0xf0, 0x95, 0x3b, 0x37, 0xfd, 0x1b, 0x42, 0x58,
+ 0xfe, 0x22, 0xc8, 0x6b, 0xbd, 0x38, 0x5e, 0xd1, 0x3b, 0x25, 0x6e, 0x12,
+ 0xeb, 0x5e, 0x67, 0x76, 0x46, 0x40, 0x90, 0xda, 0x14, 0xc8, 0x78, 0x0d,
+ 0xed, 0x95, 0x66, 0xda, 0x8e, 0x86, 0x6f, 0x80, 0xa1, 0xba, 0x56, 0x32,
+ 0x95, 0x86, 0xdc, 0xdc, 0x6a, 0xca, 0x04, 0x8c, 0x5b, 0x7f, 0xf6, 0xbf,
+ 0xcc, 0x6f, 0x85, 0x03, 0x58, 0xc3, 0x68, 0x51, 0x13, 0xcd, 0xfd, 0xc8,
+ 0xf7, 0x79, 0x3d, 0x99, 0x35, 0xf0, 0x56, 0xa3, 0xbd, 0xe0, 0x59, 0xed,
+ 0x4f, 0x44, 0x09, 0xa3, 0x9e, 0x38, 0x7a, 0xf6, 0x46, 0xd1, 0x1d, 0x12,
+ 0x9d, 0x4f, 0xbe, 0xd0, 0x40, 0xfc, 0x55, 0xfe, 0x06, 0x5e, 0x3c, 0xda,
+ 0x1c, 0x56, 0xbd, 0x96, 0x51, 0x7b, 0x6f, 0x57, 0x2a, 0xdb, 0xa2, 0xaa,
+ 0x96, 0xdc, 0x8c, 0x74, 0xc2, 0x95, 0xbe, 0xf0, 0x6e, 0x95, 0x13, 0xff,
+ 0x17, 0xf0, 0x3c, 0xac, 0xb2, 0x10, 0x8d, 0xcc, 0x73, 0xfb, 0xe8, 0x8f,
+ 0x02, 0xc6, 0xf0, 0xfb, 0x33, 0xb3, 0x95, 0x3b, 0xe3, 0xc2, 0xcb, 0x68,
+ 0x58, 0x73, 0xdb, 0xa8, 0x24, 0x62, 0x3b, 0x06, 0x35, 0x9d, 0x0d, 0xa9,
+ 0x33, 0xbd, 0x78, 0x03, 0x90, 0x2e, 0x4c, 0x78, 0x5d, 0x50, 0x3a, 0x81,
+ 0xd4, 0xee, 0xa0, 0xc8, 0x70, 0x38, 0xdc, 0xb2, 0xf9, 0x67, 0xfa, 0x87,
+ 0x40, 0x5d, 0x61, 0xc0, 0x51, 0x8f, 0x6b, 0x83, 0x6b, 0xcd, 0x05, 0x3a,
+ 0xca, 0xe1, 0xa7, 0x05, 0x78, 0xfc, 0xca, 0xda, 0x94, 0xd0, 0x2c, 0x08,
+ 0x3d, 0x7e, 0x16, 0x79, 0xc8, 0xa0, 0x50, 0x20, 0x24, 0x54, 0x33, 0x71,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120033005 (0x7278eed)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Apr 18 16:36:18 2012 GMT
+ Not After : Aug 13 16:35:17 2018 GMT
+ Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+ d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+ 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+ 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+ 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+ 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+ 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+ a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+ 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+ d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+ 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+ 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+ ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+ 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+ c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+ ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+ 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+ 1a:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://cybertrust.omniroot.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 93:1d:fe:8b:ae:46:ec:cb:a9:0f:ab:e5:ef:ca:b2:68:16:68:
+ d8:8f:fa:13:a9:af:b3:cb:2d:e7:4b:6e:8e:69:2a:c2:2b:10:
+ 0a:8d:f6:ae:73:b6:b9:fb:14:fd:5f:6d:b8:50:b6:c4:8a:d6:
+ 40:7e:d7:c3:cb:73:dc:c9:5d:5b:af:b0:41:b5:37:eb:ea:dc:
+ 20:91:c4:34:6a:f4:a1:f3:96:9d:37:86:97:e1:71:a4:dd:7d:
+ fa:44:84:94:ae:d7:09:04:22:76:0f:64:51:35:a9:24:0f:f9:
+ 0b:db:32:da:c2:fe:c1:b9:2a:5c:7a:27:13:ca:b1:48:3a:71:
+ d0:43
+-----BEGIN CERTIFICATE-----
+MIIEFTCCA36gAwIBAgIEByeO7TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEyMDQxODE2MzYxOFoXDTE4MDgxMzE2MzUxN1owWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAUcw
+ggFDMBIGA1UdEwEB/wQIMAYBAf8CAQMwSgYDVR0gBEMwQTA/BgRVHSAAMDcwNQYI
+KwYBBQUHAgEWKWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5MA4GA1UdDwEB/wQEAwIBBjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYT
+AlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJl
+clRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3Qg
+R2xvYmFsIFJvb3SCAgGlMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVi
+bGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwDQYJKoZIhvcN
+AQEFBQADgYEAkx3+i65G7MupD6vl78qyaBZo2I/6E6mvs8st50tujmkqwisQCo32
+rnO2ufsU/V9tuFC2xIrWQH7Xw8tz3MldW6+wQbU36+rcIJHENGr0ofOWnTeGl+Fx
+pN19+kSElK7XCQQidg9kUTWpJA/5C9sy2sL+wbkqXHonE8qxSDpx0EM=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert11[] = {
+ 0x30, 0x82, 0x04, 0x15, 0x30, 0x82, 0x03, 0x7e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x8e, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x32, 0x30, 0x34, 0x31, 0x38, 0x31, 0x36, 0x33, 0x36, 0x31,
+ 0x38, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x33, 0x31, 0x36,
+ 0x33, 0x35, 0x31, 0x37, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69,
+ 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79,
+ 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f,
+ 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f,
+ 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04,
+ 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79,
+ 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e,
+ 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d,
+ 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12,
+ 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88,
+ 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7,
+ 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5,
+ 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab,
+ 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5,
+ 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f,
+ 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77,
+ 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0,
+ 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd,
+ 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0,
+ 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4,
+ 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9,
+ 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c,
+ 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c,
+ 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b,
+ 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76,
+ 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27,
+ 0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x47, 0x30,
+ 0x82, 0x01, 0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30,
+ 0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x37, 0x30, 0x35, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x29, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81, 0x89, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77,
+ 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23,
+ 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45,
+ 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82,
+ 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e,
+ 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52,
+ 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x93, 0x1d, 0xfe,
+ 0x8b, 0xae, 0x46, 0xec, 0xcb, 0xa9, 0x0f, 0xab, 0xe5, 0xef, 0xca, 0xb2,
+ 0x68, 0x16, 0x68, 0xd8, 0x8f, 0xfa, 0x13, 0xa9, 0xaf, 0xb3, 0xcb, 0x2d,
+ 0xe7, 0x4b, 0x6e, 0x8e, 0x69, 0x2a, 0xc2, 0x2b, 0x10, 0x0a, 0x8d, 0xf6,
+ 0xae, 0x73, 0xb6, 0xb9, 0xfb, 0x14, 0xfd, 0x5f, 0x6d, 0xb8, 0x50, 0xb6,
+ 0xc4, 0x8a, 0xd6, 0x40, 0x7e, 0xd7, 0xc3, 0xcb, 0x73, 0xdc, 0xc9, 0x5d,
+ 0x5b, 0xaf, 0xb0, 0x41, 0xb5, 0x37, 0xeb, 0xea, 0xdc, 0x20, 0x91, 0xc4,
+ 0x34, 0x6a, 0xf4, 0xa1, 0xf3, 0x96, 0x9d, 0x37, 0x86, 0x97, 0xe1, 0x71,
+ 0xa4, 0xdd, 0x7d, 0xfa, 0x44, 0x84, 0x94, 0xae, 0xd7, 0x09, 0x04, 0x22,
+ 0x76, 0x0f, 0x64, 0x51, 0x35, 0xa9, 0x24, 0x0f, 0xf9, 0x0b, 0xdb, 0x32,
+ 0xda, 0xc2, 0xfe, 0xc1, 0xb9, 0x2a, 0x5c, 0x7a, 0x27, 0x13, 0xca, 0xb1,
+ 0x48, 0x3a, 0x71, 0xd0, 0x43,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120010508 (0x727370c)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: Sep 8 17:35:16 2010 GMT
+ Not After : Sep 8 17:34:08 2020 GMT
+ Subject: O=Cybertrust Inc, CN=Cybertrust Public SureServer SV CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a3:ba:99:8d:b7:e1:cd:73:88:f9:b9:dd:de:f4:
+ 05:f3:25:f5:3f:c5:52:1e:51:5a:3f:9a:ff:4d:84:
+ b7:50:7f:f1:10:8a:5d:7f:64:55:1c:3b:a3:f3:ff:
+ 97:7f:1c:4b:ed:6f:7f:e9:54:ec:97:2a:42:03:67:
+ 7f:b9:c8:6c:a2:97:f8:40:93:24:c3:25:5e:a5:66:
+ 8b:86:bd:d7:b9:26:22:6e:d2:66:83:b3:78:c1:7c:
+ 58:76:11:eb:16:55:47:32:f0:b9:34:10:bd:8f:26:
+ a2:25:68:c1:14:2b:a2:73:d6:66:3d:44:87:5c:13:
+ 7f:58:91:62:3d:57:7f:6c:ae:42:e8:12:7e:bd:78:
+ f1:f1:ac:5c:35:60:68:45:bc:53:73:87:11:1d:c5:
+ 2e:fa:60:35:da:91:f9:da:f2:55:6c:bf:ca:a2:57:
+ 5c:c8:64:bc:a9:5b:15:a0:fc:1c:f3:44:2e:bd:06:
+ f2:68:d8:40:2d:bb:b3:61:25:92:93:25:1c:77:46:
+ 90:bf:d0:af:b7:83:a0:3c:87:5e:a5:91:a8:ff:c1:
+ 31:1b:b6:4b:ac:12:34:08:d5:db:ec:89:87:63:06:
+ a7:53:f8:d5:f5:e6:66:ac:5e:84:65:46:c9:f4:3a:
+ 25:0f:6c:cc:0f:66:b8:9a:55:a1:46:6c:fc:91:23:
+ 5f:bd
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.50
+ CPS: http://cybertrust.omniroot.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+ X509v3 Subject Key Identifier:
+ 04:98:60:DF:80:1B:96:49:5D:65:56:2D:A5:2C:09:24:0A:EC:DC:B9
+ Signature Algorithm: sha1WithRSAEncryption
+ 5f:df:8b:cf:29:79:78:2b:f3:7c:f4:82:5f:79:e0:e1:b3:28:
+ bd:08:75:41:ce:8c:88:d7:0e:55:b9:02:b5:05:79:3e:bb:52:
+ 31:b3:4b:1e:b1:fe:d3:a2:21:43:d2:91:d3:16:fa:6b:79:e4:
+ 8e:4d:19:ec:4c:86:68:34:52:b7:6f:c2:bd:9c:78:be:f0:6f:
+ 3f:3d:9e:9f:49:74:c4:7c:97:19:45:57:ac:6f:fa:5a:3e:3f:
+ d3:d6:e3:2b:dc:8a:f8:c8:0a:0d:6b:8c:3f:94:78:37:98:88:
+ 61:91:df:59:14:0f:09:c5:63:54:fb:f4:f6:af:97:ec:fc:63:
+ 64:43:a6:bc:cc:e4:e3:1f:df:73:b0:6e:f7:b5:c8:29:9b:ae:
+ 25:52:b8:b4:72:e1:de:93:48:f1:28:9f:7e:66:3f:3f:8b:55:
+ 0f:f8:16:07:71:05:d7:65:9c:d7:1b:3c:34:e6:44:16:3a:bd:
+ d8:60:93:83:83:0c:88:96:65:33:40:df:6a:ac:ff:fe:94:51:
+ 61:bb:89:3f:f7:ac:c4:e4:b3:47:e2:fd:a2:6a:32:83:e2:7e:
+ 6f:f0:12:8e:a3:66:76:40:97:fb:11:e1:f7:73:1f:da:8b:1c:
+ 31:42:8b:9f:11:c5:49:a5:60:ed:48:2b:05:84:15:ab:2f:8a:
+ 2c:51:72:c0
+-----BEGIN CERTIFICATE-----
+MIIEGzCCAwOgAwIBAgIEByc3DDANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEwMDkwODE3MzUxNloX
+DTIwMDkwODE3MzQwOFowRjEXMBUGA1UEChMOQ3liZXJ0cnVzdCBJbmMxKzApBgNV
+BAMTIkN5YmVydHJ1c3QgUHVibGljIFN1cmVTZXJ2ZXIgU1YgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjupmNt+HNc4j5ud3e9AXzJfU/xVIeUVo/
+mv9NhLdQf/EQil1/ZFUcO6Pz/5d/HEvtb3/pVOyXKkIDZ3+5yGyil/hAkyTDJV6l
+ZouGvde5JiJu0maDs3jBfFh2EesWVUcy8Lk0EL2PJqIlaMEUK6Jz1mY9RIdcE39Y
+kWI9V39srkLoEn69ePHxrFw1YGhFvFNzhxEdxS76YDXakfna8lVsv8qiV1zIZLyp
+WxWg/BzzRC69BvJo2EAtu7NhJZKTJRx3RpC/0K+3g6A8h16lkaj/wTEbtkusEjQI
+1dvsiYdjBqdT+NX15masXoRlRsn0OiUPbMwPZriaVaFGbPyRI1+9AgMBAAGjgfww
+gfkwEgYDVR0TAQH/BAgwBgEB/wIBADBPBgNVHSAESDBGMEQGCSsGAQQBsT4BMjA3
+MDUGCCsGAQUFBwIBFilodHRwOi8vY3liZXJ0cnVzdC5vbW5pcm9vdC5jb20vcmVw
+b3NpdG9yeTAOBgNVHQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAU5Z1ZMIJHWMys+ghU
+NoZ7OrUETfAwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NkcDEucHVibGljLXRy
+dXN0LmNvbS9DUkwvT21uaXJvb3QyMDI1LmNybDAdBgNVHQ4EFgQUBJhg34Ablkld
+ZVYtpSwJJArs3LkwDQYJKoZIhvcNAQEFBQADggEBAF/fi88peXgr83z0gl954OGz
+KL0IdUHOjIjXDlW5ArUFeT67UjGzSx6x/tOiIUPSkdMW+mt55I5NGexMhmg0Urdv
+wr2ceL7wbz89np9JdMR8lxlFV6xv+lo+P9PW4yvcivjICg1rjD+UeDeYiGGR31kU
+DwnFY1T79Pavl+z8Y2RDprzM5OMf33Owbve1yCmbriVSuLRy4d6TSPEon35mPz+L
+VQ/4FgdxBddlnNcbPDTmRBY6vdhgk4ODDIiWZTNA32qs//6UUWG7iT/3rMTks0fi
+/aJqMoPifm/wEo6jZnZAl/sR4fdzH9qLHDFCi58RxUmlYO1IKwWEFasviixRcsA=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert12[] = {
+ 0x30, 0x82, 0x04, 0x1b, 0x30, 0x82, 0x03, 0x03, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x37, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+ 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+ 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30,
+ 0x30, 0x39, 0x30, 0x38, 0x31, 0x37, 0x33, 0x35, 0x31, 0x36, 0x5a, 0x17,
+ 0x0d, 0x32, 0x30, 0x30, 0x39, 0x30, 0x38, 0x31, 0x37, 0x33, 0x34, 0x30,
+ 0x38, 0x5a, 0x30, 0x46, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0e, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x22, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x53, 0x75,
+ 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x53, 0x56, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3,
+ 0xba, 0x99, 0x8d, 0xb7, 0xe1, 0xcd, 0x73, 0x88, 0xf9, 0xb9, 0xdd, 0xde,
+ 0xf4, 0x05, 0xf3, 0x25, 0xf5, 0x3f, 0xc5, 0x52, 0x1e, 0x51, 0x5a, 0x3f,
+ 0x9a, 0xff, 0x4d, 0x84, 0xb7, 0x50, 0x7f, 0xf1, 0x10, 0x8a, 0x5d, 0x7f,
+ 0x64, 0x55, 0x1c, 0x3b, 0xa3, 0xf3, 0xff, 0x97, 0x7f, 0x1c, 0x4b, 0xed,
+ 0x6f, 0x7f, 0xe9, 0x54, 0xec, 0x97, 0x2a, 0x42, 0x03, 0x67, 0x7f, 0xb9,
+ 0xc8, 0x6c, 0xa2, 0x97, 0xf8, 0x40, 0x93, 0x24, 0xc3, 0x25, 0x5e, 0xa5,
+ 0x66, 0x8b, 0x86, 0xbd, 0xd7, 0xb9, 0x26, 0x22, 0x6e, 0xd2, 0x66, 0x83,
+ 0xb3, 0x78, 0xc1, 0x7c, 0x58, 0x76, 0x11, 0xeb, 0x16, 0x55, 0x47, 0x32,
+ 0xf0, 0xb9, 0x34, 0x10, 0xbd, 0x8f, 0x26, 0xa2, 0x25, 0x68, 0xc1, 0x14,
+ 0x2b, 0xa2, 0x73, 0xd6, 0x66, 0x3d, 0x44, 0x87, 0x5c, 0x13, 0x7f, 0x58,
+ 0x91, 0x62, 0x3d, 0x57, 0x7f, 0x6c, 0xae, 0x42, 0xe8, 0x12, 0x7e, 0xbd,
+ 0x78, 0xf1, 0xf1, 0xac, 0x5c, 0x35, 0x60, 0x68, 0x45, 0xbc, 0x53, 0x73,
+ 0x87, 0x11, 0x1d, 0xc5, 0x2e, 0xfa, 0x60, 0x35, 0xda, 0x91, 0xf9, 0xda,
+ 0xf2, 0x55, 0x6c, 0xbf, 0xca, 0xa2, 0x57, 0x5c, 0xc8, 0x64, 0xbc, 0xa9,
+ 0x5b, 0x15, 0xa0, 0xfc, 0x1c, 0xf3, 0x44, 0x2e, 0xbd, 0x06, 0xf2, 0x68,
+ 0xd8, 0x40, 0x2d, 0xbb, 0xb3, 0x61, 0x25, 0x92, 0x93, 0x25, 0x1c, 0x77,
+ 0x46, 0x90, 0xbf, 0xd0, 0xaf, 0xb7, 0x83, 0xa0, 0x3c, 0x87, 0x5e, 0xa5,
+ 0x91, 0xa8, 0xff, 0xc1, 0x31, 0x1b, 0xb6, 0x4b, 0xac, 0x12, 0x34, 0x08,
+ 0xd5, 0xdb, 0xec, 0x89, 0x87, 0x63, 0x06, 0xa7, 0x53, 0xf8, 0xd5, 0xf5,
+ 0xe6, 0x66, 0xac, 0x5e, 0x84, 0x65, 0x46, 0xc9, 0xf4, 0x3a, 0x25, 0x0f,
+ 0x6c, 0xcc, 0x0f, 0x66, 0xb8, 0x9a, 0x55, 0xa1, 0x46, 0x6c, 0xfc, 0x91,
+ 0x23, 0x5f, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfc, 0x30,
+ 0x81, 0xf9, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x4f,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x48, 0x30, 0x46, 0x30, 0x44, 0x06,
+ 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x32, 0x30, 0x37,
+ 0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+ 0x16, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69,
+ 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54,
+ 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0,
+ 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64,
+ 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f,
+ 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x04, 0x98, 0x60, 0xdf, 0x80, 0x1b, 0x96, 0x49, 0x5d,
+ 0x65, 0x56, 0x2d, 0xa5, 0x2c, 0x09, 0x24, 0x0a, 0xec, 0xdc, 0xb9, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5f, 0xdf, 0x8b, 0xcf, 0x29,
+ 0x79, 0x78, 0x2b, 0xf3, 0x7c, 0xf4, 0x82, 0x5f, 0x79, 0xe0, 0xe1, 0xb3,
+ 0x28, 0xbd, 0x08, 0x75, 0x41, 0xce, 0x8c, 0x88, 0xd7, 0x0e, 0x55, 0xb9,
+ 0x02, 0xb5, 0x05, 0x79, 0x3e, 0xbb, 0x52, 0x31, 0xb3, 0x4b, 0x1e, 0xb1,
+ 0xfe, 0xd3, 0xa2, 0x21, 0x43, 0xd2, 0x91, 0xd3, 0x16, 0xfa, 0x6b, 0x79,
+ 0xe4, 0x8e, 0x4d, 0x19, 0xec, 0x4c, 0x86, 0x68, 0x34, 0x52, 0xb7, 0x6f,
+ 0xc2, 0xbd, 0x9c, 0x78, 0xbe, 0xf0, 0x6f, 0x3f, 0x3d, 0x9e, 0x9f, 0x49,
+ 0x74, 0xc4, 0x7c, 0x97, 0x19, 0x45, 0x57, 0xac, 0x6f, 0xfa, 0x5a, 0x3e,
+ 0x3f, 0xd3, 0xd6, 0xe3, 0x2b, 0xdc, 0x8a, 0xf8, 0xc8, 0x0a, 0x0d, 0x6b,
+ 0x8c, 0x3f, 0x94, 0x78, 0x37, 0x98, 0x88, 0x61, 0x91, 0xdf, 0x59, 0x14,
+ 0x0f, 0x09, 0xc5, 0x63, 0x54, 0xfb, 0xf4, 0xf6, 0xaf, 0x97, 0xec, 0xfc,
+ 0x63, 0x64, 0x43, 0xa6, 0xbc, 0xcc, 0xe4, 0xe3, 0x1f, 0xdf, 0x73, 0xb0,
+ 0x6e, 0xf7, 0xb5, 0xc8, 0x29, 0x9b, 0xae, 0x25, 0x52, 0xb8, 0xb4, 0x72,
+ 0xe1, 0xde, 0x93, 0x48, 0xf1, 0x28, 0x9f, 0x7e, 0x66, 0x3f, 0x3f, 0x8b,
+ 0x55, 0x0f, 0xf8, 0x16, 0x07, 0x71, 0x05, 0xd7, 0x65, 0x9c, 0xd7, 0x1b,
+ 0x3c, 0x34, 0xe6, 0x44, 0x16, 0x3a, 0xbd, 0xd8, 0x60, 0x93, 0x83, 0x83,
+ 0x0c, 0x88, 0x96, 0x65, 0x33, 0x40, 0xdf, 0x6a, 0xac, 0xff, 0xfe, 0x94,
+ 0x51, 0x61, 0xbb, 0x89, 0x3f, 0xf7, 0xac, 0xc4, 0xe4, 0xb3, 0x47, 0xe2,
+ 0xfd, 0xa2, 0x6a, 0x32, 0x83, 0xe2, 0x7e, 0x6f, 0xf0, 0x12, 0x8e, 0xa3,
+ 0x66, 0x76, 0x40, 0x97, 0xfb, 0x11, 0xe1, 0xf7, 0x73, 0x1f, 0xda, 0x8b,
+ 0x1c, 0x31, 0x42, 0x8b, 0x9f, 0x11, 0xc5, 0x49, 0xa5, 0x60, 0xed, 0x48,
+ 0x2b, 0x05, 0x84, 0x15, 0xab, 0x2f, 0x8a, 0x2c, 0x51, 0x72, 0xc0,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 7 (0x7)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=America Online Inc., CN=America Online Root Certification Authority 1
+ Validity
+ Not Before: Jun 4 17:26:39 2004 GMT
+ Not After : Jun 4 17:26:39 2029 GMT
+ Subject: C=US, ST=Virginia, L=Dulles, O=America Online Inc., CN=AOL Member CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:f8:63:d8:f1:cc:d6:11:6f:05:e1:d6:9f:43:27:
+ 27:25:fa:af:67:87:74:ac:dc:d6:fc:c2:9a:bd:33:
+ 72:4e:65:b0:5a:cd:eb:f8:a9:69:48:39:6e:68:2e:
+ 71:16:6e:9e:59:7c:c2:7c:cd:ed:6e:43:d8:09:42:
+ 4e:0d:9a:7d:ee:b5:5a:23:81:d2:a4:5b:a9:51:54:
+ 1c:df:f6:84:df:19:c3:3e:94:2d:8d:ba:10:f8:e8:
+ 43:08:0f:32:35:6c:35:31:f8:d6:d3:fc:09:31:d6:
+ a9:a1:7a:20:06:59:0c:e0:2b:8d:84:c3:37:a0:08:
+ 1e:f1:35:73:10:dd:4f:fd:0c:72:93:26:6e:af:c5:
+ 1c:39:e3:ca:f3:95:6f:30:c2:85:3d:4d:84:20:c8:
+ 3e:3d:d0:40:d6:fe:06:4a:18:73:0b:6e:57:67:db:
+ 83:c1:13:66:97:d3:bd:59:bc:7e:fa:2f:36:45:14:
+ cd:bc:bf:ab:68:77:bf:48:eb:11:89:4e:6a:84:f3:
+ 5d:1c:e5:6b:6a:00:e6:6b:8d:48:a4:09:b9:21:dc:
+ 2d:66:29:f4:56:9e:f0:05:68:ff:cc:c1:c9:88:bc:
+ d2:2c:0b:af:1d:74:1a:86:68:a4:6d:14:74:ec:24:
+ 80:f8:95:b9:f3:2e:3c:3d:20:6f:09:02:38:ea:3a:
+ 38:05
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 61:A6:99:6D:24:9F:0E:11:88:E6:39:E0:FE:74:D1:05:69:52:A9:43
+ X509v3 Authority Key Identifier:
+ keyid:00:AD:D9:A3:F6:79:F6:6E:74:A9:7F:33:3D:81:17:D7:4C:CF:33:DE
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://pki-info.aol.com/AOL/index.html
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.aol.com/AOL/MasterCRL.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 0c:9d:bc:cc:1d:d4:2e:91:1b:af:a0:3a:eb:cd:5e:fc:25:2a:
+ 29:8e:b3:20:e0:17:37:fa:fc:bb:c5:b5:14:bb:1c:66:0e:6f:
+ 58:5f:c6:71:d0:13:89:c7:ad:23:0b:ed:4c:b8:58:c1:e3:c2:
+ a4:24:4c:65:76:0d:b3:86:64:4f:28:ba:cf:96:f8:65:9a:0e:
+ 82:26:f5:82:85:4e:35:20:b3:45:cc:60:ee:0f:4e:20:94:3a:
+ 2b:2f:cb:23:10:9b:46:1b:7e:c3:56:75:49:24:a4:b8:4d:9f:
+ 1c:68:d4:e6:f2:2f:af:8e:ed:2b:b7:e5:96:6b:1c:3d:8d:bf:
+ 20:d3:6f:2d:54:2f:9c:79:35:fd:da:06:de:68:20:20:4b:af:
+ 5d:ab:5e:66:c3:14:64:7b:f7:02:e6:27:96:ad:18:1e:ab:f3:
+ 82:60:fc:4c:5f:b6:0a:52:7b:9e:9c:3b:2e:ce:3c:42:5f:36:
+ 6d:6b:fe:a1:76:8a:22:21:fd:5b:e8:bd:7f:9f:ce:51:74:48:
+ 6c:ac:b5:d1:a2:6a:fa:07:44:de:d0:db:a9:8d:18:1f:f1:b9:
+ c5:e8:2a:eb:ba:3d:3b:18:8c:c0:0c:30:b3:c9:21:1c:33:4c:
+ 3a:49:53:d4:a8:ba:ba:38:23:3d:3a:65:82:5e:79:71:15:f8:
+ 25:2b:7d:19
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIBBzANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
+MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
+bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTA0MDYwNDE3
+MjYzOVoXDTI5MDYwNDE3MjYzOVowZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZp
+cmdpbmlhMQ8wDQYDVQQHEwZEdWxsZXMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5l
+IEluYy4xFjAUBgNVBAMTDUFPTCBNZW1iZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQD4Y9jxzNYRbwXh1p9DJycl+q9nh3Ss3Nb8wpq9M3JOZbBa
+zev4qWlIOW5oLnEWbp5ZfMJ8ze1uQ9gJQk4Nmn3utVojgdKkW6lRVBzf9oTfGcM+
+lC2NuhD46EMIDzI1bDUx+NbT/Akx1qmheiAGWQzgK42EwzegCB7xNXMQ3U/9DHKT
+Jm6vxRw548rzlW8wwoU9TYQgyD490EDW/gZKGHMLbldn24PBE2aX071ZvH76LzZF
+FM28v6tod79I6xGJTmqE810c5WtqAOZrjUikCbkh3C1mKfRWnvAFaP/MwcmIvNIs
+C68ddBqGaKRtFHTsJID4lbnzLjw9IG8JAjjqOjgFAgMBAAGjgeUwgeIwDgYDVR0P
+AQH/BAQDAgGGMB0GA1UdDgQWBBRhppltJJ8OEYjmOeD+dNEFaVKpQzAfBgNVHSME
+GDAWgBQArdmj9nn2bnSpfzM9gRfXTM8z3jAPBgNVHRMBAf8EBTADAQH/MEgGA1Ud
+IARBMD8wPQYEVR0gADA1MDMGCCsGAQUFBwIBFidodHRwczovL3BraS1pbmZvLmFv
+bC5jb20vQU9ML2luZGV4Lmh0bWwwNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2Ny
+bC5hb2wuY29tL0FPTC9NYXN0ZXJDUkwuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAM
+nbzMHdQukRuvoDrrzV78JSopjrMg4Bc3+vy7xbUUuxxmDm9YX8Zx0BOJx60jC+1M
+uFjB48KkJExldg2zhmRPKLrPlvhlmg6CJvWChU41ILNFzGDuD04glDorL8sjEJtG
+G37DVnVJJKS4TZ8caNTm8i+vju0rt+WWaxw9jb8g028tVC+ceTX92gbeaCAgS69d
+q15mwxRke/cC5ieWrRgeq/OCYPxMX7YKUnuenDsuzjxCXzZta/6hdooiIf1b6L1/
+n85RdEhsrLXRomr6B0Te0NupjRgf8bnF6Crruj07GIzADDCzySEcM0w6SVPUqLq6
+OCM9OmWCXnlxFfglK30Z
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert13[] = {
+ 0x30, 0x82, 0x04, 0x2b, 0x30, 0x82, 0x03, 0x13, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x01, 0x07, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x63, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x1c,
+ 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x13, 0x41, 0x6d, 0x65,
+ 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x2d, 0x41, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f,
+ 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x31,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34, 0x30, 0x36, 0x30, 0x34, 0x31, 0x37,
+ 0x32, 0x36, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x39, 0x30, 0x36, 0x30,
+ 0x34, 0x31, 0x37, 0x32, 0x36, 0x33, 0x39, 0x5a, 0x30, 0x67, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x08, 0x56, 0x69,
+ 0x72, 0x67, 0x69, 0x6e, 0x69, 0x61, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03,
+ 0x55, 0x04, 0x07, 0x13, 0x06, 0x44, 0x75, 0x6c, 0x6c, 0x65, 0x73, 0x31,
+ 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x13, 0x41, 0x6d,
+ 0x65, 0x72, 0x69, 0x63, 0x61, 0x20, 0x4f, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0d, 0x41, 0x4f, 0x4c, 0x20, 0x4d, 0x65, 0x6d, 0x62,
+ 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xf8, 0x63, 0xd8, 0xf1, 0xcc, 0xd6, 0x11, 0x6f, 0x05, 0xe1,
+ 0xd6, 0x9f, 0x43, 0x27, 0x27, 0x25, 0xfa, 0xaf, 0x67, 0x87, 0x74, 0xac,
+ 0xdc, 0xd6, 0xfc, 0xc2, 0x9a, 0xbd, 0x33, 0x72, 0x4e, 0x65, 0xb0, 0x5a,
+ 0xcd, 0xeb, 0xf8, 0xa9, 0x69, 0x48, 0x39, 0x6e, 0x68, 0x2e, 0x71, 0x16,
+ 0x6e, 0x9e, 0x59, 0x7c, 0xc2, 0x7c, 0xcd, 0xed, 0x6e, 0x43, 0xd8, 0x09,
+ 0x42, 0x4e, 0x0d, 0x9a, 0x7d, 0xee, 0xb5, 0x5a, 0x23, 0x81, 0xd2, 0xa4,
+ 0x5b, 0xa9, 0x51, 0x54, 0x1c, 0xdf, 0xf6, 0x84, 0xdf, 0x19, 0xc3, 0x3e,
+ 0x94, 0x2d, 0x8d, 0xba, 0x10, 0xf8, 0xe8, 0x43, 0x08, 0x0f, 0x32, 0x35,
+ 0x6c, 0x35, 0x31, 0xf8, 0xd6, 0xd3, 0xfc, 0x09, 0x31, 0xd6, 0xa9, 0xa1,
+ 0x7a, 0x20, 0x06, 0x59, 0x0c, 0xe0, 0x2b, 0x8d, 0x84, 0xc3, 0x37, 0xa0,
+ 0x08, 0x1e, 0xf1, 0x35, 0x73, 0x10, 0xdd, 0x4f, 0xfd, 0x0c, 0x72, 0x93,
+ 0x26, 0x6e, 0xaf, 0xc5, 0x1c, 0x39, 0xe3, 0xca, 0xf3, 0x95, 0x6f, 0x30,
+ 0xc2, 0x85, 0x3d, 0x4d, 0x84, 0x20, 0xc8, 0x3e, 0x3d, 0xd0, 0x40, 0xd6,
+ 0xfe, 0x06, 0x4a, 0x18, 0x73, 0x0b, 0x6e, 0x57, 0x67, 0xdb, 0x83, 0xc1,
+ 0x13, 0x66, 0x97, 0xd3, 0xbd, 0x59, 0xbc, 0x7e, 0xfa, 0x2f, 0x36, 0x45,
+ 0x14, 0xcd, 0xbc, 0xbf, 0xab, 0x68, 0x77, 0xbf, 0x48, 0xeb, 0x11, 0x89,
+ 0x4e, 0x6a, 0x84, 0xf3, 0x5d, 0x1c, 0xe5, 0x6b, 0x6a, 0x00, 0xe6, 0x6b,
+ 0x8d, 0x48, 0xa4, 0x09, 0xb9, 0x21, 0xdc, 0x2d, 0x66, 0x29, 0xf4, 0x56,
+ 0x9e, 0xf0, 0x05, 0x68, 0xff, 0xcc, 0xc1, 0xc9, 0x88, 0xbc, 0xd2, 0x2c,
+ 0x0b, 0xaf, 0x1d, 0x74, 0x1a, 0x86, 0x68, 0xa4, 0x6d, 0x14, 0x74, 0xec,
+ 0x24, 0x80, 0xf8, 0x95, 0xb9, 0xf3, 0x2e, 0x3c, 0x3d, 0x20, 0x6f, 0x09,
+ 0x02, 0x38, 0xea, 0x3a, 0x38, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x81, 0xe5, 0x30, 0x81, 0xe2, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x61, 0xa6, 0x99, 0x6d,
+ 0x24, 0x9f, 0x0e, 0x11, 0x88, 0xe6, 0x39, 0xe0, 0xfe, 0x74, 0xd1, 0x05,
+ 0x69, 0x52, 0xa9, 0x43, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x00, 0xad, 0xd9, 0xa3, 0xf6, 0x79, 0xf6,
+ 0x6e, 0x74, 0xa9, 0x7f, 0x33, 0x3d, 0x81, 0x17, 0xd7, 0x4c, 0xcf, 0x33,
+ 0xde, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x48, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x41, 0x30, 0x3f, 0x30, 0x3d, 0x06, 0x04, 0x55, 0x1d, 0x20,
+ 0x00, 0x30, 0x35, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x27, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+ 0x2f, 0x70, 0x6b, 0x69, 0x2d, 0x69, 0x6e, 0x66, 0x6f, 0x2e, 0x61, 0x6f,
+ 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x4f, 0x4c, 0x2f, 0x69, 0x6e,
+ 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x30, 0x35, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x2e, 0x30, 0x2c, 0x30, 0x2a, 0xa0, 0x28, 0xa0,
+ 0x26, 0x86, 0x24, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x61, 0x6f, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x4f,
+ 0x4c, 0x2f, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x52, 0x4c, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x0c,
+ 0x9d, 0xbc, 0xcc, 0x1d, 0xd4, 0x2e, 0x91, 0x1b, 0xaf, 0xa0, 0x3a, 0xeb,
+ 0xcd, 0x5e, 0xfc, 0x25, 0x2a, 0x29, 0x8e, 0xb3, 0x20, 0xe0, 0x17, 0x37,
+ 0xfa, 0xfc, 0xbb, 0xc5, 0xb5, 0x14, 0xbb, 0x1c, 0x66, 0x0e, 0x6f, 0x58,
+ 0x5f, 0xc6, 0x71, 0xd0, 0x13, 0x89, 0xc7, 0xad, 0x23, 0x0b, 0xed, 0x4c,
+ 0xb8, 0x58, 0xc1, 0xe3, 0xc2, 0xa4, 0x24, 0x4c, 0x65, 0x76, 0x0d, 0xb3,
+ 0x86, 0x64, 0x4f, 0x28, 0xba, 0xcf, 0x96, 0xf8, 0x65, 0x9a, 0x0e, 0x82,
+ 0x26, 0xf5, 0x82, 0x85, 0x4e, 0x35, 0x20, 0xb3, 0x45, 0xcc, 0x60, 0xee,
+ 0x0f, 0x4e, 0x20, 0x94, 0x3a, 0x2b, 0x2f, 0xcb, 0x23, 0x10, 0x9b, 0x46,
+ 0x1b, 0x7e, 0xc3, 0x56, 0x75, 0x49, 0x24, 0xa4, 0xb8, 0x4d, 0x9f, 0x1c,
+ 0x68, 0xd4, 0xe6, 0xf2, 0x2f, 0xaf, 0x8e, 0xed, 0x2b, 0xb7, 0xe5, 0x96,
+ 0x6b, 0x1c, 0x3d, 0x8d, 0xbf, 0x20, 0xd3, 0x6f, 0x2d, 0x54, 0x2f, 0x9c,
+ 0x79, 0x35, 0xfd, 0xda, 0x06, 0xde, 0x68, 0x20, 0x20, 0x4b, 0xaf, 0x5d,
+ 0xab, 0x5e, 0x66, 0xc3, 0x14, 0x64, 0x7b, 0xf7, 0x02, 0xe6, 0x27, 0x96,
+ 0xad, 0x18, 0x1e, 0xab, 0xf3, 0x82, 0x60, 0xfc, 0x4c, 0x5f, 0xb6, 0x0a,
+ 0x52, 0x7b, 0x9e, 0x9c, 0x3b, 0x2e, 0xce, 0x3c, 0x42, 0x5f, 0x36, 0x6d,
+ 0x6b, 0xfe, 0xa1, 0x76, 0x8a, 0x22, 0x21, 0xfd, 0x5b, 0xe8, 0xbd, 0x7f,
+ 0x9f, 0xce, 0x51, 0x74, 0x48, 0x6c, 0xac, 0xb5, 0xd1, 0xa2, 0x6a, 0xfa,
+ 0x07, 0x44, 0xde, 0xd0, 0xdb, 0xa9, 0x8d, 0x18, 0x1f, 0xf1, 0xb9, 0xc5,
+ 0xe8, 0x2a, 0xeb, 0xba, 0x3d, 0x3b, 0x18, 0x8c, 0xc0, 0x0c, 0x30, 0xb3,
+ 0xc9, 0x21, 0x1c, 0x33, 0x4c, 0x3a, 0x49, 0x53, 0xd4, 0xa8, 0xba, 0xba,
+ 0x38, 0x23, 0x3d, 0x3a, 0x65, 0x82, 0x5e, 0x79, 0x71, 0x15, 0xf8, 0x25,
+ 0x2b, 0x7d, 0x19,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 11:20:96:f6:c8:03:7c:9e:07:b1:38:bf:2e:72:10:8a:d7:ed
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=FR, O=Certplus, CN=Class 2 Primary CA
+ Validity
+ Not Before: Jun 5 00:00:00 2007 GMT
+ Not After : Jun 20 00:00:00 2019 GMT
+ Subject: C=FR, O=KEYNECTIS, CN=CLASS 2 KEYNECTIS CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:be:fe:44:23:04:d4:ef:2f:3b:86:aa:35:58:
+ 81:d1:e1:9a:d6:b1:d4:27:45:28:fc:d1:1e:46:85:
+ ba:54:23:11:7d:e0:66:3f:d4:a3:57:66:78:f9:6b:
+ eb:74:7c:2a:b8:37:a5:e8:70:ae:82:b5:4e:d4:81:
+ fe:5b:e2:ea:e7:22:16:f8:f9:d7:ba:3a:f6:88:56:
+ dc:c4:f2:a0:a4:e5:75:06:60:72:2b:fb:f5:94:ee:
+ 2c:83:28:de:91:9a:b3:83:3a:b0:9f:08:fa:dd:d8:
+ 9e:8c:24:e6:df:66:5b:c8:7e:a3:62:4d:3f:3a:85:
+ 23:ec:e8:71:8f:0a:00:ac:89:6d:7e:d8:72:e5:dd:
+ c1:94:8e:5f:e4:73:e6:c1:c6:0c:87:58:4f:37:da:
+ d1:a9:88:26:76:b4:ee:11:8d:f6:ad:b2:a7:bc:73:
+ c4:cd:1c:6e:1a:e6:8d:72:56:44:a0:98:f7:92:f9:
+ d7:79:9b:03:e6:68:5f:a4:5c:7c:3d:50:b4:83:cc:
+ e5:ac:0d:e1:3e:4f:14:f2:b4:e4:7d:bf:71:a4:c3:
+ 97:73:38:d6:52:7c:c8:a4:b5:ea:e9:b2:54:56:d4:
+ eb:b8:57:3a:40:52:5a:5e:46:27:a3:7b:30:2d:08:
+ 3d:85:1e:9a:f0:32:a8:f2:10:a2:83:9b:e2:28:f6:
+ 9d:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.4.1.22234.2.5.3.3
+ CPS: http://www.keynectis.com/PC
+ Policy: 1.3.6.4.1.22234.2.5.1.3
+ CPS: http://www.keynectis.com/PC
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.certplus.com/CRL/class2.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 00:11:41:DF:3B:9D:3B:CB:B8:A2:C1:33:92:A8:81:CC:E5:7D:E7:99
+ X509v3 Authority Key Identifier:
+ keyid:E3:73:2D:DF:CB:0E:28:0C:DE:DD:B3:A4:CA:79:B8:8E:BB:E8:30:89
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 08:88:fe:1f:a2:ca:cd:e2:a0:f1:2e:7c:67:49:fb:dc:94:ac:
+ 7f:41:0d:78:01:ba:31:f7:9b:fb:31:18:77:2f:66:25:94:b8:
+ 6d:16:74:81:f1:c0:ae:67:c6:14:45:7a:01:d1:13:88:fc:e2:
+ 8d:22:1d:bd:1e:0c:c7:a9:7e:d0:c3:97:f6:37:5b:41:5e:67:
+ 94:8e:ab:69:02:17:18:f5:4d:38:c2:49:28:09:6e:5a:9b:a6:
+ 27:db:c0:5f:8f:44:9c:90:65:99:d8:b3:2e:c1:92:ee:1a:9d:
+ 0f:72:45:20:fa:2c:0c:9c:5d:cd:5b:54:41:54:4f:d3:e2:c7:
+ 59:84:3f:17:7b:7d:0e:c2:ef:62:c7:ba:b1:26:6c:83:4e:d3:
+ 19:c5:ff:56:a7:b4:45:3f:7a:9e:fa:d0:39:3e:80:46:75:5d:
+ 5a:79:7a:33:c5:01:bc:02:44:ce:1b:c0:31:4e:47:96:15:6e:
+ e7:e4:76:f0:c2:90:0d:a1:78:f4:38:00:91:2b:65:7c:79:13:
+ a8:3e:91:14:dc:88:05:08:d7:6f:53:f6:15:43:ee:c5:53:56:
+ 1a:02:b5:a6:a2:46:8d:1e:13:e4:67:c2:45:5f:40:5e:10:42:
+ 58:b5:cd:44:a3:94:4c:1c:54:90:4d:91:9a:26:8b:ad:a2:80:
+ 50:8d:14:14
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgISESCW9sgDfJ4HsTi/LnIQitftMA0GCSqGSIb3DQEBBQUA
+MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xh
+c3MgMiBQcmltYXJ5IENBMB4XDTA3MDYwNTAwMDAwMFoXDTE5MDYyMDAwMDAwMFow
+QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoTCUtFWU5FQ1RJUzEdMBsGA1UEAxMUQ0xB
+U1MgMiBLRVlORUNUSVMgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDGvv5EIwTU7y87hqo1WIHR4ZrWsdQnRSj80R5GhbpUIxF94GY/1KNXZnj5a+t0
+fCq4N6XocK6CtU7Ugf5b4urnIhb4+de6OvaIVtzE8qCk5XUGYHIr+/WU7iyDKN6R
+mrODOrCfCPrd2J6MJObfZlvIfqNiTT86hSPs6HGPCgCsiW1+2HLl3cGUjl/kc+bB
+xgyHWE832tGpiCZ2tO4Rjfatsqe8c8TNHG4a5o1yVkSgmPeS+dd5mwPmaF+kXHw9
+ULSDzOWsDeE+TxTytOR9v3Gkw5dzONZSfMikterpslRW1Ou4VzpAUlpeRiejezAt
+CD2FHprwMqjyEKKDm+Io9p3LAgMBAAGjggEgMIIBHDASBgNVHRMBAf8ECDAGAQH/
+AgEAMH0GA1UdIAR2MHQwOAYLKwYEAYGtWgIFAwMwKTAnBggrBgEFBQcCARYbaHR0
+cDovL3d3dy5rZXluZWN0aXMuY29tL1BDMDgGCysGBAGBrVoCBQEDMCkwJwYIKwYB
+BQUHAgEWG2h0dHA6Ly93d3cua2V5bmVjdGlzLmNvbS9QQzA3BgNVHR8EMDAuMCyg
+KqAohiZodHRwOi8vd3d3LmNlcnRwbHVzLmNvbS9DUkwvY2xhc3MyLmNybDAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAARQd87nTvLuKLBM5KogczlfeeZMB8GA1Ud
+IwQYMBaAFONzLd/LDigM3t2zpMp5uI676DCJMA0GCSqGSIb3DQEBBQUAA4IBAQAI
+iP4fosrN4qDxLnxnSfvclKx/QQ14Abox95v7MRh3L2YllLhtFnSB8cCuZ8YURXoB
+0ROI/OKNIh29HgzHqX7Qw5f2N1tBXmeUjqtpAhcY9U04wkkoCW5am6Yn28Bfj0Sc
+kGWZ2LMuwZLuGp0PckUg+iwMnF3NW1RBVE/T4sdZhD8Xe30Owu9ix7qxJmyDTtMZ
+xf9Wp7RFP3qe+tA5PoBGdV1aeXozxQG8AkTOG8AxTkeWFW7n5HbwwpANoXj0OACR
+K2V8eROoPpEU3IgFCNdvU/YVQ+7FU1YaArWmokaNHhPkZ8JFX0BeEEJYtc1Eo5RM
+HFSQTZGaJoutooBQjRQU
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert14[] = {
+ 0x30, 0x82, 0x04, 0x2b, 0x30, 0x82, 0x03, 0x13, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x12, 0x11, 0x20, 0x96, 0xf6, 0xc8, 0x03, 0x7c, 0x9e, 0x07,
+ 0xb1, 0x38, 0xbf, 0x2e, 0x72, 0x10, 0x8a, 0xd7, 0xed, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x30, 0x3d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x46, 0x52, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x08, 0x43, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73, 0x31, 0x1b,
+ 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x43, 0x6c, 0x61,
+ 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
+ 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x36, 0x30,
+ 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39,
+ 0x30, 0x36, 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30,
+ 0x40, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x46, 0x52, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x09, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x53, 0x31, 0x1d,
+ 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x14, 0x43, 0x4c, 0x41,
+ 0x53, 0x53, 0x20, 0x32, 0x20, 0x4b, 0x45, 0x59, 0x4e, 0x45, 0x43, 0x54,
+ 0x49, 0x53, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xc6, 0xbe, 0xfe, 0x44, 0x23, 0x04, 0xd4, 0xef, 0x2f, 0x3b,
+ 0x86, 0xaa, 0x35, 0x58, 0x81, 0xd1, 0xe1, 0x9a, 0xd6, 0xb1, 0xd4, 0x27,
+ 0x45, 0x28, 0xfc, 0xd1, 0x1e, 0x46, 0x85, 0xba, 0x54, 0x23, 0x11, 0x7d,
+ 0xe0, 0x66, 0x3f, 0xd4, 0xa3, 0x57, 0x66, 0x78, 0xf9, 0x6b, 0xeb, 0x74,
+ 0x7c, 0x2a, 0xb8, 0x37, 0xa5, 0xe8, 0x70, 0xae, 0x82, 0xb5, 0x4e, 0xd4,
+ 0x81, 0xfe, 0x5b, 0xe2, 0xea, 0xe7, 0x22, 0x16, 0xf8, 0xf9, 0xd7, 0xba,
+ 0x3a, 0xf6, 0x88, 0x56, 0xdc, 0xc4, 0xf2, 0xa0, 0xa4, 0xe5, 0x75, 0x06,
+ 0x60, 0x72, 0x2b, 0xfb, 0xf5, 0x94, 0xee, 0x2c, 0x83, 0x28, 0xde, 0x91,
+ 0x9a, 0xb3, 0x83, 0x3a, 0xb0, 0x9f, 0x08, 0xfa, 0xdd, 0xd8, 0x9e, 0x8c,
+ 0x24, 0xe6, 0xdf, 0x66, 0x5b, 0xc8, 0x7e, 0xa3, 0x62, 0x4d, 0x3f, 0x3a,
+ 0x85, 0x23, 0xec, 0xe8, 0x71, 0x8f, 0x0a, 0x00, 0xac, 0x89, 0x6d, 0x7e,
+ 0xd8, 0x72, 0xe5, 0xdd, 0xc1, 0x94, 0x8e, 0x5f, 0xe4, 0x73, 0xe6, 0xc1,
+ 0xc6, 0x0c, 0x87, 0x58, 0x4f, 0x37, 0xda, 0xd1, 0xa9, 0x88, 0x26, 0x76,
+ 0xb4, 0xee, 0x11, 0x8d, 0xf6, 0xad, 0xb2, 0xa7, 0xbc, 0x73, 0xc4, 0xcd,
+ 0x1c, 0x6e, 0x1a, 0xe6, 0x8d, 0x72, 0x56, 0x44, 0xa0, 0x98, 0xf7, 0x92,
+ 0xf9, 0xd7, 0x79, 0x9b, 0x03, 0xe6, 0x68, 0x5f, 0xa4, 0x5c, 0x7c, 0x3d,
+ 0x50, 0xb4, 0x83, 0xcc, 0xe5, 0xac, 0x0d, 0xe1, 0x3e, 0x4f, 0x14, 0xf2,
+ 0xb4, 0xe4, 0x7d, 0xbf, 0x71, 0xa4, 0xc3, 0x97, 0x73, 0x38, 0xd6, 0x52,
+ 0x7c, 0xc8, 0xa4, 0xb5, 0xea, 0xe9, 0xb2, 0x54, 0x56, 0xd4, 0xeb, 0xb8,
+ 0x57, 0x3a, 0x40, 0x52, 0x5a, 0x5e, 0x46, 0x27, 0xa3, 0x7b, 0x30, 0x2d,
+ 0x08, 0x3d, 0x85, 0x1e, 0x9a, 0xf0, 0x32, 0xa8, 0xf2, 0x10, 0xa2, 0x83,
+ 0x9b, 0xe2, 0x28, 0xf6, 0x9d, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x12, 0x06, 0x03, 0x55,
+ 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff,
+ 0x02, 0x01, 0x00, 0x30, 0x7d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x76,
+ 0x30, 0x74, 0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad,
+ 0x5a, 0x02, 0x05, 0x03, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e,
+ 0x65, 0x63, 0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43,
+ 0x30, 0x38, 0x06, 0x0b, 0x2b, 0x06, 0x04, 0x01, 0x81, 0xad, 0x5a, 0x02,
+ 0x05, 0x01, 0x03, 0x30, 0x29, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6b, 0x65, 0x79, 0x6e, 0x65, 0x63,
+ 0x74, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x43, 0x30, 0x37,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x30, 0x30, 0x2e, 0x30, 0x2c, 0xa0,
+ 0x2a, 0xa0, 0x28, 0x86, 0x26, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x72, 0x74, 0x70, 0x6c, 0x75, 0x73,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x63, 0x6c, 0x61,
+ 0x73, 0x73, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x00, 0x11,
+ 0x41, 0xdf, 0x3b, 0x9d, 0x3b, 0xcb, 0xb8, 0xa2, 0xc1, 0x33, 0x92, 0xa8,
+ 0x81, 0xcc, 0xe5, 0x7d, 0xe7, 0x99, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe3, 0x73, 0x2d, 0xdf, 0xcb,
+ 0x0e, 0x28, 0x0c, 0xde, 0xdd, 0xb3, 0xa4, 0xca, 0x79, 0xb8, 0x8e, 0xbb,
+ 0xe8, 0x30, 0x89, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x08,
+ 0x88, 0xfe, 0x1f, 0xa2, 0xca, 0xcd, 0xe2, 0xa0, 0xf1, 0x2e, 0x7c, 0x67,
+ 0x49, 0xfb, 0xdc, 0x94, 0xac, 0x7f, 0x41, 0x0d, 0x78, 0x01, 0xba, 0x31,
+ 0xf7, 0x9b, 0xfb, 0x31, 0x18, 0x77, 0x2f, 0x66, 0x25, 0x94, 0xb8, 0x6d,
+ 0x16, 0x74, 0x81, 0xf1, 0xc0, 0xae, 0x67, 0xc6, 0x14, 0x45, 0x7a, 0x01,
+ 0xd1, 0x13, 0x88, 0xfc, 0xe2, 0x8d, 0x22, 0x1d, 0xbd, 0x1e, 0x0c, 0xc7,
+ 0xa9, 0x7e, 0xd0, 0xc3, 0x97, 0xf6, 0x37, 0x5b, 0x41, 0x5e, 0x67, 0x94,
+ 0x8e, 0xab, 0x69, 0x02, 0x17, 0x18, 0xf5, 0x4d, 0x38, 0xc2, 0x49, 0x28,
+ 0x09, 0x6e, 0x5a, 0x9b, 0xa6, 0x27, 0xdb, 0xc0, 0x5f, 0x8f, 0x44, 0x9c,
+ 0x90, 0x65, 0x99, 0xd8, 0xb3, 0x2e, 0xc1, 0x92, 0xee, 0x1a, 0x9d, 0x0f,
+ 0x72, 0x45, 0x20, 0xfa, 0x2c, 0x0c, 0x9c, 0x5d, 0xcd, 0x5b, 0x54, 0x41,
+ 0x54, 0x4f, 0xd3, 0xe2, 0xc7, 0x59, 0x84, 0x3f, 0x17, 0x7b, 0x7d, 0x0e,
+ 0xc2, 0xef, 0x62, 0xc7, 0xba, 0xb1, 0x26, 0x6c, 0x83, 0x4e, 0xd3, 0x19,
+ 0xc5, 0xff, 0x56, 0xa7, 0xb4, 0x45, 0x3f, 0x7a, 0x9e, 0xfa, 0xd0, 0x39,
+ 0x3e, 0x80, 0x46, 0x75, 0x5d, 0x5a, 0x79, 0x7a, 0x33, 0xc5, 0x01, 0xbc,
+ 0x02, 0x44, 0xce, 0x1b, 0xc0, 0x31, 0x4e, 0x47, 0x96, 0x15, 0x6e, 0xe7,
+ 0xe4, 0x76, 0xf0, 0xc2, 0x90, 0x0d, 0xa1, 0x78, 0xf4, 0x38, 0x00, 0x91,
+ 0x2b, 0x65, 0x7c, 0x79, 0x13, 0xa8, 0x3e, 0x91, 0x14, 0xdc, 0x88, 0x05,
+ 0x08, 0xd7, 0x6f, 0x53, 0xf6, 0x15, 0x43, 0xee, 0xc5, 0x53, 0x56, 0x1a,
+ 0x02, 0xb5, 0xa6, 0xa2, 0x46, 0x8d, 0x1e, 0x13, 0xe4, 0x67, 0xc2, 0x45,
+ 0x5f, 0x40, 0x5e, 0x10, 0x42, 0x58, 0xb5, 0xcd, 0x44, 0xa3, 0x94, 0x4c,
+ 0x1c, 0x54, 0x90, 0x4d, 0x91, 0x9a, 0x26, 0x8b, 0xad, 0xa2, 0x80, 0x50,
+ 0x8d, 0x14, 0x14,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1184831531 (0x469f182b)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Validity
+ Not Before: Nov 26 20:33:13 2009 GMT
+ Not After : Nov 1 04:00:00 2015 GMT
+ Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, CN=USERTrust Legacy Secure Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d9:4d:20:3a:e6:29:30:86:f2:e9:86:89:76:34:
+ 4e:68:1f:96:44:f7:d1:f9:d6:82:4e:a6:38:9e:ee:
+ cb:5b:e1:8e:2e:bd:f2:57:80:fd:c9:3f:fc:90:73:
+ 44:bc:8f:bb:57:5b:e5:2d:1f:14:30:75:36:f5:7f:
+ bc:cf:56:f4:7f:81:ff:ae:91:cd:d8:d2:6a:cb:97:
+ f9:f7:cd:90:6a:45:2d:c4:bb:a4:85:13:68:57:5f:
+ ef:29:ba:2a:ca:ea:f5:cc:a4:04:9b:63:cd:00:eb:
+ fd:ed:8d:dd:23:c6:7b:1e:57:1d:36:7f:1f:08:9a:
+ 0d:61:db:5a:6c:71:02:53:28:c2:fa:8d:fd:ab:bb:
+ b3:f1:8d:74:4b:df:bd:bd:cc:06:93:63:09:95:c2:
+ 10:7a:9d:25:90:32:9d:01:c2:39:53:b0:e0:15:6b:
+ c7:d7:74:e5:a4:22:9b:e4:94:ff:84:91:fb:2d:b3:
+ 19:43:2d:93:0f:9c:12:09:e4:67:b9:27:7a:32:ad:
+ 7a:2a:cc:41:58:c0:6e:59:5f:ee:38:2b:17:22:9c:
+ 89:fa:6e:e7:e5:57:35:f4:5a:ed:92:95:93:2d:f9:
+ cc:24:3f:a5:1c:3d:27:bd:22:03:73:cc:f5:ca:f3:
+ a9:f4:dc:fe:cf:e9:d0:5c:d0:0f:ab:87:fc:83:fd:
+ c8:a9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.1.3.4
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/server1.crl
+
+ X509v3 Subject Key Identifier:
+ AF:A4:40:AF:9F:16:FE:AB:31:FD:FB:D5:97:8B:F5:91:A3:24:86:16
+ X509v3 Authority Key Identifier:
+ keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 33:46:31:c3:2a:b7:b7:41:0e:aa:8e:93:14:2f:78:c3:4a:8e:
+ 16:5a:dc:72:32:94:96:57:9a:ac:bc:55:a8:57:cf:7c:e0:79:
+ 62:ff:31:ee:d5:9c:54:d0:c0:fd:87:e2:15:06:9e:be:a2:4a:
+ d0:82:eb:6e:4a:58:6a:d9:1f:11:c0:c8:e3:9e:e3:d6:c5:4f:
+ f7:ff:c3:ef:36:8a:68:aa:b2:50:92:ab:59:9d:ea:5b:27:1f:
+ 16:a9:3c:45:5f:eb:a5:2a:5d:56:29:8d:3a:14:0d:12:74:71:
+ be:d6:ab:97:de:92:87:61:21:88:7b:41:46:3d:fc:3d:4f:d0:
+ 54:5b
+-----BEGIN CERTIFICATE-----
+MIIELTCCA5agAwIBAgIERp8YKzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wOTEx
+MjYyMDMzMTNaFw0xNTExMDEwNDAwMDBaMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQI
+EwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VS
+VFJVU1QgTmV0d29yazEqMCgGA1UEAxMhVVNFUlRydXN0IExlZ2FjeSBTZWN1cmUg
+U2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2U0gOuYp
+MIby6YaJdjROaB+WRPfR+daCTqY4nu7LW+GOLr3yV4D9yT/8kHNEvI+7V1vlLR8U
+MHU29X+8z1b0f4H/rpHN2NJqy5f5982QakUtxLukhRNoV1/vKboqyur1zKQEm2PN
+AOv97Y3dI8Z7HlcdNn8fCJoNYdtabHECUyjC+o39q7uz8Y10S9+9vcwGk2MJlcIQ
+ep0lkDKdAcI5U7DgFWvH13TlpCKb5JT/hJH7LbMZQy2TD5wSCeRnuSd6Mq16KsxB
+WMBuWV/uOCsXIpyJ+m7n5Vc19FrtkpWTLfnMJD+lHD0nvSIDc8z1yvOp9Nz+z+nQ
+XNAPq4f8g/3IqQIDAQABo4HsMIHpMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8E
+CDAGAQH/AgEAMBkGA1UdIAQSMBAwDgYMKwYBBAGyMQECAQMEMDMGCCsGAQUFBwEB
+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZW50cnVzdC5uZXQwMwYDVR0f
+BCwwKjAooCagJIYiaHR0cDovL2NybC5lbnRydXN0Lm5ldC9zZXJ2ZXIxLmNybDAd
+BgNVHQ4EFgQUr6RAr58W/qsx/fvVl4v1kaMkhhYwHwYDVR0jBBgwFoAU8BdiE1U9
+s/8KAGv7UISX8+1i0BowDQYJKoZIhvcNAQEFBQADgYEAM0Yxwyq3t0EOqo6TFC94
+w0qOFlrccjKUllearLxVqFfPfOB5Yv8x7tWcVNDA/YfiFQaevqJK0ILrbkpYatkf
+EcDI457j1sVP9//D7zaKaKqyUJKrWZ3qWycfFqk8RV/rpSpdVimNOhQNEnRxvtar
+l96Sh2EhiHtBRj38PU/QVFs=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert15[] = {
+ 0x30, 0x82, 0x04, 0x2d, 0x30, 0x82, 0x03, 0x96, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x46, 0x9f, 0x18, 0x2b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+ 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+ 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+ 0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x31,
+ 0x32, 0x36, 0x32, 0x30, 0x33, 0x33, 0x31, 0x33, 0x5a, 0x17, 0x0d, 0x31,
+ 0x35, 0x31, 0x31, 0x30, 0x31, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x5a,
+ 0x30, 0x7f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+ 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+ 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21,
+ 0x55, 0x53, 0x45, 0x52, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4c, 0x65,
+ 0x67, 0x61, 0x63, 0x79, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd9, 0x4d, 0x20, 0x3a, 0xe6, 0x29,
+ 0x30, 0x86, 0xf2, 0xe9, 0x86, 0x89, 0x76, 0x34, 0x4e, 0x68, 0x1f, 0x96,
+ 0x44, 0xf7, 0xd1, 0xf9, 0xd6, 0x82, 0x4e, 0xa6, 0x38, 0x9e, 0xee, 0xcb,
+ 0x5b, 0xe1, 0x8e, 0x2e, 0xbd, 0xf2, 0x57, 0x80, 0xfd, 0xc9, 0x3f, 0xfc,
+ 0x90, 0x73, 0x44, 0xbc, 0x8f, 0xbb, 0x57, 0x5b, 0xe5, 0x2d, 0x1f, 0x14,
+ 0x30, 0x75, 0x36, 0xf5, 0x7f, 0xbc, 0xcf, 0x56, 0xf4, 0x7f, 0x81, 0xff,
+ 0xae, 0x91, 0xcd, 0xd8, 0xd2, 0x6a, 0xcb, 0x97, 0xf9, 0xf7, 0xcd, 0x90,
+ 0x6a, 0x45, 0x2d, 0xc4, 0xbb, 0xa4, 0x85, 0x13, 0x68, 0x57, 0x5f, 0xef,
+ 0x29, 0xba, 0x2a, 0xca, 0xea, 0xf5, 0xcc, 0xa4, 0x04, 0x9b, 0x63, 0xcd,
+ 0x00, 0xeb, 0xfd, 0xed, 0x8d, 0xdd, 0x23, 0xc6, 0x7b, 0x1e, 0x57, 0x1d,
+ 0x36, 0x7f, 0x1f, 0x08, 0x9a, 0x0d, 0x61, 0xdb, 0x5a, 0x6c, 0x71, 0x02,
+ 0x53, 0x28, 0xc2, 0xfa, 0x8d, 0xfd, 0xab, 0xbb, 0xb3, 0xf1, 0x8d, 0x74,
+ 0x4b, 0xdf, 0xbd, 0xbd, 0xcc, 0x06, 0x93, 0x63, 0x09, 0x95, 0xc2, 0x10,
+ 0x7a, 0x9d, 0x25, 0x90, 0x32, 0x9d, 0x01, 0xc2, 0x39, 0x53, 0xb0, 0xe0,
+ 0x15, 0x6b, 0xc7, 0xd7, 0x74, 0xe5, 0xa4, 0x22, 0x9b, 0xe4, 0x94, 0xff,
+ 0x84, 0x91, 0xfb, 0x2d, 0xb3, 0x19, 0x43, 0x2d, 0x93, 0x0f, 0x9c, 0x12,
+ 0x09, 0xe4, 0x67, 0xb9, 0x27, 0x7a, 0x32, 0xad, 0x7a, 0x2a, 0xcc, 0x41,
+ 0x58, 0xc0, 0x6e, 0x59, 0x5f, 0xee, 0x38, 0x2b, 0x17, 0x22, 0x9c, 0x89,
+ 0xfa, 0x6e, 0xe7, 0xe5, 0x57, 0x35, 0xf4, 0x5a, 0xed, 0x92, 0x95, 0x93,
+ 0x2d, 0xf9, 0xcc, 0x24, 0x3f, 0xa5, 0x1c, 0x3d, 0x27, 0xbd, 0x22, 0x03,
+ 0x73, 0xcc, 0xf5, 0xca, 0xf3, 0xa9, 0xf4, 0xdc, 0xfe, 0xcf, 0xe9, 0xd0,
+ 0x5c, 0xd0, 0x0f, 0xab, 0x87, 0xfc, 0x83, 0xfd, 0xc8, 0xa9, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x81, 0xec, 0x30, 0x81, 0xe9, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x19, 0x06,
+ 0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30, 0x10, 0x30, 0x0e, 0x06, 0x0c,
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x01, 0x03, 0x04,
+ 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65,
+ 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x73,
+ 0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xaf, 0xa4, 0x40,
+ 0xaf, 0x9f, 0x16, 0xfe, 0xab, 0x31, 0xfd, 0xfb, 0xd5, 0x97, 0x8b, 0xf5,
+ 0x91, 0xa3, 0x24, 0x86, 0x16, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d,
+ 0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97, 0xf3, 0xed, 0x62,
+ 0xd0, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x33, 0x46, 0x31,
+ 0xc3, 0x2a, 0xb7, 0xb7, 0x41, 0x0e, 0xaa, 0x8e, 0x93, 0x14, 0x2f, 0x78,
+ 0xc3, 0x4a, 0x8e, 0x16, 0x5a, 0xdc, 0x72, 0x32, 0x94, 0x96, 0x57, 0x9a,
+ 0xac, 0xbc, 0x55, 0xa8, 0x57, 0xcf, 0x7c, 0xe0, 0x79, 0x62, 0xff, 0x31,
+ 0xee, 0xd5, 0x9c, 0x54, 0xd0, 0xc0, 0xfd, 0x87, 0xe2, 0x15, 0x06, 0x9e,
+ 0xbe, 0xa2, 0x4a, 0xd0, 0x82, 0xeb, 0x6e, 0x4a, 0x58, 0x6a, 0xd9, 0x1f,
+ 0x11, 0xc0, 0xc8, 0xe3, 0x9e, 0xe3, 0xd6, 0xc5, 0x4f, 0xf7, 0xff, 0xc3,
+ 0xef, 0x36, 0x8a, 0x68, 0xaa, 0xb2, 0x50, 0x92, 0xab, 0x59, 0x9d, 0xea,
+ 0x5b, 0x27, 0x1f, 0x16, 0xa9, 0x3c, 0x45, 0x5f, 0xeb, 0xa5, 0x2a, 0x5d,
+ 0x56, 0x29, 0x8d, 0x3a, 0x14, 0x0d, 0x12, 0x74, 0x71, 0xbe, 0xd6, 0xab,
+ 0x97, 0xde, 0x92, 0x87, 0x61, 0x21, 0x88, 0x7b, 0x41, 0x46, 0x3d, 0xfc,
+ 0x3d, 0x4f, 0xd0, 0x54, 0x5b,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:37:02
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: O=AlphaSSL, CN=AlphaSSL CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c3:f0:65:88:df:1b:dd:c6:82:87:2f:c9:0b:ba:
+ 54:c6:63:3f:46:75:ac:4b:14:1f:98:72:8b:1c:10:
+ ff:09:a9:52:6e:2f:65:df:65:84:3f:5f:81:b2:d8:
+ f1:4f:d7:f0:5a:bb:c9:af:d0:31:dd:26:46:2a:99:
+ 9e:d8:a9:a3:b6:b8:07:c4:c9:71:f7:95:84:ef:d2:
+ ea:1f:54:a0:e5:be:e4:41:21:56:31:10:64:7d:1e:
+ 63:8e:9c:71:5c:3c:a0:2e:de:67:dc:c8:9a:20:f0:
+ 75:c8:b0:b6:27:81:eb:97:0d:ee:22:45:a5:c2:2f:
+ 34:27:ec:e0:59:12:51:b3:1e:05:e5:38:20:d2:69:
+ 59:7a:59:17:be:1a:4b:39:08:12:79:33:9b:64:68:
+ fe:58:81:dd:88:0c:6a:ba:59:b4:af:24:4f:61:e0:
+ ca:fc:17:5a:d2:3c:72:ab:a7:4c:b7:b9:ea:2d:e3:
+ f4:3f:99:a2:4d:c8:1d:58:f8:7f:53:35:8e:d7:22:
+ 88:b7:61:76:08:13:13:69:66:b0:57:59:13:31:0a:
+ 70:82:2b:93:d7:f6:e2:40:15:d0:1d:01:72:c7:13:
+ 58:6a:5a:ec:19:89:16:3c:e0:c8:8d:86:2a:fa:37:
+ f0:35:32:dd:ec:e5:fe:80:8e:f7:05:67:b4:8b:42:
+ 75:35
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 14:EA:19:55:F0:0E:0D:32:C6:1F:74:33:B7:8E:66:1A:4C:12:31:1E
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.alphassl.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 06:30:42:9b:cf:49:02:7e:89:e9:f5:83:5a:3d:02:f3:bc:b2:
+ 46:de:4a:50:ee:b9:9a:90:73:da:a0:5c:26:ca:82:ac:0e:ad:
+ b3:94:fa:28:2e:b2:e6:49:3f:50:77:0e:95:2f:68:f3:65:3c:
+ 9f:14:f2:68:60:92:b6:fc:04:0d:f6:a4:18:a1:69:60:0d:e3:
+ 9d:68:5b:bc:9e:0b:38:59:8d:21:da:23:fa:99:8a:09:b9:1f:
+ a7:2e:b5:55:6c:47:e7:41:ec:e6:e2:7f:af:55:44:39:e0:ac:
+ 74:ee:65:d3:fa:ab:51:48:30:f1:3e:77:6d:ed:e4:0f:40:98:
+ ee:47:7f:8d:b6:58:27:cd:92:6f:60:23:cc:02:9b:59:28:78:
+ a2:51:9d:d0:4a:9c:e5:93:5e:98:8f:cb:ef:3f:ca:fe:e0:af:
+ a4:c9:5b:6e:40:58:a5:92:2d:bd:5d:65:55:c5:bf:7c:04:41:
+ d9:a4:b5:80:e9:94:60:02:10:38:6a:08:08:d7:53:1c:2d:93:
+ af:c9:13:7b:d4:6c:c4:3a:c4:fb:80:ac:bb:3a:4e:54:7a:cd:
+ 4e:b3:3e:ed:f1:fc:11:4e:9f:f5:f3:14:bc:b9:b1:31:ce:f6:
+ aa:2f:a5:f8:c3:e9:66:a9:b2:20:9d:c4:f8:b8:03:62:a7:85:
+ d1:18:63:5b
+-----BEGIN CERTIFICATE-----
+MIIELzCCAxegAwIBAgILBAAAAAABL07hNwIwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMC4xETAPBgNVBAoTCEFscGhhU1NMMRkwFwYDVQQD
+ExBBbHBoYVNTTCBDQSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAw/BliN8b3caChy/JC7pUxmM/RnWsSxQfmHKLHBD/CalSbi9l32WEP1+Bstjx
+T9fwWrvJr9Ax3SZGKpme2KmjtrgHxMlx95WE79LqH1Sg5b7kQSFWMRBkfR5jjpxx
+XDygLt5n3MiaIPB1yLC2J4Hrlw3uIkWlwi80J+zgWRJRsx4F5Tgg0mlZelkXvhpL
+OQgSeTObZGj+WIHdiAxqulm0ryRPYeDK/Bda0jxyq6dMt7nqLeP0P5miTcgdWPh/
+UzWO1yKIt2F2CBMTaWawV1kTMQpwgiuT1/biQBXQHQFyxxNYalrsGYkWPODIjYYq
++jfwNTLd7OX+gI73BWe0i0J1NQIDAQABo4IBIzCCAR8wDgYDVR0PAQH/BAQDAgEG
+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFBTqGVXwDg0yxh90M7eOZhpM
+EjEeMEUGA1UdIAQ+MDwwOgYEVR0gADAyMDAGCCsGAQUFBwIBFiRodHRwczovL3d3
+dy5hbHBoYXNzbC5jb20vcmVwb3NpdG9yeS8wMwYDVR0fBCwwKjAooCagJIYiaHR0
+cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNybDA9BggrBgEFBQcBAQQxMC8w
+LQYIKwYBBQUHMAGGIWh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29tL3Jvb3RyMTAf
+BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUFAAOC
+AQEABjBCm89JAn6J6fWDWj0C87yyRt5KUO65mpBz2qBcJsqCrA6ts5T6KC6y5kk/
+UHcOlS9o82U8nxTyaGCStvwEDfakGKFpYA3jnWhbvJ4LOFmNIdoj+pmKCbkfpy61
+VWxH50Hs5uJ/r1VEOeCsdO5l0/qrUUgw8T53be3kD0CY7kd/jbZYJ82Sb2AjzAKb
+WSh4olGd0Eqc5ZNemI/L7z/K/uCvpMlbbkBYpZItvV1lVcW/fARB2aS1gOmUYAIQ
+OGoICNdTHC2Tr8kTe9RsxDrE+4CsuzpOVHrNTrM+7fH8EU6f9fMUvLmxMc72qi+l
++MPpZqmyIJ3E+LgDYqeF0RhjWw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert16[] = {
+ 0x30, 0x82, 0x04, 0x2f, 0x30, 0x82, 0x03, 0x17, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x37, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x2e, 0x31, 0x11, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61,
+ 0x53, 0x53, 0x4c, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x10, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x53, 0x53, 0x4c, 0x20, 0x43,
+ 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0xc3, 0xf0, 0x65, 0x88, 0xdf, 0x1b, 0xdd, 0xc6, 0x82,
+ 0x87, 0x2f, 0xc9, 0x0b, 0xba, 0x54, 0xc6, 0x63, 0x3f, 0x46, 0x75, 0xac,
+ 0x4b, 0x14, 0x1f, 0x98, 0x72, 0x8b, 0x1c, 0x10, 0xff, 0x09, 0xa9, 0x52,
+ 0x6e, 0x2f, 0x65, 0xdf, 0x65, 0x84, 0x3f, 0x5f, 0x81, 0xb2, 0xd8, 0xf1,
+ 0x4f, 0xd7, 0xf0, 0x5a, 0xbb, 0xc9, 0xaf, 0xd0, 0x31, 0xdd, 0x26, 0x46,
+ 0x2a, 0x99, 0x9e, 0xd8, 0xa9, 0xa3, 0xb6, 0xb8, 0x07, 0xc4, 0xc9, 0x71,
+ 0xf7, 0x95, 0x84, 0xef, 0xd2, 0xea, 0x1f, 0x54, 0xa0, 0xe5, 0xbe, 0xe4,
+ 0x41, 0x21, 0x56, 0x31, 0x10, 0x64, 0x7d, 0x1e, 0x63, 0x8e, 0x9c, 0x71,
+ 0x5c, 0x3c, 0xa0, 0x2e, 0xde, 0x67, 0xdc, 0xc8, 0x9a, 0x20, 0xf0, 0x75,
+ 0xc8, 0xb0, 0xb6, 0x27, 0x81, 0xeb, 0x97, 0x0d, 0xee, 0x22, 0x45, 0xa5,
+ 0xc2, 0x2f, 0x34, 0x27, 0xec, 0xe0, 0x59, 0x12, 0x51, 0xb3, 0x1e, 0x05,
+ 0xe5, 0x38, 0x20, 0xd2, 0x69, 0x59, 0x7a, 0x59, 0x17, 0xbe, 0x1a, 0x4b,
+ 0x39, 0x08, 0x12, 0x79, 0x33, 0x9b, 0x64, 0x68, 0xfe, 0x58, 0x81, 0xdd,
+ 0x88, 0x0c, 0x6a, 0xba, 0x59, 0xb4, 0xaf, 0x24, 0x4f, 0x61, 0xe0, 0xca,
+ 0xfc, 0x17, 0x5a, 0xd2, 0x3c, 0x72, 0xab, 0xa7, 0x4c, 0xb7, 0xb9, 0xea,
+ 0x2d, 0xe3, 0xf4, 0x3f, 0x99, 0xa2, 0x4d, 0xc8, 0x1d, 0x58, 0xf8, 0x7f,
+ 0x53, 0x35, 0x8e, 0xd7, 0x22, 0x88, 0xb7, 0x61, 0x76, 0x08, 0x13, 0x13,
+ 0x69, 0x66, 0xb0, 0x57, 0x59, 0x13, 0x31, 0x0a, 0x70, 0x82, 0x2b, 0x93,
+ 0xd7, 0xf6, 0xe2, 0x40, 0x15, 0xd0, 0x1d, 0x01, 0x72, 0xc7, 0x13, 0x58,
+ 0x6a, 0x5a, 0xec, 0x19, 0x89, 0x16, 0x3c, 0xe0, 0xc8, 0x8d, 0x86, 0x2a,
+ 0xfa, 0x37, 0xf0, 0x35, 0x32, 0xdd, 0xec, 0xe5, 0xfe, 0x80, 0x8e, 0xf7,
+ 0x05, 0x67, 0xb4, 0x8b, 0x42, 0x75, 0x35, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x01, 0x23, 0x30, 0x82, 0x01, 0x1f, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x14, 0xea, 0x19, 0x55, 0xf0,
+ 0x0e, 0x0d, 0x32, 0xc6, 0x1f, 0x74, 0x33, 0xb7, 0x8e, 0x66, 0x1a, 0x4c,
+ 0x12, 0x31, 0x1e, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3e,
+ 0x30, 0x3c, 0x30, 0x3a, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x32,
+ 0x30, 0x30, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+ 0x16, 0x24, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+ 0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30,
+ 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30,
+ 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+ 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+ 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60,
+ 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04,
+ 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x06, 0x30, 0x42, 0x9b, 0xcf, 0x49, 0x02, 0x7e, 0x89,
+ 0xe9, 0xf5, 0x83, 0x5a, 0x3d, 0x02, 0xf3, 0xbc, 0xb2, 0x46, 0xde, 0x4a,
+ 0x50, 0xee, 0xb9, 0x9a, 0x90, 0x73, 0xda, 0xa0, 0x5c, 0x26, 0xca, 0x82,
+ 0xac, 0x0e, 0xad, 0xb3, 0x94, 0xfa, 0x28, 0x2e, 0xb2, 0xe6, 0x49, 0x3f,
+ 0x50, 0x77, 0x0e, 0x95, 0x2f, 0x68, 0xf3, 0x65, 0x3c, 0x9f, 0x14, 0xf2,
+ 0x68, 0x60, 0x92, 0xb6, 0xfc, 0x04, 0x0d, 0xf6, 0xa4, 0x18, 0xa1, 0x69,
+ 0x60, 0x0d, 0xe3, 0x9d, 0x68, 0x5b, 0xbc, 0x9e, 0x0b, 0x38, 0x59, 0x8d,
+ 0x21, 0xda, 0x23, 0xfa, 0x99, 0x8a, 0x09, 0xb9, 0x1f, 0xa7, 0x2e, 0xb5,
+ 0x55, 0x6c, 0x47, 0xe7, 0x41, 0xec, 0xe6, 0xe2, 0x7f, 0xaf, 0x55, 0x44,
+ 0x39, 0xe0, 0xac, 0x74, 0xee, 0x65, 0xd3, 0xfa, 0xab, 0x51, 0x48, 0x30,
+ 0xf1, 0x3e, 0x77, 0x6d, 0xed, 0xe4, 0x0f, 0x40, 0x98, 0xee, 0x47, 0x7f,
+ 0x8d, 0xb6, 0x58, 0x27, 0xcd, 0x92, 0x6f, 0x60, 0x23, 0xcc, 0x02, 0x9b,
+ 0x59, 0x28, 0x78, 0xa2, 0x51, 0x9d, 0xd0, 0x4a, 0x9c, 0xe5, 0x93, 0x5e,
+ 0x98, 0x8f, 0xcb, 0xef, 0x3f, 0xca, 0xfe, 0xe0, 0xaf, 0xa4, 0xc9, 0x5b,
+ 0x6e, 0x40, 0x58, 0xa5, 0x92, 0x2d, 0xbd, 0x5d, 0x65, 0x55, 0xc5, 0xbf,
+ 0x7c, 0x04, 0x41, 0xd9, 0xa4, 0xb5, 0x80, 0xe9, 0x94, 0x60, 0x02, 0x10,
+ 0x38, 0x6a, 0x08, 0x08, 0xd7, 0x53, 0x1c, 0x2d, 0x93, 0xaf, 0xc9, 0x13,
+ 0x7b, 0xd4, 0x6c, 0xc4, 0x3a, 0xc4, 0xfb, 0x80, 0xac, 0xbb, 0x3a, 0x4e,
+ 0x54, 0x7a, 0xcd, 0x4e, 0xb3, 0x3e, 0xed, 0xf1, 0xfc, 0x11, 0x4e, 0x9f,
+ 0xf5, 0xf3, 0x14, 0xbc, 0xb9, 0xb1, 0x31, 0xce, 0xf6, 0xaa, 0x2f, 0xa5,
+ 0xf8, 0xc3, 0xe9, 0x66, 0xa9, 0xb2, 0x20, 0x9d, 0xc4, 0xf8, 0xb8, 0x03,
+ 0x62, 0xa7, 0x85, 0xd1, 0x18, 0x63, 0x5b,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:1e:44:a5:f1:71
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 11 12:00:00 2007 GMT
+ Not After : Apr 11 12:00:00 2017 GMT
+ Subject: OU=Alpha CA, O=Alpha, CN=Alpha CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bb:32:2e:2b:13:fd:f4:24:8a:5b:fa:6b:cd:ab:
+ 9c:b2:b6:0b:89:6a:e6:1f:41:ce:8a:24:42:ff:5c:
+ af:5f:90:3d:f5:90:b9:73:c7:70:b4:c2:ca:38:d6:
+ ad:06:15:1a:80:38:1d:79:2b:a5:43:20:e7:b9:fa:
+ 8d:06:22:57:0d:64:b5:d1:4d:ed:24:38:49:b4:6a:
+ 07:d4:33:db:3b:76:38:3f:af:77:69:ef:7a:13:33:
+ 29:7b:40:84:90:35:78:5a:8f:23:29:57:6f:b0:57:
+ ab:37:99:94:28:cf:d3:c7:57:a5:96:b1:8a:81:2e:
+ 73:80:bd:68:ec:1b:11:17:8a:1e:d8:94:77:4b:76:
+ 91:ea:b4:cc:16:33:03:62:b8:06:1a:65:69:be:ac:
+ d6:97:1b:a7:b1:27:a1:c0:25:52:2f:49:bc:da:04:
+ 06:ba:b8:b5:a6:a8:e1:cb:25:87:b6:28:d4:89:6b:
+ 34:01:77:1a:b6:ec:de:59:dc:99:bb:5d:dc:8f:84:
+ c2:b9:62:03:13:63:02:09:9e:e1:09:c8:be:f1:18:
+ 79:71:6d:c9:d0:b5:42:97:ca:f8:34:4d:92:87:c0:
+ 39:fa:5c:21:3d:94:52:04:5a:83:a9:d4:ab:83:05:
+ 28:d8:17:23:24:83:64:9b:21:2f:f8:3b:2b:78:64:
+ 87:93
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 0A:29:FA:AD:AF:4D:FD:FD:5D:7D:76:26:87:AB:AA:5A:AA:74:22:15
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.4146.1.10.10
+ CPS: http://www.alphassl.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Netscape Cert Type:
+ SSL CA
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 71:13:44:fe:0b:ce:87:60:b5:26:7d:a2:b7:75:63:6a:72:43:
+ 70:40:29:ad:0a:49:c6:88:f7:36:cc:d7:85:93:c2:0a:9a:ff:
+ d7:c9:2e:2d:3b:fa:96:39:2f:7b:f3:1e:6e:82:11:6f:d1:20:
+ c9:f8:5d:54:2f:16:28:42:3a:cc:2c:9f:48:87:a3:ba:76:a4:
+ 11:f9:8f:b3:a6:f3:86:3d:62:74:40:e4:2d:94:cd:65:ab:1b:
+ 41:09:8b:f6:db:fd:d3:02:3f:7c:c5:a1:25:a8:d2:95:50:df:
+ 21:2a:71:3b:b1:67:b7:b2:ec:1f:0e:c1:fe:3f:32:3d:3d:87:
+ 68:3a:25:f6:21:9c:5f:5c:9a:1c:10:cc:48:8d:83:76:78:39:
+ 57:67:ea:64:7e:71:1d:8e:40:e6:a5:ab:64:32:f7:83:c7:7b:
+ bd:a4:de:dc:83:13:a4:a2:8c:f3:2a:76:e9:1a:70:4a:51:17:
+ b7:6c:26:df:ee:05:c7:4e:5b:da:36:54:a1:49:79:f6:4a:06:
+ 0a:e3:01:ea:fe:48:73:0b:3d:9c:b8:28:81:f0:b4:a5:c8:62:
+ 9a:11:28:cd:18:d1:07:23:d2:ba:ee:14:db:87:64:ed:2b:aa:
+ 7f:1a:bd:0a:77:14:d5:d5:cc:31:12:a2:ef:06:a3:17:c1:e0:
+ 18:ab:c7:53
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgILBAAAAAABHkSl8XEwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNzA0MTExMjAw
+MDBaFw0xNzA0MTExMjAwMDBaMDYxETAPBgNVBAsTCEFscGhhIENBMQ4wDAYDVQQK
+EwVBbHBoYTERMA8GA1UEAxMIQWxwaGEgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQC7Mi4rE/30JIpb+mvNq5yytguJauYfQc6KJEL/XK9fkD31kLlz
+x3C0wso41q0GFRqAOB15K6VDIOe5+o0GIlcNZLXRTe0kOEm0agfUM9s7djg/r3dp
+73oTMyl7QISQNXhajyMpV2+wV6s3mZQoz9PHV6WWsYqBLnOAvWjsGxEXih7YlHdL
+dpHqtMwWMwNiuAYaZWm+rNaXG6exJ6HAJVIvSbzaBAa6uLWmqOHLJYe2KNSJazQB
+dxq27N5Z3Jm7XdyPhMK5YgMTYwIJnuEJyL7xGHlxbcnQtUKXyvg0TZKHwDn6XCE9
+lFIEWoOp1KuDBSjYFyMkg2SbIS/4Oyt4ZIeTAgMBAAGjggEeMIIBGjAOBgNVHQ8B
+Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUCin6ra9N/f1d
+fXYmh6uqWqp0IhUwSgYDVR0gBEMwQTA/BgorBgEEAaAyAQoKMDEwLwYIKwYBBQUH
+AgEWI2h0dHA6Ly93d3cuYWxwaGFzc2wuY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQs
+MCowKKAmoCSGImh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwEQYJ
+YIZIAYb4QgEBBAQDAgIEMCAGA1UdJQQZMBcGCisGAQQBgjcKAwMGCWCGSAGG+EIE
+ATAfBgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEAcRNE/gvOh2C1Jn2it3VjanJDcEAprQpJxoj3NszXhZPCCpr/18kuLTv6
+ljkve/MeboIRb9EgyfhdVC8WKEI6zCyfSIejunakEfmPs6bzhj1idEDkLZTNZasb
+QQmL9tv90wI/fMWhJajSlVDfISpxO7Fnt7LsHw7B/j8yPT2HaDol9iGcX1yaHBDM
+SI2Ddng5V2fqZH5xHY5A5qWrZDL3g8d7vaTe3IMTpKKM8yp26RpwSlEXt2wm3+4F
+x05b2jZUoUl59koGCuMB6v5Icws9nLgogfC0pchimhEozRjRByPSuu4U24dk7Suq
+fxq9CncU1dXMMRKi7wajF8HgGKvHUw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert17[] = {
+ 0x30, 0x82, 0x04, 0x32, 0x30, 0x82, 0x03, 0x1a, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1e, 0x44, 0xa5,
+ 0xf1, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31, 0x32, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31,
+ 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x36, 0x31, 0x11, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61,
+ 0x20, 0x43, 0x41, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x05, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x11, 0x30, 0x0f, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x08, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbb,
+ 0x32, 0x2e, 0x2b, 0x13, 0xfd, 0xf4, 0x24, 0x8a, 0x5b, 0xfa, 0x6b, 0xcd,
+ 0xab, 0x9c, 0xb2, 0xb6, 0x0b, 0x89, 0x6a, 0xe6, 0x1f, 0x41, 0xce, 0x8a,
+ 0x24, 0x42, 0xff, 0x5c, 0xaf, 0x5f, 0x90, 0x3d, 0xf5, 0x90, 0xb9, 0x73,
+ 0xc7, 0x70, 0xb4, 0xc2, 0xca, 0x38, 0xd6, 0xad, 0x06, 0x15, 0x1a, 0x80,
+ 0x38, 0x1d, 0x79, 0x2b, 0xa5, 0x43, 0x20, 0xe7, 0xb9, 0xfa, 0x8d, 0x06,
+ 0x22, 0x57, 0x0d, 0x64, 0xb5, 0xd1, 0x4d, 0xed, 0x24, 0x38, 0x49, 0xb4,
+ 0x6a, 0x07, 0xd4, 0x33, 0xdb, 0x3b, 0x76, 0x38, 0x3f, 0xaf, 0x77, 0x69,
+ 0xef, 0x7a, 0x13, 0x33, 0x29, 0x7b, 0x40, 0x84, 0x90, 0x35, 0x78, 0x5a,
+ 0x8f, 0x23, 0x29, 0x57, 0x6f, 0xb0, 0x57, 0xab, 0x37, 0x99, 0x94, 0x28,
+ 0xcf, 0xd3, 0xc7, 0x57, 0xa5, 0x96, 0xb1, 0x8a, 0x81, 0x2e, 0x73, 0x80,
+ 0xbd, 0x68, 0xec, 0x1b, 0x11, 0x17, 0x8a, 0x1e, 0xd8, 0x94, 0x77, 0x4b,
+ 0x76, 0x91, 0xea, 0xb4, 0xcc, 0x16, 0x33, 0x03, 0x62, 0xb8, 0x06, 0x1a,
+ 0x65, 0x69, 0xbe, 0xac, 0xd6, 0x97, 0x1b, 0xa7, 0xb1, 0x27, 0xa1, 0xc0,
+ 0x25, 0x52, 0x2f, 0x49, 0xbc, 0xda, 0x04, 0x06, 0xba, 0xb8, 0xb5, 0xa6,
+ 0xa8, 0xe1, 0xcb, 0x25, 0x87, 0xb6, 0x28, 0xd4, 0x89, 0x6b, 0x34, 0x01,
+ 0x77, 0x1a, 0xb6, 0xec, 0xde, 0x59, 0xdc, 0x99, 0xbb, 0x5d, 0xdc, 0x8f,
+ 0x84, 0xc2, 0xb9, 0x62, 0x03, 0x13, 0x63, 0x02, 0x09, 0x9e, 0xe1, 0x09,
+ 0xc8, 0xbe, 0xf1, 0x18, 0x79, 0x71, 0x6d, 0xc9, 0xd0, 0xb5, 0x42, 0x97,
+ 0xca, 0xf8, 0x34, 0x4d, 0x92, 0x87, 0xc0, 0x39, 0xfa, 0x5c, 0x21, 0x3d,
+ 0x94, 0x52, 0x04, 0x5a, 0x83, 0xa9, 0xd4, 0xab, 0x83, 0x05, 0x28, 0xd8,
+ 0x17, 0x23, 0x24, 0x83, 0x64, 0x9b, 0x21, 0x2f, 0xf8, 0x3b, 0x2b, 0x78,
+ 0x64, 0x87, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x1e,
+ 0x30, 0x82, 0x01, 0x1a, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x0a, 0x29, 0xfa, 0xad, 0xaf, 0x4d, 0xfd, 0xfd, 0x5d,
+ 0x7d, 0x76, 0x26, 0x87, 0xab, 0xaa, 0x5a, 0xaa, 0x74, 0x22, 0x15, 0x30,
+ 0x4a, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f,
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xa0, 0x32, 0x01, 0x0a, 0x0a,
+ 0x30, 0x31, 0x30, 0x2f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x73, 0x73, 0x6c, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+ 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x11, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03,
+ 0x02, 0x02, 0x04, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x19,
+ 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a,
+ 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+ 0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50,
+ 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x71, 0x13, 0x44, 0xfe, 0x0b, 0xce,
+ 0x87, 0x60, 0xb5, 0x26, 0x7d, 0xa2, 0xb7, 0x75, 0x63, 0x6a, 0x72, 0x43,
+ 0x70, 0x40, 0x29, 0xad, 0x0a, 0x49, 0xc6, 0x88, 0xf7, 0x36, 0xcc, 0xd7,
+ 0x85, 0x93, 0xc2, 0x0a, 0x9a, 0xff, 0xd7, 0xc9, 0x2e, 0x2d, 0x3b, 0xfa,
+ 0x96, 0x39, 0x2f, 0x7b, 0xf3, 0x1e, 0x6e, 0x82, 0x11, 0x6f, 0xd1, 0x20,
+ 0xc9, 0xf8, 0x5d, 0x54, 0x2f, 0x16, 0x28, 0x42, 0x3a, 0xcc, 0x2c, 0x9f,
+ 0x48, 0x87, 0xa3, 0xba, 0x76, 0xa4, 0x11, 0xf9, 0x8f, 0xb3, 0xa6, 0xf3,
+ 0x86, 0x3d, 0x62, 0x74, 0x40, 0xe4, 0x2d, 0x94, 0xcd, 0x65, 0xab, 0x1b,
+ 0x41, 0x09, 0x8b, 0xf6, 0xdb, 0xfd, 0xd3, 0x02, 0x3f, 0x7c, 0xc5, 0xa1,
+ 0x25, 0xa8, 0xd2, 0x95, 0x50, 0xdf, 0x21, 0x2a, 0x71, 0x3b, 0xb1, 0x67,
+ 0xb7, 0xb2, 0xec, 0x1f, 0x0e, 0xc1, 0xfe, 0x3f, 0x32, 0x3d, 0x3d, 0x87,
+ 0x68, 0x3a, 0x25, 0xf6, 0x21, 0x9c, 0x5f, 0x5c, 0x9a, 0x1c, 0x10, 0xcc,
+ 0x48, 0x8d, 0x83, 0x76, 0x78, 0x39, 0x57, 0x67, 0xea, 0x64, 0x7e, 0x71,
+ 0x1d, 0x8e, 0x40, 0xe6, 0xa5, 0xab, 0x64, 0x32, 0xf7, 0x83, 0xc7, 0x7b,
+ 0xbd, 0xa4, 0xde, 0xdc, 0x83, 0x13, 0xa4, 0xa2, 0x8c, 0xf3, 0x2a, 0x76,
+ 0xe9, 0x1a, 0x70, 0x4a, 0x51, 0x17, 0xb7, 0x6c, 0x26, 0xdf, 0xee, 0x05,
+ 0xc7, 0x4e, 0x5b, 0xda, 0x36, 0x54, 0xa1, 0x49, 0x79, 0xf6, 0x4a, 0x06,
+ 0x0a, 0xe3, 0x01, 0xea, 0xfe, 0x48, 0x73, 0x0b, 0x3d, 0x9c, 0xb8, 0x28,
+ 0x81, 0xf0, 0xb4, 0xa5, 0xc8, 0x62, 0x9a, 0x11, 0x28, 0xcd, 0x18, 0xd1,
+ 0x07, 0x23, 0xd2, 0xba, 0xee, 0x14, 0xdb, 0x87, 0x64, 0xed, 0x2b, 0xaa,
+ 0x7f, 0x1a, 0xbd, 0x0a, 0x77, 0x14, 0xd5, 0xd5, 0xcc, 0x31, 0x12, 0xa2,
+ 0xef, 0x06, 0xa3, 0x17, 0xc1, 0xe0, 0x18, 0xab, 0xc7, 0x53,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120020006 (0x7275c26)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: Aug 18 18:36:33 2011 GMT
+ Not After : Aug 9 18:35:49 2018 GMT
+ Subject: C=JP, O=Cybertrust Japan Co., Ltd., CN=Cybertrust Japan Public CA G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:dc:76:fb:b9:44:fb:12:7c:54:b0:bb:41:75:
+ 74:f0:35:47:9e:27:ba:43:44:45:60:19:cd:44:0a:
+ b2:78:e5:fa:1e:24:3b:02:1f:68:77:60:f4:eb:22:
+ 05:0a:37:6e:db:f7:15:20:bb:3d:53:d4:d0:11:8e:
+ d4:eb:67:be:d8:dd:05:37:94:41:25:23:ef:9a:10:
+ a3:c3:f7:00:bd:ba:26:92:67:81:50:a9:4f:9e:91:
+ 3c:4e:20:ba:52:7f:ff:4c:4d:8e:aa:ad:4e:5f:bc:
+ 27:97:02:29:fd:95:83:49:bf:0a:ba:b1:35:18:35:
+ a9:73:88:62:51:ac:14:17:34:50:e7:9f:c6:f3:93:
+ 5d:b9:c5:00:4f:37:90:7a:81:0f:d4:f5:72:29:e8:
+ 8c:62:ac:71:3b:f3:2e:df:2a:5c:e9:be:e7:56:24:
+ 82:28:9a:bf:3d:fd:71:42:fd:c5:7b:6b:37:09:47:
+ 50:3e:72:91:aa:3b:c5:4f:9d:12:6e:3d:80:03:54:
+ 49:41:15:3c:f2:86:44:74:28:f5:b9:a3:1b:db:76:
+ c7:ea:03:89:a0:60:e9:51:f6:a1:64:3e:74:9b:db:
+ 2a:cb:18:3c:c1:e7:68:18:89:ec:ab:38:2e:50:b9:
+ 6f:6e:15:33:28:1d:2f:d5:80:17:86:22:f3:1d:0c:
+ e0:87
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+ X509v3 Subject Key Identifier:
+ 1B:E4:8D:EF:3A:71:6B:12:65:68:CF:B6:91:BC:39:43:01:8D:75:C9
+ Signature Algorithm: sha1WithRSAEncryption
+ 6d:2b:ed:e9:8f:b6:29:d6:b6:30:ba:e6:6d:b5:c3:53:d2:c9:
+ b8:0a:4e:48:9f:f1:3f:3a:2a:b4:fb:74:96:e6:bd:18:bc:a7:
+ 79:c0:78:c1:39:ba:20:e3:4e:80:d2:24:d5:a5:7b:7f:62:a3:
+ da:d4:0f:0d:a9:9d:aa:c5:e9:bc:08:c9:48:66:98:9e:c0:f9:
+ de:ab:6d:e5:b0:33:c2:70:d0:96:d7:1e:6f:98:04:15:63:48:
+ bb:9a:85:5d:1b:69:f3:b4:4f:f3:a6:70:16:0f:21:19:7f:ad:
+ b0:03:39:49:86:8c:a2:73:4e:93:dc:21:01:68:b0:ef:0d:70:
+ 1a:7a:a2:b2:cc:22:42:91:54:bd:ac:70:04:8d:4c:5c:fc:3a:
+ a2:92:f6:f9:2a:12:40:cd:76:b7:66:f4:d7:d2:ab:de:8e:2c:
+ d9:dd:09:35:37:27:56:0b:ae:b6:ae:9a:83:60:f0:5f:7f:f0:
+ ad:b3:1c:fa:b8:74:5f:c7:ec:c0:0e:ad:d8:c3:cf:9f:d2:30:
+ 0e:5c:51:1f:3e:19:e2:c1:d2:a9:90:0f:e6:57:b9:af:04:76:
+ a2:f2:08:04:25:0a:35:bb:73:04:31:d0:98:46:7f:36:c1:61:
+ 16:a3:8a:8c:99:de:10:05:f2:de:ed:a5:09:64:97:84:8f:42:
+ a8:7b:22:c4
+-----BEGIN CERTIFICATE-----
+MIIENDCCAxygAwIBAgIEBydcJjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTExMDgxODE4MzYzM1oX
+DTE4MDgwOTE4MzU0OVowWjELMAkGA1UEBhMCSlAxIzAhBgNVBAoMGkN5YmVydHJ1
+c3QgSmFwYW4gQ28uLCBMdGQuMSYwJAYDVQQDDB1DeWJlcnRydXN0IEphcGFuIFB1
+YmxpYyBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALbcdvu5
+RPsSfFSwu0F1dPA1R54nukNERWAZzUQKsnjl+h4kOwIfaHdg9OsiBQo3btv3FSC7
+PVPU0BGO1OtnvtjdBTeUQSUj75oQo8P3AL26JpJngVCpT56RPE4gulJ//0xNjqqt
+Tl+8J5cCKf2Vg0m/CrqxNRg1qXOIYlGsFBc0UOefxvOTXbnFAE83kHqBD9T1cino
+jGKscTvzLt8qXOm+51Ykgiiavz39cUL9xXtrNwlHUD5ykao7xU+dEm49gANUSUEV
+PPKGRHQo9bmjG9t2x+oDiaBg6VH2oWQ+dJvbKssYPMHnaBiJ7Ks4LlC5b24VMygd
+L9WAF4Yi8x0M4IcCAwEAAaOCAQAwgf0wEgYDVR0TAQH/BAgwBgEB/wIBADBTBgNV
+HSAETDBKMEgGCSsGAQQBsT4BADA7MDkGCCsGAQUFBwIBFi1odHRwOi8vY3liZXJ0
+cnVzdC5vbW5pcm9vdC5jb20vcmVwb3NpdG9yeS5jZm0wDgYDVR0PAQH/BAQDAgEG
+MB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1BE3wMEIGA1UdHwQ7MDkwN6A1
+oDOGMWh0dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5jb20vQ1JML09tbmlyb290MjAy
+NS5jcmwwHQYDVR0OBBYEFBvkje86cWsSZWjPtpG8OUMBjXXJMA0GCSqGSIb3DQEB
+BQUAA4IBAQBtK+3pj7Yp1rYwuuZttcNT0sm4Ck5In/E/Oiq0+3SW5r0YvKd5wHjB
+Obog406A0iTVpXt/YqPa1A8NqZ2qxem8CMlIZpiewPneq23lsDPCcNCW1x5vmAQV
+Y0i7moVdG2nztE/zpnAWDyEZf62wAzlJhoyic06T3CEBaLDvDXAaeqKyzCJCkVS9
+rHAEjUxc/Dqikvb5KhJAzXa3ZvTX0qvejizZ3Qk1NydWC662rpqDYPBff/Ctsxz6
+uHRfx+zADq3Yw8+f0jAOXFEfPhniwdKpkA/mV7mvBHai8ggEJQo1u3MEMdCYRn82
+wWEWo4qMmd4QBfLe7aUJZJeEj0KoeyLE
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert18[] = {
+ 0x30, 0x82, 0x04, 0x34, 0x30, 0x82, 0x03, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x5c, 0x26, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+ 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+ 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31,
+ 0x30, 0x38, 0x31, 0x38, 0x31, 0x38, 0x33, 0x36, 0x33, 0x33, 0x5a, 0x17,
+ 0x0d, 0x31, 0x38, 0x30, 0x38, 0x30, 0x39, 0x31, 0x38, 0x33, 0x35, 0x34,
+ 0x39, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x0c, 0x1a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x43, 0x6f, 0x2e,
+ 0x2c, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x0c, 0x1d, 0x43, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x4a, 0x61, 0x70, 0x61, 0x6e, 0x20, 0x50, 0x75,
+ 0x62, 0x6c, 0x69, 0x63, 0x20, 0x43, 0x41, 0x20, 0x47, 0x32, 0x30, 0x82,
+ 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0xdc, 0x76, 0xfb, 0xb9,
+ 0x44, 0xfb, 0x12, 0x7c, 0x54, 0xb0, 0xbb, 0x41, 0x75, 0x74, 0xf0, 0x35,
+ 0x47, 0x9e, 0x27, 0xba, 0x43, 0x44, 0x45, 0x60, 0x19, 0xcd, 0x44, 0x0a,
+ 0xb2, 0x78, 0xe5, 0xfa, 0x1e, 0x24, 0x3b, 0x02, 0x1f, 0x68, 0x77, 0x60,
+ 0xf4, 0xeb, 0x22, 0x05, 0x0a, 0x37, 0x6e, 0xdb, 0xf7, 0x15, 0x20, 0xbb,
+ 0x3d, 0x53, 0xd4, 0xd0, 0x11, 0x8e, 0xd4, 0xeb, 0x67, 0xbe, 0xd8, 0xdd,
+ 0x05, 0x37, 0x94, 0x41, 0x25, 0x23, 0xef, 0x9a, 0x10, 0xa3, 0xc3, 0xf7,
+ 0x00, 0xbd, 0xba, 0x26, 0x92, 0x67, 0x81, 0x50, 0xa9, 0x4f, 0x9e, 0x91,
+ 0x3c, 0x4e, 0x20, 0xba, 0x52, 0x7f, 0xff, 0x4c, 0x4d, 0x8e, 0xaa, 0xad,
+ 0x4e, 0x5f, 0xbc, 0x27, 0x97, 0x02, 0x29, 0xfd, 0x95, 0x83, 0x49, 0xbf,
+ 0x0a, 0xba, 0xb1, 0x35, 0x18, 0x35, 0xa9, 0x73, 0x88, 0x62, 0x51, 0xac,
+ 0x14, 0x17, 0x34, 0x50, 0xe7, 0x9f, 0xc6, 0xf3, 0x93, 0x5d, 0xb9, 0xc5,
+ 0x00, 0x4f, 0x37, 0x90, 0x7a, 0x81, 0x0f, 0xd4, 0xf5, 0x72, 0x29, 0xe8,
+ 0x8c, 0x62, 0xac, 0x71, 0x3b, 0xf3, 0x2e, 0xdf, 0x2a, 0x5c, 0xe9, 0xbe,
+ 0xe7, 0x56, 0x24, 0x82, 0x28, 0x9a, 0xbf, 0x3d, 0xfd, 0x71, 0x42, 0xfd,
+ 0xc5, 0x7b, 0x6b, 0x37, 0x09, 0x47, 0x50, 0x3e, 0x72, 0x91, 0xaa, 0x3b,
+ 0xc5, 0x4f, 0x9d, 0x12, 0x6e, 0x3d, 0x80, 0x03, 0x54, 0x49, 0x41, 0x15,
+ 0x3c, 0xf2, 0x86, 0x44, 0x74, 0x28, 0xf5, 0xb9, 0xa3, 0x1b, 0xdb, 0x76,
+ 0xc7, 0xea, 0x03, 0x89, 0xa0, 0x60, 0xe9, 0x51, 0xf6, 0xa1, 0x64, 0x3e,
+ 0x74, 0x9b, 0xdb, 0x2a, 0xcb, 0x18, 0x3c, 0xc1, 0xe7, 0x68, 0x18, 0x89,
+ 0xec, 0xab, 0x38, 0x2e, 0x50, 0xb9, 0x6f, 0x6e, 0x15, 0x33, 0x28, 0x1d,
+ 0x2f, 0xd5, 0x80, 0x17, 0x86, 0x22, 0xf3, 0x1d, 0x0c, 0xe0, 0x87, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x00, 0x30, 0x81, 0xfd, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x53, 0x06, 0x03, 0x55,
+ 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06,
+ 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,
+ 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08,
+ 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35,
+ 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c,
+ 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32,
+ 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0x1b, 0xe4, 0x8d, 0xef, 0x3a, 0x71, 0x6b, 0x12,
+ 0x65, 0x68, 0xcf, 0xb6, 0x91, 0xbc, 0x39, 0x43, 0x01, 0x8d, 0x75, 0xc9,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x6d, 0x2b, 0xed, 0xe9,
+ 0x8f, 0xb6, 0x29, 0xd6, 0xb6, 0x30, 0xba, 0xe6, 0x6d, 0xb5, 0xc3, 0x53,
+ 0xd2, 0xc9, 0xb8, 0x0a, 0x4e, 0x48, 0x9f, 0xf1, 0x3f, 0x3a, 0x2a, 0xb4,
+ 0xfb, 0x74, 0x96, 0xe6, 0xbd, 0x18, 0xbc, 0xa7, 0x79, 0xc0, 0x78, 0xc1,
+ 0x39, 0xba, 0x20, 0xe3, 0x4e, 0x80, 0xd2, 0x24, 0xd5, 0xa5, 0x7b, 0x7f,
+ 0x62, 0xa3, 0xda, 0xd4, 0x0f, 0x0d, 0xa9, 0x9d, 0xaa, 0xc5, 0xe9, 0xbc,
+ 0x08, 0xc9, 0x48, 0x66, 0x98, 0x9e, 0xc0, 0xf9, 0xde, 0xab, 0x6d, 0xe5,
+ 0xb0, 0x33, 0xc2, 0x70, 0xd0, 0x96, 0xd7, 0x1e, 0x6f, 0x98, 0x04, 0x15,
+ 0x63, 0x48, 0xbb, 0x9a, 0x85, 0x5d, 0x1b, 0x69, 0xf3, 0xb4, 0x4f, 0xf3,
+ 0xa6, 0x70, 0x16, 0x0f, 0x21, 0x19, 0x7f, 0xad, 0xb0, 0x03, 0x39, 0x49,
+ 0x86, 0x8c, 0xa2, 0x73, 0x4e, 0x93, 0xdc, 0x21, 0x01, 0x68, 0xb0, 0xef,
+ 0x0d, 0x70, 0x1a, 0x7a, 0xa2, 0xb2, 0xcc, 0x22, 0x42, 0x91, 0x54, 0xbd,
+ 0xac, 0x70, 0x04, 0x8d, 0x4c, 0x5c, 0xfc, 0x3a, 0xa2, 0x92, 0xf6, 0xf9,
+ 0x2a, 0x12, 0x40, 0xcd, 0x76, 0xb7, 0x66, 0xf4, 0xd7, 0xd2, 0xab, 0xde,
+ 0x8e, 0x2c, 0xd9, 0xdd, 0x09, 0x35, 0x37, 0x27, 0x56, 0x0b, 0xae, 0xb6,
+ 0xae, 0x9a, 0x83, 0x60, 0xf0, 0x5f, 0x7f, 0xf0, 0xad, 0xb3, 0x1c, 0xfa,
+ 0xb8, 0x74, 0x5f, 0xc7, 0xec, 0xc0, 0x0e, 0xad, 0xd8, 0xc3, 0xcf, 0x9f,
+ 0xd2, 0x30, 0x0e, 0x5c, 0x51, 0x1f, 0x3e, 0x19, 0xe2, 0xc1, 0xd2, 0xa9,
+ 0x90, 0x0f, 0xe6, 0x57, 0xb9, 0xaf, 0x04, 0x76, 0xa2, 0xf2, 0x08, 0x04,
+ 0x25, 0x0a, 0x35, 0xbb, 0x73, 0x04, 0x31, 0xd0, 0x98, 0x46, 0x7f, 0x36,
+ 0xc1, 0x61, 0x16, 0xa3, 0x8a, 0x8c, 0x99, 0xde, 0x10, 0x05, 0xf2, 0xde,
+ 0xed, 0xa5, 0x09, 0x64, 0x97, 0x84, 0x8f, 0x42, 0xa8, 0x7b, 0x22, 0xc4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 314159292 (0x12b9b0bc)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1
+ Validity
+ Not Before: Feb 28 08:51:17 2008 GMT
+ Not After : Feb 28 08:51:17 2018 GMT
+ Subject: C=JP, O=SECOM Trust Systems CO.,LTD., CN=SECOM Passport for Web SR 2.0 CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:81:dd:77:fa:50:b2:3f:38:54:f7:2b:6a:04:
+ 23:1d:db:84:08:6d:dc:51:41:bf:09:01:d7:84:af:
+ c8:7e:92:f9:72:47:35:6a:d5:85:eb:2d:5a:82:72:
+ 6f:6c:10:e6:37:9d:fc:d7:0a:5f:99:07:24:3d:50:
+ bb:36:f7:d1:50:2d:38:5d:bf:6b:56:2e:e6:79:b4:
+ 59:3e:a0:09:7e:d0:8e:a8:0c:ed:0a:c3:5b:f8:79:
+ b5:c1:23:5a:b3:b5:f3:f2:f6:e4:e4:2a:a9:ff:86:
+ 53:3e:57:cc:9d:00:36:57:f1:c2:af:34:2b:52:68:
+ 57:4d:a1:57:cb:28:c6:21:ba:58:4a:99:a7:65:e5:
+ 85:24:3f:23:42:e7:cb:96:a5:03:97:3f:ab:ea:40:
+ 57:6e:1f:a7:4e:24:25:4e:ee:92:8b:f1:a5:e4:8f:
+ dc:96:56:54:01:17:8c:42:07:df:de:d3:c2:c2:7e:
+ 97:5d:9d:25:f3:e5:04:ee:6e:3b:4f:23:25:ce:4c:
+ 75:e0:e4:e9:19:ab:55:2e:ad:8d:cb:b9:8b:c2:25:
+ 91:4e:09:78:af:35:3c:5f:b1:a7:40:9d:e1:12:0b:
+ 83:e0:7a:c8:f4:af:81:a6:3c:43:b0:e6:59:9b:cc:
+ 66:27:75:49:6e:39:ef:8b:22:ed:9b:99:a4:94:94:
+ 8e:09
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 30:9A:00:57:99:44:63:6B:C9:B2:F2:3D:8D:83:6B:3B:D7:9D:EF:64
+ X509v3 Authority Key Identifier:
+ keyid:A0:73:49:99:68:DC:85:5B:65:E3:9B:28:2F:57:9F:BD:33:BC:07:48
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://repository.secomtrust.net/SC-Root1/SCRoot1CRL.crl
+
+ X509v3 Certificate Policies:
+ Policy: 1.2.392.200091.100.901.1
+ CPS: https://repository.secomtrust.net/SC-Root1/
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 4d:b3:4b:19:c4:de:f8:9e:7b:54:3a:69:77:b8:31:ed:50:dc:
+ f4:d2:ad:2f:08:5b:af:77:c1:23:be:14:38:39:fd:2d:4e:d1:
+ d2:29:b6:9d:ad:8e:79:76:41:1a:d5:4c:dd:b6:d1:54:68:bb:
+ 8c:2d:b1:d9:18:77:03:bb:46:91:1a:28:ec:e1:b8:00:2d:8c:
+ b5:0c:a2:24:df:75:ba:58:fc:dd:8a:c1:b4:53:12:29:43:77:
+ ed:4c:73:8d:b1:49:1f:14:56:be:11:8d:6d:d6:55:35:55:25:
+ 48:8a:84:e8:3e:74:38:e2:7d:ba:37:64:69:14:a7:69:d9:c6:
+ 40:3e:74:b4:23:bb:c2:71:9f:2b:3d:6a:88:81:11:ce:2e:bc:
+ 12:9f:c8:18:e7:f9:63:0d:25:87:c3:85:05:ba:9c:9d:b7:a7:
+ 69:41:ee:c8:67:82:d3:2f:03:e6:43:c3:53:8a:60:58:d6:2a:
+ 1a:c0:80:9a:97:89:54:40:e6:25:c5:1e:8e:af:7f:a1:10:bf:
+ 37:13:05:1d:8a:d0:42:18:f8:bb:f9:64:a8:05:56:06:fa:27:
+ 71:0f:5c:79:90:ff:5a:43:a2:a7:b7:6c:68:64:8a:94:25:ee:
+ be:7f:7b:27:0c:92:4b:99:c5:33:3d:93:e0:62:71:29:81:cb:
+ 26:7a:a7:c8
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIEErmwvDANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK
+UDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBD
+b21tdW5pY2F0aW9uIFJvb3RDQTEwHhcNMDgwMjI4MDg1MTE3WhcNMTgwMjI4MDg1
+MTE3WjBfMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVt
+cyBDTy4sTFRELjEpMCcGA1UEAxMgU0VDT00gUGFzc3BvcnQgZm9yIFdlYiBTUiAy
+LjAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtgd13+lCyPzhU
+9ytqBCMd24QIbdxRQb8JAdeEr8h+kvlyRzVq1YXrLVqCcm9sEOY3nfzXCl+ZByQ9
+ULs299FQLThdv2tWLuZ5tFk+oAl+0I6oDO0Kw1v4ebXBI1qztfPy9uTkKqn/hlM+
+V8ydADZX8cKvNCtSaFdNoVfLKMYhulhKmadl5YUkPyNC58uWpQOXP6vqQFduH6dO
+JCVO7pKL8aXkj9yWVlQBF4xCB9/e08LCfpddnSXz5QTubjtPIyXOTHXg5OkZq1Uu
+rY3LuYvCJZFOCXivNTxfsadAneESC4Pgesj0r4GmPEOw5lmbzGYndUluOe+LIu2b
+maSUlI4JAgMBAAGjggEHMIIBAzAdBgNVHQ4EFgQUMJoAV5lEY2vJsvI9jYNrO9ed
+72QwHwYDVR0jBBgwFoAUoHNJmWjchVtl45soL1efvTO8B0gwEgYDVR0TAQH/BAgw
+BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDov
+L3JlcG9zaXRvcnkuc2Vjb210cnVzdC5uZXQvU0MtUm9vdDEvU0NSb290MUNSTC5j
+cmwwUgYDVR0gBEswSTBHBgoqgwiMmxtkhwUBMDkwNwYIKwYBBQUHAgEWK2h0dHBz
+Oi8vcmVwb3NpdG9yeS5zZWNvbXRydXN0Lm5ldC9TQy1Sb290MS8wDQYJKoZIhvcN
+AQEFBQADggEBAE2zSxnE3viee1Q6aXe4Me1Q3PTSrS8IW693wSO+FDg5/S1O0dIp
+tp2tjnl2QRrVTN220VRou4wtsdkYdwO7RpEaKOzhuAAtjLUMoiTfdbpY/N2KwbRT
+EilDd+1Mc42xSR8UVr4RjW3WVTVVJUiKhOg+dDjifbo3ZGkUp2nZxkA+dLQju8Jx
+nys9aoiBEc4uvBKfyBjn+WMNJYfDhQW6nJ23p2lB7shngtMvA+ZDw1OKYFjWKhrA
+gJqXiVRA5iXFHo6vf6EQvzcTBR2K0EIY+Lv5ZKgFVgb6J3EPXHmQ/1pDoqe3bGhk
+ipQl7r5/eycMkkuZxTM9k+BicSmByyZ6p8g=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert19[] = {
+ 0x30, 0x82, 0x04, 0x36, 0x30, 0x82, 0x03, 0x1e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x12, 0xb9, 0xb0, 0xbc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x50,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a,
+ 0x50, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x20, 0x43,
+ 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x31, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x38, 0x30, 0x32, 0x32, 0x38, 0x30, 0x38, 0x35, 0x31, 0x31, 0x37,
+ 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x32, 0x32, 0x38, 0x30, 0x38, 0x35,
+ 0x31, 0x31, 0x37, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+ 0x73, 0x20, 0x43, 0x4f, 0x2e, 0x2c, 0x4c, 0x54, 0x44, 0x2e, 0x31, 0x29,
+ 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x45, 0x43,
+ 0x4f, 0x4d, 0x20, 0x50, 0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x20,
+ 0x66, 0x6f, 0x72, 0x20, 0x57, 0x65, 0x62, 0x20, 0x53, 0x52, 0x20, 0x32,
+ 0x2e, 0x30, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xad, 0x81, 0xdd, 0x77, 0xfa, 0x50, 0xb2, 0x3f, 0x38, 0x54,
+ 0xf7, 0x2b, 0x6a, 0x04, 0x23, 0x1d, 0xdb, 0x84, 0x08, 0x6d, 0xdc, 0x51,
+ 0x41, 0xbf, 0x09, 0x01, 0xd7, 0x84, 0xaf, 0xc8, 0x7e, 0x92, 0xf9, 0x72,
+ 0x47, 0x35, 0x6a, 0xd5, 0x85, 0xeb, 0x2d, 0x5a, 0x82, 0x72, 0x6f, 0x6c,
+ 0x10, 0xe6, 0x37, 0x9d, 0xfc, 0xd7, 0x0a, 0x5f, 0x99, 0x07, 0x24, 0x3d,
+ 0x50, 0xbb, 0x36, 0xf7, 0xd1, 0x50, 0x2d, 0x38, 0x5d, 0xbf, 0x6b, 0x56,
+ 0x2e, 0xe6, 0x79, 0xb4, 0x59, 0x3e, 0xa0, 0x09, 0x7e, 0xd0, 0x8e, 0xa8,
+ 0x0c, 0xed, 0x0a, 0xc3, 0x5b, 0xf8, 0x79, 0xb5, 0xc1, 0x23, 0x5a, 0xb3,
+ 0xb5, 0xf3, 0xf2, 0xf6, 0xe4, 0xe4, 0x2a, 0xa9, 0xff, 0x86, 0x53, 0x3e,
+ 0x57, 0xcc, 0x9d, 0x00, 0x36, 0x57, 0xf1, 0xc2, 0xaf, 0x34, 0x2b, 0x52,
+ 0x68, 0x57, 0x4d, 0xa1, 0x57, 0xcb, 0x28, 0xc6, 0x21, 0xba, 0x58, 0x4a,
+ 0x99, 0xa7, 0x65, 0xe5, 0x85, 0x24, 0x3f, 0x23, 0x42, 0xe7, 0xcb, 0x96,
+ 0xa5, 0x03, 0x97, 0x3f, 0xab, 0xea, 0x40, 0x57, 0x6e, 0x1f, 0xa7, 0x4e,
+ 0x24, 0x25, 0x4e, 0xee, 0x92, 0x8b, 0xf1, 0xa5, 0xe4, 0x8f, 0xdc, 0x96,
+ 0x56, 0x54, 0x01, 0x17, 0x8c, 0x42, 0x07, 0xdf, 0xde, 0xd3, 0xc2, 0xc2,
+ 0x7e, 0x97, 0x5d, 0x9d, 0x25, 0xf3, 0xe5, 0x04, 0xee, 0x6e, 0x3b, 0x4f,
+ 0x23, 0x25, 0xce, 0x4c, 0x75, 0xe0, 0xe4, 0xe9, 0x19, 0xab, 0x55, 0x2e,
+ 0xad, 0x8d, 0xcb, 0xb9, 0x8b, 0xc2, 0x25, 0x91, 0x4e, 0x09, 0x78, 0xaf,
+ 0x35, 0x3c, 0x5f, 0xb1, 0xa7, 0x40, 0x9d, 0xe1, 0x12, 0x0b, 0x83, 0xe0,
+ 0x7a, 0xc8, 0xf4, 0xaf, 0x81, 0xa6, 0x3c, 0x43, 0xb0, 0xe6, 0x59, 0x9b,
+ 0xcc, 0x66, 0x27, 0x75, 0x49, 0x6e, 0x39, 0xef, 0x8b, 0x22, 0xed, 0x9b,
+ 0x99, 0xa4, 0x94, 0x94, 0x8e, 0x09, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x07, 0x30, 0x82, 0x01, 0x03, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x30, 0x9a, 0x00, 0x57, 0x99, 0x44,
+ 0x63, 0x6b, 0xc9, 0xb2, 0xf2, 0x3d, 0x8d, 0x83, 0x6b, 0x3b, 0xd7, 0x9d,
+ 0xef, 0x64, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+ 0x16, 0x80, 0x14, 0xa0, 0x73, 0x49, 0x99, 0x68, 0xdc, 0x85, 0x5b, 0x65,
+ 0xe3, 0x9b, 0x28, 0x2f, 0x57, 0x9f, 0xbd, 0x33, 0xbc, 0x07, 0x48, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e,
+ 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
+ 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x2f,
+ 0x53, 0x43, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x43, 0x52, 0x4c, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, 0x30,
+ 0x49, 0x30, 0x47, 0x06, 0x0a, 0x2a, 0x83, 0x08, 0x8c, 0x9b, 0x1b, 0x64,
+ 0x87, 0x05, 0x01, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x2e, 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74,
+ 0x31, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4d, 0xb3,
+ 0x4b, 0x19, 0xc4, 0xde, 0xf8, 0x9e, 0x7b, 0x54, 0x3a, 0x69, 0x77, 0xb8,
+ 0x31, 0xed, 0x50, 0xdc, 0xf4, 0xd2, 0xad, 0x2f, 0x08, 0x5b, 0xaf, 0x77,
+ 0xc1, 0x23, 0xbe, 0x14, 0x38, 0x39, 0xfd, 0x2d, 0x4e, 0xd1, 0xd2, 0x29,
+ 0xb6, 0x9d, 0xad, 0x8e, 0x79, 0x76, 0x41, 0x1a, 0xd5, 0x4c, 0xdd, 0xb6,
+ 0xd1, 0x54, 0x68, 0xbb, 0x8c, 0x2d, 0xb1, 0xd9, 0x18, 0x77, 0x03, 0xbb,
+ 0x46, 0x91, 0x1a, 0x28, 0xec, 0xe1, 0xb8, 0x00, 0x2d, 0x8c, 0xb5, 0x0c,
+ 0xa2, 0x24, 0xdf, 0x75, 0xba, 0x58, 0xfc, 0xdd, 0x8a, 0xc1, 0xb4, 0x53,
+ 0x12, 0x29, 0x43, 0x77, 0xed, 0x4c, 0x73, 0x8d, 0xb1, 0x49, 0x1f, 0x14,
+ 0x56, 0xbe, 0x11, 0x8d, 0x6d, 0xd6, 0x55, 0x35, 0x55, 0x25, 0x48, 0x8a,
+ 0x84, 0xe8, 0x3e, 0x74, 0x38, 0xe2, 0x7d, 0xba, 0x37, 0x64, 0x69, 0x14,
+ 0xa7, 0x69, 0xd9, 0xc6, 0x40, 0x3e, 0x74, 0xb4, 0x23, 0xbb, 0xc2, 0x71,
+ 0x9f, 0x2b, 0x3d, 0x6a, 0x88, 0x81, 0x11, 0xce, 0x2e, 0xbc, 0x12, 0x9f,
+ 0xc8, 0x18, 0xe7, 0xf9, 0x63, 0x0d, 0x25, 0x87, 0xc3, 0x85, 0x05, 0xba,
+ 0x9c, 0x9d, 0xb7, 0xa7, 0x69, 0x41, 0xee, 0xc8, 0x67, 0x82, 0xd3, 0x2f,
+ 0x03, 0xe6, 0x43, 0xc3, 0x53, 0x8a, 0x60, 0x58, 0xd6, 0x2a, 0x1a, 0xc0,
+ 0x80, 0x9a, 0x97, 0x89, 0x54, 0x40, 0xe6, 0x25, 0xc5, 0x1e, 0x8e, 0xaf,
+ 0x7f, 0xa1, 0x10, 0xbf, 0x37, 0x13, 0x05, 0x1d, 0x8a, 0xd0, 0x42, 0x18,
+ 0xf8, 0xbb, 0xf9, 0x64, 0xa8, 0x05, 0x56, 0x06, 0xfa, 0x27, 0x71, 0x0f,
+ 0x5c, 0x79, 0x90, 0xff, 0x5a, 0x43, 0xa2, 0xa7, 0xb7, 0x6c, 0x68, 0x64,
+ 0x8a, 0x94, 0x25, 0xee, 0xbe, 0x7f, 0x7b, 0x27, 0x0c, 0x92, 0x4b, 0x99,
+ 0xc5, 0x33, 0x3d, 0x93, 0xe0, 0x62, 0x71, 0x29, 0x81, 0xcb, 0x26, 0x7a,
+ 0xa7, 0xc8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 314159330 (0x12b9b0e2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1
+ Validity
+ Not Before: Feb 17 04:49:28 2012 GMT
+ Not After : Feb 17 04:49:28 2022 GMT
+ Subject: C=JP, O=SECOM Trust Systems CO.,LTD., CN=SECOM Passport for Web SR 2.0 CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:81:dd:77:fa:50:b2:3f:38:54:f7:2b:6a:04:
+ 23:1d:db:84:08:6d:dc:51:41:bf:09:01:d7:84:af:
+ c8:7e:92:f9:72:47:35:6a:d5:85:eb:2d:5a:82:72:
+ 6f:6c:10:e6:37:9d:fc:d7:0a:5f:99:07:24:3d:50:
+ bb:36:f7:d1:50:2d:38:5d:bf:6b:56:2e:e6:79:b4:
+ 59:3e:a0:09:7e:d0:8e:a8:0c:ed:0a:c3:5b:f8:79:
+ b5:c1:23:5a:b3:b5:f3:f2:f6:e4:e4:2a:a9:ff:86:
+ 53:3e:57:cc:9d:00:36:57:f1:c2:af:34:2b:52:68:
+ 57:4d:a1:57:cb:28:c6:21:ba:58:4a:99:a7:65:e5:
+ 85:24:3f:23:42:e7:cb:96:a5:03:97:3f:ab:ea:40:
+ 57:6e:1f:a7:4e:24:25:4e:ee:92:8b:f1:a5:e4:8f:
+ dc:96:56:54:01:17:8c:42:07:df:de:d3:c2:c2:7e:
+ 97:5d:9d:25:f3:e5:04:ee:6e:3b:4f:23:25:ce:4c:
+ 75:e0:e4:e9:19:ab:55:2e:ad:8d:cb:b9:8b:c2:25:
+ 91:4e:09:78:af:35:3c:5f:b1:a7:40:9d:e1:12:0b:
+ 83:e0:7a:c8:f4:af:81:a6:3c:43:b0:e6:59:9b:cc:
+ 66:27:75:49:6e:39:ef:8b:22:ed:9b:99:a4:94:94:
+ 8e:09
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 30:9A:00:57:99:44:63:6B:C9:B2:F2:3D:8D:83:6B:3B:D7:9D:EF:64
+ X509v3 Authority Key Identifier:
+ keyid:A0:73:49:99:68:DC:85:5B:65:E3:9B:28:2F:57:9F:BD:33:BC:07:48
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://repository.secomtrust.net/SC-Root1/SCRoot1CRL.crl
+
+ X509v3 Certificate Policies:
+ Policy: 1.2.392.200091.100.901.1
+ CPS: https://repository.secomtrust.net/SC-Root1/
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 32:ff:99:55:69:c1:3e:48:62:52:69:ac:09:10:5f:93:3f:69:
+ c4:53:47:34:44:a3:7d:9c:19:2f:94:9b:ba:68:14:e7:75:e4:
+ 9b:ad:f2:65:a7:68:13:f9:ca:1d:6b:8e:1c:94:62:10:70:4c:
+ 14:6a:c5:a9:64:47:c5:c4:de:b3:0d:04:1f:fd:a0:ae:3f:68:
+ 59:de:af:4a:b9:d2:cb:8f:09:ec:df:8c:b9:34:3a:0f:5e:e6:
+ bd:97:79:f3:e2:ef:19:6a:8a:97:19:7d:d1:f9:04:41:fc:93:
+ 7a:54:44:42:a7:bc:e7:79:a9:9e:68:d8:1c:5d:39:5d:97:bb:
+ bb:f1:14:b7:b5:0c:f3:bc:11:9e:dc:67:b1:df:d4:91:68:e5:
+ df:6d:ac:69:f3:b9:56:87:25:e4:cc:8d:86:d1:38:ce:93:ec:
+ 76:c1:c4:01:44:1e:13:16:90:73:5e:5d:80:4d:42:69:68:5b:
+ aa:4d:0f:f5:38:2e:0e:d6:28:9d:d4:b9:5a:d1:4d:08:39:b6:
+ f4:47:9c:82:48:e6:b4:71:8e:0d:05:cd:15:48:8b:e7:ae:12:
+ fd:24:e8:6d:7a:cf:bf:1e:4e:7e:6d:87:f3:af:56:8f:4e:12:
+ 42:4f:b4:7c:5b:fd:10:f8:78:e3:9b:52:3d:cd:a9:99:75:b0:
+ c3:c9:fd:24
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIEErmw4jANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK
+UDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBD
+b21tdW5pY2F0aW9uIFJvb3RDQTEwHhcNMTIwMjE3MDQ0OTI4WhcNMjIwMjE3MDQ0
+OTI4WjBfMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVt
+cyBDTy4sTFRELjEpMCcGA1UEAxMgU0VDT00gUGFzc3BvcnQgZm9yIFdlYiBTUiAy
+LjAgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtgd13+lCyPzhU
+9ytqBCMd24QIbdxRQb8JAdeEr8h+kvlyRzVq1YXrLVqCcm9sEOY3nfzXCl+ZByQ9
+ULs299FQLThdv2tWLuZ5tFk+oAl+0I6oDO0Kw1v4ebXBI1qztfPy9uTkKqn/hlM+
+V8ydADZX8cKvNCtSaFdNoVfLKMYhulhKmadl5YUkPyNC58uWpQOXP6vqQFduH6dO
+JCVO7pKL8aXkj9yWVlQBF4xCB9/e08LCfpddnSXz5QTubjtPIyXOTHXg5OkZq1Uu
+rY3LuYvCJZFOCXivNTxfsadAneESC4Pgesj0r4GmPEOw5lmbzGYndUluOe+LIu2b
+maSUlI4JAgMBAAGjggEHMIIBAzAdBgNVHQ4EFgQUMJoAV5lEY2vJsvI9jYNrO9ed
+72QwHwYDVR0jBBgwFoAUoHNJmWjchVtl45soL1efvTO8B0gwEgYDVR0TAQH/BAgw
+BgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDov
+L3JlcG9zaXRvcnkuc2Vjb210cnVzdC5uZXQvU0MtUm9vdDEvU0NSb290MUNSTC5j
+cmwwUgYDVR0gBEswSTBHBgoqgwiMmxtkhwUBMDkwNwYIKwYBBQUHAgEWK2h0dHBz
+Oi8vcmVwb3NpdG9yeS5zZWNvbXRydXN0Lm5ldC9TQy1Sb290MS8wDQYJKoZIhvcN
+AQEFBQADggEBADL/mVVpwT5IYlJprAkQX5M/acRTRzREo32cGS+Um7poFOd15Jut
+8mWnaBP5yh1rjhyUYhBwTBRqxalkR8XE3rMNBB/9oK4/aFner0q50suPCezfjLk0
+Og9e5r2XefPi7xlqipcZfdH5BEH8k3pUREKnvOd5qZ5o2BxdOV2Xu7vxFLe1DPO8
+EZ7cZ7Hf1JFo5d9trGnzuVaHJeTMjYbROM6T7HbBxAFEHhMWkHNeXYBNQmloW6pN
+D/U4Lg7WKJ3UuVrRTQg5tvRHnIJI5rRxjg0FzRVIi+euEv0k6G16z78eTn5th/Ov
+Vo9OEkJPtHxb/RD4eOObUj3NqZl1sMPJ/SQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert20[] = {
+ 0x30, 0x82, 0x04, 0x36, 0x30, 0x82, 0x03, 0x1e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x12, 0xb9, 0xb0, 0xe2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x50,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a,
+ 0x50, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x20, 0x43,
+ 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x31, 0x30, 0x1e, 0x17, 0x0d,
+ 0x31, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x34, 0x34, 0x39, 0x32, 0x38,
+ 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x34, 0x34,
+ 0x39, 0x32, 0x38, 0x5a, 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x4a, 0x50, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x45, 0x43, 0x4f, 0x4d, 0x20,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+ 0x73, 0x20, 0x43, 0x4f, 0x2e, 0x2c, 0x4c, 0x54, 0x44, 0x2e, 0x31, 0x29,
+ 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x45, 0x43,
+ 0x4f, 0x4d, 0x20, 0x50, 0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x20,
+ 0x66, 0x6f, 0x72, 0x20, 0x57, 0x65, 0x62, 0x20, 0x53, 0x52, 0x20, 0x32,
+ 0x2e, 0x30, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xad, 0x81, 0xdd, 0x77, 0xfa, 0x50, 0xb2, 0x3f, 0x38, 0x54,
+ 0xf7, 0x2b, 0x6a, 0x04, 0x23, 0x1d, 0xdb, 0x84, 0x08, 0x6d, 0xdc, 0x51,
+ 0x41, 0xbf, 0x09, 0x01, 0xd7, 0x84, 0xaf, 0xc8, 0x7e, 0x92, 0xf9, 0x72,
+ 0x47, 0x35, 0x6a, 0xd5, 0x85, 0xeb, 0x2d, 0x5a, 0x82, 0x72, 0x6f, 0x6c,
+ 0x10, 0xe6, 0x37, 0x9d, 0xfc, 0xd7, 0x0a, 0x5f, 0x99, 0x07, 0x24, 0x3d,
+ 0x50, 0xbb, 0x36, 0xf7, 0xd1, 0x50, 0x2d, 0x38, 0x5d, 0xbf, 0x6b, 0x56,
+ 0x2e, 0xe6, 0x79, 0xb4, 0x59, 0x3e, 0xa0, 0x09, 0x7e, 0xd0, 0x8e, 0xa8,
+ 0x0c, 0xed, 0x0a, 0xc3, 0x5b, 0xf8, 0x79, 0xb5, 0xc1, 0x23, 0x5a, 0xb3,
+ 0xb5, 0xf3, 0xf2, 0xf6, 0xe4, 0xe4, 0x2a, 0xa9, 0xff, 0x86, 0x53, 0x3e,
+ 0x57, 0xcc, 0x9d, 0x00, 0x36, 0x57, 0xf1, 0xc2, 0xaf, 0x34, 0x2b, 0x52,
+ 0x68, 0x57, 0x4d, 0xa1, 0x57, 0xcb, 0x28, 0xc6, 0x21, 0xba, 0x58, 0x4a,
+ 0x99, 0xa7, 0x65, 0xe5, 0x85, 0x24, 0x3f, 0x23, 0x42, 0xe7, 0xcb, 0x96,
+ 0xa5, 0x03, 0x97, 0x3f, 0xab, 0xea, 0x40, 0x57, 0x6e, 0x1f, 0xa7, 0x4e,
+ 0x24, 0x25, 0x4e, 0xee, 0x92, 0x8b, 0xf1, 0xa5, 0xe4, 0x8f, 0xdc, 0x96,
+ 0x56, 0x54, 0x01, 0x17, 0x8c, 0x42, 0x07, 0xdf, 0xde, 0xd3, 0xc2, 0xc2,
+ 0x7e, 0x97, 0x5d, 0x9d, 0x25, 0xf3, 0xe5, 0x04, 0xee, 0x6e, 0x3b, 0x4f,
+ 0x23, 0x25, 0xce, 0x4c, 0x75, 0xe0, 0xe4, 0xe9, 0x19, 0xab, 0x55, 0x2e,
+ 0xad, 0x8d, 0xcb, 0xb9, 0x8b, 0xc2, 0x25, 0x91, 0x4e, 0x09, 0x78, 0xaf,
+ 0x35, 0x3c, 0x5f, 0xb1, 0xa7, 0x40, 0x9d, 0xe1, 0x12, 0x0b, 0x83, 0xe0,
+ 0x7a, 0xc8, 0xf4, 0xaf, 0x81, 0xa6, 0x3c, 0x43, 0xb0, 0xe6, 0x59, 0x9b,
+ 0xcc, 0x66, 0x27, 0x75, 0x49, 0x6e, 0x39, 0xef, 0x8b, 0x22, 0xed, 0x9b,
+ 0x99, 0xa4, 0x94, 0x94, 0x8e, 0x09, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x07, 0x30, 0x82, 0x01, 0x03, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x30, 0x9a, 0x00, 0x57, 0x99, 0x44,
+ 0x63, 0x6b, 0xc9, 0xb2, 0xf2, 0x3d, 0x8d, 0x83, 0x6b, 0x3b, 0xd7, 0x9d,
+ 0xef, 0x64, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+ 0x16, 0x80, 0x14, 0xa0, 0x73, 0x49, 0x99, 0x68, 0xdc, 0x85, 0x5b, 0x65,
+ 0xe3, 0x9b, 0x28, 0x2f, 0x57, 0x9f, 0xbd, 0x33, 0xbc, 0x07, 0x48, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e,
+ 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e,
+ 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x2f,
+ 0x53, 0x43, 0x52, 0x6f, 0x6f, 0x74, 0x31, 0x43, 0x52, 0x4c, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, 0x30,
+ 0x49, 0x30, 0x47, 0x06, 0x0a, 0x2a, 0x83, 0x08, 0x8c, 0x9b, 0x1b, 0x64,
+ 0x87, 0x05, 0x01, 0x30, 0x39, 0x30, 0x37, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x2e, 0x73, 0x65, 0x63, 0x6f, 0x6d, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x53, 0x43, 0x2d, 0x52, 0x6f, 0x6f, 0x74,
+ 0x31, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x32, 0xff,
+ 0x99, 0x55, 0x69, 0xc1, 0x3e, 0x48, 0x62, 0x52, 0x69, 0xac, 0x09, 0x10,
+ 0x5f, 0x93, 0x3f, 0x69, 0xc4, 0x53, 0x47, 0x34, 0x44, 0xa3, 0x7d, 0x9c,
+ 0x19, 0x2f, 0x94, 0x9b, 0xba, 0x68, 0x14, 0xe7, 0x75, 0xe4, 0x9b, 0xad,
+ 0xf2, 0x65, 0xa7, 0x68, 0x13, 0xf9, 0xca, 0x1d, 0x6b, 0x8e, 0x1c, 0x94,
+ 0x62, 0x10, 0x70, 0x4c, 0x14, 0x6a, 0xc5, 0xa9, 0x64, 0x47, 0xc5, 0xc4,
+ 0xde, 0xb3, 0x0d, 0x04, 0x1f, 0xfd, 0xa0, 0xae, 0x3f, 0x68, 0x59, 0xde,
+ 0xaf, 0x4a, 0xb9, 0xd2, 0xcb, 0x8f, 0x09, 0xec, 0xdf, 0x8c, 0xb9, 0x34,
+ 0x3a, 0x0f, 0x5e, 0xe6, 0xbd, 0x97, 0x79, 0xf3, 0xe2, 0xef, 0x19, 0x6a,
+ 0x8a, 0x97, 0x19, 0x7d, 0xd1, 0xf9, 0x04, 0x41, 0xfc, 0x93, 0x7a, 0x54,
+ 0x44, 0x42, 0xa7, 0xbc, 0xe7, 0x79, 0xa9, 0x9e, 0x68, 0xd8, 0x1c, 0x5d,
+ 0x39, 0x5d, 0x97, 0xbb, 0xbb, 0xf1, 0x14, 0xb7, 0xb5, 0x0c, 0xf3, 0xbc,
+ 0x11, 0x9e, 0xdc, 0x67, 0xb1, 0xdf, 0xd4, 0x91, 0x68, 0xe5, 0xdf, 0x6d,
+ 0xac, 0x69, 0xf3, 0xb9, 0x56, 0x87, 0x25, 0xe4, 0xcc, 0x8d, 0x86, 0xd1,
+ 0x38, 0xce, 0x93, 0xec, 0x76, 0xc1, 0xc4, 0x01, 0x44, 0x1e, 0x13, 0x16,
+ 0x90, 0x73, 0x5e, 0x5d, 0x80, 0x4d, 0x42, 0x69, 0x68, 0x5b, 0xaa, 0x4d,
+ 0x0f, 0xf5, 0x38, 0x2e, 0x0e, 0xd6, 0x28, 0x9d, 0xd4, 0xb9, 0x5a, 0xd1,
+ 0x4d, 0x08, 0x39, 0xb6, 0xf4, 0x47, 0x9c, 0x82, 0x48, 0xe6, 0xb4, 0x71,
+ 0x8e, 0x0d, 0x05, 0xcd, 0x15, 0x48, 0x8b, 0xe7, 0xae, 0x12, 0xfd, 0x24,
+ 0xe8, 0x6d, 0x7a, 0xcf, 0xbf, 0x1e, 0x4e, 0x7e, 0x6d, 0x87, 0xf3, 0xaf,
+ 0x56, 0x8f, 0x4e, 0x12, 0x42, 0x4f, 0xb4, 0x7c, 0x5b, 0xfd, 0x10, 0xf8,
+ 0x78, 0xe3, 0x9b, 0x52, 0x3d, 0xcd, 0xa9, 0x99, 0x75, 0xb0, 0xc3, 0xc9,
+ 0xfd, 0x24,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120024505 (0x7276db9)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Nov 30 16:35:21 2010 GMT
+ Not After : Aug 10 15:34:26 2018 GMT
+ Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79:
+ d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a:
+ 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2:
+ 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01:
+ 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7:
+ 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6:
+ 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c:
+ a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70:
+ 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77:
+ d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae:
+ 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18:
+ 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85:
+ ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9:
+ 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5:
+ c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a:
+ ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0:
+ 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27:
+ 1a:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+ Signature Algorithm: sha1WithRSAEncryption
+ 16:b4:2c:c9:f1:5e:e1:a2:7b:9b:78:20:7a:4a:70:70:86:19:
+ 00:b7:05:2a:e8:c9:25:39:0f:c3:64:3c:75:09:d9:89:15:80:
+ 07:c2:8d:bc:29:a5:64:50:cf:71:75:47:23:bd:4d:d8:7f:77:
+ 9a:51:10:6e:4e:1f:20:3c:47:9c:43:74:7f:96:84:10:4c:13:
+ 43:be:f8:e0:72:2e:ff:bf:ae:3c:0a:03:60:82:4b:6f:f9:9a:
+ c5:1e:f6:af:90:3b:9f:61:3b:3e:de:9b:05:1a:c6:2c:3c:57:
+ 21:08:0f:54:fa:28:63:6c:e8:1b:9c:0f:cf:dd:30:44:13:b9:
+ 57:fe
+-----BEGIN CERTIFICATE-----
+MIIEODCCA6GgAwIBAgIEBydtuTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMTEzMDE2MzUyMVoXDTE4MDgxMDE1MzQyNlowWjELMAkG
+A1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVz
+dDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKrmD1X6CZymrV51Cni4eiVgLGw41uO
+KymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsBUnuId9Mcj8e6uYi1agnn
+c+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/CG9VwcPCP
+wBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPg
+kAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFc
+B5kpvcY67Oduhjprl3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaOCAWow
+ggFmMBIGA1UdEwEB/wQIMAYBAf8CAQMwTgYDVR0gBEcwRTBDBgRVHSAAMDswOQYI
+KwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9yZXBvc2l0
+b3J5LmNmbTAOBgNVHQ8BAf8EBAMCAQYwgYkGA1UdIwSBgTB/oXmkdzB1MQswCQYD
+VQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUg
+Q3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRy
+dXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vd3d3
+LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3JsMB0GA1Ud
+DgQWBBTlnVkwgkdYzKz6CFQ2hns6tQRN8DANBgkqhkiG9w0BAQUFAAOBgQAWtCzJ
+8V7honubeCB6SnBwhhkAtwUq6MklOQ/DZDx1CdmJFYAHwo28KaVkUM9xdUcjvU3Y
+f3eaURBuTh8gPEecQ3R/loQQTBNDvvjgci7/v648CgNggktv+ZrFHvavkDufYTs+
+3psFGsYsPFchCA9U+ihjbOgbnA/P3TBEE7lX/g==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert21[] = {
+ 0x30, 0x82, 0x04, 0x38, 0x30, 0x82, 0x03, 0xa1, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x6d, 0xb9, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x30, 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x33, 0x35, 0x32,
+ 0x31, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x38, 0x31, 0x30, 0x31, 0x35,
+ 0x33, 0x34, 0x32, 0x36, 0x5a, 0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69,
+ 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79,
+ 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f,
+ 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f,
+ 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x04,
+ 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a, 0xb5, 0x79,
+ 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3, 0x5b, 0x8e,
+ 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09, 0x05, 0x6d,
+ 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88, 0xda, 0x12,
+ 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52, 0x7b, 0x88,
+ 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a, 0x09, 0xe7,
+ 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d, 0x2d, 0xe5,
+ 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea, 0xf5, 0xab,
+ 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f, 0x0c, 0xd5,
+ 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70, 0xf0, 0x8f,
+ 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33, 0x7a, 0x77,
+ 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xd2, 0xc0,
+ 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc, 0xb4, 0xdd,
+ 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5, 0x63, 0xe0,
+ 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea, 0xeb, 0xd4,
+ 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69, 0xbc, 0xf9,
+ 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9, 0x90, 0x2c,
+ 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98, 0x21, 0x5c,
+ 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86, 0x3a, 0x6b,
+ 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78, 0x8d, 0x76,
+ 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90, 0xdc, 0x27,
+ 0x1a, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6a, 0x30,
+ 0x82, 0x01, 0x66, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x03, 0x30,
+ 0x4e, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x47, 0x30, 0x45, 0x30, 0x43,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f,
+ 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f,
+ 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30,
+ 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20,
+ 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53,
+ 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36,
+ 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69,
+ 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63,
+ 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58,
+ 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d,
+ 0xf0, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x16, 0xb4, 0x2c, 0xc9,
+ 0xf1, 0x5e, 0xe1, 0xa2, 0x7b, 0x9b, 0x78, 0x20, 0x7a, 0x4a, 0x70, 0x70,
+ 0x86, 0x19, 0x00, 0xb7, 0x05, 0x2a, 0xe8, 0xc9, 0x25, 0x39, 0x0f, 0xc3,
+ 0x64, 0x3c, 0x75, 0x09, 0xd9, 0x89, 0x15, 0x80, 0x07, 0xc2, 0x8d, 0xbc,
+ 0x29, 0xa5, 0x64, 0x50, 0xcf, 0x71, 0x75, 0x47, 0x23, 0xbd, 0x4d, 0xd8,
+ 0x7f, 0x77, 0x9a, 0x51, 0x10, 0x6e, 0x4e, 0x1f, 0x20, 0x3c, 0x47, 0x9c,
+ 0x43, 0x74, 0x7f, 0x96, 0x84, 0x10, 0x4c, 0x13, 0x43, 0xbe, 0xf8, 0xe0,
+ 0x72, 0x2e, 0xff, 0xbf, 0xae, 0x3c, 0x0a, 0x03, 0x60, 0x82, 0x4b, 0x6f,
+ 0xf9, 0x9a, 0xc5, 0x1e, 0xf6, 0xaf, 0x90, 0x3b, 0x9f, 0x61, 0x3b, 0x3e,
+ 0xde, 0x9b, 0x05, 0x1a, 0xc6, 0x2c, 0x3c, 0x57, 0x21, 0x08, 0x0f, 0x54,
+ 0xfa, 0x28, 0x63, 0x6c, 0xe8, 0x1b, 0x9c, 0x0f, 0xcf, 0xdd, 0x30, 0x44,
+ 0x13, 0xb9, 0x57, 0xfe,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 48:4b:ac:f1:aa:c7:d7:13:43:d1:a2:74:35:49:97:25
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Jun 7 08:09:10 2005 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b1:f7:c3:38:3f:b4:a8:7f:cf:39:82:51:67:d0:
+ 6d:9f:d2:ff:58:f3:e7:9f:2b:ec:0d:89:54:99:b9:
+ 38:99:16:f7:e0:21:79:48:c2:bb:61:74:12:96:1d:
+ 3c:6a:72:d5:3c:10:67:3a:39:ed:2b:13:cd:66:eb:
+ 95:09:33:a4:6c:97:b1:e8:c6:ec:c1:75:79:9c:46:
+ 5e:8d:ab:d0:6a:fd:b9:2a:55:17:10:54:b3:19:f0:
+ 9a:f6:f1:b1:5d:b6:a7:6d:fb:e0:71:17:6b:a2:88:
+ fb:00:df:fe:1a:31:77:0c:9a:01:7a:b1:32:e3:2b:
+ 01:07:38:6e:c3:a5:5e:23:bc:45:9b:7b:50:c1:c9:
+ 30:8f:db:e5:2b:7a:d3:5b:fb:33:40:1e:a0:d5:98:
+ 17:bc:8b:87:c3:89:d3:5d:a0:8e:b2:aa:aa:f6:8e:
+ 69:88:06:c5:fa:89:21:f3:08:9d:69:2e:09:33:9b:
+ 29:0d:46:0f:8c:cc:49:34:b0:69:51:bd:f9:06:cd:
+ 68:ad:66:4c:bc:3e:ac:61:bd:0a:88:0e:c8:df:3d:
+ ee:7c:04:4c:9d:0a:5e:6b:91:d6:ee:c7:ed:28:8d:
+ ab:4d:87:89:73:d0:6e:a4:d0:1e:16:8b:14:e1:76:
+ 44:03:7f:63:ac:e4:cd:49:9c:c5:92:f4:ab:32:a1:
+ 48:5b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3c:ec:7b:e0:ae:a3:0e:96:6d:30:d7:85:c6:d2:68:5b:45:5a:
+ 82:a6:34:0f:b0:c9:92:23:5e:11:6d:08:11:b2:74:09:23:3a:
+ 35:25:73:58:5e:ca:b9:7c:28:fa:47:ec:f9:a0:03:58:50:b6:
+ 53:ef:8c:db:39:e4:67:e9:d8:ca:28:46:d4:a7:e0:f5:38:75:
+ f8:e7:cb:5c:bf:1d:11:3c:6a:40:9b:2d:44:56:d3:f7:ff:05:
+ 28:32:0c:15:c8:64:45:93:e8:21:24:8f:2d:da:7a:84:7b:4f:
+ cf:cd:b2:25:7c:77:10:d3:94:d1:04:91:a8:25:1c:09:22:0f:
+ 7d:44:35:11:14:ef:af:00:fe:5e:ea:5f:8e:b0:d9:92:59:ba:
+ fc:13:96:a0:18:01:56:ce:da:f6:28:0b:b1:af:dd:5c:4f:5c:
+ b2:f3:8f:5a:71:cf:ed:18:ad:63:88:1d:8e:95:f7:ea:95:e7:
+ 1f:ad:90:b8:84:08:47:85:7f:22:2f:1a:1d:48:30:d6:4c:08:
+ d8:37:19:67:32:2b:eb:5c:d0:b2:fc:6e:57:9f:04:35:5e:90:
+ 00:7e:11:c7:de:13:2a:cd:a4:6d:45:26:c7:88:56:a0:f0:6a:
+ f7:d8:e7:fc:27:7e:67:08:d0:bd:fa:b6:c3:61:02:01:65:b9:
+ b8:2f:cf:5a
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIQSEus8arH1xND0aJ0NUmXJTANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0
+LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0
+qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt
+KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr
+ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX
+vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM
+vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN
+SZzFkvSrMqFIWwIDAQABo4GqMIGnMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D
+veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8v
+Y3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5jcmwwDQYJ
+KoZIhvcNAQEFBQADggEBADzse+Cuow6WbTDXhcbSaFtFWoKmNA+wyZIjXhFtCBGy
+dAkjOjUlc1heyrl8KPpH7PmgA1hQtlPvjNs55Gfp2MooRtSn4PU4dfjny1y/HRE8
+akCbLURW0/f/BSgyDBXIZEWT6CEkjy3aeoR7T8/NsiV8dxDTlNEEkaglHAkiD31E
+NREU768A/l7qX46w2ZJZuvwTlqAYAVbO2vYoC7Gv3VxPXLLzj1pxz+0YrWOIHY6V
+9+qV5x+tkLiECEeFfyIvGh1IMNZMCNg3GWcyK+tc0LL8blefBDVekAB+EcfeEyrN
+pG1FJseIVqDwavfY5/wnfmcI0L36tsNhAgFlubgvz1o=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert22[] = {
+ 0x30, 0x82, 0x04, 0x3c, 0x30, 0x82, 0x03, 0x24, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x48, 0x4b, 0xac, 0xf1, 0xaa, 0xc7, 0xd7, 0x13, 0x43,
+ 0xd1, 0xa2, 0x74, 0x35, 0x49, 0x97, 0x25, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+ 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+ 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+ 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55,
+ 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+ 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xf7, 0xc3, 0x38, 0x3f, 0xb4,
+ 0xa8, 0x7f, 0xcf, 0x39, 0x82, 0x51, 0x67, 0xd0, 0x6d, 0x9f, 0xd2, 0xff,
+ 0x58, 0xf3, 0xe7, 0x9f, 0x2b, 0xec, 0x0d, 0x89, 0x54, 0x99, 0xb9, 0x38,
+ 0x99, 0x16, 0xf7, 0xe0, 0x21, 0x79, 0x48, 0xc2, 0xbb, 0x61, 0x74, 0x12,
+ 0x96, 0x1d, 0x3c, 0x6a, 0x72, 0xd5, 0x3c, 0x10, 0x67, 0x3a, 0x39, 0xed,
+ 0x2b, 0x13, 0xcd, 0x66, 0xeb, 0x95, 0x09, 0x33, 0xa4, 0x6c, 0x97, 0xb1,
+ 0xe8, 0xc6, 0xec, 0xc1, 0x75, 0x79, 0x9c, 0x46, 0x5e, 0x8d, 0xab, 0xd0,
+ 0x6a, 0xfd, 0xb9, 0x2a, 0x55, 0x17, 0x10, 0x54, 0xb3, 0x19, 0xf0, 0x9a,
+ 0xf6, 0xf1, 0xb1, 0x5d, 0xb6, 0xa7, 0x6d, 0xfb, 0xe0, 0x71, 0x17, 0x6b,
+ 0xa2, 0x88, 0xfb, 0x00, 0xdf, 0xfe, 0x1a, 0x31, 0x77, 0x0c, 0x9a, 0x01,
+ 0x7a, 0xb1, 0x32, 0xe3, 0x2b, 0x01, 0x07, 0x38, 0x6e, 0xc3, 0xa5, 0x5e,
+ 0x23, 0xbc, 0x45, 0x9b, 0x7b, 0x50, 0xc1, 0xc9, 0x30, 0x8f, 0xdb, 0xe5,
+ 0x2b, 0x7a, 0xd3, 0x5b, 0xfb, 0x33, 0x40, 0x1e, 0xa0, 0xd5, 0x98, 0x17,
+ 0xbc, 0x8b, 0x87, 0xc3, 0x89, 0xd3, 0x5d, 0xa0, 0x8e, 0xb2, 0xaa, 0xaa,
+ 0xf6, 0x8e, 0x69, 0x88, 0x06, 0xc5, 0xfa, 0x89, 0x21, 0xf3, 0x08, 0x9d,
+ 0x69, 0x2e, 0x09, 0x33, 0x9b, 0x29, 0x0d, 0x46, 0x0f, 0x8c, 0xcc, 0x49,
+ 0x34, 0xb0, 0x69, 0x51, 0xbd, 0xf9, 0x06, 0xcd, 0x68, 0xad, 0x66, 0x4c,
+ 0xbc, 0x3e, 0xac, 0x61, 0xbd, 0x0a, 0x88, 0x0e, 0xc8, 0xdf, 0x3d, 0xee,
+ 0x7c, 0x04, 0x4c, 0x9d, 0x0a, 0x5e, 0x6b, 0x91, 0xd6, 0xee, 0xc7, 0xed,
+ 0x28, 0x8d, 0xab, 0x4d, 0x87, 0x89, 0x73, 0xd0, 0x6e, 0xa4, 0xd0, 0x1e,
+ 0x16, 0x8b, 0x14, 0xe1, 0x76, 0x44, 0x03, 0x7f, 0x63, 0xac, 0xe4, 0xcd,
+ 0x49, 0x9c, 0xc5, 0x92, 0xf4, 0xab, 0x32, 0xa1, 0x48, 0x5b, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x81, 0xaa, 0x30, 0x81, 0xa7, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+ 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+ 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+ 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+ 0x45, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x44,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+ 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+ 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x3c, 0xec, 0x7b, 0xe0, 0xae, 0xa3, 0x0e, 0x96,
+ 0x6d, 0x30, 0xd7, 0x85, 0xc6, 0xd2, 0x68, 0x5b, 0x45, 0x5a, 0x82, 0xa6,
+ 0x34, 0x0f, 0xb0, 0xc9, 0x92, 0x23, 0x5e, 0x11, 0x6d, 0x08, 0x11, 0xb2,
+ 0x74, 0x09, 0x23, 0x3a, 0x35, 0x25, 0x73, 0x58, 0x5e, 0xca, 0xb9, 0x7c,
+ 0x28, 0xfa, 0x47, 0xec, 0xf9, 0xa0, 0x03, 0x58, 0x50, 0xb6, 0x53, 0xef,
+ 0x8c, 0xdb, 0x39, 0xe4, 0x67, 0xe9, 0xd8, 0xca, 0x28, 0x46, 0xd4, 0xa7,
+ 0xe0, 0xf5, 0x38, 0x75, 0xf8, 0xe7, 0xcb, 0x5c, 0xbf, 0x1d, 0x11, 0x3c,
+ 0x6a, 0x40, 0x9b, 0x2d, 0x44, 0x56, 0xd3, 0xf7, 0xff, 0x05, 0x28, 0x32,
+ 0x0c, 0x15, 0xc8, 0x64, 0x45, 0x93, 0xe8, 0x21, 0x24, 0x8f, 0x2d, 0xda,
+ 0x7a, 0x84, 0x7b, 0x4f, 0xcf, 0xcd, 0xb2, 0x25, 0x7c, 0x77, 0x10, 0xd3,
+ 0x94, 0xd1, 0x04, 0x91, 0xa8, 0x25, 0x1c, 0x09, 0x22, 0x0f, 0x7d, 0x44,
+ 0x35, 0x11, 0x14, 0xef, 0xaf, 0x00, 0xfe, 0x5e, 0xea, 0x5f, 0x8e, 0xb0,
+ 0xd9, 0x92, 0x59, 0xba, 0xfc, 0x13, 0x96, 0xa0, 0x18, 0x01, 0x56, 0xce,
+ 0xda, 0xf6, 0x28, 0x0b, 0xb1, 0xaf, 0xdd, 0x5c, 0x4f, 0x5c, 0xb2, 0xf3,
+ 0x8f, 0x5a, 0x71, 0xcf, 0xed, 0x18, 0xad, 0x63, 0x88, 0x1d, 0x8e, 0x95,
+ 0xf7, 0xea, 0x95, 0xe7, 0x1f, 0xad, 0x90, 0xb8, 0x84, 0x08, 0x47, 0x85,
+ 0x7f, 0x22, 0x2f, 0x1a, 0x1d, 0x48, 0x30, 0xd6, 0x4c, 0x08, 0xd8, 0x37,
+ 0x19, 0x67, 0x32, 0x2b, 0xeb, 0x5c, 0xd0, 0xb2, 0xfc, 0x6e, 0x57, 0x9f,
+ 0x04, 0x35, 0x5e, 0x90, 0x00, 0x7e, 0x11, 0xc7, 0xde, 0x13, 0x2a, 0xcd,
+ 0xa4, 0x6d, 0x45, 0x26, 0xc7, 0x88, 0x56, 0xa0, 0xf0, 0x6a, 0xf7, 0xd8,
+ 0xe7, 0xfc, 0x27, 0x7e, 0x67, 0x08, 0xd0, 0xbd, 0xfa, 0xb6, 0xc3, 0x61,
+ 0x02, 0x01, 0x65, 0xb9, 0xb8, 0x2f, 0xcf, 0x5a,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120001525 (0x72713f5)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Jan 17 15:16:20 2007 GMT
+ Not After : Jan 17 15:15:46 2014 GMT
+ Subject: DC=ru, DC=yandex, DC=ld, CN=YandexExternalCA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ce:7f:78:9c:38:3c:99:97:b3:5c:22:91:ca:b8:
+ 94:39:a2:5e:57:fe:ba:15:1c:98:86:d2:cd:b0:bb:
+ 7b:d7:62:6c:84:61:31:50:2c:63:35:aa:bf:f2:8c:
+ e0:0a:9c:db:70:a5:03:2b:f7:cf:aa:b8:ee:d2:5a:
+ cf:13:be:dc:53:fa:67:fb:e6:5a:6d:46:e0:f6:25:
+ ac:03:d9:5a:e4:aa:af:e0:bf:dd:8b:d2:5c:a0:ea:
+ f7:e6:5a:0a:2f:5a:11:9f:b4:a8:f2:e9:2f:0b:3d:
+ 31:a1:b3:2a:5f:3c:4b:c2:8c:1c:c6:dc:87:32:22:
+ 55:0f:4b:fe:15:22:f9:39:85:72:cd:16:5b:d1:f6:
+ 23:e3:31:9e:8f:7e:cd:4c:7d:4f:86:c2:e7:41:5a:
+ 41:b8:1d:e7:d2:4d:ca:ec:25:5e:23:fe:5f:de:39:
+ 12:24:09:cd:fa:c9:65:93:26:b0:94:4d:38:a0:c7:
+ 9d:2a:79:18:e2:1f:a0:2a:f1:4c:44:85:a3:4d:53:
+ a1:91:3a:01:10:c9:aa:c3:4f:49:fb:f1:9b:b8:bf:
+ cf:d2:e9:b4:41:84:bf:aa:c8:33:13:50:3b:97:cc:
+ bb:1e:0c:da:f9:8b:5c:3c:83:a3:59:f5:76:ef:98:
+ c1:78:7e:5e:52:18:02:8a:36:d2:c5:c5:f7:83:aa:
+ ca:17
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://www.public-trust.com/CPS/OmniRoot.html
+
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ DB:41:27:30:4F:1A:F5:5B:3E:84:56:C8:EC:85:98:B3:51:2C:2D:27
+ Signature Algorithm: sha1WithRSAEncryption
+ 19:b8:d2:c4:39:b0:e5:1d:d5:b7:40:96:e8:92:ae:40:36:b4:
+ e9:f7:f5:8b:2d:d4:4e:36:31:4a:d2:d3:e4:1e:ae:45:8d:ec:
+ 97:e0:68:0f:56:f0:14:4e:e4:1a:c9:d0:b7:e6:7c:fb:1f:ed:
+ 52:19:90:69:f4:5f:a9:4f:d6:27:68:d1:fa:94:a9:7b:a3:c9:
+ 97:3c:e0:b3:9d:06:1e:22:f1:82:80:8e:0b:d6:eb:f7:ed:0b:
+ 41:bd:ba:e2:07:f2:3c:87:e1:58:ff:8d:c5:32:30:27:93:d7:
+ 22:47:5c:60:6c:04:4a:e1:b5:0a:65:a3:dd:f4:c7:54:fb:f4:
+ d8:ef
+-----BEGIN CERTIFICATE-----
+MIIEPjCCA6egAwIBAgIEBycT9TANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA3MDExNzE1MTYyMFoXDTE0MDExNzE1MTU0NlowWzESMBAG
+CgmSJomT8ixkARkWAnJ1MRYwFAYKCZImiZPyLGQBGRYGeWFuZGV4MRIwEAYKCZIm
+iZPyLGQBGRYCbGQxGTAXBgNVBAMTEFlhbmRleEV4dGVybmFsQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOf3icODyZl7NcIpHKuJQ5ol5X/roVHJiG
+0s2wu3vXYmyEYTFQLGM1qr/yjOAKnNtwpQMr98+quO7SWs8TvtxT+mf75lptRuD2
+JawD2Vrkqq/gv92L0lyg6vfmWgovWhGftKjy6S8LPTGhsypfPEvCjBzG3IcyIlUP
+S/4VIvk5hXLNFlvR9iPjMZ6Pfs1MfU+GwudBWkG4HefSTcrsJV4j/l/eORIkCc36
+yWWTJrCUTTigx50qeRjiH6Aq8UxEhaNNU6GROgEQyarDT0n78Zu4v8/S6bRBhL+q
+yDMTUDuXzLseDNr5i1w8g6NZ9XbvmMF4fl5SGAKKNtLFxfeDqsoXAgMBAAGjggFv
+MIIBazASBgNVHRMBAf8ECDAGAQH/AgEBMFMGA1UdIARMMEowSAYJKwYBBAGxPgEA
+MDswOQYIKwYBBQUHAgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9DUFMv
+T21uaVJvb3QuaHRtbDAOBgNVHQ8BAf8EBAMCAYYwgYkGA1UdIwSBgTB/oXmkdzB1
+MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBD
+eWJlclRydXN0IEdsb2JhbCBSb290ggIBpTBFBgNVHR8EPjA8MDqgOKA2hjRodHRw
+Oi8vd3d3LnB1YmxpYy10cnVzdC5jb20vY2dpLWJpbi9DUkwvMjAxOC9jZHAuY3Js
+MB0GA1UdDgQWBBTbQScwTxr1Wz6EVsjshZizUSwtJzANBgkqhkiG9w0BAQUFAAOB
+gQAZuNLEObDlHdW3QJbokq5ANrTp9/WLLdRONjFK0tPkHq5FjeyX4GgPVvAUTuQa
+ydC35nz7H+1SGZBp9F+pT9YnaNH6lKl7o8mXPOCznQYeIvGCgI4L1uv37QtBvbri
+B/I8h+FY/43FMjAnk9ciR1xgbARK4bUKZaPd9MdU+/TY7w==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert23[] = {
+ 0x30, 0x82, 0x04, 0x3e, 0x30, 0x82, 0x03, 0xa7, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x13, 0xf5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x30, 0x37, 0x30, 0x31, 0x31, 0x37, 0x31, 0x35, 0x31, 0x36, 0x32,
+ 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x31, 0x31, 0x37, 0x31, 0x35,
+ 0x31, 0x35, 0x34, 0x36, 0x5a, 0x30, 0x5b, 0x31, 0x12, 0x30, 0x10, 0x06,
+ 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16,
+ 0x02, 0x72, 0x75, 0x31, 0x16, 0x30, 0x14, 0x06, 0x0a, 0x09, 0x92, 0x26,
+ 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x06, 0x79, 0x61, 0x6e,
+ 0x64, 0x65, 0x78, 0x31, 0x12, 0x30, 0x10, 0x06, 0x0a, 0x09, 0x92, 0x26,
+ 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x02, 0x6c, 0x64, 0x31,
+ 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x59, 0x61,
+ 0x6e, 0x64, 0x65, 0x78, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xce,
+ 0x7f, 0x78, 0x9c, 0x38, 0x3c, 0x99, 0x97, 0xb3, 0x5c, 0x22, 0x91, 0xca,
+ 0xb8, 0x94, 0x39, 0xa2, 0x5e, 0x57, 0xfe, 0xba, 0x15, 0x1c, 0x98, 0x86,
+ 0xd2, 0xcd, 0xb0, 0xbb, 0x7b, 0xd7, 0x62, 0x6c, 0x84, 0x61, 0x31, 0x50,
+ 0x2c, 0x63, 0x35, 0xaa, 0xbf, 0xf2, 0x8c, 0xe0, 0x0a, 0x9c, 0xdb, 0x70,
+ 0xa5, 0x03, 0x2b, 0xf7, 0xcf, 0xaa, 0xb8, 0xee, 0xd2, 0x5a, 0xcf, 0x13,
+ 0xbe, 0xdc, 0x53, 0xfa, 0x67, 0xfb, 0xe6, 0x5a, 0x6d, 0x46, 0xe0, 0xf6,
+ 0x25, 0xac, 0x03, 0xd9, 0x5a, 0xe4, 0xaa, 0xaf, 0xe0, 0xbf, 0xdd, 0x8b,
+ 0xd2, 0x5c, 0xa0, 0xea, 0xf7, 0xe6, 0x5a, 0x0a, 0x2f, 0x5a, 0x11, 0x9f,
+ 0xb4, 0xa8, 0xf2, 0xe9, 0x2f, 0x0b, 0x3d, 0x31, 0xa1, 0xb3, 0x2a, 0x5f,
+ 0x3c, 0x4b, 0xc2, 0x8c, 0x1c, 0xc6, 0xdc, 0x87, 0x32, 0x22, 0x55, 0x0f,
+ 0x4b, 0xfe, 0x15, 0x22, 0xf9, 0x39, 0x85, 0x72, 0xcd, 0x16, 0x5b, 0xd1,
+ 0xf6, 0x23, 0xe3, 0x31, 0x9e, 0x8f, 0x7e, 0xcd, 0x4c, 0x7d, 0x4f, 0x86,
+ 0xc2, 0xe7, 0x41, 0x5a, 0x41, 0xb8, 0x1d, 0xe7, 0xd2, 0x4d, 0xca, 0xec,
+ 0x25, 0x5e, 0x23, 0xfe, 0x5f, 0xde, 0x39, 0x12, 0x24, 0x09, 0xcd, 0xfa,
+ 0xc9, 0x65, 0x93, 0x26, 0xb0, 0x94, 0x4d, 0x38, 0xa0, 0xc7, 0x9d, 0x2a,
+ 0x79, 0x18, 0xe2, 0x1f, 0xa0, 0x2a, 0xf1, 0x4c, 0x44, 0x85, 0xa3, 0x4d,
+ 0x53, 0xa1, 0x91, 0x3a, 0x01, 0x10, 0xc9, 0xaa, 0xc3, 0x4f, 0x49, 0xfb,
+ 0xf1, 0x9b, 0xb8, 0xbf, 0xcf, 0xd2, 0xe9, 0xb4, 0x41, 0x84, 0xbf, 0xaa,
+ 0xc8, 0x33, 0x13, 0x50, 0x3b, 0x97, 0xcc, 0xbb, 0x1e, 0x0c, 0xda, 0xf9,
+ 0x8b, 0x5c, 0x3c, 0x83, 0xa3, 0x59, 0xf5, 0x76, 0xef, 0x98, 0xc1, 0x78,
+ 0x7e, 0x5e, 0x52, 0x18, 0x02, 0x8a, 0x36, 0xd2, 0xc5, 0xc5, 0xf7, 0x83,
+ 0xaa, 0xca, 0x17, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6f,
+ 0x30, 0x82, 0x01, 0x6b, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+ 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30,
+ 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+ 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x2f,
+ 0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x68, 0x74, 0x6d,
+ 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01,
+ 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c,
+ 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69,
+ 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f,
+ 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb,
+ 0x41, 0x27, 0x30, 0x4f, 0x1a, 0xf5, 0x5b, 0x3e, 0x84, 0x56, 0xc8, 0xec,
+ 0x85, 0x98, 0xb3, 0x51, 0x2c, 0x2d, 0x27, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81,
+ 0x81, 0x00, 0x19, 0xb8, 0xd2, 0xc4, 0x39, 0xb0, 0xe5, 0x1d, 0xd5, 0xb7,
+ 0x40, 0x96, 0xe8, 0x92, 0xae, 0x40, 0x36, 0xb4, 0xe9, 0xf7, 0xf5, 0x8b,
+ 0x2d, 0xd4, 0x4e, 0x36, 0x31, 0x4a, 0xd2, 0xd3, 0xe4, 0x1e, 0xae, 0x45,
+ 0x8d, 0xec, 0x97, 0xe0, 0x68, 0x0f, 0x56, 0xf0, 0x14, 0x4e, 0xe4, 0x1a,
+ 0xc9, 0xd0, 0xb7, 0xe6, 0x7c, 0xfb, 0x1f, 0xed, 0x52, 0x19, 0x90, 0x69,
+ 0xf4, 0x5f, 0xa9, 0x4f, 0xd6, 0x27, 0x68, 0xd1, 0xfa, 0x94, 0xa9, 0x7b,
+ 0xa3, 0xc9, 0x97, 0x3c, 0xe0, 0xb3, 0x9d, 0x06, 0x1e, 0x22, 0xf1, 0x82,
+ 0x80, 0x8e, 0x0b, 0xd6, 0xeb, 0xf7, 0xed, 0x0b, 0x41, 0xbd, 0xba, 0xe2,
+ 0x07, 0xf2, 0x3c, 0x87, 0xe1, 0x58, 0xff, 0x8d, 0xc5, 0x32, 0x30, 0x27,
+ 0x93, 0xd7, 0x22, 0x47, 0x5c, 0x60, 0x6c, 0x04, 0x4a, 0xe1, 0xb5, 0x0a,
+ 0x65, 0xa3, 0xdd, 0xf4, 0xc7, 0x54, 0xfb, 0xf4, 0xd8, 0xef,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1116160165 (0x428740a5)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Validity
+ Not Before: Oct 1 05:00:00 2006 GMT
+ Not After : Jul 26 18:15:15 2014 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+ e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+ a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+ a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+ cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+ 91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+ f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+ 55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+ be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+ ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+ 22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+ 57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+ 68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+ 64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+ 39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+ a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+ 87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+ 4b:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, E-mail Protection
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/server1.crl
+
+ X509v3 Subject Key Identifier:
+ B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V7.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ 48:0e:2b:6f:20:62:4c:28:93:a3:24:3d:58:ab:21:cf:80:f8:
+ 9a:97:90:6a:22:ed:5a:7c:47:36:99:e7:79:84:75:ab:24:8f:
+ 92:0a:d5:61:04:ae:c3:6a:5c:b2:cc:d9:e4:44:87:6f:db:8f:
+ 38:62:f7:44:36:9d:ba:bc:6e:07:c4:d4:8d:e8:1f:d1:0b:60:
+ a3:b5:9c:ce:63:be:ed:67:dc:f8:ba:de:6e:c9:25:cb:5b:b5:
+ 9d:76:70:0b:df:42:72:f8:4f:41:11:64:a5:d2:ea:fc:d5:af:
+ 11:f4:15:38:67:9c:20:a8:4b:77:5a:91:32:42:32:e7:85:b3:
+ df:36
+-----BEGIN CERTIFICATE-----
+MIIEQjCCA6ugAwIBAgIEQodApTANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEw
+MDEwNTAwMDBaFw0xNDA3MjYxODE1MTVaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
+EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
+BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD
+1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt
+cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46
+OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd
+HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm
+t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET
+MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr
+BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo
+dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v
+Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU
+mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7
+UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF
+BQADgYEASA4rbyBiTCiToyQ9WKshz4D4mpeQaiLtWnxHNpnneYR1qySPkgrVYQSu
+w2pcsszZ5ESHb9uPOGL3RDadurxuB8TUjegf0Qtgo7WczmO+7Wfc+Lrebskly1u1
+nXZwC99CcvhPQRFkpdLq/NWvEfQVOGecIKhLd1qRMkIy54Wz3zY=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert24[] = {
+ 0x30, 0x82, 0x04, 0x42, 0x30, 0x82, 0x03, 0xab, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x42, 0x87, 0x40, 0xa5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+ 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+ 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+ 0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x30,
+ 0x30, 0x31, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31,
+ 0x34, 0x30, 0x37, 0x32, 0x36, 0x31, 0x38, 0x31, 0x35, 0x31, 0x35, 0x5a,
+ 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49,
+ 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+ 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61,
+ 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6,
+ 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6,
+ 0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3,
+ 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69, 0xa6, 0x40,
+ 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, 0x3a,
+ 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d,
+ 0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d,
+ 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4, 0x84, 0xf8,
+ 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, 0x06,
+ 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89,
+ 0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a,
+ 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24, 0x45, 0x9a,
+ 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, 0x51,
+ 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50,
+ 0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d,
+ 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4, 0x68, 0x73,
+ 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, 0xdb,
+ 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29,
+ 0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66,
+ 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab, 0x0a, 0x64,
+ 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, 0x5d,
+ 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38,
+ 0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x13,
+ 0x30, 0x82, 0x01, 0x0f, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+ 0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x20, 0x30, 0x1e, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65,
+ 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0,
+ 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4,
+ 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0b,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d, 0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb,
+ 0x50, 0x84, 0x97, 0xf3, 0xed, 0x62, 0xd0, 0x1a, 0x30, 0x19, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30,
+ 0x0a, 0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x48, 0x0e, 0x2b, 0x6f, 0x20, 0x62,
+ 0x4c, 0x28, 0x93, 0xa3, 0x24, 0x3d, 0x58, 0xab, 0x21, 0xcf, 0x80, 0xf8,
+ 0x9a, 0x97, 0x90, 0x6a, 0x22, 0xed, 0x5a, 0x7c, 0x47, 0x36, 0x99, 0xe7,
+ 0x79, 0x84, 0x75, 0xab, 0x24, 0x8f, 0x92, 0x0a, 0xd5, 0x61, 0x04, 0xae,
+ 0xc3, 0x6a, 0x5c, 0xb2, 0xcc, 0xd9, 0xe4, 0x44, 0x87, 0x6f, 0xdb, 0x8f,
+ 0x38, 0x62, 0xf7, 0x44, 0x36, 0x9d, 0xba, 0xbc, 0x6e, 0x07, 0xc4, 0xd4,
+ 0x8d, 0xe8, 0x1f, 0xd1, 0x0b, 0x60, 0xa3, 0xb5, 0x9c, 0xce, 0x63, 0xbe,
+ 0xed, 0x67, 0xdc, 0xf8, 0xba, 0xde, 0x6e, 0xc9, 0x25, 0xcb, 0x5b, 0xb5,
+ 0x9d, 0x76, 0x70, 0x0b, 0xdf, 0x42, 0x72, 0xf8, 0x4f, 0x41, 0x11, 0x64,
+ 0xa5, 0xd2, 0xea, 0xfc, 0xd5, 0xaf, 0x11, 0xf4, 0x15, 0x38, 0x67, 0x9c,
+ 0x20, 0xa8, 0x4b, 0x77, 0x5a, 0x91, 0x32, 0x42, 0x32, 0xe7, 0x85, 0xb3,
+ 0xdf, 0x36,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 33:65:50:08:79:ad:73:e2:30:b9:e0:1d:0d:7f:ac:91
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+ Validity
+ Not Before: Nov 17 00:00:00 2006 GMT
+ Not After : Dec 30 23:59:59 2020 GMT
+ Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ac:a0:f0:fb:80:59:d4:9c:c7:a4:cf:9d:a1:59:
+ 73:09:10:45:0c:0d:2c:6e:68:f1:6c:5b:48:68:49:
+ 59:37:fc:0b:33:19:c2:77:7f:cc:10:2d:95:34:1c:
+ e6:eb:4d:09:a7:1c:d2:b8:c9:97:36:02:b7:89:d4:
+ 24:5f:06:c0:cc:44:94:94:8d:02:62:6f:eb:5a:dd:
+ 11:8d:28:9a:5c:84:90:10:7a:0d:bd:74:66:2f:6a:
+ 38:a0:e2:d5:54:44:eb:1d:07:9f:07:ba:6f:ee:e9:
+ fd:4e:0b:29:f5:3e:84:a0:01:f1:9c:ab:f8:1c:7e:
+ 89:a4:e8:a1:d8:71:65:0d:a3:51:7b:ee:bc:d2:22:
+ 60:0d:b9:5b:9d:df:ba:fc:51:5b:0b:af:98:b2:e9:
+ 2e:e9:04:e8:62:87:de:2b:c8:d7:4e:c1:4c:64:1e:
+ dd:cf:87:58:ba:4a:4f:ca:68:07:1d:1c:9d:4a:c6:
+ d5:2f:91:cc:7c:71:72:1c:c5:c0:67:eb:32:fd:c9:
+ 92:5c:94:da:85:c0:9b:bf:53:7d:2b:09:f4:8c:9d:
+ 91:1f:97:6a:52:cb:de:09:36:a4:77:d8:7b:87:50:
+ 44:d5:3e:6e:29:69:fb:39:49:26:1e:09:a5:80:7b:
+ 40:2d:eb:e8:27:85:c9:fe:61:fd:7e:e6:7c:97:1d:
+ d5:9d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.thawte.com/cps
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.thawte.com/ThawtePremiumServerCA.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 84:a8:4c:c9:3e:2a:bc:9a:e2:cc:8f:0b:b2:25:77:c4:61:89:
+ 89:63:5a:d4:a3:15:40:d4:fb:5e:3f:b4:43:ea:63:17:2b:6b:
+ 99:74:9e:09:a8:dd:d4:56:15:2e:7a:79:31:5f:63:96:53:1b:
+ 34:d9:15:ea:4f:6d:70:ca:be:f6:82:a9:ed:da:85:77:cc:76:
+ 1c:6a:81:0a:21:d8:41:99:7f:5e:2e:82:c1:e8:aa:f7:93:81:
+ 05:aa:92:b4:1f:b7:9a:c0:07:17:f5:cb:c6:b4:4c:0e:d7:56:
+ dc:71:20:74:38:d6:74:c6:d6:8f:6b:af:8b:8d:a0:6c:29:0b:
+ 61:e0
+-----BEGIN CERTIFICATE-----
+MIIERTCCA66gAwIBAgIQM2VQCHmtc+IwueAdDX+skTANBgkqhkiG9w0BAQUFADCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTA2MTExNzAwMDAwMFoXDTIwMTIzMDIzNTk1OVow
+gakxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xKDAmBgNVBAsT
+H0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xODA2BgNVBAsTLyhjKSAy
+MDA2IHRoYXd0ZSwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQDExZ0aGF3dGUgUHJpbWFyeSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEArKDw+4BZ1JzHpM+doVlzCRBFDA0sbmjxbFtIaElZN/wLMxnC
+d3/MEC2VNBzm600JpxzSuMmXNgK3idQkXwbAzESUlI0CYm/rWt0RjSiaXISQEHoN
+vXRmL2o4oOLVVETrHQefB7pv7un9Tgsp9T6EoAHxnKv4HH6JpOih2HFlDaNRe+68
+0iJgDblbnd+6/FFbC6+Ysuku6QToYofeK8jXTsFMZB7dz4dYukpPymgHHRydSsbV
+L5HMfHFyHMXAZ+sy/cmSXJTahcCbv1N9Kwn0jJ2RH5dqUsveCTakd9h7h1BE1T5u
+KWn7OUkmHgmlgHtALevoJ4XJ/mH9fuZ8lx3VnQIDAQABo4HCMIG/MA8GA1UdEwEB
+/wQFMAMBAf8wOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHBz
+Oi8vd3d3LnRoYXd0ZS5jb20vY3BzMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+e1tFz6/Oy3r9MZIaarbzRutXSFAwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cDovL2Ny
+bC50aGF3dGUuY29tL1RoYXd0ZVByZW1pdW1TZXJ2ZXJDQS5jcmwwDQYJKoZIhvcN
+AQEFBQADgYEAhKhMyT4qvJrizI8LsiV3xGGJiWNa1KMVQNT7Xj+0Q+pjFytrmXSe
+Cajd1FYVLnp5MV9jllMbNNkV6k9tcMq+9oKp7dqFd8x2HGqBCiHYQZl/Xi6Cweiq
+95OBBaqStB+3msAHF/XLxrRMDtdW3HEgdDjWdMbWj2uvi42gbCkLYeA=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert25[] = {
+ 0x30, 0x82, 0x04, 0x45, 0x30, 0x82, 0x03, 0xae, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x33, 0x65, 0x50, 0x08, 0x79, 0xad, 0x73, 0xe2, 0x30,
+ 0xb9, 0xe0, 0x1d, 0x0d, 0x7f, 0xac, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+ 0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+ 0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+ 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+ 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+ 0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+ 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+ 0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+ 0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31,
+ 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30,
+ 0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20,
+ 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32,
+ 0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+ 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+ 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74,
+ 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d,
+ 0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1,
+ 0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2,
+ 0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09,
+ 0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24,
+ 0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb,
+ 0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d,
+ 0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb,
+ 0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29,
+ 0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89,
+ 0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc,
+ 0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b,
+ 0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde,
+ 0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58,
+ 0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5,
+ 0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32,
+ 0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d,
+ 0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde,
+ 0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e,
+ 0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40,
+ 0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c,
+ 0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xc2,
+ 0x30, 0x81, 0xbf, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74,
+ 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a,
+ 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0, 0x33, 0xa0,
+ 0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65, 0x6d, 0x69,
+ 0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0xa8, 0x4c,
+ 0xc9, 0x3e, 0x2a, 0xbc, 0x9a, 0xe2, 0xcc, 0x8f, 0x0b, 0xb2, 0x25, 0x77,
+ 0xc4, 0x61, 0x89, 0x89, 0x63, 0x5a, 0xd4, 0xa3, 0x15, 0x40, 0xd4, 0xfb,
+ 0x5e, 0x3f, 0xb4, 0x43, 0xea, 0x63, 0x17, 0x2b, 0x6b, 0x99, 0x74, 0x9e,
+ 0x09, 0xa8, 0xdd, 0xd4, 0x56, 0x15, 0x2e, 0x7a, 0x79, 0x31, 0x5f, 0x63,
+ 0x96, 0x53, 0x1b, 0x34, 0xd9, 0x15, 0xea, 0x4f, 0x6d, 0x70, 0xca, 0xbe,
+ 0xf6, 0x82, 0xa9, 0xed, 0xda, 0x85, 0x77, 0xcc, 0x76, 0x1c, 0x6a, 0x81,
+ 0x0a, 0x21, 0xd8, 0x41, 0x99, 0x7f, 0x5e, 0x2e, 0x82, 0xc1, 0xe8, 0xaa,
+ 0xf7, 0x93, 0x81, 0x05, 0xaa, 0x92, 0xb4, 0x1f, 0xb7, 0x9a, 0xc0, 0x07,
+ 0x17, 0xf5, 0xcb, 0xc6, 0xb4, 0x4c, 0x0e, 0xd7, 0x56, 0xdc, 0x71, 0x20,
+ 0x74, 0x38, 0xd6, 0x74, 0xc6, 0xd6, 0x8f, 0x6b, 0xaf, 0x8b, 0x8d, 0xa0,
+ 0x6c, 0x29, 0x0b, 0x61, 0xe0,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120026506 (0x727758a)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: Jul 25 17:58:28 2012 GMT
+ Not After : Jul 25 17:57:44 2019 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+ e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+ a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+ a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+ cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+ 91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+ f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+ 55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+ be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+ ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+ 22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+ 57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+ 68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+ 64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+ 39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+ a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+ 87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+ 4b:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+ X509v3 Subject Key Identifier:
+ B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+ Signature Algorithm: sha1WithRSAEncryption
+ 76:56:58:36:0d:19:98:b4:d9:a5:cb:30:75:ae:b1:d6:80:97:
+ cc:ee:38:72:68:39:b0:02:3e:46:b6:c4:f2:ac:d1:d2:e1:66:
+ 16:e6:85:a4:55:77:cb:2e:1c:59:dd:a5:4b:df:2f:33:bb:ce:
+ 60:57:27:3a:a1:4d:49:6f:55:76:6d:d5:d7:c2:a0:5b:2a:9b:
+ f9:4b:f7:7f:21:dd:ee:5c:57:0d:00:35:3a:f1:8c:46:cb:04:
+ f6:46:8f:ce:05:6a:d5:c4:6c:fe:6e:98:bf:a4:9c:bd:8e:89:
+ 2c:be:71:01:43:cc:36:2a:64:06:56:97:93:a5:47:bd:4a:3f:
+ 8c:1b:75:c8:9e:b0:f0:25:98:77:21:c0:76:a7:51:7a:24:25:
+ 7d:18:35:06:fe:c1:09:c5:0e:3b:99:a8:cd:9d:29:b0:3a:89:
+ f5:ea:e7:2a:e5:e2:24:4e:68:a9:1d:a7:dd:d2:08:4b:a1:d1:
+ 6f:0c:bd:2c:e0:bb:7c:fa:a1:3c:65:cf:3a:52:4b:d3:20:7a:
+ 0a:10:55:f8:ad:43:16:54:27:4e:53:73:c8:a3:96:89:d0:e1:
+ 79:c6:09:78:d5:f5:bd:b1:b3:c5:7f:a6:4b:af:49:11:c8:97:
+ 9c:4f:7c:70:69:16:5c:2d:b8:d0:df:1c:32:52:b9:de:f3:c3:
+ 06:e8:83:22
+-----BEGIN CERTIFICATE-----
+MIIERjCCAy6gAwIBAgIEByd1ijANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEyMDcyNTE3NTgyOFoX
+DTE5MDcyNTE3NTc0NFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
+IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNl
+cnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMbM5XPm+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9p
+pkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8Ok
+hPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ck
+RZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhak
+aHPQNAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDir
+CmTCv2ZdlYTBoSUeh10aUAsgEsxBu24LUTi4S8sCAwEAAaOCAQAwgf0wEgYDVR0T
+AQH/BAgwBgEB/wIBATBTBgNVHSAETDBKMEgGCSsGAQQBsT4BADA7MDkGCCsGAQUF
+BwIBFi1odHRwOi8vY3liZXJ0cnVzdC5vbW5pcm9vdC5jb20vcmVwb3NpdG9yeS5j
+Zm0wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMEIGA1UdHwQ7MDkwN6A1oDOGMWh0dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5j
+b20vQ1JML09tbmlyb290MjAyNS5jcmwwHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoI
+Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQB2Vlg2DRmYtNmlyzB1rrHWgJfM7jhy
+aDmwAj5GtsTyrNHS4WYW5oWkVXfLLhxZ3aVL3y8zu85gVyc6oU1Jb1V2bdXXwqBb
+Kpv5S/d/Id3uXFcNADU68YxGywT2Ro/OBWrVxGz+bpi/pJy9joksvnEBQ8w2KmQG
+VpeTpUe9Sj+MG3XInrDwJZh3IcB2p1F6JCV9GDUG/sEJxQ47majNnSmwOon16ucq
+5eIkTmipHafd0ghLodFvDL0s4Lt8+qE8Zc86UkvTIHoKEFX4rUMWVCdOU3PIo5aJ
+0OF5xgl41fW9sbPFf6ZLr0kRyJecT3xwaRZcLbjQ3xwyUrne88MG6IMi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert26[] = {
+ 0x30, 0x82, 0x04, 0x46, 0x30, 0x82, 0x03, 0x2e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x75, 0x8a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+ 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+ 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32,
+ 0x30, 0x37, 0x32, 0x35, 0x31, 0x37, 0x35, 0x38, 0x32, 0x38, 0x5a, 0x17,
+ 0x0d, 0x31, 0x39, 0x30, 0x37, 0x32, 0x35, 0x31, 0x37, 0x35, 0x37, 0x34,
+ 0x34, 0x5a, 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+ 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63,
+ 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65,
+ 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75,
+ 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f,
+ 0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xc6, 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d,
+ 0x32, 0xa6, 0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71,
+ 0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69,
+ 0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41,
+ 0x59, 0x3a, 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a,
+ 0xcc, 0x4d, 0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3,
+ 0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4,
+ 0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92,
+ 0xe3, 0x06, 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb,
+ 0x5a, 0x89, 0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d,
+ 0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24,
+ 0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13,
+ 0xeb, 0x51, 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81,
+ 0x41, 0x50, 0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e,
+ 0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4,
+ 0x68, 0x73, 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c,
+ 0x55, 0xdb, 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11,
+ 0xb4, 0x29, 0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c,
+ 0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab,
+ 0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e,
+ 0x87, 0x5d, 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b,
+ 0x51, 0x38, 0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0x00, 0x30, 0x81, 0xfd, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x01, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a,
+ 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01,
+ 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f,
+ 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63,
+ 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82,
+ 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5,
+ 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b,
+ 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75,
+ 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72,
+ 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1, 0x3e,
+ 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08,
+ 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x76, 0x56, 0x58, 0x36, 0x0d, 0x19, 0x98, 0xb4, 0xd9, 0xa5,
+ 0xcb, 0x30, 0x75, 0xae, 0xb1, 0xd6, 0x80, 0x97, 0xcc, 0xee, 0x38, 0x72,
+ 0x68, 0x39, 0xb0, 0x02, 0x3e, 0x46, 0xb6, 0xc4, 0xf2, 0xac, 0xd1, 0xd2,
+ 0xe1, 0x66, 0x16, 0xe6, 0x85, 0xa4, 0x55, 0x77, 0xcb, 0x2e, 0x1c, 0x59,
+ 0xdd, 0xa5, 0x4b, 0xdf, 0x2f, 0x33, 0xbb, 0xce, 0x60, 0x57, 0x27, 0x3a,
+ 0xa1, 0x4d, 0x49, 0x6f, 0x55, 0x76, 0x6d, 0xd5, 0xd7, 0xc2, 0xa0, 0x5b,
+ 0x2a, 0x9b, 0xf9, 0x4b, 0xf7, 0x7f, 0x21, 0xdd, 0xee, 0x5c, 0x57, 0x0d,
+ 0x00, 0x35, 0x3a, 0xf1, 0x8c, 0x46, 0xcb, 0x04, 0xf6, 0x46, 0x8f, 0xce,
+ 0x05, 0x6a, 0xd5, 0xc4, 0x6c, 0xfe, 0x6e, 0x98, 0xbf, 0xa4, 0x9c, 0xbd,
+ 0x8e, 0x89, 0x2c, 0xbe, 0x71, 0x01, 0x43, 0xcc, 0x36, 0x2a, 0x64, 0x06,
+ 0x56, 0x97, 0x93, 0xa5, 0x47, 0xbd, 0x4a, 0x3f, 0x8c, 0x1b, 0x75, 0xc8,
+ 0x9e, 0xb0, 0xf0, 0x25, 0x98, 0x77, 0x21, 0xc0, 0x76, 0xa7, 0x51, 0x7a,
+ 0x24, 0x25, 0x7d, 0x18, 0x35, 0x06, 0xfe, 0xc1, 0x09, 0xc5, 0x0e, 0x3b,
+ 0x99, 0xa8, 0xcd, 0x9d, 0x29, 0xb0, 0x3a, 0x89, 0xf5, 0xea, 0xe7, 0x2a,
+ 0xe5, 0xe2, 0x24, 0x4e, 0x68, 0xa9, 0x1d, 0xa7, 0xdd, 0xd2, 0x08, 0x4b,
+ 0xa1, 0xd1, 0x6f, 0x0c, 0xbd, 0x2c, 0xe0, 0xbb, 0x7c, 0xfa, 0xa1, 0x3c,
+ 0x65, 0xcf, 0x3a, 0x52, 0x4b, 0xd3, 0x20, 0x7a, 0x0a, 0x10, 0x55, 0xf8,
+ 0xad, 0x43, 0x16, 0x54, 0x27, 0x4e, 0x53, 0x73, 0xc8, 0xa3, 0x96, 0x89,
+ 0xd0, 0xe1, 0x79, 0xc6, 0x09, 0x78, 0xd5, 0xf5, 0xbd, 0xb1, 0xb3, 0xc5,
+ 0x7f, 0xa6, 0x4b, 0xaf, 0x49, 0x11, 0xc8, 0x97, 0x9c, 0x4f, 0x7c, 0x70,
+ 0x69, 0x16, 0x5c, 0x2d, 0xb8, 0xd0, 0xdf, 0x1c, 0x32, 0x52, 0xb9, 0xde,
+ 0xf3, 0xc3, 0x06, 0xe8, 0x83, 0x22,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120019005 (0x727583d)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Jan 13 19:20:32 2010 GMT
+ Not After : Sep 30 18:19:47 2015 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+ e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+ a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+ a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+ cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+ 91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+ f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+ 55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+ be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+ ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+ 22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+ 57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+ 68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+ 64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+ 39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+ a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+ 87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+ 4b:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+ Signature Algorithm: sha1WithRSAEncryption
+ 2e:76:85:d9:37:96:6d:af:89:f3:06:78:82:31:c4:46:07:1f:
+ 65:c9:8e:b3:c9:54:78:e6:d1:42:df:75:2e:1e:55:ea:f7:fa:
+ 9b:04:c0:75:7b:d1:79:3c:05:ec:79:c4:52:dd:a6:03:d7:a7:
+ 50:99:3f:05:59:da:c6:55:f4:86:9c:0d:67:a3:49:04:95:32:
+ 1d:c7:87:ec:85:af:64:6e:d5:c5:5f:09:a7:40:7d:16:ba:49:
+ 0d:a2:fd:f6:df:55:30:6c:d7:78:c6:b9:cf:58:29:64:16:4c:
+ a3:20:81:47:b1:44:92:84:16:1b:6f:4a:bc:21:c6:0a:3d:ed:
+ 33:ca
+-----BEGIN CERTIFICATE-----
+MIIETzCCA7igAwIBAgIEBydYPTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDExMzE5MjAzMloXDTE1MDkzMDE4MTk0N1owbDELMAkG
+A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
+Z2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYg
+Um9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S7
+5S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0
+OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChB
+VfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5l
+YYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3hzBW
+BOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsgEsxB
+u24LUTi4S8sCAwEAAaOCAW8wggFrMBIGA1UdEwEB/wQIMAYBAf8CAQEwUwYDVR0g
+BEwwSjBIBgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYtaHR0cDovL2N5YmVydHJ1
+c3Qub21uaXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4GA1UdDwEB/wQEAwIBBjCB
+iQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29y
+cG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5j
+LjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUGA1Ud
+HwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2ktYmlu
+L0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvD
+MA0GCSqGSIb3DQEBBQUAA4GBAC52hdk3lm2vifMGeIIxxEYHH2XJjrPJVHjm0ULf
+dS4eVer3+psEwHV70Xk8Bex5xFLdpgPXp1CZPwVZ2sZV9IacDWejSQSVMh3Hh+yF
+r2Ru1cVfCadAfRa6SQ2i/fbfVTBs13jGuc9YKWQWTKMggUexRJKEFhtvSrwhxgo9
+7TPK
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert27[] = {
+ 0x30, 0x82, 0x04, 0x4f, 0x30, 0x82, 0x03, 0xb8, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x58, 0x3d, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x30, 0x30, 0x31, 0x31, 0x33, 0x31, 0x39, 0x32, 0x30, 0x33,
+ 0x32, 0x5a, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x39, 0x33, 0x30, 0x31, 0x38,
+ 0x31, 0x39, 0x34, 0x37, 0x5a, 0x30, 0x6c, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69, 0x67, 0x69, 0x43,
+ 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69,
+ 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x2b,
+ 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, 0x44, 0x69, 0x67,
+ 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+ 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x45, 0x56, 0x20,
+ 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xc6, 0xcc, 0xe5, 0x73, 0xe6, 0xfb, 0xd4, 0xbb,
+ 0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81, 0x3f, 0xc9, 0xcd, 0x25,
+ 0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67, 0xa2, 0x0a, 0x1c,
+ 0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2, 0x8f, 0xd0, 0x98,
+ 0xa4, 0xa9, 0x41, 0x59, 0x3a, 0xd3, 0xdc, 0x94, 0xd6, 0x3c, 0xdb, 0x74,
+ 0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7, 0x4a, 0xa5, 0x53, 0x12,
+ 0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63, 0xb6, 0xab, 0xa6,
+ 0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe, 0xf8, 0xc5, 0xec,
+ 0xdb, 0x38, 0x92, 0xe3, 0x06, 0xe5, 0x08, 0x91, 0x0c, 0xc4, 0x28, 0x41,
+ 0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71, 0xe8, 0x35, 0xbf, 0x4d,
+ 0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77, 0x31, 0x1b, 0x8d,
+ 0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00, 0x14, 0x5a, 0x04,
+ 0xb7, 0xba, 0x13, 0xeb, 0x51, 0x0a, 0x98, 0x41, 0x41, 0x22, 0x4e, 0x65,
+ 0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c, 0x89, 0xde, 0x19, 0x4a,
+ 0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e, 0x98, 0xcd, 0x1a,
+ 0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04, 0x13, 0x5c, 0xa1, 0x71,
+ 0xd3, 0x5a, 0x7c, 0x55, 0xdb, 0x5e, 0x64, 0xe1, 0x37, 0x87, 0x30, 0x56,
+ 0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1, 0x79, 0x39, 0x88, 0xa2,
+ 0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78, 0xf2, 0xca, 0x0a,
+ 0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d, 0x95, 0x84, 0xc1,
+ 0xa1, 0x25, 0x1e, 0x87, 0x5d, 0x1a, 0x50, 0x0b, 0x20, 0x12, 0xcc, 0x41,
+ 0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x01, 0x6f, 0x30, 0x82, 0x01, 0x6b, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+ 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x53, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x81,
+ 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1,
+ 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72,
+ 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f,
+ 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f,
+ 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+ 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+ 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+ 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47,
+ 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x2e, 0x76, 0x85, 0xd9, 0x37,
+ 0x96, 0x6d, 0xaf, 0x89, 0xf3, 0x06, 0x78, 0x82, 0x31, 0xc4, 0x46, 0x07,
+ 0x1f, 0x65, 0xc9, 0x8e, 0xb3, 0xc9, 0x54, 0x78, 0xe6, 0xd1, 0x42, 0xdf,
+ 0x75, 0x2e, 0x1e, 0x55, 0xea, 0xf7, 0xfa, 0x9b, 0x04, 0xc0, 0x75, 0x7b,
+ 0xd1, 0x79, 0x3c, 0x05, 0xec, 0x79, 0xc4, 0x52, 0xdd, 0xa6, 0x03, 0xd7,
+ 0xa7, 0x50, 0x99, 0x3f, 0x05, 0x59, 0xda, 0xc6, 0x55, 0xf4, 0x86, 0x9c,
+ 0x0d, 0x67, 0xa3, 0x49, 0x04, 0x95, 0x32, 0x1d, 0xc7, 0x87, 0xec, 0x85,
+ 0xaf, 0x64, 0x6e, 0xd5, 0xc5, 0x5f, 0x09, 0xa7, 0x40, 0x7d, 0x16, 0xba,
+ 0x49, 0x0d, 0xa2, 0xfd, 0xf6, 0xdf, 0x55, 0x30, 0x6c, 0xd7, 0x78, 0xc6,
+ 0xb9, 0xcf, 0x58, 0x29, 0x64, 0x16, 0x4c, 0xa3, 0x20, 0x81, 0x47, 0xb1,
+ 0x44, 0x92, 0x84, 0x16, 0x1b, 0x6f, 0x4a, 0xbc, 0x21, 0xc6, 0x0a, 0x3d,
+ 0xed, 0x33, 0xca,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:41:43
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Domain Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b1:a3:cd:c0:df:33:40:26:eb:de:5a:d7:94:66:
+ d4:01:63:cc:33:44:89:e0:e2:b8:c2:47:0d:8f:ad:
+ 69:86:1c:a8:73:42:0b:f1:72:fb:2d:ac:b5:11:72:
+ 83:22:f6:56:e7:2e:c5:67:71:9d:00:1c:32:bc:e3:
+ ed:2e:08:45:a9:e6:fa:dd:c8:8c:83:05:c1:6f:4b:
+ d0:26:4a:0b:f6:1b:45:c0:4d:7e:93:bc:0d:27:84:
+ ed:30:a3:e9:c6:26:26:dd:2d:1f:d8:8b:c3:ce:19:
+ d0:5b:fc:08:9f:e4:d8:e2:35:e4:a0:68:a6:f6:0d:
+ a3:74:60:42:b2:97:82:24:8e:41:a4:f2:2e:5e:b6:
+ 8e:a7:6e:d9:6c:7f:0d:3b:24:35:6a:d0:ab:5b:6a:
+ f7:97:02:00:3f:51:a6:a7:6e:73:ca:77:0d:76:7c:
+ 9b:b6:30:1a:1a:9c:f7:1f:28:7b:0e:8b:47:1f:e7:
+ 7f:05:8c:c6:c9:c8:bb:cf:e9:dc:7a:41:2e:a1:86:
+ da:d4:39:b2:e2:13:40:a6:a8:3a:fa:0f:53:1e:4f:
+ ec:6e:98:09:1b:ca:9a:77:b3:55:85:85:e9:2e:16:
+ b5:9d:5e:54:f1:4a:7a:6c:39:ba:6e:17:06:34:b3:
+ b2:42:e1:f7:f3:9c:9a:0b:11:44:de:6a:78:8e:b1:
+ 13:4f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 96:AD:FA:B0:5B:B9:83:64:2A:76:C2:1C:8A:69:DA:42:DC:FE:FD:28
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3a:e7:fc:ae:af:05:43:80:27:75:41:5f:a8:f0:28:8f:1f:8f:
+ 83:7e:b2:b8:ba:ae:75:31:27:88:a5:e5:b9:4e:04:43:d2:ad:
+ e8:13:00:a3:db:19:01:30:9e:6c:3c:52:7f:5c:de:ab:67:c3:
+ 84:04:54:51:99:9e:63:2f:bd:d5:b7:c0:d5:da:03:0e:49:d3:
+ e1:b3:92:4f:df:92:4e:7d:ae:22:6a:ce:d8:bc:fc:7c:ae:6b:
+ b6:8a:ea:45:62:90:11:d3:0b:71:a7:5e:06:22:ff:4d:38:ea:
+ b9:3a:6e:cd:67:1a:02:7f:4b:f3:bf:0e:79:6f:be:d5:29:32:
+ 59:59:1d:96:08:9b:70:8f:f7:1e:5c:46:7b:4e:d0:9d:b4:53:
+ c8:12:02:1b:0d:bb:32:eb:59:53:b9:3e:1b:56:8d:15:c8:f1:
+ 42:3f:77:fe:1f:e5:6d:9e:66:1f:ab:da:b2:83:57:b4:0c:22:
+ d2:86:bc:da:32:d7:c0:ed:70:85:7c:93:aa:f0:97:dc:39:11:
+ d2:d8:89:eb:8d:90:a3:b6:50:25:cb:6c:d9:a6:c3:6f:fb:88:
+ 54:b8:e4:92:70:87:ce:79:3b:f0:de:36:bf:03:04:00:3d:f9:
+ ef:9e:a9:67:a4:f4:86:3e:23:97:b8:2a:71:e2:ed:fe:69:88:
+ 67:bf:26:5c
+-----BEGIN CERTIFICATE-----
+MIIEWjCCA0KgAwIBAgILBAAAAAABL07hQUMwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0
+aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxo83A
+3zNAJuveWteUZtQBY8wzRIng4rjCRw2PrWmGHKhzQgvxcvstrLURcoMi9lbnLsVn
+cZ0AHDK84+0uCEWp5vrdyIyDBcFvS9AmSgv2G0XATX6TvA0nhO0wo+nGJibdLR/Y
+i8POGdBb/Aif5NjiNeSgaKb2DaN0YEKyl4IkjkGk8i5eto6nbtlsfw07JDVq0Ktb
+aveXAgA/UaanbnPKdw12fJu2MBoanPcfKHsOi0cf538FjMbJyLvP6dx6QS6hhtrU
+ObLiE0CmqDr6D1MeT+xumAkbypp3s1WFhekuFrWdXlTxSnpsObpuFwY0s7JC4ffz
+nJoLEUTeaniOsRNPAgMBAAGjggElMIIBITAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUlq36sFu5g2QqdsIcimnaQtz+/SgwRwYD
+VR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jh
+bHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9j
+cmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEwHwYDVR0j
+BBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQADggEBADrn
+/K6vBUOAJ3VBX6jwKI8fj4N+sri6rnUxJ4il5blOBEPSregTAKPbGQEwnmw8Un9c
+3qtnw4QEVFGZnmMvvdW3wNXaAw5J0+Gzkk/fkk59riJqzti8/Hyua7aK6kVikBHT
+C3GnXgYi/0046rk6bs1nGgJ/S/O/DnlvvtUpMllZHZYIm3CP9x5cRntO0J20U8gS
+AhsNuzLrWVO5PhtWjRXI8UI/d/4f5W2eZh+r2rKDV7QMItKGvNoy18DtcIV8k6rw
+l9w5EdLYieuNkKO2UCXLbNmmw2/7iFS45JJwh855O/DeNr8DBAA9+e+eqWek9IY+
+I5e4KnHi7f5piGe/Jlw=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert28[] = {
+ 0x30, 0x82, 0x04, 0x5a, 0x30, 0x82, 0x03, 0x42, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x41, 0x43, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+ 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+ 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x47,
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f,
+ 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xa3, 0xcd, 0xc0,
+ 0xdf, 0x33, 0x40, 0x26, 0xeb, 0xde, 0x5a, 0xd7, 0x94, 0x66, 0xd4, 0x01,
+ 0x63, 0xcc, 0x33, 0x44, 0x89, 0xe0, 0xe2, 0xb8, 0xc2, 0x47, 0x0d, 0x8f,
+ 0xad, 0x69, 0x86, 0x1c, 0xa8, 0x73, 0x42, 0x0b, 0xf1, 0x72, 0xfb, 0x2d,
+ 0xac, 0xb5, 0x11, 0x72, 0x83, 0x22, 0xf6, 0x56, 0xe7, 0x2e, 0xc5, 0x67,
+ 0x71, 0x9d, 0x00, 0x1c, 0x32, 0xbc, 0xe3, 0xed, 0x2e, 0x08, 0x45, 0xa9,
+ 0xe6, 0xfa, 0xdd, 0xc8, 0x8c, 0x83, 0x05, 0xc1, 0x6f, 0x4b, 0xd0, 0x26,
+ 0x4a, 0x0b, 0xf6, 0x1b, 0x45, 0xc0, 0x4d, 0x7e, 0x93, 0xbc, 0x0d, 0x27,
+ 0x84, 0xed, 0x30, 0xa3, 0xe9, 0xc6, 0x26, 0x26, 0xdd, 0x2d, 0x1f, 0xd8,
+ 0x8b, 0xc3, 0xce, 0x19, 0xd0, 0x5b, 0xfc, 0x08, 0x9f, 0xe4, 0xd8, 0xe2,
+ 0x35, 0xe4, 0xa0, 0x68, 0xa6, 0xf6, 0x0d, 0xa3, 0x74, 0x60, 0x42, 0xb2,
+ 0x97, 0x82, 0x24, 0x8e, 0x41, 0xa4, 0xf2, 0x2e, 0x5e, 0xb6, 0x8e, 0xa7,
+ 0x6e, 0xd9, 0x6c, 0x7f, 0x0d, 0x3b, 0x24, 0x35, 0x6a, 0xd0, 0xab, 0x5b,
+ 0x6a, 0xf7, 0x97, 0x02, 0x00, 0x3f, 0x51, 0xa6, 0xa7, 0x6e, 0x73, 0xca,
+ 0x77, 0x0d, 0x76, 0x7c, 0x9b, 0xb6, 0x30, 0x1a, 0x1a, 0x9c, 0xf7, 0x1f,
+ 0x28, 0x7b, 0x0e, 0x8b, 0x47, 0x1f, 0xe7, 0x7f, 0x05, 0x8c, 0xc6, 0xc9,
+ 0xc8, 0xbb, 0xcf, 0xe9, 0xdc, 0x7a, 0x41, 0x2e, 0xa1, 0x86, 0xda, 0xd4,
+ 0x39, 0xb2, 0xe2, 0x13, 0x40, 0xa6, 0xa8, 0x3a, 0xfa, 0x0f, 0x53, 0x1e,
+ 0x4f, 0xec, 0x6e, 0x98, 0x09, 0x1b, 0xca, 0x9a, 0x77, 0xb3, 0x55, 0x85,
+ 0x85, 0xe9, 0x2e, 0x16, 0xb5, 0x9d, 0x5e, 0x54, 0xf1, 0x4a, 0x7a, 0x6c,
+ 0x39, 0xba, 0x6e, 0x17, 0x06, 0x34, 0xb3, 0xb2, 0x42, 0xe1, 0xf7, 0xf3,
+ 0x9c, 0x9a, 0x0b, 0x11, 0x44, 0xde, 0x6a, 0x78, 0x8e, 0xb1, 0x13, 0x4f,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x25, 0x30, 0x82, 0x01,
+ 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x96, 0xad, 0xfa, 0xb0, 0x5b, 0xb9, 0x83, 0x64, 0x2a, 0x76, 0xc2, 0x1c,
+ 0x8a, 0x69, 0xda, 0x42, 0xdc, 0xfe, 0xfd, 0x28, 0x30, 0x47, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+ 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d,
+ 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc,
+ 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3a, 0xe7,
+ 0xfc, 0xae, 0xaf, 0x05, 0x43, 0x80, 0x27, 0x75, 0x41, 0x5f, 0xa8, 0xf0,
+ 0x28, 0x8f, 0x1f, 0x8f, 0x83, 0x7e, 0xb2, 0xb8, 0xba, 0xae, 0x75, 0x31,
+ 0x27, 0x88, 0xa5, 0xe5, 0xb9, 0x4e, 0x04, 0x43, 0xd2, 0xad, 0xe8, 0x13,
+ 0x00, 0xa3, 0xdb, 0x19, 0x01, 0x30, 0x9e, 0x6c, 0x3c, 0x52, 0x7f, 0x5c,
+ 0xde, 0xab, 0x67, 0xc3, 0x84, 0x04, 0x54, 0x51, 0x99, 0x9e, 0x63, 0x2f,
+ 0xbd, 0xd5, 0xb7, 0xc0, 0xd5, 0xda, 0x03, 0x0e, 0x49, 0xd3, 0xe1, 0xb3,
+ 0x92, 0x4f, 0xdf, 0x92, 0x4e, 0x7d, 0xae, 0x22, 0x6a, 0xce, 0xd8, 0xbc,
+ 0xfc, 0x7c, 0xae, 0x6b, 0xb6, 0x8a, 0xea, 0x45, 0x62, 0x90, 0x11, 0xd3,
+ 0x0b, 0x71, 0xa7, 0x5e, 0x06, 0x22, 0xff, 0x4d, 0x38, 0xea, 0xb9, 0x3a,
+ 0x6e, 0xcd, 0x67, 0x1a, 0x02, 0x7f, 0x4b, 0xf3, 0xbf, 0x0e, 0x79, 0x6f,
+ 0xbe, 0xd5, 0x29, 0x32, 0x59, 0x59, 0x1d, 0x96, 0x08, 0x9b, 0x70, 0x8f,
+ 0xf7, 0x1e, 0x5c, 0x46, 0x7b, 0x4e, 0xd0, 0x9d, 0xb4, 0x53, 0xc8, 0x12,
+ 0x02, 0x1b, 0x0d, 0xbb, 0x32, 0xeb, 0x59, 0x53, 0xb9, 0x3e, 0x1b, 0x56,
+ 0x8d, 0x15, 0xc8, 0xf1, 0x42, 0x3f, 0x77, 0xfe, 0x1f, 0xe5, 0x6d, 0x9e,
+ 0x66, 0x1f, 0xab, 0xda, 0xb2, 0x83, 0x57, 0xb4, 0x0c, 0x22, 0xd2, 0x86,
+ 0xbc, 0xda, 0x32, 0xd7, 0xc0, 0xed, 0x70, 0x85, 0x7c, 0x93, 0xaa, 0xf0,
+ 0x97, 0xdc, 0x39, 0x11, 0xd2, 0xd8, 0x89, 0xeb, 0x8d, 0x90, 0xa3, 0xb6,
+ 0x50, 0x25, 0xcb, 0x6c, 0xd9, 0xa6, 0xc3, 0x6f, 0xfb, 0x88, 0x54, 0xb8,
+ 0xe4, 0x92, 0x70, 0x87, 0xce, 0x79, 0x3b, 0xf0, 0xde, 0x36, 0xbf, 0x03,
+ 0x04, 0x00, 0x3d, 0xf9, 0xef, 0x9e, 0xa9, 0x67, 0xa4, 0xf4, 0x86, 0x3e,
+ 0x23, 0x97, 0xb8, 0x2a, 0x71, 0xe2, 0xed, 0xfe, 0x69, 0x88, 0x67, 0xbf,
+ 0x26, 0x5c,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:5b:63
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cd:a1:46:cc:52:9a:b8:a5:b4:7f:58:d9:cd:d8:
+ 4b:a0:72:0c:97:5b:a6:88:20:c3:b9:3d:46:dc:d0:
+ 5c:52:30:f6:fa:59:4f:85:5f:b0:db:88:b4:a9:5f:
+ 2b:23:48:ac:ab:f5:92:78:14:b6:32:0f:fb:5c:6a:
+ 85:5b:00:90:e0:bb:65:f5:5a:f9:4f:67:7e:c7:6c:
+ 29:ec:93:c0:2b:ca:c4:5e:d8:b0:db:d6:be:3f:9b:
+ 0b:c0:8f:a9:5d:ae:f7:00:02:a4:fc:ba:66:11:38:
+ 77:fe:23:20:25:55:10:c5:bd:82:b9:4c:b1:68:c6:
+ e2:70:7b:83:5c:13:67:c1:a1:f3:7c:0b:a8:99:9a:
+ d0:e2:9b:25:31:c8:2b:8d:40:f6:52:63:b1:a0:ad:
+ 5a:2e:f5:79:36:6d:35:2c:0e:dd:05:e4:d0:e2:07:
+ 48:b7:28:5e:2b:d5:58:d5:6c:d0:0c:a1:01:46:01:
+ 5a:8f:c6:af:64:c7:55:01:5d:e1:d1:c6:6c:50:25:
+ a0:05:ad:00:ab:0c:8d:65:6b:dd:eb:c2:72:54:c9:
+ 0f:3c:00:17:87:22:ef:db:b9:86:78:16:51:ae:77:
+ d9:a6:28:4d:f3:58:8d:83:67:b9:34:25:9b:1c:51:
+ 80:51:f3:83:92:6a:a3:ae:47:9a:d6:e4:8b:1b:c0:
+ ed:b1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ B0:B0:4A:FD:1C:75:28:F8:1C:61:AA:13:F6:FA:C1:90:3D:6B:16:A3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root-r2.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/ExtendedSSLCA
+
+ X509v3 Authority Key Identifier:
+ keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 5f:28:90:0c:2d:e9:20:b2:30:7c:88:ab:40:05:fa:b1:9d:5c:
+ 22:93:d5:9d:ca:35:31:fa:2c:ea:1d:93:59:19:c4:a0:0d:fb:
+ 09:40:31:da:64:56:cd:52:be:e7:18:66:e8:6d:09:9b:b2:db:
+ 94:3e:ee:36:45:1e:24:54:b6:20:05:93:b5:31:1a:b8:64:57:
+ e6:d3:2c:01:4c:39:96:79:fe:b7:04:98:12:ef:b7:2e:5a:77:
+ fe:47:f3:79:98:42:dd:16:be:5b:69:2b:c9:26:c8:29:68:77:
+ e6:ac:f6:4e:90:13:28:67:04:ec:72:25:1f:d7:a7:0a:50:7f:
+ 38:0e:72:18:b1:29:b8:ff:ae:a1:d4:54:b8:66:4d:a0:d5:cf:
+ d3:ef:a9:32:2a:c5:97:62:d2:84:cc:b0:a0:d8:98:a9:ca:38:
+ e4:cc:44:35:6f:61:26:b0:2e:98:72:f9:38:32:0d:b4:a1:62:
+ 0a:21:62:15:de:bb:6d:93:10:36:53:3b:4a:21:7b:c2:f5:be:
+ 2e:f6:02:13:e9:ae:4c:70:e9:2a:f6:1f:c3:8b:e5:9f:e0:8d:
+ 2a:28:e8:19:2c:b3:65:dd:f7:f1:6f:97:35:9e:db:92:35:63:
+ 81:d7:27:e4:2b:62:aa:fa:62:a1:71:92:8c:0a:16:b7:3d:b5:
+ 4a:65:5b:02
+-----BEGIN CERTIFICATE-----
+MIIEWzCCA0OgAwIBAgILBAAAAAABL07hW2MwDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTEwNDEzMTAwMDAwWhcNMjIwNDEz
+MTAwMDAwWjBZMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1z
+YTEvMC0GA1UEAxMmR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIC0g
+RzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNoUbMUpq4pbR/WNnN
+2EugcgyXW6aIIMO5PUbc0FxSMPb6WU+FX7DbiLSpXysjSKyr9ZJ4FLYyD/tcaoVb
+AJDgu2X1WvlPZ37HbCnsk8ArysRe2LDb1r4/mwvAj6ldrvcAAqT8umYROHf+IyAl
+VRDFvYK5TLFoxuJwe4NcE2fBofN8C6iZmtDimyUxyCuNQPZSY7GgrVou9Xk2bTUs
+Dt0F5NDiB0i3KF4r1VjVbNAMoQFGAVqPxq9kx1UBXeHRxmxQJaAFrQCrDI1la93r
+wnJUyQ88ABeHIu/buYZ4FlGud9mmKE3zWI2DZ7k0JZscUYBR84OSaqOuR5rW5Isb
+wO2xAgMBAAGjggEvMIIBKzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADAdBgNVHQ4EFgQUsLBK/Rx1KPgcYaoT9vrBkD1rFqMwRwYDVR0gBEAwPjA8
+BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t
+L3JlcG9zaXRvcnkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFs
+c2lnbi5uZXQvcm9vdC1yMi5jcmwwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzAB
+hihodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMB8GA1Ud
+IwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMA0GCSqGSIb3DQEBBQUAA4IBAQBf
+KJAMLekgsjB8iKtABfqxnVwik9WdyjUx+izqHZNZGcSgDfsJQDHaZFbNUr7nGGbo
+bQmbstuUPu42RR4kVLYgBZO1MRq4ZFfm0ywBTDmWef63BJgS77cuWnf+R/N5mELd
+Fr5baSvJJsgpaHfmrPZOkBMoZwTsciUf16cKUH84DnIYsSm4/66h1FS4Zk2g1c/T
+76kyKsWXYtKEzLCg2JipyjjkzEQ1b2EmsC6Ycvk4Mg20oWIKIWIV3rttkxA2UztK
+IXvC9b4u9gIT6a5McOkq9h/Di+Wf4I0qKOgZLLNl3ffxb5c1ntuSNWOB1yfkK2Kq
++mKhcZKMCha3PbVKZVsC
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert29[] = {
+ 0x30, 0x82, 0x04, 0x5b, 0x30, 0x82, 0x03, 0x43, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x5b, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33,
+ 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x59, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73,
+ 0x61, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+ 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69,
+ 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+ 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+ 0xa1, 0x46, 0xcc, 0x52, 0x9a, 0xb8, 0xa5, 0xb4, 0x7f, 0x58, 0xd9, 0xcd,
+ 0xd8, 0x4b, 0xa0, 0x72, 0x0c, 0x97, 0x5b, 0xa6, 0x88, 0x20, 0xc3, 0xb9,
+ 0x3d, 0x46, 0xdc, 0xd0, 0x5c, 0x52, 0x30, 0xf6, 0xfa, 0x59, 0x4f, 0x85,
+ 0x5f, 0xb0, 0xdb, 0x88, 0xb4, 0xa9, 0x5f, 0x2b, 0x23, 0x48, 0xac, 0xab,
+ 0xf5, 0x92, 0x78, 0x14, 0xb6, 0x32, 0x0f, 0xfb, 0x5c, 0x6a, 0x85, 0x5b,
+ 0x00, 0x90, 0xe0, 0xbb, 0x65, 0xf5, 0x5a, 0xf9, 0x4f, 0x67, 0x7e, 0xc7,
+ 0x6c, 0x29, 0xec, 0x93, 0xc0, 0x2b, 0xca, 0xc4, 0x5e, 0xd8, 0xb0, 0xdb,
+ 0xd6, 0xbe, 0x3f, 0x9b, 0x0b, 0xc0, 0x8f, 0xa9, 0x5d, 0xae, 0xf7, 0x00,
+ 0x02, 0xa4, 0xfc, 0xba, 0x66, 0x11, 0x38, 0x77, 0xfe, 0x23, 0x20, 0x25,
+ 0x55, 0x10, 0xc5, 0xbd, 0x82, 0xb9, 0x4c, 0xb1, 0x68, 0xc6, 0xe2, 0x70,
+ 0x7b, 0x83, 0x5c, 0x13, 0x67, 0xc1, 0xa1, 0xf3, 0x7c, 0x0b, 0xa8, 0x99,
+ 0x9a, 0xd0, 0xe2, 0x9b, 0x25, 0x31, 0xc8, 0x2b, 0x8d, 0x40, 0xf6, 0x52,
+ 0x63, 0xb1, 0xa0, 0xad, 0x5a, 0x2e, 0xf5, 0x79, 0x36, 0x6d, 0x35, 0x2c,
+ 0x0e, 0xdd, 0x05, 0xe4, 0xd0, 0xe2, 0x07, 0x48, 0xb7, 0x28, 0x5e, 0x2b,
+ 0xd5, 0x58, 0xd5, 0x6c, 0xd0, 0x0c, 0xa1, 0x01, 0x46, 0x01, 0x5a, 0x8f,
+ 0xc6, 0xaf, 0x64, 0xc7, 0x55, 0x01, 0x5d, 0xe1, 0xd1, 0xc6, 0x6c, 0x50,
+ 0x25, 0xa0, 0x05, 0xad, 0x00, 0xab, 0x0c, 0x8d, 0x65, 0x6b, 0xdd, 0xeb,
+ 0xc2, 0x72, 0x54, 0xc9, 0x0f, 0x3c, 0x00, 0x17, 0x87, 0x22, 0xef, 0xdb,
+ 0xb9, 0x86, 0x78, 0x16, 0x51, 0xae, 0x77, 0xd9, 0xa6, 0x28, 0x4d, 0xf3,
+ 0x58, 0x8d, 0x83, 0x67, 0xb9, 0x34, 0x25, 0x9b, 0x1c, 0x51, 0x80, 0x51,
+ 0xf3, 0x83, 0x92, 0x6a, 0xa3, 0xae, 0x47, 0x9a, 0xd6, 0xe4, 0x8b, 0x1b,
+ 0xc0, 0xed, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x2f,
+ 0x30, 0x82, 0x01, 0x2b, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0xb0, 0xb0, 0x4a, 0xfd, 0x1c, 0x75, 0x28, 0xf8, 0x1c,
+ 0x61, 0xaa, 0x13, 0xf6, 0xfa, 0xc1, 0x90, 0x3d, 0x6b, 0x16, 0xa3, 0x30,
+ 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74,
+ 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f,
+ 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30,
+ 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f,
+ 0x74, 0x2d, 0x72, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x44, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x38, 0x30, 0x36,
+ 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+ 0x64, 0x53, 0x53, 0x4c, 0x43, 0x41, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x9b, 0xe2, 0x07, 0x57, 0x67,
+ 0x1c, 0x1e, 0xc0, 0x6a, 0x06, 0xde, 0x59, 0xb4, 0x9a, 0x2d, 0xdf, 0xdc,
+ 0x19, 0x86, 0x2e, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5f,
+ 0x28, 0x90, 0x0c, 0x2d, 0xe9, 0x20, 0xb2, 0x30, 0x7c, 0x88, 0xab, 0x40,
+ 0x05, 0xfa, 0xb1, 0x9d, 0x5c, 0x22, 0x93, 0xd5, 0x9d, 0xca, 0x35, 0x31,
+ 0xfa, 0x2c, 0xea, 0x1d, 0x93, 0x59, 0x19, 0xc4, 0xa0, 0x0d, 0xfb, 0x09,
+ 0x40, 0x31, 0xda, 0x64, 0x56, 0xcd, 0x52, 0xbe, 0xe7, 0x18, 0x66, 0xe8,
+ 0x6d, 0x09, 0x9b, 0xb2, 0xdb, 0x94, 0x3e, 0xee, 0x36, 0x45, 0x1e, 0x24,
+ 0x54, 0xb6, 0x20, 0x05, 0x93, 0xb5, 0x31, 0x1a, 0xb8, 0x64, 0x57, 0xe6,
+ 0xd3, 0x2c, 0x01, 0x4c, 0x39, 0x96, 0x79, 0xfe, 0xb7, 0x04, 0x98, 0x12,
+ 0xef, 0xb7, 0x2e, 0x5a, 0x77, 0xfe, 0x47, 0xf3, 0x79, 0x98, 0x42, 0xdd,
+ 0x16, 0xbe, 0x5b, 0x69, 0x2b, 0xc9, 0x26, 0xc8, 0x29, 0x68, 0x77, 0xe6,
+ 0xac, 0xf6, 0x4e, 0x90, 0x13, 0x28, 0x67, 0x04, 0xec, 0x72, 0x25, 0x1f,
+ 0xd7, 0xa7, 0x0a, 0x50, 0x7f, 0x38, 0x0e, 0x72, 0x18, 0xb1, 0x29, 0xb8,
+ 0xff, 0xae, 0xa1, 0xd4, 0x54, 0xb8, 0x66, 0x4d, 0xa0, 0xd5, 0xcf, 0xd3,
+ 0xef, 0xa9, 0x32, 0x2a, 0xc5, 0x97, 0x62, 0xd2, 0x84, 0xcc, 0xb0, 0xa0,
+ 0xd8, 0x98, 0xa9, 0xca, 0x38, 0xe4, 0xcc, 0x44, 0x35, 0x6f, 0x61, 0x26,
+ 0xb0, 0x2e, 0x98, 0x72, 0xf9, 0x38, 0x32, 0x0d, 0xb4, 0xa1, 0x62, 0x0a,
+ 0x21, 0x62, 0x15, 0xde, 0xbb, 0x6d, 0x93, 0x10, 0x36, 0x53, 0x3b, 0x4a,
+ 0x21, 0x7b, 0xc2, 0xf5, 0xbe, 0x2e, 0xf6, 0x02, 0x13, 0xe9, 0xae, 0x4c,
+ 0x70, 0xe9, 0x2a, 0xf6, 0x1f, 0xc3, 0x8b, 0xe5, 0x9f, 0xe0, 0x8d, 0x2a,
+ 0x28, 0xe8, 0x19, 0x2c, 0xb3, 0x65, 0xdd, 0xf7, 0xf1, 0x6f, 0x97, 0x35,
+ 0x9e, 0xdb, 0x92, 0x35, 0x63, 0x81, 0xd7, 0x27, 0xe4, 0x2b, 0x62, 0xaa,
+ 0xfa, 0x62, 0xa1, 0x71, 0x92, 0x8c, 0x0a, 0x16, 0xb7, 0x3d, 0xb5, 0x4a,
+ 0x65, 0x5b, 0x02,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120013506 (0x72742c2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: Nov 30 16:24:37 2010 GMT
+ Not After : Nov 30 16:23:46 2017 GMT
+ Subject: C=DE, O=T-Systems International GmbH, OU=Trust Center Services, CN=TeleSec ServerPass CA 1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e6:e8:cd:30:21:8d:b9:8f:ee:f7:54:57:ab:5c:
+ da:9d:70:d0:20:7a:a6:89:f8:e6:21:7d:97:1b:69:
+ 00:83:1d:4f:ba:d2:98:6d:0b:c7:ed:8f:26:01:a6:
+ 66:1c:48:f5:d5:d3:db:0a:ad:bf:77:44:7e:06:33:
+ 90:f7:e7:28:f5:82:c3:f0:43:49:76:ed:73:77:5d:
+ 83:e9:6b:18:01:ea:ee:4f:1d:b2:49:e0:32:02:d1:
+ 25:36:dc:6f:28:64:bf:89:34:83:0f:ea:03:56:df:
+ b6:57:93:bf:c6:56:f7:f0:dd:50:ac:b6:4f:4d:2a:
+ 53:11:f1:d3:24:2f:11:39:0b:05:57:1f:de:3d:71:
+ 1a:2f:b4:9c:f5:ab:e3:d2:d1:d1:29:c0:36:91:21:
+ 61:e7:c5:d0:f6:40:da:e9:f1:4c:96:6c:d7:c9:13:
+ 09:a5:22:5d:06:f7:0c:3d:65:a3:fc:52:b9:7e:72:
+ bd:33:7b:dd:7c:ae:7a:2b:c1:4f:aa:fc:c8:f8:c5:
+ 9d:25:86:53:55:74:bc:1e:ca:42:4a:33:e2:12:2d:
+ dc:d2:29:d9:7b:ad:3a:40:ec:01:d1:05:ec:8a:9c:
+ 23:ea:86:30:be:f3:e6:e9:08:cc:9e:b4:40:8c:af:
+ 02:e7:7e:3f:7e:e2:c1:08:02:d7:23:29:63:7b:eb:
+ 82:55
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+ Policy: 1.3.6.1.4.1.7879.13.2
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+ X509v3 Subject Key Identifier:
+ 33:DC:9E:96:EC:D8:E8:35:1F:6D:90:1B:0B:38:A4:AF:74:1B:C6:58
+ Signature Algorithm: sha1WithRSAEncryption
+ 74:76:a8:19:77:97:5c:af:5b:8f:e5:e5:ec:dd:59:bb:90:e6:
+ 52:4c:1e:c4:fe:da:34:e6:2a:fc:3c:dd:b0:7b:69:57:82:c5:
+ 24:26:0c:74:1e:d9:a0:7d:7e:8b:fa:76:ab:a1:7d:58:ae:34:
+ 12:fb:99:24:14:94:31:15:62:d7:d9:5c:79:33:1c:e2:1b:91:
+ 13:fd:f8:cf:1c:ff:2c:cc:b5:ed:27:8e:b9:97:30:b1:de:e9:
+ e6:d2:10:e3:a6:d7:9a:f0:39:1a:4e:3b:ae:e2:0b:b2:ea:0d:
+ 80:61:31:33:cc:73:f1:d3:0c:e9:31:26:9e:78:d9:08:67:93:
+ 71:e8:b6:f1:a3:66:fd:00:46:0c:7f:da:c3:fc:63:d8:c5:3f:
+ c8:23:70:b9:a8:60:c2:5e:47:a8:8d:19:89:63:a7:a0:69:07:
+ 9c:27:54:22:2c:4e:c7:3d:99:3c:b9:fc:93:80:65:bd:bb:d0:
+ f8:ea:8c:fb:71:e1:e8:a6:07:40:f1:d0:05:85:da:38:71:59:
+ f4:98:b7:64:d3:76:10:f5:b9:6a:d7:15:0e:58:fe:e6:6b:29:
+ 4a:ee:75:88:9f:5f:66:80:07:6b:6b:55:68:bc:66:39:c5:9f:
+ 57:fe:35:f5:89:0f:07:57:fa:d1:57:ef:4d:b2:d7:77:c8:bc:
+ f8:65:0f:9e
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIEBydCwjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEwMTEzMDE2MjQzN1oX
+DTE3MTEzMDE2MjM0NlowdjELMAkGA1UEBhMCREUxJTAjBgNVBAoTHFQtU3lzdGVt
+cyBJbnRlcm5hdGlvbmFsIEdtYkgxHjAcBgNVBAsTFVRydXN0IENlbnRlciBTZXJ2
+aWNlczEgMB4GA1UEAxMXVGVsZVNlYyBTZXJ2ZXJQYXNzIENBIDEwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDm6M0wIY25j+73VFerXNqdcNAgeqaJ+OYh
+fZcbaQCDHU+60phtC8ftjyYBpmYcSPXV09sKrb93RH4GM5D35yj1gsPwQ0l27XN3
+XYPpaxgB6u5PHbJJ4DIC0SU23G8oZL+JNIMP6gNW37ZXk7/GVvfw3VCstk9NKlMR
+8dMkLxE5CwVXH949cRovtJz1q+PS0dEpwDaRIWHnxdD2QNrp8UyWbNfJEwmlIl0G
+9ww9ZaP8Url+cr0ze918rnorwU+q/Mj4xZ0lhlNVdLweykJKM+ISLdzSKdl7rTpA
+7AHRBeyKnCPqhjC+8+bpCMyetECMrwLnfj9+4sEIAtcjKWN764JVAgMBAAGjggEO
+MIIBCjASBgNVHRMBAf8ECDAGAQH/AgEAMGAGA1UdIARZMFcwSAYJKwYBBAGxPgEA
+MDswOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9tbmlyb290LmNvbS9y
+ZXBvc2l0b3J5LmNmbTALBgkrBgEEAb1HDQIwDgYDVR0PAQH/BAQDAgEGMB8GA1Ud
+IwQYMBaAFOWdWTCCR1jMrPoIVDaGezq1BE3wMEIGA1UdHwQ7MDkwN6A1oDOGMWh0
+dHA6Ly9jZHAxLnB1YmxpYy10cnVzdC5jb20vQ1JML09tbmlyb290MjAyNS5jcmww
+HQYDVR0OBBYEFDPcnpbs2Og1H22QGws4pK90G8ZYMA0GCSqGSIb3DQEBBQUAA4IB
+AQB0dqgZd5dcr1uP5eXs3Vm7kOZSTB7E/to05ir8PN2we2lXgsUkJgx0HtmgfX6L
++naroX1YrjQS+5kkFJQxFWLX2Vx5MxziG5ET/fjPHP8szLXtJ465lzCx3unm0hDj
+ptea8DkaTjuu4guy6g2AYTEzzHPx0wzpMSaeeNkIZ5Nx6Lbxo2b9AEYMf9rD/GPY
+xT/II3C5qGDCXkeojRmJY6egaQecJ1QiLE7HPZk8ufyTgGW9u9D46oz7ceHopgdA
+8dAFhdo4cVn0mLdk03YQ9blq1xUOWP7maylK7nWIn19mgAdra1VovGY5xZ9X/jX1
+iQ8HV/rRV+9Nstd3yLz4ZQ+e
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert30[] = {
+ 0x30, 0x82, 0x04, 0x5e, 0x30, 0x82, 0x03, 0x46, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x42, 0xc2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+ 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+ 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30,
+ 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x32, 0x34, 0x33, 0x37, 0x5a, 0x17,
+ 0x0d, 0x31, 0x37, 0x31, 0x31, 0x33, 0x30, 0x31, 0x36, 0x32, 0x33, 0x34,
+ 0x36, 0x5a, 0x30, 0x76, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x44, 0x45, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x1c, 0x54, 0x2d, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d,
+ 0x73, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x61, 0x6c, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x31, 0x1e, 0x30, 0x1c,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x15, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x20, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20, 0x53, 0x65, 0x72, 0x76,
+ 0x69, 0x63, 0x65, 0x73, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x17, 0x54, 0x65, 0x6c, 0x65, 0x53, 0x65, 0x63, 0x20, 0x53,
+ 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x61, 0x73, 0x73, 0x20, 0x43, 0x41,
+ 0x20, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe6,
+ 0xe8, 0xcd, 0x30, 0x21, 0x8d, 0xb9, 0x8f, 0xee, 0xf7, 0x54, 0x57, 0xab,
+ 0x5c, 0xda, 0x9d, 0x70, 0xd0, 0x20, 0x7a, 0xa6, 0x89, 0xf8, 0xe6, 0x21,
+ 0x7d, 0x97, 0x1b, 0x69, 0x00, 0x83, 0x1d, 0x4f, 0xba, 0xd2, 0x98, 0x6d,
+ 0x0b, 0xc7, 0xed, 0x8f, 0x26, 0x01, 0xa6, 0x66, 0x1c, 0x48, 0xf5, 0xd5,
+ 0xd3, 0xdb, 0x0a, 0xad, 0xbf, 0x77, 0x44, 0x7e, 0x06, 0x33, 0x90, 0xf7,
+ 0xe7, 0x28, 0xf5, 0x82, 0xc3, 0xf0, 0x43, 0x49, 0x76, 0xed, 0x73, 0x77,
+ 0x5d, 0x83, 0xe9, 0x6b, 0x18, 0x01, 0xea, 0xee, 0x4f, 0x1d, 0xb2, 0x49,
+ 0xe0, 0x32, 0x02, 0xd1, 0x25, 0x36, 0xdc, 0x6f, 0x28, 0x64, 0xbf, 0x89,
+ 0x34, 0x83, 0x0f, 0xea, 0x03, 0x56, 0xdf, 0xb6, 0x57, 0x93, 0xbf, 0xc6,
+ 0x56, 0xf7, 0xf0, 0xdd, 0x50, 0xac, 0xb6, 0x4f, 0x4d, 0x2a, 0x53, 0x11,
+ 0xf1, 0xd3, 0x24, 0x2f, 0x11, 0x39, 0x0b, 0x05, 0x57, 0x1f, 0xde, 0x3d,
+ 0x71, 0x1a, 0x2f, 0xb4, 0x9c, 0xf5, 0xab, 0xe3, 0xd2, 0xd1, 0xd1, 0x29,
+ 0xc0, 0x36, 0x91, 0x21, 0x61, 0xe7, 0xc5, 0xd0, 0xf6, 0x40, 0xda, 0xe9,
+ 0xf1, 0x4c, 0x96, 0x6c, 0xd7, 0xc9, 0x13, 0x09, 0xa5, 0x22, 0x5d, 0x06,
+ 0xf7, 0x0c, 0x3d, 0x65, 0xa3, 0xfc, 0x52, 0xb9, 0x7e, 0x72, 0xbd, 0x33,
+ 0x7b, 0xdd, 0x7c, 0xae, 0x7a, 0x2b, 0xc1, 0x4f, 0xaa, 0xfc, 0xc8, 0xf8,
+ 0xc5, 0x9d, 0x25, 0x86, 0x53, 0x55, 0x74, 0xbc, 0x1e, 0xca, 0x42, 0x4a,
+ 0x33, 0xe2, 0x12, 0x2d, 0xdc, 0xd2, 0x29, 0xd9, 0x7b, 0xad, 0x3a, 0x40,
+ 0xec, 0x01, 0xd1, 0x05, 0xec, 0x8a, 0x9c, 0x23, 0xea, 0x86, 0x30, 0xbe,
+ 0xf3, 0xe6, 0xe9, 0x08, 0xcc, 0x9e, 0xb4, 0x40, 0x8c, 0xaf, 0x02, 0xe7,
+ 0x7e, 0x3f, 0x7e, 0xe2, 0xc1, 0x08, 0x02, 0xd7, 0x23, 0x29, 0x63, 0x7b,
+ 0xeb, 0x82, 0x55, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0e,
+ 0x30, 0x82, 0x01, 0x0a, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x60, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x59, 0x30, 0x57, 0x30,
+ 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+ 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d,
+ 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66,
+ 0x6d, 0x30, 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xbd, 0x47,
+ 0x0d, 0x02, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82,
+ 0x47, 0x58, 0xcc, 0xac, 0xfa, 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5,
+ 0x04, 0x4d, 0xf0, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b,
+ 0x30, 0x39, 0x30, 0x37, 0xa0, 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75,
+ 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72,
+ 0x6f, 0x6f, 0x74, 0x32, 0x30, 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x33, 0xdc,
+ 0x9e, 0x96, 0xec, 0xd8, 0xe8, 0x35, 0x1f, 0x6d, 0x90, 0x1b, 0x0b, 0x38,
+ 0xa4, 0xaf, 0x74, 0x1b, 0xc6, 0x58, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x74, 0x76, 0xa8, 0x19, 0x77, 0x97, 0x5c, 0xaf, 0x5b, 0x8f,
+ 0xe5, 0xe5, 0xec, 0xdd, 0x59, 0xbb, 0x90, 0xe6, 0x52, 0x4c, 0x1e, 0xc4,
+ 0xfe, 0xda, 0x34, 0xe6, 0x2a, 0xfc, 0x3c, 0xdd, 0xb0, 0x7b, 0x69, 0x57,
+ 0x82, 0xc5, 0x24, 0x26, 0x0c, 0x74, 0x1e, 0xd9, 0xa0, 0x7d, 0x7e, 0x8b,
+ 0xfa, 0x76, 0xab, 0xa1, 0x7d, 0x58, 0xae, 0x34, 0x12, 0xfb, 0x99, 0x24,
+ 0x14, 0x94, 0x31, 0x15, 0x62, 0xd7, 0xd9, 0x5c, 0x79, 0x33, 0x1c, 0xe2,
+ 0x1b, 0x91, 0x13, 0xfd, 0xf8, 0xcf, 0x1c, 0xff, 0x2c, 0xcc, 0xb5, 0xed,
+ 0x27, 0x8e, 0xb9, 0x97, 0x30, 0xb1, 0xde, 0xe9, 0xe6, 0xd2, 0x10, 0xe3,
+ 0xa6, 0xd7, 0x9a, 0xf0, 0x39, 0x1a, 0x4e, 0x3b, 0xae, 0xe2, 0x0b, 0xb2,
+ 0xea, 0x0d, 0x80, 0x61, 0x31, 0x33, 0xcc, 0x73, 0xf1, 0xd3, 0x0c, 0xe9,
+ 0x31, 0x26, 0x9e, 0x78, 0xd9, 0x08, 0x67, 0x93, 0x71, 0xe8, 0xb6, 0xf1,
+ 0xa3, 0x66, 0xfd, 0x00, 0x46, 0x0c, 0x7f, 0xda, 0xc3, 0xfc, 0x63, 0xd8,
+ 0xc5, 0x3f, 0xc8, 0x23, 0x70, 0xb9, 0xa8, 0x60, 0xc2, 0x5e, 0x47, 0xa8,
+ 0x8d, 0x19, 0x89, 0x63, 0xa7, 0xa0, 0x69, 0x07, 0x9c, 0x27, 0x54, 0x22,
+ 0x2c, 0x4e, 0xc7, 0x3d, 0x99, 0x3c, 0xb9, 0xfc, 0x93, 0x80, 0x65, 0xbd,
+ 0xbb, 0xd0, 0xf8, 0xea, 0x8c, 0xfb, 0x71, 0xe1, 0xe8, 0xa6, 0x07, 0x40,
+ 0xf1, 0xd0, 0x05, 0x85, 0xda, 0x38, 0x71, 0x59, 0xf4, 0x98, 0xb7, 0x64,
+ 0xd3, 0x76, 0x10, 0xf5, 0xb9, 0x6a, 0xd7, 0x15, 0x0e, 0x58, 0xfe, 0xe6,
+ 0x6b, 0x29, 0x4a, 0xee, 0x75, 0x88, 0x9f, 0x5f, 0x66, 0x80, 0x07, 0x6b,
+ 0x6b, 0x55, 0x68, 0xbc, 0x66, 0x39, 0xc5, 0x9f, 0x57, 0xfe, 0x35, 0xf5,
+ 0x89, 0x0f, 0x07, 0x57, 0xfa, 0xd1, 0x57, 0xef, 0x4d, 0xb2, 0xd7, 0x77,
+ 0xc8, 0xbc, 0xf8, 0x65, 0x0f, 0x9e,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:45:0c
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:dd:35:1d:f2:20:54:26:1a:d0:ef:a5:6f:81:76:
+ 59:70:dc:e7:f4:d4:03:24:1f:24:0e:9d:22:9f:d4:
+ 27:32:7a:2b:7c:ee:8b:e3:61:62:38:17:af:b4:4b:
+ 7a:9f:67:21:1c:2d:95:54:ba:79:ba:b6:c4:f2:0d:
+ 21:74:17:67:74:e2:b1:64:08:99:60:78:fb:67:c2:
+ 4b:f7:27:8d:6f:36:76:cf:31:8c:e5:f1:06:d7:dc:
+ 57:0e:5b:ac:ee:ce:2d:ab:aa:a9:70:2f:02:86:c8:
+ b1:d0:08:07:95:ea:2a:ec:d1:9e:e4:36:5c:3b:a6:
+ 36:b5:43:8b:ab:f7:8e:3e:00:1b:ff:85:59:6b:62:
+ 01:8d:82:e8:4a:ba:38:b3:e0:c3:f4:6d:19:a7:ea:
+ 05:dd:84:67:c2:66:c7:24:02:73:5a:b5:ee:a4:19:
+ d9:fc:00:ce:b6:a4:8d:df:7e:bd:5f:b2:3a:9d:84:
+ 31:4f:c8:63:0c:e4:d8:0d:52:a3:7e:01:1b:d4:67:
+ a5:18:28:eb:01:a7:82:3c:d9:8e:1d:e5:47:0d:ba:
+ 8b:59:14:a3:1f:1f:4b:ea:e2:27:46:86:ce:9d:39:
+ c4:66:41:a7:e2:15:23:6b:56:47:c1:ed:c5:53:e4:
+ d4:80:1f:6b:fa:80:46:98:b2:09:a6:0f:95:be:66:
+ 88:93
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 5D:46:B2:8D:C4:4B:74:1C:BB:ED:F5:73:B6:3A:B7:38:8F:75:9E:7E
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 1b:e0:88:00:c7:05:11:1c:ff:ab:2d:48:42:52:cd:20:68:e7:
+ 7c:41:8d:c1:27:c5:2c:59:67:a0:9a:35:db:b3:50:a7:1b:62:
+ e9:a5:3b:fa:b6:21:07:a7:7c:f3:0e:2b:6e:7e:2e:4d:93:9c:
+ ba:f2:86:3e:63:88:10:d8:5b:61:50:12:db:87:ae:19:bb:d2:
+ df:32:96:00:a8:5e:dc:2d:23:bc:b0:d3:b5:4a:a0:8e:65:91:
+ 2f:d9:f6:82:f6:74:b2:df:7c:26:ef:19:2b:97:2f:e0:a1:ee:
+ b9:17:22:48:3f:a5:f7:0d:60:d5:0d:51:47:e5:58:fe:b7:9f:
+ 8d:5e:75:3c:c6:41:f0:cf:81:54:49:11:c6:17:a4:e0:56:61:
+ dc:3d:3f:dd:67:6c:76:45:da:4a:ea:ae:1a:a4:60:4f:c7:a3:
+ d6:aa:a7:d9:cd:81:2b:c1:66:75:b2:80:8f:f5:87:4d:5f:c2:
+ 5a:f5:90:c6:da:c1:bd:f4:85:a8:3c:23:2a:e1:14:7b:c1:37:
+ dd:62:d1:92:6c:ba:60:7d:88:e4:1c:b7:e4:76:51:38:c4:a9:
+ 47:4e:a8:2b:2e:90:d2:b5:38:51:eb:c1:9c:8a:6a:b5:cc:b2:
+ 1d:e8:c0:56:54:4c:a8:8b:f0:89:32:86:dc:93:32:be:4d:1a:
+ fa:35:75:b5
+-----BEGIN CERTIFICATE-----
+MIIEYDCCA0igAwIBAgILBAAAAAABL07hRQwwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMTMwMQYDVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBW
+YWxpZGF0aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDdNR3yIFQmGtDvpW+Bdllw3Of01AMkHyQOnSKf1Ccyeit87ovjYWI4F6+0S3qf
+ZyEcLZVUunm6tsTyDSF0F2d04rFkCJlgePtnwkv3J41vNnbPMYzl8QbX3FcOW6zu
+zi2rqqlwLwKGyLHQCAeV6irs0Z7kNlw7pja1Q4ur944+ABv/hVlrYgGNguhKujiz
+4MP0bRmn6gXdhGfCZsckAnNate6kGdn8AM62pI3ffr1fsjqdhDFPyGMM5NgNUqN+
+ARvUZ6UYKOsBp4I82Y4d5UcNuotZFKMfH0vq4idGhs6dOcRmQafiFSNrVkfB7cVT
+5NSAH2v6gEaYsgmmD5W+ZoiTAgMBAAGjggElMIIBITAOBgNVHQ8BAf8EBAMCAQYw
+EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUXUayjcRLdBy77fVztjq3OI91
+nn4wRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3
+Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0
+dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAv
+MC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEw
+HwYDVR0jBBgwFoAUYHtmGkUNl8qJUC99BM00qP/8/UswDQYJKoZIhvcNAQEFBQAD
+ggEBABvgiADHBREc/6stSEJSzSBo53xBjcEnxSxZZ6CaNduzUKcbYumlO/q2IQen
+fPMOK25+Lk2TnLryhj5jiBDYW2FQEtuHrhm70t8ylgCoXtwtI7yw07VKoI5lkS/Z
+9oL2dLLffCbvGSuXL+Ch7rkXIkg/pfcNYNUNUUflWP63n41edTzGQfDPgVRJEcYX
+pOBWYdw9P91nbHZF2krqrhqkYE/Ho9aqp9nNgSvBZnWygI/1h01fwlr1kMbawb30
+hag8IyrhFHvBN91i0ZJsumB9iOQct+R2UTjEqUdOqCsukNK1OFHrwZyKarXMsh3o
+wFZUTKiL8IkyhtyTMr5NGvo1dbU=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert31[] = {
+ 0x30, 0x82, 0x04, 0x60, 0x30, 0x82, 0x03, 0x48, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x45, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x5d, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+ 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+ 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x47,
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72,
+ 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41,
+ 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xdd, 0x35, 0x1d, 0xf2, 0x20, 0x54, 0x26, 0x1a, 0xd0, 0xef,
+ 0xa5, 0x6f, 0x81, 0x76, 0x59, 0x70, 0xdc, 0xe7, 0xf4, 0xd4, 0x03, 0x24,
+ 0x1f, 0x24, 0x0e, 0x9d, 0x22, 0x9f, 0xd4, 0x27, 0x32, 0x7a, 0x2b, 0x7c,
+ 0xee, 0x8b, 0xe3, 0x61, 0x62, 0x38, 0x17, 0xaf, 0xb4, 0x4b, 0x7a, 0x9f,
+ 0x67, 0x21, 0x1c, 0x2d, 0x95, 0x54, 0xba, 0x79, 0xba, 0xb6, 0xc4, 0xf2,
+ 0x0d, 0x21, 0x74, 0x17, 0x67, 0x74, 0xe2, 0xb1, 0x64, 0x08, 0x99, 0x60,
+ 0x78, 0xfb, 0x67, 0xc2, 0x4b, 0xf7, 0x27, 0x8d, 0x6f, 0x36, 0x76, 0xcf,
+ 0x31, 0x8c, 0xe5, 0xf1, 0x06, 0xd7, 0xdc, 0x57, 0x0e, 0x5b, 0xac, 0xee,
+ 0xce, 0x2d, 0xab, 0xaa, 0xa9, 0x70, 0x2f, 0x02, 0x86, 0xc8, 0xb1, 0xd0,
+ 0x08, 0x07, 0x95, 0xea, 0x2a, 0xec, 0xd1, 0x9e, 0xe4, 0x36, 0x5c, 0x3b,
+ 0xa6, 0x36, 0xb5, 0x43, 0x8b, 0xab, 0xf7, 0x8e, 0x3e, 0x00, 0x1b, 0xff,
+ 0x85, 0x59, 0x6b, 0x62, 0x01, 0x8d, 0x82, 0xe8, 0x4a, 0xba, 0x38, 0xb3,
+ 0xe0, 0xc3, 0xf4, 0x6d, 0x19, 0xa7, 0xea, 0x05, 0xdd, 0x84, 0x67, 0xc2,
+ 0x66, 0xc7, 0x24, 0x02, 0x73, 0x5a, 0xb5, 0xee, 0xa4, 0x19, 0xd9, 0xfc,
+ 0x00, 0xce, 0xb6, 0xa4, 0x8d, 0xdf, 0x7e, 0xbd, 0x5f, 0xb2, 0x3a, 0x9d,
+ 0x84, 0x31, 0x4f, 0xc8, 0x63, 0x0c, 0xe4, 0xd8, 0x0d, 0x52, 0xa3, 0x7e,
+ 0x01, 0x1b, 0xd4, 0x67, 0xa5, 0x18, 0x28, 0xeb, 0x01, 0xa7, 0x82, 0x3c,
+ 0xd9, 0x8e, 0x1d, 0xe5, 0x47, 0x0d, 0xba, 0x8b, 0x59, 0x14, 0xa3, 0x1f,
+ 0x1f, 0x4b, 0xea, 0xe2, 0x27, 0x46, 0x86, 0xce, 0x9d, 0x39, 0xc4, 0x66,
+ 0x41, 0xa7, 0xe2, 0x15, 0x23, 0x6b, 0x56, 0x47, 0xc1, 0xed, 0xc5, 0x53,
+ 0xe4, 0xd4, 0x80, 0x1f, 0x6b, 0xfa, 0x80, 0x46, 0x98, 0xb2, 0x09, 0xa6,
+ 0x0f, 0x95, 0xbe, 0x66, 0x88, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x25, 0x30, 0x82, 0x01, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0x46, 0xb2, 0x8d, 0xc4, 0x4b,
+ 0x74, 0x1c, 0xbb, 0xed, 0xf5, 0x73, 0xb6, 0x3a, 0xb7, 0x38, 0x8f, 0x75,
+ 0x9e, 0x7e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30,
+ 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30,
+ 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+ 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+ 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+ 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d,
+ 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x1b, 0xe0, 0x88, 0x00, 0xc7, 0x05, 0x11, 0x1c,
+ 0xff, 0xab, 0x2d, 0x48, 0x42, 0x52, 0xcd, 0x20, 0x68, 0xe7, 0x7c, 0x41,
+ 0x8d, 0xc1, 0x27, 0xc5, 0x2c, 0x59, 0x67, 0xa0, 0x9a, 0x35, 0xdb, 0xb3,
+ 0x50, 0xa7, 0x1b, 0x62, 0xe9, 0xa5, 0x3b, 0xfa, 0xb6, 0x21, 0x07, 0xa7,
+ 0x7c, 0xf3, 0x0e, 0x2b, 0x6e, 0x7e, 0x2e, 0x4d, 0x93, 0x9c, 0xba, 0xf2,
+ 0x86, 0x3e, 0x63, 0x88, 0x10, 0xd8, 0x5b, 0x61, 0x50, 0x12, 0xdb, 0x87,
+ 0xae, 0x19, 0xbb, 0xd2, 0xdf, 0x32, 0x96, 0x00, 0xa8, 0x5e, 0xdc, 0x2d,
+ 0x23, 0xbc, 0xb0, 0xd3, 0xb5, 0x4a, 0xa0, 0x8e, 0x65, 0x91, 0x2f, 0xd9,
+ 0xf6, 0x82, 0xf6, 0x74, 0xb2, 0xdf, 0x7c, 0x26, 0xef, 0x19, 0x2b, 0x97,
+ 0x2f, 0xe0, 0xa1, 0xee, 0xb9, 0x17, 0x22, 0x48, 0x3f, 0xa5, 0xf7, 0x0d,
+ 0x60, 0xd5, 0x0d, 0x51, 0x47, 0xe5, 0x58, 0xfe, 0xb7, 0x9f, 0x8d, 0x5e,
+ 0x75, 0x3c, 0xc6, 0x41, 0xf0, 0xcf, 0x81, 0x54, 0x49, 0x11, 0xc6, 0x17,
+ 0xa4, 0xe0, 0x56, 0x61, 0xdc, 0x3d, 0x3f, 0xdd, 0x67, 0x6c, 0x76, 0x45,
+ 0xda, 0x4a, 0xea, 0xae, 0x1a, 0xa4, 0x60, 0x4f, 0xc7, 0xa3, 0xd6, 0xaa,
+ 0xa7, 0xd9, 0xcd, 0x81, 0x2b, 0xc1, 0x66, 0x75, 0xb2, 0x80, 0x8f, 0xf5,
+ 0x87, 0x4d, 0x5f, 0xc2, 0x5a, 0xf5, 0x90, 0xc6, 0xda, 0xc1, 0xbd, 0xf4,
+ 0x85, 0xa8, 0x3c, 0x23, 0x2a, 0xe1, 0x14, 0x7b, 0xc1, 0x37, 0xdd, 0x62,
+ 0xd1, 0x92, 0x6c, 0xba, 0x60, 0x7d, 0x88, 0xe4, 0x1c, 0xb7, 0xe4, 0x76,
+ 0x51, 0x38, 0xc4, 0xa9, 0x47, 0x4e, 0xa8, 0x2b, 0x2e, 0x90, 0xd2, 0xb5,
+ 0x38, 0x51, 0xeb, 0xc1, 0x9c, 0x8a, 0x6a, 0xb5, 0xcc, 0xb2, 0x1d, 0xe8,
+ 0xc0, 0x56, 0x54, 0x4c, 0xa8, 0x8b, 0xf0, 0x89, 0x32, 0x86, 0xdc, 0x93,
+ 0x32, 0xbe, 0x4d, 0x1a, 0xfa, 0x35, 0x75, 0xb5,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:1e:44:a5:f5:2a
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 11 12:00:00 2007 GMT
+ Not After : Apr 11 12:00:00 2017 GMT
+ Subject: OU=Organization Validation CA, O=GlobalSign, CN=GlobalSign Organization Validation CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a1:2f:c4:bc:ce:87:03:e9:67:c1:89:c8:e5:93:
+ fc:7d:b4:ad:9e:f6:63:4e:6a:e8:9c:2c:73:89:a2:
+ 01:f4:8f:21:f8:fd:25:9d:58:16:6d:86:f6:ee:49:
+ 57:75:7e:75:ea:22:11:7e:3d:fb:c7:42:41:dc:fc:
+ c5:0c:91:55:80:7b:eb:64:33:1d:9b:f9:ca:38:e9:
+ ab:c6:25:43:51:25:40:f4:e4:7e:18:55:6a:a9:8f:
+ 10:3a:40:1e:d6:57:83:ef:7f:2f:34:2f:2d:d2:f6:
+ 53:c2:19:0d:b7:ed:c9:81:f5:46:2c:b4:23:42:5e:
+ 9d:13:03:75:ec:ea:6a:fc:57:7c:c9:36:97:3b:98:
+ dc:13:13:ec:ec:41:fa:5d:34:ea:b9:93:e7:10:16:
+ 65:cc:9c:92:fd:f5:c5:9d:3e:4a:b9:09:fc:e4:5f:
+ 1e:69:5f:4d:f4:56:72:44:b1:1d:23:03:c8:36:f6:
+ 65:88:c8:bf:39:16:45:8e:1e:26:6c:51:16:c5:2a:
+ 00:38:c5:a4:13:69:95:7d:ab:01:3b:a8:c4:14:b4:
+ 80:da:ac:1a:44:20:d5:fe:a9:06:7b:14:27:af:e0:
+ 30:21:dd:90:f4:a9:d5:23:19:2e:1e:03:e6:c1:df:
+ 95:29:e4:c1:94:43:dd:3e:90:aa:cb:4b:c9:be:8a:
+ d3:39
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 7D:6D:2A:EC:66:AB:A7:51:36:AB:02:69:F1:70:8F:C4:59:0B:9A:1F
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.4146.1.20
+ CPS: http://www.globalsign.net/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Netscape Cert Type:
+ SSL CA
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 79:47:fc:15:d7:4c:79:df:0f:7a:9e:ce:d4:7c:4b:63:c9:89:
+ b5:7b:3f:99:12:e8:9c:8c:9a:49:2f:e0:4e:95:4a:ed:c7:bc:
+ be:f1:a2:db:8e:93:1d:ba:71:54:aa:4b:d9:89:22:24:87:c5:
+ 04:a8:ac:82:52:a0:52:f8:b8:e1:4f:a1:27:66:63:21:4a:39:
+ e7:c7:c5:4e:5f:b2:d6:1d:13:6d:30:e9:ce:d7:a2:1c:bc:29:
+ 0a:73:3c:5b:23:49:fe:d6:ff:ca:b0:4f:f5:f2:67:98:c0:47:
+ 11:f8:b7:48:a6:90:09:d6:42:be:ea:b1:b9:53:42:c3:9c:20:
+ c9:fb:a1:5b:b5:56:6d:87:81:c8:60:ac:c4:b9:72:27:0a:8e:
+ 1e:a8:b1:2e:cd:32:a2:78:57:b0:9c:f8:95:bb:43:8e:8c:31:
+ 86:6e:53:0d:c6:12:05:ba:41:6e:a8:35:30:09:18:1d:02:61:
+ ff:fd:ee:35:de:6a:c3:3b:d0:4d:4b:4e:50:b2:56:36:0c:44:
+ 5d:da:1a:65:2a:e6:98:56:a9:63:33:2e:04:e7:ae:e8:f4:8e:
+ b7:b2:da:7d:c0:c8:e2:ae:a6:28:2f:e3:c9:73:bd:fc:07:41:
+ 34:b7:aa:6e:ee:a7:db:d1:93:3c:ed:90:ec:32:92:88:d9:c8:
+ 23:6c:74:21
+-----BEGIN CERTIFICATE-----
+MIIEZzCCA0+gAwIBAgILBAAAAAABHkSl9SowDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNzA0MTExMjAw
+MDBaFw0xNzA0MTExMjAwMDBaMGoxIzAhBgNVBAsTGk9yZ2FuaXphdGlvbiBWYWxp
+ZGF0aW9uIENBMRMwEQYDVQQKEwpHbG9iYWxTaWduMS4wLAYDVQQDEyVHbG9iYWxT
+aWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAoS/EvM6HA+lnwYnI5ZP8fbStnvZjTmronCxziaIB9I8h
++P0lnVgWbYb27klXdX516iIRfj37x0JB3PzFDJFVgHvrZDMdm/nKOOmrxiVDUSVA
+9OR+GFVqqY8QOkAe1leD738vNC8t0vZTwhkNt+3JgfVGLLQjQl6dEwN17Opq/Fd8
+yTaXO5jcExPs7EH6XTTquZPnEBZlzJyS/fXFnT5KuQn85F8eaV9N9FZyRLEdIwPI
+NvZliMi/ORZFjh4mbFEWxSoAOMWkE2mVfasBO6jEFLSA2qwaRCDV/qkGexQnr+Aw
+Id2Q9KnVIxkuHgPmwd+VKeTBlEPdPpCqy0vJvorTOQIDAQABo4IBHzCCARswDgYD
+VR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFH1tKuxm
+q6dRNqsCafFwj8RZC5ofMEsGA1UdIAREMEIwQAYJKwYBBAGgMgEUMDMwMQYIKwYB
+BQUHAgEWJWh0dHA6Ly93d3cuZ2xvYmFsc2lnbi5uZXQvcmVwb3NpdG9yeS8wMwYD
+VR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5nbG9iYWxzaWduLm5ldC9yb290LmNy
+bDARBglghkgBhvhCAQEEBAMCAgQwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI
+AYb4QgQBMB8GA1UdIwQYMBaAFGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3
+DQEBBQUAA4IBAQB5R/wV10x53w96ns7UfEtjyYm1ez+ZEuicjJpJL+BOlUrtx7y+
+8aLbjpMdunFUqkvZiSIkh8UEqKyCUqBS+LjhT6EnZmMhSjnnx8VOX7LWHRNtMOnO
+16IcvCkKczxbI0n+1v/KsE/18meYwEcR+LdIppAJ1kK+6rG5U0LDnCDJ+6FbtVZt
+h4HIYKzEuXInCo4eqLEuzTKieFewnPiVu0OOjDGGblMNxhIFukFuqDUwCRgdAmH/
+/e413mrDO9BNS05QslY2DERd2hplKuaYVqljMy4E567o9I63stp9wMjirqYoL+PJ
+c738B0E0t6pu7qfb0ZM87ZDsMpKI2cgjbHQh
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert32[] = {
+ 0x30, 0x82, 0x04, 0x67, 0x30, 0x82, 0x03, 0x4f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1e, 0x44, 0xa5,
+ 0xf5, 0x2a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31, 0x32, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x30, 0x34, 0x31, 0x31, 0x31,
+ 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6a, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1a, 0x4f, 0x72, 0x67, 0x61, 0x6e,
+ 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69,
+ 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x25, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+ 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0xa1, 0x2f, 0xc4, 0xbc, 0xce, 0x87, 0x03, 0xe9, 0x67,
+ 0xc1, 0x89, 0xc8, 0xe5, 0x93, 0xfc, 0x7d, 0xb4, 0xad, 0x9e, 0xf6, 0x63,
+ 0x4e, 0x6a, 0xe8, 0x9c, 0x2c, 0x73, 0x89, 0xa2, 0x01, 0xf4, 0x8f, 0x21,
+ 0xf8, 0xfd, 0x25, 0x9d, 0x58, 0x16, 0x6d, 0x86, 0xf6, 0xee, 0x49, 0x57,
+ 0x75, 0x7e, 0x75, 0xea, 0x22, 0x11, 0x7e, 0x3d, 0xfb, 0xc7, 0x42, 0x41,
+ 0xdc, 0xfc, 0xc5, 0x0c, 0x91, 0x55, 0x80, 0x7b, 0xeb, 0x64, 0x33, 0x1d,
+ 0x9b, 0xf9, 0xca, 0x38, 0xe9, 0xab, 0xc6, 0x25, 0x43, 0x51, 0x25, 0x40,
+ 0xf4, 0xe4, 0x7e, 0x18, 0x55, 0x6a, 0xa9, 0x8f, 0x10, 0x3a, 0x40, 0x1e,
+ 0xd6, 0x57, 0x83, 0xef, 0x7f, 0x2f, 0x34, 0x2f, 0x2d, 0xd2, 0xf6, 0x53,
+ 0xc2, 0x19, 0x0d, 0xb7, 0xed, 0xc9, 0x81, 0xf5, 0x46, 0x2c, 0xb4, 0x23,
+ 0x42, 0x5e, 0x9d, 0x13, 0x03, 0x75, 0xec, 0xea, 0x6a, 0xfc, 0x57, 0x7c,
+ 0xc9, 0x36, 0x97, 0x3b, 0x98, 0xdc, 0x13, 0x13, 0xec, 0xec, 0x41, 0xfa,
+ 0x5d, 0x34, 0xea, 0xb9, 0x93, 0xe7, 0x10, 0x16, 0x65, 0xcc, 0x9c, 0x92,
+ 0xfd, 0xf5, 0xc5, 0x9d, 0x3e, 0x4a, 0xb9, 0x09, 0xfc, 0xe4, 0x5f, 0x1e,
+ 0x69, 0x5f, 0x4d, 0xf4, 0x56, 0x72, 0x44, 0xb1, 0x1d, 0x23, 0x03, 0xc8,
+ 0x36, 0xf6, 0x65, 0x88, 0xc8, 0xbf, 0x39, 0x16, 0x45, 0x8e, 0x1e, 0x26,
+ 0x6c, 0x51, 0x16, 0xc5, 0x2a, 0x00, 0x38, 0xc5, 0xa4, 0x13, 0x69, 0x95,
+ 0x7d, 0xab, 0x01, 0x3b, 0xa8, 0xc4, 0x14, 0xb4, 0x80, 0xda, 0xac, 0x1a,
+ 0x44, 0x20, 0xd5, 0xfe, 0xa9, 0x06, 0x7b, 0x14, 0x27, 0xaf, 0xe0, 0x30,
+ 0x21, 0xdd, 0x90, 0xf4, 0xa9, 0xd5, 0x23, 0x19, 0x2e, 0x1e, 0x03, 0xe6,
+ 0xc1, 0xdf, 0x95, 0x29, 0xe4, 0xc1, 0x94, 0x43, 0xdd, 0x3e, 0x90, 0xaa,
+ 0xcb, 0x4b, 0xc9, 0xbe, 0x8a, 0xd3, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x01, 0x1f, 0x30, 0x82, 0x01, 0x1b, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x7d, 0x6d, 0x2a, 0xec, 0x66,
+ 0xab, 0xa7, 0x51, 0x36, 0xab, 0x02, 0x69, 0xf1, 0x70, 0x8f, 0xc4, 0x59,
+ 0x0b, 0x9a, 0x1f, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x44,
+ 0x30, 0x42, 0x30, 0x40, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xa0,
+ 0x32, 0x01, 0x14, 0x30, 0x33, 0x30, 0x31, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0,
+ 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+ 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45,
+ 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff,
+ 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x79,
+ 0x47, 0xfc, 0x15, 0xd7, 0x4c, 0x79, 0xdf, 0x0f, 0x7a, 0x9e, 0xce, 0xd4,
+ 0x7c, 0x4b, 0x63, 0xc9, 0x89, 0xb5, 0x7b, 0x3f, 0x99, 0x12, 0xe8, 0x9c,
+ 0x8c, 0x9a, 0x49, 0x2f, 0xe0, 0x4e, 0x95, 0x4a, 0xed, 0xc7, 0xbc, 0xbe,
+ 0xf1, 0xa2, 0xdb, 0x8e, 0x93, 0x1d, 0xba, 0x71, 0x54, 0xaa, 0x4b, 0xd9,
+ 0x89, 0x22, 0x24, 0x87, 0xc5, 0x04, 0xa8, 0xac, 0x82, 0x52, 0xa0, 0x52,
+ 0xf8, 0xb8, 0xe1, 0x4f, 0xa1, 0x27, 0x66, 0x63, 0x21, 0x4a, 0x39, 0xe7,
+ 0xc7, 0xc5, 0x4e, 0x5f, 0xb2, 0xd6, 0x1d, 0x13, 0x6d, 0x30, 0xe9, 0xce,
+ 0xd7, 0xa2, 0x1c, 0xbc, 0x29, 0x0a, 0x73, 0x3c, 0x5b, 0x23, 0x49, 0xfe,
+ 0xd6, 0xff, 0xca, 0xb0, 0x4f, 0xf5, 0xf2, 0x67, 0x98, 0xc0, 0x47, 0x11,
+ 0xf8, 0xb7, 0x48, 0xa6, 0x90, 0x09, 0xd6, 0x42, 0xbe, 0xea, 0xb1, 0xb9,
+ 0x53, 0x42, 0xc3, 0x9c, 0x20, 0xc9, 0xfb, 0xa1, 0x5b, 0xb5, 0x56, 0x6d,
+ 0x87, 0x81, 0xc8, 0x60, 0xac, 0xc4, 0xb9, 0x72, 0x27, 0x0a, 0x8e, 0x1e,
+ 0xa8, 0xb1, 0x2e, 0xcd, 0x32, 0xa2, 0x78, 0x57, 0xb0, 0x9c, 0xf8, 0x95,
+ 0xbb, 0x43, 0x8e, 0x8c, 0x31, 0x86, 0x6e, 0x53, 0x0d, 0xc6, 0x12, 0x05,
+ 0xba, 0x41, 0x6e, 0xa8, 0x35, 0x30, 0x09, 0x18, 0x1d, 0x02, 0x61, 0xff,
+ 0xfd, 0xee, 0x35, 0xde, 0x6a, 0xc3, 0x3b, 0xd0, 0x4d, 0x4b, 0x4e, 0x50,
+ 0xb2, 0x56, 0x36, 0x0c, 0x44, 0x5d, 0xda, 0x1a, 0x65, 0x2a, 0xe6, 0x98,
+ 0x56, 0xa9, 0x63, 0x33, 0x2e, 0x04, 0xe7, 0xae, 0xe8, 0xf4, 0x8e, 0xb7,
+ 0xb2, 0xda, 0x7d, 0xc0, 0xc8, 0xe2, 0xae, 0xa6, 0x28, 0x2f, 0xe3, 0xc9,
+ 0x73, 0xbd, 0xfc, 0x07, 0x41, 0x34, 0xb7, 0xaa, 0x6e, 0xee, 0xa7, 0xdb,
+ 0xd1, 0x93, 0x3c, 0xed, 0x90, 0xec, 0x32, 0x92, 0x88, 0xd9, 0xc8, 0x23,
+ 0x6c, 0x74, 0x21,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1200005093 (0x47869fe5)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=SecureTrust Corporation, CN=SecureTrust CA
+ Validity
+ Not Before: Dec 22 23:47:39 2008 GMT
+ Not After : Dec 22 23:47:39 2028 GMT
+ Subject: C=US, ST=Illinois, L=Chicago, O=Trustwave Holdings, Inc., CN=Trustwave Organization Validation CA, Level 2/emailAddress=ca@trustwave.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e8:14:ee:a0:da:97:bd:89:bd:0c:cc:8d:df:08:
+ fb:06:09:83:a8:23:51:5b:01:3f:34:da:1f:1f:6e:
+ c1:e3:58:65:cb:0a:f9:f3:87:c8:c8:fa:a1:cc:f5:
+ 74:e2:b8:ae:2d:06:84:80:28:6f:5a:c1:22:5c:92:
+ 94:42:cd:19:02:12:5c:10:62:7e:a2:44:fb:16:5e:
+ 9c:72:b1:ac:ea:04:e6:15:aa:99:e8:5a:f8:58:b9:
+ 87:24:e8:75:cd:25:88:e2:58:92:5e:86:83:7f:8a:
+ 23:53:ae:8a:e8:a3:21:7e:83:af:40:09:18:49:af:
+ e1:d0:5a:b0:4f:6f:e2:31:ad:f4:f1:37:1f:c9:2a:
+ e1:8b:d6:8c:12:31:d4:27:1a:df:ea:6b:9e:78:53:
+ ed:9a:19:b0:ce:45:44:5b:1b:ef:64:59:21:fa:c7:
+ b7:d1:d3:0c:1e:cb:88:da:fd:23:3f:f4:ac:2b:a0:
+ 4d:61:d3:be:ca:de:19:61:61:24:f1:f6:9c:b4:96:
+ bd:9d:eb:17:9f:24:39:78:e9:23:50:d3:01:50:77:
+ d8:52:64:2f:3e:19:4f:75:b9:17:b1:da:8d:e0:d0:
+ ed:db:37:13:dc:2f:e0:5f:80:68:d7:f4:87:ba:c1:
+ 1f:12:78:d0:08:27:17:7a:98:a6:9f:d2:21:ba:4e:
+ 87:bf
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 5D:D9:96:9A:40:C7:27:CB:2C:9B:A2:EC:CF:19:AB:C8:AF:CC:86:48
+ X509v3 Authority Key Identifier:
+ keyid:42:32:B6:16:FA:04:FD:FE:5D:4B:7A:C3:FD:F7:4C:40:1D:5A:43:AF
+
+ X509v3 Key Usage:
+ Certificate Sign, CRL Sign
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.securetrust.com/STCA.crl
+
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.30360.3.0
+ Policy: 1.3.6.1.4.1.30360.3.3.3.3.4.4.3
+ CPS: http://www.securetrust.com/legal/
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 53:f1:c8:a0:17:ec:6c:88:82:b1:c0:24:af:d1:08:58:b3:2c:
+ 6f:7b:c1:5c:89:92:6f:88:fc:4b:c0:02:50:93:2f:5a:41:98:
+ 59:b6:e3:7f:8c:14:63:77:7d:45:3c:88:50:5e:a6:81:52:00:
+ c8:c5:fe:48:ee:1f:5d:ad:de:44:0b:42:58:9c:e1:67:5c:43:
+ b6:a0:85:98:ff:16:d4:1a:28:be:76:e1:2f:e1:84:f4:7e:b9:
+ 27:aa:77:cb:36:b3:fe:c3:fa:d2:17:f6:e1:62:4e:d3:cc:cc:
+ b3:19:65:d3:4b:a8:e8:b3:d5:4c:ea:f6:4e:ae:cb:ae:34:48:
+ 1f:60:cc:58:e7:e7:74:c9:01:35:fd:6a:e0:58:8a:d2:16:eb:
+ ec:e9:3e:bb:f0:1d:cf:b6:ff:1e:0c:b7:bb:39:e9:b7:98:1b:
+ c0:52:21:eb:3a:3d:78:38:8c:a9:19:5f:27:a4:d0:7f:36:61:
+ ab:24:7e:9f:f8:2d:3f:92:29:63:be:cb:10:db:0d:40:36:02:
+ a0:d4:17:a2:8d:7f:7e:7c:99:af:45:5a:40:cd:a2:6b:5c:be:
+ 0e:f3:d3:87:fc:a1:10:ca:aa:33:b7:ba:4b:c0:3d:a4:21:8c:
+ 17:9c:cf:d8:bf:e6:57:fe:cd:eb:fa:30:1a:d5:fe:e8:25:97:
+ a9:be:3b:ea
+-----BEGIN CERTIFICATE-----
+MIIEajCCA1KgAwIBAgIER4af5TANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQGEwJV
+UzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNl
+Y3VyZVRydXN0IENBMB4XDTA4MTIyMjIzNDczOVoXDTI4MTIyMjIzNDczOVowga4x
+CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczEQMA4GA1UEBxMHQ2hpY2Fn
+bzEhMB8GA1UEChMYVHJ1c3R3YXZlIEhvbGRpbmdzLCBJbmMuMTYwNAYDVQQDEy1U
+cnVzdHdhdmUgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gQ0EsIExldmVsIDIxHzAd
+BgkqhkiG9w0BCQEWEGNhQHRydXN0d2F2ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDoFO6g2pe9ib0MzI3fCPsGCYOoI1FbAT802h8fbsHjWGXL
+Cvnzh8jI+qHM9XTiuK4tBoSAKG9awSJckpRCzRkCElwQYn6iRPsWXpxysazqBOYV
+qpnoWvhYuYck6HXNJYjiWJJehoN/iiNTrorooyF+g69ACRhJr+HQWrBPb+IxrfTx
+Nx/JKuGL1owSMdQnGt/qa554U+2aGbDORURbG+9kWSH6x7fR0wwey4ja/SM/9Kwr
+oE1h077K3hlhYSTx9py0lr2d6xefJDl46SNQ0wFQd9hSZC8+GU91uRex2o3g0O3b
+NxPcL+BfgGjX9Ie6wR8SeNAIJxd6mKaf0iG6Toe/AgMBAAGjgfQwgfEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUXdmWmkDHJ8ssm6LszxmryK/MhkgwHwYDVR0j
+BBgwFoAUQjK2FvoE/f5dS3rD/fdMQB1aQ68wCwYDVR0PBAQDAgEGMDQGA1UdHwQt
+MCswKaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NUQ0EuY3JsMFsG
+A1UdIARUMFIwDAYKKwYBBAGB7RgDADBCBg8rBgEEAYHtGAMDAwMEBAMwLzAtBggr
+BgEFBQcCARYhaHR0cDovL3d3dy5zZWN1cmV0cnVzdC5jb20vbGVnYWwvMA0GCSqG
+SIb3DQEBBQUAA4IBAQBT8cigF+xsiIKxwCSv0QhYsyxve8FciZJviPxLwAJQky9a
+QZhZtuN/jBRjd31FPIhQXqaBUgDIxf5I7h9drd5EC0JYnOFnXEO2oIWY/xbUGii+
+duEv4YT0frknqnfLNrP+w/rSF/bhYk7TzMyzGWXTS6jos9VM6vZOrsuuNEgfYMxY
+5+d0yQE1/WrgWIrSFuvs6T678B3Ptv8eDLe7Oem3mBvAUiHrOj14OIypGV8npNB/
+NmGrJH6f+C0/kiljvssQ2w1ANgKg1BeijX9+fJmvRVpAzaJrXL4O89OH/KEQyqoz
+t7pLwD2kIYwXnM/Yv+ZX/s3r+jAa1f7oJZepvjvq
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert33[] = {
+ 0x30, 0x82, 0x04, 0x6a, 0x30, 0x82, 0x03, 0x52, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x47, 0x86, 0x9f, 0xe5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x48,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x17,
+ 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x53, 0x65,
+ 0x63, 0x75, 0x72, 0x65, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x31, 0x32, 0x32, 0x32, 0x32, 0x33,
+ 0x34, 0x37, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x31, 0x32, 0x32,
+ 0x32, 0x32, 0x33, 0x34, 0x37, 0x33, 0x39, 0x5a, 0x30, 0x81, 0xae, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x08, 0x49,
+ 0x6c, 0x6c, 0x69, 0x6e, 0x6f, 0x69, 0x73, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x43, 0x68, 0x69, 0x63, 0x61, 0x67,
+ 0x6f, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76, 0x65, 0x20, 0x48, 0x6f,
+ 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76, 0x65, 0x20, 0x4f, 0x72, 0x67,
+ 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56, 0x61,
+ 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x2c,
+ 0x20, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x32, 0x31, 0x1f, 0x30, 0x1d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+ 0x10, 0x63, 0x61, 0x40, 0x74, 0x72, 0x75, 0x73, 0x74, 0x77, 0x61, 0x76,
+ 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xe8, 0x14, 0xee, 0xa0, 0xda, 0x97, 0xbd, 0x89, 0xbd, 0x0c,
+ 0xcc, 0x8d, 0xdf, 0x08, 0xfb, 0x06, 0x09, 0x83, 0xa8, 0x23, 0x51, 0x5b,
+ 0x01, 0x3f, 0x34, 0xda, 0x1f, 0x1f, 0x6e, 0xc1, 0xe3, 0x58, 0x65, 0xcb,
+ 0x0a, 0xf9, 0xf3, 0x87, 0xc8, 0xc8, 0xfa, 0xa1, 0xcc, 0xf5, 0x74, 0xe2,
+ 0xb8, 0xae, 0x2d, 0x06, 0x84, 0x80, 0x28, 0x6f, 0x5a, 0xc1, 0x22, 0x5c,
+ 0x92, 0x94, 0x42, 0xcd, 0x19, 0x02, 0x12, 0x5c, 0x10, 0x62, 0x7e, 0xa2,
+ 0x44, 0xfb, 0x16, 0x5e, 0x9c, 0x72, 0xb1, 0xac, 0xea, 0x04, 0xe6, 0x15,
+ 0xaa, 0x99, 0xe8, 0x5a, 0xf8, 0x58, 0xb9, 0x87, 0x24, 0xe8, 0x75, 0xcd,
+ 0x25, 0x88, 0xe2, 0x58, 0x92, 0x5e, 0x86, 0x83, 0x7f, 0x8a, 0x23, 0x53,
+ 0xae, 0x8a, 0xe8, 0xa3, 0x21, 0x7e, 0x83, 0xaf, 0x40, 0x09, 0x18, 0x49,
+ 0xaf, 0xe1, 0xd0, 0x5a, 0xb0, 0x4f, 0x6f, 0xe2, 0x31, 0xad, 0xf4, 0xf1,
+ 0x37, 0x1f, 0xc9, 0x2a, 0xe1, 0x8b, 0xd6, 0x8c, 0x12, 0x31, 0xd4, 0x27,
+ 0x1a, 0xdf, 0xea, 0x6b, 0x9e, 0x78, 0x53, 0xed, 0x9a, 0x19, 0xb0, 0xce,
+ 0x45, 0x44, 0x5b, 0x1b, 0xef, 0x64, 0x59, 0x21, 0xfa, 0xc7, 0xb7, 0xd1,
+ 0xd3, 0x0c, 0x1e, 0xcb, 0x88, 0xda, 0xfd, 0x23, 0x3f, 0xf4, 0xac, 0x2b,
+ 0xa0, 0x4d, 0x61, 0xd3, 0xbe, 0xca, 0xde, 0x19, 0x61, 0x61, 0x24, 0xf1,
+ 0xf6, 0x9c, 0xb4, 0x96, 0xbd, 0x9d, 0xeb, 0x17, 0x9f, 0x24, 0x39, 0x78,
+ 0xe9, 0x23, 0x50, 0xd3, 0x01, 0x50, 0x77, 0xd8, 0x52, 0x64, 0x2f, 0x3e,
+ 0x19, 0x4f, 0x75, 0xb9, 0x17, 0xb1, 0xda, 0x8d, 0xe0, 0xd0, 0xed, 0xdb,
+ 0x37, 0x13, 0xdc, 0x2f, 0xe0, 0x5f, 0x80, 0x68, 0xd7, 0xf4, 0x87, 0xba,
+ 0xc1, 0x1f, 0x12, 0x78, 0xd0, 0x08, 0x27, 0x17, 0x7a, 0x98, 0xa6, 0x9f,
+ 0xd2, 0x21, 0xba, 0x4e, 0x87, 0xbf, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x81, 0xf4, 0x30, 0x81, 0xf1, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0xd9, 0x96,
+ 0x9a, 0x40, 0xc7, 0x27, 0xcb, 0x2c, 0x9b, 0xa2, 0xec, 0xcf, 0x19, 0xab,
+ 0xc8, 0xaf, 0xcc, 0x86, 0x48, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x42, 0x32, 0xb6, 0x16, 0xfa, 0x04,
+ 0xfd, 0xfe, 0x5d, 0x4b, 0x7a, 0xc3, 0xfd, 0xf7, 0x4c, 0x40, 0x1d, 0x5a,
+ 0x43, 0xaf, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d,
+ 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x53, 0x54, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x5b, 0x06,
+ 0x03, 0x55, 0x1d, 0x20, 0x04, 0x54, 0x30, 0x52, 0x30, 0x0c, 0x06, 0x0a,
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xed, 0x18, 0x03, 0x00, 0x30, 0x42,
+ 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xed, 0x18, 0x03, 0x03,
+ 0x03, 0x03, 0x04, 0x04, 0x03, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x21, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x53, 0xf1, 0xc8, 0xa0, 0x17, 0xec, 0x6c, 0x88, 0x82, 0xb1,
+ 0xc0, 0x24, 0xaf, 0xd1, 0x08, 0x58, 0xb3, 0x2c, 0x6f, 0x7b, 0xc1, 0x5c,
+ 0x89, 0x92, 0x6f, 0x88, 0xfc, 0x4b, 0xc0, 0x02, 0x50, 0x93, 0x2f, 0x5a,
+ 0x41, 0x98, 0x59, 0xb6, 0xe3, 0x7f, 0x8c, 0x14, 0x63, 0x77, 0x7d, 0x45,
+ 0x3c, 0x88, 0x50, 0x5e, 0xa6, 0x81, 0x52, 0x00, 0xc8, 0xc5, 0xfe, 0x48,
+ 0xee, 0x1f, 0x5d, 0xad, 0xde, 0x44, 0x0b, 0x42, 0x58, 0x9c, 0xe1, 0x67,
+ 0x5c, 0x43, 0xb6, 0xa0, 0x85, 0x98, 0xff, 0x16, 0xd4, 0x1a, 0x28, 0xbe,
+ 0x76, 0xe1, 0x2f, 0xe1, 0x84, 0xf4, 0x7e, 0xb9, 0x27, 0xaa, 0x77, 0xcb,
+ 0x36, 0xb3, 0xfe, 0xc3, 0xfa, 0xd2, 0x17, 0xf6, 0xe1, 0x62, 0x4e, 0xd3,
+ 0xcc, 0xcc, 0xb3, 0x19, 0x65, 0xd3, 0x4b, 0xa8, 0xe8, 0xb3, 0xd5, 0x4c,
+ 0xea, 0xf6, 0x4e, 0xae, 0xcb, 0xae, 0x34, 0x48, 0x1f, 0x60, 0xcc, 0x58,
+ 0xe7, 0xe7, 0x74, 0xc9, 0x01, 0x35, 0xfd, 0x6a, 0xe0, 0x58, 0x8a, 0xd2,
+ 0x16, 0xeb, 0xec, 0xe9, 0x3e, 0xbb, 0xf0, 0x1d, 0xcf, 0xb6, 0xff, 0x1e,
+ 0x0c, 0xb7, 0xbb, 0x39, 0xe9, 0xb7, 0x98, 0x1b, 0xc0, 0x52, 0x21, 0xeb,
+ 0x3a, 0x3d, 0x78, 0x38, 0x8c, 0xa9, 0x19, 0x5f, 0x27, 0xa4, 0xd0, 0x7f,
+ 0x36, 0x61, 0xab, 0x24, 0x7e, 0x9f, 0xf8, 0x2d, 0x3f, 0x92, 0x29, 0x63,
+ 0xbe, 0xcb, 0x10, 0xdb, 0x0d, 0x40, 0x36, 0x02, 0xa0, 0xd4, 0x17, 0xa2,
+ 0x8d, 0x7f, 0x7e, 0x7c, 0x99, 0xaf, 0x45, 0x5a, 0x40, 0xcd, 0xa2, 0x6b,
+ 0x5c, 0xbe, 0x0e, 0xf3, 0xd3, 0x87, 0xfc, 0xa1, 0x10, 0xca, 0xaa, 0x33,
+ 0xb7, 0xba, 0x4b, 0xc0, 0x3d, 0xa4, 0x21, 0x8c, 0x17, 0x9c, 0xcf, 0xd8,
+ 0xbf, 0xe6, 0x57, 0xfe, 0xcd, 0xeb, 0xfa, 0x30, 0x1a, 0xd5, 0xfe, 0xe8,
+ 0x25, 0x97, 0xa9, 0xbe, 0x3b, 0xea,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 4d:5f:2c:34:08:b2:4c:20:cd:6d:50:7e:24:4d:c9:ec
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+ Validity
+ Not Before: Feb 8 00:00:00 2010 GMT
+ Not After : Feb 7 23:59:59 2020 GMT
+ Subject: C=US, O=Thawte, Inc., CN=Thawte SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:99:e4:85:5b:76:49:7d:2f:05:d8:c5:ac:c8:c8:
+ a9:d3:dc:98:e6:d7:34:a6:2f:0c:f2:22:26:d8:a3:
+ c9:14:4c:8f:05:a4:45:e8:14:0c:58:90:05:1a:b7:
+ c5:c1:06:a5:80:af:bb:1d:49:6b:52:34:88:c3:59:
+ e7:ef:6b:c4:27:41:8c:2b:66:1d:d0:e0:a3:97:98:
+ 19:34:4b:41:d5:98:d5:c7:05:ad:a2:e4:d7:ed:0c:
+ ad:4f:c1:b5:b0:21:fd:3e:50:53:b2:c4:90:d0:d4:
+ 30:67:6c:9a:f1:0e:74:c4:c2:dc:8a:e8:97:ff:c9:
+ 92:ae:01:8a:56:0a:98:32:b0:00:23:ec:90:1a:60:
+ c3:ed:bb:3a:cb:0f:63:9f:0d:44:c9:52:e1:25:96:
+ bf:ed:50:95:89:7f:56:14:b1:b7:61:1d:1c:07:8c:
+ 3a:2c:f7:ff:80:de:39:45:d5:af:1a:d1:78:d8:c7:
+ 71:6a:a3:19:a7:32:50:21:e9:f2:0e:a1:c6:13:03:
+ 44:48:d1:66:a8:52:57:d7:11:b4:93:8b:e5:99:9f:
+ 5d:e7:78:51:e5:4d:f6:b7:59:b4:76:b5:09:37:4d:
+ 06:38:13:7a:1c:08:98:5c:c4:48:4a:cb:52:a0:a9:
+ f8:b1:9d:8e:7b:79:b0:20:2f:3c:96:a8:11:62:47:
+ bb:11
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://ocsp.thawte.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.thawte.com/ThawtePCA.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-9
+ X509v3 Subject Key Identifier:
+ A7:A2:83:BB:34:45:40:3D:FC:D5:30:4F:12:B9:3E:A1:01:9F:F6:DB
+ X509v3 Authority Key Identifier:
+ keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 80:22:80:e0:6c:c8:95:16:d7:57:26:87:f3:72:34:db:c6:72:
+ 56:27:3e:d3:96:f6:2e:25:91:a5:3e:33:97:a7:4b:e5:2f:fb:
+ 25:7d:2f:07:61:fa:6f:83:74:4c:4c:53:72:20:a4:7a:cf:51:
+ 51:56:81:88:b0:6d:1f:36:2c:c8:2b:b1:88:99:c1:fe:44:ab:
+ 48:51:7c:d8:f2:44:64:2a:d8:71:a7:fb:1a:2f:f9:19:8d:34:
+ b2:23:bf:c4:4c:55:1d:8e:44:e8:aa:5d:9a:dd:9f:fd:03:c7:
+ ba:24:43:8d:2d:47:44:db:f6:d8:98:c8:b2:f9:da:ef:ed:29:
+ 5c:69:12:fa:d1:23:96:0f:bf:9c:0d:f2:79:45:53:37:9a:56:
+ 2f:e8:57:10:70:f6:ee:89:0c:49:89:9a:c1:23:f5:c2:2a:cc:
+ 41:cf:22:ab:65:6e:b7:94:82:6d:2f:40:5f:58:de:eb:95:2b:
+ a6:72:68:52:19:91:2a:ae:75:9d:4e:92:e6:ca:de:54:ea:18:
+ ab:25:3c:e6:64:a6:79:1f:26:7d:61:ed:7d:d2:e5:71:55:d8:
+ 93:17:7c:14:38:30:3c:df:86:e3:4c:ad:49:e3:97:59:ce:1b:
+ 9b:2b:ce:dc:65:d4:0b:28:6b:4e:84:46:51:44:f7:33:08:2d:
+ 58:97:21:ae
+-----BEGIN CERTIFICATE-----
+MIIEbDCCA1SgAwIBAgIQTV8sNAiyTCDNbVB+JE3J7DANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjA4MDAwMDAwWhcNMjAw
+MjA3MjM1OTU5WjA8MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMu
+MRYwFAYDVQQDEw1UaGF3dGUgU1NMIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAmeSFW3ZJfS8F2MWsyMip09yY5tc0pi8M8iIm2KPJFEyPBaRF6BQM
+WJAFGrfFwQalgK+7HUlrUjSIw1nn72vEJ0GMK2Yd0OCjl5gZNEtB1ZjVxwWtouTX
+7QytT8G1sCH9PlBTssSQ0NQwZ2ya8Q50xMLciuiX/8mSrgGKVgqYMrAAI+yQGmDD
+7bs6yw9jnw1EyVLhJZa/7VCViX9WFLG3YR0cB4w6LPf/gN45RdWvGtF42MdxaqMZ
+pzJQIenyDqHGEwNESNFmqFJX1xG0k4vlmZ9d53hR5U32t1m0drUJN00GOBN6HAiY
+XMRISstSoKn4sZ2Oe3mwIC88lqgRYke7EQIDAQABo4H7MIH4MDIGCCsGAQUFBwEB
+BCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29jc3AudGhhd3RlLmNvbTASBgNVHRMB
+Af8ECDAGAQH/AgEAMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwudGhhd3Rl
+LmNvbS9UaGF3dGVQQ0EuY3JsMA4GA1UdDwEB/wQEAwIBBjAoBgNVHREEITAfpB0w
+GzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItOTAdBgNVHQ4EFgQUp6KDuzRFQD38
+1TBPErk+oQGf9tswHwYDVR0jBBgwFoAUe1tFz6/Oy3r9MZIaarbzRutXSFAwDQYJ
+KoZIhvcNAQEFBQADggEBAIAigOBsyJUW11cmh/NyNNvGclYnPtOW9i4lkaU+M5en
+S+Uv+yV9Lwdh+m+DdExMU3IgpHrPUVFWgYiwbR82LMgrsYiZwf5Eq0hRfNjyRGQq
+2HGn+xov+RmNNLIjv8RMVR2OROiqXZrdn/0Dx7okQ40tR0Tb9tiYyLL52u/tKVxp
+EvrRI5YPv5wN8nlFUzeaVi/oVxBw9u6JDEmJmsEj9cIqzEHPIqtlbreUgm0vQF9Y
+3uuVK6ZyaFIZkSqudZ1OkubK3lTqGKslPOZkpnkfJn1h7X3S5XFV2JMXfBQ4MDzf
+huNMrUnjl1nOG5srztxl1Asoa06ERlFE9zMILViXIa4=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert34[] = {
+ 0x30, 0x82, 0x04, 0x6c, 0x30, 0x82, 0x03, 0x54, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x4d, 0x5f, 0x2c, 0x34, 0x08, 0xb2, 0x4c, 0x20, 0xcd,
+ 0x6d, 0x50, 0x7e, 0x24, 0x4d, 0xc9, 0xec, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+ 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+ 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+ 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+ 0x32, 0x30, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x3c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0d, 0x54,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41,
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0xe4, 0x85,
+ 0x5b, 0x76, 0x49, 0x7d, 0x2f, 0x05, 0xd8, 0xc5, 0xac, 0xc8, 0xc8, 0xa9,
+ 0xd3, 0xdc, 0x98, 0xe6, 0xd7, 0x34, 0xa6, 0x2f, 0x0c, 0xf2, 0x22, 0x26,
+ 0xd8, 0xa3, 0xc9, 0x14, 0x4c, 0x8f, 0x05, 0xa4, 0x45, 0xe8, 0x14, 0x0c,
+ 0x58, 0x90, 0x05, 0x1a, 0xb7, 0xc5, 0xc1, 0x06, 0xa5, 0x80, 0xaf, 0xbb,
+ 0x1d, 0x49, 0x6b, 0x52, 0x34, 0x88, 0xc3, 0x59, 0xe7, 0xef, 0x6b, 0xc4,
+ 0x27, 0x41, 0x8c, 0x2b, 0x66, 0x1d, 0xd0, 0xe0, 0xa3, 0x97, 0x98, 0x19,
+ 0x34, 0x4b, 0x41, 0xd5, 0x98, 0xd5, 0xc7, 0x05, 0xad, 0xa2, 0xe4, 0xd7,
+ 0xed, 0x0c, 0xad, 0x4f, 0xc1, 0xb5, 0xb0, 0x21, 0xfd, 0x3e, 0x50, 0x53,
+ 0xb2, 0xc4, 0x90, 0xd0, 0xd4, 0x30, 0x67, 0x6c, 0x9a, 0xf1, 0x0e, 0x74,
+ 0xc4, 0xc2, 0xdc, 0x8a, 0xe8, 0x97, 0xff, 0xc9, 0x92, 0xae, 0x01, 0x8a,
+ 0x56, 0x0a, 0x98, 0x32, 0xb0, 0x00, 0x23, 0xec, 0x90, 0x1a, 0x60, 0xc3,
+ 0xed, 0xbb, 0x3a, 0xcb, 0x0f, 0x63, 0x9f, 0x0d, 0x44, 0xc9, 0x52, 0xe1,
+ 0x25, 0x96, 0xbf, 0xed, 0x50, 0x95, 0x89, 0x7f, 0x56, 0x14, 0xb1, 0xb7,
+ 0x61, 0x1d, 0x1c, 0x07, 0x8c, 0x3a, 0x2c, 0xf7, 0xff, 0x80, 0xde, 0x39,
+ 0x45, 0xd5, 0xaf, 0x1a, 0xd1, 0x78, 0xd8, 0xc7, 0x71, 0x6a, 0xa3, 0x19,
+ 0xa7, 0x32, 0x50, 0x21, 0xe9, 0xf2, 0x0e, 0xa1, 0xc6, 0x13, 0x03, 0x44,
+ 0x48, 0xd1, 0x66, 0xa8, 0x52, 0x57, 0xd7, 0x11, 0xb4, 0x93, 0x8b, 0xe5,
+ 0x99, 0x9f, 0x5d, 0xe7, 0x78, 0x51, 0xe5, 0x4d, 0xf6, 0xb7, 0x59, 0xb4,
+ 0x76, 0xb5, 0x09, 0x37, 0x4d, 0x06, 0x38, 0x13, 0x7a, 0x1c, 0x08, 0x98,
+ 0x5c, 0xc4, 0x48, 0x4a, 0xcb, 0x52, 0xa0, 0xa9, 0xf8, 0xb1, 0x9d, 0x8e,
+ 0x7b, 0x79, 0xb0, 0x20, 0x2f, 0x3c, 0x96, 0xa8, 0x11, 0x62, 0x47, 0xbb,
+ 0x11, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfb, 0x30, 0x81, 0xf8,
+ 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30,
+ 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50,
+ 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x28,
+ 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30,
+ 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49,
+ 0x2d, 0x32, 0x2d, 0x39, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0xa7, 0xa2, 0x83, 0xbb, 0x34, 0x45, 0x40, 0x3d, 0xfc,
+ 0xd5, 0x30, 0x4f, 0x12, 0xb9, 0x3e, 0xa1, 0x01, 0x9f, 0xf6, 0xdb, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a,
+ 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x80, 0x22, 0x80, 0xe0, 0x6c, 0xc8, 0x95, 0x16,
+ 0xd7, 0x57, 0x26, 0x87, 0xf3, 0x72, 0x34, 0xdb, 0xc6, 0x72, 0x56, 0x27,
+ 0x3e, 0xd3, 0x96, 0xf6, 0x2e, 0x25, 0x91, 0xa5, 0x3e, 0x33, 0x97, 0xa7,
+ 0x4b, 0xe5, 0x2f, 0xfb, 0x25, 0x7d, 0x2f, 0x07, 0x61, 0xfa, 0x6f, 0x83,
+ 0x74, 0x4c, 0x4c, 0x53, 0x72, 0x20, 0xa4, 0x7a, 0xcf, 0x51, 0x51, 0x56,
+ 0x81, 0x88, 0xb0, 0x6d, 0x1f, 0x36, 0x2c, 0xc8, 0x2b, 0xb1, 0x88, 0x99,
+ 0xc1, 0xfe, 0x44, 0xab, 0x48, 0x51, 0x7c, 0xd8, 0xf2, 0x44, 0x64, 0x2a,
+ 0xd8, 0x71, 0xa7, 0xfb, 0x1a, 0x2f, 0xf9, 0x19, 0x8d, 0x34, 0xb2, 0x23,
+ 0xbf, 0xc4, 0x4c, 0x55, 0x1d, 0x8e, 0x44, 0xe8, 0xaa, 0x5d, 0x9a, 0xdd,
+ 0x9f, 0xfd, 0x03, 0xc7, 0xba, 0x24, 0x43, 0x8d, 0x2d, 0x47, 0x44, 0xdb,
+ 0xf6, 0xd8, 0x98, 0xc8, 0xb2, 0xf9, 0xda, 0xef, 0xed, 0x29, 0x5c, 0x69,
+ 0x12, 0xfa, 0xd1, 0x23, 0x96, 0x0f, 0xbf, 0x9c, 0x0d, 0xf2, 0x79, 0x45,
+ 0x53, 0x37, 0x9a, 0x56, 0x2f, 0xe8, 0x57, 0x10, 0x70, 0xf6, 0xee, 0x89,
+ 0x0c, 0x49, 0x89, 0x9a, 0xc1, 0x23, 0xf5, 0xc2, 0x2a, 0xcc, 0x41, 0xcf,
+ 0x22, 0xab, 0x65, 0x6e, 0xb7, 0x94, 0x82, 0x6d, 0x2f, 0x40, 0x5f, 0x58,
+ 0xde, 0xeb, 0x95, 0x2b, 0xa6, 0x72, 0x68, 0x52, 0x19, 0x91, 0x2a, 0xae,
+ 0x75, 0x9d, 0x4e, 0x92, 0xe6, 0xca, 0xde, 0x54, 0xea, 0x18, 0xab, 0x25,
+ 0x3c, 0xe6, 0x64, 0xa6, 0x79, 0x1f, 0x26, 0x7d, 0x61, 0xed, 0x7d, 0xd2,
+ 0xe5, 0x71, 0x55, 0xd8, 0x93, 0x17, 0x7c, 0x14, 0x38, 0x30, 0x3c, 0xdf,
+ 0x86, 0xe3, 0x4c, 0xad, 0x49, 0xe3, 0x97, 0x59, 0xce, 0x1b, 0x9b, 0x2b,
+ 0xce, 0xdc, 0x65, 0xd4, 0x0b, 0x28, 0x6b, 0x4e, 0x84, 0x46, 0x51, 0x44,
+ 0xf7, 0x33, 0x08, 0x2d, 0x58, 0x97, 0x21, 0xae,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:47:10
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Dec 15 08:00:00 2006 GMT
+ Not After : Jan 28 12:00:00 2028 GMT
+ Subject: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a6:cf:24:0e:be:2e:6f:28:99:45:42:c4:ab:3e:
+ 21:54:9b:0b:d3:7f:84:70:fa:12:b3:cb:bf:87:5f:
+ c6:7f:86:d3:b2:30:5c:d6:fd:ad:f1:7b:dc:e5:f8:
+ 60:96:09:92:10:f5:d0:53:de:fb:7b:7e:73:88:ac:
+ 52:88:7b:4a:a6:ca:49:a6:5e:a8:a7:8c:5a:11:bc:
+ 7a:82:eb:be:8c:e9:b3:ac:96:25:07:97:4a:99:2a:
+ 07:2f:b4:1e:77:bf:8a:0f:b5:02:7c:1b:96:b8:c5:
+ b9:3a:2c:bc:d6:12:b9:eb:59:7d:e2:d0:06:86:5f:
+ 5e:49:6a:b5:39:5e:88:34:ec:bc:78:0c:08:98:84:
+ 6c:a8:cd:4b:b4:a0:7d:0c:79:4d:f0:b8:2d:cb:21:
+ ca:d5:6c:5b:7d:e1:a0:29:84:a1:f9:d3:94:49:cb:
+ 24:62:91:20:bc:dd:0b:d5:d9:cc:f9:ea:27:0a:2b:
+ 73:91:c6:9d:1b:ac:c8:cb:e8:e0:a0:f4:2f:90:8b:
+ 4d:fb:b0:36:1b:f6:19:7a:85:e0:6d:f2:61:13:88:
+ 5c:9f:e0:93:0a:51:97:8a:5a:ce:af:ab:d5:f7:aa:
+ 09:aa:60:bd:dc:d9:5f:df:72:a9:60:13:5e:00:01:
+ c9:4a:fa:3f:a4:ea:07:03:21:02:8e:82:ca:03:c2:
+ 9b:8f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3a:0f:cd:26:4d:38:30:08:a8:c6:fc:5c:d8:08:7a:ef:fa:1c:
+ 2a:03:ce:32:ae:44:96:e1:52:03:95:0a:52:d6:67:af:5b:96:
+ 7c:dd:19:8b:30:5b:36:3a:6b:6e:a0:15:c6:82:a1:cb:39:66:
+ 00:57:8b:02:a2:6e:85:fb:ac:55:5a:b8:15:50:1a:90:de:09:
+ 48:ec:a8:f6:57:1c:18:31:bd:c6:7d:c8:bd:eb:c2:a7:39:51:
+ 6d:a2:ff:1c:78:de:1c:27:04:e1:cf:24:95:e8:0e:e4:d5:1f:
+ b0:f9:fb:50:ca:cb:6e:9e:62:26:78:86:f5:c4:f5:78:8f:dd:
+ 72:af:6e:2e:d5:9e:dd:ce:3c:cb:b8:c7:2d:54:60:d7:e5:9c:
+ 02:4b:86:44:f0:57:51:2b:cd:0a:9b:3c:b1:f5:3a:4c:1d:8a:
+ c5:f0:30:3e:65:87:c4:0e:5f:6e:4a:ac:8a:a8:1e:e7:fa:e4:
+ 33:80:15:84:56:65:25:9b:fb:9e:30:88:cb:91:16:c1:05:c3:
+ a9:24:ec:21:d2:d5:b0:fc:b7:23:46:a7:9d:f7:f7:c6:53:12:
+ 78:37:b4:13:73:8f:37:97:5e:04:9b:f9:99:8b:93:3e:26:42:
+ 97:9f:fd:1e:b5:d5:cb:88:48:34:a2:66:a0:fa:ac:72:8f:dd:
+ 47:2f:82:74
+-----BEGIN CERTIFICATE-----
+MIIEdzCCA1+gAwIBAgILBAAAAAABL07hRxAwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0wNjEyMTUwODAw
+MDBaFw0yODAxMjgxMjAwMDBaMEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBD
+QSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps8kDr4ubyiZRULEqz4h
+VJsL03+EcPoSs8u/h1/Gf4bTsjBc1v2t8Xvc5fhglgmSEPXQU977e35ziKxSiHtK
+pspJpl6op4xaEbx6guu+jOmzrJYlB5dKmSoHL7Qed7+KD7UCfBuWuMW5Oiy81hK5
+61l94tAGhl9eSWq1OV6INOy8eAwImIRsqM1LtKB9DHlN8LgtyyHK1WxbfeGgKYSh
++dOUScskYpEgvN0L1dnM+eonCitzkcadG6zIy+jgoPQvkItN+7A2G/YZeoXgbfJh
+E4hcn+CTClGXilrOr6vV96oJqmC93Nlf33KpYBNeAAHJSvo/pOoHAyECjoLKA8Kb
+jwIDAQABo4IBTTCCAUkwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+HQYDVR0OBBYEFJviB1dnHB7AagbeWbSaLd/cGYYuMEcGA1UdIARAMD4wPAYEVR0g
+ADA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBv
+c2l0b3J5LzAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmdsb2JhbHNpZ24u
+bmV0L3Jvb3QuY3JsMD0GCCsGAQUFBwEBBDEwLzAtBggrBgEFBQcwAYYhaHR0cDov
+L29jc3AuZ2xvYmFsc2lnbi5jb20vcm9vdHIxMCkGA1UdJQQiMCAGCCsGAQUFBwMB
+BggrBgEFBQcDAgYKKwYBBAGCNwoDAzAfBgNVHSMEGDAWgBRge2YaRQ2XyolQL30E
+zTSo//z9SzANBgkqhkiG9w0BAQUFAAOCAQEAOg/NJk04MAioxvxc2Ah67/ocKgPO
+Mq5EluFSA5UKUtZnr1uWfN0ZizBbNjprbqAVxoKhyzlmAFeLAqJuhfusVVq4FVAa
+kN4JSOyo9lccGDG9xn3IvevCpzlRbaL/HHjeHCcE4c8klegO5NUfsPn7UMrLbp5i
+JniG9cT1eI/dcq9uLtWe3c48y7jHLVRg1+WcAkuGRPBXUSvNCps8sfU6TB2KxfAw
+PmWHxA5fbkqsiqge5/rkM4AVhFZlJZv7njCIy5EWwQXDqSTsIdLVsPy3I0annff3
+xlMSeDe0E3OPN5deBJv5mYuTPiZCl5/9HrXVy4hINKJmoPqsco/dRy+CdA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert35[] = {
+ 0x30, 0x82, 0x04, 0x77, 0x30, 0x82, 0x03, 0x5f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x47, 0x10, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x31, 0x35, 0x30, 0x38, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x38, 0x30, 0x31, 0x32, 0x38, 0x31,
+ 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43,
+ 0x41, 0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53,
+ 0x69, 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e,
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa6, 0xcf, 0x24,
+ 0x0e, 0xbe, 0x2e, 0x6f, 0x28, 0x99, 0x45, 0x42, 0xc4, 0xab, 0x3e, 0x21,
+ 0x54, 0x9b, 0x0b, 0xd3, 0x7f, 0x84, 0x70, 0xfa, 0x12, 0xb3, 0xcb, 0xbf,
+ 0x87, 0x5f, 0xc6, 0x7f, 0x86, 0xd3, 0xb2, 0x30, 0x5c, 0xd6, 0xfd, 0xad,
+ 0xf1, 0x7b, 0xdc, 0xe5, 0xf8, 0x60, 0x96, 0x09, 0x92, 0x10, 0xf5, 0xd0,
+ 0x53, 0xde, 0xfb, 0x7b, 0x7e, 0x73, 0x88, 0xac, 0x52, 0x88, 0x7b, 0x4a,
+ 0xa6, 0xca, 0x49, 0xa6, 0x5e, 0xa8, 0xa7, 0x8c, 0x5a, 0x11, 0xbc, 0x7a,
+ 0x82, 0xeb, 0xbe, 0x8c, 0xe9, 0xb3, 0xac, 0x96, 0x25, 0x07, 0x97, 0x4a,
+ 0x99, 0x2a, 0x07, 0x2f, 0xb4, 0x1e, 0x77, 0xbf, 0x8a, 0x0f, 0xb5, 0x02,
+ 0x7c, 0x1b, 0x96, 0xb8, 0xc5, 0xb9, 0x3a, 0x2c, 0xbc, 0xd6, 0x12, 0xb9,
+ 0xeb, 0x59, 0x7d, 0xe2, 0xd0, 0x06, 0x86, 0x5f, 0x5e, 0x49, 0x6a, 0xb5,
+ 0x39, 0x5e, 0x88, 0x34, 0xec, 0xbc, 0x78, 0x0c, 0x08, 0x98, 0x84, 0x6c,
+ 0xa8, 0xcd, 0x4b, 0xb4, 0xa0, 0x7d, 0x0c, 0x79, 0x4d, 0xf0, 0xb8, 0x2d,
+ 0xcb, 0x21, 0xca, 0xd5, 0x6c, 0x5b, 0x7d, 0xe1, 0xa0, 0x29, 0x84, 0xa1,
+ 0xf9, 0xd3, 0x94, 0x49, 0xcb, 0x24, 0x62, 0x91, 0x20, 0xbc, 0xdd, 0x0b,
+ 0xd5, 0xd9, 0xcc, 0xf9, 0xea, 0x27, 0x0a, 0x2b, 0x73, 0x91, 0xc6, 0x9d,
+ 0x1b, 0xac, 0xc8, 0xcb, 0xe8, 0xe0, 0xa0, 0xf4, 0x2f, 0x90, 0x8b, 0x4d,
+ 0xfb, 0xb0, 0x36, 0x1b, 0xf6, 0x19, 0x7a, 0x85, 0xe0, 0x6d, 0xf2, 0x61,
+ 0x13, 0x88, 0x5c, 0x9f, 0xe0, 0x93, 0x0a, 0x51, 0x97, 0x8a, 0x5a, 0xce,
+ 0xaf, 0xab, 0xd5, 0xf7, 0xaa, 0x09, 0xaa, 0x60, 0xbd, 0xdc, 0xd9, 0x5f,
+ 0xdf, 0x72, 0xa9, 0x60, 0x13, 0x5e, 0x00, 0x01, 0xc9, 0x4a, 0xfa, 0x3f,
+ 0xa4, 0xea, 0x07, 0x03, 0x21, 0x02, 0x8e, 0x82, 0xca, 0x03, 0xc2, 0x9b,
+ 0x8f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x4d, 0x30, 0x82,
+ 0x01, 0x49, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x9b, 0xe2,
+ 0x07, 0x57, 0x67, 0x1c, 0x1e, 0xc0, 0x6a, 0x06, 0xde, 0x59, 0xb4, 0x9a,
+ 0x2d, 0xdf, 0xdc, 0x19, 0x86, 0x2e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20,
+ 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+ 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+ 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24,
+ 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f,
+ 0x74, 0x72, 0x31, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x22,
+ 0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x0a,
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60,
+ 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04,
+ 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x3a, 0x0f, 0xcd, 0x26, 0x4d, 0x38, 0x30, 0x08, 0xa8,
+ 0xc6, 0xfc, 0x5c, 0xd8, 0x08, 0x7a, 0xef, 0xfa, 0x1c, 0x2a, 0x03, 0xce,
+ 0x32, 0xae, 0x44, 0x96, 0xe1, 0x52, 0x03, 0x95, 0x0a, 0x52, 0xd6, 0x67,
+ 0xaf, 0x5b, 0x96, 0x7c, 0xdd, 0x19, 0x8b, 0x30, 0x5b, 0x36, 0x3a, 0x6b,
+ 0x6e, 0xa0, 0x15, 0xc6, 0x82, 0xa1, 0xcb, 0x39, 0x66, 0x00, 0x57, 0x8b,
+ 0x02, 0xa2, 0x6e, 0x85, 0xfb, 0xac, 0x55, 0x5a, 0xb8, 0x15, 0x50, 0x1a,
+ 0x90, 0xde, 0x09, 0x48, 0xec, 0xa8, 0xf6, 0x57, 0x1c, 0x18, 0x31, 0xbd,
+ 0xc6, 0x7d, 0xc8, 0xbd, 0xeb, 0xc2, 0xa7, 0x39, 0x51, 0x6d, 0xa2, 0xff,
+ 0x1c, 0x78, 0xde, 0x1c, 0x27, 0x04, 0xe1, 0xcf, 0x24, 0x95, 0xe8, 0x0e,
+ 0xe4, 0xd5, 0x1f, 0xb0, 0xf9, 0xfb, 0x50, 0xca, 0xcb, 0x6e, 0x9e, 0x62,
+ 0x26, 0x78, 0x86, 0xf5, 0xc4, 0xf5, 0x78, 0x8f, 0xdd, 0x72, 0xaf, 0x6e,
+ 0x2e, 0xd5, 0x9e, 0xdd, 0xce, 0x3c, 0xcb, 0xb8, 0xc7, 0x2d, 0x54, 0x60,
+ 0xd7, 0xe5, 0x9c, 0x02, 0x4b, 0x86, 0x44, 0xf0, 0x57, 0x51, 0x2b, 0xcd,
+ 0x0a, 0x9b, 0x3c, 0xb1, 0xf5, 0x3a, 0x4c, 0x1d, 0x8a, 0xc5, 0xf0, 0x30,
+ 0x3e, 0x65, 0x87, 0xc4, 0x0e, 0x5f, 0x6e, 0x4a, 0xac, 0x8a, 0xa8, 0x1e,
+ 0xe7, 0xfa, 0xe4, 0x33, 0x80, 0x15, 0x84, 0x56, 0x65, 0x25, 0x9b, 0xfb,
+ 0x9e, 0x30, 0x88, 0xcb, 0x91, 0x16, 0xc1, 0x05, 0xc3, 0xa9, 0x24, 0xec,
+ 0x21, 0xd2, 0xd5, 0xb0, 0xfc, 0xb7, 0x23, 0x46, 0xa7, 0x9d, 0xf7, 0xf7,
+ 0xc6, 0x53, 0x12, 0x78, 0x37, 0xb4, 0x13, 0x73, 0x8f, 0x37, 0x97, 0x5e,
+ 0x04, 0x9b, 0xf9, 0x99, 0x8b, 0x93, 0x3e, 0x26, 0x42, 0x97, 0x9f, 0xfd,
+ 0x1e, 0xb5, 0xd5, 0xcb, 0x88, 0x48, 0x34, 0xa2, 0x66, 0xa0, 0xfa, 0xac,
+ 0x72, 0x8f, 0xdd, 0x47, 0x2f, 0x82, 0x74,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:3f:11
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Domain Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b1:a3:cd:c0:df:33:40:26:eb:de:5a:d7:94:66:
+ d4:01:63:cc:33:44:89:e0:e2:b8:c2:47:0d:8f:ad:
+ 69:86:1c:a8:73:42:0b:f1:72:fb:2d:ac:b5:11:72:
+ 83:22:f6:56:e7:2e:c5:67:71:9d:00:1c:32:bc:e3:
+ ed:2e:08:45:a9:e6:fa:dd:c8:8c:83:05:c1:6f:4b:
+ d0:26:4a:0b:f6:1b:45:c0:4d:7e:93:bc:0d:27:84:
+ ed:30:a3:e9:c6:26:26:dd:2d:1f:d8:8b:c3:ce:19:
+ d0:5b:fc:08:9f:e4:d8:e2:35:e4:a0:68:a6:f6:0d:
+ a3:74:60:42:b2:97:82:24:8e:41:a4:f2:2e:5e:b6:
+ 8e:a7:6e:d9:6c:7f:0d:3b:24:35:6a:d0:ab:5b:6a:
+ f7:97:02:00:3f:51:a6:a7:6e:73:ca:77:0d:76:7c:
+ 9b:b6:30:1a:1a:9c:f7:1f:28:7b:0e:8b:47:1f:e7:
+ 7f:05:8c:c6:c9:c8:bb:cf:e9:dc:7a:41:2e:a1:86:
+ da:d4:39:b2:e2:13:40:a6:a8:3a:fa:0f:53:1e:4f:
+ ec:6e:98:09:1b:ca:9a:77:b3:55:85:85:e9:2e:16:
+ b5:9d:5e:54:f1:4a:7a:6c:39:ba:6e:17:06:34:b3:
+ b2:42:e1:f7:f3:9c:9a:0b:11:44:de:6a:78:8e:b1:
+ 13:4f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 96:AD:FA:B0:5B:B9:83:64:2A:76:C2:1C:8A:69:DA:42:DC:FE:FD:28
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 7e:9a:13:39:71:69:a0:fc:8c:35:ac:af:b4:d6:de:64:ea:33:
+ 6f:95:53:92:71:ad:4c:c0:fb:d0:6b:ba:80:0e:c2:0a:e6:37:
+ fa:d2:25:a3:22:f7:89:9f:52:12:43:2f:bb:c4:fc:6c:ce:e4:
+ aa:9d:f6:9d:57:7b:cc:2a:ac:75:49:1b:54:66:cf:a7:e9:b9:
+ b0:c2:7c:70:23:fb:9c:97:00:f2:25:a4:d9:a1:0a:5d:85:06:
+ 1d:1a:87:f5:2d:54:c5:64:21:8e:ac:aa:ec:19:3e:9b:ff:c0:
+ 67:a7:2e:00:e3:f1:81:40:00:5b:83:e2:a8:a7:ef:35:50:83:
+ c0:f4:9b:88:2a:89:a9:a9:9c:2f:82:b9:18:9e:fa:eb:47:24:
+ 6e:13:ee:b2:8c:f0:42:37:5e:e6:8f:91:bc:a5:5f:51:2b:ae:
+ bb:8c:76:31:4e:53:11:79:ec:11:4e:38:73:e5:1a:66:70:f4:
+ 82:f7:7b:10:55:f8:bb:a5:c3:1d:e5:d3:f6:bc:fa:28:b6:31:
+ 10:d5:fe:91:23:a4:21:3f:ba:4c:91:8f:87:c7:82:ab:38:c2:
+ 01:73:89:48:1a:f9:0c:91:b9:95:fb:6d:21:5f:03:c8:bf:7b:
+ 74:ef:7b:71:79:b5:3e:73:23:d1:5a:dc:a6:0c:e1:2d:64:65:
+ 91:be:c2:b9
+-----BEGIN CERTIFICATE-----
+MIIEhTCCA22gAwIBAgILBAAAAAABL07hPxEwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0
+aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxo83A
+3zNAJuveWteUZtQBY8wzRIng4rjCRw2PrWmGHKhzQgvxcvstrLURcoMi9lbnLsVn
+cZ0AHDK84+0uCEWp5vrdyIyDBcFvS9AmSgv2G0XATX6TvA0nhO0wo+nGJibdLR/Y
+i8POGdBb/Aif5NjiNeSgaKb2DaN0YEKyl4IkjkGk8i5eto6nbtlsfw07JDVq0Ktb
+aveXAgA/UaanbnPKdw12fJu2MBoanPcfKHsOi0cf538FjMbJyLvP6dx6QS6hhtrU
+ObLiE0CmqDr6D1MeT+xumAkbypp3s1WFhekuFrWdXlTxSnpsObpuFwY0s7JC4ffz
+nJoLEUTeaniOsRNPAgMBAAGjggFQMIIBTDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUlq36sFu5g2QqdsIcimnaQtz+/SgwRwYD
+VR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2Jh
+bHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9j
+cmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEwKQYDVR0l
+BCIwIAYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDMB8GA1UdIwQYMBaA
+FGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQB+mhM5cWmg
+/Iw1rK+01t5k6jNvlVOSca1MwPvQa7qADsIK5jf60iWjIveJn1ISQy+7xPxszuSq
+nfadV3vMKqx1SRtUZs+n6bmwwnxwI/uclwDyJaTZoQpdhQYdGof1LVTFZCGOrKrs
+GT6b/8Bnpy4A4/GBQABbg+Kop+81UIPA9JuIKompqZwvgrkYnvrrRyRuE+6yjPBC
+N17mj5G8pV9RK667jHYxTlMReewRTjhz5RpmcPSC93sQVfi7pcMd5dP2vPootjEQ
+1f6RI6QhP7pMkY+Hx4KrOMIBc4lIGvkMkbmV+20hXwPIv3t073txebU+cyPRWtym
+DOEtZGWRvsK5
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert36[] = {
+ 0x30, 0x82, 0x04, 0x85, 0x30, 0x82, 0x03, 0x6d, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x3f, 0x11, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+ 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+ 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x47,
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x44, 0x6f,
+ 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xa3, 0xcd, 0xc0,
+ 0xdf, 0x33, 0x40, 0x26, 0xeb, 0xde, 0x5a, 0xd7, 0x94, 0x66, 0xd4, 0x01,
+ 0x63, 0xcc, 0x33, 0x44, 0x89, 0xe0, 0xe2, 0xb8, 0xc2, 0x47, 0x0d, 0x8f,
+ 0xad, 0x69, 0x86, 0x1c, 0xa8, 0x73, 0x42, 0x0b, 0xf1, 0x72, 0xfb, 0x2d,
+ 0xac, 0xb5, 0x11, 0x72, 0x83, 0x22, 0xf6, 0x56, 0xe7, 0x2e, 0xc5, 0x67,
+ 0x71, 0x9d, 0x00, 0x1c, 0x32, 0xbc, 0xe3, 0xed, 0x2e, 0x08, 0x45, 0xa9,
+ 0xe6, 0xfa, 0xdd, 0xc8, 0x8c, 0x83, 0x05, 0xc1, 0x6f, 0x4b, 0xd0, 0x26,
+ 0x4a, 0x0b, 0xf6, 0x1b, 0x45, 0xc0, 0x4d, 0x7e, 0x93, 0xbc, 0x0d, 0x27,
+ 0x84, 0xed, 0x30, 0xa3, 0xe9, 0xc6, 0x26, 0x26, 0xdd, 0x2d, 0x1f, 0xd8,
+ 0x8b, 0xc3, 0xce, 0x19, 0xd0, 0x5b, 0xfc, 0x08, 0x9f, 0xe4, 0xd8, 0xe2,
+ 0x35, 0xe4, 0xa0, 0x68, 0xa6, 0xf6, 0x0d, 0xa3, 0x74, 0x60, 0x42, 0xb2,
+ 0x97, 0x82, 0x24, 0x8e, 0x41, 0xa4, 0xf2, 0x2e, 0x5e, 0xb6, 0x8e, 0xa7,
+ 0x6e, 0xd9, 0x6c, 0x7f, 0x0d, 0x3b, 0x24, 0x35, 0x6a, 0xd0, 0xab, 0x5b,
+ 0x6a, 0xf7, 0x97, 0x02, 0x00, 0x3f, 0x51, 0xa6, 0xa7, 0x6e, 0x73, 0xca,
+ 0x77, 0x0d, 0x76, 0x7c, 0x9b, 0xb6, 0x30, 0x1a, 0x1a, 0x9c, 0xf7, 0x1f,
+ 0x28, 0x7b, 0x0e, 0x8b, 0x47, 0x1f, 0xe7, 0x7f, 0x05, 0x8c, 0xc6, 0xc9,
+ 0xc8, 0xbb, 0xcf, 0xe9, 0xdc, 0x7a, 0x41, 0x2e, 0xa1, 0x86, 0xda, 0xd4,
+ 0x39, 0xb2, 0xe2, 0x13, 0x40, 0xa6, 0xa8, 0x3a, 0xfa, 0x0f, 0x53, 0x1e,
+ 0x4f, 0xec, 0x6e, 0x98, 0x09, 0x1b, 0xca, 0x9a, 0x77, 0xb3, 0x55, 0x85,
+ 0x85, 0xe9, 0x2e, 0x16, 0xb5, 0x9d, 0x5e, 0x54, 0xf1, 0x4a, 0x7a, 0x6c,
+ 0x39, 0xba, 0x6e, 0x17, 0x06, 0x34, 0xb3, 0xb2, 0x42, 0xe1, 0xf7, 0xf3,
+ 0x9c, 0x9a, 0x0b, 0x11, 0x44, 0xde, 0x6a, 0x78, 0x8e, 0xb1, 0x13, 0x4f,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x50, 0x30, 0x82, 0x01,
+ 0x4c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x96, 0xad, 0xfa, 0xb0, 0x5b, 0xb9, 0x83, 0x64, 0x2a, 0x76, 0xc2, 0x1c,
+ 0x8a, 0x69, 0xda, 0x42, 0xdc, 0xfe, 0xfd, 0x28, 0x30, 0x47, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+ 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x25,
+ 0x04, 0x22, 0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45, 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f,
+ 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff, 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x01, 0x00, 0x7e, 0x9a, 0x13, 0x39, 0x71, 0x69, 0xa0,
+ 0xfc, 0x8c, 0x35, 0xac, 0xaf, 0xb4, 0xd6, 0xde, 0x64, 0xea, 0x33, 0x6f,
+ 0x95, 0x53, 0x92, 0x71, 0xad, 0x4c, 0xc0, 0xfb, 0xd0, 0x6b, 0xba, 0x80,
+ 0x0e, 0xc2, 0x0a, 0xe6, 0x37, 0xfa, 0xd2, 0x25, 0xa3, 0x22, 0xf7, 0x89,
+ 0x9f, 0x52, 0x12, 0x43, 0x2f, 0xbb, 0xc4, 0xfc, 0x6c, 0xce, 0xe4, 0xaa,
+ 0x9d, 0xf6, 0x9d, 0x57, 0x7b, 0xcc, 0x2a, 0xac, 0x75, 0x49, 0x1b, 0x54,
+ 0x66, 0xcf, 0xa7, 0xe9, 0xb9, 0xb0, 0xc2, 0x7c, 0x70, 0x23, 0xfb, 0x9c,
+ 0x97, 0x00, 0xf2, 0x25, 0xa4, 0xd9, 0xa1, 0x0a, 0x5d, 0x85, 0x06, 0x1d,
+ 0x1a, 0x87, 0xf5, 0x2d, 0x54, 0xc5, 0x64, 0x21, 0x8e, 0xac, 0xaa, 0xec,
+ 0x19, 0x3e, 0x9b, 0xff, 0xc0, 0x67, 0xa7, 0x2e, 0x00, 0xe3, 0xf1, 0x81,
+ 0x40, 0x00, 0x5b, 0x83, 0xe2, 0xa8, 0xa7, 0xef, 0x35, 0x50, 0x83, 0xc0,
+ 0xf4, 0x9b, 0x88, 0x2a, 0x89, 0xa9, 0xa9, 0x9c, 0x2f, 0x82, 0xb9, 0x18,
+ 0x9e, 0xfa, 0xeb, 0x47, 0x24, 0x6e, 0x13, 0xee, 0xb2, 0x8c, 0xf0, 0x42,
+ 0x37, 0x5e, 0xe6, 0x8f, 0x91, 0xbc, 0xa5, 0x5f, 0x51, 0x2b, 0xae, 0xbb,
+ 0x8c, 0x76, 0x31, 0x4e, 0x53, 0x11, 0x79, 0xec, 0x11, 0x4e, 0x38, 0x73,
+ 0xe5, 0x1a, 0x66, 0x70, 0xf4, 0x82, 0xf7, 0x7b, 0x10, 0x55, 0xf8, 0xbb,
+ 0xa5, 0xc3, 0x1d, 0xe5, 0xd3, 0xf6, 0xbc, 0xfa, 0x28, 0xb6, 0x31, 0x10,
+ 0xd5, 0xfe, 0x91, 0x23, 0xa4, 0x21, 0x3f, 0xba, 0x4c, 0x91, 0x8f, 0x87,
+ 0xc7, 0x82, 0xab, 0x38, 0xc2, 0x01, 0x73, 0x89, 0x48, 0x1a, 0xf9, 0x0c,
+ 0x91, 0xb9, 0x95, 0xfb, 0x6d, 0x21, 0x5f, 0x03, 0xc8, 0xbf, 0x7b, 0x74,
+ 0xef, 0x7b, 0x71, 0x79, 0xb5, 0x3e, 0x73, 0x23, 0xd1, 0x5a, 0xdc, 0xa6,
+ 0x0c, 0xe1, 0x2d, 0x64, 0x65, 0x91, 0xbe, 0xc2, 0xb9,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:5d:d4
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Extended Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cd:a1:46:cc:52:9a:b8:a5:b4:7f:58:d9:cd:d8:
+ 4b:a0:72:0c:97:5b:a6:88:20:c3:b9:3d:46:dc:d0:
+ 5c:52:30:f6:fa:59:4f:85:5f:b0:db:88:b4:a9:5f:
+ 2b:23:48:ac:ab:f5:92:78:14:b6:32:0f:fb:5c:6a:
+ 85:5b:00:90:e0:bb:65:f5:5a:f9:4f:67:7e:c7:6c:
+ 29:ec:93:c0:2b:ca:c4:5e:d8:b0:db:d6:be:3f:9b:
+ 0b:c0:8f:a9:5d:ae:f7:00:02:a4:fc:ba:66:11:38:
+ 77:fe:23:20:25:55:10:c5:bd:82:b9:4c:b1:68:c6:
+ e2:70:7b:83:5c:13:67:c1:a1:f3:7c:0b:a8:99:9a:
+ d0:e2:9b:25:31:c8:2b:8d:40:f6:52:63:b1:a0:ad:
+ 5a:2e:f5:79:36:6d:35:2c:0e:dd:05:e4:d0:e2:07:
+ 48:b7:28:5e:2b:d5:58:d5:6c:d0:0c:a1:01:46:01:
+ 5a:8f:c6:af:64:c7:55:01:5d:e1:d1:c6:6c:50:25:
+ a0:05:ad:00:ab:0c:8d:65:6b:dd:eb:c2:72:54:c9:
+ 0f:3c:00:17:87:22:ef:db:b9:86:78:16:51:ae:77:
+ d9:a6:28:4d:f3:58:8d:83:67:b9:34:25:9b:1c:51:
+ 80:51:f3:83:92:6a:a3:ae:47:9a:d6:e4:8b:1b:c0:
+ ed:b1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ B0:B0:4A:FD:1C:75:28:F8:1C:61:AA:13:F6:FA:C1:90:3D:6B:16:A3
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root-r2.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/ExtendedSSLCA
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 2f:49:b6:f2:b6:5a:a4:95:ab:9e:5a:e9:2b:82:9b:cc:90:6b:
+ 7c:74:45:20:e7:5e:d8:c7:23:ee:28:35:b1:35:65:2a:a5:51:
+ e0:55:3f:f6:83:67:b4:e4:36:29:b0:da:ec:97:95:a9:8a:05:
+ a3:45:fe:23:2e:52:88:b4:1f:10:80:ad:d2:8b:9f:a3:5f:a8:
+ c5:eb:73:de:79:88:41:98:33:ee:a7:60:18:b1:46:c9:40:10:
+ 07:9c:8f:0a:52:c9:13:1a:06:6e:a0:9b:2d:3a:f6:ae:4f:e7:
+ a3:51:35:2a:5b:18:05:12:e5:51:dc:b6:36:62:f3:e1:a4:0f:
+ fb:e4:cf:c3:94:bf:11:ab:a1:59:31:01:f0:cc:53:ec:8f:63:
+ d7:6c:96:d3:48:2a:8a:23:ed:45:56:a8:66:41:ea:01:b9:47:
+ ee:a1:47:0c:14:f1:23:e1:20:73:ca:7d:50:7c:64:38:57:a3:
+ 8f:4a:9c:9b:e9:6d:45:cf:44:6b:4d:60:20:40:71:25:b5:46:
+ aa:6c:08:7e:df:c8:fa:c8:56:2a:92:cb:83:b8:79:09:97:2d:
+ 5e:a1:01:ce:06:ed:b4:97:c9:04:dc:41:ef:e0:4f:36:4d:e4:
+ 40:73:46:ec:11:12:7c:88:5b:34:26:25:4d:ea:dc:18:be:c5:
+ 1b:cd:64:c0
+-----BEGIN CERTIFICATE-----
+MIIEhjCCA26gAwIBAgILBAAAAAABL07hXdQwDQYJKoZIhvcNAQEFBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTEwNDEzMTAwMDAwWhcNMjIwNDEz
+MTAwMDAwWjBZMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1z
+YTEvMC0GA1UEAxMmR2xvYmFsU2lnbiBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIC0g
+RzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNoUbMUpq4pbR/WNnN
+2EugcgyXW6aIIMO5PUbc0FxSMPb6WU+FX7DbiLSpXysjSKyr9ZJ4FLYyD/tcaoVb
+AJDgu2X1WvlPZ37HbCnsk8ArysRe2LDb1r4/mwvAj6ldrvcAAqT8umYROHf+IyAl
+VRDFvYK5TLFoxuJwe4NcE2fBofN8C6iZmtDimyUxyCuNQPZSY7GgrVou9Xk2bTUs
+Dt0F5NDiB0i3KF4r1VjVbNAMoQFGAVqPxq9kx1UBXeHRxmxQJaAFrQCrDI1la93r
+wnJUyQ88ABeHIu/buYZ4FlGud9mmKE3zWI2DZ7k0JZscUYBR84OSaqOuR5rW5Isb
+wO2xAgMBAAGjggFaMIIBVjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADAdBgNVHQ4EFgQUsLBK/Rx1KPgcYaoT9vrBkD1rFqMwRwYDVR0gBEAwPjA8
+BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t
+L3JlcG9zaXRvcnkvMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFs
+c2lnbi5uZXQvcm9vdC1yMi5jcmwwRAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzAB
+hihodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9FeHRlbmRlZFNTTENBMCkGA1Ud
+JQQiMCAGCCsGAQUFBwMBBggrBgEFBQcDAgYKKwYBBAGCNwoDAzAfBgNVHSMEGDAW
+gBSb4gdXZxwewGoG3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAL0m28rZa
+pJWrnlrpK4KbzJBrfHRFIOde2Mcj7ig1sTVlKqVR4FU/9oNntOQ2KbDa7JeVqYoF
+o0X+Iy5SiLQfEICt0oufo1+oxetz3nmIQZgz7qdgGLFGyUAQB5yPClLJExoGbqCb
+LTr2rk/no1E1KlsYBRLlUdy2NmLz4aQP++TPw5S/EauhWTEB8MxT7I9j12yW00gq
+iiPtRVaoZkHqAblH7qFHDBTxI+Egc8p9UHxkOFejj0qcm+ltRc9Ea01gIEBxJbVG
+qmwIft/I+shWKpLLg7h5CZctXqEBzgbttJfJBNxB7+BPNk3kQHNG7BESfIhbNCYl
+TercGL7FG81kwA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert37[] = {
+ 0x30, 0x82, 0x04, 0x86, 0x30, 0x82, 0x03, 0x6e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x5d, 0xd4, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c, 0x31, 0x20, 0x30, 0x1e, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x17, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x20, 0x2d, 0x20, 0x52, 0x32, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x0a, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33,
+ 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x59, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73,
+ 0x61, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+ 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69,
+ 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+ 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+ 0xa1, 0x46, 0xcc, 0x52, 0x9a, 0xb8, 0xa5, 0xb4, 0x7f, 0x58, 0xd9, 0xcd,
+ 0xd8, 0x4b, 0xa0, 0x72, 0x0c, 0x97, 0x5b, 0xa6, 0x88, 0x20, 0xc3, 0xb9,
+ 0x3d, 0x46, 0xdc, 0xd0, 0x5c, 0x52, 0x30, 0xf6, 0xfa, 0x59, 0x4f, 0x85,
+ 0x5f, 0xb0, 0xdb, 0x88, 0xb4, 0xa9, 0x5f, 0x2b, 0x23, 0x48, 0xac, 0xab,
+ 0xf5, 0x92, 0x78, 0x14, 0xb6, 0x32, 0x0f, 0xfb, 0x5c, 0x6a, 0x85, 0x5b,
+ 0x00, 0x90, 0xe0, 0xbb, 0x65, 0xf5, 0x5a, 0xf9, 0x4f, 0x67, 0x7e, 0xc7,
+ 0x6c, 0x29, 0xec, 0x93, 0xc0, 0x2b, 0xca, 0xc4, 0x5e, 0xd8, 0xb0, 0xdb,
+ 0xd6, 0xbe, 0x3f, 0x9b, 0x0b, 0xc0, 0x8f, 0xa9, 0x5d, 0xae, 0xf7, 0x00,
+ 0x02, 0xa4, 0xfc, 0xba, 0x66, 0x11, 0x38, 0x77, 0xfe, 0x23, 0x20, 0x25,
+ 0x55, 0x10, 0xc5, 0xbd, 0x82, 0xb9, 0x4c, 0xb1, 0x68, 0xc6, 0xe2, 0x70,
+ 0x7b, 0x83, 0x5c, 0x13, 0x67, 0xc1, 0xa1, 0xf3, 0x7c, 0x0b, 0xa8, 0x99,
+ 0x9a, 0xd0, 0xe2, 0x9b, 0x25, 0x31, 0xc8, 0x2b, 0x8d, 0x40, 0xf6, 0x52,
+ 0x63, 0xb1, 0xa0, 0xad, 0x5a, 0x2e, 0xf5, 0x79, 0x36, 0x6d, 0x35, 0x2c,
+ 0x0e, 0xdd, 0x05, 0xe4, 0xd0, 0xe2, 0x07, 0x48, 0xb7, 0x28, 0x5e, 0x2b,
+ 0xd5, 0x58, 0xd5, 0x6c, 0xd0, 0x0c, 0xa1, 0x01, 0x46, 0x01, 0x5a, 0x8f,
+ 0xc6, 0xaf, 0x64, 0xc7, 0x55, 0x01, 0x5d, 0xe1, 0xd1, 0xc6, 0x6c, 0x50,
+ 0x25, 0xa0, 0x05, 0xad, 0x00, 0xab, 0x0c, 0x8d, 0x65, 0x6b, 0xdd, 0xeb,
+ 0xc2, 0x72, 0x54, 0xc9, 0x0f, 0x3c, 0x00, 0x17, 0x87, 0x22, 0xef, 0xdb,
+ 0xb9, 0x86, 0x78, 0x16, 0x51, 0xae, 0x77, 0xd9, 0xa6, 0x28, 0x4d, 0xf3,
+ 0x58, 0x8d, 0x83, 0x67, 0xb9, 0x34, 0x25, 0x9b, 0x1c, 0x51, 0x80, 0x51,
+ 0xf3, 0x83, 0x92, 0x6a, 0xa3, 0xae, 0x47, 0x9a, 0xd6, 0xe4, 0x8b, 0x1b,
+ 0xc0, 0xed, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5a,
+ 0x30, 0x82, 0x01, 0x56, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0xb0, 0xb0, 0x4a, 0xfd, 0x1c, 0x75, 0x28, 0xf8, 0x1c,
+ 0x61, 0xaa, 0x13, 0xf6, 0xfa, 0xc1, 0x90, 0x3d, 0x6b, 0x16, 0xa3, 0x30,
+ 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30, 0x3e, 0x30, 0x3c,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30, 0x32, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x26, 0x68, 0x74,
+ 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f,
+ 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2f, 0x30, 0x2d, 0x30,
+ 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f,
+ 0x74, 0x2d, 0x72, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x44, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x38, 0x30, 0x36,
+ 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65,
+ 0x64, 0x53, 0x53, 0x4c, 0x43, 0x41, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d,
+ 0x25, 0x04, 0x22, 0x30, 0x20, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03,
+ 0x02, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03,
+ 0x03, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0x9b, 0xe2, 0x07, 0x57, 0x67, 0x1c, 0x1e, 0xc0, 0x6a, 0x06,
+ 0xde, 0x59, 0xb4, 0x9a, 0x2d, 0xdf, 0xdc, 0x19, 0x86, 0x2e, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x2f, 0x49, 0xb6, 0xf2, 0xb6, 0x5a,
+ 0xa4, 0x95, 0xab, 0x9e, 0x5a, 0xe9, 0x2b, 0x82, 0x9b, 0xcc, 0x90, 0x6b,
+ 0x7c, 0x74, 0x45, 0x20, 0xe7, 0x5e, 0xd8, 0xc7, 0x23, 0xee, 0x28, 0x35,
+ 0xb1, 0x35, 0x65, 0x2a, 0xa5, 0x51, 0xe0, 0x55, 0x3f, 0xf6, 0x83, 0x67,
+ 0xb4, 0xe4, 0x36, 0x29, 0xb0, 0xda, 0xec, 0x97, 0x95, 0xa9, 0x8a, 0x05,
+ 0xa3, 0x45, 0xfe, 0x23, 0x2e, 0x52, 0x88, 0xb4, 0x1f, 0x10, 0x80, 0xad,
+ 0xd2, 0x8b, 0x9f, 0xa3, 0x5f, 0xa8, 0xc5, 0xeb, 0x73, 0xde, 0x79, 0x88,
+ 0x41, 0x98, 0x33, 0xee, 0xa7, 0x60, 0x18, 0xb1, 0x46, 0xc9, 0x40, 0x10,
+ 0x07, 0x9c, 0x8f, 0x0a, 0x52, 0xc9, 0x13, 0x1a, 0x06, 0x6e, 0xa0, 0x9b,
+ 0x2d, 0x3a, 0xf6, 0xae, 0x4f, 0xe7, 0xa3, 0x51, 0x35, 0x2a, 0x5b, 0x18,
+ 0x05, 0x12, 0xe5, 0x51, 0xdc, 0xb6, 0x36, 0x62, 0xf3, 0xe1, 0xa4, 0x0f,
+ 0xfb, 0xe4, 0xcf, 0xc3, 0x94, 0xbf, 0x11, 0xab, 0xa1, 0x59, 0x31, 0x01,
+ 0xf0, 0xcc, 0x53, 0xec, 0x8f, 0x63, 0xd7, 0x6c, 0x96, 0xd3, 0x48, 0x2a,
+ 0x8a, 0x23, 0xed, 0x45, 0x56, 0xa8, 0x66, 0x41, 0xea, 0x01, 0xb9, 0x47,
+ 0xee, 0xa1, 0x47, 0x0c, 0x14, 0xf1, 0x23, 0xe1, 0x20, 0x73, 0xca, 0x7d,
+ 0x50, 0x7c, 0x64, 0x38, 0x57, 0xa3, 0x8f, 0x4a, 0x9c, 0x9b, 0xe9, 0x6d,
+ 0x45, 0xcf, 0x44, 0x6b, 0x4d, 0x60, 0x20, 0x40, 0x71, 0x25, 0xb5, 0x46,
+ 0xaa, 0x6c, 0x08, 0x7e, 0xdf, 0xc8, 0xfa, 0xc8, 0x56, 0x2a, 0x92, 0xcb,
+ 0x83, 0xb8, 0x79, 0x09, 0x97, 0x2d, 0x5e, 0xa1, 0x01, 0xce, 0x06, 0xed,
+ 0xb4, 0x97, 0xc9, 0x04, 0xdc, 0x41, 0xef, 0xe0, 0x4f, 0x36, 0x4d, 0xe4,
+ 0x40, 0x73, 0x46, 0xec, 0x11, 0x12, 0x7c, 0x88, 0x5b, 0x34, 0x26, 0x25,
+ 0x4d, 0xea, 0xdc, 0x18, 0xbe, 0xc5, 0x1b, 0xcd, 0x64, 0xc0,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 52:42:06:4a:4f:37:fe:43:69:48:7a:96:67:ff:5d:27
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Jun 7 08:09:10 2005 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b1:f7:c3:38:3f:b4:a8:7f:cf:39:82:51:67:d0:
+ 6d:9f:d2:ff:58:f3:e7:9f:2b:ec:0d:89:54:99:b9:
+ 38:99:16:f7:e0:21:79:48:c2:bb:61:74:12:96:1d:
+ 3c:6a:72:d5:3c:10:67:3a:39:ed:2b:13:cd:66:eb:
+ 95:09:33:a4:6c:97:b1:e8:c6:ec:c1:75:79:9c:46:
+ 5e:8d:ab:d0:6a:fd:b9:2a:55:17:10:54:b3:19:f0:
+ 9a:f6:f1:b1:5d:b6:a7:6d:fb:e0:71:17:6b:a2:88:
+ fb:00:df:fe:1a:31:77:0c:9a:01:7a:b1:32:e3:2b:
+ 01:07:38:6e:c3:a5:5e:23:bc:45:9b:7b:50:c1:c9:
+ 30:8f:db:e5:2b:7a:d3:5b:fb:33:40:1e:a0:d5:98:
+ 17:bc:8b:87:c3:89:d3:5d:a0:8e:b2:aa:aa:f6:8e:
+ 69:88:06:c5:fa:89:21:f3:08:9d:69:2e:09:33:9b:
+ 29:0d:46:0f:8c:cc:49:34:b0:69:51:bd:f9:06:cd:
+ 68:ad:66:4c:bc:3e:ac:61:bd:0a:88:0e:c8:df:3d:
+ ee:7c:04:4c:9d:0a:5e:6b:91:d6:ee:c7:ed:28:8d:
+ ab:4d:87:89:73:d0:6e:a4:d0:1e:16:8b:14:e1:76:
+ 44:03:7f:63:ac:e4:cd:49:9c:c5:92:f4:ab:32:a1:
+ 48:5b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/AddTrustExternalCARoot.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/AddTrustExternalCARoot.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 60:64:39:59:a2:43:65:2e:fd:f9:1f:d6:ae:33:bb:e8:53:13:
+ c4:88:ee:23:1a:6c:ce:d8:64:59:53:53:90:e8:36:df:d4:fc:
+ f3:4e:79:2f:d5:e6:8f:0c:ef:2a:41:6d:71:bd:9b:78:38:23:
+ d3:70:4b:86:0c:fd:12:a7:22:62:12:d8:cc:e0:51:ef:2d:e5:
+ cd:0c:45:a2:ea:da:ed:7e:ec:f7:32:9a:e7:05:35:5e:6e:c2:
+ 2c:68:68:9d:ff:8c:f1:ca:55:87:c4:2f:b1:40:06:dc:84:22:
+ 5c:6d:b3:cd:d1:9b:1a:0a:33:28:66:16:0c:bd:33:c2:f6:07:
+ f1:e3:a1:79:94:e0:f8:d0:d0:d3:df:52:86:3f:a9:e1:c9:1d:
+ 3e:86:84:b1:db:5f:ee:e4:49:43:c1:39:7d:cf:2f:96:a7:75:
+ 5d:7e:67:67:84:e5:59:20:40:bf:37:22:bf:07:43:b4:30:e1:
+ 43:8a:cd:03:5d:6d:b9:29:d9:84:a7:f5:62:63:84:86:d6:37:
+ be:6f:67:bb:ff:62:57:39:9d:0c:4d:b2:2a:61:3d:1d:9c:ef:
+ 9a:77:20:a0:2f:ee:1a:72:9d:b0:9d:bf:78:13:27:07:0a:60:
+ 11:93:f5:0f:2e:c9:ef:6b:24:83:fe:9b:90:b4:4b:68:81:d0:
+ c2:fa:e0:3f
+-----BEGIN CERTIFICATE-----
+MIIEhjCCA26gAwIBAgIQUkIGSk83/kNpSHqWZ/9dJzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZcxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR8wHQYDVQQDExZVVE4tVVNFUkZpcnN0
+LUhhcmR3YXJlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsffDOD+0
+qH/POYJRZ9Btn9L/WPPnnyvsDYlUmbk4mRb34CF5SMK7YXQSlh08anLVPBBnOjnt
+KxPNZuuVCTOkbJex6MbswXV5nEZejavQav25KlUXEFSzGfCa9vGxXbanbfvgcRdr
+ooj7AN/+GjF3DJoBerEy4ysBBzhuw6VeI7xFm3tQwckwj9vlK3rTW/szQB6g1ZgX
+vIuHw4nTXaCOsqqq9o5piAbF+okh8widaS4JM5spDUYPjMxJNLBpUb35Bs1orWZM
+vD6sYb0KiA7I3z3ufARMnQpea5HW7sftKI2rTYeJc9BupNAeFosU4XZEA39jrOTN
+SZzFkvSrMqFIWwIDAQABo4H0MIHxMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8D
+veAky1QaMB0GA1UdDgQWBBShcl8mGyiYQ5VdBzfVhZadS9LDRTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBgNVHSAECjAIMAYGBFUdIAAwewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQWRkVHJ1c3RFeHRl
+cm5hbENBUm9vdC5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BZGRU
+cnVzdEV4dGVybmFsQ0FSb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAYGQ5WaJD
+ZS79+R/WrjO76FMTxIjuIxpszthkWVNTkOg239T88055L9XmjwzvKkFtcb2beDgj
+03BLhgz9EqciYhLYzOBR7y3lzQxFoura7X7s9zKa5wU1Xm7CLGhonf+M8cpVh8Qv
+sUAG3IQiXG2zzdGbGgozKGYWDL0zwvYH8eOheZTg+NDQ099Shj+p4ckdPoaEsdtf
+7uRJQ8E5fc8vlqd1XX5nZ4TlWSBAvzcivwdDtDDhQ4rNA11tuSnZhKf1YmOEhtY3
+vm9nu/9iVzmdDE2yKmE9HZzvmncgoC/uGnKdsJ2/eBMnBwpgEZP1Dy7J72skg/6b
+kLRLaIHQwvrgPw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert38[] = {
+ 0x30, 0x82, 0x04, 0x86, 0x30, 0x82, 0x03, 0x6e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x52, 0x42, 0x06, 0x4a, 0x4f, 0x37, 0xfe, 0x43, 0x69,
+ 0x48, 0x7a, 0x96, 0x67, 0xff, 0x5d, 0x27, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+ 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+ 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+ 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55,
+ 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+ 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0xf7, 0xc3, 0x38, 0x3f, 0xb4,
+ 0xa8, 0x7f, 0xcf, 0x39, 0x82, 0x51, 0x67, 0xd0, 0x6d, 0x9f, 0xd2, 0xff,
+ 0x58, 0xf3, 0xe7, 0x9f, 0x2b, 0xec, 0x0d, 0x89, 0x54, 0x99, 0xb9, 0x38,
+ 0x99, 0x16, 0xf7, 0xe0, 0x21, 0x79, 0x48, 0xc2, 0xbb, 0x61, 0x74, 0x12,
+ 0x96, 0x1d, 0x3c, 0x6a, 0x72, 0xd5, 0x3c, 0x10, 0x67, 0x3a, 0x39, 0xed,
+ 0x2b, 0x13, 0xcd, 0x66, 0xeb, 0x95, 0x09, 0x33, 0xa4, 0x6c, 0x97, 0xb1,
+ 0xe8, 0xc6, 0xec, 0xc1, 0x75, 0x79, 0x9c, 0x46, 0x5e, 0x8d, 0xab, 0xd0,
+ 0x6a, 0xfd, 0xb9, 0x2a, 0x55, 0x17, 0x10, 0x54, 0xb3, 0x19, 0xf0, 0x9a,
+ 0xf6, 0xf1, 0xb1, 0x5d, 0xb6, 0xa7, 0x6d, 0xfb, 0xe0, 0x71, 0x17, 0x6b,
+ 0xa2, 0x88, 0xfb, 0x00, 0xdf, 0xfe, 0x1a, 0x31, 0x77, 0x0c, 0x9a, 0x01,
+ 0x7a, 0xb1, 0x32, 0xe3, 0x2b, 0x01, 0x07, 0x38, 0x6e, 0xc3, 0xa5, 0x5e,
+ 0x23, 0xbc, 0x45, 0x9b, 0x7b, 0x50, 0xc1, 0xc9, 0x30, 0x8f, 0xdb, 0xe5,
+ 0x2b, 0x7a, 0xd3, 0x5b, 0xfb, 0x33, 0x40, 0x1e, 0xa0, 0xd5, 0x98, 0x17,
+ 0xbc, 0x8b, 0x87, 0xc3, 0x89, 0xd3, 0x5d, 0xa0, 0x8e, 0xb2, 0xaa, 0xaa,
+ 0xf6, 0x8e, 0x69, 0x88, 0x06, 0xc5, 0xfa, 0x89, 0x21, 0xf3, 0x08, 0x9d,
+ 0x69, 0x2e, 0x09, 0x33, 0x9b, 0x29, 0x0d, 0x46, 0x0f, 0x8c, 0xcc, 0x49,
+ 0x34, 0xb0, 0x69, 0x51, 0xbd, 0xf9, 0x06, 0xcd, 0x68, 0xad, 0x66, 0x4c,
+ 0xbc, 0x3e, 0xac, 0x61, 0xbd, 0x0a, 0x88, 0x0e, 0xc8, 0xdf, 0x3d, 0xee,
+ 0x7c, 0x04, 0x4c, 0x9d, 0x0a, 0x5e, 0x6b, 0x91, 0xd6, 0xee, 0xc7, 0xed,
+ 0x28, 0x8d, 0xab, 0x4d, 0x87, 0x89, 0x73, 0xd0, 0x6e, 0xa4, 0xd0, 0x1e,
+ 0x16, 0x8b, 0x14, 0xe1, 0x76, 0x44, 0x03, 0x7f, 0x63, 0xac, 0xe4, 0xcd,
+ 0x49, 0x9c, 0xc5, 0x92, 0xf4, 0xab, 0x32, 0xa1, 0x48, 0x5b, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x81, 0xf4, 0x30, 0x81, 0xf1, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+ 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+ 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+ 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+ 0x45, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x11,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65,
+ 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+ 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x60, 0x64, 0x39, 0x59, 0xa2, 0x43,
+ 0x65, 0x2e, 0xfd, 0xf9, 0x1f, 0xd6, 0xae, 0x33, 0xbb, 0xe8, 0x53, 0x13,
+ 0xc4, 0x88, 0xee, 0x23, 0x1a, 0x6c, 0xce, 0xd8, 0x64, 0x59, 0x53, 0x53,
+ 0x90, 0xe8, 0x36, 0xdf, 0xd4, 0xfc, 0xf3, 0x4e, 0x79, 0x2f, 0xd5, 0xe6,
+ 0x8f, 0x0c, 0xef, 0x2a, 0x41, 0x6d, 0x71, 0xbd, 0x9b, 0x78, 0x38, 0x23,
+ 0xd3, 0x70, 0x4b, 0x86, 0x0c, 0xfd, 0x12, 0xa7, 0x22, 0x62, 0x12, 0xd8,
+ 0xcc, 0xe0, 0x51, 0xef, 0x2d, 0xe5, 0xcd, 0x0c, 0x45, 0xa2, 0xea, 0xda,
+ 0xed, 0x7e, 0xec, 0xf7, 0x32, 0x9a, 0xe7, 0x05, 0x35, 0x5e, 0x6e, 0xc2,
+ 0x2c, 0x68, 0x68, 0x9d, 0xff, 0x8c, 0xf1, 0xca, 0x55, 0x87, 0xc4, 0x2f,
+ 0xb1, 0x40, 0x06, 0xdc, 0x84, 0x22, 0x5c, 0x6d, 0xb3, 0xcd, 0xd1, 0x9b,
+ 0x1a, 0x0a, 0x33, 0x28, 0x66, 0x16, 0x0c, 0xbd, 0x33, 0xc2, 0xf6, 0x07,
+ 0xf1, 0xe3, 0xa1, 0x79, 0x94, 0xe0, 0xf8, 0xd0, 0xd0, 0xd3, 0xdf, 0x52,
+ 0x86, 0x3f, 0xa9, 0xe1, 0xc9, 0x1d, 0x3e, 0x86, 0x84, 0xb1, 0xdb, 0x5f,
+ 0xee, 0xe4, 0x49, 0x43, 0xc1, 0x39, 0x7d, 0xcf, 0x2f, 0x96, 0xa7, 0x75,
+ 0x5d, 0x7e, 0x67, 0x67, 0x84, 0xe5, 0x59, 0x20, 0x40, 0xbf, 0x37, 0x22,
+ 0xbf, 0x07, 0x43, 0xb4, 0x30, 0xe1, 0x43, 0x8a, 0xcd, 0x03, 0x5d, 0x6d,
+ 0xb9, 0x29, 0xd9, 0x84, 0xa7, 0xf5, 0x62, 0x63, 0x84, 0x86, 0xd6, 0x37,
+ 0xbe, 0x6f, 0x67, 0xbb, 0xff, 0x62, 0x57, 0x39, 0x9d, 0x0c, 0x4d, 0xb2,
+ 0x2a, 0x61, 0x3d, 0x1d, 0x9c, 0xef, 0x9a, 0x77, 0x20, 0xa0, 0x2f, 0xee,
+ 0x1a, 0x72, 0x9d, 0xb0, 0x9d, 0xbf, 0x78, 0x13, 0x27, 0x07, 0x0a, 0x60,
+ 0x11, 0x93, 0xf5, 0x0f, 0x2e, 0xc9, 0xef, 0x6b, 0x24, 0x83, 0xfe, 0x9b,
+ 0x90, 0xb4, 0x4b, 0x68, 0x81, 0xd0, 0xc2, 0xfa, 0xe0, 0x3f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 04:00:00:00:00:01:2f:4e:e1:42:f9
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA
+ Validity
+ Not Before: Apr 13 10:00:00 2011 GMT
+ Not After : Apr 13 10:00:00 2022 GMT
+ Subject: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:dd:35:1d:f2:20:54:26:1a:d0:ef:a5:6f:81:76:
+ 59:70:dc:e7:f4:d4:03:24:1f:24:0e:9d:22:9f:d4:
+ 27:32:7a:2b:7c:ee:8b:e3:61:62:38:17:af:b4:4b:
+ 7a:9f:67:21:1c:2d:95:54:ba:79:ba:b6:c4:f2:0d:
+ 21:74:17:67:74:e2:b1:64:08:99:60:78:fb:67:c2:
+ 4b:f7:27:8d:6f:36:76:cf:31:8c:e5:f1:06:d7:dc:
+ 57:0e:5b:ac:ee:ce:2d:ab:aa:a9:70:2f:02:86:c8:
+ b1:d0:08:07:95:ea:2a:ec:d1:9e:e4:36:5c:3b:a6:
+ 36:b5:43:8b:ab:f7:8e:3e:00:1b:ff:85:59:6b:62:
+ 01:8d:82:e8:4a:ba:38:b3:e0:c3:f4:6d:19:a7:ea:
+ 05:dd:84:67:c2:66:c7:24:02:73:5a:b5:ee:a4:19:
+ d9:fc:00:ce:b6:a4:8d:df:7e:bd:5f:b2:3a:9d:84:
+ 31:4f:c8:63:0c:e4:d8:0d:52:a3:7e:01:1b:d4:67:
+ a5:18:28:eb:01:a7:82:3c:d9:8e:1d:e5:47:0d:ba:
+ 8b:59:14:a3:1f:1f:4b:ea:e2:27:46:86:ce:9d:39:
+ c4:66:41:a7:e2:15:23:6b:56:47:c1:ed:c5:53:e4:
+ d4:80:1f:6b:fa:80:46:98:b2:09:a6:0f:95:be:66:
+ 88:93
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 5D:46:B2:8D:C4:4B:74:1C:BB:ED:F5:73:B6:3A:B7:38:8F:75:9E:7E
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.globalsign.com/repository/
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.globalsign.net/root.crl
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.globalsign.com/rootr1
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Microsoft Server Gated Crypto
+ X509v3 Authority Key Identifier:
+ keyid:60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 73:7a:ec:01:2c:17:22:91:9a:ca:b1:67:18:a2:ba:c8:05:89:
+ 92:24:de:1f:b8:ab:44:9f:f7:40:55:65:f2:e0:f4:2e:c7:de:
+ b0:3f:99:15:1f:95:70:82:e9:9b:4a:64:24:20:16:f0:76:17:
+ d2:1b:fe:ac:fa:06:b4:77:cf:98:d8:2a:ec:57:15:d8:5e:4e:
+ dd:8b:96:e1:53:33:19:91:d5:84:6e:25:ef:0f:cb:ad:bf:db:
+ 4b:6b:56:cc:b5:d4:40:3e:26:5e:b6:59:f4:c5:90:c9:09:c4:
+ 84:df:bc:26:7d:82:e9:eb:f4:5b:fc:c8:15:de:09:18:45:86:
+ b3:8b:4d:c7:6b:35:27:9b:60:f6:a4:5a:2a:58:49:b1:d8:35:
+ 43:c6:32:bb:5e:3b:c4:4a:21:c1:a0:3b:5e:c1:23:a9:ce:db:
+ d5:ba:fe:5d:6d:fd:00:7e:fa:f1:94:37:61:b9:00:39:66:96:
+ a9:9c:b4:1e:11:ef:55:d8:b4:d8:b0:c4:a5:ae:32:0a:2f:f8:
+ 2d:f4:a2:a7:ff:36:d3:5e:63:8b:4e:12:f7:b5:28:80:75:ee:
+ 94:2f:70:a0:56:77:39:aa:39:97:17:fc:00:f3:cf:66:e7:a2:
+ 71:92:ab:05:9b:73:2e:7a:e7:e7:21:59:09:8d:30:a1:ac:5c:
+ ca:19:7a:f8
+-----BEGIN CERTIFICATE-----
+MIIEizCCA3OgAwIBAgILBAAAAAABL07hQvkwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw0xMTA0MTMxMDAw
+MDBaFw0yMjA0MTMxMDAwMDBaMF0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMTMwMQYDVQQDEypHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBW
+YWxpZGF0aW9uIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDdNR3yIFQmGtDvpW+Bdllw3Of01AMkHyQOnSKf1Ccyeit87ovjYWI4F6+0S3qf
+ZyEcLZVUunm6tsTyDSF0F2d04rFkCJlgePtnwkv3J41vNnbPMYzl8QbX3FcOW6zu
+zi2rqqlwLwKGyLHQCAeV6irs0Z7kNlw7pja1Q4ur944+ABv/hVlrYgGNguhKujiz
+4MP0bRmn6gXdhGfCZsckAnNate6kGdn8AM62pI3ffr1fsjqdhDFPyGMM5NgNUqN+
+ARvUZ6UYKOsBp4I82Y4d5UcNuotZFKMfH0vq4idGhs6dOcRmQafiFSNrVkfB7cVT
+5NSAH2v6gEaYsgmmD5W+ZoiTAgMBAAGjggFQMIIBTDAOBgNVHQ8BAf8EBAMCAQYw
+EgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUXUayjcRLdBy77fVztjq3OI91
+nn4wRwYDVR0gBEAwPjA8BgRVHSAAMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3
+Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMDMGA1UdHwQsMCowKKAmoCSGImh0
+dHA6Ly9jcmwuZ2xvYmFsc2lnbi5uZXQvcm9vdC5jcmwwPQYIKwYBBQUHAQEEMTAv
+MC0GCCsGAQUFBzABhiFodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNvbS9yb290cjEw
+KQYDVR0lBCIwIAYIKwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3CgMDMB8GA1Ud
+IwQYMBaAFGB7ZhpFDZfKiVAvfQTNNKj//P1LMA0GCSqGSIb3DQEBBQUAA4IBAQBz
+euwBLBcikZrKsWcYorrIBYmSJN4fuKtEn/dAVWXy4PQux96wP5kVH5VwgumbSmQk
+IBbwdhfSG/6s+ga0d8+Y2CrsVxXYXk7di5bhUzMZkdWEbiXvD8utv9tLa1bMtdRA
+PiZetln0xZDJCcSE37wmfYLp6/Rb/MgV3gkYRYazi03HazUnm2D2pFoqWEmx2DVD
+xjK7XjvESiHBoDtewSOpztvVuv5dbf0AfvrxlDdhuQA5ZpapnLQeEe9V2LTYsMSl
+rjIKL/gt9KKn/zbTXmOLThL3tSiAde6UL3CgVnc5qjmXF/wA889m56JxkqsFm3Mu
+eufnIVkJjTChrFzKGXr4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert39[] = {
+ 0x30, 0x82, 0x04, 0x8b, 0x30, 0x82, 0x03, 0x73, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x4e, 0xe1,
+ 0x42, 0xf9, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x57, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61, 0x31,
+ 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x52, 0x6f,
+ 0x6f, 0x74, 0x20, 0x43, 0x41, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69,
+ 0x67, 0x6e, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x31, 0x30, 0x34, 0x31, 0x33, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x31, 0x33, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x5d, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x42, 0x45, 0x31, 0x19, 0x30,
+ 0x17, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x10, 0x47, 0x6c, 0x6f, 0x62,
+ 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 0x76, 0x2d, 0x73, 0x61,
+ 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x47,
+ 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x4f, 0x72,
+ 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41,
+ 0x20, 0x2d, 0x20, 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xdd, 0x35, 0x1d, 0xf2, 0x20, 0x54, 0x26, 0x1a, 0xd0, 0xef,
+ 0xa5, 0x6f, 0x81, 0x76, 0x59, 0x70, 0xdc, 0xe7, 0xf4, 0xd4, 0x03, 0x24,
+ 0x1f, 0x24, 0x0e, 0x9d, 0x22, 0x9f, 0xd4, 0x27, 0x32, 0x7a, 0x2b, 0x7c,
+ 0xee, 0x8b, 0xe3, 0x61, 0x62, 0x38, 0x17, 0xaf, 0xb4, 0x4b, 0x7a, 0x9f,
+ 0x67, 0x21, 0x1c, 0x2d, 0x95, 0x54, 0xba, 0x79, 0xba, 0xb6, 0xc4, 0xf2,
+ 0x0d, 0x21, 0x74, 0x17, 0x67, 0x74, 0xe2, 0xb1, 0x64, 0x08, 0x99, 0x60,
+ 0x78, 0xfb, 0x67, 0xc2, 0x4b, 0xf7, 0x27, 0x8d, 0x6f, 0x36, 0x76, 0xcf,
+ 0x31, 0x8c, 0xe5, 0xf1, 0x06, 0xd7, 0xdc, 0x57, 0x0e, 0x5b, 0xac, 0xee,
+ 0xce, 0x2d, 0xab, 0xaa, 0xa9, 0x70, 0x2f, 0x02, 0x86, 0xc8, 0xb1, 0xd0,
+ 0x08, 0x07, 0x95, 0xea, 0x2a, 0xec, 0xd1, 0x9e, 0xe4, 0x36, 0x5c, 0x3b,
+ 0xa6, 0x36, 0xb5, 0x43, 0x8b, 0xab, 0xf7, 0x8e, 0x3e, 0x00, 0x1b, 0xff,
+ 0x85, 0x59, 0x6b, 0x62, 0x01, 0x8d, 0x82, 0xe8, 0x4a, 0xba, 0x38, 0xb3,
+ 0xe0, 0xc3, 0xf4, 0x6d, 0x19, 0xa7, 0xea, 0x05, 0xdd, 0x84, 0x67, 0xc2,
+ 0x66, 0xc7, 0x24, 0x02, 0x73, 0x5a, 0xb5, 0xee, 0xa4, 0x19, 0xd9, 0xfc,
+ 0x00, 0xce, 0xb6, 0xa4, 0x8d, 0xdf, 0x7e, 0xbd, 0x5f, 0xb2, 0x3a, 0x9d,
+ 0x84, 0x31, 0x4f, 0xc8, 0x63, 0x0c, 0xe4, 0xd8, 0x0d, 0x52, 0xa3, 0x7e,
+ 0x01, 0x1b, 0xd4, 0x67, 0xa5, 0x18, 0x28, 0xeb, 0x01, 0xa7, 0x82, 0x3c,
+ 0xd9, 0x8e, 0x1d, 0xe5, 0x47, 0x0d, 0xba, 0x8b, 0x59, 0x14, 0xa3, 0x1f,
+ 0x1f, 0x4b, 0xea, 0xe2, 0x27, 0x46, 0x86, 0xce, 0x9d, 0x39, 0xc4, 0x66,
+ 0x41, 0xa7, 0xe2, 0x15, 0x23, 0x6b, 0x56, 0x47, 0xc1, 0xed, 0xc5, 0x53,
+ 0xe4, 0xd4, 0x80, 0x1f, 0x6b, 0xfa, 0x80, 0x46, 0x98, 0xb2, 0x09, 0xa6,
+ 0x0f, 0x95, 0xbe, 0x66, 0x88, 0x93, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x50, 0x30, 0x82, 0x01, 0x4c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5d, 0x46, 0xb2, 0x8d, 0xc4, 0x4b,
+ 0x74, 0x1c, 0xbb, 0xed, 0xf5, 0x73, 0xb6, 0x3a, 0xb7, 0x38, 0x8f, 0x75,
+ 0x9e, 0x7e, 0x30, 0x47, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x40, 0x30,
+ 0x3e, 0x30, 0x3c, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x34, 0x30,
+ 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+ 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2f, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+ 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3d, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f,
+ 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x31, 0x30,
+ 0x29, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x22, 0x30, 0x20, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x60, 0x7b, 0x66, 0x1a, 0x45,
+ 0x0d, 0x97, 0xca, 0x89, 0x50, 0x2f, 0x7d, 0x04, 0xcd, 0x34, 0xa8, 0xff,
+ 0xfc, 0xfd, 0x4b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x73,
+ 0x7a, 0xec, 0x01, 0x2c, 0x17, 0x22, 0x91, 0x9a, 0xca, 0xb1, 0x67, 0x18,
+ 0xa2, 0xba, 0xc8, 0x05, 0x89, 0x92, 0x24, 0xde, 0x1f, 0xb8, 0xab, 0x44,
+ 0x9f, 0xf7, 0x40, 0x55, 0x65, 0xf2, 0xe0, 0xf4, 0x2e, 0xc7, 0xde, 0xb0,
+ 0x3f, 0x99, 0x15, 0x1f, 0x95, 0x70, 0x82, 0xe9, 0x9b, 0x4a, 0x64, 0x24,
+ 0x20, 0x16, 0xf0, 0x76, 0x17, 0xd2, 0x1b, 0xfe, 0xac, 0xfa, 0x06, 0xb4,
+ 0x77, 0xcf, 0x98, 0xd8, 0x2a, 0xec, 0x57, 0x15, 0xd8, 0x5e, 0x4e, 0xdd,
+ 0x8b, 0x96, 0xe1, 0x53, 0x33, 0x19, 0x91, 0xd5, 0x84, 0x6e, 0x25, 0xef,
+ 0x0f, 0xcb, 0xad, 0xbf, 0xdb, 0x4b, 0x6b, 0x56, 0xcc, 0xb5, 0xd4, 0x40,
+ 0x3e, 0x26, 0x5e, 0xb6, 0x59, 0xf4, 0xc5, 0x90, 0xc9, 0x09, 0xc4, 0x84,
+ 0xdf, 0xbc, 0x26, 0x7d, 0x82, 0xe9, 0xeb, 0xf4, 0x5b, 0xfc, 0xc8, 0x15,
+ 0xde, 0x09, 0x18, 0x45, 0x86, 0xb3, 0x8b, 0x4d, 0xc7, 0x6b, 0x35, 0x27,
+ 0x9b, 0x60, 0xf6, 0xa4, 0x5a, 0x2a, 0x58, 0x49, 0xb1, 0xd8, 0x35, 0x43,
+ 0xc6, 0x32, 0xbb, 0x5e, 0x3b, 0xc4, 0x4a, 0x21, 0xc1, 0xa0, 0x3b, 0x5e,
+ 0xc1, 0x23, 0xa9, 0xce, 0xdb, 0xd5, 0xba, 0xfe, 0x5d, 0x6d, 0xfd, 0x00,
+ 0x7e, 0xfa, 0xf1, 0x94, 0x37, 0x61, 0xb9, 0x00, 0x39, 0x66, 0x96, 0xa9,
+ 0x9c, 0xb4, 0x1e, 0x11, 0xef, 0x55, 0xd8, 0xb4, 0xd8, 0xb0, 0xc4, 0xa5,
+ 0xae, 0x32, 0x0a, 0x2f, 0xf8, 0x2d, 0xf4, 0xa2, 0xa7, 0xff, 0x36, 0xd3,
+ 0x5e, 0x63, 0x8b, 0x4e, 0x12, 0xf7, 0xb5, 0x28, 0x80, 0x75, 0xee, 0x94,
+ 0x2f, 0x70, 0xa0, 0x56, 0x77, 0x39, 0xaa, 0x39, 0x97, 0x17, 0xfc, 0x00,
+ 0xf3, 0xcf, 0x66, 0xe7, 0xa2, 0x71, 0x92, 0xab, 0x05, 0x9b, 0x73, 0x2e,
+ 0x7a, 0xe7, 0xe7, 0x21, 0x59, 0x09, 0x8d, 0x30, 0xa1, 0xac, 0x5c, 0xca,
+ 0x19, 0x7a, 0xf8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 67109810 (0x40003b2)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Dec 15 20:32:00 2004 GMT
+ Not After : Dec 15 23:59:00 2014 GMT
+ Subject: C=IT, O=I.T. Telecom, OU=Servizi di certificazione, CN=I.T. Telecom Global CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c3:14:14:be:cd:a1:7e:89:2d:ab:8f:e5:ab:5c:
+ 2e:70:a1:e3:cb:08:2e:07:2c:a9:38:f8:9a:1b:3b:
+ 02:32:43:47:e7:fd:98:04:83:2a:23:34:8b:a4:3c:
+ 92:1b:95:fb:ae:bb:5a:70:c9:92:5e:86:09:64:c6:
+ 42:c2:f3:6c:3c:18:35:8b:5f:f1:c9:ed:3d:72:cb:
+ 4b:3f:ed:61:c3:5f:dd:2c:38:27:e9:73:1d:04:e9:
+ 35:c7:7f:4f:92:a9:c2:f6:b9:a9:6d:05:0d:5b:02:
+ bf:c7:0c:a9:0d:a2:5f:15:48:30:79:b7:ab:77:48:
+ 51:13:83:1f:0e:72:07:4a:75:13:be:b0:90:3f:c6:
+ 8f:17:03:32:55:76:1f:2f:3b:c1:ee:53:35:1d:33:
+ 86:25:6c:81:1b:eb:10:fe:d2:ab:b5:1b:3c:38:fd:
+ 90:71:02:70:3b:09:3a:c7:71:4a:c2:51:65:7c:f5:
+ 59:08:e1:4a:83:d4:5f:d0:10:1c:7b:f6:3c:95:24:
+ 99:59:2c:df:14:59:28:83:bf:f8:e8:52:83:c8:12:
+ 65:0f:95:a5:94:1c:02:d8:b7:aa:2d:6c:e5:59:53:
+ 65:f4:51:d7:48:96:2e:60:9c:59:9c:51:d0:97:b3:
+ 9e:38:91:ab:b3:7a:5e:b4:3b:bb:c8:62:3f:c0:b8:
+ 69:27
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ 3E:81:72:D4:41:E6:9F:BC:B0:37:DA:3F:68:0E:7A:86:0B:EF:55:74
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://www.public-trust.com/CPS/OmniRoot.html
+ Policy: 1.3.76.12.1.1.3
+ CPS: https://www.tipki.com/GlobalCA/CPS
+
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 Key Usage: critical
+ Digital Signature, Non Repudiation, Key Encipherment, Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ Signature Algorithm: sha1WithRSAEncryption
+ 84:5b:69:ea:0c:c7:1e:de:b2:ca:fe:e8:26:ae:36:94:00:09:
+ 25:69:07:93:e3:c3:96:4b:97:4c:a6:79:38:d2:df:dc:41:7b:
+ d8:61:70:ec:c0:36:8c:8c:b1:65:ac:d6:e4:32:b9:7c:3c:04:
+ 79:72:af:87:0a:58:19:25:f7:25:b7:1d:49:c9:8b:33:54:43:
+ c4:8b:09:26:1c:a9:0c:50:e3:df:92:c5:68:6c:05:2d:c6:d7:
+ 23:13:9e:bf:46:36:10:8d:f1:27:c1:74:a8:f5:0f:c6:b8:e2:
+ 91:3c:2d:4c:fb:9d:da:1e:e4:a5:87:80:a6:ce:43:b7:14:7d:
+ 9f:38
+-----BEGIN CERTIFICATE-----
+MIIEizCCA/SgAwIBAgIEBAADsjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTA0MTIxNTIwMzIwMFoXDTE0MTIxNTIzNTkwMFowaTELMAkG
+A1UEBhMCSVQxFTATBgNVBAoTDEkuVC4gVGVsZWNvbTEiMCAGA1UECxMZU2Vydml6
+aSBkaSBjZXJ0aWZpY2F6aW9uZTEfMB0GA1UEAxMWSS5ULiBUZWxlY29tIEdsb2Jh
+bCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMUFL7NoX6JLauP
+5atcLnCh48sILgcsqTj4mhs7AjJDR+f9mASDKiM0i6Q8khuV+667WnDJkl6GCWTG
+QsLzbDwYNYtf8cntPXLLSz/tYcNf3Sw4J+lzHQTpNcd/T5Kpwva5qW0FDVsCv8cM
+qQ2iXxVIMHm3q3dIURODHw5yB0p1E76wkD/GjxcDMlV2Hy87we5TNR0zhiVsgRvr
+EP7Sq7UbPDj9kHECcDsJOsdxSsJRZXz1WQjhSoPUX9AQHHv2PJUkmVks3xRZKIO/
++OhSg8gSZQ+VpZQcAti3qi1s5VlTZfRR10iWLmCcWZxR0JeznjiRq7N6XrQ7u8hi
+P8C4aScCAwEAAaOCAa4wggGqMEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cu
+cHVibGljLXRydXN0LmNvbS9jZ2ktYmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0O
+BBYEFD6BctRB5p+8sDfaP2gOeoYL71V0MIGRBgNVHSAEgYkwgYYwSAYJKwYBBAGx
+PgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9D
+UFMvT21uaVJvb3QuaHRtbDA6BgYrTAwBAQMwMDAuBggrBgEFBQcCARYiaHR0cHM6
+Ly93d3cudGlwa2kuY29tL0dsb2JhbENBL0NQUzCBiQYDVR0jBIGBMH+heaR3MHUx
+CzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsT
+HkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5
+YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMA4GA1UdDwEB/wQEAwIB5jASBgNVHRMB
+Af8ECDAGAQH/AgEBMA0GCSqGSIb3DQEBBQUAA4GBAIRbaeoMxx7essr+6CauNpQA
+CSVpB5Pjw5ZLl0ymeTjS39xBe9hhcOzANoyMsWWs1uQyuXw8BHlyr4cKWBkl9yW3
+HUnJizNUQ8SLCSYcqQxQ49+SxWhsBS3G1yMTnr9GNhCN8SfBdKj1D8a44pE8LUz7
+ndoe5KWHgKbOQ7cUfZ84
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert40[] = {
+ 0x30, 0x82, 0x04, 0x8b, 0x30, 0x82, 0x03, 0xf4, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x04, 0x00, 0x03, 0xb2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x30, 0x34, 0x31, 0x32, 0x31, 0x35, 0x32, 0x30, 0x33, 0x32, 0x30,
+ 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x32, 0x31, 0x35, 0x32, 0x33,
+ 0x35, 0x39, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x54, 0x31, 0x15, 0x30, 0x13,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x49, 0x2e, 0x54, 0x2e, 0x20,
+ 0x54, 0x65, 0x6c, 0x65, 0x63, 0x6f, 0x6d, 0x31, 0x22, 0x30, 0x20, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x19, 0x53, 0x65, 0x72, 0x76, 0x69, 0x7a,
+ 0x69, 0x20, 0x64, 0x69, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x7a, 0x69, 0x6f, 0x6e, 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x49, 0x2e, 0x54, 0x2e, 0x20, 0x54,
+ 0x65, 0x6c, 0x65, 0x63, 0x6f, 0x6d, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61,
+ 0x6c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xc3, 0x14, 0x14, 0xbe, 0xcd, 0xa1, 0x7e, 0x89, 0x2d, 0xab, 0x8f,
+ 0xe5, 0xab, 0x5c, 0x2e, 0x70, 0xa1, 0xe3, 0xcb, 0x08, 0x2e, 0x07, 0x2c,
+ 0xa9, 0x38, 0xf8, 0x9a, 0x1b, 0x3b, 0x02, 0x32, 0x43, 0x47, 0xe7, 0xfd,
+ 0x98, 0x04, 0x83, 0x2a, 0x23, 0x34, 0x8b, 0xa4, 0x3c, 0x92, 0x1b, 0x95,
+ 0xfb, 0xae, 0xbb, 0x5a, 0x70, 0xc9, 0x92, 0x5e, 0x86, 0x09, 0x64, 0xc6,
+ 0x42, 0xc2, 0xf3, 0x6c, 0x3c, 0x18, 0x35, 0x8b, 0x5f, 0xf1, 0xc9, 0xed,
+ 0x3d, 0x72, 0xcb, 0x4b, 0x3f, 0xed, 0x61, 0xc3, 0x5f, 0xdd, 0x2c, 0x38,
+ 0x27, 0xe9, 0x73, 0x1d, 0x04, 0xe9, 0x35, 0xc7, 0x7f, 0x4f, 0x92, 0xa9,
+ 0xc2, 0xf6, 0xb9, 0xa9, 0x6d, 0x05, 0x0d, 0x5b, 0x02, 0xbf, 0xc7, 0x0c,
+ 0xa9, 0x0d, 0xa2, 0x5f, 0x15, 0x48, 0x30, 0x79, 0xb7, 0xab, 0x77, 0x48,
+ 0x51, 0x13, 0x83, 0x1f, 0x0e, 0x72, 0x07, 0x4a, 0x75, 0x13, 0xbe, 0xb0,
+ 0x90, 0x3f, 0xc6, 0x8f, 0x17, 0x03, 0x32, 0x55, 0x76, 0x1f, 0x2f, 0x3b,
+ 0xc1, 0xee, 0x53, 0x35, 0x1d, 0x33, 0x86, 0x25, 0x6c, 0x81, 0x1b, 0xeb,
+ 0x10, 0xfe, 0xd2, 0xab, 0xb5, 0x1b, 0x3c, 0x38, 0xfd, 0x90, 0x71, 0x02,
+ 0x70, 0x3b, 0x09, 0x3a, 0xc7, 0x71, 0x4a, 0xc2, 0x51, 0x65, 0x7c, 0xf5,
+ 0x59, 0x08, 0xe1, 0x4a, 0x83, 0xd4, 0x5f, 0xd0, 0x10, 0x1c, 0x7b, 0xf6,
+ 0x3c, 0x95, 0x24, 0x99, 0x59, 0x2c, 0xdf, 0x14, 0x59, 0x28, 0x83, 0xbf,
+ 0xf8, 0xe8, 0x52, 0x83, 0xc8, 0x12, 0x65, 0x0f, 0x95, 0xa5, 0x94, 0x1c,
+ 0x02, 0xd8, 0xb7, 0xaa, 0x2d, 0x6c, 0xe5, 0x59, 0x53, 0x65, 0xf4, 0x51,
+ 0xd7, 0x48, 0x96, 0x2e, 0x60, 0x9c, 0x59, 0x9c, 0x51, 0xd0, 0x97, 0xb3,
+ 0x9e, 0x38, 0x91, 0xab, 0xb3, 0x7a, 0x5e, 0xb4, 0x3b, 0xbb, 0xc8, 0x62,
+ 0x3f, 0xc0, 0xb8, 0x69, 0x27, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0xae, 0x30, 0x82, 0x01, 0xaa, 0x30, 0x45, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38, 0xa0, 0x36, 0x86,
+ 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d, 0x62, 0x69, 0x6e,
+ 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38, 0x2f, 0x63, 0x64,
+ 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0x3e, 0x81, 0x72, 0xd4, 0x41, 0xe6, 0x9f, 0xbc,
+ 0xb0, 0x37, 0xda, 0x3f, 0x68, 0x0e, 0x7a, 0x86, 0x0b, 0xef, 0x55, 0x74,
+ 0x30, 0x81, 0x91, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x81, 0x89, 0x30,
+ 0x81, 0x86, 0x30, 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1,
+ 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+ 0x2d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43,
+ 0x50, 0x53, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x52, 0x6f, 0x6f, 0x74, 0x2e,
+ 0x68, 0x74, 0x6d, 0x6c, 0x30, 0x3a, 0x06, 0x06, 0x2b, 0x4c, 0x0c, 0x01,
+ 0x01, 0x03, 0x30, 0x30, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x01, 0x16, 0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x69, 0x70, 0x6b, 0x69, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x43, 0x41,
+ 0x2f, 0x43, 0x50, 0x53, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x81, 0x81, 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47,
+ 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+ 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79,
+ 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f,
+ 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+ 0x03, 0x02, 0x01, 0xe6, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x84, 0x5b, 0x69, 0xea, 0x0c,
+ 0xc7, 0x1e, 0xde, 0xb2, 0xca, 0xfe, 0xe8, 0x26, 0xae, 0x36, 0x94, 0x00,
+ 0x09, 0x25, 0x69, 0x07, 0x93, 0xe3, 0xc3, 0x96, 0x4b, 0x97, 0x4c, 0xa6,
+ 0x79, 0x38, 0xd2, 0xdf, 0xdc, 0x41, 0x7b, 0xd8, 0x61, 0x70, 0xec, 0xc0,
+ 0x36, 0x8c, 0x8c, 0xb1, 0x65, 0xac, 0xd6, 0xe4, 0x32, 0xb9, 0x7c, 0x3c,
+ 0x04, 0x79, 0x72, 0xaf, 0x87, 0x0a, 0x58, 0x19, 0x25, 0xf7, 0x25, 0xb7,
+ 0x1d, 0x49, 0xc9, 0x8b, 0x33, 0x54, 0x43, 0xc4, 0x8b, 0x09, 0x26, 0x1c,
+ 0xa9, 0x0c, 0x50, 0xe3, 0xdf, 0x92, 0xc5, 0x68, 0x6c, 0x05, 0x2d, 0xc6,
+ 0xd7, 0x23, 0x13, 0x9e, 0xbf, 0x46, 0x36, 0x10, 0x8d, 0xf1, 0x27, 0xc1,
+ 0x74, 0xa8, 0xf5, 0x0f, 0xc6, 0xb8, 0xe2, 0x91, 0x3c, 0x2d, 0x4c, 0xfb,
+ 0x9d, 0xda, 0x1e, 0xe4, 0xa5, 0x87, 0x80, 0xa6, 0xce, 0x43, 0xb7, 0x14,
+ 0x7d, 0x9f, 0x38,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 76:10:12:8a:17:b6:82:bb:3a:1f:9d:1a:9a:35:c0:92
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+ Validity
+ Not Before: Feb 18 00:00:00 2010 GMT
+ Not After : Feb 17 23:59:59 2020 GMT
+ Subject: C=US, O=Thawte, Inc., OU=Domain Validated SSL, CN=Thawte DV SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cb:98:c9:36:3f:d2:9c:d8:16:07:d4:49:63:f9:
+ 83:b0:e8:02:2d:cc:5c:5a:74:97:a6:13:ef:13:13:
+ de:05:7c:a7:e6:ca:00:23:da:39:f9:ef:13:cf:52:
+ c5:af:9a:e3:ca:be:f3:82:d9:8b:3d:aa:e1:cc:ae:
+ 88:50:66:a3:2d:ec:61:14:75:49:ab:0e:24:f1:ac:
+ 44:5b:0b:28:a2:33:20:76:1e:06:60:6a:67:05:71:
+ 8b:ba:66:62:16:7a:b3:6d:0d:c7:d0:94:40:c6:8c:
+ 3d:1e:92:0c:62:34:0d:44:89:d5:f7:89:fe:29:ed:
+ 18:8f:f6:9b:2b:08:f7:6a:ab:d8:48:97:5a:f4:9f:
+ ed:0c:75:52:22:f7:d5:5e:84:00:9f:c0:4a:0d:31:
+ 77:4c:64:d0:12:e6:0f:3a:f0:a1:c0:d5:5c:1d:e7:
+ 5f:2d:c2:f7:d6:36:18:d9:95:6e:44:4e:c9:58:14:
+ 4d:b6:8e:bb:cd:de:62:1e:fa:5b:b5:bd:18:2b:98:
+ ac:ac:93:3f:50:5a:f5:14:0b:a2:cf:b6:f3:9e:4f:
+ 5a:cd:5a:c3:36:23:da:1a:af:b0:4d:d6:4a:22:03:
+ 8f:43:02:19:bd:ea:ac:dd:c4:7a:35:32:14:f1:72:
+ 2e:08:55:40:0c:f4:07:41:41:af:38:37:84:29:42:
+ b2:55
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://ocsp.thawte.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.thawte.com/ThawtePCA.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-11
+ X509v3 Subject Key Identifier:
+ AB:44:E4:5D:EC:83:C7:D9:C0:85:9F:F7:E1:C6:97:90:B0:8C:3F:98
+ X509v3 Authority Key Identifier:
+ keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 04:ba:fb:ac:bb:fc:4b:54:11:a3:2d:88:b3:3c:bd:00:6d:8a:
+ 1a:b6:8d:c4:c1:83:f8:c7:53:2a:c1:32:6e:3a:81:a1:54:7d:
+ da:1a:3f:3a:45:4f:36:e7:42:b0:0a:42:85:97:a0:ac:fb:e5:
+ 87:a7:83:4f:e8:b1:b7:9b:58:65:6e:26:80:0b:92:4d:47:55:
+ b9:61:16:51:65:e9:2b:f1:68:d9:58:b8:03:81:d1:b7:66:1c:
+ d3:bc:c5:a6:7b:5f:3e:c5:38:46:76:e7:75:b4:a0:0c:4b:ce:
+ a2:c2:a9:c1:cc:36:73:7b:fb:b9:24:24:a0:5e:a7:f6:fa:bb:
+ 0c:28:43:9e:1d:f0:4e:f0:3f:d8:24:b0:21:dc:6d:2d:ee:bf:
+ 5a:3b:fa:88:9c:74:6c:af:21:dd:92:ec:c3:15:ef:94:75:26:
+ 46:d6:a6:3f:bf:66:48:aa:1d:ef:dd:27:e6:b7:51:89:38:7d:
+ 13:84:0c:40:fc:d0:b5:f1:e0:db:f9:4f:2f:40:1c:b4:8e:47:
+ 22:61:b8:4c:96:de:f0:5f:11:7e:4f:11:d9:ec:50:47:22:0e:
+ c5:1d:e2:64:49:e7:68:63:45:3a:8a:d9:71:f4:5e:f1:6e:b7:
+ 14:4d:3e:6f:14:1e:dc:52:fe:bc:df:0c:bd:29:3f:76:fb:11:
+ 5f:68:68:15
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIQdhASihe2grs6H50amjXAkjANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMTAwMjE4MDAwMDAwWhcNMjAw
+MjE3MjM1OTU5WjBeMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMu
+MR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEZMBcGA1UEAxMQVGhhd3Rl
+IERWIFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMuYyTY/
+0pzYFgfUSWP5g7DoAi3MXFp0l6YT7xMT3gV8p+bKACPaOfnvE89Sxa+a48q+84LZ
+iz2q4cyuiFBmoy3sYRR1SasOJPGsRFsLKKIzIHYeBmBqZwVxi7pmYhZ6s20Nx9CU
+QMaMPR6SDGI0DUSJ1feJ/intGI/2mysI92qr2EiXWvSf7Qx1UiL31V6EAJ/ASg0x
+d0xk0BLmDzrwocDVXB3nXy3C99Y2GNmVbkROyVgUTbaOu83eYh76W7W9GCuYrKyT
+P1Ba9RQLos+2855PWs1awzYj2hqvsE3WSiIDj0MCGb3qrN3EejUyFPFyLghVQAz0
+B0FBrzg3hClCslUCAwEAAaOB/DCB+TAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUH
+MAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgwBgEB/wIBADA0
+BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vY3JsLnRoYXd0ZS5jb20vVGhhd3RlUENB
+LmNybDAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwxGjAYBgNVBAMTEVZl
+cmlTaWduTVBLSS0yLTExMB0GA1UdDgQWBBSrRORd7IPH2cCFn/fhxpeQsIw/mDAf
+BgNVHSMEGDAWgBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOC
+AQEABLr7rLv8S1QRoy2Iszy9AG2KGraNxMGD+MdTKsEybjqBoVR92ho/OkVPNudC
+sApChZegrPvlh6eDT+ixt5tYZW4mgAuSTUdVuWEWUWXpK/Fo2Vi4A4HRt2Yc07zF
+pntfPsU4RnbndbSgDEvOosKpwcw2c3v7uSQkoF6n9vq7DChDnh3wTvA/2CSwIdxt
+Le6/Wjv6iJx0bK8h3ZLswxXvlHUmRtamP79mSKod790n5rdRiTh9E4QMQPzQtfHg
+2/lPL0ActI5HImG4TJbe8F8Rfk8R2exQRyIOxR3iZEnnaGNFOorZcfRe8W63FE0+
+bxQe3FL+vN8MvSk/dvsRX2hoFQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert41[] = {
+ 0x30, 0x82, 0x04, 0x8f, 0x30, 0x82, 0x03, 0x77, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x76, 0x10, 0x12, 0x8a, 0x17, 0xb6, 0x82, 0xbb, 0x3a,
+ 0x1f, 0x9d, 0x1a, 0x9a, 0x35, 0xc0, 0x92, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+ 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+ 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+ 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x31, 0x38,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+ 0x32, 0x31, 0x37, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x5e,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x14, 0x44,
+ 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61,
+ 0x74, 0x65, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x31, 0x19, 0x30, 0x17, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x20, 0x44, 0x56, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82,
+ 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcb, 0x98, 0xc9, 0x36, 0x3f,
+ 0xd2, 0x9c, 0xd8, 0x16, 0x07, 0xd4, 0x49, 0x63, 0xf9, 0x83, 0xb0, 0xe8,
+ 0x02, 0x2d, 0xcc, 0x5c, 0x5a, 0x74, 0x97, 0xa6, 0x13, 0xef, 0x13, 0x13,
+ 0xde, 0x05, 0x7c, 0xa7, 0xe6, 0xca, 0x00, 0x23, 0xda, 0x39, 0xf9, 0xef,
+ 0x13, 0xcf, 0x52, 0xc5, 0xaf, 0x9a, 0xe3, 0xca, 0xbe, 0xf3, 0x82, 0xd9,
+ 0x8b, 0x3d, 0xaa, 0xe1, 0xcc, 0xae, 0x88, 0x50, 0x66, 0xa3, 0x2d, 0xec,
+ 0x61, 0x14, 0x75, 0x49, 0xab, 0x0e, 0x24, 0xf1, 0xac, 0x44, 0x5b, 0x0b,
+ 0x28, 0xa2, 0x33, 0x20, 0x76, 0x1e, 0x06, 0x60, 0x6a, 0x67, 0x05, 0x71,
+ 0x8b, 0xba, 0x66, 0x62, 0x16, 0x7a, 0xb3, 0x6d, 0x0d, 0xc7, 0xd0, 0x94,
+ 0x40, 0xc6, 0x8c, 0x3d, 0x1e, 0x92, 0x0c, 0x62, 0x34, 0x0d, 0x44, 0x89,
+ 0xd5, 0xf7, 0x89, 0xfe, 0x29, 0xed, 0x18, 0x8f, 0xf6, 0x9b, 0x2b, 0x08,
+ 0xf7, 0x6a, 0xab, 0xd8, 0x48, 0x97, 0x5a, 0xf4, 0x9f, 0xed, 0x0c, 0x75,
+ 0x52, 0x22, 0xf7, 0xd5, 0x5e, 0x84, 0x00, 0x9f, 0xc0, 0x4a, 0x0d, 0x31,
+ 0x77, 0x4c, 0x64, 0xd0, 0x12, 0xe6, 0x0f, 0x3a, 0xf0, 0xa1, 0xc0, 0xd5,
+ 0x5c, 0x1d, 0xe7, 0x5f, 0x2d, 0xc2, 0xf7, 0xd6, 0x36, 0x18, 0xd9, 0x95,
+ 0x6e, 0x44, 0x4e, 0xc9, 0x58, 0x14, 0x4d, 0xb6, 0x8e, 0xbb, 0xcd, 0xde,
+ 0x62, 0x1e, 0xfa, 0x5b, 0xb5, 0xbd, 0x18, 0x2b, 0x98, 0xac, 0xac, 0x93,
+ 0x3f, 0x50, 0x5a, 0xf5, 0x14, 0x0b, 0xa2, 0xcf, 0xb6, 0xf3, 0x9e, 0x4f,
+ 0x5a, 0xcd, 0x5a, 0xc3, 0x36, 0x23, 0xda, 0x1a, 0xaf, 0xb0, 0x4d, 0xd6,
+ 0x4a, 0x22, 0x03, 0x8f, 0x43, 0x02, 0x19, 0xbd, 0xea, 0xac, 0xdd, 0xc4,
+ 0x7a, 0x35, 0x32, 0x14, 0xf1, 0x72, 0x2e, 0x08, 0x55, 0x40, 0x0c, 0xf4,
+ 0x07, 0x41, 0x41, 0xaf, 0x38, 0x37, 0x84, 0x29, 0x42, 0xb2, 0x55, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xfc, 0x30, 0x81, 0xf9, 0x30, 0x32,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26,
+ 0x30, 0x24, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f,
+ 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x34,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0,
+ 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03,
+ 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31,
+ 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32,
+ 0x2d, 0x31, 0x31, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0xab, 0x44, 0xe4, 0x5d, 0xec, 0x83, 0xc7, 0xd9, 0xc0, 0x85,
+ 0x9f, 0xf7, 0xe1, 0xc6, 0x97, 0x90, 0xb0, 0x8c, 0x3f, 0x98, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7b,
+ 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31, 0x92, 0x1a, 0x6a,
+ 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x04, 0xba, 0xfb, 0xac, 0xbb, 0xfc, 0x4b, 0x54, 0x11,
+ 0xa3, 0x2d, 0x88, 0xb3, 0x3c, 0xbd, 0x00, 0x6d, 0x8a, 0x1a, 0xb6, 0x8d,
+ 0xc4, 0xc1, 0x83, 0xf8, 0xc7, 0x53, 0x2a, 0xc1, 0x32, 0x6e, 0x3a, 0x81,
+ 0xa1, 0x54, 0x7d, 0xda, 0x1a, 0x3f, 0x3a, 0x45, 0x4f, 0x36, 0xe7, 0x42,
+ 0xb0, 0x0a, 0x42, 0x85, 0x97, 0xa0, 0xac, 0xfb, 0xe5, 0x87, 0xa7, 0x83,
+ 0x4f, 0xe8, 0xb1, 0xb7, 0x9b, 0x58, 0x65, 0x6e, 0x26, 0x80, 0x0b, 0x92,
+ 0x4d, 0x47, 0x55, 0xb9, 0x61, 0x16, 0x51, 0x65, 0xe9, 0x2b, 0xf1, 0x68,
+ 0xd9, 0x58, 0xb8, 0x03, 0x81, 0xd1, 0xb7, 0x66, 0x1c, 0xd3, 0xbc, 0xc5,
+ 0xa6, 0x7b, 0x5f, 0x3e, 0xc5, 0x38, 0x46, 0x76, 0xe7, 0x75, 0xb4, 0xa0,
+ 0x0c, 0x4b, 0xce, 0xa2, 0xc2, 0xa9, 0xc1, 0xcc, 0x36, 0x73, 0x7b, 0xfb,
+ 0xb9, 0x24, 0x24, 0xa0, 0x5e, 0xa7, 0xf6, 0xfa, 0xbb, 0x0c, 0x28, 0x43,
+ 0x9e, 0x1d, 0xf0, 0x4e, 0xf0, 0x3f, 0xd8, 0x24, 0xb0, 0x21, 0xdc, 0x6d,
+ 0x2d, 0xee, 0xbf, 0x5a, 0x3b, 0xfa, 0x88, 0x9c, 0x74, 0x6c, 0xaf, 0x21,
+ 0xdd, 0x92, 0xec, 0xc3, 0x15, 0xef, 0x94, 0x75, 0x26, 0x46, 0xd6, 0xa6,
+ 0x3f, 0xbf, 0x66, 0x48, 0xaa, 0x1d, 0xef, 0xdd, 0x27, 0xe6, 0xb7, 0x51,
+ 0x89, 0x38, 0x7d, 0x13, 0x84, 0x0c, 0x40, 0xfc, 0xd0, 0xb5, 0xf1, 0xe0,
+ 0xdb, 0xf9, 0x4f, 0x2f, 0x40, 0x1c, 0xb4, 0x8e, 0x47, 0x22, 0x61, 0xb8,
+ 0x4c, 0x96, 0xde, 0xf0, 0x5f, 0x11, 0x7e, 0x4f, 0x11, 0xd9, 0xec, 0x50,
+ 0x47, 0x22, 0x0e, 0xc5, 0x1d, 0xe2, 0x64, 0x49, 0xe7, 0x68, 0x63, 0x45,
+ 0x3a, 0x8a, 0xd9, 0x71, 0xf4, 0x5e, 0xf1, 0x6e, 0xb7, 0x14, 0x4d, 0x3e,
+ 0x6f, 0x14, 0x1e, 0xdc, 0x52, 0xfe, 0xbc, 0xdf, 0x0c, 0xbd, 0x29, 0x3f,
+ 0x76, 0xfb, 0x11, 0x5f, 0x68, 0x68, 0x15,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 1b:09:3b:78:60:96:da:37:bb:a4:51:94:46:c8:96:78
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2021 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+ 4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+ 08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+ 2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+ 8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+ a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+ 54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+ d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+ 7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+ bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+ f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+ ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+ f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+ 21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+ 63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+ ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+ 9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+ 25:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 Subject Key Identifier:
+ 7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ a3:cd:7d:1e:f7:c7:75:8d:48:e7:56:34:4c:00:90:75:a9:51:
+ a5:56:c1:6d:bc:fe:f5:53:22:e9:98:a2:ac:9a:7e:70:1e:b3:
+ 8e:3b:45:e3:86:95:31:da:6d:4c:fb:34:50:80:96:cd:24:f2:
+ 40:df:04:3f:e2:65:ce:34:22:61:15:ea:66:70:64:d2:f1:6e:
+ f3:ca:18:59:6a:41:46:7e:82:de:19:b0:70:31:56:69:0d:0c:
+ e6:1d:9d:71:58:dc:cc:de:62:f5:e1:7a:10:02:d8:7a:dc:3b:
+ fa:57:bd:c9:e9:8f:46:21:39:9f:51:65:4c:8e:3a:be:28:41:
+ 70:1d
+-----BEGIN CERTIFICATE-----
+MIIEkDCCA/mgAwIBAgIQGwk7eGCW2je7pFGURsiWeDANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggFbMIIBVzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
+KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
+j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
+L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
+b2NzcC52ZXJpc2lnbi5jb20wDQYJKoZIhvcNAQEFBQADgYEAo819HvfHdY1I51Y0
+TACQdalRpVbBbbz+9VMi6ZiirJp+cB6zjjtF44aVMdptTPs0UICWzSTyQN8EP+Jl
+zjQiYRXqZnBk0vFu88oYWWpBRn6C3hmwcDFWaQ0M5h2dcVjczN5i9eF6EALYetw7
++le9yemPRiE5n1FlTI46vihBcB0=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert42[] = {
+ 0x30, 0x82, 0x04, 0x90, 0x30, 0x82, 0x03, 0xf9, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x1b, 0x09, 0x3b, 0x78, 0x60, 0x96, 0xda, 0x37, 0xbb,
+ 0xa4, 0x51, 0x94, 0x46, 0xc8, 0x96, 0x78, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+ 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+ 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+ 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+ 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+ 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+ 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+ 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+ 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+ 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+ 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+ 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+ 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+ 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+ 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+ 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+ 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+ 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+ 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+ 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+ 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+ 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+ 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+ 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+ 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+ 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+ 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+ 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+ 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x5b, 0x30, 0x82, 0x01, 0x57, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+ 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+ 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+ 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+ 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+ 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f,
+ 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09,
+ 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30,
+ 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14,
+ 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80,
+ 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30,
+ 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+ 0xa3, 0xcd, 0x7d, 0x1e, 0xf7, 0xc7, 0x75, 0x8d, 0x48, 0xe7, 0x56, 0x34,
+ 0x4c, 0x00, 0x90, 0x75, 0xa9, 0x51, 0xa5, 0x56, 0xc1, 0x6d, 0xbc, 0xfe,
+ 0xf5, 0x53, 0x22, 0xe9, 0x98, 0xa2, 0xac, 0x9a, 0x7e, 0x70, 0x1e, 0xb3,
+ 0x8e, 0x3b, 0x45, 0xe3, 0x86, 0x95, 0x31, 0xda, 0x6d, 0x4c, 0xfb, 0x34,
+ 0x50, 0x80, 0x96, 0xcd, 0x24, 0xf2, 0x40, 0xdf, 0x04, 0x3f, 0xe2, 0x65,
+ 0xce, 0x34, 0x22, 0x61, 0x15, 0xea, 0x66, 0x70, 0x64, 0xd2, 0xf1, 0x6e,
+ 0xf3, 0xca, 0x18, 0x59, 0x6a, 0x41, 0x46, 0x7e, 0x82, 0xde, 0x19, 0xb0,
+ 0x70, 0x31, 0x56, 0x69, 0x0d, 0x0c, 0xe6, 0x1d, 0x9d, 0x71, 0x58, 0xdc,
+ 0xcc, 0xde, 0x62, 0xf5, 0xe1, 0x7a, 0x10, 0x02, 0xd8, 0x7a, 0xdc, 0x3b,
+ 0xfa, 0x57, 0xbd, 0xc9, 0xe9, 0x8f, 0x46, 0x21, 0x39, 0x9f, 0x51, 0x65,
+ 0x4c, 0x8e, 0x3a, 0xbe, 0x28, 0x41, 0x70, 0x1d,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 3d:3a:05:26:09:b6:2e:e5:8c:36:29:38:63:54:e1:24
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Validity
+ Not Before: Dec 1 00:00:00 2006 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+ 98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+ e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+ b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+ 86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+ da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+ d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+ 7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+ 9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+ 30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+ 77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+ 02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+ c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+ 00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+ 0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+ 2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+ 47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+ 26:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+ X509v3 Subject Key Identifier:
+ 0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 9e:cf:0c:29:ff:99:5f:85:24:63:19:5a:68:f5:e4:46:ce:53:
+ 57:91:d6:25:fd:ea:ed:64:0f:73:da:aa:1c:25:d9:fb:ee:2c:
+ 03:87:9d:8d:a9:7a:63:5c:50:a8:f7:64:8c:96:2c:ba:0b:f2:
+ 7e:f9:74:29:c6:e5:4b:0b:50:30:af:c3:2e:3e:52:1a:fb:35:
+ 66:59:12:62:e0:68:7a:b0:42:01:a9:16:c3:8a:fa:45:19:7a:
+ f2:e0:2b:bf:78:87:49:6f:7e:ff:d4:7c:bd:e1:8b:a5:7b:43:
+ 9b:2c:42:cf:62:ef:63:1e:1c:85:d5:4c:b0:4a:91:4c:61:66:
+ 5b:26:7e:25:e1:c7:15:c0:54:4b:c9:66:16:29:63:df:71:ab:
+ b6:07:92:fa:f3:4f:f2:31:d6:32:d0:4d:35:db:5b:89:b8:08:
+ e4:68:de:d8:47:cb:d7:5e:e8:16:b2:94:21:9c:6a:5b:bf:b4:
+ 81:86:dd:c5:f2:a8:71:3e:dd:a7:4a:b5:fa:f8:6c:3b:34:9a:
+ 9b:58:7d:4d:d4:d3:5b:53:23:6b:49:38:16:a1:98:9f:84:5e:
+ ab:ae:3f:ae:ce:7f:c8:17:e4:32:ab:c4:d3:2f:9a:90:31:c2:
+ 92:53:96:ed:72:a7:fe:c4:da:39:29:51:68:ed:90:8d:97:8e:
+ fe:45:19:b7
+-----BEGIN CERTIFICATE-----
+MIIEmTCCA4GgAwIBAgIQPToFJgm2LuWMNik4Y1ThJDANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYxMjAxMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgTELMAkG
+A1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMH
+U2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNP
+TU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBANBAi4ty45Eb91HBG1QEmNOpv8Hmil07h/u7iM4N4y8/Bpbw
+oilQma7bO6FXsHRRcc3tQpFNQf6pyNhqhndEu1lml1BetNQscETP2jeVQmk8MMRx
+s1LwIU2h2Lo5fByeoySd8oMWmKoWfEObFVu3rjSR/tRiJhhGmj/rwfnxkFfrrHoN
+i9tyMGpm1eBGo3DcaNn/BEiJd9616ftnbUHpvDm9MtliAvGxqD1uN5ziL+LToiaL
+xrhVQ4jhIz6l0iQ5akerANShs6kl/g0/px2601HBC6TarDjvVVAkBWVGkzRPLY2t
+xtQhGdKOygVhcQdzR+WKGRK9BE3OTpylSKy7JvcCAwEAAaOB9DCB8TAfBgNVHSME
+GDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNVHQ4EFgQUC1jli8ZMFTekQKkw
+qSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0g
+BAowCDAGBgRVHSAAMHsGA1UdHwR0MHIwOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2Rv
+Y2EuY29tL1VUTi1VU0VSRmlyc3QtSGFyZHdhcmUuY3JsMDagNKAyhjBodHRwOi8v
+Y3JsLmNvbW9kby5uZXQvVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5jcmwwDQYJKoZI
+hvcNAQEFBQADggEBAJ7PDCn/mV+FJGMZWmj15EbOU1eR1iX96u1kD3Paqhwl2fvu
+LAOHnY2pemNcUKj3ZIyWLLoL8n75dCnG5UsLUDCvwy4+Uhr7NWZZEmLgaHqwQgGp
+FsOK+kUZevLgK794h0lvfv/UfL3hi6V7Q5ssQs9i72MeHIXVTLBKkUxhZlsmfiXh
+xxXAVEvJZhYpY99xq7YHkvrzT/Ix1jLQTTXbW4m4CORo3thHy9de6BaylCGcalu/
+tIGG3cXyqHE+3adKtfr4bDs0mptYfU3U01tTI2tJOBahmJ+EXquuP67Of8gX5DKr
+xNMvmpAxwpJTlu1yp/7E2jkpUWjtkI2Xjv5FGbc=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert43[] = {
+ 0x30, 0x82, 0x04, 0x99, 0x30, 0x82, 0x03, 0x81, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x3d, 0x3a, 0x05, 0x26, 0x09, 0xb6, 0x2e, 0xe5, 0x8c,
+ 0x36, 0x29, 0x38, 0x63, 0x54, 0xe1, 0x24, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+ 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+ 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+ 0x38, 0x33, 0x38, 0x5a, 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06,
+ 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19,
+ 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74,
+ 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65,
+ 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07,
+ 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f,
+ 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31,
+ 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f,
+ 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xd0, 0x40, 0x8b, 0x8b, 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1,
+ 0x1b, 0x54, 0x04, 0x98, 0xd3, 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b,
+ 0x87, 0xfb, 0xbb, 0x88, 0xce, 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0,
+ 0xa2, 0x29, 0x50, 0x99, 0xae, 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51,
+ 0x71, 0xcd, 0xed, 0x42, 0x91, 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a,
+ 0x86, 0x77, 0x44, 0xbb, 0x59, 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c,
+ 0x70, 0x44, 0xcf, 0xda, 0x37, 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71,
+ 0xb3, 0x52, 0xf0, 0x21, 0x4d, 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e,
+ 0xa3, 0x24, 0x9d, 0xf2, 0x83, 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b,
+ 0x15, 0x5b, 0xb7, 0xae, 0x34, 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46,
+ 0x9a, 0x3f, 0xeb, 0xc1, 0xf9, 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d,
+ 0x8b, 0xdb, 0x72, 0x30, 0x6a, 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc,
+ 0x68, 0xd9, 0xff, 0x04, 0x48, 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67,
+ 0x6d, 0x41, 0xe9, 0xbc, 0x39, 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1,
+ 0xa8, 0x3d, 0x6e, 0x37, 0x9c, 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b,
+ 0xc6, 0xb8, 0x55, 0x43, 0x88, 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39,
+ 0x6a, 0x47, 0xab, 0x00, 0xd4, 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f,
+ 0xa7, 0x1d, 0xba, 0xd3, 0x51, 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef,
+ 0x55, 0x50, 0x24, 0x05, 0x65, 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad,
+ 0xc6, 0xd4, 0x21, 0x19, 0xd2, 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73,
+ 0x47, 0xe5, 0x8a, 0x19, 0x12, 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5,
+ 0x48, 0xac, 0xbb, 0x26, 0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81,
+ 0xf4, 0x30, 0x81, 0xf1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98,
+ 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3,
+ 0x45, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30,
+ 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+ 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+ 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72, 0x30,
+ 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+ 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55,
+ 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72,
+ 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x36, 0xa0,
+ 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46,
+ 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72,
+ 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+ 0x00, 0x9e, 0xcf, 0x0c, 0x29, 0xff, 0x99, 0x5f, 0x85, 0x24, 0x63, 0x19,
+ 0x5a, 0x68, 0xf5, 0xe4, 0x46, 0xce, 0x53, 0x57, 0x91, 0xd6, 0x25, 0xfd,
+ 0xea, 0xed, 0x64, 0x0f, 0x73, 0xda, 0xaa, 0x1c, 0x25, 0xd9, 0xfb, 0xee,
+ 0x2c, 0x03, 0x87, 0x9d, 0x8d, 0xa9, 0x7a, 0x63, 0x5c, 0x50, 0xa8, 0xf7,
+ 0x64, 0x8c, 0x96, 0x2c, 0xba, 0x0b, 0xf2, 0x7e, 0xf9, 0x74, 0x29, 0xc6,
+ 0xe5, 0x4b, 0x0b, 0x50, 0x30, 0xaf, 0xc3, 0x2e, 0x3e, 0x52, 0x1a, 0xfb,
+ 0x35, 0x66, 0x59, 0x12, 0x62, 0xe0, 0x68, 0x7a, 0xb0, 0x42, 0x01, 0xa9,
+ 0x16, 0xc3, 0x8a, 0xfa, 0x45, 0x19, 0x7a, 0xf2, 0xe0, 0x2b, 0xbf, 0x78,
+ 0x87, 0x49, 0x6f, 0x7e, 0xff, 0xd4, 0x7c, 0xbd, 0xe1, 0x8b, 0xa5, 0x7b,
+ 0x43, 0x9b, 0x2c, 0x42, 0xcf, 0x62, 0xef, 0x63, 0x1e, 0x1c, 0x85, 0xd5,
+ 0x4c, 0xb0, 0x4a, 0x91, 0x4c, 0x61, 0x66, 0x5b, 0x26, 0x7e, 0x25, 0xe1,
+ 0xc7, 0x15, 0xc0, 0x54, 0x4b, 0xc9, 0x66, 0x16, 0x29, 0x63, 0xdf, 0x71,
+ 0xab, 0xb6, 0x07, 0x92, 0xfa, 0xf3, 0x4f, 0xf2, 0x31, 0xd6, 0x32, 0xd0,
+ 0x4d, 0x35, 0xdb, 0x5b, 0x89, 0xb8, 0x08, 0xe4, 0x68, 0xde, 0xd8, 0x47,
+ 0xcb, 0xd7, 0x5e, 0xe8, 0x16, 0xb2, 0x94, 0x21, 0x9c, 0x6a, 0x5b, 0xbf,
+ 0xb4, 0x81, 0x86, 0xdd, 0xc5, 0xf2, 0xa8, 0x71, 0x3e, 0xdd, 0xa7, 0x4a,
+ 0xb5, 0xfa, 0xf8, 0x6c, 0x3b, 0x34, 0x9a, 0x9b, 0x58, 0x7d, 0x4d, 0xd4,
+ 0xd3, 0x5b, 0x53, 0x23, 0x6b, 0x49, 0x38, 0x16, 0xa1, 0x98, 0x9f, 0x84,
+ 0x5e, 0xab, 0xae, 0x3f, 0xae, 0xce, 0x7f, 0xc8, 0x17, 0xe4, 0x32, 0xab,
+ 0xc4, 0xd3, 0x2f, 0x9a, 0x90, 0x31, 0xc2, 0x92, 0x53, 0x96, 0xed, 0x72,
+ 0xa7, 0xfe, 0xc4, 0xda, 0x39, 0x29, 0x51, 0x68, 0xed, 0x90, 0x8d, 0x97,
+ 0x8e, 0xfe, 0x45, 0x19, 0xb7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1116155212 (0x42872d4c)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Validity
+ Not Before: Jan 5 19:20:39 2007 GMT
+ Not After : Jan 5 19:50:39 2017 GMT
+ Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:95:b6:43:42:fa:c6:6d:2a:6f:48:df:94:4c:
+ 39:57:05:ee:c3:79:11:41:68:36:ed:ec:fe:9a:01:
+ 8f:a1:38:28:fc:f7:10:46:66:2e:4d:1e:1a:b1:1a:
+ 4e:c6:d1:c0:95:88:b0:c9:ff:31:8b:33:03:db:b7:
+ 83:7b:3e:20:84:5e:ed:b2:56:28:a7:f8:e0:b9:40:
+ 71:37:c5:cb:47:0e:97:2a:68:c0:22:95:62:15:db:
+ 47:d9:f5:d0:2b:ff:82:4b:c9:ad:3e:de:4c:db:90:
+ 80:50:3f:09:8a:84:00:ec:30:0a:3d:18:cd:fb:fd:
+ 2a:59:9a:23:95:17:2c:45:9e:1f:6e:43:79:6d:0c:
+ 5c:98:fe:48:a7:c5:23:47:5c:5e:fd:6e:e7:1e:b4:
+ f6:68:45:d1:86:83:5b:a2:8a:8d:b1:e3:29:80:fe:
+ 25:71:88:ad:be:bc:8f:ac:52:96:4b:aa:51:8d:e4:
+ 13:31:19:e8:4e:4d:9f:db:ac:b3:6a:d5:bc:39:54:
+ 71:ca:7a:7a:7f:90:dd:7d:1d:80:d9:81:bb:59:26:
+ c2:11:fe:e6:93:e2:f7:80:e4:65:fb:34:37:0e:29:
+ 80:70:4d:af:38:86:2e:9e:7f:57:af:9e:17:ae:eb:
+ 1c:cb:28:21:5f:b6:1c:d8:e7:a2:04:22:f9:d3:da:
+ d8:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/server1.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/CPS
+
+ X509v3 Subject Key Identifier:
+ 68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+ X509v3 Authority Key Identifier:
+ keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V7.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ 0c:b0:84:7c:2d:13:fe:9a:3d:bf:18:05:95:3d:20:48:a3:16:
+ 81:87:15:50:15:a4:88:8d:9f:60:d4:3a:6f:eb:2d:6e:3a:86:
+ a4:a9:d2:c1:9d:89:7a:08:1c:a4:2d:b3:47:8e:0f:64:4a:6f:
+ 66:03:83:3f:4f:34:94:36:aa:29:6d:8b:8d:02:22:2b:8c:cd:
+ 77:a5:70:95:86:91:d1:b6:bf:52:be:33:6a:6b:99:f9:6f:e1:
+ 12:be:04:cb:33:bf:f5:12:1a:4e:44:ba:5b:16:4d:30:b9:f3:
+ b4:74:ce:6e:f2:68:56:58:dd:d8:a1:fd:54:05:f4:23:91:85:
+ c9:f9
+-----BEGIN CERTIFICATE-----
+MIIEmzCCBASgAwIBAgIEQoctTDANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNzAx
+MDUxOTIwMzlaFw0xNzAxMDUxOTUwMzlaMIGwMQswCQYDVQQGEwJVUzEWMBQGA1UE
+ChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBp
+cyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBF
+bnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2lbZD
+QvrGbSpvSN+UTDlXBe7DeRFBaDbt7P6aAY+hOCj89xBGZi5NHhqxGk7G0cCViLDJ
+/zGLMwPbt4N7PiCEXu2yViin+OC5QHE3xctHDpcqaMAilWIV20fZ9dAr/4JLya0+
+3kzbkIBQPwmKhADsMAo9GM37/SpZmiOVFyxFnh9uQ3ltDFyY/kinxSNHXF79buce
+tPZoRdGGg1uiio2x4ymA/iVxiK2+vI+sUpZLqlGN5BMxGehOTZ/brLNq1bw5VHHK
+enp/kN19HYDZgbtZJsIR/uaT4veA5GX7NDcOKYBwTa84hi6ef1evnheu6xzLKCFf
+thzY56IEIvnT2tjLAgMBAAGjggEnMIIBIzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9v
+Y3NwLmVudHJ1c3QubmV0MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50
+cnVzdC5uZXQvc2VydmVyMS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYB
+BQUHAgEWGmh0dHA6Ly93d3cuZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRokORn
+pKZTgMeGZqTx90tD+4S9bTAfBgNVHSMEGDAWgBTwF2ITVT2z/woAa/tQhJfz7WLQ
+GjAZBgkqhkiG9n0HQQAEDDAKGwRWNy4xAwIAgTANBgkqhkiG9w0BAQUFAAOBgQAM
+sIR8LRP+mj2/GAWVPSBIoxaBhxVQFaSIjZ9g1Dpv6y1uOoakqdLBnYl6CBykLbNH
+jg9kSm9mA4M/TzSUNqopbYuNAiIrjM13pXCVhpHRtr9SvjNqa5n5b+ESvgTLM7/1
+EhpORLpbFk0wufO0dM5u8mhWWN3Yof1UBfQjkYXJ+Q==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert44[] = {
+ 0x30, 0x82, 0x04, 0x9b, 0x30, 0x82, 0x04, 0x04, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x42, 0x87, 0x2d, 0x4c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+ 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+ 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+ 0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x31,
+ 0x30, 0x35, 0x31, 0x39, 0x32, 0x30, 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x31,
+ 0x37, 0x30, 0x31, 0x30, 0x35, 0x31, 0x39, 0x35, 0x30, 0x33, 0x39, 0x5a,
+ 0x30, 0x81, 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69,
+ 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65,
+ 0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45,
+ 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45,
+ 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x95, 0xb6, 0x43,
+ 0x42, 0xfa, 0xc6, 0x6d, 0x2a, 0x6f, 0x48, 0xdf, 0x94, 0x4c, 0x39, 0x57,
+ 0x05, 0xee, 0xc3, 0x79, 0x11, 0x41, 0x68, 0x36, 0xed, 0xec, 0xfe, 0x9a,
+ 0x01, 0x8f, 0xa1, 0x38, 0x28, 0xfc, 0xf7, 0x10, 0x46, 0x66, 0x2e, 0x4d,
+ 0x1e, 0x1a, 0xb1, 0x1a, 0x4e, 0xc6, 0xd1, 0xc0, 0x95, 0x88, 0xb0, 0xc9,
+ 0xff, 0x31, 0x8b, 0x33, 0x03, 0xdb, 0xb7, 0x83, 0x7b, 0x3e, 0x20, 0x84,
+ 0x5e, 0xed, 0xb2, 0x56, 0x28, 0xa7, 0xf8, 0xe0, 0xb9, 0x40, 0x71, 0x37,
+ 0xc5, 0xcb, 0x47, 0x0e, 0x97, 0x2a, 0x68, 0xc0, 0x22, 0x95, 0x62, 0x15,
+ 0xdb, 0x47, 0xd9, 0xf5, 0xd0, 0x2b, 0xff, 0x82, 0x4b, 0xc9, 0xad, 0x3e,
+ 0xde, 0x4c, 0xdb, 0x90, 0x80, 0x50, 0x3f, 0x09, 0x8a, 0x84, 0x00, 0xec,
+ 0x30, 0x0a, 0x3d, 0x18, 0xcd, 0xfb, 0xfd, 0x2a, 0x59, 0x9a, 0x23, 0x95,
+ 0x17, 0x2c, 0x45, 0x9e, 0x1f, 0x6e, 0x43, 0x79, 0x6d, 0x0c, 0x5c, 0x98,
+ 0xfe, 0x48, 0xa7, 0xc5, 0x23, 0x47, 0x5c, 0x5e, 0xfd, 0x6e, 0xe7, 0x1e,
+ 0xb4, 0xf6, 0x68, 0x45, 0xd1, 0x86, 0x83, 0x5b, 0xa2, 0x8a, 0x8d, 0xb1,
+ 0xe3, 0x29, 0x80, 0xfe, 0x25, 0x71, 0x88, 0xad, 0xbe, 0xbc, 0x8f, 0xac,
+ 0x52, 0x96, 0x4b, 0xaa, 0x51, 0x8d, 0xe4, 0x13, 0x31, 0x19, 0xe8, 0x4e,
+ 0x4d, 0x9f, 0xdb, 0xac, 0xb3, 0x6a, 0xd5, 0xbc, 0x39, 0x54, 0x71, 0xca,
+ 0x7a, 0x7a, 0x7f, 0x90, 0xdd, 0x7d, 0x1d, 0x80, 0xd9, 0x81, 0xbb, 0x59,
+ 0x26, 0xc2, 0x11, 0xfe, 0xe6, 0x93, 0xe2, 0xf7, 0x80, 0xe4, 0x65, 0xfb,
+ 0x34, 0x37, 0x0e, 0x29, 0x80, 0x70, 0x4d, 0xaf, 0x38, 0x86, 0x2e, 0x9e,
+ 0x7f, 0x57, 0xaf, 0x9e, 0x17, 0xae, 0xeb, 0x1c, 0xcb, 0x28, 0x21, 0x5f,
+ 0xb6, 0x1c, 0xd8, 0xe7, 0xa2, 0x04, 0x22, 0xf9, 0xd3, 0xda, 0xd8, 0xcb,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x27, 0x30, 0x82, 0x01,
+ 0x23, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x33,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27,
+ 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f,
+ 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c,
+ 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x68, 0x90, 0xe4, 0x67,
+ 0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86, 0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43,
+ 0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62, 0x13, 0x55, 0x3d, 0xb3,
+ 0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97, 0xf3, 0xed, 0x62, 0xd0,
+ 0x1a, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07,
+ 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31,
+ 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x0c,
+ 0xb0, 0x84, 0x7c, 0x2d, 0x13, 0xfe, 0x9a, 0x3d, 0xbf, 0x18, 0x05, 0x95,
+ 0x3d, 0x20, 0x48, 0xa3, 0x16, 0x81, 0x87, 0x15, 0x50, 0x15, 0xa4, 0x88,
+ 0x8d, 0x9f, 0x60, 0xd4, 0x3a, 0x6f, 0xeb, 0x2d, 0x6e, 0x3a, 0x86, 0xa4,
+ 0xa9, 0xd2, 0xc1, 0x9d, 0x89, 0x7a, 0x08, 0x1c, 0xa4, 0x2d, 0xb3, 0x47,
+ 0x8e, 0x0f, 0x64, 0x4a, 0x6f, 0x66, 0x03, 0x83, 0x3f, 0x4f, 0x34, 0x94,
+ 0x36, 0xaa, 0x29, 0x6d, 0x8b, 0x8d, 0x02, 0x22, 0x2b, 0x8c, 0xcd, 0x77,
+ 0xa5, 0x70, 0x95, 0x86, 0x91, 0xd1, 0xb6, 0xbf, 0x52, 0xbe, 0x33, 0x6a,
+ 0x6b, 0x99, 0xf9, 0x6f, 0xe1, 0x12, 0xbe, 0x04, 0xcb, 0x33, 0xbf, 0xf5,
+ 0x12, 0x1a, 0x4e, 0x44, 0xba, 0x5b, 0x16, 0x4d, 0x30, 0xb9, 0xf3, 0xb4,
+ 0x74, 0xce, 0x6e, 0xf2, 0x68, 0x56, 0x58, 0xdd, 0xd8, 0xa1, 0xfd, 0x54,
+ 0x05, 0xf4, 0x23, 0x91, 0x85, 0xc9, 0xf9,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 69:48:a2:6b:20:1a:a4:21:e8:98:b1:c4:92:c7:c5:8e
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
+ Validity
+ Not Before: Nov 29 00:00:00 2006 GMT
+ Not After : Nov 28 23:59:59 2016 GMT
+ Subject: C=US, O=GeoTrust Inc, OU=See www.geotrust.com/resources/cps (c)06, CN=GeoTrust Extended Validation SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c2:ef:ed:ec:0b:2d:72:8a:74:68:73:36:6e:10:
+ a8:7e:48:7f:58:bb:78:67:dc:ed:7b:d6:7c:a6:4f:
+ 3d:9f:5d:6f:0a:d0:a0:b4:65:fd:be:d3:bf:77:b6:
+ 94:a5:82:ff:81:95:9d:28:10:06:ec:c2:b4:90:aa:
+ 5a:51:4c:73:d9:6b:74:a8:35:49:f4:a6:36:80:d4:
+ 5c:75:9e:9e:7c:01:c7:8c:9c:81:c8:86:83:1a:8e:
+ bd:00:13:a2:dc:ff:a5:78:aa:77:2c:21:62:08:97:
+ 3f:80:bd:f7:67:a4:79:db:7d:d7:3e:6e:b6:d5:96:
+ b9:98:86:4e:7a:67:e2:93:af:da:a5:d1:27:fb:f1:
+ 66:c3:2a:03:0c:b6:c7:82:1d:39:fb:3c:de:29:36:
+ 71:5d:e1:a8:b5:16:39:7c:1b:ff:7b:86:f5:80:92:
+ 95:e0:03:3b:aa:44:fb:f4:00:b5:e5:a9:e2:fa:18:
+ f9:84:9a:c1:e1:f6:2e:0e:81:8b:14:29:34:ff:1f:
+ 55:60:88:a4:99:c6:6f:6f:04:39:3a:75:a4:a7:1e:
+ 58:df:b7:ff:c9:9a:1d:70:db:83:a0:d3:83:1b:2d:
+ 6d:2a:90:5b:a3:63:91:73:b5:ff:9d:82:7a:41:f3:
+ d3:aa:2f:0b:0d:9f:cf:44:c0:5e:c7:a1:6b:cf:ae:
+ 94:db
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 28:C4:EB:8F:F1:5F:79:90:A3:2B:55:C3:56:4E:7D:6B:53:72:2C:18
+ Authority Information Access:
+ OCSP - URI:http://EVSecure-ocsp.geotrust.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.geotrust.com/resources/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://EVSecure-crl.geotrust.com/GeoTrustPCA.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 02:60:a3:16:12:9d:d8:1c:19:e4:5a:37:6c:ff:32:98:37:46:
+ 4f:bc:81:7c:80:c3:ca:89:2a:00:fe:5e:3e:ec:ba:8c:2b:1f:
+ ab:95:6b:91:94:21:a0:60:1f:02:06:fa:cf:17:6d:f8:95:ab:
+ cd:78:23:14:96:c0:9d:1f:1b:eb:50:e1:65:42:8a:d2:b3:c9:
+ ad:80:c3:67:cf:b4:58:1b:d5:04:e4:58:fe:34:45:e0:fb:a4:
+ 84:22:8b:e9:e2:37:4c:98:f1:0b:ff:a4:89:53:d1:4d:c0:68:
+ 48:d7:59:87:1a:3b:7d:f5:d0:f9:23:72:ca:60:fd:c3:22:15:
+ f0:9a:95:58:6f:7c:24:93:ec:a5:12:3d:b4:1b:01:e8:ee:69:
+ ed:41:6b:52:cb:9a:b7:5c:15:d1:bd:06:40:7a:e0:0c:97:cb:
+ 60:e7:82:5f:6a:5f:de:49:84:56:6a:af:7c:b0:4b:ad:8c:4f:
+ 0f:79:a0:cc:11:3c:25:e7:46:bf:7a:d0:2f:88:c8:bf:eb:94:
+ 0b:6a:75:33:7f:73:00:b8:12:70:23:5e:55:7f:45:5b:1e:10:
+ b1:02:68:d8:27:40:cf:24:09:e2:65:74:ce:89:44:8d:7b:28:
+ 90:68:ae:ac:c2:38:c8:56:0d:33:88:28:7f:54:fc:3c:3c:50:
+ 09:93:3d:38
+-----BEGIN CERTIFICATE-----
+MIIEnDCCA4SgAwIBAgIQaUiiayAapCHomLHEksfFjjANBgkqhkiG9w0BAQUFADBY
+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
+R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
+MjkwMDAwMDBaFw0xNjExMjgyMzU5NTlaMIGFMQswCQYDVQQGEwJVUzEVMBMGA1UE
+ChMMR2VvVHJ1c3QgSW5jMTEwLwYDVQQLEyhTZWUgd3d3Lmdlb3RydXN0LmNvbS9y
+ZXNvdXJjZXMvY3BzIChjKTA2MSwwKgYDVQQDEyNHZW9UcnVzdCBFeHRlbmRlZCBW
+YWxpZGF0aW9uIFNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMLv7ewLLXKKdGhzNm4QqH5If1i7eGfc7XvWfKZPPZ9dbwrQoLRl/b7Tv3e2lKWC
+/4GVnSgQBuzCtJCqWlFMc9lrdKg1SfSmNoDUXHWennwBx4ycgciGgxqOvQATotz/
+pXiqdywhYgiXP4C992ekedt91z5uttWWuZiGTnpn4pOv2qXRJ/vxZsMqAwy2x4Id
+Ofs83ik2cV3hqLUWOXwb/3uG9YCSleADO6pE+/QAteWp4voY+YSaweH2Lg6BixQp
+NP8fVWCIpJnGb28EOTp1pKceWN+3/8maHXDbg6DTgxstbSqQW6NjkXO1/52CekHz
+06ovCw2fz0TAXseha8+ulNsCAwEAAaOCATIwggEuMB0GA1UdDgQWBBQoxOuP8V95
+kKMrVcNWTn1rU3IsGDA9BggrBgEFBQcBAQQxMC8wLQYIKwYBBQUHMAGGIWh0dHA6
+Ly9FVlNlY3VyZS1vY3NwLmdlb3RydXN0LmNvbTASBgNVHRMBAf8ECDAGAQH/AgEA
+MEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3Lmdl
+b3RydXN0LmNvbS9yZXNvdXJjZXMvY3BzMEEGA1UdHwQ6MDgwNqA0oDKGMGh0dHA6
+Ly9FVlNlY3VyZS1jcmwuZ2VvdHJ1c3QuY29tL0dlb1RydXN0UENBLmNybDAOBgNV
+HQ8BAf8EBAMCAQYwHwYDVR0jBBgwFoAULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
+KoZIhvcNAQEFBQADggEBAAJgoxYSndgcGeRaN2z/Mpg3Rk+8gXyAw8qJKgD+Xj7s
+uowrH6uVa5GUIaBgHwIG+s8XbfiVq814IxSWwJ0fG+tQ4WVCitKzya2Aw2fPtFgb
+1QTkWP40ReD7pIQii+niN0yY8Qv/pIlT0U3AaEjXWYcaO3310Pkjcspg/cMiFfCa
+lVhvfCST7KUSPbQbAejuae1Ba1LLmrdcFdG9BkB64AyXy2Dngl9qX95JhFZqr3yw
+S62MTw95oMwRPCXnRr960C+IyL/rlAtqdTN/cwC4EnAjXlV/RVseELECaNgnQM8k
+CeJldM6JRI17KJBorqzCOMhWDTOIKH9U/Dw8UAmTPTg=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert45[] = {
+ 0x30, 0x82, 0x04, 0x9c, 0x30, 0x82, 0x03, 0x84, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x69, 0x48, 0xa2, 0x6b, 0x20, 0x1a, 0xa4, 0x21, 0xe8,
+ 0x98, 0xb1, 0xc4, 0x92, 0xc7, 0xc5, 0x8e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x58,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+ 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28,
+ 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x50, 0x72, 0x69,
+ 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31,
+ 0x32, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31,
+ 0x36, 0x31, 0x31, 0x32, 0x38, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a,
+ 0x30, 0x81, 0x85, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0c, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x49, 0x6e, 0x63, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x28, 0x53, 0x65, 0x65, 0x20, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65,
+ 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73,
+ 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x2c, 0x30, 0x2a, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x23, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+ 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xc2, 0xef, 0xed, 0xec, 0x0b, 0x2d, 0x72, 0x8a, 0x74, 0x68, 0x73,
+ 0x36, 0x6e, 0x10, 0xa8, 0x7e, 0x48, 0x7f, 0x58, 0xbb, 0x78, 0x67, 0xdc,
+ 0xed, 0x7b, 0xd6, 0x7c, 0xa6, 0x4f, 0x3d, 0x9f, 0x5d, 0x6f, 0x0a, 0xd0,
+ 0xa0, 0xb4, 0x65, 0xfd, 0xbe, 0xd3, 0xbf, 0x77, 0xb6, 0x94, 0xa5, 0x82,
+ 0xff, 0x81, 0x95, 0x9d, 0x28, 0x10, 0x06, 0xec, 0xc2, 0xb4, 0x90, 0xaa,
+ 0x5a, 0x51, 0x4c, 0x73, 0xd9, 0x6b, 0x74, 0xa8, 0x35, 0x49, 0xf4, 0xa6,
+ 0x36, 0x80, 0xd4, 0x5c, 0x75, 0x9e, 0x9e, 0x7c, 0x01, 0xc7, 0x8c, 0x9c,
+ 0x81, 0xc8, 0x86, 0x83, 0x1a, 0x8e, 0xbd, 0x00, 0x13, 0xa2, 0xdc, 0xff,
+ 0xa5, 0x78, 0xaa, 0x77, 0x2c, 0x21, 0x62, 0x08, 0x97, 0x3f, 0x80, 0xbd,
+ 0xf7, 0x67, 0xa4, 0x79, 0xdb, 0x7d, 0xd7, 0x3e, 0x6e, 0xb6, 0xd5, 0x96,
+ 0xb9, 0x98, 0x86, 0x4e, 0x7a, 0x67, 0xe2, 0x93, 0xaf, 0xda, 0xa5, 0xd1,
+ 0x27, 0xfb, 0xf1, 0x66, 0xc3, 0x2a, 0x03, 0x0c, 0xb6, 0xc7, 0x82, 0x1d,
+ 0x39, 0xfb, 0x3c, 0xde, 0x29, 0x36, 0x71, 0x5d, 0xe1, 0xa8, 0xb5, 0x16,
+ 0x39, 0x7c, 0x1b, 0xff, 0x7b, 0x86, 0xf5, 0x80, 0x92, 0x95, 0xe0, 0x03,
+ 0x3b, 0xaa, 0x44, 0xfb, 0xf4, 0x00, 0xb5, 0xe5, 0xa9, 0xe2, 0xfa, 0x18,
+ 0xf9, 0x84, 0x9a, 0xc1, 0xe1, 0xf6, 0x2e, 0x0e, 0x81, 0x8b, 0x14, 0x29,
+ 0x34, 0xff, 0x1f, 0x55, 0x60, 0x88, 0xa4, 0x99, 0xc6, 0x6f, 0x6f, 0x04,
+ 0x39, 0x3a, 0x75, 0xa4, 0xa7, 0x1e, 0x58, 0xdf, 0xb7, 0xff, 0xc9, 0x9a,
+ 0x1d, 0x70, 0xdb, 0x83, 0xa0, 0xd3, 0x83, 0x1b, 0x2d, 0x6d, 0x2a, 0x90,
+ 0x5b, 0xa3, 0x63, 0x91, 0x73, 0xb5, 0xff, 0x9d, 0x82, 0x7a, 0x41, 0xf3,
+ 0xd3, 0xaa, 0x2f, 0x0b, 0x0d, 0x9f, 0xcf, 0x44, 0xc0, 0x5e, 0xc7, 0xa1,
+ 0x6b, 0xcf, 0xae, 0x94, 0xdb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0x32, 0x30, 0x82, 0x01, 0x2e, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x28, 0xc4, 0xeb, 0x8f, 0xf1, 0x5f, 0x79,
+ 0x90, 0xa3, 0x2b, 0x55, 0xc3, 0x56, 0x4e, 0x7d, 0x6b, 0x53, 0x72, 0x2c,
+ 0x18, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+ 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f,
+ 0x63, 0x73, 0x70, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3f, 0x30, 0x3d, 0x30,
+ 0x3b, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x33, 0x30, 0x31, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x25, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x65,
+ 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x70, 0x73,
+ 0x30, 0x41, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3a, 0x30, 0x38, 0x30,
+ 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x63,
+ 0x72, 0x6c, 0x2e, 0x67, 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x6f, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x50, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0x2c, 0xd5, 0x50, 0x41, 0x97, 0x15, 0x8b, 0xf0, 0x8f, 0x36, 0x61, 0x5b,
+ 0x4a, 0xfb, 0x6b, 0xd9, 0x99, 0xc9, 0x33, 0x92, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x02, 0x60, 0xa3, 0x16, 0x12, 0x9d, 0xd8, 0x1c,
+ 0x19, 0xe4, 0x5a, 0x37, 0x6c, 0xff, 0x32, 0x98, 0x37, 0x46, 0x4f, 0xbc,
+ 0x81, 0x7c, 0x80, 0xc3, 0xca, 0x89, 0x2a, 0x00, 0xfe, 0x5e, 0x3e, 0xec,
+ 0xba, 0x8c, 0x2b, 0x1f, 0xab, 0x95, 0x6b, 0x91, 0x94, 0x21, 0xa0, 0x60,
+ 0x1f, 0x02, 0x06, 0xfa, 0xcf, 0x17, 0x6d, 0xf8, 0x95, 0xab, 0xcd, 0x78,
+ 0x23, 0x14, 0x96, 0xc0, 0x9d, 0x1f, 0x1b, 0xeb, 0x50, 0xe1, 0x65, 0x42,
+ 0x8a, 0xd2, 0xb3, 0xc9, 0xad, 0x80, 0xc3, 0x67, 0xcf, 0xb4, 0x58, 0x1b,
+ 0xd5, 0x04, 0xe4, 0x58, 0xfe, 0x34, 0x45, 0xe0, 0xfb, 0xa4, 0x84, 0x22,
+ 0x8b, 0xe9, 0xe2, 0x37, 0x4c, 0x98, 0xf1, 0x0b, 0xff, 0xa4, 0x89, 0x53,
+ 0xd1, 0x4d, 0xc0, 0x68, 0x48, 0xd7, 0x59, 0x87, 0x1a, 0x3b, 0x7d, 0xf5,
+ 0xd0, 0xf9, 0x23, 0x72, 0xca, 0x60, 0xfd, 0xc3, 0x22, 0x15, 0xf0, 0x9a,
+ 0x95, 0x58, 0x6f, 0x7c, 0x24, 0x93, 0xec, 0xa5, 0x12, 0x3d, 0xb4, 0x1b,
+ 0x01, 0xe8, 0xee, 0x69, 0xed, 0x41, 0x6b, 0x52, 0xcb, 0x9a, 0xb7, 0x5c,
+ 0x15, 0xd1, 0xbd, 0x06, 0x40, 0x7a, 0xe0, 0x0c, 0x97, 0xcb, 0x60, 0xe7,
+ 0x82, 0x5f, 0x6a, 0x5f, 0xde, 0x49, 0x84, 0x56, 0x6a, 0xaf, 0x7c, 0xb0,
+ 0x4b, 0xad, 0x8c, 0x4f, 0x0f, 0x79, 0xa0, 0xcc, 0x11, 0x3c, 0x25, 0xe7,
+ 0x46, 0xbf, 0x7a, 0xd0, 0x2f, 0x88, 0xc8, 0xbf, 0xeb, 0x94, 0x0b, 0x6a,
+ 0x75, 0x33, 0x7f, 0x73, 0x00, 0xb8, 0x12, 0x70, 0x23, 0x5e, 0x55, 0x7f,
+ 0x45, 0x5b, 0x1e, 0x10, 0xb1, 0x02, 0x68, 0xd8, 0x27, 0x40, 0xcf, 0x24,
+ 0x09, 0xe2, 0x65, 0x74, 0xce, 0x89, 0x44, 0x8d, 0x7b, 0x28, 0x90, 0x68,
+ 0xae, 0xac, 0xc2, 0x38, 0xc8, 0x56, 0x0d, 0x33, 0x88, 0x28, 0x7f, 0x54,
+ 0xfc, 0x3c, 0x3c, 0x50, 0x09, 0x93, 0x3d, 0x38,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1184796954 (0x469e911a)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority
+ Validity
+ Not Before: Mar 23 15:18:27 2009 GMT
+ Not After : Mar 23 15:48:27 2019 GMT
+ Subject: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:4d:4b:a9:12:86:b2:ea:a3:20:07:15:16:64:
+ 2a:2b:4b:d1:bf:0b:4a:4d:8e:ed:80:76:a5:67:b7:
+ 78:40:c0:73:42:c8:68:c0:db:53:2b:dd:5e:b8:76:
+ 98:35:93:8b:1a:9d:7c:13:3a:0e:1f:5b:b7:1e:cf:
+ e5:24:14:1e:b1:81:a9:8d:7d:b8:cc:6b:4b:03:f1:
+ 02:0c:dc:ab:a5:40:24:00:7f:74:94:a1:9d:08:29:
+ b3:88:0b:f5:87:77:9d:55:cd:e4:c3:7e:d7:6a:64:
+ ab:85:14:86:95:5b:97:32:50:6f:3d:c8:ba:66:0c:
+ e3:fc:bd:b8:49:c1:76:89:49:19:fd:c0:a8:bd:89:
+ a3:67:2f:c6:9f:bc:71:19:60:b8:2d:e9:2c:c9:90:
+ 76:66:7b:94:e2:af:78:d6:65:53:5d:3c:d6:9c:b2:
+ cf:29:03:f9:2f:a4:50:b2:d4:48:ce:05:32:55:8a:
+ fd:b2:64:4c:0e:e4:98:07:75:db:7f:df:b9:08:55:
+ 60:85:30:29:f9:7b:48:a4:69:86:e3:35:3f:1e:86:
+ 5d:7a:7a:15:bd:ef:00:8e:15:22:54:17:00:90:26:
+ 93:bc:0e:49:68:91:bf:f8:47:d3:9d:95:42:c1:0e:
+ 4d:df:6f:26:cf:c3:18:21:62:66:43:70:d6:d5:c0:
+ 07:e1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/server1.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/CPS
+
+ X509v3 Subject Key Identifier:
+ 55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+ X509v3 Authority Key Identifier:
+ keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V7.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ 8f:65:a2:30:8e:26:ab:8a:ec:35:16:98:e9:03:f0:8d:17:5f:
+ bc:4c:6c:02:f6:74:52:e0:c2:c6:1f:ce:f2:a6:11:0c:a8:b1:
+ 0e:4d:84:8b:71:36:ef:b3:35:45:f3:c1:f8:96:c5:8b:55:a4:
+ cc:6b:83:16:20:32:da:be:fb:af:9b:b7:9f:e1:7e:84:9f:9e:
+ 3c:50:a7:3f:5c:c2:be:8b:86:b8:08:92:ee:f8:42:2b:0d:13:
+ e3:76:85:48:0a:4a:bf:d0:a5:3b:0a:b0:54:b8:6d:e3:08:f9:
+ 34:8d:0b:8e:8b:12:cc:17:1a:33:87:95:c8:9e:0a:dc:50:53:
+ 17:7b
+-----BEGIN CERTIFICATE-----
+MIIEnzCCBAigAwIBAgIERp6RGjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
+VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
+ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
+KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
+ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wOTAz
+MjMxNTE4MjdaFw0xOTAzMjMxNTQ4MjdaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5l
+dDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5u
+ZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+rU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3ed
+Vc3kw37XamSrhRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4
+LeksyZB2ZnuU4q941mVTXTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5
+CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N
+328mz8MYIWJmQ3DW1cAH4QIDAQABo4IBJzCCASMwDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRw
+Oi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js
+LmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYG
+CCsGAQUFBwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NQUzAdBgNVHQ4EFgQU
+VeSB0RGAvtiJuQijMfmhJAkWuXAwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX
+8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEFBQAD
+gYEAj2WiMI4mq4rsNRaY6QPwjRdfvExsAvZ0UuDCxh/O8qYRDKixDk2Ei3E277M1
+RfPB+JbFi1WkzGuDFiAy2r77r5u3n+F+hJ+ePFCnP1zCvouGuAiS7vhCKw0T43aF
+SApKv9ClOwqwVLht4wj5NI0LjosSzBcaM4eVyJ4K3FBTF3s=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert46[] = {
+ 0x30, 0x82, 0x04, 0x9f, 0x30, 0x82, 0x04, 0x08, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x46, 0x9e, 0x91, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xc3, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28,
+ 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e,
+ 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c,
+ 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d,
+ 0x69, 0x74, 0x65, 0x64, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x31, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x33,
+ 0x32, 0x33, 0x31, 0x35, 0x31, 0x38, 0x32, 0x37, 0x5a, 0x17, 0x0d, 0x31,
+ 0x39, 0x30, 0x33, 0x32, 0x33, 0x31, 0x35, 0x34, 0x38, 0x32, 0x37, 0x5a,
+ 0x30, 0x81, 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x31, 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37,
+ 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38,
+ 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20,
+ 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73,
+ 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39,
+ 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33,
+ 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30,
+ 0x34, 0x38, 0x29, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xad, 0x4d, 0x4b, 0xa9, 0x12, 0x86, 0xb2, 0xea, 0xa3, 0x20, 0x07, 0x15,
+ 0x16, 0x64, 0x2a, 0x2b, 0x4b, 0xd1, 0xbf, 0x0b, 0x4a, 0x4d, 0x8e, 0xed,
+ 0x80, 0x76, 0xa5, 0x67, 0xb7, 0x78, 0x40, 0xc0, 0x73, 0x42, 0xc8, 0x68,
+ 0xc0, 0xdb, 0x53, 0x2b, 0xdd, 0x5e, 0xb8, 0x76, 0x98, 0x35, 0x93, 0x8b,
+ 0x1a, 0x9d, 0x7c, 0x13, 0x3a, 0x0e, 0x1f, 0x5b, 0xb7, 0x1e, 0xcf, 0xe5,
+ 0x24, 0x14, 0x1e, 0xb1, 0x81, 0xa9, 0x8d, 0x7d, 0xb8, 0xcc, 0x6b, 0x4b,
+ 0x03, 0xf1, 0x02, 0x0c, 0xdc, 0xab, 0xa5, 0x40, 0x24, 0x00, 0x7f, 0x74,
+ 0x94, 0xa1, 0x9d, 0x08, 0x29, 0xb3, 0x88, 0x0b, 0xf5, 0x87, 0x77, 0x9d,
+ 0x55, 0xcd, 0xe4, 0xc3, 0x7e, 0xd7, 0x6a, 0x64, 0xab, 0x85, 0x14, 0x86,
+ 0x95, 0x5b, 0x97, 0x32, 0x50, 0x6f, 0x3d, 0xc8, 0xba, 0x66, 0x0c, 0xe3,
+ 0xfc, 0xbd, 0xb8, 0x49, 0xc1, 0x76, 0x89, 0x49, 0x19, 0xfd, 0xc0, 0xa8,
+ 0xbd, 0x89, 0xa3, 0x67, 0x2f, 0xc6, 0x9f, 0xbc, 0x71, 0x19, 0x60, 0xb8,
+ 0x2d, 0xe9, 0x2c, 0xc9, 0x90, 0x76, 0x66, 0x7b, 0x94, 0xe2, 0xaf, 0x78,
+ 0xd6, 0x65, 0x53, 0x5d, 0x3c, 0xd6, 0x9c, 0xb2, 0xcf, 0x29, 0x03, 0xf9,
+ 0x2f, 0xa4, 0x50, 0xb2, 0xd4, 0x48, 0xce, 0x05, 0x32, 0x55, 0x8a, 0xfd,
+ 0xb2, 0x64, 0x4c, 0x0e, 0xe4, 0x98, 0x07, 0x75, 0xdb, 0x7f, 0xdf, 0xb9,
+ 0x08, 0x55, 0x60, 0x85, 0x30, 0x29, 0xf9, 0x7b, 0x48, 0xa4, 0x69, 0x86,
+ 0xe3, 0x35, 0x3f, 0x1e, 0x86, 0x5d, 0x7a, 0x7a, 0x15, 0xbd, 0xef, 0x00,
+ 0x8e, 0x15, 0x22, 0x54, 0x17, 0x00, 0x90, 0x26, 0x93, 0xbc, 0x0e, 0x49,
+ 0x68, 0x91, 0xbf, 0xf8, 0x47, 0xd3, 0x9d, 0x95, 0x42, 0xc1, 0x0e, 0x4d,
+ 0xdf, 0x6f, 0x26, 0xcf, 0xc3, 0x18, 0x21, 0x62, 0x66, 0x43, 0x70, 0xd6,
+ 0xd5, 0xc0, 0x07, 0xe1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+ 0x27, 0x30, 0x82, 0x01, 0x23, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+ 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0xa0, 0x24,
+ 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30,
+ 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50,
+ 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9, 0x08, 0xa3,
+ 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9, 0x70, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf0, 0x17, 0x62,
+ 0x13, 0x55, 0x3d, 0xb3, 0xff, 0x0a, 0x00, 0x6b, 0xfb, 0x50, 0x84, 0x97,
+ 0xf3, 0xed, 0x62, 0xd0, 0x1a, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04,
+ 0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x81, 0x81, 0x00, 0x8f, 0x65, 0xa2, 0x30, 0x8e, 0x26, 0xab, 0x8a, 0xec,
+ 0x35, 0x16, 0x98, 0xe9, 0x03, 0xf0, 0x8d, 0x17, 0x5f, 0xbc, 0x4c, 0x6c,
+ 0x02, 0xf6, 0x74, 0x52, 0xe0, 0xc2, 0xc6, 0x1f, 0xce, 0xf2, 0xa6, 0x11,
+ 0x0c, 0xa8, 0xb1, 0x0e, 0x4d, 0x84, 0x8b, 0x71, 0x36, 0xef, 0xb3, 0x35,
+ 0x45, 0xf3, 0xc1, 0xf8, 0x96, 0xc5, 0x8b, 0x55, 0xa4, 0xcc, 0x6b, 0x83,
+ 0x16, 0x20, 0x32, 0xda, 0xbe, 0xfb, 0xaf, 0x9b, 0xb7, 0x9f, 0xe1, 0x7e,
+ 0x84, 0x9f, 0x9e, 0x3c, 0x50, 0xa7, 0x3f, 0x5c, 0xc2, 0xbe, 0x8b, 0x86,
+ 0xb8, 0x08, 0x92, 0xee, 0xf8, 0x42, 0x2b, 0x0d, 0x13, 0xe3, 0x76, 0x85,
+ 0x48, 0x0a, 0x4a, 0xbf, 0xd0, 0xa5, 0x3b, 0x0a, 0xb0, 0x54, 0xb8, 0x6d,
+ 0xe3, 0x08, 0xf9, 0x34, 0x8d, 0x0b, 0x8e, 0x8b, 0x12, 0xcc, 0x17, 0x1a,
+ 0x33, 0x87, 0x95, 0xc8, 0x9e, 0x0a, 0xdc, 0x50, 0x53, 0x17, 0x7b,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 5a:b6:1d:ac:1e:4d:a2:06:14:c7:55:3d:3d:a9:b2:dc
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Validity
+ Not Before: Oct 23 00:00:00 2008 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=FR, O=GANDI SAS, CN=Gandi Standard SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:54:3d:a5:db:0d:22:78:50:6a:5a:23:89:3f:
+ 97:a1:d4:07:1a:a9:58:08:9b:a0:15:c3:32:b6:b7:
+ f1:e8:b9:a5:6f:ad:37:f6:6e:71:1b:b4:75:2d:48:
+ 5e:9f:c6:15:aa:81:ef:e5:c4:88:95:8a:3a:6c:77:
+ cc:b5:cd:65:e4:67:e5:73:c9:50:52:94:c1:27:49:
+ 3e:a0:6b:41:16:41:b6:94:99:41:ae:3e:cb:e2:06:
+ 46:09:e9:4d:be:c9:4c:55:a9:18:7e:a6:df:6e:fd:
+ 4a:b2:cc:6c:4e:d9:c8:50:15:93:b3:f2:e9:e3:c2:
+ 6a:ad:3a:d5:fb:c3:79:50:9f:25:79:29:b2:47:64:
+ 7c:20:3e:e2:08:4d:93:29:14:b6:34:6e:cf:71:46:
+ 7e:76:10:f4:fd:6c:aa:01:d2:c2:06:de:92:83:cc:
+ 58:90:2e:92:de:1e:65:b7:63:2f:3d:b2:eb:70:8c:
+ 4c:e0:be:15:9d:de:c1:4d:56:f8:0b:c6:8e:07:b9:
+ 5d:df:95:f0:7b:40:1f:1a:2c:d7:9c:2b:4b:76:f4:
+ 59:f5:43:c1:2c:66:10:9e:9e:66:96:60:9d:1c:74:
+ 1b:4e:18:5c:08:b0:6e:6c:ca:69:1a:02:e9:bb:ca:
+ 78:ef:66:2e:e3:32:fd:41:5c:95:74:81:4d:f4:da:
+ fe:4b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+ X509v3 Subject Key Identifier:
+ B6:A8:FF:A2:A8:2F:D0:A6:CD:4B:B1:68:F3:E7:50:10:31:A7:79:21
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.2.26
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/UTNAddTrustServer_CA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 19:53:bf:03:3d:9b:e2:6b:5a:fd:ba:49:1f:4f:ec:e1:c6:82:
+ 39:3c:d2:03:04:0f:ab:7b:3e:82:a9:85:10:1f:f4:de:32:af:
+ 58:3f:ff:70:f3:30:1d:97:2d:4c:9a:e2:ec:0c:3e:14:2d:2f:
+ 98:48:9d:ae:16:6a:ac:2d:42:aa:b5:64:a4:70:bb:eb:73:94:
+ 7b:46:4c:e7:7a:14:76:5b:4c:1d:84:a1:20:74:1f:2e:4b:5c:
+ 70:88:dc:bd:f7:19:3d:ed:59:0d:e2:3f:26:e2:9c:ac:a4:3c:
+ 95:1c:f8:be:8c:03:ae:f0:e5:9c:4d:bc:c7:9b:58:00:bf:af:
+ ad:fa:37:6e:71:6d:18:34:0e:c1:ea:6a:f8:0d:df:69:54:56:
+ 15:f2:28:b3:fe:a4:63:ec:c5:04:64:60:bb:fe:2a:f0:f4:87:
+ a1:b0:ae:bd:aa:e4:2f:e3:03:0b:2f:66:5f:85:a4:32:7b:46:
+ ed:25:0c:e7:f1:b7:e7:19:fd:60:ba:5f:87:77:de:98:07:96:
+ e4:5e:ea:63:7d:a8:de:55:da:61:5c:3c:90:83:43:04:07:3c:
+ dd:f3:f8:9f:06:52:0a:de:c7:b6:7b:8f:e1:11:f7:04:7a:35:
+ ff:6a:bc:5b:c7:50:49:08:70:6f:94:43:cd:9e:c7:70:f1:db:
+ d0:6d:da:8f
+-----BEGIN CERTIFICATE-----
+MIIEozCCA4ugAwIBAgIQWrYdrB5NogYUx1U9Pamy3DANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDgxMDIzMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBBMQswCQYD
+VQQGEwJGUjESMBAGA1UEChMJR0FOREkgU0FTMR4wHAYDVQQDExVHYW5kaSBTdGFu
+ZGFyZCBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2VD2l
+2w0ieFBqWiOJP5eh1AcaqVgIm6AVwzK2t/HouaVvrTf2bnEbtHUtSF6fxhWqge/l
+xIiVijpsd8y1zWXkZ+VzyVBSlMEnST6ga0EWQbaUmUGuPsviBkYJ6U2+yUxVqRh+
+pt9u/UqyzGxO2chQFZOz8unjwmqtOtX7w3lQnyV5KbJHZHwgPuIITZMpFLY0bs9x
+Rn52EPT9bKoB0sIG3pKDzFiQLpLeHmW3Yy89sutwjEzgvhWd3sFNVvgLxo4HuV3f
+lfB7QB8aLNecK0t29Fn1Q8EsZhCenmaWYJ0cdBtOGFwIsG5symkaAum7ynjvZi7j
+Mv1BXJV0gU302v5LAgMBAAGjggE+MIIBOjAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd
+BzfVhZadS9LDRTAdBgNVHQ4EFgQUtqj/oqgv0KbNS7Fo8+dQEDGneSEwDgYDVR0P
+AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwGAYDVR0gBBEwDzANBgsrBgEE
+AbIxAQICGjBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5j
+b20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5jcmwwdAYIKwYBBQUHAQEEaDBmMD0G
+CCsGAQUFBzAChjFodHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RT
+ZXJ2ZXJfQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAZU78DPZvia1r9ukkfT+zhxoI5PNIDBA+r
+ez6CqYUQH/TeMq9YP/9w8zAdly1MmuLsDD4ULS+YSJ2uFmqsLUKqtWSkcLvrc5R7
+RkznehR2W0wdhKEgdB8uS1xwiNy99xk97VkN4j8m4pyspDyVHPi+jAOu8OWcTbzH
+m1gAv6+t+jducW0YNA7B6mr4Dd9pVFYV8iiz/qRj7MUEZGC7/irw9IehsK69quQv
+4wMLL2ZfhaQye0btJQzn8bfnGf1gul+Hd96YB5bkXupjfajeVdphXDyQg0MEBzzd
+8/ifBlIK3se2e4/hEfcEejX/arxbx1BJCHBvlEPNnsdw8dvQbdqP
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert47[] = {
+ 0x30, 0x82, 0x04, 0xa3, 0x30, 0x82, 0x03, 0x8b, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x5a, 0xb6, 0x1d, 0xac, 0x1e, 0x4d, 0xa2, 0x06, 0x14,
+ 0xc7, 0x55, 0x3d, 0x3d, 0xa9, 0xb2, 0xdc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+ 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+ 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x38, 0x31, 0x30, 0x32, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+ 0x38, 0x33, 0x38, 0x5a, 0x30, 0x41, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x46, 0x52, 0x31, 0x12, 0x30, 0x10, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09, 0x47, 0x41, 0x4e, 0x44, 0x49, 0x20,
+ 0x53, 0x41, 0x53, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x15, 0x47, 0x61, 0x6e, 0x64, 0x69, 0x20, 0x53, 0x74, 0x61, 0x6e,
+ 0x64, 0x61, 0x72, 0x64, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x54, 0x3d, 0xa5,
+ 0xdb, 0x0d, 0x22, 0x78, 0x50, 0x6a, 0x5a, 0x23, 0x89, 0x3f, 0x97, 0xa1,
+ 0xd4, 0x07, 0x1a, 0xa9, 0x58, 0x08, 0x9b, 0xa0, 0x15, 0xc3, 0x32, 0xb6,
+ 0xb7, 0xf1, 0xe8, 0xb9, 0xa5, 0x6f, 0xad, 0x37, 0xf6, 0x6e, 0x71, 0x1b,
+ 0xb4, 0x75, 0x2d, 0x48, 0x5e, 0x9f, 0xc6, 0x15, 0xaa, 0x81, 0xef, 0xe5,
+ 0xc4, 0x88, 0x95, 0x8a, 0x3a, 0x6c, 0x77, 0xcc, 0xb5, 0xcd, 0x65, 0xe4,
+ 0x67, 0xe5, 0x73, 0xc9, 0x50, 0x52, 0x94, 0xc1, 0x27, 0x49, 0x3e, 0xa0,
+ 0x6b, 0x41, 0x16, 0x41, 0xb6, 0x94, 0x99, 0x41, 0xae, 0x3e, 0xcb, 0xe2,
+ 0x06, 0x46, 0x09, 0xe9, 0x4d, 0xbe, 0xc9, 0x4c, 0x55, 0xa9, 0x18, 0x7e,
+ 0xa6, 0xdf, 0x6e, 0xfd, 0x4a, 0xb2, 0xcc, 0x6c, 0x4e, 0xd9, 0xc8, 0x50,
+ 0x15, 0x93, 0xb3, 0xf2, 0xe9, 0xe3, 0xc2, 0x6a, 0xad, 0x3a, 0xd5, 0xfb,
+ 0xc3, 0x79, 0x50, 0x9f, 0x25, 0x79, 0x29, 0xb2, 0x47, 0x64, 0x7c, 0x20,
+ 0x3e, 0xe2, 0x08, 0x4d, 0x93, 0x29, 0x14, 0xb6, 0x34, 0x6e, 0xcf, 0x71,
+ 0x46, 0x7e, 0x76, 0x10, 0xf4, 0xfd, 0x6c, 0xaa, 0x01, 0xd2, 0xc2, 0x06,
+ 0xde, 0x92, 0x83, 0xcc, 0x58, 0x90, 0x2e, 0x92, 0xde, 0x1e, 0x65, 0xb7,
+ 0x63, 0x2f, 0x3d, 0xb2, 0xeb, 0x70, 0x8c, 0x4c, 0xe0, 0xbe, 0x15, 0x9d,
+ 0xde, 0xc1, 0x4d, 0x56, 0xf8, 0x0b, 0xc6, 0x8e, 0x07, 0xb9, 0x5d, 0xdf,
+ 0x95, 0xf0, 0x7b, 0x40, 0x1f, 0x1a, 0x2c, 0xd7, 0x9c, 0x2b, 0x4b, 0x76,
+ 0xf4, 0x59, 0xf5, 0x43, 0xc1, 0x2c, 0x66, 0x10, 0x9e, 0x9e, 0x66, 0x96,
+ 0x60, 0x9d, 0x1c, 0x74, 0x1b, 0x4e, 0x18, 0x5c, 0x08, 0xb0, 0x6e, 0x6c,
+ 0xca, 0x69, 0x1a, 0x02, 0xe9, 0xbb, 0xca, 0x78, 0xef, 0x66, 0x2e, 0xe3,
+ 0x32, 0xfd, 0x41, 0x5c, 0x95, 0x74, 0x81, 0x4d, 0xf4, 0xda, 0xfe, 0x4b,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x3e, 0x30, 0x82, 0x01,
+ 0x3a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d,
+ 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb6, 0xa8, 0xff,
+ 0xa2, 0xa8, 0x2f, 0xd0, 0xa6, 0xcd, 0x4b, 0xb1, 0x68, 0xf3, 0xe7, 0x50,
+ 0x10, 0x31, 0xa7, 0x79, 0x21, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+ 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x1a, 0x30, 0x44, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35,
+ 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46,
+ 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72,
+ 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x74, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x68, 0x30, 0x66, 0x30, 0x3d, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x31, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53,
+ 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+ 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x19,
+ 0x53, 0xbf, 0x03, 0x3d, 0x9b, 0xe2, 0x6b, 0x5a, 0xfd, 0xba, 0x49, 0x1f,
+ 0x4f, 0xec, 0xe1, 0xc6, 0x82, 0x39, 0x3c, 0xd2, 0x03, 0x04, 0x0f, 0xab,
+ 0x7b, 0x3e, 0x82, 0xa9, 0x85, 0x10, 0x1f, 0xf4, 0xde, 0x32, 0xaf, 0x58,
+ 0x3f, 0xff, 0x70, 0xf3, 0x30, 0x1d, 0x97, 0x2d, 0x4c, 0x9a, 0xe2, 0xec,
+ 0x0c, 0x3e, 0x14, 0x2d, 0x2f, 0x98, 0x48, 0x9d, 0xae, 0x16, 0x6a, 0xac,
+ 0x2d, 0x42, 0xaa, 0xb5, 0x64, 0xa4, 0x70, 0xbb, 0xeb, 0x73, 0x94, 0x7b,
+ 0x46, 0x4c, 0xe7, 0x7a, 0x14, 0x76, 0x5b, 0x4c, 0x1d, 0x84, 0xa1, 0x20,
+ 0x74, 0x1f, 0x2e, 0x4b, 0x5c, 0x70, 0x88, 0xdc, 0xbd, 0xf7, 0x19, 0x3d,
+ 0xed, 0x59, 0x0d, 0xe2, 0x3f, 0x26, 0xe2, 0x9c, 0xac, 0xa4, 0x3c, 0x95,
+ 0x1c, 0xf8, 0xbe, 0x8c, 0x03, 0xae, 0xf0, 0xe5, 0x9c, 0x4d, 0xbc, 0xc7,
+ 0x9b, 0x58, 0x00, 0xbf, 0xaf, 0xad, 0xfa, 0x37, 0x6e, 0x71, 0x6d, 0x18,
+ 0x34, 0x0e, 0xc1, 0xea, 0x6a, 0xf8, 0x0d, 0xdf, 0x69, 0x54, 0x56, 0x15,
+ 0xf2, 0x28, 0xb3, 0xfe, 0xa4, 0x63, 0xec, 0xc5, 0x04, 0x64, 0x60, 0xbb,
+ 0xfe, 0x2a, 0xf0, 0xf4, 0x87, 0xa1, 0xb0, 0xae, 0xbd, 0xaa, 0xe4, 0x2f,
+ 0xe3, 0x03, 0x0b, 0x2f, 0x66, 0x5f, 0x85, 0xa4, 0x32, 0x7b, 0x46, 0xed,
+ 0x25, 0x0c, 0xe7, 0xf1, 0xb7, 0xe7, 0x19, 0xfd, 0x60, 0xba, 0x5f, 0x87,
+ 0x77, 0xde, 0x98, 0x07, 0x96, 0xe4, 0x5e, 0xea, 0x63, 0x7d, 0xa8, 0xde,
+ 0x55, 0xda, 0x61, 0x5c, 0x3c, 0x90, 0x83, 0x43, 0x04, 0x07, 0x3c, 0xdd,
+ 0xf3, 0xf8, 0x9f, 0x06, 0x52, 0x0a, 0xde, 0xc7, 0xb6, 0x7b, 0x8f, 0xe1,
+ 0x11, 0xf7, 0x04, 0x7a, 0x35, 0xff, 0x6a, 0xbc, 0x5b, 0xc7, 0x50, 0x49,
+ 0x08, 0x70, 0x6f, 0x94, 0x43, 0xcd, 0x9e, 0xc7, 0x70, 0xf1, 0xdb, 0xd0,
+ 0x6d, 0xda, 0x8f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 10:e7:76:e8:a6:5a:6e:37:7e:05:03:06:d4:3c:25:ea
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Validity
+ Not Before: Apr 10 00:00:00 2006 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c3:dd:36:cc:83:c3:18:55:b0:96:d9:13:25:d3:
+ 26:86:48:38:bb:16:7f:f1:9f:29:f6:fd:03:f1:ed:
+ 4d:26:9a:56:f0:b5:1a:1a:cd:e6:cc:85:55:40:a4:
+ b5:d0:0d:ca:22:ef:3d:23:c6:7e:6c:cc:bc:a1:e9:
+ 7c:50:46:e0:bd:14:ad:65:12:c2:0b:11:69:52:0a:
+ 07:92:1f:73:6f:c1:ba:d7:62:f0:ce:00:2e:34:a5:
+ c8:e6:2f:0f:ec:0d:ea:44:61:75:68:e5:e4:dc:80:
+ 36:4f:da:78:5d:53:25:94:94:f5:4f:2e:3a:60:6f:
+ 0c:a6:d9:b3:f6:2a:2e:03:12:d5:26:42:07:51:b2:
+ 64:57:71:dc:21:1c:89:c7:69:a3:e6:fb:c2:7b:6e:
+ ef:0c:87:fb:50:64:e8:4e:4b:ef:e7:71:9b:83:63:
+ 61:c9:32:8d:8c:ec:14:a7:e4:89:ad:3f:2b:26:64:
+ e4:85:42:f2:89:50:e1:3a:be:15:e3:45:25:e2:5a:
+ cb:8c:3f:e0:33:1e:35:09:5a:84:ea:7e:5d:a1:f5:
+ 91:80:0a:28:06:b7:cb:31:41:25:61:8b:01:e9:56:
+ a2:f6:3e:5f:2f:f3:c4:43:f6:19:94:75:83:4c:a1:
+ 82:42:3a:c6:ba:c4:09:30:a6:e1:75:02:51:b9:5e:
+ 64:8b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+ X509v3 Subject Key Identifier:
+ 3C:41:E2:8F:08:08:A9:4C:25:89:8D:6D:C5:38:D0:FC:85:8C:62:17
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.782.1.2.1.3.1
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://www.usertrust.com/cacerts/UTNAddTrustServer_CA.crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 68:ab:fc:ef:80:6b:18:b2:b0:b3:a3:45:89:cb:53:c5:a2:e6:
+ af:08:a9:fd:ff:0f:49:ac:ff:e4:9f:d7:41:7c:a3:c5:a2:e8:
+ aa:e0:57:21:2d:c3:aa:7c:0c:4c:28:0b:79:f4:ee:4c:32:ad:
+ 79:0e:7e:a2:5e:34:18:4f:df:54:f1:bd:68:7c:e3:d3:d7:46:
+ 5e:6d:64:c2:f7:6d:88:82:73:0c:ef:99:85:ea:a9:ef:32:4a:
+ f0:83:9f:73:91:0c:a4:3e:2b:31:51:a6:62:8f:15:84:f9:a6:
+ 3a:12:30:3f:da:6e:f8:cc:c7:19:92:0f:5c:f4:fe:17:f1:95:
+ 08:47:52:2c:50:8f:e8:9b:a5:ee:ae:70:33:89:91:82:fe:30:
+ aa:76:76:59:d7:6c:18:d3:2b:12:5b:1d:28:1d:78:71:f6:cd:
+ 36:a2:e9:07:48:44:3b:e7:57:6e:82:0a:ad:c5:8a:dd:e8:53:
+ b4:71:af:13:d2:06:9d:37:6d:53:3f:8a:35:08:fa:fe:a2:16:
+ e6:b9:6f:5c:56:39:d6:c6:aa:ef:19:67:ce:13:c5:b8:95:05:
+ fb:0a:44:c9:9f:a9:40:25:4b:32:11:af:07:fe:08:d5:42:71:
+ e9:e1:53:8b:15:1f:dd:2a:07:95:70:24:6f:64:5e:d3:b7:90:
+ 2e:8b:21:d8
+-----BEGIN CERTIFICATE-----
+MIIEpjCCA46gAwIBAgIQEOd26KZabjd+BQMG1Dwl6jANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYwNDEwMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBiMQswCQYD
+VQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYD
+VQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD3TbMg8MYVbCW2RMl0yaGSDi7
+Fn/xnyn2/QPx7U0mmlbwtRoazebMhVVApLXQDcoi7z0jxn5szLyh6XxQRuC9FK1l
+EsILEWlSCgeSH3NvwbrXYvDOAC40pcjmLw/sDepEYXVo5eTcgDZP2nhdUyWUlPVP
+Ljpgbwym2bP2Ki4DEtUmQgdRsmRXcdwhHInHaaPm+8J7bu8Mh/tQZOhOS+/ncZuD
+Y2HJMo2M7BSn5ImtPysmZOSFQvKJUOE6vhXjRSXiWsuMP+AzHjUJWoTqfl2h9ZGA
+CigGt8sxQSVhiwHpVqL2Pl8v88RD9hmUdYNMoYJCOsa6xAkwpuF1AlG5XmSLAgMB
+AAGjggEgMIIBHDAfBgNVHSMEGDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNV
+HQ4EFgQUPEHijwgIqUwliY1txTjQ/IWMYhcwDgYDVR0PAQH/BAQDAgEGMBIGA1Ud
+EwEB/wQIMAYBAf8CAQAwGQYDVR0gBBIwEDAOBgwrBgEEAYYOAQIBAwEwRAYDVR0f
+BD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly
+c3QtSGFyZHdhcmUuY3JsMFUGCCsGAQUFBwEBBEkwRzBFBggrBgEFBQcwAoY5aHR0
+cDovL3d3dy51c2VydHJ1c3QuY29tL2NhY2VydHMvVVROQWRkVHJ1c3RTZXJ2ZXJf
+Q0EuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQBoq/zvgGsYsrCzo0WJy1PFouavCKn9
+/w9JrP/kn9dBfKPFouiq4FchLcOqfAxMKAt59O5MMq15Dn6iXjQYT99U8b1ofOPT
+10ZebWTC922IgnMM75mF6qnvMkrwg59zkQykPisxUaZijxWE+aY6EjA/2m74zMcZ
+kg9c9P4X8ZUIR1IsUI/om6XurnAziZGC/jCqdnZZ12wY0ysSWx0oHXhx9s02oukH
+SEQ751duggqtxYrd6FO0ca8T0gadN21TP4o1CPr+ohbmuW9cVjnWxqrvGWfOE8W4
+lQX7CkTJn6lAJUsyEa8H/gjVQnHp4VOLFR/dKgeVcCRvZF7Tt5AuiyHY
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert48[] = {
+ 0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x10, 0xe7, 0x76, 0xe8, 0xa6, 0x5a, 0x6e, 0x37, 0x7e,
+ 0x05, 0x03, 0x06, 0xd4, 0x3c, 0x25, 0xea, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+ 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+ 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x36, 0x30, 0x34, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+ 0x38, 0x33, 0x38, 0x5a, 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20,
+ 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xc3, 0xdd, 0x36, 0xcc, 0x83, 0xc3, 0x18,
+ 0x55, 0xb0, 0x96, 0xd9, 0x13, 0x25, 0xd3, 0x26, 0x86, 0x48, 0x38, 0xbb,
+ 0x16, 0x7f, 0xf1, 0x9f, 0x29, 0xf6, 0xfd, 0x03, 0xf1, 0xed, 0x4d, 0x26,
+ 0x9a, 0x56, 0xf0, 0xb5, 0x1a, 0x1a, 0xcd, 0xe6, 0xcc, 0x85, 0x55, 0x40,
+ 0xa4, 0xb5, 0xd0, 0x0d, 0xca, 0x22, 0xef, 0x3d, 0x23, 0xc6, 0x7e, 0x6c,
+ 0xcc, 0xbc, 0xa1, 0xe9, 0x7c, 0x50, 0x46, 0xe0, 0xbd, 0x14, 0xad, 0x65,
+ 0x12, 0xc2, 0x0b, 0x11, 0x69, 0x52, 0x0a, 0x07, 0x92, 0x1f, 0x73, 0x6f,
+ 0xc1, 0xba, 0xd7, 0x62, 0xf0, 0xce, 0x00, 0x2e, 0x34, 0xa5, 0xc8, 0xe6,
+ 0x2f, 0x0f, 0xec, 0x0d, 0xea, 0x44, 0x61, 0x75, 0x68, 0xe5, 0xe4, 0xdc,
+ 0x80, 0x36, 0x4f, 0xda, 0x78, 0x5d, 0x53, 0x25, 0x94, 0x94, 0xf5, 0x4f,
+ 0x2e, 0x3a, 0x60, 0x6f, 0x0c, 0xa6, 0xd9, 0xb3, 0xf6, 0x2a, 0x2e, 0x03,
+ 0x12, 0xd5, 0x26, 0x42, 0x07, 0x51, 0xb2, 0x64, 0x57, 0x71, 0xdc, 0x21,
+ 0x1c, 0x89, 0xc7, 0x69, 0xa3, 0xe6, 0xfb, 0xc2, 0x7b, 0x6e, 0xef, 0x0c,
+ 0x87, 0xfb, 0x50, 0x64, 0xe8, 0x4e, 0x4b, 0xef, 0xe7, 0x71, 0x9b, 0x83,
+ 0x63, 0x61, 0xc9, 0x32, 0x8d, 0x8c, 0xec, 0x14, 0xa7, 0xe4, 0x89, 0xad,
+ 0x3f, 0x2b, 0x26, 0x64, 0xe4, 0x85, 0x42, 0xf2, 0x89, 0x50, 0xe1, 0x3a,
+ 0xbe, 0x15, 0xe3, 0x45, 0x25, 0xe2, 0x5a, 0xcb, 0x8c, 0x3f, 0xe0, 0x33,
+ 0x1e, 0x35, 0x09, 0x5a, 0x84, 0xea, 0x7e, 0x5d, 0xa1, 0xf5, 0x91, 0x80,
+ 0x0a, 0x28, 0x06, 0xb7, 0xcb, 0x31, 0x41, 0x25, 0x61, 0x8b, 0x01, 0xe9,
+ 0x56, 0xa2, 0xf6, 0x3e, 0x5f, 0x2f, 0xf3, 0xc4, 0x43, 0xf6, 0x19, 0x94,
+ 0x75, 0x83, 0x4c, 0xa1, 0x82, 0x42, 0x3a, 0xc6, 0xba, 0xc4, 0x09, 0x30,
+ 0xa6, 0xe1, 0x75, 0x02, 0x51, 0xb9, 0x5e, 0x64, 0x8b, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x20, 0x30, 0x82, 0x01, 0x1c, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xa1,
+ 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5,
+ 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08,
+ 0xa9, 0x4c, 0x25, 0x89, 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c,
+ 0x62, 0x17, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+ 0x01, 0x00, 0x30, 0x19, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30,
+ 0x10, 0x30, 0x0e, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x86, 0x0e,
+ 0x01, 0x02, 0x01, 0x03, 0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72,
+ 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x55, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x01, 0x01, 0x04, 0x49, 0x30, 0x47, 0x30, 0x45, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x39, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61,
+ 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
+ 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x68, 0xab, 0xfc, 0xef, 0x80, 0x6b, 0x18, 0xb2, 0xb0, 0xb3,
+ 0xa3, 0x45, 0x89, 0xcb, 0x53, 0xc5, 0xa2, 0xe6, 0xaf, 0x08, 0xa9, 0xfd,
+ 0xff, 0x0f, 0x49, 0xac, 0xff, 0xe4, 0x9f, 0xd7, 0x41, 0x7c, 0xa3, 0xc5,
+ 0xa2, 0xe8, 0xaa, 0xe0, 0x57, 0x21, 0x2d, 0xc3, 0xaa, 0x7c, 0x0c, 0x4c,
+ 0x28, 0x0b, 0x79, 0xf4, 0xee, 0x4c, 0x32, 0xad, 0x79, 0x0e, 0x7e, 0xa2,
+ 0x5e, 0x34, 0x18, 0x4f, 0xdf, 0x54, 0xf1, 0xbd, 0x68, 0x7c, 0xe3, 0xd3,
+ 0xd7, 0x46, 0x5e, 0x6d, 0x64, 0xc2, 0xf7, 0x6d, 0x88, 0x82, 0x73, 0x0c,
+ 0xef, 0x99, 0x85, 0xea, 0xa9, 0xef, 0x32, 0x4a, 0xf0, 0x83, 0x9f, 0x73,
+ 0x91, 0x0c, 0xa4, 0x3e, 0x2b, 0x31, 0x51, 0xa6, 0x62, 0x8f, 0x15, 0x84,
+ 0xf9, 0xa6, 0x3a, 0x12, 0x30, 0x3f, 0xda, 0x6e, 0xf8, 0xcc, 0xc7, 0x19,
+ 0x92, 0x0f, 0x5c, 0xf4, 0xfe, 0x17, 0xf1, 0x95, 0x08, 0x47, 0x52, 0x2c,
+ 0x50, 0x8f, 0xe8, 0x9b, 0xa5, 0xee, 0xae, 0x70, 0x33, 0x89, 0x91, 0x82,
+ 0xfe, 0x30, 0xaa, 0x76, 0x76, 0x59, 0xd7, 0x6c, 0x18, 0xd3, 0x2b, 0x12,
+ 0x5b, 0x1d, 0x28, 0x1d, 0x78, 0x71, 0xf6, 0xcd, 0x36, 0xa2, 0xe9, 0x07,
+ 0x48, 0x44, 0x3b, 0xe7, 0x57, 0x6e, 0x82, 0x0a, 0xad, 0xc5, 0x8a, 0xdd,
+ 0xe8, 0x53, 0xb4, 0x71, 0xaf, 0x13, 0xd2, 0x06, 0x9d, 0x37, 0x6d, 0x53,
+ 0x3f, 0x8a, 0x35, 0x08, 0xfa, 0xfe, 0xa2, 0x16, 0xe6, 0xb9, 0x6f, 0x5c,
+ 0x56, 0x39, 0xd6, 0xc6, 0xaa, 0xef, 0x19, 0x67, 0xce, 0x13, 0xc5, 0xb8,
+ 0x95, 0x05, 0xfb, 0x0a, 0x44, 0xc9, 0x9f, 0xa9, 0x40, 0x25, 0x4b, 0x32,
+ 0x11, 0xaf, 0x07, 0xfe, 0x08, 0xd5, 0x42, 0x71, 0xe9, 0xe1, 0x53, 0x8b,
+ 0x15, 0x1f, 0xdd, 0x2a, 0x07, 0x95, 0x70, 0x24, 0x6f, 0x64, 0x5e, 0xd3,
+ 0xb7, 0x90, 0x2e, 0x8b, 0x21, 0xd8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 46:ea:f0:96:05:4c:c5:e3:fa:65:ea:6e:9f:42:c6:64
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Jun 7 08:09:10 2005 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:df:ee:58:10:a2:2b:6e:55:c4:8e:bf:2e:46:09:
+ e7:e0:08:0f:2e:2b:7a:13:94:1b:bd:f6:b6:80:8e:
+ 65:05:93:00:1e:bc:af:e2:0f:8e:19:0d:12:47:ec:
+ ac:ad:a3:fa:2e:70:f8:de:6e:fb:56:42:15:9e:2e:
+ 5c:ef:23:de:21:b9:05:76:27:19:0f:4f:d6:c3:9c:
+ b4:be:94:19:63:f2:a6:11:0a:eb:53:48:9c:be:f2:
+ 29:3b:16:e8:1a:a0:4c:a6:c9:f4:18:59:68:c0:70:
+ f2:53:00:c0:5e:50:82:a5:56:6f:36:f9:4a:e0:44:
+ 86:a0:4d:4e:d6:47:6e:49:4a:cb:67:d7:a6:c4:05:
+ b9:8e:1e:f4:fc:ff:cd:e7:36:e0:9c:05:6c:b2:33:
+ 22:15:d0:b4:e0:cc:17:c0:b2:c0:f4:fe:32:3f:29:
+ 2a:95:7b:d8:f2:a7:4e:0f:54:7c:a1:0d:80:b3:09:
+ 03:c1:ff:5c:dd:5e:9a:3e:bc:ae:bc:47:8a:6a:ae:
+ 71:ca:1f:b1:2a:b8:5f:42:05:0b:ec:46:30:d1:72:
+ 0b:ca:e9:56:6d:f5:ef:df:78:be:61:ba:b2:a5:ae:
+ 04:4c:bc:a8:ac:69:15:97:bd:ef:eb:b4:8c:bf:35:
+ f8:d4:c3:d1:28:0e:5c:3a:9f:70:18:33:20:77:c4:
+ a2:af
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 53:32:D1:B3:CF:7F:FA:E0:F1:A0:5D:85:4E:92:D2:9E:45:1D:B4:4F
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/AddTrustExternalCARoot.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/AddTrustExternalCARoot.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 63:86:92:10:b1:13:fa:37:be:8e:2a:b6:1b:8a:43:f5:5c:ae:
+ 0e:14:df:f7:69:40:7f:bf:1a:71:00:09:d8:bf:d4:24:4a:bf:
+ e0:93:ff:01:d8:0b:c6:0f:ec:7e:47:9c:b0:5d:f7:7c:14:9d:
+ fc:c0:33:92:84:5b:d2:83:f4:52:e2:22:58:74:fc:43:1b:3f:
+ a7:a3:58:da:03:fd:bc:f0:3a:e4:ed:cc:12:bb:c9:b9:ae:7b:
+ 04:a0:04:72:bf:e9:de:2d:d2:a7:51:66:00:73:d2:bd:7e:aa:
+ 9e:53:96:7d:69:b2:18:3e:8e:ad:56:50:7e:f7:d5:b0:ff:39:
+ 62:65:82:8c:96:57:c3:8f:f7:60:f6:c2:8d:34:87:fc:4f:43:
+ e5:db:bf:1c:aa:f6:86:cd:e6:df:11:3f:8d:07:f7:6d:83:13:
+ c0:38:88:39:60:a1:7e:30:e1:e3:88:3e:a4:bb:63:6f:2c:e9:
+ 8a:68:2c:ee:96:69:ac:04:61:e1:4f:4e:0e:9d:72:4c:f6:79:
+ 38:c8:c7:48:69:6f:94:0f:74:b4:bc:c8:cf:57:4d:b9:75:71:
+ 96:0d:8a:06:0b:eb:dd:d0:f0:3c:7d:c6:2e:98:46:6a:38:c7:
+ 02:b5:c8:b8:b2:65:75:de:da:90:08:b6:77:b8:53:00:25:cb:
+ 47:ca:73:5f
+-----BEGIN CERTIFICATE-----
+MIIEpjCCA46gAwIBAgIQRurwlgVMxeP6Zepun0LGZDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEwNDgzOFow
+gZMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtl
+IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMY
+aHR0cDovL3d3dy51c2VydHJ1c3QuY29tMRswGQYDVQQDExJVVE4gLSBEQVRBQ29y
+cCBTR0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf7lgQoituVcSO
+vy5GCefgCA8uK3oTlBu99raAjmUFkwAevK/iD44ZDRJH7Kyto/oucPjebvtWQhWe
+LlzvI94huQV2JxkPT9bDnLS+lBlj8qYRCutTSJy+8ik7FugaoEymyfQYWWjAcPJT
+AMBeUIKlVm82+UrgRIagTU7WR25JSstn16bEBbmOHvT8/83nNuCcBWyyMyIV0LTg
+zBfAssD0/jI/KSqVe9jyp04PVHyhDYCzCQPB/1zdXpo+vK68R4pqrnHKH7EquF9C
+BQvsRjDRcgvK6VZt9e/feL5hurKlrgRMvKisaRWXve/rtIy/NfjUw9EoDlw6n3AY
+MyB3xKKvAgMBAAGjggEXMIIBEzAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUUzLRs89/+uDxoF2FTpLSnkUdtE8wDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZI
+AYb4QgQBMBEGA1UdIAQKMAgwBgYEVR0gADB7BgNVHR8EdDByMDigNqA0hjJodHRw
+Oi8vY3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2
+oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJv
+b3QuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBjhpIQsRP6N76OKrYbikP1XK4OFN/3
+aUB/vxpxAAnYv9QkSr/gk/8B2AvGD+x+R5ywXfd8FJ38wDOShFvSg/RS4iJYdPxD
+Gz+no1jaA/288Drk7cwSu8m5rnsEoARyv+neLdKnUWYAc9K9fqqeU5Z9abIYPo6t
+VlB+99Ww/zliZYKMllfDj/dg9sKNNIf8T0Pl278cqvaGzebfET+NB/dtgxPAOIg5
+YKF+MOHjiD6ku2NvLOmKaCzulmmsBGHhT04OnXJM9nk4yMdIaW+UD3S0vMjPV025
+dXGWDYoGC+vd0PA8fcYumEZqOMcCtci4smV13tqQCLZ3uFMAJctHynNf
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert49[] = {
+ 0x30, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x46, 0xea, 0xf0, 0x96, 0x05, 0x4c, 0xc5, 0xe3, 0xfa,
+ 0x65, 0xea, 0x6e, 0x9f, 0x42, 0xc6, 0x64, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x30, 0x36, 0x30,
+ 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x93, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04,
+ 0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65,
+ 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52,
+ 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x55,
+ 0x54, 0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, 0x72,
+ 0x70, 0x20, 0x53, 0x47, 0x43, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xdf, 0xee, 0x58, 0x10, 0xa2, 0x2b, 0x6e, 0x55, 0xc4, 0x8e,
+ 0xbf, 0x2e, 0x46, 0x09, 0xe7, 0xe0, 0x08, 0x0f, 0x2e, 0x2b, 0x7a, 0x13,
+ 0x94, 0x1b, 0xbd, 0xf6, 0xb6, 0x80, 0x8e, 0x65, 0x05, 0x93, 0x00, 0x1e,
+ 0xbc, 0xaf, 0xe2, 0x0f, 0x8e, 0x19, 0x0d, 0x12, 0x47, 0xec, 0xac, 0xad,
+ 0xa3, 0xfa, 0x2e, 0x70, 0xf8, 0xde, 0x6e, 0xfb, 0x56, 0x42, 0x15, 0x9e,
+ 0x2e, 0x5c, 0xef, 0x23, 0xde, 0x21, 0xb9, 0x05, 0x76, 0x27, 0x19, 0x0f,
+ 0x4f, 0xd6, 0xc3, 0x9c, 0xb4, 0xbe, 0x94, 0x19, 0x63, 0xf2, 0xa6, 0x11,
+ 0x0a, 0xeb, 0x53, 0x48, 0x9c, 0xbe, 0xf2, 0x29, 0x3b, 0x16, 0xe8, 0x1a,
+ 0xa0, 0x4c, 0xa6, 0xc9, 0xf4, 0x18, 0x59, 0x68, 0xc0, 0x70, 0xf2, 0x53,
+ 0x00, 0xc0, 0x5e, 0x50, 0x82, 0xa5, 0x56, 0x6f, 0x36, 0xf9, 0x4a, 0xe0,
+ 0x44, 0x86, 0xa0, 0x4d, 0x4e, 0xd6, 0x47, 0x6e, 0x49, 0x4a, 0xcb, 0x67,
+ 0xd7, 0xa6, 0xc4, 0x05, 0xb9, 0x8e, 0x1e, 0xf4, 0xfc, 0xff, 0xcd, 0xe7,
+ 0x36, 0xe0, 0x9c, 0x05, 0x6c, 0xb2, 0x33, 0x22, 0x15, 0xd0, 0xb4, 0xe0,
+ 0xcc, 0x17, 0xc0, 0xb2, 0xc0, 0xf4, 0xfe, 0x32, 0x3f, 0x29, 0x2a, 0x95,
+ 0x7b, 0xd8, 0xf2, 0xa7, 0x4e, 0x0f, 0x54, 0x7c, 0xa1, 0x0d, 0x80, 0xb3,
+ 0x09, 0x03, 0xc1, 0xff, 0x5c, 0xdd, 0x5e, 0x9a, 0x3e, 0xbc, 0xae, 0xbc,
+ 0x47, 0x8a, 0x6a, 0xae, 0x71, 0xca, 0x1f, 0xb1, 0x2a, 0xb8, 0x5f, 0x42,
+ 0x05, 0x0b, 0xec, 0x46, 0x30, 0xd1, 0x72, 0x0b, 0xca, 0xe9, 0x56, 0x6d,
+ 0xf5, 0xef, 0xdf, 0x78, 0xbe, 0x61, 0xba, 0xb2, 0xa5, 0xae, 0x04, 0x4c,
+ 0xbc, 0xa8, 0xac, 0x69, 0x15, 0x97, 0xbd, 0xef, 0xeb, 0xb4, 0x8c, 0xbf,
+ 0x35, 0xf8, 0xd4, 0xc3, 0xd1, 0x28, 0x0e, 0x5c, 0x3a, 0x9f, 0x70, 0x18,
+ 0x33, 0x20, 0x77, 0xc4, 0xa2, 0xaf, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x17, 0x30, 0x82, 0x01, 0x13, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a,
+ 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0,
+ 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x53, 0x32, 0xd1, 0xb3, 0xcf, 0x7f, 0xfa, 0xe0, 0xf1,
+ 0xa0, 0x5d, 0x85, 0x4e, 0x92, 0xd2, 0x9e, 0x45, 0x1d, 0xb4, 0x4f, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20,
+ 0x00, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x74, 0x30, 0x72,
+ 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+ 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x36,
+ 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x63, 0x86, 0x92, 0x10, 0xb1, 0x13, 0xfa, 0x37, 0xbe, 0x8e,
+ 0x2a, 0xb6, 0x1b, 0x8a, 0x43, 0xf5, 0x5c, 0xae, 0x0e, 0x14, 0xdf, 0xf7,
+ 0x69, 0x40, 0x7f, 0xbf, 0x1a, 0x71, 0x00, 0x09, 0xd8, 0xbf, 0xd4, 0x24,
+ 0x4a, 0xbf, 0xe0, 0x93, 0xff, 0x01, 0xd8, 0x0b, 0xc6, 0x0f, 0xec, 0x7e,
+ 0x47, 0x9c, 0xb0, 0x5d, 0xf7, 0x7c, 0x14, 0x9d, 0xfc, 0xc0, 0x33, 0x92,
+ 0x84, 0x5b, 0xd2, 0x83, 0xf4, 0x52, 0xe2, 0x22, 0x58, 0x74, 0xfc, 0x43,
+ 0x1b, 0x3f, 0xa7, 0xa3, 0x58, 0xda, 0x03, 0xfd, 0xbc, 0xf0, 0x3a, 0xe4,
+ 0xed, 0xcc, 0x12, 0xbb, 0xc9, 0xb9, 0xae, 0x7b, 0x04, 0xa0, 0x04, 0x72,
+ 0xbf, 0xe9, 0xde, 0x2d, 0xd2, 0xa7, 0x51, 0x66, 0x00, 0x73, 0xd2, 0xbd,
+ 0x7e, 0xaa, 0x9e, 0x53, 0x96, 0x7d, 0x69, 0xb2, 0x18, 0x3e, 0x8e, 0xad,
+ 0x56, 0x50, 0x7e, 0xf7, 0xd5, 0xb0, 0xff, 0x39, 0x62, 0x65, 0x82, 0x8c,
+ 0x96, 0x57, 0xc3, 0x8f, 0xf7, 0x60, 0xf6, 0xc2, 0x8d, 0x34, 0x87, 0xfc,
+ 0x4f, 0x43, 0xe5, 0xdb, 0xbf, 0x1c, 0xaa, 0xf6, 0x86, 0xcd, 0xe6, 0xdf,
+ 0x11, 0x3f, 0x8d, 0x07, 0xf7, 0x6d, 0x83, 0x13, 0xc0, 0x38, 0x88, 0x39,
+ 0x60, 0xa1, 0x7e, 0x30, 0xe1, 0xe3, 0x88, 0x3e, 0xa4, 0xbb, 0x63, 0x6f,
+ 0x2c, 0xe9, 0x8a, 0x68, 0x2c, 0xee, 0x96, 0x69, 0xac, 0x04, 0x61, 0xe1,
+ 0x4f, 0x4e, 0x0e, 0x9d, 0x72, 0x4c, 0xf6, 0x79, 0x38, 0xc8, 0xc7, 0x48,
+ 0x69, 0x6f, 0x94, 0x0f, 0x74, 0xb4, 0xbc, 0xc8, 0xcf, 0x57, 0x4d, 0xb9,
+ 0x75, 0x71, 0x96, 0x0d, 0x8a, 0x06, 0x0b, 0xeb, 0xdd, 0xd0, 0xf0, 0x3c,
+ 0x7d, 0xc6, 0x2e, 0x98, 0x46, 0x6a, 0x38, 0xc7, 0x02, 0xb5, 0xc8, 0xb8,
+ 0xb2, 0x65, 0x75, 0xde, 0xda, 0x90, 0x08, 0xb6, 0x77, 0xb8, 0x53, 0x00,
+ 0x25, 0xcb, 0x47, 0xca, 0x73, 0x5f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 2e:79:83:2e:90:88:87:ea:8b:8e:f3:1a:6e:e6:7a:44
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC
+ Validity
+ Not Before: Dec 1 00:00:00 2006 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+ 98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+ e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+ b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+ 86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+ da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+ d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+ 7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+ 9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+ 30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+ 77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+ 02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+ c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+ 00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+ 0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+ 2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+ 47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+ 26:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:53:32:D1:B3:CF:7F:FA:E0:F1:A0:5D:85:4E:92:D2:9E:45:1D:B4:4F
+
+ X509v3 Subject Key Identifier:
+ 0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/UTN-DATACorpSGC.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/UTN-DATACorpSGC.crl
+
+ Signature Algorithm: sha1WithRSAEncryption
+ d8:5e:92:c4:ae:14:dc:43:ad:c2:a4:c3:67:45:07:1d:f9:37:
+ a2:19:c7:1c:37:35:91:13:1c:07:c4:7d:42:a6:0e:f0:86:5c:
+ 43:6b:0e:44:cf:be:24:61:3a:42:a9:ce:9d:4c:af:79:39:70:
+ dd:0e:04:20:4e:95:9c:3c:de:b7:60:ba:63:43:40:ed:6a:0f:
+ 81:49:46:bb:1e:93:c0:4b:f3:f8:e1:36:49:1b:6f:b6:0c:0d:
+ f2:90:57:8a:fc:6d:93:f2:28:c7:fa:86:0a:28:b3:17:0e:59:
+ 8a:2e:b6:bf:cd:e1:ac:4c:66:6c:f2:55:91:56:b7:32:bf:b1:
+ e4:7d:b5:e8:3a:b6:2f:db:b2:9c:da:50:93:8e:4e:c5:ac:9a:
+ 7e:5c:9e:12:3c:3b:4d:c6:50:70:b3:65:2b:8e:f7:6b:a1:bb:
+ 25:c0:00:bb:f5:ec:16:65:81:0e:fb:d4:a3:21:96:77:9a:a8:
+ 74:bc:53:aa:c2:39:50:ff:0b:02:09:61:cc:95:b7:d7:88:6a:
+ f6:5c:c5:68:d3:14:95:1a:47:5f:d9:fb:2d:e4:2f:8f:13:86:
+ ab:31:13:40:13:ac:6e:ed:b5:10:30:8b:1b:50:a9:ce:ee:8c:
+ ca:eb:7c:b5:b9:16:3d:d4:fa:6f:92:6d:1e:a2:bd:fb:02:4a:
+ c5:70:be:f1
+-----BEGIN CERTIFICATE-----
+MIIEqzCCA5OgAwIBAgIQLnmDLpCIh+qLjvMabuZ6RDANBgkqhkiG9w0BAQUFADCB
+kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
+IFNHQzAeFw0wNjEyMDEwMDAwMDBaFw0yMDA1MzAxMDQ4MzhaMIGBMQswCQYDVQQG
+EwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxm
+b3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RP
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZ
+rts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAh
+TaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23Iw
+ambV4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVD
+iOEjPqXSJDlqR6sA1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ
+0o7KBWFxB3NH5YoZEr0ETc5OnKVIrLsm9wIDAQABo4IBCTCCAQUwHwYDVR0jBBgw
+FoAUUzLRs89/+uDxoF2FTpLSnkUdtE8wHQYDVR0OBBYEFAtY5YvGTBU3pECpMKkh
+vkc2Wlb/MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MCAGA1UdJQQZ
+MBcGCisGAQQBgjcKAwMGCWCGSAGG+EIEATARBgNVHSAECjAIMAYGBFUdIAAwbQYD
+VR0fBGYwZDAxoC+gLYYraHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLURBVEFD
+b3JwU0dDLmNybDAvoC2gK4YpaHR0cDovL2NybC5jb21vZG8ubmV0L1VUTi1EQVRB
+Q29ycFNHQy5jcmwwDQYJKoZIhvcNAQEFBQADggEBANheksSuFNxDrcKkw2dFBx35
+N6IZxxw3NZETHAfEfUKmDvCGXENrDkTPviRhOkKpzp1Mr3k5cN0OBCBOlZw83rdg
+umNDQO1qD4FJRrsek8BL8/jhNkkbb7YMDfKQV4r8bZPyKMf6hgoosxcOWYoutr/N
+4axMZmzyVZFWtzK/seR9teg6ti/bspzaUJOOTsWsmn5cnhI8O03GUHCzZSuO92uh
+uyXAALv17BZlgQ771KMhlneaqHS8U6rCOVD/CwIJYcyVt9eIavZcxWjTFJUaR1/Z
++y3kL48ThqsxE0ATrG7ttRAwixtQqc7ujMrrfLW5Fj3U+m+SbR6ivfsCSsVwvvE=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert50[] = {
+ 0x30, 0x82, 0x04, 0xab, 0x30, 0x82, 0x03, 0x93, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x2e, 0x79, 0x83, 0x2e, 0x90, 0x88, 0x87, 0xea, 0x8b,
+ 0x8e, 0xf3, 0x1a, 0x6e, 0xe6, 0x7a, 0x44, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x93, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x55, 0x54,
+ 0x4e, 0x20, 0x2d, 0x20, 0x44, 0x41, 0x54, 0x41, 0x43, 0x6f, 0x72, 0x70,
+ 0x20, 0x53, 0x47, 0x43, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32,
+ 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32,
+ 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a,
+ 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d,
+ 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66,
+ 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20,
+ 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f,
+ 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b,
+ 0x8b, 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98,
+ 0xd3, 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88,
+ 0xce, 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99,
+ 0xae, 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42,
+ 0x91, 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb,
+ 0x59, 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda,
+ 0x37, 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21,
+ 0x4d, 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2,
+ 0x83, 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae,
+ 0x34, 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1,
+ 0xf9, 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30,
+ 0x6a, 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04,
+ 0x48, 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc,
+ 0x39, 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37,
+ 0x9c, 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43,
+ 0x88, 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00,
+ 0xd4, 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3,
+ 0x51, 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05,
+ 0x65, 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19,
+ 0xd2, 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19,
+ 0x12, 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26,
+ 0xf7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x09, 0x30, 0x82,
+ 0x01, 0x05, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+ 0x16, 0x80, 0x14, 0x53, 0x32, 0xd1, 0xb3, 0xcf, 0x7f, 0xfa, 0xe0, 0xf1,
+ 0xa0, 0x5d, 0x85, 0x4e, 0x92, 0xd2, 0x9e, 0x45, 0x1d, 0xb4, 0x4f, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58,
+ 0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21,
+ 0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+ 0x01, 0x01, 0xff, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x19,
+ 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0a,
+ 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04,
+ 0x01, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+ 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x6d, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x66, 0x30, 0x64, 0x30, 0x31, 0xa0, 0x2f, 0xa0,
+ 0x2d, 0x86, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x44, 0x41, 0x54, 0x41, 0x43,
+ 0x6f, 0x72, 0x70, 0x53, 0x47, 0x43, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x2f,
+ 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x44, 0x41, 0x54, 0x41,
+ 0x43, 0x6f, 0x72, 0x70, 0x53, 0x47, 0x43, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd8, 0x5e, 0x92, 0xc4, 0xae,
+ 0x14, 0xdc, 0x43, 0xad, 0xc2, 0xa4, 0xc3, 0x67, 0x45, 0x07, 0x1d, 0xf9,
+ 0x37, 0xa2, 0x19, 0xc7, 0x1c, 0x37, 0x35, 0x91, 0x13, 0x1c, 0x07, 0xc4,
+ 0x7d, 0x42, 0xa6, 0x0e, 0xf0, 0x86, 0x5c, 0x43, 0x6b, 0x0e, 0x44, 0xcf,
+ 0xbe, 0x24, 0x61, 0x3a, 0x42, 0xa9, 0xce, 0x9d, 0x4c, 0xaf, 0x79, 0x39,
+ 0x70, 0xdd, 0x0e, 0x04, 0x20, 0x4e, 0x95, 0x9c, 0x3c, 0xde, 0xb7, 0x60,
+ 0xba, 0x63, 0x43, 0x40, 0xed, 0x6a, 0x0f, 0x81, 0x49, 0x46, 0xbb, 0x1e,
+ 0x93, 0xc0, 0x4b, 0xf3, 0xf8, 0xe1, 0x36, 0x49, 0x1b, 0x6f, 0xb6, 0x0c,
+ 0x0d, 0xf2, 0x90, 0x57, 0x8a, 0xfc, 0x6d, 0x93, 0xf2, 0x28, 0xc7, 0xfa,
+ 0x86, 0x0a, 0x28, 0xb3, 0x17, 0x0e, 0x59, 0x8a, 0x2e, 0xb6, 0xbf, 0xcd,
+ 0xe1, 0xac, 0x4c, 0x66, 0x6c, 0xf2, 0x55, 0x91, 0x56, 0xb7, 0x32, 0xbf,
+ 0xb1, 0xe4, 0x7d, 0xb5, 0xe8, 0x3a, 0xb6, 0x2f, 0xdb, 0xb2, 0x9c, 0xda,
+ 0x50, 0x93, 0x8e, 0x4e, 0xc5, 0xac, 0x9a, 0x7e, 0x5c, 0x9e, 0x12, 0x3c,
+ 0x3b, 0x4d, 0xc6, 0x50, 0x70, 0xb3, 0x65, 0x2b, 0x8e, 0xf7, 0x6b, 0xa1,
+ 0xbb, 0x25, 0xc0, 0x00, 0xbb, 0xf5, 0xec, 0x16, 0x65, 0x81, 0x0e, 0xfb,
+ 0xd4, 0xa3, 0x21, 0x96, 0x77, 0x9a, 0xa8, 0x74, 0xbc, 0x53, 0xaa, 0xc2,
+ 0x39, 0x50, 0xff, 0x0b, 0x02, 0x09, 0x61, 0xcc, 0x95, 0xb7, 0xd7, 0x88,
+ 0x6a, 0xf6, 0x5c, 0xc5, 0x68, 0xd3, 0x14, 0x95, 0x1a, 0x47, 0x5f, 0xd9,
+ 0xfb, 0x2d, 0xe4, 0x2f, 0x8f, 0x13, 0x86, 0xab, 0x31, 0x13, 0x40, 0x13,
+ 0xac, 0x6e, 0xed, 0xb5, 0x10, 0x30, 0x8b, 0x1b, 0x50, 0xa9, 0xce, 0xee,
+ 0x8c, 0xca, 0xeb, 0x7c, 0xb5, 0xb9, 0x16, 0x3d, 0xd4, 0xfa, 0x6f, 0x92,
+ 0x6d, 0x1e, 0xa2, 0xbd, 0xfb, 0x02, 0x4a, 0xc5, 0x70, 0xbe, 0xf1,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 7f:71:c1:d3:a2:26:b0:d2:b1:13:f3:e6:81:67:64:3e
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Dec 7 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Internet2, OU=InCommon, CN=InCommon Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:97:7c:c7:c8:fe:b3:e9:20:6a:a3:a4:4f:8e:8e:
+ 34:56:06:b3:7a:6c:aa:10:9b:48:61:2b:36:90:69:
+ e3:34:0a:47:a7:bb:7b:de:aa:6a:fb:eb:82:95:8f:
+ ca:1d:7f:af:75:a6:a8:4c:da:20:67:61:1a:0d:86:
+ c1:ca:c1:87:af:ac:4e:e4:de:62:1b:2f:9d:b1:98:
+ af:c6:01:fb:17:70:db:ac:14:59:ec:6f:3f:33:7f:
+ a6:98:0b:e4:e2:38:af:f5:7f:85:6d:0e:74:04:9d:
+ f6:27:86:c7:9b:8f:e7:71:2a:08:f4:03:02:40:63:
+ 24:7d:40:57:8f:54:e0:54:7e:b6:13:48:61:f1:de:
+ ce:0e:bd:b6:fa:4d:98:b2:d9:0d:8d:79:a6:e0:aa:
+ cd:0c:91:9a:a5:df:ab:73:bb:ca:14:78:5c:47:29:
+ a1:ca:c5:ba:9f:c7:da:60:f7:ff:e7:7f:f2:d9:da:
+ a1:2d:0f:49:16:a7:d3:00:92:cf:8a:47:d9:4d:f8:
+ d5:95:66:d3:74:f9:80:63:00:4f:4c:84:16:1f:b3:
+ f5:24:1f:a1:4e:de:e8:95:d6:b2:0b:09:8b:2c:6b:
+ c7:5c:2f:8c:63:c9:99:cb:52:b1:62:7b:73:01:62:
+ 7f:63:6c:d8:68:a0:ee:6a:a8:8d:1f:29:f3:d0:18:
+ ac:ad
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 48:4F:5A:FA:2F:4A:9A:5E:E0:50:F3:6B:7B:55:A5:DE:F5:BE:34:5D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 93:66:21:80:74:45:85:4b:c2:ab:ce:32:b0:29:fe:dd:df:d6:
+ 24:5b:bf:03:6a:6f:50:3e:0e:1b:b3:0d:88:a3:5b:ee:c4:a4:
+ 12:3b:56:ef:06:7f:cf:7f:21:95:56:3b:41:31:fe:e1:aa:93:
+ d2:95:f3:95:0d:3c:47:ab:ca:5c:26:ad:3e:f1:f9:8c:34:6e:
+ 11:be:f4:67:e3:02:49:f9:a6:7c:7b:64:25:dd:17:46:f2:50:
+ e3:e3:0a:21:3a:49:24:cd:c6:84:65:68:67:68:b0:45:2d:47:
+ 99:cd:9c:ab:86:29:11:72:dc:d6:9c:36:43:74:f3:d4:97:9e:
+ 56:a0:fe:5f:40:58:d2:d5:d7:7e:7c:c5:8e:1a:b2:04:5c:92:
+ 66:0e:85:ad:2e:06:ce:c8:a3:d8:eb:14:27:91:de:cf:17:30:
+ 81:53:b6:66:12:ad:37:e4:f5:ef:96:5c:20:0e:36:e9:ac:62:
+ 7d:19:81:8a:f5:90:61:a6:49:ab:ce:3c:df:e6:ca:64:ee:82:
+ 65:39:45:95:16:ba:41:06:00:98:ba:0c:56:61:e4:c6:c6:86:
+ 01:cf:66:a9:22:29:02:d6:3d:cf:c4:2a:8d:99:de:fb:09:14:
+ 9e:0e:d1:d5:c6:d7:81:dd:ad:24:ab:ac:07:05:e2:1d:68:c3:
+ 70:66:5f:d3
+-----BEGIN CERTIFICATE-----
+MIIEwzCCA6ugAwIBAgIQf3HB06ImsNKxE/PmgWdkPjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMTIwNzAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+UTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUludGVybmV0MjERMA8GA1UECxMISW5D
+b21tb24xGzAZBgNVBAMTEkluQ29tbW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAJd8x8j+s+kgaqOkT46ONFYGs3psqhCbSGErNpBp
+4zQKR6e7e96qavvrgpWPyh1/r3WmqEzaIGdhGg2GwcrBh6+sTuTeYhsvnbGYr8YB
++xdw26wUWexvPzN/ppgL5OI4r/V/hW0OdASd9ieGx5uP53EqCPQDAkBjJH1AV49U
+4FR+thNIYfHezg69tvpNmLLZDY15puCqzQyRmqXfq3O7yhR4XEcpocrFup/H2mD3
+/+d/8tnaoS0PSRan0wCSz4pH2U341ZVm03T5gGMAT0yEFh+z9SQfoU7e6JXWsgsJ
+iyxrx1wvjGPJmctSsWJ7cwFif2Ns2Gig7mqojR8p89AYrK0CAwEAAaOCAXcwggFz
+MB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBRIT1r6
+L0qaXuBQ82t7VaXe9b40XTAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB
+/wIBADARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDov
+L2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGz
+BggrBgEFBQcBAQSBpjCBozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1
+c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1o
+dHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYI
+KwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEF
+BQADggEBAJNmIYB0RYVLwqvOMrAp/t3f1iRbvwNqb1A+DhuzDYijW+7EpBI7Vu8G
+f89/IZVWO0Ex/uGqk9KV85UNPEerylwmrT7x+Yw0bhG+9GfjAkn5pnx7ZCXdF0by
+UOPjCiE6SSTNxoRlaGdosEUtR5nNnKuGKRFy3NacNkN089SXnlag/l9AWNLV1358
+xY4asgRckmYOha0uBs7Io9jrFCeR3s8XMIFTtmYSrTfk9e+WXCAONumsYn0ZgYr1
+kGGmSavOPN/mymTugmU5RZUWukEGAJi6DFZh5MbGhgHPZqkiKQLWPc/EKo2Z3vsJ
+FJ4O0dXG14HdrSSrrAcF4h1ow3BmX9M=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert51[] = {
+ 0x30, 0x82, 0x04, 0xc3, 0x30, 0x82, 0x03, 0xab, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x7f, 0x71, 0xc1, 0xd3, 0xa2, 0x26, 0xb0, 0xd2, 0xb1,
+ 0x13, 0xf3, 0xe6, 0x81, 0x67, 0x64, 0x3e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x32, 0x30,
+ 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x51, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x09, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x32, 0x31, 0x11,
+ 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x08, 0x49, 0x6e, 0x43,
+ 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x12, 0x49, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+ 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82,
+ 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0x7c, 0xc7, 0xc8, 0xfe,
+ 0xb3, 0xe9, 0x20, 0x6a, 0xa3, 0xa4, 0x4f, 0x8e, 0x8e, 0x34, 0x56, 0x06,
+ 0xb3, 0x7a, 0x6c, 0xaa, 0x10, 0x9b, 0x48, 0x61, 0x2b, 0x36, 0x90, 0x69,
+ 0xe3, 0x34, 0x0a, 0x47, 0xa7, 0xbb, 0x7b, 0xde, 0xaa, 0x6a, 0xfb, 0xeb,
+ 0x82, 0x95, 0x8f, 0xca, 0x1d, 0x7f, 0xaf, 0x75, 0xa6, 0xa8, 0x4c, 0xda,
+ 0x20, 0x67, 0x61, 0x1a, 0x0d, 0x86, 0xc1, 0xca, 0xc1, 0x87, 0xaf, 0xac,
+ 0x4e, 0xe4, 0xde, 0x62, 0x1b, 0x2f, 0x9d, 0xb1, 0x98, 0xaf, 0xc6, 0x01,
+ 0xfb, 0x17, 0x70, 0xdb, 0xac, 0x14, 0x59, 0xec, 0x6f, 0x3f, 0x33, 0x7f,
+ 0xa6, 0x98, 0x0b, 0xe4, 0xe2, 0x38, 0xaf, 0xf5, 0x7f, 0x85, 0x6d, 0x0e,
+ 0x74, 0x04, 0x9d, 0xf6, 0x27, 0x86, 0xc7, 0x9b, 0x8f, 0xe7, 0x71, 0x2a,
+ 0x08, 0xf4, 0x03, 0x02, 0x40, 0x63, 0x24, 0x7d, 0x40, 0x57, 0x8f, 0x54,
+ 0xe0, 0x54, 0x7e, 0xb6, 0x13, 0x48, 0x61, 0xf1, 0xde, 0xce, 0x0e, 0xbd,
+ 0xb6, 0xfa, 0x4d, 0x98, 0xb2, 0xd9, 0x0d, 0x8d, 0x79, 0xa6, 0xe0, 0xaa,
+ 0xcd, 0x0c, 0x91, 0x9a, 0xa5, 0xdf, 0xab, 0x73, 0xbb, 0xca, 0x14, 0x78,
+ 0x5c, 0x47, 0x29, 0xa1, 0xca, 0xc5, 0xba, 0x9f, 0xc7, 0xda, 0x60, 0xf7,
+ 0xff, 0xe7, 0x7f, 0xf2, 0xd9, 0xda, 0xa1, 0x2d, 0x0f, 0x49, 0x16, 0xa7,
+ 0xd3, 0x00, 0x92, 0xcf, 0x8a, 0x47, 0xd9, 0x4d, 0xf8, 0xd5, 0x95, 0x66,
+ 0xd3, 0x74, 0xf9, 0x80, 0x63, 0x00, 0x4f, 0x4c, 0x84, 0x16, 0x1f, 0xb3,
+ 0xf5, 0x24, 0x1f, 0xa1, 0x4e, 0xde, 0xe8, 0x95, 0xd6, 0xb2, 0x0b, 0x09,
+ 0x8b, 0x2c, 0x6b, 0xc7, 0x5c, 0x2f, 0x8c, 0x63, 0xc9, 0x99, 0xcb, 0x52,
+ 0xb1, 0x62, 0x7b, 0x73, 0x01, 0x62, 0x7f, 0x63, 0x6c, 0xd8, 0x68, 0xa0,
+ 0xee, 0x6a, 0xa8, 0x8d, 0x1f, 0x29, 0xf3, 0xd0, 0x18, 0xac, 0xad, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26,
+ 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x48, 0x4f, 0x5a, 0xfa,
+ 0x2f, 0x4a, 0x9a, 0x5e, 0xe0, 0x50, 0xf3, 0x6b, 0x7b, 0x55, 0xa5, 0xde,
+ 0xf5, 0xbe, 0x34, 0x5d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x0a, 0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30,
+ 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39,
+ 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43,
+ 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81,
+ 0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43,
+ 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53,
+ 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x93, 0x66, 0x21, 0x80, 0x74,
+ 0x45, 0x85, 0x4b, 0xc2, 0xab, 0xce, 0x32, 0xb0, 0x29, 0xfe, 0xdd, 0xdf,
+ 0xd6, 0x24, 0x5b, 0xbf, 0x03, 0x6a, 0x6f, 0x50, 0x3e, 0x0e, 0x1b, 0xb3,
+ 0x0d, 0x88, 0xa3, 0x5b, 0xee, 0xc4, 0xa4, 0x12, 0x3b, 0x56, 0xef, 0x06,
+ 0x7f, 0xcf, 0x7f, 0x21, 0x95, 0x56, 0x3b, 0x41, 0x31, 0xfe, 0xe1, 0xaa,
+ 0x93, 0xd2, 0x95, 0xf3, 0x95, 0x0d, 0x3c, 0x47, 0xab, 0xca, 0x5c, 0x26,
+ 0xad, 0x3e, 0xf1, 0xf9, 0x8c, 0x34, 0x6e, 0x11, 0xbe, 0xf4, 0x67, 0xe3,
+ 0x02, 0x49, 0xf9, 0xa6, 0x7c, 0x7b, 0x64, 0x25, 0xdd, 0x17, 0x46, 0xf2,
+ 0x50, 0xe3, 0xe3, 0x0a, 0x21, 0x3a, 0x49, 0x24, 0xcd, 0xc6, 0x84, 0x65,
+ 0x68, 0x67, 0x68, 0xb0, 0x45, 0x2d, 0x47, 0x99, 0xcd, 0x9c, 0xab, 0x86,
+ 0x29, 0x11, 0x72, 0xdc, 0xd6, 0x9c, 0x36, 0x43, 0x74, 0xf3, 0xd4, 0x97,
+ 0x9e, 0x56, 0xa0, 0xfe, 0x5f, 0x40, 0x58, 0xd2, 0xd5, 0xd7, 0x7e, 0x7c,
+ 0xc5, 0x8e, 0x1a, 0xb2, 0x04, 0x5c, 0x92, 0x66, 0x0e, 0x85, 0xad, 0x2e,
+ 0x06, 0xce, 0xc8, 0xa3, 0xd8, 0xeb, 0x14, 0x27, 0x91, 0xde, 0xcf, 0x17,
+ 0x30, 0x81, 0x53, 0xb6, 0x66, 0x12, 0xad, 0x37, 0xe4, 0xf5, 0xef, 0x96,
+ 0x5c, 0x20, 0x0e, 0x36, 0xe9, 0xac, 0x62, 0x7d, 0x19, 0x81, 0x8a, 0xf5,
+ 0x90, 0x61, 0xa6, 0x49, 0xab, 0xce, 0x3c, 0xdf, 0xe6, 0xca, 0x64, 0xee,
+ 0x82, 0x65, 0x39, 0x45, 0x95, 0x16, 0xba, 0x41, 0x06, 0x00, 0x98, 0xba,
+ 0x0c, 0x56, 0x61, 0xe4, 0xc6, 0xc6, 0x86, 0x01, 0xcf, 0x66, 0xa9, 0x22,
+ 0x29, 0x02, 0xd6, 0x3d, 0xcf, 0xc4, 0x2a, 0x8d, 0x99, 0xde, 0xfb, 0x09,
+ 0x14, 0x9e, 0x0e, 0xd1, 0xd5, 0xc6, 0xd7, 0x81, 0xdd, 0xad, 0x24, 0xab,
+ 0xac, 0x07, 0x05, 0xe2, 0x1d, 0x68, 0xc3, 0x70, 0x66, 0x5f, 0xd3,
+};
diff --git a/chromium/net/quic/crypto/common_cert_set_51_100.inc b/chromium/net/quic/crypto/common_cert_set_51_100.inc
new file mode 100644
index 00000000000..3c79622493c
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set_51_100.inc
@@ -0,0 +1,11308 @@
+/* Copyright (c) 2013 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.
+ */
+
+/* This file contains common certificates. It's designed to be #included in
+ * another file, in a namespace. */
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 35:97:31:87:f3:87:3a:07:32:7e:ce:58:0c:9b:7e:da
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2021 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+ 4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+ 08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+ 2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+ 8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+ a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+ 54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+ d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+ 7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+ bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+ f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+ ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+ f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+ 21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+ 63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+ ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+ 9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+ 25:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 Subject Key Identifier:
+ 7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 0f:25:ae:48:ed:1b:33:85:4c:0c:b5:c2:d7:fe:4d:d6:83:28:
+ 4c:41:65:60:00:0b:77:48:71:82:fe:7f:db:5a:0e:20:cc:d2:
+ ea:47:bc:64:42:61:44:34:74:30:81:81:26:8a:4a:f7:44:5d:
+ 7e:34:80:a8:b8:83:e2:09:d7:6d:23:dd:89:ed:28:08:bd:63:
+ 5a:11:57:08:c4:9e:da:e2:68:28:af:dd:50:3c:ec:82:21:d8:
+ 00:c2:55:44:50:70:41:ad:83:17:79:ba:08:f3:2b:de:ed:34:
+ 1d:44:9e:d2:04:93:f4:cb:05:17:2d:09:2d:2d:63:ef:f6:26:
+ 0b:7b
+-----BEGIN CERTIFICATE-----
+MIIExjCCBC+gAwIBAgIQNZcxh/OHOgcyfs5YDJt+2jANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggGRMIIBjTAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwNAYD
+VR0lBC0wKwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBBggrBgEFBQcDAQYIKwYBBQUH
+AwIwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUr
+DgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNp
+Z24uY29tL3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhho
+dHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wDQYJKoZIhvcNAQEFBQADgYEADyWuSO0b
+M4VMDLXC1/5N1oMoTEFlYAALd0hxgv5/21oOIMzS6ke8ZEJhRDR0MIGBJopK90Rd
+fjSAqLiD4gnXbSPdie0oCL1jWhFXCMSe2uJoKK/dUDzsgiHYAMJVRFBwQa2DF3m6
+CPMr3u00HUSe0gST9MsFFy0JLS1j7/YmC3s=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert52[] = {
+ 0x30, 0x82, 0x04, 0xc6, 0x30, 0x82, 0x04, 0x2f, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x35, 0x97, 0x31, 0x87, 0xf3, 0x87, 0x3a, 0x07, 0x32,
+ 0x7e, 0xce, 0x58, 0x0c, 0x9b, 0x7e, 0xda, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+ 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+ 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+ 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+ 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+ 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+ 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+ 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+ 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+ 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+ 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+ 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+ 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+ 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+ 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+ 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+ 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+ 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+ 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+ 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+ 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+ 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+ 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+ 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+ 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+ 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+ 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+ 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+ 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x91, 0x30, 0x82, 0x01, 0x8d, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+ 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+ 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+ 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+ 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+ 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x34, 0x06, 0x03,
+ 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01,
+ 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x02, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+ 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+ 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+ 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+ 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+ 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76,
+ 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x0f, 0x25, 0xae, 0x48, 0xed, 0x1b,
+ 0x33, 0x85, 0x4c, 0x0c, 0xb5, 0xc2, 0xd7, 0xfe, 0x4d, 0xd6, 0x83, 0x28,
+ 0x4c, 0x41, 0x65, 0x60, 0x00, 0x0b, 0x77, 0x48, 0x71, 0x82, 0xfe, 0x7f,
+ 0xdb, 0x5a, 0x0e, 0x20, 0xcc, 0xd2, 0xea, 0x47, 0xbc, 0x64, 0x42, 0x61,
+ 0x44, 0x34, 0x74, 0x30, 0x81, 0x81, 0x26, 0x8a, 0x4a, 0xf7, 0x44, 0x5d,
+ 0x7e, 0x34, 0x80, 0xa8, 0xb8, 0x83, 0xe2, 0x09, 0xd7, 0x6d, 0x23, 0xdd,
+ 0x89, 0xed, 0x28, 0x08, 0xbd, 0x63, 0x5a, 0x11, 0x57, 0x08, 0xc4, 0x9e,
+ 0xda, 0xe2, 0x68, 0x28, 0xaf, 0xdd, 0x50, 0x3c, 0xec, 0x82, 0x21, 0xd8,
+ 0x00, 0xc2, 0x55, 0x44, 0x50, 0x70, 0x41, 0xad, 0x83, 0x17, 0x79, 0xba,
+ 0x08, 0xf3, 0x2b, 0xde, 0xed, 0x34, 0x1d, 0x44, 0x9e, 0xd2, 0x04, 0x93,
+ 0xf4, 0xcb, 0x05, 0x17, 0x2d, 0x09, 0x2d, 0x2d, 0x63, 0xef, 0xf6, 0x26,
+ 0x0b, 0x7b,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 18:a2:23:6c:d7:27:c7:52:8d:f6:7b:4b:85:6e:ff:ed
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Jul 29 00:00:00 2010 GMT
+ Not After : Jul 28 23:59:59 2020 GMT
+ Subject: C=US, O=Thawte, Inc., CN=Thawte SGC CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cd:d9:e9:5c:55:4c:c6:fd:26:0d:3c:9d:56:3a:
+ 7a:46:02:05:eb:f0:c2:ad:be:12:2f:59:ff:67:35:
+ 29:d9:69:d9:4d:37:3e:6d:87:49:bc:bb:d5:16:62:
+ 44:29:71:96:5c:a6:27:e8:c5:9c:fc:19:0b:29:af:
+ 2e:5c:da:0b:8f:bf:ed:53:15:a7:82:35:30:5e:08:
+ 36:32:24:36:36:1a:e4:72:2b:c4:68:48:a4:78:1f:
+ 33:34:20:fe:97:6e:9c:ac:3a:fd:e6:fd:83:5f:75:
+ 83:71:5d:90:df:bd:48:57:6d:10:26:af:6f:41:d8:
+ cc:78:9e:3d:9c:85:28:89:43:31:ab:a7:6e:a1:bc:
+ 02:e6:be:8f:c3:63:a4:64:68:3b:1b:c3:da:33:c8:
+ 7b:5a:1f:d6:08:72:b2:36:34:18:d3:20:4f:98:e8:
+ 02:93:df:50:b2:67:c8:3d:96:64:55:c7:69:25:0a:
+ ba:21:36:70:d3:59:a8:82:d2:54:6d:4e:06:5a:e1:
+ d8:07:8d:35:b8:d0:16:a1:74:fe:4a:1b:70:a8:a9:
+ 43:9a:80:27:a0:40:b7:6f:f9:e3:a8:a8:1e:8a:93:
+ 3c:96:36:a7:88:e9:36:9d:c1:e3:ef:b6:7e:02:37:
+ 62:09:d7:8b:c6:70:d9:32:50:9a:b1:a7:1e:54:21:
+ 1e:49
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://ocsp.thawte.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3-g5.crl
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-17
+ X509v3 Subject Key Identifier:
+ 24:C0:C0:A4:49:3C:52:0B:12:D8:92:0C:51:D1:87:A7:4D:54:75:2C
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 38:da:76:35:18:49:32:34:f0:b4:e8:28:08:45:eb:8f:62:3e:
+ 99:21:72:77:95:e0:36:82:b3:ff:ab:7f:12:6c:e1:1c:10:c9:
+ 54:98:e5:0c:31:74:cc:80:7a:a0:26:a7:45:c8:11:4c:76:e4:
+ d0:a9:b1:c8:92:a3:80:79:26:0d:8d:cf:c8:47:63:2d:13:3c:
+ c2:96:34:d7:00:42:3a:4a:8b:9e:17:a9:dc:c9:50:c5:40:e1:
+ 29:45:61:22:f5:b3:b0:88:78:8d:ae:a1:8d:50:6f:44:82:74:
+ 52:87:15:0c:1c:4e:f2:16:37:da:c1:05:69:d9:01:54:ee:cd:
+ 71:49:f6:6c:56:7c:75:73:e2:8a:9f:a6:69:d7:60:9f:04:c3:
+ a3:9f:81:60:b3:c5:bd:a5:55:d0:69:db:45:98:64:20:f2:c0:
+ 8b:8c:4e:e9:57:52:36:ab:bb:53:67:30:89:63:13:28:f3:44:
+ d1:43:76:b4:81:68:2a:07:21:3f:8f:f4:67:d3:08:a0:79:de:
+ cc:b9:53:2d:1f:44:d3:54:9c:a3:07:4d:8a:08:34:4d:dd:17:
+ 7a:fe:ad:6b:4b:99:b6:00:c9:62:76:7e:98:9a:a2:49:1c:86:
+ be:b2:55:95:2c:2d:27:21:bc:19:b0:f1:3e:ad:b6:d1:1a:de:
+ ed:b6:ee:35
+-----BEGIN CERTIFICATE-----
+MIIEyzCCA7OgAwIBAgIQGKIjbNcnx1KN9ntLhW7/7TANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwNzI5MDAwMDAwWhcNMjAwNzI4MjM1OTU5WjBBMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMVGhhd3RlLCBJbmMuMRswGQYDVQQDExJUaGF3
+dGUgU0dDIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDN
+2elcVUzG/SYNPJ1WOnpGAgXr8MKtvhIvWf9nNSnZadlNNz5th0m8u9UWYkQpcZZc
+pifoxZz8GQspry5c2guPv+1TFaeCNTBeCDYyJDY2GuRyK8RoSKR4HzM0IP6Xbpys
+Ov3m/YNfdYNxXZDfvUhXbRAmr29B2Mx4nj2chSiJQzGrp26hvALmvo/DY6RkaDsb
+w9ozyHtaH9YIcrI2NBjTIE+Y6AKT31CyZ8g9lmRVx2klCrohNnDTWaiC0lRtTgZa
+4dgHjTW40BahdP5KG3CoqUOagCegQLdv+eOoqB6KkzyWNqeI6TadwePvtn4CN2IJ
+14vGcNkyUJqxpx5UIR5JAgMBAAGjggEzMIIBLzAyBggrBgEFBQcBAQQmMCQwIgYI
+KwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgwBgEB
+/wIBADA0BgNVHR8ELTArMCmgJ6AlhiNodHRwOi8vY3JsLnZlcmlzaWduLmNvbS9w
+Y2EzLWc1LmNybDA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIGCWCGSAGG
++EIEAQYKYIZIAYb4RQEIATAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0RBCIwIKQeMBwx
+GjAYBgNVBAMTEVZlcmlTaWduTVBLSS0yLTE3MB0GA1UdDgQWBBQkwMCkSTxSCxLY
+kgxR0YenTVR1LDAfBgNVHSMEGDAWgBR/02Wnwt3su/AwCfNDOfoCrzMxMzANBgkq
+hkiG9w0BAQUFAAOCAQEAONp2NRhJMjTwtOgoCEXrj2I+mSFyd5XgNoKz/6t/Emzh
+HBDJVJjlDDF0zIB6oCanRcgRTHbk0KmxyJKjgHkmDY3PyEdjLRM8wpY01wBCOkqL
+nhep3MlQxUDhKUVhIvWzsIh4ja6hjVBvRIJ0UocVDBxO8hY32sEFadkBVO7NcUn2
+bFZ8dXPiip+maddgnwTDo5+BYLPFvaVV0GnbRZhkIPLAi4xO6VdSNqu7U2cwiWMT
+KPNE0UN2tIFoKgchP4/0Z9MIoHnezLlTLR9E01ScowdNigg0Td0Xev6ta0uZtgDJ
+YnZ+mJqiSRyGvrJVlSwtJyG8GbDxPq220Rre7bbuNQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert53[] = {
+ 0x30, 0x82, 0x04, 0xcb, 0x30, 0x82, 0x03, 0xb3, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x18, 0xa2, 0x23, 0x6c, 0xd7, 0x27, 0xc7, 0x52, 0x8d,
+ 0xf6, 0x7b, 0x4b, 0x85, 0x6e, 0xff, 0xed, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x37, 0x32, 0x39, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x37, 0x32, 0x38,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x41, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x15,
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1b, 0x30,
+ 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x54, 0x68, 0x61, 0x77,
+ 0x74, 0x65, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+ 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xcd,
+ 0xd9, 0xe9, 0x5c, 0x55, 0x4c, 0xc6, 0xfd, 0x26, 0x0d, 0x3c, 0x9d, 0x56,
+ 0x3a, 0x7a, 0x46, 0x02, 0x05, 0xeb, 0xf0, 0xc2, 0xad, 0xbe, 0x12, 0x2f,
+ 0x59, 0xff, 0x67, 0x35, 0x29, 0xd9, 0x69, 0xd9, 0x4d, 0x37, 0x3e, 0x6d,
+ 0x87, 0x49, 0xbc, 0xbb, 0xd5, 0x16, 0x62, 0x44, 0x29, 0x71, 0x96, 0x5c,
+ 0xa6, 0x27, 0xe8, 0xc5, 0x9c, 0xfc, 0x19, 0x0b, 0x29, 0xaf, 0x2e, 0x5c,
+ 0xda, 0x0b, 0x8f, 0xbf, 0xed, 0x53, 0x15, 0xa7, 0x82, 0x35, 0x30, 0x5e,
+ 0x08, 0x36, 0x32, 0x24, 0x36, 0x36, 0x1a, 0xe4, 0x72, 0x2b, 0xc4, 0x68,
+ 0x48, 0xa4, 0x78, 0x1f, 0x33, 0x34, 0x20, 0xfe, 0x97, 0x6e, 0x9c, 0xac,
+ 0x3a, 0xfd, 0xe6, 0xfd, 0x83, 0x5f, 0x75, 0x83, 0x71, 0x5d, 0x90, 0xdf,
+ 0xbd, 0x48, 0x57, 0x6d, 0x10, 0x26, 0xaf, 0x6f, 0x41, 0xd8, 0xcc, 0x78,
+ 0x9e, 0x3d, 0x9c, 0x85, 0x28, 0x89, 0x43, 0x31, 0xab, 0xa7, 0x6e, 0xa1,
+ 0xbc, 0x02, 0xe6, 0xbe, 0x8f, 0xc3, 0x63, 0xa4, 0x64, 0x68, 0x3b, 0x1b,
+ 0xc3, 0xda, 0x33, 0xc8, 0x7b, 0x5a, 0x1f, 0xd6, 0x08, 0x72, 0xb2, 0x36,
+ 0x34, 0x18, 0xd3, 0x20, 0x4f, 0x98, 0xe8, 0x02, 0x93, 0xdf, 0x50, 0xb2,
+ 0x67, 0xc8, 0x3d, 0x96, 0x64, 0x55, 0xc7, 0x69, 0x25, 0x0a, 0xba, 0x21,
+ 0x36, 0x70, 0xd3, 0x59, 0xa8, 0x82, 0xd2, 0x54, 0x6d, 0x4e, 0x06, 0x5a,
+ 0xe1, 0xd8, 0x07, 0x8d, 0x35, 0xb8, 0xd0, 0x16, 0xa1, 0x74, 0xfe, 0x4a,
+ 0x1b, 0x70, 0xa8, 0xa9, 0x43, 0x9a, 0x80, 0x27, 0xa0, 0x40, 0xb7, 0x6f,
+ 0xf9, 0xe3, 0xa8, 0xa8, 0x1e, 0x8a, 0x93, 0x3c, 0x96, 0x36, 0xa7, 0x88,
+ 0xe9, 0x36, 0x9d, 0xc1, 0xe3, 0xef, 0xb6, 0x7e, 0x02, 0x37, 0x62, 0x09,
+ 0xd7, 0x8b, 0xc6, 0x70, 0xd9, 0x32, 0x50, 0x9a, 0xb1, 0xa7, 0x1e, 0x54,
+ 0x21, 0x1e, 0x49, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x33,
+ 0x30, 0x82, 0x01, 0x2f, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68,
+ 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65,
+ 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+ 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x34,
+ 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+ 0x45, 0x01, 0x08, 0x01, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x29, 0x06, 0x03,
+ 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31,
+ 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32,
+ 0x2d, 0x31, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0x24, 0xc0, 0xc0, 0xa4, 0x49, 0x3c, 0x52, 0x0b, 0x12, 0xd8,
+ 0x92, 0x0c, 0x51, 0xd1, 0x87, 0xa7, 0x4d, 0x54, 0x75, 0x2c, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f,
+ 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43,
+ 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x38, 0xda, 0x76, 0x35, 0x18, 0x49, 0x32, 0x34, 0xf0,
+ 0xb4, 0xe8, 0x28, 0x08, 0x45, 0xeb, 0x8f, 0x62, 0x3e, 0x99, 0x21, 0x72,
+ 0x77, 0x95, 0xe0, 0x36, 0x82, 0xb3, 0xff, 0xab, 0x7f, 0x12, 0x6c, 0xe1,
+ 0x1c, 0x10, 0xc9, 0x54, 0x98, 0xe5, 0x0c, 0x31, 0x74, 0xcc, 0x80, 0x7a,
+ 0xa0, 0x26, 0xa7, 0x45, 0xc8, 0x11, 0x4c, 0x76, 0xe4, 0xd0, 0xa9, 0xb1,
+ 0xc8, 0x92, 0xa3, 0x80, 0x79, 0x26, 0x0d, 0x8d, 0xcf, 0xc8, 0x47, 0x63,
+ 0x2d, 0x13, 0x3c, 0xc2, 0x96, 0x34, 0xd7, 0x00, 0x42, 0x3a, 0x4a, 0x8b,
+ 0x9e, 0x17, 0xa9, 0xdc, 0xc9, 0x50, 0xc5, 0x40, 0xe1, 0x29, 0x45, 0x61,
+ 0x22, 0xf5, 0xb3, 0xb0, 0x88, 0x78, 0x8d, 0xae, 0xa1, 0x8d, 0x50, 0x6f,
+ 0x44, 0x82, 0x74, 0x52, 0x87, 0x15, 0x0c, 0x1c, 0x4e, 0xf2, 0x16, 0x37,
+ 0xda, 0xc1, 0x05, 0x69, 0xd9, 0x01, 0x54, 0xee, 0xcd, 0x71, 0x49, 0xf6,
+ 0x6c, 0x56, 0x7c, 0x75, 0x73, 0xe2, 0x8a, 0x9f, 0xa6, 0x69, 0xd7, 0x60,
+ 0x9f, 0x04, 0xc3, 0xa3, 0x9f, 0x81, 0x60, 0xb3, 0xc5, 0xbd, 0xa5, 0x55,
+ 0xd0, 0x69, 0xdb, 0x45, 0x98, 0x64, 0x20, 0xf2, 0xc0, 0x8b, 0x8c, 0x4e,
+ 0xe9, 0x57, 0x52, 0x36, 0xab, 0xbb, 0x53, 0x67, 0x30, 0x89, 0x63, 0x13,
+ 0x28, 0xf3, 0x44, 0xd1, 0x43, 0x76, 0xb4, 0x81, 0x68, 0x2a, 0x07, 0x21,
+ 0x3f, 0x8f, 0xf4, 0x67, 0xd3, 0x08, 0xa0, 0x79, 0xde, 0xcc, 0xb9, 0x53,
+ 0x2d, 0x1f, 0x44, 0xd3, 0x54, 0x9c, 0xa3, 0x07, 0x4d, 0x8a, 0x08, 0x34,
+ 0x4d, 0xdd, 0x17, 0x7a, 0xfe, 0xad, 0x6b, 0x4b, 0x99, 0xb6, 0x00, 0xc9,
+ 0x62, 0x76, 0x7e, 0x98, 0x9a, 0xa2, 0x49, 0x1c, 0x86, 0xbe, 0xb2, 0x55,
+ 0x95, 0x2c, 0x2d, 0x27, 0x21, 0xbc, 0x19, 0xb0, 0xf1, 0x3e, 0xad, 0xb6,
+ 0xd1, 0x1a, 0xde, 0xed, 0xb6, 0xee, 0x35,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 25:0c:e8:e0:30:61:2e:9f:2b:89:f7:05:4d:7c:f8:fd
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2021 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+ 4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+ 08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+ 2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+ 8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+ a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+ 54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+ d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+ 7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+ bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+ f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+ ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+ f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+ 21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+ 63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+ ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+ 9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+ 25:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 Subject Key Identifier:
+ 7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ Signature Algorithm: sha1WithRSAEncryption
+ 13:02:dd:f8:e8:86:00:f2:5a:f8:f8:20:0c:59:88:62:07:ce:
+ ce:f7:4e:f9:bb:59:a1:98:e5:e1:38:dd:4e:bc:66:18:d3:ad:
+ eb:18:f2:0d:c9:6d:3e:4a:94:20:c3:3c:ba:bd:65:54:c6:af:
+ 44:b3:10:ad:2c:6b:3e:ab:d7:07:b6:b8:81:63:c5:f9:5e:2e:
+ e5:2a:67:ce:cd:33:0c:2a:d7:89:56:03:23:1f:b3:be:e8:3a:
+ 08:59:b4:ec:45:35:f7:8a:5b:ff:66:cf:50:af:c6:6d:57:8d:
+ 19:78:b7:b9:a2:d1:57:ea:1f:9a:4b:af:ba:c9:8e:12:7e:c6:
+ bd:ff
+-----BEGIN CERTIFICATE-----
+MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9
+BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy
+aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI
+KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU
+j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t
+L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v
+b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC
+BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA
+A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K
+lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ
+tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert54[] = {
+ 0x30, 0x82, 0x04, 0xd0, 0x30, 0x82, 0x04, 0x39, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x25, 0x0c, 0xe8, 0xe0, 0x30, 0x61, 0x2e, 0x9f, 0x2b,
+ 0x89, 0xf7, 0x05, 0x4d, 0x7c, 0xf8, 0xfd, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+ 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+ 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+ 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+ 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+ 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+ 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+ 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+ 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+ 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+ 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+ 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+ 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+ 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+ 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+ 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+ 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+ 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+ 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+ 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+ 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+ 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+ 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+ 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+ 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+ 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+ 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+ 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+ 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x9b, 0x30, 0x82, 0x01, 0x97, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+ 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+ 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+ 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x3d,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+ 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+ 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x6d, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f,
+ 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09,
+ 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30,
+ 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14,
+ 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80,
+ 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30,
+ 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x25,
+ 0x04, 0x37, 0x30, 0x35, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60,
+ 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x81, 0x81, 0x00, 0x13, 0x02, 0xdd, 0xf8, 0xe8, 0x86, 0x00, 0xf2,
+ 0x5a, 0xf8, 0xf8, 0x20, 0x0c, 0x59, 0x88, 0x62, 0x07, 0xce, 0xce, 0xf7,
+ 0x4e, 0xf9, 0xbb, 0x59, 0xa1, 0x98, 0xe5, 0xe1, 0x38, 0xdd, 0x4e, 0xbc,
+ 0x66, 0x18, 0xd3, 0xad, 0xeb, 0x18, 0xf2, 0x0d, 0xc9, 0x6d, 0x3e, 0x4a,
+ 0x94, 0x20, 0xc3, 0x3c, 0xba, 0xbd, 0x65, 0x54, 0xc6, 0xaf, 0x44, 0xb3,
+ 0x10, 0xad, 0x2c, 0x6b, 0x3e, 0xab, 0xd7, 0x07, 0xb6, 0xb8, 0x81, 0x63,
+ 0xc5, 0xf9, 0x5e, 0x2e, 0xe5, 0x2a, 0x67, 0xce, 0xcd, 0x33, 0x0c, 0x2a,
+ 0xd7, 0x89, 0x56, 0x03, 0x23, 0x1f, 0xb3, 0xbe, 0xe8, 0x3a, 0x08, 0x59,
+ 0xb4, 0xec, 0x45, 0x35, 0xf7, 0x8a, 0x5b, 0xff, 0x66, 0xcf, 0x50, 0xaf,
+ 0xc6, 0x6d, 0x57, 0x8d, 0x19, 0x78, 0xb7, 0xb9, 0xa2, 0xd1, 0x57, 0xea,
+ 0x1f, 0x9a, 0x4b, 0xaf, 0xba, 0xc9, 0x8e, 0x12, 0x7e, 0xc6, 0xbd, 0xff,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 48:fc:4b:0a:37:06:ff:46:fe:d3:de:5d:4c:1e:ca:62
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Nov 26 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions DV Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a7:57:66:35:dc:90:4e:78:74:65:a8:da:ac:e8:
+ e9:62:65:46:ca:64:f0:d0:c3:a7:42:5b:8c:e1:8e:
+ 00:05:2b:53:e4:1b:84:22:b4:df:57:b0:40:8f:17:
+ 92:7e:31:97:1e:f5:ad:f0:80:99:db:97:30:95:35:
+ 0d:64:41:df:c4:b3:82:79:cd:bc:96:e2:fd:00:29:
+ c5:3e:be:7c:08:6c:be:fe:90:b1:15:39:21:86:34:
+ 40:bd:9c:9d:fa:6a:e5:2a:68:45:0e:68:e0:e8:b0:
+ 08:65:84:36:31:9c:46:e1:4e:cb:3f:58:83:f3:6c:
+ 8e:34:19:82:53:26:2c:8d:ab:92:22:5f:05:a1:3d:
+ 9b:ae:67:b4:56:c0:f9:97:78:c0:b5:98:15:0c:ad:
+ 03:ad:ff:78:8f:2f:26:7c:3a:dc:94:00:87:c3:7e:
+ c2:b6:a8:8c:0b:1d:1d:0f:8c:b5:d0:fb:93:3a:38:
+ f6:08:fe:3b:8d:66:6b:45:c6:5f:b2:7b:f0:14:f9:
+ 81:75:de:0b:4b:83:cb:ee:77:bb:9c:7e:9b:9d:27:
+ d8:90:06:9d:cf:4b:3c:2b:fa:bf:01:0a:c5:6d:1c:
+ 5a:60:68:92:f9:0e:43:fb:f2:88:78:96:e5:53:4b:
+ 51:f6:b1:e7:6d:f7:c6:ff:4f:d7:03:7b:73:f2:60:
+ 0a:21
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 58:D8:25:92:A4:55:5A:6E:D9:A3:D1:A3:7C:0C:AA:04:21:71:2E:60
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.782.1.2.1.9.1
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 27:94:9f:7e:7f:fc:73:48:f2:38:1f:b0:05:bf:71:dc:3b:a9:
+ 7e:c3:10:86:25:2d:14:ee:44:9d:4a:8d:f2:b3:3a:c5:66:fa:
+ 02:bf:d5:f0:00:16:77:c9:74:d7:88:c0:b1:18:7a:f3:4e:13:
+ 31:70:6f:46:70:41:e1:1a:42:3e:aa:5f:46:18:2d:85:0c:3b:
+ bb:fe:cf:02:d6:cf:ae:db:1a:93:52:74:6c:9e:fa:b2:ee:af:
+ 2f:7d:07:42:17:7d:31:e5:6a:36:28:2b:fd:d4:72:f1:fe:b9:
+ c5:f7:f0:72:61:e0:9d:bc:ca:eb:45:0b:b8:68:09:01:1b:4d:
+ 73:7f:df:e6:93:ba:1d:fc:6b:28:b3:64:30:bb:d0:3a:aa:35:
+ 6b:0b:83:61:68:d7:32:5a:49:de:1a:d1:fc:6d:8b:a0:dc:fa:
+ 7a:a4:92:7f:74:e2:0d:92:a0:9e:b8:46:1c:62:63:b0:b8:08:
+ c4:fd:b0:b4:9f:24:09:b3:2d:9c:75:14:77:4a:6e:c4:63:c1:
+ 4d:13:86:ce:98:72:1d:3d:b9:c6:4e:73:30:e4:c6:73:a2:d1:
+ f7:90:e4:90:cc:e1:3a:37:d6:53:02:5f:45:2d:2f:a6:4f:49:
+ 41:ea:df:8f:2f:97:1c:76:db:78:40:63:cb:e4:d5:d7:53:38:
+ 0e:11:10:38
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQSPxLCjcG/0b+095dTB7KYjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMTEyNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+WTELMAkGA1UEBhMCVVMxITAfBgNVBAoTGE5ldHdvcmsgU29sdXRpb25zIEwuTC5D
+LjEnMCUGA1UEAxMeTmV0d29yayBTb2x1dGlvbnMgRFYgU2VydmVyIENBMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp1dmNdyQTnh0ZajarOjpYmVGymTw
+0MOnQluM4Y4ABStT5BuEIrTfV7BAjxeSfjGXHvWt8ICZ25cwlTUNZEHfxLOCec28
+luL9ACnFPr58CGy+/pCxFTkhhjRAvZyd+mrlKmhFDmjg6LAIZYQ2MZxG4U7LP1iD
+82yONBmCUyYsjauSIl8FoT2brme0VsD5l3jAtZgVDK0Drf94jy8mfDrclACHw37C
+tqiMCx0dD4y10PuTOjj2CP47jWZrRcZfsnvwFPmBdd4LS4PL7ne7nH6bnSfYkAad
+z0s8K/q/AQrFbRxaYGiS+Q5D+/KIeJblU0tR9rHnbffG/0/XA3tz8mAKIQIDAQAB
+o4IBfzCCAXswHwYDVR0jBBgwFoAUrb2YejS0Jvf6xCZU7wO94CTLVBowHQYDVR0O
+BBYEFFjYJZKkVVpu2aPRo3wMqgQhcS5gMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMB
+Af8ECDAGAQH/AgEAMBkGA1UdIAQSMBAwDgYMKwYBBAGGDgECAQkBMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0
+dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3
+YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAnlJ9+f/xzSPI4H7AFv3HcO6l+wxCGJS0U
+7kSdSo3yszrFZvoCv9XwABZ3yXTXiMCxGHrzThMxcG9GcEHhGkI+ql9GGC2FDDu7
+/s8C1s+u2xqTUnRsnvqy7q8vfQdCF30x5Wo2KCv91HLx/rnF9/ByYeCdvMrrRQu4
+aAkBG01zf9/mk7od/Gsos2Qwu9A6qjVrC4NhaNcyWkneGtH8bYug3Pp6pJJ/dOIN
+kqCeuEYcYmOwuAjE/bC0nyQJsy2cdRR3Sm7EY8FNE4bOmHIdPbnGTnMw5MZzotH3
+kOSQzOE6N9ZTAl9FLS+mT0lB6t+PL5ccdtt4QGPL5NXXUzgOERA4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert55[] = {
+ 0x30, 0x82, 0x04, 0xd3, 0x30, 0x82, 0x03, 0xbb, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x48, 0xfc, 0x4b, 0x0a, 0x37, 0x06, 0xff, 0x46, 0xfe,
+ 0xd3, 0xde, 0x5d, 0x4c, 0x1e, 0xca, 0x62, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x31, 0x32,
+ 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x59, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c,
+ 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43,
+ 0x2e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x44, 0x56, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0xa7, 0x57, 0x66, 0x35, 0xdc, 0x90, 0x4e, 0x78, 0x74,
+ 0x65, 0xa8, 0xda, 0xac, 0xe8, 0xe9, 0x62, 0x65, 0x46, 0xca, 0x64, 0xf0,
+ 0xd0, 0xc3, 0xa7, 0x42, 0x5b, 0x8c, 0xe1, 0x8e, 0x00, 0x05, 0x2b, 0x53,
+ 0xe4, 0x1b, 0x84, 0x22, 0xb4, 0xdf, 0x57, 0xb0, 0x40, 0x8f, 0x17, 0x92,
+ 0x7e, 0x31, 0x97, 0x1e, 0xf5, 0xad, 0xf0, 0x80, 0x99, 0xdb, 0x97, 0x30,
+ 0x95, 0x35, 0x0d, 0x64, 0x41, 0xdf, 0xc4, 0xb3, 0x82, 0x79, 0xcd, 0xbc,
+ 0x96, 0xe2, 0xfd, 0x00, 0x29, 0xc5, 0x3e, 0xbe, 0x7c, 0x08, 0x6c, 0xbe,
+ 0xfe, 0x90, 0xb1, 0x15, 0x39, 0x21, 0x86, 0x34, 0x40, 0xbd, 0x9c, 0x9d,
+ 0xfa, 0x6a, 0xe5, 0x2a, 0x68, 0x45, 0x0e, 0x68, 0xe0, 0xe8, 0xb0, 0x08,
+ 0x65, 0x84, 0x36, 0x31, 0x9c, 0x46, 0xe1, 0x4e, 0xcb, 0x3f, 0x58, 0x83,
+ 0xf3, 0x6c, 0x8e, 0x34, 0x19, 0x82, 0x53, 0x26, 0x2c, 0x8d, 0xab, 0x92,
+ 0x22, 0x5f, 0x05, 0xa1, 0x3d, 0x9b, 0xae, 0x67, 0xb4, 0x56, 0xc0, 0xf9,
+ 0x97, 0x78, 0xc0, 0xb5, 0x98, 0x15, 0x0c, 0xad, 0x03, 0xad, 0xff, 0x78,
+ 0x8f, 0x2f, 0x26, 0x7c, 0x3a, 0xdc, 0x94, 0x00, 0x87, 0xc3, 0x7e, 0xc2,
+ 0xb6, 0xa8, 0x8c, 0x0b, 0x1d, 0x1d, 0x0f, 0x8c, 0xb5, 0xd0, 0xfb, 0x93,
+ 0x3a, 0x38, 0xf6, 0x08, 0xfe, 0x3b, 0x8d, 0x66, 0x6b, 0x45, 0xc6, 0x5f,
+ 0xb2, 0x7b, 0xf0, 0x14, 0xf9, 0x81, 0x75, 0xde, 0x0b, 0x4b, 0x83, 0xcb,
+ 0xee, 0x77, 0xbb, 0x9c, 0x7e, 0x9b, 0x9d, 0x27, 0xd8, 0x90, 0x06, 0x9d,
+ 0xcf, 0x4b, 0x3c, 0x2b, 0xfa, 0xbf, 0x01, 0x0a, 0xc5, 0x6d, 0x1c, 0x5a,
+ 0x60, 0x68, 0x92, 0xf9, 0x0e, 0x43, 0xfb, 0xf2, 0x88, 0x78, 0x96, 0xe5,
+ 0x53, 0x4b, 0x51, 0xf6, 0xb1, 0xe7, 0x6d, 0xf7, 0xc6, 0xff, 0x4f, 0xd7,
+ 0x03, 0x7b, 0x73, 0xf2, 0x60, 0x0a, 0x21, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x01, 0x7f, 0x30, 0x82, 0x01, 0x7b, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98,
+ 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd,
+ 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0x58, 0xd8, 0x25, 0x92, 0xa4, 0x55, 0x5a, 0x6e,
+ 0xd9, 0xa3, 0xd1, 0xa3, 0x7c, 0x0c, 0xaa, 0x04, 0x21, 0x71, 0x2e, 0x60,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+ 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x19, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x12, 0x30, 0x10, 0x30,
+ 0x0e, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x86, 0x0e, 0x01, 0x02,
+ 0x01, 0x09, 0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d,
+ 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+ 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+ 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37,
+ 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+ 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x27,
+ 0x94, 0x9f, 0x7e, 0x7f, 0xfc, 0x73, 0x48, 0xf2, 0x38, 0x1f, 0xb0, 0x05,
+ 0xbf, 0x71, 0xdc, 0x3b, 0xa9, 0x7e, 0xc3, 0x10, 0x86, 0x25, 0x2d, 0x14,
+ 0xee, 0x44, 0x9d, 0x4a, 0x8d, 0xf2, 0xb3, 0x3a, 0xc5, 0x66, 0xfa, 0x02,
+ 0xbf, 0xd5, 0xf0, 0x00, 0x16, 0x77, 0xc9, 0x74, 0xd7, 0x88, 0xc0, 0xb1,
+ 0x18, 0x7a, 0xf3, 0x4e, 0x13, 0x31, 0x70, 0x6f, 0x46, 0x70, 0x41, 0xe1,
+ 0x1a, 0x42, 0x3e, 0xaa, 0x5f, 0x46, 0x18, 0x2d, 0x85, 0x0c, 0x3b, 0xbb,
+ 0xfe, 0xcf, 0x02, 0xd6, 0xcf, 0xae, 0xdb, 0x1a, 0x93, 0x52, 0x74, 0x6c,
+ 0x9e, 0xfa, 0xb2, 0xee, 0xaf, 0x2f, 0x7d, 0x07, 0x42, 0x17, 0x7d, 0x31,
+ 0xe5, 0x6a, 0x36, 0x28, 0x2b, 0xfd, 0xd4, 0x72, 0xf1, 0xfe, 0xb9, 0xc5,
+ 0xf7, 0xf0, 0x72, 0x61, 0xe0, 0x9d, 0xbc, 0xca, 0xeb, 0x45, 0x0b, 0xb8,
+ 0x68, 0x09, 0x01, 0x1b, 0x4d, 0x73, 0x7f, 0xdf, 0xe6, 0x93, 0xba, 0x1d,
+ 0xfc, 0x6b, 0x28, 0xb3, 0x64, 0x30, 0xbb, 0xd0, 0x3a, 0xaa, 0x35, 0x6b,
+ 0x0b, 0x83, 0x61, 0x68, 0xd7, 0x32, 0x5a, 0x49, 0xde, 0x1a, 0xd1, 0xfc,
+ 0x6d, 0x8b, 0xa0, 0xdc, 0xfa, 0x7a, 0xa4, 0x92, 0x7f, 0x74, 0xe2, 0x0d,
+ 0x92, 0xa0, 0x9e, 0xb8, 0x46, 0x1c, 0x62, 0x63, 0xb0, 0xb8, 0x08, 0xc4,
+ 0xfd, 0xb0, 0xb4, 0x9f, 0x24, 0x09, 0xb3, 0x2d, 0x9c, 0x75, 0x14, 0x77,
+ 0x4a, 0x6e, 0xc4, 0x63, 0xc1, 0x4d, 0x13, 0x86, 0xce, 0x98, 0x72, 0x1d,
+ 0x3d, 0xb9, 0xc6, 0x4e, 0x73, 0x30, 0xe4, 0xc6, 0x73, 0xa2, 0xd1, 0xf7,
+ 0x90, 0xe4, 0x90, 0xcc, 0xe1, 0x3a, 0x37, 0xd6, 0x53, 0x02, 0x5f, 0x45,
+ 0x2d, 0x2f, 0xa6, 0x4f, 0x49, 0x41, 0xea, 0xdf, 0x8f, 0x2f, 0x97, 0x1c,
+ 0x76, 0xdb, 0x78, 0x40, 0x63, 0xcb, 0xe4, 0xd5, 0xd7, 0x53, 0x38, 0x0e,
+ 0x11, 0x10, 0x38,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 769 (0x301)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
+ Validity
+ Not Before: Nov 16 01:54:37 2006 GMT
+ Not After : Nov 16 01:54:37 2026 GMT
+ Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c4:2d:d5:15:8c:9c:26:4c:ec:32:35:eb:5f:b8:
+ 59:01:5a:a6:61:81:59:3b:70:63:ab:e3:dc:3d:c7:
+ 2a:b8:c9:33:d3:79:e4:3a:ed:3c:30:23:84:8e:b3:
+ 30:14:b6:b2:87:c3:3d:95:54:04:9e:df:99:dd:0b:
+ 25:1e:21:de:65:29:7e:35:a8:a9:54:eb:f6:f7:32:
+ 39:d4:26:55:95:ad:ef:fb:fe:58:86:d7:9e:f4:00:
+ 8d:8c:2a:0c:bd:42:04:ce:a7:3f:04:f6:ee:80:f2:
+ aa:ef:52:a1:69:66:da:be:1a:ad:5d:da:2c:66:ea:
+ 1a:6b:bb:e5:1a:51:4a:00:2f:48:c7:98:75:d8:b9:
+ 29:c8:ee:f8:66:6d:0a:9c:b3:f3:fc:78:7c:a2:f8:
+ a3:f2:b5:c3:f3:b9:7a:91:c1:a7:e6:25:2e:9c:a8:
+ ed:12:65:6e:6a:f6:12:44:53:70:30:95:c3:9c:2b:
+ 58:2b:3d:08:74:4a:f2:be:51:b0:bf:87:d0:4c:27:
+ 58:6b:b5:35:c5:9d:af:17:31:f8:0b:8f:ee:ad:81:
+ 36:05:89:08:98:cf:3a:af:25:87:c0:49:ea:a7:fd:
+ 67:f7:45:8e:97:cc:14:39:e2:36:85:b5:7e:1a:37:
+ fd:16:f6:71:11:9a:74:30:16:fe:13:94:a3:3f:84:
+ 0d:4f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ FD:AC:61:32:93:6C:45:D6:E2:EE:85:5F:9A:BA:E7:76:99:68:CC:E7
+ X509v3 Authority Key Identifier:
+ keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.godaddy.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://certificates.godaddy.com/repository/gdroot.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://certificates.godaddy.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ d2:86:c0:ec:bd:f9:a1:b6:67:ee:66:0b:a2:06:3a:04:50:8e:
+ 15:72:ac:4a:74:95:53:cb:37:cb:44:49:ef:07:90:6b:33:d9:
+ 96:f0:94:56:a5:13:30:05:3c:85:32:21:7b:c9:c7:0a:a8:24:
+ a4:90:de:46:d3:25:23:14:03:67:c2:10:d6:6f:0f:5d:7b:7a:
+ cc:9f:c5:58:2a:c1:c4:9e:21:a8:5a:f3:ac:a4:46:f3:9e:e4:
+ 63:cb:2f:90:a4:29:29:01:d9:72:2c:29:df:37:01:27:bc:4f:
+ ee:68:d3:21:8f:c0:b3:e4:f5:09:ed:d2:10:aa:53:b4:be:f0:
+ cc:59:0b:d6:3b:96:1c:95:24:49:df:ce:ec:fd:a7:48:91:14:
+ 45:0e:3a:36:6f:da:45:b3:45:a2:41:c9:d4:d7:44:4e:3e:b9:
+ 74:76:d5:a2:13:55:2c:c6:87:a3:b5:99:ac:06:84:87:7f:75:
+ 06:fc:bf:14:4c:0e:cc:6e:c4:df:3d:b7:12:71:f4:e8:f1:51:
+ 40:22:28:49:e0:1d:4b:87:a8:34:cc:06:a2:dd:12:5a:d1:86:
+ 36:64:03:35:6f:6f:77:6e:eb:f2:85:50:98:5e:ab:03:53:ad:
+ 91:23:63:1f:16:9c:cd:b9:b2:05:63:3a:e1:f4:68:1b:17:05:
+ 35:95:53:ee
+-----BEGIN CERTIFICATE-----
+MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
+ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
+MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
+QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
+b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
+b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
+KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
+VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
+SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
+cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
+6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
+MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
+kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
+BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
+BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
+c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
+AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
+BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
+OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
+A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
+0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
+RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
+qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
+U+4=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert56[] = {
+ 0x30, 0x82, 0x04, 0xde, 0x30, 0x82, 0x03, 0xc6, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x02, 0x03, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x63, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x54, 0x68,
+ 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x47,
+ 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31,
+ 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x28, 0x47, 0x6f, 0x20,
+ 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x36, 0x30,
+ 0x31, 0x35, 0x34, 0x33, 0x37, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x31, 0x31,
+ 0x31, 0x36, 0x30, 0x31, 0x35, 0x34, 0x33, 0x37, 0x5a, 0x30, 0x81, 0xca,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07,
+ 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73,
+ 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27,
+ 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x05,
+ 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37, 0x30, 0x82,
+ 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
+ 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0x2d, 0xd5, 0x15, 0x8c,
+ 0x9c, 0x26, 0x4c, 0xec, 0x32, 0x35, 0xeb, 0x5f, 0xb8, 0x59, 0x01, 0x5a,
+ 0xa6, 0x61, 0x81, 0x59, 0x3b, 0x70, 0x63, 0xab, 0xe3, 0xdc, 0x3d, 0xc7,
+ 0x2a, 0xb8, 0xc9, 0x33, 0xd3, 0x79, 0xe4, 0x3a, 0xed, 0x3c, 0x30, 0x23,
+ 0x84, 0x8e, 0xb3, 0x30, 0x14, 0xb6, 0xb2, 0x87, 0xc3, 0x3d, 0x95, 0x54,
+ 0x04, 0x9e, 0xdf, 0x99, 0xdd, 0x0b, 0x25, 0x1e, 0x21, 0xde, 0x65, 0x29,
+ 0x7e, 0x35, 0xa8, 0xa9, 0x54, 0xeb, 0xf6, 0xf7, 0x32, 0x39, 0xd4, 0x26,
+ 0x55, 0x95, 0xad, 0xef, 0xfb, 0xfe, 0x58, 0x86, 0xd7, 0x9e, 0xf4, 0x00,
+ 0x8d, 0x8c, 0x2a, 0x0c, 0xbd, 0x42, 0x04, 0xce, 0xa7, 0x3f, 0x04, 0xf6,
+ 0xee, 0x80, 0xf2, 0xaa, 0xef, 0x52, 0xa1, 0x69, 0x66, 0xda, 0xbe, 0x1a,
+ 0xad, 0x5d, 0xda, 0x2c, 0x66, 0xea, 0x1a, 0x6b, 0xbb, 0xe5, 0x1a, 0x51,
+ 0x4a, 0x00, 0x2f, 0x48, 0xc7, 0x98, 0x75, 0xd8, 0xb9, 0x29, 0xc8, 0xee,
+ 0xf8, 0x66, 0x6d, 0x0a, 0x9c, 0xb3, 0xf3, 0xfc, 0x78, 0x7c, 0xa2, 0xf8,
+ 0xa3, 0xf2, 0xb5, 0xc3, 0xf3, 0xb9, 0x7a, 0x91, 0xc1, 0xa7, 0xe6, 0x25,
+ 0x2e, 0x9c, 0xa8, 0xed, 0x12, 0x65, 0x6e, 0x6a, 0xf6, 0x12, 0x44, 0x53,
+ 0x70, 0x30, 0x95, 0xc3, 0x9c, 0x2b, 0x58, 0x2b, 0x3d, 0x08, 0x74, 0x4a,
+ 0xf2, 0xbe, 0x51, 0xb0, 0xbf, 0x87, 0xd0, 0x4c, 0x27, 0x58, 0x6b, 0xb5,
+ 0x35, 0xc5, 0x9d, 0xaf, 0x17, 0x31, 0xf8, 0x0b, 0x8f, 0xee, 0xad, 0x81,
+ 0x36, 0x05, 0x89, 0x08, 0x98, 0xcf, 0x3a, 0xaf, 0x25, 0x87, 0xc0, 0x49,
+ 0xea, 0xa7, 0xfd, 0x67, 0xf7, 0x45, 0x8e, 0x97, 0xcc, 0x14, 0x39, 0xe2,
+ 0x36, 0x85, 0xb5, 0x7e, 0x1a, 0x37, 0xfd, 0x16, 0xf6, 0x71, 0x11, 0x9a,
+ 0x74, 0x30, 0x16, 0xfe, 0x13, 0x94, 0xa3, 0x3f, 0x84, 0x0d, 0x4f, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x32, 0x30, 0x82, 0x01, 0x2e,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xfd,
+ 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, 0x85, 0x5f, 0x9a,
+ 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xd2, 0xc4, 0xb0, 0xd2,
+ 0x91, 0xd4, 0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd,
+ 0xa8, 0x6a, 0xd4, 0xe3, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64,
+ 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x46, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x3f, 0x30, 0x3d, 0x30, 0x3b, 0xa0, 0x39, 0xa0, 0x37, 0x86, 0x35,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61,
+ 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f,
+ 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x72, 0x6f, 0x6f,
+ 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+ 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e,
+ 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xd2, 0x86,
+ 0xc0, 0xec, 0xbd, 0xf9, 0xa1, 0xb6, 0x67, 0xee, 0x66, 0x0b, 0xa2, 0x06,
+ 0x3a, 0x04, 0x50, 0x8e, 0x15, 0x72, 0xac, 0x4a, 0x74, 0x95, 0x53, 0xcb,
+ 0x37, 0xcb, 0x44, 0x49, 0xef, 0x07, 0x90, 0x6b, 0x33, 0xd9, 0x96, 0xf0,
+ 0x94, 0x56, 0xa5, 0x13, 0x30, 0x05, 0x3c, 0x85, 0x32, 0x21, 0x7b, 0xc9,
+ 0xc7, 0x0a, 0xa8, 0x24, 0xa4, 0x90, 0xde, 0x46, 0xd3, 0x25, 0x23, 0x14,
+ 0x03, 0x67, 0xc2, 0x10, 0xd6, 0x6f, 0x0f, 0x5d, 0x7b, 0x7a, 0xcc, 0x9f,
+ 0xc5, 0x58, 0x2a, 0xc1, 0xc4, 0x9e, 0x21, 0xa8, 0x5a, 0xf3, 0xac, 0xa4,
+ 0x46, 0xf3, 0x9e, 0xe4, 0x63, 0xcb, 0x2f, 0x90, 0xa4, 0x29, 0x29, 0x01,
+ 0xd9, 0x72, 0x2c, 0x29, 0xdf, 0x37, 0x01, 0x27, 0xbc, 0x4f, 0xee, 0x68,
+ 0xd3, 0x21, 0x8f, 0xc0, 0xb3, 0xe4, 0xf5, 0x09, 0xed, 0xd2, 0x10, 0xaa,
+ 0x53, 0xb4, 0xbe, 0xf0, 0xcc, 0x59, 0x0b, 0xd6, 0x3b, 0x96, 0x1c, 0x95,
+ 0x24, 0x49, 0xdf, 0xce, 0xec, 0xfd, 0xa7, 0x48, 0x91, 0x14, 0x45, 0x0e,
+ 0x3a, 0x36, 0x6f, 0xda, 0x45, 0xb3, 0x45, 0xa2, 0x41, 0xc9, 0xd4, 0xd7,
+ 0x44, 0x4e, 0x3e, 0xb9, 0x74, 0x76, 0xd5, 0xa2, 0x13, 0x55, 0x2c, 0xc6,
+ 0x87, 0xa3, 0xb5, 0x99, 0xac, 0x06, 0x84, 0x87, 0x7f, 0x75, 0x06, 0xfc,
+ 0xbf, 0x14, 0x4c, 0x0e, 0xcc, 0x6e, 0xc4, 0xdf, 0x3d, 0xb7, 0x12, 0x71,
+ 0xf4, 0xe8, 0xf1, 0x51, 0x40, 0x22, 0x28, 0x49, 0xe0, 0x1d, 0x4b, 0x87,
+ 0xa8, 0x34, 0xcc, 0x06, 0xa2, 0xdd, 0x12, 0x5a, 0xd1, 0x86, 0x36, 0x64,
+ 0x03, 0x35, 0x6f, 0x6f, 0x77, 0x6e, 0xeb, 0xf2, 0x85, 0x50, 0x98, 0x5e,
+ 0xab, 0x03, 0x53, 0xad, 0x91, 0x23, 0x63, 0x1f, 0x16, 0x9c, 0xcd, 0xb9,
+ 0xb2, 0x05, 0x63, 0x3a, 0xe1, 0xf4, 0x68, 0x1b, 0x17, 0x05, 0x35, 0x95,
+ 0x53, 0xee,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 6e:ba:f0:8f:79:83:fa:9d:e1:b2:6f:96:fc:6e:98:bf
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Aug 23 00:00:00 2011 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d4:2b:2e:1c:d2:a3:f8:7f:55:14:40:de:f7:44:
+ dd:84:55:f7:85:7b:55:66:69:a7:e5:59:eb:65:83:
+ f4:f3:76:b1:66:c3:4f:4e:98:93:09:b7:40:b3:d1:
+ 17:a0:12:09:a8:80:e1:29:63:97:02:8c:31:9d:0a:
+ 02:e0:59:5b:bb:ed:30:b5:ef:7e:5d:af:08:4e:8d:
+ 8b:c2:39:56:16:98:73:94:78:0a:c9:a6:4f:28:b7:
+ a8:34:37:db:25:21:b1:3c:99:f6:e0:12:3e:73:ea:
+ 64:32:9f:42:06:3c:19:d8:0a:04:7a:4c:57:49:2b:
+ d2:77:7a:d0:00:bc:5e:fa:8e:ee:cc:c2:e4:13:6e:
+ 25:5f:dc:3c:a4:88:a3:dc:49:c7:bc:c7:0f:dd:19:
+ c0:b1:72:ed:78:ef:38:83:0a:45:17:1b:c9:7d:9d:
+ ed:df:ab:2c:2c:a3:75:ae:5b:82:1d:88:83:8d:ce:
+ 08:65:0c:66:26:57:05:a1:0c:df:e6:07:84:0b:84:
+ a3:c8:ab:d5:95:47:bf:dc:dc:fe:1d:fc:02:93:44:
+ 01:ca:e6:b5:b7:6b:16:30:01:5d:e9:89:09:95:9e:
+ f8:5e:29:5c:dd:c7:55:8c:f2:8e:20:4e:40:7a:e4:
+ f5:45:03:b4:98:2b:c4:80:7e:53:87:6f:c2:d2:57:
+ b0:e9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 1B:6B:BD:1F:8A:49:18:94:54:37:55:B4:20:17:ED:37:B9:77:18:7D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 43:25:39:23:07:04:ac:99:5d:59:67:3d:e6:2f:61:7d:5a:56:
+ 7b:fc:06:8d:b3:4b:9d:fa:d5:05:4c:0d:66:b5:bd:3c:c7:a2:
+ 2a:6b:b5:cf:e6:ba:83:3e:60:90:36:0c:d5:c2:ed:8a:95:d9:
+ 92:42:23:1c:03:76:3e:c2:48:f1:75:72:9d:b3:8c:cf:b3:58:
+ 34:56:49:1d:a1:2e:2b:3d:b2:e8:5a:10:46:de:64:b5:4d:ae:
+ 4b:6e:fc:01:b7:21:10:d5:95:b7:eb:2c:be:14:06:cc:41:2e:
+ e4:6c:e2:46:90:ff:c6:28:7e:73:fe:e5:17:ba:82:c3:10:05:
+ 81:66:c2:8b:28:38:a0:44:3e:e9:e4:ce:33:b0:7c:f8:e1:53:
+ 9d:b8:b4:cb:da:c9:2e:d9:93:70:8e:7c:0b:e3:73:3e:99:99:
+ 8f:eb:e1:11:44:35:d8:60:81:62:45:d4:de:45:5b:90:2e:49:
+ 1b:1b:db:a4:0f:80:62:21:73:69:f1:e3:de:6d:d8:48:7c:56:
+ 12:26:22:11:47:01:c6:5e:19:c2:b4:95:97:ee:61:00:55:f1:
+ 04:38:fc:84:e6:78:b4:0d:43:be:43:33:dd:68:d3:22:5b:00:
+ fb:14:82:e8:4b:62:79:30:cf:d3:95:9f:b3:b9:84:01:d4:dd:
+ cf:23:12:f8
+-----BEGIN CERTIFICATE-----
+MIIE4jCCA8qgAwIBAgIQbrrwj3mD+p3hsm+W/G6YvzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTExMDgyMzAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+cDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxFjAUBgNV
+BAMTDUNPTU9ETyBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDUKy4c0qP4f1UUQN73RN2EVfeFe1VmaaflWetlg/TzdrFmw09OmJMJt0Cz0Reg
+EgmogOEpY5cCjDGdCgLgWVu77TC1735drwhOjYvCOVYWmHOUeArJpk8ot6g0N9sl
+IbE8mfbgEj5z6mQyn0IGPBnYCgR6TFdJK9J3etAAvF76ju7MwuQTbiVf3DykiKPc
+Sce8xw/dGcCxcu147ziDCkUXG8l9ne3fqywso3WuW4IdiIONzghlDGYmVwWhDN/m
+B4QLhKPIq9WVR7/c3P4d/AKTRAHK5rW3axYwAV3piQmVnvheKVzdx1WM8o4gTkB6
+5PVFA7SYK8SAflOHb8LSV7DpAgMBAAGjggF3MIIBczAfBgNVHSMEGDAWgBStvZh6
+NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUG2u9H4pJGJRUN1W0IBftN7l3GH0w
+DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEQYDVR0gBAowCDAG
+BgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNv
+bS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYwgaMw
+PwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4
+dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2VydHJ1
+c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8v
+b2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBDJTkjBwSsmV1Z
+Zz3mL2F9WlZ7/AaNs0ud+tUFTA1mtb08x6Iqa7XP5rqDPmCQNgzVwu2KldmSQiMc
+A3Y+wkjxdXKds4zPs1g0VkkdoS4rPbLoWhBG3mS1Ta5LbvwBtyEQ1ZW36yy+FAbM
+QS7kbOJGkP/GKH5z/uUXuoLDEAWBZsKLKDigRD7p5M4zsHz44VOduLTL2sku2ZNw
+jnwL43M+mZmP6+ERRDXYYIFiRdTeRVuQLkkbG9ukD4BiIXNp8ePebdhIfFYSJiIR
+RwHGXhnCtJWX7mEAVfEEOPyE5ni0DUO+QzPdaNMiWwD7FILoS2J5MM/TlZ+zuYQB
+1N3PIxL4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert57[] = {
+ 0x30, 0x82, 0x04, 0xe2, 0x30, 0x82, 0x03, 0xca, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x6e, 0xba, 0xf0, 0x8f, 0x79, 0x83, 0xfa, 0x9d, 0xe1,
+ 0xb2, 0x6f, 0x96, 0xfc, 0x6e, 0x98, 0xbf, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x38, 0x32,
+ 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x70, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0d, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x53,
+ 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xd4, 0x2b, 0x2e, 0x1c, 0xd2, 0xa3, 0xf8, 0x7f, 0x55, 0x14,
+ 0x40, 0xde, 0xf7, 0x44, 0xdd, 0x84, 0x55, 0xf7, 0x85, 0x7b, 0x55, 0x66,
+ 0x69, 0xa7, 0xe5, 0x59, 0xeb, 0x65, 0x83, 0xf4, 0xf3, 0x76, 0xb1, 0x66,
+ 0xc3, 0x4f, 0x4e, 0x98, 0x93, 0x09, 0xb7, 0x40, 0xb3, 0xd1, 0x17, 0xa0,
+ 0x12, 0x09, 0xa8, 0x80, 0xe1, 0x29, 0x63, 0x97, 0x02, 0x8c, 0x31, 0x9d,
+ 0x0a, 0x02, 0xe0, 0x59, 0x5b, 0xbb, 0xed, 0x30, 0xb5, 0xef, 0x7e, 0x5d,
+ 0xaf, 0x08, 0x4e, 0x8d, 0x8b, 0xc2, 0x39, 0x56, 0x16, 0x98, 0x73, 0x94,
+ 0x78, 0x0a, 0xc9, 0xa6, 0x4f, 0x28, 0xb7, 0xa8, 0x34, 0x37, 0xdb, 0x25,
+ 0x21, 0xb1, 0x3c, 0x99, 0xf6, 0xe0, 0x12, 0x3e, 0x73, 0xea, 0x64, 0x32,
+ 0x9f, 0x42, 0x06, 0x3c, 0x19, 0xd8, 0x0a, 0x04, 0x7a, 0x4c, 0x57, 0x49,
+ 0x2b, 0xd2, 0x77, 0x7a, 0xd0, 0x00, 0xbc, 0x5e, 0xfa, 0x8e, 0xee, 0xcc,
+ 0xc2, 0xe4, 0x13, 0x6e, 0x25, 0x5f, 0xdc, 0x3c, 0xa4, 0x88, 0xa3, 0xdc,
+ 0x49, 0xc7, 0xbc, 0xc7, 0x0f, 0xdd, 0x19, 0xc0, 0xb1, 0x72, 0xed, 0x78,
+ 0xef, 0x38, 0x83, 0x0a, 0x45, 0x17, 0x1b, 0xc9, 0x7d, 0x9d, 0xed, 0xdf,
+ 0xab, 0x2c, 0x2c, 0xa3, 0x75, 0xae, 0x5b, 0x82, 0x1d, 0x88, 0x83, 0x8d,
+ 0xce, 0x08, 0x65, 0x0c, 0x66, 0x26, 0x57, 0x05, 0xa1, 0x0c, 0xdf, 0xe6,
+ 0x07, 0x84, 0x0b, 0x84, 0xa3, 0xc8, 0xab, 0xd5, 0x95, 0x47, 0xbf, 0xdc,
+ 0xdc, 0xfe, 0x1d, 0xfc, 0x02, 0x93, 0x44, 0x01, 0xca, 0xe6, 0xb5, 0xb7,
+ 0x6b, 0x16, 0x30, 0x01, 0x5d, 0xe9, 0x89, 0x09, 0x95, 0x9e, 0xf8, 0x5e,
+ 0x29, 0x5c, 0xdd, 0xc7, 0x55, 0x8c, 0xf2, 0x8e, 0x20, 0x4e, 0x40, 0x7a,
+ 0xe4, 0xf5, 0x45, 0x03, 0xb4, 0x98, 0x2b, 0xc4, 0x80, 0x7e, 0x53, 0x87,
+ 0x6f, 0xc2, 0xd2, 0x57, 0xb0, 0xe9, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a,
+ 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0,
+ 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x1b, 0x6b, 0xbd, 0x1f, 0x8a, 0x49, 0x18, 0x94, 0x54,
+ 0x37, 0x55, 0xb4, 0x20, 0x17, 0xed, 0x37, 0xb9, 0x77, 0x18, 0x7d, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08, 0x30, 0x06,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86,
+ 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e,
+ 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78,
+ 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30,
+ 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
+ 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e,
+ 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x45, 0x78,
+ 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74,
+ 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e,
+ 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x43, 0x25, 0x39, 0x23, 0x07, 0x04, 0xac, 0x99, 0x5d, 0x59,
+ 0x67, 0x3d, 0xe6, 0x2f, 0x61, 0x7d, 0x5a, 0x56, 0x7b, 0xfc, 0x06, 0x8d,
+ 0xb3, 0x4b, 0x9d, 0xfa, 0xd5, 0x05, 0x4c, 0x0d, 0x66, 0xb5, 0xbd, 0x3c,
+ 0xc7, 0xa2, 0x2a, 0x6b, 0xb5, 0xcf, 0xe6, 0xba, 0x83, 0x3e, 0x60, 0x90,
+ 0x36, 0x0c, 0xd5, 0xc2, 0xed, 0x8a, 0x95, 0xd9, 0x92, 0x42, 0x23, 0x1c,
+ 0x03, 0x76, 0x3e, 0xc2, 0x48, 0xf1, 0x75, 0x72, 0x9d, 0xb3, 0x8c, 0xcf,
+ 0xb3, 0x58, 0x34, 0x56, 0x49, 0x1d, 0xa1, 0x2e, 0x2b, 0x3d, 0xb2, 0xe8,
+ 0x5a, 0x10, 0x46, 0xde, 0x64, 0xb5, 0x4d, 0xae, 0x4b, 0x6e, 0xfc, 0x01,
+ 0xb7, 0x21, 0x10, 0xd5, 0x95, 0xb7, 0xeb, 0x2c, 0xbe, 0x14, 0x06, 0xcc,
+ 0x41, 0x2e, 0xe4, 0x6c, 0xe2, 0x46, 0x90, 0xff, 0xc6, 0x28, 0x7e, 0x73,
+ 0xfe, 0xe5, 0x17, 0xba, 0x82, 0xc3, 0x10, 0x05, 0x81, 0x66, 0xc2, 0x8b,
+ 0x28, 0x38, 0xa0, 0x44, 0x3e, 0xe9, 0xe4, 0xce, 0x33, 0xb0, 0x7c, 0xf8,
+ 0xe1, 0x53, 0x9d, 0xb8, 0xb4, 0xcb, 0xda, 0xc9, 0x2e, 0xd9, 0x93, 0x70,
+ 0x8e, 0x7c, 0x0b, 0xe3, 0x73, 0x3e, 0x99, 0x99, 0x8f, 0xeb, 0xe1, 0x11,
+ 0x44, 0x35, 0xd8, 0x60, 0x81, 0x62, 0x45, 0xd4, 0xde, 0x45, 0x5b, 0x90,
+ 0x2e, 0x49, 0x1b, 0x1b, 0xdb, 0xa4, 0x0f, 0x80, 0x62, 0x21, 0x73, 0x69,
+ 0xf1, 0xe3, 0xde, 0x6d, 0xd8, 0x48, 0x7c, 0x56, 0x12, 0x26, 0x22, 0x11,
+ 0x47, 0x01, 0xc6, 0x5e, 0x19, 0xc2, 0xb4, 0x95, 0x97, 0xee, 0x61, 0x00,
+ 0x55, 0xf1, 0x04, 0x38, 0xfc, 0x84, 0xe6, 0x78, 0xb4, 0x0d, 0x43, 0xbe,
+ 0x43, 0x33, 0xdd, 0x68, 0xd3, 0x22, 0x5b, 0x00, 0xfb, 0x14, 0x82, 0xe8,
+ 0x4b, 0x62, 0x79, 0x30, 0xcf, 0xd3, 0x95, 0x9f, 0xb3, 0xb9, 0x84, 0x01,
+ 0xd4, 0xdd, 0xcf, 0x23, 0x12, 0xf8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 4e:6c:48:88:36:bb:28:ce:2b:e3:5a:c3:79:8f:4a:24
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Nov 14 00:00:00 2012 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO SSL CA 2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:90:3d:54:2c:85:28:dd:9b:d0:b8:2b:8d:cc:31:
+ a5:96:97:0c:58:92:24:77:84:ad:9e:8a:92:e3:87:
+ d9:8a:55:63:ea:ea:9e:e7:08:9d:bf:e5:8a:e9:60:
+ 53:3e:80:6f:86:52:49:10:91:70:cf:10:b3:eb:08:
+ 58:25:48:5d:5d:eb:b7:ab:de:26:c1:5b:e1:9b:04:
+ de:5d:19:3a:be:40:17:d7:4e:dd:f9:d1:83:ca:36:
+ 36:2c:48:08:71:5c:eb:f2:0f:af:12:7a:4e:ad:2b:
+ b7:5d:8b:4b:ec:ee:fe:df:34:69:2c:fc:73:af:b1:
+ ce:0f:79:79:db:0a:90:02:fd:ca:33:b4:a2:d5:9d:
+ 79:5f:7f:b3:a4:59:a8:28:aa:78:e5:54:0a:18:d0:
+ 2e:6a:94:26:10:18:2b:7e:b3:cf:dd:28:28:bd:f8:
+ 8b:6b:ca:05:df:7a:50:ba:b8:4c:55:f6:79:ef:4f:
+ c4:4c:0f:8b:dc:79:a5:be:49:9d:7a:18:aa:f1:a6:
+ 6c:f8:59:e0:41:c2:e7:7c:1d:0c:ea:be:8d:e9:c8:
+ 0f:55:22:f5:71:42:a9:d0:81:ba:92:58:95:f8:c2:
+ ad:5a:7b:2f:00:81:d7:70:8d:b6:d7:45:f6:08:c0:
+ 8d:cb:5d:48:db:63:65:97:31:d1:15:9a:03:4f:1e:
+ 76:9d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ BF:D4:7D:6F:AF:74:93:90:88:A8:43:C6:1E:F7:13:6C:AE:B5:CC:AF
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.comodoca3.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3d:6d:89:77:8f:fe:66:51:37:17:ea:1e:f1:51:44:78:2a:f2:
+ a5:69:a9:40:0d:29:cf:35:5e:4d:bc:9e:7f:13:09:ce:cc:0e:
+ be:e0:b8:62:be:a1:18:cd:7e:91:78:ea:9b:46:dd:f7:0e:f8:
+ 4c:7d:2e:48:6d:a8:6a:94:a4:73:7d:de:94:90:b0:f7:9c:ab:
+ 6a:98:3c:08:45:4d:81:c6:0f:dd:9c:2b:3e:5b:b3:39:07:c4:
+ 9d:34:ce:4a:c3:47:30:85:24:36:d6:48:59:84:e4:d7:06:ed:
+ a9:dc:c7:0c:ca:7b:33:ea:bf:d4:4b:88:fc:3b:3b:4a:84:8d:
+ bd:f8:ff:ae:71:43:ff:98:d3:e9:7e:7d:59:9c:98:1d:14:00:
+ 4b:dc:ce:72:ce:1c:6f:dc:e7:33:d1:ca:3f:f7:8c:1e:d9:89:
+ 39:52:77:86:ea:cf:66:6c:1b:6b:38:c2:cb:f8:a7:47:60:87:
+ 18:d2:c0:ff:a9:b6:0a:22:41:4b:bf:55:78:ec:c1:95:2c:3c:
+ f1:7e:5b:58:d0:6c:29:f6:36:ba:dc:cb:99:49:75:4d:2a:9d:
+ a5:b5:33:5d:35:db:9b:d5:f5:b3:67:a3:db:c0:85:5f:11:33:
+ 09:8f:e1:8a:42:f7:a0:da:a3:b9:7f:35:d4:6c:74:8f:df:f8:
+ ff:be:bf:8e
+-----BEGIN CERTIFICATE-----
+MIIE5DCCA8ygAwIBAgIQTmxIiDa7KM4r41rDeY9KJDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEyMTExNDAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+cjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxGDAWBgNV
+BAMTD0NPTU9ETyBTU0wgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAJA9VCyFKN2b0LgrjcwxpZaXDFiSJHeErZ6KkuOH2YpVY+rqnucInb/liulg
+Uz6Ab4ZSSRCRcM8Qs+sIWCVIXV3rt6veJsFb4ZsE3l0ZOr5AF9dO3fnRg8o2NixI
+CHFc6/IPrxJ6Tq0rt12LS+zu/t80aSz8c6+xzg95edsKkAL9yjO0otWdeV9/s6RZ
+qCiqeOVUChjQLmqUJhAYK36zz90oKL34i2vKBd96ULq4TFX2ee9PxEwPi9x5pb5J
+nXoYqvGmbPhZ4EHC53wdDOq+jenID1Ui9XFCqdCBupJYlfjCrVp7LwCB13CNttdF
+9gjAjctdSNtjZZcx0RWaA08edp0CAwEAAaOCAXcwggFzMB8GA1UdIwQYMBaAFK29
+mHo0tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBS/1H1vr3STkIioQ8Ye9xNsrrXM
+rzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADARBgNVHSAECjAI
+MAYGBFUdIAAwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3Qu
+Y29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGzBggrBgEFBQcBAQSBpjCB
+ozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0
+cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLmNvbW9kb2NhMy5jb20wDQYJKoZIhvcNAQEFBQADggEBAD1tiXeP/mZR
+NxfqHvFRRHgq8qVpqUANKc81Xk28nn8TCc7MDr7guGK+oRjNfpF46ptG3fcO+Ex9
+LkhtqGqUpHN93pSQsPecq2qYPAhFTYHGD92cKz5bszkHxJ00zkrDRzCFJDbWSFmE
+5NcG7ancxwzKezPqv9RLiPw7O0qEjb34/65xQ/+Y0+l+fVmcmB0UAEvcznLOHG/c
+5zPRyj/3jB7ZiTlSd4bqz2ZsG2s4wsv4p0dghxjSwP+ptgoiQUu/VXjswZUsPPF+
+W1jQbCn2Nrrcy5lJdU0qnaW1M10125vV9bNno9vAhV8RMwmP4YpC96Dao7l/NdRs
+dI/f+P++v44=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert58[] = {
+ 0x30, 0x82, 0x04, 0xe4, 0x30, 0x82, 0x03, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x4e, 0x6c, 0x48, 0x88, 0x36, 0xbb, 0x28, 0xce, 0x2b,
+ 0xe3, 0x5a, 0xc3, 0x79, 0x8f, 0x4a, 0x24, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x31, 0x31, 0x31,
+ 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x72, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x53,
+ 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0x90, 0x3d, 0x54, 0x2c, 0x85, 0x28, 0xdd, 0x9b,
+ 0xd0, 0xb8, 0x2b, 0x8d, 0xcc, 0x31, 0xa5, 0x96, 0x97, 0x0c, 0x58, 0x92,
+ 0x24, 0x77, 0x84, 0xad, 0x9e, 0x8a, 0x92, 0xe3, 0x87, 0xd9, 0x8a, 0x55,
+ 0x63, 0xea, 0xea, 0x9e, 0xe7, 0x08, 0x9d, 0xbf, 0xe5, 0x8a, 0xe9, 0x60,
+ 0x53, 0x3e, 0x80, 0x6f, 0x86, 0x52, 0x49, 0x10, 0x91, 0x70, 0xcf, 0x10,
+ 0xb3, 0xeb, 0x08, 0x58, 0x25, 0x48, 0x5d, 0x5d, 0xeb, 0xb7, 0xab, 0xde,
+ 0x26, 0xc1, 0x5b, 0xe1, 0x9b, 0x04, 0xde, 0x5d, 0x19, 0x3a, 0xbe, 0x40,
+ 0x17, 0xd7, 0x4e, 0xdd, 0xf9, 0xd1, 0x83, 0xca, 0x36, 0x36, 0x2c, 0x48,
+ 0x08, 0x71, 0x5c, 0xeb, 0xf2, 0x0f, 0xaf, 0x12, 0x7a, 0x4e, 0xad, 0x2b,
+ 0xb7, 0x5d, 0x8b, 0x4b, 0xec, 0xee, 0xfe, 0xdf, 0x34, 0x69, 0x2c, 0xfc,
+ 0x73, 0xaf, 0xb1, 0xce, 0x0f, 0x79, 0x79, 0xdb, 0x0a, 0x90, 0x02, 0xfd,
+ 0xca, 0x33, 0xb4, 0xa2, 0xd5, 0x9d, 0x79, 0x5f, 0x7f, 0xb3, 0xa4, 0x59,
+ 0xa8, 0x28, 0xaa, 0x78, 0xe5, 0x54, 0x0a, 0x18, 0xd0, 0x2e, 0x6a, 0x94,
+ 0x26, 0x10, 0x18, 0x2b, 0x7e, 0xb3, 0xcf, 0xdd, 0x28, 0x28, 0xbd, 0xf8,
+ 0x8b, 0x6b, 0xca, 0x05, 0xdf, 0x7a, 0x50, 0xba, 0xb8, 0x4c, 0x55, 0xf6,
+ 0x79, 0xef, 0x4f, 0xc4, 0x4c, 0x0f, 0x8b, 0xdc, 0x79, 0xa5, 0xbe, 0x49,
+ 0x9d, 0x7a, 0x18, 0xaa, 0xf1, 0xa6, 0x6c, 0xf8, 0x59, 0xe0, 0x41, 0xc2,
+ 0xe7, 0x7c, 0x1d, 0x0c, 0xea, 0xbe, 0x8d, 0xe9, 0xc8, 0x0f, 0x55, 0x22,
+ 0xf5, 0x71, 0x42, 0xa9, 0xd0, 0x81, 0xba, 0x92, 0x58, 0x95, 0xf8, 0xc2,
+ 0xad, 0x5a, 0x7b, 0x2f, 0x00, 0x81, 0xd7, 0x70, 0x8d, 0xb6, 0xd7, 0x45,
+ 0xf6, 0x08, 0xc0, 0x8d, 0xcb, 0x5d, 0x48, 0xdb, 0x63, 0x65, 0x97, 0x31,
+ 0xd1, 0x15, 0x9a, 0x03, 0x4f, 0x1e, 0x76, 0x9d, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+ 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+ 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xbf, 0xd4, 0x7d, 0x6f, 0xaf, 0x74, 0x93,
+ 0x90, 0x88, 0xa8, 0x43, 0xc6, 0x1e, 0xf7, 0x13, 0x6c, 0xae, 0xb5, 0xcc,
+ 0xaf, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+ 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+ 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+ 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+ 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x63, 0x61, 0x33, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x3d, 0x6d, 0x89, 0x77, 0x8f, 0xfe, 0x66, 0x51,
+ 0x37, 0x17, 0xea, 0x1e, 0xf1, 0x51, 0x44, 0x78, 0x2a, 0xf2, 0xa5, 0x69,
+ 0xa9, 0x40, 0x0d, 0x29, 0xcf, 0x35, 0x5e, 0x4d, 0xbc, 0x9e, 0x7f, 0x13,
+ 0x09, 0xce, 0xcc, 0x0e, 0xbe, 0xe0, 0xb8, 0x62, 0xbe, 0xa1, 0x18, 0xcd,
+ 0x7e, 0x91, 0x78, 0xea, 0x9b, 0x46, 0xdd, 0xf7, 0x0e, 0xf8, 0x4c, 0x7d,
+ 0x2e, 0x48, 0x6d, 0xa8, 0x6a, 0x94, 0xa4, 0x73, 0x7d, 0xde, 0x94, 0x90,
+ 0xb0, 0xf7, 0x9c, 0xab, 0x6a, 0x98, 0x3c, 0x08, 0x45, 0x4d, 0x81, 0xc6,
+ 0x0f, 0xdd, 0x9c, 0x2b, 0x3e, 0x5b, 0xb3, 0x39, 0x07, 0xc4, 0x9d, 0x34,
+ 0xce, 0x4a, 0xc3, 0x47, 0x30, 0x85, 0x24, 0x36, 0xd6, 0x48, 0x59, 0x84,
+ 0xe4, 0xd7, 0x06, 0xed, 0xa9, 0xdc, 0xc7, 0x0c, 0xca, 0x7b, 0x33, 0xea,
+ 0xbf, 0xd4, 0x4b, 0x88, 0xfc, 0x3b, 0x3b, 0x4a, 0x84, 0x8d, 0xbd, 0xf8,
+ 0xff, 0xae, 0x71, 0x43, 0xff, 0x98, 0xd3, 0xe9, 0x7e, 0x7d, 0x59, 0x9c,
+ 0x98, 0x1d, 0x14, 0x00, 0x4b, 0xdc, 0xce, 0x72, 0xce, 0x1c, 0x6f, 0xdc,
+ 0xe7, 0x33, 0xd1, 0xca, 0x3f, 0xf7, 0x8c, 0x1e, 0xd9, 0x89, 0x39, 0x52,
+ 0x77, 0x86, 0xea, 0xcf, 0x66, 0x6c, 0x1b, 0x6b, 0x38, 0xc2, 0xcb, 0xf8,
+ 0xa7, 0x47, 0x60, 0x87, 0x18, 0xd2, 0xc0, 0xff, 0xa9, 0xb6, 0x0a, 0x22,
+ 0x41, 0x4b, 0xbf, 0x55, 0x78, 0xec, 0xc1, 0x95, 0x2c, 0x3c, 0xf1, 0x7e,
+ 0x5b, 0x58, 0xd0, 0x6c, 0x29, 0xf6, 0x36, 0xba, 0xdc, 0xcb, 0x99, 0x49,
+ 0x75, 0x4d, 0x2a, 0x9d, 0xa5, 0xb5, 0x33, 0x5d, 0x35, 0xdb, 0x9b, 0xd5,
+ 0xf5, 0xb3, 0x67, 0xa3, 0xdb, 0xc0, 0x85, 0x5f, 0x11, 0x33, 0x09, 0x8f,
+ 0xe1, 0x8a, 0x42, 0xf7, 0xa0, 0xda, 0xa3, 0xb9, 0x7f, 0x35, 0xd4, 0x6c,
+ 0x74, 0x8f, 0xdf, 0xf8, 0xff, 0xbe, 0xbf, 0x8e,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 4f:e3:e2:65:21:07:ab:20:37:41:6e:48:70:ce:d2:c2
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: May 25 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Trusted Secure Certificate Authority, CN=Trusted Secure Certificate Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:80:0b:42:c6:06:6c:cf:22:b3:1a:9e:11:2e:42:
+ 6e:39:bf:e8:12:af:3c:42:21:12:95:40:5d:32:b1:
+ 6d:1c:21:d1:34:e5:4f:a8:d1:43:a2:26:4e:30:7d:
+ 73:44:2c:73:aa:c5:4d:66:01:19:d2:ea:50:59:65:
+ d0:68:9d:05:a0:7c:a1:79:53:d0:21:90:59:0e:37:
+ db:1e:dc:92:a7:8b:0d:c4:f5:f8:e6:ff:b5:35:1a:
+ da:a8:b6:9b:20:85:65:c4:a2:4d:df:f3:94:4d:63:
+ 7e:ee:89:07:af:fe:e1:ba:00:15:2d:c6:77:8e:a3:
+ fe:ad:cf:26:54:5a:df:fc:d2:de:c2:ad:f6:b2:23:
+ fd:a8:83:e5:65:bd:27:f7:27:1a:18:59:6a:9e:14:
+ f6:b4:86:ff:1c:58:14:43:73:96:24:bf:10:43:d5:
+ 5c:89:f0:ce:f7:e1:96:16:5e:18:4a:27:28:90:80:
+ 18:fc:32:fe:f4:c7:b8:d6:82:3d:35:af:bb:4a:1c:
+ 5b:05:78:f6:fd:55:3e:82:74:b2:73:b8:89:4e:f7:
+ 1b:85:9a:d8:ca:b1:5a:b1:00:20:41:14:30:2b:14:
+ 24:ed:37:0e:32:3e:23:88:39:7e:b9:d9:38:03:e2:
+ 4c:d9:0d:43:41:33:10:eb:30:72:53:88:f7:52:9b:
+ 4f:81
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ CC:03:5B:96:5A:9E:16:CC:26:1E:BD:A3:70:FB:E3:CB:79:19:FC:4D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.2.8
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 7b:f0:fc:a1:28:47:bc:2b:b4:04:73:3f:4b:dd:1e:d1:b9:cd:
+ 1c:ed:7d:e5:e8:cb:51:f4:92:bf:dd:9c:0d:5c:6e:1d:95:ed:
+ 5b:70:50:89:d4:67:9a:15:54:d1:90:0a:fa:09:68:06:18:bb:
+ d7:27:e4:93:ff:43:48:81:3b:c8:59:49:35:ea:ac:b6:ae:46:
+ b5:d4:f3:b8:c3:c6:e4:91:bf:c9:34:fd:7e:d0:59:6e:61:a1:
+ 1f:48:63:54:b2:7d:46:bf:c8:fa:c3:bf:48:58:98:f6:69:84:
+ a7:16:69:08:27:a4:22:cb:a2:2c:c8:df:6e:a9:ee:f8:41:df:
+ 1b:a8:b7:f3:e3:ae:ce:a3:fe:d9:27:60:50:3f:04:7d:7a:44:
+ ea:76:42:5c:d3:55:46:ef:27:c5:6a:4a:80:e7:35:a0:91:c6:
+ 1b:a6:86:9c:5a:3b:04:83:54:34:d7:d1:88:a6:36:e9:7f:40:
+ 27:da:56:0a:50:21:9d:29:8b:a0:84:ec:fe:71:23:53:04:18:
+ 19:70:67:86:44:95:72:40:55:f6:dd:a3:b4:3d:2d:09:60:a5:
+ e7:5f:fc:ac:3b:ec:0c:91:9f:f8:ee:6a:ba:b2:3c:fd:95:7d:
+ 9a:07:f4:b0:65:43:a2:f6:df:7d:b8:21:49:84:04:ee:bd:ce:
+ 53:8f:0f:29
+-----BEGIN CERTIFICATE-----
+MIIE5DCCA8ygAwIBAgIQT+PiZSEHqyA3QW5IcM7SwjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDUyNTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+azELMAkGA1UEBhMCVVMxLTArBgNVBAoTJFRydXN0ZWQgU2VjdXJlIENlcnRpZmlj
+YXRlIEF1dGhvcml0eTEtMCsGA1UEAxMkVHJ1c3RlZCBTZWN1cmUgQ2VydGlmaWNh
+dGUgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgAtC
+xgZszyKzGp4RLkJuOb/oEq88QiESlUBdMrFtHCHRNOVPqNFDoiZOMH1zRCxzqsVN
+ZgEZ0upQWWXQaJ0FoHyheVPQIZBZDjfbHtySp4sNxPX45v+1NRraqLabIIVlxKJN
+3/OUTWN+7okHr/7hugAVLcZ3jqP+rc8mVFrf/NLewq32siP9qIPlZb0n9ycaGFlq
+nhT2tIb/HFgUQ3OWJL8QQ9VcifDO9+GWFl4YSicokIAY/DL+9Me41oI9Na+7Shxb
+BXj2/VU+gnSyc7iJTvcbhZrYyrFasQAgQRQwKxQk7TcOMj4jiDl+udk4A+JM2Q1D
+QTMQ6zByU4j3UptPgQIDAQABo4IBfjCCAXowHwYDVR0jBBgwFoAUrb2YejS0Jvf6
+xCZU7wO94CTLVBowHQYDVR0OBBYEFMwDW5ZanhbMJh69o3D748t5GfxNMA4GA1Ud
+DwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMBgGA1UdIAQRMA8wDQYLKwYB
+BAGyMQECAggwRAYDVR0fBD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3Qu
+Y29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMIGzBggrBgEFBQcBAQSBpjCB
+ozA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0
+RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsGAQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0
+cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0NDQS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAHvw/KEoR7wr
+tARzP0vdHtG5zRztfeXoy1H0kr/dnA1cbh2V7VtwUInUZ5oVVNGQCvoJaAYYu9cn
+5JP/Q0iBO8hZSTXqrLauRrXU87jDxuSRv8k0/X7QWW5hoR9IY1SyfUa/yPrDv0hY
+mPZphKcWaQgnpCLLoizI326p7vhB3xuot/Pjrs6j/tknYFA/BH16ROp2QlzTVUbv
+J8VqSoDnNaCRxhumhpxaOwSDVDTX0YimNul/QCfaVgpQIZ0pi6CE7P5xI1MEGBlw
+Z4ZElXJAVfbdo7Q9LQlgpedf/Kw77AyRn/juarqyPP2VfZoH9LBlQ6L23324IUmE
+BO69zlOPDyk=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert59[] = {
+ 0x30, 0x82, 0x04, 0xe4, 0x30, 0x82, 0x03, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x4f, 0xe3, 0xe2, 0x65, 0x21, 0x07, 0xab, 0x20, 0x37,
+ 0x41, 0x6e, 0x48, 0x70, 0xce, 0xd2, 0xc2, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x35, 0x32,
+ 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x6b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x24, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00,
+ 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x80, 0x0b, 0x42,
+ 0xc6, 0x06, 0x6c, 0xcf, 0x22, 0xb3, 0x1a, 0x9e, 0x11, 0x2e, 0x42, 0x6e,
+ 0x39, 0xbf, 0xe8, 0x12, 0xaf, 0x3c, 0x42, 0x21, 0x12, 0x95, 0x40, 0x5d,
+ 0x32, 0xb1, 0x6d, 0x1c, 0x21, 0xd1, 0x34, 0xe5, 0x4f, 0xa8, 0xd1, 0x43,
+ 0xa2, 0x26, 0x4e, 0x30, 0x7d, 0x73, 0x44, 0x2c, 0x73, 0xaa, 0xc5, 0x4d,
+ 0x66, 0x01, 0x19, 0xd2, 0xea, 0x50, 0x59, 0x65, 0xd0, 0x68, 0x9d, 0x05,
+ 0xa0, 0x7c, 0xa1, 0x79, 0x53, 0xd0, 0x21, 0x90, 0x59, 0x0e, 0x37, 0xdb,
+ 0x1e, 0xdc, 0x92, 0xa7, 0x8b, 0x0d, 0xc4, 0xf5, 0xf8, 0xe6, 0xff, 0xb5,
+ 0x35, 0x1a, 0xda, 0xa8, 0xb6, 0x9b, 0x20, 0x85, 0x65, 0xc4, 0xa2, 0x4d,
+ 0xdf, 0xf3, 0x94, 0x4d, 0x63, 0x7e, 0xee, 0x89, 0x07, 0xaf, 0xfe, 0xe1,
+ 0xba, 0x00, 0x15, 0x2d, 0xc6, 0x77, 0x8e, 0xa3, 0xfe, 0xad, 0xcf, 0x26,
+ 0x54, 0x5a, 0xdf, 0xfc, 0xd2, 0xde, 0xc2, 0xad, 0xf6, 0xb2, 0x23, 0xfd,
+ 0xa8, 0x83, 0xe5, 0x65, 0xbd, 0x27, 0xf7, 0x27, 0x1a, 0x18, 0x59, 0x6a,
+ 0x9e, 0x14, 0xf6, 0xb4, 0x86, 0xff, 0x1c, 0x58, 0x14, 0x43, 0x73, 0x96,
+ 0x24, 0xbf, 0x10, 0x43, 0xd5, 0x5c, 0x89, 0xf0, 0xce, 0xf7, 0xe1, 0x96,
+ 0x16, 0x5e, 0x18, 0x4a, 0x27, 0x28, 0x90, 0x80, 0x18, 0xfc, 0x32, 0xfe,
+ 0xf4, 0xc7, 0xb8, 0xd6, 0x82, 0x3d, 0x35, 0xaf, 0xbb, 0x4a, 0x1c, 0x5b,
+ 0x05, 0x78, 0xf6, 0xfd, 0x55, 0x3e, 0x82, 0x74, 0xb2, 0x73, 0xb8, 0x89,
+ 0x4e, 0xf7, 0x1b, 0x85, 0x9a, 0xd8, 0xca, 0xb1, 0x5a, 0xb1, 0x00, 0x20,
+ 0x41, 0x14, 0x30, 0x2b, 0x14, 0x24, 0xed, 0x37, 0x0e, 0x32, 0x3e, 0x23,
+ 0x88, 0x39, 0x7e, 0xb9, 0xd9, 0x38, 0x03, 0xe2, 0x4c, 0xd9, 0x0d, 0x43,
+ 0x41, 0x33, 0x10, 0xeb, 0x30, 0x72, 0x53, 0x88, 0xf7, 0x52, 0x9b, 0x4f,
+ 0x81, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7e, 0x30, 0x82,
+ 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30,
+ 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa,
+ 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcc, 0x03,
+ 0x5b, 0x96, 0x5a, 0x9e, 0x16, 0xcc, 0x26, 0x1e, 0xbd, 0xa3, 0x70, 0xfb,
+ 0xe3, 0xcb, 0x79, 0x19, 0xfc, 0x4d, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06,
+ 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x08, 0x30, 0x44, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+ 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+ 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+ 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x7b, 0xf0, 0xfc, 0xa1, 0x28, 0x47, 0xbc, 0x2b,
+ 0xb4, 0x04, 0x73, 0x3f, 0x4b, 0xdd, 0x1e, 0xd1, 0xb9, 0xcd, 0x1c, 0xed,
+ 0x7d, 0xe5, 0xe8, 0xcb, 0x51, 0xf4, 0x92, 0xbf, 0xdd, 0x9c, 0x0d, 0x5c,
+ 0x6e, 0x1d, 0x95, 0xed, 0x5b, 0x70, 0x50, 0x89, 0xd4, 0x67, 0x9a, 0x15,
+ 0x54, 0xd1, 0x90, 0x0a, 0xfa, 0x09, 0x68, 0x06, 0x18, 0xbb, 0xd7, 0x27,
+ 0xe4, 0x93, 0xff, 0x43, 0x48, 0x81, 0x3b, 0xc8, 0x59, 0x49, 0x35, 0xea,
+ 0xac, 0xb6, 0xae, 0x46, 0xb5, 0xd4, 0xf3, 0xb8, 0xc3, 0xc6, 0xe4, 0x91,
+ 0xbf, 0xc9, 0x34, 0xfd, 0x7e, 0xd0, 0x59, 0x6e, 0x61, 0xa1, 0x1f, 0x48,
+ 0x63, 0x54, 0xb2, 0x7d, 0x46, 0xbf, 0xc8, 0xfa, 0xc3, 0xbf, 0x48, 0x58,
+ 0x98, 0xf6, 0x69, 0x84, 0xa7, 0x16, 0x69, 0x08, 0x27, 0xa4, 0x22, 0xcb,
+ 0xa2, 0x2c, 0xc8, 0xdf, 0x6e, 0xa9, 0xee, 0xf8, 0x41, 0xdf, 0x1b, 0xa8,
+ 0xb7, 0xf3, 0xe3, 0xae, 0xce, 0xa3, 0xfe, 0xd9, 0x27, 0x60, 0x50, 0x3f,
+ 0x04, 0x7d, 0x7a, 0x44, 0xea, 0x76, 0x42, 0x5c, 0xd3, 0x55, 0x46, 0xef,
+ 0x27, 0xc5, 0x6a, 0x4a, 0x80, 0xe7, 0x35, 0xa0, 0x91, 0xc6, 0x1b, 0xa6,
+ 0x86, 0x9c, 0x5a, 0x3b, 0x04, 0x83, 0x54, 0x34, 0xd7, 0xd1, 0x88, 0xa6,
+ 0x36, 0xe9, 0x7f, 0x40, 0x27, 0xda, 0x56, 0x0a, 0x50, 0x21, 0x9d, 0x29,
+ 0x8b, 0xa0, 0x84, 0xec, 0xfe, 0x71, 0x23, 0x53, 0x04, 0x18, 0x19, 0x70,
+ 0x67, 0x86, 0x44, 0x95, 0x72, 0x40, 0x55, 0xf6, 0xdd, 0xa3, 0xb4, 0x3d,
+ 0x2d, 0x09, 0x60, 0xa5, 0xe7, 0x5f, 0xfc, 0xac, 0x3b, 0xec, 0x0c, 0x91,
+ 0x9f, 0xf8, 0xee, 0x6a, 0xba, 0xb2, 0x3c, 0xfd, 0x95, 0x7d, 0x9a, 0x07,
+ 0xf4, 0xb0, 0x65, 0x43, 0xa2, 0xf6, 0xdf, 0x7d, 0xb8, 0x21, 0x49, 0x84,
+ 0x04, 0xee, 0xbd, 0xce, 0x53, 0x8f, 0x0f, 0x29,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 07:6f:12:46:81:45:9c:28:d5:48:d6:97:c4:0e:00:1b
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Feb 16 00:00:00 2012 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=PositiveSSL CA 2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e8:ea:39:e3:22:a6:aa:b9:c4:00:d0:e7:aa:67:
+ 3b:43:07:bd:4f:92:eb:bc:be:01:a3:40:ad:e0:ef:
+ 44:28:b5:d0:3a:be:80:54:17:85:7a:6b:84:6c:36:
+ 36:e5:a3:24:e2:fe:28:01:90:bc:d7:dd:0f:b9:2b:
+ 4e:48:77:05:69:af:de:57:30:b1:e8:fb:1a:03:f6:
+ 3c:5b:53:1e:a1:01:49:68:72:73:d6:33:2b:43:a9:
+ 37:32:52:0f:ae:27:56:31:30:60:ad:c9:bd:73:2c:
+ 39:ee:90:d8:75:b0:25:21:60:7b:2a:7f:02:fd:82:
+ 85:1f:74:4f:92:34:73:5c:1d:00:a0:b0:c0:ea:98:
+ e2:be:01:14:58:17:28:22:8a:77:5d:50:25:cd:9a:
+ 6c:a6:e5:0c:e5:ab:28:c3:b2:20:89:f0:07:24:1e:
+ 95:c2:2e:c0:e5:e9:ec:f6:3d:12:07:48:3d:d2:c3:
+ 23:56:41:ec:d3:df:35:4b:c8:e7:f6:86:05:52:10:
+ 43:9a:8c:17:7c:8b:aa:bc:78:e0:f0:45:3b:ac:80:
+ 55:fe:28:93:e1:0a:11:68:f4:52:57:6f:fe:48:0b:
+ 5b:5d:1a:6a:67:73:99:82:b4:9e:43:60:3e:c7:5b:
+ 2a:12:6e:1a:ee:cb:39:ae:c3:35:9d:a8:bc:5d:b0:
+ 2f:c3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 99:E4:40:5F:6B:14:5E:3E:05:D9:DD:D3:63:54:FC:62:B8:F7:00:AC
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 9c:36:e3:4e:ae:f1:8a:bb:6c:97:8c:8f:4b:67:d0:9f:d8:84:
+ aa:9f:21:5f:35:a1:5b:c4:2b:63:0d:e8:bc:77:5d:a7:c4:37:
+ fd:4b:2d:9e:e8:1d:69:a1:c0:84:cc:d1:6d:8b:f3:81:cb:9f:
+ 4b:74:b0:49:2a:31:e8:37:40:eb:1f:d9:97:a3:1a:11:d5:26:
+ a7:6e:0f:ba:d5:be:2c:fd:b4:91:64:dc:be:3b:19:50:0d:7a:
+ 95:f3:04:13:a9:bb:47:0f:8b:5c:d1:ac:c2:7b:77:21:50:dd:
+ 5b:ab:ee:f4:a6:d8:d4:4a:53:6b:4d:ad:b8:c8:e7:e6:52:58:
+ 4d:43:4c:c2:a2:23:4f:0e:c0:20:39:af:df:4f:42:5b:1e:d3:
+ 09:f4:18:09:59:2a:d9:e8:4a:18:bf:32:fb:fa:2d:64:8b:87:
+ ca:5b:2b:e8:b8:0b:7e:be:17:12:c7:03:82:29:af:58:af:85:
+ 84:5d:3d:0a:df:23:51:c3:cd:af:10:bf:80:69:77:91:0a:4f:
+ e5:ba:e1:ad:9b:ce:df:33:4e:30:3b:e9:8f:66:7f:82:fa:6b:
+ fa:db:a3:c0:73:00:e3:d6:12:af:4d:f2:0f:5a:14:51:1f:6d:
+ b8:86:81:62:07:ce:5c:72:c2:4f:f3:57:2a:71:d9:d4:97:85:
+ e6:18:53:b7
+-----BEGIN CERTIFICATE-----
+MIIE5TCCA82gAwIBAgIQB28SRoFFnCjVSNaXxA4AGzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEyMDIxNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+czELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxGTAXBgNV
+BAMTEFBvc2l0aXZlU1NMIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDo6jnjIqaqucQA0OeqZztDB71Pkuu8vgGjQK3g70QotdA6voBUF4V6a4Rs
+NjbloyTi/igBkLzX3Q+5K05IdwVpr95XMLHo+xoD9jxbUx6hAUlocnPWMytDqTcy
+Ug+uJ1YxMGCtyb1zLDnukNh1sCUhYHsqfwL9goUfdE+SNHNcHQCgsMDqmOK+ARRY
+FygiinddUCXNmmym5QzlqyjDsiCJ8AckHpXCLsDl6ez2PRIHSD3SwyNWQezT3zVL
+yOf2hgVSEEOajBd8i6q8eODwRTusgFX+KJPhChFo9FJXb/5IC1tdGmpnc5mCtJ5D
+YD7HWyoSbhruyzmuwzWdqLxdsC/DAgMBAAGjggF3MIIBczAfBgNVHSMEGDAWgBSt
+vZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUmeRAX2sUXj4F2d3TY1T8Yrj3
+AKwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwEQYDVR0gBAow
+CDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEEgaYw
+gaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVz
+dEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51c2Vy
+dHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlodHRw
+Oi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQCcNuNOrvGK
+u2yXjI9LZ9Cf2ISqnyFfNaFbxCtjDei8d12nxDf9Sy2e6B1pocCEzNFti/OBy59L
+dLBJKjHoN0DrH9mXoxoR1Sanbg+61b4s/bSRZNy+OxlQDXqV8wQTqbtHD4tc0azC
+e3chUN1bq+70ptjUSlNrTa24yOfmUlhNQ0zCoiNPDsAgOa/fT0JbHtMJ9BgJWSrZ
+6EoYvzL7+i1ki4fKWyvouAt+vhcSxwOCKa9Yr4WEXT0K3yNRw82vEL+AaXeRCk/l
+uuGtm87fM04wO+mPZn+C+mv626PAcwDj1hKvTfIPWhRRH224hoFiB85ccsJP81cq
+cdnUl4XmGFO3
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert60[] = {
+ 0x30, 0x82, 0x04, 0xe5, 0x30, 0x82, 0x03, 0xcd, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x07, 0x6f, 0x12, 0x46, 0x81, 0x45, 0x9c, 0x28, 0xd5,
+ 0x48, 0xd6, 0x97, 0xc4, 0x0e, 0x00, 0x1b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32, 0x30, 0x32, 0x31,
+ 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x73, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x10, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65,
+ 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xe8, 0xea, 0x39, 0xe3, 0x22, 0xa6, 0xaa,
+ 0xb9, 0xc4, 0x00, 0xd0, 0xe7, 0xaa, 0x67, 0x3b, 0x43, 0x07, 0xbd, 0x4f,
+ 0x92, 0xeb, 0xbc, 0xbe, 0x01, 0xa3, 0x40, 0xad, 0xe0, 0xef, 0x44, 0x28,
+ 0xb5, 0xd0, 0x3a, 0xbe, 0x80, 0x54, 0x17, 0x85, 0x7a, 0x6b, 0x84, 0x6c,
+ 0x36, 0x36, 0xe5, 0xa3, 0x24, 0xe2, 0xfe, 0x28, 0x01, 0x90, 0xbc, 0xd7,
+ 0xdd, 0x0f, 0xb9, 0x2b, 0x4e, 0x48, 0x77, 0x05, 0x69, 0xaf, 0xde, 0x57,
+ 0x30, 0xb1, 0xe8, 0xfb, 0x1a, 0x03, 0xf6, 0x3c, 0x5b, 0x53, 0x1e, 0xa1,
+ 0x01, 0x49, 0x68, 0x72, 0x73, 0xd6, 0x33, 0x2b, 0x43, 0xa9, 0x37, 0x32,
+ 0x52, 0x0f, 0xae, 0x27, 0x56, 0x31, 0x30, 0x60, 0xad, 0xc9, 0xbd, 0x73,
+ 0x2c, 0x39, 0xee, 0x90, 0xd8, 0x75, 0xb0, 0x25, 0x21, 0x60, 0x7b, 0x2a,
+ 0x7f, 0x02, 0xfd, 0x82, 0x85, 0x1f, 0x74, 0x4f, 0x92, 0x34, 0x73, 0x5c,
+ 0x1d, 0x00, 0xa0, 0xb0, 0xc0, 0xea, 0x98, 0xe2, 0xbe, 0x01, 0x14, 0x58,
+ 0x17, 0x28, 0x22, 0x8a, 0x77, 0x5d, 0x50, 0x25, 0xcd, 0x9a, 0x6c, 0xa6,
+ 0xe5, 0x0c, 0xe5, 0xab, 0x28, 0xc3, 0xb2, 0x20, 0x89, 0xf0, 0x07, 0x24,
+ 0x1e, 0x95, 0xc2, 0x2e, 0xc0, 0xe5, 0xe9, 0xec, 0xf6, 0x3d, 0x12, 0x07,
+ 0x48, 0x3d, 0xd2, 0xc3, 0x23, 0x56, 0x41, 0xec, 0xd3, 0xdf, 0x35, 0x4b,
+ 0xc8, 0xe7, 0xf6, 0x86, 0x05, 0x52, 0x10, 0x43, 0x9a, 0x8c, 0x17, 0x7c,
+ 0x8b, 0xaa, 0xbc, 0x78, 0xe0, 0xf0, 0x45, 0x3b, 0xac, 0x80, 0x55, 0xfe,
+ 0x28, 0x93, 0xe1, 0x0a, 0x11, 0x68, 0xf4, 0x52, 0x57, 0x6f, 0xfe, 0x48,
+ 0x0b, 0x5b, 0x5d, 0x1a, 0x6a, 0x67, 0x73, 0x99, 0x82, 0xb4, 0x9e, 0x43,
+ 0x60, 0x3e, 0xc7, 0x5b, 0x2a, 0x12, 0x6e, 0x1a, 0xee, 0xcb, 0x39, 0xae,
+ 0xc3, 0x35, 0x9d, 0xa8, 0xbc, 0x5d, 0xb0, 0x2f, 0xc3, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad,
+ 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef,
+ 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x99, 0xe4, 0x40, 0x5f, 0x6b, 0x14,
+ 0x5e, 0x3e, 0x05, 0xd9, 0xdd, 0xd3, 0x63, 0x54, 0xfc, 0x62, 0xb8, 0xf7,
+ 0x00, 0xac, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+ 0x01, 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+ 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+ 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+ 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+ 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x01, 0x00, 0x9c, 0x36, 0xe3, 0x4e, 0xae, 0xf1, 0x8a,
+ 0xbb, 0x6c, 0x97, 0x8c, 0x8f, 0x4b, 0x67, 0xd0, 0x9f, 0xd8, 0x84, 0xaa,
+ 0x9f, 0x21, 0x5f, 0x35, 0xa1, 0x5b, 0xc4, 0x2b, 0x63, 0x0d, 0xe8, 0xbc,
+ 0x77, 0x5d, 0xa7, 0xc4, 0x37, 0xfd, 0x4b, 0x2d, 0x9e, 0xe8, 0x1d, 0x69,
+ 0xa1, 0xc0, 0x84, 0xcc, 0xd1, 0x6d, 0x8b, 0xf3, 0x81, 0xcb, 0x9f, 0x4b,
+ 0x74, 0xb0, 0x49, 0x2a, 0x31, 0xe8, 0x37, 0x40, 0xeb, 0x1f, 0xd9, 0x97,
+ 0xa3, 0x1a, 0x11, 0xd5, 0x26, 0xa7, 0x6e, 0x0f, 0xba, 0xd5, 0xbe, 0x2c,
+ 0xfd, 0xb4, 0x91, 0x64, 0xdc, 0xbe, 0x3b, 0x19, 0x50, 0x0d, 0x7a, 0x95,
+ 0xf3, 0x04, 0x13, 0xa9, 0xbb, 0x47, 0x0f, 0x8b, 0x5c, 0xd1, 0xac, 0xc2,
+ 0x7b, 0x77, 0x21, 0x50, 0xdd, 0x5b, 0xab, 0xee, 0xf4, 0xa6, 0xd8, 0xd4,
+ 0x4a, 0x53, 0x6b, 0x4d, 0xad, 0xb8, 0xc8, 0xe7, 0xe6, 0x52, 0x58, 0x4d,
+ 0x43, 0x4c, 0xc2, 0xa2, 0x23, 0x4f, 0x0e, 0xc0, 0x20, 0x39, 0xaf, 0xdf,
+ 0x4f, 0x42, 0x5b, 0x1e, 0xd3, 0x09, 0xf4, 0x18, 0x09, 0x59, 0x2a, 0xd9,
+ 0xe8, 0x4a, 0x18, 0xbf, 0x32, 0xfb, 0xfa, 0x2d, 0x64, 0x8b, 0x87, 0xca,
+ 0x5b, 0x2b, 0xe8, 0xb8, 0x0b, 0x7e, 0xbe, 0x17, 0x12, 0xc7, 0x03, 0x82,
+ 0x29, 0xaf, 0x58, 0xaf, 0x85, 0x84, 0x5d, 0x3d, 0x0a, 0xdf, 0x23, 0x51,
+ 0xc3, 0xcd, 0xaf, 0x10, 0xbf, 0x80, 0x69, 0x77, 0x91, 0x0a, 0x4f, 0xe5,
+ 0xba, 0xe1, 0xad, 0x9b, 0xce, 0xdf, 0x33, 0x4e, 0x30, 0x3b, 0xe9, 0x8f,
+ 0x66, 0x7f, 0x82, 0xfa, 0x6b, 0xfa, 0xdb, 0xa3, 0xc0, 0x73, 0x00, 0xe3,
+ 0xd6, 0x12, 0xaf, 0x4d, 0xf2, 0x0f, 0x5a, 0x14, 0x51, 0x1f, 0x6d, 0xb8,
+ 0x86, 0x81, 0x62, 0x07, 0xce, 0x5c, 0x72, 0xc2, 0x4f, 0xf3, 0x57, 0x2a,
+ 0x71, 0xd9, 0xd4, 0x97, 0x85, 0xe6, 0x18, 0x53, 0xb7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 74:86:21:96:95:10:c9:29:26:29:4b:cc:8b:f8:29:2c
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Jun 22 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Globe Hosting, Inc., OU=GlobeSSL DV Certification Authority, CN=GlobeSSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a0:47:04:ce:a8:35:ab:ff:18:63:88:8e:c1:89:
+ fb:2d:03:0f:cd:2d:28:97:a1:da:d2:10:5b:83:4f:
+ 2c:46:98:0b:12:98:f4:b3:39:b7:97:a3:86:5d:80:
+ 22:02:33:c8:9d:ba:9b:ac:ba:d9:7c:7e:de:f7:a3:
+ b9:2a:69:e8:17:7b:fc:56:2f:99:87:da:2a:a2:77:
+ a4:ac:56:8c:ac:f5:1e:df:38:4f:97:d2:3a:03:6a:
+ f6:49:c3:2a:7b:b3:26:54:4c:15:e1:00:f3:02:90:
+ 38:a4:99:57:db:fd:8a:91:01:f1:71:96:75:df:21:
+ f9:15:19:5d:18:2b:0e:73:10:2d:0e:5b:56:28:cb:
+ fd:61:ec:b6:f0:5b:f9:3c:14:f6:42:0b:ca:cd:17:
+ df:d9:76:5f:d6:54:29:40:d1:79:15:fb:f5:45:a9:
+ 2d:6f:54:35:d7:5e:39:e6:a6:b5:04:5b:90:d9:6f:
+ 5c:2f:58:85:00:00:f0:68:11:08:19:40:50:49:8c:
+ da:87:e9:82:99:d3:86:ae:d4:c1:36:a2:56:0e:08:
+ c3:b7:36:7a:91:f0:24:0c:79:8f:30:a5:e2:4c:99:
+ 9d:7e:76:dd:98:81:e8:49:46:ac:01:c8:25:f7:7e:
+ 04:c5:9e:fa:0d:e2:f7:b8:40:f1:45:fc:e6:c2:c9:
+ 6f:c7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ C3:AB:A0:02:F0:9B:F5:66:7F:28:15:92:22:95:DB:B8:4E:D3:93:08
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.2.27
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 66:9c:13:6d:d2:7e:2c:dd:6b:d6:1a:91:37:85:86:51:23:4d:
+ 64:63:f5:5e:83:3e:88:fe:6f:67:87:0e:ca:85:6d:bb:3b:3c:
+ d5:ad:fc:ba:4d:ba:8b:bb:c8:c1:ed:2c:d4:6d:cb:10:c2:33:
+ e3:e7:66:97:8f:2b:e5:8f:81:8f:ed:bc:dd:87:b5:db:dc:23:
+ 5f:ae:0f:40:91:29:9e:07:d4:b1:ce:d0:82:1b:6e:1d:d1:a4:
+ 08:50:12:ae:8f:0f:79:67:a7:00:67:de:ba:90:9b:48:bc:5f:
+ 90:c3:1b:fe:cc:b6:3a:1e:db:15:15:b5:de:ab:78:e3:41:aa:
+ 93:8a:e1:bf:43:15:ec:c9:6b:21:fe:ed:a1:df:e9:0b:2d:cb:
+ a0:73:1f:d6:3e:f8:98:9b:46:78:e4:ad:25:20:41:86:28:d0:
+ de:7d:14:96:04:47:ac:c8:b9:6b:dd:00:f0:47:11:9f:8b:7e:
+ b1:a2:ed:47:e9:17:23:34:e6:bd:8b:67:41:64:60:0a:1a:cd:
+ 75:69:89:39:66:95:e1:32:87:73:91:d0:9b:83:8d:b8:c7:e0:
+ bc:22:8f:2c:24:13:c8:c2:94:97:fa:31:26:22:82:2b:b5:ef:
+ 05:a6:a0:7e:9a:00:b4:6b:e3:9e:59:43:bc:76:98:f3:3c:30:
+ db:1c:30:2e
+-----BEGIN CERTIFICATE-----
+MIIE6DCCA9CgAwIBAgIQdIYhlpUQySkmKUvMi/gpLDANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDYyMjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+bzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0dsb2JlIEhvc3RpbmcsIEluYy4xLDAq
+BgNVBAsTI0dsb2JlU1NMIERWIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRQwEgYD
+VQQDEwtHbG9iZVNTTCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKBHBM6oNav/GGOIjsGJ+y0DD80tKJeh2tIQW4NPLEaYCxKY9LM5t5ejhl2AIgIz
+yJ26m6y62Xx+3vejuSpp6Bd7/FYvmYfaKqJ3pKxWjKz1Ht84T5fSOgNq9knDKnuz
+JlRMFeEA8wKQOKSZV9v9ipEB8XGWdd8h+RUZXRgrDnMQLQ5bVijL/WHstvBb+TwU
+9kILys0X39l2X9ZUKUDReRX79UWpLW9UNddeOeamtQRbkNlvXC9YhQAA8GgRCBlA
+UEmM2ofpgpnThq7UwTaiVg4Iw7c2epHwJAx5jzCl4kyZnX523ZiB6ElGrAHIJfd+
+BMWe+g3i97hA8UX85sLJb8cCAwEAAaOCAX4wggF6MB8GA1UdIwQYMBaAFK29mHo0
+tCb3+sQmVO8DveAky1QaMB0GA1UdDgQWBBTDq6AC8Jv1Zn8oFZIildu4TtOTCDAO
+BgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAYBgNVHSAEETAPMA0G
+CysGAQQBsjEBAgIbMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRy
+dXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYIKwYBBQUHAQEE
+gaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRU
+cnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0cDovL2NydC51
+c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsGAQUFBzABhhlo
+dHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBmnBNt
+0n4s3WvWGpE3hYZRI01kY/Vegz6I/m9nhw7KhW27OzzVrfy6TbqLu8jB7SzUbcsQ
+wjPj52aXjyvlj4GP7bzdh7Xb3CNfrg9AkSmeB9SxztCCG24d0aQIUBKujw95Z6cA
+Z966kJtIvF+Qwxv+zLY6HtsVFbXeq3jjQaqTiuG/QxXsyWsh/u2h3+kLLcugcx/W
+PviYm0Z45K0lIEGGKNDefRSWBEesyLlr3QDwRxGfi36xou1H6RcjNOa9i2dBZGAK
+Gs11aYk5ZpXhModzkdCbg424x+C8Io8sJBPIwpSX+jEmIoIrte8FpqB+mgC0a+Oe
+WUO8dpjzPDDbHDAu
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert61[] = {
+ 0x30, 0x82, 0x04, 0xe8, 0x30, 0x82, 0x03, 0xd0, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x74, 0x86, 0x21, 0x96, 0x95, 0x10, 0xc9, 0x29, 0x26,
+ 0x29, 0x4b, 0xcc, 0x8b, 0xf8, 0x29, 0x2c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x36, 0x32,
+ 0x32, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x13, 0x47, 0x6c, 0x6f, 0x62, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x69,
+ 0x6e, 0x67, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2c, 0x30, 0x2a,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x23, 0x47, 0x6c, 0x6f, 0x62, 0x65,
+ 0x53, 0x53, 0x4c, 0x20, 0x44, 0x56, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x0b, 0x47, 0x6c, 0x6f, 0x62, 0x65, 0x53, 0x53,
+ 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xa0, 0x47, 0x04, 0xce, 0xa8, 0x35, 0xab, 0xff, 0x18, 0x63, 0x88,
+ 0x8e, 0xc1, 0x89, 0xfb, 0x2d, 0x03, 0x0f, 0xcd, 0x2d, 0x28, 0x97, 0xa1,
+ 0xda, 0xd2, 0x10, 0x5b, 0x83, 0x4f, 0x2c, 0x46, 0x98, 0x0b, 0x12, 0x98,
+ 0xf4, 0xb3, 0x39, 0xb7, 0x97, 0xa3, 0x86, 0x5d, 0x80, 0x22, 0x02, 0x33,
+ 0xc8, 0x9d, 0xba, 0x9b, 0xac, 0xba, 0xd9, 0x7c, 0x7e, 0xde, 0xf7, 0xa3,
+ 0xb9, 0x2a, 0x69, 0xe8, 0x17, 0x7b, 0xfc, 0x56, 0x2f, 0x99, 0x87, 0xda,
+ 0x2a, 0xa2, 0x77, 0xa4, 0xac, 0x56, 0x8c, 0xac, 0xf5, 0x1e, 0xdf, 0x38,
+ 0x4f, 0x97, 0xd2, 0x3a, 0x03, 0x6a, 0xf6, 0x49, 0xc3, 0x2a, 0x7b, 0xb3,
+ 0x26, 0x54, 0x4c, 0x15, 0xe1, 0x00, 0xf3, 0x02, 0x90, 0x38, 0xa4, 0x99,
+ 0x57, 0xdb, 0xfd, 0x8a, 0x91, 0x01, 0xf1, 0x71, 0x96, 0x75, 0xdf, 0x21,
+ 0xf9, 0x15, 0x19, 0x5d, 0x18, 0x2b, 0x0e, 0x73, 0x10, 0x2d, 0x0e, 0x5b,
+ 0x56, 0x28, 0xcb, 0xfd, 0x61, 0xec, 0xb6, 0xf0, 0x5b, 0xf9, 0x3c, 0x14,
+ 0xf6, 0x42, 0x0b, 0xca, 0xcd, 0x17, 0xdf, 0xd9, 0x76, 0x5f, 0xd6, 0x54,
+ 0x29, 0x40, 0xd1, 0x79, 0x15, 0xfb, 0xf5, 0x45, 0xa9, 0x2d, 0x6f, 0x54,
+ 0x35, 0xd7, 0x5e, 0x39, 0xe6, 0xa6, 0xb5, 0x04, 0x5b, 0x90, 0xd9, 0x6f,
+ 0x5c, 0x2f, 0x58, 0x85, 0x00, 0x00, 0xf0, 0x68, 0x11, 0x08, 0x19, 0x40,
+ 0x50, 0x49, 0x8c, 0xda, 0x87, 0xe9, 0x82, 0x99, 0xd3, 0x86, 0xae, 0xd4,
+ 0xc1, 0x36, 0xa2, 0x56, 0x0e, 0x08, 0xc3, 0xb7, 0x36, 0x7a, 0x91, 0xf0,
+ 0x24, 0x0c, 0x79, 0x8f, 0x30, 0xa5, 0xe2, 0x4c, 0x99, 0x9d, 0x7e, 0x76,
+ 0xdd, 0x98, 0x81, 0xe8, 0x49, 0x46, 0xac, 0x01, 0xc8, 0x25, 0xf7, 0x7e,
+ 0x04, 0xc5, 0x9e, 0xfa, 0x0d, 0xe2, 0xf7, 0xb8, 0x40, 0xf1, 0x45, 0xfc,
+ 0xe6, 0xc2, 0xc9, 0x6f, 0xc7, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0x7e, 0x30, 0x82, 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34,
+ 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24,
+ 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0xc3, 0xab, 0xa0, 0x02, 0xf0, 0x9b, 0xf5, 0x66, 0x7f, 0x28,
+ 0x15, 0x92, 0x22, 0x95, 0xdb, 0xb8, 0x4e, 0xd3, 0x93, 0x08, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06,
+ 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x1b,
+ 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30,
+ 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+ 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81,
+ 0xb3, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x81, 0xa6, 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+ 0x43, 0x41, 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e,
+ 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75,
+ 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x66, 0x9c, 0x13, 0x6d,
+ 0xd2, 0x7e, 0x2c, 0xdd, 0x6b, 0xd6, 0x1a, 0x91, 0x37, 0x85, 0x86, 0x51,
+ 0x23, 0x4d, 0x64, 0x63, 0xf5, 0x5e, 0x83, 0x3e, 0x88, 0xfe, 0x6f, 0x67,
+ 0x87, 0x0e, 0xca, 0x85, 0x6d, 0xbb, 0x3b, 0x3c, 0xd5, 0xad, 0xfc, 0xba,
+ 0x4d, 0xba, 0x8b, 0xbb, 0xc8, 0xc1, 0xed, 0x2c, 0xd4, 0x6d, 0xcb, 0x10,
+ 0xc2, 0x33, 0xe3, 0xe7, 0x66, 0x97, 0x8f, 0x2b, 0xe5, 0x8f, 0x81, 0x8f,
+ 0xed, 0xbc, 0xdd, 0x87, 0xb5, 0xdb, 0xdc, 0x23, 0x5f, 0xae, 0x0f, 0x40,
+ 0x91, 0x29, 0x9e, 0x07, 0xd4, 0xb1, 0xce, 0xd0, 0x82, 0x1b, 0x6e, 0x1d,
+ 0xd1, 0xa4, 0x08, 0x50, 0x12, 0xae, 0x8f, 0x0f, 0x79, 0x67, 0xa7, 0x00,
+ 0x67, 0xde, 0xba, 0x90, 0x9b, 0x48, 0xbc, 0x5f, 0x90, 0xc3, 0x1b, 0xfe,
+ 0xcc, 0xb6, 0x3a, 0x1e, 0xdb, 0x15, 0x15, 0xb5, 0xde, 0xab, 0x78, 0xe3,
+ 0x41, 0xaa, 0x93, 0x8a, 0xe1, 0xbf, 0x43, 0x15, 0xec, 0xc9, 0x6b, 0x21,
+ 0xfe, 0xed, 0xa1, 0xdf, 0xe9, 0x0b, 0x2d, 0xcb, 0xa0, 0x73, 0x1f, 0xd6,
+ 0x3e, 0xf8, 0x98, 0x9b, 0x46, 0x78, 0xe4, 0xad, 0x25, 0x20, 0x41, 0x86,
+ 0x28, 0xd0, 0xde, 0x7d, 0x14, 0x96, 0x04, 0x47, 0xac, 0xc8, 0xb9, 0x6b,
+ 0xdd, 0x00, 0xf0, 0x47, 0x11, 0x9f, 0x8b, 0x7e, 0xb1, 0xa2, 0xed, 0x47,
+ 0xe9, 0x17, 0x23, 0x34, 0xe6, 0xbd, 0x8b, 0x67, 0x41, 0x64, 0x60, 0x0a,
+ 0x1a, 0xcd, 0x75, 0x69, 0x89, 0x39, 0x66, 0x95, 0xe1, 0x32, 0x87, 0x73,
+ 0x91, 0xd0, 0x9b, 0x83, 0x8d, 0xb8, 0xc7, 0xe0, 0xbc, 0x22, 0x8f, 0x2c,
+ 0x24, 0x13, 0xc8, 0xc2, 0x94, 0x97, 0xfa, 0x31, 0x26, 0x22, 0x82, 0x2b,
+ 0xb5, 0xef, 0x05, 0xa6, 0xa0, 0x7e, 0x9a, 0x00, 0xb4, 0x6b, 0xe3, 0x9e,
+ 0x59, 0x43, 0xbc, 0x76, 0x98, 0xf3, 0x3c, 0x30, 0xdb, 0x1c, 0x30, 0x2e,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 7a:ac:a2:1d:53:9d:14:54:11:3c:04:5e:d8:35:f8:ea
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+ Validity
+ Not Before: Nov 26 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions EV Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:35:5c:e2:e7:95:1a:98:8f:d8:4f:d6:d5:dc:
+ 7e:5c:82:bf:9f:cc:4b:fa:3c:4a:81:bc:da:c5:a7:
+ e9:ad:9a:26:8f:dc:19:2c:63:12:3e:56:df:75:e6:
+ 48:ac:e3:47:90:7f:5f:08:f1:a3:80:d1:d0:cd:25:
+ cd:59:f3:ad:2e:c3:eb:06:09:fe:39:24:39:a2:a1:
+ ec:c4:c4:9a:d7:a0:08:55:fe:c8:c5:64:2e:fc:e7:
+ 06:88:95:c1:3e:31:5a:55:f0:1d:98:04:94:b4:7f:
+ 5e:dc:90:a9:a1:85:c7:aa:12:b9:87:d1:a3:71:11:
+ 02:6c:7e:9b:c9:39:eb:ec:b5:58:27:8b:a3:98:11:
+ a0:ab:83:fb:24:30:00:ae:02:57:fe:80:e2:ca:8f:
+ 48:60:63:39:db:af:96:74:83:bb:3b:6c:ef:b3:33:
+ c6:a6:dc:31:e9:f9:bc:aa:b7:1e:c8:f4:7f:58:69:
+ 72:ee:5a:8f:36:0a:fe:32:11:1c:34:3d:79:88:69:
+ d7:da:30:73:36:68:e1:fc:10:28:41:ee:6c:7f:88:
+ 08:3e:93:77:63:8a:aa:c8:a8:7b:cb:34:70:04:a1:
+ 6c:3b:6d:48:27:d4:3d:17:ba:0c:a3:e1:8a:5a:ab:
+ 1f:e1:72:26:c3:8e:26:32:28:d9:72:49:0e:ee:e5:
+ 75:43
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:21:30:C9:FB:00:D7:4E:98:DA:87:AA:2A:D0:A7:2E:B1:40:31:A7:4C
+
+ X509v3 Subject Key Identifier:
+ 8A:35:E4:35:3A:BC:11:A1:9E:FB:F5:4F:34:66:D5:4B:AC:4C:62:68
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.networksolutions.com/legal/SSL-legal-repository-ev-cps.jsp
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.netsolssl.com/NetworkSolutionsCertificateAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/NetworkSolutionsAddTrustEVServerCA.crt
+ OCSP - URI:http://ocsp.netsolssl.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3b:41:a7:b0:f6:24:18:e5:c8:77:0e:a8:05:bc:e8:48:57:ce:
+ 81:23:ff:17:98:68:01:89:c5:69:9e:c2:ab:45:ab:73:4c:25:
+ c9:6f:77:05:72:10:eb:9e:5e:72:0c:f7:d3:7f:bc:63:1c:b0:
+ e5:4c:44:01:99:1f:e1:de:fc:70:e3:77:e5:d8:e9:a9:2d:95:
+ dd:05:cf:6e:c5:c7:d9:dc:2f:d1:40:7e:8f:e9:47:8b:87:d9:
+ 81:33:a5:2b:4c:b9:2e:a4:e1:a8:cc:1c:6b:cf:04:36:5a:aa:
+ a4:a0:74:30:1b:51:20:c7:61:b9:50:18:e4:bf:2b:c3:f8:a6:
+ fa:8c:89:16:21:99:a7:5a:43:99:03:6d:74:e0:8b:ea:b0:78:
+ 8e:20:01:d2:29:b2:8c:f1:7b:2a:08:b2:62:6a:30:36:5d:5c:
+ a7:3b:4a:ee:f7:07:32:47:2d:f6:88:62:0c:a9:24:e0:70:df:
+ a2:a6:42:0c:7b:7d:28:05:d7:0b:6d:e5:84:fb:f0:c9:88:b3:
+ a9:d9:01:c3:9c:98:dc:cb:83:47:ec:f9:d1:9e:a0:5c:5d:a7:
+ 31:52:b8:5d:b0:91:03:6f:1e:6a:ef:e3:36:02:e3:1a:5d:31:
+ 4a:90:16:1b:d7:33:05:30:fb:00:aa:28:eb:5f:0d:e7:14:56:
+ 27:5d:7c:b4
+-----BEGIN CERTIFICATE-----
+MIIE8DCCA9igAwIBAgIQeqyiHVOdFFQRPARe2DX46jANBgkqhkiG9w0BAQUFADBi
+MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
+dHkwHhcNMTAxMTI2MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBZMQswCQYDVQQGEwJV
+UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMScwJQYDVQQDEx5O
+ZXR3b3JrIFNvbHV0aW9ucyBFViBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDQNVzi55UamI/YT9bV3H5cgr+fzEv6PEqBvNrFp+mtmiaP
+3BksYxI+Vt915kis40eQf18I8aOA0dDNJc1Z860uw+sGCf45JDmioezExJrXoAhV
+/sjFZC785waIlcE+MVpV8B2YBJS0f17ckKmhhceqErmH0aNxEQJsfpvJOevstVgn
+i6OYEaCrg/skMACuAlf+gOLKj0hgYznbr5Z0g7s7bO+zM8am3DHp+byqtx7I9H9Y
+aXLuWo82Cv4yERw0PXmIadfaMHM2aOH8EChB7mx/iAg+k3djiqrIqHvLNHAEoWw7
+bUgn1D0Xugyj4Ypaqx/hcibDjiYyKNlySQ7u5XVDAgMBAAGjggGpMIIBpTAfBgNV
+HSMEGDAWgBQhMMn7ANdOmNqHqirQpy6xQDGnTDAdBgNVHQ4EFgQUijXkNTq8EaGe
++/VPNGbVS6xMYmgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+ZgYDVR0gBF8wXTBbBgRVHSAAMFMwUQYIKwYBBQUHAgEWRWh0dHA6Ly93d3cubmV0
+d29ya3NvbHV0aW9ucy5jb20vbGVnYWwvU1NMLWxlZ2FsLXJlcG9zaXRvcnktZXYt
+Y3BzLmpzcDBSBgNVHR8ESzBJMEegRaBDhkFodHRwOi8vY3JsLm5ldHNvbHNzbC5j
+b20vTmV0d29ya1NvbHV0aW9uc0NlcnRpZmljYXRlQXV0aG9yaXR5LmNybDCBggYI
+KwYBBQUHAQEEdjB0MEsGCCsGAQUFBzAChj9odHRwOi8vY3J0LnVzZXJ0cnVzdC5j
+b20vTmV0d29ya1NvbHV0aW9uc0FkZFRydXN0RVZTZXJ2ZXJDQS5jcnQwJQYIKwYB
+BQUHMAGGGWh0dHA6Ly9vY3NwLm5ldHNvbHNzbC5jb20wDQYJKoZIhvcNAQEFBQAD
+ggEBADtBp7D2JBjlyHcOqAW86EhXzoEj/xeYaAGJxWmewqtFq3NMJclvdwVyEOue
+XnIM99N/vGMcsOVMRAGZH+He/HDjd+XY6aktld0Fz27Fx9ncL9FAfo/pR4uH2YEz
+pStMuS6k4ajMHGvPBDZaqqSgdDAbUSDHYblQGOS/K8P4pvqMiRYhmadaQ5kDbXTg
+i+qweI4gAdIpsozxeyoIsmJqMDZdXKc7Su73BzJHLfaIYgypJOBw36KmQgx7fSgF
+1wtt5YT78MmIs6nZAcOcmNzLg0fs+dGeoFxdpzFSuF2wkQNvHmrv4zYC4xpdMUqQ
+FhvXMwUw+wCqKOtfDecUViddfLQ=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert62[] = {
+ 0x30, 0x82, 0x04, 0xf0, 0x30, 0x82, 0x03, 0xd8, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x7a, 0xac, 0xa2, 0x1d, 0x53, 0x9d, 0x14, 0x54, 0x11,
+ 0x3c, 0x04, 0x5e, 0xd8, 0x35, 0xf8, 0xea, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x62,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+ 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x4e,
+ 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x31, 0x32, 0x36,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30,
+ 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, 0x59,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e,
+ 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1e, 0x4e,
+ 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x45, 0x56, 0x20, 0x53, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xd0, 0x35, 0x5c, 0xe2, 0xe7, 0x95, 0x1a, 0x98, 0x8f, 0xd8,
+ 0x4f, 0xd6, 0xd5, 0xdc, 0x7e, 0x5c, 0x82, 0xbf, 0x9f, 0xcc, 0x4b, 0xfa,
+ 0x3c, 0x4a, 0x81, 0xbc, 0xda, 0xc5, 0xa7, 0xe9, 0xad, 0x9a, 0x26, 0x8f,
+ 0xdc, 0x19, 0x2c, 0x63, 0x12, 0x3e, 0x56, 0xdf, 0x75, 0xe6, 0x48, 0xac,
+ 0xe3, 0x47, 0x90, 0x7f, 0x5f, 0x08, 0xf1, 0xa3, 0x80, 0xd1, 0xd0, 0xcd,
+ 0x25, 0xcd, 0x59, 0xf3, 0xad, 0x2e, 0xc3, 0xeb, 0x06, 0x09, 0xfe, 0x39,
+ 0x24, 0x39, 0xa2, 0xa1, 0xec, 0xc4, 0xc4, 0x9a, 0xd7, 0xa0, 0x08, 0x55,
+ 0xfe, 0xc8, 0xc5, 0x64, 0x2e, 0xfc, 0xe7, 0x06, 0x88, 0x95, 0xc1, 0x3e,
+ 0x31, 0x5a, 0x55, 0xf0, 0x1d, 0x98, 0x04, 0x94, 0xb4, 0x7f, 0x5e, 0xdc,
+ 0x90, 0xa9, 0xa1, 0x85, 0xc7, 0xaa, 0x12, 0xb9, 0x87, 0xd1, 0xa3, 0x71,
+ 0x11, 0x02, 0x6c, 0x7e, 0x9b, 0xc9, 0x39, 0xeb, 0xec, 0xb5, 0x58, 0x27,
+ 0x8b, 0xa3, 0x98, 0x11, 0xa0, 0xab, 0x83, 0xfb, 0x24, 0x30, 0x00, 0xae,
+ 0x02, 0x57, 0xfe, 0x80, 0xe2, 0xca, 0x8f, 0x48, 0x60, 0x63, 0x39, 0xdb,
+ 0xaf, 0x96, 0x74, 0x83, 0xbb, 0x3b, 0x6c, 0xef, 0xb3, 0x33, 0xc6, 0xa6,
+ 0xdc, 0x31, 0xe9, 0xf9, 0xbc, 0xaa, 0xb7, 0x1e, 0xc8, 0xf4, 0x7f, 0x58,
+ 0x69, 0x72, 0xee, 0x5a, 0x8f, 0x36, 0x0a, 0xfe, 0x32, 0x11, 0x1c, 0x34,
+ 0x3d, 0x79, 0x88, 0x69, 0xd7, 0xda, 0x30, 0x73, 0x36, 0x68, 0xe1, 0xfc,
+ 0x10, 0x28, 0x41, 0xee, 0x6c, 0x7f, 0x88, 0x08, 0x3e, 0x93, 0x77, 0x63,
+ 0x8a, 0xaa, 0xc8, 0xa8, 0x7b, 0xcb, 0x34, 0x70, 0x04, 0xa1, 0x6c, 0x3b,
+ 0x6d, 0x48, 0x27, 0xd4, 0x3d, 0x17, 0xba, 0x0c, 0xa3, 0xe1, 0x8a, 0x5a,
+ 0xab, 0x1f, 0xe1, 0x72, 0x26, 0xc3, 0x8e, 0x26, 0x32, 0x28, 0xd9, 0x72,
+ 0x49, 0x0e, 0xee, 0xe5, 0x75, 0x43, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0xa9, 0x30, 0x82, 0x01, 0xa5, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x21, 0x30, 0xc9, 0xfb,
+ 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1,
+ 0x40, 0x31, 0xa7, 0x4c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x8a, 0x35, 0xe4, 0x35, 0x3a, 0xbc, 0x11, 0xa1, 0x9e,
+ 0xfb, 0xf5, 0x4f, 0x34, 0x66, 0xd5, 0x4b, 0xac, 0x4c, 0x62, 0x68, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x66, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x5f, 0x30, 0x5d, 0x30, 0x5b,
+ 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x53, 0x30, 0x51, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x45, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6e, 0x65, 0x74,
+ 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e,
+ 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2f,
+ 0x53, 0x53, 0x4c, 0x2d, 0x6c, 0x65, 0x67, 0x61, 0x6c, 0x2d, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2d, 0x65, 0x76, 0x2d,
+ 0x63, 0x70, 0x73, 0x2e, 0x6a, 0x73, 0x70, 0x30, 0x52, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x4b, 0x30, 0x49, 0x30, 0x47, 0xa0, 0x45, 0xa0, 0x43,
+ 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2e, 0x6e, 0x65, 0x74, 0x73, 0x6f, 0x6c, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x6f,
+ 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x82, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x76, 0x30, 0x74,
+ 0x30, 0x4b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02,
+ 0x86, 0x3f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74,
+ 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x6f,
+ 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x45, 0x56, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43,
+ 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x6e, 0x65, 0x74, 0x73, 0x6f,
+ 0x6c, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x3b, 0x41, 0xa7, 0xb0, 0xf6, 0x24, 0x18, 0xe5,
+ 0xc8, 0x77, 0x0e, 0xa8, 0x05, 0xbc, 0xe8, 0x48, 0x57, 0xce, 0x81, 0x23,
+ 0xff, 0x17, 0x98, 0x68, 0x01, 0x89, 0xc5, 0x69, 0x9e, 0xc2, 0xab, 0x45,
+ 0xab, 0x73, 0x4c, 0x25, 0xc9, 0x6f, 0x77, 0x05, 0x72, 0x10, 0xeb, 0x9e,
+ 0x5e, 0x72, 0x0c, 0xf7, 0xd3, 0x7f, 0xbc, 0x63, 0x1c, 0xb0, 0xe5, 0x4c,
+ 0x44, 0x01, 0x99, 0x1f, 0xe1, 0xde, 0xfc, 0x70, 0xe3, 0x77, 0xe5, 0xd8,
+ 0xe9, 0xa9, 0x2d, 0x95, 0xdd, 0x05, 0xcf, 0x6e, 0xc5, 0xc7, 0xd9, 0xdc,
+ 0x2f, 0xd1, 0x40, 0x7e, 0x8f, 0xe9, 0x47, 0x8b, 0x87, 0xd9, 0x81, 0x33,
+ 0xa5, 0x2b, 0x4c, 0xb9, 0x2e, 0xa4, 0xe1, 0xa8, 0xcc, 0x1c, 0x6b, 0xcf,
+ 0x04, 0x36, 0x5a, 0xaa, 0xa4, 0xa0, 0x74, 0x30, 0x1b, 0x51, 0x20, 0xc7,
+ 0x61, 0xb9, 0x50, 0x18, 0xe4, 0xbf, 0x2b, 0xc3, 0xf8, 0xa6, 0xfa, 0x8c,
+ 0x89, 0x16, 0x21, 0x99, 0xa7, 0x5a, 0x43, 0x99, 0x03, 0x6d, 0x74, 0xe0,
+ 0x8b, 0xea, 0xb0, 0x78, 0x8e, 0x20, 0x01, 0xd2, 0x29, 0xb2, 0x8c, 0xf1,
+ 0x7b, 0x2a, 0x08, 0xb2, 0x62, 0x6a, 0x30, 0x36, 0x5d, 0x5c, 0xa7, 0x3b,
+ 0x4a, 0xee, 0xf7, 0x07, 0x32, 0x47, 0x2d, 0xf6, 0x88, 0x62, 0x0c, 0xa9,
+ 0x24, 0xe0, 0x70, 0xdf, 0xa2, 0xa6, 0x42, 0x0c, 0x7b, 0x7d, 0x28, 0x05,
+ 0xd7, 0x0b, 0x6d, 0xe5, 0x84, 0xfb, 0xf0, 0xc9, 0x88, 0xb3, 0xa9, 0xd9,
+ 0x01, 0xc3, 0x9c, 0x98, 0xdc, 0xcb, 0x83, 0x47, 0xec, 0xf9, 0xd1, 0x9e,
+ 0xa0, 0x5c, 0x5d, 0xa7, 0x31, 0x52, 0xb8, 0x5d, 0xb0, 0x91, 0x03, 0x6f,
+ 0x1e, 0x6a, 0xef, 0xe3, 0x36, 0x02, 0xe3, 0x1a, 0x5d, 0x31, 0x4a, 0x90,
+ 0x16, 0x1b, 0xd7, 0x33, 0x05, 0x30, 0xfb, 0x00, 0xaa, 0x28, 0xeb, 0x5f,
+ 0x0d, 0xe7, 0x14, 0x56, 0x27, 0x5d, 0x7c, 0xb4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 4b:75:57:82:69:39:0c:9b:e3:2f:12:ec:5f:6d:94:5e
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Feb 11 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+ 98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+ e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+ b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+ 86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+ da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+ d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+ 7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+ 9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+ 30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+ 77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+ 02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+ c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+ 00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+ 0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+ 2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+ 47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+ 26:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 4d:87:0d:50:30:f3:82:5d:c4:3f:d4:ef:ee:8d:48:e3:e7:bd:
+ 90:6b:c4:32:38:c6:5e:28:ab:5c:a5:ad:61:f9:8e:bb:85:14:
+ 39:21:51:5b:8e:8c:dc:17:92:80:2f:83:94:69:88:c1:be:27:
+ 8e:4f:9f:a9:83:d8:be:d7:87:92:71:a3:b6:fd:11:74:b8:95:
+ 81:28:20:77:0d:43:77:75:76:38:1d:4d:1b:2e:97:89:8c:0a:
+ 1b:66:16:52:d4:14:9a:6f:80:48:16:de:30:c0:42:68:ea:bf:
+ a2:ba:2a:44:4d:ac:89:e2:f3:cc:53:9b:e3:e6:1d:6e:4f:98:
+ 9f:d9:0e:51:50:86:e0:1a:34:32:24:80:7d:3a:87:f3:3c:e5:
+ 5a:4d:b7:8b:bd:0a:24:0d:ae:db:f4:8f:5c:d2:66:0c:82:1c:
+ 72:37:b6:d1:b9:d0:98:34:1b:27:6d:8b:5e:1e:40:73:18:fa:
+ a8:e4:c6:e8:90:c3:ab:19:e4:c1:a1:cd:4c:d4:3a:b6:88:c8:
+ f3:d0:65:61:3a:bf:18:f4:af:1c:56:a9:eb:97:38:d9:20:29:
+ 1f:3f:2a:29:47:9d:8a:0f:6a:12:81:44:02:21:d4:3b:3a:1a:
+ 2b:1e:40:43:7d:94:a0:69:0e:fc:2e:fb:52:f6:fd:2e:32:d8:
+ cb:6b:bd:eb
+-----BEGIN CERTIFICATE-----
+MIIE8TCCA9mgAwIBAgIQS3VXgmk5DJvjLxLsX22UXjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDIxMTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMScwJQYD
+VQQDEx5DT01PRE8gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQQIuLcuORG/dRwRtUBJjTqb/B5opdO4f7u4jO
+DeMvPwaW8KIpUJmu2zuhV7B0UXHN7UKRTUH+qcjYaoZ3RLtZZpdQXrTULHBEz9o3
+lUJpPDDEcbNS8CFNodi6OXwcnqMknfKDFpiqFnxDmxVbt640kf7UYiYYRpo/68H5
+8ZBX66x6DYvbcjBqZtXgRqNw3GjZ/wRIiXfeten7Z21B6bw5vTLZYgLxsag9bjec
+4i/i06Imi8a4VUOI4SM+pdIkOWpHqwDUobOpJf4NP6cdutNRwQuk2qw471VQJAVl
+RpM0Ty2NrcbUIRnSjsoFYXEHc0flihkSvQRNzk6cpUisuyb3AgMBAAGjggF0MIIB
+cDAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUC1jl
+i8ZMFTekQKkwqSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYI
+KwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0
+cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUA
+A4IBAQBNhw1QMPOCXcQ/1O/ujUjj572Qa8QyOMZeKKtcpa1h+Y67hRQ5IVFbjozc
+F5KAL4OUaYjBvieOT5+pg9i+14eScaO2/RF0uJWBKCB3DUN3dXY4HU0bLpeJjAob
+ZhZS1BSab4BIFt4wwEJo6r+iuipETayJ4vPMU5vj5h1uT5if2Q5RUIbgGjQyJIB9
+OofzPOVaTbeLvQokDa7b9I9c0mYMghxyN7bRudCYNBsnbYteHkBzGPqo5MbokMOr
+GeTBoc1M1Dq2iMjz0GVhOr8Y9K8cVqnrlzjZICkfPyopR52KD2oSgUQCIdQ7Ohor
+HkBDfZSgaQ78LvtS9v0uMtjLa73r
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert63[] = {
+ 0x30, 0x82, 0x04, 0xf1, 0x30, 0x82, 0x03, 0xd9, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x4b, 0x75, 0x57, 0x82, 0x69, 0x39, 0x0c, 0x9b, 0xe3,
+ 0x2f, 0x12, 0xec, 0x5f, 0x6d, 0x94, 0x5e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x31,
+ 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+ 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+ 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b, 0x8b,
+ 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98, 0xd3,
+ 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88, 0xce,
+ 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99, 0xae,
+ 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42, 0x91,
+ 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb, 0x59,
+ 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda, 0x37,
+ 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21, 0x4d,
+ 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2, 0x83,
+ 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae, 0x34,
+ 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1, 0xf9,
+ 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30, 0x6a,
+ 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04, 0x48,
+ 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc, 0x39,
+ 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37, 0x9c,
+ 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43, 0x88,
+ 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00, 0xd4,
+ 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3, 0x51,
+ 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05, 0x65,
+ 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19, 0xd2,
+ 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19, 0x12,
+ 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26, 0xf7,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x74, 0x30, 0x82, 0x01,
+ 0x70, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4,
+ 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58, 0xe5,
+ 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe,
+ 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+ 0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+ 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+ 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+ 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+ 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x01, 0x00, 0x4d, 0x87, 0x0d, 0x50, 0x30, 0xf3, 0x82,
+ 0x5d, 0xc4, 0x3f, 0xd4, 0xef, 0xee, 0x8d, 0x48, 0xe3, 0xe7, 0xbd, 0x90,
+ 0x6b, 0xc4, 0x32, 0x38, 0xc6, 0x5e, 0x28, 0xab, 0x5c, 0xa5, 0xad, 0x61,
+ 0xf9, 0x8e, 0xbb, 0x85, 0x14, 0x39, 0x21, 0x51, 0x5b, 0x8e, 0x8c, 0xdc,
+ 0x17, 0x92, 0x80, 0x2f, 0x83, 0x94, 0x69, 0x88, 0xc1, 0xbe, 0x27, 0x8e,
+ 0x4f, 0x9f, 0xa9, 0x83, 0xd8, 0xbe, 0xd7, 0x87, 0x92, 0x71, 0xa3, 0xb6,
+ 0xfd, 0x11, 0x74, 0xb8, 0x95, 0x81, 0x28, 0x20, 0x77, 0x0d, 0x43, 0x77,
+ 0x75, 0x76, 0x38, 0x1d, 0x4d, 0x1b, 0x2e, 0x97, 0x89, 0x8c, 0x0a, 0x1b,
+ 0x66, 0x16, 0x52, 0xd4, 0x14, 0x9a, 0x6f, 0x80, 0x48, 0x16, 0xde, 0x30,
+ 0xc0, 0x42, 0x68, 0xea, 0xbf, 0xa2, 0xba, 0x2a, 0x44, 0x4d, 0xac, 0x89,
+ 0xe2, 0xf3, 0xcc, 0x53, 0x9b, 0xe3, 0xe6, 0x1d, 0x6e, 0x4f, 0x98, 0x9f,
+ 0xd9, 0x0e, 0x51, 0x50, 0x86, 0xe0, 0x1a, 0x34, 0x32, 0x24, 0x80, 0x7d,
+ 0x3a, 0x87, 0xf3, 0x3c, 0xe5, 0x5a, 0x4d, 0xb7, 0x8b, 0xbd, 0x0a, 0x24,
+ 0x0d, 0xae, 0xdb, 0xf4, 0x8f, 0x5c, 0xd2, 0x66, 0x0c, 0x82, 0x1c, 0x72,
+ 0x37, 0xb6, 0xd1, 0xb9, 0xd0, 0x98, 0x34, 0x1b, 0x27, 0x6d, 0x8b, 0x5e,
+ 0x1e, 0x40, 0x73, 0x18, 0xfa, 0xa8, 0xe4, 0xc6, 0xe8, 0x90, 0xc3, 0xab,
+ 0x19, 0xe4, 0xc1, 0xa1, 0xcd, 0x4c, 0xd4, 0x3a, 0xb6, 0x88, 0xc8, 0xf3,
+ 0xd0, 0x65, 0x61, 0x3a, 0xbf, 0x18, 0xf4, 0xaf, 0x1c, 0x56, 0xa9, 0xeb,
+ 0x97, 0x38, 0xd9, 0x20, 0x29, 0x1f, 0x3f, 0x2a, 0x29, 0x47, 0x9d, 0x8a,
+ 0x0f, 0x6a, 0x12, 0x81, 0x44, 0x02, 0x21, 0xd4, 0x3b, 0x3a, 0x1a, 0x2b,
+ 0x1e, 0x40, 0x43, 0x7d, 0x94, 0xa0, 0x69, 0x0e, 0xfc, 0x2e, 0xfb, 0x52,
+ 0xf6, 0xfd, 0x2e, 0x32, 0xd8, 0xcb, 0x6b, 0xbd, 0xeb,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 6f:25:dc:15:af:df:5e:a3:08:56:0c:3b:7a:4f:c7:f8
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: May 30 10:48:38 2000 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d0:40:8b:8b:72:e3:91:1b:f7:51:c1:1b:54:04:
+ 98:d3:a9:bf:c1:e6:8a:5d:3b:87:fb:bb:88:ce:0d:
+ e3:2f:3f:06:96:f0:a2:29:50:99:ae:db:3b:a1:57:
+ b0:74:51:71:cd:ed:42:91:4d:41:fe:a9:c8:d8:6a:
+ 86:77:44:bb:59:66:97:50:5e:b4:d4:2c:70:44:cf:
+ da:37:95:42:69:3c:30:c4:71:b3:52:f0:21:4d:a1:
+ d8:ba:39:7c:1c:9e:a3:24:9d:f2:83:16:98:aa:16:
+ 7c:43:9b:15:5b:b7:ae:34:91:fe:d4:62:26:18:46:
+ 9a:3f:eb:c1:f9:f1:90:57:eb:ac:7a:0d:8b:db:72:
+ 30:6a:66:d5:e0:46:a3:70:dc:68:d9:ff:04:48:89:
+ 77:de:b5:e9:fb:67:6d:41:e9:bc:39:bd:32:d9:62:
+ 02:f1:b1:a8:3d:6e:37:9c:e2:2f:e2:d3:a2:26:8b:
+ c6:b8:55:43:88:e1:23:3e:a5:d2:24:39:6a:47:ab:
+ 00:d4:a1:b3:a9:25:fe:0d:3f:a7:1d:ba:d3:51:c1:
+ 0b:a4:da:ac:38:ef:55:50:24:05:65:46:93:34:4f:
+ 2d:8d:ad:c6:d4:21:19:d2:8e:ca:05:61:71:07:73:
+ 47:e5:8a:19:12:bd:04:4d:ce:4e:9c:a5:48:ac:bb:
+ 26:f7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 07:60:93:99:aa:ce:d0:d3:47:d0:37:33:de:3f:64:b7:e5:2e:
+ a3:25:0c:d5:33:1d:0d:8d:ab:f6:7e:46:7b:59:06:92:e3:82:
+ c4:e7:f5:f6:f3:d9:05:cf:49:34:2d:37:5f:f4:25:c7:f0:fb:
+ 6b:23:77:f1:f1:40:d7:4c:bb:49:45:31:dd:00:28:67:b7:29:
+ 4c:75:a8:1f:79:31:c9:36:37:0f:ca:35:4f:8c:f1:7e:de:fc:
+ 46:ab:bf:68:9b:70:23:30:2e:b7:c5:5c:7b:8a:fb:18:13:79:
+ 4b:92:42:8c:dc:2c:ab:6c:22:b7:28:53:b3:1a:4a:ce:1b:fb:
+ 28:0e:b7:3a:a4:da:0d:f7:40:32:4f:df:6f:bb:01:50:fc:87:
+ d3:76:d9:fc:fb:b6:84:03:ca:c9:36:18:f7:dd:6c:db:bb:ba:
+ 81:1c:a6:ad:fe:28:f9:cf:b9:a2:71:5d:19:05:ea:4a:46:dc:
+ 73:41:ef:89:94:42:b1:43:88:6f:35:17:af:1e:60:83:ac:7a:
+ 8c:10:7b:9f:c9:f6:83:6d:9e:fa:88:ee:3e:dd:ee:9e:b0:bf:
+ e0:6a:b9:d0:9f:07:b2:09:13:9a:f5:a4:e5:c8:5b:79:a7:47:
+ 35:33:68:e5:55:9e:aa:5b:cb:30:0b:9d:c7:0f:bf:68:44:81:
+ 97:8b:51:4a
+-----BEGIN CERTIFICATE-----
+MIIE8TCCA9mgAwIBAgIQbyXcFa/fXqMIVgw7ek/H+DANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYExCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMScwJQYD
+VQQDEx5DT01PRE8gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDQQIuLcuORG/dRwRtUBJjTqb/B5opdO4f7u4jO
+DeMvPwaW8KIpUJmu2zuhV7B0UXHN7UKRTUH+qcjYaoZ3RLtZZpdQXrTULHBEz9o3
+lUJpPDDEcbNS8CFNodi6OXwcnqMknfKDFpiqFnxDmxVbt640kf7UYiYYRpo/68H5
+8ZBX66x6DYvbcjBqZtXgRqNw3GjZ/wRIiXfeten7Z21B6bw5vTLZYgLxsag9bjec
+4i/i06Imi8a4VUOI4SM+pdIkOWpHqwDUobOpJf4NP6cdutNRwQuk2qw471VQJAVl
+RpM0Ty2NrcbUIRnSjsoFYXEHc0flihkSvQRNzk6cpUisuyb3AgMBAAGjggF0MIIB
+cDAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQUC1jl
+i8ZMFTekQKkwqSG+RzZaVv8wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9j
+cmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDCBswYI
+KwYBBQUHAQEEgaYwgaMwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0
+LmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LnA3YzA5BggrBgEFBQcwAoYtaHR0
+cDovL2NydC51c2VydHJ1c3QuY29tL0FkZFRydXN0VVROU0dDQ0EuY3J0MCUGCCsG
+AQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBBQUA
+A4IBAQAHYJOZqs7Q00fQNzPeP2S35S6jJQzVMx0Njav2fkZ7WQaS44LE5/X289kF
+z0k0LTdf9CXH8PtrI3fx8UDXTLtJRTHdAChntylMdagfeTHJNjcPyjVPjPF+3vxG
+q79om3AjMC63xVx7ivsYE3lLkkKM3CyrbCK3KFOzGkrOG/soDrc6pNoN90AyT99v
+uwFQ/IfTdtn8+7aEA8rJNhj33Wzbu7qBHKat/ij5z7micV0ZBepKRtxzQe+JlEKx
+Q4hvNRevHmCDrHqMEHufyfaDbZ76iO4+3e6esL/garnQnweyCROa9aTlyFt5p0c1
+M2jlVZ6qW8swC53HD79oRIGXi1FK
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert64[] = {
+ 0x30, 0x82, 0x04, 0xf1, 0x30, 0x82, 0x03, 0xd9, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x6f, 0x25, 0xdc, 0x15, 0xaf, 0xdf, 0x5e, 0xa3, 0x08,
+ 0x56, 0x0c, 0x3b, 0x7a, 0x4f, 0xc7, 0xf8, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x35, 0x33,
+ 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+ 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+ 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x40, 0x8b, 0x8b,
+ 0x72, 0xe3, 0x91, 0x1b, 0xf7, 0x51, 0xc1, 0x1b, 0x54, 0x04, 0x98, 0xd3,
+ 0xa9, 0xbf, 0xc1, 0xe6, 0x8a, 0x5d, 0x3b, 0x87, 0xfb, 0xbb, 0x88, 0xce,
+ 0x0d, 0xe3, 0x2f, 0x3f, 0x06, 0x96, 0xf0, 0xa2, 0x29, 0x50, 0x99, 0xae,
+ 0xdb, 0x3b, 0xa1, 0x57, 0xb0, 0x74, 0x51, 0x71, 0xcd, 0xed, 0x42, 0x91,
+ 0x4d, 0x41, 0xfe, 0xa9, 0xc8, 0xd8, 0x6a, 0x86, 0x77, 0x44, 0xbb, 0x59,
+ 0x66, 0x97, 0x50, 0x5e, 0xb4, 0xd4, 0x2c, 0x70, 0x44, 0xcf, 0xda, 0x37,
+ 0x95, 0x42, 0x69, 0x3c, 0x30, 0xc4, 0x71, 0xb3, 0x52, 0xf0, 0x21, 0x4d,
+ 0xa1, 0xd8, 0xba, 0x39, 0x7c, 0x1c, 0x9e, 0xa3, 0x24, 0x9d, 0xf2, 0x83,
+ 0x16, 0x98, 0xaa, 0x16, 0x7c, 0x43, 0x9b, 0x15, 0x5b, 0xb7, 0xae, 0x34,
+ 0x91, 0xfe, 0xd4, 0x62, 0x26, 0x18, 0x46, 0x9a, 0x3f, 0xeb, 0xc1, 0xf9,
+ 0xf1, 0x90, 0x57, 0xeb, 0xac, 0x7a, 0x0d, 0x8b, 0xdb, 0x72, 0x30, 0x6a,
+ 0x66, 0xd5, 0xe0, 0x46, 0xa3, 0x70, 0xdc, 0x68, 0xd9, 0xff, 0x04, 0x48,
+ 0x89, 0x77, 0xde, 0xb5, 0xe9, 0xfb, 0x67, 0x6d, 0x41, 0xe9, 0xbc, 0x39,
+ 0xbd, 0x32, 0xd9, 0x62, 0x02, 0xf1, 0xb1, 0xa8, 0x3d, 0x6e, 0x37, 0x9c,
+ 0xe2, 0x2f, 0xe2, 0xd3, 0xa2, 0x26, 0x8b, 0xc6, 0xb8, 0x55, 0x43, 0x88,
+ 0xe1, 0x23, 0x3e, 0xa5, 0xd2, 0x24, 0x39, 0x6a, 0x47, 0xab, 0x00, 0xd4,
+ 0xa1, 0xb3, 0xa9, 0x25, 0xfe, 0x0d, 0x3f, 0xa7, 0x1d, 0xba, 0xd3, 0x51,
+ 0xc1, 0x0b, 0xa4, 0xda, 0xac, 0x38, 0xef, 0x55, 0x50, 0x24, 0x05, 0x65,
+ 0x46, 0x93, 0x34, 0x4f, 0x2d, 0x8d, 0xad, 0xc6, 0xd4, 0x21, 0x19, 0xd2,
+ 0x8e, 0xca, 0x05, 0x61, 0x71, 0x07, 0x73, 0x47, 0xe5, 0x8a, 0x19, 0x12,
+ 0xbd, 0x04, 0x4d, 0xce, 0x4e, 0x9c, 0xa5, 0x48, 0xac, 0xbb, 0x26, 0xf7,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x74, 0x30, 0x82, 0x01,
+ 0x70, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4,
+ 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x0b, 0x58, 0xe5,
+ 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe,
+ 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01,
+ 0x01, 0xff, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30,
+ 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+ 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30,
+ 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43,
+ 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x01, 0x00, 0x07, 0x60, 0x93, 0x99, 0xaa, 0xce, 0xd0,
+ 0xd3, 0x47, 0xd0, 0x37, 0x33, 0xde, 0x3f, 0x64, 0xb7, 0xe5, 0x2e, 0xa3,
+ 0x25, 0x0c, 0xd5, 0x33, 0x1d, 0x0d, 0x8d, 0xab, 0xf6, 0x7e, 0x46, 0x7b,
+ 0x59, 0x06, 0x92, 0xe3, 0x82, 0xc4, 0xe7, 0xf5, 0xf6, 0xf3, 0xd9, 0x05,
+ 0xcf, 0x49, 0x34, 0x2d, 0x37, 0x5f, 0xf4, 0x25, 0xc7, 0xf0, 0xfb, 0x6b,
+ 0x23, 0x77, 0xf1, 0xf1, 0x40, 0xd7, 0x4c, 0xbb, 0x49, 0x45, 0x31, 0xdd,
+ 0x00, 0x28, 0x67, 0xb7, 0x29, 0x4c, 0x75, 0xa8, 0x1f, 0x79, 0x31, 0xc9,
+ 0x36, 0x37, 0x0f, 0xca, 0x35, 0x4f, 0x8c, 0xf1, 0x7e, 0xde, 0xfc, 0x46,
+ 0xab, 0xbf, 0x68, 0x9b, 0x70, 0x23, 0x30, 0x2e, 0xb7, 0xc5, 0x5c, 0x7b,
+ 0x8a, 0xfb, 0x18, 0x13, 0x79, 0x4b, 0x92, 0x42, 0x8c, 0xdc, 0x2c, 0xab,
+ 0x6c, 0x22, 0xb7, 0x28, 0x53, 0xb3, 0x1a, 0x4a, 0xce, 0x1b, 0xfb, 0x28,
+ 0x0e, 0xb7, 0x3a, 0xa4, 0xda, 0x0d, 0xf7, 0x40, 0x32, 0x4f, 0xdf, 0x6f,
+ 0xbb, 0x01, 0x50, 0xfc, 0x87, 0xd3, 0x76, 0xd9, 0xfc, 0xfb, 0xb6, 0x84,
+ 0x03, 0xca, 0xc9, 0x36, 0x18, 0xf7, 0xdd, 0x6c, 0xdb, 0xbb, 0xba, 0x81,
+ 0x1c, 0xa6, 0xad, 0xfe, 0x28, 0xf9, 0xcf, 0xb9, 0xa2, 0x71, 0x5d, 0x19,
+ 0x05, 0xea, 0x4a, 0x46, 0xdc, 0x73, 0x41, 0xef, 0x89, 0x94, 0x42, 0xb1,
+ 0x43, 0x88, 0x6f, 0x35, 0x17, 0xaf, 0x1e, 0x60, 0x83, 0xac, 0x7a, 0x8c,
+ 0x10, 0x7b, 0x9f, 0xc9, 0xf6, 0x83, 0x6d, 0x9e, 0xfa, 0x88, 0xee, 0x3e,
+ 0xdd, 0xee, 0x9e, 0xb0, 0xbf, 0xe0, 0x6a, 0xb9, 0xd0, 0x9f, 0x07, 0xb2,
+ 0x09, 0x13, 0x9a, 0xf5, 0xa4, 0xe5, 0xc8, 0x5b, 0x79, 0xa7, 0x47, 0x35,
+ 0x33, 0x68, 0xe5, 0x55, 0x9e, 0xaa, 0x5b, 0xcb, 0x30, 0x0b, 0x9d, 0xc7,
+ 0x0f, 0xbf, 0x68, 0x44, 0x81, 0x97, 0x8b, 0x51, 0x4a,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 946072060 (0x3863e9fc)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+ Validity
+ Not Before: Dec 10 20:43:54 2009 GMT
+ Not After : Dec 10 21:13:54 2019 GMT
+ Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1C
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:97:a3:2d:3c:9e:de:05:da:13:c2:11:8d:9d:8e:
+ e3:7f:c7:4b:7e:5a:9f:b3:ff:62:ab:73:c8:28:6b:
+ ba:10:64:82:87:13:cd:57:18:ff:28:ce:c0:e6:0e:
+ 06:91:50:29:83:d1:f2:c3:2a:db:d8:db:4e:04:cc:
+ 00:eb:8b:b6:96:dc:bc:aa:fa:52:77:04:c1:db:19:
+ e4:ae:9c:fd:3c:8b:03:ef:4d:bc:1a:03:65:f9:c1:
+ b1:3f:72:86:f2:38:aa:19:ae:10:88:78:28:da:75:
+ c3:3d:02:82:02:9c:b9:c1:65:77:76:24:4c:98:f7:
+ 6d:31:38:fb:db:fe:db:37:02:76:a1:18:97:a6:cc:
+ de:20:09:49:36:24:69:42:f6:e4:37:62:f1:59:6d:
+ a9:3c:ed:34:9c:a3:8e:db:dc:3a:d7:f7:0a:6f:ef:
+ 2e:d8:d5:93:5a:7a:ed:08:49:68:e2:41:e3:5a:90:
+ c1:86:55:fc:51:43:9d:e0:b2:c4:67:b4:cb:32:31:
+ 25:f0:54:9f:4b:d1:6f:db:d4:dd:fc:af:5e:6c:78:
+ 90:95:de:ca:3a:48:b9:79:3c:9b:19:d6:75:05:a0:
+ f9:88:d7:c1:e8:a5:09:e4:1a:15:dc:87:23:aa:b2:
+ 75:8c:63:25:87:d8:f8:3d:a6:c2:cc:66:ff:a5:66:
+ 68:55
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/2048ca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/rpa
+
+ X509v3 Subject Key Identifier:
+ 1E:F1:AB:89:06:F8:49:0F:01:33:77:EE:14:7A:EE:19:7C:93:28:4D
+ X509v3 Authority Key Identifier:
+ keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 07:f6:5f:82:84:7f:80:40:c7:90:34:46:42:24:03:ce:2f:ab:
+ ba:83:9e:25:73:0d:ed:ac:05:69:c6:87:ed:a3:5c:f2:57:c1:
+ b1:49:76:9a:4d:f2:3f:dd:e4:0e:fe:0b:3e:b9:98:d9:32:95:
+ 1d:32:f4:01:ee:9c:c8:c8:e5:3f:e0:53:76:62:fc:dd:ab:6d:
+ 3d:94:90:f2:c0:b3:3c:98:27:36:5e:28:97:22:fc:1b:40:d3:
+ 2b:0d:ad:b5:57:6d:df:0f:e3:4b:ef:73:02:10:65:fa:1b:d0:
+ ac:31:d5:e3:0f:e8:ba:32:30:83:ee:4a:d0:bf:df:22:90:7a:
+ be:ec:3a:1b:c4:49:04:1d:f1:ae:80:77:3c:42:08:db:a7:3b:
+ 28:a6:80:01:03:e6:39:a3:eb:df:80:59:1b:f3:2c:be:dc:72:
+ 44:79:a0:6c:07:a5:6d:4d:44:8e:42:68:ca:94:7c:2e:36:ba:
+ 85:9e:cd:aa:c4:5e:3c:54:be:fe:2f:ea:69:9d:1c:1e:29:9b:
+ 96:d8:c8:fe:51:90:f1:24:a6:90:06:b3:f0:29:a2:ff:78:2e:
+ 77:5c:45:21:d9:44:00:31:f3:be:32:4f:f5:0a:32:0d:fc:fc:
+ ba:16:76:56:b2:d6:48:92:f2:8b:a6:3e:b7:ac:5c:69:ea:0b:
+ 3f:66:45:b9
+-----BEGIN CERTIFICATE-----
+MIIE8jCCA9qgAwIBAgIEOGPp/DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0wOTEyMTAyMDQzNTRaFw0xOTEy
+MTAyMTEzNTRaMIGxMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5j
+LjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L3JwYSBpcyBpbmNvcnBvcmF0ZWQg
+YnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwOSBFbnRydXN0LCBJbmMuMS4w
+LAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFDMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6MtPJ7eBdoTwhGNnY7jf8dL
+flqfs/9iq3PIKGu6EGSChxPNVxj/KM7A5g4GkVApg9Hywyrb2NtOBMwA64u2lty8
+qvpSdwTB2xnkrpz9PIsD7028GgNl+cGxP3KG8jiqGa4QiHgo2nXDPQKCApy5wWV3
+diRMmPdtMTj72/7bNwJ2oRiXpszeIAlJNiRpQvbkN2LxWW2pPO00nKOO29w61/cK
+b+8u2NWTWnrtCElo4kHjWpDBhlX8UUOd4LLEZ7TLMjEl8FSfS9Fv29Td/K9ebHiQ
+ld7KOki5eTybGdZ1BaD5iNfB6KUJ5BoV3IcjqrJ1jGMlh9j4PabCzGb/pWZoVQID
+AQABo4IBCzCCAQcwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wMwYI
+KwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0Lm5l
+dDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmVudHJ1c3QubmV0LzIwNDhj
+YS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93
+d3cuZW50cnVzdC5uZXQvcnBhMB0GA1UdDgQWBBQe8auJBvhJDwEzd+4Ueu4ZfJMo
+TTAfBgNVHSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDANBgkqhkiG9w0BAQUF
+AAOCAQEAB/ZfgoR/gEDHkDRGQiQDzi+ruoOeJXMN7awFacaH7aNc8lfBsUl2mk3y
+P93kDv4LPrmY2TKVHTL0Ae6cyMjlP+BTdmL83attPZSQ8sCzPJgnNl4olyL8G0DT
+Kw2ttVdt3w/jS+9zAhBl+hvQrDHV4w/oujIwg+5K0L/fIpB6vuw6G8RJBB3xroB3
+PEII26c7KKaAAQPmOaPr34BZG/MsvtxyRHmgbAelbU1EjkJoypR8Lja6hZ7NqsRe
+PFS+/i/qaZ0cHimbltjI/lGQ8SSmkAaz8Cmi/3gud1xFIdlEADHzvjJP9QoyDfz8
+uhZ2VrLWSJLyi6Y+t6xcaeoLP2ZFuQ==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert65[] = {
+ 0x30, 0x82, 0x04, 0xf2, 0x30, 0x82, 0x03, 0xda, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x38, 0x63, 0xe9, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+ 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+ 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+ 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+ 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+ 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+ 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+ 0x29, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32,
+ 0x30, 0x34, 0x33, 0x35, 0x34, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32,
+ 0x31, 0x30, 0x32, 0x31, 0x31, 0x33, 0x35, 0x34, 0x5a, 0x30, 0x81, 0xb1,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+ 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+ 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69,
+ 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20,
+ 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30,
+ 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x43, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde,
+ 0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b,
+ 0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba,
+ 0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0,
+ 0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb,
+ 0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc,
+ 0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd,
+ 0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1,
+ 0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28,
+ 0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77,
+ 0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb,
+ 0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49,
+ 0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9,
+ 0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a,
+ 0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68,
+ 0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d,
+ 0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f,
+ 0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90,
+ 0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75,
+ 0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15,
+ 0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8,
+ 0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0b, 0x30, 0x82, 0x01, 0x07, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25,
+ 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29,
+ 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30, 0x34, 0x38, 0x63,
+ 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+ 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x1e, 0xf1, 0xab, 0x89, 0x06, 0xf8, 0x49,
+ 0x0f, 0x01, 0x33, 0x77, 0xee, 0x14, 0x7a, 0xee, 0x19, 0x7c, 0x93, 0x28,
+ 0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9,
+ 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9, 0x70, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x07, 0xf6, 0x5f, 0x82, 0x84, 0x7f,
+ 0x80, 0x40, 0xc7, 0x90, 0x34, 0x46, 0x42, 0x24, 0x03, 0xce, 0x2f, 0xab,
+ 0xba, 0x83, 0x9e, 0x25, 0x73, 0x0d, 0xed, 0xac, 0x05, 0x69, 0xc6, 0x87,
+ 0xed, 0xa3, 0x5c, 0xf2, 0x57, 0xc1, 0xb1, 0x49, 0x76, 0x9a, 0x4d, 0xf2,
+ 0x3f, 0xdd, 0xe4, 0x0e, 0xfe, 0x0b, 0x3e, 0xb9, 0x98, 0xd9, 0x32, 0x95,
+ 0x1d, 0x32, 0xf4, 0x01, 0xee, 0x9c, 0xc8, 0xc8, 0xe5, 0x3f, 0xe0, 0x53,
+ 0x76, 0x62, 0xfc, 0xdd, 0xab, 0x6d, 0x3d, 0x94, 0x90, 0xf2, 0xc0, 0xb3,
+ 0x3c, 0x98, 0x27, 0x36, 0x5e, 0x28, 0x97, 0x22, 0xfc, 0x1b, 0x40, 0xd3,
+ 0x2b, 0x0d, 0xad, 0xb5, 0x57, 0x6d, 0xdf, 0x0f, 0xe3, 0x4b, 0xef, 0x73,
+ 0x02, 0x10, 0x65, 0xfa, 0x1b, 0xd0, 0xac, 0x31, 0xd5, 0xe3, 0x0f, 0xe8,
+ 0xba, 0x32, 0x30, 0x83, 0xee, 0x4a, 0xd0, 0xbf, 0xdf, 0x22, 0x90, 0x7a,
+ 0xbe, 0xec, 0x3a, 0x1b, 0xc4, 0x49, 0x04, 0x1d, 0xf1, 0xae, 0x80, 0x77,
+ 0x3c, 0x42, 0x08, 0xdb, 0xa7, 0x3b, 0x28, 0xa6, 0x80, 0x01, 0x03, 0xe6,
+ 0x39, 0xa3, 0xeb, 0xdf, 0x80, 0x59, 0x1b, 0xf3, 0x2c, 0xbe, 0xdc, 0x72,
+ 0x44, 0x79, 0xa0, 0x6c, 0x07, 0xa5, 0x6d, 0x4d, 0x44, 0x8e, 0x42, 0x68,
+ 0xca, 0x94, 0x7c, 0x2e, 0x36, 0xba, 0x85, 0x9e, 0xcd, 0xaa, 0xc4, 0x5e,
+ 0x3c, 0x54, 0xbe, 0xfe, 0x2f, 0xea, 0x69, 0x9d, 0x1c, 0x1e, 0x29, 0x9b,
+ 0x96, 0xd8, 0xc8, 0xfe, 0x51, 0x90, 0xf1, 0x24, 0xa6, 0x90, 0x06, 0xb3,
+ 0xf0, 0x29, 0xa2, 0xff, 0x78, 0x2e, 0x77, 0x5c, 0x45, 0x21, 0xd9, 0x44,
+ 0x00, 0x31, 0xf3, 0xbe, 0x32, 0x4f, 0xf5, 0x0a, 0x32, 0x0d, 0xfc, 0xfc,
+ 0xba, 0x16, 0x76, 0x56, 0xb2, 0xd6, 0x48, 0x92, 0xf2, 0x8b, 0xa6, 0x3e,
+ 0xb7, 0xac, 0x5c, 0x69, 0xea, 0x0b, 0x3f, 0x66, 0x45, 0xb9,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1276021817 (0x4c0e8c39)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+ Validity
+ Not Before: Nov 11 15:40:40 2011 GMT
+ Not After : Nov 12 02:51:17 2021 GMT
+ Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1C
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:97:a3:2d:3c:9e:de:05:da:13:c2:11:8d:9d:8e:
+ e3:7f:c7:4b:7e:5a:9f:b3:ff:62:ab:73:c8:28:6b:
+ ba:10:64:82:87:13:cd:57:18:ff:28:ce:c0:e6:0e:
+ 06:91:50:29:83:d1:f2:c3:2a:db:d8:db:4e:04:cc:
+ 00:eb:8b:b6:96:dc:bc:aa:fa:52:77:04:c1:db:19:
+ e4:ae:9c:fd:3c:8b:03:ef:4d:bc:1a:03:65:f9:c1:
+ b1:3f:72:86:f2:38:aa:19:ae:10:88:78:28:da:75:
+ c3:3d:02:82:02:9c:b9:c1:65:77:76:24:4c:98:f7:
+ 6d:31:38:fb:db:fe:db:37:02:76:a1:18:97:a6:cc:
+ de:20:09:49:36:24:69:42:f6:e4:37:62:f1:59:6d:
+ a9:3c:ed:34:9c:a3:8e:db:dc:3a:d7:f7:0a:6f:ef:
+ 2e:d8:d5:93:5a:7a:ed:08:49:68:e2:41:e3:5a:90:
+ c1:86:55:fc:51:43:9d:e0:b2:c4:67:b4:cb:32:31:
+ 25:f0:54:9f:4b:d1:6f:db:d4:dd:fc:af:5e:6c:78:
+ 90:95:de:ca:3a:48:b9:79:3c:9b:19:d6:75:05:a0:
+ f9:88:d7:c1:e8:a5:09:e4:1a:15:dc:87:23:aa:b2:
+ 75:8c:63:25:87:d8:f8:3d:a6:c2:cc:66:ff:a5:66:
+ 68:55
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/2048ca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/rpa
+
+ X509v3 Subject Key Identifier:
+ 1E:F1:AB:89:06:F8:49:0F:01:33:77:EE:14:7A:EE:19:7C:93:28:4D
+ X509v3 Authority Key Identifier:
+ keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 40:9a:87:7e:88:d4:cc:26:a7:4b:fa:78:4a:20:d5:f9:a2:36:
+ 21:bb:ee:5b:a0:4f:44:8d:cf:aa:f9:97:17:96:84:a9:c8:67:
+ 9b:bb:e6:10:de:79:d6:56:6a:a4:78:14:49:d9:7c:ed:30:5e:
+ 69:ea:6d:24:46:5a:88:34:3d:26:27:cf:69:41:84:1c:04:da:
+ 19:38:2e:db:89:41:39:7e:65:1f:9d:5a:3a:cc:e1:0c:4c:37:
+ a1:ce:60:93:a8:b5:8c:ca:3f:ba:2b:5d:4c:1b:81:89:7a:ca:
+ 36:30:9c:ff:84:e3:fe:3a:f1:f7:79:71:c9:b5:d3:33:03:ca:
+ 77:ce:b0:ba:29:d2:34:5d:73:ff:a4:fd:f2:25:b8:35:45:79:
+ 7a:1f:97:ae:c9:be:0a:68:84:99:74:39:a8:4e:7a:26:f5:cd:
+ de:25:e2:37:85:65:07:a7:ca:c5:05:b7:13:38:0d:2d:f0:6d:
+ 19:ce:de:99:61:27:ee:45:6e:c7:39:ff:f6:c5:8b:e0:cb:7c:
+ 8a:1e:d5:7a:07:31:2a:52:5c:3a:50:19:38:a9:44:fa:3c:a8:
+ cf:ef:79:9d:6a:d9:e5:2e:a1:8f:29:28:d7:ec:aa:c1:fb:26:
+ e6:9f:46:24:a6:b1:07:cd:b9:0c:e8:0d:82:16:00:1d:96:92:
+ fc:a6:08:a0
+-----BEGIN CERTIFICATE-----
+MIIE9TCCA92gAwIBAgIETA6MOTANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0xMTExMTExNTQwNDBaFw0yMTEx
+MTIwMjUxMTdaMIGxMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5j
+LjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L3JwYSBpcyBpbmNvcnBvcmF0ZWQg
+YnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwOSBFbnRydXN0LCBJbmMuMS4w
+LAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFDMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl6MtPJ7eBdoTwhGNnY7jf8dL
+flqfs/9iq3PIKGu6EGSChxPNVxj/KM7A5g4GkVApg9Hywyrb2NtOBMwA64u2lty8
+qvpSdwTB2xnkrpz9PIsD7028GgNl+cGxP3KG8jiqGa4QiHgo2nXDPQKCApy5wWV3
+diRMmPdtMTj72/7bNwJ2oRiXpszeIAlJNiRpQvbkN2LxWW2pPO00nKOO29w61/cK
+b+8u2NWTWnrtCElo4kHjWpDBhlX8UUOd4LLEZ7TLMjEl8FSfS9Fv29Td/K9ebHiQ
+ld7KOki5eTybGdZ1BaD5iNfB6KUJ5BoV3IcjqrJ1jGMlh9j4PabCzGb/pWZoVQID
+AQABo4IBDjCCAQowDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
+MwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5lbnRydXN0
+Lm5ldDAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmVudHJ1c3QubmV0LzIw
+NDhjYS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6
+Ly93d3cuZW50cnVzdC5uZXQvcnBhMB0GA1UdDgQWBBQe8auJBvhJDwEzd+4Ueu4Z
+fJMoTTAfBgNVHSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDANBgkqhkiG9w0B
+AQUFAAOCAQEAQJqHfojUzCanS/p4SiDV+aI2IbvuW6BPRI3PqvmXF5aEqchnm7vm
+EN551lZqpHgUSdl87TBeaeptJEZaiDQ9JifPaUGEHATaGTgu24lBOX5lH51aOszh
+DEw3oc5gk6i1jMo/uitdTBuBiXrKNjCc/4Tj/jrx93lxybXTMwPKd86wuinSNF1z
+/6T98iW4NUV5eh+Xrsm+CmiEmXQ5qE56JvXN3iXiN4VlB6fKxQW3EzgNLfBtGc7e
+mWEn7kVuxzn/9sWL4Mt8ih7VegcxKlJcOlAZOKlE+jyoz+95nWrZ5S6hjyko1+yq
+wfsm5p9GJKaxB825DOgNghYAHZaS/KYIoA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert66[] = {
+ 0x30, 0x82, 0x04, 0xf5, 0x30, 0x82, 0x03, 0xdd, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x4c, 0x0e, 0x8c, 0x39, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+ 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+ 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+ 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+ 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+ 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+ 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+ 0x29, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x35, 0x34, 0x30, 0x34, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31,
+ 0x31, 0x32, 0x30, 0x32, 0x35, 0x31, 0x31, 0x37, 0x5a, 0x30, 0x81, 0xb1,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d,
+ 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+ 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69,
+ 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20,
+ 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30,
+ 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x4c, 0x31, 0x43, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x97, 0xa3, 0x2d, 0x3c, 0x9e, 0xde,
+ 0x05, 0xda, 0x13, 0xc2, 0x11, 0x8d, 0x9d, 0x8e, 0xe3, 0x7f, 0xc7, 0x4b,
+ 0x7e, 0x5a, 0x9f, 0xb3, 0xff, 0x62, 0xab, 0x73, 0xc8, 0x28, 0x6b, 0xba,
+ 0x10, 0x64, 0x82, 0x87, 0x13, 0xcd, 0x57, 0x18, 0xff, 0x28, 0xce, 0xc0,
+ 0xe6, 0x0e, 0x06, 0x91, 0x50, 0x29, 0x83, 0xd1, 0xf2, 0xc3, 0x2a, 0xdb,
+ 0xd8, 0xdb, 0x4e, 0x04, 0xcc, 0x00, 0xeb, 0x8b, 0xb6, 0x96, 0xdc, 0xbc,
+ 0xaa, 0xfa, 0x52, 0x77, 0x04, 0xc1, 0xdb, 0x19, 0xe4, 0xae, 0x9c, 0xfd,
+ 0x3c, 0x8b, 0x03, 0xef, 0x4d, 0xbc, 0x1a, 0x03, 0x65, 0xf9, 0xc1, 0xb1,
+ 0x3f, 0x72, 0x86, 0xf2, 0x38, 0xaa, 0x19, 0xae, 0x10, 0x88, 0x78, 0x28,
+ 0xda, 0x75, 0xc3, 0x3d, 0x02, 0x82, 0x02, 0x9c, 0xb9, 0xc1, 0x65, 0x77,
+ 0x76, 0x24, 0x4c, 0x98, 0xf7, 0x6d, 0x31, 0x38, 0xfb, 0xdb, 0xfe, 0xdb,
+ 0x37, 0x02, 0x76, 0xa1, 0x18, 0x97, 0xa6, 0xcc, 0xde, 0x20, 0x09, 0x49,
+ 0x36, 0x24, 0x69, 0x42, 0xf6, 0xe4, 0x37, 0x62, 0xf1, 0x59, 0x6d, 0xa9,
+ 0x3c, 0xed, 0x34, 0x9c, 0xa3, 0x8e, 0xdb, 0xdc, 0x3a, 0xd7, 0xf7, 0x0a,
+ 0x6f, 0xef, 0x2e, 0xd8, 0xd5, 0x93, 0x5a, 0x7a, 0xed, 0x08, 0x49, 0x68,
+ 0xe2, 0x41, 0xe3, 0x5a, 0x90, 0xc1, 0x86, 0x55, 0xfc, 0x51, 0x43, 0x9d,
+ 0xe0, 0xb2, 0xc4, 0x67, 0xb4, 0xcb, 0x32, 0x31, 0x25, 0xf0, 0x54, 0x9f,
+ 0x4b, 0xd1, 0x6f, 0xdb, 0xd4, 0xdd, 0xfc, 0xaf, 0x5e, 0x6c, 0x78, 0x90,
+ 0x95, 0xde, 0xca, 0x3a, 0x48, 0xb9, 0x79, 0x3c, 0x9b, 0x19, 0xd6, 0x75,
+ 0x05, 0xa0, 0xf9, 0x88, 0xd7, 0xc1, 0xe8, 0xa5, 0x09, 0xe4, 0x1a, 0x15,
+ 0xdc, 0x87, 0x23, 0xaa, 0xb2, 0x75, 0x8c, 0x63, 0x25, 0x87, 0xd8, 0xf8,
+ 0x3d, 0xa6, 0xc2, 0xcc, 0x66, 0xff, 0xa5, 0x66, 0x68, 0x55, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x0e, 0x30, 0x82, 0x01, 0x0a, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x2b, 0x30, 0x29, 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30,
+ 0x34, 0x38, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x1e, 0xf1, 0xab, 0x89,
+ 0x06, 0xf8, 0x49, 0x0f, 0x01, 0x33, 0x77, 0xee, 0x14, 0x7a, 0xee, 0x19,
+ 0x7c, 0x93, 0x28, 0x4d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1, 0x11, 0x80, 0xbe,
+ 0xd8, 0x89, 0xb9, 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24, 0x09, 0x16, 0xb9,
+ 0x70, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x40, 0x9a, 0x87,
+ 0x7e, 0x88, 0xd4, 0xcc, 0x26, 0xa7, 0x4b, 0xfa, 0x78, 0x4a, 0x20, 0xd5,
+ 0xf9, 0xa2, 0x36, 0x21, 0xbb, 0xee, 0x5b, 0xa0, 0x4f, 0x44, 0x8d, 0xcf,
+ 0xaa, 0xf9, 0x97, 0x17, 0x96, 0x84, 0xa9, 0xc8, 0x67, 0x9b, 0xbb, 0xe6,
+ 0x10, 0xde, 0x79, 0xd6, 0x56, 0x6a, 0xa4, 0x78, 0x14, 0x49, 0xd9, 0x7c,
+ 0xed, 0x30, 0x5e, 0x69, 0xea, 0x6d, 0x24, 0x46, 0x5a, 0x88, 0x34, 0x3d,
+ 0x26, 0x27, 0xcf, 0x69, 0x41, 0x84, 0x1c, 0x04, 0xda, 0x19, 0x38, 0x2e,
+ 0xdb, 0x89, 0x41, 0x39, 0x7e, 0x65, 0x1f, 0x9d, 0x5a, 0x3a, 0xcc, 0xe1,
+ 0x0c, 0x4c, 0x37, 0xa1, 0xce, 0x60, 0x93, 0xa8, 0xb5, 0x8c, 0xca, 0x3f,
+ 0xba, 0x2b, 0x5d, 0x4c, 0x1b, 0x81, 0x89, 0x7a, 0xca, 0x36, 0x30, 0x9c,
+ 0xff, 0x84, 0xe3, 0xfe, 0x3a, 0xf1, 0xf7, 0x79, 0x71, 0xc9, 0xb5, 0xd3,
+ 0x33, 0x03, 0xca, 0x77, 0xce, 0xb0, 0xba, 0x29, 0xd2, 0x34, 0x5d, 0x73,
+ 0xff, 0xa4, 0xfd, 0xf2, 0x25, 0xb8, 0x35, 0x45, 0x79, 0x7a, 0x1f, 0x97,
+ 0xae, 0xc9, 0xbe, 0x0a, 0x68, 0x84, 0x99, 0x74, 0x39, 0xa8, 0x4e, 0x7a,
+ 0x26, 0xf5, 0xcd, 0xde, 0x25, 0xe2, 0x37, 0x85, 0x65, 0x07, 0xa7, 0xca,
+ 0xc5, 0x05, 0xb7, 0x13, 0x38, 0x0d, 0x2d, 0xf0, 0x6d, 0x19, 0xce, 0xde,
+ 0x99, 0x61, 0x27, 0xee, 0x45, 0x6e, 0xc7, 0x39, 0xff, 0xf6, 0xc5, 0x8b,
+ 0xe0, 0xcb, 0x7c, 0x8a, 0x1e, 0xd5, 0x7a, 0x07, 0x31, 0x2a, 0x52, 0x5c,
+ 0x3a, 0x50, 0x19, 0x38, 0xa9, 0x44, 0xfa, 0x3c, 0xa8, 0xcf, 0xef, 0x79,
+ 0x9d, 0x6a, 0xd9, 0xe5, 0x2e, 0xa1, 0x8f, 0x29, 0x28, 0xd7, 0xec, 0xaa,
+ 0xc1, 0xfb, 0x26, 0xe6, 0x9f, 0x46, 0x24, 0xa6, 0xb1, 0x07, 0xcd, 0xb9,
+ 0x0c, 0xe8, 0x0d, 0x82, 0x16, 0x00, 0x1d, 0x96, 0x92, 0xfc, 0xa6, 0x08,
+ 0xa0,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 40:89:95:44:7e:5f:b1:19:d8:65:73:70:2f:8d:64:fc
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Validity
+ Not Before: Dec 1 00:00:00 2006 GMT
+ Not After : Dec 31 23:59:59 2019 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=EssentialSSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:f0:08:b0:72:c6:ab:83:12:31:17:70:89:85:
+ a9:20:12:d4:98:6a:ed:80:d4:d1:df:e4:8e:59:2d:
+ d3:96:21:8d:76:d2:3f:18:0b:46:19:63:0b:c7:20:
+ f3:e5:0b:dd:80:1a:f1:5a:a0:bd:1d:76:cd:b7:23:
+ 3a:74:5e:61:1b:75:aa:9b:d4:85:f4:e1:78:91:d3:
+ 2d:e1:af:fc:98:2e:06:d2:79:3d:5a:c0:1f:21:2d:
+ 1c:ae:21:53:c6:3a:a7:21:7e:be:ed:67:6f:75:1d:
+ 1a:9f:6a:5b:06:b3:6a:e3:b1:0b:aa:6a:0e:e7:6d:
+ 6c:c3:ca:95:8c:37:ce:21:1f:35:90:7d:db:da:1a:
+ 5c:a8:88:14:b2:0f:c8:12:20:5f:c5:d3:7f:e8:e1:
+ 38:e0:db:bc:f9:1f:a1:aa:d6:1b:90:07:21:fa:45:
+ 24:50:5d:27:2a:a0:28:41:45:5b:7d:bc:a0:a2:2f:
+ aa:9b:7e:5b:53:c5:f1:05:16:57:7e:11:d7:3b:b4:
+ d9:01:76:dc:df:7d:10:cf:51:a9:e5:38:f2:7b:14:
+ 00:75:59:f9:f0:59:db:17:3e:f7:af:e6:02:2d:a4:
+ 79:c1:5d:a2:1c:c3:9a:c8:a7:a8:0b:48:0a:6a:2e:
+ 7f:2d:97:65:f6:c5:04:9c:44:c8:99:96:7e:7e:a4:
+ dd:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+ X509v3 Subject Key Identifier:
+ DA:CB:EA:AD:5B:08:5D:CC:FF:FC:26:54:CE:49:E5:55:C6:38:F4:F8
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://secure.comodo.net/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/ComodoUTNServerCA.crt
+ CA Issuers - URI:http://crt.comodo.net/ComodoUTNServerCA.crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 6d:c3:6b:56:47:47:6b:3d:36:c2:db:51:ca:75:d0:5a:aa:62:
+ 40:42:07:61:d8:fe:6b:2c:d8:03:e1:54:f6:9e:5c:16:e3:17:
+ 37:ec:a0:f6:2b:7a:5f:2e:c3:50:01:d7:33:0e:0a:3e:ad:b3:
+ 33:c7:4f:9e:45:26:c8:f1:ee:bd:64:62:ff:88:1f:c4:59:f7:
+ 92:15:c8:e7:f7:38:ab:1f:00:ec:4c:f1:27:aa:01:0d:34:c7:
+ 04:5a:b4:79:b2:9c:e4:31:61:ef:9a:12:33:69:d4:e0:30:6e:
+ 1c:67:5d:f9:68:d6:31:37:6a:49:c5:0b:75:99:54:65:1e:5f:
+ 2d:99:cb:a7:41:a4:fa:d2:b5:f0:d4:1e:48:ec:90:3f:d3:7d:
+ b1:ff:23:96:6b:23:35:b0:ed:9e:5f:3d:31:74:48:80:7d:90:
+ 56:6d:10:fe:63:7c:ee:9a:d3:fd:9f:5f:21:09:0d:5e:cc:b3:
+ 8d:5a:8f:d8:a0:41:35:a3:86:73:05:ae:d9:19:7a:3a:cb:20:
+ af:51:91:a3:cc:46:4d:47:50:c6:fb:dc:15:2c:54:71:bf:fe:
+ 57:fb:89:ac:ff:d0:bb:8f:66:3e:ef:e4:21:af:80:47:ff:86:
+ db:39:11:c8:e6:50:cd:45:6d:59:96:ca:55:76:6d:b5:8e:b0:
+ de:09:68:00
+-----BEGIN CERTIFICATE-----
+MIIE+DCCA+CgAwIBAgIQQImVRH5fsRnYZXNwL41k/DANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh
+dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E
+TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf
+5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR
+0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD
+ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq
+oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX
+Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggF4
+MIIBdDAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU
+2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwPgYDVR0gBDcwNTAzBgRVHSAAMCswKQYIKwYBBQUHAgEWHWh0dHBz
+Oi8vc2VjdXJlLmNvbW9kby5uZXQvQ1BTMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6
+Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET0NlcnRpZmljYXRpb25BdXRob3JpdHku
+Y3JsMIGCBggrBgEFBQcBAQR2MHQwOQYIKwYBBQUHMAKGLWh0dHA6Ly9jcnQuY29t
+b2RvY2EuY29tL0NvbW9kb1VUTlNlcnZlckNBLmNydDA3BggrBgEFBQcwAoYraHR0
+cDovL2NydC5jb21vZG8ubmV0L0NvbW9kb1VUTlNlcnZlckNBLmNydDANBgkqhkiG
+9w0BAQUFAAOCAQEAbcNrVkdHaz02wttRynXQWqpiQEIHYdj+ayzYA+FU9p5cFuMX
+N+yg9it6Xy7DUAHXMw4KPq2zM8dPnkUmyPHuvWRi/4gfxFn3khXI5/c4qx8A7Ezx
+J6oBDTTHBFq0ebKc5DFh75oSM2nU4DBuHGdd+WjWMTdqScULdZlUZR5fLZnLp0Gk
++tK18NQeSOyQP9N9sf8jlmsjNbDtnl89MXRIgH2QVm0Q/mN87prT/Z9fIQkNXsyz
+jVqP2KBBNaOGcwWu2Rl6Ossgr1GRo8xGTUdQxvvcFSxUcb/+V/uJrP/Qu49mPu/k
+Ia+AR/+G2zkRyOZQzUVtWZbKVXZttY6w3gloAA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert67[] = {
+ 0x30, 0x82, 0x04, 0xf8, 0x30, 0x82, 0x03, 0xe0, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x40, 0x89, 0x95, 0x44, 0x7e, 0x5f, 0xb1, 0x19, 0xd8,
+ 0x65, 0x73, 0x70, 0x2f, 0x8d, 0x64, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+ 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x72, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30,
+ 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61,
+ 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74,
+ 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
+ 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+ 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
+ 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0f, 0x45,
+ 0x73, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xad,
+ 0xf0, 0x08, 0xb0, 0x72, 0xc6, 0xab, 0x83, 0x12, 0x31, 0x17, 0x70, 0x89,
+ 0x85, 0xa9, 0x20, 0x12, 0xd4, 0x98, 0x6a, 0xed, 0x80, 0xd4, 0xd1, 0xdf,
+ 0xe4, 0x8e, 0x59, 0x2d, 0xd3, 0x96, 0x21, 0x8d, 0x76, 0xd2, 0x3f, 0x18,
+ 0x0b, 0x46, 0x19, 0x63, 0x0b, 0xc7, 0x20, 0xf3, 0xe5, 0x0b, 0xdd, 0x80,
+ 0x1a, 0xf1, 0x5a, 0xa0, 0xbd, 0x1d, 0x76, 0xcd, 0xb7, 0x23, 0x3a, 0x74,
+ 0x5e, 0x61, 0x1b, 0x75, 0xaa, 0x9b, 0xd4, 0x85, 0xf4, 0xe1, 0x78, 0x91,
+ 0xd3, 0x2d, 0xe1, 0xaf, 0xfc, 0x98, 0x2e, 0x06, 0xd2, 0x79, 0x3d, 0x5a,
+ 0xc0, 0x1f, 0x21, 0x2d, 0x1c, 0xae, 0x21, 0x53, 0xc6, 0x3a, 0xa7, 0x21,
+ 0x7e, 0xbe, 0xed, 0x67, 0x6f, 0x75, 0x1d, 0x1a, 0x9f, 0x6a, 0x5b, 0x06,
+ 0xb3, 0x6a, 0xe3, 0xb1, 0x0b, 0xaa, 0x6a, 0x0e, 0xe7, 0x6d, 0x6c, 0xc3,
+ 0xca, 0x95, 0x8c, 0x37, 0xce, 0x21, 0x1f, 0x35, 0x90, 0x7d, 0xdb, 0xda,
+ 0x1a, 0x5c, 0xa8, 0x88, 0x14, 0xb2, 0x0f, 0xc8, 0x12, 0x20, 0x5f, 0xc5,
+ 0xd3, 0x7f, 0xe8, 0xe1, 0x38, 0xe0, 0xdb, 0xbc, 0xf9, 0x1f, 0xa1, 0xaa,
+ 0xd6, 0x1b, 0x90, 0x07, 0x21, 0xfa, 0x45, 0x24, 0x50, 0x5d, 0x27, 0x2a,
+ 0xa0, 0x28, 0x41, 0x45, 0x5b, 0x7d, 0xbc, 0xa0, 0xa2, 0x2f, 0xaa, 0x9b,
+ 0x7e, 0x5b, 0x53, 0xc5, 0xf1, 0x05, 0x16, 0x57, 0x7e, 0x11, 0xd7, 0x3b,
+ 0xb4, 0xd9, 0x01, 0x76, 0xdc, 0xdf, 0x7d, 0x10, 0xcf, 0x51, 0xa9, 0xe5,
+ 0x38, 0xf2, 0x7b, 0x14, 0x00, 0x75, 0x59, 0xf9, 0xf0, 0x59, 0xdb, 0x17,
+ 0x3e, 0xf7, 0xaf, 0xe6, 0x02, 0x2d, 0xa4, 0x79, 0xc1, 0x5d, 0xa2, 0x1c,
+ 0xc3, 0x9a, 0xc8, 0xa7, 0xa8, 0x0b, 0x48, 0x0a, 0x6a, 0x2e, 0x7f, 0x2d,
+ 0x97, 0x65, 0xf6, 0xc5, 0x04, 0x9c, 0x44, 0xc8, 0x99, 0x96, 0x7e, 0x7e,
+ 0xa4, 0xdd, 0x2f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x78,
+ 0x30, 0x82, 0x01, 0x74, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+ 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+ 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0xda, 0xcb, 0xea, 0xad, 0x5b, 0x08, 0x5d, 0xcc, 0xff, 0xfc, 0x26, 0x54,
+ 0xce, 0x49, 0xe5, 0x55, 0xc6, 0x38, 0xf4, 0xf8, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3e, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55,
+ 0x1d, 0x20, 0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53,
+ 0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30,
+ 0x3e, 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+ 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+ 0x4f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x81, 0x82, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x76, 0x30, 0x74, 0x30, 0x39, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f,
+ 0x6d, 0x6f, 0x64, 0x6f, 0x55, 0x54, 0x4e, 0x53, 0x65, 0x72, 0x76, 0x65,
+ 0x72, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x37, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2b, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+ 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x55, 0x54, 0x4e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00,
+ 0x6d, 0xc3, 0x6b, 0x56, 0x47, 0x47, 0x6b, 0x3d, 0x36, 0xc2, 0xdb, 0x51,
+ 0xca, 0x75, 0xd0, 0x5a, 0xaa, 0x62, 0x40, 0x42, 0x07, 0x61, 0xd8, 0xfe,
+ 0x6b, 0x2c, 0xd8, 0x03, 0xe1, 0x54, 0xf6, 0x9e, 0x5c, 0x16, 0xe3, 0x17,
+ 0x37, 0xec, 0xa0, 0xf6, 0x2b, 0x7a, 0x5f, 0x2e, 0xc3, 0x50, 0x01, 0xd7,
+ 0x33, 0x0e, 0x0a, 0x3e, 0xad, 0xb3, 0x33, 0xc7, 0x4f, 0x9e, 0x45, 0x26,
+ 0xc8, 0xf1, 0xee, 0xbd, 0x64, 0x62, 0xff, 0x88, 0x1f, 0xc4, 0x59, 0xf7,
+ 0x92, 0x15, 0xc8, 0xe7, 0xf7, 0x38, 0xab, 0x1f, 0x00, 0xec, 0x4c, 0xf1,
+ 0x27, 0xaa, 0x01, 0x0d, 0x34, 0xc7, 0x04, 0x5a, 0xb4, 0x79, 0xb2, 0x9c,
+ 0xe4, 0x31, 0x61, 0xef, 0x9a, 0x12, 0x33, 0x69, 0xd4, 0xe0, 0x30, 0x6e,
+ 0x1c, 0x67, 0x5d, 0xf9, 0x68, 0xd6, 0x31, 0x37, 0x6a, 0x49, 0xc5, 0x0b,
+ 0x75, 0x99, 0x54, 0x65, 0x1e, 0x5f, 0x2d, 0x99, 0xcb, 0xa7, 0x41, 0xa4,
+ 0xfa, 0xd2, 0xb5, 0xf0, 0xd4, 0x1e, 0x48, 0xec, 0x90, 0x3f, 0xd3, 0x7d,
+ 0xb1, 0xff, 0x23, 0x96, 0x6b, 0x23, 0x35, 0xb0, 0xed, 0x9e, 0x5f, 0x3d,
+ 0x31, 0x74, 0x48, 0x80, 0x7d, 0x90, 0x56, 0x6d, 0x10, 0xfe, 0x63, 0x7c,
+ 0xee, 0x9a, 0xd3, 0xfd, 0x9f, 0x5f, 0x21, 0x09, 0x0d, 0x5e, 0xcc, 0xb3,
+ 0x8d, 0x5a, 0x8f, 0xd8, 0xa0, 0x41, 0x35, 0xa3, 0x86, 0x73, 0x05, 0xae,
+ 0xd9, 0x19, 0x7a, 0x3a, 0xcb, 0x20, 0xaf, 0x51, 0x91, 0xa3, 0xcc, 0x46,
+ 0x4d, 0x47, 0x50, 0xc6, 0xfb, 0xdc, 0x15, 0x2c, 0x54, 0x71, 0xbf, 0xfe,
+ 0x57, 0xfb, 0x89, 0xac, 0xff, 0xd0, 0xbb, 0x8f, 0x66, 0x3e, 0xef, 0xe4,
+ 0x21, 0xaf, 0x80, 0x47, 0xff, 0x86, 0xdb, 0x39, 0x11, 0xc8, 0xe6, 0x50,
+ 0xcd, 0x45, 0x6d, 0x59, 0x96, 0xca, 0x55, 0x76, 0x6d, 0xb5, 0x8e, 0xb0,
+ 0xde, 0x09, 0x68, 0x00,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 269 (0x10d)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com
+ Validity
+ Not Before: Jun 29 17:06:20 2004 GMT
+ Not After : Jun 29 17:06:20 2024 GMT
+ Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:de:9d:d7:ea:57:18:49:a1:5b:eb:d7:5f:48:86:
+ ea:be:dd:ff:e4:ef:67:1c:f4:65:68:b3:57:71:a0:
+ 5e:77:bb:ed:9b:49:e9:70:80:3d:56:18:63:08:6f:
+ da:f2:cc:d0:3f:7f:02:54:22:54:10:d8:b2:81:d4:
+ c0:75:3d:4b:7f:c7:77:c3:3e:78:ab:1a:03:b5:20:
+ 6b:2f:6a:2b:b1:c5:88:7e:c4:bb:1e:b0:c1:d8:45:
+ 27:6f:aa:37:58:f7:87:26:d7:d8:2d:f6:a9:17:b7:
+ 1f:72:36:4e:a6:17:3f:65:98:92:db:2a:6e:5d:a2:
+ fe:88:e0:0b:de:7f:e5:8d:15:e1:eb:cb:3a:d5:e2:
+ 12:a2:13:2d:d8:8e:af:5f:12:3d:a0:08:05:08:b6:
+ 5c:a5:65:38:04:45:99:1e:a3:60:60:74:c5:41:a5:
+ 72:62:1b:62:c5:1f:6f:5f:1a:42:be:02:51:65:a8:
+ ae:23:18:6a:fc:78:03:a9:4d:7f:80:c3:fa:ab:5a:
+ fc:a1:40:a4:ca:19:16:fe:b2:c8:ef:5e:73:0d:ee:
+ 77:bd:9a:f6:79:98:bc:b1:07:67:a2:15:0d:dd:a0:
+ 58:c6:44:7b:0a:3e:62:28:5f:ba:41:07:53:58:cf:
+ 11:7e:38:74:c5:f8:ff:b5:69:90:8f:84:74:ea:97:
+ 1b:af
+ Exponent: 3 (0x3)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
+ X509v3 Authority Key Identifier:
+ DirName:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com
+ serial:01
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.godaddy.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://certificates.godaddy.com/repository/root.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://certificates.godaddy.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ b5:40:f9:a7:1d:f6:ea:fe:a4:1a:42:5a:44:f7:15:d4:85:46:
+ 89:c0:be:9e:e3:e3:eb:c5:e3:58:89:8f:92:9f:57:a8:71:2c:
+ 48:d1:81:b2:79:1f:ac:06:35:19:b0:4e:0e:58:1b:14:b3:98:
+ 81:d1:04:1e:c8:07:c9:83:9f:78:44:0a:18:0b:98:dc:76:7a:
+ 65:0d:0d:6d:80:c4:0b:01:1c:cb:ad:47:3e:71:be:77:4b:cc:
+ 06:77:d0:f4:56:6b:1f:4b:13:9a:14:8a:88:23:a8:51:f0:83:
+ 4c:ab:35:bf:46:7e:39:dc:75:a4:ae:e8:29:fb:ef:39:8f:4f:
+ 55:67
+-----BEGIN CERTIFICATE-----
+MIIE+zCCBGSgAwIBAgICAQ0wDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
+bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
+Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
+QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
+BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MDYyMFoX
+DTI0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBE
+YWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0
+aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC
+ggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+q
+N1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiO
+r18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lN
+f4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+YihfukEH
+U1jPEX44dMX4/7VpkI+EdOqXG68CAQOjggHhMIIB3TAdBgNVHQ4EFgQU0sSw0pHU
+TBFxs2HLPaH+3ahq1OMwgdIGA1UdIwSByjCBx6GBwaSBvjCBuzEkMCIGA1UEBxMb
+VmFsaUNlcnQgVmFsaWRhdGlvbiBOZXR3b3JrMRcwFQYDVQQKEw5WYWxpQ2VydCwg
+SW5jLjE1MDMGA1UECxMsVmFsaUNlcnQgQ2xhc3MgMiBQb2xpY3kgVmFsaWRhdGlv
+biBBdXRob3JpdHkxITAfBgNVBAMTGGh0dHA6Ly93d3cudmFsaWNlcnQuY29tLzEg
+MB4GCSqGSIb3DQEJARYRaW5mb0B2YWxpY2VydC5jb22CAQEwDwYDVR0TAQH/BAUw
+AwEB/zAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmdv
+ZGFkZHkuY29tMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jZXJ0aWZpY2F0ZXMu
+Z29kYWRkeS5jb20vcmVwb3NpdG9yeS9yb290LmNybDBLBgNVHSAERDBCMEAGBFUd
+IAAwODA2BggrBgEFBQcCARYqaHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNv
+bS9yZXBvc2l0b3J5MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOBgQC1
+QPmnHfbq/qQaQlpE9xXUhUaJwL6e4+PrxeNYiY+Sn1eocSxI0YGyeR+sBjUZsE4O
+WBsUs5iB0QQeyAfJg594RAoYC5jcdnplDQ1tgMQLARzLrUc+cb53S8wGd9D0Vmsf
+SxOaFIqII6hR8INMqzW/Rn453HWkrugp++85j09VZw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert68[] = {
+ 0x30, 0x82, 0x04, 0xfb, 0x30, 0x82, 0x04, 0x64, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x02, 0x01, 0x0d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xbb, 0x31,
+ 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61,
+ 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c,
+ 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+ 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63,
+ 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+ 0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65,
+ 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34,
+ 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x30, 0x36, 0x32, 0x30, 0x5a, 0x17,
+ 0x0d, 0x32, 0x34, 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x30, 0x36, 0x32,
+ 0x30, 0x5a, 0x30, 0x63, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x18, 0x54, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x44,
+ 0x61, 0x64, 0x64, 0x79, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x28, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20,
+ 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x20, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, 0x00, 0x30, 0x82, 0x01, 0x08, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xde, 0x9d, 0xd7, 0xea, 0x57, 0x18, 0x49, 0xa1,
+ 0x5b, 0xeb, 0xd7, 0x5f, 0x48, 0x86, 0xea, 0xbe, 0xdd, 0xff, 0xe4, 0xef,
+ 0x67, 0x1c, 0xf4, 0x65, 0x68, 0xb3, 0x57, 0x71, 0xa0, 0x5e, 0x77, 0xbb,
+ 0xed, 0x9b, 0x49, 0xe9, 0x70, 0x80, 0x3d, 0x56, 0x18, 0x63, 0x08, 0x6f,
+ 0xda, 0xf2, 0xcc, 0xd0, 0x3f, 0x7f, 0x02, 0x54, 0x22, 0x54, 0x10, 0xd8,
+ 0xb2, 0x81, 0xd4, 0xc0, 0x75, 0x3d, 0x4b, 0x7f, 0xc7, 0x77, 0xc3, 0x3e,
+ 0x78, 0xab, 0x1a, 0x03, 0xb5, 0x20, 0x6b, 0x2f, 0x6a, 0x2b, 0xb1, 0xc5,
+ 0x88, 0x7e, 0xc4, 0xbb, 0x1e, 0xb0, 0xc1, 0xd8, 0x45, 0x27, 0x6f, 0xaa,
+ 0x37, 0x58, 0xf7, 0x87, 0x26, 0xd7, 0xd8, 0x2d, 0xf6, 0xa9, 0x17, 0xb7,
+ 0x1f, 0x72, 0x36, 0x4e, 0xa6, 0x17, 0x3f, 0x65, 0x98, 0x92, 0xdb, 0x2a,
+ 0x6e, 0x5d, 0xa2, 0xfe, 0x88, 0xe0, 0x0b, 0xde, 0x7f, 0xe5, 0x8d, 0x15,
+ 0xe1, 0xeb, 0xcb, 0x3a, 0xd5, 0xe2, 0x12, 0xa2, 0x13, 0x2d, 0xd8, 0x8e,
+ 0xaf, 0x5f, 0x12, 0x3d, 0xa0, 0x08, 0x05, 0x08, 0xb6, 0x5c, 0xa5, 0x65,
+ 0x38, 0x04, 0x45, 0x99, 0x1e, 0xa3, 0x60, 0x60, 0x74, 0xc5, 0x41, 0xa5,
+ 0x72, 0x62, 0x1b, 0x62, 0xc5, 0x1f, 0x6f, 0x5f, 0x1a, 0x42, 0xbe, 0x02,
+ 0x51, 0x65, 0xa8, 0xae, 0x23, 0x18, 0x6a, 0xfc, 0x78, 0x03, 0xa9, 0x4d,
+ 0x7f, 0x80, 0xc3, 0xfa, 0xab, 0x5a, 0xfc, 0xa1, 0x40, 0xa4, 0xca, 0x19,
+ 0x16, 0xfe, 0xb2, 0xc8, 0xef, 0x5e, 0x73, 0x0d, 0xee, 0x77, 0xbd, 0x9a,
+ 0xf6, 0x79, 0x98, 0xbc, 0xb1, 0x07, 0x67, 0xa2, 0x15, 0x0d, 0xdd, 0xa0,
+ 0x58, 0xc6, 0x44, 0x7b, 0x0a, 0x3e, 0x62, 0x28, 0x5f, 0xba, 0x41, 0x07,
+ 0x53, 0x58, 0xcf, 0x11, 0x7e, 0x38, 0x74, 0xc5, 0xf8, 0xff, 0xb5, 0x69,
+ 0x90, 0x8f, 0x84, 0x74, 0xea, 0x97, 0x1b, 0xaf, 0x02, 0x01, 0x03, 0xa3,
+ 0x82, 0x01, 0xe1, 0x30, 0x82, 0x01, 0xdd, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd2, 0xc4, 0xb0, 0xd2, 0x91, 0xd4,
+ 0x4c, 0x11, 0x71, 0xb3, 0x61, 0xcb, 0x3d, 0xa1, 0xfe, 0xdd, 0xa8, 0x6a,
+ 0xd4, 0xe3, 0x30, 0x81, 0xd2, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81,
+ 0xca, 0x30, 0x81, 0xc7, 0xa1, 0x81, 0xc1, 0xa4, 0x81, 0xbe, 0x30, 0x81,
+ 0xbb, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b,
+ 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c,
+ 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20,
+ 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69,
+ 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31,
+ 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c,
+ 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20,
+ 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09,
+ 0x01, 0x16, 0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69,
+ 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x82, 0x01, 0x01, 0x30,
+ 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+ 0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f,
+ 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x44, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37,
+ 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e,
+ 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x72,
+ 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x4b, 0x06, 0x03, 0x55,
+ 0x1d, 0x20, 0x04, 0x44, 0x30, 0x42, 0x30, 0x40, 0x06, 0x04, 0x55, 0x1d,
+ 0x20, 0x00, 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+ 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+ 0x03, 0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xb5,
+ 0x40, 0xf9, 0xa7, 0x1d, 0xf6, 0xea, 0xfe, 0xa4, 0x1a, 0x42, 0x5a, 0x44,
+ 0xf7, 0x15, 0xd4, 0x85, 0x46, 0x89, 0xc0, 0xbe, 0x9e, 0xe3, 0xe3, 0xeb,
+ 0xc5, 0xe3, 0x58, 0x89, 0x8f, 0x92, 0x9f, 0x57, 0xa8, 0x71, 0x2c, 0x48,
+ 0xd1, 0x81, 0xb2, 0x79, 0x1f, 0xac, 0x06, 0x35, 0x19, 0xb0, 0x4e, 0x0e,
+ 0x58, 0x1b, 0x14, 0xb3, 0x98, 0x81, 0xd1, 0x04, 0x1e, 0xc8, 0x07, 0xc9,
+ 0x83, 0x9f, 0x78, 0x44, 0x0a, 0x18, 0x0b, 0x98, 0xdc, 0x76, 0x7a, 0x65,
+ 0x0d, 0x0d, 0x6d, 0x80, 0xc4, 0x0b, 0x01, 0x1c, 0xcb, 0xad, 0x47, 0x3e,
+ 0x71, 0xbe, 0x77, 0x4b, 0xcc, 0x06, 0x77, 0xd0, 0xf4, 0x56, 0x6b, 0x1f,
+ 0x4b, 0x13, 0x9a, 0x14, 0x8a, 0x88, 0x23, 0xa8, 0x51, 0xf0, 0x83, 0x4c,
+ 0xab, 0x35, 0xbf, 0x46, 0x7e, 0x39, 0xdc, 0x75, 0xa4, 0xae, 0xe8, 0x29,
+ 0xfb, 0xef, 0x39, 0x8f, 0x4f, 0x55, 0x67,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 16:90:c3:29:b6:78:06:07:51:1f:05:b0:34:48:46:cb
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Apr 16 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO High-Assurance Secure Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e7:87:da:c0:77:e4:bb:3a:fa:6a:24:c8:80:41:
+ ac:d2:16:13:15:3d:fa:f7:f8:2a:76:dc:a8:2d:39:
+ 08:ce:48:4a:be:0f:7d:f0:de:ba:bb:47:d5:bd:2d:
+ d7:1b:ab:0f:20:81:23:08:72:b1:c0:11:95:0d:e6:
+ ea:a9:87:ff:c7:6e:1e:4f:66:32:ba:53:bc:05:aa:
+ 1c:2c:0c:ef:4d:37:47:6b:10:0c:db:c5:a0:98:7e:
+ 58:db:37:d6:ae:e9:06:bd:d7:a8:65:f3:37:b9:c7:
+ 6d:ce:77:c7:26:e0:d7:74:1f:a6:98:16:bb:0c:6b:
+ c8:be:77:d0:ef:58:a7:29:a0:b9:b8:69:05:36:cb:
+ b2:da:58:a3:0b:75:ad:3d:8b:22:82:20:3e:70:86:
+ 99:1c:b9:4f:cf:77:a4:07:1a:23:63:d1:38:56:84:
+ ec:bf:8f:c5:4e:f4:18:96:9b:1a:e8:93:ec:8d:af:
+ 15:9c:24:f0:5a:3b:e8:0f:b9:a8:5a:01:d3:b2:1c:
+ 60:c9:9c:52:04:dd:92:a7:fe:0c:ac:e2:45:8d:03:
+ 61:bc:79:e0:77:2e:87:41:3c:58:5f:cb:f5:c5:77:
+ f2:58:c8:4d:28:d0:9a:fa:f3:73:09:24:68:74:bc:
+ 20:4c:d8:2c:b0:aa:e8:d9:4e:6d:f2:8c:24:d3:93:
+ 5d:91
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 3F:D5:B5:D0:D6:44:79:50:4A:17:A3:9B:8C:4A:DC:B8:B0:22:64:6B
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 13:85:1f:52:80:18:c9:53:f7:fe:2e:1a:af:cc:d9:0b:3c:c2:
+ d3:85:81:10:f0:28:8d:b9:40:7e:2c:9e:8f:d6:36:86:0a:4c:
+ 14:2d:d6:97:43:92:41:19:37:4b:96:9e:eb:a9:30:79:12:95:
+ b3:02:36:57:ed:2b:b9:1d:98:1a:a3:18:0a:3f:9b:39:8b:cd:
+ a1:49:29:4c:2f:f9:d0:95:8c:c8:4d:95:ba:a8:43:cf:33:aa:
+ 25:2a:5a:0e:aa:27:c9:4e:6b:b1:e6:73:1f:b3:74:04:c3:f3:
+ 4c:e2:a8:eb:67:b7:5d:b8:08:05:1a:56:9a:54:29:85:f5:29:
+ 4e:80:3b:95:d0:7b:53:96:11:56:c1:02:d3:ea:b2:7f:ca:8f:
+ 9c:70:4a:14:8d:5a:b9:16:60:75:d6:cd:27:1e:16:cd:5b:33:
+ 8e:79:40:cf:28:48:e7:dc:71:16:4e:74:91:75:b9:2a:8c:f1:
+ 70:ac:26:dd:04:b9:40:c2:85:de:1c:93:40:d0:cc:6e:c3:9b:
+ aa:ef:60:65:df:60:22:f0:5a:a5:7a:a2:2f:e4:70:73:ee:3c:
+ d4:26:2b:68:07:c1:20:7a:e8:98:5a:3e:7b:9f:02:8b:62:c0:
+ 85:81:80:60:35:7e:a5:1d:0c:d2:9c:df:62:45:0d:db:fc:37:
+ fb:f5:25:22
+-----BEGIN CERTIFICATE-----
+MIIE/DCCA+SgAwIBAgIQFpDDKbZ4BgdRHwWwNEhGyzANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTEwMDQxNjAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYkxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMS8wLQYD
+VQQDEyZDT01PRE8gSGlnaC1Bc3N1cmFuY2UgU2VjdXJlIFNlcnZlciBDQTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOeH2sB35Ls6+mokyIBBrNIWExU9
++vf4KnbcqC05CM5ISr4PffDeurtH1b0t1xurDyCBIwhyscARlQ3m6qmH/8duHk9m
+MrpTvAWqHCwM7003R2sQDNvFoJh+WNs31q7pBr3XqGXzN7nHbc53xybg13QfppgW
+uwxryL530O9YpymgubhpBTbLstpYowt1rT2LIoIgPnCGmRy5T893pAcaI2PROFaE
+7L+PxU70GJabGuiT7I2vFZwk8Fo76A+5qFoB07IcYMmcUgTdkqf+DKziRY0DYbx5
+4Hcuh0E8WF/L9cV38ljITSjQmvrzcwkkaHS8IEzYLLCq6NlObfKMJNOTXZECAwEA
+AaOCAXcwggFzMB8GA1UdIwQYMBaAFK29mHo0tCb3+sQmVO8DveAky1QaMB0GA1Ud
+DgQWBBQ/1bXQ1kR5UEoXo5uMSty4sCJkazAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0T
+AQH/BAgwBgEB/wIBADARBgNVHSAECjAIMAYGBFUdIAAwRAYDVR0fBD0wOzA5oDeg
+NYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJv
+b3QuY3JsMIGzBggrBgEFBQcBAQSBpjCBozA/BggrBgEFBQcwAoYzaHR0cDovL2Ny
+dC51c2VydHJ1c3QuY29tL0FkZFRydXN0RXh0ZXJuYWxDQVJvb3QucDdjMDkGCCsG
+AQUFBzAChi1odHRwOi8vY3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RVVE5TR0ND
+QS5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJ
+KoZIhvcNAQEFBQADggEBABOFH1KAGMlT9/4uGq/M2Qs8wtOFgRDwKI25QH4sno/W
+NoYKTBQt1pdDkkEZN0uWnuupMHkSlbMCNlftK7kdmBqjGAo/mzmLzaFJKUwv+dCV
+jMhNlbqoQ88zqiUqWg6qJ8lOa7Hmcx+zdATD80ziqOtnt124CAUaVppUKYX1KU6A
+O5XQe1OWEVbBAtPqsn/Kj5xwShSNWrkWYHXWzSceFs1bM455QM8oSOfccRZOdJF1
+uSqM8XCsJt0EuUDChd4ck0DQzG7Dm6rvYGXfYCLwWqV6oi/kcHPuPNQmK2gHwSB6
+6JhaPnufAotiwIWBgGA1fqUdDNKc32JFDdv8N/v1JSI=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert69[] = {
+ 0x30, 0x82, 0x04, 0xfc, 0x30, 0x82, 0x03, 0xe4, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x16, 0x90, 0xc3, 0x29, 0xb6, 0x78, 0x06, 0x07, 0x51,
+ 0x1f, 0x05, 0xb0, 0x34, 0x48, 0x46, 0xcb, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x34, 0x31,
+ 0x36, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x89, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61,
+ 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f,
+ 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c,
+ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x26, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20,
+ 0x48, 0x69, 0x67, 0x68, 0x2d, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e,
+ 0x63, 0x65, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xe7, 0x87, 0xda, 0xc0, 0x77, 0xe4, 0xbb, 0x3a,
+ 0xfa, 0x6a, 0x24, 0xc8, 0x80, 0x41, 0xac, 0xd2, 0x16, 0x13, 0x15, 0x3d,
+ 0xfa, 0xf7, 0xf8, 0x2a, 0x76, 0xdc, 0xa8, 0x2d, 0x39, 0x08, 0xce, 0x48,
+ 0x4a, 0xbe, 0x0f, 0x7d, 0xf0, 0xde, 0xba, 0xbb, 0x47, 0xd5, 0xbd, 0x2d,
+ 0xd7, 0x1b, 0xab, 0x0f, 0x20, 0x81, 0x23, 0x08, 0x72, 0xb1, 0xc0, 0x11,
+ 0x95, 0x0d, 0xe6, 0xea, 0xa9, 0x87, 0xff, 0xc7, 0x6e, 0x1e, 0x4f, 0x66,
+ 0x32, 0xba, 0x53, 0xbc, 0x05, 0xaa, 0x1c, 0x2c, 0x0c, 0xef, 0x4d, 0x37,
+ 0x47, 0x6b, 0x10, 0x0c, 0xdb, 0xc5, 0xa0, 0x98, 0x7e, 0x58, 0xdb, 0x37,
+ 0xd6, 0xae, 0xe9, 0x06, 0xbd, 0xd7, 0xa8, 0x65, 0xf3, 0x37, 0xb9, 0xc7,
+ 0x6d, 0xce, 0x77, 0xc7, 0x26, 0xe0, 0xd7, 0x74, 0x1f, 0xa6, 0x98, 0x16,
+ 0xbb, 0x0c, 0x6b, 0xc8, 0xbe, 0x77, 0xd0, 0xef, 0x58, 0xa7, 0x29, 0xa0,
+ 0xb9, 0xb8, 0x69, 0x05, 0x36, 0xcb, 0xb2, 0xda, 0x58, 0xa3, 0x0b, 0x75,
+ 0xad, 0x3d, 0x8b, 0x22, 0x82, 0x20, 0x3e, 0x70, 0x86, 0x99, 0x1c, 0xb9,
+ 0x4f, 0xcf, 0x77, 0xa4, 0x07, 0x1a, 0x23, 0x63, 0xd1, 0x38, 0x56, 0x84,
+ 0xec, 0xbf, 0x8f, 0xc5, 0x4e, 0xf4, 0x18, 0x96, 0x9b, 0x1a, 0xe8, 0x93,
+ 0xec, 0x8d, 0xaf, 0x15, 0x9c, 0x24, 0xf0, 0x5a, 0x3b, 0xe8, 0x0f, 0xb9,
+ 0xa8, 0x5a, 0x01, 0xd3, 0xb2, 0x1c, 0x60, 0xc9, 0x9c, 0x52, 0x04, 0xdd,
+ 0x92, 0xa7, 0xfe, 0x0c, 0xac, 0xe2, 0x45, 0x8d, 0x03, 0x61, 0xbc, 0x79,
+ 0xe0, 0x77, 0x2e, 0x87, 0x41, 0x3c, 0x58, 0x5f, 0xcb, 0xf5, 0xc5, 0x77,
+ 0xf2, 0x58, 0xc8, 0x4d, 0x28, 0xd0, 0x9a, 0xfa, 0xf3, 0x73, 0x09, 0x24,
+ 0x68, 0x74, 0xbc, 0x20, 0x4c, 0xd8, 0x2c, 0xb0, 0xaa, 0xe8, 0xd9, 0x4e,
+ 0x6d, 0xf2, 0x8c, 0x24, 0xd3, 0x93, 0x5d, 0x91, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x01, 0x77, 0x30, 0x82, 0x01, 0x73, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd,
+ 0x98, 0x7a, 0x34, 0xb4, 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03,
+ 0xbd, 0xe0, 0x24, 0xcb, 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x3f, 0xd5, 0xb5, 0xd0, 0xd6, 0x44, 0x79,
+ 0x50, 0x4a, 0x17, 0xa3, 0x9b, 0x8c, 0x4a, 0xdc, 0xb8, 0xb0, 0x22, 0x64,
+ 0x6b, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, 0x30, 0x08,
+ 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x44, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0, 0x37, 0xa0,
+ 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6, 0x30, 0x81,
+ 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41, 0x52, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43,
+ 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x13, 0x85, 0x1f, 0x52, 0x80, 0x18, 0xc9, 0x53,
+ 0xf7, 0xfe, 0x2e, 0x1a, 0xaf, 0xcc, 0xd9, 0x0b, 0x3c, 0xc2, 0xd3, 0x85,
+ 0x81, 0x10, 0xf0, 0x28, 0x8d, 0xb9, 0x40, 0x7e, 0x2c, 0x9e, 0x8f, 0xd6,
+ 0x36, 0x86, 0x0a, 0x4c, 0x14, 0x2d, 0xd6, 0x97, 0x43, 0x92, 0x41, 0x19,
+ 0x37, 0x4b, 0x96, 0x9e, 0xeb, 0xa9, 0x30, 0x79, 0x12, 0x95, 0xb3, 0x02,
+ 0x36, 0x57, 0xed, 0x2b, 0xb9, 0x1d, 0x98, 0x1a, 0xa3, 0x18, 0x0a, 0x3f,
+ 0x9b, 0x39, 0x8b, 0xcd, 0xa1, 0x49, 0x29, 0x4c, 0x2f, 0xf9, 0xd0, 0x95,
+ 0x8c, 0xc8, 0x4d, 0x95, 0xba, 0xa8, 0x43, 0xcf, 0x33, 0xaa, 0x25, 0x2a,
+ 0x5a, 0x0e, 0xaa, 0x27, 0xc9, 0x4e, 0x6b, 0xb1, 0xe6, 0x73, 0x1f, 0xb3,
+ 0x74, 0x04, 0xc3, 0xf3, 0x4c, 0xe2, 0xa8, 0xeb, 0x67, 0xb7, 0x5d, 0xb8,
+ 0x08, 0x05, 0x1a, 0x56, 0x9a, 0x54, 0x29, 0x85, 0xf5, 0x29, 0x4e, 0x80,
+ 0x3b, 0x95, 0xd0, 0x7b, 0x53, 0x96, 0x11, 0x56, 0xc1, 0x02, 0xd3, 0xea,
+ 0xb2, 0x7f, 0xca, 0x8f, 0x9c, 0x70, 0x4a, 0x14, 0x8d, 0x5a, 0xb9, 0x16,
+ 0x60, 0x75, 0xd6, 0xcd, 0x27, 0x1e, 0x16, 0xcd, 0x5b, 0x33, 0x8e, 0x79,
+ 0x40, 0xcf, 0x28, 0x48, 0xe7, 0xdc, 0x71, 0x16, 0x4e, 0x74, 0x91, 0x75,
+ 0xb9, 0x2a, 0x8c, 0xf1, 0x70, 0xac, 0x26, 0xdd, 0x04, 0xb9, 0x40, 0xc2,
+ 0x85, 0xde, 0x1c, 0x93, 0x40, 0xd0, 0xcc, 0x6e, 0xc3, 0x9b, 0xaa, 0xef,
+ 0x60, 0x65, 0xdf, 0x60, 0x22, 0xf0, 0x5a, 0xa5, 0x7a, 0xa2, 0x2f, 0xe4,
+ 0x70, 0x73, 0xee, 0x3c, 0xd4, 0x26, 0x2b, 0x68, 0x07, 0xc1, 0x20, 0x7a,
+ 0xe8, 0x98, 0x5a, 0x3e, 0x7b, 0x9f, 0x02, 0x8b, 0x62, 0xc0, 0x85, 0x81,
+ 0x80, 0x60, 0x35, 0x7e, 0xa5, 0x1d, 0x0c, 0xd2, 0x9c, 0xdf, 0x62, 0x45,
+ 0x0d, 0xdb, 0xfc, 0x37, 0xfb, 0xf5, 0x25, 0x22,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 45:4f:a2:0d:78:11:74:59:f8:c6:ab:3c:7b:cd:03:0e
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: Jul 29 00:00:00 2011 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=BR, O=TrustSign Certificadora Digital, OU=Security Dept., CN=TrustSign BR Certification Authority (OV)
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a3:fb:cb:bf:d7:c0:51:de:8e:5f:6e:76:a7:1f:
+ c3:30:67:68:ef:63:a9:0d:3c:7a:0e:f6:dd:26:ff:
+ bc:7a:26:d5:b6:22:91:08:b8:38:43:e1:15:3d:0b:
+ f0:b0:df:9d:34:40:06:5e:ea:3b:e9:9b:2e:23:d3:
+ eb:1f:17:1c:16:ad:5a:78:41:e6:2d:61:ed:7d:2c:
+ 5a:5d:8a:6c:0e:70:6b:ff:ce:c4:80:50:14:cc:2b:
+ 60:0f:19:3d:f1:6d:e0:a7:a3:59:22:97:73:ec:4a:
+ d6:ba:f0:9e:a9:0b:f4:66:3e:13:5f:c8:72:5f:04:
+ 4a:13:21:e6:80:85:1f:63:5a:26:f0:c4:25:9b:07:
+ 33:71:ea:32:26:04:b2:d8:68:ff:6a:e6:09:5f:09:
+ 83:0c:c3:a8:e5:5a:bf:6c:86:b2:28:6a:94:a3:74:
+ 99:4a:d8:d0:11:e5:e4:ee:8c:b2:2c:4d:72:86:1b:
+ 17:4d:ee:cc:9b:5a:b2:36:7d:05:92:17:47:9b:f7:
+ 06:9d:96:be:ac:da:c4:db:97:c0:d4:10:cb:11:26:
+ 62:59:17:05:14:63:cb:81:4f:5a:a3:9d:cf:50:e7:
+ 4a:b6:e3:30:d5:29:44:ac:99:45:8c:0d:d0:97:66:
+ bb:38:f6:ad:0f:4d:ea:bb:0a:8b:02:27:11:37:eb:
+ 42:bb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 22:82:72:0D:B6:41:E3:DC:E4:81:8B:FB:86:02:11:71:93:FA:9B:4D
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.2.38
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3e:d9:cd:2e:64:0f:59:01:5c:22:22:b1:71:44:9d:59:4d:4c:
+ 8c:8f:10:59:5e:f6:ba:f8:5a:e3:4c:62:ea:09:8d:d7:9b:69:
+ a1:af:90:1d:de:f9:18:d6:83:c2:a5:e9:a8:ac:b6:ba:bf:db:
+ 4d:0a:50:f1:78:a3:13:91:6b:26:d8:e9:94:bb:2c:fb:0d:6f:
+ 7e:a7:f5:16:e2:99:77:d3:c6:52:3f:06:6e:18:f0:2e:28:c7:
+ 72:01:d7:9c:0b:12:40:76:08:7a:5c:69:eb:a1:d9:82:97:26:
+ c2:76:40:47:0f:56:54:2d:2c:92:65:98:28:b7:10:b2:dc:9c:
+ 26:b5:54:11:d6:be:b8:b6:03:9c:f0:b5:a5:f7:6e:f1:97:9c:
+ bd:88:23:a5:48:c7:8c:ca:0d:26:fd:48:09:a7:22:92:04:2a:
+ 3a:7e:97:a4:86:ef:9f:ff:2c:4b:e1:00:14:c1:55:6d:c1:5c:
+ 91:57:ec:8d:43:c5:f9:7d:b4:0c:06:34:c3:b4:a7:67:87:b6:
+ c4:cf:1a:35:08:95:11:20:21:a1:77:bf:2f:19:8a:73:81:c2:
+ 87:23:eb:d3:99:b2:7f:27:89:d2:8e:40:fa:3d:ee:06:49:0b:
+ fe:f4:a4:34:49:77:88:3d:42:91:96:b4:2c:9c:0b:e6:d4:41:
+ c4:df:ad:90
+-----BEGIN CERTIFICATE-----
+MIIE/jCCA+agAwIBAgIQRU+iDXgRdFn4xqs8e80DDjANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTExMDcyOTAwMDAwMFoXDTIwMDUzMDEwNDgzOFow
+gYQxCzAJBgNVBAYTAkJSMSgwJgYDVQQKEx9UcnVzdFNpZ24gQ2VydGlmaWNhZG9y
+YSBEaWdpdGFsMRcwFQYDVQQLEw5TZWN1cml0eSBEZXB0LjEyMDAGA1UEAxMpVHJ1
+c3RTaWduIEJSIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IChPVikwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCj+8u/18BR3o5fbnanH8MwZ2jvY6kNPHoO
+9t0m/7x6JtW2IpEIuDhD4RU9C/Cw3500QAZe6jvpmy4j0+sfFxwWrVp4QeYtYe19
+LFpdimwOcGv/zsSAUBTMK2APGT3xbeCno1kil3PsSta68J6pC/RmPhNfyHJfBEoT
+IeaAhR9jWibwxCWbBzNx6jImBLLYaP9q5glfCYMMw6jlWr9shrIoapSjdJlK2NAR
+5eTujLIsTXKGGxdN7sybWrI2fQWSF0eb9wadlr6s2sTbl8DUEMsRJmJZFwUUY8uB
+T1qjnc9Q50q24zDVKUSsmUWMDdCXZrs49q0PTeq7CosCJxE360K7AgMBAAGjggF+
+MIIBejAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73gJMtUGjAdBgNVHQ4EFgQU
+IoJyDbZB49zkgYv7hgIRcZP6m00wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwGAYDVR0gBBEwDzANBgsrBgEEAbIxAQICJjBEBgNVHR8EPTA7MDmg
+N6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENB
+Um9vdC5jcmwwgbMGCCsGAQUFBwEBBIGmMIGjMD8GCCsGAQUFBzAChjNodHRwOi8v
+Y3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5wN2MwOQYI
+KwYBBQUHMAKGLWh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdFVUTlNH
+Q0NBLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTAN
+BgkqhkiG9w0BAQUFAAOCAQEAPtnNLmQPWQFcIiKxcUSdWU1MjI8QWV72uvha40xi
+6gmN15tpoa+QHd75GNaDwqXpqKy2ur/bTQpQ8XijE5FrJtjplLss+w1vfqf1FuKZ
+d9PGUj8GbhjwLijHcgHXnAsSQHYIelxp66HZgpcmwnZARw9WVC0skmWYKLcQstyc
+JrVUEda+uLYDnPC1pfdu8ZecvYgjpUjHjMoNJv1ICacikgQqOn6XpIbvn/8sS+EA
+FMFVbcFckVfsjUPF+X20DAY0w7SnZ4e2xM8aNQiVESAhoXe/LxmKc4HChyPr05my
+fyeJ0o5A+j3uBkkL/vSkNEl3iD1CkZa0LJwL5tRBxN+tkA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert70[] = {
+ 0x30, 0x82, 0x04, 0xfe, 0x30, 0x82, 0x03, 0xe6, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x45, 0x4f, 0xa2, 0x0d, 0x78, 0x11, 0x74, 0x59, 0xf8,
+ 0xc6, 0xab, 0x3c, 0x7b, 0xcd, 0x03, 0x0e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x37, 0x32,
+ 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x81, 0x84, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x42, 0x52, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x1f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x64, 0x6f, 0x72,
+ 0x61, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x31, 0x17, 0x30,
+ 0x15, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0e, 0x53, 0x65, 0x63, 0x75,
+ 0x72, 0x69, 0x74, 0x79, 0x20, 0x44, 0x65, 0x70, 0x74, 0x2e, 0x31, 0x32,
+ 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x42, 0x52, 0x20, 0x43, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x4f,
+ 0x56, 0x29, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3,
+ 0xfb, 0xcb, 0xbf, 0xd7, 0xc0, 0x51, 0xde, 0x8e, 0x5f, 0x6e, 0x76, 0xa7,
+ 0x1f, 0xc3, 0x30, 0x67, 0x68, 0xef, 0x63, 0xa9, 0x0d, 0x3c, 0x7a, 0x0e,
+ 0xf6, 0xdd, 0x26, 0xff, 0xbc, 0x7a, 0x26, 0xd5, 0xb6, 0x22, 0x91, 0x08,
+ 0xb8, 0x38, 0x43, 0xe1, 0x15, 0x3d, 0x0b, 0xf0, 0xb0, 0xdf, 0x9d, 0x34,
+ 0x40, 0x06, 0x5e, 0xea, 0x3b, 0xe9, 0x9b, 0x2e, 0x23, 0xd3, 0xeb, 0x1f,
+ 0x17, 0x1c, 0x16, 0xad, 0x5a, 0x78, 0x41, 0xe6, 0x2d, 0x61, 0xed, 0x7d,
+ 0x2c, 0x5a, 0x5d, 0x8a, 0x6c, 0x0e, 0x70, 0x6b, 0xff, 0xce, 0xc4, 0x80,
+ 0x50, 0x14, 0xcc, 0x2b, 0x60, 0x0f, 0x19, 0x3d, 0xf1, 0x6d, 0xe0, 0xa7,
+ 0xa3, 0x59, 0x22, 0x97, 0x73, 0xec, 0x4a, 0xd6, 0xba, 0xf0, 0x9e, 0xa9,
+ 0x0b, 0xf4, 0x66, 0x3e, 0x13, 0x5f, 0xc8, 0x72, 0x5f, 0x04, 0x4a, 0x13,
+ 0x21, 0xe6, 0x80, 0x85, 0x1f, 0x63, 0x5a, 0x26, 0xf0, 0xc4, 0x25, 0x9b,
+ 0x07, 0x33, 0x71, 0xea, 0x32, 0x26, 0x04, 0xb2, 0xd8, 0x68, 0xff, 0x6a,
+ 0xe6, 0x09, 0x5f, 0x09, 0x83, 0x0c, 0xc3, 0xa8, 0xe5, 0x5a, 0xbf, 0x6c,
+ 0x86, 0xb2, 0x28, 0x6a, 0x94, 0xa3, 0x74, 0x99, 0x4a, 0xd8, 0xd0, 0x11,
+ 0xe5, 0xe4, 0xee, 0x8c, 0xb2, 0x2c, 0x4d, 0x72, 0x86, 0x1b, 0x17, 0x4d,
+ 0xee, 0xcc, 0x9b, 0x5a, 0xb2, 0x36, 0x7d, 0x05, 0x92, 0x17, 0x47, 0x9b,
+ 0xf7, 0x06, 0x9d, 0x96, 0xbe, 0xac, 0xda, 0xc4, 0xdb, 0x97, 0xc0, 0xd4,
+ 0x10, 0xcb, 0x11, 0x26, 0x62, 0x59, 0x17, 0x05, 0x14, 0x63, 0xcb, 0x81,
+ 0x4f, 0x5a, 0xa3, 0x9d, 0xcf, 0x50, 0xe7, 0x4a, 0xb6, 0xe3, 0x30, 0xd5,
+ 0x29, 0x44, 0xac, 0x99, 0x45, 0x8c, 0x0d, 0xd0, 0x97, 0x66, 0xbb, 0x38,
+ 0xf6, 0xad, 0x0f, 0x4d, 0xea, 0xbb, 0x0a, 0x8b, 0x02, 0x27, 0x11, 0x37,
+ 0xeb, 0x42, 0xbb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x7e,
+ 0x30, 0x82, 0x01, 0x7a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4, 0x26,
+ 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb, 0x54,
+ 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x22, 0x82, 0x72, 0x0d, 0xb6, 0x41, 0xe3, 0xdc, 0xe4, 0x81, 0x8b, 0xfb,
+ 0x86, 0x02, 0x11, 0x71, 0x93, 0xfa, 0x9b, 0x4d, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x18, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x11, 0x30, 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b,
+ 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31, 0x01, 0x02, 0x02, 0x26, 0x30, 0x44,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+ 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+ 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6,
+ 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+ 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+ 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47,
+ 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x3e, 0xd9, 0xcd, 0x2e, 0x64, 0x0f,
+ 0x59, 0x01, 0x5c, 0x22, 0x22, 0xb1, 0x71, 0x44, 0x9d, 0x59, 0x4d, 0x4c,
+ 0x8c, 0x8f, 0x10, 0x59, 0x5e, 0xf6, 0xba, 0xf8, 0x5a, 0xe3, 0x4c, 0x62,
+ 0xea, 0x09, 0x8d, 0xd7, 0x9b, 0x69, 0xa1, 0xaf, 0x90, 0x1d, 0xde, 0xf9,
+ 0x18, 0xd6, 0x83, 0xc2, 0xa5, 0xe9, 0xa8, 0xac, 0xb6, 0xba, 0xbf, 0xdb,
+ 0x4d, 0x0a, 0x50, 0xf1, 0x78, 0xa3, 0x13, 0x91, 0x6b, 0x26, 0xd8, 0xe9,
+ 0x94, 0xbb, 0x2c, 0xfb, 0x0d, 0x6f, 0x7e, 0xa7, 0xf5, 0x16, 0xe2, 0x99,
+ 0x77, 0xd3, 0xc6, 0x52, 0x3f, 0x06, 0x6e, 0x18, 0xf0, 0x2e, 0x28, 0xc7,
+ 0x72, 0x01, 0xd7, 0x9c, 0x0b, 0x12, 0x40, 0x76, 0x08, 0x7a, 0x5c, 0x69,
+ 0xeb, 0xa1, 0xd9, 0x82, 0x97, 0x26, 0xc2, 0x76, 0x40, 0x47, 0x0f, 0x56,
+ 0x54, 0x2d, 0x2c, 0x92, 0x65, 0x98, 0x28, 0xb7, 0x10, 0xb2, 0xdc, 0x9c,
+ 0x26, 0xb5, 0x54, 0x11, 0xd6, 0xbe, 0xb8, 0xb6, 0x03, 0x9c, 0xf0, 0xb5,
+ 0xa5, 0xf7, 0x6e, 0xf1, 0x97, 0x9c, 0xbd, 0x88, 0x23, 0xa5, 0x48, 0xc7,
+ 0x8c, 0xca, 0x0d, 0x26, 0xfd, 0x48, 0x09, 0xa7, 0x22, 0x92, 0x04, 0x2a,
+ 0x3a, 0x7e, 0x97, 0xa4, 0x86, 0xef, 0x9f, 0xff, 0x2c, 0x4b, 0xe1, 0x00,
+ 0x14, 0xc1, 0x55, 0x6d, 0xc1, 0x5c, 0x91, 0x57, 0xec, 0x8d, 0x43, 0xc5,
+ 0xf9, 0x7d, 0xb4, 0x0c, 0x06, 0x34, 0xc3, 0xb4, 0xa7, 0x67, 0x87, 0xb6,
+ 0xc4, 0xcf, 0x1a, 0x35, 0x08, 0x95, 0x11, 0x20, 0x21, 0xa1, 0x77, 0xbf,
+ 0x2f, 0x19, 0x8a, 0x73, 0x81, 0xc2, 0x87, 0x23, 0xeb, 0xd3, 0x99, 0xb2,
+ 0x7f, 0x27, 0x89, 0xd2, 0x8e, 0x40, 0xfa, 0x3d, 0xee, 0x06, 0x49, 0x0b,
+ 0xfe, 0xf4, 0xa4, 0x34, 0x49, 0x77, 0x88, 0x3d, 0x42, 0x91, 0x96, 0xb4,
+ 0x2c, 0x9c, 0x0b, 0xe6, 0xd4, 0x41, 0xc4, 0xdf, 0xad, 0x90,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120025006 (0x7276fae)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root
+ Validity
+ Not Before: Apr 25 17:41:36 2012 GMT
+ Not After : Apr 25 17:40:55 2020 GMT
+ Subject: CN=Microsoft Internet Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (4096 bit)
+ Modulus:
+ 00:be:40:54:11:aa:28:79:66:5e:a9:bb:b0:93:62:
+ 4b:73:19:aa:e9:2f:91:4c:6d:97:37:25:68:b8:b0:
+ 36:50:94:5c:ef:ff:c4:c4:17:76:43:57:fc:a0:e1:
+ c6:33:ec:30:62:be:35:0f:23:7a:96:4d:6c:74:62:
+ 09:7e:31:48:4e:9f:4d:aa:5b:b3:16:b7:fe:a0:29:
+ 2f:65:a5:4b:ac:6a:56:8a:bf:28:70:6a:df:37:18:
+ df:ec:c6:87:22:3e:16:dd:38:1c:0f:e2:05:be:77:
+ ab:ee:85:8a:1e:0d:23:3f:e9:24:e1:90:47:b2:67:
+ 3b:15:3e:55:20:3c:f9:f5:42:d7:af:c5:66:0d:9a:
+ 94:17:b9:6a:67:7c:a8:fa:b1:26:00:04:23:d5:4d:
+ a3:ae:8d:79:60:b1:8e:83:4f:cd:bb:77:78:27:b2:
+ e8:74:ad:1f:4c:34:80:31:7f:8b:e4:50:7e:6f:7f:
+ cf:55:da:c6:fb:b6:5a:f5:5d:1d:38:94:a5:fe:b0:
+ 8d:22:63:23:e7:70:40:40:bf:89:aa:54:46:25:03:
+ ec:a4:f2:ad:c0:40:b3:72:fe:94:b5:d9:96:b2:1b:
+ 73:5e:d5:f4:6b:47:41:79:a4:da:f3:8e:2e:8d:38:
+ 4d:c9:5e:17:1c:ae:b5:4c:36:6b:e8:7f:d9:24:c7:
+ 28:f0:a7:86:be:e8:1d:08:b4:db:72:23:64:d1:42:
+ f3:f5:4c:da:ac:f3:b6:a5:75:68:fc:f0:bb:02:61:
+ 5d:1e:7a:07:52:29:be:74:12:42:53:9c:8d:cc:e9:
+ 88:7b:a4:55:36:08:58:8c:21:89:c5:ba:13:e8:6d:
+ 9b:81:8a:77:c6:38:6c:f5:3a:1d:91:37:57:2b:a9:
+ eb:2b:46:4a:a8:97:28:8b:a3:7e:4a:dd:8d:a7:d3:
+ 02:10:7c:96:b0:c8:bc:86:28:06:d6:57:a2:a7:66:
+ f1:17:d6:cc:5f:55:12:3d:59:03:a2:c7:d2:d6:69:
+ b4:0d:25:f9:f1:d3:94:56:16:2e:26:bc:96:f9:ba:
+ 1c:51:9b:81:f1:37:bc:77:ee:7e:d9:9a:78:f6:41:
+ b4:71:df:10:25:5b:b2:e1:3a:c7:7b:f2:6c:b3:19:
+ b7:20:e7:89:e9:c4:a5:6a:a4:7c:39:24:ee:e7:5d:
+ 2b:2b:c9:91:fe:a1:3c:33:25:bd:c5:41:14:92:ee:
+ cf:56:3a:26:13:75:ca:11:5c:c6:27:ab:49:88:ab:
+ 55:72:fc:65:48:dc:ae:cd:f1:8f:8b:10:66:2a:90:
+ 2e:0b:8b:b7:fe:38:75:cd:b1:75:80:b6:8f:cf:43:
+ 1c:21:29:93:0f:e3:13:b9:e2:b0:c8:b2:46:2a:5a:
+ 14:0c:1d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ keyid:E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://cdp1.public-trust.com/CRL/Omniroot2025.crl
+
+ X509v3 Subject Key Identifier:
+ 2A:4D:97:95:5D:34:7E:9D:B6:E6:33:BE:9C:27:C1:70:7E:67:DB:C1
+ Signature Algorithm: sha1WithRSAEncryption
+ 23:3e:87:6e:d3:ca:d6:0e:b0:36:25:8d:cc:0a:ce:dc:5d:74:
+ f3:42:1b:ef:aa:c0:49:73:85:cc:7d:2f:77:09:23:c5:2c:50:
+ 1d:46:c3:68:68:ef:10:32:d9:4d:7e:e3:5a:e3:1d:18:3c:d2:
+ 40:54:cd:ef:59:ee:df:fe:fb:70:4a:bc:d8:74:37:2b:dc:a4:
+ 68:71:50:ba:63:cb:44:dd:11:48:ff:f8:f1:ff:60:ba:7c:aa:
+ 30:7d:dd:17:b1:77:ea:50:90:20:00:0b:a3:3d:5d:98:71:51:
+ 9f:dd:2d:c0:78:5e:0d:55:b1:83:45:de:e5:59:98:6d:a4:e1:
+ 69:7c:32:d0:04:7b:f7:a9:1d:97:3b:d5:59:bf:cb:6f:9d:a4:
+ b6:a5:bb:41:11:ed:c8:91:83:15:55:ae:59:36:b7:9f:6a:f0:
+ b8:38:f9:7c:32:25:95:cc:33:f1:31:e7:df:cb:78:4b:36:1f:
+ f4:55:e0:bd:28:f9:ca:b9:64:99:ce:eb:61:e9:81:72:94:d3:
+ 9b:cd:0a:2b:2a:a4:94:83:ae:6a:0c:23:44:0e:35:ad:a1:e9:
+ ec:d8:d7:75:90:8d:e1:d6:b3:c5:50:fc:5d:d5:fb:6f:92:e1:
+ f4:7e:f0:ae:af:f9:39:b3:ce:4b:01:9c:bd:4e:f7:f1:f2:6f:
+ ce:c0:36:d8
+-----BEGIN CERTIFICATE-----
+MIIFATCCA+mgAwIBAgIEBydvrjANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTEyMDQyNTE3NDEzNloX
+DTIwMDQyNTE3NDA1NVowJzElMCMGA1UEAxMcTWljcm9zb2Z0IEludGVybmV0IEF1
+dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5AVBGqKHlm
+Xqm7sJNiS3MZqukvkUxtlzclaLiwNlCUXO//xMQXdkNX/KDhxjPsMGK+NQ8jepZN
+bHRiCX4xSE6fTapbsxa3/qApL2WlS6xqVoq/KHBq3zcY3+zGhyI+Ft04HA/iBb53
+q+6Fih4NIz/pJOGQR7JnOxU+VSA8+fVC16/FZg2alBe5amd8qPqxJgAEI9VNo66N
+eWCxjoNPzbt3eCey6HStH0w0gDF/i+RQfm9/z1Xaxvu2WvVdHTiUpf6wjSJjI+dw
+QEC/iapURiUD7KTyrcBAs3L+lLXZlrIbc17V9GtHQXmk2vOOLo04TcleFxyutUw2
+a+h/2STHKPCnhr7oHQi023IjZNFC8/VM2qzztqV1aPzwuwJhXR56B1IpvnQSQlOc
+jczpiHukVTYIWIwhicW6E+htm4GKd8Y4bPU6HZE3Vyup6ytGSqiXKIujfkrdjafT
+AhB8lrDIvIYoBtZXoqdm8RfWzF9VEj1ZA6LH0tZptA0l+fHTlFYWLia8lvm6HFGb
+gfE3vHfuftmaePZBtHHfECVbsuE6x3vybLMZtyDnienEpWqkfDkk7uddKyvJkf6h
+PDMlvcVBFJLuz1Y6JhN1yhFcxierSYirVXL8ZUjcrs3xj4sQZiqQLguLt/44dc2x
+dYC2j89DHCEpkw/jE7nisMiyRipaFAwdAgMBAAGjggEAMIH9MBIGA1UdEwEB/wQI
+MAYBAf8CAQEwUwYDVR0gBEwwSjBIBgkrBgEEAbE+AQAwOzA5BggrBgEFBQcCARYt
+aHR0cDovL2N5YmVydHJ1c3Qub21uaXJvb3QuY29tL3JlcG9zaXRvcnkuY2ZtMA4G
+A1UdDwEB/wQEAwIBhjAfBgNVHSMEGDAWgBTlnVkwgkdYzKz6CFQ2hns6tQRN8DBC
+BgNVHR8EOzA5MDegNaAzhjFodHRwOi8vY2RwMS5wdWJsaWMtdHJ1c3QuY29tL0NS
+TC9PbW5pcm9vdDIwMjUuY3JsMB0GA1UdDgQWBBQqTZeVXTR+nbbmM76cJ8Fwfmfb
+wTANBgkqhkiG9w0BAQUFAAOCAQEAIz6HbtPK1g6wNiWNzArO3F1080Ib76rASXOF
+zH0vdwkjxSxQHUbDaGjvEDLZTX7jWuMdGDzSQFTN71nu3/77cEq82HQ3K9ykaHFQ
+umPLRN0RSP/48f9gunyqMH3dF7F36lCQIAALoz1dmHFRn90twHheDVWxg0Xe5VmY
+baThaXwy0AR796kdlzvVWb/Lb52ktqW7QRHtyJGDFVWuWTa3n2rwuDj5fDIllcwz
+8THn38t4SzYf9FXgvSj5yrlkmc7rYemBcpTTm80KKyqklIOuagwjRA41raHp7NjX
+dZCN4dazxVD8XdX7b5Lh9H7wrq/5ObPOSwGcvU738fJvzsA22A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert71[] = {
+ 0x30, 0x82, 0x05, 0x01, 0x30, 0x82, 0x03, 0xe9, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x6f, 0xae, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5a,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49,
+ 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x09,
+ 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79, 0x62, 0x65,
+ 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f,
+ 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x32,
+ 0x30, 0x34, 0x32, 0x35, 0x31, 0x37, 0x34, 0x31, 0x33, 0x36, 0x5a, 0x17,
+ 0x0d, 0x32, 0x30, 0x30, 0x34, 0x32, 0x35, 0x31, 0x37, 0x34, 0x30, 0x35,
+ 0x35, 0x5a, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+ 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82, 0x02, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02,
+ 0x82, 0x02, 0x01, 0x00, 0xbe, 0x40, 0x54, 0x11, 0xaa, 0x28, 0x79, 0x66,
+ 0x5e, 0xa9, 0xbb, 0xb0, 0x93, 0x62, 0x4b, 0x73, 0x19, 0xaa, 0xe9, 0x2f,
+ 0x91, 0x4c, 0x6d, 0x97, 0x37, 0x25, 0x68, 0xb8, 0xb0, 0x36, 0x50, 0x94,
+ 0x5c, 0xef, 0xff, 0xc4, 0xc4, 0x17, 0x76, 0x43, 0x57, 0xfc, 0xa0, 0xe1,
+ 0xc6, 0x33, 0xec, 0x30, 0x62, 0xbe, 0x35, 0x0f, 0x23, 0x7a, 0x96, 0x4d,
+ 0x6c, 0x74, 0x62, 0x09, 0x7e, 0x31, 0x48, 0x4e, 0x9f, 0x4d, 0xaa, 0x5b,
+ 0xb3, 0x16, 0xb7, 0xfe, 0xa0, 0x29, 0x2f, 0x65, 0xa5, 0x4b, 0xac, 0x6a,
+ 0x56, 0x8a, 0xbf, 0x28, 0x70, 0x6a, 0xdf, 0x37, 0x18, 0xdf, 0xec, 0xc6,
+ 0x87, 0x22, 0x3e, 0x16, 0xdd, 0x38, 0x1c, 0x0f, 0xe2, 0x05, 0xbe, 0x77,
+ 0xab, 0xee, 0x85, 0x8a, 0x1e, 0x0d, 0x23, 0x3f, 0xe9, 0x24, 0xe1, 0x90,
+ 0x47, 0xb2, 0x67, 0x3b, 0x15, 0x3e, 0x55, 0x20, 0x3c, 0xf9, 0xf5, 0x42,
+ 0xd7, 0xaf, 0xc5, 0x66, 0x0d, 0x9a, 0x94, 0x17, 0xb9, 0x6a, 0x67, 0x7c,
+ 0xa8, 0xfa, 0xb1, 0x26, 0x00, 0x04, 0x23, 0xd5, 0x4d, 0xa3, 0xae, 0x8d,
+ 0x79, 0x60, 0xb1, 0x8e, 0x83, 0x4f, 0xcd, 0xbb, 0x77, 0x78, 0x27, 0xb2,
+ 0xe8, 0x74, 0xad, 0x1f, 0x4c, 0x34, 0x80, 0x31, 0x7f, 0x8b, 0xe4, 0x50,
+ 0x7e, 0x6f, 0x7f, 0xcf, 0x55, 0xda, 0xc6, 0xfb, 0xb6, 0x5a, 0xf5, 0x5d,
+ 0x1d, 0x38, 0x94, 0xa5, 0xfe, 0xb0, 0x8d, 0x22, 0x63, 0x23, 0xe7, 0x70,
+ 0x40, 0x40, 0xbf, 0x89, 0xaa, 0x54, 0x46, 0x25, 0x03, 0xec, 0xa4, 0xf2,
+ 0xad, 0xc0, 0x40, 0xb3, 0x72, 0xfe, 0x94, 0xb5, 0xd9, 0x96, 0xb2, 0x1b,
+ 0x73, 0x5e, 0xd5, 0xf4, 0x6b, 0x47, 0x41, 0x79, 0xa4, 0xda, 0xf3, 0x8e,
+ 0x2e, 0x8d, 0x38, 0x4d, 0xc9, 0x5e, 0x17, 0x1c, 0xae, 0xb5, 0x4c, 0x36,
+ 0x6b, 0xe8, 0x7f, 0xd9, 0x24, 0xc7, 0x28, 0xf0, 0xa7, 0x86, 0xbe, 0xe8,
+ 0x1d, 0x08, 0xb4, 0xdb, 0x72, 0x23, 0x64, 0xd1, 0x42, 0xf3, 0xf5, 0x4c,
+ 0xda, 0xac, 0xf3, 0xb6, 0xa5, 0x75, 0x68, 0xfc, 0xf0, 0xbb, 0x02, 0x61,
+ 0x5d, 0x1e, 0x7a, 0x07, 0x52, 0x29, 0xbe, 0x74, 0x12, 0x42, 0x53, 0x9c,
+ 0x8d, 0xcc, 0xe9, 0x88, 0x7b, 0xa4, 0x55, 0x36, 0x08, 0x58, 0x8c, 0x21,
+ 0x89, 0xc5, 0xba, 0x13, 0xe8, 0x6d, 0x9b, 0x81, 0x8a, 0x77, 0xc6, 0x38,
+ 0x6c, 0xf5, 0x3a, 0x1d, 0x91, 0x37, 0x57, 0x2b, 0xa9, 0xeb, 0x2b, 0x46,
+ 0x4a, 0xa8, 0x97, 0x28, 0x8b, 0xa3, 0x7e, 0x4a, 0xdd, 0x8d, 0xa7, 0xd3,
+ 0x02, 0x10, 0x7c, 0x96, 0xb0, 0xc8, 0xbc, 0x86, 0x28, 0x06, 0xd6, 0x57,
+ 0xa2, 0xa7, 0x66, 0xf1, 0x17, 0xd6, 0xcc, 0x5f, 0x55, 0x12, 0x3d, 0x59,
+ 0x03, 0xa2, 0xc7, 0xd2, 0xd6, 0x69, 0xb4, 0x0d, 0x25, 0xf9, 0xf1, 0xd3,
+ 0x94, 0x56, 0x16, 0x2e, 0x26, 0xbc, 0x96, 0xf9, 0xba, 0x1c, 0x51, 0x9b,
+ 0x81, 0xf1, 0x37, 0xbc, 0x77, 0xee, 0x7e, 0xd9, 0x9a, 0x78, 0xf6, 0x41,
+ 0xb4, 0x71, 0xdf, 0x10, 0x25, 0x5b, 0xb2, 0xe1, 0x3a, 0xc7, 0x7b, 0xf2,
+ 0x6c, 0xb3, 0x19, 0xb7, 0x20, 0xe7, 0x89, 0xe9, 0xc4, 0xa5, 0x6a, 0xa4,
+ 0x7c, 0x39, 0x24, 0xee, 0xe7, 0x5d, 0x2b, 0x2b, 0xc9, 0x91, 0xfe, 0xa1,
+ 0x3c, 0x33, 0x25, 0xbd, 0xc5, 0x41, 0x14, 0x92, 0xee, 0xcf, 0x56, 0x3a,
+ 0x26, 0x13, 0x75, 0xca, 0x11, 0x5c, 0xc6, 0x27, 0xab, 0x49, 0x88, 0xab,
+ 0x55, 0x72, 0xfc, 0x65, 0x48, 0xdc, 0xae, 0xcd, 0xf1, 0x8f, 0x8b, 0x10,
+ 0x66, 0x2a, 0x90, 0x2e, 0x0b, 0x8b, 0xb7, 0xfe, 0x38, 0x75, 0xcd, 0xb1,
+ 0x75, 0x80, 0xb6, 0x8f, 0xcf, 0x43, 0x1c, 0x21, 0x29, 0x93, 0x0f, 0xe3,
+ 0x13, 0xb9, 0xe2, 0xb0, 0xc8, 0xb2, 0x46, 0x2a, 0x5a, 0x14, 0x0c, 0x1d,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x00, 0x30, 0x81, 0xfd,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x53, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x4c, 0x30, 0x4a, 0x30, 0x48, 0x06, 0x09, 0x2b,
+ 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00, 0x30, 0x3b, 0x30, 0x39,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2d,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x79, 0x62, 0x65, 0x72,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d, 0x6e, 0x69, 0x72, 0x6f,
+ 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+ 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66, 0x6d, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x86, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xe5, 0x9d, 0x59, 0x30, 0x82, 0x47, 0x58, 0xcc, 0xac, 0xfa,
+ 0x08, 0x54, 0x36, 0x86, 0x7b, 0x3a, 0xb5, 0x04, 0x4d, 0xf0, 0x30, 0x42,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0xa0,
+ 0x35, 0xa0, 0x33, 0x86, 0x31, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x64, 0x70, 0x31, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x52,
+ 0x4c, 0x2f, 0x4f, 0x6d, 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x32, 0x30,
+ 0x32, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2a, 0x4d, 0x97, 0x95, 0x5d, 0x34, 0x7e,
+ 0x9d, 0xb6, 0xe6, 0x33, 0xbe, 0x9c, 0x27, 0xc1, 0x70, 0x7e, 0x67, 0xdb,
+ 0xc1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x23, 0x3e, 0x87,
+ 0x6e, 0xd3, 0xca, 0xd6, 0x0e, 0xb0, 0x36, 0x25, 0x8d, 0xcc, 0x0a, 0xce,
+ 0xdc, 0x5d, 0x74, 0xf3, 0x42, 0x1b, 0xef, 0xaa, 0xc0, 0x49, 0x73, 0x85,
+ 0xcc, 0x7d, 0x2f, 0x77, 0x09, 0x23, 0xc5, 0x2c, 0x50, 0x1d, 0x46, 0xc3,
+ 0x68, 0x68, 0xef, 0x10, 0x32, 0xd9, 0x4d, 0x7e, 0xe3, 0x5a, 0xe3, 0x1d,
+ 0x18, 0x3c, 0xd2, 0x40, 0x54, 0xcd, 0xef, 0x59, 0xee, 0xdf, 0xfe, 0xfb,
+ 0x70, 0x4a, 0xbc, 0xd8, 0x74, 0x37, 0x2b, 0xdc, 0xa4, 0x68, 0x71, 0x50,
+ 0xba, 0x63, 0xcb, 0x44, 0xdd, 0x11, 0x48, 0xff, 0xf8, 0xf1, 0xff, 0x60,
+ 0xba, 0x7c, 0xaa, 0x30, 0x7d, 0xdd, 0x17, 0xb1, 0x77, 0xea, 0x50, 0x90,
+ 0x20, 0x00, 0x0b, 0xa3, 0x3d, 0x5d, 0x98, 0x71, 0x51, 0x9f, 0xdd, 0x2d,
+ 0xc0, 0x78, 0x5e, 0x0d, 0x55, 0xb1, 0x83, 0x45, 0xde, 0xe5, 0x59, 0x98,
+ 0x6d, 0xa4, 0xe1, 0x69, 0x7c, 0x32, 0xd0, 0x04, 0x7b, 0xf7, 0xa9, 0x1d,
+ 0x97, 0x3b, 0xd5, 0x59, 0xbf, 0xcb, 0x6f, 0x9d, 0xa4, 0xb6, 0xa5, 0xbb,
+ 0x41, 0x11, 0xed, 0xc8, 0x91, 0x83, 0x15, 0x55, 0xae, 0x59, 0x36, 0xb7,
+ 0x9f, 0x6a, 0xf0, 0xb8, 0x38, 0xf9, 0x7c, 0x32, 0x25, 0x95, 0xcc, 0x33,
+ 0xf1, 0x31, 0xe7, 0xdf, 0xcb, 0x78, 0x4b, 0x36, 0x1f, 0xf4, 0x55, 0xe0,
+ 0xbd, 0x28, 0xf9, 0xca, 0xb9, 0x64, 0x99, 0xce, 0xeb, 0x61, 0xe9, 0x81,
+ 0x72, 0x94, 0xd3, 0x9b, 0xcd, 0x0a, 0x2b, 0x2a, 0xa4, 0x94, 0x83, 0xae,
+ 0x6a, 0x0c, 0x23, 0x44, 0x0e, 0x35, 0xad, 0xa1, 0xe9, 0xec, 0xd8, 0xd7,
+ 0x75, 0x90, 0x8d, 0xe1, 0xd6, 0xb3, 0xc5, 0x50, 0xfc, 0x5d, 0xd5, 0xfb,
+ 0x6f, 0x92, 0xe1, 0xf4, 0x7e, 0xf0, 0xae, 0xaf, 0xf9, 0x39, 0xb3, 0xce,
+ 0x4b, 0x01, 0x9c, 0xbd, 0x4e, 0xf7, 0xf1, 0xf2, 0x6f, 0xce, 0xc0, 0x36,
+ 0xd8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 20:93:42:da:a7:64:7c:05:c5:f5:fd:93:76:a4:42:8c
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Validity
+ Not Before: Jan 17 00:00:00 2007 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=RU, O=RBC Hosting Center, CN=RBC HC High Assurance Services CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c7:16:e6:21:49:a0:b4:13:ca:18:12:2c:a8:aa:
+ fa:46:9c:d1:5b:5a:1d:26:a3:e6:11:68:3b:02:00:
+ ea:a5:be:95:f7:4c:d7:a1:bc:70:9a:a2:ec:9f:de:
+ d1:b3:1a:c8:16:e0:68:cf:8a:23:3a:6b:1b:59:94:
+ 49:7d:60:cf:51:c6:e3:66:45:f9:6a:5d:f7:88:e9:
+ a1:b1:ab:96:17:3c:46:72:a9:4f:8b:a0:58:cb:bf:
+ c3:9d:91:c4:67:c6:33:ef:fb:16:8b:94:0a:0a:a6:
+ 52:b9:fe:c5:3e:a4:37:c5:ca:ce:b1:bf:a2:db:49:
+ 63:3e:cd:21:75:fa:28:8f:e0:67:4e:32:b2:c3:f3:
+ ea:b2:8b:e5:6a:93:2d:90:dd:19:45:12:f3:ce:16:
+ 7d:1c:ca:b1:93:c0:02:41:97:d0:b7:fc:ac:4f:86:
+ 2c:61:de:cd:ad:ac:4f:19:6b:e2:9e:91:3e:9b:3d:
+ e5:ed:4f:df:d1:10:59:c4:18:f5:25:2c:de:73:ad:
+ 7b:25:31:b5:5d:c5:91:95:f3:6b:9f:fa:d2:b8:ad:
+ 23:7c:9e:45:86:72:aa:09:c0:86:29:e4:75:63:5b:
+ 7d:87:77:d2:89:51:91:27:3a:08:97:25:0d:83:21:
+ 9f:a7:39:fc:43:41:74:c9:ed:c7:00:9d:eb:3c:41:
+ f7:d5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+ X509v3 Subject Key Identifier:
+ 66:8F:F1:9F:4D:DB:8E:DD:FA:EC:1B:99:13:6A:B8:82:68:85:06:ED
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6449.1.2.2.16
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/UTNAddTrustServerCA.crt
+ CA Issuers - URI:http://crt.comodo.net/UTNAddTrustServerCA.crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 65:2c:c5:19:68:1d:3c:1c:e9:15:9f:a5:b4:fe:32:19:d0:76:
+ f3:b3:f8:58:b5:10:e1:4e:44:f3:c1:e1:b5:98:81:41:69:d7:
+ 89:d4:05:49:e3:15:30:4a:24:c4:1e:67:e7:3d:ab:99:b9:85:
+ 91:04:2f:15:3e:42:a6:e6:e6:25:ac:6e:30:91:3f:03:3f:78:
+ f9:2f:ad:15:fa:e6:ad:1e:57:ea:7b:cc:e4:ce:3a:89:5d:3d:
+ 89:d1:ba:8a:fa:2d:f5:eb:a9:31:95:50:06:f4:34:f3:95:a6:
+ 9b:43:41:20:65:3b:ab:85:d7:e0:53:53:df:54:89:f7:10:c1:
+ 67:79:b0:2e:a2:8b:c8:cd:02:c7:92:5d:e1:4c:e7:d1:87:bb:
+ 8f:82:ee:ce:b7:0c:7c:68:76:da:f3:1f:e5:96:71:e8:77:97:
+ 34:06:13:55:28:81:58:40:75:74:47:28:97:14:0a:2b:53:b0:
+ eb:6e:42:68:76:ef:67:21:61:b6:64:74:08:70:6d:11:c6:e7:
+ 7f:e5:36:1c:66:3a:fd:4e:7a:63:9e:15:ce:cf:a9:04:e6:30:
+ ea:10:d2:5a:81:24:4b:c2:f2:3b:fb:34:e6:69:7b:ff:5d:9b:
+ 30:46:59:69:2d:0d:f2:7f:fc:fb:0a:29:ea:35:14:d0:bb:3c:
+ a3:b1:10:41
+-----BEGIN CERTIFICATE-----
+MIIFAjCCA+qgAwIBAgIQIJNC2qdkfAXF9f2TdqRCjDANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDcwMTE3MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBWMQswCQYD
+VQQGEwJSVTEbMBkGA1UEChMSUkJDIEhvc3RpbmcgQ2VudGVyMSowKAYDVQQDEyFS
+QkMgSEMgSGlnaCBBc3N1cmFuY2UgU2VydmljZXMgQ0EwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDHFuYhSaC0E8oYEiyoqvpGnNFbWh0mo+YRaDsCAOql
+vpX3TNehvHCaouyf3tGzGsgW4GjPiiM6axtZlEl9YM9RxuNmRflqXfeI6aGxq5YX
+PEZyqU+LoFjLv8OdkcRnxjPv+xaLlAoKplK5/sU+pDfFys6xv6LbSWM+zSF1+iiP
+4GdOMrLD8+qyi+Vqky2Q3RlFEvPOFn0cyrGTwAJBl9C3/KxPhixh3s2trE8Za+Ke
+kT6bPeXtT9/REFnEGPUlLN5zrXslMbVdxZGV82uf+tK4rSN8nkWGcqoJwIYp5HVj
+W32Hd9KJUZEnOgiXJQ2DIZ+nOfxDQXTJ7ccAnes8QffVAgMBAAGjggGIMIIBhDAf
+BgNVHSMEGDAWgBShcl8mGyiYQ5VdBzfVhZadS9LDRTAdBgNVHQ4EFgQUZo/xn03b
+jt367BuZE2q4gmiFBu0wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwGAYDVR0gBBEwDzANBgsrBgEEAbIxAQICEDB7BgNVHR8EdDByMDigNqA0hjJo
+dHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDA2oDSgMoYwaHR0cDovL2NybC5jb21vZG8ubmV0L1VUTi1VU0VSRmlyc3QtSGFy
+ZHdhcmUuY3JsMIGGBggrBgEFBQcBAQR6MHgwOwYIKwYBBQUHMAKGL2h0dHA6Ly9j
+cnQuY29tb2RvY2EuY29tL1VUTkFkZFRydXN0U2VydmVyQ0EuY3J0MDkGCCsGAQUF
+BzAChi1odHRwOi8vY3J0LmNvbW9kby5uZXQvVVROQWRkVHJ1c3RTZXJ2ZXJDQS5j
+cnQwDQYJKoZIhvcNAQEFBQADggEBAGUsxRloHTwc6RWfpbT+MhnQdvOz+Fi1EOFO
+RPPB4bWYgUFp14nUBUnjFTBKJMQeZ+c9q5m5hZEELxU+Qqbm5iWsbjCRPwM/ePkv
+rRX65q0eV+p7zOTOOoldPYnRuor6LfXrqTGVUAb0NPOVpptDQSBlO6uF1+BTU99U
+ifcQwWd5sC6ii8jNAseSXeFM59GHu4+C7s63DHxodtrzH+WWceh3lzQGE1UogVhA
+dXRHKJcUCitTsOtuQmh272chYbZkdAhwbRHG53/lNhxmOv1OemOeFc7PqQTmMOoQ
+0lqBJEvC8jv7NOZpe/9dmzBGWWktDfJ//PsKKeo1FNC7PKOxEEE=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert72[] = {
+ 0x30, 0x82, 0x05, 0x02, 0x30, 0x82, 0x03, 0xea, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x20, 0x93, 0x42, 0xda, 0xa7, 0x64, 0x7c, 0x05, 0xc5,
+ 0xf5, 0xfd, 0x93, 0x76, 0xa4, 0x42, 0x8c, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+ 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+ 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x37, 0x30, 0x31, 0x31, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+ 0x38, 0x33, 0x38, 0x5a, 0x30, 0x56, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x52, 0x55, 0x31, 0x1b, 0x30, 0x19, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x12, 0x52, 0x42, 0x43, 0x20, 0x48, 0x6f,
+ 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72,
+ 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x52,
+ 0x42, 0x43, 0x20, 0x48, 0x43, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+ 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xc7, 0x16, 0xe6, 0x21, 0x49, 0xa0, 0xb4,
+ 0x13, 0xca, 0x18, 0x12, 0x2c, 0xa8, 0xaa, 0xfa, 0x46, 0x9c, 0xd1, 0x5b,
+ 0x5a, 0x1d, 0x26, 0xa3, 0xe6, 0x11, 0x68, 0x3b, 0x02, 0x00, 0xea, 0xa5,
+ 0xbe, 0x95, 0xf7, 0x4c, 0xd7, 0xa1, 0xbc, 0x70, 0x9a, 0xa2, 0xec, 0x9f,
+ 0xde, 0xd1, 0xb3, 0x1a, 0xc8, 0x16, 0xe0, 0x68, 0xcf, 0x8a, 0x23, 0x3a,
+ 0x6b, 0x1b, 0x59, 0x94, 0x49, 0x7d, 0x60, 0xcf, 0x51, 0xc6, 0xe3, 0x66,
+ 0x45, 0xf9, 0x6a, 0x5d, 0xf7, 0x88, 0xe9, 0xa1, 0xb1, 0xab, 0x96, 0x17,
+ 0x3c, 0x46, 0x72, 0xa9, 0x4f, 0x8b, 0xa0, 0x58, 0xcb, 0xbf, 0xc3, 0x9d,
+ 0x91, 0xc4, 0x67, 0xc6, 0x33, 0xef, 0xfb, 0x16, 0x8b, 0x94, 0x0a, 0x0a,
+ 0xa6, 0x52, 0xb9, 0xfe, 0xc5, 0x3e, 0xa4, 0x37, 0xc5, 0xca, 0xce, 0xb1,
+ 0xbf, 0xa2, 0xdb, 0x49, 0x63, 0x3e, 0xcd, 0x21, 0x75, 0xfa, 0x28, 0x8f,
+ 0xe0, 0x67, 0x4e, 0x32, 0xb2, 0xc3, 0xf3, 0xea, 0xb2, 0x8b, 0xe5, 0x6a,
+ 0x93, 0x2d, 0x90, 0xdd, 0x19, 0x45, 0x12, 0xf3, 0xce, 0x16, 0x7d, 0x1c,
+ 0xca, 0xb1, 0x93, 0xc0, 0x02, 0x41, 0x97, 0xd0, 0xb7, 0xfc, 0xac, 0x4f,
+ 0x86, 0x2c, 0x61, 0xde, 0xcd, 0xad, 0xac, 0x4f, 0x19, 0x6b, 0xe2, 0x9e,
+ 0x91, 0x3e, 0x9b, 0x3d, 0xe5, 0xed, 0x4f, 0xdf, 0xd1, 0x10, 0x59, 0xc4,
+ 0x18, 0xf5, 0x25, 0x2c, 0xde, 0x73, 0xad, 0x7b, 0x25, 0x31, 0xb5, 0x5d,
+ 0xc5, 0x91, 0x95, 0xf3, 0x6b, 0x9f, 0xfa, 0xd2, 0xb8, 0xad, 0x23, 0x7c,
+ 0x9e, 0x45, 0x86, 0x72, 0xaa, 0x09, 0xc0, 0x86, 0x29, 0xe4, 0x75, 0x63,
+ 0x5b, 0x7d, 0x87, 0x77, 0xd2, 0x89, 0x51, 0x91, 0x27, 0x3a, 0x08, 0x97,
+ 0x25, 0x0d, 0x83, 0x21, 0x9f, 0xa7, 0x39, 0xfc, 0x43, 0x41, 0x74, 0xc9,
+ 0xed, 0xc7, 0x00, 0x9d, 0xeb, 0x3c, 0x41, 0xf7, 0xd5, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x88, 0x30, 0x82, 0x01, 0x84, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xa1,
+ 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d, 0x07, 0x37, 0xd5,
+ 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x66, 0x8f, 0xf1, 0x9f, 0x4d, 0xdb,
+ 0x8e, 0xdd, 0xfa, 0xec, 0x1b, 0x99, 0x13, 0x6a, 0xb8, 0x82, 0x68, 0x85,
+ 0x06, 0xed, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02,
+ 0x01, 0x00, 0x30, 0x18, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x11, 0x30,
+ 0x0f, 0x30, 0x0d, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb2, 0x31,
+ 0x01, 0x02, 0x02, 0x10, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55,
+ 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74,
+ 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+ 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d, 0x55,
+ 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61, 0x72,
+ 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x86,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x7a,
+ 0x30, 0x78, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e,
+ 0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e, 0x63,
+ 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x65, 0x2c,
+ 0xc5, 0x19, 0x68, 0x1d, 0x3c, 0x1c, 0xe9, 0x15, 0x9f, 0xa5, 0xb4, 0xfe,
+ 0x32, 0x19, 0xd0, 0x76, 0xf3, 0xb3, 0xf8, 0x58, 0xb5, 0x10, 0xe1, 0x4e,
+ 0x44, 0xf3, 0xc1, 0xe1, 0xb5, 0x98, 0x81, 0x41, 0x69, 0xd7, 0x89, 0xd4,
+ 0x05, 0x49, 0xe3, 0x15, 0x30, 0x4a, 0x24, 0xc4, 0x1e, 0x67, 0xe7, 0x3d,
+ 0xab, 0x99, 0xb9, 0x85, 0x91, 0x04, 0x2f, 0x15, 0x3e, 0x42, 0xa6, 0xe6,
+ 0xe6, 0x25, 0xac, 0x6e, 0x30, 0x91, 0x3f, 0x03, 0x3f, 0x78, 0xf9, 0x2f,
+ 0xad, 0x15, 0xfa, 0xe6, 0xad, 0x1e, 0x57, 0xea, 0x7b, 0xcc, 0xe4, 0xce,
+ 0x3a, 0x89, 0x5d, 0x3d, 0x89, 0xd1, 0xba, 0x8a, 0xfa, 0x2d, 0xf5, 0xeb,
+ 0xa9, 0x31, 0x95, 0x50, 0x06, 0xf4, 0x34, 0xf3, 0x95, 0xa6, 0x9b, 0x43,
+ 0x41, 0x20, 0x65, 0x3b, 0xab, 0x85, 0xd7, 0xe0, 0x53, 0x53, 0xdf, 0x54,
+ 0x89, 0xf7, 0x10, 0xc1, 0x67, 0x79, 0xb0, 0x2e, 0xa2, 0x8b, 0xc8, 0xcd,
+ 0x02, 0xc7, 0x92, 0x5d, 0xe1, 0x4c, 0xe7, 0xd1, 0x87, 0xbb, 0x8f, 0x82,
+ 0xee, 0xce, 0xb7, 0x0c, 0x7c, 0x68, 0x76, 0xda, 0xf3, 0x1f, 0xe5, 0x96,
+ 0x71, 0xe8, 0x77, 0x97, 0x34, 0x06, 0x13, 0x55, 0x28, 0x81, 0x58, 0x40,
+ 0x75, 0x74, 0x47, 0x28, 0x97, 0x14, 0x0a, 0x2b, 0x53, 0xb0, 0xeb, 0x6e,
+ 0x42, 0x68, 0x76, 0xef, 0x67, 0x21, 0x61, 0xb6, 0x64, 0x74, 0x08, 0x70,
+ 0x6d, 0x11, 0xc6, 0xe7, 0x7f, 0xe5, 0x36, 0x1c, 0x66, 0x3a, 0xfd, 0x4e,
+ 0x7a, 0x63, 0x9e, 0x15, 0xce, 0xcf, 0xa9, 0x04, 0xe6, 0x30, 0xea, 0x10,
+ 0xd2, 0x5a, 0x81, 0x24, 0x4b, 0xc2, 0xf2, 0x3b, 0xfb, 0x34, 0xe6, 0x69,
+ 0x7b, 0xff, 0x5d, 0x9b, 0x30, 0x46, 0x59, 0x69, 0x2d, 0x0d, 0xf2, 0x7f,
+ 0xfc, 0xfb, 0x0a, 0x29, 0xea, 0x35, 0x14, 0xd0, 0xbb, 0x3c, 0xa3, 0xb1,
+ 0x10, 0x41,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 18:b2:cb:ba:a3:04:f1:a0:0f:c1:f2:f3:26:46:2a:4a
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Validity
+ Not Before: Dec 1 00:00:00 2006 GMT
+ Not After : Dec 31 23:59:59 2019 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=EssentialSSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ad:f0:08:b0:72:c6:ab:83:12:31:17:70:89:85:
+ a9:20:12:d4:98:6a:ed:80:d4:d1:df:e4:8e:59:2d:
+ d3:96:21:8d:76:d2:3f:18:0b:46:19:63:0b:c7:20:
+ f3:e5:0b:dd:80:1a:f1:5a:a0:bd:1d:76:cd:b7:23:
+ 3a:74:5e:61:1b:75:aa:9b:d4:85:f4:e1:78:91:d3:
+ 2d:e1:af:fc:98:2e:06:d2:79:3d:5a:c0:1f:21:2d:
+ 1c:ae:21:53:c6:3a:a7:21:7e:be:ed:67:6f:75:1d:
+ 1a:9f:6a:5b:06:b3:6a:e3:b1:0b:aa:6a:0e:e7:6d:
+ 6c:c3:ca:95:8c:37:ce:21:1f:35:90:7d:db:da:1a:
+ 5c:a8:88:14:b2:0f:c8:12:20:5f:c5:d3:7f:e8:e1:
+ 38:e0:db:bc:f9:1f:a1:aa:d6:1b:90:07:21:fa:45:
+ 24:50:5d:27:2a:a0:28:41:45:5b:7d:bc:a0:a2:2f:
+ aa:9b:7e:5b:53:c5:f1:05:16:57:7e:11:d7:3b:b4:
+ d9:01:76:dc:df:7d:10:cf:51:a9:e5:38:f2:7b:14:
+ 00:75:59:f9:f0:59:db:17:3e:f7:af:e6:02:2d:a4:
+ 79:c1:5d:a2:1c:c3:9a:c8:a7:a8:0b:48:0a:6a:2e:
+ 7f:2d:97:65:f6:c5:04:9c:44:c8:99:96:7e:7e:a4:
+ dd:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+ X509v3 Subject Key Identifier:
+ DA:CB:EA:AD:5B:08:5D:CC:FF:FC:26:54:CE:49:E5:55:C6:38:F4:F8
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://secure.comodo.com/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/ComodoUTNSGCCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 2d:97:34:7a:40:32:ea:70:97:2f:81:3b:4b:79:12:77:ae:fb:
+ aa:d7:1a:a8:da:5f:f3:a1:db:9e:4d:96:cb:37:7a:a8:ea:ee:
+ 9b:95:db:9d:bb:e1:27:9e:fd:45:ed:0e:52:96:ac:f4:27:bf:
+ 74:aa:92:f4:a5:c4:43:00:1f:0e:b5:78:f9:8a:c5:8c:70:bd:
+ 9a:7a:31:a3:29:d0:59:6b:4c:33:b5:2c:f8:8b:0f:92:63:57:
+ 56:ac:24:67:8a:5b:2f:29:c2:b1:b9:da:24:c5:e4:62:0e:7e:
+ 79:c3:fe:b9:83:ea:27:3b:bc:1d:43:b5:6e:17:aa:fb:c8:98:
+ 88:6a:d9:f2:7c:a1:f6:71:ba:19:4f:b8:38:e3:42:d7:f0:da:
+ b1:c0:23:df:dd:d7:f1:a7:ed:09:8f:56:a0:ab:c3:0b:cb:a4:
+ 92:80:81:92:1f:a9:6f:f9:6c:33:dc:3e:57:c6:a7:f2:1f:cc:
+ 2a:7c:e4:2c:4c:46:5f:eb:f3:61:f7:2b:c4:35:9f:8d:58:f5:
+ 3a:83:44:0e:d8:93:ac:4c:6b:cc:77:f4:03:cd:cc:dc:e0:1c:
+ 4b:5d:25:da:3d:5e:ce:77:8a:e1:3e:c6:d7:94:cd:70:49:3c:
+ ff:0e:bd:08:48:ab:e5:52:14:15:9d:0e:9c:1a:87:56:68:ad:
+ 9c:09:00:64
+-----BEGIN CERTIFICATE-----
+MIIFAzCCA+ugAwIBAgIQGLLLuqME8aAPwfLzJkYqSjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMHIxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVh
+dGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9E
+TyBDQSBMaW1pdGVkMRgwFgYDVQQDEw9Fc3NlbnRpYWxTU0wgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt8AiwcsargxIxF3CJhakgEtSYau2A1NHf
+5I5ZLdOWIY120j8YC0YZYwvHIPPlC92AGvFaoL0dds23Izp0XmEbdaqb1IX04XiR
+0y3hr/yYLgbSeT1awB8hLRyuIVPGOqchfr7tZ291HRqfalsGs2rjsQuqag7nbWzD
+ypWMN84hHzWQfdvaGlyoiBSyD8gSIF/F03/o4Tjg27z5H6Gq1huQByH6RSRQXScq
+oChBRVt9vKCiL6qbfltTxfEFFld+Edc7tNkBdtzffRDPUanlOPJ7FAB1WfnwWdsX
+Pvev5gItpHnBXaIcw5rIp6gLSApqLn8tl2X2xQScRMiZln5+pN0vAgMBAAGjggGD
+MIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQU
+2svqrVsIXcz//CZUzknlVcY49PgwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMD4GA1Ud
+IAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21v
+ZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9kb2Nh
+LmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBsBggrBgEFBQcB
+AQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9k
+b1VUTlNHQ0NBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2Eu
+Y29tMA0GCSqGSIb3DQEBBQUAA4IBAQAtlzR6QDLqcJcvgTtLeRJ3rvuq1xqo2l/z
+odueTZbLN3qo6u6bldudu+Ennv1F7Q5Slqz0J790qpL0pcRDAB8OtXj5isWMcL2a
+ejGjKdBZa0wztSz4iw+SY1dWrCRnilsvKcKxudokxeRiDn55w/65g+onO7wdQ7Vu
+F6r7yJiIatnyfKH2cboZT7g440LX8NqxwCPf3dfxp+0Jj1agq8MLy6SSgIGSH6lv
++Wwz3D5XxqfyH8wqfOQsTEZf6/Nh9yvENZ+NWPU6g0QO2JOsTGvMd/QDzczc4BxL
+XSXaPV7Od4rhPsbXlM1wSTz/Dr0ISKvlUhQVnQ6cGodWaK2cCQBk
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert73[] = {
+ 0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x18, 0xb2, 0xcb, 0xba, 0xa3, 0x04, 0xf1, 0xa0, 0x0f,
+ 0xc1, 0xf2, 0xf3, 0x26, 0x46, 0x2a, 0x4a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+ 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x72, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30,
+ 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61,
+ 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74,
+ 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
+ 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f, 0x44,
+ 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64,
+ 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0f, 0x45,
+ 0x73, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x53, 0x4c, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xad,
+ 0xf0, 0x08, 0xb0, 0x72, 0xc6, 0xab, 0x83, 0x12, 0x31, 0x17, 0x70, 0x89,
+ 0x85, 0xa9, 0x20, 0x12, 0xd4, 0x98, 0x6a, 0xed, 0x80, 0xd4, 0xd1, 0xdf,
+ 0xe4, 0x8e, 0x59, 0x2d, 0xd3, 0x96, 0x21, 0x8d, 0x76, 0xd2, 0x3f, 0x18,
+ 0x0b, 0x46, 0x19, 0x63, 0x0b, 0xc7, 0x20, 0xf3, 0xe5, 0x0b, 0xdd, 0x80,
+ 0x1a, 0xf1, 0x5a, 0xa0, 0xbd, 0x1d, 0x76, 0xcd, 0xb7, 0x23, 0x3a, 0x74,
+ 0x5e, 0x61, 0x1b, 0x75, 0xaa, 0x9b, 0xd4, 0x85, 0xf4, 0xe1, 0x78, 0x91,
+ 0xd3, 0x2d, 0xe1, 0xaf, 0xfc, 0x98, 0x2e, 0x06, 0xd2, 0x79, 0x3d, 0x5a,
+ 0xc0, 0x1f, 0x21, 0x2d, 0x1c, 0xae, 0x21, 0x53, 0xc6, 0x3a, 0xa7, 0x21,
+ 0x7e, 0xbe, 0xed, 0x67, 0x6f, 0x75, 0x1d, 0x1a, 0x9f, 0x6a, 0x5b, 0x06,
+ 0xb3, 0x6a, 0xe3, 0xb1, 0x0b, 0xaa, 0x6a, 0x0e, 0xe7, 0x6d, 0x6c, 0xc3,
+ 0xca, 0x95, 0x8c, 0x37, 0xce, 0x21, 0x1f, 0x35, 0x90, 0x7d, 0xdb, 0xda,
+ 0x1a, 0x5c, 0xa8, 0x88, 0x14, 0xb2, 0x0f, 0xc8, 0x12, 0x20, 0x5f, 0xc5,
+ 0xd3, 0x7f, 0xe8, 0xe1, 0x38, 0xe0, 0xdb, 0xbc, 0xf9, 0x1f, 0xa1, 0xaa,
+ 0xd6, 0x1b, 0x90, 0x07, 0x21, 0xfa, 0x45, 0x24, 0x50, 0x5d, 0x27, 0x2a,
+ 0xa0, 0x28, 0x41, 0x45, 0x5b, 0x7d, 0xbc, 0xa0, 0xa2, 0x2f, 0xaa, 0x9b,
+ 0x7e, 0x5b, 0x53, 0xc5, 0xf1, 0x05, 0x16, 0x57, 0x7e, 0x11, 0xd7, 0x3b,
+ 0xb4, 0xd9, 0x01, 0x76, 0xdc, 0xdf, 0x7d, 0x10, 0xcf, 0x51, 0xa9, 0xe5,
+ 0x38, 0xf2, 0x7b, 0x14, 0x00, 0x75, 0x59, 0xf9, 0xf0, 0x59, 0xdb, 0x17,
+ 0x3e, 0xf7, 0xaf, 0xe6, 0x02, 0x2d, 0xa4, 0x79, 0xc1, 0x5d, 0xa2, 0x1c,
+ 0xc3, 0x9a, 0xc8, 0xa7, 0xa8, 0x0b, 0x48, 0x0a, 0x6a, 0x2e, 0x7f, 0x2d,
+ 0x97, 0x65, 0xf6, 0xc5, 0x04, 0x9c, 0x44, 0xc8, 0x99, 0x96, 0x7e, 0x7e,
+ 0xa4, 0xdd, 0x2f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x83,
+ 0x30, 0x82, 0x01, 0x7f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+ 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+ 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0xda, 0xcb, 0xea, 0xad, 0x5b, 0x08, 0x5d, 0xcc, 0xff, 0xfc, 0x26, 0x54,
+ 0xce, 0x49, 0xe5, 0x55, 0xc6, 0x38, 0xf4, 0xf8, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20,
+ 0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+ 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+ 0x64, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0,
+ 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x6c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+ 0x01, 0x04, 0x60, 0x30, 0x5e, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+ 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72,
+ 0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+ 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x2d,
+ 0x97, 0x34, 0x7a, 0x40, 0x32, 0xea, 0x70, 0x97, 0x2f, 0x81, 0x3b, 0x4b,
+ 0x79, 0x12, 0x77, 0xae, 0xfb, 0xaa, 0xd7, 0x1a, 0xa8, 0xda, 0x5f, 0xf3,
+ 0xa1, 0xdb, 0x9e, 0x4d, 0x96, 0xcb, 0x37, 0x7a, 0xa8, 0xea, 0xee, 0x9b,
+ 0x95, 0xdb, 0x9d, 0xbb, 0xe1, 0x27, 0x9e, 0xfd, 0x45, 0xed, 0x0e, 0x52,
+ 0x96, 0xac, 0xf4, 0x27, 0xbf, 0x74, 0xaa, 0x92, 0xf4, 0xa5, 0xc4, 0x43,
+ 0x00, 0x1f, 0x0e, 0xb5, 0x78, 0xf9, 0x8a, 0xc5, 0x8c, 0x70, 0xbd, 0x9a,
+ 0x7a, 0x31, 0xa3, 0x29, 0xd0, 0x59, 0x6b, 0x4c, 0x33, 0xb5, 0x2c, 0xf8,
+ 0x8b, 0x0f, 0x92, 0x63, 0x57, 0x56, 0xac, 0x24, 0x67, 0x8a, 0x5b, 0x2f,
+ 0x29, 0xc2, 0xb1, 0xb9, 0xda, 0x24, 0xc5, 0xe4, 0x62, 0x0e, 0x7e, 0x79,
+ 0xc3, 0xfe, 0xb9, 0x83, 0xea, 0x27, 0x3b, 0xbc, 0x1d, 0x43, 0xb5, 0x6e,
+ 0x17, 0xaa, 0xfb, 0xc8, 0x98, 0x88, 0x6a, 0xd9, 0xf2, 0x7c, 0xa1, 0xf6,
+ 0x71, 0xba, 0x19, 0x4f, 0xb8, 0x38, 0xe3, 0x42, 0xd7, 0xf0, 0xda, 0xb1,
+ 0xc0, 0x23, 0xdf, 0xdd, 0xd7, 0xf1, 0xa7, 0xed, 0x09, 0x8f, 0x56, 0xa0,
+ 0xab, 0xc3, 0x0b, 0xcb, 0xa4, 0x92, 0x80, 0x81, 0x92, 0x1f, 0xa9, 0x6f,
+ 0xf9, 0x6c, 0x33, 0xdc, 0x3e, 0x57, 0xc6, 0xa7, 0xf2, 0x1f, 0xcc, 0x2a,
+ 0x7c, 0xe4, 0x2c, 0x4c, 0x46, 0x5f, 0xeb, 0xf3, 0x61, 0xf7, 0x2b, 0xc4,
+ 0x35, 0x9f, 0x8d, 0x58, 0xf5, 0x3a, 0x83, 0x44, 0x0e, 0xd8, 0x93, 0xac,
+ 0x4c, 0x6b, 0xcc, 0x77, 0xf4, 0x03, 0xcd, 0xcc, 0xdc, 0xe0, 0x1c, 0x4b,
+ 0x5d, 0x25, 0xda, 0x3d, 0x5e, 0xce, 0x77, 0x8a, 0xe1, 0x3e, 0xc6, 0xd7,
+ 0x94, 0xcd, 0x70, 0x49, 0x3c, 0xff, 0x0e, 0xbd, 0x08, 0x48, 0xab, 0xe5,
+ 0x52, 0x14, 0x15, 0x9d, 0x0e, 0x9c, 0x1a, 0x87, 0x56, 0x68, 0xad, 0x9c,
+ 0x09, 0x00, 0x64,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 4c:cd:4a:9a:5b:45:13:21:8c:cf:90:2f:8b:2b:51:71
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
+ Validity
+ Not Before: Sep 18 00:00:00 2006 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=PositiveSSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bd:4f:79:58:22:93:c9:28:3e:52:11:00:2f:c0:
+ a9:20:8a:d7:2d:55:1e:10:e7:b8:7f:e2:86:2a:a4:
+ ec:5e:e9:e4:92:58:96:54:0d:f3:17:ba:41:9e:15:
+ 91:55:ef:c2:ee:00:20:18:45:83:26:df:20:cc:3d:
+ b3:a1:13:31:0a:21:5c:79:83:79:ab:24:15:5c:56:
+ f0:b4:95:98:a1:da:d2:1b:ea:16:b5:cb:b7:b0:c1:
+ 53:f9:a4:46:da:f0:2e:24:b8:62:9e:8e:8b:5e:2b:
+ 5a:96:a0:ea:50:e9:88:fb:28:2a:4d:9a:9c:48:4f:
+ 83:b6:87:ae:4f:c5:c8:b5:d9:fd:be:3f:d1:a7:9d:
+ c6:2c:13:0d:c0:01:c7:b3:70:f3:8f:69:bb:b0:3c:
+ 10:df:eb:09:00:84:3f:6e:ef:fc:e3:2d:b4:c7:5d:
+ 11:cc:f7:f2:f1:f6:e2:e3:00:7e:12:0e:de:8d:7d:
+ 00:ca:3a:3d:f6:72:e8:79:25:a6:08:16:f7:ab:88:
+ ff:56:5e:09:17:c0:5a:82:05:62:34:2b:28:48:32:
+ 97:d0:84:0c:ac:13:18:db:9e:66:c7:aa:14:8b:11:
+ 69:4d:f1:09:d3:ba:5d:a9:88:37:62:d8:bf:03:9b:
+ 9d:d7:e6:05:7e:c2:6a:52:aa:8d:07:80:ea:8e:0e:
+ f3:1d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
+
+ X509v3 Subject Key Identifier:
+ B8:CA:11:E9:06:31:79:DB:C3:94:C6:E8:19:2A:BC:BB:35:16:31:A4
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/UTN-USERFirst-Hardware.crl
+
+ Full Name:
+ URI:http://crl.comodo.net/UTN-USERFirst-Hardware.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/UTNAddTrustServerCA.crt
+ CA Issuers - URI:http://crt.comodo.net/UTNAddTrustServerCA.crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 1d:b4:e7:f9:18:48:5d:ed:fa:5a:c3:1d:e3:b7:ef:86:15:c9:
+ 6c:13:49:16:0d:31:8c:31:5c:e7:da:87:64:8e:af:12:16:a4:
+ 5a:2d:92:1a:3b:3e:21:65:aa:17:c3:95:7e:40:1c:fd:14:69:
+ 06:18:cd:ac:31:ec:6a:f1:16:9e:89:26:3d:5b:21:8d:e8:e8:
+ f9:ab:3d:aa:3c:cb:99:f2:86:5f:88:53:15:05:17:e5:a8:d2:
+ 85:a7:4e:49:7d:d6:dd:c7:8f:ce:88:bf:65:05:e0:14:b5:31:
+ 77:ee:bd:2a:86:e8:e8:a7:6a:2d:da:65:19:8c:67:e2:6d:f6:
+ 4a:85:66:83:b6:32:4d:9f:42:23:17:d7:45:41:6a:76:04:d4:
+ ad:b9:8f:6e:dc:c2:3e:e9:51:f2:9e:d8:f3:7f:fb:50:2a:f0:
+ 8b:fd:6f:0d:22:36:2e:ce:0e:46:f2:de:8f:da:3b:7d:1e:93:
+ ac:fc:f4:32:2b:0f:ab:01:1f:a5:40:8f:e3:24:99:1f:5d:b2:
+ aa:0c:b9:e2:a1:e7:92:0c:90:4b:53:7d:1f:28:ee:56:3d:af:
+ 18:67:49:df:d5:1e:c2:a8:98:2b:4c:47:83:81:4c:2e:44:c2:
+ ef:c8:63:ee:8b:7b:5b:31:f6:26:61:bf:79:1c:b0:a9:4e:a9:
+ c9:50:7b:e2
+-----BEGIN CERTIFICATE-----
+MIIFAzCCA+ugAwIBAgIQTM1KmltFEyGMz5AviytRcTANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNMDYwOTE4MDAwMDAwWhcNMjAwNTMwMTA0ODM4WjBxMQswCQYD
+VQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdT
+YWxmb3JkMRowGAYDVQQKExFDb21vZG8gQ0EgTGltaXRlZDEXMBUGA1UEAxMOUG9z
+aXRpdmVTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9T3lY
+IpPJKD5SEQAvwKkgitctVR4Q57h/4oYqpOxe6eSSWJZUDfMXukGeFZFV78LuACAY
+RYMm3yDMPbOhEzEKIVx5g3mrJBVcVvC0lZih2tIb6ha1y7ewwVP5pEba8C4kuGKe
+joteK1qWoOpQ6Yj7KCpNmpxIT4O2h65Pxci12f2+P9GnncYsEw3AAcezcPOPabuw
+PBDf6wkAhD9u7/zjLbTHXRHM9/Lx9uLjAH4SDt6NfQDKOj32cuh5JaYIFveriP9W
+XgkXwFqCBWI0KyhIMpfQhAysExjbnmbHqhSLEWlN8QnTul2piDdi2L8Dm53X5gV+
+wmpSqo0HgOqODvMdAgMBAAGjggFuMIIBajAfBgNVHSMEGDAWgBShcl8mGyiYQ5Vd
+BzfVhZadS9LDRTAdBgNVHQ4EFgQUuMoR6QYxedvDlMboGSq8uzUWMaQwDgYDVR0P
+AQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwewYDVR0fBHQwcjA4oDagNIYy
+aHR0cDovL2NybC5jb21vZG9jYS5jb20vVVROLVVTRVJGaXJzdC1IYXJkd2FyZS5j
+cmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9VVE4tVVNFUkZpcnN0LUhh
+cmR3YXJlLmNybDCBhgYIKwYBBQUHAQEEejB4MDsGCCsGAQUFBzAChi9odHRwOi8v
+Y3J0LmNvbW9kb2NhLmNvbS9VVE5BZGRUcnVzdFNlcnZlckNBLmNydDA5BggrBgEF
+BQcwAoYtaHR0cDovL2NydC5jb21vZG8ubmV0L1VUTkFkZFRydXN0U2VydmVyQ0Eu
+Y3J0MA0GCSqGSIb3DQEBBQUAA4IBAQAdtOf5GEhd7fpawx3jt++GFclsE0kWDTGM
+MVzn2odkjq8SFqRaLZIaOz4hZaoXw5V+QBz9FGkGGM2sMexq8RaeiSY9WyGN6Oj5
+qz2qPMuZ8oZfiFMVBRflqNKFp05Jfdbdx4/OiL9lBeAUtTF37r0qhujop2ot2mUZ
+jGfibfZKhWaDtjJNn0IjF9dFQWp2BNStuY9u3MI+6VHyntjzf/tQKvCL/W8NIjYu
+zg5G8t6P2jt9HpOs/PQyKw+rAR+lQI/jJJkfXbKqDLnioeeSDJBLU30fKO5WPa8Y
+Z0nf1R7CqJgrTEeDgUwuRMLvyGPui3tbMfYmYb95HLCpTqnJUHvi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert74[] = {
+ 0x30, 0x82, 0x05, 0x03, 0x30, 0x82, 0x03, 0xeb, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x4c, 0xcd, 0x4a, 0x9a, 0x5b, 0x45, 0x13, 0x21, 0x8c,
+ 0xcf, 0x90, 0x2f, 0x8b, 0x2b, 0x51, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b, 0x65, 0x20,
+ 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45, 0x52, 0x54,
+ 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x75, 0x73,
+ 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x31,
+ 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x55, 0x54,
+ 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d,
+ 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x36, 0x30, 0x39, 0x31, 0x38, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34,
+ 0x38, 0x33, 0x38, 0x5a, 0x30, 0x71, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06,
+ 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65,
+ 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53,
+ 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x20,
+ 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x17,
+ 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x50, 0x6f, 0x73,
+ 0x69, 0x74, 0x69, 0x76, 0x65, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x4f, 0x79, 0x58,
+ 0x22, 0x93, 0xc9, 0x28, 0x3e, 0x52, 0x11, 0x00, 0x2f, 0xc0, 0xa9, 0x20,
+ 0x8a, 0xd7, 0x2d, 0x55, 0x1e, 0x10, 0xe7, 0xb8, 0x7f, 0xe2, 0x86, 0x2a,
+ 0xa4, 0xec, 0x5e, 0xe9, 0xe4, 0x92, 0x58, 0x96, 0x54, 0x0d, 0xf3, 0x17,
+ 0xba, 0x41, 0x9e, 0x15, 0x91, 0x55, 0xef, 0xc2, 0xee, 0x00, 0x20, 0x18,
+ 0x45, 0x83, 0x26, 0xdf, 0x20, 0xcc, 0x3d, 0xb3, 0xa1, 0x13, 0x31, 0x0a,
+ 0x21, 0x5c, 0x79, 0x83, 0x79, 0xab, 0x24, 0x15, 0x5c, 0x56, 0xf0, 0xb4,
+ 0x95, 0x98, 0xa1, 0xda, 0xd2, 0x1b, 0xea, 0x16, 0xb5, 0xcb, 0xb7, 0xb0,
+ 0xc1, 0x53, 0xf9, 0xa4, 0x46, 0xda, 0xf0, 0x2e, 0x24, 0xb8, 0x62, 0x9e,
+ 0x8e, 0x8b, 0x5e, 0x2b, 0x5a, 0x96, 0xa0, 0xea, 0x50, 0xe9, 0x88, 0xfb,
+ 0x28, 0x2a, 0x4d, 0x9a, 0x9c, 0x48, 0x4f, 0x83, 0xb6, 0x87, 0xae, 0x4f,
+ 0xc5, 0xc8, 0xb5, 0xd9, 0xfd, 0xbe, 0x3f, 0xd1, 0xa7, 0x9d, 0xc6, 0x2c,
+ 0x13, 0x0d, 0xc0, 0x01, 0xc7, 0xb3, 0x70, 0xf3, 0x8f, 0x69, 0xbb, 0xb0,
+ 0x3c, 0x10, 0xdf, 0xeb, 0x09, 0x00, 0x84, 0x3f, 0x6e, 0xef, 0xfc, 0xe3,
+ 0x2d, 0xb4, 0xc7, 0x5d, 0x11, 0xcc, 0xf7, 0xf2, 0xf1, 0xf6, 0xe2, 0xe3,
+ 0x00, 0x7e, 0x12, 0x0e, 0xde, 0x8d, 0x7d, 0x00, 0xca, 0x3a, 0x3d, 0xf6,
+ 0x72, 0xe8, 0x79, 0x25, 0xa6, 0x08, 0x16, 0xf7, 0xab, 0x88, 0xff, 0x56,
+ 0x5e, 0x09, 0x17, 0xc0, 0x5a, 0x82, 0x05, 0x62, 0x34, 0x2b, 0x28, 0x48,
+ 0x32, 0x97, 0xd0, 0x84, 0x0c, 0xac, 0x13, 0x18, 0xdb, 0x9e, 0x66, 0xc7,
+ 0xaa, 0x14, 0x8b, 0x11, 0x69, 0x4d, 0xf1, 0x09, 0xd3, 0xba, 0x5d, 0xa9,
+ 0x88, 0x37, 0x62, 0xd8, 0xbf, 0x03, 0x9b, 0x9d, 0xd7, 0xe6, 0x05, 0x7e,
+ 0xc2, 0x6a, 0x52, 0xaa, 0x8d, 0x07, 0x80, 0xea, 0x8e, 0x0e, 0xf3, 0x1d,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x6e, 0x30, 0x82, 0x01,
+ 0x6a, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xa1, 0x72, 0x5f, 0x26, 0x1b, 0x28, 0x98, 0x43, 0x95, 0x5d,
+ 0x07, 0x37, 0xd5, 0x85, 0x96, 0x9d, 0x4b, 0xd2, 0xc3, 0x45, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb8, 0xca, 0x11,
+ 0xe9, 0x06, 0x31, 0x79, 0xdb, 0xc3, 0x94, 0xc6, 0xe8, 0x19, 0x2a, 0xbc,
+ 0xbb, 0x35, 0x16, 0x31, 0xa4, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+ 0x01, 0xff, 0x02, 0x01, 0x01, 0x30, 0x7b, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x74, 0x30, 0x72, 0x30, 0x38, 0xa0, 0x36, 0xa0, 0x34, 0x86, 0x32,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73,
+ 0x74, 0x2d, 0x48, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x36, 0xa0, 0x34, 0xa0, 0x32, 0x86, 0x30, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x6f, 0x64, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x2d,
+ 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x48, 0x61,
+ 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81,
+ 0x86, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x7a, 0x30, 0x78, 0x30, 0x3b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x02, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x74, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x55, 0x54, 0x4e, 0x41, 0x64, 0x64, 0x54, 0x72,
+ 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41, 0x2e,
+ 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x1d,
+ 0xb4, 0xe7, 0xf9, 0x18, 0x48, 0x5d, 0xed, 0xfa, 0x5a, 0xc3, 0x1d, 0xe3,
+ 0xb7, 0xef, 0x86, 0x15, 0xc9, 0x6c, 0x13, 0x49, 0x16, 0x0d, 0x31, 0x8c,
+ 0x31, 0x5c, 0xe7, 0xda, 0x87, 0x64, 0x8e, 0xaf, 0x12, 0x16, 0xa4, 0x5a,
+ 0x2d, 0x92, 0x1a, 0x3b, 0x3e, 0x21, 0x65, 0xaa, 0x17, 0xc3, 0x95, 0x7e,
+ 0x40, 0x1c, 0xfd, 0x14, 0x69, 0x06, 0x18, 0xcd, 0xac, 0x31, 0xec, 0x6a,
+ 0xf1, 0x16, 0x9e, 0x89, 0x26, 0x3d, 0x5b, 0x21, 0x8d, 0xe8, 0xe8, 0xf9,
+ 0xab, 0x3d, 0xaa, 0x3c, 0xcb, 0x99, 0xf2, 0x86, 0x5f, 0x88, 0x53, 0x15,
+ 0x05, 0x17, 0xe5, 0xa8, 0xd2, 0x85, 0xa7, 0x4e, 0x49, 0x7d, 0xd6, 0xdd,
+ 0xc7, 0x8f, 0xce, 0x88, 0xbf, 0x65, 0x05, 0xe0, 0x14, 0xb5, 0x31, 0x77,
+ 0xee, 0xbd, 0x2a, 0x86, 0xe8, 0xe8, 0xa7, 0x6a, 0x2d, 0xda, 0x65, 0x19,
+ 0x8c, 0x67, 0xe2, 0x6d, 0xf6, 0x4a, 0x85, 0x66, 0x83, 0xb6, 0x32, 0x4d,
+ 0x9f, 0x42, 0x23, 0x17, 0xd7, 0x45, 0x41, 0x6a, 0x76, 0x04, 0xd4, 0xad,
+ 0xb9, 0x8f, 0x6e, 0xdc, 0xc2, 0x3e, 0xe9, 0x51, 0xf2, 0x9e, 0xd8, 0xf3,
+ 0x7f, 0xfb, 0x50, 0x2a, 0xf0, 0x8b, 0xfd, 0x6f, 0x0d, 0x22, 0x36, 0x2e,
+ 0xce, 0x0e, 0x46, 0xf2, 0xde, 0x8f, 0xda, 0x3b, 0x7d, 0x1e, 0x93, 0xac,
+ 0xfc, 0xf4, 0x32, 0x2b, 0x0f, 0xab, 0x01, 0x1f, 0xa5, 0x40, 0x8f, 0xe3,
+ 0x24, 0x99, 0x1f, 0x5d, 0xb2, 0xaa, 0x0c, 0xb9, 0xe2, 0xa1, 0xe7, 0x92,
+ 0x0c, 0x90, 0x4b, 0x53, 0x7d, 0x1f, 0x28, 0xee, 0x56, 0x3d, 0xaf, 0x18,
+ 0x67, 0x49, 0xdf, 0xd5, 0x1e, 0xc2, 0xa8, 0x98, 0x2b, 0x4c, 0x47, 0x83,
+ 0x81, 0x4c, 0x2e, 0x44, 0xc2, 0xef, 0xc8, 0x63, 0xee, 0x8b, 0x7b, 0x5b,
+ 0x31, 0xf6, 0x26, 0x61, 0xbf, 0x79, 0x1c, 0xb0, 0xa9, 0x4e, 0xa9, 0xc9,
+ 0x50, 0x7b, 0xe2,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 11:a3:b4:d0:ec:8d:b7:7f:9d:a0:cd:5d:2d:51:2f:42
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Validity
+ Not Before: May 24 00:00:00 2010 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Extended Validation Secure Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:cc:4a:96:33:cd:25:8d:67:ee:28:96:37:87:46:
+ f0:f6:04:a2:84:7f:53:aa:96:e6:1f:b1:02:1c:6e:
+ ed:7d:21:d4:d7:3c:1e:a2:d8:69:2f:a8:b7:f5:a2:
+ ed:64:58:64:e1:44:65:36:49:41:20:01:8d:3b:13:
+ e2:08:f3:0c:f2:57:39:93:37:b7:1c:93:44:83:8e:
+ bf:2d:f1:a1:05:75:da:6e:ee:7b:6f:1b:ea:76:83:
+ 28:74:4a:1c:2b:d3:f5:c4:03:72:93:af:86:ce:09:
+ 8c:3c:75:d4:c9:0a:2f:72:f3:ad:bd:0e:30:3c:84:
+ a1:73:1f:03:25:14:a5:8f:c3:d6:f4:b5:e4:dd:86:
+ 7a:f5:19:ba:68:f2:85:54:a2:30:11:ca:d1:92:cb:
+ 3b:74:06:12:a0:37:ab:6a:d8:54:11:df:6c:9a:16:
+ 94:b9:b4:a7:65:c6:74:2d:31:f3:4d:52:e9:55:51:
+ 9f:cb:3e:a2:8d:76:98:70:d2:6f:a6:65:45:2f:1b:
+ 85:bb:5b:6d:f9:f2:c0:04:66:13:84:7a:9d:ce:27:
+ d8:f4:44:9e:bf:ac:be:99:db:6b:4f:db:58:21:b0:
+ 89:27:b4:8f:32:d6:4b:5e:72:91:5e:df:05:9d:d9:
+ 49:2f:f4:b6:6f:50:1f:75:cb:80:9d:e6:d3:e4:d1:
+ f2:d3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+ X509v3 Subject Key Identifier:
+ 88:44:51:FF:50:2A:69:5E:2D:88:F4:21:BA:D9:0C:F2:CE:CB:EA:7C
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://secure.comodo.com/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/COMODOAddTrustServerCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 9a:43:bf:af:a4:72:5e:cd:7d:6f:7f:f4:fc:3d:8c:bb:70:e6:
+ 1e:dd:04:fd:3f:dc:9d:9f:bf:89:76:9b:f2:86:31:fc:7f:b3:
+ ed:2a:91:53:2c:e2:aa:b0:e3:c8:2c:71:f7:15:8a:23:1c:f1:
+ 69:2e:81:fb:b1:bc:62:0b:ab:1a:54:1c:d9:22:5e:34:4c:a5:
+ f6:23:0f:5d:7a:3d:db:43:cd:69:7e:17:37:52:cd:53:a1:c2:
+ 11:d4:53:78:27:64:d5:89:41:4d:16:55:bb:90:cb:f0:d8:e4:
+ dd:dd:d3:09:64:48:28:ff:32:23:84:2f:8c:7b:55:2f:cf:29:
+ 88:37:34:78:0f:33:aa:ff:b7:f2:96:a4:9b:44:80:b5:be:6c:
+ 56:54:ab:a4:81:9e:25:18:28:54:3a:7f:2c:63:cf:59:20:8c:
+ 18:6b:38:2c:b4:dd:ed:e3:40:de:0c:36:25:57:9a:c0:d1:60:
+ 9e:5e:03:68:97:ae:1a:3b:ea:45:d7:51:99:49:ee:44:59:56:
+ 0b:5e:b1:8f:68:ea:8a:9e:ca:d2:c9:a0:03:7e:70:25:f4:32:
+ c9:4e:50:83:87:a2:34:48:3d:4f:35:77:fc:d8:88:ea:f6:7d:
+ 1e:ce:43:b6:d5:c2:6a:7e:38:66:63:4d:e7:ee:32:ef:0f:24:
+ e8:2a:67:fa
+-----BEGIN CERTIFICATE-----
+MIIFBjCCA+6gAwIBAgIQEaO00OyNt3+doM1dLVEvQjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xMDA1MjQwMDAw
+MDBaFw0yMDA1MzAxMDQ4MzhaMIGOMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDE0MDIGA1UEAxMrQ09NT0RPIEV4dGVuZGVkIFZhbGlkYXRp
+b24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMxKljPNJY1n7iiWN4dG8PYEooR/U6qW5h+xAhxu7X0h1Nc8HqLYaS+ot/Wi
+7WRYZOFEZTZJQSABjTsT4gjzDPJXOZM3txyTRIOOvy3xoQV12m7ue28b6naDKHRK
+HCvT9cQDcpOvhs4JjDx11MkKL3Lzrb0OMDyEoXMfAyUUpY/D1vS15N2GevUZumjy
+hVSiMBHK0ZLLO3QGEqA3q2rYVBHfbJoWlLm0p2XGdC0x801S6VVRn8s+oo12mHDS
+b6ZlRS8bhbtbbfnywARmE4R6nc4n2PREnr+svpnba0/bWCGwiSe0jzLWS15ykV7f
+BZ3ZSS/0tm9QH3XLgJ3m0+TR8tMCAwEAAaOCAWkwggFlMB8GA1UdIwQYMBaAFAtY
+5YvGTBU3pECpMKkhvkc2Wlb/MB0GA1UdDgQWBBSIRFH/UCppXi2I9CG62Qzyzsvq
+fDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADA+BgNVHSAENzA1
+MDMGBFUdIAAwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNv
+bS9DUFMwSQYDVR0fBEIwQDA+oDygOoY4aHR0cDovL2NybC5jb21vZG9jYS5jb20v
+Q09NT0RPQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwdAYIKwYBBQUHAQEEaDBm
+MD4GCCsGAQUFBzAChjJodHRwOi8vY3J0LmNvbW9kb2NhLmNvbS9DT01PRE9BZGRU
+cnVzdFNlcnZlckNBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2Rv
+Y2EuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQCaQ7+vpHJezX1vf/T8PYy7cOYe3QT9
+P9ydn7+JdpvyhjH8f7PtKpFTLOKqsOPILHH3FYojHPFpLoH7sbxiC6saVBzZIl40
+TKX2Iw9dej3bQ81pfhc3Us1TocIR1FN4J2TViUFNFlW7kMvw2OTd3dMJZEgo/zIj
+hC+Me1UvzymINzR4DzOq/7fylqSbRIC1vmxWVKukgZ4lGChUOn8sY89ZIIwYazgs
+tN3t40DeDDYlV5rA0WCeXgNol64aO+pF11GZSe5EWVYLXrGPaOqKnsrSyaADfnAl
+9DLJTlCDh6I0SD1PNXf82Ijq9n0ezkO21cJqfjhmY03n7jLvDyToKmf6
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert75[] = {
+ 0x30, 0x82, 0x05, 0x06, 0x30, 0x82, 0x03, 0xee, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x11, 0xa3, 0xb4, 0xd0, 0xec, 0x8d, 0xb7, 0x7f, 0x9d,
+ 0xa0, 0xcd, 0x5d, 0x2d, 0x51, 0x2f, 0x42, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x30, 0x30, 0x35, 0x32, 0x34, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x35, 0x33, 0x30, 0x31,
+ 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30, 0x81, 0x8e, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b,
+ 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65,
+ 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73,
+ 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30,
+ 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f,
+ 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65,
+ 0x64, 0x31, 0x34, 0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e,
+ 0x64, 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xcc, 0x4a, 0x96, 0x33, 0xcd, 0x25, 0x8d, 0x67,
+ 0xee, 0x28, 0x96, 0x37, 0x87, 0x46, 0xf0, 0xf6, 0x04, 0xa2, 0x84, 0x7f,
+ 0x53, 0xaa, 0x96, 0xe6, 0x1f, 0xb1, 0x02, 0x1c, 0x6e, 0xed, 0x7d, 0x21,
+ 0xd4, 0xd7, 0x3c, 0x1e, 0xa2, 0xd8, 0x69, 0x2f, 0xa8, 0xb7, 0xf5, 0xa2,
+ 0xed, 0x64, 0x58, 0x64, 0xe1, 0x44, 0x65, 0x36, 0x49, 0x41, 0x20, 0x01,
+ 0x8d, 0x3b, 0x13, 0xe2, 0x08, 0xf3, 0x0c, 0xf2, 0x57, 0x39, 0x93, 0x37,
+ 0xb7, 0x1c, 0x93, 0x44, 0x83, 0x8e, 0xbf, 0x2d, 0xf1, 0xa1, 0x05, 0x75,
+ 0xda, 0x6e, 0xee, 0x7b, 0x6f, 0x1b, 0xea, 0x76, 0x83, 0x28, 0x74, 0x4a,
+ 0x1c, 0x2b, 0xd3, 0xf5, 0xc4, 0x03, 0x72, 0x93, 0xaf, 0x86, 0xce, 0x09,
+ 0x8c, 0x3c, 0x75, 0xd4, 0xc9, 0x0a, 0x2f, 0x72, 0xf3, 0xad, 0xbd, 0x0e,
+ 0x30, 0x3c, 0x84, 0xa1, 0x73, 0x1f, 0x03, 0x25, 0x14, 0xa5, 0x8f, 0xc3,
+ 0xd6, 0xf4, 0xb5, 0xe4, 0xdd, 0x86, 0x7a, 0xf5, 0x19, 0xba, 0x68, 0xf2,
+ 0x85, 0x54, 0xa2, 0x30, 0x11, 0xca, 0xd1, 0x92, 0xcb, 0x3b, 0x74, 0x06,
+ 0x12, 0xa0, 0x37, 0xab, 0x6a, 0xd8, 0x54, 0x11, 0xdf, 0x6c, 0x9a, 0x16,
+ 0x94, 0xb9, 0xb4, 0xa7, 0x65, 0xc6, 0x74, 0x2d, 0x31, 0xf3, 0x4d, 0x52,
+ 0xe9, 0x55, 0x51, 0x9f, 0xcb, 0x3e, 0xa2, 0x8d, 0x76, 0x98, 0x70, 0xd2,
+ 0x6f, 0xa6, 0x65, 0x45, 0x2f, 0x1b, 0x85, 0xbb, 0x5b, 0x6d, 0xf9, 0xf2,
+ 0xc0, 0x04, 0x66, 0x13, 0x84, 0x7a, 0x9d, 0xce, 0x27, 0xd8, 0xf4, 0x44,
+ 0x9e, 0xbf, 0xac, 0xbe, 0x99, 0xdb, 0x6b, 0x4f, 0xdb, 0x58, 0x21, 0xb0,
+ 0x89, 0x27, 0xb4, 0x8f, 0x32, 0xd6, 0x4b, 0x5e, 0x72, 0x91, 0x5e, 0xdf,
+ 0x05, 0x9d, 0xd9, 0x49, 0x2f, 0xf4, 0xb6, 0x6f, 0x50, 0x1f, 0x75, 0xcb,
+ 0x80, 0x9d, 0xe6, 0xd3, 0xe4, 0xd1, 0xf2, 0xd3, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x01, 0x69, 0x30, 0x82, 0x01, 0x65, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58,
+ 0xe5, 0x8b, 0xc6, 0x4c, 0x15, 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21,
+ 0xbe, 0x47, 0x36, 0x5a, 0x56, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x88, 0x44, 0x51, 0xff, 0x50, 0x2a, 0x69,
+ 0x5e, 0x2d, 0x88, 0xf4, 0x21, 0xba, 0xd9, 0x0c, 0xf2, 0xce, 0xcb, 0xea,
+ 0x7c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x37, 0x30, 0x35,
+ 0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2b, 0x30, 0x29,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1d,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0, 0x3c, 0xa0, 0x3a, 0x86, 0x38,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x74, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x68, 0x30, 0x66,
+ 0x30, 0x3e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02,
+ 0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x41, 0x64, 0x64, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+ 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x9a, 0x43, 0xbf, 0xaf, 0xa4, 0x72, 0x5e, 0xcd, 0x7d, 0x6f,
+ 0x7f, 0xf4, 0xfc, 0x3d, 0x8c, 0xbb, 0x70, 0xe6, 0x1e, 0xdd, 0x04, 0xfd,
+ 0x3f, 0xdc, 0x9d, 0x9f, 0xbf, 0x89, 0x76, 0x9b, 0xf2, 0x86, 0x31, 0xfc,
+ 0x7f, 0xb3, 0xed, 0x2a, 0x91, 0x53, 0x2c, 0xe2, 0xaa, 0xb0, 0xe3, 0xc8,
+ 0x2c, 0x71, 0xf7, 0x15, 0x8a, 0x23, 0x1c, 0xf1, 0x69, 0x2e, 0x81, 0xfb,
+ 0xb1, 0xbc, 0x62, 0x0b, 0xab, 0x1a, 0x54, 0x1c, 0xd9, 0x22, 0x5e, 0x34,
+ 0x4c, 0xa5, 0xf6, 0x23, 0x0f, 0x5d, 0x7a, 0x3d, 0xdb, 0x43, 0xcd, 0x69,
+ 0x7e, 0x17, 0x37, 0x52, 0xcd, 0x53, 0xa1, 0xc2, 0x11, 0xd4, 0x53, 0x78,
+ 0x27, 0x64, 0xd5, 0x89, 0x41, 0x4d, 0x16, 0x55, 0xbb, 0x90, 0xcb, 0xf0,
+ 0xd8, 0xe4, 0xdd, 0xdd, 0xd3, 0x09, 0x64, 0x48, 0x28, 0xff, 0x32, 0x23,
+ 0x84, 0x2f, 0x8c, 0x7b, 0x55, 0x2f, 0xcf, 0x29, 0x88, 0x37, 0x34, 0x78,
+ 0x0f, 0x33, 0xaa, 0xff, 0xb7, 0xf2, 0x96, 0xa4, 0x9b, 0x44, 0x80, 0xb5,
+ 0xbe, 0x6c, 0x56, 0x54, 0xab, 0xa4, 0x81, 0x9e, 0x25, 0x18, 0x28, 0x54,
+ 0x3a, 0x7f, 0x2c, 0x63, 0xcf, 0x59, 0x20, 0x8c, 0x18, 0x6b, 0x38, 0x2c,
+ 0xb4, 0xdd, 0xed, 0xe3, 0x40, 0xde, 0x0c, 0x36, 0x25, 0x57, 0x9a, 0xc0,
+ 0xd1, 0x60, 0x9e, 0x5e, 0x03, 0x68, 0x97, 0xae, 0x1a, 0x3b, 0xea, 0x45,
+ 0xd7, 0x51, 0x99, 0x49, 0xee, 0x44, 0x59, 0x56, 0x0b, 0x5e, 0xb1, 0x8f,
+ 0x68, 0xea, 0x8a, 0x9e, 0xca, 0xd2, 0xc9, 0xa0, 0x03, 0x7e, 0x70, 0x25,
+ 0xf4, 0x32, 0xc9, 0x4e, 0x50, 0x83, 0x87, 0xa2, 0x34, 0x48, 0x3d, 0x4f,
+ 0x35, 0x77, 0xfc, 0xd8, 0x88, 0xea, 0xf6, 0x7d, 0x1e, 0xce, 0x43, 0xb6,
+ 0xd5, 0xc2, 0x6a, 0x7e, 0x38, 0x66, 0x63, 0x4d, 0xe7, 0xee, 0x32, 0xef,
+ 0x0f, 0x24, 0xe8, 0x2a, 0x67, 0xfa,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 513 (0x201)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority
+ Validity
+ Not Before: Nov 16 01:15:40 2006 GMT
+ Not After : Nov 16 01:15:40 2026 GMT
+ Subject: C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., OU=http://certificates.starfieldtech.com/repository, CN=Starfield Secure Certification Authority/serialNumber=10688435
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e2:a7:5d:a3:ed:66:ef:6a:2f:2b:36:1f:dd:8d:
+ d3:05:02:a0:ca:0f:5e:19:ae:38:72:cf:16:da:54:
+ 4a:cb:48:0a:f4:a1:73:11:65:85:43:c9:5b:17:0c:
+ 9a:2b:be:0f:98:51:7a:60:29:0d:6c:de:e2:e8:e5:
+ 15:4d:56:ff:90:d1:a7:a6:04:3f:60:07:4a:ca:6f:
+ a5:10:e7:b3:f8:5c:b1:bc:2b:2a:dc:01:79:f5:1d:
+ 35:f5:7a:28:83:f2:93:73:82:89:ac:60:6d:cb:c2:
+ 48:c2:1d:d4:06:44:17:3c:ac:01:47:ab:3e:70:84:
+ 09:0b:b8:20:08:40:20:87:a1:63:1a:ca:3e:83:d2:
+ 37:b3:98:8d:32:3f:37:bf:a1:b7:5b:5f:de:5c:33:
+ 92:cf:3e:07:ce:b9:48:4b:e2:f0:55:50:2f:f8:70:
+ 42:89:d1:93:96:8a:63:d9:66:0d:e6:58:6e:b9:6d:
+ 90:bd:ca:dc:84:66:f2:39:8e:5b:a6:58:55:73:cb:
+ 62:6c:1b:d7:20:16:3b:2c:59:f5:cb:c8:56:32:4a:
+ 50:27:ba:55:d3:a8:01:cb:72:a9:74:8b:0c:ad:3a:
+ e5:15:b6:2a:df:65:f8:de:8a:f5:ef:84:3b:f9:e7:
+ 54:65:0b:80:bd:47:45:a5:f0:44:d8:53:3b:be:80:
+ f1:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 49:4B:52:27:D1:1B:BC:F2:A1:21:6A:62:7B:51:42:7A:8A:D7:D5:56
+ X509v3 Authority Key Identifier:
+ keyid:BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.starfieldtech.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://certificates.starfieldtech.com/repository/sfroot.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://certificates.starfieldtech.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ 86:52:ba:b3:1f:a6:5e:6b:90:a6:64:2a:fc:45:b2:ae:9f:3e:
+ b3:62:af:db:1f:67:c4:bd:ca:a1:2f:c7:9c:0d:21:57:d0:f8:
+ 36:21:ce:3a:25:3e:78:76:b3:d9:dd:bc:de:fb:6c:84:5f:0c:
+ a3:0d:12:eb:11:3b:71:5f:80:1e:f1:1f:6d:0e:5f:c1:ec:d4:
+ a5:f7:65:bb:1f:4c:95:01:13:b2:6a:9c:0b:eb:1f:9d:b1:e7:
+ ed:19:0d:bc:85:7c:f3:17:bd:59:63:ae:a7:1a:05:cd:47:e3:
+ 2d:96:62:51:32:0a:08:68:4b:22:77:5f:f7:45:dc:61:de:f4:
+ cb:2b:22:29:44:25:d2:9f:0b:77:7a:a1:26:7c:4a:d7:0f:c2:
+ d1:3c:ba:0e:a7:95:9a:5b:05:0a:10:f9:55:5f:c1:97:8b:74:
+ cc:5e:28:69:13:7e:d0:0a:8d:9d:0f:60:54:7a:c4:8c:1b:35:
+ 0f:74:7a:70:b2:82:cf:1d:b5:e2:8a:db:2a:c6:b2:51:69:bf:
+ 12:17:92:60:17:aa:3d:5b:09:f8:87:65:1d:a7:a4:28:e5:22:
+ 02:03:82:44:9a:34:63:9e:fb:28:cf:e8:cd:2e:0e:52:20:ed:
+ 4a:cb:38:7c:9d:ae:6e:79:d7:95:2c:a8:91:f3:86:01:21:91:
+ 4b:b5:40:a4
+-----BEGIN CERTIFICATE-----
+MIIFBzCCA++gAwIBAgICAgEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCVVMx
+JTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsT
+KVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2
+MTExNjAxMTU0MFoXDTI2MTExNjAxMTU0MFowgdwxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTkwNwYDVQQLEzBodHRwOi8vY2VydGlm
+aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKFN0
+YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUT
+CDEwNjg4NDM1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qddo+1m
+72ovKzYf3Y3TBQKgyg9eGa44cs8W2lRKy0gK9KFzEWWFQ8lbFwyaK74PmFF6YCkN
+bN7i6OUVTVb/kNGnpgQ/YAdKym+lEOez+FyxvCsq3AF59R019Xoog/KTc4KJrGBt
+y8JIwh3UBkQXPKwBR6s+cIQJC7ggCEAgh6FjGso+g9I3s5iNMj83v6G3W1/eXDOS
+zz4HzrlIS+LwVVAv+HBCidGTlopj2WYN5lhuuW2QvcrchGbyOY5bplhVc8tibBvX
+IBY7LFn1y8hWMkpQJ7pV06gBy3KpdIsMrTrlFbYq32X43or174Q7+edUZQuAvUdF
+pfBE2FM7voDxLwIDAQABo4IBRDCCAUAwHQYDVR0OBBYEFElLUifRG7zyoSFqYntR
+QnqK19VWMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtVrNzXEMIOqYjnMBIGA1UdEwEB
+/wQIMAYBAf8CAQAwOQYIKwYBBQUHAQEELTArMCkGCCsGAQUFBzABhh1odHRwOi8v
+b2NzcC5zdGFyZmllbGR0ZWNoLmNvbTBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8v
+Y2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvc2Zyb290
+LmNybDBRBgNVHSAESjBIMEYGBFUdIAAwPjA8BggrBgEFBQcCARYwaHR0cDovL2Nl
+cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB
+/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAhlK6sx+mXmuQpmQq/EWyrp8+s2Kv
+2x9nxL3KoS/HnA0hV9D4NiHOOiU+eHaz2d283vtshF8Mow0S6xE7cV+AHvEfbQ5f
+wezUpfdlux9MlQETsmqcC+sfnbHn7RkNvIV88xe9WWOupxoFzUfjLZZiUTIKCGhL
+Indf90XcYd70yysiKUQl0p8Ld3qhJnxK1w/C0Ty6DqeVmlsFChD5VV/Bl4t0zF4o
+aRN+0AqNnQ9gVHrEjBs1D3R6cLKCzx214orbKsayUWm/EheSYBeqPVsJ+IdlHaek
+KOUiAgOCRJo0Y577KM/ozS4OUiDtSss4fJ2ubnnXlSyokfOGASGRS7VApA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert76[] = {
+ 0x30, 0x82, 0x05, 0x07, 0x30, 0x82, 0x03, 0xef, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x02, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x68, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74,
+ 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68,
+ 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x29, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43,
+ 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36,
+ 0x31, 0x31, 0x31, 0x36, 0x30, 0x31, 0x31, 0x35, 0x34, 0x30, 0x5a, 0x17,
+ 0x0d, 0x32, 0x36, 0x31, 0x31, 0x31, 0x36, 0x30, 0x31, 0x31, 0x35, 0x34,
+ 0x30, 0x5a, 0x30, 0x81, 0xdc, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53,
+ 0x63, 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30,
+ 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72,
+ 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f,
+ 0x6c, 0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x31,
+ 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x28, 0x53, 0x74,
+ 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13,
+ 0x08, 0x31, 0x30, 0x36, 0x38, 0x38, 0x34, 0x33, 0x35, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0xa7, 0x5d, 0xa3, 0xed, 0x66,
+ 0xef, 0x6a, 0x2f, 0x2b, 0x36, 0x1f, 0xdd, 0x8d, 0xd3, 0x05, 0x02, 0xa0,
+ 0xca, 0x0f, 0x5e, 0x19, 0xae, 0x38, 0x72, 0xcf, 0x16, 0xda, 0x54, 0x4a,
+ 0xcb, 0x48, 0x0a, 0xf4, 0xa1, 0x73, 0x11, 0x65, 0x85, 0x43, 0xc9, 0x5b,
+ 0x17, 0x0c, 0x9a, 0x2b, 0xbe, 0x0f, 0x98, 0x51, 0x7a, 0x60, 0x29, 0x0d,
+ 0x6c, 0xde, 0xe2, 0xe8, 0xe5, 0x15, 0x4d, 0x56, 0xff, 0x90, 0xd1, 0xa7,
+ 0xa6, 0x04, 0x3f, 0x60, 0x07, 0x4a, 0xca, 0x6f, 0xa5, 0x10, 0xe7, 0xb3,
+ 0xf8, 0x5c, 0xb1, 0xbc, 0x2b, 0x2a, 0xdc, 0x01, 0x79, 0xf5, 0x1d, 0x35,
+ 0xf5, 0x7a, 0x28, 0x83, 0xf2, 0x93, 0x73, 0x82, 0x89, 0xac, 0x60, 0x6d,
+ 0xcb, 0xc2, 0x48, 0xc2, 0x1d, 0xd4, 0x06, 0x44, 0x17, 0x3c, 0xac, 0x01,
+ 0x47, 0xab, 0x3e, 0x70, 0x84, 0x09, 0x0b, 0xb8, 0x20, 0x08, 0x40, 0x20,
+ 0x87, 0xa1, 0x63, 0x1a, 0xca, 0x3e, 0x83, 0xd2, 0x37, 0xb3, 0x98, 0x8d,
+ 0x32, 0x3f, 0x37, 0xbf, 0xa1, 0xb7, 0x5b, 0x5f, 0xde, 0x5c, 0x33, 0x92,
+ 0xcf, 0x3e, 0x07, 0xce, 0xb9, 0x48, 0x4b, 0xe2, 0xf0, 0x55, 0x50, 0x2f,
+ 0xf8, 0x70, 0x42, 0x89, 0xd1, 0x93, 0x96, 0x8a, 0x63, 0xd9, 0x66, 0x0d,
+ 0xe6, 0x58, 0x6e, 0xb9, 0x6d, 0x90, 0xbd, 0xca, 0xdc, 0x84, 0x66, 0xf2,
+ 0x39, 0x8e, 0x5b, 0xa6, 0x58, 0x55, 0x73, 0xcb, 0x62, 0x6c, 0x1b, 0xd7,
+ 0x20, 0x16, 0x3b, 0x2c, 0x59, 0xf5, 0xcb, 0xc8, 0x56, 0x32, 0x4a, 0x50,
+ 0x27, 0xba, 0x55, 0xd3, 0xa8, 0x01, 0xcb, 0x72, 0xa9, 0x74, 0x8b, 0x0c,
+ 0xad, 0x3a, 0xe5, 0x15, 0xb6, 0x2a, 0xdf, 0x65, 0xf8, 0xde, 0x8a, 0xf5,
+ 0xef, 0x84, 0x3b, 0xf9, 0xe7, 0x54, 0x65, 0x0b, 0x80, 0xbd, 0x47, 0x45,
+ 0xa5, 0xf0, 0x44, 0xd8, 0x53, 0x3b, 0xbe, 0x80, 0xf1, 0x2f, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x44, 0x30, 0x82, 0x01, 0x40, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x49, 0x4b,
+ 0x52, 0x27, 0xd1, 0x1b, 0xbc, 0xf2, 0xa1, 0x21, 0x6a, 0x62, 0x7b, 0x51,
+ 0x42, 0x7a, 0x8a, 0xd7, 0xd5, 0x56, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xbf, 0x5f, 0xb7, 0xd1, 0xce,
+ 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, 0xdc, 0xd7, 0x10, 0xc2, 0x0e,
+ 0xa9, 0x88, 0xe7, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+ 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30,
+ 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x2d, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65,
+ 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4c,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x45, 0x30, 0x43, 0x30, 0x41, 0xa0,
+ 0x3f, 0xa0, 0x3d, 0x86, 0x3b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73,
+ 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65,
+ 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73,
+ 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x73, 0x66, 0x72, 0x6f, 0x6f, 0x74,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x51, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x4a, 0x30, 0x48, 0x30, 0x46, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30,
+ 0x3e, 0x30, 0x3c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+ 0x01, 0x16, 0x30, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x86, 0x52, 0xba, 0xb3, 0x1f, 0xa6, 0x5e, 0x6b, 0x90,
+ 0xa6, 0x64, 0x2a, 0xfc, 0x45, 0xb2, 0xae, 0x9f, 0x3e, 0xb3, 0x62, 0xaf,
+ 0xdb, 0x1f, 0x67, 0xc4, 0xbd, 0xca, 0xa1, 0x2f, 0xc7, 0x9c, 0x0d, 0x21,
+ 0x57, 0xd0, 0xf8, 0x36, 0x21, 0xce, 0x3a, 0x25, 0x3e, 0x78, 0x76, 0xb3,
+ 0xd9, 0xdd, 0xbc, 0xde, 0xfb, 0x6c, 0x84, 0x5f, 0x0c, 0xa3, 0x0d, 0x12,
+ 0xeb, 0x11, 0x3b, 0x71, 0x5f, 0x80, 0x1e, 0xf1, 0x1f, 0x6d, 0x0e, 0x5f,
+ 0xc1, 0xec, 0xd4, 0xa5, 0xf7, 0x65, 0xbb, 0x1f, 0x4c, 0x95, 0x01, 0x13,
+ 0xb2, 0x6a, 0x9c, 0x0b, 0xeb, 0x1f, 0x9d, 0xb1, 0xe7, 0xed, 0x19, 0x0d,
+ 0xbc, 0x85, 0x7c, 0xf3, 0x17, 0xbd, 0x59, 0x63, 0xae, 0xa7, 0x1a, 0x05,
+ 0xcd, 0x47, 0xe3, 0x2d, 0x96, 0x62, 0x51, 0x32, 0x0a, 0x08, 0x68, 0x4b,
+ 0x22, 0x77, 0x5f, 0xf7, 0x45, 0xdc, 0x61, 0xde, 0xf4, 0xcb, 0x2b, 0x22,
+ 0x29, 0x44, 0x25, 0xd2, 0x9f, 0x0b, 0x77, 0x7a, 0xa1, 0x26, 0x7c, 0x4a,
+ 0xd7, 0x0f, 0xc2, 0xd1, 0x3c, 0xba, 0x0e, 0xa7, 0x95, 0x9a, 0x5b, 0x05,
+ 0x0a, 0x10, 0xf9, 0x55, 0x5f, 0xc1, 0x97, 0x8b, 0x74, 0xcc, 0x5e, 0x28,
+ 0x69, 0x13, 0x7e, 0xd0, 0x0a, 0x8d, 0x9d, 0x0f, 0x60, 0x54, 0x7a, 0xc4,
+ 0x8c, 0x1b, 0x35, 0x0f, 0x74, 0x7a, 0x70, 0xb2, 0x82, 0xcf, 0x1d, 0xb5,
+ 0xe2, 0x8a, 0xdb, 0x2a, 0xc6, 0xb2, 0x51, 0x69, 0xbf, 0x12, 0x17, 0x92,
+ 0x60, 0x17, 0xaa, 0x3d, 0x5b, 0x09, 0xf8, 0x87, 0x65, 0x1d, 0xa7, 0xa4,
+ 0x28, 0xe5, 0x22, 0x02, 0x03, 0x82, 0x44, 0x9a, 0x34, 0x63, 0x9e, 0xfb,
+ 0x28, 0xcf, 0xe8, 0xcd, 0x2e, 0x0e, 0x52, 0x20, 0xed, 0x4a, 0xcb, 0x38,
+ 0x7c, 0x9d, 0xae, 0x6e, 0x79, 0xd7, 0x95, 0x2c, 0xa8, 0x91, 0xf3, 0x86,
+ 0x01, 0x21, 0x91, 0x4b, 0xb5, 0x40, 0xa4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1276028635 (0x4c0ea6db)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
+ Validity
+ Not Before: Oct 1 19:42:24 2006 GMT
+ Not After : Nov 4 03:38:44 2016 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
+ e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
+ a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
+ a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
+ cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
+ 91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
+ f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
+ 55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
+ be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
+ ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
+ 22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
+ 57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
+ 68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
+ 64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
+ 39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
+ a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
+ 87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
+ 4b:cb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, E-mail Protection
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/2048ca.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.digicert.com/ssl-cps-repository.htm
+
+ X509v3 Subject Key Identifier:
+ B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+ X509v3 Authority Key Identifier:
+ keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V8.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ 59:e1:94:14:89:c6:72:3c:e7:6b:75:4b:25:7a:2d:3e:a3:db:
+ ac:3c:72:4f:9b:30:b0:a2:5e:d6:62:5d:8f:36:6b:e7:dd:23:
+ 59:c1:80:2c:a0:ed:7e:11:a0:c9:a3:bb:f6:96:b8:34:c9:fe:
+ c6:d7:58:b4:bb:27:7f:e5:6b:23:04:68:61:4b:16:57:df:e1:
+ 7e:c0:c5:36:8f:0c:04:de:ef:77:68:68:83:6d:7c:05:fb:45:
+ dd:ce:16:56:91:39:d2:58:91:51:95:87:9e:4d:b4:0a:d7:05:
+ 63:83:43:26:de:08:a6:19:77:9d:fe:59:a2:5f:db:32:33:4a:
+ 65:10:c4:47:ef:ba:57:07:1f:4c:9f:af:68:65:ef:67:6d:9a:
+ de:1e:5e:4e:87:85:ee:9d:0d:7b:3d:d2:03:a9:dd:b7:05:04:
+ 9e:95:0d:c1:b2:11:fd:5a:77:c4:1f:98:9f:2e:a0:d0:c9:7c:
+ d3:34:62:f5:2f:96:37:48:48:b4:21:fb:2f:ad:53:65:34:c2:
+ 7b:4a:7c:fc:90:49:9f:f3:f7:37:08:9e:41:00:b2:63:1b:4b:
+ b9:f6:c1:7d:59:66:ab:d1:f3:8a:30:05:18:7a:41:47:ab:c7:
+ 67:14:3a:7c:60:b1:08:4e:d0:ce:c7:e1:ad:a6:4d:ee:ae:32:
+ ac:ac:c6:5a
+-----BEGIN CERTIFICATE-----
+MIIFBzCCA++gAwIBAgIETA6m2zANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw0wNjEwMDExOTQyMjRaFw0xNjEx
+MDQwMzM4NDRaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
+GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhp
+Z2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD1ZQ0Z6IKHLBfaaZAscS3
+so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80ltcZF+Y7arpl/DpIT4T2JR
+vvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46OFBbdzEbjbPHJEWap6xt
+ABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZdHFMsfpjNGgYWpGhz0DQE
+E1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdmt4i3ePLKCqg4qwpkwr9m
+XZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggFmMIIBYjAOBgNVHQ8BAf8E
+BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAnBgNVHSUEIDAeBggrBgEFBQcDAQYI
+KwYBBQUHAwIGCCsGAQUFBwMEMDMGCCsGAQUFBwEBBCcwJTAjBggrBgEFBQcwAYYX
+aHR0cDovL29jc3AuZW50cnVzdC5uZXQwMgYDVR0fBCswKTAnoCWgI4YhaHR0cDov
+L2NybC5lbnRydXN0Lm5ldC8yMDQ4Y2EuY3JsME8GA1UdIARIMEYwRAYEVR0gADA8
+MDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJl
+cG9zaXRvcnkuaHRtMB0GA1UdDgQWBBSxPsNpA/i/RwHUmCYaCALvY2QrwzAfBgNV
+HSMEGDAWgBRV5IHREYC+2Im5CKMx+aEkCRa5cDAZBgkqhkiG9n0HQQAEDDAKGwRW
+OC4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAWeGUFInGcjzna3VLJXotPqPbrDxy
+T5swsKJe1mJdjzZr590jWcGALKDtfhGgyaO79pa4NMn+xtdYtLsnf+VrIwRoYUsW
+V9/hfsDFNo8MBN7vd2hog218BftF3c4WVpE50liRUZWHnk20CtcFY4NDJt4Iphl3
+nf5Zol/bMjNKZRDER++6VwcfTJ+vaGXvZ22a3h5eToeF7p0Nez3SA6ndtwUEnpUN
+wbIR/Vp3xB+Yny6g0Ml80zRi9S+WN0hItCH7L61TZTTCe0p8/JBJn/P3NwieQQCy
+YxtLufbBfVlmq9HzijAFGHpBR6vHZxQ6fGCxCE7QzsfhraZN7q4yrKzGWg==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert77[] = {
+ 0x30, 0x82, 0x05, 0x07, 0x30, 0x82, 0x03, 0xef, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x4c, 0x0e, 0xa6, 0xdb, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x31,
+ 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37, 0x77, 0x77,
+ 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x20, 0x69,
+ 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20, 0x72, 0x65,
+ 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x6c,
+ 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+ 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74, 0x72, 0x75,
+ 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30, 0x34, 0x38,
+ 0x29, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x30, 0x30, 0x31, 0x31,
+ 0x39, 0x34, 0x32, 0x32, 0x34, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31,
+ 0x30, 0x34, 0x30, 0x33, 0x33, 0x38, 0x34, 0x34, 0x5a, 0x30, 0x6c, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44,
+ 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31,
+ 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77,
+ 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69,
+ 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65,
+ 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6, 0xcc, 0xe5, 0x73,
+ 0xe6, 0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81,
+ 0x3f, 0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34,
+ 0x67, 0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7,
+ 0xb2, 0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, 0x3a, 0xd3, 0xdc, 0x94,
+ 0xd6, 0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7,
+ 0x4a, 0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e,
+ 0x63, 0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51,
+ 0xbe, 0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, 0x06, 0xe5, 0x08, 0x91,
+ 0x0c, 0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71,
+ 0xe8, 0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b,
+ 0x77, 0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d,
+ 0x00, 0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, 0x51, 0x0a, 0x98, 0x41,
+ 0x41, 0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c,
+ 0x89, 0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c,
+ 0x7e, 0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04,
+ 0x13, 0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, 0xdb, 0x5e, 0x64, 0xe1,
+ 0x37, 0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1,
+ 0x79, 0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7,
+ 0x78, 0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66,
+ 0x5d, 0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, 0x5d, 0x1a, 0x50, 0x0b,
+ 0x20, 0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x66, 0x30, 0x82, 0x01,
+ 0x62, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x01, 0x30, 0x27, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x20, 0x30, 0x1e,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e,
+ 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30,
+ 0x32, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2b, 0x30, 0x29, 0x30, 0x27,
+ 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x32, 0x30, 0x34, 0x38, 0x63, 0x61, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x4f, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x48,
+ 0x30, 0x46, 0x30, 0x44, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3c,
+ 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+ 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xb1,
+ 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a,
+ 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x1f, 0x06, 0x03, 0x55,
+ 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x55, 0xe4, 0x81, 0xd1,
+ 0x11, 0x80, 0xbe, 0xd8, 0x89, 0xb9, 0x08, 0xa3, 0x31, 0xf9, 0xa1, 0x24,
+ 0x09, 0x16, 0xb9, 0x70, 0x30, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56,
+ 0x38, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x01, 0x00, 0x59, 0xe1, 0x94, 0x14, 0x89, 0xc6, 0x72, 0x3c, 0xe7,
+ 0x6b, 0x75, 0x4b, 0x25, 0x7a, 0x2d, 0x3e, 0xa3, 0xdb, 0xac, 0x3c, 0x72,
+ 0x4f, 0x9b, 0x30, 0xb0, 0xa2, 0x5e, 0xd6, 0x62, 0x5d, 0x8f, 0x36, 0x6b,
+ 0xe7, 0xdd, 0x23, 0x59, 0xc1, 0x80, 0x2c, 0xa0, 0xed, 0x7e, 0x11, 0xa0,
+ 0xc9, 0xa3, 0xbb, 0xf6, 0x96, 0xb8, 0x34, 0xc9, 0xfe, 0xc6, 0xd7, 0x58,
+ 0xb4, 0xbb, 0x27, 0x7f, 0xe5, 0x6b, 0x23, 0x04, 0x68, 0x61, 0x4b, 0x16,
+ 0x57, 0xdf, 0xe1, 0x7e, 0xc0, 0xc5, 0x36, 0x8f, 0x0c, 0x04, 0xde, 0xef,
+ 0x77, 0x68, 0x68, 0x83, 0x6d, 0x7c, 0x05, 0xfb, 0x45, 0xdd, 0xce, 0x16,
+ 0x56, 0x91, 0x39, 0xd2, 0x58, 0x91, 0x51, 0x95, 0x87, 0x9e, 0x4d, 0xb4,
+ 0x0a, 0xd7, 0x05, 0x63, 0x83, 0x43, 0x26, 0xde, 0x08, 0xa6, 0x19, 0x77,
+ 0x9d, 0xfe, 0x59, 0xa2, 0x5f, 0xdb, 0x32, 0x33, 0x4a, 0x65, 0x10, 0xc4,
+ 0x47, 0xef, 0xba, 0x57, 0x07, 0x1f, 0x4c, 0x9f, 0xaf, 0x68, 0x65, 0xef,
+ 0x67, 0x6d, 0x9a, 0xde, 0x1e, 0x5e, 0x4e, 0x87, 0x85, 0xee, 0x9d, 0x0d,
+ 0x7b, 0x3d, 0xd2, 0x03, 0xa9, 0xdd, 0xb7, 0x05, 0x04, 0x9e, 0x95, 0x0d,
+ 0xc1, 0xb2, 0x11, 0xfd, 0x5a, 0x77, 0xc4, 0x1f, 0x98, 0x9f, 0x2e, 0xa0,
+ 0xd0, 0xc9, 0x7c, 0xd3, 0x34, 0x62, 0xf5, 0x2f, 0x96, 0x37, 0x48, 0x48,
+ 0xb4, 0x21, 0xfb, 0x2f, 0xad, 0x53, 0x65, 0x34, 0xc2, 0x7b, 0x4a, 0x7c,
+ 0xfc, 0x90, 0x49, 0x9f, 0xf3, 0xf7, 0x37, 0x08, 0x9e, 0x41, 0x00, 0xb2,
+ 0x63, 0x1b, 0x4b, 0xb9, 0xf6, 0xc1, 0x7d, 0x59, 0x66, 0xab, 0xd1, 0xf3,
+ 0x8a, 0x30, 0x05, 0x18, 0x7a, 0x41, 0x47, 0xab, 0xc7, 0x67, 0x14, 0x3a,
+ 0x7c, 0x60, 0xb1, 0x08, 0x4e, 0xd0, 0xce, 0xc7, 0xe1, 0xad, 0xa6, 0x4d,
+ 0xee, 0xae, 0x32, 0xac, 0xac, 0xc6, 0x5a,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1164679900 (0x456b9adc)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+ Validity
+ Not Before: Dec 10 20:55:43 2009 GMT
+ Not After : Dec 10 21:25:43 2019 GMT
+ Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1E
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:5b:04:54:77:dd:0e:24:66:dc:2a:a1:db:80:
+ cc:5d:c7:5f:fd:52:16:58:da:5f:94:06:a9:b8:b6:
+ b9:63:0c:47:20:82:ec:c7:95:4e:8b:b8:77:52:6a:
+ 3d:b5:87:a9:d6:e1:cc:74:e5:a6:c8:c0:d4:56:4f:
+ 8d:2e:d6:08:3e:0c:4c:43:3e:f0:41:93:5e:46:ef:
+ 39:e7:d9:65:2a:0c:76:50:27:bd:5b:0d:33:33:07:
+ e0:f7:a2:a9:9c:e1:11:33:ad:66:fc:d2:2c:7a:aa:
+ a3:73:16:be:93:85:75:0f:d7:37:8c:fa:23:b7:64:
+ f8:e3:4c:6e:ed:b3:05:bd:e2:36:db:7c:de:76:44:
+ da:82:72:76:b6:6e:ff:94:a1:d0:86:f7:10:cd:4a:
+ 5a:8b:b0:75:8c:66:52:80:4e:48:4c:49:83:a6:40:
+ d7:77:81:13:4d:5e:72:7e:48:46:22:aa:0f:e2:3e:
+ 65:94:38:e1:72:71:fe:4a:71:09:ba:35:7f:55:89:
+ 3d:81:d5:b8:28:01:10:77:36:5a:10:85:d2:bd:60:
+ 84:2b:49:61:94:0c:de:4c:40:6a:2a:c4:79:60:84:
+ 24:82:32:69:4a:98:4b:e2:56:10:ba:03:45:51:20:
+ d3:cf:da:8e:54:1b:45:b6:7a:ba:97:9a:5a:d8:c6:
+ d1:5f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/rootca1.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/CPS
+
+ X509v3 Subject Key Identifier:
+ 5B:41:8A:B2:C4:43:C1:BD:BF:C8:54:41:55:9D:E0:96:AD:FF:B9:A1
+ X509v3 Authority Key Identifier:
+ keyid:68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V7.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ b2:3b:d2:9e:c1:bc:3b:48:b6:dc:d8:5a:18:66:53:c3:bd:35:
+ 0d:48:42:2c:35:01:d8:10:a2:e2:e3:8d:2c:ba:a6:03:11:ed:
+ 6b:b1:49:cb:5f:cd:ec:60:b3:ba:d4:02:eb:61:4f:4e:7e:f8:
+ df:90:5f:4e:d3:90:02:1c:52:da:12:00:2f:9b:71:da:04:12:
+ 14:c1:90:83:2e:28:d2:10:40:11:8b:26:2d:eb:99:55:54:6f:
+ 60:8e:c5:83:1d:c0:a3:3f:d5:8a:14:39:6a:1b:0d:ef:d3:5a:
+ 77:39:cf:69:b4:bd:69:6f:4f:78:d3:a1:86:a3:9b:b7:d7:fb:
+ aa:2d:f0:fa:26:a1:f9:67:2c:88:4b:a5:34:d5:83:fb:4c:f1:
+ 5b:70:22:66:1b:9b:59:4f:4d:ce:98:db:41:a4:fe:1a:a3:eb:
+ 38:e6:f9:f1:39:02:9d:46:b6:c9:c2:9e:3e:82:b6:1f:9f:ca:
+ 4a:a8:b1:06:5f:10:34:3b:fd:da:7b:ac:33:4e:ed:a6:b7:4b:
+ f3:91:f5:9c:0b:11:92:dc:13:6a:c8:d5:f1:3b:6d:96:6b:01:
+ e4:23:4c:b1:c1:e0:d2:12:21:9f:29:d4:ad:95:3d:a6:f7:e7:
+ 32:c5:75:b7:0b:57:d8:a4:f9:c0:ec:ec:32:33:0c:4d:ae:e8:
+ 08:d5:ec:aa
+-----BEGIN CERTIFICATE-----
+MIIFCjCCA/KgAwIBAgIERWua3DANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MTIxMDIwNTU0M1oXDTE5MTIxMDIx
+MjU0M1owgbExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvcnBhIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA5IEVudHJ1c3QsIEluYy4xLjAsBgNV
+BAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBMMUUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2WwRUd90OJGbcKqHbgMxdx1/9UhZY
+2l+UBqm4trljDEcgguzHlU6LuHdSaj21h6nW4cx05abIwNRWT40u1gg+DExDPvBB
+k15G7znn2WUqDHZQJ71bDTMzB+D3oqmc4REzrWb80ix6qqNzFr6ThXUP1zeM+iO3
+ZPjjTG7tswW94jbbfN52RNqCcna2bv+UodCG9xDNSlqLsHWMZlKATkhMSYOmQNd3
+gRNNXnJ+SEYiqg/iPmWUOOFycf5KcQm6NX9ViT2B1bgoARB3NloQhdK9YIQrSWGU
+DN5MQGoqxHlghCSCMmlKmEviVhC6A0VRINPP2o5UG0W2erqXmlrYxtFfAgMBAAGj
+ggEnMIIBIzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAzBggrBgEF
+BQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0MDMG
+A1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvcm9vdGNhMS5j
+cmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cu
+ZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRbQYqyxEPBvb/IVEFVneCWrf+5oTAf
+BgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAZBgkqhkiG9n0HQQAEDDAK
+GwRWNy4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAsjvSnsG8O0i23NhaGGZTw701
+DUhCLDUB2BCi4uONLLqmAxHta7FJy1/N7GCzutQC62FPTn7435BfTtOQAhxS2hIA
+L5tx2gQSFMGQgy4o0hBAEYsmLeuZVVRvYI7Fgx3Aoz/VihQ5ahsN79NadznPabS9
+aW9PeNOhhqObt9f7qi3w+iah+WcsiEulNNWD+0zxW3AiZhubWU9NzpjbQaT+GqPr
+OOb58TkCnUa2ycKePoK2H5/KSqixBl8QNDv92nusM07tprdL85H1nAsRktwTasjV
+8TttlmsB5CNMscHg0hIhnynUrZU9pvfnMsV1twtX2KT5wOzsMjMMTa7oCNXsqg==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert78[] = {
+ 0x30, 0x82, 0x05, 0x0a, 0x30, 0x82, 0x03, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x45, 0x6b, 0x9a, 0xdc, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20,
+ 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64,
+ 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
+ 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16,
+ 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d,
+ 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+ 0x0d, 0x30, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32, 0x30, 0x35, 0x35, 0x34,
+ 0x33, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x31, 0x30, 0x32, 0x31,
+ 0x32, 0x35, 0x34, 0x33, 0x5a, 0x30, 0x81, 0xb1, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30,
+ 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30,
+ 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e,
+ 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72,
+ 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32,
+ 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c,
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+ 0x2d, 0x20, 0x4c, 0x31, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xb6, 0x5b, 0x04, 0x54, 0x77, 0xdd, 0x0e, 0x24, 0x66, 0xdc,
+ 0x2a, 0xa1, 0xdb, 0x80, 0xcc, 0x5d, 0xc7, 0x5f, 0xfd, 0x52, 0x16, 0x58,
+ 0xda, 0x5f, 0x94, 0x06, 0xa9, 0xb8, 0xb6, 0xb9, 0x63, 0x0c, 0x47, 0x20,
+ 0x82, 0xec, 0xc7, 0x95, 0x4e, 0x8b, 0xb8, 0x77, 0x52, 0x6a, 0x3d, 0xb5,
+ 0x87, 0xa9, 0xd6, 0xe1, 0xcc, 0x74, 0xe5, 0xa6, 0xc8, 0xc0, 0xd4, 0x56,
+ 0x4f, 0x8d, 0x2e, 0xd6, 0x08, 0x3e, 0x0c, 0x4c, 0x43, 0x3e, 0xf0, 0x41,
+ 0x93, 0x5e, 0x46, 0xef, 0x39, 0xe7, 0xd9, 0x65, 0x2a, 0x0c, 0x76, 0x50,
+ 0x27, 0xbd, 0x5b, 0x0d, 0x33, 0x33, 0x07, 0xe0, 0xf7, 0xa2, 0xa9, 0x9c,
+ 0xe1, 0x11, 0x33, 0xad, 0x66, 0xfc, 0xd2, 0x2c, 0x7a, 0xaa, 0xa3, 0x73,
+ 0x16, 0xbe, 0x93, 0x85, 0x75, 0x0f, 0xd7, 0x37, 0x8c, 0xfa, 0x23, 0xb7,
+ 0x64, 0xf8, 0xe3, 0x4c, 0x6e, 0xed, 0xb3, 0x05, 0xbd, 0xe2, 0x36, 0xdb,
+ 0x7c, 0xde, 0x76, 0x44, 0xda, 0x82, 0x72, 0x76, 0xb6, 0x6e, 0xff, 0x94,
+ 0xa1, 0xd0, 0x86, 0xf7, 0x10, 0xcd, 0x4a, 0x5a, 0x8b, 0xb0, 0x75, 0x8c,
+ 0x66, 0x52, 0x80, 0x4e, 0x48, 0x4c, 0x49, 0x83, 0xa6, 0x40, 0xd7, 0x77,
+ 0x81, 0x13, 0x4d, 0x5e, 0x72, 0x7e, 0x48, 0x46, 0x22, 0xaa, 0x0f, 0xe2,
+ 0x3e, 0x65, 0x94, 0x38, 0xe1, 0x72, 0x71, 0xfe, 0x4a, 0x71, 0x09, 0xba,
+ 0x35, 0x7f, 0x55, 0x89, 0x3d, 0x81, 0xd5, 0xb8, 0x28, 0x01, 0x10, 0x77,
+ 0x36, 0x5a, 0x10, 0x85, 0xd2, 0xbd, 0x60, 0x84, 0x2b, 0x49, 0x61, 0x94,
+ 0x0c, 0xde, 0x4c, 0x40, 0x6a, 0x2a, 0xc4, 0x79, 0x60, 0x84, 0x24, 0x82,
+ 0x32, 0x69, 0x4a, 0x98, 0x4b, 0xe2, 0x56, 0x10, 0xba, 0x03, 0x45, 0x51,
+ 0x20, 0xd3, 0xcf, 0xda, 0x8e, 0x54, 0x1b, 0x45, 0xb6, 0x7a, 0xba, 0x97,
+ 0x9a, 0x5a, 0xd8, 0xc6, 0xd1, 0x5f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x27, 0x30, 0x82, 0x01, 0x23, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+ 0x03, 0x01, 0x01, 0xff, 0x30, 0x33, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30, 0x23, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x65, 0x6e,
+ 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x30, 0x33, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30, 0x28, 0xa0, 0x26,
+ 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61, 0x31, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30,
+ 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30,
+ 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+ 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0x5b, 0x41, 0x8a, 0xb2, 0xc4, 0x43, 0xc1, 0xbd, 0xbf, 0xc8,
+ 0x54, 0x41, 0x55, 0x9d, 0xe0, 0x96, 0xad, 0xff, 0xb9, 0xa1, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x68,
+ 0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86, 0x66, 0xa4, 0xf1,
+ 0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x19, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04, 0x0c, 0x30, 0x0a,
+ 0x1b, 0x04, 0x56, 0x37, 0x2e, 0x31, 0x03, 0x02, 0x00, 0x81, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xb2, 0x3b, 0xd2, 0x9e, 0xc1, 0xbc,
+ 0x3b, 0x48, 0xb6, 0xdc, 0xd8, 0x5a, 0x18, 0x66, 0x53, 0xc3, 0xbd, 0x35,
+ 0x0d, 0x48, 0x42, 0x2c, 0x35, 0x01, 0xd8, 0x10, 0xa2, 0xe2, 0xe3, 0x8d,
+ 0x2c, 0xba, 0xa6, 0x03, 0x11, 0xed, 0x6b, 0xb1, 0x49, 0xcb, 0x5f, 0xcd,
+ 0xec, 0x60, 0xb3, 0xba, 0xd4, 0x02, 0xeb, 0x61, 0x4f, 0x4e, 0x7e, 0xf8,
+ 0xdf, 0x90, 0x5f, 0x4e, 0xd3, 0x90, 0x02, 0x1c, 0x52, 0xda, 0x12, 0x00,
+ 0x2f, 0x9b, 0x71, 0xda, 0x04, 0x12, 0x14, 0xc1, 0x90, 0x83, 0x2e, 0x28,
+ 0xd2, 0x10, 0x40, 0x11, 0x8b, 0x26, 0x2d, 0xeb, 0x99, 0x55, 0x54, 0x6f,
+ 0x60, 0x8e, 0xc5, 0x83, 0x1d, 0xc0, 0xa3, 0x3f, 0xd5, 0x8a, 0x14, 0x39,
+ 0x6a, 0x1b, 0x0d, 0xef, 0xd3, 0x5a, 0x77, 0x39, 0xcf, 0x69, 0xb4, 0xbd,
+ 0x69, 0x6f, 0x4f, 0x78, 0xd3, 0xa1, 0x86, 0xa3, 0x9b, 0xb7, 0xd7, 0xfb,
+ 0xaa, 0x2d, 0xf0, 0xfa, 0x26, 0xa1, 0xf9, 0x67, 0x2c, 0x88, 0x4b, 0xa5,
+ 0x34, 0xd5, 0x83, 0xfb, 0x4c, 0xf1, 0x5b, 0x70, 0x22, 0x66, 0x1b, 0x9b,
+ 0x59, 0x4f, 0x4d, 0xce, 0x98, 0xdb, 0x41, 0xa4, 0xfe, 0x1a, 0xa3, 0xeb,
+ 0x38, 0xe6, 0xf9, 0xf1, 0x39, 0x02, 0x9d, 0x46, 0xb6, 0xc9, 0xc2, 0x9e,
+ 0x3e, 0x82, 0xb6, 0x1f, 0x9f, 0xca, 0x4a, 0xa8, 0xb1, 0x06, 0x5f, 0x10,
+ 0x34, 0x3b, 0xfd, 0xda, 0x7b, 0xac, 0x33, 0x4e, 0xed, 0xa6, 0xb7, 0x4b,
+ 0xf3, 0x91, 0xf5, 0x9c, 0x0b, 0x11, 0x92, 0xdc, 0x13, 0x6a, 0xc8, 0xd5,
+ 0xf1, 0x3b, 0x6d, 0x96, 0x6b, 0x01, 0xe4, 0x23, 0x4c, 0xb1, 0xc1, 0xe0,
+ 0xd2, 0x12, 0x21, 0x9f, 0x29, 0xd4, 0xad, 0x95, 0x3d, 0xa6, 0xf7, 0xe7,
+ 0x32, 0xc5, 0x75, 0xb7, 0x0b, 0x57, 0xd8, 0xa4, 0xf9, 0xc0, 0xec, 0xec,
+ 0x32, 0x33, 0x0c, 0x4d, 0xae, 0xe8, 0x08, 0xd5, 0xec, 0xaa,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 7b:11:55:eb:78:9a:90:85:b5:8c:92:ff:42:b7:fe:56
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+ Validity
+ Not Before: Nov 17 00:00:00 2006 GMT
+ Not After : Nov 16 23:59:59 2016 GMT
+ Subject: C=US, O=thawte, Inc., OU=Terms of use at https://www.thawte.com/cps (c)06, CN=thawte Extended Validation SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b5:8d:47:f7:b0:48:76:9b:bd:fb:a9:cb:bf:04:
+ 31:a2:3d:9a:7e:30:29:d3:28:b8:fe:68:ce:cf:e9:
+ 30:6a:53:95:0e:50:65:80:26:c9:98:bf:f2:14:ff:
+ 06:7c:6a:7b:dc:50:07:e2:98:fa:df:cf:30:5d:ca:
+ a8:b9:8a:9b:2d:2d:7e:59:8b:1a:f7:b3:c9:c3:69:
+ 80:0f:89:19:08:77:b2:52:55:ad:78:83:9d:6b:b9:
+ 87:e4:53:24:37:2c:fc:19:0e:8b:79:14:4d:be:80:
+ 9e:b4:9b:73:74:31:f2:38:ec:8a:af:2a:36:8e:64:
+ ce:31:26:14:03:54:53:8e:fb:84:08:c1:7e:47:32:
+ 3d:71:e0:ba:ba:8c:82:58:96:4d:68:43:56:1a:f3:
+ 46:5a:32:99:95:b0:60:6f:e9:41:8a:48:cc:16:0d:
+ 44:68:b1:8a:dd:dd:17:3d:a4:9b:78:7f:2e:29:06:
+ f0:dc:d5:d2:13:3f:c0:36:05:fd:c7:b5:b9:80:1b:
+ 8a:46:74:2f:f1:ab:79:9e:97:6e:f8:a5:13:5a:f3:
+ fc:b5:d7:c8:96:19:37:ee:06:bc:c6:27:14:81:05:
+ 14:33:38:16:9f:4b:e2:0f:db:38:bb:f3:01:ef:35:
+ 2e:de:af:f1:e4:6f:6f:f7:96:00:56:5e:8f:60:94:
+ 1d:2f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://EVSecure-ocsp.thawte.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.thawte.com/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.thawte.com/ThawtePCA.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Alternative Name:
+ DirName:/CN=PrivateLabel3-2048-234
+ X509v3 Subject Key Identifier:
+ CD:32:E2:F2:5D:25:47:02:AA:8F:79:4B:32:EE:03:99:FD:30:49:D1
+ X509v3 Authority Key Identifier:
+ keyid:7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 0b:b4:96:ce:03:0c:d1:9d:af:cb:e3:39:56:0d:c6:22:a0:c9:
+ 71:7d:ea:65:95:31:f1:dc:b6:1e:f2:8d:31:5d:61:b3:54:84:
+ 13:cc:2b:3f:02:5c:c7:1f:15:01:82:90:1e:31:25:06:e3:32:
+ 0c:87:f0:c3:be:9a:c4:00:41:f6:c6:91:e5:6c:3e:92:5d:a3:
+ e4:3d:1f:32:2d:31:1e:50:c1:02:21:b4:23:e3:07:75:9a:52:
+ 45:51:fa:d3:1d:fd:01:6f:60:6d:25:d9:bf:43:b1:a7:43:6c:
+ ad:8c:bb:bc:f7:99:41:eb:d6:95:cf:20:5c:7e:6f:c4:2a:da:
+ 4b:4d:1b:5b:c2:9f:b0:94:d4:bf:47:97:fd:9d:49:79:60:8e:
+ ae:96:19:a1:b0:eb:e8:df:42:c7:22:74:61:0c:25:a3:7f:8f:
+ 45:d2:7e:e7:4a:6e:1d:4f:48:bb:c2:da:1a:7e:4a:59:81:fa:
+ 1c:e3:fb:14:73:41:03:a1:77:fa:9b:06:fc:7c:33:bd:46:3d:
+ 0c:06:17:85:7b:2a:7b:e3:36:e8:83:df:fa:aa:cb:32:0c:79:
+ aa:86:74:6c:44:54:f6:d8:07:9e:cd:98:f4:23:05:09:2f:a2:
+ 53:b5:db:0a:81:cc:5f:23:cb:79:11:c5:11:5b:85:6b:27:01:
+ 89:f3:0e:bb
+-----BEGIN CERTIFICATE-----
+MIIFCjCCA/KgAwIBAgIQexFV63iakIW1jJL/Qrf+VjANBgkqhkiG9w0BAQUFADCB
+qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
+BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMTYx
+MTE2MjM1OTU5WjCBizELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
+LjE5MDcGA1UECxMwVGVybXMgb2YgdXNlIGF0IGh0dHBzOi8vd3d3LnRoYXd0ZS5j
+b20vY3BzIChjKTA2MSowKAYDVQQDEyF0aGF3dGUgRXh0ZW5kZWQgVmFsaWRhdGlv
+biBTU0wgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1jUf3sEh2
+m737qcu/BDGiPZp+MCnTKLj+aM7P6TBqU5UOUGWAJsmYv/IU/wZ8anvcUAfimPrf
+zzBdyqi5ipstLX5Zixr3s8nDaYAPiRkId7JSVa14g51ruYfkUyQ3LPwZDot5FE2+
+gJ60m3N0MfI47IqvKjaOZM4xJhQDVFOO+4QIwX5HMj1x4Lq6jIJYlk1oQ1Ya80Za
+MpmVsGBv6UGKSMwWDURosYrd3Rc9pJt4fy4pBvDc1dITP8A2Bf3HtbmAG4pGdC/x
+q3mel274pRNa8/y118iWGTfuBrzGJxSBBRQzOBafS+IP2zi78wHvNS7er/Hkb2/3
+lgBWXo9glB0vAgMBAAGjggFIMIIBRDA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUH
+MAGGH2h0dHA6Ly9FVlNlY3VyZS1vY3NwLnRoYXd0ZS5jb20wEgYDVR0TAQH/BAgw
+BgEB/wIBADA7BgNVHSAENDAyMDAGBFUdIAAwKDAmBggrBgEFBQcCARYaaHR0cHM6
+Ly93d3cudGhhd3RlLmNvbS9jcHMwNAYDVR0fBC0wKzApoCegJYYjaHR0cDovL2Ny
+bC50aGF3dGUuY29tL1RoYXd0ZVBDQS5jcmwwDgYDVR0PAQH/BAQDAgEGMC4GA1Ud
+EQQnMCWkIzAhMR8wHQYDVQQDExZQcml2YXRlTGFiZWwzLTIwNDgtMjM0MB0GA1Ud
+DgQWBBTNMuLyXSVHAqqPeUsy7gOZ/TBJ0TAfBgNVHSMEGDAWgBR7W0XPr87Lev0x
+khpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAC7SWzgMM0Z2vy+M5Vg3GIqDJ
+cX3qZZUx8dy2HvKNMV1hs1SEE8wrPwJcxx8VAYKQHjElBuMyDIfww76axABB9saR
+5Ww+kl2j5D0fMi0xHlDBAiG0I+MHdZpSRVH60x39AW9gbSXZv0Oxp0NsrYy7vPeZ
+QevWlc8gXH5vxCraS00bW8KfsJTUv0eX/Z1JeWCOrpYZobDr6N9CxyJ0YQwlo3+P
+RdJ+50puHU9Iu8LaGn5KWYH6HOP7FHNBA6F3+psG/HwzvUY9DAYXhXsqe+M26IPf
++qrLMgx5qoZ0bERU9tgHns2Y9CMFCS+iU7XbCoHMXyPLeRHFEVuFaycBifMOuw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert79[] = {
+ 0x30, 0x82, 0x05, 0x0a, 0x30, 0x82, 0x03, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x7b, 0x11, 0x55, 0xeb, 0x78, 0x9a, 0x90, 0x85, 0xb5,
+ 0x8c, 0x92, 0xff, 0x42, 0xb7, 0xfe, 0x56, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1f,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x44,
+ 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30,
+ 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20,
+ 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x37,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31,
+ 0x31, 0x31, 0x36, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81,
+ 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+ 0x54, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x61, 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36,
+ 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x74,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64,
+ 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xb5, 0x8d, 0x47, 0xf7, 0xb0, 0x48, 0x76,
+ 0x9b, 0xbd, 0xfb, 0xa9, 0xcb, 0xbf, 0x04, 0x31, 0xa2, 0x3d, 0x9a, 0x7e,
+ 0x30, 0x29, 0xd3, 0x28, 0xb8, 0xfe, 0x68, 0xce, 0xcf, 0xe9, 0x30, 0x6a,
+ 0x53, 0x95, 0x0e, 0x50, 0x65, 0x80, 0x26, 0xc9, 0x98, 0xbf, 0xf2, 0x14,
+ 0xff, 0x06, 0x7c, 0x6a, 0x7b, 0xdc, 0x50, 0x07, 0xe2, 0x98, 0xfa, 0xdf,
+ 0xcf, 0x30, 0x5d, 0xca, 0xa8, 0xb9, 0x8a, 0x9b, 0x2d, 0x2d, 0x7e, 0x59,
+ 0x8b, 0x1a, 0xf7, 0xb3, 0xc9, 0xc3, 0x69, 0x80, 0x0f, 0x89, 0x19, 0x08,
+ 0x77, 0xb2, 0x52, 0x55, 0xad, 0x78, 0x83, 0x9d, 0x6b, 0xb9, 0x87, 0xe4,
+ 0x53, 0x24, 0x37, 0x2c, 0xfc, 0x19, 0x0e, 0x8b, 0x79, 0x14, 0x4d, 0xbe,
+ 0x80, 0x9e, 0xb4, 0x9b, 0x73, 0x74, 0x31, 0xf2, 0x38, 0xec, 0x8a, 0xaf,
+ 0x2a, 0x36, 0x8e, 0x64, 0xce, 0x31, 0x26, 0x14, 0x03, 0x54, 0x53, 0x8e,
+ 0xfb, 0x84, 0x08, 0xc1, 0x7e, 0x47, 0x32, 0x3d, 0x71, 0xe0, 0xba, 0xba,
+ 0x8c, 0x82, 0x58, 0x96, 0x4d, 0x68, 0x43, 0x56, 0x1a, 0xf3, 0x46, 0x5a,
+ 0x32, 0x99, 0x95, 0xb0, 0x60, 0x6f, 0xe9, 0x41, 0x8a, 0x48, 0xcc, 0x16,
+ 0x0d, 0x44, 0x68, 0xb1, 0x8a, 0xdd, 0xdd, 0x17, 0x3d, 0xa4, 0x9b, 0x78,
+ 0x7f, 0x2e, 0x29, 0x06, 0xf0, 0xdc, 0xd5, 0xd2, 0x13, 0x3f, 0xc0, 0x36,
+ 0x05, 0xfd, 0xc7, 0xb5, 0xb9, 0x80, 0x1b, 0x8a, 0x46, 0x74, 0x2f, 0xf1,
+ 0xab, 0x79, 0x9e, 0x97, 0x6e, 0xf8, 0xa5, 0x13, 0x5a, 0xf3, 0xfc, 0xb5,
+ 0xd7, 0xc8, 0x96, 0x19, 0x37, 0xee, 0x06, 0xbc, 0xc6, 0x27, 0x14, 0x81,
+ 0x05, 0x14, 0x33, 0x38, 0x16, 0x9f, 0x4b, 0xe2, 0x0f, 0xdb, 0x38, 0xbb,
+ 0xf3, 0x01, 0xef, 0x35, 0x2e, 0xde, 0xaf, 0xf1, 0xe4, 0x6f, 0x6f, 0xf7,
+ 0x96, 0x00, 0x56, 0x5e, 0x8f, 0x60, 0x94, 0x1d, 0x2f, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0x48, 0x30, 0x82, 0x01, 0x44, 0x30, 0x3b,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2f,
+ 0x30, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x01, 0x86, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45,
+ 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, 0x63, 0x73, 0x70,
+ 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3b, 0x06, 0x03, 0x55,
+ 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d,
+ 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30, 0x34, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0,
+ 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x43, 0x41, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x1d,
+ 0x11, 0x04, 0x27, 0x30, 0x25, 0xa4, 0x23, 0x30, 0x21, 0x31, 0x1f, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x50, 0x72, 0x69, 0x76,
+ 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x33, 0x2d, 0x32, 0x30,
+ 0x34, 0x38, 0x2d, 0x32, 0x33, 0x34, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcd, 0x32, 0xe2, 0xf2, 0x5d, 0x25, 0x47,
+ 0x02, 0xaa, 0x8f, 0x79, 0x4b, 0x32, 0xee, 0x03, 0x99, 0xfd, 0x30, 0x49,
+ 0xd1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31,
+ 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x0b, 0xb4, 0x96, 0xce, 0x03, 0x0c,
+ 0xd1, 0x9d, 0xaf, 0xcb, 0xe3, 0x39, 0x56, 0x0d, 0xc6, 0x22, 0xa0, 0xc9,
+ 0x71, 0x7d, 0xea, 0x65, 0x95, 0x31, 0xf1, 0xdc, 0xb6, 0x1e, 0xf2, 0x8d,
+ 0x31, 0x5d, 0x61, 0xb3, 0x54, 0x84, 0x13, 0xcc, 0x2b, 0x3f, 0x02, 0x5c,
+ 0xc7, 0x1f, 0x15, 0x01, 0x82, 0x90, 0x1e, 0x31, 0x25, 0x06, 0xe3, 0x32,
+ 0x0c, 0x87, 0xf0, 0xc3, 0xbe, 0x9a, 0xc4, 0x00, 0x41, 0xf6, 0xc6, 0x91,
+ 0xe5, 0x6c, 0x3e, 0x92, 0x5d, 0xa3, 0xe4, 0x3d, 0x1f, 0x32, 0x2d, 0x31,
+ 0x1e, 0x50, 0xc1, 0x02, 0x21, 0xb4, 0x23, 0xe3, 0x07, 0x75, 0x9a, 0x52,
+ 0x45, 0x51, 0xfa, 0xd3, 0x1d, 0xfd, 0x01, 0x6f, 0x60, 0x6d, 0x25, 0xd9,
+ 0xbf, 0x43, 0xb1, 0xa7, 0x43, 0x6c, 0xad, 0x8c, 0xbb, 0xbc, 0xf7, 0x99,
+ 0x41, 0xeb, 0xd6, 0x95, 0xcf, 0x20, 0x5c, 0x7e, 0x6f, 0xc4, 0x2a, 0xda,
+ 0x4b, 0x4d, 0x1b, 0x5b, 0xc2, 0x9f, 0xb0, 0x94, 0xd4, 0xbf, 0x47, 0x97,
+ 0xfd, 0x9d, 0x49, 0x79, 0x60, 0x8e, 0xae, 0x96, 0x19, 0xa1, 0xb0, 0xeb,
+ 0xe8, 0xdf, 0x42, 0xc7, 0x22, 0x74, 0x61, 0x0c, 0x25, 0xa3, 0x7f, 0x8f,
+ 0x45, 0xd2, 0x7e, 0xe7, 0x4a, 0x6e, 0x1d, 0x4f, 0x48, 0xbb, 0xc2, 0xda,
+ 0x1a, 0x7e, 0x4a, 0x59, 0x81, 0xfa, 0x1c, 0xe3, 0xfb, 0x14, 0x73, 0x41,
+ 0x03, 0xa1, 0x77, 0xfa, 0x9b, 0x06, 0xfc, 0x7c, 0x33, 0xbd, 0x46, 0x3d,
+ 0x0c, 0x06, 0x17, 0x85, 0x7b, 0x2a, 0x7b, 0xe3, 0x36, 0xe8, 0x83, 0xdf,
+ 0xfa, 0xaa, 0xcb, 0x32, 0x0c, 0x79, 0xaa, 0x86, 0x74, 0x6c, 0x44, 0x54,
+ 0xf6, 0xd8, 0x07, 0x9e, 0xcd, 0x98, 0xf4, 0x23, 0x05, 0x09, 0x2f, 0xa2,
+ 0x53, 0xb5, 0xdb, 0x0a, 0x81, 0xcc, 0x5f, 0x23, 0xcb, 0x79, 0x11, 0xc5,
+ 0x11, 0x5b, 0x85, 0x6b, 0x27, 0x01, 0x89, 0xf3, 0x0e, 0xbb,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1276037400 (0x4c0ec918)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
+ Validity
+ Not Before: Nov 11 14:57:22 2011 GMT
+ Not After : Nov 12 08:12:31 2021 GMT
+ Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/rpa is incorporated by reference, OU=(c) 2009 Entrust, Inc., CN=Entrust Certification Authority - L1E
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:5b:04:54:77:dd:0e:24:66:dc:2a:a1:db:80:
+ cc:5d:c7:5f:fd:52:16:58:da:5f:94:06:a9:b8:b6:
+ b9:63:0c:47:20:82:ec:c7:95:4e:8b:b8:77:52:6a:
+ 3d:b5:87:a9:d6:e1:cc:74:e5:a6:c8:c0:d4:56:4f:
+ 8d:2e:d6:08:3e:0c:4c:43:3e:f0:41:93:5e:46:ef:
+ 39:e7:d9:65:2a:0c:76:50:27:bd:5b:0d:33:33:07:
+ e0:f7:a2:a9:9c:e1:11:33:ad:66:fc:d2:2c:7a:aa:
+ a3:73:16:be:93:85:75:0f:d7:37:8c:fa:23:b7:64:
+ f8:e3:4c:6e:ed:b3:05:bd:e2:36:db:7c:de:76:44:
+ da:82:72:76:b6:6e:ff:94:a1:d0:86:f7:10:cd:4a:
+ 5a:8b:b0:75:8c:66:52:80:4e:48:4c:49:83:a6:40:
+ d7:77:81:13:4d:5e:72:7e:48:46:22:aa:0f:e2:3e:
+ 65:94:38:e1:72:71:fe:4a:71:09:ba:35:7f:55:89:
+ 3d:81:d5:b8:28:01:10:77:36:5a:10:85:d2:bd:60:
+ 84:2b:49:61:94:0c:de:4c:40:6a:2a:c4:79:60:84:
+ 24:82:32:69:4a:98:4b:e2:56:10:ba:03:45:51:20:
+ d3:cf:da:8e:54:1b:45:b6:7a:ba:97:9a:5a:d8:c6:
+ d1:5f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.entrust.net
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.entrust.net/rootca1.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://www.entrust.net/CPS
+
+ X509v3 Subject Key Identifier:
+ 5B:41:8A:B2:C4:43:C1:BD:BF:C8:54:41:55:9D:E0:96:AD:FF:B9:A1
+ X509v3 Authority Key Identifier:
+ keyid:68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
+
+ 1.2.840.113533.7.65.0:
+ 0
+..V8.1....
+ Signature Algorithm: sha1WithRSAEncryption
+ a1:f1:a8:10:e8:e6:29:b9:22:6c:61:5b:2a:3f:3c:01:c7:82:
+ 21:0b:e8:4e:0f:c4:c9:c6:bc:99:9d:f6:ef:5b:c7:69:b2:d9:
+ 9e:ac:52:42:e9:8a:b8:31:c4:13:96:03:8f:65:93:06:69:fe:
+ 28:b6:a6:fd:ad:87:8c:d5:cc:a6:e7:f9:1a:37:ef:32:2d:05:
+ 2d:1e:4e:b9:d5:d5:d1:0f:9b:7f:24:4e:b8:90:ec:e6:69:bf:
+ 9f:2a:3c:63:02:e1:69:a3:6e:a0:34:72:c8:50:50:b6:da:8e:
+ 92:2e:b8:4b:28:fe:f4:92:f0:04:b6:d6:9d:3d:07:66:11:75:
+ 6d:85:71:5e:32:f2:d7:0c:db:30:21:15:e1:74:b7:b5:eb:6b:
+ f9:73:ea:0a:49:ad:48:f6:23:23:8c:60:47:2c:51:96:b1:cc:
+ 23:77:cd:96:c5:c6:cd:b5:4c:2c:95:f7:22:45:f8:b6:ad:84:
+ 0c:08:ca:13:b0:a8:9d:35:6f:8b:48:d8:5f:b6:2b:a7:a8:27:
+ 44:c3:0c:8e:a6:0d:e3:64:26:61:92:97:13:5e:80:31:0c:b7:
+ 9e:90:20:87:0b:d0:aa:0a:06:04:27:3c:86:6a:20:0d:9d:bb:
+ ce:7d:57:c9:59:93:a2:03:3b:8c:b3:6f:42:fd:a4:d5:9b:ca:
+ 01:aa:04:0c
+-----BEGIN CERTIFICATE-----
+MIIFDTCCA/WgAwIBAgIETA7JGDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTExMTExMTE0NTcyMloXDTIxMTExMjA4
+MTIzMVowgbExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvcnBhIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA5IEVudHJ1c3QsIEluYy4xLjAsBgNV
+BAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBMMUUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2WwRUd90OJGbcKqHbgMxdx1/9UhZY
+2l+UBqm4trljDEcgguzHlU6LuHdSaj21h6nW4cx05abIwNRWT40u1gg+DExDPvBB
+k15G7znn2WUqDHZQJ71bDTMzB+D3oqmc4REzrWb80ix6qqNzFr6ThXUP1zeM+iO3
+ZPjjTG7tswW94jbbfN52RNqCcna2bv+UodCG9xDNSlqLsHWMZlKATkhMSYOmQNd3
+gRNNXnJ+SEYiqg/iPmWUOOFycf5KcQm6NX9ViT2B1bgoARB3NloQhdK9YIQrSWGU
+DN5MQGoqxHlghCSCMmlKmEviVhC6A0VRINPP2o5UG0W2erqXmlrYxtFfAgMBAAGj
+ggEqMIIBJjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAzBggr
+BgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0
+MDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvcm9vdGNh
+MS5jcmwwOwYDVR0gBDQwMjAwBgRVHSAAMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly93
+d3cuZW50cnVzdC5uZXQvQ1BTMB0GA1UdDgQWBBRbQYqyxEPBvb/IVEFVneCWrf+5
+oTAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAZBgkqhkiG9n0HQQAE
+DDAKGwRWOC4xAwIAgTANBgkqhkiG9w0BAQUFAAOCAQEAofGoEOjmKbkibGFbKj88
+AceCIQvoTg/Eyca8mZ3271vHabLZnqxSQumKuDHEE5YDj2WTBmn+KLam/a2HjNXM
+puf5GjfvMi0FLR5OudXV0Q+bfyROuJDs5mm/nyo8YwLhaaNuoDRyyFBQttqOki64
+Syj+9JLwBLbWnT0HZhF1bYVxXjLy1wzbMCEV4XS3tetr+XPqCkmtSPYjI4xgRyxR
+lrHMI3fNlsXGzbVMLJX3IkX4tq2EDAjKE7ConTVvi0jYX7Yrp6gnRMMMjqYN42Qm
+YZKXE16AMQy3npAghwvQqgoGBCc8hmogDZ27zn1XyVmTogM7jLNvQv2k1ZvKAaoE
+DA==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert80[] = {
+ 0x30, 0x82, 0x05, 0x0d, 0x30, 0x82, 0x03, 0xf5, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x4c, 0x0e, 0xc9, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xb0, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0d, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x30, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74,
+ 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x20, 0x69, 0x73, 0x20,
+ 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64,
+ 0x20, 0x62, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
+ 0x65, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16,
+ 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2d,
+ 0x30, 0x2b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x24, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x34, 0x35, 0x37, 0x32,
+ 0x32, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31, 0x32, 0x30, 0x38,
+ 0x31, 0x32, 0x33, 0x31, 0x5a, 0x30, 0x81, 0xb1, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30,
+ 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x45, 0x6e, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x39, 0x30,
+ 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30, 0x77, 0x77, 0x77, 0x2e,
+ 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x72, 0x70, 0x61, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72,
+ 0x70, 0x6f, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x72,
+ 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x31, 0x1f, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x28, 0x63, 0x29, 0x20, 0x32,
+ 0x30, 0x30, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2c,
+ 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x25, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+ 0x2d, 0x20, 0x4c, 0x31, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xb6, 0x5b, 0x04, 0x54, 0x77, 0xdd, 0x0e, 0x24, 0x66, 0xdc,
+ 0x2a, 0xa1, 0xdb, 0x80, 0xcc, 0x5d, 0xc7, 0x5f, 0xfd, 0x52, 0x16, 0x58,
+ 0xda, 0x5f, 0x94, 0x06, 0xa9, 0xb8, 0xb6, 0xb9, 0x63, 0x0c, 0x47, 0x20,
+ 0x82, 0xec, 0xc7, 0x95, 0x4e, 0x8b, 0xb8, 0x77, 0x52, 0x6a, 0x3d, 0xb5,
+ 0x87, 0xa9, 0xd6, 0xe1, 0xcc, 0x74, 0xe5, 0xa6, 0xc8, 0xc0, 0xd4, 0x56,
+ 0x4f, 0x8d, 0x2e, 0xd6, 0x08, 0x3e, 0x0c, 0x4c, 0x43, 0x3e, 0xf0, 0x41,
+ 0x93, 0x5e, 0x46, 0xef, 0x39, 0xe7, 0xd9, 0x65, 0x2a, 0x0c, 0x76, 0x50,
+ 0x27, 0xbd, 0x5b, 0x0d, 0x33, 0x33, 0x07, 0xe0, 0xf7, 0xa2, 0xa9, 0x9c,
+ 0xe1, 0x11, 0x33, 0xad, 0x66, 0xfc, 0xd2, 0x2c, 0x7a, 0xaa, 0xa3, 0x73,
+ 0x16, 0xbe, 0x93, 0x85, 0x75, 0x0f, 0xd7, 0x37, 0x8c, 0xfa, 0x23, 0xb7,
+ 0x64, 0xf8, 0xe3, 0x4c, 0x6e, 0xed, 0xb3, 0x05, 0xbd, 0xe2, 0x36, 0xdb,
+ 0x7c, 0xde, 0x76, 0x44, 0xda, 0x82, 0x72, 0x76, 0xb6, 0x6e, 0xff, 0x94,
+ 0xa1, 0xd0, 0x86, 0xf7, 0x10, 0xcd, 0x4a, 0x5a, 0x8b, 0xb0, 0x75, 0x8c,
+ 0x66, 0x52, 0x80, 0x4e, 0x48, 0x4c, 0x49, 0x83, 0xa6, 0x40, 0xd7, 0x77,
+ 0x81, 0x13, 0x4d, 0x5e, 0x72, 0x7e, 0x48, 0x46, 0x22, 0xaa, 0x0f, 0xe2,
+ 0x3e, 0x65, 0x94, 0x38, 0xe1, 0x72, 0x71, 0xfe, 0x4a, 0x71, 0x09, 0xba,
+ 0x35, 0x7f, 0x55, 0x89, 0x3d, 0x81, 0xd5, 0xb8, 0x28, 0x01, 0x10, 0x77,
+ 0x36, 0x5a, 0x10, 0x85, 0xd2, 0xbd, 0x60, 0x84, 0x2b, 0x49, 0x61, 0x94,
+ 0x0c, 0xde, 0x4c, 0x40, 0x6a, 0x2a, 0xc4, 0x79, 0x60, 0x84, 0x24, 0x82,
+ 0x32, 0x69, 0x4a, 0x98, 0x4b, 0xe2, 0x56, 0x10, 0xba, 0x03, 0x45, 0x51,
+ 0x20, 0xd3, 0xcf, 0xda, 0x8e, 0x54, 0x1b, 0x45, 0xb6, 0x7a, 0xba, 0x97,
+ 0x9a, 0x5a, 0xd8, 0xc6, 0xd1, 0x5f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x2a, 0x30, 0x82, 0x01, 0x26, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30,
+ 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x33, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x27, 0x30, 0x25, 0x30,
+ 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86,
+ 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70,
+ 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74,
+ 0x30, 0x33, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2c, 0x30, 0x2a, 0x30,
+ 0x28, 0xa0, 0x26, 0xa0, 0x24, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x63, 0x61,
+ 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00,
+ 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0x5b, 0x41, 0x8a, 0xb2, 0xc4, 0x43, 0xc1,
+ 0xbd, 0xbf, 0xc8, 0x54, 0x41, 0x55, 0x9d, 0xe0, 0x96, 0xad, 0xff, 0xb9,
+ 0xa1, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0x68, 0x90, 0xe4, 0x67, 0xa4, 0xa6, 0x53, 0x80, 0xc7, 0x86,
+ 0x66, 0xa4, 0xf1, 0xf7, 0x4b, 0x43, 0xfb, 0x84, 0xbd, 0x6d, 0x30, 0x19,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf6, 0x7d, 0x07, 0x41, 0x00, 0x04,
+ 0x0c, 0x30, 0x0a, 0x1b, 0x04, 0x56, 0x38, 0x2e, 0x31, 0x03, 0x02, 0x00,
+ 0x81, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xa1, 0xf1, 0xa8,
+ 0x10, 0xe8, 0xe6, 0x29, 0xb9, 0x22, 0x6c, 0x61, 0x5b, 0x2a, 0x3f, 0x3c,
+ 0x01, 0xc7, 0x82, 0x21, 0x0b, 0xe8, 0x4e, 0x0f, 0xc4, 0xc9, 0xc6, 0xbc,
+ 0x99, 0x9d, 0xf6, 0xef, 0x5b, 0xc7, 0x69, 0xb2, 0xd9, 0x9e, 0xac, 0x52,
+ 0x42, 0xe9, 0x8a, 0xb8, 0x31, 0xc4, 0x13, 0x96, 0x03, 0x8f, 0x65, 0x93,
+ 0x06, 0x69, 0xfe, 0x28, 0xb6, 0xa6, 0xfd, 0xad, 0x87, 0x8c, 0xd5, 0xcc,
+ 0xa6, 0xe7, 0xf9, 0x1a, 0x37, 0xef, 0x32, 0x2d, 0x05, 0x2d, 0x1e, 0x4e,
+ 0xb9, 0xd5, 0xd5, 0xd1, 0x0f, 0x9b, 0x7f, 0x24, 0x4e, 0xb8, 0x90, 0xec,
+ 0xe6, 0x69, 0xbf, 0x9f, 0x2a, 0x3c, 0x63, 0x02, 0xe1, 0x69, 0xa3, 0x6e,
+ 0xa0, 0x34, 0x72, 0xc8, 0x50, 0x50, 0xb6, 0xda, 0x8e, 0x92, 0x2e, 0xb8,
+ 0x4b, 0x28, 0xfe, 0xf4, 0x92, 0xf0, 0x04, 0xb6, 0xd6, 0x9d, 0x3d, 0x07,
+ 0x66, 0x11, 0x75, 0x6d, 0x85, 0x71, 0x5e, 0x32, 0xf2, 0xd7, 0x0c, 0xdb,
+ 0x30, 0x21, 0x15, 0xe1, 0x74, 0xb7, 0xb5, 0xeb, 0x6b, 0xf9, 0x73, 0xea,
+ 0x0a, 0x49, 0xad, 0x48, 0xf6, 0x23, 0x23, 0x8c, 0x60, 0x47, 0x2c, 0x51,
+ 0x96, 0xb1, 0xcc, 0x23, 0x77, 0xcd, 0x96, 0xc5, 0xc6, 0xcd, 0xb5, 0x4c,
+ 0x2c, 0x95, 0xf7, 0x22, 0x45, 0xf8, 0xb6, 0xad, 0x84, 0x0c, 0x08, 0xca,
+ 0x13, 0xb0, 0xa8, 0x9d, 0x35, 0x6f, 0x8b, 0x48, 0xd8, 0x5f, 0xb6, 0x2b,
+ 0xa7, 0xa8, 0x27, 0x44, 0xc3, 0x0c, 0x8e, 0xa6, 0x0d, 0xe3, 0x64, 0x26,
+ 0x61, 0x92, 0x97, 0x13, 0x5e, 0x80, 0x31, 0x0c, 0xb7, 0x9e, 0x90, 0x20,
+ 0x87, 0x0b, 0xd0, 0xaa, 0x0a, 0x06, 0x04, 0x27, 0x3c, 0x86, 0x6a, 0x20,
+ 0x0d, 0x9d, 0xbb, 0xce, 0x7d, 0x57, 0xc9, 0x59, 0x93, 0xa2, 0x03, 0x3b,
+ 0x8c, 0xb3, 0x6f, 0x42, 0xfd, 0xa4, 0xd5, 0x9b, 0xca, 0x01, 0xaa, 0x04,
+ 0x0c,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 268 (0x10c)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com
+ Validity
+ Not Before: Jun 29 17:39:16 2004 GMT
+ Not After : Jun 29 17:39:16 2024 GMT
+ Subject: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b7:32:c8:fe:e9:71:a6:04:85:ad:0c:11:64:df:
+ ce:4d:ef:c8:03:18:87:3f:a1:ab:fb:3c:a6:9f:f0:
+ c3:a1:da:d4:d8:6e:2b:53:90:fb:24:a4:3e:84:f0:
+ 9e:e8:5f:ec:e5:27:44:f5:28:a6:3f:7b:de:e0:2a:
+ f0:c8:af:53:2f:9e:ca:05:01:93:1e:8f:66:1c:39:
+ a7:4d:fa:5a:b6:73:04:25:66:eb:77:7f:e7:59:c6:
+ 4a:99:25:14:54:eb:26:c7:f3:7f:19:d5:30:70:8f:
+ af:b0:46:2a:ff:ad:eb:29:ed:d7:9f:aa:04:87:a3:
+ d4:f9:89:a5:34:5f:db:43:91:82:36:d9:66:3c:b1:
+ b8:b9:82:fd:9c:3a:3e:10:c8:3b:ef:06:65:66:7a:
+ 9b:19:18:3d:ff:71:51:3c:30:2e:5f:be:3d:77:73:
+ b2:5d:06:6c:c3:23:56:9a:2b:85:26:92:1c:a7:02:
+ b3:e4:3f:0d:af:08:79:82:b8:36:3d:ea:9c:d3:35:
+ b3:bc:69:ca:f5:cc:9d:e8:fd:64:8d:17:80:33:6e:
+ 5e:4a:5d:99:c9:1e:87:b4:9d:1a:c0:d5:6e:13:35:
+ 23:5e:df:9b:5f:3d:ef:d6:f7:76:c2:ea:3e:bb:78:
+ 0d:1c:42:67:6b:04:d8:f8:d6:da:6f:8b:f2:44:a0:
+ 01:ab
+ Exponent: 3 (0x3)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7
+ X509v3 Authority Key Identifier:
+ DirName:/L=ValiCert Validation Network/O=ValiCert, Inc./OU=ValiCert Class 2 Policy Validation Authority/CN=http://www.valicert.com//emailAddress=info@valicert.com
+ serial:01
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.starfieldtech.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://certificates.starfieldtech.com/repository/root.crl
+
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: http://certificates.starfieldtech.com/repository
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Signature Algorithm: sha1WithRSAEncryption
+ a5:62:f1:a7:c2:5d:25:a5:70:3d:bc:e2:2a:71:b3:7d:e4:0d:
+ 37:1d:55:6a:6d:a1:b0:ab:98:00:e4:85:60:4a:20:cb:a1:f0:
+ 3d:75:f7:94:da:43:7f:68:5c:25:08:b8:d2:7d:33:10:7b:dc:
+ 76:67:f1:b8:e1:53:7c:e1:c6:83:5b:29:7b:8d:4a:6f:2e:7f:
+ 44:a8:1a:45:6b:32:07:c7:78:ca:64:92:c2:b4:84:0c:dc:dd:
+ 2d:5f:4d:bb:dd:8a:ea:38:dc:d9:66:a2:ec:41:ba:55:6d:5a:
+ 64:3d:b7:03:cc:1c:e2:91:50:9e:e3:09:44:95:17:17:73:3d:
+ cc:25
+-----BEGIN CERTIFICATE-----
+MIIFEjCCBHugAwIBAgICAQwwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1Zh
+bGlDZXJ0IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIElu
+Yy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24g
+QXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAe
+BgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTA0MDYyOTE3MzkxNloX
+DTI0MDYyOTE3MzkxNlowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVs
+ZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAy
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0A
+MIIBCAKCAQEAtzLI/ulxpgSFrQwRZN/OTe/IAxiHP6Gr+zymn/DDodrU2G4rU5D7
+JKQ+hPCe6F/s5SdE9SimP3ve4CrwyK9TL57KBQGTHo9mHDmnTfpatnMEJWbrd3/n
+WcZKmSUUVOsmx/N/GdUwcI+vsEYq/63rKe3Xn6oEh6PU+YmlNF/bQ5GCNtlmPLG4
+uYL9nDo+EMg77wZlZnqbGRg9/3FRPDAuX749d3OyXQZswyNWmiuFJpIcpwKz5D8N
+rwh5grg2Peqc0zWzvGnK9cyd6P1kjReAM25eSl2ZyR6HtJ0awNVuEzUjXt+bXz3v
+1vd2wuo+u3gNHEJnawTY+Nbab4vyRKABqwIBA6OCAfMwggHvMB0GA1UdDgQWBBS/
+X7fRzt0fhvRbVazc1xDCDqmI5zCB0gYDVR0jBIHKMIHHoYHBpIG+MIG7MSQwIgYD
+VQQHExtWYWxpQ2VydCBWYWxpZGF0aW9uIE5ldHdvcmsxFzAVBgNVBAoTDlZhbGlD
+ZXJ0LCBJbmMuMTUwMwYDVQQLEyxWYWxpQ2VydCBDbGFzcyAyIFBvbGljeSBWYWxp
+ZGF0aW9uIEF1dGhvcml0eTEhMB8GA1UEAxMYaHR0cDovL3d3dy52YWxpY2VydC5j
+b20vMSAwHgYJKoZIhvcNAQkBFhFpbmZvQHZhbGljZXJ0LmNvbYIBATAPBgNVHRMB
+Af8EBTADAQH/MDkGCCsGAQUFBwEBBC0wKzApBggrBgEFBQcwAYYdaHR0cDovL29j
+c3Auc3RhcmZpZWxkdGVjaC5jb20wSgYDVR0fBEMwQTA/oD2gO4Y5aHR0cDovL2Nl
+cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5L3Jvb3QuY3Js
+MFEGA1UdIARKMEgwRgYEVR0gADA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY2VydGlm
+aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkwDgYDVR0PAQH/BAQD
+AgEGMA0GCSqGSIb3DQEBBQUAA4GBAKVi8afCXSWlcD284ipxs33kDTcdVWptobCr
+mADkhWBKIMuh8D1195TaQ39oXCUIuNJ9MxB73HZn8bjhU3zhxoNbKXuNSm8uf0So
+GkVrMgfHeMpkksK0hAzc3S1fTbvdiuo43NlmouxBulVtWmQ9twPMHOKRUJ7jCUSV
+FxdzPcwl
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert81[] = {
+ 0x30, 0x82, 0x05, 0x12, 0x30, 0x82, 0x04, 0x7b, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x02, 0x01, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xbb, 0x31,
+ 0x24, 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61,
+ 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x35, 0x30, 0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2c, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c,
+ 0x61, 0x73, 0x73, 0x20, 0x32, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+ 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63,
+ 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16,
+ 0x11, 0x69, 0x6e, 0x66, 0x6f, 0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65,
+ 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x34,
+ 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x33, 0x39, 0x31, 0x36, 0x5a, 0x17,
+ 0x0d, 0x32, 0x34, 0x30, 0x36, 0x32, 0x39, 0x31, 0x37, 0x33, 0x39, 0x31,
+ 0x36, 0x5a, 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55,
+ 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c,
+ 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, 0x6f, 0x67, 0x69,
+ 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x32, 0x30, 0x30,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x29, 0x53, 0x74, 0x61, 0x72, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32,
+ 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+ 0x30, 0x82, 0x01, 0x20, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0d, 0x00,
+ 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0x32, 0xc8,
+ 0xfe, 0xe9, 0x71, 0xa6, 0x04, 0x85, 0xad, 0x0c, 0x11, 0x64, 0xdf, 0xce,
+ 0x4d, 0xef, 0xc8, 0x03, 0x18, 0x87, 0x3f, 0xa1, 0xab, 0xfb, 0x3c, 0xa6,
+ 0x9f, 0xf0, 0xc3, 0xa1, 0xda, 0xd4, 0xd8, 0x6e, 0x2b, 0x53, 0x90, 0xfb,
+ 0x24, 0xa4, 0x3e, 0x84, 0xf0, 0x9e, 0xe8, 0x5f, 0xec, 0xe5, 0x27, 0x44,
+ 0xf5, 0x28, 0xa6, 0x3f, 0x7b, 0xde, 0xe0, 0x2a, 0xf0, 0xc8, 0xaf, 0x53,
+ 0x2f, 0x9e, 0xca, 0x05, 0x01, 0x93, 0x1e, 0x8f, 0x66, 0x1c, 0x39, 0xa7,
+ 0x4d, 0xfa, 0x5a, 0xb6, 0x73, 0x04, 0x25, 0x66, 0xeb, 0x77, 0x7f, 0xe7,
+ 0x59, 0xc6, 0x4a, 0x99, 0x25, 0x14, 0x54, 0xeb, 0x26, 0xc7, 0xf3, 0x7f,
+ 0x19, 0xd5, 0x30, 0x70, 0x8f, 0xaf, 0xb0, 0x46, 0x2a, 0xff, 0xad, 0xeb,
+ 0x29, 0xed, 0xd7, 0x9f, 0xaa, 0x04, 0x87, 0xa3, 0xd4, 0xf9, 0x89, 0xa5,
+ 0x34, 0x5f, 0xdb, 0x43, 0x91, 0x82, 0x36, 0xd9, 0x66, 0x3c, 0xb1, 0xb8,
+ 0xb9, 0x82, 0xfd, 0x9c, 0x3a, 0x3e, 0x10, 0xc8, 0x3b, 0xef, 0x06, 0x65,
+ 0x66, 0x7a, 0x9b, 0x19, 0x18, 0x3d, 0xff, 0x71, 0x51, 0x3c, 0x30, 0x2e,
+ 0x5f, 0xbe, 0x3d, 0x77, 0x73, 0xb2, 0x5d, 0x06, 0x6c, 0xc3, 0x23, 0x56,
+ 0x9a, 0x2b, 0x85, 0x26, 0x92, 0x1c, 0xa7, 0x02, 0xb3, 0xe4, 0x3f, 0x0d,
+ 0xaf, 0x08, 0x79, 0x82, 0xb8, 0x36, 0x3d, 0xea, 0x9c, 0xd3, 0x35, 0xb3,
+ 0xbc, 0x69, 0xca, 0xf5, 0xcc, 0x9d, 0xe8, 0xfd, 0x64, 0x8d, 0x17, 0x80,
+ 0x33, 0x6e, 0x5e, 0x4a, 0x5d, 0x99, 0xc9, 0x1e, 0x87, 0xb4, 0x9d, 0x1a,
+ 0xc0, 0xd5, 0x6e, 0x13, 0x35, 0x23, 0x5e, 0xdf, 0x9b, 0x5f, 0x3d, 0xef,
+ 0xd6, 0xf7, 0x76, 0xc2, 0xea, 0x3e, 0xbb, 0x78, 0x0d, 0x1c, 0x42, 0x67,
+ 0x6b, 0x04, 0xd8, 0xf8, 0xd6, 0xda, 0x6f, 0x8b, 0xf2, 0x44, 0xa0, 0x01,
+ 0xab, 0x02, 0x01, 0x03, 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xbf,
+ 0x5f, 0xb7, 0xd1, 0xce, 0xdd, 0x1f, 0x86, 0xf4, 0x5b, 0x55, 0xac, 0xdc,
+ 0xd7, 0x10, 0xc2, 0x0e, 0xa9, 0x88, 0xe7, 0x30, 0x81, 0xd2, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x81, 0xca, 0x30, 0x81, 0xc7, 0xa1, 0x81, 0xc1,
+ 0xa4, 0x81, 0xbe, 0x30, 0x81, 0xbb, 0x31, 0x24, 0x30, 0x22, 0x06, 0x03,
+ 0x55, 0x04, 0x07, 0x13, 0x1b, 0x56, 0x61, 0x6c, 0x69, 0x43, 0x65, 0x72,
+ 0x74, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x17, 0x30, 0x15,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x43,
+ 0x65, 0x72, 0x74, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x35, 0x30,
+ 0x33, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2c, 0x56, 0x61, 0x6c, 0x69,
+ 0x43, 0x65, 0x72, 0x74, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x32,
+ 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x56, 0x61, 0x6c, 0x69,
+ 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x79, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+ 0x77, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x69, 0x6e, 0x66, 0x6f,
+ 0x40, 0x76, 0x61, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x82, 0x01, 0x01, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x39, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x2d, 0x30,
+ 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x01, 0x86, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+ 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64,
+ 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4a, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x43, 0x30, 0x41, 0x30, 0x3f, 0xa0, 0x3d, 0xa0,
+ 0x3b, 0x86, 0x39, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65,
+ 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,
+ 0x6f, 0x72, 0x79, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x51, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4a, 0x30, 0x48, 0x30,
+ 0x46, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x3e, 0x30, 0x3c, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x30, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x66,
+ 0x69, 0x65, 0x6c, 0x64, 0x74, 0x65, 0x63, 0x68, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xa5, 0x62,
+ 0xf1, 0xa7, 0xc2, 0x5d, 0x25, 0xa5, 0x70, 0x3d, 0xbc, 0xe2, 0x2a, 0x71,
+ 0xb3, 0x7d, 0xe4, 0x0d, 0x37, 0x1d, 0x55, 0x6a, 0x6d, 0xa1, 0xb0, 0xab,
+ 0x98, 0x00, 0xe4, 0x85, 0x60, 0x4a, 0x20, 0xcb, 0xa1, 0xf0, 0x3d, 0x75,
+ 0xf7, 0x94, 0xda, 0x43, 0x7f, 0x68, 0x5c, 0x25, 0x08, 0xb8, 0xd2, 0x7d,
+ 0x33, 0x10, 0x7b, 0xdc, 0x76, 0x67, 0xf1, 0xb8, 0xe1, 0x53, 0x7c, 0xe1,
+ 0xc6, 0x83, 0x5b, 0x29, 0x7b, 0x8d, 0x4a, 0x6f, 0x2e, 0x7f, 0x44, 0xa8,
+ 0x1a, 0x45, 0x6b, 0x32, 0x07, 0xc7, 0x78, 0xca, 0x64, 0x92, 0xc2, 0xb4,
+ 0x84, 0x0c, 0xdc, 0xdd, 0x2d, 0x5f, 0x4d, 0xbb, 0xdd, 0x8a, 0xea, 0x38,
+ 0xdc, 0xd9, 0x66, 0xa2, 0xec, 0x41, 0xba, 0x55, 0x6d, 0x5a, 0x64, 0x3d,
+ 0xb7, 0x03, 0xcc, 0x1c, 0xe2, 0x91, 0x50, 0x9e, 0xe3, 0x09, 0x44, 0x95,
+ 0x17, 0x17, 0x73, 0x3d, 0xcc, 0x25,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 120021506 (0x7276202)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root
+ Validity
+ Not Before: Apr 14 18:12:26 2010 GMT
+ Not After : Apr 14 18:12:14 2018 GMT
+ Subject: CN=Microsoft Internet Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (4096 bit)
+ Modulus:
+ 00:bd:f4:cd:27:a5:4a:d8:90:19:db:b2:0a:7b:60:
+ a4:4e:8f:0e:98:a3:9c:7c:50:76:eb:b3:4a:8c:9f:
+ 18:b0:e7:9a:c5:2b:82:86:09:28:ac:11:12:32:26:
+ f5:19:ea:f0:b8:67:68:c5:06:fd:f4:19:ae:d9:13:
+ a0:d0:81:27:08:a6:79:9c:04:f5:48:32:2e:36:1f:
+ ab:6b:26:ec:a1:43:b4:9d:80:b0:49:03:ea:82:49:
+ 5f:05:13:c5:a0:83:5f:e1:2a:f4:04:19:4b:7e:c8:
+ da:88:bc:b5:5d:03:bc:78:56:a1:e9:7f:c5:6a:ef:
+ b6:ff:1d:01:59:b7:1f:53:5a:5f:c6:f8:91:6d:c5:
+ 7d:43:93:18:74:45:ed:15:ba:b2:7c:c8:3a:34:14:
+ 1e:aa:63:f7:e5:d4:4b:c8:23:2b:87:69:95:13:99:
+ 09:14:ef:7a:01:20:4e:b7:c6:48:41:ae:c9:87:01:
+ 29:d9:c2:87:38:7f:b6:42:a4:f0:b2:ce:2d:fd:b4:
+ 4c:57:f0:a8:d6:cb:4e:fa:5f:5d:fd:b9:fb:09:dc:
+ 16:85:64:e5:71:9c:d5:f1:33:97:38:67:2e:9b:bc:
+ 17:36:05:7e:10:36:7f:7e:eb:98:5a:5b:1c:ad:a5:
+ e7:09:10:7d:f9:4a:2f:b3:8f:37:15:d6:6f:b9:5b:
+ 37:dc:b7:9f:7f:8e:66:7f:23:5c:ed:12:7f:8c:07:
+ f0:fe:19:f9:b8:34:43:7b:b2:ea:85:fb:8c:a9:aa:
+ df:fd:91:0d:2c:f5:fb:af:97:89:f1:06:8a:af:49:
+ f6:3c:2e:23:f6:44:16:25:91:11:e2:23:c3:ca:85:
+ 55:49:2a:c8:21:af:7d:11:26:86:b0:28:45:ba:87:
+ ee:36:13:81:d5:4b:47:1a:8e:db:09:f1:d1:97:29:
+ 50:14:32:99:09:e3:f2:c0:e7:53:8f:6b:f4:fa:13:
+ 5c:3c:8d:ee:54:99:0f:27:47:4e:3c:12:f3:8f:12:
+ 17:46:f0:89:6a:45:b3:b5:3c:0c:77:45:04:2f:bd:
+ be:b5:9e:98:3c:05:3b:bb:41:39:84:20:bc:79:04:
+ d6:42:cd:3e:89:e9:e7:7a:37:49:10:b4:cc:9f:24:
+ 5c:23:a6:48:6e:fb:e3:d4:ee:21:29:93:e4:fd:80:
+ 1a:1b:3a:6c:c1:f7:eb:d9:d4:4d:be:f1:11:f6:a2:
+ 8e:42:24:a1:4f:69:b5:d2:68:14:89:d9:9f:90:d8:
+ 1f:9e:1b:e6:6d:64:25:29:b6:34:43:a4:5b:f5:0d:
+ eb:74:06:7e:9f:f1:63:dc:45:a7:7c:3a:9a:5c:6b:
+ 73:d8:c3:58:04:8e:88:6f:13:d0:e6:d0:df:cd:c4:
+ 0a:0e:07
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.6334.1.0
+ CPS: http://cybertrust.omniroot.com/repository.cfm
+ Policy: X509v3 Any Policy
+
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=GTE Corporation/OU=GTE CyberTrust Solutions, Inc./CN=GTE CyberTrust Global Root
+ serial:01:A5
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.public-trust.com/cgi-bin/CRL/2018/cdp.crl
+
+ X509v3 Subject Key Identifier:
+ 33:21:F0:CB:FE:A2:A0:44:92:DE:F6:3B:33:D8:5F:01:4B:97:78:5D
+ Signature Algorithm: sha1WithRSAEncryption
+ 2b:48:f3:94:fb:44:c5:93:6a:d6:4d:fe:b4:13:4e:12:26:17:
+ ca:b2:5a:ab:09:b9:56:a4:6f:7f:57:9e:64:b2:f5:e4:d3:35:
+ ef:63:65:cb:e5:2c:15:9c:ef:ce:f8:2a:c5:92:64:2b:49:3e:
+ 3c:36:6c:bd:18:9b:64:67:97:3f:ed:68:d0:16:c1:13:3c:f2:
+ 51:a0:57:de:24:ce:35:ab:69:90:4e:2b:0c:3a:f9:b4:f1:80:
+ fa:6d:00:79:a6:3a:96:99:4e:3a:6e:54:d0:a3:59:6e:8b:1d:
+ 95:49:bb:95:d8:75:b8:e1:12:33:ac:5c:27:bb:cb:55:71:d5:
+ fa:ed
+-----BEGIN CERTIFICATE-----
+MIIFEjCCBHugAwIBAgIEBydiAjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJV
+UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
+cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
+b2JhbCBSb290MB4XDTEwMDQxNDE4MTIyNloXDTE4MDQxNDE4MTIxNFowJzElMCMG
+A1UEAxMcTWljcm9zb2Z0IEludGVybmV0IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAL30zSelStiQGduyCntgpE6PDpijnHxQduuzSoyf
+GLDnmsUrgoYJKKwREjIm9Rnq8LhnaMUG/fQZrtkToNCBJwimeZwE9UgyLjYfq2sm
+7KFDtJ2AsEkD6oJJXwUTxaCDX+Eq9AQZS37I2oi8tV0DvHhWoel/xWrvtv8dAVm3
+H1NaX8b4kW3FfUOTGHRF7RW6snzIOjQUHqpj9+XUS8gjK4dplROZCRTvegEgTrfG
+SEGuyYcBKdnChzh/tkKk8LLOLf20TFfwqNbLTvpfXf25+wncFoVk5XGc1fEzlzhn
+Lpu8FzYFfhA2f37rmFpbHK2l5wkQfflKL7OPNxXWb7lbN9y3n3+OZn8jXO0Sf4wH
+8P4Z+bg0Q3uy6oX7jKmq3/2RDSz1+6+XifEGiq9J9jwuI/ZEFiWREeIjw8qFVUkq
+yCGvfREmhrAoRbqH7jYTgdVLRxqO2wnx0ZcpUBQymQnj8sDnU49r9PoTXDyN7lSZ
+DydHTjwS848SF0bwiWpFs7U8DHdFBC+9vrWemDwFO7tBOYQgvHkE1kLNPonp53o3
+SRC0zJ8kXCOmSG7749TuISmT5P2AGhs6bMH369nUTb7xEfaijkIkoU9ptdJoFInZ
+n5DYH54b5m1kJSm2NEOkW/UN63QGfp/xY9xFp3w6mlxrc9jDWASOiG8T0ObQ383E
+Cg4HAgMBAAGjggF3MIIBczASBgNVHRMBAf8ECDAGAQH/AgEBMFsGA1UdIARUMFIw
+SAYJKwYBBAGxPgEAMDswOQYIKwYBBQUHAgEWLWh0dHA6Ly9jeWJlcnRydXN0Lm9t
+bmlyb290LmNvbS9yZXBvc2l0b3J5LmNmbTAGBgRVHSAAMA4GA1UdDwEB/wQEAwIB
+hjCBiQYDVR0jBIGBMH+heaR3MHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywg
+SW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3SCAgGlMEUG
+A1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly93d3cucHVibGljLXRydXN0LmNvbS9jZ2kt
+YmluL0NSTC8yMDE4L2NkcC5jcmwwHQYDVR0OBBYEFDMh8Mv+oqBEkt72OzPYXwFL
+l3hdMA0GCSqGSIb3DQEBBQUAA4GBACtI85T7RMWTatZN/rQTThImF8qyWqsJuVak
+b39XnmSy9eTTNe9jZcvlLBWc7874KsWSZCtJPjw2bL0Ym2Rnlz/taNAWwRM88lGg
+V94kzjWraZBOKww6+bTxgPptAHmmOpaZTjpuVNCjWW6LHZVJu5XYdbjhEjOsXCe7
+y1Vx1frt
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert82[] = {
+ 0x30, 0x82, 0x05, 0x12, 0x30, 0x82, 0x04, 0x7b, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x04, 0x07, 0x27, 0x62, 0x02, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x75,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f,
+ 0x47, 0x54, 0x45, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x1e, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43,
+ 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c,
+ 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x30, 0x30, 0x34, 0x31, 0x34, 0x31, 0x38, 0x31, 0x32, 0x32,
+ 0x36, 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x34, 0x31, 0x34, 0x31, 0x38,
+ 0x31, 0x32, 0x31, 0x34, 0x5a, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+ 0x6f, 0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x82,
+ 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00, 0x30, 0x82,
+ 0x02, 0x0a, 0x02, 0x82, 0x02, 0x01, 0x00, 0xbd, 0xf4, 0xcd, 0x27, 0xa5,
+ 0x4a, 0xd8, 0x90, 0x19, 0xdb, 0xb2, 0x0a, 0x7b, 0x60, 0xa4, 0x4e, 0x8f,
+ 0x0e, 0x98, 0xa3, 0x9c, 0x7c, 0x50, 0x76, 0xeb, 0xb3, 0x4a, 0x8c, 0x9f,
+ 0x18, 0xb0, 0xe7, 0x9a, 0xc5, 0x2b, 0x82, 0x86, 0x09, 0x28, 0xac, 0x11,
+ 0x12, 0x32, 0x26, 0xf5, 0x19, 0xea, 0xf0, 0xb8, 0x67, 0x68, 0xc5, 0x06,
+ 0xfd, 0xf4, 0x19, 0xae, 0xd9, 0x13, 0xa0, 0xd0, 0x81, 0x27, 0x08, 0xa6,
+ 0x79, 0x9c, 0x04, 0xf5, 0x48, 0x32, 0x2e, 0x36, 0x1f, 0xab, 0x6b, 0x26,
+ 0xec, 0xa1, 0x43, 0xb4, 0x9d, 0x80, 0xb0, 0x49, 0x03, 0xea, 0x82, 0x49,
+ 0x5f, 0x05, 0x13, 0xc5, 0xa0, 0x83, 0x5f, 0xe1, 0x2a, 0xf4, 0x04, 0x19,
+ 0x4b, 0x7e, 0xc8, 0xda, 0x88, 0xbc, 0xb5, 0x5d, 0x03, 0xbc, 0x78, 0x56,
+ 0xa1, 0xe9, 0x7f, 0xc5, 0x6a, 0xef, 0xb6, 0xff, 0x1d, 0x01, 0x59, 0xb7,
+ 0x1f, 0x53, 0x5a, 0x5f, 0xc6, 0xf8, 0x91, 0x6d, 0xc5, 0x7d, 0x43, 0x93,
+ 0x18, 0x74, 0x45, 0xed, 0x15, 0xba, 0xb2, 0x7c, 0xc8, 0x3a, 0x34, 0x14,
+ 0x1e, 0xaa, 0x63, 0xf7, 0xe5, 0xd4, 0x4b, 0xc8, 0x23, 0x2b, 0x87, 0x69,
+ 0x95, 0x13, 0x99, 0x09, 0x14, 0xef, 0x7a, 0x01, 0x20, 0x4e, 0xb7, 0xc6,
+ 0x48, 0x41, 0xae, 0xc9, 0x87, 0x01, 0x29, 0xd9, 0xc2, 0x87, 0x38, 0x7f,
+ 0xb6, 0x42, 0xa4, 0xf0, 0xb2, 0xce, 0x2d, 0xfd, 0xb4, 0x4c, 0x57, 0xf0,
+ 0xa8, 0xd6, 0xcb, 0x4e, 0xfa, 0x5f, 0x5d, 0xfd, 0xb9, 0xfb, 0x09, 0xdc,
+ 0x16, 0x85, 0x64, 0xe5, 0x71, 0x9c, 0xd5, 0xf1, 0x33, 0x97, 0x38, 0x67,
+ 0x2e, 0x9b, 0xbc, 0x17, 0x36, 0x05, 0x7e, 0x10, 0x36, 0x7f, 0x7e, 0xeb,
+ 0x98, 0x5a, 0x5b, 0x1c, 0xad, 0xa5, 0xe7, 0x09, 0x10, 0x7d, 0xf9, 0x4a,
+ 0x2f, 0xb3, 0x8f, 0x37, 0x15, 0xd6, 0x6f, 0xb9, 0x5b, 0x37, 0xdc, 0xb7,
+ 0x9f, 0x7f, 0x8e, 0x66, 0x7f, 0x23, 0x5c, 0xed, 0x12, 0x7f, 0x8c, 0x07,
+ 0xf0, 0xfe, 0x19, 0xf9, 0xb8, 0x34, 0x43, 0x7b, 0xb2, 0xea, 0x85, 0xfb,
+ 0x8c, 0xa9, 0xaa, 0xdf, 0xfd, 0x91, 0x0d, 0x2c, 0xf5, 0xfb, 0xaf, 0x97,
+ 0x89, 0xf1, 0x06, 0x8a, 0xaf, 0x49, 0xf6, 0x3c, 0x2e, 0x23, 0xf6, 0x44,
+ 0x16, 0x25, 0x91, 0x11, 0xe2, 0x23, 0xc3, 0xca, 0x85, 0x55, 0x49, 0x2a,
+ 0xc8, 0x21, 0xaf, 0x7d, 0x11, 0x26, 0x86, 0xb0, 0x28, 0x45, 0xba, 0x87,
+ 0xee, 0x36, 0x13, 0x81, 0xd5, 0x4b, 0x47, 0x1a, 0x8e, 0xdb, 0x09, 0xf1,
+ 0xd1, 0x97, 0x29, 0x50, 0x14, 0x32, 0x99, 0x09, 0xe3, 0xf2, 0xc0, 0xe7,
+ 0x53, 0x8f, 0x6b, 0xf4, 0xfa, 0x13, 0x5c, 0x3c, 0x8d, 0xee, 0x54, 0x99,
+ 0x0f, 0x27, 0x47, 0x4e, 0x3c, 0x12, 0xf3, 0x8f, 0x12, 0x17, 0x46, 0xf0,
+ 0x89, 0x6a, 0x45, 0xb3, 0xb5, 0x3c, 0x0c, 0x77, 0x45, 0x04, 0x2f, 0xbd,
+ 0xbe, 0xb5, 0x9e, 0x98, 0x3c, 0x05, 0x3b, 0xbb, 0x41, 0x39, 0x84, 0x20,
+ 0xbc, 0x79, 0x04, 0xd6, 0x42, 0xcd, 0x3e, 0x89, 0xe9, 0xe7, 0x7a, 0x37,
+ 0x49, 0x10, 0xb4, 0xcc, 0x9f, 0x24, 0x5c, 0x23, 0xa6, 0x48, 0x6e, 0xfb,
+ 0xe3, 0xd4, 0xee, 0x21, 0x29, 0x93, 0xe4, 0xfd, 0x80, 0x1a, 0x1b, 0x3a,
+ 0x6c, 0xc1, 0xf7, 0xeb, 0xd9, 0xd4, 0x4d, 0xbe, 0xf1, 0x11, 0xf6, 0xa2,
+ 0x8e, 0x42, 0x24, 0xa1, 0x4f, 0x69, 0xb5, 0xd2, 0x68, 0x14, 0x89, 0xd9,
+ 0x9f, 0x90, 0xd8, 0x1f, 0x9e, 0x1b, 0xe6, 0x6d, 0x64, 0x25, 0x29, 0xb6,
+ 0x34, 0x43, 0xa4, 0x5b, 0xf5, 0x0d, 0xeb, 0x74, 0x06, 0x7e, 0x9f, 0xf1,
+ 0x63, 0xdc, 0x45, 0xa7, 0x7c, 0x3a, 0x9a, 0x5c, 0x6b, 0x73, 0xd8, 0xc3,
+ 0x58, 0x04, 0x8e, 0x88, 0x6f, 0x13, 0xd0, 0xe6, 0xd0, 0xdf, 0xcd, 0xc4,
+ 0x0a, 0x0e, 0x07, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x77,
+ 0x30, 0x82, 0x01, 0x73, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x01,
+ 0x30, 0x5b, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x54, 0x30, 0x52, 0x30,
+ 0x48, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb1, 0x3e, 0x01, 0x00,
+ 0x30, 0x3b, 0x30, 0x39, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x79, 0x62, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6f, 0x6d,
+ 0x6e, 0x69, 0x72, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72,
+ 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x63, 0x66,
+ 0x6d, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x86, 0x30, 0x81, 0x89, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0x81,
+ 0x30, 0x7f, 0xa1, 0x79, 0xa4, 0x77, 0x30, 0x75, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x18, 0x30,
+ 0x16, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0f, 0x47, 0x54, 0x45, 0x20,
+ 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31,
+ 0x27, 0x30, 0x25, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1e, 0x47, 0x54,
+ 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74,
+ 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x1a, 0x47, 0x54, 0x45, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+ 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x82, 0x02, 0x01, 0xa5, 0x30, 0x45, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3e, 0x30, 0x3c, 0x30, 0x3a, 0xa0, 0x38,
+ 0xa0, 0x36, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x74, 0x72,
+ 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x67, 0x69, 0x2d,
+ 0x62, 0x69, 0x6e, 0x2f, 0x43, 0x52, 0x4c, 0x2f, 0x32, 0x30, 0x31, 0x38,
+ 0x2f, 0x63, 0x64, 0x70, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x33, 0x21, 0xf0, 0xcb, 0xfe,
+ 0xa2, 0xa0, 0x44, 0x92, 0xde, 0xf6, 0x3b, 0x33, 0xd8, 0x5f, 0x01, 0x4b,
+ 0x97, 0x78, 0x5d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x2b, 0x48,
+ 0xf3, 0x94, 0xfb, 0x44, 0xc5, 0x93, 0x6a, 0xd6, 0x4d, 0xfe, 0xb4, 0x13,
+ 0x4e, 0x12, 0x26, 0x17, 0xca, 0xb2, 0x5a, 0xab, 0x09, 0xb9, 0x56, 0xa4,
+ 0x6f, 0x7f, 0x57, 0x9e, 0x64, 0xb2, 0xf5, 0xe4, 0xd3, 0x35, 0xef, 0x63,
+ 0x65, 0xcb, 0xe5, 0x2c, 0x15, 0x9c, 0xef, 0xce, 0xf8, 0x2a, 0xc5, 0x92,
+ 0x64, 0x2b, 0x49, 0x3e, 0x3c, 0x36, 0x6c, 0xbd, 0x18, 0x9b, 0x64, 0x67,
+ 0x97, 0x3f, 0xed, 0x68, 0xd0, 0x16, 0xc1, 0x13, 0x3c, 0xf2, 0x51, 0xa0,
+ 0x57, 0xde, 0x24, 0xce, 0x35, 0xab, 0x69, 0x90, 0x4e, 0x2b, 0x0c, 0x3a,
+ 0xf9, 0xb4, 0xf1, 0x80, 0xfa, 0x6d, 0x00, 0x79, 0xa6, 0x3a, 0x96, 0x99,
+ 0x4e, 0x3a, 0x6e, 0x54, 0xd0, 0xa3, 0x59, 0x6e, 0x8b, 0x1d, 0x95, 0x49,
+ 0xbb, 0x95, 0xd8, 0x75, 0xb8, 0xe1, 0x12, 0x33, 0xac, 0x5c, 0x27, 0xbb,
+ 0xcb, 0x55, 0x71, 0xd5, 0xfa, 0xed,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 57:bf:fb:03:fb:2c:46:d4:e1:9e:ce:e0:d7:43:7f:13
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2021 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:af:24:08:08:29:7a:35:9e:60:0c:aa:e7:4b:3b:
+ 4e:dc:7c:bc:3c:45:1c:bb:2b:e0:fe:29:02:f9:57:
+ 08:a3:64:85:15:27:f5:f1:ad:c8:31:89:5d:22:e8:
+ 2a:aa:a6:42:b3:8f:f8:b9:55:b7:b1:b7:4b:b3:fe:
+ 8f:7e:07:57:ec:ef:43:db:66:62:15:61:cf:60:0d:
+ a4:d8:de:f8:e0:c3:62:08:3d:54:13:eb:49:ca:59:
+ 54:85:26:e5:2b:8f:1b:9f:eb:f5:a1:91:c2:33:49:
+ d8:43:63:6a:52:4b:d2:8f:e8:70:51:4d:d1:89:69:
+ 7b:c7:70:f6:b3:dc:12:74:db:7b:5d:4b:56:d3:96:
+ bf:15:77:a1:b0:f4:a2:25:f2:af:1c:92:67:18:e5:
+ f4:06:04:ef:90:b9:e4:00:e4:dd:3a:b5:19:ff:02:
+ ba:f4:3c:ee:e0:8b:eb:37:8b:ec:f4:d7:ac:f2:f6:
+ f0:3d:af:dd:75:91:33:19:1d:1c:40:cb:74:24:19:
+ 21:93:d9:14:fe:ac:2a:52:c7:8f:d5:04:49:e4:8d:
+ 63:47:88:3c:69:83:cb:fe:47:bd:2b:7e:4f:c5:95:
+ ae:0e:9d:d4:d1:43:c0:67:73:e3:14:08:7e:e5:3f:
+ 9f:73:b8:33:0a:cf:5d:3f:34:87:96:8a:ee:53:e8:
+ 25:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 Subject Key Identifier:
+ 7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
+ serial:70:BA:E4:1D:10:D9:29:34:B6:38:CA:7B:03:CC:BA:BF
+
+ Signature Algorithm: sha1WithRSAEncryption
+ a9:7b:66:29:30:f7:d5:b4:a6:96:12:d0:ee:72:f0:58:11:69:
+ 15:55:5f:41:ff:d2:12:84:13:a4:d9:03:66:ff:a9:e0:4c:c9:
+ ed:8c:72:8b:b4:d7:55:3b:29:15:60:c8:3c:21:ef:44:2e:93:
+ 3d:c6:0b:0c:8d:24:3f:1e:fb:01:5a:7a:dd:83:66:14:d1:c7:
+ fd:30:53:48:51:85:85:13:a8:54:e1:ee:76:a2:89:18:d3:97:
+ 89:7a:c6:fd:b3:bd:94:61:5a:3a:08:cf:14:93:bd:93:fd:09:
+ a9:7b:56:c8:00:b8:44:58:e9:de:5b:77:bd:07:1c:6c:0b:30:
+ 30:c7
+-----BEGIN CERTIFICATE-----
+MIIFEzCCBHygAwIBAgIQV7/7A/ssRtThns7g10N/EzANBgkqhkiG9w0BAQUFADBf
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT
+LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
+HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv
+ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8
+RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb
+ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR
+TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH
+iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB
+AAGjggHeMIIB2jAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0
+dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjBt
+BggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYwITAfMAcGBSsOAwIa
+BBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9nby52ZXJpc2lnbi5j
+b20vdnNsb2dvLmdpZjA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7Lvw
+MAnzQzn6Aq8zMTMwNAYDVR0lBC0wKwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBBggr
+BgEFBQcDAQYIKwYBBQUHAwIwgYAGA1UdIwR5MHehY6RhMF8xCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJs
+aWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eYIQcLrkHRDZKTS2OMp7
+A8y6vzANBgkqhkiG9w0BAQUFAAOBgQCpe2YpMPfVtKaWEtDucvBYEWkVVV9B/9IS
+hBOk2QNm/6ngTMntjHKLtNdVOykVYMg8Ie9ELpM9xgsMjSQ/HvsBWnrdg2YU0cf9
+MFNIUYWFE6hU4e52ookY05eJesb9s72UYVo6CM8Uk72T/Qmpe1bIALhEWOneW3e9
+BxxsCzAwxw==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert83[] = {
+ 0x30, 0x82, 0x05, 0x13, 0x30, 0x82, 0x04, 0x7c, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x57, 0xbf, 0xfb, 0x03, 0xfb, 0x2c, 0x46, 0xd4, 0xe1,
+ 0x9e, 0xce, 0xe0, 0xd7, 0x43, 0x7f, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xca, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30,
+ 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20,
+ 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+ 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f,
+ 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64,
+ 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30,
+ 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d,
+ 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xaf, 0x24, 0x08, 0x08, 0x29, 0x7a, 0x35,
+ 0x9e, 0x60, 0x0c, 0xaa, 0xe7, 0x4b, 0x3b, 0x4e, 0xdc, 0x7c, 0xbc, 0x3c,
+ 0x45, 0x1c, 0xbb, 0x2b, 0xe0, 0xfe, 0x29, 0x02, 0xf9, 0x57, 0x08, 0xa3,
+ 0x64, 0x85, 0x15, 0x27, 0xf5, 0xf1, 0xad, 0xc8, 0x31, 0x89, 0x5d, 0x22,
+ 0xe8, 0x2a, 0xaa, 0xa6, 0x42, 0xb3, 0x8f, 0xf8, 0xb9, 0x55, 0xb7, 0xb1,
+ 0xb7, 0x4b, 0xb3, 0xfe, 0x8f, 0x7e, 0x07, 0x57, 0xec, 0xef, 0x43, 0xdb,
+ 0x66, 0x62, 0x15, 0x61, 0xcf, 0x60, 0x0d, 0xa4, 0xd8, 0xde, 0xf8, 0xe0,
+ 0xc3, 0x62, 0x08, 0x3d, 0x54, 0x13, 0xeb, 0x49, 0xca, 0x59, 0x54, 0x85,
+ 0x26, 0xe5, 0x2b, 0x8f, 0x1b, 0x9f, 0xeb, 0xf5, 0xa1, 0x91, 0xc2, 0x33,
+ 0x49, 0xd8, 0x43, 0x63, 0x6a, 0x52, 0x4b, 0xd2, 0x8f, 0xe8, 0x70, 0x51,
+ 0x4d, 0xd1, 0x89, 0x69, 0x7b, 0xc7, 0x70, 0xf6, 0xb3, 0xdc, 0x12, 0x74,
+ 0xdb, 0x7b, 0x5d, 0x4b, 0x56, 0xd3, 0x96, 0xbf, 0x15, 0x77, 0xa1, 0xb0,
+ 0xf4, 0xa2, 0x25, 0xf2, 0xaf, 0x1c, 0x92, 0x67, 0x18, 0xe5, 0xf4, 0x06,
+ 0x04, 0xef, 0x90, 0xb9, 0xe4, 0x00, 0xe4, 0xdd, 0x3a, 0xb5, 0x19, 0xff,
+ 0x02, 0xba, 0xf4, 0x3c, 0xee, 0xe0, 0x8b, 0xeb, 0x37, 0x8b, 0xec, 0xf4,
+ 0xd7, 0xac, 0xf2, 0xf6, 0xf0, 0x3d, 0xaf, 0xdd, 0x75, 0x91, 0x33, 0x19,
+ 0x1d, 0x1c, 0x40, 0xcb, 0x74, 0x24, 0x19, 0x21, 0x93, 0xd9, 0x14, 0xfe,
+ 0xac, 0x2a, 0x52, 0xc7, 0x8f, 0xd5, 0x04, 0x49, 0xe4, 0x8d, 0x63, 0x47,
+ 0x88, 0x3c, 0x69, 0x83, 0xcb, 0xfe, 0x47, 0xbd, 0x2b, 0x7e, 0x4f, 0xc5,
+ 0x95, 0xae, 0x0e, 0x9d, 0xd4, 0xd1, 0x43, 0xc0, 0x67, 0x73, 0xe3, 0x14,
+ 0x08, 0x7e, 0xe5, 0x3f, 0x9f, 0x73, 0xb8, 0x33, 0x0a, 0xcf, 0x5d, 0x3f,
+ 0x34, 0x87, 0x96, 0x8a, 0xee, 0x53, 0xe8, 0x25, 0x15, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0xde, 0x30, 0x82, 0x01, 0xda, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03,
+ 0x01, 0x01, 0xff, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2a,
+ 0x30, 0x28, 0x30, 0x26, 0xa0, 0x24, 0xa0, 0x22, 0x86, 0x20, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63,
+ 0x61, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61,
+ 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55,
+ 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30,
+ 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+ 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3,
+ 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25,
+ 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69,
+ 0x66, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+ 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x63, 0x70, 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0,
+ 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30,
+ 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60,
+ 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x79, 0x30, 0x77, 0xa1, 0x63, 0xa4, 0x61, 0x30, 0x5f, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2e,
+ 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c,
+ 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x82, 0x10,
+ 0x70, 0xba, 0xe4, 0x1d, 0x10, 0xd9, 0x29, 0x34, 0xb6, 0x38, 0xca, 0x7b,
+ 0x03, 0xcc, 0xba, 0xbf, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0xa9,
+ 0x7b, 0x66, 0x29, 0x30, 0xf7, 0xd5, 0xb4, 0xa6, 0x96, 0x12, 0xd0, 0xee,
+ 0x72, 0xf0, 0x58, 0x11, 0x69, 0x15, 0x55, 0x5f, 0x41, 0xff, 0xd2, 0x12,
+ 0x84, 0x13, 0xa4, 0xd9, 0x03, 0x66, 0xff, 0xa9, 0xe0, 0x4c, 0xc9, 0xed,
+ 0x8c, 0x72, 0x8b, 0xb4, 0xd7, 0x55, 0x3b, 0x29, 0x15, 0x60, 0xc8, 0x3c,
+ 0x21, 0xef, 0x44, 0x2e, 0x93, 0x3d, 0xc6, 0x0b, 0x0c, 0x8d, 0x24, 0x3f,
+ 0x1e, 0xfb, 0x01, 0x5a, 0x7a, 0xdd, 0x83, 0x66, 0x14, 0xd1, 0xc7, 0xfd,
+ 0x30, 0x53, 0x48, 0x51, 0x85, 0x85, 0x13, 0xa8, 0x54, 0xe1, 0xee, 0x76,
+ 0xa2, 0x89, 0x18, 0xd3, 0x97, 0x89, 0x7a, 0xc6, 0xfd, 0xb3, 0xbd, 0x94,
+ 0x61, 0x5a, 0x3a, 0x08, 0xcf, 0x14, 0x93, 0xbd, 0x93, 0xfd, 0x09, 0xa9,
+ 0x7b, 0x56, 0xc8, 0x00, 0xb8, 0x44, 0x58, 0xe9, 0xde, 0x5b, 0x77, 0xbd,
+ 0x07, 0x1c, 0x6c, 0x0b, 0x30, 0x30, 0xc7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 08:0a:57:82:2c:c6:f5:e1:4f:19:b7:09:55:c8:03:42
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority
+ Validity
+ Not Before: Dec 1 00:00:00 2006 GMT
+ Not After : Dec 31 23:59:59 2019 GMT
+ Subject: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO High Assurance Secure Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b8:4c:b0:aa:8e:06:df:37:4a:a4:f2:08:ff:e2:
+ b9:92:d6:6f:eb:9e:35:4e:ec:e5:65:08:8b:13:c0:
+ bc:38:7b:11:12:1c:0b:4b:f4:37:22:15:cc:60:d1:
+ c5:1e:28:d7:2f:9a:97:d7:1c:04:8d:9b:63:7d:6e:
+ 2f:ee:f8:1f:4b:33:3e:d8:4d:86:61:0b:5a:9b:d8:
+ 96:3b:05:76:0b:2b:cb:d7:85:21:bc:19:a7:c6:68:
+ 44:83:18:b2:17:44:b5:90:9c:65:6f:71:92:50:71:
+ a0:55:72:26:92:5e:d3:69:eb:08:3f:f2:7e:a7:a0:
+ b3:eb:ab:e1:03:b9:88:7a:81:3f:a5:84:dc:92:43:
+ 4e:3b:57:70:00:1e:6b:99:50:0d:53:e8:e2:b6:18:
+ 92:1a:cd:b8:4c:5e:d1:a0:c4:a0:f1:c6:ec:fc:dd:
+ d1:7c:91:1a:14:91:32:9d:79:46:ab:f1:f0:48:60:
+ 28:55:b4:4c:e6:16:0e:bc:ef:5e:ca:d0:fe:ec:91:
+ f0:d5:11:18:5c:aa:c3:86:67:c4:11:43:08:69:55:
+ 80:b5:b0:20:48:da:78:89:09:04:57:37:f7:5d:28:
+ f3:47:fb:18:c9:be:be:78:b0:32:74:da:55:da:d6:
+ 54:86:3e:95:2b:15:1a:ed:94:5b:96:6a:f8:e3:c5:
+ 9d:ab
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:0B:58:E5:8B:C6:4C:15:37:A4:40:A9:30:A9:21:BE:47:36:5A:56:FF
+
+ X509v3 Subject Key Identifier:
+ 60:59:CD:80:C7:C5:E3:AB:8C:2F:FC:6B:E5:5B:0A:F5:0F:DE:4B:FF
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Extended Key Usage:
+ Microsoft Server Gated Crypto, Netscape Server Gated Crypto
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://secure.comodo.com/CPS
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.comodoca.com/COMODOCertificationAuthority.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.comodoca.com/ComodoUTNSGCCA.crt
+ OCSP - URI:http://ocsp.comodoca.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 32:86:37:17:d4:67:f2:ee:2d:7f:a8:03:f6:df:22:c7:ee:4c:
+ 0d:b1:4c:c1:27:b4:2c:5d:0d:30:07:8a:b3:41:51:e4:47:b0:
+ ca:a2:1d:6d:b9:a4:fb:96:23:87:03:b6:27:29:25:51:fe:48:
+ b5:98:c3:dc:34:47:e5:40:43:31:14:33:ef:bd:7f:99:43:ff:
+ 48:68:01:de:88:44:63:27:f1:22:be:c0:2f:74:d6:57:63:d6:
+ 30:c7:3f:3b:cc:bb:d2:e5:33:72:99:5a:bf:d9:33:59:b6:41:
+ 83:b4:98:3a:9c:77:00:a4:f1:c4:30:e8:1d:af:e2:d6:f8:7e:
+ 2a:66:45:58:81:21:8f:50:60:15:ef:60:62:d4:ab:3a:b9:f0:
+ fa:5c:e7:3c:3d:9d:b9:5f:75:c9:c8:73:af:5e:fe:03:6c:4c:
+ e6:ea:28:14:54:21:8e:99:4c:db:25:7c:cc:03:d5:81:26:fa:
+ 57:42:8b:79:10:03:70:f5:6a:43:82:5c:5b:5c:1f:85:09:20:
+ 42:66:71:59:d5:2f:49:b5:ab:29:63:54:e3:03:99:8d:8e:38:
+ 28:44:ff:b5:3e:c3:cd:43:73:3d:d9:3a:3b:0d:bc:f6:88:21:
+ 34:99:d9:99:e8:56:7c:27:84:ae:d3:c8:bd:b7:82:fa:74:2c:
+ e0:33:a6:8f
+-----BEGIN CERTIFICATE-----
+MIIFGzCCBAOgAwIBAgIQCApXgizG9eFPGbcJVcgDQjANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0xOTEyMzEyMzU5NTlaMIGJMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEvMC0GA1UEAxMmQ09NT0RPIEhpZ2ggQXNzdXJhbmNlIFNl
+Y3VyZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4
+TLCqjgbfN0qk8gj/4rmS1m/rnjVO7OVlCIsTwLw4exESHAtL9DciFcxg0cUeKNcv
+mpfXHASNm2N9bi/u+B9LMz7YTYZhC1qb2JY7BXYLK8vXhSG8GafGaESDGLIXRLWQ
+nGVvcZJQcaBVciaSXtNp6wg/8n6noLPrq+EDuYh6gT+lhNySQ047V3AAHmuZUA1T
+6OK2GJIazbhMXtGgxKDxxuz83dF8kRoUkTKdeUar8fBIYChVtEzmFg68717K0P7s
+kfDVERhcqsOGZ8QRQwhpVYC1sCBI2niJCQRXN/ddKPNH+xjJvr54sDJ02lXa1lSG
+PpUrFRrtlFuWavjjxZ2rAgMBAAGjggGDMIIBfzAfBgNVHSMEGDAWgBQLWOWLxkwV
+N6RAqTCpIb5HNlpW/zAdBgNVHQ4EFgQUYFnNgMfF46uML/xr5VsK9Q/eS/8wDgYD
+VR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwIAYDVR0lBBkwFwYKKwYB
+BAGCNwoDAwYJYIZIAYb4QgQBMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUF
+BwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29tL0NQUzBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDBsBggrBgEFBQcBAQRgMF4wNgYIKwYBBQUHMAKGKmh0dHA6
+Ly9jcnQuY29tb2RvY2EuY29tL0NvbW9kb1VUTlNHQ0NBLmNydDAkBggrBgEFBQcw
+AYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQAy
+hjcX1Gfy7i1/qAP23yLH7kwNsUzBJ7QsXQ0wB4qzQVHkR7DKoh1tuaT7liOHA7Yn
+KSVR/ki1mMPcNEflQEMxFDPvvX+ZQ/9IaAHeiERjJ/EivsAvdNZXY9Ywxz87zLvS
+5TNymVq/2TNZtkGDtJg6nHcApPHEMOgdr+LW+H4qZkVYgSGPUGAV72Bi1Ks6ufD6
+XOc8PZ25X3XJyHOvXv4DbEzm6igUVCGOmUzbJXzMA9WBJvpXQot5EANw9WpDglxb
+XB+FCSBCZnFZ1S9JtaspY1TjA5mNjjgoRP+1PsPNQ3M92To7Dbz2iCE0mdmZ6FZ8
+J4Su08i9t4L6dCzgM6aP
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert84[] = {
+ 0x30, 0x82, 0x05, 0x1b, 0x30, 0x82, 0x04, 0x03, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x08, 0x0a, 0x57, 0x82, 0x2c, 0xc6, 0xf5, 0xe1, 0x4f,
+ 0x19, 0xb7, 0x09, 0x55, 0xc8, 0x03, 0x42, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x47, 0x42, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x12, 0x47, 0x72, 0x65, 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e,
+ 0x63, 0x68, 0x65, 0x73, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72,
+ 0x64, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69,
+ 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x27, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x1e, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x36, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32,
+ 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0x89, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x47, 0x42, 0x31, 0x1b,
+ 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x12, 0x47, 0x72, 0x65,
+ 0x61, 0x74, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x73,
+ 0x74, 0x65, 0x72, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07,
+ 0x13, 0x07, 0x53, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x64, 0x31, 0x1a, 0x30,
+ 0x18, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x43, 0x4f, 0x4d, 0x4f,
+ 0x44, 0x4f, 0x20, 0x43, 0x41, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65,
+ 0x64, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+ 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20,
+ 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x53, 0x65,
+ 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20,
+ 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb8,
+ 0x4c, 0xb0, 0xaa, 0x8e, 0x06, 0xdf, 0x37, 0x4a, 0xa4, 0xf2, 0x08, 0xff,
+ 0xe2, 0xb9, 0x92, 0xd6, 0x6f, 0xeb, 0x9e, 0x35, 0x4e, 0xec, 0xe5, 0x65,
+ 0x08, 0x8b, 0x13, 0xc0, 0xbc, 0x38, 0x7b, 0x11, 0x12, 0x1c, 0x0b, 0x4b,
+ 0xf4, 0x37, 0x22, 0x15, 0xcc, 0x60, 0xd1, 0xc5, 0x1e, 0x28, 0xd7, 0x2f,
+ 0x9a, 0x97, 0xd7, 0x1c, 0x04, 0x8d, 0x9b, 0x63, 0x7d, 0x6e, 0x2f, 0xee,
+ 0xf8, 0x1f, 0x4b, 0x33, 0x3e, 0xd8, 0x4d, 0x86, 0x61, 0x0b, 0x5a, 0x9b,
+ 0xd8, 0x96, 0x3b, 0x05, 0x76, 0x0b, 0x2b, 0xcb, 0xd7, 0x85, 0x21, 0xbc,
+ 0x19, 0xa7, 0xc6, 0x68, 0x44, 0x83, 0x18, 0xb2, 0x17, 0x44, 0xb5, 0x90,
+ 0x9c, 0x65, 0x6f, 0x71, 0x92, 0x50, 0x71, 0xa0, 0x55, 0x72, 0x26, 0x92,
+ 0x5e, 0xd3, 0x69, 0xeb, 0x08, 0x3f, 0xf2, 0x7e, 0xa7, 0xa0, 0xb3, 0xeb,
+ 0xab, 0xe1, 0x03, 0xb9, 0x88, 0x7a, 0x81, 0x3f, 0xa5, 0x84, 0xdc, 0x92,
+ 0x43, 0x4e, 0x3b, 0x57, 0x70, 0x00, 0x1e, 0x6b, 0x99, 0x50, 0x0d, 0x53,
+ 0xe8, 0xe2, 0xb6, 0x18, 0x92, 0x1a, 0xcd, 0xb8, 0x4c, 0x5e, 0xd1, 0xa0,
+ 0xc4, 0xa0, 0xf1, 0xc6, 0xec, 0xfc, 0xdd, 0xd1, 0x7c, 0x91, 0x1a, 0x14,
+ 0x91, 0x32, 0x9d, 0x79, 0x46, 0xab, 0xf1, 0xf0, 0x48, 0x60, 0x28, 0x55,
+ 0xb4, 0x4c, 0xe6, 0x16, 0x0e, 0xbc, 0xef, 0x5e, 0xca, 0xd0, 0xfe, 0xec,
+ 0x91, 0xf0, 0xd5, 0x11, 0x18, 0x5c, 0xaa, 0xc3, 0x86, 0x67, 0xc4, 0x11,
+ 0x43, 0x08, 0x69, 0x55, 0x80, 0xb5, 0xb0, 0x20, 0x48, 0xda, 0x78, 0x89,
+ 0x09, 0x04, 0x57, 0x37, 0xf7, 0x5d, 0x28, 0xf3, 0x47, 0xfb, 0x18, 0xc9,
+ 0xbe, 0xbe, 0x78, 0xb0, 0x32, 0x74, 0xda, 0x55, 0xda, 0xd6, 0x54, 0x86,
+ 0x3e, 0x95, 0x2b, 0x15, 0x1a, 0xed, 0x94, 0x5b, 0x96, 0x6a, 0xf8, 0xe3,
+ 0xc5, 0x9d, 0xab, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x83,
+ 0x30, 0x82, 0x01, 0x7f, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x0b, 0x58, 0xe5, 0x8b, 0xc6, 0x4c, 0x15,
+ 0x37, 0xa4, 0x40, 0xa9, 0x30, 0xa9, 0x21, 0xbe, 0x47, 0x36, 0x5a, 0x56,
+ 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x60, 0x59, 0xcd, 0x80, 0xc7, 0xc5, 0xe3, 0xab, 0x8c, 0x2f, 0xfc, 0x6b,
+ 0xe5, 0x5b, 0x0a, 0xf5, 0x0f, 0xde, 0x4b, 0xff, 0x30, 0x0e, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x20, 0x06, 0x03,
+ 0x55, 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0x37, 0x0a, 0x03, 0x03, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x37, 0x30, 0x35, 0x30, 0x33, 0x06, 0x04, 0x55, 0x1d, 0x20,
+ 0x00, 0x30, 0x2b, 0x30, 0x29, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x1d, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+ 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6f,
+ 0x64, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x50, 0x53, 0x30, 0x49,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0,
+ 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x4f, 0x4d, 0x4f, 0x44, 0x4f, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x6c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+ 0x01, 0x04, 0x60, 0x30, 0x5e, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f,
+ 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x6f, 0x6d, 0x6f, 0x64,
+ 0x6f, 0x55, 0x54, 0x4e, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72,
+ 0x74, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
+ 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63,
+ 0x73, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x6f, 0x64, 0x6f, 0x63, 0x61, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x32,
+ 0x86, 0x37, 0x17, 0xd4, 0x67, 0xf2, 0xee, 0x2d, 0x7f, 0xa8, 0x03, 0xf6,
+ 0xdf, 0x22, 0xc7, 0xee, 0x4c, 0x0d, 0xb1, 0x4c, 0xc1, 0x27, 0xb4, 0x2c,
+ 0x5d, 0x0d, 0x30, 0x07, 0x8a, 0xb3, 0x41, 0x51, 0xe4, 0x47, 0xb0, 0xca,
+ 0xa2, 0x1d, 0x6d, 0xb9, 0xa4, 0xfb, 0x96, 0x23, 0x87, 0x03, 0xb6, 0x27,
+ 0x29, 0x25, 0x51, 0xfe, 0x48, 0xb5, 0x98, 0xc3, 0xdc, 0x34, 0x47, 0xe5,
+ 0x40, 0x43, 0x31, 0x14, 0x33, 0xef, 0xbd, 0x7f, 0x99, 0x43, 0xff, 0x48,
+ 0x68, 0x01, 0xde, 0x88, 0x44, 0x63, 0x27, 0xf1, 0x22, 0xbe, 0xc0, 0x2f,
+ 0x74, 0xd6, 0x57, 0x63, 0xd6, 0x30, 0xc7, 0x3f, 0x3b, 0xcc, 0xbb, 0xd2,
+ 0xe5, 0x33, 0x72, 0x99, 0x5a, 0xbf, 0xd9, 0x33, 0x59, 0xb6, 0x41, 0x83,
+ 0xb4, 0x98, 0x3a, 0x9c, 0x77, 0x00, 0xa4, 0xf1, 0xc4, 0x30, 0xe8, 0x1d,
+ 0xaf, 0xe2, 0xd6, 0xf8, 0x7e, 0x2a, 0x66, 0x45, 0x58, 0x81, 0x21, 0x8f,
+ 0x50, 0x60, 0x15, 0xef, 0x60, 0x62, 0xd4, 0xab, 0x3a, 0xb9, 0xf0, 0xfa,
+ 0x5c, 0xe7, 0x3c, 0x3d, 0x9d, 0xb9, 0x5f, 0x75, 0xc9, 0xc8, 0x73, 0xaf,
+ 0x5e, 0xfe, 0x03, 0x6c, 0x4c, 0xe6, 0xea, 0x28, 0x14, 0x54, 0x21, 0x8e,
+ 0x99, 0x4c, 0xdb, 0x25, 0x7c, 0xcc, 0x03, 0xd5, 0x81, 0x26, 0xfa, 0x57,
+ 0x42, 0x8b, 0x79, 0x10, 0x03, 0x70, 0xf5, 0x6a, 0x43, 0x82, 0x5c, 0x5b,
+ 0x5c, 0x1f, 0x85, 0x09, 0x20, 0x42, 0x66, 0x71, 0x59, 0xd5, 0x2f, 0x49,
+ 0xb5, 0xab, 0x29, 0x63, 0x54, 0xe3, 0x03, 0x99, 0x8d, 0x8e, 0x38, 0x28,
+ 0x44, 0xff, 0xb5, 0x3e, 0xc3, 0xcd, 0x43, 0x73, 0x3d, 0xd9, 0x3a, 0x3b,
+ 0x0d, 0xbc, 0xf6, 0x88, 0x21, 0x34, 0x99, 0xd9, 0x99, 0xe8, 0x56, 0x7c,
+ 0x27, 0x84, 0xae, 0xd3, 0xc8, 0xbd, 0xb7, 0x82, 0xfa, 0x74, 0x2c, 0xe0,
+ 0x33, 0xa6, 0x8f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 03:0e:95:29:4d:ae:c1:2c:03:cf:31:ab:5b:02:71:d7
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
+ Validity
+ Not Before: May 30 10:48:38 2000 GMT
+ Not After : May 30 10:48:38 2020 GMT
+ Subject: C=US, O=Network Solutions L.L.C., CN=Network Solutions Certificate Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e4:bc:7e:92:30:6d:c6:d8:8e:2b:0b:bc:46:ce:
+ e0:27:96:de:de:f9:fa:12:d3:3c:33:73:b3:04:2f:
+ bc:71:8c:e5:9f:b6:22:60:3e:5f:5d:ce:09:ff:82:
+ 0c:1b:9a:51:50:1a:26:89:dd:d5:61:5d:19:dc:12:
+ 0f:2d:0a:a2:43:5d:17:d0:34:92:20:ea:73:cf:38:
+ 2c:06:26:09:7a:72:f7:fa:50:32:f8:c2:93:d3:69:
+ a2:23:ce:41:b1:cc:e4:d5:1f:36:d1:8a:3a:f8:8c:
+ 63:e2:14:59:69:ed:0d:d3:7f:6b:e8:b8:03:e5:4f:
+ 6a:e5:98:63:69:48:05:be:2e:ff:33:b6:e9:97:59:
+ 69:f8:67:19:ae:93:61:96:44:15:d3:72:b0:3f:bc:
+ 6a:7d:ec:48:7f:8d:c3:ab:aa:71:2b:53:69:41:53:
+ 34:b5:b0:b9:c5:06:0a:c4:b0:45:f5:41:5d:6e:89:
+ 45:7b:3d:3b:26:8c:74:c2:e5:d2:d1:7d:b2:11:d4:
+ fb:58:32:22:9a:80:c9:dc:fd:0c:e9:7f:5e:03:97:
+ ce:3b:00:14:87:27:70:38:a9:8e:6e:b3:27:76:98:
+ 51:e0:05:e3:21:ab:1a:d5:85:22:3c:29:b5:9a:16:
+ c5:80:a8:f4:bb:6b:30:8f:2f:46:02:a2:b1:0c:22:
+ e0:d3
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Authority Key Identifier:
+ keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A
+
+ X509v3 Subject Key Identifier:
+ 21:30:C9:FB:00:D7:4E:98:DA:87:AA:2A:D0:A7:2E:B1:40:31:A7:4C
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.782.1.2.1.8.1
+ CPS: http://www.networksolutions.com/legal/SSL-legal-repository-ev-cps.jsp
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.usertrust.com/AddTrustExternalCARoot.crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustExternalCARoot.p7c
+ CA Issuers - URI:http://crt.usertrust.com/AddTrustUTNSGCCA.crt
+ OCSP - URI:http://ocsp.usertrust.com
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 56:45:d5:72:11:a7:cc:89:b3:32:5a:90:8c:53:73:05:36:c4:
+ 33:27:af:3e:41:c0:e9:72:2d:49:9d:52:1e:55:5a:9e:87:d0:
+ a9:4e:f0:5c:0c:9d:d2:3a:0f:8a:e7:4a:cc:5e:ba:a5:b5:9f:
+ c9:ab:3d:99:4c:46:86:7c:c0:35:8e:17:7b:5d:4a:21:2b:6e:
+ 2c:67:fd:1f:66:3e:c8:5d:4b:0c:d3:cd:ef:51:74:c1:00:b7:
+ c7:ac:b0:12:d5:77:20:f9:f0:f3:e2:f4:e7:e2:f6:07:fd:1d:
+ 94:f8:e5:3d:83:1d:37:2a:93:aa:2c:7a:99:d6:62:90:11:80:
+ 23:08:f8:62:cc:1d:27:47:d4:53:97:1c:17:52:10:70:07:22:
+ 3a:ec:9a:37:d1:19:1e:d4:20:8e:6f:9f:44:7f:3a:11:ab:6b:
+ 9b:ce:81:4f:c4:8e:ee:3c:b0:27:4a:1f:9c:79:d1:91:bf:73:
+ f0:dd:b2:00:5c:33:ee:59:61:fd:ae:27:72:f3:b1:3d:7a:ff:
+ 4c:41:92:e4:83:e2:56:a2:44:e3:03:56:f6:34:9a:36:7b:62:
+ 96:22:f3:6f:d3:4e:7f:2c:b2:b0:b2:5b:b1:2a:06:0e:36:5b:
+ 63:34:c8:3b:69:b3:ef:51:ac:9a:68:85:ed:2e:2d:44:fe:9e:
+ 09:d7:26:f8
+-----BEGIN CERTIFICATE-----
+MIIFLjCCBBagAwIBAgIQAw6VKU2uwSwDzzGrWwJx1zANBgkqhkiG9w0BAQUFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+YjELMAkGA1UEBhMCVVMxITAfBgNVBAoTGE5ldHdvcmsgU29sdXRpb25zIEwuTC5D
+LjEwMC4GA1UEAxMnTmV0d29yayBTb2x1dGlvbnMgQ2VydGlmaWNhdGUgQXV0aG9y
+aXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5Lx+kjBtxtiOKwu8
+Rs7gJ5be3vn6EtM8M3OzBC+8cYzln7YiYD5fXc4J/4IMG5pRUBomid3VYV0Z3BIP
+LQqiQ10X0DSSIOpzzzgsBiYJenL3+lAy+MKT02miI85Bsczk1R820Yo6+Ixj4hRZ
+ae0N039r6LgD5U9q5ZhjaUgFvi7/M7bpl1lp+GcZrpNhlkQV03KwP7xqfexIf43D
+q6pxK1NpQVM0tbC5xQYKxLBF9UFdbolFez07Jox0wuXS0X2yEdT7WDIimoDJ3P0M
+6X9eA5fOOwAUhydwOKmObrMndphR4AXjIasa1YUiPCm1mhbFgKj0u2swjy9GAqKx
+DCLg0wIDAQABo4IB0TCCAc0wHwYDVR0jBBgwFoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBowHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MG4GA1UdIARnMGUwYwYMKwYBBAGGDgECAQgBMFMw
+UQYIKwYBBQUHAgEWRWh0dHA6Ly93d3cubmV0d29ya3NvbHV0aW9ucy5jb20vbGVn
+YWwvU1NMLWxlZ2FsLXJlcG9zaXRvcnktZXYtY3BzLmpzcDBEBgNVHR8EPTA7MDmg
+N6A1hjNodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENB
+Um9vdC5jcmwwgbMGCCsGAQUFBwEBBIGmMIGjMD8GCCsGAQUFBzAChjNodHRwOi8v
+Y3J0LnVzZXJ0cnVzdC5jb20vQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5wN2MwOQYI
+KwYBBQUHMAKGLWh0dHA6Ly9jcnQudXNlcnRydXN0LmNvbS9BZGRUcnVzdFVUTlNH
+Q0NBLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTAN
+BgkqhkiG9w0BAQUFAAOCAQEAVkXVchGnzImzMlqQjFNzBTbEMyevPkHA6XItSZ1S
+HlVanofQqU7wXAyd0joPiudKzF66pbWfyas9mUxGhnzANY4Xe11KIStuLGf9H2Y+
+yF1LDNPN71F0wQC3x6ywEtV3IPnw8+L05+L2B/0dlPjlPYMdNyqTqix6mdZikBGA
+Iwj4YswdJ0fUU5ccF1IQcAciOuyaN9EZHtQgjm+fRH86Eatrm86BT8SO7jywJ0of
+nHnRkb9z8N2yAFwz7llh/a4ncvOxPXr/TEGS5IPiVqJE4wNW9jSaNntiliLzb9NO
+fyyysLJbsSoGDjZbYzTIO2mz71GsmmiF7S4tRP6eCdcm+A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert85[] = {
+ 0x30, 0x82, 0x05, 0x2e, 0x30, 0x82, 0x04, 0x16, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x03, 0x0e, 0x95, 0x29, 0x4d, 0xae, 0xc1, 0x2c, 0x03,
+ 0xcf, 0x31, 0xab, 0x5b, 0x02, 0x71, 0xd7, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6f,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x53,
+ 0x45, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0b,
+ 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x41, 0x42, 0x31,
+ 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d, 0x41, 0x64,
+ 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, 0x65, 0x72,
+ 0x6e, 0x61, 0x6c, 0x20, 0x54, 0x54, 0x50, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x19, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x45,
+ 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x43, 0x41, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x35, 0x33,
+ 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x30, 0x35, 0x33, 0x30, 0x31, 0x30, 0x34, 0x38, 0x33, 0x38, 0x5a, 0x30,
+ 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c,
+ 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43,
+ 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27,
+ 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xe4, 0xbc, 0x7e, 0x92, 0x30, 0x6d, 0xc6, 0xd8, 0x8e, 0x2b, 0x0b, 0xbc,
+ 0x46, 0xce, 0xe0, 0x27, 0x96, 0xde, 0xde, 0xf9, 0xfa, 0x12, 0xd3, 0x3c,
+ 0x33, 0x73, 0xb3, 0x04, 0x2f, 0xbc, 0x71, 0x8c, 0xe5, 0x9f, 0xb6, 0x22,
+ 0x60, 0x3e, 0x5f, 0x5d, 0xce, 0x09, 0xff, 0x82, 0x0c, 0x1b, 0x9a, 0x51,
+ 0x50, 0x1a, 0x26, 0x89, 0xdd, 0xd5, 0x61, 0x5d, 0x19, 0xdc, 0x12, 0x0f,
+ 0x2d, 0x0a, 0xa2, 0x43, 0x5d, 0x17, 0xd0, 0x34, 0x92, 0x20, 0xea, 0x73,
+ 0xcf, 0x38, 0x2c, 0x06, 0x26, 0x09, 0x7a, 0x72, 0xf7, 0xfa, 0x50, 0x32,
+ 0xf8, 0xc2, 0x93, 0xd3, 0x69, 0xa2, 0x23, 0xce, 0x41, 0xb1, 0xcc, 0xe4,
+ 0xd5, 0x1f, 0x36, 0xd1, 0x8a, 0x3a, 0xf8, 0x8c, 0x63, 0xe2, 0x14, 0x59,
+ 0x69, 0xed, 0x0d, 0xd3, 0x7f, 0x6b, 0xe8, 0xb8, 0x03, 0xe5, 0x4f, 0x6a,
+ 0xe5, 0x98, 0x63, 0x69, 0x48, 0x05, 0xbe, 0x2e, 0xff, 0x33, 0xb6, 0xe9,
+ 0x97, 0x59, 0x69, 0xf8, 0x67, 0x19, 0xae, 0x93, 0x61, 0x96, 0x44, 0x15,
+ 0xd3, 0x72, 0xb0, 0x3f, 0xbc, 0x6a, 0x7d, 0xec, 0x48, 0x7f, 0x8d, 0xc3,
+ 0xab, 0xaa, 0x71, 0x2b, 0x53, 0x69, 0x41, 0x53, 0x34, 0xb5, 0xb0, 0xb9,
+ 0xc5, 0x06, 0x0a, 0xc4, 0xb0, 0x45, 0xf5, 0x41, 0x5d, 0x6e, 0x89, 0x45,
+ 0x7b, 0x3d, 0x3b, 0x26, 0x8c, 0x74, 0xc2, 0xe5, 0xd2, 0xd1, 0x7d, 0xb2,
+ 0x11, 0xd4, 0xfb, 0x58, 0x32, 0x22, 0x9a, 0x80, 0xc9, 0xdc, 0xfd, 0x0c,
+ 0xe9, 0x7f, 0x5e, 0x03, 0x97, 0xce, 0x3b, 0x00, 0x14, 0x87, 0x27, 0x70,
+ 0x38, 0xa9, 0x8e, 0x6e, 0xb3, 0x27, 0x76, 0x98, 0x51, 0xe0, 0x05, 0xe3,
+ 0x21, 0xab, 0x1a, 0xd5, 0x85, 0x22, 0x3c, 0x29, 0xb5, 0x9a, 0x16, 0xc5,
+ 0x80, 0xa8, 0xf4, 0xbb, 0x6b, 0x30, 0x8f, 0x2f, 0x46, 0x02, 0xa2, 0xb1,
+ 0x0c, 0x22, 0xe0, 0xd3, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+ 0xd1, 0x30, 0x82, 0x01, 0xcd, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xad, 0xbd, 0x98, 0x7a, 0x34, 0xb4,
+ 0x26, 0xf7, 0xfa, 0xc4, 0x26, 0x54, 0xef, 0x03, 0xbd, 0xe0, 0x24, 0xcb,
+ 0x54, 0x1a, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
+ 0x14, 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, 0xaa,
+ 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x06, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x6e, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x67, 0x30, 0x65, 0x30, 0x63, 0x06, 0x0c, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x86, 0x0e, 0x01, 0x02, 0x01, 0x08, 0x01, 0x30, 0x53, 0x30,
+ 0x51, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16,
+ 0x45, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x6f, 0x6c, 0x75, 0x74,
+ 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x67,
+ 0x61, 0x6c, 0x2f, 0x53, 0x53, 0x4c, 0x2d, 0x6c, 0x65, 0x67, 0x61, 0x6c,
+ 0x2d, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2d,
+ 0x65, 0x76, 0x2d, 0x63, 0x70, 0x73, 0x2e, 0x6a, 0x73, 0x70, 0x30, 0x44,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x3d, 0x30, 0x3b, 0x30, 0x39, 0xa0,
+ 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+ 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0xb3, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x81, 0xa6,
+ 0x30, 0x81, 0xa3, 0x30, 0x3f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x02, 0x86, 0x33, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41, 0x64, 0x64, 0x54, 0x72, 0x75,
+ 0x73, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x43, 0x41,
+ 0x52, 0x6f, 0x6f, 0x74, 0x2e, 0x70, 0x37, 0x63, 0x30, 0x39, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x2d, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x41,
+ 0x64, 0x64, 0x54, 0x72, 0x75, 0x73, 0x74, 0x55, 0x54, 0x4e, 0x53, 0x47,
+ 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x25, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x19, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x75, 0x73, 0x65,
+ 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x56, 0x45, 0xd5, 0x72, 0x11, 0xa7,
+ 0xcc, 0x89, 0xb3, 0x32, 0x5a, 0x90, 0x8c, 0x53, 0x73, 0x05, 0x36, 0xc4,
+ 0x33, 0x27, 0xaf, 0x3e, 0x41, 0xc0, 0xe9, 0x72, 0x2d, 0x49, 0x9d, 0x52,
+ 0x1e, 0x55, 0x5a, 0x9e, 0x87, 0xd0, 0xa9, 0x4e, 0xf0, 0x5c, 0x0c, 0x9d,
+ 0xd2, 0x3a, 0x0f, 0x8a, 0xe7, 0x4a, 0xcc, 0x5e, 0xba, 0xa5, 0xb5, 0x9f,
+ 0xc9, 0xab, 0x3d, 0x99, 0x4c, 0x46, 0x86, 0x7c, 0xc0, 0x35, 0x8e, 0x17,
+ 0x7b, 0x5d, 0x4a, 0x21, 0x2b, 0x6e, 0x2c, 0x67, 0xfd, 0x1f, 0x66, 0x3e,
+ 0xc8, 0x5d, 0x4b, 0x0c, 0xd3, 0xcd, 0xef, 0x51, 0x74, 0xc1, 0x00, 0xb7,
+ 0xc7, 0xac, 0xb0, 0x12, 0xd5, 0x77, 0x20, 0xf9, 0xf0, 0xf3, 0xe2, 0xf4,
+ 0xe7, 0xe2, 0xf6, 0x07, 0xfd, 0x1d, 0x94, 0xf8, 0xe5, 0x3d, 0x83, 0x1d,
+ 0x37, 0x2a, 0x93, 0xaa, 0x2c, 0x7a, 0x99, 0xd6, 0x62, 0x90, 0x11, 0x80,
+ 0x23, 0x08, 0xf8, 0x62, 0xcc, 0x1d, 0x27, 0x47, 0xd4, 0x53, 0x97, 0x1c,
+ 0x17, 0x52, 0x10, 0x70, 0x07, 0x22, 0x3a, 0xec, 0x9a, 0x37, 0xd1, 0x19,
+ 0x1e, 0xd4, 0x20, 0x8e, 0x6f, 0x9f, 0x44, 0x7f, 0x3a, 0x11, 0xab, 0x6b,
+ 0x9b, 0xce, 0x81, 0x4f, 0xc4, 0x8e, 0xee, 0x3c, 0xb0, 0x27, 0x4a, 0x1f,
+ 0x9c, 0x79, 0xd1, 0x91, 0xbf, 0x73, 0xf0, 0xdd, 0xb2, 0x00, 0x5c, 0x33,
+ 0xee, 0x59, 0x61, 0xfd, 0xae, 0x27, 0x72, 0xf3, 0xb1, 0x3d, 0x7a, 0xff,
+ 0x4c, 0x41, 0x92, 0xe4, 0x83, 0xe2, 0x56, 0xa2, 0x44, 0xe3, 0x03, 0x56,
+ 0xf6, 0x34, 0x9a, 0x36, 0x7b, 0x62, 0x96, 0x22, 0xf3, 0x6f, 0xd3, 0x4e,
+ 0x7f, 0x2c, 0xb2, 0xb0, 0xb2, 0x5b, 0xb1, 0x2a, 0x06, 0x0e, 0x36, 0x5b,
+ 0x63, 0x34, 0xc8, 0x3b, 0x69, 0xb3, 0xef, 0x51, 0xac, 0x9a, 0x68, 0x85,
+ 0xed, 0x2e, 0x2d, 0x44, 0xfe, 0x9e, 0x09, 0xd7, 0x26, 0xf8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 5f:a6:be:80:b6:86:c6:2f:01:ed:0c:ab:b1:96:a1:05
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+ Validity
+ Not Before: Nov 17 00:00:00 2006 GMT
+ Not After : Dec 30 23:59:59 2020 GMT
+ Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ac:a0:f0:fb:80:59:d4:9c:c7:a4:cf:9d:a1:59:
+ 73:09:10:45:0c:0d:2c:6e:68:f1:6c:5b:48:68:49:
+ 59:37:fc:0b:33:19:c2:77:7f:cc:10:2d:95:34:1c:
+ e6:eb:4d:09:a7:1c:d2:b8:c9:97:36:02:b7:89:d4:
+ 24:5f:06:c0:cc:44:94:94:8d:02:62:6f:eb:5a:dd:
+ 11:8d:28:9a:5c:84:90:10:7a:0d:bd:74:66:2f:6a:
+ 38:a0:e2:d5:54:44:eb:1d:07:9f:07:ba:6f:ee:e9:
+ fd:4e:0b:29:f5:3e:84:a0:01:f1:9c:ab:f8:1c:7e:
+ 89:a4:e8:a1:d8:71:65:0d:a3:51:7b:ee:bc:d2:22:
+ 60:0d:b9:5b:9d:df:ba:fc:51:5b:0b:af:98:b2:e9:
+ 2e:e9:04:e8:62:87:de:2b:c8:d7:4e:c1:4c:64:1e:
+ dd:cf:87:58:ba:4a:4f:ca:68:07:1d:1c:9d:4a:c6:
+ d5:2f:91:cc:7c:71:72:1c:c5:c0:67:eb:32:fd:c9:
+ 92:5c:94:da:85:c0:9b:bf:53:7d:2b:09:f4:8c:9d:
+ 91:1f:97:6a:52:cb:de:09:36:a4:77:d8:7b:87:50:
+ 44:d5:3e:6e:29:69:fb:39:49:26:1e:09:a5:80:7b:
+ 40:2d:eb:e8:27:85:c9:fe:61:fd:7e:e6:7c:97:1d:
+ d5:9d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.thawte.com/cps
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.thawte.com/ThawtePremiumServerCA.crl
+
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Authority Key Identifier:
+ DirName:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
+ serial:01
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 2b:ca:12:c9:dd:d7:cc:63:1c:9b:31:35:4a:dd:e4:b7:f6:9d:
+ d1:a4:fb:1e:f8:47:f9:ae:07:8e:0d:58:12:fb:da:ed:b5:cc:
+ 33:e5:97:68:47:61:42:d5:66:a9:6e:1e:47:bf:85:db:7d:58:
+ d1:77:5a:cc:90:61:98:9a:29:f5:9d:b1:cf:b8:dc:f3:7b:80:
+ 47:48:d1:7d:f4:68:8c:c4:41:cb:b4:e9:fd:f0:23:e0:b1:9b:
+ 76:2a:6d:28:56:a3:8c:cd:e9:ec:21:00:71:f0:5f:dd:50:a5:
+ 69:42:1b:83:11:5d:84:28:d3:27:ae:ec:2a:ab:2f:60:42:c5:
+ c4:78
+-----BEGIN CERTIFICATE-----
+MIIFUTCCBLqgAwIBAgIQX6a+gLaGxi8B7QyrsZahBTANBgkqhkiG9w0BAQUFADCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tMB4XDTA2MTExNzAwMDAwMFoXDTIwMTIzMDIzNTk1OVow
+gakxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xKDAmBgNVBAsT
+H0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xODA2BgNVBAsTLyhjKSAy
+MDA2IHRoYXd0ZSwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQDExZ0aGF3dGUgUHJpbWFyeSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEArKDw+4BZ1JzHpM+doVlzCRBFDA0sbmjxbFtIaElZN/wLMxnC
+d3/MEC2VNBzm600JpxzSuMmXNgK3idQkXwbAzESUlI0CYm/rWt0RjSiaXISQEHoN
+vXRmL2o4oOLVVETrHQefB7pv7un9Tgsp9T6EoAHxnKv4HH6JpOih2HFlDaNRe+68
+0iJgDblbnd+6/FFbC6+Ysuku6QToYofeK8jXTsFMZB7dz4dYukpPymgHHRydSsbV
+L5HMfHFyHMXAZ+sy/cmSXJTahcCbv1N9Kwn0jJ2RH5dqUsveCTakd9h7h1BE1T5u
+KWn7OUkmHgmlgHtALevoJ4XJ/mH9fuZ8lx3VnQIDAQABo4IBzTCCAckwDwYDVR0T
+AQH/BAUwAwEB/zA7BgNVHSAENDAyMDAGBFUdIAAwKDAmBggrBgEFBQcCARYaaHR0
+cHM6Ly93d3cudGhhd3RlLmNvbS9jcHMwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW
+BBR7W0XPr87Lev0xkhpqtvNG61dIUDBABgNVHR8EOTA3MDWgM6Axhi9odHRwOi8v
+Y3JsLnRoYXd0ZS5jb20vVGhhd3RlUHJlbWl1bVNlcnZlckNBLmNybDAgBgNVHSUE
+GTAXBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwgeUGA1UdIwSB3TCB2qGB1KSB0TCB
+zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
+Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
+CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
+d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
+cnZlckB0aGF3dGUuY29tggEBMA0GCSqGSIb3DQEBBQUAA4GBACvKEsnd18xjHJsx
+NUrd5Lf2ndGk+x74R/muB44NWBL72u21zDPll2hHYULVZqluHke/hdt9WNF3WsyQ
+YZiaKfWdsc+43PN7gEdI0X30aIzEQcu06f3wI+Cxm3YqbShWo4zN6ewhAHHwX91Q
+pWlCG4MRXYQo0yeu7CqrL2BCxcR4
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert86[] = {
+ 0x30, 0x82, 0x05, 0x51, 0x30, 0x82, 0x04, 0xba, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x5f, 0xa6, 0xbe, 0x80, 0xb6, 0x86, 0xc6, 0x2f, 0x01,
+ 0xed, 0x0c, 0xab, 0xb1, 0x96, 0xa1, 0x05, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+ 0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+ 0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+ 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+ 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+ 0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+ 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+ 0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+ 0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31,
+ 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30,
+ 0x31, 0x32, 0x33, 0x30, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30,
+ 0x81, 0xa9, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e,
+ 0x63, 0x2e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20,
+ 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x38, 0x30, 0x36,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2f, 0x28, 0x63, 0x29, 0x20, 0x32,
+ 0x30, 0x30, 0x36, 0x20, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+ 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x16, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+ 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x52, 0x6f, 0x6f, 0x74,
+ 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xac, 0xa0, 0xf0, 0xfb, 0x80, 0x59, 0xd4, 0x9c, 0xc7, 0xa4, 0xcf, 0x9d,
+ 0xa1, 0x59, 0x73, 0x09, 0x10, 0x45, 0x0c, 0x0d, 0x2c, 0x6e, 0x68, 0xf1,
+ 0x6c, 0x5b, 0x48, 0x68, 0x49, 0x59, 0x37, 0xfc, 0x0b, 0x33, 0x19, 0xc2,
+ 0x77, 0x7f, 0xcc, 0x10, 0x2d, 0x95, 0x34, 0x1c, 0xe6, 0xeb, 0x4d, 0x09,
+ 0xa7, 0x1c, 0xd2, 0xb8, 0xc9, 0x97, 0x36, 0x02, 0xb7, 0x89, 0xd4, 0x24,
+ 0x5f, 0x06, 0xc0, 0xcc, 0x44, 0x94, 0x94, 0x8d, 0x02, 0x62, 0x6f, 0xeb,
+ 0x5a, 0xdd, 0x11, 0x8d, 0x28, 0x9a, 0x5c, 0x84, 0x90, 0x10, 0x7a, 0x0d,
+ 0xbd, 0x74, 0x66, 0x2f, 0x6a, 0x38, 0xa0, 0xe2, 0xd5, 0x54, 0x44, 0xeb,
+ 0x1d, 0x07, 0x9f, 0x07, 0xba, 0x6f, 0xee, 0xe9, 0xfd, 0x4e, 0x0b, 0x29,
+ 0xf5, 0x3e, 0x84, 0xa0, 0x01, 0xf1, 0x9c, 0xab, 0xf8, 0x1c, 0x7e, 0x89,
+ 0xa4, 0xe8, 0xa1, 0xd8, 0x71, 0x65, 0x0d, 0xa3, 0x51, 0x7b, 0xee, 0xbc,
+ 0xd2, 0x22, 0x60, 0x0d, 0xb9, 0x5b, 0x9d, 0xdf, 0xba, 0xfc, 0x51, 0x5b,
+ 0x0b, 0xaf, 0x98, 0xb2, 0xe9, 0x2e, 0xe9, 0x04, 0xe8, 0x62, 0x87, 0xde,
+ 0x2b, 0xc8, 0xd7, 0x4e, 0xc1, 0x4c, 0x64, 0x1e, 0xdd, 0xcf, 0x87, 0x58,
+ 0xba, 0x4a, 0x4f, 0xca, 0x68, 0x07, 0x1d, 0x1c, 0x9d, 0x4a, 0xc6, 0xd5,
+ 0x2f, 0x91, 0xcc, 0x7c, 0x71, 0x72, 0x1c, 0xc5, 0xc0, 0x67, 0xeb, 0x32,
+ 0xfd, 0xc9, 0x92, 0x5c, 0x94, 0xda, 0x85, 0xc0, 0x9b, 0xbf, 0x53, 0x7d,
+ 0x2b, 0x09, 0xf4, 0x8c, 0x9d, 0x91, 0x1f, 0x97, 0x6a, 0x52, 0xcb, 0xde,
+ 0x09, 0x36, 0xa4, 0x77, 0xd8, 0x7b, 0x87, 0x50, 0x44, 0xd5, 0x3e, 0x6e,
+ 0x29, 0x69, 0xfb, 0x39, 0x49, 0x26, 0x1e, 0x09, 0xa5, 0x80, 0x7b, 0x40,
+ 0x2d, 0xeb, 0xe8, 0x27, 0x85, 0xc9, 0xfe, 0x61, 0xfd, 0x7e, 0xe6, 0x7c,
+ 0x97, 0x1d, 0xd5, 0x9d, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+ 0xcd, 0x30, 0x82, 0x01, 0xc9, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x3b,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x34, 0x30, 0x32, 0x30, 0x30, 0x06,
+ 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x28, 0x30, 0x26, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03,
+ 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0x7b, 0x5b, 0x45, 0xcf, 0xaf, 0xce, 0xcb, 0x7a, 0xfd, 0x31,
+ 0x92, 0x1a, 0x6a, 0xb6, 0xf3, 0x46, 0xeb, 0x57, 0x48, 0x50, 0x30, 0x40,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x39, 0x30, 0x37, 0x30, 0x35, 0xa0,
+ 0x33, 0xa0, 0x31, 0x86, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x50, 0x72, 0x65,
+ 0x6d, 0x69, 0x75, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x20, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+ 0x19, 0x30, 0x17, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+ 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+ 0x08, 0x01, 0x30, 0x81, 0xe5, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81,
+ 0xdd, 0x30, 0x81, 0xda, 0xa1, 0x81, 0xd4, 0xa4, 0x81, 0xd1, 0x30, 0x81,
+ 0xce, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x5a, 0x41, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x0c, 0x57, 0x65, 0x73, 0x74, 0x65, 0x72, 0x6e, 0x20, 0x43, 0x61, 0x70,
+ 0x65, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09,
+ 0x43, 0x61, 0x70, 0x65, 0x20, 0x54, 0x6f, 0x77, 0x6e, 0x31, 0x1d, 0x30,
+ 0x1b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x14, 0x54, 0x68, 0x61, 0x77,
+ 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e,
+ 0x67, 0x20, 0x63, 0x63, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x1f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
+ 0x73, 0x20, 0x44, 0x69, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x31, 0x21,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x20, 0x50, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x28, 0x30,
+ 0x26, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
+ 0x16, 0x19, 0x70, 0x72, 0x65, 0x6d, 0x69, 0x75, 0x6d, 0x2d, 0x73, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x40, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x82, 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81,
+ 0x00, 0x2b, 0xca, 0x12, 0xc9, 0xdd, 0xd7, 0xcc, 0x63, 0x1c, 0x9b, 0x31,
+ 0x35, 0x4a, 0xdd, 0xe4, 0xb7, 0xf6, 0x9d, 0xd1, 0xa4, 0xfb, 0x1e, 0xf8,
+ 0x47, 0xf9, 0xae, 0x07, 0x8e, 0x0d, 0x58, 0x12, 0xfb, 0xda, 0xed, 0xb5,
+ 0xcc, 0x33, 0xe5, 0x97, 0x68, 0x47, 0x61, 0x42, 0xd5, 0x66, 0xa9, 0x6e,
+ 0x1e, 0x47, 0xbf, 0x85, 0xdb, 0x7d, 0x58, 0xd1, 0x77, 0x5a, 0xcc, 0x90,
+ 0x61, 0x98, 0x9a, 0x29, 0xf5, 0x9d, 0xb1, 0xcf, 0xb8, 0xdc, 0xf3, 0x7b,
+ 0x80, 0x47, 0x48, 0xd1, 0x7d, 0xf4, 0x68, 0x8c, 0xc4, 0x41, 0xcb, 0xb4,
+ 0xe9, 0xfd, 0xf0, 0x23, 0xe0, 0xb1, 0x9b, 0x76, 0x2a, 0x6d, 0x28, 0x56,
+ 0xa3, 0x8c, 0xcd, 0xe9, 0xec, 0x21, 0x00, 0x71, 0xf0, 0x5f, 0xdd, 0x50,
+ 0xa5, 0x69, 0x42, 0x1b, 0x83, 0x11, 0x5d, 0x84, 0x28, 0xd3, 0x27, 0xae,
+ 0xec, 0x2a, 0xab, 0x2f, 0x60, 0x42, 0xc5, 0xc4, 0x78,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 5b:77:59:c6:17:84:e1:5e:c7:27:c0:32:95:29:28:6b
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2016 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:98:db:a0:55:eb:9c:fd:17:79:e3:9a:6e:14:1d:
+ b1:5b:98:23:87:16:6e:87:76:9c:b5:38:3b:b5:a0:
+ 7a:b4:07:63:09:19:e6:2a:88:48:a9:e7:9d:b6:30:
+ 5a:08:97:0c:ec:aa:e4:16:69:72:62:23:9a:fb:7a:
+ 54:28:98:c5:0c:2d:b7:d7:22:b6:c8:f9:38:17:c7:
+ dd:da:31:46:9a:94:14:8e:9e:ee:78:a0:b7:22:d4:
+ 49:54:97:4d:e5:74:5b:92:bc:ec:6c:2c:df:e7:c1:
+ b6:1b:1a:55:6b:66:08:03:7f:45:af:9a:33:f1:10:
+ c0:6c:99:4a:92:24:31:08:6d:dd:02:3e:61:76:78:
+ 78:b6:ed:7e:37:ae:6c:f3:89:e1:b7:e1:dc:15:cc:
+ b7:56:9f:80:a0:b1:05:7f:4e:37:15:ff:b7:2f:1e:
+ 8f:06:38:3f:50:b7:69:28:a3:b5:66:5f:36:1a:52:
+ 48:43:66:52:df:a2:92:4f:d3:18:60:be:e3:ea:5e:
+ 19:71:05:bf:9e:1c:6c:68:72:25:6f:b3:7b:73:c9:
+ 6d:bd:12:ff:9b:41:32:5e:f4:e8:7e:c5:0b:a3:4c:
+ 64:d1:4e:bc:26:08:65:fb:19:97:58:78:e1:33:bf:
+ ed:68:3e:b1:27:45:6f:c0:e2:ec:97:69:f7:5c:d3:
+ f7:51
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ FC:8A:50:BA:9E:B9:25:5A:7B:55:85:4F:95:00:63:8F:E9:58:6B:43
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Subject Alternative Name:
+ DirName:/CN=Class3CA2048-1-47
+ Authority Information Access:
+ OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 96:a2:fa:7f:e6:3d:ed:d4:2b:ce:b7:15:3f:c0:72:03:5f:8b:
+ ba:16:90:25:f7:c2:83:d8:c7:75:34:63:68:12:53:0c:53:89:
+ 7b:c9:56:09:a7:c3:36:44:4e:0e:d0:62:62:b3:86:fa:e8:a1:
+ 9b:34:67:8d:53:22:17:3e:fd:ac:ee:67:2e:43:e2:5d:7f:33:
+ 84:f2:a2:70:c0:6e:82:97:c0:34:fd:25:c6:23:7f:ed:e6:b0:
+ c5:57:43:84:b2:de:2d:f1:d0:f6:48:1f:14:71:57:b2:ac:31:
+ e1:97:24:23:c9:13:5d:74:e5:46:ef:09:7c:9e:e1:99:31:0a:
+ 08:79:1b:8f:71:9f:17:66:c8:38:cf:ee:8c:97:b6:06:b9:73:
+ 46:e4:d3:94:c1:e5:60:b5:25:75:2d:d9:69:31:ec:cd:96:c3:
+ a3:76:fd:e8:74:44:ac:12:b9:4d:bf:51:e8:b9:d4:44:4e:27:
+ cb:ae:20:d1:7e:2a:7c:b6:63:47:9e:76:ba:97:d0:16:e7:0b:
+ 6c:6d:f7:43:6f:33:0b:29:30:77:fa:9d:f9:f5:4e:b8:76:b3:
+ cd:18:b4:f9:20:ef:3d:db:e6:ca:ad:9b:d0:4e:d2:87:a9:0d:
+ a6:44:73:50:dd:70:5b:ed:ad:7e:4a:bc:22:d5:a8:26:e4:c2:
+ 85:20:0d:d9
+-----BEGIN CERTIFICATE-----
+MIIF5DCCBMygAwIBAgIQW3dZxheE4V7HJ8AylSkoazANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBujEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE0MDIGA1UEAxMrVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJjboFXrnP0XeeOabhQdsVuYI4cWbod2
+nLU4O7WgerQHYwkZ5iqISKnnnbYwWgiXDOyq5BZpcmIjmvt6VCiYxQwtt9citsj5
+OBfH3doxRpqUFI6e7nigtyLUSVSXTeV0W5K87Gws3+fBthsaVWtmCAN/Ra+aM/EQ
+wGyZSpIkMQht3QI+YXZ4eLbtfjeubPOJ4bfh3BXMt1afgKCxBX9ONxX/ty8ejwY4
+P1C3aSijtWZfNhpSSENmUt+ikk/TGGC+4+peGXEFv54cbGhyJW+ze3PJbb0S/5tB
+Ml706H7FC6NMZNFOvCYIZfsZl1h44TO/7Wg+sSdFb8Di7Jdp91zT91ECAwEAAaOC
+AdIwggHOMB0GA1UdDgQWBBT8ilC6nrklWntVhU+VAGOP6VhrQzASBgNVHRMBAf8E
+CDAGAQH/AgEAMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRw
+czovL3d3dy52ZXJpc2lnbi5jb20vY3BzMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6
+Ly9FVlNlY3VyZS1jcmwudmVyaXNpZ24uY29tL3BjYTMtZzUuY3JsMA4GA1UdDwEB
+/wQEAwIBBjARBglghkgBhvhCAQEEBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZ
+MFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7
+GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwKQYDVR0R
+BCIwIKQeMBwxGjAYBgNVBAMTEUNsYXNzM0NBMjA0OC0xLTQ3MD0GCCsGAQUFBwEB
+BDEwLzAtBggrBgEFBQcwAYYhaHR0cDovL0VWU2VjdXJlLW9jc3AudmVyaXNpZ24u
+Y29tMB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqGSIb3DQEB
+BQUAA4IBAQCWovp/5j3t1CvOtxU/wHIDX4u6FpAl98KD2Md1NGNoElMMU4l7yVYJ
+p8M2RE4O0GJis4b66KGbNGeNUyIXPv2s7mcuQ+JdfzOE8qJwwG6Cl8A0/SXGI3/t
+5rDFV0OEst4t8dD2SB8UcVeyrDHhlyQjyRNddOVG7wl8nuGZMQoIeRuPcZ8XZsg4
+z+6Ml7YGuXNG5NOUweVgtSV1LdlpMezNlsOjdv3odESsErlNv1HoudRETifLriDR
+fip8tmNHnna6l9AW5wtsbfdDbzMLKTB3+p359U64drPNGLT5IO892+bKrZvQTtKH
+qQ2mRHNQ3XBb7a1+Srwi1agm5MKFIA3Z
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert87[] = {
+ 0x30, 0x82, 0x05, 0xe4, 0x30, 0x82, 0x04, 0xcc, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x5b, 0x77, 0x59, 0xc6, 0x17, 0x84, 0xe1, 0x5e, 0xc7,
+ 0x27, 0xc0, 0x32, 0x95, 0x29, 0x28, 0x6b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xba, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+ 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x34,
+ 0x30, 0x32, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2b, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+ 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0x98, 0xdb, 0xa0, 0x55, 0xeb, 0x9c, 0xfd, 0x17, 0x79, 0xe3, 0x9a,
+ 0x6e, 0x14, 0x1d, 0xb1, 0x5b, 0x98, 0x23, 0x87, 0x16, 0x6e, 0x87, 0x76,
+ 0x9c, 0xb5, 0x38, 0x3b, 0xb5, 0xa0, 0x7a, 0xb4, 0x07, 0x63, 0x09, 0x19,
+ 0xe6, 0x2a, 0x88, 0x48, 0xa9, 0xe7, 0x9d, 0xb6, 0x30, 0x5a, 0x08, 0x97,
+ 0x0c, 0xec, 0xaa, 0xe4, 0x16, 0x69, 0x72, 0x62, 0x23, 0x9a, 0xfb, 0x7a,
+ 0x54, 0x28, 0x98, 0xc5, 0x0c, 0x2d, 0xb7, 0xd7, 0x22, 0xb6, 0xc8, 0xf9,
+ 0x38, 0x17, 0xc7, 0xdd, 0xda, 0x31, 0x46, 0x9a, 0x94, 0x14, 0x8e, 0x9e,
+ 0xee, 0x78, 0xa0, 0xb7, 0x22, 0xd4, 0x49, 0x54, 0x97, 0x4d, 0xe5, 0x74,
+ 0x5b, 0x92, 0xbc, 0xec, 0x6c, 0x2c, 0xdf, 0xe7, 0xc1, 0xb6, 0x1b, 0x1a,
+ 0x55, 0x6b, 0x66, 0x08, 0x03, 0x7f, 0x45, 0xaf, 0x9a, 0x33, 0xf1, 0x10,
+ 0xc0, 0x6c, 0x99, 0x4a, 0x92, 0x24, 0x31, 0x08, 0x6d, 0xdd, 0x02, 0x3e,
+ 0x61, 0x76, 0x78, 0x78, 0xb6, 0xed, 0x7e, 0x37, 0xae, 0x6c, 0xf3, 0x89,
+ 0xe1, 0xb7, 0xe1, 0xdc, 0x15, 0xcc, 0xb7, 0x56, 0x9f, 0x80, 0xa0, 0xb1,
+ 0x05, 0x7f, 0x4e, 0x37, 0x15, 0xff, 0xb7, 0x2f, 0x1e, 0x8f, 0x06, 0x38,
+ 0x3f, 0x50, 0xb7, 0x69, 0x28, 0xa3, 0xb5, 0x66, 0x5f, 0x36, 0x1a, 0x52,
+ 0x48, 0x43, 0x66, 0x52, 0xdf, 0xa2, 0x92, 0x4f, 0xd3, 0x18, 0x60, 0xbe,
+ 0xe3, 0xea, 0x5e, 0x19, 0x71, 0x05, 0xbf, 0x9e, 0x1c, 0x6c, 0x68, 0x72,
+ 0x25, 0x6f, 0xb3, 0x7b, 0x73, 0xc9, 0x6d, 0xbd, 0x12, 0xff, 0x9b, 0x41,
+ 0x32, 0x5e, 0xf4, 0xe8, 0x7e, 0xc5, 0x0b, 0xa3, 0x4c, 0x64, 0xd1, 0x4e,
+ 0xbc, 0x26, 0x08, 0x65, 0xfb, 0x19, 0x97, 0x58, 0x78, 0xe1, 0x33, 0xbf,
+ 0xed, 0x68, 0x3e, 0xb1, 0x27, 0x45, 0x6f, 0xc0, 0xe2, 0xec, 0x97, 0x69,
+ 0xf7, 0x5c, 0xd3, 0xf7, 0x51, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0xd2, 0x30, 0x82, 0x01, 0xce, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d,
+ 0x0e, 0x04, 0x16, 0x04, 0x14, 0xfc, 0x8a, 0x50, 0xba, 0x9e, 0xb9, 0x25,
+ 0x5a, 0x7b, 0x55, 0x85, 0x4f, 0x95, 0x00, 0x63, 0x8f, 0xe9, 0x58, 0x6b,
+ 0x43, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04,
+ 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3d, 0x06,
+ 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34, 0x30, 0x32, 0x06, 0x04,
+ 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70,
+ 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73,
+ 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x36, 0x30, 0x34, 0x30,
+ 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x63,
+ 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x11, 0x06, 0x09, 0x60,
+ 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+ 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+ 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+ 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+ 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+ 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11,
+ 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43, 0x6c, 0x61, 0x73, 0x73,
+ 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d, 0x34, 0x37,
+ 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f, 0x63,
+ 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb,
+ 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x96, 0xa2, 0xfa, 0x7f,
+ 0xe6, 0x3d, 0xed, 0xd4, 0x2b, 0xce, 0xb7, 0x15, 0x3f, 0xc0, 0x72, 0x03,
+ 0x5f, 0x8b, 0xba, 0x16, 0x90, 0x25, 0xf7, 0xc2, 0x83, 0xd8, 0xc7, 0x75,
+ 0x34, 0x63, 0x68, 0x12, 0x53, 0x0c, 0x53, 0x89, 0x7b, 0xc9, 0x56, 0x09,
+ 0xa7, 0xc3, 0x36, 0x44, 0x4e, 0x0e, 0xd0, 0x62, 0x62, 0xb3, 0x86, 0xfa,
+ 0xe8, 0xa1, 0x9b, 0x34, 0x67, 0x8d, 0x53, 0x22, 0x17, 0x3e, 0xfd, 0xac,
+ 0xee, 0x67, 0x2e, 0x43, 0xe2, 0x5d, 0x7f, 0x33, 0x84, 0xf2, 0xa2, 0x70,
+ 0xc0, 0x6e, 0x82, 0x97, 0xc0, 0x34, 0xfd, 0x25, 0xc6, 0x23, 0x7f, 0xed,
+ 0xe6, 0xb0, 0xc5, 0x57, 0x43, 0x84, 0xb2, 0xde, 0x2d, 0xf1, 0xd0, 0xf6,
+ 0x48, 0x1f, 0x14, 0x71, 0x57, 0xb2, 0xac, 0x31, 0xe1, 0x97, 0x24, 0x23,
+ 0xc9, 0x13, 0x5d, 0x74, 0xe5, 0x46, 0xef, 0x09, 0x7c, 0x9e, 0xe1, 0x99,
+ 0x31, 0x0a, 0x08, 0x79, 0x1b, 0x8f, 0x71, 0x9f, 0x17, 0x66, 0xc8, 0x38,
+ 0xcf, 0xee, 0x8c, 0x97, 0xb6, 0x06, 0xb9, 0x73, 0x46, 0xe4, 0xd3, 0x94,
+ 0xc1, 0xe5, 0x60, 0xb5, 0x25, 0x75, 0x2d, 0xd9, 0x69, 0x31, 0xec, 0xcd,
+ 0x96, 0xc3, 0xa3, 0x76, 0xfd, 0xe8, 0x74, 0x44, 0xac, 0x12, 0xb9, 0x4d,
+ 0xbf, 0x51, 0xe8, 0xb9, 0xd4, 0x44, 0x4e, 0x27, 0xcb, 0xae, 0x20, 0xd1,
+ 0x7e, 0x2a, 0x7c, 0xb6, 0x63, 0x47, 0x9e, 0x76, 0xba, 0x97, 0xd0, 0x16,
+ 0xe7, 0x0b, 0x6c, 0x6d, 0xf7, 0x43, 0x6f, 0x33, 0x0b, 0x29, 0x30, 0x77,
+ 0xfa, 0x9d, 0xf9, 0xf5, 0x4e, 0xb8, 0x76, 0xb3, 0xcd, 0x18, 0xb4, 0xf9,
+ 0x20, 0xef, 0x3d, 0xdb, 0xe6, 0xca, 0xad, 0x9b, 0xd0, 0x4e, 0xd2, 0x87,
+ 0xa9, 0x0d, 0xa6, 0x44, 0x73, 0x50, 0xdd, 0x70, 0x5b, 0xed, 0xad, 0x7e,
+ 0x4a, 0xbc, 0x22, 0xd5, 0xa8, 0x26, 0xe4, 0xc2, 0x85, 0x20, 0x0d, 0xd9,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 6e:cc:7a:a5:a7:03:20:09:b8:ce:bc:f4:e9:52:d4:91
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Feb 8 00:00:00 2010 GMT
+ Not After : Feb 7 23:59:59 2020 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Secure Server CA - G3
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b1:87:84:1f:c2:0c:45:f5:bc:ab:25:97:a7:ad:
+ a2:3e:9c:ba:f6:c1:39:b8:8b:ca:c2:ac:56:c6:e5:
+ bb:65:8e:44:4f:4d:ce:6f:ed:09:4a:d4:af:4e:10:
+ 9c:68:8b:2e:95:7b:89:9b:13:ca:e2:34:34:c1:f3:
+ 5b:f3:49:7b:62:83:48:81:74:d1:88:78:6c:02:53:
+ f9:bc:7f:43:26:57:58:33:83:3b:33:0a:17:b0:d0:
+ 4e:91:24:ad:86:7d:64:12:dc:74:4a:34:a1:1d:0a:
+ ea:96:1d:0b:15:fc:a3:4b:3b:ce:63:88:d0:f8:2d:
+ 0c:94:86:10:ca:b6:9a:3d:ca:eb:37:9c:00:48:35:
+ 86:29:50:78:e8:45:63:cd:19:41:4f:f5:95:ec:7b:
+ 98:d4:c4:71:b3:50:be:28:b3:8f:a0:b9:53:9c:f5:
+ ca:2c:23:a9:fd:14:06:e8:18:b4:9a:e8:3c:6e:81:
+ fd:e4:cd:35:36:b3:51:d3:69:ec:12:ba:56:6e:6f:
+ 9b:57:c5:8b:14:e7:0e:c7:9c:ed:4a:54:6a:c9:4d:
+ c5:bf:11:b1:ae:1c:67:81:cb:44:55:33:99:7f:24:
+ 9b:3f:53:45:7f:86:1a:f3:3c:fa:6d:7f:81:f5:b8:
+ 4a:d3:f5:85:37:1c:b5:a6:d0:09:e4:18:7b:38:4e:
+ fa:0f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.23.3
+ CPS: https://www.verisign.com/cps
+ User Notice:
+ Explicit Text: https://www.verisign.com/rpa
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3-g5.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-6
+ X509v3 Subject Key Identifier:
+ 0D:44:5C:16:53:44:C1:82:7E:1D:20:AB:25:F4:01:63:D8:BE:79:A5
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 0c:83:24:ef:dd:c3:0c:d9:58:9c:fe:36:b6:eb:8a:80:4b:d1:
+ a3:f7:9d:f3:cc:53:ef:82:9e:a3:a1:e6:97:c1:58:9d:75:6c:
+ e0:1d:1b:4c:fa:d1:c1:2d:05:c0:ea:6e:b2:22:70:55:d9:20:
+ 33:40:33:07:c2:65:83:fa:8f:43:37:9b:ea:0e:9a:6c:70:ee:
+ f6:9c:80:3b:d9:37:f4:7a:6d:ec:d0:18:7d:49:4a:ca:99:c7:
+ 19:28:a2:be:d8:77:24:f7:85:26:86:6d:87:05:40:41:67:d1:
+ 27:3a:ed:dc:48:1d:22:cd:0b:0b:8b:bc:f4:b1:7b:fd:b4:99:
+ a8:e9:76:2a:e1:1a:2d:87:6e:74:d3:88:dd:1e:22:c6:df:16:
+ b6:2b:82:14:0a:94:5c:f2:50:ec:af:ce:ff:62:37:0d:ad:65:
+ d3:06:41:53:ed:02:14:c8:b5:58:28:a1:ac:e0:5b:ec:b3:7f:
+ 95:4a:fb:03:c8:ad:26:db:e6:66:78:12:4a:d9:9f:42:fb:e1:
+ 98:e6:42:83:9b:8f:8f:67:24:e8:61:19:b5:dd:cd:b5:0b:26:
+ 05:8e:c3:6e:c4:c8:75:b8:46:cf:e2:18:06:5e:a9:ae:a8:81:
+ 9a:47:16:de:0c:28:6c:25:27:b9:de:b7:84:58:c6:1f:38:1e:
+ a4:c4:cb:66
+-----BEGIN CERTIFICATE-----
+MIIF7DCCBNSgAwIBAgIQbsx6pacDIAm4zrz06VLUkTANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtTEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEvMC0GA1UEAxMmVmVy
+aVNpZ24gQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCxh4QfwgxF9byrJZenraI+nLr2wTm4i8rCrFbG
+5btljkRPTc5v7QlK1K9OEJxoiy6Ve4mbE8riNDTB81vzSXtig0iBdNGIeGwCU/m8
+f0MmV1gzgzszChew0E6RJK2GfWQS3HRKNKEdCuqWHQsV/KNLO85jiND4LQyUhhDK
+tpo9yus3nABINYYpUHjoRWPNGUFP9ZXse5jUxHGzUL4os4+guVOc9cosI6n9FAbo
+GLSa6Dxugf3kzTU2s1HTaewSulZub5tXxYsU5w7HnO1KVGrJTcW/EbGuHGeBy0RV
+M5l/JJs/U0V/hhrzPPptf4H1uErT9YU3HLWm0AnkGHs4TvoPAgMBAAGjggHfMIIB
+2zA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnZlcmlz
+aWduLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4
+RQEHFwMwVjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2Nw
+czAqBggrBgEFBQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMDQG
+A1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMtZzUu
+Y3JsMA4GA1UdDwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglp
+bWFnZS9naWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNo
+dHRwOi8vbG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjAoBgNVHREEITAfpB0w
+GzEZMBcGA1UEAxMQVmVyaVNpZ25NUEtJLTItNjAdBgNVHQ4EFgQUDURcFlNEwYJ+
+HSCrJfQBY9i+eaUwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJ
+KoZIhvcNAQEFBQADggEBAAyDJO/dwwzZWJz+NrbrioBL0aP3nfPMU++CnqOh5pfB
+WJ11bOAdG0z60cEtBcDqbrIicFXZIDNAMwfCZYP6j0M3m+oOmmxw7vacgDvZN/R6
+bezQGH1JSsqZxxkoor7YdyT3hSaGbYcFQEFn0Sc67dxIHSLNCwuLvPSxe/20majp
+dirhGi2HbnTTiN0eIsbfFrYrghQKlFzyUOyvzv9iNw2tZdMGQVPtAhTItVgooazg
+W+yzf5VK+wPIrSbb5mZ4EkrZn0L74ZjmQoObj49nJOhhGbXdzbULJgWOw27EyHW4
+Rs/iGAZeqa6ogZpHFt4MKGwlJ7net4RYxh84HqTEy2Y=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert88[] = {
+ 0x30, 0x82, 0x05, 0xec, 0x30, 0x82, 0x04, 0xd4, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x6e, 0xcc, 0x7a, 0xa5, 0xa7, 0x03, 0x20, 0x09, 0xb8,
+ 0xce, 0xbc, 0xf4, 0xe9, 0x52, 0xd4, 0x91, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xb5, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+ 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x2f,
+ 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30,
+ 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30,
+ 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb1, 0x87, 0x84, 0x1f,
+ 0xc2, 0x0c, 0x45, 0xf5, 0xbc, 0xab, 0x25, 0x97, 0xa7, 0xad, 0xa2, 0x3e,
+ 0x9c, 0xba, 0xf6, 0xc1, 0x39, 0xb8, 0x8b, 0xca, 0xc2, 0xac, 0x56, 0xc6,
+ 0xe5, 0xbb, 0x65, 0x8e, 0x44, 0x4f, 0x4d, 0xce, 0x6f, 0xed, 0x09, 0x4a,
+ 0xd4, 0xaf, 0x4e, 0x10, 0x9c, 0x68, 0x8b, 0x2e, 0x95, 0x7b, 0x89, 0x9b,
+ 0x13, 0xca, 0xe2, 0x34, 0x34, 0xc1, 0xf3, 0x5b, 0xf3, 0x49, 0x7b, 0x62,
+ 0x83, 0x48, 0x81, 0x74, 0xd1, 0x88, 0x78, 0x6c, 0x02, 0x53, 0xf9, 0xbc,
+ 0x7f, 0x43, 0x26, 0x57, 0x58, 0x33, 0x83, 0x3b, 0x33, 0x0a, 0x17, 0xb0,
+ 0xd0, 0x4e, 0x91, 0x24, 0xad, 0x86, 0x7d, 0x64, 0x12, 0xdc, 0x74, 0x4a,
+ 0x34, 0xa1, 0x1d, 0x0a, 0xea, 0x96, 0x1d, 0x0b, 0x15, 0xfc, 0xa3, 0x4b,
+ 0x3b, 0xce, 0x63, 0x88, 0xd0, 0xf8, 0x2d, 0x0c, 0x94, 0x86, 0x10, 0xca,
+ 0xb6, 0x9a, 0x3d, 0xca, 0xeb, 0x37, 0x9c, 0x00, 0x48, 0x35, 0x86, 0x29,
+ 0x50, 0x78, 0xe8, 0x45, 0x63, 0xcd, 0x19, 0x41, 0x4f, 0xf5, 0x95, 0xec,
+ 0x7b, 0x98, 0xd4, 0xc4, 0x71, 0xb3, 0x50, 0xbe, 0x28, 0xb3, 0x8f, 0xa0,
+ 0xb9, 0x53, 0x9c, 0xf5, 0xca, 0x2c, 0x23, 0xa9, 0xfd, 0x14, 0x06, 0xe8,
+ 0x18, 0xb4, 0x9a, 0xe8, 0x3c, 0x6e, 0x81, 0xfd, 0xe4, 0xcd, 0x35, 0x36,
+ 0xb3, 0x51, 0xd3, 0x69, 0xec, 0x12, 0xba, 0x56, 0x6e, 0x6f, 0x9b, 0x57,
+ 0xc5, 0x8b, 0x14, 0xe7, 0x0e, 0xc7, 0x9c, 0xed, 0x4a, 0x54, 0x6a, 0xc9,
+ 0x4d, 0xc5, 0xbf, 0x11, 0xb1, 0xae, 0x1c, 0x67, 0x81, 0xcb, 0x44, 0x55,
+ 0x33, 0x99, 0x7f, 0x24, 0x9b, 0x3f, 0x53, 0x45, 0x7f, 0x86, 0x1a, 0xf3,
+ 0x3c, 0xfa, 0x6d, 0x7f, 0x81, 0xf5, 0xb8, 0x4a, 0xd3, 0xf5, 0x85, 0x37,
+ 0x1c, 0xb5, 0xa6, 0xd0, 0x09, 0xe4, 0x18, 0x7b, 0x38, 0x4e, 0xfa, 0x0f,
+ 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xdf, 0x30, 0x82, 0x01,
+ 0xdb, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+ 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55,
+ 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff,
+ 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x69,
+ 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+ 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74,
+ 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72,
+ 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+ 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02,
+ 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+ 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x34, 0x06,
+ 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27,
+ 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e,
+ 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1,
+ 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69,
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f,
+ 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f,
+ 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a,
+ 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76,
+ 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x28,
+ 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d, 0x30,
+ 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49,
+ 0x2d, 0x32, 0x2d, 0x36, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x0d, 0x44, 0x5c, 0x16, 0x53, 0x44, 0xc1, 0x82, 0x7e,
+ 0x1d, 0x20, 0xab, 0x25, 0xf4, 0x01, 0x63, 0xd8, 0xbe, 0x79, 0xa5, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+ 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3,
+ 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x0c, 0x83, 0x24, 0xef, 0xdd, 0xc3, 0x0c, 0xd9,
+ 0x58, 0x9c, 0xfe, 0x36, 0xb6, 0xeb, 0x8a, 0x80, 0x4b, 0xd1, 0xa3, 0xf7,
+ 0x9d, 0xf3, 0xcc, 0x53, 0xef, 0x82, 0x9e, 0xa3, 0xa1, 0xe6, 0x97, 0xc1,
+ 0x58, 0x9d, 0x75, 0x6c, 0xe0, 0x1d, 0x1b, 0x4c, 0xfa, 0xd1, 0xc1, 0x2d,
+ 0x05, 0xc0, 0xea, 0x6e, 0xb2, 0x22, 0x70, 0x55, 0xd9, 0x20, 0x33, 0x40,
+ 0x33, 0x07, 0xc2, 0x65, 0x83, 0xfa, 0x8f, 0x43, 0x37, 0x9b, 0xea, 0x0e,
+ 0x9a, 0x6c, 0x70, 0xee, 0xf6, 0x9c, 0x80, 0x3b, 0xd9, 0x37, 0xf4, 0x7a,
+ 0x6d, 0xec, 0xd0, 0x18, 0x7d, 0x49, 0x4a, 0xca, 0x99, 0xc7, 0x19, 0x28,
+ 0xa2, 0xbe, 0xd8, 0x77, 0x24, 0xf7, 0x85, 0x26, 0x86, 0x6d, 0x87, 0x05,
+ 0x40, 0x41, 0x67, 0xd1, 0x27, 0x3a, 0xed, 0xdc, 0x48, 0x1d, 0x22, 0xcd,
+ 0x0b, 0x0b, 0x8b, 0xbc, 0xf4, 0xb1, 0x7b, 0xfd, 0xb4, 0x99, 0xa8, 0xe9,
+ 0x76, 0x2a, 0xe1, 0x1a, 0x2d, 0x87, 0x6e, 0x74, 0xd3, 0x88, 0xdd, 0x1e,
+ 0x22, 0xc6, 0xdf, 0x16, 0xb6, 0x2b, 0x82, 0x14, 0x0a, 0x94, 0x5c, 0xf2,
+ 0x50, 0xec, 0xaf, 0xce, 0xff, 0x62, 0x37, 0x0d, 0xad, 0x65, 0xd3, 0x06,
+ 0x41, 0x53, 0xed, 0x02, 0x14, 0xc8, 0xb5, 0x58, 0x28, 0xa1, 0xac, 0xe0,
+ 0x5b, 0xec, 0xb3, 0x7f, 0x95, 0x4a, 0xfb, 0x03, 0xc8, 0xad, 0x26, 0xdb,
+ 0xe6, 0x66, 0x78, 0x12, 0x4a, 0xd9, 0x9f, 0x42, 0xfb, 0xe1, 0x98, 0xe6,
+ 0x42, 0x83, 0x9b, 0x8f, 0x8f, 0x67, 0x24, 0xe8, 0x61, 0x19, 0xb5, 0xdd,
+ 0xcd, 0xb5, 0x0b, 0x26, 0x05, 0x8e, 0xc3, 0x6e, 0xc4, 0xc8, 0x75, 0xb8,
+ 0x46, 0xcf, 0xe2, 0x18, 0x06, 0x5e, 0xa9, 0xae, 0xa8, 0x81, 0x9a, 0x47,
+ 0x16, 0xde, 0x0c, 0x28, 0x6c, 0x25, 0x27, 0xb9, 0xde, 0xb7, 0x84, 0x58,
+ 0xc6, 0x1f, 0x38, 0x1e, 0xa4, 0xc4, 0xcb, 0x66,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 71:63:66:35:eb:f3:82:3d:7e:13:09:59:a2:d8:e5:de
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G3
+ Validity
+ Not Before: Oct 12 00:00:00 2010 GMT
+ Not After : Oct 11 23:59:59 2020 GMT
+ Subject: C=US, O=Oracle Corporation, OU=VeriSign Trust Network, OU=Class 3 MPKI Secure Server CA, CN=Oracle SSL CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:a4:3e:5a:70:29:b8:f1:9f:22:b9:21:8e:c5:52:
+ ac:32:aa:bf:1a:1b:29:f3:3d:79:ea:6a:1c:29:48:
+ f4:6f:fe:86:31:23:3d:07:23:17:05:3b:5e:04:fd:
+ 3e:5c:1a:6d:63:61:64:90:49:67:de:69:8f:f4:72:
+ a7:23:87:1a:66:98:da:07:3c:21:2c:ac:86:8d:11:
+ 7d:40:98:40:26:59:a9:c5:af:aa:f2:e9:d7:11:91:
+ 9b:f1:d6:6f:cd:65:63:3d:8f:76:a1:99:2f:c6:3f:
+ 9d:fa:57:82:b1:ff:11:0b:c4:ec:84:d2:d4:47:ef:
+ 2c:bf:90:eb:61:95:ee:eb:17:c0:43:d6:83:67:7b:
+ 54:80:f4:0d:06:9f:0a:ed:d9:de:5c:66:fd:49:a6:
+ e8:3f:96:3c:fa:c9:46:96:65:af:82:73:26:e0:94:
+ 0b:bd:99:c0:b5:61:a6:ec:dc:be:57:d4:57:91:ca:
+ 18:0e:2c:cc:0c:8a:e0:a4:7c:a3:e5:7c:0c:3e:97:
+ df:62:b9:80:a5:11:35:db:6b:fb:91:45:3c:2f:48:
+ e9:58:05:6d:8e:cd:04:72:2e:04:a2:ae:18:66:79:
+ e9:38:f7:78:ec:62:af:eb:a6:f8:c5:4a:7c:58:85:
+ 60:7d:20:6a:7d:84:c6:32:3a:66:ea:33:ec:e8:4d:
+ 93:f1
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:1
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.23.3
+ CPS: https://www.verisign.com/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3-g3.crl
+
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-20
+ X509v3 Subject Key Identifier:
+ CC:F8:BB:65:47:6A:52:16:C4:EC:7E:9B:27:9C:FC:2E:A9:C2:F0:0F
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 1999 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G3
+ serial:9B:7E:06:49:A3:3E:62:B9:D5:EE:90:48:71:29:EF:57
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 47:8b:0e:12:54:a1:ce:ca:59:ad:c1:81:65:66:2f:8f:5b:71:
+ c5:86:a3:90:a3:7b:e0:7c:f1:60:1a:81:87:7f:df:c1:7e:9c:
+ a0:5b:d4:db:c0:bc:ac:78:4d:51:59:9d:28:24:db:46:a1:74:
+ e2:d7:2e:2b:7f:74:09:03:d3:aa:31:f1:47:fb:b5:a3:e5:5e:
+ 40:d1:b6:a3:c5:e5:cf:5f:26:7b:ab:17:ab:91:8a:2f:f9:d8:
+ 0a:34:54:f6:6a:63:52:9b:d7:70:8b:34:46:14:2a:7b:09:40:
+ 04:a0:1a:6f:d3:4b:2a:f5:12:9c:22:db:3b:ce:b2:7e:15:f0:
+ f3:4e:3e:e4:7f:b6:8a:bf:68:04:b5:9c:5d:bb:8f:a5:c8:29:
+ 95:5f:5b:c6:e4:df:84:9c:80:74:1a:35:1d:fd:94:ac:86:85:
+ 89:2e:90:7f:59:b6:9c:06:e5:35:ff:ff:ff:b5:53:d9:3e:b5:
+ dd:ae:fe:06:4f:66:71:e0:4f:f7:fc:c1:85:b5:7b:85:43:22:
+ cf:5b:f6:94:85:a6:59:b2:5d:fe:29:4f:8c:9c:1e:92:ce:0f:
+ 33:20:19:49:59:54:36:6c:c4:e9:f9:66:1b:20:6c:b2:6f:3e:
+ 24:39:6f:91:fb:b4:d8:93:50:c0:c2:97:de:e9:93:5e:97:20:
+ 05:4a:09:13
+-----BEGIN CERTIFICATE-----
+MIIF+zCCBOOgAwIBAgIQcWNmNevzgj1+EwlZotjl3jANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMTk5OSBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzMwHhcNMTAxMDEyMDAwMDAwWhcNMjAxMDExMjM1OTU5WjCBizEL
+MAkGA1UEBhMCVVMxGzAZBgNVBAoTEk9yYWNsZSBDb3Jwb3JhdGlvbjEfMB0GA1UE
+CxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEmMCQGA1UECxMdQ2xhc3MgMyBNUEtJ
+IFNlY3VyZSBTZXJ2ZXIgQ0ExFjAUBgNVBAMTDU9yYWNsZSBTU0wgQ0EwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkPlpwKbjxnyK5IY7FUqwyqr8aGynz
+PXnqahwpSPRv/oYxIz0HIxcFO14E/T5cGm1jYWSQSWfeaY/0cqcjhxpmmNoHPCEs
+rIaNEX1AmEAmWanFr6ry6dcRkZvx1m/NZWM9j3ahmS/GP536V4Kx/xELxOyE0tRH
+7yy/kOthle7rF8BD1oNne1SA9A0Gnwrt2d5cZv1Jpug/ljz6yUaWZa+CcybglAu9
+mcC1Yabs3L5X1FeRyhgOLMwMiuCkfKPlfAw+l99iuYClETXba/uRRTwvSOlYBW2O
+zQRyLgSirhhmeek493jsYq/rpvjFSnxYhWB9IGp9hMYyOmbqM+zoTZPxAgMBAAGj
+ggIYMIICFDAOBgNVHQ8BAf8EBAMCAQYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF
+BzABhhhodHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wEgYDVR0TAQH/BAgwBgEB/wIB
+ATBEBgNVHSAEPTA7MDkGC2CGSAGG+EUBBxcDMCowKAYIKwYBBQUHAgEWHGh0dHBz
+Oi8vd3d3LnZlcmlzaWduLmNvbS9jcHMwNAYDVR0fBC0wKzApoCegJYYjaHR0cDov
+L2NybC52ZXJpc2lnbi5jb20vcGNhMy1nMy5jcmwwKQYDVR0RBCIwIKQeMBwxGjAY
+BgNVBAMTEVZlcmlTaWduTVBLSS0yLTIwMB0GA1UdDgQWBBTM+LtlR2pSFsTsfpsn
+nPwuqcLwDzCB8QYDVR0jBIHpMIHmoYHQpIHNMIHKMQswCQYDVQQGEwJVUzEXMBUG
+A1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5l
+dHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1
+dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVi
+bGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHM4IRAJt+Bkmj
+PmK51e6QSHEp71cwDQYJKoZIhvcNAQEFBQADggEBAEeLDhJUoc7KWa3BgWVmL49b
+ccWGo5Cje+B88WAagYd/38F+nKBb1NvAvKx4TVFZnSgk20ahdOLXLit/dAkD06ox
+8Uf7taPlXkDRtqPF5c9fJnurF6uRii/52Ao0VPZqY1Kb13CLNEYUKnsJQASgGm/T
+Syr1Epwi2zvOsn4V8PNOPuR/toq/aAS1nF27j6XIKZVfW8bk34ScgHQaNR39lKyG
+hYkukH9ZtpwG5TX///+1U9k+td2u/gZPZnHgT/f8wYW1e4VDIs9b9pSFplmyXf4p
+T4ycHpLODzMgGUlZVDZsxOn5ZhsgbLJvPiQ5b5H7tNiTUMDCl97pk16XIAVKCRM=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert89[] = {
+ 0x30, 0x82, 0x05, 0xfb, 0x30, 0x82, 0x04, 0xe3, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x71, 0x63, 0x66, 0x35, 0xeb, 0xf3, 0x82, 0x3d, 0x7e,
+ 0x13, 0x09, 0x59, 0xa2, 0xd8, 0xe5, 0xde, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x31, 0x30, 0x31, 0x32, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x31, 0x30, 0x31, 0x31,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x1b, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x12, 0x4f, 0x72,
+ 0x61, 0x63, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20,
+ 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+ 0x6b, 0x31, 0x26, 0x30, 0x24, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1d,
+ 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x4d, 0x50, 0x4b, 0x49,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53, 0x65, 0x72, 0x76,
+ 0x65, 0x72, 0x20, 0x43, 0x41, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x0d, 0x4f, 0x72, 0x61, 0x63, 0x6c, 0x65, 0x20, 0x53,
+ 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01,
+ 0x01, 0x00, 0xa4, 0x3e, 0x5a, 0x70, 0x29, 0xb8, 0xf1, 0x9f, 0x22, 0xb9,
+ 0x21, 0x8e, 0xc5, 0x52, 0xac, 0x32, 0xaa, 0xbf, 0x1a, 0x1b, 0x29, 0xf3,
+ 0x3d, 0x79, 0xea, 0x6a, 0x1c, 0x29, 0x48, 0xf4, 0x6f, 0xfe, 0x86, 0x31,
+ 0x23, 0x3d, 0x07, 0x23, 0x17, 0x05, 0x3b, 0x5e, 0x04, 0xfd, 0x3e, 0x5c,
+ 0x1a, 0x6d, 0x63, 0x61, 0x64, 0x90, 0x49, 0x67, 0xde, 0x69, 0x8f, 0xf4,
+ 0x72, 0xa7, 0x23, 0x87, 0x1a, 0x66, 0x98, 0xda, 0x07, 0x3c, 0x21, 0x2c,
+ 0xac, 0x86, 0x8d, 0x11, 0x7d, 0x40, 0x98, 0x40, 0x26, 0x59, 0xa9, 0xc5,
+ 0xaf, 0xaa, 0xf2, 0xe9, 0xd7, 0x11, 0x91, 0x9b, 0xf1, 0xd6, 0x6f, 0xcd,
+ 0x65, 0x63, 0x3d, 0x8f, 0x76, 0xa1, 0x99, 0x2f, 0xc6, 0x3f, 0x9d, 0xfa,
+ 0x57, 0x82, 0xb1, 0xff, 0x11, 0x0b, 0xc4, 0xec, 0x84, 0xd2, 0xd4, 0x47,
+ 0xef, 0x2c, 0xbf, 0x90, 0xeb, 0x61, 0x95, 0xee, 0xeb, 0x17, 0xc0, 0x43,
+ 0xd6, 0x83, 0x67, 0x7b, 0x54, 0x80, 0xf4, 0x0d, 0x06, 0x9f, 0x0a, 0xed,
+ 0xd9, 0xde, 0x5c, 0x66, 0xfd, 0x49, 0xa6, 0xe8, 0x3f, 0x96, 0x3c, 0xfa,
+ 0xc9, 0x46, 0x96, 0x65, 0xaf, 0x82, 0x73, 0x26, 0xe0, 0x94, 0x0b, 0xbd,
+ 0x99, 0xc0, 0xb5, 0x61, 0xa6, 0xec, 0xdc, 0xbe, 0x57, 0xd4, 0x57, 0x91,
+ 0xca, 0x18, 0x0e, 0x2c, 0xcc, 0x0c, 0x8a, 0xe0, 0xa4, 0x7c, 0xa3, 0xe5,
+ 0x7c, 0x0c, 0x3e, 0x97, 0xdf, 0x62, 0xb9, 0x80, 0xa5, 0x11, 0x35, 0xdb,
+ 0x6b, 0xfb, 0x91, 0x45, 0x3c, 0x2f, 0x48, 0xe9, 0x58, 0x05, 0x6d, 0x8e,
+ 0xcd, 0x04, 0x72, 0x2e, 0x04, 0xa2, 0xae, 0x18, 0x66, 0x79, 0xe9, 0x38,
+ 0xf7, 0x78, 0xec, 0x62, 0xaf, 0xeb, 0xa6, 0xf8, 0xc5, 0x4a, 0x7c, 0x58,
+ 0x85, 0x60, 0x7d, 0x20, 0x6a, 0x7d, 0x84, 0xc6, 0x32, 0x3a, 0x66, 0xea,
+ 0x33, 0xec, 0xe8, 0x4d, 0x93, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x02, 0x18, 0x30, 0x82, 0x02, 0x14, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x01, 0x30, 0x44, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x3d, 0x30, 0x3b,
+ 0x30, 0x39, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+ 0x07, 0x17, 0x03, 0x30, 0x2a, 0x30, 0x28, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x30,
+ 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29,
+ 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67,
+ 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11,
+ 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18,
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x56, 0x65, 0x72, 0x69, 0x53,
+ 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b, 0x49, 0x2d, 0x32, 0x2d, 0x32, 0x30,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xcc,
+ 0xf8, 0xbb, 0x65, 0x47, 0x6a, 0x52, 0x16, 0xc4, 0xec, 0x7e, 0x9b, 0x27,
+ 0x9c, 0xfc, 0x2e, 0xa9, 0xc2, 0xf0, 0x0f, 0x30, 0x81, 0xf1, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x81, 0xe9, 0x30, 0x81, 0xe6, 0xa1, 0x81, 0xd0,
+ 0xa4, 0x81, 0xcd, 0x30, 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06,
+ 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69,
+ 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53,
+ 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65,
+ 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x39,
+ 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x3c, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+ 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20,
+ 0x2d, 0x20, 0x47, 0x33, 0x82, 0x11, 0x00, 0x9b, 0x7e, 0x06, 0x49, 0xa3,
+ 0x3e, 0x62, 0xb9, 0xd5, 0xee, 0x90, 0x48, 0x71, 0x29, 0xef, 0x57, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x47, 0x8b, 0x0e, 0x12, 0x54,
+ 0xa1, 0xce, 0xca, 0x59, 0xad, 0xc1, 0x81, 0x65, 0x66, 0x2f, 0x8f, 0x5b,
+ 0x71, 0xc5, 0x86, 0xa3, 0x90, 0xa3, 0x7b, 0xe0, 0x7c, 0xf1, 0x60, 0x1a,
+ 0x81, 0x87, 0x7f, 0xdf, 0xc1, 0x7e, 0x9c, 0xa0, 0x5b, 0xd4, 0xdb, 0xc0,
+ 0xbc, 0xac, 0x78, 0x4d, 0x51, 0x59, 0x9d, 0x28, 0x24, 0xdb, 0x46, 0xa1,
+ 0x74, 0xe2, 0xd7, 0x2e, 0x2b, 0x7f, 0x74, 0x09, 0x03, 0xd3, 0xaa, 0x31,
+ 0xf1, 0x47, 0xfb, 0xb5, 0xa3, 0xe5, 0x5e, 0x40, 0xd1, 0xb6, 0xa3, 0xc5,
+ 0xe5, 0xcf, 0x5f, 0x26, 0x7b, 0xab, 0x17, 0xab, 0x91, 0x8a, 0x2f, 0xf9,
+ 0xd8, 0x0a, 0x34, 0x54, 0xf6, 0x6a, 0x63, 0x52, 0x9b, 0xd7, 0x70, 0x8b,
+ 0x34, 0x46, 0x14, 0x2a, 0x7b, 0x09, 0x40, 0x04, 0xa0, 0x1a, 0x6f, 0xd3,
+ 0x4b, 0x2a, 0xf5, 0x12, 0x9c, 0x22, 0xdb, 0x3b, 0xce, 0xb2, 0x7e, 0x15,
+ 0xf0, 0xf3, 0x4e, 0x3e, 0xe4, 0x7f, 0xb6, 0x8a, 0xbf, 0x68, 0x04, 0xb5,
+ 0x9c, 0x5d, 0xbb, 0x8f, 0xa5, 0xc8, 0x29, 0x95, 0x5f, 0x5b, 0xc6, 0xe4,
+ 0xdf, 0x84, 0x9c, 0x80, 0x74, 0x1a, 0x35, 0x1d, 0xfd, 0x94, 0xac, 0x86,
+ 0x85, 0x89, 0x2e, 0x90, 0x7f, 0x59, 0xb6, 0x9c, 0x06, 0xe5, 0x35, 0xff,
+ 0xff, 0xff, 0xb5, 0x53, 0xd9, 0x3e, 0xb5, 0xdd, 0xae, 0xfe, 0x06, 0x4f,
+ 0x66, 0x71, 0xe0, 0x4f, 0xf7, 0xfc, 0xc1, 0x85, 0xb5, 0x7b, 0x85, 0x43,
+ 0x22, 0xcf, 0x5b, 0xf6, 0x94, 0x85, 0xa6, 0x59, 0xb2, 0x5d, 0xfe, 0x29,
+ 0x4f, 0x8c, 0x9c, 0x1e, 0x92, 0xce, 0x0f, 0x33, 0x20, 0x19, 0x49, 0x59,
+ 0x54, 0x36, 0x6c, 0xc4, 0xe9, 0xf9, 0x66, 0x1b, 0x20, 0x6c, 0xb2, 0x6f,
+ 0x3e, 0x24, 0x39, 0x6f, 0x91, 0xfb, 0xb4, 0xd8, 0x93, 0x50, 0xc0, 0xc2,
+ 0x97, 0xde, 0xe9, 0x93, 0x5e, 0x97, 0x20, 0x05, 0x4a, 0x09, 0x13,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 61:5d:aa:d2:00:06:00:00:00:40
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Microsoft Internet Authority
+ Validity
+ Not Before: May 15 20:40:55 2012 GMT
+ Not After : May 15 20:50:55 2016 GMT
+ Subject: DC=com, DC=microsoft, DC=corp, DC=redmond, CN=MSIT Machine Auth CA 2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bd:c8:e8:00:eb:58:69:29:11:84:87:1c:9f:87:
+ 4d:44:3d:38:1b:c7:13:93:e7:14:4c:17:2b:db:75:
+ 08:c0:c9:21:ca:ae:e0:1f:e8:8c:fe:a1:df:24:8c:
+ bf:02:9c:ed:99:be:3a:53:2a:45:4e:b0:48:78:37:
+ dc:a1:63:ef:03:b7:94:29:66:6c:66:d7:6c:6a:48:
+ 65:b2:dd:47:21:23:1b:b8:41:74:2f:96:dd:98:22:
+ b9:fa:f3:0e:4a:b0:2f:0b:a2:de:b7:02:13:42:70:
+ 3f:78:04:14:72:e3:3a:2b:7e:28:48:1d:96:b4:db:
+ 16:39:8d:b3:c4:59:a1:d7:a2:d2:64:61:33:2b:41:
+ 18:c5:ab:95:6f:5d:22:07:77:cd:53:9d:03:49:65:
+ d5:88:f5:5f:9d:f0:c4:69:4f:91:08:a5:39:07:96:
+ 36:af:2d:64:dc:26:5a:c1:13:ee:31:39:d6:5f:dc:
+ 97:fc:27:aa:05:78:47:c5:22:26:63:53:7e:37:c2:
+ 7b:64:3d:69:cb:f0:fb:8a:15:3e:52:b3:86:6a:b4:
+ c1:1c:3b:b2:f5:c7:3e:c5:76:dd:74:68:76:7a:55:
+ e6:80:7b:2e:8c:a6:da:bb:91:5a:07:cd:19:4a:ea:
+ 08:5c:ff:c1:49:d6:7b:06:bf:eb:b7:4a:9f:b4:27:
+ 9c:17
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ 1.3.6.1.4.1.311.21.1:
+ .....
+ 1.3.6.1.4.1.311.21.2:
+ ..#...h..f...@z.g.3...
+ X509v3 Subject Key Identifier:
+ EB:DB:11:5E:F8:09:9E:D8:D6:62:9C:FD:62:9D:E3:84:4A:28:E1:27
+ 1.3.6.1.4.1.311.20.2:
+ .
+.S.u.b.C.A
+ X509v3 Key Usage:
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Authority Key Identifier:
+ keyid:2A:4D:97:95:5D:34:7E:9D:B6:E6:33:BE:9C:27:C1:70:7E:67:DB:C1
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://mscrl.microsoft.com/pki/mscorp/crl/mswww(6).crl
+ URI:http://crl.microsoft.com/pki/mscorp/crl/mswww(6).crl
+ URI:http://corppki/crl/mswww(6).crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://www.microsoft.com/pki/mscorp/mswww(6).crt
+ CA Issuers - URI:http://corppki/aia/mswww(6).crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ a3:36:72:f7:45:0b:a7:86:37:de:20:8d:f5:d7:8e:da:89:00:
+ 7a:52:4b:85:32:b2:32:d7:ed:80:57:fd:0d:51:1c:d1:1e:3d:
+ 0c:3e:91:2a:30:e2:53:bc:61:07:89:02:11:b9:1b:83:48:d3:
+ 1a:c7:47:96:62:b7:cf:de:d7:0d:b8:9d:84:8e:de:d5:e6:e4:
+ b9:c0:06:e3:1c:f4:31:43:1f:53:fc:4a:42:b5:9b:23:cd:b2:
+ ec:0d:0f:81:89:ff:59:a9:00:d3:04:4a:0b:af:5b:84:f5:3b:
+ 4f:b9:37:91:88:21:e4:9c:ca:52:76:63:7e:88:7a:65:b4:8e:
+ 16:6a:f4:60:bd:2c:0e:ef:17:86:2b:75:09:58:73:c5:4f:b9:
+ a8:1b:ef:2a:f4:b6:a3:b9:07:f0:a4:90:39:53:df:e1:ba:02:
+ 98:a5:a5:82:11:a1:06:53:b2:c2:eb:fd:ea:67:72:37:63:d0:
+ 85:cf:86:05:c8:c3:73:c1:db:f3:c4:d2:55:ff:a0:1d:e7:72:
+ 14:1a:ff:b2:ff:08:42:48:40:0a:19:82:cf:22:f3:05:c5:d5:
+ df:0b:29:c4:3a:0e:c5:32:38:ef:2f:94:9a:0c:f2:d4:ee:bf:
+ 62:c7:e2:a4:5f:3d:6e:78:bc:10:c1:2e:4a:30:f2:87:db:89:
+ 38:be:fe:cc:80:8f:f5:f9:5c:cb:5c:f1:ea:02:08:6b:a5:0b:
+ ef:20:5c:a3:34:cc:f0:80:b6:1f:63:b9:44:32:1c:26:41:9b:
+ dd:93:a3:64:01:57:11:21:43:94:2a:57:2d:8a:4a:cb:8a:28:
+ 40:0a:fb:50:f9:af:26:74:13:97:82:4e:6c:64:eb:d1:c6:bf:
+ 5e:fd:25:da:05:46:4a:ae:c7:2f:c7:04:ef:2e:71:2e:e2:a8:
+ 5b:a4:ea:b2:6f:a8:91:35:c4:b7:63:17:62:0e:27:8e:3c:24:
+ cd:3d:45:69:05:dc:52:c5:35:f8:11:c0:1d:df:62:60:f4:e1:
+ a6:5e:70:b8:45:74:03:ab:d1:16:74:e3:9e:d3:c1:a3:e8:90:
+ 96:8a:8a:c2:46:46:9d:b9:5c:6c:02:1d:32:84:eb:14:85:76:
+ 95:aa:4d:2d:69:b6:02:f6:fe:ed:34:d5:8c:e6:fa:ac:5d:dc:
+ 03:40:e6:cf:77:89:ff:b1:28:ca:86:8c:c8:e7:31:47:fc:16:
+ fe:54:0c:f5:26:b1:7e:dc:98:26:70:58:26:13:5c:c7:75:db:
+ 12:de:4c:ac:ff:9a:0c:ea:a2:c2:1c:41:04:8c:e6:47:97:47:
+ 6f:89:c5:48:de:37:0d:6a:d9:f0:68:24:5c:ff:19:59:e6:e1:
+ 70:37:38:0d:db:ee:b0:e2
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIKYV2q0gAGAAAAQDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
+VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTEyMDUxNTIwNDA1
+NVoXDTE2MDUxNTIwNTA1NVowgYAxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
+kiaJk/IsZAEZFgltaWNyb3NvZnQxFDASBgoJkiaJk/IsZAEZFgRjb3JwMRcwFQYK
+CZImiZPyLGQBGRYHcmVkbW9uZDEfMB0GA1UEAxMWTVNJVCBNYWNoaW5lIEF1dGgg
+Q0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3I6ADrWGkpEYSH
+HJ+HTUQ9OBvHE5PnFEwXK9t1CMDJIcqu4B/ojP6h3ySMvwKc7Zm+OlMqRU6wSHg3
+3KFj7wO3lClmbGbXbGpIZbLdRyEjG7hBdC+W3ZgiufrzDkqwLwui3rcCE0JwP3gE
+FHLjOit+KEgdlrTbFjmNs8RZodei0mRhMytBGMWrlW9dIgd3zVOdA0ll1Yj1X53w
+xGlPkQilOQeWNq8tZNwmWsET7jE51l/cl/wnqgV4R8UiJmNTfjfCe2Q9acvw+4oV
+PlKzhmq0wRw7svXHPsV23XRodnpV5oB7Loym2ruRWgfNGUrqCFz/wUnWewa/67dK
+n7QnnBcCAwEAAaOCAdowggHWMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGC
+NxUCBBYEFCO30O1oke9mm+EFQHq+Z8oz1ga9MB0GA1UdDgQWBBTr2xFe+Ame2NZi
+nP1ineOESijhJzAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
+AYYwEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBQqTZeVXTR+nbbmM76c
+J8FwfmfbwTCBowYDVR0fBIGbMIGYMIGVoIGSoIGPhjZodHRwOi8vbXNjcmwubWlj
+cm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9tc3d3dyg2KS5jcmyGNGh0dHA6Ly9j
+cmwubWljcm9zb2Z0LmNvbS9wa2kvbXNjb3JwL2NybC9tc3d3dyg2KS5jcmyGH2h0
+dHA6Ly9jb3JwcGtpL2NybC9tc3d3dyg2KS5jcmwweQYIKwYBBQUHAQEEbTBrMDwG
+CCsGAQUFBzAChjBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL21zY29ycC9t
+c3d3dyg2KS5jcnQwKwYIKwYBBQUHMAKGH2h0dHA6Ly9jb3JwcGtpL2FpYS9tc3d3
+dyg2KS5jcnQwDQYJKoZIhvcNAQEFBQADggIBAKM2cvdFC6eGN94gjfXXjtqJAHpS
+S4UysjLX7YBX/Q1RHNEePQw+kSow4lO8YQeJAhG5G4NI0xrHR5Zit8/e1w24nYSO
+3tXm5LnABuMc9DFDH1P8SkK1myPNsuwND4GJ/1mpANMESguvW4T1O0+5N5GIIeSc
+ylJ2Y36IemW0jhZq9GC9LA7vF4YrdQlYc8VPuagb7yr0tqO5B/CkkDlT3+G6Apil
+pYIRoQZTssLr/epncjdj0IXPhgXIw3PB2/PE0lX/oB3nchQa/7L/CEJIQAoZgs8i
+8wXF1d8LKcQ6DsUyOO8vlJoM8tTuv2LH4qRfPW54vBDBLkow8ofbiTi+/syAj/X5
+XMtc8eoCCGulC+8gXKM0zPCAth9juUQyHCZBm92To2QBVxEhQ5QqVy2KSsuKKEAK
++1D5ryZ0E5eCTmxk69HGv179JdoFRkquxy/HBO8ucS7iqFuk6rJvqJE1xLdjF2IO
+J448JM09RWkF3FLFNfgRwB3fYmD04aZecLhFdAOr0RZ0457TwaPokJaKisJGRp25
+XGwCHTKE6xSFdpWqTS1ptgL2/u001Yzm+qxd3ANA5s93if+xKMqGjMjnMUf8Fv5U
+DPUmsX7cmCZwWCYTXMd12xLeTKz/mgzqosIcQQSM5keXR2+JxUjeNw1q2fBoJFz/
+GVnm4XA3OA3b7rDi
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert90[] = {
+ 0x30, 0x82, 0x06, 0x08, 0x30, 0x82, 0x03, 0xf0, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0a, 0x61, 0x5d, 0xaa, 0xd2, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x40, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x32, 0x30, 0x35, 0x31, 0x35, 0x32, 0x30, 0x34, 0x30, 0x35,
+ 0x35, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x30, 0x35, 0x31, 0x35, 0x32, 0x30,
+ 0x35, 0x30, 0x35, 0x35, 0x5a, 0x30, 0x81, 0x80, 0x31, 0x13, 0x30, 0x11,
+ 0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+ 0x16, 0x03, 0x63, 0x6f, 0x6d, 0x31, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x09,
+ 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x09, 0x6d,
+ 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x31, 0x14, 0x30, 0x12,
+ 0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+ 0x16, 0x04, 0x63, 0x6f, 0x72, 0x70, 0x31, 0x17, 0x30, 0x15, 0x06, 0x0a,
+ 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x07,
+ 0x72, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1f, 0x30, 0x1d, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x16, 0x4d, 0x53, 0x49, 0x54, 0x20, 0x4d,
+ 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20,
+ 0x43, 0x41, 0x20, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xbd, 0xc8, 0xe8, 0x00, 0xeb, 0x58, 0x69, 0x29, 0x11, 0x84, 0x87,
+ 0x1c, 0x9f, 0x87, 0x4d, 0x44, 0x3d, 0x38, 0x1b, 0xc7, 0x13, 0x93, 0xe7,
+ 0x14, 0x4c, 0x17, 0x2b, 0xdb, 0x75, 0x08, 0xc0, 0xc9, 0x21, 0xca, 0xae,
+ 0xe0, 0x1f, 0xe8, 0x8c, 0xfe, 0xa1, 0xdf, 0x24, 0x8c, 0xbf, 0x02, 0x9c,
+ 0xed, 0x99, 0xbe, 0x3a, 0x53, 0x2a, 0x45, 0x4e, 0xb0, 0x48, 0x78, 0x37,
+ 0xdc, 0xa1, 0x63, 0xef, 0x03, 0xb7, 0x94, 0x29, 0x66, 0x6c, 0x66, 0xd7,
+ 0x6c, 0x6a, 0x48, 0x65, 0xb2, 0xdd, 0x47, 0x21, 0x23, 0x1b, 0xb8, 0x41,
+ 0x74, 0x2f, 0x96, 0xdd, 0x98, 0x22, 0xb9, 0xfa, 0xf3, 0x0e, 0x4a, 0xb0,
+ 0x2f, 0x0b, 0xa2, 0xde, 0xb7, 0x02, 0x13, 0x42, 0x70, 0x3f, 0x78, 0x04,
+ 0x14, 0x72, 0xe3, 0x3a, 0x2b, 0x7e, 0x28, 0x48, 0x1d, 0x96, 0xb4, 0xdb,
+ 0x16, 0x39, 0x8d, 0xb3, 0xc4, 0x59, 0xa1, 0xd7, 0xa2, 0xd2, 0x64, 0x61,
+ 0x33, 0x2b, 0x41, 0x18, 0xc5, 0xab, 0x95, 0x6f, 0x5d, 0x22, 0x07, 0x77,
+ 0xcd, 0x53, 0x9d, 0x03, 0x49, 0x65, 0xd5, 0x88, 0xf5, 0x5f, 0x9d, 0xf0,
+ 0xc4, 0x69, 0x4f, 0x91, 0x08, 0xa5, 0x39, 0x07, 0x96, 0x36, 0xaf, 0x2d,
+ 0x64, 0xdc, 0x26, 0x5a, 0xc1, 0x13, 0xee, 0x31, 0x39, 0xd6, 0x5f, 0xdc,
+ 0x97, 0xfc, 0x27, 0xaa, 0x05, 0x78, 0x47, 0xc5, 0x22, 0x26, 0x63, 0x53,
+ 0x7e, 0x37, 0xc2, 0x7b, 0x64, 0x3d, 0x69, 0xcb, 0xf0, 0xfb, 0x8a, 0x15,
+ 0x3e, 0x52, 0xb3, 0x86, 0x6a, 0xb4, 0xc1, 0x1c, 0x3b, 0xb2, 0xf5, 0xc7,
+ 0x3e, 0xc5, 0x76, 0xdd, 0x74, 0x68, 0x76, 0x7a, 0x55, 0xe6, 0x80, 0x7b,
+ 0x2e, 0x8c, 0xa6, 0xda, 0xbb, 0x91, 0x5a, 0x07, 0xcd, 0x19, 0x4a, 0xea,
+ 0x08, 0x5c, 0xff, 0xc1, 0x49, 0xd6, 0x7b, 0x06, 0xbf, 0xeb, 0xb7, 0x4a,
+ 0x9f, 0xb4, 0x27, 0x9c, 0x17, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x01, 0xda, 0x30, 0x82, 0x01, 0xd6, 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06,
+ 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x05, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0x30, 0x23, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82,
+ 0x37, 0x15, 0x02, 0x04, 0x16, 0x04, 0x14, 0x23, 0xb7, 0xd0, 0xed, 0x68,
+ 0x91, 0xef, 0x66, 0x9b, 0xe1, 0x05, 0x40, 0x7a, 0xbe, 0x67, 0xca, 0x33,
+ 0xd6, 0x06, 0xbd, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, 0xeb, 0xdb, 0x11, 0x5e, 0xf8, 0x09, 0x9e, 0xd8, 0xd6, 0x62,
+ 0x9c, 0xfd, 0x62, 0x9d, 0xe3, 0x84, 0x4a, 0x28, 0xe1, 0x27, 0x30, 0x19,
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04,
+ 0x0c, 0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00,
+ 0x41, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x86, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x1f,
+ 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x2a,
+ 0x4d, 0x97, 0x95, 0x5d, 0x34, 0x7e, 0x9d, 0xb6, 0xe6, 0x33, 0xbe, 0x9c,
+ 0x27, 0xc1, 0x70, 0x7e, 0x67, 0xdb, 0xc1, 0x30, 0x81, 0xa3, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x30, 0x81, 0x95,
+ 0xa0, 0x81, 0x92, 0xa0, 0x81, 0x8f, 0x86, 0x36, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6d, 0x73, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63,
+ 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+ 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63,
+ 0x72, 0x6c, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63,
+ 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77,
+ 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x6c, 0x86, 0x1f, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70, 0x6b, 0x69,
+ 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x36,
+ 0x29, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x79, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6d, 0x30, 0x6b, 0x30, 0x3c, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x30, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69,
+ 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x6d,
+ 0x73, 0x77, 0x77, 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30,
+ 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
+ 0x1f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70,
+ 0x70, 0x6b, 0x69, 0x2f, 0x61, 0x69, 0x61, 0x2f, 0x6d, 0x73, 0x77, 0x77,
+ 0x77, 0x28, 0x36, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x02, 0x01, 0x00, 0xa3, 0x36, 0x72, 0xf7, 0x45, 0x0b, 0xa7, 0x86,
+ 0x37, 0xde, 0x20, 0x8d, 0xf5, 0xd7, 0x8e, 0xda, 0x89, 0x00, 0x7a, 0x52,
+ 0x4b, 0x85, 0x32, 0xb2, 0x32, 0xd7, 0xed, 0x80, 0x57, 0xfd, 0x0d, 0x51,
+ 0x1c, 0xd1, 0x1e, 0x3d, 0x0c, 0x3e, 0x91, 0x2a, 0x30, 0xe2, 0x53, 0xbc,
+ 0x61, 0x07, 0x89, 0x02, 0x11, 0xb9, 0x1b, 0x83, 0x48, 0xd3, 0x1a, 0xc7,
+ 0x47, 0x96, 0x62, 0xb7, 0xcf, 0xde, 0xd7, 0x0d, 0xb8, 0x9d, 0x84, 0x8e,
+ 0xde, 0xd5, 0xe6, 0xe4, 0xb9, 0xc0, 0x06, 0xe3, 0x1c, 0xf4, 0x31, 0x43,
+ 0x1f, 0x53, 0xfc, 0x4a, 0x42, 0xb5, 0x9b, 0x23, 0xcd, 0xb2, 0xec, 0x0d,
+ 0x0f, 0x81, 0x89, 0xff, 0x59, 0xa9, 0x00, 0xd3, 0x04, 0x4a, 0x0b, 0xaf,
+ 0x5b, 0x84, 0xf5, 0x3b, 0x4f, 0xb9, 0x37, 0x91, 0x88, 0x21, 0xe4, 0x9c,
+ 0xca, 0x52, 0x76, 0x63, 0x7e, 0x88, 0x7a, 0x65, 0xb4, 0x8e, 0x16, 0x6a,
+ 0xf4, 0x60, 0xbd, 0x2c, 0x0e, 0xef, 0x17, 0x86, 0x2b, 0x75, 0x09, 0x58,
+ 0x73, 0xc5, 0x4f, 0xb9, 0xa8, 0x1b, 0xef, 0x2a, 0xf4, 0xb6, 0xa3, 0xb9,
+ 0x07, 0xf0, 0xa4, 0x90, 0x39, 0x53, 0xdf, 0xe1, 0xba, 0x02, 0x98, 0xa5,
+ 0xa5, 0x82, 0x11, 0xa1, 0x06, 0x53, 0xb2, 0xc2, 0xeb, 0xfd, 0xea, 0x67,
+ 0x72, 0x37, 0x63, 0xd0, 0x85, 0xcf, 0x86, 0x05, 0xc8, 0xc3, 0x73, 0xc1,
+ 0xdb, 0xf3, 0xc4, 0xd2, 0x55, 0xff, 0xa0, 0x1d, 0xe7, 0x72, 0x14, 0x1a,
+ 0xff, 0xb2, 0xff, 0x08, 0x42, 0x48, 0x40, 0x0a, 0x19, 0x82, 0xcf, 0x22,
+ 0xf3, 0x05, 0xc5, 0xd5, 0xdf, 0x0b, 0x29, 0xc4, 0x3a, 0x0e, 0xc5, 0x32,
+ 0x38, 0xef, 0x2f, 0x94, 0x9a, 0x0c, 0xf2, 0xd4, 0xee, 0xbf, 0x62, 0xc7,
+ 0xe2, 0xa4, 0x5f, 0x3d, 0x6e, 0x78, 0xbc, 0x10, 0xc1, 0x2e, 0x4a, 0x30,
+ 0xf2, 0x87, 0xdb, 0x89, 0x38, 0xbe, 0xfe, 0xcc, 0x80, 0x8f, 0xf5, 0xf9,
+ 0x5c, 0xcb, 0x5c, 0xf1, 0xea, 0x02, 0x08, 0x6b, 0xa5, 0x0b, 0xef, 0x20,
+ 0x5c, 0xa3, 0x34, 0xcc, 0xf0, 0x80, 0xb6, 0x1f, 0x63, 0xb9, 0x44, 0x32,
+ 0x1c, 0x26, 0x41, 0x9b, 0xdd, 0x93, 0xa3, 0x64, 0x01, 0x57, 0x11, 0x21,
+ 0x43, 0x94, 0x2a, 0x57, 0x2d, 0x8a, 0x4a, 0xcb, 0x8a, 0x28, 0x40, 0x0a,
+ 0xfb, 0x50, 0xf9, 0xaf, 0x26, 0x74, 0x13, 0x97, 0x82, 0x4e, 0x6c, 0x64,
+ 0xeb, 0xd1, 0xc6, 0xbf, 0x5e, 0xfd, 0x25, 0xda, 0x05, 0x46, 0x4a, 0xae,
+ 0xc7, 0x2f, 0xc7, 0x04, 0xef, 0x2e, 0x71, 0x2e, 0xe2, 0xa8, 0x5b, 0xa4,
+ 0xea, 0xb2, 0x6f, 0xa8, 0x91, 0x35, 0xc4, 0xb7, 0x63, 0x17, 0x62, 0x0e,
+ 0x27, 0x8e, 0x3c, 0x24, 0xcd, 0x3d, 0x45, 0x69, 0x05, 0xdc, 0x52, 0xc5,
+ 0x35, 0xf8, 0x11, 0xc0, 0x1d, 0xdf, 0x62, 0x60, 0xf4, 0xe1, 0xa6, 0x5e,
+ 0x70, 0xb8, 0x45, 0x74, 0x03, 0xab, 0xd1, 0x16, 0x74, 0xe3, 0x9e, 0xd3,
+ 0xc1, 0xa3, 0xe8, 0x90, 0x96, 0x8a, 0x8a, 0xc2, 0x46, 0x46, 0x9d, 0xb9,
+ 0x5c, 0x6c, 0x02, 0x1d, 0x32, 0x84, 0xeb, 0x14, 0x85, 0x76, 0x95, 0xaa,
+ 0x4d, 0x2d, 0x69, 0xb6, 0x02, 0xf6, 0xfe, 0xed, 0x34, 0xd5, 0x8c, 0xe6,
+ 0xfa, 0xac, 0x5d, 0xdc, 0x03, 0x40, 0xe6, 0xcf, 0x77, 0x89, 0xff, 0xb1,
+ 0x28, 0xca, 0x86, 0x8c, 0xc8, 0xe7, 0x31, 0x47, 0xfc, 0x16, 0xfe, 0x54,
+ 0x0c, 0xf5, 0x26, 0xb1, 0x7e, 0xdc, 0x98, 0x26, 0x70, 0x58, 0x26, 0x13,
+ 0x5c, 0xc7, 0x75, 0xdb, 0x12, 0xde, 0x4c, 0xac, 0xff, 0x9a, 0x0c, 0xea,
+ 0xa2, 0xc2, 0x1c, 0x41, 0x04, 0x8c, 0xe6, 0x47, 0x97, 0x47, 0x6f, 0x89,
+ 0xc5, 0x48, 0xde, 0x37, 0x0d, 0x6a, 0xd9, 0xf0, 0x68, 0x24, 0x5c, 0xff,
+ 0x19, 0x59, 0xe6, 0xe1, 0x70, 0x37, 0x38, 0x0d, 0xdb, 0xee, 0xb0, 0xe2,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 11:2a:00:6d:37:e5:10:6f:d6:ca:7c:c3:ef:ba:cc:18
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2016 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL SGC CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bd:56:88:ba:88:34:64:64:cf:cd:ca:b0:ee:e7:
+ 19:73:c5:72:d9:bb:45:bc:b5:a8:ff:83:be:1c:03:
+ db:ed:89:b7:2e:10:1a:25:bc:55:ca:41:a1:9f:0b:
+ cf:19:5e:70:b9:5e:39:4b:9e:31:1c:5f:87:ae:2a:
+ aa:a8:2b:a2:1b:3b:10:23:5f:13:b1:dd:08:8c:4e:
+ 14:da:83:81:e3:b5:8c:e3:68:ed:24:67:ce:56:b6:
+ ac:9b:73:96:44:db:8a:8c:b3:d6:f0:71:93:8e:db:
+ 71:54:4a:eb:73:59:6a:8f:70:51:2c:03:9f:97:d1:
+ cc:11:7a:bc:62:0d:95:2a:c9:1c:75:57:e9:f5:c7:
+ ea:ba:84:35:cb:c7:85:5a:7e:e4:4d:e1:11:97:7d:
+ 0e:20:34:45:db:f1:a2:09:eb:eb:3d:9e:b8:96:43:
+ 5e:34:4b:08:25:1e:43:1a:a2:d9:b7:8a:01:34:3d:
+ c3:f8:e5:af:4f:8c:ff:cd:65:f0:23:4e:c5:97:b3:
+ 5c:da:90:1c:82:85:0d:06:0d:c1:22:b6:7b:28:a4:
+ 03:c3:4c:53:d1:58:bc:72:bc:08:39:fc:a0:76:a8:
+ a8:e9:4b:6e:88:3d:e3:b3:31:25:8c:73:29:48:0e:
+ 32:79:06:ed:3d:43:f4:f6:e4:e9:fc:7d:be:8e:08:
+ d5:1f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 4E:43:C8:1D:76:EF:37:53:7A:4F:F2:58:6F:94:F3:38:E2:D5:BD:DF
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Subject Alternative Name:
+ DirName:/CN=Class3CA2048-1-48
+ Authority Information Access:
+ OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 5a:a2:b1:bf:eb:8d:d4:38:a8:80:72:c2:dc:38:2e:ac:a7:71:
+ f9:2b:a3:bb:47:bb:6d:69:6f:10:36:98:8c:c7:56:2e:bb:bc:
+ ab:4a:9b:7a:d6:f2:82:93:e0:14:fe:8a:ce:83:b7:83:db:93:
+ 87:ab:ac:65:79:49:fd:57:a9:b1:ce:09:1f:ba:10:15:c4:09:
+ 0e:62:e3:f9:0a:25:d5:64:98:f0:f2:a8:0f:76:32:7e:91:e6:
+ 18:ee:bc:e7:da:d0:4e:8d:78:bb:e2:9d:c0:59:2b:c0:ce:95:
+ 0d:24:0c:72:ca:34:5e:70:22:89:2b:4a:b0:f1:68:87:f3:ee:
+ 44:8d:28:40:77:39:6e:48:72:45:31:5d:6b:39:0e:86:02:ea:
+ 66:99:93:31:0f:df:67:de:a6:9f:8c:9d:4c:ce:71:6f:3a:21:
+ f6:b9:34:3f:f9:6e:d8:9a:f7:3e:da:f3:81:5f:7a:5c:6d:8f:
+ 7c:f6:99:74:b7:ff:e4:17:5d:ed:61:5e:ab:48:bb:96:8d:66:
+ 45:39:b4:12:0a:f6:70:e9:9c:76:22:4b:60:e9:2a:1b:34:49:
+ f7:a2:d4:67:c0:b1:26:ad:13:ba:d9:84:01:c1:ab:e1:8e:6d:
+ 70:16:3b:77:ac:91:9a:bb:1a:1f:da:58:a7:e4:4f:c1:61:ae:
+ bc:a2:fe:4b
+-----BEGIN CERTIFICATE-----
+MIIGCjCCBPKgAwIBAgIQESoAbTflEG/WynzD77rMGDANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBvjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMvVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9Voi6iDRkZM/NyrDu5xlzxXLZ
+u0W8taj/g74cA9vtibcuEBolvFXKQaGfC88ZXnC5XjlLnjEcX4euKqqoK6IbOxAj
+XxOx3QiMThTag4HjtYzjaO0kZ85Wtqybc5ZE24qMs9bwcZOO23FUSutzWWqPcFEs
+A5+X0cwRerxiDZUqyRx1V+n1x+q6hDXLx4VafuRN4RGXfQ4gNEXb8aIJ6+s9nriW
+Q140SwglHkMaotm3igE0PcP45a9PjP/NZfAjTsWXs1zakByChQ0GDcEitnsopAPD
+TFPRWLxyvAg5/KB2qKjpS26IPeOzMSWMcylIDjJ5Bu09Q/T25On8fb6OCNUfAgMB
+AAGjggH0MIIB8DAdBgNVHQ4EFgQUTkPIHXbvN1N6T/JYb5TzOOLVvd8wEgYDVR0T
+AQH/BAgwBgEB/wIBADA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczA9BgNVHR8ENjA0MDKgMKAuhixo
+dHRwOi8vRVZTZWN1cmUtY3JsLnZlcmlzaWduLmNvbS9wY2EzLWc1LmNybDAgBgNV
+HSUEGTAXBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwDgYDVR0PAQH/BAQDAgEGMBEG
+CWCGSAGG+EIBAQQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFn
+ZS9naWYwITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRw
+Oi8vbG9nby52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjApBgNVHREEIjAgpB4wHDEa
+MBgGA1UEAxMRQ2xhc3MzQ0EyMDQ4LTEtNDgwPQYIKwYBBQUHAQEEMTAvMC0GCCsG
+AQUFBzABhiFodHRwOi8vRVZTZWN1cmUtb2NzcC52ZXJpc2lnbi5jb20wHwYDVR0j
+BBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZIhvcNAQEFBQADggEBAFqi
+sb/rjdQ4qIBywtw4Lqyncfkro7tHu21pbxA2mIzHVi67vKtKm3rW8oKT4BT+is6D
+t4Pbk4errGV5Sf1XqbHOCR+6EBXECQ5i4/kKJdVkmPDyqA92Mn6R5hjuvOfa0E6N
+eLvincBZK8DOlQ0kDHLKNF5wIokrSrDxaIfz7kSNKEB3OW5IckUxXWs5DoYC6maZ
+kzEP32fepp+MnUzOcW86Ifa5ND/5btia9z7a84Ffelxtj3z2mXS3/+QXXe1hXqtI
+u5aNZkU5tBIK9nDpnHYiS2DpKhs0Sfei1GfAsSatE7rZhAHBq+GObXAWO3eskZq7
+Gh/aWKfkT8Fhrryi/ks=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert91[] = {
+ 0x30, 0x82, 0x06, 0x0a, 0x30, 0x82, 0x04, 0xf2, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x11, 0x2a, 0x00, 0x6d, 0x37, 0xe5, 0x10, 0x6f, 0xd6,
+ 0xca, 0x7c, 0xc3, 0xef, 0xba, 0xcc, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+ 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x38,
+ 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+ 0x4c, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x56, 0x88, 0xba, 0x88, 0x34, 0x64,
+ 0x64, 0xcf, 0xcd, 0xca, 0xb0, 0xee, 0xe7, 0x19, 0x73, 0xc5, 0x72, 0xd9,
+ 0xbb, 0x45, 0xbc, 0xb5, 0xa8, 0xff, 0x83, 0xbe, 0x1c, 0x03, 0xdb, 0xed,
+ 0x89, 0xb7, 0x2e, 0x10, 0x1a, 0x25, 0xbc, 0x55, 0xca, 0x41, 0xa1, 0x9f,
+ 0x0b, 0xcf, 0x19, 0x5e, 0x70, 0xb9, 0x5e, 0x39, 0x4b, 0x9e, 0x31, 0x1c,
+ 0x5f, 0x87, 0xae, 0x2a, 0xaa, 0xa8, 0x2b, 0xa2, 0x1b, 0x3b, 0x10, 0x23,
+ 0x5f, 0x13, 0xb1, 0xdd, 0x08, 0x8c, 0x4e, 0x14, 0xda, 0x83, 0x81, 0xe3,
+ 0xb5, 0x8c, 0xe3, 0x68, 0xed, 0x24, 0x67, 0xce, 0x56, 0xb6, 0xac, 0x9b,
+ 0x73, 0x96, 0x44, 0xdb, 0x8a, 0x8c, 0xb3, 0xd6, 0xf0, 0x71, 0x93, 0x8e,
+ 0xdb, 0x71, 0x54, 0x4a, 0xeb, 0x73, 0x59, 0x6a, 0x8f, 0x70, 0x51, 0x2c,
+ 0x03, 0x9f, 0x97, 0xd1, 0xcc, 0x11, 0x7a, 0xbc, 0x62, 0x0d, 0x95, 0x2a,
+ 0xc9, 0x1c, 0x75, 0x57, 0xe9, 0xf5, 0xc7, 0xea, 0xba, 0x84, 0x35, 0xcb,
+ 0xc7, 0x85, 0x5a, 0x7e, 0xe4, 0x4d, 0xe1, 0x11, 0x97, 0x7d, 0x0e, 0x20,
+ 0x34, 0x45, 0xdb, 0xf1, 0xa2, 0x09, 0xeb, 0xeb, 0x3d, 0x9e, 0xb8, 0x96,
+ 0x43, 0x5e, 0x34, 0x4b, 0x08, 0x25, 0x1e, 0x43, 0x1a, 0xa2, 0xd9, 0xb7,
+ 0x8a, 0x01, 0x34, 0x3d, 0xc3, 0xf8, 0xe5, 0xaf, 0x4f, 0x8c, 0xff, 0xcd,
+ 0x65, 0xf0, 0x23, 0x4e, 0xc5, 0x97, 0xb3, 0x5c, 0xda, 0x90, 0x1c, 0x82,
+ 0x85, 0x0d, 0x06, 0x0d, 0xc1, 0x22, 0xb6, 0x7b, 0x28, 0xa4, 0x03, 0xc3,
+ 0x4c, 0x53, 0xd1, 0x58, 0xbc, 0x72, 0xbc, 0x08, 0x39, 0xfc, 0xa0, 0x76,
+ 0xa8, 0xa8, 0xe9, 0x4b, 0x6e, 0x88, 0x3d, 0xe3, 0xb3, 0x31, 0x25, 0x8c,
+ 0x73, 0x29, 0x48, 0x0e, 0x32, 0x79, 0x06, 0xed, 0x3d, 0x43, 0xf4, 0xf6,
+ 0xe4, 0xe9, 0xfc, 0x7d, 0xbe, 0x8e, 0x08, 0xd5, 0x1f, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x01, 0xf4, 0x30, 0x82, 0x01, 0xf0, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4e, 0x43, 0xc8,
+ 0x1d, 0x76, 0xef, 0x37, 0x53, 0x7a, 0x4f, 0xf2, 0x58, 0x6f, 0x94, 0xf3,
+ 0x38, 0xe2, 0xd5, 0xbd, 0xdf, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+ 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x63, 0x70, 0x73, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x36, 0x30, 0x34, 0x30, 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33,
+ 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x20, 0x06, 0x03, 0x55,
+ 0x1d, 0x25, 0x04, 0x19, 0x30, 0x17, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+ 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f,
+ 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x11, 0x06,
+ 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04,
+ 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b,
+ 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67,
+ 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06,
+ 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a,
+ 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18,
+ 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c,
+ 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06, 0x03, 0x55,
+ 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a,
+ 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43, 0x6c, 0x61,
+ 0x73, 0x73, 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d,
+ 0x34, 0x38, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23,
+ 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd,
+ 0xec, 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33,
+ 0x31, 0x33, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5a, 0xa2,
+ 0xb1, 0xbf, 0xeb, 0x8d, 0xd4, 0x38, 0xa8, 0x80, 0x72, 0xc2, 0xdc, 0x38,
+ 0x2e, 0xac, 0xa7, 0x71, 0xf9, 0x2b, 0xa3, 0xbb, 0x47, 0xbb, 0x6d, 0x69,
+ 0x6f, 0x10, 0x36, 0x98, 0x8c, 0xc7, 0x56, 0x2e, 0xbb, 0xbc, 0xab, 0x4a,
+ 0x9b, 0x7a, 0xd6, 0xf2, 0x82, 0x93, 0xe0, 0x14, 0xfe, 0x8a, 0xce, 0x83,
+ 0xb7, 0x83, 0xdb, 0x93, 0x87, 0xab, 0xac, 0x65, 0x79, 0x49, 0xfd, 0x57,
+ 0xa9, 0xb1, 0xce, 0x09, 0x1f, 0xba, 0x10, 0x15, 0xc4, 0x09, 0x0e, 0x62,
+ 0xe3, 0xf9, 0x0a, 0x25, 0xd5, 0x64, 0x98, 0xf0, 0xf2, 0xa8, 0x0f, 0x76,
+ 0x32, 0x7e, 0x91, 0xe6, 0x18, 0xee, 0xbc, 0xe7, 0xda, 0xd0, 0x4e, 0x8d,
+ 0x78, 0xbb, 0xe2, 0x9d, 0xc0, 0x59, 0x2b, 0xc0, 0xce, 0x95, 0x0d, 0x24,
+ 0x0c, 0x72, 0xca, 0x34, 0x5e, 0x70, 0x22, 0x89, 0x2b, 0x4a, 0xb0, 0xf1,
+ 0x68, 0x87, 0xf3, 0xee, 0x44, 0x8d, 0x28, 0x40, 0x77, 0x39, 0x6e, 0x48,
+ 0x72, 0x45, 0x31, 0x5d, 0x6b, 0x39, 0x0e, 0x86, 0x02, 0xea, 0x66, 0x99,
+ 0x93, 0x31, 0x0f, 0xdf, 0x67, 0xde, 0xa6, 0x9f, 0x8c, 0x9d, 0x4c, 0xce,
+ 0x71, 0x6f, 0x3a, 0x21, 0xf6, 0xb9, 0x34, 0x3f, 0xf9, 0x6e, 0xd8, 0x9a,
+ 0xf7, 0x3e, 0xda, 0xf3, 0x81, 0x5f, 0x7a, 0x5c, 0x6d, 0x8f, 0x7c, 0xf6,
+ 0x99, 0x74, 0xb7, 0xff, 0xe4, 0x17, 0x5d, 0xed, 0x61, 0x5e, 0xab, 0x48,
+ 0xbb, 0x96, 0x8d, 0x66, 0x45, 0x39, 0xb4, 0x12, 0x0a, 0xf6, 0x70, 0xe9,
+ 0x9c, 0x76, 0x22, 0x4b, 0x60, 0xe9, 0x2a, 0x1b, 0x34, 0x49, 0xf7, 0xa2,
+ 0xd4, 0x67, 0xc0, 0xb1, 0x26, 0xad, 0x13, 0xba, 0xd9, 0x84, 0x01, 0xc1,
+ 0xab, 0xe1, 0x8e, 0x6d, 0x70, 0x16, 0x3b, 0x77, 0xac, 0x91, 0x9a, 0xbb,
+ 0x1a, 0x1f, 0xda, 0x58, 0xa7, 0xe4, 0x4f, 0xc1, 0x61, 0xae, 0xbc, 0xa2,
+ 0xfe, 0x4b,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 61:03:33:36:00:05:00:00:00:30
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: CN=Microsoft Internet Authority
+ Validity
+ Not Before: May 19 22:13:30 2010 GMT
+ Not After : May 19 22:23:30 2014 GMT
+ Subject: DC=com, DC=microsoft, DC=corp, DC=redmond, CN=Microsoft Secure Server Authority
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:ea:9f:5f:91:0b:cd:19:82:5f:91:ea:ab:f5:8b:
+ 28:d8:8b:f5:1c:e0:91:c9:bc:cd:02:10:50:22:b7:
+ 38:0a:5c:cf:71:0c:58:2d:88:6c:a8:b8:3c:33:63:
+ f9:73:9d:3c:e9:c3:79:ed:f2:fe:c9:cb:c3:6e:24:
+ e2:3c:42:70:d8:5f:b7:5b:f7:9b:5f:f5:27:6f:78:
+ 00:eb:96:5d:b7:6f:cf:e4:41:04:f0:bb:43:bd:6f:
+ 5f:26:0f:b7:8e:37:41:13:54:67:1b:90:00:27:38:
+ b8:1a:c3:96:6d:1c:31:35:35:49:c5:46:1e:e7:73:
+ a4:ca:03:11:79:41:81:af:d3:8e:46:a2:c5:be:00:
+ 53:05:b9:38:9c:b7:60:29:b3:ca:52:9a:92:c5:53:
+ 27:b6:41:0d:40:f8:2f:9b:e7:81:49:1a:5a:6a:a8:
+ 4f:71:c7:e8:6d:81:be:27:ef:c9:d6:c6:92:2b:10:
+ e4:36:35:40:08:d0:4d:70:fd:70:9b:20:1c:b3:b9:
+ df:75:9d:2b:77:d0:c4:cd:6a:71:ef:5a:58:0b:f9:
+ 70:85:88:05:89:6d:66:92:30:ab:af:88:39:d7:d4:
+ 2d:0b:96:9c:78:24:af:00:ab:cf:09:3e:13:ae:6b:
+ c3:e0:e1:cf:60:7f:8b:53:dc:02:d0:f3:b0:86:11:
+ de:bd
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Subject Key Identifier:
+ 08:42:E3:DB:4E:11:66:F3:B5:08:C5:40:DB:55:7C:33:46:11:83:38
+ X509v3 Key Usage:
+ Digital Signature, Certificate Sign, CRL Sign
+ 1.3.6.1.4.1.311.21.1:
+ .....
+ 1.3.6.1.4.1.311.21.2:
+ ..~...Z2..q..Oup......
+ 1.3.6.1.4.1.311.20.2:
+ .
+.S.u.b.C.A
+ X509v3 Authority Key Identifier:
+ keyid:33:21:F0:CB:FE:A2:A0:44:92:DE:F6:3B:33:D8:5F:01:4B:97:78:5D
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://mscrl.microsoft.com/pki/mscorp/crl/mswww(5).crl
+ URI:http://crl.microsoft.com/pki/mscorp/crl/mswww(5).crl
+ URI:http://corppki/crl/mswww(5).crl
+
+ Authority Information Access:
+ CA Issuers - URI:http://www.microsoft.com/pki/mscorp/mswww(5).crt
+ CA Issuers - URI:http://corppki/aia/mswww(5).crt
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 8f:c2:d1:5c:ef:14:11:77:17:63:07:3c:4c:7c:68:da:fe:86:
+ 4a:e2:20:cc:3f:b0:27:3d:d1:e2:ac:c8:8b:48:a6:e4:59:f7:
+ 3a:06:ad:7d:52:f1:f6:65:61:96:21:22:ae:68:be:2f:7a:de:
+ b3:0c:f5:e9:c5:dd:f8:65:82:5d:cb:6c:3e:0c:37:11:74:15:
+ 09:78:55:bd:26:12:bb:d6:95:74:d3:bc:f5:76:09:2a:6a:df:
+ 36:c4:8e:56:d5:1f:20:df:7f:82:30:d7:43:ab:68:22:8b:6a:
+ 5a:c5:9b:d0:9d:8d:0b:0c:50:85:7e:cc:5a:80:07:8b:03:4e:
+ bf:bd:5f:6c:56:0f:05:a9:e2:54:c3:a5:d3:52:5c:5f:4d:0b:
+ dd:05:f8:51:12:03:21:6f:9c:6c:97:98:2a:c1:c1:11:bc:bd:
+ 1b:ae:fb:e3:57:5f:4f:1f:00:9e:e2:a4:51:d3:f7:ac:09:37:
+ 58:a5:09:21:d1:72:d0:b2:c1:8b:db:4d:dc:13:d1:54:58:4d:
+ 2b:c0:ad:fa:53:19:35:b1:15:a8:42:64:b7:ed:c7:1f:a5:79:
+ a8:0d:38:d4:50:bf:f4:5a:ff:2f:e9:bf:3f:7d:38:e5:fb:20:
+ 0c:d4:4e:e0:2f:1d:45:7a:fb:28:2f:31:48:6f:cc:6e:5c:68:
+ 42:fa:ea:c8:0b:01:30:ec:10:26:42:38:23:a9:c3:19:b8:d9:
+ 70:1a:68:2c:92:cb:9f:73:e6:cc:ff:33:23:ee:db:5e:b5:7f:
+ 05:58:3f:50:c5:1c:08:18:f4:eb:2f:62:aa:53:f7:a1:cd:de:
+ e3:eb:82:1c:1a:67:6b:a1:4c:a7:68:71:40:d1:65:3b:41:18:
+ 9c:49:e3:71:fb:eb:4d:83:93:d3:47:e6:64:42:cb:b6:35:1c:
+ fb:34:0e:a1:28:fb:8c:a1:a7:1f:01:28:51:e5:71:94:37:9c:
+ dc:41:5b:7c:7e:e9:2c:23:67:94:9d:73:df:5f:40:79:a3:8d:
+ 95:30:cc:53:17:08:bc:50:86:f3:fc:10:19:81:fc:f4:5a:6e:
+ f3:dc:a2:9a:75:7b:c3:ac:a0:51:ed:32:b6:58:df:4f:8e:91:
+ 53:6a:d2:aa:1b:5d:e6:53:b8:89:a3:9e:89:a1:e3:29:e0:b3:
+ 6c:eb:1a:cc:6f:5a:aa:c2:e2:f6:1e:45:29:ef:d6:c2:43:b1:
+ 3b:ad:3e:26:fc:81:97:5c:48:fd:62:59:34:92:c9:fb:b9:a1:
+ d7:42:05:fb:19:f6:7e:32:fb:29:34:d5:87:66:e5:04:1d:c8:
+ 3e:10:fa:a6:78:f5:1e:7d:de:1a:3a:78:7c:dc:2a:71:06:a3:
+ 2d:6f:05:55:23:8b:90:ef
+-----BEGIN CERTIFICATE-----
+MIIGEzCCA/ugAwIBAgIKYQMzNgAFAAAAMDANBgkqhkiG9w0BAQUFADAnMSUwIwYD
+VQQDExxNaWNyb3NvZnQgSW50ZXJuZXQgQXV0aG9yaXR5MB4XDTEwMDUxOTIyMTMz
+MFoXDTE0MDUxOTIyMjMzMFowgYsxEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJ
+kiaJk/IsZAEZFgltaWNyb3NvZnQxFDASBgoJkiaJk/IsZAEZFgRjb3JwMRcwFQYK
+CZImiZPyLGQBGRYHcmVkbW9uZDEqMCgGA1UEAxMhTWljcm9zb2Z0IFNlY3VyZSBT
+ZXJ2ZXIgQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+6p9fkQvNGYJfkeqr9Yso2Iv1HOCRybzNAhBQIrc4ClzPcQxYLYhsqLg8M2P5c508
+6cN57fL+ycvDbiTiPEJw2F+3W/ebX/Unb3gA65Zdt2/P5EEE8LtDvW9fJg+3jjdB
+E1RnG5AAJzi4GsOWbRwxNTVJxUYe53OkygMReUGBr9OORqLFvgBTBbk4nLdgKbPK
+UpqSxVMntkENQPgvm+eBSRpaaqhPccfobYG+J+/J1saSKxDkNjVACNBNcP1wmyAc
+s7nfdZ0rd9DEzWpx71pYC/lwhYgFiW1mkjCrr4g519QtC5aceCSvAKvPCT4TrmvD
+4OHPYH+LU9wC0POwhhHevQIDAQABo4IB2jCCAdYwEgYDVR0TAQH/BAgwBgEB/wIB
+ADAdBgNVHQ4EFgQUCELj204RZvO1CMVA21V8M0YRgzgwCwYDVR0PBAQDAgGGMBIG
+CSsGAQQBgjcVAQQFAgMIAAgwIwYJKwYBBAGCNxUCBBYEFH6KwpxaMozCcaLZT3Vw
+96kb9pQFMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMB8GA1UdIwQYMBaAFDMh
+8Mv+oqBEkt72OzPYXwFLl3hdMIGjBgNVHR8EgZswgZgwgZWggZKggY+GNmh0dHA6
+Ly9tc2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3KDUpLmNy
+bIY0aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9tc2NvcnAvY3JsL21zd3d3
+KDUpLmNybIYfaHR0cDovL2NvcnBwa2kvY3JsL21zd3d3KDUpLmNybDB5BggrBgEF
+BQcBAQRtMGswPAYIKwYBBQUHMAKGMGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
+a2kvbXNjb3JwL21zd3d3KDUpLmNydDArBggrBgEFBQcwAoYfaHR0cDovL2NvcnBw
+a2kvYWlhL21zd3d3KDUpLmNydDANBgkqhkiG9w0BAQUFAAOCAgEAj8LRXO8UEXcX
+Ywc8THxo2v6GSuIgzD+wJz3R4qzIi0im5Fn3OgatfVLx9mVhliEirmi+L3reswz1
+6cXd+GWCXctsPgw3EXQVCXhVvSYSu9aVdNO89XYJKmrfNsSOVtUfIN9/gjDXQ6to
+IotqWsWb0J2NCwxQhX7MWoAHiwNOv71fbFYPBaniVMOl01JcX00L3QX4URIDIW+c
+bJeYKsHBEby9G67741dfTx8AnuKkUdP3rAk3WKUJIdFy0LLBi9tN3BPRVFhNK8Ct
++lMZNbEVqEJkt+3HH6V5qA041FC/9Fr/L+m/P3045fsgDNRO4C8dRXr7KC8xSG/M
+blxoQvrqyAsBMOwQJkI4I6nDGbjZcBpoLJLLn3PmzP8zI+7bXrV/BVg/UMUcCBj0
+6y9iqlP3oc3e4+uCHBpna6FMp2hxQNFlO0EYnEnjcfvrTYOT00fmZELLtjUc+zQO
+oSj7jKGnHwEoUeVxlDec3EFbfH7pLCNnlJ1z319AeaONlTDMUxcIvFCG8/wQGYH8
+9Fpu89yimnV7w6ygUe0ytljfT46RU2rSqhtd5lO4iaOeiaHjKeCzbOsazG9aqsLi
+9h5FKe/WwkOxO60+JvyBl1xI/WJZNJLJ+7mh10IF+xn2fjL7KTTVh2blBB3IPhD6
+pnj1Hn3eGjp4fNwqcQajLW8FVSOLkO8=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert92[] = {
+ 0x30, 0x82, 0x06, 0x13, 0x30, 0x82, 0x03, 0xfb, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x0a, 0x61, 0x03, 0x33, 0x36, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x30, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x30, 0x27, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x1c, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17,
+ 0x0d, 0x31, 0x30, 0x30, 0x35, 0x31, 0x39, 0x32, 0x32, 0x31, 0x33, 0x33,
+ 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x35, 0x31, 0x39, 0x32, 0x32,
+ 0x32, 0x33, 0x33, 0x30, 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x13, 0x30, 0x11,
+ 0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+ 0x16, 0x03, 0x63, 0x6f, 0x6d, 0x31, 0x19, 0x30, 0x17, 0x06, 0x0a, 0x09,
+ 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x09, 0x6d,
+ 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x31, 0x14, 0x30, 0x12,
+ 0x06, 0x0a, 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19,
+ 0x16, 0x04, 0x63, 0x6f, 0x72, 0x70, 0x31, 0x17, 0x30, 0x15, 0x06, 0x0a,
+ 0x09, 0x92, 0x26, 0x89, 0x93, 0xf2, 0x2c, 0x64, 0x01, 0x19, 0x16, 0x07,
+ 0x72, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x2a, 0x30, 0x28, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+ 0x6f, 0x66, 0x74, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x53,
+ 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82,
+ 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
+ 0xea, 0x9f, 0x5f, 0x91, 0x0b, 0xcd, 0x19, 0x82, 0x5f, 0x91, 0xea, 0xab,
+ 0xf5, 0x8b, 0x28, 0xd8, 0x8b, 0xf5, 0x1c, 0xe0, 0x91, 0xc9, 0xbc, 0xcd,
+ 0x02, 0x10, 0x50, 0x22, 0xb7, 0x38, 0x0a, 0x5c, 0xcf, 0x71, 0x0c, 0x58,
+ 0x2d, 0x88, 0x6c, 0xa8, 0xb8, 0x3c, 0x33, 0x63, 0xf9, 0x73, 0x9d, 0x3c,
+ 0xe9, 0xc3, 0x79, 0xed, 0xf2, 0xfe, 0xc9, 0xcb, 0xc3, 0x6e, 0x24, 0xe2,
+ 0x3c, 0x42, 0x70, 0xd8, 0x5f, 0xb7, 0x5b, 0xf7, 0x9b, 0x5f, 0xf5, 0x27,
+ 0x6f, 0x78, 0x00, 0xeb, 0x96, 0x5d, 0xb7, 0x6f, 0xcf, 0xe4, 0x41, 0x04,
+ 0xf0, 0xbb, 0x43, 0xbd, 0x6f, 0x5f, 0x26, 0x0f, 0xb7, 0x8e, 0x37, 0x41,
+ 0x13, 0x54, 0x67, 0x1b, 0x90, 0x00, 0x27, 0x38, 0xb8, 0x1a, 0xc3, 0x96,
+ 0x6d, 0x1c, 0x31, 0x35, 0x35, 0x49, 0xc5, 0x46, 0x1e, 0xe7, 0x73, 0xa4,
+ 0xca, 0x03, 0x11, 0x79, 0x41, 0x81, 0xaf, 0xd3, 0x8e, 0x46, 0xa2, 0xc5,
+ 0xbe, 0x00, 0x53, 0x05, 0xb9, 0x38, 0x9c, 0xb7, 0x60, 0x29, 0xb3, 0xca,
+ 0x52, 0x9a, 0x92, 0xc5, 0x53, 0x27, 0xb6, 0x41, 0x0d, 0x40, 0xf8, 0x2f,
+ 0x9b, 0xe7, 0x81, 0x49, 0x1a, 0x5a, 0x6a, 0xa8, 0x4f, 0x71, 0xc7, 0xe8,
+ 0x6d, 0x81, 0xbe, 0x27, 0xef, 0xc9, 0xd6, 0xc6, 0x92, 0x2b, 0x10, 0xe4,
+ 0x36, 0x35, 0x40, 0x08, 0xd0, 0x4d, 0x70, 0xfd, 0x70, 0x9b, 0x20, 0x1c,
+ 0xb3, 0xb9, 0xdf, 0x75, 0x9d, 0x2b, 0x77, 0xd0, 0xc4, 0xcd, 0x6a, 0x71,
+ 0xef, 0x5a, 0x58, 0x0b, 0xf9, 0x70, 0x85, 0x88, 0x05, 0x89, 0x6d, 0x66,
+ 0x92, 0x30, 0xab, 0xaf, 0x88, 0x39, 0xd7, 0xd4, 0x2d, 0x0b, 0x96, 0x9c,
+ 0x78, 0x24, 0xaf, 0x00, 0xab, 0xcf, 0x09, 0x3e, 0x13, 0xae, 0x6b, 0xc3,
+ 0xe0, 0xe1, 0xcf, 0x60, 0x7f, 0x8b, 0x53, 0xdc, 0x02, 0xd0, 0xf3, 0xb0,
+ 0x86, 0x11, 0xde, 0xbd, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01,
+ 0xda, 0x30, 0x82, 0x01, 0xd6, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x08, 0x42, 0xe3, 0xdb, 0x4e, 0x11, 0x66, 0xf3, 0xb5, 0x08, 0xc5, 0x40,
+ 0xdb, 0x55, 0x7c, 0x33, 0x46, 0x11, 0x83, 0x38, 0x30, 0x0b, 0x06, 0x03,
+ 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x12, 0x06,
+ 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x05,
+ 0x02, 0x03, 0x08, 0x00, 0x08, 0x30, 0x23, 0x06, 0x09, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x82, 0x37, 0x15, 0x02, 0x04, 0x16, 0x04, 0x14, 0x7e, 0x8a,
+ 0xc2, 0x9c, 0x5a, 0x32, 0x8c, 0xc2, 0x71, 0xa2, 0xd9, 0x4f, 0x75, 0x70,
+ 0xf7, 0xa9, 0x1b, 0xf6, 0x94, 0x05, 0x30, 0x19, 0x06, 0x09, 0x2b, 0x06,
+ 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, 0x1e, 0x0a, 0x00,
+ 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x33, 0x21,
+ 0xf0, 0xcb, 0xfe, 0xa2, 0xa0, 0x44, 0x92, 0xde, 0xf6, 0x3b, 0x33, 0xd8,
+ 0x5f, 0x01, 0x4b, 0x97, 0x78, 0x5d, 0x30, 0x81, 0xa3, 0x06, 0x03, 0x55,
+ 0x1d, 0x1f, 0x04, 0x81, 0x9b, 0x30, 0x81, 0x98, 0x30, 0x81, 0x95, 0xa0,
+ 0x81, 0x92, 0xa0, 0x81, 0x8f, 0x86, 0x36, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x6d, 0x73, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72,
+ 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b,
+ 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c,
+ 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72,
+ 0x6c, 0x86, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f,
+ 0x72, 0x70, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77,
+ 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x6c, 0x86, 0x1f, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70, 0x6b, 0x69, 0x2f,
+ 0x63, 0x72, 0x6c, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77, 0x28, 0x35, 0x29,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x79, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x6d, 0x30, 0x6b, 0x30, 0x3c, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x30, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63,
+ 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+ 0x6b, 0x69, 0x2f, 0x6d, 0x73, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x6d, 0x73,
+ 0x77, 0x77, 0x77, 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x2b,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x1f,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x6f, 0x72, 0x70, 0x70,
+ 0x6b, 0x69, 0x2f, 0x61, 0x69, 0x61, 0x2f, 0x6d, 0x73, 0x77, 0x77, 0x77,
+ 0x28, 0x35, 0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82,
+ 0x02, 0x01, 0x00, 0x8f, 0xc2, 0xd1, 0x5c, 0xef, 0x14, 0x11, 0x77, 0x17,
+ 0x63, 0x07, 0x3c, 0x4c, 0x7c, 0x68, 0xda, 0xfe, 0x86, 0x4a, 0xe2, 0x20,
+ 0xcc, 0x3f, 0xb0, 0x27, 0x3d, 0xd1, 0xe2, 0xac, 0xc8, 0x8b, 0x48, 0xa6,
+ 0xe4, 0x59, 0xf7, 0x3a, 0x06, 0xad, 0x7d, 0x52, 0xf1, 0xf6, 0x65, 0x61,
+ 0x96, 0x21, 0x22, 0xae, 0x68, 0xbe, 0x2f, 0x7a, 0xde, 0xb3, 0x0c, 0xf5,
+ 0xe9, 0xc5, 0xdd, 0xf8, 0x65, 0x82, 0x5d, 0xcb, 0x6c, 0x3e, 0x0c, 0x37,
+ 0x11, 0x74, 0x15, 0x09, 0x78, 0x55, 0xbd, 0x26, 0x12, 0xbb, 0xd6, 0x95,
+ 0x74, 0xd3, 0xbc, 0xf5, 0x76, 0x09, 0x2a, 0x6a, 0xdf, 0x36, 0xc4, 0x8e,
+ 0x56, 0xd5, 0x1f, 0x20, 0xdf, 0x7f, 0x82, 0x30, 0xd7, 0x43, 0xab, 0x68,
+ 0x22, 0x8b, 0x6a, 0x5a, 0xc5, 0x9b, 0xd0, 0x9d, 0x8d, 0x0b, 0x0c, 0x50,
+ 0x85, 0x7e, 0xcc, 0x5a, 0x80, 0x07, 0x8b, 0x03, 0x4e, 0xbf, 0xbd, 0x5f,
+ 0x6c, 0x56, 0x0f, 0x05, 0xa9, 0xe2, 0x54, 0xc3, 0xa5, 0xd3, 0x52, 0x5c,
+ 0x5f, 0x4d, 0x0b, 0xdd, 0x05, 0xf8, 0x51, 0x12, 0x03, 0x21, 0x6f, 0x9c,
+ 0x6c, 0x97, 0x98, 0x2a, 0xc1, 0xc1, 0x11, 0xbc, 0xbd, 0x1b, 0xae, 0xfb,
+ 0xe3, 0x57, 0x5f, 0x4f, 0x1f, 0x00, 0x9e, 0xe2, 0xa4, 0x51, 0xd3, 0xf7,
+ 0xac, 0x09, 0x37, 0x58, 0xa5, 0x09, 0x21, 0xd1, 0x72, 0xd0, 0xb2, 0xc1,
+ 0x8b, 0xdb, 0x4d, 0xdc, 0x13, 0xd1, 0x54, 0x58, 0x4d, 0x2b, 0xc0, 0xad,
+ 0xfa, 0x53, 0x19, 0x35, 0xb1, 0x15, 0xa8, 0x42, 0x64, 0xb7, 0xed, 0xc7,
+ 0x1f, 0xa5, 0x79, 0xa8, 0x0d, 0x38, 0xd4, 0x50, 0xbf, 0xf4, 0x5a, 0xff,
+ 0x2f, 0xe9, 0xbf, 0x3f, 0x7d, 0x38, 0xe5, 0xfb, 0x20, 0x0c, 0xd4, 0x4e,
+ 0xe0, 0x2f, 0x1d, 0x45, 0x7a, 0xfb, 0x28, 0x2f, 0x31, 0x48, 0x6f, 0xcc,
+ 0x6e, 0x5c, 0x68, 0x42, 0xfa, 0xea, 0xc8, 0x0b, 0x01, 0x30, 0xec, 0x10,
+ 0x26, 0x42, 0x38, 0x23, 0xa9, 0xc3, 0x19, 0xb8, 0xd9, 0x70, 0x1a, 0x68,
+ 0x2c, 0x92, 0xcb, 0x9f, 0x73, 0xe6, 0xcc, 0xff, 0x33, 0x23, 0xee, 0xdb,
+ 0x5e, 0xb5, 0x7f, 0x05, 0x58, 0x3f, 0x50, 0xc5, 0x1c, 0x08, 0x18, 0xf4,
+ 0xeb, 0x2f, 0x62, 0xaa, 0x53, 0xf7, 0xa1, 0xcd, 0xde, 0xe3, 0xeb, 0x82,
+ 0x1c, 0x1a, 0x67, 0x6b, 0xa1, 0x4c, 0xa7, 0x68, 0x71, 0x40, 0xd1, 0x65,
+ 0x3b, 0x41, 0x18, 0x9c, 0x49, 0xe3, 0x71, 0xfb, 0xeb, 0x4d, 0x83, 0x93,
+ 0xd3, 0x47, 0xe6, 0x64, 0x42, 0xcb, 0xb6, 0x35, 0x1c, 0xfb, 0x34, 0x0e,
+ 0xa1, 0x28, 0xfb, 0x8c, 0xa1, 0xa7, 0x1f, 0x01, 0x28, 0x51, 0xe5, 0x71,
+ 0x94, 0x37, 0x9c, 0xdc, 0x41, 0x5b, 0x7c, 0x7e, 0xe9, 0x2c, 0x23, 0x67,
+ 0x94, 0x9d, 0x73, 0xdf, 0x5f, 0x40, 0x79, 0xa3, 0x8d, 0x95, 0x30, 0xcc,
+ 0x53, 0x17, 0x08, 0xbc, 0x50, 0x86, 0xf3, 0xfc, 0x10, 0x19, 0x81, 0xfc,
+ 0xf4, 0x5a, 0x6e, 0xf3, 0xdc, 0xa2, 0x9a, 0x75, 0x7b, 0xc3, 0xac, 0xa0,
+ 0x51, 0xed, 0x32, 0xb6, 0x58, 0xdf, 0x4f, 0x8e, 0x91, 0x53, 0x6a, 0xd2,
+ 0xaa, 0x1b, 0x5d, 0xe6, 0x53, 0xb8, 0x89, 0xa3, 0x9e, 0x89, 0xa1, 0xe3,
+ 0x29, 0xe0, 0xb3, 0x6c, 0xeb, 0x1a, 0xcc, 0x6f, 0x5a, 0xaa, 0xc2, 0xe2,
+ 0xf6, 0x1e, 0x45, 0x29, 0xef, 0xd6, 0xc2, 0x43, 0xb1, 0x3b, 0xad, 0x3e,
+ 0x26, 0xfc, 0x81, 0x97, 0x5c, 0x48, 0xfd, 0x62, 0x59, 0x34, 0x92, 0xc9,
+ 0xfb, 0xb9, 0xa1, 0xd7, 0x42, 0x05, 0xfb, 0x19, 0xf6, 0x7e, 0x32, 0xfb,
+ 0x29, 0x34, 0xd5, 0x87, 0x66, 0xe5, 0x04, 0x1d, 0xc8, 0x3e, 0x10, 0xfa,
+ 0xa6, 0x78, 0xf5, 0x1e, 0x7d, 0xde, 0x1a, 0x3a, 0x78, 0x7c, 0xdc, 0x2a,
+ 0x71, 0x06, 0xa3, 0x2d, 0x6f, 0x05, 0x55, 0x23, 0x8b, 0x90, 0xef,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 2c:48:dd:93:0d:f5:59:8e:f9:3c:99:54:7a:60:ed:43
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Nov 8 00:00:00 2006 GMT
+ Not After : Nov 7 23:59:59 2016 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)06, CN=VeriSign Class 3 Extended Validation SSL SGC CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bd:56:88:ba:88:34:64:64:cf:cd:ca:b0:ee:e7:
+ 19:73:c5:72:d9:bb:45:bc:b5:a8:ff:83:be:1c:03:
+ db:ed:89:b7:2e:10:1a:25:bc:55:ca:41:a1:9f:0b:
+ cf:19:5e:70:b9:5e:39:4b:9e:31:1c:5f:87:ae:2a:
+ aa:a8:2b:a2:1b:3b:10:23:5f:13:b1:dd:08:8c:4e:
+ 14:da:83:81:e3:b5:8c:e3:68:ed:24:67:ce:56:b6:
+ ac:9b:73:96:44:db:8a:8c:b3:d6:f0:71:93:8e:db:
+ 71:54:4a:eb:73:59:6a:8f:70:51:2c:03:9f:97:d1:
+ cc:11:7a:bc:62:0d:95:2a:c9:1c:75:57:e9:f5:c7:
+ ea:ba:84:35:cb:c7:85:5a:7e:e4:4d:e1:11:97:7d:
+ 0e:20:34:45:db:f1:a2:09:eb:eb:3d:9e:b8:96:43:
+ 5e:34:4b:08:25:1e:43:1a:a2:d9:b7:8a:01:34:3d:
+ c3:f8:e5:af:4f:8c:ff:cd:65:f0:23:4e:c5:97:b3:
+ 5c:da:90:1c:82:85:0d:06:0d:c1:22:b6:7b:28:a4:
+ 03:c3:4c:53:d1:58:bc:72:bc:08:39:fc:a0:76:a8:
+ a8:e9:4b:6e:88:3d:e3:b3:31:25:8c:73:29:48:0e:
+ 32:79:06:ed:3d:43:f4:f6:e4:e9:fc:7d:be:8e:08:
+ d5:1f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 4E:43:C8:1D:76:EF:37:53:7A:4F:F2:58:6F:94:F3:38:E2:D5:BD:DF
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: X509v3 Any Policy
+ CPS: https://www.verisign.com/cps
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://EVSecure-crl.verisign.com/pca3-g5.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ Netscape Cert Type:
+ SSL CA, S/MIME CA
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Subject Alternative Name:
+ DirName:/CN=Class3CA2048-1-48
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Authority Information Access:
+ OCSP - URI:http://EVSecure-ocsp.verisign.com
+
+ X509v3 Extended Key Usage:
+ Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1, TLS Web Server Authentication, TLS Web Client Authentication
+ Signature Algorithm: sha1WithRSAEncryption
+ 27:74:a6:34:ea:1d:9d:e1:53:d6:1c:9d:0c:a7:5b:4c:a9:67:
+ f2:f0:32:b7:01:0f:fb:42:18:38:de:e4:ee:49:c8:13:c9:0b:
+ ec:04:c3:40:71:18:72:76:43:02:23:5d:ab:7b:c8:48:14:1a:
+ c8:7b:1d:fc:f6:0a:9f:36:a1:d2:09:73:71:66:96:75:51:34:
+ bf:99:30:51:67:9d:54:b7:26:45:ac:73:08:23:86:26:99:71:
+ f4:8e:d7:ea:39:9b:06:09:23:bf:62:dd:a8:c4:b6:7d:a4:89:
+ 07:3e:f3:6d:ae:40:59:50:79:97:37:3d:32:78:7d:b2:63:4b:
+ f9:ea:08:69:0e:13:ed:e8:cf:bb:ac:05:86:ca:22:cf:88:62:
+ 5d:3c:22:49:d8:63:d5:24:a6:bd:ef:5c:e3:cc:20:3b:22:ea:
+ fc:44:c6:a8:e5:1f:e1:86:cd:0c:4d:8f:93:53:d9:7f:ee:a1:
+ 08:a7:b3:30:96:49:70:6e:a3:6c:3d:d0:63:ef:25:66:63:cc:
+ aa:b7:18:17:4e:ea:70:76:f6:ba:42:a6:80:37:09:4e:9f:66:
+ 88:2e:6b:33:66:c8:c0:71:a4:41:eb:5a:e3:fc:14:2e:4b:88:
+ fd:ae:6e:5b:65:e9:27:e4:bf:e4:b0:23:c1:b2:7d:5b:62:25:
+ d7:3e:10:d4
+-----BEGIN CERTIFICATE-----
+MIIGHjCCBQagAwIBAgIQLEjdkw31WY75PJlUemDtQzANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMTYxMTA3MjM1OTU5WjCBvjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMvVmVy
+aVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9Voi6iDRkZM/NyrDu5xlzxXLZ
+u0W8taj/g74cA9vtibcuEBolvFXKQaGfC88ZXnC5XjlLnjEcX4euKqqoK6IbOxAj
+XxOx3QiMThTag4HjtYzjaO0kZ85Wtqybc5ZE24qMs9bwcZOO23FUSutzWWqPcFEs
+A5+X0cwRerxiDZUqyRx1V+n1x+q6hDXLx4VafuRN4RGXfQ4gNEXb8aIJ6+s9nriW
+Q140SwglHkMaotm3igE0PcP45a9PjP/NZfAjTsWXs1zakByChQ0GDcEitnsopAPD
+TFPRWLxyvAg5/KB2qKjpS26IPeOzMSWMcylIDjJ5Bu09Q/T25On8fb6OCNUfAgMB
+AAGjggIIMIICBDAdBgNVHQ4EFgQUTkPIHXbvN1N6T/JYb5TzOOLVvd8wEgYDVR0T
+AQH/BAgwBgEB/wIBADA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYc
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczA9BgNVHR8ENjA0MDKgMKAuhixo
+dHRwOi8vRVZTZWN1cmUtY3JsLnZlcmlzaWduLmNvbS9wY2EzLWc1LmNybDAOBgNV
+HQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgEGMG0GCCsGAQUFBwEMBGEwX6Fd
+oFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrU
+SBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMCkG
+A1UdEQQiMCCkHjAcMRowGAYDVQQDExFDbGFzczNDQTIwNDgtMS00ODAfBgNVHSME
+GDAWgBR/02Wnwt3su/AwCfNDOfoCrzMxMzA9BggrBgEFBQcBAQQxMC8wLQYIKwYB
+BQUHMAGGIWh0dHA6Ly9FVlNlY3VyZS1vY3NwLnZlcmlzaWduLmNvbTA0BgNVHSUE
+LTArBglghkgBhvhCBAEGCmCGSAGG+EUBCAEGCCsGAQUFBwMBBggrBgEFBQcDAjAN
+BgkqhkiG9w0BAQUFAAOCAQEAJ3SmNOodneFT1hydDKdbTKln8vAytwEP+0IYON7k
+7knIE8kL7ATDQHEYcnZDAiNdq3vISBQayHsd/PYKnzah0glzcWaWdVE0v5kwUWed
+VLcmRaxzCCOGJplx9I7X6jmbBgkjv2LdqMS2faSJBz7zba5AWVB5lzc9Mnh9smNL
++eoIaQ4T7ejPu6wFhsoiz4hiXTwiSdhj1SSmve9c48wgOyLq/ETGqOUf4YbNDE2P
+k1PZf+6hCKezMJZJcG6jbD3QY+8lZmPMqrcYF07qcHb2ukKmgDcJTp9miC5rM2bI
+wHGkQeta4/wULkuI/a5uW2XpJ+S/5LAjwbJ9W2Il1z4Q1A==
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert93[] = {
+ 0x30, 0x82, 0x06, 0x1e, 0x30, 0x82, 0x05, 0x06, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x2c, 0x48, 0xdd, 0x93, 0x0d, 0xf5, 0x59, 0x8e, 0xf9,
+ 0x3c, 0x99, 0x54, 0x7a, 0x60, 0xed, 0x43, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x36, 0x31, 0x31, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbe, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+ 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36, 0x31, 0x38,
+ 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x56,
+ 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x53, 0x53,
+ 0x4c, 0x20, 0x53, 0x47, 0x43, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01, 0x22,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
+ 0x02, 0x82, 0x01, 0x01, 0x00, 0xbd, 0x56, 0x88, 0xba, 0x88, 0x34, 0x64,
+ 0x64, 0xcf, 0xcd, 0xca, 0xb0, 0xee, 0xe7, 0x19, 0x73, 0xc5, 0x72, 0xd9,
+ 0xbb, 0x45, 0xbc, 0xb5, 0xa8, 0xff, 0x83, 0xbe, 0x1c, 0x03, 0xdb, 0xed,
+ 0x89, 0xb7, 0x2e, 0x10, 0x1a, 0x25, 0xbc, 0x55, 0xca, 0x41, 0xa1, 0x9f,
+ 0x0b, 0xcf, 0x19, 0x5e, 0x70, 0xb9, 0x5e, 0x39, 0x4b, 0x9e, 0x31, 0x1c,
+ 0x5f, 0x87, 0xae, 0x2a, 0xaa, 0xa8, 0x2b, 0xa2, 0x1b, 0x3b, 0x10, 0x23,
+ 0x5f, 0x13, 0xb1, 0xdd, 0x08, 0x8c, 0x4e, 0x14, 0xda, 0x83, 0x81, 0xe3,
+ 0xb5, 0x8c, 0xe3, 0x68, 0xed, 0x24, 0x67, 0xce, 0x56, 0xb6, 0xac, 0x9b,
+ 0x73, 0x96, 0x44, 0xdb, 0x8a, 0x8c, 0xb3, 0xd6, 0xf0, 0x71, 0x93, 0x8e,
+ 0xdb, 0x71, 0x54, 0x4a, 0xeb, 0x73, 0x59, 0x6a, 0x8f, 0x70, 0x51, 0x2c,
+ 0x03, 0x9f, 0x97, 0xd1, 0xcc, 0x11, 0x7a, 0xbc, 0x62, 0x0d, 0x95, 0x2a,
+ 0xc9, 0x1c, 0x75, 0x57, 0xe9, 0xf5, 0xc7, 0xea, 0xba, 0x84, 0x35, 0xcb,
+ 0xc7, 0x85, 0x5a, 0x7e, 0xe4, 0x4d, 0xe1, 0x11, 0x97, 0x7d, 0x0e, 0x20,
+ 0x34, 0x45, 0xdb, 0xf1, 0xa2, 0x09, 0xeb, 0xeb, 0x3d, 0x9e, 0xb8, 0x96,
+ 0x43, 0x5e, 0x34, 0x4b, 0x08, 0x25, 0x1e, 0x43, 0x1a, 0xa2, 0xd9, 0xb7,
+ 0x8a, 0x01, 0x34, 0x3d, 0xc3, 0xf8, 0xe5, 0xaf, 0x4f, 0x8c, 0xff, 0xcd,
+ 0x65, 0xf0, 0x23, 0x4e, 0xc5, 0x97, 0xb3, 0x5c, 0xda, 0x90, 0x1c, 0x82,
+ 0x85, 0x0d, 0x06, 0x0d, 0xc1, 0x22, 0xb6, 0x7b, 0x28, 0xa4, 0x03, 0xc3,
+ 0x4c, 0x53, 0xd1, 0x58, 0xbc, 0x72, 0xbc, 0x08, 0x39, 0xfc, 0xa0, 0x76,
+ 0xa8, 0xa8, 0xe9, 0x4b, 0x6e, 0x88, 0x3d, 0xe3, 0xb3, 0x31, 0x25, 0x8c,
+ 0x73, 0x29, 0x48, 0x0e, 0x32, 0x79, 0x06, 0xed, 0x3d, 0x43, 0xf4, 0xf6,
+ 0xe4, 0xe9, 0xfc, 0x7d, 0xbe, 0x8e, 0x08, 0xd5, 0x1f, 0x02, 0x03, 0x01,
+ 0x00, 0x01, 0xa3, 0x82, 0x02, 0x08, 0x30, 0x82, 0x02, 0x04, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4e, 0x43, 0xc8,
+ 0x1d, 0x76, 0xef, 0x37, 0x53, 0x7a, 0x4f, 0xf2, 0x58, 0x6f, 0x94, 0xf3,
+ 0x38, 0xe2, 0xd5, 0xbd, 0xdf, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13,
+ 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01,
+ 0x00, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x36, 0x30, 0x34,
+ 0x30, 0x32, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x2a, 0x30, 0x28,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x63, 0x70, 0x73, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x36, 0x30, 0x34, 0x30, 0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75,
+ 0x72, 0x65, 0x2d, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73,
+ 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33,
+ 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55,
+ 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30,
+ 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01,
+ 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d,
+ 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d,
+ 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30,
+ 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5,
+ 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4,
+ 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65,
+ 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76,
+ 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x29, 0x06,
+ 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20, 0xa4, 0x1e, 0x30, 0x1c,
+ 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x43,
+ 0x6c, 0x61, 0x73, 0x73, 0x33, 0x43, 0x41, 0x32, 0x30, 0x34, 0x38, 0x2d,
+ 0x31, 0x2d, 0x34, 0x38, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec,
+ 0xbb, 0xf0, 0x30, 0x09, 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31,
+ 0x33, 0x30, 0x3d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01,
+ 0x01, 0x04, 0x31, 0x30, 0x2f, 0x30, 0x2d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x45, 0x56, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2d, 0x6f,
+ 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04,
+ 0x2d, 0x30, 0x2b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+ 0x04, 0x01, 0x06, 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01,
+ 0x08, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x27, 0x74, 0xa6, 0x34, 0xea, 0x1d,
+ 0x9d, 0xe1, 0x53, 0xd6, 0x1c, 0x9d, 0x0c, 0xa7, 0x5b, 0x4c, 0xa9, 0x67,
+ 0xf2, 0xf0, 0x32, 0xb7, 0x01, 0x0f, 0xfb, 0x42, 0x18, 0x38, 0xde, 0xe4,
+ 0xee, 0x49, 0xc8, 0x13, 0xc9, 0x0b, 0xec, 0x04, 0xc3, 0x40, 0x71, 0x18,
+ 0x72, 0x76, 0x43, 0x02, 0x23, 0x5d, 0xab, 0x7b, 0xc8, 0x48, 0x14, 0x1a,
+ 0xc8, 0x7b, 0x1d, 0xfc, 0xf6, 0x0a, 0x9f, 0x36, 0xa1, 0xd2, 0x09, 0x73,
+ 0x71, 0x66, 0x96, 0x75, 0x51, 0x34, 0xbf, 0x99, 0x30, 0x51, 0x67, 0x9d,
+ 0x54, 0xb7, 0x26, 0x45, 0xac, 0x73, 0x08, 0x23, 0x86, 0x26, 0x99, 0x71,
+ 0xf4, 0x8e, 0xd7, 0xea, 0x39, 0x9b, 0x06, 0x09, 0x23, 0xbf, 0x62, 0xdd,
+ 0xa8, 0xc4, 0xb6, 0x7d, 0xa4, 0x89, 0x07, 0x3e, 0xf3, 0x6d, 0xae, 0x40,
+ 0x59, 0x50, 0x79, 0x97, 0x37, 0x3d, 0x32, 0x78, 0x7d, 0xb2, 0x63, 0x4b,
+ 0xf9, 0xea, 0x08, 0x69, 0x0e, 0x13, 0xed, 0xe8, 0xcf, 0xbb, 0xac, 0x05,
+ 0x86, 0xca, 0x22, 0xcf, 0x88, 0x62, 0x5d, 0x3c, 0x22, 0x49, 0xd8, 0x63,
+ 0xd5, 0x24, 0xa6, 0xbd, 0xef, 0x5c, 0xe3, 0xcc, 0x20, 0x3b, 0x22, 0xea,
+ 0xfc, 0x44, 0xc6, 0xa8, 0xe5, 0x1f, 0xe1, 0x86, 0xcd, 0x0c, 0x4d, 0x8f,
+ 0x93, 0x53, 0xd9, 0x7f, 0xee, 0xa1, 0x08, 0xa7, 0xb3, 0x30, 0x96, 0x49,
+ 0x70, 0x6e, 0xa3, 0x6c, 0x3d, 0xd0, 0x63, 0xef, 0x25, 0x66, 0x63, 0xcc,
+ 0xaa, 0xb7, 0x18, 0x17, 0x4e, 0xea, 0x70, 0x76, 0xf6, 0xba, 0x42, 0xa6,
+ 0x80, 0x37, 0x09, 0x4e, 0x9f, 0x66, 0x88, 0x2e, 0x6b, 0x33, 0x66, 0xc8,
+ 0xc0, 0x71, 0xa4, 0x41, 0xeb, 0x5a, 0xe3, 0xfc, 0x14, 0x2e, 0x4b, 0x88,
+ 0xfd, 0xae, 0x6e, 0x5b, 0x65, 0xe9, 0x27, 0xe4, 0xbf, 0xe4, 0xb0, 0x23,
+ 0xc1, 0xb2, 0x7d, 0x5b, 0x62, 0x25, 0xd7, 0x3e, 0x10, 0xd4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 64:1b:e8:20:ce:02:08:13:f3:2d:4d:2d:95:d6:7e:67
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5
+ Validity
+ Not Before: Feb 8 00:00:00 2010 GMT
+ Not After : Feb 7 23:59:59 2020 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 International Server CA - G3
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:99:d6:9c:62:f0:15:f4:81:9a:41:08:59:8f:13:
+ 9d:17:c9:9f:51:dc:da:b1:52:ef:ff:e3:41:dd:e0:
+ df:c4:28:c6:e3:ad:79:1f:27:10:98:b8:bb:20:97:
+ c1:28:44:41:0f:ea:a9:a8:52:cf:4d:4e:1b:8b:bb:
+ b5:c4:76:d9:cc:56:06:ee:b3:55:20:2a:de:15:8d:
+ 71:cb:54:c8:6f:17:cd:89:00:e4:dc:ff:e1:c0:1f:
+ 68:71:e9:c7:29:2e:7e:bc:3b:fc:e5:bb:ab:26:54:
+ 8b:66:90:cd:f6:92:b9:31:24:80:bc:9e:6c:d5:fc:
+ 7e:d2:e1:4b:8c:dc:42:fa:44:4b:5f:f8:18:b5:2e:
+ 30:f4:3d:12:98:d3:62:05:73:54:a6:9c:a2:1d:be:
+ 52:83:3a:07:46:c4:3b:02:56:21:bf:f2:51:4f:d0:
+ a6:99:39:e9:ae:a5:3f:89:9b:9c:7d:fe:4d:60:07:
+ 25:20:f7:bb:d7:69:83:2b:82:93:43:37:d9:83:41:
+ 1b:6b:0b:ab:4a:66:84:4f:4a:8e:de:7e:34:99:8e:
+ 68:d6:ca:39:06:9b:4c:b3:9a:48:4d:13:46:b4:58:
+ 21:04:c4:fb:a0:4d:ac:2e:4b:62:12:e3:fb:4d:f6:
+ c9:51:00:01:1f:fc:1e:6a:81:2a:38:e0:b9:4f:d6:
+ 2d:45
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.23.3
+ CPS: https://www.verisign.com/cps
+ User Notice:
+ Explicit Text: https://www.verisign.com/rpa
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Netscape Server Gated Crypto, 2.16.840.1.113733.1.8.1
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3-g5.crl
+
+ X509v3 Subject Alternative Name:
+ DirName:/CN=VeriSignMPKI-2-7
+ X509v3 Subject Key Identifier:
+ D7:9B:7C:D8:22:A0:15:F7:DD:AD:5F:CE:29:9B:58:C3:BC:46:00:B5
+ X509v3 Authority Key Identifier:
+ keyid:7F:D3:65:A7:C2:DD:EC:BB:F0:30:09:F3:43:39:FA:02:AF:33:31:33
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 71:b5:7d:73:52:4a:dd:d7:4d:34:2b:2e:af:94:46:a5:49:50:
+ 02:4f:f8:2f:17:70:f2:13:dc:1f:21:86:aa:c2:4f:7c:37:3c:
+ d4:46:78:ae:5d:78:6f:d1:ba:5a:bc:10:ab:58:36:c5:8c:62:
+ 15:45:60:17:21:e2:d5:42:a8:77:a1:55:d8:43:04:51:f6:6e:
+ ba:48:e6:5d:4c:b7:44:d3:3e:a4:d5:d6:33:9a:9f:0d:e6:d7:
+ 4e:96:44:95:5a:6c:d6:a3:16:53:0e:98:43:ce:a4:b8:c3:66:
+ 7a:05:5c:62:10:e8:1b:12:db:7d:2e:76:50:ff:df:d7:6b:1b:
+ cc:8a:cc:71:fa:b3:40:56:7c:33:7a:77:94:5b:f5:0b:53:fb:
+ 0e:5f:bc:68:fb:af:2a:ee:30:37:79:16:93:25:7f:4d:10:ff:
+ 57:fb:bf:6e:3b:33:21:de:79:dc:86:17:59:2d:43:64:b7:a6:
+ 66:87:ea:bc:96:46:19:1a:86:8b:6f:d7:b7:49:00:5b:db:a3:
+ bf:29:9a:ee:f7:d3:33:ae:a3:f4:9e:4c:ca:5e:69:d4:1b:ad:
+ b7:90:77:6a:d8:59:6f:79:ab:01:fa:55:f0:8a:21:66:e5:65:
+ 6e:fd:7c:d3:df:1e:eb:7e:3f:06:90:fb:19:0b:d3:06:02:1b:
+ 78:43:99:a8
+-----BEGIN CERTIFICATE-----
+MIIGKTCCBRGgAwIBAgIQZBvoIM4CCBPzLU0tldZ+ZzANBgkqhkiG9w0BAQUFADCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
+U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
+ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBvDEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
+ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg
+aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDE2MDQGA1UEAxMtVmVy
+aVNpZ24gQ2xhc3MgMyBJbnRlcm5hdGlvbmFsIFNlcnZlciBDQSAtIEczMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmdacYvAV9IGaQQhZjxOdF8mfUdza
+sVLv/+NB3eDfxCjG4615HycQmLi7IJfBKERBD+qpqFLPTU4bi7u1xHbZzFYG7rNV
+ICreFY1xy1TIbxfNiQDk3P/hwB9ocenHKS5+vDv85burJlSLZpDN9pK5MSSAvJ5s
+1fx+0uFLjNxC+kRLX/gYtS4w9D0SmNNiBXNUppyiHb5SgzoHRsQ7AlYhv/JRT9Cm
+mTnprqU/iZucff5NYAclIPe712mDK4KTQzfZg0EbawurSmaET0qO3n40mY5o1so5
+BptMs5pITRNGtFghBMT7oE2sLktiEuP7TfbJUQABH/weaoEqOOC5T9YtRQIDAQAB
+o4ICFTCCAhEwEgYDVR0TAQH/BAgwBgEB/wIBADBwBgNVHSAEaTBnMGUGC2CGSAGG
++EUBBxcDMFYwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9j
+cHMwKgYIKwYBBQUHAgIwHhocaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYTAO
+BgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDov
+L2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwNAYDVR0lBC0wKwYIKwYBBQUH
+AwEGCCsGAQUFBwMCBglghkgBhvhCBAEGCmCGSAGG+EUBCAEwNAYIKwYBBQUHAQEE
+KDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC52ZXJpc2lnbi5jb20wNAYDVR0f
+BC0wKzApoCegJYYjaHR0cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy1nNS5jcmww
+KAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFZlcmlTaWduTVBLSS0yLTcwHQYDVR0O
+BBYEFNebfNgioBX33a1fzimbWMO8RgC1MB8GA1UdIwQYMBaAFH/TZafC3ey78DAJ
+80M5+gKvMzEzMA0GCSqGSIb3DQEBBQUAA4IBAQBxtX1zUkrd1000Ky6vlEalSVAC
+T/gvF3DyE9wfIYaqwk98NzzURniuXXhv0bpavBCrWDbFjGIVRWAXIeLVQqh3oVXY
+QwRR9m66SOZdTLdE0z6k1dYzmp8N5tdOlkSVWmzWoxZTDphDzqS4w2Z6BVxiEOgb
+Ett9LnZQ/9/XaxvMisxx+rNAVnwzeneUW/ULU/sOX7xo+68q7jA3eRaTJX9NEP9X
++79uOzMh3nnchhdZLUNkt6Zmh+q8lkYZGoaLb9e3SQBb26O/KZru99MzrqP0nkzK
+XmnUG623kHdq2FlveasB+lXwiiFm5WVu/XzT3x7rfj8GkPsZC9MGAht4Q5mo
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert94[] = {
+ 0x30, 0x82, 0x06, 0x29, 0x30, 0x82, 0x05, 0x11, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x64, 0x1b, 0xe8, 0x20, 0xce, 0x02, 0x08, 0x13, 0xf3,
+ 0x2d, 0x4d, 0x2d, 0x95, 0xd6, 0x7e, 0x67, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28,
+ 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x56, 0x65, 0x72, 0x69,
+ 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d,
+ 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x31, 0x45, 0x30, 0x43, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x3c, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73,
+ 0x73, 0x20, 0x33, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50,
+ 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
+ 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x35, 0x30,
+ 0x1e, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x32, 0x30, 0x38, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x32, 0x30, 0x37,
+ 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xbc, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0e, 0x56, 0x65,
+ 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e,
+ 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56,
+ 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73,
+ 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x31, 0x3b, 0x30,
+ 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54, 0x65, 0x72, 0x6d,
+ 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x31, 0x30, 0x31, 0x36,
+ 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d, 0x56, 0x65, 0x72,
+ 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20,
+ 0x33, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43,
+ 0x41, 0x20, 0x2d, 0x20, 0x47, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0x99, 0xd6, 0x9c, 0x62, 0xf0, 0x15, 0xf4, 0x81, 0x9a,
+ 0x41, 0x08, 0x59, 0x8f, 0x13, 0x9d, 0x17, 0xc9, 0x9f, 0x51, 0xdc, 0xda,
+ 0xb1, 0x52, 0xef, 0xff, 0xe3, 0x41, 0xdd, 0xe0, 0xdf, 0xc4, 0x28, 0xc6,
+ 0xe3, 0xad, 0x79, 0x1f, 0x27, 0x10, 0x98, 0xb8, 0xbb, 0x20, 0x97, 0xc1,
+ 0x28, 0x44, 0x41, 0x0f, 0xea, 0xa9, 0xa8, 0x52, 0xcf, 0x4d, 0x4e, 0x1b,
+ 0x8b, 0xbb, 0xb5, 0xc4, 0x76, 0xd9, 0xcc, 0x56, 0x06, 0xee, 0xb3, 0x55,
+ 0x20, 0x2a, 0xde, 0x15, 0x8d, 0x71, 0xcb, 0x54, 0xc8, 0x6f, 0x17, 0xcd,
+ 0x89, 0x00, 0xe4, 0xdc, 0xff, 0xe1, 0xc0, 0x1f, 0x68, 0x71, 0xe9, 0xc7,
+ 0x29, 0x2e, 0x7e, 0xbc, 0x3b, 0xfc, 0xe5, 0xbb, 0xab, 0x26, 0x54, 0x8b,
+ 0x66, 0x90, 0xcd, 0xf6, 0x92, 0xb9, 0x31, 0x24, 0x80, 0xbc, 0x9e, 0x6c,
+ 0xd5, 0xfc, 0x7e, 0xd2, 0xe1, 0x4b, 0x8c, 0xdc, 0x42, 0xfa, 0x44, 0x4b,
+ 0x5f, 0xf8, 0x18, 0xb5, 0x2e, 0x30, 0xf4, 0x3d, 0x12, 0x98, 0xd3, 0x62,
+ 0x05, 0x73, 0x54, 0xa6, 0x9c, 0xa2, 0x1d, 0xbe, 0x52, 0x83, 0x3a, 0x07,
+ 0x46, 0xc4, 0x3b, 0x02, 0x56, 0x21, 0xbf, 0xf2, 0x51, 0x4f, 0xd0, 0xa6,
+ 0x99, 0x39, 0xe9, 0xae, 0xa5, 0x3f, 0x89, 0x9b, 0x9c, 0x7d, 0xfe, 0x4d,
+ 0x60, 0x07, 0x25, 0x20, 0xf7, 0xbb, 0xd7, 0x69, 0x83, 0x2b, 0x82, 0x93,
+ 0x43, 0x37, 0xd9, 0x83, 0x41, 0x1b, 0x6b, 0x0b, 0xab, 0x4a, 0x66, 0x84,
+ 0x4f, 0x4a, 0x8e, 0xde, 0x7e, 0x34, 0x99, 0x8e, 0x68, 0xd6, 0xca, 0x39,
+ 0x06, 0x9b, 0x4c, 0xb3, 0x9a, 0x48, 0x4d, 0x13, 0x46, 0xb4, 0x58, 0x21,
+ 0x04, 0xc4, 0xfb, 0xa0, 0x4d, 0xac, 0x2e, 0x4b, 0x62, 0x12, 0xe3, 0xfb,
+ 0x4d, 0xf6, 0xc9, 0x51, 0x00, 0x01, 0x1f, 0xfc, 0x1e, 0x6a, 0x81, 0x2a,
+ 0x38, 0xe0, 0xb9, 0x4f, 0xd6, 0x2d, 0x45, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x02, 0x15, 0x30, 0x82, 0x02, 0x11, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01,
+ 0xff, 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c, 0x68, 0x74,
+ 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65,
+ 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63,
+ 0x70, 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+ 0x01, 0x06, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x01, 0x0c, 0x04, 0x61, 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59,
+ 0x30, 0x57, 0x30, 0x55, 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f,
+ 0x67, 0x69, 0x66, 0x30, 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b,
+ 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac,
+ 0x8d, 0x8e, 0x6b, 0xc3, 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b,
+ 0x19, 0x2e, 0x30, 0x25, 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x2e, 0x67, 0x69, 0x66, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x25,
+ 0x04, 0x2d, 0x30, 0x2b, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01, 0x06,
+ 0x0a, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x45, 0x01, 0x08, 0x01, 0x30,
+ 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67,
+ 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x2d, 0x30, 0x2b, 0x30, 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76,
+ 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x70, 0x63, 0x61, 0x33, 0x2d, 0x67, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x28, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x21, 0x30, 0x1f, 0xa4, 0x1d,
+ 0x30, 0x1b, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x10, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x50, 0x4b,
+ 0x49, 0x2d, 0x32, 0x2d, 0x37, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0xd7, 0x9b, 0x7c, 0xd8, 0x22, 0xa0, 0x15, 0xf7,
+ 0xdd, 0xad, 0x5f, 0xce, 0x29, 0x9b, 0x58, 0xc3, 0xbc, 0x46, 0x00, 0xb5,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0x7f, 0xd3, 0x65, 0xa7, 0xc2, 0xdd, 0xec, 0xbb, 0xf0, 0x30, 0x09,
+ 0xf3, 0x43, 0x39, 0xfa, 0x02, 0xaf, 0x33, 0x31, 0x33, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x82, 0x01, 0x01, 0x00, 0x71, 0xb5, 0x7d, 0x73, 0x52, 0x4a, 0xdd,
+ 0xd7, 0x4d, 0x34, 0x2b, 0x2e, 0xaf, 0x94, 0x46, 0xa5, 0x49, 0x50, 0x02,
+ 0x4f, 0xf8, 0x2f, 0x17, 0x70, 0xf2, 0x13, 0xdc, 0x1f, 0x21, 0x86, 0xaa,
+ 0xc2, 0x4f, 0x7c, 0x37, 0x3c, 0xd4, 0x46, 0x78, 0xae, 0x5d, 0x78, 0x6f,
+ 0xd1, 0xba, 0x5a, 0xbc, 0x10, 0xab, 0x58, 0x36, 0xc5, 0x8c, 0x62, 0x15,
+ 0x45, 0x60, 0x17, 0x21, 0xe2, 0xd5, 0x42, 0xa8, 0x77, 0xa1, 0x55, 0xd8,
+ 0x43, 0x04, 0x51, 0xf6, 0x6e, 0xba, 0x48, 0xe6, 0x5d, 0x4c, 0xb7, 0x44,
+ 0xd3, 0x3e, 0xa4, 0xd5, 0xd6, 0x33, 0x9a, 0x9f, 0x0d, 0xe6, 0xd7, 0x4e,
+ 0x96, 0x44, 0x95, 0x5a, 0x6c, 0xd6, 0xa3, 0x16, 0x53, 0x0e, 0x98, 0x43,
+ 0xce, 0xa4, 0xb8, 0xc3, 0x66, 0x7a, 0x05, 0x5c, 0x62, 0x10, 0xe8, 0x1b,
+ 0x12, 0xdb, 0x7d, 0x2e, 0x76, 0x50, 0xff, 0xdf, 0xd7, 0x6b, 0x1b, 0xcc,
+ 0x8a, 0xcc, 0x71, 0xfa, 0xb3, 0x40, 0x56, 0x7c, 0x33, 0x7a, 0x77, 0x94,
+ 0x5b, 0xf5, 0x0b, 0x53, 0xfb, 0x0e, 0x5f, 0xbc, 0x68, 0xfb, 0xaf, 0x2a,
+ 0xee, 0x30, 0x37, 0x79, 0x16, 0x93, 0x25, 0x7f, 0x4d, 0x10, 0xff, 0x57,
+ 0xfb, 0xbf, 0x6e, 0x3b, 0x33, 0x21, 0xde, 0x79, 0xdc, 0x86, 0x17, 0x59,
+ 0x2d, 0x43, 0x64, 0xb7, 0xa6, 0x66, 0x87, 0xea, 0xbc, 0x96, 0x46, 0x19,
+ 0x1a, 0x86, 0x8b, 0x6f, 0xd7, 0xb7, 0x49, 0x00, 0x5b, 0xdb, 0xa3, 0xbf,
+ 0x29, 0x9a, 0xee, 0xf7, 0xd3, 0x33, 0xae, 0xa3, 0xf4, 0x9e, 0x4c, 0xca,
+ 0x5e, 0x69, 0xd4, 0x1b, 0xad, 0xb7, 0x90, 0x77, 0x6a, 0xd8, 0x59, 0x6f,
+ 0x79, 0xab, 0x01, 0xfa, 0x55, 0xf0, 0x8a, 0x21, 0x66, 0xe5, 0x65, 0x6e,
+ 0xfd, 0x7c, 0xd3, 0xdf, 0x1e, 0xeb, 0x7e, 0x3f, 0x06, 0x90, 0xfb, 0x19,
+ 0x0b, 0xd3, 0x06, 0x02, 0x1b, 0x78, 0x43, 0x99, 0xa8,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 6e:4f:fa:b3:c5:e6:69:c4:d1:67:c9:92:ab:e8:58:c4
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network
+ Validity
+ Not Before: Mar 25 00:00:00 2009 GMT
+ Not After : Mar 24 23:59:59 2019 GMT
+ Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)09, CN=VeriSign Class 3 Secure Server CA - G2
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d4:56:8f:57:3b:37:28:a6:40:63:d2:95:d5:05:
+ 74:da:b5:19:6a:96:d6:71:57:2f:e2:c0:34:8c:a0:
+ 95:b3:8c:e1:37:24:f3:2e:ed:43:45:05:8e:89:d7:
+ fa:da:4a:b5:f8:3e:8d:4e:c7:f9:49:50:45:37:40:
+ 9f:74:aa:a0:51:55:61:f1:60:84:89:a5:9e:80:8d:
+ 2f:b0:21:aa:45:82:c4:cf:b4:14:7f:47:15:20:28:
+ 82:b0:68:12:c0:ae:5c:07:d7:f6:59:cc:cb:62:56:
+ 5c:4d:49:ff:26:88:ab:54:51:3a:2f:4a:da:0e:98:
+ e2:89:72:b9:fc:f7:68:3c:c4:1f:39:7a:cb:17:81:
+ f3:0c:ad:0f:dc:61:62:1b:10:0b:04:1e:29:18:71:
+ 5e:62:cb:43:de:be:31:ba:71:02:19:4e:26:a9:51:
+ da:8c:64:69:03:de:9c:fd:7d:fd:7b:61:bc:fc:84:
+ 7c:88:5c:b4:c3:7b:ed:5f:2b:46:12:f1:fd:00:01:
+ 9a:8b:5b:e9:a3:05:2e:8f:2e:5b:de:f3:1b:78:f8:
+ 66:91:08:c0:5e:ce:d5:b0:36:ca:d4:a8:7b:a0:7d:
+ f9:30:7a:bf:f8:dd:19:51:2b:20:ba:fe:a7:cf:a1:
+ 4e:b0:67:f5:80:aa:2b:83:2e:d2:8e:54:89:8e:1e:
+ 29:0b
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ Authority Information Access:
+ OCSP - URI:http://ocsp.verisign.com
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.113733.1.7.23.3
+ CPS: https://www.verisign.com/cps
+ User Notice:
+ Explicit Text: https://www.verisign.com/rpa
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl.verisign.com/pca3-g2.crl
+
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ 1.3.6.1.5.5.7.1.12:
+ 0_.].[0Y0W0U..image/gif0!0.0...+..............k...j.H.,{..0%.#http://logo.verisign.com/vslogo.gif
+ X509v3 Subject Alternative Name:
+ DirName:/CN=Class3CA2048-1-52
+ X509v3 Subject Key Identifier:
+ A5:EF:0B:11:CE:C0:41:03:A3:4A:65:90:48:B2:1C:E0:57:2D:7D:47
+ X509v3 Authority Key Identifier:
+ DirName:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority - G2/OU=(c) 1998 VeriSign, Inc. - For authorized use only/OU=VeriSign Trust Network
+ serial:7D:D9:FE:07:CF:A8:1E:B7:10:79:67:FB:A7:89:34:C6
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 63:74:2f:3d:53:aa:2f:97:ec:26:11:66:1a:fe:f1:de:41:27:
+ 19:d2:7f:d8:c1:1c:f9:e2:38:56:3a:1f:90:ae:39:c5:20:75:
+ ab:f8:6c:2d:67:1f:29:c2:21:d7:14:88:63:4b:b0:9b:27:63:
+ 91:f8:f0:a3:01:24:b6:fb:8f:e3:3d:02:0b:6f:54:fe:d4:cc:
+ db:d6:85:bf:7c:95:1e:5e:62:11:c1:d9:09:9c:42:b9:b2:d4:
+ aa:2d:98:3a:23:60:cc:a2:9a:f1:6e:e8:cf:8e:d1:1a:3c:5e:
+ 19:c5:d7:9b:35:b0:02:23:24:e5:05:b8:d5:88:e3:e0:fa:b9:
+ f4:5f
+-----BEGIN CERTIFICATE-----
+MIIGLDCCBZWgAwIBAgIQbk/6s8XmacTRZ8mSq+hYxDANBgkqhkiG9w0BAQUFADCB
+wTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTwwOgYDVQQL
+EzNDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+IC0gRzIxOjA4BgNVBAsTMShjKSAxOTk4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1
+dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmswHhcNMDkwMzI1MDAwMDAwWhcNMTkwMzI0MjM1OTU5WjCBtTELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93
+d3cudmVyaXNpZ24uY29tL3JwYSAoYykwOTEvMC0GA1UEAxMmVmVyaVNpZ24gQ2xh
+c3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDUVo9XOzcopkBj0pXVBXTatRlqltZxVy/iwDSMoJWzjOE3JPMu
+7UNFBY6J1/raSrX4Po1Ox/lJUEU3QJ90qqBRVWHxYISJpZ6AjS+wIapFgsTPtBR/
+RxUgKIKwaBLArlwH1/ZZzMtiVlxNSf8miKtUUTovStoOmOKJcrn892g8xB85essX
+gfMMrQ/cYWIbEAsEHikYcV5iy0PevjG6cQIZTiapUdqMZGkD3pz9ff17Ybz8hHyI
+XLTDe+1fK0YS8f0AAZqLW+mjBS6PLlve8xt4+GaRCMBeztWwNsrUqHugffkwer/4
+3RlRKyC6/qfPoU6wZ/WAqiuDLtKOVImOHikLAgMBAAGjggKpMIICpTA0BggrBgEF
+BQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnZlcmlzaWduLmNvbTAS
+BgNVHRMBAf8ECDAGAQH/AgEAMHAGA1UdIARpMGcwZQYLYIZIAYb4RQEHFwMwVjAo
+BggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL2NwczAqBggrBgEF
+BQcCAjAeGhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMtZzIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjBtBggrBgEFBQcBDARhMF+hXaBbMFkwVzBVFglpbWFnZS9naWYw
+ITAfMAcGBSsOAwIaBBSP5dMahqyNjmvDz4Bq1EgYLHsZLjAlFiNodHRwOi8vbG9n
+by52ZXJpc2lnbi5jb20vdnNsb2dvLmdpZjApBgNVHREEIjAgpB4wHDEaMBgGA1UE
+AxMRQ2xhc3MzQ0EyMDQ4LTEtNTIwHQYDVR0OBBYEFKXvCxHOwEEDo0plkEiyHOBX
+LX1HMIHnBgNVHSMEgd8wgdyhgcekgcQwgcExCzAJBgNVBAYTAlVTMRcwFQYDVQQK
+Ew5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5
+OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYD
+VQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrghB92f4Hz6getxB5Z/uniTTGMA0G
+CSqGSIb3DQEBBQUAA4GBAGN0Lz1Tqi+X7CYRZhr+8d5BJxnSf9jBHPniOFY6H5Cu
+OcUgdav4bC1nHynCIdcUiGNLsJsnY5H48KMBJLb7j+M9AgtvVP7UzNvWhb98lR5e
+YhHB2QmcQrmy1KotmDojYMyimvFu6M+O0Ro8XhnF15s1sAIjJOUFuNWI4+D6ufRf
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert95[] = {
+ 0x30, 0x82, 0x06, 0x2c, 0x30, 0x82, 0x05, 0x95, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x6e, 0x4f, 0xfa, 0xb3, 0xc5, 0xe6, 0x69, 0xc4, 0xd1,
+ 0x67, 0xc9, 0x92, 0xab, 0xe8, 0x58, 0xc4, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0xc1, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x33, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50, 0x75,
+ 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79,
+ 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79,
+ 0x20, 0x2d, 0x20, 0x47, 0x32, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38,
+ 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e,
+ 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f,
+ 0x72, 0x6b, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x33, 0x32, 0x35,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x30,
+ 0x33, 0x32, 0x34, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81,
+ 0xb5, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20, 0x49,
+ 0x6e, 0x63, 0x2e, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x54,
+ 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+ 0x31, 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x32, 0x54,
+ 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, 0x20,
+ 0x61, 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61, 0x20, 0x28, 0x63, 0x29, 0x30,
+ 0x39, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+ 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x43, 0x6c, 0x61,
+ 0x73, 0x73, 0x20, 0x33, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x20, 0x2d, 0x20,
+ 0x47, 0x32, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd4,
+ 0x56, 0x8f, 0x57, 0x3b, 0x37, 0x28, 0xa6, 0x40, 0x63, 0xd2, 0x95, 0xd5,
+ 0x05, 0x74, 0xda, 0xb5, 0x19, 0x6a, 0x96, 0xd6, 0x71, 0x57, 0x2f, 0xe2,
+ 0xc0, 0x34, 0x8c, 0xa0, 0x95, 0xb3, 0x8c, 0xe1, 0x37, 0x24, 0xf3, 0x2e,
+ 0xed, 0x43, 0x45, 0x05, 0x8e, 0x89, 0xd7, 0xfa, 0xda, 0x4a, 0xb5, 0xf8,
+ 0x3e, 0x8d, 0x4e, 0xc7, 0xf9, 0x49, 0x50, 0x45, 0x37, 0x40, 0x9f, 0x74,
+ 0xaa, 0xa0, 0x51, 0x55, 0x61, 0xf1, 0x60, 0x84, 0x89, 0xa5, 0x9e, 0x80,
+ 0x8d, 0x2f, 0xb0, 0x21, 0xaa, 0x45, 0x82, 0xc4, 0xcf, 0xb4, 0x14, 0x7f,
+ 0x47, 0x15, 0x20, 0x28, 0x82, 0xb0, 0x68, 0x12, 0xc0, 0xae, 0x5c, 0x07,
+ 0xd7, 0xf6, 0x59, 0xcc, 0xcb, 0x62, 0x56, 0x5c, 0x4d, 0x49, 0xff, 0x26,
+ 0x88, 0xab, 0x54, 0x51, 0x3a, 0x2f, 0x4a, 0xda, 0x0e, 0x98, 0xe2, 0x89,
+ 0x72, 0xb9, 0xfc, 0xf7, 0x68, 0x3c, 0xc4, 0x1f, 0x39, 0x7a, 0xcb, 0x17,
+ 0x81, 0xf3, 0x0c, 0xad, 0x0f, 0xdc, 0x61, 0x62, 0x1b, 0x10, 0x0b, 0x04,
+ 0x1e, 0x29, 0x18, 0x71, 0x5e, 0x62, 0xcb, 0x43, 0xde, 0xbe, 0x31, 0xba,
+ 0x71, 0x02, 0x19, 0x4e, 0x26, 0xa9, 0x51, 0xda, 0x8c, 0x64, 0x69, 0x03,
+ 0xde, 0x9c, 0xfd, 0x7d, 0xfd, 0x7b, 0x61, 0xbc, 0xfc, 0x84, 0x7c, 0x88,
+ 0x5c, 0xb4, 0xc3, 0x7b, 0xed, 0x5f, 0x2b, 0x46, 0x12, 0xf1, 0xfd, 0x00,
+ 0x01, 0x9a, 0x8b, 0x5b, 0xe9, 0xa3, 0x05, 0x2e, 0x8f, 0x2e, 0x5b, 0xde,
+ 0xf3, 0x1b, 0x78, 0xf8, 0x66, 0x91, 0x08, 0xc0, 0x5e, 0xce, 0xd5, 0xb0,
+ 0x36, 0xca, 0xd4, 0xa8, 0x7b, 0xa0, 0x7d, 0xf9, 0x30, 0x7a, 0xbf, 0xf8,
+ 0xdd, 0x19, 0x51, 0x2b, 0x20, 0xba, 0xfe, 0xa7, 0xcf, 0xa1, 0x4e, 0xb0,
+ 0x67, 0xf5, 0x80, 0xaa, 0x2b, 0x83, 0x2e, 0xd2, 0x8e, 0x54, 0x89, 0x8e,
+ 0x1e, 0x29, 0x0b, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x02, 0xa9,
+ 0x30, 0x82, 0x02, 0xa5, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x76, 0x65,
+ 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x12,
+ 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06,
+ 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x0b, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x45, 0x01, 0x07, 0x17, 0x03, 0x30, 0x56, 0x30, 0x28,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1c,
+ 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x63, 0x70, 0x73, 0x30, 0x2a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x02, 0x30, 0x1e, 0x1a, 0x1c, 0x68, 0x74, 0x74, 0x70,
+ 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x72, 0x69,
+ 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x70, 0x61,
+ 0x30, 0x34, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x2d, 0x30, 0x2b, 0x30,
+ 0x29, 0xa0, 0x27, 0xa0, 0x25, 0x86, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69,
+ 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x63, 0x61, 0x33, 0x2d,
+ 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x6d,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x0c, 0x04, 0x61,
+ 0x30, 0x5f, 0xa1, 0x5d, 0xa0, 0x5b, 0x30, 0x59, 0x30, 0x57, 0x30, 0x55,
+ 0x16, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 0x69, 0x66, 0x30,
+ 0x21, 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a,
+ 0x04, 0x14, 0x8f, 0xe5, 0xd3, 0x1a, 0x86, 0xac, 0x8d, 0x8e, 0x6b, 0xc3,
+ 0xcf, 0x80, 0x6a, 0xd4, 0x48, 0x18, 0x2c, 0x7b, 0x19, 0x2e, 0x30, 0x25,
+ 0x16, 0x23, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x2e, 0x76, 0x65, 0x72, 0x69, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x76, 0x73, 0x6c, 0x6f, 0x67, 0x6f, 0x2e, 0x67, 0x69,
+ 0x66, 0x30, 0x29, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x22, 0x30, 0x20,
+ 0xa4, 0x1e, 0x30, 0x1c, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x11, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x33, 0x43, 0x41, 0x32,
+ 0x30, 0x34, 0x38, 0x2d, 0x31, 0x2d, 0x35, 0x32, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xa5, 0xef, 0x0b, 0x11, 0xce,
+ 0xc0, 0x41, 0x03, 0xa3, 0x4a, 0x65, 0x90, 0x48, 0xb2, 0x1c, 0xe0, 0x57,
+ 0x2d, 0x7d, 0x47, 0x30, 0x81, 0xe7, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x81, 0xdf, 0x30, 0x81, 0xdc, 0xa1, 0x81, 0xc7, 0xa4, 0x81, 0xc4, 0x30,
+ 0x81, 0xc1, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x3c, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x33, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x33, 0x20, 0x50,
+ 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72,
+ 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, 0x31, 0x3a, 0x30, 0x38, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x31, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39,
+ 0x38, 0x20, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x2d, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x75, 0x73,
+ 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x16, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67,
+ 0x6e, 0x20, 0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x4e, 0x65, 0x74, 0x77,
+ 0x6f, 0x72, 0x6b, 0x82, 0x10, 0x7d, 0xd9, 0xfe, 0x07, 0xcf, 0xa8, 0x1e,
+ 0xb7, 0x10, 0x79, 0x67, 0xfb, 0xa7, 0x89, 0x34, 0xc6, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x81, 0x81, 0x00, 0x63, 0x74, 0x2f, 0x3d, 0x53, 0xaa, 0x2f, 0x97,
+ 0xec, 0x26, 0x11, 0x66, 0x1a, 0xfe, 0xf1, 0xde, 0x41, 0x27, 0x19, 0xd2,
+ 0x7f, 0xd8, 0xc1, 0x1c, 0xf9, 0xe2, 0x38, 0x56, 0x3a, 0x1f, 0x90, 0xae,
+ 0x39, 0xc5, 0x20, 0x75, 0xab, 0xf8, 0x6c, 0x2d, 0x67, 0x1f, 0x29, 0xc2,
+ 0x21, 0xd7, 0x14, 0x88, 0x63, 0x4b, 0xb0, 0x9b, 0x27, 0x63, 0x91, 0xf8,
+ 0xf0, 0xa3, 0x01, 0x24, 0xb6, 0xfb, 0x8f, 0xe3, 0x3d, 0x02, 0x0b, 0x6f,
+ 0x54, 0xfe, 0xd4, 0xcc, 0xdb, 0xd6, 0x85, 0xbf, 0x7c, 0x95, 0x1e, 0x5e,
+ 0x62, 0x11, 0xc1, 0xd9, 0x09, 0x9c, 0x42, 0xb9, 0xb2, 0xd4, 0xaa, 0x2d,
+ 0x98, 0x3a, 0x23, 0x60, 0xcc, 0xa2, 0x9a, 0xf1, 0x6e, 0xe8, 0xcf, 0x8e,
+ 0xd1, 0x1a, 0x3c, 0x5e, 0x19, 0xc5, 0xd7, 0x9b, 0x35, 0xb0, 0x02, 0x23,
+ 0x24, 0xe5, 0x05, 0xb8, 0xd5, 0x88, 0xe3, 0xe0, 0xfa, 0xb9, 0xf4, 0x5f,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 24 (0x18)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+ Validity
+ Not Before: Oct 24 20:54:17 2007 GMT
+ Not After : Oct 24 20:54:17 2017 GMT
+ Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 1 Primary Intermediate Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b6:89:c6:ac:ef:09:52:78:07:ac:92:63:d0:f4:
+ 44:18:18:84:80:56:1f:91:ae:e1:87:fa:32:50:b4:
+ d3:47:06:f0:e6:07:5f:70:0e:10:f7:1d:c0:ce:10:
+ 36:34:85:5a:0f:92:ac:83:c6:ac:58:52:3f:ba:38:
+ e8:fc:e7:a7:24:e2:40:a6:08:76:c0:92:6e:9e:2a:
+ 6d:4d:3f:6e:61:20:0a:db:59:de:d2:7d:63:b3:3e:
+ 46:fe:fa:21:51:18:d7:cd:30:a6:ed:07:6e:3b:70:
+ 87:b4:f9:fa:eb:ee:82:3c:05:6f:92:f7:a4:dc:0a:
+ 30:1e:93:73:fe:07:ca:d7:5f:80:9d:22:58:52:ae:
+ 06:da:8b:87:23:69:b0:e4:2a:d8:ea:83:d2:bd:f3:
+ 71:db:70:5a:28:0f:af:5a:38:70:45:12:3f:30:4d:
+ cd:3b:af:17:e5:0f:cb:a0:a9:5d:48:aa:b1:61:50:
+ cb:34:cd:3c:5c:c3:0b:e8:10:c0:8c:9b:f0:03:03:
+ 62:fe:b2:6c:3e:72:0e:ee:1c:43:2a:c9:48:0e:57:
+ 39:c4:31:21:c8:10:c1:2c:87:fe:54:95:52:1f:52:
+ 3c:31:12:9b:7f:e7:c0:a0:a5:59:d5:e2:8f:3e:f0:
+ d5:a8:e1:d7:70:31:a9:c4:b3:cf:af:6d:53:2f:06:
+ f4:a7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ EB:42:34:D0:98:B0:AB:9F:F4:1B:6B:08:F7:CC:64:2E:EF:0E:2C:45
+ X509v3 Authority Key Identifier:
+ keyid:4E:0B:EF:1A:A4:40:5B:A5:17:69:87:30:CA:34:68:43:D0:41:AE:F2
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.startssl.com/ca
+ CA Issuers - URI:http://www.startssl.com/sfsca.crt
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.startssl.com/sfsca.crl
+
+ Full Name:
+ URI:http://crl.startssl.com/sfsca.crl
+
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.23223.1.2.1
+ CPS: http://www.startssl.com/policy.pdf
+ CPS: http://www.startssl.com/intermediate.pdf
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 21:09:49:3e:a5:88:6e:e0:0b:8b:48:da:31:4d:8f:f7:56:57:
+ a2:e1:d3:62:57:e9:b5:56:f3:85:45:75:3b:e5:50:1f:04:8b:
+ e6:a0:5a:3e:e7:00:ae:85:d0:fb:ff:20:03:64:cb:ad:02:e1:
+ c6:91:72:f8:a3:4d:d6:de:e8:cc:3f:a1:8a:a2:e3:7c:37:a7:
+ c6:4f:8f:35:d6:f4:d6:6e:06:7b:dd:21:d9:cf:56:ff:cb:30:
+ 22:49:fe:89:04:f3:85:e5:aa:f1:e7:1f:e8:75:90:4d:dd:f9:
+ 46:f7:42:34:f7:45:58:0c:11:0d:84:b0:c6:da:5d:3e:f9:01:
+ 9e:e7:e1:da:55:95:be:74:1c:7b:fc:4d:14:4f:ac:7e:55:47:
+ 7d:7b:f4:a5:0d:49:1e:95:e8:f7:12:c1:cc:ff:76:a6:25:47:
+ d0:f3:75:35:be:97:b7:58:16:eb:aa:5c:78:6f:ec:53:30:af:
+ ea:04:4d:cc:a9:02:e3:f0:b6:04:12:f6:30:b1:11:3d:90:4e:
+ 56:64:d7:dc:3c:43:5f:73:39:ef:4b:af:87:eb:f6:fe:68:88:
+ 44:72:ea:d2:07:c6:69:b0:c1:a1:8b:ef:17:49:d7:61:b1:45:
+ 48:5f:3b:20:21:e9:5b:b2:cc:f4:d7:e9:31:f5:0b:15:61:3b:
+ 7a:94:e3:eb:d9:bc:7f:94:ae:6a:e3:62:62:96:a8:64:7c:b8:
+ 87:f3:99:32:7e:92:a2:52:be:bb:f8:65:cf:c9:f2:30:fc:8b:
+ c1:c2:a6:96:d7:5f:89:e1:5c:34:80:f5:8f:47:07:2f:b4:91:
+ bf:b1:a2:7e:5f:4b:5a:d0:5b:9f:24:86:05:51:5a:69:03:65:
+ 43:49:71:c5:e0:6f:94:34:6b:f6:1b:d8:a9:b0:4c:7e:53:eb:
+ 8f:48:df:ca:33:b5:48:fa:36:4a:1a:53:a6:33:0c:d0:89:cd:
+ 49:15:cd:89:31:3c:90:c0:72:d7:65:4b:52:35:8a:46:11:44:
+ b9:3d:8e:28:65:a6:3e:79:9e:5c:08:44:29:ad:b0:35:11:2e:
+ 21:4e:b8:d2:e7:10:3e:5d:84:83:b3:c3:c2:e4:d2:c6:fd:09:
+ 4b:74:09:dd:f1:b3:d3:19:3e:80:0d:a2:0b:19:f0:38:e7:c5:
+ c2:af:e2:23:db:61:e2:9d:5c:6e:20:89:49:2e:23:6a:b2:62:
+ c1:45:b4:9f:af:8b:a7:f1:22:3b:f8:7d:e2:90:d0:7a:19:fb:
+ 4a:4c:e3:d2:7d:5f:4a:83:03:ed:27:d6:23:9e:6b:8d:b4:59:
+ a2:d9:ef:6c:82:29:dd:75:19:3c:3f:4c:10:8d:ef:bb:75:27:
+ d2:ae:83:a7:a8:ce:5b:a7
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE
+gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA
+pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv
+kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/
+ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5
+xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp
+tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen
+xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw
+xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X
+t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI
+RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi
+YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L
+WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN
+SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD
+wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L
+p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un
+0q6Dp6jOW6c=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert96[] = {
+ 0x30, 0x82, 0x06, 0x34, 0x30, 0x82, 0x04, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x01, 0x18, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16,
+ 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61,
+ 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b,
+ 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+ 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43,
+ 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x30, 0x32, 0x34,
+ 0x32, 0x30, 0x35, 0x34, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x31,
+ 0x30, 0x32, 0x34, 0x32, 0x30, 0x35, 0x34, 0x31, 0x37, 0x5a, 0x30, 0x81,
+ 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74,
+ 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69,
+ 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31,
+ 0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x53, 0x74,
+ 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73,
+ 0x20, 0x31, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x49,
+ 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb6, 0x89, 0xc6, 0xac, 0xef, 0x09,
+ 0x52, 0x78, 0x07, 0xac, 0x92, 0x63, 0xd0, 0xf4, 0x44, 0x18, 0x18, 0x84,
+ 0x80, 0x56, 0x1f, 0x91, 0xae, 0xe1, 0x87, 0xfa, 0x32, 0x50, 0xb4, 0xd3,
+ 0x47, 0x06, 0xf0, 0xe6, 0x07, 0x5f, 0x70, 0x0e, 0x10, 0xf7, 0x1d, 0xc0,
+ 0xce, 0x10, 0x36, 0x34, 0x85, 0x5a, 0x0f, 0x92, 0xac, 0x83, 0xc6, 0xac,
+ 0x58, 0x52, 0x3f, 0xba, 0x38, 0xe8, 0xfc, 0xe7, 0xa7, 0x24, 0xe2, 0x40,
+ 0xa6, 0x08, 0x76, 0xc0, 0x92, 0x6e, 0x9e, 0x2a, 0x6d, 0x4d, 0x3f, 0x6e,
+ 0x61, 0x20, 0x0a, 0xdb, 0x59, 0xde, 0xd2, 0x7d, 0x63, 0xb3, 0x3e, 0x46,
+ 0xfe, 0xfa, 0x21, 0x51, 0x18, 0xd7, 0xcd, 0x30, 0xa6, 0xed, 0x07, 0x6e,
+ 0x3b, 0x70, 0x87, 0xb4, 0xf9, 0xfa, 0xeb, 0xee, 0x82, 0x3c, 0x05, 0x6f,
+ 0x92, 0xf7, 0xa4, 0xdc, 0x0a, 0x30, 0x1e, 0x93, 0x73, 0xfe, 0x07, 0xca,
+ 0xd7, 0x5f, 0x80, 0x9d, 0x22, 0x58, 0x52, 0xae, 0x06, 0xda, 0x8b, 0x87,
+ 0x23, 0x69, 0xb0, 0xe4, 0x2a, 0xd8, 0xea, 0x83, 0xd2, 0xbd, 0xf3, 0x71,
+ 0xdb, 0x70, 0x5a, 0x28, 0x0f, 0xaf, 0x5a, 0x38, 0x70, 0x45, 0x12, 0x3f,
+ 0x30, 0x4d, 0xcd, 0x3b, 0xaf, 0x17, 0xe5, 0x0f, 0xcb, 0xa0, 0xa9, 0x5d,
+ 0x48, 0xaa, 0xb1, 0x61, 0x50, 0xcb, 0x34, 0xcd, 0x3c, 0x5c, 0xc3, 0x0b,
+ 0xe8, 0x10, 0xc0, 0x8c, 0x9b, 0xf0, 0x03, 0x03, 0x62, 0xfe, 0xb2, 0x6c,
+ 0x3e, 0x72, 0x0e, 0xee, 0x1c, 0x43, 0x2a, 0xc9, 0x48, 0x0e, 0x57, 0x39,
+ 0xc4, 0x31, 0x21, 0xc8, 0x10, 0xc1, 0x2c, 0x87, 0xfe, 0x54, 0x95, 0x52,
+ 0x1f, 0x52, 0x3c, 0x31, 0x12, 0x9b, 0x7f, 0xe7, 0xc0, 0xa0, 0xa5, 0x59,
+ 0xd5, 0xe2, 0x8f, 0x3e, 0xf0, 0xd5, 0xa8, 0xe1, 0xd7, 0x70, 0x31, 0xa9,
+ 0xc4, 0xb3, 0xcf, 0xaf, 0x6d, 0x53, 0x2f, 0x06, 0xf4, 0xa7, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xad, 0x30, 0x82, 0x01, 0xa9, 0x30,
+ 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+ 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xeb, 0x42, 0x34, 0xd0, 0x98,
+ 0xb0, 0xab, 0x9f, 0xf4, 0x1b, 0x6b, 0x08, 0xf7, 0xcc, 0x64, 0x2e, 0xef,
+ 0x0e, 0x2c, 0x45, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, 0xa4, 0x40, 0x5b, 0xa5,
+ 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, 0xd0, 0x41, 0xae, 0xf2,
+ 0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x5a, 0x30, 0x58, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73,
+ 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x30, 0x2d, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x74,
+ 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73,
+ 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x5b, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x54, 0x30, 0x52, 0x30, 0x27, 0xa0, 0x25, 0xa0,
+ 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+ 0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74,
+ 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63,
+ 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x79, 0x30, 0x77, 0x30, 0x75, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x81, 0xb5, 0x37, 0x01, 0x02, 0x01, 0x30, 0x66, 0x30, 0x2e,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x64, 0x66, 0x30, 0x34,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x28,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65,
+ 0x2e, 0x70, 0x64, 0x66, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00,
+ 0x21, 0x09, 0x49, 0x3e, 0xa5, 0x88, 0x6e, 0xe0, 0x0b, 0x8b, 0x48, 0xda,
+ 0x31, 0x4d, 0x8f, 0xf7, 0x56, 0x57, 0xa2, 0xe1, 0xd3, 0x62, 0x57, 0xe9,
+ 0xb5, 0x56, 0xf3, 0x85, 0x45, 0x75, 0x3b, 0xe5, 0x50, 0x1f, 0x04, 0x8b,
+ 0xe6, 0xa0, 0x5a, 0x3e, 0xe7, 0x00, 0xae, 0x85, 0xd0, 0xfb, 0xff, 0x20,
+ 0x03, 0x64, 0xcb, 0xad, 0x02, 0xe1, 0xc6, 0x91, 0x72, 0xf8, 0xa3, 0x4d,
+ 0xd6, 0xde, 0xe8, 0xcc, 0x3f, 0xa1, 0x8a, 0xa2, 0xe3, 0x7c, 0x37, 0xa7,
+ 0xc6, 0x4f, 0x8f, 0x35, 0xd6, 0xf4, 0xd6, 0x6e, 0x06, 0x7b, 0xdd, 0x21,
+ 0xd9, 0xcf, 0x56, 0xff, 0xcb, 0x30, 0x22, 0x49, 0xfe, 0x89, 0x04, 0xf3,
+ 0x85, 0xe5, 0xaa, 0xf1, 0xe7, 0x1f, 0xe8, 0x75, 0x90, 0x4d, 0xdd, 0xf9,
+ 0x46, 0xf7, 0x42, 0x34, 0xf7, 0x45, 0x58, 0x0c, 0x11, 0x0d, 0x84, 0xb0,
+ 0xc6, 0xda, 0x5d, 0x3e, 0xf9, 0x01, 0x9e, 0xe7, 0xe1, 0xda, 0x55, 0x95,
+ 0xbe, 0x74, 0x1c, 0x7b, 0xfc, 0x4d, 0x14, 0x4f, 0xac, 0x7e, 0x55, 0x47,
+ 0x7d, 0x7b, 0xf4, 0xa5, 0x0d, 0x49, 0x1e, 0x95, 0xe8, 0xf7, 0x12, 0xc1,
+ 0xcc, 0xff, 0x76, 0xa6, 0x25, 0x47, 0xd0, 0xf3, 0x75, 0x35, 0xbe, 0x97,
+ 0xb7, 0x58, 0x16, 0xeb, 0xaa, 0x5c, 0x78, 0x6f, 0xec, 0x53, 0x30, 0xaf,
+ 0xea, 0x04, 0x4d, 0xcc, 0xa9, 0x02, 0xe3, 0xf0, 0xb6, 0x04, 0x12, 0xf6,
+ 0x30, 0xb1, 0x11, 0x3d, 0x90, 0x4e, 0x56, 0x64, 0xd7, 0xdc, 0x3c, 0x43,
+ 0x5f, 0x73, 0x39, 0xef, 0x4b, 0xaf, 0x87, 0xeb, 0xf6, 0xfe, 0x68, 0x88,
+ 0x44, 0x72, 0xea, 0xd2, 0x07, 0xc6, 0x69, 0xb0, 0xc1, 0xa1, 0x8b, 0xef,
+ 0x17, 0x49, 0xd7, 0x61, 0xb1, 0x45, 0x48, 0x5f, 0x3b, 0x20, 0x21, 0xe9,
+ 0x5b, 0xb2, 0xcc, 0xf4, 0xd7, 0xe9, 0x31, 0xf5, 0x0b, 0x15, 0x61, 0x3b,
+ 0x7a, 0x94, 0xe3, 0xeb, 0xd9, 0xbc, 0x7f, 0x94, 0xae, 0x6a, 0xe3, 0x62,
+ 0x62, 0x96, 0xa8, 0x64, 0x7c, 0xb8, 0x87, 0xf3, 0x99, 0x32, 0x7e, 0x92,
+ 0xa2, 0x52, 0xbe, 0xbb, 0xf8, 0x65, 0xcf, 0xc9, 0xf2, 0x30, 0xfc, 0x8b,
+ 0xc1, 0xc2, 0xa6, 0x96, 0xd7, 0x5f, 0x89, 0xe1, 0x5c, 0x34, 0x80, 0xf5,
+ 0x8f, 0x47, 0x07, 0x2f, 0xb4, 0x91, 0xbf, 0xb1, 0xa2, 0x7e, 0x5f, 0x4b,
+ 0x5a, 0xd0, 0x5b, 0x9f, 0x24, 0x86, 0x05, 0x51, 0x5a, 0x69, 0x03, 0x65,
+ 0x43, 0x49, 0x71, 0xc5, 0xe0, 0x6f, 0x94, 0x34, 0x6b, 0xf6, 0x1b, 0xd8,
+ 0xa9, 0xb0, 0x4c, 0x7e, 0x53, 0xeb, 0x8f, 0x48, 0xdf, 0xca, 0x33, 0xb5,
+ 0x48, 0xfa, 0x36, 0x4a, 0x1a, 0x53, 0xa6, 0x33, 0x0c, 0xd0, 0x89, 0xcd,
+ 0x49, 0x15, 0xcd, 0x89, 0x31, 0x3c, 0x90, 0xc0, 0x72, 0xd7, 0x65, 0x4b,
+ 0x52, 0x35, 0x8a, 0x46, 0x11, 0x44, 0xb9, 0x3d, 0x8e, 0x28, 0x65, 0xa6,
+ 0x3e, 0x79, 0x9e, 0x5c, 0x08, 0x44, 0x29, 0xad, 0xb0, 0x35, 0x11, 0x2e,
+ 0x21, 0x4e, 0xb8, 0xd2, 0xe7, 0x10, 0x3e, 0x5d, 0x84, 0x83, 0xb3, 0xc3,
+ 0xc2, 0xe4, 0xd2, 0xc6, 0xfd, 0x09, 0x4b, 0x74, 0x09, 0xdd, 0xf1, 0xb3,
+ 0xd3, 0x19, 0x3e, 0x80, 0x0d, 0xa2, 0x0b, 0x19, 0xf0, 0x38, 0xe7, 0xc5,
+ 0xc2, 0xaf, 0xe2, 0x23, 0xdb, 0x61, 0xe2, 0x9d, 0x5c, 0x6e, 0x20, 0x89,
+ 0x49, 0x2e, 0x23, 0x6a, 0xb2, 0x62, 0xc1, 0x45, 0xb4, 0x9f, 0xaf, 0x8b,
+ 0xa7, 0xf1, 0x22, 0x3b, 0xf8, 0x7d, 0xe2, 0x90, 0xd0, 0x7a, 0x19, 0xfb,
+ 0x4a, 0x4c, 0xe3, 0xd2, 0x7d, 0x5f, 0x4a, 0x83, 0x03, 0xed, 0x27, 0xd6,
+ 0x23, 0x9e, 0x6b, 0x8d, 0xb4, 0x59, 0xa2, 0xd9, 0xef, 0x6c, 0x82, 0x29,
+ 0xdd, 0x75, 0x19, 0x3c, 0x3f, 0x4c, 0x10, 0x8d, 0xef, 0xbb, 0x75, 0x27,
+ 0xd2, 0xae, 0x83, 0xa7, 0xa8, 0xce, 0x5b, 0xa7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 26 (0x1a)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority
+ Validity
+ Not Before: Oct 24 20:57:09 2007 GMT
+ Not After : Oct 24 20:57:09 2017 GMT
+ Subject: C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Class 2 Primary Intermediate Server CA
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:e2:4f:39:2f:a1:8c:9a:85:ad:08:0e:08:3e:57:
+ f2:88:01:21:1b:94:a9:6c:e2:b8:db:aa:19:18:46:
+ 3a:52:a1:f5:0f:f4:6e:8c:ea:96:8c:96:87:79:13:
+ 40:51:2f:22:f2:0c:8b:87:0f:65:df:71:74:34:43:
+ 55:b1:35:09:9b:d9:bc:1f:fa:eb:42:d0:97:40:72:
+ b7:43:96:3d:ba:96:9d:5d:50:02:1c:9b:91:8d:9c:
+ c0:ac:d7:bb:2f:17:d7:cb:3e:82:9d:73:eb:07:42:
+ 92:b2:cd:64:b3:74:55:1b:b4:4b:86:21:2c:f7:78:
+ 87:32:e0:16:e4:da:bd:4c:95:ea:a4:0a:7e:b6:0a:
+ 0d:2e:8a:cf:55:ab:c3:e5:dd:41:8a:4e:e6:6f:65:
+ 6c:b2:40:cf:17:5d:b9:c3:6a:0b:27:11:84:77:61:
+ f6:c2:7c:ed:c0:8d:78:14:18:99:81:99:75:63:b7:
+ e8:53:d3:ba:61:e9:0e:fa:a2:30:f3:46:a2:b9:c9:
+ 1f:6c:80:5a:40:ac:27:ed:48:47:33:b0:54:c6:46:
+ 1a:f3:35:61:c1:02:29:90:54:7e:64:4d:c4:30:52:
+ 02:82:d7:df:ce:21:6e:18:91:d7:b8:ab:8c:27:17:
+ b5:f0:a3:01:2f:8e:d2:2e:87:3a:3d:b4:29:67:8a:
+ c4:03
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ X509v3 Key Usage: critical
+ Certificate Sign, CRL Sign
+ X509v3 Subject Key Identifier:
+ 11:DB:23:45:FD:54:CC:6A:71:6F:84:8A:03:D7:BE:F7:01:2F:26:86
+ X509v3 Authority Key Identifier:
+ keyid:4E:0B:EF:1A:A4:40:5B:A5:17:69:87:30:CA:34:68:43:D0:41:AE:F2
+
+ Authority Information Access:
+ OCSP - URI:http://ocsp.startssl.com/ca
+ CA Issuers - URI:http://www.startssl.com/sfsca.crt
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://www.startssl.com/sfsca.crl
+
+ Full Name:
+ URI:http://crl.startssl.com/sfsca.crl
+
+ X509v3 Certificate Policies:
+ Policy: 1.3.6.1.4.1.23223.1.2.1
+ CPS: http://www.startssl.com/policy.pdf
+ CPS: http://www.startssl.com/intermediate.pdf
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 9d:07:e1:ee:90:76:31:67:16:45:70:8c:cb:84:8b:4b:57:68:
+ 44:a5:89:c1:f2:7e:cb:28:8b:f5:e7:70:77:d5:b6:f4:0b:21:
+ 60:a5:a1:74:73:24:22:80:d6:d8:ba:8d:a2:62:5d:09:35:42:
+ 29:fb:39:63:45:0b:a4:b0:38:1a:68:f4:95:13:cc:e0:43:94:
+ ec:eb:39:1a:ec:57:29:d9:99:6d:f5:84:cd:8e:73:ae:c9:dc:
+ 6a:fa:9e:9d:16:64:93:08:c7:1c:c2:89:54:9e:77:80:90:f6:
+ b9:29:76:eb:13:67:48:59:f8:2e:3a:31:b8:c9:d3:88:e5:5f:
+ 4e:d2:19:3d:43:8e:d7:92:ff:cf:38:b6:e1:5b:8a:53:1d:ce:
+ ac:b4:76:2f:d8:f7:40:63:d5:ee:69:f3:45:7d:a0:62:c1:61:
+ c3:75:ed:b2:7b:4d:ac:21:27:30:4e:59:46:6a:93:17:ca:c8:
+ 39:2d:01:73:65:5b:e9:41:9b:11:17:9c:c8:c8:4a:ef:a1:76:
+ 60:2d:ae:93:ff:0c:d5:33:13:9f:4f:13:ce:dd:86:f1:fc:f8:
+ 35:54:15:a8:5b:e7:85:7e:fa:37:09:ff:8b:b8:31:49:9e:0d:
+ 6e:de:b4:d2:12:2d:b8:ed:c8:c3:f1:b6:42:a0:4c:97:79:df:
+ fe:c3:a3:9f:a1:f4:6d:2c:84:77:a4:a2:05:e1:17:ff:31:dd:
+ 9a:f3:b8:7a:c3:52:c2:11:11:b7:50:31:8a:7f:cc:e7:5a:89:
+ cc:f7:86:9a:61:92:4f:2f:94:b6:98:c7:78:e0:62:4b:43:7d:
+ 3c:de:d6:9a:b4:10:a1:40:9c:4b:2a:dc:b8:d0:d4:9e:fd:f1:
+ 84:78:1b:0e:57:8f:69:54:42:68:7b:ea:a0:ef:75:0f:07:a2:
+ 8c:73:99:ab:55:f5:07:09:d2:af:38:03:6a:90:03:0c:2f:8f:
+ e2:e8:43:c2:31:e9:6f:ad:87:e5:8d:bd:4e:2c:89:4b:51:e6:
+ 9c:4c:54:76:c0:12:81:53:9b:ec:a0:fc:2c:9c:da:18:95:6e:
+ 1e:38:26:42:27:78:60:08:df:7f:6d:32:e8:d8:c0:6f:1f:eb:
+ 26:75:9f:93:fc:7b:1b:fe:35:90:dc:53:a3:07:a6:3f:83:55:
+ 0a:2b:4e:62:82:25:ce:66:30:5d:2c:e0:f9:19:1b:75:b9:9d:
+ 98:56:a6:83:27:7a:d1:8f:8d:59:93:fc:3f:73:d7:2e:b4:2c:
+ 95:d8:8b:f7:c9:7e:c7:fc:9d:ac:72:04:1f:d2:cc:17:f4:ed:
+ 34:60:9b:9e:4a:97:04:fe:dd:72:0e:57:54:51:06:70:4d:ef:
+ aa:1c:a4:82:e0:33:c7:f4
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGjANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh
+G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8
+H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL
+hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE
+d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa
+8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAnQfh7pB2MWcWRXCMy4SLS1doRKWJwfJ+
+yyiL9edwd9W29AshYKWhdHMkIoDW2LqNomJdCTVCKfs5Y0ULpLA4Gmj0lRPM4EOU
+7Os5GuxXKdmZbfWEzY5zrsncavqenRZkkwjHHMKJVJ53gJD2uSl26xNnSFn4Ljox
+uMnTiOVfTtIZPUOO15L/zzi24VuKUx3OrLR2L9j3QGPV7mnzRX2gYsFhw3XtsntN
+rCEnME5ZRmqTF8rIOS0Bc2Vb6UGbERecyMhK76F2YC2uk/8M1TMTn08Tzt2G8fz4
+NVQVqFvnhX76Nwn/i7gxSZ4Nbt600hItuO3Iw/G2QqBMl3nf/sOjn6H0bSyEd6Si
+BeEX/zHdmvO4esNSwhERt1Axin/M51qJzPeGmmGSTy+UtpjHeOBiS0N9PN7WmrQQ
+oUCcSyrcuNDUnv3xhHgbDlePaVRCaHvqoO91DweijHOZq1X1BwnSrzgDapADDC+P
+4uhDwjHpb62H5Y29TiyJS1HmnExUdsASgVOb7KD8LJzaGJVuHjgmQid4YAjff20y
+6NjAbx/rJnWfk/x7G/41kNxTowemP4NVCitOYoIlzmYwXSzg+RkbdbmdmFamgyd6
+0Y+NWZP8P3PXLrQsldiL98l+x/ydrHIEH9LMF/TtNGCbnkqXBP7dcg5XVFEGcE3v
+qhykguAzx/Q=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert97[] = {
+ 0x30, 0x82, 0x06, 0x34, 0x30, 0x82, 0x04, 0x1c, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x01, 0x1a, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x7d, 0x31, 0x0b, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x4c, 0x31, 0x16,
+ 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0d, 0x53, 0x74, 0x61,
+ 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x2b,
+ 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x22, 0x53, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x20,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+ 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43,
+ 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
+ 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x30, 0x32, 0x34,
+ 0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x37, 0x31,
+ 0x30, 0x32, 0x34, 0x32, 0x30, 0x35, 0x37, 0x30, 0x39, 0x5a, 0x30, 0x81,
+ 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c, 0x74,
+ 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69,
+ 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x31,
+ 0x38, 0x30, 0x36, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2f, 0x53, 0x74,
+ 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x6c, 0x61, 0x73, 0x73,
+ 0x20, 0x32, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x20, 0x49,
+ 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x41, 0x30, 0x82, 0x01,
+ 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01,
+ 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe2, 0x4f, 0x39, 0x2f, 0xa1, 0x8c,
+ 0x9a, 0x85, 0xad, 0x08, 0x0e, 0x08, 0x3e, 0x57, 0xf2, 0x88, 0x01, 0x21,
+ 0x1b, 0x94, 0xa9, 0x6c, 0xe2, 0xb8, 0xdb, 0xaa, 0x19, 0x18, 0x46, 0x3a,
+ 0x52, 0xa1, 0xf5, 0x0f, 0xf4, 0x6e, 0x8c, 0xea, 0x96, 0x8c, 0x96, 0x87,
+ 0x79, 0x13, 0x40, 0x51, 0x2f, 0x22, 0xf2, 0x0c, 0x8b, 0x87, 0x0f, 0x65,
+ 0xdf, 0x71, 0x74, 0x34, 0x43, 0x55, 0xb1, 0x35, 0x09, 0x9b, 0xd9, 0xbc,
+ 0x1f, 0xfa, 0xeb, 0x42, 0xd0, 0x97, 0x40, 0x72, 0xb7, 0x43, 0x96, 0x3d,
+ 0xba, 0x96, 0x9d, 0x5d, 0x50, 0x02, 0x1c, 0x9b, 0x91, 0x8d, 0x9c, 0xc0,
+ 0xac, 0xd7, 0xbb, 0x2f, 0x17, 0xd7, 0xcb, 0x3e, 0x82, 0x9d, 0x73, 0xeb,
+ 0x07, 0x42, 0x92, 0xb2, 0xcd, 0x64, 0xb3, 0x74, 0x55, 0x1b, 0xb4, 0x4b,
+ 0x86, 0x21, 0x2c, 0xf7, 0x78, 0x87, 0x32, 0xe0, 0x16, 0xe4, 0xda, 0xbd,
+ 0x4c, 0x95, 0xea, 0xa4, 0x0a, 0x7e, 0xb6, 0x0a, 0x0d, 0x2e, 0x8a, 0xcf,
+ 0x55, 0xab, 0xc3, 0xe5, 0xdd, 0x41, 0x8a, 0x4e, 0xe6, 0x6f, 0x65, 0x6c,
+ 0xb2, 0x40, 0xcf, 0x17, 0x5d, 0xb9, 0xc3, 0x6a, 0x0b, 0x27, 0x11, 0x84,
+ 0x77, 0x61, 0xf6, 0xc2, 0x7c, 0xed, 0xc0, 0x8d, 0x78, 0x14, 0x18, 0x99,
+ 0x81, 0x99, 0x75, 0x63, 0xb7, 0xe8, 0x53, 0xd3, 0xba, 0x61, 0xe9, 0x0e,
+ 0xfa, 0xa2, 0x30, 0xf3, 0x46, 0xa2, 0xb9, 0xc9, 0x1f, 0x6c, 0x80, 0x5a,
+ 0x40, 0xac, 0x27, 0xed, 0x48, 0x47, 0x33, 0xb0, 0x54, 0xc6, 0x46, 0x1a,
+ 0xf3, 0x35, 0x61, 0xc1, 0x02, 0x29, 0x90, 0x54, 0x7e, 0x64, 0x4d, 0xc4,
+ 0x30, 0x52, 0x02, 0x82, 0xd7, 0xdf, 0xce, 0x21, 0x6e, 0x18, 0x91, 0xd7,
+ 0xb8, 0xab, 0x8c, 0x27, 0x17, 0xb5, 0xf0, 0xa3, 0x01, 0x2f, 0x8e, 0xd2,
+ 0x2e, 0x87, 0x3a, 0x3d, 0xb4, 0x29, 0x67, 0x8a, 0xc4, 0x03, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xad, 0x30, 0x82, 0x01, 0xa9, 0x30,
+ 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30,
+ 0x03, 0x01, 0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x11, 0xdb, 0x23, 0x45, 0xfd,
+ 0x54, 0xcc, 0x6a, 0x71, 0x6f, 0x84, 0x8a, 0x03, 0xd7, 0xbe, 0xf7, 0x01,
+ 0x2f, 0x26, 0x86, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x4e, 0x0b, 0xef, 0x1a, 0xa4, 0x40, 0x5b, 0xa5,
+ 0x17, 0x69, 0x87, 0x30, 0xca, 0x34, 0x68, 0x43, 0xd0, 0x41, 0xae, 0xf2,
+ 0x30, 0x66, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x5a, 0x30, 0x58, 0x30, 0x27, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x1b, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73,
+ 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x30, 0x2d, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x21, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x74,
+ 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73,
+ 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x5b, 0x06, 0x03,
+ 0x55, 0x1d, 0x1f, 0x04, 0x54, 0x30, 0x52, 0x30, 0x27, 0xa0, 0x25, 0xa0,
+ 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77,
+ 0x77, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x27, 0xa0, 0x25, 0xa0, 0x23, 0x86, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x74,
+ 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x66, 0x73, 0x63,
+ 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x81, 0x80, 0x06, 0x03, 0x55, 0x1d,
+ 0x20, 0x04, 0x79, 0x30, 0x77, 0x30, 0x75, 0x06, 0x0b, 0x2b, 0x06, 0x01,
+ 0x04, 0x01, 0x81, 0xb5, 0x37, 0x01, 0x02, 0x01, 0x30, 0x66, 0x30, 0x2e,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x64, 0x66, 0x30, 0x34,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x28,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73,
+ 0x74, 0x61, 0x72, 0x74, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65,
+ 0x2e, 0x70, 0x64, 0x66, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00,
+ 0x9d, 0x07, 0xe1, 0xee, 0x90, 0x76, 0x31, 0x67, 0x16, 0x45, 0x70, 0x8c,
+ 0xcb, 0x84, 0x8b, 0x4b, 0x57, 0x68, 0x44, 0xa5, 0x89, 0xc1, 0xf2, 0x7e,
+ 0xcb, 0x28, 0x8b, 0xf5, 0xe7, 0x70, 0x77, 0xd5, 0xb6, 0xf4, 0x0b, 0x21,
+ 0x60, 0xa5, 0xa1, 0x74, 0x73, 0x24, 0x22, 0x80, 0xd6, 0xd8, 0xba, 0x8d,
+ 0xa2, 0x62, 0x5d, 0x09, 0x35, 0x42, 0x29, 0xfb, 0x39, 0x63, 0x45, 0x0b,
+ 0xa4, 0xb0, 0x38, 0x1a, 0x68, 0xf4, 0x95, 0x13, 0xcc, 0xe0, 0x43, 0x94,
+ 0xec, 0xeb, 0x39, 0x1a, 0xec, 0x57, 0x29, 0xd9, 0x99, 0x6d, 0xf5, 0x84,
+ 0xcd, 0x8e, 0x73, 0xae, 0xc9, 0xdc, 0x6a, 0xfa, 0x9e, 0x9d, 0x16, 0x64,
+ 0x93, 0x08, 0xc7, 0x1c, 0xc2, 0x89, 0x54, 0x9e, 0x77, 0x80, 0x90, 0xf6,
+ 0xb9, 0x29, 0x76, 0xeb, 0x13, 0x67, 0x48, 0x59, 0xf8, 0x2e, 0x3a, 0x31,
+ 0xb8, 0xc9, 0xd3, 0x88, 0xe5, 0x5f, 0x4e, 0xd2, 0x19, 0x3d, 0x43, 0x8e,
+ 0xd7, 0x92, 0xff, 0xcf, 0x38, 0xb6, 0xe1, 0x5b, 0x8a, 0x53, 0x1d, 0xce,
+ 0xac, 0xb4, 0x76, 0x2f, 0xd8, 0xf7, 0x40, 0x63, 0xd5, 0xee, 0x69, 0xf3,
+ 0x45, 0x7d, 0xa0, 0x62, 0xc1, 0x61, 0xc3, 0x75, 0xed, 0xb2, 0x7b, 0x4d,
+ 0xac, 0x21, 0x27, 0x30, 0x4e, 0x59, 0x46, 0x6a, 0x93, 0x17, 0xca, 0xc8,
+ 0x39, 0x2d, 0x01, 0x73, 0x65, 0x5b, 0xe9, 0x41, 0x9b, 0x11, 0x17, 0x9c,
+ 0xc8, 0xc8, 0x4a, 0xef, 0xa1, 0x76, 0x60, 0x2d, 0xae, 0x93, 0xff, 0x0c,
+ 0xd5, 0x33, 0x13, 0x9f, 0x4f, 0x13, 0xce, 0xdd, 0x86, 0xf1, 0xfc, 0xf8,
+ 0x35, 0x54, 0x15, 0xa8, 0x5b, 0xe7, 0x85, 0x7e, 0xfa, 0x37, 0x09, 0xff,
+ 0x8b, 0xb8, 0x31, 0x49, 0x9e, 0x0d, 0x6e, 0xde, 0xb4, 0xd2, 0x12, 0x2d,
+ 0xb8, 0xed, 0xc8, 0xc3, 0xf1, 0xb6, 0x42, 0xa0, 0x4c, 0x97, 0x79, 0xdf,
+ 0xfe, 0xc3, 0xa3, 0x9f, 0xa1, 0xf4, 0x6d, 0x2c, 0x84, 0x77, 0xa4, 0xa2,
+ 0x05, 0xe1, 0x17, 0xff, 0x31, 0xdd, 0x9a, 0xf3, 0xb8, 0x7a, 0xc3, 0x52,
+ 0xc2, 0x11, 0x11, 0xb7, 0x50, 0x31, 0x8a, 0x7f, 0xcc, 0xe7, 0x5a, 0x89,
+ 0xcc, 0xf7, 0x86, 0x9a, 0x61, 0x92, 0x4f, 0x2f, 0x94, 0xb6, 0x98, 0xc7,
+ 0x78, 0xe0, 0x62, 0x4b, 0x43, 0x7d, 0x3c, 0xde, 0xd6, 0x9a, 0xb4, 0x10,
+ 0xa1, 0x40, 0x9c, 0x4b, 0x2a, 0xdc, 0xb8, 0xd0, 0xd4, 0x9e, 0xfd, 0xf1,
+ 0x84, 0x78, 0x1b, 0x0e, 0x57, 0x8f, 0x69, 0x54, 0x42, 0x68, 0x7b, 0xea,
+ 0xa0, 0xef, 0x75, 0x0f, 0x07, 0xa2, 0x8c, 0x73, 0x99, 0xab, 0x55, 0xf5,
+ 0x07, 0x09, 0xd2, 0xaf, 0x38, 0x03, 0x6a, 0x90, 0x03, 0x0c, 0x2f, 0x8f,
+ 0xe2, 0xe8, 0x43, 0xc2, 0x31, 0xe9, 0x6f, 0xad, 0x87, 0xe5, 0x8d, 0xbd,
+ 0x4e, 0x2c, 0x89, 0x4b, 0x51, 0xe6, 0x9c, 0x4c, 0x54, 0x76, 0xc0, 0x12,
+ 0x81, 0x53, 0x9b, 0xec, 0xa0, 0xfc, 0x2c, 0x9c, 0xda, 0x18, 0x95, 0x6e,
+ 0x1e, 0x38, 0x26, 0x42, 0x27, 0x78, 0x60, 0x08, 0xdf, 0x7f, 0x6d, 0x32,
+ 0xe8, 0xd8, 0xc0, 0x6f, 0x1f, 0xeb, 0x26, 0x75, 0x9f, 0x93, 0xfc, 0x7b,
+ 0x1b, 0xfe, 0x35, 0x90, 0xdc, 0x53, 0xa3, 0x07, 0xa6, 0x3f, 0x83, 0x55,
+ 0x0a, 0x2b, 0x4e, 0x62, 0x82, 0x25, 0xce, 0x66, 0x30, 0x5d, 0x2c, 0xe0,
+ 0xf9, 0x19, 0x1b, 0x75, 0xb9, 0x9d, 0x98, 0x56, 0xa6, 0x83, 0x27, 0x7a,
+ 0xd1, 0x8f, 0x8d, 0x59, 0x93, 0xfc, 0x3f, 0x73, 0xd7, 0x2e, 0xb4, 0x2c,
+ 0x95, 0xd8, 0x8b, 0xf7, 0xc9, 0x7e, 0xc7, 0xfc, 0x9d, 0xac, 0x72, 0x04,
+ 0x1f, 0xd2, 0xcc, 0x17, 0xf4, 0xed, 0x34, 0x60, 0x9b, 0x9e, 0x4a, 0x97,
+ 0x04, 0xfe, 0xdd, 0x72, 0x0e, 0x57, 0x54, 0x51, 0x06, 0x70, 0x4d, 0xef,
+ 0xaa, 0x1c, 0xa4, 0x82, 0xe0, 0x33, 0xc7, 0xf4,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 08:51:f9:59:81:41:45:ca:bd:e0:24:e2:12:c9:c2:0e
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Validity
+ Not Before: Apr 3 00:00:00 2007 GMT
+ Not After : Apr 3 00:00:00 2022 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bf:61:0a:29:10:1f:5e:fe:34:37:51:08:f8:1e:
+ fb:22:ed:61:be:0b:0d:70:4c:50:63:26:75:15:b9:
+ 41:88:97:b6:f0:a0:15:bb:08:60:e0:42:e8:05:29:
+ 10:87:36:8a:28:65:a8:ef:31:07:74:6d:36:97:2f:
+ 28:46:66:04:c7:2a:79:26:7a:99:d5:8e:c3:6d:4f:
+ a0:5e:ad:bc:3d:91:c2:59:7b:5e:36:6c:c0:53:cf:
+ 00:08:32:3e:10:64:58:10:13:69:c7:0c:ee:9c:42:
+ 51:00:f9:05:44:ee:24:ce:7a:1f:ed:8c:11:bd:12:
+ a8:f3:15:f4:1c:7a:31:69:01:1b:a7:e6:5d:c0:9a:
+ 6c:7e:09:9e:e7:52:44:4a:10:3a:23:e4:9b:b6:03:
+ af:a8:9c:b4:5b:9f:d4:4b:ad:92:8c:ce:b5:11:2a:
+ aa:37:18:8d:b4:c2:b8:d8:5c:06:8c:f8:ff:23:bd:
+ 35:5e:d4:7c:3e:7e:83:0e:91:96:05:98:c3:b2:1f:
+ e3:c8:65:eb:a9:7b:5d:a0:2c:cc:fc:3c:d9:6d:ed:
+ cc:fa:4b:43:8c:c9:d4:b8:a5:61:1c:b2:40:b6:28:
+ 12:df:b9:f8:5f:fe:d3:b2:c9:ef:3d:b4:1e:4b:7c:
+ 1c:4c:99:36:9e:3d:eb:ec:a7:68:5e:1d:df:67:6e:
+ 5e:fb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.114412.1.3.0.2
+ CPS: http://www.digicert.com/ssl-cps-repository.htm
+ User Notice:
+ Explicit Text:
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.digicert.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ Full Name:
+ URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ X509v3 Authority Key Identifier:
+ keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+ X509v3 Subject Key Identifier:
+ 50:EA:73:89:DB:29:FB:10:8F:9E:E5:01:20:D4:DE:79:99:48:83:F7
+ Signature Algorithm: sha1WithRSAEncryption
+ 5d:4f:84:f1:a8:88:d3:a3:b2:bc:9c:6d:e5:29:49:77:e1:e7:
+ d6:dc:a9:d8:35:ae:c9:71:dc:e5:db:dc:9d:24:21:90:a6:cf:
+ b7:01:1c:9b:d4:57:97:91:d7:75:16:a5:12:d7:b9:3d:2e:89:
+ 3d:39:69:8a:d6:35:37:f9:f1:21:c4:5b:40:ad:59:a9:2f:5f:
+ 3a:00:29:43:27:71:03:e4:bd:30:32:55:a6:fe:84:0e:0b:9b:
+ 38:19:2c:43:7c:ac:43:bf:75:31:e5:23:1c:45:55:b7:69:08:
+ 91:b5:cf:d7:d5:b1:5e:ee:9f:94:e4:d6:7a:b9:18:c3:b8:d6:
+ 52:63:1c:10:ba:8b:2f:6d:5d:cc:05:38:f4:56:05:6d:ef:9e:
+ ec:e8:61:36:0c:14:4b:85:14:5a:0c:83:4f:22:5c:59:cb:8c:
+ 8a:71:da:fa:c5:10:84:58:cf:07:ee:e3:90:c2:f5:f9:29:c7:
+ 5a:23:71:f9:59:b4:64:2b:88:b0:a7:36:c7:9a:20:61:eb:fa:
+ 4e:b5:ae:6b:1b:e4:e3:ec:e2:d9:3c:41:49:a8:20:a4:54:f5:
+ 92:8d:bb:c0:55:20:04:a6:d8:b0:17:16:cc:e3:d0:c8:b4:3d:
+ e5:d9:84:c6:d3:f6:6e:6d:78:c9:79:43:e8:7a:37:ff:5c:35:
+ 49:bf:a1:c5
+-----BEGIN CERTIFICATE-----
+MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
+CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
+KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
+BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
+1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
+zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
+32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
+ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
+AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
+AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
+AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
+AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
+AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
+AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
+AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG
+CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB
+hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz
+c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
+Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW
+gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe
+eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1
+rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv
+XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk
+1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF
+EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU
+9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert98[] = {
+ 0x30, 0x82, 0x06, 0x55, 0x30, 0x82, 0x05, 0x3d, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x08, 0x51, 0xf9, 0x59, 0x81, 0x41, 0x45, 0xca, 0xbd,
+ 0xe0, 0x24, 0xe2, 0x12, 0xc9, 0xc2, 0x0e, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+ 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+ 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x30, 0x34, 0x30, 0x33, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x30,
+ 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+ 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+ 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+ 0x43, 0x41, 0x2d, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xbf, 0x61, 0x0a, 0x29, 0x10, 0x1f, 0x5e, 0xfe, 0x34, 0x37, 0x51,
+ 0x08, 0xf8, 0x1e, 0xfb, 0x22, 0xed, 0x61, 0xbe, 0x0b, 0x0d, 0x70, 0x4c,
+ 0x50, 0x63, 0x26, 0x75, 0x15, 0xb9, 0x41, 0x88, 0x97, 0xb6, 0xf0, 0xa0,
+ 0x15, 0xbb, 0x08, 0x60, 0xe0, 0x42, 0xe8, 0x05, 0x29, 0x10, 0x87, 0x36,
+ 0x8a, 0x28, 0x65, 0xa8, 0xef, 0x31, 0x07, 0x74, 0x6d, 0x36, 0x97, 0x2f,
+ 0x28, 0x46, 0x66, 0x04, 0xc7, 0x2a, 0x79, 0x26, 0x7a, 0x99, 0xd5, 0x8e,
+ 0xc3, 0x6d, 0x4f, 0xa0, 0x5e, 0xad, 0xbc, 0x3d, 0x91, 0xc2, 0x59, 0x7b,
+ 0x5e, 0x36, 0x6c, 0xc0, 0x53, 0xcf, 0x00, 0x08, 0x32, 0x3e, 0x10, 0x64,
+ 0x58, 0x10, 0x13, 0x69, 0xc7, 0x0c, 0xee, 0x9c, 0x42, 0x51, 0x00, 0xf9,
+ 0x05, 0x44, 0xee, 0x24, 0xce, 0x7a, 0x1f, 0xed, 0x8c, 0x11, 0xbd, 0x12,
+ 0xa8, 0xf3, 0x15, 0xf4, 0x1c, 0x7a, 0x31, 0x69, 0x01, 0x1b, 0xa7, 0xe6,
+ 0x5d, 0xc0, 0x9a, 0x6c, 0x7e, 0x09, 0x9e, 0xe7, 0x52, 0x44, 0x4a, 0x10,
+ 0x3a, 0x23, 0xe4, 0x9b, 0xb6, 0x03, 0xaf, 0xa8, 0x9c, 0xb4, 0x5b, 0x9f,
+ 0xd4, 0x4b, 0xad, 0x92, 0x8c, 0xce, 0xb5, 0x11, 0x2a, 0xaa, 0x37, 0x18,
+ 0x8d, 0xb4, 0xc2, 0xb8, 0xd8, 0x5c, 0x06, 0x8c, 0xf8, 0xff, 0x23, 0xbd,
+ 0x35, 0x5e, 0xd4, 0x7c, 0x3e, 0x7e, 0x83, 0x0e, 0x91, 0x96, 0x05, 0x98,
+ 0xc3, 0xb2, 0x1f, 0xe3, 0xc8, 0x65, 0xeb, 0xa9, 0x7b, 0x5d, 0xa0, 0x2c,
+ 0xcc, 0xfc, 0x3c, 0xd9, 0x6d, 0xed, 0xcc, 0xfa, 0x4b, 0x43, 0x8c, 0xc9,
+ 0xd4, 0xb8, 0xa5, 0x61, 0x1c, 0xb2, 0x40, 0xb6, 0x28, 0x12, 0xdf, 0xb9,
+ 0xf8, 0x5f, 0xfe, 0xd3, 0xb2, 0xc9, 0xef, 0x3d, 0xb4, 0x1e, 0x4b, 0x7c,
+ 0x1c, 0x4c, 0x99, 0x36, 0x9e, 0x3d, 0xeb, 0xec, 0xa7, 0x68, 0x5e, 0x1d,
+ 0xdf, 0x67, 0x6e, 0x5e, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x02, 0xf7, 0x30, 0x82, 0x02, 0xf3, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x82,
+ 0x01, 0xc6, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x82, 0x01, 0xbd, 0x30,
+ 0x82, 0x01, 0xb9, 0x30, 0x82, 0x01, 0xb5, 0x06, 0x0b, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xfd, 0x6c, 0x01, 0x03, 0x00, 0x02, 0x30, 0x82, 0x01, 0xa4,
+ 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+ 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+ 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, 0x00, 0x41,
+ 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65,
+ 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68,
+ 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72,
+ 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e,
+ 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, 0x00, 0x74,
+ 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63,
+ 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63,
+ 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67,
+ 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x20,
+ 0x00, 0x43, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x50, 0x00, 0x53,
+ 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, 0x00, 0x6c,
+ 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x50,
+ 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41,
+ 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65,
+ 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, 0x00, 0x69,
+ 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6d,
+ 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x61,
+ 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79,
+ 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x61,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x63,
+ 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x62,
+ 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x2e,
+ 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+ 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x34, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26, 0x30, 0x24, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x18, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64,
+ 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30, 0x81,
+ 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67,
+ 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69,
+ 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73,
+ 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f,
+ 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e, 0xa0,
+ 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72,
+ 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+ 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+ 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72,
+ 0x6c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4,
+ 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x1d,
+ 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x50, 0xea, 0x73,
+ 0x89, 0xdb, 0x29, 0xfb, 0x10, 0x8f, 0x9e, 0xe5, 0x01, 0x20, 0xd4, 0xde,
+ 0x79, 0x99, 0x48, 0x83, 0xf7, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+ 0x00, 0x5d, 0x4f, 0x84, 0xf1, 0xa8, 0x88, 0xd3, 0xa3, 0xb2, 0xbc, 0x9c,
+ 0x6d, 0xe5, 0x29, 0x49, 0x77, 0xe1, 0xe7, 0xd6, 0xdc, 0xa9, 0xd8, 0x35,
+ 0xae, 0xc9, 0x71, 0xdc, 0xe5, 0xdb, 0xdc, 0x9d, 0x24, 0x21, 0x90, 0xa6,
+ 0xcf, 0xb7, 0x01, 0x1c, 0x9b, 0xd4, 0x57, 0x97, 0x91, 0xd7, 0x75, 0x16,
+ 0xa5, 0x12, 0xd7, 0xb9, 0x3d, 0x2e, 0x89, 0x3d, 0x39, 0x69, 0x8a, 0xd6,
+ 0x35, 0x37, 0xf9, 0xf1, 0x21, 0xc4, 0x5b, 0x40, 0xad, 0x59, 0xa9, 0x2f,
+ 0x5f, 0x3a, 0x00, 0x29, 0x43, 0x27, 0x71, 0x03, 0xe4, 0xbd, 0x30, 0x32,
+ 0x55, 0xa6, 0xfe, 0x84, 0x0e, 0x0b, 0x9b, 0x38, 0x19, 0x2c, 0x43, 0x7c,
+ 0xac, 0x43, 0xbf, 0x75, 0x31, 0xe5, 0x23, 0x1c, 0x45, 0x55, 0xb7, 0x69,
+ 0x08, 0x91, 0xb5, 0xcf, 0xd7, 0xd5, 0xb1, 0x5e, 0xee, 0x9f, 0x94, 0xe4,
+ 0xd6, 0x7a, 0xb9, 0x18, 0xc3, 0xb8, 0xd6, 0x52, 0x63, 0x1c, 0x10, 0xba,
+ 0x8b, 0x2f, 0x6d, 0x5d, 0xcc, 0x05, 0x38, 0xf4, 0x56, 0x05, 0x6d, 0xef,
+ 0x9e, 0xec, 0xe8, 0x61, 0x36, 0x0c, 0x14, 0x4b, 0x85, 0x14, 0x5a, 0x0c,
+ 0x83, 0x4f, 0x22, 0x5c, 0x59, 0xcb, 0x8c, 0x8a, 0x71, 0xda, 0xfa, 0xc5,
+ 0x10, 0x84, 0x58, 0xcf, 0x07, 0xee, 0xe3, 0x90, 0xc2, 0xf5, 0xf9, 0x29,
+ 0xc7, 0x5a, 0x23, 0x71, 0xf9, 0x59, 0xb4, 0x64, 0x2b, 0x88, 0xb0, 0xa7,
+ 0x36, 0xc7, 0x9a, 0x20, 0x61, 0xeb, 0xfa, 0x4e, 0xb5, 0xae, 0x6b, 0x1b,
+ 0xe4, 0xe3, 0xec, 0xe2, 0xd9, 0x3c, 0x41, 0x49, 0xa8, 0x20, 0xa4, 0x54,
+ 0xf5, 0x92, 0x8d, 0xbb, 0xc0, 0x55, 0x20, 0x04, 0xa6, 0xd8, 0xb0, 0x17,
+ 0x16, 0xcc, 0xe3, 0xd0, 0xc8, 0xb4, 0x3d, 0xe5, 0xd9, 0x84, 0xc6, 0xd3,
+ 0xf6, 0x6e, 0x6d, 0x78, 0xc9, 0x79, 0x43, 0xe8, 0x7a, 0x37, 0xff, 0x5c,
+ 0x35, 0x49, 0xbf, 0xa1, 0xc5,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 0a:5f:11:4d:03:5b:17:91:17:d2:ef:d4:03:8c:3f:3b
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Validity
+ Not Before: Apr 2 12:00:00 2008 GMT
+ Not After : Apr 3 00:00:00 2022 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:bf:61:0a:29:10:1f:5e:fe:34:37:51:08:f8:1e:
+ fb:22:ed:61:be:0b:0d:70:4c:50:63:26:75:15:b9:
+ 41:88:97:b6:f0:a0:15:bb:08:60:e0:42:e8:05:29:
+ 10:87:36:8a:28:65:a8:ef:31:07:74:6d:36:97:2f:
+ 28:46:66:04:c7:2a:79:26:7a:99:d5:8e:c3:6d:4f:
+ a0:5e:ad:bc:3d:91:c2:59:7b:5e:36:6c:c0:53:cf:
+ 00:08:32:3e:10:64:58:10:13:69:c7:0c:ee:9c:42:
+ 51:00:f9:05:44:ee:24:ce:7a:1f:ed:8c:11:bd:12:
+ a8:f3:15:f4:1c:7a:31:69:01:1b:a7:e6:5d:c0:9a:
+ 6c:7e:09:9e:e7:52:44:4a:10:3a:23:e4:9b:b6:03:
+ af:a8:9c:b4:5b:9f:d4:4b:ad:92:8c:ce:b5:11:2a:
+ aa:37:18:8d:b4:c2:b8:d8:5c:06:8c:f8:ff:23:bd:
+ 35:5e:d4:7c:3e:7e:83:0e:91:96:05:98:c3:b2:1f:
+ e3:c8:65:eb:a9:7b:5d:a0:2c:cc:fc:3c:d9:6d:ed:
+ cc:fa:4b:43:8c:c9:d4:b8:a5:61:1c:b2:40:b6:28:
+ 12:df:b9:f8:5f:fe:d3:b2:c9:ef:3d:b4:1e:4b:7c:
+ 1c:4c:99:36:9e:3d:eb:ec:a7:68:5e:1d:df:67:6e:
+ 5e:fb
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.114412.1.3.0.2
+ CPS: http://www.digicert.com/ssl-cps-repository.htm
+ User Notice:
+ Explicit Text:
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.digicert.com
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ Full Name:
+ URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ X509v3 Authority Key Identifier:
+ keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+ X509v3 Subject Key Identifier:
+ 50:EA:73:89:DB:29:FB:10:8F:9E:E5:01:20:D4:DE:79:99:48:83:F7
+ Signature Algorithm: sha1WithRSAEncryption
+ 1e:e2:a5:48:9e:6c:db:53:38:0f:ef:a6:1a:2a:ac:e2:03:43:
+ ed:9a:bc:3e:8e:75:1b:f0:fd:2e:22:59:ac:13:c0:61:e2:e7:
+ fa:e9:99:cd:87:09:75:54:28:bf:46:60:dc:be:51:2c:92:f3:
+ 1b:91:7c:31:08:70:e2:37:b9:c1:5b:a8:bd:a3:0b:00:fb:1a:
+ 15:fd:03:ad:58:6a:c5:c7:24:99:48:47:46:31:1e:92:ef:b4:
+ 5f:4e:34:c7:90:bf:31:c1:f8:b1:84:86:d0:9c:01:aa:df:8a:
+ 56:06:ce:3a:e9:0e:ae:97:74:5d:d7:71:9a:42:74:5f:de:8d:
+ 43:7c:de:e9:55:ed:69:00:cb:05:e0:7a:61:61:33:d1:19:4d:
+ f9:08:ee:a0:39:c5:25:35:b7:2b:c4:0f:b2:dd:f1:a5:b7:0e:
+ 24:c4:26:28:8d:79:77:f5:2f:f0:57:ba:7c:07:d4:e1:fc:cd:
+ 5a:30:57:7e:86:10:47:dd:31:1f:d7:fc:a2:c2:bf:30:7c:5d:
+ 24:aa:e8:f9:ae:5f:6a:74:c2:ce:6b:b3:46:d8:21:be:29:d4:
+ 8e:5e:15:d6:42:4a:e7:32:6f:a4:b1:6b:51:83:58:be:3f:6d:
+ c7:fb:da:03:21:cb:6a:16:19:4e:0a:f0:ad:84:ca:5d:94:b3:
+ 5a:76:f7:61
+-----BEGIN CERTIFICATE-----
+MIIGWDCCBUCgAwIBAgIQCl8RTQNbF5EX0u/UA4w/OzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA4MDQwMjEyMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
+CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
+KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
+BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
+1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
+zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
+32duXvsCAwEAAaOCAvowggL2MA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
+ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
+AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
+AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
+AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
+AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
+AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
+AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
+AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
+AHIAZQBuAGMAZQAuMBIGA1UdEwEB/wQIMAYBAf8CAQAwNAYIKwYBBQUHAQEEKDAm
+MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSB
+hzCBhDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGln
+aEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNl
+cnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSME
+GDAWgBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUB
+INTeeZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAB7ipUiebNtTOA/vphoqrOIDQ+2a
+vD6OdRvw/S4iWawTwGHi5/rpmc2HCXVUKL9GYNy+USyS8xuRfDEIcOI3ucFbqL2j
+CwD7GhX9A61YasXHJJlIR0YxHpLvtF9ONMeQvzHB+LGEhtCcAarfilYGzjrpDq6X
+dF3XcZpCdF/ejUN83ulV7WkAywXgemFhM9EZTfkI7qA5xSU1tyvED7Ld8aW3DiTE
+JiiNeXf1L/BXunwH1OH8zVowV36GEEfdMR/X/KLCvzB8XSSq6PmuX2p0ws5rs0bY
+Ib4p1I5eFdZCSucyb6Sxa1GDWL4/bcf72gMhy2oWGU4K8K2Eyl2Us1p292E=
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert99[] = {
+ 0x30, 0x82, 0x06, 0x58, 0x30, 0x82, 0x05, 0x40, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x0a, 0x5f, 0x11, 0x4d, 0x03, 0x5b, 0x17, 0x91, 0x17,
+ 0xd2, 0xef, 0xd4, 0x03, 0x8c, 0x3f, 0x3b, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+ 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+ 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x34, 0x30, 0x32, 0x31, 0x32,
+ 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x34, 0x30,
+ 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x66, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+ 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+ 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+ 0x43, 0x41, 0x2d, 0x33, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
+ 0x00, 0xbf, 0x61, 0x0a, 0x29, 0x10, 0x1f, 0x5e, 0xfe, 0x34, 0x37, 0x51,
+ 0x08, 0xf8, 0x1e, 0xfb, 0x22, 0xed, 0x61, 0xbe, 0x0b, 0x0d, 0x70, 0x4c,
+ 0x50, 0x63, 0x26, 0x75, 0x15, 0xb9, 0x41, 0x88, 0x97, 0xb6, 0xf0, 0xa0,
+ 0x15, 0xbb, 0x08, 0x60, 0xe0, 0x42, 0xe8, 0x05, 0x29, 0x10, 0x87, 0x36,
+ 0x8a, 0x28, 0x65, 0xa8, 0xef, 0x31, 0x07, 0x74, 0x6d, 0x36, 0x97, 0x2f,
+ 0x28, 0x46, 0x66, 0x04, 0xc7, 0x2a, 0x79, 0x26, 0x7a, 0x99, 0xd5, 0x8e,
+ 0xc3, 0x6d, 0x4f, 0xa0, 0x5e, 0xad, 0xbc, 0x3d, 0x91, 0xc2, 0x59, 0x7b,
+ 0x5e, 0x36, 0x6c, 0xc0, 0x53, 0xcf, 0x00, 0x08, 0x32, 0x3e, 0x10, 0x64,
+ 0x58, 0x10, 0x13, 0x69, 0xc7, 0x0c, 0xee, 0x9c, 0x42, 0x51, 0x00, 0xf9,
+ 0x05, 0x44, 0xee, 0x24, 0xce, 0x7a, 0x1f, 0xed, 0x8c, 0x11, 0xbd, 0x12,
+ 0xa8, 0xf3, 0x15, 0xf4, 0x1c, 0x7a, 0x31, 0x69, 0x01, 0x1b, 0xa7, 0xe6,
+ 0x5d, 0xc0, 0x9a, 0x6c, 0x7e, 0x09, 0x9e, 0xe7, 0x52, 0x44, 0x4a, 0x10,
+ 0x3a, 0x23, 0xe4, 0x9b, 0xb6, 0x03, 0xaf, 0xa8, 0x9c, 0xb4, 0x5b, 0x9f,
+ 0xd4, 0x4b, 0xad, 0x92, 0x8c, 0xce, 0xb5, 0x11, 0x2a, 0xaa, 0x37, 0x18,
+ 0x8d, 0xb4, 0xc2, 0xb8, 0xd8, 0x5c, 0x06, 0x8c, 0xf8, 0xff, 0x23, 0xbd,
+ 0x35, 0x5e, 0xd4, 0x7c, 0x3e, 0x7e, 0x83, 0x0e, 0x91, 0x96, 0x05, 0x98,
+ 0xc3, 0xb2, 0x1f, 0xe3, 0xc8, 0x65, 0xeb, 0xa9, 0x7b, 0x5d, 0xa0, 0x2c,
+ 0xcc, 0xfc, 0x3c, 0xd9, 0x6d, 0xed, 0xcc, 0xfa, 0x4b, 0x43, 0x8c, 0xc9,
+ 0xd4, 0xb8, 0xa5, 0x61, 0x1c, 0xb2, 0x40, 0xb6, 0x28, 0x12, 0xdf, 0xb9,
+ 0xf8, 0x5f, 0xfe, 0xd3, 0xb2, 0xc9, 0xef, 0x3d, 0xb4, 0x1e, 0x4b, 0x7c,
+ 0x1c, 0x4c, 0x99, 0x36, 0x9e, 0x3d, 0xeb, 0xec, 0xa7, 0x68, 0x5e, 0x1d,
+ 0xdf, 0x67, 0x6e, 0x5e, 0xfb, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82,
+ 0x02, 0xfa, 0x30, 0x82, 0x02, 0xf6, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
+ 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x82,
+ 0x01, 0xc6, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x82, 0x01, 0xbd, 0x30,
+ 0x82, 0x01, 0xb9, 0x30, 0x82, 0x01, 0xb5, 0x06, 0x0b, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xfd, 0x6c, 0x01, 0x03, 0x00, 0x02, 0x30, 0x82, 0x01, 0xa4,
+ 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01,
+ 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d, 0x72, 0x65,
+ 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68, 0x74, 0x6d,
+ 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52, 0x00, 0x41,
+ 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73, 0x00, 0x65,
+ 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74, 0x00, 0x68,
+ 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72,
+ 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6e,
+ 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75, 0x00, 0x74,
+ 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63, 0x00, 0x63,
+ 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x63,
+ 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69, 0x00, 0x67,
+ 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x20,
+ 0x00, 0x43, 0x00, 0x50, 0x00, 0x2f, 0x00, 0x43, 0x00, 0x50, 0x00, 0x53,
+ 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65, 0x00, 0x6c,
+ 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20, 0x00, 0x50,
+ 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20, 0x00, 0x41,
+ 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65,
+ 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68, 0x00, 0x69,
+ 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x6d,
+ 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x61,
+ 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74, 0x00, 0x79,
+ 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20, 0x00, 0x61,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x63,
+ 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x61,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x62,
+ 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65, 0x00, 0x2e,
+ 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x34, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x28, 0x30, 0x26,
+ 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81,
+ 0x87, 0x30, 0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e,
+ 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67,
+ 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56,
+ 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40,
+ 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65,
+ 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43,
+ 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72,
+ 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41,
+ 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04,
+ 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf,
+ 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b,
+ 0xc3, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+ 0x50, 0xea, 0x73, 0x89, 0xdb, 0x29, 0xfb, 0x10, 0x8f, 0x9e, 0xe5, 0x01,
+ 0x20, 0xd4, 0xde, 0x79, 0x99, 0x48, 0x83, 0xf7, 0x30, 0x0d, 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03,
+ 0x82, 0x01, 0x01, 0x00, 0x1e, 0xe2, 0xa5, 0x48, 0x9e, 0x6c, 0xdb, 0x53,
+ 0x38, 0x0f, 0xef, 0xa6, 0x1a, 0x2a, 0xac, 0xe2, 0x03, 0x43, 0xed, 0x9a,
+ 0xbc, 0x3e, 0x8e, 0x75, 0x1b, 0xf0, 0xfd, 0x2e, 0x22, 0x59, 0xac, 0x13,
+ 0xc0, 0x61, 0xe2, 0xe7, 0xfa, 0xe9, 0x99, 0xcd, 0x87, 0x09, 0x75, 0x54,
+ 0x28, 0xbf, 0x46, 0x60, 0xdc, 0xbe, 0x51, 0x2c, 0x92, 0xf3, 0x1b, 0x91,
+ 0x7c, 0x31, 0x08, 0x70, 0xe2, 0x37, 0xb9, 0xc1, 0x5b, 0xa8, 0xbd, 0xa3,
+ 0x0b, 0x00, 0xfb, 0x1a, 0x15, 0xfd, 0x03, 0xad, 0x58, 0x6a, 0xc5, 0xc7,
+ 0x24, 0x99, 0x48, 0x47, 0x46, 0x31, 0x1e, 0x92, 0xef, 0xb4, 0x5f, 0x4e,
+ 0x34, 0xc7, 0x90, 0xbf, 0x31, 0xc1, 0xf8, 0xb1, 0x84, 0x86, 0xd0, 0x9c,
+ 0x01, 0xaa, 0xdf, 0x8a, 0x56, 0x06, 0xce, 0x3a, 0xe9, 0x0e, 0xae, 0x97,
+ 0x74, 0x5d, 0xd7, 0x71, 0x9a, 0x42, 0x74, 0x5f, 0xde, 0x8d, 0x43, 0x7c,
+ 0xde, 0xe9, 0x55, 0xed, 0x69, 0x00, 0xcb, 0x05, 0xe0, 0x7a, 0x61, 0x61,
+ 0x33, 0xd1, 0x19, 0x4d, 0xf9, 0x08, 0xee, 0xa0, 0x39, 0xc5, 0x25, 0x35,
+ 0xb7, 0x2b, 0xc4, 0x0f, 0xb2, 0xdd, 0xf1, 0xa5, 0xb7, 0x0e, 0x24, 0xc4,
+ 0x26, 0x28, 0x8d, 0x79, 0x77, 0xf5, 0x2f, 0xf0, 0x57, 0xba, 0x7c, 0x07,
+ 0xd4, 0xe1, 0xfc, 0xcd, 0x5a, 0x30, 0x57, 0x7e, 0x86, 0x10, 0x47, 0xdd,
+ 0x31, 0x1f, 0xd7, 0xfc, 0xa2, 0xc2, 0xbf, 0x30, 0x7c, 0x5d, 0x24, 0xaa,
+ 0xe8, 0xf9, 0xae, 0x5f, 0x6a, 0x74, 0xc2, 0xce, 0x6b, 0xb3, 0x46, 0xd8,
+ 0x21, 0xbe, 0x29, 0xd4, 0x8e, 0x5e, 0x15, 0xd6, 0x42, 0x4a, 0xe7, 0x32,
+ 0x6f, 0xa4, 0xb1, 0x6b, 0x51, 0x83, 0x58, 0xbe, 0x3f, 0x6d, 0xc7, 0xfb,
+ 0xda, 0x03, 0x21, 0xcb, 0x6a, 0x16, 0x19, 0x4e, 0x0a, 0xf0, 0xad, 0x84,
+ 0xca, 0x5d, 0x94, 0xb3, 0x5a, 0x76, 0xf7, 0x61,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 08:bb:b0:25:47:13:4b:c9:b1:10:d7:c1:a2:12:59:c5
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Validity
+ Not Before: Nov 10 00:00:00 2006 GMT
+ Not After : Nov 10 00:00:00 2021 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:f3:96:62:d8:75:6e:19:ff:3f:34:7c:49:4f:31:
+ 7e:0d:04:4e:99:81:e2:b3:85:55:91:30:b1:c0:af:
+ 70:bb:2c:a8:e7:18:aa:3f:78:f7:90:68:52:86:01:
+ 88:97:e2:3b:06:65:90:aa:bd:65:76:c2:ec:be:10:
+ 5b:37:78:83:60:75:45:c6:bd:74:aa:b6:9f:a4:3a:
+ 01:50:17:c4:39:69:b9:f1:4f:ef:82:c1:ca:f3:4a:
+ db:cc:9e:50:4f:4d:40:a3:3a:90:e7:86:66:bc:f0:
+ 3e:76:28:4c:d1:75:80:9e:6a:35:14:35:03:9e:db:
+ 0c:8c:c2:28:ad:50:b2:ce:f6:91:a3:c3:a5:0a:58:
+ 49:f6:75:44:6c:ba:f9:ce:e9:ab:3a:02:e0:4d:f3:
+ ac:e2:7a:e0:60:22:05:3c:82:d3:52:e2:f3:9c:47:
+ f8:3b:d8:b2:4b:93:56:4a:bf:70:ab:3e:e9:68:c8:
+ 1d:8f:58:1d:2a:4d:5e:27:3d:ad:0a:59:2f:5a:11:
+ 20:40:d9:68:04:68:2d:f4:c0:84:0b:0a:1b:78:df:
+ ed:1a:58:dc:fb:41:5a:6d:6b:f2:ed:1c:ee:5c:32:
+ b6:5c:ec:d7:a6:03:32:a6:e8:de:b7:28:27:59:88:
+ 80:ff:7b:ad:89:58:d5:1e:14:a4:f2:b0:70:d4:a0:
+ 3e:a7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.114412.2.1
+ CPS: http://www.digicert.com/ssl-cps-repository.htm
+ User Notice:
+ Explicit Text:
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE
+ Authority Information Access:
+ OCSP - URI:http://ocsp.digicert.com
+ CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ Full Name:
+ URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ X509v3 Subject Key Identifier:
+ 4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+ X509v3 Authority Key Identifier:
+ keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 50:1e:43:b0:f7:4d:29:96:5b:bb:a7:d3:0a:b5:b5:d5:d0:27:
+ aa:f9:af:c7:25:d1:95:d5:2f:5a:53:bd:42:07:7e:78:49:ca:
+ 0b:eb:4c:55:e2:ea:2f:7f:49:ad:c7:ff:d1:2d:3e:9c:a0:64:
+ 2b:51:9e:91:26:28:bb:87:bb:75:7c:bc:a1:fd:66:68:2e:4c:
+ 4a:16:cc:fe:06:cf:31:ea:80:6e:e4:bd:e8:03:72:f6:25:b5:
+ 41:83:61:d0:97:0a:27:1d:b3:f7:2b:32:84:8f:5b:e7:cc:3f:
+ e2:2c:67:86:94:f4:b2:2b:6c:52:3b:67:2a:8d:58:95:00:14:
+ 46:24:ac:0b:fa:c9:8e:c7:26:80:df:d1:e1:97:e3:f8:bb:68:
+ c6:9c:bd:be:08:54:3b:10:32:7c:81:1f:2b:28:95:a8:41:0a:
+ c6:d0:30:66:b4:e9:f2:a2:00:69:20:07:ca:82:4c:1e:cf:a7:
+ 98:b8:0c:ee:cd:16:1c:be:1a:63:d4:c0:99:f6:67:b2:f0:8e:
+ 17:2d:58:c2:80:aa:5d:96:c7:b3:28:ed:f0:da:8e:b6:47:1b:
+ 8f:4e:15:f1:97:4c:0b:4b:af:81:d4:46:94:62:2c:43:a7:3c:
+ 25:48:19:63:f2:5c:aa:15:89:76:84:85:73:91:7d:28:3c:09:
+ 83:82:bc:f7
+-----BEGIN CERTIFICATE-----
+MIIG4zCCBcugAwIBAgIQCLuwJUcTS8mxENfBohJZxTANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/
+PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC
+7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw
+PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6
+4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo
+LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U
+pPKwcNSgPqcCAwEAAaOCA4IwggN+MA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy
+BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH
+AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wDwYDVR0TAQH/BAUwAwEB/zCBgwYI
+KwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
+b20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2VydHMv
+RGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcwgYQw
+QKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1
+cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv
+bS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYEFExY
+yyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoIAu9j
+ZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBQHkOw900pllu7p9MKtbXV0Ceq+a/HJdGV
+1S9aU71CB354ScoL60xV4uovf0mtx//RLT6coGQrUZ6RJii7h7t1fLyh/WZoLkxK
+Fsz+Bs8x6oBu5L3oA3L2JbVBg2HQlwonHbP3KzKEj1vnzD/iLGeGlPSyK2xSO2cq
+jViVABRGJKwL+smOxyaA39Hhl+P4u2jGnL2+CFQ7EDJ8gR8rKJWoQQrG0DBmtOny
+ogBpIAfKgkwez6eYuAzuzRYcvhpj1MCZ9mey8I4XLVjCgKpdlsezKO3w2o62RxuP
+ThXxl0wLS6+B1EaUYixDpzwlSBlj8lyqFYl2hIVzkX0oPAmDgrz3
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert100[] = {
+ 0x30, 0x82, 0x06, 0xe3, 0x30, 0x82, 0x05, 0xcb, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x08, 0xbb, 0xb0, 0x25, 0x47, 0x13, 0x4b, 0xc9, 0xb1,
+ 0x10, 0xd7, 0xc1, 0xa2, 0x12, 0x59, 0xc5, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+ 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+ 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+ 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1f,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+ 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+ 0x45, 0x56, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xf3, 0x96, 0x62, 0xd8, 0x75, 0x6e, 0x19, 0xff,
+ 0x3f, 0x34, 0x7c, 0x49, 0x4f, 0x31, 0x7e, 0x0d, 0x04, 0x4e, 0x99, 0x81,
+ 0xe2, 0xb3, 0x85, 0x55, 0x91, 0x30, 0xb1, 0xc0, 0xaf, 0x70, 0xbb, 0x2c,
+ 0xa8, 0xe7, 0x18, 0xaa, 0x3f, 0x78, 0xf7, 0x90, 0x68, 0x52, 0x86, 0x01,
+ 0x88, 0x97, 0xe2, 0x3b, 0x06, 0x65, 0x90, 0xaa, 0xbd, 0x65, 0x76, 0xc2,
+ 0xec, 0xbe, 0x10, 0x5b, 0x37, 0x78, 0x83, 0x60, 0x75, 0x45, 0xc6, 0xbd,
+ 0x74, 0xaa, 0xb6, 0x9f, 0xa4, 0x3a, 0x01, 0x50, 0x17, 0xc4, 0x39, 0x69,
+ 0xb9, 0xf1, 0x4f, 0xef, 0x82, 0xc1, 0xca, 0xf3, 0x4a, 0xdb, 0xcc, 0x9e,
+ 0x50, 0x4f, 0x4d, 0x40, 0xa3, 0x3a, 0x90, 0xe7, 0x86, 0x66, 0xbc, 0xf0,
+ 0x3e, 0x76, 0x28, 0x4c, 0xd1, 0x75, 0x80, 0x9e, 0x6a, 0x35, 0x14, 0x35,
+ 0x03, 0x9e, 0xdb, 0x0c, 0x8c, 0xc2, 0x28, 0xad, 0x50, 0xb2, 0xce, 0xf6,
+ 0x91, 0xa3, 0xc3, 0xa5, 0x0a, 0x58, 0x49, 0xf6, 0x75, 0x44, 0x6c, 0xba,
+ 0xf9, 0xce, 0xe9, 0xab, 0x3a, 0x02, 0xe0, 0x4d, 0xf3, 0xac, 0xe2, 0x7a,
+ 0xe0, 0x60, 0x22, 0x05, 0x3c, 0x82, 0xd3, 0x52, 0xe2, 0xf3, 0x9c, 0x47,
+ 0xf8, 0x3b, 0xd8, 0xb2, 0x4b, 0x93, 0x56, 0x4a, 0xbf, 0x70, 0xab, 0x3e,
+ 0xe9, 0x68, 0xc8, 0x1d, 0x8f, 0x58, 0x1d, 0x2a, 0x4d, 0x5e, 0x27, 0x3d,
+ 0xad, 0x0a, 0x59, 0x2f, 0x5a, 0x11, 0x20, 0x40, 0xd9, 0x68, 0x04, 0x68,
+ 0x2d, 0xf4, 0xc0, 0x84, 0x0b, 0x0a, 0x1b, 0x78, 0xdf, 0xed, 0x1a, 0x58,
+ 0xdc, 0xfb, 0x41, 0x5a, 0x6d, 0x6b, 0xf2, 0xed, 0x1c, 0xee, 0x5c, 0x32,
+ 0xb6, 0x5c, 0xec, 0xd7, 0xa6, 0x03, 0x32, 0xa6, 0xe8, 0xde, 0xb7, 0x28,
+ 0x27, 0x59, 0x88, 0x80, 0xff, 0x7b, 0xad, 0x89, 0x58, 0xd5, 0x1e, 0x14,
+ 0xa4, 0xf2, 0xb0, 0x70, 0xd4, 0xa0, 0x3e, 0xa7, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x03, 0x82, 0x30, 0x82, 0x03, 0x7e, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x86, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x34, 0x30, 0x32,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x08, 0x30, 0x82, 0x01, 0xc4, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0xb7, 0x30, 0x82, 0x01, 0xb3, 0x06,
+ 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6c, 0x02, 0x01, 0x30, 0x82,
+ 0x01, 0xa4, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d,
+ 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68,
+ 0x74, 0x6d, 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52,
+ 0x00, 0x41, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73,
+ 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63,
+ 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f,
+ 0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63,
+ 0x00, 0x63, 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e,
+ 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20,
+ 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69,
+ 0x00, 0x67, 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74,
+ 0x00, 0x20, 0x00, 0x45, 0x00, 0x56, 0x00, 0x20, 0x00, 0x43, 0x00, 0x50,
+ 0x00, 0x53, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+ 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65,
+ 0x00, 0x6c, 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20,
+ 0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20,
+ 0x00, 0x41, 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d,
+ 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68,
+ 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+ 0x00, 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+ 0x00, 0x61, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74,
+ 0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+ 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e,
+ 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72,
+ 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68,
+ 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20,
+ 0x00, 0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66,
+ 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65,
+ 0x00, 0x2e, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x81, 0x83, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x77, 0x30, 0x75,
+ 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01,
+ 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73,
+ 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x30, 0x4d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x41, 0x43, 0x65, 0x72, 0x74, 0x73, 0x2f,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68,
+ 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52,
+ 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x81, 0x8f,
+ 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30, 0x81, 0x84, 0x30,
+ 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63,
+ 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69,
+ 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75,
+ 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43,
+ 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86,
+ 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x34,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69,
+ 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45,
+ 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4c, 0x58,
+ 0xcb, 0x25, 0xf0, 0x41, 0x4f, 0x52, 0xf4, 0x28, 0xc8, 0x81, 0x43, 0x9b,
+ 0xa6, 0xa8, 0xa0, 0xe6, 0x92, 0xe5, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03,
+ 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63,
+ 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x50,
+ 0x1e, 0x43, 0xb0, 0xf7, 0x4d, 0x29, 0x96, 0x5b, 0xbb, 0xa7, 0xd3, 0x0a,
+ 0xb5, 0xb5, 0xd5, 0xd0, 0x27, 0xaa, 0xf9, 0xaf, 0xc7, 0x25, 0xd1, 0x95,
+ 0xd5, 0x2f, 0x5a, 0x53, 0xbd, 0x42, 0x07, 0x7e, 0x78, 0x49, 0xca, 0x0b,
+ 0xeb, 0x4c, 0x55, 0xe2, 0xea, 0x2f, 0x7f, 0x49, 0xad, 0xc7, 0xff, 0xd1,
+ 0x2d, 0x3e, 0x9c, 0xa0, 0x64, 0x2b, 0x51, 0x9e, 0x91, 0x26, 0x28, 0xbb,
+ 0x87, 0xbb, 0x75, 0x7c, 0xbc, 0xa1, 0xfd, 0x66, 0x68, 0x2e, 0x4c, 0x4a,
+ 0x16, 0xcc, 0xfe, 0x06, 0xcf, 0x31, 0xea, 0x80, 0x6e, 0xe4, 0xbd, 0xe8,
+ 0x03, 0x72, 0xf6, 0x25, 0xb5, 0x41, 0x83, 0x61, 0xd0, 0x97, 0x0a, 0x27,
+ 0x1d, 0xb3, 0xf7, 0x2b, 0x32, 0x84, 0x8f, 0x5b, 0xe7, 0xcc, 0x3f, 0xe2,
+ 0x2c, 0x67, 0x86, 0x94, 0xf4, 0xb2, 0x2b, 0x6c, 0x52, 0x3b, 0x67, 0x2a,
+ 0x8d, 0x58, 0x95, 0x00, 0x14, 0x46, 0x24, 0xac, 0x0b, 0xfa, 0xc9, 0x8e,
+ 0xc7, 0x26, 0x80, 0xdf, 0xd1, 0xe1, 0x97, 0xe3, 0xf8, 0xbb, 0x68, 0xc6,
+ 0x9c, 0xbd, 0xbe, 0x08, 0x54, 0x3b, 0x10, 0x32, 0x7c, 0x81, 0x1f, 0x2b,
+ 0x28, 0x95, 0xa8, 0x41, 0x0a, 0xc6, 0xd0, 0x30, 0x66, 0xb4, 0xe9, 0xf2,
+ 0xa2, 0x00, 0x69, 0x20, 0x07, 0xca, 0x82, 0x4c, 0x1e, 0xcf, 0xa7, 0x98,
+ 0xb8, 0x0c, 0xee, 0xcd, 0x16, 0x1c, 0xbe, 0x1a, 0x63, 0xd4, 0xc0, 0x99,
+ 0xf6, 0x67, 0xb2, 0xf0, 0x8e, 0x17, 0x2d, 0x58, 0xc2, 0x80, 0xaa, 0x5d,
+ 0x96, 0xc7, 0xb3, 0x28, 0xed, 0xf0, 0xda, 0x8e, 0xb6, 0x47, 0x1b, 0x8f,
+ 0x4e, 0x15, 0xf1, 0x97, 0x4c, 0x0b, 0x4b, 0xaf, 0x81, 0xd4, 0x46, 0x94,
+ 0x62, 0x2c, 0x43, 0xa7, 0x3c, 0x25, 0x48, 0x19, 0x63, 0xf2, 0x5c, 0xaa,
+ 0x15, 0x89, 0x76, 0x84, 0x85, 0x73, 0x91, 0x7d, 0x28, 0x3c, 0x09, 0x83,
+ 0x82, 0xbc, 0xf7,
+};
+
+#if 0
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ 03:37:b9:28:34:7c:60:a6:ae:c5:ad:b1:21:7f:38:60
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
+ Validity
+ Not Before: Nov 9 12:00:00 2007 GMT
+ Not After : Nov 10 00:00:00 2021 GMT
+ Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV CA-1
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:f3:96:62:d8:75:6e:19:ff:3f:34:7c:49:4f:31:
+ 7e:0d:04:4e:99:81:e2:b3:85:55:91:30:b1:c0:af:
+ 70:bb:2c:a8:e7:18:aa:3f:78:f7:90:68:52:86:01:
+ 88:97:e2:3b:06:65:90:aa:bd:65:76:c2:ec:be:10:
+ 5b:37:78:83:60:75:45:c6:bd:74:aa:b6:9f:a4:3a:
+ 01:50:17:c4:39:69:b9:f1:4f:ef:82:c1:ca:f3:4a:
+ db:cc:9e:50:4f:4d:40:a3:3a:90:e7:86:66:bc:f0:
+ 3e:76:28:4c:d1:75:80:9e:6a:35:14:35:03:9e:db:
+ 0c:8c:c2:28:ad:50:b2:ce:f6:91:a3:c3:a5:0a:58:
+ 49:f6:75:44:6c:ba:f9:ce:e9:ab:3a:02:e0:4d:f3:
+ ac:e2:7a:e0:60:22:05:3c:82:d3:52:e2:f3:9c:47:
+ f8:3b:d8:b2:4b:93:56:4a:bf:70:ab:3e:e9:68:c8:
+ 1d:8f:58:1d:2a:4d:5e:27:3d:ad:0a:59:2f:5a:11:
+ 20:40:d9:68:04:68:2d:f4:c0:84:0b:0a:1b:78:df:
+ ed:1a:58:dc:fb:41:5a:6d:6b:f2:ed:1c:ee:5c:32:
+ b6:5c:ec:d7:a6:03:32:a6:e8:de:b7:28:27:59:88:
+ 80:ff:7b:ad:89:58:d5:1e:14:a4:f2:b0:70:d4:a0:
+ 3e:a7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Certificate Sign, CRL Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
+ X509v3 Certificate Policies:
+ Policy: 2.16.840.1.114412.2.1
+ CPS: http://www.digicert.com/ssl-cps-repository.htm
+ User Notice:
+ Explicit Text:
+
+ X509v3 Basic Constraints: critical
+ CA:TRUE, pathlen:0
+ Authority Information Access:
+ OCSP - URI:http://ocsp.digicert.com
+ CA Issuers - URI:http://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt
+
+ X509v3 CRL Distribution Points:
+
+ Full Name:
+ URI:http://crl3.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ Full Name:
+ URI:http://crl4.digicert.com/DigiCertHighAssuranceEVRootCA.crl
+
+ X509v3 Subject Key Identifier:
+ 4C:58:CB:25:F0:41:4F:52:F4:28:C8:81:43:9B:A6:A8:A0:E6:92:E5
+ X509v3 Authority Key Identifier:
+ keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 4c:7a:17:87:28:5d:17:bc:b2:32:73:bf:cd:2e:f5:58:31:1d:
+ f0:b1:71:54:9c:d6:9b:67:93:db:2f:03:3e:16:6f:1e:03:c9:
+ 53:84:a3:56:60:1e:78:94:1b:a2:a8:6f:a3:a4:8b:52:91:d7:
+ dd:5c:95:bb:ef:b5:16:49:e9:a5:42:4f:34:f2:47:ff:ae:81:
+ 7f:13:54:b7:20:c4:70:15:cb:81:0a:81:cb:74:57:dc:9c:df:
+ 24:a4:29:0c:18:f0:1c:e4:ae:07:33:ec:f1:49:3e:55:cf:6e:
+ 4f:0d:54:7b:d3:c9:e8:15:48:d4:c5:bb:dc:35:1c:77:45:07:
+ 48:45:85:bd:d7:7e:53:b8:c0:16:d9:95:cd:8b:8d:7d:c9:60:
+ 4f:d1:a2:9b:e3:d0:30:d6:b4:73:36:e6:d2:f9:03:b2:e3:a4:
+ f5:e5:b8:3e:04:49:00:ba:2e:a6:4a:72:83:72:9d:f7:0b:8c:
+ a9:89:e7:b3:d7:64:1f:d6:e3:60:cb:03:c4:dc:88:e9:9d:25:
+ 01:00:71:cb:03:b4:29:60:25:8f:f9:46:d1:7b:71:ae:cd:53:
+ 12:5b:84:8e:c2:0f:c7:ed:93:19:d9:c9:fa:8f:58:34:76:32:
+ 2f:ae:e1:50:14:61:d4:a8:58:a3:c8:30:13:23:ef:c6:25:8c:
+ 36:8f:1c:80
+-----BEGIN CERTIFICATE-----
+MIIG5jCCBc6gAwIBAgIQAze5KDR8YKauxa2xIX84YDANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA3MTEwOTEyMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/
+PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC
+7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw
+PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6
+4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo
+LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U
+pPKwcNSgPqcCAwEAAaOCA4UwggOBMA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy
+BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH
+AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH
+AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
+dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
+AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
+AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
+AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
+AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
+AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
+AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
+AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wEgYDVR0TAQH/BAgwBgEB/wIBADCB
+gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
+dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2Vy
+dHMvRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcw
+gYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hB
+c3N1cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0
+LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYE
+FExYyyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoI
+Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBMeheHKF0XvLIyc7/NLvVYMR3wsXFU
+nNabZ5PbLwM+Fm8eA8lThKNWYB54lBuiqG+jpItSkdfdXJW777UWSemlQk808kf/
+roF/E1S3IMRwFcuBCoHLdFfcnN8kpCkMGPAc5K4HM+zxST5Vz25PDVR708noFUjU
+xbvcNRx3RQdIRYW9135TuMAW2ZXNi419yWBP0aKb49Aw1rRzNubS+QOy46T15bg+
+BEkAui6mSnKDcp33C4ypieez12Qf1uNgywPE3IjpnSUBAHHLA7QpYCWP+UbRe3Gu
+zVMSW4SOwg/H7ZMZ2cn6j1g0djIvruFQFGHUqFijyDATI+/GJYw2jxyA
+-----END CERTIFICATE-----
+#endif
+static const unsigned char kDERCert101[] = {
+ 0x30, 0x82, 0x06, 0xe6, 0x30, 0x82, 0x05, 0xce, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x03, 0x37, 0xb9, 0x28, 0x34, 0x7c, 0x60, 0xa6, 0xae,
+ 0xc5, 0xad, 0xb1, 0x21, 0x7f, 0x38, 0x60, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
+ 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
+ 0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
+ 0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x37, 0x31, 0x31, 0x30, 0x39, 0x31, 0x32,
+ 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, 0x31,
+ 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x69, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
+ 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
+ 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
+ 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1f,
+ 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
+ 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
+ 0x45, 0x56, 0x20, 0x43, 0x41, 0x2d, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
+ 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xf3, 0x96, 0x62, 0xd8, 0x75, 0x6e, 0x19, 0xff,
+ 0x3f, 0x34, 0x7c, 0x49, 0x4f, 0x31, 0x7e, 0x0d, 0x04, 0x4e, 0x99, 0x81,
+ 0xe2, 0xb3, 0x85, 0x55, 0x91, 0x30, 0xb1, 0xc0, 0xaf, 0x70, 0xbb, 0x2c,
+ 0xa8, 0xe7, 0x18, 0xaa, 0x3f, 0x78, 0xf7, 0x90, 0x68, 0x52, 0x86, 0x01,
+ 0x88, 0x97, 0xe2, 0x3b, 0x06, 0x65, 0x90, 0xaa, 0xbd, 0x65, 0x76, 0xc2,
+ 0xec, 0xbe, 0x10, 0x5b, 0x37, 0x78, 0x83, 0x60, 0x75, 0x45, 0xc6, 0xbd,
+ 0x74, 0xaa, 0xb6, 0x9f, 0xa4, 0x3a, 0x01, 0x50, 0x17, 0xc4, 0x39, 0x69,
+ 0xb9, 0xf1, 0x4f, 0xef, 0x82, 0xc1, 0xca, 0xf3, 0x4a, 0xdb, 0xcc, 0x9e,
+ 0x50, 0x4f, 0x4d, 0x40, 0xa3, 0x3a, 0x90, 0xe7, 0x86, 0x66, 0xbc, 0xf0,
+ 0x3e, 0x76, 0x28, 0x4c, 0xd1, 0x75, 0x80, 0x9e, 0x6a, 0x35, 0x14, 0x35,
+ 0x03, 0x9e, 0xdb, 0x0c, 0x8c, 0xc2, 0x28, 0xad, 0x50, 0xb2, 0xce, 0xf6,
+ 0x91, 0xa3, 0xc3, 0xa5, 0x0a, 0x58, 0x49, 0xf6, 0x75, 0x44, 0x6c, 0xba,
+ 0xf9, 0xce, 0xe9, 0xab, 0x3a, 0x02, 0xe0, 0x4d, 0xf3, 0xac, 0xe2, 0x7a,
+ 0xe0, 0x60, 0x22, 0x05, 0x3c, 0x82, 0xd3, 0x52, 0xe2, 0xf3, 0x9c, 0x47,
+ 0xf8, 0x3b, 0xd8, 0xb2, 0x4b, 0x93, 0x56, 0x4a, 0xbf, 0x70, 0xab, 0x3e,
+ 0xe9, 0x68, 0xc8, 0x1d, 0x8f, 0x58, 0x1d, 0x2a, 0x4d, 0x5e, 0x27, 0x3d,
+ 0xad, 0x0a, 0x59, 0x2f, 0x5a, 0x11, 0x20, 0x40, 0xd9, 0x68, 0x04, 0x68,
+ 0x2d, 0xf4, 0xc0, 0x84, 0x0b, 0x0a, 0x1b, 0x78, 0xdf, 0xed, 0x1a, 0x58,
+ 0xdc, 0xfb, 0x41, 0x5a, 0x6d, 0x6b, 0xf2, 0xed, 0x1c, 0xee, 0x5c, 0x32,
+ 0xb6, 0x5c, 0xec, 0xd7, 0xa6, 0x03, 0x32, 0xa6, 0xe8, 0xde, 0xb7, 0x28,
+ 0x27, 0x59, 0x88, 0x80, 0xff, 0x7b, 0xad, 0x89, 0x58, 0xd5, 0x1e, 0x14,
+ 0xa4, 0xf2, 0xb0, 0x70, 0xd4, 0xa0, 0x3e, 0xa7, 0x02, 0x03, 0x01, 0x00,
+ 0x01, 0xa3, 0x82, 0x03, 0x85, 0x30, 0x82, 0x03, 0x81, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01,
+ 0x86, 0x30, 0x3b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x34, 0x30, 0x32,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x03, 0x03, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x03, 0x04, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x08, 0x30, 0x82, 0x01, 0xc4, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04,
+ 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0xb7, 0x30, 0x82, 0x01, 0xb3, 0x06,
+ 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6c, 0x02, 0x01, 0x30, 0x82,
+ 0x01, 0xa4, 0x30, 0x3a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x02, 0x01, 0x16, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x73, 0x6c, 0x2d, 0x63, 0x70, 0x73, 0x2d,
+ 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x68,
+ 0x74, 0x6d, 0x30, 0x82, 0x01, 0x64, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x02, 0x02, 0x30, 0x82, 0x01, 0x56, 0x1e, 0x82, 0x01, 0x52,
+ 0x00, 0x41, 0x00, 0x6e, 0x00, 0x79, 0x00, 0x20, 0x00, 0x75, 0x00, 0x73,
+ 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x69, 0x00, 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x65,
+ 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63,
+ 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x00, 0x63, 0x00, 0x6f,
+ 0x00, 0x6e, 0x00, 0x73, 0x00, 0x74, 0x00, 0x69, 0x00, 0x74, 0x00, 0x75,
+ 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x20, 0x00, 0x61, 0x00, 0x63,
+ 0x00, 0x63, 0x00, 0x65, 0x00, 0x70, 0x00, 0x74, 0x00, 0x61, 0x00, 0x6e,
+ 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x20,
+ 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x44, 0x00, 0x69,
+ 0x00, 0x67, 0x00, 0x69, 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74,
+ 0x00, 0x20, 0x00, 0x45, 0x00, 0x56, 0x00, 0x20, 0x00, 0x43, 0x00, 0x50,
+ 0x00, 0x53, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+ 0x00, 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x20, 0x00, 0x52, 0x00, 0x65,
+ 0x00, 0x6c, 0x00, 0x79, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x20,
+ 0x00, 0x50, 0x00, 0x61, 0x00, 0x72, 0x00, 0x74, 0x00, 0x79, 0x00, 0x20,
+ 0x00, 0x41, 0x00, 0x67, 0x00, 0x72, 0x00, 0x65, 0x00, 0x65, 0x00, 0x6d,
+ 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x20, 0x00, 0x77, 0x00, 0x68,
+ 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+ 0x00, 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x69,
+ 0x00, 0x61, 0x00, 0x62, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x74,
+ 0x00, 0x79, 0x00, 0x20, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x20,
+ 0x00, 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6e,
+ 0x00, 0x63, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x70, 0x00, 0x6f, 0x00, 0x72,
+ 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, 0x68,
+ 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x20,
+ 0x00, 0x62, 0x00, 0x79, 0x00, 0x20, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66,
+ 0x00, 0x65, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x65,
+ 0x00, 0x2e, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+ 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x81,
+ 0x83, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x77, 0x30, 0x75, 0x30, 0x24, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+ 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4d, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x41, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65,
+ 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x41, 0x43, 0x65, 0x72,
+ 0x74, 0x73, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48,
+ 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65,
+ 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74,
+ 0x30, 0x81, 0x8f, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x81, 0x87, 0x30,
+ 0x81, 0x84, 0x30, 0x40, 0xa0, 0x3e, 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x33, 0x2e, 0x64, 0x69,
+ 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44,
+ 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x48, 0x69, 0x67, 0x68, 0x41,
+ 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f,
+ 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x40, 0xa0, 0x3e,
+ 0xa0, 0x3c, 0x86, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63,
+ 0x72, 0x6c, 0x34, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72,
+ 0x74, 0x48, 0x69, 0x67, 0x68, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e,
+ 0x63, 0x65, 0x45, 0x56, 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x41, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04,
+ 0x14, 0x4c, 0x58, 0xcb, 0x25, 0xf0, 0x41, 0x4f, 0x52, 0xf4, 0x28, 0xc8,
+ 0x81, 0x43, 0x9b, 0xa6, 0xa8, 0xa0, 0xe6, 0x92, 0xe5, 0x30, 0x1f, 0x06,
+ 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb1, 0x3e,
+ 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08,
+ 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x01, 0x00, 0x4c, 0x7a, 0x17, 0x87, 0x28, 0x5d, 0x17, 0xbc, 0xb2, 0x32,
+ 0x73, 0xbf, 0xcd, 0x2e, 0xf5, 0x58, 0x31, 0x1d, 0xf0, 0xb1, 0x71, 0x54,
+ 0x9c, 0xd6, 0x9b, 0x67, 0x93, 0xdb, 0x2f, 0x03, 0x3e, 0x16, 0x6f, 0x1e,
+ 0x03, 0xc9, 0x53, 0x84, 0xa3, 0x56, 0x60, 0x1e, 0x78, 0x94, 0x1b, 0xa2,
+ 0xa8, 0x6f, 0xa3, 0xa4, 0x8b, 0x52, 0x91, 0xd7, 0xdd, 0x5c, 0x95, 0xbb,
+ 0xef, 0xb5, 0x16, 0x49, 0xe9, 0xa5, 0x42, 0x4f, 0x34, 0xf2, 0x47, 0xff,
+ 0xae, 0x81, 0x7f, 0x13, 0x54, 0xb7, 0x20, 0xc4, 0x70, 0x15, 0xcb, 0x81,
+ 0x0a, 0x81, 0xcb, 0x74, 0x57, 0xdc, 0x9c, 0xdf, 0x24, 0xa4, 0x29, 0x0c,
+ 0x18, 0xf0, 0x1c, 0xe4, 0xae, 0x07, 0x33, 0xec, 0xf1, 0x49, 0x3e, 0x55,
+ 0xcf, 0x6e, 0x4f, 0x0d, 0x54, 0x7b, 0xd3, 0xc9, 0xe8, 0x15, 0x48, 0xd4,
+ 0xc5, 0xbb, 0xdc, 0x35, 0x1c, 0x77, 0x45, 0x07, 0x48, 0x45, 0x85, 0xbd,
+ 0xd7, 0x7e, 0x53, 0xb8, 0xc0, 0x16, 0xd9, 0x95, 0xcd, 0x8b, 0x8d, 0x7d,
+ 0xc9, 0x60, 0x4f, 0xd1, 0xa2, 0x9b, 0xe3, 0xd0, 0x30, 0xd6, 0xb4, 0x73,
+ 0x36, 0xe6, 0xd2, 0xf9, 0x03, 0xb2, 0xe3, 0xa4, 0xf5, 0xe5, 0xb8, 0x3e,
+ 0x04, 0x49, 0x00, 0xba, 0x2e, 0xa6, 0x4a, 0x72, 0x83, 0x72, 0x9d, 0xf7,
+ 0x0b, 0x8c, 0xa9, 0x89, 0xe7, 0xb3, 0xd7, 0x64, 0x1f, 0xd6, 0xe3, 0x60,
+ 0xcb, 0x03, 0xc4, 0xdc, 0x88, 0xe9, 0x9d, 0x25, 0x01, 0x00, 0x71, 0xcb,
+ 0x03, 0xb4, 0x29, 0x60, 0x25, 0x8f, 0xf9, 0x46, 0xd1, 0x7b, 0x71, 0xae,
+ 0xcd, 0x53, 0x12, 0x5b, 0x84, 0x8e, 0xc2, 0x0f, 0xc7, 0xed, 0x93, 0x19,
+ 0xd9, 0xc9, 0xfa, 0x8f, 0x58, 0x34, 0x76, 0x32, 0x2f, 0xae, 0xe1, 0x50,
+ 0x14, 0x61, 0xd4, 0xa8, 0x58, 0xa3, 0xc8, 0x30, 0x13, 0x23, 0xef, 0xc6,
+ 0x25, 0x8c, 0x36, 0x8f, 0x1c, 0x80,
+};
diff --git a/chromium/net/quic/crypto/common_cert_set_test.cc b/chromium/net/quic/crypto/common_cert_set_test.cc
new file mode 100644
index 00000000000..325d0b2c1f3
--- /dev/null
+++ b/chromium/net/quic/crypto/common_cert_set_test.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 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/quic/crypto/common_cert_set.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+static const unsigned char kGIACertificate[] = {
+ 0x30, 0x82, 0x02, 0xb0, 0x30, 0x82, 0x02, 0x19, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x0b, 0x67, 0x71, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4e, 0x31,
+ 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
+ 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x45,
+ 0x71, 0x75, 0x69, 0x66, 0x61, 0x78, 0x31, 0x2d, 0x30, 0x2b, 0x06, 0x03,
+ 0x55, 0x04, 0x0b, 0x13, 0x24, 0x45, 0x71, 0x75, 0x69, 0x66, 0x61, 0x78,
+ 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x74, 0x79, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30,
+ 0x36, 0x30, 0x38, 0x32, 0x30, 0x34, 0x33, 0x32, 0x37, 0x5a, 0x17, 0x0d,
+ 0x31, 0x33, 0x30, 0x36, 0x30, 0x37, 0x31, 0x39, 0x34, 0x33, 0x32, 0x37,
+ 0x5a, 0x30, 0x46, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x0a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e,
+ 0x63, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19,
+ 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72,
+ 0x6e, 0x65, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30,
+ 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xc9, 0xed, 0xb7, 0xa4, 0x8b, 0x9c,
+ 0x57, 0xe7, 0x84, 0x3e, 0x40, 0x7d, 0x84, 0xf4, 0x8f, 0xd1, 0x71, 0x63,
+ 0x53, 0x99, 0xe7, 0x79, 0x74, 0x14, 0xaf, 0x44, 0x99, 0x33, 0x20, 0x92,
+ 0x8d, 0x7b, 0xe5, 0x28, 0x0c, 0xba, 0xad, 0x6c, 0x49, 0x7e, 0x83, 0x5f,
+ 0x34, 0x59, 0x4e, 0x0a, 0x7a, 0x30, 0xcd, 0xd0, 0xd7, 0xc4, 0x57, 0x45,
+ 0xed, 0xd5, 0xaa, 0xd6, 0x73, 0x26, 0xce, 0xad, 0x32, 0x13, 0xb8, 0xd7,
+ 0x0f, 0x1d, 0x3b, 0xdf, 0xdd, 0xdc, 0x08, 0x36, 0xa8, 0x6f, 0x51, 0x44,
+ 0x9b, 0xca, 0xd6, 0x20, 0x52, 0x73, 0xb7, 0x26, 0x87, 0x35, 0x6a, 0xdb,
+ 0xa9, 0xe5, 0xd4, 0x59, 0xa5, 0x2b, 0xfc, 0x67, 0x19, 0x39, 0xfa, 0x93,
+ 0x18, 0x18, 0x6c, 0xde, 0xdd, 0x25, 0x8a, 0x0e, 0x33, 0x14, 0x47, 0xc2,
+ 0xef, 0x01, 0x50, 0x79, 0xe4, 0xfd, 0x69, 0xd1, 0xa7, 0xc0, 0xac, 0xe2,
+ 0x57, 0x6f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xa3, 0x30, 0x81,
+ 0xa0, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04,
+ 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
+ 0x04, 0x16, 0x04, 0x14, 0xbf, 0xc0, 0x30, 0xeb, 0xf5, 0x43, 0x11, 0x3e,
+ 0x67, 0xba, 0x9e, 0x91, 0xfb, 0xfc, 0x6a, 0xda, 0xe3, 0x6b, 0x12, 0x24,
+ 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+ 0x14, 0x48, 0xe6, 0x68, 0xf9, 0x2b, 0xd2, 0xb2, 0x95, 0xd7, 0x47, 0xd8,
+ 0x23, 0x20, 0x10, 0x4f, 0x33, 0x98, 0x90, 0x9f, 0xd4, 0x30, 0x12, 0x06,
+ 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01,
+ 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x3a, 0x06, 0x03, 0x55, 0x1d, 0x1f,
+ 0x04, 0x33, 0x30, 0x31, 0x30, 0x2f, 0xa0, 0x2d, 0xa0, 0x2b, 0x86, 0x29,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x67,
+ 0x65, 0x6f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x63, 0x72, 0x6c, 0x73, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x63,
+ 0x61, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00,
+ 0xb8, 0x8a, 0x23, 0xc6, 0x48, 0x96, 0xb1, 0x11, 0x7c, 0x60, 0x77, 0x5e,
+ 0x05, 0x9a, 0xab, 0xa1, 0xc6, 0xfa, 0x82, 0x1c, 0x18, 0x07, 0xc4, 0xeb,
+ 0x81, 0xb0, 0xa8, 0x66, 0xeb, 0x49, 0xa8, 0xe9, 0x0c, 0xd3, 0x29, 0xad,
+ 0xf5, 0xef, 0x24, 0x4c, 0xfd, 0xe4, 0x4b, 0xca, 0x7f, 0x5e, 0x63, 0xab,
+ 0x99, 0x27, 0xcb, 0x9f, 0x36, 0x21, 0x2c, 0xb9, 0x10, 0x60, 0x67, 0xcd,
+ 0xd2, 0xb4, 0xf0, 0xf0, 0xab, 0x71, 0xe5, 0x8b, 0x5a, 0x89, 0x27, 0x11,
+ 0x84, 0xaa, 0x8e, 0xbf, 0x99, 0xf0, 0x9d, 0x09, 0x21, 0x0a, 0x52, 0x19,
+ 0x9a, 0x5a, 0x09, 0xd2, 0x90, 0xb7, 0xfa, 0x0c, 0xf8, 0x7e, 0x78, 0xa2,
+ 0xb0, 0x85, 0xaf, 0x5c, 0x4c, 0x99, 0xd9, 0x5c, 0x55, 0x29, 0xf9, 0xa5,
+ 0x51, 0x42, 0x2e, 0x3a, 0xcb, 0x38, 0x8c, 0x78, 0x3b, 0xcb, 0xf8, 0xfb,
+ 0x95, 0x87, 0xbc, 0xbc, 0x90, 0xf9, 0x50, 0x32,
+};
+
+TEST(CommonCertSets, FindGIA) {
+ StringPiece gia(reinterpret_cast<const char*>(kGIACertificate),
+ sizeof(kGIACertificate));
+
+ const CommonCertSets* sets(CommonCertSets::GetInstanceQUIC());
+
+ const uint64 in_hash = GG_UINT64_C(0xc9fef74053f99f39);
+ uint64 hash;
+ uint32 index;
+ ASSERT_TRUE(sets->MatchCert(
+ gia,
+ StringPiece(reinterpret_cast<const char*>(&in_hash), sizeof(in_hash)),
+ &hash, &index));
+ EXPECT_EQ(in_hash, hash);
+
+ StringPiece gia_copy = sets->GetCert(hash, index);
+ EXPECT_FALSE(gia_copy.empty());
+ ASSERT_EQ(gia.size(), gia_copy.size());
+ EXPECT_TRUE(0 == memcmp(gia.data(), gia_copy.data(), gia.size()));
+}
+
+TEST(CommonCertSets, NonMatch) {
+ const CommonCertSets* sets(CommonCertSets::GetInstanceQUIC());
+ StringPiece not_a_cert("hello");
+ const uint64 in_hash = GG_UINT64_C(0xc9fef74053f99f39);
+ uint64 hash;
+ uint32 index;
+ EXPECT_FALSE(sets->MatchCert(
+ not_a_cert,
+ StringPiece(reinterpret_cast<const char*>(&in_hash), sizeof(in_hash)),
+ &hash, &index));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_framer.cc b/chromium/net/quic/crypto/crypto_framer.cc
new file mode 100644
index 00000000000..dd5d24f9ae2
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_framer.cc
@@ -0,0 +1,292 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/crypto_framer.h"
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_data_writer.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+const size_t kQuicTagSize = sizeof(uint32);
+const size_t kCryptoEndOffsetSize = sizeof(uint32);
+const size_t kNumEntriesSize = sizeof(uint16);
+
+// OneShotVisitor is a framer visitor that records a single handshake message.
+class OneShotVisitor : public CryptoFramerVisitorInterface {
+ public:
+ explicit OneShotVisitor(CryptoHandshakeMessage* out)
+ : out_(out),
+ error_(false) {
+ }
+
+ virtual void OnError(CryptoFramer* framer) OVERRIDE { error_ = true; }
+
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE {
+ *out_ = message;
+ }
+
+ bool error() const { return error_; }
+
+ private:
+ CryptoHandshakeMessage* const out_;
+ bool error_;
+};
+
+} // namespace
+
+CryptoFramer::CryptoFramer()
+ : visitor_(NULL),
+ num_entries_(0),
+ values_len_(0) {
+ Clear();
+}
+
+CryptoFramer::~CryptoFramer() {}
+
+// static
+CryptoHandshakeMessage* CryptoFramer::ParseMessage(StringPiece in) {
+ scoped_ptr<CryptoHandshakeMessage> msg(new CryptoHandshakeMessage);
+ OneShotVisitor visitor(msg.get());
+ CryptoFramer framer;
+
+ framer.set_visitor(&visitor);
+ if (!framer.ProcessInput(in) || visitor.error() ||
+ framer.InputBytesRemaining()) {
+ return NULL;
+ }
+
+ return msg.release();
+}
+
+bool CryptoFramer::ProcessInput(StringPiece input) {
+ DCHECK_EQ(QUIC_NO_ERROR, error_);
+ if (error_ != QUIC_NO_ERROR) {
+ return false;
+ }
+ error_ = Process(input);
+ if (error_ != QUIC_NO_ERROR) {
+ visitor_->OnError(this);
+ return false;
+ }
+
+ return true;
+}
+
+// static
+QuicData* CryptoFramer::ConstructHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ size_t num_entries = message.tag_value_map().size();
+ size_t pad_length = 0;
+ bool need_pad_tag = false;
+ bool need_pad_value = false;
+
+ size_t len = message.size();
+ if (len < message.minimum_size()) {
+ need_pad_tag = true;
+ need_pad_value = true;
+ num_entries++;
+
+ size_t delta = message.minimum_size() - len;
+ const size_t overhead = kQuicTagSize + kCryptoEndOffsetSize;
+ if (delta > overhead) {
+ pad_length = delta - overhead;
+ }
+ len += overhead + pad_length;
+ }
+
+ if (num_entries > kMaxEntries) {
+ return NULL;
+ }
+
+
+ QuicDataWriter writer(len);
+ if (!writer.WriteUInt32(message.tag())) {
+ DCHECK(false) << "Failed to write message tag.";
+ return NULL;
+ }
+ if (!writer.WriteUInt16(num_entries)) {
+ DCHECK(false) << "Failed to write size.";
+ return NULL;
+ }
+ if (!writer.WriteUInt16(0)) {
+ DCHECK(false) << "Failed to write padding.";
+ return NULL;
+ }
+
+ uint32 end_offset = 0;
+ // Tags and offsets
+ for (QuicTagValueMap::const_iterator it = message.tag_value_map().begin();
+ it != message.tag_value_map().end(); ++it) {
+ if (it->first == kPAD && need_pad_tag) {
+ // Existing PAD tags are only checked when padding needs to be added
+ // because parts of the code may need to reserialize received messages
+ // and those messages may, legitimately include padding.
+ DCHECK(false) << "Message needed padding but already contained a PAD tag";
+ return NULL;
+ }
+
+ if (it->first > kPAD && need_pad_tag) {
+ need_pad_tag = false;
+ if (!WritePadTag(&writer, pad_length, &end_offset)) {
+ return NULL;
+ }
+ }
+
+ if (!writer.WriteUInt32(it->first)) {
+ DCHECK(false) << "Failed to write tag.";
+ return NULL;
+ }
+ end_offset += it->second.length();
+ if (!writer.WriteUInt32(end_offset)) {
+ DCHECK(false) << "Failed to write end offset.";
+ return NULL;
+ }
+ }
+
+ if (need_pad_tag) {
+ if (!WritePadTag(&writer, pad_length, &end_offset)) {
+ return NULL;
+ }
+ }
+
+ // Values
+ for (QuicTagValueMap::const_iterator it = message.tag_value_map().begin();
+ it != message.tag_value_map().end(); ++it) {
+ if (it->first > kPAD && need_pad_value) {
+ need_pad_value = false;
+ if (!writer.WriteRepeatedByte('-', pad_length)) {
+ DCHECK(false) << "Failed to write padding.";
+ return NULL;
+ }
+ }
+
+ if (!writer.WriteBytes(it->second.data(), it->second.length())) {
+ DCHECK(false) << "Failed to write value.";
+ return NULL;
+ }
+ }
+
+ if (need_pad_value) {
+ if (!writer.WriteRepeatedByte('-', pad_length)) {
+ DCHECK(false) << "Failed to write padding.";
+ return NULL;
+ }
+ }
+
+ return new QuicData(writer.take(), len, true);
+}
+
+void CryptoFramer::Clear() {
+ message_.Clear();
+ tags_and_lengths_.clear();
+ error_ = QUIC_NO_ERROR;
+ state_ = STATE_READING_TAG;
+}
+
+QuicErrorCode CryptoFramer::Process(StringPiece input) {
+ // Add this data to the buffer.
+ buffer_.append(input.data(), input.length());
+ QuicDataReader reader(buffer_.data(), buffer_.length());
+
+ switch (state_) {
+ case STATE_READING_TAG:
+ if (reader.BytesRemaining() < kQuicTagSize) {
+ break;
+ }
+ QuicTag message_tag;
+ reader.ReadUInt32(&message_tag);
+ message_.set_tag(message_tag);
+ state_ = STATE_READING_NUM_ENTRIES;
+ case STATE_READING_NUM_ENTRIES:
+ if (reader.BytesRemaining() < kNumEntriesSize + sizeof(uint16)) {
+ break;
+ }
+ reader.ReadUInt16(&num_entries_);
+ if (num_entries_ > kMaxEntries) {
+ return QUIC_CRYPTO_TOO_MANY_ENTRIES;
+ }
+ uint16 padding;
+ reader.ReadUInt16(&padding);
+
+ tags_and_lengths_.reserve(num_entries_);
+ state_ = STATE_READING_TAGS_AND_LENGTHS;
+ values_len_ = 0;
+ case STATE_READING_TAGS_AND_LENGTHS: {
+ if (reader.BytesRemaining() <
+ num_entries_ * (kQuicTagSize + kCryptoEndOffsetSize)) {
+ break;
+ }
+
+ uint32 last_end_offset = 0;
+ for (unsigned i = 0; i < num_entries_; ++i) {
+ QuicTag tag;
+ reader.ReadUInt32(&tag);
+ if (i > 0 && tag <= tags_and_lengths_[i-1].first) {
+ if (tag == tags_and_lengths_[i-1].first) {
+ return QUIC_CRYPTO_DUPLICATE_TAG;
+ }
+ return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
+ }
+
+ uint32 end_offset;
+ reader.ReadUInt32(&end_offset);
+
+ if (end_offset < last_end_offset) {
+ return QUIC_CRYPTO_TAGS_OUT_OF_ORDER;
+ }
+ tags_and_lengths_.push_back(
+ make_pair(tag, static_cast<size_t>(end_offset - last_end_offset)));
+ last_end_offset = end_offset;
+ }
+ values_len_ = last_end_offset;
+ state_ = STATE_READING_VALUES;
+ }
+ case STATE_READING_VALUES:
+ if (reader.BytesRemaining() < values_len_) {
+ break;
+ }
+ for (vector<pair<QuicTag, size_t> >::const_iterator
+ it = tags_and_lengths_.begin(); it != tags_and_lengths_.end();
+ it++) {
+ StringPiece value;
+ reader.ReadStringPiece(&value, it->second);
+ message_.SetStringPiece(it->first, value);
+ }
+ visitor_->OnHandshakeMessage(message_);
+ Clear();
+ state_ = STATE_READING_TAG;
+ break;
+ }
+ // Save any remaining data.
+ buffer_ = reader.PeekRemainingPayload().as_string();
+ return QUIC_NO_ERROR;
+}
+
+// static
+bool CryptoFramer::WritePadTag(QuicDataWriter* writer,
+ size_t pad_length,
+ uint32* end_offset) {
+ if (!writer->WriteUInt32(kPAD)) {
+ DCHECK(false) << "Failed to write tag.";
+ return false;
+ }
+ *end_offset += pad_length;
+ if (!writer->WriteUInt32(*end_offset)) {
+ DCHECK(false) << "Failed to write end offset.";
+ return false;
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_framer.h b/chromium/net/quic/crypto/crypto_framer.h
new file mode 100644
index 00000000000..b070c66e277
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_framer.h
@@ -0,0 +1,118 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
+#define NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CryptoFramer;
+class QuicData;
+class QuicDataReader;
+class QuicDataWriter;
+
+class NET_EXPORT_PRIVATE CryptoFramerVisitorInterface {
+ public:
+ virtual ~CryptoFramerVisitorInterface() {}
+
+ // Called if an error is detected.
+ virtual void OnError(CryptoFramer* framer) = 0;
+
+ // Called when a complete handshake message has been parsed.
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) = 0;
+};
+
+// A class for framing the crypto messages that are exchanged in a QUIC
+// session.
+class NET_EXPORT_PRIVATE CryptoFramer {
+ public:
+ CryptoFramer();
+
+ virtual ~CryptoFramer();
+
+ // ParseMessage parses exactly one message from the given StringPiece. If
+ // there is an error, the message is truncated, or the message has trailing
+ // garbage then NULL will be returned.
+ static CryptoHandshakeMessage* ParseMessage(base::StringPiece in);
+
+ // Set callbacks to be called from the framer. A visitor must be set, or
+ // else the framer will crash. It is acceptable for the visitor to do
+ // nothing. If this is called multiple times, only the last visitor
+ // will be used. |visitor| will be owned by the framer.
+ void set_visitor(CryptoFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+ }
+
+ QuicErrorCode error() const { return error_; }
+
+ // Processes input data, which must be delivered in order. Returns
+ // false if there was an error, and true otherwise.
+ bool ProcessInput(base::StringPiece input);
+
+ // Returns the number of bytes of buffered input data remaining to be
+ // parsed.
+ size_t InputBytesRemaining() const { return buffer_.length(); }
+
+ // Returns a new QuicData owned by the caller that contains a serialized
+ // |message|, or NULL if there was an error.
+ static QuicData* ConstructHandshakeMessage(
+ const CryptoHandshakeMessage& message);
+
+ private:
+ // Clears per-message state. Does not clear the visitor.
+ void Clear();
+
+ // Process does does the work of |ProcessInput|, but returns an error code,
+ // doesn't set error_ and doesn't call |visitor_->OnError()|.
+ QuicErrorCode Process(base::StringPiece input);
+
+ static bool WritePadTag(QuicDataWriter* writer,
+ size_t pad_length,
+ uint32* end_offset);
+
+ void set_error(QuicErrorCode error) { error_ = error; }
+
+ // Represents the current state of the parsing state machine.
+ enum CryptoFramerState {
+ STATE_READING_TAG,
+ STATE_READING_NUM_ENTRIES,
+ STATE_READING_TAGS_AND_LENGTHS,
+ STATE_READING_VALUES
+ };
+
+ // Visitor to invoke when messages are parsed.
+ CryptoFramerVisitorInterface* visitor_;
+ // Last error.
+ QuicErrorCode error_;
+ // Remaining unparsed data.
+ std::string buffer_;
+ // Current state of the parsing.
+ CryptoFramerState state_;
+ // The message currently being parsed.
+ CryptoHandshakeMessage message_;
+ // Number of entires in the message currently being parsed.
+ uint16 num_entries_;
+ // tags_and_lengths_ contains the tags that are currently being parsed and
+ // their lengths.
+ std::vector<std::pair<QuicTag, size_t> > tags_and_lengths_;
+ // Cumulative length of all values in the message currently being parsed.
+ size_t values_len_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_FRAMER_H_
diff --git a/chromium/net/quic/crypto/crypto_framer_test.cc b/chromium/net/quic/crypto/crypto_framer_test.cc
new file mode 100644
index 00000000000..4486f595eee
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_framer_test.cc
@@ -0,0 +1,485 @@
+// Copyright (c) 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.
+
+#include <map>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+char* AsChars(unsigned char* data) { return reinterpret_cast<char*>(data); }
+
+} // namespace
+
+namespace test {
+
+class TestCryptoVisitor : public ::net::CryptoFramerVisitorInterface {
+ public:
+ TestCryptoVisitor() : error_count_(0) {}
+
+ virtual void OnError(CryptoFramer* framer) OVERRIDE {
+ DLOG(ERROR) << "CryptoFramer Error: " << framer->error();
+ ++error_count_;
+ }
+
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE {
+ messages_.push_back(message);
+ }
+
+ // Counters from the visitor callbacks.
+ int error_count_;
+
+ vector<CryptoHandshakeMessage> messages_;
+};
+
+TEST(CryptoFramerTest, ConstructHandshakeMessage) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ message.SetStringPiece(0x12345678, "abcdef");
+ message.SetStringPiece(0x12345679, "ghijk");
+ message.SetStringPiece(0x1234567A, "lmnopqr");
+
+ unsigned char packet[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x03, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x06, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x0b, 0x00, 0x00, 0x00,
+ // tag 3
+ 0x7A, 0x56, 0x34, 0x12,
+ // end offset 3
+ 0x12, 0x00, 0x00, 0x00,
+ // value 1
+ 'a', 'b', 'c', 'd',
+ 'e', 'f',
+ // value 2
+ 'g', 'h', 'i', 'j',
+ 'k',
+ // value 3
+ 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r',
+ };
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ ASSERT_TRUE(data.get() != NULL);
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(packet),
+ arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageWithTwoKeys) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ message.SetStringPiece(0x12345678, "abcdef");
+ message.SetStringPiece(0x12345679, "ghijk");
+
+ unsigned char packet[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x06, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x0b, 0x00, 0x00, 0x00,
+ // value 1
+ 'a', 'b', 'c', 'd',
+ 'e', 'f',
+ // value 2
+ 'g', 'h', 'i', 'j',
+ 'k',
+ };
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ ASSERT_TRUE(data.get() != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(packet),
+ arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageZeroLength) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ message.SetStringPiece(0x12345678, "");
+
+ unsigned char packet[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x01, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ ASSERT_TRUE(data.get() != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(packet),
+ arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageTooManyEntries) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ for (uint32 key = 1; key <= kMaxEntries + 1; ++key) {
+ message.SetStringPiece(key, "abcdef");
+ }
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ EXPECT_TRUE(data.get() == NULL);
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSize) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ message.SetStringPiece(0x01020304, "test");
+ message.set_minimum_size(64);
+
+ unsigned char packet[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 'P', 'A', 'D', 0,
+ // end offset 1
+ 0x24, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x04, 0x03, 0x02, 0x01,
+ // end offset 2
+ 0x28, 0x00, 0x00, 0x00,
+ // 36 bytes of padding.
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-',
+ // value 2
+ 't', 'e', 's', 't',
+ };
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ ASSERT_TRUE(data.get() != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(packet),
+ arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ConstructHandshakeMessageMinimumSizePadLast) {
+ CryptoHandshakeMessage message;
+ message.set_tag(0xFFAA7733);
+ message.SetStringPiece(1, "");
+ message.set_minimum_size(64);
+
+ unsigned char packet[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x01, 0x00, 0x00, 0x00,
+ // end offset 1
+ 0x00, 0x00, 0x00, 0x00,
+ // tag 2
+ 'P', 'A', 'D', 0,
+ // end offset 2
+ 0x28, 0x00, 0x00, 0x00,
+ // 40 bytes of padding.
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ '-', '-', '-', '-', '-', '-', '-', '-',
+ };
+
+ CryptoFramer framer;
+ scoped_ptr<QuicData> data(framer.ConstructHandshakeMessage(message));
+ ASSERT_TRUE(data.get() != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet", data->data(),
+ data->length(), AsChars(packet),
+ arraysize(packet));
+}
+
+TEST(CryptoFramerTest, ProcessInput) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x06, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x0b, 0x00, 0x00, 0x00,
+ // value 1
+ 'a', 'b', 'c', 'd',
+ 'e', 'f',
+ // value 2
+ 'g', 'h', 'i', 'j',
+ 'k',
+ };
+
+ EXPECT_TRUE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(0u, framer.InputBytesRemaining());
+ EXPECT_EQ(0, visitor.error_count_);
+ ASSERT_EQ(1u, visitor.messages_.size());
+ const CryptoHandshakeMessage& message = visitor.messages_[0];
+ EXPECT_EQ(0xFFAA7733, message.tag());
+ EXPECT_EQ(2u, message.tag_value_map().size());
+ EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+ EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+}
+
+TEST(CryptoFramerTest, ProcessInputWithThreeKeys) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x03, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x06, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x0b, 0x00, 0x00, 0x00,
+ // tag 3
+ 0x7A, 0x56, 0x34, 0x12,
+ // end offset 3
+ 0x12, 0x00, 0x00, 0x00,
+ // value 1
+ 'a', 'b', 'c', 'd',
+ 'e', 'f',
+ // value 2
+ 'g', 'h', 'i', 'j',
+ 'k',
+ // value 3
+ 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r',
+ };
+
+ EXPECT_TRUE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(0u, framer.InputBytesRemaining());
+ EXPECT_EQ(0, visitor.error_count_);
+ ASSERT_EQ(1u, visitor.messages_.size());
+ const CryptoHandshakeMessage& message = visitor.messages_[0];
+ EXPECT_EQ(0xFFAA7733, message.tag());
+ EXPECT_EQ(3u, message.tag_value_map().size());
+ EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+ EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+ EXPECT_EQ("lmnopqr", CryptoTestUtils::GetValueForTag(message, 0x1234567A));
+}
+
+TEST(CryptoFramerTest, ProcessInputIncrementally) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x06, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x0b, 0x00, 0x00, 0x00,
+ // value 1
+ 'a', 'b', 'c', 'd',
+ 'e', 'f',
+ // value 2
+ 'g', 'h', 'i', 'j',
+ 'k',
+ };
+
+ for (size_t i = 0; i < arraysize(input); i++) {
+ EXPECT_TRUE(framer.ProcessInput(StringPiece(AsChars(input) + i, 1)));
+ }
+ EXPECT_EQ(0u, framer.InputBytesRemaining());
+ ASSERT_EQ(1u, visitor.messages_.size());
+ const CryptoHandshakeMessage& message = visitor.messages_[0];
+ EXPECT_EQ(0xFFAA7733, message.tag());
+ EXPECT_EQ(2u, message.tag_value_map().size());
+ EXPECT_EQ("abcdef", CryptoTestUtils::GetValueForTag(message, 0x12345678));
+ EXPECT_EQ("ghijk", CryptoTestUtils::GetValueForTag(message, 0x12345679));
+}
+
+TEST(CryptoFramerTest, ProcessInputTagsOutOfOrder) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x13,
+ // end offset 1
+ 0x01, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x02, 0x00, 0x00, 0x00,
+ };
+
+ EXPECT_FALSE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(QUIC_CRYPTO_TAGS_OUT_OF_ORDER, framer.error());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessEndOffsetsOutOfOrder) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x01, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x78, 0x56, 0x34, 0x13,
+ // end offset 2
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ EXPECT_FALSE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(QUIC_CRYPTO_TAGS_OUT_OF_ORDER, framer.error());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessInputTooManyEntries) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0xA0, 0x00,
+ // padding
+ 0x00, 0x00,
+ };
+
+ EXPECT_FALSE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(QUIC_CRYPTO_TOO_MANY_ENTRIES, framer.error());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST(CryptoFramerTest, ProcessInputZeroLength) {
+ test::TestCryptoVisitor visitor;
+ CryptoFramer framer;
+ framer.set_visitor(&visitor);
+
+ unsigned char input[] = {
+ // tag
+ 0x33, 0x77, 0xAA, 0xFF,
+ // num entries
+ 0x02, 0x00,
+ // padding
+ 0x00, 0x00,
+ // tag 1
+ 0x78, 0x56, 0x34, 0x12,
+ // end offset 1
+ 0x00, 0x00, 0x00, 0x00,
+ // tag 2
+ 0x79, 0x56, 0x34, 0x12,
+ // end offset 2
+ 0x05, 0x00, 0x00, 0x00,
+ };
+
+ EXPECT_TRUE(
+ framer.ProcessInput(StringPiece(AsChars(input), arraysize(input))));
+ EXPECT_EQ(0, visitor.error_count_);
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_handshake.cc b/chromium/net/quic/crypto/crypto_handshake.cc
new file mode 100644
index 00000000000..d6a76f9f511
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_handshake.cc
@@ -0,0 +1,897 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_handshake.h"
+
+#include <ctype.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "crypto/secure_hash.h"
+#include "net/base/net_util.h"
+#include "net/quic/crypto/cert_compressor.h"
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/curve25519_key_exchange.h"
+#include "net/quic/crypto/key_exchange.h"
+#include "net/quic/crypto/p256_key_exchange.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+using base::StringPiece;
+using base::StringPrintf;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace net {
+
+CryptoHandshakeMessage::CryptoHandshakeMessage()
+ : tag_(0),
+ minimum_size_(0) {}
+
+CryptoHandshakeMessage::CryptoHandshakeMessage(
+ const CryptoHandshakeMessage& other)
+ : tag_(other.tag_),
+ tag_value_map_(other.tag_value_map_),
+ minimum_size_(other.minimum_size_) {
+ // Don't copy serialized_. scoped_ptr doesn't have a copy constructor.
+ // The new object can lazily reconstruct serialized_.
+}
+
+CryptoHandshakeMessage::~CryptoHandshakeMessage() {}
+
+CryptoHandshakeMessage& CryptoHandshakeMessage::operator=(
+ const CryptoHandshakeMessage& other) {
+ tag_ = other.tag_;
+ tag_value_map_ = other.tag_value_map_;
+ // Don't copy serialized_. scoped_ptr doesn't have an assignment operator.
+ // However, invalidate serialized_.
+ serialized_.reset();
+ minimum_size_ = other.minimum_size_;
+ return *this;
+}
+
+void CryptoHandshakeMessage::Clear() {
+ tag_ = 0;
+ tag_value_map_.clear();
+ minimum_size_ = 0;
+ serialized_.reset();
+}
+
+const QuicData& CryptoHandshakeMessage::GetSerialized() const {
+ if (!serialized_.get()) {
+ serialized_.reset(CryptoFramer::ConstructHandshakeMessage(*this));
+ }
+ return *serialized_.get();
+}
+
+void CryptoHandshakeMessage::MarkDirty() {
+ serialized_.reset();
+}
+
+void CryptoHandshakeMessage::Insert(QuicTagValueMap::const_iterator begin,
+ QuicTagValueMap::const_iterator end) {
+ tag_value_map_.insert(begin, end);
+}
+
+void CryptoHandshakeMessage::SetTaglist(QuicTag tag, ...) {
+ // Warning, if sizeof(QuicTag) > sizeof(int) then this function will break
+ // because the terminating 0 will only be promoted to int.
+ COMPILE_ASSERT(sizeof(QuicTag) <= sizeof(int),
+ crypto_tag_may_not_be_larger_than_int_or_varargs_will_break);
+
+ vector<QuicTag> tags;
+ va_list ap;
+
+ va_start(ap, tag);
+ for (;;) {
+ QuicTag list_item = va_arg(ap, QuicTag);
+ if (list_item == 0) {
+ break;
+ }
+ tags.push_back(list_item);
+ }
+
+ // Because of the way that we keep tags in memory, we can copy the contents
+ // of the vector and get the correct bytes in wire format. See
+ // crypto_protocol.h. This assumes that the system is little-endian.
+ SetVector(tag, tags);
+
+ va_end(ap);
+}
+
+void CryptoHandshakeMessage::SetStringPiece(QuicTag tag, StringPiece value) {
+ tag_value_map_[tag] = value.as_string();
+}
+
+void CryptoHandshakeMessage::Erase(QuicTag tag) {
+ tag_value_map_.erase(tag);
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetTaglist(QuicTag tag,
+ const QuicTag** out_tags,
+ size_t* out_len) const {
+ QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+ QuicErrorCode ret = QUIC_NO_ERROR;
+
+ if (it == tag_value_map_.end()) {
+ ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+ } else if (it->second.size() % sizeof(QuicTag) != 0) {
+ ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (ret != QUIC_NO_ERROR) {
+ *out_tags = NULL;
+ *out_len = 0;
+ return ret;
+ }
+
+ *out_tags = reinterpret_cast<const QuicTag*>(it->second.data());
+ *out_len = it->second.size() / sizeof(QuicTag);
+ return ret;
+}
+
+bool CryptoHandshakeMessage::GetStringPiece(QuicTag tag,
+ StringPiece* out) const {
+ QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+ if (it == tag_value_map_.end()) {
+ return false;
+ }
+ *out = it->second;
+ return true;
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetNthValue24(QuicTag tag,
+ unsigned index,
+ StringPiece* out) const {
+ StringPiece value;
+ if (!GetStringPiece(tag, &value)) {
+ return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+ }
+
+ for (unsigned i = 0;; i++) {
+ if (value.empty()) {
+ return QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND;
+ }
+ if (value.size() < 3) {
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(value.data());
+ size_t size = static_cast<size_t>(data[0]) |
+ (static_cast<size_t>(data[1]) << 8) |
+ (static_cast<size_t>(data[2]) << 16);
+ value.remove_prefix(3);
+
+ if (value.size() < size) {
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (i == index) {
+ *out = StringPiece(value.data(), size);
+ return QUIC_NO_ERROR;
+ }
+
+ value.remove_prefix(size);
+ }
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint16(QuicTag tag,
+ uint16* out) const {
+ return GetPOD(tag, out, sizeof(uint16));
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint32(QuicTag tag,
+ uint32* out) const {
+ return GetPOD(tag, out, sizeof(uint32));
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetUint64(QuicTag tag,
+ uint64* out) const {
+ return GetPOD(tag, out, sizeof(uint64));
+}
+
+size_t CryptoHandshakeMessage::size() const {
+ size_t ret = sizeof(QuicTag) +
+ sizeof(uint16) /* number of entries */ +
+ sizeof(uint16) /* padding */;
+ ret += (sizeof(QuicTag) + sizeof(uint32) /* end offset */) *
+ tag_value_map_.size();
+ for (QuicTagValueMap::const_iterator i = tag_value_map_.begin();
+ i != tag_value_map_.end(); ++i) {
+ ret += i->second.size();
+ }
+
+ return ret;
+}
+
+void CryptoHandshakeMessage::set_minimum_size(size_t min_bytes) {
+ if (min_bytes == minimum_size_) {
+ return;
+ }
+ serialized_.reset();
+ minimum_size_ = min_bytes;
+}
+
+size_t CryptoHandshakeMessage::minimum_size() const {
+ return minimum_size_;
+}
+
+string CryptoHandshakeMessage::DebugString() const {
+ return DebugStringInternal(0);
+}
+
+QuicErrorCode CryptoHandshakeMessage::GetPOD(
+ QuicTag tag, void* out, size_t len) const {
+ QuicTagValueMap::const_iterator it = tag_value_map_.find(tag);
+ QuicErrorCode ret = QUIC_NO_ERROR;
+
+ if (it == tag_value_map_.end()) {
+ ret = QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+ } else if (it->second.size() != len) {
+ ret = QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (ret != QUIC_NO_ERROR) {
+ memset(out, 0, len);
+ return ret;
+ }
+
+ memcpy(out, it->second.data(), len);
+ return ret;
+}
+
+string CryptoHandshakeMessage::DebugStringInternal(size_t indent) const {
+ string ret = string(2 * indent, ' ') + QuicUtils::TagToString(tag_) + "<\n";
+ ++indent;
+ for (QuicTagValueMap::const_iterator it = tag_value_map_.begin();
+ it != tag_value_map_.end(); ++it) {
+ ret += string(2 * indent, ' ') + QuicUtils::TagToString(it->first) + ": ";
+
+ bool done = false;
+ switch (it->first) {
+ case kKATO:
+ case kVERS:
+ // uint32 value
+ if (it->second.size() == 4) {
+ uint32 value;
+ memcpy(&value, it->second.data(), sizeof(value));
+ ret += base::UintToString(value);
+ done = true;
+ }
+ break;
+ case kKEXS:
+ case kAEAD:
+ case kCGST:
+ case kPDMD:
+ // tag lists
+ if (it->second.size() % sizeof(QuicTag) == 0) {
+ for (size_t j = 0; j < it->second.size(); j += sizeof(QuicTag)) {
+ QuicTag tag;
+ memcpy(&tag, it->second.data() + j, sizeof(tag));
+ if (j > 0) {
+ ret += ",";
+ }
+ ret += QuicUtils::TagToString(tag);
+ }
+ done = true;
+ }
+ break;
+ case kSCFG:
+ // nested messages.
+ if (!it->second.empty()) {
+ scoped_ptr<CryptoHandshakeMessage> msg(
+ CryptoFramer::ParseMessage(it->second));
+ if (msg.get()) {
+ ret += "\n";
+ ret += msg->DebugStringInternal(indent + 1);
+
+ done = true;
+ }
+ }
+ break;
+ case kPAD:
+ ret += StringPrintf("(%d bytes of padding)",
+ static_cast<int>(it->second.size()));
+ done = true;
+ break;
+ }
+
+ if (!done) {
+ // If there's no specific format for this tag, or the value is invalid,
+ // then just use hex.
+ ret += base::HexEncode(it->second.data(), it->second.size());
+ }
+ ret += "\n";
+ }
+ --indent;
+ ret += string(2 * indent, ' ') + ">";
+ return ret;
+}
+
+QuicCryptoNegotiatedParameters::QuicCryptoNegotiatedParameters()
+ : version(0),
+ key_exchange(0),
+ aead(0) {
+}
+
+QuicCryptoNegotiatedParameters::~QuicCryptoNegotiatedParameters() {}
+
+CrypterPair::CrypterPair() {}
+
+CrypterPair::~CrypterPair() {}
+
+// static
+const char QuicCryptoConfig::kInitialLabel[] = "QUIC key expansion";
+
+// static
+const char QuicCryptoConfig::kCETVLabel[] = "QUIC CETV block";
+
+// static
+const char QuicCryptoConfig::kForwardSecureLabel[] =
+ "QUIC forward secure key expansion";
+
+QuicCryptoConfig::QuicCryptoConfig()
+ : version(0),
+ common_cert_sets(CommonCertSets::GetInstanceQUIC()) {
+}
+
+QuicCryptoConfig::~QuicCryptoConfig() {}
+
+QuicCryptoClientConfig::QuicCryptoClientConfig() {}
+
+QuicCryptoClientConfig::~QuicCryptoClientConfig() {
+ STLDeleteValues(&cached_states_);
+}
+
+QuicCryptoClientConfig::CachedState::CachedState()
+ : server_config_valid_(false),
+ generation_counter_(0) {}
+
+QuicCryptoClientConfig::CachedState::~CachedState() {}
+
+bool QuicCryptoClientConfig::CachedState::IsComplete(QuicWallTime now) const {
+ if (server_config_.empty() || !server_config_valid_) {
+ return false;
+ }
+
+ const CryptoHandshakeMessage* scfg = GetServerConfig();
+ if (!scfg) {
+ // Should be impossible short of cache corruption.
+ DCHECK(false);
+ return false;
+ }
+
+ uint64 expiry_seconds;
+ if (scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR ||
+ now.ToUNIXSeconds() >= expiry_seconds) {
+ return false;
+ }
+
+ return true;
+}
+
+const CryptoHandshakeMessage*
+QuicCryptoClientConfig::CachedState::GetServerConfig() const {
+ if (server_config_.empty()) {
+ return NULL;
+ }
+
+ if (!scfg_.get()) {
+ scfg_.reset(CryptoFramer::ParseMessage(server_config_));
+ DCHECK(scfg_.get());
+ }
+ return scfg_.get();
+}
+
+QuicErrorCode QuicCryptoClientConfig::CachedState::SetServerConfig(
+ StringPiece server_config, QuicWallTime now, string* error_details) {
+ const bool matches_existing = server_config == server_config_;
+
+ // Even if the new server config matches the existing one, we still wish to
+ // reject it if it has expired.
+ scoped_ptr<CryptoHandshakeMessage> new_scfg_storage;
+ const CryptoHandshakeMessage* new_scfg;
+
+ if (!matches_existing) {
+ new_scfg_storage.reset(CryptoFramer::ParseMessage(server_config));
+ new_scfg = new_scfg_storage.get();
+ } else {
+ new_scfg = GetServerConfig();
+ }
+
+ if (!new_scfg) {
+ *error_details = "SCFG invalid";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ uint64 expiry_seconds;
+ if (new_scfg->GetUint64(kEXPY, &expiry_seconds) != QUIC_NO_ERROR) {
+ *error_details = "SCFG missing EXPY";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (now.ToUNIXSeconds() >= expiry_seconds) {
+ *error_details = "SCFG has expired";
+ return QUIC_CRYPTO_SERVER_CONFIG_EXPIRED;
+ }
+
+ if (!matches_existing) {
+ server_config_ = server_config.as_string();
+ SetProofInvalid();
+ scfg_.reset(new_scfg_storage.release());
+ }
+ return QUIC_NO_ERROR;
+}
+
+void QuicCryptoClientConfig::CachedState::InvalidateServerConfig() {
+ server_config_.clear();
+ scfg_.reset();
+ SetProofInvalid();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProof(const vector<string>& certs,
+ StringPiece signature) {
+ bool has_changed =
+ signature != server_config_sig_ || certs_.size() != certs.size();
+
+ if (!has_changed) {
+ for (size_t i = 0; i < certs_.size(); i++) {
+ if (certs_[i] != certs[i]) {
+ has_changed = true;
+ break;
+ }
+ }
+ }
+
+ if (!has_changed) {
+ return;
+ }
+
+ // If the proof has changed then it needs to be revalidated.
+ SetProofInvalid();
+ certs_ = certs;
+ server_config_sig_ = signature.as_string();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofValid() {
+ server_config_valid_ = true;
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofInvalid() {
+ server_config_valid_ = false;
+ ++generation_counter_;
+}
+
+const string& QuicCryptoClientConfig::CachedState::server_config() const {
+ return server_config_;
+}
+
+const string&
+QuicCryptoClientConfig::CachedState::source_address_token() const {
+ return source_address_token_;
+}
+
+const vector<string>& QuicCryptoClientConfig::CachedState::certs() const {
+ return certs_;
+}
+
+const string& QuicCryptoClientConfig::CachedState::signature() const {
+ return server_config_sig_;
+}
+
+bool QuicCryptoClientConfig::CachedState::proof_valid() const {
+ return server_config_valid_;
+}
+
+uint64 QuicCryptoClientConfig::CachedState::generation_counter() const {
+ return generation_counter_;
+}
+
+const ProofVerifyDetails*
+QuicCryptoClientConfig::CachedState::proof_verify_details() const {
+ return proof_verify_details_.get();
+}
+
+void QuicCryptoClientConfig::CachedState::set_source_address_token(
+ StringPiece token) {
+ source_address_token_ = token.as_string();
+}
+
+void QuicCryptoClientConfig::CachedState::SetProofVerifyDetails(
+ ProofVerifyDetails* details) {
+ proof_verify_details_.reset(details);
+}
+
+void QuicCryptoClientConfig::SetDefaults() {
+ // Version must be 0.
+ // TODO(agl): this version stuff is obsolete now.
+ version = QuicCryptoConfig::CONFIG_VERSION;
+
+ // Key exchange methods.
+ kexs.resize(2);
+ kexs[0] = kC255;
+ kexs[1] = kP256;
+
+ // Authenticated encryption algorithms.
+ aead.resize(1);
+ aead[0] = kAESG;
+}
+
+QuicCryptoClientConfig::CachedState* QuicCryptoClientConfig::LookupOrCreate(
+ const string& server_hostname) {
+ map<string, CachedState*>::const_iterator it =
+ cached_states_.find(server_hostname);
+ if (it != cached_states_.end()) {
+ return it->second;
+ }
+
+ CachedState* cached = new CachedState;
+ cached_states_.insert(make_pair(server_hostname, cached));
+ return cached;
+}
+
+void QuicCryptoClientConfig::FillInchoateClientHello(
+ const string& server_hostname,
+ const CachedState* cached,
+ QuicCryptoNegotiatedParameters* out_params,
+ CryptoHandshakeMessage* out) const {
+ out->set_tag(kCHLO);
+ out->set_minimum_size(kClientHelloMinimumSize);
+
+ // Server name indication. We only send SNI if it's a valid domain name, as
+ // per the spec.
+ if (CryptoUtils::IsValidSNI(server_hostname)) {
+ out->SetStringPiece(kSNI, server_hostname);
+ }
+ out->SetValue(kVERS, version);
+
+ if (!cached->source_address_token().empty()) {
+ out->SetStringPiece(kSourceAddressTokenTag, cached->source_address_token());
+ }
+
+ if (proof_verifier_.get()) {
+ // Don't request ECDSA proofs on platforms that do not support ECDSA
+ // certificates.
+ bool disableECDSA = false;
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ disableECDSA = true;
+#endif
+ if (disableECDSA) {
+ out->SetTaglist(kPDMD, kX59R, 0);
+ } else {
+ out->SetTaglist(kPDMD, kX509, 0);
+ }
+
+ if (!cached->proof_valid()) {
+ // If we are expecting a certificate chain, double the size of the client
+ // hello so that the response from the server can be larger - hopefully
+ // including the whole certificate chain.
+ out->set_minimum_size(kClientHelloMinimumSize * 2);
+ }
+ }
+
+ if (common_cert_sets) {
+ out->SetStringPiece(kCCS, common_cert_sets->GetCommonHashes());
+ }
+
+ const vector<string>& certs = cached->certs();
+ // We save |certs| in the QuicCryptoNegotiatedParameters so that, if the
+ // client config is being used for multiple connections, another connection
+ // doesn't update the cached certificates and cause us to be unable to
+ // process the server's compressed certificate chain.
+ out_params->cached_certs = certs;
+ if (!certs.empty()) {
+ vector<uint64> hashes;
+ hashes.reserve(certs.size());
+ for (vector<string>::const_iterator i = certs.begin();
+ i != certs.end(); ++i) {
+ hashes.push_back(QuicUtils::FNV1a_64_Hash(i->data(), i->size()));
+ }
+ out->SetVector(kCCRT, hashes);
+ }
+}
+
+QuicErrorCode QuicCryptoClientConfig::FillClientHello(
+ const string& server_hostname,
+ QuicGuid guid,
+ const CachedState* cached,
+ QuicWallTime now,
+ QuicRandom* rand,
+ QuicCryptoNegotiatedParameters* out_params,
+ CryptoHandshakeMessage* out,
+ string* error_details) const {
+ DCHECK(error_details != NULL);
+
+ FillInchoateClientHello(server_hostname, cached, out_params, out);
+
+ const CryptoHandshakeMessage* scfg = cached->GetServerConfig();
+ if (!scfg) {
+ // This should never happen as our caller should have checked
+ // cached->IsComplete() before calling this function.
+ *error_details = "Handshake not ready";
+ return QUIC_CRYPTO_INTERNAL_ERROR;
+ }
+
+ StringPiece scid;
+ if (!scfg->GetStringPiece(kSCID, &scid)) {
+ *error_details = "SCFG missing SCID";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+ out->SetStringPiece(kSCID, scid);
+
+ const QuicTag* their_aeads;
+ const QuicTag* their_key_exchanges;
+ size_t num_their_aeads, num_their_key_exchanges;
+ if (scfg->GetTaglist(kAEAD, &their_aeads,
+ &num_their_aeads) != QUIC_NO_ERROR ||
+ scfg->GetTaglist(kKEXS, &their_key_exchanges,
+ &num_their_key_exchanges) != QUIC_NO_ERROR) {
+ *error_details = "Missing AEAD or KEXS";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ size_t key_exchange_index;
+ if (!QuicUtils::FindMutualTag(
+ aead, their_aeads, num_their_aeads, QuicUtils::PEER_PRIORITY,
+ &out_params->aead, NULL) ||
+ !QuicUtils::FindMutualTag(
+ kexs, their_key_exchanges, num_their_key_exchanges,
+ QuicUtils::PEER_PRIORITY, &out_params->key_exchange,
+ &key_exchange_index)) {
+ *error_details = "Unsupported AEAD or KEXS";
+ return QUIC_CRYPTO_NO_SUPPORT;
+ }
+ out->SetTaglist(kAEAD, out_params->aead, 0);
+ out->SetTaglist(kKEXS, out_params->key_exchange, 0);
+
+ StringPiece public_value;
+ if (scfg->GetNthValue24(kPUBS, key_exchange_index, &public_value) !=
+ QUIC_NO_ERROR) {
+ *error_details = "Missing public value";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ StringPiece orbit;
+ if (!scfg->GetStringPiece(kORBT, &orbit) || orbit.size() != kOrbitSize) {
+ *error_details = "SCFG missing OBIT";
+ return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+ }
+
+ CryptoUtils::GenerateNonce(now, rand, orbit, &out_params->client_nonce);
+ out->SetStringPiece(kNONC, out_params->client_nonce);
+ if (!out_params->server_nonce.empty()) {
+ out->SetStringPiece(kServerNonceTag, out_params->server_nonce);
+ }
+
+ switch (out_params->key_exchange) {
+ case kC255:
+ out_params->client_key_exchange.reset(Curve25519KeyExchange::New(
+ Curve25519KeyExchange::NewPrivateKey(rand)));
+ break;
+ case kP256:
+ out_params->client_key_exchange.reset(P256KeyExchange::New(
+ P256KeyExchange::NewPrivateKey()));
+ break;
+ default:
+ DCHECK(false);
+ *error_details = "Configured to support an unknown key exchange";
+ return QUIC_CRYPTO_INTERNAL_ERROR;
+ }
+
+ if (!out_params->client_key_exchange->CalculateSharedKey(
+ public_value, &out_params->initial_premaster_secret)) {
+ *error_details = "Key exchange failure";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+ out->SetStringPiece(kPUBS, out_params->client_key_exchange->public_value());
+
+ bool do_channel_id = false;
+ if (channel_id_signer_.get()) {
+ const QuicTag* their_proof_demands;
+ size_t num_their_proof_demands;
+ if (scfg->GetTaglist(kPDMD, &their_proof_demands,
+ &num_their_proof_demands) == QUIC_NO_ERROR) {
+ for (size_t i = 0; i < num_their_proof_demands; i++) {
+ if (their_proof_demands[i] == kCHID) {
+ do_channel_id = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (do_channel_id) {
+ // In order to calculate the encryption key for the CETV block we need to
+ // serialise the client hello as it currently is (i.e. without the CETV
+ // block). For this, the client hello is serialized without padding.
+ const size_t orig_min_size = out->minimum_size();
+ out->set_minimum_size(0);
+
+ CryptoHandshakeMessage cetv;
+ cetv.set_tag(kCETV);
+
+ string hkdf_input;
+ const QuicData& client_hello_serialized = out->GetSerialized();
+ hkdf_input.append(QuicCryptoConfig::kCETVLabel,
+ strlen(QuicCryptoConfig::kCETVLabel) + 1);
+ hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid));
+ hkdf_input.append(client_hello_serialized.data(),
+ client_hello_serialized.length());
+ hkdf_input.append(cached->server_config());
+
+ string key, signature;
+ if (!channel_id_signer_->Sign(server_hostname, hkdf_input,
+ &key, &signature)) {
+ *error_details = "Channel ID signature failed";
+ return QUIC_INTERNAL_ERROR;
+ }
+
+ cetv.SetStringPiece(kCIDK, key);
+ cetv.SetStringPiece(kCIDS, signature);
+
+ CrypterPair crypters;
+ CryptoUtils::DeriveKeys(out_params->initial_premaster_secret,
+ out_params->aead, out_params->client_nonce,
+ out_params->server_nonce, hkdf_input,
+ CryptoUtils::CLIENT, &crypters);
+
+ const QuicData& cetv_plaintext = cetv.GetSerialized();
+ scoped_ptr<QuicData> cetv_ciphertext(crypters.encrypter->EncryptPacket(
+ 0 /* sequence number */,
+ StringPiece() /* associated data */,
+ cetv_plaintext.AsStringPiece()));
+
+ out->SetStringPiece(kCETV, cetv_ciphertext->AsStringPiece());
+ out->MarkDirty();
+
+ out->set_minimum_size(orig_min_size);
+ }
+
+ out_params->hkdf_input_suffix.clear();
+ out_params->hkdf_input_suffix.append(reinterpret_cast<char*>(&guid),
+ sizeof(guid));
+ const QuicData& client_hello_serialized = out->GetSerialized();
+ out_params->hkdf_input_suffix.append(client_hello_serialized.data(),
+ client_hello_serialized.length());
+ out_params->hkdf_input_suffix.append(cached->server_config());
+
+ string hkdf_input;
+ const size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
+ hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size());
+ hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len);
+ hkdf_input.append(out_params->hkdf_input_suffix);
+
+ CryptoUtils::DeriveKeys(out_params->initial_premaster_secret,
+ out_params->aead, out_params->client_nonce,
+ out_params->server_nonce, hkdf_input,
+ CryptoUtils::CLIENT, &out_params->initial_crypters);
+
+ return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::ProcessRejection(
+ CachedState* cached,
+ const CryptoHandshakeMessage& rej,
+ QuicWallTime now,
+ QuicCryptoNegotiatedParameters* out_params,
+ string* error_details) {
+ DCHECK(error_details != NULL);
+
+ if (rej.tag() != kREJ) {
+ *error_details = "Message is not REJ";
+ return QUIC_CRYPTO_INTERNAL_ERROR;
+ }
+
+ StringPiece scfg;
+ if (!rej.GetStringPiece(kSCFG, &scfg)) {
+ *error_details = "Missing SCFG";
+ return QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND;
+ }
+
+ QuicErrorCode error = cached->SetServerConfig(scfg, now, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ StringPiece token;
+ if (rej.GetStringPiece(kSourceAddressTokenTag, &token)) {
+ cached->set_source_address_token(token);
+ }
+
+ StringPiece nonce;
+ if (rej.GetStringPiece(kServerNonceTag, &nonce)) {
+ out_params->server_nonce = nonce.as_string();
+ }
+
+ StringPiece proof, cert_bytes;
+ if (rej.GetStringPiece(kPROF, &proof) &&
+ rej.GetStringPiece(kCertificateTag, &cert_bytes)) {
+ vector<string> certs;
+ if (!CertCompressor::DecompressChain(cert_bytes, out_params->cached_certs,
+ common_cert_sets, &certs)) {
+ *error_details = "Certificate data invalid";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ cached->SetProof(certs, proof);
+ }
+
+ return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicCryptoClientConfig::ProcessServerHello(
+ const CryptoHandshakeMessage& server_hello,
+ QuicGuid guid,
+ QuicCryptoNegotiatedParameters* out_params,
+ string* error_details) {
+ DCHECK(error_details != NULL);
+
+ if (server_hello.tag() != kSHLO) {
+ *error_details = "Bad tag";
+ return QUIC_INVALID_CRYPTO_MESSAGE_TYPE;
+ }
+
+ // TODO(agl):
+ // learn about updated SCFGs.
+
+ StringPiece public_value;
+ if (!server_hello.GetStringPiece(kPUBS, &public_value)) {
+ *error_details = "server hello missing forward secure public value";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (!out_params->client_key_exchange->CalculateSharedKey(
+ public_value, &out_params->forward_secure_premaster_secret)) {
+ *error_details = "Key exchange failure";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ string hkdf_input;
+ const size_t label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1;
+ hkdf_input.reserve(label_len + out_params->hkdf_input_suffix.size());
+ hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel, label_len);
+ hkdf_input.append(out_params->hkdf_input_suffix);
+
+ CryptoUtils::DeriveKeys(
+ out_params->forward_secure_premaster_secret, out_params->aead,
+ out_params->client_nonce, out_params->server_nonce, hkdf_input,
+ CryptoUtils::CLIENT, &out_params->forward_secure_crypters);
+
+ return QUIC_NO_ERROR;
+}
+
+ProofVerifier* QuicCryptoClientConfig::proof_verifier() const {
+ return proof_verifier_.get();
+}
+
+void QuicCryptoClientConfig::SetProofVerifier(ProofVerifier* verifier) {
+ proof_verifier_.reset(verifier);
+}
+
+ChannelIDSigner* QuicCryptoClientConfig::channel_id_signer() const {
+ return channel_id_signer_.get();
+}
+
+void QuicCryptoClientConfig::SetChannelIDSigner(ChannelIDSigner* signer) {
+ channel_id_signer_.reset(signer);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_handshake.h b/chromium/net/quic/crypto/crypto_handshake.h
new file mode 100644
index 00000000000..fdc92a0fc38
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_handshake.h
@@ -0,0 +1,399 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
+#define NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ChannelIDSigner;
+class CommonCertSets;
+class KeyExchange;
+class ProofVerifier;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicRandom;
+
+// An intermediate format of a handshake message that's convenient for a
+// CryptoFramer to serialize from or parse into.
+class NET_EXPORT_PRIVATE CryptoHandshakeMessage {
+ public:
+ CryptoHandshakeMessage();
+ CryptoHandshakeMessage(const CryptoHandshakeMessage& other);
+ ~CryptoHandshakeMessage();
+
+ CryptoHandshakeMessage& operator=(const CryptoHandshakeMessage& other);
+
+ // Clears state.
+ void Clear();
+
+ // GetSerialized returns the serialized form of this message and caches the
+ // result. Subsequently altering the message does not invalidate the cache.
+ const QuicData& GetSerialized() const;
+
+ // MarkDirty invalidates the cache created by |GetSerialized|.
+ void MarkDirty();
+
+ // SetValue sets an element with the given tag to the raw, memory contents of
+ // |v|.
+ template<class T> void SetValue(QuicTag tag, const T& v) {
+ tag_value_map_[tag] =
+ std::string(reinterpret_cast<const char*>(&v), sizeof(v));
+ }
+
+ // SetVector sets an element with the given tag to the raw contents of an
+ // array of elements in |v|.
+ template<class T> void SetVector(QuicTag tag, const std::vector<T>& v) {
+ if (v.empty()) {
+ tag_value_map_[tag] = std::string();
+ } else {
+ tag_value_map_[tag] = std::string(reinterpret_cast<const char*>(&v[0]),
+ v.size() * sizeof(T));
+ }
+ }
+
+ // Returns the message tag.
+ QuicTag tag() const { return tag_; }
+ // Sets the message tag.
+ void set_tag(QuicTag tag) { tag_ = tag; }
+
+ const QuicTagValueMap& tag_value_map() const { return tag_value_map_; }
+
+ void Insert(QuicTagValueMap::const_iterator begin,
+ QuicTagValueMap::const_iterator end);
+
+ // SetTaglist sets an element with the given tag to contain a list of tags,
+ // passed as varargs. The argument list must be terminated with a 0 element.
+ void SetTaglist(QuicTag tag, ...);
+
+ void SetStringPiece(QuicTag tag, base::StringPiece value);
+
+ // Erase removes a tag/value, if present, from the message.
+ void Erase(QuicTag tag);
+
+ // GetTaglist finds an element with the given tag containing zero or more
+ // tags. If such a tag doesn't exist, it returns false. Otherwise it sets
+ // |out_tags| and |out_len| to point to the array of tags and returns true.
+ // The array points into the CryptoHandshakeMessage and is valid only for as
+ // long as the CryptoHandshakeMessage exists and is not modified.
+ QuicErrorCode GetTaglist(QuicTag tag, const QuicTag** out_tags,
+ size_t* out_len) const;
+
+ bool GetStringPiece(QuicTag tag, base::StringPiece* out) const;
+
+ // GetNthValue24 interprets the value with the given tag to be a series of
+ // 24-bit, length prefixed values and it returns the subvalue with the given
+ // index.
+ QuicErrorCode GetNthValue24(QuicTag tag,
+ unsigned index,
+ base::StringPiece* out) const;
+ QuicErrorCode GetUint16(QuicTag tag, uint16* out) const;
+ QuicErrorCode GetUint32(QuicTag tag, uint32* out) const;
+ QuicErrorCode GetUint64(QuicTag tag, uint64* out) const;
+
+ // size returns 4 (message tag) + 2 (uint16, number of entries) +
+ // (4 (tag) + 4 (end offset))*tag_value_map_.size() + ∑ value sizes.
+ size_t size() const;
+
+ // set_minimum_size sets the minimum number of bytes that the message should
+ // consume. The CryptoFramer will add a PAD tag as needed when serializing in
+ // order to ensure this. Setting a value of 0 disables padding.
+ //
+ // Padding is useful in order to ensure that messages are a minimum size. A
+ // QUIC server can require a minimum size in order to reduce the
+ // amplification factor of any mirror DoS attack.
+ void set_minimum_size(size_t min_bytes);
+
+ size_t minimum_size() const;
+
+ // DebugString returns a multi-line, string representation of the message
+ // suitable for including in debug output.
+ std::string DebugString() const;
+
+ private:
+ // GetPOD is a utility function for extracting a plain-old-data value. If
+ // |tag| exists in the message, and has a value of exactly |len| bytes then
+ // it copies |len| bytes of data into |out|. Otherwise |len| bytes at |out|
+ // are zeroed out.
+ //
+ // If used to copy integers then this assumes that the machine is
+ // little-endian.
+ QuicErrorCode GetPOD(QuicTag tag, void* out, size_t len) const;
+
+ std::string DebugStringInternal(size_t indent) const;
+
+ QuicTag tag_;
+ QuicTagValueMap tag_value_map_;
+
+ size_t minimum_size_;
+
+ // The serialized form of the handshake message. This member is constructed
+ // lasily.
+ mutable scoped_ptr<QuicData> serialized_;
+};
+
+// A CrypterPair contains the encrypter and decrypter for an encryption level.
+struct NET_EXPORT_PRIVATE CrypterPair {
+ CrypterPair();
+ ~CrypterPair();
+ scoped_ptr<QuicEncrypter> encrypter;
+ scoped_ptr<QuicDecrypter> decrypter;
+};
+
+// Parameters negotiated by the crypto handshake.
+struct NET_EXPORT_PRIVATE QuicCryptoNegotiatedParameters {
+ // Initializes the members to 0 or empty values.
+ QuicCryptoNegotiatedParameters();
+ ~QuicCryptoNegotiatedParameters();
+
+ uint16 version;
+ QuicTag key_exchange;
+ QuicTag aead;
+ std::string initial_premaster_secret;
+ std::string forward_secure_premaster_secret;
+ CrypterPair initial_crypters;
+ CrypterPair forward_secure_crypters;
+ // Normalized SNI: converted to lower case and trailing '.' removed.
+ std::string sni;
+ std::string client_nonce;
+ std::string server_nonce;
+ // hkdf_input_suffix contains the HKDF input following the label: the GUID,
+ // client hello and server config. This is only populated in the client
+ // because only the client needs to derive the forward secure keys at a later
+ // time from the initial keys.
+ std::string hkdf_input_suffix;
+ // cached_certs contains the cached certificates that a client used when
+ // sending a client hello.
+ std::vector<std::string> cached_certs;
+ // client_key_exchange is used by clients to store the ephemeral KeyExchange
+ // for the connection.
+ scoped_ptr<KeyExchange> client_key_exchange;
+ // channel_id is set by servers to a ChannelID key when the client correctly
+ // proves possession of the corresponding private key. It consists of 32
+ // bytes of x coordinate, followed by 32 bytes of y coordinate. Both values
+ // are big-endian and the pair is a P-256 public key.
+ std::string channel_id;
+};
+
+// QuicCryptoConfig contains common configuration between clients and servers.
+class NET_EXPORT_PRIVATE QuicCryptoConfig {
+ public:
+ enum {
+ // CONFIG_VERSION is the one (and, for the moment, only) version number that
+ // we implement.
+ CONFIG_VERSION = 0,
+ };
+
+ // kInitialLabel is a constant that is used when deriving the initial
+ // (non-forward secure) keys for the connection in order to tie the resulting
+ // key to this protocol.
+ static const char kInitialLabel[];
+
+ // kCETVLabel is a constant that is used when deriving the keys for the
+ // encrypted tag/value block in the client hello.
+ static const char kCETVLabel[];
+
+ // kForwardSecureLabel is a constant that is used when deriving the forward
+ // secure keys for the connection in order to tie the resulting key to this
+ // protocol.
+ static const char kForwardSecureLabel[];
+
+ QuicCryptoConfig();
+ ~QuicCryptoConfig();
+
+ // Protocol version
+ uint16 version;
+ // Key exchange methods. The following two members' values correspond by
+ // index.
+ QuicTagVector kexs;
+ // Authenticated encryption with associated data (AEAD) algorithms.
+ QuicTagVector aead;
+
+ const CommonCertSets* common_cert_sets;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicCryptoConfig);
+};
+
+// QuicCryptoClientConfig contains crypto-related configuration settings for a
+// client. Note that this object isn't thread-safe. It's designed to be used on
+// a single thread at a time.
+class NET_EXPORT_PRIVATE QuicCryptoClientConfig : public QuicCryptoConfig {
+ public:
+ // A CachedState contains the information that the client needs in order to
+ // perform a 0-RTT handshake with a server. This information can be reused
+ // over several connections to the same server.
+ class CachedState {
+ public:
+ CachedState();
+ ~CachedState();
+
+ // IsComplete returns true if this object contains enough information to
+ // perform a handshake with the server. |now| is used to judge whether any
+ // cached server config has expired.
+ bool IsComplete(QuicWallTime now) const;
+
+ // GetServerConfig returns the parsed contents of |server_config|, or NULL
+ // if |server_config| is empty. The return value is owned by this object
+ // and is destroyed when this object is.
+ const CryptoHandshakeMessage* GetServerConfig() const;
+
+ // SetServerConfig checks that |server_config| parses correctly and stores
+ // it in |server_config_|. |now| is used to judge whether |server_config|
+ // has expired.
+ QuicErrorCode SetServerConfig(base::StringPiece server_config,
+ QuicWallTime now,
+ std::string* error_details);
+
+ // InvalidateServerConfig clears the cached server config (if any).
+ void InvalidateServerConfig();
+
+ // SetProof stores a certificate chain and signature.
+ void SetProof(const std::vector<std::string>& certs,
+ base::StringPiece signature);
+
+ // SetProofValid records that the certificate chain and signature have been
+ // validated and that it's safe to assume that the server is legitimate.
+ // (Note: this does not check the chain or signature.)
+ void SetProofValid();
+
+ // If the server config or the proof has changed then it needs to be
+ // revalidated. Helper function to keep server_config_valid_ and
+ // generation_counter_ in sync.
+ void SetProofInvalid();
+
+ const std::string& server_config() const;
+ const std::string& source_address_token() const;
+ const std::vector<std::string>& certs() const;
+ const std::string& signature() const;
+ bool proof_valid() const;
+ uint64 generation_counter() const;
+ const ProofVerifyDetails* proof_verify_details() const;
+
+ void set_source_address_token(base::StringPiece token);
+
+ // SetProofVerifyDetails takes ownership of |details|.
+ void SetProofVerifyDetails(ProofVerifyDetails* details);
+
+ private:
+ std::string server_config_id_; // An opaque id from the server.
+ std::string server_config_; // A serialized handshake message.
+ std::string source_address_token_; // An opaque proof of IP ownership.
+ std::vector<std::string> certs_; // A list of certificates in leaf-first
+ // order.
+ std::string server_config_sig_; // A signature of |server_config_|.
+ bool server_config_valid_; // True if |server_config_| is correctly
+ // signed and |certs_| has been
+ // validated.
+ // Generation counter associated with the |server_config_|, |certs_| and
+ // |server_config_sig_| combination. It is incremented whenever we set
+ // server_config_valid_ to false.
+ uint64 generation_counter_;
+
+ scoped_ptr<ProofVerifyDetails> proof_verify_details_;
+
+ // scfg contains the cached, parsed value of |server_config|.
+ mutable scoped_ptr<CryptoHandshakeMessage> scfg_;
+ };
+
+ QuicCryptoClientConfig();
+ ~QuicCryptoClientConfig();
+
+ // Sets the members to reasonable, default values.
+ void SetDefaults();
+
+ // LookupOrCreate returns a CachedState for the given hostname. If no such
+ // CachedState currently exists, it will be created and cached.
+ CachedState* LookupOrCreate(const std::string& server_hostname);
+
+ // FillInchoateClientHello sets |out| to be a CHLO message that elicits a
+ // source-address token or SCFG from a server. If |cached| is non-NULL, the
+ // source-address token will be taken from it. |out_params| is used in order
+ // to store the cached certs that were sent as hints to the server in
+ // |out_params->cached_certs|.
+ void FillInchoateClientHello(const std::string& server_hostname,
+ const CachedState* cached,
+ QuicCryptoNegotiatedParameters* out_params,
+ CryptoHandshakeMessage* out) const;
+
+ // FillClientHello sets |out| to be a CHLO message based on the configuration
+ // of this object. This object must have cached enough information about
+ // |server_hostname| in order to perform a handshake. This can be checked
+ // with the |IsComplete| member of |CachedState|.
+ //
+ // |clock| and |rand| are used to generate the nonce and |out_params| is
+ // filled with the results of the handshake that the server is expected to
+ // accept.
+ QuicErrorCode FillClientHello(const std::string& server_hostname,
+ QuicGuid guid,
+ const CachedState* cached,
+ QuicWallTime now,
+ QuicRandom* rand,
+ QuicCryptoNegotiatedParameters* out_params,
+ CryptoHandshakeMessage* out,
+ std::string* error_details) const;
+
+ // ProcessRejection processes a REJ message from a server and updates the
+ // cached information about that server. After this, |IsComplete| may return
+ // true for that server's CachedState. If the rejection message contains
+ // state about a future handshake (i.e. an nonce value from the server), then
+ // it will be saved in |out_params|. |now| is used to judge whether the
+ // server config in the rejection message has expired.
+ QuicErrorCode ProcessRejection(CachedState* cached,
+ const CryptoHandshakeMessage& rej,
+ QuicWallTime now,
+ QuicCryptoNegotiatedParameters* out_params,
+ std::string* error_details);
+
+ // ProcessServerHello processes the message in |server_hello|, writes the
+ // negotiated parameters to |out_params| and returns QUIC_NO_ERROR. If
+ // |server_hello| is unacceptable then it puts an error message in
+ // |error_details| and returns an error code.
+ QuicErrorCode ProcessServerHello(const CryptoHandshakeMessage& server_hello,
+ QuicGuid guid,
+ QuicCryptoNegotiatedParameters* out_params,
+ std::string* error_details);
+
+ ProofVerifier* proof_verifier() const;
+
+ // SetProofVerifier takes ownership of a |ProofVerifier| that clients are
+ // free to use in order to verify certificate chains from servers. If a
+ // ProofVerifier is set then the client will request a certificate chain from
+ // the server.
+ void SetProofVerifier(ProofVerifier* verifier);
+
+ ChannelIDSigner* channel_id_signer() const;
+
+ // SetChannelIDSigner sets a ChannelIDSigner that will be called when the
+ // server supports channel IDs to sign a message proving possession of the
+ // given ChannelID. This object takes ownership of |signer|.
+ void SetChannelIDSigner(ChannelIDSigner* signer);
+
+ private:
+ // cached_states_ maps from the server hostname to the cached information
+ // about that server.
+ std::map<std::string, CachedState*> cached_states_;
+
+ scoped_ptr<ProofVerifier> proof_verifier_;
+ scoped_ptr<ChannelIDSigner> channel_id_signer_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicCryptoClientConfig);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_HANDSHAKE_H_
diff --git a/chromium/net/quic/crypto/crypto_handshake_test.cc b/chromium/net/quic/crypto/crypto_handshake_test.cc
new file mode 100644
index 00000000000..3ad50bb7f5a
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_handshake_test.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_handshake.h"
+
+#include <stdarg.h>
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/crypto_server_config_protobuf.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class QuicCryptoServerConfigPeer {
+ public:
+ explicit QuicCryptoServerConfigPeer(QuicCryptoServerConfig* server_config)
+ : server_config_(server_config) {}
+
+ string NewSourceAddressToken(IPEndPoint ip,
+ QuicRandom* rand,
+ QuicWallTime now) {
+ return server_config_->NewSourceAddressToken(ip, rand, now);
+ }
+
+ bool ValidateSourceAddressToken(StringPiece srct,
+ IPEndPoint ip,
+ QuicWallTime now) {
+ return server_config_->ValidateSourceAddressToken(srct, ip, now);
+ }
+
+ // CheckConfigs compares the state of the Configs in |server_config_| to the
+ // description given as arguments. The arguments are given as NULL-terminated
+ // pairs. The first of each pair is the server config ID of a Config. The
+ // second is a boolean describing whether the config is the primary. For
+ // example:
+ // CheckConfigs(NULL); // checks that no Configs are loaded.
+ //
+ // // Checks that exactly three Configs are loaded with the given IDs and
+ // // status.
+ // CheckConfigs(
+ // "id1", false,
+ // "id2", true,
+ // "id3", false,
+ // NULL);
+ void CheckConfigs(const char* server_config_id1, ...) {
+ va_list ap;
+ va_start(ap, server_config_id1);
+
+ vector<pair<ServerConfigID, bool> > expected;
+ bool first = true;
+ for (;;) {
+ const char* server_config_id;
+ if (first) {
+ server_config_id = server_config_id1;
+ first = false;
+ } else {
+ server_config_id = va_arg(ap, const char*);
+ }
+
+ if (!server_config_id) {
+ break;
+ }
+
+ // varargs will promote the value to an int so we have to read that from
+ // the stack and cast down.
+ const bool is_primary = static_cast<bool>(va_arg(ap, int));
+ expected.push_back(make_pair(server_config_id, is_primary));
+ }
+
+ va_end(ap);
+
+ base::AutoLock locked(server_config_->configs_lock_);
+
+ ASSERT_EQ(expected.size(), server_config_->configs_.size())
+ << ConfigsDebug();
+
+ for (QuicCryptoServerConfig::ConfigMap::const_iterator
+ i = server_config_->configs_.begin();
+ i != server_config_->configs_.end(); ++i) {
+ bool found = false;
+ for (vector<pair<ServerConfigID, bool> >::iterator j = expected.begin();
+ j != expected.end(); ++j) {
+ if (i->first == j->first && i->second->is_primary == j->second) {
+ found = true;
+ j->first.clear();
+ break;
+ }
+ }
+
+ ASSERT_TRUE(found) << "Failed to find match for " << i->first
+ << " in configs:\n" << ConfigsDebug();
+ }
+ }
+
+ // ConfigsDebug returns a string that contains debugging information about
+ // the set of Configs loaded in |server_config_| and their status.
+ // ConfigsDebug() should be called after acquiring
+ // server_config_->configs_lock_.
+ string ConfigsDebug() {
+ if (server_config_->configs_.empty()) {
+ return "No Configs in QuicCryptoServerConfig";
+ }
+
+ string s;
+
+ for (QuicCryptoServerConfig::ConfigMap::const_iterator
+ i = server_config_->configs_.begin();
+ i != server_config_->configs_.end(); ++i) {
+ const scoped_refptr<QuicCryptoServerConfig::Config> config = i->second;
+ if (config->is_primary) {
+ s += "(primary) ";
+ } else {
+ s += " ";
+ }
+ s += config->id;
+ s += "\n";
+ }
+
+ return s;
+ }
+
+ void SelectNewPrimaryConfig(int seconds) {
+ base::AutoLock locked(server_config_->configs_lock_);
+ server_config_->SelectNewPrimaryConfig(
+ QuicWallTime::FromUNIXSeconds(seconds));
+ }
+
+ private:
+ const QuicCryptoServerConfig* server_config_;
+};
+
+TEST(QuicCryptoServerConfigTest, ServerConfig) {
+ QuicRandom* rand = QuicRandom::GetInstance();
+ QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+ MockClock clock;
+
+ scoped_ptr<CryptoHandshakeMessage>(
+ server.AddDefaultConfig(rand, &clock,
+ QuicCryptoServerConfig::ConfigOptions()));
+}
+
+TEST(QuicCryptoServerConfigTest, SourceAddressTokens) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ QuicRandom* rand = QuicRandom::GetInstance();
+ QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand);
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ IPEndPoint ip4 = IPEndPoint(ip, 1);
+ CHECK(ParseIPLiteralToNumber("2001:db8:0::42", &ip));
+ IPEndPoint ip6 = IPEndPoint(ip, 2);
+ MockClock clock;
+ clock.AdvanceTime(QuicTime::Delta::FromSeconds(1000000));
+ QuicCryptoServerConfigPeer peer(&server);
+
+ QuicWallTime now = clock.WallNow();
+ const QuicWallTime original_time = now;
+
+ const string token4 = peer.NewSourceAddressToken(ip4, rand, now);
+ const string token6 = peer.NewSourceAddressToken(ip6, rand, now);
+ EXPECT_TRUE(peer.ValidateSourceAddressToken(token4, ip4, now));
+ EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip6, now));
+ EXPECT_TRUE(peer.ValidateSourceAddressToken(token6, ip6, now));
+
+ now = original_time.Add(QuicTime::Delta::FromSeconds(86400 * 7));
+ EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip4, now));
+
+ now = original_time.Subtract(QuicTime::Delta::FromSeconds(3600 * 2));
+ EXPECT_FALSE(peer.ValidateSourceAddressToken(token4, ip4, now));
+}
+
+class CryptoServerConfigsTest : public ::testing::Test {
+ public:
+ CryptoServerConfigsTest()
+ : rand_(QuicRandom::GetInstance()),
+ config_(QuicCryptoServerConfig::TESTING, rand_),
+ test_peer_(&config_) {}
+
+ virtual void SetUp() {
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1000));
+ }
+
+ // SetConfigs constructs suitable config protobufs and calls SetConfigs on
+ // |config_|. The arguments are given as NULL-terminated pairs. The first of
+ // each pair is the server config ID of a Config. The second is the
+ // |primary_time| of that Config, given in epoch seconds. (Although note
+ // that, in these tests, time is set to 1000 seconds since the epoch.) For
+ // example:
+ // SetConfigs(NULL); // calls |config_.SetConfigs| with no protobufs.
+ //
+ // // Calls |config_.SetConfigs| with two protobufs: one for a Config with
+ // // a |primary_time| of 900, and another with a |primary_time| of 1000.
+ // CheckConfigs(
+ // "id1", 900,
+ // "id2", 1000,
+ // NULL);
+ //
+ // If the server config id starts with "INVALID" then the generated protobuf
+ // will be invalid.
+ void SetConfigs(const char* server_config_id1, ...) {
+ va_list ap;
+ va_start(ap, server_config_id1);
+ bool has_invalid = false;
+
+ vector<QuicServerConfigProtobuf*> protobufs;
+ bool first = true;
+ for (;;) {
+ const char* server_config_id;
+ if (first) {
+ server_config_id = server_config_id1;
+ first = false;
+ } else {
+ server_config_id = va_arg(ap, const char*);
+ }
+
+ if (!server_config_id) {
+ break;
+ }
+
+ int primary_time = va_arg(ap, int);
+
+ QuicCryptoServerConfig::ConfigOptions options;
+ options.id = server_config_id;
+ QuicServerConfigProtobuf* protobuf(
+ QuicCryptoServerConfig::DefaultConfig(rand_, &clock_, options));
+ protobuf->set_primary_time(primary_time);
+ if (string(server_config_id).find("INVALID") == 0) {
+ protobuf->clear_key();
+ has_invalid = true;
+ }
+ protobufs.push_back(protobuf);
+ }
+
+ ASSERT_EQ(!has_invalid, config_.SetConfigs(protobufs, clock_.WallNow()));
+ STLDeleteElements(&protobufs);
+ }
+
+ protected:
+ QuicRandom* const rand_;
+ MockClock clock_;
+ QuicCryptoServerConfig config_;
+ QuicCryptoServerConfigPeer test_peer_;
+};
+
+TEST_F(CryptoServerConfigsTest, NoConfigs) {
+ test_peer_.CheckConfigs(NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) {
+ // Make sure that "b" is primary even though "a" comes first.
+ SetConfigs("a", 1100,
+ "b", 900,
+ NULL);
+ test_peer_.CheckConfigs(
+ "a", false,
+ "b", true,
+ NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, MakePrimarySecond) {
+ // Make sure that a remains primary after b is added.
+ SetConfigs("a", 900,
+ "b", 1100,
+ NULL);
+ test_peer_.CheckConfigs(
+ "a", true,
+ "b", false,
+ NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, Delete) {
+ // Ensure that configs get deleted when removed.
+ SetConfigs("a", 800,
+ "b", 900,
+ "c", 1100,
+ NULL);
+ SetConfigs("b", 900,
+ "c", 1100,
+ NULL);
+ test_peer_.CheckConfigs(
+ "b", true,
+ "c", false,
+ NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, DontDeletePrimary) {
+ // Ensure that the primary config isn't deleted when removed.
+ SetConfigs("a", 800,
+ "b", 900,
+ "c", 1100,
+ NULL);
+ SetConfigs("a", 800,
+ "c", 1100,
+ NULL);
+ test_peer_.CheckConfigs(
+ "a", false,
+ "b", true,
+ "c", false,
+ NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, AdvancePrimary) {
+ // Check that a new primary config is enabled at the right time.
+ SetConfigs("a", 900,
+ "b", 1100,
+ NULL);
+ test_peer_.SelectNewPrimaryConfig(1000);
+ test_peer_.CheckConfigs(
+ "a", true,
+ "b", false,
+ NULL);
+ test_peer_.SelectNewPrimaryConfig(1101);
+ test_peer_.CheckConfigs(
+ "a", false,
+ "b", true,
+ NULL);
+}
+
+TEST_F(CryptoServerConfigsTest, InvalidConfigs) {
+ // Ensure that invalid configs don't change anything.
+ SetConfigs("a", 800,
+ "b", 900,
+ "c", 1100,
+ NULL);
+ test_peer_.CheckConfigs(
+ "a", false,
+ "b", true,
+ "c", false,
+ NULL);
+ SetConfigs("a", 800,
+ "c", 1100,
+ "INVALID1", 1000,
+ NULL);
+ test_peer_.CheckConfigs(
+ "a", false,
+ "b", true,
+ "c", false,
+ NULL);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_protocol.h b/chromium/net/quic/crypto/crypto_protocol.h
new file mode 100644
index 00000000000..586569a6a0a
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_protocol.h
@@ -0,0 +1,131 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
+#define NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+// Version and Crypto tags are written to the wire with a big-endian
+// representation of the name of the tag. For example
+// the client hello tag (CHLO) will be written as the
+// following 4 bytes: 'C' 'H' 'L' 'O'. Since it is
+// stored in memory as a little endian uint32, we need
+// to reverse the order of the bytes.
+//
+// We use a macro to ensure that no static initialisers are created. Use the
+// MakeQuicTag function in normal code.
+#define TAG(a, b, c, d) ((d << 24) + (c << 16) + (b << 8) + a)
+
+namespace net {
+
+typedef std::string ServerConfigID;
+typedef std::map<QuicTag, std::string> QuicTagValueMap;
+
+const QuicTag kCHLO = TAG('C', 'H', 'L', 'O'); // Client hello
+const QuicTag kSHLO = TAG('S', 'H', 'L', 'O'); // Server hello
+const QuicTag kSCFG = TAG('S', 'C', 'F', 'G'); // Server config
+const QuicTag kREJ = TAG('R', 'E', 'J', '\0'); // Reject
+const QuicTag kCETV = TAG('C', 'E', 'T', 'V'); // Client encrypted tag-value
+ // pairs
+
+// Key exchange methods
+const QuicTag kP256 = TAG('P', '2', '5', '6'); // ECDH, Curve P-256
+const QuicTag kC255 = TAG('C', '2', '5', '5'); // ECDH, Curve25519
+
+// AEAD algorithms
+const QuicTag kNULL = TAG('N', 'U', 'L', 'L'); // null algorithm
+const QuicTag kAESG = TAG('A', 'E', 'S', 'G'); // AES128 + GCM-12
+
+// Congestion control feedback types
+const QuicTag kQBIC = TAG('Q', 'B', 'I', 'C'); // TCP cubic
+const QuicTag kINAR = TAG('I', 'N', 'A', 'R'); // Inter arrival
+
+// Proof types (i.e. certificate types)
+// NOTE: although it would be silly to do so, specifying both kX509 and kX59R
+// is allowed and is equivalent to specifying only kX509.
+const QuicTag kX509 = TAG('X', '5', '0', '9'); // X.509 certificate, all key
+ // types
+const QuicTag kX59R = TAG('X', '5', '9', 'R'); // X.509 certificate, RSA keys
+ // only
+const QuicTag kCHID = TAG('C', 'H', 'I', 'D'); // Channel ID.
+
+// Client hello tags
+const QuicTag kVERS = TAG('V', 'E', 'R', 'S'); // Version
+const QuicTag kNONC = TAG('N', 'O', 'N', 'C'); // The client's nonce
+const QuicTag kSSID = TAG('S', 'S', 'I', 'D'); // Session ID
+const QuicTag kKEXS = TAG('K', 'E', 'X', 'S'); // Key exchange methods
+const QuicTag kAEAD = TAG('A', 'E', 'A', 'D'); // Authenticated
+ // encryption algorithms
+const QuicTag kCGST = TAG('C', 'G', 'S', 'T'); // Congestion control
+ // feedback types
+const QuicTag kICSL = TAG('I', 'C', 'S', 'L'); // Idle connection state
+ // lifetime
+const QuicTag kKATO = TAG('K', 'A', 'T', 'O'); // Keepalive timeout
+const QuicTag kMSPC = TAG('M', 'S', 'P', 'C'); // Max streams per connection.
+const QuicTag kSNI = TAG('S', 'N', 'I', '\0'); // Server name
+ // indication
+const QuicTag kPUBS = TAG('P', 'U', 'B', 'S'); // Public key values
+const QuicTag kSCID = TAG('S', 'C', 'I', 'D'); // Server config id
+const QuicTag kORBT = TAG('O', 'B', 'I', 'T'); // Server orbit.
+const QuicTag kPDMD = TAG('P', 'D', 'M', 'D'); // Proof demand.
+const QuicTag kPROF = TAG('P', 'R', 'O', 'F'); // Proof (signature).
+const QuicTag kCCS = TAG('C', 'C', 'S', 0); // Common certificate set
+const QuicTag kCCRT = TAG('C', 'C', 'R', 'T'); // Cached certificate
+const QuicTag kEXPY = TAG('E', 'X', 'P', 'Y'); // Expiry
+
+// CETV tags
+const QuicTag kCIDK = TAG('C', 'I', 'D', 'K'); // ChannelID key
+const QuicTag kCIDS = TAG('C', 'I', 'D', 'S'); // ChannelID signature
+
+// Universal tags
+const QuicTag kPAD = TAG('P', 'A', 'D', '\0'); // Padding
+
+// These tags have a special form so that they appear either at the beginning
+// or the end of a handshake message. Since handshake messages are sorted by
+// tag value, the tags with 0 at the end will sort first and those with 255 at
+// the end will sort last.
+//
+// The certificate chain should have a tag that will cause it to be sorted at
+// the end of any handshake messages because it's likely to be large and the
+// client might be able to get everything that it needs from the small values at
+// the beginning.
+//
+// Likewise tags with random values should be towards the beginning of the
+// message because the server mightn't hold state for a rejected client hello
+// and therefore the client may have issues reassembling the rejection message
+// in the event that it sent two client hellos.
+const QuicTag kServerNonceTag =
+ TAG('S', 'N', 'O', 0); // The server's nonce
+const QuicTag kSourceAddressTokenTag =
+ TAG('S', 'T', 'K', 0); // Source-address token
+const QuicTag kCertificateTag =
+ TAG('C', 'R', 'T', 255); // Certificate chain
+
+#undef TAG
+
+const size_t kMaxEntries = 128; // Max number of entries in a message.
+
+const size_t kNonceSize = 32; // Size in bytes of the connection nonce.
+
+const size_t kOrbitSize = 8; // Number of bytes in an orbit value.
+
+// kProofSignatureLabel is prepended to server configs before signing to avoid
+// any cross-protocol attacks on the signature.
+const char kProofSignatureLabel[] = "QUIC server config signature";
+
+// kClientHelloMinimumSize is the minimum size of a client hello. Client hellos
+// will have PAD tags added in order to ensure this minimum is met and client
+// hellos smaller than this will be an error. This minimum size reduces the
+// amplification factor of any mirror DoS attack.
+const size_t kClientHelloMinimumSize = 512;
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_PROTOCOL_H_
diff --git a/chromium/net/quic/crypto/crypto_secret_boxer.cc b/chromium/net/quic/crypto/crypto_secret_boxer.cc
new file mode 100644
index 00000000000..73562c638bc
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_secret_boxer.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_secret_boxer.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "net/quic/crypto/quic_random.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// Defined kKeySize for GetKeySize() and SetKey().
+static const size_t kKeySize = 16;
+
+// kBoxNonceSize contains the number of bytes of nonce that we use in each box.
+static const size_t kBoxNonceSize = 16;
+
+// static
+size_t CryptoSecretBoxer::GetKeySize() { return kKeySize; }
+
+void CryptoSecretBoxer::SetKey(StringPiece key) {
+ DCHECK_EQ(static_cast<size_t>(kKeySize), key.size());
+ key_ = key.as_string();
+}
+
+// TODO(rtenneti): Delete sha256 based code. Use Aes128Gcm12Encrypter to Box the
+// plaintext. This is temporary solution for tests to pass.
+string CryptoSecretBoxer::Box(QuicRandom* rand, StringPiece plaintext) const {
+ string ret;
+ const size_t len = kBoxNonceSize + plaintext.size() + crypto::kSHA256Length;
+ ret.resize(len);
+ char* data = &ret[0];
+
+ // Generate nonce.
+ rand->RandBytes(data, kBoxNonceSize);
+ memcpy(data + kBoxNonceSize, plaintext.data(), plaintext.size());
+
+ // Compute sha256 for nonce + plaintext.
+ scoped_ptr<crypto::SecureHash> sha256(crypto::SecureHash::Create(
+ crypto::SecureHash::SHA256));
+ sha256->Update(data, kBoxNonceSize + plaintext.size());
+ sha256->Finish(data + kBoxNonceSize + plaintext.size(),
+ crypto::kSHA256Length);
+
+ return ret;
+}
+
+// TODO(rtenneti): Delete sha256 based code. Use Aes128Gcm12Decrypter to Unbox
+// the plaintext. This is temporary solution for tests to pass.
+bool CryptoSecretBoxer::Unbox(StringPiece ciphertext,
+ string* out_storage,
+ StringPiece* out) const {
+ if (ciphertext.size() < kBoxNonceSize + crypto::kSHA256Length) {
+ return false;
+ }
+
+ const size_t plaintext_len =
+ ciphertext.size() - kBoxNonceSize - crypto::kSHA256Length;
+ out_storage->resize(plaintext_len);
+ char* data = const_cast<char*>(out_storage->data());
+
+ // Copy plaintext from ciphertext.
+ if (plaintext_len != 0) {
+ memcpy(data, ciphertext.data() + kBoxNonceSize, plaintext_len);
+ }
+
+ // Compute sha256 for nonce + plaintext.
+ scoped_ptr<crypto::SecureHash> sha256(crypto::SecureHash::Create(
+ crypto::SecureHash::SHA256));
+ sha256->Update(ciphertext.data(), ciphertext.size() - crypto::kSHA256Length);
+ char sha256_bytes[crypto::kSHA256Length];
+ sha256->Finish(sha256_bytes, sizeof(sha256_bytes));
+
+ // Verify sha256.
+ if (0 != memcmp(ciphertext.data() + ciphertext.size() - crypto::kSHA256Length,
+ sha256_bytes, crypto::kSHA256Length)) {
+ return false;
+ }
+
+ out->set(data, plaintext_len);
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_secret_boxer.h b/chromium/net/quic/crypto/crypto_secret_boxer.h
new file mode 100644
index 00000000000..ba9baf2bb2a
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_secret_boxer.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
+#define NET_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class QuicRandom;
+
+// CryptoSecretBoxer encrypts small chunks of plaintext (called 'boxing') and
+// then, later, can authenticate+decrypt the resulting boxes. This object is
+// thread-safe.
+class NET_EXPORT_PRIVATE CryptoSecretBoxer {
+ public:
+ // GetKeySize returns the number of bytes in a key.
+ static size_t GetKeySize();
+
+ // SetKey sets the key for this object. This must be done before |Box| or
+ // |Unbox| are called. |key| must be |GetKeySize()| bytes long.
+ void SetKey(base::StringPiece key);
+
+ // Box encrypts |plaintext| using a random nonce generated from |rand| and
+ // returns the resulting ciphertext. Since an authenticator and nonce are
+ // included, the result will be slightly larger than |plaintext|.
+ std::string Box(QuicRandom* rand, base::StringPiece plaintext) const;
+
+ // Unbox takes the result of a previous call to |Box| in |ciphertext| and
+ // authenticates+decrypts it. If |ciphertext| is not authentic then it
+ // returns false. Otherwise, |out_storage| is used to store the result and
+ // |out| is set to point into |out_storage| and contains the original
+ // plaintext.
+ bool Unbox(base::StringPiece ciphertext,
+ std::string* out_storage,
+ base::StringPiece* out) const;
+
+ private:
+ std::string key_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_SECRET_BOXER_H_
diff --git a/chromium/net/quic/crypto/crypto_secret_boxer_test.cc b/chromium/net/quic/crypto/crypto_secret_boxer_test.cc
new file mode 100644
index 00000000000..427d052d011
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_secret_boxer_test.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_secret_boxer.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/quic_random.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+TEST(CryptoSecretBoxerTest, BoxAndUnbox) {
+ StringPiece message("hello world");
+ const size_t key_size = CryptoSecretBoxer::GetKeySize();
+ scoped_ptr<uint8[]> key(new uint8[key_size]);
+ memset(key.get(), 0x11, key_size);
+
+ CryptoSecretBoxer boxer;
+ boxer.SetKey(StringPiece(reinterpret_cast<char*>(key.get()), key_size));
+
+ const string box = boxer.Box(QuicRandom::GetInstance(), message);
+
+ string storage;
+ StringPiece result;
+ EXPECT_TRUE(boxer.Unbox(box, &storage, &result));
+ EXPECT_EQ(result, message);
+
+ EXPECT_FALSE(boxer.Unbox(string(1, 'X') + box, &storage, &result));
+ EXPECT_FALSE(boxer.Unbox(box.substr(1, string::npos), &storage, &result));
+ EXPECT_FALSE(boxer.Unbox(string(), &storage, &result));
+ EXPECT_FALSE(boxer.Unbox(string(1, box[0]^0x80) + box.substr(1, string::npos),
+ &storage, &result));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_server_config.cc b/chromium/net/quic/crypto/crypto_server_config.cc
new file mode 100644
index 00000000000..f270ddeb31a
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_server_config.cc
@@ -0,0 +1,1070 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_server_config.h"
+
+#include <stdlib.h>
+#include <algorithm>
+
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "crypto/hkdf.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/cert_compressor.h"
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_server_config_protobuf.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/curve25519_key_exchange.h"
+#include "net/quic/crypto/ephemeral_key_source.h"
+#include "net/quic/crypto/key_exchange.h"
+#include "net/quic/crypto/p256_key_exchange.h"
+#include "net/quic/crypto/proof_source.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/crypto/source_address_token.h"
+#include "net/quic/crypto/strike_register.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using crypto::SecureHash;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace net {
+
+// static
+const char QuicCryptoServerConfig::TESTING[] = "secret string for testing";
+
+QuicCryptoServerConfig::ConfigOptions::ConfigOptions()
+ : expiry_time(QuicWallTime::Zero()),
+ channel_id_enabled(false) { }
+
+QuicCryptoServerConfig::QuicCryptoServerConfig(
+ StringPiece source_address_token_secret,
+ QuicRandom* rand)
+ : replay_protection_(true),
+ configs_lock_(),
+ primary_config_(NULL),
+ next_config_promotion_time_(QuicWallTime::Zero()),
+ strike_register_lock_(),
+ server_nonce_strike_register_lock_(),
+ strike_register_max_entries_(1 << 10),
+ strike_register_window_secs_(600),
+ source_address_token_future_secs_(3600),
+ source_address_token_lifetime_secs_(86400),
+ server_nonce_strike_register_max_entries_(1 << 10),
+ server_nonce_strike_register_window_secs_(120) {
+ crypto::HKDF hkdf(source_address_token_secret, StringPiece() /* no salt */,
+ "QUIC source address token key",
+ CryptoSecretBoxer::GetKeySize(),
+ 0 /* no fixed IV needed */);
+ source_address_token_boxer_.SetKey(hkdf.server_write_key());
+
+ // Generate a random key and orbit for server nonces.
+ rand->RandBytes(server_nonce_orbit_, sizeof(server_nonce_orbit_));
+ const size_t key_size = server_nonce_boxer_.GetKeySize();
+ scoped_ptr<uint8[]> key_bytes(new uint8[key_size]);
+ rand->RandBytes(key_bytes.get(), key_size);
+
+ server_nonce_boxer_.SetKey(
+ StringPiece(reinterpret_cast<char*>(key_bytes.get()), key_size));
+}
+
+QuicCryptoServerConfig::~QuicCryptoServerConfig() {
+ primary_config_ = NULL;
+}
+
+// static
+QuicServerConfigProtobuf* QuicCryptoServerConfig::DefaultConfig(
+ QuicRandom* rand,
+ const QuicClock* clock,
+ const ConfigOptions& options) {
+ CryptoHandshakeMessage msg;
+
+ const string curve25519_private_key =
+ Curve25519KeyExchange::NewPrivateKey(rand);
+ scoped_ptr<Curve25519KeyExchange> curve25519(
+ Curve25519KeyExchange::New(curve25519_private_key));
+ StringPiece curve25519_public_value = curve25519->public_value();
+
+ const string p256_private_key = P256KeyExchange::NewPrivateKey();
+ scoped_ptr<P256KeyExchange> p256(P256KeyExchange::New(p256_private_key));
+ StringPiece p256_public_value = p256->public_value();
+
+ string encoded_public_values;
+ // First three bytes encode the length of the public value.
+ encoded_public_values.push_back(curve25519_public_value.size());
+ encoded_public_values.push_back(curve25519_public_value.size() >> 8);
+ encoded_public_values.push_back(curve25519_public_value.size() >> 16);
+ encoded_public_values.append(curve25519_public_value.data(),
+ curve25519_public_value.size());
+ encoded_public_values.push_back(p256_public_value.size());
+ encoded_public_values.push_back(p256_public_value.size() >> 8);
+ encoded_public_values.push_back(p256_public_value.size() >> 16);
+ encoded_public_values.append(p256_public_value.data(),
+ p256_public_value.size());
+
+ msg.set_tag(kSCFG);
+ msg.SetTaglist(kKEXS, kC255, kP256, 0);
+ msg.SetTaglist(kAEAD, kAESG, 0);
+ msg.SetValue(kVERS, static_cast<uint16>(0));
+ msg.SetStringPiece(kPUBS, encoded_public_values);
+
+ if (options.expiry_time.IsZero()) {
+ const QuicWallTime now = clock->WallNow();
+ const QuicWallTime expiry = now.Add(QuicTime::Delta::FromSeconds(
+ 60 * 60 * 24 * 180 /* 180 days, ~six months */));
+ const uint64 expiry_seconds = expiry.ToUNIXSeconds();
+ msg.SetValue(kEXPY, expiry_seconds);
+ } else {
+ msg.SetValue(kEXPY, options.expiry_time.ToUNIXSeconds());
+ }
+
+ if (options.id.empty()) {
+ char scid_bytes[16];
+ rand->RandBytes(scid_bytes, sizeof(scid_bytes));
+ msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes)));
+ } else {
+ msg.SetStringPiece(kSCID, options.id);
+ }
+
+ char orbit_bytes[kOrbitSize];
+ if (options.orbit.size() == sizeof(orbit_bytes)) {
+ memcpy(orbit_bytes, options.orbit.data(), sizeof(orbit_bytes));
+ } else {
+ DCHECK(options.orbit.empty());
+ rand->RandBytes(orbit_bytes, sizeof(orbit_bytes));
+ }
+ msg.SetStringPiece(kORBT, StringPiece(orbit_bytes, sizeof(orbit_bytes)));
+
+ if (options.channel_id_enabled) {
+ msg.SetTaglist(kPDMD, kCHID, 0);
+ }
+
+ scoped_ptr<QuicData> serialized(CryptoFramer::ConstructHandshakeMessage(msg));
+
+ scoped_ptr<QuicServerConfigProtobuf> config(new QuicServerConfigProtobuf);
+ config->set_config(serialized->AsStringPiece());
+ QuicServerConfigProtobuf::PrivateKey* curve25519_key = config->add_key();
+ curve25519_key->set_tag(kC255);
+ curve25519_key->set_private_key(curve25519_private_key);
+ QuicServerConfigProtobuf::PrivateKey* p256_key = config->add_key();
+ p256_key->set_tag(kP256);
+ p256_key->set_private_key(p256_private_key);
+
+ return config.release();
+}
+
+CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig(
+ QuicServerConfigProtobuf* protobuf,
+ const QuicWallTime now) {
+ scoped_ptr<CryptoHandshakeMessage> msg(
+ CryptoFramer::ParseMessage(protobuf->config()));
+
+ if (!msg.get()) {
+ LOG(WARNING) << "Failed to parse server config message";
+ return NULL;
+ }
+
+ scoped_refptr<Config> config(ParseConfigProtobuf(protobuf));
+ if (!config.get()) {
+ LOG(WARNING) << "Failed to parse server config message";
+ return NULL;
+ }
+
+ {
+ base::AutoLock locked(configs_lock_);
+ if (configs_.find(config->id) != configs_.end()) {
+ LOG(WARNING) << "Failed to add config because another with the same "
+ "server config id already exists: "
+ << base::HexEncode(config->id.data(), config->id.size());
+ return NULL;
+ }
+
+ configs_[config->id] = config;
+ SelectNewPrimaryConfig(now);
+ DCHECK(primary_config_.get());
+ }
+
+ return msg.release();
+}
+
+CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig(
+ QuicRandom* rand,
+ const QuicClock* clock,
+ const ConfigOptions& options) {
+ scoped_ptr<QuicServerConfigProtobuf> config(
+ DefaultConfig(rand, clock, options));
+ return AddConfig(config.get(), clock->WallNow());
+}
+
+bool QuicCryptoServerConfig::SetConfigs(
+ const vector<QuicServerConfigProtobuf*>& protobufs,
+ const QuicWallTime now) {
+ vector<scoped_refptr<Config> > new_configs;
+ bool ok = true;
+
+ for (vector<QuicServerConfigProtobuf*>::const_iterator i = protobufs.begin();
+ i != protobufs.end(); ++i) {
+ scoped_refptr<Config> config(ParseConfigProtobuf(*i));
+ if (!config.get()) {
+ ok = false;
+ break;
+ }
+ new_configs.push_back(config);
+ }
+
+ if (!ok) {
+ LOG(WARNING) << "Rejecting QUIC configs because of above errors";
+ } else {
+ base::AutoLock locked(configs_lock_);
+ typedef ConfigMap::iterator ConfigMapIterator;
+ vector<ConfigMapIterator> to_delete;
+
+ DCHECK_EQ(protobufs.size(), new_configs.size());
+
+ // First, look for any configs that have been removed.
+ for (ConfigMapIterator i = configs_.begin();
+ i != configs_.end(); ++i) {
+ const scoped_refptr<Config> old_config = i->second;
+ bool found = false;
+
+ for (vector<scoped_refptr<Config> >::const_iterator j =
+ new_configs.begin();
+ j != new_configs.end(); ++j) {
+ if ((*j)->id == old_config->id) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // We cannot remove the primary config. This has probably happened
+ // because our source of config information failed for a time and we're
+ // suddenly seeing a jump in time. No matter - we'll configure a new
+ // primary config and then we'll be able to delete it next time.
+ if (!old_config->is_primary) {
+ to_delete.push_back(i);
+ }
+ }
+ }
+
+ for (vector<ConfigMapIterator>::const_iterator i = to_delete.begin();
+ i != to_delete.end(); ++i) {
+ configs_.erase(*i);
+ }
+
+ // Find any configs that need to be added.
+ for (vector<scoped_refptr<Config> >::const_iterator i = new_configs.begin();
+ i != new_configs.end(); ++i) {
+ const scoped_refptr<Config> new_config = *i;
+ if (configs_.find(new_config->id) != configs_.end()) {
+ continue;
+ }
+
+ configs_[new_config->id] = new_config;
+ }
+
+ SelectNewPrimaryConfig(now);
+ }
+
+ return ok;
+}
+
+// ClientHelloInfo contains information about a client hello message that is
+// only kept for as long as it's being processed.
+struct ClientHelloInfo {
+ ClientHelloInfo(const IPEndPoint& in_client_ip, QuicWallTime in_now)
+ : client_ip(in_client_ip),
+ now(in_now),
+ valid_source_address_token(false),
+ client_nonce_well_formed(false),
+ unique(false) {}
+
+ // Inputs to EvaluateClientHello.
+ const IPEndPoint client_ip;
+ const QuicWallTime now;
+
+ // Outputs from EvaluateClientHello.
+ bool valid_source_address_token;
+ bool client_nonce_well_formed;
+ bool unique;
+ StringPiece sni;
+ StringPiece client_nonce;
+ StringPiece server_nonce;
+};
+
+QuicErrorCode QuicCryptoServerConfig::ProcessClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ QuicVersion version,
+ QuicGuid guid,
+ const IPEndPoint& client_ip,
+ const QuicClock* clock,
+ QuicRandom* rand,
+ QuicCryptoNegotiatedParameters *params,
+ CryptoHandshakeMessage* out,
+ string* error_details) const {
+ DCHECK(error_details);
+
+ StringPiece requested_scid;
+ client_hello.GetStringPiece(kSCID, &requested_scid);
+ const QuicWallTime now(clock->WallNow());
+
+ scoped_refptr<Config> requested_config;
+ scoped_refptr<Config> primary_config;
+ {
+ base::AutoLock locked(configs_lock_);
+
+ if (!primary_config_.get()) {
+ *error_details = "No configurations loaded";
+ return QUIC_CRYPTO_INTERNAL_ERROR;
+ }
+
+ if (!next_config_promotion_time_.IsZero() &&
+ next_config_promotion_time_.IsAfter(now)) {
+ SelectNewPrimaryConfig(now);
+ }
+
+ primary_config = primary_config_;
+
+ if (!requested_scid.empty()) {
+ ConfigMap::const_iterator it = configs_.find(requested_scid.as_string());
+ if (it != configs_.end()) {
+ // We'll use the config that the client requested in order to do
+ // key-agreement. Otherwise we'll give it a copy of |primary_config_|
+ // to use.
+ requested_config = it->second;
+ }
+ }
+ }
+
+ ClientHelloInfo info(client_ip, now);
+ QuicErrorCode error = EvaluateClientHello(
+ client_hello, primary_config->orbit, &info, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ out->Clear();
+
+ if (!info.valid_source_address_token ||
+ !info.client_nonce_well_formed ||
+ !info.unique ||
+ !requested_config.get()) {
+ BuildRejection(version, primary_config.get(), client_hello, info, rand,
+ out);
+ return QUIC_NO_ERROR;
+ }
+
+ const QuicTag* their_aeads;
+ const QuicTag* their_key_exchanges;
+ size_t num_their_aeads, num_their_key_exchanges;
+ if (client_hello.GetTaglist(kAEAD, &their_aeads,
+ &num_their_aeads) != QUIC_NO_ERROR ||
+ client_hello.GetTaglist(kKEXS, &their_key_exchanges,
+ &num_their_key_exchanges) != QUIC_NO_ERROR ||
+ num_their_aeads != 1 ||
+ num_their_key_exchanges != 1) {
+ *error_details = "Missing or invalid AEAD or KEXS";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ size_t key_exchange_index;
+ if (!QuicUtils::FindMutualTag(requested_config->aead, their_aeads,
+ num_their_aeads, QuicUtils::LOCAL_PRIORITY,
+ &params->aead, NULL) ||
+ !QuicUtils::FindMutualTag(
+ requested_config->kexs, their_key_exchanges, num_their_key_exchanges,
+ QuicUtils::LOCAL_PRIORITY, &params->key_exchange,
+ &key_exchange_index)) {
+ *error_details = "Unsupported AEAD or KEXS";
+ return QUIC_CRYPTO_NO_SUPPORT;
+ }
+
+ StringPiece public_value;
+ if (!client_hello.GetStringPiece(kPUBS, &public_value)) {
+ *error_details = "Missing public value";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ const KeyExchange* key_exchange =
+ requested_config->key_exchanges[key_exchange_index];
+ if (!key_exchange->CalculateSharedKey(public_value,
+ &params->initial_premaster_secret)) {
+ *error_details = "Invalid public value";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ if (!info.sni.empty()) {
+ scoped_ptr<char[]> sni_tmp(new char[info.sni.length() + 1]);
+ memcpy(sni_tmp.get(), info.sni.data(), info.sni.length());
+ sni_tmp[info.sni.length()] = 0;
+ params->sni = CryptoUtils::NormalizeHostname(sni_tmp.get());
+ }
+
+ string hkdf_suffix;
+ const QuicData& client_hello_serialized = client_hello.GetSerialized();
+ hkdf_suffix.reserve(sizeof(guid) + client_hello_serialized.length() +
+ requested_config->serialized.size());
+ hkdf_suffix.append(reinterpret_cast<char*>(&guid), sizeof(guid));
+ hkdf_suffix.append(client_hello_serialized.data(),
+ client_hello_serialized.length());
+ hkdf_suffix.append(requested_config->serialized);
+
+ StringPiece cetv_ciphertext;
+ if (requested_config->channel_id_enabled &&
+ client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) {
+ CryptoHandshakeMessage client_hello_copy(client_hello);
+ client_hello_copy.Erase(kCETV);
+ client_hello_copy.Erase(kPAD);
+
+ const QuicData& client_hello_serialized = client_hello_copy.GetSerialized();
+ string hkdf_input;
+ hkdf_input.append(QuicCryptoConfig::kCETVLabel,
+ strlen(QuicCryptoConfig::kCETVLabel) + 1);
+ hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid));
+ hkdf_input.append(client_hello_serialized.data(),
+ client_hello_serialized.length());
+ hkdf_input.append(requested_config->serialized);
+
+ CrypterPair crypters;
+ CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead,
+ info.client_nonce, info.server_nonce, hkdf_input,
+ CryptoUtils::SERVER, &crypters);
+
+ scoped_ptr<QuicData> cetv_plaintext(crypters.decrypter->DecryptPacket(
+ 0 /* sequence number */, StringPiece() /* associated data */,
+ cetv_ciphertext));
+ if (!cetv_plaintext.get()) {
+ *error_details = "CETV decryption failure";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ scoped_ptr<CryptoHandshakeMessage> cetv(CryptoFramer::ParseMessage(
+ cetv_plaintext->AsStringPiece()));
+ if (!cetv.get()) {
+ *error_details = "CETV parse error";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ StringPiece key, signature;
+ if (cetv->GetStringPiece(kCIDK, &key) &&
+ cetv->GetStringPiece(kCIDS, &signature)) {
+ if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) {
+ *error_details = "ChannelID signature failure";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ params->channel_id = key.as_string();
+ }
+ }
+
+ string hkdf_input;
+ size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1;
+ hkdf_input.reserve(label_len + hkdf_suffix.size());
+ hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len);
+ hkdf_input.append(hkdf_suffix);
+
+ CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead,
+ info.client_nonce, info.server_nonce, hkdf_input,
+ CryptoUtils::SERVER, &params->initial_crypters);
+
+ string forward_secure_public_value;
+ if (ephemeral_key_source_.get()) {
+ params->forward_secure_premaster_secret =
+ ephemeral_key_source_->CalculateForwardSecureKey(
+ key_exchange, rand, clock->ApproximateNow(), public_value,
+ &forward_secure_public_value);
+ } else {
+ scoped_ptr<KeyExchange> forward_secure_key_exchange(
+ key_exchange->NewKeyPair(rand));
+ forward_secure_public_value =
+ forward_secure_key_exchange->public_value().as_string();
+ if (!forward_secure_key_exchange->CalculateSharedKey(
+ public_value, &params->forward_secure_premaster_secret)) {
+ *error_details = "Invalid public value";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+ }
+
+ string forward_secure_hkdf_input;
+ label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1;
+ forward_secure_hkdf_input.reserve(label_len + hkdf_suffix.size());
+ forward_secure_hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel,
+ label_len);
+ forward_secure_hkdf_input.append(hkdf_suffix);
+
+ CryptoUtils::DeriveKeys(params->forward_secure_premaster_secret, params->aead,
+ info.client_nonce, info.server_nonce,
+ forward_secure_hkdf_input, CryptoUtils::SERVER,
+ &params->forward_secure_crypters);
+
+ out->set_tag(kSHLO);
+ out->SetStringPiece(kSourceAddressTokenTag,
+ NewSourceAddressToken(client_ip, rand, info.now));
+ out->SetStringPiece(kPUBS, forward_secure_public_value);
+ return QUIC_NO_ERROR;
+}
+
+// ConfigPrimaryTimeLessThan is a comparator that implements "less than" for
+// Config's based on their primary_time.
+// static
+bool QuicCryptoServerConfig::ConfigPrimaryTimeLessThan(
+ const scoped_refptr<Config>& a,
+ const scoped_refptr<Config>& b) {
+ return a->primary_time.IsBefore(b->primary_time);
+}
+
+void QuicCryptoServerConfig::SelectNewPrimaryConfig(
+ const QuicWallTime now) const {
+ vector<scoped_refptr<Config> > configs;
+ configs.reserve(configs_.size());
+ scoped_refptr<Config> first_config = NULL;
+
+ for (ConfigMap::const_iterator it = configs_.begin();
+ it != configs_.end(); ++it) {
+ const scoped_refptr<Config> config(it->second);
+ if (!first_config.get()) {
+ first_config = config;
+ }
+ if (config->primary_time.IsZero()) {
+ continue;
+ }
+ configs.push_back(it->second);
+ }
+
+ if (configs.size() == 0) {
+ // Tests don't set |primary_time_|. For that case we promote the first
+ // Config and leave it as primary forever.
+ if (!primary_config_.get() && first_config.get()) {
+ primary_config_ = first_config;
+ primary_config_->is_primary = true;
+ }
+ return;
+ }
+
+ std::sort(configs.begin(), configs.end(), ConfigPrimaryTimeLessThan);
+
+ for (size_t i = 0; i < configs.size(); ++i) {
+ const scoped_refptr<Config> config(configs[i]);
+
+ if (!config->primary_time.IsAfter(now)) {
+ continue;
+ }
+
+ // This is the first config with a primary_time in the future. Thus the
+ // previous Config should be the primary and this one should determine the
+ // next_config_promotion_time_.
+ scoped_refptr<Config> new_primary;
+ if (i == 0) {
+ // There was no previous Config, so this will have to be primary.
+ new_primary = config;
+
+ // We need the primary_time of the next config.
+ if (configs.size() > 1) {
+ next_config_promotion_time_ = configs[1]->primary_time;
+ } else {
+ next_config_promotion_time_ = QuicWallTime::Zero();
+ }
+ } else {
+ new_primary = configs[i - 1];
+ next_config_promotion_time_ = config->primary_time;
+ }
+
+ if (primary_config_.get()) {
+ primary_config_->is_primary = false;
+ }
+ primary_config_ = new_primary;
+ new_primary->is_primary = true;
+
+ return;
+ }
+
+ // All config's primary times are in the past. We should make the most recent
+ // primary.
+ scoped_refptr<Config> new_primary = configs[configs.size() - 1];
+ if (primary_config_.get()) {
+ primary_config_->is_primary = false;
+ }
+ primary_config_ = new_primary;
+ new_primary->is_primary = true;
+ next_config_promotion_time_ = QuicWallTime::Zero();
+}
+
+QuicErrorCode QuicCryptoServerConfig::EvaluateClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ const uint8* orbit,
+ ClientHelloInfo* info,
+ string* error_details) const {
+ if (client_hello.size() < kClientHelloMinimumSize) {
+ *error_details = "Client hello too small";
+ return QUIC_CRYPTO_INVALID_VALUE_LENGTH;
+ }
+
+ StringPiece srct;
+ if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct) &&
+ ValidateSourceAddressToken(srct, info->client_ip, info->now)) {
+ info->valid_source_address_token = true;
+ }
+
+ if (client_hello.GetStringPiece(kSNI, &info->sni) &&
+ !CryptoUtils::IsValidSNI(info->sni)) {
+ *error_details = "Invalid SNI name";
+ return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER;
+ }
+
+ // The client nonce is used first to try and establish uniqueness.
+ bool unique_by_strike_register = false;
+
+ if (client_hello.GetStringPiece(kNONC, &info->client_nonce) &&
+ info->client_nonce.size() == kNonceSize) {
+ info->client_nonce_well_formed = true;
+ if (replay_protection_) {
+ base::AutoLock auto_lock(strike_register_lock_);
+
+ if (strike_register_.get() == NULL) {
+ strike_register_.reset(new StrikeRegister(
+ strike_register_max_entries_,
+ static_cast<uint32>(info->now.ToUNIXSeconds()),
+ strike_register_window_secs_,
+ orbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+ }
+
+ unique_by_strike_register = strike_register_->Insert(
+ reinterpret_cast<const uint8*>(info->client_nonce.data()),
+ static_cast<uint32>(info->now.ToUNIXSeconds()));
+ }
+ }
+
+ client_hello.GetStringPiece(kServerNonceTag, &info->server_nonce);
+
+ // If the client nonce didn't establish uniqueness then an echoed server
+ // nonce may.
+ bool unique_by_server_nonce = false;
+ if (replay_protection_ &&
+ !unique_by_strike_register &&
+ !info->server_nonce.empty()) {
+ unique_by_server_nonce = ValidateServerNonce(info->server_nonce, info->now);
+ }
+
+ info->unique = !replay_protection_ ||
+ unique_by_strike_register ||
+ unique_by_server_nonce;
+
+ return QUIC_NO_ERROR;
+}
+
+void QuicCryptoServerConfig::BuildRejection(
+ QuicVersion version,
+ const scoped_refptr<Config>& config,
+ const CryptoHandshakeMessage& client_hello,
+ const ClientHelloInfo& info,
+ QuicRandom* rand,
+ CryptoHandshakeMessage* out) const {
+ out->set_tag(kREJ);
+ out->SetStringPiece(kSCFG, config->serialized);
+ out->SetStringPiece(kSourceAddressTokenTag,
+ NewSourceAddressToken(info.client_ip, rand, info.now));
+ if (replay_protection_) {
+ out->SetStringPiece(kServerNonceTag, NewServerNonce(rand, info.now));
+ }
+
+ // The client may have requested a certificate chain.
+ const QuicTag* their_proof_demands;
+ size_t num_their_proof_demands;
+
+ if (proof_source_.get() == NULL ||
+ client_hello.GetTaglist(kPDMD, &their_proof_demands,
+ &num_their_proof_demands) !=
+ QUIC_NO_ERROR) {
+ return;
+ }
+
+ bool x509_supported = false, x509_ecdsa_supported = false;
+ for (size_t i = 0; i < num_their_proof_demands; i++) {
+ switch (their_proof_demands[i]) {
+ case kX509:
+ x509_supported = true;
+ x509_ecdsa_supported = true;
+ break;
+ case kX59R:
+ x509_supported = true;
+ break;
+ }
+ }
+
+ if (!x509_supported) {
+ return;
+ }
+
+ const vector<string>* certs;
+ string signature;
+ if (!proof_source_->GetProof(version, info.sni.as_string(),
+ config->serialized, x509_ecdsa_supported,
+ &certs, &signature)) {
+ return;
+ }
+
+ StringPiece their_common_set_hashes;
+ StringPiece their_cached_cert_hashes;
+ client_hello.GetStringPiece(kCCS, &their_common_set_hashes);
+ client_hello.GetStringPiece(kCCRT, &their_cached_cert_hashes);
+
+ const string compressed = CertCompressor::CompressChain(
+ *certs, their_common_set_hashes, their_cached_cert_hashes,
+ config->common_cert_sets);
+
+ // kREJOverheadBytes is a very rough estimate of how much of a REJ
+ // message is taken up by things other than the certificates.
+ const size_t kREJOverheadBytes = 112;
+ // max_unverified_size is the number of bytes that the certificate chain
+ // and signature can consume before we will demand a valid source-address
+ // token.
+ const size_t max_unverified_size = client_hello.size() - kREJOverheadBytes;
+ COMPILE_ASSERT(kClientHelloMinimumSize >= kREJOverheadBytes,
+ overhead_calculation_may_underflow);
+ if (info.valid_source_address_token ||
+ signature.size() + compressed.size() < max_unverified_size) {
+ out->SetStringPiece(kCertificateTag, compressed);
+ out->SetStringPiece(kPROF, signature);
+ }
+}
+
+scoped_refptr<QuicCryptoServerConfig::Config>
+QuicCryptoServerConfig::ParseConfigProtobuf(
+ QuicServerConfigProtobuf* protobuf) {
+ scoped_ptr<CryptoHandshakeMessage> msg(
+ CryptoFramer::ParseMessage(protobuf->config()));
+
+ if (msg->tag() != kSCFG) {
+ LOG(WARNING) << "Server config message has tag " << msg->tag()
+ << " expected " << kSCFG;
+ return NULL;
+ }
+
+ scoped_refptr<Config> config(new Config);
+ config->serialized = protobuf->config();
+
+ if (protobuf->has_primary_time()) {
+ config->primary_time =
+ QuicWallTime::FromUNIXSeconds(protobuf->primary_time());
+ }
+
+ StringPiece scid;
+ if (!msg->GetStringPiece(kSCID, &scid)) {
+ LOG(WARNING) << "Server config message is missing SCID";
+ return NULL;
+ }
+ config->id = scid.as_string();
+
+ const QuicTag* aead_tags;
+ size_t aead_len;
+ if (msg->GetTaglist(kAEAD, &aead_tags, &aead_len) != QUIC_NO_ERROR) {
+ LOG(WARNING) << "Server config message is missing AEAD";
+ return NULL;
+ }
+ config->aead = vector<QuicTag>(aead_tags, aead_tags + aead_len);
+
+ const QuicTag* kexs_tags;
+ size_t kexs_len;
+ if (msg->GetTaglist(kKEXS, &kexs_tags, &kexs_len) != QUIC_NO_ERROR) {
+ LOG(WARNING) << "Server config message is missing KEXS";
+ return NULL;
+ }
+
+ StringPiece orbit;
+ if (!msg->GetStringPiece(kORBT, &orbit)) {
+ LOG(WARNING) << "Server config message is missing OBIT";
+ return NULL;
+ }
+
+ if (orbit.size() != kOrbitSize) {
+ LOG(WARNING) << "Orbit value in server config is the wrong length."
+ " Got " << orbit.size() << " want " << kOrbitSize;
+ return NULL;
+ }
+ COMPILE_ASSERT(sizeof(config->orbit) == kOrbitSize, orbit_incorrect_size);
+ memcpy(config->orbit, orbit.data(), sizeof(config->orbit));
+
+ {
+ base::AutoLock locked(strike_register_lock_);
+ if (strike_register_.get()) {
+ const uint8* orbit = strike_register_->orbit();
+ if (0 != memcmp(orbit, config->orbit, kOrbitSize)) {
+ LOG(WARNING)
+ << "Server config has different orbit than current config. "
+ "Switching orbits at run-time is not supported.";
+ return NULL;
+ }
+ }
+ }
+
+ if (kexs_len != protobuf->key_size()) {
+ LOG(WARNING) << "Server config has " << kexs_len
+ << " key exchange methods configured, but "
+ << protobuf->key_size() << " private keys";
+ return NULL;
+ }
+
+ const QuicTag* proof_demand_tags;
+ size_t num_proof_demand_tags;
+ if (msg->GetTaglist(kPDMD, &proof_demand_tags, &num_proof_demand_tags) ==
+ QUIC_NO_ERROR) {
+ for (size_t i = 0; i < num_proof_demand_tags; i++) {
+ if (proof_demand_tags[i] == kCHID) {
+ config->channel_id_enabled = true;
+ break;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < kexs_len; i++) {
+ const QuicTag tag = kexs_tags[i];
+ string private_key;
+
+ config->kexs.push_back(tag);
+
+ for (size_t j = 0; j < protobuf->key_size(); j++) {
+ const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i);
+ if (key.tag() == tag) {
+ private_key = key.private_key();
+ break;
+ }
+ }
+
+ if (private_key.empty()) {
+ LOG(WARNING) << "Server config contains key exchange method without "
+ "corresponding private key: " << tag;
+ return NULL;
+ }
+
+ scoped_ptr<KeyExchange> ka;
+ switch (tag) {
+ case kC255:
+ ka.reset(Curve25519KeyExchange::New(private_key));
+ if (!ka.get()) {
+ LOG(WARNING) << "Server config contained an invalid curve25519"
+ " private key.";
+ return NULL;
+ }
+ break;
+ case kP256:
+ ka.reset(P256KeyExchange::New(private_key));
+ if (!ka.get()) {
+ LOG(WARNING) << "Server config contained an invalid P-256"
+ " private key.";
+ return NULL;
+ }
+ break;
+ default:
+ LOG(WARNING) << "Server config message contains unknown key exchange "
+ "method: " << tag;
+ return NULL;
+ }
+
+ for (vector<KeyExchange*>::const_iterator i = config->key_exchanges.begin();
+ i != config->key_exchanges.end(); ++i) {
+ if ((*i)->tag() == tag) {
+ LOG(WARNING) << "Duplicate key exchange in config: " << tag;
+ return NULL;
+ }
+ }
+
+ config->key_exchanges.push_back(ka.release());
+ }
+
+ if (msg->GetUint16(kVERS, &config->version) != QUIC_NO_ERROR) {
+ LOG(WARNING) << "Server config message is missing version";
+ return NULL;
+ }
+
+ if (config->version != QuicCryptoConfig::CONFIG_VERSION) {
+ LOG(WARNING) << "Server config specifies an unsupported version";
+ return NULL;
+ }
+
+ return config;
+}
+
+void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) {
+ proof_source_.reset(proof_source);
+}
+
+void QuicCryptoServerConfig::SetEphemeralKeySource(
+ EphemeralKeySource* ephemeral_key_source) {
+ ephemeral_key_source_.reset(ephemeral_key_source);
+}
+
+void QuicCryptoServerConfig::set_replay_protection(bool on) {
+ replay_protection_ = on;
+}
+
+void QuicCryptoServerConfig::set_strike_register_max_entries(
+ uint32 max_entries) {
+ base::AutoLock locker(strike_register_lock_);
+ DCHECK(!strike_register_.get());
+ strike_register_max_entries_ = max_entries;
+}
+
+void QuicCryptoServerConfig::set_strike_register_window_secs(
+ uint32 window_secs) {
+ base::AutoLock locker(strike_register_lock_);
+ DCHECK(!strike_register_.get());
+ strike_register_window_secs_ = window_secs;
+}
+
+void QuicCryptoServerConfig::set_source_address_token_future_secs(
+ uint32 future_secs) {
+ source_address_token_future_secs_ = future_secs;
+}
+
+void QuicCryptoServerConfig::set_source_address_token_lifetime_secs(
+ uint32 lifetime_secs) {
+ source_address_token_lifetime_secs_ = lifetime_secs;
+}
+
+void QuicCryptoServerConfig::set_server_nonce_strike_register_max_entries(
+ uint32 max_entries) {
+ DCHECK(!server_nonce_strike_register_.get());
+ server_nonce_strike_register_max_entries_ = max_entries;
+}
+
+void QuicCryptoServerConfig::set_server_nonce_strike_register_window_secs(
+ uint32 window_secs) {
+ DCHECK(!server_nonce_strike_register_.get());
+ server_nonce_strike_register_window_secs_ = window_secs;
+}
+
+string QuicCryptoServerConfig::NewSourceAddressToken(
+ const IPEndPoint& ip,
+ QuicRandom* rand,
+ QuicWallTime now) const {
+ SourceAddressToken source_address_token;
+ source_address_token.set_ip(ip.ToString());
+ source_address_token.set_timestamp(now.ToUNIXSeconds());
+
+ return source_address_token_boxer_.Box(
+ rand, source_address_token.SerializeAsString());
+}
+
+bool QuicCryptoServerConfig::ValidateSourceAddressToken(
+ StringPiece token,
+ const IPEndPoint& ip,
+ QuicWallTime now) const {
+ string storage;
+ StringPiece plaintext;
+ if (!source_address_token_boxer_.Unbox(token, &storage, &plaintext)) {
+ return false;
+ }
+
+ SourceAddressToken source_address_token;
+ if (!source_address_token.ParseFromArray(plaintext.data(),
+ plaintext.size())) {
+ return false;
+ }
+
+ if (source_address_token.ip() != ip.ToString()) {
+ // It's for a different IP address.
+ return false;
+ }
+
+ const QuicWallTime timestamp(
+ QuicWallTime::FromUNIXSeconds(source_address_token.timestamp()));
+ const QuicTime::Delta delta(now.AbsoluteDifference(timestamp));
+
+ if (now.IsBefore(timestamp) &&
+ delta.ToSeconds() > source_address_token_future_secs_) {
+ return false;
+ }
+
+ if (now.IsAfter(timestamp) &&
+ delta.ToSeconds() > source_address_token_lifetime_secs_) {
+ return false;
+ }
+
+ return true;
+}
+
+// kServerNoncePlaintextSize is the number of bytes in an unencrypted server
+// nonce.
+static const size_t kServerNoncePlaintextSize =
+ 4 /* timestamp */ + 20 /* random bytes */;
+
+string QuicCryptoServerConfig::NewServerNonce(QuicRandom* rand,
+ QuicWallTime now) const {
+ const uint32 timestamp = static_cast<uint32>(now.ToUNIXSeconds());
+
+ uint8 server_nonce[kServerNoncePlaintextSize];
+ COMPILE_ASSERT(sizeof(server_nonce) > sizeof(timestamp), nonce_too_small);
+ server_nonce[0] = static_cast<uint8>(timestamp >> 24);
+ server_nonce[1] = static_cast<uint8>(timestamp >> 16);
+ server_nonce[2] = static_cast<uint8>(timestamp >> 8);
+ server_nonce[3] = static_cast<uint8>(timestamp);
+ rand->RandBytes(&server_nonce[sizeof(timestamp)],
+ sizeof(server_nonce) - sizeof(timestamp));
+
+ return server_nonce_boxer_.Box(
+ rand,
+ StringPiece(reinterpret_cast<char*>(server_nonce), sizeof(server_nonce)));
+}
+
+bool QuicCryptoServerConfig::ValidateServerNonce(StringPiece token,
+ QuicWallTime now) const {
+ string storage;
+ StringPiece plaintext;
+ if (!server_nonce_boxer_.Unbox(token, &storage, &plaintext)) {
+ return false;
+ }
+
+ // plaintext contains:
+ // uint32 timestamp
+ // uint8[20] random bytes
+
+ if (plaintext.size() != kServerNoncePlaintextSize) {
+ // This should never happen because the value decrypted correctly.
+ LOG(DFATAL) << "Seemingly valid server nonce had incorrect length.";
+ return false;
+ }
+
+ uint8 server_nonce[32];
+ memcpy(server_nonce, plaintext.data(), 4);
+ memcpy(server_nonce + 4, server_nonce_orbit_, sizeof(server_nonce_orbit_));
+ memcpy(server_nonce + 4 + sizeof(server_nonce_orbit_), plaintext.data() + 4,
+ 20);
+ COMPILE_ASSERT(4 + sizeof(server_nonce_orbit_) + 20 == sizeof(server_nonce),
+ bad_nonce_buffer_length);
+
+ bool is_unique;
+ {
+ base::AutoLock auto_lock(server_nonce_strike_register_lock_);
+ if (server_nonce_strike_register_.get() == NULL) {
+ server_nonce_strike_register_.reset(new StrikeRegister(
+ server_nonce_strike_register_max_entries_,
+ static_cast<uint32>(now.ToUNIXSeconds()),
+ server_nonce_strike_register_window_secs_, server_nonce_orbit_,
+ StrikeRegister::NO_STARTUP_PERIOD_NEEDED));
+ }
+ is_unique = server_nonce_strike_register_->Insert(
+ server_nonce, static_cast<uint32>(now.ToUNIXSeconds()));
+ }
+
+ return is_unique;
+}
+
+QuicCryptoServerConfig::Config::Config()
+ : channel_id_enabled(false),
+ is_primary(false),
+ primary_time(QuicWallTime::Zero()) {}
+
+QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); }
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_server_config.h b/chromium/net/quic/crypto/crypto_server_config.h
new file mode 100644
index 00000000000..364c200a149
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_server_config.h
@@ -0,0 +1,364 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_H_
+#define NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_secret_boxer.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class EphemeralKeySource;
+class KeyExchange;
+class ProofSource;
+class QuicClock;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicRandom;
+class QuicServerConfigProtobuf;
+class StrikeRegister;
+
+struct ClientHelloInfo;
+
+namespace test {
+class QuicCryptoServerConfigPeer;
+} // namespace test
+
+// QuicCryptoServerConfig contains the crypto configuration of a QUIC server.
+// Unlike a client, a QUIC server can have multiple configurations active in
+// order to support clients resuming with a previous configuration.
+// TODO(agl): when adding configurations at runtime is added, this object will
+// need to consider locking.
+class NET_EXPORT_PRIVATE QuicCryptoServerConfig {
+ public:
+ // ConfigOptions contains options for generating server configs.
+ struct NET_EXPORT_PRIVATE ConfigOptions {
+ ConfigOptions();
+
+ // expiry_time is the time, in UNIX seconds, when the server config will
+ // expire. If unset, it defaults to the current time plus six months.
+ QuicWallTime expiry_time;
+ // channel_id_enabled controls whether the server config will indicate
+ // support for ChannelIDs.
+ bool channel_id_enabled;
+ // id contains the server config id for the resulting config. If empty, a
+ // random id is generated.
+ std::string id;
+ // orbit contains the kOrbitSize bytes of the orbit value for the server
+ // config. If |orbit| is empty then a random orbit is generated.
+ std::string orbit;
+ };
+
+ // |source_address_token_secret|: secret key material used for encrypting and
+ // decrypting source address tokens. It can be of any length as it is fed
+ // into a KDF before use. In tests, use TESTING.
+ // |server_nonce_entropy|: an entropy source used to generate the orbit and
+ // key for server nonces, which are always local to a given instance of a
+ // server.
+ QuicCryptoServerConfig(base::StringPiece source_address_token_secret,
+ QuicRandom* server_nonce_entropy);
+ ~QuicCryptoServerConfig();
+
+ // TESTING is a magic parameter for passing to the constructor in tests.
+ static const char TESTING[];
+
+ // DefaultConfig generates a QuicServerConfigProtobuf protobuf suitable for
+ // using in tests.
+ static QuicServerConfigProtobuf* DefaultConfig(
+ QuicRandom* rand,
+ const QuicClock* clock,
+ const ConfigOptions& options);
+
+ // AddConfig adds a QuicServerConfigProtobuf to the availible configurations.
+ // It returns the SCFG message from the config if successful. The caller
+ // takes ownership of the CryptoHandshakeMessage. |now| is used in
+ // conjunction with |protobuf->primary_time()| to determine whether the
+ // config should be made primary.
+ CryptoHandshakeMessage* AddConfig(QuicServerConfigProtobuf* protobuf,
+ QuicWallTime now);
+
+ // AddDefaultConfig calls DefaultConfig to create a config and then calls
+ // AddConfig to add it. See the comment for |DefaultConfig| for details of
+ // the arguments.
+ CryptoHandshakeMessage* AddDefaultConfig(
+ QuicRandom* rand,
+ const QuicClock* clock,
+ const ConfigOptions& options);
+
+ // SetConfigs takes a vector of config protobufs and the current time.
+ // Configs are assumed to be uniquely identified by their server config ID.
+ // Previously unknown configs are added and possibly made the primary config
+ // depending on their |primary_time| and the value of |now|. Configs that are
+ // known, but are missing from the protobufs are deleted, unless they are
+ // currently the primary config. SetConfigs returns false if any errors were
+ // encountered and no changes to the QuicCryptoServerConfig will occur.
+ bool SetConfigs(const std::vector<QuicServerConfigProtobuf*>& protobufs,
+ QuicWallTime now);
+
+ // ProcessClientHello processes |client_hello| and decides whether to accept
+ // or reject the connection. If the connection is to be accepted, |out| is
+ // set to the contents of the ServerHello, |out_params| is completed and
+ // QUIC_NO_ERROR is returned. Otherwise |out| is set to be a REJ message and
+ // an error code is returned.
+ //
+ // client_hello: the incoming client hello message.
+ // version: the QUIC version for the connection. TODO(wtc): Remove once
+ // QUIC_VERSION_7 and before are removed.
+ // guid: the GUID for the connection, which is used in key derivation.
+ // client_ip: the IP address of the client, which is used to generate and
+ // validate source-address tokens.
+ // clock: used to validate client nonces and ephemeral keys.
+ // rand: an entropy source
+ // params: the state of the handshake. This may be updated with a server
+ // nonce when we send a rejection. After a successful handshake, this will
+ // contain the state of the connection.
+ // out: the resulting handshake message (either REJ or SHLO)
+ // error_details: used to store a string describing any error.
+ QuicErrorCode ProcessClientHello(const CryptoHandshakeMessage& client_hello,
+ QuicVersion version,
+ QuicGuid guid,
+ const IPEndPoint& client_ip,
+ const QuicClock* clock,
+ QuicRandom* rand,
+ QuicCryptoNegotiatedParameters* params,
+ CryptoHandshakeMessage* out,
+ std::string* error_details) const;
+
+ // SetProofSource installs |proof_source| as the ProofSource for handshakes.
+ // This object takes ownership of |proof_source|.
+ void SetProofSource(ProofSource* proof_source);
+
+ // SetEphemeralKeySource installs an object that can cache ephemeral keys for
+ // a short period of time. This object takes ownership of
+ // |ephemeral_key_source|. If not set then ephemeral keys will be generated
+ // per-connection.
+ void SetEphemeralKeySource(EphemeralKeySource* ephemeral_key_source);
+
+ // set_replay_protection controls whether replay protection is enabled. If
+ // replay protection is disabled then no strike registers are needed and
+ // frontends can share an orbit value without a shared strike-register.
+ // However, an attacker can duplicate a handshake and cause a client's
+ // request to be processed twice.
+ void set_replay_protection(bool on);
+
+ // set_strike_register_max_entries sets the maximum number of entries that
+ // the internal strike register will hold. If the strike register fills up
+ // then the oldest entries (by the client's clock) will be dropped.
+ void set_strike_register_max_entries(uint32 max_entries);
+
+ // set_strike_register_window_secs sets the number of seconds around the
+ // current time that the strike register will attempt to be authoritative
+ // for. Setting a larger value allows for greater client clock-skew, but
+ // means that the quiescent startup period must be longer.
+ void set_strike_register_window_secs(uint32 window_secs);
+
+ // set_source_address_token_future_secs sets the number of seconds into the
+ // future that source-address tokens will be accepted from. Since
+ // source-address tokens are authenticated, this should only happen if
+ // another, valid server has clock-skew.
+ void set_source_address_token_future_secs(uint32 future_secs);
+
+ // set_source_address_token_lifetime_secs sets the number of seconds that a
+ // source-address token will be valid for.
+ void set_source_address_token_lifetime_secs(uint32 lifetime_secs);
+
+ // set_server_nonce_strike_register_max_entries sets the number of entries in
+ // the server-nonce strike-register. This is used to record that server nonce
+ // values have been used. If the number of entries is too small then clients
+ // which are depending on server nonces may fail to handshake because their
+ // nonce has expired in the amount of time it took to go from the server to
+ // the client and back.
+ void set_server_nonce_strike_register_max_entries(uint32 max_entries);
+
+ // set_server_nonce_strike_register_window_secs sets the number of seconds
+ // around the current time that the server-nonce strike-register will accept
+ // nonces from. Setting a larger value allows for clients to delay follow-up
+ // client hellos for longer and still use server nonces as proofs of
+ // uniqueness.
+ void set_server_nonce_strike_register_window_secs(uint32 window_secs);
+
+ private:
+ friend class test::QuicCryptoServerConfigPeer;
+
+ // Config represents a server config: a collection of preferences and
+ // Diffie-Hellman public values.
+ class NET_EXPORT_PRIVATE Config : public QuicCryptoConfig,
+ public base::RefCounted<Config> {
+ public:
+ Config();
+
+ // TODO(rtenneti): since this is a class, we should probably do
+ // getters/setters here.
+ // |serialized| contains the bytes of this server config, suitable for
+ // sending on the wire.
+ std::string serialized;
+ // id contains the SCID of this server config.
+ std::string id;
+ // orbit contains the orbit value for this config: an opaque identifier
+ // used to identify clusters of server frontends.
+ unsigned char orbit[kOrbitSize];
+
+ // key_exchanges contains key exchange objects with the private keys
+ // already loaded. The values correspond, one-to-one, with the tags in
+ // |kexs| from the parent class.
+ std::vector<KeyExchange*> key_exchanges;
+
+ // tag_value_map contains the raw key/value pairs for the config.
+ QuicTagValueMap tag_value_map;
+
+ // channel_id_enabled is true if the config in |serialized| specifies that
+ // ChannelIDs are supported.
+ bool channel_id_enabled;
+
+ // is_primary is true if this config is the one that we'll give out to
+ // clients as the current one.
+ bool is_primary;
+
+ // primary_time contains the timestamp when this config should become the
+ // primary config. A value of QuicWallTime::Zero() means that this config
+ // will not be promoted at a specific time.
+ QuicWallTime primary_time;
+
+ private:
+ friend class base::RefCounted<Config>;
+ virtual ~Config();
+
+ DISALLOW_COPY_AND_ASSIGN(Config);
+ };
+
+ typedef std::map<ServerConfigID, scoped_refptr<Config> > ConfigMap;
+
+ // ConfigPrimaryTimeLessThan returns true if a->primary_time <
+ // b->primary_time.
+ static bool ConfigPrimaryTimeLessThan(const scoped_refptr<Config>& a,
+ const scoped_refptr<Config>& b);
+
+ // SelectNewPrimaryConfig reevaluates the primary config based on the
+ // "primary_time" deadlines contained in each.
+ void SelectNewPrimaryConfig(QuicWallTime now) const;
+
+ // EvaluateClientHello checks |client_hello| for gross errors and determines
+ // whether it can be shown to be fresh (i.e. not a replay). The results are
+ // written to |info|.
+ QuicErrorCode EvaluateClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ const uint8* orbit,
+ ClientHelloInfo* info,
+ std::string* error_details) const;
+
+ // BuildRejection sets |out| to be a REJ message in reply to |client_hello|.
+ void BuildRejection(
+ QuicVersion version,
+ const scoped_refptr<Config>& config,
+ const CryptoHandshakeMessage& client_hello,
+ const ClientHelloInfo& info,
+ QuicRandom* rand,
+ CryptoHandshakeMessage* out) const;
+
+ // ParseConfigProtobuf parses the given config protobuf and returns a
+ // scoped_refptr<Config> if successful. The caller adopts the reference to the
+ // Config. On error, ParseConfigProtobuf returns NULL.
+ scoped_refptr<Config> ParseConfigProtobuf(QuicServerConfigProtobuf* protobuf);
+
+ // NewSourceAddressToken returns a fresh source address token for the given
+ // IP address.
+ std::string NewSourceAddressToken(const IPEndPoint& ip,
+ QuicRandom* rand,
+ QuicWallTime now) const;
+
+ // ValidateSourceAddressToken returns true if the source address token in
+ // |token| is a valid and timely token for the IP address |ip| given that the
+ // current time is |now|.
+ bool ValidateSourceAddressToken(base::StringPiece token,
+ const IPEndPoint& ip,
+ QuicWallTime now) const;
+
+ // NewServerNonce generates and encrypts a random nonce.
+ std::string NewServerNonce(QuicRandom* rand, QuicWallTime now) const;
+
+ // ValidateServerNonce decrypts |token| and verifies that it hasn't been
+ // previously used and is recent enough that it is plausible that it was part
+ // of a very recently provided rejection ("recent" will be on the order of
+ // 10-30 seconds). If so, it records that it has been used and returns true.
+ // Otherwise it returns false.
+ bool ValidateServerNonce(base::StringPiece echoed_server_nonce,
+ QuicWallTime now) const;
+
+ // replay_protection_ controls whether the server enforces that handshakes
+ // aren't replays.
+ bool replay_protection_;
+
+ // configs_ satisfies the following invariants:
+ // 1) configs_.empty() <-> primary_config_ == NULL
+ // 2) primary_config_ != NULL -> primary_config_->is_primary
+ // 3) ∀ c∈configs_, c->is_primary <-> c == primary_config_
+ mutable base::Lock configs_lock_;
+ // configs_ contains all active server configs. It's expected that there are
+ // about half-a-dozen configs active at any one time.
+ ConfigMap configs_;
+ // primary_config_ points to a Config (which is also in |configs_|) which is
+ // the primary config - i.e. the one that we'll give out to new clients.
+ mutable scoped_refptr<Config> primary_config_;
+ // next_config_promotion_time_ contains the nearest, future time when an
+ // active config will be promoted to primary.
+ mutable QuicWallTime next_config_promotion_time_;
+
+ mutable base::Lock strike_register_lock_;
+ // strike_register_ contains a data structure that keeps track of previously
+ // observed client nonces in order to prevent replay attacks.
+ mutable scoped_ptr<StrikeRegister> strike_register_;
+
+ // source_address_token_boxer_ is used to protect the source-address tokens
+ // that are given to clients.
+ CryptoSecretBoxer source_address_token_boxer_;
+
+ // server_nonce_boxer_ is used to encrypt and validate suggested server
+ // nonces.
+ CryptoSecretBoxer server_nonce_boxer_;
+
+ // server_nonce_orbit_ contains the random, per-server orbit values that this
+ // server will use to generate server nonces (the moral equivalent of a SYN
+ // cookies).
+ uint8 server_nonce_orbit_[8];
+
+ mutable base::Lock server_nonce_strike_register_lock_;
+ // server_nonce_strike_register_ contains a data structure that keeps track of
+ // previously observed server nonces from this server, in order to prevent
+ // replay attacks.
+ mutable scoped_ptr<StrikeRegister> server_nonce_strike_register_;
+
+ // proof_source_ contains an object that can provide certificate chains and
+ // signatures.
+ scoped_ptr<ProofSource> proof_source_;
+
+ // ephemeral_key_source_ contains an object that caches ephemeral keys for a
+ // short period of time.
+ scoped_ptr<EphemeralKeySource> ephemeral_key_source_;
+
+ // These fields store configuration values. See the comments for their
+ // respective setter functions.
+ uint32 strike_register_max_entries_;
+ uint32 strike_register_window_secs_;
+ uint32 source_address_token_future_secs_;
+ uint32 source_address_token_lifetime_secs_;
+ uint32 server_nonce_strike_register_max_entries_;
+ uint32 server_nonce_strike_register_window_secs_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_H_
diff --git a/chromium/net/quic/crypto/crypto_server_config_protobuf.cc b/chromium/net/quic/crypto/crypto_server_config_protobuf.cc
new file mode 100644
index 00000000000..a3418e4228a
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_server_config_protobuf.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_server_config_protobuf.h"
+
+#include "base/stl_util.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+QuicServerConfigProtobuf::QuicServerConfigProtobuf()
+ : primary_time_(QuicWallTime::Zero().ToUNIXSeconds()) {
+}
+
+QuicServerConfigProtobuf::~QuicServerConfigProtobuf() {
+ STLDeleteElements(&keys_);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_server_config_protobuf.h b/chromium/net/quic/crypto/crypto_server_config_protobuf.h
new file mode 100644
index 00000000000..6340ae023c4
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_server_config_protobuf.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
+#define NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+// QuicServerConfigProtobuf contains QUIC server config block and the private
+// keys needed to prove ownership.
+// TODO(rch): sync with server more rationally.
+class NET_EXPORT_PRIVATE QuicServerConfigProtobuf {
+ public:
+ // PrivateKey contains a QUIC tag of a key exchange algorithm and a
+ // serialised private key for that algorithm. The format of the serialised
+ // private key is specific to the algorithm in question.
+ class NET_EXPORT_PRIVATE PrivateKey {
+ public:
+ QuicTag tag() const {
+ return tag_;
+ }
+ void set_tag(QuicTag tag) {
+ tag_ = tag;
+ }
+ std::string private_key() const {
+ return private_key_;
+ }
+ void set_private_key(std::string key) {
+ private_key_ = key;
+ }
+
+ private:
+ QuicTag tag_;
+ std::string private_key_;
+ };
+
+ QuicServerConfigProtobuf();
+ ~QuicServerConfigProtobuf();
+
+ size_t key_size() const {
+ return keys_.size();
+ }
+
+ const PrivateKey& key(size_t i) const {
+ DCHECK_GT(keys_.size(), i);
+ return *keys_[i];
+ }
+
+ std::string config() const {
+ return config_;
+ }
+
+ void set_config(base::StringPiece config) {
+ config_ = config.as_string();
+ }
+
+ QuicServerConfigProtobuf::PrivateKey* add_key() {
+ keys_.push_back(new PrivateKey);
+ return keys_.back();
+ }
+
+ void clear_key() {
+ STLDeleteElements(&keys_);
+ }
+
+ bool has_primary_time() const {
+ return primary_time_ > 0;
+ }
+
+ int64 primary_time() const {
+ return primary_time_;
+ }
+
+ void set_primary_time(int64 primary_time) {
+ primary_time_ = primary_time;
+ }
+
+ private:
+ std::vector<PrivateKey*> keys_;
+
+ // config_ is a serialised config in QUIC wire format.
+ std::string config_;
+
+ // primary_time_ contains a UNIX epoch seconds value that indicates when this
+ // config should become primary.
+ int64 primary_time_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_SERVER_CONFIG_PROTOBUF_H_
diff --git a/chromium/net/quic/crypto/crypto_server_test.cc b/chromium/net/quic/crypto/crypto_server_test.cc
new file mode 100644
index 00000000000..6744d12e5e0
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_server_test.cc
@@ -0,0 +1,256 @@
+// Copyright (c) 2013 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 "base/strings/string_number_conversions.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+class CryptoServerTest : public ::testing::Test {
+ public:
+ CryptoServerTest()
+ : rand_(QuicRandom::GetInstance()),
+ config_(QuicCryptoServerConfig::TESTING, rand_),
+ addr_(ParseIPLiteralToNumber("192.0.2.33", &ip_) ?
+ ip_ : IPAddressNumber(), 1) {
+ config_.SetProofSource(CryptoTestUtils::ProofSourceForTesting());
+ }
+
+ virtual void SetUp() {
+ scoped_ptr<CryptoHandshakeMessage> msg(
+ config_.AddDefaultConfig(rand_, &clock_,
+ QuicCryptoServerConfig::ConfigOptions()));
+
+ StringPiece orbit;
+ CHECK(msg->GetStringPiece(kORBT, &orbit));
+ CHECK_EQ(sizeof(orbit_), orbit.size());
+ memcpy(orbit_, orbit.data(), orbit.size());
+
+ char public_value[32];
+ memset(public_value, 42, sizeof(public_value));
+
+ const string nonce_str = GenerateNonce();
+ nonce_hex_ = "#" + base::HexEncode(nonce_str.data(), nonce_str.size());
+ pub_hex_ = "#" + base::HexEncode(public_value, sizeof(public_value));
+
+ CryptoHandshakeMessage client_hello = CryptoTestUtils::Message(
+ "CHLO",
+ "AEAD", "AESG",
+ "KEXS", "C255",
+ "PUBS", pub_hex_.c_str(),
+ "NONC", nonce_hex_.c_str(),
+ "$padding", static_cast<int>(kClientHelloMinimumSize),
+ NULL);
+ ShouldSucceed(client_hello);
+ // The message should be rejected because the source-address token is
+ // missing.
+ ASSERT_EQ(kREJ, out_.tag());
+
+ StringPiece srct;
+ ASSERT_TRUE(out_.GetStringPiece(kSourceAddressTokenTag, &srct));
+ srct_hex_ = "#" + base::HexEncode(srct.data(), srct.size());
+
+ StringPiece scfg;
+ ASSERT_TRUE(out_.GetStringPiece(kSCFG, &scfg));
+ server_config_.reset(CryptoFramer::ParseMessage(scfg));
+
+ StringPiece scid;
+ ASSERT_TRUE(server_config_->GetStringPiece(kSCID, &scid));
+ scid_hex_ = "#" + base::HexEncode(scid.data(), scid.size());
+ }
+
+ void ShouldSucceed(const CryptoHandshakeMessage& message) {
+ string error_details;
+ QuicErrorCode error = config_.ProcessClientHello(
+ message, QuicVersionMax(), 1 /* GUID */, addr_,
+ &clock_, rand_, &params_, &out_, &error_details);
+
+ ASSERT_EQ(error, QUIC_NO_ERROR)
+ << "Message failed with error " << error_details << ": "
+ << message.DebugString();
+ }
+
+ void ShouldFailMentioning(const char* error_substr,
+ const CryptoHandshakeMessage& message) {
+ string error_details;
+ QuicErrorCode error = config_.ProcessClientHello(
+ message, QuicVersionMax(), 1 /* GUID */, addr_,
+ &clock_, rand_, &params_, &out_, &error_details);
+
+ ASSERT_NE(error, QUIC_NO_ERROR)
+ << "Message didn't fail: " << message.DebugString();
+
+ EXPECT_TRUE(error_details.find(error_substr) != string::npos)
+ << error_substr << " not in " << error_details;
+ }
+
+ CryptoHandshakeMessage InchoateClientHello(const char* message_tag, ...) {
+ va_list ap;
+ va_start(ap, message_tag);
+
+ CryptoHandshakeMessage message =
+ CryptoTestUtils::BuildMessage(message_tag, ap);
+ va_end(ap);
+
+ message.SetStringPiece(kPAD, string(kClientHelloMinimumSize, '-'));
+ return message;
+ }
+
+ string GenerateNonce() {
+ string nonce;
+ CryptoUtils::GenerateNonce(
+ clock_.WallNow(), rand_,
+ StringPiece(reinterpret_cast<const char*>(orbit_), sizeof(orbit_)),
+ &nonce);
+ return nonce;
+ }
+
+ protected:
+ QuicRandom* const rand_;
+ MockClock clock_;
+ QuicCryptoServerConfig config_;
+ QuicCryptoNegotiatedParameters params_;
+ CryptoHandshakeMessage out_;
+ IPAddressNumber ip_;
+ IPEndPoint addr_;
+ uint8 orbit_[kOrbitSize];
+
+ // These strings contain hex escaped values from the server suitable for
+ // passing to |InchoateClientHello| when constructing client hello messages.
+ string nonce_hex_, pub_hex_, srct_hex_, scid_hex_;
+ scoped_ptr<CryptoHandshakeMessage> server_config_;
+};
+
+TEST_F(CryptoServerTest, BadSNI) {
+ static const char* kBadSNIs[] = {
+ "",
+ "foo",
+ "#00",
+ "#ff00",
+ "127.0.0.1",
+ "ffee::1",
+ };
+
+ for (size_t i = 0; i < arraysize(kBadSNIs); i++) {
+ ShouldFailMentioning("SNI", InchoateClientHello(
+ "CHLO",
+ "SNI", kBadSNIs[i],
+ NULL));
+ }
+}
+
+// TODO(rtenneti): Enable the DefaultCert test after implementing ProofSource.
+TEST_F(CryptoServerTest, DISABLED_DefaultCert) {
+ // Check that the server replies with a default certificate when no SNI is
+ // specified.
+ ShouldSucceed(InchoateClientHello(
+ "CHLO",
+ "AEAD", "AESG",
+ "KEXS", "C255",
+ "SCID", scid_hex_.c_str(),
+ "#004b5453", srct_hex_.c_str(),
+ "PUBS", pub_hex_.c_str(),
+ "NONC", nonce_hex_.c_str(),
+ "$padding", static_cast<int>(kClientHelloMinimumSize),
+ "PDMD", "X509",
+ NULL));
+
+ StringPiece cert, proof;
+ EXPECT_TRUE(out_.GetStringPiece(kCertificateTag, &cert));
+ EXPECT_TRUE(out_.GetStringPiece(kPROF, &proof));
+ EXPECT_NE(0u, cert.size());
+ EXPECT_NE(0u, proof.size());
+}
+
+TEST_F(CryptoServerTest, TooSmall) {
+ ShouldFailMentioning("too small", CryptoTestUtils::Message(
+ "CHLO",
+ NULL));
+}
+
+TEST_F(CryptoServerTest, BadSourceAddressToken) {
+ // Invalid source-address tokens should be ignored.
+ static const char* kBadSourceAddressTokens[] = {
+ "",
+ "foo",
+ "#0000",
+ "#0000000000000000000000000000000000000000",
+ };
+
+ for (size_t i = 0; i < arraysize(kBadSourceAddressTokens); i++) {
+ ShouldSucceed(InchoateClientHello(
+ "CHLO",
+ "STK", kBadSourceAddressTokens[i],
+ NULL));
+ }
+}
+
+TEST_F(CryptoServerTest, BadClientNonce) {
+ // Invalid nonces should be ignored.
+ static const char* kBadNonces[] = {
+ "",
+ "#0000",
+ "#0000000000000000000000000000000000000000",
+ };
+
+ for (size_t i = 0; i < arraysize(kBadNonces); i++) {
+ ShouldSucceed(InchoateClientHello(
+ "CHLO",
+ "NONC", kBadNonces[i],
+ NULL));
+ }
+}
+
+TEST_F(CryptoServerTest, ReplayProtection) {
+ // This tests that disabling replay protection works.
+ CryptoHandshakeMessage msg = CryptoTestUtils::Message(
+ "CHLO",
+ "AEAD", "AESG",
+ "KEXS", "C255",
+ "SCID", scid_hex_.c_str(),
+ "#004b5453", srct_hex_.c_str(),
+ "PUBS", pub_hex_.c_str(),
+ "NONC", nonce_hex_.c_str(),
+ "$padding", static_cast<int>(kClientHelloMinimumSize),
+ NULL);
+ ShouldSucceed(msg);
+ // The message should be rejected because the strike-register is still
+ // quiescent.
+ ASSERT_EQ(kREJ, out_.tag());
+
+ config_.set_replay_protection(false);
+
+ ShouldSucceed(msg);
+ // The message should be accepted now.
+ ASSERT_EQ(kSHLO, out_.tag());
+
+ ShouldSucceed(msg);
+ // The message should accepted twice when replay protection is off.
+ ASSERT_EQ(kSHLO, out_.tag());
+}
+
+class CryptoServerTestNoConfig : public CryptoServerTest {
+ public:
+ virtual void SetUp() {
+ // Deliberately don't add a config so that we can test this situation.
+ }
+};
+
+TEST_F(CryptoServerTestNoConfig, DontCrash) {
+ ShouldFailMentioning("No config", InchoateClientHello(
+ "CHLO",
+ NULL));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_utils.cc b/chromium/net/quic/crypto/crypto_utils.cc
new file mode 100644
index 00000000000..469e582268f
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_utils.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_utils.h"
+
+#include "crypto/hkdf.h"
+#include "net/base/net_util.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_time.h"
+#include "url/url_canon.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// static
+void CryptoUtils::GenerateNonce(QuicWallTime now,
+ QuicRandom* random_generator,
+ StringPiece orbit,
+ string* nonce) {
+ // a 4-byte timestamp + 28 random bytes.
+ nonce->reserve(kNonceSize);
+ nonce->resize(kNonceSize);
+ uint32 gmt_unix_time = now.ToUNIXSeconds();
+ // The time in the nonce must be encoded in big-endian because the
+ // strike-register depends on the nonces being ordered by time.
+ (*nonce)[0] = static_cast<char>(gmt_unix_time >> 24);
+ (*nonce)[1] = static_cast<char>(gmt_unix_time >> 16);
+ (*nonce)[2] = static_cast<char>(gmt_unix_time >> 8);
+ (*nonce)[3] = static_cast<char>(gmt_unix_time);
+
+ size_t bytes_written = sizeof(gmt_unix_time);
+ if (orbit.size() == 8) {
+ memcpy(&(*nonce)[bytes_written], orbit.data(), orbit.size());
+ bytes_written += orbit.size();
+ }
+ random_generator->RandBytes(&(*nonce)[bytes_written],
+ kNonceSize - bytes_written);
+}
+
+// static
+bool CryptoUtils::IsValidSNI(StringPiece sni) {
+ // TODO(rtenneti): Support RFC2396 hostname.
+ // NOTE: Microsoft does NOT enforce this spec, so if we throw away hostnames
+ // based on the above spec, we may be losing some hostnames that windows
+ // would consider valid. By far the most common hostname character NOT
+ // accepted by the above spec is '_'.
+ url_canon::CanonHostInfo host_info;
+ string canonicalized_host(CanonicalizeHost(sni.as_string(), &host_info));
+ return !host_info.IsIPAddress() &&
+ IsCanonicalizedHostCompliant(canonicalized_host, std::string()) &&
+ sni.find_last_of('.') != string::npos;
+}
+
+// static
+string CryptoUtils::NormalizeHostname(const char* hostname) {
+ url_canon::CanonHostInfo host_info;
+ string host(CanonicalizeHost(hostname, &host_info));
+
+ // Walk backwards over the string, stopping at the first trailing dot.
+ size_t host_end = host.length();
+ while (host_end != 0 && host[host_end - 1] == '.') {
+ host_end--;
+ }
+
+ // Erase the trailing dots.
+ if (host_end != host.length()) {
+ host.erase(host_end, host.length() - host_end);
+ }
+ return host;
+}
+
+// static
+void CryptoUtils::DeriveKeys(StringPiece premaster_secret,
+ QuicTag aead,
+ StringPiece client_nonce,
+ StringPiece server_nonce,
+ const string& hkdf_input,
+ Perspective perspective,
+ CrypterPair* out) {
+ out->encrypter.reset(QuicEncrypter::Create(aead));
+ out->decrypter.reset(QuicDecrypter::Create(aead));
+ size_t key_bytes = out->encrypter->GetKeySize();
+ size_t nonce_prefix_bytes = out->encrypter->GetNoncePrefixSize();
+
+ StringPiece nonce = client_nonce;
+ string nonce_storage;
+ if (!server_nonce.empty()) {
+ nonce_storage = client_nonce.as_string() + server_nonce.as_string();
+ nonce = nonce_storage;
+ }
+
+ crypto::HKDF hkdf(premaster_secret, nonce, hkdf_input, key_bytes,
+ nonce_prefix_bytes);
+ if (perspective == SERVER) {
+ out->encrypter->SetKey(hkdf.server_write_key());
+ out->encrypter->SetNoncePrefix(hkdf.server_write_iv());
+ out->decrypter->SetKey(hkdf.client_write_key());
+ out->decrypter->SetNoncePrefix(hkdf.client_write_iv());
+ } else {
+ out->encrypter->SetKey(hkdf.client_write_key());
+ out->encrypter->SetNoncePrefix(hkdf.client_write_iv());
+ out->decrypter->SetKey(hkdf.server_write_key());
+ out->decrypter->SetNoncePrefix(hkdf.server_write_iv());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/crypto_utils.h b/chromium/net/quic/crypto/crypto_utils.h
new file mode 100644
index 00000000000..6dfce2a58e3
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_utils.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 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.
+//
+// Some helpers for quic crypto
+
+#ifndef NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
+#define NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class QuicTime;
+class QuicRandom;
+struct QuicCryptoNegotiatedParameters;
+
+class NET_EXPORT_PRIVATE CryptoUtils {
+ public:
+ enum Perspective {
+ SERVER,
+ CLIENT,
+ };
+
+ // Generates the connection nonce. The nonce is formed as:
+ // <4 bytes> current time
+ // <8 bytes> |orbit| (or random if |orbit| is empty)
+ // <20 bytes> random
+ static void GenerateNonce(QuicWallTime now,
+ QuicRandom* random_generator,
+ base::StringPiece orbit,
+ std::string* nonce);
+
+ // Returns true if the sni is valid, false otherwise.
+ // (1) disallow IP addresses;
+ // (2) check that the hostname contains valid characters only; and
+ // (3) contains at least one dot.
+ static bool IsValidSNI(base::StringPiece sni);
+
+ // Convert hostname to lowercase and remove the trailing '.'.
+ // Returns |hostname|. NormalizeHostname() doesn't support IP address
+ // literals. IsValidSNI() should be called before calling NormalizeHostname().
+ static std::string NormalizeHostname(const char* hostname);
+
+ // DeriveKeys populates |out->encrypter| and |out->decrypter| given the
+ // contents of |premaster_secret|, |client_nonce|, |server_nonce| and
+ // |hkdf_input|. |aead| determines which cipher will be used. |perspective|
+ // controls whether the server's keys are assigned to |encrypter| or
+ // |decrypter|. |server_nonce| is optional and, if non-empty, is mixed into
+ // the key derivation.
+ static void DeriveKeys(base::StringPiece premaster_secret,
+ QuicTag aead,
+ base::StringPiece client_nonce,
+ base::StringPiece server_nonce,
+ const std::string& hkdf_input,
+ Perspective perspective,
+ CrypterPair* out);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CRYPTO_UTILS_H_
diff --git a/chromium/net/quic/crypto/crypto_utils_test.cc b/chromium/net/quic/crypto/crypto_utils_test.cc
new file mode 100644
index 00000000000..17eb19250ee
--- /dev/null
+++ b/chromium/net/quic/crypto/crypto_utils_test.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 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/quic/crypto/crypto_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(CryptoUtilsTest, IsValidSNI) {
+ // IP as SNI.
+ EXPECT_FALSE(CryptoUtils::IsValidSNI("192.168.0.1"));
+ // SNI without any dot.
+ EXPECT_FALSE(CryptoUtils::IsValidSNI("somedomain"));
+ // Invalid RFC2396 hostname
+ // TODO(rtenneti): Support RFC2396 hostname.
+ // EXPECT_FALSE(CryptoUtils::IsValidSNI("some_domain.com"));
+ // An empty string must be invalid otherwise the QUIC client will try sending
+ // it.
+ EXPECT_FALSE(CryptoUtils::IsValidSNI(""));
+
+ // Valid SNI
+ EXPECT_TRUE(CryptoUtils::IsValidSNI("test.google.com"));
+}
+
+TEST(CryptoUtilsTest, NormalizeHostname) {
+ struct {
+ const char *input, *expected;
+ } tests[] = {
+ { "www.google.com", "www.google.com", },
+ { "WWW.GOOGLE.COM", "www.google.com", },
+ { "www.google.com.", "www.google.com", },
+ { "www.google.COM.", "www.google.com", },
+ { "www.google.com..", "www.google.com", },
+ { "www.google.com........", "www.google.com", },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ EXPECT_EQ(std::string(tests[i].expected),
+ CryptoUtils::NormalizeHostname(tests[i].input));
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/curve25519_key_exchange.cc b/chromium/net/quic/crypto/curve25519_key_exchange.cc
new file mode 100644
index 00000000000..3b8880453a3
--- /dev/null
+++ b/chromium/net/quic/crypto/curve25519_key_exchange.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2013 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/quic/crypto/curve25519_key_exchange.h"
+
+#include "base/logging.h"
+#include "crypto/curve25519.h"
+#include "net/quic/crypto/quic_random.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+Curve25519KeyExchange::Curve25519KeyExchange() {}
+
+Curve25519KeyExchange::~Curve25519KeyExchange() {}
+
+// static
+Curve25519KeyExchange* Curve25519KeyExchange::New(
+ const StringPiece& private_key) {
+ Curve25519KeyExchange* ka;
+ // We don't want to #include the NaCl headers in the public header file, so
+ // we use literals for the sizes of private_key_ and public_key_. Here we
+ // assert that those values are equal to the values from the NaCl header.
+ COMPILE_ASSERT(
+ sizeof(ka->private_key_) == crypto::curve25519::kScalarBytes,
+ header_out_of_sync);
+ COMPILE_ASSERT(sizeof(ka->public_key_) == crypto::curve25519::kBytes,
+ header_out_of_sync);
+
+ if (private_key.size() != crypto::curve25519::kScalarBytes) {
+ return NULL;
+ }
+
+ ka = new Curve25519KeyExchange();
+ memcpy(ka->private_key_, private_key.data(),
+ crypto::curve25519::kScalarBytes);
+ crypto::curve25519::ScalarBaseMult(ka->private_key_, ka->public_key_);
+ return ka;
+}
+
+// static
+string Curve25519KeyExchange::NewPrivateKey(QuicRandom* rand) {
+ uint8 private_key[crypto::curve25519::kScalarBytes];
+ rand->RandBytes(private_key, sizeof(private_key));
+
+ // This makes |private_key| a valid scalar, as specified on
+ // http://cr.yp.to/ecdh.html
+ private_key[0] &= 248;
+ private_key[31] &= 127;
+ private_key[31] |= 64;
+ return string(reinterpret_cast<char*>(private_key), sizeof(private_key));
+}
+
+KeyExchange* Curve25519KeyExchange::NewKeyPair(QuicRandom* rand) const {
+ const string private_value = NewPrivateKey(rand);
+ return Curve25519KeyExchange::New(private_value);
+}
+
+bool Curve25519KeyExchange::CalculateSharedKey(
+ const StringPiece& peer_public_value,
+ string* out_result) const {
+ if (peer_public_value.size() != crypto::curve25519::kBytes) {
+ return false;
+ }
+
+ uint8 result[crypto::curve25519::kBytes];
+ crypto::curve25519::ScalarMult(
+ private_key_,
+ reinterpret_cast<const uint8*>(peer_public_value.data()),
+ result);
+ out_result->assign(reinterpret_cast<char*>(result), sizeof(result));
+
+ return true;
+}
+
+StringPiece Curve25519KeyExchange::public_value() const {
+ return StringPiece(reinterpret_cast<const char*>(public_key_),
+ sizeof(public_key_));
+}
+
+QuicTag Curve25519KeyExchange::tag() const { return kC255; }
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/curve25519_key_exchange.h b/chromium/net/quic/crypto/curve25519_key_exchange.h
new file mode 100644
index 00000000000..ecc0880ce67
--- /dev/null
+++ b/chromium/net/quic/crypto/curve25519_key_exchange.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/key_exchange.h"
+
+namespace net {
+
+class QuicRandom;
+
+// Curve25519KeyExchange implements a KeyExchange using elliptic-curve
+// Diffie-Hellman on curve25519. See http://cr.yp.to/ecdh.html
+class NET_EXPORT_PRIVATE Curve25519KeyExchange : public KeyExchange {
+ public:
+ virtual ~Curve25519KeyExchange();
+
+ // New creates a new object from a private key. If the private key is
+ // invalid, NULL is returned.
+ static Curve25519KeyExchange* New(const base::StringPiece& private_key);
+
+ // NewPrivateKey returns a private key, generated from |rand|, suitable for
+ // passing to |New|.
+ static std::string NewPrivateKey(QuicRandom* rand);
+
+ // KeyExchange interface.
+ virtual KeyExchange* NewKeyPair(QuicRandom* rand) const OVERRIDE;
+ virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+ std::string* shared_key) const OVERRIDE;
+ virtual base::StringPiece public_value() const OVERRIDE;
+ virtual QuicTag tag() const OVERRIDE;
+
+ private:
+ Curve25519KeyExchange();
+
+ uint8 private_key_[32];
+ uint8 public_key_[32];
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_CURVE25519_KEY_EXCHANGE_H_
diff --git a/chromium/net/quic/crypto/curve25519_key_exchange_test.cc b/chromium/net/quic/crypto/curve25519_key_exchange_test.cc
new file mode 100644
index 00000000000..93ef63010bd
--- /dev/null
+++ b/chromium/net/quic/crypto/curve25519_key_exchange_test.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 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/quic/crypto/curve25519_key_exchange.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/quic_random.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+
+// SharedKey just tests that the basic key exchange identity holds: that both
+// parties end up with the same key.
+TEST(Curve25519KeyExchange, SharedKey) {
+ QuicRandom* const rand = QuicRandom::GetInstance();
+
+ for (int i = 0; i < 5; i++) {
+ const string alice_key(Curve25519KeyExchange::NewPrivateKey(rand));
+ const string bob_key(Curve25519KeyExchange::NewPrivateKey(rand));
+
+ scoped_ptr<Curve25519KeyExchange> alice(
+ Curve25519KeyExchange::New(alice_key));
+ scoped_ptr<Curve25519KeyExchange> bob(Curve25519KeyExchange::New(bob_key));
+
+ const StringPiece alice_public(alice->public_value());
+ const StringPiece bob_public(bob->public_value());
+
+ string alice_shared, bob_shared;
+ ASSERT_TRUE(alice->CalculateSharedKey(bob_public, &alice_shared));
+ ASSERT_TRUE(bob->CalculateSharedKey(alice_public, &bob_shared));
+ ASSERT_EQ(alice_shared, bob_shared);
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/ephemeral_key_source.h b/chromium/net/quic/crypto/ephemeral_key_source.h
new file mode 100644
index 00000000000..2700be0fe1b
--- /dev/null
+++ b/chromium/net/quic/crypto/ephemeral_key_source.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
+#define NET_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class KeyExchange;
+class QuicRandom;
+
+// EphemeralKeySource manages and rotates ephemeral keys as they can be reused
+// for several connections in a short space of time. Since the implementation
+// of this may involve locking or thread-local data, this interface abstracts
+// that away.
+class NET_EXPORT_PRIVATE EphemeralKeySource {
+ public:
+ virtual ~EphemeralKeySource() {}
+
+ // CalculateForwardSecureKey generates an ephemeral public/private key pair
+ // using the algorithm |key_exchange|, sets |*public_value| to the public key
+ // and returns the shared key between |peer_public_value| and the private
+ // key. |*public_value| will be sent to the peer to be used with the peer's
+ // private key.
+ virtual std::string CalculateForwardSecureKey(
+ const KeyExchange* key_exchange,
+ QuicRandom* rand,
+ QuicTime now,
+ base::StringPiece peer_public_value,
+ std::string* public_value) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_EPHEMERAL_KEY_SOURCE_H_
diff --git a/chromium/net/quic/crypto/key_exchange.h b/chromium/net/quic/crypto/key_exchange.h
new file mode 100644
index 00000000000..8690f0eff2c
--- /dev/null
+++ b/chromium/net/quic/crypto/key_exchange.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+
+namespace net {
+
+class QuicRandom;
+
+// KeyExchange is an abstract class that provides an interface to a
+// key-exchange primitive.
+class NET_EXPORT_PRIVATE KeyExchange {
+ public:
+ virtual ~KeyExchange() {}
+
+ // NewKeyPair generates a new public, private key pair. The caller takes
+ // ownership of the return value. (This is intended for servers that need to
+ // generate forward-secure keys.)
+ virtual KeyExchange* NewKeyPair(QuicRandom* rand) const = 0;
+
+ // CalculateSharedKey computes the shared key between the local private key
+ // (which is implicitly known by a KeyExchange object) and a public value
+ // from the peer.
+ virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+ std::string* shared_key) const = 0;
+
+ // public_value returns the local public key which can be sent to a peer in
+ // order to complete a key exchange. The returned StringPiece is a reference
+ // to a member of the KeyExchange and is only valid for as long as the
+ // KeyExchange exists.
+ virtual base::StringPiece public_value() const = 0;
+
+ // tag returns the tag value that identifies this key exchange function.
+ virtual QuicTag tag() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_KEY_EXCHANGE_H_
diff --git a/chromium/net/quic/crypto/null_decrypter.cc b/chromium/net/quic/crypto/null_decrypter.cc
new file mode 100644
index 00000000000..7bda75fe80d
--- /dev/null
+++ b/chromium/net/quic/crypto/null_decrypter.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/null_decrypter.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/quic_data_reader.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+bool NullDecrypter::SetKey(StringPiece key) { return key.empty(); }
+
+bool NullDecrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ return nonce_prefix.empty();
+}
+
+bool NullDecrypter::Decrypt(StringPiece /*nonce*/,
+ StringPiece associated_data,
+ StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) {
+ QuicDataReader reader(ciphertext.data(), ciphertext.length());
+
+ uint128 hash;
+ if (!reader.ReadUInt128(&hash)) {
+ return false;
+ }
+
+ StringPiece plaintext = reader.ReadRemainingPayload();
+
+ // TODO(rch): avoid buffer copy here
+ string buffer = associated_data.as_string();
+ plaintext.AppendToString(&buffer);
+
+ if (hash != QuicUtils::FNV1a_128_Hash(buffer.data(), buffer.length())) {
+ return false;
+ }
+ memcpy(output, plaintext.data(), plaintext.length());
+ *output_length = plaintext.length();
+ return true;
+}
+
+QuicData* NullDecrypter::DecryptPacket(QuicPacketSequenceNumber /*seq_number*/,
+ StringPiece associated_data,
+ StringPiece ciphertext) {
+ // It's worth duplicating |Decrypt|, above, in order to save a copy by using
+ // the shared-data QuicData constructor directly.
+ QuicDataReader reader(ciphertext.data(), ciphertext.length());
+
+ uint128 hash;
+ if (!reader.ReadUInt128(&hash)) {
+ return NULL;
+ }
+
+ StringPiece plaintext = reader.ReadRemainingPayload();
+
+ // TODO(rch): avoid buffer copy here
+ string buffer = associated_data.as_string();
+ plaintext.AppendToString(&buffer);
+
+ if (hash != QuicUtils::FNV1a_128_Hash(buffer.data(), buffer.length())) {
+ return NULL;
+ }
+ return new QuicData(plaintext.data(), plaintext.length());
+}
+
+StringPiece NullDecrypter::GetKey() const { return StringPiece(); }
+
+StringPiece NullDecrypter::GetNoncePrefix() const { return StringPiece(); }
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/null_decrypter.h b/chromium/net/quic/crypto/null_decrypter.h
new file mode 100644
index 00000000000..01beb2d7110
--- /dev/null
+++ b/chromium/net/quic/crypto/null_decrypter.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/quic_decrypter.h"
+
+namespace net {
+
+// A NullDecrypter is a QuicDecrypter used before a crypto negotiation
+// has occurred. It does not actually decrypt the payload, but does
+// verify a hash (fnv128) over both the payload and associated data.
+class NET_EXPORT_PRIVATE NullDecrypter : public QuicDecrypter {
+ public:
+ virtual ~NullDecrypter() {}
+
+ // QuicDecrypter implementation
+ virtual bool SetKey(base::StringPiece key) OVERRIDE;
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+ virtual bool Decrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) OVERRIDE;
+ virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext) OVERRIDE;
+ virtual base::StringPiece GetKey() const OVERRIDE;
+ virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_NULL_DECRYPTER_H_
diff --git a/chromium/net/quic/crypto/null_decrypter_test.cc b/chromium/net/quic/crypto/null_decrypter_test.cc
new file mode 100644
index 00000000000..e9b9647d202
--- /dev/null
+++ b/chromium/net/quic/crypto/null_decrypter_test.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/null_decrypter.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+TEST(NullDecrypterTest, Decrypt) {
+ unsigned char expected[] = {
+ // fnv hash
+ 0xa0, 0x6f, 0x44, 0x8a,
+ 0x44, 0xf8, 0x18, 0x3b,
+ 0x47, 0x91, 0xb2, 0x13,
+ 0x6b, 0x09, 0xbb, 0xae,
+ // payload
+ 'g', 'o', 'o', 'd',
+ 'b', 'y', 'e', '!',
+ };
+ NullDecrypter decrypter;
+ scoped_ptr<QuicData> decrypted(decrypter.DecryptPacket(
+ 0, "hello world!", StringPiece(reinterpret_cast<const char*>(expected),
+ arraysize(expected))));
+ ASSERT_TRUE(decrypted.get());
+ EXPECT_EQ("goodbye!", decrypted->AsStringPiece());
+}
+
+TEST(NullDecrypterTest, BadHash) {
+ unsigned char expected[] = {
+ // fnv hash
+ 0x46, 0x11, 0xea, 0x5f,
+ 0xcf, 0x1d, 0x66, 0x5b,
+ 0xba, 0xf0, 0xbc, 0xfd,
+ 0x88, 0x79, 0xca, 0x37,
+ // payload
+ 'g', 'o', 'o', 'd',
+ 'b', 'y', 'e', '!',
+ };
+ NullDecrypter decrypter;
+ scoped_ptr<QuicData> decrypted(decrypter.DecryptPacket(
+ 0, "hello world!", StringPiece(reinterpret_cast<const char*>(expected),
+ arraysize(expected))));
+ ASSERT_FALSE(decrypted.get());
+}
+
+TEST(NullDecrypterTest, ShortInput) {
+ unsigned char expected[] = {
+ // fnv hash (truncated)
+ 0x46, 0x11, 0xea, 0x5f,
+ 0xcf, 0x1d, 0x66, 0x5b,
+ 0xba, 0xf0, 0xbc, 0xfd,
+ 0x88, 0x79, 0xca,
+ };
+ NullDecrypter decrypter;
+ scoped_ptr<QuicData> decrypted(decrypter.DecryptPacket(
+ 0, "hello world!", StringPiece(reinterpret_cast<const char*>(expected),
+ arraysize(expected))));
+ ASSERT_FALSE(decrypted.get());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/null_encrypter.cc b/chromium/net/quic/crypto/null_encrypter.cc
new file mode 100644
index 00000000000..a0a680d9c71
--- /dev/null
+++ b/chromium/net/quic/crypto/null_encrypter.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/quic_data_writer.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+const size_t kHashSize = 16; // size of uint128 serialized
+
+bool NullEncrypter::SetKey(StringPiece key) { return key.empty(); }
+
+bool NullEncrypter::SetNoncePrefix(StringPiece nonce_prefix) {
+ return nonce_prefix.empty();
+}
+
+bool NullEncrypter::Encrypt(
+ StringPiece /*nonce*/,
+ StringPiece associated_data,
+ StringPiece plaintext,
+ unsigned char* output) {
+ string buffer = associated_data.as_string();
+ plaintext.AppendToString(&buffer);
+ uint128 hash = QuicUtils::FNV1a_128_Hash(buffer.data(), buffer.length());
+ QuicUtils::SerializeUint128(hash, output);
+ memcpy(output + sizeof(hash), plaintext.data(), plaintext.size());
+ return true;
+}
+
+QuicData* NullEncrypter::EncryptPacket(
+ QuicPacketSequenceNumber /*sequence_number*/,
+ StringPiece associated_data,
+ StringPiece plaintext) {
+ const size_t len = plaintext.size() + sizeof(uint128);
+ uint8* buffer = new uint8[len];
+ Encrypt(StringPiece(), associated_data, plaintext, buffer);
+ return new QuicData(reinterpret_cast<char*>(buffer), len, true);
+}
+
+size_t NullEncrypter::GetKeySize() const { return 0; }
+
+size_t NullEncrypter::GetNoncePrefixSize() const { return 0; }
+
+size_t NullEncrypter::GetMaxPlaintextSize(size_t ciphertext_size) const {
+ return ciphertext_size - kHashSize;
+}
+
+size_t NullEncrypter::GetCiphertextSize(size_t plaintext_size) const {
+ return plaintext_size + kHashSize;
+}
+
+StringPiece NullEncrypter::GetKey() const { return StringPiece(); }
+
+StringPiece NullEncrypter::GetNoncePrefix() const { return StringPiece(); }
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/null_encrypter.h b/chromium/net/quic/crypto/null_encrypter.h
new file mode 100644
index 00000000000..ed05e1faaa0
--- /dev/null
+++ b/chromium/net/quic/crypto/null_encrypter.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
+
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+namespace net {
+
+// A NullEncrypter is a QuicEncrypter used before a crypto negotiation
+// has occurred. It does not actually encrypt the payload, but does
+// generate a MAC (fnv128) over both the payload and associated data.
+class NET_EXPORT_PRIVATE NullEncrypter : public QuicEncrypter {
+ public:
+ virtual ~NullEncrypter() {}
+
+ // QuicEncrypter implementation
+ virtual bool SetKey(base::StringPiece key) OVERRIDE;
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) OVERRIDE;
+ virtual bool Encrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext,
+ unsigned char* output) OVERRIDE;
+ virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext) OVERRIDE;
+ virtual size_t GetKeySize() const OVERRIDE;
+ virtual size_t GetNoncePrefixSize() const OVERRIDE;
+ virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE;
+ virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE;
+ virtual base::StringPiece GetKey() const OVERRIDE;
+ virtual base::StringPiece GetNoncePrefix() const OVERRIDE;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_NULL_ENCRYPTER_H_
diff --git a/chromium/net/quic/crypto/null_encrypter_test.cc b/chromium/net/quic/crypto/null_encrypter_test.cc
new file mode 100644
index 00000000000..4c00f918177
--- /dev/null
+++ b/chromium/net/quic/crypto/null_encrypter_test.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace test {
+
+TEST(NullEncrypterTest, Encrypt) {
+ unsigned char expected[] = {
+ // fnv hash
+ 0xa0, 0x6f, 0x44, 0x8a,
+ 0x44, 0xf8, 0x18, 0x3b,
+ 0x47, 0x91, 0xb2, 0x13,
+ 0x6b, 0x09, 0xbb, 0xae,
+ // payload
+ 'g', 'o', 'o', 'd',
+ 'b', 'y', 'e', '!',
+ };
+ NullEncrypter encrypter;
+ scoped_ptr<QuicData> encrypted(
+ encrypter.EncryptPacket(0, "hello world!", "goodbye!"));
+ ASSERT_TRUE(encrypted.get());
+ test::CompareCharArraysWithHexError(
+ "encrypted data", encrypted->data(), encrypted->length(),
+ reinterpret_cast<const char*>(expected), arraysize(expected));
+}
+
+TEST(NullEncrypterTest, GetMaxPlaintextSize) {
+ NullEncrypter encrypter;
+ EXPECT_EQ(1000u, encrypter.GetMaxPlaintextSize(1016));
+ EXPECT_EQ(100u, encrypter.GetMaxPlaintextSize(116));
+ EXPECT_EQ(10u, encrypter.GetMaxPlaintextSize(26));
+}
+
+TEST(NullEncrypterTest, GetCiphertextSize) {
+ NullEncrypter encrypter;
+ EXPECT_EQ(1016u, encrypter.GetCiphertextSize(1000));
+ EXPECT_EQ(116u, encrypter.GetCiphertextSize(100));
+ EXPECT_EQ(26u, encrypter.GetCiphertextSize(10));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/p256_key_exchange.h b/chromium/net/quic/crypto/p256_key_exchange.h
new file mode 100644
index 00000000000..8145cc05249
--- /dev/null
+++ b/chromium/net/quic/crypto/p256_key_exchange.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
+#define NET_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/key_exchange.h"
+
+#if defined(USE_OPENSSL)
+#include "crypto/openssl_util.h"
+// Forward declaration for openssl/*.h
+typedef struct ec_key_st EC_KEY;
+extern "C" void EC_KEY_free(EC_KEY* key);
+#else
+#include "crypto/ec_private_key.h"
+#include "crypto/scoped_nss_types.h"
+#endif
+
+namespace net {
+
+// P256KeyExchange implements a KeyExchange using elliptic-curve
+// Diffie-Hellman on NIST P-256.
+class NET_EXPORT_PRIVATE P256KeyExchange : public KeyExchange {
+ public:
+ virtual ~P256KeyExchange();
+
+ // New creates a new key exchange object from a private key. If
+ // |private_key| is invalid, NULL is returned.
+ static P256KeyExchange* New(base::StringPiece private_key);
+
+ // |NewPrivateKey| returns a private key, suitable for passing to |New|.
+ // If |NewPrivateKey| can't generate a private key, it returns an empty
+ // string.
+ static std::string NewPrivateKey();
+
+ // KeyExchange interface.
+ virtual KeyExchange* NewKeyPair(QuicRandom* rand) const OVERRIDE;
+ virtual bool CalculateSharedKey(const base::StringPiece& peer_public_value,
+ std::string* shared_key) const OVERRIDE;
+ virtual base::StringPiece public_value() const OVERRIDE;
+ virtual QuicTag tag() const OVERRIDE;
+
+ private:
+ enum {
+ // A P-256 field element consists of 32 bytes.
+ kP256FieldBytes = 32,
+ // A P-256 point in uncompressed form consists of 0x04 (to denote
+ // that the point is uncompressed) followed by two, 32-byte field
+ // elements.
+ kUncompressedP256PointBytes = 1 + 2 * kP256FieldBytes,
+ // The first byte in an uncompressed P-256 point.
+ kUncompressedECPointForm = 0x04,
+ };
+
+#if defined(USE_OPENSSL)
+ // P256KeyExchange takes ownership of |private_key|, and expects
+ // |public_key| consists of |kUncompressedP256PointBytes| bytes.
+ P256KeyExchange(EC_KEY* private_key, const uint8* public_key);
+
+ crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> private_key_;
+#else
+ // P256KeyExchange takes ownership of |key_pair|, and expects
+ // |public_key| consists of |kUncompressedP256PointBytes| bytes.
+ P256KeyExchange(crypto::ECPrivateKey* key_pair, const uint8* public_key);
+
+ scoped_ptr<crypto::ECPrivateKey> key_pair_;
+#endif
+ // The public key stored as an uncompressed P-256 point.
+ uint8 public_key_[kUncompressedP256PointBytes];
+};
+
+} // namespace net
+#endif // NET_QUIC_CRYPTO_P256_KEY_EXCHANGE_H_
+
diff --git a/chromium/net/quic/crypto/p256_key_exchange_nss.cc b/chromium/net/quic/crypto/p256_key_exchange_nss.cc
new file mode 100644
index 00000000000..dede5ba7b21
--- /dev/null
+++ b/chromium/net/quic/crypto/p256_key_exchange_nss.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2013 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/quic/crypto/p256_key_exchange.h"
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// Password used by |NewPrivateKey| to encrypt exported EC private keys.
+// This is not used to provide any security, but to workaround NSS being
+// unwilling to export unencrypted EC keys. Note that SPDY and ChannelID
+// use the same approach.
+const char kExportPassword[] = "";
+
+// Convert StringPiece to vector of uint8.
+static vector<uint8> StringPieceToVector(StringPiece piece) {
+ return vector<uint8>(piece.data(), piece.data() + piece.length());
+}
+
+} // namespace
+
+P256KeyExchange::P256KeyExchange(crypto::ECPrivateKey* key_pair,
+ const uint8* public_key)
+ : key_pair_(key_pair) {
+ memcpy(public_key_, public_key, sizeof(public_key_));
+}
+
+P256KeyExchange::~P256KeyExchange() {
+}
+
+// static
+P256KeyExchange* P256KeyExchange::New(StringPiece key) {
+ if (key.size() < 2) {
+ DLOG(INFO) << "Key pair is too small.";
+ return NULL;
+ }
+
+ const uint8* data = reinterpret_cast<const uint8*>(key.data());
+ size_t size = static_cast<size_t>(data[0]) |
+ (static_cast<size_t>(data[1]) << 8);
+ key.remove_prefix(2);
+ if (key.size() < size) {
+ DLOG(INFO) << "Key pair does not contain key material.";
+ return NULL;
+ }
+
+ StringPiece private_piece(key.data(), size);
+ key.remove_prefix(size);
+ if (key.empty()) {
+ DLOG(INFO) << "Key pair does not contain public key.";
+ return NULL;
+ }
+
+ StringPiece public_piece(key);
+
+ scoped_ptr<crypto::ECPrivateKey> key_pair(
+ crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+ kExportPassword,
+ // TODO(thaidn): fix this interface to avoid copying secrets.
+ StringPieceToVector(private_piece),
+ StringPieceToVector(public_piece)));
+
+ if (!key_pair.get()) {
+ DLOG(INFO) << "Can't decrypt private key.";
+ return NULL;
+ }
+
+ // Perform some sanity checks on the public key.
+ SECKEYPublicKey* public_key = key_pair->public_key();
+ if (public_key->keyType != ecKey ||
+ public_key->u.ec.publicValue.len != kUncompressedP256PointBytes ||
+ !public_key->u.ec.publicValue.data ||
+ public_key->u.ec.publicValue.data[0] != kUncompressedECPointForm) {
+ DLOG(INFO) << "Key is invalid.";
+ return NULL;
+ }
+
+ // Ensure that the key is using the correct curve, i.e., NIST P-256.
+ const SECOidData* oid_data = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
+ if (!oid_data) {
+ DLOG(INFO) << "Can't get P-256's OID.";
+ return NULL;
+ }
+
+ if (public_key->u.ec.DEREncodedParams.len != oid_data->oid.len + 2 ||
+ !public_key->u.ec.DEREncodedParams.data ||
+ public_key->u.ec.DEREncodedParams.data[0] != SEC_ASN1_OBJECT_ID ||
+ public_key->u.ec.DEREncodedParams.data[1] != oid_data->oid.len ||
+ memcmp(public_key->u.ec.DEREncodedParams.data + 2,
+ oid_data->oid.data, oid_data->oid.len) != 0) {
+ DLOG(INFO) << "Key is invalid.";
+ }
+
+ return new P256KeyExchange(key_pair.release(),
+ public_key->u.ec.publicValue.data);
+}
+
+// static
+string P256KeyExchange::NewPrivateKey() {
+ scoped_ptr<crypto::ECPrivateKey> key_pair(crypto::ECPrivateKey::Create());
+
+ if (!key_pair.get()) {
+ DLOG(INFO) << "Can't generate new key pair.";
+ return string();
+ }
+
+ vector<uint8> private_key;
+ if (!key_pair->ExportEncryptedPrivateKey(kExportPassword,
+ 1 /* iteration */,
+ &private_key)) {
+ DLOG(INFO) << "Can't export private key.";
+ return string();
+ }
+
+ // NSS lacks the ability to import an ECC private key without
+ // also importing the public key, so it is necessary to also
+ // store the public key.
+ vector<uint8> public_key;
+ if (!key_pair->ExportPublicKey(&public_key)) {
+ DLOG(INFO) << "Can't export public key.";
+ return string();
+ }
+
+ // TODO(thaidn): determine how large encrypted private key can be
+ uint16 private_key_size = private_key.size();
+ const size_t result_size = sizeof(private_key_size) +
+ private_key_size +
+ public_key.size();
+ vector<char> result(result_size);
+ char* resultp = &result[0];
+ // Export the key string.
+ // The first two bytes are the private key's size in little endian.
+ private_key_size = base::ByteSwapToLE16(private_key_size);
+ memcpy(resultp, &private_key_size, sizeof(private_key_size));
+ resultp += sizeof(private_key_size);
+ memcpy(resultp, &private_key[0], private_key.size());
+ resultp += private_key.size();
+ memcpy(resultp, &public_key[0], public_key.size());
+
+ return string(&result[0], result_size);
+}
+
+KeyExchange* P256KeyExchange::NewKeyPair(QuicRandom* /*rand*/) const {
+ // TODO(agl): avoid the serialisation/deserialisation in this function.
+ const string private_value = NewPrivateKey();
+ return P256KeyExchange::New(private_value);
+}
+
+bool P256KeyExchange::CalculateSharedKey(const StringPiece& peer_public_value,
+ string* out_result) const {
+ if (peer_public_value.size() != kUncompressedP256PointBytes ||
+ peer_public_value[0] != kUncompressedECPointForm) {
+ DLOG(INFO) << "Peer public value is invalid.";
+ return false;
+ }
+
+ DCHECK(key_pair_.get());
+ DCHECK(key_pair_->public_key());
+
+ SECKEYPublicKey peer_public_key;
+ memset(&peer_public_key, 0, sizeof(peer_public_key));
+
+ peer_public_key.keyType = ecKey;
+ // Both sides of a ECDH key exchange need to use the same EC params.
+ peer_public_key.u.ec.DEREncodedParams.len =
+ key_pair_->public_key()->u.ec.DEREncodedParams.len;
+ peer_public_key.u.ec.DEREncodedParams.data =
+ key_pair_->public_key()->u.ec.DEREncodedParams.data;
+
+ peer_public_key.u.ec.publicValue.type = siBuffer;
+ peer_public_key.u.ec.publicValue.data =
+ reinterpret_cast<uint8*>(const_cast<char*>(peer_public_value.data()));
+ peer_public_key.u.ec.publicValue.len = peer_public_value.size();
+
+ // The NSS function performing ECDH key exchange is PK11_PubDeriveWithKDF.
+ // As this function is used for SSL/TLS's ECDH key exchanges it has many
+ // arguments, most of which are not required in QUIC.
+ // Key derivation function CKD_NULL is used because the return value of
+ // |CalculateSharedKey| is the actual ECDH shared key, not any derived keys
+ // from it.
+ crypto::ScopedPK11SymKey premaster_secret(
+ PK11_PubDeriveWithKDF(
+ key_pair_->key(),
+ &peer_public_key,
+ PR_FALSE,
+ NULL,
+ NULL,
+ CKM_ECDH1_DERIVE, /* mechanism */
+ CKM_GENERIC_SECRET_KEY_GEN, /* target */
+ CKA_DERIVE,
+ 0,
+ CKD_NULL, /* kdf */
+ NULL,
+ NULL));
+
+ if (!premaster_secret.get()) {
+ DLOG(INFO) << "Can't derive ECDH shared key.";
+ return false;
+ }
+
+ if (PK11_ExtractKeyValue(premaster_secret.get()) != SECSuccess) {
+ DLOG(INFO) << "Can't extract raw ECDH shared key.";
+ return false;
+ }
+
+ SECItem* key_data = PK11_GetKeyData(premaster_secret.get());
+ if (!key_data || !key_data->data || key_data->len != kP256FieldBytes) {
+ DLOG(INFO) << "ECDH shared key is invalid.";
+ return false;
+ }
+
+ out_result->assign(reinterpret_cast<char*>(key_data->data), key_data->len);
+ return true;
+}
+
+StringPiece P256KeyExchange::public_value() const {
+ return StringPiece(reinterpret_cast<const char*>(public_key_),
+ sizeof(public_key_));
+}
+
+QuicTag P256KeyExchange::tag() const { return kP256; }
+
+} // namespace net
+
diff --git a/chromium/net/quic/crypto/p256_key_exchange_openssl.cc b/chromium/net/quic/crypto/p256_key_exchange_openssl.cc
new file mode 100644
index 00000000000..6eaa981a86d
--- /dev/null
+++ b/chromium/net/quic/crypto/p256_key_exchange_openssl.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2013 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/quic/crypto/p256_key_exchange.h"
+
+#include <openssl/ec.h>
+#include <openssl/ecdh.h>
+#include <openssl/evp.h>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+P256KeyExchange::P256KeyExchange(EC_KEY* private_key, const uint8* public_key)
+ : private_key_(private_key) {
+ memcpy(public_key_, public_key, sizeof(public_key_));
+}
+
+P256KeyExchange::~P256KeyExchange() {}
+
+// static
+P256KeyExchange* P256KeyExchange::New(StringPiece key) {
+ if (key.empty()) {
+ DLOG(INFO) << "Private key is empty";
+ return NULL;
+ }
+
+ const uint8* keyp = reinterpret_cast<const uint8*>(key.data());
+ crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> private_key(
+ d2i_ECPrivateKey(NULL, &keyp, key.size()));
+ if (!private_key.get() || !EC_KEY_check_key(private_key.get())) {
+ DLOG(INFO) << "Private key is invalid.";
+ return NULL;
+ }
+
+ uint8 public_key[kUncompressedP256PointBytes];
+ if (EC_POINT_point2oct(EC_KEY_get0_group(private_key.get()),
+ EC_KEY_get0_public_key(private_key.get()),
+ POINT_CONVERSION_UNCOMPRESSED, public_key,
+ sizeof(public_key), NULL) != sizeof(public_key)) {
+ DLOG(INFO) << "Can't get public key.";
+ return NULL;
+ }
+
+ return new P256KeyExchange(private_key.release(), public_key);
+}
+
+// static
+string P256KeyExchange::NewPrivateKey() {
+ crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> key(
+ EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ if (!key.get() || !EC_KEY_generate_key(key.get())) {
+ DLOG(INFO) << "Can't generate a new private key.";
+ return string();
+ }
+
+ int key_len = i2d_ECPrivateKey(key.get(), NULL);
+ if (key_len <= 0) {
+ DLOG(INFO) << "Can't convert private key to string";
+ return string();
+ }
+ scoped_ptr<uint8[]> private_key(new uint8[key_len]);
+ uint8* keyp = private_key.get();
+ if (!i2d_ECPrivateKey(key.get(), &keyp)) {
+ DLOG(INFO) << "Can't convert private key to string.";
+ return string();
+ }
+ return string(reinterpret_cast<char*>(private_key.get()), key_len);
+}
+
+KeyExchange* P256KeyExchange::NewKeyPair(QuicRandom* /*rand*/) const {
+ // TODO(agl): avoid the serialisation/deserialisation in this function.
+ const string private_value = NewPrivateKey();
+ return P256KeyExchange::New(private_value);
+}
+
+bool P256KeyExchange::CalculateSharedKey(const StringPiece& peer_public_value,
+ string* out_result) const {
+ if (peer_public_value.size() != kUncompressedP256PointBytes) {
+ DLOG(INFO) << "Peer public value is invalid";
+ return false;
+ }
+
+ crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free> point(
+ EC_POINT_new(EC_KEY_get0_group(private_key_.get())));
+ if (!point.get() ||
+ !EC_POINT_oct2point( /* also test if point is on curve */
+ EC_KEY_get0_group(private_key_.get()),
+ point.get(),
+ reinterpret_cast<const uint8*>(peer_public_value.data()),
+ peer_public_value.size(), NULL)) {
+ DLOG(INFO) << "Can't convert peer public value to curve point.";
+ return false;
+ }
+
+ uint8 result[kP256FieldBytes];
+ if (ECDH_compute_key(result, sizeof(result), point.get(), private_key_.get(),
+ NULL) != sizeof(result)) {
+ DLOG(INFO) << "Can't compute ECDH shared key.";
+ return false;
+ }
+
+ out_result->assign(reinterpret_cast<char*>(result), sizeof(result));
+ return true;
+}
+
+StringPiece P256KeyExchange::public_value() const {
+ return StringPiece(reinterpret_cast<const char*>(public_key_),
+ sizeof(public_key_));
+}
+
+QuicTag P256KeyExchange::tag() const { return kP256; }
+
+} // namespace net
+
diff --git a/chromium/net/quic/crypto/p256_key_exchange_test.cc b/chromium/net/quic/crypto/p256_key_exchange_test.cc
new file mode 100644
index 00000000000..8ea59dacdbe
--- /dev/null
+++ b/chromium/net/quic/crypto/p256_key_exchange_test.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2013 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/quic/crypto/p256_key_exchange.h"
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+
+// SharedKey just tests that the basic key exchange identity holds: that both
+// parties end up with the same key.
+TEST(P256KeyExchange, SharedKey) {
+ for (int i = 0; i < 5; i++) {
+ string alice_private(P256KeyExchange::NewPrivateKey());
+ string bob_private(P256KeyExchange::NewPrivateKey());
+
+ ASSERT_FALSE(alice_private.empty());
+ ASSERT_FALSE(bob_private.empty());
+ ASSERT_NE(alice_private, bob_private);
+
+ scoped_ptr<P256KeyExchange> alice(P256KeyExchange::New(alice_private));
+ scoped_ptr<P256KeyExchange> bob(P256KeyExchange::New(bob_private));
+
+ ASSERT_TRUE(alice.get() != NULL);
+ ASSERT_TRUE(bob.get() != NULL);
+
+ const base::StringPiece alice_public(alice->public_value());
+ const base::StringPiece bob_public(bob->public_value());
+
+ std::string alice_shared, bob_shared;
+ ASSERT_TRUE(alice->CalculateSharedKey(bob_public, &alice_shared));
+ ASSERT_TRUE(bob->CalculateSharedKey(alice_public, &bob_shared));
+ ASSERT_EQ(alice_shared, bob_shared);
+ }
+}
+
+} // namespace test
+} // namespace net
+
diff --git a/chromium/net/quic/crypto/proof_source.h b/chromium/net/quic/crypto/proof_source.h
new file mode 100644
index 00000000000..ba5087b0f61
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_source.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_PROOF_SOURCE_H_
+#define NET_QUIC_CRYPTO_PROOF_SOURCE_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// ProofSource is an interface by which a QUIC server can obtain certificate
+// chains and signatures that prove its identity.
+class NET_EXPORT_PRIVATE ProofSource {
+ public:
+ virtual ~ProofSource() {}
+
+ // GetProof finds a certificate chain for |hostname|, sets |out_certs| to
+ // point to it (in leaf-first order), calculates a signature of
+ // |server_config| using that chain and puts the result in |out_signature|.
+ //
+ // The signature uses SHA-256 as the hash function and PSS padding when the
+ // key is RSA.
+ //
+ // The signature uses SHA-256 as the hash function when the key is ECDSA.
+ //
+ // |version| is the QUIC version for the connection. TODO(wtc): Remove once
+ // QUIC_VERSION_7 and before are removed.
+ //
+ // If |ecdsa_ok| is true, the signature may use an ECDSA key. Otherwise, the
+ // signature must use an RSA key.
+ //
+ // |out_certs| is a pointer to a pointer, not a pointer to an array.
+ //
+ // The number of certificate chains is expected to be small and fixed thus
+ // the ProofSource retains ownership of the contents of |out_certs|. The
+ // expectation is that they will be cached forever.
+ //
+ // The signature values should be cached because |server_config| will be
+ // somewhat static. However, since they aren't bounded, the ProofSource may
+ // wish to evicit entries from that cache, thus the caller takes ownership of
+ // |*out_signature|.
+ //
+ // |hostname| may be empty to signify that a default certificate should be
+ // used.
+ //
+ // This function may be called concurrently.
+ virtual bool GetProof(QuicVersion version,
+ const std::string& hostname,
+ const std::string& server_config,
+ bool ecdsa_ok,
+ const std::vector<std::string>** out_certs,
+ std::string* out_signature) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_PROOF_SOURCE_H_
diff --git a/chromium/net/quic/crypto/proof_source_chromium.cc b/chromium/net/quic/crypto/proof_source_chromium.cc
new file mode 100644
index 00000000000..4c1fe263b62
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_source_chromium.cc
@@ -0,0 +1,24 @@
+// Copyright 2013 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/quic/crypto/proof_source_chromium.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+
+ProofSourceChromium::ProofSourceChromium() {
+}
+
+bool ProofSourceChromium::GetProof(QuicVersion version,
+ const string& hostname,
+ const string& server_config,
+ bool ecdsa_ok,
+ const vector<string>** out_certs,
+ string* out_signature) {
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/proof_source_chromium.h b/chromium/net/quic/crypto/proof_source_chromium.h
new file mode 100644
index 00000000000..2b93e2d9a4c
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_source_chromium.h
@@ -0,0 +1,39 @@
+// Copyright 2013 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_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
+#define NET_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/quic/crypto/proof_source.h"
+
+namespace net {
+
+// ProofSourceChromium implements the QUIC ProofSource interface.
+// TODO(rtenneti): implement details of this class.
+class NET_EXPORT_PRIVATE ProofSourceChromium : public ProofSource {
+ public:
+ ProofSourceChromium();
+ virtual ~ProofSourceChromium() {}
+
+ // ProofSource interface
+ virtual bool GetProof(QuicVersion version,
+ const std::string& hostname,
+ const std::string& server_config,
+ bool ecdsa_ok,
+ const std::vector<std::string>** out_certs,
+ std::string* out_signature) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProofSourceChromium);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_PROOF_SOURCE_CHROMIUM_H_
diff --git a/chromium/net/quic/crypto/proof_test.cc b/chromium/net/quic/crypto/proof_test.cc
new file mode 100644
index 00000000000..97b0dcb4ea9
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_test.cc
@@ -0,0 +1,443 @@
+// Copyright (c) 2013 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 "base/files/file_path.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_source.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class ProofTest : public ::testing::TestWithParam<QuicVersion> {
+ protected:
+ ProofTest() {
+ version_ = GetParam();
+ }
+
+ QuicVersion version_;
+};
+
+// Run all ProofTests with QUIC versions 7 and 8.
+INSTANTIATE_TEST_CASE_P(ProofTests,
+ ProofTest,
+ ::testing::Values(QUIC_VERSION_7, QUIC_VERSION_8));
+
+TEST_P(ProofTest, Verify) {
+ // TODO(rtenneti): Enable testing of ProofVerifier.
+#if 0
+ scoped_ptr<ProofSource> source(CryptoTestUtils::ProofSourceForTesting());
+ scoped_ptr<ProofVerifier> verifier(
+ CryptoTestUtils::ProofVerifierForTesting());
+
+ const string server_config = "server config bytes";
+ const string hostname = "test.example.com";
+ const vector<string>* certs;
+ const vector<string>* first_certs;
+ string error_details, signature, first_signature;
+ CertVerifyResult cert_verify_result;
+
+ ASSERT_TRUE(source->GetProof(version_, hostname, server_config,
+ false /* no ECDSA */, &first_certs,
+ &first_signature));
+ ASSERT_TRUE(source->GetProof(version_, hostname, server_config,
+ false /* no ECDSA */, &certs, &signature));
+
+ // Check that the proof source is caching correctly:
+ ASSERT_EQ(first_certs, certs);
+ ASSERT_EQ(signature, first_signature);
+
+ int rv;
+ TestCompletionCallback callback;
+ rv = verifier->VerifyProof(version_, hostname, server_config, *certs,
+ signature, &error_details, &cert_verify_result,
+ callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(OK, rv);
+ ASSERT_EQ("", error_details);
+ ASSERT_FALSE(IsCertStatusError(cert_verify_result.cert_status));
+
+ rv = verifier->VerifyProof(version_, "foo.com", server_config, *certs,
+ signature, &error_details, &cert_verify_result,
+ callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(ERR_FAILED, rv);
+ ASSERT_NE("", error_details);
+
+ rv = verifier->VerifyProof(version_, hostname,
+ server_config.substr(1, string::npos),
+ *certs, signature, &error_details,
+ &cert_verify_result, callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(ERR_FAILED, rv);
+ ASSERT_NE("", error_details);
+
+ const string corrupt_signature = "1" + signature;
+ rv = verifier->VerifyProof(version_, hostname, server_config, *certs,
+ corrupt_signature, &error_details,
+ &cert_verify_result, callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(ERR_FAILED, rv);
+ ASSERT_NE("", error_details);
+
+ vector<string> wrong_certs;
+ for (size_t i = 1; i < certs->size(); i++) {
+ wrong_certs.push_back((*certs)[i]);
+ }
+ rv = verifier->VerifyProof(version_, "foo.com", server_config, wrong_certs,
+ signature, &error_details, &cert_verify_result,
+ callback.callback());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(ERR_FAILED, rv);
+ ASSERT_NE("", error_details);
+#endif // 0
+}
+
+// TestProofVerifierCallback is a simple callback for a ProofVerifier that
+// signals a TestCompletionCallback when called and stores the results from the
+// ProofVerifier in pointers passed to the constructor.
+class TestProofVerifierCallback : public ProofVerifierCallback {
+ public:
+ TestProofVerifierCallback(TestCompletionCallback* comp_callback,
+ bool* ok,
+ std::string* error_details)
+ : comp_callback_(comp_callback),
+ ok_(ok),
+ error_details_(error_details) {}
+
+ virtual void Run(bool ok,
+ const std::string& error_details,
+ scoped_ptr<ProofVerifyDetails>* details) OVERRIDE {
+ *ok_ = ok;
+ *error_details_ = error_details;
+
+ comp_callback_->callback().Run(0);
+ }
+
+ private:
+ TestCompletionCallback* const comp_callback_;
+ bool* const ok_;
+ std::string* const error_details_;
+};
+
+// RunVerification runs |verifier->VerifyProof| and asserts that the result
+// matches |expected_ok|.
+static void RunVerification(QuicVersion version,
+ ProofVerifier* verifier,
+ const std::string& hostname,
+ const std::string& server_config,
+ const vector<std::string>& certs,
+ const std::string& proof,
+ bool expected_ok) {
+ scoped_ptr<ProofVerifyDetails> details;
+ TestCompletionCallback comp_callback;
+ bool ok;
+ std::string error_details;
+ TestProofVerifierCallback* callback =
+ new TestProofVerifierCallback(&comp_callback, &ok, &error_details);
+
+ ProofVerifier::Status status = verifier->VerifyProof(
+ version, hostname, server_config, certs, proof, &error_details, &details,
+ callback);
+
+ switch (status) {
+ case ProofVerifier::FAILURE:
+ ASSERT_FALSE(expected_ok);
+ ASSERT_NE("", error_details);
+ return;
+ case ProofVerifier::SUCCESS:
+ ASSERT_TRUE(expected_ok);
+ ASSERT_EQ("", error_details);
+ return;
+ case ProofVerifier::PENDING:
+ comp_callback.WaitForResult();
+ ASSERT_EQ(expected_ok, ok);
+ break;
+ }
+}
+
+static string PEMCertFileToDER(const string& file_name) {
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> cert =
+ ImportCertFromFile(certs_dir, file_name);
+ CHECK_NE(static_cast<X509Certificate*>(NULL), cert);
+
+ string der_bytes;
+ CHECK(X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_bytes));
+ return der_bytes;
+}
+
+// A known answer test that allows us to test ProofVerifier without a working
+// ProofSource.
+TEST_P(ProofTest, VerifyRSAKnownAnswerTest) {
+ // These sample signatures were generated by running the Proof.Verify test
+ // and dumping the bytes of the |signature| output of ProofSource::GetProof().
+ // sLen = special value -2 used by OpenSSL.
+ static const unsigned char signature_data_0[] = {
+ 0x4c, 0x68, 0x3c, 0xc2, 0x1f, 0x31, 0x73, 0xa5, 0x29, 0xd3,
+ 0x56, 0x75, 0xb1, 0xbf, 0xbd, 0x31, 0x17, 0xfb, 0x2e, 0x24,
+ 0xb3, 0xc4, 0x0d, 0xfa, 0x56, 0xb8, 0x65, 0x94, 0x12, 0x38,
+ 0x6e, 0xff, 0xb3, 0x10, 0x2e, 0xf8, 0x5c, 0xc1, 0x21, 0x9d,
+ 0x29, 0x0c, 0x3a, 0x0a, 0x1a, 0xbf, 0x6b, 0x1c, 0x63, 0x77,
+ 0xf7, 0x86, 0xd3, 0xa4, 0x36, 0xf2, 0xb1, 0x6f, 0xac, 0xc3,
+ 0x23, 0x8d, 0xda, 0xe6, 0xd5, 0x83, 0xba, 0xdf, 0x28, 0x3e,
+ 0x7f, 0x4e, 0x79, 0xfc, 0xba, 0xdb, 0xf7, 0xd0, 0x4b, 0xad,
+ 0x79, 0xd0, 0xeb, 0xcf, 0xfa, 0x6e, 0x84, 0x44, 0x7a, 0x26,
+ 0xb1, 0x29, 0xa3, 0x08, 0xa8, 0x63, 0xfd, 0xed, 0x85, 0xff,
+ 0x9a, 0xe6, 0x79, 0x8b, 0xb6, 0x81, 0x13, 0x2c, 0xde, 0xe2,
+ 0xd8, 0x31, 0x29, 0xa4, 0xe0, 0x1b, 0x75, 0x2d, 0x8a, 0xf8,
+ 0x27, 0x55, 0xbc, 0xc7, 0x3b, 0x1e, 0xc1, 0x42,
+ };
+ static const unsigned char signature_data_1[] = {
+ 0xbb, 0xd1, 0x17, 0x43, 0xf3, 0x42, 0x16, 0xe9, 0xf9, 0x76,
+ 0xe6, 0xe3, 0xaa, 0x50, 0x47, 0x5f, 0x93, 0xb6, 0x7d, 0x35,
+ 0x03, 0x49, 0x0a, 0x07, 0x61, 0xd5, 0xf1, 0x9c, 0x6b, 0xaf,
+ 0xaa, 0xd7, 0x64, 0xe4, 0x0a, 0x0c, 0xab, 0x97, 0xfb, 0x4e,
+ 0x5c, 0x14, 0x08, 0xf6, 0xb9, 0xa9, 0x1d, 0xa9, 0xf8, 0x6d,
+ 0xb0, 0x2b, 0x2a, 0x0e, 0xc4, 0xd0, 0xd2, 0xe9, 0x96, 0x4f,
+ 0x44, 0x70, 0x90, 0x46, 0xb9, 0xd5, 0x89, 0x72, 0xb9, 0xa8,
+ 0xe4, 0xfb, 0x88, 0xbc, 0x69, 0x7f, 0xc9, 0xdc, 0x84, 0x87,
+ 0x18, 0x21, 0x9b, 0xde, 0x22, 0x33, 0xde, 0x16, 0x3f, 0xe6,
+ 0xfd, 0x27, 0x56, 0xd3, 0xa4, 0x97, 0x91, 0x65, 0x1a, 0xe7,
+ 0x5e, 0x80, 0x9a, 0xbf, 0xbf, 0x1a, 0x29, 0x8a, 0xbe, 0xa2,
+ 0x8c, 0x9c, 0x23, 0xf4, 0xcb, 0xba, 0x79, 0x31, 0x28, 0xab,
+ 0x77, 0x94, 0x92, 0xb2, 0xc2, 0x35, 0xb2, 0xfa,
+ };
+ static const unsigned char signature_data_2[] = {
+ 0x7e, 0x17, 0x01, 0xcb, 0x76, 0x9e, 0x9f, 0xce, 0xeb, 0x66,
+ 0x3e, 0xaa, 0xc9, 0x36, 0x5b, 0x7e, 0x48, 0x25, 0x99, 0xf8,
+ 0x0d, 0xe1, 0xa8, 0x48, 0x93, 0x3c, 0xe8, 0x97, 0x2e, 0x98,
+ 0xd6, 0x73, 0x0f, 0xd0, 0x74, 0x9c, 0x17, 0xef, 0xee, 0xf8,
+ 0x0e, 0x2a, 0x27, 0x3f, 0xc6, 0x55, 0xc6, 0xb9, 0xfe, 0x17,
+ 0xcc, 0xeb, 0x5d, 0xa1, 0xdc, 0xbd, 0x64, 0xd9, 0x5e, 0xec,
+ 0x57, 0x9d, 0xc3, 0xdc, 0x11, 0xbf, 0x23, 0x02, 0x58, 0xc4,
+ 0xf1, 0x18, 0xc1, 0x6f, 0x3f, 0xef, 0x18, 0x4d, 0xa6, 0x1e,
+ 0xe8, 0x25, 0x32, 0x8f, 0x92, 0x1e, 0xad, 0xbc, 0xbe, 0xde,
+ 0x83, 0x2a, 0x92, 0xd5, 0x59, 0x6f, 0xe4, 0x95, 0x6f, 0xe6,
+ 0xb1, 0xf9, 0xaf, 0x3f, 0xdb, 0x69, 0x6f, 0xae, 0xa6, 0x36,
+ 0xd2, 0x50, 0x81, 0x78, 0x41, 0x13, 0x2c, 0x65, 0x9c, 0x9e,
+ 0xf4, 0xd2, 0xd5, 0x58, 0x5b, 0x8b, 0x87, 0xcf,
+ };
+ static const unsigned char signature_data_4[] = {
+ 0x9e, 0xe6, 0x74, 0x3b, 0x8f, 0xb8, 0x66, 0x77, 0x57, 0x09,
+ 0x8a, 0x04, 0xe9, 0xf0, 0x7c, 0x91, 0xa9, 0x5c, 0xe9, 0xdf,
+ 0x12, 0x4d, 0x23, 0x82, 0x8c, 0x29, 0x72, 0x7f, 0xc2, 0x20,
+ 0xa7, 0xb3, 0xe5, 0xbc, 0xcf, 0x3c, 0x0d, 0x8f, 0xae, 0x46,
+ 0x6a, 0xb9, 0xee, 0x0c, 0xe1, 0x13, 0x21, 0xc0, 0x7e, 0x45,
+ 0x24, 0x24, 0x4b, 0x72, 0x43, 0x5e, 0xc4, 0x0d, 0xdf, 0x6c,
+ 0xd8, 0xaa, 0x35, 0x97, 0x05, 0x40, 0x76, 0xd3, 0x2c, 0xee,
+ 0x82, 0x16, 0x6a, 0x43, 0xf9, 0xa2, 0xd0, 0x41, 0x3c, 0xed,
+ 0x3f, 0x40, 0x10, 0x95, 0xc7, 0xa9, 0x1f, 0x04, 0xdb, 0xd5,
+ 0x98, 0x9f, 0xe2, 0xbf, 0x77, 0x3d, 0xc9, 0x9a, 0xaf, 0xf7,
+ 0xef, 0x63, 0x0b, 0x7d, 0xc8, 0x37, 0xda, 0x37, 0x23, 0x88,
+ 0x78, 0xc8, 0x8b, 0xf5, 0xb9, 0x36, 0x5d, 0x72, 0x1f, 0xfc,
+ 0x14, 0xff, 0xa7, 0x81, 0x27, 0x49, 0xae, 0xe1,
+ };
+ static const unsigned char signature_data_5[] = {
+ 0x5e, 0xc2, 0xab, 0x6b, 0x16, 0xe6, 0x55, 0xf3, 0x16, 0x46,
+ 0x35, 0xdc, 0xcc, 0xde, 0xd0, 0xbd, 0x6c, 0x66, 0xb2, 0x3d,
+ 0xd3, 0x14, 0x78, 0xed, 0x47, 0x55, 0xfb, 0xdb, 0xe1, 0x7d,
+ 0xbf, 0x31, 0xf6, 0xf4, 0x10, 0x4c, 0x8d, 0x22, 0x17, 0xaa,
+ 0xe1, 0x85, 0xc7, 0x96, 0x4c, 0x42, 0xfb, 0xf4, 0x63, 0x53,
+ 0x8a, 0x79, 0x01, 0x63, 0x48, 0xa8, 0x3a, 0xbc, 0xc9, 0xd2,
+ 0xf5, 0xec, 0xe9, 0x09, 0x71, 0xaf, 0xce, 0x34, 0x56, 0xe5,
+ 0x00, 0xbe, 0xee, 0x3c, 0x1c, 0xc4, 0xa0, 0x07, 0xd5, 0x77,
+ 0xb8, 0x83, 0x57, 0x7d, 0x1a, 0xc9, 0xd0, 0xc0, 0x59, 0x9a,
+ 0x88, 0x19, 0x3f, 0xb9, 0xf0, 0x45, 0x37, 0xc3, 0x00, 0x8b,
+ 0xb3, 0x89, 0xf4, 0x89, 0x07, 0xa9, 0xc3, 0x26, 0xbf, 0x81,
+ 0xaf, 0x6b, 0x47, 0xbc, 0x16, 0x55, 0x37, 0x0a, 0xbe, 0x0e,
+ 0xc5, 0x75, 0x3f, 0x3d, 0x8e, 0xe8, 0x44, 0xe3,
+ };
+ static const unsigned char signature_data_6[] = {
+ 0x8e, 0x5c, 0x78, 0x63, 0x74, 0x99, 0x2e, 0x96, 0xc0, 0x14,
+ 0x8d, 0xb5, 0x13, 0x74, 0xa3, 0xa4, 0xe0, 0x43, 0x3e, 0x85,
+ 0xba, 0x8f, 0x3c, 0x5e, 0x14, 0x64, 0x0e, 0x5e, 0xff, 0x89,
+ 0x88, 0x8a, 0x65, 0xe2, 0xa2, 0x79, 0xe4, 0xe9, 0x3a, 0x7f,
+ 0xf6, 0x9d, 0x3d, 0xe2, 0xb0, 0x8a, 0x35, 0x55, 0xed, 0x21,
+ 0xee, 0x20, 0xd8, 0x8a, 0x60, 0x47, 0xca, 0x52, 0x54, 0x91,
+ 0x99, 0x69, 0x8d, 0x16, 0x34, 0x69, 0xe1, 0x46, 0x56, 0x67,
+ 0x5f, 0x50, 0xf0, 0x94, 0xe7, 0x8b, 0xf2, 0x6a, 0x73, 0x0f,
+ 0x30, 0x30, 0xde, 0x59, 0xdc, 0xc7, 0xfe, 0xb6, 0x83, 0xe1,
+ 0x86, 0x1d, 0x88, 0xd3, 0x2f, 0x2f, 0x74, 0x68, 0xbd, 0x6c,
+ 0xd1, 0x46, 0x76, 0x06, 0xa9, 0xd4, 0x03, 0x3f, 0xda, 0x7d,
+ 0xa7, 0xff, 0x48, 0xe4, 0xb4, 0x42, 0x06, 0xac, 0x19, 0x12,
+ 0xe6, 0x05, 0xae, 0xbe, 0x29, 0x94, 0x8f, 0x99,
+ };
+
+ scoped_ptr<ProofVerifier> verifier(
+ CryptoTestUtils::ProofVerifierForTesting());
+
+ const string server_config = "server config bytes";
+ const string hostname = "test.example.com";
+ string error_details;
+ CertVerifyResult cert_verify_result;
+
+ vector<string> certs(2);
+ certs[0] = PEMCertFileToDER("quic_test.example.com.crt");
+ certs[1] = PEMCertFileToDER("quic_intermediate.crt");
+
+ // Signatures are nondeterministic, so we test multiple signatures on the
+ // same server_config.
+ vector<string> signatures(3);
+ if (version_ < QUIC_VERSION_8) {
+ signatures[0].assign(reinterpret_cast<const char*>(signature_data_0),
+ sizeof(signature_data_0));
+ signatures[1].assign(reinterpret_cast<const char*>(signature_data_1),
+ sizeof(signature_data_1));
+ signatures[2].assign(reinterpret_cast<const char*>(signature_data_2),
+ sizeof(signature_data_2));
+ } else {
+ signatures[0].assign(reinterpret_cast<const char*>(signature_data_4),
+ sizeof(signature_data_4));
+ signatures[1].assign(reinterpret_cast<const char*>(signature_data_5),
+ sizeof(signature_data_5));
+ signatures[2].assign(reinterpret_cast<const char*>(signature_data_6),
+ sizeof(signature_data_6));
+ }
+
+ for (size_t i = 0; i < signatures.size(); i++) {
+ const string& signature = signatures[i];
+
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, certs, signature,
+ true);
+ RunVerification(
+ version_, verifier.get(), "foo.com", server_config, certs, signature,
+ false);
+ RunVerification(
+ version_, verifier.get(), hostname,
+ server_config.substr(1, string::npos), certs, signature, false);
+
+ const string corrupt_signature = "1" + signature;
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, certs,
+ corrupt_signature, false);
+
+ vector<string> wrong_certs;
+ for (size_t i = 1; i < certs.size(); i++) {
+ wrong_certs.push_back(certs[i]);
+ }
+ RunVerification(version_, verifier.get(), hostname, server_config,
+ wrong_certs, signature, false);
+ }
+}
+
+// A known answer test that allows us to test ProofVerifier without a working
+// ProofSource.
+TEST_P(ProofTest, VerifyECDSAKnownAnswerTest) {
+ // Disable this test on platforms that do not support ECDSA certificates.
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return;
+#endif
+
+ // These sample signatures were generated by running the Proof.Verify test
+ // (modified to use ECDSA for signing proofs) and dumping the bytes of the
+ // |signature| output of ProofSource::GetProof().
+ static const unsigned char signature_data_0[] = {
+ 0x30, 0x45, 0x02, 0x20, 0x15, 0xb7, 0x9f, 0xe3, 0xd9, 0x7a,
+ 0x3c, 0x3b, 0x18, 0xb0, 0xdb, 0x60, 0x23, 0x56, 0xa0, 0x06,
+ 0x4e, 0x70, 0xa3, 0xf7, 0x4b, 0xe5, 0x0d, 0x69, 0xf0, 0x35,
+ 0x8c, 0xae, 0xb5, 0x54, 0x32, 0xe9, 0x02, 0x21, 0x00, 0xf7,
+ 0xe3, 0x06, 0x99, 0x16, 0x56, 0x7e, 0xab, 0x33, 0x53, 0x0d,
+ 0xde, 0xbe, 0xef, 0x6d, 0xb0, 0xc7, 0xa6, 0x63, 0xaf, 0x8d,
+ 0xab, 0x34, 0xa9, 0xc0, 0x63, 0x88, 0x47, 0x17, 0x4c, 0x4c,
+ 0x04,
+ };
+ static const unsigned char signature_data_1[] = {
+ 0x30, 0x44, 0x02, 0x20, 0x69, 0x60, 0x55, 0xbb, 0x11, 0x93,
+ 0x6a, 0xdc, 0x9b, 0x61, 0x2c, 0x60, 0x19, 0xbc, 0x15, 0x55,
+ 0xcf, 0xf2, 0x8e, 0x2e, 0x27, 0x0b, 0x69, 0xef, 0x33, 0x25,
+ 0x1e, 0x5d, 0x8c, 0x00, 0x11, 0xef, 0x02, 0x20, 0x0c, 0x26,
+ 0xfe, 0x0b, 0x06, 0x8f, 0xe8, 0xe2, 0x02, 0x63, 0xe5, 0x43,
+ 0x0d, 0xc9, 0x80, 0x4d, 0xe9, 0x6f, 0x6e, 0x18, 0xdb, 0xb0,
+ 0x04, 0x2a, 0x45, 0x37, 0x1a, 0x60, 0x0e, 0xc6, 0xc4, 0x8f,
+ };
+ static const unsigned char signature_data_2[] = {
+ 0x30, 0x45, 0x02, 0x21, 0x00, 0xd5, 0x43, 0x36, 0x60, 0x50,
+ 0xce, 0xe0, 0x00, 0x51, 0x02, 0x84, 0x95, 0x51, 0x47, 0xaf,
+ 0xe4, 0xf9, 0xe1, 0x23, 0xae, 0x21, 0xb4, 0x98, 0xd1, 0xa3,
+ 0x5f, 0x3b, 0xf3, 0x6a, 0x65, 0x44, 0x6b, 0x02, 0x20, 0x30,
+ 0x7e, 0xb4, 0xea, 0xf0, 0xda, 0xdb, 0xbd, 0x38, 0xb9, 0x7a,
+ 0x5d, 0x12, 0x04, 0x0e, 0xc2, 0xf0, 0xb1, 0x0e, 0x25, 0xf8,
+ 0x0a, 0x27, 0xa3, 0x16, 0x94, 0xac, 0x1e, 0xb8, 0x6e, 0x00,
+ 0x05,
+ };
+
+ scoped_ptr<ProofVerifier> verifier(
+ CryptoTestUtils::ProofVerifierForTesting());
+
+ const string server_config = "server config bytes";
+ const string hostname = "test.example.com";
+ string error_details;
+ CertVerifyResult cert_verify_result;
+
+ vector<string> certs(2);
+ certs[0] = PEMCertFileToDER("quic_test_ecc.example.com.crt");
+ certs[1] = PEMCertFileToDER("quic_intermediate.crt");
+
+ // Signatures are nondeterministic, so we test multiple signatures on the
+ // same server_config.
+ vector<string> signatures(3);
+ signatures[0].assign(reinterpret_cast<const char*>(signature_data_0),
+ sizeof(signature_data_0));
+ signatures[1].assign(reinterpret_cast<const char*>(signature_data_1),
+ sizeof(signature_data_1));
+ signatures[2].assign(reinterpret_cast<const char*>(signature_data_2),
+ sizeof(signature_data_2));
+
+ for (size_t i = 0; i < signatures.size(); i++) {
+ const string& signature = signatures[i];
+
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, certs, signature,
+ true);
+ RunVerification(
+ version_, verifier.get(), "foo.com", server_config, certs, signature,
+ false);
+ RunVerification(
+ version_, verifier.get(), hostname,
+ server_config.substr(1, string::npos), certs, signature, false);
+
+ // An ECDSA signature is DER-encoded. Corrupt the last byte so that the
+ // signature can still be DER-decoded correctly.
+ string corrupt_signature = signature;
+ corrupt_signature[corrupt_signature.size() - 1] += 1;
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, certs,
+ corrupt_signature, false);
+
+ // Prepending a "1" makes the DER invalid.
+ const string bad_der_signature1 = "1" + signature;
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, certs,
+ bad_der_signature1, false);
+
+ vector<string> wrong_certs;
+ for (size_t i = 1; i < certs.size(); i++) {
+ wrong_certs.push_back(certs[i]);
+ }
+ RunVerification(
+ version_, verifier.get(), hostname, server_config, wrong_certs,
+ signature, false);
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/proof_verifier.cc b/chromium/net/quic/crypto/proof_verifier.cc
new file mode 100644
index 00000000000..7bccba2bdb8
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_verifier.cc
@@ -0,0 +1,15 @@
+// Copyright 2013 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/quic/crypto/proof_verifier.h"
+
+namespace net {
+
+ProofVerifyDetails::~ProofVerifyDetails() {}
+
+ProofVerifierCallback::~ProofVerifierCallback() {}
+
+ProofVerifier::~ProofVerifier() {}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/proof_verifier.h b/chromium/net/quic/crypto/proof_verifier.h
new file mode 100644
index 00000000000..ecab113e694
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_verifier.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_PROOF_VERIFIER_H_
+#define NET_QUIC_CRYPTO_PROOF_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CertVerifyResult;
+
+// ProofVerifyDetails is an abstract class that acts as a container for any
+// implementation specific details that a ProofVerifier wishes to return. These
+// details are saved in the CachedInfo for the origin in question.
+class ProofVerifyDetails {
+ public:
+ virtual ~ProofVerifyDetails();
+};
+
+// ProofVerifierCallback provides a generic mechanism for a ProofVerifier to
+// call back after an asynchronous verification.
+class NET_EXPORT_PRIVATE ProofVerifierCallback {
+ public:
+ virtual ~ProofVerifierCallback();
+
+ // Run is called on the original thread to mark the completion of an
+ // asynchonous verification. If |ok| is true then the certificate is valid
+ // and |*error_details| is unused. Otherwise, |*error_details| contains a
+ // description of the error. |details| contains implementation-specific
+ // details of the verification. |Run| may take ownership of |details| by
+ // calling |release| on it.
+ virtual void Run(bool ok,
+ const std::string& error_details,
+ scoped_ptr<ProofVerifyDetails>* details) = 0;
+};
+
+// A ProofVerifier checks the signature on a server config, and the certificate
+// chain that backs the public key.
+class NET_EXPORT_PRIVATE ProofVerifier {
+ public:
+ // Status enumerates the possible results of verifying a proof.
+ enum Status {
+ SUCCESS = 0,
+ FAILURE = 1,
+ // PENDING results from a verification which will occur asynchonously. When
+ // the verification is complete, |callback|'s |Run| method will be called.
+ PENDING = 2,
+ };
+
+ virtual ~ProofVerifier();
+
+ // VerifyProof checks that |signature| is a valid signature of
+ // |server_config| by the public key in the leaf certificate of |certs|, and
+ // that |certs| is a valid chain for |hostname|. On success, it returns
+ // SUCCESS. On failure, it returns ERROR and sets |*error_details| to a
+ // description of the problem. In either case it may set |*details|, which the
+ // caller takes ownership of.
+ //
+ // This function may also return PENDING, in which case the ProofVerifier
+ // will call back, on the original thread, via |callback| when complete.
+ //
+ // This function takes ownership of |callback|. It will be deleted even if
+ // the call returns immediately.
+ //
+ // The signature uses SHA-256 as the hash function and PSS padding in the
+ // case of RSA.
+ //
+ // |version| is the QUIC version for the connection. TODO(wtc): Remove once
+ // QUIC_VERSION_7 and before are removed.
+ virtual Status VerifyProof(QuicVersion version,
+ const std::string& hostname,
+ const std::string& server_config,
+ const std::vector<std::string>& certs,
+ const std::string& signature,
+ std::string* error_details,
+ scoped_ptr<ProofVerifyDetails>* details,
+ ProofVerifierCallback* callback) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_PROOF_VERIFIER_H_
diff --git a/chromium/net/quic/crypto/proof_verifier_chromium.cc b/chromium/net/quic/crypto/proof_verifier_chromium.cc
new file mode 100644
index 00000000000..88653053f3e
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_verifier_chromium.cc
@@ -0,0 +1,259 @@
+// Copyright 2013 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/quic/crypto/proof_verifier_chromium.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/signature_verifier.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/single_request_cert_verifier.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/ssl/ssl_config_service.h"
+
+using base::StringPiece;
+using base::StringPrintf;
+using std::string;
+using std::vector;
+
+namespace net {
+
+ProofVerifierChromium::ProofVerifierChromium(CertVerifier* cert_verifier,
+ const BoundNetLog& net_log)
+ : cert_verifier_(cert_verifier),
+ next_state_(STATE_NONE),
+ net_log_(net_log) {
+}
+
+ProofVerifierChromium::~ProofVerifierChromium() {
+ verifier_.reset();
+}
+
+ProofVerifierChromium::Status ProofVerifierChromium::VerifyProof(
+ QuicVersion version,
+ const string& hostname,
+ const string& server_config,
+ const vector<string>& certs,
+ const string& signature,
+ std::string* error_details,
+ scoped_ptr<ProofVerifyDetails>* details,
+ ProofVerifierCallback* callback) {
+ DCHECK(error_details);
+ DCHECK(details);
+ DCHECK(callback);
+
+ callback_.reset(callback);
+ error_details->clear();
+
+ DCHECK_EQ(STATE_NONE, next_state_);
+ if (STATE_NONE != next_state_) {
+ *error_details = "Certificate is already set and VerifyProof has begun";
+ DLOG(WARNING) << *error_details;
+ return FAILURE;
+ }
+
+ verify_details_.reset(new ProofVerifyDetailsChromium);
+
+ if (certs.empty()) {
+ *error_details = "Failed to create certificate chain. Certs are empty.";
+ DLOG(WARNING) << *error_details;
+ verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+ details->reset(verify_details_.release());
+ return FAILURE;
+ }
+
+ // Convert certs to X509Certificate.
+ vector<StringPiece> cert_pieces(certs.size());
+ for (unsigned i = 0; i < certs.size(); i++) {
+ cert_pieces[i] = base::StringPiece(certs[i]);
+ }
+ cert_ = X509Certificate::CreateFromDERCertChain(cert_pieces);
+ if (!cert_.get()) {
+ *error_details = "Failed to create certificate chain";
+ DLOG(WARNING) << *error_details;
+ verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+ details->reset(verify_details_.release());
+ return FAILURE;
+ }
+
+ // We call VerifySignature first to avoid copying of server_config and
+ // signature.
+ if (!VerifySignature(version, server_config, signature, certs[0])) {
+ *error_details = "Failed to verify signature of server config";
+ DLOG(WARNING) << *error_details;
+ verify_details_->cert_verify_result.cert_status = CERT_STATUS_INVALID;
+ details->reset(verify_details_.release());
+ return FAILURE;
+ }
+
+ hostname_ = hostname;
+
+ next_state_ = STATE_VERIFY_CERT;
+ switch (DoLoop(OK)) {
+ case OK:
+ details->reset(verify_details_.release());
+ return SUCCESS;
+ case ERR_IO_PENDING:
+ return PENDING;
+ default:
+ *error_details = error_details_;
+ details->reset(verify_details_.release());
+ return FAILURE;
+ }
+}
+
+int ProofVerifierChromium::DoLoop(int last_result) {
+ int rv = last_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_VERIFY_CERT:
+ DCHECK(rv == OK);
+ rv = DoVerifyCert(rv);
+ break;
+ case STATE_VERIFY_CERT_COMPLETE:
+ rv = DoVerifyCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+void ProofVerifierChromium::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ scoped_ptr<ProofVerifyDetails> scoped_details(verify_details_.release());
+ callback_->Run(rv == OK, error_details_, &scoped_details);
+ callback_.reset();
+ }
+}
+
+int ProofVerifierChromium::DoVerifyCert(int result) {
+ next_state_ = STATE_VERIFY_CERT_COMPLETE;
+
+ int flags = 0;
+ verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
+ return verifier_->Verify(
+ cert_.get(),
+ hostname_,
+ flags,
+ SSLConfigService::GetCRLSet().get(),
+ &verify_details_->cert_verify_result,
+ base::Bind(&ProofVerifierChromium::OnIOComplete,
+ base::Unretained(this)),
+ net_log_);
+}
+
+int ProofVerifierChromium::DoVerifyCertComplete(int result) {
+ verifier_.reset();
+
+ if (result <= ERR_FAILED) {
+ error_details_ = StringPrintf("Failed to verify certificate chain: %s",
+ ErrorToString(result));
+ DLOG(WARNING) << error_details_;
+ result = ERR_FAILED;
+ }
+
+ // Exit DoLoop and return the result to the caller to VerifyProof.
+ DCHECK_EQ(STATE_NONE, next_state_);
+ return result;
+}
+
+bool ProofVerifierChromium::VerifySignature(QuicVersion version,
+ const string& signed_data,
+ const string& signature,
+ const string& cert) {
+ StringPiece spki;
+ if (!asn1::ExtractSPKIFromDERCert(cert, &spki)) {
+ DLOG(WARNING) << "ExtractSPKIFromDERCert failed";
+ return false;
+ }
+
+ crypto::SignatureVerifier verifier;
+
+ size_t size_bits;
+ X509Certificate::PublicKeyType type;
+ X509Certificate::GetPublicKeyInfo(cert_->os_cert_handle(), &size_bits,
+ &type);
+ if (type == X509Certificate::kPublicKeyTypeRSA) {
+ crypto::SignatureVerifier::HashAlgorithm hash_alg =
+ crypto::SignatureVerifier::SHA256;
+ crypto::SignatureVerifier::HashAlgorithm mask_hash_alg = hash_alg;
+ unsigned int hash_len = 32; // 32 is the length of a SHA-256 hash.
+ unsigned int salt_len =
+ version >= QUIC_VERSION_8 ? hash_len : signature.size() - hash_len - 2;
+
+ bool ok = verifier.VerifyInitRSAPSS(
+ hash_alg, mask_hash_alg, salt_len,
+ reinterpret_cast<const uint8*>(signature.data()), signature.size(),
+ reinterpret_cast<const uint8*>(spki.data()), spki.size());
+ if (!ok) {
+ DLOG(WARNING) << "VerifyInitRSAPSS failed";
+ return false;
+ }
+ } else if (type == X509Certificate::kPublicKeyTypeECDSA) {
+ // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
+ // RFC 5758:
+ // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+ // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
+ // ...
+ // When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
+ // ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
+ // as an AlgorithmIdentifier, the encoding MUST omit the parameters
+ // field. That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
+ // component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
+ // SHA384, or ecdsa-with-SHA512.
+ // See also RFC 5480, Appendix A.
+ static const uint8 kECDSAWithSHA256AlgorithmID[] = {
+ 0x30, 0x0a,
+ 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
+ };
+
+ if (!verifier.VerifyInit(
+ kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
+ reinterpret_cast<const uint8*>(signature.data()),
+ signature.size(),
+ reinterpret_cast<const uint8*>(spki.data()),
+ spki.size())) {
+ DLOG(WARNING) << "VerifyInit failed";
+ return false;
+ }
+ } else {
+ LOG(ERROR) << "Unsupported public key type " << type;
+ return false;
+ }
+
+ verifier.VerifyUpdate(reinterpret_cast<const uint8*>(kProofSignatureLabel),
+ sizeof(kProofSignatureLabel));
+ verifier.VerifyUpdate(reinterpret_cast<const uint8*>(signed_data.data()),
+ signed_data.size());
+
+ if (!verifier.VerifyFinal()) {
+ DLOG(WARNING) << "VerifyFinal failed";
+ return false;
+ }
+
+ DLOG(INFO) << "VerifyFinal success";
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/proof_verifier_chromium.h b/chromium/net/quic/crypto/proof_verifier_chromium.h
new file mode 100644
index 00000000000..8786e52e7dd
--- /dev/null
+++ b/chromium/net/quic/crypto/proof_verifier_chromium.h
@@ -0,0 +1,91 @@
+// Copyright 2013 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_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
+#define NET_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_verifier.h"
+
+namespace net {
+
+class CertVerifier;
+class SingleRequestCertVerifier;
+
+// ProofVerifyDetailsChromium is the implementation-specific information that a
+// ProofVerifierChromium returns about a certificate verification.
+struct ProofVerifyDetailsChromium : public ProofVerifyDetails {
+ public:
+ CertVerifyResult cert_verify_result;
+};
+
+// ProofVerifierChromium implements the QUIC ProofVerifier interface.
+// TODO(rtenneti): Add support for multiple requests for one ProofVerifier.
+class NET_EXPORT_PRIVATE ProofVerifierChromium : public ProofVerifier {
+ public:
+ ProofVerifierChromium(CertVerifier* cert_verifier,
+ const BoundNetLog& net_log);
+ virtual ~ProofVerifierChromium();
+
+ // ProofVerifier interface
+ virtual Status VerifyProof(QuicVersion version,
+ const std::string& hostname,
+ const std::string& server_config,
+ const std::vector<std::string>& certs,
+ const std::string& signature,
+ std::string* error_details,
+ scoped_ptr<ProofVerifyDetails>* details,
+ ProofVerifierCallback* callback) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_VERIFY_CERT,
+ STATE_VERIFY_CERT_COMPLETE,
+ };
+
+ int DoLoop(int last_io_result);
+ void OnIOComplete(int result);
+ int DoVerifyCert(int result);
+ int DoVerifyCertComplete(int result);
+
+ bool VerifySignature(QuicVersion version,
+ const std::string& signed_data,
+ const std::string& signature,
+ const std::string& cert);
+
+ // |cert_verifier_| and |verifier_| are used for verifying certificates.
+ CertVerifier* const cert_verifier_;
+ scoped_ptr<SingleRequestCertVerifier> verifier_;
+
+ // |hostname| specifies the hostname for which |certs| is a valid chain.
+ std::string hostname_;
+
+ scoped_ptr<ProofVerifierCallback> callback_;
+ scoped_ptr<ProofVerifyDetailsChromium> verify_details_;
+ std::string error_details_;
+
+ // X509Certificate from a chain of DER encoded certificates.
+ scoped_refptr<X509Certificate> cert_;
+
+ State next_state_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProofVerifierChromium);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_PROOF_VERIFIER_CHROMIUM_H_
diff --git a/chromium/net/quic/crypto/quic_decrypter.cc b/chromium/net/quic/crypto/quic_decrypter.cc
new file mode 100644
index 00000000000..fb19d4c0d70
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_decrypter.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/quic_decrypter.h"
+
+#include "net/quic/crypto/aes_128_gcm_12_decrypter.h"
+#include "net/quic/crypto/null_decrypter.h"
+
+namespace net {
+
+// static
+QuicDecrypter* QuicDecrypter::Create(QuicTag algorithm) {
+ switch (algorithm) {
+ case kAESG:
+ return new Aes128Gcm12Decrypter();
+ case kNULL:
+ return new NullDecrypter();
+ default:
+ LOG(FATAL) << "Unsupported algorithm: " << algorithm;
+ return NULL;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/quic_decrypter.h b/chromium/net/quic/crypto/quic_decrypter.h
new file mode 100644
index 00000000000..124d98fea8d
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_decrypter.h
@@ -0,0 +1,72 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
+#define NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
+
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicDecrypter {
+ public:
+ virtual ~QuicDecrypter() {}
+
+ static QuicDecrypter* Create(QuicTag algorithm);
+
+ // Sets the encryption key. Returns true on success, false on failure.
+ //
+ // NOTE: The key is the client_write_key or server_write_key derived from
+ // the master secret.
+ virtual bool SetKey(base::StringPiece key) = 0;
+
+ // Sets the fixed initial bytes of the nonce. Returns true on success,
+ // false on failure.
+ //
+ // NOTE: The nonce prefix is the client_write_iv or server_write_iv
+ // derived from the master secret. A 64-bit packet sequence number will
+ // be appended to form the nonce.
+ //
+ // <------------ 64 bits ----------->
+ // +---------------------+----------------------------------+
+ // | Fixed prefix | Packet sequence number |
+ // +---------------------+----------------------------------+
+ // Nonce format
+ //
+ // The security of the nonce format requires that QUIC never reuse a
+ // packet sequence number, even when retransmitting a lost packet.
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) = 0;
+
+ // Decrypt authenticates |associated_data| and |ciphertext| and then decrypts
+ // |ciphertext| into |output|, using |nonce|. |nonce| must be 8 bytes longer
+ // than the nonce prefix length returned by GetNoncePrefixSize() (of the
+ // encrypter). |output| must be as long as |ciphertext| on entry and, on
+ // successful return, the true length of the plaintext will be written to
+ // |*output_length|.
+ virtual bool Decrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) = 0;
+
+ // Returns a newly created QuicData object containing the decrypted
+ // |ciphertext| or NULL if there is an error. |sequence_number| is
+ // appended to the |nonce_prefix| value provided in SetNoncePrefix()
+ // to form the nonce.
+ // TODO(wtc): add a way for DecryptPacket to report decryption failure due
+ // to non-authentic inputs, as opposed to other reasons for failure.
+ virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece ciphertext) = 0;
+
+ // For use by unit tests only.
+ virtual base::StringPiece GetKey() const = 0;
+ virtual base::StringPiece GetNoncePrefix() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_QUIC_DECRYPTER_H_
diff --git a/chromium/net/quic/crypto/quic_encrypter.cc b/chromium/net/quic/crypto/quic_encrypter.cc
new file mode 100644
index 00000000000..489da8ed2ec
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_encrypter.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/quic_encrypter.h"
+
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/null_encrypter.h"
+
+namespace net {
+
+// static
+QuicEncrypter* QuicEncrypter::Create(QuicTag algorithm) {
+ switch (algorithm) {
+ case kAESG:
+ return new Aes128Gcm12Encrypter();
+ case kNULL:
+ return new NullEncrypter();
+ default:
+ LOG(FATAL) << "Unsupported algorithm: " << algorithm;
+ return NULL;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/quic_encrypter.h b/chromium/net/quic/crypto/quic_encrypter.h
new file mode 100644
index 00000000000..edddf3628b6
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_encrypter.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
+#define NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
+
+#include "net/base/net_export.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicEncrypter {
+ public:
+ virtual ~QuicEncrypter() {}
+
+ static QuicEncrypter* Create(QuicTag algorithm);
+
+ // Sets the encryption key. Returns true on success, false on failure.
+ //
+ // NOTE: The key is the client_write_key or server_write_key derived from
+ // the master secret.
+ virtual bool SetKey(base::StringPiece key) = 0;
+
+ // Sets the fixed initial bytes of the nonce. Returns true on success,
+ // false on failure.
+ //
+ // NOTE: The nonce prefix is the client_write_iv or server_write_iv
+ // derived from the master secret. A 64-bit packet sequence number will
+ // be appended to form the nonce.
+ //
+ // <------------ 64 bits ----------->
+ // +---------------------+----------------------------------+
+ // | Fixed prefix | Packet sequence number |
+ // +---------------------+----------------------------------+
+ // Nonce format
+ //
+ // The security of the nonce format requires that QUIC never reuse a
+ // packet sequence number, even when retransmitting a lost packet.
+ virtual bool SetNoncePrefix(base::StringPiece nonce_prefix) = 0;
+
+ // Encrypt encrypts |plaintext| and writes the ciphertext, plus a MAC over
+ // both |associated_data| and |plaintext| to |output|, using |nonce| as the
+ // nonce. |nonce| must be |8+GetNoncePrefixSize()| bytes long and |output|
+ // must point to a buffer that is at least
+ // |GetCiphertextSize(plaintext.size()| bytes long.
+ virtual bool Encrypt(base::StringPiece nonce,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext,
+ unsigned char* output) = 0;
+
+ // Returns a newly created QuicData object containing the encrypted
+ // |plaintext| as well as a MAC over both |plaintext| and |associated_data|,
+ // or NULL if there is an error. |sequence_number| is appended to the
+ // |nonce_prefix| value provided in SetNoncePrefix() to form the nonce.
+ virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+ base::StringPiece associated_data,
+ base::StringPiece plaintext) = 0;
+
+ // GetKeySize() and GetNoncePrefixSize() tell the HKDF class how many bytes
+ // of key material needs to be derived from the master secret.
+ // NOTE: the sizes returned by GetKeySize() and GetNoncePrefixSize() are
+ // also correct for the QuicDecrypter of the same algorithm. So only
+ // QuicEncrypter has these two methods.
+
+ // Returns the size in bytes of a key for the algorithm.
+ virtual size_t GetKeySize() const = 0;
+ // Returns the size in bytes of the fixed initial part of the nonce.
+ virtual size_t GetNoncePrefixSize() const = 0;
+
+ // Returns the maximum length of plaintext that can be encrypted
+ // to ciphertext no larger than |ciphertext_size|.
+ virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const = 0;
+
+ // Returns the length of the ciphertext that would be generated by encrypting
+ // to plaintext of size |plaintext_size|.
+ virtual size_t GetCiphertextSize(size_t plaintext_size) const = 0;
+
+ // For use by unit tests only.
+ virtual base::StringPiece GetKey() const = 0;
+ virtual base::StringPiece GetNoncePrefix() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_QUIC_ENCRYPTER_H_
diff --git a/chromium/net/quic/crypto/quic_random.cc b/chromium/net/quic/crypto/quic_random.cc
new file mode 100644
index 00000000000..c96f01a9139
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_random.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/quic_random.h"
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "crypto/random.h"
+
+namespace net {
+
+namespace {
+
+class DefaultRandom : public QuicRandom {
+ public:
+ static DefaultRandom* GetInstance();
+
+ // QuicRandom implementation
+ virtual void RandBytes(void* data, size_t len) OVERRIDE;
+ virtual uint64 RandUint64() OVERRIDE;
+ virtual bool RandBool() OVERRIDE;
+ virtual void Reseed(const void* additional_entropy,
+ size_t entropy_len) OVERRIDE;
+
+ private:
+ DefaultRandom();
+ virtual ~DefaultRandom() {}
+
+ friend struct DefaultSingletonTraits<DefaultRandom>;
+ DISALLOW_COPY_AND_ASSIGN(DefaultRandom);
+};
+
+DefaultRandom* DefaultRandom::GetInstance() {
+ return Singleton<DefaultRandom>::get();
+}
+
+void DefaultRandom::RandBytes(void* data, size_t len) {
+ crypto::RandBytes(data, len);
+}
+
+uint64 DefaultRandom::RandUint64() {
+ uint64 value;
+ RandBytes(&value, sizeof(value));
+ return value;
+}
+
+bool DefaultRandom::RandBool() {
+ char value;
+ RandBytes(&value, sizeof(value));
+ return (value & 1) == 1;
+}
+
+void DefaultRandom::Reseed(const void* additional_entropy, size_t entropy_len) {
+ // No such function exists in crypto/random.h.
+}
+
+DefaultRandom::DefaultRandom() {
+}
+
+} // namespace
+
+// static
+QuicRandom* QuicRandom::GetInstance() { return DefaultRandom::GetInstance(); }
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/quic_random.h b/chromium/net/quic/crypto/quic_random.h
new file mode 100644
index 00000000000..68640c1695d
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_random.h
@@ -0,0 +1,41 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_CRYPTO_QUIC_RANDOM_H_
+#define NET_QUIC_CRYPTO_QUIC_RANDOM_H_
+
+#include <stddef.h>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// The interface for a random number generator.
+class NET_EXPORT_PRIVATE QuicRandom {
+ public:
+ virtual ~QuicRandom() {}
+
+ // Returns the default random number generator, which is cryptographically
+ // secure and thread-safe.
+ static QuicRandom* GetInstance();
+
+ // Generates |len| random bytes in the |data| buffer.
+ virtual void RandBytes(void* data, size_t len) = 0;
+
+ // Returns a random number in the range [0, kuint64max].
+ virtual uint64 RandUint64() = 0;
+
+ // Returns a random boolean value.
+ virtual bool RandBool() = 0;
+
+ // Reseeds the random number generator with additional entropy input.
+ // NOTE: the constructor of a QuicRandom object is responsible for seeding
+ // itself with enough entropy input.
+ virtual void Reseed(const void* additional_entropy, size_t entropy_len) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_QUIC_RANDOM_H_
diff --git a/chromium/net/quic/crypto/quic_random_test.cc b/chromium/net/quic/crypto/quic_random_test.cc
new file mode 100644
index 00000000000..425089a10e5
--- /dev/null
+++ b/chromium/net/quic/crypto/quic_random_test.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#include "net/quic/crypto/quic_random.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicRandomTest, RandBytes) {
+ unsigned char buf1[16];
+ unsigned char buf2[16];
+ memset(buf1, 0xaf, sizeof(buf1));
+ memset(buf2, 0xaf, sizeof(buf2));
+ ASSERT_TRUE(memcmp(buf1, buf2, sizeof(buf1)) == 0);
+
+ QuicRandom* rng = QuicRandom::GetInstance();
+ rng->RandBytes(buf1, sizeof(buf1));
+ EXPECT_FALSE(memcmp(buf1, buf2, sizeof(buf1)) == 0);
+}
+
+TEST(QuicRandomTest, RandUint64) {
+ QuicRandom* rng = QuicRandom::GetInstance();
+ uint64 value1 = rng->RandUint64();
+ uint64 value2 = rng->RandUint64();
+ EXPECT_NE(value1, value2);
+}
+
+TEST(QuicRandomTest, Reseed) {
+ char buf[1024];
+ memset(buf, 0xaf, sizeof(buf));
+
+ QuicRandom* rng = QuicRandom::GetInstance();
+ rng->Reseed(buf, sizeof(buf));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/crypto/scoped_evp_cipher_ctx.cc b/chromium/net/quic/crypto/scoped_evp_cipher_ctx.cc
new file mode 100644
index 00000000000..b904f870fdd
--- /dev/null
+++ b/chromium/net/quic/crypto/scoped_evp_cipher_ctx.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2013 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/quic/crypto/scoped_evp_cipher_ctx.h"
+
+#include <openssl/evp.h>
+
+namespace net {
+
+ScopedEVPCipherCtx::ScopedEVPCipherCtx()
+ : ctx_(EVP_CIPHER_CTX_new()) { }
+
+ScopedEVPCipherCtx::~ScopedEVPCipherCtx() {
+ EVP_CIPHER_CTX_free(ctx_);
+}
+
+EVP_CIPHER_CTX* ScopedEVPCipherCtx::get() const {
+ return ctx_;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/scoped_evp_cipher_ctx.h b/chromium/net/quic/crypto/scoped_evp_cipher_ctx.h
new file mode 100644
index 00000000000..ec0fd51b92a
--- /dev/null
+++ b/chromium/net/quic/crypto/scoped_evp_cipher_ctx.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_SCOPED_EVP_CIPHER_CTX_H_
+#define NET_QUIC_CRYPTO_SCOPED_EVP_CIPHER_CTX_H_
+
+typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX;
+
+namespace net {
+
+// TODO(wtc): this is the same as the ScopedCipherCTX class defined in
+// crypto/encryptor_openssl.cc. Eliminate the duplicate code.
+// crypto::ScopedOpenSSL is not suitable for EVP_CIPHER_CTX because
+// there are no EVP_CIPHER_CTX_create and EVP_CIPHER_CTX_destroy
+// functions.
+class ScopedEVPCipherCtx {
+ public:
+ ScopedEVPCipherCtx();
+ ~ScopedEVPCipherCtx();
+
+ EVP_CIPHER_CTX* get() const;
+
+ private:
+ EVP_CIPHER_CTX* const ctx_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_SCOPED_EVP_CIPHER_CTX_H_
diff --git a/chromium/net/quic/crypto/source_address_token.cc b/chromium/net/quic/crypto/source_address_token.cc
new file mode 100644
index 00000000000..d15afebf2a7
--- /dev/null
+++ b/chromium/net/quic/crypto/source_address_token.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 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/quic/crypto/source_address_token.h"
+
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+
+SourceAddressToken::SourceAddressToken() {
+}
+
+SourceAddressToken::~SourceAddressToken() {
+}
+
+string SourceAddressToken::SerializeAsString() const {
+ return ip_ + " " + base::Int64ToString(timestamp_);
+}
+
+bool SourceAddressToken::ParseFromArray(const char* plaintext,
+ size_t plaintext_length) {
+ string data(plaintext, plaintext_length);
+ vector<string> results;
+ base::SplitString(data, ' ', &results);
+ if (results.size() < 2) {
+ return false;
+ }
+
+ int64 timestamp;
+ if (!base::StringToInt64(results[1], &timestamp)) {
+ return false;
+ }
+
+ ip_ = results[0];
+ timestamp_ = timestamp;
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/source_address_token.h b/chromium/net/quic/crypto/source_address_token.h
new file mode 100644
index 00000000000..8c509651e28
--- /dev/null
+++ b/chromium/net/quic/crypto/source_address_token.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
+#define NET_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+
+namespace net {
+
+// TODO(rtenneti): sync with server more rationally.
+class SourceAddressToken {
+ public:
+ SourceAddressToken();
+ ~SourceAddressToken();
+
+ std::string SerializeAsString() const;
+
+ bool ParseFromArray(const char* plaintext, size_t plaintext_length);
+
+ std::string ip() const {
+ return ip_;
+ }
+
+ int64 timestamp() const {
+ return timestamp_;
+ }
+
+ void set_ip(base::StringPiece ip) {
+ ip_ = ip.as_string();
+ }
+
+ void set_timestamp(int64 timestamp) {
+ timestamp_ = timestamp;
+ }
+
+ private:
+ std::string ip_;
+ int64 timestamp_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_SOURCE_ADDRESS_TOKEN_H_
diff --git a/chromium/net/quic/crypto/strike_register.cc b/chromium/net/quic/crypto/strike_register.cc
new file mode 100644
index 00000000000..97aca184cd0
--- /dev/null
+++ b/chromium/net/quic/crypto/strike_register.cc
@@ -0,0 +1,465 @@
+// Copyright (c) 2013 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/quic/crypto/strike_register.h"
+
+#include "base/logging.h"
+
+using std::pair;
+using std::set;
+using std::vector;
+
+namespace net {
+
+// static
+const uint32 StrikeRegister::kExternalNodeSize = 24;
+// static
+const uint32 StrikeRegister::kNil = (1 << 31) | 1;
+// static
+const uint32 StrikeRegister::kExternalFlag = 1 << 23;
+
+// InternalNode represents a non-leaf node in the critbit tree. See the comment
+// in the .h file for details.
+class StrikeRegister::InternalNode {
+ public:
+ void SetChild(unsigned direction, uint32 child) {
+ data_[direction] = (data_[direction] & 0xff) | (child << 8);
+ }
+
+ void SetCritByte(uint8 critbyte) {
+ data_[0] &= 0xffffff00;
+ data_[0] |= critbyte;
+ }
+
+ void SetOtherBits(uint8 otherbits) {
+ data_[1] &= 0xffffff00;
+ data_[1] |= otherbits;
+ }
+
+ void SetNextPtr(uint32 next) { data_[0] = next; }
+
+ uint32 next() const { return data_[0]; }
+
+ uint32 child(unsigned n) const { return data_[n] >> 8; }
+
+ uint8 critbyte() const { return data_[0]; }
+
+ uint8 otherbits() const { return data_[1]; }
+
+ // These bytes are organised thus:
+ // <24 bits> left child
+ // <8 bits> crit-byte
+ // <24 bits> right child
+ // <8 bits> other-bits
+ uint32 data_[2];
+};
+
+// kCreationTimeFromInternalEpoch contains the number of seconds between the
+// start of the internal epoch and |creation_time_external_|. This allows us
+// to consider times that are before |creation_time_external_|.
+static const uint32 kCreationTimeFromInternalEpoch = 63115200.0; // 2 years.
+
+StrikeRegister::StrikeRegister(unsigned max_entries,
+ uint32 current_time,
+ uint32 window_secs,
+ const uint8 orbit[8],
+ StartupType startup)
+ : max_entries_(max_entries),
+ window_secs_(window_secs),
+ // The horizon is initially set |window_secs| into the future because, if
+ // we just crashed, then we may have accepted nonces in the span
+ // [current_time...current_time+window_secs) and so we conservatively
+ // reject the whole timespan unless |startup| tells us otherwise.
+ creation_time_external_(current_time),
+ internal_epoch_(current_time > kCreationTimeFromInternalEpoch
+ ? current_time - kCreationTimeFromInternalEpoch
+ : 0),
+ horizon_(ExternalTimeToInternal(current_time) + window_secs),
+ horizon_valid_(startup == DENY_REQUESTS_AT_STARTUP) {
+ memcpy(orbit_, orbit, sizeof(orbit_));
+
+ // TODO(rtenneti): Remove the following check, Added the following to silence
+ // "is not used" error.
+ CHECK_GE(creation_time_external_, 0u);
+
+ // We only have 23 bits of index available.
+ CHECK_LT(max_entries, 1u << 23);
+ CHECK_GT(max_entries, 1u); // There must be at least two entries.
+ CHECK_EQ(sizeof(InternalNode), 8u); // in case of compiler changes.
+ internal_nodes_ = new InternalNode[max_entries];
+ external_nodes_.reset(new uint8[kExternalNodeSize * max_entries]);
+
+ Reset();
+}
+
+StrikeRegister::~StrikeRegister() { delete[] internal_nodes_; }
+
+void StrikeRegister::Reset() {
+ // Thread a free list through all of the internal nodes.
+ internal_node_free_head_ = 0;
+ for (unsigned i = 0; i < max_entries_ - 1; i++)
+ internal_nodes_[i].SetNextPtr(i + 1);
+ internal_nodes_[max_entries_ - 1].SetNextPtr(kNil);
+
+ // Also thread a free list through the external nodes.
+ external_node_free_head_ = 0;
+ for (unsigned i = 0; i < max_entries_ - 1; i++)
+ external_node_next_ptr(i) = i + 1;
+ external_node_next_ptr(max_entries_ - 1) = kNil;
+
+ // This is the root of the tree.
+ internal_node_head_ = kNil;
+}
+
+bool StrikeRegister::Insert(const uint8 nonce[32],
+ const uint32 current_time_external) {
+ const uint32 current_time = ExternalTimeToInternal(current_time_external);
+
+ // Check to see if the orbit is correct.
+ if (memcmp(nonce + sizeof(current_time), orbit_, sizeof(orbit_))) {
+ return false;
+ }
+ const uint32 nonce_time = ExternalTimeToInternal(TimeFromBytes(nonce));
+ // We have dropped one or more nonces with a time value of |horizon_|, so
+ // we have to reject anything with a timestamp less than or equal to that.
+ if (horizon_valid_ && nonce_time <= horizon_) {
+ return false;
+ }
+
+ // Check that the timestamp is in the current window.
+ if ((current_time > window_secs_ &&
+ nonce_time < (current_time - window_secs_)) ||
+ nonce_time > (current_time + window_secs_)) {
+ return false;
+ }
+
+ // We strip the orbit out of the nonce.
+ uint8 value[24];
+ memcpy(value, &nonce_time, sizeof(nonce_time));
+ memcpy(value + sizeof(nonce_time),
+ nonce + sizeof(nonce_time) + sizeof(orbit_),
+ sizeof(value) - sizeof(nonce_time));
+
+ // Find the best match to |value| in the crit-bit tree. The best match is
+ // simply the value which /could/ match |value|, if any does, so we still
+ // need a memcmp to check.
+ uint32 best_match_index = BestMatch(value);
+ if (best_match_index == kNil) {
+ // Empty tree. Just insert the new value at the root.
+ uint32 index = GetFreeExternalNode();
+ memcpy(external_node(index), value, sizeof(value));
+ internal_node_head_ = (index | kExternalFlag) << 8;
+ return true;
+ }
+
+ const uint8* best_match = external_node(best_match_index);
+ if (memcmp(best_match, value, sizeof(value)) == 0) {
+ // We found the value in the tree.
+ return false;
+ }
+
+ // We are going to insert a new entry into the tree, so get the nodes now.
+ uint32 internal_node_index = GetFreeInternalNode();
+ uint32 external_node_index = GetFreeExternalNode();
+
+ // If we just evicted the best match, then we have to try and match again.
+ // We know that we didn't just empty the tree because we require that
+ // max_entries_ >= 2. Also, we know that it doesn't match because, if it
+ // did, it would have been returned previously.
+ if (external_node_index == best_match_index) {
+ best_match_index = BestMatch(value);
+ best_match = external_node(best_match_index);
+ }
+
+ // Now we need to find the first bit where we differ from |best_match|.
+ unsigned differing_byte;
+ uint8 new_other_bits;
+ for (differing_byte = 0; differing_byte < sizeof(value); differing_byte++) {
+ new_other_bits = value[differing_byte] ^ best_match[differing_byte];
+ if (new_other_bits) {
+ break;
+ }
+ }
+
+ // Once we have the XOR the of first differing byte in new_other_bits we need
+ // to find the most significant differing bit. We could do this with a simple
+ // for loop, testing bits 7..0. Instead we fold the bits so that we end up
+ // with a byte where all the bits below the most significant one, are set.
+ new_other_bits |= new_other_bits >> 1;
+ new_other_bits |= new_other_bits >> 2;
+ new_other_bits |= new_other_bits >> 4;
+ // Now this bit trick results in all the bits set, except the original
+ // most-significant one.
+ new_other_bits = (new_other_bits & ~(new_other_bits >> 1)) ^ 255;
+
+ // Consider the effect of ORing against |new_other_bits|. If |value| did not
+ // have the critical bit set, the result is the same as |new_other_bits|. If
+ // it did, the result is all ones.
+
+ unsigned newdirection;
+ if ((new_other_bits | value[differing_byte]) == 0xff) {
+ newdirection = 1;
+ } else {
+ newdirection = 0;
+ }
+
+ memcpy(external_node(external_node_index), value, sizeof(value));
+ InternalNode* inode = &internal_nodes_[internal_node_index];
+
+ inode->SetChild(newdirection, external_node_index | kExternalFlag);
+ inode->SetCritByte(differing_byte);
+ inode->SetOtherBits(new_other_bits);
+
+ // |where_index| is a pointer to the uint32 which needs to be updated in
+ // order to insert the new internal node into the tree. The internal nodes
+ // store the child indexes in the top 24-bits of a 32-bit word and, to keep
+ // the code simple, we define that |internal_node_head_| is organised the
+ // same way.
+ DCHECK_EQ(internal_node_head_ & 0xff, 0u);
+ uint32* where_index = &internal_node_head_;
+ while (((*where_index >> 8) & kExternalFlag) == 0) {
+ InternalNode* node = &internal_nodes_[*where_index >> 8];
+ if (node->critbyte() > differing_byte) {
+ break;
+ }
+ if (node->critbyte() == differing_byte &&
+ node->otherbits() > new_other_bits) {
+ break;
+ }
+ if (node->critbyte() == differing_byte &&
+ node->otherbits() == new_other_bits) {
+ CHECK(false);
+ }
+
+ uint8 c = value[node->critbyte()];
+ const int direction =
+ (1 + static_cast<unsigned>(node->otherbits() | c)) >> 8;
+ where_index = &node->data_[direction];
+ }
+
+ inode->SetChild(newdirection ^ 1, *where_index >> 8);
+ *where_index = (*where_index & 0xff) | (internal_node_index << 8);
+
+ return true;
+}
+
+const uint8* StrikeRegister::orbit() const {
+ return orbit_;
+}
+
+void StrikeRegister::Validate() {
+ set<uint32> free_internal_nodes;
+ for (uint32 i = internal_node_free_head_; i != kNil;
+ i = internal_nodes_[i].next()) {
+ CHECK_LT(i, max_entries_);
+ CHECK_EQ(free_internal_nodes.count(i), 0u);
+ free_internal_nodes.insert(i);
+ }
+
+ set<uint32> free_external_nodes;
+ for (uint32 i = external_node_free_head_; i != kNil;
+ i = external_node_next_ptr(i)) {
+ CHECK_LT(i, max_entries_);
+ CHECK_EQ(free_external_nodes.count(i), 0u);
+ free_external_nodes.insert(i);
+ }
+
+ set<uint32> used_external_nodes;
+ set<uint32> used_internal_nodes;
+
+ if (internal_node_head_ != kNil &&
+ ((internal_node_head_ >> 8) & kExternalFlag) == 0) {
+ vector<pair<unsigned, bool> > bits;
+ ValidateTree(internal_node_head_ >> 8, -1, bits, free_internal_nodes,
+ free_external_nodes, &used_internal_nodes,
+ &used_external_nodes);
+ }
+}
+
+// static
+uint32 StrikeRegister::TimeFromBytes(const uint8 d[4]) {
+ return static_cast<uint32>(d[0]) << 24 |
+ static_cast<uint32>(d[1]) << 16 |
+ static_cast<uint32>(d[2]) << 8 |
+ static_cast<uint32>(d[3]);
+}
+
+uint32 StrikeRegister::ExternalTimeToInternal(uint32 external_time) {
+ return external_time - internal_epoch_;
+}
+
+uint32 StrikeRegister::BestMatch(const uint8 v[24]) const {
+ if (internal_node_head_ == kNil) {
+ return kNil;
+ }
+
+ uint32 next = internal_node_head_ >> 8;
+ while ((next & kExternalFlag) == 0) {
+ InternalNode* node = &internal_nodes_[next];
+ uint8 b = v[node->critbyte()];
+ unsigned direction =
+ (1 + static_cast<unsigned>(node->otherbits() | b)) >> 8;
+ next = node->child(direction);
+ }
+
+ return next & ~kExternalFlag;
+}
+
+uint32& StrikeRegister::external_node_next_ptr(unsigned i) {
+ return *reinterpret_cast<uint32*>(&external_nodes_[i * kExternalNodeSize]);
+}
+
+uint8* StrikeRegister::external_node(unsigned i) {
+ return &external_nodes_[i * kExternalNodeSize];
+}
+
+uint32 StrikeRegister::GetFreeExternalNode() {
+ uint32 index = external_node_free_head_;
+ if (index == kNil) {
+ DropNode();
+ return GetFreeExternalNode();
+ }
+
+ external_node_free_head_ = external_node_next_ptr(index);
+ return index;
+}
+
+uint32 StrikeRegister::GetFreeInternalNode() {
+ uint32 index = internal_node_free_head_;
+ if (index == kNil) {
+ DropNode();
+ return GetFreeInternalNode();
+ }
+
+ internal_node_free_head_ = internal_nodes_[index].next();
+ return index;
+}
+
+void StrikeRegister::DropNode() {
+ // DropNode should never be called on an empty tree.
+ DCHECK(internal_node_head_ != kNil);
+
+ // An internal node in a crit-bit tree always has exactly two children.
+ // This means that, if we are removing an external node (which is one of
+ // those children), then we also need to remove an internal node. In order
+ // to do that we keep pointers to the parent (wherep) and grandparent
+ // (whereq) when walking down the tree.
+
+ uint32 p = internal_node_head_ >> 8, *wherep = &internal_node_head_,
+ *whereq = NULL;
+ while ((p & kExternalFlag) == 0) {
+ whereq = wherep;
+ InternalNode* inode = &internal_nodes_[p];
+ // We always go left, towards the smallest element, exploiting the fact
+ // that the timestamp is big-endian and at the start of the value.
+ wherep = &inode->data_[0];
+ p = (*wherep) >> 8;
+ }
+
+ const uint32 ext_index = p & ~kExternalFlag;
+ const uint8* ext_node = external_node(ext_index);
+ horizon_ = TimeFromBytes(ext_node);
+
+ if (!whereq) {
+ // We are removing the last element in a tree.
+ internal_node_head_ = kNil;
+ FreeExternalNode(ext_index);
+ return;
+ }
+
+ // |wherep| points to the left child pointer in the parent so we can add
+ // one and dereference to get the right child.
+ const uint32 other_child = wherep[1];
+ FreeInternalNode((*whereq) >> 8);
+ *whereq = (*whereq & 0xff) | (other_child & 0xffffff00);
+ FreeExternalNode(ext_index);
+}
+
+void StrikeRegister::FreeExternalNode(uint32 index) {
+ external_node_next_ptr(index) = external_node_free_head_;
+ external_node_free_head_ = index;
+}
+
+void StrikeRegister::FreeInternalNode(uint32 index) {
+ internal_nodes_[index].SetNextPtr(internal_node_free_head_);
+ internal_node_free_head_ = index;
+}
+
+void StrikeRegister::ValidateTree(
+ uint32 internal_node,
+ int last_bit,
+ const vector<pair<unsigned, bool> >& bits,
+ const set<uint32>& free_internal_nodes,
+ const set<uint32>& free_external_nodes,
+ set<uint32>* used_internal_nodes,
+ set<uint32>* used_external_nodes) {
+ CHECK_LT(internal_node, max_entries_);
+ const InternalNode* i = &internal_nodes_[internal_node];
+ unsigned bit = 0;
+ switch (i->otherbits()) {
+ case 0xff & ~(1 << 7):
+ bit = 0;
+ break;
+ case 0xff & ~(1 << 6):
+ bit = 1;
+ break;
+ case 0xff & ~(1 << 5):
+ bit = 2;
+ break;
+ case 0xff & ~(1 << 4):
+ bit = 3;
+ break;
+ case 0xff & ~(1 << 3):
+ bit = 4;
+ break;
+ case 0xff & ~(1 << 2):
+ bit = 5;
+ break;
+ case 0xff & ~(1 << 1):
+ bit = 6;
+ break;
+ case 0xff & ~1:
+ bit = 7;
+ break;
+ default:
+ CHECK(false);
+ }
+
+ bit += 8 * i->critbyte();
+ if (last_bit > -1) {
+ CHECK_GT(bit, static_cast<unsigned>(last_bit));
+ }
+
+ CHECK_EQ(free_internal_nodes.count(internal_node), 0u);
+
+ for (unsigned child = 0; child < 2; child++) {
+ if (i->child(child) & kExternalFlag) {
+ uint32 ext = i->child(child) & ~kExternalFlag;
+ CHECK_EQ(free_external_nodes.count(ext), 0u);
+ CHECK_EQ(used_external_nodes->count(ext), 0u);
+ used_external_nodes->insert(ext);
+ const uint8* bytes = external_node(ext);
+ for (vector<pair<unsigned, bool> >::const_iterator i = bits.begin();
+ i != bits.end(); i++) {
+ unsigned byte = i->first / 8;
+ DCHECK_LE(byte, 0xffu);
+ unsigned bit = i->first % 8;
+ static const uint8 kMasks[8] =
+ {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
+ CHECK_EQ((bytes[byte] & kMasks[bit]) != 0, i->second);
+ }
+ } else {
+ uint32 inter = i->child(child);
+ vector<pair<unsigned, bool> > new_bits(bits);
+ new_bits.push_back(pair<unsigned, bool>(bit, child != 0));
+ CHECK_EQ(free_internal_nodes.count(inter), 0u);
+ CHECK_EQ(used_internal_nodes->count(inter), 0u);
+ used_internal_nodes->insert(inter);
+ ValidateTree(inter, bit, bits, free_internal_nodes, free_external_nodes,
+ used_internal_nodes, used_external_nodes);
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/crypto/strike_register.h b/chromium/net/quic/crypto/strike_register.h
new file mode 100644
index 00000000000..98bc04cb630
--- /dev/null
+++ b/chromium/net/quic/crypto/strike_register.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2013 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_QUIC_CRYPTO_STRIKE_REGISTER_H_
+#define NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
+
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A StrikeRegister is critbit tree which stores a set of observed nonces.
+// We use a critbit tree because:
+// 1) It's immune to algorithmic complexity attacks. If we had used a hash
+// tree, an attacker could send us a series of values which stretch out one
+// of the hash chains, causing us to do much more work than normal.
+// 2) We can write it to use a fixed block of memory: avoiding fragmentation
+// issues and so forth. (We might be able to do that with the STL
+// algorithms and a custom allocator, but I don't want to go there.)
+// 3) It's simple (compared to balanced binary trees) and doesn't involve
+// bouncing nearly as many cache lines around.
+// 4) It allows us to query for the oldest element in log(n) time.
+//
+// This code is based on djb's public domain critbit tree from qhasm.
+//
+// A critbit tree has external and internal nodes. External nodes are just the
+// nonce values (which are stored with internal times, see below, and without
+// the orbit values included). Internal nodes contain the bit number at which
+// the tree is branching and exactly two children. The critical bit is stored
+// as a byte number and a byte (|otherbits|) which has all the bits set
+// /except/ the one in question.
+//
+// Internal nodes have exactly two children: an internal node with only a
+// single child would be useless.
+//
+// The branching bit number (considering the MSB to be the 1st bit) is
+// monotonically increasing as you go down the tree.
+//
+// There are two distinct time representations used. External times are those
+// which are exposed to the users of this class. They are expected to be a
+// count of the number of seconds since the UNIX epoch. Internal times are a
+// count of the number of seconds since a point in time a couple of years
+// before the creation time given to the constructor. (See
+// |ExternalTimeToInternal|) This avoids having to worry about overflow since
+// we assume that no process will run for 130 years.
+class NET_EXPORT_PRIVATE StrikeRegister {
+ public:
+ enum StartupType {
+ // DENY_REQUESTS_AT_STARTUP is the typical mode for a strike register.
+ // Because servers can crash and the strike-register memory-based, the
+ // state of the strike-register may be lost at any time. Thus the previous
+ // instance of the server may have accepted an nonce with time
+ // now+window_secs, which was forgotten in the crash. Therefore
+ // DENY_REQUESTS_AT_STARTUP causes the strike-register to reject all
+ // requests timestampped before window_secs + the creation time (the
+ // quiescent period).
+ DENY_REQUESTS_AT_STARTUP,
+ // NO_STARTUP_PERIOD_NEEDED indicates that no quiescent period is required.
+ // This may be because the strike-register is using an orbit randomly
+ // generated at startup and therefore nonces accepted by the previous
+ // instance of the strike-register are invalid for that reason.
+ NO_STARTUP_PERIOD_NEEDED,
+ };
+
+ // An external node takes 24 bytes as we don't record the orbit.
+ static const uint32 kExternalNodeSize;
+
+ // We address the nodes by their index in the array. This means that 0 is a
+ // valid index. Therefore this is our invalid index. It also has a one bit
+ // in the LSB position because we tend to store indexes shifted up 8 bits
+ // and this distinguishes kNil from (kExternalFlag | 0) << 8.
+ static const uint32 kNil;
+
+ // Our pointers from internal nodes can either point to an internal or
+ // external node. We flag the 24th bit to mark a pointer as external.
+ static const uint32 kExternalFlag;
+
+ // Construct a new set which can hold, at most, |max_entries| (which must be
+ // less than 2**23). See the comments around StartupType about initial
+ // behaviour. Otherwise, all nonces that are outside +/- |window_secs| from
+ // the current time will be rejected. Additionally, all nonces that have an
+ // orbit value other than |orbit| will be rejected.
+ //
+ // (Note that this code is independent of the actual units of time used, but
+ // you should use seconds.)
+ StrikeRegister(unsigned max_entries,
+ uint32 current_time_external,
+ uint32 window_secs,
+ const uint8 orbit[8],
+ StartupType startup);
+
+ ~StrikeRegister();
+
+ void Reset();
+
+ // |Insert| queries to see if |nonce| is
+ // a) for the wrong orbit
+ // b) before the current horizon
+ // c) outside of the valid time window
+ // d) already in the set of observed nonces
+ // and returns false if any of these are true. It is also free to return
+ // false for other reasons as it's always safe to reject an nonce.
+ //
+ // nonces are:
+ // 4 bytes of timestamp (UNIX epoch seconds)
+ // 8 bytes of orbit value (a cluster id)
+ // 20 bytes of random data
+ //
+ // Otherwise, it inserts |nonce| into the observed set and returns true.
+ bool Insert(const uint8 nonce[32], const uint32 current_time);
+
+ // orbit returns a pointer to the 8-byte orbit value for this
+ // strike-register.
+ const uint8* orbit() const;
+
+ // This is a debugging aid which checks the tree for sanity.
+ void Validate();
+
+ private:
+ class InternalNode;
+
+ // TimeFromBytes returns a big-endian uint32 from |d|.
+ static uint32 TimeFromBytes(const uint8 d[4]);
+
+ // ExternalTimeToInternal converts an external time value into an internal
+ // time value using |creation_time_external_|.
+ uint32 ExternalTimeToInternal(uint32 external_time);
+
+ // BestMatch returns either kNil, or an external node index which could
+ // possibly match |v|.
+ uint32 BestMatch(const uint8 v[24]) const;
+
+ // external_node_next_ptr returns the 'next' pointer embedded in external
+ // node |i|. This is used to thread a free list through the external nodes.
+ uint32& external_node_next_ptr(unsigned i);
+
+ uint8* external_node(unsigned i);
+
+ uint32 GetFreeExternalNode();
+
+ uint32 GetFreeInternalNode();
+
+ // DropNode removes the oldest node in the tree and updates |horizon_|
+ // accordingly.
+ void DropNode();
+
+ void FreeExternalNode(uint32 index);
+
+ void FreeInternalNode(uint32 index);
+
+ void ValidateTree(uint32 internal_node,
+ int last_bit,
+ const std::vector<std::pair<unsigned, bool> >& bits,
+ const std::set<uint32>& free_internal_nodes,
+ const std::set<uint32>& free_external_nodes,
+ std::set<uint32>* used_internal_nodes,
+ std::set<uint32>* used_external_nodes);
+
+ const uint32 max_entries_;
+ const uint32 window_secs_;
+ // creation_time_external_ contains the uint32, external time when this
+ // object was created (i.e. the value passed to the constructor). This is
+ // used to translate external times to internal times.
+ const uint32 creation_time_external_;
+ // internal_epoch_ contains the external time value of the start of internal
+ // time.
+ const uint32 internal_epoch_;
+ uint8 orbit_[8];
+ uint32 horizon_;
+ bool horizon_valid_;
+
+ uint32 internal_node_free_head_;
+ uint32 external_node_free_head_;
+ uint32 internal_node_head_;
+ // internal_nodes_ can't be a scoped_ptr because the type isn't defined in
+ // this header.
+ InternalNode* internal_nodes_;
+ scoped_ptr<uint8[]> external_nodes_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_CRYPTO_STRIKE_REGISTER_H_
diff --git a/chromium/net/quic/crypto/strike_register_test.cc b/chromium/net/quic/crypto/strike_register_test.cc
new file mode 100644
index 00000000000..ddfa96535de
--- /dev/null
+++ b/chromium/net/quic/crypto/strike_register_test.cc
@@ -0,0 +1,303 @@
+// Copyright (c) 2013 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/quic/crypto/strike_register.h"
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using net::StrikeRegister;
+using std::set;
+using std::string;
+
+const uint8 kOrbit[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+
+// StrikeRegisterTests don't look at the random bytes so this function can
+// simply set the random bytes to 0.
+void SetNonce(uint8 nonce[32], unsigned time, const uint8 orbit[8]) {
+ nonce[0] = time >> 24;
+ nonce[1] = time >> 16;
+ nonce[2] = time >> 8;
+ nonce[3] = time;
+ memcpy(nonce + 4, orbit, 8);
+ memset(nonce + 12, 0, 20);
+}
+
+TEST(StrikeRegisterTest, SimpleHorizon) {
+ // The set must reject values created on or before its own creation time.
+ StrikeRegister set(10 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[32];
+ SetNonce(nonce, 999, kOrbit);
+ ASSERT_FALSE(set.Insert(nonce, 1000));
+ SetNonce(nonce, 1000, kOrbit);
+ ASSERT_FALSE(set.Insert(nonce, 1000));
+}
+
+TEST(StrikeRegisterTest, NoStartupMode) {
+ // Check that a strike register works immediately if NO_STARTUP_PERIOD_NEEDED
+ // is specified.
+ StrikeRegister set(10 /* max size */, 0 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::NO_STARTUP_PERIOD_NEEDED);
+ uint8 nonce[32];
+ SetNonce(nonce, 0, kOrbit);
+ ASSERT_TRUE(set.Insert(nonce, 0));
+ ASSERT_FALSE(set.Insert(nonce, 0));
+}
+
+TEST(StrikeRegisterTest, WindowFuture) {
+ // The set must reject values outside the window.
+ StrikeRegister set(10 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[32];
+ SetNonce(nonce, 1101, kOrbit);
+ ASSERT_FALSE(set.Insert(nonce, 1000));
+ SetNonce(nonce, 999, kOrbit);
+ ASSERT_FALSE(set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, BadOrbit) {
+ // The set must reject values with the wrong orbit
+ StrikeRegister set(10 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[32];
+ static const uint8 kBadOrbit[8] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+ SetNonce(nonce, 1101, kBadOrbit);
+ ASSERT_FALSE(set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, OneValue) {
+ StrikeRegister set(10 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[32];
+ SetNonce(nonce, 1101, kOrbit);
+ ASSERT_TRUE(set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, RejectDuplicate) {
+ // The set must reject values with the wrong orbit
+ StrikeRegister set(10 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[32];
+ SetNonce(nonce, 1101, kOrbit);
+ ASSERT_TRUE(set.Insert(nonce, 1100));
+ ASSERT_FALSE(set.Insert(nonce, 1100));
+}
+
+TEST(StrikeRegisterTest, HorizonUpdating) {
+ StrikeRegister set(5 /* max size */, 1000 /* current time */,
+ 100 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+ uint8 nonce[6][32];
+ for (unsigned i = 0; i < 5; i++) {
+ SetNonce(nonce[i], 1101, kOrbit);
+ nonce[i][31] = i;
+ ASSERT_TRUE(set.Insert(nonce[i], 1100));
+ }
+
+ // This should push the oldest value out and force the horizon to be updated.
+ SetNonce(nonce[5], 1102, kOrbit);
+ ASSERT_TRUE(set.Insert(nonce[5], 1100));
+
+ // This should be behind the horizon now:
+ SetNonce(nonce[5], 1101, kOrbit);
+ nonce[5][31] = 10;
+ ASSERT_FALSE(set.Insert(nonce[5], 1100));
+}
+
+TEST(StrikeRegisterTest, InsertMany) {
+ StrikeRegister set(5000 /* max size */, 1000 /* current time */,
+ 500 /* window secs */, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP);
+
+ uint8 nonce[32];
+ SetNonce(nonce, 1101, kOrbit);
+ for (unsigned i = 0; i < 100000; i++) {
+ SetNonce(nonce, 1101 + i/500, kOrbit);
+ memcpy(nonce + 12, &i, sizeof(i));
+ set.Insert(nonce, 1100);
+ }
+}
+
+
+// For the following test we create a slow, but simple, version of a
+// StrikeRegister. The behaviour of this object is much easier to understand
+// than the fully fledged version. We then create a test to show, empirically,
+// that the two objects have identical behaviour.
+
+// A SlowStrikeRegister has the same public interface as a StrikeRegister, but
+// is much slower. Hopefully it is also more obviously correct and we can
+// empirically test that their behaviours are identical.
+class SlowStrikeRegister {
+ public:
+ SlowStrikeRegister(unsigned max_entries, uint32 current_time,
+ uint32 window_secs, const uint8 orbit[8])
+ : max_entries_(max_entries),
+ window_secs_(window_secs),
+ creation_time_(current_time),
+ horizon_(ExternalTimeToInternal(current_time + window_secs)) {
+ memcpy(orbit_, orbit, sizeof(orbit_));
+ }
+
+ bool Insert(const uint8 nonce_bytes[32], const uint32 current_time_external) {
+ const uint32 current_time = ExternalTimeToInternal(current_time_external);
+
+ // Check to see if the orbit is correct.
+ if (memcmp(nonce_bytes + 4, orbit_, sizeof(orbit_))) {
+ return false;
+ }
+ const uint32 nonce_time =
+ ExternalTimeToInternal(TimeFromBytes(nonce_bytes));
+ // We have dropped one or more nonces with a time value of |horizon_|, so
+ // we have to reject anything with a timestamp less than or equal to that.
+ if (nonce_time <= horizon_) {
+ return false;
+ }
+
+ // Check that the timestamp is in the current window.
+ if ((current_time > window_secs_ &&
+ nonce_time < (current_time - window_secs_)) ||
+ nonce_time > (current_time + window_secs_)) {
+ return false;
+ }
+
+ string nonce;
+ nonce.reserve(32);
+ nonce +=
+ string(reinterpret_cast<const char*>(&nonce_time), sizeof(nonce_time));
+ nonce +=
+ string(reinterpret_cast<const char*>(nonce_bytes) + sizeof(nonce_time),
+ 32 - sizeof(nonce_time));
+
+ set<string>::const_iterator it = nonces_.find(nonce);
+ if (it != nonces_.end()) {
+ return false;
+ }
+
+ if (nonces_.size() == max_entries_) {
+ DropOldestEntry();
+ }
+
+ nonces_.insert(nonce);
+ return true;
+ }
+
+ private:
+ // TimeFromBytes returns a big-endian uint32 from |d|.
+ static uint32 TimeFromBytes(const uint8 d[4]) {
+ return static_cast<uint32>(d[0]) << 24 |
+ static_cast<uint32>(d[1]) << 16 |
+ static_cast<uint32>(d[2]) << 8 |
+ static_cast<uint32>(d[3]);
+ }
+
+ uint32 ExternalTimeToInternal(uint32 external_time) {
+ static const uint32 kCreationTimeFromInternalEpoch = 63115200.0;
+ uint32 internal_epoch = 0;
+ if (creation_time_ > kCreationTimeFromInternalEpoch) {
+ internal_epoch = creation_time_ - kCreationTimeFromInternalEpoch;
+ }
+
+ return external_time - internal_epoch;
+ }
+
+ void DropOldestEntry() {
+ set<string>::iterator oldest = nonces_.begin(), it;
+ uint32 oldest_time =
+ TimeFromBytes(reinterpret_cast<const uint8*>(oldest->data()));
+
+ for (it = oldest; it != nonces_.end(); it++) {
+ uint32 t = TimeFromBytes(reinterpret_cast<const uint8*>(it->data()));
+ if (t < oldest_time ||
+ (t == oldest_time && memcmp(it->data(), oldest->data(), 32) < 0)) {
+ oldest_time = t;
+ oldest = it;
+ }
+ }
+
+ nonces_.erase(oldest);
+ horizon_ = oldest_time;
+ }
+
+ const unsigned max_entries_;
+ const unsigned window_secs_;
+ const uint32 creation_time_;
+ uint8 orbit_[8];
+ uint32 horizon_;
+
+ set<string> nonces_;
+};
+
+TEST(StrikeRegisterStressTest, Stress) {
+ // Fixed seed gives reproducibility for this test.
+ srand(42);
+ unsigned max_entries = 64;
+ uint32 current_time = 10000, window = 200;
+ scoped_ptr<StrikeRegister> s1(
+ new StrikeRegister(max_entries, current_time, window, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+ scoped_ptr<SlowStrikeRegister> s2(
+ new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
+ uint64 i;
+
+ // When making changes it's worth removing the limit on this test and running
+ // it for a while. For the initial development an opt binary was left running
+ // for 10 minutes.
+ const uint64 kMaxIterations = 10000;
+ for (i = 0; i < kMaxIterations; i++) {
+ if (rand() % 1000 == 0) {
+ // 0.1% chance of resetting the sets.
+ max_entries = rand() % 300 + 2;
+ current_time = rand() % 10000;
+ window = rand() % 500;
+ s1.reset(new StrikeRegister(max_entries, current_time, window, kOrbit,
+ StrikeRegister::DENY_REQUESTS_AT_STARTUP));
+ s2.reset(
+ new SlowStrikeRegister(max_entries, current_time, window, kOrbit));
+ }
+
+ int32 time_delta = rand() % (window * 4);
+ time_delta -= window * 2;
+ const uint32 time = current_time + time_delta;
+ if (time_delta < 0 && time > current_time) {
+ continue; // overflow
+ }
+
+ uint8 nonce[32];
+ SetNonce(nonce, time, kOrbit);
+
+ // There are 2048 possible nonce values:
+ const uint32 v = rand() % 2048;
+ nonce[30] = v >> 8;
+ nonce[31] = v;
+
+ const bool r2 = s2->Insert(nonce, time);
+ const bool r1 = s1->Insert(nonce, time);
+
+ if (r1 != r2) {
+ break;
+ }
+
+ if (i % 10 == 0) {
+ s1->Validate();
+ }
+ }
+
+ if (i != kMaxIterations) {
+ FAIL() << "Failed after " << i << " iterations";
+ }
+}
+
+} // anonymous namespace
diff --git a/chromium/net/quic/quic_alarm.cc b/chromium/net/quic/quic_alarm.cc
new file mode 100644
index 00000000000..e1d4a16ae9c
--- /dev/null
+++ b/chromium/net/quic/quic_alarm.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 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/quic/quic_alarm.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+QuicAlarm::QuicAlarm(Delegate* delegate)
+ : delegate_(delegate),
+ deadline_(QuicTime::Zero()) {
+}
+
+QuicAlarm::~QuicAlarm() {}
+
+void QuicAlarm::Set(QuicTime deadline) {
+ DCHECK(!IsSet());
+ DCHECK(deadline.IsInitialized());
+ deadline_ = deadline;
+ SetImpl();
+}
+
+void QuicAlarm::Cancel() {
+ deadline_ = QuicTime::Zero();
+ CancelImpl();
+}
+
+bool QuicAlarm::IsSet() const {
+ return deadline_.IsInitialized();
+}
+
+void QuicAlarm::Fire() {
+ if (!deadline_.IsInitialized()) {
+ return;
+ }
+
+ deadline_ = QuicTime::Zero();
+ QuicTime deadline = delegate_->OnAlarm();
+ // delegate_->OnAlarm() might call Set(), in which case deadline_ will
+ // already contain the new value, so don't overwrite it.
+ if (!deadline_.IsInitialized() && deadline.IsInitialized()) {
+ Set(deadline);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_alarm.h b/chromium/net/quic/quic_alarm.h
new file mode 100644
index 00000000000..bee51577395
--- /dev/null
+++ b/chromium/net/quic/quic_alarm.h
@@ -0,0 +1,77 @@
+// Copyright 2013 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_QUIC_QUIC_ALARM_H_
+#define NET_QUIC_QUIC_ALARM_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+// Abstract class which represents an alarm which will go off at a
+// scheduled time, and execute the |OnAlarm| method of the delegate.
+// An alarm may be cancelled, in which case it may or may not be
+// removed from the underlying scheduling system, but in either case
+// the task will not be executed.
+class NET_EXPORT_PRIVATE QuicAlarm {
+ public:
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Invoked when the alarm fires. If the return value is not
+ // infinite, then the alarm will be rescheduled at the
+ // specified time.
+ virtual QuicTime OnAlarm() = 0;
+ };
+
+ explicit QuicAlarm(Delegate* delegate);
+ virtual ~QuicAlarm();
+
+ // Sets the alarm to fire at |deadline|. Must not be called while
+ // the alarm is set. To reschedule an alarm, call Cancel() first,
+ // then Set().
+ void Set(QuicTime deadline);
+
+ // Cancels the alarm. May be called repeatedly. Does not
+ // guarantee that the underlying scheduling system will remove
+ // the alarm's associated task, but guarantees that the
+ // delegates OnAlarm method will not be called.
+ void Cancel();
+
+ bool IsSet() const;
+
+ QuicTime deadline() const { return deadline_; }
+
+ protected:
+ // Subclasses implement this method to perform the platform-specific
+ // scheduling of the alarm. Is called from Set() or Fire(), after the
+ // deadline has been updated.
+ virtual void SetImpl() = 0;
+
+ // Subclasses implement this method to perform the platform-specific
+ // cancelation of the alarm.
+ virtual void CancelImpl() = 0;
+
+ // Called by subclasses when the alarm fires. Invokes the
+ // delegates |OnAlarm| if a delegate is set, and if the deadline
+ // has been exceeded. Implementations which do not remove the
+ // alarm from the underlying scheduler on Cancel() may need to handle
+ // the situation where the task executes before the deadline has been
+ // reached, in which case they need to reschedule the task and must not
+ // call invoke this method.
+ void Fire();
+
+ private:
+ scoped_ptr<Delegate> delegate_;
+ QuicTime deadline_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicAlarm);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_ALARM_H_
diff --git a/chromium/net/quic/quic_alarm_test.cc b/chromium/net/quic/quic_alarm_test.cc
new file mode 100644
index 00000000000..cd7d8a559be
--- /dev/null
+++ b/chromium/net/quic/quic_alarm_test.cc
@@ -0,0 +1,126 @@
+// Copyright 2013 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/quic/quic_alarm.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Return;
+using testing::Invoke;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockDelegate : public QuicAlarm::Delegate {
+ public:
+ MOCK_METHOD0(OnAlarm, QuicTime());
+};
+
+class TestAlarm : public QuicAlarm {
+ public:
+ TestAlarm(QuicAlarm::Delegate* delegate)
+ : QuicAlarm(delegate) {
+ }
+
+ bool scheduled() const { return scheduled_; }
+
+ void FireAlarm() {
+ scheduled_ = false;
+ Fire();
+ }
+
+ protected:
+ virtual void SetImpl() OVERRIDE {
+ DCHECK(deadline().IsInitialized());
+ scheduled_ = true;
+ }
+
+ virtual void CancelImpl() OVERRIDE {
+ DCHECK(!deadline().IsInitialized());
+ scheduled_ = false;
+ }
+
+ private:
+ bool scheduled_;
+};
+
+class QuicAlarmTest : public ::testing::Test {
+ public:
+ QuicAlarmTest()
+ : delegate_(new MockDelegate()),
+ alarm_(delegate_),
+ deadline_(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7))),
+ deadline2_(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(14))),
+ new_deadline_(QuicTime::Zero()) {
+ }
+
+ void ResetAlarm() {
+ alarm_.Set(new_deadline_);
+ }
+
+ MockDelegate* delegate_; // not owned
+ TestAlarm alarm_;
+ QuicTime deadline_;
+ QuicTime deadline2_;
+ QuicTime new_deadline_;
+};
+
+TEST_F(QuicAlarmTest, IsSet) {
+ EXPECT_FALSE(alarm_.IsSet());
+}
+
+TEST_F(QuicAlarmTest, Set) {
+ QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+ alarm_.Set(deadline);
+ EXPECT_TRUE(alarm_.IsSet());
+ EXPECT_TRUE(alarm_.scheduled());
+ EXPECT_EQ(deadline, alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, Cancel) {
+ QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+ alarm_.Set(deadline);
+ alarm_.Cancel();
+ EXPECT_FALSE(alarm_.IsSet());
+ EXPECT_FALSE(alarm_.scheduled());
+ EXPECT_EQ(QuicTime::Zero(), alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, Fire) {
+ QuicTime deadline = QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(7));
+ alarm_.Set(deadline);
+ EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(Return(QuicTime::Zero()));
+ alarm_.FireAlarm();
+ EXPECT_FALSE(alarm_.IsSet());
+ EXPECT_FALSE(alarm_.scheduled());
+ EXPECT_EQ(QuicTime::Zero(), alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, FireAndResetViaReturn) {
+ alarm_.Set(deadline_);
+ EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(Return(deadline2_));
+ alarm_.FireAlarm();
+ EXPECT_TRUE(alarm_.IsSet());
+ EXPECT_TRUE(alarm_.scheduled());
+ EXPECT_EQ(deadline2_, alarm_.deadline());
+}
+
+TEST_F(QuicAlarmTest, FireAndResetViaSet) {
+ alarm_.Set(deadline_);
+ new_deadline_ = deadline2_;
+ EXPECT_CALL(*delegate_, OnAlarm()).WillOnce(DoAll(
+ Invoke(this, &QuicAlarmTest::ResetAlarm),
+ Return(QuicTime::Zero())));
+ alarm_.FireAlarm();
+ EXPECT_TRUE(alarm_.IsSet());
+ EXPECT_TRUE(alarm_.scheduled());
+ EXPECT_EQ(deadline2_, alarm_.deadline());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_bandwidth.cc b/chromium/net/quic/quic_bandwidth.cc
new file mode 100644
index 00000000000..37b3b3883f2
--- /dev/null
+++ b/chromium/net/quic/quic_bandwidth.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_bandwidth.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace net {
+
+// Highest number that QuicBandwidth can hold.
+const int64 kQuicInfiniteBandwidth = GG_INT64_C(0x7fffffffffffffff);
+
+// static
+QuicBandwidth QuicBandwidth::Zero() {
+ return QuicBandwidth(0);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBitsPerSecond(int64 bits_per_second) {
+ return QuicBandwidth(bits_per_second);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromKBitsPerSecond(int64 k_bits_per_second) {
+ DCHECK(k_bits_per_second < kQuicInfiniteBandwidth / 1000);
+ return QuicBandwidth(k_bits_per_second * 1000);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBytesPerSecond(int64 bytes_per_second) {
+ DCHECK(bytes_per_second < kQuicInfiniteBandwidth / 8);
+ return QuicBandwidth(bytes_per_second * 8);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromKBytesPerSecond(int64 k_bytes_per_second) {
+ DCHECK(k_bytes_per_second < kQuicInfiniteBandwidth / 8000);
+ return QuicBandwidth(k_bytes_per_second * 8000);
+}
+
+// static
+QuicBandwidth QuicBandwidth::FromBytesAndTimeDelta(QuicByteCount bytes,
+ QuicTime::Delta delta) {
+ DCHECK_LT(bytes,
+ static_cast<uint64>(kQuicInfiniteBandwidth /
+ (8 * base::Time::kMicrosecondsPerSecond)));
+ int64 bytes_per_second = (bytes * base::Time::kMicrosecondsPerSecond) /
+ delta.ToMicroseconds();
+ return QuicBandwidth(bytes_per_second * 8);
+}
+
+QuicBandwidth::QuicBandwidth(int64 bits_per_second)
+ : bits_per_second_(bits_per_second) {
+ DCHECK_GE(bits_per_second, 0);
+}
+
+int64 QuicBandwidth::ToBitsPerSecond() const {
+ return bits_per_second_;
+}
+
+int64 QuicBandwidth::ToKBitsPerSecond() const {
+ return bits_per_second_ / 1000;
+}
+
+int64 QuicBandwidth::ToBytesPerSecond() const {
+ return bits_per_second_ / 8;
+}
+
+int64 QuicBandwidth::ToKBytesPerSecond() const {
+ return bits_per_second_ / 8000;
+}
+
+QuicByteCount QuicBandwidth::ToBytesPerPeriod(
+ QuicTime::Delta time_period) const {
+ return ToBytesPerSecond() * time_period.ToMicroseconds() /
+ base::Time::kMicrosecondsPerSecond;
+}
+
+int64 QuicBandwidth::ToKBytesPerPeriod(QuicTime::Delta time_period) const {
+ return ToKBytesPerSecond() * time_period.ToMicroseconds() /
+ base::Time::kMicrosecondsPerSecond;
+}
+
+bool QuicBandwidth::IsZero() const {
+ return (bits_per_second_ == 0);
+}
+
+QuicBandwidth QuicBandwidth::Add(const QuicBandwidth& delta) const {
+ return QuicBandwidth(bits_per_second_ + delta.bits_per_second_);
+}
+
+QuicBandwidth QuicBandwidth::Subtract(const QuicBandwidth& delta) const {
+ return QuicBandwidth(bits_per_second_ - delta.bits_per_second_);
+}
+
+QuicBandwidth QuicBandwidth::Scale(float scale_factor) const {
+ return QuicBandwidth(bits_per_second_ * scale_factor);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_bandwidth.h b/chromium/net/quic/quic_bandwidth.h
new file mode 100644
index 00000000000..71c4b2d9268
--- /dev/null
+++ b/chromium/net/quic/quic_bandwidth.h
@@ -0,0 +1,85 @@
+// Copyright (c) 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.
+//
+// QuicBandwidth represents a bandwidth, stored in bits per second resolution.
+
+#ifndef NET_QUIC_QUIC_BANDWIDTH_H_
+#define NET_QUIC_QUIC_BANDWIDTH_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+typedef uint64 QuicByteCount;
+
+class NET_EXPORT_PRIVATE QuicBandwidth {
+
+ public:
+ // Creates a new QuicBandwidth with an internal value of 0.
+ static QuicBandwidth Zero();
+
+ // Create a new QuicBandwidth holding the bits per second.
+ static QuicBandwidth FromBitsPerSecond(int64 bits_per_second);
+
+ // Create a new QuicBandwidth holding the kilo bits per second.
+ static QuicBandwidth FromKBitsPerSecond(int64 k_bits_per_second);
+
+ // Create a new QuicBandwidth holding the bytes per second.
+ static QuicBandwidth FromBytesPerSecond(int64 bytes_per_second);
+
+ // Create a new QuicBandwidth holding the kilo bytes per second.
+ static QuicBandwidth FromKBytesPerSecond(int64 k_bytes_per_second);
+
+ // Create a new QuicBandwidth based on the bytes per the elapsed delta.
+ static QuicBandwidth FromBytesAndTimeDelta(QuicByteCount bytes,
+ QuicTime::Delta delta);
+
+ int64 ToBitsPerSecond() const;
+
+ int64 ToKBitsPerSecond() const;
+
+ int64 ToBytesPerSecond() const;
+
+ int64 ToKBytesPerSecond() const;
+
+ QuicByteCount ToBytesPerPeriod(QuicTime::Delta time_period) const;
+
+ int64 ToKBytesPerPeriod(QuicTime::Delta time_period) const;
+
+ bool IsZero() const;
+
+ QuicBandwidth Add(const QuicBandwidth& delta) const;
+
+ QuicBandwidth Subtract(const QuicBandwidth& delta) const;
+
+ QuicBandwidth Scale(float scale_factor) const;
+
+ private:
+ explicit QuicBandwidth(int64 bits_per_second);
+ int64 bits_per_second_;
+};
+
+// Non-member relational operators for QuicBandwidth.
+inline bool operator==(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return lhs.ToBitsPerSecond() == rhs.ToBitsPerSecond();
+}
+inline bool operator!=(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return !(lhs == rhs);
+}
+inline bool operator<(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return lhs.ToBitsPerSecond() < rhs.ToBitsPerSecond();
+}
+inline bool operator>(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return rhs < lhs;
+}
+inline bool operator<=(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return !(rhs < lhs);
+}
+inline bool operator>=(QuicBandwidth lhs, QuicBandwidth rhs) {
+ return !(lhs < rhs);
+}
+
+} // namespace net
+#endif // NET_QUIC_QUIC_BANDWIDTH_H_
diff --git a/chromium/net/quic/quic_bandwidth_test.cc b/chromium/net/quic/quic_bandwidth_test.cc
new file mode 100644
index 00000000000..1090f0b4b27
--- /dev/null
+++ b/chromium/net/quic/quic_bandwidth_test.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_bandwidth.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicBandwidthTest : public ::testing::Test {
+};
+
+TEST_F(QuicBandwidthTest, FromTo) {
+ EXPECT_EQ(QuicBandwidth::FromKBitsPerSecond(1),
+ QuicBandwidth::FromBitsPerSecond(1000));
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1),
+ QuicBandwidth::FromBytesPerSecond(1000));
+ EXPECT_EQ(QuicBandwidth::FromBitsPerSecond(8000),
+ QuicBandwidth::FromBytesPerSecond(1000));
+ EXPECT_EQ(QuicBandwidth::FromKBitsPerSecond(8),
+ QuicBandwidth::FromKBytesPerSecond(1));
+
+ EXPECT_EQ(0, QuicBandwidth::Zero().ToBitsPerSecond());
+ EXPECT_EQ(0, QuicBandwidth::Zero().ToKBitsPerSecond());
+ EXPECT_EQ(0, QuicBandwidth::Zero().ToBytesPerSecond());
+ EXPECT_EQ(0, QuicBandwidth::Zero().ToKBytesPerSecond());
+
+ EXPECT_EQ(1, QuicBandwidth::FromBitsPerSecond(1000).ToKBitsPerSecond());
+ EXPECT_EQ(1000, QuicBandwidth::FromKBitsPerSecond(1).ToBitsPerSecond());
+ EXPECT_EQ(1, QuicBandwidth::FromBytesPerSecond(1000).ToKBytesPerSecond());
+ EXPECT_EQ(1000, QuicBandwidth::FromKBytesPerSecond(1).ToBytesPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, Add) {
+ QuicBandwidth bandwidht_1 = QuicBandwidth::FromKBitsPerSecond(1);
+ QuicBandwidth bandwidht_2 = QuicBandwidth::FromKBytesPerSecond(1);
+
+ EXPECT_EQ(9000, bandwidht_1.Add(bandwidht_2).ToBitsPerSecond());
+ EXPECT_EQ(9000, bandwidht_2.Add(bandwidht_1).ToBitsPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, Subtract) {
+ QuicBandwidth bandwidht_1 = QuicBandwidth::FromKBitsPerSecond(1);
+ QuicBandwidth bandwidht_2 = QuicBandwidth::FromKBytesPerSecond(1);
+
+ EXPECT_EQ(7000, bandwidht_2.Subtract(bandwidht_1).ToBitsPerSecond());
+}
+
+TEST_F(QuicBandwidthTest, TimeDelta) {
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1000),
+ QuicBandwidth::FromBytesAndTimeDelta(
+ 1000, QuicTime::Delta::FromMilliseconds(1)));
+
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(10),
+ QuicBandwidth::FromBytesAndTimeDelta(
+ 1000, QuicTime::Delta::FromMilliseconds(100)));
+}
+
+TEST_F(QuicBandwidthTest, Scale) {
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(500),
+ QuicBandwidth::FromKBytesPerSecond(1000).Scale(0.5f));
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(750),
+ QuicBandwidth::FromKBytesPerSecond(1000).Scale(0.75f));
+ EXPECT_EQ(QuicBandwidth::FromKBytesPerSecond(1250),
+ QuicBandwidth::FromKBytesPerSecond(1000).Scale(1.25f));
+}
+
+
+TEST_F(QuicBandwidthTest, BytesPerPeriod) {
+ EXPECT_EQ(2000u, QuicBandwidth::FromKBytesPerSecond(2000).ToBytesPerPeriod(
+ QuicTime::Delta::FromMilliseconds(1)));
+ EXPECT_EQ(2u, QuicBandwidth::FromKBytesPerSecond(2000).ToKBytesPerPeriod(
+ QuicTime::Delta::FromMilliseconds(1)));
+ EXPECT_EQ(200000u, QuicBandwidth::FromKBytesPerSecond(2000).ToBytesPerPeriod(
+ QuicTime::Delta::FromMilliseconds(100)));
+ EXPECT_EQ(200u, QuicBandwidth::FromKBytesPerSecond(2000).ToKBytesPerPeriod(
+ QuicTime::Delta::FromMilliseconds(100)));
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_blocked_writer_interface.h b/chromium/net/quic/quic_blocked_writer_interface.h
new file mode 100644
index 00000000000..a694193fe31
--- /dev/null
+++ b/chromium/net/quic/quic_blocked_writer_interface.h
@@ -0,0 +1,28 @@
+// Copyright (c) 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.
+
+// This is an interface for all objects that want to be notified that
+// the underlying UDP socket is available for writing (not write blocked
+// anymore).
+
+#ifndef NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
+#define NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicBlockedWriterInterface {
+ public:
+ virtual ~QuicBlockedWriterInterface() {}
+
+ // Called by the PacketWriter when the underlying socket becomes writable
+ // so that the BlockedWriter can go ahead and try writing. This methods
+ // should return false if the socket has become blocked while writing.
+ virtual bool OnCanWrite() = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_BLOCKED_WRITER_INTERFACE_H_
diff --git a/chromium/net/quic/quic_client_session.cc b/chromium/net/quic/quic_client_session.cc
new file mode 100644
index 00000000000..67620787307
--- /dev/null
+++ b/chromium/net/quic/quic_client_session.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_client_session.h"
+
+#include "base/callback_helpers.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_stream_factory.h"
+#include "net/ssl/ssl_info.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace net {
+
+namespace {
+
+// Note: these values must be kept in sync with the corresponding values in:
+// tools/metrics/histograms/histograms.xml
+enum HandshakeState {
+ STATE_STARTED = 0,
+ STATE_ENCRYPTION_ESTABLISHED = 1,
+ STATE_HANDSHAKE_CONFIRMED = 2,
+ STATE_FAILED = 3,
+ NUM_HANDSHAKE_STATES = 4
+};
+
+void RecordHandshakeState(HandshakeState state) {
+ UMA_HISTOGRAM_ENUMERATION("Net.QuicHandshakeState", state,
+ NUM_HANDSHAKE_STATES);
+}
+
+} // namespace
+
+QuicClientSession::StreamRequest::StreamRequest() : stream_(NULL) {}
+
+QuicClientSession::StreamRequest::~StreamRequest() {
+ CancelRequest();
+}
+
+int QuicClientSession::StreamRequest::StartRequest(
+ const base::WeakPtr<QuicClientSession> session,
+ QuicReliableClientStream** stream,
+ const CompletionCallback& callback) {
+ session_ = session;
+ stream_ = stream;
+ int rv = session_->TryCreateStream(this, stream_);
+ if (rv == ERR_IO_PENDING) {
+ callback_ = callback;
+ }
+
+ return rv;
+}
+
+void QuicClientSession::StreamRequest::CancelRequest() {
+ if (session_)
+ session_->CancelRequest(this);
+ session_.reset();
+ callback_.Reset();
+}
+
+void QuicClientSession::StreamRequest::OnRequestCompleteSuccess(
+ QuicReliableClientStream* stream) {
+ session_.reset();
+ *stream_ = stream;
+ ResetAndReturn(&callback_).Run(OK);
+}
+
+void QuicClientSession::StreamRequest::OnRequestCompleteFailure(int rv) {
+ session_.reset();
+ ResetAndReturn(&callback_).Run(rv);
+}
+
+QuicClientSession::QuicClientSession(
+ QuicConnection* connection,
+ scoped_ptr<DatagramClientSocket> socket,
+ QuicStreamFactory* stream_factory,
+ QuicCryptoClientStreamFactory* crypto_client_stream_factory,
+ const string& server_hostname,
+ const QuicConfig& config,
+ QuicCryptoClientConfig* crypto_config,
+ NetLog* net_log)
+ : QuicSession(connection, config, false),
+ weak_factory_(this),
+ stream_factory_(stream_factory),
+ socket_(socket.Pass()),
+ read_buffer_(new IOBufferWithSize(kMaxPacketSize)),
+ read_pending_(false),
+ num_total_streams_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_QUIC_SESSION)),
+ logger_(net_log_) {
+ crypto_stream_.reset(
+ crypto_client_stream_factory ?
+ crypto_client_stream_factory->CreateQuicCryptoClientStream(
+ server_hostname, this, crypto_config) :
+ new QuicCryptoClientStream(server_hostname, this, crypto_config));
+
+ connection->set_debug_visitor(&logger_);
+ // TODO(rch): pass in full host port proxy pair
+ net_log_.BeginEvent(
+ NetLog::TYPE_QUIC_SESSION,
+ NetLog::StringCallback("host", &server_hostname));
+}
+
+QuicClientSession::~QuicClientSession() {
+ connection()->set_debug_visitor(NULL);
+ net_log_.EndEvent(NetLog::TYPE_QUIC_SESSION);
+
+ while (!stream_requests_.empty()) {
+ StreamRequest* request = stream_requests_.front();
+ stream_requests_.pop_front();
+ request->OnRequestCompleteFailure(ERR_ABORTED);
+ }
+
+ if (IsEncryptionEstablished())
+ RecordHandshakeState(STATE_ENCRYPTION_ESTABLISHED);
+ if (IsCryptoHandshakeConfirmed())
+ RecordHandshakeState(STATE_HANDSHAKE_CONFIRMED);
+ else
+ RecordHandshakeState(STATE_FAILED);
+
+ UMA_HISTOGRAM_COUNTS("Net.QuicNumSentClientHellos",
+ crypto_stream_->num_sent_client_hellos());
+ if (IsCryptoHandshakeConfirmed()) {
+ UMA_HISTOGRAM_COUNTS("Net.QuicNumSentClientHellosCryptoHandshakeConfirmed",
+ crypto_stream_->num_sent_client_hellos());
+ }
+}
+
+int QuicClientSession::TryCreateStream(StreamRequest* request,
+ QuicReliableClientStream** stream) {
+ if (!crypto_stream_->encryption_established()) {
+ DLOG(DFATAL) << "Encryption not established.";
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ if (goaway_received()) {
+ DLOG(INFO) << "Going away.";
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ if (!connection()->connected()) {
+ DLOG(INFO) << "Already closed.";
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ if (GetNumOpenStreams() < get_max_open_streams()) {
+ *stream = CreateOutgoingReliableStreamImpl();
+ return OK;
+ }
+
+ stream_requests_.push_back(request);
+ return ERR_IO_PENDING;
+}
+
+void QuicClientSession::CancelRequest(StreamRequest* request) {
+ // Remove |request| from the queue while preserving the order of the
+ // other elements.
+ StreamRequestQueue::iterator it =
+ std::find(stream_requests_.begin(), stream_requests_.end(), request);
+ if (it != stream_requests_.end()) {
+ it = stream_requests_.erase(it);
+ }
+}
+
+QuicReliableClientStream* QuicClientSession::CreateOutgoingReliableStream() {
+ if (!crypto_stream_->encryption_established()) {
+ DLOG(INFO) << "Encryption not active so no outgoing stream created.";
+ return NULL;
+ }
+ if (GetNumOpenStreams() >= get_max_open_streams()) {
+ DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already " << GetNumOpenStreams() << " open.";
+ return NULL;
+ }
+ if (goaway_received()) {
+ DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already received goaway.";
+ return NULL;
+ }
+
+ return CreateOutgoingReliableStreamImpl();
+}
+
+QuicReliableClientStream*
+QuicClientSession::CreateOutgoingReliableStreamImpl() {
+ DCHECK(connection()->connected());
+ QuicReliableClientStream* stream =
+ new QuicReliableClientStream(GetNextStreamId(), this, net_log_);
+ ActivateStream(stream);
+ ++num_total_streams_;
+ return stream;
+}
+
+QuicCryptoClientStream* QuicClientSession::GetCryptoStream() {
+ return crypto_stream_.get();
+};
+
+bool QuicClientSession::GetSSLInfo(SSLInfo* ssl_info) {
+ DCHECK(crypto_stream_.get());
+ return crypto_stream_->GetSSLInfo(ssl_info);
+}
+
+int QuicClientSession::CryptoConnect(const CompletionCallback& callback) {
+ RecordHandshakeState(STATE_STARTED);
+ if (!crypto_stream_->CryptoConnect()) {
+ // TODO(wtc): change crypto_stream_.CryptoConnect() to return a
+ // QuicErrorCode and map it to a net error code.
+ return ERR_CONNECTION_FAILED;
+ }
+
+ if (IsEncryptionEstablished()) {
+ return OK;
+ }
+
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+ReliableQuicStream* QuicClientSession::CreateIncomingReliableStream(
+ QuicStreamId id) {
+ DLOG(ERROR) << "Server push not supported";
+ return NULL;
+}
+
+void QuicClientSession::CloseStream(QuicStreamId stream_id) {
+ QuicSession::CloseStream(stream_id);
+
+ if (GetNumOpenStreams() < get_max_open_streams() &&
+ !stream_requests_.empty() &&
+ crypto_stream_->encryption_established() &&
+ !goaway_received() &&
+ connection()->connected()) {
+ StreamRequest* request = stream_requests_.front();
+ stream_requests_.pop_front();
+ request->OnRequestCompleteSuccess(CreateOutgoingReliableStreamImpl());
+ }
+
+ if (GetNumOpenStreams() == 0) {
+ stream_factory_->OnIdleSession(this);
+ }
+}
+
+void QuicClientSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+ if (!callback_.is_null()) {
+ // TODO(rtenneti): Currently for all CryptoHandshakeEvent events, callback_
+ // could be called because there are no error events in CryptoHandshakeEvent
+ // enum. If error events are added to CryptoHandshakeEvent, then the
+ // following code needs to changed.
+ base::ResetAndReturn(&callback_).Run(OK);
+ }
+ QuicSession::OnCryptoHandshakeEvent(event);
+}
+
+void QuicClientSession::OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message) {
+ logger_.OnCryptoHandshakeMessageSent(message);
+}
+
+void QuicClientSession::OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message) {
+ logger_.OnCryptoHandshakeMessageReceived(message);
+}
+
+void QuicClientSession::ConnectionClose(QuicErrorCode error, bool from_peer) {
+ logger_.OnConnectionClose(error, from_peer);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.ConnectionCloseErrorCode",
+ error);
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.QuicVersion",
+ connection()->version());
+ if (!callback_.is_null()) {
+ base::ResetAndReturn(&callback_).Run(ERR_QUIC_PROTOCOL_ERROR);
+ }
+ QuicSession::ConnectionClose(error, from_peer);
+ NotifyFactoryOfSessionCloseLater();
+}
+
+void QuicClientSession::StartReading() {
+ if (read_pending_) {
+ return;
+ }
+ read_pending_ = true;
+ int rv = socket_->Read(read_buffer_.get(),
+ read_buffer_->size(),
+ base::Bind(&QuicClientSession::OnReadComplete,
+ weak_factory_.GetWeakPtr()));
+ if (rv == ERR_IO_PENDING) {
+ return;
+ }
+
+ // Data was read, process it.
+ // Schedule the work through the message loop to avoid recursive
+ // callbacks.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&QuicClientSession::OnReadComplete,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void QuicClientSession::CloseSessionOnError(int error) {
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.QuicSession.CloseSessionOnError", -error);
+ CloseSessionOnErrorInner(error);
+ NotifyFactoryOfSessionClose();
+}
+
+void QuicClientSession::CloseSessionOnErrorInner(int error) {
+ if (!callback_.is_null()) {
+ base::ResetAndReturn(&callback_).Run(error);
+ }
+ while (!streams()->empty()) {
+ ReliableQuicStream* stream = streams()->begin()->second;
+ QuicStreamId id = stream->id();
+ static_cast<QuicReliableClientStream*>(stream)->OnError(error);
+ CloseStream(id);
+ }
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CLOSE_ON_ERROR,
+ NetLog::IntegerCallback("net_error", error));
+
+ connection()->CloseConnection(QUIC_INTERNAL_ERROR, false);
+ DCHECK(!connection()->connected());
+}
+
+base::Value* QuicClientSession::GetInfoAsValue(const HostPortPair& pair) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("host_port_pair", pair.ToString());
+ dict->SetString("version", QuicVersionToString(connection()->version()));
+ dict->SetInteger("open_streams", GetNumOpenStreams());
+ dict->SetInteger("total_streams", num_total_streams_);
+ dict->SetString("peer_address", peer_address().ToString());
+ dict->SetString("guid", base::Uint64ToString(guid()));
+ return dict;
+}
+
+base::WeakPtr<QuicClientSession> QuicClientSession::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+void QuicClientSession::OnReadComplete(int result) {
+ read_pending_ = false;
+ if (result == 0)
+ result = ERR_CONNECTION_CLOSED;
+
+ if (result < 0) {
+ DLOG(INFO) << "Closing session on read error: " << result;
+ CloseSessionOnErrorInner(result);
+ NotifyFactoryOfSessionCloseLater();
+ return;
+ }
+
+ scoped_refptr<IOBufferWithSize> buffer(read_buffer_);
+ read_buffer_ = new IOBufferWithSize(kMaxPacketSize);
+ QuicEncryptedPacket packet(buffer->data(), result);
+ IPEndPoint local_address;
+ IPEndPoint peer_address;
+ socket_->GetLocalAddress(&local_address);
+ socket_->GetPeerAddress(&peer_address);
+ // ProcessUdpPacket might result in |this| being deleted, so we
+ // use a weak pointer to be safe.
+ connection()->ProcessUdpPacket(local_address, peer_address, packet);
+ if (!connection()->connected()) {
+ stream_factory_->OnSessionClose(this);
+ return;
+ }
+ StartReading();
+}
+
+void QuicClientSession::NotifyFactoryOfSessionCloseLater() {
+ DCHECK_EQ(0u, GetNumOpenStreams());
+ DCHECK(!connection()->connected());
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&QuicClientSession::NotifyFactoryOfSessionClose,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuicClientSession::NotifyFactoryOfSessionClose() {
+ DCHECK_EQ(0u, GetNumOpenStreams());
+ DCHECK(stream_factory_);
+ // Will delete |this|.
+ stream_factory_->OnSessionClose(this);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_client_session.h b/chromium/net/quic/quic_client_session.h
new file mode 100644
index 00000000000..555837fe9a5
--- /dev/null
+++ b/chromium/net/quic/quic_client_session.h
@@ -0,0 +1,172 @@
+// Copyright (c) 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.
+//
+// A client specific QuicSession subclass. This class owns the underlying
+// QuicConnection and QuicConnectionHelper objects. The connection stores
+// a non-owning pointer to the helper so this session needs to ensure that
+// the helper outlives the connection.
+
+#ifndef NET_QUIC_QUIC_CLIENT_SESSION_H_
+#define NET_QUIC_QUIC_CLIENT_SESSION_H_
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/quic/quic_connection_logger.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_reliable_client_stream.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+class DatagramClientSocket;
+class QuicConnectionHelper;
+class QuicCryptoClientStreamFactory;
+class QuicStreamFactory;
+class SSLInfo;
+
+namespace test {
+class QuicClientSessionPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicClientSession : public QuicSession {
+ public:
+ // A helper class used to manage a request to create a stream.
+ class NET_EXPORT_PRIVATE StreamRequest {
+ public:
+ StreamRequest();
+ ~StreamRequest();
+
+ // Starts a request to create a stream. If OK is returned, then
+ // |stream| will be updated with the newly created stream. If
+ // ERR_IO_PENDING is returned, then when the request is eventuallly
+ // complete |callback| will be called.
+ int StartRequest(const base::WeakPtr<QuicClientSession> session,
+ QuicReliableClientStream** stream,
+ const CompletionCallback& callback);
+
+ // Cancels any pending stream creation request. May be called
+ // repeatedly.
+ void CancelRequest();
+
+ private:
+ friend class QuicClientSession;
+
+ // Called by |session_| for an asynchronous request when the stream
+ // request has finished successfully.
+ void OnRequestCompleteSuccess(QuicReliableClientStream* stream);
+
+ // Called by |session_| for an asynchronous request when the stream
+ // request has finished with an error. Also called with ERR_ABORTED
+ // if |session_| is destroyed while the stream request is still pending.
+ void OnRequestCompleteFailure(int rv);
+
+ base::WeakPtr<QuicClientSession> session_;
+ CompletionCallback callback_;
+ QuicReliableClientStream** stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamRequest);
+ };
+
+ // Constructs a new session which will own |connection| and |helper|, but
+ // not |stream_factory|, which must outlive this session.
+ // TODO(rch): decouple the factory from the session via a Delegate interface.
+ QuicClientSession(QuicConnection* connection,
+ scoped_ptr<DatagramClientSocket> socket,
+ QuicStreamFactory* stream_factory,
+ QuicCryptoClientStreamFactory* crypto_client_stream_factory,
+ const std::string& server_hostname,
+ const QuicConfig& config,
+ QuicCryptoClientConfig* crypto_config,
+ NetLog* net_log);
+
+ virtual ~QuicClientSession();
+
+ // Attempts to create a new stream. If the stream can be
+ // created immediately, returns OK. If the open stream limit
+ // has been reached, returns ERR_IO_PENDING, and |request|
+ // will be added to the stream requets queue and will
+ // be completed asynchronously.
+ // TODO(rch): remove |stream| from this and use setter on |request|
+ // and fix in spdy too.
+ int TryCreateStream(StreamRequest* request,
+ QuicReliableClientStream** stream);
+
+ // Cancels the pending stream creation request.
+ void CancelRequest(StreamRequest* request);
+
+ // QuicSession methods:
+ virtual QuicReliableClientStream* CreateOutgoingReliableStream() OVERRIDE;
+ virtual QuicCryptoClientStream* GetCryptoStream() OVERRIDE;
+ virtual void CloseStream(QuicStreamId stream_id) OVERRIDE;
+ virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event) OVERRIDE;
+ virtual void OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+ virtual void OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // QuicConnectionVisitorInterface methods:
+ virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE;
+
+ // Performs a crypto handshake with the server.
+ int CryptoConnect(const CompletionCallback& callback);
+
+ // Causes the QuicConnectionHelper to start reading from the socket
+ // and passing the data along to the QuicConnection.
+ void StartReading();
+
+ // Close the session because of |error| and notifies the factory
+ // that this session has been closed, which will delete the session.
+ void CloseSessionOnError(int error);
+
+ base::Value* GetInfoAsValue(const HostPortPair& pair) const;
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ base::WeakPtr<QuicClientSession> GetWeakPtr();
+
+ protected:
+ // QuicSession methods:
+ virtual ReliableQuicStream* CreateIncomingReliableStream(
+ QuicStreamId id) OVERRIDE;
+
+ private:
+ friend class test::QuicClientSessionPeer;
+
+ typedef std::list<StreamRequest*> StreamRequestQueue;
+
+ QuicReliableClientStream* CreateOutgoingReliableStreamImpl();
+ // A completion callback invoked when a read completes.
+ void OnReadComplete(int result);
+
+ void CloseSessionOnErrorInner(int error);
+
+ // Posts a task to notify the factory that this session has been closed.
+ void NotifyFactoryOfSessionCloseLater();
+
+ // Notifies the factory that this session has been closed which will
+ // delete |this|.
+ void NotifyFactoryOfSessionClose();
+
+ base::WeakPtrFactory<QuicClientSession> weak_factory_;
+ scoped_ptr<QuicCryptoClientStream> crypto_stream_;
+ QuicStreamFactory* stream_factory_;
+ scoped_ptr<DatagramClientSocket> socket_;
+ scoped_refptr<IOBufferWithSize> read_buffer_;
+ StreamRequestQueue stream_requests_;
+ bool read_pending_;
+ CompletionCallback callback_;
+ size_t num_total_streams_;
+ BoundNetLog net_log_;
+ QuicConnectionLogger logger_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicClientSession);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CLIENT_SESSION_H_
diff --git a/chromium/net/quic/quic_client_session_test.cc b/chromium/net/quic/quic_client_session_test.cc
new file mode 100644
index 00000000000..09e7d210116
--- /dev/null
+++ b/chromium/net/quic/quic_client_session_test.cc
@@ -0,0 +1,134 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_client_session.h"
+
+#include <vector>
+
+#include "net/base/capturing_net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_client_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/udp/datagram_client_socket.h"
+
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "www.example.com";
+
+class QuicClientSessionTest : public ::testing::Test {
+ protected:
+ QuicClientSessionTest()
+ : guid_(1),
+ connection_(new PacketSavingConnection(guid_, IPEndPoint(), false)),
+ session_(connection_, scoped_ptr<DatagramClientSocket>(), NULL,
+ NULL, kServerHostname, DefaultQuicConfig(), &crypto_config_,
+ &net_log_) {
+ session_.config()->SetDefaults();
+ crypto_config_.SetDefaults();
+ }
+
+ void CompleteCryptoHandshake() {
+ ASSERT_EQ(ERR_IO_PENDING,
+ session_.CryptoConnect(callback_.callback()));
+ CryptoTestUtils::HandshakeWithFakeServer(
+ connection_, session_.GetCryptoStream());
+ ASSERT_EQ(OK, callback_.WaitForResult());
+ }
+
+ QuicGuid guid_;
+ PacketSavingConnection* connection_;
+ CapturingNetLog net_log_;
+ QuicClientSession session_;
+ MockClock clock_;
+ MockRandom random_;
+ QuicConnectionVisitorInterface* visitor_;
+ TestCompletionCallback callback_;
+ QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(QuicClientSessionTest, CryptoConnect) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+}
+
+TEST_F(QuicClientSessionTest, MaxNumStreams) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ std::vector<QuicReliableClientStream*> streams;
+ for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+ QuicReliableClientStream* stream = session_.CreateOutgoingReliableStream();
+ EXPECT_TRUE(stream);
+ streams.push_back(stream);
+ }
+ EXPECT_FALSE(session_.CreateOutgoingReliableStream());
+
+ // Close a stream and ensure I can now open a new one.
+ session_.CloseStream(streams[0]->id());
+ EXPECT_TRUE(session_.CreateOutgoingReliableStream());
+}
+
+TEST_F(QuicClientSessionTest, MaxNumStreamsViaRequest) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ std::vector<QuicReliableClientStream*> streams;
+ for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; i++) {
+ QuicReliableClientStream* stream = session_.CreateOutgoingReliableStream();
+ EXPECT_TRUE(stream);
+ streams.push_back(stream);
+ }
+
+ QuicReliableClientStream* stream;
+ QuicClientSession::StreamRequest stream_request;
+ TestCompletionCallback callback;
+ ASSERT_EQ(ERR_IO_PENDING,
+ stream_request.StartRequest(session_.GetWeakPtr(), &stream,
+ callback.callback()));
+
+ // Close a stream and ensure I can now open a new one.
+ session_.CloseStream(streams[0]->id());
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(stream != NULL);
+}
+
+TEST_F(QuicClientSessionTest, GoAwayReceived) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ // After receiving a GoAway, I should no longer be able to create outgoing
+ // streams.
+ session_.OnGoAway(QuicGoAwayFrame(QUIC_PEER_GOING_AWAY, 1u, "Going away."));
+ EXPECT_EQ(NULL, session_.CreateOutgoingReliableStream());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_clock.cc b/chromium/net/quic/quic_clock.cc
new file mode 100644
index 00000000000..865562369fa
--- /dev/null
+++ b/chromium/net/quic/quic_clock.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_clock.h"
+
+#include "base/time/time.h"
+
+namespace net {
+
+QuicClock::QuicClock() {
+}
+
+QuicClock::~QuicClock() {}
+
+QuicTime QuicClock::ApproximateNow() const {
+ // Chrome does not have a distinct notion of ApproximateNow().
+ return Now();
+}
+
+QuicTime QuicClock::Now() const {
+ return QuicTime(base::TimeTicks::Now());
+}
+
+QuicWallTime QuicClock::WallNow() const {
+ return QuicWallTime::FromUNIXSeconds(base::Time::Now().ToTimeT());
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_clock.h b/chromium/net/quic/quic_clock.h
new file mode 100644
index 00000000000..e6135a930d0
--- /dev/null
+++ b/chromium/net/quic/quic_clock.h
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_CLOCK_H_
+#define NET_QUIC_QUIC_CLOCK_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+typedef double WallTime;
+
+// Clock to efficiently retrieve an approximately accurate time from an
+// EpollServer.
+class NET_EXPORT_PRIVATE QuicClock {
+ public:
+ QuicClock();
+ virtual ~QuicClock();
+
+ // Returns the approximate current time as a QuicTime object.
+ virtual QuicTime ApproximateNow() const;
+
+ // Returns the current time as a QuicTime object.
+ // Note: this use significant resources please use only if needed.
+ virtual QuicTime Now() const;
+
+ // WallNow returns the current wall-time - a time that is consistent across
+ // different clocks.
+ virtual QuicWallTime WallNow() const;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CLOCK_H_
diff --git a/chromium/net/quic/quic_clock_test.cc b/chromium/net/quic/quic_clock_test.cc
new file mode 100644
index 00000000000..6ee4540889f
--- /dev/null
+++ b/chromium/net/quic/quic_clock_test.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_clock.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TEST(QuicClockTest, Now) {
+ QuicClock clock;
+
+ QuicTime start(base::TimeTicks::Now());
+ QuicTime now = clock.ApproximateNow();
+ QuicTime end(base::TimeTicks::Now());
+
+ EXPECT_LE(start, now);
+ EXPECT_LE(now, end);
+}
+
+TEST(QuicClockTest, WallNow) {
+ QuicClock clock;
+
+ base::Time start = base::Time::Now();
+ QuicWallTime now = clock.WallNow();
+ base::Time end = base::Time::Now();
+
+ // If end > start, then we can check now is between start and end.
+ if (end > start) {
+ EXPECT_LE(static_cast<uint64>(start.ToTimeT()), now.ToUNIXSeconds());
+ EXPECT_LE(now.ToUNIXSeconds(), static_cast<uint64>(end.ToTimeT()));
+ }
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_config.cc b/chromium/net/quic/quic_config.cc
new file mode 100644
index 00000000000..545d4c165e4
--- /dev/null
+++ b/chromium/net/quic/quic_config.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 2013 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/quic/quic_config.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+using std::string;
+
+namespace net {
+
+QuicNegotiableValue::QuicNegotiableValue(QuicTag tag, Presence presence)
+ : tag_(tag),
+ presence_(presence),
+ negotiated_(false) {
+}
+
+QuicNegotiableUint32::QuicNegotiableUint32(QuicTag tag, Presence presence)
+ : QuicNegotiableValue(tag, presence) {
+}
+
+void QuicNegotiableUint32::set(uint32 max, uint32 default_value) {
+ DCHECK_LE(default_value, max);
+ max_value_ = max;
+ default_value_ = default_value;
+}
+
+uint32 QuicNegotiableUint32::GetUint32() const {
+ if (negotiated_) {
+ return negotiated_value_;
+ }
+ return default_value_;
+}
+
+void QuicNegotiableUint32::ToHandshakeMessage(
+ CryptoHandshakeMessage* out) const {
+ if (negotiated_) {
+ out->SetValue(tag_, negotiated_value_);
+ } else {
+ out->SetValue(tag_, max_value_);
+ }
+}
+
+QuicErrorCode QuicNegotiableUint32::ReadUint32(
+ const CryptoHandshakeMessage& msg,
+ uint32* out,
+ string* error_details) const {
+ DCHECK(error_details != NULL);
+ QuicErrorCode error = msg.GetUint32(tag_, out);
+ switch (error) {
+ case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+ if (presence_ == QuicNegotiableValue::PRESENCE_REQUIRED) {
+ *error_details = "Missing " + QuicUtils::TagToString(tag_);
+ break;
+ }
+ error = QUIC_NO_ERROR;
+ *out = default_value_;
+
+ case QUIC_NO_ERROR:
+ break;
+ default:
+ *error_details = "Bad " + QuicUtils::TagToString(tag_);
+ break;
+ }
+ return error;
+}
+
+QuicErrorCode QuicNegotiableUint32::ProcessClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ string* error_details) {
+ DCHECK(!negotiated_);
+ DCHECK(error_details != NULL);
+ uint32 value;
+ QuicErrorCode error = ReadUint32(client_hello, &value, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ negotiated_ = true;
+ negotiated_value_ = std::min(value, max_value_);
+
+ return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicNegotiableUint32::ProcessServerHello(
+ const CryptoHandshakeMessage& server_hello,
+ string* error_details) {
+ DCHECK(!negotiated_);
+ DCHECK(error_details != NULL);
+ uint32 value;
+ QuicErrorCode error = ReadUint32(server_hello, &value, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ if (value > max_value_) {
+ *error_details = "Invalid value received for " +
+ QuicUtils::TagToString(tag_);
+ return QUIC_INVALID_NEGOTIATED_VALUE;
+ }
+
+ negotiated_ = true;
+ negotiated_value_ = value;
+ return QUIC_NO_ERROR;
+}
+
+QuicNegotiableTag::QuicNegotiableTag(QuicTag tag, Presence presence)
+ : QuicNegotiableValue(tag, presence) {
+}
+
+QuicNegotiableTag::~QuicNegotiableTag() {}
+
+void QuicNegotiableTag::set(const QuicTagVector& possible,
+ QuicTag default_value) {
+ DCHECK(std::find(possible.begin(), possible.end(), default_value) !=
+ possible.end());
+ possible_values_ = possible;
+ default_value_ = default_value;
+}
+
+QuicTag QuicNegotiableTag::GetTag() const {
+ if (negotiated_) {
+ return negotiated_tag_;
+ }
+ return default_value_;
+}
+
+void QuicNegotiableTag::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+ if (negotiated_) {
+ // Because of the way we serialize and parse handshake messages we can
+ // serialize this as value and still parse it as a vector.
+ out->SetValue(tag_, negotiated_tag_);
+ } else {
+ out->SetVector(tag_, possible_values_);
+ }
+}
+
+QuicErrorCode QuicNegotiableTag::ReadVector(
+ const CryptoHandshakeMessage& msg,
+ const QuicTag** out,
+ size_t* out_length,
+ string* error_details) const {
+ DCHECK(error_details != NULL);
+ QuicErrorCode error = msg.GetTaglist(tag_, out, out_length);
+ switch (error) {
+ case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND:
+ if (presence_ == PRESENCE_REQUIRED) {
+ *error_details = "Missing " + QuicUtils::TagToString(tag_);
+ break;
+ }
+ error = QUIC_NO_ERROR;
+ *out_length = 1;
+ *out = &default_value_;
+
+ case QUIC_NO_ERROR:
+ break;
+ default:
+ *error_details = "Bad " + QuicUtils::TagToString(tag_);
+ break;
+ }
+ return error;
+}
+
+QuicErrorCode QuicNegotiableTag::ProcessClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ string* error_details) {
+ DCHECK(!negotiated_);
+ DCHECK(error_details != NULL);
+ const QuicTag* received_tags;
+ size_t received_tags_length;
+ QuicErrorCode error = ReadVector(client_hello, &received_tags,
+ &received_tags_length, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ QuicTag negotiated_tag;
+ if (!QuicUtils::FindMutualTag(possible_values_,
+ received_tags,
+ received_tags_length,
+ QuicUtils::LOCAL_PRIORITY,
+ &negotiated_tag,
+ NULL)) {
+ *error_details = "Unsuported " + QuicUtils::TagToString(tag_);
+ return QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP;
+ }
+
+ negotiated_ = true;
+ negotiated_tag_ = negotiated_tag;
+ return QUIC_NO_ERROR;
+}
+
+QuicErrorCode QuicNegotiableTag::ProcessServerHello(
+ const CryptoHandshakeMessage& server_hello,
+ string* error_details) {
+ DCHECK(!negotiated_);
+ DCHECK(error_details != NULL);
+ const QuicTag* received_tags;
+ size_t received_tags_length;
+ QuicErrorCode error = ReadVector(server_hello, &received_tags,
+ &received_tags_length, error_details);
+ if (error != QUIC_NO_ERROR) {
+ return error;
+ }
+
+ if (received_tags_length != 1 ||
+ std::find(possible_values_.begin(), possible_values_.end(),
+ *received_tags) == possible_values_.end()) {
+ *error_details = "Invalid " + QuicUtils::TagToString(tag_);
+ return QUIC_INVALID_NEGOTIATED_VALUE;
+ }
+
+ negotiated_ = true;
+ negotiated_tag_ = *received_tags;
+ return QUIC_NO_ERROR;
+}
+
+QuicConfig::QuicConfig() :
+ congestion_control_(kCGST, QuicNegotiableValue::PRESENCE_REQUIRED),
+ idle_connection_state_lifetime_seconds_(
+ kICSL, QuicNegotiableValue::PRESENCE_REQUIRED),
+ keepalive_timeout_seconds_(kKATO, QuicNegotiableValue::PRESENCE_OPTIONAL),
+ max_streams_per_connection_(kMSPC, QuicNegotiableValue::PRESENCE_REQUIRED),
+ max_time_before_crypto_handshake_(QuicTime::Delta::Zero()) {
+ idle_connection_state_lifetime_seconds_.set(0, 0);
+ keepalive_timeout_seconds_.set(0, 0);
+}
+
+QuicConfig::~QuicConfig() {}
+
+void QuicConfig::set_congestion_control(
+ const QuicTagVector& congestion_control,
+ QuicTag default_congestion_control) {
+ congestion_control_.set(congestion_control, default_congestion_control);
+}
+
+QuicTag QuicConfig::congestion_control() const {
+ return congestion_control_.GetTag();
+}
+
+void QuicConfig::set_idle_connection_state_lifetime(
+ QuicTime::Delta max_idle_connection_state_lifetime,
+ QuicTime::Delta default_idle_conection_state_lifetime) {
+ idle_connection_state_lifetime_seconds_.set(
+ max_idle_connection_state_lifetime.ToSeconds(),
+ default_idle_conection_state_lifetime.ToSeconds());
+}
+
+QuicTime::Delta QuicConfig::idle_connection_state_lifetime() const {
+ return QuicTime::Delta::FromSeconds(
+ idle_connection_state_lifetime_seconds_.GetUint32());
+}
+
+QuicTime::Delta QuicConfig::keepalive_timeout() const {
+ return QuicTime::Delta::FromSeconds(
+ keepalive_timeout_seconds_.GetUint32());
+}
+
+void QuicConfig::set_max_streams_per_connection(size_t max_streams,
+ size_t default_streams) {
+ max_streams_per_connection_.set(max_streams, default_streams);
+}
+
+uint32 QuicConfig::max_streams_per_connection() const {
+ return max_streams_per_connection_.GetUint32();
+}
+
+void QuicConfig::set_max_time_before_crypto_handshake(
+ QuicTime::Delta max_time_before_crypto_handshake) {
+ max_time_before_crypto_handshake_ = max_time_before_crypto_handshake;
+}
+
+QuicTime::Delta QuicConfig::max_time_before_crypto_handshake() const {
+ return max_time_before_crypto_handshake_;
+}
+
+bool QuicConfig::negotiated() {
+ return congestion_control_.negotiated() &&
+ idle_connection_state_lifetime_seconds_.negotiated() &&
+ keepalive_timeout_seconds_.negotiated() &&
+ max_streams_per_connection_.negotiated();
+}
+
+void QuicConfig::SetDefaults() {
+ congestion_control_.set(QuicTagVector(1, kQBIC), kQBIC);
+ idle_connection_state_lifetime_seconds_.set(kDefaultTimeoutSecs,
+ kDefaultInitialTimeoutSecs);
+ // kKATO is optional. Return 0 if not negotiated.
+ keepalive_timeout_seconds_.set(0, 0);
+ max_streams_per_connection_.set(kDefaultMaxStreamsPerConnection,
+ kDefaultMaxStreamsPerConnection);
+ max_time_before_crypto_handshake_ = QuicTime::Delta::FromSeconds(
+ kDefaultMaxTimeForCryptoHandshakeSecs);
+}
+
+void QuicConfig::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+ congestion_control_.ToHandshakeMessage(out);
+ idle_connection_state_lifetime_seconds_.ToHandshakeMessage(out);
+ keepalive_timeout_seconds_.ToHandshakeMessage(out);
+ max_streams_per_connection_.ToHandshakeMessage(out);
+}
+
+QuicErrorCode QuicConfig::ProcessClientHello(
+ const CryptoHandshakeMessage& client_hello,
+ string* error_details) {
+ DCHECK(error_details != NULL);
+
+ QuicErrorCode error = QUIC_NO_ERROR;
+ if (error == QUIC_NO_ERROR) {
+ error = congestion_control_.ProcessClientHello(client_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = idle_connection_state_lifetime_seconds_.ProcessClientHello(
+ client_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = keepalive_timeout_seconds_.ProcessClientHello(
+ client_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = max_streams_per_connection_.ProcessClientHello(
+ client_hello, error_details);
+ }
+ return error;
+}
+
+QuicErrorCode QuicConfig::ProcessServerHello(
+ const CryptoHandshakeMessage& server_hello,
+ string* error_details) {
+ DCHECK(error_details != NULL);
+
+ QuicErrorCode error = QUIC_NO_ERROR;
+ if (error == QUIC_NO_ERROR) {
+ error = congestion_control_.ProcessServerHello(server_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = idle_connection_state_lifetime_seconds_.ProcessServerHello(
+ server_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = keepalive_timeout_seconds_.ProcessServerHello(
+ server_hello, error_details);
+ }
+ if (error == QUIC_NO_ERROR) {
+ error = max_streams_per_connection_.ProcessServerHello(
+ server_hello, error_details);
+ }
+ return error;
+}
+
+} // namespace net
+
diff --git a/chromium/net/quic/quic_config.h b/chromium/net/quic/quic_config.h
new file mode 100644
index 00000000000..c07f332ac6f
--- /dev/null
+++ b/chromium/net/quic/quic_config.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2013 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_QUIC_QUIC_CONFIG_H_
+#define NET_QUIC_QUIC_CONFIG_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/quic/quic_utils.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicNegotiableValue {
+ public:
+ enum Presence {
+ // This negotiable value can be absent from the handshake message. Default
+ // value is selected as the negotiated value in such a case.
+ PRESENCE_OPTIONAL,
+ // This negotiable value is required in the handshake message otherwise the
+ // Process*Hello function returns an error.
+ PRESENCE_REQUIRED,
+ };
+
+ QuicNegotiableValue(QuicTag tag, Presence presence);
+
+ bool negotiated() const {
+ return negotiated_;
+ }
+
+ protected:
+ const QuicTag tag_;
+ const Presence presence_;
+ bool negotiated_;
+};
+
+class NET_EXPORT_PRIVATE QuicNegotiableUint32 : public QuicNegotiableValue {
+ public:
+ QuicNegotiableUint32(QuicTag name, Presence presence);
+
+ // Sets the maximum possible value that can be achieved after negotiation and
+ // also the default values to be assumed if PRESENCE_OPTIONAL and the *HLO msg
+ // doesn't contain a value corresponding to |name_|. |max| is serialised via
+ // ToHandshakeMessage call if |negotiated_| is false.
+ void set(uint32 max, uint32 default_value);
+
+ // Returns the value negotiated if |negotiated_| is true, otherwise returns
+ // default_value_ (used to set default values before negotiation finishes).
+ uint32 GetUint32() const;
+
+ // Serialises |name_| and value to |out|. If |negotiated_| is true then
+ // |negotiated_value_| is serialised, otherwise |max_value_| is serialised.
+ void ToHandshakeMessage(CryptoHandshakeMessage* out) const;
+
+ // Sets |negotiated_value_| to the minimum of |max_value_| and the
+ // corresponding value from |client_hello|. If the corresponding value is
+ // missing and PRESENCE_OPTIONAL then |negotiated_value_| is set to
+ // |default_value_|.
+ QuicErrorCode ProcessClientHello(const CryptoHandshakeMessage& client_hello,
+ std::string* error_details);
+
+ // Sets the |negotiated_value_| to the corresponding value from
+ // |server_hello|. Returns error if the value received in |server_hello| is
+ // greater than |max_value_|. If the corresponding value is missing and
+ // PRESENCE_OPTIONAL then |negotiated_value_| is set to |0|,
+ QuicErrorCode ProcessServerHello(const CryptoHandshakeMessage& server_hello,
+ std::string* error_details);
+
+ private:
+ // Reads the value corresponding to |name_| from |msg| into |out|. If the
+ // |name_| is absent in |msg| and |presence_| is set to OPTIONAL |out| is set
+ // to |max_value_|.
+ QuicErrorCode ReadUint32(const CryptoHandshakeMessage& msg,
+ uint32* out,
+ std::string* error_details) const;
+
+ uint32 max_value_;
+ uint32 default_value_;
+ uint32 negotiated_value_;
+};
+
+class NET_EXPORT_PRIVATE QuicNegotiableTag : public QuicNegotiableValue {
+ public:
+ QuicNegotiableTag(QuicTag name, Presence presence);
+ ~QuicNegotiableTag();
+
+ // Sets the possible values that |negotiated_tag_| can take after negotiation
+ // and the default value that |negotiated_tag_| takes if OPTIONAL and *HLO
+ // msg doesn't contain tag |name_|.
+ void set(const QuicTagVector& possible_values, QuicTag default_value);
+
+ // Returns the negotiated tag if |negotiated_| is true, otherwise returns
+ // |default_value_| (used to set default values before negotiation finishes).
+ QuicTag GetTag() const;
+
+ // Serialises |name_| and vector (either possible or negotiated) to |out|. If
+ // |negotiated_| is true then |negotiated_tag_| is serialised, otherwise
+ // |possible_values_| is serialised.
+ void ToHandshakeMessage(CryptoHandshakeMessage* out) const;
+
+ // Selects the tag common to both tags in |client_hello| for |name_| and
+ // |possible_values_| with preference to tag in |possible_values_|. The
+ // selected tag is set as |negotiated_tag_|.
+ QuicErrorCode ProcessClientHello(const CryptoHandshakeMessage& client_hello,
+ std::string* error_details);
+
+ // Sets the value for |name_| tag in |server_hello| as |negotiated_value_|.
+ // Returns error if the value received in |server_hello| isn't present in
+ // |possible_values_|.
+ QuicErrorCode ProcessServerHello(const CryptoHandshakeMessage& server_hello,
+ std::string* error_details);
+
+ private:
+ // Reads the vector corresponding to |name_| from |msg| into |out|. If the
+ // |name_| is absent in |msg| and |presence_| is set to OPTIONAL |out| is set
+ // to |possible_values_|.
+ QuicErrorCode ReadVector(const CryptoHandshakeMessage& msg,
+ const QuicTag** out,
+ size_t* out_length,
+ std::string* error_details) const;
+
+ QuicTag negotiated_tag_;
+ QuicTagVector possible_values_;
+ QuicTag default_value_;
+};
+
+// QuicConfig contains non-crypto configuration options that are negotiated in
+// the crypto handshake.
+class NET_EXPORT_PRIVATE QuicConfig {
+ public:
+ QuicConfig();
+ ~QuicConfig();
+
+ void set_congestion_control(const QuicTagVector& congestion_control,
+ QuicTag default_congestion_control);
+
+ QuicTag congestion_control() const;
+
+ void set_idle_connection_state_lifetime(
+ QuicTime::Delta max_idle_connection_state_lifetime,
+ QuicTime::Delta default_idle_conection_state_lifetime);
+
+ QuicTime::Delta idle_connection_state_lifetime() const;
+
+ QuicTime::Delta keepalive_timeout() const;
+
+ void set_max_streams_per_connection(size_t max_streams,
+ size_t default_streams);
+
+ uint32 max_streams_per_connection() const;
+
+ void set_max_time_before_crypto_handshake(
+ QuicTime::Delta max_time_before_crypto_handshake);
+
+ QuicTime::Delta max_time_before_crypto_handshake() const;
+
+ bool negotiated();
+
+ // SetDefaults sets the members to sensible, default values.
+ void SetDefaults();
+
+ // ToHandshakeMessage serializes the settings in this object as a series of
+ // tags /value pairs and adds them to |out|.
+ void ToHandshakeMessage(CryptoHandshakeMessage* out) const;
+
+ // Calls ProcessClientHello on each negotiable parameter. On failure returns
+ // the corresponding QuicErrorCode and sets detailed error in |error_details|.
+ QuicErrorCode ProcessClientHello(const CryptoHandshakeMessage& client_hello,
+ std::string* error_details);
+
+ // Calls ProcessServerHello on each negotiable parameter. On failure returns
+ // the corresponding QuicErrorCode and sets detailed error in |error_details|.
+ QuicErrorCode ProcessServerHello(const CryptoHandshakeMessage& server_hello,
+ std::string* error_details);
+
+ private:
+ // Congestion control feedback type.
+ QuicNegotiableTag congestion_control_;
+ // Idle connection state lifetime
+ QuicNegotiableUint32 idle_connection_state_lifetime_seconds_;
+ // Keepalive timeout, or 0 to turn off keepalive probes
+ QuicNegotiableUint32 keepalive_timeout_seconds_;
+ // Maximum number of streams that the connection can support.
+ QuicNegotiableUint32 max_streams_per_connection_;
+ // Maximum time till the session can be alive before crypto handshake is
+ // finished. (Not negotiated).
+ QuicTime::Delta max_time_before_crypto_handshake_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CONFIG_H_
diff --git a/chromium/net/quic/quic_config_test.cc b/chromium/net/quic/quic_config_test.cc
new file mode 100644
index 00000000000..eeaa97deaab
--- /dev/null
+++ b/chromium/net/quic/quic_config_test.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2013 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/quic/quic_config.h"
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicConfigTest : public ::testing::Test {
+ protected:
+ QuicConfigTest() {
+ config_.SetDefaults();
+ }
+
+ QuicConfig config_;
+};
+
+TEST_F(QuicConfigTest, ToHandshakeMessage) {
+ config_.set_idle_connection_state_lifetime(QuicTime::Delta::FromSeconds(5),
+ QuicTime::Delta::FromSeconds(2));
+ config_.set_max_streams_per_connection(4, 2);
+ CryptoHandshakeMessage msg;
+ config_.ToHandshakeMessage(&msg);
+
+ uint32 value;
+ QuicErrorCode error = msg.GetUint32(kICSL, &value);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ EXPECT_EQ(5u, value);
+
+ error = msg.GetUint32(kMSPC, &value);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ EXPECT_EQ(4u, value);
+
+ const QuicTag* out;
+ size_t out_len;
+ error = msg.GetTaglist(kCGST, &out, &out_len);
+ EXPECT_EQ(1u, out_len);
+ EXPECT_EQ(kQBIC, *out);
+}
+
+TEST_F(QuicConfigTest, ProcessClientHello) {
+ QuicConfig client_config;
+ QuicTagVector cgst;
+ cgst.push_back(kINAR);
+ cgst.push_back(kQBIC);
+ client_config.set_congestion_control(cgst, kQBIC);
+ client_config.set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromSeconds(2 * kDefaultTimeoutSecs),
+ QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs));
+ client_config.set_max_streams_per_connection(
+ 2 * kDefaultMaxStreamsPerConnection, kDefaultMaxStreamsPerConnection);
+
+ CryptoHandshakeMessage msg;
+ client_config.ToHandshakeMessage(&msg);
+ string error_details;
+ const QuicErrorCode error = config_.ProcessClientHello(msg, &error_details);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ EXPECT_TRUE(config_.negotiated());
+ EXPECT_EQ(kQBIC, config_.congestion_control());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs),
+ config_.idle_connection_state_lifetime());
+ EXPECT_EQ(kDefaultMaxStreamsPerConnection,
+ config_.max_streams_per_connection());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(0), config_.keepalive_timeout());
+}
+
+TEST_F(QuicConfigTest, ProcessServerHello) {
+ QuicConfig server_config;
+ QuicTagVector cgst;
+ cgst.push_back(kQBIC);
+ server_config.set_congestion_control(cgst, kQBIC);
+ server_config.set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs / 2),
+ QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs / 2));
+ server_config.set_max_streams_per_connection(
+ kDefaultMaxStreamsPerConnection / 2,
+ kDefaultMaxStreamsPerConnection / 2);
+
+ CryptoHandshakeMessage msg;
+ server_config.ToHandshakeMessage(&msg);
+ string error_details;
+ const QuicErrorCode error = config_.ProcessServerHello(msg, &error_details);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ EXPECT_TRUE(config_.negotiated());
+ EXPECT_EQ(kQBIC, config_.congestion_control());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs / 2),
+ config_.idle_connection_state_lifetime());
+ EXPECT_EQ(kDefaultMaxStreamsPerConnection / 2,
+ config_.max_streams_per_connection());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(0), config_.keepalive_timeout());
+}
+
+TEST_F(QuicConfigTest, MissingValueInCHLO) {
+ CryptoHandshakeMessage msg;
+ msg.SetValue(kICSL, 1);
+ msg.SetVector(kCGST, QuicTagVector(1, kQBIC));
+ // Missing kMSPC. KATO is optional.
+ string error_details;
+ const QuicErrorCode error = config_.ProcessClientHello(msg, &error_details);
+ EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
+}
+
+TEST_F(QuicConfigTest, MissingValueInSHLO) {
+ CryptoHandshakeMessage msg;
+ msg.SetValue(kICSL, 1);
+ msg.SetValue(kMSPC, 3);
+ // Missing CGST. KATO is optional.
+ string error_details;
+ const QuicErrorCode error = config_.ProcessServerHello(msg, &error_details);
+ EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
+}
+
+TEST_F(QuicConfigTest, OutOfBoundSHLO) {
+ QuicConfig server_config;
+ server_config.set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromSeconds(2 * kDefaultTimeoutSecs),
+ QuicTime::Delta::FromSeconds(2 * kDefaultTimeoutSecs));
+
+ CryptoHandshakeMessage msg;
+ server_config.ToHandshakeMessage(&msg);
+ string error_details;
+ const QuicErrorCode error = config_.ProcessServerHello(msg, &error_details);
+ EXPECT_EQ(QUIC_INVALID_NEGOTIATED_VALUE, error);
+}
+
+TEST_F(QuicConfigTest, MultipleNegotiatedValuesInVectorTag) {
+ QuicConfig server_config;
+ QuicTagVector cgst;
+ cgst.push_back(kQBIC);
+ cgst.push_back(kINAR);
+ server_config.set_congestion_control(cgst, kQBIC);
+
+ CryptoHandshakeMessage msg;
+ server_config.ToHandshakeMessage(&msg);
+ string error_details;
+ const QuicErrorCode error = config_.ProcessServerHello(msg, &error_details);
+ EXPECT_EQ(QUIC_INVALID_NEGOTIATED_VALUE, error);
+}
+
+TEST_F(QuicConfigTest, NoOverLapInCGST) {
+ QuicConfig server_config;
+ server_config.SetDefaults();
+ QuicTagVector cgst;
+ cgst.push_back(kINAR);
+ server_config.set_congestion_control(cgst, kINAR);
+
+ CryptoHandshakeMessage msg;
+ string error_details;
+ server_config.ToHandshakeMessage(&msg);
+ const QuicErrorCode error = config_.ProcessClientHello(msg, &error_details);
+ LOG(INFO) << QuicUtils::ErrorToString(error);
+ EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP, error);
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_connection.cc b/chromium/net/quic/quic_connection.cc
new file mode 100644
index 00000000000..718b78864c9
--- /dev/null
+++ b/chromium/net/quic/quic_connection.cc
@@ -0,0 +1,1661 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_connection.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_utils.h"
+
+using base::hash_map;
+using base::hash_set;
+using base::StringPiece;
+using std::list;
+using std::make_pair;
+using std::min;
+using std::max;
+using std::vector;
+using std::set;
+using std::string;
+
+namespace net {
+namespace {
+
+// The largest gap in packets we'll accept without closing the connection.
+// This will likely have to be tuned.
+const QuicPacketSequenceNumber kMaxPacketGap = 5000;
+
+// We want to make sure if we get a large nack packet, we don't queue up too
+// many packets at once. 10 is arbitrary.
+const int kMaxRetransmissionsPerAck = 10;
+
+// TCP retransmits after 2 nacks. We allow for a third in case of out-of-order
+// delivery.
+// TODO(ianswett): Change to match TCP's rule of retransmitting once an ack
+// at least 3 sequence numbers larger arrives.
+const size_t kNumberOfNacksBeforeRetransmission = 3;
+
+// The maxiumum number of packets we'd like to queue. We may end up queueing
+// more in the case of many control frames.
+// 6 is arbitrary.
+const int kMaxPacketsToSerializeAtOnce = 6;
+
+// Limit the number of packets we send per retransmission-alarm so we
+// eventually cede. 10 is arbitrary.
+const size_t kMaxPacketsPerRetransmissionAlarm = 10;
+
+// Limit the number of FEC groups to two. If we get enough out of order packets
+// that this becomes limiting, we can revisit.
+const size_t kMaxFecGroups = 2;
+
+// Limit the number of undecryptable packets we buffer in
+// expectation of the CHLO/SHLO arriving.
+const size_t kMaxUndecryptablePackets = 10;
+
+bool Near(QuicPacketSequenceNumber a, QuicPacketSequenceNumber b) {
+ QuicPacketSequenceNumber delta = (a > b) ? a - b : b - a;
+ return delta <= kMaxPacketGap;
+}
+
+
+// An alarm that is scheduled to send an ack if a timeout occurs.
+class AckAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit AckAlarm(QuicConnection* connection)
+ : connection_(connection) {
+ }
+
+ virtual QuicTime OnAlarm() OVERRIDE {
+ connection_->SendAck();
+ return QuicTime::Zero();
+ }
+
+ private:
+ QuicConnection* connection_;
+};
+
+// This alarm will be scheduled any time a data-bearing packet is sent out.
+// When the alarm goes off, the connection checks to see if the oldest packets
+// have been acked, and retransmit them if they have not.
+class RetransmissionAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit RetransmissionAlarm(QuicConnection* connection)
+ : connection_(connection) {
+ }
+
+ virtual QuicTime OnAlarm() OVERRIDE {
+ return connection_->OnRetransmissionTimeout();
+ }
+
+ private:
+ QuicConnection* connection_;
+};
+
+// An alarm that is scheduled when the sent scheduler requires a
+// a delay before sending packets and fires when the packet may be sent.
+class SendAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit SendAlarm(QuicConnection* connection)
+ : connection_(connection) {
+ }
+
+ virtual QuicTime OnAlarm() OVERRIDE {
+ connection_->OnCanWrite();
+ // Never reschedule the alarm, since OnCanWrite does that.
+ return QuicTime::Zero();
+ }
+
+ private:
+ QuicConnection* connection_;
+};
+
+class TimeoutAlarm : public QuicAlarm::Delegate {
+ public:
+ explicit TimeoutAlarm(QuicConnection* connection)
+ : connection_(connection) {
+ }
+
+ virtual QuicTime OnAlarm() OVERRIDE {
+ connection_->CheckForTimeout();
+ // Never reschedule the alarm, since CheckForTimeout does that.
+ return QuicTime::Zero();
+ }
+
+ private:
+ QuicConnection* connection_;
+};
+
+} // namespace
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+QuicConnection::QuicConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper,
+ bool is_server,
+ QuicVersion version)
+ : framer_(version,
+ helper->GetClock()->ApproximateNow(),
+ is_server),
+ helper_(helper),
+ encryption_level_(ENCRYPTION_NONE),
+ clock_(helper->GetClock()),
+ random_generator_(helper->GetRandomGenerator()),
+ guid_(guid),
+ peer_address_(address),
+ largest_seen_packet_with_ack_(0),
+ handling_retransmission_timeout_(false),
+ write_blocked_(false),
+ ack_alarm_(helper->CreateAlarm(new AckAlarm(this))),
+ retransmission_alarm_(helper->CreateAlarm(new RetransmissionAlarm(this))),
+ send_alarm_(helper->CreateAlarm(new SendAlarm(this))),
+ timeout_alarm_(helper->CreateAlarm(new TimeoutAlarm(this))),
+ debug_visitor_(NULL),
+ packet_creator_(guid_, &framer_, random_generator_, is_server),
+ packet_generator_(this, NULL, &packet_creator_),
+ idle_network_timeout_(
+ QuicTime::Delta::FromSeconds(kDefaultInitialTimeoutSecs)),
+ overall_connection_timeout_(QuicTime::Delta::Infinite()),
+ creation_time_(clock_->ApproximateNow()),
+ time_of_last_received_packet_(clock_->ApproximateNow()),
+ time_of_last_sent_packet_(clock_->ApproximateNow()),
+ congestion_manager_(clock_, kTCP),
+ version_negotiation_state_(START_NEGOTIATION),
+ max_packets_per_retransmission_alarm_(kMaxPacketsPerRetransmissionAlarm),
+ is_server_(is_server),
+ connected_(true),
+ received_truncated_ack_(false),
+ send_ack_in_response_to_packet_(false),
+ address_migrating_(false) {
+ helper_->SetConnection(this);
+ timeout_alarm_->Set(clock_->ApproximateNow().Add(idle_network_timeout_));
+ framer_.set_visitor(this);
+ framer_.set_received_entropy_calculator(&received_packet_manager_);
+
+ /*
+ if (FLAGS_fake_packet_loss_percentage > 0) {
+ int32 seed = RandomBase::WeakSeed32();
+ LOG(INFO) << ENDPOINT << "Seeding packet loss with " << seed;
+ random_.reset(new MTRandom(seed));
+ }
+ */
+}
+
+QuicConnection::~QuicConnection() {
+ STLDeleteElements(&undecryptable_packets_);
+ STLDeleteValues(&unacked_packets_);
+ STLDeleteValues(&group_map_);
+ for (QueuedPacketList::iterator it = queued_packets_.begin();
+ it != queued_packets_.end(); ++it) {
+ delete it->packet;
+ }
+}
+
+bool QuicConnection::SelectMutualVersion(
+ const QuicVersionVector& available_versions) {
+ // Try to find the highest mutual version by iterating over supported
+ // versions, starting with the highest, and breaking out of the loop once we
+ // find a matching version in the provided available_versions vector.
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ const QuicVersion& version = kSupportedQuicVersions[i];
+ if (std::find(available_versions.begin(), available_versions.end(),
+ version) != available_versions.end()) {
+ framer_.set_version(version);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QuicConnection::OnError(QuicFramer* framer) {
+ // Packets that we cannot decrypt are dropped.
+ // TODO(rch): add stats to measure this.
+ if (!connected_ || framer->error() == QUIC_DECRYPTION_FAILURE) {
+ return;
+ }
+ SendConnectionClose(framer->error());
+}
+
+void QuicConnection::OnPacket() {
+ DCHECK(last_stream_frames_.empty() &&
+ last_goaway_frames_.empty() &&
+ last_rst_frames_.empty() &&
+ last_ack_frames_.empty() &&
+ last_congestion_frames_.empty());
+}
+
+void QuicConnection::OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) {
+ if (debug_visitor_) {
+ debug_visitor_->OnPublicResetPacket(packet);
+ }
+ CloseConnection(QUIC_PUBLIC_RESET, true);
+}
+
+bool QuicConnection::OnProtocolVersionMismatch(QuicVersion received_version) {
+ // TODO(satyamshekhar): Implement no server state in this mode.
+ if (!is_server_) {
+ LOG(DFATAL) << ENDPOINT << "Framer called OnProtocolVersionMismatch. "
+ << "Closing connection.";
+ CloseConnection(QUIC_INTERNAL_ERROR, false);
+ return false;
+ }
+ DCHECK_NE(version(), received_version);
+
+ if (debug_visitor_) {
+ debug_visitor_->OnProtocolVersionMismatch(received_version);
+ }
+
+ switch (version_negotiation_state_) {
+ case START_NEGOTIATION:
+ if (!framer_.IsSupportedVersion(received_version)) {
+ SendVersionNegotiationPacket();
+ version_negotiation_state_ = NEGOTIATION_IN_PROGRESS;
+ return false;
+ }
+ break;
+
+ case NEGOTIATION_IN_PROGRESS:
+ if (!framer_.IsSupportedVersion(received_version)) {
+ // Drop packets which can't be parsed due to version mismatch.
+ return false;
+ }
+ break;
+
+ case NEGOTIATED_VERSION:
+ // Might be old packets that were sent by the client before the version
+ // was negotiated. Drop these.
+ return false;
+
+ default:
+ DCHECK(false);
+ }
+
+ version_negotiation_state_ = NEGOTIATED_VERSION;
+
+ // Store the new version.
+ framer_.set_version(received_version);
+
+ // TODO(satyamshekhar): Store the sequence number of this packet and close the
+ // connection if we ever received a packet with incorrect version and whose
+ // sequence number is greater.
+ return true;
+}
+
+// Handles version negotiation for client connection.
+void QuicConnection::OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) {
+ if (is_server_) {
+ LOG(DFATAL) << ENDPOINT << "Framer parsed VersionNegotiationPacket."
+ << " Closing connection.";
+ CloseConnection(QUIC_INTERNAL_ERROR, false);
+ return;
+ }
+ if (debug_visitor_) {
+ debug_visitor_->OnVersionNegotiationPacket(packet);
+ }
+
+ if (version_negotiation_state_ != START_NEGOTIATION) {
+ // Possibly a duplicate version negotiation packet.
+ return;
+ }
+
+ if (std::find(packet.versions.begin(),
+ packet.versions.end(), version()) !=
+ packet.versions.end()) {
+ DLOG(WARNING) << ENDPOINT << "The server already supports our version. "
+ << "It should have accepted our connection.";
+ // Just drop the connection.
+ CloseConnection(QUIC_INVALID_VERSION_NEGOTIATION_PACKET, false);
+ return;
+ }
+
+ if (!SelectMutualVersion(packet.versions)) {
+ SendConnectionCloseWithDetails(QUIC_INVALID_VERSION,
+ "no common version found");
+ return;
+ }
+
+ version_negotiation_state_ = NEGOTIATION_IN_PROGRESS;
+ RetransmitUnackedPackets(ALL_PACKETS);
+}
+
+void QuicConnection::OnRevivedPacket() {
+}
+
+bool QuicConnection::OnPacketHeader(const QuicPacketHeader& header) {
+ if (debug_visitor_) {
+ debug_visitor_->OnPacketHeader(header);
+ }
+
+ if (!ProcessValidatedPacket()) {
+ return false;
+ }
+
+ // Will be decrement below if we fall through to return true;
+ ++stats_.packets_dropped;
+
+ if (header.public_header.guid != guid_) {
+ DLOG(INFO) << ENDPOINT << "Ignoring packet from unexpected GUID: "
+ << header.public_header.guid << " instead of " << guid_;
+ return false;
+ }
+
+ if (!Near(header.packet_sequence_number,
+ last_header_.packet_sequence_number)) {
+ DLOG(INFO) << ENDPOINT << "Packet " << header.packet_sequence_number
+ << " out of bounds. Discarding";
+ SendConnectionCloseWithDetails(QUIC_INVALID_PACKET_HEADER,
+ "Packet sequence number out of bounds");
+ return false;
+ }
+
+ // If this packet has already been seen, or that the sender
+ // has told us will not be retransmitted, then stop processing the packet.
+ if (!received_packet_manager_.IsAwaitingPacket(
+ header.packet_sequence_number)) {
+ return false;
+ }
+
+ if (version_negotiation_state_ != NEGOTIATED_VERSION) {
+ if (is_server_) {
+ if (!header.public_header.version_flag) {
+ DLOG(WARNING) << ENDPOINT << "Got packet without version flag before "
+ << "version negotiated.";
+ // Packets should have the version flag till version negotiation is
+ // done.
+ CloseConnection(QUIC_INVALID_VERSION, false);
+ return false;
+ } else {
+ DCHECK_EQ(1u, header.public_header.versions.size());
+ DCHECK_EQ(header.public_header.versions[0], version());
+ version_negotiation_state_ = NEGOTIATED_VERSION;
+ }
+ } else {
+ DCHECK(!header.public_header.version_flag);
+ // If the client gets a packet without the version flag from the server
+ // it should stop sending version since the version negotiation is done.
+ packet_creator_.StopSendingVersion();
+ version_negotiation_state_ = NEGOTIATED_VERSION;
+ }
+ }
+
+ DCHECK_EQ(NEGOTIATED_VERSION, version_negotiation_state_);
+
+ --stats_.packets_dropped;
+ DVLOG(1) << ENDPOINT << "Received packet header: " << header;
+ last_header_ = header;
+ DCHECK(connected_);
+ return true;
+}
+
+void QuicConnection::OnFecProtectedPayload(StringPiece payload) {
+ DCHECK_EQ(IN_FEC_GROUP, last_header_.is_in_fec_group);
+ DCHECK_NE(0u, last_header_.fec_group);
+ QuicFecGroup* group = GetFecGroup();
+ if (group != NULL) {
+ group->Update(last_header_, payload);
+ }
+}
+
+bool QuicConnection::OnStreamFrame(const QuicStreamFrame& frame) {
+ DCHECK(connected_);
+ if (debug_visitor_) {
+ debug_visitor_->OnStreamFrame(frame);
+ }
+ last_stream_frames_.push_back(frame);
+ return true;
+}
+
+bool QuicConnection::OnAckFrame(const QuicAckFrame& incoming_ack) {
+ DCHECK(connected_);
+ if (debug_visitor_) {
+ debug_visitor_->OnAckFrame(incoming_ack);
+ }
+ DVLOG(1) << ENDPOINT << "OnAckFrame: " << incoming_ack;
+
+ if (last_header_.packet_sequence_number <= largest_seen_packet_with_ack_) {
+ DLOG(INFO) << ENDPOINT << "Received an old ack frame: ignoring";
+ return true;
+ }
+
+ if (!ValidateAckFrame(incoming_ack)) {
+ SendConnectionClose(QUIC_INVALID_ACK_DATA);
+ return false;
+ }
+ last_ack_frames_.push_back(incoming_ack);
+ return connected_;
+}
+
+void QuicConnection::ProcessAckFrame(const QuicAckFrame& incoming_ack) {
+ largest_seen_packet_with_ack_ = last_header_.packet_sequence_number;
+
+ received_truncated_ack_ =
+ incoming_ack.received_info.missing_packets.size() >=
+ QuicFramer::GetMaxUnackedPackets(last_header_);
+
+ received_packet_manager_.UpdatePacketInformationReceivedByPeer(incoming_ack);
+ received_packet_manager_.UpdatePacketInformationSentByPeer(incoming_ack);
+ // Possibly close any FecGroups which are now irrelevant.
+ CloseFecGroupsBefore(incoming_ack.sent_info.least_unacked + 1);
+
+ sent_entropy_manager_.ClearEntropyBefore(
+ received_packet_manager_.least_packet_awaited_by_peer() - 1);
+
+ SequenceNumberSet acked_packets;
+ HandleAckForSentPackets(incoming_ack, &acked_packets);
+ HandleAckForSentFecPackets(incoming_ack, &acked_packets);
+ if (acked_packets.size() > 0) {
+ visitor_->OnAck(acked_packets);
+ }
+ congestion_manager_.OnIncomingAckFrame(incoming_ack,
+ time_of_last_received_packet_);
+}
+
+bool QuicConnection::OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& feedback) {
+ DCHECK(connected_);
+ if (debug_visitor_) {
+ debug_visitor_->OnCongestionFeedbackFrame(feedback);
+ }
+ last_congestion_frames_.push_back(feedback);
+ return connected_;
+}
+
+bool QuicConnection::ValidateAckFrame(const QuicAckFrame& incoming_ack) {
+ if (incoming_ack.received_info.largest_observed >
+ packet_creator_.sequence_number()) {
+ DLOG(ERROR) << ENDPOINT << "Peer's observed unsent packet:"
+ << incoming_ack.received_info.largest_observed << " vs "
+ << packet_creator_.sequence_number();
+ // We got an error for data we have not sent. Error out.
+ return false;
+ }
+
+ if (incoming_ack.received_info.largest_observed <
+ received_packet_manager_.peer_largest_observed_packet()) {
+ DLOG(ERROR) << ENDPOINT << "Peer's largest_observed packet decreased:"
+ << incoming_ack.received_info.largest_observed << " vs "
+ << received_packet_manager_.peer_largest_observed_packet();
+ // A new ack has a diminished largest_observed value. Error out.
+ // If this was an old packet, we wouldn't even have checked.
+ return false;
+ }
+
+ // We can't have too many unacked packets, or our ack frames go over
+ // kMaxPacketSize.
+ DCHECK_LE(incoming_ack.received_info.missing_packets.size(),
+ QuicFramer::GetMaxUnackedPackets(last_header_));
+
+ if (incoming_ack.sent_info.least_unacked <
+ received_packet_manager_.peer_least_packet_awaiting_ack()) {
+ DLOG(ERROR) << ENDPOINT << "Peer's sent low least_unacked: "
+ << incoming_ack.sent_info.least_unacked << " vs "
+ << received_packet_manager_.peer_least_packet_awaiting_ack();
+ // We never process old ack frames, so this number should only increase.
+ return false;
+ }
+
+ if (incoming_ack.sent_info.least_unacked >
+ last_header_.packet_sequence_number) {
+ DLOG(ERROR) << ENDPOINT << "Peer sent least_unacked:"
+ << incoming_ack.sent_info.least_unacked
+ << " greater than the enclosing packet sequence number:"
+ << last_header_.packet_sequence_number;
+ return false;
+ }
+
+ if (!incoming_ack.received_info.missing_packets.empty() &&
+ *incoming_ack.received_info.missing_packets.rbegin() >
+ incoming_ack.received_info.largest_observed) {
+ DLOG(ERROR) << ENDPOINT << "Peer sent missing packet: "
+ << *incoming_ack.received_info.missing_packets.rbegin()
+ << " greater than largest observed: "
+ << incoming_ack.received_info.largest_observed;
+ return false;
+ }
+
+ if (!incoming_ack.received_info.missing_packets.empty() &&
+ *incoming_ack.received_info.missing_packets.begin() <
+ received_packet_manager_.least_packet_awaited_by_peer()) {
+ DLOG(ERROR) << ENDPOINT << "Peer sent missing packet: "
+ << *incoming_ack.received_info.missing_packets.begin()
+ << "smaller than least_packet_awaited_by_peer_: "
+ << received_packet_manager_.least_packet_awaited_by_peer();
+ return false;
+ }
+
+ if (!sent_entropy_manager_.IsValidEntropy(
+ incoming_ack.received_info.largest_observed,
+ incoming_ack.received_info.missing_packets,
+ incoming_ack.received_info.entropy_hash)) {
+ DLOG(ERROR) << ENDPOINT << "Peer sent invalid entropy.";
+ return false;
+ }
+
+ return true;
+}
+
+void QuicConnection::HandleAckForSentPackets(const QuicAckFrame& incoming_ack,
+ SequenceNumberSet* acked_packets) {
+ int retransmitted_packets = 0;
+ // Go through the packets we have not received an ack for and see if this
+ // incoming_ack shows they've been seen by the peer.
+ UnackedPacketMap::iterator it = unacked_packets_.begin();
+ while (it != unacked_packets_.end()) {
+ QuicPacketSequenceNumber sequence_number = it->first;
+ if (sequence_number >
+ received_packet_manager_.peer_largest_observed_packet()) {
+ // These are very new sequence_numbers.
+ break;
+ }
+ RetransmittableFrames* unacked = it->second;
+ if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) {
+ // Packet was acked, so remove it from our unacked packet list.
+ DVLOG(1) << ENDPOINT <<"Got an ack for packet " << sequence_number;
+ acked_packets->insert(sequence_number);
+ delete unacked;
+ unacked_packets_.erase(it++);
+ retransmission_map_.erase(sequence_number);
+ } else {
+ // This is a packet which we planned on retransmitting and has not been
+ // seen at the time of this ack being sent out. See if it's our new
+ // lowest unacked packet.
+ DVLOG(1) << ENDPOINT << "still missing packet " << sequence_number;
+ ++it;
+ // The peer got packets after this sequence number. This is an explicit
+ // nack.
+ RetransmissionMap::iterator retransmission_it =
+ retransmission_map_.find(sequence_number);
+ ++(retransmission_it->second.number_nacks);
+ if (retransmission_it->second.number_nacks >=
+ kNumberOfNacksBeforeRetransmission &&
+ retransmitted_packets < kMaxRetransmissionsPerAck) {
+ ++retransmitted_packets;
+ DVLOG(1) << ENDPOINT << "Trying to retransmit packet "
+ << sequence_number
+ << " as it has been nacked 3 or more times.";
+ // RetransmitPacket will retransmit with a new sequence_number.
+ RetransmitPacket(sequence_number);
+ }
+ }
+ }
+}
+
+void QuicConnection::HandleAckForSentFecPackets(
+ const QuicAckFrame& incoming_ack, SequenceNumberSet* acked_packets) {
+ UnackedPacketMap::iterator it = unacked_fec_packets_.begin();
+ while (it != unacked_fec_packets_.end()) {
+ QuicPacketSequenceNumber sequence_number = it->first;
+ if (sequence_number >
+ received_packet_manager_.peer_largest_observed_packet()) {
+ break;
+ }
+ if (!IsAwaitingPacket(incoming_ack.received_info, sequence_number)) {
+ DVLOG(1) << ENDPOINT << "Got an ack for fec packet: " << sequence_number;
+ acked_packets->insert(sequence_number);
+ unacked_fec_packets_.erase(it++);
+ } else {
+ DVLOG(1) << ENDPOINT << "Still missing ack for fec packet: "
+ << sequence_number;
+ ++it;
+ }
+ }
+}
+
+void QuicConnection::OnFecData(const QuicFecData& fec) {
+ DCHECK_EQ(IN_FEC_GROUP, last_header_.is_in_fec_group);
+ DCHECK_NE(0u, last_header_.fec_group);
+ QuicFecGroup* group = GetFecGroup();
+ if (group != NULL) {
+ group->UpdateFec(last_header_.packet_sequence_number,
+ last_header_.entropy_flag, fec);
+ }
+}
+
+bool QuicConnection::OnRstStreamFrame(const QuicRstStreamFrame& frame) {
+ DCHECK(connected_);
+ if (debug_visitor_) {
+ debug_visitor_->OnRstStreamFrame(frame);
+ }
+ DLOG(INFO) << ENDPOINT << "Stream reset with error "
+ << QuicUtils::StreamErrorToString(frame.error_code);
+ last_rst_frames_.push_back(frame);
+ return connected_;
+}
+
+bool QuicConnection::OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) {
+ DCHECK(connected_);
+ if (debug_visitor_) {
+ debug_visitor_->OnConnectionCloseFrame(frame);
+ }
+ DLOG(INFO) << ENDPOINT << "Connection closed with error "
+ << QuicUtils::ErrorToString(frame.error_code)
+ << " " << frame.error_details;
+ CloseConnection(frame.error_code, true);
+ DCHECK(!connected_);
+ return false;
+}
+
+bool QuicConnection::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+ DCHECK(connected_);
+ DLOG(INFO) << ENDPOINT << "Go away received with error "
+ << QuicUtils::ErrorToString(frame.error_code)
+ << " and reason:" << frame.reason_phrase;
+ last_goaway_frames_.push_back(frame);
+ return connected_;
+}
+
+void QuicConnection::OnPacketComplete() {
+ // Don't do anything if this packet closed the connection.
+ if (!connected_) {
+ ClearLastFrames();
+ return;
+ }
+
+ DLOG(INFO) << ENDPOINT << (last_packet_revived_ ? "Revived" : "Got")
+ << " packet " << last_header_.packet_sequence_number
+ << " with " << last_ack_frames_.size() << " acks, "
+ << last_congestion_frames_.size() << " congestions, "
+ << last_goaway_frames_.size() << " goaways, "
+ << last_rst_frames_.size() << " rsts, "
+ << last_stream_frames_.size()
+ << " stream frames for " << last_header_.public_header.guid;
+ if (!last_packet_revived_) {
+ congestion_manager_.RecordIncomingPacket(
+ last_size_, last_header_.packet_sequence_number,
+ time_of_last_received_packet_, last_packet_revived_);
+ }
+
+ // Must called before ack processing, because processing acks removes entries
+ // from unacket_packets_, increasing the least_unacked.
+ const bool last_packet_should_instigate_ack = ShouldLastPacketInstigateAck();
+
+ if ((last_stream_frames_.empty() ||
+ visitor_->OnPacket(self_address_, peer_address_,
+ last_header_, last_stream_frames_))) {
+ received_packet_manager_.RecordPacketReceived(
+ last_header_, time_of_last_received_packet_);
+ }
+
+ // Process stream resets, then acks, then congestion feedback.
+ for (size_t i = 0; i < last_goaway_frames_.size(); ++i) {
+ visitor_->OnGoAway(last_goaway_frames_[i]);
+ }
+ for (size_t i = 0; i < last_rst_frames_.size(); ++i) {
+ visitor_->OnRstStream(last_rst_frames_[i]);
+ }
+ for (size_t i = 0; i < last_ack_frames_.size(); ++i) {
+ ProcessAckFrame(last_ack_frames_[i]);
+ }
+ for (size_t i = 0; i < last_congestion_frames_.size(); ++i) {
+ congestion_manager_.OnIncomingQuicCongestionFeedbackFrame(
+ last_congestion_frames_[i], time_of_last_received_packet_);
+ }
+
+ MaybeSendInResponseToPacket(last_packet_should_instigate_ack);
+
+ ClearLastFrames();
+}
+
+void QuicConnection::ClearLastFrames() {
+ last_stream_frames_.clear();
+ last_goaway_frames_.clear();
+ last_rst_frames_.clear();
+ last_ack_frames_.clear();
+ last_congestion_frames_.clear();
+}
+
+QuicAckFrame* QuicConnection::CreateAckFrame() {
+ QuicAckFrame* outgoing_ack = new QuicAckFrame();
+ received_packet_manager_.UpdateReceivedPacketInfo(
+ &(outgoing_ack->received_info), clock_->ApproximateNow());
+ UpdateSentPacketInfo(&(outgoing_ack->sent_info));
+ DVLOG(1) << ENDPOINT << "Creating ack frame: " << *outgoing_ack;
+ return outgoing_ack;
+}
+
+QuicCongestionFeedbackFrame* QuicConnection::CreateFeedbackFrame() {
+ return new QuicCongestionFeedbackFrame(outgoing_congestion_feedback_);
+}
+
+bool QuicConnection::ShouldLastPacketInstigateAck() {
+ if (!last_stream_frames_.empty() ||
+ !last_goaway_frames_.empty() ||
+ !last_rst_frames_.empty()) {
+ return true;
+ }
+
+ // If the peer is still waiting for a packet that we are no
+ // longer planning to send, we should send an ack to raise
+ // the high water mark.
+ if (!last_ack_frames_.empty() &&
+ !last_ack_frames_.back().received_info.missing_packets.empty() &&
+ !unacked_packets_.empty()) {
+ if (unacked_packets_.begin()->first >
+ *last_ack_frames_.back().received_info.missing_packets.begin()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QuicConnection::MaybeSendInResponseToPacket(
+ bool last_packet_should_instigate_ack) {
+ // TODO(ianswett): Better merge these two blocks to queue up an ack if
+ // necessary, then either only send the ack or bundle it with other data.
+ if (!last_ack_frames_.empty()) {
+ // Now the we have received an ack, we might be able to send packets which
+ // are queued locally, or drain streams which are blocked.
+ QuicTime::Delta delay = congestion_manager_.TimeUntilSend(
+ time_of_last_received_packet_, NOT_RETRANSMISSION,
+ HAS_RETRANSMITTABLE_DATA, NOT_HANDSHAKE);
+ if (delay.IsZero()) {
+ send_alarm_->Cancel();
+ WriteIfNotBlocked();
+ } else if (!delay.IsInfinite()) {
+ send_alarm_->Cancel();
+ send_alarm_->Set(time_of_last_received_packet_.Add(delay));
+ }
+ }
+
+ if (!last_packet_should_instigate_ack) {
+ return;
+ }
+
+ if (send_ack_in_response_to_packet_) {
+ SendAck();
+ } else if (!last_stream_frames_.empty()) {
+ // TODO(alyssar) this case should really be "if the packet contained any
+ // non-ack frame", rather than "if the packet contained a stream frame"
+ if (!ack_alarm_->IsSet()) {
+ ack_alarm_->Set(clock_->ApproximateNow().Add(
+ congestion_manager_.DefaultRetransmissionTime()));
+ }
+ }
+ send_ack_in_response_to_packet_ = !send_ack_in_response_to_packet_;
+}
+
+void QuicConnection::SendVersionNegotiationPacket() {
+ QuicVersionVector supported_versions;
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ supported_versions.push_back(kSupportedQuicVersions[i]);
+ }
+ QuicEncryptedPacket* encrypted =
+ packet_creator_.SerializeVersionNegotiationPacket(supported_versions);
+ // TODO(satyamshekhar): implement zero server state negotiation.
+ int error;
+ helper_->WritePacketToWire(*encrypted, &error);
+ delete encrypted;
+}
+
+QuicConsumedData QuicConnection::SendStreamData(QuicStreamId id,
+ StringPiece data,
+ QuicStreamOffset offset,
+ bool fin) {
+ return packet_generator_.ConsumeData(id, data, offset, fin);
+}
+
+void QuicConnection::SendRstStream(QuicStreamId id,
+ QuicRstStreamErrorCode error) {
+ packet_generator_.AddControlFrame(
+ QuicFrame(new QuicRstStreamFrame(id, error)));
+}
+
+const QuicConnectionStats& QuicConnection::GetStats() {
+ // Update rtt and estimated bandwidth.
+ stats_.rtt = congestion_manager_.SmoothedRtt().ToMicroseconds();
+ stats_.estimated_bandwidth =
+ congestion_manager_.BandwidthEstimate().ToBytesPerSecond();
+ return stats_;
+}
+
+void QuicConnection::ProcessUdpPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) {
+ if (!connected_) {
+ return;
+ }
+ if (debug_visitor_) {
+ debug_visitor_->OnPacketReceived(self_address, peer_address, packet);
+ }
+ last_packet_revived_ = false;
+ last_size_ = packet.length();
+
+ address_migrating_ = false;
+
+ if (peer_address_.address().empty()) {
+ peer_address_ = peer_address;
+ }
+ if (self_address_.address().empty()) {
+ self_address_ = self_address;
+ }
+
+ if (!(peer_address == peer_address_ && self_address == self_address_)) {
+ address_migrating_ = true;
+ }
+
+ stats_.bytes_received += packet.length();
+ ++stats_.packets_received;
+
+ if (!framer_.ProcessPacket(packet)) {
+ // If we are unable to decrypt this packet, it might be
+ // because the CHLO or SHLO packet was lost.
+ if (encryption_level_ != ENCRYPTION_FORWARD_SECURE &&
+ framer_.error() == QUIC_DECRYPTION_FAILURE &&
+ undecryptable_packets_.size() < kMaxUndecryptablePackets) {
+ QueueUndecryptablePacket(packet);
+ }
+ DVLOG(1) << ENDPOINT << "Unable to process packet. Last packet processed: "
+ << last_header_.packet_sequence_number;
+ return;
+ }
+ MaybeProcessUndecryptablePackets();
+ MaybeProcessRevivedPacket();
+}
+
+bool QuicConnection::OnCanWrite() {
+ write_blocked_ = false;
+ return DoWrite();
+}
+
+bool QuicConnection::WriteIfNotBlocked() {
+ if (write_blocked_) {
+ return false;
+ }
+ return DoWrite();
+}
+
+bool QuicConnection::DoWrite() {
+ DCHECK(!write_blocked_);
+ WriteQueuedPackets();
+
+ // Sending queued packets may have caused the socket to become write blocked,
+ // or the congestion manager to prohibit sending. If we've sent everything
+ // we had queued and we're still not blocked, let the visitor know it can
+ // write more.
+ if (CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+ NOT_HANDSHAKE)) {
+ packet_generator_.StartBatchOperations();
+ bool all_bytes_written = visitor_->OnCanWrite();
+ packet_generator_.FinishBatchOperations();
+
+ // After the visitor writes, it may have caused the socket to become write
+ // blocked or the congestion manager to prohibit sending, so check again.
+ if (!write_blocked_ && !all_bytes_written &&
+ CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+ NOT_HANDSHAKE)) {
+ // We're not write blocked, but some stream didn't write out all of its
+ // bytes. Register for 'immediate' resumption so we'll keep writing after
+ // other quic connections have had a chance to use the socket.
+ send_alarm_->Cancel();
+ send_alarm_->Set(clock_->ApproximateNow());
+ }
+ }
+
+ return !write_blocked_;
+}
+
+bool QuicConnection::ProcessValidatedPacket() {
+ if (address_migrating_) {
+ SendConnectionCloseWithDetails(
+ QUIC_ERROR_MIGRATING_ADDRESS,
+ "Address migration is not yet a supported feature");
+ return false;
+ }
+ time_of_last_received_packet_ = clock_->Now();
+ DVLOG(1) << ENDPOINT << "time of last received packet: "
+ << time_of_last_received_packet_.ToDebuggingValue();
+ return true;
+}
+
+bool QuicConnection::WriteQueuedPackets() {
+ DCHECK(!write_blocked_);
+
+ size_t num_queued_packets = queued_packets_.size() + 1;
+ QueuedPacketList::iterator packet_iterator = queued_packets_.begin();
+ while (!write_blocked_ && packet_iterator != queued_packets_.end()) {
+ // Ensure that from one iteration of this loop to the next we
+ // succeeded in sending a packet so we don't infinitely loop.
+ // TODO(rch): clean up and close the connection if we really hit this.
+ DCHECK_LT(queued_packets_.size(), num_queued_packets);
+ num_queued_packets = queued_packets_.size();
+ if (WritePacket(packet_iterator->encryption_level,
+ packet_iterator->sequence_number,
+ packet_iterator->packet,
+ packet_iterator->retransmittable,
+ NO_FORCE)) {
+ packet_iterator = queued_packets_.erase(packet_iterator);
+ } else {
+ // Continue, because some queued packets may still be writable.
+ // This can happen if a retransmit send fail.
+ ++packet_iterator;
+ }
+ }
+
+ return !write_blocked_;
+}
+
+bool QuicConnection::MaybeRetransmitPacketForRTO(
+ QuicPacketSequenceNumber sequence_number) {
+ DCHECK_EQ(ContainsKey(unacked_packets_, sequence_number),
+ ContainsKey(retransmission_map_, sequence_number));
+
+ if (!ContainsKey(unacked_packets_, sequence_number)) {
+ DVLOG(2) << ENDPOINT << "alarm fired for " << sequence_number
+ << " but it has been acked or already retransmitted with"
+ << " different sequence number.";
+ // So no extra delay is added for this packet.
+ return true;
+ }
+
+ RetransmissionMap::iterator retransmission_it =
+ retransmission_map_.find(sequence_number);
+ // If the packet hasn't been acked and we're getting truncated acks, ignore
+ // any RTO for packets larger than the peer's largest observed packet; it may
+ // have been received by the peer and just wasn't acked due to the ack frame
+ // running out of space.
+ if (received_truncated_ack_ && sequence_number >
+ received_packet_manager_.peer_largest_observed_packet() &&
+ // We allow retransmission of already retransmitted packets so that we
+ // retransmit packets that were retransmissions of the packet with
+ // sequence number < the largest observed field of the truncated ack.
+ retransmission_it->second.number_retransmissions == 0) {
+ return false;
+ } else {
+ ++stats_.rto_count;
+ RetransmitPacket(sequence_number);
+ return true;
+ }
+}
+
+void QuicConnection::RetransmitUnackedPackets(
+ RetransmissionType retransmission_type) {
+ if (unacked_packets_.empty()) {
+ return;
+ }
+ UnackedPacketMap::iterator next_it = unacked_packets_.begin();
+ QuicPacketSequenceNumber end_sequence_number =
+ unacked_packets_.rbegin()->first;
+ do {
+ UnackedPacketMap::iterator current_it = next_it;
+ ++next_it;
+
+ if (retransmission_type == ALL_PACKETS ||
+ current_it->second->encryption_level() == ENCRYPTION_INITIAL) {
+ // TODO(satyamshekhar): Think about congestion control here.
+ // Specifically, about the retransmission count of packets being sent
+ // proactively to achieve 0 (minimal) RTT.
+ RetransmitPacket(current_it->first);
+ }
+ } while (next_it != unacked_packets_.end() &&
+ next_it->first <= end_sequence_number);
+}
+
+void QuicConnection::RetransmitPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ UnackedPacketMap::iterator unacked_it =
+ unacked_packets_.find(sequence_number);
+ RetransmissionMap::iterator retransmission_it =
+ retransmission_map_.find(sequence_number);
+ // There should always be an entry corresponding to |sequence_number| in
+ // both |retransmission_map_| and |unacked_packets_|. Retransmissions due to
+ // RTO for sequence numbers that are already acked or retransmitted are
+ // ignored by MaybeRetransmitPacketForRTO.
+ DCHECK(unacked_it != unacked_packets_.end());
+ DCHECK(retransmission_it != retransmission_map_.end());
+ RetransmittableFrames* unacked = unacked_it->second;
+ // TODO(pwestin): Need to fix potential issue with FEC and a 1 packet
+ // congestion window see b/8331807 for details.
+ congestion_manager_.AbandoningPacket(sequence_number);
+
+ // Re-packetize the frames with a new sequence number for retransmission.
+ // Retransmitted data packets do not use FEC, even when it's enabled.
+ SerializedPacket serialized_packet =
+ packet_creator_.SerializeAllFrames(unacked->frames());
+ RetransmissionInfo retransmission_info(serialized_packet.sequence_number);
+ retransmission_info.number_retransmissions =
+ retransmission_it->second.number_retransmissions + 1;
+ // Remove info with old sequence number.
+ unacked_packets_.erase(unacked_it);
+ retransmission_map_.erase(retransmission_it);
+ DVLOG(1) << ENDPOINT << "Retransmitting unacked packet " << sequence_number
+ << " as " << serialized_packet.sequence_number;
+ DCHECK(unacked_packets_.empty() ||
+ unacked_packets_.rbegin()->first < serialized_packet.sequence_number);
+ unacked_packets_.insert(make_pair(serialized_packet.sequence_number,
+ unacked));
+ retransmission_map_.insert(make_pair(serialized_packet.sequence_number,
+ retransmission_info));
+ if (debug_visitor_) {
+ debug_visitor_->OnPacketRetransmitted(sequence_number,
+ serialized_packet.sequence_number);
+ }
+ SendOrQueuePacket(unacked->encryption_level(),
+ serialized_packet.sequence_number,
+ serialized_packet.packet,
+ serialized_packet.entropy_hash,
+ HAS_RETRANSMITTABLE_DATA);
+}
+
+bool QuicConnection::CanWrite(Retransmission retransmission,
+ HasRetransmittableData retransmittable,
+ IsHandshake handshake) {
+ // TODO(ianswett): If the packet is a retransmit, the current send alarm may
+ // be too long.
+ if (write_blocked_ || send_alarm_->IsSet()) {
+ return false;
+ }
+
+ QuicTime now = clock_->Now();
+ QuicTime::Delta delay = congestion_manager_.TimeUntilSend(
+ now, retransmission, retransmittable, handshake);
+ if (delay.IsInfinite()) {
+ return false;
+ }
+
+ // If the scheduler requires a delay, then we can not send this packet now.
+ if (!delay.IsZero()) {
+ send_alarm_->Cancel();
+ send_alarm_->Set(now.Add(delay));
+ return false;
+ }
+ return true;
+}
+
+bool QuicConnection::IsRetransmission(
+ QuicPacketSequenceNumber sequence_number) {
+ RetransmissionMap::iterator it = retransmission_map_.find(sequence_number);
+ return it != retransmission_map_.end() &&
+ it->second.number_retransmissions > 0;
+}
+
+void QuicConnection::SetupRetransmission(
+ QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level) {
+ RetransmissionMap::iterator it = retransmission_map_.find(sequence_number);
+ if (it == retransmission_map_.end()) {
+ DVLOG(1) << ENDPOINT << "Will not retransmit packet " << sequence_number;
+ return;
+ }
+
+ RetransmissionInfo retransmission_info = it->second;
+ // TODO(rch): consider using a much smaller retransmisison_delay
+ // for the ENCRYPTION_NONE packets.
+ size_t effective_retransmission_count =
+ level == ENCRYPTION_NONE ? 0 : retransmission_info.number_retransmissions;
+ QuicTime::Delta retransmission_delay =
+ congestion_manager_.GetRetransmissionDelay(
+ unacked_packets_.size(),
+ effective_retransmission_count);
+
+ retransmission_timeouts_.push(RetransmissionTime(
+ sequence_number,
+ clock_->ApproximateNow().Add(retransmission_delay),
+ false));
+
+ // Do not set the retransmisson alarm if we're already handling the
+ // retransmission alarm because the retransmission alarm will be reset when
+ // OnRetransmissionTimeout completes.
+ if (!handling_retransmission_timeout_ && !retransmission_alarm_->IsSet()) {
+ retransmission_alarm_->Set(
+ clock_->ApproximateNow().Add(retransmission_delay));
+ }
+ // TODO(satyamshekhar): restore packet reordering with Ian's TODO in
+ // SendStreamData().
+}
+
+void QuicConnection::SetupAbandonFecTimer(
+ QuicPacketSequenceNumber sequence_number) {
+ DCHECK(ContainsKey(unacked_fec_packets_, sequence_number));
+ QuicTime::Delta retransmission_delay =
+ QuicTime::Delta::FromMilliseconds(
+ congestion_manager_.DefaultRetransmissionTime().ToMilliseconds() * 3);
+ retransmission_timeouts_.push(RetransmissionTime(
+ sequence_number,
+ clock_->ApproximateNow().Add(retransmission_delay),
+ true));
+}
+
+void QuicConnection::DropPacket(QuicPacketSequenceNumber sequence_number) {
+ UnackedPacketMap::iterator unacked_it =
+ unacked_packets_.find(sequence_number);
+ // Packet was not meant to be retransmitted.
+ if (unacked_it == unacked_packets_.end()) {
+ DCHECK(!ContainsKey(retransmission_map_, sequence_number));
+ return;
+ }
+ // Delete the unacked packet.
+ delete unacked_it->second;
+ unacked_packets_.erase(unacked_it);
+ retransmission_map_.erase(sequence_number);
+ return;
+}
+
+bool QuicConnection::WritePacket(EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ HasRetransmittableData retransmittable,
+ Force forced) {
+ if (!connected_) {
+ DLOG(INFO) << ENDPOINT
+ << "Not sending packet as connection is disconnected.";
+ delete packet;
+ // Returning true because we deleted the packet and the caller shouldn't
+ // delete it again.
+ return true;
+ }
+
+ if (encryption_level_ == ENCRYPTION_FORWARD_SECURE &&
+ level == ENCRYPTION_NONE) {
+ // Drop packets that are NULL encrypted since the peer won't accept them
+ // anymore.
+ DLOG(INFO) << ENDPOINT << "Dropped packet: " << sequence_number
+ << " since the packet is NULL encrypted.";
+ DropPacket(sequence_number);
+ delete packet;
+ return true;
+ }
+
+ Retransmission retransmission = IsRetransmission(sequence_number) ?
+ IS_RETRANSMISSION : NOT_RETRANSMISSION;
+ IsHandshake handshake = level == ENCRYPTION_NONE ? IS_HANDSHAKE
+ : NOT_HANDSHAKE;
+
+ // If we are not forced and we can't write, then simply return false;
+ if (forced == NO_FORCE &&
+ !CanWrite(retransmission, retransmittable, handshake)) {
+ return false;
+ }
+
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(level, sequence_number, *packet));
+ DLOG(INFO) << ENDPOINT << "Sending packet number " << sequence_number
+ << " : " << (packet->is_fec_packet() ? "FEC " :
+ (retransmittable == HAS_RETRANSMITTABLE_DATA
+ ? "data bearing " : " ack only "))
+ << ", encryption level: "
+ << QuicUtils::EncryptionLevelToString(level)
+ << ", length:" << packet->length();
+ DVLOG(2) << ENDPOINT << "packet(" << sequence_number << "): " << std::endl
+ << QuicUtils::StringToHexASCIIDump(packet->AsStringPiece());
+
+ DCHECK(encrypted->length() <= kMaxPacketSize)
+ << "Packet " << sequence_number << " will not be read; too large: "
+ << packet->length() << " " << encrypted->length() << " "
+ << " forced: " << (forced == FORCE ? "yes" : "no");
+
+ int error;
+ QuicTime now = clock_->Now();
+ if (!retransmission) {
+ time_of_last_sent_packet_ = now;
+ }
+ DVLOG(1) << ENDPOINT << "time of last sent packet: "
+ << now.ToDebuggingValue();
+ if (WritePacketToWire(sequence_number, level, *encrypted, &error) == -1) {
+ if (helper_->IsWriteBlocked(error)) {
+ // TODO(satyashekhar): It might be more efficient (fewer system calls), if
+ // all connections share this variable i.e this becomes a part of
+ // PacketWriterInterface.
+ write_blocked_ = true;
+ // If the socket buffers the the data, then the packet should not
+ // be queued and sent again, which would result in an unnecessary
+ // duplicate packet being sent.
+ return helper_->IsWriteBlockedDataBuffered();
+ }
+ // We can't send an error as the socket is presumably borked.
+ CloseConnection(QUIC_PACKET_WRITE_ERROR, false);
+ return false;
+ }
+
+ // Set the retransmit alarm only when we have sent the packet to the client
+ // and not when it goes to the pending queue, otherwise we will end up adding
+ // an entry to retransmission_timeout_ every time we attempt a write.
+ if (retransmittable == HAS_RETRANSMITTABLE_DATA) {
+ SetupRetransmission(sequence_number, level);
+ } else if (packet->is_fec_packet()) {
+ SetupAbandonFecTimer(sequence_number);
+ }
+
+ congestion_manager_.SentPacket(sequence_number, now, packet->length(),
+ retransmission);
+
+ stats_.bytes_sent += encrypted->length();
+ ++stats_.packets_sent;
+
+ if (retransmission == IS_RETRANSMISSION) {
+ stats_.bytes_retransmitted += encrypted->length();
+ ++stats_.packets_retransmitted;
+ }
+
+ delete packet;
+ return true;
+}
+
+int QuicConnection::WritePacketToWire(QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ const QuicEncryptedPacket& packet,
+ int* error) {
+ int bytes_written = helper_->WritePacketToWire(packet, error);
+ if (debug_visitor_) {
+ // WritePacketToWire returned -1, then |error| will be populated with
+ // an error code, which we want to pass along to the visitor.
+ debug_visitor_->OnPacketSent(sequence_number, level, packet,
+ bytes_written == -1 ? *error : bytes_written);
+ }
+ return bytes_written;
+}
+
+bool QuicConnection::OnSerializedPacket(
+ const SerializedPacket& serialized_packet) {
+ if (serialized_packet.retransmittable_frames != NULL) {
+ DCHECK(unacked_packets_.empty() ||
+ unacked_packets_.rbegin()->first <
+ serialized_packet.sequence_number);
+ // Retransmitted frames will be sent with the same encryption level as the
+ // original.
+ serialized_packet.retransmittable_frames->set_encryption_level(
+ encryption_level_);
+ unacked_packets_.insert(
+ make_pair(serialized_packet.sequence_number,
+ serialized_packet.retransmittable_frames));
+ // All unacked packets might be retransmitted.
+ retransmission_map_.insert(
+ make_pair(serialized_packet.sequence_number,
+ RetransmissionInfo(serialized_packet.sequence_number)));
+ } else if (serialized_packet.packet->is_fec_packet()) {
+ unacked_fec_packets_.insert(make_pair(
+ serialized_packet.sequence_number,
+ serialized_packet.retransmittable_frames));
+ }
+ return SendOrQueuePacket(encryption_level_,
+ serialized_packet.sequence_number,
+ serialized_packet.packet,
+ serialized_packet.entropy_hash,
+ serialized_packet.retransmittable_frames != NULL ?
+ HAS_RETRANSMITTABLE_DATA :
+ NO_RETRANSMITTABLE_DATA);
+}
+
+bool QuicConnection::SendOrQueuePacket(EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ QuicPacketEntropyHash entropy_hash,
+ HasRetransmittableData retransmittable) {
+ sent_entropy_manager_.RecordPacketEntropyHash(sequence_number, entropy_hash);
+ if (!WritePacket(level, sequence_number, packet, retransmittable, NO_FORCE)) {
+ queued_packets_.push_back(QueuedPacket(sequence_number, packet, level,
+ retransmittable));
+ return false;
+ }
+ return true;
+}
+
+bool QuicConnection::ShouldSimulateLostPacket() {
+ // TODO(rch): enable this
+ return false;
+ /*
+ return FLAGS_fake_packet_loss_percentage > 0 &&
+ random_->Rand32() % 100 < FLAGS_fake_packet_loss_percentage;
+ */
+}
+
+void QuicConnection::UpdateSentPacketInfo(SentPacketInfo* sent_info) {
+ if (!unacked_packets_.empty()) {
+ sent_info->least_unacked = unacked_packets_.begin()->first;
+ } else {
+ // If there are no unacked packets, set the least unacked packet to
+ // sequence_number() + 1 since that will be the sequence number of this
+ // ack packet whenever it is sent.
+ sent_info->least_unacked = packet_creator_.sequence_number() + 1;
+ }
+ sent_info->entropy_hash = sent_entropy_manager_.EntropyHash(
+ sent_info->least_unacked - 1);
+}
+
+void QuicConnection::SendAck() {
+ ack_alarm_->Cancel();
+
+ // TODO(rch): delay this until the CreateFeedbackFrame
+ // method is invoked. This requires changes SetShouldSendAck
+ // to be a no-arg method, and re-jiggering its implementation.
+ bool send_feedback = false;
+ if (congestion_manager_.GenerateCongestionFeedback(
+ &outgoing_congestion_feedback_)) {
+ DVLOG(1) << ENDPOINT << "Sending feedback "
+ << outgoing_congestion_feedback_;
+ send_feedback = true;
+ }
+
+ packet_generator_.SetShouldSendAck(send_feedback);
+}
+
+void QuicConnection::MaybeAbandonFecPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ if (!ContainsKey(unacked_fec_packets_, sequence_number)) {
+ DVLOG(2) << ENDPOINT << "no need to abandon fec packet: "
+ << sequence_number << "; it's already acked'";
+ return;
+ }
+ congestion_manager_.AbandoningPacket(sequence_number);
+ // TODO(satyashekhar): Should this decrease the congestion window?
+}
+
+QuicTime QuicConnection::OnRetransmissionTimeout() {
+ // This guards against registering the alarm later than we should.
+ //
+ // If we have packet A and B in the list and we call
+ // MaybeRetransmitPacketForRTO on A, that may trigger a call to
+ // SetRetransmissionAlarm if A is retransmitted as C. In that case we
+ // don't want to register the alarm under SetRetransmissionAlarm; we
+ // want to set it to the RTO of B when we return from this function.
+ handling_retransmission_timeout_ = true;
+
+ for (size_t i = 0; i < max_packets_per_retransmission_alarm_ &&
+ !retransmission_timeouts_.empty(); ++i) {
+ RetransmissionTime retransmission_time = retransmission_timeouts_.top();
+ DCHECK(retransmission_time.scheduled_time.IsInitialized());
+ if (retransmission_time.scheduled_time > clock_->ApproximateNow()) {
+ break;
+ }
+ retransmission_timeouts_.pop();
+
+ if (retransmission_time.for_fec) {
+ MaybeAbandonFecPacket(retransmission_time.sequence_number);
+ continue;
+ } else if (
+ !MaybeRetransmitPacketForRTO(retransmission_time.sequence_number)) {
+ DLOG(INFO) << ENDPOINT << "MaybeRetransmitPacketForRTO failed: "
+ << "adding an extra delay for "
+ << retransmission_time.sequence_number;
+ retransmission_time.scheduled_time = clock_->ApproximateNow().Add(
+ congestion_manager_.DefaultRetransmissionTime());
+ retransmission_timeouts_.push(retransmission_time);
+ }
+ }
+
+ handling_retransmission_timeout_ = false;
+
+ if (retransmission_timeouts_.empty()) {
+ return QuicTime::Zero();
+ }
+
+ // We have packets remaining. Return the absolute RTO of the oldest packet
+ // on the list.
+ return retransmission_timeouts_.top().scheduled_time;
+}
+
+void QuicConnection::SetEncrypter(EncryptionLevel level,
+ QuicEncrypter* encrypter) {
+ framer_.SetEncrypter(level, encrypter);
+}
+
+const QuicEncrypter* QuicConnection::encrypter(EncryptionLevel level) const {
+ return framer_.encrypter(level);
+}
+
+void QuicConnection::SetDefaultEncryptionLevel(
+ EncryptionLevel level) {
+ encryption_level_ = level;
+}
+
+void QuicConnection::SetDecrypter(QuicDecrypter* decrypter) {
+ framer_.SetDecrypter(decrypter);
+}
+
+void QuicConnection::SetAlternativeDecrypter(QuicDecrypter* decrypter,
+ bool latch_once_used) {
+ framer_.SetAlternativeDecrypter(decrypter, latch_once_used);
+}
+
+const QuicDecrypter* QuicConnection::decrypter() const {
+ return framer_.decrypter();
+}
+
+const QuicDecrypter* QuicConnection::alternative_decrypter() const {
+ return framer_.alternative_decrypter();
+}
+
+void QuicConnection::QueueUndecryptablePacket(
+ const QuicEncryptedPacket& packet) {
+ DVLOG(1) << ENDPOINT << "Queueing undecryptable packet.";
+ char* data = new char[packet.length()];
+ memcpy(data, packet.data(), packet.length());
+ undecryptable_packets_.push_back(
+ new QuicEncryptedPacket(data, packet.length(), true));
+}
+
+void QuicConnection::MaybeProcessUndecryptablePackets() {
+ if (undecryptable_packets_.empty() ||
+ encryption_level_ == ENCRYPTION_NONE) {
+ return;
+ }
+
+ while (connected_ && !undecryptable_packets_.empty()) {
+ DVLOG(1) << ENDPOINT << "Attempting to process undecryptable packet";
+ QuicEncryptedPacket* packet = undecryptable_packets_.front();
+ if (!framer_.ProcessPacket(*packet) &&
+ framer_.error() == QUIC_DECRYPTION_FAILURE) {
+ DVLOG(1) << ENDPOINT << "Unable to process undecryptable packet...";
+ break;
+ }
+ DVLOG(1) << ENDPOINT << "Processed undecryptable packet!";
+ delete packet;
+ undecryptable_packets_.pop_front();
+ }
+
+ // Once forward secure encryption is in use, there will be no
+ // new keys installed and hence any undecryptable packets will
+ // never be able to be decrypted.
+ if (encryption_level_ == ENCRYPTION_FORWARD_SECURE) {
+ STLDeleteElements(&undecryptable_packets_);
+ }
+}
+
+void QuicConnection::MaybeProcessRevivedPacket() {
+ QuicFecGroup* group = GetFecGroup();
+ if (!connected_ || group == NULL || !group->CanRevive()) {
+ return;
+ }
+ QuicPacketHeader revived_header;
+ char revived_payload[kMaxPacketSize];
+ size_t len = group->Revive(&revived_header, revived_payload, kMaxPacketSize);
+ revived_header.public_header.guid = guid_;
+ revived_header.public_header.version_flag = false;
+ revived_header.public_header.reset_flag = false;
+ revived_header.fec_flag = false;
+ revived_header.is_in_fec_group = NOT_IN_FEC_GROUP;
+ revived_header.fec_group = 0;
+ group_map_.erase(last_header_.fec_group);
+ delete group;
+
+ last_packet_revived_ = true;
+ if (debug_visitor_) {
+ debug_visitor_->OnRevivedPacket(revived_header,
+ StringPiece(revived_payload, len));
+ }
+
+ ++stats_.packets_revived;
+ framer_.ProcessRevivedPacket(&revived_header,
+ StringPiece(revived_payload, len));
+}
+
+QuicFecGroup* QuicConnection::GetFecGroup() {
+ QuicFecGroupNumber fec_group_num = last_header_.fec_group;
+ if (fec_group_num == 0) {
+ return NULL;
+ }
+ if (group_map_.count(fec_group_num) == 0) {
+ if (group_map_.size() >= kMaxFecGroups) { // Too many groups
+ if (fec_group_num < group_map_.begin()->first) {
+ // The group being requested is a group we've seen before and deleted.
+ // Don't recreate it.
+ return NULL;
+ }
+ // Clear the lowest group number.
+ delete group_map_.begin()->second;
+ group_map_.erase(group_map_.begin());
+ }
+ group_map_[fec_group_num] = new QuicFecGroup();
+ }
+ return group_map_[fec_group_num];
+}
+
+void QuicConnection::SendConnectionClose(QuicErrorCode error) {
+ SendConnectionCloseWithDetails(error, string());
+}
+
+void QuicConnection::SendConnectionClosePacket(QuicErrorCode error,
+ const string& details) {
+ DLOG(INFO) << ENDPOINT << "Force closing with error "
+ << QuicUtils::ErrorToString(error) << " (" << error << ") "
+ << details;
+ QuicConnectionCloseFrame frame;
+ frame.error_code = error;
+ frame.error_details = details;
+ UpdateSentPacketInfo(&frame.ack_frame.sent_info);
+ received_packet_manager_.UpdateReceivedPacketInfo(
+ &frame.ack_frame.received_info, clock_->ApproximateNow());
+
+ SerializedPacket serialized_packet =
+ packet_creator_.SerializeConnectionClose(&frame);
+
+ // We need to update the sent entropy hash for all sent packets.
+ sent_entropy_manager_.RecordPacketEntropyHash(
+ serialized_packet.sequence_number,
+ serialized_packet.entropy_hash);
+
+ if (!WritePacket(encryption_level_,
+ serialized_packet.sequence_number,
+ serialized_packet.packet,
+ serialized_packet.retransmittable_frames != NULL ?
+ HAS_RETRANSMITTABLE_DATA : NO_RETRANSMITTABLE_DATA,
+ FORCE)) {
+ delete serialized_packet.packet;
+ }
+}
+
+void QuicConnection::SendConnectionCloseWithDetails(QuicErrorCode error,
+ const string& details) {
+ if (!write_blocked_) {
+ SendConnectionClosePacket(error, details);
+ }
+ CloseConnection(error, false);
+}
+
+void QuicConnection::CloseConnection(QuicErrorCode error, bool from_peer) {
+ DCHECK(connected_);
+ connected_ = false;
+ visitor_->ConnectionClose(error, from_peer);
+}
+
+void QuicConnection::SendGoAway(QuicErrorCode error,
+ QuicStreamId last_good_stream_id,
+ const string& reason) {
+ DLOG(INFO) << ENDPOINT << "Going away with error "
+ << QuicUtils::ErrorToString(error)
+ << " (" << error << ")";
+ packet_generator_.AddControlFrame(
+ QuicFrame(new QuicGoAwayFrame(error, last_good_stream_id, reason)));
+}
+
+void QuicConnection::CloseFecGroupsBefore(
+ QuicPacketSequenceNumber sequence_number) {
+ FecGroupMap::iterator it = group_map_.begin();
+ while (it != group_map_.end()) {
+ // If this is the current group or the group doesn't protect this packet
+ // we can ignore it.
+ if (last_header_.fec_group == it->first ||
+ !it->second->ProtectsPacketsBefore(sequence_number)) {
+ ++it;
+ continue;
+ }
+ QuicFecGroup* fec_group = it->second;
+ DCHECK(!fec_group->CanRevive());
+ FecGroupMap::iterator next = it;
+ ++next;
+ group_map_.erase(it);
+ delete fec_group;
+ it = next;
+ }
+}
+
+bool QuicConnection::HasQueuedData() const {
+ return !queued_packets_.empty() || packet_generator_.HasQueuedFrames();
+}
+
+void QuicConnection::SetIdleNetworkTimeout(QuicTime::Delta timeout) {
+ if (timeout < idle_network_timeout_) {
+ idle_network_timeout_ = timeout;
+ CheckForTimeout();
+ } else {
+ idle_network_timeout_ = timeout;
+ }
+}
+
+void QuicConnection::SetOverallConnectionTimeout(QuicTime::Delta timeout) {
+ if (timeout < overall_connection_timeout_) {
+ overall_connection_timeout_ = timeout;
+ CheckForTimeout();
+ } else {
+ overall_connection_timeout_ = timeout;
+ }
+}
+
+bool QuicConnection::CheckForTimeout() {
+ QuicTime now = clock_->ApproximateNow();
+ QuicTime time_of_last_packet = std::max(time_of_last_received_packet_,
+ time_of_last_sent_packet_);
+
+ // |delta| can be < 0 as |now| is approximate time but |time_of_last_packet|
+ // is accurate time. However, this should not change the behavior of
+ // timeout handling.
+ QuicTime::Delta delta = now.Subtract(time_of_last_packet);
+ DVLOG(1) << ENDPOINT << "last packet "
+ << time_of_last_packet.ToDebuggingValue()
+ << " now:" << now.ToDebuggingValue()
+ << " delta:" << delta.ToMicroseconds()
+ << " network_timeout: " << idle_network_timeout_.ToMicroseconds();
+ if (delta >= idle_network_timeout_) {
+ DVLOG(1) << ENDPOINT << "Connection timedout due to no network activity.";
+ SendConnectionClose(QUIC_CONNECTION_TIMED_OUT);
+ return true;
+ }
+
+ // Next timeout delta.
+ QuicTime::Delta timeout = idle_network_timeout_.Subtract(delta);
+
+ if (!overall_connection_timeout_.IsInfinite()) {
+ QuicTime::Delta connected_time = now.Subtract(creation_time_);
+ DVLOG(1) << ENDPOINT << "connection time: "
+ << connected_time.ToMilliseconds() << " overall timeout: "
+ << overall_connection_timeout_.ToMilliseconds();
+ if (connected_time >= overall_connection_timeout_) {
+ DVLOG(1) << ENDPOINT <<
+ "Connection timedout due to overall connection timeout.";
+ SendConnectionClose(QUIC_CONNECTION_TIMED_OUT);
+ return true;
+ }
+
+ // Take the min timeout.
+ QuicTime::Delta connection_timeout =
+ overall_connection_timeout_.Subtract(connected_time);
+ if (connection_timeout < timeout) {
+ timeout = connection_timeout;
+ }
+ }
+
+ timeout_alarm_->Cancel();
+ timeout_alarm_->Set(clock_->ApproximateNow().Add(timeout));
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_connection.h b/chromium/net/quic/quic_connection.h
new file mode 100644
index 00000000000..20878d91bc7
--- /dev/null
+++ b/chromium/net/quic/quic_connection.h
@@ -0,0 +1,698 @@
+// Copyright (c) 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.
+//
+// The entity that handles framing writes for a Quic client or server.
+// Each QuicSession will have a connection associated with it.
+//
+// On the server side, the Dispatcher handles the raw reads, and hands off
+// packets via ProcessUdpPacket for framing and processing.
+//
+// On the client side, the Connection handles the raw reads, as well as the
+// processing.
+//
+// Note: this class is not thread-safe.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_H_
+#define NET_QUIC_QUIC_CONNECTION_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/congestion_control/quic_congestion_manager.h"
+#include "net/quic/quic_alarm.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_packet_generator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_received_packet_manager.h"
+#include "net/quic/quic_sent_entropy_manager.h"
+#include "net/quic/quic_stats.h"
+
+namespace net {
+
+class QuicClock;
+class QuicConnection;
+class QuicFecGroup;
+class QuicRandom;
+
+namespace test {
+class QuicConnectionPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicConnectionVisitorInterface {
+ public:
+ virtual ~QuicConnectionVisitorInterface() {}
+
+ // A simple visitor interface for dealing with data frames. The session
+ // should determine if all frames will be accepted, and return true if so.
+ // If any frames can't be processed or buffered, none of the data should
+ // be used, and the callee should return false.
+ virtual bool OnPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const std::vector<QuicStreamFrame>& frame) = 0;
+
+ // Called when the stream is reset by the peer.
+ virtual void OnRstStream(const QuicRstStreamFrame& frame) = 0;
+
+ // Called when the connection is going away according to the peer.
+ virtual void OnGoAway(const QuicGoAwayFrame& frame) = 0;
+
+ // Called when the connection is closed either locally by the framer, or
+ // remotely by the peer.
+ virtual void ConnectionClose(QuicErrorCode error,
+ bool from_peer) = 0;
+
+ // Called when packets are acked by the peer.
+ virtual void OnAck(const SequenceNumberSet& acked_packets) = 0;
+
+ // Called when a blocked socket becomes writable. If all pending bytes for
+ // this visitor are consumed by the connection successfully this should
+ // return true, otherwise it should return false.
+ virtual bool OnCanWrite() = 0;
+};
+
+// Interface which gets callbacks from the QuicConnection at interesting
+// points. Implementations must not mutate the state of the connection
+// as a result of these callbacks.
+class NET_EXPORT_PRIVATE QuicConnectionDebugVisitorInterface
+ : public QuicPacketGenerator::DebugDelegateInterface {
+ public:
+ virtual ~QuicConnectionDebugVisitorInterface() {}
+
+ // Called when a packet has been sent.
+ virtual void OnPacketSent(QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ const QuicEncryptedPacket& packet,
+ int rv) = 0;
+
+ // Called when the contents of a packet have been retransmitted as
+ // a new packet.
+ virtual void OnPacketRetransmitted(
+ QuicPacketSequenceNumber old_sequence_number,
+ QuicPacketSequenceNumber new_sequence_number) = 0;
+
+ // Called when a packet has been received, but before it is
+ // validated or parsed.
+ virtual void OnPacketReceived(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) = 0;
+
+ // Called when the protocol version on the received packet doensn't match
+ // current protocol version of the connection.
+ virtual void OnProtocolVersionMismatch(QuicVersion version) = 0;
+
+ // Called when the complete header of a packet has been parsed.
+ virtual void OnPacketHeader(const QuicPacketHeader& header) = 0;
+
+ // Called when a StreamFrame has been parsed.
+ virtual void OnStreamFrame(const QuicStreamFrame& frame) = 0;
+
+ // Called when a AckFrame has been parsed.
+ virtual void OnAckFrame(const QuicAckFrame& frame) = 0;
+
+ // Called when a CongestionFeedbackFrame has been parsed.
+ virtual void OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) = 0;
+
+ // Called when a RstStreamFrame has been parsed.
+ virtual void OnRstStreamFrame(const QuicRstStreamFrame& frame) = 0;
+
+ // Called when a ConnectionCloseFrame has been parsed.
+ virtual void OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) = 0;
+
+ // Called when a public reset packet has been received.
+ virtual void OnPublicResetPacket(const QuicPublicResetPacket& packet) = 0;
+
+ // Called when a version negotiation packet has been received.
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) = 0;
+
+ // Called after a packet has been successfully parsed which results
+ // in the revival of a packet via FEC.
+ virtual void OnRevivedPacket(const QuicPacketHeader& revived_header,
+ base::StringPiece payload) = 0;
+};
+
+class NET_EXPORT_PRIVATE QuicConnectionHelperInterface {
+ public:
+ virtual ~QuicConnectionHelperInterface() {}
+
+ // Sets the QuicConnection to be used by this helper. This method
+ // must only be called once.
+ virtual void SetConnection(QuicConnection* connection) = 0;
+
+ // Returns a QuicClock to be used for all time related functions.
+ virtual const QuicClock* GetClock() const = 0;
+
+ // Returns a QuicRandom to be used for all random number related functions.
+ virtual QuicRandom* GetRandomGenerator() = 0;
+
+ // Sends the packet out to the peer, possibly simulating packet
+ // loss if FLAGS_fake_packet_loss_percentage is set. If the write
+ // succeeded, returns the number of bytes written. If the write
+ // failed, returns -1 and the error code will be copied to |*error|.
+ virtual int WritePacketToWire(const QuicEncryptedPacket& packet,
+ int* error) = 0;
+
+ // Returns true if the helper buffers and subsequently rewrites data
+ // when an attempt to write results in the underlying socket becoming
+ // write blocked.
+ virtual bool IsWriteBlockedDataBuffered() = 0;
+
+ // Returns true if |error| represents a write-block error code such
+ // as EAGAIN or ERR_IO_PENDING.
+ virtual bool IsWriteBlocked(int error) = 0;
+
+ // Creates a new platform-specific alarm which will be configured to
+ // notify |delegate| when the alarm fires. Caller takes ownership
+ // of the new alarm, which will not yet be "set" to fire.
+ virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) = 0;
+};
+
+class NET_EXPORT_PRIVATE QuicConnection
+ : public QuicFramerVisitorInterface,
+ public QuicBlockedWriterInterface,
+ public QuicPacketGenerator::DelegateInterface {
+ public:
+ enum Force {
+ NO_FORCE,
+ FORCE
+ };
+
+ enum RetransmissionType {
+ INITIAL_ENCRYPTION_ONLY,
+ ALL_PACKETS
+ };
+
+ // Constructs a new QuicConnection for the specified |guid| and |address|.
+ // |helper| will be owned by this connection.
+ QuicConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper,
+ bool is_server,
+ QuicVersion version);
+ virtual ~QuicConnection();
+
+ static void DeleteEnclosedFrame(QuicFrame* frame);
+
+ // Send the data payload to the peer.
+ // Returns a pair with the number of bytes consumed from data, and a boolean
+ // indicating if the fin bit was consumed. This does not indicate the data
+ // has been sent on the wire: it may have been turned into a packet and queued
+ // if the socket was unexpectedly blocked.
+ QuicConsumedData SendStreamData(QuicStreamId id,
+ base::StringPiece data,
+ QuicStreamOffset offset,
+ bool fin);
+ // Send a stream reset frame to the peer.
+ virtual void SendRstStream(QuicStreamId id,
+ QuicRstStreamErrorCode error);
+
+ // Sends the connection close packet without affecting the state of the
+ // connection. This should only be called if the session is actively being
+ // destroyed: otherwise call SendConnectionCloseWithDetails instead.
+ virtual void SendConnectionClosePacket(QuicErrorCode error,
+ const std::string& details);
+
+ // Sends a connection close frame to the peer, and closes the connection by
+ // calling CloseConnection(notifying the visitor as it does so).
+ virtual void SendConnectionClose(QuicErrorCode error);
+ virtual void SendConnectionCloseWithDetails(QuicErrorCode error,
+ const std::string& details);
+ // Notifies the visitor of the close and marks the connection as disconnected.
+ void CloseConnection(QuicErrorCode error, bool from_peer);
+ virtual void SendGoAway(QuicErrorCode error,
+ QuicStreamId last_good_stream_id,
+ const std::string& reason);
+
+ // Returns statistics tracked for this connection.
+ const QuicConnectionStats& GetStats();
+
+ // Processes an incoming UDP packet (consisting of a QuicEncryptedPacket) from
+ // the peer. If processing this packet permits a packet to be revived from
+ // its FEC group that packet will be revived and processed.
+ virtual void ProcessUdpPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet);
+
+ // QuicBlockedWriterInterface
+ // Called when the underlying connection becomes writable to allow queued
+ // writes to happen. Returns false if the socket has become blocked.
+ virtual bool OnCanWrite() OVERRIDE;
+
+ // If the socket is not blocked, this allows queued writes to happen. Returns
+ // false if the socket has become blocked.
+ bool WriteIfNotBlocked();
+
+ // Do any work which logically would be done in OnPacket but can not be
+ // safely done until the packet is validated. Returns true if the packet
+ // can be handled, false otherwise.
+ bool ProcessValidatedPacket();
+
+ // The version of the protocol this connection is using.
+ QuicVersion version() const { return framer_.version(); }
+
+ // From QuicFramerVisitorInterface
+ virtual void OnError(QuicFramer* framer) OVERRIDE;
+ virtual bool OnProtocolVersionMismatch(QuicVersion received_version) OVERRIDE;
+ virtual void OnPacket() OVERRIDE;
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE;
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE;
+ virtual void OnRevivedPacket() OVERRIDE;
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+ virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE;
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE;
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+ virtual void OnFecData(const QuicFecData& fec) OVERRIDE;
+ virtual void OnPacketComplete() OVERRIDE;
+
+ // QuicPacketGenerator::DelegateInterface
+ virtual bool CanWrite(
+ Retransmission is_retransmission,
+ HasRetransmittableData has_retransmittable_data,
+ IsHandshake handshake) OVERRIDE;
+ virtual QuicAckFrame* CreateAckFrame() OVERRIDE;
+ virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() OVERRIDE;
+ virtual bool OnSerializedPacket(const SerializedPacket& packet) OVERRIDE;
+
+ // Accessors
+ void set_visitor(QuicConnectionVisitorInterface* visitor) {
+ visitor_ = visitor;
+ }
+ void set_debug_visitor(QuicConnectionDebugVisitorInterface* debug_visitor) {
+ debug_visitor_ = debug_visitor;
+ packet_generator_.set_debug_delegate(debug_visitor);
+ }
+ const IPEndPoint& self_address() const { return self_address_; }
+ const IPEndPoint& peer_address() const { return peer_address_; }
+ QuicGuid guid() const { return guid_; }
+ const QuicClock* clock() const { return clock_; }
+ QuicRandom* random_generator() const { return random_generator_; }
+
+ // Called by a RetransmissionAlarm when the timer goes off. If the peer
+ // appears to be sending truncated acks, this returns false to indicate
+ // failure, otherwise it calls MaybeRetransmitPacket and returns true.
+ bool MaybeRetransmitPacketForRTO(QuicPacketSequenceNumber sequence_number);
+
+ // Called to retransmit a packet, in the case a packet was sufficiently
+ // nacked by the peer, or not acked within the time out window.
+ void RetransmitPacket(QuicPacketSequenceNumber sequence_number);
+
+ QuicPacketCreator::Options* options() { return packet_creator_.options(); }
+
+ bool connected() { return connected_; }
+
+ size_t NumFecGroups() const { return group_map_.size(); }
+
+ // Testing only.
+ size_t NumQueuedPackets() const { return queued_packets_.size(); }
+
+ // Returns true if the connection has queued packets or frames.
+ bool HasQueuedData() const;
+
+ // Sets (or resets) the idle state connection timeout. Also, checks and times
+ // out the connection if network timer has expired for |timeout|.
+ void SetIdleNetworkTimeout(QuicTime::Delta timeout);
+ // Sets (or resets) the total time delta the connection can be alive for.
+ // Also, checks and times out the connection if timer has expired for
+ // |timeout|. Used to limit the time a connection can be alive before crypto
+ // handshake finishes.
+ void SetOverallConnectionTimeout(QuicTime::Delta timeout);
+
+ // If the connection has timed out, this will close the connection and return
+ // true. Otherwise, it will return false and will reset the timeout alarm.
+ bool CheckForTimeout();
+
+ // Returns true of the next packet to be sent should be "lost" by
+ // not actually writing it to the wire.
+ bool ShouldSimulateLostPacket();
+
+ // Sets up a packet with an QuicAckFrame and sends it out.
+ void SendAck();
+
+ // Called when an RTO fires. Returns the time when this alarm
+ // should next fire, or 0 if no retransmission alarm should be set.
+ QuicTime OnRetransmissionTimeout();
+
+ // Retransmits unacked packets which were sent with initial encryption, if
+ // |initial_encryption_only| is true, otherwise retransmits all unacked
+ // packets. Used when the negotiated protocol version is different than what
+ // was initially assumed and when the visitor wants to re-transmit packets
+ // with initial encryption when the initial encrypter changes.
+ void RetransmitUnackedPackets(RetransmissionType retransmission_type);
+
+ // Changes the encrypter used for level |level| to |encrypter|. The function
+ // takes ownership of |encrypter|.
+ void SetEncrypter(EncryptionLevel level, QuicEncrypter* encrypter);
+ const QuicEncrypter* encrypter(EncryptionLevel level) const;
+
+ // SetDefaultEncryptionLevel sets the encryption level that will be applied
+ // to new packets.
+ void SetDefaultEncryptionLevel(EncryptionLevel level);
+
+ // SetDecrypter sets the primary decrypter, replacing any that already exists,
+ // and takes ownership. If an alternative decrypter is in place then the
+ // function DCHECKs. This is intended for cases where one knows that future
+ // packets will be using the new decrypter and the previous decrypter is now
+ // obsolete.
+ void SetDecrypter(QuicDecrypter* decrypter);
+
+ // SetAlternativeDecrypter sets a decrypter that may be used to decrypt
+ // future packets and takes ownership of it. If |latch_once_used| is true,
+ // then the first time that the decrypter is successful it will replace the
+ // primary decrypter. Otherwise both decrypters will remain active and the
+ // primary decrypter will be the one last used.
+ void SetAlternativeDecrypter(QuicDecrypter* decrypter,
+ bool latch_once_used);
+
+ const QuicDecrypter* decrypter() const;
+ const QuicDecrypter* alternative_decrypter() const;
+
+ protected:
+ // Send a packet to the peer using encryption |level|. If |sequence_number|
+ // is present in the |retransmission_map_|, then contents of this packet will
+ // be retransmitted with a new sequence number if it's not acked by the peer.
+ // Deletes |packet| via WritePacket call or transfers ownership to
+ // QueuedPacket, ultimately deleted via WritePacket. Also, it updates the
+ // entropy map corresponding to |sequence_number| using |entropy_hash|.
+ // TODO(wtc): none of the callers check the return value.
+ virtual bool SendOrQueuePacket(EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ QuicPacketEntropyHash entropy_hash,
+ HasRetransmittableData retransmittable);
+
+ // Writes the given packet to socket, encrypted with |level|, with the help
+ // of helper. Returns true on successful write, false otherwise. However,
+ // behavior is undefined if connection is not established or broken. In any
+ // circumstances, a return value of true implies that |packet| has been
+ // deleted and should not be accessed. If |sequence_number| is present in
+ // |retransmission_map_| it also sets up retransmission of the given packet
+ // in case of successful write. If |force| is FORCE, then the packet will be
+ // sent immediately and the send scheduler will not be consulted.
+ bool WritePacket(EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ HasRetransmittableData retransmittable,
+ Force force);
+
+ int WritePacketToWire(QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ const QuicEncryptedPacket& packet,
+ int* error);
+
+ // Make sure an ack we got from our peer is sane.
+ bool ValidateAckFrame(const QuicAckFrame& incoming_ack);
+
+ QuicConnectionHelperInterface* helper() { return helper_.get(); }
+
+ // Selects and updates the version of the protocol being used by selecting a
+ // version from |available_versions| which is also supported. Returns true if
+ // such a version exists, false otherwise.
+ bool SelectMutualVersion(const QuicVersionVector& available_versions);
+
+ QuicFramer framer_;
+
+ private:
+ friend class test::QuicConnectionPeer;
+
+ // Packets which have not been written to the wire.
+ // Owns the QuicPacket* packet.
+ struct QueuedPacket {
+ QueuedPacket(QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ EncryptionLevel level,
+ HasRetransmittableData retransmittable)
+ : sequence_number(sequence_number),
+ packet(packet),
+ encryption_level(level),
+ retransmittable(retransmittable) {
+ }
+
+ QuicPacketSequenceNumber sequence_number;
+ QuicPacket* packet;
+ const EncryptionLevel encryption_level;
+ HasRetransmittableData retransmittable;
+ };
+
+ struct RetransmissionInfo {
+ explicit RetransmissionInfo(QuicPacketSequenceNumber sequence_number)
+ : sequence_number(sequence_number),
+ number_nacks(0),
+ number_retransmissions(0) {
+ }
+
+ QuicPacketSequenceNumber sequence_number;
+ size_t number_nacks;
+ size_t number_retransmissions;
+ };
+
+ struct RetransmissionTime {
+ RetransmissionTime(QuicPacketSequenceNumber sequence_number,
+ const QuicTime& scheduled_time,
+ bool for_fec)
+ : sequence_number(sequence_number),
+ scheduled_time(scheduled_time),
+ for_fec(for_fec) { }
+
+ QuicPacketSequenceNumber sequence_number;
+ QuicTime scheduled_time;
+ bool for_fec;
+ };
+
+ class RetransmissionTimeComparator {
+ public:
+ bool operator()(const RetransmissionTime& lhs,
+ const RetransmissionTime& rhs) const {
+ DCHECK(lhs.scheduled_time.IsInitialized() &&
+ rhs.scheduled_time.IsInitialized());
+ return lhs.scheduled_time > rhs.scheduled_time;
+ }
+ };
+
+ typedef std::list<QueuedPacket> QueuedPacketList;
+ typedef linked_hash_map<QuicPacketSequenceNumber,
+ RetransmittableFrames*> UnackedPacketMap;
+ typedef std::map<QuicFecGroupNumber, QuicFecGroup*> FecGroupMap;
+ typedef base::hash_map<QuicPacketSequenceNumber,
+ RetransmissionInfo> RetransmissionMap;
+ typedef std::priority_queue<RetransmissionTime,
+ std::vector<RetransmissionTime>,
+ RetransmissionTimeComparator>
+ RetransmissionTimeouts;
+
+ // Sends a version negotiation packet to the peer.
+ void SendVersionNegotiationPacket();
+
+ void SetupRetransmission(QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level);
+ bool IsRetransmission(QuicPacketSequenceNumber sequence_number);
+
+ void SetupAbandonFecTimer(QuicPacketSequenceNumber sequence_number);
+
+ // Clears any accumulated frames from the last received packet.
+ void ClearLastFrames();
+
+ // Called from OnCanWrite and WriteIfNotBlocked to write queued packets.
+ // Returns false if the socket has become blocked.
+ bool DoWrite();
+
+ // Drop packet corresponding to |sequence_number| by deleting entries from
+ // |unacked_packets_| and |retransmission_map_|, if present. We need to drop
+ // all packets with encryption level NONE after the default level has been set
+ // to FORWARD_SECURE.
+ void DropPacket(QuicPacketSequenceNumber sequence_number);
+
+ // Writes as many queued packets as possible. The connection must not be
+ // blocked when this is called.
+ bool WriteQueuedPackets();
+
+ // Queues |packet| in the hopes that it can be decrypted in the
+ // future, when a new key is installed.
+ void QueueUndecryptablePacket(const QuicEncryptedPacket& packet);
+
+ // Attempts to process any queued undecryptable packets.
+ void MaybeProcessUndecryptablePackets();
+
+ // If a packet can be revived from the current FEC group, then
+ // revive and process the packet.
+ void MaybeProcessRevivedPacket();
+
+ void ProcessAckFrame(const QuicAckFrame& incoming_ack);
+
+ void HandleAckForSentPackets(const QuicAckFrame& incoming_ack,
+ SequenceNumberSet* acked_packets);
+ void HandleAckForSentFecPackets(const QuicAckFrame& incoming_ack,
+ SequenceNumberSet* acked_packets);
+
+ // Update the |sent_info| for an outgoing ack.
+ void UpdateSentPacketInfo(SentPacketInfo* sent_info);
+
+ // Checks if the last packet should instigate an ack.
+ bool ShouldLastPacketInstigateAck();
+
+ // Sends any packets which are a response to the last packet, including both
+ // acks and pending writes if an ack opened the congestion window.
+ void MaybeSendInResponseToPacket(bool last_packet_should_instigate_ack);
+
+ void MaybeAbandonFecPacket(QuicPacketSequenceNumber sequence_number);
+
+ // Get the FEC group associate with the last processed packet or NULL, if the
+ // group has already been deleted.
+ QuicFecGroup* GetFecGroup();
+
+ // Closes any FEC groups protecting packets before |sequence_number|.
+ void CloseFecGroupsBefore(QuicPacketSequenceNumber sequence_number);
+
+ scoped_ptr<QuicConnectionHelperInterface> helper_;
+ EncryptionLevel encryption_level_;
+ const QuicClock* clock_;
+ QuicRandom* random_generator_;
+
+ const QuicGuid guid_;
+ // Address on the last successfully processed packet received from the
+ // client.
+ IPEndPoint self_address_;
+ IPEndPoint peer_address_;
+
+ bool last_packet_revived_; // True if the last packet was revived from FEC.
+ size_t last_size_; // Size of the last received packet.
+ QuicPacketHeader last_header_;
+ std::vector<QuicStreamFrame> last_stream_frames_;
+ std::vector<QuicAckFrame> last_ack_frames_;
+ std::vector<QuicCongestionFeedbackFrame> last_congestion_frames_;
+ std::vector<QuicRstStreamFrame> last_rst_frames_;
+ std::vector<QuicGoAwayFrame> last_goaway_frames_;
+
+ QuicCongestionFeedbackFrame outgoing_congestion_feedback_;
+
+ // Track some peer state so we can do less bookkeeping
+ // Largest sequence sent by the peer which had an ack frame (latest ack info).
+ QuicPacketSequenceNumber largest_seen_packet_with_ack_;
+
+ // When new packets are created which may be retransmitted, they are added
+ // to this map, which contains owning pointers to the contained frames.
+ UnackedPacketMap unacked_packets_;
+
+ // Pending fec packets that have not been acked yet. These packets need to be
+ // cleared out of the cgst_window after a timeout since FEC packets are never
+ // retransmitted.
+ // Ask: What should be the timeout for these packets?
+ UnackedPacketMap unacked_fec_packets_;
+
+ // Collection of packets which were received before encryption was
+ // established, but which could not be decrypted. We buffer these on
+ // the assumption that they could not be processed because they were
+ // sent with the INITIAL encryption and the CHLO message was lost.
+ std::deque<QuicEncryptedPacket*> undecryptable_packets_;
+
+ // Heap of packets that we might need to retransmit, and the time at
+ // which we should retransmit them. Every time a packet is sent it is added
+ // to this heap which is O(log(number of pending packets to be retransmitted))
+ // which might be costly. This should be optimized to O(1) by maintaining a
+ // priority queue of lists of packets to be retransmitted, where list x
+ // contains all packets that have been retransmitted x times.
+ RetransmissionTimeouts retransmission_timeouts_;
+
+ // Map from sequence number to the retransmission info.
+ RetransmissionMap retransmission_map_;
+
+ // True while OnRetransmissionTimeout is running to prevent
+ // SetRetransmissionAlarm from being called erroneously.
+ bool handling_retransmission_timeout_;
+
+ // When packets could not be sent because the socket was not writable,
+ // they are added to this list. All corresponding frames are in
+ // unacked_packets_ if they are to be retransmitted.
+ QueuedPacketList queued_packets_;
+
+ // True when the socket becomes unwritable.
+ bool write_blocked_;
+
+ FecGroupMap group_map_;
+
+ QuicReceivedPacketManager received_packet_manager_;
+ QuicSentEntropyManager sent_entropy_manager_;
+
+ // An alarm that fires when an ACK should be sent to the peer.
+ scoped_ptr<QuicAlarm> ack_alarm_;
+ // An alarm that fires when a packet needs to be retransmitted.
+ scoped_ptr<QuicAlarm> retransmission_alarm_;
+ // An alarm that is scheduled when the sent scheduler requires a
+ // a delay before sending packets and fires when the packet may be sent.
+ scoped_ptr<QuicAlarm> send_alarm_;
+ // An alarm that fires when the connection may have timed out.
+ scoped_ptr<QuicAlarm> timeout_alarm_;
+
+ QuicConnectionVisitorInterface* visitor_;
+ QuicConnectionDebugVisitorInterface* debug_visitor_;
+ QuicPacketCreator packet_creator_;
+ QuicPacketGenerator packet_generator_;
+
+ // Network idle time before we kill of this connection.
+ QuicTime::Delta idle_network_timeout_;
+ // Overall connection timeout.
+ QuicTime::Delta overall_connection_timeout_;
+ // Connection creation time.
+ QuicTime creation_time_;
+
+ // Statistics for this session.
+ QuicConnectionStats stats_;
+
+ // The time that we got a packet for this connection.
+ // This is used for timeouts, and does not indicate the packet was processed.
+ QuicTime time_of_last_received_packet_;
+
+ // The time that we last sent a packet for this connection.
+ QuicTime time_of_last_sent_packet_;
+
+ // Congestion manager which controls the rate the connection sends packets
+ // as well as collecting and generating congestion feedback.
+ QuicCongestionManager congestion_manager_;
+
+ // The state of connection in version negotiation finite state machine.
+ QuicVersionNegotiationState version_negotiation_state_;
+
+ size_t max_packets_per_retransmission_alarm_;
+
+ // Tracks if the connection was created by the server.
+ bool is_server_;
+
+ // True by default. False if we've received or sent an explicit connection
+ // close.
+ bool connected_;
+
+ // True if the last ack received from the peer may have been truncated. False
+ // otherwise.
+ bool received_truncated_ack_;
+ bool send_ack_in_response_to_packet_;
+
+ // Set to true if the udp packet headers have a new self or peer address.
+ // This is checked later on validating a data or version negotiation packet.
+ bool address_migrating_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicConnection);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CONNECTION_H_
diff --git a/chromium/net/quic/quic_connection_helper.cc b/chromium/net/quic/quic_connection_helper.cc
new file mode 100644
index 00000000000..c3e796fa321
--- /dev/null
+++ b/chromium/net/quic/quic_connection_helper.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_connection_helper.h"
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/quic/quic_utils.h"
+
+namespace net {
+
+namespace {
+
+class QuicChromeAlarm : public QuicAlarm {
+ public:
+ QuicChromeAlarm(const QuicClock* clock,
+ base::TaskRunner* task_runner,
+ QuicAlarm::Delegate* delegate)
+ : QuicAlarm(delegate),
+ clock_(clock),
+ task_runner_(task_runner),
+ task_posted_(false),
+ weak_factory_(this) {}
+
+ protected:
+ virtual void SetImpl() OVERRIDE {
+ DCHECK(deadline().IsInitialized());
+ if (task_posted_) {
+ // Since tasks can not be un-posted, OnAlarm will be invoked which
+ // will notice that deadline has not yet been reached, and will set
+ // the alarm for the new deadline.
+ return;
+ }
+
+ int64 delay_us = deadline().Subtract(clock_->Now()).ToMicroseconds();
+ if (delay_us < 0) {
+ delay_us = 0;
+ }
+ task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&QuicChromeAlarm::OnAlarm, weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMicroseconds(delay_us));
+ task_posted_ = true;
+ }
+
+ virtual void CancelImpl() OVERRIDE {
+ DCHECK(!deadline().IsInitialized());
+ // Since tasks can not be un-posted, OnAlarm will be invoked which
+ // will notice that deadline is not Initialized and will do nothing.
+ }
+
+ private:
+ void OnAlarm() {
+ DCHECK(task_posted_);
+ task_posted_ = false;
+ // The alarm may have been cancelled.
+ if (!deadline().IsInitialized()) {
+ return;
+ }
+
+ // The alarm may have been re-set to a later time.
+ if (clock_->Now() < deadline()) {
+ SetImpl();
+ return;
+ }
+
+ Fire();
+ }
+
+ const QuicClock* clock_;
+ base::TaskRunner* task_runner_;
+ bool task_posted_;
+ base::WeakPtrFactory<QuicChromeAlarm> weak_factory_;
+};
+
+} // namespace
+
+QuicConnectionHelper::QuicConnectionHelper(base::TaskRunner* task_runner,
+ const QuicClock* clock,
+ QuicRandom* random_generator,
+ DatagramClientSocket* socket)
+ : weak_factory_(this),
+ task_runner_(task_runner),
+ socket_(socket),
+ clock_(clock),
+ random_generator_(random_generator) {
+}
+
+QuicConnectionHelper::~QuicConnectionHelper() {
+}
+
+void QuicConnectionHelper::SetConnection(QuicConnection* connection) {
+ connection_ = connection;
+}
+
+const QuicClock* QuicConnectionHelper::GetClock() const {
+ return clock_;
+}
+
+QuicRandom* QuicConnectionHelper::GetRandomGenerator() {
+ return random_generator_;
+}
+
+int QuicConnectionHelper::WritePacketToWire(
+ const QuicEncryptedPacket& packet,
+ int* error) {
+ if (connection_->ShouldSimulateLostPacket()) {
+ DLOG(INFO) << "Dropping packet due to fake packet loss.";
+ *error = 0;
+ return packet.length();
+ }
+
+ scoped_refptr<StringIOBuffer> buf(
+ new StringIOBuffer(std::string(packet.data(),
+ packet.length())));
+ int rv = socket_->Write(buf.get(),
+ packet.length(),
+ base::Bind(&QuicConnectionHelper::OnWriteComplete,
+ weak_factory_.GetWeakPtr()));
+ if (rv >= 0) {
+ *error = 0;
+ } else {
+ *error = rv;
+ rv = -1;
+ }
+ return rv;
+}
+
+bool QuicConnectionHelper::IsWriteBlockedDataBuffered() {
+ // Chrome sockets' Write() methods buffer the data until the Write is
+ // permitted.
+ return true;
+}
+
+bool QuicConnectionHelper::IsWriteBlocked(int error) {
+ return error == ERR_IO_PENDING;
+}
+
+QuicAlarm* QuicConnectionHelper::CreateAlarm(QuicAlarm::Delegate* delegate) {
+ return new QuicChromeAlarm(clock_, task_runner_, delegate);
+}
+
+void QuicConnectionHelper::OnWriteComplete(int result) {
+ // TODO(rch): Inform the connection about the result.
+ connection_->OnCanWrite();
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_connection_helper.h b/chromium/net/quic/quic_connection_helper.h
new file mode 100644
index 00000000000..6667b958ab8
--- /dev/null
+++ b/chromium/net/quic/quic_connection_helper.h
@@ -0,0 +1,72 @@
+// Copyright (c) 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.
+//
+// The Chrome-specific helper for QuicConnection which uses
+// a TaskRunner for alarms, and uses a DatagramClientSocket for writing data.
+
+#ifndef NET_QUIC_QUIC_CONNECTION_HELPER_H_
+#define NET_QUIC_QUIC_CONNECTION_HELPER_H_
+
+#include "net/quic/quic_connection.h"
+
+#include <set>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/udp/datagram_client_socket.h"
+
+namespace base {
+class TaskRunner;
+} // namespace base
+
+namespace net {
+
+class QuicClock;
+class QuicRandom;
+
+namespace test {
+class QuicConnectionHelperPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicConnectionHelper
+ : public QuicConnectionHelperInterface {
+ public:
+ QuicConnectionHelper(base::TaskRunner* task_runner,
+ const QuicClock* clock,
+ QuicRandom* random_generator,
+ DatagramClientSocket* socket);
+
+ virtual ~QuicConnectionHelper();
+
+ // QuicConnectionHelperInterface
+ virtual void SetConnection(QuicConnection* connection) OVERRIDE;
+ virtual const QuicClock* GetClock() const OVERRIDE;
+ virtual QuicRandom* GetRandomGenerator() OVERRIDE;
+ virtual int WritePacketToWire(const QuicEncryptedPacket& packet,
+ int* error) OVERRIDE;
+ virtual bool IsWriteBlockedDataBuffered() OVERRIDE;
+ virtual bool IsWriteBlocked(int error) OVERRIDE;
+ virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE;
+
+ private:
+ friend class test::QuicConnectionHelperPeer;
+
+ // A completion callback invoked when a write completes.
+ void OnWriteComplete(int result);
+
+ base::WeakPtrFactory<QuicConnectionHelper> weak_factory_;
+ base::TaskRunner* task_runner_;
+ DatagramClientSocket* socket_;
+ QuicConnection* connection_;
+ const QuicClock* clock_;
+ QuicRandom* random_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicConnectionHelper);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CONNECTION_HELPER_H_
diff --git a/chromium/net/quic/quic_connection_helper_test.cc b/chromium/net/quic/quic_connection_helper_test.cc
new file mode 100644
index 00000000000..4822ea67541
--- /dev/null
+++ b/chromium/net/quic/quic_connection_helper_test.cc
@@ -0,0 +1,474 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_connection_helper.h"
+
+#include <vector>
+
+#include "net/base/net_errors.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/test_task_runner.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace net {
+namespace test {
+
+const char kData[] = "foo";
+const bool kFromPeer = true;
+
+class TestDelegate : public QuicAlarm::Delegate {
+ public:
+ TestDelegate() : fired_(false) {}
+
+ virtual QuicTime OnAlarm() OVERRIDE {
+ fired_ = true;
+ return QuicTime::Zero();
+ }
+
+ bool fired() const { return fired_; }
+
+ private:
+ bool fired_;
+};
+
+class TestConnection : public QuicConnection {
+ public:
+ TestConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelper* helper)
+ : QuicConnection(guid, address, helper, false, QuicVersionMax()) {
+ }
+
+ void SendAck() {
+ QuicConnectionPeer::SendAck(this);
+ }
+
+ void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+ QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+ }
+
+ using QuicConnection::SendOrQueuePacket;
+};
+
+class QuicConnectionHelperTest : public ::testing::Test {
+ protected:
+ // Holds a packet to be written to the wire, and the IO mode that should
+ // be used by the mock socket when performing the write.
+ struct PacketToWrite {
+ PacketToWrite(IoMode mode, QuicEncryptedPacket* packet)
+ : mode(mode),
+ packet(packet) {
+ }
+ IoMode mode;
+ QuicEncryptedPacket* packet;
+ };
+
+ QuicConnectionHelperTest()
+ : guid_(2),
+ framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ net_log_(BoundNetLog()),
+ frame_(1, false, 0, kData) {
+ Initialize();
+ }
+
+ ~QuicConnectionHelperTest() {
+ for (size_t i = 0; i < writes_.size(); i++) {
+ delete writes_[i].packet;
+ }
+ }
+
+ // Adds a packet to the list of expected writes.
+ void AddWrite(IoMode mode, QuicEncryptedPacket* packet) {
+ writes_.push_back(PacketToWrite(mode, packet));
+ }
+
+ // Returns the packet to be written at position |pos|.
+ QuicEncryptedPacket* GetWrite(size_t pos) {
+ return writes_[pos].packet;
+ }
+
+ bool AtEof() {
+ return socket_data_->at_read_eof() && socket_data_->at_write_eof();
+ }
+
+ // Configures the test fixture to use the list of expected writes.
+ void Initialize() {
+ mock_writes_.reset(new MockWrite[writes_.size()]);
+ for (size_t i = 0; i < writes_.size(); i++) {
+ mock_writes_[i] = MockWrite(writes_[i].mode,
+ writes_[i].packet->data(),
+ writes_[i].packet->length());
+ };
+
+ socket_data_.reset(new StaticSocketDataProvider(NULL, 0, mock_writes_.get(),
+ writes_.size()));
+
+ socket_.reset(new MockUDPClientSocket(socket_data_.get(),
+ net_log_.net_log()));
+ socket_->Connect(IPEndPoint());
+ runner_ = new TestTaskRunner(&clock_);
+ helper_ = new QuicConnectionHelper(runner_.get(), &clock_,
+ &random_generator_, socket_.get());
+ send_algorithm_ = new testing::StrictMock<MockSendAlgorithm>();
+ EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)).
+ WillRepeatedly(testing::Return(QuicTime::Delta::Zero()));
+ connection_.reset(new TestConnection(guid_, IPEndPoint(), helper_));
+ connection_->set_visitor(&visitor_);
+ connection_->SetSendAlgorithm(send_algorithm_);
+ }
+
+ // Returns a newly created packet to send kData on stream 1.
+ QuicEncryptedPacket* ConstructDataPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ InitializeHeader(sequence_number);
+
+ return ConstructPacket(header_, QuicFrame(&frame_));
+ }
+
+ // Returns a newly created packet to send kData on stream 1.
+ QuicPacket* ConstructRawDataPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ InitializeHeader(sequence_number);
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&frame_));
+ return framer_.BuildUnsizedDataPacket(header_, frames).packet;
+ }
+
+ // Returns a newly created packet to send ack data.
+ QuicEncryptedPacket* ConstructAckPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ InitializeHeader(sequence_number);
+
+ QuicAckFrame ack(0, QuicTime::Zero(), sequence_number);
+ ack.sent_info.entropy_hash = 0;
+ ack.received_info.entropy_hash = 0;
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kTCP;
+ feedback.tcp.accumulated_number_of_lost_packets = 0;
+ feedback.tcp.receive_window = 16000 << 4;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&ack));
+ frames.push_back(QuicFrame(&feedback));
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header_, frames).packet);
+ return framer_.EncryptPacket(
+ ENCRYPTION_NONE, header_.packet_sequence_number, *packet);
+ }
+
+ // Returns a newly created packet to send a connection close frame.
+ QuicEncryptedPacket* ConstructClosePacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacketSequenceNumber least_waiting) {
+ InitializeHeader(sequence_number);
+
+ QuicFrames frames;
+ QuicAckFrame ack(0, QuicTime::Zero(), least_waiting + 1);
+ ack.sent_info.entropy_hash = 0;
+ ack.received_info.entropy_hash = 0;
+ QuicConnectionCloseFrame close;
+ close.error_code = QUIC_CONNECTION_TIMED_OUT;
+ close.ack_frame = ack;
+
+ return ConstructPacket(header_, QuicFrame(&close));
+ }
+
+ testing::StrictMock<MockSendAlgorithm>* send_algorithm_;
+ scoped_refptr<TestTaskRunner> runner_;
+ QuicConnectionHelper* helper_;
+ scoped_ptr<MockUDPClientSocket> socket_;
+ scoped_ptr<MockWrite[]> mock_writes_;
+ MockClock clock_;
+ MockRandom random_generator_;
+ scoped_ptr<TestConnection> connection_;
+ testing::StrictMock<MockConnectionVisitor> visitor_;
+
+ private:
+ void InitializeHeader(QuicPacketSequenceNumber sequence_number) {
+ header_.public_header.guid = guid_;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = true;
+ header_.packet_sequence_number = sequence_number;
+ header_.entropy_flag = false;
+ header_.fec_flag = false;
+ header_.is_in_fec_group = NOT_IN_FEC_GROUP;
+ header_.fec_group = 0;
+ }
+
+ QuicEncryptedPacket* ConstructPacket(const QuicPacketHeader& header,
+ const QuicFrame& frame) {
+ QuicFrames frames;
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header_, frames).packet);
+ return framer_.EncryptPacket(
+ ENCRYPTION_NONE, header_.packet_sequence_number, *packet);
+ }
+
+ QuicGuid guid_;
+ QuicFramer framer_;
+ QuicPacketHeader header_;
+ BoundNetLog net_log_;
+ QuicStreamFrame frame_;
+ scoped_ptr<StaticSocketDataProvider> socket_data_;
+ std::vector<PacketToWrite> writes_;
+};
+
+TEST_F(QuicConnectionHelperTest, GetClock) {
+ EXPECT_EQ(&clock_, helper_->GetClock());
+}
+
+TEST_F(QuicConnectionHelperTest, GetRandomGenerator) {
+ EXPECT_EQ(&random_generator_, helper_->GetRandomGenerator());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarm) {
+ TestDelegate* delegate = new TestDelegate();
+ scoped_ptr<QuicAlarm> alarm(helper_->CreateAlarm(delegate));
+
+ // The timeout alarm task is always posted.
+ ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+
+ QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+ alarm->Set(clock_.Now().Add(delta));
+
+ // Verify that the alarm task has been posted.
+ ASSERT_EQ(2u, runner_->GetPostedTasks().size());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+ runner_->GetPostedTasks()[1].delay);
+
+ runner_->RunNextTask();
+ EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+ EXPECT_TRUE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndCancel) {
+ TestDelegate* delegate = new TestDelegate();
+ scoped_ptr<QuicAlarm> alarm(helper_->CreateAlarm(delegate));
+
+ QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+ alarm->Set(clock_.Now().Add(delta));
+ alarm->Cancel();
+
+ // The alarm task should still be posted.
+ ASSERT_EQ(2u, runner_->GetPostedTasks().size());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+ runner_->GetPostedTasks()[1].delay);
+
+ runner_->RunNextTask();
+ EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+ EXPECT_FALSE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, CreateAlarmAndReset) {
+ TestDelegate* delegate = new TestDelegate();
+ scoped_ptr<QuicAlarm> alarm(helper_->CreateAlarm(delegate));
+
+ QuicTime::Delta delta = QuicTime::Delta::FromMicroseconds(1);
+ alarm->Set(clock_.Now().Add(delta));
+ alarm->Cancel();
+ QuicTime::Delta new_delta = QuicTime::Delta::FromMicroseconds(3);
+ alarm->Set(clock_.Now().Add(new_delta));
+
+ // The alarm task should still be posted.
+ ASSERT_EQ(2u, runner_->GetPostedTasks().size());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(delta.ToMicroseconds()),
+ runner_->GetPostedTasks()[1].delay);
+
+ runner_->RunNextTask();
+ EXPECT_EQ(QuicTime::Zero().Add(delta), clock_.Now());
+ EXPECT_FALSE(delegate->fired());
+
+ // The alarm task should be posted again.
+ ASSERT_EQ(2u, runner_->GetPostedTasks().size());
+
+ runner_->RunNextTask();
+ EXPECT_EQ(QuicTime::Zero().Add(new_delta), clock_.Now());
+ EXPECT_TRUE(delegate->fired());
+}
+
+TEST_F(QuicConnectionHelperTest, TestRetransmission) {
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(2));
+ Initialize();
+
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+
+ QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+ QuicTime start = clock_.ApproximateNow();
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _));
+ // Send a packet.
+ connection_->SendStreamData(1, kData, 0, false);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION));
+ // Since no ack was received, the retransmission alarm will fire and
+ // retransmit it.
+ runner_->RunNextTask();
+
+ EXPECT_EQ(kDefaultRetransmissionTime,
+ clock_.ApproximateNow().Subtract(start));
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, TestMultipleRetransmission) {
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(2));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(3));
+ Initialize();
+
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+
+ QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+ QuicTime start = clock_.ApproximateNow();
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _));
+ // Send a packet.
+ connection_->SendStreamData(1, kData, 0, false);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, IS_RETRANSMISSION));
+ // Since no ack was received, the retransmission alarm will fire and
+ // retransmit it.
+ runner_->RunNextTask();
+
+ EXPECT_EQ(kDefaultRetransmissionTime,
+ clock_.ApproximateNow().Subtract(start));
+
+ // Since no ack was received, the retransmission alarm will fire and
+ // retransmit it.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 3, _, IS_RETRANSMISSION));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(2, _));
+ runner_->RunNextTask();
+
+ EXPECT_EQ(kDefaultRetransmissionTime.Add(kDefaultRetransmissionTime),
+ clock_.ApproximateNow().Subtract(start));
+
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, InitialTimeout) {
+ AddWrite(SYNCHRONOUS, ConstructClosePacket(1, 0));
+ Initialize();
+
+ // Verify that a single task was posted.
+ ASSERT_EQ(1u, runner_->GetPostedTasks().size());
+ EXPECT_EQ(base::TimeDelta::FromSeconds(kDefaultInitialTimeoutSecs),
+ runner_->GetPostedTasks().front().delay);
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ // After we run the next task, we should close the connection.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false));
+
+ runner_->RunNextTask();
+ EXPECT_EQ(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(
+ kDefaultInitialTimeoutSecs)),
+ clock_.ApproximateNow());
+ EXPECT_FALSE(connection_->connected());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, WritePacketToWire) {
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1));
+ Initialize();
+
+ int len = GetWrite(0)->length();
+ int error = 0;
+ EXPECT_EQ(len, helper_->WritePacketToWire(*GetWrite(0), &error));
+ EXPECT_EQ(0, error);
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, WritePacketToWireAsync) {
+ AddWrite(ASYNC, ConstructClosePacket(1, 0));
+ Initialize();
+
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true));
+ int error = 0;
+ EXPECT_EQ(-1, helper_->WritePacketToWire(*GetWrite(0), &error));
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, TimeoutAfterSend) {
+ AddWrite(SYNCHRONOUS, ConstructAckPacket(1));
+ AddWrite(SYNCHRONOUS, ConstructClosePacket(2, 1));
+ Initialize();
+
+ EXPECT_TRUE(connection_->connected());
+ QuicTime start = clock_.ApproximateNow();
+
+ // When we send a packet, the timeout will change to 5000 +
+ // kDefaultInitialTimeoutSecs.
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(5000));
+ EXPECT_EQ(5000u, clock_.ApproximateNow().Subtract(start).ToMicroseconds());
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+
+ // Send an ack so we don't set the retransmission alarm.
+ connection_->SendAck();
+
+ // The original alarm will fire. We should not time out because we had a
+ // network event at t=5000. The alarm will reregister.
+ runner_->RunNextTask();
+
+ EXPECT_EQ(QuicTime::Zero().Add(QuicTime::Delta::FromSeconds(
+ kDefaultInitialTimeoutSecs)),
+ clock_.ApproximateNow());
+ EXPECT_TRUE(connection_->connected());
+
+ // This time, we should time out.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION));
+ runner_->RunNextTask();
+ EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000 + 5000,
+ clock_.ApproximateNow().Subtract(
+ QuicTime::Zero()).ToMicroseconds());
+ EXPECT_FALSE(connection_->connected());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicConnectionHelperTest, SendSchedulerDelayThenSend) {
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1));
+ Initialize();
+
+ // Test that if we send a packet with a delay, it ends up queued.
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(
+ *send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+
+ QuicPacket* packet = ConstructRawDataPacket(1);
+ connection_->SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, 0, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ EXPECT_EQ(1u, connection_->NumQueuedPackets());
+
+ // Advance the clock to fire the alarm, and configure the scheduler
+ // to permit the packet to be sent.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true));
+ runner_->RunNextTask();
+ EXPECT_EQ(0u, connection_->NumQueuedPackets());
+ EXPECT_TRUE(AtEof());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_connection_logger.cc b/chromium/net/quic/quic_connection_logger.cc
new file mode 100644
index 00000000000..b5dfcd61028
--- /dev/null
+++ b/chromium/net/quic/quic_connection_logger.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2013 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/quic/quic_connection_logger.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/net_log.h"
+#include "net/quic/crypto/crypto_handshake.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogQuicPacketCallback(const IPEndPoint* self_address,
+ const IPEndPoint* peer_address,
+ size_t packet_size,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("self_address", self_address->ToString());
+ dict->SetString("peer_address", peer_address->ToString());
+ dict->SetInteger("size", packet_size);
+ return dict;
+}
+
+base::Value* NetLogQuicPacketSentCallback(
+ QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ size_t packet_size,
+ int rv,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("encryption_level", level);
+ dict->SetString("packet_sequence_number",
+ base::Uint64ToString(sequence_number));
+ dict->SetInteger("size", packet_size);
+ if (rv < 0) {
+ dict->SetInteger("net_error", rv);
+ }
+ return dict;
+}
+
+base::Value* NetLogQuicPacketRetransmittedCallback(
+ QuicPacketSequenceNumber old_sequence_number,
+ QuicPacketSequenceNumber new_sequence_number,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("old_packet_sequence_number",
+ base::Uint64ToString(old_sequence_number));
+ dict->SetString("new_packet_sequence_number",
+ base::Uint64ToString(new_sequence_number));
+ return dict;
+}
+
+base::Value* NetLogQuicPacketHeaderCallback(const QuicPacketHeader* header,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("guid",
+ base::Uint64ToString(header->public_header.guid));
+ dict->SetInteger("reset_flag", header->public_header.reset_flag);
+ dict->SetInteger("version_flag", header->public_header.version_flag);
+ dict->SetString("packet_sequence_number",
+ base::Uint64ToString(header->packet_sequence_number));
+ dict->SetInteger("entropy_flag", header->entropy_flag);
+ dict->SetInteger("fec_flag", header->fec_flag);
+ dict->SetInteger("fec_group", header->fec_group);
+ return dict;
+}
+
+base::Value* NetLogQuicStreamFrameCallback(const QuicStreamFrame* frame,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", frame->stream_id);
+ dict->SetBoolean("fin", frame->fin);
+ dict->SetString("offset", base::Uint64ToString(frame->offset));
+ dict->SetInteger("length", frame->data.length());
+ return dict;
+}
+
+base::Value* NetLogQuicAckFrameCallback(const QuicAckFrame* frame,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::DictionaryValue* sent_info = new base::DictionaryValue();
+ dict->Set("sent_info", sent_info);
+ sent_info->SetString("least_unacked",
+ base::Uint64ToString(frame->sent_info.least_unacked));
+ base::DictionaryValue* received_info = new base::DictionaryValue();
+ dict->Set("received_info", received_info);
+ received_info->SetString(
+ "largest_observed",
+ base::Uint64ToString(frame->received_info.largest_observed));
+ base::ListValue* missing = new base::ListValue();
+ received_info->Set("missing_packets", missing);
+ const SequenceNumberSet& missing_packets =
+ frame->received_info.missing_packets;
+ for (SequenceNumberSet::const_iterator it = missing_packets.begin();
+ it != missing_packets.end(); ++it) {
+ missing->AppendString(base::Uint64ToString(*it));
+ }
+ return dict;
+}
+
+base::Value* NetLogQuicCongestionFeedbackFrameCallback(
+ const QuicCongestionFeedbackFrame* frame,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ switch (frame->type) {
+ case kInterArrival: {
+ dict->SetString("type", "InterArrival");
+ dict->SetInteger("accumulated_number_of_lost_packets",
+ frame->inter_arrival.accumulated_number_of_lost_packets);
+ base::ListValue* received = new base::ListValue();
+ dict->Set("received_packets", received);
+ for (TimeMap::const_iterator it =
+ frame->inter_arrival.received_packet_times.begin();
+ it != frame->inter_arrival.received_packet_times.end(); ++it) {
+ std::string value = base::Uint64ToString(it->first) + "@" +
+ base::Uint64ToString(it->second.ToDebuggingValue());
+ received->AppendString(value);
+ }
+ break;
+ }
+ case kFixRate:
+ dict->SetString("type", "FixRate");
+ dict->SetInteger("bitrate_in_bytes_per_second",
+ frame->fix_rate.bitrate.ToBytesPerSecond());
+ break;
+ case kTCP:
+ dict->SetString("type", "TCP");
+ dict->SetInteger("accumulated_number_of_lost_packets",
+ frame->tcp.accumulated_number_of_lost_packets);
+ dict->SetInteger("receive_window", frame->tcp.receive_window);
+ break;
+ }
+
+ return dict;
+}
+
+base::Value* NetLogQuicRstStreamFrameCallback(
+ const QuicRstStreamFrame* frame,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", frame->stream_id);
+ dict->SetInteger("quic_rst_stream_error", frame->error_code);
+ dict->SetString("details", frame->error_details);
+ return dict;
+}
+
+base::Value* NetLogQuicConnectionCloseFrameCallback(
+ const QuicConnectionCloseFrame* frame,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("quic_error", frame->error_code);
+ dict->SetString("details", frame->error_details);
+ return dict;
+}
+
+base::Value* NetLogQuicVersionNegotiationPacketCallback(
+ const QuicVersionNegotiationPacket* packet,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* versions = new base::ListValue();
+ dict->Set("versions", versions);
+ for (QuicVersionVector::const_iterator it = packet->versions.begin();
+ it != packet->versions.end(); ++it) {
+ versions->AppendString(QuicVersionToString(*it));
+ }
+ return dict;
+}
+
+base::Value* NetLogQuicCryptoHandshakeMessageCallback(
+ const CryptoHandshakeMessage* message,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("quic_crypto_handshake_message", message->DebugString());
+ return dict;
+}
+
+base::Value* NetLogQuicConnectionClosedCallback(
+ QuicErrorCode error,
+ bool from_peer,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("quic_error", error);
+ dict->SetBoolean("from_peer", from_peer);
+ return dict;
+}
+
+void UpdatePacketGapSentHistogram(size_t num_consecutive_missing_packets) {
+ UMA_HISTOGRAM_COUNTS("Net.QuicSession.PacketGapSent",
+ num_consecutive_missing_packets);
+}
+
+} // namespace
+
+QuicConnectionLogger::QuicConnectionLogger(const BoundNetLog& net_log)
+ : net_log_(net_log),
+ last_received_packet_sequence_number_(0),
+ largest_received_packet_sequence_number_(0),
+ largest_received_missing_packet_sequence_number_(0),
+ out_of_order_recieved_packet_count_(0) {
+}
+
+QuicConnectionLogger::~QuicConnectionLogger() {
+ UMA_HISTOGRAM_COUNTS("Net.QuicSession.OutOfOrderPacketsReceived",
+ out_of_order_recieved_packet_count_);
+}
+
+void QuicConnectionLogger::OnFrameAddedToPacket(const QuicFrame& frame) {
+ switch (frame.type) {
+ case PADDING_FRAME:
+ break;
+ case STREAM_FRAME:
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_SENT,
+ base::Bind(&NetLogQuicStreamFrameCallback, frame.stream_frame));
+ break;
+ case ACK_FRAME:
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_ACK_FRAME_SENT,
+ base::Bind(&NetLogQuicAckFrameCallback, frame.ack_frame));
+ break;
+ case CONGESTION_FEEDBACK_FRAME:
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_SENT,
+ base::Bind(&NetLogQuicCongestionFeedbackFrameCallback,
+ frame.congestion_feedback_frame));
+ break;
+ case RST_STREAM_FRAME:
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_RST_STREAM_FRAME_SENT,
+ base::Bind(&NetLogQuicRstStreamFrameCallback,
+ frame.rst_stream_frame));
+ break;
+ case CONNECTION_CLOSE_FRAME:
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CONNECTION_CLOSE_FRAME_SENT,
+ base::Bind(&NetLogQuicConnectionCloseFrameCallback,
+ frame.connection_close_frame));
+ break;
+ case GOAWAY_FRAME:
+ break;
+ default:
+ DCHECK(false) << "Illegal frame type: " << frame.type;
+ }
+}
+
+void QuicConnectionLogger::OnPacketSent(
+ QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ const QuicEncryptedPacket& packet,
+ int rv) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_PACKET_SENT,
+ base::Bind(&NetLogQuicPacketSentCallback, sequence_number, level,
+ packet.length(), rv));
+}
+
+void QuicConnectionLogger:: OnPacketRetransmitted(
+ QuicPacketSequenceNumber old_sequence_number,
+ QuicPacketSequenceNumber new_sequence_number) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_PACKET_RETRANSMITTED,
+ base::Bind(&NetLogQuicPacketRetransmittedCallback,
+ old_sequence_number, new_sequence_number));
+}
+
+void QuicConnectionLogger::OnPacketReceived(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_PACKET_RECEIVED,
+ base::Bind(&NetLogQuicPacketCallback, &self_address, &peer_address,
+ packet.length()));
+}
+
+void QuicConnectionLogger::OnProtocolVersionMismatch(
+ QuicVersion received_version) {
+ // TODO(rtenneti): Add logging.
+}
+
+void QuicConnectionLogger::OnPacketHeader(const QuicPacketHeader& header) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED,
+ base::Bind(&NetLogQuicPacketHeaderCallback, &header));
+ if (largest_received_packet_sequence_number_ <
+ header.packet_sequence_number) {
+ QuicPacketSequenceNumber delta = header.packet_sequence_number -
+ largest_received_packet_sequence_number_;
+ if (delta > 1) {
+ // There is a gap between the largest packet previously received and
+ // the current packet. This indicates either loss, or out-of-order
+ // delivery.
+ UMA_HISTOGRAM_COUNTS("Net.QuicSession.PacketGapReceived", delta - 1);
+ }
+ largest_received_packet_sequence_number_ = header.packet_sequence_number;
+ }
+ if (header.packet_sequence_number < last_received_packet_sequence_number_) {
+ ++out_of_order_recieved_packet_count_;
+ }
+ last_received_packet_sequence_number_ = header.packet_sequence_number;
+}
+
+void QuicConnectionLogger::OnStreamFrame(const QuicStreamFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_RECEIVED,
+ base::Bind(&NetLogQuicStreamFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnAckFrame(const QuicAckFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_ACK_FRAME_RECEIVED,
+ base::Bind(&NetLogQuicAckFrameCallback, &frame));
+
+ if (frame.received_info.missing_packets.empty())
+ return;
+
+ SequenceNumberSet missing_packets = frame.received_info.missing_packets;
+ SequenceNumberSet::const_iterator it = missing_packets.lower_bound(
+ largest_received_missing_packet_sequence_number_);
+ if (it == missing_packets.end())
+ return;
+
+ if (*it == largest_received_missing_packet_sequence_number_) {
+ ++it;
+ if (it == missing_packets.end())
+ return;
+ }
+ // Scan through the list and log consecutive ranges of missing packets.
+ size_t num_consecutive_missing_packets = 0;
+ QuicPacketSequenceNumber previous_missing_packet = *it - 1;
+ while (it != missing_packets.end()) {
+ if (previous_missing_packet == *it - 1) {
+ ++num_consecutive_missing_packets;
+ } else {
+ DCHECK_NE(0u, num_consecutive_missing_packets);
+ UpdatePacketGapSentHistogram(num_consecutive_missing_packets);
+ // Make sure this packet it included in the count.
+ num_consecutive_missing_packets = 1;
+ }
+ previous_missing_packet = *it;
+ ++it;
+ }
+ if (num_consecutive_missing_packets != 0) {
+ UpdatePacketGapSentHistogram(num_consecutive_missing_packets);
+ }
+ largest_received_missing_packet_sequence_number_ =
+ *missing_packets.rbegin();
+}
+
+void QuicConnectionLogger::OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CONGESTION_FEEDBACK_FRAME_RECEIVED,
+ base::Bind(&NetLogQuicCongestionFeedbackFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnRstStreamFrame(const QuicRstStreamFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_RST_STREAM_FRAME_RECEIVED,
+ base::Bind(&NetLogQuicRstStreamFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CONNECTION_CLOSE_FRAME_RECEIVED,
+ base::Bind(&NetLogQuicConnectionCloseFrameCallback, &frame));
+}
+
+void QuicConnectionLogger::OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) {
+ net_log_.AddEvent(NetLog::TYPE_QUIC_SESSION_PUBLIC_RESET_PACKET_RECEIVED);
+}
+
+void QuicConnectionLogger::OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_VERSION_NEGOTIATION_PACKET_RECEIVED,
+ base::Bind(&NetLogQuicVersionNegotiationPacketCallback, &packet));
+}
+
+void QuicConnectionLogger::OnRevivedPacket(
+ const QuicPacketHeader& revived_header,
+ base::StringPiece payload) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_REVIVED,
+ base::Bind(&NetLogQuicPacketHeaderCallback, &revived_header));
+}
+
+void QuicConnectionLogger::OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_RECEIVED,
+ base::Bind(&NetLogQuicCryptoHandshakeMessageCallback, &message));
+}
+
+void QuicConnectionLogger::OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_SENT,
+ base::Bind(&NetLogQuicCryptoHandshakeMessageCallback, &message));
+}
+
+void QuicConnectionLogger::OnConnectionClose(QuicErrorCode error,
+ bool from_peer) {
+ net_log_.AddEvent(
+ NetLog::TYPE_QUIC_SESSION_CLOSED,
+ base::Bind(&NetLogQuicConnectionClosedCallback, error, from_peer));
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_connection_logger.h b/chromium/net/quic/quic_connection_logger.h
new file mode 100644
index 00000000000..f9080d643e3
--- /dev/null
+++ b/chromium/net/quic/quic_connection_logger.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2013 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_QUIC_QUIC_CONNECTION_LOGGER_H_
+#define NET_QUIC_QUIC_CONNECTION_LOGGER_H_
+
+#include "net/quic/quic_connection.h"
+
+namespace net {
+
+class BoundNetLog;
+class CryptoHandshakeMessage;
+
+// This class is a debug visitor of a QuicConnection which logs
+// events to |net_log|.
+class NET_EXPORT_PRIVATE QuicConnectionLogger
+ : public QuicConnectionDebugVisitorInterface {
+ public:
+ explicit QuicConnectionLogger(const BoundNetLog& net_log);
+
+ virtual ~QuicConnectionLogger();
+
+ // QuicPacketGenerator::DebugDelegateInterface
+ virtual void OnFrameAddedToPacket(const QuicFrame& frame) OVERRIDE;
+
+ // QuicConnectionDebugVisitorInterface
+ virtual void OnPacketSent(QuicPacketSequenceNumber sequence_number,
+ EncryptionLevel level,
+ const QuicEncryptedPacket& packet,
+ int rv) OVERRIDE;
+ virtual void OnPacketRetransmitted(
+ QuicPacketSequenceNumber old_sequence_number,
+ QuicPacketSequenceNumber new_sequence_number) OVERRIDE;
+ virtual void OnPacketReceived(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) OVERRIDE;
+ virtual void OnProtocolVersionMismatch(QuicVersion version) OVERRIDE;
+ virtual void OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+ virtual void OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ virtual void OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+ virtual void OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+ virtual void OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual void OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE;
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE;
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE;
+ virtual void OnRevivedPacket(const QuicPacketHeader& revived_header,
+ base::StringPiece payload) OVERRIDE;
+
+ void OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message);
+ void OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message);
+ void OnConnectionClose(QuicErrorCode error, bool from_peer);
+
+ private:
+ BoundNetLog net_log_;
+ // The last packet sequence number received.
+ QuicPacketSequenceNumber last_received_packet_sequence_number_;
+ // The largest packet sequence number received. In case of that a packet is
+ // received late, this value will not be updated.
+ QuicPacketSequenceNumber largest_received_packet_sequence_number_;
+ // The largest packet sequence number which the peer has failed to
+ // receive, according to the missing packet set in their ack frames.
+ QuicPacketSequenceNumber largest_received_missing_packet_sequence_number_;
+ // Number of times that the current received packet sequence number is
+ // smaller than the last received packet sequence number.
+ size_t out_of_order_recieved_packet_count_;
+ DISALLOW_COPY_AND_ASSIGN(QuicConnectionLogger);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CONNECTION_LOGGER_H_
diff --git a/chromium/net/quic/quic_connection_test.cc b/chromium/net/quic/quic_connection_test.cc
new file mode 100644
index 00000000000..bd698c3eb1d
--- /dev/null
+++ b/chromium/net/quic/quic_connection_test.cc
@@ -0,0 +1,2445 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_connection.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "net/base/net_errors.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::map;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::Between;
+using testing::ContainerEq;
+using testing::DoAll;
+using testing::InSequence;
+using testing::InvokeWithoutArgs;
+using testing::Return;
+using testing::StrictMock;
+using testing::SaveArg;
+
+namespace net {
+namespace test {
+namespace {
+
+const char data1[] = "foo";
+const char data2[] = "bar";
+
+const bool kFin = true;
+const bool kEntropyFlag = true;
+
+const QuicPacketEntropyHash kTestEntropyHash = 76;
+
+class TestReceiveAlgorithm : public ReceiveAlgorithmInterface {
+ public:
+ explicit TestReceiveAlgorithm(QuicCongestionFeedbackFrame* feedback)
+ : feedback_(feedback) {
+ }
+
+ bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* congestion_feedback) {
+ if (feedback_ == NULL) {
+ return false;
+ }
+ *congestion_feedback = *feedback_;
+ return true;
+ }
+
+ MOCK_METHOD4(RecordIncomingPacket,
+ void(QuicByteCount, QuicPacketSequenceNumber, QuicTime, bool));
+
+ private:
+ QuicCongestionFeedbackFrame* feedback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestReceiveAlgorithm);
+};
+
+// TaggingEncrypter appends kTagSize bytes of |tag| to the end of each message.
+class TaggingEncrypter : public QuicEncrypter {
+ public:
+ explicit TaggingEncrypter(uint8 tag)
+ : tag_(tag) {
+ }
+
+ virtual ~TaggingEncrypter() {}
+
+ // QuicEncrypter interface.
+ virtual bool SetKey(StringPiece key) OVERRIDE { return true; }
+ virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+ return true;
+ }
+
+ virtual bool Encrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece plaintext,
+ unsigned char* output) OVERRIDE {
+ memcpy(output, plaintext.data(), plaintext.size());
+ output += plaintext.size();
+ memset(output, tag_, kTagSize);
+ return true;
+ }
+
+ virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece plaintext) OVERRIDE {
+ const size_t len = plaintext.size() + kTagSize;
+ uint8* buffer = new uint8[len];
+ Encrypt(StringPiece(), associated_data, plaintext, buffer);
+ return new QuicData(reinterpret_cast<char*>(buffer), len, true);
+ }
+
+ virtual size_t GetKeySize() const OVERRIDE { return 0; }
+ virtual size_t GetNoncePrefixSize() const OVERRIDE { return 0; }
+
+ virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE {
+ return ciphertext_size - kTagSize;
+ }
+
+ virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE {
+ return plaintext_size + kTagSize;
+ }
+
+ virtual StringPiece GetKey() const OVERRIDE {
+ return StringPiece();
+ }
+
+ virtual StringPiece GetNoncePrefix() const OVERRIDE {
+ return StringPiece();
+ }
+
+ private:
+ enum {
+ kTagSize = 12,
+ };
+
+ const uint8 tag_;
+};
+
+// TaggingDecrypter ensures that the final kTagSize bytes of the message all
+// have the same value and then removes them.
+class TaggingDecrypter : public QuicDecrypter {
+ public:
+ virtual ~TaggingDecrypter() {}
+
+ // QuicDecrypter interface
+ virtual bool SetKey(StringPiece key) OVERRIDE { return true; }
+ virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+ return true;
+ }
+
+ virtual bool Decrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) OVERRIDE {
+ if (ciphertext.size() < kTagSize) {
+ return false;
+ }
+ if (!CheckTag(ciphertext, GetTag(ciphertext))) {
+ return false;
+ }
+ *output_length = ciphertext.size() - kTagSize;
+ memcpy(output, ciphertext.data(), *output_length);
+ return true;
+ }
+
+ virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece ciphertext) OVERRIDE {
+ if (ciphertext.size() < kTagSize) {
+ return NULL;
+ }
+ if (!CheckTag(ciphertext, GetTag(ciphertext))) {
+ return NULL;
+ }
+ const size_t len = ciphertext.size() - kTagSize;
+ uint8* buf = new uint8[len];
+ memcpy(buf, ciphertext.data(), len);
+ return new QuicData(reinterpret_cast<char*>(buf), len,
+ true /* owns buffer */);
+ }
+
+ virtual StringPiece GetKey() const OVERRIDE { return StringPiece(); }
+ virtual StringPiece GetNoncePrefix() const OVERRIDE { return StringPiece(); }
+
+ protected:
+ virtual uint8 GetTag(StringPiece ciphertext) {
+ return ciphertext.data()[ciphertext.size()-1];
+ }
+
+ private:
+ enum {
+ kTagSize = 12,
+ };
+
+ bool CheckTag(StringPiece ciphertext, uint8 tag) {
+ for (size_t i = ciphertext.size() - kTagSize; i < ciphertext.size(); i++) {
+ if (ciphertext.data()[i] != tag) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+};
+
+// StringTaggingDecrypter ensures that the final kTagSize bytes of the message
+// match the expected value.
+class StrictTaggingDecrypter : public TaggingDecrypter {
+ public:
+ explicit StrictTaggingDecrypter(uint8 tag) : tag_(tag) {}
+ virtual ~StrictTaggingDecrypter() {}
+
+ // TaggingQuicDecrypter
+ virtual uint8 GetTag(StringPiece ciphertext) OVERRIDE {
+ return tag_;
+ }
+
+ private:
+ const uint8 tag_;
+};
+
+class TestConnectionHelper : public QuicConnectionHelperInterface {
+ public:
+ class TestAlarm : public QuicAlarm {
+ public:
+ explicit TestAlarm(QuicAlarm::Delegate* delegate)
+ : QuicAlarm(delegate) {
+ }
+
+ virtual void SetImpl() OVERRIDE {}
+ virtual void CancelImpl() OVERRIDE {}
+ };
+
+ TestConnectionHelper(MockClock* clock, MockRandom* random_generator)
+ : clock_(clock),
+ random_generator_(random_generator),
+ blocked_(false),
+ is_server_(true),
+ use_tagging_decrypter_(false),
+ packets_write_attempts_(0) {
+ clock_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ }
+
+ // QuicConnectionHelperInterface
+ virtual void SetConnection(QuicConnection* connection) OVERRIDE {}
+
+ virtual const QuicClock* GetClock() const OVERRIDE {
+ return clock_;
+ }
+
+ virtual QuicRandom* GetRandomGenerator() OVERRIDE {
+ return random_generator_;
+ }
+
+ virtual int WritePacketToWire(const QuicEncryptedPacket& packet,
+ int* error) OVERRIDE {
+ ++packets_write_attempts_;
+
+ if (packet.length() >= sizeof(final_bytes_of_last_packet_)) {
+ memcpy(&final_bytes_of_last_packet_, packet.data() + packet.length() - 4,
+ sizeof(final_bytes_of_last_packet_));
+ }
+
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), is_server_);
+ if (use_tagging_decrypter_) {
+ framer.SetDecrypter(new TaggingDecrypter);
+ }
+ FramerVisitorCapturingFrames visitor;
+ framer.set_visitor(&visitor);
+ EXPECT_TRUE(framer.ProcessPacket(packet));
+ header_ = *visitor.header();
+ frame_count_ = visitor.frame_count();
+ if (visitor.ack()) {
+ ack_.reset(new QuicAckFrame(*visitor.ack()));
+ }
+ if (visitor.feedback()) {
+ feedback_.reset(new QuicCongestionFeedbackFrame(*visitor.feedback()));
+ }
+ if (visitor.stream_frames() != NULL && !visitor.stream_frames()->empty()) {
+ stream_frames_ = *visitor.stream_frames();
+ }
+ if (visitor.version_negotiation_packet() != NULL) {
+ version_negotiation_packet_.reset(new QuicVersionNegotiationPacket(
+ *visitor.version_negotiation_packet()));
+ }
+ if (blocked_) {
+ *error = ERR_IO_PENDING;
+ return -1;
+ }
+ *error = 0;
+ last_packet_size_ = packet.length();
+ return last_packet_size_;
+ }
+
+ virtual bool IsWriteBlockedDataBuffered() OVERRIDE {
+ return false;
+ }
+
+ virtual bool IsWriteBlocked(int error) OVERRIDE {
+ return error == ERR_IO_PENDING;
+ }
+
+ virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE {
+ return new TestAlarm(delegate);
+ }
+
+ QuicPacketHeader* header() { return &header_; }
+
+ size_t frame_count() const { return frame_count_; }
+
+ QuicAckFrame* ack() { return ack_.get(); }
+
+ QuicCongestionFeedbackFrame* feedback() { return feedback_.get(); }
+
+ const vector<QuicStreamFrame>* stream_frames() const {
+ return &stream_frames_;
+ }
+
+ size_t last_packet_size() {
+ return last_packet_size_;
+ }
+
+ QuicVersionNegotiationPacket* version_negotiation_packet() {
+ return version_negotiation_packet_.get();
+ }
+
+ void set_blocked(bool blocked) { blocked_ = blocked; }
+
+ void set_is_server(bool is_server) { is_server_ = is_server; }
+
+ // final_bytes_of_last_packet_ returns the last four bytes of the previous
+ // packet as a little-endian, uint32. This is intended to be used with a
+ // TaggingEncrypter so that tests can determine which encrypter was used for
+ // a given packet.
+ uint32 final_bytes_of_last_packet() { return final_bytes_of_last_packet_; }
+
+ void use_tagging_decrypter() {
+ use_tagging_decrypter_ = true;
+ }
+
+ uint32 packets_write_attempts() { return packets_write_attempts_; }
+
+ private:
+ MockClock* clock_;
+ MockRandom* random_generator_;
+ QuicPacketHeader header_;
+ size_t frame_count_;
+ scoped_ptr<QuicAckFrame> ack_;
+ scoped_ptr<QuicCongestionFeedbackFrame> feedback_;
+ vector<QuicStreamFrame> stream_frames_;
+ scoped_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+ size_t last_packet_size_;
+ bool blocked_;
+ bool is_server_;
+ uint32 final_bytes_of_last_packet_;
+ bool use_tagging_decrypter_;
+ uint32 packets_write_attempts_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConnectionHelper);
+};
+
+class TestConnection : public QuicConnection {
+ public:
+ TestConnection(QuicGuid guid,
+ IPEndPoint address,
+ TestConnectionHelper* helper,
+ bool is_server)
+ : QuicConnection(guid, address, helper, is_server, QuicVersionMax()),
+ helper_(helper) {
+ helper_->set_is_server(!is_server);
+ }
+
+ void SendAck() {
+ QuicConnectionPeer::SendAck(this);
+ }
+
+ void SetReceiveAlgorithm(TestReceiveAlgorithm* receive_algorithm) {
+ QuicConnectionPeer::SetReceiveAlgorithm(this, receive_algorithm);
+ }
+
+ void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+ QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+ }
+
+ QuicConsumedData SendStreamData1() {
+ return SendStreamData(1u, "food", 0, !kFin);
+ }
+
+ QuicConsumedData SendStreamData2() {
+ return SendStreamData(2u, "food2", 0, !kFin);
+ }
+
+ bool is_server() {
+ return QuicConnectionPeer::IsServer(this);
+ }
+
+ void set_version(QuicVersion version) {
+ framer_.set_version(version);
+ }
+
+ void set_is_server(bool is_server) {
+ helper_->set_is_server(!is_server);
+ QuicPacketCreatorPeer::SetIsServer(
+ QuicConnectionPeer::GetPacketCreator(this), is_server);
+ QuicConnectionPeer::SetIsServer(this, is_server);
+ }
+
+ QuicAlarm* GetAckAlarm() {
+ return QuicConnectionPeer::GetAckAlarm(this);
+ }
+
+ QuicAlarm* GetRetransmissionAlarm() {
+ return QuicConnectionPeer::GetRetransmissionAlarm(this);
+ }
+
+ QuicAlarm* GetSendAlarm() {
+ return QuicConnectionPeer::GetSendAlarm(this);
+ }
+
+ QuicAlarm* GetTimeoutAlarm() {
+ return QuicConnectionPeer::GetTimeoutAlarm(this);
+ }
+
+ using QuicConnection::SendOrQueuePacket;
+ using QuicConnection::SelectMutualVersion;
+
+ private:
+ TestConnectionHelper* helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConnection);
+};
+
+class QuicConnectionTest : public ::testing::Test {
+ protected:
+ QuicConnectionTest()
+ : guid_(42),
+ framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ creator_(guid_, &framer_, QuicRandom::GetInstance(), false),
+ send_algorithm_(new StrictMock<MockSendAlgorithm>),
+ helper_(new TestConnectionHelper(&clock_, &random_generator_)),
+ connection_(guid_, IPEndPoint(), helper_, false),
+ frame1_(1, false, 0, data1),
+ frame2_(1, false, 3, data2),
+ accept_packet_(true) {
+ connection_.set_visitor(&visitor_);
+ connection_.SetSendAlgorithm(send_algorithm_);
+ // Simplify tests by not sending feedback unless specifically configured.
+ SetFeedback(NULL);
+ EXPECT_CALL(
+ *send_algorithm_, TimeUntilSend(_, _, _, _)).WillRepeatedly(Return(
+ QuicTime::Delta::Zero()));
+ EXPECT_CALL(*receive_algorithm_,
+ RecordIncomingPacket(_, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ Return(QuicTime::Delta::Zero()));
+ }
+
+ QuicAckFrame* outgoing_ack() {
+ outgoing_ack_.reset(QuicConnectionPeer::CreateAckFrame(&connection_));
+ return outgoing_ack_.get();
+ }
+
+ QuicAckFrame* last_ack() {
+ return helper_->ack();
+ }
+
+ QuicCongestionFeedbackFrame* last_feedback() {
+ return helper_->feedback();
+ }
+
+ QuicPacketHeader* last_header() {
+ return helper_->header();
+ }
+
+ size_t last_sent_packet_size() {
+ return helper_->last_packet_size();
+ }
+
+ uint32 final_bytes_of_last_packet() {
+ return helper_->final_bytes_of_last_packet();
+ }
+
+ void use_tagging_decrypter() {
+ helper_->use_tagging_decrypter();
+ }
+
+ void ProcessPacket(QuicPacketSequenceNumber number) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _))
+ .WillOnce(Return(accept_packet_));
+ ProcessDataPacket(number, 0, !kEntropyFlag);
+ }
+
+ QuicPacketEntropyHash ProcessFramePacket(QuicFrame frame) {
+ QuicFrames frames;
+ frames.push_back(QuicFrame(frame));
+ QuicPacketCreatorPeer::SetSendVersionInPacket(&creator_,
+ connection_.is_server());
+ SerializedPacket serialized_packet = creator_.SerializeAllFrames(frames);
+ scoped_ptr<QuicPacket> packet(serialized_packet.packet);
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(ENCRYPTION_NONE,
+ serialized_packet.sequence_number, *packet));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+ return serialized_packet.entropy_hash;
+ }
+
+ size_t ProcessFecProtectedPacket(QuicPacketSequenceNumber number,
+ bool expect_revival) {
+ if (expect_revival) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(2).WillRepeatedly(
+ Return(accept_packet_));
+ } else {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(
+ Return(accept_packet_));
+ }
+ return ProcessDataPacket(number, 1, !kEntropyFlag);
+ }
+
+ size_t ProcessDataPacket(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group,
+ bool entropy_flag) {
+ return ProcessDataPacketAtLevel(number, fec_group, entropy_flag,
+ ENCRYPTION_NONE);
+ }
+
+ size_t ProcessDataPacketAtLevel(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group,
+ bool entropy_flag,
+ EncryptionLevel level) {
+ scoped_ptr<QuicPacket> packet(ConstructDataPacket(number, fec_group,
+ entropy_flag));
+ scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+ level, number, *packet));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+ return encrypted->length();
+ }
+
+ void ProcessClosePacket(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group) {
+ scoped_ptr<QuicPacket> packet(ConstructClosePacket(number, fec_group));
+ scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+ ENCRYPTION_NONE, number, *packet));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+ }
+
+ size_t ProcessFecProtectedPacket(QuicPacketSequenceNumber number,
+ bool expect_revival, bool entropy_flag) {
+ if (expect_revival) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(DoAll(
+ SaveArg<2>(&revived_header_), Return(accept_packet_)));
+ }
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(Return(accept_packet_))
+ .RetiresOnSaturation();
+ return ProcessDataPacket(number, 1, entropy_flag);
+ }
+
+ // Sends an FEC packet that covers the packets that would have been sent.
+ size_t ProcessFecPacket(QuicPacketSequenceNumber number,
+ QuicPacketSequenceNumber min_protected_packet,
+ bool expect_revival,
+ bool entropy_flag) {
+ if (expect_revival) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(DoAll(
+ SaveArg<2>(&revived_header_), Return(accept_packet_)));
+ }
+
+ // Construct the decrypted data packet so we can compute the correct
+ // redundancy.
+ scoped_ptr<QuicPacket> data_packet(ConstructDataPacket(number, 1,
+ !kEntropyFlag));
+
+ header_.public_header.guid = guid_;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = false;
+ header_.entropy_flag = entropy_flag;
+ header_.fec_flag = true;
+ header_.packet_sequence_number = number;
+ header_.is_in_fec_group = IN_FEC_GROUP;
+ header_.fec_group = min_protected_packet;
+ QuicFecData fec_data;
+ fec_data.fec_group = header_.fec_group;
+ // Since all data packets in this test have the same payload, the
+ // redundancy is either equal to that payload or the xor of that payload
+ // with itself, depending on the number of packets.
+ if (((number - min_protected_packet) % 2) == 0) {
+ for (size_t i = GetStartOfFecProtectedData(
+ header_.public_header.guid_length,
+ header_.public_header.version_flag,
+ header_.public_header.sequence_number_length);
+ i < data_packet->length(); ++i) {
+ data_packet->mutable_data()[i] ^= data_packet->data()[i];
+ }
+ }
+ fec_data.redundancy = data_packet->FecProtectedData();
+ scoped_ptr<QuicPacket> fec_packet(
+ framer_.BuildFecPacket(header_, fec_data).packet);
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(ENCRYPTION_NONE, number, *fec_packet));
+
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+ return encrypted->length();
+ }
+
+ QuicByteCount SendStreamDataToPeer(QuicStreamId id, StringPiece data,
+ QuicStreamOffset offset, bool fin,
+ QuicPacketSequenceNumber* last_packet) {
+ QuicByteCount packet_size;
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce(
+ SaveArg<2>(&packet_size));
+ connection_.SendStreamData(id, data, offset, fin);
+ if (last_packet != NULL) {
+ *last_packet =
+ QuicConnectionPeer::GetPacketCreator(&connection_)->sequence_number();
+ }
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber());
+ return packet_size;
+ }
+
+ void SendAckPacketToPeer() {
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ connection_.SendAck();
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(AnyNumber());
+ }
+
+ QuicPacketEntropyHash ProcessAckPacket(QuicAckFrame* frame,
+ bool expect_writes) {
+ if (expect_writes) {
+ EXPECT_CALL(visitor_, OnCanWrite()).Times(1).WillOnce(Return(true));
+ }
+ return ProcessFramePacket(QuicFrame(frame));
+ }
+
+ QuicPacketEntropyHash ProcessGoAwayPacket(QuicGoAwayFrame* frame) {
+ return ProcessFramePacket(QuicFrame(frame));
+ }
+
+ bool IsMissing(QuicPacketSequenceNumber number) {
+ return IsAwaitingPacket(outgoing_ack()->received_info, number);
+ }
+
+ QuicPacket* ConstructDataPacket(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group,
+ bool entropy_flag) {
+ header_.public_header.guid = guid_;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = false;
+ header_.entropy_flag = entropy_flag;
+ header_.fec_flag = false;
+ header_.packet_sequence_number = number;
+ header_.is_in_fec_group = fec_group == 0u ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+ header_.fec_group = fec_group;
+
+ QuicFrames frames;
+ QuicFrame frame(&frame1_);
+ frames.push_back(frame);
+ QuicPacket* packet =
+ framer_.BuildUnsizedDataPacket(header_, frames).packet;
+ EXPECT_TRUE(packet != NULL);
+ return packet;
+ }
+
+ QuicPacket* ConstructClosePacket(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group) {
+ header_.public_header.guid = guid_;
+ header_.packet_sequence_number = number;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = false;
+ header_.entropy_flag = false;
+ header_.fec_flag = false;
+ header_.is_in_fec_group = fec_group == 0u ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+ header_.fec_group = fec_group;
+
+ QuicConnectionCloseFrame qccf;
+ qccf.error_code = QUIC_PEER_GOING_AWAY;
+ qccf.ack_frame = QuicAckFrame(0, QuicTime::Zero(), 1);
+
+ QuicFrames frames;
+ QuicFrame frame(&qccf);
+ frames.push_back(frame);
+ QuicPacket* packet =
+ framer_.BuildUnsizedDataPacket(header_, frames).packet;
+ EXPECT_TRUE(packet != NULL);
+ return packet;
+ }
+
+ void SetFeedback(QuicCongestionFeedbackFrame* feedback) {
+ receive_algorithm_ = new TestReceiveAlgorithm(feedback);
+ connection_.SetReceiveAlgorithm(receive_algorithm_);
+ }
+
+ QuicGuid guid_;
+ QuicFramer framer_;
+ QuicPacketCreator creator_;
+
+ MockSendAlgorithm* send_algorithm_;
+ TestReceiveAlgorithm* receive_algorithm_;
+ MockClock clock_;
+ MockRandom random_generator_;
+ TestConnectionHelper* helper_;
+ TestConnection connection_;
+ testing::StrictMock<MockConnectionVisitor> visitor_;
+
+ QuicPacketHeader header_;
+ QuicPacketHeader revived_header_;
+ QuicStreamFrame frame1_;
+ QuicStreamFrame frame2_;
+ scoped_ptr<QuicAckFrame> outgoing_ack_;
+ bool accept_packet_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicConnectionTest);
+};
+
+TEST_F(QuicConnectionTest, PacketsInOrder) {
+ ProcessPacket(1);
+ EXPECT_EQ(1u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size());
+
+ ProcessPacket(2);
+ EXPECT_EQ(2u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size());
+
+ ProcessPacket(3);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size());
+}
+
+TEST_F(QuicConnectionTest, PacketsRejected) {
+ ProcessPacket(1);
+ EXPECT_EQ(1u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size());
+
+ accept_packet_ = false;
+ ProcessPacket(2);
+ // We should not have an ack for two.
+ EXPECT_EQ(1u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_EQ(0u, outgoing_ack()->received_info.missing_packets.size());
+}
+
+TEST_F(QuicConnectionTest, PacketsOutOfOrder) {
+ ProcessPacket(3);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(2));
+ EXPECT_TRUE(IsMissing(1));
+
+ ProcessPacket(2);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_FALSE(IsMissing(2));
+ EXPECT_TRUE(IsMissing(1));
+
+ ProcessPacket(1);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_FALSE(IsMissing(2));
+ EXPECT_FALSE(IsMissing(1));
+}
+
+TEST_F(QuicConnectionTest, DuplicatePacket) {
+ ProcessPacket(3);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(2));
+ EXPECT_TRUE(IsMissing(1));
+
+ // Send packet 3 again, but do not set the expectation that
+ // the visitor OnPacket() will be called.
+ ProcessDataPacket(3, 0, !kEntropyFlag);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(2));
+ EXPECT_TRUE(IsMissing(1));
+}
+
+TEST_F(QuicConnectionTest, PacketsOutOfOrderWithAdditionsAndLeastAwaiting) {
+ ProcessPacket(3);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(2));
+ EXPECT_TRUE(IsMissing(1));
+
+ ProcessPacket(2);
+ EXPECT_EQ(3u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(1));
+
+ ProcessPacket(5);
+ EXPECT_EQ(5u, outgoing_ack()->received_info.largest_observed);
+ EXPECT_TRUE(IsMissing(1));
+ EXPECT_TRUE(IsMissing(4));
+
+ // Pretend at this point the client has gotten acks for 2 and 3 and 1 is a
+ // packet the peer will not retransmit. It indicates this by sending 'least
+ // awaiting' is 4. The connection should then realize 1 will not be
+ // retransmitted, and will remove it from the missing list.
+ creator_.set_sequence_number(5);
+ QuicAckFrame frame(0, QuicTime::Zero(), 4);
+ ProcessAckPacket(&frame, true);
+
+ // Force an ack to be sent.
+ SendAckPacketToPeer();
+ EXPECT_TRUE(IsMissing(4));
+}
+
+TEST_F(QuicConnectionTest, RejectPacketTooFarOut) {
+ // Call ProcessDataPacket rather than ProcessPacket, as we should not get a
+ // packet call to the visitor.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_PACKET_HEADER, false));
+ ProcessDataPacket(6000, 0, !kEntropyFlag);
+}
+
+TEST_F(QuicConnectionTest, TruncatedAck) {
+ EXPECT_CALL(visitor_, OnAck(_)).Times(testing::AnyNumber());
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(2);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ for (int i = 0; i < 200; ++i) {
+ SendStreamDataToPeer(1, "foo", i * 3, !kFin, NULL);
+ }
+
+ QuicAckFrame frame(0, QuicTime::Zero(), 1);
+ frame.received_info.largest_observed = 192;
+ InsertMissingPacketsBetween(&frame.received_info, 1, 192);
+ frame.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 192) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 191);
+
+ ProcessAckPacket(&frame, true);
+
+ EXPECT_TRUE(QuicConnectionPeer::GetReceivedTruncatedAck(&connection_));
+
+ frame.received_info.missing_packets.erase(191);
+ frame.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 192) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 190);
+
+ ProcessAckPacket(&frame, true);
+ EXPECT_FALSE(QuicConnectionPeer::GetReceivedTruncatedAck(&connection_));
+}
+
+TEST_F(QuicConnectionTest, AckReceiptCausesAckSendBadEntropy) {
+ ProcessPacket(1);
+ // Delay sending, then queue up an ack.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ QuicConnectionPeer::SendAck(&connection_);
+
+ // Process an ack with a least unacked of the received ack.
+ // This causes an ack to be sent when TimeUntilSend returns 0.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ // Skip a packet and then record an ack.
+ creator_.set_sequence_number(2);
+ QuicAckFrame frame(0, QuicTime::Zero(), 3);
+ ProcessAckPacket(&frame, true);
+}
+
+TEST_F(QuicConnectionTest, AckReceiptCausesAckSend) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ QuicPacketSequenceNumber largest_observed;
+ QuicByteCount packet_size;
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION))
+ .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size)));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ QuicAckFrame frame(1, QuicTime::Zero(), largest_observed);
+ frame.received_info.missing_packets.insert(largest_observed);
+ frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(
+ &connection_, largest_observed - 1);
+ ProcessAckPacket(&frame, true);
+ ProcessAckPacket(&frame, true);
+ // Third nack should retransmit the largest observed packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, packet_size - kQuicVersionSize,
+ IS_RETRANSMISSION));
+ ProcessAckPacket(&frame, true);
+
+ // Now if the peer sends an ack which still reports the retransmitted packet
+ // as missing, then that will count as a packet which instigates an ack.
+ ProcessAckPacket(&frame, true);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION));
+ ProcessAckPacket(&frame, true);
+
+ // But an ack with no new missing packest will not send an ack.
+ frame.received_info.missing_packets.clear();
+ ProcessAckPacket(&frame, true);
+ ProcessAckPacket(&frame, true);
+}
+
+TEST_F(QuicConnectionTest, LeastUnackedLower) {
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ SendStreamDataToPeer(1, "bar", 3, !kFin, NULL);
+ SendStreamDataToPeer(1, "eep", 6, !kFin, NULL);
+
+ // Start out saying the least unacked is 2
+ creator_.set_sequence_number(5);
+ QuicAckFrame frame(0, QuicTime::Zero(), 2);
+ ProcessAckPacket(&frame, true);
+
+ // Change it to 1, but lower the sequence number to fake out-of-order packets.
+ // This should be fine.
+ creator_.set_sequence_number(1);
+ QuicAckFrame frame2(0, QuicTime::Zero(), 1);
+ // The scheduler will not process out of order acks.
+ ProcessAckPacket(&frame2, false);
+
+ // Now claim it's one, but set the ordering so it was sent "after" the first
+ // one. This should cause a connection error.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ creator_.set_sequence_number(7);
+ ProcessAckPacket(&frame2, false);
+}
+
+TEST_F(QuicConnectionTest, LargestObservedLower) {
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ SendStreamDataToPeer(1, "bar", 3, !kFin, NULL);
+ SendStreamDataToPeer(1, "eep", 6, !kFin, NULL);
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(2);
+
+ // Start out saying the largest observed is 2.
+ QuicAckFrame frame(2, QuicTime::Zero(), 0);
+ frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(
+ &connection_, 2);
+ EXPECT_CALL(visitor_, OnAck(_));
+ ProcessAckPacket(&frame, true);
+
+ // Now change it to 1, and it should cause a connection error.
+ QuicAckFrame frame2(1, QuicTime::Zero(), 0);
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false));
+ ProcessAckPacket(&frame2, false);
+}
+
+TEST_F(QuicConnectionTest, LeastUnackedGreaterThanPacketSequenceNumber) {
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ // Create an ack with least_unacked is 2 in packet number 1.
+ creator_.set_sequence_number(0);
+ QuicAckFrame frame(0, QuicTime::Zero(), 2);
+ ProcessAckPacket(&frame, false);
+}
+
+TEST_F(QuicConnectionTest,
+ NackSequenceNumberGreaterThanLargestReceived) {
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ SendStreamDataToPeer(1, "bar", 3, !kFin, NULL);
+ SendStreamDataToPeer(1, "eep", 6, !kFin, NULL);
+
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ QuicAckFrame frame(0, QuicTime::Zero(), 1);
+ frame.received_info.missing_packets.insert(3);
+ ProcessAckPacket(&frame, false);
+}
+
+TEST_F(QuicConnectionTest, AckUnsentData) {
+ // Ack a packet which has not been sent.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_ACK_DATA, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ QuicAckFrame frame(1, QuicTime::Zero(), 0);
+ ProcessAckPacket(&frame, false);
+}
+
+TEST_F(QuicConnectionTest, AckAll) {
+ ProcessPacket(1);
+
+ creator_.set_sequence_number(1);
+ QuicAckFrame frame1(0, QuicTime::Zero(), 1);
+ ProcessAckPacket(&frame1, true);
+}
+
+TEST_F(QuicConnectionTest, BasicSending) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(6);
+ QuicPacketSequenceNumber last_packet;
+ SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet); // Packet 1
+ EXPECT_EQ(1u, last_packet);
+ SendAckPacketToPeer(); // Packet 2
+
+ EXPECT_EQ(1u, last_ack()->sent_info.least_unacked);
+
+ SendAckPacketToPeer(); // Packet 3
+ EXPECT_EQ(1u, last_ack()->sent_info.least_unacked);
+
+ SendStreamDataToPeer(1u, "bar", 3, !kFin, &last_packet); // Packet 4
+ EXPECT_EQ(4u, last_packet);
+ SendAckPacketToPeer(); // Packet 5
+ EXPECT_EQ(1u, last_ack()->sent_info.least_unacked);
+
+ SequenceNumberSet expected_acks;
+ expected_acks.insert(1);
+
+ // Peer acks up to packet 3.
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+ QuicAckFrame frame(3, QuicTime::Zero(), 0);
+ frame.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 3);
+ ProcessAckPacket(&frame, true);
+ SendAckPacketToPeer(); // Packet 6
+
+ // As soon as we've acked one, we skip ack packets 2 and 3 and note lack of
+ // ack for 4.
+ EXPECT_EQ(4u, last_ack()->sent_info.least_unacked);
+
+ expected_acks.clear();
+ expected_acks.insert(4);
+
+ // Peer acks up to packet 4, the last packet.
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+ QuicAckFrame frame2(6, QuicTime::Zero(), 0);
+ frame2.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 6);
+ ProcessAckPacket(&frame2, true); // Acks don't instigate acks.
+
+ // Verify that we did not send an ack.
+ EXPECT_EQ(6u, last_header()->packet_sequence_number);
+
+ // So the last ack has not changed.
+ EXPECT_EQ(4u, last_ack()->sent_info.least_unacked);
+
+ // If we force an ack, we shouldn't change our retransmit state.
+ SendAckPacketToPeer(); // Packet 7
+ EXPECT_EQ(7u, last_ack()->sent_info.least_unacked);
+
+ // But if we send more data it should.
+ SendStreamDataToPeer(1, "eep", 6, !kFin, &last_packet); // Packet 8
+ EXPECT_EQ(8u, last_packet);
+ SendAckPacketToPeer(); // Packet 9
+ EXPECT_EQ(8u, last_ack()->sent_info.least_unacked);
+}
+
+TEST_F(QuicConnectionTest, FECSending) {
+ // All packets carry version info till version is negotiated.
+ size_t payload_length;
+ connection_.options()->max_packet_length =
+ GetPacketLengthForOneStream(connection_.version(), kIncludeVersion,
+ IN_FEC_GROUP, &payload_length);
+ // And send FEC every two packets.
+ connection_.options()->max_packets_per_fec_group = 2;
+
+ // Send 4 data packets and 2 FEC packets.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(6);
+ // The first stream frame will consume 2 fewer bytes than the other three.
+ const string payload(payload_length * 4 - 6, 'a');
+ connection_.SendStreamData(1, payload, 0, !kFin);
+ // Expect the FEC group to be closed after SendStreamData.
+ EXPECT_FALSE(creator_.ShouldSendFec(true));
+}
+
+TEST_F(QuicConnectionTest, FECQueueing) {
+ // All packets carry version info till version is negotiated.
+ size_t payload_length;
+ connection_.options()->max_packet_length =
+ GetPacketLengthForOneStream(connection_.version(), kIncludeVersion,
+ IN_FEC_GROUP, &payload_length);
+ // And send FEC every two packets.
+ connection_.options()->max_packets_per_fec_group = 2;
+
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ helper_->set_blocked(true);
+ const string payload(payload_length, 'a');
+ connection_.SendStreamData(1, payload, 0, !kFin);
+ EXPECT_FALSE(creator_.ShouldSendFec(true));
+ // Expect the first data packet and the fec packet to be queued.
+ EXPECT_EQ(2u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, AbandonFECFromCongestionWindow) {
+ connection_.options()->max_packets_per_fec_group = 1;
+ // 1 Data and 1 FEC packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(2);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+
+ // Larger timeout for FEC bytes to expire.
+ const QuicTime::Delta retransmission_time =
+ QuicTime::Delta::FromMilliseconds(5000);
+ clock_.AdvanceTime(retransmission_time);
+
+ // Send only data packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ // Abandon both FEC and data packet.
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2);
+
+ connection_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicConnectionTest, DontAbandonAckedFEC) {
+ connection_.options()->max_packets_per_fec_group = 1;
+ const QuicPacketSequenceNumber sequence_number =
+ QuicConnectionPeer::GetPacketCreator(&connection_)->sequence_number() + 1;
+
+ // 1 Data and 1 FEC packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(2);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+
+ QuicAckFrame ack_fec(2, QuicTime::Zero(), 1);
+ // Data packet missing.
+ ack_fec.received_info.missing_packets.insert(1);
+ ack_fec.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 2) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1);
+
+ EXPECT_CALL(visitor_, OnAck(_)).Times(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+
+ ProcessAckPacket(&ack_fec, true);
+
+ const QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(5000);
+ clock_.AdvanceTime(kDefaultRetransmissionTime);
+
+ // Abandon only data packet, FEC has been acked.
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(sequence_number, _)).Times(1);
+ // Send only data packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ connection_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicConnectionTest, FramePacking) {
+ // Block the connection.
+ connection_.GetSendAlarm()->Set(
+ clock_.ApproximateNow().Add(QuicTime::Delta::FromSeconds(1)));
+
+ // Send an ack and two stream frames in 1 packet by queueing them.
+ connection_.SendAck();
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData1)),
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData2)),
+ Return(true)));
+
+ // Unblock the connection.
+ connection_.GetSendAlarm()->Cancel();
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, NOT_RETRANSMISSION))
+ .Times(1);
+ connection_.OnCanWrite();
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ EXPECT_FALSE(connection_.HasQueuedData());
+
+ // Parse the last packet and ensure it's an ack and two stream frames from
+ // two different streams.
+ EXPECT_EQ(3u, helper_->frame_count());
+ EXPECT_TRUE(helper_->ack());
+ EXPECT_EQ(2u, helper_->stream_frames()->size());
+ EXPECT_EQ(1u, (*helper_->stream_frames())[0].stream_id);
+ EXPECT_EQ(2u, (*helper_->stream_frames())[1].stream_id);
+}
+
+TEST_F(QuicConnectionTest, FramePackingFEC) {
+ // Enable fec.
+ connection_.options()->max_packets_per_fec_group = 6;
+ // Block the connection.
+ connection_.GetSendAlarm()->Set(
+ clock_.ApproximateNow().Add(QuicTime::Delta::FromSeconds(1)));
+
+ // Send an ack and two stream frames in 1 packet by queueing them.
+ connection_.SendAck();
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData1)),
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData2)),
+ Return(true)));
+
+ // Unblock the connection.
+ connection_.GetSendAlarm()->Cancel();
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, NOT_RETRANSMISSION)).Times(2);
+ connection_.OnCanWrite();
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ EXPECT_FALSE(connection_.HasQueuedData());
+
+ // Parse the last packet and ensure it's in an fec group.
+ EXPECT_EQ(1u, helper_->header()->fec_group);
+ EXPECT_EQ(0u, helper_->frame_count());
+}
+
+TEST_F(QuicConnectionTest, OnCanWrite) {
+ // Visitor's OnCanWill send data, but will return false.
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(DoAll(
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData1)),
+ IgnoreResult(InvokeWithoutArgs(&connection_,
+ &TestConnection::SendStreamData2)),
+ Return(false)));
+
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+
+ // Unblock the connection.
+ connection_.OnCanWrite();
+ // Parse the last packet and ensure it's the two stream frames from
+ // two different streams.
+ EXPECT_EQ(2u, helper_->frame_count());
+ EXPECT_EQ(2u, helper_->stream_frames()->size());
+ EXPECT_EQ(1u, (*helper_->stream_frames())[0].stream_id);
+ EXPECT_EQ(2u, (*helper_->stream_frames())[1].stream_id);
+}
+
+TEST_F(QuicConnectionTest, RetransmitOnNack) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(2);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(2, _)).Times(1);
+ QuicPacketSequenceNumber last_packet;
+ QuicByteCount second_packet_size;
+ SendStreamDataToPeer(1, "foo", 0, !kFin, &last_packet); // Packet 1
+ second_packet_size =
+ SendStreamDataToPeer(1, "foos", 3, !kFin, &last_packet); // Packet 2
+ SendStreamDataToPeer(1, "fooos", 7, !kFin, &last_packet); // Packet 3
+
+ SequenceNumberSet expected_acks;
+ expected_acks.insert(1);
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+
+ // Peer acks one but not two or three. Right now we only retransmit on
+ // explicit nack, so it should not trigger a retransimission.
+ QuicAckFrame ack_one(1, QuicTime::Zero(), 0);
+ ack_one.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1);
+ ProcessAckPacket(&ack_one, true);
+ ProcessAckPacket(&ack_one, true);
+ ProcessAckPacket(&ack_one, true);
+
+ expected_acks.clear();
+ expected_acks.insert(3);
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+
+ // Peer acks up to 3 with two explicitly missing. Two nacks should cause no
+ // change.
+ QuicAckFrame nack_two(3, QuicTime::Zero(), 0);
+ nack_two.received_info.missing_packets.insert(2);
+ nack_two.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 3) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 2) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1);
+ ProcessAckPacket(&nack_two, true);
+ ProcessAckPacket(&nack_two, true);
+
+ // The third nack should trigger a retransimission.
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, second_packet_size - kQuicVersionSize,
+ IS_RETRANSMISSION)).Times(1);
+ ProcessAckPacket(&nack_two, true);
+}
+
+TEST_F(QuicConnectionTest, RetransmitNackedLargestObserved) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ QuicPacketSequenceNumber largest_observed;
+ QuicByteCount packet_size;
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION))
+ .WillOnce(DoAll(SaveArg<1>(&largest_observed), SaveArg<2>(&packet_size)));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ QuicAckFrame frame(1, QuicTime::Zero(), largest_observed);
+ frame.received_info.missing_packets.insert(largest_observed);
+ frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(
+ &connection_, largest_observed - 1);
+ ProcessAckPacket(&frame, true);
+ ProcessAckPacket(&frame, true);
+ // Third nack should retransmit the largest observed packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, packet_size - kQuicVersionSize,
+ IS_RETRANSMISSION));
+ ProcessAckPacket(&frame, true);
+}
+
+TEST_F(QuicConnectionTest, RetransmitNackedPacketsOnTruncatedAck) {
+ for (int i = 0; i < 200; ++i) {
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ connection_.SendStreamData(1, "foo", i * 3, !kFin);
+ }
+
+ // Make a truncated ack frame.
+ QuicAckFrame frame(0, QuicTime::Zero(), 1);
+ frame.received_info.largest_observed = 192;
+ InsertMissingPacketsBetween(&frame.received_info, 1, 192);
+ frame.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 192) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 191);
+
+
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ EXPECT_CALL(visitor_, OnAck(_)).Times(1);
+ ProcessAckPacket(&frame, true);
+ EXPECT_TRUE(QuicConnectionPeer::GetReceivedTruncatedAck(&connection_));
+
+ QuicConnectionPeer::SetMaxPacketsPerRetransmissionAlarm(&connection_, 200);
+ const QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+ clock_.AdvanceTime(kDefaultRetransmissionTime);
+ // Only packets that are less than largest observed should be retransmitted.
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(191);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(191);
+ connection_.OnRetransmissionTimeout();
+
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(
+ 2 * kDefaultRetransmissionTime.ToMicroseconds()));
+ // Retransmit already retransmitted packets event though the sequence number
+ // greater than the largest observed.
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(191);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(191);
+ connection_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicConnectionTest, LimitPacketsPerNack) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(12, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(11);
+ int offset = 0;
+ // Send packets 1 to 12
+ for (int i = 0; i < 12; ++i) {
+ SendStreamDataToPeer(1, "foo", offset, !kFin, NULL);
+ offset += 3;
+ }
+
+ // Ack 12, nack 1-11
+ QuicAckFrame nack(12, QuicTime::Zero(), 0);
+ for (int i = 1; i < 12; ++i) {
+ nack.received_info.missing_packets.insert(i);
+ }
+
+ nack.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 12) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 11);
+ SequenceNumberSet expected_acks;
+ expected_acks.insert(12);
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+
+ // Nack three times.
+ ProcessAckPacket(&nack, true);
+ ProcessAckPacket(&nack, true);
+ // The third call should trigger retransmitting 10 packets.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(10);
+ ProcessAckPacket(&nack, true);
+
+ // The fourth call should trigger retransmitting the 11th packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ ProcessAckPacket(&nack, true);
+}
+
+// Test sending multiple acks from the connection to the session.
+TEST_F(QuicConnectionTest, MultipleAcks) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(6);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ QuicPacketSequenceNumber last_packet;
+ SendStreamDataToPeer(1u, "foo", 0, !kFin, &last_packet); // Packet 1
+ EXPECT_EQ(1u, last_packet);
+ SendStreamDataToPeer(3u, "foo", 0, !kFin, &last_packet); // Packet 2
+ EXPECT_EQ(2u, last_packet);
+ SendAckPacketToPeer(); // Packet 3
+ SendStreamDataToPeer(5u, "foo", 0, !kFin, &last_packet); // Packet 4
+ EXPECT_EQ(4u, last_packet);
+ SendStreamDataToPeer(1u, "foo", 3, !kFin, &last_packet); // Packet 5
+ EXPECT_EQ(5u, last_packet);
+ SendStreamDataToPeer(3u, "foo", 3, !kFin, &last_packet); // Packet 6
+ EXPECT_EQ(6u, last_packet);
+
+ // Client will ack packets 1, [!2], 3, 4, 5
+ QuicAckFrame frame1(5, QuicTime::Zero(), 0);
+ frame1.received_info.missing_packets.insert(2);
+ frame1.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 5) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 2) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 1);
+
+ // The connection should pass up acks for 1, 4, 5. 2 is not acked, and 3 was
+ // an ackframe so should not be passed up.
+ SequenceNumberSet expected_acks;
+ expected_acks.insert(1);
+ expected_acks.insert(4);
+ expected_acks.insert(5);
+
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+ ProcessAckPacket(&frame1, true);
+
+ // Now the client implicitly acks 2, and explicitly acks 6
+ QuicAckFrame frame2(6, QuicTime::Zero(), 0);
+ frame2.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 6);
+ expected_acks.clear();
+ // Both acks should be passed up.
+ expected_acks.insert(2);
+ expected_acks.insert(6);
+
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+ ProcessAckPacket(&frame2, true);
+}
+
+TEST_F(QuicConnectionTest, DontLatchUnackedPacket) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1);
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL); // Packet 1;
+ SendAckPacketToPeer(); // Packet 2
+
+ // This sets least unacked to 3 (unsent packet), since we don't need
+ // an ack for Packet 2 (ack packet).
+ SequenceNumberSet expected_acks;
+ expected_acks.insert(1);
+ // Peer acks packet 1.
+ EXPECT_CALL(visitor_, OnAck(ContainerEq(expected_acks)));
+ QuicAckFrame frame(1, QuicTime::Zero(), 0);
+ frame.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(
+ &connection_, 1);
+ ProcessAckPacket(&frame, true);
+
+ // Verify that our internal state has least-unacked as 3.
+ EXPECT_EQ(3u, outgoing_ack()->sent_info.least_unacked);
+
+ // When we send an ack, we make sure our least-unacked makes sense. In this
+ // case since we're not waiting on an ack for 2 and all packets are acked, we
+ // set it to 3.
+ SendAckPacketToPeer(); // Packet 3
+ // Since this was an ack packet, we set least_unacked to 4.
+ EXPECT_EQ(4u, outgoing_ack()->sent_info.least_unacked);
+ // Check that the outgoing ack had its sequence number as least_unacked.
+ EXPECT_EQ(3u, last_ack()->sent_info.least_unacked);
+
+ SendStreamDataToPeer(1, "bar", 3, false, NULL); // Packet 4
+ EXPECT_EQ(4u, outgoing_ack()->sent_info.least_unacked);
+ SendAckPacketToPeer(); // Packet 5
+ EXPECT_EQ(4u, last_ack()->sent_info.least_unacked);
+}
+
+TEST_F(QuicConnectionTest, ReviveMissingPacketAfterFecPacket) {
+ // Don't send missing packet 1.
+ ProcessFecPacket(2, 1, true, !kEntropyFlag);
+ EXPECT_FALSE(revived_header_.entropy_flag);
+}
+
+TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacketThenFecPacket) {
+ ProcessFecProtectedPacket(1, false, kEntropyFlag);
+ // Don't send missing packet 2.
+ ProcessFecPacket(3, 1, true, !kEntropyFlag);
+ EXPECT_TRUE(revived_header_.entropy_flag);
+}
+
+TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacketsThenFecPacket) {
+ ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+ // Don't send missing packet 2.
+ ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+ ProcessFecPacket(4, 1, true, kEntropyFlag);
+ EXPECT_TRUE(revived_header_.entropy_flag);
+}
+
+TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPacket) {
+ // Don't send missing packet 1.
+ ProcessFecPacket(3, 1, false, !kEntropyFlag);
+ // out of order
+ ProcessFecProtectedPacket(2, true, !kEntropyFlag);
+ EXPECT_FALSE(revived_header_.entropy_flag);
+}
+
+TEST_F(QuicConnectionTest, ReviveMissingPacketAfterDataPackets) {
+ ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+ // Don't send missing packet 2.
+ ProcessFecPacket(6, 1, false, kEntropyFlag);
+ ProcessFecProtectedPacket(3, false, kEntropyFlag);
+ ProcessFecProtectedPacket(4, false, kEntropyFlag);
+ ProcessFecProtectedPacket(5, true, !kEntropyFlag);
+ EXPECT_TRUE(revived_header_.entropy_flag);
+}
+
+TEST_F(QuicConnectionTest, TestRetransmit) {
+ const QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+
+ QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+ kDefaultRetransmissionTime);
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ EXPECT_EQ(1u, outgoing_ack()->sent_info.least_unacked);
+
+ EXPECT_EQ(1u, last_header()->packet_sequence_number);
+ EXPECT_EQ(default_retransmission_time,
+ connection_.GetRetransmissionAlarm()->deadline());
+ // Simulate the retransimission alarm firing
+ clock_.AdvanceTime(kDefaultRetransmissionTime);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1);
+ connection_.RetransmitPacket(1);
+ EXPECT_EQ(2u, last_header()->packet_sequence_number);
+ EXPECT_EQ(2u, outgoing_ack()->sent_info.least_unacked);
+}
+
+TEST_F(QuicConnectionTest, RetransmitWithSameEncryptionLevel) {
+ const QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+
+ QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+ kDefaultRetransmissionTime);
+ use_tagging_decrypter();
+
+ // A TaggingEncrypter puts kTagSize copies of the given byte (0x01 here) at
+ // the end of the packet. We can test this to check which encrypter was used.
+ connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ EXPECT_EQ(0x01010101u, final_bytes_of_last_packet());
+
+ connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(0x02));
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+ EXPECT_EQ(0x02020202u, final_bytes_of_last_packet());
+
+ EXPECT_EQ(default_retransmission_time,
+ connection_.GetRetransmissionAlarm()->deadline());
+ // Simulate the retransimission alarm firing
+ clock_.AdvanceTime(kDefaultRetransmissionTime);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2);
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ connection_.RetransmitPacket(1);
+ // Packet should have been sent with ENCRYPTION_NONE.
+ EXPECT_EQ(0x01010101u, final_bytes_of_last_packet());
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ connection_.RetransmitPacket(2);
+ // Packet should have been sent with ENCRYPTION_INITIAL.
+ EXPECT_EQ(0x02020202u, final_bytes_of_last_packet());
+}
+
+TEST_F(QuicConnectionTest,
+ DropRetransmitsForNullEncryptedPacketAfterForwardSecure) {
+ use_tagging_decrypter();
+ connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+ QuicPacketSequenceNumber sequence_number;
+ SendStreamDataToPeer(1, "foo", 0, !kFin, &sequence_number);
+
+ connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+ new TaggingEncrypter(0x02));
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(0);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(sequence_number, _)).Times(1);
+
+ const QuicTime::Delta kDefaultRetransmissionTime =
+ QuicTime::Delta::FromMilliseconds(500);
+ QuicTime default_retransmission_time = clock_.ApproximateNow().Add(
+ kDefaultRetransmissionTime);
+
+ EXPECT_EQ(default_retransmission_time,
+ connection_.GetRetransmissionAlarm()->deadline());
+ // Simulate the retransimission alarm firing
+ clock_.AdvanceTime(kDefaultRetransmissionTime);
+ connection_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicConnectionTest, RetransmitPacketsWithInitialEncryption) {
+ use_tagging_decrypter();
+ connection_.SetEncrypter(ENCRYPTION_NONE, new TaggingEncrypter(0x01));
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_NONE);
+
+ SendStreamDataToPeer(1, "foo", 0, !kFin, NULL);
+
+ connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(0x02));
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+
+ SendStreamDataToPeer(2, "bar", 0, !kFin, NULL);
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(1);
+
+ connection_.RetransmitUnackedPackets(QuicConnection::INITIAL_ENCRYPTION_ONLY);
+}
+
+TEST_F(QuicConnectionTest, BufferNonDecryptablePackets) {
+ use_tagging_decrypter();
+
+ const uint8 tag = 0x07;
+ framer_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag));
+
+ // Process an encrypted packet which can not yet be decrypted
+ // which should result in the packet being buffered.
+ ProcessDataPacketAtLevel(1, false, kEntropyFlag, ENCRYPTION_INITIAL);
+
+ // Transition to the new encryption state and process another
+ // encrypted packet which should result in the original packet being
+ // processed.
+ connection_.SetDecrypter(new StrictTaggingDecrypter(tag));
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+ connection_.SetEncrypter(ENCRYPTION_INITIAL, new TaggingEncrypter(tag));
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(2).WillRepeatedly(
+ Return(true));
+ ProcessDataPacketAtLevel(2, false, kEntropyFlag, ENCRYPTION_INITIAL);
+
+ // Finally, process a third packet and note that we do not
+ // reprocess the buffered packet.
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillOnce(Return(true));
+ ProcessDataPacketAtLevel(3, false, kEntropyFlag, ENCRYPTION_INITIAL);
+}
+
+TEST_F(QuicConnectionTest, TestRetransmitOrder) {
+ QuicByteCount first_packet_size;
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce(
+ SaveArg<2>(&first_packet_size));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2);
+
+ connection_.SendStreamData(1, "first_packet", 0, !kFin);
+ QuicByteCount second_packet_size;
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).WillOnce(
+ SaveArg<2>(&second_packet_size));
+ connection_.SendStreamData(1, "second_packet", 12, !kFin);
+ EXPECT_NE(first_packet_size, second_packet_size);
+ // Advance the clock by huge time to make sure packets will be retransmitted.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+ {
+ InSequence s;
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, first_packet_size, _));
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, second_packet_size, _));
+ }
+ connection_.OnRetransmissionTimeout();
+}
+
+TEST_F(QuicConnectionTest, TestRetransmissionCountCalculation) {
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(2);
+ QuicPacketSequenceNumber original_sequence_number;
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, NOT_RETRANSMISSION))
+ .WillOnce(SaveArg<1>(&original_sequence_number));
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ EXPECT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+ &connection_, original_sequence_number));
+ EXPECT_EQ(0u, QuicConnectionPeer::GetRetransmissionCount(
+ &connection_, original_sequence_number));
+ // Force retransmission due to RTO.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+ QuicPacketSequenceNumber rto_sequence_number;
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, IS_RETRANSMISSION))
+ .WillOnce(SaveArg<1>(&rto_sequence_number));
+ connection_.OnRetransmissionTimeout();
+ EXPECT_FALSE(QuicConnectionPeer::IsSavedForRetransmission(
+ &connection_, original_sequence_number));
+ ASSERT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+ &connection_, rto_sequence_number));
+ EXPECT_EQ(1u, QuicConnectionPeer::GetRetransmissionCount(
+ &connection_, rto_sequence_number));
+ // Once by explicit nack.
+ QuicPacketSequenceNumber nack_sequence_number;
+ // Ack packets might generate some other packets, which are not
+ // retransmissions. (More ack packets).
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, NOT_RETRANSMISSION))
+ .Times(AnyNumber());
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, IS_RETRANSMISSION))
+ .WillOnce(SaveArg<1>(&nack_sequence_number));
+ QuicAckFrame ack(rto_sequence_number, QuicTime::Zero(), 0);
+ // Ack the retransmitted packet.
+ ack.received_info.missing_packets.insert(rto_sequence_number);
+ ack.received_info.entropy_hash = QuicConnectionPeer::GetSentEntropyHash(
+ &connection_, rto_sequence_number - 1);
+ for (int i = 0; i < 3; i++) {
+ ProcessAckPacket(&ack, true);
+ }
+ EXPECT_FALSE(QuicConnectionPeer::IsSavedForRetransmission(
+ &connection_, rto_sequence_number));
+ EXPECT_TRUE(QuicConnectionPeer::IsSavedForRetransmission(
+ &connection_, nack_sequence_number));
+ EXPECT_EQ(2u, QuicConnectionPeer::GetRetransmissionCount(
+ &connection_, nack_sequence_number));
+}
+
+TEST_F(QuicConnectionTest, SetRTOAfterWritingToSocket) {
+ helper_->set_blocked(true);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ // Make sure that RTO is not started when the packet is queued.
+ EXPECT_EQ(0u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_));
+
+ // Test that RTO is started once we write to the socket.
+ helper_->set_blocked(false);
+ EXPECT_CALL(visitor_, OnCanWrite());
+ connection_.OnCanWrite();
+ EXPECT_EQ(1u, QuicConnectionPeer::GetNumRetransmissionTimeouts(&connection_));
+}
+
+TEST_F(QuicConnectionTest, TestQueued) {
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ helper_->set_blocked(true);
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Attempt to send all packets, but since we're actually still
+ // blocked, they should all remain queued.
+ EXPECT_FALSE(connection_.OnCanWrite());
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Unblock the writes and actually send.
+ helper_->set_blocked(false);
+ EXPECT_CALL(visitor_, OnCanWrite());
+ EXPECT_TRUE(connection_.OnCanWrite());
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, CloseFecGroup) {
+ // Don't send missing packet 1
+ // Don't send missing packet 2
+ ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+ // Don't send missing FEC packet 3
+ ASSERT_EQ(1u, connection_.NumFecGroups());
+
+ // Now send non-fec protected ack packet and close the group
+ QuicAckFrame frame(0, QuicTime::Zero(), 5);
+ creator_.set_sequence_number(4);
+ ProcessAckPacket(&frame, true);
+ ASSERT_EQ(0u, connection_.NumFecGroups());
+}
+
+TEST_F(QuicConnectionTest, NoQuicCongestionFeedbackFrame) {
+ SendAckPacketToPeer();
+ EXPECT_TRUE(last_feedback() == NULL);
+}
+
+TEST_F(QuicConnectionTest, WithQuicCongestionFeedbackFrame) {
+ QuicCongestionFeedbackFrame info;
+ info.type = kFixRate;
+ info.fix_rate.bitrate = QuicBandwidth::FromBytesPerSecond(123);
+ SetFeedback(&info);
+
+ SendAckPacketToPeer();
+ EXPECT_EQ(kFixRate, last_feedback()->type);
+ EXPECT_EQ(info.fix_rate.bitrate, last_feedback()->fix_rate.bitrate);
+}
+
+TEST_F(QuicConnectionTest, UpdateQuicCongestionFeedbackFrame) {
+ SendAckPacketToPeer();
+ EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _));
+ ProcessPacket(1);
+}
+
+TEST_F(QuicConnectionTest, DontUpdateQuicCongestionFeedbackFrameForRevived) {
+ SendAckPacketToPeer();
+ // Process an FEC packet, and revive the missing data packet
+ // but only contact the receive_algorithm once.
+ EXPECT_CALL(*receive_algorithm_, RecordIncomingPacket(_, _, _, _));
+ ProcessFecPacket(2, 1, true, !kEntropyFlag);
+}
+
+TEST_F(QuicConnectionTest, InitialTimeout) {
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+
+ QuicTime default_timeout = clock_.ApproximateNow().Add(
+ QuicTime::Delta::FromSeconds(kDefaultInitialTimeoutSecs));
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+ // Simulate the timeout alarm firing
+ clock_.AdvanceTime(
+ QuicTime::Delta::FromSeconds(kDefaultInitialTimeoutSecs));
+ EXPECT_TRUE(connection_.CheckForTimeout());
+ EXPECT_FALSE(connection_.connected());
+}
+
+TEST_F(QuicConnectionTest, TimeoutAfterSend) {
+ EXPECT_TRUE(connection_.connected());
+
+ QuicTime default_timeout = clock_.ApproximateNow().Add(
+ QuicTime::Delta::FromSeconds(kDefaultInitialTimeoutSecs));
+
+ // When we send a packet, the timeout will change to 5000 +
+ // kDefaultInitialTimeoutSecs.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+
+ // Send an ack so we don't set the retransimission alarm.
+ SendAckPacketToPeer();
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+
+ // The original alarm will fire. We should not time out because we had a
+ // network event at t=5000. The alarm will reregister.
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(
+ kDefaultInitialTimeoutSecs * 1000000 - 5000));
+ EXPECT_EQ(default_timeout, clock_.ApproximateNow());
+ EXPECT_FALSE(connection_.CheckForTimeout());
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_EQ(default_timeout.Add(QuicTime::Delta::FromMilliseconds(5)),
+ connection_.GetTimeoutAlarm()->deadline());
+
+ // This time, we should time out.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, false));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+ EXPECT_EQ(default_timeout.Add(QuicTime::Delta::FromMilliseconds(5)),
+ clock_.ApproximateNow());
+ EXPECT_TRUE(connection_.CheckForTimeout());
+ EXPECT_FALSE(connection_.connected());
+}
+
+// TODO(ianswett): Add scheduler tests when should_retransmit is false.
+TEST_F(QuicConnectionTest, SendScheduler) {
+ // Test that if we send a packet without delay, it is not queued.
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelay) {
+ // Test that if we send a packet with a delay, it ends up queued.
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0);
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerForce) {
+ // Test that if we force send a packet, it is not queued.
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, IS_RETRANSMISSION, _, _)).Times(0);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ // XXX: fixme. was: connection_.SendOrQueuePacket(1, packet, kForce);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerEAGAIN) {
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ helper_->set_blocked(true);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0);
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayThenSend) {
+ // Test that if we send a packet with a delay, it ends up queued.
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Advance the clock to fire the alarm, and configure the scheduler
+ // to permit the packet to be sent.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(1));
+ connection_.GetSendAlarm()->Cancel();
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _));
+ EXPECT_CALL(visitor_, OnCanWrite());
+ connection_.OnCanWrite();
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayThenRetransmit) {
+ EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _))
+ .WillRepeatedly(testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ // Advance the time for retransmission of lost packet.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(501));
+ // Test that if we send a retransmit with a delay, it ends up queued.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, IS_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ connection_.OnRetransmissionTimeout();
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Advance the clock to fire the alarm, and configure the scheduler
+ // to permit the packet to be sent.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, IS_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::Zero()));
+
+ // Ensure the scheduler is notified this is a retransmit.
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, IS_RETRANSMISSION));
+ clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(1));
+ connection_.GetSendAlarm()->Cancel();
+ EXPECT_CALL(visitor_, OnCanWrite());
+ connection_.OnCanWrite();
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayAndQueue) {
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Attempt to send another packet and make sure that it gets queued.
+ packet = ConstructDataPacket(2, 0, !kEntropyFlag);
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 2, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(2u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndSend) {
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(10)));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Now send non-retransmitting information, that we're not going to
+ // retransmit 3. The far end should stop waiting for it.
+ QuicAckFrame frame(0, QuicTime::Zero(), 1);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, _));
+ ProcessAckPacket(&frame, true);
+
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ // Ensure alarm is not set
+ EXPECT_FALSE(connection_.GetSendAlarm()->IsSet());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayThenAckAndHold) {
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(10)));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Now send non-retransmitting information, that we're not going to
+ // retransmit 3. The far end should stop waiting for it.
+ QuicAckFrame frame(0, QuicTime::Zero(), 1);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ ProcessAckPacket(&frame, false);
+
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, SendSchedulerDelayThenOnCanWrite) {
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(10)));
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // OnCanWrite should not send the packet (because of the delay)
+ // but should still return true.
+ EXPECT_TRUE(connection_.OnCanWrite());
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, TestQueueLimitsOnSendStreamData) {
+ // All packets carry version info till version is negotiated.
+ size_t payload_length;
+ connection_.options()->max_packet_length =
+ GetPacketLengthForOneStream(connection_.version(), kIncludeVersion,
+ NOT_IN_FEC_GROUP, &payload_length);
+
+ // Queue the first packet.
+ EXPECT_CALL(*send_algorithm_,
+ TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(10)));
+ const string payload(payload_length, 'a');
+ EXPECT_EQ(0u,
+ connection_.SendStreamData(1, payload, 0, !kFin).bytes_consumed);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+TEST_F(QuicConnectionTest, LoopThroughSendingPackets) {
+ // All packets carry version info till version is negotiated.
+ size_t payload_length;
+ connection_.options()->max_packet_length =
+ GetPacketLengthForOneStream(connection_.version(), kIncludeVersion,
+ NOT_IN_FEC_GROUP, &payload_length);
+
+ // Queue the first packet.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(7);
+ // The first stream frame will consume 2 fewer bytes than the other six.
+ const string payload(payload_length * 7 - 12, 'a');
+ EXPECT_EQ(payload.size(),
+ connection_.SendStreamData(1, payload, 0, !kFin).bytes_consumed);
+}
+
+TEST_F(QuicConnectionTest, NoAckForClose) {
+ ProcessPacket(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(0);
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_PEER_GOING_AWAY, true));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, _, _, _)).Times(0);
+ ProcessClosePacket(2, 0);
+}
+
+TEST_F(QuicConnectionTest, SendWhenDisconnected) {
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_PEER_GOING_AWAY, false));
+ connection_.CloseConnection(QUIC_PEER_GOING_AWAY, false);
+ EXPECT_FALSE(connection_.connected());
+ QuicPacket* packet = ConstructDataPacket(1, 0, !kEntropyFlag);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, _)).Times(0);
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, 1, packet, kTestEntropyHash, HAS_RETRANSMITTABLE_DATA);
+}
+
+TEST_F(QuicConnectionTest, PublicReset) {
+ QuicPublicResetPacket header;
+ header.public_header.guid = guid_;
+ header.public_header.reset_flag = true;
+ header.public_header.version_flag = false;
+ header.rejected_sequence_number = 10101;
+ scoped_ptr<QuicEncryptedPacket> packet(
+ framer_.BuildPublicResetPacket(header));
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_PUBLIC_RESET, true));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *packet);
+}
+
+TEST_F(QuicConnectionTest, GoAway) {
+ QuicGoAwayFrame goaway;
+ goaway.last_good_stream_id = 1;
+ goaway.error_code = QUIC_PEER_GOING_AWAY;
+ goaway.reason_phrase = "Going away.";
+ EXPECT_CALL(visitor_, OnGoAway(_));
+ ProcessGoAwayPacket(&goaway);
+}
+
+TEST_F(QuicConnectionTest, MissingPacketsBeforeLeastUnacked) {
+ QuicAckFrame ack(0, QuicTime::Zero(), 4);
+ // Set the sequence number of the ack packet to be least unacked (4)
+ creator_.set_sequence_number(3);
+ ProcessAckPacket(&ack, true);
+ EXPECT_TRUE(outgoing_ack()->received_info.missing_packets.empty());
+}
+
+TEST_F(QuicConnectionTest, ReceivedEntropyHashCalculation) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true));
+ ProcessDataPacket(1, 1, kEntropyFlag);
+ ProcessDataPacket(4, 1, kEntropyFlag);
+ ProcessDataPacket(3, 1, !kEntropyFlag);
+ ProcessDataPacket(7, 1, kEntropyFlag);
+ EXPECT_EQ(146u, outgoing_ack()->received_info.entropy_hash);
+}
+
+TEST_F(QuicConnectionTest, UpdateEntropyForReceivedPackets) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true));
+ ProcessDataPacket(1, 1, kEntropyFlag);
+ ProcessDataPacket(5, 1, kEntropyFlag);
+ ProcessDataPacket(4, 1, !kEntropyFlag);
+ EXPECT_EQ(34u, outgoing_ack()->received_info.entropy_hash);
+ // Make 4th packet my least unacked, and update entropy for 2, 3 packets.
+ QuicAckFrame ack(0, QuicTime::Zero(), 4);
+ QuicPacketEntropyHash kRandomEntropyHash = 129u;
+ ack.sent_info.entropy_hash = kRandomEntropyHash;
+ creator_.set_sequence_number(5);
+ QuicPacketEntropyHash six_packet_entropy_hash = 0;
+ if (ProcessAckPacket(&ack, true)) {
+ six_packet_entropy_hash = 1 << 6;
+ };
+
+ EXPECT_EQ((kRandomEntropyHash + (1 << 5) + six_packet_entropy_hash),
+ outgoing_ack()->received_info.entropy_hash);
+}
+
+TEST_F(QuicConnectionTest, UpdateEntropyHashUptoCurrentPacket) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true));
+ ProcessDataPacket(1, 1, kEntropyFlag);
+ ProcessDataPacket(5, 1, !kEntropyFlag);
+ ProcessDataPacket(22, 1, kEntropyFlag);
+ EXPECT_EQ(66u, outgoing_ack()->received_info.entropy_hash);
+ creator_.set_sequence_number(22);
+ QuicPacketEntropyHash kRandomEntropyHash = 85u;
+ // Current packet is the least unacked packet.
+ QuicAckFrame ack(0, QuicTime::Zero(), 23);
+ ack.sent_info.entropy_hash = kRandomEntropyHash;
+ QuicPacketEntropyHash ack_entropy_hash = ProcessAckPacket(&ack, true);
+ EXPECT_EQ((kRandomEntropyHash + ack_entropy_hash),
+ outgoing_ack()->received_info.entropy_hash);
+ ProcessDataPacket(25, 1, kEntropyFlag);
+ EXPECT_EQ((kRandomEntropyHash + ack_entropy_hash + (1 << (25 % 8))),
+ outgoing_ack()->received_info.entropy_hash);
+}
+
+TEST_F(QuicConnectionTest, EntropyCalculationForTruncatedAck) {
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).WillRepeatedly(Return(true));
+ QuicPacketEntropyHash entropy[51];
+ entropy[0] = 0;
+ for (int i = 1; i < 51; ++i) {
+ bool should_send = i % 10 != 0;
+ bool entropy_flag = (i & (i - 1)) != 0;
+ if (!should_send) {
+ entropy[i] = entropy[i - 1];
+ continue;
+ }
+ if (entropy_flag) {
+ entropy[i] = entropy[i - 1] ^ (1 << (i % 8));
+ } else {
+ entropy[i] = entropy[i - 1];
+ }
+ ProcessDataPacket(i, 1, entropy_flag);
+ }
+ // Till 50 since 50th packet is not sent.
+ for (int i = 1; i < 50; ++i) {
+ EXPECT_EQ(entropy[i], QuicConnectionPeer::ReceivedEntropyHash(
+ &connection_, i));
+ }
+}
+
+TEST_F(QuicConnectionTest, CheckSentEntropyHash) {
+ creator_.set_sequence_number(1);
+ SequenceNumberSet missing_packets;
+ QuicPacketEntropyHash entropy_hash = 0;
+ QuicPacketSequenceNumber max_sequence_number = 51;
+ for (QuicPacketSequenceNumber i = 1; i <= max_sequence_number; ++i) {
+ bool is_missing = i % 10 != 0;
+ bool entropy_flag = (i & (i - 1)) != 0;
+ QuicPacketEntropyHash packet_entropy_hash = 0;
+ if (entropy_flag) {
+ packet_entropy_hash = 1 << (i % 8);
+ }
+ QuicPacket* packet = ConstructDataPacket(i, 0, entropy_flag);
+ connection_.SendOrQueuePacket(
+ ENCRYPTION_NONE, i, packet, packet_entropy_hash,
+ HAS_RETRANSMITTABLE_DATA);
+
+ if (is_missing) {
+ missing_packets.insert(i);
+ continue;
+ }
+
+ entropy_hash ^= packet_entropy_hash;
+ }
+ EXPECT_TRUE(QuicConnectionPeer::IsValidEntropy(
+ &connection_, max_sequence_number, missing_packets, entropy_hash))
+ << "";
+}
+
+TEST_F(QuicConnectionTest, ServerSendsVersionNegotiationPacket) {
+ framer_.set_version_for_tests(QUIC_VERSION_UNSUPPORTED);
+
+ QuicPacketHeader header;
+ header.public_header.guid = guid_;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = true;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.packet_sequence_number = 12;
+ header.fec_group = 0;
+
+ QuicFrames frames;
+ QuicFrame frame(&frame1_);
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+
+ framer_.set_version(QuicVersionMax());
+ connection_.set_is_server(true);
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+ EXPECT_TRUE(helper_->version_negotiation_packet() != NULL);
+
+ size_t num_versions = arraysize(kSupportedQuicVersions);
+ EXPECT_EQ(num_versions,
+ helper_->version_negotiation_packet()->versions.size());
+
+ // We expect all versions in kSupportedQuicVersions to be
+ // included in the packet.
+ for (size_t i = 0; i < num_versions; ++i) {
+ EXPECT_EQ(kSupportedQuicVersions[i],
+ helper_->version_negotiation_packet()->versions[i]);
+ }
+}
+
+TEST_F(QuicConnectionTest, ClientHandlesVersionNegotiation) {
+ // Start out with some unsupported version.
+ QuicConnectionPeer::GetFramer(&connection_)->set_version_for_tests(
+ QUIC_VERSION_UNSUPPORTED);
+
+ QuicPacketHeader header;
+ header.public_header.guid = guid_;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = true;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.packet_sequence_number = 12;
+ header.fec_group = 0;
+
+ QuicVersionVector supported_versions;
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ supported_versions.push_back(kSupportedQuicVersions[i]);
+ }
+
+ // Send a version negotiation packet.
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.BuildVersionNegotiationPacket(
+ header.public_header, supported_versions));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+
+ // Now force another packet. The connection should transition into
+ // NEGOTIATED_VERSION state and tell the packet creator to StopSendingVersion.
+ header.public_header.version_flag = false;
+ QuicFrames frames;
+ QuicFrame frame(&frame1_);
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ encrypted.reset(framer_.EncryptPacket(ENCRYPTION_NONE, 12, *packet));
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(1);
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+
+ ASSERT_FALSE(QuicPacketCreatorPeer::SendVersionInPacket(
+ QuicConnectionPeer::GetPacketCreator(&connection_)));
+}
+
+TEST_F(QuicConnectionTest, BadVersionNegotiation) {
+ QuicPacketHeader header;
+ header.public_header.guid = guid_;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = true;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.packet_sequence_number = 12;
+ header.fec_group = 0;
+
+ QuicVersionVector supported_versions;
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ supported_versions.push_back(kSupportedQuicVersions[i]);
+ }
+
+ // Send a version negotiation packet with the version the client started with.
+ // It should be rejected.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_VERSION_NEGOTIATION_PACKET,
+ false));
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.BuildVersionNegotiationPacket(
+ header.public_header, supported_versions));
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+}
+
+TEST_F(QuicConnectionTest, CheckSendStats) {
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(_, _)).Times(3);
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, NOT_RETRANSMISSION));
+ connection_.SendStreamData(1u, "first", 0, !kFin);
+ size_t first_packet_size = last_sent_packet_size();
+
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, NOT_RETRANSMISSION));
+ connection_.SendStreamData(1u, "second", 0, !kFin);
+ size_t second_packet_size = last_sent_packet_size();
+
+ // 2 retransmissions due to rto, 1 due to explicit nack.
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, _, _, IS_RETRANSMISSION)).Times(3);
+
+ // Retransmit due to RTO.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10));
+ connection_.OnRetransmissionTimeout();
+
+ // Retransmit due to explicit nacks
+ QuicAckFrame nack_three(4, QuicTime::Zero(), 0);
+ nack_three.received_info.missing_packets.insert(3);
+ nack_three.received_info.entropy_hash =
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 4) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 3) ^
+ QuicConnectionPeer::GetSentEntropyHash(&connection_, 2);
+ QuicFrame frame(&nack_three);
+ EXPECT_CALL(visitor_, OnAck(_));
+ EXPECT_CALL(*send_algorithm_, OnIncomingAck(_, _, _)).Times(1);
+ EXPECT_CALL(*send_algorithm_, OnIncomingLoss(_)).Times(1);
+ EXPECT_CALL(visitor_, OnCanWrite()).Times(3).WillRepeatedly(Return(true));
+
+ ProcessFramePacket(frame);
+ ProcessFramePacket(frame);
+ ProcessFramePacket(frame);
+
+ EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillOnce(
+ Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillOnce(
+ Return(QuicBandwidth::Zero()));
+
+ const QuicConnectionStats& stats = connection_.GetStats();
+ EXPECT_EQ(3 * first_packet_size + 2 * second_packet_size - kQuicVersionSize,
+ stats.bytes_sent);
+ EXPECT_EQ(5u, stats.packets_sent);
+ EXPECT_EQ(2 * first_packet_size + second_packet_size - kQuicVersionSize,
+ stats.bytes_retransmitted);
+ EXPECT_EQ(3u, stats.packets_retransmitted);
+ EXPECT_EQ(2u, stats.rto_count);
+}
+
+TEST_F(QuicConnectionTest, CheckReceiveStats) {
+ size_t received_bytes = 0;
+ received_bytes += ProcessFecProtectedPacket(1, false, !kEntropyFlag);
+ received_bytes += ProcessFecProtectedPacket(3, false, !kEntropyFlag);
+ // Should be counted against dropped packets.
+ received_bytes += ProcessDataPacket(3, 1, !kEntropyFlag);
+ received_bytes += ProcessFecPacket(4, 1, true, !kEntropyFlag); // Fec packet
+
+ EXPECT_CALL(*send_algorithm_, SmoothedRtt()).WillOnce(
+ Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, BandwidthEstimate()).WillOnce(
+ Return(QuicBandwidth::Zero()));
+
+ const QuicConnectionStats& stats = connection_.GetStats();
+ EXPECT_EQ(received_bytes, stats.bytes_received);
+ EXPECT_EQ(4u, stats.packets_received);
+
+ EXPECT_EQ(1u, stats.packets_revived);
+ EXPECT_EQ(1u, stats.packets_dropped);
+}
+
+TEST_F(QuicConnectionTest, TestFecGroupLimits) {
+ // Create and return a group for 1
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 1) != NULL);
+
+ // Create and return a group for 2
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) != NULL);
+
+ // Create and return a group for 4. This should remove 1 but not 2.
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 4) != NULL);
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 1) == NULL);
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) != NULL);
+
+ // Create and return a group for 3. This will kill off 2.
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 3) != NULL);
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 2) == NULL);
+
+ // Verify that adding 5 kills off 3, despite 4 being created before 3.
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 5) != NULL);
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 4) != NULL);
+ ASSERT_TRUE(QuicConnectionPeer::GetFecGroup(&connection_, 3) == NULL);
+}
+
+TEST_F(QuicConnectionTest, DontProcessFramesIfPacketClosedConnection) {
+ // Construct a packet with stream frame and connection close frame.
+ header_.public_header.guid = guid_;
+ header_.packet_sequence_number = 1;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = false;
+ header_.entropy_flag = false;
+ header_.fec_flag = false;
+ header_.fec_group = 0;
+
+ QuicConnectionCloseFrame qccf;
+ qccf.error_code = QUIC_PEER_GOING_AWAY;
+ qccf.ack_frame = QuicAckFrame(0, QuicTime::Zero(), 1);
+ QuicFrame close_frame(&qccf);
+ QuicFrame stream_frame(&frame1_);
+
+ QuicFrames frames;
+ frames.push_back(stream_frame);
+ frames.push_back(close_frame);
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header_, frames).packet);
+ EXPECT_TRUE(NULL != packet.get());
+ scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+ ENCRYPTION_NONE, 1, *packet));
+
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_PEER_GOING_AWAY, true));
+ EXPECT_CALL(visitor_, OnPacket(_, _, _, _)).Times(0);
+
+ connection_.ProcessUdpPacket(IPEndPoint(), IPEndPoint(), *encrypted);
+}
+
+TEST_F(QuicConnectionTest, SelectMutualVersion) {
+ // Set the connection to speak the lowest quic version.
+ connection_.set_version(QuicVersionMin());
+ EXPECT_EQ(QuicVersionMin(), connection_.version());
+
+ // Pass in available versions which includes a higher mutually supported
+ // version. The higher mutually supported version should be selected.
+ QuicVersionVector supported_versions;
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ supported_versions.push_back(kSupportedQuicVersions[i]);
+ }
+ EXPECT_TRUE(connection_.SelectMutualVersion(supported_versions));
+ EXPECT_EQ(QuicVersionMax(), connection_.version());
+
+ // Expect that the lowest version is selected.
+ // Ensure the lowest supported version is less than the max, unless they're
+ // the same.
+ EXPECT_LE(QuicVersionMin(), QuicVersionMax());
+ QuicVersionVector lowest_version_vector;
+ lowest_version_vector.push_back(QuicVersionMin());
+ EXPECT_TRUE(connection_.SelectMutualVersion(lowest_version_vector));
+ EXPECT_EQ(QuicVersionMin(), connection_.version());
+
+ // Shouldn't be able to find a mutually supported version.
+ QuicVersionVector unsupported_version;
+ unsupported_version.push_back(QUIC_VERSION_UNSUPPORTED);
+ EXPECT_FALSE(connection_.SelectMutualVersion(unsupported_version));
+}
+
+TEST_F(QuicConnectionTest, ConnectionCloseWhenNotWriteBlocked) {
+ helper_->set_blocked(false); // Already default.
+
+ // Send a packet (but write will not block).
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ EXPECT_EQ(1u, helper_->packets_write_attempts());
+
+ // Send an erroneous packet to close the connection.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_PACKET_HEADER, false));
+ ProcessDataPacket(6000, 0, !kEntropyFlag);
+ EXPECT_EQ(2u, helper_->packets_write_attempts());
+}
+
+TEST_F(QuicConnectionTest, ConnectionCloseWhenWriteBlocked) {
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+ helper_->set_blocked(true);
+
+ // Send a packet to so that write will really block.
+ connection_.SendStreamData(1, "foo", 0, !kFin);
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+ EXPECT_EQ(1u, helper_->packets_write_attempts());
+
+ // Send an erroneous packet to close the connection.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_PACKET_HEADER, false));
+ ProcessDataPacket(6000, 0, !kEntropyFlag);
+ EXPECT_EQ(1u, helper_->packets_write_attempts());
+}
+
+TEST_F(QuicConnectionTest, ConnectionCloseWhenNothingPending) {
+ helper_->set_blocked(true);
+
+ // Send an erroneous packet to close the connection.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_INVALID_PACKET_HEADER, false));
+ ProcessDataPacket(6000, 0, !kEntropyFlag);
+ EXPECT_EQ(1u, helper_->packets_write_attempts());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_client_stream.cc b/chromium/net/quic/quic_crypto_client_stream.cc
new file mode 100644
index 00000000000..585b293560b
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_client_stream.cc
@@ -0,0 +1,375 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_client_stream.h"
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+
+namespace {
+
+// Copies CertVerifyResult from |verify_details| to |cert_verify_result|.
+void CopyCertVerifyResult(
+ const ProofVerifyDetails* verify_details,
+ scoped_ptr<CertVerifyResult>* cert_verify_result) {
+ const CertVerifyResult* cert_verify_result_other =
+ &(reinterpret_cast<const ProofVerifyDetailsChromium*>(
+ verify_details))->cert_verify_result;
+ CertVerifyResult* result_copy = new CertVerifyResult;
+ result_copy->CopyFrom(*cert_verify_result_other);
+ cert_verify_result->reset(result_copy);
+}
+
+} // namespace
+
+QuicCryptoClientStream::ProofVerifierCallbackImpl::ProofVerifierCallbackImpl(
+ QuicCryptoClientStream* stream)
+ : stream_(stream) {}
+
+QuicCryptoClientStream::ProofVerifierCallbackImpl::
+ ~ProofVerifierCallbackImpl() {}
+
+void QuicCryptoClientStream::ProofVerifierCallbackImpl::Run(
+ bool ok,
+ const string& error_details,
+ scoped_ptr<ProofVerifyDetails>* details) {
+ if (stream_ == NULL) {
+ return;
+ }
+
+ stream_->verify_ok_ = ok;
+ stream_->verify_error_details_ = error_details;
+ stream_->verify_details_.reset(details->release());
+ stream_->proof_verify_callback_ = NULL;
+ stream_->DoHandshakeLoop(NULL);
+
+ // The ProofVerifier owns this object and will delete it when this method
+ // returns.
+}
+
+void QuicCryptoClientStream::ProofVerifierCallbackImpl::Cancel() {
+ stream_ = NULL;
+}
+
+
+QuicCryptoClientStream::QuicCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config)
+ : QuicCryptoStream(session),
+ next_state_(STATE_IDLE),
+ num_client_hellos_(0),
+ crypto_config_(crypto_config),
+ server_hostname_(server_hostname),
+ generation_counter_(0),
+ proof_verify_callback_(NULL) {
+}
+
+QuicCryptoClientStream::~QuicCryptoClientStream() {
+ if (proof_verify_callback_) {
+ proof_verify_callback_->Cancel();
+ }
+}
+
+void QuicCryptoClientStream::OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ QuicCryptoStream::OnHandshakeMessage(message);
+
+ DoHandshakeLoop(&message);
+}
+
+bool QuicCryptoClientStream::CryptoConnect() {
+ next_state_ = STATE_SEND_CHLO;
+ DoHandshakeLoop(NULL);
+ return true;
+}
+
+int QuicCryptoClientStream::num_sent_client_hellos() const {
+ return num_client_hellos_;
+}
+
+// TODO(rtenneti): Add unittests for GetSSLInfo which exercise the various ways
+// we learn about SSL info (sync vs async vs cached).
+bool QuicCryptoClientStream::GetSSLInfo(SSLInfo* ssl_info) {
+ ssl_info->Reset();
+ if (!cert_verify_result_) {
+ return false;
+ }
+
+ ssl_info->cert_status = cert_verify_result_->cert_status;
+ ssl_info->cert = cert_verify_result_->verified_cert;
+
+ // TODO(rtenneti): Figure out what to set for the following.
+ // Temporarily hard coded cipher_suite as 0xc031 to represent
+ // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (from
+ // net/ssl/ssl_cipher_suite_names.cc) and encryption as 256.
+ int cipher_suite = 0xc02f;
+ int ssl_connection_status = 0;
+ ssl_connection_status |=
+ (cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) <<
+ SSL_CONNECTION_CIPHERSUITE_SHIFT;
+ ssl_connection_status |=
+ (SSL_CONNECTION_VERSION_TLS1_2 & SSL_CONNECTION_VERSION_MASK) <<
+ SSL_CONNECTION_VERSION_SHIFT;
+
+ ssl_info->public_key_hashes = cert_verify_result_->public_key_hashes;
+ ssl_info->is_issued_by_known_root =
+ cert_verify_result_->is_issued_by_known_root;
+
+ ssl_info->connection_status = ssl_connection_status;
+ ssl_info->client_cert_sent = false;
+ ssl_info->channel_id_sent = false;
+ ssl_info->security_bits = 256;
+ ssl_info->handshake_type = SSLInfo::HANDSHAKE_FULL;
+ return true;
+}
+
+// kMaxClientHellos is the maximum number of times that we'll send a client
+// hello. The value 3 accounts for:
+// * One failure due to an incorrect or missing source-address token.
+// * One failure due the server's certificate chain being unavailible and the
+// server being unwilling to send it without a valid source-address token.
+static const int kMaxClientHellos = 3;
+
+void QuicCryptoClientStream::DoHandshakeLoop(
+ const CryptoHandshakeMessage* in) {
+ CryptoHandshakeMessage out;
+ QuicErrorCode error;
+ string error_details;
+ QuicCryptoClientConfig::CachedState* cached =
+ crypto_config_->LookupOrCreate(server_hostname_);
+
+ if (in != NULL) {
+ DVLOG(1) << "Client received: " << in->DebugString();
+ }
+
+ for (;;) {
+ const State state = next_state_;
+ next_state_ = STATE_IDLE;
+ switch (state) {
+ case STATE_SEND_CHLO: {
+ // Send the client hello in plaintext.
+ session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_NONE);
+ if (num_client_hellos_ > kMaxClientHellos) {
+ CloseConnection(QUIC_CRYPTO_TOO_MANY_REJECTS);
+ return;
+ }
+ num_client_hellos_++;
+
+ if (!cached->IsComplete(session()->connection()->clock()->WallNow())) {
+ crypto_config_->FillInchoateClientHello(
+ server_hostname_, cached, &crypto_negotiated_params_, &out);
+ next_state_ = STATE_RECV_REJ;
+ DVLOG(1) << "Client Sending: " << out.DebugString();
+ SendHandshakeMessage(out);
+ return;
+ }
+ session()->config()->ToHandshakeMessage(&out);
+ error = crypto_config_->FillClientHello(
+ server_hostname_,
+ session()->connection()->guid(),
+ cached,
+ session()->connection()->clock()->WallNow(),
+ session()->connection()->random_generator(),
+ &crypto_negotiated_params_,
+ &out,
+ &error_details);
+ if (error != QUIC_NO_ERROR) {
+ // Flush the cached config so that, if it's bad, the server has a
+ // chance to send us another in the future.
+ cached->InvalidateServerConfig();
+ CloseConnectionWithDetails(error, error_details);
+ return;
+ }
+ if (cached->proof_verify_details()) {
+ CopyCertVerifyResult(cached->proof_verify_details(),
+ &cert_verify_result_);
+ } else {
+ cert_verify_result_.reset();
+ }
+ next_state_ = STATE_RECV_SHLO;
+ DVLOG(1) << "Client Sending: " << out.DebugString();
+ SendHandshakeMessage(out);
+ // Be prepared to decrypt with the new server write key.
+ session()->connection()->SetAlternativeDecrypter(
+ crypto_negotiated_params_.initial_crypters.decrypter.release(),
+ true /* latch once used */);
+ // Send subsequent packets under encryption on the assumption that the
+ // server will accept the handshake.
+ session()->connection()->SetEncrypter(
+ ENCRYPTION_INITIAL,
+ crypto_negotiated_params_.initial_crypters.encrypter.release());
+ session()->connection()->SetDefaultEncryptionLevel(
+ ENCRYPTION_INITIAL);
+ if (!encryption_established_) {
+ encryption_established_ = true;
+ session()->OnCryptoHandshakeEvent(
+ QuicSession::ENCRYPTION_FIRST_ESTABLISHED);
+ } else {
+ session()->OnCryptoHandshakeEvent(
+ QuicSession::ENCRYPTION_REESTABLISHED);
+ }
+ return;
+ }
+ case STATE_RECV_REJ:
+ // We sent a dummy CHLO because we didn't have enough information to
+ // perform a handshake, or we sent a full hello that the server
+ // rejected. Here we hope to have a REJ that contains the information
+ // that we need.
+ if (in->tag() != kREJ) {
+ CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
+ "Expected REJ");
+ return;
+ }
+ error = crypto_config_->ProcessRejection(
+ cached, *in, session()->connection()->clock()->WallNow(),
+ &crypto_negotiated_params_, &error_details);
+ if (error != QUIC_NO_ERROR) {
+ CloseConnectionWithDetails(error, error_details);
+ return;
+ }
+ if (!cached->proof_valid()) {
+ ProofVerifier* verifier = crypto_config_->proof_verifier();
+ if (!verifier) {
+ // If no verifier is set then we don't check the certificates.
+ cached->SetProofValid();
+ } else if (!cached->signature().empty()) {
+ next_state_ = STATE_VERIFY_PROOF;
+ break;
+ }
+ }
+ next_state_ = STATE_SEND_CHLO;
+ break;
+ case STATE_VERIFY_PROOF: {
+ ProofVerifier* verifier = crypto_config_->proof_verifier();
+ DCHECK(verifier);
+ next_state_ = STATE_VERIFY_PROOF_COMPLETE;
+ generation_counter_ = cached->generation_counter();
+
+ ProofVerifierCallbackImpl* proof_verify_callback =
+ new ProofVerifierCallbackImpl(this);
+
+ verify_ok_ = false;
+
+ ProofVerifier::Status status = verifier->VerifyProof(
+ session()->connection()->version(),
+ server_hostname_,
+ cached->server_config(),
+ cached->certs(),
+ cached->signature(),
+ &verify_error_details_,
+ &verify_details_,
+ proof_verify_callback);
+
+ switch (status) {
+ case ProofVerifier::PENDING:
+ proof_verify_callback_ = proof_verify_callback;
+ DVLOG(1) << "Doing VerifyProof";
+ return;
+ case ProofVerifier::FAILURE:
+ break;
+ case ProofVerifier::SUCCESS:
+ verify_ok_ = true;
+ break;
+ }
+ break;
+ }
+ case STATE_VERIFY_PROOF_COMPLETE:
+ if (!verify_ok_) {
+ CopyCertVerifyResult(verify_details_.get(), &cert_verify_result_);
+ CloseConnectionWithDetails(
+ QUIC_PROOF_INVALID, "Proof invalid: " + verify_error_details_);
+ return;
+ }
+ // Check if generation_counter has changed between STATE_VERIFY_PROOF
+ // and STATE_VERIFY_PROOF_COMPLETE state changes.
+ if (generation_counter_ != cached->generation_counter()) {
+ next_state_ = STATE_VERIFY_PROOF;
+ } else {
+ cached->SetProofValid();
+ cached->SetProofVerifyDetails(verify_details_.release());
+ next_state_ = STATE_SEND_CHLO;
+ }
+ break;
+ case STATE_RECV_SHLO: {
+ // We sent a CHLO that we expected to be accepted and now we're hoping
+ // for a SHLO from the server to confirm that.
+ if (in->tag() == kREJ) {
+ // alternative_decrypter will be NULL if the original alternative
+ // decrypter latched and became the primary decrypter. That happens
+ // if we received a message encrypted with the INITIAL key.
+ if (session()->connection()->alternative_decrypter() == NULL) {
+ // The rejection was sent encrypted!
+ CloseConnectionWithDetails(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT,
+ "encrypted REJ message");
+ return;
+ }
+ next_state_ = STATE_RECV_REJ;
+ break;
+ }
+ if (in->tag() != kSHLO) {
+ CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
+ "Expected SHLO or REJ");
+ return;
+ }
+ // alternative_decrypter will be NULL if the original alternative
+ // decrypter latched and became the primary decrypter. That happens
+ // if we received a message encrypted with the INITIAL key.
+ if (session()->connection()->alternative_decrypter() != NULL) {
+ // The server hello was sent without encryption.
+ CloseConnectionWithDetails(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT,
+ "unencrypted SHLO message");
+ return;
+ }
+ error = crypto_config_->ProcessServerHello(
+ *in, session()->connection()->guid(), &crypto_negotiated_params_,
+ &error_details);
+ if (error != QUIC_NO_ERROR) {
+ CloseConnectionWithDetails(
+ error, "Server hello invalid: " + error_details);
+ return;
+ }
+ error = session()->config()->ProcessServerHello(*in, &error_details);
+ if (error != QUIC_NO_ERROR) {
+ CloseConnectionWithDetails(
+ error, "Server hello invalid: " + error_details);
+ return;
+ }
+ CrypterPair* crypters =
+ &crypto_negotiated_params_.forward_secure_crypters;
+ // TODO(agl): we don't currently latch this decrypter because the idea
+ // has been floated that the server shouldn't send packets encrypted
+ // with the FORWARD_SECURE key until it receives a FORWARD_SECURE
+ // packet from the client.
+ session()->connection()->SetAlternativeDecrypter(
+ crypters->decrypter.release(), false /* don't latch */);
+ session()->connection()->SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE, crypters->encrypter.release());
+ session()->connection()->SetDefaultEncryptionLevel(
+ ENCRYPTION_FORWARD_SECURE);
+
+ handshake_confirmed_ = true;
+ session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+ return;
+ }
+ case STATE_IDLE:
+ // This means that the peer sent us a message that we weren't expecting.
+ CloseConnection(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+ return;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_client_stream.h b/chromium/net/quic/quic_crypto_client_stream.h
new file mode 100644
index 00000000000..5a9042b925c
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_client_stream.h
@@ -0,0 +1,123 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
+
+#include <string>
+
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+
+namespace net {
+
+class ProofVerifyDetails;
+class QuicSession;
+class SSLInfo;
+
+namespace test {
+class CryptoTestUtils;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicCryptoClientStream : public QuicCryptoStream {
+ public:
+ QuicCryptoClientStream(const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config);
+ virtual ~QuicCryptoClientStream();
+
+ // CryptoFramerVisitorInterface implementation
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+
+ // Performs a crypto handshake with the server. Returns true if the crypto
+ // handshake is started successfully.
+ // TODO(agl): this should probably return void.
+ virtual bool CryptoConnect();
+
+ // num_sent_client_hellos returns the number of client hello messages that
+ // have been sent. If the handshake has completed then this is one greater
+ // than the number of round-trips needed for the handshake.
+ int num_sent_client_hellos() const;
+
+ // Gets the SSL connection information.
+ bool GetSSLInfo(SSLInfo* ssl_info);
+
+ private:
+ // ProofVerifierCallbackImpl is passed as the callback method to VerifyProof.
+ // The ProofVerifier calls this class with the result of proof verification
+ // when verification is performed asynchronously.
+ class ProofVerifierCallbackImpl : public ProofVerifierCallback {
+ public:
+ explicit ProofVerifierCallbackImpl(QuicCryptoClientStream* stream);
+ virtual ~ProofVerifierCallbackImpl();
+
+ // ProofVerifierCallback interface.
+ virtual void Run(bool ok,
+ const string& error_details,
+ scoped_ptr<ProofVerifyDetails>* details) OVERRIDE;
+
+ // Cancel causes any future callbacks to be ignored. It must be called on
+ // the same thread as the callback will be made on.
+ void Cancel();
+
+ private:
+ QuicCryptoClientStream* stream_;
+ };
+
+ friend class test::CryptoTestUtils;
+ friend class ProofVerifierCallbackImpl;
+
+ enum State {
+ STATE_IDLE,
+ STATE_SEND_CHLO,
+ STATE_RECV_REJ,
+ STATE_VERIFY_PROOF,
+ STATE_VERIFY_PROOF_COMPLETE,
+ STATE_RECV_SHLO,
+ };
+
+ // DoHandshakeLoop performs a step of the handshake state machine. Note that
+ // |in| may be NULL if the call did not result from a received message
+ void DoHandshakeLoop(const CryptoHandshakeMessage* in);
+
+ State next_state_;
+ // num_client_hellos_ contains the number of client hello messages that this
+ // connection has sent.
+ int num_client_hellos_;
+
+ QuicCryptoClientConfig* const crypto_config_;
+
+ // Client's connection nonce (4-byte timestamp + 28 random bytes)
+ std::string nonce_;
+ // Server's hostname
+ std::string server_hostname_;
+
+ // Generation counter from QuicCryptoClientConfig's CachedState.
+ uint64 generation_counter_;
+
+ // proof_verify_callback_ contains the callback object that we passed to an
+ // asynchronous proof verification. The ProofVerifier owns this object.
+ ProofVerifierCallbackImpl* proof_verify_callback_;
+
+ // These members are used to store the result of an asynchronous proof
+ // verification. These members must not be used after
+ // STATE_VERIFY_PROOF_COMPLETE.
+ bool verify_ok_;
+ string verify_error_details_;
+ scoped_ptr<ProofVerifyDetails> verify_details_;
+
+ // The result of certificate verification.
+ scoped_ptr<CertVerifyResult> cert_verify_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicCryptoClientStream);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_H_
diff --git a/chromium/net/quic/quic_crypto_client_stream_factory.h b/chromium/net/quic/quic_crypto_client_stream_factory.h
new file mode 100644
index 00000000000..4fa5f573ea0
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_client_stream_factory.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 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_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
+#define NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class QuicCryptoClientStream;
+class QuicSession;
+
+// An interface used to instantiate QuicCryptoClientStream objects. Used to
+// facilitate testing code with mock implementations.
+class NET_EXPORT QuicCryptoClientStreamFactory {
+ public:
+ virtual ~QuicCryptoClientStreamFactory() {}
+
+ virtual QuicCryptoClientStream* CreateQuicCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config) = 0;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CRYPTO_CLIENT_STREAM_FACTORY_H_
diff --git a/chromium/net/quic/quic_crypto_client_stream_test.cc b/chromium/net/quic/quic_crypto_client_stream_test.cc
new file mode 100644
index 00000000000..9f9e7f75c57
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_client_stream_test.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_client_stream.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "example.com";
+
+class TestQuicVisitor : public NoOpFramerVisitor {
+ public:
+ TestQuicVisitor()
+ : frame_valid_(false) {
+ }
+
+ // NoOpFramerVisitor
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+ frame_ = frame;
+ frame_valid_ = true;
+ return true;
+ }
+
+ bool frame_valid() const {
+ return frame_valid_;
+ }
+ QuicStreamFrame* frame() { return &frame_; }
+
+ private:
+ QuicStreamFrame frame_;
+ bool frame_valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestQuicVisitor);
+};
+
+class QuicCryptoClientStreamTest : public ::testing::Test {
+ public:
+ QuicCryptoClientStreamTest()
+ : addr_(),
+ connection_(new PacketSavingConnection(1, addr_, true)),
+ session_(new TestSession(connection_, DefaultQuicConfig(), true)),
+ stream_(new QuicCryptoClientStream(kServerHostname, session_.get(),
+ &crypto_config_)) {
+ session_->SetCryptoStream(stream_.get());
+ crypto_config_.SetDefaults();
+ }
+
+ void CompleteCryptoHandshake() {
+ EXPECT_TRUE(stream_->CryptoConnect());
+ CryptoTestUtils::HandshakeWithFakeServer(connection_, stream_.get());
+ }
+
+ void ConstructHandshakeMessage() {
+ CryptoFramer framer;
+ message_data_.reset(framer.ConstructHandshakeMessage(message_));
+ }
+
+ IPEndPoint addr_;
+ PacketSavingConnection* connection_;
+ scoped_ptr<TestSession> session_;
+ scoped_ptr<QuicCryptoClientStream> stream_;
+ CryptoHandshakeMessage message_;
+ scoped_ptr<QuicData> message_data_;
+ QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(QuicCryptoClientStreamTest, NotInitiallyConected) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ EXPECT_FALSE(stream_->encryption_established());
+ EXPECT_FALSE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, ConnectedAfterSHLO) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+ EXPECT_TRUE(stream_->encryption_established());
+ EXPECT_TRUE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, MessageAfterHandshake) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ EXPECT_CALL(*connection_, SendConnectionClose(
+ QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE));
+ message_.set_tag(kCHLO);
+ ConstructHandshakeMessage();
+ stream_->ProcessData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoClientStreamTest, BadMessageType) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ EXPECT_TRUE(stream_->CryptoConnect());
+
+ message_.set_tag(kCHLO);
+ ConstructHandshakeMessage();
+
+ EXPECT_CALL(*connection_, SendConnectionCloseWithDetails(
+ QUIC_INVALID_CRYPTO_MESSAGE_TYPE, "Expected REJ"));
+ stream_->ProcessData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoClientStreamTest, NegotiatedParameters) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ const QuicConfig* config = session_->config();
+ EXPECT_EQ(kQBIC, config->congestion_control());
+ EXPECT_EQ(kDefaultTimeoutSecs,
+ config->idle_connection_state_lifetime().ToSeconds());
+ EXPECT_EQ(kDefaultMaxStreamsPerConnection,
+ config->max_streams_per_connection());
+ EXPECT_EQ(0, config->keepalive_timeout().ToSeconds());
+
+ const QuicCryptoNegotiatedParameters& crypto_params(
+ stream_->crypto_negotiated_params());
+ EXPECT_EQ(kAESG, crypto_params.aead);
+ EXPECT_EQ(kC255, crypto_params.key_exchange);
+}
+
+TEST_F(QuicCryptoClientStreamTest, InvalidHostname) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ stream_.reset(new QuicCryptoClientStream("invalid", session_.get(),
+ &crypto_config_));
+ session_->SetCryptoStream(stream_.get());
+
+ CompleteCryptoHandshake();
+ EXPECT_TRUE(stream_->encryption_established());
+ EXPECT_TRUE(stream_->handshake_confirmed());
+}
+
+TEST_F(QuicCryptoClientStreamTest, ExpiredServerConfig) {
+ // Seed the config with a cached server config.
+ CompleteCryptoHandshake();
+
+ connection_ = new PacketSavingConnection(1, addr_, true);
+ session_.reset(new TestSession(connection_, QuicConfig(), true));
+ stream_.reset(new QuicCryptoClientStream(kServerHostname, session_.get(),
+ &crypto_config_));
+
+ session_->SetCryptoStream(stream_.get());
+ session_->config()->SetDefaults();
+
+ // Advance time 5 years to ensure that we pass the expiry time of the cached
+ // server config.
+ reinterpret_cast<MockClock*>(const_cast<QuicClock*>(connection_->clock()))
+ ->AdvanceTime(QuicTime::Delta::FromSeconds(60 * 60 * 24 * 365 * 5));
+
+ // Check that a client hello was sent and that CryptoConnect doesn't fail
+ // with an error.
+ EXPECT_TRUE(stream_->CryptoConnect());
+ ASSERT_EQ(1u, connection_->packets_.size());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_server_stream.cc b/chromium/net/quic/quic_crypto_server_stream.cc
new file mode 100644
index 00000000000..a23a34d5b49
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_server_stream.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_server_stream.h"
+
+#include "base/base64.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+QuicCryptoServerStream::QuicCryptoServerStream(
+ const QuicCryptoServerConfig& crypto_config,
+ QuicSession* session)
+ : QuicCryptoStream(session),
+ crypto_config_(crypto_config) {
+}
+
+QuicCryptoServerStream::~QuicCryptoServerStream() {
+}
+
+void QuicCryptoServerStream::OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ QuicCryptoStream::OnHandshakeMessage(message);
+
+ // Do not process handshake messages after the handshake is confirmed.
+ if (handshake_confirmed_) {
+ CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+ return;
+ }
+
+ if (message.tag() != kCHLO) {
+ CloseConnection(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+ return;
+ }
+
+ string error_details;
+ CryptoHandshakeMessage reply;
+
+ QuicErrorCode error = ProcessClientHello(message, &reply, &error_details);
+
+ if (error != QUIC_NO_ERROR) {
+ CloseConnectionWithDetails(error, error_details);
+ return;
+ }
+
+ if (reply.tag() != kSHLO) {
+ SendHandshakeMessage(reply);
+ return;
+ }
+
+ // If we are returning a SHLO then we accepted the handshake.
+ QuicConfig* config = session()->config();
+ error = config->ProcessClientHello(message, &error_details);
+ if (error != QUIC_NO_ERROR) {
+ CloseConnectionWithDetails(error, error_details);
+ return;
+ }
+
+ config->ToHandshakeMessage(&reply);
+
+ // Receiving a full CHLO implies the client is prepared to decrypt with
+ // the new server write key. We can start to encrypt with the new server
+ // write key.
+ //
+ // NOTE: the SHLO will be encrypted with the new server write key.
+ session()->connection()->SetEncrypter(
+ ENCRYPTION_INITIAL,
+ crypto_negotiated_params_.initial_crypters.encrypter.release());
+ session()->connection()->SetDefaultEncryptionLevel(
+ ENCRYPTION_INITIAL);
+ // Set the decrypter immediately so that we no longer accept unencrypted
+ // packets.
+ session()->connection()->SetDecrypter(
+ crypto_negotiated_params_.initial_crypters.decrypter.release());
+ SendHandshakeMessage(reply);
+
+ session()->connection()->SetEncrypter(
+ ENCRYPTION_FORWARD_SECURE,
+ crypto_negotiated_params_.forward_secure_crypters.encrypter.release());
+ session()->connection()->SetDefaultEncryptionLevel(
+ ENCRYPTION_FORWARD_SECURE);
+ session()->connection()->SetAlternativeDecrypter(
+ crypto_negotiated_params_.forward_secure_crypters.decrypter.release(),
+ false /* don't latch */);
+
+ encryption_established_ = true;
+ handshake_confirmed_ = true;
+ session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+}
+
+bool QuicCryptoServerStream::GetBase64SHA256ClientChannelID(
+ string* output) const {
+ if (!encryption_established_ ||
+ crypto_negotiated_params_.channel_id.empty()) {
+ return false;
+ }
+
+ const string& channel_id(crypto_negotiated_params_.channel_id);
+ scoped_ptr<crypto::SecureHash> hash(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ hash->Update(channel_id.data(), channel_id.size());
+ uint8 digest[32];
+ hash->Finish(digest, sizeof(digest));
+
+ base::Base64Encode(string(
+ reinterpret_cast<const char*>(digest), sizeof(digest)), output);
+ // Remove padding.
+ size_t len = output->size();
+ if (len >= 2) {
+ if ((*output)[len - 1] == '=') {
+ len--;
+ if ((*output)[len - 1] == '=') {
+ len--;
+ }
+ output->resize(len);
+ }
+ }
+ return true;
+}
+
+QuicErrorCode QuicCryptoServerStream::ProcessClientHello(
+ const CryptoHandshakeMessage& message,
+ CryptoHandshakeMessage* reply,
+ string* error_details) {
+ return crypto_config_.ProcessClientHello(
+ message,
+ session()->connection()->version(),
+ session()->connection()->guid(),
+ session()->connection()->peer_address(),
+ session()->connection()->clock(),
+ session()->connection()->random_generator(),
+ &crypto_negotiated_params_, reply, error_details);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_server_stream.h b/chromium/net/quic/quic_crypto_server_stream.h
new file mode 100644
index 00000000000..f1e30cb558a
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_server_stream.h
@@ -0,0 +1,57 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
+
+#include <string>
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+class QuicCryptoServerConfig;
+class QuicSession;
+
+namespace test {
+class CryptoTestUtils;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicCryptoServerStream : public QuicCryptoStream {
+ public:
+ QuicCryptoServerStream(const QuicCryptoServerConfig& crypto_config,
+ QuicSession* session);
+ explicit QuicCryptoServerStream(QuicSession* session);
+ virtual ~QuicCryptoServerStream();
+
+ // CryptoFramerVisitorInterface implementation
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+
+ // GetBase64SHA256ClientChannelID sets |*output| to the base64 encoded,
+ // SHA-256 hash of the client's ChannelID key and returns true, if the client
+ // presented a ChannelID. Otherwise it returns false.
+ bool GetBase64SHA256ClientChannelID(std::string* output) const;
+
+ protected:
+ virtual QuicErrorCode ProcessClientHello(
+ const CryptoHandshakeMessage& message,
+ CryptoHandshakeMessage* reply,
+ std::string* error_details);
+
+ const QuicCryptoServerConfig* crypto_config() { return &crypto_config_; }
+
+ private:
+ friend class test::CryptoTestUtils;
+
+ // crypto_config_ contains crypto parameters for the handshake.
+ const QuicCryptoServerConfig& crypto_config_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CRYPTO_SERVER_STREAM_H_
diff --git a/chromium/net/quic/quic_crypto_server_stream_test.cc b/chromium/net/quic/quic_crypto_server_stream_test.cc
new file mode 100644
index 00000000000..3bb2593f1b2
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_server_stream_test.cc
@@ -0,0 +1,265 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_server_stream.h"
+
+#include <map>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+class QuicConnection;
+class ReliableQuicStream;
+} // namespace net
+
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+// TODO(agl): Use rch's utility class for parsing a message when committed.
+class TestQuicVisitor : public NoOpFramerVisitor {
+ public:
+ TestQuicVisitor() {}
+
+ // NoOpFramerVisitor
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+ frame_ = frame;
+ return true;
+ }
+
+ QuicStreamFrame* frame() { return &frame_; }
+
+ private:
+ QuicStreamFrame frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestQuicVisitor);
+};
+
+class QuicCryptoServerStreamTest : public ::testing::Test {
+ public:
+ QuicCryptoServerStreamTest()
+ : guid_(1),
+ addr_(ParseIPLiteralToNumber("192.0.2.33", &ip_) ?
+ ip_ : IPAddressNumber(), 1),
+ connection_(new PacketSavingConnection(guid_, addr_, true)),
+ session_(connection_, QuicConfig(), true),
+ crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance()),
+ stream_(crypto_config_, &session_) {
+ config_.SetDefaults();
+ session_.config()->SetDefaults();
+ session_.SetCryptoStream(&stream_);
+ // We advance the clock initially because the default time is zero and the
+ // strike register worries that we've just overflowed a uint32 time.
+ connection_->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+ // TODO(rtenneti): Enable testing of ProofSource.
+ // crypto_config_.SetProofSource(CryptoTestUtils::ProofSourceForTesting());
+
+ CryptoTestUtils::SetupCryptoServerConfigForTest(
+ connection_->clock(), connection_->random_generator(),
+ session_.config(), &crypto_config_);
+ }
+
+ void ConstructHandshakeMessage() {
+ CryptoFramer framer;
+ message_data_.reset(framer.ConstructHandshakeMessage(message_));
+ }
+
+ int CompleteCryptoHandshake() {
+ return CryptoTestUtils::HandshakeWithFakeClient(connection_, &stream_,
+ client_options_);
+ }
+
+ protected:
+ IPAddressNumber ip_;
+ QuicGuid guid_;
+ IPEndPoint addr_;
+ PacketSavingConnection* connection_;
+ TestSession session_;
+ QuicConfig config_;
+ QuicCryptoServerConfig crypto_config_;
+ QuicCryptoServerStream stream_;
+ CryptoHandshakeMessage message_;
+ scoped_ptr<QuicData> message_data_;
+ CryptoTestUtils::FakeClientOptions client_options_;
+};
+
+TEST_F(QuicCryptoServerStreamTest, NotInitiallyConected) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ EXPECT_FALSE(stream_.encryption_established());
+ EXPECT_FALSE(stream_.handshake_confirmed());
+}
+
+TEST_F(QuicCryptoServerStreamTest, ConnectedAfterCHLO) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ // CompleteCryptoHandshake returns the number of client hellos sent. This
+ // test should send:
+ // * One to get a source-address token and certificates.
+ // * One to complete the handshake.
+ EXPECT_EQ(2, CompleteCryptoHandshake());
+ EXPECT_TRUE(stream_.encryption_established());
+ EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+TEST_F(QuicCryptoServerStreamTest, ZeroRTT) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ QuicGuid guid(1);
+ IPAddressNumber ip;
+ ParseIPLiteralToNumber("127.0.0.1", &ip);
+ IPEndPoint addr(ip, 0);
+ PacketSavingConnection* client_conn =
+ new PacketSavingConnection(guid, addr, false);
+ PacketSavingConnection* server_conn =
+ new PacketSavingConnection(guid, addr, false);
+ client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+ server_conn->AdvanceTime(QuicTime::Delta::FromSeconds(100000));
+
+ QuicConfig client_config;
+ client_config.SetDefaults();
+ scoped_ptr<TestSession> client_session(
+ new TestSession(client_conn, client_config, false));
+ QuicCryptoClientConfig client_crypto_config;
+ client_crypto_config.SetDefaults();
+
+ scoped_ptr<QuicCryptoClientStream> client(new QuicCryptoClientStream(
+ "test.example.com", client_session.get(), &client_crypto_config));
+ client_session->SetCryptoStream(client.get());
+
+ // Do a first handshake in order to prime the client config with the server's
+ // information.
+ CHECK(client->CryptoConnect());
+ CHECK_EQ(1u, client_conn->packets_.size());
+
+ scoped_ptr<TestSession> server_session(
+ new TestSession(server_conn, config_, true));
+ scoped_ptr<QuicCryptoServerStream> server(
+ new QuicCryptoServerStream(crypto_config_, server_session.get()));
+ server_session->SetCryptoStream(server.get());
+
+ CryptoTestUtils::CommunicateHandshakeMessages(
+ client_conn, client.get(), server_conn, server.get());
+ EXPECT_EQ(2, client->num_sent_client_hellos());
+
+ // Now do another handshake, hopefully in 0-RTT.
+ LOG(INFO) << "Resetting for 0-RTT handshake attempt";
+
+ client_conn = new PacketSavingConnection(guid, addr, false);
+ server_conn = new PacketSavingConnection(guid, addr, false);
+ // We need to advance time past the strike-server window so that it's
+ // authoritative in this time span.
+ client_conn->AdvanceTime(QuicTime::Delta::FromSeconds(102000));
+ server_conn->AdvanceTime(QuicTime::Delta::FromSeconds(102000));
+
+ // This causes the client's nonce to be different and thus stops the
+ // strike-register from rejecting the repeated nonce.
+ reinterpret_cast<MockRandom*>(client_conn->random_generator())->ChangeValue();
+ client_session.reset(new TestSession(client_conn, client_config, false));
+ server_session.reset(new TestSession(server_conn, config_, true));
+ client.reset(new QuicCryptoClientStream(
+ "test.example.com", client_session.get(), &client_crypto_config));
+ client_session->SetCryptoStream(client.get());
+
+ server.reset(new QuicCryptoServerStream(crypto_config_,
+ server_session.get()));
+ server_session->SetCryptoStream(server.get());
+
+ CHECK(client->CryptoConnect());
+
+ CryptoTestUtils::CommunicateHandshakeMessages(
+ client_conn, client.get(), server_conn, server.get());
+ EXPECT_EQ(1, client->num_sent_client_hellos());
+}
+
+TEST_F(QuicCryptoServerStreamTest, MessageAfterHandshake) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+ EXPECT_CALL(*connection_, SendConnectionClose(
+ QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE));
+ message_.set_tag(kCHLO);
+ ConstructHandshakeMessage();
+ stream_.ProcessData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoServerStreamTest, BadMessageType) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ message_.set_tag(kSHLO);
+ ConstructHandshakeMessage();
+ EXPECT_CALL(*connection_, SendConnectionClose(
+ QUIC_INVALID_CRYPTO_MESSAGE_TYPE));
+ stream_.ProcessData(message_data_->data(), message_data_->length());
+}
+
+TEST_F(QuicCryptoServerStreamTest, WithoutCertificates) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ crypto_config_.SetProofSource(NULL);
+ client_options_.dont_verify_certs = true;
+
+ // Only 2 client hellos need to be sent in the no-certs case: one to get the
+ // source-address token and the second to finish.
+ EXPECT_EQ(2, CompleteCryptoHandshake());
+ EXPECT_TRUE(stream_.encryption_established());
+ EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+TEST_F(QuicCryptoServerStreamTest, ChannelID) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ client_options_.channel_id_enabled = true;
+ // TODO(rtenneti): Enable testing of ProofVerifier.
+ // CompleteCryptoHandshake verifies
+ // stream_.crypto_negotiated_params().channel_id is correct.
+ EXPECT_EQ(2, CompleteCryptoHandshake());
+ EXPECT_TRUE(stream_.encryption_established());
+ EXPECT_TRUE(stream_.handshake_confirmed());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_stream.cc b/chromium/net/quic/quic_crypto_stream.cc
new file mode 100644
index 00000000000..569648f1baa
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_stream.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_stream.h"
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_session.h"
+
+using std::string;
+using base::StringPiece;
+
+namespace net {
+
+QuicCryptoStream::QuicCryptoStream(QuicSession* session)
+ : ReliableQuicStream(kCryptoStreamId, session),
+ encryption_established_(false),
+ handshake_confirmed_(false) {
+ crypto_framer_.set_visitor(this);
+}
+
+void QuicCryptoStream::OnError(CryptoFramer* framer) {
+ session()->ConnectionClose(framer->error(), false);
+}
+
+void QuicCryptoStream::OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ session()->OnCryptoHandshakeMessageReceived(message);
+}
+
+uint32 QuicCryptoStream::ProcessData(const char* data,
+ uint32 data_len) {
+ // Do not process handshake messages after the handshake is confirmed.
+ if (handshake_confirmed()) {
+ CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+ return 0;
+ }
+ if (!crypto_framer_.ProcessInput(StringPiece(data, data_len))) {
+ CloseConnection(crypto_framer_.error());
+ return 0;
+ }
+ return data_len;
+}
+
+void QuicCryptoStream::CloseConnection(QuicErrorCode error) {
+ session()->connection()->SendConnectionClose(error);
+}
+
+void QuicCryptoStream::CloseConnectionWithDetails(QuicErrorCode error,
+ const string& details) {
+ session()->connection()->SendConnectionCloseWithDetails(error, details);
+}
+
+void QuicCryptoStream::SendHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ session()->OnCryptoHandshakeMessageSent(message);
+ const QuicData& data = message.GetSerialized();
+ // TODO(wtc): check the return value.
+ WriteData(string(data.data(), data.length()), false);
+}
+
+const QuicCryptoNegotiatedParameters&
+QuicCryptoStream::crypto_negotiated_params() const {
+ return crypto_negotiated_params_;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_crypto_stream.h b/chromium/net/quic/quic_crypto_stream.h
new file mode 100644
index 00000000000..c402b0d9b44
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_stream.h
@@ -0,0 +1,70 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_CRYPTO_STREAM_H_
+#define NET_QUIC_QUIC_CRYPTO_STREAM_H_
+
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+class QuicSession;
+
+// Crypto handshake messages in QUIC take place over a reserved
+// reliable stream with the id 1. Each endpoint (client and server)
+// will allocate an instance of a subclass of QuicCryptoStream
+// to send and receive handshake messages. (In the normal 1-RTT
+// handshake, the client will send a client hello, CHLO, message.
+// The server will receive this message and respond with a server
+// hello message, SHLO. At this point both sides will have established
+// a crypto context they can use to send encrypted messages.
+//
+// For more details: http://goto.google.com/quic-crypto
+class NET_EXPORT_PRIVATE QuicCryptoStream
+ : public ReliableQuicStream,
+ public CryptoFramerVisitorInterface {
+ public:
+ explicit QuicCryptoStream(QuicSession* session);
+
+ // CryptoFramerVisitorInterface implementation
+ virtual void OnError(CryptoFramer* framer) OVERRIDE;
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+
+ // ReliableQuicStream implementation
+ virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+
+ // Sends |message| to the peer.
+ // TODO(wtc): return a success/failure status.
+ void SendHandshakeMessage(const CryptoHandshakeMessage& message);
+
+ bool encryption_established() { return encryption_established_; }
+ bool handshake_confirmed() { return handshake_confirmed_; }
+
+ const QuicCryptoNegotiatedParameters& crypto_negotiated_params() const;
+
+ protected:
+ // Closes the connection
+ void CloseConnection(QuicErrorCode error);
+ void CloseConnectionWithDetails(QuicErrorCode error, const string& details);
+
+ bool encryption_established_;
+ bool handshake_confirmed_;
+
+ QuicCryptoNegotiatedParameters crypto_negotiated_params_;
+
+ private:
+ CryptoFramer crypto_framer_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicCryptoStream);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_CRYPTO_STREAM_H_
diff --git a/chromium/net/quic/quic_crypto_stream_test.cc b/chromium/net/quic/quic_crypto_stream_test.cc
new file mode 100644
index 00000000000..cc69304b749
--- /dev/null
+++ b/chromium/net/quic/quic_crypto_stream_test.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_crypto_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockQuicCryptoStream : public QuicCryptoStream {
+ public:
+ explicit MockQuicCryptoStream(QuicSession* session)
+ : QuicCryptoStream(session) {
+ }
+
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE {
+ messages_.push_back(message);
+ }
+
+ vector<CryptoHandshakeMessage>* messages() {
+ return &messages_;
+ }
+
+ private:
+ vector<CryptoHandshakeMessage> messages_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockQuicCryptoStream);
+};
+
+class QuicCryptoStreamTest : public ::testing::Test {
+ public:
+ QuicCryptoStreamTest()
+ : addr_(IPAddressNumber(), 1),
+ connection_(new MockConnection(1, addr_, false)),
+ session_(connection_, true),
+ stream_(&session_) {
+ message_.set_tag(kSHLO);
+ message_.SetStringPiece(1, "abc");
+ message_.SetStringPiece(2, "def");
+ ConstructHandshakeMessage();
+ }
+
+ void ConstructHandshakeMessage() {
+ CryptoFramer framer;
+ message_data_.reset(framer.ConstructHandshakeMessage(message_));
+ }
+
+ protected:
+ IPEndPoint addr_;
+ MockConnection* connection_;
+ MockSession session_;
+ MockQuicCryptoStream stream_;
+ CryptoHandshakeMessage message_;
+ scoped_ptr<QuicData> message_data_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicCryptoStreamTest);
+};
+
+TEST_F(QuicCryptoStreamTest, NotInitiallyConected) {
+ EXPECT_FALSE(stream_.encryption_established());
+ EXPECT_FALSE(stream_.handshake_confirmed());
+}
+
+TEST_F(QuicCryptoStreamTest, OnErrorClosesConnection) {
+ CryptoFramer framer;
+ EXPECT_CALL(session_, ConnectionClose(QUIC_NO_ERROR, false));
+ stream_.OnError(&framer);
+}
+
+TEST_F(QuicCryptoStreamTest, ProcessData) {
+ EXPECT_EQ(message_data_->length(),
+ stream_.ProcessData(message_data_->data(),
+ message_data_->length()));
+ ASSERT_EQ(1u, stream_.messages()->size());
+ const CryptoHandshakeMessage& message = (*stream_.messages())[0];
+ EXPECT_EQ(kSHLO, message.tag());
+ EXPECT_EQ(2u, message.tag_value_map().size());
+ EXPECT_EQ("abc", CryptoTestUtils::GetValueForTag(message, 1));
+ EXPECT_EQ("def", CryptoTestUtils::GetValueForTag(message, 2));
+}
+
+TEST_F(QuicCryptoStreamTest, ProcessBadData) {
+ string bad(message_data_->data(), message_data_->length());
+ const int kFirstTagIndex = sizeof(uint32) + // message tag
+ sizeof(uint16) + // number of tag-value pairs
+ sizeof(uint16); // padding
+ EXPECT_EQ(1, bad[kFirstTagIndex]);
+ bad[kFirstTagIndex] = 0x7F; // out of order tag
+
+ EXPECT_CALL(*connection_,
+ SendConnectionClose(QUIC_CRYPTO_TAGS_OUT_OF_ORDER));
+ EXPECT_EQ(0u, stream_.ProcessData(bad.data(), bad.length()));
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_data_reader.cc b/chromium/net/quic/quic_data_reader.cc
new file mode 100644
index 00000000000..3bb7fc3f8ab
--- /dev/null
+++ b/chromium/net/quic/quic_data_reader.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_data_reader.h"
+
+using base::StringPiece;
+
+namespace net {
+
+QuicDataReader::QuicDataReader(const char* data, const size_t len)
+ : data_(data),
+ len_(len),
+ pos_(0) {
+}
+
+bool QuicDataReader::ReadUInt16(uint16* result) {
+ return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt32(uint32* result) {
+ return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt48(uint64* result) {
+ uint32 lo;
+ if (!ReadUInt32(&lo)) {
+ return false;
+ }
+
+ uint16 hi;
+ if (!ReadUInt16(&hi)) {
+ return false;
+ }
+
+ *result = hi;
+ *result <<= 32;
+ *result += lo;
+
+ return true;
+}
+
+bool QuicDataReader::ReadUInt64(uint64* result) {
+ return ReadBytes(result, sizeof(*result));
+}
+
+bool QuicDataReader::ReadUInt128(uint128* result) {
+ uint64 high_hash;
+ uint64 low_hash;
+
+ if (!ReadUInt64(&low_hash)) {
+ return false;
+ }
+ if (!ReadUInt64(&high_hash)) {
+ return false;
+ }
+
+ *result = uint128(high_hash, low_hash);
+ return true;
+}
+
+bool QuicDataReader::ReadStringPiece16(StringPiece* result) {
+ // Read resultant length.
+ uint16 result_len;
+ if (!ReadUInt16(&result_len)) {
+ // OnFailure() already called.
+ return false;
+ }
+
+ return ReadStringPiece(result, result_len);
+}
+
+bool QuicDataReader::ReadStringPiece(StringPiece* result, size_t size) {
+ // Make sure that we have enough data to read.
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Set result.
+ result->set(data_ + pos_, size);
+
+ // Iterate.
+ pos_ += size;
+
+ return true;
+}
+
+StringPiece QuicDataReader::ReadRemainingPayload() {
+ StringPiece payload = PeekRemainingPayload();
+ pos_ = len_;
+ return payload;
+}
+
+StringPiece QuicDataReader::PeekRemainingPayload() {
+ return StringPiece(data_ + pos_, len_ - pos_);
+}
+
+bool QuicDataReader::ReadBytes(void* result, size_t size) {
+ // Make sure that we have enough data to read.
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ memcpy(result, data_ + pos_, size);
+
+ // Iterate.
+ pos_ += size;
+
+ return true;
+}
+
+bool QuicDataReader::IsDoneReading() const {
+ return len_ == pos_;
+}
+
+size_t QuicDataReader::BytesRemaining() const {
+ return len_ - pos_;
+}
+
+bool QuicDataReader::CanRead(size_t bytes) const {
+ return bytes <= (len_ - pos_);
+}
+
+void QuicDataReader::OnFailure() {
+ // Set our iterator to the end of the buffer so that further reads fail
+ // immediately.
+ pos_ = len_;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_data_reader.h b/chromium/net/quic/quic_data_reader.h
new file mode 100644
index 00000000000..fff07ddc703
--- /dev/null
+++ b/chromium/net/quic/quic_data_reader.h
@@ -0,0 +1,126 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_DATA_READER_H_
+#define NET_QUIC_QUIC_DATA_READER_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Used for reading QUIC data. Though there isn't really anything terribly
+// QUIC-specific here, it's a helper class that's useful when doing QUIC
+// framing.
+//
+// To use, simply construct a QuicDataReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the QuicDataReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class NET_EXPORT_PRIVATE QuicDataReader {
+ public:
+ // Caller must provide an underlying buffer to work on.
+ QuicDataReader(const char* data, const size_t len);
+
+ // Empty destructor.
+ ~QuicDataReader() {}
+
+ // Reads a 16-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt16(uint16* result);
+
+ // Reads a 32-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt32(uint32* result);
+
+ // Reads a 48-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt48(uint64* result);
+
+ // Reads a 64-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt64(uint64* result);
+
+ // Reads a 128-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt128(uint128* result);
+
+ // Reads a string prefixed with 16-bit length into the given output parameter.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece16(base::StringPiece* result);
+
+ // Reads a given number of bytes into the given buffer. The buffer
+ // must be of adequate size.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece(base::StringPiece* result, size_t len);
+
+ // Returns the remaining payload as a StringPiece.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterator.
+ base::StringPiece ReadRemainingPayload();
+
+ // Returns the remaining payload as a StringPiece.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // DOES NOT forward the internal iterator.
+ base::StringPiece PeekRemainingPayload();
+
+ // Reads a given number of bytes into the given buffer. The buffer
+ // must be of adequate size.
+ // Forwards the internal iterator on success.
+ // Returns true on success, false otherwise.
+ bool ReadBytes(void* result, size_t size);
+
+ // Returns true if the entirety of the underlying buffer has been read via
+ // Read*() calls.
+ bool IsDoneReading() const;
+
+ // Returns the number of bytes remaining to be read.
+ size_t BytesRemaining() const;
+
+ private:
+ // Returns true if the underlying buffer has enough room to read the given
+ // amount of bytes.
+ bool CanRead(size_t bytes) const;
+
+ // To be called when a read fails for any reason.
+ void OnFailure();
+
+ // The data buffer that we're reading from.
+ const char* data_;
+
+ // The length of the data buffer that we're reading from.
+ const size_t len_;
+
+ // The location of the next read from our data buffer.
+ size_t pos_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_DATA_READER_H_
diff --git a/chromium/net/quic/quic_data_writer.cc b/chromium/net/quic/quic_data_writer.cc
new file mode 100644
index 00000000000..e52cd03248b
--- /dev/null
+++ b/chromium/net/quic/quic_data_writer.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_data_writer.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::numeric_limits;
+
+namespace net {
+
+QuicDataWriter::QuicDataWriter(size_t size)
+ : buffer_(new char[size]),
+ capacity_(size),
+ length_(0) {
+}
+
+QuicDataWriter::~QuicDataWriter() {
+ delete[] buffer_;
+}
+
+char* QuicDataWriter::take() {
+ char* rv = buffer_;
+ buffer_ = NULL;
+ capacity_ = 0;
+ length_ = 0;
+ return rv;
+}
+
+bool QuicDataWriter::WriteUInt8(uint8 value) {
+ return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt16(uint16 value) {
+ return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt32(uint32 value) {
+ return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt48(uint64 value) {
+ uint32 hi = value >> 32;
+ uint32 lo = value & GG_UINT64_C(0x00000000FFFFFFFF);
+ return WriteUInt32(lo) && WriteUInt16(hi);
+}
+
+bool QuicDataWriter::WriteUInt64(uint64 value) {
+ return WriteBytes(&value, sizeof(value));
+}
+
+bool QuicDataWriter::WriteUInt128(uint128 value) {
+ return WriteUInt64(Uint128Low64(value)) && WriteUInt64(Uint128High64(value));
+}
+
+bool QuicDataWriter::WriteStringPiece16(StringPiece val) {
+ if (val.length() > numeric_limits<uint16>::max()) {
+ return false;
+ }
+ if (!WriteUInt16(val.size())) {
+ return false;
+ }
+ return WriteBytes(val.data(), val.size());
+}
+
+char* QuicDataWriter::BeginWrite(size_t length) {
+ if (length_ > capacity_) {
+ return NULL;
+ }
+
+ if (capacity_ - length_ < length) {
+ return NULL;
+ }
+
+#ifdef ARCH_CPU_64_BITS
+ DCHECK_LE(length, numeric_limits<uint32>::max());
+#endif
+
+ return buffer_ + length_;
+}
+
+bool QuicDataWriter::WriteBytes(const void* data, size_t data_len) {
+ char* dest = BeginWrite(data_len);
+ if (!dest) {
+ return false;
+ }
+
+ memcpy(dest, data, data_len);
+
+ length_ += data_len;
+ return true;
+}
+
+bool QuicDataWriter::WriteRepeatedByte(uint8 byte, size_t count) {
+ char* dest = BeginWrite(count);
+ if (!dest) {
+ return false;
+ }
+
+ memset(dest, byte, count);
+
+ length_ += count;
+ return true;
+}
+
+void QuicDataWriter::WritePadding() {
+ DCHECK_LE(length_, capacity_);
+ if (length_ > capacity_) {
+ return;
+ }
+ memset(buffer_ + length_, 0x00, capacity_ - length_);
+ length_ = capacity_;
+}
+
+bool QuicDataWriter::WriteUInt8ToOffset(uint8 value, size_t offset) {
+ DCHECK_LT(offset, capacity_);
+ size_t latched_length = length_;
+ length_ = offset;
+ bool success = WriteUInt8(value);
+ DCHECK_LE(length_, latched_length);
+ length_ = latched_length;
+ return success;
+}
+
+bool QuicDataWriter::WriteUInt32ToOffset(uint32 value, size_t offset) {
+ DCHECK_LT(offset, capacity_);
+ size_t latched_length = length_;
+ length_ = offset;
+ bool success = WriteUInt32(value);
+ DCHECK_LE(length_, latched_length);
+ length_ = latched_length;
+ return success;
+}
+
+bool QuicDataWriter::WriteUInt48ToOffset(uint64 value, size_t offset) {
+ DCHECK_LT(offset, capacity_);
+ size_t latched_length = length_;
+ length_ = offset;
+ bool success = WriteUInt48(value);
+ DCHECK_LE(length_, latched_length);
+ length_ = latched_length;
+ return success;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_data_writer.h b/chromium/net/quic/quic_data_writer.h
new file mode 100644
index 00000000000..f3408d12215
--- /dev/null
+++ b/chromium/net/quic/quic_data_writer.h
@@ -0,0 +1,80 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_DATA_WRITER_H_
+#define NET_QUIC_QUIC_DATA_WRITER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// This class provides facilities for packing QUIC data.
+//
+// The QuicDataWriter supports appending primitive values (int, string, etc)
+// to a frame instance. The QuicDataWriter grows its internal memory buffer
+// dynamically to hold the sequence of primitive values. The internal memory
+// buffer is exposed as the "data" of the QuicDataWriter.
+class NET_EXPORT_PRIVATE QuicDataWriter {
+ public:
+ explicit QuicDataWriter(size_t length);
+
+ ~QuicDataWriter();
+
+ // Returns the size of the QuicDataWriter's data.
+ size_t length() const { return length_; }
+
+ // Takes the buffer from the QuicDataWriter.
+ char* take();
+
+ // Methods for adding to the payload. These values are appended to the end
+ // of the QuicDataWriter payload. Note - binary integers are written in
+ // host byte order (little endian) not network byte order (big endian).
+ bool WriteUInt8(uint8 value);
+ bool WriteUInt16(uint16 value);
+ bool WriteUInt32(uint32 value);
+ bool WriteUInt48(uint64 value);
+ bool WriteUInt64(uint64 value);
+ bool WriteUInt128(uint128 value);
+ bool WriteStringPiece16(base::StringPiece val);
+ bool WriteBytes(const void* data, size_t data_len);
+ bool WriteRepeatedByte(uint8 byte, size_t count);
+ // Fills the remaining buffer with null characters.
+ void WritePadding();
+
+ // Methods for editing the payload at a specific offset, where the
+ // offset must be within the writer's capacity.
+ // Return true if there is enough space at that offset, false otherwise.
+ bool WriteUInt8ToOffset(uint8 value, size_t offset);
+ bool WriteUInt32ToOffset(uint32 value, size_t offset);
+ bool WriteUInt48ToOffset(uint64 value, size_t offset);
+
+ size_t capacity() const {
+ return capacity_;
+ }
+
+ protected:
+ const char* end_of_payload() const { return buffer_ + length_; }
+
+ private:
+ // Returns the location that the data should be written at, or NULL if there
+ // is not enough room. Call EndWrite with the returned offset and the given
+ // length to pad out for the next write.
+ char* BeginWrite(size_t length);
+
+ char* buffer_;
+ size_t capacity_; // Allocation size of payload (or -1 if buffer is const).
+ size_t length_; // Current length of the buffer.
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_DATA_WRITER_H_
diff --git a/chromium/net/quic/quic_data_writer_test.cc b/chromium/net/quic/quic_data_writer_test.cc
new file mode 100644
index 00000000000..4fbd7e4efdf
--- /dev/null
+++ b/chromium/net/quic/quic_data_writer_test.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_data_writer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicDataWriterTest, WriteUint8ToOffset) {
+ QuicDataWriter writer(4);
+
+ writer.WriteUInt32(0xfefdfcfb);
+ EXPECT_TRUE(writer.WriteUInt8ToOffset(1, 0));
+ EXPECT_TRUE(writer.WriteUInt8ToOffset(2, 1));
+ EXPECT_TRUE(writer.WriteUInt8ToOffset(3, 2));
+ EXPECT_TRUE(writer.WriteUInt8ToOffset(4, 3));
+
+ char* data = writer.take();
+
+ EXPECT_EQ(1, data[0]);
+ EXPECT_EQ(2, data[1]);
+ EXPECT_EQ(3, data[2]);
+ EXPECT_EQ(4, data[3]);
+
+ delete[] data;
+}
+
+TEST(QuicDataWriterDeathTest, WriteUint8ToOffset) {
+ QuicDataWriter writer(4);
+
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+ EXPECT_DEBUG_DEATH(writer.WriteUInt8ToOffset(5, 4), "Check failed");
+#else
+ EXPECT_DEATH(writer.WriteUInt8ToOffset(5, 4), "Check failed");
+#endif
+#endif
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_fec_group.cc b/chromium/net/quic/quic_fec_group.cc
new file mode 100644
index 00000000000..f5f4a77b908
--- /dev/null
+++ b/chromium/net/quic/quic_fec_group.cc
@@ -0,0 +1,166 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_fec_group.h"
+
+#include <limits>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::numeric_limits;
+using std::set;
+
+namespace net {
+
+namespace {
+const QuicPacketSequenceNumber kNoSequenceNumber = kuint64max;
+} // namespace
+
+QuicFecGroup::QuicFecGroup()
+ : min_protected_packet_(kNoSequenceNumber),
+ max_protected_packet_(kNoSequenceNumber),
+ payload_parity_len_(0),
+ entropy_parity_(false) {
+}
+
+QuicFecGroup::~QuicFecGroup() {}
+
+bool QuicFecGroup::Update(const QuicPacketHeader& header,
+ StringPiece decrypted_payload) {
+ if (received_packets_.count(header.packet_sequence_number) != 0) {
+ return false;
+ }
+ if (min_protected_packet_ != kNoSequenceNumber &&
+ max_protected_packet_ != kNoSequenceNumber &&
+ (header.packet_sequence_number < min_protected_packet_ ||
+ header.packet_sequence_number > max_protected_packet_)) {
+ DLOG(ERROR) << "FEC group does not cover received packet: "
+ << header.packet_sequence_number;
+ return false;
+ }
+ if (!UpdateParity(decrypted_payload, header.entropy_flag)) {
+ return false;
+ }
+ received_packets_.insert(header.packet_sequence_number);
+ return true;
+}
+
+bool QuicFecGroup::UpdateFec(
+ QuicPacketSequenceNumber fec_packet_sequence_number,
+ bool fec_packet_entropy,
+ const QuicFecData& fec) {
+ if (min_protected_packet_ != kNoSequenceNumber) {
+ return false;
+ }
+ SequenceNumberSet::const_iterator it = received_packets_.begin();
+ while (it != received_packets_.end()) {
+ if ((*it < fec.fec_group) ||
+ (*it >= fec_packet_sequence_number)) {
+ DLOG(ERROR) << "FEC group does not cover received packet: " << *it;
+ return false;
+ }
+ ++it;
+ }
+ if (!UpdateParity(fec.redundancy, fec_packet_entropy)) {
+ return false;
+ }
+ min_protected_packet_ = fec.fec_group;
+ max_protected_packet_ = fec_packet_sequence_number - 1;
+ return true;
+}
+
+bool QuicFecGroup::CanRevive() const {
+ // We can revive if we're missing exactly 1 packet.
+ return NumMissingPackets() == 1;
+}
+
+bool QuicFecGroup::IsFinished() const {
+ // We are finished if we are not missing any packets.
+ return NumMissingPackets() == 0;
+}
+
+size_t QuicFecGroup::Revive(QuicPacketHeader* header,
+ char* decrypted_payload,
+ size_t decrypted_payload_len) {
+ if (!CanRevive()) {
+ return 0;
+ }
+
+ // Identify the packet sequence number to be resurrected.
+ QuicPacketSequenceNumber missing = kNoSequenceNumber;
+ for (QuicPacketSequenceNumber i = min_protected_packet_;
+ i <= max_protected_packet_; ++i) {
+ // Is this packet missing?
+ if (received_packets_.count(i) == 0) {
+ missing = i;
+ break;
+ }
+ }
+ DCHECK_NE(kNoSequenceNumber, missing);
+
+ DCHECK_LE(payload_parity_len_, decrypted_payload_len);
+ if (payload_parity_len_ > decrypted_payload_len) {
+ return 0;
+ }
+ for (size_t i = 0; i < payload_parity_len_; ++i) {
+ decrypted_payload[i] = payload_parity_[i];
+ }
+
+ header->packet_sequence_number = missing;
+ header->entropy_flag = entropy_parity_;
+
+ received_packets_.insert(missing);
+ return payload_parity_len_;
+}
+
+bool QuicFecGroup::ProtectsPacketsBefore(QuicPacketSequenceNumber num) const {
+ if (max_protected_packet_ != kNoSequenceNumber) {
+ return max_protected_packet_ < num;
+ }
+ // Since we might not yet have recevied the FEC packet, we must check
+ // the packets we have received.
+ return *received_packets_.begin() < num;
+}
+
+bool QuicFecGroup::UpdateParity(StringPiece payload, bool entropy) {
+ DCHECK_LE(payload.size(), kMaxPacketSize);
+ if (payload.size() > kMaxPacketSize) {
+ DLOG(ERROR) << "Illegal payload size: " << payload.size();
+ return false;
+ }
+ if (payload_parity_len_ < payload.size()) {
+ payload_parity_len_ = payload.size();
+ }
+ DCHECK_LE(payload.size(), kMaxPacketSize);
+ if (received_packets_.size() == 0 &&
+ min_protected_packet_ == kNoSequenceNumber) {
+ // Initialize the parity to the value of this payload
+ memcpy(payload_parity_, payload.data(), payload.size());
+ if (payload.size() < kMaxPacketSize) {
+ // TODO(rch): expand as needed.
+ memset(payload_parity_ + payload.size(), 0,
+ kMaxPacketSize - payload.size());
+ }
+ entropy_parity_ = entropy;
+ return true;
+ }
+ // Update the parity by XORing in the data (padding with 0s if necessary).
+ for (size_t i = 0; i < kMaxPacketSize; ++i) {
+ uint8 byte = i < payload.size() ? payload[i] : 0x00;
+ payload_parity_[i] ^= byte;
+ }
+ // xor of boolean values.
+ entropy_parity_ = (entropy_parity_ != entropy);
+ return true;
+}
+
+size_t QuicFecGroup::NumMissingPackets() const {
+ if (min_protected_packet_ == kNoSequenceNumber)
+ return numeric_limits<size_t>::max();
+ return (max_protected_packet_ - min_protected_packet_ + 1) -
+ received_packets_.size();
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_fec_group.h b/chromium/net/quic/quic_fec_group.h
new file mode 100644
index 00000000000..d905d03236e
--- /dev/null
+++ b/chromium/net/quic/quic_fec_group.h
@@ -0,0 +1,98 @@
+// Copyright (c) 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.
+//
+// Tracks information about an FEC group, including the packets
+// that have been seen, and the running parity. Provided the ability
+// to revive a dropped packet.
+
+#ifndef NET_QUIC_QUIC_FEC_GROUP_H_
+#define NET_QUIC_QUIC_FEC_GROUP_H_
+
+#include <set>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicFecGroup {
+ public:
+ QuicFecGroup();
+ ~QuicFecGroup();
+
+ // Updates the FEC group based on the delivery of a data packet.
+ // Returns false if this packet has already been seen, true otherwise.
+ bool Update(const QuicPacketHeader& header,
+ base::StringPiece decrypted_payload);
+
+ // Updates the FEC group based on the delivery of an FEC packet.
+ // Returns false if this packet has already been seen or if it does
+ // not claim to protect all the packets previously seen in this group.
+ // |fec_packet_entropy|: XOR of entropy of all packets in the fec group.
+ bool UpdateFec(QuicPacketSequenceNumber fec_packet_sequence_number,
+ bool fec_packet_entropy,
+ const QuicFecData& fec);
+
+ // Returns true if a packet can be revived from this FEC group.
+ bool CanRevive() const;
+
+ // Returns true if all packets (FEC and data) from this FEC group have been
+ // seen or revived
+ bool IsFinished() const;
+
+ // Revives the missing packet from this FEC group. This may return a packet
+ // that is null padded to a greater length than the original packet, but
+ // the framer will handle it correctly. Returns the length of the data
+ // written to |decrypted_payload|, or 0 if the packet could not be revived.
+ size_t Revive(QuicPacketHeader* header,
+ char* decrypted_payload,
+ size_t decrypted_payload_len);
+
+ // Returns true of this FEC group protects any packets with sequence
+ // numbers less than |num|.
+ bool ProtectsPacketsBefore(QuicPacketSequenceNumber num) const;
+
+ const base::StringPiece payload_parity() const {
+ return base::StringPiece(payload_parity_, payload_parity_len_);
+ }
+
+ bool entropy_parity() const {
+ return entropy_parity_;
+ }
+
+ QuicPacketSequenceNumber min_protected_packet() const {
+ return min_protected_packet_;
+ }
+
+ size_t NumReceivedPackets() const {
+ return received_packets_.size();
+ }
+
+ private:
+ bool UpdateParity(base::StringPiece payload, bool entropy);
+ // Returns the number of missing packets, or size_t max if the number
+ // of missing packets is not known.
+ size_t NumMissingPackets() const;
+
+ // Set of packets that we have recevied.
+ SequenceNumberSet received_packets_;
+ // Sequence number of the first protected packet in this group (the one
+ // with the lowest packet sequence number). Will only be set once the FEC
+ // packet has been seen.
+ QuicPacketSequenceNumber min_protected_packet_;
+ // Sequence number of the last protected packet in this group (the one
+ // with the highest packet sequence number). Will only be set once the FEC
+ // packet has been seen.
+ QuicPacketSequenceNumber max_protected_packet_;
+ // The cumulative parity calculation of all received packets.
+ char payload_parity_[kMaxPacketSize];
+ size_t payload_parity_len_;
+ bool entropy_parity_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicFecGroup);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_FEC_GROUP_H_
diff --git a/chromium/net/quic/quic_fec_group_test.cc b/chromium/net/quic/quic_fec_group_test.cc
new file mode 100644
index 00000000000..d9c303aee5b
--- /dev/null
+++ b/chromium/net/quic/quic_fec_group_test.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_fec_group.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::_;
+using base::StringPiece;
+
+namespace net {
+
+namespace {
+
+const char* kData[] = {
+ "abc12345678",
+ "987defg",
+ "ghi12345",
+ "987jlkmno",
+ "mno4567890",
+ "789pqrstuvw",
+};
+
+const bool kEntropyFlag[] = {
+ false,
+ true,
+ true,
+ false,
+ true,
+ true,
+};
+
+const bool kTestFecPacketEntropy = false;
+
+} // namespace
+
+class QuicFecGroupTest : public ::testing::Test {
+ protected:
+ void RunTest(size_t num_packets, size_t lost_packet, bool out_of_order) {
+ size_t max_len = strlen(kData[0]);
+ scoped_ptr<char[]>redundancy(new char[max_len]);
+ bool entropy_redundancy = false;
+ for (size_t packet = 0; packet < num_packets; ++packet) {
+ for (size_t i = 0; i < max_len; i++) {
+ if (packet == 0) {
+ // Initialize to the first packet.
+ redundancy[i] = kData[0][i];
+ continue;
+ }
+ // XOR in the remaining packets.
+ uint8 byte = i > strlen(kData[packet]) ? 0x00 : kData[packet][i];
+ redundancy[i] = redundancy[i] ^ byte;
+ }
+ entropy_redundancy = (entropy_redundancy != kEntropyFlag[packet]);
+ }
+
+ QuicFecGroup group;
+
+ // If we're out of order, send the FEC packet in the position of the
+ // lost packet. Otherwise send all (non-missing) packets, then FEC.
+ if (out_of_order) {
+ // Update the FEC state for each non-lost packet.
+ for (size_t packet = 0; packet < num_packets; packet++) {
+ if (packet == lost_packet) {
+ ASSERT_FALSE(group.IsFinished());
+ QuicFecData fec;
+ fec.fec_group = 0;
+ fec.redundancy = StringPiece(redundancy.get(), strlen(kData[0]));
+ ASSERT_TRUE(group.UpdateFec(num_packets, entropy_redundancy, fec));
+ } else {
+ QuicPacketHeader header;
+ header.packet_sequence_number = packet;
+ header.entropy_flag = kEntropyFlag[packet];
+ ASSERT_TRUE(group.Update(header, kData[packet]));
+ }
+ ASSERT_TRUE(group.CanRevive() == (packet == num_packets - 1));
+ }
+ } else {
+ // Update the FEC state for each non-lost packet.
+ for (size_t packet = 0; packet < num_packets; packet++) {
+ if (packet == lost_packet) {
+ continue;
+ }
+
+ QuicPacketHeader header;
+ header.packet_sequence_number = packet;
+ header.entropy_flag = kEntropyFlag[packet];
+ ASSERT_TRUE(group.Update(header, kData[packet]));
+ ASSERT_FALSE(group.CanRevive());
+ }
+
+ ASSERT_FALSE(group.IsFinished());
+ // Attempt to revive the missing packet.
+ QuicFecData fec;
+ fec.fec_group = 0;
+ fec.redundancy = StringPiece(redundancy.get(), strlen(kData[0]));
+
+ ASSERT_TRUE(group.UpdateFec(num_packets, entropy_redundancy, fec));
+ }
+ QuicPacketHeader header;
+ char recovered[kMaxPacketSize];
+ ASSERT_TRUE(group.CanRevive());
+ size_t len = group.Revive(&header, recovered, arraysize(recovered));
+ ASSERT_NE(0u, len)
+ << "Failed to revive packet " << lost_packet << " out of "
+ << num_packets;
+ EXPECT_EQ(lost_packet, header.packet_sequence_number)
+ << "Failed to revive packet " << lost_packet << " out of "
+ << num_packets;
+ EXPECT_EQ(kEntropyFlag[lost_packet], header.entropy_flag);
+ ASSERT_GE(len, strlen(kData[lost_packet])) << "Incorrect length";
+ for (size_t i = 0; i < strlen(kData[lost_packet]); i++) {
+ EXPECT_EQ(kData[lost_packet][i], recovered[i]);
+ }
+ ASSERT_TRUE(group.IsFinished());
+ }
+};
+
+TEST_F(QuicFecGroupTest, UpdateAndRevive) {
+ RunTest(2, 0, false);
+ RunTest(2, 1, false);
+
+ RunTest(3, 0, false);
+ RunTest(3, 1, false);
+ RunTest(3, 2, false);
+}
+
+TEST_F(QuicFecGroupTest, UpdateAndReviveOutOfOrder) {
+ RunTest(2, 0, true);
+ RunTest(2, 1, true);
+
+ RunTest(3, 0, true);
+ RunTest(3, 1, true);
+ RunTest(3, 2, true);
+}
+
+TEST_F(QuicFecGroupTest, UpdateFecIfReceivedPacketIsNotCovered) {
+ char data1[] = "abc123";
+ char redundancy[arraysize(data1)];
+ for (size_t i = 0; i < arraysize(data1); i++) {
+ redundancy[i] = data1[i];
+ }
+
+ QuicFecGroup group;
+
+ QuicPacketHeader header;
+ header.packet_sequence_number = 3;
+ group.Update(header, data1);
+
+ QuicFecData fec;
+ fec.fec_group = 1;
+ fec.redundancy = redundancy;
+
+ header.packet_sequence_number = 2;
+ ASSERT_FALSE(group.UpdateFec(2, kTestFecPacketEntropy, fec));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBefore) {
+ QuicPacketHeader header;
+ header.packet_sequence_number = 3;
+
+ QuicFecGroup group;
+ ASSERT_TRUE(group.Update(header, kData[0]));
+
+ EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+ EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+ EXPECT_FALSE(group.ProtectsPacketsBefore(3));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBeforeWithSeveralPackets) {
+ QuicPacketHeader header;
+ header.packet_sequence_number = 3;
+
+ QuicFecGroup group;
+ ASSERT_TRUE(group.Update(header, kData[0]));
+
+ header.packet_sequence_number = 7;
+ ASSERT_TRUE(group.Update(header, kData[0]));
+
+ header.packet_sequence_number = 5;
+ ASSERT_TRUE(group.Update(header, kData[0]));
+
+ EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+ EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+ EXPECT_FALSE(group.ProtectsPacketsBefore(3));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(6));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(7));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(8));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(9));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+TEST_F(QuicFecGroupTest, ProtectsPacketsBeforeWithFecData) {
+ QuicFecData fec;
+ fec.fec_group = 2;
+ fec.redundancy = kData[0];
+
+ QuicFecGroup group;
+ ASSERT_TRUE(group.UpdateFec(3, kTestFecPacketEntropy, fec));
+
+ EXPECT_FALSE(group.ProtectsPacketsBefore(1));
+ EXPECT_FALSE(group.ProtectsPacketsBefore(2));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(3));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(4));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(5));
+ EXPECT_TRUE(group.ProtectsPacketsBefore(50));
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_framer.cc b/chromium/net/quic/quic_framer.cc
new file mode 100644
index 00000000000..8796456bcdc
--- /dev/null
+++ b/chromium/net/quic/quic_framer.cc
@@ -0,0 +1,1859 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_framer.h"
+
+#include "base/containers/hash_tables.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_data_writer.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::numeric_limits;
+using std::string;
+
+namespace net {
+
+namespace {
+
+// Mask to select the lowest 48 bits of a sequence number.
+const QuicPacketSequenceNumber k6ByteSequenceNumberMask =
+ GG_UINT64_C(0x0000FFFFFFFFFFFF);
+const QuicPacketSequenceNumber k4ByteSequenceNumberMask =
+ GG_UINT64_C(0x00000000FFFFFFFF);
+const QuicPacketSequenceNumber k2ByteSequenceNumberMask =
+ GG_UINT64_C(0x000000000000FFFF);
+const QuicPacketSequenceNumber k1ByteSequenceNumberMask =
+ GG_UINT64_C(0x00000000000000FF);
+
+const QuicGuid k1ByteGuidMask = GG_UINT64_C(0x00000000000000FF);
+const QuicGuid k4ByteGuidMask = GG_UINT64_C(0x00000000FFFFFFFF);
+
+// Mask to determine if it's a special frame type(Stream, Ack, or
+// Congestion Control) by checking if the first bit is 0, then shifting right.
+const uint8 kQuicFrameType0BitMask = 0x01;
+
+// Default frame type shift and mask.
+const uint8 kQuicDefaultFrameTypeShift = 3;
+const uint8 kQuicDefaultFrameTypeMask = 0x07;
+
+// Stream frame relative shifts and masks for interpreting the stream flags.
+// StreamID may be 1, 2, 3, or 4 bytes.
+const uint8 kQuicStreamIdShift = 2;
+const uint8 kQuicStreamIDLengthMask = 0x03;
+
+// Offset may be 0, 2, 3, 4, 5, 6, 7, 8 bytes.
+const uint8 kQuicStreamOffsetShift = 3;
+const uint8 kQuicStreamOffsetMask = 0x07;
+
+// Data length may be 0 or 2 bytes.
+const uint8 kQuicStreamDataLengthShift = 1;
+const uint8 kQuicStreamDataLengthMask = 0x01;
+
+// Fin bit may be set or not.
+const uint8 kQuicStreamFinShift = 1;
+const uint8 kQuicStreamFinMask = 0x01;
+
+
+const uint32 kInvalidDeltaTime = 0xffffffff;
+
+// Returns the absolute value of the difference between |a| and |b|.
+QuicPacketSequenceNumber Delta(QuicPacketSequenceNumber a,
+ QuicPacketSequenceNumber b) {
+ // Since these are unsigned numbers, we can't just return abs(a - b)
+ if (a < b) {
+ return b - a;
+ }
+ return a - b;
+}
+
+QuicPacketSequenceNumber ClosestTo(QuicPacketSequenceNumber target,
+ QuicPacketSequenceNumber a,
+ QuicPacketSequenceNumber b) {
+ return (Delta(target, a) < Delta(target, b)) ? a : b;
+}
+
+} // namespace
+
+QuicFramer::QuicFramer(QuicVersion version,
+ QuicTime creation_time,
+ bool is_server)
+ : visitor_(NULL),
+ fec_builder_(NULL),
+ error_(QUIC_NO_ERROR),
+ last_sequence_number_(0),
+ last_serialized_guid_(0),
+ quic_version_(version),
+ decrypter_(QuicDecrypter::Create(kNULL)),
+ alternative_decrypter_latch_(false),
+ is_server_(is_server),
+ creation_time_(creation_time) {
+ DCHECK(IsSupportedVersion(version));
+ encrypter_[ENCRYPTION_NONE].reset(QuicEncrypter::Create(kNULL));
+}
+
+QuicFramer::~QuicFramer() {}
+
+// static
+size_t QuicFramer::GetMinStreamFrameSize(QuicVersion version,
+ QuicStreamId stream_id,
+ QuicStreamOffset offset,
+ bool last_frame_in_packet) {
+ return kQuicFrameTypeSize + GetStreamIdSize(stream_id) +
+ GetStreamOffsetSize(offset) +
+ (last_frame_in_packet ? 0 : kQuicStreamPayloadLengthSize);
+}
+
+// static
+size_t QuicFramer::GetMinAckFrameSize() {
+ return kQuicFrameTypeSize + kQuicEntropyHashSize +
+ PACKET_6BYTE_SEQUENCE_NUMBER + kQuicEntropyHashSize +
+ PACKET_6BYTE_SEQUENCE_NUMBER + kQuicDeltaTimeLargestObservedSize +
+ kNumberOfMissingPacketsSize;
+}
+
+// static
+size_t QuicFramer::GetMinRstStreamFrameSize() {
+ return kQuicFrameTypeSize + kQuicMaxStreamIdSize + kQuicErrorCodeSize +
+ kQuicErrorDetailsLengthSize;
+}
+
+// static
+size_t QuicFramer::GetMinConnectionCloseFrameSize() {
+ return kQuicFrameTypeSize + kQuicErrorCodeSize + kQuicErrorDetailsLengthSize +
+ GetMinAckFrameSize();
+}
+
+// static
+size_t QuicFramer::GetMinGoAwayFrameSize() {
+ return kQuicFrameTypeSize + kQuicErrorCodeSize + kQuicErrorDetailsLengthSize +
+ kQuicMaxStreamIdSize;
+}
+
+// static
+// TODO(satyamshekhar): 16 - Crypto hash for integrity. Not a static value. Use
+// QuicEncrypter::GetMaxPlaintextSize.
+// 16 is a conservative estimate in the case of AEAD_AES_128_GCM_12, which uses
+// 12-byte tags.
+size_t QuicFramer::GetMaxUnackedPackets(QuicPacketHeader header) {
+ return (kMaxPacketSize - GetPacketHeaderSize(header) -
+ GetMinAckFrameSize() - 16) / PACKET_6BYTE_SEQUENCE_NUMBER;
+}
+
+// static
+size_t QuicFramer::GetStreamIdSize(QuicStreamId stream_id) {
+ // Sizes are 1 through 4 bytes.
+ for (int i = 1; i <= 4; ++i) {
+ stream_id >>= 8;
+ if (stream_id == 0) {
+ return i;
+ }
+ }
+ LOG(DFATAL) << "Failed to determine StreamIDSize.";
+ return 4;
+}
+
+// static
+size_t QuicFramer::GetStreamOffsetSize(QuicStreamOffset offset) {
+ // 0 is a special case.
+ if (offset == 0) {
+ return 0;
+ }
+ // 2 through 8 are the remaining sizes.
+ offset >>= 8;
+ for (int i = 2; i <= 8; ++i) {
+ offset >>= 8;
+ if (offset == 0) {
+ return i;
+ }
+ }
+ LOG(DFATAL) << "Failed to determine StreamOffsetSize.";
+ return 8;
+}
+
+// static
+size_t QuicFramer::GetVersionNegotiationPacketSize(size_t number_versions) {
+ return kPublicFlagsSize + PACKET_8BYTE_GUID +
+ number_versions * kQuicVersionSize;
+}
+
+// static
+bool QuicFramer::CanTruncate(const QuicFrame& frame, size_t free_bytes) {
+ // TODO(ianswett): GetMinConnectionCloseFrameSize may be incorrect, because
+ // checking for it here results in frames not being added, but the resulting
+ // frames do actually fit.
+ if ((frame.type == ACK_FRAME || frame.type == CONNECTION_CLOSE_FRAME) &&
+ free_bytes >= GetMinAckFrameSize()) {
+ return true;
+ }
+ return false;
+}
+
+bool QuicFramer::IsSupportedVersion(const QuicVersion version) const {
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ if (version == kSupportedQuicVersions[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t QuicFramer::GetSerializedFrameLength(
+ const QuicFrame& frame, size_t free_bytes, bool first_frame) {
+ if (frame.type == PADDING_FRAME) {
+ // PADDING implies end of packet.
+ return free_bytes;
+ }
+ // See if it fits as the non-last frame.
+ size_t frame_len = ComputeFrameLength(frame, false);
+ // STREAM frames save two bytes when they're the last frame in the packet.
+ if (frame_len > free_bytes && frame.type == STREAM_FRAME) {
+ frame_len = ComputeFrameLength(frame, true);
+ }
+ if (frame_len > free_bytes) {
+ // Only truncate the first frame in a packet, so if subsequent ones go
+ // over, stop including more frames.
+ if (!first_frame) {
+ return 0;
+ }
+ if (CanTruncate(frame, free_bytes)) {
+ // Truncate the frame so the packet will not exceed kMaxPacketSize.
+ // Note that we may not use every byte of the writer in this case.
+ DLOG(INFO) << "Truncating large frame";
+ return free_bytes;
+ }
+ }
+ return frame_len;
+}
+
+QuicPacketEntropyHash QuicFramer::GetPacketEntropyHash(
+ const QuicPacketHeader& header) const {
+ if (!header.entropy_flag) {
+ // TODO(satyamshekhar): Return some more better value here (something that
+ // is not a constant).
+ return 0;
+ }
+ return 1 << (header.packet_sequence_number % 8);
+}
+
+SerializedPacket QuicFramer::BuildUnsizedDataPacket(
+ const QuicPacketHeader& header,
+ const QuicFrames& frames) {
+ const size_t max_plaintext_size = GetMaxPlaintextSize(kMaxPacketSize);
+ size_t packet_size = GetPacketHeaderSize(header);
+ for (size_t i = 0; i < frames.size(); ++i) {
+ DCHECK_LE(packet_size, max_plaintext_size);
+ const size_t frame_size = GetSerializedFrameLength(
+ frames[i], max_plaintext_size - packet_size, i == 0);
+ DCHECK(frame_size);
+ packet_size += frame_size;
+ }
+ return BuildDataPacket(header, frames, packet_size);
+}
+
+SerializedPacket QuicFramer::BuildDataPacket(
+ const QuicPacketHeader& header,
+ const QuicFrames& frames,
+ size_t packet_size) {
+ QuicDataWriter writer(packet_size);
+ const SerializedPacket kNoPacket(0, NULL, 0, NULL);
+ if (!WritePacketHeader(header, &writer)) {
+ return kNoPacket;
+ }
+
+ for (size_t i = 0; i < frames.size(); ++i) {
+ const QuicFrame& frame = frames[i];
+
+ const bool last_frame_in_packet = i == (frames.size() - 1);
+ if (!AppendTypeByte(frame, last_frame_in_packet, &writer)) {
+ return kNoPacket;
+ }
+
+ switch (frame.type) {
+ case PADDING_FRAME:
+ writer.WritePadding();
+ break;
+ case STREAM_FRAME:
+ if (!AppendStreamFramePayload(
+ *frame.stream_frame, last_frame_in_packet, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ case ACK_FRAME:
+ if (!AppendAckFramePayload(*frame.ack_frame, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ case CONGESTION_FEEDBACK_FRAME:
+ if (!AppendQuicCongestionFeedbackFramePayload(
+ *frame.congestion_feedback_frame, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ case RST_STREAM_FRAME:
+ if (!AppendRstStreamFramePayload(*frame.rst_stream_frame, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ case CONNECTION_CLOSE_FRAME:
+ if (!AppendConnectionCloseFramePayload(
+ *frame.connection_close_frame, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ case GOAWAY_FRAME:
+ if (!AppendGoAwayFramePayload(*frame.goaway_frame, &writer)) {
+ return kNoPacket;
+ }
+ break;
+ default:
+ RaiseError(QUIC_INVALID_FRAME_DATA);
+ return kNoPacket;
+ }
+ }
+
+ // Save the length before writing, because take clears it.
+ const size_t len = writer.length();
+ // Less than or equal because truncated acks end up with max_plaintex_size
+ // length, even though they're typically slightly shorter.
+ DCHECK_LE(len, packet_size);
+ QuicPacket* packet = QuicPacket::NewDataPacket(
+ writer.take(), len, true, header.public_header.guid_length,
+ header.public_header.version_flag,
+ header.public_header.sequence_number_length);
+
+ if (fec_builder_) {
+ fec_builder_->OnBuiltFecProtectedPayload(header,
+ packet->FecProtectedData());
+ }
+
+ return SerializedPacket(header.packet_sequence_number, packet,
+ GetPacketEntropyHash(header), NULL);
+}
+
+SerializedPacket QuicFramer::BuildFecPacket(const QuicPacketHeader& header,
+ const QuicFecData& fec) {
+ DCHECK_EQ(IN_FEC_GROUP, header.is_in_fec_group);
+ DCHECK_NE(0u, header.fec_group);
+ size_t len = GetPacketHeaderSize(header);
+ len += fec.redundancy.length();
+
+ QuicDataWriter writer(len);
+ SerializedPacket kNoPacket = SerializedPacket(0, NULL, 0, NULL);
+ if (!WritePacketHeader(header, &writer)) {
+ return kNoPacket;
+ }
+
+ if (!writer.WriteBytes(fec.redundancy.data(), fec.redundancy.length())) {
+ return kNoPacket;
+ }
+
+ return SerializedPacket(
+ header.packet_sequence_number,
+ QuicPacket::NewFecPacket(writer.take(), len, true,
+ header.public_header.guid_length,
+ header.public_header.version_flag,
+ header.public_header.sequence_number_length),
+ GetPacketEntropyHash(header), NULL);
+}
+
+// static
+QuicEncryptedPacket* QuicFramer::BuildPublicResetPacket(
+ const QuicPublicResetPacket& packet) {
+ DCHECK(packet.public_header.reset_flag);
+ size_t len = GetPublicResetPacketSize();
+ QuicDataWriter writer(len);
+
+ uint8 flags = static_cast<uint8>(PACKET_PUBLIC_FLAGS_RST |
+ PACKET_PUBLIC_FLAGS_8BYTE_GUID |
+ PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE);
+ if (!writer.WriteUInt8(flags)) {
+ return NULL;
+ }
+
+ if (!writer.WriteUInt64(packet.public_header.guid)) {
+ return NULL;
+ }
+
+ if (!writer.WriteUInt64(packet.nonce_proof)) {
+ return NULL;
+ }
+
+ if (!AppendPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ packet.rejected_sequence_number,
+ &writer)) {
+ return NULL;
+ }
+
+ return new QuicEncryptedPacket(writer.take(), len, true);
+}
+
+QuicEncryptedPacket* QuicFramer::BuildVersionNegotiationPacket(
+ const QuicPacketPublicHeader& header,
+ const QuicVersionVector& supported_versions) {
+ DCHECK(header.version_flag);
+ size_t len = GetVersionNegotiationPacketSize(supported_versions.size());
+ QuicDataWriter writer(len);
+
+ uint8 flags = static_cast<uint8>(PACKET_PUBLIC_FLAGS_VERSION |
+ PACKET_PUBLIC_FLAGS_8BYTE_GUID |
+ PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE);
+ if (!writer.WriteUInt8(flags)) {
+ return NULL;
+ }
+
+ if (!writer.WriteUInt64(header.guid)) {
+ return NULL;
+ }
+
+ for (size_t i = 0; i < supported_versions.size(); ++i) {
+ if (!writer.WriteUInt32(QuicVersionToQuicTag(supported_versions[i]))) {
+ return NULL;
+ }
+ }
+
+ return new QuicEncryptedPacket(writer.take(), len, true);
+}
+
+bool QuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
+ // TODO(satyamshekhar): Don't RaiseError (and close the connection) for
+ // invalid (unauthenticated) packets.
+ DCHECK(!reader_.get());
+ reader_.reset(new QuicDataReader(packet.data(), packet.length()));
+
+ visitor_->OnPacket();
+
+ // First parse the public header.
+ QuicPacketPublicHeader public_header;
+ if (!ProcessPublicHeader(&public_header)) {
+ DLOG(WARNING) << "Unable to process public header.";
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+
+ if (is_server_ && public_header.version_flag &&
+ public_header.versions[0] != quic_version_) {
+ if (!visitor_->OnProtocolVersionMismatch(public_header.versions[0])) {
+ reader_.reset(NULL);
+ return true;
+ }
+ }
+
+ bool rv;
+ if (!is_server_ && public_header.version_flag) {
+ rv = ProcessVersionNegotiationPacket(&public_header);
+ } else if (public_header.reset_flag) {
+ rv = ProcessPublicResetPacket(public_header);
+ } else {
+ rv = ProcessDataPacket(public_header, packet);
+ }
+
+ reader_.reset(NULL);
+ return rv;
+}
+
+bool QuicFramer::ProcessVersionNegotiationPacket(
+ QuicPacketPublicHeader* public_header) {
+ DCHECK(!is_server_);
+ // Try reading at least once to raise error if the packet is invalid.
+ do {
+ QuicTag version;
+ if (!reader_->ReadBytes(&version, kQuicVersionSize)) {
+ set_detailed_error("Unable to read supported version in negotiation.");
+ return RaiseError(QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+ }
+ public_header->versions.push_back(QuicTagToQuicVersion(version));
+ } while (!reader_->IsDoneReading());
+
+ visitor_->OnVersionNegotiationPacket(*public_header);
+ return true;
+}
+
+bool QuicFramer::ProcessDataPacket(
+ const QuicPacketPublicHeader& public_header,
+ const QuicEncryptedPacket& packet) {
+ QuicPacketHeader header(public_header);
+ if (!ProcessPacketHeader(&header, packet)) {
+ DCHECK_NE(QUIC_NO_ERROR, error_); // ProcessPacketHeader sets the error.
+ DLOG(WARNING) << "Unable to process data packet header.";
+ return false;
+ }
+
+ if (!visitor_->OnPacketHeader(header)) {
+ // The visitor suppresses further processing of the packet.
+ return true;
+ }
+
+ if (packet.length() > kMaxPacketSize) {
+ DLOG(WARNING) << "Packet too large: " << packet.length();
+ return RaiseError(QUIC_PACKET_TOO_LARGE);
+ }
+
+ // Handle the payload.
+ if (!header.fec_flag) {
+ if (header.is_in_fec_group == IN_FEC_GROUP) {
+ StringPiece payload = reader_->PeekRemainingPayload();
+ visitor_->OnFecProtectedPayload(payload);
+ }
+ if (!ProcessFrameData()) {
+ DCHECK_NE(QUIC_NO_ERROR, error_); // ProcessFrameData sets the error.
+ DLOG(WARNING) << "Unable to process frame data.";
+ return false;
+ }
+ } else {
+ QuicFecData fec_data;
+ fec_data.fec_group = header.fec_group;
+ fec_data.redundancy = reader_->ReadRemainingPayload();
+ visitor_->OnFecData(fec_data);
+ }
+
+ visitor_->OnPacketComplete();
+ return true;
+}
+
+bool QuicFramer::ProcessPublicResetPacket(
+ const QuicPacketPublicHeader& public_header) {
+ QuicPublicResetPacket packet(public_header);
+ if (!reader_->ReadUInt64(&packet.nonce_proof)) {
+ set_detailed_error("Unable to read nonce proof.");
+ return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+ }
+ // TODO(satyamshekhar): validate nonce to protect against DoS.
+
+ if (!reader_->ReadUInt48(&packet.rejected_sequence_number)) {
+ set_detailed_error("Unable to read rejected sequence number.");
+ return RaiseError(QUIC_INVALID_PUBLIC_RST_PACKET);
+ }
+ visitor_->OnPublicResetPacket(packet);
+ return true;
+}
+
+bool QuicFramer::ProcessRevivedPacket(QuicPacketHeader* header,
+ StringPiece payload) {
+ DCHECK(!reader_.get());
+
+ visitor_->OnRevivedPacket();
+
+ header->entropy_hash = GetPacketEntropyHash(*header);
+
+ if (!visitor_->OnPacketHeader(*header)) {
+ return true;
+ }
+
+ if (payload.length() > kMaxPacketSize) {
+ set_detailed_error("Revived packet too large.");
+ return RaiseError(QUIC_PACKET_TOO_LARGE);
+ }
+
+ reader_.reset(new QuicDataReader(payload.data(), payload.length()));
+ if (!ProcessFrameData()) {
+ DCHECK_NE(QUIC_NO_ERROR, error_); // ProcessFrameData sets the error.
+ DLOG(WARNING) << "Unable to process frame data.";
+ return false;
+ }
+
+ visitor_->OnPacketComplete();
+ reader_.reset(NULL);
+ return true;
+}
+
+bool QuicFramer::WritePacketHeader(const QuicPacketHeader& header,
+ QuicDataWriter* writer) {
+ DCHECK(header.fec_group > 0 || header.is_in_fec_group == NOT_IN_FEC_GROUP);
+ uint8 public_flags = 0;
+ if (header.public_header.reset_flag) {
+ public_flags |= PACKET_PUBLIC_FLAGS_RST;
+ }
+ if (header.public_header.version_flag) {
+ public_flags |= PACKET_PUBLIC_FLAGS_VERSION;
+ }
+ switch (header.public_header.sequence_number_length) {
+ case PACKET_1BYTE_SEQUENCE_NUMBER:
+ public_flags |= PACKET_PUBLIC_FLAGS_1BYTE_SEQUENCE;
+ break;
+ case PACKET_2BYTE_SEQUENCE_NUMBER:
+ public_flags |= PACKET_PUBLIC_FLAGS_2BYTE_SEQUENCE;
+ break;
+ case PACKET_4BYTE_SEQUENCE_NUMBER:
+ public_flags |= PACKET_PUBLIC_FLAGS_4BYTE_SEQUENCE;
+ break;
+ case PACKET_6BYTE_SEQUENCE_NUMBER:
+ public_flags |= PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE;
+ break;
+ }
+
+ switch (header.public_header.guid_length) {
+ case PACKET_0BYTE_GUID:
+ if (!writer->WriteUInt8(public_flags | PACKET_PUBLIC_FLAGS_0BYTE_GUID)) {
+ return false;
+ }
+ break;
+ case PACKET_1BYTE_GUID:
+ if (!writer->WriteUInt8(public_flags | PACKET_PUBLIC_FLAGS_1BYTE_GUID)) {
+ return false;
+ }
+ if (!writer->WriteUInt8(header.public_header.guid & k1ByteGuidMask)) {
+ return false;
+ }
+ break;
+ case PACKET_4BYTE_GUID:
+ if (!writer->WriteUInt8(public_flags | PACKET_PUBLIC_FLAGS_4BYTE_GUID)) {
+ return false;
+ }
+ if (!writer->WriteUInt32(header.public_header.guid & k4ByteGuidMask)) {
+ return false;
+ }
+ break;
+ case PACKET_8BYTE_GUID:
+ if (!writer->WriteUInt8(public_flags | PACKET_PUBLIC_FLAGS_8BYTE_GUID)) {
+ return false;
+ }
+ if (!writer->WriteUInt64(header.public_header.guid)) {
+ return false;
+ }
+ break;
+ }
+ last_serialized_guid_ = header.public_header.guid;
+
+ if (header.public_header.version_flag) {
+ DCHECK(!is_server_);
+ writer->WriteUInt32(QuicVersionToQuicTag(quic_version_));
+ }
+
+ if (!AppendPacketSequenceNumber(header.public_header.sequence_number_length,
+ header.packet_sequence_number, writer)) {
+ return false;
+ }
+
+ uint8 private_flags = 0;
+ if (header.entropy_flag) {
+ private_flags |= PACKET_PRIVATE_FLAGS_ENTROPY;
+ }
+ if (header.is_in_fec_group == IN_FEC_GROUP) {
+ private_flags |= PACKET_PRIVATE_FLAGS_FEC_GROUP;
+ }
+ if (header.fec_flag) {
+ private_flags |= PACKET_PRIVATE_FLAGS_FEC;
+ }
+ if (!writer->WriteUInt8(private_flags)) {
+ return false;
+ }
+
+ // The FEC group number is the sequence number of the first fec
+ // protected packet, or 0 if this packet is not protected.
+ if (header.is_in_fec_group == IN_FEC_GROUP) {
+ DCHECK_GE(header.packet_sequence_number, header.fec_group);
+ DCHECK_GT(255u, header.packet_sequence_number - header.fec_group);
+ // Offset from the current packet sequence number to the first fec
+ // protected packet.
+ uint8 first_fec_protected_packet_offset =
+ header.packet_sequence_number - header.fec_group;
+ if (!writer->WriteBytes(&first_fec_protected_packet_offset, 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+QuicPacketSequenceNumber QuicFramer::CalculatePacketSequenceNumberFromWire(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number) const {
+ // The new sequence number might have wrapped to the next epoch, or
+ // it might have reverse wrapped to the previous epoch, or it might
+ // remain in the same epoch. Select the sequence number closest to the
+ // next expected sequence number, the previous sequence number plus 1.
+
+ // epoch_delta is the delta between epochs the sequence number was serialized
+ // with, so the correct value is likely the same epoch as the last sequence
+ // number or an adjacent epoch.
+ const QuicPacketSequenceNumber epoch_delta =
+ GG_UINT64_C(1) << (8 * sequence_number_length);
+ QuicPacketSequenceNumber next_sequence_number = last_sequence_number_ + 1;
+ QuicPacketSequenceNumber epoch = last_sequence_number_ & ~(epoch_delta - 1);
+ QuicPacketSequenceNumber prev_epoch = epoch - epoch_delta;
+ QuicPacketSequenceNumber next_epoch = epoch + epoch_delta;
+
+ return ClosestTo(next_sequence_number,
+ epoch + packet_sequence_number,
+ ClosestTo(next_sequence_number,
+ prev_epoch + packet_sequence_number,
+ next_epoch + packet_sequence_number));
+}
+
+bool QuicFramer::ProcessPublicHeader(
+ QuicPacketPublicHeader* public_header) {
+ uint8 public_flags;
+ if (!reader_->ReadBytes(&public_flags, 1)) {
+ set_detailed_error("Unable to read public flags.");
+ return false;
+ }
+
+ public_header->reset_flag = (public_flags & PACKET_PUBLIC_FLAGS_RST) != 0;
+ public_header->version_flag =
+ (public_flags & PACKET_PUBLIC_FLAGS_VERSION) != 0;
+
+ if (!public_header->version_flag && public_flags > PACKET_PUBLIC_FLAGS_MAX) {
+ set_detailed_error("Illegal public flags value.");
+ return false;
+ }
+
+ if (public_header->reset_flag && public_header->version_flag) {
+ set_detailed_error("Got version flag in reset packet");
+ return false;
+ }
+
+ switch (public_flags & PACKET_PUBLIC_FLAGS_8BYTE_GUID) {
+ case PACKET_PUBLIC_FLAGS_8BYTE_GUID:
+ if (!reader_->ReadUInt64(&public_header->guid)) {
+ set_detailed_error("Unable to read GUID.");
+ return false;
+ }
+ public_header->guid_length = PACKET_8BYTE_GUID;
+ break;
+ case PACKET_PUBLIC_FLAGS_4BYTE_GUID:
+ // If the guid is truncated, expect to read the last serialized guid.
+ if (!reader_->ReadBytes(&public_header->guid, PACKET_4BYTE_GUID)) {
+ set_detailed_error("Unable to read GUID.");
+ return false;
+ }
+ if ((public_header->guid & k4ByteGuidMask) !=
+ (last_serialized_guid_ & k4ByteGuidMask)) {
+ set_detailed_error(
+ "Truncated 4 byte GUID does not match previous guid.");
+ return false;
+ }
+ public_header->guid_length = PACKET_4BYTE_GUID;
+ public_header->guid = last_serialized_guid_;
+ break;
+ case PACKET_PUBLIC_FLAGS_1BYTE_GUID:
+ if (!reader_->ReadBytes(&public_header->guid, PACKET_1BYTE_GUID)) {
+ set_detailed_error("Unable to read GUID.");
+ return false;
+ }
+ if ((public_header->guid & k1ByteGuidMask) !=
+ (last_serialized_guid_ & k1ByteGuidMask)) {
+ set_detailed_error(
+ "Truncated 1 byte GUID does not match previous guid.");
+ return false;
+ }
+ public_header->guid_length = PACKET_1BYTE_GUID;
+ public_header->guid = last_serialized_guid_;
+ break;
+ case PACKET_PUBLIC_FLAGS_0BYTE_GUID:
+ public_header->guid_length = PACKET_0BYTE_GUID;
+ public_header->guid = last_serialized_guid_;
+ break;
+ }
+
+ switch (public_flags & PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE) {
+ case PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE:
+ public_header->sequence_number_length = PACKET_6BYTE_SEQUENCE_NUMBER;
+ break;
+ case PACKET_PUBLIC_FLAGS_4BYTE_SEQUENCE:
+ public_header->sequence_number_length = PACKET_4BYTE_SEQUENCE_NUMBER;
+ break;
+ case PACKET_PUBLIC_FLAGS_2BYTE_SEQUENCE:
+ public_header->sequence_number_length = PACKET_2BYTE_SEQUENCE_NUMBER;
+ break;
+ case PACKET_PUBLIC_FLAGS_1BYTE_SEQUENCE:
+ public_header->sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+ break;
+ }
+
+ // Read the version only if the packet is from the client.
+ // version flag from the server means version negotiation packet.
+ if (public_header->version_flag && is_server_) {
+ QuicTag version_tag;
+ if (!reader_->ReadUInt32(&version_tag)) {
+ set_detailed_error("Unable to read protocol version.");
+ return false;
+ }
+
+ // If the version from the new packet is the same as the version of this
+ // framer, then the public flags should be set to something we understand.
+ // If not, this raises an error.
+ QuicVersion version = QuicTagToQuicVersion(version_tag);
+ if (version == quic_version_ && public_flags > PACKET_PUBLIC_FLAGS_MAX) {
+ set_detailed_error("Illegal public flags value.");
+ return false;
+ }
+ public_header->versions.push_back(version);
+ }
+ return true;
+}
+
+// static
+bool QuicFramer::ReadGuidFromPacket(const QuicEncryptedPacket& packet,
+ QuicGuid* guid) {
+ QuicDataReader reader(packet.data(), packet.length());
+ uint8 public_flags;
+ if (!reader.ReadBytes(&public_flags, 1)) {
+ return false;
+ }
+ // Ensure it's an 8 byte guid.
+ if ((public_flags & PACKET_PUBLIC_FLAGS_8BYTE_GUID) !=
+ PACKET_PUBLIC_FLAGS_8BYTE_GUID) {
+ return false;
+ }
+
+ return reader.ReadUInt64(guid);
+}
+
+bool QuicFramer::ProcessPacketHeader(
+ QuicPacketHeader* header,
+ const QuicEncryptedPacket& packet) {
+ if (!ProcessPacketSequenceNumber(header->public_header.sequence_number_length,
+ &header->packet_sequence_number)) {
+ set_detailed_error("Unable to read sequence number.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+
+ if (header->packet_sequence_number == 0u) {
+ set_detailed_error("Packet sequence numbers cannot be 0.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+
+ if (!DecryptPayload(*header, packet)) {
+ set_detailed_error("Unable to decrypt payload.");
+ return RaiseError(QUIC_DECRYPTION_FAILURE);
+ }
+
+ uint8 private_flags;
+ if (!reader_->ReadBytes(&private_flags, 1)) {
+ set_detailed_error("Unable to read private flags.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+
+ if (private_flags > PACKET_PRIVATE_FLAGS_MAX) {
+ set_detailed_error("Illegal private flags value.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+
+ header->entropy_flag = (private_flags & PACKET_PRIVATE_FLAGS_ENTROPY) != 0;
+ header->fec_flag = (private_flags & PACKET_PRIVATE_FLAGS_FEC) != 0;
+
+ if ((private_flags & PACKET_PRIVATE_FLAGS_FEC_GROUP) != 0) {
+ header->is_in_fec_group = IN_FEC_GROUP;
+ uint8 first_fec_protected_packet_offset;
+ if (!reader_->ReadBytes(&first_fec_protected_packet_offset, 1)) {
+ set_detailed_error("Unable to read first fec protected packet offset.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+ if (first_fec_protected_packet_offset >= header->packet_sequence_number) {
+ set_detailed_error("First fec protected packet offset must be less "
+ "than the sequence number.");
+ return RaiseError(QUIC_INVALID_PACKET_HEADER);
+ }
+ header->fec_group =
+ header->packet_sequence_number - first_fec_protected_packet_offset;
+ }
+
+ header->entropy_hash = GetPacketEntropyHash(*header);
+ // Set the last sequence number after we have decrypted the packet
+ // so we are confident is not attacker controlled.
+ last_sequence_number_ = header->packet_sequence_number;
+ return true;
+}
+
+bool QuicFramer::ProcessPacketSequenceNumber(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber* sequence_number) {
+ QuicPacketSequenceNumber wire_sequence_number = 0u;
+ if (!reader_->ReadBytes(&wire_sequence_number, sequence_number_length)) {
+ return false;
+ }
+
+ // TODO(ianswett): Explore the usefulness of trying multiple sequence numbers
+ // in case the first guess is incorrect.
+ *sequence_number =
+ CalculatePacketSequenceNumberFromWire(sequence_number_length,
+ wire_sequence_number);
+ return true;
+}
+
+bool QuicFramer::ProcessFrameData() {
+ if (reader_->IsDoneReading()) {
+ set_detailed_error("Unable to read frame type.");
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+ while (!reader_->IsDoneReading()) {
+ uint8 frame_type;
+ if (!reader_->ReadBytes(&frame_type, 1)) {
+ set_detailed_error("Unable to read frame type.");
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+
+ if ((frame_type & kQuicFrameType0BitMask) == 0) {
+ QuicStreamFrame frame;
+ if (!ProcessStreamFrame(frame_type, &frame)) {
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+ if (!visitor_->OnStreamFrame(frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ frame_type >>= 1;
+ if ((frame_type & kQuicFrameType0BitMask) == 0) {
+ QuicAckFrame frame;
+ if (!ProcessAckFrame(&frame)) {
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+ if (!visitor_->OnAckFrame(frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ frame_type >>= 1;
+ if ((frame_type & kQuicFrameType0BitMask) == 0) {
+ QuicCongestionFeedbackFrame frame;
+ if (!ProcessQuicCongestionFeedbackFrame(&frame)) {
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+ if (!visitor_->OnCongestionFeedbackFrame(frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ frame_type >>= 1;
+
+ switch (frame_type) {
+ // STREAM_FRAME, ACK_FRAME, and CONGESTION_FEEDBACK_FRAME are handled
+ // above.
+ case PADDING_FRAME:
+ // We're done with the packet
+ return true;
+
+ case RST_STREAM_FRAME: {
+ QuicRstStreamFrame frame;
+ if (!ProcessRstStreamFrame(&frame)) {
+ return RaiseError(QUIC_INVALID_RST_STREAM_DATA);
+ }
+ if (!visitor_->OnRstStreamFrame(frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ case CONNECTION_CLOSE_FRAME: {
+ QuicConnectionCloseFrame frame;
+ if (!ProcessConnectionCloseFrame(&frame)) {
+ return RaiseError(QUIC_INVALID_CONNECTION_CLOSE_DATA);
+ }
+
+ if (!visitor_->OnAckFrame(frame.ack_frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+
+ if (!visitor_->OnConnectionCloseFrame(frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ case GOAWAY_FRAME: {
+ QuicGoAwayFrame goaway_frame;
+ if (!ProcessGoAwayFrame(&goaway_frame)) {
+ return RaiseError(QUIC_INVALID_GOAWAY_DATA);
+ }
+ if (!visitor_->OnGoAwayFrame(goaway_frame)) {
+ DLOG(INFO) << "Visitor asked to stop further processing.";
+ // Returning true since there was no parsing error.
+ return true;
+ }
+ continue;
+ }
+
+ set_detailed_error("Illegal frame type.");
+ DLOG(WARNING) << "Illegal frame type: "
+ << static_cast<int>(frame_type);
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessStreamFrame(uint8 frame_type,
+ QuicStreamFrame* frame) {
+ uint8 stream_flags = frame_type >> 1;
+ // Read from right to left: StreamID, Offset, Data Length, Fin.
+ const uint8 stream_id_length = (stream_flags & kQuicStreamIDLengthMask) + 1;
+ stream_flags >>= kQuicStreamIdShift;
+
+ uint8 offset_length = (stream_flags & kQuicStreamOffsetMask);
+ // There is no encoding for 1 byte, only 0 and 2 through 8.
+ if (offset_length > 0) {
+ offset_length += 1;
+ }
+ stream_flags >>= kQuicStreamOffsetShift;
+
+ bool has_data_length =
+ (stream_flags & kQuicStreamDataLengthMask) == kQuicStreamDataLengthMask;
+ stream_flags >>= kQuicStreamDataLengthShift;
+
+ frame->fin = (stream_flags & kQuicStreamFinMask) == kQuicStreamFinShift;
+
+ frame->stream_id = 0;
+ if (!reader_->ReadBytes(&frame->stream_id, stream_id_length)) {
+ set_detailed_error("Unable to read stream_id.");
+ return false;
+ }
+
+ frame->offset = 0;
+ if (!reader_->ReadBytes(&frame->offset, offset_length)) {
+ set_detailed_error("Unable to read offset.");
+ return false;
+ }
+
+ if (has_data_length) {
+ if (!reader_->ReadStringPiece16(&frame->data)) {
+ set_detailed_error("Unable to read frame data.");
+ return false;
+ }
+ } else {
+ if (!reader_->ReadStringPiece(&frame->data, reader_->BytesRemaining())) {
+ set_detailed_error("Unable to read frame data.");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessAckFrame(QuicAckFrame* frame) {
+ if (!ProcessSentInfo(&frame->sent_info)) {
+ return false;
+ }
+ if (!ProcessReceivedInfo(&frame->received_info)) {
+ return false;
+ }
+ return true;
+}
+
+bool QuicFramer::ProcessReceivedInfo(ReceivedPacketInfo* received_info) {
+ if (!reader_->ReadBytes(&received_info->entropy_hash, 1)) {
+ set_detailed_error("Unable to read entropy hash for received packets.");
+ return false;
+ }
+
+ if (!ProcessPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ &received_info->largest_observed)) {
+ set_detailed_error("Unable to read largest observed.");
+ return false;
+ }
+
+ uint32 delta_time_largest_observed_us;
+ if (!reader_->ReadUInt32(&delta_time_largest_observed_us)) {
+ set_detailed_error("Unable to read delta time largest observed.");
+ return false;
+ }
+
+ if (delta_time_largest_observed_us == kInvalidDeltaTime) {
+ received_info->delta_time_largest_observed = QuicTime::Delta::Infinite();
+ } else {
+ received_info->delta_time_largest_observed =
+ QuicTime::Delta::FromMicroseconds(delta_time_largest_observed_us);
+ }
+
+ uint8 num_missing_packets;
+ if (!reader_->ReadBytes(&num_missing_packets, 1)) {
+ set_detailed_error("Unable to read num missing packets.");
+ return false;
+ }
+
+ for (int i = 0; i < num_missing_packets; ++i) {
+ QuicPacketSequenceNumber sequence_number;
+ if (!ProcessPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ &sequence_number)) {
+ set_detailed_error("Unable to read sequence number in missing packets.");
+ return false;
+ }
+ received_info->missing_packets.insert(sequence_number);
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessSentInfo(SentPacketInfo* sent_info) {
+ if (!reader_->ReadBytes(&sent_info->entropy_hash, 1)) {
+ set_detailed_error("Unable to read entropy hash for sent packets.");
+ return false;
+ }
+
+ if (!ProcessPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ &sent_info->least_unacked)) {
+ set_detailed_error("Unable to read least unacked.");
+ return false;
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessQuicCongestionFeedbackFrame(
+ QuicCongestionFeedbackFrame* frame) {
+ uint8 feedback_type;
+ if (!reader_->ReadBytes(&feedback_type, 1)) {
+ set_detailed_error("Unable to read congestion feedback type.");
+ return false;
+ }
+ frame->type =
+ static_cast<CongestionFeedbackType>(feedback_type);
+
+ switch (frame->type) {
+ case kInterArrival: {
+ CongestionFeedbackMessageInterArrival* inter_arrival =
+ &frame->inter_arrival;
+ if (!reader_->ReadUInt16(
+ &inter_arrival->accumulated_number_of_lost_packets)) {
+ set_detailed_error(
+ "Unable to read accumulated number of lost packets.");
+ return false;
+ }
+ uint8 num_received_packets;
+ if (!reader_->ReadBytes(&num_received_packets, 1)) {
+ set_detailed_error("Unable to read num received packets.");
+ return false;
+ }
+
+ if (num_received_packets > 0u) {
+ uint64 smallest_received;
+ if (!ProcessPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ &smallest_received)) {
+ set_detailed_error("Unable to read smallest received.");
+ return false;
+ }
+
+ uint64 time_received_us;
+ if (!reader_->ReadUInt64(&time_received_us)) {
+ set_detailed_error("Unable to read time received.");
+ return false;
+ }
+ QuicTime time_received = creation_time_.Add(
+ QuicTime::Delta::FromMicroseconds(time_received_us));
+
+ inter_arrival->received_packet_times.insert(
+ make_pair(smallest_received, time_received));
+
+ for (int i = 0; i < num_received_packets - 1; ++i) {
+ uint16 sequence_delta;
+ if (!reader_->ReadUInt16(&sequence_delta)) {
+ set_detailed_error(
+ "Unable to read sequence delta in received packets.");
+ return false;
+ }
+
+ int32 time_delta_us;
+ if (!reader_->ReadBytes(&time_delta_us, sizeof(time_delta_us))) {
+ set_detailed_error(
+ "Unable to read time delta in received packets.");
+ return false;
+ }
+ QuicPacketSequenceNumber packet = smallest_received + sequence_delta;
+ inter_arrival->received_packet_times.insert(
+ make_pair(packet, time_received.Add(
+ QuicTime::Delta::FromMicroseconds(time_delta_us))));
+ }
+ }
+ break;
+ }
+ case kFixRate: {
+ uint32 bitrate = 0;
+ if (!reader_->ReadUInt32(&bitrate)) {
+ set_detailed_error("Unable to read bitrate.");
+ return false;
+ }
+ frame->fix_rate.bitrate = QuicBandwidth::FromBytesPerSecond(bitrate);
+ break;
+ }
+ case kTCP: {
+ CongestionFeedbackMessageTCP* tcp = &frame->tcp;
+ if (!reader_->ReadUInt16(&tcp->accumulated_number_of_lost_packets)) {
+ set_detailed_error(
+ "Unable to read accumulated number of lost packets.");
+ return false;
+ }
+ uint16 receive_window = 0;
+ if (!reader_->ReadUInt16(&receive_window)) {
+ set_detailed_error("Unable to read receive window.");
+ return false;
+ }
+ // Simple bit packing, don't send the 4 least significant bits.
+ tcp->receive_window = static_cast<QuicByteCount>(receive_window) << 4;
+ break;
+ }
+ default:
+ set_detailed_error("Illegal congestion feedback type.");
+ DLOG(WARNING) << "Illegal congestion feedback type: "
+ << frame->type;
+ return RaiseError(QUIC_INVALID_FRAME_DATA);
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessRstStreamFrame(QuicRstStreamFrame* frame) {
+ if (!reader_->ReadUInt32(&frame->stream_id)) {
+ set_detailed_error("Unable to read stream_id.");
+ return false;
+ }
+
+ uint32 error_code;
+ if (!reader_->ReadUInt32(&error_code)) {
+ set_detailed_error("Unable to read rst stream error code.");
+ return false;
+ }
+
+ if (error_code >= QUIC_STREAM_LAST_ERROR ||
+ error_code < QUIC_STREAM_NO_ERROR) {
+ set_detailed_error("Invalid rst stream error code.");
+ return false;
+ }
+
+ frame->error_code = static_cast<QuicRstStreamErrorCode>(error_code);
+
+ StringPiece error_details;
+ if (!reader_->ReadStringPiece16(&error_details)) {
+ set_detailed_error("Unable to read rst stream error details.");
+ return false;
+ }
+ frame->error_details = error_details.as_string();
+
+ return true;
+}
+
+bool QuicFramer::ProcessConnectionCloseFrame(QuicConnectionCloseFrame* frame) {
+ uint32 error_code;
+ if (!reader_->ReadUInt32(&error_code)) {
+ set_detailed_error("Unable to read connection close error code.");
+ return false;
+ }
+
+ if (error_code >= QUIC_LAST_ERROR ||
+ error_code < QUIC_NO_ERROR) {
+ set_detailed_error("Invalid error code.");
+ return false;
+ }
+
+ frame->error_code = static_cast<QuicErrorCode>(error_code);
+
+ StringPiece error_details;
+ if (!reader_->ReadStringPiece16(&error_details)) {
+ set_detailed_error("Unable to read connection close error details.");
+ return false;
+ }
+ frame->error_details = error_details.as_string();
+
+ if (!ProcessAckFrame(&frame->ack_frame)) {
+ DLOG(WARNING) << "Unable to process ack frame.";
+ return false;
+ }
+
+ return true;
+}
+
+bool QuicFramer::ProcessGoAwayFrame(QuicGoAwayFrame* frame) {
+ uint32 error_code;
+ if (!reader_->ReadUInt32(&error_code)) {
+ set_detailed_error("Unable to read go away error code.");
+ return false;
+ }
+ frame->error_code = static_cast<QuicErrorCode>(error_code);
+
+ if (error_code >= QUIC_LAST_ERROR ||
+ error_code < QUIC_NO_ERROR) {
+ set_detailed_error("Invalid error code.");
+ return false;
+ }
+
+ uint32 stream_id;
+ if (!reader_->ReadUInt32(&stream_id)) {
+ set_detailed_error("Unable to read last good stream id.");
+ return false;
+ }
+ frame->last_good_stream_id = static_cast<QuicStreamId>(stream_id);
+
+ StringPiece reason_phrase;
+ if (!reader_->ReadStringPiece16(&reason_phrase)) {
+ set_detailed_error("Unable to read goaway reason.");
+ return false;
+ }
+ frame->reason_phrase = reason_phrase.as_string();
+
+ return true;
+}
+
+// static
+StringPiece QuicFramer::GetAssociatedDataFromEncryptedPacket(
+ const QuicEncryptedPacket& encrypted,
+ QuicGuidLength guid_length,
+ bool includes_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return StringPiece(encrypted.data() + kStartOfHashData,
+ GetStartOfEncryptedData(
+ guid_length, includes_version, sequence_number_length)
+ - kStartOfHashData);
+}
+
+void QuicFramer::SetDecrypter(QuicDecrypter* decrypter) {
+ DCHECK(alternative_decrypter_.get() == NULL);
+ decrypter_.reset(decrypter);
+}
+
+void QuicFramer::SetAlternativeDecrypter(QuicDecrypter* decrypter,
+ bool latch_once_used) {
+ alternative_decrypter_.reset(decrypter);
+ alternative_decrypter_latch_ = latch_once_used;
+}
+
+const QuicDecrypter* QuicFramer::decrypter() const {
+ return decrypter_.get();
+}
+
+const QuicDecrypter* QuicFramer::alternative_decrypter() const {
+ return alternative_decrypter_.get();
+}
+
+void QuicFramer::SetEncrypter(EncryptionLevel level,
+ QuicEncrypter* encrypter) {
+ DCHECK_GE(level, 0);
+ DCHECK_LT(level, NUM_ENCRYPTION_LEVELS);
+ encrypter_[level].reset(encrypter);
+}
+
+const QuicEncrypter* QuicFramer::encrypter(EncryptionLevel level) const {
+ DCHECK_GE(level, 0);
+ DCHECK_LT(level, NUM_ENCRYPTION_LEVELS);
+ DCHECK(encrypter_[level].get() != NULL);
+ return encrypter_[level].get();
+}
+
+void QuicFramer::SwapCryptersForTest(QuicFramer* other) {
+ for (int i = ENCRYPTION_NONE; i < NUM_ENCRYPTION_LEVELS; i++) {
+ encrypter_[i].swap(other->encrypter_[i]);
+ }
+ decrypter_.swap(other->decrypter_);
+ alternative_decrypter_.swap(other->alternative_decrypter_);
+
+ const bool other_latch = other->alternative_decrypter_latch_;
+ other->alternative_decrypter_latch_ = alternative_decrypter_latch_;
+ alternative_decrypter_latch_ = other_latch;
+}
+
+QuicEncryptedPacket* QuicFramer::EncryptPacket(
+ EncryptionLevel level,
+ QuicPacketSequenceNumber packet_sequence_number,
+ const QuicPacket& packet) {
+ DCHECK(encrypter_[level].get() != NULL);
+
+ scoped_ptr<QuicData> out(encrypter_[level]->EncryptPacket(
+ packet_sequence_number, packet.AssociatedData(), packet.Plaintext()));
+ if (out.get() == NULL) {
+ RaiseError(QUIC_ENCRYPTION_FAILURE);
+ return NULL;
+ }
+ StringPiece header_data = packet.BeforePlaintext();
+ size_t len = header_data.length() + out->length();
+ char* buffer = new char[len];
+ // TODO(rch): eliminate this buffer copy by passing in a buffer to Encrypt().
+ memcpy(buffer, header_data.data(), header_data.length());
+ memcpy(buffer + header_data.length(), out->data(), out->length());
+ return new QuicEncryptedPacket(buffer, len, true);
+}
+
+size_t QuicFramer::GetMaxPlaintextSize(size_t ciphertext_size) {
+ // In order to keep the code simple, we don't have the current encryption
+ // level to hand. At the moment, the NullEncrypter has a tag length of 16
+ // bytes and AES-GCM has a tag length of 12. We take the minimum plaintext
+ // length just to be safe.
+ size_t min_plaintext_size = ciphertext_size;
+
+ for (int i = ENCRYPTION_NONE; i < NUM_ENCRYPTION_LEVELS; i++) {
+ if (encrypter_[i].get() != NULL) {
+ size_t size = encrypter_[i]->GetMaxPlaintextSize(ciphertext_size);
+ if (size < min_plaintext_size) {
+ min_plaintext_size = size;
+ }
+ }
+ }
+
+ return min_plaintext_size;
+}
+
+bool QuicFramer::DecryptPayload(const QuicPacketHeader& header,
+ const QuicEncryptedPacket& packet) {
+ StringPiece encrypted;
+ if (!reader_->ReadStringPiece(&encrypted, reader_->BytesRemaining())) {
+ return false;
+ }
+ DCHECK(decrypter_.get() != NULL);
+ decrypted_.reset(decrypter_->DecryptPacket(
+ header.packet_sequence_number,
+ GetAssociatedDataFromEncryptedPacket(
+ packet,
+ header.public_header.guid_length,
+ header.public_header.version_flag,
+ header.public_header.sequence_number_length),
+ encrypted));
+ if (decrypted_.get() == NULL && alternative_decrypter_.get() != NULL) {
+ decrypted_.reset(alternative_decrypter_->DecryptPacket(
+ header.packet_sequence_number,
+ GetAssociatedDataFromEncryptedPacket(
+ packet,
+ header.public_header.guid_length,
+ header.public_header.version_flag,
+ header.public_header.sequence_number_length),
+ encrypted));
+ if (decrypted_.get() != NULL) {
+ if (alternative_decrypter_latch_) {
+ // Switch to the alternative decrypter and latch so that we cannot
+ // switch back.
+ decrypter_.reset(alternative_decrypter_.release());
+ } else {
+ // Switch the alternative decrypter so that we use it first next time.
+ decrypter_.swap(alternative_decrypter_);
+ }
+ }
+ }
+
+ if (decrypted_.get() == NULL) {
+ return false;
+ }
+
+ reader_.reset(new QuicDataReader(decrypted_->data(), decrypted_->length()));
+ return true;
+}
+
+size_t QuicFramer::ComputeFrameLength(const QuicFrame& frame,
+ bool last_frame_in_packet) {
+ switch (frame.type) {
+ case STREAM_FRAME:
+ return GetMinStreamFrameSize(quic_version_,
+ frame.stream_frame->stream_id,
+ frame.stream_frame->offset,
+ last_frame_in_packet) +
+ frame.stream_frame->data.size();
+ case ACK_FRAME: {
+ const QuicAckFrame& ack = *frame.ack_frame;
+ return GetMinAckFrameSize() + PACKET_6BYTE_SEQUENCE_NUMBER *
+ ack.received_info.missing_packets.size();
+ }
+ case CONGESTION_FEEDBACK_FRAME: {
+ size_t len = kQuicFrameTypeSize;
+ const QuicCongestionFeedbackFrame& congestion_feedback =
+ *frame.congestion_feedback_frame;
+ len += 1; // Congestion feedback type.
+
+ switch (congestion_feedback.type) {
+ case kInterArrival: {
+ const CongestionFeedbackMessageInterArrival& inter_arrival =
+ congestion_feedback.inter_arrival;
+ len += 2;
+ len += 1; // Number received packets.
+ if (inter_arrival.received_packet_times.size() > 0) {
+ len += PACKET_6BYTE_SEQUENCE_NUMBER; // Smallest received.
+ len += 8; // Time.
+ // 2 bytes per sequence number delta plus 4 bytes per delta time.
+ len += PACKET_6BYTE_SEQUENCE_NUMBER *
+ (inter_arrival.received_packet_times.size() - 1);
+ }
+ break;
+ }
+ case kFixRate:
+ len += 4;
+ break;
+ case kTCP:
+ len += 4;
+ break;
+ default:
+ set_detailed_error("Illegal feedback type.");
+ DLOG(INFO) << "Illegal feedback type: " << congestion_feedback.type;
+ break;
+ }
+ return len;
+ }
+ case RST_STREAM_FRAME:
+ return GetMinRstStreamFrameSize() +
+ frame.rst_stream_frame->error_details.size();
+ case CONNECTION_CLOSE_FRAME: {
+ const QuicAckFrame& ack = frame.connection_close_frame->ack_frame;
+ return GetMinConnectionCloseFrameSize() +
+ frame.connection_close_frame->error_details.size() +
+ PACKET_6BYTE_SEQUENCE_NUMBER *
+ ack.received_info.missing_packets.size();
+ }
+ case GOAWAY_FRAME:
+ return GetMinGoAwayFrameSize() + frame.goaway_frame->reason_phrase.size();
+ case PADDING_FRAME:
+ DCHECK(false);
+ return 0;
+ case NUM_FRAME_TYPES:
+ DCHECK(false);
+ return 0;
+ }
+
+ // Not reachable, but some Chrome compilers can't figure that out. *sigh*
+ DCHECK(false);
+ return 0;
+}
+
+bool QuicFramer::AppendTypeByte(const QuicFrame& frame,
+ bool last_frame_in_packet,
+ QuicDataWriter* writer) {
+ uint8 type_byte = 0;
+ switch (frame.type) {
+ case STREAM_FRAME: {
+ if (frame.stream_frame == NULL) {
+ LOG(DFATAL) << "Failed to append STREAM frame with no stream_frame.";
+ }
+ // Fin bit.
+ type_byte |= frame.stream_frame->fin ? kQuicStreamFinMask : 0;
+
+ // Data Length bit.
+ type_byte <<= kQuicStreamDataLengthShift;
+ type_byte |= last_frame_in_packet ? 0 : kQuicStreamDataLengthMask;
+
+ // Offset 3 bits.
+ type_byte <<= kQuicStreamOffsetShift;
+ const size_t offset_len = GetStreamOffsetSize(frame.stream_frame->offset);
+ if (offset_len > 0) {
+ type_byte |= offset_len - 1;
+ }
+
+ // stream id 2 bits.
+ type_byte <<= kQuicStreamIdShift;
+ type_byte |= GetStreamIdSize(frame.stream_frame->stream_id) - 1;
+
+ type_byte <<= 1; // Leaves the last bit as a 0.
+ break;
+ }
+ case ACK_FRAME: {
+ // TODO(ianswett): Use extra 5 bits in the ack framing.
+ type_byte = 0x01;
+ break;
+ }
+ case CONGESTION_FEEDBACK_FRAME: {
+ // TODO(ianswett): Use extra 5 bits in the congestion feedback framing.
+ type_byte = 0x03;
+ break;
+ }
+ default:
+ type_byte =
+ frame.type << kQuicDefaultFrameTypeShift | kQuicDefaultFrameTypeMask;
+ break;
+ }
+
+ return writer->WriteUInt8(type_byte);
+}
+
+// static
+bool QuicFramer::AppendPacketSequenceNumber(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number,
+ QuicDataWriter* writer) {
+ // Ensure the entire sequence number can be written.
+ if (writer->capacity() - writer->length() <
+ static_cast<size_t>(sequence_number_length)) {
+ return false;
+ }
+ switch (sequence_number_length) {
+ case PACKET_1BYTE_SEQUENCE_NUMBER:
+ return writer->WriteUInt8(
+ packet_sequence_number & k1ByteSequenceNumberMask);
+ break;
+ case PACKET_2BYTE_SEQUENCE_NUMBER:
+ return writer->WriteUInt16(
+ packet_sequence_number & k2ByteSequenceNumberMask);
+ break;
+ case PACKET_4BYTE_SEQUENCE_NUMBER:
+ return writer->WriteUInt32(
+ packet_sequence_number & k4ByteSequenceNumberMask);
+ break;
+ case PACKET_6BYTE_SEQUENCE_NUMBER:
+ return writer->WriteUInt48(
+ packet_sequence_number & k6ByteSequenceNumberMask);
+ break;
+ default:
+ NOTREACHED() << "sequence_number_length: " << sequence_number_length;
+ return false;
+ }
+}
+
+bool QuicFramer::AppendStreamFramePayload(
+ const QuicStreamFrame& frame,
+ bool last_frame_in_packet,
+ QuicDataWriter* writer) {
+ if (!writer->WriteBytes(&frame.stream_id, GetStreamIdSize(frame.stream_id))) {
+ return false;
+ }
+ if (!writer->WriteBytes(&frame.offset, GetStreamOffsetSize(frame.offset))) {
+ return false;
+ }
+ if (!last_frame_in_packet) {
+ if (!writer->WriteUInt16(frame.data.size())) {
+ return false;
+ }
+ }
+ if (!writer->WriteBytes(frame.data.data(), frame.data.size())) {
+ return false;
+ }
+ return true;
+}
+
+QuicPacketSequenceNumber QuicFramer::CalculateLargestObserved(
+ const SequenceNumberSet& missing_packets,
+ SequenceNumberSet::const_iterator largest_written) {
+ SequenceNumberSet::const_iterator it = largest_written;
+ QuicPacketSequenceNumber previous_missing = *it;
+ ++it;
+
+ // See if the next thing is a gap in the missing packets: if it's a
+ // non-missing packet we can return it.
+ if (it != missing_packets.end() && previous_missing + 1 != *it) {
+ return *it - 1;
+ }
+
+ // Otherwise return the largest missing packet, as indirectly observed.
+ return *largest_written;
+}
+
+// TODO(ianswett): Use varints or another more compact approach for all deltas.
+bool QuicFramer::AppendAckFramePayload(
+ const QuicAckFrame& frame,
+ QuicDataWriter* writer) {
+ // TODO(satyamshekhar): Decide how often we really should send this
+ // entropy_hash update.
+ if (!writer->WriteUInt8(frame.sent_info.entropy_hash)) {
+ return false;
+ }
+
+ if (!AppendPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ frame.sent_info.least_unacked, writer)) {
+ return false;
+ }
+
+ size_t received_entropy_offset = writer->length();
+ if (!writer->WriteUInt8(frame.received_info.entropy_hash)) {
+ return false;
+ }
+
+ size_t largest_observed_offset = writer->length();
+ if (!AppendPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ frame.received_info.largest_observed,
+ writer)) {
+ return false;
+ }
+ uint32 delta_time_largest_observed_us = kInvalidDeltaTime;
+ if (!frame.received_info.delta_time_largest_observed.IsInfinite()) {
+ delta_time_largest_observed_us =
+ frame.received_info.delta_time_largest_observed.ToMicroseconds();
+ }
+
+ size_t delta_time_largest_observed_offset = writer->length();
+ if (!writer->WriteUInt32(delta_time_largest_observed_us)) {
+ return false;
+ }
+
+ // We don't check for overflowing uint8 here, because we only can fit 192 acks
+ // per packet, so if we overflow we will be truncated.
+ uint8 num_missing_packets = frame.received_info.missing_packets.size();
+ size_t num_missing_packets_offset = writer->length();
+ if (!writer->WriteBytes(&num_missing_packets, 1)) {
+ return false;
+ }
+
+ SequenceNumberSet::const_iterator it =
+ frame.received_info.missing_packets.begin();
+ int num_missing_packets_written = 0;
+ for (; it != frame.received_info.missing_packets.end(); ++it) {
+ if (!AppendPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ *it, writer)) {
+ // We are truncating.
+ QuicPacketSequenceNumber largest_observed =
+ CalculateLargestObserved(frame.received_info.missing_packets, --it);
+ // Overwrite entropy hash for received packets.
+ writer->WriteUInt8ToOffset(
+ entropy_calculator_->EntropyHash(largest_observed),
+ received_entropy_offset);
+ // Overwrite largest_observed.
+ writer->WriteUInt48ToOffset(largest_observed & k6ByteSequenceNumberMask,
+ largest_observed_offset);
+ writer->WriteUInt32ToOffset(kInvalidDeltaTime,
+ delta_time_largest_observed_offset);
+ writer->WriteUInt8ToOffset(num_missing_packets_written,
+ num_missing_packets_offset);
+ return true;
+ }
+ ++num_missing_packets_written;
+ DCHECK_GE(numeric_limits<uint8>::max(), num_missing_packets_written);
+ }
+
+ return true;
+}
+
+bool QuicFramer::AppendQuicCongestionFeedbackFramePayload(
+ const QuicCongestionFeedbackFrame& frame,
+ QuicDataWriter* writer) {
+ if (!writer->WriteBytes(&frame.type, 1)) {
+ return false;
+ }
+
+ switch (frame.type) {
+ case kInterArrival: {
+ const CongestionFeedbackMessageInterArrival& inter_arrival =
+ frame.inter_arrival;
+ if (!writer->WriteUInt16(
+ inter_arrival.accumulated_number_of_lost_packets)) {
+ return false;
+ }
+ DCHECK_GE(numeric_limits<uint8>::max(),
+ inter_arrival.received_packet_times.size());
+ if (inter_arrival.received_packet_times.size() >
+ numeric_limits<uint8>::max()) {
+ return false;
+ }
+ // TODO(ianswett): Make num_received_packets a varint.
+ uint8 num_received_packets =
+ inter_arrival.received_packet_times.size();
+ if (!writer->WriteBytes(&num_received_packets, 1)) {
+ return false;
+ }
+ if (num_received_packets > 0) {
+ TimeMap::const_iterator it =
+ inter_arrival.received_packet_times.begin();
+
+ QuicPacketSequenceNumber lowest_sequence = it->first;
+ if (!AppendPacketSequenceNumber(PACKET_6BYTE_SEQUENCE_NUMBER,
+ lowest_sequence, writer)) {
+ return false;
+ }
+
+ QuicTime lowest_time = it->second;
+ if (!writer->WriteUInt64(
+ lowest_time.Subtract(creation_time_).ToMicroseconds())) {
+ return false;
+ }
+
+ for (++it; it != inter_arrival.received_packet_times.end(); ++it) {
+ QuicPacketSequenceNumber sequence_delta = it->first - lowest_sequence;
+ DCHECK_GE(numeric_limits<uint16>::max(), sequence_delta);
+ if (sequence_delta > numeric_limits<uint16>::max()) {
+ return false;
+ }
+ if (!writer->WriteUInt16(static_cast<uint16>(sequence_delta))) {
+ return false;
+ }
+
+ int32 time_delta_us =
+ it->second.Subtract(lowest_time).ToMicroseconds();
+ if (!writer->WriteBytes(&time_delta_us, sizeof(time_delta_us))) {
+ return false;
+ }
+ }
+ }
+ break;
+ }
+ case kFixRate: {
+ const CongestionFeedbackMessageFixRate& fix_rate =
+ frame.fix_rate;
+ if (!writer->WriteUInt32(fix_rate.bitrate.ToBytesPerSecond())) {
+ return false;
+ }
+ break;
+ }
+ case kTCP: {
+ const CongestionFeedbackMessageTCP& tcp = frame.tcp;
+ DCHECK_LE(tcp.receive_window, 1u << 20);
+ // Simple bit packing, don't send the 4 least significant bits.
+ uint16 receive_window = static_cast<uint16>(tcp.receive_window >> 4);
+ if (!writer->WriteUInt16(tcp.accumulated_number_of_lost_packets)) {
+ return false;
+ }
+ if (!writer->WriteUInt16(receive_window)) {
+ return false;
+ }
+ break;
+ }
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+bool QuicFramer::AppendRstStreamFramePayload(
+ const QuicRstStreamFrame& frame,
+ QuicDataWriter* writer) {
+ if (!writer->WriteUInt32(frame.stream_id)) {
+ return false;
+ }
+
+ uint32 error_code = static_cast<uint32>(frame.error_code);
+ if (!writer->WriteUInt32(error_code)) {
+ return false;
+ }
+
+ if (!writer->WriteStringPiece16(frame.error_details)) {
+ return false;
+ }
+ return true;
+}
+
+bool QuicFramer::AppendConnectionCloseFramePayload(
+ const QuicConnectionCloseFrame& frame,
+ QuicDataWriter* writer) {
+ uint32 error_code = static_cast<uint32>(frame.error_code);
+ if (!writer->WriteUInt32(error_code)) {
+ return false;
+ }
+ if (!writer->WriteStringPiece16(frame.error_details)) {
+ return false;
+ }
+ AppendAckFramePayload(frame.ack_frame, writer);
+ return true;
+}
+
+bool QuicFramer::AppendGoAwayFramePayload(const QuicGoAwayFrame& frame,
+ QuicDataWriter* writer) {
+ uint32 error_code = static_cast<uint32>(frame.error_code);
+ if (!writer->WriteUInt32(error_code)) {
+ return false;
+ }
+ uint32 stream_id = static_cast<uint32>(frame.last_good_stream_id);
+ if (!writer->WriteUInt32(stream_id)) {
+ return false;
+ }
+ if (!writer->WriteStringPiece16(frame.reason_phrase)) {
+ return false;
+ }
+ return true;
+}
+
+bool QuicFramer::RaiseError(QuicErrorCode error) {
+ DLOG(INFO) << detailed_error_;
+ set_error(error);
+ visitor_->OnError(this);
+ reader_.reset(NULL);
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_framer.h b/chromium/net/quic/quic_framer.h
new file mode 100644
index 00000000000..841172698df
--- /dev/null
+++ b/chromium/net/quic/quic_framer.h
@@ -0,0 +1,455 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_FRAMER_H_
+#define NET_QUIC_QUIC_FRAMER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicFramerPeer;
+} // namespace test
+
+class QuicDataReader;
+class QuicDataWriter;
+class QuicDecrypter;
+class QuicEncrypter;
+class QuicFramer;
+
+// Number of bytes reserved for the frame type preceding each frame.
+const size_t kQuicFrameTypeSize = 1;
+// Number of bytes reserved for error code.
+const size_t kQuicErrorCodeSize = 4;
+// Number of bytes reserved to denote the length of error details field.
+const size_t kQuicErrorDetailsLengthSize = 2;
+
+// Maximum number of bytes reserved for stream id.
+const size_t kQuicMaxStreamIdSize = 4;
+// Maximum number of bytes reserved for byte offset in stream frame.
+const size_t kQuicMaxStreamOffsetSize = 8;
+// Number of bytes reserved to store payload length in stream frame.
+const size_t kQuicStreamPayloadLengthSize = 2;
+
+// Size in bytes of the entropy hash sent in ack frames.
+const size_t kQuicEntropyHashSize = 1;
+// Size in bytes reserved for the delta time of the largest observed
+// sequence number in ack frames.
+const size_t kQuicDeltaTimeLargestObservedSize = 4;
+// Size in bytes reserved for the number of missing packets in ack frames.
+const size_t kNumberOfMissingPacketsSize = 1;
+
+// This class receives callbacks from the framer when packets
+// are processed.
+class NET_EXPORT_PRIVATE QuicFramerVisitorInterface {
+ public:
+ virtual ~QuicFramerVisitorInterface() {}
+
+ // Called if an error is detected in the QUIC protocol.
+ virtual void OnError(QuicFramer* framer) = 0;
+
+ // Called only when |is_server_| is true and the the framer gets a packet with
+ // version flag true and the version on the packet doesn't match
+ // |quic_version_|. The visitor should return true after it updates the
+ // version of the |framer_| to |received_version| or false to stop processing
+ // this packet.
+ virtual bool OnProtocolVersionMismatch(QuicVersion received_version) = 0;
+
+ // Called when a new packet has been received, before it
+ // has been validated or processed.
+ virtual void OnPacket() = 0;
+
+ // Called when a public reset packet has been parsed but has not yet
+ // been validated.
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) = 0;
+
+ // Called only when |is_server_| is false and a version negotiation packet has
+ // been parsed.
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) = 0;
+
+ // Called when a lost packet has been recovered via FEC,
+ // before it has been processed.
+ virtual void OnRevivedPacket() = 0;
+
+ // Called when the complete header of a packet had been parsed.
+ // If OnPacketHeader returns false, framing for this packet will cease.
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) = 0;
+
+ // Called when a data packet is parsed that is part of an FEC group.
+ // |payload| is the non-encrypted FEC protected payload of the packet.
+ virtual void OnFecProtectedPayload(base::StringPiece payload) = 0;
+
+ // Called when a StreamFrame has been parsed.
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) = 0;
+
+ // Called when a AckFrame has been parsed. If OnAckFrame returns false,
+ // the framer will stop parsing the current packet.
+ virtual bool OnAckFrame(const QuicAckFrame& frame) = 0;
+
+ // Called when a CongestionFeedbackFrame has been parsed.
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) = 0;
+
+ // Called when a RstStreamFrame has been parsed.
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) = 0;
+
+ // Called when a ConnectionCloseFrame has been parsed.
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) = 0;
+
+ // Called when a GoAwayFrame has been parsed.
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) = 0;
+
+ // Called when FEC data has been parsed.
+ virtual void OnFecData(const QuicFecData& fec) = 0;
+
+ // Called when a packet has been completely processed.
+ virtual void OnPacketComplete() = 0;
+};
+
+class NET_EXPORT_PRIVATE QuicFecBuilderInterface {
+ public:
+ virtual ~QuicFecBuilderInterface() {}
+
+ // Called when a data packet is constructed that is part of an FEC group.
+ // |payload| is the non-encrypted FEC protected payload of the packet.
+ virtual void OnBuiltFecProtectedPayload(const QuicPacketHeader& header,
+ base::StringPiece payload) = 0;
+};
+
+// This class calculates the received entropy of the ack packet being
+// framed, should it get truncated.
+class NET_EXPORT_PRIVATE QuicReceivedEntropyHashCalculatorInterface {
+ public:
+ virtual ~QuicReceivedEntropyHashCalculatorInterface() {}
+
+ // When an ack frame gets truncated while being framed the received
+ // entropy of the ack frame needs to be calculated since the some of the
+ // missing packets are not added and the largest observed might be lowered.
+ // This should return the received entropy hash of the packets received up to
+ // and including |sequence_number|.
+ virtual QuicPacketEntropyHash EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const = 0;
+};
+
+// Class for parsing and constructing QUIC packets. It has a
+// QuicFramerVisitorInterface that is called when packets are parsed.
+// It also has a QuicFecBuilder that is called when packets are constructed
+// in order to generate FEC data for subsequently building FEC packets.
+class NET_EXPORT_PRIVATE QuicFramer {
+ public:
+ // Constructs a new framer that installs a kNULL QuicEncrypter and
+ // QuicDecrypter for level ENCRYPTION_NONE.
+ QuicFramer(QuicVersion quic_version,
+ QuicTime creation_time,
+ bool is_server);
+
+ virtual ~QuicFramer();
+
+ // Returns true if |version| is a supported protocol version.
+ bool IsSupportedVersion(const QuicVersion version) const;
+
+ // Calculates the largest observed packet to advertise in the case an Ack
+ // Frame was truncated. last_written in this case is the iterator for the
+ // last missing packet which fit in the outgoing ack.
+ static QuicPacketSequenceNumber CalculateLargestObserved(
+ const SequenceNumberSet& missing_packets,
+ SequenceNumberSet::const_iterator last_written);
+
+ // Set callbacks to be called from the framer. A visitor must be set, or
+ // else the framer will likely crash. It is acceptable for the visitor
+ // to do nothing. If this is called multiple times, only the last visitor
+ // will be used.
+ void set_visitor(QuicFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+ }
+
+ // Set a builder to be called from the framer when building FEC protected
+ // packets. If this is called multiple times, only the last builder
+ // will be used. The builder need not be set.
+ void set_fec_builder(QuicFecBuilderInterface* builder) {
+ fec_builder_ = builder;
+ }
+
+ QuicVersion version() const {
+ return quic_version_;
+ }
+
+ void set_version(const QuicVersion version) {
+ DCHECK(IsSupportedVersion(version));
+ quic_version_ = version;
+ }
+
+ // Does not DCHECK for supported version. Used by tests to set unsupported
+ // version to trigger version negotiation.
+ void set_version_for_tests(const QuicVersion version) {
+ quic_version_ = version;
+ }
+
+ // Set entropy calculator to be called from the framer when it needs the
+ // entropy of a truncated ack frame. An entropy calculator must be set or else
+ // the framer will likely crash. If this is called multiple times, only the
+ // last calculator will be used.
+ void set_received_entropy_calculator(
+ QuicReceivedEntropyHashCalculatorInterface* entropy_calculator) {
+ entropy_calculator_ = entropy_calculator;
+ }
+
+ QuicErrorCode error() const {
+ return error_;
+ }
+
+ // Pass a UDP packet into the framer for parsing.
+ // Return true if the packet was processed succesfully. |packet| must be a
+ // single, complete UDP packet (not a frame of a packet). This packet
+ // might be null padded past the end of the payload, which will be correctly
+ // ignored.
+ bool ProcessPacket(const QuicEncryptedPacket& packet);
+
+ // Pass a data packet that was revived from FEC data into the framer
+ // for parsing.
+ // Return true if the packet was processed succesfully. |payload| must be
+ // the complete DECRYPTED payload of the revived packet.
+ bool ProcessRevivedPacket(QuicPacketHeader* header,
+ base::StringPiece payload);
+
+ // Largest size in bytes of all stream frame fields without the payload.
+ static size_t GetMinStreamFrameSize(QuicVersion version,
+ QuicStreamId stream_id,
+ QuicStreamOffset offset,
+ bool last_frame_in_packet);
+ // Size in bytes of all ack frame fields without the missing packets.
+ static size_t GetMinAckFrameSize();
+ // Size in bytes of all reset stream frame without the error details.
+ static size_t GetMinRstStreamFrameSize();
+ // Size in bytes of all connection close frame fields without the error
+ // details and the missing packets from the enclosed ack frame.
+ static size_t GetMinConnectionCloseFrameSize();
+ // Size in bytes of all GoAway frame fields without the reason phrase.
+ static size_t GetMinGoAwayFrameSize();
+ // The maximum number of nacks which can be transmitted in a single ack packet
+ // without exceeding kMaxPacketSize.
+ static size_t GetMaxUnackedPackets(QuicPacketHeader header);
+ // Size in bytes required to serialize the stream id.
+ static size_t GetStreamIdSize(QuicStreamId stream_id);
+ // Size in bytes required to serialize the stream offset.
+ static size_t GetStreamOffsetSize(QuicStreamOffset offset);
+ // Size in bytes required for a serialized version negotiation packet
+ static size_t GetVersionNegotiationPacketSize(size_t number_versions);
+
+
+ static bool CanTruncate(const QuicFrame& frame, size_t free_bytes);
+
+ // Returns the number of bytes added to the packet for the specified frame,
+ // and 0 if the frame doesn't fit. Includes the header size for the first
+ // frame.
+ size_t GetSerializedFrameLength(
+ const QuicFrame& frame, size_t free_bytes, bool first_frame);
+
+ // Returns the associated data from the encrypted packet |encrypted| as a
+ // stringpiece.
+ static base::StringPiece GetAssociatedDataFromEncryptedPacket(
+ const QuicEncryptedPacket& encrypted,
+ QuicGuidLength guid_length,
+ bool includes_version,
+ QuicSequenceNumberLength sequence_number_length);
+
+ // Returns a SerializedPacket whose |packet| member is owned by the caller,
+ // and is populated with the fields in |header| and |frames|, or is NULL if
+ // the packet could not be created.
+ // TODO(ianswett): Used for testing only.
+ SerializedPacket BuildUnsizedDataPacket(const QuicPacketHeader& header,
+ const QuicFrames& frames);
+
+ // Returns a SerializedPacket whose |packet| member is owned by the caller,
+ // is created from the first |num_frames| frames, or is NULL if the packet
+ // could not be created. The packet must be of size |packet_size|.
+ SerializedPacket BuildDataPacket(const QuicPacketHeader& header,
+ const QuicFrames& frames,
+ size_t packet_size);
+
+ // Returns a SerializedPacket whose |packet| member is owned by the caller,
+ // and is populated with the fields in |header| and |fec|, or is NULL if the
+ // packet could not be created.
+ SerializedPacket BuildFecPacket(const QuicPacketHeader& header,
+ const QuicFecData& fec);
+
+ // Returns a new public reset packet, owned by the caller.
+ static QuicEncryptedPacket* BuildPublicResetPacket(
+ const QuicPublicResetPacket& packet);
+
+ QuicEncryptedPacket* BuildVersionNegotiationPacket(
+ const QuicPacketPublicHeader& header,
+ const QuicVersionVector& supported_versions);
+
+ // SetDecrypter sets the primary decrypter, replacing any that already exists,
+ // and takes ownership. If an alternative decrypter is in place then the
+ // function DCHECKs. This is intended for cases where one knows that future
+ // packets will be using the new decrypter and the previous decrypter is not
+ // obsolete.
+ void SetDecrypter(QuicDecrypter* decrypter);
+
+ // SetAlternativeDecrypter sets a decrypter that may be used to decrypt
+ // future packets and takes ownership of it. If |latch_once_used| is true,
+ // then the first time that the decrypter is successful it will replace the
+ // primary decrypter. Otherwise both decrypters will remain active and the
+ // primary decrypter will be the one last used.
+ void SetAlternativeDecrypter(QuicDecrypter* decrypter,
+ bool latch_once_used);
+
+ const QuicDecrypter* decrypter() const;
+ const QuicDecrypter* alternative_decrypter() const;
+
+ // Changes the encrypter used for level |level| to |encrypter|. The function
+ // takes ownership of |encrypter|.
+ void SetEncrypter(EncryptionLevel level, QuicEncrypter* encrypter);
+ const QuicEncrypter* encrypter(EncryptionLevel level) const;
+
+ // SwapCryptersForTest exchanges the state of the crypters with |other|. To
+ // be used in tests only.
+ void SwapCryptersForTest(QuicFramer* other);
+
+ // Returns a new encrypted packet, owned by the caller.
+ QuicEncryptedPacket* EncryptPacket(EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ const QuicPacket& packet);
+
+ // Returns the maximum length of plaintext that can be encrypted
+ // to ciphertext no larger than |ciphertext_size|.
+ size_t GetMaxPlaintextSize(size_t ciphertext_size);
+
+ const std::string& detailed_error() { return detailed_error_; }
+
+ // Read the full 8 byte guid from a packet header.
+ // Return true on success, else false.
+ static bool ReadGuidFromPacket(const QuicEncryptedPacket& packet,
+ QuicGuid* guid);
+
+ private:
+ friend class test::QuicFramerPeer;
+
+ QuicPacketEntropyHash GetPacketEntropyHash(
+ const QuicPacketHeader& header) const;
+
+ bool ProcessDataPacket(const QuicPacketPublicHeader& public_header,
+ const QuicEncryptedPacket& packet);
+
+ bool ProcessPublicResetPacket(const QuicPacketPublicHeader& public_header);
+
+ bool ProcessVersionNegotiationPacket(QuicPacketPublicHeader* public_header);
+
+ bool WritePacketHeader(const QuicPacketHeader& header,
+ QuicDataWriter* writer);
+
+ bool ProcessPublicHeader(QuicPacketPublicHeader* header);
+
+ bool ProcessPacketHeader(QuicPacketHeader* header,
+ const QuicEncryptedPacket& packet);
+
+ bool ProcessPacketSequenceNumber(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber* sequence_number);
+ bool ProcessFrameData();
+ bool ProcessStreamFrame(uint8 frame_type, QuicStreamFrame* frame);
+ bool ProcessAckFrame(QuicAckFrame* frame);
+ bool ProcessReceivedInfo(ReceivedPacketInfo* received_info);
+ bool ProcessSentInfo(SentPacketInfo* sent_info);
+ bool ProcessQuicCongestionFeedbackFrame(
+ QuicCongestionFeedbackFrame* congestion_feedback);
+ bool ProcessRstStreamFrame(QuicRstStreamFrame* frame);
+ bool ProcessConnectionCloseFrame(QuicConnectionCloseFrame* frame);
+ bool ProcessGoAwayFrame(QuicGoAwayFrame* frame);
+
+ bool DecryptPayload(const QuicPacketHeader& header,
+ const QuicEncryptedPacket& packet);
+
+ // Returns the full packet sequence number from the truncated
+ // wire format version and the last seen packet sequence number.
+ QuicPacketSequenceNumber CalculatePacketSequenceNumberFromWire(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number) const;
+
+ // Computes the wire size in bytes of the payload of |frame|.
+ size_t ComputeFrameLength(const QuicFrame& frame, bool last_frame_in_packet);
+
+ static bool AppendPacketSequenceNumber(
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number,
+ QuicDataWriter* writer);
+
+ bool AppendTypeByte(const QuicFrame& frame,
+ bool last_frame_in_packet,
+ QuicDataWriter* writer);
+ bool AppendStreamFramePayload(const QuicStreamFrame& frame,
+ bool last_frame_in_packet,
+ QuicDataWriter* builder);
+ bool AppendAckFramePayload(const QuicAckFrame& frame,
+ QuicDataWriter* builder);
+ bool AppendQuicCongestionFeedbackFramePayload(
+ const QuicCongestionFeedbackFrame& frame,
+ QuicDataWriter* builder);
+ bool AppendRstStreamFramePayload(const QuicRstStreamFrame& frame,
+ QuicDataWriter* builder);
+ bool AppendConnectionCloseFramePayload(
+ const QuicConnectionCloseFrame& frame,
+ QuicDataWriter* builder);
+ bool AppendGoAwayFramePayload(const QuicGoAwayFrame& frame,
+ QuicDataWriter* writer);
+ bool RaiseError(QuicErrorCode error);
+
+ void set_error(QuicErrorCode error) {
+ error_ = error;
+ }
+
+ void set_detailed_error(const char* error) {
+ detailed_error_ = error;
+ }
+
+ std::string detailed_error_;
+ scoped_ptr<QuicDataReader> reader_;
+ QuicFramerVisitorInterface* visitor_;
+ QuicFecBuilderInterface* fec_builder_;
+ QuicReceivedEntropyHashCalculatorInterface* entropy_calculator_;
+ QuicErrorCode error_;
+ // Updated by ProcessPacketHeader when it succeeds.
+ QuicPacketSequenceNumber last_sequence_number_;
+ // Updated by WritePacketHeader.
+ QuicGuid last_serialized_guid_;
+ // Buffer containing decrypted payload data during parsing.
+ scoped_ptr<QuicData> decrypted_;
+ // Version of the protocol being used.
+ QuicVersion quic_version_;
+ // Primary decrypter used to decrypt packets during parsing.
+ scoped_ptr<QuicDecrypter> decrypter_;
+ // Alternative decrypter that can also be used to decrypt packets.
+ scoped_ptr<QuicDecrypter> alternative_decrypter_;
+ // alternative_decrypter_latch_is true if, when |alternative_decrypter_|
+ // successfully decrypts a packet, we should install it as the only
+ // decrypter.
+ bool alternative_decrypter_latch_;
+ // Encrypters used to encrypt packets via EncryptPacket().
+ scoped_ptr<QuicEncrypter> encrypter_[NUM_ENCRYPTION_LEVELS];
+ // Tracks if the framer is being used by the entity that received the
+ // connection or the entity that initiated it.
+ bool is_server_;
+ // The time this frames was created. Time written to the wire will be
+ // written as a delta from this value.
+ QuicTime creation_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicFramer);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_FRAMER_H_
diff --git a/chromium/net/quic/quic_framer_test.cc b/chromium/net/quic/quic_framer_test.cc
new file mode 100644
index 00000000000..e4148fea31d
--- /dev/null
+++ b/chromium/net/quic/quic_framer_test.cc
@@ -0,0 +1,3639 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/port.h"
+#include "base/stl_util.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+
+using base::hash_set;
+using base::StringPiece;
+using std::make_pair;
+using std::map;
+using std::numeric_limits;
+using std::string;
+using std::vector;
+using testing::Return;
+using testing::_;
+
+namespace net {
+namespace test {
+
+const QuicPacketSequenceNumber kEpoch = GG_UINT64_C(1) << 48;
+const QuicPacketSequenceNumber kMask = kEpoch - 1;
+
+// Index into the flags offset in the header.
+const size_t kPublicFlagsOffset = 0;
+// Index into the guid offset in the header.
+const size_t kGuidOffset = kPublicFlagsSize;
+// Index into the version string in the header. (if present).
+const size_t kVersionOffset = kGuidOffset + PACKET_8BYTE_GUID;
+
+// Index into the sequence number offset in the header.
+size_t GetSequenceNumberOffset(QuicGuidLength guid_length,
+ bool include_version) {
+ return kGuidOffset + guid_length +
+ (include_version ? kQuicVersionSize : 0);
+}
+
+size_t GetSequenceNumberOffset(bool include_version) {
+ return GetSequenceNumberOffset(PACKET_8BYTE_GUID, include_version);
+}
+
+// Index into the private flags offset in the data packet header.
+size_t GetPrivateFlagsOffset(QuicGuidLength guid_length, bool include_version) {
+ return GetSequenceNumberOffset(guid_length, include_version) +
+ PACKET_6BYTE_SEQUENCE_NUMBER;
+}
+
+size_t GetPrivateFlagsOffset(bool include_version) {
+ return GetPrivateFlagsOffset(PACKET_8BYTE_GUID, include_version);
+}
+
+size_t GetPrivateFlagsOffset(bool include_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return GetSequenceNumberOffset(PACKET_8BYTE_GUID, include_version) +
+ sequence_number_length;
+}
+
+// Index into the fec group offset in the header.
+size_t GetFecGroupOffset(QuicGuidLength guid_length, bool include_version) {
+ return GetPrivateFlagsOffset(guid_length, include_version) +
+ kPrivateFlagsSize;
+}
+
+size_t GetFecGroupOffset(bool include_version) {
+ return GetPrivateFlagsOffset(PACKET_8BYTE_GUID, include_version) +
+ kPrivateFlagsSize;
+}
+
+size_t GetFecGroupOffset(bool include_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return GetPrivateFlagsOffset(include_version, sequence_number_length) +
+ kPrivateFlagsSize;
+}
+
+// Index into the nonce proof of the public reset packet.
+// Public resets always have full guids.
+const size_t kPublicResetPacketNonceProofOffset =
+ kGuidOffset + PACKET_8BYTE_GUID;
+
+// Index into the rejected sequence number of the public reset packet.
+const size_t kPublicResetPacketRejectedSequenceNumberOffset =
+ kPublicResetPacketNonceProofOffset + kPublicResetNonceSize;
+
+class TestEncrypter : public QuicEncrypter {
+ public:
+ virtual ~TestEncrypter() {}
+ virtual bool SetKey(StringPiece key) OVERRIDE {
+ return true;
+ }
+ virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+ return true;
+ }
+ virtual bool Encrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece plaintext,
+ unsigned char* output) OVERRIDE {
+ CHECK(false) << "Not implemented";
+ return false;
+ }
+ virtual QuicData* EncryptPacket(QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece plaintext) OVERRIDE {
+ sequence_number_ = sequence_number;
+ associated_data_ = associated_data.as_string();
+ plaintext_ = plaintext.as_string();
+ return new QuicData(plaintext.data(), plaintext.length());
+ }
+ virtual size_t GetKeySize() const OVERRIDE {
+ return 0;
+ }
+ virtual size_t GetNoncePrefixSize() const OVERRIDE {
+ return 0;
+ }
+ virtual size_t GetMaxPlaintextSize(size_t ciphertext_size) const OVERRIDE {
+ return ciphertext_size;
+ }
+ virtual size_t GetCiphertextSize(size_t plaintext_size) const OVERRIDE {
+ return plaintext_size;
+ }
+ virtual StringPiece GetKey() const OVERRIDE {
+ return StringPiece();
+ }
+ virtual StringPiece GetNoncePrefix() const OVERRIDE {
+ return StringPiece();
+ }
+ QuicPacketSequenceNumber sequence_number_;
+ string associated_data_;
+ string plaintext_;
+};
+
+class TestDecrypter : public QuicDecrypter {
+ public:
+ virtual ~TestDecrypter() {}
+ virtual bool SetKey(StringPiece key) OVERRIDE {
+ return true;
+ }
+ virtual bool SetNoncePrefix(StringPiece nonce_prefix) OVERRIDE {
+ return true;
+ }
+ virtual bool Decrypt(StringPiece nonce,
+ StringPiece associated_data,
+ StringPiece ciphertext,
+ unsigned char* output,
+ size_t* output_length) OVERRIDE {
+ CHECK(false) << "Not implemented";
+ return false;
+ }
+ virtual QuicData* DecryptPacket(QuicPacketSequenceNumber sequence_number,
+ StringPiece associated_data,
+ StringPiece ciphertext) OVERRIDE {
+ sequence_number_ = sequence_number;
+ associated_data_ = associated_data.as_string();
+ ciphertext_ = ciphertext.as_string();
+ return new QuicData(ciphertext.data(), ciphertext.length());
+ }
+ virtual StringPiece GetKey() const OVERRIDE {
+ return StringPiece();
+ }
+ virtual StringPiece GetNoncePrefix() const OVERRIDE {
+ return StringPiece();
+ }
+ QuicPacketSequenceNumber sequence_number_;
+ string associated_data_;
+ string ciphertext_;
+};
+
+class TestQuicVisitor : public ::net::QuicFramerVisitorInterface {
+ public:
+ TestQuicVisitor()
+ : error_count_(0),
+ version_mismatch_(0),
+ packet_count_(0),
+ frame_count_(0),
+ fec_count_(0),
+ complete_packets_(0),
+ revived_packets_(0),
+ accept_packet_(true) {
+ }
+
+ virtual ~TestQuicVisitor() {
+ STLDeleteElements(&stream_frames_);
+ STLDeleteElements(&ack_frames_);
+ STLDeleteElements(&congestion_feedback_frames_);
+ STLDeleteElements(&fec_data_);
+ }
+
+ virtual void OnError(QuicFramer* f) OVERRIDE {
+ DLOG(INFO) << "QuicFramer Error: " << QuicUtils::ErrorToString(f->error())
+ << " (" << f->error() << ")";
+ error_count_++;
+ }
+
+ virtual void OnPacket() OVERRIDE {}
+
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE {
+ public_reset_packet_.reset(new QuicPublicResetPacket(packet));
+ }
+
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE {
+ version_negotiation_packet_.reset(new QuicVersionNegotiationPacket(packet));
+ }
+
+ virtual void OnRevivedPacket() OVERRIDE {
+ revived_packets_++;
+ }
+
+ virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+ DLOG(INFO) << "QuicFramer Version Mismatch, version: " << version;
+ version_mismatch_++;
+ return true;
+ }
+
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE {
+ packet_count_++;
+ header_.reset(new QuicPacketHeader(header));
+ return accept_packet_;
+ }
+
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+ frame_count_++;
+ stream_frames_.push_back(new QuicStreamFrame(frame));
+ return true;
+ }
+
+ virtual void OnFecProtectedPayload(StringPiece payload) OVERRIDE {
+ fec_protected_payload_ = payload.as_string();
+ }
+
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE {
+ frame_count_++;
+ ack_frames_.push_back(new QuicAckFrame(frame));
+ return true;
+ }
+
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE {
+ frame_count_++;
+ congestion_feedback_frames_.push_back(
+ new QuicCongestionFeedbackFrame(frame));
+ return true;
+ }
+
+ virtual void OnFecData(const QuicFecData& fec) OVERRIDE {
+ fec_count_++;
+ fec_data_.push_back(new QuicFecData(fec));
+ }
+
+ virtual void OnPacketComplete() OVERRIDE {
+ complete_packets_++;
+ }
+
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE {
+ rst_stream_frame_ = frame;
+ return true;
+ }
+
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE {
+ connection_close_frame_ = frame;
+ return true;
+ }
+
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE {
+ goaway_frame_ = frame;
+ return true;
+ }
+
+ // Counters from the visitor_ callbacks.
+ int error_count_;
+ int version_mismatch_;
+ int packet_count_;
+ int frame_count_;
+ int fec_count_;
+ int complete_packets_;
+ int revived_packets_;
+ bool accept_packet_;
+
+ scoped_ptr<QuicPacketHeader> header_;
+ scoped_ptr<QuicPublicResetPacket> public_reset_packet_;
+ scoped_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+ vector<QuicStreamFrame*> stream_frames_;
+ vector<QuicAckFrame*> ack_frames_;
+ vector<QuicCongestionFeedbackFrame*> congestion_feedback_frames_;
+ vector<QuicFecData*> fec_data_;
+ string fec_protected_payload_;
+ QuicRstStreamFrame rst_stream_frame_;
+ QuicConnectionCloseFrame connection_close_frame_;
+ QuicGoAwayFrame goaway_frame_;
+};
+
+class QuicFramerTest : public ::testing::TestWithParam<QuicVersion> {
+ public:
+ QuicFramerTest()
+ : encrypter_(new test::TestEncrypter()),
+ decrypter_(new test::TestDecrypter()),
+ start_(QuicTime::Zero().Add(QuicTime::Delta::FromMicroseconds(0x10))),
+ framer_(QuicVersionMax(), start_, true) {
+ framer_.SetDecrypter(decrypter_);
+ framer_.SetEncrypter(ENCRYPTION_NONE, encrypter_);
+ framer_.set_visitor(&visitor_);
+ framer_.set_received_entropy_calculator(&entropy_calculator_);
+
+ version_ = GetParam();
+ framer_.set_version(version_);
+ }
+
+ bool CheckEncryption(QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet) {
+ if (sequence_number != encrypter_->sequence_number_) {
+ LOG(ERROR) << "Encrypted incorrect packet sequence number. expected "
+ << sequence_number << " actual: "
+ << encrypter_->sequence_number_;
+ return false;
+ }
+ if (packet->AssociatedData() != encrypter_->associated_data_) {
+ LOG(ERROR) << "Encrypted incorrect associated data. expected "
+ << packet->AssociatedData() << " actual: "
+ << encrypter_->associated_data_;
+ return false;
+ }
+ if (packet->Plaintext() != encrypter_->plaintext_) {
+ LOG(ERROR) << "Encrypted incorrect plaintext data. expected "
+ << packet->Plaintext() << " actual: "
+ << encrypter_->plaintext_;
+ return false;
+ }
+ return true;
+ }
+
+ bool CheckDecryption(const QuicEncryptedPacket& encrypted,
+ bool includes_version) {
+ if (visitor_.header_->packet_sequence_number !=
+ decrypter_->sequence_number_) {
+ LOG(ERROR) << "Decrypted incorrect packet sequence number. expected "
+ << visitor_.header_->packet_sequence_number << " actual: "
+ << decrypter_->sequence_number_;
+ return false;
+ }
+ if (QuicFramer::GetAssociatedDataFromEncryptedPacket(
+ encrypted, PACKET_8BYTE_GUID,
+ includes_version, PACKET_6BYTE_SEQUENCE_NUMBER) !=
+ decrypter_->associated_data_) {
+ LOG(ERROR) << "Decrypted incorrect associated data. expected "
+ << QuicFramer::GetAssociatedDataFromEncryptedPacket(
+ encrypted, PACKET_8BYTE_GUID,
+ includes_version, PACKET_6BYTE_SEQUENCE_NUMBER)
+ << " actual: " << decrypter_->associated_data_;
+ return false;
+ }
+ StringPiece ciphertext(encrypted.AsStringPiece().substr(
+ GetStartOfEncryptedData(PACKET_8BYTE_GUID, includes_version,
+ PACKET_6BYTE_SEQUENCE_NUMBER)));
+ if (ciphertext != decrypter_->ciphertext_) {
+ LOG(ERROR) << "Decrypted incorrect ciphertext data. expected "
+ << ciphertext << " actual: "
+ << decrypter_->ciphertext_;
+ return false;
+ }
+ return true;
+ }
+
+ char* AsChars(unsigned char* data) {
+ return reinterpret_cast<char*>(data);
+ }
+
+ void CheckProcessingFails(unsigned char* packet,
+ size_t len,
+ string expected_error,
+ QuicErrorCode error_code) {
+ QuicEncryptedPacket encrypted(AsChars(packet), len, false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted)) << "len: " << len;
+ EXPECT_EQ(expected_error, framer_.detailed_error()) << "len: " << len;
+ EXPECT_EQ(error_code, framer_.error()) << "len: " << len;
+ }
+
+ void ValidateTruncatedAck(const QuicAckFrame* ack, size_t keys) {
+ for (size_t i = 1; i < keys; ++i) {
+ EXPECT_TRUE(ContainsKey(ack->received_info.missing_packets, i)) << i;
+ }
+ EXPECT_EQ(keys, ack->received_info.largest_observed);
+ }
+
+ void CheckCalculatePacketSequenceNumber(
+ QuicPacketSequenceNumber expected_sequence_number,
+ QuicPacketSequenceNumber last_sequence_number) {
+ QuicPacketSequenceNumber wire_sequence_number =
+ expected_sequence_number & kMask;
+ QuicFramerPeer::SetLastSequenceNumber(&framer_, last_sequence_number);
+ EXPECT_EQ(expected_sequence_number,
+ QuicFramerPeer::CalculatePacketSequenceNumberFromWire(
+ &framer_, PACKET_6BYTE_SEQUENCE_NUMBER, wire_sequence_number))
+ << "last_sequence_number: " << last_sequence_number
+ << " wire_sequence_number: " << wire_sequence_number;
+ }
+
+ test::TestEncrypter* encrypter_;
+ test::TestDecrypter* decrypter_;
+ QuicVersion version_;
+ QuicTime start_;
+ QuicFramer framer_;
+ test::TestQuicVisitor visitor_;
+ test::TestEntropyCalculator entropy_calculator_;
+};
+
+// Run all framer tests with all supported versions of QUIC.
+INSTANTIATE_TEST_CASE_P(QuicFramerTests,
+ QuicFramerTest,
+ ::testing::ValuesIn(kSupportedQuicVersions));
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearEpochStart) {
+ // A few quick manual sanity checks
+ CheckCalculatePacketSequenceNumber(GG_UINT64_C(1), GG_UINT64_C(0));
+ CheckCalculatePacketSequenceNumber(kEpoch + 1, kMask);
+ CheckCalculatePacketSequenceNumber(kEpoch, kMask);
+
+ // Cases where the last number was close to the start of the range
+ for (uint64 last = 0; last < 10; last++) {
+ // Small numbers should not wrap (even if they're out of order).
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(j, last);
+ }
+
+ // Large numbers should not wrap either (because we're near 0 already).
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(kEpoch - 1 - j, last);
+ }
+ }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearEpochEnd) {
+ // Cases where the last number was close to the end of the range
+ for (uint64 i = 0; i < 10; i++) {
+ QuicPacketSequenceNumber last = kEpoch - i;
+
+ // Small numbers should wrap.
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(kEpoch + j, last);
+ }
+
+ // Large numbers should not (even if they're out of order).
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(kEpoch - 1 - j, last);
+ }
+ }
+}
+
+// Next check where we're in a non-zero epoch to verify we handle
+// reverse wrapping, too.
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearPrevEpoch) {
+ const uint64 prev_epoch = 1 * kEpoch;
+ const uint64 cur_epoch = 2 * kEpoch;
+ // Cases where the last number was close to the start of the range
+ for (uint64 i = 0; i < 10; i++) {
+ uint64 last = cur_epoch + i;
+ // Small number should not wrap (even if they're out of order).
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(cur_epoch + j, last);
+ }
+
+ // But large numbers should reverse wrap.
+ for (uint64 j = 0; j < 10; j++) {
+ uint64 num = kEpoch - 1 - j;
+ CheckCalculatePacketSequenceNumber(prev_epoch + num, last);
+ }
+ }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearNextEpoch) {
+ const uint64 cur_epoch = 2 * kEpoch;
+ const uint64 next_epoch = 3 * kEpoch;
+ // Cases where the last number was close to the end of the range
+ for (uint64 i = 0; i < 10; i++) {
+ QuicPacketSequenceNumber last = next_epoch - 1 - i;
+
+ // Small numbers should wrap.
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(next_epoch + j, last);
+ }
+
+ // but large numbers should not (even if they're out of order).
+ for (uint64 j = 0; j < 10; j++) {
+ uint64 num = kEpoch - 1 - j;
+ CheckCalculatePacketSequenceNumber(cur_epoch + num, last);
+ }
+ }
+}
+
+TEST_P(QuicFramerTest, CalculatePacketSequenceNumberFromWireNearNextMax) {
+ const uint64 max_number = numeric_limits<uint64>::max();
+ const uint64 max_epoch = max_number & ~kMask;
+
+ // Cases where the last number was close to the end of the range
+ for (uint64 i = 0; i < 10; i++) {
+ // Subtract 1, because the expected next sequence number is 1 more than the
+ // last sequence number.
+ QuicPacketSequenceNumber last = max_number - i - 1;
+
+ // Small numbers should not wrap, because they have nowhere to go.
+ for (uint64 j = 0; j < 10; j++) {
+ CheckCalculatePacketSequenceNumber(max_epoch + j, last);
+ }
+
+ // Large numbers should not wrap either.
+ for (uint64 j = 0; j < 10; j++) {
+ uint64 num = kEpoch - 1 - j;
+ CheckCalculatePacketSequenceNumber(max_epoch + num, last);
+ }
+ }
+}
+
+TEST_P(QuicFramerTest, EmptyPacket) {
+ char packet[] = { 0x00 };
+ QuicEncryptedPacket encrypted(packet, 0, false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
+}
+
+TEST_P(QuicFramerTest, LargePacket) {
+ unsigned char packet[kMaxPacketSize + 1] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ memset(packet + GetPacketHeaderSize(
+ PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP), 0,
+ kMaxPacketSize - GetPacketHeaderSize(
+ PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) + 1);
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+
+ ASSERT_TRUE(visitor_.header_.get());
+ // Make sure we've parsed the packet header, so we can send an error.
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ // Make sure the correct error is propagated.
+ EXPECT_EQ(QUIC_PACKET_TOO_LARGE, framer_.error());
+}
+
+TEST_P(QuicFramerTest, PacketHeader) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith4ByteGuid) {
+ QuicFramerPeer::SetLastSerializedGuid(&framer_,
+ GG_UINT64_C(0xFEDCBA9876543210));
+
+ unsigned char packet[] = {
+ // public flags (4 byte guid)
+ 0x38,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_4BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(PACKET_4BYTE_GUID,
+ !kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(PACKET_4BYTE_GUID,
+ !kIncludeVersion)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(PACKET_4BYTE_GUID, !kIncludeVersion)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeader1ByteGuid) {
+ QuicFramerPeer::SetLastSerializedGuid(&framer_,
+ GG_UINT64_C(0xFEDCBA9876543210));
+
+ unsigned char packet[] = {
+ // public flags (1 byte guid)
+ 0x34,
+ // guid
+ 0x10,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_1BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(PACKET_1BYTE_GUID,
+ !kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(PACKET_1BYTE_GUID, !kIncludeVersion)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(PACKET_1BYTE_GUID, !kIncludeVersion)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith0ByteGuid) {
+ QuicFramerPeer::SetLastSerializedGuid(&framer_,
+ GG_UINT64_C(0xFEDCBA9876543210));
+
+ unsigned char packet[] = {
+ // public flags (0 byte guid)
+ 0x30,
+ // guid
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_0BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(PACKET_0BYTE_GUID,
+ !kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(PACKET_0BYTE_GUID, !kIncludeVersion)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(PACKET_0BYTE_GUID, !kIncludeVersion)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWithVersionFlag) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (version)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_TRUE(visitor_.header_->public_header.version_flag);
+ EXPECT_EQ(QUIC_VERSION_7, visitor_.header_->public_header.versions[0]);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_8BYTE_GUID, kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < kVersionOffset) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetSequenceNumberOffset(kIncludeVersion)) {
+ expected_error = "Unable to read protocol version.";
+ } else if (i < GetPrivateFlagsOffset(kIncludeVersion)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(kIncludeVersion)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith4ByteSequenceNumber) {
+ QuicFramerPeer::SetLastSequenceNumber(&framer_,
+ GG_UINT64_C(0x123456789ABA));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid and 4 byte sequence number)
+ 0x2C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_4BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+ PACKET_4BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(!kIncludeVersion,
+ PACKET_4BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith2ByteSequenceNumber) {
+ QuicFramerPeer::SetLastSequenceNumber(&framer_,
+ GG_UINT64_C(0x123456789ABA));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid and 2 byte sequence number)
+ 0x1C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_2BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+ PACKET_2BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(!kIncludeVersion,
+ PACKET_2BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, PacketHeaderWith1ByteSequenceNumber) {
+ QuicFramerPeer::SetLastSequenceNumber(&framer_,
+ GG_UINT64_C(0x123456789ABA));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid and 1 byte sequence number)
+ 0x0C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC,
+ // private flags
+ 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+ EXPECT_FALSE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(0, visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ // Now test framing boundaries
+ for (size_t i = 0;
+ i < GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ ++i) {
+ string expected_error;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < GetSequenceNumberOffset(!kIncludeVersion)) {
+ expected_error = "Unable to read GUID.";
+ } else if (i < GetPrivateFlagsOffset(!kIncludeVersion,
+ PACKET_1BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read sequence number.";
+ } else if (i < GetFecGroupOffset(!kIncludeVersion,
+ PACKET_1BYTE_SEQUENCE_NUMBER)) {
+ expected_error = "Unable to read private flags.";
+ } else {
+ expected_error = "Unable to read first fec protected packet offset.";
+ }
+ CheckProcessingFails(packet, i, expected_error, QUIC_INVALID_PACKET_HEADER);
+ }
+}
+
+TEST_P(QuicFramerTest, InvalidPublicFlag) {
+ unsigned char packet[] = {
+ // public flags, unknown flag at bit 6
+ 0x40,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame count
+ 0x01,
+ // frame type (stream frame)
+ 0x01,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // fin
+ 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+ CheckProcessingFails(packet,
+ arraysize(packet),
+ "Illegal public flags value.",
+ QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, InvalidPublicFlagWithMatchingVersions) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid and version flag and an unknown flag)
+ 0x4D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame count
+ 0x01,
+ // frame type (stream frame)
+ 0x01,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // fin
+ 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+ CheckProcessingFails(packet,
+ arraysize(packet),
+ "Illegal public flags value.",
+ QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, LargePublicFlagWithMismatchedVersions) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid, version flag and an unknown flag)
+ 0x7D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '0',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ 0x07,
+ };
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(0, visitor_.frame_count_);
+ EXPECT_EQ(1, visitor_.version_mismatch_);
+};
+
+TEST_P(QuicFramerTest, InvalidPrivateFlag) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x10,
+
+ // frame count
+ 0x01,
+ // frame type (stream frame)
+ 0x01,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // fin
+ 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+ CheckProcessingFails(packet,
+ arraysize(packet),
+ "Illegal private flags value.",
+ QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, InvalidFECGroupOffset) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ // private flags (fec group)
+ 0x02,
+ // first fec protected packet offset
+ 0x10
+ };
+ CheckProcessingFails(packet,
+ arraysize(packet),
+ "First fec protected packet offset must be less "
+ "than the sequence number.",
+ QUIC_INVALID_PACKET_HEADER);
+};
+
+TEST_P(QuicFramerTest, PaddingFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ 0x07,
+ // Ignored data (which in this case is a stream frame)
+ 0x01,
+ 0x04, 0x03, 0x02, 0x01,
+ 0x01,
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ 0x0c, 0x00,
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(0u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ // A packet with no frames is not acceptable.
+ CheckProcessingFails(
+ packet,
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ "Unable to read frame type.", QUIC_INVALID_FRAME_DATA);
+}
+
+TEST_P(QuicFramerTest, StreamFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(static_cast<uint64>(0x01020304),
+ visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < GetMinStreamFrameSize(framer_.version()); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+ expected_error = "Unable to read fin.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+ kQuicMaxStreamOffsetSize) {
+ expected_error = "Unable to read offset.";
+ } else {
+ expected_error = "Unable to read frame data.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, StreamFrame3ByteStreamId) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xFC,
+ // stream id
+ 0x04, 0x03, 0x02,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(static_cast<uint64>(0x00020304),
+ visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+
+ // Now test framing boundaries
+ const size_t stream_id_size = 3;
+ for (size_t i = 0; i < GetMinStreamFrameSize(framer_.version()); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size - 1) {
+ expected_error = "Unable to read fin.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size +
+ kQuicMaxStreamOffsetSize) {
+ expected_error = "Unable to read offset.";
+ } else {
+ expected_error = "Unable to read frame data.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, StreamFrame2ByteStreamId) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xFA,
+ // stream id
+ 0x04, 0x03,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(static_cast<uint64>(0x00000304),
+ visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+
+ // Now test framing boundaries
+ const size_t stream_id_size = 2;
+ for (size_t i = 0; i < GetMinStreamFrameSize(framer_.version()); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size - 1) {
+ expected_error = "Unable to read fin.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size +
+ kQuicMaxStreamOffsetSize) {
+ expected_error = "Unable to read offset.";
+ } else {
+ expected_error = "Unable to read frame data.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, StreamFrame1ByteStreamId) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xF8,
+ // stream id
+ 0x04,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(static_cast<uint64>(0x00000004),
+ visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+
+ // Now test framing boundaries
+ const size_t stream_id_size = 1;
+ for (size_t i = 0; i < GetMinStreamFrameSize(framer_.version()); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size - 1) {
+ expected_error = "Unable to read fin.";
+ } else if (i < kQuicFrameTypeSize + stream_id_size +
+ kQuicMaxStreamOffsetSize) {
+ expected_error = "Unable to read offset.";
+ } else {
+ expected_error = "Unable to read frame data.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, StreamFrameWithVersion) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (version, 8 byte guid)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(visitor_.header_.get()->public_header.version_flag);
+ EXPECT_EQ(QUIC_VERSION_7, visitor_.header_.get()->public_header.versions[0]);
+ EXPECT_TRUE(CheckDecryption(encrypted, kIncludeVersion));
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(static_cast<uint64>(0x01020304),
+ visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < GetMinStreamFrameSize(framer_.version()); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+ kQuicMaxStreamOffsetSize) {
+ expected_error = "Unable to read offset.";
+ } else {
+ expected_error = "Unable to read frame data.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, RejectPacket) {
+ visitor_.accept_packet_ = false;
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ ASSERT_EQ(0u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+}
+
+TEST_P(QuicFramerTest, RevivedStreamFrame) {
+ unsigned char payload[] = {
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = true;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ // Do not encrypt the payload because the revived payload is post-encryption.
+ EXPECT_TRUE(framer_.ProcessRevivedPacket(&header,
+ StringPiece(AsChars(payload),
+ arraysize(payload))));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_EQ(1, visitor_.revived_packets_);
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.header_->public_header.guid);
+ EXPECT_FALSE(visitor_.header_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.header_->public_header.version_flag);
+ EXPECT_TRUE(visitor_.header_->fec_flag);
+ EXPECT_TRUE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(1 << (header.packet_sequence_number % 8),
+ visitor_.header_->entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.header_->packet_sequence_number);
+ EXPECT_EQ(NOT_IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(0x00u, visitor_.header_->fec_group);
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+}
+
+TEST_P(QuicFramerTest, StreamFrameInFecGroup) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x12, 0x34,
+ // private flags (fec group)
+ 0x02,
+ // first fec protected packet offset
+ 0x02,
+
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+ EXPECT_EQ(IN_FEC_GROUP, visitor_.header_->is_in_fec_group);
+ EXPECT_EQ(GG_UINT64_C(0x341256789ABA),
+ visitor_.header_->fec_group);
+ const size_t fec_offset = GetStartOfFecProtectedData(
+ PACKET_8BYTE_GUID, !kIncludeVersion, PACKET_6BYTE_SEQUENCE_NUMBER);
+ EXPECT_EQ(
+ string(AsChars(packet) + fec_offset, arraysize(packet) - fec_offset),
+ visitor_.fec_protected_payload_);
+
+ ASSERT_EQ(1u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.stream_frames_[0]->stream_id);
+ EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+ EXPECT_EQ(GG_UINT64_C(0xBA98FEDC32107654),
+ visitor_.stream_frames_[0]->offset);
+ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data);
+}
+
+TEST_P(QuicFramerTest, AckFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (ack frame)
+ static_cast<unsigned char>(0x01),
+ // entropy hash of sent packets till least awaiting - 1.
+ 0xAB,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0xBA,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Infinite delta time.
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ ASSERT_EQ(1u, visitor_.ack_frames_.size());
+ const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+ EXPECT_EQ(0xAB, frame.sent_info.entropy_hash);
+ EXPECT_EQ(0xBA, frame.received_info.entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.received_info.largest_observed);
+ ASSERT_EQ(1u, frame.received_info.missing_packets.size());
+ SequenceNumberSet::const_iterator missing_iter =
+ frame.received_info.missing_packets.begin();
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+ EXPECT_EQ(GG_UINT64_C(0x0123456789AA0), frame.sent_info.least_unacked);
+
+ const size_t kSentEntropyOffset = kQuicFrameTypeSize;
+ const size_t kLeastUnackedOffset = kSentEntropyOffset + kQuicEntropyHashSize;
+ const size_t kReceivedEntropyOffset = kLeastUnackedOffset +
+ PACKET_6BYTE_SEQUENCE_NUMBER;
+ const size_t kLargestObservedOffset = kReceivedEntropyOffset +
+ kQuicEntropyHashSize;
+ const size_t kMissingDeltaTimeOffset = kLargestObservedOffset +
+ PACKET_6BYTE_SEQUENCE_NUMBER;
+ const size_t kNumMissingPacketOffset = kMissingDeltaTimeOffset +
+ kQuicDeltaTimeLargestObservedSize;
+ const size_t kMissingPacketsOffset = kNumMissingPacketOffset +
+ kNumberOfMissingPacketsSize;
+ // Now test framing boundaries
+ const size_t missing_packets_size = 1 * PACKET_6BYTE_SEQUENCE_NUMBER;
+ for (size_t i = 0;
+ i < QuicFramer::GetMinAckFrameSize() + missing_packets_size; ++i) {
+ string expected_error;
+ if (i < kSentEntropyOffset) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < kLeastUnackedOffset) {
+ expected_error = "Unable to read entropy hash for sent packets.";
+ } else if (i < kReceivedEntropyOffset) {
+ expected_error = "Unable to read least unacked.";
+ } else if (i < kLargestObservedOffset) {
+ expected_error = "Unable to read entropy hash for received packets.";
+ } else if (i < kMissingDeltaTimeOffset) {
+ expected_error = "Unable to read largest observed.";
+ } else if (i < kNumMissingPacketOffset) {
+ expected_error = "Unable to read delta time largest observed.";
+ } else if (i < kMissingPacketsOffset) {
+ expected_error = "Unable to read num missing packets.";
+ } else {
+ expected_error = "Unable to read sequence number in missing packets.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameTCP) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (tcp)
+ 0x00,
+ // ack_frame.feedback.tcp.accumulated_number_of_lost_packets
+ 0x01, 0x02,
+ // ack_frame.feedback.tcp.receive_window
+ 0x03, 0x04,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ ASSERT_EQ(1u, visitor_.congestion_feedback_frames_.size());
+ const QuicCongestionFeedbackFrame& frame =
+ *visitor_.congestion_feedback_frames_[0];
+ ASSERT_EQ(kTCP, frame.type);
+ EXPECT_EQ(0x0201,
+ frame.tcp.accumulated_number_of_lost_packets);
+ EXPECT_EQ(0x4030u, frame.tcp.receive_window);
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < 6; ++i) {
+ string expected_error;
+ if (i < 1) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < 2) {
+ expected_error = "Unable to read congestion feedback type.";
+ } else if (i < 4) {
+ expected_error = "Unable to read accumulated number of lost packets.";
+ } else if (i < 6) {
+ expected_error = "Unable to read receive window.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameInterArrival) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (inter arrival)
+ 0x01,
+ // accumulated_number_of_lost_packets
+ 0x02, 0x03,
+ // num received packets
+ 0x03,
+ // lowest sequence number
+ 0xBA, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // receive time
+ 0x87, 0x96, 0xA5, 0xB4,
+ 0xC3, 0xD2, 0xE1, 0x07,
+ // sequence delta
+ 0x01, 0x00,
+ // time delta
+ 0x01, 0x00, 0x00, 0x00,
+ // sequence delta (skip one packet)
+ 0x03, 0x00,
+ // time delta
+ 0x02, 0x00, 0x00, 0x00,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ ASSERT_EQ(1u, visitor_.congestion_feedback_frames_.size());
+ const QuicCongestionFeedbackFrame& frame =
+ *visitor_.congestion_feedback_frames_[0];
+ ASSERT_EQ(kInterArrival, frame.type);
+ EXPECT_EQ(0x0302, frame.inter_arrival.
+ accumulated_number_of_lost_packets);
+ ASSERT_EQ(3u, frame.inter_arrival.received_packet_times.size());
+ TimeMap::const_iterator iter =
+ frame.inter_arrival.received_packet_times.begin();
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABA), iter->first);
+ EXPECT_EQ(GG_INT64_C(0x07E1D2C3B4A59687),
+ iter->second.Subtract(start_).ToMicroseconds());
+ ++iter;
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABB), iter->first);
+ EXPECT_EQ(GG_INT64_C(0x07E1D2C3B4A59688),
+ iter->second.Subtract(start_).ToMicroseconds());
+ ++iter;
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABD), iter->first);
+ EXPECT_EQ(GG_INT64_C(0x07E1D2C3B4A59689),
+ iter->second.Subtract(start_).ToMicroseconds());
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < 31; ++i) {
+ string expected_error;
+ if (i < 1) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < 2) {
+ expected_error = "Unable to read congestion feedback type.";
+ } else if (i < 4) {
+ expected_error = "Unable to read accumulated number of lost packets.";
+ } else if (i < 5) {
+ expected_error = "Unable to read num received packets.";
+ } else if (i < 11) {
+ expected_error = "Unable to read smallest received.";
+ } else if (i < 19) {
+ expected_error = "Unable to read time received.";
+ } else if (i < 21) {
+ expected_error = "Unable to read sequence delta in received packets.";
+ } else if (i < 25) {
+ expected_error = "Unable to read time delta in received packets.";
+ } else if (i < 27) {
+ expected_error = "Unable to read sequence delta in received packets.";
+ } else if (i < 31) {
+ expected_error = "Unable to read time delta in received packets.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameFixRate) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (fix rate)
+ 0x02,
+ // bitrate_in_bytes_per_second;
+ 0x01, 0x02, 0x03, 0x04,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ ASSERT_EQ(1u, visitor_.congestion_feedback_frames_.size());
+ const QuicCongestionFeedbackFrame& frame =
+ *visitor_.congestion_feedback_frames_[0];
+ ASSERT_EQ(kFixRate, frame.type);
+ EXPECT_EQ(static_cast<uint32>(0x04030201),
+ frame.fix_rate.bitrate.ToBytesPerSecond());
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < 6; ++i) {
+ string expected_error;
+ if (i < 1) {
+ expected_error = "Unable to read frame type.";
+ } else if (i < 2) {
+ expected_error = "Unable to read congestion feedback type.";
+ } else if (i < 6) {
+ expected_error = "Unable to read bitrate.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_FRAME_DATA);
+ }
+}
+
+
+TEST_P(QuicFramerTest, CongestionFeedbackFrameInvalidFeedback) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (invalid)
+ 0x03,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error());
+}
+
+TEST_P(QuicFramerTest, RstStreamFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (rst stream frame)
+ static_cast<unsigned char>(0x27),
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // error code
+ 0x01, 0x00, 0x00, 0x00,
+
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(GG_UINT64_C(0x01020304), visitor_.rst_stream_frame_.stream_id);
+ EXPECT_EQ(0x01, visitor_.rst_stream_frame_.error_code);
+ EXPECT_EQ("because I can", visitor_.rst_stream_frame_.error_details);
+
+ // Now test framing boundaries
+ for (size_t i = 2; i < 24; ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize) {
+ expected_error = "Unable to read stream_id.";
+ } else if (i < kQuicFrameTypeSize + kQuicMaxStreamIdSize +
+ kQuicErrorCodeSize) {
+ expected_error = "Unable to read rst stream error code.";
+ } else {
+ expected_error = "Unable to read rst stream error details.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_RST_STREAM_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, ConnectionCloseFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (connection close frame)
+ static_cast<unsigned char>(0x2F),
+ // error code
+ 0x11, 0x00, 0x00, 0x00,
+
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+
+ // Ack frame.
+ // entropy hash of sent packets till least awaiting - 1.
+ 0xBF,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0xEB,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Infinite delta time.
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+
+ EXPECT_EQ(0x11, visitor_.connection_close_frame_.error_code);
+ EXPECT_EQ("because I can", visitor_.connection_close_frame_.error_details);
+
+ ASSERT_EQ(1u, visitor_.ack_frames_.size());
+ const QuicAckFrame& frame = *visitor_.ack_frames_[0];
+ EXPECT_EQ(0xBF, frame.sent_info.entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x0123456789AA0), frame.sent_info.least_unacked);
+ EXPECT_EQ(0xEB, frame.received_info.entropy_hash);
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABF), frame.received_info.largest_observed);
+ ASSERT_EQ(1u, frame.received_info.missing_packets.size());
+ SequenceNumberSet::const_iterator missing_iter =
+ frame.received_info.missing_packets.begin();
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABE), *missing_iter);
+
+ // Now test framing boundaries
+ for (size_t i = kQuicFrameTypeSize;
+ i < QuicFramer::GetMinConnectionCloseFrameSize() -
+ QuicFramer::GetMinAckFrameSize(); ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize + kQuicErrorCodeSize) {
+ expected_error = "Unable to read connection close error code.";
+ } else {
+ expected_error = "Unable to read connection close error details.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_CONNECTION_CLOSE_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, GoAwayFrame) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (go away frame)
+ static_cast<unsigned char>(0x37),
+ // error code
+ 0x09, 0x00, 0x00, 0x00,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(GG_UINT64_C(0x01020304),
+ visitor_.goaway_frame_.last_good_stream_id);
+ EXPECT_EQ(0x9, visitor_.goaway_frame_.error_code);
+ EXPECT_EQ("because I can", visitor_.goaway_frame_.reason_phrase);
+
+ const size_t reason_size = arraysize("because I can") - 1;
+ // Now test framing boundaries
+ for (size_t i = kQuicFrameTypeSize;
+ i < QuicFramer::GetMinGoAwayFrameSize() + reason_size; ++i) {
+ string expected_error;
+ if (i < kQuicFrameTypeSize + kQuicErrorCodeSize) {
+ expected_error = "Unable to read go away error code.";
+ } else if (i < kQuicFrameTypeSize + kQuicErrorCodeSize +
+ kQuicMaxStreamIdSize) {
+ expected_error = "Unable to read last good stream id.";
+ } else {
+ expected_error = "Unable to read goaway reason.";
+ }
+ CheckProcessingFails(
+ packet,
+ i + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP),
+ expected_error, QUIC_INVALID_GOAWAY_DATA);
+ }
+}
+
+TEST_P(QuicFramerTest, PublicResetPacket) {
+ unsigned char packet[] = {
+ // public flags (public reset, 8 byte guid)
+ 0x3E,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // nonce proof
+ 0x89, 0x67, 0x45, 0x23,
+ 0x01, 0xEF, 0xCD, 0xAB,
+ // rejected sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.public_reset_packet_.get());
+ EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210),
+ visitor_.public_reset_packet_->public_header.guid);
+ EXPECT_TRUE(visitor_.public_reset_packet_->public_header.reset_flag);
+ EXPECT_FALSE(visitor_.public_reset_packet_->public_header.version_flag);
+ EXPECT_EQ(GG_UINT64_C(0xABCDEF0123456789),
+ visitor_.public_reset_packet_->nonce_proof);
+ EXPECT_EQ(GG_UINT64_C(0x123456789ABC),
+ visitor_.public_reset_packet_->rejected_sequence_number);
+
+ // Now test framing boundaries
+ for (size_t i = 0; i < GetPublicResetPacketSize(); ++i) {
+ string expected_error;
+ DLOG(INFO) << "iteration: " << i;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ CheckProcessingFails(packet, i, expected_error,
+ QUIC_INVALID_PACKET_HEADER);
+ } else if (i < kPublicResetPacketNonceProofOffset) {
+ expected_error = "Unable to read GUID.";
+ CheckProcessingFails(packet, i, expected_error,
+ QUIC_INVALID_PACKET_HEADER);
+ } else if (i < kPublicResetPacketRejectedSequenceNumberOffset) {
+ expected_error = "Unable to read nonce proof.";
+ CheckProcessingFails(packet, i, expected_error,
+ QUIC_INVALID_PUBLIC_RST_PACKET);
+ } else {
+ expected_error = "Unable to read rejected sequence number.";
+ CheckProcessingFails(packet, i, expected_error,
+ QUIC_INVALID_PUBLIC_RST_PACKET);
+ }
+ }
+}
+
+TEST_P(QuicFramerTest, VersionNegotiationPacket) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (version, 8 byte guid)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ 'Q', '2', '.', '0',
+ };
+
+ QuicFramerPeer::SetIsServer(&framer_, false);
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ ASSERT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.version_negotiation_packet_.get());
+ EXPECT_EQ(2u, visitor_.version_negotiation_packet_->versions.size());
+ EXPECT_EQ(QUIC_VERSION_7,
+ visitor_.version_negotiation_packet_->versions[0]);
+
+ for (size_t i = 0; i <= kPublicFlagsSize + PACKET_8BYTE_GUID; ++i) {
+ string expected_error;
+ QuicErrorCode error_code = QUIC_INVALID_PACKET_HEADER;
+ if (i < kGuidOffset) {
+ expected_error = "Unable to read public flags.";
+ } else if (i < kVersionOffset) {
+ expected_error = "Unable to read GUID.";
+ } else {
+ expected_error = "Unable to read supported version in negotiation.";
+ error_code = QUIC_INVALID_VERSION_NEGOTIATION_PACKET;
+ }
+ CheckProcessingFails(packet, i, expected_error, error_code);
+ }
+}
+
+TEST_P(QuicFramerTest, FecPacket) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (fec group & FEC)
+ 0x06,
+ // first fec protected packet offset
+ 0x01,
+
+ // redundancy
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(CheckDecryption(encrypted, !kIncludeVersion));
+
+ EXPECT_EQ(0u, visitor_.stream_frames_.size());
+ EXPECT_EQ(0u, visitor_.ack_frames_.size());
+ ASSERT_EQ(1, visitor_.fec_count_);
+ const QuicFecData& fec_data = *visitor_.fec_data_[0];
+ EXPECT_EQ(GG_UINT64_C(0x0123456789ABB), fec_data.fec_group);
+ EXPECT_EQ("abcdefghijklmnop", fec_data.redundancy);
+}
+
+TEST_P(QuicFramerTest, BuildPaddingFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicPaddingFrame padding_frame;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&padding_frame));
+
+ unsigned char packet[kMaxPacketSize] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ static_cast<unsigned char>(0x07),
+ };
+
+ uint64 header_size =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build4ByteSequenceNumberPaddingFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.public_header.sequence_number_length = PACKET_4BYTE_SEQUENCE_NUMBER;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicPaddingFrame padding_frame;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&padding_frame));
+
+ unsigned char packet[kMaxPacketSize] = {
+ // public flags (8 byte guid and 4 byte sequence number)
+ 0x2C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ static_cast<unsigned char>(0x07),
+ };
+
+ uint64 header_size =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_4BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build2ByteSequenceNumberPaddingFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.public_header.sequence_number_length = PACKET_2BYTE_SEQUENCE_NUMBER;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicPaddingFrame padding_frame;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&padding_frame));
+
+ unsigned char packet[kMaxPacketSize] = {
+ // public flags (8 byte guid and 2 byte sequence number)
+ 0x1C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ static_cast<unsigned char>(0x07),
+ };
+
+ uint64 header_size =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_2BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, Build1ByteSequenceNumberPaddingFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.public_header.sequence_number_length = PACKET_1BYTE_SEQUENCE_NUMBER;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicPaddingFrame padding_frame;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&padding_frame));
+
+ unsigned char packet[kMaxPacketSize] = {
+ // public flags (8 byte guid and 1 byte sequence number)
+ 0x0C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC,
+ // private flags
+ 0x00,
+
+ // frame type (padding frame)
+ static_cast<unsigned char>(0x07),
+ };
+
+ uint64 header_size =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_1BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ memset(packet + header_size + 1, 0x00, kMaxPacketSize - header_size - 1);
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacket) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x77123456789ABC);
+ header.fec_group = 0;
+
+ QuicStreamFrame stream_frame;
+ stream_frame.stream_id = 0x01020304;
+ stream_frame.fin = true;
+ stream_frame.offset = GG_UINT64_C(0xBA98FEDC32107654);
+ stream_frame.data = "hello world!";
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&stream_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (entropy)
+ 0x01,
+
+ // frame type (stream frame with fin and no length)
+ 0xBE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildStreamFramePacketWithVersionFlag) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = true;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x77123456789ABC);
+ header.fec_group = 0;
+
+ QuicStreamFrame stream_frame;
+ stream_frame.stream_id = 0x01020304;
+ stream_frame.fin = true;
+ stream_frame.offset = GG_UINT64_C(0xBA98FEDC32107654);
+ stream_frame.data = "hello world!";
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&stream_frame));
+
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+ unsigned char packet[] = {
+ // public flags (version, 8 byte guid)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (entropy)
+ 0x01,
+
+ // frame type (stream frame with fin and no length)
+ 0xBE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicFramerPeer::SetIsServer(&framer_, false);
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildVersionNegotiationPacket) {
+ QuicPacketPublicHeader header;
+ header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.reset_flag = false;
+ header.version_flag = true;
+
+ unsigned char packet[] = {
+ // public flags (version, 8 byte guid)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '0', '0', '7',
+ };
+
+ QuicVersionVector versions;
+ versions.push_back(QUIC_VERSION_7);
+ scoped_ptr<QuicEncryptedPacket> data(
+ framer_.BuildVersionNegotiationPacket(header, versions));
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildAckFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicAckFrame ack_frame;
+ ack_frame.received_info.entropy_hash = 0x43;
+ ack_frame.received_info.largest_observed = GG_UINT64_C(0x770123456789ABF);
+ ack_frame.received_info.delta_time_largest_observed = QuicTime::Delta::Zero();
+ ack_frame.received_info.missing_packets.insert(
+ GG_UINT64_C(0x770123456789ABE));
+ ack_frame.sent_info.entropy_hash = 0x14;
+ ack_frame.sent_info.least_unacked = GG_UINT64_C(0x770123456789AA0);
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&ack_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (entropy)
+ 0x01,
+
+ // frame type (ack frame)
+ static_cast<unsigned char>(0x01),
+ // entropy hash of sent packets till least awaiting - 1.
+ 0x14,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0x43,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Zero delta time.
+ 0x0, 0x0, 0x0, 0x0,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketTCP) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicCongestionFeedbackFrame congestion_feedback_frame;
+ congestion_feedback_frame.type = kTCP;
+ congestion_feedback_frame.tcp.accumulated_number_of_lost_packets = 0x0201;
+ congestion_feedback_frame.tcp.receive_window = 0x4030;
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&congestion_feedback_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (TCP)
+ 0x00,
+ // accumulated number of lost packets
+ 0x01, 0x02,
+ // TCP receive window
+ 0x03, 0x04,
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketInterArrival) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicCongestionFeedbackFrame frame;
+ frame.type = kInterArrival;
+ frame.inter_arrival.accumulated_number_of_lost_packets = 0x0302;
+ frame.inter_arrival.received_packet_times.insert(
+ make_pair(GG_UINT64_C(0x0123456789ABA),
+ start_.Add(QuicTime::Delta::FromMicroseconds(
+ GG_UINT64_C(0x07E1D2C3B4A59687)))));
+ frame.inter_arrival.received_packet_times.insert(
+ make_pair(GG_UINT64_C(0x0123456789ABB),
+ start_.Add(QuicTime::Delta::FromMicroseconds(
+ GG_UINT64_C(0x07E1D2C3B4A59688)))));
+ frame.inter_arrival.received_packet_times.insert(
+ make_pair(GG_UINT64_C(0x0123456789ABD),
+ start_.Add(QuicTime::Delta::FromMicroseconds(
+ GG_UINT64_C(0x07E1D2C3B4A59689)))));
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (inter arrival)
+ 0x01,
+ // accumulated_number_of_lost_packets
+ 0x02, 0x03,
+ // num received packets
+ 0x03,
+ // lowest sequence number
+ 0xBA, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // receive time
+ 0x87, 0x96, 0xA5, 0xB4,
+ 0xC3, 0xD2, 0xE1, 0x07,
+ // sequence delta
+ 0x01, 0x00,
+ // time delta
+ 0x01, 0x00, 0x00, 0x00,
+ // sequence delta (skip one packet)
+ 0x03, 0x00,
+ // time delta
+ 0x02, 0x00, 0x00, 0x00,
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketFixRate) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicCongestionFeedbackFrame congestion_feedback_frame;
+ congestion_feedback_frame.type = kFixRate;
+ congestion_feedback_frame.fix_rate.bitrate
+ = QuicBandwidth::FromBytesPerSecond(0x04030201);
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&congestion_feedback_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (congestion feedback frame)
+ 0x03,
+ // congestion feedback type (fix rate)
+ 0x02,
+ // bitrate_in_bytes_per_second;
+ 0x01, 0x02, 0x03, 0x04,
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCongestionFeedbackFramePacketInvalidFeedback) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicCongestionFeedbackFrame congestion_feedback_frame;
+ congestion_feedback_frame.type =
+ static_cast<CongestionFeedbackType>(kFixRate + 1);
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&congestion_feedback_frame));
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data == NULL);
+}
+
+TEST_P(QuicFramerTest, BuildRstFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicRstStreamFrame rst_frame;
+ rst_frame.stream_id = 0x01020304;
+ rst_frame.error_code = static_cast<QuicRstStreamErrorCode>(0x05060708);
+ rst_frame.error_details = "because I can";
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (rst stream frame)
+ static_cast<unsigned char>(0x27),
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // error code
+ 0x08, 0x07, 0x06, 0x05,
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&rst_frame));
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildCloseFramePacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicConnectionCloseFrame close_frame;
+ close_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+ close_frame.error_details = "because I can";
+
+ QuicAckFrame* ack_frame = &close_frame.ack_frame;
+ ack_frame->received_info.entropy_hash = 0x43;
+ ack_frame->received_info.largest_observed = GG_UINT64_C(0x0123456789ABF);
+ ack_frame->received_info.missing_packets.insert(GG_UINT64_C(0x0123456789ABE));
+ ack_frame->sent_info.entropy_hash = 0xE0;
+ ack_frame->sent_info.least_unacked = GG_UINT64_C(0x0123456789AA0);
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&close_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (entropy)
+ 0x01,
+
+ // frame type (connection close frame)
+ static_cast<unsigned char>(0x2F),
+ // error code
+ 0x08, 0x07, 0x06, 0x05,
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+
+ // Ack frame.
+ // entropy hash of sent packets till least awaiting - 1.
+ 0xE0,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0x43,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Infinite delta time.
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildGoAwayPacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicGoAwayFrame goaway_frame;
+ goaway_frame.error_code = static_cast<QuicErrorCode>(0x05060708);
+ goaway_frame.last_good_stream_id = 0x01020304;
+ goaway_frame.reason_phrase = "because I can";
+
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&goaway_frame));
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags(entropy)
+ 0x01,
+
+ // frame type (go away frame)
+ static_cast<unsigned char>(0x37),
+ // error code
+ 0x08, 0x07, 0x06, 0x05,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildPublicResetPacket) {
+ QuicPublicResetPacket reset_packet;
+ reset_packet.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ reset_packet.public_header.reset_flag = true;
+ reset_packet.public_header.version_flag = false;
+ reset_packet.rejected_sequence_number = GG_UINT64_C(0x123456789ABC);
+ reset_packet.nonce_proof = GG_UINT64_C(0xABCDEF0123456789);
+
+ unsigned char packet[] = {
+ // public flags (public reset, 8 byte GUID)
+ 0x3E,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // nonce proof
+ 0x89, 0x67, 0x45, 0x23,
+ 0x01, 0xEF, 0xCD, 0xAB,
+ // rejected sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ scoped_ptr<QuicEncryptedPacket> data(
+ framer_.BuildPublicResetPacket(reset_packet));
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, BuildFecPacket) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = true;
+ header.entropy_flag = true;
+ header.packet_sequence_number = (GG_UINT64_C(0x123456789ABC));
+ header.is_in_fec_group = IN_FEC_GROUP;
+ header.fec_group = GG_UINT64_C(0x123456789ABB);;
+
+ QuicFecData fec_data;
+ fec_data.fec_group = 1;
+ fec_data.redundancy = "abcdefghijklmnop";
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (entropy & fec group & fec packet)
+ 0x07,
+ // first fec protected packet offset
+ 0x01,
+
+ // redundancy
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p',
+ };
+
+ scoped_ptr<QuicPacket> data(
+ framer_.BuildFecPacket(header, fec_data).packet);
+ ASSERT_TRUE(data != NULL);
+
+ test::CompareCharArraysWithHexError("constructed packet",
+ data->data(), data->length(),
+ AsChars(packet), arraysize(packet));
+}
+
+TEST_P(QuicFramerTest, EncryptPacket) {
+ QuicPacketSequenceNumber sequence_number = GG_UINT64_C(0x123456789ABC);
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (fec group & fec packet)
+ 0x06,
+ // first fec protected packet offset
+ 0x01,
+
+ // redundancy
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p',
+ };
+
+ scoped_ptr<QuicPacket> raw(
+ QuicPacket::NewDataPacket(AsChars(packet), arraysize(packet), false,
+ PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER));
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number, *raw));
+
+ ASSERT_TRUE(encrypted.get() != NULL);
+ EXPECT_TRUE(CheckEncryption(sequence_number, raw.get()));
+}
+
+TEST_P(QuicFramerTest, EncryptPacketWithVersionFlag) {
+ QuicPacketSequenceNumber sequence_number = GG_UINT64_C(0x123456789ABC);
+ unsigned char packet[] = {
+ // public flags (version, 8 byte guid)
+ 0x3D,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // version tag
+ 'Q', '.', '1', '0',
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (fec group & fec flags)
+ 0x06,
+ // first fec protected packet offset
+ 0x01,
+
+ // redundancy
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p',
+ };
+
+ scoped_ptr<QuicPacket> raw(
+ QuicPacket::NewDataPacket(AsChars(packet), arraysize(packet), false,
+ PACKET_8BYTE_GUID, kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER));
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number, *raw));
+
+ ASSERT_TRUE(encrypted.get() != NULL);
+ EXPECT_TRUE(CheckEncryption(sequence_number, raw.get()));
+}
+
+// TODO(rch): re-enable after https://codereview.chromium.org/11820005/
+// lands. Currently this is causing valgrind problems, but it should be
+// fixed in the followup CL.
+TEST_P(QuicFramerTest, DISABLED_CalculateLargestReceived) {
+ SequenceNumberSet missing;
+ missing.insert(1);
+ missing.insert(5);
+ missing.insert(7);
+
+ // These two we just walk to the next gap, and return the largest seen.
+ EXPECT_EQ(4u, QuicFramer::CalculateLargestObserved(missing, missing.find(1)));
+ EXPECT_EQ(6u, QuicFramer::CalculateLargestObserved(missing, missing.find(5)));
+
+ missing.insert(2);
+ // For 1, we can't go forward as 2 would be implicitly acked so we return the
+ // largest missing packet.
+ EXPECT_EQ(1u, QuicFramer::CalculateLargestObserved(missing, missing.find(1)));
+ // For 2, we've seen 3 and 4, so can admit to a largest observed.
+ EXPECT_EQ(4u, QuicFramer::CalculateLargestObserved(missing, missing.find(2)));
+}
+
+// TODO(rch) enable after landing the revised truncation CL.
+TEST_P(QuicFramerTest, DISABLED_Truncation) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = false;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicConnectionCloseFrame close_frame;
+ QuicAckFrame* ack_frame = &close_frame.ack_frame;
+ close_frame.error_code = static_cast<QuicErrorCode>(0x05);
+ close_frame.error_details = "because I can";
+ ack_frame->received_info.largest_observed = 201;
+ ack_frame->sent_info.least_unacked = 0;
+ for (uint64 i = 1; i < ack_frame->received_info.largest_observed; ++i) {
+ ack_frame->received_info.missing_packets.insert(i);
+ }
+
+ // Create a packet with just the ack
+ QuicFrame frame;
+ frame.type = ACK_FRAME;
+ frame.ack_frame = ack_frame;
+ QuicFrames frames;
+ frames.push_back(frame);
+
+ scoped_ptr<QuicPacket> raw_ack_packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_ack_packet != NULL);
+
+ scoped_ptr<QuicEncryptedPacket> ack_packet(
+ framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+ *raw_ack_packet));
+
+ // Create a packet with just connection close.
+ frames.clear();
+ frame.type = CONNECTION_CLOSE_FRAME;
+ frame.connection_close_frame = &close_frame;
+ frames.push_back(frame);
+
+ scoped_ptr<QuicPacket> raw_close_packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_close_packet != NULL);
+
+ scoped_ptr<QuicEncryptedPacket> close_packet(
+ framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+ *raw_close_packet));
+
+ // Now make sure we can turn our ack packet back into an ack frame
+ ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+
+ // And do the same for the close frame.
+ ASSERT_TRUE(framer_.ProcessPacket(*close_packet));
+}
+
+TEST_P(QuicFramerTest, CleanTruncation) {
+ QuicPacketHeader header;
+ header.public_header.guid = GG_UINT64_C(0xFEDCBA9876543210);
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.fec_flag = false;
+ header.entropy_flag = true;
+ header.packet_sequence_number = GG_UINT64_C(0x123456789ABC);
+ header.fec_group = 0;
+
+ QuicConnectionCloseFrame close_frame;
+ QuicAckFrame* ack_frame = &close_frame.ack_frame;
+ close_frame.error_code = static_cast<QuicErrorCode>(0x05);
+ close_frame.error_details = "because I can";
+ ack_frame->received_info.largest_observed = 201;
+ ack_frame->sent_info.least_unacked = 0;
+ for (uint64 i = 1; i < ack_frame->received_info.largest_observed; ++i) {
+ ack_frame->received_info.missing_packets.insert(i);
+ }
+
+ // Create a packet with just the ack
+ QuicFrame frame;
+ frame.type = ACK_FRAME;
+ frame.ack_frame = ack_frame;
+ QuicFrames frames;
+ frames.push_back(frame);
+
+ scoped_ptr<QuicPacket> raw_ack_packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_ack_packet != NULL);
+
+ scoped_ptr<QuicEncryptedPacket> ack_packet(
+ framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+ *raw_ack_packet));
+
+ // Create a packet with just connection close.
+ frames.clear();
+ frame.type = CONNECTION_CLOSE_FRAME;
+ frame.connection_close_frame = &close_frame;
+ frames.push_back(frame);
+
+ scoped_ptr<QuicPacket> raw_close_packet(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_close_packet != NULL);
+
+ scoped_ptr<QuicEncryptedPacket> close_packet(
+ framer_.EncryptPacket(ENCRYPTION_NONE, header.packet_sequence_number,
+ *raw_close_packet));
+
+ // Now make sure we can turn our ack packet back into an ack frame
+ ASSERT_TRUE(framer_.ProcessPacket(*ack_packet));
+
+ // And do the same for the close frame.
+ ASSERT_TRUE(framer_.ProcessPacket(*close_packet));
+
+ // Test for clean truncation of the ack by comparing the length of the
+ // original packets to the re-serialized packets.
+ frames.clear();
+ frame.type = ACK_FRAME;
+ frame.ack_frame = visitor_.ack_frames_[0];
+ frames.push_back(frame);
+
+ size_t original_raw_length = raw_ack_packet->length();
+ raw_ack_packet.reset(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_ack_packet != NULL);
+ EXPECT_EQ(original_raw_length, raw_ack_packet->length());
+
+ frames.clear();
+ frame.type = CONNECTION_CLOSE_FRAME;
+ frame.connection_close_frame = &visitor_.connection_close_frame_;
+ frames.push_back(frame);
+
+ original_raw_length = raw_close_packet->length();
+ raw_close_packet.reset(
+ framer_.BuildUnsizedDataPacket(header, frames).packet);
+ ASSERT_TRUE(raw_ack_packet != NULL);
+ EXPECT_EQ(original_raw_length, raw_close_packet->length());
+}
+
+TEST_P(QuicFramerTest, EntropyFlagTest) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (Entropy)
+ 0x01,
+
+ // frame type (stream frame with fin and no length)
+ 0xBE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(1 << 4, visitor_.header_->entropy_hash);
+ EXPECT_FALSE(visitor_.header_->fec_flag);
+};
+
+TEST_P(QuicFramerTest, FecEntropyTest) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags (Entropy & fec group & FEC)
+ 0x07,
+ // first fec protected packet offset
+ 0xFF,
+
+ // frame type (stream frame with fin and no length)
+ 0xBE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+ };
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+ ASSERT_TRUE(visitor_.header_.get());
+ EXPECT_TRUE(visitor_.header_->fec_flag);
+ EXPECT_TRUE(visitor_.header_->entropy_flag);
+ EXPECT_EQ(1 << 4, visitor_.header_->entropy_hash);
+};
+
+TEST_P(QuicFramerTest, StopPacketProcessing) {
+ // Set a specific version.
+ framer_.set_version(QUIC_VERSION_7);
+
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Entropy
+ 0x01,
+
+ // frame type (stream frame with fin)
+ 0xFE,
+ // stream id
+ 0x04, 0x03, 0x02, 0x01,
+ // offset
+ 0x54, 0x76, 0x10, 0x32,
+ 0xDC, 0xFE, 0x98, 0xBA,
+ // data length
+ 0x0c, 0x00,
+ // data
+ 'h', 'e', 'l', 'l',
+ 'o', ' ', 'w', 'o',
+ 'r', 'l', 'd', '!',
+
+ // frame type (ack frame)
+ 0x02,
+ // entropy hash of sent packets till least awaiting - 1.
+ 0x14,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0x43,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ MockFramerVisitor visitor;
+ framer_.set_visitor(&visitor);
+ EXPECT_CALL(visitor, OnPacket());
+ EXPECT_CALL(visitor, OnPacketHeader(_));
+ EXPECT_CALL(visitor, OnStreamFrame(_)).WillOnce(Return(false));
+ EXPECT_CALL(visitor, OnAckFrame(_)).Times(0);
+ EXPECT_CALL(visitor, OnPacketComplete());
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+}
+
+TEST_P(QuicFramerTest, ConnectionCloseWithInvalidAck) {
+ unsigned char packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00,
+
+ // frame type (connection close frame)
+ static_cast<unsigned char>(0x2F),
+ // error code
+ 0x11, 0x00, 0x00, 0x00,
+ // error details length
+ 0x0d, 0x00,
+ // error details
+ 'b', 'e', 'c', 'a',
+ 'u', 's', 'e', ' ',
+ 'I', ' ', 'c', 'a',
+ 'n',
+
+ // Ack frame.
+ // entropy hash of sent packets till least awaiting - 1.
+ 0xE0,
+ // least packet sequence number awaiting an ack
+ 0xA0, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // entropy hash of all received packets.
+ 0x43,
+ // largest observed packet sequence number
+ 0xBF, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // Infinite delta time.
+ 0xFF, 0xFF, 0xFF, 0xFF,
+ // num missing packets
+ 0x01,
+ // missing packet
+ 0xBE, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ };
+
+ MockFramerVisitor visitor;
+ framer_.set_visitor(&visitor);
+ EXPECT_CALL(visitor, OnPacket());
+ EXPECT_CALL(visitor, OnPacketHeader(_));
+ EXPECT_CALL(visitor, OnAckFrame(_)).WillOnce(Return(false));
+ EXPECT_CALL(visitor, OnConnectionCloseFrame(_)).Times(0);
+ EXPECT_CALL(visitor, OnPacketComplete());
+
+ QuicEncryptedPacket encrypted(AsChars(packet), arraysize(packet), false);
+ EXPECT_TRUE(framer_.ProcessPacket(encrypted));
+ EXPECT_EQ(QUIC_NO_ERROR, framer_.error());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_http_stream.cc b/chromium/net/quic/quic_http_stream.cc
new file mode 100644
index 00000000000..73581248d5b
--- /dev/null
+++ b/chromium/net/quic/quic_http_stream.cc
@@ -0,0 +1,512 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_http_stream.h"
+
+#include "base/callback_helpers.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_reliable_client_stream.h"
+#include "net/quic/quic_utils.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+
+static const size_t kHeaderBufInitialSize = 4096;
+
+QuicHttpStream::QuicHttpStream(const base::WeakPtr<QuicClientSession> session)
+ : next_state_(STATE_NONE),
+ session_(session),
+ stream_(NULL),
+ request_info_(NULL),
+ request_body_stream_(NULL),
+ response_info_(NULL),
+ response_status_(OK),
+ response_headers_received_(false),
+ read_buf_(new GrowableIOBuffer()),
+ user_buffer_len_(0),
+ weak_factory_(this) {
+ DCHECK(session_);
+}
+
+QuicHttpStream::~QuicHttpStream() {
+ Close(false);
+}
+
+int QuicHttpStream::InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback) {
+ DCHECK(!stream_);
+ if (!session_)
+ return ERR_CONNECTION_CLOSED;
+
+ stream_net_log_ = stream_net_log;
+ request_info_ = request_info;
+
+ int rv = stream_request_.StartRequest(
+ session_, &stream_, base::Bind(&QuicHttpStream::OnStreamReady,
+ weak_factory_.GetWeakPtr()));
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ if (rv == OK)
+ stream_->SetDelegate(this);
+
+ return rv;
+}
+
+void QuicHttpStream::OnStreamReady(int rv) {
+ DCHECK(rv == OK || !stream_);
+ if (rv == OK)
+ stream_->SetDelegate(this);
+
+ ResetAndReturn(&callback_).Run(rv);
+}
+
+int QuicHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ CHECK(stream_);
+ CHECK(!request_body_stream_);
+ CHECK(!response_info_);
+ CHECK(!callback.is_null());
+ CHECK(response);
+
+ // Store the serialized request headers.
+ SpdyHeaderBlock headers;
+ CreateSpdyHeadersFromHttpRequest(*request_info_, request_headers,
+ &headers, 3, /*direct=*/true);
+ request_ = stream_->compressor()->CompressHeaders(headers);
+ // Log the actual request with the URL Request's net log.
+ stream_net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS,
+ base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
+ // Also log to the QuicSession's net log.
+ stream_->net_log().AddEvent(
+ NetLog::TYPE_QUIC_HTTP_STREAM_SEND_REQUEST_HEADERS,
+ base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
+
+ // Store the request body.
+ request_body_stream_ = request_info_->upload_data_stream;
+ if (request_body_stream_) {
+ // TODO(rch): Can we be more precise about when to allocate
+ // raw_request_body_buf_. Removed the following check. DoReadRequestBody()
+ // was being called even if we didn't yet allocate raw_request_body_buf_.
+ // && (request_body_stream_->size() ||
+ // request_body_stream_->is_chunked()))
+ //
+ // Use kMaxPacketSize as the buffer size, since the request
+ // body data is written with this size at a time.
+ // TODO(rch): use a smarter value since we can't write an entire
+ // packet due to overhead.
+ raw_request_body_buf_ = new IOBufferWithSize(kMaxPacketSize);
+ // The request body buffer is empty at first.
+ request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_.get(), 0);
+ }
+
+ // Store the response info.
+ response_info_ = response;
+
+ next_state_ = STATE_SEND_HEADERS;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv > 0 ? OK : rv;
+}
+
+UploadProgress QuicHttpStream::GetUploadProgress() const {
+ if (!request_body_stream_)
+ return UploadProgress();
+
+ return UploadProgress(request_body_stream_->position(),
+ request_body_stream_->size());
+}
+
+int QuicHttpStream::ReadResponseHeaders(const CompletionCallback& callback) {
+ CHECK(!callback.is_null());
+
+ if (stream_ == NULL)
+ return response_status_;
+
+ // Check if we already have the response headers. If so, return synchronously.
+ if (response_headers_received_)
+ return OK;
+
+ // Still waiting for the response, return IO_PENDING.
+ CHECK(callback_.is_null());
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+const HttpResponseInfo* QuicHttpStream::GetResponseInfo() const {
+ return response_info_;
+}
+
+int QuicHttpStream::ReadResponseBody(
+ IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+ CHECK(buf);
+ CHECK(buf_len);
+ CHECK(!callback.is_null());
+
+ // If we have data buffered, complete the IO immediately.
+ if (!response_body_.empty()) {
+ int bytes_read = 0;
+ while (!response_body_.empty() && buf_len > 0) {
+ scoped_refptr<IOBufferWithSize> data = response_body_.front();
+ const int bytes_to_copy = std::min(buf_len, data->size());
+ memcpy(&(buf->data()[bytes_read]), data->data(), bytes_to_copy);
+ buf_len -= bytes_to_copy;
+ if (bytes_to_copy == data->size()) {
+ response_body_.pop_front();
+ } else {
+ const int bytes_remaining = data->size() - bytes_to_copy;
+ IOBufferWithSize* new_buffer = new IOBufferWithSize(bytes_remaining);
+ memcpy(new_buffer->data(), &(data->data()[bytes_to_copy]),
+ bytes_remaining);
+ response_body_.pop_front();
+ response_body_.push_front(make_scoped_refptr(new_buffer));
+ }
+ bytes_read += bytes_to_copy;
+ }
+ return bytes_read;
+ }
+
+ if (!stream_) {
+ // If the stream is already closed, there is no body to read.
+ return response_status_;
+ }
+
+ CHECK(callback_.is_null());
+ CHECK(!user_buffer_.get());
+ CHECK_EQ(0, user_buffer_len_);
+
+ callback_ = callback;
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+}
+
+void QuicHttpStream::Close(bool not_reusable) {
+ // Note: the not_reusable flag has no meaning for SPDY streams.
+ if (stream_) {
+ stream_->SetDelegate(NULL);
+ stream_->Close(QUIC_STREAM_NO_ERROR);
+ stream_ = NULL;
+ }
+}
+
+HttpStream* QuicHttpStream::RenewStreamForAuth() {
+ return NULL;
+}
+
+bool QuicHttpStream::IsResponseBodyComplete() const {
+ return next_state_ == STATE_OPEN && !stream_;
+}
+
+bool QuicHttpStream::CanFindEndOfResponse() const {
+ return true;
+}
+
+bool QuicHttpStream::IsConnectionReused() const {
+ // TODO(rch): do something smarter here.
+ return stream_ && stream_->id() > 1;
+}
+
+void QuicHttpStream::SetConnectionReused() {
+ // QUIC doesn't need an indicator here.
+}
+
+bool QuicHttpStream::IsConnectionReusable() const {
+ // QUIC streams aren't considered reusable.
+ return false;
+}
+
+bool QuicHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ // TODO(mmenke): Figure out what to do here.
+ return true;
+}
+
+void QuicHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+ DCHECK(stream_);
+ stream_->GetSSLInfo(ssl_info);
+}
+
+void QuicHttpStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK(stream_);
+ NOTIMPLEMENTED();
+}
+
+bool QuicHttpStream::IsSpdyHttpStream() const {
+ return false;
+}
+
+void QuicHttpStream::Drain(HttpNetworkSession* session) {
+ Close(false);
+ delete this;
+}
+
+void QuicHttpStream::SetPriority(RequestPriority priority) {
+ // Nothing to do here (yet).
+}
+
+int QuicHttpStream::OnSendData() {
+ // TODO(rch): Change QUIC IO to provide notifications to the streams.
+ NOTREACHED();
+ return OK;
+}
+
+int QuicHttpStream::OnSendDataComplete(int status, bool* eof) {
+ // TODO(rch): Change QUIC IO to provide notifications to the streams.
+ NOTREACHED();
+ return OK;
+}
+
+int QuicHttpStream::OnDataReceived(const char* data, int length) {
+ DCHECK_NE(0, length);
+ // Are we still reading the response headers.
+ if (!response_headers_received_) {
+ // Grow the read buffer if necessary.
+ if (read_buf_->RemainingCapacity() < length) {
+ read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize);
+ }
+ memcpy(read_buf_->data(), data, length);
+ read_buf_->set_offset(read_buf_->offset() + length);
+ int rv = ParseResponseHeaders();
+ if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+ DoCallback(rv);
+ }
+ return OK;
+ }
+
+ if (callback_.is_null()) {
+ BufferResponseBody(data, length);
+ return OK;
+ }
+
+ if (length <= user_buffer_len_) {
+ memcpy(user_buffer_->data(), data, length);
+ } else {
+ memcpy(user_buffer_->data(), data, user_buffer_len_);
+ int delta = length - user_buffer_len_;
+ BufferResponseBody(data + user_buffer_len_, delta);
+ }
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ DoCallback(length);
+ return OK;
+}
+
+void QuicHttpStream::OnClose(QuicErrorCode error) {
+ if (error != QUIC_NO_ERROR) {
+ response_status_ = ERR_QUIC_PROTOCOL_ERROR;
+ } else if (!response_headers_received_) {
+ response_status_ = ERR_ABORTED;
+ }
+
+ stream_ = NULL;
+ if (!callback_.is_null())
+ DoCallback(response_status_);
+}
+
+void QuicHttpStream::OnError(int error) {
+ stream_ = NULL;
+ response_status_ = error;
+ if (!callback_.is_null())
+ DoCallback(response_status_);
+}
+
+void QuicHttpStream::OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+
+ if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+ DoCallback(rv);
+ }
+}
+
+void QuicHttpStream::DoCallback(int rv) {
+ CHECK_NE(rv, ERR_IO_PENDING);
+ CHECK(!callback_.is_null());
+
+ // The client callback can do anything, including destroying this class,
+ // so any pending callback must be issued after everything else is done.
+ base::ResetAndReturn(&callback_).Run(rv);
+}
+
+int QuicHttpStream::DoLoop(int rv) {
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_SEND_HEADERS:
+ CHECK_EQ(OK, rv);
+ rv = DoSendHeaders();
+ break;
+ case STATE_SEND_HEADERS_COMPLETE:
+ rv = DoSendHeadersComplete(rv);
+ break;
+ case STATE_READ_REQUEST_BODY:
+ CHECK_EQ(OK, rv);
+ rv = DoReadRequestBody();
+ break;
+ case STATE_READ_REQUEST_BODY_COMPLETE:
+ rv = DoReadRequestBodyComplete(rv);
+ break;
+ case STATE_SEND_BODY:
+ CHECK_EQ(OK, rv);
+ rv = DoSendBody();
+ break;
+ case STATE_SEND_BODY_COMPLETE:
+ rv = DoSendBodyComplete(rv);
+ break;
+ case STATE_OPEN:
+ CHECK_EQ(OK, rv);
+ break;
+ default:
+ NOTREACHED() << "next_state_: " << next_state_;
+ break;
+ }
+ } while (next_state_ != STATE_NONE && next_state_ != STATE_OPEN &&
+ rv != ERR_IO_PENDING);
+
+ return rv;
+}
+
+int QuicHttpStream::DoSendHeaders() {
+ if (!stream_)
+ return ERR_UNEXPECTED;
+
+ bool has_upload_data = request_body_stream_ != NULL;
+
+ next_state_ = STATE_SEND_HEADERS_COMPLETE;
+ QuicConsumedData rv = stream_->WriteData(request_, !has_upload_data);
+ return rv.bytes_consumed;
+}
+
+int QuicHttpStream::DoSendHeadersComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ next_state_ = request_body_stream_ ?
+ STATE_READ_REQUEST_BODY : STATE_OPEN;
+
+ return OK;
+}
+
+int QuicHttpStream::DoReadRequestBody() {
+ next_state_ = STATE_READ_REQUEST_BODY_COMPLETE;
+ return request_body_stream_->Read(
+ raw_request_body_buf_.get(),
+ raw_request_body_buf_->size(),
+ base::Bind(&QuicHttpStream::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int QuicHttpStream::DoReadRequestBodyComplete(int rv) {
+ // |rv| is the result of read from the request body from the last call to
+ // DoSendBody().
+ if (rv < 0)
+ return rv;
+
+ request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_.get(), rv);
+ if (rv == 0) { // Reached the end.
+ DCHECK(request_body_stream_->IsEOF());
+ }
+
+ next_state_ = STATE_SEND_BODY;
+ return OK;
+}
+
+int QuicHttpStream::DoSendBody() {
+ if (!stream_)
+ return ERR_UNEXPECTED;
+
+ CHECK(request_body_stream_);
+ CHECK(request_body_buf_.get());
+ const bool eof = request_body_stream_->IsEOF();
+ int len = request_body_buf_->BytesRemaining();
+ if (len > 0 || eof) {
+ base::StringPiece data(request_body_buf_->data(), len);
+ QuicConsumedData rv = stream_->WriteData(data, eof);
+ request_body_buf_->DidConsume(rv.bytes_consumed);
+ if (eof) {
+ next_state_ = STATE_OPEN;
+ return OK;
+ }
+ next_state_ = STATE_SEND_BODY_COMPLETE;
+ return rv.bytes_consumed;
+ }
+
+ next_state_ = STATE_SEND_BODY_COMPLETE;
+ return OK;
+}
+
+int QuicHttpStream::DoSendBodyComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ next_state_ = STATE_READ_REQUEST_BODY;
+ return OK;
+}
+
+int QuicHttpStream::ParseResponseHeaders() {
+ size_t read_buf_len = static_cast<size_t>(read_buf_->offset());
+ SpdyFramer framer(SPDY3);
+ SpdyHeaderBlock headers;
+ char* data = read_buf_->StartOfBuffer();
+ size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(),
+ &headers);
+
+ if (len == 0) {
+ return ERR_IO_PENDING;
+ }
+
+ // Save the remaining received data.
+ size_t delta = read_buf_len - len;
+ if (delta > 0) {
+ BufferResponseBody(data + len, delta);
+ }
+
+ // The URLRequest logs these headers, so only log to the QuicSession's
+ // net log.
+ stream_->net_log().AddEvent(
+ NetLog::TYPE_QUIC_HTTP_STREAM_READ_RESPONSE_HEADERS,
+ base::Bind(&SpdyHeaderBlockNetLogCallback, &headers));
+
+ if (!SpdyHeadersToHttpResponse(headers, 3, response_info_)) {
+ DLOG(WARNING) << "Invalid headers";
+ return ERR_QUIC_PROTOCOL_ERROR;
+ }
+ // Put the peer's IP address and port into the response.
+ IPEndPoint address = stream_->GetPeerAddress();
+ response_info_->socket_address = HostPortPair::FromIPEndPoint(address);
+ response_info_->connection_info =
+ HttpResponseInfo::CONNECTION_INFO_QUIC1_SPDY3;
+ response_info_->vary_data
+ .Init(*request_info_, *response_info_->headers.get());
+ response_info_->was_npn_negotiated = true;
+ response_info_->npn_negotiated_protocol = "quic/1+spdy/3";
+ response_headers_received_ = true;
+
+ return OK;
+}
+
+void QuicHttpStream::BufferResponseBody(const char* data, int length) {
+ if (length == 0)
+ return;
+ IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+ memcpy(io_buffer->data(), data, length);
+ response_body_.push_back(make_scoped_refptr(io_buffer));
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_http_stream.h b/chromium/net/quic/quic_http_stream.h
new file mode 100644
index 00000000000..cc2b973e87f
--- /dev/null
+++ b/chromium/net/quic/quic_http_stream.h
@@ -0,0 +1,149 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_HTTP_STREAM_H_
+#define NET_QUIC_QUIC_HTTP_STREAM_H_
+
+#include <list>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_stream.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+// The QuicHttpStream is a QUIC-specific HttpStream subclass. It holds a
+// non-owning pointer to a QuicReliableClientStream which it uses to
+// send and receive data.
+class NET_EXPORT_PRIVATE QuicHttpStream :
+ public QuicReliableClientStream::Delegate,
+ public HttpStream {
+ public:
+ explicit QuicHttpStream(const base::WeakPtr<QuicClientSession> session);
+
+ virtual ~QuicHttpStream();
+
+ // HttpStream implementation.
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual int ReadResponseBody(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Close(bool not_reusable) OVERRIDE;
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+ virtual bool IsConnectionReused() const OVERRIDE;
+ virtual void SetConnectionReused() OVERRIDE;
+ virtual bool IsConnectionReusable() const OVERRIDE;
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ // QuicReliableClientStream::Delegate implementation
+ virtual int OnSendData() OVERRIDE;
+ virtual int OnSendDataComplete(int status, bool* eof) OVERRIDE;
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+ virtual void OnClose(QuicErrorCode error) OVERRIDE;
+ virtual void OnError(int error) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_SEND_HEADERS,
+ STATE_SEND_HEADERS_COMPLETE,
+ STATE_READ_REQUEST_BODY,
+ STATE_READ_REQUEST_BODY_COMPLETE,
+ STATE_SEND_BODY,
+ STATE_SEND_BODY_COMPLETE,
+ STATE_OPEN,
+ };
+
+ void OnStreamReady(int rv);
+ void OnIOComplete(int rv);
+ void DoCallback(int rv);
+
+ int DoLoop(int);
+ int DoSendHeaders();
+ int DoSendHeadersComplete(int rv);
+ int DoReadRequestBody();
+ int DoReadRequestBodyComplete(int rv);
+ int DoSendBody();
+ int DoSendBodyComplete(int rv);
+ int DoReadResponseHeaders();
+ int DoReadResponseHeadersComplete(int rv);
+
+ int ParseResponseHeaders();
+
+ void BufferResponseBody(const char* data, int length);
+
+ State next_state_;
+
+ const base::WeakPtr<QuicClientSession> session_;
+ QuicClientSession::StreamRequest stream_request_;
+ QuicReliableClientStream* stream_; // Non-owning.
+
+ // The following three fields are all owned by the caller and must
+ // outlive this object, according to the HttpStream contract.
+
+ // The request to send.
+ const HttpRequestInfo* request_info_;
+ // The request body to send, if any, owned by the caller.
+ UploadDataStream* request_body_stream_;
+ // |response_info_| is the HTTP response data object which is filled in
+ // when a the response headers are read. It is not owned by this stream.
+ HttpResponseInfo* response_info_;
+ // Because response data is buffered, also buffer the response status if the
+ // stream is explicitly closed via OnError or OnClose with an error.
+ // Once all buffered data has been returned, this will be used as the final
+ // response.
+ int response_status_;
+
+ bool response_headers_received_;
+
+ // Serialized HTTP request.
+ std::string request_;
+
+ // Buffer into which response header data is read.
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+
+ // We buffer the response body as it arrives asynchronously from the stream.
+ // TODO(rch): This is infinite buffering, which is bad.
+ std::list<scoped_refptr<IOBufferWithSize> > response_body_;
+
+ // The caller's callback to be used for asynchronous operations.
+ CompletionCallback callback_;
+
+ // Caller provided buffer for the ReadResponseBody() response.
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ // Temporary buffer used to read the request body from UploadDataStream.
+ scoped_refptr<IOBufferWithSize> raw_request_body_buf_;
+ // Wraps raw_request_body_buf_ to read the remaining data progressively.
+ scoped_refptr<DrainableIOBuffer> request_body_buf_;
+
+ BoundNetLog stream_net_log_;
+
+ base::WeakPtrFactory<QuicHttpStream> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_HTTP_STREAM_H_
diff --git a/chromium/net/quic/quic_http_stream_test.cc b/chromium/net/quic/quic_http_stream_test.cc
new file mode 100644
index 00000000000..1e4ac916bbf
--- /dev/null
+++ b/chromium/net/quic/quic_http_stream_test.cc
@@ -0,0 +1,562 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_http_stream.h"
+
+#include <vector>
+
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/test_task_runner.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kUploadData[] = "hello world!";
+
+class TestQuicConnection : public QuicConnection {
+ public:
+ TestQuicConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelper* helper)
+ : QuicConnection(guid, address, helper, false, QuicVersionMax()) {
+ }
+
+ void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+ QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+ }
+
+ void SetReceiveAlgorithm(ReceiveAlgorithmInterface* receive_algorithm) {
+ QuicConnectionPeer::SetReceiveAlgorithm(this, receive_algorithm);
+ }
+};
+
+class TestReceiveAlgorithm : public ReceiveAlgorithmInterface {
+ public:
+ explicit TestReceiveAlgorithm(QuicCongestionFeedbackFrame* feedback)
+ : feedback_(feedback) {
+ }
+
+ bool GenerateCongestionFeedback(
+ QuicCongestionFeedbackFrame* congestion_feedback) {
+ if (feedback_ == NULL) {
+ return false;
+ }
+ *congestion_feedback = *feedback_;
+ return true;
+ }
+
+ MOCK_METHOD4(RecordIncomingPacket,
+ void(QuicByteCount, QuicPacketSequenceNumber, QuicTime, bool));
+
+ private:
+ MockClock clock_;
+ QuicCongestionFeedbackFrame* feedback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestReceiveAlgorithm);
+};
+
+// Subclass of QuicHttpStream that closes itself when the first piece of data
+// is received.
+class AutoClosingStream : public QuicHttpStream {
+ public:
+ explicit AutoClosingStream(const base::WeakPtr<QuicClientSession>& session)
+ : QuicHttpStream(session) {
+ }
+
+ virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+ Close(false);
+ return OK;
+ }
+};
+
+} // namespace
+
+class QuicHttpStreamTest : public ::testing::TestWithParam<bool> {
+ protected:
+ const static bool kFin = true;
+ // Holds a packet to be written to the wire, and the IO mode that should
+ // be used by the mock socket when performing the write.
+ struct PacketToWrite {
+ PacketToWrite(IoMode mode, QuicEncryptedPacket* packet)
+ : mode(mode),
+ packet(packet) {
+ }
+ IoMode mode;
+ QuicEncryptedPacket* packet;
+ };
+
+ QuicHttpStreamTest()
+ : net_log_(BoundNetLog()),
+ use_closing_stream_(false),
+ read_buffer_(new IOBufferWithSize(4096)),
+ guid_(2),
+ framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ creator_(guid_, &framer_, &random_, false) {
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ peer_addr_ = IPEndPoint(ip, 443);
+ self_addr_ = IPEndPoint(ip, 8435);
+ }
+
+ ~QuicHttpStreamTest() {
+ for (size_t i = 0; i < writes_.size(); i++) {
+ delete writes_[i].packet;
+ }
+ }
+
+ // Adds a packet to the list of expected writes.
+ void AddWrite(IoMode mode, QuicEncryptedPacket* packet) {
+ writes_.push_back(PacketToWrite(mode, packet));
+ }
+
+ // Returns the packet to be written at position |pos|.
+ QuicEncryptedPacket* GetWrite(size_t pos) {
+ return writes_[pos].packet;
+ }
+
+ bool AtEof() {
+ return socket_data_->at_read_eof() && socket_data_->at_write_eof();
+ }
+
+ void ProcessPacket(const QuicEncryptedPacket& packet) {
+ connection_->ProcessUdpPacket(self_addr_, peer_addr_, packet);
+ }
+
+ // Configures the test fixture to use the list of expected writes.
+ void Initialize() {
+ mock_writes_.reset(new MockWrite[writes_.size()]);
+ for (size_t i = 0; i < writes_.size(); i++) {
+ mock_writes_[i] = MockWrite(writes_[i].mode,
+ writes_[i].packet->data(),
+ writes_[i].packet->length());
+ };
+
+ socket_data_.reset(new StaticSocketDataProvider(NULL, 0, mock_writes_.get(),
+ writes_.size()));
+
+ MockUDPClientSocket* socket = new MockUDPClientSocket(socket_data_.get(),
+ net_log_.net_log());
+ socket->Connect(peer_addr_);
+ runner_ = new TestTaskRunner(&clock_);
+ send_algorithm_ = new MockSendAlgorithm();
+ receive_algorithm_ = new TestReceiveAlgorithm(NULL);
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)).
+ WillRepeatedly(testing::Return(QuicTime::Delta::Zero()));
+ helper_ = new QuicConnectionHelper(runner_.get(), &clock_,
+ &random_generator_, socket);
+ connection_ = new TestQuicConnection(guid_, peer_addr_, helper_);
+ connection_->set_visitor(&visitor_);
+ connection_->SetSendAlgorithm(send_algorithm_);
+ connection_->SetReceiveAlgorithm(receive_algorithm_);
+ crypto_config_.SetDefaults();
+ session_.reset(
+ new QuicClientSession(connection_,
+ scoped_ptr<DatagramClientSocket>(socket), NULL,
+ &crypto_client_stream_factory_,
+ "www.google.com", DefaultQuicConfig(),
+ &crypto_config_, NULL));
+ session_->GetCryptoStream()->CryptoConnect();
+ EXPECT_TRUE(session_->IsCryptoHandshakeConfirmed());
+ stream_.reset(use_closing_stream_ ?
+ new AutoClosingStream(session_->GetWeakPtr()) :
+ new QuicHttpStream(session_->GetWeakPtr()));
+ }
+
+ void SetRequestString(const std::string& method, const std::string& path) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = method;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = path;
+ headers[":scheme"] = "http";
+ headers[":version"] = "HTTP/1.1";
+ request_data_ = SerializeHeaderBlock(headers);
+ }
+
+ void SetResponseString(const std::string& status, const std::string& body) {
+ SpdyHeaderBlock headers;
+ headers[":status"] = status;
+ headers[":version"] = "HTTP/1.1";
+ headers["content-type"] = "text/plain";
+ response_data_ = SerializeHeaderBlock(headers) + body;
+ }
+
+ std::string SerializeHeaderBlock(const SpdyHeaderBlock& headers) {
+ QuicSpdyCompressor compressor;
+ return compressor.CompressHeaders(headers);
+ }
+
+ // Returns a newly created packet to send kData on stream 1.
+ QuicEncryptedPacket* ConstructDataPacket(
+ QuicPacketSequenceNumber sequence_number,
+ bool should_include_version,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data) {
+ InitializeHeader(sequence_number, should_include_version);
+ QuicStreamFrame frame(3, fin, offset, data);
+ return ConstructPacket(header_, QuicFrame(&frame));
+ }
+
+ // Returns a newly created packet to send ack data.
+ QuicEncryptedPacket* ConstructAckPacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacketSequenceNumber largest_received,
+ QuicPacketSequenceNumber least_unacked) {
+ InitializeHeader(sequence_number, false);
+
+ QuicAckFrame ack(largest_received, QuicTime::Zero(), least_unacked);
+ ack.sent_info.entropy_hash = 0;
+ ack.received_info.entropy_hash = 0;
+
+ return ConstructPacket(header_, QuicFrame(&ack));
+ }
+
+ // Returns a newly created packet to send ack data.
+ QuicEncryptedPacket* ConstructRstPacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicStreamId stream_id) {
+ InitializeHeader(sequence_number, false);
+
+ QuicRstStreamFrame rst(stream_id, QUIC_STREAM_NO_ERROR);
+ return ConstructPacket(header_, QuicFrame(&rst));
+ }
+
+ BoundNetLog net_log_;
+ bool use_closing_stream_;
+ MockSendAlgorithm* send_algorithm_;
+ TestReceiveAlgorithm* receive_algorithm_;
+ scoped_refptr<TestTaskRunner> runner_;
+ scoped_ptr<MockWrite[]> mock_writes_;
+ MockClock clock_;
+ MockRandom random_generator_;
+ TestQuicConnection* connection_;
+ QuicConnectionHelper* helper_;
+ testing::StrictMock<MockConnectionVisitor> visitor_;
+ scoped_ptr<QuicHttpStream> stream_;
+ scoped_ptr<QuicClientSession> session_;
+ QuicCryptoClientConfig crypto_config_;
+ TestCompletionCallback callback_;
+ HttpRequestInfo request_;
+ HttpRequestHeaders headers_;
+ HttpResponseInfo response_;
+ scoped_refptr<IOBufferWithSize> read_buffer_;
+ std::string request_data_;
+ std::string response_data_;
+
+ private:
+ void InitializeHeader(QuicPacketSequenceNumber sequence_number,
+ bool should_include_version) {
+ header_.public_header.guid = guid_;
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = should_include_version;
+ header_.packet_sequence_number = sequence_number;
+ header_.fec_group = 0;
+ header_.entropy_flag = false;
+ header_.fec_flag = false;
+ }
+
+ QuicEncryptedPacket* ConstructPacket(const QuicPacketHeader& header,
+ const QuicFrame& frame) {
+ QuicFrames frames;
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer_.BuildUnsizedDataPacket(header_, frames).packet);
+ return framer_.EncryptPacket(
+ ENCRYPTION_NONE, header.packet_sequence_number, *packet);
+ }
+
+ const QuicGuid guid_;
+ QuicFramer framer_;
+ IPEndPoint self_addr_;
+ IPEndPoint peer_addr_;
+ MockRandom random_;
+ MockCryptoClientStreamFactory crypto_client_stream_factory_;
+ QuicPacketCreator creator_;
+ QuicPacketHeader header_;
+ scoped_ptr<StaticSocketDataProvider> socket_data_;
+ std::vector<PacketToWrite> writes_;
+};
+
+TEST_F(QuicHttpStreamTest, RenewStreamForAuth) {
+ Initialize();
+ EXPECT_EQ(NULL, stream_->RenewStreamForAuth());
+}
+
+TEST_F(QuicHttpStreamTest, CanFindEndOfResponse) {
+ Initialize();
+ EXPECT_TRUE(stream_->CanFindEndOfResponse());
+}
+
+TEST_F(QuicHttpStreamTest, IsConnectionReusable) {
+ Initialize();
+ EXPECT_FALSE(stream_->IsConnectionReusable());
+}
+
+TEST_F(QuicHttpStreamTest, GetRequest) {
+ SetRequestString("GET", "/");
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0,
+ request_data_));
+ Initialize();
+
+ request_.method = "GET";
+ request_.url = GURL("http://www.google.com/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+ EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+ EXPECT_EQ(&response_, stream_->GetResponseInfo());
+
+ // Ack the request.
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0));
+ ProcessPacket(*ack);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->ReadResponseHeaders(callback_.callback()));
+
+ // Send the response without a body.
+ SetResponseString("404 Not Found", std::string());
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(2, false, kFin, 0, response_data_));
+ ProcessPacket(*resp);
+
+ // Now that the headers have been processed, the callback will return.
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(404, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+ // There is no body, so this should return immediately.
+ EXPECT_EQ(0, stream_->ReadResponseBody(read_buffer_.get(),
+ read_buffer_->size(),
+ callback_.callback()));
+ EXPECT_TRUE(stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicHttpStreamTest, GetRequestFullResponseInSinglePacket) {
+ SetRequestString("GET", "/");
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_));
+ Initialize();
+
+ request_.method = "GET";
+ request_.url = GURL("http://www.google.com/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+ EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+ EXPECT_EQ(&response_, stream_->GetResponseInfo());
+
+ // Ack the request.
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0));
+ ProcessPacket(*ack);
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->ReadResponseHeaders(callback_.callback()));
+
+ // Send the response with a body.
+ SetResponseString("200 OK", "hello world!");
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(2, false, kFin, 0, response_data_));
+ ProcessPacket(*resp);
+
+ // Now that the headers have been processed, the callback will return.
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(200, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+ // There is no body, so this should return immediately.
+ // Since the body has already arrived, this should return immediately.
+ EXPECT_EQ(12, stream_->ReadResponseBody(read_buffer_.get(),
+ read_buffer_->size(),
+ callback_.callback()));
+ EXPECT_TRUE(stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicHttpStreamTest, SendPostRequest) {
+ SetRequestString("POST", "/");
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, !kFin, 0, request_data_));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(2, true, kFin,
+ request_data_.length(),
+ kUploadData));
+ AddWrite(SYNCHRONOUS, ConstructAckPacket(3, 3, 1));
+
+ Initialize();
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadBytesElementReader(kUploadData, strlen(kUploadData)));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+ request_.method = "POST";
+ request_.url = GURL("http://www.google.com/");
+ request_.upload_data_stream = &upload_data_stream;
+ ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+ EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+ EXPECT_EQ(&response_, stream_->GetResponseInfo());
+
+ // Ack both packets in the request.
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0));
+ ProcessPacket(*ack);
+
+ // Send the response headers (but not the body).
+ SetResponseString("200 OK", std::string());
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(2, false, !kFin, 0, response_data_));
+ ProcessPacket(*resp);
+
+ // Since the headers have already arrived, this should return immediately.
+ EXPECT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(200, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+ // Send the response body.
+ const char kResponseBody[] = "Hello world!";
+ scoped_ptr<QuicEncryptedPacket> resp_body(
+ ConstructDataPacket(3, false, kFin, response_data_.length(),
+ kResponseBody));
+ ProcessPacket(*resp_body);
+
+ // Since the body has already arrived, this should return immediately.
+ EXPECT_EQ(static_cast<int>(strlen(kResponseBody)),
+ stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+ callback_.callback()));
+
+ EXPECT_TRUE(stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicHttpStreamTest, SendChunkedPostRequest) {
+ SetRequestString("POST", "/");
+ size_t chunk_size = strlen(kUploadData);
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, !kFin, 0, request_data_));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(2, true, !kFin,
+ request_data_.length(),
+ kUploadData));
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(3, true, kFin,
+ request_data_.length() + chunk_size,
+ kUploadData));
+ AddWrite(SYNCHRONOUS, ConstructAckPacket(4, 3, 1));
+
+ Initialize();
+
+ UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0);
+ upload_data_stream.AppendChunk(kUploadData, chunk_size, false);
+
+ request_.method = "POST";
+ request_.url = GURL("http://www.google.com/");
+ request_.upload_data_stream = &upload_data_stream;
+ ASSERT_EQ(OK, request_.upload_data_stream->Init(CompletionCallback()));
+
+ ASSERT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+ ASSERT_EQ(ERR_IO_PENDING, stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+ EXPECT_EQ(&response_, stream_->GetResponseInfo());
+
+ upload_data_stream.AppendChunk(kUploadData, chunk_size, true);
+
+ // Ack both packets in the request.
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0));
+ ProcessPacket(*ack);
+
+ // Send the response headers (but not the body).
+ SetResponseString("200 OK", std::string());
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(2, false, !kFin, 0, response_data_));
+ ProcessPacket(*resp);
+
+ // Since the headers have already arrived, this should return immediately.
+ ASSERT_EQ(OK, stream_->ReadResponseHeaders(callback_.callback()));
+ ASSERT_TRUE(response_.headers.get());
+ EXPECT_EQ(200, response_.headers->response_code());
+ EXPECT_TRUE(response_.headers->HasHeaderValue("Content-Type", "text/plain"));
+
+ // Send the response body.
+ const char kResponseBody[] = "Hello world!";
+ scoped_ptr<QuicEncryptedPacket> resp_body(
+ ConstructDataPacket(3, false, kFin, response_data_.length(),
+ kResponseBody));
+ ProcessPacket(*resp_body);
+
+ // Since the body has already arrived, this should return immediately.
+ ASSERT_EQ(static_cast<int>(strlen(kResponseBody)),
+ stream_->ReadResponseBody(read_buffer_.get(), read_buffer_->size(),
+ callback_.callback()));
+
+ EXPECT_TRUE(stream_->IsResponseBodyComplete());
+ EXPECT_TRUE(AtEof());
+}
+
+TEST_F(QuicHttpStreamTest, DestroyedEarly) {
+ SetRequestString("GET", "/");
+ AddWrite(SYNCHRONOUS, ConstructDataPacket(1, true, kFin, 0, request_data_));
+ use_closing_stream_ = true;
+ Initialize();
+
+ request_.method = "GET";
+ request_.url = GURL("http://www.google.com/");
+
+ EXPECT_EQ(OK, stream_->InitializeStream(&request_, DEFAULT_PRIORITY,
+ net_log_, callback_.callback()));
+ EXPECT_EQ(OK, stream_->SendRequest(headers_, &response_,
+ callback_.callback()));
+ EXPECT_EQ(&response_, stream_->GetResponseInfo());
+
+ // Ack the request.
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0, 0));
+ ProcessPacket(*ack);
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream_->ReadResponseHeaders(callback_.callback()));
+
+ // Send the response with a body.
+ SetResponseString("404 OK", "hello world!");
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(2, false, kFin, 0, response_data_));
+
+ // In the course of processing this packet, the QuicHttpStream close itself.
+ ProcessPacket(*resp);
+
+ EXPECT_TRUE(AtEof());
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/quic/quic_network_transaction_unittest.cc b/chromium/net/quic/quic_network_transaction_unittest.cc
new file mode 100644
index 00000000000..0722b58121f
--- /dev/null
+++ b/chromium/net/quic/quic_network_transaction_unittest.cc
@@ -0,0 +1,752 @@
+// Copyright (c) 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.
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/proxy/proxy_resolver.h"
+#include "net/proxy/proxy_service.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/mock_client_socket_pool_manager.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+// This is the expected return from a current server advertising QUIC.
+static const char kQuicAlternateProtocolHttpHeader[] =
+ "Alternate-Protocol: 80:quic\r\n\r\n";
+static const char kQuicAlternateProtocolHttpsHeader[] =
+ "Alternate-Protocol: 443:quic\r\n\r\n";
+} // namespace
+
+namespace net {
+namespace test {
+
+class QuicNetworkTransactionTest : public PlatformTest {
+ protected:
+ QuicNetworkTransactionTest()
+ : clock_(new MockClock),
+ ssl_config_service_(new SSLConfigServiceDefaults),
+ proxy_service_(ProxyService::CreateDirect()),
+ compressor_(new QuicSpdyCompressor()),
+ auth_handler_factory_(
+ HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
+ hanging_data_(NULL, 0, NULL, 0) {
+ request_.method = "GET";
+ request_.url = GURL("http://www.google.com/");
+ request_.load_flags = 0;
+ }
+
+ virtual void SetUp() {
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ virtual void TearDown() {
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ HttpStreamFactory::set_use_alternate_protocols(false);
+ HttpStreamFactory::SetNextProtos(std::vector<NextProto>());
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructRstPacket(
+ QuicPacketSequenceNumber num,
+ QuicStreamId stream_id) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.packet_sequence_number = num;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicRstStreamFrame rst(stream_id, QUIC_STREAM_NO_ERROR);
+ return scoped_ptr<QuicEncryptedPacket>(
+ ConstructPacket(header, QuicFrame(&rst)));
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructConnectionClosePacket(
+ QuicPacketSequenceNumber num) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.packet_sequence_number = num;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicAckFrame ack_frame(0, QuicTime::Zero(), 0);
+ QuicConnectionCloseFrame close;
+ close.error_code = QUIC_CRYPTO_VERSION_NOT_SUPPORTED;
+ close.error_details = "Time to panic!";
+ close.ack_frame = ack_frame;
+ return scoped_ptr<QuicEncryptedPacket>(
+ ConstructPacket(header, QuicFrame(&close)));
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructAckPacket(
+ QuicPacketSequenceNumber largest_received,
+ QuicPacketSequenceNumber least_unacked) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.packet_sequence_number = 2;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicAckFrame ack(largest_received, QuicTime::Zero(), least_unacked);
+
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kTCP;
+ feedback.tcp.accumulated_number_of_lost_packets = 0;
+ feedback.tcp.receive_window = 256000;
+
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), false);
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&ack));
+ frames.push_back(QuicFrame(&feedback));
+ scoped_ptr<QuicPacket> packet(
+ framer.BuildUnsizedDataPacket(header, frames).packet);
+ return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+ ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+ }
+
+ std::string GetRequestString(const std::string& method,
+ const std::string& scheme,
+ const std::string& path) {
+ SpdyHeaderBlock headers;
+ headers[":method"] = method;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = path;
+ headers[":scheme"] = scheme;
+ headers[":version"] = "HTTP/1.1";
+ return SerializeHeaderBlock(headers);
+ }
+
+ std::string GetResponseString(const std::string& status,
+ const std::string& body) {
+ SpdyHeaderBlock headers;
+ headers[":status"] = status;
+ headers[":version"] = "HTTP/1.1";
+ headers["content-type"] = "text/plain";
+ return compressor_->CompressHeaders(headers) + body;
+ }
+
+ std::string SerializeHeaderBlock(const SpdyHeaderBlock& headers) {
+ QuicSpdyCompressor compressor;
+ return compressor.CompressHeaders(headers);
+ }
+
+ // Returns a newly created packet to send kData on stream 1.
+ QuicEncryptedPacket* ConstructDataPacket(
+ QuicPacketSequenceNumber sequence_number,
+ QuicStreamId stream_id,
+ bool should_include_version,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data) {
+ InitializeHeader(sequence_number, should_include_version);
+ QuicStreamFrame frame(stream_id, fin, offset, data);
+ return ConstructPacket(header_, QuicFrame(&frame)).release();
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructPacket(
+ const QuicPacketHeader& header,
+ const QuicFrame& frame) {
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), false);
+ QuicFrames frames;
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer.BuildUnsizedDataPacket(header, frames).packet);
+ return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+ ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+ }
+
+ void InitializeHeader(QuicPacketSequenceNumber sequence_number,
+ bool should_include_version) {
+ header_.public_header.guid = random_generator_.RandUint64();
+ header_.public_header.reset_flag = false;
+ header_.public_header.version_flag = should_include_version;
+ header_.packet_sequence_number = sequence_number;
+ header_.fec_group = 0;
+ header_.entropy_flag = false;
+ header_.fec_flag = false;
+ }
+
+ void CreateSession() {
+ CreateSessionWithFactory(&socket_factory_);
+ }
+
+ void CreateSessionWithFactory(ClientSocketFactory* socket_factory) {
+ params_.enable_quic = true;
+ params_.quic_clock = clock_;
+ params_.quic_random = &random_generator_;
+ params_.client_socket_factory = socket_factory;
+ params_.quic_crypto_client_stream_factory = &crypto_client_stream_factory_;
+ params_.host_resolver = &host_resolver_;
+ params_.cert_verifier = &cert_verifier_;
+ params_.transport_security_state = &transport_security_state_;
+ params_.proxy_service = proxy_service_.get();
+ params_.ssl_config_service = ssl_config_service_.get();
+ params_.http_auth_handler_factory = auth_handler_factory_.get();
+ params_.http_server_properties = http_server_properties.GetWeakPtr();
+
+ session_ = new HttpNetworkSession(params_);
+ }
+
+ void CheckWasQuicResponse(const scoped_ptr<HttpNetworkTransaction>& trans) {
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ EXPECT_TRUE(response->was_npn_negotiated);
+ EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_QUIC1_SPDY3,
+ response->connection_info);
+ }
+
+ void CheckWasHttpResponse(const scoped_ptr<HttpNetworkTransaction>& trans) {
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_FALSE(response->was_fetched_via_spdy);
+ EXPECT_FALSE(response->was_npn_negotiated);
+ EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP1,
+ response->connection_info);
+ }
+
+ void CheckResponseData(HttpNetworkTransaction* trans,
+ const std::string& expected) {
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans, &response_data));
+ EXPECT_EQ(expected, response_data);
+ }
+
+ void RunTransaction(HttpNetworkTransaction* trans) {
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ }
+
+ void SendRequestAndExpectHttpResponse(const std::string& expected) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ RunTransaction(trans.get());
+ CheckWasHttpResponse(trans);
+ CheckResponseData(trans.get(), expected);
+ }
+
+ void SendRequestAndExpectQuicResponse(const std::string& expected) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ RunTransaction(trans.get());
+ CheckWasQuicResponse(trans);
+ CheckResponseData(trans.get(), expected);
+ }
+
+ void AddQuicAlternateProtocolMapping(
+ MockCryptoClientStream::HandshakeMode handshake_mode) {
+ crypto_client_stream_factory_.set_handshake_mode(handshake_mode);
+ session_->http_server_properties()->SetAlternateProtocol(
+ HostPortPair::FromURL(request_.url), 80, QUIC);
+ }
+
+ void ExpectBrokenAlternateProtocolMapping() {
+ ASSERT_TRUE(session_->http_server_properties()->HasAlternateProtocol(
+ HostPortPair::FromURL(request_.url)));
+ const PortAlternateProtocolPair alternate =
+ session_->http_server_properties()->GetAlternateProtocol(
+ HostPortPair::FromURL(request_.url));
+ EXPECT_EQ(ALTERNATE_PROTOCOL_BROKEN, alternate.protocol);
+ }
+
+ void AddHangingNonAlternateProtocolSocketData() {
+ MockConnect hanging_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ hanging_data_.set_connect_data(hanging_connect);
+ socket_factory_.AddSocketDataProvider(&hanging_data_);
+ }
+
+ QuicPacketHeader header_;
+ scoped_refptr<HttpNetworkSession> session_;
+ MockClientSocketFactory socket_factory_;
+ MockCryptoClientStreamFactory crypto_client_stream_factory_;
+ MockClock* clock_; // Owned by QuicStreamFactory after CreateSession.
+ MockHostResolver host_resolver_;
+ MockCertVerifier cert_verifier_;
+ TransportSecurityState transport_security_state_;
+ scoped_refptr<SSLConfigServiceDefaults> ssl_config_service_;
+ scoped_ptr<ProxyService> proxy_service_;
+ scoped_ptr<QuicSpdyCompressor> compressor_;
+ scoped_ptr<HttpAuthHandlerFactory> auth_handler_factory_;
+ MockRandom random_generator_;
+ HttpServerPropertiesImpl http_server_properties;
+ HttpNetworkSession::Params params_;
+ HttpRequestInfo request_;
+ CapturingBoundNetLog net_log_;
+ StaticSocketDataProvider hanging_data_;
+};
+
+TEST_F(QuicNetworkTransactionTest, ForceQuic) {
+ params_.origin_to_force_quic_on =
+ HostPortPair::FromString("www.google.com:80");
+
+ QuicStreamId stream_id = 3;
+ scoped_ptr<QuicEncryptedPacket> req(
+ ConstructDataPacket(1, stream_id, true, true, 0,
+ GetRequestString("GET", "http", "/")));
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0));
+
+ MockWrite quic_writes[] = {
+ MockWrite(SYNCHRONOUS, req->data(), req->length()),
+ MockWrite(SYNCHRONOUS, ack->data(), ack->length()),
+ };
+
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(
+ 1, stream_id, false, true, 0, GetResponseString("200 OK", "hello!")));
+ MockRead quic_reads[] = {
+ MockRead(SYNCHRONOUS, resp->data(), resp->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+
+ DelayedSocketData quic_data(
+ 1, // wait for one write to finish before reading.
+ quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // The non-alternate protocol job needs to hang in order to guarantee that
+ // the alternate-protocol job will "win".
+ AddHangingNonAlternateProtocolSocketData();
+
+ CreateSession();
+
+ SendRequestAndExpectQuicResponse("hello!");
+
+ // Check that the NetLog was filled reasonably.
+ net::CapturingNetLog::CapturedEntryList entries;
+ net_log_.GetEntries(&entries);
+ EXPECT_LT(0u, entries.size());
+
+ // Check that we logged a QUIC_SESSION_PACKET_RECEIVED.
+ int pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_QUIC_SESSION_PACKET_RECEIVED,
+ net::NetLog::PHASE_NONE);
+ EXPECT_LT(0, pos);
+
+ // ... and also a TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED.
+ pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_QUIC_SESSION_PACKET_HEADER_RECEIVED,
+ net::NetLog::PHASE_NONE);
+ EXPECT_LT(0, pos);
+
+ std::string packet_sequence_number;
+ ASSERT_TRUE(entries[pos].GetStringValue(
+ "packet_sequence_number", &packet_sequence_number));
+ EXPECT_EQ("1", packet_sequence_number);
+
+ // ... and also a QUIC_SESSION_STREAM_FRAME_RECEIVED.
+ pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_QUIC_SESSION_STREAM_FRAME_RECEIVED,
+ net::NetLog::PHASE_NONE);
+ EXPECT_LT(0, pos);
+
+ int log_stream_id;
+ ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &log_stream_id));
+ EXPECT_EQ(stream_id, static_cast<QuicStreamId>(log_stream_id));
+}
+
+TEST_F(QuicNetworkTransactionTest, ForceQuicWithErrorConnecting) {
+ params_.origin_to_force_quic_on =
+ HostPortPair::FromString("www.google.com:80");
+
+ MockRead quic_reads[] = {
+ MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+ };
+ StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ CreateSession();
+
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get()));
+ TestCompletionCallback callback;
+ int rv = trans->Start(&request_, callback.callback(), net_log_.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.WaitForResult());
+}
+
+TEST_F(QuicNetworkTransactionTest, DoNotForceQuicForHttps) {
+ // Attempt to "force" quic on 443, which will not be honored.
+ params_.origin_to_force_quic_on =
+ HostPortPair::FromString("www.google.com:443");
+
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider data(http_reads, arraysize(http_reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreateSession();
+
+ SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_F(QuicNetworkTransactionTest, UseAlternateProtocolForQuic) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(kQuicAlternateProtocolHttpHeader),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&http_data);
+
+ scoped_ptr<QuicEncryptedPacket> req(
+ ConstructDataPacket(1, 3, true, true, 0,
+ GetRequestString("GET", "http", "/")));
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0));
+
+ MockWrite quic_writes[] = {
+ MockWrite(SYNCHRONOUS, req->data(), req->length()),
+ MockWrite(SYNCHRONOUS, ack->data(), ack->length()),
+ };
+
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(
+ 1, 3, false, true, 0, GetResponseString("200 OK", "hello!")));
+ MockRead quic_reads[] = {
+ MockRead(SYNCHRONOUS, resp->data(), resp->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+
+ DelayedSocketData quic_data(
+ 1, // wait for one write to finish before reading.
+ quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // The non-alternate protocol job needs to hang in order to guarantee that
+ // the alternate-protocol job will "win".
+ AddHangingNonAlternateProtocolSocketData();
+
+ CreateSession();
+
+ SendRequestAndExpectHttpResponse("hello world");
+ SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_F(QuicNetworkTransactionTest, UseAlternateProtocolForQuicForHttps) {
+ params_.origin_to_force_quic_on =
+ HostPortPair::FromString("www.google.com:443");
+ params_.enable_quic_https = true;
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead(kQuicAlternateProtocolHttpsHeader),
+ MockRead("hello world"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&http_data);
+
+ scoped_ptr<QuicEncryptedPacket> req(
+ ConstructDataPacket(1, 3, true, true, 0,
+ GetRequestString("GET", "https", "/")));
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0));
+
+ MockWrite quic_writes[] = {
+ MockWrite(SYNCHRONOUS, req->data(), req->length()),
+ MockWrite(SYNCHRONOUS, ack->data(), ack->length()),
+ };
+
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(
+ 1, 3, false, true, 0, GetResponseString("200 OK", "hello!")));
+ MockRead quic_reads[] = {
+ MockRead(SYNCHRONOUS, resp->data(), resp->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+
+ DelayedSocketData quic_data(
+ 1, // wait for one write to finish before reading.
+ quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // The non-alternate protocol job needs to hang in order to guarantee that
+ // the alternate-protocol job will "win".
+ AddHangingNonAlternateProtocolSocketData();
+
+ CreateSession();
+
+ // TODO(rtenneti): Test QUIC over HTTPS, GetSSLInfo().
+ SendRequestAndExpectHttpResponse("hello world");
+}
+
+TEST_F(QuicNetworkTransactionTest, HungAlternateProtocol) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+ crypto_client_stream_factory_.set_handshake_mode(
+ MockCryptoClientStream::COLD_START);
+
+ MockWrite http_writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET / HTTP/1.1\r\n"),
+ MockWrite(SYNCHRONOUS, 1, "Host: www.google.com\r\n"),
+ MockWrite(SYNCHRONOUS, 2, "Connection: keep-alive\r\n\r\n")
+ };
+
+ MockRead http_reads[] = {
+ MockRead(SYNCHRONOUS, 3, "HTTP/1.1 200 OK\r\n"),
+ MockRead(SYNCHRONOUS, 4, kQuicAlternateProtocolHttpHeader),
+ MockRead(SYNCHRONOUS, 5, "hello world"),
+ MockRead(SYNCHRONOUS, OK, 6)
+ };
+
+ DeterministicMockClientSocketFactory socket_factory;
+
+ DeterministicSocketData http_data(http_reads, arraysize(http_reads),
+ http_writes, arraysize(http_writes));
+ socket_factory.AddSocketDataProvider(&http_data);
+
+ // The QUIC transaction will not be allowed to complete.
+ MockWrite quic_writes[] = {
+ MockWrite(ASYNC, ERR_IO_PENDING, 0)
+ };
+ MockRead quic_reads[] = {
+ MockRead(ASYNC, ERR_IO_PENDING, 1),
+ };
+ DeterministicSocketData quic_data(quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+ socket_factory.AddSocketDataProvider(&quic_data);
+
+ // The HTTP transaction will complete.
+ DeterministicSocketData http_data2(http_reads, arraysize(http_reads),
+ http_writes, arraysize(http_writes));
+ socket_factory.AddSocketDataProvider(&http_data2);
+
+ CreateSessionWithFactory(&socket_factory);
+
+ // Run the first request.
+ http_data.StopAfter(arraysize(http_reads) + arraysize(http_writes));
+ SendRequestAndExpectHttpResponse("hello world");
+ ASSERT_TRUE(http_data.at_read_eof());
+ ASSERT_TRUE(http_data.at_write_eof());
+
+ // Now run the second request in which the QUIC socket hangs,
+ // and verify the the transaction continues over HTTP.
+ http_data2.StopAfter(arraysize(http_reads) + arraysize(http_writes));
+ SendRequestAndExpectHttpResponse("hello world");
+
+ ASSERT_TRUE(http_data2.at_read_eof());
+ ASSERT_TRUE(http_data2.at_write_eof());
+ ASSERT_TRUE(!quic_data.at_read_eof());
+ ASSERT_TRUE(!quic_data.at_write_eof());
+}
+
+TEST_F(QuicNetworkTransactionTest, ZeroRTTWithHttpRace) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ scoped_ptr<QuicEncryptedPacket> req(
+ ConstructDataPacket(1, 3, true, true, 0,
+ GetRequestString("GET", "http", "/")));
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0));
+
+ MockWrite quic_writes[] = {
+ MockWrite(SYNCHRONOUS, req->data(), req->length()),
+ MockWrite(SYNCHRONOUS, ack->data(), ack->length()),
+ };
+
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(
+ 1, 3, false, true, 0, GetResponseString("200 OK", "hello!")));
+ MockRead quic_reads[] = {
+ MockRead(SYNCHRONOUS, resp->data(), resp->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+
+ DelayedSocketData quic_data(
+ 1, // wait for one write to finish before reading.
+ quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // The non-alternate protocol job needs to hang in order to guarantee that
+ // the alternate-protocol job will "win".
+ AddHangingNonAlternateProtocolSocketData();
+
+ CreateSession();
+ AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+ SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_F(QuicNetworkTransactionTest, ZeroRTTWithNoHttpRace) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ scoped_ptr<QuicEncryptedPacket> req(
+ ConstructDataPacket(1, 3, true, true, 0,
+ GetRequestString("GET", "http", "/")));
+ scoped_ptr<QuicEncryptedPacket> ack(ConstructAckPacket(1, 0));
+
+ MockWrite quic_writes[] = {
+ MockWrite(SYNCHRONOUS, req->data(), req->length()),
+ MockWrite(SYNCHRONOUS, ack->data(), ack->length()),
+ };
+
+ scoped_ptr<QuicEncryptedPacket> resp(
+ ConstructDataPacket(
+ 1, 3, false, true, 0, GetResponseString("200 OK", "hello!")));
+ MockRead quic_reads[] = {
+ MockRead(SYNCHRONOUS, resp->data(), resp->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+
+ DelayedSocketData quic_data(
+ 1, // wait for one write to finish before reading.
+ quic_reads, arraysize(quic_reads),
+ quic_writes, arraysize(quic_writes));
+
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // In order for a new QUIC session to be established via alternate-protocol
+ // without racing an HTTP connection, we need the host resolution to happen
+ // synchronously.
+ host_resolver_.set_synchronous_mode(true);
+ host_resolver_.rules()->AddIPLiteralRule("www.google.com", "192.168.0.1", "");
+ HostResolver::RequestInfo info(HostPortPair("www.google.com", 80));
+ AddressList address;
+ host_resolver_.Resolve(info, &address, CompletionCallback(), NULL,
+ net_log_.bound());
+
+ CreateSession();
+ AddQuicAlternateProtocolMapping(MockCryptoClientStream::ZERO_RTT);
+ SendRequestAndExpectQuicResponse("hello!");
+}
+
+TEST_F(QuicNetworkTransactionTest, BrokenAlternateProtocol) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ // Alternate-protocol job
+ scoped_ptr<QuicEncryptedPacket> close(ConstructConnectionClosePacket(1));
+ MockRead quic_reads[] = {
+ MockRead(ASYNC, close->data(), close->length()),
+ MockRead(ASYNC, OK), // EOF
+ };
+ StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // Main job which will succeed even though the alternate job fails.
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello from http"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&http_data);
+
+ CreateSession();
+ AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+ SendRequestAndExpectHttpResponse("hello from http");
+ ExpectBrokenAlternateProtocolMapping();
+}
+
+TEST_F(QuicNetworkTransactionTest, BrokenAlternateProtocolReadError) {
+ HttpStreamFactory::EnableNpnSpdy(); // Enables QUIC too.
+
+ // Alternate-protocol job
+ MockRead quic_reads[] = {
+ MockRead(ASYNC, ERR_SOCKET_NOT_CONNECTED),
+ };
+ StaticSocketDataProvider quic_data(quic_reads, arraysize(quic_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&quic_data);
+
+ // Main job which will succeed even though the alternate job fails.
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello from http"),
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
+ MockRead(ASYNC, OK)
+ };
+
+ StaticSocketDataProvider http_data(http_reads, arraysize(http_reads),
+ NULL, 0);
+ socket_factory_.AddSocketDataProvider(&http_data);
+
+ CreateSession();
+
+ AddQuicAlternateProtocolMapping(MockCryptoClientStream::COLD_START);
+ SendRequestAndExpectHttpResponse("hello from http");
+ ExpectBrokenAlternateProtocolMapping();
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_packet_creator.cc b/chromium/net/quic/quic_packet_creator.cc
new file mode 100644
index 00000000000..6d696768612
--- /dev/null
+++ b/chromium/net/quic/quic_packet_creator.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_packet_creator.h"
+
+#include "base/logging.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::make_pair;
+using std::min;
+using std::pair;
+using std::vector;
+
+namespace net {
+
+QuicPacketCreator::QuicPacketCreator(QuicGuid guid,
+ QuicFramer* framer,
+ QuicRandom* random_generator,
+ bool is_server)
+ : guid_(guid),
+ framer_(framer),
+ random_generator_(random_generator),
+ sequence_number_(0),
+ fec_group_number_(0),
+ is_server_(is_server),
+ send_version_in_packet_(!is_server),
+ packet_size_(GetPacketHeaderSize(options_.send_guid_length,
+ send_version_in_packet_,
+ options_.send_sequence_number_length,
+ NOT_IN_FEC_GROUP)) {
+ framer_->set_fec_builder(this);
+}
+
+QuicPacketCreator::~QuicPacketCreator() {
+}
+
+void QuicPacketCreator::OnBuiltFecProtectedPayload(
+ const QuicPacketHeader& header, StringPiece payload) {
+ if (fec_group_.get()) {
+ fec_group_->Update(header, payload);
+ }
+}
+
+bool QuicPacketCreator::ShouldSendFec(bool force_close) const {
+ return fec_group_.get() != NULL && fec_group_->NumReceivedPackets() > 0 &&
+ (force_close ||
+ fec_group_->NumReceivedPackets() >= options_.max_packets_per_fec_group);
+}
+
+void QuicPacketCreator::MaybeStartFEC() {
+ if (options_.max_packets_per_fec_group > 0 && fec_group_.get() == NULL) {
+ DCHECK(queued_frames_.empty());
+ // Set the fec group number to the sequence number of the next packet.
+ fec_group_number_ = sequence_number() + 1;
+ fec_group_.reset(new QuicFecGroup());
+ packet_size_ = GetPacketHeaderSize(options_.send_guid_length,
+ send_version_in_packet_,
+ options_.send_sequence_number_length,
+ IN_FEC_GROUP);
+ DCHECK_LE(packet_size_, options_.max_packet_length);
+ }
+}
+
+// Stops serializing version of the protocol in packets sent after this call.
+// A packet that is already open might send kQuicVersionSize bytes less than the
+// maximum packet size if we stop sending version before it is serialized.
+void QuicPacketCreator::StopSendingVersion() {
+ DCHECK(send_version_in_packet_);
+ send_version_in_packet_ = false;
+ if (packet_size_ > 0) {
+ DCHECK_LT(kQuicVersionSize, packet_size_);
+ packet_size_ -= kQuicVersionSize;
+ }
+}
+
+bool QuicPacketCreator::HasRoomForStreamFrame(QuicStreamId id,
+ QuicStreamOffset offset) const {
+ return BytesFree() >
+ QuicFramer::GetMinStreamFrameSize(framer_->version(), id, offset, true);
+}
+
+// static
+size_t QuicPacketCreator::StreamFramePacketOverhead(
+ QuicVersion version,
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length,
+ InFecGroup is_in_fec_group) {
+ return GetPacketHeaderSize(guid_length, include_version,
+ sequence_number_length, is_in_fec_group) +
+ // Assumes this is a stream with a single lone packet.
+ QuicFramer::GetMinStreamFrameSize(version, 1u, 0u, true);
+}
+
+size_t QuicPacketCreator::CreateStreamFrame(QuicStreamId id,
+ StringPiece data,
+ QuicStreamOffset offset,
+ bool fin,
+ QuicFrame* frame) {
+ DCHECK_GT(options_.max_packet_length,
+ StreamFramePacketOverhead(
+ framer_->version(), PACKET_8BYTE_GUID, kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, IN_FEC_GROUP));
+ DCHECK(HasRoomForStreamFrame(id, offset));
+
+ const size_t free_bytes = BytesFree();
+ size_t bytes_consumed = 0;
+
+ if (data.size() != 0) {
+ // When a STREAM frame is the last frame in a packet, it consumes two fewer
+ // bytes of framing overhead.
+ // Anytime more data is available than fits in with the extra two bytes,
+ // the frame will be the last, and up to two extra bytes are consumed.
+ // TODO(ianswett): If QUIC pads, the 1 byte PADDING frame does not fit when
+ // 1 byte is available, because then the STREAM frame isn't the last.
+
+ // The minimum frame size(0 bytes of data) if it's not the last frame.
+ size_t min_frame_size = QuicFramer::GetMinStreamFrameSize(
+ framer_->version(), id, offset, false);
+ // Check if it's the last frame in the packet.
+ if (data.size() + min_frame_size > free_bytes) {
+ // The minimum frame size(0 bytes of data) if it is the last frame.
+ size_t min_last_frame_size = QuicFramer::GetMinStreamFrameSize(
+ framer_->version(), id, offset, true);
+ bytes_consumed =
+ min<size_t>(free_bytes - min_last_frame_size, data.size());
+ } else {
+ DCHECK_LT(data.size(), BytesFree());
+ bytes_consumed = data.size();
+ }
+
+ bool set_fin = fin && bytes_consumed == data.size(); // Last frame.
+ StringPiece data_frame(data.data(), bytes_consumed);
+ *frame = QuicFrame(new QuicStreamFrame(id, set_fin, offset, data_frame));
+ } else {
+ DCHECK(fin);
+ // Create a new packet for the fin, if necessary.
+ *frame = QuicFrame(new QuicStreamFrame(id, true, offset, ""));
+ }
+
+ return bytes_consumed;
+}
+
+SerializedPacket QuicPacketCreator::SerializeAllFrames(
+ const QuicFrames& frames) {
+ // TODO(satyamshekhar): Verify that this DCHECK won't fail. What about queued
+ // frames from SendStreamData()[send_stream_should_flush_ == false &&
+ // data.empty() == true] and retransmit due to RTO.
+ DCHECK_EQ(0u, queued_frames_.size());
+ for (size_t i = 0; i < frames.size(); ++i) {
+ bool success = AddFrame(frames[i], false);
+ DCHECK(success);
+ }
+ SerializedPacket packet = SerializePacket();
+ DCHECK(packet.retransmittable_frames == NULL);
+ return packet;
+}
+
+bool QuicPacketCreator::HasPendingFrames() {
+ return !queued_frames_.empty();
+}
+
+size_t QuicPacketCreator::BytesFree() const {
+ const size_t max_plaintext_size =
+ framer_->GetMaxPlaintextSize(options_.max_packet_length);
+ if (packet_size_ > max_plaintext_size) {
+ return 0;
+ }
+ return max_plaintext_size - packet_size_;
+}
+
+bool QuicPacketCreator::AddSavedFrame(const QuicFrame& frame) {
+ return AddFrame(frame, true);
+}
+
+SerializedPacket QuicPacketCreator::SerializePacket() {
+ DCHECK_EQ(false, queued_frames_.empty());
+ QuicPacketHeader header;
+ FillPacketHeader(fec_group_number_, false, false, &header);
+
+ SerializedPacket serialized = framer_->BuildDataPacket(
+ header, queued_frames_, packet_size_);
+ queued_frames_.clear();
+ packet_size_ = GetPacketHeaderSize(options_.send_guid_length,
+ send_version_in_packet_,
+ options_.send_sequence_number_length,
+ fec_group_.get() != NULL ?
+ IN_FEC_GROUP : NOT_IN_FEC_GROUP);
+ serialized.retransmittable_frames = queued_retransmittable_frames_.release();
+ return serialized;
+}
+
+SerializedPacket QuicPacketCreator::SerializeFec() {
+ DCHECK_LT(0u, fec_group_->NumReceivedPackets());
+ DCHECK_EQ(0u, queued_frames_.size());
+ QuicPacketHeader header;
+ FillPacketHeader(fec_group_number_, true,
+ fec_group_->entropy_parity(), &header);
+ QuicFecData fec_data;
+ fec_data.fec_group = fec_group_->min_protected_packet();
+ fec_data.redundancy = fec_group_->payload_parity();
+ SerializedPacket serialized = framer_->BuildFecPacket(header, fec_data);
+ fec_group_.reset(NULL);
+ fec_group_number_ = 0;
+ // Reset packet_size_, since the next packet may not have an FEC group.
+ packet_size_ = GetPacketHeaderSize(options_.send_guid_length,
+ send_version_in_packet_,
+ options_.send_sequence_number_length,
+ NOT_IN_FEC_GROUP);
+ DCHECK(serialized.packet);
+ DCHECK_GE(options_.max_packet_length, serialized.packet->length());
+ return serialized;
+}
+
+SerializedPacket QuicPacketCreator::SerializeConnectionClose(
+ QuicConnectionCloseFrame* close_frame) {
+ QuicFrames frames;
+ frames.push_back(QuicFrame(close_frame));
+ return SerializeAllFrames(frames);
+}
+
+QuicEncryptedPacket* QuicPacketCreator::SerializeVersionNegotiationPacket(
+ const QuicVersionVector& supported_versions) {
+ DCHECK(is_server_);
+ QuicPacketPublicHeader header;
+ header.guid = guid_;
+ header.reset_flag = false;
+ header.version_flag = true;
+ header.versions = supported_versions;
+ QuicEncryptedPacket* encrypted =
+ framer_->BuildVersionNegotiationPacket(header, supported_versions);
+ DCHECK(encrypted);
+ DCHECK_GE(options_.max_packet_length, encrypted->length());
+ return encrypted;
+}
+
+void QuicPacketCreator::FillPacketHeader(QuicFecGroupNumber fec_group,
+ bool fec_flag,
+ bool fec_entropy_flag,
+ QuicPacketHeader* header) {
+ header->public_header.guid = guid_;
+ header->public_header.reset_flag = false;
+ header->public_header.version_flag = send_version_in_packet_;
+ header->fec_flag = fec_flag;
+ header->packet_sequence_number = ++sequence_number_;
+
+ bool entropy_flag;
+ if (header->packet_sequence_number == 1) {
+ DCHECK(!fec_flag);
+ // TODO(satyamshekhar): No entropy in the first message.
+ // For crypto tests to pass. Fix this by using deterministic QuicRandom.
+ entropy_flag = 0;
+ } else if (fec_flag) {
+ // FEC packets don't have an entropy of their own. Entropy flag for FEC
+ // packets is the XOR of entropy of previous packets.
+ entropy_flag = fec_entropy_flag;
+ } else {
+ entropy_flag = random_generator_->RandBool();
+ }
+ header->entropy_flag = entropy_flag;
+ header->is_in_fec_group = fec_group == 0 ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+ header->fec_group = fec_group;
+}
+
+bool QuicPacketCreator::ShouldRetransmit(const QuicFrame& frame) {
+ return frame.type != ACK_FRAME && frame.type != CONGESTION_FEEDBACK_FRAME &&
+ frame.type != PADDING_FRAME;
+}
+
+bool QuicPacketCreator::AddFrame(const QuicFrame& frame,
+ bool save_retransmittable_frames) {
+ size_t frame_len = framer_->GetSerializedFrameLength(
+ frame, BytesFree(), queued_frames_.empty());
+ if (frame_len == 0) {
+ return false;
+ }
+ packet_size_ += frame_len;
+
+ if (save_retransmittable_frames && ShouldRetransmit(frame)) {
+ if (queued_retransmittable_frames_.get() == NULL) {
+ queued_retransmittable_frames_.reset(new RetransmittableFrames());
+ }
+ if (frame.type == STREAM_FRAME) {
+ queued_frames_.push_back(
+ queued_retransmittable_frames_->AddStreamFrame(frame.stream_frame));
+ } else {
+ queued_frames_.push_back(
+ queued_retransmittable_frames_->AddNonStreamFrame(frame));
+ }
+ } else {
+ queued_frames_.push_back(frame);
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_packet_creator.h b/chromium/net/quic/quic_packet_creator.h
new file mode 100644
index 00000000000..a1d74fa532f
--- /dev/null
+++ b/chromium/net/quic/quic_packet_creator.h
@@ -0,0 +1,182 @@
+// Copyright (c) 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.
+//
+// Accumulates frames for the next packet until more frames no longer fit or
+// it's time to create a packet from them. Also provides packet creation of
+// FEC packets based on previously created packets.
+
+#ifndef NET_QUIC_QUIC_PACKET_CREATOR_H_
+#define NET_QUIC_QUIC_PACKET_CREATOR_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+class QuicPacketCreatorPeer;
+}
+
+class QuicRandom;
+
+class NET_EXPORT_PRIVATE QuicPacketCreator : public QuicFecBuilderInterface {
+ public:
+ // Options for controlling how packets are created.
+ struct Options {
+ Options()
+ : max_packet_length(kMaxPacketSize),
+ random_reorder(false),
+ max_packets_per_fec_group(0),
+ send_guid_length(PACKET_8BYTE_GUID),
+ send_sequence_number_length(PACKET_6BYTE_SEQUENCE_NUMBER) {
+ }
+
+ size_t max_packet_length;
+ bool random_reorder; // Inefficient: rewrite if used at scale.
+ // 0 indicates fec is disabled.
+ size_t max_packets_per_fec_group;
+ // Length of guid to send over the wire.
+ QuicGuidLength send_guid_length;
+ QuicSequenceNumberLength send_sequence_number_length;
+ };
+
+ // QuicRandom* required for packet entropy.
+ QuicPacketCreator(QuicGuid guid,
+ QuicFramer* framer,
+ QuicRandom* random_generator,
+ bool is_server);
+
+ virtual ~QuicPacketCreator();
+
+ // QuicFecBuilderInterface
+ virtual void OnBuiltFecProtectedPayload(const QuicPacketHeader& header,
+ base::StringPiece payload) OVERRIDE;
+
+ // Checks if it's time to send an FEC packet. |force_close| forces this to
+ // return true if an fec group is open.
+ bool ShouldSendFec(bool force_close) const;
+
+ // Starts a new FEC group with the next serialized packet, if FEC is enabled
+ // and there is not already an FEC group open.
+ void MaybeStartFEC();
+
+ // Makes the framer not serialize the protocol version in sent packets.
+ void StopSendingVersion();
+
+ // The overhead the framing will add for a packet with one frame.
+ static size_t StreamFramePacketOverhead(
+ QuicVersion version,
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length,
+ InFecGroup is_in_fec_group);
+
+ bool HasRoomForStreamFrame(QuicStreamId id, QuicStreamOffset offset) const;
+
+ // Converts a raw payload to a frame which fits into the currently open
+ // packet if there is one. Returns the number of bytes consumed from data.
+ // If data is empty and fin is true, the expected behavior is to consume the
+ // fin but return 0.
+ size_t CreateStreamFrame(QuicStreamId id,
+ base::StringPiece data,
+ QuicStreamOffset offset,
+ bool fin,
+ QuicFrame* frame);
+
+ // Serializes all frames into a single packet. All frames must fit into a
+ // single packet. Also, sets the entropy hash of the serialized packet to a
+ // random bool and returns that value as a member of SerializedPacket.
+ // Never returns a RetransmittableFrames in SerializedPacket.
+ SerializedPacket SerializeAllFrames(const QuicFrames& frames);
+
+ // Returns true if there are frames pending to be serialized.
+ bool HasPendingFrames();
+
+ // Returns the number of bytes which are free to frames in the current packet.
+ size_t BytesFree() const;
+
+ // Adds |frame| to the packet creator's list of frames to be serialized.
+ // Returns false if the frame doesn't fit into the current packet.
+ bool AddSavedFrame(const QuicFrame& frame);
+
+ // Serializes all frames which have been added and adds any which should be
+ // retransmitted to |retransmittable_frames| if it's not NULL. All frames must
+ // fit into a single packet. Sets the entropy hash of the serialized
+ // packet to a random bool and returns that value as a member of
+ // SerializedPacket. Also, sets |serialized_frames| in the SerializedPacket
+ // to the corresponding RetransmittableFrames if any frames are to be
+ // retransmitted.
+ SerializedPacket SerializePacket();
+
+ // Packetize FEC data. All frames must fit into a single packet. Also, sets
+ // the entropy hash of the serialized packet to a random bool and returns
+ // that value as a member of SerializedPacket.
+ SerializedPacket SerializeFec();
+
+ // Creates a packet with connection close frame. Caller owns the created
+ // packet. Also, sets the entropy hash of the serialized packet to a random
+ // bool and returns that value as a member of SerializedPacket.
+ SerializedPacket SerializeConnectionClose(
+ QuicConnectionCloseFrame* close_frame);
+
+ // Creates a version negotiation packet which supports |supported_versions|.
+ // Caller owns the created packet. Also, sets the entropy hash of the
+ // serialized packet to a random bool and returns that value as a member of
+ // SerializedPacket.
+ QuicEncryptedPacket* SerializeVersionNegotiationPacket(
+ const QuicVersionVector& supported_versions);
+
+ QuicPacketSequenceNumber sequence_number() const {
+ return sequence_number_;
+ }
+
+ void set_sequence_number(QuicPacketSequenceNumber s) {
+ sequence_number_ = s;
+ }
+
+ Options* options() {
+ return &options_;
+ }
+
+ private:
+ friend class test::QuicPacketCreatorPeer;
+
+ static bool ShouldRetransmit(const QuicFrame& frame);
+
+ void FillPacketHeader(QuicFecGroupNumber fec_group,
+ bool fec_flag,
+ bool fec_entropy_flag,
+ QuicPacketHeader* header);
+
+ // Allows a frame to be added without creating retransmittable frames.
+ // Particularly useful for retransmits using SerializeAllFrames().
+ bool AddFrame(const QuicFrame& frame, bool save_retransmittable_frames);
+
+ Options options_;
+ QuicGuid guid_;
+ QuicFramer* framer_;
+ QuicRandom* random_generator_;
+ QuicPacketSequenceNumber sequence_number_;
+ QuicFecGroupNumber fec_group_number_;
+ scoped_ptr<QuicFecGroup> fec_group_;
+ // bool to keep track if this packet creator is being used the server.
+ bool is_server_;
+ // Controls whether protocol version should be included while serializing the
+ // packet.
+ bool send_version_in_packet_;
+ size_t packet_size_;
+ QuicFrames queued_frames_;
+ scoped_ptr<RetransmittableFrames> queued_retransmittable_frames_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicPacketCreator);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_PACKET_CREATOR_H_
diff --git a/chromium/net/quic/quic_packet_creator_test.cc b/chromium/net/quic/quic_packet_creator_test.cc
new file mode 100644
index 00000000000..1d793d23e18
--- /dev/null
+++ b/chromium/net/quic/quic_packet_creator_test.cc
@@ -0,0 +1,333 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_packet_creator.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_packet_creator_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicPacketCreatorTest : public ::testing::TestWithParam<bool> {
+ protected:
+ QuicPacketCreatorTest()
+ : server_framer_(QuicVersionMax(), QuicTime::Zero(), true),
+ client_framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ id_(1),
+ sequence_number_(0),
+ guid_(2),
+ data_("foo"),
+ creator_(guid_, &client_framer_, QuicRandom::GetInstance(), false) {
+ client_framer_.set_visitor(&framer_visitor_);
+ server_framer_.set_visitor(&framer_visitor_);
+ }
+ ~QuicPacketCreatorTest() {
+ }
+
+ void ProcessPacket(QuicPacket* packet) {
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ server_framer_.EncryptPacket(ENCRYPTION_NONE, sequence_number_,
+ *packet));
+ server_framer_.ProcessPacket(*encrypted);
+ }
+
+ void CheckStreamFrame(const QuicFrame& frame, QuicStreamId stream_id,
+ const string& data, QuicStreamOffset offset, bool fin) {
+ EXPECT_EQ(STREAM_FRAME, frame.type);
+ ASSERT_TRUE(frame.stream_frame);
+ EXPECT_EQ(stream_id, frame.stream_frame->stream_id);
+ EXPECT_EQ(data, frame.stream_frame->data);
+ EXPECT_EQ(offset, frame.stream_frame->offset);
+ EXPECT_EQ(fin, frame.stream_frame->fin);
+ }
+
+ QuicFrames frames_;
+ QuicFramer server_framer_;
+ QuicFramer client_framer_;
+ testing::StrictMock<MockFramerVisitor> framer_visitor_;
+ QuicStreamId id_;
+ QuicPacketSequenceNumber sequence_number_;
+ QuicGuid guid_;
+ string data_;
+ QuicPacketCreator creator_;
+};
+
+TEST_F(QuicPacketCreatorTest, SerializeFrames) {
+ frames_.push_back(QuicFrame(new QuicAckFrame(0u, QuicTime::Zero(), 0u)));
+ frames_.push_back(QuicFrame(new QuicStreamFrame(
+ 0u, false, 0u, StringPiece(""))));
+ frames_.push_back(QuicFrame(new QuicStreamFrame(
+ 0u, true, 0u, StringPiece(""))));
+ SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+ delete frames_[0].ack_frame;
+ delete frames_[1].stream_frame;
+ delete frames_[2].stream_frame;
+
+ {
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+ EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+ }
+ ProcessPacket(serialized.packet);
+ delete serialized.packet;
+}
+
+TEST_F(QuicPacketCreatorTest, SerializeWithFEC) {
+ creator_.options()->max_packets_per_fec_group = 6;
+ ASSERT_FALSE(creator_.ShouldSendFec(false));
+ creator_.MaybeStartFEC();
+
+ frames_.push_back(QuicFrame(new QuicStreamFrame(
+ 0u, false, 0u, StringPiece(""))));
+ SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+ delete frames_[0].stream_frame;
+
+ {
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+ EXPECT_CALL(framer_visitor_, OnFecProtectedPayload(_));
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+ }
+ ProcessPacket(serialized.packet);
+ delete serialized.packet;
+
+ ASSERT_FALSE(creator_.ShouldSendFec(false));
+ ASSERT_TRUE(creator_.ShouldSendFec(true));
+
+ serialized = creator_.SerializeFec();
+ ASSERT_EQ(2u, serialized.sequence_number);
+
+ {
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+ EXPECT_CALL(framer_visitor_, OnFecData(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+ }
+ ProcessPacket(serialized.packet);
+ delete serialized.packet;
+}
+
+TEST_F(QuicPacketCreatorTest, SerializeConnectionClose) {
+ QuicConnectionCloseFrame frame;
+ frame.error_code = QUIC_NO_ERROR;
+ frame.ack_frame = QuicAckFrame(0u, QuicTime::Zero(), 0u);
+
+ SerializedPacket serialized = creator_.SerializeConnectionClose(&frame);
+ ASSERT_EQ(1u, serialized.sequence_number);
+ ASSERT_EQ(1u, creator_.sequence_number());
+
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_));
+ EXPECT_CALL(framer_visitor_, OnAckFrame(_));
+ EXPECT_CALL(framer_visitor_, OnConnectionCloseFrame(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+
+ ProcessPacket(serialized.packet);
+ delete serialized.packet;
+}
+
+TEST_F(QuicPacketCreatorTest, CreateStreamFrame) {
+ QuicFrame frame;
+ size_t consumed = creator_.CreateStreamFrame(1u, "test", 0u, false, &frame);
+ EXPECT_EQ(4u, consumed);
+ CheckStreamFrame(frame, 1u, "test", 0u, false);
+ delete frame.stream_frame;
+}
+
+TEST_F(QuicPacketCreatorTest, CreateStreamFrameFin) {
+ QuicFrame frame;
+ size_t consumed = creator_.CreateStreamFrame(1u, "test", 10u, true, &frame);
+ EXPECT_EQ(4u, consumed);
+ CheckStreamFrame(frame, 1u, "test", 10u, true);
+ delete frame.stream_frame;
+}
+
+TEST_F(QuicPacketCreatorTest, CreateStreamFrameFinOnly) {
+ QuicFrame frame;
+ size_t consumed = creator_.CreateStreamFrame(1u, "", 0u, true, &frame);
+ EXPECT_EQ(0u, consumed);
+ CheckStreamFrame(frame, 1u, string(), 0u, true);
+ delete frame.stream_frame;
+}
+
+TEST_F(QuicPacketCreatorTest, CreateAllFreeBytesForStreamFrames) {
+ QuicStreamId kStreamId = 1u;
+ QuicStreamOffset kOffset = 1u;
+ for (int i = 0; i < 100; ++i) {
+ creator_.options()->max_packet_length = i;
+ const size_t max_plaintext_size = client_framer_.GetMaxPlaintextSize(i);
+ const bool should_have_room = max_plaintext_size >
+ (QuicFramer::GetMinStreamFrameSize(
+ client_framer_.version(), kStreamId, kOffset, true) +
+ GetPacketHeaderSize(creator_.options()->send_guid_length,
+ kIncludeVersion,
+ creator_.options()->send_sequence_number_length,
+ NOT_IN_FEC_GROUP));
+ ASSERT_EQ(should_have_room,
+ creator_.HasRoomForStreamFrame(kStreamId, kOffset));
+ if (should_have_room) {
+ QuicFrame frame;
+ size_t bytes_consumed = creator_.CreateStreamFrame(
+ kStreamId, "testdata", kOffset, false, &frame);
+ EXPECT_LT(0u, bytes_consumed);
+ ASSERT_TRUE(creator_.AddSavedFrame(frame));
+ SerializedPacket serialized_packet = creator_.SerializePacket();
+ ASSERT_TRUE(serialized_packet.packet);
+ delete serialized_packet.packet;
+ delete serialized_packet.retransmittable_frames;
+ }
+ }
+}
+
+TEST_F(QuicPacketCreatorTest, SerializeVersionNegotiationPacket) {
+ QuicPacketCreatorPeer::SetIsServer(&creator_, true);
+ QuicVersionVector versions;
+ versions.push_back(QuicVersionMax());
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ creator_.SerializeVersionNegotiationPacket(versions));
+
+ {
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnVersionNegotiationPacket(_));
+ }
+ client_framer_.ProcessPacket(*encrypted.get());
+}
+
+INSTANTIATE_TEST_CASE_P(ToggleVersionSerialization,
+ QuicPacketCreatorTest,
+ ::testing::Values(false, true));
+
+TEST_P(QuicPacketCreatorTest, SerializeFrame) {
+ if (!GetParam()) {
+ creator_.StopSendingVersion();
+ }
+ frames_.push_back(QuicFrame(new QuicStreamFrame(
+ 0u, false, 0u, StringPiece(""))));
+ SerializedPacket serialized = creator_.SerializeAllFrames(frames_);
+ delete frames_[0].stream_frame;
+
+ QuicPacketHeader header;
+ {
+ InSequence s;
+ EXPECT_CALL(framer_visitor_, OnPacket());
+ EXPECT_CALL(framer_visitor_, OnPacketHeader(_)).WillOnce(
+ DoAll(SaveArg<0>(&header), Return(true)));
+ EXPECT_CALL(framer_visitor_, OnStreamFrame(_));
+ EXPECT_CALL(framer_visitor_, OnPacketComplete());
+ }
+ ProcessPacket(serialized.packet);
+ EXPECT_EQ(GetParam(), header.public_header.version_flag);
+ delete serialized.packet;
+}
+
+TEST_P(QuicPacketCreatorTest, CreateStreamFrameTooLarge) {
+ if (!GetParam()) {
+ creator_.StopSendingVersion();
+ }
+ // A string larger than fits into a frame.
+ size_t payload_length;
+ creator_.options()->max_packet_length = GetPacketLengthForOneStream(
+ client_framer_.version(),
+ QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+ NOT_IN_FEC_GROUP, &payload_length);
+ QuicFrame frame;
+ const string too_long_payload(payload_length * 2, 'a');
+ size_t consumed = creator_.CreateStreamFrame(
+ 1u, too_long_payload, 0u, true, &frame);
+ EXPECT_EQ(payload_length, consumed);
+ const string payload(payload_length, 'a');
+ CheckStreamFrame(frame, 1u, payload, 0u, false);
+ delete frame.stream_frame;
+}
+
+TEST_P(QuicPacketCreatorTest, AddFrameAndSerialize) {
+ if (!GetParam()) {
+ creator_.StopSendingVersion();
+ }
+ const size_t max_plaintext_size =
+ client_framer_.GetMaxPlaintextSize(creator_.options()->max_packet_length);
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ EXPECT_EQ(max_plaintext_size -
+ GetPacketHeaderSize(
+ creator_.options()->send_guid_length,
+ QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+ PACKET_6BYTE_SEQUENCE_NUMBER,
+ NOT_IN_FEC_GROUP),
+ creator_.BytesFree());
+
+ // Add a variety of frame types and then a padding frame.
+ QuicAckFrame ack_frame;
+ EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+
+ QuicCongestionFeedbackFrame congestion_feedback;
+ congestion_feedback.type = kFixRate;
+ EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&congestion_feedback)));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+
+ QuicFrame frame;
+ size_t consumed = creator_.CreateStreamFrame(1u, "test", 0u, false, &frame);
+ EXPECT_EQ(4u, consumed);
+ ASSERT_TRUE(frame.stream_frame);
+ EXPECT_TRUE(creator_.AddSavedFrame(frame));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+
+ QuicPaddingFrame padding_frame;
+ EXPECT_TRUE(creator_.AddSavedFrame(QuicFrame(&padding_frame)));
+ EXPECT_TRUE(creator_.HasPendingFrames());
+ EXPECT_EQ(0u, creator_.BytesFree());
+
+ EXPECT_FALSE(creator_.AddSavedFrame(QuicFrame(&ack_frame)));
+
+ // Ensure the packet is successfully created.
+ SerializedPacket serialized = creator_.SerializePacket();
+ ASSERT_TRUE(serialized.packet);
+ delete serialized.packet;
+ ASSERT_TRUE(serialized.retransmittable_frames);
+ RetransmittableFrames* retransmittable = serialized.retransmittable_frames;
+ ASSERT_EQ(1u, retransmittable->frames().size());
+ EXPECT_EQ(STREAM_FRAME, retransmittable->frames()[0].type);
+ ASSERT_TRUE(retransmittable->frames()[0].stream_frame);
+ delete serialized.retransmittable_frames;
+
+ EXPECT_FALSE(creator_.HasPendingFrames());
+ EXPECT_EQ(max_plaintext_size -
+ GetPacketHeaderSize(
+ creator_.options()->send_guid_length,
+ QuicPacketCreatorPeer::SendVersionInPacket(&creator_),
+ PACKET_6BYTE_SEQUENCE_NUMBER,
+ NOT_IN_FEC_GROUP),
+ creator_.BytesFree());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_packet_generator.cc b/chromium/net/quic/quic_packet_generator.cc
new file mode 100644
index 00000000000..7600010a067
--- /dev/null
+++ b/chromium/net/quic/quic_packet_generator.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_packet_generator.h"
+
+#include "base/logging.h"
+#include "net/quic/quic_fec_group.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+
+namespace net {
+
+QuicPacketGenerator::QuicPacketGenerator(DelegateInterface* delegate,
+ DebugDelegateInterface* debug_delegate,
+ QuicPacketCreator* creator)
+ : delegate_(delegate),
+ debug_delegate_(debug_delegate),
+ packet_creator_(creator),
+ should_flush_(true),
+ should_send_ack_(false),
+ should_send_feedback_(false) {
+}
+
+QuicPacketGenerator::~QuicPacketGenerator() {
+ for (QuicFrames::iterator it = queued_control_frames_.begin();
+ it != queued_control_frames_.end(); ++it) {
+ switch (it->type) {
+ case PADDING_FRAME:
+ delete it->padding_frame;
+ break;
+ case STREAM_FRAME:
+ delete it->stream_frame;
+ break;
+ case ACK_FRAME:
+ delete it->ack_frame;
+ break;
+ case CONGESTION_FEEDBACK_FRAME:
+ delete it->congestion_feedback_frame;
+ break;
+ case RST_STREAM_FRAME:
+ delete it->rst_stream_frame;
+ break;
+ case CONNECTION_CLOSE_FRAME:
+ delete it->connection_close_frame;
+ break;
+ case GOAWAY_FRAME:
+ delete it->goaway_frame;
+ break;
+ case NUM_FRAME_TYPES:
+ DCHECK(false) << "Cannot delete type: " << it->type;
+ }
+ }
+}
+
+void QuicPacketGenerator::SetShouldSendAck(bool also_send_feedback) {
+ should_send_ack_ = true;
+ should_send_feedback_ = also_send_feedback;
+ SendQueuedFrames();
+}
+
+
+void QuicPacketGenerator::AddControlFrame(const QuicFrame& frame) {
+ queued_control_frames_.push_back(frame);
+ SendQueuedFrames();
+}
+
+QuicConsumedData QuicPacketGenerator::ConsumeData(QuicStreamId id,
+ StringPiece data,
+ QuicStreamOffset offset,
+ bool fin) {
+ SendQueuedFrames();
+
+ size_t total_bytes_consumed = 0;
+ bool fin_consumed = false;
+
+ while (delegate_->CanWrite(NOT_RETRANSMISSION, HAS_RETRANSMITTABLE_DATA,
+ NOT_HANDSHAKE)) {
+ QuicFrame frame;
+ size_t bytes_consumed = packet_creator_->CreateStreamFrame(
+ id, data, offset + total_bytes_consumed, fin, &frame);
+ bool success = AddFrame(frame);
+ DCHECK(success);
+
+ total_bytes_consumed += bytes_consumed;
+ fin_consumed = fin && bytes_consumed == data.size();
+ data.remove_prefix(bytes_consumed);
+ DCHECK(data.empty() || packet_creator_->BytesFree() == 0u);
+
+ // TODO(ianswett): Restore packet reordering.
+ if (should_flush_ || !packet_creator_->HasRoomForStreamFrame(id, offset)) {
+ SerializeAndSendPacket();
+ }
+
+ if (data.empty()) {
+ // We're done writing the data. Exit the loop.
+ // We don't make this a precondition because we could have 0 bytes of data
+ // if we're simply writing a fin.
+ break;
+ }
+ }
+
+ // Ensure the FEC group is closed at the end of this method unless other
+ // writes are pending.
+ if (should_flush_ && packet_creator_->ShouldSendFec(true)) {
+ SerializedPacket serialized_fec = packet_creator_->SerializeFec();
+ DCHECK(serialized_fec.packet);
+ delegate_->OnSerializedPacket(serialized_fec);
+ }
+
+ DCHECK(!should_flush_ || !packet_creator_->HasPendingFrames());
+ return QuicConsumedData(total_bytes_consumed, fin_consumed);
+}
+
+bool QuicPacketGenerator::CanSendWithNextPendingFrameAddition() const {
+ DCHECK(HasPendingFrames());
+ HasRetransmittableData retransmittable =
+ (should_send_ack_ || should_send_feedback_) ? NO_RETRANSMITTABLE_DATA
+ : HAS_RETRANSMITTABLE_DATA;
+ if (retransmittable == HAS_RETRANSMITTABLE_DATA) {
+ DCHECK(!queued_control_frames_.empty()); // These are retransmittable.
+ }
+ return delegate_->CanWrite(NOT_RETRANSMISSION, retransmittable,
+ NOT_HANDSHAKE);
+}
+
+void QuicPacketGenerator::SendQueuedFrames() {
+ packet_creator_->MaybeStartFEC();
+ // Only add pending frames if we are SURE we can then send the whole packet.
+ while (HasPendingFrames() && CanSendWithNextPendingFrameAddition()) {
+ if (!AddNextPendingFrame()) {
+ // Packet was full, so serialize and send it.
+ SerializeAndSendPacket();
+ }
+ }
+
+ if (should_flush_) {
+ if (packet_creator_->HasPendingFrames()) {
+ SerializeAndSendPacket();
+ }
+
+ // Ensure the FEC group is closed at the end of this method unless other
+ // writes are pending.
+ if (packet_creator_->ShouldSendFec(true)) {
+ SerializedPacket serialized_fec = packet_creator_->SerializeFec();
+ DCHECK(serialized_fec.packet);
+ delegate_->OnSerializedPacket(serialized_fec);
+ packet_creator_->MaybeStartFEC();
+ }
+ }
+}
+
+void QuicPacketGenerator::StartBatchOperations() {
+ should_flush_ = false;
+}
+
+void QuicPacketGenerator::FinishBatchOperations() {
+ should_flush_ = true;
+ SendQueuedFrames();
+}
+
+bool QuicPacketGenerator::HasQueuedFrames() const {
+ return packet_creator_->HasPendingFrames() || HasPendingFrames();
+}
+
+bool QuicPacketGenerator::HasPendingFrames() const {
+ return should_send_ack_ || should_send_feedback_ ||
+ !queued_control_frames_.empty();
+}
+
+bool QuicPacketGenerator::AddNextPendingFrame() {
+ if (should_send_ack_) {
+ pending_ack_frame_.reset((delegate_->CreateAckFrame()));
+ // If we can't this add the frame now, then we still need to do so later.
+ should_send_ack_ = !AddFrame(QuicFrame(pending_ack_frame_.get()));
+ // Return success if we have cleared out this flag (i.e., added the frame).
+ // If we still need to send, then the frame is full, and we have failed.
+ return !should_send_ack_;
+ }
+
+ if (should_send_feedback_) {
+ pending_feedback_frame_.reset((delegate_->CreateFeedbackFrame()));
+ // If we can't this add the frame now, then we still need to do so later.
+ should_send_feedback_ = !AddFrame(QuicFrame(pending_feedback_frame_.get()));
+ // Return success if we have cleared out this flag (i.e., added the frame).
+ // If we still need to send, then the frame is full, and we have failed.
+ return !should_send_feedback_;
+ }
+
+ DCHECK(!queued_control_frames_.empty());
+ if (!AddFrame(queued_control_frames_.back())) {
+ // Packet was full.
+ return false;
+ }
+ queued_control_frames_.pop_back();
+ return true;
+}
+
+bool QuicPacketGenerator::AddFrame(const QuicFrame& frame) {
+ bool success = packet_creator_->AddSavedFrame(frame);
+ if (success && debug_delegate_) {
+ debug_delegate_->OnFrameAddedToPacket(frame);
+ }
+ return success;
+}
+
+void QuicPacketGenerator::SerializeAndSendPacket() {
+ SerializedPacket serialized_packet = packet_creator_->SerializePacket();
+ DCHECK(serialized_packet.packet);
+ delegate_->OnSerializedPacket(serialized_packet);
+
+ if (packet_creator_->ShouldSendFec(false)) {
+ SerializedPacket serialized_fec = packet_creator_->SerializeFec();
+ DCHECK(serialized_fec.packet);
+ delegate_->OnSerializedPacket(serialized_fec);
+ packet_creator_->MaybeStartFEC();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_packet_generator.h b/chromium/net/quic/quic_packet_generator.h
new file mode 100644
index 00000000000..e8b09e64634
--- /dev/null
+++ b/chromium/net/quic/quic_packet_generator.h
@@ -0,0 +1,145 @@
+// Copyright (c) 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.
+//
+// Responsible for generating packets on behalf of a QuicConnection.
+// Packets are serialized just-in-time. Control frames are queued.
+// Ack and Feedback frames will be requested from the Connection
+// just-in-time. When a packet needs to be sent, the Generator
+// will serialize a packet and pass it to QuicConnection::SendOrQueuePacket()
+//
+// The Generator's mode of operation is controlled by two conditions:
+//
+// 1) Is the Delegate writable?
+//
+// If the Delegate is not writable, then no operations will cause
+// a packet to be serialized. In particular:
+// * SetShouldSendAck will simply record that an ack is to be sent.
+// * AddControlFram will enqueue the control frame.
+// * ConsumeData will do nothing.
+//
+// If the Delegate is writable, then the behavior depends on the second
+// condition:
+//
+// 2) Is the Generator in batch mode?
+//
+// If the Generator is NOT in batch mode, then each call to a write
+// operation will serialize one or more packets. The contents will
+// include any previous queued frames. If an ack should be sent
+// but has not been sent, then the Delegate will be asked to create
+// an Ack frame which will then be included in the packet. When
+// the write call completes, the current packet will be serialized
+// and sent to the Delegate, even if it is not full.
+//
+// If the Generator is in batch mode, then each write operation will
+// add data to the "current" packet. When the current packet becomes
+// full, it will be serialized and sent to the packet. When batch
+// mode is ended via |FinishBatchOperations|, the current packet
+// will be serialzied, even if it is not full.
+//
+// FEC behavior also depends on batch mode. In batch mode, FEC packets
+// will be sent after |max_packets_per_group| have been sent, as well
+// as after batch operations are complete. When not in batch mode,
+// an FEC packet will be sent after each write call completes.
+//
+// TODO(rch): This behavior should probably be tuned. When not in batch
+// mode, we should probably set a timer so that several independent
+// operations can be grouped into the same FEC group.
+//
+// When an FEC packet is generated, it will be send to the Delegate,
+// even if the Delegate has become unwritable after handling the
+// data packet immediately proceeding the FEC packet.
+
+#ifndef NET_QUIC_QUIC_PACKET_GENERATOR_H_
+#define NET_QUIC_QUIC_PACKET_GENERATOR_H_
+
+#include "net/quic/quic_packet_creator.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicPacketGenerator {
+ public:
+ class NET_EXPORT_PRIVATE DelegateInterface {
+ public:
+ virtual ~DelegateInterface() {}
+ virtual bool CanWrite(Retransmission retransmission,
+ HasRetransmittableData retransmittable,
+ IsHandshake handshake) = 0;
+ virtual QuicAckFrame* CreateAckFrame() = 0;
+ virtual QuicCongestionFeedbackFrame* CreateFeedbackFrame() = 0;
+ // Takes ownership of |packet.packet| and |packet.retransmittable_frames|.
+ virtual bool OnSerializedPacket(const SerializedPacket& packet) = 0;
+ };
+
+ // Interface which gets callbacks from the QuicPacketGenerator at interesting
+ // points. Implementations must not mutate the state of the generator
+ // as a result of these callbacks.
+ class NET_EXPORT_PRIVATE DebugDelegateInterface {
+ public:
+ virtual ~DebugDelegateInterface() {}
+
+ // Called when a frame has been added to the current packet.
+ virtual void OnFrameAddedToPacket(const QuicFrame& frame) = 0;
+ };
+
+ QuicPacketGenerator(DelegateInterface* delegate,
+ DebugDelegateInterface* debug_delegate,
+ QuicPacketCreator* creator);
+
+ virtual ~QuicPacketGenerator();
+
+ void SetShouldSendAck(bool also_send_feedback);
+ void AddControlFrame(const QuicFrame& frame);
+ QuicConsumedData ConsumeData(QuicStreamId id,
+ base::StringPiece data,
+ QuicStreamOffset offset,
+ bool fin);
+
+ // Disables flushing.
+ void StartBatchOperations();
+ // Enables flushing and flushes queued data.
+ void FinishBatchOperations();
+
+ bool HasQueuedFrames() const;
+
+ void set_debug_delegate(DebugDelegateInterface* debug_delegate) {
+ debug_delegate_ = debug_delegate;
+ }
+
+ private:
+ void SendQueuedFrames();
+
+ // Test to see if we have pending ack, feedback, or control frames.
+ bool HasPendingFrames() const;
+ // Test to see if the addition of a pending frame (which might be
+ // retransmittable) would still allow the resulting packet to be sent now.
+ bool CanSendWithNextPendingFrameAddition() const;
+ // Add exactly one pending frame, preferring ack over feedback over control
+ // frames.
+ bool AddNextPendingFrame();
+
+ bool AddFrame(const QuicFrame& frame);
+ void SerializeAndSendPacket();
+
+ DelegateInterface* delegate_;
+ DebugDelegateInterface* debug_delegate_;
+
+ QuicPacketCreator* packet_creator_;
+ QuicFrames queued_control_frames_;
+ bool should_flush_;
+ // Flags to indicate the need for just-in-time construction of a frame.
+ bool should_send_ack_;
+ bool should_send_feedback_;
+ // If we put a non-retransmittable frame (namley ack or feedback frame) in
+ // this packet, then we have to hold a reference to it until we flush (and
+ // serialize it). Retransmittable frames are referenced elsewhere so that they
+ // can later be (optionally) retransmitted.
+ scoped_ptr<QuicAckFrame> pending_ack_frame_;
+ scoped_ptr<QuicCongestionFeedbackFrame> pending_feedback_frame_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicPacketGenerator);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_PACKET_GENERATOR_H_
diff --git a/chromium/net/quic/quic_packet_generator_test.cc b/chromium/net/quic/quic_packet_generator_test.cc
new file mode 100644
index 00000000000..45556f174e9
--- /dev/null
+++ b/chromium/net/quic/quic_packet_generator_test.cc
@@ -0,0 +1,536 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_packet_generator.h"
+
+#include <string>
+
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockDelegate : public QuicPacketGenerator::DelegateInterface {
+ public:
+ MockDelegate() {}
+ virtual ~MockDelegate() {}
+
+ MOCK_METHOD3(CanWrite, bool(Retransmission retransmission,
+ HasRetransmittableData retransmittable,
+ IsHandshake handshake));
+
+ MOCK_METHOD0(CreateAckFrame, QuicAckFrame*());
+ MOCK_METHOD0(CreateFeedbackFrame, QuicCongestionFeedbackFrame*());
+ MOCK_METHOD1(OnSerializedPacket, bool(const SerializedPacket& packet));
+
+ void SetCanWriteAnything() {
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, _, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA, _))
+ .WillRepeatedly(Return(true));
+ }
+
+ void SetCanNotWrite() {
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, _, _))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA, _))
+ .WillRepeatedly(Return(false));
+ }
+
+ // Use this when only ack and feedback frames should be allowed to be written.
+ void SetCanWriteOnlyNonRetransmittable() {
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, _, _))
+ .WillRepeatedly(Return(false));
+ EXPECT_CALL(*this, CanWrite(NOT_RETRANSMISSION, NO_RETRANSMITTABLE_DATA, _))
+ .WillRepeatedly(Return(true));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+// Simple struct for describing the contents of a packet.
+// Useful in conjunction with a SimpleQuicFrame for validating
+// that a packet contains the expected frames.
+struct PacketContents {
+ PacketContents()
+ : num_ack_frames(0),
+ num_connection_close_frames(0),
+ num_feedback_frames(0),
+ num_goaway_frames(0),
+ num_rst_stream_frames(0),
+ num_stream_frames(0),
+ fec_group(0) {
+ }
+
+ size_t num_ack_frames;
+ size_t num_connection_close_frames;
+ size_t num_feedback_frames;
+ size_t num_goaway_frames;
+ size_t num_rst_stream_frames;
+ size_t num_stream_frames;
+
+ QuicFecGroupNumber fec_group;
+};
+
+} // namespace
+
+class QuicPacketGeneratorTest : public ::testing::Test {
+ protected:
+ QuicPacketGeneratorTest()
+ : framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ creator_(42, &framer_, &random_, false),
+ generator_(&delegate_, NULL, &creator_),
+ packet_(0, NULL, 0, NULL),
+ packet2_(0, NULL, 0, NULL),
+ packet3_(0, NULL, 0, NULL),
+ packet4_(0, NULL, 0, NULL),
+ packet5_(0, NULL, 0, NULL) {
+ }
+
+ ~QuicPacketGeneratorTest() {
+ delete packet_.packet;
+ delete packet_.retransmittable_frames;
+ delete packet2_.packet;
+ delete packet2_.retransmittable_frames;
+ delete packet3_.packet;
+ delete packet3_.retransmittable_frames;
+ delete packet4_.packet;
+ delete packet4_.retransmittable_frames;
+ delete packet5_.packet;
+ delete packet5_.retransmittable_frames;
+ }
+
+ QuicAckFrame* CreateAckFrame() {
+ // TODO(rch): Initialize this so it can be verified later.
+ return new QuicAckFrame(0, QuicTime::Zero(), 0);
+ }
+
+ QuicCongestionFeedbackFrame* CreateFeedbackFrame() {
+ QuicCongestionFeedbackFrame* frame = new QuicCongestionFeedbackFrame;
+ frame->type = kFixRate;
+ frame->fix_rate.bitrate = QuicBandwidth::FromBytesPerSecond(42);
+ return frame;
+ }
+
+ QuicRstStreamFrame* CreateRstStreamFrame() {
+ return new QuicRstStreamFrame(1, QUIC_STREAM_NO_ERROR);
+ }
+
+ QuicGoAwayFrame* CreateGoAwayFrame() {
+ return new QuicGoAwayFrame(QUIC_NO_ERROR, 1, string());
+ }
+
+ void CheckPacketContains(const PacketContents& contents,
+ const SerializedPacket& packet) {
+ size_t num_retransmittable_frames = contents.num_connection_close_frames +
+ contents.num_goaway_frames + contents.num_rst_stream_frames +
+ contents.num_stream_frames;
+ size_t num_frames = contents.num_feedback_frames + contents.num_ack_frames +
+ num_retransmittable_frames;
+
+ if (num_retransmittable_frames == 0) {
+ ASSERT_TRUE(packet.retransmittable_frames == NULL);
+ } else {
+ ASSERT_TRUE(packet.retransmittable_frames != NULL);
+ EXPECT_EQ(num_retransmittable_frames,
+ packet.retransmittable_frames->frames().size());
+ }
+
+ ASSERT_TRUE(packet.packet != NULL);
+ ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+ EXPECT_EQ(num_frames, simple_framer_.num_frames());
+ EXPECT_EQ(contents.num_ack_frames, simple_framer_.ack_frames().size());
+ EXPECT_EQ(contents.num_connection_close_frames,
+ simple_framer_.connection_close_frames().size());
+ EXPECT_EQ(contents.num_feedback_frames,
+ simple_framer_.feedback_frames().size());
+ EXPECT_EQ(contents.num_goaway_frames,
+ simple_framer_.goaway_frames().size());
+ EXPECT_EQ(contents.num_rst_stream_frames,
+ simple_framer_.rst_stream_frames().size());
+ EXPECT_EQ(contents.num_stream_frames,
+ simple_framer_.stream_frames().size());
+ EXPECT_EQ(contents.fec_group, simple_framer_.header().fec_group);
+ }
+
+ void CheckPacketHasSingleStreamFrame(const SerializedPacket& packet) {
+ ASSERT_TRUE(packet.retransmittable_frames != NULL);
+ EXPECT_EQ(1u, packet.retransmittable_frames->frames().size());
+ ASSERT_TRUE(packet.packet != NULL);
+ ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+ EXPECT_EQ(1u, simple_framer_.num_frames());
+ EXPECT_EQ(1u, simple_framer_.stream_frames().size());
+ }
+
+ void CheckPacketIsFec(const SerializedPacket& packet,
+ QuicPacketSequenceNumber fec_group) {
+ ASSERT_TRUE(packet.retransmittable_frames == NULL);
+ ASSERT_TRUE(packet.packet != NULL);
+ ASSERT_TRUE(simple_framer_.ProcessPacket(*packet.packet));
+ EXPECT_TRUE(simple_framer_.header().fec_flag);
+ EXPECT_EQ(fec_group, simple_framer_.fec_data().fec_group);
+ }
+
+ StringPiece CreateData(size_t len) {
+ data_array_.reset(new char[len]);
+ memset(data_array_.get(), '?', len);
+ return StringPiece(data_array_.get(), len);
+ }
+
+ QuicFramer framer_;
+ MockRandom random_;
+ QuicPacketCreator creator_;
+ testing::StrictMock<MockDelegate> delegate_;
+ QuicPacketGenerator generator_;
+ SimpleQuicFramer simple_framer_;
+ SerializedPacket packet_;
+ SerializedPacket packet2_;
+ SerializedPacket packet3_;
+ SerializedPacket packet4_;
+ SerializedPacket packet5_;
+
+ private:
+ scoped_ptr<char[]> data_array_;
+};
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_NotWritable) {
+ delegate_.SetCanNotWrite();
+
+ generator_.SetShouldSendAck(false);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_WritableAndShouldNotFlush) {
+ delegate_.SetCanWriteOnlyNonRetransmittable();
+ generator_.StartBatchOperations();
+
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+
+ generator_.SetShouldSendAck(false);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ShouldSendAck_WritableAndShouldFlush) {
+ delegate_.SetCanWriteOnlyNonRetransmittable();
+
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+
+ generator_.SetShouldSendAck(false);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_ack_frames = 1;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest,
+ ShouldSendAckWithFeedback_WritableAndShouldNotFlush) {
+ delegate_.SetCanWriteOnlyNonRetransmittable();
+ generator_.StartBatchOperations();
+
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+ EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+ Return(CreateFeedbackFrame()));
+
+ generator_.SetShouldSendAck(true);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest,
+ ShouldSendAckWithFeedback_WritableAndShouldFlush) {
+ delegate_.SetCanWriteOnlyNonRetransmittable();
+
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+ EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+ Return(CreateFeedbackFrame()));
+
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+
+ generator_.SetShouldSendAck(true);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_ack_frames = 1;
+ contents.num_feedback_frames = 1;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_NotWritable) {
+ delegate_.SetCanNotWrite();
+
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_OnlyAckWritable) {
+ delegate_.SetCanWriteOnlyNonRetransmittable();
+
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_WritableAndShouldNotFlush) {
+ delegate_.SetCanWriteAnything();
+ generator_.StartBatchOperations();
+
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, AddControlFrame_WritableAndShouldFlush) {
+ delegate_.SetCanWriteAnything();
+
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_rst_stream_frames = 1;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_NotWritable) {
+ delegate_.SetCanNotWrite();
+
+ QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true);
+ EXPECT_EQ(0u, consumed.bytes_consumed);
+ EXPECT_FALSE(consumed.fin_consumed);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldNotFlush) {
+ delegate_.SetCanWriteAnything();
+ generator_.StartBatchOperations();
+
+ QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true);
+ EXPECT_EQ(3u, consumed.bytes_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_WritableAndShouldFlush) {
+ delegate_.SetCanWriteAnything();
+
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ QuicConsumedData consumed = generator_.ConsumeData(1, "foo", 2, true);
+ EXPECT_EQ(3u, consumed.bytes_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_stream_frames = 1;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest,
+ ConsumeDataMultipleTimes_WritableAndShouldNotFlush) {
+ delegate_.SetCanWriteAnything();
+ generator_.StartBatchOperations();
+
+ generator_.ConsumeData(1, "foo", 2, true);
+ QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false);
+ EXPECT_EQ(4u, consumed.bytes_consumed);
+ EXPECT_FALSE(consumed.fin_consumed);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeData_BatchOperations) {
+ delegate_.SetCanWriteAnything();
+ generator_.StartBatchOperations();
+
+ generator_.ConsumeData(1, "foo", 2, true);
+ QuicConsumedData consumed = generator_.ConsumeData(3, "quux", 7, false);
+ EXPECT_EQ(4u, consumed.bytes_consumed);
+ EXPECT_FALSE(consumed.fin_consumed);
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+
+ // Now both frames will be flushed out.
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ generator_.FinishBatchOperations();
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_stream_frames = 2;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeDataFEC) {
+ delegate_.SetCanWriteAnything();
+
+ // Send FEC every two packets.
+ creator_.options()->max_packets_per_fec_group = 2;
+
+ {
+ InSequence dummy;
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet2_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet3_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet4_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet5_), Return(true)));
+ }
+
+ // Send enough data to create 3 packets: two full and one partial.
+ size_t data_len = 2 * kMaxPacketSize + 100;
+ QuicConsumedData consumed =
+ generator_.ConsumeData(3, CreateData(data_len), 0, true);
+ EXPECT_EQ(data_len, consumed.bytes_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ CheckPacketHasSingleStreamFrame(packet_);
+ CheckPacketHasSingleStreamFrame(packet2_);
+ CheckPacketIsFec(packet3_, 1);
+
+ CheckPacketHasSingleStreamFrame(packet4_);
+ CheckPacketIsFec(packet5_, 4);
+}
+
+TEST_F(QuicPacketGeneratorTest, ConsumeDataSendsFecAtEnd) {
+ delegate_.SetCanWriteAnything();
+
+ // Send FEC every six packets.
+ creator_.options()->max_packets_per_fec_group = 6;
+
+ {
+ InSequence dummy;
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet2_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet3_), Return(true)));
+ }
+
+ // Send enough data to create 2 packets: one full and one partial.
+ size_t data_len = 1 * kMaxPacketSize + 100;
+ QuicConsumedData consumed =
+ generator_.ConsumeData(3, CreateData(data_len), 0, true);
+ EXPECT_EQ(data_len, consumed.bytes_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ CheckPacketHasSingleStreamFrame(packet_);
+ CheckPacketHasSingleStreamFrame(packet2_);
+ CheckPacketIsFec(packet3_, 1);
+}
+
+TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations) {
+ delegate_.SetCanNotWrite();
+
+ generator_.SetShouldSendAck(true);
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+
+ delegate_.SetCanWriteAnything();
+
+ generator_.StartBatchOperations();
+
+ // When the first write operation is invoked, the ack and feedback
+ // frames will be returned.
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+ EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+ Return(CreateFeedbackFrame()));
+
+ // Send some data and a control frame
+ generator_.ConsumeData(3, "quux", 7, false);
+ generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame()));
+
+ // All five frames will be flushed out in a single packet.
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ generator_.FinishBatchOperations();
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ PacketContents contents;
+ contents.num_ack_frames = 1;
+ contents.num_goaway_frames = 1;
+ contents.num_feedback_frames = 1;
+ contents.num_rst_stream_frames = 1;
+ contents.num_stream_frames = 1;
+ CheckPacketContains(contents, packet_);
+}
+
+TEST_F(QuicPacketGeneratorTest, NotWritableThenBatchOperations2) {
+ delegate_.SetCanNotWrite();
+
+ generator_.SetShouldSendAck(true);
+ generator_.AddControlFrame(QuicFrame(CreateRstStreamFrame()));
+ EXPECT_TRUE(generator_.HasQueuedFrames());
+
+ delegate_.SetCanWriteAnything();
+
+ generator_.StartBatchOperations();
+
+ // When the first write operation is invoked, the ack and feedback
+ // frames will be returned.
+ EXPECT_CALL(delegate_, CreateAckFrame()).WillOnce(Return(CreateAckFrame()));
+ EXPECT_CALL(delegate_, CreateFeedbackFrame()).WillOnce(
+ Return(CreateFeedbackFrame()));
+
+ {
+ InSequence dummy;
+ // All five frames will be flushed out in a single packet
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet_), Return(true)));
+ EXPECT_CALL(delegate_, OnSerializedPacket(_)).WillOnce(
+ DoAll(SaveArg<0>(&packet2_), Return(true)));
+ }
+
+ // Send enough data to exceed one packet
+ size_t data_len = kMaxPacketSize + 100;
+ QuicConsumedData consumed =
+ generator_.ConsumeData(3, CreateData(data_len), 0, true);
+ EXPECT_EQ(data_len, consumed.bytes_consumed);
+ EXPECT_TRUE(consumed.fin_consumed);
+ generator_.AddControlFrame(QuicFrame(CreateGoAwayFrame()));
+
+ generator_.FinishBatchOperations();
+ EXPECT_FALSE(generator_.HasQueuedFrames());
+
+ // The first packet should have the queued data and part of the stream data.
+ PacketContents contents;
+ contents.num_ack_frames = 1;
+ contents.num_feedback_frames = 1;
+ contents.num_rst_stream_frames = 1;
+ contents.num_stream_frames = 1;
+ CheckPacketContains(contents, packet_);
+
+ // The second should have the remainder of the stream data.
+ PacketContents contents2;
+ contents2.num_goaway_frames = 1;
+ contents2.num_stream_frames = 1;
+ CheckPacketContains(contents2, packet2_);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_protocol.cc b/chromium/net/quic/quic_protocol.cc
new file mode 100644
index 00000000000..4c2e5527971
--- /dev/null
+++ b/chromium/net/quic/quic_protocol.cc
@@ -0,0 +1,429 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_protocol.h"
+
+#include "base/stl_util.h"
+#include "net/quic/quic_utils.h"
+
+using base::StringPiece;
+using std::map;
+using std::numeric_limits;
+using std::ostream;
+using std::string;
+
+namespace net {
+
+size_t GetPacketHeaderSize(QuicPacketHeader header) {
+ return GetPacketHeaderSize(header.public_header.guid_length,
+ header.public_header.version_flag,
+ header.public_header.sequence_number_length,
+ header.is_in_fec_group);
+}
+
+size_t GetPacketHeaderSize(QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length,
+ InFecGroup is_in_fec_group) {
+ return kPublicFlagsSize + guid_length +
+ (include_version ? kQuicVersionSize : 0) + sequence_number_length +
+ kPrivateFlagsSize + (is_in_fec_group == IN_FEC_GROUP ? kFecGroupSize : 0);
+}
+
+size_t GetPublicResetPacketSize() {
+ return kPublicFlagsSize + PACKET_8BYTE_GUID + kPublicResetNonceSize +
+ PACKET_6BYTE_SEQUENCE_NUMBER;
+}
+
+size_t GetStartOfFecProtectedData(
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return GetPacketHeaderSize(
+ guid_length, include_version, sequence_number_length, IN_FEC_GROUP);
+}
+
+size_t GetStartOfEncryptedData(
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ // Don't include the fec size, since encryption starts before private flags.
+ return GetPacketHeaderSize(
+ guid_length, include_version, sequence_number_length, NOT_IN_FEC_GROUP) -
+ kPrivateFlagsSize;
+}
+
+QuicPacketPublicHeader::QuicPacketPublicHeader()
+ : guid(0),
+ guid_length(PACKET_8BYTE_GUID),
+ reset_flag(false),
+ version_flag(false),
+ sequence_number_length(PACKET_6BYTE_SEQUENCE_NUMBER) {
+}
+
+QuicPacketPublicHeader::QuicPacketPublicHeader(
+ const QuicPacketPublicHeader& other)
+ : guid(other.guid),
+ guid_length(other.guid_length),
+ reset_flag(other.reset_flag),
+ version_flag(other.version_flag),
+ sequence_number_length(other.sequence_number_length),
+ versions(other.versions) {
+}
+
+QuicPacketPublicHeader::~QuicPacketPublicHeader() {}
+
+QuicPacketPublicHeader& QuicPacketPublicHeader::operator=(
+ const QuicPacketPublicHeader& other) {
+ guid = other.guid;
+ reset_flag = other.reset_flag;
+ version_flag = other.version_flag;
+ versions = other.versions;
+ return *this;
+}
+
+QuicPacketHeader::QuicPacketHeader()
+ : fec_flag(false),
+ entropy_flag(false),
+ entropy_hash(0),
+ packet_sequence_number(0),
+ is_in_fec_group(NOT_IN_FEC_GROUP),
+ fec_group(0) {
+}
+
+QuicPacketHeader::QuicPacketHeader(const QuicPacketPublicHeader& header)
+ : public_header(header),
+ fec_flag(false),
+ entropy_flag(false),
+ entropy_hash(0),
+ packet_sequence_number(0),
+ is_in_fec_group(NOT_IN_FEC_GROUP),
+ fec_group(0) {
+}
+
+QuicStreamFrame::QuicStreamFrame() {}
+
+QuicStreamFrame::QuicStreamFrame(QuicStreamId stream_id,
+ bool fin,
+ QuicStreamOffset offset,
+ StringPiece data)
+ : stream_id(stream_id),
+ fin(fin),
+ offset(offset),
+ data(data) {
+}
+
+uint32 MakeQuicTag(char a, char b, char c, char d) {
+ return static_cast<uint32>(a) |
+ static_cast<uint32>(b) << 8 |
+ static_cast<uint32>(c) << 16 |
+ static_cast<uint32>(d) << 24;
+}
+
+QuicVersion QuicVersionMax() { return kSupportedQuicVersions[0]; }
+
+QuicVersion QuicVersionMin() {
+ return kSupportedQuicVersions[arraysize(kSupportedQuicVersions) - 1];
+}
+
+QuicTag QuicVersionToQuicTag(const QuicVersion version) {
+ switch (version) {
+ case QUIC_VERSION_7:
+ return MakeQuicTag('Q', '0', '0', '7');
+ case QUIC_VERSION_8:
+ return MakeQuicTag('Q', '0', '0', '8');
+ default:
+ // This shold be an ERROR because we should never attempt to convert an
+ // invalid QuicVersion to be written to the wire.
+ LOG(ERROR) << "Unsupported QuicVersion: " << version;
+ return 0;
+ }
+}
+
+QuicVersion QuicTagToQuicVersion(const QuicTag version_tag) {
+ const QuicTag quic_tag_v7 = MakeQuicTag('Q', '0', '0', '7');
+ const QuicTag quic_tag_v8 = MakeQuicTag('Q', '0', '0', '8');
+
+ if (version_tag == quic_tag_v7) {
+ return QUIC_VERSION_7;
+ } else if (version_tag == quic_tag_v8) {
+ return QUIC_VERSION_8;
+ } else {
+ // Reading from the client so this should not be considered an ERROR.
+ DLOG(INFO) << "Unsupported QuicTag version: "
+ << QuicUtils::TagToString(version_tag);
+ return QUIC_VERSION_UNSUPPORTED;
+ }
+}
+
+#define RETURN_STRING_LITERAL(x) \
+case x: \
+return #x
+
+string QuicVersionToString(const QuicVersion version) {
+ switch (version) {
+ RETURN_STRING_LITERAL(QUIC_VERSION_7);
+ RETURN_STRING_LITERAL(QUIC_VERSION_8);
+ default:
+ return "QUIC_VERSION_UNSUPPORTED";
+ }
+}
+
+string QuicVersionArrayToString(const QuicVersion versions[],
+ int num_versions) {
+ string result = "";
+ for (int i = 0; i < num_versions; ++i) {
+ const QuicVersion& version = versions[i];
+ result.append(QuicVersionToString(version));
+ result.append(",");
+ }
+ return result;
+}
+
+ostream& operator<<(ostream& os, const QuicPacketHeader& header) {
+ os << "{ guid: " << header.public_header.guid
+ << ", guid_length:" << header.public_header.guid_length
+ << ", reset_flag: " << header.public_header.reset_flag
+ << ", version_flag: " << header.public_header.version_flag;
+ if (header.public_header.version_flag) {
+ os << " version: ";
+ for (size_t i = 0; i < header.public_header.versions.size(); ++i) {
+ os << header.public_header.versions[0] << " ";
+ }
+ }
+ os << ", fec_flag: " << header.fec_flag
+ << ", entropy_flag: " << header.entropy_flag
+ << ", entropy hash: " << static_cast<int>(header.entropy_hash)
+ << ", sequence_number: " << header.packet_sequence_number
+ << ", is_in_fec_group:" << header.is_in_fec_group
+ << ", fec_group: " << header.fec_group<< "}\n";
+ return os;
+}
+
+// TODO(ianswett): Initializing largest_observed to 0 should not be necessary.
+ReceivedPacketInfo::ReceivedPacketInfo()
+ : largest_observed(0),
+ delta_time_largest_observed(QuicTime::Delta::Infinite()) {
+}
+
+ReceivedPacketInfo::~ReceivedPacketInfo() {}
+
+bool IsAwaitingPacket(const ReceivedPacketInfo& received_info,
+ QuicPacketSequenceNumber sequence_number) {
+ return sequence_number > received_info.largest_observed ||
+ ContainsKey(received_info.missing_packets, sequence_number);
+}
+
+void InsertMissingPacketsBetween(ReceivedPacketInfo* received_info,
+ QuicPacketSequenceNumber lower,
+ QuicPacketSequenceNumber higher) {
+ for (QuicPacketSequenceNumber i = lower; i < higher; ++i) {
+ received_info->missing_packets.insert(i);
+ }
+}
+
+SentPacketInfo::SentPacketInfo() {}
+
+SentPacketInfo::~SentPacketInfo() {}
+
+// Testing convenience method.
+QuicAckFrame::QuicAckFrame(QuicPacketSequenceNumber largest_observed,
+ QuicTime largest_observed_receive_time,
+ QuicPacketSequenceNumber least_unacked) {
+ received_info.largest_observed = largest_observed;
+ received_info.entropy_hash = 0;
+ sent_info.least_unacked = least_unacked;
+ sent_info.entropy_hash = 0;
+}
+
+ostream& operator<<(ostream& os, const SentPacketInfo& sent_info) {
+ os << "entropy_hash: " << static_cast<int>(sent_info.entropy_hash)
+ << " least_unacked: " << sent_info.least_unacked;
+ return os;
+}
+
+ostream& operator<<(ostream& os, const ReceivedPacketInfo& received_info) {
+ os << "entropy_hash: " << static_cast<int>(received_info.entropy_hash)
+ << " largest_observed: " << received_info.largest_observed
+ << " missing_packets: [ ";
+ for (SequenceNumberSet::const_iterator it =
+ received_info.missing_packets.begin();
+ it != received_info.missing_packets.end(); ++it) {
+ os << *it << " ";
+ }
+ os << " ] ";
+ return os;
+}
+
+QuicCongestionFeedbackFrame::QuicCongestionFeedbackFrame() {
+}
+
+QuicCongestionFeedbackFrame::~QuicCongestionFeedbackFrame() {
+}
+
+ostream& operator<<(ostream& os,
+ const QuicCongestionFeedbackFrame& congestion_frame) {
+ os << "type: " << congestion_frame.type;
+ switch (congestion_frame.type) {
+ case kInterArrival: {
+ const CongestionFeedbackMessageInterArrival& inter_arrival =
+ congestion_frame.inter_arrival;
+ os << " accumulated_number_of_lost_packets: "
+ << inter_arrival.accumulated_number_of_lost_packets;
+ os << " received packets: [ ";
+ for (TimeMap::const_iterator it =
+ inter_arrival.received_packet_times.begin();
+ it != inter_arrival.received_packet_times.end(); ++it) {
+ os << it->first << "@" << it->second.ToDebuggingValue() << " ";
+ }
+ os << "]";
+ break;
+ }
+ case kFixRate: {
+ os << " bitrate_in_bytes_per_second: "
+ << congestion_frame.fix_rate.bitrate.ToBytesPerSecond();
+ break;
+ }
+ case kTCP: {
+ const CongestionFeedbackMessageTCP& tcp = congestion_frame.tcp;
+ os << " accumulated_number_of_lost_packets: "
+ << congestion_frame.tcp.accumulated_number_of_lost_packets;
+ os << " receive_window: " << tcp.receive_window;
+ break;
+ }
+ }
+ return os;
+}
+
+ostream& operator<<(ostream& os, const QuicAckFrame& ack_frame) {
+ os << "sent info { " << ack_frame.sent_info << " } "
+ << "received info { " << ack_frame.received_info << " }\n";
+ return os;
+}
+
+CongestionFeedbackMessageFixRate::CongestionFeedbackMessageFixRate()
+ : bitrate(QuicBandwidth::Zero()) {
+}
+
+CongestionFeedbackMessageInterArrival::
+CongestionFeedbackMessageInterArrival() {}
+
+CongestionFeedbackMessageInterArrival::
+~CongestionFeedbackMessageInterArrival() {}
+
+QuicGoAwayFrame::QuicGoAwayFrame(QuicErrorCode error_code,
+ QuicStreamId last_good_stream_id,
+ const string& reason)
+ : error_code(error_code),
+ last_good_stream_id(last_good_stream_id),
+ reason_phrase(reason) {
+ DCHECK_LE(error_code, numeric_limits<uint8>::max());
+}
+
+QuicFecData::QuicFecData() {}
+
+QuicData::~QuicData() {
+ if (owns_buffer_) {
+ delete [] const_cast<char*>(buffer_);
+ }
+}
+
+StringPiece QuicPacket::FecProtectedData() const {
+ const size_t start_of_fec = GetStartOfFecProtectedData(
+ guid_length_, includes_version_, sequence_number_length_);
+ return StringPiece(data() + start_of_fec, length() - start_of_fec);
+}
+
+StringPiece QuicPacket::AssociatedData() const {
+ return StringPiece(
+ data() + kStartOfHashData,
+ GetStartOfEncryptedData(
+ guid_length_, includes_version_, sequence_number_length_) -
+ kStartOfHashData);
+}
+
+StringPiece QuicPacket::BeforePlaintext() const {
+ return StringPiece(data(), GetStartOfEncryptedData(guid_length_,
+ includes_version_,
+ sequence_number_length_));
+}
+
+StringPiece QuicPacket::Plaintext() const {
+ const size_t start_of_encrypted_data =
+ GetStartOfEncryptedData(
+ guid_length_, includes_version_, sequence_number_length_);
+ return StringPiece(data() + start_of_encrypted_data,
+ length() - start_of_encrypted_data);
+}
+
+RetransmittableFrames::RetransmittableFrames()
+ : encryption_level_(NUM_ENCRYPTION_LEVELS) {
+}
+
+RetransmittableFrames::~RetransmittableFrames() {
+ for (QuicFrames::iterator it = frames_.begin(); it != frames_.end(); ++it) {
+ switch (it->type) {
+ case PADDING_FRAME:
+ delete it->padding_frame;
+ break;
+ case STREAM_FRAME:
+ delete it->stream_frame;
+ break;
+ case ACK_FRAME:
+ delete it->ack_frame;
+ break;
+ case CONGESTION_FEEDBACK_FRAME:
+ delete it->congestion_feedback_frame;
+ break;
+ case RST_STREAM_FRAME:
+ delete it->rst_stream_frame;
+ break;
+ case CONNECTION_CLOSE_FRAME:
+ delete it->connection_close_frame;
+ break;
+ case GOAWAY_FRAME:
+ delete it->goaway_frame;
+ break;
+ case NUM_FRAME_TYPES:
+ DCHECK(false) << "Cannot delete type: " << it->type;
+ }
+ }
+ STLDeleteElements(&stream_data_);
+}
+
+const QuicFrame& RetransmittableFrames::AddStreamFrame(
+ QuicStreamFrame* stream_frame) {
+ // Make an owned copy of the StringPiece.
+ string* stream_data = new string(stream_frame->data.data(),
+ stream_frame->data.size());
+ // Ensure the frame's StringPiece points to the owned copy of the data.
+ stream_frame->data = StringPiece(*stream_data);
+ stream_data_.push_back(stream_data);
+ frames_.push_back(QuicFrame(stream_frame));
+ return frames_.back();
+}
+
+const QuicFrame& RetransmittableFrames::AddNonStreamFrame(
+ const QuicFrame& frame) {
+ DCHECK_NE(frame.type, STREAM_FRAME);
+ frames_.push_back(frame);
+ return frames_.back();
+}
+
+void RetransmittableFrames::set_encryption_level(EncryptionLevel level) {
+ encryption_level_ = level;
+}
+
+ostream& operator<<(ostream& os, const QuicEncryptedPacket& s) {
+ os << s.length() << "-byte data";
+ return os;
+}
+
+ostream& operator<<(ostream& os, const QuicConsumedData& s) {
+ os << "bytes_consumed: " << s.bytes_consumed
+ << " fin_consumed: " << s.fin_consumed;
+ return os;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_protocol.h b/chromium/net/quic/quic_protocol.h
new file mode 100644
index 00000000000..737bb16106f
--- /dev/null
+++ b/chromium/net/quic/quic_protocol.h
@@ -0,0 +1,853 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_PROTOCOL_H_
+#define NET_QUIC_QUIC_PROTOCOL_H_
+
+#include <stddef.h>
+#include <limits>
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_bandwidth.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+using ::operator<<;
+
+class QuicPacket;
+struct QuicPacketHeader;
+
+typedef uint64 QuicGuid;
+typedef uint32 QuicStreamId;
+typedef uint64 QuicStreamOffset;
+typedef uint64 QuicPacketSequenceNumber;
+typedef QuicPacketSequenceNumber QuicFecGroupNumber;
+typedef uint64 QuicPublicResetNonceProof;
+typedef uint8 QuicPacketEntropyHash;
+typedef uint32 QuicHeaderId;
+// QuicTag is the type of a tag in the wire protocol.
+typedef uint32 QuicTag;
+typedef std::vector<QuicTag> QuicTagVector;
+
+// TODO(rch): Consider Quic specific names for these constants.
+// Maximum size in bytes of a QUIC packet.
+const QuicByteCount kMaxPacketSize = 1200;
+
+// Maximum number of open streams per connection.
+const size_t kDefaultMaxStreamsPerConnection = 100;
+
+// Number of bytes reserved for public flags in the packet header.
+const size_t kPublicFlagsSize = 1;
+// Number of bytes reserved for version number in the packet header.
+const size_t kQuicVersionSize = 4;
+// Number of bytes reserved for private flags in the packet header.
+const size_t kPrivateFlagsSize = 1;
+// Number of bytes reserved for FEC group in the packet header.
+const size_t kFecGroupSize = 1;
+// Number of bytes reserved for the nonce proof in public reset packet.
+const size_t kPublicResetNonceSize = 8;
+
+// Signifies that the QuicPacket will contain version of the protocol.
+const bool kIncludeVersion = true;
+
+// Index of the first byte in a QUIC packet which is used in hash calculation.
+const size_t kStartOfHashData = 0;
+
+// Limit on the delta between stream IDs.
+const QuicStreamId kMaxStreamIdDelta = 100;
+// Limit on the delta between header IDs.
+const QuicHeaderId kMaxHeaderIdDelta = 100;
+
+// Reserved ID for the crypto stream.
+// TODO(rch): ensure that this is not usable by any other streams.
+const QuicStreamId kCryptoStreamId = 1;
+
+// This is the default network timeout a for connection till the crypto
+// handshake succeeds and the negotiated timeout from the handshake is received.
+const int64 kDefaultInitialTimeoutSecs = 120; // 2 mins.
+const int64 kDefaultTimeoutSecs = 60 * 10; // 10 minutes.
+const int64 kDefaultMaxTimeForCryptoHandshakeSecs = 5; // 5 secs.
+
+enum Retransmission {
+ NOT_RETRANSMISSION,
+ IS_RETRANSMISSION,
+};
+
+enum HasRetransmittableData {
+ NO_RETRANSMITTABLE_DATA,
+ HAS_RETRANSMITTABLE_DATA,
+};
+
+enum IsHandshake {
+ NOT_HANDSHAKE,
+ IS_HANDSHAKE
+};
+
+enum QuicFrameType {
+ PADDING_FRAME = 0,
+ STREAM_FRAME,
+ ACK_FRAME,
+ CONGESTION_FEEDBACK_FRAME,
+ RST_STREAM_FRAME,
+ CONNECTION_CLOSE_FRAME,
+ GOAWAY_FRAME,
+ NUM_FRAME_TYPES
+};
+
+enum QuicGuidLength {
+ PACKET_0BYTE_GUID = 0,
+ PACKET_1BYTE_GUID = 1,
+ PACKET_4BYTE_GUID = 4,
+ PACKET_8BYTE_GUID = 8
+};
+
+enum InFecGroup {
+ NOT_IN_FEC_GROUP,
+ IN_FEC_GROUP,
+};
+
+enum QuicSequenceNumberLength {
+ PACKET_1BYTE_SEQUENCE_NUMBER = 1,
+ PACKET_2BYTE_SEQUENCE_NUMBER = 2,
+ PACKET_4BYTE_SEQUENCE_NUMBER = 4,
+ PACKET_6BYTE_SEQUENCE_NUMBER = 6
+};
+
+// The public flags are specified in one byte.
+enum QuicPacketPublicFlags {
+ PACKET_PUBLIC_FLAGS_NONE = 0,
+
+ // Bit 0: Does the packet header contains version info?
+ PACKET_PUBLIC_FLAGS_VERSION = 1 << 0,
+
+ // Bit 1: Is this packet a public reset packet?
+ PACKET_PUBLIC_FLAGS_RST = 1 << 1,
+
+ // Bits 2 and 3 specify the length of the GUID as follows:
+ // ----00--: 0 bytes
+ // ----01--: 1 byte
+ // ----10--: 4 bytes
+ // ----11--: 8 bytes
+ PACKET_PUBLIC_FLAGS_0BYTE_GUID = 0,
+ PACKET_PUBLIC_FLAGS_1BYTE_GUID = 1 << 2,
+ PACKET_PUBLIC_FLAGS_4BYTE_GUID = 1 << 3,
+ PACKET_PUBLIC_FLAGS_8BYTE_GUID = 1 << 3 | 1 << 2,
+
+ // Bits 4 and 5 describe the packet sequence number length as follows:
+ // --00----: 1 byte
+ // --01----: 2 bytes
+ // --10----: 4 bytes
+ // --11----: 6 bytes
+ PACKET_PUBLIC_FLAGS_1BYTE_SEQUENCE = 0,
+ PACKET_PUBLIC_FLAGS_2BYTE_SEQUENCE = 1 << 4,
+ PACKET_PUBLIC_FLAGS_4BYTE_SEQUENCE = 1 << 5,
+ PACKET_PUBLIC_FLAGS_6BYTE_SEQUENCE = 1 << 5 | 1 << 4,
+
+ // All bits set (bits 6 and 7 are not currently used): 00111111
+ PACKET_PUBLIC_FLAGS_MAX = (1 << 6) - 1
+};
+
+// The private flags are specified in one byte.
+enum QuicPacketPrivateFlags {
+ PACKET_PRIVATE_FLAGS_NONE = 0,
+
+ // Bit 0: Does this packet contain an entropy bit?
+ PACKET_PRIVATE_FLAGS_ENTROPY = 1 << 0,
+
+ // Bit 1: Payload is part of an FEC group?
+ PACKET_PRIVATE_FLAGS_FEC_GROUP = 1 << 1,
+
+ // Bit 2: Payload is FEC as opposed to frames?
+ PACKET_PRIVATE_FLAGS_FEC = 1 << 2,
+
+ // All bits set (bits 3-7 are not currently used): 00000111
+ PACKET_PRIVATE_FLAGS_MAX = (1 << 3) - 1
+};
+
+// The available versions of QUIC. Guaranteed that the integer value of the enum
+// will match the version number.
+// When adding a new version to this enum you should add it to
+// kSupportedQuicVersions (if appropriate), and also add a new case to the
+// helper methods QuicVersionToQuicTag, QuicTagToQuicVersion, and
+// QuicVersionToString.
+enum QuicVersion {
+ // Special case to indicate unknown/unsupported QUIC version.
+ QUIC_VERSION_UNSUPPORTED = 0,
+
+ QUIC_VERSION_7 = 7,
+ QUIC_VERSION_8 = 8, // Current version.
+};
+
+// This vector contains QUIC versions which we currently support.
+// This should be ordered such that the highest supported version is the first
+// element, with subsequent elements in descending order (versions can be
+// skipped as necessary).
+static const QuicVersion kSupportedQuicVersions[] =
+ {QUIC_VERSION_8, QUIC_VERSION_7};
+
+typedef std::vector<QuicVersion> QuicVersionVector;
+
+// Upper limit on versions we support.
+NET_EXPORT_PRIVATE QuicVersion QuicVersionMax();
+
+// Lower limit on versions we support.
+NET_EXPORT_PRIVATE QuicVersion QuicVersionMin();
+
+// QuicTag is written to and read from the wire, but we prefer to use
+// the more readable QuicVersion at other levels.
+// Helper function which translates from a QuicVersion to a QuicTag. Returns 0
+// if QuicVersion is unsupported.
+NET_EXPORT_PRIVATE QuicTag QuicVersionToQuicTag(const QuicVersion version);
+
+// Returns appropriate QuicVersion from a QuicTag.
+// Returns QUIC_VERSION_UNSUPPORTED if version_tag cannot be understood.
+NET_EXPORT_PRIVATE QuicVersion QuicTagToQuicVersion(const QuicTag version_tag);
+
+// Helper function which translates from a QuicVersion to a string.
+// Returns strings corresponding to enum names (e.g. QUIC_VERSION_6).
+NET_EXPORT_PRIVATE std::string QuicVersionToString(const QuicVersion version);
+
+// Returns comma separated list of string representations of QuicVersion enum
+// values in the supplied QuicVersionArray.
+NET_EXPORT_PRIVATE std::string QuicVersionArrayToString(
+ const QuicVersion versions[], int num_versions);
+
+// Version and Crypto tags are written to the wire with a big-endian
+// representation of the name of the tag. For example
+// the client hello tag (CHLO) will be written as the
+// following 4 bytes: 'C' 'H' 'L' 'O'. Since it is
+// stored in memory as a little endian uint32, we need
+// to reverse the order of the bytes.
+
+// MakeQuicTag returns a value given the four bytes. For example:
+// MakeQuicTag('C', 'H', 'L', 'O');
+NET_EXPORT_PRIVATE QuicTag MakeQuicTag(char a, char b, char c, char d);
+
+// Size in bytes of the data or fec packet header.
+NET_EXPORT_PRIVATE size_t GetPacketHeaderSize(QuicPacketHeader header);
+
+NET_EXPORT_PRIVATE size_t GetPacketHeaderSize(
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length,
+ InFecGroup is_in_fec_group);
+
+// Size in bytes of the public reset packet.
+NET_EXPORT_PRIVATE size_t GetPublicResetPacketSize();
+
+// Index of the first byte in a QUIC packet of FEC protected data.
+NET_EXPORT_PRIVATE size_t GetStartOfFecProtectedData(
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length);
+// Index of the first byte in a QUIC packet of encrypted data.
+NET_EXPORT_PRIVATE size_t GetStartOfEncryptedData(
+ QuicGuidLength guid_length,
+ bool include_version,
+ QuicSequenceNumberLength sequence_number_length);
+
+enum QuicRstStreamErrorCode {
+ QUIC_STREAM_NO_ERROR = 0,
+
+ // There was some server error which halted stream processing.
+ QUIC_SERVER_ERROR_PROCESSING_STREAM,
+ // We got two fin or reset offsets which did not match.
+ QUIC_MULTIPLE_TERMINATION_OFFSETS,
+ // We got bad payload and can not respond to it at the protocol level.
+ QUIC_BAD_APPLICATION_PAYLOAD,
+ // Stream closed due to connection error. No reset frame is sent when this
+ // happens.
+ QUIC_STREAM_CONNECTION_ERROR,
+ // GoAway frame sent. No more stream can be created.
+ QUIC_STREAM_PEER_GOING_AWAY,
+
+ // No error. Used as bound while iterating.
+ QUIC_STREAM_LAST_ERROR,
+};
+
+enum QuicErrorCode {
+ QUIC_NO_ERROR = 0,
+
+ // Connection has reached an invalid state.
+ QUIC_INTERNAL_ERROR,
+ // There were data frames after the a fin or reset.
+ QUIC_STREAM_DATA_AFTER_TERMINATION,
+ // Control frame is malformed.
+ QUIC_INVALID_PACKET_HEADER,
+ // Frame data is malformed.
+ QUIC_INVALID_FRAME_DATA,
+ // FEC data is malformed.
+ QUIC_INVALID_FEC_DATA,
+ // Stream rst data is malformed
+ QUIC_INVALID_RST_STREAM_DATA,
+ // Connection close data is malformed.
+ QUIC_INVALID_CONNECTION_CLOSE_DATA,
+ // GoAway data is malformed.
+ QUIC_INVALID_GOAWAY_DATA,
+ // Ack data is malformed.
+ QUIC_INVALID_ACK_DATA,
+ // Version negotiation packet is malformed.
+ QUIC_INVALID_VERSION_NEGOTIATION_PACKET,
+ // Public RST packet is malformed.
+ QUIC_INVALID_PUBLIC_RST_PACKET,
+ // There was an error decrypting.
+ QUIC_DECRYPTION_FAILURE,
+ // There was an error encrypting.
+ QUIC_ENCRYPTION_FAILURE,
+ // The packet exceeded kMaxPacketSize.
+ QUIC_PACKET_TOO_LARGE,
+ // Data was sent for a stream which did not exist.
+ QUIC_PACKET_FOR_NONEXISTENT_STREAM,
+ // The peer is going away. May be a client or server.
+ QUIC_PEER_GOING_AWAY,
+ // A stream ID was invalid.
+ QUIC_INVALID_STREAM_ID,
+ // Too many streams already open.
+ QUIC_TOO_MANY_OPEN_STREAMS,
+ // Received public reset for this connection.
+ QUIC_PUBLIC_RESET,
+ // Invalid protocol version.
+ QUIC_INVALID_VERSION,
+ // Stream reset before headers decompressed.
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED,
+ // The Header ID for a stream was too far from the previous.
+ QUIC_INVALID_HEADER_ID,
+ // Negotiable parameter received during handshake had invalid value.
+ QUIC_INVALID_NEGOTIATED_VALUE,
+ // There was an error decompressing data.
+ QUIC_DECOMPRESSION_FAILURE,
+ // We hit our prenegotiated (or default) timeout
+ QUIC_CONNECTION_TIMED_OUT,
+ // There was an error encountered migrating addresses
+ QUIC_ERROR_MIGRATING_ADDRESS,
+ // There was an error while writing the packet.
+ QUIC_PACKET_WRITE_ERROR,
+
+
+ // Crypto errors.
+
+ // Hanshake failed.
+ QUIC_HANDSHAKE_FAILED,
+ // Handshake message contained out of order tags.
+ QUIC_CRYPTO_TAGS_OUT_OF_ORDER,
+ // Handshake message contained too many entries.
+ QUIC_CRYPTO_TOO_MANY_ENTRIES,
+ // Handshake message contained an invalid value length.
+ QUIC_CRYPTO_INVALID_VALUE_LENGTH,
+ // A crypto message was received after the handshake was complete.
+ QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE,
+ // A crypto message was received with an illegal message tag.
+ QUIC_INVALID_CRYPTO_MESSAGE_TYPE,
+ // A crypto message was received with an illegal parameter.
+ QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER,
+ // A crypto message was received with a mandatory parameter missing.
+ QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND,
+ // A crypto message was received with a parameter that has no overlap
+ // with the local parameter.
+ QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP,
+ // A crypto message was received that contained a parameter with too few
+ // values.
+ QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND,
+ // An internal error occured in crypto processing.
+ QUIC_CRYPTO_INTERNAL_ERROR,
+ // A crypto handshake message specified an unsupported version.
+ QUIC_CRYPTO_VERSION_NOT_SUPPORTED,
+ // There was no intersection between the crypto primitives supported by the
+ // peer and ourselves.
+ QUIC_CRYPTO_NO_SUPPORT,
+ // The server rejected our client hello messages too many times.
+ QUIC_CRYPTO_TOO_MANY_REJECTS,
+ // The client rejected the server's certificate chain or signature.
+ QUIC_PROOF_INVALID,
+ // A crypto message was received with a duplicate tag.
+ QUIC_CRYPTO_DUPLICATE_TAG,
+ // A crypto message was received with the wrong encryption level (i.e. it
+ // should have been encrypted but was not.)
+ QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT,
+ // The server config for a server has expired.
+ QUIC_CRYPTO_SERVER_CONFIG_EXPIRED,
+
+ // No error. Used as bound while iterating.
+ QUIC_LAST_ERROR,
+};
+
+struct NET_EXPORT_PRIVATE QuicPacketPublicHeader {
+ QuicPacketPublicHeader();
+ explicit QuicPacketPublicHeader(const QuicPacketPublicHeader& other);
+ ~QuicPacketPublicHeader();
+
+ QuicPacketPublicHeader& operator=(const QuicPacketPublicHeader& other);
+
+ // Universal header. All QuicPacket headers will have a guid and public flags.
+ QuicGuid guid;
+ QuicGuidLength guid_length;
+ bool reset_flag;
+ bool version_flag;
+ QuicSequenceNumberLength sequence_number_length;
+ QuicVersionVector versions;
+};
+
+// Header for Data or FEC packets.
+struct NET_EXPORT_PRIVATE QuicPacketHeader {
+ QuicPacketHeader();
+ explicit QuicPacketHeader(const QuicPacketPublicHeader& header);
+
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const QuicPacketHeader& s);
+
+ QuicPacketPublicHeader public_header;
+ bool fec_flag;
+ bool entropy_flag;
+ QuicPacketEntropyHash entropy_hash;
+ QuicPacketSequenceNumber packet_sequence_number;
+ InFecGroup is_in_fec_group;
+ QuicFecGroupNumber fec_group;
+};
+
+struct NET_EXPORT_PRIVATE QuicPublicResetPacket {
+ QuicPublicResetPacket() {}
+ explicit QuicPublicResetPacket(const QuicPacketPublicHeader& header)
+ : public_header(header) {}
+ QuicPacketPublicHeader public_header;
+ QuicPacketSequenceNumber rejected_sequence_number;
+ QuicPublicResetNonceProof nonce_proof;
+};
+
+enum QuicVersionNegotiationState {
+ START_NEGOTIATION = 0,
+ // Server-side this implies we've sent a version negotiation packet and are
+ // waiting on the client to select a compatible version. Client-side this
+ // implies we've gotten a version negotiation packet, are retransmitting the
+ // initial packets with a supported version and are waiting for our first
+ // packet from the server.
+ NEGOTIATION_IN_PROGRESS,
+ // This indicates this endpoint has received a packet from the peer with a
+ // version this endpoint supports. Version negotiation is complete, and the
+ // version number will no longer be sent with future packets.
+ NEGOTIATED_VERSION
+};
+
+typedef QuicPacketPublicHeader QuicVersionNegotiationPacket;
+
+// A padding frame contains no payload.
+struct NET_EXPORT_PRIVATE QuicPaddingFrame {
+};
+
+struct NET_EXPORT_PRIVATE QuicStreamFrame {
+ QuicStreamFrame();
+ QuicStreamFrame(QuicStreamId stream_id,
+ bool fin,
+ QuicStreamOffset offset,
+ base::StringPiece data);
+
+ QuicStreamId stream_id;
+ bool fin;
+ QuicStreamOffset offset; // Location of this data in the stream.
+ base::StringPiece data;
+};
+
+// TODO(ianswett): Re-evaluate the trade-offs of hash_set vs set when framing
+// is finalized.
+typedef std::set<QuicPacketSequenceNumber> SequenceNumberSet;
+// TODO(pwestin): Add a way to enforce the max size of this map.
+typedef std::map<QuicPacketSequenceNumber, QuicTime> TimeMap;
+
+struct NET_EXPORT_PRIVATE ReceivedPacketInfo {
+ ReceivedPacketInfo();
+ ~ReceivedPacketInfo();
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const ReceivedPacketInfo& s);
+
+ // Entropy hash of all packets up to largest observed not including missing
+ // packets.
+ QuicPacketEntropyHash entropy_hash;
+
+ // The highest packet sequence number we've observed from the peer.
+ //
+ // In general, this should be the largest packet number we've received. In
+ // the case of truncated acks, we may have to advertise a lower "upper bound"
+ // than largest received, to avoid implicitly acking missing packets that
+ // don't fit in the missing packet list due to size limitations. In this
+ // case, largest_observed may be a packet which is also in the missing packets
+ // list.
+ QuicPacketSequenceNumber largest_observed;
+
+ // Time elapsed since largest_observed was received until this Ack frame was
+ // sent.
+ QuicTime::Delta delta_time_largest_observed;
+
+ // TODO(satyamshekhar): Can be optimized using an interval set like data
+ // structure.
+ // The set of packets which we're expecting and have not received.
+ SequenceNumberSet missing_packets;
+};
+
+// True if the sequence number is greater than largest_observed or is listed
+// as missing.
+// Always returns false for sequence numbers less than least_unacked.
+bool NET_EXPORT_PRIVATE IsAwaitingPacket(
+ const ReceivedPacketInfo& received_info,
+ QuicPacketSequenceNumber sequence_number);
+
+// Inserts missing packets between [lower, higher).
+void NET_EXPORT_PRIVATE InsertMissingPacketsBetween(
+ ReceivedPacketInfo* received_info,
+ QuicPacketSequenceNumber lower,
+ QuicPacketSequenceNumber higher);
+
+struct NET_EXPORT_PRIVATE SentPacketInfo {
+ SentPacketInfo();
+ ~SentPacketInfo();
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const SentPacketInfo& s);
+
+ // Entropy hash of all packets up to, but not including, the least unacked
+ // packet.
+ QuicPacketEntropyHash entropy_hash;
+ // The lowest packet we've sent which is unacked, and we expect an ack for.
+ QuicPacketSequenceNumber least_unacked;
+};
+
+struct NET_EXPORT_PRIVATE QuicAckFrame {
+ QuicAckFrame() {}
+ // Testing convenience method to construct a QuicAckFrame with all packets
+ // from least_unacked to largest_observed acked.
+ QuicAckFrame(QuicPacketSequenceNumber largest_observed,
+ QuicTime largest_observed_receive_time,
+ QuicPacketSequenceNumber least_unacked);
+
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const QuicAckFrame& s);
+
+ SentPacketInfo sent_info;
+ ReceivedPacketInfo received_info;
+};
+
+// Defines for all types of congestion feedback that will be negotiated in QUIC,
+// kTCP MUST be supported by all QUIC implementations to guarantee 100%
+// compatibility.
+enum CongestionFeedbackType {
+ kTCP, // Used to mimic TCP.
+ kInterArrival, // Use additional inter arrival information.
+ kFixRate, // Provided for testing.
+};
+
+struct NET_EXPORT_PRIVATE CongestionFeedbackMessageTCP {
+ uint16 accumulated_number_of_lost_packets;
+ QuicByteCount receive_window;
+};
+
+struct NET_EXPORT_PRIVATE CongestionFeedbackMessageInterArrival {
+ CongestionFeedbackMessageInterArrival();
+ ~CongestionFeedbackMessageInterArrival();
+ uint16 accumulated_number_of_lost_packets;
+ // The set of received packets since the last feedback was sent, along with
+ // their arrival times.
+ TimeMap received_packet_times;
+};
+
+struct NET_EXPORT_PRIVATE CongestionFeedbackMessageFixRate {
+ CongestionFeedbackMessageFixRate();
+ QuicBandwidth bitrate;
+};
+
+struct NET_EXPORT_PRIVATE QuicCongestionFeedbackFrame {
+ QuicCongestionFeedbackFrame();
+ ~QuicCongestionFeedbackFrame();
+
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const QuicCongestionFeedbackFrame& c);
+
+ CongestionFeedbackType type;
+ // This should really be a union, but since the inter arrival struct
+ // is non-trivial, C++ prohibits it.
+ CongestionFeedbackMessageTCP tcp;
+ CongestionFeedbackMessageInterArrival inter_arrival;
+ CongestionFeedbackMessageFixRate fix_rate;
+};
+
+struct NET_EXPORT_PRIVATE QuicRstStreamFrame {
+ QuicRstStreamFrame() {}
+ QuicRstStreamFrame(QuicStreamId stream_id, QuicRstStreamErrorCode error_code)
+ : stream_id(stream_id), error_code(error_code) {
+ DCHECK_LE(error_code, std::numeric_limits<uint8>::max());
+ }
+
+ QuicStreamId stream_id;
+ QuicRstStreamErrorCode error_code;
+ std::string error_details;
+};
+
+struct NET_EXPORT_PRIVATE QuicConnectionCloseFrame {
+ QuicErrorCode error_code;
+ std::string error_details;
+ QuicAckFrame ack_frame;
+};
+
+struct NET_EXPORT_PRIVATE QuicGoAwayFrame {
+ QuicGoAwayFrame() {}
+ QuicGoAwayFrame(QuicErrorCode error_code,
+ QuicStreamId last_good_stream_id,
+ const std::string& reason);
+
+ QuicErrorCode error_code;
+ QuicStreamId last_good_stream_id;
+ std::string reason_phrase;
+};
+
+// EncryptionLevel enumerates the stages of encryption that a QUIC connection
+// progresses through. When retransmitting a packet, the encryption level needs
+// to be specified so that it is retransmitted at a level which the peer can
+// understand.
+enum EncryptionLevel {
+ ENCRYPTION_NONE = 0,
+ ENCRYPTION_INITIAL = 1,
+ ENCRYPTION_FORWARD_SECURE = 2,
+
+ NUM_ENCRYPTION_LEVELS,
+};
+
+struct NET_EXPORT_PRIVATE QuicFrame {
+ QuicFrame() {}
+ explicit QuicFrame(QuicPaddingFrame* padding_frame)
+ : type(PADDING_FRAME),
+ padding_frame(padding_frame) {
+ }
+ explicit QuicFrame(QuicStreamFrame* stream_frame)
+ : type(STREAM_FRAME),
+ stream_frame(stream_frame) {
+ }
+ explicit QuicFrame(QuicAckFrame* frame)
+ : type(ACK_FRAME),
+ ack_frame(frame) {
+ }
+ explicit QuicFrame(QuicCongestionFeedbackFrame* frame)
+ : type(CONGESTION_FEEDBACK_FRAME),
+ congestion_feedback_frame(frame) {
+ }
+ explicit QuicFrame(QuicRstStreamFrame* frame)
+ : type(RST_STREAM_FRAME),
+ rst_stream_frame(frame) {
+ }
+ explicit QuicFrame(QuicConnectionCloseFrame* frame)
+ : type(CONNECTION_CLOSE_FRAME),
+ connection_close_frame(frame) {
+ }
+ explicit QuicFrame(QuicGoAwayFrame* frame)
+ : type(GOAWAY_FRAME),
+ goaway_frame(frame) {
+ }
+
+ QuicFrameType type;
+ union {
+ QuicPaddingFrame* padding_frame;
+ QuicStreamFrame* stream_frame;
+ QuicAckFrame* ack_frame;
+ QuicCongestionFeedbackFrame* congestion_feedback_frame;
+ QuicRstStreamFrame* rst_stream_frame;
+ QuicConnectionCloseFrame* connection_close_frame;
+ QuicGoAwayFrame* goaway_frame;
+ };
+};
+
+typedef std::vector<QuicFrame> QuicFrames;
+
+struct NET_EXPORT_PRIVATE QuicFecData {
+ QuicFecData();
+
+ // The FEC group number is also the sequence number of the first
+ // FEC protected packet. The last protected packet's sequence number will
+ // be one less than the sequence number of the FEC packet.
+ QuicFecGroupNumber fec_group;
+ base::StringPiece redundancy;
+};
+
+struct NET_EXPORT_PRIVATE QuicPacketData {
+ std::string data;
+};
+
+class NET_EXPORT_PRIVATE QuicData {
+ public:
+ QuicData(const char* buffer, size_t length)
+ : buffer_(buffer),
+ length_(length),
+ owns_buffer_(false) {}
+
+ QuicData(char* buffer, size_t length, bool owns_buffer)
+ : buffer_(buffer),
+ length_(length),
+ owns_buffer_(owns_buffer) {}
+
+ virtual ~QuicData();
+
+ base::StringPiece AsStringPiece() const {
+ return base::StringPiece(data(), length());
+ }
+
+ const char* data() const { return buffer_; }
+ size_t length() const { return length_; }
+
+ private:
+ const char* buffer_;
+ size_t length_;
+ bool owns_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicData);
+};
+
+class NET_EXPORT_PRIVATE QuicPacket : public QuicData {
+ public:
+ static QuicPacket* NewDataPacket(
+ char* buffer,
+ size_t length,
+ bool owns_buffer,
+ QuicGuidLength guid_length,
+ bool includes_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return new QuicPacket(buffer, length, owns_buffer, guid_length,
+ includes_version, sequence_number_length, false);
+ }
+
+ static QuicPacket* NewFecPacket(
+ char* buffer,
+ size_t length,
+ bool owns_buffer,
+ QuicGuidLength guid_length,
+ bool includes_version,
+ QuicSequenceNumberLength sequence_number_length) {
+ return new QuicPacket(buffer, length, owns_buffer, guid_length,
+ includes_version, sequence_number_length, true);
+ }
+
+ base::StringPiece FecProtectedData() const;
+ base::StringPiece AssociatedData() const;
+ base::StringPiece BeforePlaintext() const;
+ base::StringPiece Plaintext() const;
+
+ bool is_fec_packet() const { return is_fec_packet_; }
+
+ bool includes_version() const { return includes_version_; }
+
+ char* mutable_data() { return buffer_; }
+
+ private:
+ QuicPacket(char* buffer,
+ size_t length,
+ bool owns_buffer,
+ QuicGuidLength guid_length,
+ bool includes_version,
+ QuicSequenceNumberLength sequence_number_length,
+ bool is_fec_packet)
+ : QuicData(buffer, length, owns_buffer),
+ buffer_(buffer),
+ is_fec_packet_(is_fec_packet),
+ guid_length_(guid_length),
+ includes_version_(includes_version),
+ sequence_number_length_(sequence_number_length) {}
+
+ char* buffer_;
+ const bool is_fec_packet_;
+ const QuicGuidLength guid_length_;
+ const bool includes_version_;
+ const QuicSequenceNumberLength sequence_number_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicPacket);
+};
+
+class NET_EXPORT_PRIVATE QuicEncryptedPacket : public QuicData {
+ public:
+ QuicEncryptedPacket(const char* buffer, size_t length)
+ : QuicData(buffer, length) {}
+
+ QuicEncryptedPacket(char* buffer, size_t length, bool owns_buffer)
+ : QuicData(buffer, length, owns_buffer) {}
+
+ // By default, gtest prints the raw bytes of an object. The bool data
+ // member (in the base class QuicData) causes this object to have padding
+ // bytes, which causes the default gtest object printer to read
+ // uninitialize memory. So we need to teach gtest how to print this object.
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const QuicEncryptedPacket& s);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicEncryptedPacket);
+};
+
+class NET_EXPORT_PRIVATE RetransmittableFrames {
+ public:
+ RetransmittableFrames();
+ ~RetransmittableFrames();
+
+ // Allocates a local copy of the referenced StringPiece has QuicStreamFrame
+ // use it.
+ // Takes ownership of |stream_frame|.
+ const QuicFrame& AddStreamFrame(QuicStreamFrame* stream_frame);
+ // Takes ownership of the frame inside |frame|.
+ const QuicFrame& AddNonStreamFrame(const QuicFrame& frame);
+ const QuicFrames& frames() const { return frames_; }
+
+ void set_encryption_level(EncryptionLevel level);
+ EncryptionLevel encryption_level() const {
+ return encryption_level_;
+ }
+
+ private:
+ QuicFrames frames_;
+ EncryptionLevel encryption_level_;
+ // Data referenced by the StringPiece of a QuicStreamFrame.
+ std::vector<std::string*> stream_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(RetransmittableFrames);
+};
+
+struct NET_EXPORT_PRIVATE SerializedPacket {
+ SerializedPacket(QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ QuicPacketEntropyHash entropy_hash,
+ RetransmittableFrames* retransmittable_frames)
+ : sequence_number(sequence_number),
+ packet(packet),
+ entropy_hash(entropy_hash),
+ retransmittable_frames(retransmittable_frames) {}
+
+ QuicPacketSequenceNumber sequence_number;
+ QuicPacket* packet;
+ QuicPacketEntropyHash entropy_hash;
+ RetransmittableFrames* retransmittable_frames;
+};
+
+// A struct for functions which consume data payloads and fins.
+// The first member of the pair indicates bytes consumed.
+// The second member of the pair indicates if an incoming fin was consumed.
+struct QuicConsumedData {
+ QuicConsumedData(size_t bytes_consumed, bool fin_consumed)
+ : bytes_consumed(bytes_consumed),
+ fin_consumed(fin_consumed) {}
+
+ // By default, gtest prints the raw bytes of an object. The bool data
+ // member causes this object to have padding bytes, which causes the
+ // default gtest object printer to read uninitialize memory. So we need
+ // to teach gtest how to print this object.
+ NET_EXPORT_PRIVATE friend std::ostream& operator<<(
+ std::ostream& os, const QuicConsumedData& s);
+
+ size_t bytes_consumed;
+ bool fin_consumed;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_PROTOCOL_H_
diff --git a/chromium/net/quic/quic_protocol_test.cc b/chromium/net/quic/quic_protocol_test.cc
new file mode 100644
index 00000000000..b073d859f5d
--- /dev/null
+++ b/chromium/net/quic/quic_protocol_test.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_protocol.h"
+
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+namespace {
+
+TEST(QuicProtocolTest, MakeQuicTag) {
+ QuicTag tag = MakeQuicTag('A', 'B', 'C', 'D');
+ char bytes[4];
+ memcpy(bytes, &tag, 4);
+ EXPECT_EQ('A', bytes[0]);
+ EXPECT_EQ('B', bytes[1]);
+ EXPECT_EQ('C', bytes[2]);
+ EXPECT_EQ('D', bytes[3]);
+}
+
+TEST(QuicProtocolTest, IsAawaitingPacket) {
+ ReceivedPacketInfo received_info;
+ received_info.largest_observed = 10u;
+ EXPECT_TRUE(IsAwaitingPacket(received_info, 11u));
+ EXPECT_FALSE(IsAwaitingPacket(received_info, 1u));
+
+ received_info.missing_packets.insert(10);
+ EXPECT_TRUE(IsAwaitingPacket(received_info, 10u));
+}
+
+TEST(QuicProtocolTest, InsertMissingPacketsBetween) {
+ ReceivedPacketInfo received_info;
+ InsertMissingPacketsBetween(&received_info, 4u, 10u);
+ EXPECT_EQ(6u, received_info.missing_packets.size());
+
+ QuicPacketSequenceNumber i = 4;
+ for (SequenceNumberSet::iterator it = received_info.missing_packets.begin();
+ it != received_info.missing_packets.end(); ++it, ++i) {
+ EXPECT_EQ(i, *it);
+ }
+}
+
+TEST(QuicProtocolTest, QuicVersionToQuicTag) {
+ // If you add a new version to the QuicVersion enum you will need to add a new
+ // case to QuicVersionToQuicTag, otherwise this test will fail.
+
+ // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+ // Any logs would indicate an unsupported version which we don't expect.
+ ScopedMockLog log(kDoNotCaptureLogsYet);
+ EXPECT_CALL(log, Log(_, _, _)).Times(0);
+ log.StartCapturingLogs();
+#endif
+
+ // Explicitly test a specific version.
+ EXPECT_EQ(MakeQuicTag('Q', '0', '0', '7'),
+ QuicVersionToQuicTag(QUIC_VERSION_7));
+
+ // Loop over all supported versions and make sure that we never hit the
+ // default case (i.e. all supported versions should be successfully converted
+ // to valid QuicTags).
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ const QuicVersion& version = kSupportedQuicVersions[i];
+ EXPECT_LT(0u, QuicVersionToQuicTag(version));
+ }
+}
+
+TEST(QuicProtocolTest, QuicVersionToQuicTagUnsupported) {
+ // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+ // TODO(rjshade): Change to DFATAL once we actually support multiple versions,
+ // and QuicConnectionTest::SendVersionNegotiationPacket can be changed to use
+ // mis-matched versions rather than relying on QUIC_VERSION_UNSUPPORTED.
+ ScopedMockLog log(kDoNotCaptureLogsYet);
+ EXPECT_CALL(log, Log(ERROR, _, "Unsupported QuicVersion: 0")).Times(1);
+ log.StartCapturingLogs();
+#endif
+
+ EXPECT_EQ(0u, QuicVersionToQuicTag(QUIC_VERSION_UNSUPPORTED));
+}
+
+TEST(QuicProtocolTest, QuicTagToQuicVersion) {
+ // If you add a new version to the QuicVersion enum you will need to add a new
+ // case to QuicTagToQuicVersion, otherwise this test will fail.
+
+ // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+ // Any logs would indicate an unsupported version which we don't expect.
+ ScopedMockLog log(kDoNotCaptureLogsYet);
+ EXPECT_CALL(log, Log(_, _, _)).Times(0);
+ log.StartCapturingLogs();
+#endif
+
+ // Explicitly test specific versions.
+ EXPECT_EQ(QUIC_VERSION_7,
+ QuicTagToQuicVersion(MakeQuicTag('Q', '0', '0', '7')));
+
+ for (size_t i = 0; i < arraysize(kSupportedQuicVersions); ++i) {
+ const QuicVersion& version = kSupportedQuicVersions[i];
+
+ // Get the tag from the version (we can loop over QuicVersions easily).
+ QuicTag tag = QuicVersionToQuicTag(version);
+ EXPECT_LT(0u, tag);
+
+ // Now try converting back.
+ QuicVersion tag_to_quic_version = QuicTagToQuicVersion(tag);
+ EXPECT_EQ(version, tag_to_quic_version);
+ EXPECT_NE(QUIC_VERSION_UNSUPPORTED, tag_to_quic_version);
+ }
+}
+
+TEST(QuicProtocolTest, QuicTagToQuicVersionUnsupported) {
+ // TODO(rtenneti): Enable checking of Log(ERROR) messages.
+#if 0
+ ScopedMockLog log(kDoNotCaptureLogsYet);
+#ifndef NDEBUG
+ EXPECT_CALL(log, Log(INFO, _, "Unsupported QuicTag version: FAKE")).Times(1);
+#endif
+ log.StartCapturingLogs();
+#endif
+
+ EXPECT_EQ(QUIC_VERSION_UNSUPPORTED,
+ QuicTagToQuicVersion(MakeQuicTag('F', 'A', 'K', 'E')));
+}
+
+TEST(QuicProtocolTest, QuicVersionToString) {
+ EXPECT_EQ("QUIC_VERSION_7",
+ QuicVersionToString(QUIC_VERSION_7));
+ EXPECT_EQ("QUIC_VERSION_UNSUPPORTED",
+ QuicVersionToString(QUIC_VERSION_UNSUPPORTED));
+
+ QuicVersion single_version[] = {QUIC_VERSION_7};
+ EXPECT_EQ("QUIC_VERSION_7,", QuicVersionArrayToString(single_version,
+ arraysize(single_version)));
+ QuicVersion multiple_versions[] = {QUIC_VERSION_8, QUIC_VERSION_7};
+ EXPECT_EQ("QUIC_VERSION_8,QUIC_VERSION_7,",
+ QuicVersionArrayToString(multiple_versions,
+ arraysize(multiple_versions)));
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_received_packet_manager.cc b/chromium/net/quic/quic_received_packet_manager.cc
new file mode 100644
index 00000000000..9a136db5202
--- /dev/null
+++ b/chromium/net/quic/quic_received_packet_manager.cc
@@ -0,0 +1,188 @@
+// Copyright 2013 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/quic/quic_received_packet_manager.h"
+
+#include "base/logging.h"
+#include "net/base/linked_hash_map.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+
+namespace net {
+
+QuicReceivedPacketManager::QuicReceivedPacketManager()
+ : packets_entropy_hash_(0),
+ largest_sequence_number_(0),
+ peer_largest_observed_packet_(0),
+ least_packet_awaited_by_peer_(1),
+ peer_least_packet_awaiting_ack_(0),
+ time_largest_observed_(QuicTime::Zero()) {
+ received_info_.largest_observed = 0;
+ received_info_.entropy_hash = 0;
+}
+
+QuicReceivedPacketManager::~QuicReceivedPacketManager() {}
+
+void QuicReceivedPacketManager::RecordPacketReceived(
+ const QuicPacketHeader& header, QuicTime receipt_time) {
+ QuicPacketSequenceNumber sequence_number = header.packet_sequence_number;
+ DCHECK(IsAwaitingPacket(sequence_number));
+
+ InsertMissingPacketsBetween(
+ &received_info_,
+ max(received_info_.largest_observed + 1, peer_least_packet_awaiting_ack_),
+ header.packet_sequence_number);
+
+ if (received_info_.largest_observed > header.packet_sequence_number) {
+ // We've gotten one of the out of order packets - remove it from our
+ // "missing packets" list.
+ DVLOG(1) << "Removing " << sequence_number << " from missing list";
+ received_info_.missing_packets.erase(sequence_number);
+ }
+ if (header.packet_sequence_number > received_info_.largest_observed) {
+ received_info_.largest_observed = header.packet_sequence_number;
+ time_largest_observed_ = receipt_time;
+ }
+ RecordPacketEntropyHash(sequence_number, header.entropy_hash);
+}
+
+bool QuicReceivedPacketManager::IsAwaitingPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ return ::net::IsAwaitingPacket(received_info_, sequence_number);
+}
+
+void QuicReceivedPacketManager::UpdateReceivedPacketInfo(
+ ReceivedPacketInfo* received_info, QuicTime approximate_now) {
+ *received_info = received_info_;
+ received_info->entropy_hash = EntropyHash(received_info_.largest_observed);
+ if (time_largest_observed_ == QuicTime::Zero()) {
+ // We have not received any new higher sequence numbers since we sent our
+ // last ACK.
+ received_info->delta_time_largest_observed = QuicTime::Delta::Infinite();
+ } else {
+ received_info->delta_time_largest_observed =
+ approximate_now.Subtract(time_largest_observed_);
+
+ time_largest_observed_ = QuicTime::Zero();
+ }
+}
+
+void QuicReceivedPacketManager::RecordPacketEntropyHash(
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacketEntropyHash entropy_hash) {
+ if (sequence_number < largest_sequence_number_) {
+ DLOG(INFO) << "Ignoring received packet entropy for sequence_number:"
+ << sequence_number << " less than largest_peer_sequence_number:"
+ << largest_sequence_number_;
+ return;
+ }
+ packets_entropy_.insert(make_pair(sequence_number, entropy_hash));
+ packets_entropy_hash_ ^= entropy_hash;
+ DVLOG(2) << "setting cumulative received entropy hash to: "
+ << static_cast<int>(packets_entropy_hash_)
+ << " updated with sequence number " << sequence_number
+ << " entropy hash: " << static_cast<int>(entropy_hash);
+}
+
+QuicPacketEntropyHash QuicReceivedPacketManager::EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const {
+ DCHECK_LE(sequence_number, received_info_.largest_observed);
+ DCHECK_GE(sequence_number, largest_sequence_number_);
+ if (sequence_number == received_info_.largest_observed) {
+ return packets_entropy_hash_;
+ }
+
+ ReceivedEntropyMap::const_iterator it =
+ packets_entropy_.upper_bound(sequence_number);
+ // When this map is empty we should only query entropy for
+ // |largest_received_sequence_number_|.
+ // TODO(rtenneti): add support for LOG_IF_EVERY_N_SEC to chromium.
+ // LOG_IF_EVERY_N_SEC(WARNING, it != packets_entropy_.end(), 10)
+ LOG_IF(WARNING, it != packets_entropy_.end())
+ << "largest_received: " << received_info_.largest_observed
+ << " sequence_number: " << sequence_number;
+
+ // TODO(satyamshekhar): Make this O(1).
+ QuicPacketEntropyHash hash = packets_entropy_hash_;
+ for (; it != packets_entropy_.end(); ++it) {
+ hash ^= it->second;
+ }
+ return hash;
+}
+
+void QuicReceivedPacketManager::RecalculateEntropyHash(
+ QuicPacketSequenceNumber peer_least_unacked,
+ QuicPacketEntropyHash entropy_hash) {
+ DCHECK_LE(peer_least_unacked, received_info_.largest_observed);
+ if (peer_least_unacked < largest_sequence_number_) {
+ DLOG(INFO) << "Ignoring received peer_least_unacked:" << peer_least_unacked
+ << " less than largest_peer_sequence_number:"
+ << largest_sequence_number_;
+ return;
+ }
+ largest_sequence_number_ = peer_least_unacked;
+ packets_entropy_hash_ = entropy_hash;
+ ReceivedEntropyMap::iterator it =
+ packets_entropy_.lower_bound(peer_least_unacked);
+ // TODO(satyamshekhar): Make this O(1).
+ for (; it != packets_entropy_.end(); ++it) {
+ packets_entropy_hash_ ^= it->second;
+ }
+ // Discard entropies before least unacked.
+ packets_entropy_.erase(
+ packets_entropy_.begin(),
+ packets_entropy_.lower_bound(
+ min(peer_least_unacked, received_info_.largest_observed)));
+}
+
+void QuicReceivedPacketManager::UpdatePacketInformationReceivedByPeer(
+ const QuicAckFrame& incoming_ack) {
+ // ValidateAck should fail if largest_observed ever shrinks.
+ DCHECK_LE(peer_largest_observed_packet_,
+ incoming_ack.received_info.largest_observed);
+ peer_largest_observed_packet_ = incoming_ack.received_info.largest_observed;
+
+ if (incoming_ack.received_info.missing_packets.empty()) {
+ least_packet_awaited_by_peer_ = peer_largest_observed_packet_ + 1;
+ } else {
+ least_packet_awaited_by_peer_ =
+ *(incoming_ack.received_info.missing_packets.begin());
+ }
+}
+
+bool QuicReceivedPacketManager::DontWaitForPacketsBefore(
+ QuicPacketSequenceNumber least_unacked) {
+ size_t missing_packets_count = received_info_.missing_packets.size();
+ received_info_.missing_packets.erase(
+ received_info_.missing_packets.begin(),
+ received_info_.missing_packets.lower_bound(least_unacked));
+ return missing_packets_count != received_info_.missing_packets.size();
+}
+
+void QuicReceivedPacketManager::UpdatePacketInformationSentByPeer(
+ const QuicAckFrame& incoming_ack) {
+ // ValidateAck() should fail if peer_least_packet_awaiting_ack_ shrinks.
+ DCHECK_LE(peer_least_packet_awaiting_ack_,
+ incoming_ack.sent_info.least_unacked);
+ if (incoming_ack.sent_info.least_unacked > peer_least_packet_awaiting_ack_) {
+ bool missed_packets =
+ DontWaitForPacketsBefore(incoming_ack.sent_info.least_unacked);
+ if (missed_packets || incoming_ack.sent_info.least_unacked >
+ received_info_.largest_observed + 1) {
+ DVLOG(1) << "Updating entropy hashed since we missed packets";
+ // There were some missing packets that we won't ever get now. Recalculate
+ // the received entropy hash.
+ RecalculateEntropyHash(incoming_ack.sent_info.least_unacked,
+ incoming_ack.sent_info.entropy_hash);
+ }
+ peer_least_packet_awaiting_ack_ = incoming_ack.sent_info.least_unacked;
+ }
+ DCHECK(received_info_.missing_packets.empty() ||
+ *received_info_.missing_packets.begin() >=
+ peer_least_packet_awaiting_ack_);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_received_packet_manager.h b/chromium/net/quic/quic_received_packet_manager.h
new file mode 100644
index 00000000000..a762ae804c7
--- /dev/null
+++ b/chromium/net/quic/quic_received_packet_manager.h
@@ -0,0 +1,124 @@
+// Copyright 2013 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.
+//
+// Manages the packet entropy calculation for both sent and received packets
+// for a connection.
+
+#ifndef NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
+#define NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
+
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+namespace test {
+class QuicReceivedPacketManagerPeer;
+} // namespace test
+
+// Records all received packets by a connection and tracks their entropy.
+// Also calculates the correct entropy for the framer when it truncates an ack
+// frame being serialized.
+class NET_EXPORT_PRIVATE QuicReceivedPacketManager :
+ public QuicReceivedEntropyHashCalculatorInterface {
+ public:
+ QuicReceivedPacketManager();
+ virtual ~QuicReceivedPacketManager();
+
+ // Updates the internal state concerning which packets have been acked.
+ void RecordPacketReceived(const QuicPacketHeader& header,
+ QuicTime receipt_time);
+
+ // Checks if we're still waiting for the packet with |sequence_number|.
+ bool IsAwaitingPacket(QuicPacketSequenceNumber sequence_number);
+
+ // Update the |received_info| for an outgoing ack.
+ void UpdateReceivedPacketInfo(ReceivedPacketInfo* received_info,
+ QuicTime approximate_now);
+
+ // QuicReceivedEntropyHashCalculatorInterface
+ // Called by QuicFramer, when the outgoing ack gets truncated, to recalculate
+ // the received entropy hash for the truncated ack frame.
+ virtual QuicPacketEntropyHash EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const OVERRIDE;
+
+ // These two are called by OnAckFrame.
+ //
+ // Updates internal state based on |incoming_ack.received_info|.
+ void UpdatePacketInformationReceivedByPeer(const QuicAckFrame& incoming_ack);
+ // Updates internal state based on |incoming_ack.sent_info|.
+ void UpdatePacketInformationSentByPeer(const QuicAckFrame& incoming_ack);
+
+ QuicPacketSequenceNumber peer_largest_observed_packet() {
+ return peer_largest_observed_packet_;
+ }
+
+ QuicPacketSequenceNumber least_packet_awaited_by_peer() {
+ return least_packet_awaited_by_peer_;
+ }
+
+ QuicPacketSequenceNumber peer_least_packet_awaiting_ack() {
+ return peer_least_packet_awaiting_ack_;
+ }
+
+ private:
+ friend class test::QuicReceivedPacketManagerPeer;
+
+ typedef std::map<QuicPacketSequenceNumber,
+ QuicPacketEntropyHash> ReceivedEntropyMap;
+
+ // Record the received entropy hash against |sequence_number|.
+ void RecordPacketEntropyHash(QuicPacketSequenceNumber sequence_number,
+ QuicPacketEntropyHash entropy_hash);
+
+ // Recalculate the entropy hash and clears old packet entropies,
+ // now that the sender sent us the |entropy_hash| for packets up to,
+ // but not including, |peer_least_unacked|.
+ void RecalculateEntropyHash(QuicPacketSequenceNumber peer_least_unacked,
+ QuicPacketEntropyHash entropy_hash);
+
+ // Deletes all missing packets before least unacked. The connection won't
+ // process any packets with sequence number before |least_unacked| that it
+ // received after this call. Returns true if there were missing packets before
+ // |least_unacked| unacked, false otherwise.
+ bool DontWaitForPacketsBefore(QuicPacketSequenceNumber least_unacked);
+
+ // TODO(satyamshekhar): Can be optimized using an interval set like data
+ // structure.
+ // Map of received sequence numbers to their corresponding entropy.
+ // Every received packet has an entry, and packets without the entropy bit set
+ // have an entropy value of 0.
+ // TODO(ianswett): When the entropy flag is off, the entropy should not be 0.
+ ReceivedEntropyMap packets_entropy_;
+
+ // Cumulative hash of entropy of all received packets.
+ QuicPacketEntropyHash packets_entropy_hash_;
+
+ // The largest sequence number cleared by RecalculateEntropyHash.
+ // Received entropy cannot be calculated for numbers less than it.
+ QuicPacketSequenceNumber largest_sequence_number_;
+
+
+ // Track some peer state so we can do less bookkeeping.
+ // Largest sequence number that the peer has observed. Mostly received,
+ // missing in case of truncated acks.
+ QuicPacketSequenceNumber peer_largest_observed_packet_;
+ // Least sequence number which the peer is still waiting for.
+ QuicPacketSequenceNumber least_packet_awaited_by_peer_;
+ // Least sequence number of the the packet sent by the peer for which it
+ // hasn't received an ack.
+ QuicPacketSequenceNumber peer_least_packet_awaiting_ack_;
+
+ // Received packet information used to produce acks.
+ ReceivedPacketInfo received_info_;
+
+ // The time we received the largest_observed sequence number, or zero if
+ // no sequence numbers have been received since UpdateReceivedPacketInfo.
+ // Needed for calculating delta_time_largest_observed.
+ QuicTime time_largest_observed_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_RECEIVED_PACKET_MANAGER_H_
diff --git a/chromium/net/quic/quic_received_packet_manager_test.cc b/chromium/net/quic/quic_received_packet_manager_test.cc
new file mode 100644
index 00000000000..76be470c265
--- /dev/null
+++ b/chromium/net/quic/quic_received_packet_manager_test.cc
@@ -0,0 +1,119 @@
+// Copyright 2013 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/quic/quic_received_packet_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "net/quic/test_tools/quic_received_packet_manager_peer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicReceivedPacketManagerTest : public ::testing::Test {
+ protected:
+ void RecordPacketEntropyHash(QuicPacketSequenceNumber sequence_number,
+ QuicPacketEntropyHash entropy_hash) {
+ QuicPacketHeader header;
+ header.packet_sequence_number = sequence_number;
+ header.entropy_hash = entropy_hash;
+ received_manager_.RecordPacketReceived(header, QuicTime::Zero());;
+ }
+
+ QuicReceivedPacketManager received_manager_;
+};
+
+TEST_F(QuicReceivedPacketManagerTest, ReceivedPacketEntropyHash) {
+ vector<pair<QuicPacketSequenceNumber, QuicPacketEntropyHash> > entropies;
+ entropies.push_back(make_pair(1, 12));
+ entropies.push_back(make_pair(7, 1));
+ entropies.push_back(make_pair(2, 33));
+ entropies.push_back(make_pair(5, 3));
+ entropies.push_back(make_pair(8, 34));
+
+ for (size_t i = 0; i < entropies.size(); ++i) {
+ RecordPacketEntropyHash(entropies[i].first,
+ entropies[i].second);
+ }
+
+ sort(entropies.begin(), entropies.end());
+
+ QuicPacketEntropyHash hash = 0;
+ size_t index = 0;
+ for (size_t i = 1; i <= (*entropies.rbegin()).first; ++i) {
+ if (entropies[index].first == i) {
+ hash ^= entropies[index].second;
+ ++index;
+ }
+ EXPECT_EQ(hash, received_manager_.EntropyHash(i));
+ }
+}
+
+TEST_F(QuicReceivedPacketManagerTest, EntropyHashBelowLeastObserved) {
+ EXPECT_EQ(0, received_manager_.EntropyHash(0));
+ RecordPacketEntropyHash(4, 5);
+ EXPECT_EQ(0, received_manager_.EntropyHash(3));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, EntropyHashAboveLargestObserved) {
+ EXPECT_EQ(0, received_manager_.EntropyHash(0));
+ RecordPacketEntropyHash(4, 5);
+ EXPECT_EQ(0, received_manager_.EntropyHash(3));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, RecalculateEntropyHash) {
+ vector<pair<QuicPacketSequenceNumber, QuicPacketEntropyHash> > entropies;
+ entropies.push_back(make_pair(1, 12));
+ entropies.push_back(make_pair(2, 1));
+ entropies.push_back(make_pair(3, 33));
+ entropies.push_back(make_pair(4, 3));
+ entropies.push_back(make_pair(5, 34));
+ entropies.push_back(make_pair(6, 29));
+
+ QuicPacketEntropyHash entropy_hash = 0;
+ for (size_t i = 0; i < entropies.size(); ++i) {
+ RecordPacketEntropyHash(entropies[i].first, entropies[i].second);
+ entropy_hash ^= entropies[i].second;
+ }
+ EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(6));
+
+ // Now set the entropy hash up to 4 to be 100.
+ entropy_hash ^= 100;
+ for (size_t i = 0; i < 3; ++i) {
+ entropy_hash ^= entropies[i].second;
+ }
+ QuicReceivedPacketManagerPeer::RecalculateEntropyHash(
+ &received_manager_, 4, 100);
+ EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(6));
+
+ QuicReceivedPacketManagerPeer::RecalculateEntropyHash(
+ &received_manager_, 1, 50);
+ EXPECT_EQ(entropy_hash, received_manager_.EntropyHash(6));
+}
+
+TEST_F(QuicReceivedPacketManagerTest, DontWaitForPacketsBefore) {
+ QuicPacketHeader header;
+ header.packet_sequence_number = 2u;
+ received_manager_.RecordPacketReceived(header, QuicTime::Zero());
+ header.packet_sequence_number = 7u;
+ received_manager_.RecordPacketReceived(header, QuicTime::Zero());
+ EXPECT_TRUE(received_manager_.IsAwaitingPacket(3u));
+ EXPECT_TRUE(received_manager_.IsAwaitingPacket(6u));
+ EXPECT_TRUE(QuicReceivedPacketManagerPeer::DontWaitForPacketsBefore(
+ &received_manager_, 4));
+ EXPECT_FALSE(received_manager_.IsAwaitingPacket(3u));
+ EXPECT_TRUE(received_manager_.IsAwaitingPacket(6u));
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_reliable_client_stream.cc b/chromium/net/quic/quic_reliable_client_stream.cc
new file mode 100644
index 00000000000..1951ae85a55
--- /dev/null
+++ b/chromium/net/quic/quic_reliable_client_stream.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_reliable_client_stream.h"
+
+#include "net/base/net_errors.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+QuicReliableClientStream::QuicReliableClientStream(QuicStreamId id,
+ QuicSession* session,
+ const BoundNetLog& net_log)
+ : ReliableQuicStream(id, session),
+ net_log_(net_log),
+ delegate_(NULL) {
+}
+
+QuicReliableClientStream::~QuicReliableClientStream() {
+ if (delegate_)
+ delegate_->OnClose(connection_error());
+}
+
+uint32 QuicReliableClientStream::ProcessData(const char* data,
+ uint32 data_len) {
+ // TODO(rch): buffer data if we don't have a delegate.
+ if (!delegate_)
+ return ERR_ABORTED;
+
+ int rv = delegate_->OnDataReceived(data, data_len);
+ if (rv != OK) {
+ DLOG(ERROR) << "Delegate refused data, rv: " << rv;
+ Close(QUIC_BAD_APPLICATION_PAYLOAD);
+ return 0;
+ }
+ return data_len;
+}
+
+void QuicReliableClientStream::TerminateFromPeer(bool half_close) {
+ if (delegate_) {
+ delegate_->OnClose(connection_error());
+ delegate_ = NULL;
+ }
+ ReliableQuicStream::TerminateFromPeer(half_close);
+}
+
+void QuicReliableClientStream::SetDelegate(
+ QuicReliableClientStream::Delegate* delegate) {
+ DCHECK((!delegate_ && delegate) || (delegate_ && !delegate));
+ delegate_ = delegate;
+}
+
+void QuicReliableClientStream::OnError(int error) {
+ if (delegate_) {
+ QuicReliableClientStream::Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ delegate->OnError(error);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_reliable_client_stream.h b/chromium/net/quic/quic_reliable_client_stream.h
new file mode 100644
index 00000000000..77ac787f10d
--- /dev/null
+++ b/chromium/net/quic/quic_reliable_client_stream.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+//
+// NOTE: This code is not shared between Google and Chrome.
+
+#ifndef NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+#define NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_stream.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+class QuicClientSession;
+
+// A client-initiated ReliableQuicStream. Instances of this class
+// are owned by the QuicClientSession which created them.
+class NET_EXPORT_PRIVATE QuicReliableClientStream : public ReliableQuicStream {
+ public:
+ // Delegate handles protocol specific behavior of a quic stream.
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ Delegate() {}
+
+ // Called when stream is ready to send data.
+ // Returns network error code. OK when it successfully sent data.
+ // ERR_IO_PENDING when performing operation asynchronously.
+ virtual int OnSendData() = 0;
+
+ // Called when data has been sent. |status| indicates network error
+ // or number of bytes that has been sent. On return, |eof| is set to true
+ // if no more data is available to send.
+ // Returns network error code. OK when it successfully sent data.
+ virtual int OnSendDataComplete(int status, bool* eof) = 0;
+
+ // Called when data is received.
+ // Returns network error code. OK when it successfully receives data.
+ virtual int OnDataReceived(const char* data, int length) = 0;
+
+ // Called when the stream is closed by the peer.
+ virtual void OnClose(QuicErrorCode error) = 0;
+
+ // Called when the stream is closed because of an error.
+ virtual void OnError(int error) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ QuicReliableClientStream(QuicStreamId id,
+ QuicSession* session,
+ const BoundNetLog& net_log);
+
+ virtual ~QuicReliableClientStream();
+
+ // ReliableQuicStream
+ virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+ virtual void TerminateFromPeer(bool half_close) OVERRIDE;
+ using ReliableQuicStream::WriteData;
+
+ // Set new |delegate|. |delegate| must not be NULL.
+ // If this stream has already received data, OnDataReceived() will be
+ // called on the delegate.
+ void SetDelegate(Delegate* delegate);
+ Delegate* GetDelegate() { return delegate_; }
+ void OnError(int error);
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ private:
+ BoundNetLog net_log_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicReliableClientStream);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
diff --git a/chromium/net/quic/quic_reliable_client_stream_test.cc b/chromium/net/quic/quic_reliable_client_stream_test.cc
new file mode 100644
index 00000000000..12402fd23eb
--- /dev/null
+++ b/chromium/net/quic/quic_reliable_client_stream_test.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_reliable_client_stream.h"
+
+#include "net/base/net_errors.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using testing::Return;
+using testing::StrEq;
+
+namespace net {
+namespace test {
+namespace {
+
+class MockDelegate : public QuicReliableClientStream::Delegate {
+ public:
+ MockDelegate() {}
+
+ MOCK_METHOD0(OnSendData, int());
+ MOCK_METHOD2(OnSendDataComplete, int(int, bool*));
+ MOCK_METHOD2(OnDataReceived, int(const char*, int));
+ MOCK_METHOD1(OnClose, void(QuicErrorCode));
+ MOCK_METHOD1(OnError, void(int));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDelegate);
+};
+
+class QuicReliableClientStreamTest : public ::testing::Test {
+ public:
+ QuicReliableClientStreamTest()
+ : session_(new MockConnection(1, IPEndPoint(), false), false),
+ stream_(1, &session_, BoundNetLog()) {
+ stream_.SetDelegate(&delegate_);
+ }
+
+ testing::StrictMock<MockDelegate> delegate_;
+ MockSession session_;
+ QuicReliableClientStream stream_;
+ QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(QuicReliableClientStreamTest, TerminateFromPeer) {
+ EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+ stream_.TerminateFromPeer(true);
+}
+
+TEST_F(QuicReliableClientStreamTest, ProcessData) {
+ const char data[] = "hello world!";
+ EXPECT_CALL(delegate_, OnDataReceived(StrEq(data), arraysize(data)));
+ EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+ EXPECT_EQ(arraysize(data), stream_.ProcessData(data, arraysize(data)));
+}
+
+TEST_F(QuicReliableClientStreamTest, ProcessDataWithError) {
+ const char data[] = "hello world!";
+ EXPECT_CALL(delegate_,
+ OnDataReceived(StrEq(data),
+ arraysize(data))).WillOnce(Return(ERR_UNEXPECTED));
+ EXPECT_CALL(delegate_, OnClose(QUIC_NO_ERROR));
+
+
+ EXPECT_EQ(0u, stream_.ProcessData(data, arraysize(data)));
+}
+
+TEST_F(QuicReliableClientStreamTest, OnError) {
+ EXPECT_CALL(delegate_, OnError(ERR_INTERNET_DISCONNECTED));
+
+ stream_.OnError(ERR_INTERNET_DISCONNECTED);
+ EXPECT_FALSE(stream_.GetDelegate());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_sent_entropy_manager.cc b/chromium/net/quic/quic_sent_entropy_manager.cc
new file mode 100644
index 00000000000..0f33e2d56e9
--- /dev/null
+++ b/chromium/net/quic/quic_sent_entropy_manager.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 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/quic/quic_sent_entropy_manager.h"
+
+#include "base/logging.h"
+#include "net/base/linked_hash_map.h"
+
+using std::make_pair;
+using std::max;
+using std::min;
+
+namespace net {
+
+QuicSentEntropyManager::QuicSentEntropyManager()
+ : packets_entropy_hash_(0) {}
+
+QuicSentEntropyManager::~QuicSentEntropyManager() {}
+
+void QuicSentEntropyManager::RecordPacketEntropyHash(
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacketEntropyHash entropy_hash) {
+ // TODO(satyamshekhar): Check this logic again when/if we enable packet
+ // reordering.
+ packets_entropy_hash_ ^= entropy_hash;
+ packets_entropy_.insert(
+ make_pair(sequence_number,
+ make_pair(entropy_hash, packets_entropy_hash_)));
+ DVLOG(2) << "setting cumulative sent entropy hash to: "
+ << static_cast<int>(packets_entropy_hash_)
+ << " updated with sequence number " << sequence_number
+ << " entropy hash: " << static_cast<int>(entropy_hash);
+}
+
+QuicPacketEntropyHash QuicSentEntropyManager::EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const {
+ SentEntropyMap::const_iterator it =
+ packets_entropy_.find(sequence_number);
+ if (it == packets_entropy_.end()) {
+ // Should only happen when we have not received ack for any packet.
+ DCHECK_EQ(0u, sequence_number);
+ return 0;
+ }
+ return it->second.second;
+}
+
+bool QuicSentEntropyManager::IsValidEntropy(
+ QuicPacketSequenceNumber sequence_number,
+ const SequenceNumberSet& missing_packets,
+ QuicPacketEntropyHash entropy_hash) const {
+ SentEntropyMap::const_iterator entropy_it =
+ packets_entropy_.find(sequence_number);
+ if (entropy_it == packets_entropy_.end()) {
+ DCHECK_EQ(0u, sequence_number);
+ // Close connection if something goes wrong.
+ return 0 == sequence_number;
+ }
+ QuicPacketEntropyHash expected_entropy_hash = entropy_it->second.second;
+ for (SequenceNumberSet::const_iterator it = missing_packets.begin();
+ it != missing_packets.end(); ++it) {
+ entropy_it = packets_entropy_.find(*it);
+ DCHECK(entropy_it != packets_entropy_.end());
+ expected_entropy_hash ^= entropy_it->second.first;
+ }
+ DLOG_IF(WARNING, entropy_hash != expected_entropy_hash)
+ << "Invalid entropy hash: " << static_cast<int>(entropy_hash)
+ << " expected entropy hash: " << static_cast<int>(expected_entropy_hash);
+ return entropy_hash == expected_entropy_hash;
+}
+
+void QuicSentEntropyManager::ClearEntropyBefore(
+ QuicPacketSequenceNumber sequence_number) {
+ if (packets_entropy_.empty()) {
+ return;
+ }
+ SentEntropyMap::iterator it = packets_entropy_.begin();
+ while (it->first < sequence_number) {
+ packets_entropy_.erase(it);
+ it = packets_entropy_.begin();
+ DCHECK(it != packets_entropy_.end());
+ }
+ DVLOG(2) << "Cleared entropy before: "
+ << packets_entropy_.begin()->first;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_sent_entropy_manager.h b/chromium/net/quic/quic_sent_entropy_manager.h
new file mode 100644
index 00000000000..4f684fc9a73
--- /dev/null
+++ b/chromium/net/quic/quic_sent_entropy_manager.h
@@ -0,0 +1,62 @@
+// Copyright 2013 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.
+//
+// Manages the packet entropy calculation for both sent and received packets
+// for a connection.
+
+#ifndef NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
+#define NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
+
+#include "net/base/linked_hash_map.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+// Records all sent packets by a connection to track the cumulative entropy of
+// sent packets. It is used by the connection to validate an ack
+// frame sent by the peer as a preventive measure against the optimistic ack
+// attack.
+class NET_EXPORT_PRIVATE QuicSentEntropyManager {
+ public:
+ QuicSentEntropyManager();
+ virtual ~QuicSentEntropyManager();
+
+ // Record |entropy_hash| for sent packet corresponding to |sequence_number|.
+ void RecordPacketEntropyHash(QuicPacketSequenceNumber sequence_number,
+ QuicPacketEntropyHash entropy_hash);
+
+ QuicPacketEntropyHash EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const;
+
+ // Returns true if |entropy_hash| matches the expected sent entropy hash
+ // up to |sequence_number| removing sequence numbers from |missing_packets|.
+ bool IsValidEntropy(QuicPacketSequenceNumber sequence_number,
+ const SequenceNumberSet& missing_packets,
+ QuicPacketEntropyHash entropy_hash) const;
+
+ // Removes not required entries from |packets_entropy_| before
+ // |sequence_number|.
+ void ClearEntropyBefore(QuicPacketSequenceNumber sequence_number);
+
+ QuicPacketEntropyHash packets_entropy_hash() const {
+ return packets_entropy_hash_;
+ }
+
+ private:
+ typedef linked_hash_map<QuicPacketSequenceNumber,
+ std::pair<QuicPacketEntropyHash,
+ QuicPacketEntropyHash> > SentEntropyMap;
+
+ // Linked hash map from sequence numbers to the sent entropy hash up to the
+ // sequence number in the key.
+ SentEntropyMap packets_entropy_;
+
+ // Cumulative hash of entropy of all sent packets.
+ QuicPacketEntropyHash packets_entropy_hash_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_SENT_ENTROPY_MANAGER_H_
diff --git a/chromium/net/quic/quic_sent_entropy_manager_test.cc b/chromium/net/quic/quic_sent_entropy_manager_test.cc
new file mode 100644
index 00000000000..e4e9847b516
--- /dev/null
+++ b/chromium/net/quic/quic_sent_entropy_manager_test.cc
@@ -0,0 +1,73 @@
+// Copyright 2013 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/quic/quic_sent_entropy_manager.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::make_pair;
+using std::pair;
+using std::vector;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicSentEntropyManagerTest : public ::testing::Test {
+ protected:
+ QuicSentEntropyManager entropy_manager_;
+};
+
+TEST_F(QuicSentEntropyManagerTest, SentEntropyHash) {
+ EXPECT_EQ(0, entropy_manager_.EntropyHash(0));
+
+ vector<pair<QuicPacketSequenceNumber, QuicPacketEntropyHash> > entropies;
+ entropies.push_back(make_pair(1, 12));
+ entropies.push_back(make_pair(2, 1));
+ entropies.push_back(make_pair(3, 33));
+ entropies.push_back(make_pair(4, 3));
+
+ for (size_t i = 0; i < entropies.size(); ++i) {
+ entropy_manager_.RecordPacketEntropyHash(entropies[i].first,
+ entropies[i].second);
+ }
+
+ QuicPacketEntropyHash hash = 0;
+ for (size_t i = 0; i < entropies.size(); ++i) {
+ hash ^= entropies[i].second;
+ EXPECT_EQ(hash, entropy_manager_.EntropyHash(i + 1));
+ }
+}
+
+TEST_F(QuicSentEntropyManagerTest, IsValidEntropy) {
+ QuicPacketEntropyHash entropies[10] =
+ {12, 1, 33, 3, 32, 100, 28, 42, 22, 255};
+ for (size_t i = 0; i < 10; ++i) {
+ entropy_manager_.RecordPacketEntropyHash(i + 1, entropies[i]);
+ }
+
+ SequenceNumberSet missing_packets;
+ missing_packets.insert(1);
+ missing_packets.insert(4);
+ missing_packets.insert(7);
+ missing_packets.insert(8);
+
+ QuicPacketEntropyHash entropy_hash = 0;
+ for (size_t i = 0; i < 10; ++i) {
+ if (missing_packets.find(i + 1) == missing_packets.end()) {
+ entropy_hash ^= entropies[i];
+ }
+ }
+
+ EXPECT_TRUE(entropy_manager_.IsValidEntropy(10, missing_packets,
+ entropy_hash));
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_session.cc b/chromium/net/quic/quic_session.cc
new file mode 100644
index 00000000000..5abf30593ed
--- /dev/null
+++ b/chromium/net/quic/quic_session.cc
@@ -0,0 +1,414 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_session.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/quic/quic_connection.h"
+#include "net/ssl/ssl_info.h"
+
+using base::StringPiece;
+using base::hash_map;
+using base::hash_set;
+using std::make_pair;
+using std::vector;
+
+namespace net {
+
+const size_t kMaxPrematurelyClosedStreamsTracked = 20;
+
+#define ENDPOINT (is_server_ ? "Server: " : " Client: ")
+
+// We want to make sure we delete any closed streams in a safe manner.
+// To avoid deleting a stream in mid-operation, we have a simple shim between
+// us and the stream, so we can delete any streams when we return from
+// processing.
+//
+// We could just override the base methods, but this makes it easier to make
+// sure we don't miss any.
+class VisitorShim : public QuicConnectionVisitorInterface {
+ public:
+ explicit VisitorShim(QuicSession* session) : session_(session) {}
+
+ virtual bool OnPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const vector<QuicStreamFrame>& frame) OVERRIDE {
+ bool accepted = session_->OnPacket(self_address, peer_address, header,
+ frame);
+ session_->PostProcessAfterData();
+ return accepted;
+ }
+ virtual void OnRstStream(const QuicRstStreamFrame& frame) OVERRIDE {
+ session_->OnRstStream(frame);
+ session_->PostProcessAfterData();
+ }
+
+ virtual void OnGoAway(const QuicGoAwayFrame& frame) OVERRIDE {
+ session_->OnGoAway(frame);
+ session_->PostProcessAfterData();
+ }
+
+ virtual void OnAck(const SequenceNumberSet& acked_packets) OVERRIDE {
+ session_->OnAck(acked_packets);
+ session_->PostProcessAfterData();
+ }
+
+ virtual bool OnCanWrite() OVERRIDE {
+ bool rc = session_->OnCanWrite();
+ session_->PostProcessAfterData();
+ return rc;
+ }
+
+ virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE {
+ session_->ConnectionClose(error, from_peer);
+ // The session will go away, so don't bother with cleanup.
+ }
+
+ private:
+ QuicSession* session_;
+};
+
+QuicSession::QuicSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server)
+ : connection_(connection),
+ visitor_shim_(new VisitorShim(this)),
+ config_(config),
+ max_open_streams_(config_.max_streams_per_connection()),
+ next_stream_id_(is_server ? 2 : 3),
+ is_server_(is_server),
+ largest_peer_created_stream_id_(0),
+ error_(QUIC_NO_ERROR),
+ goaway_received_(false),
+ goaway_sent_(false) {
+
+ connection_->set_visitor(visitor_shim_.get());
+ connection_->SetIdleNetworkTimeout(config_.idle_connection_state_lifetime());
+ if (connection_->connected()) {
+ connection_->SetOverallConnectionTimeout(
+ config_.max_time_before_crypto_handshake());
+ }
+ // TODO(satyamshekhar): Set congestion control and ICSL also.
+}
+
+QuicSession::~QuicSession() {
+ STLDeleteElements(&closed_streams_);
+ STLDeleteValues(&stream_map_);
+}
+
+bool QuicSession::OnPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const vector<QuicStreamFrame>& frames) {
+ if (header.public_header.guid != connection()->guid()) {
+ DLOG(INFO) << ENDPOINT << "Got packet header for invalid GUID: "
+ << header.public_header.guid;
+ return false;
+ }
+
+ for (size_t i = 0; i < frames.size(); ++i) {
+ // TODO(rch) deal with the error case of stream id 0
+ if (IsClosedStream(frames[i].stream_id)) {
+ // If we get additional frames for a stream where we didn't process
+ // headers, it's highly likely our compression context will end up
+ // permanently out of sync with the peer's, so we give up and close the
+ // connection.
+ if (ContainsKey(prematurely_closed_streams_, frames[i].stream_id)) {
+ connection()->SendConnectionClose(
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED);
+ return false;
+ }
+ continue;
+ }
+
+ ReliableQuicStream* stream = GetStream(frames[i].stream_id);
+ if (stream == NULL) return false;
+ if (!stream->WillAcceptStreamFrame(frames[i])) return false;
+
+ // TODO(alyssar) check against existing connection address: if changed, make
+ // sure we update the connection.
+ }
+
+ for (size_t i = 0; i < frames.size(); ++i) {
+ ReliableQuicStream* stream = GetStream(frames[i].stream_id);
+ if (stream) {
+ stream->OnStreamFrame(frames[i]);
+ }
+ }
+
+ while (!decompression_blocked_streams_.empty()) {
+ QuicHeaderId header_id = decompression_blocked_streams_.begin()->first;
+ if (header_id != decompressor_.current_header_id()) {
+ break;
+ }
+ QuicStreamId stream_id = decompression_blocked_streams_.begin()->second;
+ decompression_blocked_streams_.erase(header_id);
+ ReliableQuicStream* stream = GetStream(stream_id);
+ if (!stream) {
+ connection()->SendConnectionClose(
+ QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED);
+ return false;
+ }
+ stream->OnDecompressorAvailable();
+ }
+ return true;
+}
+
+void QuicSession::OnRstStream(const QuicRstStreamFrame& frame) {
+ ReliableQuicStream* stream = GetStream(frame.stream_id);
+ if (!stream) {
+ return; // Errors are handled by GetStream.
+ }
+ stream->OnStreamReset(frame.error_code);
+}
+
+void QuicSession::OnGoAway(const QuicGoAwayFrame& frame) {
+ DCHECK(frame.last_good_stream_id < next_stream_id_);
+ goaway_received_ = true;
+}
+
+void QuicSession::ConnectionClose(QuicErrorCode error, bool from_peer) {
+ if (error_ == QUIC_NO_ERROR) {
+ error_ = error;
+ }
+
+ while (stream_map_.size() != 0) {
+ ReliableStreamMap::iterator it = stream_map_.begin();
+ QuicStreamId id = it->first;
+ it->second->ConnectionClose(error, from_peer);
+ // The stream should call CloseStream as part of ConnectionClose.
+ if (stream_map_.find(id) != stream_map_.end()) {
+ LOG(DFATAL) << ENDPOINT << "Stream failed to close under ConnectionClose";
+ CloseStream(id);
+ }
+ }
+}
+
+bool QuicSession::OnCanWrite() {
+ // We latch this here rather than doing a traditional loop, because streams
+ // may be modifying the list as we loop.
+ int remaining_writes = write_blocked_streams_.NumObjects();
+
+ while (!connection_->HasQueuedData() &&
+ remaining_writes > 0) {
+ DCHECK(!write_blocked_streams_.IsEmpty());
+ ReliableQuicStream* stream =
+ GetStream(write_blocked_streams_.GetNextBlockedObject());
+ if (stream != NULL) {
+ // If the stream can't write all bytes, it'll re-add itself to the blocked
+ // list.
+ stream->OnCanWrite();
+ }
+ --remaining_writes;
+ }
+
+ return write_blocked_streams_.IsEmpty();
+}
+
+QuicConsumedData QuicSession::WriteData(QuicStreamId id,
+ StringPiece data,
+ QuicStreamOffset offset,
+ bool fin) {
+ return connection_->SendStreamData(id, data, offset, fin);
+}
+
+void QuicSession::SendRstStream(QuicStreamId id,
+ QuicRstStreamErrorCode error) {
+ connection_->SendRstStream(id, error);
+ CloseStream(id);
+}
+
+void QuicSession::SendGoAway(QuicErrorCode error_code, const string& reason) {
+ goaway_sent_ = true;
+ connection_->SendGoAway(error_code, largest_peer_created_stream_id_, reason);
+}
+
+void QuicSession::CloseStream(QuicStreamId stream_id) {
+ DLOG(INFO) << ENDPOINT << "Closing stream " << stream_id;
+
+ ReliableStreamMap::iterator it = stream_map_.find(stream_id);
+ if (it == stream_map_.end()) {
+ DLOG(INFO) << ENDPOINT << "Stream is already closed: " << stream_id;
+ return;
+ }
+ ReliableQuicStream* stream = it->second;
+ if (!stream->headers_decompressed()) {
+ if (prematurely_closed_streams_.size() ==
+ kMaxPrematurelyClosedStreamsTracked) {
+ prematurely_closed_streams_.erase(prematurely_closed_streams_.begin());
+ }
+ prematurely_closed_streams_.insert(make_pair(stream->id(), true));
+ }
+ closed_streams_.push_back(it->second);
+ stream_map_.erase(it);
+ stream->OnClose();
+}
+
+bool QuicSession::IsEncryptionEstablished() {
+ return GetCryptoStream()->encryption_established();
+}
+
+bool QuicSession::IsCryptoHandshakeConfirmed() {
+ return GetCryptoStream()->handshake_confirmed();
+}
+
+void QuicSession::OnCryptoHandshakeEvent(CryptoHandshakeEvent event) {
+ switch (event) {
+ // TODO(satyamshekhar): Move the logic of setting the encrypter/decrypter
+ // to QuicSession since it is the glue.
+ case ENCRYPTION_FIRST_ESTABLISHED:
+ break;
+
+ case ENCRYPTION_REESTABLISHED:
+ // Retransmit originally packets that were sent, since they can't be
+ // decrypted by the peer.
+ connection_->RetransmitUnackedPackets(
+ QuicConnection::INITIAL_ENCRYPTION_ONLY);
+ break;
+
+ case HANDSHAKE_CONFIRMED:
+ LOG_IF(DFATAL, !config_.negotiated()) << ENDPOINT
+ << "Handshake confirmed without parameter negotiation.";
+ connection_->SetIdleNetworkTimeout(
+ config_.idle_connection_state_lifetime());
+ connection_->SetOverallConnectionTimeout(QuicTime::Delta::Infinite());
+ max_open_streams_ = config_.max_streams_per_connection();
+ break;
+
+ default:
+ LOG(ERROR) << ENDPOINT << "Got unknown handshake event: " << event;
+ }
+}
+
+void QuicSession::OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message) {
+}
+
+void QuicSession::OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message) {
+}
+
+QuicConfig* QuicSession::config() {
+ return &config_;
+}
+
+void QuicSession::ActivateStream(ReliableQuicStream* stream) {
+ DLOG(INFO) << ENDPOINT << "num_streams: " << stream_map_.size()
+ << ". activating " << stream->id();
+ DCHECK(stream_map_.count(stream->id()) == 0);
+ stream_map_[stream->id()] = stream;
+}
+
+QuicStreamId QuicSession::GetNextStreamId() {
+ QuicStreamId id = next_stream_id_;
+ next_stream_id_ += 2;
+ return id;
+}
+
+ReliableQuicStream* QuicSession::GetStream(const QuicStreamId stream_id) {
+ if (stream_id == kCryptoStreamId) {
+ return GetCryptoStream();
+ }
+
+ ReliableStreamMap::iterator it = stream_map_.find(stream_id);
+ if (it != stream_map_.end()) {
+ return it->second;
+ }
+
+ if (IsClosedStream(stream_id)) {
+ return NULL;
+ }
+
+ if (stream_id % 2 == next_stream_id_ % 2) {
+ // We've received a frame for a locally-created stream that is not
+ // currently active. This is an error.
+ connection()->SendConnectionClose(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+ return NULL;
+ }
+
+ return GetIncomingReliableStream(stream_id);
+}
+
+ReliableQuicStream* QuicSession::GetIncomingReliableStream(
+ QuicStreamId stream_id) {
+ if (IsClosedStream(stream_id)) {
+ return NULL;
+ }
+
+ if (goaway_sent_) {
+ // We've already sent a GoAway
+ SendRstStream(stream_id, QUIC_STREAM_PEER_GOING_AWAY);
+ return NULL;
+ }
+
+ implicitly_created_streams_.erase(stream_id);
+ if (stream_id > largest_peer_created_stream_id_) {
+ // TODO(rch) add unit test for this
+ if (stream_id - largest_peer_created_stream_id_ > kMaxStreamIdDelta) {
+ connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID);
+ return NULL;
+ }
+ if (largest_peer_created_stream_id_ != 0) {
+ for (QuicStreamId id = largest_peer_created_stream_id_ + 2;
+ id < stream_id;
+ id += 2) {
+ implicitly_created_streams_.insert(id);
+ }
+ }
+ largest_peer_created_stream_id_ = stream_id;
+ }
+ ReliableQuicStream* stream = CreateIncomingReliableStream(stream_id);
+ if (stream == NULL) {
+ return NULL;
+ }
+ ActivateStream(stream);
+ return stream;
+}
+
+bool QuicSession::IsClosedStream(QuicStreamId id) {
+ DCHECK_NE(0u, id);
+ if (id == kCryptoStreamId) {
+ return false;
+ }
+ if (stream_map_.count(id) != 0) {
+ // Stream is active
+ return false;
+ }
+ if (id % 2 == next_stream_id_ % 2) {
+ // Locally created streams are strictly in-order. If the id is in the
+ // range of created streams and it's not active, it must have been closed.
+ return id < next_stream_id_;
+ }
+ // For peer created streams, we also need to consider implicitly created
+ // streams.
+ return id <= largest_peer_created_stream_id_ &&
+ implicitly_created_streams_.count(id) == 0;
+}
+
+size_t QuicSession::GetNumOpenStreams() const {
+ return stream_map_.size() + implicitly_created_streams_.size();
+}
+
+void QuicSession::MarkWriteBlocked(QuicStreamId id) {
+ write_blocked_streams_.AddBlockedObject(id);
+}
+
+void QuicSession::MarkDecompressionBlocked(QuicHeaderId header_id,
+ QuicStreamId stream_id) {
+ decompression_blocked_streams_[header_id] = stream_id;
+}
+
+bool QuicSession::GetSSLInfo(SSLInfo* ssl_info) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void QuicSession::PostProcessAfterData() {
+ STLDeleteElements(&closed_streams_);
+ closed_streams_.clear();
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_session.h b/chromium/net/quic/quic_session.h
new file mode 100644
index 00000000000..24a6deb45d1
--- /dev/null
+++ b/chromium/net/quic/quic_session.h
@@ -0,0 +1,266 @@
+// Copyright (c) 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.
+//
+// A QuicSession, which demuxes a single connection to individual streams.
+
+#ifndef NET_QUIC_QUIC_SESSION_H_
+#define NET_QUIC_QUIC_SESSION_H_
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/linked_hash_map.h"
+#include "net/quic/blocked_list.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+
+class QuicCryptoStream;
+class ReliableQuicStream;
+class SSLInfo;
+class VisitorShim;
+
+namespace test {
+class QuicSessionPeer;
+} // namespace test
+
+class NET_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface {
+ public:
+ // CryptoHandshakeEvent enumerates the events generated by a QuicCryptoStream.
+ enum CryptoHandshakeEvent {
+ // ENCRYPTION_FIRST_ESTABLISHED indicates that a full client hello has been
+ // sent by a client and that subsequent packets will be encrypted. (Client
+ // only.)
+ ENCRYPTION_FIRST_ESTABLISHED,
+ // ENCRYPTION_REESTABLISHED indicates that a client hello was rejected by
+ // the server and thus the encryption key has been updated. Therefore the
+ // connection should resend any packets that were sent under
+ // ENCRYPTION_INITIAL. (Client only.)
+ ENCRYPTION_REESTABLISHED,
+ // HANDSHAKE_CONFIRMED, in a client, indicates the the server has accepted
+ // our handshake. In a server it indicates that a full, valid client hello
+ // has been received. (Client and server.)
+ HANDSHAKE_CONFIRMED,
+ };
+
+ QuicSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server);
+
+ virtual ~QuicSession();
+
+ // QuicConnectionVisitorInterface methods:
+ virtual bool OnPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const std::vector<QuicStreamFrame>& frame) OVERRIDE;
+ virtual void OnRstStream(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual void OnGoAway(const QuicGoAwayFrame& frame) OVERRIDE;
+ virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE;
+ // Not needed for HTTP.
+ virtual void OnAck(const SequenceNumberSet& acked_packets) OVERRIDE {}
+ virtual bool OnCanWrite() OVERRIDE;
+
+ // Called by streams when they want to write data to the peer.
+ // Returns a pair with the number of bytes consumed from data, and a boolean
+ // indicating if the fin bit was consumed. This does not indicate the data
+ // has been sent on the wire: it may have been turned into a packet and queued
+ // if the socket was unexpectedly blocked.
+ virtual QuicConsumedData WriteData(QuicStreamId id,
+ base::StringPiece data,
+ QuicStreamOffset offset,
+ bool fin);
+ // Called by streams when they want to close the stream in both directions.
+ virtual void SendRstStream(QuicStreamId id, QuicRstStreamErrorCode error);
+
+ // Called when the session wants to go away and not accept any new streams.
+ void SendGoAway(QuicErrorCode error_code, const std::string& reason);
+
+ // Removes the stream associated with 'stream_id' from the active stream map.
+ virtual void CloseStream(QuicStreamId stream_id);
+
+ // Returns true if outgoing packets will be encrypted, even if the server
+ // hasn't confirmed the handshake yet.
+ virtual bool IsEncryptionEstablished();
+
+ // For a client, returns true if the server has confirmed our handshake. For
+ // a server, returns true if a full, valid client hello has been received.
+ virtual bool IsCryptoHandshakeConfirmed();
+
+ // Called by the QuicCryptoStream when the handshake enters a new state.
+ //
+ // Clients will call this function in the order:
+ // ENCRYPTION_FIRST_ESTABLISHED
+ // zero or more ENCRYPTION_REESTABLISHED
+ // HANDSHAKE_CONFIRMED
+ //
+ // Servers will simply call it once with HANDSHAKE_CONFIRMED.
+ virtual void OnCryptoHandshakeEvent(CryptoHandshakeEvent event);
+
+ // Called by the QuicCryptoStream when a handshake message is sent.
+ virtual void OnCryptoHandshakeMessageSent(
+ const CryptoHandshakeMessage& message);
+
+ // Called by the QuicCryptoStream when a handshake message is received.
+ virtual void OnCryptoHandshakeMessageReceived(
+ const CryptoHandshakeMessage& message);
+
+ // Returns mutable config for this session. Returned config is owned
+ // by QuicSession.
+ QuicConfig* config();
+
+ // Returns true if the stream existed previously and has been closed.
+ // Returns false if the stream is still active or if the stream has
+ // not yet been created.
+ bool IsClosedStream(QuicStreamId id);
+
+ QuicConnection* connection() { return connection_.get(); }
+ const QuicConnection* connection() const { return connection_.get(); }
+ size_t num_active_requests() const { return stream_map_.size(); }
+ const IPEndPoint& peer_address() const {
+ return connection_->peer_address();
+ }
+ QuicGuid guid() const { return connection_->guid(); }
+
+ QuicPacketCreator::Options* options() { return connection()->options(); }
+
+ // Returns the number of currently open streams, including those which have
+ // been implicitly created.
+ virtual size_t GetNumOpenStreams() const;
+
+ void MarkWriteBlocked(QuicStreamId id);
+
+ // Marks that |stream_id| is blocked waiting to decompress the
+ // headers identified by |decompression_id|.
+ void MarkDecompressionBlocked(QuicHeaderId decompression_id,
+ QuicStreamId stream_id);
+
+ bool goaway_received() const {
+ return goaway_received_;
+ }
+
+ bool goaway_sent() const {
+ return goaway_sent_;
+ }
+
+ QuicSpdyDecompressor* decompressor() { return &decompressor_; }
+ QuicSpdyCompressor* compressor() { return &compressor_; }
+
+ // Gets the SSL connection information.
+ virtual bool GetSSLInfo(SSLInfo* ssl_info);
+
+ QuicErrorCode error() const { return error_; }
+
+ protected:
+ // Creates a new stream, owned by the caller, to handle a peer-initiated
+ // stream. Returns NULL and does error handling if the stream can not be
+ // created.
+ virtual ReliableQuicStream* CreateIncomingReliableStream(QuicStreamId id) = 0;
+
+ // Create a new stream, owned by the caller, to handle a locally-initiated
+ // stream. Returns NULL if max streams have already been opened.
+ virtual ReliableQuicStream* CreateOutgoingReliableStream() = 0;
+
+ // Return the reserved crypto stream.
+ virtual QuicCryptoStream* GetCryptoStream() = 0;
+
+ // Adds 'stream' to the active stream map.
+ virtual void ActivateStream(ReliableQuicStream* stream);
+
+ // Returns the stream id for a new stream.
+ QuicStreamId GetNextStreamId();
+
+ ReliableQuicStream* GetIncomingReliableStream(QuicStreamId stream_id);
+
+ ReliableQuicStream* GetStream(const QuicStreamId stream_id);
+
+ // This is called after every call other than OnConnectionClose from the
+ // QuicConnectionVisitor to allow post-processing once the work has been done.
+ // In this case, it deletes streams given that it's safe to do so (no other
+ // operations are being done on the streams at this time)
+ virtual void PostProcessAfterData();
+
+ base::hash_map<QuicStreamId, ReliableQuicStream*>* streams() {
+ return &stream_map_;
+ }
+
+ const base::hash_map<QuicStreamId, ReliableQuicStream*>* streams() const {
+ return &stream_map_;
+ }
+
+ std::vector<ReliableQuicStream*>* closed_streams() {
+ return &closed_streams_;
+ }
+
+ size_t get_max_open_streams() const {
+ return max_open_streams_;
+ }
+
+ private:
+ friend class test::QuicSessionPeer;
+ friend class VisitorShim;
+
+ typedef base::hash_map<QuicStreamId, ReliableQuicStream*> ReliableStreamMap;
+
+ scoped_ptr<QuicConnection> connection_;
+
+ // Tracks the last 20 streams which closed without decompressing headers.
+ // This is for best-effort detection of an unrecoverable compression context.
+ // Ideally this would be a linked_hash_set as the boolean is unused.
+ linked_hash_map<QuicStreamId, bool> prematurely_closed_streams_;
+
+ // A shim to stand between the connection and the session, to handle stream
+ // deletions.
+ scoped_ptr<VisitorShim> visitor_shim_;
+
+ std::vector<ReliableQuicStream*> closed_streams_;
+
+ QuicSpdyDecompressor decompressor_;
+ QuicSpdyCompressor compressor_;
+
+ QuicConfig config_;
+
+ // Returns the maximum number of streams this connection can open.
+ size_t max_open_streams_;
+
+ // Map from StreamId to pointers to streams that are owned by the caller.
+ ReliableStreamMap stream_map_;
+ QuicStreamId next_stream_id_;
+ bool is_server_;
+
+ // Set of stream ids that have been "implicitly created" by receipt
+ // of a stream id larger than the next expected stream id.
+ base::hash_set<QuicStreamId> implicitly_created_streams_;
+
+ // A list of streams which need to write more data.
+ BlockedList<QuicStreamId> write_blocked_streams_;
+
+ // A map of headers waiting to be compressed, and the streams
+ // they are associated with.
+ map<uint32, QuicStreamId> decompression_blocked_streams_;
+
+ QuicStreamId largest_peer_created_stream_id_;
+
+ // The latched error with which the connection was closed.
+ QuicErrorCode error_;
+
+ // Whether a GoAway has been received.
+ bool goaway_received_;
+ // Whether a GoAway has been sent.
+ bool goaway_sent_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicSession);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_SESSION_H_
diff --git a/chromium/net/quic/quic_session_test.cc b/chromium/net/quic/quic_session_test.cc
new file mode 100644
index 00000000000..e417c436840
--- /dev/null
+++ b/chromium/net/quic/quic_session_test.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_session.h"
+
+#include <set>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::hash_map;
+using std::set;
+using std::vector;
+using testing::_;
+using testing::InSequence;
+
+namespace net {
+namespace test {
+namespace {
+
+class TestCryptoStream : public QuicCryptoStream {
+ public:
+ explicit TestCryptoStream(QuicSession* session)
+ : QuicCryptoStream(session) {
+ }
+
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE {
+ encryption_established_ = true;
+ handshake_confirmed_ = true;
+ CryptoHandshakeMessage msg;
+ string error_details;
+ session()->config()->ToHandshakeMessage(&msg);
+ const QuicErrorCode error = session()->config()->ProcessClientHello(
+ msg, &error_details);
+ EXPECT_EQ(QUIC_NO_ERROR, error);
+ session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+ }
+};
+
+class TestStream : public ReliableQuicStream {
+ public:
+ TestStream(QuicStreamId id, QuicSession* session)
+ : ReliableQuicStream(id, session) {
+ }
+
+ using ReliableQuicStream::CloseWriteSide;
+
+ virtual uint32 ProcessData(const char* data, uint32 data_len) {
+ return data_len;
+ }
+
+ MOCK_METHOD0(OnCanWrite, void());
+};
+
+class TestSession : public QuicSession {
+ public:
+ TestSession(QuicConnection* connection, bool is_server)
+ : QuicSession(connection, DefaultQuicConfig(), is_server),
+ crypto_stream_(this) {
+ }
+
+ virtual QuicCryptoStream* GetCryptoStream() OVERRIDE {
+ return &crypto_stream_;
+ }
+
+ virtual TestStream* CreateOutgoingReliableStream() OVERRIDE {
+ TestStream* stream = new TestStream(GetNextStreamId(), this);
+ ActivateStream(stream);
+ return stream;
+ }
+
+ virtual TestStream* CreateIncomingReliableStream(QuicStreamId id) OVERRIDE {
+ return new TestStream(id, this);
+ }
+
+ bool IsClosedStream(QuicStreamId id) {
+ return QuicSession::IsClosedStream(id);
+ }
+
+ ReliableQuicStream* GetIncomingReliableStream(QuicStreamId stream_id) {
+ return QuicSession::GetIncomingReliableStream(stream_id);
+ }
+
+ // Helper method for gmock
+ void MarkTwoWriteBlocked() {
+ this->MarkWriteBlocked(2);
+ }
+
+ TestCryptoStream crypto_stream_;
+};
+
+class QuicSessionTest : public ::testing::Test {
+ protected:
+ QuicSessionTest()
+ : guid_(1),
+ connection_(new MockConnection(guid_, IPEndPoint(), false)),
+ session_(connection_, true) {
+ }
+
+ void CheckClosedStreams() {
+ for (int i = kCryptoStreamId; i < 100; i++) {
+ if (closed_streams_.count(i) == 0) {
+ EXPECT_FALSE(session_.IsClosedStream(i)) << " stream id: " << i;
+ } else {
+ EXPECT_TRUE(session_.IsClosedStream(i)) << " stream id: " << i;
+ }
+ }
+ }
+
+ void CloseStream(QuicStreamId id) {
+ session_.CloseStream(id);
+ closed_streams_.insert(id);
+ }
+
+ QuicGuid guid_;
+ MockConnection* connection_;
+ TestSession session_;
+ QuicConnectionVisitorInterface* visitor_;
+ hash_map<QuicStreamId, ReliableQuicStream*>* streams_;
+ set<QuicStreamId> closed_streams_;
+};
+
+TEST_F(QuicSessionTest, IsCryptoHandshakeConfirmed) {
+ EXPECT_FALSE(session_.IsCryptoHandshakeConfirmed());
+ CryptoHandshakeMessage message;
+ session_.crypto_stream_.OnHandshakeMessage(message);
+ EXPECT_TRUE(session_.IsCryptoHandshakeConfirmed());
+}
+
+TEST_F(QuicSessionTest, IsClosedStreamDefault) {
+ // Ensure that no streams are initially closed.
+ for (int i = kCryptoStreamId; i < 100; i++) {
+ EXPECT_FALSE(session_.IsClosedStream(i));
+ }
+}
+
+TEST_F(QuicSessionTest, IsClosedStreamLocallyCreated) {
+ TestStream* stream2 = session_.CreateOutgoingReliableStream();
+ EXPECT_EQ(2u, stream2->id());
+ TestStream* stream4 = session_.CreateOutgoingReliableStream();
+ EXPECT_EQ(4u, stream4->id());
+
+ CheckClosedStreams();
+ CloseStream(4);
+ CheckClosedStreams();
+ CloseStream(2);
+ CheckClosedStreams();
+}
+
+TEST_F(QuicSessionTest, IsClosedStreamPeerCreated) {
+ session_.GetIncomingReliableStream(3);
+ session_.GetIncomingReliableStream(5);
+
+ CheckClosedStreams();
+ CloseStream(3);
+ CheckClosedStreams();
+ CloseStream(5);
+ // Create stream id 9, and implicitly 7
+ session_.GetIncomingReliableStream(9);
+ CheckClosedStreams();
+ // Close 9, but make sure 7 is still not closed
+ CloseStream(9);
+ CheckClosedStreams();
+}
+
+TEST_F(QuicSessionTest, StreamIdTooLarge) {
+ session_.GetIncomingReliableStream(3);
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_STREAM_ID));
+ session_.GetIncomingReliableStream(105);
+}
+
+TEST_F(QuicSessionTest, DecompressionError) {
+ ReliableQuicStream* stream = session_.GetIncomingReliableStream(3);
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_DECOMPRESSION_FAILURE));
+ const char data[] =
+ "\1\0\0\0" // headers id
+ "\0\0\0\4" // length
+ "abcd"; // invalid compressed data
+ stream->ProcessRawData(data, arraysize(data));
+}
+
+TEST_F(QuicSessionTest, OnCanWrite) {
+ TestStream* stream2 = session_.CreateOutgoingReliableStream();
+ TestStream* stream4 = session_.CreateOutgoingReliableStream();
+ TestStream* stream6 = session_.CreateOutgoingReliableStream();
+
+ session_.MarkWriteBlocked(2);
+ session_.MarkWriteBlocked(6);
+ session_.MarkWriteBlocked(4);
+
+ InSequence s;
+ EXPECT_CALL(*stream2, OnCanWrite()).WillOnce(
+ // Reregister, to test the loop limit.
+ testing::InvokeWithoutArgs(&session_, &TestSession::MarkTwoWriteBlocked));
+ EXPECT_CALL(*stream6, OnCanWrite());
+ EXPECT_CALL(*stream4, OnCanWrite());
+
+ EXPECT_FALSE(session_.OnCanWrite());
+}
+
+TEST_F(QuicSessionTest, OnCanWriteWithClosedStream) {
+ TestStream* stream2 = session_.CreateOutgoingReliableStream();
+ TestStream* stream4 = session_.CreateOutgoingReliableStream();
+ session_.CreateOutgoingReliableStream(); // stream 6
+
+ session_.MarkWriteBlocked(2);
+ session_.MarkWriteBlocked(6);
+ session_.MarkWriteBlocked(4);
+ CloseStream(6);
+
+ InSequence s;
+ EXPECT_CALL(*stream2, OnCanWrite());
+ EXPECT_CALL(*stream4, OnCanWrite());
+ EXPECT_TRUE(session_.OnCanWrite());
+}
+
+// Regression test for http://crbug.com/248737
+TEST_F(QuicSessionTest, OutOfOrderHeaders) {
+ QuicSpdyCompressor compressor;
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "http";
+ vector<QuicStreamFrame> frames;
+ QuicPacketHeader header;
+ header.public_header.guid = session_.guid();
+
+ TestStream* stream2 = session_.CreateOutgoingReliableStream();
+ TestStream* stream4 = session_.CreateOutgoingReliableStream();
+ stream2->CloseWriteSide();
+ stream4->CloseWriteSide();
+
+ // Create frame with headers for stream2.
+ string compressed_headers1 = compressor.CompressHeaders(headers);
+ QuicStreamFrame frame1(stream2->id(), false, 0, compressed_headers1);
+
+ // Create frame with headers for stream4.
+ string compressed_headers2 = compressor.CompressHeaders(headers);
+ QuicStreamFrame frame2(stream4->id(), true, 0, compressed_headers2);
+
+ // Process the second frame first. This will cause the headers to
+ // be queued up and processed after the first frame is processed.
+ frames.push_back(frame2);
+ session_.OnPacket(IPEndPoint(), IPEndPoint(), header, frames);
+
+ // Process the first frame, and un-cork the buffered headers.
+ frames[0] = frame1;
+ session_.OnPacket(IPEndPoint(), IPEndPoint(), header, frames);
+
+ // Ensure that the streams actually close and we don't DCHECK.
+ session_.ConnectionClose(QUIC_CONNECTION_TIMED_OUT, true);
+}
+
+TEST_F(QuicSessionTest, SendGoAway) {
+ // After sending a GoAway, ensure new incoming streams cannot be created and
+ // result in a RST being sent.
+ EXPECT_CALL(*connection_,
+ SendGoAway(QUIC_PEER_GOING_AWAY, 0u, "Going Away."));
+ session_.SendGoAway(QUIC_PEER_GOING_AWAY, "Going Away.");
+ EXPECT_TRUE(session_.goaway_sent());
+
+ EXPECT_CALL(*connection_, SendRstStream(3u, QUIC_STREAM_PEER_GOING_AWAY));
+ EXPECT_FALSE(session_.GetIncomingReliableStream(3u));
+}
+
+TEST_F(QuicSessionTest, IncreasedTimeoutAfterCryptoHandshake) {
+ EXPECT_EQ(kDefaultInitialTimeoutSecs,
+ QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+ CryptoHandshakeMessage msg;
+ session_.crypto_stream_.OnHandshakeMessage(msg);
+ EXPECT_EQ(kDefaultTimeoutSecs,
+ QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_spdy_compressor.cc b/chromium/net/quic/quic_spdy_compressor.cc
new file mode 100644
index 00000000000..7efd45ce76f
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_compressor.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 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/quic/quic_spdy_compressor.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+
+using std::string;
+
+namespace net {
+
+QuicSpdyCompressor::QuicSpdyCompressor()
+ : spdy_framer_(SPDY3),
+ header_sequence_id_(1) {
+ spdy_framer_.set_enable_compression(true);
+}
+
+QuicSpdyCompressor::~QuicSpdyCompressor() {
+}
+
+string QuicSpdyCompressor::CompressHeaders(
+ const SpdyHeaderBlock& headers) {
+ // TODO(rch): Modify the SpdyFramer to expose a
+ // CreateCompressedHeaderBlock method, or some such.
+ SpdyStreamId stream_id = 3; // unused.
+ scoped_ptr<SpdyFrame> frame(spdy_framer_.CreateHeaders(
+ stream_id, CONTROL_FLAG_NONE, true, &headers));
+
+ // The size of the spdy HEADER frame's fixed prefix which
+ // needs to be stripped off from the resulting frame.
+ const size_t header_frame_prefix_len = 12;
+ string serialized = string(frame->data() + header_frame_prefix_len,
+ frame->size() - header_frame_prefix_len);
+ uint32 serialized_len = serialized.length();
+ char id_str[sizeof(header_sequence_id_)];
+ memcpy(&id_str, &header_sequence_id_, sizeof(header_sequence_id_));
+ char len_str[sizeof(serialized_len)];
+ memcpy(&len_str, &serialized_len, sizeof(serialized_len));
+ string compressed;
+ compressed.reserve(arraysize(id_str) + arraysize(len_str) + serialized_len);
+ compressed.append(id_str, arraysize(id_str));
+ compressed.append(len_str, arraysize(len_str));
+ compressed.append(serialized);
+ ++header_sequence_id_;
+ return compressed;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_spdy_compressor.h b/chromium/net/quic/quic_spdy_compressor.h
new file mode 100644
index 00000000000..c88c47eae7e
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_compressor.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2013 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_QUIC_QUIC_SPDY_COMPRESSOR_H_
+#define NET_QUIC_QUIC_SPDY_COMPRESSOR_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+// Handles the compression of request/response headers blocks. The
+// serialized format is:
+// uint32 - Header ID
+// uint32 - Compressed header length
+// ... - Compressed data
+//
+class NET_EXPORT_PRIVATE QuicSpdyCompressor {
+ public:
+ QuicSpdyCompressor();
+ ~QuicSpdyCompressor();
+
+ std::string CompressHeaders(const SpdyHeaderBlock& headers);
+
+ private:
+ SpdyFramer spdy_framer_;
+ QuicHeaderId header_sequence_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicSpdyCompressor);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_SPDY_COMPRESSOR_H_
diff --git a/chromium/net/quic/quic_spdy_compressor_test.cc b/chromium/net/quic/quic_spdy_compressor_test.cc
new file mode 100644
index 00000000000..93066f29091
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_compressor_test.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 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 <string>
+
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicSpdyCompressorTest : public ::testing::Test {
+ protected:
+ TestDecompressorVisitor visitor_;
+};
+
+TEST_F(QuicSpdyCompressorTest, Compress) {
+ QuicSpdyCompressor compressor;
+ QuicSpdyDecompressor decompressor;
+
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "https";
+
+ string compressed_headers = compressor.CompressHeaders(headers);
+ EXPECT_EQ('\1', compressed_headers[0]);
+ EXPECT_EQ('\0', compressed_headers[1]);
+ EXPECT_EQ('\0', compressed_headers[2]);
+ EXPECT_EQ('\0', compressed_headers[3]);
+ string compressed_data = compressed_headers.substr(4);
+ decompressor.DecompressData(compressed_data, &visitor_);
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers),
+ visitor_.data());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_spdy_decompressor.cc b/chromium/net/quic/quic_spdy_decompressor.cc
new file mode 100644
index 00000000000..f8bd4c142a8
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_decompressor.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 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/quic/quic_spdy_decompressor.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+
+using base::StringPiece;
+using std::min;
+
+namespace net {
+
+class SpdyFramerVisitor : public SpdyFramerVisitorInterface {
+ public:
+ explicit SpdyFramerVisitor(QuicSpdyDecompressor::Visitor* visitor)
+ : visitor_(visitor),
+ error_(false) {
+ }
+
+ virtual void OnError(SpdyFramer* framer) OVERRIDE {
+ error_ = true;
+ }
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE {}
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {}
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) OVERRIDE;
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) OVERRIDE {}
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE {}
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {}
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {}
+ virtual void OnPing(uint32 unique_id) OVERRIDE {}
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {}
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE {}
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {}
+ virtual bool OnCredentialFrameData(const char* credential_data,
+ size_t len) OVERRIDE {
+ return false;
+ }
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {}
+ void set_visitor(QuicSpdyDecompressor::Visitor* visitor) {
+ DCHECK(visitor);
+ visitor_ = visitor;
+ }
+
+ private:
+ QuicSpdyDecompressor::Visitor* visitor_;
+ bool error_;
+};
+
+bool SpdyFramerVisitor::OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ DCHECK(visitor_);
+ return visitor_->OnDecompressedData(StringPiece(header_data, len));
+}
+
+QuicSpdyDecompressor::QuicSpdyDecompressor()
+ : spdy_framer_(SPDY3),
+ spdy_visitor_(new SpdyFramerVisitor(NULL)),
+ current_header_id_(1),
+ has_current_compressed_size_(false),
+ current_compressed_size_(0),
+ compressed_bytes_consumed_(0) {
+ spdy_framer_.set_visitor(spdy_visitor_.get());
+}
+
+QuicSpdyDecompressor::~QuicSpdyDecompressor() {
+}
+
+size_t QuicSpdyDecompressor::DecompressData(StringPiece data,
+ Visitor* visitor) {
+ spdy_visitor_->set_visitor(visitor);
+ size_t bytes_consumed = 0;
+
+ if (!has_current_compressed_size_) {
+ const size_t kCompressedBufferSizeSize = sizeof(uint32);
+ DCHECK_GT(kCompressedBufferSizeSize, compressed_size_buffer_.length());
+ size_t missing_size =
+ kCompressedBufferSizeSize - compressed_size_buffer_.length();
+ if (data.length() < missing_size) {
+ data.AppendToString(&compressed_size_buffer_);
+ return data.length();
+ }
+ bytes_consumed += missing_size;
+ data.substr(0, missing_size).AppendToString(&compressed_size_buffer_);
+ DCHECK_EQ(kCompressedBufferSizeSize, compressed_size_buffer_.length());
+ memcpy(&current_compressed_size_, compressed_size_buffer_.data(),
+ kCompressedBufferSizeSize);
+ compressed_size_buffer_.clear();
+ has_current_compressed_size_ = true;
+ data = data.substr(missing_size);
+ compressed_bytes_consumed_ = 0;
+ }
+
+ size_t bytes_to_consume =
+ min(current_compressed_size_ - compressed_bytes_consumed_,
+ static_cast<uint32>(data.length()));
+ if (bytes_to_consume > 0) {
+ if (!spdy_framer_.IncrementallyDecompressControlFrameHeaderData(
+ current_header_id_, data.data(), bytes_to_consume)) {
+ visitor->OnDecompressionError();
+ return bytes_consumed;
+ }
+ compressed_bytes_consumed_ += bytes_to_consume;
+ bytes_consumed += bytes_to_consume;
+ }
+ if (current_compressed_size_ - compressed_bytes_consumed_ == 0) {
+ ResetForNextHeaders();
+ }
+ return bytes_consumed;
+}
+
+void QuicSpdyDecompressor::ResetForNextHeaders() {
+ has_current_compressed_size_ = false;
+ current_compressed_size_ = 0;
+ compressed_bytes_consumed_ = 0;
+ ++current_header_id_;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_spdy_decompressor.h b/chromium/net/quic/quic_spdy_decompressor.h
new file mode 100644
index 00000000000..a56c4799015
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_decompressor.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 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_QUIC_QUIC_SPDY_DECOMPRESSOR_H_
+#define NET_QUIC_QUIC_SPDY_DECOMPRESSOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+class SpdyFramerVisitor;
+
+// Handles the compression of request/response headers blocks.
+class NET_EXPORT_PRIVATE QuicSpdyDecompressor {
+ public:
+ // Interface that receives callbacks with decompressed data as it
+ // becomes available.
+ class NET_EXPORT_PRIVATE Visitor {
+ public:
+ virtual ~Visitor() {}
+ virtual bool OnDecompressedData(base::StringPiece data) = 0;
+ virtual void OnDecompressionError() = 0;
+ };
+
+ QuicSpdyDecompressor();
+ ~QuicSpdyDecompressor();
+
+ // Decompresses the data in |data| and invokes |OnDecompressedData|,
+ // possibly multiple times, on |visitor|. Returns number of bytes
+ // consumed from |data|.
+ size_t DecompressData(base::StringPiece data, Visitor* visitor);
+
+ QuicHeaderId current_header_id() { return current_header_id_; }
+
+ private:
+ void ResetForNextHeaders();
+
+ SpdyFramer spdy_framer_;
+ scoped_ptr<SpdyFramerVisitor> spdy_visitor_;
+ // ID of the header currently being parsed.
+ QuicHeaderId current_header_id_;
+ // True when the size of the headers has been parsed.
+ bool has_current_compressed_size_;
+ // Size of the headers being parsed.
+ uint32 current_compressed_size_;
+ // Buffer into which the partial compressed size is written until
+ // it is fully parsed.
+ std::string compressed_size_buffer_;
+ // Number of compressed bytes consumed, out of the total in
+ // |current_compressed_size_|.
+ uint32 compressed_bytes_consumed_;
+ DISALLOW_COPY_AND_ASSIGN(QuicSpdyDecompressor);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_SPDY_DECOMPRESSOR_H_
diff --git a/chromium/net/quic/quic_spdy_decompressor_test.cc b/chromium/net/quic/quic_spdy_decompressor_test.cc
new file mode 100644
index 00000000000..de9156bdfa4
--- /dev/null
+++ b/chromium/net/quic/quic_spdy_decompressor_test.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2013 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 <string>
+
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+class QuicSpdyDecompressorTest : public ::testing::Test {
+ protected:
+ QuicSpdyDecompressor decompressor_;
+ QuicSpdyCompressor compressor_;
+ TestDecompressorVisitor visitor_;
+};
+
+TEST_F(QuicSpdyDecompressorTest, Decompress) {
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "https";
+
+ EXPECT_EQ(1u, decompressor_.current_header_id());
+ string compressed_headers = compressor_.CompressHeaders(headers).substr(4);
+ EXPECT_EQ(compressed_headers.length(),
+ decompressor_.DecompressData(compressed_headers, &visitor_));
+
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers), visitor_.data());
+ EXPECT_EQ(2u, decompressor_.current_header_id());
+}
+
+TEST_F(QuicSpdyDecompressorTest, DecompressPartial) {
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "https";
+ string compressed_headers = compressor_.CompressHeaders(headers).substr(4);
+
+ for (size_t i = 0; i < compressed_headers.length(); ++i) {
+ QuicSpdyDecompressor decompressor;
+ TestDecompressorVisitor visitor;
+
+ EXPECT_EQ(1u, decompressor.current_header_id());
+
+ string partial_compressed_headers = compressed_headers.substr(0, i);
+ EXPECT_EQ(partial_compressed_headers.length(),
+ decompressor.DecompressData(partial_compressed_headers,
+ &visitor));
+ EXPECT_EQ(1u, decompressor.current_header_id()) << "i: " << i;
+
+ string remaining_compressed_headers =
+ compressed_headers.substr(partial_compressed_headers.length());
+ EXPECT_EQ(remaining_compressed_headers.length(),
+ decompressor.DecompressData(remaining_compressed_headers,
+ &visitor));
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers), visitor.data());
+
+ EXPECT_EQ(2u, decompressor.current_header_id());
+ }
+}
+
+TEST_F(QuicSpdyDecompressorTest, DecompressAndIgnoreTrailingData) {
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "https";
+
+ string compressed_headers = compressor_.CompressHeaders(headers).substr(4);
+ EXPECT_EQ(compressed_headers.length(),
+ decompressor_.DecompressData(compressed_headers + "abc123",
+ &visitor_));
+
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers), visitor_.data());
+}
+
+TEST_F(QuicSpdyDecompressorTest, DecompressError) {
+ SpdyHeaderBlock headers;
+ headers[":host"] = "www.google.com";
+ headers[":path"] = "/index.hml";
+ headers[":scheme"] = "https";
+
+ EXPECT_EQ(1u, decompressor_.current_header_id());
+ string compressed_headers = compressor_.CompressHeaders(headers).substr(4);
+ compressed_headers[compressed_headers.length() - 1] ^= 0x01;
+ EXPECT_NE(compressed_headers.length(),
+ decompressor_.DecompressData(compressed_headers, &visitor_));
+
+ EXPECT_TRUE(visitor_.error());
+ EXPECT_EQ("", visitor_.data());
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_stats.cc b/chromium/net/quic/quic_stats.cc
new file mode 100644
index 00000000000..7404d927843
--- /dev/null
+++ b/chromium/net/quic/quic_stats.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_stats.h"
+
+namespace net {
+
+QuicConnectionStats::QuicConnectionStats()
+ : bytes_sent(0),
+ packets_sent(0),
+ bytes_received(0),
+ packets_received(0),
+ bytes_retransmitted(0),
+ packets_retransmitted(0),
+ packets_revived(0),
+ packets_dropped(0),
+ rto_count(0),
+ rtt(0),
+ estimated_bandwidth(0) {
+}
+
+QuicConnectionStats::~QuicConnectionStats() {}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_stats.h b/chromium/net/quic/quic_stats.h
new file mode 100644
index 00000000000..252791e80de
--- /dev/null
+++ b/chromium/net/quic/quic_stats.h
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_STATS_H_
+#define NET_QUIC_QUIC_STATS_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+// TODO(satyamshekhar): Add more interesting stats:
+// 1. (C/S)HLO retransmission count.
+// 2. SHLO received to first stream packet processed time.
+// 3. CHLO sent to SHLO received time.
+// 4. Number of migrations.
+// 5. Number of out of order packets.
+// 6. Avg packet size.
+// 7. Number of connections that require more that 1-RTT.
+// 8. Avg number of streams / session.
+// 9. Number of duplicates received.
+// 10. Fraction of traffic sent/received that was not data (protocol overhead).
+// 11. Fraction of data transferred that was padding.
+
+// Structure to hold stats for a QuicConnection.
+struct NET_EXPORT_PRIVATE QuicConnectionStats {
+ QuicConnectionStats();
+ ~QuicConnectionStats();
+
+ uint64 bytes_sent; // includes retransmissions, fec.
+ uint32 packets_sent;
+
+ uint64 bytes_received; // includes duplicate data for a stream, fec.
+ uint32 packets_received; // includes dropped packets
+
+ uint64 bytes_retransmitted;
+ uint32 packets_retransmitted;
+
+ uint32 packets_revived;
+ uint32 packets_dropped; // duplicate or less than least unacked.
+ uint32 rto_count;
+
+ uint32 rtt;
+ uint64 estimated_bandwidth;
+ // TODO(satyamshekhar): Add window_size, mss and mtu.
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_STATS_H_
diff --git a/chromium/net/quic/quic_stream_factory.cc b/chromium/net/quic/quic_stream_factory.cc
new file mode 100644
index 00000000000..86bd8a18aaa
--- /dev/null
+++ b/chromium/net/quic/quic_stream_factory.cc
@@ -0,0 +1,489 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_stream_factory.h"
+
+#include <set>
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_client_session.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_connection_helper.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/socket/client_socket_factory.h"
+
+namespace net {
+
+// Responsible for creating a new QUIC session to the specified server, and
+// for notifying any associated requests when complete.
+class QuicStreamFactory::Job {
+ public:
+ Job(QuicStreamFactory* factory,
+ HostResolver* host_resolver,
+ const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log);
+
+ ~Job();
+
+ int Run(const CompletionCallback& callback);
+
+ int DoLoop(int rv);
+ int DoResolveHost();
+ int DoResolveHostComplete(int rv);
+ int DoConnect();
+ int DoConnectComplete(int rv);
+
+ void OnIOComplete(int rv);
+
+ CompletionCallback callback() {
+ return callback_;
+ }
+
+ const HostPortProxyPair& host_port_proxy_pair() const {
+ return host_port_proxy_pair_;
+ }
+
+ private:
+ enum IoState {
+ STATE_NONE,
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_CONNECT,
+ STATE_CONNECT_COMPLETE,
+ };
+ IoState io_state_;
+
+ QuicStreamFactory* factory_;
+ SingleRequestHostResolver host_resolver_;
+ const HostPortProxyPair host_port_proxy_pair_;
+ bool is_https_;
+ CertVerifier* cert_verifier_;
+ const BoundNetLog net_log_;
+ QuicClientSession* session_;
+ CompletionCallback callback_;
+ AddressList address_list_;
+ DISALLOW_COPY_AND_ASSIGN(Job);
+};
+
+QuicStreamFactory::Job::Job(
+ QuicStreamFactory* factory,
+ HostResolver* host_resolver,
+ const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log)
+ : factory_(factory),
+ host_resolver_(host_resolver),
+ host_port_proxy_pair_(host_port_proxy_pair),
+ is_https_(is_https),
+ cert_verifier_(cert_verifier),
+ net_log_(net_log) {
+}
+
+QuicStreamFactory::Job::~Job() {
+}
+
+int QuicStreamFactory::Job::Run(const CompletionCallback& callback) {
+ io_state_ = STATE_RESOLVE_HOST;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv > 0 ? OK : rv;
+}
+
+int QuicStreamFactory::Job::DoLoop(int rv) {
+ do {
+ IoState state = io_state_;
+ io_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_RESOLVE_HOST:
+ CHECK_EQ(OK, rv);
+ rv = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ rv = DoResolveHostComplete(rv);
+ break;
+ case STATE_CONNECT:
+ CHECK_EQ(OK, rv);
+ rv = DoConnect();
+ break;
+ case STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "io_state_: " << io_state_;
+ break;
+ }
+ } while (io_state_ != STATE_NONE && rv != ERR_IO_PENDING);
+ return rv;
+}
+
+void QuicStreamFactory::Job::OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+
+ if (rv != ERR_IO_PENDING && !callback_.is_null()) {
+ callback_.Run(rv);
+ }
+}
+
+int QuicStreamFactory::Job::DoResolveHost() {
+ io_state_ = STATE_RESOLVE_HOST_COMPLETE;
+ return host_resolver_.Resolve(
+ HostResolver::RequestInfo(host_port_proxy_pair_.first), &address_list_,
+ base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+ base::Unretained(this)),
+ net_log_);
+}
+
+int QuicStreamFactory::Job::DoResolveHostComplete(int rv) {
+ if (rv != OK)
+ return rv;
+
+ // TODO(rch): remove this code!
+ AddressList::iterator it = address_list_.begin();
+ while (it != address_list_.end()) {
+ if (it->GetFamily() == ADDRESS_FAMILY_IPV6) {
+ it = address_list_.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ DCHECK(!factory_->HasActiveSession(host_port_proxy_pair_));
+ io_state_ = STATE_CONNECT;
+ return OK;
+}
+
+QuicStreamRequest::QuicStreamRequest(QuicStreamFactory* factory)
+ : factory_(factory) {}
+
+QuicStreamRequest::~QuicStreamRequest() {
+ if (factory_ && !callback_.is_null())
+ factory_->CancelRequest(this);
+}
+
+int QuicStreamRequest::Request(
+ const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ DCHECK(!stream_);
+ DCHECK(callback_.is_null());
+ int rv = factory_->Create(host_port_proxy_pair, is_https, cert_verifier,
+ net_log, this);
+ if (rv == ERR_IO_PENDING) {
+ host_port_proxy_pair_ = host_port_proxy_pair;
+ is_https_ = is_https;
+ cert_verifier_ = cert_verifier;
+ net_log_ = net_log;
+ callback_ = callback;
+ } else {
+ factory_ = NULL;
+ }
+ if (rv == OK)
+ DCHECK(stream_);
+ return rv;
+}
+
+void QuicStreamRequest::set_stream(scoped_ptr<QuicHttpStream> stream) {
+ DCHECK(stream);
+ stream_ = stream.Pass();
+}
+
+void QuicStreamRequest::OnRequestComplete(int rv) {
+ factory_ = NULL;
+ callback_.Run(rv);
+}
+
+scoped_ptr<QuicHttpStream> QuicStreamRequest::ReleaseStream() {
+ DCHECK(stream_);
+ return stream_.Pass();
+}
+
+int QuicStreamFactory::Job::DoConnect() {
+ io_state_ = STATE_CONNECT_COMPLETE;
+
+ session_ = factory_->CreateSession(host_port_proxy_pair_, is_https_,
+ cert_verifier_, address_list_, net_log_);
+ session_->StartReading();
+ int rv = session_->CryptoConnect(
+ base::Bind(&QuicStreamFactory::Job::OnIOComplete,
+ base::Unretained(this)));
+ return rv;
+}
+
+int QuicStreamFactory::Job::DoConnectComplete(int rv) {
+ if (rv != OK)
+ return rv;
+
+ DCHECK(!factory_->HasActiveSession(host_port_proxy_pair_));
+ factory_->ActivateSession(host_port_proxy_pair_, session_);
+
+ return OK;
+}
+
+QuicStreamFactory::QuicStreamFactory(
+ HostResolver* host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory,
+ QuicRandom* random_generator,
+ QuicClock* clock)
+ : host_resolver_(host_resolver),
+ client_socket_factory_(client_socket_factory),
+ quic_crypto_client_stream_factory_(quic_crypto_client_stream_factory),
+ random_generator_(random_generator),
+ clock_(clock),
+ weak_factory_(this) {
+ config_.SetDefaults();
+ config_.set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromSeconds(30),
+ QuicTime::Delta::FromSeconds(30));
+}
+
+QuicStreamFactory::~QuicStreamFactory() {
+ STLDeleteElements(&all_sessions_);
+ STLDeleteValues(&active_jobs_);
+ STLDeleteValues(&all_crypto_configs_);
+}
+
+int QuicStreamFactory::Create(const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log,
+ QuicStreamRequest* request) {
+ if (HasActiveSession(host_port_proxy_pair)) {
+ request->set_stream(CreateIfSessionExists(host_port_proxy_pair, net_log));
+ return OK;
+ }
+
+ if (HasActiveJob(host_port_proxy_pair)) {
+ Job* job = active_jobs_[host_port_proxy_pair];
+ active_requests_[request] = job;
+ job_requests_map_[job].insert(request);
+ return ERR_IO_PENDING;
+ }
+
+ scoped_ptr<Job> job(new Job(this, host_resolver_, host_port_proxy_pair,
+ is_https, cert_verifier, net_log));
+ int rv = job->Run(base::Bind(&QuicStreamFactory::OnJobComplete,
+ base::Unretained(this), job.get()));
+
+ if (rv == ERR_IO_PENDING) {
+ active_requests_[request] = job.get();
+ job_requests_map_[job.get()].insert(request);
+ active_jobs_[host_port_proxy_pair] = job.release();
+ }
+ if (rv == OK) {
+ DCHECK(HasActiveSession(host_port_proxy_pair));
+ request->set_stream(CreateIfSessionExists(host_port_proxy_pair, net_log));
+ }
+ return rv;
+}
+
+void QuicStreamFactory::OnJobComplete(Job* job, int rv) {
+ if (rv == OK) {
+ // Create all the streams, but do not notify them yet.
+ for (RequestSet::iterator it = job_requests_map_[job].begin();
+ it != job_requests_map_[job].end() ; ++it) {
+ DCHECK(HasActiveSession(job->host_port_proxy_pair()));
+ (*it)->set_stream(CreateIfSessionExists(job->host_port_proxy_pair(),
+ (*it)->net_log()));
+ }
+ }
+ while (!job_requests_map_[job].empty()) {
+ RequestSet::iterator it = job_requests_map_[job].begin();
+ QuicStreamRequest* request = *it;
+ job_requests_map_[job].erase(it);
+ active_requests_.erase(request);
+ // Even though we're invoking callbacks here, we don't need to worry
+ // about |this| being deleted, because the factory is owned by the
+ // profile which can not be deleted via callbacks.
+ request->OnRequestComplete(rv);
+ }
+ active_jobs_.erase(job->host_port_proxy_pair());
+ job_requests_map_.erase(job);
+ delete job;
+ return;
+}
+
+// Returns a newly created QuicHttpStream owned by the caller, if a
+// matching session already exists. Returns NULL otherwise.
+scoped_ptr<QuicHttpStream> QuicStreamFactory::CreateIfSessionExists(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log) {
+ if (!HasActiveSession(host_port_proxy_pair)) {
+ DLOG(INFO) << "No active session";
+ return scoped_ptr<QuicHttpStream>();
+ }
+
+ QuicClientSession* session = active_sessions_[host_port_proxy_pair];
+ DCHECK(session);
+ return scoped_ptr<QuicHttpStream>(new QuicHttpStream(session->GetWeakPtr()));
+}
+
+void QuicStreamFactory::OnIdleSession(QuicClientSession* session) {
+}
+
+void QuicStreamFactory::OnSessionClose(QuicClientSession* session) {
+ DCHECK_EQ(0u, session->GetNumOpenStreams());
+ const AliasSet& aliases = session_aliases_[session];
+ for (AliasSet::const_iterator it = aliases.begin(); it != aliases.end();
+ ++it) {
+ DCHECK(active_sessions_.count(*it));
+ DCHECK_EQ(session, active_sessions_[*it]);
+ active_sessions_.erase(*it);
+ }
+ all_sessions_.erase(session);
+ session_aliases_.erase(session);
+ delete session;
+}
+
+void QuicStreamFactory::CancelRequest(QuicStreamRequest* request) {
+ DCHECK(ContainsKey(active_requests_, request));
+ Job* job = active_requests_[request];
+ job_requests_map_[job].erase(request);
+ active_requests_.erase(request);
+}
+
+void QuicStreamFactory::CloseAllSessions(int error) {
+ while (!active_sessions_.empty()) {
+ size_t initial_size = active_sessions_.size();
+ active_sessions_.begin()->second->CloseSessionOnError(error);
+ DCHECK_NE(initial_size, active_sessions_.size());
+ }
+ while (!all_sessions_.empty()) {
+ size_t initial_size = all_sessions_.size();
+ (*all_sessions_.begin())->CloseSessionOnError(error);
+ DCHECK_NE(initial_size, all_sessions_.size());
+ }
+ DCHECK(all_sessions_.empty());
+}
+
+base::Value* QuicStreamFactory::QuicStreamFactoryInfoToValue() const {
+ base::ListValue* list = new base::ListValue();
+
+ for (SessionMap::const_iterator it = active_sessions_.begin();
+ it != active_sessions_.end(); ++it) {
+ const HostPortProxyPair& pair = it->first;
+ const QuicClientSession* session = it->second;
+
+ list->Append(session->GetInfoAsValue(pair.first));
+ }
+ return list;
+}
+
+void QuicStreamFactory::OnIPAddressChanged() {
+ CloseAllSessions(ERR_NETWORK_CHANGED);
+}
+
+bool QuicStreamFactory::HasActiveSession(
+ const HostPortProxyPair& host_port_proxy_pair) {
+ return ContainsKey(active_sessions_, host_port_proxy_pair);
+}
+
+QuicClientSession* QuicStreamFactory::CreateSession(
+ const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const AddressList& address_list,
+ const BoundNetLog& net_log) {
+ QuicGuid guid = random_generator_->RandUint64();
+ IPEndPoint addr = *address_list.begin();
+ scoped_ptr<DatagramClientSocket> socket(
+ client_socket_factory_->CreateDatagramClientSocket(
+ DatagramSocket::DEFAULT_BIND, base::Bind(&base::RandInt),
+ net_log.net_log(), net_log.source()));
+ socket->Connect(addr);
+
+ // We should adaptively set this buffer size, but for now, we'll use a size
+ // that is more than large enough for a 100 packet congestion window, and yet
+ // does not consume "too much" memory. If we see bursty packet loss, we may
+ // revisit this setting and test for its impact.
+ const int32 kSocketBufferSize(kMaxPacketSize * 100); // Support 100 packets.
+ socket->SetReceiveBufferSize(kSocketBufferSize);
+ // TODO(jar): What should the UDP send buffer be set to? If the send buffer
+ // is too large, then we might(?) wastefully queue packets in the OS, when
+ // we'd rather construct packets just in time. We do however expect that the
+ // calculated send rate (paced, or ack clocked), will be well below the egress
+ // rate of the local machine, so that *shouldn't* be a problem.
+ // If the buffer setting is too small, then we will starve our outgoing link
+ // on a fast connection, because we won't respond fast enough to the many
+ // async callbacks to get data from us. On the other hand, until we have real
+ // pacing support (beyond ack-clocked pacing), we get a bit of adhoc-pacing by
+ // requiring the application to refill this OS buffer (ensuring that we don't
+ // blast a pile of packets at the kernel's max egress rate).
+ // socket->SetSendBufferSize(????);
+
+ QuicConnectionHelper* helper = new QuicConnectionHelper(
+ base::MessageLoop::current()->message_loop_proxy().get(),
+ clock_.get(),
+ random_generator_,
+ socket.get());
+
+ QuicConnection* connection = new QuicConnection(guid, addr, helper, false,
+ QuicVersionMax());
+
+ QuicCryptoClientConfig* crypto_config =
+ GetOrCreateCryptoConfig(host_port_proxy_pair);
+ DCHECK(crypto_config);
+
+ QuicClientSession* session =
+ new QuicClientSession(connection, socket.Pass(), this,
+ quic_crypto_client_stream_factory_,
+ host_port_proxy_pair.first.host(), config_,
+ crypto_config, net_log.net_log());
+ all_sessions_.insert(session); // owning pointer
+ if (is_https) {
+ crypto_config->SetProofVerifier(
+ new ProofVerifierChromium(cert_verifier, net_log));
+ }
+ return session;
+}
+
+bool QuicStreamFactory::HasActiveJob(
+ const HostPortProxyPair& host_port_proxy_pair) {
+ return ContainsKey(active_jobs_, host_port_proxy_pair);
+}
+
+void QuicStreamFactory::ActivateSession(
+ const HostPortProxyPair& host_port_proxy_pair,
+ QuicClientSession* session) {
+ DCHECK(!HasActiveSession(host_port_proxy_pair));
+ active_sessions_[host_port_proxy_pair] = session;
+ session_aliases_[session].insert(host_port_proxy_pair);
+}
+
+QuicCryptoClientConfig* QuicStreamFactory::GetOrCreateCryptoConfig(
+ const HostPortProxyPair& host_port_proxy_pair) {
+ QuicCryptoClientConfig* crypto_config;
+ if (ContainsKey(all_crypto_configs_, host_port_proxy_pair)) {
+ crypto_config = all_crypto_configs_[host_port_proxy_pair];
+ DCHECK(crypto_config);
+ } else {
+ crypto_config = new QuicCryptoClientConfig();
+ crypto_config->SetDefaults();
+ all_crypto_configs_[host_port_proxy_pair] = crypto_config;
+ }
+ return crypto_config;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_stream_factory.h b/chromium/net/quic/quic_stream_factory.h
new file mode 100644
index 00000000000..963a60369ac
--- /dev/null
+++ b/chromium/net/quic/quic_stream_factory.h
@@ -0,0 +1,183 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_STREAM_FACTORY_H_
+#define NET_QUIC_QUIC_STREAM_FACTORY_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/proxy/proxy_server.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CertVerifier;
+class ClientSocketFactory;
+class HostResolver;
+class QuicClock;
+class QuicClientSession;
+class QuicCryptoClientStreamFactory;
+class QuicRandom;
+class QuicStreamFactory;
+
+// Encapsulates a pending request for a QuicHttpStream.
+// If the request is still pending when it is destroyed, it will
+// cancel the request with the factory.
+class NET_EXPORT_PRIVATE QuicStreamRequest {
+ public:
+ explicit QuicStreamRequest(QuicStreamFactory* factory);
+ ~QuicStreamRequest();
+
+ // For http, |is_https| is false and |cert_verifier| can be null.
+ int Request(const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback);
+
+ void OnRequestComplete(int rv);
+
+ scoped_ptr<QuicHttpStream> ReleaseStream();
+
+ void set_stream(scoped_ptr<QuicHttpStream> stream);
+
+ const BoundNetLog& net_log() const{
+ return net_log_;
+ }
+
+ private:
+ QuicStreamFactory* factory_;
+ HostPortProxyPair host_port_proxy_pair_;
+ bool is_https_;
+ CertVerifier* cert_verifier_;
+ BoundNetLog net_log_;
+ CompletionCallback callback_;
+ scoped_ptr<QuicHttpStream> stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicStreamRequest);
+};
+
+// A factory for creating new QuicHttpStreams on top of a pool of
+// QuicClientSessions.
+class NET_EXPORT_PRIVATE QuicStreamFactory
+ : public NetworkChangeNotifier::IPAddressObserver {
+ public:
+ QuicStreamFactory(
+ HostResolver* host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory,
+ QuicRandom* random_generator,
+ QuicClock* clock);
+ virtual ~QuicStreamFactory();
+
+ // Creates a new QuicHttpStream to |host_port_proxy_pair| which will be
+ // owned by |request|. |is_https| specifies if the protocol is https or not.
+ // |cert_verifier| is used by ProofVerifier for verifying the certificate
+ // chain and signature. For http, this can be null. If a matching session
+ // already exists, this method will return OK. If no matching session exists,
+ // this will return ERR_IO_PENDING and will invoke OnRequestComplete
+ // asynchronously.
+ int Create(const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const BoundNetLog& net_log,
+ QuicStreamRequest* request);
+
+ // Returns a newly created QuicHttpStream owned by the caller, if a
+ // matching session already exists. Returns NULL otherwise.
+ scoped_ptr<QuicHttpStream> CreateIfSessionExists(
+ const HostPortProxyPair& host_port_proxy_pair,
+ const BoundNetLog& net_log);
+
+ // Called by a session when it becomes idle.
+ void OnIdleSession(QuicClientSession* session);
+
+ // Called by a session after it shuts down.
+ void OnSessionClose(QuicClientSession* session);
+
+ // Cancels a pending request.
+ void CancelRequest(QuicStreamRequest* request);
+
+ // Closes all current sessions.
+ void CloseAllSessions(int error);
+
+ base::Value* QuicStreamFactoryInfoToValue() const;
+
+ // NetworkChangeNotifier::IPAddressObserver methods:
+
+ // Until the servers support roaming, close all connections when the local
+ // IP address changes.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ private:
+ class Job;
+
+ typedef std::map<HostPortProxyPair, QuicClientSession*> SessionMap;
+ typedef std::set<HostPortProxyPair> AliasSet;
+ typedef std::map<QuicClientSession*, AliasSet> SessionAliasMap;
+ typedef std::set<QuicClientSession*> SessionSet;
+ typedef std::map<HostPortProxyPair, QuicCryptoClientConfig*> CryptoConfigMap;
+ typedef std::map<HostPortProxyPair, Job*> JobMap;
+ typedef std::map<QuicStreamRequest*, Job*> RequestMap;
+ typedef std::set<QuicStreamRequest*> RequestSet;
+ typedef std::map<Job*, RequestSet> JobRequestsMap;
+
+ void OnJobComplete(Job* job, int rv);
+ bool HasActiveSession(const HostPortProxyPair& host_port_proxy_pair);
+ bool HasActiveJob(const HostPortProxyPair& host_port_proxy_pair);
+ QuicClientSession* CreateSession(
+ const HostPortProxyPair& host_port_proxy_pair,
+ bool is_https,
+ CertVerifier* cert_verifier,
+ const AddressList& address_list,
+ const BoundNetLog& net_log);
+ void ActivateSession(const HostPortProxyPair& host_port_proxy_pair,
+ QuicClientSession* session);
+
+ QuicCryptoClientConfig* GetOrCreateCryptoConfig(
+ const HostPortProxyPair& host_port_proxy_pair);
+
+ HostResolver* host_resolver_;
+ ClientSocketFactory* client_socket_factory_;
+ QuicCryptoClientStreamFactory* quic_crypto_client_stream_factory_;
+ QuicRandom* random_generator_;
+ scoped_ptr<QuicClock> clock_;
+
+ // Contains owning pointers to all sessions that currently exist.
+ SessionSet all_sessions_;
+ // Contains non-owning pointers to currently active session
+ // (not going away session, once they're implemented).
+ SessionMap active_sessions_;
+ SessionAliasMap session_aliases_;
+
+ // Contains owning pointers to QuicCryptoClientConfig. QuicCryptoClientConfig
+ // contains configuration and cached state about servers.
+ // TODO(rtenneti): Persist all_crypto_configs_ to disk and decide when to
+ // clear the data in the map.
+ CryptoConfigMap all_crypto_configs_;
+
+ QuicConfig config_;
+
+ JobMap active_jobs_;
+ JobRequestsMap job_requests_map_;
+ RequestMap active_requests_;
+
+ base::WeakPtrFactory<QuicStreamFactory> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicStreamFactory);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_STREAM_FACTORY_H_
diff --git a/chromium/net/quic/quic_stream_factory_test.cc b/chromium/net/quic/quic_stream_factory_test.cc
new file mode 100644
index 00000000000..2f46772d0fc
--- /dev/null
+++ b/chromium/net/quic/quic_stream_factory_test.cc
@@ -0,0 +1,365 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_stream_factory.h"
+
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_http_stream.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_crypto_client_stream_factory.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicStreamFactoryTest : public ::testing::Test {
+ protected:
+ QuicStreamFactoryTest()
+ : clock_(new MockClock()),
+ factory_(&host_resolver_, &socket_factory_,
+ &crypto_client_stream_factory_,
+ &random_generator_, clock_),
+ host_port_proxy_pair_(HostPortPair("www.google.com", 443),
+ ProxyServer::Direct()),
+ is_https_(false),
+ cert_verifier_(CertVerifier::CreateDefault()) {
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructRstPacket(
+ QuicPacketSequenceNumber num,
+ QuicStreamId stream_id) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = true;
+ header.packet_sequence_number = num;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicRstStreamFrame rst(stream_id, QUIC_STREAM_NO_ERROR);
+ return scoped_ptr<QuicEncryptedPacket>(
+ ConstructPacket(header, QuicFrame(&rst)));
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructAckPacket(
+ QuicPacketSequenceNumber largest_received,
+ QuicPacketSequenceNumber least_unacked) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.packet_sequence_number = 2;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicAckFrame ack(largest_received, QuicTime::Zero(), least_unacked);
+ QuicCongestionFeedbackFrame feedback;
+ feedback.type = kTCP;
+ feedback.tcp.accumulated_number_of_lost_packets = 0;
+ feedback.tcp.receive_window = 16000;
+
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), false);
+ QuicFrames frames;
+ frames.push_back(QuicFrame(&ack));
+ frames.push_back(QuicFrame(&feedback));
+ scoped_ptr<QuicPacket> packet(
+ framer.BuildUnsizedDataPacket(header, frames).packet);
+ return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+ ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+ }
+
+ // Returns a newly created packet to send congestion feedback data.
+ scoped_ptr<QuicEncryptedPacket> ConstructFeedbackPacket(
+ QuicPacketSequenceNumber sequence_number) {
+ QuicPacketHeader header;
+ header.public_header.guid = 0xDEADBEEF;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = false;
+ header.packet_sequence_number = sequence_number;
+ header.entropy_flag = false;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicCongestionFeedbackFrame frame;
+ frame.type = kTCP;
+ frame.tcp.accumulated_number_of_lost_packets = 0;
+ frame.tcp.receive_window = 16000;
+
+ return scoped_ptr<QuicEncryptedPacket>(
+ ConstructPacket(header, QuicFrame(&frame)));
+ }
+
+ scoped_ptr<QuicEncryptedPacket> ConstructPacket(
+ const QuicPacketHeader& header,
+ const QuicFrame& frame) {
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), false);
+ QuicFrames frames;
+ frames.push_back(frame);
+ scoped_ptr<QuicPacket> packet(
+ framer.BuildUnsizedDataPacket(header, frames).packet);
+ return scoped_ptr<QuicEncryptedPacket>(framer.EncryptPacket(
+ ENCRYPTION_NONE, header.packet_sequence_number, *packet));
+ }
+
+ MockHostResolver host_resolver_;
+ DeterministicMockClientSocketFactory socket_factory_;
+ MockCryptoClientStreamFactory crypto_client_stream_factory_;
+ MockRandom random_generator_;
+ MockClock* clock_; // Owned by factory_.
+ QuicStreamFactory factory_;
+ HostPortProxyPair host_port_proxy_pair_;
+ bool is_https_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ BoundNetLog net_log_;
+ TestCompletionCallback callback_;
+};
+
+TEST_F(QuicStreamFactoryTest, CreateIfSessionExists) {
+ EXPECT_EQ(NULL, factory_.CreateIfSessionExists(host_port_proxy_pair_,
+ net_log_).get());
+}
+
+TEST_F(QuicStreamFactoryTest, Create) {
+ MockRead reads[] = {
+ MockRead(ASYNC, OK, 0) // EOF
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+ socket_data.StopAfter(1);
+
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream.get());
+
+ // Will reset stream 3.
+ stream = factory_.CreateIfSessionExists(host_port_proxy_pair_, net_log_);
+ EXPECT_TRUE(stream.get());
+
+ // TODO(rtenneti): We should probably have a tests that HTTP and HTTPS result
+ // in streams on different sessions.
+ QuicStreamRequest request2(&factory_);
+ EXPECT_EQ(OK, request2.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+ stream = request2.ReleaseStream(); // Will reset stream 5.
+ stream.reset(); // Will reset stream 7.
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_F(QuicStreamFactoryTest, MaxOpenStream) {
+ MockRead reads[] = {
+ MockRead(ASYNC, OK, 0) // EOF
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+ socket_data.StopAfter(1);
+
+ HttpRequestInfo request_info;
+ std::vector<QuicHttpStream*> streams;
+ // The MockCryptoClientStream sets max_open_streams to be
+ // 2 * kDefaultMaxStreamsPerConnection.
+ for (size_t i = 0; i < 2 * kDefaultMaxStreamsPerConnection; i++) {
+ QuicStreamRequest request(&factory_);
+ int rv = request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback());
+ if (i == 0) {
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ } else {
+ EXPECT_EQ(OK, rv);
+ }
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream);
+ EXPECT_EQ(OK, stream->InitializeStream(
+ &request_info, DEFAULT_PRIORITY, net_log_, CompletionCallback()));
+ streams.push_back(stream.release());
+ }
+
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(OK, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ CompletionCallback()));
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ EXPECT_TRUE(stream);
+ EXPECT_EQ(ERR_IO_PENDING, stream->InitializeStream(
+ &request_info, DEFAULT_PRIORITY, net_log_, callback_.callback()));
+
+ // Close the first stream.
+ streams.front()->Close(false);
+
+ ASSERT_TRUE(callback_.have_result());
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+ STLDeleteElements(&streams);
+}
+
+TEST_F(QuicStreamFactoryTest, CreateError) {
+ DeterministicSocketData socket_data(NULL, 0, NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ host_resolver_.rules()->AddSimulatedFailure("www.google.com");
+
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, callback_.WaitForResult());
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_F(QuicStreamFactoryTest, CancelCreate) {
+ MockRead reads[] = {
+ MockRead(ASYNC, OK, 0) // EOF
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+ {
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+ }
+
+ socket_data.StopAfter(1);
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+
+ scoped_ptr<QuicHttpStream> stream(
+ factory_.CreateIfSessionExists(host_port_proxy_pair_, net_log_));
+ EXPECT_TRUE(stream.get());
+ stream.reset();
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+}
+
+TEST_F(QuicStreamFactoryTest, CloseAllSessions) {
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+ socket_data.StopAfter(1);
+
+ MockRead reads2[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData socket_data2(reads2, arraysize(reads2), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data2);
+ socket_data2.StopAfter(1);
+
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+ DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Close the session and verify that stream saw the error.
+ factory_.CloseAllSessions(ERR_INTERNET_DISCONNECTED);
+ EXPECT_EQ(ERR_INTERNET_DISCONNECTED,
+ stream->ReadResponseHeaders(callback_.callback()));
+
+ // Now attempting to request a stream to the same origin should create
+ // a new session.
+
+ QuicStreamRequest request2(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request2.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ stream = request2.ReleaseStream();
+ stream.reset(); // Will reset stream 3.
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+ EXPECT_TRUE(socket_data2.at_read_eof());
+ EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+TEST_F(QuicStreamFactoryTest, OnIPAddressChanged) {
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData socket_data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+ socket_data.StopAfter(1);
+
+ MockRead reads2[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData socket_data2(reads2, arraysize(reads2), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&socket_data2);
+ socket_data2.StopAfter(1);
+
+ QuicStreamRequest request(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ scoped_ptr<QuicHttpStream> stream = request.ReleaseStream();
+ HttpRequestInfo request_info;
+ EXPECT_EQ(OK, stream->InitializeStream(&request_info,
+ DEFAULT_PRIORITY,
+ net_log_, CompletionCallback()));
+
+ // Change the IP address and verify that stream saw the error.
+ factory_.OnIPAddressChanged();
+ EXPECT_EQ(ERR_NETWORK_CHANGED,
+ stream->ReadResponseHeaders(callback_.callback()));
+
+ // Now attempting to request a stream to the same origin should create
+ // a new session.
+
+ QuicStreamRequest request2(&factory_);
+ EXPECT_EQ(ERR_IO_PENDING, request2.Request(host_port_proxy_pair_, is_https_,
+ cert_verifier_.get(), net_log_,
+ callback_.callback()));
+
+ EXPECT_EQ(OK, callback_.WaitForResult());
+ stream = request2.ReleaseStream();
+ stream.reset(); // Will reset stream 3.
+
+ EXPECT_TRUE(socket_data.at_read_eof());
+ EXPECT_TRUE(socket_data.at_write_eof());
+ EXPECT_TRUE(socket_data2.at_read_eof());
+ EXPECT_TRUE(socket_data2.at_write_eof());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_stream_sequencer.cc b/chromium/net/quic/quic_stream_sequencer.cc
new file mode 100644
index 00000000000..7cf67d351e3
--- /dev/null
+++ b/chromium/net/quic/quic_stream_sequencer.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_stream_sequencer.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/logging.h"
+#include "net/quic/reliable_quic_stream.h"
+
+using std::min;
+using std::numeric_limits;
+
+namespace net {
+
+QuicStreamSequencer::QuicStreamSequencer(ReliableQuicStream* quic_stream)
+ : stream_(quic_stream),
+ num_bytes_consumed_(0),
+ max_frame_memory_(numeric_limits<size_t>::max()),
+ close_offset_(numeric_limits<QuicStreamOffset>::max()) {
+}
+
+QuicStreamSequencer::QuicStreamSequencer(size_t max_frame_memory,
+ ReliableQuicStream* quic_stream)
+ : stream_(quic_stream),
+ num_bytes_consumed_(0),
+ max_frame_memory_(max_frame_memory),
+ close_offset_(numeric_limits<QuicStreamOffset>::max()) {
+ if (max_frame_memory < kMaxPacketSize) {
+ LOG(DFATAL) << "Setting max frame memory to " << max_frame_memory
+ << ". Some frames will be impossible to handle.";
+ }
+}
+
+QuicStreamSequencer::~QuicStreamSequencer() {
+}
+
+bool QuicStreamSequencer::WillAcceptStreamFrame(
+ const QuicStreamFrame& frame) const {
+ size_t data_len = frame.data.size();
+ DCHECK_LE(data_len, max_frame_memory_);
+
+ if (IsDuplicate(frame)) {
+ return true;
+ }
+ QuicStreamOffset byte_offset = frame.offset;
+ if (data_len > max_frame_memory_) {
+ // We're never going to buffer this frame and we can't pass it up.
+ // The stream might only consume part of it and we'd need a partial ack.
+ //
+ // Ideally this should never happen, as we check that
+ // max_frame_memory_ > kMaxPacketSize and lower levels should reject
+ // frames larger than that.
+ return false;
+ }
+ if (byte_offset + data_len - num_bytes_consumed_ > max_frame_memory_) {
+ // We can buffer this but not right now. Toss it.
+ // It might be worth trying an experiment where we try best-effort buffering
+ return false;
+ }
+ return true;
+}
+
+bool QuicStreamSequencer::OnStreamFrame(const QuicStreamFrame& frame) {
+ if (!WillAcceptStreamFrame(frame)) {
+ // This should not happen, as WillAcceptFrame should be called before
+ // OnStreamFrame. Error handling should be done by the caller.
+ return false;
+ }
+ if (IsDuplicate(frame)) {
+ // Silently ignore duplicates.
+ return true;
+ }
+
+ if (frame.fin) {
+ CloseStreamAtOffset(frame.offset + frame.data.size());
+ }
+
+ QuicStreamOffset byte_offset = frame.offset;
+ const char* data = frame.data.data();
+ size_t data_len = frame.data.size();
+
+ if (data_len == 0) {
+ // TODO(rch): Close the stream if there was no data and no fin.
+ return true;
+ }
+
+ if (byte_offset == num_bytes_consumed_) {
+ DVLOG(1) << "Processing byte offset " << byte_offset;
+ size_t bytes_consumed = stream_->ProcessRawData(data, data_len);
+ num_bytes_consumed_ += bytes_consumed;
+
+ if (MaybeCloseStream()) {
+ return true;
+ }
+ if (bytes_consumed > data_len) {
+ stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM);
+ return false;
+ } else if (bytes_consumed == data_len) {
+ FlushBufferedFrames();
+ return true; // it's safe to ack this frame.
+ } else {
+ // Set ourselves up to buffer what's left
+ data_len -= bytes_consumed;
+ data += bytes_consumed;
+ byte_offset += bytes_consumed;
+ }
+ }
+ DVLOG(1) << "Buffering packet at offset " << byte_offset;
+ frames_.insert(make_pair(byte_offset, string(data, data_len)));
+ return true;
+}
+
+void QuicStreamSequencer::CloseStreamAtOffset(QuicStreamOffset offset) {
+ const QuicStreamOffset kMaxOffset = numeric_limits<QuicStreamOffset>::max();
+
+ // If we have a scheduled termination or close, any new offset should match
+ // it.
+ if (close_offset_ != kMaxOffset && offset != close_offset_) {
+ stream_->Close(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+ return;
+ }
+
+ close_offset_ = offset;
+
+ MaybeCloseStream();
+}
+
+bool QuicStreamSequencer::MaybeCloseStream() {
+ if (IsHalfClosed()) {
+ DVLOG(1) << "Passing up termination, as we've processed "
+ << num_bytes_consumed_ << " of " << close_offset_
+ << " bytes.";
+ // Technically it's an error if num_bytes_consumed isn't exactly
+ // equal, but error handling seems silly at this point.
+ stream_->TerminateFromPeer(true);
+ return true;
+ }
+ return false;
+}
+
+int QuicStreamSequencer::GetReadableRegions(iovec* iov, size_t iov_len) {
+ FrameMap::iterator it = frames_.begin();
+ size_t index = 0;
+ QuicStreamOffset offset = num_bytes_consumed_;
+ while (it != frames_.end() && index < iov_len) {
+ if (it->first != offset) return index;
+
+ iov[index].iov_base = static_cast<void*>(
+ const_cast<char*>(it->second.data()));
+ iov[index].iov_len = it->second.size();
+ offset += it->second.size();
+
+ ++index;
+ ++it;
+ }
+ return index;
+}
+
+int QuicStreamSequencer::Readv(const struct iovec* iov, size_t iov_len) {
+ FrameMap::iterator it = frames_.begin();
+ size_t iov_index = 0;
+ size_t iov_offset = 0;
+ size_t frame_offset = 0;
+ size_t initial_bytes_consumed = num_bytes_consumed_;
+
+ while (iov_index < iov_len &&
+ it != frames_.end() &&
+ it->first == num_bytes_consumed_) {
+ int bytes_to_read = min(iov[iov_index].iov_len - iov_offset,
+ it->second.size() - frame_offset);
+
+ char* iov_ptr = static_cast<char*>(iov[iov_index].iov_base) + iov_offset;
+ memcpy(iov_ptr,
+ it->second.data() + frame_offset, bytes_to_read);
+ frame_offset += bytes_to_read;
+ iov_offset += bytes_to_read;
+
+ if (iov[iov_index].iov_len == iov_offset) {
+ // We've filled this buffer.
+ iov_offset = 0;
+ ++iov_index;
+ }
+ if (it->second.size() == frame_offset) {
+ // We've copied this whole frame
+ num_bytes_consumed_ += it->second.size();
+ frames_.erase(it);
+ it = frames_.begin();
+ frame_offset = 0;
+ }
+ }
+ // We've finished copying. If we have a partial frame, update it.
+ if (frame_offset != 0) {
+ frames_.insert(make_pair(it->first + frame_offset,
+ it->second.substr(frame_offset)));
+ frames_.erase(frames_.begin());
+ num_bytes_consumed_ += frame_offset;
+ }
+ return num_bytes_consumed_ - initial_bytes_consumed;
+}
+
+void QuicStreamSequencer::MarkConsumed(size_t num_bytes_consumed) {
+ size_t end_offset = num_bytes_consumed_ + num_bytes_consumed;
+ while (!frames_.empty() && end_offset != num_bytes_consumed_) {
+ FrameMap::iterator it = frames_.begin();
+ if (it->first != num_bytes_consumed_) {
+ LOG(DFATAL) << "Invalid argument to MarkConsumed. "
+ << " num_bytes_consumed_: " << num_bytes_consumed_
+ << " end_offset: " << end_offset
+ << " offset: " << it->first
+ << " length: " << it->second.length();
+ stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM);
+ return;
+ }
+
+ if (it->first + it->second.length() <= end_offset) {
+ num_bytes_consumed_ += it->second.length();
+ // This chunk is entirely consumed.
+ frames_.erase(it);
+ continue;
+ }
+
+ // Partially consume this frame.
+ size_t delta = end_offset - it->first;
+ num_bytes_consumed_ += delta;
+ frames_.insert(make_pair(end_offset, it->second.substr(delta)));
+ frames_.erase(it);
+ break;
+ }
+}
+
+bool QuicStreamSequencer::HasBytesToRead() const {
+ FrameMap::const_iterator it = frames_.begin();
+
+ return it != frames_.end() && it->first == num_bytes_consumed_;
+}
+
+bool QuicStreamSequencer::IsHalfClosed() const {
+ return num_bytes_consumed_ >= close_offset_;
+}
+
+bool QuicStreamSequencer::IsDuplicate(const QuicStreamFrame& frame) const {
+ // A frame is duplicate if the frame offset is smaller than our bytes consumed
+ // or we have stored the frame in our map.
+ // TODO(pwestin): Is it possible that a new frame contain more data even if
+ // the offset is the same?
+ return frame.offset < num_bytes_consumed_ ||
+ frames_.find(frame.offset) != frames_.end();
+}
+
+void QuicStreamSequencer::FlushBufferedFrames() {
+ FrameMap::iterator it = frames_.find(num_bytes_consumed_);
+ while (it != frames_.end()) {
+ DVLOG(1) << "Flushing buffered packet at offset " << it->first;
+ string* data = &it->second;
+ size_t bytes_consumed = stream_->ProcessRawData(data->c_str(),
+ data->size());
+ num_bytes_consumed_ += bytes_consumed;
+ if (MaybeCloseStream()) {
+ return;
+ }
+ if (bytes_consumed > data->size()) {
+ stream_->Close(QUIC_SERVER_ERROR_PROCESSING_STREAM); // Programming error
+ return;
+ } else if (bytes_consumed == data->size()) {
+ frames_.erase(it);
+ it = frames_.find(num_bytes_consumed_);
+ } else {
+ string new_data = it->second.substr(bytes_consumed);
+ frames_.erase(it);
+ frames_.insert(make_pair(num_bytes_consumed_, new_data));
+ return;
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_stream_sequencer.h b/chromium/net/quic/quic_stream_sequencer.h
new file mode 100644
index 00000000000..fe9fba56830
--- /dev/null
+++ b/chromium/net/quic/quic_stream_sequencer.h
@@ -0,0 +1,102 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_QUIC_STREAM_SEQUENCER_H_
+#define NET_QUIC_QUIC_STREAM_SEQUENCER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/iovec.h"
+#include "net/quic/quic_protocol.h"
+
+using std::map;
+using std::string;
+
+namespace net {
+
+namespace test {
+class QuicStreamSequencerPeer;
+} // namespace test
+
+class QuicSession;
+class ReliableQuicStream;
+
+// Buffers frames until we have something which can be passed
+// up to the next layer.
+// TOOD(alyssar) add some checks for overflow attempts [1, 256,] [2, 256]
+class NET_EXPORT_PRIVATE QuicStreamSequencer {
+ public:
+ static size_t kMaxUdpPacketSize;
+
+ explicit QuicStreamSequencer(ReliableQuicStream* quic_stream);
+ QuicStreamSequencer(size_t max_frame_memory,
+ ReliableQuicStream* quic_stream);
+
+ virtual ~QuicStreamSequencer();
+
+ // Returns the expected value of OnStreamFrame for this frame.
+ bool WillAcceptStreamFrame(const QuicStreamFrame& frame) const;
+
+ // If the frame is the next one we need in order to process in-order data,
+ // ProcessData will be immediately called on the stream until all buffered
+ // data is processed or the stream fails to consume data. Any unconsumed
+ // data will be buffered.
+ //
+ // If the frame is not the next in line, it will either be buffered, and
+ // this will return true, or it will be rejected and this will return false.
+ bool OnStreamFrame(const QuicStreamFrame& frame);
+
+ // Once data is buffered, it's up to the stream to read it when the stream
+ // can handle more data. The following three functions make that possible.
+
+ // Fills in up to iov_len iovecs with the next readable regions. Returns the
+ // number of iovs used. Non-destructive of the underlying data.
+ int GetReadableRegions(iovec* iov, size_t iov_len);
+
+ // Copies the data into the iov_len buffers provided. Returns the number of
+ // bytes read. Any buffered data no longer in use will be released.
+ int Readv(const struct iovec* iov, size_t iov_len);
+
+ // Consumes |num_bytes| data. Used in conjunction with |GetReadableRegions|
+ // to do zero-copy reads.
+ void MarkConsumed(size_t num_bytes);
+
+ // Returns true if the sequncer has bytes available for reading.
+ bool HasBytesToRead() const;
+
+ // Returns true if the sequencer has delivered a half close.
+ bool IsHalfClosed() const;
+
+ // Returns true if the sequencer has received this frame before.
+ bool IsDuplicate(const QuicStreamFrame& frame) const;
+
+ // Calls |ProcessRawData| on |stream_| for each buffered frame that may
+ // be processed.
+ void FlushBufferedFrames();
+
+ private:
+ friend class test::QuicStreamSequencerPeer;
+
+ // TODO(alyssar) use something better than strings.
+ typedef map<QuicStreamOffset, string> FrameMap;
+
+ // Wait until we've seen 'offset' bytes, and then terminate the stream.
+ void CloseStreamAtOffset(QuicStreamOffset offset);
+
+ bool MaybeCloseStream();
+
+ ReliableQuicStream* stream_; // The stream which owns this sequencer.
+ QuicStreamOffset num_bytes_consumed_; // The last data consumed by the stream
+ FrameMap frames_; // sequence number -> frame
+ size_t max_frame_memory_; // the maximum memory the sequencer can buffer.
+ // The offset, if any, we got a stream termination for. When this many bytes
+ // have been processed, the stream will be half closed.
+ QuicStreamOffset close_offset_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_STREAM_SEQUENCER_H_
diff --git a/chromium/net/quic/quic_stream_sequencer_test.cc b/chromium/net/quic/quic_stream_sequencer_test.cc
new file mode 100644
index 00000000000..0d40db92636
--- /dev/null
+++ b/chromium/net/quic/quic_stream_sequencer_test.cc
@@ -0,0 +1,577 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_stream_sequencer.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/rand_util.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::min;
+using std::pair;
+using std::vector;
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Return;
+using testing::StrEq;
+
+namespace net {
+namespace test {
+
+class QuicStreamSequencerPeer : public QuicStreamSequencer {
+ public:
+ explicit QuicStreamSequencerPeer(ReliableQuicStream* stream)
+ : QuicStreamSequencer(stream) {
+ }
+
+ QuicStreamSequencerPeer(int32 max_mem, ReliableQuicStream* stream)
+ : QuicStreamSequencer(max_mem, stream) {
+ }
+
+ virtual bool OnFinFrame(QuicStreamOffset byte_offset,
+ const char* data) {
+ QuicStreamFrame frame;
+ frame.stream_id = 1;
+ frame.offset = byte_offset;
+ frame.data = StringPiece(data);
+ frame.fin = true;
+ return OnStreamFrame(frame);
+ }
+
+ virtual bool OnFrame(QuicStreamOffset byte_offset,
+ const char* data) {
+ QuicStreamFrame frame;
+ frame.stream_id = 1;
+ frame.offset = byte_offset;
+ frame.data = StringPiece(data);
+ frame.fin = false;
+ return OnStreamFrame(frame);
+ }
+
+ void SetMemoryLimit(size_t limit) {
+ max_frame_memory_ = limit;
+ }
+
+ const ReliableQuicStream* stream() const { return stream_; }
+ uint64 num_bytes_consumed() const { return num_bytes_consumed_; }
+ const FrameMap* frames() const { return &frames_; }
+ int32 max_frame_memory() const { return max_frame_memory_; }
+ QuicStreamOffset close_offset() const { return close_offset_; }
+};
+
+class MockStream : public ReliableQuicStream {
+ public:
+ MockStream(QuicSession* session, QuicStreamId id)
+ : ReliableQuicStream(id, session) {
+ }
+
+ MOCK_METHOD1(TerminateFromPeer, void(bool half_close));
+ MOCK_METHOD2(ProcessData, uint32(const char* data, uint32 data_len));
+ MOCK_METHOD1(Close, void(QuicRstStreamErrorCode error));
+ MOCK_METHOD0(OnCanWrite, void());
+};
+
+namespace {
+
+static const char kPayload[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+class QuicStreamSequencerTest : public ::testing::Test {
+ protected:
+ QuicStreamSequencerTest()
+ : session_(NULL),
+ stream_(session_, 1),
+ sequencer_(new QuicStreamSequencerPeer(&stream_)) {
+ }
+
+ bool VerifyReadableRegions(const char** expected, size_t num_expected) {
+ iovec iovecs[5];
+ size_t num_iovecs = sequencer_->GetReadableRegions(iovecs,
+ arraysize(iovecs));
+ return VerifyIovecs(iovecs, num_iovecs, expected, num_expected);
+ }
+
+ bool VerifyIovecs(iovec* iovecs,
+ size_t num_iovecs,
+ const char** expected,
+ size_t num_expected) {
+ if (num_expected != num_iovecs) {
+ LOG(ERROR) << "Incorrect number of iovecs. Expected: "
+ << num_expected << " Actual: " << num_iovecs;
+ return false;
+ }
+ for (size_t i = 0; i < num_expected; ++i) {
+ if (!VerifyIovec(iovecs[i], expected[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool VerifyIovec(const iovec& iovec, StringPiece expected) {
+ if (iovec.iov_len != expected.length()) {
+ LOG(ERROR) << "Invalid length: " << iovec.iov_len
+ << " vs " << expected.length();
+ return false;
+ }
+ if (memcmp(iovec.iov_base, expected.data(), expected.length()) != 0) {
+ LOG(ERROR) << "Invalid data: " << static_cast<char*>(iovec.iov_base)
+ << " vs " << expected.data();
+ return false;
+ }
+ return true;
+ }
+
+ QuicSession* session_;
+ testing::StrictMock<MockStream> stream_;
+ scoped_ptr<QuicStreamSequencerPeer> sequencer_;
+};
+
+TEST_F(QuicStreamSequencerTest, RejectOldFrame) {
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3))
+ .WillOnce(Return(3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+ // Ignore this - it matches a past sequence number and we should not see it
+ // again.
+ EXPECT_TRUE(sequencer_->OnFrame(0, "def"));
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+}
+
+TEST_F(QuicStreamSequencerTest, RejectOverlyLargeFrame) {
+ // TODO(rch): enable when chromium supports EXPECT_DFATAL.
+ /*
+ EXPECT_DFATAL(sequencer_.reset(new QuicStreamSequencerPeer(2, &stream_)),
+ "Setting max frame memory to 2. "
+ "Some frames will be impossible to handle.");
+
+ EXPECT_DEBUG_DEATH(sequencer_->OnFrame(0, "abc"), "");
+ */
+}
+
+TEST_F(QuicStreamSequencerTest, DropFramePastBuffering) {
+ sequencer_->SetMemoryLimit(3);
+
+ EXPECT_FALSE(sequencer_->OnFrame(3, "abc"));
+}
+
+TEST_F(QuicStreamSequencerTest, RejectBufferedFrame) {
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+ // Ignore this - it matches a buffered frame.
+ // Right now there's no checking that the payload is consistent.
+ EXPECT_TRUE(sequencer_->OnFrame(0, "def"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+}
+
+TEST_F(QuicStreamSequencerTest, FullFrameConsumed) {
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, EmptyFrame) {
+ EXPECT_TRUE(sequencer_->OnFrame(0, ""));
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, EmptyFinFrame) {
+ EXPECT_CALL(stream_, TerminateFromPeer(true));
+ EXPECT_TRUE(sequencer_->OnFinFrame(0, ""));
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+}
+
+TEST_F(QuicStreamSequencerTest, PartialFrameConsumed) {
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(2));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(2u, sequencer_->num_bytes_consumed());
+ EXPECT_EQ("c", sequencer_->frames()->find(2)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, NextxFrameNotConsumed) {
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(0));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+ EXPECT_EQ("abc", sequencer_->frames()->find(0)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, FutureFrameNotProcessed) {
+ EXPECT_TRUE(sequencer_->OnFrame(3, "abc"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+ EXPECT_EQ("abc", sequencer_->frames()->find(3)->second);
+}
+
+TEST_F(QuicStreamSequencerTest, OutOfOrderFrameProcessed) {
+ // Buffer the first
+ EXPECT_TRUE(sequencer_->OnFrame(6, "ghi"));
+ EXPECT_EQ(1u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+ // Buffer the second
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_EQ(2u, sequencer_->frames()->size());
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("def"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("ghi"), 3)).WillOnce(Return(3));
+
+ // Ack right away
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(9u, sequencer_->num_bytes_consumed());
+
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+}
+
+TEST_F(QuicStreamSequencerTest, OutOfOrderFramesProcessedWithBuffering) {
+ sequencer_->SetMemoryLimit(9);
+
+ // Too far to buffer.
+ EXPECT_FALSE(sequencer_->OnFrame(9, "jkl"));
+
+ // We can afford to buffer this.
+ EXPECT_TRUE(sequencer_->OnFrame(6, "ghi"));
+ EXPECT_EQ(0u, sequencer_->num_bytes_consumed());
+
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+
+ // Ack right away
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+
+ // We should be willing to buffer this now.
+ EXPECT_TRUE(sequencer_->OnFrame(9, "jkl"));
+ EXPECT_EQ(3u, sequencer_->num_bytes_consumed());
+
+ EXPECT_CALL(stream_, ProcessData(StrEq("def"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("ghi"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("jkl"), 3)).WillOnce(Return(3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_EQ(12u, sequencer_->num_bytes_consumed());
+ EXPECT_EQ(0u, sequencer_->frames()->size());
+}
+
+TEST_F(QuicStreamSequencerTest, OutOfOrderFramesBlockignWithReadv) {
+ sequencer_->SetMemoryLimit(9);
+ char buffer[20];
+ iovec iov[2];
+ iov[0].iov_base = &buffer[0];
+ iov[0].iov_len = 1;
+ iov[1].iov_base = &buffer[1];
+ iov[1].iov_len = 2;
+
+ // Push abc - process.
+ // Push jkl - buffer (not next data)
+ // Push def - don't process.
+ // Push mno - drop (too far out)
+ // Push ghi - buffer (def not processed)
+ // Read 2.
+ // Push mno - buffer (not all read)
+ // Read all
+ // Push pqr - process
+
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("def"), 3)).WillOnce(Return(0));
+ EXPECT_CALL(stream_, ProcessData(StrEq("pqr"), 3)).WillOnce(Return(3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_TRUE(sequencer_->OnFrame(9, "jkl"));
+ EXPECT_FALSE(sequencer_->OnFrame(12, "mno"));
+ EXPECT_TRUE(sequencer_->OnFrame(6, "ghi"));
+
+ // Read 3 bytes.
+ EXPECT_EQ(3, sequencer_->Readv(iov, 2));
+ EXPECT_EQ(0, strncmp(buffer, "def", 3));
+
+ // Now we have space to bufer this.
+ EXPECT_TRUE(sequencer_->OnFrame(12, "mno"));
+
+ // Read the remaining 9 bytes.
+ iov[1].iov_len = 19;
+ EXPECT_EQ(9, sequencer_->Readv(iov, 2));
+ EXPECT_EQ(0, strncmp(buffer, "ghijklmno", 9));
+
+ EXPECT_TRUE(sequencer_->OnFrame(15, "pqr"));
+}
+
+// Same as above, just using a different method for reading.
+TEST_F(QuicStreamSequencerTest, OutOfOrderFramesBlockignWithGetReadableRegion) {
+ sequencer_->SetMemoryLimit(9);
+
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("def"), 3)).WillOnce(Return(0));
+ EXPECT_CALL(stream_, ProcessData(StrEq("pqr"), 3)).WillOnce(Return(3));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_TRUE(sequencer_->OnFrame(9, "jkl"));
+ EXPECT_FALSE(sequencer_->OnFrame(12, "mno"));
+ EXPECT_TRUE(sequencer_->OnFrame(6, "ghi"));
+
+ // Read 3 bytes.
+ const char* expected[] = {"def", "ghi", "jkl"};
+ ASSERT_TRUE(VerifyReadableRegions(expected, arraysize(expected)));
+ char buffer[9];
+ iovec read_iov = { &buffer[0], 3 };
+ ASSERT_EQ(3, sequencer_->Readv(&read_iov, 1));
+
+ // Now we have space to bufer this.
+ EXPECT_TRUE(sequencer_->OnFrame(12, "mno"));
+
+ // Read the remaining 9 bytes.
+ const char* expected2[] = {"ghi", "jkl", "mno"};
+ ASSERT_TRUE(VerifyReadableRegions(expected2, arraysize(expected2)));
+ read_iov.iov_len = 9;
+ ASSERT_EQ(9, sequencer_->Readv(&read_iov, 1));
+
+ EXPECT_TRUE(sequencer_->OnFrame(15, "pqr"));
+}
+
+// Same as above, just using a different method for reading.
+TEST_F(QuicStreamSequencerTest, MarkConsumed) {
+ sequencer_->SetMemoryLimit(9);
+
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(0));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_TRUE(sequencer_->OnFrame(6, "ghi"));
+
+ // Peek into the data.
+ const char* expected[] = {"abc", "def", "ghi"};
+ ASSERT_TRUE(VerifyReadableRegions(expected, arraysize(expected)));
+
+ // Consume 1 byte.
+ sequencer_->MarkConsumed(1);
+ // Verify data.
+ const char* expected2[] = {"bc", "def", "ghi"};
+ ASSERT_TRUE(VerifyReadableRegions(expected2, arraysize(expected2)));
+
+ // Consume 2 bytes.
+ sequencer_->MarkConsumed(2);
+ // Verify data.
+ const char* expected3[] = {"def", "ghi"};
+ ASSERT_TRUE(VerifyReadableRegions(expected3, arraysize(expected3)));
+
+ // Consume 5 bytes.
+ sequencer_->MarkConsumed(5);
+ // Verify data.
+ const char* expected4[] = {"i"};
+ ASSERT_TRUE(VerifyReadableRegions(expected4, arraysize(expected4)));
+}
+
+TEST_F(QuicStreamSequencerTest, MarkConsumedError) {
+ // TODO(rch): enable when chromium supports EXPECT_DFATAL.
+ /*
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(0));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_TRUE(sequencer_->OnFrame(9, "jklmnopqrstuvwxyz"));
+
+ // Peek into the data. Only the first chunk should be readable
+ // because of the missing data.
+ const char* expected[] = {"abc"};
+ ASSERT_TRUE(VerifyReadableRegions(expected, arraysize(expected)));
+
+ // Now, attempt to mark consumed more data than was readable
+ // and expect the stream to be closed.
+ EXPECT_CALL(stream_, Close(QUIC_SERVER_ERROR_PROCESSING_STREAM));
+ EXPECT_DFATAL(sequencer_->MarkConsumed(4),
+ "Invalid argument to MarkConsumed. num_bytes_consumed_: 3 "
+ "end_offset: 4 offset: 9 length: 17");
+ */
+}
+
+TEST_F(QuicStreamSequencerTest, MarkConsumedWithMissingPacket) {
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(0));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ // Missing packet: 6, ghi
+ EXPECT_TRUE(sequencer_->OnFrame(9, "jkl"));
+
+ const char* expected[] = {"abc", "def"};
+ ASSERT_TRUE(VerifyReadableRegions(expected, arraysize(expected)));
+
+ sequencer_->MarkConsumed(6);
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfCloseOrdered) {
+ InSequence s;
+
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, TerminateFromPeer(true));
+ EXPECT_TRUE(sequencer_->OnFinFrame(0, "abc"));
+
+ EXPECT_EQ(3u, sequencer_->close_offset());
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfCloseUnorderedWithFlush) {
+ sequencer_->OnFinFrame(6, "");
+ EXPECT_EQ(6u, sequencer_->close_offset());
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, ProcessData(StrEq("def"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, TerminateFromPeer(true));
+
+ EXPECT_TRUE(sequencer_->OnFrame(3, "def"));
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+}
+
+TEST_F(QuicStreamSequencerTest, BasicHalfUnordered) {
+ sequencer_->OnFinFrame(3, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+ InSequence s;
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(3));
+ EXPECT_CALL(stream_, TerminateFromPeer(true));
+
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+}
+
+TEST_F(QuicStreamSequencerTest, TerminateWithReadv) {
+ char buffer[3];
+
+ sequencer_->OnFinFrame(3, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+
+ EXPECT_FALSE(sequencer_->IsHalfClosed());
+
+ EXPECT_CALL(stream_, ProcessData(StrEq("abc"), 3)).WillOnce(Return(0));
+ EXPECT_TRUE(sequencer_->OnFrame(0, "abc"));
+
+ iovec iov = { &buffer[0], 3 };
+ int bytes_read = sequencer_->Readv(&iov, 1);
+ EXPECT_EQ(3, bytes_read);
+ EXPECT_TRUE(sequencer_->IsHalfClosed());
+}
+
+TEST_F(QuicStreamSequencerTest, MutipleOffsets) {
+ sequencer_->OnFinFrame(3, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+
+ EXPECT_CALL(stream_, Close(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ sequencer_->OnFinFrame(5, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+
+ EXPECT_CALL(stream_, Close(QUIC_MULTIPLE_TERMINATION_OFFSETS));
+ sequencer_->OnFinFrame(1, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+
+ sequencer_->OnFinFrame(3, "");
+ EXPECT_EQ(3u, sequencer_->close_offset());
+}
+
+class QuicSequencerRandomTest : public QuicStreamSequencerTest {
+ public:
+ typedef pair<int, string> Frame;
+ typedef vector<Frame> FrameList;
+
+ void CreateFrames() {
+ int payload_size = arraysize(kPayload) - 1;
+ int remaining_payload = payload_size;
+ while (remaining_payload != 0) {
+ int size = min(OneToN(6), remaining_payload);
+ int index = payload_size - remaining_payload;
+ list_.push_back(make_pair(index, string(kPayload + index, size)));
+ remaining_payload -= size;
+ }
+ }
+
+ QuicSequencerRandomTest() {
+ CreateFrames();
+ }
+
+ int OneToN(int n) {
+ return base::RandInt(1, n);
+ }
+
+ int MaybeProcessMaybeBuffer(const char* data, uint32 len) {
+ int to_process = len;
+ if (base::RandUint64() % 2 != 0) {
+ to_process = base::RandInt(0, len);
+ }
+ output_.append(data, to_process);
+ return to_process;
+ }
+
+ string output_;
+ FrameList list_;
+};
+
+// All frames are processed as soon as we have sequential data.
+// Infinite buffering, so all frames are acked right away.
+TEST_F(QuicSequencerRandomTest, RandomFramesNoDroppingNoBackup) {
+ InSequence s;
+ for (size_t i = 0; i < list_.size(); ++i) {
+ string* data = &list_[i].second;
+ EXPECT_CALL(stream_, ProcessData(StrEq(*data), data->size()))
+ .WillOnce(Return(data->size()));
+ }
+
+ while (list_.size() != 0) {
+ int index = OneToN(list_.size()) - 1;
+ LOG(ERROR) << "Sending index " << index << " "
+ << list_[index].second.data();
+ EXPECT_TRUE(sequencer_->OnFrame(list_[index].first,
+ list_[index].second.data()));
+
+ list_.erase(list_.begin() + index);
+ }
+}
+
+// All frames are processed as soon as we have sequential data.
+// Buffering, so some frames are rejected.
+TEST_F(QuicSequencerRandomTest, RandomFramesDroppingNoBackup) {
+ sequencer_->SetMemoryLimit(26);
+
+ InSequence s;
+ for (size_t i = 0; i < list_.size(); ++i) {
+ string* data = &list_[i].second;
+ EXPECT_CALL(stream_, ProcessData(StrEq(*data), data->size()))
+ .WillOnce(Return(data->size()));
+ }
+
+ while (list_.size() != 0) {
+ int index = OneToN(list_.size()) - 1;
+ LOG(ERROR) << "Sending index " << index << " "
+ << list_[index].second.data();
+ bool acked = sequencer_->OnFrame(list_[index].first,
+ list_[index].second.data());
+
+ if (acked) {
+ list_.erase(list_.begin() + index);
+ }
+ }
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_time.cc b/chromium/net/quic/quic_time.cc
new file mode 100644
index 00000000000..a56775663e4
--- /dev/null
+++ b/chromium/net/quic/quic_time.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_time.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+// Highest number of microseconds that DateTimeOffset can hold.
+const int64 kQuicInfiniteTimeUs = GG_INT64_C(0x7fffffffffffffff) / 10;
+
+QuicTime::Delta::Delta(base::TimeDelta delta)
+ : delta_(delta) {
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::Zero() {
+ return QuicTime::Delta::FromMicroseconds(0);
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::Infinite() {
+ return QuicTime::Delta::FromMicroseconds(kQuicInfiniteTimeUs);
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromSeconds(int64 seconds) {
+ return QuicTime::Delta(base::TimeDelta::FromSeconds(seconds));
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromMilliseconds(int64 ms) {
+ return QuicTime::Delta(base::TimeDelta::FromMilliseconds(ms));
+}
+
+// static
+QuicTime::Delta QuicTime::Delta::FromMicroseconds(int64 us) {
+ return QuicTime::Delta(base::TimeDelta::FromMicroseconds(us));
+}
+
+int64 QuicTime::Delta::ToSeconds() const {
+ return delta_.InSeconds();
+}
+
+int64 QuicTime::Delta::ToMilliseconds() const {
+ return delta_.InMilliseconds();
+}
+
+int64 QuicTime::Delta::ToMicroseconds() const {
+ return delta_.InMicroseconds();
+}
+
+QuicTime::Delta QuicTime::Delta::Add(const Delta& delta) const {
+ return QuicTime::Delta::FromMicroseconds(ToMicroseconds() +
+ delta.ToMicroseconds());
+}
+
+QuicTime::Delta QuicTime::Delta::Subtract(const Delta& delta) const {
+ return QuicTime::Delta::FromMicroseconds(ToMicroseconds() -
+ delta.ToMicroseconds());
+}
+
+bool QuicTime::Delta::IsZero() const {
+ return delta_.InMicroseconds() == 0;
+}
+
+bool QuicTime::Delta::IsInfinite() const {
+ return delta_.InMicroseconds() == kQuicInfiniteTimeUs;
+}
+
+// static
+QuicTime QuicTime::Zero() {
+ return QuicTime(base::TimeTicks());
+}
+
+QuicTime::QuicTime(base::TimeTicks ticks)
+ : ticks_(ticks) {
+}
+
+int64 QuicTime::ToDebuggingValue() const {
+ return (ticks_ - base::TimeTicks()).InMicroseconds();
+}
+
+bool QuicTime::IsInitialized() const {
+ return ticks_ != base::TimeTicks();
+}
+
+QuicTime QuicTime::Add(const Delta& delta) const {
+ return QuicTime(ticks_ + delta.delta_);
+}
+
+QuicTime QuicTime::Subtract(const Delta& delta) const {
+ return QuicTime(ticks_ - delta.delta_);
+}
+
+QuicTime::Delta QuicTime::Subtract(const QuicTime& other) const {
+ return QuicTime::Delta(ticks_ - other.ticks_);
+}
+
+// static
+QuicWallTime QuicWallTime::FromUNIXSeconds(uint64 seconds) {
+ return QuicWallTime(seconds);
+}
+
+// static
+QuicWallTime QuicWallTime::Zero() {
+ return QuicWallTime(0);
+}
+
+uint64 QuicWallTime::ToUNIXSeconds() const {
+ return seconds_;
+}
+
+bool QuicWallTime::IsAfter(QuicWallTime other) const {
+ return seconds_ > other.seconds_;
+}
+
+bool QuicWallTime::IsBefore(QuicWallTime other) const {
+ return seconds_ < other.seconds_;
+}
+
+bool QuicWallTime::IsZero() const {
+ return seconds_ == 0;
+}
+
+QuicTime::Delta QuicWallTime::AbsoluteDifference(QuicWallTime other) const {
+ uint64 d;
+
+ if (seconds_ > other.seconds_) {
+ d = seconds_ - other.seconds_;
+ } else {
+ d = other.seconds_ - seconds_;
+ }
+
+ if (d > static_cast<uint64>(kint64max)) {
+ d = kint64max;
+ }
+ return QuicTime::Delta::FromSeconds(d);
+}
+
+QuicWallTime QuicWallTime::Add(QuicTime::Delta delta) const {
+ uint64 seconds = seconds_ + delta.ToSeconds();
+ if (seconds < seconds_) {
+ seconds = kuint64max;
+ }
+ return QuicWallTime(seconds);
+}
+
+QuicWallTime QuicWallTime::Subtract(QuicTime::Delta delta) const {
+ uint64 seconds = seconds_ - delta.ToSeconds();
+ if (seconds > seconds_) {
+ seconds = 0;
+ }
+ return QuicWallTime(seconds);
+}
+
+QuicWallTime::QuicWallTime(uint64 seconds)
+ : seconds_(seconds) {
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_time.h b/chromium/net/quic/quic_time.h
new file mode 100644
index 00000000000..cdb7f83e81e
--- /dev/null
+++ b/chromium/net/quic/quic_time.h
@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+//
+// QuicTime represents one point in time, stored in microsecond resolution.
+// QuicTime is monotonically increasing, even across system clock adjustments.
+// The epoch (time 0) of QuicTime is unspecified.
+//
+// This implementation wraps the classes base::TimeTicks and base::TimeDelta.
+
+#ifndef NET_QUIC_QUIC_TIME_H_
+#define NET_QUIC_QUIC_TIME_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+static const uint64 kNumMicrosPerSecond = base::Time::kMicrosecondsPerSecond;
+
+// A QuicTime is a purely relative time. QuicTime values from different clocks
+// cannot be compared to each other. If you need an absolute time, see
+// QuicWallTime, below.
+class NET_EXPORT_PRIVATE QuicTime {
+ public:
+ // A QuicTime::Delta represents the signed difference between two points in
+ // time, stored in microsecond resolution.
+ class NET_EXPORT_PRIVATE Delta {
+ public:
+ explicit Delta(base::TimeDelta delta);
+
+ // Create a object with an offset of 0.
+ static Delta Zero();
+
+ // Create a object with infinite offset time.
+ static Delta Infinite();
+
+ // Converts a number of seconds to a time offset.
+ static Delta FromSeconds(int64 secs);
+
+ // Converts a number of milliseconds to a time offset.
+ static Delta FromMilliseconds(int64 ms);
+
+ // Converts a number of microseconds to a time offset.
+ static Delta FromMicroseconds(int64 us);
+
+ // Converts the time offset to a rounded number of seconds.
+ int64 ToSeconds() const;
+
+ // Converts the time offset to a rounded number of milliseconds.
+ int64 ToMilliseconds() const;
+
+ // Converts the time offset to a rounded number of microseconds.
+ int64 ToMicroseconds() const;
+
+ Delta Add(const Delta& delta) const;
+
+ Delta Subtract(const Delta& delta) const;
+
+ bool IsZero() const;
+
+ bool IsInfinite() const;
+
+ private:
+ base::TimeDelta delta_;
+
+ friend class QuicTime;
+ friend class QuicClock;
+ };
+
+ explicit QuicTime(base::TimeTicks ticks);
+
+ // Creates a new QuicTime with an internal value of 0. IsInitialized()
+ // will return false for these times.
+ static QuicTime Zero();
+
+ // Produce the internal value to be used when logging. This value
+ // represents the number of microseconds since some epoch. It may
+ // be the UNIX epoch on some platforms. On others, it may
+ // be a CPU ticks based value.
+ int64 ToDebuggingValue() const;
+
+ bool IsInitialized() const;
+
+ QuicTime Add(const Delta& delta) const;
+
+ QuicTime Subtract(const Delta& delta) const;
+
+ Delta Subtract(const QuicTime& other) const;
+
+ private:
+ friend bool operator==(QuicTime lhs, QuicTime rhs);
+ friend bool operator<(QuicTime lhs, QuicTime rhs);
+
+ friend class QuicClock;
+ friend class QuicClockTest;
+
+ base::TimeTicks ticks_;
+};
+
+// A QuicWallTime represents an absolute time that is globally consistent. It
+// provides, at most, one second granularity and, in practice, clock-skew means
+// that you shouldn't even depend on that.
+class NET_EXPORT_PRIVATE QuicWallTime {
+ public:
+ // FromUNIXSeconds constructs a QuicWallTime from a count of the seconds
+ // since the UNIX epoch.
+ static QuicWallTime FromUNIXSeconds(uint64 seconds);
+
+ // Zero returns a QuicWallTime set to zero. IsZero will return true for this
+ // value.
+ static QuicWallTime Zero();
+
+ // ToUNIXSeconds converts a QuicWallTime into a count of seconds since the
+ // UNIX epoch.
+ uint64 ToUNIXSeconds() const;
+
+ bool IsAfter(QuicWallTime other) const;
+ bool IsBefore(QuicWallTime other) const;
+
+ // IsZero returns true if this object is the result of calling |Zero|.
+ bool IsZero() const;
+
+ // AbsoluteDifference returns the absolute value of the time difference
+ // between |this| and |other|.
+ QuicTime::Delta AbsoluteDifference(QuicWallTime other) const;
+
+ // Add returns a new QuicWallTime that represents the time of |this| plus
+ // |delta|.
+ QuicWallTime Add(QuicTime::Delta delta) const;
+
+ // Subtract returns a new QuicWallTime that represents the time of |this|
+ // minus |delta|.
+ QuicWallTime Subtract(QuicTime::Delta delta) const;
+
+ private:
+ explicit QuicWallTime(uint64 seconds);
+
+ uint64 seconds_;
+};
+
+// Non-member relational operators for QuicTime::Delta.
+inline bool operator==(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return lhs.ToMicroseconds() == rhs.ToMicroseconds();
+}
+inline bool operator!=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return !(lhs == rhs);
+}
+inline bool operator<(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return lhs.ToMicroseconds() < rhs.ToMicroseconds();
+}
+inline bool operator>(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return rhs < lhs;
+}
+inline bool operator<=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return !(rhs < lhs);
+}
+inline bool operator>=(QuicTime::Delta lhs, QuicTime::Delta rhs) {
+ return !(lhs < rhs);
+}
+// Non-member relational operators for QuicTime.
+inline bool operator==(QuicTime lhs, QuicTime rhs) {
+ return lhs.ticks_ == rhs.ticks_;
+}
+inline bool operator!=(QuicTime lhs, QuicTime rhs) {
+ return !(lhs == rhs);
+}
+inline bool operator<(QuicTime lhs, QuicTime rhs) {
+ return lhs.ticks_ < rhs.ticks_;
+}
+inline bool operator>(QuicTime lhs, QuicTime rhs) {
+ return rhs < lhs;
+}
+inline bool operator<=(QuicTime lhs, QuicTime rhs) {
+ return !(rhs < lhs);
+}
+inline bool operator>=(QuicTime lhs, QuicTime rhs) {
+ return !(lhs < rhs);
+}
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_TIME_H_
diff --git a/chromium/net/quic/quic_time_test.cc b/chromium/net/quic/quic_time_test.cc
new file mode 100644
index 00000000000..c4ea0e20047
--- /dev/null
+++ b/chromium/net/quic/quic_time_test.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_time.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+class QuicTimeDeltaTest : public ::testing::Test {
+ protected:
+};
+
+TEST_F(QuicTimeDeltaTest, Zero) {
+ EXPECT_TRUE(QuicTime::Delta::Zero().IsZero());
+ EXPECT_FALSE(QuicTime::Delta::Zero().IsInfinite());
+ EXPECT_FALSE(QuicTime::Delta::FromMilliseconds(1).IsZero());
+}
+
+TEST_F(QuicTimeDeltaTest, Infinite) {
+ EXPECT_TRUE(QuicTime::Delta::Infinite().IsInfinite());
+ EXPECT_FALSE(QuicTime::Delta::Zero().IsInfinite());
+ EXPECT_FALSE(QuicTime::Delta::FromMilliseconds(1).IsInfinite());
+}
+
+TEST_F(QuicTimeDeltaTest, FromTo) {
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1),
+ QuicTime::Delta::FromMicroseconds(1000));
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+ QuicTime::Delta::FromMilliseconds(1000));
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(1),
+ QuicTime::Delta::FromMicroseconds(1000000));
+
+ EXPECT_EQ(1, QuicTime::Delta::FromMicroseconds(1000).ToMilliseconds());
+ EXPECT_EQ(2, QuicTime::Delta::FromMilliseconds(2000).ToSeconds());
+ EXPECT_EQ(1000, QuicTime::Delta::FromMilliseconds(1).ToMicroseconds());
+ EXPECT_EQ(1, QuicTime::Delta::FromMicroseconds(1000).ToMilliseconds());
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(2000).ToMicroseconds(),
+ QuicTime::Delta::FromSeconds(2).ToMicroseconds());
+}
+
+TEST_F(QuicTimeDeltaTest, Add) {
+ EXPECT_EQ(QuicTime::Delta::FromMicroseconds(2000),
+ QuicTime::Delta::Zero().Add(QuicTime::Delta::FromMilliseconds(2)));
+}
+
+TEST_F(QuicTimeDeltaTest, Subtract) {
+ EXPECT_EQ(QuicTime::Delta::FromMicroseconds(1000),
+ QuicTime::Delta::FromMilliseconds(2).Subtract(
+ QuicTime::Delta::FromMilliseconds(1)));
+}
+
+class QuicTimeTest : public ::testing::Test {
+ protected:
+ MockClock clock_;
+};
+
+TEST_F(QuicTimeTest, Initialized) {
+ EXPECT_FALSE(QuicTime::Zero().IsInitialized());
+ EXPECT_TRUE(QuicTime::Zero().Add(
+ QuicTime::Delta::FromMicroseconds(1)).IsInitialized());
+}
+
+TEST_F(QuicTimeTest, Add) {
+ QuicTime time_1 = QuicTime::Zero().Add(
+ QuicTime::Delta::FromMilliseconds(1));
+ QuicTime time_2 = QuicTime::Zero().Add(
+ QuicTime::Delta::FromMilliseconds(2));
+
+ QuicTime::Delta diff = time_2.Subtract(time_1);
+
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), diff);
+ EXPECT_EQ(1000, diff.ToMicroseconds());
+ EXPECT_EQ(1, diff.ToMilliseconds());
+}
+
+TEST_F(QuicTimeTest, Subtract) {
+ QuicTime time_1 = QuicTime::Zero().Add(
+ QuicTime::Delta::FromMilliseconds(1));
+ QuicTime time_2 = QuicTime::Zero().Add(
+ QuicTime::Delta::FromMilliseconds(2));
+
+ EXPECT_EQ(QuicTime::Delta::FromMilliseconds(1), time_2.Subtract(time_1));
+}
+
+TEST_F(QuicTimeTest, SubtractDelta) {
+ QuicTime time = QuicTime::Zero().Add(
+ QuicTime::Delta::FromMilliseconds(2));
+ EXPECT_EQ(QuicTime::Zero().Add(QuicTime::Delta::FromMilliseconds(1)),
+ time.Subtract(QuicTime::Delta::FromMilliseconds(1)));
+}
+
+TEST_F(QuicTimeTest, MockClock) {
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+
+ QuicTime now = clock_.ApproximateNow();
+ QuicTime time = QuicTime::Zero().Add(QuicTime::Delta::FromMicroseconds(1000));
+
+ EXPECT_EQ(now, time);
+
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ now = clock_.ApproximateNow();
+
+ EXPECT_NE(now, time);
+
+ time = time.Add(QuicTime::Delta::FromMilliseconds(1));
+ EXPECT_EQ(now, time);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/quic_utils.cc b/chromium/net/quic/quic_utils.cc
new file mode 100644
index 00000000000..17957440a3d
--- /dev/null
+++ b/chromium/net/quic/quic_utils.cc
@@ -0,0 +1,270 @@
+// Copyright (c) 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.
+
+#include "net/quic/quic_utils.h"
+
+#include <ctype.h>
+
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+// static
+uint64 QuicUtils::FNV1a_64_Hash(const char* data, int len) {
+ static const uint64 kOffset = GG_UINT64_C(14695981039346656037);
+ static const uint64 kPrime = GG_UINT64_C(1099511628211);
+
+ const uint8* octets = reinterpret_cast<const uint8*>(data);
+
+ uint64 hash = kOffset;
+
+ for (int i = 0; i < len; ++i) {
+ hash = hash ^ octets[i];
+ hash = hash * kPrime;
+ }
+
+ return hash;
+}
+
+// static
+uint128 QuicUtils::FNV1a_128_Hash(const char* data, int len) {
+ // The following two constants are defined as part of the hash algorithm.
+ // see http://www.isthe.com/chongo/tech/comp/fnv/
+ // 309485009821345068724781371
+ const uint128 kPrime(16777216, 315);
+ // 144066263297769815596495629667062367629
+ const uint128 kOffset(GG_UINT64_C(7809847782465536322),
+ GG_UINT64_C(7113472399480571277));
+
+ const uint8* octets = reinterpret_cast<const uint8*>(data);
+
+ uint128 hash = kOffset;
+
+ for (int i = 0; i < len; ++i) {
+ hash = hash ^ uint128(0, octets[i]);
+ hash = hash * kPrime;
+ }
+
+ return hash;
+}
+
+// static
+bool QuicUtils::FindMutualTag(const QuicTagVector& our_tags_vector,
+ const QuicTag* their_tags,
+ size_t num_their_tags,
+ Priority priority,
+ QuicTag* out_result,
+ size_t* out_index) {
+ if (our_tags_vector.empty()) {
+ return false;
+ }
+ const size_t num_our_tags = our_tags_vector.size();
+ const QuicTag* our_tags = &our_tags_vector[0];
+
+ size_t num_priority_tags, num_inferior_tags;
+ const QuicTag* priority_tags;
+ const QuicTag* inferior_tags;
+ if (priority == LOCAL_PRIORITY) {
+ num_priority_tags = num_our_tags;
+ priority_tags = our_tags;
+ num_inferior_tags = num_their_tags;
+ inferior_tags = their_tags;
+ } else {
+ num_priority_tags = num_their_tags;
+ priority_tags = their_tags;
+ num_inferior_tags = num_our_tags;
+ inferior_tags = our_tags;
+ }
+
+ for (size_t i = 0; i < num_priority_tags; i++) {
+ for (size_t j = 0; j < num_inferior_tags; j++) {
+ if (priority_tags[i] == inferior_tags[j]) {
+ *out_result = priority_tags[i];
+ if (out_index) {
+ if (priority == LOCAL_PRIORITY) {
+ *out_index = j;
+ } else {
+ *out_index = i;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// static
+void QuicUtils::SerializeUint128(uint128 v, uint8* out) {
+ const uint64 lo = Uint128Low64(v);
+ const uint64 hi = Uint128High64(v);
+ // This assumes that the system is little-endian.
+ memcpy(out, &lo, sizeof(lo));
+ memcpy(out + sizeof(lo), &hi, sizeof(hi));
+}
+
+// static
+uint128 QuicUtils::ParseUint128(const uint8* in) {
+ uint64 lo, hi;
+ memcpy(&lo, in, sizeof(lo));
+ memcpy(&hi, in + sizeof(lo), sizeof(hi));
+ return uint128(hi, lo);
+}
+
+#define RETURN_STRING_LITERAL(x) \
+case x: \
+return #x;
+
+// static
+const char* QuicUtils::StreamErrorToString(QuicRstStreamErrorCode error) {
+ switch (error) {
+ RETURN_STRING_LITERAL(QUIC_STREAM_NO_ERROR);
+ RETURN_STRING_LITERAL(QUIC_STREAM_CONNECTION_ERROR);
+ RETURN_STRING_LITERAL(QUIC_SERVER_ERROR_PROCESSING_STREAM);
+ RETURN_STRING_LITERAL(QUIC_MULTIPLE_TERMINATION_OFFSETS);
+ RETURN_STRING_LITERAL(QUIC_BAD_APPLICATION_PAYLOAD);
+ RETURN_STRING_LITERAL(QUIC_STREAM_PEER_GOING_AWAY);
+ RETURN_STRING_LITERAL(QUIC_STREAM_LAST_ERROR);
+ }
+ // Return a default value so that we return this when |error| doesn't match
+ // any of the QuicRstStreamErrorCodes. This can happen when the RstStream
+ // frame sent by the peer (attacker) has invalid error code.
+ return "INVALID_RST_STREAM_ERROR_CODE";
+}
+
+// static
+const char* QuicUtils::ErrorToString(QuicErrorCode error) {
+ switch (error) {
+ RETURN_STRING_LITERAL(QUIC_NO_ERROR);
+ RETURN_STRING_LITERAL(QUIC_INTERNAL_ERROR);
+ RETURN_STRING_LITERAL(QUIC_STREAM_DATA_AFTER_TERMINATION);
+ RETURN_STRING_LITERAL(QUIC_INVALID_PACKET_HEADER);
+ RETURN_STRING_LITERAL(QUIC_INVALID_FRAME_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_FEC_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_RST_STREAM_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_CONNECTION_CLOSE_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_GOAWAY_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_ACK_DATA);
+ RETURN_STRING_LITERAL(QUIC_INVALID_VERSION_NEGOTIATION_PACKET);
+ RETURN_STRING_LITERAL(QUIC_INVALID_PUBLIC_RST_PACKET);
+ RETURN_STRING_LITERAL(QUIC_DECRYPTION_FAILURE);
+ RETURN_STRING_LITERAL(QUIC_ENCRYPTION_FAILURE);
+ RETURN_STRING_LITERAL(QUIC_PACKET_TOO_LARGE);
+ RETURN_STRING_LITERAL(QUIC_PACKET_FOR_NONEXISTENT_STREAM);
+ RETURN_STRING_LITERAL(QUIC_PEER_GOING_AWAY);
+ RETURN_STRING_LITERAL(QUIC_HANDSHAKE_FAILED);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_TAGS_OUT_OF_ORDER);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_ENTRIES);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_TOO_MANY_REJECTS);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_INVALID_VALUE_LENGTH)
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_INTERNAL_ERROR);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_VERSION_NOT_SUPPORTED);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_NO_SUPPORT);
+ RETURN_STRING_LITERAL(QUIC_INVALID_CRYPTO_MESSAGE_TYPE);
+ RETURN_STRING_LITERAL(QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_PARAMETER_NO_OVERLAP);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_MESSAGE_INDEX_NOT_FOUND);
+ RETURN_STRING_LITERAL(QUIC_INVALID_STREAM_ID);
+ RETURN_STRING_LITERAL(QUIC_TOO_MANY_OPEN_STREAMS);
+ RETURN_STRING_LITERAL(QUIC_PUBLIC_RESET);
+ RETURN_STRING_LITERAL(QUIC_INVALID_VERSION);
+ RETURN_STRING_LITERAL(QUIC_STREAM_RST_BEFORE_HEADERS_DECOMPRESSED);
+ RETURN_STRING_LITERAL(QUIC_INVALID_HEADER_ID);
+ RETURN_STRING_LITERAL(QUIC_INVALID_NEGOTIATED_VALUE);
+ RETURN_STRING_LITERAL(QUIC_DECOMPRESSION_FAILURE);
+ RETURN_STRING_LITERAL(QUIC_CONNECTION_TIMED_OUT);
+ RETURN_STRING_LITERAL(QUIC_ERROR_MIGRATING_ADDRESS);
+ RETURN_STRING_LITERAL(QUIC_PACKET_WRITE_ERROR);
+ RETURN_STRING_LITERAL(QUIC_PROOF_INVALID);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_DUPLICATE_TAG);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_ENCRYPTION_LEVEL_INCORRECT);
+ RETURN_STRING_LITERAL(QUIC_CRYPTO_SERVER_CONFIG_EXPIRED);
+ RETURN_STRING_LITERAL(QUIC_LAST_ERROR);
+ // Intentionally have no default case, so we'll break the build
+ // if we add errors and don't put them here.
+ }
+ // Return a default value so that we return this when |error| doesn't match
+ // any of the QuicErrorCodes. This can happen when the ConnectionClose
+ // frame sent by the peer (attacker) has invalid error code.
+ return "INVALID_ERROR_CODE";
+}
+
+// static
+const char* QuicUtils::EncryptionLevelToString(EncryptionLevel level) {
+ switch (level) {
+ RETURN_STRING_LITERAL(ENCRYPTION_NONE);
+ RETURN_STRING_LITERAL(ENCRYPTION_INITIAL);
+ RETURN_STRING_LITERAL(ENCRYPTION_FORWARD_SECURE);
+ RETURN_STRING_LITERAL(NUM_ENCRYPTION_LEVELS);
+ }
+ return "INVALID_ENCRYPTION_LEVEL";
+}
+
+// static
+string QuicUtils::TagToString(QuicTag tag) {
+ char chars[4];
+ bool ascii = true;
+ const QuicTag orig_tag = tag;
+
+ for (size_t i = 0; i < sizeof(chars); i++) {
+ chars[i] = tag;
+ if (chars[i] == 0 && i == 3) {
+ chars[i] = ' ';
+ }
+ if (!isprint(static_cast<unsigned char>(chars[i]))) {
+ ascii = false;
+ break;
+ }
+ tag >>= 8;
+ }
+
+ if (ascii) {
+ return string(chars, sizeof(chars));
+ }
+
+ return base::UintToString(orig_tag);
+}
+
+// static
+string QuicUtils::StringToHexASCIIDump(StringPiece in_buffer) {
+ int offset = 0;
+ const int kBytesPerLine = 16; // Max bytes dumped per line
+ const char* buf = in_buffer.data();
+ int bytes_remaining = in_buffer.size();
+ string s; // our output
+ const char* p = buf;
+ while (bytes_remaining > 0) {
+ const int line_bytes = std::min(bytes_remaining, kBytesPerLine);
+ base::StringAppendF(&s, "0x%04x: ", offset); // Do the line header
+ for (int i = 0; i < kBytesPerLine; ++i) {
+ if (i < line_bytes) {
+ base::StringAppendF(&s, "%02x", static_cast<unsigned char>(p[i]));
+ } else {
+ s += " "; // two-space filler instead of two-space hex digits
+ }
+ if (i % 2) s += ' ';
+ }
+ s += ' ';
+ for (int i = 0; i < line_bytes; ++i) { // Do the ASCII dump
+ s+= (p[i] > 32 && p[i] < 127) ? p[i] : '.';
+ }
+
+ bytes_remaining -= line_bytes;
+ offset += line_bytes;
+ p += line_bytes;
+ s += '\n';
+ }
+ return s;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/quic_utils.h b/chromium/net/quic/quic_utils.h
new file mode 100644
index 00000000000..6650dbf453b
--- /dev/null
+++ b/chromium/net/quic/quic_utils.h
@@ -0,0 +1,81 @@
+// Copyright (c) 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.
+//
+// Some helpers for quic
+
+#ifndef NET_QUIC_QUIC_UTILS_H_
+#define NET_QUIC_QUIC_UTILS_H_
+
+#include "net/base/int128.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE QuicUtils {
+ public:
+ enum Priority {
+ LOCAL_PRIORITY,
+ PEER_PRIORITY,
+ };
+
+ // returns the 64 bit FNV1a hash of the data. See
+ // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param
+ static uint64 FNV1a_64_Hash(const char* data, int len);
+
+ // returns the 128 bit FNV1a hash of the data. See
+ // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param
+ static uint128 FNV1a_128_Hash(const char* data, int len);
+
+ // FindMutualTag sets |out_result| to the first tag in the priority list that
+ // is also in the other list and returns true. If there is no intersection it
+ // returns false.
+ //
+ // Which list has priority is determined by |priority|.
+ //
+ // If |out_index| is non-NULL and a match is found then the index of that
+ // match in |their_tags| is written to |out_index|.
+ static bool FindMutualTag(const QuicTagVector& our_tags,
+ const QuicTag* their_tags,
+ size_t num_their_tags,
+ Priority priority,
+ QuicTag* out_result,
+ size_t* out_index);
+
+ // SerializeUint128 writes |v| in little-endian form to |out|.
+ static void SerializeUint128(uint128 v, uint8* out);
+
+ // ParseUint128 parses a little-endian uint128 from |in| and returns it.
+ static uint128 ParseUint128(const uint8* in);
+
+ // Returns the name of the QuicRstStreamErrorCode as a char*
+ static const char* StreamErrorToString(QuicRstStreamErrorCode error);
+
+ // Returns the name of the QuicErrorCode as a char*
+ static const char* ErrorToString(QuicErrorCode error);
+
+ // Returns the level of encryption as a char*
+ static const char* EncryptionLevelToString(EncryptionLevel level);
+
+ // TagToString is a utility function for pretty-printing handshake messages
+ // that converts a tag to a string. It will try to maintain the human friendly
+ // name if possible (i.e. kABCD -> "ABCD"), or will just treat it as a number
+ // if not.
+ static std::string TagToString(QuicTag tag);
+
+ // Given a binary buffer, return a hex+ASCII dump in the style of
+ // tcpdump's -X and -XX options:
+ // "0x0000: 0090 69bd 5400 000d 610f 0189 0800 4500 ..i.T...a.....E.\n"
+ // "0x0010: 001c fb98 4000 4001 7e18 d8ef 2301 455d ....@.@.~...#.E]\n"
+ // "0x0020: 7fe2 0800 6bcb 0bc6 806e ....k....n\n"
+ static std::string StringToHexASCIIDump(base::StringPiece in_buffer);
+
+ static char* AsChars(unsigned char* data) {
+ return reinterpret_cast<char*>(data);
+ }
+};
+
+} // namespace net
+
+#endif // NET_QUIC_QUIC_UTILS_H_
diff --git a/chromium/net/quic/quic_utils_test.cc b/chromium/net/quic/quic_utils_test.cc
new file mode 100644
index 00000000000..0e50aec0ec1
--- /dev/null
+++ b/chromium/net/quic/quic_utils_test.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2013 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/quic/quic_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace test {
+namespace {
+
+// A test string and a hex+ASCII dump of the same string.
+const unsigned char kString[] = {
+ 0x00, 0x90, 0x69, 0xbd, 0x54, 0x00, 0x00, 0x0d,
+ 0x61, 0x0f, 0x01, 0x89, 0x08, 0x00, 0x45, 0x00,
+ 0x00, 0x1c, 0xfb, 0x98, 0x40, 0x00, 0x40, 0x01,
+ 0x7e, 0x18, 0xd8, 0xef, 0x23, 0x01, 0x45, 0x5d,
+ 0x7f, 0xe2, 0x08, 0x00, 0x6b, 0xcb, 0x0b, 0xc6,
+ 0x80, 0x6e
+};
+
+const unsigned char kHexDump[] =
+"0x0000: 0090 69bd 5400 000d 610f 0189 0800 4500 ..i.T...a.....E.\n"
+"0x0010: 001c fb98 4000 4001 7e18 d8ef 2301 455d ....@.@.~...#.E]\n"
+"0x0020: 7fe2 0800 6bcb 0bc6 806e ....k....n\n";
+
+TEST(QuicUtilsTest, StreamErrorToString) {
+ EXPECT_STREQ("QUIC_BAD_APPLICATION_PAYLOAD",
+ QuicUtils::StreamErrorToString(QUIC_BAD_APPLICATION_PAYLOAD));
+}
+
+TEST(QuicUtilsTest, ErrorToString) {
+ EXPECT_STREQ("QUIC_NO_ERROR",
+ QuicUtils::ErrorToString(QUIC_NO_ERROR));
+}
+
+TEST(QuicUtilsTest, StringToHexASCIIDumpArgTypes) {
+ // Verify that char*, string and StringPiece are all valid argument types.
+ struct {
+ const string input;
+ const string expected;
+ } tests[] = {
+ { "", "", },
+ { "A", "0x0000: 41 A\n", },
+ { "AB", "0x0000: 4142 AB\n", },
+ { "ABC", "0x0000: 4142 43 ABC\n", },
+ { "original",
+ "0x0000: 6f72 6967 696e 616c original\n", },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ EXPECT_EQ(tests[i].expected,
+ QuicUtils::StringToHexASCIIDump(tests[i].input.c_str()));
+ EXPECT_EQ(tests[i].expected,
+ QuicUtils::StringToHexASCIIDump(tests[i].input));
+ EXPECT_EQ(tests[i].expected,
+ QuicUtils::StringToHexASCIIDump(StringPiece(tests[i].input)));
+ }
+}
+
+TEST(QuicUtilsTest, StringToHexASCIIDumpSuccess) {
+ EXPECT_EQ(string(reinterpret_cast<const char*>(kHexDump)),
+ QuicUtils::StringToHexASCIIDump(
+ string(reinterpret_cast<const char*>(kString), sizeof(kString))));
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/reliable_quic_stream.cc b/chromium/net/quic/reliable_quic_stream.cc
new file mode 100644
index 00000000000..c2cb3f1ff52
--- /dev/null
+++ b/chromium/net/quic/reliable_quic_stream.cc
@@ -0,0 +1,436 @@
+// Copyright (c) 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.
+
+#include "net/quic/reliable_quic_stream.h"
+
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_spdy_decompressor.h"
+
+using base::StringPiece;
+using std::min;
+
+namespace net {
+
+ReliableQuicStream::ReliableQuicStream(QuicStreamId id,
+ QuicSession* session)
+ : sequencer_(this),
+ id_(id),
+ session_(session),
+ visitor_(NULL),
+ stream_bytes_read_(0),
+ stream_bytes_written_(0),
+ headers_decompressed_(false),
+ headers_id_(0),
+ decompression_failed_(false),
+ stream_error_(QUIC_STREAM_NO_ERROR),
+ connection_error_(QUIC_NO_ERROR),
+ read_side_closed_(false),
+ write_side_closed_(false),
+ fin_buffered_(false),
+ fin_sent_(false) {
+}
+
+ReliableQuicStream::~ReliableQuicStream() {
+}
+
+bool ReliableQuicStream::WillAcceptStreamFrame(
+ const QuicStreamFrame& frame) const {
+ if (read_side_closed_) {
+ return true;
+ }
+ if (frame.stream_id != id_) {
+ LOG(ERROR) << "Error!";
+ return false;
+ }
+ return sequencer_.WillAcceptStreamFrame(frame);
+}
+
+bool ReliableQuicStream::OnStreamFrame(const QuicStreamFrame& frame) {
+ DCHECK_EQ(frame.stream_id, id_);
+ if (read_side_closed_) {
+ DLOG(INFO) << "Ignoring frame " << frame.stream_id;
+ // We don't want to be reading: blackhole the data.
+ return true;
+ }
+ // Note: This count include duplicate data received.
+ stream_bytes_read_ += frame.data.length();
+
+ bool accepted = sequencer_.OnStreamFrame(frame);
+
+ return accepted;
+}
+
+void ReliableQuicStream::OnStreamReset(QuicRstStreamErrorCode error) {
+ stream_error_ = error;
+ TerminateFromPeer(false); // Full close.
+}
+
+void ReliableQuicStream::ConnectionClose(QuicErrorCode error, bool from_peer) {
+ if (read_side_closed_ && write_side_closed_) {
+ return;
+ }
+ if (error != QUIC_NO_ERROR) {
+ stream_error_ = QUIC_STREAM_CONNECTION_ERROR;
+ connection_error_ = error;
+ }
+
+ if (from_peer) {
+ TerminateFromPeer(false);
+ } else {
+ CloseWriteSide();
+ CloseReadSide();
+ }
+}
+
+void ReliableQuicStream::TerminateFromPeer(bool half_close) {
+ if (!half_close) {
+ CloseWriteSide();
+ }
+ CloseReadSide();
+}
+
+void ReliableQuicStream::Close(QuicRstStreamErrorCode error) {
+ stream_error_ = error;
+ if (error != QUIC_STREAM_NO_ERROR) {
+ // Sending a RstStream results in calling CloseStream.
+ session()->SendRstStream(id(), error);
+ } else {
+ session_->CloseStream(id());
+ }
+}
+
+size_t ReliableQuicStream::Readv(const struct iovec* iov, size_t iov_len) {
+ if (headers_decompressed_ && decompressed_headers_.empty()) {
+ return sequencer_.Readv(iov, iov_len);
+ }
+ size_t bytes_consumed = 0;
+ size_t iov_index = 0;
+ while (iov_index < iov_len &&
+ decompressed_headers_.length() > bytes_consumed) {
+ size_t bytes_to_read = min(iov[iov_index].iov_len,
+ decompressed_headers_.length() - bytes_consumed);
+ char* iov_ptr = static_cast<char*>(iov[iov_index].iov_base);
+ memcpy(iov_ptr,
+ decompressed_headers_.data() + bytes_consumed, bytes_to_read);
+ bytes_consumed += bytes_to_read;
+ ++iov_index;
+ }
+ decompressed_headers_.erase(0, bytes_consumed);
+ return bytes_consumed;
+}
+
+int ReliableQuicStream::GetReadableRegions(iovec* iov, size_t iov_len) {
+ if (headers_decompressed_ && decompressed_headers_.empty()) {
+ return sequencer_.GetReadableRegions(iov, iov_len);
+ }
+ if (iov_len == 0) {
+ return 0;
+ }
+ iov[0].iov_base = static_cast<void*>(
+ const_cast<char*>(decompressed_headers_.data()));
+ iov[0].iov_len = decompressed_headers_.length();
+ return 1;
+}
+
+bool ReliableQuicStream::IsHalfClosed() const {
+ if (!headers_decompressed_ || !decompressed_headers_.empty()) {
+ return false;
+ }
+ return sequencer_.IsHalfClosed();
+}
+
+bool ReliableQuicStream::HasBytesToRead() const {
+ return !decompressed_headers_.empty() || sequencer_.HasBytesToRead();
+}
+
+const IPEndPoint& ReliableQuicStream::GetPeerAddress() const {
+ return session_->peer_address();
+}
+
+QuicSpdyCompressor* ReliableQuicStream::compressor() {
+ return session_->compressor();
+}
+
+bool ReliableQuicStream::GetSSLInfo(SSLInfo* ssl_info) {
+ return session_->GetSSLInfo(ssl_info);
+}
+
+QuicConsumedData ReliableQuicStream::WriteData(StringPiece data, bool fin) {
+ DCHECK(data.size() > 0 || fin);
+ return WriteOrBuffer(data, fin);
+}
+
+QuicConsumedData ReliableQuicStream::WriteOrBuffer(StringPiece data, bool fin) {
+ DCHECK(!fin_buffered_);
+
+ QuicConsumedData consumed_data(0, false);
+ fin_buffered_ = fin;
+
+ if (queued_data_.empty()) {
+ consumed_data = WriteDataInternal(string(data.data(), data.length()), fin);
+ DCHECK_LE(consumed_data.bytes_consumed, data.length());
+ }
+
+ // If there's unconsumed data or an unconsumed fin, queue it.
+ if (consumed_data.bytes_consumed < data.length() ||
+ (fin && !consumed_data.fin_consumed)) {
+ queued_data_.push_back(
+ string(data.data() + consumed_data.bytes_consumed,
+ data.length() - consumed_data.bytes_consumed));
+ }
+
+ return QuicConsumedData(data.size(), true);
+}
+
+void ReliableQuicStream::OnCanWrite() {
+ bool fin = false;
+ while (!queued_data_.empty()) {
+ const string& data = queued_data_.front();
+ if (queued_data_.size() == 1 && fin_buffered_) {
+ fin = true;
+ }
+ QuicConsumedData consumed_data = WriteDataInternal(data, fin);
+ if (consumed_data.bytes_consumed == data.size() &&
+ fin == consumed_data.fin_consumed) {
+ queued_data_.pop_front();
+ } else {
+ queued_data_.front().erase(0, consumed_data.bytes_consumed);
+ break;
+ }
+ }
+}
+
+QuicConsumedData ReliableQuicStream::WriteDataInternal(
+ StringPiece data, bool fin) {
+ if (write_side_closed_) {
+ DLOG(ERROR) << "Attempt to write when the write side is closed";
+ return QuicConsumedData(0, false);
+ }
+
+ QuicConsumedData consumed_data =
+ session()->WriteData(id(), data, stream_bytes_written_, fin);
+ stream_bytes_written_ += consumed_data.bytes_consumed;
+ if (consumed_data.bytes_consumed == data.length()) {
+ if (fin && consumed_data.fin_consumed) {
+ fin_sent_ = true;
+ CloseWriteSide();
+ } else if (fin && !consumed_data.fin_consumed) {
+ session_->MarkWriteBlocked(id());
+ }
+ } else {
+ session_->MarkWriteBlocked(id());
+ }
+ return consumed_data;
+}
+
+void ReliableQuicStream::CloseReadSide() {
+ if (read_side_closed_) {
+ return;
+ }
+ DLOG(INFO) << "Done reading from stream " << id();
+
+ read_side_closed_ = true;
+ if (write_side_closed_) {
+ DLOG(INFO) << "Closing stream: " << id();
+ session_->CloseStream(id());
+ }
+}
+
+uint32 ReliableQuicStream::ProcessRawData(const char* data, uint32 data_len) {
+ if (id() == kCryptoStreamId) {
+ if (data_len == 0) {
+ return 0;
+ }
+ // The crypto stream does not use compression.
+ return ProcessData(data, data_len);
+ }
+ uint32 total_bytes_consumed = 0;
+ if (headers_id_ == 0u) {
+ // The headers ID has not yet been read. Strip it from the beginning of
+ // the data stream.
+ DCHECK_GT(4u, headers_id_buffer_.length());
+ size_t missing_size = 4 - headers_id_buffer_.length();
+ if (data_len < missing_size) {
+ StringPiece(data, data_len).AppendToString(&headers_id_buffer_);
+ return data_len;
+ }
+ total_bytes_consumed += missing_size;
+ StringPiece(data, missing_size).AppendToString(&headers_id_buffer_);
+ DCHECK_EQ(4u, headers_id_buffer_.length());
+ memcpy(&headers_id_, headers_id_buffer_.data(), 4);
+ headers_id_buffer_.clear();
+ data += missing_size;
+ data_len -= missing_size;
+ }
+ DCHECK_NE(0u, headers_id_);
+ if (data_len == 0) {
+ return total_bytes_consumed;
+ }
+
+ // Once the headers are finished, we simply pass the data through.
+ if (headers_decompressed_) {
+ // Some buffered header data remains.
+ if (!decompressed_headers_.empty()) {
+ ProcessHeaderData();
+ }
+ if (decompressed_headers_.empty()) {
+ DVLOG(1) << "Delegating procesing to ProcessData";
+ total_bytes_consumed += ProcessData(data, data_len);
+ }
+ return total_bytes_consumed;
+ }
+
+ QuicHeaderId current_header_id =
+ session_->decompressor()->current_header_id();
+ // Ensure that this header id looks sane.
+ if (headers_id_ < current_header_id ||
+ headers_id_ > kMaxHeaderIdDelta + current_header_id) {
+ DVLOG(1) << "Invalid headers for stream: " << id()
+ << " header_id: " << headers_id_
+ << " current_header_id: " << current_header_id;
+ session_->connection()->SendConnectionClose(QUIC_INVALID_HEADER_ID);
+ return total_bytes_consumed;
+ }
+
+ // If we are head-of-line blocked on decompression, then back up.
+ if (current_header_id != headers_id_) {
+ session_->MarkDecompressionBlocked(headers_id_, id());
+ DVLOG(1) << "Unable to decompress header data for stream: " << id()
+ << " header_id: " << headers_id_;
+ return total_bytes_consumed;
+ }
+
+ // Decompressed data will be delivered to decompressed_headers_.
+ size_t bytes_consumed = session_->decompressor()->DecompressData(
+ StringPiece(data, data_len), this);
+ DCHECK_NE(0u, bytes_consumed);
+ if (bytes_consumed > data_len) {
+ DCHECK(false) << "DecompressData returned illegal value";
+ OnDecompressionError();
+ return total_bytes_consumed;
+ }
+ total_bytes_consumed += bytes_consumed;
+ data += bytes_consumed;
+ data_len -= bytes_consumed;
+
+ if (decompression_failed_) {
+ // The session will have been closed in OnDecompressionError.
+ return total_bytes_consumed;
+ }
+
+ // Headers are complete if the decompressor has moved on to the
+ // next stream.
+ headers_decompressed_ =
+ session_->decompressor()->current_header_id() != headers_id_;
+ if (!headers_decompressed_) {
+ DCHECK_EQ(0u, data_len);
+ }
+
+ ProcessHeaderData();
+
+ if (!headers_decompressed_ || !decompressed_headers_.empty()) {
+ return total_bytes_consumed;
+ }
+
+ // We have processed all of the decompressed data but we might
+ // have some more raw data to process.
+ if (data_len > 0) {
+ total_bytes_consumed += ProcessData(data, data_len);
+ }
+
+ // The sequencer will push any additional buffered frames if this data
+ // has been completely consumed.
+ return total_bytes_consumed;
+}
+
+uint32 ReliableQuicStream::ProcessHeaderData() {
+ if (decompressed_headers_.empty()) {
+ return 0;
+ }
+
+ size_t bytes_processed = ProcessData(decompressed_headers_.data(),
+ decompressed_headers_.length());
+ if (bytes_processed == decompressed_headers_.length()) {
+ decompressed_headers_.clear();
+ } else {
+ decompressed_headers_ = decompressed_headers_.erase(0, bytes_processed);
+ }
+ return bytes_processed;
+}
+
+void ReliableQuicStream::OnDecompressorAvailable() {
+ DCHECK_EQ(headers_id_,
+ session_->decompressor()->current_header_id());
+ DCHECK(!headers_decompressed_);
+ DCHECK(!decompression_failed_);
+ DCHECK_EQ(0u, decompressed_headers_.length());
+
+ while (!headers_decompressed_) {
+ struct iovec iovec;
+ if (sequencer_.GetReadableRegions(&iovec, 1) == 0) {
+ return;
+ }
+
+ size_t bytes_consumed = session_->decompressor()->DecompressData(
+ StringPiece(static_cast<char*>(iovec.iov_base),
+ iovec.iov_len),
+ this);
+ DCHECK_LE(bytes_consumed, iovec.iov_len);
+ if (decompression_failed_) {
+ return;
+ }
+ sequencer_.MarkConsumed(bytes_consumed);
+
+ headers_decompressed_ =
+ session_->decompressor()->current_header_id() != headers_id_;
+ }
+
+ // Either the headers are complete, or the all data as been consumed.
+ ProcessHeaderData(); // Unprocessed headers remain in decompressed_headers_.
+ if (IsHalfClosed()) {
+ TerminateFromPeer(true);
+ } else if (headers_decompressed_ && decompressed_headers_.empty()) {
+ sequencer_.FlushBufferedFrames();
+ }
+}
+
+bool ReliableQuicStream::OnDecompressedData(StringPiece data) {
+ data.AppendToString(&decompressed_headers_);
+ return true;
+}
+
+void ReliableQuicStream::OnDecompressionError() {
+ DCHECK(!decompression_failed_);
+ decompression_failed_ = true;
+ session_->connection()->SendConnectionClose(QUIC_DECOMPRESSION_FAILURE);
+}
+
+
+void ReliableQuicStream::CloseWriteSide() {
+ if (write_side_closed_) {
+ return;
+ }
+ DLOG(INFO) << "Done writing to stream " << id();
+
+ write_side_closed_ = true;
+ if (read_side_closed_) {
+ DLOG(INFO) << "Closing stream: " << id();
+ session_->CloseStream(id());
+ }
+}
+
+void ReliableQuicStream::OnClose() {
+ CloseReadSide();
+ CloseWriteSide();
+
+ if (visitor_) {
+ Visitor* visitor = visitor_;
+ // Calling Visitor::OnClose() may result the destruction of the visitor,
+ // so we need to ensure we don't call it again.
+ visitor_ = NULL;
+ visitor->OnClose(this);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/quic/reliable_quic_stream.h b/chromium/net/quic/reliable_quic_stream.h
new file mode 100644
index 00000000000..352325de36c
--- /dev/null
+++ b/chromium/net/quic/reliable_quic_stream.h
@@ -0,0 +1,199 @@
+// Copyright (c) 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.
+//
+// The base class for client/server reliable streams.
+
+#ifndef NET_QUIC_RELIABLE_QUIC_STREAM_H_
+#define NET_QUIC_RELIABLE_QUIC_STREAM_H_
+
+#include <sys/types.h>
+
+#include <list>
+
+#include "base/strings/string_piece.h"
+#include "net/base/iovec.h"
+#include "net/base/net_export.h"
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/quic_stream_sequencer.h"
+
+namespace net {
+
+namespace test {
+class ReliableQuicStreamPeer;
+} // namespace test
+
+class IPEndPoint;
+class QuicSession;
+class SSLInfo;
+
+// All this does right now is send data to subclasses via the sequencer.
+class NET_EXPORT_PRIVATE ReliableQuicStream : public
+ QuicSpdyDecompressor::Visitor {
+ public:
+ // Visitor receives callbacks from the stream.
+ class Visitor {
+ public:
+ Visitor() {}
+
+ // Called when the stream is closed.
+ virtual void OnClose(ReliableQuicStream* stream) = 0;
+
+ protected:
+ virtual ~Visitor() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Visitor);
+ };
+
+ ReliableQuicStream(QuicStreamId id,
+ QuicSession* session);
+
+ virtual ~ReliableQuicStream();
+
+ bool WillAcceptStreamFrame(const QuicStreamFrame& frame) const;
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame);
+
+ virtual void OnCanWrite();
+
+ // Called by the session just before the stream is deleted.
+ virtual void OnClose();
+
+ // Called when we get a stream reset from the client.
+ virtual void OnStreamReset(QuicRstStreamErrorCode error);
+
+ // Called when we get or send a connection close, and should immediately
+ // close the stream. This is not passed through the sequencer,
+ // but is handled immediately.
+ virtual void ConnectionClose(QuicErrorCode error, bool from_peer);
+
+ // Called when we should process a stream termination or
+ // stream close from the peer.
+ virtual void TerminateFromPeer(bool half_close);
+
+ virtual uint32 ProcessRawData(const char* data, uint32 data_len);
+ virtual uint32 ProcessHeaderData();
+
+ virtual uint32 ProcessData(const char* data, uint32 data_len) = 0;
+
+ virtual bool OnDecompressedData(base::StringPiece data) OVERRIDE;
+ virtual void OnDecompressionError() OVERRIDE;
+
+ // Called to close the stream from this end.
+ virtual void Close(QuicRstStreamErrorCode error);
+
+ // This block of functions wraps the sequencer's functions of the same
+ // name. These methods return uncompressed data until that has
+ // been fully processed. Then they simply delegate to the sequencer.
+ virtual size_t Readv(const struct iovec* iov, size_t iov_len);
+ virtual int GetReadableRegions(iovec* iov, size_t iov_len);
+ virtual bool IsHalfClosed() const;
+ virtual bool HasBytesToRead() const;
+
+ // Called by the session when a decompression blocked stream
+ // becomes unblocked.
+ virtual void OnDecompressorAvailable();
+
+ QuicStreamId id() const { return id_; }
+
+ QuicRstStreamErrorCode stream_error() const { return stream_error_; }
+ QuicErrorCode connection_error() const { return connection_error_; }
+
+ bool read_side_closed() const { return read_side_closed_; }
+ bool write_side_closed() const { return write_side_closed_; }
+
+ uint64 stream_bytes_read() { return stream_bytes_read_; }
+ uint64 stream_bytes_written() { return stream_bytes_written_; }
+
+ const IPEndPoint& GetPeerAddress() const;
+
+ Visitor* visitor() { return visitor_; }
+ void set_visitor(Visitor* visitor) { visitor_ = visitor; }
+
+ QuicSpdyCompressor* compressor();
+
+ // Gets the SSL connection information.
+ bool GetSSLInfo(SSLInfo* ssl_info);
+
+ bool headers_decompressed() const { return headers_decompressed_; }
+
+ protected:
+ // Returns a pair with the number of bytes consumed from data, and a boolean
+ // indicating if the fin bit was consumed. This does not indicate the data
+ // has been sent on the wire: it may have been turned into a packet and queued
+ // if the socket was unexpectedly blocked.
+ //
+ // The default implementation always consumed all bytes and any fin, but
+ // this behavior is not guaranteed for subclasses so callers should check the
+ // return value.
+ virtual QuicConsumedData WriteData(base::StringPiece data, bool fin);
+
+ // Close the read side of the socket. Further frames will not be accepted.
+ virtual void CloseReadSide();
+
+ // Close the write side of the socket. Further writes will fail.
+ void CloseWriteSide();
+
+ bool fin_buffered() { return fin_buffered_; }
+
+ QuicSession* session() { return session_; }
+
+ // Sends as much of 'data' to the connection as the connection will consume,
+ // and then buffers any remaining data in queued_data_.
+ // Returns (data.size(), true) as it always consumed all data: it returns for
+ // convenience to have the same return type as WriteDataInternal.
+ QuicConsumedData WriteOrBuffer(base::StringPiece data, bool fin);
+
+ // Sends as much of 'data' to the connection as the connection will consume.
+ // Returns the number of bytes consumed by the connection.
+ QuicConsumedData WriteDataInternal(base::StringPiece data, bool fin);
+
+ private:
+ friend class test::ReliableQuicStreamPeer;
+ friend class QuicStreamUtils;
+
+ std::list<string> queued_data_;
+
+ QuicStreamSequencer sequencer_;
+ QuicStreamId id_;
+ QuicSession* session_;
+ // Optional visitor of this stream to be notified when the stream is closed.
+ Visitor* visitor_;
+ // Bytes read and written refer to payload bytes only: they do not include
+ // framing, encryption overhead etc.
+ uint64 stream_bytes_read_;
+ uint64 stream_bytes_written_;
+ // True if the headers have been completely decompresssed.
+ bool headers_decompressed_;
+ // ID of the header block sent by the peer, once parsed.
+ QuicHeaderId headers_id_;
+ // Buffer into which we write bytes from the headers_id_
+ // until it is fully parsed.
+ string headers_id_buffer_;
+ // Contains a copy of the decompressed headers_ until they are consumed
+ // via ProcessData or Readv.
+ string decompressed_headers_;
+ // True if an error was encountered during decompression.
+ bool decompression_failed_;
+
+ // Stream error code received from a RstStreamFrame or error code sent by the
+ // visitor or sequencer in the RstStreamFrame.
+ QuicRstStreamErrorCode stream_error_;
+ // Connection error code due to which the stream was closed. |stream_error_|
+ // is set to |QUIC_STREAM_CONNECTION_ERROR| when this happens and consumers
+ // should check |connection_error_|.
+ QuicErrorCode connection_error_;
+
+ // True if the read side is closed and further frames should be rejected.
+ bool read_side_closed_;
+ // True if the write side is closed, and further writes should fail.
+ bool write_side_closed_;
+
+ bool fin_buffered_;
+ bool fin_sent_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_RELIABLE_QUIC_STREAM_H_
diff --git a/chromium/net/quic/reliable_quic_stream_test.cc b/chromium/net/quic/reliable_quic_stream_test.cc
new file mode 100644
index 00000000000..7167a222345
--- /dev/null
+++ b/chromium/net/quic/reliable_quic_stream_test.cc
@@ -0,0 +1,535 @@
+// Copyright (c) 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.
+
+#include "net/quic/reliable_quic_stream.h"
+
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/spdy_utils.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using base::StringPiece;
+using std::min;
+using testing::_;
+using testing::InSequence;
+using testing::Return;
+using testing::SaveArg;
+using testing::StrEq;
+using testing::StrictMock;
+
+namespace net {
+namespace test {
+namespace {
+
+const char kData1[] = "FooAndBar";
+const char kData2[] = "EepAndBaz";
+const size_t kDataLen = 9;
+const QuicGuid kGuid = 42;
+const QuicGuid kStreamId = 3;
+const bool kIsServer = true;
+const bool kShouldProcessData = true;
+
+class TestStream : public ReliableQuicStream {
+ public:
+ TestStream(QuicStreamId id,
+ QuicSession* session,
+ bool should_process_data)
+ : ReliableQuicStream(id, session),
+ should_process_data_(should_process_data) {
+ }
+
+ virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE {
+ EXPECT_NE(0u, data_len);
+ DVLOG(1) << "ProcessData data_len: " << data_len;
+ data_ += string(data, data_len);
+ return should_process_data_ ? data_len : 0;
+ }
+
+ using ReliableQuicStream::WriteData;
+ using ReliableQuicStream::CloseReadSide;
+ using ReliableQuicStream::CloseWriteSide;
+
+ const string& data() const { return data_; }
+
+ private:
+ bool should_process_data_;
+ string data_;
+};
+
+class ReliableQuicStreamTest : public ::testing::TestWithParam<bool> {
+ public:
+ ReliableQuicStreamTest() {
+ headers_[":host"] = "www.google.com";
+ headers_[":path"] = "/index.hml";
+ headers_[":scheme"] = "https";
+ headers_["cookie"] =
+ "__utma=208381060.1228362404.1372200928.1372200928.1372200928.1; "
+ "__utmc=160408618; "
+ "GX=DQAAAOEAAACWJYdewdE9rIrW6qw3PtVi2-d729qaa-74KqOsM1NVQblK4VhX"
+ "hoALMsy6HOdDad2Sz0flUByv7etmo3mLMidGrBoljqO9hSVA40SLqpG_iuKKSHX"
+ "RW3Np4bq0F0SDGDNsW0DSmTS9ufMRrlpARJDS7qAI6M3bghqJp4eABKZiRqebHT"
+ "pMU-RXvTI5D5oCF1vYxYofH_l1Kviuiy3oQ1kS1enqWgbhJ2t61_SNdv-1XJIS0"
+ "O3YeHLmVCs62O6zp89QwakfAWK9d3IDQvVSJzCQsvxvNIvaZFa567MawWlXg0Rh"
+ "1zFMi5vzcns38-8_Sns; "
+ "GA=v*2%2Fmem*57968640*47239936%2Fmem*57968640*47114716%2Fno-nm-"
+ "yj*15%2Fno-cc-yj*5%2Fpc-ch*133685%2Fpc-s-cr*133947%2Fpc-s-t*1339"
+ "47%2Fno-nm-yj*4%2Fno-cc-yj*1%2Fceft-as*1%2Fceft-nqas*0%2Fad-ra-c"
+ "v_p%2Fad-nr-cv_p-f*1%2Fad-v-cv_p*859%2Fad-ns-cv_p-f*1%2Ffn-v-ad%"
+ "2Fpc-t*250%2Fpc-cm*461%2Fpc-s-cr*722%2Fpc-s-t*722%2Fau_p*4"
+ "SICAID=AJKiYcHdKgxum7KMXG0ei2t1-W4OD1uW-ecNsCqC0wDuAXiDGIcT_HA2o1"
+ "3Rs1UKCuBAF9g8rWNOFbxt8PSNSHFuIhOo2t6bJAVpCsMU5Laa6lewuTMYI8MzdQP"
+ "ARHKyW-koxuhMZHUnGBJAM1gJODe0cATO_KGoX4pbbFxxJ5IicRxOrWK_5rU3cdy6"
+ "edlR9FsEdH6iujMcHkbE5l18ehJDwTWmBKBzVD87naobhMMrF6VvnDGxQVGp9Ir_b"
+ "Rgj3RWUoPumQVCxtSOBdX0GlJOEcDTNCzQIm9BSfetog_eP_TfYubKudt5eMsXmN6"
+ "QnyXHeGeK2UINUzJ-D30AFcpqYgH9_1BvYSpi7fc7_ydBU8TaD8ZRxvtnzXqj0RfG"
+ "tuHghmv3aD-uzSYJ75XDdzKdizZ86IG6Fbn1XFhYZM-fbHhm3mVEXnyRW4ZuNOLFk"
+ "Fas6LMcVC6Q8QLlHYbXBpdNFuGbuZGUnav5C-2I_-46lL0NGg3GewxGKGHvHEfoyn"
+ "EFFlEYHsBQ98rXImL8ySDycdLEFvBPdtctPmWCfTxwmoSMLHU2SCVDhbqMWU5b0yr"
+ "JBCScs_ejbKaqBDoB7ZGxTvqlrB__2ZmnHHjCr8RgMRtKNtIeuZAo ";
+ }
+
+ void Initialize(bool stream_should_process_data) {
+ connection_ = new testing::StrictMock<MockConnection>(
+ kGuid, IPEndPoint(), kIsServer);
+ session_.reset(new testing::StrictMock<MockSession>(
+ connection_, kIsServer));
+ stream_.reset(new TestStream(kStreamId, session_.get(),
+ stream_should_process_data));
+ stream2_.reset(new TestStream(kStreamId + 2, session_.get(),
+ stream_should_process_data));
+ compressor_.reset(new QuicSpdyCompressor());
+ decompressor_.reset(new QuicSpdyDecompressor);
+ write_blocked_list_ =
+ QuicSessionPeer::GetWriteblockedStreams(session_.get());
+ }
+
+ protected:
+ MockConnection* connection_;
+ scoped_ptr<MockSession> session_;
+ scoped_ptr<TestStream> stream_;
+ scoped_ptr<TestStream> stream2_;
+ scoped_ptr<QuicSpdyCompressor> compressor_;
+ scoped_ptr<QuicSpdyDecompressor> decompressor_;
+ SpdyHeaderBlock headers_;
+ BlockedList<QuicStreamId>* write_blocked_list_;
+};
+
+TEST_F(ReliableQuicStreamTest, WriteAllData) {
+ Initialize(kShouldProcessData);
+
+ connection_->options()->max_packet_length =
+ 1 + QuicPacketCreator::StreamFramePacketOverhead(
+ connection_->version(), PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ // TODO(rch): figure out how to get StrEq working here.
+ //EXPECT_CALL(*session_, WriteData(kStreamId, StrEq(kData1), _, _)).WillOnce(
+ EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce(
+ Return(QuicConsumedData(kDataLen, true)));
+ EXPECT_EQ(kDataLen, stream_->WriteData(kData1, false).bytes_consumed);
+ EXPECT_TRUE(write_blocked_list_->IsEmpty());
+}
+
+// TODO(rtenneti): Death tests crash on OS_ANDROID.
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) && !defined(OS_ANDROID)
+TEST_F(ReliableQuicStreamTest, NoBlockingIfNoDataOrFin) {
+ Initialize(kShouldProcessData);
+
+ // Write no data and no fin. If we consume nothing we should not be write
+ // blocked.
+ EXPECT_DEBUG_DEATH({
+ EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce(
+ Return(QuicConsumedData(0, false)));
+ stream_->WriteData(StringPiece(), false);
+ EXPECT_TRUE(write_blocked_list_->IsEmpty());
+ }, "");
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) && !defined(OS_ANDROID)
+
+TEST_F(ReliableQuicStreamTest, BlockIfOnlySomeDataConsumed) {
+ Initialize(kShouldProcessData);
+
+ // Write some data and no fin. If we consume some but not all of the data,
+ // we should be write blocked a not all the data was consumed.
+ EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce(
+ Return(QuicConsumedData(1, false)));
+ stream_->WriteData(StringPiece(kData1, 2), false);
+ ASSERT_EQ(1, write_blocked_list_->NumObjects());
+}
+
+
+TEST_F(ReliableQuicStreamTest, BlockIfFinNotConsumedWithData) {
+ Initialize(kShouldProcessData);
+
+ // Write some data and no fin. If we consume all the data but not the fin,
+ // we should be write blocked because the fin was not consumed.
+ // (This should never actually happen as the fin should be sent out with the
+ // last data)
+ EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce(
+ Return(QuicConsumedData(2, false)));
+ stream_->WriteData(StringPiece(kData1, 2), true);
+ ASSERT_EQ(1, write_blocked_list_->NumObjects());
+}
+
+TEST_F(ReliableQuicStreamTest, BlockIfSoloFinNotConsumed) {
+ Initialize(kShouldProcessData);
+
+ // Write no data and a fin. If we consume nothing we should be write blocked,
+ // as the fin was not consumed.
+ EXPECT_CALL(*session_, WriteData(kStreamId, _, _, _)).WillOnce(
+ Return(QuicConsumedData(0, false)));
+ stream_->WriteData(StringPiece(), true);
+ ASSERT_EQ(1, write_blocked_list_->NumObjects());
+}
+
+TEST_F(ReliableQuicStreamTest, WriteData) {
+ Initialize(kShouldProcessData);
+
+ EXPECT_TRUE(write_blocked_list_->IsEmpty());
+ connection_->options()->max_packet_length =
+ 1 + QuicPacketCreator::StreamFramePacketOverhead(
+ connection_->version(), PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP);
+ // TODO(rch): figure out how to get StrEq working here.
+ //EXPECT_CALL(*session_, WriteData(_, StrEq(kData1), _, _)).WillOnce(
+ EXPECT_CALL(*session_, WriteData(_, _, _, _)).WillOnce(
+ Return(QuicConsumedData(kDataLen - 1, false)));
+ // The return will be kDataLen, because the last byte gets buffered.
+ EXPECT_EQ(kDataLen, stream_->WriteData(kData1, false).bytes_consumed);
+ EXPECT_FALSE(write_blocked_list_->IsEmpty());
+
+ // Queue a bytes_consumed write.
+ EXPECT_EQ(kDataLen, stream_->WriteData(kData2, false).bytes_consumed);
+
+ // Make sure we get the tail of the first write followed by the bytes_consumed
+ InSequence s;
+ //EXPECT_CALL(*session_, WriteData(_, StrEq(&kData1[kDataLen - 1]), _, _)).
+ EXPECT_CALL(*session_, WriteData(_, _, _, _)).
+ WillOnce(Return(QuicConsumedData(1, false)));
+ //EXPECT_CALL(*session_, WriteData(_, StrEq(kData2), _, _)).
+ EXPECT_CALL(*session_, WriteData(_, _, _, _)).
+ WillOnce(Return(QuicConsumedData(kDataLen - 2, false)));
+ stream_->OnCanWrite();
+
+ // And finally the end of the bytes_consumed
+ //EXPECT_CALL(*session_, WriteData(_, StrEq(&kData2[kDataLen - 2]), _, _)).
+ EXPECT_CALL(*session_, WriteData(_, _, _, _)).
+ WillOnce(Return(QuicConsumedData(2, true)));
+ stream_->OnCanWrite();
+}
+
+TEST_F(ReliableQuicStreamTest, ConnectionCloseAfterStreamClose) {
+ Initialize(kShouldProcessData);
+
+ stream_->CloseReadSide();
+ stream_->CloseWriteSide();
+ EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+ EXPECT_EQ(QUIC_NO_ERROR, stream_->connection_error());
+ stream_->ConnectionClose(QUIC_INTERNAL_ERROR, false);
+ EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+ EXPECT_EQ(QUIC_NO_ERROR, stream_->connection_error());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeaders) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame(kStreamId, false, 0, compressed_headers);
+
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_), stream_->data());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersWithInvalidHeaderId) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ compressed_headers.replace(0, 1, 1, '\xFF'); // Illegal header id.
+ QuicStreamFrame frame(kStreamId, false, 0, compressed_headers);
+
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_HEADER_ID));
+ stream_->OnStreamFrame(frame);
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBody) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ string body = "this is the body";
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, data);
+
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
+ stream_->data());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyFragments) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ string body = "this is the body";
+ string data = compressed_headers + body;
+
+ for (size_t fragment_size = 1; fragment_size < data.size(); ++fragment_size) {
+ Initialize(kShouldProcessData);
+ for (size_t offset = 0; offset < data.size(); offset += fragment_size) {
+ size_t remaining_data = data.length() - offset;
+ StringPiece fragment(data.data() + offset,
+ min(fragment_size, remaining_data));
+ QuicStreamFrame frame(kStreamId, false, offset, fragment);
+
+ stream_->OnStreamFrame(frame);
+ }
+ ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
+ stream_->data()) << "fragment_size: " << fragment_size;
+ }
+
+ for (size_t split_point = 1; split_point < data.size() - 1; ++split_point) {
+ Initialize(kShouldProcessData);
+
+ StringPiece fragment1(data.data(), split_point);
+ QuicStreamFrame frame1(kStreamId, false, 0, fragment1);
+ stream_->OnStreamFrame(frame1);
+
+ StringPiece fragment2(data.data() + split_point, data.size() - split_point);
+ QuicStreamFrame frame2(kStreamId, false, split_point, fragment2);
+ stream_->OnStreamFrame(frame2);
+
+ ASSERT_EQ(SpdyUtils::SerializeUncompressedHeaders(headers_) + body,
+ stream_->data()) << "split_point: " << split_point;
+ }
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyReadv) {
+ Initialize(!kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ string body = "this is the body";
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, data);
+ string uncompressed_headers =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+ string uncompressed_data = uncompressed_headers + body;
+
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(uncompressed_headers, stream_->data());
+
+ char buffer[2048];
+ ASSERT_LT(data.length(), arraysize(buffer));
+ struct iovec vec;
+ vec.iov_base = buffer;
+ vec.iov_len = arraysize(buffer);
+
+ size_t bytes_read = stream_->Readv(&vec, 1);
+ EXPECT_EQ(uncompressed_headers.length(), bytes_read);
+ EXPECT_EQ(uncompressed_headers, string(buffer, bytes_read));
+
+ bytes_read = stream_->Readv(&vec, 1);
+ EXPECT_EQ(body.length(), bytes_read);
+ EXPECT_EQ(body, string(buffer, bytes_read));
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersAndBodyIncrementalReadv) {
+ Initialize(!kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ string body = "this is the body";
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, data);
+ string uncompressed_headers =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+ string uncompressed_data = uncompressed_headers + body;
+
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(uncompressed_headers, stream_->data());
+
+ char buffer[1];
+ struct iovec vec;
+ vec.iov_base = buffer;
+ vec.iov_len = arraysize(buffer);
+ for (size_t i = 0; i < uncompressed_data.length(); ++i) {
+ size_t bytes_read = stream_->Readv(&vec, 1);
+ ASSERT_EQ(1u, bytes_read);
+ EXPECT_EQ(uncompressed_data.data()[i], buffer[0]);
+ }
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersUsingReadvWithMultipleIovecs) {
+ Initialize(!kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ string body = "this is the body";
+ string data = compressed_headers + body;
+ QuicStreamFrame frame(kStreamId, false, 0, data);
+ string uncompressed_headers =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+ string uncompressed_data = uncompressed_headers + body;
+
+ stream_->OnStreamFrame(frame);
+ EXPECT_EQ(uncompressed_headers, stream_->data());
+
+ char buffer1[1];
+ char buffer2[1];
+ struct iovec vec[2];
+ vec[0].iov_base = buffer1;
+ vec[0].iov_len = arraysize(buffer1);
+ vec[1].iov_base = buffer2;
+ vec[1].iov_len = arraysize(buffer2);
+ for (size_t i = 0; i < uncompressed_data.length(); i += 2) {
+ size_t bytes_read = stream_->Readv(vec, 2);
+ ASSERT_EQ(2u, bytes_read) << i;
+ ASSERT_EQ(uncompressed_data.data()[i], buffer1[0]) << i;
+ ASSERT_EQ(uncompressed_data.data()[i + 1], buffer2[0]) << i;
+ }
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessCorruptHeadersEarly) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers1 = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1);
+ string decompressed_headers1 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ headers_["content-type"] = "text/plain";
+ string compressed_headers2 = compressor_->CompressHeaders(headers_);
+ // Corrupt the compressed data.
+ compressed_headers2[compressed_headers2.length() - 1] ^= 0xA1;
+ QuicStreamFrame frame2(stream2_->id(), false, 0, compressed_headers2);
+ string decompressed_headers2 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ // Deliver frame2 to stream2 out of order. The decompressor is not
+ // available yet, so no data will be processed. The compressed data
+ // will be buffered until OnDecompressorAvailable() is called
+ // to process it.
+ stream2_->OnStreamFrame(frame2);
+ EXPECT_EQ("", stream2_->data());
+
+ // Now deliver frame1 to stream1. The decompressor is available so
+ // the data will be processed, and the decompressor will become
+ // available for stream2.
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(decompressed_headers1, stream_->data());
+
+ // Verify that the decompressor is available, and inform stream2
+ // that it can now decompress the buffered compressed data. Since
+ // the compressed data is corrupt, the stream will shutdown the session.
+ EXPECT_EQ(2u, session_->decompressor()->current_header_id());
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_DECOMPRESSION_FAILURE));
+ stream2_->OnDecompressorAvailable();
+ EXPECT_EQ("", stream2_->data());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessPartialHeadersEarly) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers1 = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1);
+ string decompressed_headers1 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ headers_["content-type"] = "text/plain";
+ string compressed_headers2 = compressor_->CompressHeaders(headers_);
+ string partial_compressed_headers =
+ compressed_headers2.substr(0, compressed_headers2.length() / 2);
+ QuicStreamFrame frame2(stream2_->id(), false, 0, partial_compressed_headers);
+ string decompressed_headers2 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ // Deliver frame2 to stream2 out of order. The decompressor is not
+ // available yet, so no data will be processed. The compressed data
+ // will be buffered until OnDecompressorAvailable() is called
+ // to process it.
+ stream2_->OnStreamFrame(frame2);
+ EXPECT_EQ("", stream2_->data());
+
+ // Now deliver frame1 to stream1. The decompressor is available so
+ // the data will be processed, and the decompressor will become
+ // available for stream2.
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(decompressed_headers1, stream_->data());
+
+ // Verify that the decompressor is available, and inform stream2
+ // that it can now decompress the buffered compressed data. Since
+ // the compressed data is incomplete it will not be passed to
+ // the stream.
+ EXPECT_EQ(2u, session_->decompressor()->current_header_id());
+ stream2_->OnDecompressorAvailable();
+ EXPECT_EQ("", stream2_->data());
+
+ // Now send remaining data and verify that we have now received the
+ // compressed headers.
+ string remaining_compressed_headers =
+ compressed_headers2.substr(partial_compressed_headers.length());
+
+ QuicStreamFrame frame3(stream2_->id(), false,
+ partial_compressed_headers.length(),
+ remaining_compressed_headers);
+ stream2_->OnStreamFrame(frame3);
+ EXPECT_EQ(decompressed_headers2, stream2_->data());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersEarly) {
+ Initialize(kShouldProcessData);
+
+ string compressed_headers1 = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers1);
+ string decompressed_headers1 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ headers_["content-type"] = "text/plain";
+ string compressed_headers2 = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame2(stream2_->id(), false, 0, compressed_headers2);
+ string decompressed_headers2 =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ // Deliver frame2 to stream2 out of order. The decompressor is not
+ // available yet, so no data will be processed. The compressed data
+ // will be buffered until OnDecompressorAvailable() is called
+ // to process it.
+ stream2_->OnStreamFrame(frame2);
+ EXPECT_EQ("", stream2_->data());
+
+ // Now deliver frame1 to stream1. The decompressor is available so
+ // the data will be processed, and the decompressor will become
+ // available for stream2.
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(decompressed_headers1, stream_->data());
+
+ // Verify that the decompressor is available, and inform stream2
+ // that it can now decompress the buffered compressed data.
+ EXPECT_EQ(2u, session_->decompressor()->current_header_id());
+ stream2_->OnDecompressorAvailable();
+ EXPECT_EQ(decompressed_headers2, stream2_->data());
+}
+
+TEST_F(ReliableQuicStreamTest, ProcessHeadersDelay) {
+ Initialize(!kShouldProcessData);
+
+ string compressed_headers = compressor_->CompressHeaders(headers_);
+ QuicStreamFrame frame1(stream_->id(), false, 0, compressed_headers);
+ string decompressed_headers =
+ SpdyUtils::SerializeUncompressedHeaders(headers_);
+
+ // Send the headers to the stream and verify they were decompressed.
+ stream_->OnStreamFrame(frame1);
+ EXPECT_EQ(2u, session_->decompressor()->current_header_id());
+
+ // Verify that we are now able to handle the body data,
+ // even though the stream has not processed the headers.
+ EXPECT_CALL(*connection_, SendConnectionClose(QUIC_INVALID_HEADER_ID))
+ .Times(0);
+ QuicStreamFrame frame2(stream_->id(), false, compressed_headers.length(),
+ "body data");
+ stream_->OnStreamFrame(frame2);
+}
+
+} // namespace
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/spdy_utils.cc b/chromium/net/quic/spdy_utils.cc
new file mode 100644
index 00000000000..350819e9143
--- /dev/null
+++ b/chromium/net/quic/spdy_utils.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 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/quic/spdy_utils.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+using std::string;
+
+namespace net {
+
+// static
+string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) {
+ int length = SpdyFramer::GetSerializedLength(SPDY3, &headers);
+ SpdyFrameBuilder builder(length);
+ SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers);
+ scoped_ptr<SpdyFrame> block(builder.take());
+ return string(block->data(), length);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/spdy_utils.h b/chromium/net/quic/spdy_utils.h
new file mode 100644
index 00000000000..ffc78f08da2
--- /dev/null
+++ b/chromium/net/quic/spdy_utils.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2013 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_QUIC_SPDY_UTILS_H_
+#define NET_QUIC_SPDY_UTILS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE SpdyUtils {
+ public:
+ static std::string SerializeUncompressedHeaders(
+ const SpdyHeaderBlock& headers);
+};
+
+} // namespace net
+
+#endif // NET_QUIC_SPDY_UTILS_H_
diff --git a/chromium/net/quic/test_tools/crypto_test_utils.cc b/chromium/net/quic/test_tools/crypto_test_utils.cc
new file mode 100644
index 00000000000..73b5bab080a
--- /dev/null
+++ b/chromium/net/quic/test_tools/crypto_test_utils.cc
@@ -0,0 +1,505 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/crypto_test_utils.h"
+
+#include "net/quic/crypto/channel_id.h"
+#include "net/quic/crypto/common_cert_set.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_crypto_server_stream.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/quic/test_tools/simple_quic_framer.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+namespace {
+
+// CryptoFramerVisitor is a framer visitor that records handshake messages.
+class CryptoFramerVisitor : public CryptoFramerVisitorInterface {
+ public:
+ CryptoFramerVisitor()
+ : error_(false) {
+ }
+
+ virtual void OnError(CryptoFramer* framer) OVERRIDE {
+ error_ = true;
+ }
+
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE {
+ messages_.push_back(message);
+ }
+
+ bool error() const {
+ return error_;
+ }
+
+ const vector<CryptoHandshakeMessage>& messages() const {
+ return messages_;
+ }
+
+ private:
+ bool error_;
+ vector<CryptoHandshakeMessage> messages_;
+};
+
+// MovePackets parses crypto handshake messages from packet number
+// |*inout_packet_index| through to the last packet and has |dest_stream|
+// process them. |*inout_packet_index| is updated with an index one greater
+// than the last packet processed.
+void MovePackets(PacketSavingConnection* source_conn,
+ size_t *inout_packet_index,
+ QuicCryptoStream* dest_stream,
+ PacketSavingConnection* dest_conn) {
+ SimpleQuicFramer framer;
+ CryptoFramer crypto_framer;
+ CryptoFramerVisitor crypto_visitor;
+
+ // In order to properly test the code we need to perform encryption and
+ // decryption so that the crypters latch when expected. The crypters are in
+ // |dest_conn|, but we don't want to try and use them there. Instead we swap
+ // them into |framer|, perform the decryption with them, and then swap them
+ // back.
+ QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
+
+ crypto_framer.set_visitor(&crypto_visitor);
+
+ size_t index = *inout_packet_index;
+ for (; index < source_conn->encrypted_packets_.size(); index++) {
+ ASSERT_TRUE(framer.ProcessPacket(*source_conn->encrypted_packets_[index]));
+ for (vector<QuicStreamFrame>::const_iterator
+ i = framer.stream_frames().begin();
+ i != framer.stream_frames().end(); ++i) {
+ ASSERT_TRUE(crypto_framer.ProcessInput(i->data));
+ ASSERT_FALSE(crypto_visitor.error());
+ }
+ }
+ *inout_packet_index = index;
+
+ QuicConnectionPeer::SwapCrypters(dest_conn, framer.framer());
+
+ ASSERT_EQ(0u, crypto_framer.InputBytesRemaining());
+
+ for (vector<CryptoHandshakeMessage>::const_iterator
+ i = crypto_visitor.messages().begin();
+ i != crypto_visitor.messages().end(); ++i) {
+ dest_stream->OnHandshakeMessage(*i);
+ }
+}
+
+// HexChar parses |c| as a hex character. If valid, it sets |*value| to the
+// value of the hex character and returns true. Otherwise it returns false.
+bool HexChar(char c, uint8* value) {
+ if (c >= '0' && c <= '9') {
+ *value = c - '0';
+ return true;
+ }
+ if (c >= 'a' && c <= 'f') {
+ *value = c - 'a' + 10;
+ return true;
+ }
+ if (c >= 'A' && c <= 'F') {
+ *value = c - 'A' + 10;
+ return true;
+ }
+ return false;
+}
+
+} // anonymous namespace
+
+CryptoTestUtils::FakeClientOptions::FakeClientOptions()
+ : dont_verify_certs(false),
+ channel_id_enabled(false) {
+}
+
+// static
+int CryptoTestUtils::HandshakeWithFakeServer(
+ PacketSavingConnection* client_conn,
+ QuicCryptoClientStream* client) {
+ QuicGuid guid(1);
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ IPEndPoint addr = IPEndPoint(ip, 1);
+ PacketSavingConnection* server_conn =
+ new PacketSavingConnection(guid, addr, true);
+ TestSession server_session(server_conn, QuicConfig(), true);
+
+ QuicCryptoServerConfig crypto_config(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance());
+ SetupCryptoServerConfigForTest(
+ server_session.connection()->clock(),
+ server_session.connection()->random_generator(),
+ server_session.config(), &crypto_config);
+
+ QuicCryptoServerStream server(crypto_config, &server_session);
+ server_session.SetCryptoStream(&server);
+
+ // The client's handshake must have been started already.
+ CHECK_NE(0u, client_conn->packets_.size());
+
+ CommunicateHandshakeMessages(client_conn, client, server_conn, &server);
+
+ CompareClientAndServerKeys(client, &server);
+
+ return client->num_sent_client_hellos();
+}
+
+// static
+int CryptoTestUtils::HandshakeWithFakeClient(
+ PacketSavingConnection* server_conn,
+ QuicCryptoServerStream* server,
+ const FakeClientOptions& options) {
+ QuicGuid guid(1);
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ IPEndPoint addr = IPEndPoint(ip, 1);
+ PacketSavingConnection* client_conn =
+ new PacketSavingConnection(guid, addr, false);
+ TestSession client_session(client_conn, QuicConfig(), false);
+ QuicCryptoClientConfig crypto_config;
+
+ client_session.config()->SetDefaults();
+ crypto_config.SetDefaults();
+ // TODO(rtenneti): Enable testing of ProofVerifier.
+ // if (!options.dont_verify_certs) {
+ // crypto_config.SetProofVerifier(ProofVerifierForTesting());
+ // }
+ if (options.channel_id_enabled) {
+ crypto_config.SetChannelIDSigner(ChannelIDSignerForTesting());
+ }
+ QuicCryptoClientStream client("test.example.com", &client_session,
+ &crypto_config);
+ client_session.SetCryptoStream(&client);
+
+ CHECK(client.CryptoConnect());
+ CHECK_EQ(1u, client_conn->packets_.size());
+
+ CommunicateHandshakeMessages(client_conn, &client, server_conn, server);
+
+ CompareClientAndServerKeys(&client, server);
+
+ if (options.channel_id_enabled) {
+ EXPECT_EQ(crypto_config.channel_id_signer()->GetKeyForHostname(
+ "test.example.com"),
+ server->crypto_negotiated_params().channel_id);
+ }
+
+ return client.num_sent_client_hellos();
+}
+
+// static
+void CryptoTestUtils::SetupCryptoServerConfigForTest(
+ const QuicClock* clock,
+ QuicRandom* rand,
+ QuicConfig* config,
+ QuicCryptoServerConfig* crypto_config) {
+ config->SetDefaults();
+ QuicCryptoServerConfig::ConfigOptions options;
+ options.channel_id_enabled = true;
+ scoped_ptr<CryptoHandshakeMessage> scfg(
+ crypto_config->AddDefaultConfig(rand, clock, options));
+}
+
+// static
+void CryptoTestUtils::CommunicateHandshakeMessages(
+ PacketSavingConnection* a_conn,
+ QuicCryptoStream* a,
+ PacketSavingConnection* b_conn,
+ QuicCryptoStream* b) {
+ size_t a_i = 0, b_i = 0;
+ while (!a->handshake_confirmed()) {
+ ASSERT_GT(a_conn->packets_.size(), a_i);
+ LOG(INFO) << "Processing " << a_conn->packets_.size() - a_i
+ << " packets a->b";
+ MovePackets(a_conn, &a_i, b, b_conn);
+
+ ASSERT_GT(b_conn->packets_.size(), b_i);
+ LOG(INFO) << "Processing " << b_conn->packets_.size() - b_i
+ << " packets b->a";
+ if (b_conn->packets_.size() - b_i == 2) {
+ LOG(INFO) << "here";
+ }
+ MovePackets(b_conn, &b_i, a, a_conn);
+ }
+}
+
+// static
+string CryptoTestUtils::GetValueForTag(const CryptoHandshakeMessage& message,
+ QuicTag tag) {
+ QuicTagValueMap::const_iterator it = message.tag_value_map().find(tag);
+ if (it == message.tag_value_map().end()) {
+ return string();
+ }
+ return it->second;
+}
+
+class MockCommonCertSets : public CommonCertSets {
+ public:
+ MockCommonCertSets(StringPiece cert, uint64 hash, uint32 index)
+ : cert_(cert.as_string()),
+ hash_(hash),
+ index_(index) {
+ }
+
+ virtual StringPiece GetCommonHashes() const OVERRIDE {
+ CHECK(false) << "not implemented";
+ return StringPiece();
+ }
+
+ virtual StringPiece GetCert(uint64 hash, uint32 index) const OVERRIDE {
+ if (hash == hash_ && index == index_) {
+ return cert_;
+ }
+ return StringPiece();
+ }
+
+ virtual bool MatchCert(StringPiece cert,
+ StringPiece common_set_hashes,
+ uint64* out_hash,
+ uint32* out_index) const OVERRIDE {
+ if (cert != cert_) {
+ return false;
+ }
+
+ if (common_set_hashes.size() % sizeof(uint64) != 0) {
+ return false;
+ }
+ bool client_has_set = false;
+ for (size_t i = 0; i < common_set_hashes.size(); i += sizeof(uint64)) {
+ uint64 hash;
+ memcpy(&hash, common_set_hashes.data() + i, sizeof(hash));
+ if (hash == hash_) {
+ client_has_set = true;
+ break;
+ }
+ }
+
+ if (!client_has_set) {
+ return false;
+ }
+
+ *out_hash = hash_;
+ *out_index = index_;
+ return true;
+ }
+
+ private:
+ const string cert_;
+ const uint64 hash_;
+ const uint32 index_;
+};
+
+CommonCertSets* CryptoTestUtils::MockCommonCertSets(StringPiece cert,
+ uint64 hash,
+ uint32 index) {
+ return new class MockCommonCertSets(cert, hash, index);
+}
+
+void CryptoTestUtils::CompareClientAndServerKeys(
+ QuicCryptoClientStream* client,
+ QuicCryptoServerStream* server) {
+ const QuicEncrypter* client_encrypter(
+ client->session()->connection()->encrypter(ENCRYPTION_INITIAL));
+ const QuicDecrypter* client_decrypter(
+ client->session()->connection()->decrypter());
+ const QuicEncrypter* client_forward_secure_encrypter(
+ client->session()->connection()->encrypter(ENCRYPTION_FORWARD_SECURE));
+ const QuicDecrypter* client_forward_secure_decrypter(
+ client->session()->connection()->alternative_decrypter());
+ const QuicEncrypter* server_encrypter(
+ server->session()->connection()->encrypter(ENCRYPTION_INITIAL));
+ const QuicDecrypter* server_decrypter(
+ server->session()->connection()->decrypter());
+ const QuicEncrypter* server_forward_secure_encrypter(
+ server->session()->connection()->encrypter(ENCRYPTION_FORWARD_SECURE));
+ const QuicDecrypter* server_forward_secure_decrypter(
+ server->session()->connection()->alternative_decrypter());
+
+ StringPiece client_encrypter_key = client_encrypter->GetKey();
+ StringPiece client_encrypter_iv = client_encrypter->GetNoncePrefix();
+ StringPiece client_decrypter_key = client_decrypter->GetKey();
+ StringPiece client_decrypter_iv = client_decrypter->GetNoncePrefix();
+ StringPiece client_forward_secure_encrypter_key =
+ client_forward_secure_encrypter->GetKey();
+ StringPiece client_forward_secure_encrypter_iv =
+ client_forward_secure_encrypter->GetNoncePrefix();
+ StringPiece client_forward_secure_decrypter_key =
+ client_forward_secure_decrypter->GetKey();
+ StringPiece client_forward_secure_decrypter_iv =
+ client_forward_secure_decrypter->GetNoncePrefix();
+ StringPiece server_encrypter_key = server_encrypter->GetKey();
+ StringPiece server_encrypter_iv = server_encrypter->GetNoncePrefix();
+ StringPiece server_decrypter_key = server_decrypter->GetKey();
+ StringPiece server_decrypter_iv = server_decrypter->GetNoncePrefix();
+ StringPiece server_forward_secure_encrypter_key =
+ server_forward_secure_encrypter->GetKey();
+ StringPiece server_forward_secure_encrypter_iv =
+ server_forward_secure_encrypter->GetNoncePrefix();
+ StringPiece server_forward_secure_decrypter_key =
+ server_forward_secure_decrypter->GetKey();
+ StringPiece server_forward_secure_decrypter_iv =
+ server_forward_secure_decrypter->GetNoncePrefix();
+
+ CompareCharArraysWithHexError("client write key",
+ client_encrypter_key.data(),
+ client_encrypter_key.length(),
+ server_decrypter_key.data(),
+ server_decrypter_key.length());
+ CompareCharArraysWithHexError("client write IV",
+ client_encrypter_iv.data(),
+ client_encrypter_iv.length(),
+ server_decrypter_iv.data(),
+ server_decrypter_iv.length());
+ CompareCharArraysWithHexError("server write key",
+ server_encrypter_key.data(),
+ server_encrypter_key.length(),
+ client_decrypter_key.data(),
+ client_decrypter_key.length());
+ CompareCharArraysWithHexError("server write IV",
+ server_encrypter_iv.data(),
+ server_encrypter_iv.length(),
+ client_decrypter_iv.data(),
+ client_decrypter_iv.length());
+ CompareCharArraysWithHexError("client forward secure write key",
+ client_forward_secure_encrypter_key.data(),
+ client_forward_secure_encrypter_key.length(),
+ server_forward_secure_decrypter_key.data(),
+ server_forward_secure_decrypter_key.length());
+ CompareCharArraysWithHexError("client forward secure write IV",
+ client_forward_secure_encrypter_iv.data(),
+ client_forward_secure_encrypter_iv.length(),
+ server_forward_secure_decrypter_iv.data(),
+ server_forward_secure_decrypter_iv.length());
+ CompareCharArraysWithHexError("server forward secure write key",
+ server_forward_secure_encrypter_key.data(),
+ server_forward_secure_encrypter_key.length(),
+ client_forward_secure_decrypter_key.data(),
+ client_forward_secure_decrypter_key.length());
+ CompareCharArraysWithHexError("server forward secure write IV",
+ server_forward_secure_encrypter_iv.data(),
+ server_forward_secure_encrypter_iv.length(),
+ client_forward_secure_decrypter_iv.data(),
+ client_forward_secure_decrypter_iv.length());
+}
+
+// static
+QuicTag CryptoTestUtils::ParseTag(const char* tagstr) {
+ const size_t len = strlen(tagstr);
+ CHECK_NE(0u, len);
+
+ QuicTag tag = 0;
+
+ if (tagstr[0] == '#') {
+ CHECK_EQ(static_cast<size_t>(1 + 2*4), len);
+ tagstr++;
+
+ for (size_t i = 0; i < 8; i++) {
+ tag <<= 4;
+
+ uint8 v = 0;
+ CHECK(HexChar(tagstr[i], &v));
+ tag |= v;
+ }
+
+ return tag;
+ }
+
+ CHECK_LE(len, 4u);
+ for (size_t i = 0; i < 4; i++) {
+ tag >>= 8;
+ if (i < len) {
+ tag |= static_cast<uint32>(tagstr[i]) << 24;
+ }
+ }
+
+ return tag;
+}
+
+// static
+CryptoHandshakeMessage CryptoTestUtils::Message(const char* message_tag, ...) {
+ va_list ap;
+ va_start(ap, message_tag);
+
+ CryptoHandshakeMessage message = BuildMessage(message_tag, ap);
+ va_end(ap);
+ return message;
+}
+
+// static
+CryptoHandshakeMessage CryptoTestUtils::BuildMessage(const char* message_tag,
+ va_list ap) {
+ CryptoHandshakeMessage msg;
+ msg.set_tag(ParseTag(message_tag));
+
+ for (;;) {
+ const char* tagstr = va_arg(ap, const char*);
+ if (tagstr == NULL) {
+ break;
+ }
+
+ if (tagstr[0] == '$') {
+ // Special value.
+ const char* const special = tagstr + 1;
+ if (strcmp(special, "padding") == 0) {
+ const int min_bytes = va_arg(ap, int);
+ msg.set_minimum_size(min_bytes);
+ } else {
+ CHECK(false) << "Unknown special value: " << special;
+ }
+
+ continue;
+ }
+
+ const QuicTag tag = ParseTag(tagstr);
+ const char* valuestr = va_arg(ap, const char*);
+
+ size_t len = strlen(valuestr);
+ if (len > 0 && valuestr[0] == '#') {
+ valuestr++;
+ len--;
+
+ CHECK(len % 2 == 0);
+ scoped_ptr<uint8[]> buf(new uint8[len/2]);
+
+ for (size_t i = 0; i < len/2; i++) {
+ uint8 v = 0;
+ CHECK(HexChar(valuestr[i*2], &v));
+ buf[i] = v << 4;
+ CHECK(HexChar(valuestr[i*2 + 1], &v));
+ buf[i] |= v;
+ }
+
+ msg.SetStringPiece(
+ tag, StringPiece(reinterpret_cast<char*>(buf.get()), len/2));
+ continue;
+ }
+
+ msg.SetStringPiece(tag, valuestr);
+ }
+
+ // The CryptoHandshakeMessage needs to be serialized and parsed to ensure
+ // that any padding is included.
+ scoped_ptr<QuicData> bytes(CryptoFramer::ConstructHandshakeMessage(msg));
+ scoped_ptr<CryptoHandshakeMessage> parsed(
+ CryptoFramer::ParseMessage(bytes->AsStringPiece()));
+ CHECK(parsed.get());
+
+ return *parsed;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/crypto_test_utils.h b/chromium/net/quic/test_tools/crypto_test_utils.h
new file mode 100644
index 00000000000..7b0c95274d5
--- /dev/null
+++ b/chromium/net/quic/test_tools/crypto_test_utils.h
@@ -0,0 +1,132 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
+#define NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
+
+#include <stdarg.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ChannelIDSigner;
+class CommonCertSets;
+class ProofSource;
+class ProofVerifier;
+class QuicClock;
+class QuicConfig;
+class QuicCryptoClientStream;
+class QuicCryptoServerConfig;
+class QuicCryptoServerStream;
+class QuicCryptoStream;
+class QuicRandom;
+
+namespace test {
+
+class PacketSavingConnection;
+
+class CryptoTestUtils {
+ public:
+ // FakeClientOptions bundles together a number of options for configuring
+ // HandshakeWithFakeClient.
+ struct FakeClientOptions {
+ FakeClientOptions();
+
+ // If dont_verify_certs is true then no ProofVerifier is set on the client.
+ // Thus no certificates will be requested or checked.
+ bool dont_verify_certs;
+
+ // If channel_id_enabled is true then the client will attempt to send a
+ // ChannelID. The key will be the same as is returned by
+ // ChannelIDSigner's |GetKeyForHostname|.
+ bool channel_id_enabled;
+ };
+
+ // returns: the number of client hellos that the client sent.
+ static int HandshakeWithFakeServer(PacketSavingConnection* client_conn,
+ QuicCryptoClientStream* client);
+
+ // returns: the number of client hellos that the client sent.
+ static int HandshakeWithFakeClient(PacketSavingConnection* server_conn,
+ QuicCryptoServerStream* server,
+ const FakeClientOptions& options);
+
+ // SetupCryptoServerConfigForTest configures |config| and |crypto_config|
+ // with sensible defaults for testing.
+ static void SetupCryptoServerConfigForTest(
+ const QuicClock* clock,
+ QuicRandom* rand,
+ QuicConfig* config,
+ QuicCryptoServerConfig* crypto_config);
+
+ // CommunicateHandshakeMessages moves messages from |a| to |b| and back until
+ // |a|'s handshake has completed.
+ static void CommunicateHandshakeMessages(PacketSavingConnection* a_conn,
+ QuicCryptoStream* a,
+ PacketSavingConnection* b_conn,
+ QuicCryptoStream* b);
+
+ // Returns the value for the tag |tag| in the tag value map of |message|.
+ static std::string GetValueForTag(const CryptoHandshakeMessage& message,
+ QuicTag tag);
+
+ // Returns a |ProofSource| that serves up test certificates.
+ static ProofSource* ProofSourceForTesting();
+
+ // Returns a |ProofVerifier| that uses the QUIC testing root CA.
+ static ProofVerifier* ProofVerifierForTesting();
+
+ // MockCommonCertSets returns a CommonCertSets that contains a single set with
+ // hash |hash|, consisting of the certificate |cert| at index |index|.
+ static CommonCertSets* MockCommonCertSets(base::StringPiece cert,
+ uint64 hash,
+ uint32 index);
+
+ // ParseTag returns a QuicTag from parsing |tagstr|. |tagstr| may either be
+ // in the format "EXMP" (i.e. ASCII format), or "#11223344" (an explicit hex
+ // format). It CHECK fails if there's a parse error.
+ static QuicTag ParseTag(const char* tagstr);
+
+ // Message constructs a handshake message from a variable number of
+ // arguments. |message_tag| is passed to |ParseTag| and used as the tag of
+ // the resulting message. The arguments are taken in pairs and NULL
+ // terminated. The first of each pair is the tag of a tag/value and is given
+ // as an argument to |ParseTag|. The second is the value of the tag/value
+ // pair and is either a hex dump, preceeded by a '#', or a raw value.
+ //
+ // Message(
+ // "CHLO",
+ // "NOCE", "#11223344",
+ // "SNI", "www.example.com",
+ // NULL);
+ static CryptoHandshakeMessage Message(const char* message_tag, ...);
+
+ // BuildMessage is the same as |Message|, but takes the variable arguments
+ // explicitly. TODO(rtenneti): Investigate whether it'd be better for
+ // Message() and BuildMessage() to return a CryptoHandshakeMessage* pointer
+ // instead, to avoid copying the return value.
+ static CryptoHandshakeMessage BuildMessage(const char* message_tag,
+ va_list ap);
+
+ // ChannelIDSignerForTesting returns a ChannelIDSigner that generates keys
+ // deterministically based on the hostname given in the Sign call.
+ static ChannelIDSigner* ChannelIDSignerForTesting();
+
+ private:
+ static void CompareClientAndServerKeys(QuicCryptoClientStream* client,
+ QuicCryptoServerStream* server);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_CRYPTO_TEST_UTILS_H_
diff --git a/chromium/net/quic/test_tools/crypto_test_utils_chromium.cc b/chromium/net/quic/test_tools/crypto_test_utils_chromium.cc
new file mode 100644
index 00000000000..8aaef425dac
--- /dev/null
+++ b/chromium/net/quic/test_tools/crypto_test_utils_chromium.cc
@@ -0,0 +1,53 @@
+// Copyright 2013 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/quic/test_tools/crypto_test_utils.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_source_chromium.h"
+#include "net/quic/crypto/proof_verifier_chromium.h"
+#include "net/test/cert_test_util.h"
+
+namespace net {
+
+namespace test {
+
+class TestProofVerifierChromium : public ProofVerifierChromium {
+ public:
+ TestProofVerifierChromium(CertVerifier* cert_verifier,
+ const std::string& cert_file)
+ : ProofVerifierChromium(cert_verifier, BoundNetLog()),
+ cert_verifier_(cert_verifier) {
+ // Load and install the root for the validated chain.
+ scoped_refptr<X509Certificate> root_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), cert_file);
+ scoped_root_.Reset(root_cert.get());
+ }
+ virtual ~TestProofVerifierChromium() { }
+
+ private:
+ ScopedTestRoot scoped_root_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+};
+
+// static
+ProofSource* CryptoTestUtils::ProofSourceForTesting() {
+ return new ProofSourceChromium();
+}
+
+// static
+ProofVerifier* CryptoTestUtils::ProofVerifierForTesting() {
+ TestProofVerifierChromium* proof_verifier = new TestProofVerifierChromium(
+ CertVerifier::CreateDefault(), "quic_root.crt");
+ return proof_verifier;
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/crypto_test_utils_nss.cc b/chromium/net/quic/test_tools/crypto_test_utils_nss.cc
new file mode 100644
index 00000000000..88c87679b93
--- /dev/null
+++ b/chromium/net/quic/test_tools/crypto_test_utils_nss.cc
@@ -0,0 +1,138 @@
+// Copyright 2013 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/quic/test_tools/crypto_test_utils.h"
+
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <sechash.h>
+
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "crypto/ec_private_key.h"
+#include "net/quic/crypto/channel_id.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+namespace test {
+
+// TODO(rtenneti): Implement NSS support ChannelIDSigner. Convert Sign() to be
+// asynchronous using completion callback. After porting TestChannelIDSigner,
+// implement real ChannelIDSigner.
+class TestChannelIDSigner : public ChannelIDSigner {
+ public:
+ virtual ~TestChannelIDSigner() {
+ STLDeleteValues(&hostname_to_key_);
+ }
+
+ // ChannelIDSigner implementation.
+
+ virtual bool Sign(const string& hostname,
+ StringPiece signed_data,
+ string* out_key,
+ string* out_signature) OVERRIDE {
+ crypto::ECPrivateKey* ecdsa_keypair = HostnameToKey(hostname);
+ if (!ecdsa_keypair) {
+ return false;
+ }
+
+ *out_key = SerializeKey(ecdsa_keypair->public_key());
+ if (out_key->empty()) {
+ return false;
+ }
+
+ unsigned char hash_buf[SHA256_LENGTH];
+ SECItem hash_item = { siBuffer, hash_buf, sizeof(hash_buf) };
+
+ HASHContext* sha256 = HASH_Create(HASH_AlgSHA256);
+ if (!sha256) {
+ return false;
+ }
+ HASH_Begin(sha256);
+ HASH_Update(sha256,
+ reinterpret_cast<const unsigned char*>(
+ ChannelIDVerifier::kContextStr),
+ strlen(ChannelIDVerifier::kContextStr) + 1);
+ HASH_Update(sha256,
+ reinterpret_cast<const unsigned char*>(
+ ChannelIDVerifier::kClientToServerStr),
+ strlen(ChannelIDVerifier::kClientToServerStr) + 1);
+ HASH_Update(sha256,
+ reinterpret_cast<const unsigned char*>(signed_data.data()),
+ signed_data.size());
+ HASH_End(sha256, hash_buf, &hash_item.len, sizeof(hash_buf));
+ HASH_Destroy(sha256);
+
+ // The signature consists of a pair of 32-byte numbers.
+ static const unsigned int kSignatureLength = 32 * 2;
+ string signature;
+ SECItem sig_item = {
+ siBuffer,
+ reinterpret_cast<unsigned char*>(
+ WriteInto(&signature, kSignatureLength + 1)),
+ kSignatureLength
+ };
+
+ if (PK11_Sign(ecdsa_keypair->key(), &sig_item, &hash_item) != SECSuccess) {
+ return false;
+ }
+ *out_signature = signature;
+ return true;
+ }
+
+ virtual string GetKeyForHostname(const string& hostname) OVERRIDE {
+ crypto::ECPrivateKey* ecdsa_keypair = HostnameToKey(hostname);
+ if (!ecdsa_keypair) {
+ return "";
+ }
+ return SerializeKey(ecdsa_keypair->public_key());
+ }
+
+ private:
+ typedef std::map<string, crypto::ECPrivateKey*> HostnameToKeyMap;
+
+ crypto::ECPrivateKey* HostnameToKey(const string& hostname) {
+ HostnameToKeyMap::const_iterator it = hostname_to_key_.find(hostname);
+ if (it != hostname_to_key_.end()) {
+ return it->second;
+ }
+
+ crypto::ECPrivateKey* keypair = crypto::ECPrivateKey::Create();
+ if (!keypair) {
+ return NULL;
+ }
+ hostname_to_key_[hostname] = keypair;
+ return keypair;
+ }
+
+ static string SerializeKey(const SECKEYPublicKey* public_key) {
+ // public_key->u.ec.publicValue is an ANSI X9.62 public key which, for
+ // a P-256 key, is 0x04 (meaning uncompressed) followed by the x and y field
+ // elements as 32-byte, big-endian numbers.
+ static const unsigned int kExpectedKeyLength = 65;
+
+ const unsigned char* const data = public_key->u.ec.publicValue.data;
+ const unsigned int len = public_key->u.ec.publicValue.len;
+ if (len != kExpectedKeyLength || data[0] != 0x04) {
+ return "";
+ }
+
+ string key(reinterpret_cast<const char*>(data + 1), kExpectedKeyLength - 1);
+ return key;
+ }
+
+ HostnameToKeyMap hostname_to_key_;
+};
+
+// static
+ChannelIDSigner* CryptoTestUtils::ChannelIDSignerForTesting() {
+ return new TestChannelIDSigner();
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/crypto_test_utils_openssl.cc b/chromium/net/quic/test_tools/crypto_test_utils_openssl.cc
new file mode 100644
index 00000000000..bb08a044aa4
--- /dev/null
+++ b/chromium/net/quic/test_tools/crypto_test_utils_openssl.cc
@@ -0,0 +1,173 @@
+// Copyright 2013 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/quic/test_tools/crypto_test_utils.h"
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+#include <openssl/sha.h>
+
+#include "crypto/openssl_util.h"
+#include "crypto/secure_hash.h"
+#include "net/quic/crypto/channel_id.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace {
+
+void EvpMdCtxCleanUp(EVP_MD_CTX* ctx) {
+ (void)EVP_MD_CTX_cleanup(ctx);
+}
+
+} // namespace anonymous
+
+namespace net {
+
+namespace test {
+
+class TestChannelIDSigner : public ChannelIDSigner {
+ public:
+ virtual ~TestChannelIDSigner() { }
+
+ // ChannelIDSigner implementation.
+
+ virtual bool Sign(const string& hostname,
+ StringPiece signed_data,
+ string* out_key,
+ string* out_signature) OVERRIDE {
+ crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ecdsa_key(
+ HostnameToKey(hostname));
+
+ *out_key = SerializeKey(ecdsa_key.get());
+ if (out_key->empty()) {
+ return false;
+ }
+
+ EVP_MD_CTX md_ctx;
+ EVP_MD_CTX_init(&md_ctx);
+ crypto::ScopedOpenSSL<EVP_MD_CTX, EvpMdCtxCleanUp>
+ md_ctx_cleanup(&md_ctx);
+
+ if (EVP_DigestSignInit(&md_ctx, NULL, EVP_sha256(), NULL,
+ ecdsa_key.get()) != 1) {
+ return false;
+ }
+
+ EVP_DigestUpdate(&md_ctx, ChannelIDVerifier::kContextStr,
+ strlen(ChannelIDVerifier::kContextStr) + 1);
+ EVP_DigestUpdate(&md_ctx, ChannelIDVerifier::kClientToServerStr,
+ strlen(ChannelIDVerifier::kClientToServerStr) + 1);
+ EVP_DigestUpdate(&md_ctx, signed_data.data(), signed_data.size());
+
+ size_t sig_len;
+ if (!EVP_DigestSignFinal(&md_ctx, NULL, &sig_len)) {
+ return false;
+ }
+
+ scoped_ptr<uint8[]> der_sig(new uint8[sig_len]);
+ if (!EVP_DigestSignFinal(&md_ctx, der_sig.get(), &sig_len)) {
+ return false;
+ }
+
+ uint8* derp = der_sig.get();
+ crypto::ScopedOpenSSL<ECDSA_SIG, ECDSA_SIG_free> sig(
+ d2i_ECDSA_SIG(NULL, const_cast<const uint8**>(&derp), sig_len));
+ if (sig.get() == NULL) {
+ return false;
+ }
+
+ // The signature consists of a pair of 32-byte numbers.
+ static const size_t kSignatureLength = 32 * 2;
+ scoped_ptr<uint8[]> signature(new uint8[kSignatureLength]);
+ memset(signature.get(), 0, kSignatureLength);
+ BN_bn2bin(sig.get()->r, signature.get() + 32 - BN_num_bytes(sig.get()->r));
+ BN_bn2bin(sig.get()->s, signature.get() + 64 - BN_num_bytes(sig.get()->s));
+
+ *out_signature = string(reinterpret_cast<char*>(signature.get()),
+ kSignatureLength);
+
+ return true;
+ }
+
+ virtual string GetKeyForHostname(const string& hostname) OVERRIDE {
+ crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ecdsa_key(
+ HostnameToKey(hostname));
+ return SerializeKey(ecdsa_key.get());
+ }
+
+ private:
+ static EVP_PKEY* HostnameToKey(const string& hostname) {
+ // In order to generate a deterministic key for a given hostname the
+ // hostname is hashed with SHA-256 and the resulting digest is treated as a
+ // big-endian number. The most-significant bit is cleared to ensure that
+ // the resulting value is less than the order of the group and then it's
+ // taken as a private key. Given the private key, the public key is
+ // calculated with a group multiplication.
+ SHA256_CTX sha256;
+ SHA256_Init(&sha256);
+ SHA256_Update(&sha256, hostname.data(), hostname.size());
+
+ unsigned char digest[SHA256_DIGEST_LENGTH];
+ SHA256_Final(digest, &sha256);
+
+ // Ensure that the digest is less than the order of the P-256 group by
+ // clearing the most-significant bit.
+ digest[0] &= 0x7f;
+
+ crypto::ScopedOpenSSL<BIGNUM, BN_free> k(BN_new());
+ CHECK(BN_bin2bn(digest, sizeof(digest), k.get()) != NULL);
+
+ crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> p256(
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ CHECK(p256.get());
+
+ crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ecdsa_key(EC_KEY_new());
+ CHECK(ecdsa_key.get() != NULL &&
+ EC_KEY_set_group(ecdsa_key.get(), p256.get()));
+
+ crypto::ScopedOpenSSL<EC_POINT, EC_POINT_free> point(
+ EC_POINT_new(p256.get()));
+ CHECK(EC_POINT_mul(p256.get(), point.get(), k.get(), NULL, NULL, NULL));
+
+ EC_KEY_set_private_key(ecdsa_key.get(), k.get());
+ EC_KEY_set_public_key(ecdsa_key.get(), point.get());
+
+ crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> pkey(EVP_PKEY_new());
+ // EVP_PKEY_set1_EC_KEY takes a reference so no |release| here.
+ EVP_PKEY_set1_EC_KEY(pkey.get(), ecdsa_key.get());
+
+ return pkey.release();
+ }
+
+ static string SerializeKey(EVP_PKEY* key) {
+ // i2d_PublicKey will produce an ANSI X9.62 public key which, for a P-256
+ // key, is 0x04 (meaning uncompressed) followed by the x and y field
+ // elements as 32-byte, big-endian numbers.
+ static const int kExpectedKeyLength = 65;
+
+ int len = i2d_PublicKey(key, NULL);
+ if (len != kExpectedKeyLength) {
+ return "";
+ }
+
+ uint8 buf[kExpectedKeyLength];
+ uint8* derp = buf;
+ i2d_PublicKey(key, &derp);
+
+ return string(reinterpret_cast<char*>(buf + 1), kExpectedKeyLength - 1);
+ }
+};
+
+// static
+ChannelIDSigner* CryptoTestUtils::ChannelIDSignerForTesting() {
+ return new TestChannelIDSigner();
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/mock_clock.cc b/chromium/net/quic/test_tools/mock_clock.cc
new file mode 100644
index 00000000000..47f23808877
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_clock.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/mock_clock.h"
+
+namespace net {
+
+MockClock::MockClock() : now_(QuicTime::Zero()) {
+}
+
+MockClock::~MockClock() {
+}
+
+void MockClock::AdvanceTime(QuicTime::Delta delta) {
+ now_ = now_.Add(delta);
+}
+
+QuicTime MockClock::Now() const {
+ return now_;
+}
+
+QuicTime MockClock::ApproximateNow() const {
+ return now_;
+}
+
+QuicWallTime MockClock::WallNow() const {
+ return QuicWallTime::FromUNIXSeconds(
+ now_.Subtract(QuicTime::Zero()).ToSeconds());
+}
+
+base::TimeTicks MockClock::NowInTicks() const {
+ base::TimeTicks ticks;
+ return ticks + base::TimeDelta::FromMicroseconds(
+ now_.Subtract(QuicTime::Zero()).ToMicroseconds());
+}
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/mock_clock.h b/chromium/net/quic/test_tools/mock_clock.h
new file mode 100644
index 00000000000..5c9631ae84c
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_clock.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
+
+#include "net/quic/quic_clock.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+
+namespace net {
+
+class MockClock : public QuicClock {
+ public:
+ MockClock();
+
+ virtual ~MockClock();
+
+ void AdvanceTime(QuicTime::Delta delta);
+
+ virtual QuicTime Now() const OVERRIDE;
+
+ virtual QuicTime ApproximateNow() const OVERRIDE;
+
+ virtual QuicWallTime WallNow() const OVERRIDE;
+
+ base::TimeTicks NowInTicks() const;
+
+ private:
+ QuicTime now_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_MOCK_CLOCK_H_
diff --git a/chromium/net/quic/test_tools/mock_crypto_client_stream.cc b/chromium/net/quic/test_tools/mock_crypto_client_stream.cc
new file mode 100644
index 00000000000..79c33531714
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_crypto_client_stream.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2013 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/quic/test_tools/mock_crypto_client_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+MockCryptoClientStream::MockCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config,
+ HandshakeMode handshake_mode)
+ : QuicCryptoClientStream(server_hostname, session, crypto_config),
+ handshake_mode_(handshake_mode) {
+}
+
+MockCryptoClientStream::~MockCryptoClientStream() {
+}
+
+void MockCryptoClientStream::OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) {
+ CloseConnection(QUIC_CRYPTO_MESSAGE_AFTER_HANDSHAKE_COMPLETE);
+}
+
+bool MockCryptoClientStream::CryptoConnect() {
+ switch (handshake_mode_) {
+ case ZERO_RTT: {
+ encryption_established_ = true;
+ handshake_confirmed_ = false;
+ session()->OnCryptoHandshakeEvent(
+ QuicSession::ENCRYPTION_FIRST_ESTABLISHED);
+ break;
+ }
+
+ case CONFIRM_HANDSHAKE: {
+ encryption_established_ = true;
+ handshake_confirmed_ = true;
+ SetConfigNegotiated();
+ session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED);
+ break;
+ }
+
+ case COLD_START: {
+ handshake_confirmed_ = false;
+ encryption_established_ = false;
+ break;
+ }
+ }
+ return true;
+}
+
+void MockCryptoClientStream::SetConfigNegotiated() {
+ ASSERT_FALSE(session()->config()->negotiated());
+ QuicTagVector cgst;
+ cgst.push_back(kINAR);
+ cgst.push_back(kQBIC);
+ session()->config()->set_congestion_control(cgst, kQBIC);
+ session()->config()->set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromSeconds(2 * kDefaultTimeoutSecs),
+ QuicTime::Delta::FromSeconds(kDefaultTimeoutSecs));
+ session()->config()->set_max_streams_per_connection(
+ 2 * kDefaultMaxStreamsPerConnection, kDefaultMaxStreamsPerConnection);
+
+ CryptoHandshakeMessage msg;
+ session()->config()->ToHandshakeMessage(&msg);
+ string error_details;
+ const QuicErrorCode error =
+ session()->config()->ProcessClientHello(msg, &error_details);
+ ASSERT_EQ(QUIC_NO_ERROR, error);
+ ASSERT_TRUE(session()->config()->negotiated());
+}
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/mock_crypto_client_stream.h b/chromium/net/quic/test_tools/mock_crypto_client_stream.h
new file mode 100644
index 00000000000..2b73b8fd584
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_crypto_client_stream.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2013 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_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
+
+#include <string>
+
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+class MockCryptoClientStream : public QuicCryptoClientStream {
+ public:
+ // HandshakeMode enumerates the handshake mode MockCryptoClientStream should
+ // mock in CryptoConnect.
+ enum HandshakeMode {
+ // CONFIRM_HANDSHAKE indicates that CryptoConnect will immediately confirm
+ // the handshake and establish encryption. This behavior will never happen
+ // in the field, but is convenient for higher level tests.
+ CONFIRM_HANDSHAKE,
+
+ // ZERO_RTT indicates that CryptoConnect will establish encryption but will
+ // not confirm the handshake.
+ ZERO_RTT,
+
+ // COLD_START indicates that CryptoConnect will neither establish encryption
+ // nor confirm the handshake
+ COLD_START,
+ };
+
+ MockCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config,
+ HandshakeMode handshake_mode);
+ virtual ~MockCryptoClientStream();
+
+ // CryptoFramerVisitorInterface implementation.
+ virtual void OnHandshakeMessage(
+ const CryptoHandshakeMessage& message) OVERRIDE;
+
+ // QuicCryptoClientStream implementation.
+ virtual bool CryptoConnect() OVERRIDE;
+
+ HandshakeMode handshake_mode_;
+
+ private:
+ void SetConfigNegotiated();
+};
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_H_
diff --git a/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.cc b/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.cc
new file mode 100644
index 00000000000..7578790e136
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 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/quic/test_tools/mock_crypto_client_stream_factory.h"
+
+#include "base/lazy_instance.h"
+#include "net/quic/quic_crypto_client_stream.h"
+
+using std::string;
+
+namespace net {
+
+MockCryptoClientStreamFactory::MockCryptoClientStreamFactory()
+ : handshake_mode_(MockCryptoClientStream::CONFIRM_HANDSHAKE) {
+}
+
+QuicCryptoClientStream*
+MockCryptoClientStreamFactory::CreateQuicCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config) {
+ return new MockCryptoClientStream(server_hostname, session, crypto_config,
+ handshake_mode_);
+}
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.h b/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.h
new file mode 100644
index 00000000000..e3f2a4aba5c
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_crypto_client_stream_factory.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2013 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_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
+
+#include <string>
+
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_crypto_client_stream_factory.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/test_tools/mock_crypto_client_stream.h"
+
+namespace net {
+
+class MockCryptoClientStreamFactory : public QuicCryptoClientStreamFactory {
+ public:
+ MockCryptoClientStreamFactory();
+
+ virtual ~MockCryptoClientStreamFactory() {}
+
+ virtual QuicCryptoClientStream* CreateQuicCryptoClientStream(
+ const string& server_hostname,
+ QuicSession* session,
+ QuicCryptoClientConfig* crypto_config) OVERRIDE;
+
+ void set_handshake_mode(
+ MockCryptoClientStream::HandshakeMode handshake_mode) {
+ handshake_mode_ = handshake_mode;
+ }
+
+ private:
+ MockCryptoClientStream::HandshakeMode handshake_mode_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_MOCK_CRYPTO_CLIENT_STREAM_FACTORY_H_
diff --git a/chromium/net/quic/test_tools/mock_random.cc b/chromium/net/quic/test_tools/mock_random.cc
new file mode 100644
index 00000000000..19a2832bbaa
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_random.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/mock_random.h"
+
+namespace net {
+
+MockRandom::MockRandom()
+ : increment_(0) {
+}
+
+void MockRandom::RandBytes(void* data, size_t len) {
+ memset(data, 'r' + increment_, len);
+}
+
+uint64 MockRandom::RandUint64() {
+ return 0xDEADBEEF + increment_;
+}
+
+bool MockRandom::RandBool() {
+ return false;
+}
+
+void MockRandom::Reseed(const void* additional_entropy, size_t entropy_len) {
+}
+
+void MockRandom::ChangeValue() {
+ increment_++;
+}
+
+} // namespace net
diff --git a/chromium/net/quic/test_tools/mock_random.h b/chromium/net/quic/test_tools/mock_random.h
new file mode 100644
index 00000000000..544f5ce048a
--- /dev/null
+++ b/chromium/net/quic/test_tools/mock_random.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
+#define NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
+
+#include "base/compiler_specific.h"
+#include "net/quic/crypto/quic_random.h"
+
+namespace net {
+
+class MockRandom : public QuicRandom {
+ public:
+ MockRandom();
+
+ // QuicRandom:
+ // Fills the |data| buffer with a repeating byte, initially 'r'.
+ virtual void RandBytes(void* data, size_t len) OVERRIDE;
+ // Returns 0xDEADBEEF + the current increment.
+ virtual uint64 RandUint64() OVERRIDE;
+ // Returns false.
+ virtual bool RandBool() OVERRIDE;
+ // Does nothing.
+ virtual void Reseed(const void* additional_entropy,
+ size_t entropy_len) OVERRIDE;
+
+ // ChangeValue increments |increment_|. This causes the value returned by
+ // |RandUint64| and the byte that |RandBytes| fills with, to change.
+ void ChangeValue();
+
+ private:
+ uint8 increment_;
+};
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_MOCK_RANDOM_H_
diff --git a/chromium/net/quic/test_tools/quic_client_session_peer.cc b/chromium/net/quic/test_tools/quic_client_session_peer.cc
new file mode 100644
index 00000000000..e88da49e15e
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_client_session_peer.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 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/quic/test_tools/quic_client_session_peer.h"
+
+#include "net/quic/quic_client_session.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicClientSessionPeer::SetMaxOpenStreams(QuicClientSession* session,
+ size_t max_streams,
+ size_t default_streams) {
+ session->config()->set_max_streams_per_connection(max_streams,
+ default_streams);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_client_session_peer.h b/chromium/net/quic/test_tools/quic_client_session_peer.h
new file mode 100644
index 00000000000..7217d9d8600
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_client_session_peer.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 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_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicClientSession;
+
+namespace test {
+
+class QuicClientSessionPeer {
+ public:
+ static void SetMaxOpenStreams(QuicClientSession* session,
+ size_t max_streams,
+ size_t default_streams);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicClientSessionPeer);
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_CLIENT_SESSION_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_connection_peer.cc b/chromium/net/quic/test_tools/quic_connection_peer.cc
new file mode 100644
index 00000000000..610c505161b
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_connection_peer.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/quic_connection_peer.h"
+
+#include "base/stl_util.h"
+#include "net/quic/congestion_control/quic_congestion_manager.h"
+#include "net/quic/congestion_control/receive_algorithm_interface.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/test_tools/quic_framer_peer.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicConnectionPeer::SendAck(QuicConnection* connection) {
+ connection->SendAck();
+}
+
+// static
+void QuicConnectionPeer::SetReceiveAlgorithm(
+ QuicConnection* connection,
+ ReceiveAlgorithmInterface* receive_algorithm) {
+ connection->congestion_manager_.receive_algorithm_.reset(receive_algorithm);
+}
+
+// static
+void QuicConnectionPeer::SetSendAlgorithm(
+ QuicConnection* connection,
+ SendAlgorithmInterface* send_algorithm) {
+ connection->congestion_manager_.send_algorithm_.reset(send_algorithm);
+}
+
+// static
+QuicAckFrame* QuicConnectionPeer::CreateAckFrame(QuicConnection* connection) {
+ return connection->CreateAckFrame();
+}
+
+// static
+QuicConnectionVisitorInterface* QuicConnectionPeer::GetVisitor(
+ QuicConnection* connection) {
+ return connection->visitor_;
+}
+
+// static
+QuicPacketCreator* QuicConnectionPeer::GetPacketCreator(
+ QuicConnection* connection) {
+ return &connection->packet_creator_;
+}
+
+bool QuicConnectionPeer::GetReceivedTruncatedAck(QuicConnection* connection) {
+ return connection->received_truncated_ack_;
+}
+
+// static
+size_t QuicConnectionPeer::GetNumRetransmissionTimeouts(
+ QuicConnection* connection) {
+ return connection->retransmission_timeouts_.size();
+}
+
+// static
+QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout(
+ QuicConnection* connection) {
+ return connection->idle_network_timeout_;
+}
+
+// static
+bool QuicConnectionPeer::IsSavedForRetransmission(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number) {
+ return ContainsKey(connection->retransmission_map_, sequence_number);
+}
+
+// static
+size_t QuicConnectionPeer::GetRetransmissionCount(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number) {
+ QuicConnection::RetransmissionMap::iterator it =
+ connection->retransmission_map_.find(sequence_number);
+ DCHECK(connection->retransmission_map_.end() != it);
+ return it->second.number_retransmissions;
+}
+
+// static
+QuicPacketEntropyHash QuicConnectionPeer::GetSentEntropyHash(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number) {
+ return connection->sent_entropy_manager_.EntropyHash(sequence_number);
+}
+
+// static
+bool QuicConnectionPeer::IsValidEntropy(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber largest_observed,
+ const SequenceNumberSet& missing_packets,
+ QuicPacketEntropyHash entropy_hash) {
+ return connection->sent_entropy_manager_.IsValidEntropy(
+ largest_observed, missing_packets, entropy_hash);
+}
+
+// static
+QuicPacketEntropyHash QuicConnectionPeer::ReceivedEntropyHash(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number) {
+ return connection->received_packet_manager_.EntropyHash(
+ sequence_number);
+}
+
+// static
+bool QuicConnectionPeer::IsServer(QuicConnection* connection) {
+ return connection->is_server_;
+}
+
+// static
+void QuicConnectionPeer::SetIsServer(QuicConnection* connection,
+ bool is_server) {
+ connection->is_server_ = is_server;
+ QuicFramerPeer::SetIsServer(&connection->framer_, is_server);
+}
+
+// static
+void QuicConnectionPeer::SetSelfAddress(QuicConnection* connection,
+ const IPEndPoint& self_address) {
+ connection->self_address_ = self_address;
+}
+
+// static
+void QuicConnectionPeer::SwapCrypters(QuicConnection* connection,
+ QuicFramer* framer) {
+ framer->SwapCryptersForTest(&connection->framer_);
+}
+
+// static
+void QuicConnectionPeer:: SetMaxPacketsPerRetransmissionAlarm(
+ QuicConnection* connection,
+ int max_packets) {
+ connection->max_packets_per_retransmission_alarm_ = max_packets;
+}
+
+// static
+QuicConnectionHelperInterface* QuicConnectionPeer::GetHelper(
+ QuicConnection* connection) {
+ return connection->helper_.get();
+}
+
+// static
+QuicFramer* QuicConnectionPeer::GetFramer(QuicConnection* connection) {
+ return &connection->framer_;
+}
+
+QuicFecGroup* QuicConnectionPeer::GetFecGroup(QuicConnection* connection,
+ int fec_group) {
+ connection->last_header_.fec_group = fec_group;
+ return connection->GetFecGroup();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetAckAlarm(QuicConnection* connection) {
+ return connection->ack_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetRetransmissionAlarm(
+ QuicConnection* connection) {
+ return connection->retransmission_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetSendAlarm(QuicConnection* connection) {
+ return connection->send_alarm_.get();
+}
+
+// static
+QuicAlarm* QuicConnectionPeer::GetTimeoutAlarm(QuicConnection* connection) {
+ return connection->timeout_alarm_.get();
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_connection_peer.h b/chromium/net/quic/test_tools/quic_connection_peer.h
new file mode 100644
index 00000000000..4438353ddaa
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_connection_peer.h
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CONNECTION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CONNECTION_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_stats.h"
+
+namespace net {
+
+struct QuicAckFrame;
+struct QuicPacketHeader;
+class QuicAlarm;
+class QuicConnection;
+class QuicConnectionHelperInterface;
+class QuicConnectionVisitorInterface;
+class QuicFecGroup;
+class QuicFramer;
+class QuicPacketCreator;
+class ReceiveAlgorithmInterface;
+class SendAlgorithmInterface;
+
+namespace test {
+
+// Peer to make public a number of otherwise private QuicConnection methods.
+class QuicConnectionPeer {
+ public:
+ static void SendAck(QuicConnection* connection);
+
+ static void SetReceiveAlgorithm(QuicConnection* connection,
+ ReceiveAlgorithmInterface* receive_algorithm);
+
+ static void SetSendAlgorithm(QuicConnection* connection,
+ SendAlgorithmInterface* send_algorithm);
+
+ static QuicAckFrame* CreateAckFrame(QuicConnection* connection);
+
+ static QuicConnectionVisitorInterface* GetVisitor(
+ QuicConnection* connection);
+
+ static QuicPacketCreator* GetPacketCreator(QuicConnection* connection);
+
+ static bool GetReceivedTruncatedAck(QuicConnection* connection);
+
+ static size_t GetNumRetransmissionTimeouts(QuicConnection* connection);
+
+ static QuicTime::Delta GetNetworkTimeout(QuicConnection* connection);
+
+ static bool IsSavedForRetransmission(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number);
+
+ static size_t GetRetransmissionCount(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number);
+
+ static QuicPacketEntropyHash GetSentEntropyHash(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number);
+
+ static bool IsValidEntropy(QuicConnection* connection,
+ QuicPacketSequenceNumber largest_observed,
+ const SequenceNumberSet& missing_packets,
+ QuicPacketEntropyHash entropy_hash);
+
+ static QuicPacketEntropyHash ReceivedEntropyHash(
+ QuicConnection* connection,
+ QuicPacketSequenceNumber sequence_number);
+
+ static bool IsServer(QuicConnection* connection);
+
+ static void SetIsServer(QuicConnection* connection, bool is_server);
+
+ static void SetSelfAddress(QuicConnection* connection,
+ const IPEndPoint& self_address);
+
+ static void SwapCrypters(QuicConnection* connection, QuicFramer* framer);
+
+ static void SetMaxPacketsPerRetransmissionAlarm(QuicConnection* connection,
+ int max_packets);
+
+ static QuicConnectionHelperInterface* GetHelper(QuicConnection* connection);
+
+ static QuicFramer* GetFramer(QuicConnection* connection);
+
+ // Set last_header_->fec_group = fec_group and return connection->GetFecGroup
+ static QuicFecGroup* GetFecGroup(QuicConnection* connection, int fec_group);
+
+ static QuicAlarm* GetAckAlarm(QuicConnection* connection);
+ static QuicAlarm* GetRetransmissionAlarm(QuicConnection* connection);
+ static QuicAlarm* GetSendAlarm(QuicConnection* connection);
+ static QuicAlarm* GetTimeoutAlarm(QuicConnection* connection);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicConnectionPeer);
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_TEST_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_framer_peer.cc b/chromium/net/quic/test_tools/quic_framer_peer.cc
new file mode 100644
index 00000000000..5ec52dc7512
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_framer_peer.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 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/quic/test_tools/quic_framer_peer.h"
+
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+namespace test {
+
+// static
+QuicPacketSequenceNumber QuicFramerPeer::CalculatePacketSequenceNumberFromWire(
+ QuicFramer* framer,
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number) {
+ return framer->CalculatePacketSequenceNumberFromWire(sequence_number_length,
+ packet_sequence_number);
+}
+
+// static
+void QuicFramerPeer::SetLastSerializedGuid(QuicFramer* framer, QuicGuid guid) {
+ framer->last_serialized_guid_ = guid;
+}
+
+void QuicFramerPeer::SetLastSequenceNumber(
+ QuicFramer* framer,
+ QuicPacketSequenceNumber packet_sequence_number) {
+ framer->last_sequence_number_ = packet_sequence_number;
+}
+
+void QuicFramerPeer::SetIsServer(QuicFramer* framer, bool is_server) {
+ framer->is_server_ = is_server;
+}
+
+void QuicFramerPeer::SetVersion(QuicFramer* framer, QuicVersion version) {
+ framer->quic_version_ = version;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_framer_peer.h b/chromium/net/quic/test_tools/quic_framer_peer.h
new file mode 100644
index 00000000000..0508f5c7a7e
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_framer_peer.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2013 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_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicFramer;
+
+namespace test {
+
+class QuicFramerPeer {
+ public:
+ static QuicPacketSequenceNumber CalculatePacketSequenceNumberFromWire(
+ QuicFramer* framer,
+ QuicSequenceNumberLength sequence_number_length,
+ QuicPacketSequenceNumber packet_sequence_number);
+ static void SetLastSerializedGuid(QuicFramer* framer, QuicGuid guid);
+ static void SetLastSequenceNumber(
+ QuicFramer* framer,
+ QuicPacketSequenceNumber packet_sequence_number);
+ static void SetIsServer(QuicFramer* framer, bool is_server);
+ static void SetVersion(QuicFramer* framer, QuicVersion version);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicFramerPeer);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_FRAMER_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_packet_creator_peer.cc b/chromium/net/quic/test_tools/quic_packet_creator_peer.cc
new file mode 100644
index 00000000000..4451f02be84
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_packet_creator_peer.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2013 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/quic/test_tools/quic_packet_creator_peer.h"
+
+#include "net/quic/quic_packet_creator.h"
+
+namespace net {
+namespace test {
+
+// static
+bool QuicPacketCreatorPeer::SendVersionInPacket(QuicPacketCreator* creator) {
+ return creator->send_version_in_packet_;
+}
+
+// static
+void QuicPacketCreatorPeer::SetSendVersionInPacket(
+ QuicPacketCreator* creator, bool send_version_in_packet) {
+ creator->send_version_in_packet_ = send_version_in_packet;
+}
+
+// static
+void QuicPacketCreatorPeer::SetIsServer(QuicPacketCreator* creator,
+ bool is_server) {
+ creator->is_server_ = is_server;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_packet_creator_peer.h b/chromium/net/quic/test_tools/quic_packet_creator_peer.h
new file mode 100644
index 00000000000..816afa96189
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_packet_creator_peer.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 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_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+class QuicPacketCreator;
+
+namespace test {
+
+class QuicPacketCreatorPeer {
+ public:
+ static bool SendVersionInPacket(QuicPacketCreator* creator);
+
+ static void SetSendVersionInPacket(QuicPacketCreator* creator,
+ bool send_version_in_packet);
+
+ static void SetIsServer(QuicPacketCreator* creator, bool is_server);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicPacketCreatorPeer);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_PACKET_CREATOR_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_received_packet_manager_peer.cc b/chromium/net/quic/test_tools/quic_received_packet_manager_peer.cc
new file mode 100644
index 00000000000..d25a209b5f4
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_received_packet_manager_peer.cc
@@ -0,0 +1,30 @@
+// Copyright 2013 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/quic/test_tools/quic_received_packet_manager_peer.h"
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_received_packet_manager.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicReceivedPacketManagerPeer::RecalculateEntropyHash(
+ QuicReceivedPacketManager* received_packet_manager,
+ QuicPacketSequenceNumber peer_least_unacked,
+ QuicPacketEntropyHash entropy_hash) {
+ received_packet_manager->RecalculateEntropyHash(peer_least_unacked,
+ entropy_hash);
+}
+
+// static
+bool QuicReceivedPacketManagerPeer::DontWaitForPacketsBefore(
+ QuicReceivedPacketManager* received_packet_manager,
+ QuicPacketSequenceNumber least_unacked) {
+ return received_packet_manager->DontWaitForPacketsBefore(least_unacked);
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_received_packet_manager_peer.h b/chromium/net/quic/test_tools/quic_received_packet_manager_peer.h
new file mode 100644
index 00000000000..4607a0c902d
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_received_packet_manager_peer.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
+
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicReceivedPacketManager;
+
+namespace test {
+
+class QuicReceivedPacketManagerPeer {
+ public:
+ static void RecalculateEntropyHash(
+ QuicReceivedPacketManager* received_packet_manager,
+ QuicPacketSequenceNumber peer_least_unacked,
+ QuicPacketEntropyHash entropy_hash);
+
+ static bool DontWaitForPacketsBefore(
+ QuicReceivedPacketManager* received_packet_manager,
+ QuicPacketSequenceNumber least_unacked);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicReceivedPacketManagerPeer);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_RECEIVED_PACKET_MANAGER_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_session_peer.cc b/chromium/net/quic/test_tools/quic_session_peer.cc
new file mode 100644
index 00000000000..66caa15a4b6
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_session_peer.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/quic_session_peer.h"
+
+#include "net/quic/quic_session.h"
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+namespace test {
+
+// static
+void QuicSessionPeer::SetNextStreamId(QuicSession* session, QuicStreamId id) {
+ session->next_stream_id_ = id;
+}
+
+// static
+void QuicSessionPeer::SetMaxOpenStreams(QuicSession* session,
+ uint32 max_streams) {
+ session->max_open_streams_ = max_streams;
+}
+
+// static
+ReliableQuicStream* QuicSessionPeer::CreateIncomingReliableStream(
+ QuicSession* session, QuicStreamId id) {
+ return session->CreateIncomingReliableStream(id);
+}
+
+// static
+BlockedList<QuicStreamId>* QuicSessionPeer::GetWriteblockedStreams(
+ QuicSession* session) {
+ return &session->write_blocked_streams_;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_session_peer.h b/chromium/net/quic/test_tools/quic_session_peer.h
new file mode 100644
index 00000000000..6f9a8f394e2
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_session_peer.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
+
+#include "net/quic/blocked_list.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class QuicSession;
+class ReliableQuicStream;
+
+namespace test {
+
+class QuicSessionPeer {
+ public:
+ static void SetNextStreamId(QuicSession* session, QuicStreamId id);
+ static void SetMaxOpenStreams(QuicSession* session, uint32 max_streams);
+ static ReliableQuicStream* CreateIncomingReliableStream(QuicSession* session,
+ QuicStreamId id);
+ static BlockedList<QuicStreamId>* GetWriteblockedStreams(
+ QuicSession* session);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicSessionPeer);
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_SESSION_PEER_H_
diff --git a/chromium/net/quic/test_tools/quic_test_utils.cc b/chromium/net/quic/test_tools/quic_test_utils.cc
new file mode 100644
index 00000000000..2562b07d599
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_test_utils.cc
@@ -0,0 +1,453 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/quic_test_utils.h"
+
+#include "base/stl_util.h"
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_utils.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/spdy/spdy_frame_builder.h"
+
+using base::StringPiece;
+using std::max;
+using std::min;
+using std::string;
+using testing::_;
+
+namespace net {
+namespace test {
+namespace {
+
+// No-op alarm implementation used by MockHelper.
+class TestAlarm : public QuicAlarm {
+ public:
+ explicit TestAlarm(QuicAlarm::Delegate* delegate)
+ : QuicAlarm(delegate) {
+ }
+
+ virtual void SetImpl() OVERRIDE {}
+ virtual void CancelImpl() OVERRIDE {}
+};
+
+} // namespace
+
+MockFramerVisitor::MockFramerVisitor() {
+ // By default, we want to accept packets.
+ ON_CALL(*this, OnProtocolVersionMismatch(_))
+ .WillByDefault(testing::Return(false));
+
+ // By default, we want to accept packets.
+ ON_CALL(*this, OnPacketHeader(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnStreamFrame(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnAckFrame(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnCongestionFeedbackFrame(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnRstStreamFrame(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnConnectionCloseFrame(_))
+ .WillByDefault(testing::Return(true));
+
+ ON_CALL(*this, OnGoAwayFrame(_))
+ .WillByDefault(testing::Return(true));
+}
+
+MockFramerVisitor::~MockFramerVisitor() {
+}
+
+bool NoOpFramerVisitor::OnProtocolVersionMismatch(QuicVersion version) {
+ return false;
+}
+
+bool NoOpFramerVisitor::OnPacketHeader(const QuicPacketHeader& header) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnStreamFrame(const QuicStreamFrame& frame) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnAckFrame(const QuicAckFrame& frame) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnRstStreamFrame(
+ const QuicRstStreamFrame& frame) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) {
+ return true;
+}
+
+bool NoOpFramerVisitor::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+ return true;
+}
+
+FramerVisitorCapturingFrames::FramerVisitorCapturingFrames() : frame_count_(0) {
+}
+
+FramerVisitorCapturingFrames::~FramerVisitorCapturingFrames() {
+}
+
+bool FramerVisitorCapturingFrames::OnPacketHeader(
+ const QuicPacketHeader& header) {
+ header_ = header;
+ frame_count_ = 0;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnStreamFrame(const QuicStreamFrame& frame) {
+ // TODO(ianswett): Own the underlying string, so it will not exist outside
+ // this callback.
+ stream_frames_.push_back(frame);
+ ++frame_count_;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnAckFrame(const QuicAckFrame& frame) {
+ ack_.reset(new QuicAckFrame(frame));
+ ++frame_count_;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) {
+ feedback_.reset(new QuicCongestionFeedbackFrame(frame));
+ ++frame_count_;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnRstStreamFrame(
+ const QuicRstStreamFrame& frame) {
+ rst_.reset(new QuicRstStreamFrame(frame));
+ ++frame_count_;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) {
+ close_.reset(new QuicConnectionCloseFrame(frame));
+ ++frame_count_;
+ return true;
+}
+
+bool FramerVisitorCapturingFrames::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+ goaway_.reset(new QuicGoAwayFrame(frame));
+ ++frame_count_;
+ return true;
+}
+
+void FramerVisitorCapturingFrames::OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) {
+ version_negotiation_packet_.reset(new QuicVersionNegotiationPacket(packet));
+ frame_count_ = 0;
+}
+
+FramerVisitorCapturingPublicReset::FramerVisitorCapturingPublicReset() {
+}
+
+FramerVisitorCapturingPublicReset::~FramerVisitorCapturingPublicReset() {
+}
+
+void FramerVisitorCapturingPublicReset::OnPublicResetPacket(
+ const QuicPublicResetPacket& public_reset) {
+ public_reset_packet_ = public_reset;
+}
+
+MockConnectionVisitor::MockConnectionVisitor() {
+}
+
+MockConnectionVisitor::~MockConnectionVisitor() {
+}
+
+MockHelper::MockHelper() {
+}
+
+MockHelper::~MockHelper() {
+}
+
+const QuicClock* MockHelper::GetClock() const {
+ return &clock_;
+}
+
+QuicRandom* MockHelper::GetRandomGenerator() {
+ return &random_generator_;
+}
+
+QuicAlarm* MockHelper::CreateAlarm(QuicAlarm::Delegate* delegate) {
+ return new TestAlarm(delegate);
+}
+
+void MockHelper::AdvanceTime(QuicTime::Delta delta) {
+ clock_.AdvanceTime(delta);
+}
+
+MockConnection::MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ bool is_server)
+ : QuicConnection(guid, address, new testing::NiceMock<MockHelper>(),
+ is_server, QuicVersionMax()),
+ has_mock_helper_(true) {
+}
+
+MockConnection::MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper,
+ bool is_server)
+ : QuicConnection(guid, address, helper, is_server, QuicVersionMax()),
+ has_mock_helper_(false) {
+}
+
+MockConnection::~MockConnection() {
+}
+
+void MockConnection::AdvanceTime(QuicTime::Delta delta) {
+ CHECK(has_mock_helper_) << "Cannot advance time unless a MockClock is being"
+ " used";
+ static_cast<MockHelper*>(helper())->AdvanceTime(delta);
+}
+
+PacketSavingConnection::PacketSavingConnection(QuicGuid guid,
+ IPEndPoint address,
+ bool is_server)
+ : MockConnection(guid, address, is_server) {
+}
+
+PacketSavingConnection::~PacketSavingConnection() {
+ STLDeleteElements(&packets_);
+ STLDeleteElements(&encrypted_packets_);
+}
+
+bool PacketSavingConnection::SendOrQueuePacket(
+ EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ QuicPacketEntropyHash entropy_hash,
+ HasRetransmittableData retransmittable) {
+ packets_.push_back(packet);
+ QuicEncryptedPacket* encrypted =
+ framer_.EncryptPacket(level, sequence_number, *packet);
+ encrypted_packets_.push_back(encrypted);
+ return true;
+}
+
+MockSession::MockSession(QuicConnection* connection, bool is_server)
+ : QuicSession(connection, DefaultQuicConfig(), is_server) {
+ ON_CALL(*this, WriteData(_, _, _, _))
+ .WillByDefault(testing::Return(QuicConsumedData(0, false)));
+}
+
+MockSession::~MockSession() {
+}
+
+TestSession::TestSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server)
+ : QuicSession(connection, config, is_server),
+ crypto_stream_(NULL) {
+}
+
+TestSession::~TestSession() {}
+
+void TestSession::SetCryptoStream(QuicCryptoStream* stream) {
+ crypto_stream_ = stream;
+}
+
+QuicCryptoStream* TestSession::GetCryptoStream() {
+ return crypto_stream_;
+}
+
+MockSendAlgorithm::MockSendAlgorithm() {
+}
+
+MockSendAlgorithm::~MockSendAlgorithm() {
+}
+
+namespace {
+
+string HexDumpWithMarks(const char* data, int length,
+ const bool* marks, int mark_length) {
+ static const char kHexChars[] = "0123456789abcdef";
+ static const int kColumns = 4;
+
+ const int kSizeLimit = 1024;
+ if (length > kSizeLimit || mark_length > kSizeLimit) {
+ LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+ length = min(length, kSizeLimit);
+ mark_length = min(mark_length, kSizeLimit);
+ }
+
+ string hex;
+ for (const char* row = data; length > 0;
+ row += kColumns, length -= kColumns) {
+ for (const char *p = row; p < row + 4; ++p) {
+ if (p < row + length) {
+ const bool mark =
+ (marks && (p - data) < mark_length && marks[p - data]);
+ hex += mark ? '*' : ' ';
+ hex += kHexChars[(*p & 0xf0) >> 4];
+ hex += kHexChars[*p & 0x0f];
+ hex += mark ? '*' : ' ';
+ } else {
+ hex += " ";
+ }
+ }
+ hex = hex + " ";
+
+ for (const char *p = row; p < row + 4 && p < row + length; ++p)
+ hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+
+ hex = hex + '\n';
+ }
+ return hex;
+}
+
+} // namespace
+
+void CompareCharArraysWithHexError(
+ const string& description,
+ const char* actual,
+ const int actual_len,
+ const char* expected,
+ const int expected_len) {
+ const int min_len = min(actual_len, expected_len);
+ const int max_len = max(actual_len, expected_len);
+ scoped_ptr<bool[]> marks(new bool[max_len]);
+ bool identical = (actual_len == expected_len);
+ for (int i = 0; i < min_len; ++i) {
+ if (actual[i] != expected[i]) {
+ marks[i] = true;
+ identical = false;
+ } else {
+ marks[i] = false;
+ }
+ }
+ for (int i = min_len; i < max_len; ++i) {
+ marks[i] = true;
+ }
+ if (identical) return;
+ ADD_FAILURE()
+ << "Description:\n"
+ << description
+ << "\n\nExpected:\n"
+ << HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
+ << "\nActual:\n"
+ << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+void CompareQuicDataWithHexError(
+ const string& description,
+ QuicData* actual,
+ QuicData* expected) {
+ CompareCharArraysWithHexError(
+ description,
+ actual->data(), actual->length(),
+ expected->data(), expected->length());
+}
+
+static QuicPacket* ConstructPacketFromHandshakeMessage(
+ QuicGuid guid,
+ const CryptoHandshakeMessage& message,
+ bool should_include_version) {
+ CryptoFramer crypto_framer;
+ scoped_ptr<QuicData> data(crypto_framer.ConstructHandshakeMessage(message));
+ QuicFramer quic_framer(QuicVersionMax(), QuicTime::Zero(), false);
+
+ QuicPacketHeader header;
+ header.public_header.guid = guid;
+ header.public_header.reset_flag = false;
+ header.public_header.version_flag = should_include_version;
+ header.packet_sequence_number = 1;
+ header.entropy_flag = false;
+ header.entropy_hash = 0;
+ header.fec_flag = false;
+ header.fec_group = 0;
+
+ QuicStreamFrame stream_frame(kCryptoStreamId, false, 0,
+ data->AsStringPiece());
+
+ QuicFrame frame(&stream_frame);
+ QuicFrames frames;
+ frames.push_back(frame);
+ return quic_framer.BuildUnsizedDataPacket(header, frames).packet;
+}
+
+QuicPacket* ConstructHandshakePacket(QuicGuid guid, QuicTag tag) {
+ CryptoHandshakeMessage message;
+ message.set_tag(tag);
+ return ConstructPacketFromHandshakeMessage(guid, message, false);
+}
+
+size_t GetPacketLengthForOneStream(QuicVersion version,
+ bool include_version,
+ InFecGroup is_in_fec_group,
+ size_t* payload_length) {
+ *payload_length = 1;
+ const size_t stream_length =
+ NullEncrypter().GetCiphertextSize(*payload_length) +
+ QuicPacketCreator::StreamFramePacketOverhead(
+ version, PACKET_8BYTE_GUID, include_version,
+ PACKET_6BYTE_SEQUENCE_NUMBER, is_in_fec_group);
+ const size_t ack_length = NullEncrypter().GetCiphertextSize(
+ QuicFramer::GetMinAckFrameSize()) +
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, include_version,
+ PACKET_6BYTE_SEQUENCE_NUMBER, is_in_fec_group);
+ if (stream_length < ack_length) {
+ *payload_length = 1 + ack_length - stream_length;
+ }
+
+ return NullEncrypter().GetCiphertextSize(*payload_length) +
+ QuicPacketCreator::StreamFramePacketOverhead(
+ version, PACKET_8BYTE_GUID, include_version,
+ PACKET_6BYTE_SEQUENCE_NUMBER, is_in_fec_group);
+}
+
+// Size in bytes of the stream frame fields for an arbitrary StreamID and
+// offset and the last frame in a packet.
+size_t GetMinStreamFrameSize(QuicVersion version) {
+ return kQuicFrameTypeSize + kQuicMaxStreamIdSize + kQuicMaxStreamOffsetSize;
+}
+
+QuicPacketEntropyHash TestEntropyCalculator::EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const {
+ return 1u;
+}
+
+QuicConfig DefaultQuicConfig() {
+ QuicConfig config;
+ config.SetDefaults();
+ return config;
+}
+
+bool TestDecompressorVisitor::OnDecompressedData(StringPiece data) {
+ data.AppendToString(&data_);
+ return true;
+}
+
+void TestDecompressorVisitor::OnDecompressionError() {
+ error_ = true;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/quic_test_utils.h b/chromium/net/quic/test_tools/quic_test_utils.h
new file mode 100644
index 00000000000..65fba73d3b9
--- /dev/null
+++ b/chromium/net/quic/test_tools/quic_test_utils.h
@@ -0,0 +1,376 @@
+// Copyright (c) 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.
+//
+// Common utilities for Quic tests
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/congestion_control/send_algorithm_interface.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/quic/test_tools/mock_clock.h"
+#include "net/quic/test_tools/mock_random.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+
+namespace test {
+
+void CompareCharArraysWithHexError(const std::string& description,
+ const char* actual,
+ const int actual_len,
+ const char* expected,
+ const int expected_len);
+
+void CompareQuicDataWithHexError(const std::string& description,
+ QuicData* actual,
+ QuicData* expected);
+
+// Returns the length of a QuicPacket that is capable of holding either a
+// stream frame or a minimal ack frame. Sets |*payload_length| to the number
+// of bytes of stream data that will fit in such a packet.
+size_t GetPacketLengthForOneStream(QuicVersion version,
+ bool include_version,
+ InFecGroup is_in_fec_group,
+ size_t* payload_length);
+
+// Size in bytes of the stream frame fields for an arbitrary StreamID and
+// offset and the last frame in a packet.
+size_t GetMinStreamFrameSize(QuicVersion version);
+
+string SerializeUncompressedHeaders(const SpdyHeaderBlock& headers);
+
+// Returns QuicConfig set to default values.
+QuicConfig DefaultQuicConfig();
+
+class MockFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+ MockFramerVisitor();
+ ~MockFramerVisitor();
+
+ MOCK_METHOD1(OnError, void(QuicFramer* framer));
+ // The constructor sets this up to return false by default.
+ MOCK_METHOD1(OnProtocolVersionMismatch, bool(QuicVersion version));
+ MOCK_METHOD0(OnPacket, void());
+ MOCK_METHOD1(OnPublicResetPacket, void(const QuicPublicResetPacket& header));
+ MOCK_METHOD1(OnVersionNegotiationPacket,
+ void(const QuicVersionNegotiationPacket& packet));
+ MOCK_METHOD0(OnRevivedPacket, void());
+ // The constructor sets this up to return true by default.
+ MOCK_METHOD1(OnPacketHeader, bool(const QuicPacketHeader& header));
+ MOCK_METHOD1(OnFecProtectedPayload, void(base::StringPiece payload));
+ MOCK_METHOD1(OnStreamFrame, bool(const QuicStreamFrame& frame));
+ MOCK_METHOD1(OnAckFrame, bool(const QuicAckFrame& frame));
+ MOCK_METHOD1(OnCongestionFeedbackFrame,
+ bool(const QuicCongestionFeedbackFrame& frame));
+ MOCK_METHOD1(OnFecData, void(const QuicFecData& fec));
+ MOCK_METHOD1(OnRstStreamFrame, bool(const QuicRstStreamFrame& frame));
+ MOCK_METHOD1(OnConnectionCloseFrame,
+ bool(const QuicConnectionCloseFrame& frame));
+ MOCK_METHOD1(OnGoAwayFrame, bool(const QuicGoAwayFrame& frame));
+ MOCK_METHOD0(OnPacketComplete, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockFramerVisitor);
+};
+
+class NoOpFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+ NoOpFramerVisitor() {}
+
+ virtual void OnError(QuicFramer* framer) OVERRIDE {}
+ virtual void OnPacket() OVERRIDE {}
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE {}
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE {}
+ virtual void OnRevivedPacket() OVERRIDE {}
+ virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE;
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+ virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE {}
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+ virtual void OnFecData(const QuicFecData& fec) OVERRIDE {}
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE;
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+ virtual void OnPacketComplete() OVERRIDE {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NoOpFramerVisitor);
+};
+
+class FramerVisitorCapturingPublicReset : public NoOpFramerVisitor {
+ public:
+ FramerVisitorCapturingPublicReset();
+ virtual ~FramerVisitorCapturingPublicReset();
+
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE;
+
+ const QuicPublicResetPacket public_reset_packet() {
+ return public_reset_packet_;
+ }
+
+ private:
+ QuicPublicResetPacket public_reset_packet_;
+};
+
+class FramerVisitorCapturingFrames : public NoOpFramerVisitor {
+ public:
+ FramerVisitorCapturingFrames();
+ virtual ~FramerVisitorCapturingFrames();
+
+ // NoOpFramerVisitor
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE;
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE;
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+
+ size_t frame_count() const { return frame_count_; }
+ QuicPacketHeader* header() { return &header_; }
+ const std::vector<QuicStreamFrame>* stream_frames() const {
+ return &stream_frames_;
+ }
+ QuicAckFrame* ack() { return ack_.get(); }
+ QuicCongestionFeedbackFrame* feedback() { return feedback_.get(); }
+ QuicRstStreamFrame* rst() { return rst_.get(); }
+ QuicConnectionCloseFrame* close() { return close_.get(); }
+ QuicGoAwayFrame* goaway() { return goaway_.get(); }
+ QuicVersionNegotiationPacket* version_negotiation_packet() {
+ return version_negotiation_packet_.get();
+ }
+
+ private:
+ size_t frame_count_;
+ QuicPacketHeader header_;
+ std::vector<QuicStreamFrame> stream_frames_;
+ scoped_ptr<QuicAckFrame> ack_;
+ scoped_ptr<QuicCongestionFeedbackFrame> feedback_;
+ scoped_ptr<QuicRstStreamFrame> rst_;
+ scoped_ptr<QuicConnectionCloseFrame> close_;
+ scoped_ptr<QuicGoAwayFrame> goaway_;
+ scoped_ptr<QuicVersionNegotiationPacket> version_negotiation_packet_;
+
+ DISALLOW_COPY_AND_ASSIGN(FramerVisitorCapturingFrames);
+};
+
+class MockConnectionVisitor : public QuicConnectionVisitorInterface {
+ public:
+ MockConnectionVisitor();
+ virtual ~MockConnectionVisitor();
+
+ MOCK_METHOD4(OnPacket, bool(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const std::vector<QuicStreamFrame>& frame));
+ MOCK_METHOD1(OnRstStream, void(const QuicRstStreamFrame& frame));
+ MOCK_METHOD1(OnGoAway, void(const QuicGoAwayFrame& frame));
+ MOCK_METHOD2(ConnectionClose, void(QuicErrorCode error, bool from_peer));
+ MOCK_METHOD1(OnAck, void(const SequenceNumberSet& acked_packets));
+ MOCK_METHOD0(OnCanWrite, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockConnectionVisitor);
+};
+
+class MockHelper : public QuicConnectionHelperInterface {
+ public:
+ MockHelper();
+ virtual ~MockHelper();
+
+ MOCK_METHOD1(SetConnection, void(QuicConnection* connection));
+ const QuicClock* GetClock() const;
+ QuicRandom* GetRandomGenerator();
+ void AdvanceTime(QuicTime::Delta delta);
+ MOCK_METHOD2(WritePacketToWire, int(const QuicEncryptedPacket& packet,
+ int* error));
+ MOCK_METHOD0(IsWriteBlockedDataBuffered, bool());
+ MOCK_METHOD1(IsWriteBlocked, bool(int));
+ virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate);
+
+ private:
+ MockClock clock_;
+ MockRandom random_generator_;
+};
+
+class MockConnection : public QuicConnection {
+ public:
+ // Uses a MockHelper.
+ MockConnection(QuicGuid guid, IPEndPoint address, bool is_server);
+ MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper,
+ bool is_server);
+ virtual ~MockConnection();
+
+ // If the constructor that uses a MockHelper has been used then this method
+ // will advance the time of the MockClock.
+ void AdvanceTime(QuicTime::Delta delta);
+
+ MOCK_METHOD3(ProcessUdpPacket, void(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet));
+ MOCK_METHOD1(SendConnectionClose, void(QuicErrorCode error));
+ MOCK_METHOD2(SendConnectionCloseWithDetails, void(QuicErrorCode error,
+ const string& details));
+ MOCK_METHOD2(SendRstStream, void(QuicStreamId id,
+ QuicRstStreamErrorCode error));
+ MOCK_METHOD3(SendGoAway, void(QuicErrorCode error,
+ QuicStreamId last_good_stream_id,
+ const string& reason));
+ MOCK_METHOD0(OnCanWrite, bool());
+
+ void ProcessUdpPacketInternal(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) {
+ QuicConnection::ProcessUdpPacket(self_address, peer_address, packet);
+ }
+
+ virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+ return false;
+ }
+
+ private:
+ const bool has_mock_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnection);
+};
+
+class PacketSavingConnection : public MockConnection {
+ public:
+ PacketSavingConnection(QuicGuid guid, IPEndPoint address, bool is_server);
+ virtual ~PacketSavingConnection();
+
+ virtual bool SendOrQueuePacket(
+ EncryptionLevel level,
+ QuicPacketSequenceNumber sequence_number,
+ QuicPacket* packet,
+ QuicPacketEntropyHash entropy_hash,
+ HasRetransmittableData has_retransmittable_data) OVERRIDE;
+
+ std::vector<QuicPacket*> packets_;
+ std::vector<QuicEncryptedPacket*> encrypted_packets_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PacketSavingConnection);
+};
+
+class MockSession : public QuicSession {
+ public:
+ MockSession(QuicConnection* connection, bool is_server);
+ virtual ~MockSession();
+
+ MOCK_METHOD4(OnPacket, bool(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicPacketHeader& header,
+ const std::vector<QuicStreamFrame>& frame));
+ MOCK_METHOD2(ConnectionClose, void(QuicErrorCode error, bool from_peer));
+ MOCK_METHOD1(CreateIncomingReliableStream,
+ ReliableQuicStream*(QuicStreamId id));
+ MOCK_METHOD0(GetCryptoStream, QuicCryptoStream*());
+ MOCK_METHOD0(CreateOutgoingReliableStream, ReliableQuicStream*());
+ MOCK_METHOD4(WriteData, QuicConsumedData(QuicStreamId id,
+ base::StringPiece data,
+ QuicStreamOffset offset,
+ bool fin));
+ MOCK_METHOD0(IsHandshakeComplete, bool());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockSession);
+};
+
+class TestSession : public QuicSession {
+ public:
+ TestSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server);
+ virtual ~TestSession();
+
+ MOCK_METHOD1(CreateIncomingReliableStream,
+ ReliableQuicStream*(QuicStreamId id));
+ MOCK_METHOD0(CreateOutgoingReliableStream, ReliableQuicStream*());
+
+ void SetCryptoStream(QuicCryptoStream* stream);
+
+ virtual QuicCryptoStream* GetCryptoStream();
+
+ private:
+ QuicCryptoStream* crypto_stream_;
+ DISALLOW_COPY_AND_ASSIGN(TestSession);
+};
+
+class MockSendAlgorithm : public SendAlgorithmInterface {
+ public:
+ MockSendAlgorithm();
+ virtual ~MockSendAlgorithm();
+
+ MOCK_METHOD3(OnIncomingQuicCongestionFeedbackFrame,
+ void(const QuicCongestionFeedbackFrame&,
+ QuicTime feedback_receive_time,
+ const SentPacketsMap&));
+ MOCK_METHOD3(OnIncomingAck,
+ void(QuicPacketSequenceNumber, QuicByteCount, QuicTime::Delta));
+ MOCK_METHOD1(OnIncomingLoss, void(QuicTime));
+ MOCK_METHOD4(SentPacket, void(QuicTime sent_time, QuicPacketSequenceNumber,
+ QuicByteCount, Retransmission));
+ MOCK_METHOD2(AbandoningPacket, void(QuicPacketSequenceNumber sequence_number,
+ QuicByteCount abandoned_bytes));
+ MOCK_METHOD4(TimeUntilSend, QuicTime::Delta(QuicTime now, Retransmission,
+ HasRetransmittableData,
+ IsHandshake));
+ MOCK_METHOD0(BandwidthEstimate, QuicBandwidth(void));
+ MOCK_METHOD0(SmoothedRtt, QuicTime::Delta(void));
+ MOCK_METHOD0(RetransmissionDelay, QuicTime::Delta(void));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockSendAlgorithm);
+};
+
+class TestEntropyCalculator :
+ public QuicReceivedEntropyHashCalculatorInterface {
+ public:
+ TestEntropyCalculator() { }
+ virtual ~TestEntropyCalculator() { }
+
+ virtual QuicPacketEntropyHash EntropyHash(
+ QuicPacketSequenceNumber sequence_number) const OVERRIDE;
+};
+
+class TestDecompressorVisitor : public QuicSpdyDecompressor::Visitor {
+ public:
+ virtual ~TestDecompressorVisitor() {}
+ virtual bool OnDecompressedData(base::StringPiece data) OVERRIDE;
+ virtual void OnDecompressionError() OVERRIDE;
+
+ string data() { return data_; }
+ bool error() { return error_; }
+
+ private:
+ string data_;
+ bool error_;
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
diff --git a/chromium/net/quic/test_tools/reliable_quic_stream_peer.cc b/chromium/net/quic/test_tools/reliable_quic_stream_peer.cc
new file mode 100644
index 00000000000..31a64e94a45
--- /dev/null
+++ b/chromium/net/quic/test_tools/reliable_quic_stream_peer.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+
+#include "net/quic/reliable_quic_stream.h"
+
+namespace net {
+namespace test {
+
+// static
+void ReliableQuicStreamPeer::SetWriteSideClosed(bool value,
+ ReliableQuicStream* stream) {
+ stream->write_side_closed_ = value;
+}
+
+// static
+void ReliableQuicStreamPeer::SetStreamBytesWritten(
+ QuicStreamOffset stream_bytes_written,
+ ReliableQuicStream* stream) {
+ stream->stream_bytes_written_ = stream_bytes_written;
+}
+
+void ReliableQuicStreamPeer::SetHeadersDecompressed(
+ ReliableQuicStream* stream,
+ bool headers_decompressed) {
+ stream->headers_decompressed_ = headers_decompressed;
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/reliable_quic_stream_peer.h b/chromium/net/quic/test_tools/reliable_quic_stream_peer.h
new file mode 100644
index 00000000000..346a9b46411
--- /dev/null
+++ b/chromium/net/quic/test_tools/reliable_quic_stream_peer.h
@@ -0,0 +1,32 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
+#define NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
+
+#include "base/basictypes.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class ReliableQuicStream;
+
+namespace test {
+
+class ReliableQuicStreamPeer {
+ public:
+ static void SetWriteSideClosed(bool value, ReliableQuicStream* stream);
+ static void SetStreamBytesWritten(QuicStreamOffset stream_bytes_written,
+ ReliableQuicStream* stream);
+ static void SetHeadersDecompressed(ReliableQuicStream* stream,
+ bool headers_decompressed);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ReliableQuicStreamPeer);
+};
+
+} // namespace test
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_RELIABLE_QUIC_STREAM_PEER_H_
diff --git a/chromium/net/quic/test_tools/simple_quic_framer.cc b/chromium/net/quic/test_tools/simple_quic_framer.cc
new file mode 100644
index 00000000000..46be3a83c5a
--- /dev/null
+++ b/chromium/net/quic/test_tools/simple_quic_framer.cc
@@ -0,0 +1,198 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/simple_quic_framer.h"
+
+#include "net/quic/crypto/crypto_framer.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace test {
+
+class SimpleFramerVisitor : public QuicFramerVisitorInterface {
+ public:
+ SimpleFramerVisitor()
+ : error_(QUIC_NO_ERROR) {
+ }
+
+ virtual void OnError(QuicFramer* framer) OVERRIDE {
+ error_ = framer->error();
+ }
+
+ virtual bool OnProtocolVersionMismatch(QuicVersion version) OVERRIDE {
+ return false;
+ }
+
+ virtual void OnPacket() OVERRIDE {}
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE {}
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& packet) OVERRIDE {}
+ virtual void OnRevivedPacket() OVERRIDE {}
+
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE {
+ has_header_ = true;
+ header_ = header;
+ return true;
+ }
+
+ virtual void OnFecProtectedPayload(StringPiece payload) OVERRIDE {}
+
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE {
+ // Save a copy of the data so it is valid after the packet is processed.
+ stream_data_.push_back(frame.data.as_string());
+ QuicStreamFrame stream_frame(frame);
+ // Make sure that the stream frame points to this data.
+ stream_frame.data = stream_data_.back();
+ stream_frames_.push_back(stream_frame);
+ return true;
+ }
+
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE {
+ ack_frames_.push_back(frame);
+ return true;
+ }
+
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE {
+ feedback_frames_.push_back(frame);
+ return true;
+ }
+
+ virtual void OnFecData(const QuicFecData& fec) OVERRIDE {
+ fec_data_ = fec;
+ fec_redundancy_ = fec_data_.redundancy.as_string();
+ fec_data_.redundancy = fec_redundancy_;
+ }
+
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE {
+ rst_stream_frames_.push_back(frame);
+ return true;
+ }
+
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame& frame) OVERRIDE {
+ connection_close_frames_.push_back(frame);
+ return true;
+ }
+
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE {
+ goaway_frames_.push_back(frame);
+ return true;
+ }
+
+ virtual void OnPacketComplete() OVERRIDE {}
+
+ const QuicPacketHeader& header() const { return header_; }
+ const vector<QuicAckFrame>& ack_frames() const { return ack_frames_; }
+ const vector<QuicConnectionCloseFrame>& connection_close_frames() const {
+ return connection_close_frames_;
+ }
+ const vector<QuicCongestionFeedbackFrame>& feedback_frames() const {
+ return feedback_frames_;
+ }
+ const vector<QuicGoAwayFrame>& goaway_frames() const {
+ return goaway_frames_;
+ }
+ const vector<QuicRstStreamFrame>& rst_stream_frames() const {
+ return rst_stream_frames_;
+ }
+ const vector<QuicStreamFrame>& stream_frames() const {
+ return stream_frames_;
+ }
+ const QuicFecData& fec_data() const {
+ return fec_data_;
+ }
+
+ private:
+ QuicErrorCode error_;
+ bool has_header_;
+ QuicPacketHeader header_;
+ QuicFecData fec_data_;
+ string fec_redundancy_;
+ vector<QuicAckFrame> ack_frames_;
+ vector<QuicCongestionFeedbackFrame> feedback_frames_;
+ vector<QuicStreamFrame> stream_frames_;
+ vector<QuicRstStreamFrame> rst_stream_frames_;
+ vector<QuicGoAwayFrame> goaway_frames_;
+ vector<QuicConnectionCloseFrame> connection_close_frames_;
+ vector<string> stream_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleFramerVisitor);
+};
+
+SimpleQuicFramer::SimpleQuicFramer()
+ : framer_(QuicVersionMax(), QuicTime::Zero(), true) {
+}
+
+SimpleQuicFramer::~SimpleQuicFramer() {
+}
+
+bool SimpleQuicFramer::ProcessPacket(const QuicPacket& packet) {
+ scoped_ptr<QuicEncryptedPacket> encrypted(framer_.EncryptPacket(
+ ENCRYPTION_NONE, 0, packet));
+ return ProcessPacket(*encrypted);
+}
+
+bool SimpleQuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
+ visitor_.reset(new SimpleFramerVisitor);
+ framer_.set_visitor(visitor_.get());
+ return framer_.ProcessPacket(packet);
+}
+
+const QuicPacketHeader& SimpleQuicFramer::header() const {
+ return visitor_->header();
+}
+
+const QuicFecData& SimpleQuicFramer::fec_data() const {
+ return visitor_->fec_data();
+}
+
+QuicFramer* SimpleQuicFramer::framer() {
+ return &framer_;
+}
+
+size_t SimpleQuicFramer::num_frames() const {
+ return ack_frames().size() +
+ stream_frames().size() +
+ feedback_frames().size() +
+ rst_stream_frames().size() +
+ goaway_frames().size() +
+ connection_close_frames().size();
+}
+
+const vector<QuicAckFrame>& SimpleQuicFramer::ack_frames() const {
+ return visitor_->ack_frames();
+}
+
+const vector<QuicStreamFrame>& SimpleQuicFramer::stream_frames() const {
+ return visitor_->stream_frames();
+}
+
+const vector<QuicRstStreamFrame>& SimpleQuicFramer::rst_stream_frames() const {
+ return visitor_->rst_stream_frames();
+}
+
+const vector<QuicCongestionFeedbackFrame>&
+SimpleQuicFramer::feedback_frames() const {
+ return visitor_->feedback_frames();
+}
+
+const vector<QuicGoAwayFrame>&
+SimpleQuicFramer::goaway_frames() const {
+ return visitor_->goaway_frames();
+}
+
+const vector<QuicConnectionCloseFrame>&
+SimpleQuicFramer::connection_close_frames() const {
+ return visitor_->connection_close_frames();
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/simple_quic_framer.h b/chromium/net/quic/test_tools/simple_quic_framer.h
new file mode 100644
index 00000000000..4416019dba3
--- /dev/null
+++ b/chromium/net/quic/test_tools/simple_quic_framer.h
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
+#define NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+
+namespace net {
+
+class CryptoHandshakeMessage;
+struct QuicAckFrame;
+class QuicConnection;
+class QuicConnectionVisitorInterface;
+class QuicPacketCreator;
+class ReceiveAlgorithmInterface;
+class SendAlgorithmInterface;
+
+namespace test {
+
+class SimpleFramerVisitor;
+
+// Peer to make public a number of otherwise private QuicFramer methods.
+class SimpleQuicFramer {
+ public:
+ SimpleQuicFramer();
+ ~SimpleQuicFramer();
+
+ bool ProcessPacket(const QuicEncryptedPacket& packet);
+ bool ProcessPacket(const QuicPacket& packet);
+
+ const QuicPacketHeader& header() const;
+ size_t num_frames() const;
+ const std::vector<QuicAckFrame>& ack_frames() const;
+ const std::vector<QuicConnectionCloseFrame>& connection_close_frames() const;
+ const std::vector<QuicCongestionFeedbackFrame>& feedback_frames() const;
+ const std::vector<QuicGoAwayFrame>& goaway_frames() const;
+ const std::vector<QuicRstStreamFrame>& rst_stream_frames() const;
+ const std::vector<QuicStreamFrame>& stream_frames() const;
+ const QuicFecData& fec_data() const;
+ QuicFramer* framer();
+
+ private:
+ QuicFramer framer_;
+ scoped_ptr<SimpleFramerVisitor> visitor_;
+ DISALLOW_COPY_AND_ASSIGN(SimpleQuicFramer);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_SIMPLE_QUIC_FRAMER_H_
diff --git a/chromium/net/quic/test_tools/test_task_runner.cc b/chromium/net/quic/test_tools/test_task_runner.cc
new file mode 100644
index 00000000000..385fd5dfa82
--- /dev/null
+++ b/chromium/net/quic/test_tools/test_task_runner.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 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.
+
+#include "net/quic/test_tools/test_task_runner.h"
+
+#include <algorithm>
+
+#include "net/quic/test_tools/mock_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test {
+
+TestTaskRunner::TestTaskRunner(MockClock* clock)
+ : clock_(clock) {
+}
+
+TestTaskRunner::~TestTaskRunner() {
+}
+
+bool TestTaskRunner::PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) {
+ EXPECT_GE(delay, base::TimeDelta());
+ tasks_.push_back(
+ PostedTask(from_here, task, clock_->NowInTicks(), delay,
+ base::TestPendingTask::NESTABLE));
+ return false;
+}
+
+bool TestTaskRunner::RunsTasksOnCurrentThread() const {
+ return true;
+}
+
+const std::vector<PostedTask>& TestTaskRunner::GetPostedTasks() const {
+ return tasks_;
+}
+
+void TestTaskRunner::RunNextTask() {
+ // Find the next task to run, advance the time to the correct time
+ // and then run the task.
+ std::vector<PostedTask>::iterator next = FindNextTask();
+ DCHECK(next != tasks_.end());
+ clock_->AdvanceTime(QuicTime::Delta::FromMicroseconds(
+ (next->GetTimeToRun() - clock_->NowInTicks()).InMicroseconds()));
+ PostedTask task = *next;
+ tasks_.erase(next);
+ task.task.Run();
+}
+
+namespace {
+
+struct ShouldRunBeforeLessThan {
+ bool operator()(const PostedTask& task1, const PostedTask& task2) const {
+ return task1.ShouldRunBefore(task2);
+ }
+};
+
+} // namespace
+
+std::vector<PostedTask>::iterator TestTaskRunner::FindNextTask() {
+ return std::min_element(
+ tasks_.begin(), tasks_.end(), ShouldRunBeforeLessThan());
+}
+
+} // namespace test
+} // namespace net
diff --git a/chromium/net/quic/test_tools/test_task_runner.h b/chromium/net/quic/test_tools/test_task_runner.h
new file mode 100644
index 00000000000..e25bf2aef46
--- /dev/null
+++ b/chromium/net/quic/test_tools/test_task_runner.h
@@ -0,0 +1,54 @@
+// Copyright (c) 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.
+//
+// Common utilities for Quic tests
+
+#ifndef NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_
+#define NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/task_runner.h"
+#include "base/test/test_pending_task.h"
+
+namespace net {
+
+class MockClock;
+
+namespace test {
+
+typedef base::TestPendingTask PostedTask;
+
+class TestTaskRunner : public base::TaskRunner {
+ public:
+ explicit TestTaskRunner(MockClock* clock);
+
+ // base::TaskRunner implementation.
+ virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
+ const base::Closure& task,
+ base::TimeDelta delay) OVERRIDE;
+ virtual bool RunsTasksOnCurrentThread() const OVERRIDE;
+
+ const std::vector<PostedTask>& GetPostedTasks() const;
+
+ void RunNextTask();
+
+ protected:
+ virtual ~TestTaskRunner();
+
+ private:
+ std::vector<PostedTask>::iterator FindNextTask();
+
+ MockClock* const clock_;
+ std::vector<PostedTask> tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTaskRunner);
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_TEST_TASK_RUNNER_H_
diff --git a/chromium/net/server/http_connection.cc b/chromium/net/server/http_connection.cc
new file mode 100644
index 00000000000..d964cb0738b
--- /dev/null
+++ b/chromium/net/server/http_connection.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 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.
+
+#include "net/server/http_connection.h"
+
+#include "net/server/http_server.h"
+#include "net/server/http_server_response_info.h"
+#include "net/server/web_socket.h"
+#include "net/socket/stream_listen_socket.h"
+
+namespace net {
+
+int HttpConnection::last_id_ = 0;
+
+void HttpConnection::Send(const std::string& data) {
+ if (!socket_.get())
+ return;
+ socket_->Send(data);
+}
+
+void HttpConnection::Send(const char* bytes, int len) {
+ if (!socket_.get())
+ return;
+ socket_->Send(bytes, len);
+}
+
+void HttpConnection::Send(const HttpServerResponseInfo& response) {
+ Send(response.Serialize());
+}
+
+HttpConnection::HttpConnection(HttpServer* server, StreamListenSocket* sock)
+ : server_(server),
+ socket_(sock) {
+ id_ = last_id_++;
+}
+
+HttpConnection::~HttpConnection() {
+ DetachSocket();
+ server_->delegate_->OnClose(id_);
+}
+
+void HttpConnection::DetachSocket() {
+ socket_ = NULL;
+}
+
+void HttpConnection::Shift(int num_bytes) {
+ recv_data_ = recv_data_.substr(num_bytes);
+}
+
+} // namespace net
diff --git a/chromium/net/server/http_connection.h b/chromium/net/server/http_connection.h
new file mode 100644
index 00000000000..b0e37663d4f
--- /dev/null
+++ b/chromium/net/server/http_connection.h
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#ifndef NET_SERVER_HTTP_CONNECTION_H_
+#define NET_SERVER_HTTP_CONNECTION_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/http/http_status_code.h"
+
+namespace net {
+
+class HttpServer;
+class HttpServerResponseInfo;
+class StreamListenSocket;
+class WebSocket;
+
+class HttpConnection {
+ public:
+ ~HttpConnection();
+
+ void Send(const std::string& data);
+ void Send(const char* bytes, int len);
+ void Send(const HttpServerResponseInfo& response);
+
+ void Shift(int num_bytes);
+
+ const std::string& recv_data() const { return recv_data_; }
+ int id() const { return id_; }
+
+ private:
+ friend class HttpServer;
+ static int last_id_;
+
+ HttpConnection(HttpServer* server, StreamListenSocket* sock);
+
+ void DetachSocket();
+
+ HttpServer* server_;
+ scoped_refptr<StreamListenSocket> socket_;
+ scoped_ptr<WebSocket> web_socket_;
+ std::string recv_data_;
+ int id_;
+ DISALLOW_COPY_AND_ASSIGN(HttpConnection);
+};
+
+} // namespace net
+
+#endif // NET_SERVER_HTTP_CONNECTION_H_
diff --git a/chromium/net/server/http_server.cc b/chromium/net/server/http_server.cc
new file mode 100644
index 00000000000..373025c4aa6
--- /dev/null
+++ b/chromium/net/server/http_server.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 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.
+
+#include "net/server/http_server.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_byteorder.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/server/http_connection.h"
+#include "net/server/http_server_request_info.h"
+#include "net/server/http_server_response_info.h"
+#include "net/server/web_socket.h"
+#include "net/socket/tcp_listen_socket.h"
+
+namespace net {
+
+HttpServer::HttpServer(const StreamListenSocketFactory& factory,
+ HttpServer::Delegate* delegate)
+ : delegate_(delegate),
+ server_(factory.CreateAndListen(this)) {
+}
+
+void HttpServer::AcceptWebSocket(
+ int connection_id,
+ const HttpServerRequestInfo& request) {
+ HttpConnection* connection = FindConnection(connection_id);
+ if (connection == NULL)
+ return;
+
+ DCHECK(connection->web_socket_.get());
+ connection->web_socket_->Accept(request);
+}
+
+void HttpServer::SendOverWebSocket(int connection_id,
+ const std::string& data) {
+ HttpConnection* connection = FindConnection(connection_id);
+ if (connection == NULL)
+ return;
+ DCHECK(connection->web_socket_.get());
+ connection->web_socket_->Send(data);
+}
+
+void HttpServer::SendResponse(int connection_id,
+ const HttpServerResponseInfo& response) {
+ HttpConnection* connection = FindConnection(connection_id);
+ if (connection == NULL)
+ return;
+ connection->Send(response);
+}
+
+void HttpServer::Send(int connection_id,
+ HttpStatusCode status_code,
+ const std::string& data,
+ const std::string& content_type) {
+ HttpServerResponseInfo response(status_code);
+ response.SetBody(data, content_type);
+ SendResponse(connection_id, response);
+}
+
+void HttpServer::Send200(int connection_id,
+ const std::string& data,
+ const std::string& content_type) {
+ Send(connection_id, HTTP_OK, data, content_type);
+}
+
+void HttpServer::Send404(int connection_id) {
+ SendResponse(connection_id, HttpServerResponseInfo::CreateFor404());
+}
+
+void HttpServer::Send500(int connection_id, const std::string& message) {
+ SendResponse(connection_id, HttpServerResponseInfo::CreateFor500(message));
+}
+
+void HttpServer::Close(int connection_id) {
+ HttpConnection* connection = FindConnection(connection_id);
+ if (connection == NULL)
+ return;
+
+ // Initiating close from server-side does not lead to the DidClose call.
+ // Do it manually here.
+ DidClose(connection->socket_.get());
+}
+
+int HttpServer::GetLocalAddress(IPEndPoint* address) {
+ if (!server_)
+ return ERR_SOCKET_NOT_CONNECTED;
+ return server_->GetLocalAddress(address);
+}
+
+void HttpServer::DidAccept(StreamListenSocket* server,
+ StreamListenSocket* socket) {
+ HttpConnection* connection = new HttpConnection(this, socket);
+ id_to_connection_[connection->id()] = connection;
+ socket_to_connection_[socket] = connection;
+}
+
+void HttpServer::DidRead(StreamListenSocket* socket,
+ const char* data,
+ int len) {
+ HttpConnection* connection = FindConnection(socket);
+ DCHECK(connection != NULL);
+ if (connection == NULL)
+ return;
+
+ connection->recv_data_.append(data, len);
+ while (connection->recv_data_.length()) {
+ if (connection->web_socket_.get()) {
+ std::string message;
+ WebSocket::ParseResult result = connection->web_socket_->Read(&message);
+ if (result == WebSocket::FRAME_INCOMPLETE)
+ break;
+
+ if (result == WebSocket::FRAME_CLOSE ||
+ result == WebSocket::FRAME_ERROR) {
+ Close(connection->id());
+ break;
+ }
+ delegate_->OnWebSocketMessage(connection->id(), message);
+ continue;
+ }
+
+ HttpServerRequestInfo request;
+ size_t pos = 0;
+ if (!ParseHeaders(connection, &request, &pos))
+ break;
+
+ std::string connection_header = request.GetHeaderValue("connection");
+ if (connection_header == "Upgrade") {
+ connection->web_socket_.reset(WebSocket::CreateWebSocket(connection,
+ request,
+ &pos));
+
+ if (!connection->web_socket_.get()) // Not enough data was received.
+ break;
+ delegate_->OnWebSocketRequest(connection->id(), request);
+ connection->Shift(pos);
+ continue;
+ }
+
+ const char kContentLength[] = "content-length";
+ if (request.headers.count(kContentLength)) {
+ size_t content_length = 0;
+ const size_t kMaxBodySize = 100 << 20;
+ if (!base::StringToSizeT(request.GetHeaderValue(kContentLength),
+ &content_length) ||
+ content_length > kMaxBodySize) {
+ connection->Send(HttpServerResponseInfo::CreateFor500(
+ "request content-length too big or unknown: " +
+ request.GetHeaderValue(kContentLength)));
+ DidClose(socket);
+ break;
+ }
+
+ if (connection->recv_data_.length() - pos < content_length)
+ break; // Not enough data was received yet.
+ request.data = connection->recv_data_.substr(pos, content_length);
+ pos += content_length;
+ }
+
+ delegate_->OnHttpRequest(connection->id(), request);
+ connection->Shift(pos);
+ }
+}
+
+void HttpServer::DidClose(StreamListenSocket* socket) {
+ HttpConnection* connection = FindConnection(socket);
+ DCHECK(connection != NULL);
+ id_to_connection_.erase(connection->id());
+ socket_to_connection_.erase(connection->socket_.get());
+ delete connection;
+}
+
+HttpServer::~HttpServer() {
+ STLDeleteContainerPairSecondPointers(
+ id_to_connection_.begin(), id_to_connection_.end());
+ server_ = NULL;
+}
+
+//
+// HTTP Request Parser
+// This HTTP request parser uses a simple state machine to quickly parse
+// through the headers. The parser is not 100% complete, as it is designed
+// for use in this simple test driver.
+//
+// Known issues:
+// - does not handle whitespace on first HTTP line correctly. Expects
+// a single space between the method/url and url/protocol.
+
+// Input character types.
+enum header_parse_inputs {
+ INPUT_SPACE,
+ INPUT_CR,
+ INPUT_LF,
+ INPUT_COLON,
+ INPUT_DEFAULT,
+ MAX_INPUTS,
+};
+
+// Parser states.
+enum header_parse_states {
+ ST_METHOD, // Receiving the method
+ ST_URL, // Receiving the URL
+ ST_PROTO, // Receiving the protocol
+ ST_HEADER, // Starting a Request Header
+ ST_NAME, // Receiving a request header name
+ ST_SEPARATOR, // Receiving the separator between header name and value
+ ST_VALUE, // Receiving a request header value
+ ST_DONE, // Parsing is complete and successful
+ ST_ERR, // Parsing encountered invalid syntax.
+ MAX_STATES
+};
+
+// State transition table
+int parser_state[MAX_STATES][MAX_INPUTS] = {
+/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_METHOD },
+/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_URL },
+/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_PROTO },
+/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR },
+/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_VALUE, ST_NAME },
+/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_VALUE, ST_ERR },
+/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_VALUE },
+/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE },
+/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR }
+};
+
+// Convert an input character to the parser's input token.
+int charToInput(char ch) {
+ switch(ch) {
+ case ' ':
+ return INPUT_SPACE;
+ case '\r':
+ return INPUT_CR;
+ case '\n':
+ return INPUT_LF;
+ case ':':
+ return INPUT_COLON;
+ }
+ return INPUT_DEFAULT;
+}
+
+bool HttpServer::ParseHeaders(HttpConnection* connection,
+ HttpServerRequestInfo* info,
+ size_t* ppos) {
+ size_t& pos = *ppos;
+ size_t data_len = connection->recv_data_.length();
+ int state = ST_METHOD;
+ std::string buffer;
+ std::string header_name;
+ std::string header_value;
+ while (pos < data_len) {
+ char ch = connection->recv_data_[pos++];
+ int input = charToInput(ch);
+ int next_state = parser_state[state][input];
+
+ bool transition = (next_state != state);
+ if (transition) {
+ // Do any actions based on state transitions.
+ switch (state) {
+ case ST_METHOD:
+ info->method = buffer;
+ buffer.clear();
+ break;
+ case ST_URL:
+ info->path = buffer;
+ buffer.clear();
+ break;
+ case ST_PROTO:
+ // TODO(mbelshe): Deal better with parsing protocol.
+ DCHECK(buffer == "HTTP/1.1");
+ buffer.clear();
+ break;
+ case ST_NAME:
+ header_name = StringToLowerASCII(buffer);
+ buffer.clear();
+ break;
+ case ST_VALUE:
+ TrimWhitespaceASCII(buffer, TRIM_LEADING, &header_value);
+ // TODO(mbelshe): Deal better with duplicate headers
+ DCHECK(info->headers.find(header_name) == info->headers.end());
+ info->headers[header_name] = header_value;
+ buffer.clear();
+ break;
+ case ST_SEPARATOR:
+ break;
+ }
+ state = next_state;
+ } else {
+ // Do any actions based on current state
+ switch (state) {
+ case ST_METHOD:
+ case ST_URL:
+ case ST_PROTO:
+ case ST_VALUE:
+ case ST_NAME:
+ buffer.append(&ch, 1);
+ break;
+ case ST_DONE:
+ DCHECK(input == INPUT_LF);
+ return true;
+ case ST_ERR:
+ return false;
+ }
+ }
+ }
+ // No more characters, but we haven't finished parsing yet.
+ return false;
+}
+
+HttpConnection* HttpServer::FindConnection(int connection_id) {
+ IdToConnectionMap::iterator it = id_to_connection_.find(connection_id);
+ if (it == id_to_connection_.end())
+ return NULL;
+ return it->second;
+}
+
+HttpConnection* HttpServer::FindConnection(StreamListenSocket* socket) {
+ SocketToConnectionMap::iterator it = socket_to_connection_.find(socket);
+ if (it == socket_to_connection_.end())
+ return NULL;
+ return it->second;
+}
+
+} // namespace net
diff --git a/chromium/net/server/http_server.h b/chromium/net/server/http_server.h
new file mode 100644
index 00000000000..f4345752e2d
--- /dev/null
+++ b/chromium/net/server/http_server.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#ifndef NET_SERVER_HTTP_SERVER_H_
+#define NET_SERVER_HTTP_SERVER_H_
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "net/http/http_status_code.h"
+#include "net/socket/stream_listen_socket.h"
+
+namespace net {
+
+class HttpConnection;
+class HttpServerRequestInfo;
+class HttpServerResponseInfo;
+class IPEndPoint;
+class WebSocket;
+
+class HttpServer : public StreamListenSocket::Delegate,
+ public base::RefCountedThreadSafe<HttpServer> {
+ public:
+ class Delegate {
+ public:
+ virtual void OnHttpRequest(int connection_id,
+ const HttpServerRequestInfo& info) = 0;
+
+ virtual void OnWebSocketRequest(int connection_id,
+ const HttpServerRequestInfo& info) = 0;
+
+ virtual void OnWebSocketMessage(int connection_id,
+ const std::string& data) = 0;
+
+ virtual void OnClose(int connection_id) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ HttpServer(const StreamListenSocketFactory& socket_factory,
+ HttpServer::Delegate* delegate);
+
+ void AcceptWebSocket(int connection_id,
+ const HttpServerRequestInfo& request);
+ void SendOverWebSocket(int connection_id, const std::string& data);
+ void SendResponse(int connection_id, const HttpServerResponseInfo& response);
+ void Send(int connection_id,
+ HttpStatusCode status_code,
+ const std::string& data,
+ const std::string& mime_type);
+ void Send200(int connection_id,
+ const std::string& data,
+ const std::string& mime_type);
+ void Send404(int connection_id);
+ void Send500(int connection_id, const std::string& message);
+
+ void Close(int connection_id);
+
+ // Copies the local address to |address|. Returns a network error code.
+ int GetLocalAddress(IPEndPoint* address);
+
+ // ListenSocketDelegate
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* socket) OVERRIDE;
+ virtual void DidRead(StreamListenSocket* socket,
+ const char* data,
+ int len) OVERRIDE;
+ virtual void DidClose(StreamListenSocket* socket) OVERRIDE;
+
+ protected:
+ virtual ~HttpServer();
+
+ private:
+ friend class base::RefCountedThreadSafe<HttpServer>;
+ friend class HttpConnection;
+
+ // Expects the raw data to be stored in recv_data_. If parsing is successful,
+ // will remove the data parsed from recv_data_, leaving only the unused
+ // recv data.
+ bool ParseHeaders(HttpConnection* connection,
+ HttpServerRequestInfo* info,
+ size_t* pos);
+
+ HttpConnection* FindConnection(int connection_id);
+ HttpConnection* FindConnection(StreamListenSocket* socket);
+
+ HttpServer::Delegate* delegate_;
+ scoped_refptr<StreamListenSocket> server_;
+ typedef std::map<int, HttpConnection*> IdToConnectionMap;
+ IdToConnectionMap id_to_connection_;
+ typedef std::map<StreamListenSocket*, HttpConnection*> SocketToConnectionMap;
+ SocketToConnectionMap socket_to_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpServer);
+};
+
+} // namespace net
+
+#endif // NET_SERVER_HTTP_SERVER_H_
diff --git a/chromium/net/server/http_server_request_info.cc b/chromium/net/server/http_server_request_info.cc
new file mode 100644
index 00000000000..67965f29aa3
--- /dev/null
+++ b/chromium/net/server/http_server_request_info.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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/server/http_server_request_info.h"
+
+#include "base/strings/string_util.h"
+
+namespace net {
+
+HttpServerRequestInfo::HttpServerRequestInfo() {}
+
+HttpServerRequestInfo::~HttpServerRequestInfo() {}
+
+std::string HttpServerRequestInfo::GetHeaderValue(
+ const std::string& header_name) const {
+ DCHECK_EQ(StringToLowerASCII(header_name), header_name);
+ HttpServerRequestInfo::HeadersMap::const_iterator it =
+ headers.find(header_name);
+ if (it != headers.end())
+ return it->second;
+ return std::string();
+}
+
+} // namespace net
diff --git a/chromium/net/server/http_server_request_info.h b/chromium/net/server/http_server_request_info.h
new file mode 100644
index 00000000000..62824187901
--- /dev/null
+++ b/chromium/net/server/http_server_request_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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_SERVER_HTTP_SERVER_REQUEST_INFO_H_
+#define NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_
+
+#include <map>
+#include <string>
+
+namespace net {
+
+// Meta information about an HTTP request.
+// This is geared toward servers in that it keeps a map of the headers and
+// values rather than just a list of header strings (which net::HttpRequestInfo
+// does).
+class HttpServerRequestInfo {
+ public:
+ HttpServerRequestInfo();
+ ~HttpServerRequestInfo();
+
+ // Returns header value for given header name. |header_name| should be
+ // lower case.
+ std::string GetHeaderValue(const std::string& header_name) const;
+
+ // Request method.
+ std::string method;
+
+ // Request line.
+ std::string path;
+
+ // Request data.
+ std::string data;
+
+ // A map of the names -> values for HTTP headers. These should always
+ // contain lower case field names.
+ typedef std::map<std::string, std::string> HeadersMap;
+ mutable HeadersMap headers;
+};
+
+} // namespace net
+
+#endif // NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_
diff --git a/chromium/net/server/http_server_response_info.cc b/chromium/net/server/http_server_response_info.cc
new file mode 100644
index 00000000000..e4c6043aa8d
--- /dev/null
+++ b/chromium/net/server/http_server_response_info.cc
@@ -0,0 +1,67 @@
+// Copyright 2013 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/server/http_server_response_info.h"
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_request_headers.h"
+
+namespace net {
+
+HttpServerResponseInfo::HttpServerResponseInfo() : status_code_(HTTP_OK) {}
+
+HttpServerResponseInfo::HttpServerResponseInfo(HttpStatusCode status_code)
+ : status_code_(status_code) {}
+
+HttpServerResponseInfo::~HttpServerResponseInfo() {}
+
+// static
+HttpServerResponseInfo HttpServerResponseInfo::CreateFor404() {
+ HttpServerResponseInfo response(HTTP_NOT_FOUND);
+ response.SetBody(std::string(), "text/html");
+ return response;
+}
+
+// static
+HttpServerResponseInfo HttpServerResponseInfo::CreateFor500(
+ const std::string& body) {
+ HttpServerResponseInfo response(HTTP_INTERNAL_SERVER_ERROR);
+ response.SetBody(body, "text/html");
+ return response;
+}
+
+void HttpServerResponseInfo::AddHeader(const std::string& name,
+ const std::string& value) {
+ headers_.push_back(std::make_pair(name, value));
+}
+
+void HttpServerResponseInfo::SetBody(const std::string& body,
+ const std::string& content_type) {
+ DCHECK(body_.empty());
+ body_ = body;
+ AddHeader(HttpRequestHeaders::kContentLength,
+ base::StringPrintf("%" PRIuS, body.length()));
+ AddHeader(HttpRequestHeaders::kContentType, content_type);
+}
+
+std::string HttpServerResponseInfo::Serialize() const {
+ std::string response = base::StringPrintf(
+ "HTTP/1.1 %d %s\r\n", status_code_, GetHttpReasonPhrase(status_code_));
+ Headers::const_iterator header;
+ for (header = headers_.begin(); header != headers_.end(); ++header)
+ response += header->first + ":" + header->second + "\r\n";
+
+ return response + "\r\n" + body_;
+}
+
+HttpStatusCode HttpServerResponseInfo::status_code() const {
+ return status_code_;
+}
+
+const std::string& HttpServerResponseInfo::body() const {
+ return body_;
+}
+
+} // namespace net
diff --git a/chromium/net/server/http_server_response_info.h b/chromium/net/server/http_server_response_info.h
new file mode 100644
index 00000000000..d6cedaa8446
--- /dev/null
+++ b/chromium/net/server/http_server_response_info.h
@@ -0,0 +1,46 @@
+// Copyright 2013 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_SERVER_HTTP_SERVER_RESPONSE_INFO_H_
+#define NET_SERVER_HTTP_SERVER_RESPONSE_INFO_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "net/http/http_status_code.h"
+
+namespace net {
+
+class HttpServerResponseInfo {
+ public:
+ // Creates a 200 OK HttpServerResponseInfo.
+ HttpServerResponseInfo();
+ explicit HttpServerResponseInfo(HttpStatusCode status_code);
+ ~HttpServerResponseInfo();
+
+ static HttpServerResponseInfo CreateFor404();
+ static HttpServerResponseInfo CreateFor500(const std::string& body);
+
+ void AddHeader(const std::string& name, const std::string& value);
+
+ // This also adds an appropriate Content-Length header.
+ void SetBody(const std::string& body, const std::string& content_type);
+
+ std::string Serialize() const;
+
+ HttpStatusCode status_code() const;
+ const std::string& body() const;
+
+ private:
+ typedef std::vector<std::pair<std::string, std::string> > Headers;
+
+ HttpStatusCode status_code_;
+ Headers headers_;
+ std::string body_;
+};
+
+} // namespace net
+
+#endif // NET_SERVER_HTTP_SERVER_RESPONSE_INFO_H_
diff --git a/chromium/net/server/http_server_response_info_unittest.cc b/chromium/net/server/http_server_response_info_unittest.cc
new file mode 100644
index 00000000000..f7b8e251d34
--- /dev/null
+++ b/chromium/net/server/http_server_response_info_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright 2013 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/http/http_status_code.h"
+#include "net/server/http_server_response_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(HttpServerResponseInfoTest, StatusLine) {
+ HttpServerResponseInfo response;
+ ASSERT_EQ(HTTP_OK, response.status_code());
+ ASSERT_EQ("HTTP/1.1 200 OK\r\n\r\n", response.Serialize());
+}
+
+TEST(HttpServerResponseInfoTest, Headers) {
+ HttpServerResponseInfo response;
+ response.AddHeader("A", "1");
+ response.AddHeader("A", "2");
+ ASSERT_EQ("HTTP/1.1 200 OK\r\nA:1\r\nA:2\r\n\r\n", response.Serialize());
+}
+
+TEST(HttpServerResponseInfoTest, Body) {
+ HttpServerResponseInfo response;
+ ASSERT_EQ(std::string(), response.body());
+ response.SetBody("body", "type");
+ ASSERT_EQ("body", response.body());
+ ASSERT_EQ(
+ "HTTP/1.1 200 OK\r\nContent-Length:4\r\nContent-Type:type\r\n\r\nbody",
+ response.Serialize());
+}
+
+TEST(HttpServerResponseInfoTest, CreateFor404) {
+ HttpServerResponseInfo response = HttpServerResponseInfo::CreateFor404();
+ ASSERT_EQ(
+ "HTTP/1.1 404 Not Found\r\n"
+ "Content-Length:0\r\nContent-Type:text/html\r\n\r\n",
+ response.Serialize());
+}
+
+TEST(HttpServerResponseInfoTest, CreateFor500) {
+ HttpServerResponseInfo response =
+ HttpServerResponseInfo::CreateFor500("mess");
+ ASSERT_EQ(
+ "HTTP/1.1 500 Internal Server Error\r\n"
+ "Content-Length:4\r\nContent-Type:text/html\r\n\r\nmess",
+ response.Serialize());
+}
+
+} // namespace net
diff --git a/chromium/net/server/http_server_unittest.cc b/chromium/net/server/http_server_unittest.cc
new file mode 100644
index 00000000000..48a2ce7571f
--- /dev/null
+++ b/chromium/net/server/http_server_unittest.cc
@@ -0,0 +1,319 @@
+// Copyright 2013 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 <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/server/http_server.h"
+#include "net/server/http_server_request_info.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/tcp_listen_socket.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+void SetTimedOutAndQuitLoop(const base::WeakPtr<bool> timed_out,
+ const base::Closure& quit_loop_func) {
+ if (timed_out) {
+ *timed_out = true;
+ quit_loop_func.Run();
+ }
+}
+
+bool RunLoopWithTimeout(base::RunLoop* run_loop) {
+ bool timed_out = false;
+ base::WeakPtrFactory<bool> timed_out_weak_factory(&timed_out);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SetTimedOutAndQuitLoop,
+ timed_out_weak_factory.GetWeakPtr(),
+ run_loop->QuitClosure()),
+ base::TimeDelta::FromSeconds(1));
+ run_loop->Run();
+ return !timed_out;
+}
+
+class TestHttpClient {
+ public:
+ TestHttpClient() : connect_result_(OK) {}
+
+ int ConnectAndWait(const IPEndPoint& address) {
+ AddressList addresses(address);
+ NetLog::Source source;
+ socket_.reset(new TCPClientSocket(addresses, NULL, source));
+
+ base::RunLoop run_loop;
+ connect_result_ = socket_->Connect(base::Bind(&TestHttpClient::OnConnect,
+ base::Unretained(this),
+ run_loop.QuitClosure()));
+ if (connect_result_ != OK && connect_result_ != ERR_IO_PENDING)
+ return connect_result_;
+
+ if (!RunLoopWithTimeout(&run_loop))
+ return ERR_TIMED_OUT;
+ return connect_result_;
+ }
+
+ void Send(const std::string& data) {
+ write_buffer_ =
+ new DrainableIOBuffer(new StringIOBuffer(data), data.length());
+ Write();
+ }
+
+ private:
+ void OnConnect(const base::Closure& quit_loop, int result) {
+ connect_result_ = result;
+ quit_loop.Run();
+ }
+
+ void Write() {
+ int result = socket_->Write(
+ write_buffer_.get(),
+ write_buffer_->BytesRemaining(),
+ base::Bind(&TestHttpClient::OnWrite, base::Unretained(this)));
+ if (result != ERR_IO_PENDING)
+ OnWrite(result);
+ }
+
+ void OnWrite(int result) {
+ ASSERT_GT(result, 0);
+ write_buffer_->DidConsume(result);
+ if (write_buffer_->BytesRemaining())
+ Write();
+ }
+
+ scoped_refptr<DrainableIOBuffer> write_buffer_;
+ scoped_ptr<TCPClientSocket> socket_;
+ int connect_result_;
+};
+
+} // namespace
+
+class HttpServerTest : public testing::Test,
+ public HttpServer::Delegate {
+ public:
+ HttpServerTest() : quit_after_request_count_(0) {}
+
+ virtual void SetUp() OVERRIDE {
+ TCPListenSocketFactory socket_factory("127.0.0.1", 0);
+ server_ = new HttpServer(socket_factory, this);
+ ASSERT_EQ(OK, server_->GetLocalAddress(&server_address_));
+ }
+
+ virtual void OnHttpRequest(int connection_id,
+ const HttpServerRequestInfo& info) OVERRIDE {
+ requests_.push_back(info);
+ if (requests_.size() == quit_after_request_count_)
+ run_loop_quit_func_.Run();
+ }
+
+ virtual void OnWebSocketRequest(int connection_id,
+ const HttpServerRequestInfo& info) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual void OnWebSocketMessage(int connection_id,
+ const std::string& data) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual void OnClose(int connection_id) OVERRIDE {}
+
+ bool RunUntilRequestsReceived(size_t count) {
+ quit_after_request_count_ = count;
+ if (requests_.size() == count)
+ return true;
+
+ base::RunLoop run_loop;
+ run_loop_quit_func_ = run_loop.QuitClosure();
+ bool success = RunLoopWithTimeout(&run_loop);
+ run_loop_quit_func_.Reset();
+ return success;
+ }
+
+ protected:
+ scoped_refptr<HttpServer> server_;
+ IPEndPoint server_address_;
+ base::Closure run_loop_quit_func_;
+ std::vector<HttpServerRequestInfo> requests_;
+
+ private:
+ size_t quit_after_request_count_;
+};
+
+TEST_F(HttpServerTest, Request) {
+ TestHttpClient client;
+ ASSERT_EQ(OK, client.ConnectAndWait(server_address_));
+ client.Send("GET /test HTTP/1.1\r\n\r\n");
+ ASSERT_TRUE(RunUntilRequestsReceived(1));
+ ASSERT_EQ("GET", requests_[0].method);
+ ASSERT_EQ("/test", requests_[0].path);
+ ASSERT_EQ("", requests_[0].data);
+ ASSERT_EQ(0u, requests_[0].headers.size());
+}
+
+TEST_F(HttpServerTest, RequestWithHeaders) {
+ TestHttpClient client;
+ ASSERT_EQ(OK, client.ConnectAndWait(server_address_));
+ const char* kHeaders[][3] = {
+ {"Header", ": ", "1"},
+ {"HeaderWithNoWhitespace", ":", "1"},
+ {"HeaderWithWhitespace", " : \t ", "1 1 1 \t "},
+ {"HeaderWithColon", ": ", "1:1"},
+ {"EmptyHeader", ":", ""},
+ {"EmptyHeaderWithWhitespace", ": \t ", ""},
+ {"HeaderWithNonASCII", ": ", "\u00f7"},
+ };
+ std::string headers;
+ for (size_t i = 0; i < arraysize(kHeaders); ++i) {
+ headers +=
+ std::string(kHeaders[i][0]) + kHeaders[i][1] + kHeaders[i][2] + "\r\n";
+ }
+
+ client.Send("GET /test HTTP/1.1\r\n" + headers + "\r\n");
+ ASSERT_TRUE(RunUntilRequestsReceived(1));
+ ASSERT_EQ("", requests_[0].data);
+
+ for (size_t i = 0; i < arraysize(kHeaders); ++i) {
+ std::string field = StringToLowerASCII(std::string(kHeaders[i][0]));
+ std::string value = kHeaders[i][2];
+ ASSERT_EQ(1u, requests_[0].headers.count(field)) << field;
+ ASSERT_EQ(value, requests_[0].headers[field]) << kHeaders[i][0];
+ }
+}
+
+TEST_F(HttpServerTest, RequestWithBody) {
+ TestHttpClient client;
+ ASSERT_EQ(OK, client.ConnectAndWait(server_address_));
+ std::string body = "a" + std::string(1 << 10, 'b') + "c";
+ client.Send(base::StringPrintf(
+ "GET /test HTTP/1.1\r\n"
+ "SomeHeader: 1\r\n"
+ "Content-Length: %" PRIuS "\r\n\r\n%s",
+ body.length(),
+ body.c_str()));
+ ASSERT_TRUE(RunUntilRequestsReceived(1));
+ ASSERT_EQ(2u, requests_[0].headers.size());
+ ASSERT_EQ(body.length(), requests_[0].data.length());
+ ASSERT_EQ('a', body[0]);
+ ASSERT_EQ('c', *body.rbegin());
+}
+
+TEST_F(HttpServerTest, RequestWithTooLargeBody) {
+ class TestURLFetcherDelegate : public URLFetcherDelegate {
+ public:
+ TestURLFetcherDelegate(const base::Closure& quit_loop_func)
+ : quit_loop_func_(quit_loop_func) {}
+ virtual ~TestURLFetcherDelegate() {}
+
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE {
+ EXPECT_EQ(HTTP_INTERNAL_SERVER_ERROR, source->GetResponseCode());
+ quit_loop_func_.Run();
+ }
+
+ private:
+ base::Closure quit_loop_func_;
+ };
+
+ base::RunLoop run_loop;
+ TestURLFetcherDelegate delegate(run_loop.QuitClosure());
+
+ scoped_refptr<URLRequestContextGetter> request_context_getter(
+ new TestURLRequestContextGetter(base::MessageLoopProxy::current()));
+ scoped_ptr<URLFetcher> fetcher(
+ URLFetcher::Create(GURL(base::StringPrintf("http://127.0.0.1:%d/test",
+ server_address_.port())),
+ URLFetcher::GET,
+ &delegate));
+ fetcher->SetRequestContext(request_context_getter.get());
+ fetcher->AddExtraRequestHeader(
+ base::StringPrintf("content-length:%d", 1 << 30));
+ fetcher->Start();
+
+ ASSERT_TRUE(RunLoopWithTimeout(&run_loop));
+ ASSERT_EQ(0u, requests_.size());
+}
+
+namespace {
+
+class MockStreamListenSocket : public StreamListenSocket {
+ public:
+ MockStreamListenSocket(StreamListenSocket::Delegate* delegate)
+ : StreamListenSocket(kInvalidSocket, delegate) {}
+
+ virtual void Accept() OVERRIDE { NOTREACHED(); }
+
+ private:
+ virtual ~MockStreamListenSocket() {}
+};
+
+} // namespace
+
+TEST_F(HttpServerTest, RequestWithBodySplitAcrossPackets) {
+ scoped_refptr<StreamListenSocket> socket(
+ new MockStreamListenSocket(server_.get()));
+ server_->DidAccept(NULL, socket.get());
+ std::string body("body");
+ std::string request = base::StringPrintf(
+ "GET /test HTTP/1.1\r\n"
+ "SomeHeader: 1\r\n"
+ "Content-Length: %" PRIuS "\r\n\r\n%s",
+ body.length(),
+ body.c_str());
+ server_->DidRead(socket.get(), request.c_str(), request.length() - 2);
+ ASSERT_EQ(0u, requests_.size());
+ server_->DidRead(socket.get(), request.c_str() + request.length() - 2, 2);
+ ASSERT_EQ(1u, requests_.size());
+ ASSERT_EQ(body, requests_[0].data);
+}
+
+TEST_F(HttpServerTest, MultipleRequestsOnSameConnection) {
+ // The idea behind this test is that requests with or without bodies should
+ // not break parsing of the next request.
+ TestHttpClient client;
+ ASSERT_EQ(OK, client.ConnectAndWait(server_address_));
+ std::string body = "body";
+ client.Send(base::StringPrintf(
+ "GET /test HTTP/1.1\r\n"
+ "Content-Length: %" PRIuS "\r\n\r\n%s",
+ body.length(),
+ body.c_str()));
+ ASSERT_TRUE(RunUntilRequestsReceived(1));
+ ASSERT_EQ(body, requests_[0].data);
+
+ client.Send("GET /test2 HTTP/1.1\r\n\r\n");
+ ASSERT_TRUE(RunUntilRequestsReceived(2));
+ ASSERT_EQ("/test2", requests_[1].path);
+
+ client.Send("GET /test3 HTTP/1.1\r\n\r\n");
+ ASSERT_TRUE(RunUntilRequestsReceived(3));
+ ASSERT_EQ("/test3", requests_[2].path);
+}
+
+} // namespace net
diff --git a/chromium/net/server/web_socket.cc b/chromium/net/server/web_socket.cc
new file mode 100644
index 00000000000..f06b425fee0
--- /dev/null
+++ b/chromium/net/server/web_socket.cc
@@ -0,0 +1,406 @@
+// Copyright (c) 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.
+
+#include "net/server/web_socket.h"
+
+#include <limits>
+
+#include "base/base64.h"
+#include "base/rand_util.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_byteorder.h"
+#include "net/server/http_connection.h"
+#include "net/server/http_server_request_info.h"
+#include "net/server/http_server_response_info.h"
+
+namespace net {
+
+namespace {
+
+static uint32 WebSocketKeyFingerprint(const std::string& str) {
+ std::string result;
+ const char* p_char = str.c_str();
+ int length = str.length();
+ int spaces = 0;
+ for (int i = 0; i < length; ++i) {
+ if (p_char[i] >= '0' && p_char[i] <= '9')
+ result.append(&p_char[i], 1);
+ else if (p_char[i] == ' ')
+ spaces++;
+ }
+ if (spaces == 0)
+ return 0;
+ int64 number = 0;
+ if (!base::StringToInt64(result, &number))
+ return 0;
+ return base::HostToNet32(static_cast<uint32>(number / spaces));
+}
+
+class WebSocketHixie76 : public net::WebSocket {
+ public:
+ static net::WebSocket* Create(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos) {
+ if (connection->recv_data().length() < *pos + kWebSocketHandshakeBodyLen)
+ return NULL;
+ return new WebSocketHixie76(connection, request, pos);
+ }
+
+ virtual void Accept(const HttpServerRequestInfo& request) OVERRIDE {
+ std::string key1 = request.GetHeaderValue("sec-websocket-key1");
+ std::string key2 = request.GetHeaderValue("sec-websocket-key2");
+
+ uint32 fp1 = WebSocketKeyFingerprint(key1);
+ uint32 fp2 = WebSocketKeyFingerprint(key2);
+
+ char data[16];
+ memcpy(data, &fp1, 4);
+ memcpy(data + 4, &fp2, 4);
+ memcpy(data + 8, &key3_[0], 8);
+
+ base::MD5Digest digest;
+ base::MD5Sum(data, 16, &digest);
+
+ std::string origin = request.GetHeaderValue("origin");
+ std::string host = request.GetHeaderValue("host");
+ std::string location = "ws://" + host + request.path;
+ connection_->Send(base::StringPrintf(
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: %s\r\n"
+ "Sec-WebSocket-Location: %s\r\n"
+ "\r\n",
+ origin.c_str(),
+ location.c_str()));
+ connection_->Send(reinterpret_cast<char*>(digest.a), 16);
+ }
+
+ virtual ParseResult Read(std::string* message) OVERRIDE {
+ DCHECK(message);
+ const std::string& data = connection_->recv_data();
+ if (data[0])
+ return FRAME_ERROR;
+
+ size_t pos = data.find('\377', 1);
+ if (pos == std::string::npos)
+ return FRAME_INCOMPLETE;
+
+ std::string buffer(data.begin() + 1, data.begin() + pos);
+ message->swap(buffer);
+ connection_->Shift(pos + 1);
+
+ return FRAME_OK;
+ }
+
+ virtual void Send(const std::string& message) OVERRIDE {
+ char message_start = 0;
+ char message_end = -1;
+ connection_->Send(&message_start, 1);
+ connection_->Send(message);
+ connection_->Send(&message_end, 1);
+ }
+
+ private:
+ static const int kWebSocketHandshakeBodyLen;
+
+ WebSocketHixie76(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos) : WebSocket(connection) {
+ std::string key1 = request.GetHeaderValue("sec-websocket-key1");
+ std::string key2 = request.GetHeaderValue("sec-websocket-key2");
+
+ if (key1.empty()) {
+ connection->Send(HttpServerResponseInfo::CreateFor500(
+ "Invalid request format. Sec-WebSocket-Key1 is empty or isn't "
+ "specified."));
+ return;
+ }
+
+ if (key2.empty()) {
+ connection->Send(HttpServerResponseInfo::CreateFor500(
+ "Invalid request format. Sec-WebSocket-Key2 is empty or isn't "
+ "specified."));
+ return;
+ }
+
+ key3_ = connection->recv_data().substr(
+ *pos,
+ *pos + kWebSocketHandshakeBodyLen);
+ *pos += kWebSocketHandshakeBodyLen;
+ }
+
+ std::string key3_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHixie76);
+};
+
+const int WebSocketHixie76::kWebSocketHandshakeBodyLen = 8;
+
+
+// Constants for hybi-10 frame format.
+
+typedef int OpCode;
+
+const OpCode kOpCodeContinuation = 0x0;
+const OpCode kOpCodeText = 0x1;
+const OpCode kOpCodeBinary = 0x2;
+const OpCode kOpCodeClose = 0x8;
+const OpCode kOpCodePing = 0x9;
+const OpCode kOpCodePong = 0xA;
+
+const unsigned char kFinalBit = 0x80;
+const unsigned char kReserved1Bit = 0x40;
+const unsigned char kReserved2Bit = 0x20;
+const unsigned char kReserved3Bit = 0x10;
+const unsigned char kOpCodeMask = 0xF;
+const unsigned char kMaskBit = 0x80;
+const unsigned char kPayloadLengthMask = 0x7F;
+
+const size_t kMaxSingleBytePayloadLength = 125;
+const size_t kTwoBytePayloadLengthField = 126;
+const size_t kEightBytePayloadLengthField = 127;
+const size_t kMaskingKeyWidthInBytes = 4;
+
+class WebSocketHybi17 : public WebSocket {
+ public:
+ static WebSocket* Create(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos) {
+ std::string version = request.GetHeaderValue("sec-websocket-version");
+ if (version != "8" && version != "13")
+ return NULL;
+
+ std::string key = request.GetHeaderValue("sec-websocket-key");
+ if (key.empty()) {
+ connection->Send(HttpServerResponseInfo::CreateFor500(
+ "Invalid request format. Sec-WebSocket-Key is empty or isn't "
+ "specified."));
+ return NULL;
+ }
+ return new WebSocketHybi17(connection, request, pos);
+ }
+
+ virtual void Accept(const HttpServerRequestInfo& request) OVERRIDE {
+ static const char* const kWebSocketGuid =
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ std::string key = request.GetHeaderValue("sec-websocket-key");
+ std::string data = base::StringPrintf("%s%s", key.c_str(), kWebSocketGuid);
+ std::string encoded_hash;
+ base::Base64Encode(base::SHA1HashString(data), &encoded_hash);
+
+ std::string response = base::StringPrintf(
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %s\r\n"
+ "\r\n",
+ encoded_hash.c_str());
+ connection_->Send(response);
+ }
+
+ virtual ParseResult Read(std::string* message) OVERRIDE {
+ const std::string& frame = connection_->recv_data();
+ int bytes_consumed = 0;
+
+ ParseResult result =
+ WebSocket::DecodeFrameHybi17(frame, true, &bytes_consumed, message);
+ if (result == FRAME_OK)
+ connection_->Shift(bytes_consumed);
+ if (result == FRAME_CLOSE)
+ closed_ = true;
+ return result;
+ }
+
+ virtual void Send(const std::string& message) OVERRIDE {
+ if (closed_)
+ return;
+ std::string data = WebSocket::EncodeFrameHybi17(message, 0);
+ connection_->Send(data);
+ }
+
+ private:
+ WebSocketHybi17(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos)
+ : WebSocket(connection),
+ op_code_(0),
+ final_(false),
+ reserved1_(false),
+ reserved2_(false),
+ reserved3_(false),
+ masked_(false),
+ payload_(0),
+ payload_length_(0),
+ frame_end_(0),
+ closed_(false) {
+ }
+
+ OpCode op_code_;
+ bool final_;
+ bool reserved1_;
+ bool reserved2_;
+ bool reserved3_;
+ bool masked_;
+ const char* payload_;
+ size_t payload_length_;
+ const char* frame_end_;
+ bool closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHybi17);
+};
+
+} // anonymous namespace
+
+WebSocket* WebSocket::CreateWebSocket(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos) {
+ WebSocket* socket = WebSocketHybi17::Create(connection, request, pos);
+ if (socket)
+ return socket;
+
+ return WebSocketHixie76::Create(connection, request, pos);
+}
+
+// static
+WebSocket::ParseResult WebSocket::DecodeFrameHybi17(const std::string& frame,
+ bool client_frame,
+ int* bytes_consumed,
+ std::string* output) {
+ size_t data_length = frame.length();
+ if (data_length < 2)
+ return FRAME_INCOMPLETE;
+
+ const char* buffer_begin = const_cast<char*>(frame.data());
+ const char* p = buffer_begin;
+ const char* buffer_end = p + data_length;
+
+ unsigned char first_byte = *p++;
+ unsigned char second_byte = *p++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ int op_code = first_byte & kOpCodeMask;
+ bool masked = (second_byte & kMaskBit) != 0;
+ if (!final || reserved1 || reserved2 || reserved3)
+ return FRAME_ERROR; // Extensions and not supported.
+
+ bool closed = false;
+ switch (op_code) {
+ case kOpCodeClose:
+ closed = true;
+ break;
+ case kOpCodeText:
+ break;
+ case kOpCodeBinary: // We don't support binary frames yet.
+ case kOpCodeContinuation: // We don't support binary frames yet.
+ case kOpCodePing: // We don't support binary frames yet.
+ case kOpCodePong: // We don't support binary frames yet.
+ default:
+ return FRAME_ERROR;
+ }
+
+ if (client_frame && !masked) // In Hybi-17 spec client MUST mask his frame.
+ return FRAME_ERROR;
+
+ uint64 payload_length64 = second_byte & kPayloadLengthMask;
+ if (payload_length64 > kMaxSingleBytePayloadLength) {
+ int extended_payload_length_size;
+ if (payload_length64 == kTwoBytePayloadLengthField)
+ extended_payload_length_size = 2;
+ else {
+ DCHECK(payload_length64 == kEightBytePayloadLengthField);
+ extended_payload_length_size = 8;
+ }
+ if (buffer_end - p < extended_payload_length_size)
+ return FRAME_INCOMPLETE;
+ payload_length64 = 0;
+ for (int i = 0; i < extended_payload_length_size; ++i) {
+ payload_length64 <<= 8;
+ payload_length64 |= static_cast<unsigned char>(*p++);
+ }
+ }
+
+ size_t actual_masking_key_length = masked ? kMaskingKeyWidthInBytes : 0;
+ static const uint64 max_payload_length = 0x7FFFFFFFFFFFFFFFull;
+ static size_t max_length = std::numeric_limits<size_t>::max();
+ if (payload_length64 > max_payload_length ||
+ payload_length64 + actual_masking_key_length > max_length) {
+ // WebSocket frame length too large.
+ return FRAME_ERROR;
+ }
+ size_t payload_length = static_cast<size_t>(payload_length64);
+
+ size_t total_length = actual_masking_key_length + payload_length;
+ if (static_cast<size_t>(buffer_end - p) < total_length)
+ return FRAME_INCOMPLETE;
+
+ if (masked) {
+ output->resize(payload_length);
+ const char* masking_key = p;
+ char* payload = const_cast<char*>(p + kMaskingKeyWidthInBytes);
+ for (size_t i = 0; i < payload_length; ++i) // Unmask the payload.
+ (*output)[i] = payload[i] ^ masking_key[i % kMaskingKeyWidthInBytes];
+ } else {
+ std::string buffer(p, p + payload_length);
+ output->swap(buffer);
+ }
+
+ size_t pos = p + actual_masking_key_length + payload_length - buffer_begin;
+ *bytes_consumed = pos;
+ return closed ? FRAME_CLOSE : FRAME_OK;
+}
+
+// static
+std::string WebSocket::EncodeFrameHybi17(const std::string& message,
+ int masking_key) {
+ std::vector<char> frame;
+ OpCode op_code = kOpCodeText;
+ size_t data_length = message.length();
+
+ frame.push_back(kFinalBit | op_code);
+ char mask_key_bit = masking_key != 0 ? kMaskBit : 0;
+ if (data_length <= kMaxSingleBytePayloadLength)
+ frame.push_back(data_length | mask_key_bit);
+ else if (data_length <= 0xFFFF) {
+ frame.push_back(kTwoBytePayloadLengthField | mask_key_bit);
+ frame.push_back((data_length & 0xFF00) >> 8);
+ frame.push_back(data_length & 0xFF);
+ } else {
+ frame.push_back(kEightBytePayloadLengthField | mask_key_bit);
+ char extended_payload_length[8];
+ size_t remaining = data_length;
+ // Fill the length into extended_payload_length in the network byte order.
+ for (int i = 0; i < 8; ++i) {
+ extended_payload_length[7 - i] = remaining & 0xFF;
+ remaining >>= 8;
+ }
+ frame.insert(frame.end(),
+ extended_payload_length,
+ extended_payload_length + 8);
+ DCHECK(!remaining);
+ }
+
+ const char* data = const_cast<char*>(message.data());
+ if (masking_key != 0) {
+ const char* mask_bytes = reinterpret_cast<char*>(&masking_key);
+ frame.insert(frame.end(), mask_bytes, mask_bytes + 4);
+ for (size_t i = 0; i < data_length; ++i) // Mask the payload.
+ frame.push_back(data[i] ^ mask_bytes[i % kMaskingKeyWidthInBytes]);
+ } else {
+ frame.insert(frame.end(), data, data + data_length);
+ }
+ return std::string(&frame[0], frame.size());
+}
+
+WebSocket::WebSocket(HttpConnection* connection) : connection_(connection) {
+}
+
+} // namespace net
diff --git a/chromium/net/server/web_socket.h b/chromium/net/server/web_socket.h
new file mode 100644
index 00000000000..49ced84ee6d
--- /dev/null
+++ b/chromium/net/server/web_socket.h
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#ifndef NET_SERVER_WEB_SOCKET_H_
+#define NET_SERVER_WEB_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace net {
+
+class HttpConnection;
+class HttpServerRequestInfo;
+
+class WebSocket {
+ public:
+ enum ParseResult {
+ FRAME_OK,
+ FRAME_INCOMPLETE,
+ FRAME_CLOSE,
+ FRAME_ERROR
+ };
+
+ static WebSocket* CreateWebSocket(HttpConnection* connection,
+ const HttpServerRequestInfo& request,
+ size_t* pos);
+
+ static ParseResult DecodeFrameHybi17(const std::string& frame,
+ bool client_frame,
+ int* bytes_consumed,
+ std::string* output);
+
+ static std::string EncodeFrameHybi17(const std::string& data,
+ int masking_key);
+
+ virtual void Accept(const HttpServerRequestInfo& request) = 0;
+ virtual ParseResult Read(std::string* message) = 0;
+ virtual void Send(const std::string& message) = 0;
+ virtual ~WebSocket() {}
+
+ protected:
+ explicit WebSocket(HttpConnection* connection);
+ HttpConnection* connection_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocket);
+};
+
+} // namespace net
+
+#endif // NET_SERVER_WEB_SOCKET_H_
diff --git a/chromium/net/socket/buffered_write_stream_socket.cc b/chromium/net/socket/buffered_write_stream_socket.cc
new file mode 100644
index 00000000000..cf13c5e439a
--- /dev/null
+++ b/chromium/net/socket/buffered_write_stream_socket.cc
@@ -0,0 +1,161 @@
+// Copyright (c) 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.
+
+#include "net/socket/buffered_write_stream_socket.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+namespace {
+
+void AppendBuffer(GrowableIOBuffer* dst, IOBuffer* src, int src_len) {
+ int old_capacity = dst->capacity();
+ dst->SetCapacity(old_capacity + src_len);
+ memcpy(dst->StartOfBuffer() + old_capacity, src->data(), src_len);
+}
+
+} // anonymous namespace
+
+BufferedWriteStreamSocket::BufferedWriteStreamSocket(
+ scoped_ptr<StreamSocket> socket_to_wrap)
+ : wrapped_socket_(socket_to_wrap.Pass()),
+ io_buffer_(new GrowableIOBuffer()),
+ backup_buffer_(new GrowableIOBuffer()),
+ weak_factory_(this),
+ callback_pending_(false),
+ wrapped_write_in_progress_(false),
+ error_(0) {
+}
+
+BufferedWriteStreamSocket::~BufferedWriteStreamSocket() {
+}
+
+int BufferedWriteStreamSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return wrapped_socket_->Read(buf, buf_len, callback);
+}
+
+int BufferedWriteStreamSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (error_) {
+ return error_;
+ }
+ GrowableIOBuffer* idle_buffer =
+ wrapped_write_in_progress_ ? backup_buffer_.get() : io_buffer_.get();
+ AppendBuffer(idle_buffer, buf, buf_len);
+ if (!callback_pending_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BufferedWriteStreamSocket::DoDelayedWrite,
+ weak_factory_.GetWeakPtr()));
+ callback_pending_ = true;
+ }
+ return buf_len;
+}
+
+bool BufferedWriteStreamSocket::SetReceiveBufferSize(int32 size) {
+ return wrapped_socket_->SetReceiveBufferSize(size);
+}
+
+bool BufferedWriteStreamSocket::SetSendBufferSize(int32 size) {
+ return wrapped_socket_->SetSendBufferSize(size);
+}
+
+int BufferedWriteStreamSocket::Connect(const CompletionCallback& callback) {
+ return wrapped_socket_->Connect(callback);
+}
+
+void BufferedWriteStreamSocket::Disconnect() {
+ wrapped_socket_->Disconnect();
+}
+
+bool BufferedWriteStreamSocket::IsConnected() const {
+ return wrapped_socket_->IsConnected();
+}
+
+bool BufferedWriteStreamSocket::IsConnectedAndIdle() const {
+ return wrapped_socket_->IsConnectedAndIdle();
+}
+
+int BufferedWriteStreamSocket::GetPeerAddress(IPEndPoint* address) const {
+ return wrapped_socket_->GetPeerAddress(address);
+}
+
+int BufferedWriteStreamSocket::GetLocalAddress(IPEndPoint* address) const {
+ return wrapped_socket_->GetLocalAddress(address);
+}
+
+const BoundNetLog& BufferedWriteStreamSocket::NetLog() const {
+ return wrapped_socket_->NetLog();
+}
+
+void BufferedWriteStreamSocket::SetSubresourceSpeculation() {
+ wrapped_socket_->SetSubresourceSpeculation();
+}
+
+void BufferedWriteStreamSocket::SetOmniboxSpeculation() {
+ wrapped_socket_->SetOmniboxSpeculation();
+}
+
+bool BufferedWriteStreamSocket::WasEverUsed() const {
+ return wrapped_socket_->WasEverUsed();
+}
+
+bool BufferedWriteStreamSocket::UsingTCPFastOpen() const {
+ return wrapped_socket_->UsingTCPFastOpen();
+}
+
+bool BufferedWriteStreamSocket::WasNpnNegotiated() const {
+ return wrapped_socket_->WasNpnNegotiated();
+}
+
+NextProto BufferedWriteStreamSocket::GetNegotiatedProtocol() const {
+ return wrapped_socket_->GetNegotiatedProtocol();
+}
+
+bool BufferedWriteStreamSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ return wrapped_socket_->GetSSLInfo(ssl_info);
+}
+
+void BufferedWriteStreamSocket::DoDelayedWrite() {
+ int result = wrapped_socket_->Write(
+ io_buffer_.get(),
+ io_buffer_->RemainingCapacity(),
+ base::Bind(&BufferedWriteStreamSocket::OnIOComplete,
+ base::Unretained(this)));
+ if (result == ERR_IO_PENDING) {
+ callback_pending_ = true;
+ wrapped_write_in_progress_ = true;
+ } else {
+ OnIOComplete(result);
+ }
+}
+
+void BufferedWriteStreamSocket::OnIOComplete(int result) {
+ callback_pending_ = false;
+ wrapped_write_in_progress_ = false;
+ if (backup_buffer_->RemainingCapacity()) {
+ AppendBuffer(io_buffer_.get(), backup_buffer_.get(),
+ backup_buffer_->RemainingCapacity());
+ backup_buffer_->SetCapacity(0);
+ }
+ if (result < 0) {
+ error_ = result;
+ io_buffer_->SetCapacity(0);
+ } else {
+ io_buffer_->set_offset(io_buffer_->offset() + result);
+ if (io_buffer_->RemainingCapacity()) {
+ DoDelayedWrite();
+ } else {
+ io_buffer_->SetCapacity(0);
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/socket/buffered_write_stream_socket.h b/chromium/net/socket/buffered_write_stream_socket.h
new file mode 100644
index 00000000000..aad5736d0b0
--- /dev/null
+++ b/chromium/net/socket/buffered_write_stream_socket.h
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_BUFFERED_WRITE_STREAM_SOCKET_H_
+#define NET_SOCKET_BUFFERED_WRITE_STREAM_SOCKET_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_log.h"
+#include "net/socket/stream_socket.h"
+
+namespace base {
+class TimeDelta;
+}
+
+namespace net {
+
+class AddressList;
+class GrowableIOBuffer;
+class IPEndPoint;
+
+// A StreamSocket decorator. All functions are passed through to the wrapped
+// socket, except for Write().
+//
+// Writes are buffered locally so that multiple Write()s to this class are
+// issued as only one Write() to the wrapped socket. This is useful to force
+// multiple requests to be issued in a single packet, as is needed to trigger
+// edge cases in HTTP pipelining.
+//
+// Note that the Write() always returns synchronously. It will either buffer the
+// entire input or return the most recently reported error.
+//
+// There are no bounds on the local buffer size. Use carefully.
+class NET_EXPORT_PRIVATE BufferedWriteStreamSocket : public StreamSocket {
+ public:
+ explicit BufferedWriteStreamSocket(scoped_ptr<StreamSocket> socket_to_wrap);
+ virtual ~BufferedWriteStreamSocket();
+
+ // Socket interface
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ // StreamSocket interface
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ private:
+ void DoDelayedWrite();
+ void OnIOComplete(int result);
+
+ scoped_ptr<StreamSocket> wrapped_socket_;
+ scoped_refptr<GrowableIOBuffer> io_buffer_;
+ scoped_refptr<GrowableIOBuffer> backup_buffer_;
+ base::WeakPtrFactory<BufferedWriteStreamSocket> weak_factory_;
+ bool callback_pending_;
+ bool wrapped_write_in_progress_;
+ int error_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedWriteStreamSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_H_
diff --git a/chromium/net/socket/buffered_write_stream_socket_unittest.cc b/chromium/net/socket/buffered_write_stream_socket_unittest.cc
new file mode 100644
index 00000000000..485295f33f6
--- /dev/null
+++ b/chromium/net/socket/buffered_write_stream_socket_unittest.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#include "net/socket/buffered_write_stream_socket.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class BufferedWriteStreamSocketTest : public testing::Test {
+ public:
+ void Finish() {
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data_->at_read_eof());
+ EXPECT_TRUE(data_->at_write_eof());
+ }
+
+ void Initialize(MockWrite* writes, size_t writes_count) {
+ data_.reset(new DeterministicSocketData(NULL, 0, writes, writes_count));
+ data_->set_connect_data(MockConnect(SYNCHRONOUS, 0));
+ if (writes_count) {
+ data_->StopAfter(writes_count);
+ }
+ scoped_ptr<DeterministicMockTCPClientSocket> wrapped_socket(
+ new DeterministicMockTCPClientSocket(net_log_.net_log(), data_.get()));
+ data_->set_delegate(wrapped_socket->AsWeakPtr());
+ socket_.reset(new BufferedWriteStreamSocket(
+ wrapped_socket.PassAs<StreamSocket>()));
+ socket_->Connect(callback_.callback());
+ }
+
+ void TestWrite(const char* text) {
+ scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(text));
+ EXPECT_EQ(buf->size(),
+ socket_->Write(buf.get(), buf->size(), callback_.callback()));
+ }
+
+ scoped_ptr<BufferedWriteStreamSocket> socket_;
+ scoped_ptr<DeterministicSocketData> data_;
+ BoundNetLog net_log_;
+ TestCompletionCallback callback_;
+};
+
+TEST_F(BufferedWriteStreamSocketTest, SingleWrite) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "abc"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abc");
+ Finish();
+}
+
+TEST_F(BufferedWriteStreamSocketTest, AsyncWrite) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "abc"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abc");
+ data_->Run();
+ Finish();
+}
+
+TEST_F(BufferedWriteStreamSocketTest, TwoWritesIntoOne) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "abcdef"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abc");
+ TestWrite("def");
+ Finish();
+}
+
+TEST_F(BufferedWriteStreamSocketTest, WriteWhileBlocked) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "abc"),
+ MockWrite(ASYNC, 1, "def"),
+ MockWrite(ASYNC, 2, "ghi"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abc");
+ base::MessageLoop::current()->RunUntilIdle();
+ TestWrite("def");
+ data_->RunFor(1);
+ TestWrite("ghi");
+ data_->RunFor(1);
+ Finish();
+}
+
+TEST_F(BufferedWriteStreamSocketTest, ContinuesPartialWrite) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "abc"),
+ MockWrite(ASYNC, 1, "def"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abcdef");
+ data_->Run();
+ Finish();
+}
+
+TEST_F(BufferedWriteStreamSocketTest, TwoSeparateWrites) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "abc"),
+ MockWrite(ASYNC, 1, "def"),
+ };
+ Initialize(writes, arraysize(writes));
+ TestWrite("abc");
+ data_->RunFor(1);
+ TestWrite("def");
+ data_->RunFor(1);
+ Finish();
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_factory.cc b/chromium/net/socket/client_socket_factory.cc
new file mode 100644
index 00000000000..a86688e3333
--- /dev/null
+++ b/chromium/net/socket/client_socket_factory.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_factory.h"
+
+#include "base/lazy_instance.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "build/build_config.h"
+#include "net/cert/cert_database.h"
+#include "net/socket/client_socket_handle.h"
+#if defined(USE_OPENSSL)
+#include "net/socket/ssl_client_socket_openssl.h"
+#elif defined(USE_NSS) || defined(OS_MACOSX) || defined(OS_WIN)
+#include "net/socket/ssl_client_socket_nss.h"
+#endif
+#include "net/socket/tcp_client_socket.h"
+#include "net/udp/udp_client_socket.h"
+
+namespace net {
+
+class X509Certificate;
+
+namespace {
+
+// ChromeOS and Linux may require interaction with smart cards or TPMs, which
+// may cause NSS functions to block for upwards of several seconds. To avoid
+// blocking all activity on the current task runner, such as network or IPC
+// traffic, run NSS SSL functions on a dedicated thread.
+#if defined(OS_CHROMEOS) || defined(OS_LINUX)
+bool g_use_dedicated_nss_thread = true;
+#else
+bool g_use_dedicated_nss_thread = false;
+#endif
+
+class DefaultClientSocketFactory : public ClientSocketFactory,
+ public CertDatabase::Observer {
+ public:
+ DefaultClientSocketFactory() {
+ if (g_use_dedicated_nss_thread) {
+ // Use a single thread for the worker pool.
+ worker_pool_ = new base::SequencedWorkerPool(1, "NSS SSL Thread");
+ nss_thread_task_runner_ =
+ worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
+ worker_pool_->GetSequenceToken(),
+ base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+ }
+
+ CertDatabase::GetInstance()->AddObserver(this);
+ }
+
+ virtual ~DefaultClientSocketFactory() {
+ // Note: This code never runs, as the factory is defined as a Leaky
+ // singleton.
+ CertDatabase::GetInstance()->RemoveObserver(this);
+ }
+
+ virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE {
+ ClearSSLSessionCache();
+ }
+
+ virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE {
+ // Per wtc, we actually only need to flush when trust is reduced.
+ // Always flush now because OnCertTrustChanged does not tell us this.
+ // See comments in ClientSocketPoolManager::OnCertTrustChanged.
+ ClearSSLSessionCache();
+ }
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE {
+ return scoped_ptr<DatagramClientSocket>(
+ new UDPClientSocket(bind_type, rand_int_cb, net_log, source));
+ }
+
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE {
+ return scoped_ptr<StreamSocket>(
+ new TCPClientSocket(addresses, net_log, source));
+ }
+
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE {
+ // nss_thread_task_runner_ may be NULL if g_use_dedicated_nss_thread is
+ // false or if the dedicated NSS thread failed to start. If so, cause NSS
+ // functions to execute on the current task runner.
+ //
+ // Note: The current task runner is obtained on each call due to unit
+ // tests, which may create and tear down the current thread's TaskRunner
+ // between each test. Because the DefaultClientSocketFactory is leaky, it
+ // may span multiple tests, and thus the current task runner may change
+ // from call to call.
+ scoped_refptr<base::SequencedTaskRunner> nss_task_runner(
+ nss_thread_task_runner_);
+ if (!nss_task_runner.get())
+ nss_task_runner = base::ThreadTaskRunnerHandle::Get();
+
+#if defined(USE_OPENSSL)
+ return scoped_ptr<SSLClientSocket>(
+ new SSLClientSocketOpenSSL(transport_socket.Pass(), host_and_port,
+ ssl_config, context));
+#elif defined(USE_NSS) || defined(OS_MACOSX) || defined(OS_WIN)
+ return scoped_ptr<SSLClientSocket>(
+ new SSLClientSocketNSS(nss_task_runner.get(),
+ transport_socket.Pass(),
+ host_and_port,
+ ssl_config,
+ context));
+#else
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLClientSocket>();
+#endif
+ }
+
+ virtual void ClearSSLSessionCache() OVERRIDE {
+ SSLClientSocket::ClearSessionCache();
+ }
+
+ private:
+ scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+ scoped_refptr<base::SequencedTaskRunner> nss_thread_task_runner_;
+};
+
+static base::LazyInstance<DefaultClientSocketFactory>::Leaky
+ g_default_client_socket_factory = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+ClientSocketFactory* ClientSocketFactory::GetDefaultFactory() {
+ return g_default_client_socket_factory.Pointer();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_factory.h b/chromium/net/socket/client_socket_factory.h
new file mode 100644
index 00000000000..6cb5949f0b3
--- /dev/null
+++ b/chromium/net/socket/client_socket_factory.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2011 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_SOCKET_CLIENT_SOCKET_FACTORY_H_
+#define NET_SOCKET_CLIENT_SOCKET_FACTORY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/rand_callback.h"
+#include "net/udp/datagram_socket.h"
+
+namespace net {
+
+class AddressList;
+class ClientSocketHandle;
+class DatagramClientSocket;
+class HostPortPair;
+class SSLClientSocket;
+struct SSLClientSocketContext;
+struct SSLConfig;
+class StreamSocket;
+
+// An interface used to instantiate StreamSocket objects. Used to facilitate
+// testing code with mock socket implementations.
+class NET_EXPORT ClientSocketFactory {
+ public:
+ virtual ~ClientSocketFactory() {}
+
+ // |source| is the NetLog::Source for the entity trying to create the socket,
+ // if it has one.
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) = 0;
+
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* net_log,
+ const NetLog::Source& source) = 0;
+
+ // It is allowed to pass in a |transport_socket| that is not obtained from a
+ // socket pool. The caller could create a ClientSocketHandle directly and call
+ // set_socket() on it to set a valid StreamSocket instance.
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) = 0;
+
+ // Clears cache used for SSL session resumption.
+ virtual void ClearSSLSessionCache() = 0;
+
+ // Returns the default ClientSocketFactory.
+ static ClientSocketFactory* GetDefaultFactory();
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_FACTORY_H_
diff --git a/chromium/net/socket/client_socket_handle.cc b/chromium/net/socket/client_socket_handle.cc
new file mode 100644
index 00000000000..acb896b36f5
--- /dev/null
+++ b/chromium/net/socket/client_socket_handle.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_handle.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram.h"
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_histograms.h"
+
+namespace net {
+
+ClientSocketHandle::ClientSocketHandle()
+ : is_initialized_(false),
+ pool_(NULL),
+ layered_pool_(NULL),
+ is_reused_(false),
+ callback_(base::Bind(&ClientSocketHandle::OnIOComplete,
+ base::Unretained(this))),
+ is_ssl_error_(false) {}
+
+ClientSocketHandle::~ClientSocketHandle() {
+ Reset();
+}
+
+void ClientSocketHandle::Reset() {
+ ResetInternal(true);
+ ResetErrorState();
+}
+
+void ClientSocketHandle::ResetInternal(bool cancel) {
+ if (group_name_.empty()) // Was Init called?
+ return;
+ if (is_initialized()) {
+ // Because of http://crbug.com/37810 we may not have a pool, but have
+ // just a raw socket.
+ socket_->NetLog().EndEvent(NetLog::TYPE_SOCKET_IN_USE);
+ if (pool_)
+ // If we've still got a socket, release it back to the ClientSocketPool so
+ // it can be deleted or reused.
+ pool_->ReleaseSocket(group_name_, PassSocket(), pool_id_);
+ } else if (cancel) {
+ // If we did not get initialized yet, we've got a socket request pending.
+ // Cancel it.
+ pool_->CancelRequest(group_name_, this);
+ }
+ is_initialized_ = false;
+ group_name_.clear();
+ is_reused_ = false;
+ user_callback_.Reset();
+ if (layered_pool_) {
+ pool_->RemoveLayeredPool(layered_pool_);
+ layered_pool_ = NULL;
+ }
+ pool_ = NULL;
+ idle_time_ = base::TimeDelta();
+ init_time_ = base::TimeTicks();
+ setup_time_ = base::TimeDelta();
+ connect_timing_ = LoadTimingInfo::ConnectTiming();
+ pool_id_ = -1;
+}
+
+void ClientSocketHandle::ResetErrorState() {
+ is_ssl_error_ = false;
+ ssl_error_response_info_ = HttpResponseInfo();
+ pending_http_proxy_connection_.reset();
+}
+
+LoadState ClientSocketHandle::GetLoadState() const {
+ CHECK(!is_initialized());
+ CHECK(!group_name_.empty());
+ // Because of http://crbug.com/37810 we may not have a pool, but have
+ // just a raw socket.
+ if (!pool_)
+ return LOAD_STATE_IDLE;
+ return pool_->GetLoadState(group_name_, this);
+}
+
+bool ClientSocketHandle::IsPoolStalled() const {
+ return pool_->IsStalled();
+}
+
+void ClientSocketHandle::AddLayeredPool(LayeredPool* layered_pool) {
+ CHECK(layered_pool);
+ CHECK(!layered_pool_);
+ if (pool_) {
+ pool_->AddLayeredPool(layered_pool);
+ layered_pool_ = layered_pool;
+ }
+}
+
+void ClientSocketHandle::RemoveLayeredPool(LayeredPool* layered_pool) {
+ CHECK(layered_pool);
+ CHECK(layered_pool_);
+ if (pool_) {
+ pool_->RemoveLayeredPool(layered_pool);
+ layered_pool_ = NULL;
+ }
+}
+
+bool ClientSocketHandle::GetLoadTimingInfo(
+ bool is_reused,
+ LoadTimingInfo* load_timing_info) const {
+ // Only return load timing information when there's a socket.
+ if (!socket_)
+ return false;
+
+ load_timing_info->socket_log_id = socket_->NetLog().source().id;
+ load_timing_info->socket_reused = is_reused;
+
+ // No times if the socket is reused.
+ if (is_reused)
+ return true;
+
+ load_timing_info->connect_timing = connect_timing_;
+ return true;
+}
+
+void ClientSocketHandle::SetSocket(scoped_ptr<StreamSocket> s) {
+ socket_ = s.Pass();
+}
+
+void ClientSocketHandle::OnIOComplete(int result) {
+ CompletionCallback callback = user_callback_;
+ user_callback_.Reset();
+ HandleInitCompletion(result);
+ callback.Run(result);
+}
+
+scoped_ptr<StreamSocket> ClientSocketHandle::PassSocket() {
+ return socket_.Pass();
+}
+
+void ClientSocketHandle::HandleInitCompletion(int result) {
+ CHECK_NE(ERR_IO_PENDING, result);
+ ClientSocketPoolHistograms* histograms = pool_->histograms();
+ histograms->AddErrorCode(result);
+ if (result != OK) {
+ if (!socket_.get())
+ ResetInternal(false); // Nothing to cancel since the request failed.
+ else
+ is_initialized_ = true;
+ return;
+ }
+ is_initialized_ = true;
+ CHECK_NE(-1, pool_id_) << "Pool should have set |pool_id_| to a valid value.";
+ setup_time_ = base::TimeTicks::Now() - init_time_;
+
+ histograms->AddSocketType(reuse_type());
+ switch (reuse_type()) {
+ case ClientSocketHandle::UNUSED:
+ histograms->AddRequestTime(setup_time());
+ break;
+ case ClientSocketHandle::UNUSED_IDLE:
+ histograms->AddUnusedIdleTime(idle_time());
+ break;
+ case ClientSocketHandle::REUSED_IDLE:
+ histograms->AddReusedIdleTime(idle_time());
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // Broadcast that the socket has been acquired.
+ // TODO(eroman): This logging is not complete, in particular set_socket() and
+ // release() socket. It ends up working though, since those methods are being
+ // used to layer sockets (and the destination sources are the same).
+ DCHECK(socket_.get());
+ socket_->NetLog().BeginEvent(
+ NetLog::TYPE_SOCKET_IN_USE,
+ requesting_source_.ToEventParametersCallback());
+}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_handle.h b/chromium/net/socket/client_socket_handle.h
new file mode 100644
index 00000000000..9651f089a86
--- /dev/null
+++ b/chromium/net/socket/client_socket_handle.h
@@ -0,0 +1,241 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_CLIENT_SOCKET_HANDLE_H_
+#define NET_SOCKET_CLIENT_SOCKET_HANDLE_H_
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+// A container for a StreamSocket.
+//
+// The handle's |group_name| uniquely identifies the origin and type of the
+// connection. It is used by the ClientSocketPool to group similar connected
+// client socket objects.
+//
+class NET_EXPORT ClientSocketHandle {
+ public:
+ enum SocketReuseType {
+ UNUSED = 0, // unused socket that just finished connecting
+ UNUSED_IDLE, // unused socket that has been idle for awhile
+ REUSED_IDLE, // previously used socket
+ NUM_TYPES,
+ };
+
+ ClientSocketHandle();
+ ~ClientSocketHandle();
+
+ // Initializes a ClientSocketHandle object, which involves talking to the
+ // ClientSocketPool to obtain a connected socket, possibly reusing one. This
+ // method returns either OK or ERR_IO_PENDING. On ERR_IO_PENDING, |priority|
+ // is used to determine the placement in ClientSocketPool's wait list.
+ //
+ // If this method succeeds, then the socket member will be set to an existing
+ // connected socket if an existing connected socket was available to reuse,
+ // otherwise it will be set to a new connected socket. Consumers can then
+ // call is_reused() to see if the socket was reused. If not reusing an
+ // existing socket, ClientSocketPool may need to establish a new
+ // connection using |socket_params|.
+ //
+ // This method returns ERR_IO_PENDING if it cannot complete synchronously, in
+ // which case the consumer will be notified of completion via |callback|.
+ //
+ // If the pool was not able to reuse an existing socket, the new socket
+ // may report a recoverable error. In this case, the return value will
+ // indicate an error and the socket member will be set. If it is determined
+ // that the error is not recoverable, the Disconnect method should be used
+ // on the socket, so that it does not get reused.
+ //
+ // A non-recoverable error may set additional state in the ClientSocketHandle
+ // to allow the caller to determine what went wrong.
+ //
+ // Init may be called multiple times.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ //
+ template <typename SocketParams, typename PoolType>
+ int Init(const std::string& group_name,
+ const scoped_refptr<SocketParams>& socket_params,
+ RequestPriority priority,
+ const CompletionCallback& callback,
+ PoolType* pool,
+ const BoundNetLog& net_log);
+
+ // An initialized handle can be reset, which causes it to return to the
+ // un-initialized state. This releases the underlying socket, which in the
+ // case of a socket that still has an established connection, indicates that
+ // the socket may be kept alive for use by a subsequent ClientSocketHandle.
+ //
+ // NOTE: To prevent the socket from being kept alive, be sure to call its
+ // Disconnect method. This will result in the ClientSocketPool deleting the
+ // StreamSocket.
+ void Reset();
+
+ // Used after Init() is called, but before the ClientSocketPool has
+ // initialized the ClientSocketHandle.
+ LoadState GetLoadState() const;
+
+ bool IsPoolStalled() const;
+
+ void AddLayeredPool(LayeredPool* layered_pool);
+
+ void RemoveLayeredPool(LayeredPool* layered_pool);
+
+ // Returns true when Init() has completed successfully.
+ bool is_initialized() const { return is_initialized_; }
+
+ // Returns the time tick when Init() was called.
+ base::TimeTicks init_time() const { return init_time_; }
+
+ // Returns the time between Init() and when is_initialized() becomes true.
+ base::TimeDelta setup_time() const { return setup_time_; }
+
+ // Sets the portion of LoadTimingInfo related to connection establishment, and
+ // the socket id. |is_reused| is needed because the handle may not have full
+ // reuse information. |load_timing_info| must have all default values when
+ // called. Returns false and makes no changes to |load_timing_info| when
+ // |socket_| is NULL.
+ bool GetLoadTimingInfo(bool is_reused,
+ LoadTimingInfo* load_timing_info) const;
+
+ // Used by ClientSocketPool to initialize the ClientSocketHandle.
+ void SetSocket(scoped_ptr<StreamSocket> s);
+ void set_is_reused(bool is_reused) { is_reused_ = is_reused; }
+ void set_idle_time(base::TimeDelta idle_time) { idle_time_ = idle_time; }
+ void set_pool_id(int id) { pool_id_ = id; }
+ void set_is_ssl_error(bool is_ssl_error) { is_ssl_error_ = is_ssl_error; }
+ void set_ssl_error_response_info(const HttpResponseInfo& ssl_error_state) {
+ ssl_error_response_info_ = ssl_error_state;
+ }
+ void set_pending_http_proxy_connection(ClientSocketHandle* connection) {
+ pending_http_proxy_connection_.reset(connection);
+ }
+
+ // Only valid if there is no |socket_|.
+ bool is_ssl_error() const {
+ DCHECK(socket_.get() == NULL);
+ return is_ssl_error_;
+ }
+ // On an ERR_PROXY_AUTH_REQUESTED error, the |headers| and |auth_challenge|
+ // fields are filled in. On an ERR_SSL_CLIENT_AUTH_CERT_NEEDED error,
+ // the |cert_request_info| field is set.
+ const HttpResponseInfo& ssl_error_response_info() const {
+ return ssl_error_response_info_;
+ }
+ ClientSocketHandle* release_pending_http_proxy_connection() {
+ return pending_http_proxy_connection_.release();
+ }
+
+ // These may only be used if is_initialized() is true.
+ scoped_ptr<StreamSocket> PassSocket();
+ StreamSocket* socket() { return socket_.get(); }
+ const std::string& group_name() const { return group_name_; }
+ int id() const { return pool_id_; }
+ bool is_reused() const { return is_reused_; }
+ base::TimeDelta idle_time() const { return idle_time_; }
+ SocketReuseType reuse_type() const {
+ if (is_reused()) {
+ return REUSED_IDLE;
+ } else if (idle_time() == base::TimeDelta()) {
+ return UNUSED;
+ } else {
+ return UNUSED_IDLE;
+ }
+ }
+ const LoadTimingInfo::ConnectTiming& connect_timing() const {
+ return connect_timing_;
+ }
+ void set_connect_timing(const LoadTimingInfo::ConnectTiming& connect_timing) {
+ connect_timing_ = connect_timing;
+ }
+
+ private:
+ // Called on asynchronous completion of an Init() request.
+ void OnIOComplete(int result);
+
+ // Called on completion (both asynchronous & synchronous) of an Init()
+ // request.
+ void HandleInitCompletion(int result);
+
+ // Resets the state of the ClientSocketHandle. |cancel| indicates whether or
+ // not to try to cancel the request with the ClientSocketPool. Does not
+ // reset the supplemental error state.
+ void ResetInternal(bool cancel);
+
+ // Resets the supplemental error state.
+ void ResetErrorState();
+
+ bool is_initialized_;
+ ClientSocketPool* pool_;
+ LayeredPool* layered_pool_;
+ scoped_ptr<StreamSocket> socket_;
+ std::string group_name_;
+ bool is_reused_;
+ CompletionCallback callback_;
+ CompletionCallback user_callback_;
+ base::TimeDelta idle_time_;
+ int pool_id_; // See ClientSocketPool::ReleaseSocket() for an explanation.
+ bool is_ssl_error_;
+ HttpResponseInfo ssl_error_response_info_;
+ scoped_ptr<ClientSocketHandle> pending_http_proxy_connection_;
+ base::TimeTicks init_time_;
+ base::TimeDelta setup_time_;
+
+ NetLog::Source requesting_source_;
+
+ // Timing information is set when a connection is successfully established.
+ LoadTimingInfo::ConnectTiming connect_timing_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketHandle);
+};
+
+// Template function implementation:
+template <typename SocketParams, typename PoolType>
+int ClientSocketHandle::Init(const std::string& group_name,
+ const scoped_refptr<SocketParams>& socket_params,
+ RequestPriority priority,
+ const CompletionCallback& callback,
+ PoolType* pool,
+ const BoundNetLog& net_log) {
+ requesting_source_ = net_log.source();
+
+ CHECK(!group_name.empty());
+ // Note that this will result in a compile error if the SocketParams has not
+ // been registered for the PoolType via REGISTER_SOCKET_PARAMS_FOR_POOL
+ // (defined in client_socket_pool.h).
+ CheckIsValidSocketParamsForPool<PoolType, SocketParams>();
+ ResetInternal(true);
+ ResetErrorState();
+ pool_ = pool;
+ group_name_ = group_name;
+ init_time_ = base::TimeTicks::Now();
+ int rv = pool_->RequestSocket(
+ group_name, &socket_params, priority, this, callback_, net_log);
+ if (rv == ERR_IO_PENDING) {
+ user_callback_ = callback;
+ } else {
+ HandleInitCompletion(rv);
+ }
+ return rv;
+}
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_HANDLE_H_
diff --git a/chromium/net/socket/client_socket_pool.cc b/chromium/net/socket/client_socket_pool.cc
new file mode 100644
index 00000000000..0eebd11b9fb
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_pool.h"
+
+#include "base/logging.h"
+
+namespace {
+
+// The maximum duration, in seconds, to keep unused idle persistent sockets
+// alive.
+// TODO(ziadh): Change this timeout after getting histogram data on how long it
+// should be.
+int g_unused_idle_socket_timeout_s = 10;
+
+// The maximum duration, in seconds, to keep used idle persistent sockets alive.
+int g_used_idle_socket_timeout_s = 300; // 5 minutes
+
+} // namespace
+
+namespace net {
+
+// static
+base::TimeDelta ClientSocketPool::unused_idle_socket_timeout() {
+ return base::TimeDelta::FromSeconds(g_unused_idle_socket_timeout_s);
+}
+
+// static
+void ClientSocketPool::set_unused_idle_socket_timeout(base::TimeDelta timeout) {
+ DCHECK_GT(timeout.InSeconds(), 0);
+ g_unused_idle_socket_timeout_s = timeout.InSeconds();
+}
+
+// static
+base::TimeDelta ClientSocketPool::used_idle_socket_timeout() {
+ return base::TimeDelta::FromSeconds(g_used_idle_socket_timeout_s);
+}
+
+// static
+void ClientSocketPool::set_used_idle_socket_timeout(base::TimeDelta timeout) {
+ DCHECK_GT(timeout.InSeconds(), 0);
+ g_used_idle_socket_timeout_s = timeout.InSeconds();
+}
+
+ClientSocketPool::ClientSocketPool() {}
+
+ClientSocketPool::~ClientSocketPool() {}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool.h b/chromium/net/socket/client_socket_pool.h
new file mode 100644
index 00000000000..af184547d6e
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool.h
@@ -0,0 +1,221 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_H_
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/template_util.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/dns/host_resolver.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace net {
+
+class ClientSocketHandle;
+class ClientSocketPoolHistograms;
+class StreamSocket;
+
+// ClientSocketPools are layered. This defines an interface for lower level
+// socket pools to communicate with higher layer pools.
+class NET_EXPORT LayeredPool {
+ public:
+ virtual ~LayeredPool() {};
+
+ // Instructs the LayeredPool to close an idle connection. Return true if one
+ // was closed.
+ virtual bool CloseOneIdleConnection() = 0;
+};
+
+// A ClientSocketPool is used to restrict the number of sockets open at a time.
+// It also maintains a list of idle persistent sockets.
+//
+class NET_EXPORT ClientSocketPool {
+ public:
+ // Requests a connected socket for a group_name.
+ //
+ // There are five possible results from calling this function:
+ // 1) RequestSocket returns OK and initializes |handle| with a reused socket.
+ // 2) RequestSocket returns OK with a newly connected socket.
+ // 3) RequestSocket returns ERR_IO_PENDING. The handle will be added to a
+ // wait list until a socket is available to reuse or a new socket finishes
+ // connecting. |priority| will determine the placement into the wait list.
+ // 4) An error occurred early on, so RequestSocket returns an error code.
+ // 5) A recoverable error occurred while setting up the socket. An error
+ // code is returned, but the |handle| is initialized with the new socket.
+ // The caller must recover from the error before using the connection, or
+ // Disconnect the socket before releasing or resetting the |handle|.
+ // The current recoverable errors are: the errors accepted by
+ // IsCertificateError(err) and PROXY_AUTH_REQUESTED, or
+ // HTTPS_PROXY_TUNNEL_RESPONSE when reported by HttpProxyClientSocketPool.
+ //
+ // If this function returns OK, then |handle| is initialized upon return.
+ // The |handle|'s is_initialized method will return true in this case. If a
+ // StreamSocket was reused, then ClientSocketPool will call
+ // |handle|->set_reused(true). In either case, the socket will have been
+ // allocated and will be connected. A client might want to know whether or
+ // not the socket is reused in order to request a new socket if he encounters
+ // an error with the reused socket.
+ //
+ // If ERR_IO_PENDING is returned, then the callback will be used to notify the
+ // client of completion.
+ //
+ // Profiling information for the request is saved to |net_log| if non-NULL.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) = 0;
+
+ // RequestSockets is used to request that |num_sockets| be connected in the
+ // connection group for |group_name|. If the connection group already has
+ // |num_sockets| idle sockets / active sockets / currently connecting sockets,
+ // then this function doesn't do anything. Otherwise, it will start up as
+ // many connections as necessary to reach |num_sockets| total sockets for the
+ // group. It uses |params| to control how to connect the sockets. The
+ // ClientSocketPool will assign a priority to the new connections, if any.
+ // This priority will probably be lower than all others, since this method
+ // is intended to make sure ahead of time that |num_sockets| sockets are
+ // available to talk to a host.
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) = 0;
+
+ // Called to cancel a RequestSocket call that returned ERR_IO_PENDING. The
+ // same handle parameter must be passed to this method as was passed to the
+ // RequestSocket call being cancelled. The associated CompletionCallback is
+ // not run. However, for performance, we will let one ConnectJob complete
+ // and go idle.
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) = 0;
+
+ // Called to release a socket once the socket is no longer needed. If the
+ // socket still has an established connection, then it will be added to the
+ // set of idle sockets to be used to satisfy future RequestSocket calls.
+ // Otherwise, the StreamSocket is destroyed. |id| is used to differentiate
+ // between updated versions of the same pool instance. The pool's id will
+ // change when it flushes, so it can use this |id| to discard sockets with
+ // mismatched ids.
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) = 0;
+
+ // This flushes all state from the ClientSocketPool. This means that all
+ // idle and connecting sockets are discarded with the given |error|.
+ // Active sockets being held by ClientSocketPool clients will be discarded
+ // when released back to the pool.
+ // Does not flush any pools wrapped by |this|.
+ virtual void FlushWithError(int error) = 0;
+
+ // Returns true if a there is currently a request blocked on the
+ // per-pool (not per-host) max socket limit.
+ virtual bool IsStalled() const = 0;
+
+ // Called to close any idle connections held by the connection manager.
+ virtual void CloseIdleSockets() = 0;
+
+ // The total number of idle sockets in the pool.
+ virtual int IdleSocketCount() const = 0;
+
+ // The total number of idle sockets in a connection group.
+ virtual int IdleSocketCountInGroup(const std::string& group_name) const = 0;
+
+ // Determine the LoadState of a connecting ClientSocketHandle.
+ virtual LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const = 0;
+
+ // Adds a LayeredPool on top of |this|.
+ virtual void AddLayeredPool(LayeredPool* layered_pool) = 0;
+
+ // Removes a LayeredPool from |this|.
+ virtual void RemoveLayeredPool(LayeredPool* layered_pool) = 0;
+
+ // Retrieves information on the current state of the pool as a
+ // DictionaryValue. Caller takes possession of the returned value.
+ // If |include_nested_pools| is true, the states of any nested
+ // ClientSocketPools will be included.
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const = 0;
+
+ // Returns the maximum amount of time to wait before retrying a connect.
+ static const int kMaxConnectRetryIntervalMs = 250;
+
+ // The set of histograms specific to this pool. We can't use the standard
+ // UMA_HISTOGRAM_* macros because they are callsite static.
+ virtual ClientSocketPoolHistograms* histograms() const = 0;
+
+ static base::TimeDelta unused_idle_socket_timeout();
+ static void set_unused_idle_socket_timeout(base::TimeDelta timeout);
+
+ static base::TimeDelta used_idle_socket_timeout();
+ static void set_used_idle_socket_timeout(base::TimeDelta timeout);
+
+ protected:
+ ClientSocketPool();
+ virtual ~ClientSocketPool();
+
+ // Return the connection timeout for this pool.
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketPool);
+};
+
+// ClientSocketPool subclasses should indicate valid SocketParams via the
+// REGISTER_SOCKET_PARAMS_FOR_POOL macro below. By default, any given
+// <PoolType,SocketParams> pair will have its SocketParamsTrait inherit from
+// base::false_type, but REGISTER_SOCKET_PARAMS_FOR_POOL will specialize that
+// pairing to inherit from base::true_type. This provides compile time
+// verification that the correct SocketParams type is used with the appropriate
+// PoolType.
+template <typename PoolType, typename SocketParams>
+struct SocketParamTraits : public base::false_type {
+};
+
+template <typename PoolType, typename SocketParams>
+void CheckIsValidSocketParamsForPool() {
+ COMPILE_ASSERT(!base::is_pointer<scoped_refptr<SocketParams> >::value,
+ socket_params_cannot_be_pointer);
+ COMPILE_ASSERT((SocketParamTraits<PoolType,
+ scoped_refptr<SocketParams> >::value),
+ invalid_socket_params_for_pool);
+}
+
+// Provides an empty definition for CheckIsValidSocketParamsForPool() which
+// should be optimized out by the compiler.
+#define REGISTER_SOCKET_PARAMS_FOR_POOL(pool_type, socket_params) \
+template<> \
+struct SocketParamTraits<pool_type, scoped_refptr<socket_params> > \
+ : public base::true_type { \
+}
+
+template <typename PoolType, typename SocketParams>
+void RequestSocketsForPool(PoolType* pool,
+ const std::string& group_name,
+ const scoped_refptr<SocketParams>& params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ CheckIsValidSocketParamsForPool<PoolType, SocketParams>();
+ pool->RequestSockets(group_name, &params, num_sockets, net_log);
+}
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_H_
diff --git a/chromium/net/socket/client_socket_pool_base.cc b/chromium/net/socket/client_socket_pool_base.cc
new file mode 100644
index 00000000000..b1ddd40881c
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_base.cc
@@ -0,0 +1,1266 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_pool_base.h"
+
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/client_socket_handle.h"
+
+using base::TimeDelta;
+
+namespace net {
+
+namespace {
+
+// Indicate whether we should enable idle socket cleanup timer. When timer is
+// disabled, sockets are closed next time a socket request is made.
+bool g_cleanup_timer_enabled = true;
+
+// The timeout value, in seconds, used to clean up idle sockets that can't be
+// reused.
+//
+// Note: It's important to close idle sockets that have received data as soon
+// as possible because the received data may cause BSOD on Windows XP under
+// some conditions. See http://crbug.com/4606.
+const int kCleanupInterval = 10; // DO NOT INCREASE THIS TIMEOUT.
+
+// Indicate whether or not we should establish a new transport layer connection
+// after a certain timeout has passed without receiving an ACK.
+bool g_connect_backup_jobs_enabled = true;
+
+// Compares the effective priority of two results, and returns 1 if |request1|
+// has greater effective priority than |request2|, 0 if they have the same
+// effective priority, and -1 if |request2| has the greater effective priority.
+// Requests with |ignore_limits| set have higher effective priority than those
+// without. If both requests have |ignore_limits| set/unset, then the request
+// with the highest Pririoty has the highest effective priority. Does not take
+// into account the fact that Requests are serviced in FIFO order if they would
+// otherwise have the same priority.
+int CompareEffectiveRequestPriority(
+ const internal::ClientSocketPoolBaseHelper::Request& request1,
+ const internal::ClientSocketPoolBaseHelper::Request& request2) {
+ if (request1.ignore_limits() && !request2.ignore_limits())
+ return 1;
+ if (!request1.ignore_limits() && request2.ignore_limits())
+ return -1;
+ if (request1.priority() > request2.priority())
+ return 1;
+ if (request1.priority() < request2.priority())
+ return -1;
+ return 0;
+}
+
+} // namespace
+
+ConnectJob::ConnectJob(const std::string& group_name,
+ base::TimeDelta timeout_duration,
+ Delegate* delegate,
+ const BoundNetLog& net_log)
+ : group_name_(group_name),
+ timeout_duration_(timeout_duration),
+ delegate_(delegate),
+ net_log_(net_log),
+ idle_(true) {
+ DCHECK(!group_name.empty());
+ DCHECK(delegate);
+ net_log.BeginEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB,
+ NetLog::StringCallback("group_name", &group_name_));
+}
+
+ConnectJob::~ConnectJob() {
+ net_log().EndEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB);
+}
+
+scoped_ptr<StreamSocket> ConnectJob::PassSocket() {
+ return socket_.Pass();
+}
+
+int ConnectJob::Connect() {
+ if (timeout_duration_ != base::TimeDelta())
+ timer_.Start(FROM_HERE, timeout_duration_, this, &ConnectJob::OnTimeout);
+
+ idle_ = false;
+
+ LogConnectStart();
+
+ int rv = ConnectInternal();
+
+ if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
+ delegate_ = NULL;
+ }
+
+ return rv;
+}
+
+void ConnectJob::SetSocket(scoped_ptr<StreamSocket> socket) {
+ if (socket) {
+ net_log().AddEvent(NetLog::TYPE_CONNECT_JOB_SET_SOCKET,
+ socket->NetLog().source().ToEventParametersCallback());
+ }
+ socket_ = socket.Pass();
+}
+
+void ConnectJob::NotifyDelegateOfCompletion(int rv) {
+ // The delegate will own |this|.
+ Delegate* delegate = delegate_;
+ delegate_ = NULL;
+
+ LogConnectCompletion(rv);
+ delegate->OnConnectJobComplete(rv, this);
+}
+
+void ConnectJob::ResetTimer(base::TimeDelta remaining_time) {
+ timer_.Stop();
+ timer_.Start(FROM_HERE, remaining_time, this, &ConnectJob::OnTimeout);
+}
+
+void ConnectJob::LogConnectStart() {
+ connect_timing_.connect_start = base::TimeTicks::Now();
+ net_log().BeginEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT);
+}
+
+void ConnectJob::LogConnectCompletion(int net_error) {
+ connect_timing_.connect_end = base::TimeTicks::Now();
+ net_log().EndEventWithNetErrorCode(
+ NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT, net_error);
+}
+
+void ConnectJob::OnTimeout() {
+ // Make sure the socket is NULL before calling into |delegate|.
+ SetSocket(scoped_ptr<StreamSocket>());
+
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT);
+
+ NotifyDelegateOfCompletion(ERR_TIMED_OUT);
+}
+
+namespace internal {
+
+ClientSocketPoolBaseHelper::Request::Request(
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ RequestPriority priority,
+ bool ignore_limits,
+ Flags flags,
+ const BoundNetLog& net_log)
+ : handle_(handle),
+ callback_(callback),
+ priority_(priority),
+ ignore_limits_(ignore_limits),
+ flags_(flags),
+ net_log_(net_log) {}
+
+ClientSocketPoolBaseHelper::Request::~Request() {}
+
+ClientSocketPoolBaseHelper::ClientSocketPoolBaseHelper(
+ int max_sockets,
+ int max_sockets_per_group,
+ base::TimeDelta unused_idle_socket_timeout,
+ base::TimeDelta used_idle_socket_timeout,
+ ConnectJobFactory* connect_job_factory)
+ : idle_socket_count_(0),
+ connecting_socket_count_(0),
+ handed_out_socket_count_(0),
+ max_sockets_(max_sockets),
+ max_sockets_per_group_(max_sockets_per_group),
+ use_cleanup_timer_(g_cleanup_timer_enabled),
+ unused_idle_socket_timeout_(unused_idle_socket_timeout),
+ used_idle_socket_timeout_(used_idle_socket_timeout),
+ connect_job_factory_(connect_job_factory),
+ connect_backup_jobs_enabled_(false),
+ pool_generation_number_(0),
+ weak_factory_(this) {
+ DCHECK_LE(0, max_sockets_per_group);
+ DCHECK_LE(max_sockets_per_group, max_sockets);
+
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+}
+
+ClientSocketPoolBaseHelper::~ClientSocketPoolBaseHelper() {
+ // Clean up any idle sockets and pending connect jobs. Assert that we have no
+ // remaining active sockets or pending requests. They should have all been
+ // cleaned up prior to |this| being destroyed.
+ FlushWithError(ERR_ABORTED);
+ DCHECK(group_map_.empty());
+ DCHECK(pending_callback_map_.empty());
+ DCHECK_EQ(0, connecting_socket_count_);
+ CHECK(higher_layer_pools_.empty());
+
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+}
+
+ClientSocketPoolBaseHelper::CallbackResultPair::CallbackResultPair()
+ : result(OK) {
+}
+
+ClientSocketPoolBaseHelper::CallbackResultPair::CallbackResultPair(
+ const CompletionCallback& callback_in, int result_in)
+ : callback(callback_in),
+ result(result_in) {
+}
+
+ClientSocketPoolBaseHelper::CallbackResultPair::~CallbackResultPair() {}
+
+// static
+void ClientSocketPoolBaseHelper::InsertRequestIntoQueue(
+ const Request* r, RequestQueue* pending_requests) {
+ RequestQueue::iterator it = pending_requests->begin();
+ // TODO(mmenke): Should the network stack require requests with
+ // |ignore_limits| have the highest priority?
+ while (it != pending_requests->end() &&
+ CompareEffectiveRequestPriority(*r, *(*it)) <= 0) {
+ ++it;
+ }
+ pending_requests->insert(it, r);
+}
+
+// static
+const ClientSocketPoolBaseHelper::Request*
+ClientSocketPoolBaseHelper::RemoveRequestFromQueue(
+ const RequestQueue::iterator& it, Group* group) {
+ const Request* req = *it;
+ group->mutable_pending_requests()->erase(it);
+ // If there are no more requests, we kill the backup timer.
+ if (group->pending_requests().empty())
+ group->CleanupBackupJob();
+ return req;
+}
+
+void ClientSocketPoolBaseHelper::AddLayeredPool(LayeredPool* pool) {
+ CHECK(pool);
+ CHECK(!ContainsKey(higher_layer_pools_, pool));
+ higher_layer_pools_.insert(pool);
+}
+
+void ClientSocketPoolBaseHelper::RemoveLayeredPool(LayeredPool* pool) {
+ CHECK(pool);
+ CHECK(ContainsKey(higher_layer_pools_, pool));
+ higher_layer_pools_.erase(pool);
+}
+
+int ClientSocketPoolBaseHelper::RequestSocket(
+ const std::string& group_name,
+ const Request* request) {
+ CHECK(!request->callback().is_null());
+ CHECK(request->handle());
+
+ // Cleanup any timed-out idle sockets if no timer is used.
+ if (!use_cleanup_timer_)
+ CleanupIdleSockets(false);
+
+ request->net_log().BeginEvent(NetLog::TYPE_SOCKET_POOL);
+ Group* group = GetOrCreateGroup(group_name);
+
+ int rv = RequestSocketInternal(group_name, request);
+ if (rv != ERR_IO_PENDING) {
+ request->net_log().EndEventWithNetErrorCode(NetLog::TYPE_SOCKET_POOL, rv);
+ CHECK(!request->handle()->is_initialized());
+ delete request;
+ } else {
+ InsertRequestIntoQueue(request, group->mutable_pending_requests());
+ // Have to do this asynchronously, as closing sockets in higher level pools
+ // call back in to |this|, which will cause all sorts of fun and exciting
+ // re-entrancy issues if the socket pool is doing something else at the
+ // time.
+ if (group->IsStalledOnPoolMaxSockets(max_sockets_per_group_)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ClientSocketPoolBaseHelper::TryToCloseSocketsInLayeredPools,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+ return rv;
+}
+
+void ClientSocketPoolBaseHelper::RequestSockets(
+ const std::string& group_name,
+ const Request& request,
+ int num_sockets) {
+ DCHECK(request.callback().is_null());
+ DCHECK(!request.handle());
+
+ // Cleanup any timed out idle sockets if no timer is used.
+ if (!use_cleanup_timer_)
+ CleanupIdleSockets(false);
+
+ if (num_sockets > max_sockets_per_group_) {
+ num_sockets = max_sockets_per_group_;
+ }
+
+ request.net_log().BeginEvent(
+ NetLog::TYPE_SOCKET_POOL_CONNECTING_N_SOCKETS,
+ NetLog::IntegerCallback("num_sockets", num_sockets));
+
+ Group* group = GetOrCreateGroup(group_name);
+
+ // RequestSocketsInternal() may delete the group.
+ bool deleted_group = false;
+
+ int rv = OK;
+ for (int num_iterations_left = num_sockets;
+ group->NumActiveSocketSlots() < num_sockets &&
+ num_iterations_left > 0 ; num_iterations_left--) {
+ rv = RequestSocketInternal(group_name, &request);
+ if (rv < 0 && rv != ERR_IO_PENDING) {
+ // We're encountering a synchronous error. Give up.
+ if (!ContainsKey(group_map_, group_name))
+ deleted_group = true;
+ break;
+ }
+ if (!ContainsKey(group_map_, group_name)) {
+ // Unexpected. The group should only be getting deleted on synchronous
+ // error.
+ NOTREACHED();
+ deleted_group = true;
+ break;
+ }
+ }
+
+ if (!deleted_group && group->IsEmpty())
+ RemoveGroup(group_name);
+
+ if (rv == ERR_IO_PENDING)
+ rv = OK;
+ request.net_log().EndEventWithNetErrorCode(
+ NetLog::TYPE_SOCKET_POOL_CONNECTING_N_SOCKETS, rv);
+}
+
+int ClientSocketPoolBaseHelper::RequestSocketInternal(
+ const std::string& group_name,
+ const Request* request) {
+ ClientSocketHandle* const handle = request->handle();
+ const bool preconnecting = !handle;
+ Group* group = GetOrCreateGroup(group_name);
+
+ if (!(request->flags() & NO_IDLE_SOCKETS)) {
+ // Try to reuse a socket.
+ if (AssignIdleSocketToRequest(request, group))
+ return OK;
+ }
+
+ // If there are more ConnectJobs than pending requests, don't need to do
+ // anything. Can just wait for the extra job to connect, and then assign it
+ // to the request.
+ if (!preconnecting && group->TryToUseUnassignedConnectJob())
+ return ERR_IO_PENDING;
+
+ // Can we make another active socket now?
+ if (!group->HasAvailableSocketSlot(max_sockets_per_group_) &&
+ !request->ignore_limits()) {
+ // TODO(willchan): Consider whether or not we need to close a socket in a
+ // higher layered group. I don't think this makes sense since we would just
+ // reuse that socket then if we needed one and wouldn't make it down to this
+ // layer.
+ request->net_log().AddEvent(
+ NetLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP);
+ return ERR_IO_PENDING;
+ }
+
+ if (ReachedMaxSocketsLimit() && !request->ignore_limits()) {
+ // NOTE(mmenke): Wonder if we really need different code for each case
+ // here. Only reason for them now seems to be preconnects.
+ if (idle_socket_count() > 0) {
+ // There's an idle socket in this pool. Either that's because there's
+ // still one in this group, but we got here due to preconnecting bypassing
+ // idle sockets, or because there's an idle socket in another group.
+ bool closed = CloseOneIdleSocketExceptInGroup(group);
+ if (preconnecting && !closed)
+ return ERR_PRECONNECT_MAX_SOCKET_LIMIT;
+ } else {
+ // We could check if we really have a stalled group here, but it requires
+ // a scan of all groups, so just flip a flag here, and do the check later.
+ request->net_log().AddEvent(NetLog::TYPE_SOCKET_POOL_STALLED_MAX_SOCKETS);
+ return ERR_IO_PENDING;
+ }
+ }
+
+ // We couldn't find a socket to reuse, and there's space to allocate one,
+ // so allocate and connect a new one.
+ scoped_ptr<ConnectJob> connect_job(
+ connect_job_factory_->NewConnectJob(group_name, *request, this));
+
+ int rv = connect_job->Connect();
+ if (rv == OK) {
+ LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
+ if (!preconnecting) {
+ HandOutSocket(connect_job->PassSocket(), false /* not reused */,
+ connect_job->connect_timing(), handle, base::TimeDelta(),
+ group, request->net_log());
+ } else {
+ AddIdleSocket(connect_job->PassSocket(), group);
+ }
+ } else if (rv == ERR_IO_PENDING) {
+ // If we don't have any sockets in this group, set a timer for potentially
+ // creating a new one. If the SYN is lost, this backup socket may complete
+ // before the slow socket, improving end user latency.
+ if (connect_backup_jobs_enabled_ &&
+ group->IsEmpty() && !group->HasBackupJob()) {
+ group->StartBackupSocketTimer(group_name, this);
+ }
+
+ connecting_socket_count_++;
+
+ group->AddJob(connect_job.Pass(), preconnecting);
+ } else {
+ LogBoundConnectJobToRequest(connect_job->net_log().source(), request);
+ scoped_ptr<StreamSocket> error_socket;
+ if (!preconnecting) {
+ DCHECK(handle);
+ connect_job->GetAdditionalErrorState(handle);
+ error_socket = connect_job->PassSocket();
+ }
+ if (error_socket) {
+ HandOutSocket(error_socket.Pass(), false /* not reused */,
+ connect_job->connect_timing(), handle, base::TimeDelta(),
+ group, request->net_log());
+ } else if (group->IsEmpty()) {
+ RemoveGroup(group_name);
+ }
+ }
+
+ return rv;
+}
+
+bool ClientSocketPoolBaseHelper::AssignIdleSocketToRequest(
+ const Request* request, Group* group) {
+ std::list<IdleSocket>* idle_sockets = group->mutable_idle_sockets();
+ std::list<IdleSocket>::iterator idle_socket_it = idle_sockets->end();
+
+ // Iterate through the idle sockets forwards (oldest to newest)
+ // * Delete any disconnected ones.
+ // * If we find a used idle socket, assign to |idle_socket|. At the end,
+ // the |idle_socket_it| will be set to the newest used idle socket.
+ for (std::list<IdleSocket>::iterator it = idle_sockets->begin();
+ it != idle_sockets->end();) {
+ if (!it->socket->IsConnectedAndIdle()) {
+ DecrementIdleCount();
+ delete it->socket;
+ it = idle_sockets->erase(it);
+ continue;
+ }
+
+ if (it->socket->WasEverUsed()) {
+ // We found one we can reuse!
+ idle_socket_it = it;
+ }
+
+ ++it;
+ }
+
+ // If we haven't found an idle socket, that means there are no used idle
+ // sockets. Pick the oldest (first) idle socket (FIFO).
+
+ if (idle_socket_it == idle_sockets->end() && !idle_sockets->empty())
+ idle_socket_it = idle_sockets->begin();
+
+ if (idle_socket_it != idle_sockets->end()) {
+ DecrementIdleCount();
+ base::TimeDelta idle_time =
+ base::TimeTicks::Now() - idle_socket_it->start_time;
+ IdleSocket idle_socket = *idle_socket_it;
+ idle_sockets->erase(idle_socket_it);
+ HandOutSocket(
+ scoped_ptr<StreamSocket>(idle_socket.socket),
+ idle_socket.socket->WasEverUsed(),
+ LoadTimingInfo::ConnectTiming(),
+ request->handle(),
+ idle_time,
+ group,
+ request->net_log());
+ return true;
+ }
+
+ return false;
+}
+
+// static
+void ClientSocketPoolBaseHelper::LogBoundConnectJobToRequest(
+ const NetLog::Source& connect_job_source, const Request* request) {
+ request->net_log().AddEvent(NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ connect_job_source.ToEventParametersCallback());
+}
+
+void ClientSocketPoolBaseHelper::CancelRequest(
+ const std::string& group_name, ClientSocketHandle* handle) {
+ PendingCallbackMap::iterator callback_it = pending_callback_map_.find(handle);
+ if (callback_it != pending_callback_map_.end()) {
+ int result = callback_it->second.result;
+ pending_callback_map_.erase(callback_it);
+ scoped_ptr<StreamSocket> socket = handle->PassSocket();
+ if (socket) {
+ if (result != OK)
+ socket->Disconnect();
+ ReleaseSocket(handle->group_name(), socket.Pass(), handle->id());
+ }
+ return;
+ }
+
+ CHECK(ContainsKey(group_map_, group_name));
+
+ Group* group = GetOrCreateGroup(group_name);
+
+ // Search pending_requests for matching handle.
+ RequestQueue::iterator it = group->mutable_pending_requests()->begin();
+ for (; it != group->pending_requests().end(); ++it) {
+ if ((*it)->handle() == handle) {
+ scoped_ptr<const Request> req(RemoveRequestFromQueue(it, group));
+ req->net_log().AddEvent(NetLog::TYPE_CANCELLED);
+ req->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL);
+
+ // We let the job run, unless we're at the socket limit and there is
+ // not another request waiting on the job.
+ if (group->jobs().size() > group->pending_requests().size() &&
+ ReachedMaxSocketsLimit()) {
+ RemoveConnectJob(*group->jobs().begin(), group);
+ CheckForStalledSocketGroups();
+ }
+ break;
+ }
+ }
+}
+
+bool ClientSocketPoolBaseHelper::HasGroup(const std::string& group_name) const {
+ return ContainsKey(group_map_, group_name);
+}
+
+void ClientSocketPoolBaseHelper::CloseIdleSockets() {
+ CleanupIdleSockets(true);
+ DCHECK_EQ(0, idle_socket_count_);
+}
+
+int ClientSocketPoolBaseHelper::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ GroupMap::const_iterator i = group_map_.find(group_name);
+ CHECK(i != group_map_.end());
+
+ return i->second->idle_sockets().size();
+}
+
+LoadState ClientSocketPoolBaseHelper::GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const {
+ if (ContainsKey(pending_callback_map_, handle))
+ return LOAD_STATE_CONNECTING;
+
+ if (!ContainsKey(group_map_, group_name)) {
+ NOTREACHED() << "ClientSocketPool does not contain group: " << group_name
+ << " for handle: " << handle;
+ return LOAD_STATE_IDLE;
+ }
+
+ // Can't use operator[] since it is non-const.
+ const Group& group = *group_map_.find(group_name)->second;
+
+ // Search the first group.jobs().size() |pending_requests| for |handle|.
+ // If it's farther back in the deque than that, it doesn't have a
+ // corresponding ConnectJob.
+ size_t connect_jobs = group.jobs().size();
+ RequestQueue::const_iterator it = group.pending_requests().begin();
+ for (size_t i = 0; it != group.pending_requests().end() && i < connect_jobs;
+ ++it, ++i) {
+ if ((*it)->handle() != handle)
+ continue;
+
+ // Just return the state of the farthest along ConnectJob for the first
+ // group.jobs().size() pending requests.
+ LoadState max_state = LOAD_STATE_IDLE;
+ for (ConnectJobSet::const_iterator job_it = group.jobs().begin();
+ job_it != group.jobs().end(); ++job_it) {
+ max_state = std::max(max_state, (*job_it)->GetLoadState());
+ }
+ return max_state;
+ }
+
+ if (group.IsStalledOnPoolMaxSockets(max_sockets_per_group_))
+ return LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL;
+ return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET;
+}
+
+base::DictionaryValue* ClientSocketPoolBaseHelper::GetInfoAsValue(
+ const std::string& name, const std::string& type) const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("name", name);
+ dict->SetString("type", type);
+ dict->SetInteger("handed_out_socket_count", handed_out_socket_count_);
+ dict->SetInteger("connecting_socket_count", connecting_socket_count_);
+ dict->SetInteger("idle_socket_count", idle_socket_count_);
+ dict->SetInteger("max_socket_count", max_sockets_);
+ dict->SetInteger("max_sockets_per_group", max_sockets_per_group_);
+ dict->SetInteger("pool_generation_number", pool_generation_number_);
+
+ if (group_map_.empty())
+ return dict;
+
+ base::DictionaryValue* all_groups_dict = new base::DictionaryValue();
+ for (GroupMap::const_iterator it = group_map_.begin();
+ it != group_map_.end(); it++) {
+ const Group* group = it->second;
+ base::DictionaryValue* group_dict = new base::DictionaryValue();
+
+ group_dict->SetInteger("pending_request_count",
+ group->pending_requests().size());
+ if (!group->pending_requests().empty()) {
+ group_dict->SetInteger("top_pending_priority",
+ group->TopPendingPriority());
+ }
+
+ group_dict->SetInteger("active_socket_count", group->active_socket_count());
+
+ base::ListValue* idle_socket_list = new base::ListValue();
+ std::list<IdleSocket>::const_iterator idle_socket;
+ for (idle_socket = group->idle_sockets().begin();
+ idle_socket != group->idle_sockets().end();
+ idle_socket++) {
+ int source_id = idle_socket->socket->NetLog().source().id;
+ idle_socket_list->Append(new base::FundamentalValue(source_id));
+ }
+ group_dict->Set("idle_sockets", idle_socket_list);
+
+ base::ListValue* connect_jobs_list = new base::ListValue();
+ std::set<ConnectJob*>::const_iterator job = group->jobs().begin();
+ for (job = group->jobs().begin(); job != group->jobs().end(); job++) {
+ int source_id = (*job)->net_log().source().id;
+ connect_jobs_list->Append(new base::FundamentalValue(source_id));
+ }
+ group_dict->Set("connect_jobs", connect_jobs_list);
+
+ group_dict->SetBoolean("is_stalled",
+ group->IsStalledOnPoolMaxSockets(
+ max_sockets_per_group_));
+ group_dict->SetBoolean("has_backup_job", group->HasBackupJob());
+
+ all_groups_dict->SetWithoutPathExpansion(it->first, group_dict);
+ }
+ dict->Set("groups", all_groups_dict);
+ return dict;
+}
+
+bool ClientSocketPoolBaseHelper::IdleSocket::ShouldCleanup(
+ base::TimeTicks now,
+ base::TimeDelta timeout) const {
+ bool timed_out = (now - start_time) >= timeout;
+ if (timed_out)
+ return true;
+ if (socket->WasEverUsed())
+ return !socket->IsConnectedAndIdle();
+ return !socket->IsConnected();
+}
+
+void ClientSocketPoolBaseHelper::CleanupIdleSockets(bool force) {
+ if (idle_socket_count_ == 0)
+ return;
+
+ // Current time value. Retrieving it once at the function start rather than
+ // inside the inner loop, since it shouldn't change by any meaningful amount.
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ GroupMap::iterator i = group_map_.begin();
+ while (i != group_map_.end()) {
+ Group* group = i->second;
+
+ std::list<IdleSocket>::iterator j = group->mutable_idle_sockets()->begin();
+ while (j != group->idle_sockets().end()) {
+ base::TimeDelta timeout =
+ j->socket->WasEverUsed() ?
+ used_idle_socket_timeout_ : unused_idle_socket_timeout_;
+ if (force || j->ShouldCleanup(now, timeout)) {
+ delete j->socket;
+ j = group->mutable_idle_sockets()->erase(j);
+ DecrementIdleCount();
+ } else {
+ ++j;
+ }
+ }
+
+ // Delete group if no longer needed.
+ if (group->IsEmpty()) {
+ RemoveGroup(i++);
+ } else {
+ ++i;
+ }
+ }
+}
+
+ClientSocketPoolBaseHelper::Group* ClientSocketPoolBaseHelper::GetOrCreateGroup(
+ const std::string& group_name) {
+ GroupMap::iterator it = group_map_.find(group_name);
+ if (it != group_map_.end())
+ return it->second;
+ Group* group = new Group;
+ group_map_[group_name] = group;
+ return group;
+}
+
+void ClientSocketPoolBaseHelper::RemoveGroup(const std::string& group_name) {
+ GroupMap::iterator it = group_map_.find(group_name);
+ CHECK(it != group_map_.end());
+
+ RemoveGroup(it);
+}
+
+void ClientSocketPoolBaseHelper::RemoveGroup(GroupMap::iterator it) {
+ delete it->second;
+ group_map_.erase(it);
+}
+
+// static
+bool ClientSocketPoolBaseHelper::connect_backup_jobs_enabled() {
+ return g_connect_backup_jobs_enabled;
+}
+
+// static
+bool ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(bool enabled) {
+ bool old_value = g_connect_backup_jobs_enabled;
+ g_connect_backup_jobs_enabled = enabled;
+ return old_value;
+}
+
+void ClientSocketPoolBaseHelper::EnableConnectBackupJobs() {
+ connect_backup_jobs_enabled_ = g_connect_backup_jobs_enabled;
+}
+
+void ClientSocketPoolBaseHelper::IncrementIdleCount() {
+ if (++idle_socket_count_ == 1 && use_cleanup_timer_)
+ StartIdleSocketTimer();
+}
+
+void ClientSocketPoolBaseHelper::DecrementIdleCount() {
+ if (--idle_socket_count_ == 0)
+ timer_.Stop();
+}
+
+// static
+bool ClientSocketPoolBaseHelper::cleanup_timer_enabled() {
+ return g_cleanup_timer_enabled;
+}
+
+// static
+bool ClientSocketPoolBaseHelper::set_cleanup_timer_enabled(bool enabled) {
+ bool old_value = g_cleanup_timer_enabled;
+ g_cleanup_timer_enabled = enabled;
+ return old_value;
+}
+
+void ClientSocketPoolBaseHelper::StartIdleSocketTimer() {
+ timer_.Start(FROM_HERE, TimeDelta::FromSeconds(kCleanupInterval), this,
+ &ClientSocketPoolBaseHelper::OnCleanupTimerFired);
+}
+
+void ClientSocketPoolBaseHelper::ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ GroupMap::iterator i = group_map_.find(group_name);
+ CHECK(i != group_map_.end());
+
+ Group* group = i->second;
+
+ CHECK_GT(handed_out_socket_count_, 0);
+ handed_out_socket_count_--;
+
+ CHECK_GT(group->active_socket_count(), 0);
+ group->DecrementActiveSocketCount();
+
+ const bool can_reuse = socket->IsConnectedAndIdle() &&
+ id == pool_generation_number_;
+ if (can_reuse) {
+ // Add it to the idle list.
+ AddIdleSocket(socket.Pass(), group);
+ OnAvailableSocketSlot(group_name, group);
+ } else {
+ socket.reset();
+ }
+
+ CheckForStalledSocketGroups();
+}
+
+void ClientSocketPoolBaseHelper::CheckForStalledSocketGroups() {
+ // If we have idle sockets, see if we can give one to the top-stalled group.
+ std::string top_group_name;
+ Group* top_group = NULL;
+ if (!FindTopStalledGroup(&top_group, &top_group_name))
+ return;
+
+ if (ReachedMaxSocketsLimit()) {
+ if (idle_socket_count() > 0) {
+ CloseOneIdleSocket();
+ } else {
+ // We can't activate more sockets since we're already at our global
+ // limit.
+ return;
+ }
+ }
+
+ // Note: we don't loop on waking stalled groups. If the stalled group is at
+ // its limit, may be left with other stalled groups that could be
+ // woken. This isn't optimal, but there is no starvation, so to avoid
+ // the looping we leave it at this.
+ OnAvailableSocketSlot(top_group_name, top_group);
+}
+
+// Search for the highest priority pending request, amongst the groups that
+// are not at the |max_sockets_per_group_| limit. Note: for requests with
+// the same priority, the winner is based on group hash ordering (and not
+// insertion order).
+bool ClientSocketPoolBaseHelper::FindTopStalledGroup(
+ Group** group,
+ std::string* group_name) const {
+ CHECK((group && group_name) || (!group && !group_name));
+ Group* top_group = NULL;
+ const std::string* top_group_name = NULL;
+ bool has_stalled_group = false;
+ for (GroupMap::const_iterator i = group_map_.begin();
+ i != group_map_.end(); ++i) {
+ Group* curr_group = i->second;
+ const RequestQueue& queue = curr_group->pending_requests();
+ if (queue.empty())
+ continue;
+ if (curr_group->IsStalledOnPoolMaxSockets(max_sockets_per_group_)) {
+ if (!group)
+ return true;
+ has_stalled_group = true;
+ bool has_higher_priority = !top_group ||
+ curr_group->TopPendingPriority() > top_group->TopPendingPriority();
+ if (has_higher_priority) {
+ top_group = curr_group;
+ top_group_name = &i->first;
+ }
+ }
+ }
+
+ if (top_group) {
+ CHECK(group);
+ *group = top_group;
+ *group_name = *top_group_name;
+ } else {
+ CHECK(!has_stalled_group);
+ }
+ return has_stalled_group;
+}
+
+void ClientSocketPoolBaseHelper::OnConnectJobComplete(
+ int result, ConnectJob* job) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ const std::string group_name = job->group_name();
+ GroupMap::iterator group_it = group_map_.find(group_name);
+ CHECK(group_it != group_map_.end());
+ Group* group = group_it->second;
+
+ scoped_ptr<StreamSocket> socket = job->PassSocket();
+
+ // Copies of these are needed because |job| may be deleted before they are
+ // accessed.
+ BoundNetLog job_log = job->net_log();
+ LoadTimingInfo::ConnectTiming connect_timing = job->connect_timing();
+
+ // RemoveConnectJob(job, _) must be called by all branches below;
+ // otherwise, |job| will be leaked.
+
+ if (result == OK) {
+ DCHECK(socket.get());
+ RemoveConnectJob(job, group);
+ if (!group->pending_requests().empty()) {
+ scoped_ptr<const Request> r(RemoveRequestFromQueue(
+ group->mutable_pending_requests()->begin(), group));
+ LogBoundConnectJobToRequest(job_log.source(), r.get());
+ HandOutSocket(
+ socket.Pass(), false /* unused socket */, connect_timing,
+ r->handle(), base::TimeDelta(), group, r->net_log());
+ r->net_log().EndEvent(NetLog::TYPE_SOCKET_POOL);
+ InvokeUserCallbackLater(r->handle(), r->callback(), result);
+ } else {
+ AddIdleSocket(socket.Pass(), group);
+ OnAvailableSocketSlot(group_name, group);
+ CheckForStalledSocketGroups();
+ }
+ } else {
+ // If we got a socket, it must contain error information so pass that
+ // up so that the caller can retrieve it.
+ bool handed_out_socket = false;
+ if (!group->pending_requests().empty()) {
+ scoped_ptr<const Request> r(RemoveRequestFromQueue(
+ group->mutable_pending_requests()->begin(), group));
+ LogBoundConnectJobToRequest(job_log.source(), r.get());
+ job->GetAdditionalErrorState(r->handle());
+ RemoveConnectJob(job, group);
+ if (socket.get()) {
+ handed_out_socket = true;
+ HandOutSocket(socket.Pass(), false /* unused socket */,
+ connect_timing, r->handle(), base::TimeDelta(), group,
+ r->net_log());
+ }
+ r->net_log().EndEventWithNetErrorCode(NetLog::TYPE_SOCKET_POOL, result);
+ InvokeUserCallbackLater(r->handle(), r->callback(), result);
+ } else {
+ RemoveConnectJob(job, group);
+ }
+ if (!handed_out_socket) {
+ OnAvailableSocketSlot(group_name, group);
+ CheckForStalledSocketGroups();
+ }
+ }
+}
+
+void ClientSocketPoolBaseHelper::OnIPAddressChanged() {
+ FlushWithError(ERR_NETWORK_CHANGED);
+}
+
+void ClientSocketPoolBaseHelper::FlushWithError(int error) {
+ pool_generation_number_++;
+ CancelAllConnectJobs();
+ CloseIdleSockets();
+ CancelAllRequestsWithError(error);
+}
+
+bool ClientSocketPoolBaseHelper::IsStalled() const {
+ // If we are not using |max_sockets_|, then clearly we are not stalled
+ if ((handed_out_socket_count_ + connecting_socket_count_) < max_sockets_)
+ return false;
+ // So in order to be stalled we need to be using |max_sockets_| AND
+ // we need to have a request that is actually stalled on the global
+ // socket limit. To find such a request, we look for a group that
+ // a has more requests that jobs AND where the number of jobs is less
+ // than |max_sockets_per_group_|. (If the number of jobs is equal to
+ // |max_sockets_per_group_|, then the request is stalled on the group,
+ // which does not count.)
+ for (GroupMap::const_iterator it = group_map_.begin();
+ it != group_map_.end(); ++it) {
+ if (it->second->IsStalledOnPoolMaxSockets(max_sockets_per_group_))
+ return true;
+ }
+ return false;
+}
+
+void ClientSocketPoolBaseHelper::RemoveConnectJob(ConnectJob* job,
+ Group* group) {
+ CHECK_GT(connecting_socket_count_, 0);
+ connecting_socket_count_--;
+
+ DCHECK(group);
+ group->RemoveJob(job);
+
+ // If we've got no more jobs for this group, then we no longer need a
+ // backup job either.
+ if (group->jobs().empty())
+ group->CleanupBackupJob();
+}
+
+void ClientSocketPoolBaseHelper::OnAvailableSocketSlot(
+ const std::string& group_name, Group* group) {
+ DCHECK(ContainsKey(group_map_, group_name));
+ if (group->IsEmpty())
+ RemoveGroup(group_name);
+ else if (!group->pending_requests().empty())
+ ProcessPendingRequest(group_name, group);
+}
+
+void ClientSocketPoolBaseHelper::ProcessPendingRequest(
+ const std::string& group_name, Group* group) {
+ int rv = RequestSocketInternal(group_name,
+ *group->pending_requests().begin());
+ if (rv != ERR_IO_PENDING) {
+ scoped_ptr<const Request> request(RemoveRequestFromQueue(
+ group->mutable_pending_requests()->begin(), group));
+ if (group->IsEmpty())
+ RemoveGroup(group_name);
+
+ request->net_log().EndEventWithNetErrorCode(NetLog::TYPE_SOCKET_POOL, rv);
+ InvokeUserCallbackLater(request->handle(), request->callback(), rv);
+ }
+}
+
+void ClientSocketPoolBaseHelper::HandOutSocket(
+ scoped_ptr<StreamSocket> socket,
+ bool reused,
+ const LoadTimingInfo::ConnectTiming& connect_timing,
+ ClientSocketHandle* handle,
+ base::TimeDelta idle_time,
+ Group* group,
+ const BoundNetLog& net_log) {
+ DCHECK(socket);
+ handle->SetSocket(socket.Pass());
+ handle->set_is_reused(reused);
+ handle->set_idle_time(idle_time);
+ handle->set_pool_id(pool_generation_number_);
+ handle->set_connect_timing(connect_timing);
+
+ if (reused) {
+ net_log.AddEvent(
+ NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET,
+ NetLog::IntegerCallback(
+ "idle_ms", static_cast<int>(idle_time.InMilliseconds())));
+ }
+
+ net_log.AddEvent(
+ NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ handle->socket()->NetLog().source().ToEventParametersCallback());
+
+ handed_out_socket_count_++;
+ group->IncrementActiveSocketCount();
+}
+
+void ClientSocketPoolBaseHelper::AddIdleSocket(
+ scoped_ptr<StreamSocket> socket,
+ Group* group) {
+ DCHECK(socket);
+ IdleSocket idle_socket;
+ idle_socket.socket = socket.release();
+ idle_socket.start_time = base::TimeTicks::Now();
+
+ group->mutable_idle_sockets()->push_back(idle_socket);
+ IncrementIdleCount();
+}
+
+void ClientSocketPoolBaseHelper::CancelAllConnectJobs() {
+ for (GroupMap::iterator i = group_map_.begin(); i != group_map_.end();) {
+ Group* group = i->second;
+ connecting_socket_count_ -= group->jobs().size();
+ group->RemoveAllJobs();
+
+ // Delete group if no longer needed.
+ if (group->IsEmpty()) {
+ // RemoveGroup() will call .erase() which will invalidate the iterator,
+ // but i will already have been incremented to a valid iterator before
+ // RemoveGroup() is called.
+ RemoveGroup(i++);
+ } else {
+ ++i;
+ }
+ }
+ DCHECK_EQ(0, connecting_socket_count_);
+}
+
+void ClientSocketPoolBaseHelper::CancelAllRequestsWithError(int error) {
+ for (GroupMap::iterator i = group_map_.begin(); i != group_map_.end();) {
+ Group* group = i->second;
+
+ RequestQueue pending_requests;
+ pending_requests.swap(*group->mutable_pending_requests());
+ for (RequestQueue::iterator it2 = pending_requests.begin();
+ it2 != pending_requests.end(); ++it2) {
+ scoped_ptr<const Request> request(*it2);
+ InvokeUserCallbackLater(
+ request->handle(), request->callback(), error);
+ }
+
+ // Delete group if no longer needed.
+ if (group->IsEmpty()) {
+ // RemoveGroup() will call .erase() which will invalidate the iterator,
+ // but i will already have been incremented to a valid iterator before
+ // RemoveGroup() is called.
+ RemoveGroup(i++);
+ } else {
+ ++i;
+ }
+ }
+}
+
+bool ClientSocketPoolBaseHelper::ReachedMaxSocketsLimit() const {
+ // Each connecting socket will eventually connect and be handed out.
+ int total = handed_out_socket_count_ + connecting_socket_count_ +
+ idle_socket_count();
+ // There can be more sockets than the limit since some requests can ignore
+ // the limit
+ if (total < max_sockets_)
+ return false;
+ return true;
+}
+
+bool ClientSocketPoolBaseHelper::CloseOneIdleSocket() {
+ if (idle_socket_count() == 0)
+ return false;
+ return CloseOneIdleSocketExceptInGroup(NULL);
+}
+
+bool ClientSocketPoolBaseHelper::CloseOneIdleSocketExceptInGroup(
+ const Group* exception_group) {
+ CHECK_GT(idle_socket_count(), 0);
+
+ for (GroupMap::iterator i = group_map_.begin(); i != group_map_.end(); ++i) {
+ Group* group = i->second;
+ if (exception_group == group)
+ continue;
+ std::list<IdleSocket>* idle_sockets = group->mutable_idle_sockets();
+
+ if (!idle_sockets->empty()) {
+ delete idle_sockets->front().socket;
+ idle_sockets->pop_front();
+ DecrementIdleCount();
+ if (group->IsEmpty())
+ RemoveGroup(i);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ClientSocketPoolBaseHelper::CloseOneIdleConnectionInLayeredPool() {
+ // This pool doesn't have any idle sockets. It's possible that a pool at a
+ // higher layer is holding one of this sockets active, but it's actually idle.
+ // Query the higher layers.
+ for (std::set<LayeredPool*>::const_iterator it = higher_layer_pools_.begin();
+ it != higher_layer_pools_.end(); ++it) {
+ if ((*it)->CloseOneIdleConnection())
+ return true;
+ }
+ return false;
+}
+
+void ClientSocketPoolBaseHelper::InvokeUserCallbackLater(
+ ClientSocketHandle* handle, const CompletionCallback& callback, int rv) {
+ CHECK(!ContainsKey(pending_callback_map_, handle));
+ pending_callback_map_[handle] = CallbackResultPair(callback, rv);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientSocketPoolBaseHelper::InvokeUserCallback,
+ weak_factory_.GetWeakPtr(), handle));
+}
+
+void ClientSocketPoolBaseHelper::InvokeUserCallback(
+ ClientSocketHandle* handle) {
+ PendingCallbackMap::iterator it = pending_callback_map_.find(handle);
+
+ // Exit if the request has already been cancelled.
+ if (it == pending_callback_map_.end())
+ return;
+
+ CHECK(!handle->is_initialized());
+ CompletionCallback callback = it->second.callback;
+ int result = it->second.result;
+ pending_callback_map_.erase(it);
+ callback.Run(result);
+}
+
+void ClientSocketPoolBaseHelper::TryToCloseSocketsInLayeredPools() {
+ while (IsStalled()) {
+ // Closing a socket will result in calling back into |this| to use the freed
+ // socket slot, so nothing else is needed.
+ if (!CloseOneIdleConnectionInLayeredPool())
+ return;
+ }
+}
+
+ClientSocketPoolBaseHelper::Group::Group()
+ : unassigned_job_count_(0),
+ active_socket_count_(0),
+ weak_factory_(this) {}
+
+ClientSocketPoolBaseHelper::Group::~Group() {
+ CleanupBackupJob();
+ DCHECK_EQ(0u, unassigned_job_count_);
+}
+
+void ClientSocketPoolBaseHelper::Group::StartBackupSocketTimer(
+ const std::string& group_name,
+ ClientSocketPoolBaseHelper* pool) {
+ // Only allow one timer pending to create a backup socket.
+ if (weak_factory_.HasWeakPtrs())
+ return;
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Group::OnBackupSocketTimerFired, weak_factory_.GetWeakPtr(),
+ group_name, pool),
+ pool->ConnectRetryInterval());
+}
+
+bool ClientSocketPoolBaseHelper::Group::TryToUseUnassignedConnectJob() {
+ SanityCheck();
+
+ if (unassigned_job_count_ == 0)
+ return false;
+ --unassigned_job_count_;
+ return true;
+}
+
+void ClientSocketPoolBaseHelper::Group::AddJob(scoped_ptr<ConnectJob> job,
+ bool is_preconnect) {
+ SanityCheck();
+
+ if (is_preconnect)
+ ++unassigned_job_count_;
+ jobs_.insert(job.release());
+}
+
+void ClientSocketPoolBaseHelper::Group::RemoveJob(ConnectJob* job) {
+ scoped_ptr<ConnectJob> owned_job(job);
+ SanityCheck();
+
+ std::set<ConnectJob*>::iterator it = jobs_.find(job);
+ if (it != jobs_.end()) {
+ jobs_.erase(it);
+ } else {
+ NOTREACHED();
+ }
+ size_t job_count = jobs_.size();
+ if (job_count < unassigned_job_count_)
+ unassigned_job_count_ = job_count;
+}
+
+void ClientSocketPoolBaseHelper::Group::OnBackupSocketTimerFired(
+ std::string group_name,
+ ClientSocketPoolBaseHelper* pool) {
+ // If there are no more jobs pending, there is no work to do.
+ // If we've done our cleanups correctly, this should not happen.
+ if (jobs_.empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ // If our old job is waiting on DNS, or if we can't create any sockets
+ // right now due to limits, just reset the timer.
+ if (pool->ReachedMaxSocketsLimit() ||
+ !HasAvailableSocketSlot(pool->max_sockets_per_group_) ||
+ (*jobs_.begin())->GetLoadState() == LOAD_STATE_RESOLVING_HOST) {
+ StartBackupSocketTimer(group_name, pool);
+ return;
+ }
+
+ if (pending_requests_.empty())
+ return;
+
+ scoped_ptr<ConnectJob> backup_job =
+ pool->connect_job_factory_->NewConnectJob(
+ group_name, **pending_requests_.begin(), pool);
+ backup_job->net_log().AddEvent(NetLog::TYPE_SOCKET_BACKUP_CREATED);
+ SIMPLE_STATS_COUNTER("socket.backup_created");
+ int rv = backup_job->Connect();
+ pool->connecting_socket_count_++;
+ ConnectJob* raw_backup_job = backup_job.get();
+ AddJob(backup_job.Pass(), false);
+ if (rv != ERR_IO_PENDING)
+ pool->OnConnectJobComplete(rv, raw_backup_job);
+}
+
+void ClientSocketPoolBaseHelper::Group::SanityCheck() {
+ DCHECK_LE(unassigned_job_count_, jobs_.size());
+}
+
+void ClientSocketPoolBaseHelper::Group::RemoveAllJobs() {
+ SanityCheck();
+
+ // Delete active jobs.
+ STLDeleteElements(&jobs_);
+ unassigned_job_count_ = 0;
+
+ // Cancel pending backup job.
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+} // namespace internal
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool_base.h b/chromium/net/socket/client_socket_pool_base.h
new file mode 100644
index 00000000000..eb642edd730
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_base.h
@@ -0,0 +1,819 @@
+// Copyright (c) 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.
+//
+// A ClientSocketPoolBase is used to restrict the number of sockets open at
+// a time. It also maintains a list of idle persistent sockets for reuse.
+// Subclasses of ClientSocketPool should compose ClientSocketPoolBase to handle
+// the core logic of (1) restricting the number of active (connected or
+// connecting) sockets per "group" (generally speaking, the hostname), (2)
+// maintaining a per-group list of idle, persistent sockets for reuse, and (3)
+// limiting the total number of active sockets in the system.
+//
+// ClientSocketPoolBase abstracts socket connection details behind ConnectJob,
+// ConnectJobFactory, and SocketParams. When a socket "slot" becomes available,
+// the ClientSocketPoolBase will ask the ConnectJobFactory to create a
+// ConnectJob with a SocketParams. Subclasses of ClientSocketPool should
+// implement their socket specific connection by subclassing ConnectJob and
+// implementing ConnectJob::ConnectInternal(). They can control the parameters
+// passed to each new ConnectJob instance via their ConnectJobFactory subclass
+// and templated SocketParams parameter.
+//
+#ifndef NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/request_priority.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+class ClientSocketHandle;
+
+// ConnectJob provides an abstract interface for "connecting" a socket.
+// The connection may involve host resolution, tcp connection, ssl connection,
+// etc.
+class NET_EXPORT_PRIVATE ConnectJob {
+ public:
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ Delegate() {}
+ virtual ~Delegate() {}
+
+ // Alerts the delegate that the connection completed. |job| must
+ // be destroyed by the delegate. A scoped_ptr<> isn't used because
+ // the caller of this function doesn't own |job|.
+ virtual void OnConnectJobComplete(int result,
+ ConnectJob* job) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // A |timeout_duration| of 0 corresponds to no timeout.
+ ConnectJob(const std::string& group_name,
+ base::TimeDelta timeout_duration,
+ Delegate* delegate,
+ const BoundNetLog& net_log);
+ virtual ~ConnectJob();
+
+ // Accessors
+ const std::string& group_name() const { return group_name_; }
+ const BoundNetLog& net_log() { return net_log_; }
+
+ // Releases ownership of the underlying socket to the caller.
+ // Returns the released socket, or NULL if there was a connection
+ // error.
+ scoped_ptr<StreamSocket> PassSocket();
+
+ // Begins connecting the socket. Returns OK on success, ERR_IO_PENDING if it
+ // cannot complete synchronously without blocking, or another net error code
+ // on error. In asynchronous completion, the ConnectJob will notify
+ // |delegate_| via OnConnectJobComplete. In both asynchronous and synchronous
+ // completion, ReleaseSocket() can be called to acquire the connected socket
+ // if it succeeded.
+ int Connect();
+
+ virtual LoadState GetLoadState() const = 0;
+
+ // If Connect returns an error (or OnConnectJobComplete reports an error
+ // result) this method will be called, allowing the pool to add
+ // additional error state to the ClientSocketHandle (post late-binding).
+ virtual void GetAdditionalErrorState(ClientSocketHandle* handle) {}
+
+ const LoadTimingInfo::ConnectTiming& connect_timing() const {
+ return connect_timing_;
+ }
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ protected:
+ void SetSocket(scoped_ptr<StreamSocket> socket);
+ StreamSocket* socket() { return socket_.get(); }
+ void NotifyDelegateOfCompletion(int rv);
+ void ResetTimer(base::TimeDelta remainingTime);
+
+ // Connection establishment timing information.
+ LoadTimingInfo::ConnectTiming connect_timing_;
+
+ private:
+ virtual int ConnectInternal() = 0;
+
+ void LogConnectStart();
+ void LogConnectCompletion(int net_error);
+
+ // Alerts the delegate that the ConnectJob has timed out.
+ void OnTimeout();
+
+ const std::string group_name_;
+ const base::TimeDelta timeout_duration_;
+ // Timer to abort jobs that take too long.
+ base::OneShotTimer<ConnectJob> timer_;
+ Delegate* delegate_;
+ scoped_ptr<StreamSocket> socket_;
+ BoundNetLog net_log_;
+ // A ConnectJob is idle until Connect() has been called.
+ bool idle_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectJob);
+};
+
+namespace internal {
+
+// ClientSocketPoolBaseHelper is an internal class that implements almost all
+// the functionality from ClientSocketPoolBase without using templates.
+// ClientSocketPoolBase adds templated definitions built on top of
+// ClientSocketPoolBaseHelper. This class is not for external use, please use
+// ClientSocketPoolBase instead.
+class NET_EXPORT_PRIVATE ClientSocketPoolBaseHelper
+ : public ConnectJob::Delegate,
+ public NetworkChangeNotifier::IPAddressObserver {
+ public:
+ typedef uint32 Flags;
+
+ // Used to specify specific behavior for the ClientSocketPool.
+ enum Flag {
+ NORMAL = 0, // Normal behavior.
+ NO_IDLE_SOCKETS = 0x1, // Do not return an idle socket. Create a new one.
+ };
+
+ class NET_EXPORT_PRIVATE Request {
+ public:
+ Request(ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ RequestPriority priority,
+ bool ignore_limits,
+ Flags flags,
+ const BoundNetLog& net_log);
+
+ virtual ~Request();
+
+ ClientSocketHandle* handle() const { return handle_; }
+ const CompletionCallback& callback() const { return callback_; }
+ RequestPriority priority() const { return priority_; }
+ bool ignore_limits() const { return ignore_limits_; }
+ Flags flags() const { return flags_; }
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ private:
+ ClientSocketHandle* const handle_;
+ CompletionCallback callback_;
+ const RequestPriority priority_;
+ bool ignore_limits_;
+ const Flags flags_;
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(Request);
+ };
+
+ class ConnectJobFactory {
+ public:
+ ConnectJobFactory() {}
+ virtual ~ConnectJobFactory() {}
+
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const Request& request,
+ ConnectJob::Delegate* delegate) const = 0;
+
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
+ };
+
+ ClientSocketPoolBaseHelper(
+ int max_sockets,
+ int max_sockets_per_group,
+ base::TimeDelta unused_idle_socket_timeout,
+ base::TimeDelta used_idle_socket_timeout,
+ ConnectJobFactory* connect_job_factory);
+
+ virtual ~ClientSocketPoolBaseHelper();
+
+ // Adds/Removes layered pools. It is expected in the destructor that no
+ // layered pools remain.
+ void AddLayeredPool(LayeredPool* pool);
+ void RemoveLayeredPool(LayeredPool* pool);
+
+ // See ClientSocketPool::RequestSocket for documentation on this function.
+ // ClientSocketPoolBaseHelper takes ownership of |request|, which must be
+ // heap allocated.
+ int RequestSocket(const std::string& group_name, const Request* request);
+
+ // See ClientSocketPool::RequestSocket for documentation on this function.
+ void RequestSockets(const std::string& group_name,
+ const Request& request,
+ int num_sockets);
+
+ // See ClientSocketPool::CancelRequest for documentation on this function.
+ void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle);
+
+ // See ClientSocketPool::ReleaseSocket for documentation on this function.
+ void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id);
+
+ // See ClientSocketPool::FlushWithError for documentation on this function.
+ void FlushWithError(int error);
+
+ // See ClientSocketPool::IsStalled for documentation on this function.
+ bool IsStalled() const;
+
+ // See ClientSocketPool::CloseIdleSockets for documentation on this function.
+ void CloseIdleSockets();
+
+ // See ClientSocketPool::IdleSocketCount() for documentation on this function.
+ int idle_socket_count() const {
+ return idle_socket_count_;
+ }
+
+ // See ClientSocketPool::IdleSocketCountInGroup() for documentation on this
+ // function.
+ int IdleSocketCountInGroup(const std::string& group_name) const;
+
+ // See ClientSocketPool::GetLoadState() for documentation on this function.
+ LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const;
+
+ base::TimeDelta ConnectRetryInterval() const {
+ // TODO(mbelshe): Make this tuned dynamically based on measured RTT.
+ // For now, just use the max retry interval.
+ return base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs);
+ }
+
+ int NumUnassignedConnectJobsInGroup(const std::string& group_name) const {
+ return group_map_.find(group_name)->second->unassigned_job_count();
+ }
+
+ int NumConnectJobsInGroup(const std::string& group_name) const {
+ return group_map_.find(group_name)->second->jobs().size();
+ }
+
+ int NumActiveSocketsInGroup(const std::string& group_name) const {
+ return group_map_.find(group_name)->second->active_socket_count();
+ }
+
+ bool HasGroup(const std::string& group_name) const;
+
+ // Called to enable/disable cleaning up idle sockets. When enabled,
+ // idle sockets that have been around for longer than a period defined
+ // by kCleanupInterval are cleaned up using a timer. Otherwise they are
+ // closed next time client makes a request. This may reduce network
+ // activity and power consumption.
+ static bool cleanup_timer_enabled();
+ static bool set_cleanup_timer_enabled(bool enabled);
+
+ // Closes all idle sockets if |force| is true. Else, only closes idle
+ // sockets that timed out or can't be reused. Made public for testing.
+ void CleanupIdleSockets(bool force);
+
+ // Closes one idle socket. Picks the first one encountered.
+ // TODO(willchan): Consider a better algorithm for doing this. Perhaps we
+ // should keep an ordered list of idle sockets, and close them in order.
+ // Requires maintaining more state. It's not clear if it's worth it since
+ // I'm not sure if we hit this situation often.
+ bool CloseOneIdleSocket();
+
+ // Checks layered pools to see if they can close an idle connection.
+ bool CloseOneIdleConnectionInLayeredPool();
+
+ // See ClientSocketPool::GetInfoAsValue for documentation on this function.
+ base::DictionaryValue* GetInfoAsValue(const std::string& name,
+ const std::string& type) const;
+
+ base::TimeDelta ConnectionTimeout() const {
+ return connect_job_factory_->ConnectionTimeout();
+ }
+
+ static bool connect_backup_jobs_enabled();
+ static bool set_connect_backup_jobs_enabled(bool enabled);
+
+ void EnableConnectBackupJobs();
+
+ // ConnectJob::Delegate methods:
+ virtual void OnConnectJobComplete(int result, ConnectJob* job) OVERRIDE;
+
+ // NetworkChangeNotifier::IPAddressObserver methods:
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ private:
+ friend class base::RefCounted<ClientSocketPoolBaseHelper>;
+
+ // Entry for a persistent socket which became idle at time |start_time|.
+ struct IdleSocket {
+ IdleSocket() : socket(NULL) {}
+
+ // An idle socket should be removed if it can't be reused, or has been idle
+ // for too long. |now| is the current time value (TimeTicks::Now()).
+ // |timeout| is the length of time to wait before timing out an idle socket.
+ //
+ // An idle socket can't be reused if it is disconnected or has received
+ // data unexpectedly (hence no longer idle). The unread data would be
+ // mistaken for the beginning of the next response if we were to reuse the
+ // socket for a new request.
+ bool ShouldCleanup(base::TimeTicks now, base::TimeDelta timeout) const;
+
+ StreamSocket* socket;
+ base::TimeTicks start_time;
+ };
+
+ typedef std::deque<const Request* > RequestQueue;
+ typedef std::map<const ClientSocketHandle*, const Request*> RequestMap;
+
+ // A Group is allocated per group_name when there are idle sockets or pending
+ // requests. Otherwise, the Group object is removed from the map.
+ // |active_socket_count| tracks the number of sockets held by clients.
+ class Group {
+ public:
+ Group();
+ ~Group();
+
+ bool IsEmpty() const {
+ return active_socket_count_ == 0 && idle_sockets_.empty() &&
+ jobs_.empty() && pending_requests_.empty();
+ }
+
+ bool HasAvailableSocketSlot(int max_sockets_per_group) const {
+ return NumActiveSocketSlots() < max_sockets_per_group;
+ }
+
+ int NumActiveSocketSlots() const {
+ return active_socket_count_ + static_cast<int>(jobs_.size()) +
+ static_cast<int>(idle_sockets_.size());
+ }
+
+ bool IsStalledOnPoolMaxSockets(int max_sockets_per_group) const {
+ return HasAvailableSocketSlot(max_sockets_per_group) &&
+ pending_requests_.size() > jobs_.size();
+ }
+
+ RequestPriority TopPendingPriority() const {
+ return pending_requests_.front()->priority();
+ }
+
+ bool HasBackupJob() const { return weak_factory_.HasWeakPtrs(); }
+
+ void CleanupBackupJob() {
+ weak_factory_.InvalidateWeakPtrs();
+ }
+
+ // Set a timer to create a backup socket if it takes too long to create one.
+ void StartBackupSocketTimer(const std::string& group_name,
+ ClientSocketPoolBaseHelper* pool);
+
+ // If there's a ConnectJob that's never been assigned to Request,
+ // decrements |unassigned_job_count_| and returns true.
+ // Otherwise, returns false.
+ bool TryToUseUnassignedConnectJob();
+
+ void AddJob(scoped_ptr<ConnectJob> job, bool is_preconnect);
+ // Remove |job| from this group, which must already own |job|.
+ void RemoveJob(ConnectJob* job);
+ void RemoveAllJobs();
+
+ void IncrementActiveSocketCount() { active_socket_count_++; }
+ void DecrementActiveSocketCount() { active_socket_count_--; }
+
+ int unassigned_job_count() const { return unassigned_job_count_; }
+ const std::set<ConnectJob*>& jobs() const { return jobs_; }
+ const std::list<IdleSocket>& idle_sockets() const { return idle_sockets_; }
+ const RequestQueue& pending_requests() const { return pending_requests_; }
+ int active_socket_count() const { return active_socket_count_; }
+ RequestQueue* mutable_pending_requests() { return &pending_requests_; }
+ std::list<IdleSocket>* mutable_idle_sockets() { return &idle_sockets_; }
+
+ private:
+ // Called when the backup socket timer fires.
+ void OnBackupSocketTimerFired(
+ std::string group_name,
+ ClientSocketPoolBaseHelper* pool);
+
+ // Checks that |unassigned_job_count_| does not execeed the number of
+ // ConnectJobs.
+ void SanityCheck();
+
+ // Total number of ConnectJobs that have never been assigned to a Request.
+ // Since jobs use late binding to requests, which ConnectJobs have or have
+ // not been assigned to a request are not tracked. This is incremented on
+ // preconnect and decremented when a preconnect is assigned, or when there
+ // are fewer than |unassigned_job_count_| ConnectJobs. Not incremented
+ // when a request is cancelled.
+ size_t unassigned_job_count_;
+
+ std::list<IdleSocket> idle_sockets_;
+ std::set<ConnectJob*> jobs_;
+ RequestQueue pending_requests_;
+ int active_socket_count_; // number of active sockets used by clients
+ // A factory to pin the backup_job tasks.
+ base::WeakPtrFactory<Group> weak_factory_;
+ };
+
+ typedef std::map<std::string, Group*> GroupMap;
+
+ typedef std::set<ConnectJob*> ConnectJobSet;
+
+ struct CallbackResultPair {
+ CallbackResultPair();
+ CallbackResultPair(const CompletionCallback& callback_in, int result_in);
+ ~CallbackResultPair();
+
+ CompletionCallback callback;
+ int result;
+ };
+
+ typedef std::map<const ClientSocketHandle*, CallbackResultPair>
+ PendingCallbackMap;
+
+ // Inserts the request into the queue based on order they will receive
+ // sockets. Sockets which ignore the socket pool limits are first. Then
+ // requests are sorted by priority, with higher priorities closer to the
+ // front. Older requests are prioritized over requests of equal priority.
+ static void InsertRequestIntoQueue(const Request* r,
+ RequestQueue* pending_requests);
+ static const Request* RemoveRequestFromQueue(const RequestQueue::iterator& it,
+ Group* group);
+
+ Group* GetOrCreateGroup(const std::string& group_name);
+ void RemoveGroup(const std::string& group_name);
+ void RemoveGroup(GroupMap::iterator it);
+
+ // Called when the number of idle sockets changes.
+ void IncrementIdleCount();
+ void DecrementIdleCount();
+
+ // Start cleanup timer for idle sockets.
+ void StartIdleSocketTimer();
+
+ // Scans the group map for groups which have an available socket slot and
+ // at least one pending request. Returns true if any groups are stalled, and
+ // if so (and if both |group| and |group_name| are not NULL), fills |group|
+ // and |group_name| with data of the stalled group having highest priority.
+ bool FindTopStalledGroup(Group** group, std::string* group_name) const;
+
+ // Called when timer_ fires. This method scans the idle sockets removing
+ // sockets that timed out or can't be reused.
+ void OnCleanupTimerFired() {
+ CleanupIdleSockets(false);
+ }
+
+ // Removes |job| from |group|, which must already own |job|.
+ void RemoveConnectJob(ConnectJob* job, Group* group);
+
+ // Tries to see if we can handle any more requests for |group|.
+ void OnAvailableSocketSlot(const std::string& group_name, Group* group);
+
+ // Process a pending socket request for a group.
+ void ProcessPendingRequest(const std::string& group_name, Group* group);
+
+ // Assigns |socket| to |handle| and updates |group|'s counters appropriately.
+ void HandOutSocket(scoped_ptr<StreamSocket> socket,
+ bool reused,
+ const LoadTimingInfo::ConnectTiming& connect_timing,
+ ClientSocketHandle* handle,
+ base::TimeDelta time_idle,
+ Group* group,
+ const BoundNetLog& net_log);
+
+ // Adds |socket| to the list of idle sockets for |group|.
+ void AddIdleSocket(scoped_ptr<StreamSocket> socket, Group* group);
+
+ // Iterates through |group_map_|, canceling all ConnectJobs and deleting
+ // groups if they are no longer needed.
+ void CancelAllConnectJobs();
+
+ // Iterates through |group_map_|, posting |error| callbacks for all
+ // requests, and then deleting groups if they are no longer needed.
+ void CancelAllRequestsWithError(int error);
+
+ // Returns true if we can't create any more sockets due to the total limit.
+ bool ReachedMaxSocketsLimit() const;
+
+ // This is the internal implementation of RequestSocket(). It differs in that
+ // it does not handle logging into NetLog of the queueing status of
+ // |request|.
+ int RequestSocketInternal(const std::string& group_name,
+ const Request* request);
+
+ // Assigns an idle socket for the group to the request.
+ // Returns |true| if an idle socket is available, false otherwise.
+ bool AssignIdleSocketToRequest(const Request* request, Group* group);
+
+ static void LogBoundConnectJobToRequest(
+ const NetLog::Source& connect_job_source, const Request* request);
+
+ // Same as CloseOneIdleSocket() except it won't close an idle socket in
+ // |group|. If |group| is NULL, it is ignored. Returns true if it closed a
+ // socket.
+ bool CloseOneIdleSocketExceptInGroup(const Group* group);
+
+ // Checks if there are stalled socket groups that should be notified
+ // for possible wakeup.
+ void CheckForStalledSocketGroups();
+
+ // Posts a task to call InvokeUserCallback() on the next iteration through the
+ // current message loop. Inserts |callback| into |pending_callback_map_|,
+ // keyed by |handle|.
+ void InvokeUserCallbackLater(
+ ClientSocketHandle* handle, const CompletionCallback& callback, int rv);
+
+ // Invokes the user callback for |handle|. By the time this task has run,
+ // it's possible that the request has been cancelled, so |handle| may not
+ // exist in |pending_callback_map_|. We look up the callback and result code
+ // in |pending_callback_map_|.
+ void InvokeUserCallback(ClientSocketHandle* handle);
+
+ // Tries to close idle sockets in a higher level socket pool as long as this
+ // this pool is stalled.
+ void TryToCloseSocketsInLayeredPools();
+
+ GroupMap group_map_;
+
+ // Map of the ClientSocketHandles for which we have a pending Task to invoke a
+ // callback. This is necessary since, before we invoke said callback, it's
+ // possible that the request is cancelled.
+ PendingCallbackMap pending_callback_map_;
+
+ // Timer used to periodically prune idle sockets that timed out or can't be
+ // reused.
+ base::RepeatingTimer<ClientSocketPoolBaseHelper> timer_;
+
+ // The total number of idle sockets in the system.
+ int idle_socket_count_;
+
+ // Number of connecting sockets across all groups.
+ int connecting_socket_count_;
+
+ // Number of connected sockets we handed out across all groups.
+ int handed_out_socket_count_;
+
+ // The maximum total number of sockets. See ReachedMaxSocketsLimit.
+ const int max_sockets_;
+
+ // The maximum number of sockets kept per group.
+ const int max_sockets_per_group_;
+
+ // Whether to use timer to cleanup idle sockets.
+ bool use_cleanup_timer_;
+
+ // The time to wait until closing idle sockets.
+ const base::TimeDelta unused_idle_socket_timeout_;
+ const base::TimeDelta used_idle_socket_timeout_;
+
+ const scoped_ptr<ConnectJobFactory> connect_job_factory_;
+
+ // TODO(vandebo) Remove when backup jobs move to TransportClientSocketPool
+ bool connect_backup_jobs_enabled_;
+
+ // A unique id for the pool. It gets incremented every time we
+ // FlushWithError() the pool. This is so that when sockets get released back
+ // to the pool, we can make sure that they are discarded rather than reused.
+ int pool_generation_number_;
+
+ std::set<LayeredPool*> higher_layer_pools_;
+
+ base::WeakPtrFactory<ClientSocketPoolBaseHelper> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolBaseHelper);
+};
+
+} // namespace internal
+
+template <typename SocketParams>
+class ClientSocketPoolBase {
+ public:
+ class Request : public internal::ClientSocketPoolBaseHelper::Request {
+ public:
+ Request(ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ RequestPriority priority,
+ internal::ClientSocketPoolBaseHelper::Flags flags,
+ bool ignore_limits,
+ const scoped_refptr<SocketParams>& params,
+ const BoundNetLog& net_log)
+ : internal::ClientSocketPoolBaseHelper::Request(
+ handle, callback, priority, ignore_limits, flags, net_log),
+ params_(params) {}
+
+ const scoped_refptr<SocketParams>& params() const { return params_; }
+
+ private:
+ const scoped_refptr<SocketParams> params_;
+ };
+
+ class ConnectJobFactory {
+ public:
+ ConnectJobFactory() {}
+ virtual ~ConnectJobFactory() {}
+
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const Request& request,
+ ConnectJob::Delegate* delegate) const = 0;
+
+ virtual base::TimeDelta ConnectionTimeout() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory);
+ };
+
+ // |max_sockets| is the maximum number of sockets to be maintained by this
+ // ClientSocketPool. |max_sockets_per_group| specifies the maximum number of
+ // sockets a "group" can have. |unused_idle_socket_timeout| specifies how
+ // long to leave an unused idle socket open before closing it.
+ // |used_idle_socket_timeout| specifies how long to leave a previously used
+ // idle socket open before closing it.
+ ClientSocketPoolBase(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ base::TimeDelta unused_idle_socket_timeout,
+ base::TimeDelta used_idle_socket_timeout,
+ ConnectJobFactory* connect_job_factory)
+ : histograms_(histograms),
+ helper_(max_sockets, max_sockets_per_group,
+ unused_idle_socket_timeout, used_idle_socket_timeout,
+ new ConnectJobFactoryAdaptor(connect_job_factory)) {}
+
+ virtual ~ClientSocketPoolBase() {}
+
+ // These member functions simply forward to ClientSocketPoolBaseHelper.
+ void AddLayeredPool(LayeredPool* pool) {
+ helper_.AddLayeredPool(pool);
+ }
+
+ void RemoveLayeredPool(LayeredPool* pool) {
+ helper_.RemoveLayeredPool(pool);
+ }
+
+ // RequestSocket bundles up the parameters into a Request and then forwards to
+ // ClientSocketPoolBaseHelper::RequestSocket().
+ int RequestSocket(const std::string& group_name,
+ const scoped_refptr<SocketParams>& params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ Request* request =
+ new Request(handle, callback, priority,
+ internal::ClientSocketPoolBaseHelper::NORMAL,
+ params->ignore_limits(),
+ params, net_log);
+ return helper_.RequestSocket(group_name, request);
+ }
+
+ // RequestSockets bundles up the parameters into a Request and then forwards
+ // to ClientSocketPoolBaseHelper::RequestSockets(). Note that it assigns the
+ // priority to DEFAULT_PRIORITY and specifies the NO_IDLE_SOCKETS flag.
+ void RequestSockets(const std::string& group_name,
+ const scoped_refptr<SocketParams>& params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ const Request request(NULL /* no handle */,
+ CompletionCallback(),
+ DEFAULT_PRIORITY,
+ internal::ClientSocketPoolBaseHelper::NO_IDLE_SOCKETS,
+ params->ignore_limits(),
+ params,
+ net_log);
+ helper_.RequestSockets(group_name, request, num_sockets);
+ }
+
+ void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ return helper_.CancelRequest(group_name, handle);
+ }
+
+ void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ return helper_.ReleaseSocket(group_name, socket.Pass(), id);
+ }
+
+ void FlushWithError(int error) { helper_.FlushWithError(error); }
+
+ bool IsStalled() const { return helper_.IsStalled(); }
+
+ void CloseIdleSockets() { return helper_.CloseIdleSockets(); }
+
+ int idle_socket_count() const { return helper_.idle_socket_count(); }
+
+ int IdleSocketCountInGroup(const std::string& group_name) const {
+ return helper_.IdleSocketCountInGroup(group_name);
+ }
+
+ LoadState GetLoadState(const std::string& group_name,
+ const ClientSocketHandle* handle) const {
+ return helper_.GetLoadState(group_name, handle);
+ }
+
+ virtual void OnConnectJobComplete(int result, ConnectJob* job) {
+ return helper_.OnConnectJobComplete(result, job);
+ }
+
+ int NumUnassignedConnectJobsInGroup(const std::string& group_name) const {
+ return helper_.NumUnassignedConnectJobsInGroup(group_name);
+ }
+
+ int NumConnectJobsInGroup(const std::string& group_name) const {
+ return helper_.NumConnectJobsInGroup(group_name);
+ }
+
+ int NumActiveSocketsInGroup(const std::string& group_name) const {
+ return helper_.NumActiveSocketsInGroup(group_name);
+ }
+
+ bool HasGroup(const std::string& group_name) const {
+ return helper_.HasGroup(group_name);
+ }
+
+ void CleanupIdleSockets(bool force) {
+ return helper_.CleanupIdleSockets(force);
+ }
+
+ base::DictionaryValue* GetInfoAsValue(const std::string& name,
+ const std::string& type) const {
+ return helper_.GetInfoAsValue(name, type);
+ }
+
+ base::TimeDelta ConnectionTimeout() const {
+ return helper_.ConnectionTimeout();
+ }
+
+ ClientSocketPoolHistograms* histograms() const {
+ return histograms_;
+ }
+
+ void EnableConnectBackupJobs() { helper_.EnableConnectBackupJobs(); }
+
+ bool CloseOneIdleSocket() { return helper_.CloseOneIdleSocket(); }
+
+ bool CloseOneIdleConnectionInLayeredPool() {
+ return helper_.CloseOneIdleConnectionInLayeredPool();
+ }
+
+ private:
+ // This adaptor class exists to bridge the
+ // internal::ClientSocketPoolBaseHelper::ConnectJobFactory and
+ // ClientSocketPoolBase::ConnectJobFactory types, allowing clients to use the
+ // typesafe ClientSocketPoolBase::ConnectJobFactory, rather than having to
+ // static_cast themselves.
+ class ConnectJobFactoryAdaptor
+ : public internal::ClientSocketPoolBaseHelper::ConnectJobFactory {
+ public:
+ typedef typename ClientSocketPoolBase<SocketParams>::ConnectJobFactory
+ ConnectJobFactory;
+
+ explicit ConnectJobFactoryAdaptor(ConnectJobFactory* connect_job_factory)
+ : connect_job_factory_(connect_job_factory) {}
+ virtual ~ConnectJobFactoryAdaptor() {}
+
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const internal::ClientSocketPoolBaseHelper::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE {
+ const Request& casted_request = static_cast<const Request&>(request);
+ return connect_job_factory_->NewConnectJob(
+ group_name, casted_request, delegate);
+ }
+
+ virtual base::TimeDelta ConnectionTimeout() const {
+ return connect_job_factory_->ConnectionTimeout();
+ }
+
+ const scoped_ptr<ConnectJobFactory> connect_job_factory_;
+ };
+
+ // Histograms for the pool
+ ClientSocketPoolHistograms* const histograms_;
+ internal::ClientSocketPoolBaseHelper helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolBase);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_BASE_H_
diff --git a/chromium/net/socket/client_socket_pool_base_unittest.cc b/chromium/net/socket/client_socket_pool_base_unittest.cc
new file mode 100644
index 00000000000..6688e01244d
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_base_unittest.cc
@@ -0,0 +1,4168 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_pool_base.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/platform_thread.h"
+#include "base/values.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/udp/datagram_client_socket.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace net {
+
+namespace {
+
+const int kDefaultMaxSockets = 4;
+const int kDefaultMaxSocketsPerGroup = 2;
+const net::RequestPriority kDefaultPriority = MEDIUM;
+
+// Make sure |handle| sets load times correctly when it has been assigned a
+// reused socket.
+void TestLoadTimingInfoConnectedReused(const ClientSocketHandle& handle) {
+ LoadTimingInfo load_timing_info;
+ // Only pass true in as |is_reused|, as in general, HttpStream types should
+ // have stricter concepts of reuse than socket pools.
+ EXPECT_TRUE(handle.GetLoadTimingInfo(true, &load_timing_info));
+
+ EXPECT_EQ(true, load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+// Make sure |handle| sets load times correctly when it has been assigned a
+// fresh socket. Also runs TestLoadTimingInfoConnectedReused, since the owner
+// of a connection where |is_reused| is false may consider the connection
+// reused.
+void TestLoadTimingInfoConnectedNotReused(const ClientSocketHandle& handle) {
+ EXPECT_FALSE(handle.is_reused());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+
+ TestLoadTimingInfoConnectedReused(handle);
+}
+
+// Make sure |handle| sets load times correctly, in the case that it does not
+// currently have a socket.
+void TestLoadTimingInfoNotConnected(const ClientSocketHandle& handle) {
+ // Should only be set to true once a socket is assigned, if at all.
+ EXPECT_FALSE(handle.is_reused());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_FALSE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_EQ(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+class TestSocketParams : public base::RefCounted<TestSocketParams> {
+ public:
+ TestSocketParams() : ignore_limits_(false) {}
+
+ void set_ignore_limits(bool ignore_limits) {
+ ignore_limits_ = ignore_limits;
+ }
+ bool ignore_limits() { return ignore_limits_; }
+
+ private:
+ friend class base::RefCounted<TestSocketParams>;
+ ~TestSocketParams() {}
+
+ bool ignore_limits_;
+};
+typedef ClientSocketPoolBase<TestSocketParams> TestClientSocketPoolBase;
+
+class MockClientSocket : public StreamSocket {
+ public:
+ explicit MockClientSocket(net::NetLog* net_log)
+ : connected_(false),
+ net_log_(BoundNetLog::Make(net_log, net::NetLog::SOURCE_SOCKET)),
+ was_used_to_convey_data_(false) {
+ }
+
+ // Socket implementation.
+ virtual int Read(
+ IOBuffer* /* buf */, int len,
+ const CompletionCallback& /* callback */) OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int Write(
+ IOBuffer* /* buf */, int len,
+ const CompletionCallback& /* callback */) OVERRIDE {
+ was_used_to_convey_data_ = true;
+ return len;
+ }
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE { return true; }
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE { return true; }
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ connected_ = true;
+ return OK;
+ }
+
+ virtual void Disconnect() OVERRIDE { connected_ = false; }
+ virtual bool IsConnected() const OVERRIDE { return connected_; }
+ virtual bool IsConnectedAndIdle() const OVERRIDE { return connected_; }
+
+ virtual int GetPeerAddress(IPEndPoint* /* address */) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual int GetLocalAddress(IPEndPoint* /* address */) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+ virtual bool WasEverUsed() const OVERRIDE {
+ return was_used_to_convey_data_;
+ }
+ virtual bool UsingTCPFastOpen() const OVERRIDE { return false; }
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return false;
+ }
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return false;
+ }
+
+ private:
+ bool connected_;
+ BoundNetLog net_log_;
+ bool was_used_to_convey_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockClientSocket);
+};
+
+class TestConnectJob;
+
+class MockClientSocketFactory : public ClientSocketFactory {
+ public:
+ MockClientSocketFactory() : allocation_count_(0) {}
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE {
+ NOTREACHED();
+ return scoped_ptr<DatagramClientSocket>();
+ }
+
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* /* net_log */,
+ const NetLog::Source& /*source*/) OVERRIDE {
+ allocation_count_++;
+ return scoped_ptr<StreamSocket>();
+ }
+
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLClientSocket>();
+ }
+
+ virtual void ClearSSLSessionCache() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ void WaitForSignal(TestConnectJob* job) { waiting_jobs_.push_back(job); }
+
+ void SignalJobs();
+
+ void SignalJob(size_t job);
+
+ void SetJobLoadState(size_t job, LoadState load_state);
+
+ int allocation_count() const { return allocation_count_; }
+
+ private:
+ int allocation_count_;
+ std::vector<TestConnectJob*> waiting_jobs_;
+};
+
+class TestConnectJob : public ConnectJob {
+ public:
+ enum JobType {
+ kMockJob,
+ kMockFailingJob,
+ kMockPendingJob,
+ kMockPendingFailingJob,
+ kMockWaitingJob,
+ kMockRecoverableJob,
+ kMockPendingRecoverableJob,
+ kMockAdditionalErrorStateJob,
+ kMockPendingAdditionalErrorStateJob,
+ };
+
+ // The kMockPendingJob uses a slight delay before allowing the connect
+ // to complete.
+ static const int kPendingConnectDelay = 2;
+
+ TestConnectJob(JobType job_type,
+ const std::string& group_name,
+ const TestClientSocketPoolBase::Request& request,
+ base::TimeDelta timeout_duration,
+ ConnectJob::Delegate* delegate,
+ MockClientSocketFactory* client_socket_factory,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ job_type_(job_type),
+ client_socket_factory_(client_socket_factory),
+ weak_factory_(this),
+ load_state_(LOAD_STATE_IDLE),
+ store_additional_error_state_(false) {}
+
+ void Signal() {
+ DoConnect(waiting_success_, true /* async */, false /* recoverable */);
+ }
+
+ void set_load_state(LoadState load_state) { load_state_ = load_state; }
+
+ // From ConnectJob:
+
+ virtual LoadState GetLoadState() const OVERRIDE { return load_state_; }
+
+ virtual void GetAdditionalErrorState(ClientSocketHandle* handle) OVERRIDE {
+ if (store_additional_error_state_) {
+ // Set all of the additional error state fields in some way.
+ handle->set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders(std::string());
+ handle->set_ssl_error_response_info(info);
+ }
+ }
+
+ private:
+ // From ConnectJob:
+
+ virtual int ConnectInternal() OVERRIDE {
+ AddressList ignored;
+ client_socket_factory_->CreateTransportClientSocket(
+ ignored, NULL, net::NetLog::Source());
+ SetSocket(
+ scoped_ptr<StreamSocket>(new MockClientSocket(net_log().net_log())));
+ switch (job_type_) {
+ case kMockJob:
+ return DoConnect(true /* successful */, false /* sync */,
+ false /* recoverable */);
+ case kMockFailingJob:
+ return DoConnect(false /* error */, false /* sync */,
+ false /* recoverable */);
+ case kMockPendingJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+
+ // Depending on execution timings, posting a delayed task can result
+ // in the task getting executed the at the earliest possible
+ // opportunity or only after returning once from the message loop and
+ // then a second call into the message loop. In order to make behavior
+ // more deterministic, we change the default delay to 2ms. This should
+ // always require us to wait for the second call into the message loop.
+ //
+ // N.B. The correct fix for this and similar timing problems is to
+ // abstract time for the purpose of unittests. Unfortunately, we have
+ // a lot of third-party components that directly call the various
+ // time functions, so this change would be rather invasive.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&TestConnectJob::DoConnect),
+ weak_factory_.GetWeakPtr(),
+ true /* successful */,
+ true /* async */,
+ false /* recoverable */),
+ base::TimeDelta::FromMilliseconds(kPendingConnectDelay));
+ return ERR_IO_PENDING;
+ case kMockPendingFailingJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&TestConnectJob::DoConnect),
+ weak_factory_.GetWeakPtr(),
+ false /* error */,
+ true /* async */,
+ false /* recoverable */),
+ base::TimeDelta::FromMilliseconds(2));
+ return ERR_IO_PENDING;
+ case kMockWaitingJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+ client_socket_factory_->WaitForSignal(this);
+ waiting_success_ = true;
+ return ERR_IO_PENDING;
+ case kMockRecoverableJob:
+ return DoConnect(false /* error */, false /* sync */,
+ true /* recoverable */);
+ case kMockPendingRecoverableJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&TestConnectJob::DoConnect),
+ weak_factory_.GetWeakPtr(),
+ false /* error */,
+ true /* async */,
+ true /* recoverable */),
+ base::TimeDelta::FromMilliseconds(2));
+ return ERR_IO_PENDING;
+ case kMockAdditionalErrorStateJob:
+ store_additional_error_state_ = true;
+ return DoConnect(false /* error */, false /* sync */,
+ false /* recoverable */);
+ case kMockPendingAdditionalErrorStateJob:
+ set_load_state(LOAD_STATE_CONNECTING);
+ store_additional_error_state_ = true;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&TestConnectJob::DoConnect),
+ weak_factory_.GetWeakPtr(),
+ false /* error */,
+ true /* async */,
+ false /* recoverable */),
+ base::TimeDelta::FromMilliseconds(2));
+ return ERR_IO_PENDING;
+ default:
+ NOTREACHED();
+ SetSocket(scoped_ptr<StreamSocket>());
+ return ERR_FAILED;
+ }
+ }
+
+ int DoConnect(bool succeed, bool was_async, bool recoverable) {
+ int result = OK;
+ if (succeed) {
+ socket()->Connect(CompletionCallback());
+ } else if (recoverable) {
+ result = ERR_PROXY_AUTH_REQUESTED;
+ } else {
+ result = ERR_CONNECTION_FAILED;
+ SetSocket(scoped_ptr<StreamSocket>());
+ }
+
+ if (was_async)
+ NotifyDelegateOfCompletion(result);
+ return result;
+ }
+
+ bool waiting_success_;
+ const JobType job_type_;
+ MockClientSocketFactory* const client_socket_factory_;
+ base::WeakPtrFactory<TestConnectJob> weak_factory_;
+ LoadState load_state_;
+ bool store_additional_error_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConnectJob);
+};
+
+class TestConnectJobFactory
+ : public TestClientSocketPoolBase::ConnectJobFactory {
+ public:
+ TestConnectJobFactory(MockClientSocketFactory* client_socket_factory,
+ NetLog* net_log)
+ : job_type_(TestConnectJob::kMockJob),
+ job_types_(NULL),
+ client_socket_factory_(client_socket_factory),
+ net_log_(net_log) {
+ }
+
+ virtual ~TestConnectJobFactory() {}
+
+ void set_job_type(TestConnectJob::JobType job_type) { job_type_ = job_type; }
+
+ void set_job_types(std::list<TestConnectJob::JobType>* job_types) {
+ job_types_ = job_types;
+ CHECK(!job_types_->empty());
+ }
+
+ void set_timeout_duration(base::TimeDelta timeout_duration) {
+ timeout_duration_ = timeout_duration;
+ }
+
+ // ConnectJobFactory implementation.
+
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const TestClientSocketPoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE {
+ EXPECT_TRUE(!job_types_ || !job_types_->empty());
+ TestConnectJob::JobType job_type = job_type_;
+ if (job_types_ && !job_types_->empty()) {
+ job_type = job_types_->front();
+ job_types_->pop_front();
+ }
+ return scoped_ptr<ConnectJob>(new TestConnectJob(job_type,
+ group_name,
+ request,
+ timeout_duration_,
+ delegate,
+ client_socket_factory_,
+ net_log_));
+ }
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE {
+ return timeout_duration_;
+ }
+
+ private:
+ TestConnectJob::JobType job_type_;
+ std::list<TestConnectJob::JobType>* job_types_;
+ base::TimeDelta timeout_duration_;
+ MockClientSocketFactory* const client_socket_factory_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConnectJobFactory);
+};
+
+class TestClientSocketPool : public ClientSocketPool {
+ public:
+ TestClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ base::TimeDelta unused_idle_socket_timeout,
+ base::TimeDelta used_idle_socket_timeout,
+ TestClientSocketPoolBase::ConnectJobFactory* connect_job_factory)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ unused_idle_socket_timeout, used_idle_socket_timeout,
+ connect_job_factory) {}
+
+ virtual ~TestClientSocketPool() {}
+
+ virtual int RequestSocket(
+ const std::string& group_name,
+ const void* params,
+ net::RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE {
+ const scoped_refptr<TestSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<TestSocketParams>*>(params);
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+ }
+
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE {
+ const scoped_refptr<TestSocketParams>* casted_params =
+ static_cast<const scoped_refptr<TestSocketParams>*>(params);
+
+ base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
+ }
+
+ virtual void CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE {
+ base_.CancelRequest(group_name, handle);
+ }
+
+ virtual void ReleaseSocket(
+ const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE {
+ base_.ReleaseSocket(group_name, socket.Pass(), id);
+ }
+
+ virtual void FlushWithError(int error) OVERRIDE {
+ base_.FlushWithError(error);
+ }
+
+ virtual bool IsStalled() const OVERRIDE {
+ return base_.IsStalled();
+ }
+
+ virtual void CloseIdleSockets() OVERRIDE {
+ base_.CloseIdleSockets();
+ }
+
+ virtual int IdleSocketCount() const OVERRIDE {
+ return base_.idle_socket_count();
+ }
+
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE {
+ return base_.IdleSocketCountInGroup(group_name);
+ }
+
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE {
+ return base_.GetLoadState(group_name, handle);
+ }
+
+ virtual void AddLayeredPool(LayeredPool* pool) OVERRIDE {
+ base_.AddLayeredPool(pool);
+ }
+
+ virtual void RemoveLayeredPool(LayeredPool* pool) OVERRIDE {
+ base_.RemoveLayeredPool(pool);
+ }
+
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const OVERRIDE {
+ return base_.GetInfoAsValue(name, type);
+ }
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE {
+ return base_.ConnectionTimeout();
+ }
+
+ virtual ClientSocketPoolHistograms* histograms() const OVERRIDE {
+ return base_.histograms();
+ }
+
+ const TestClientSocketPoolBase* base() const { return &base_; }
+
+ int NumUnassignedConnectJobsInGroup(const std::string& group_name) const {
+ return base_.NumUnassignedConnectJobsInGroup(group_name);
+ }
+
+ int NumConnectJobsInGroup(const std::string& group_name) const {
+ return base_.NumConnectJobsInGroup(group_name);
+ }
+
+ int NumActiveSocketsInGroup(const std::string& group_name) const {
+ return base_.NumActiveSocketsInGroup(group_name);
+ }
+
+ bool HasGroup(const std::string& group_name) const {
+ return base_.HasGroup(group_name);
+ }
+
+ void CleanupTimedOutIdleSockets() { base_.CleanupIdleSockets(false); }
+
+ void EnableConnectBackupJobs() { base_.EnableConnectBackupJobs(); }
+
+ bool CloseOneIdleConnectionInLayeredPool() {
+ return base_.CloseOneIdleConnectionInLayeredPool();
+ }
+
+ private:
+ TestClientSocketPoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestClientSocketPool);
+};
+
+} // namespace
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(TestClientSocketPool, TestSocketParams);
+
+namespace {
+
+void MockClientSocketFactory::SignalJobs() {
+ for (std::vector<TestConnectJob*>::iterator it = waiting_jobs_.begin();
+ it != waiting_jobs_.end(); ++it) {
+ (*it)->Signal();
+ }
+ waiting_jobs_.clear();
+}
+
+void MockClientSocketFactory::SignalJob(size_t job) {
+ ASSERT_LT(job, waiting_jobs_.size());
+ waiting_jobs_[job]->Signal();
+ waiting_jobs_.erase(waiting_jobs_.begin() + job);
+}
+
+void MockClientSocketFactory::SetJobLoadState(size_t job,
+ LoadState load_state) {
+ ASSERT_LT(job, waiting_jobs_.size());
+ waiting_jobs_[job]->set_load_state(load_state);
+}
+
+class TestConnectJobDelegate : public ConnectJob::Delegate {
+ public:
+ TestConnectJobDelegate()
+ : have_result_(false), waiting_for_result_(false), result_(OK) {}
+ virtual ~TestConnectJobDelegate() {}
+
+ virtual void OnConnectJobComplete(int result, ConnectJob* job) OVERRIDE {
+ result_ = result;
+ scoped_ptr<ConnectJob> owned_job(job);
+ scoped_ptr<StreamSocket> socket = owned_job->PassSocket();
+ // socket.get() should be NULL iff result != OK
+ EXPECT_EQ(socket == NULL, result != OK);
+ have_result_ = true;
+ if (waiting_for_result_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ int WaitForResult() {
+ DCHECK(!waiting_for_result_);
+ while (!have_result_) {
+ waiting_for_result_ = true;
+ base::MessageLoop::current()->Run();
+ waiting_for_result_ = false;
+ }
+ have_result_ = false; // auto-reset for next callback
+ return result_;
+ }
+
+ private:
+ bool have_result_;
+ bool waiting_for_result_;
+ int result_;
+};
+
+class ClientSocketPoolBaseTest : public testing::Test {
+ protected:
+ ClientSocketPoolBaseTest()
+ : params_(new TestSocketParams()),
+ histograms_("ClientSocketPoolTest") {
+ connect_backup_jobs_enabled_ =
+ internal::ClientSocketPoolBaseHelper::connect_backup_jobs_enabled();
+ internal::ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(true);
+ cleanup_timer_enabled_ =
+ internal::ClientSocketPoolBaseHelper::cleanup_timer_enabled();
+ }
+
+ virtual ~ClientSocketPoolBaseTest() {
+ internal::ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(
+ connect_backup_jobs_enabled_);
+ internal::ClientSocketPoolBaseHelper::set_cleanup_timer_enabled(
+ cleanup_timer_enabled_);
+ }
+
+ void CreatePool(int max_sockets, int max_sockets_per_group) {
+ CreatePoolWithIdleTimeouts(
+ max_sockets,
+ max_sockets_per_group,
+ ClientSocketPool::unused_idle_socket_timeout(),
+ ClientSocketPool::used_idle_socket_timeout());
+ }
+
+ void CreatePoolWithIdleTimeouts(
+ int max_sockets, int max_sockets_per_group,
+ base::TimeDelta unused_idle_socket_timeout,
+ base::TimeDelta used_idle_socket_timeout) {
+ DCHECK(!pool_.get());
+ connect_job_factory_ = new TestConnectJobFactory(&client_socket_factory_,
+ &net_log_);
+ pool_.reset(new TestClientSocketPool(max_sockets,
+ max_sockets_per_group,
+ &histograms_,
+ unused_idle_socket_timeout,
+ used_idle_socket_timeout,
+ connect_job_factory_));
+ }
+
+ int StartRequestWithParams(
+ const std::string& group_name,
+ RequestPriority priority,
+ const scoped_refptr<TestSocketParams>& params) {
+ return test_base_.StartRequestUsingPool<
+ TestClientSocketPool, TestSocketParams>(
+ pool_.get(), group_name, priority, params);
+ }
+
+ int StartRequest(const std::string& group_name, RequestPriority priority) {
+ return StartRequestWithParams(group_name, priority, params_);
+ }
+
+ int GetOrderOfRequest(size_t index) const {
+ return test_base_.GetOrderOfRequest(index);
+ }
+
+ bool ReleaseOneConnection(ClientSocketPoolTest::KeepAlive keep_alive) {
+ return test_base_.ReleaseOneConnection(keep_alive);
+ }
+
+ void ReleaseAllConnections(ClientSocketPoolTest::KeepAlive keep_alive) {
+ test_base_.ReleaseAllConnections(keep_alive);
+ }
+
+ TestSocketRequest* request(int i) { return test_base_.request(i); }
+ size_t requests_size() const { return test_base_.requests_size(); }
+ ScopedVector<TestSocketRequest>* requests() { return test_base_.requests(); }
+ size_t completion_count() const { return test_base_.completion_count(); }
+
+ CapturingNetLog net_log_;
+ bool connect_backup_jobs_enabled_;
+ bool cleanup_timer_enabled_;
+ MockClientSocketFactory client_socket_factory_;
+ TestConnectJobFactory* connect_job_factory_;
+ scoped_refptr<TestSocketParams> params_;
+ ClientSocketPoolHistograms histograms_;
+ scoped_ptr<TestClientSocketPool> pool_;
+ ClientSocketPoolTest test_base_;
+};
+
+// Even though a timeout is specified, it doesn't time out on a synchronous
+// completion.
+TEST_F(ClientSocketPoolBaseTest, ConnectJob_NoTimeoutOnSynchronousCompletion) {
+ TestConnectJobDelegate delegate;
+ ClientSocketHandle ignored;
+ TestClientSocketPoolBase::Request request(
+ &ignored, CompletionCallback(), kDefaultPriority,
+ internal::ClientSocketPoolBaseHelper::NORMAL,
+ false, params_, BoundNetLog());
+ scoped_ptr<TestConnectJob> job(
+ new TestConnectJob(TestConnectJob::kMockJob,
+ "a",
+ request,
+ base::TimeDelta::FromMicroseconds(1),
+ &delegate,
+ &client_socket_factory_,
+ NULL));
+ EXPECT_EQ(OK, job->Connect());
+}
+
+TEST_F(ClientSocketPoolBaseTest, ConnectJob_TimedOut) {
+ TestConnectJobDelegate delegate;
+ ClientSocketHandle ignored;
+ CapturingNetLog log;
+
+ TestClientSocketPoolBase::Request request(
+ &ignored, CompletionCallback(), kDefaultPriority,
+ internal::ClientSocketPoolBaseHelper::NORMAL,
+ false, params_, BoundNetLog());
+ // Deleted by TestConnectJobDelegate.
+ TestConnectJob* job =
+ new TestConnectJob(TestConnectJob::kMockPendingJob,
+ "a",
+ request,
+ base::TimeDelta::FromMicroseconds(1),
+ &delegate,
+ &client_socket_factory_,
+ &log);
+ ASSERT_EQ(ERR_IO_PENDING, job->Connect());
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+ EXPECT_EQ(ERR_TIMED_OUT, delegate.WaitForResult());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(6u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 2, NetLog::TYPE_CONNECT_JOB_SET_SOCKET,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 3, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 4, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 5, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
+}
+
+TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ CapturingBoundNetLog log;
+ TestLoadTimingInfoNotConnected(handle);
+
+ EXPECT_EQ(OK,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ log.bound()));
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoConnectedNotReused(handle);
+
+ handle.Reset();
+ TestLoadTimingInfoNotConnected(handle);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(4u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 3, NetLog::TYPE_SOCKET_POOL));
+}
+
+TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob);
+ CapturingBoundNetLog log;
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ // Set the additional error state members to ensure that they get cleared.
+ handle.set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders(std::string());
+ handle.set_ssl_error_response_info(info);
+ EXPECT_EQ(ERR_CONNECTION_FAILED,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ log.bound()));
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+ EXPECT_TRUE(handle.ssl_error_response_info().headers.get() == NULL);
+ TestLoadTimingInfoNotConnected(handle);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(3u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_SOCKET_POOL));
+}
+
+TEST_F(ClientSocketPoolBaseTest, TotalLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ // TODO(eroman): Check that the NetLog contains this event.
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("c", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("d", kDefaultPriority));
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("f", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("g", kDefaultPriority));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+ EXPECT_EQ(6, GetOrderOfRequest(6));
+ EXPECT_EQ(7, GetOrderOfRequest(7));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8));
+}
+
+TEST_F(ClientSocketPoolBaseTest, TotalLimitReachedNewGroup) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ // TODO(eroman): Check that the NetLog contains this event.
+
+ // Reach all limits: max total sockets, and max sockets per group.
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ // Now create a new group and verify that we don't starve it.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(6));
+}
+
+TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsPriority) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("b", LOWEST));
+ EXPECT_EQ(OK, StartRequest("a", MEDIUM));
+ EXPECT_EQ(OK, StartRequest("b", HIGHEST));
+ EXPECT_EQ(OK, StartRequest("a", LOWEST));
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ // First 4 requests don't have to wait, and finish in order.
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+
+ // Request ("b", HIGHEST) has the highest priority, then ("a", MEDIUM),
+ // and then ("c", LOWEST).
+ EXPECT_EQ(7, GetOrderOfRequest(5));
+ EXPECT_EQ(6, GetOrderOfRequest(6));
+ EXPECT_EQ(5, GetOrderOfRequest(7));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(9));
+}
+
+TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsGroupLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("a", LOWEST));
+ EXPECT_EQ(OK, StartRequest("a", LOW));
+ EXPECT_EQ(OK, StartRequest("b", HIGHEST));
+ EXPECT_EQ(OK, StartRequest("b", MEDIUM));
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count());
+
+ // First 4 requests don't have to wait, and finish in order.
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+
+ // Request ("b", 7) has the highest priority, but we can't make new socket for
+ // group "b", because it has reached the per-group limit. Then we make
+ // socket for ("c", 6), because it has higher priority than ("a", 4),
+ // and we still can't make a socket for group "b".
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+ EXPECT_EQ(6, GetOrderOfRequest(6));
+ EXPECT_EQ(7, GetOrderOfRequest(7));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8));
+}
+
+// Make sure that we count connecting sockets against the total limit.
+TEST_F(ClientSocketPoolBaseTest, TotalLimitCountsConnectingSockets) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("c", kDefaultPriority));
+
+ // Create one asynchronous request.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("d", kDefaultPriority));
+
+ // We post all of our delayed tasks with a 2ms delay. I.e. they don't
+ // actually become pending until 2ms after they have been created. In order
+ // to flush all tasks, we need to wait so that we know there are no
+ // soon-to-be-pending tasks waiting.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The next synchronous request should wait for its turn.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(6));
+}
+
+TEST_F(ClientSocketPoolBaseTest, CorrectlyCountStalledGroups) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count());
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count());
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+ EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count());
+}
+
+TEST_F(ClientSocketPoolBaseTest, StallAndThenCancelAndTriggerAvailableSocket) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ClientSocketHandle handles[4];
+ for (size_t i = 0; i < arraysize(handles); ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handles[i].Init("b",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // One will be stalled, cancel all the handles now.
+ // This should hit the OnAvailableSocketSlot() code where we previously had
+ // stalled groups, but no longer have any.
+ for (size_t i = 0; i < arraysize(handles); ++i)
+ handles[i].Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelStalledSocketAtSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ {
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ TestCompletionCallback callbacks[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ EXPECT_EQ(OK, handles[i].Init(base::IntToString(i),
+ params_,
+ kDefaultPriority,
+ callbacks[i].callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // Force a stalled group.
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ // Cancel the stalled request.
+ stalled_handle.Reset();
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+
+ // Dropping out of scope will close all handles and return them to idle.
+ }
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(kDefaultMaxSockets, pool_->IdleSocketCount());
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelPendingSocketAtSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ {
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handles[i].Init(base::IntToString(i),
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // Force a stalled group.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ // Since it is stalled, it should have no connect jobs.
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("foo"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("foo"));
+
+ // Cancel the stalled request.
+ handles[0].Reset();
+
+ // Now we should have a connect job.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("foo"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("foo"));
+
+ // The stalled socket should connect.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(kDefaultMaxSockets + 1,
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("foo"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("foo"));
+
+ // Dropping out of scope will close all handles and return them to idle.
+ }
+
+ EXPECT_EQ(1, pool_->IdleSocketCount());
+}
+
+TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ ClientSocketHandle stalled_handle;
+ TestCompletionCallback callback;
+ {
+ EXPECT_FALSE(pool_->IsStalled());
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handles[i].Init(base::StringPrintf(
+ "Take 2: %d", i),
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_FALSE(pool_->IsStalled());
+
+ // Now we will hit the socket limit.
+ EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_TRUE(pool_->IsStalled());
+
+ // Dropping out of scope will close all handles and return them to idle.
+ }
+
+ // But if we wait for it, the released idle sockets will be closed in
+ // preference of the waiting request.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count());
+ EXPECT_EQ(3, pool_->IdleSocketCount());
+}
+
+// Regression test for http://crbug.com/40952.
+TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketAtSocketLimitDeleteGroup) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ pool_->EnableConnectBackupJobs();
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ for (int i = 0; i < kDefaultMaxSockets; ++i) {
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handle.Init(base::IntToString(i),
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // Flush all the DoReleaseSocket tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Stall a group. Set a pending job so it'll trigger a backup job if we don't
+ // reuse a socket.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+
+ // "0" is special here, since it should be the first entry in the sorted map,
+ // which is the one which we would close an idle socket for. We shouldn't
+ // close an idle socket though, since we should reuse the idle socket.
+ EXPECT_EQ(OK, handle.Init("0",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+ EXPECT_EQ(kDefaultMaxSockets - 1, pool_->IdleSocketCount());
+}
+
+TEST_F(ClientSocketPoolBaseTest, PendingRequests) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", IDLE));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+
+ ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE);
+
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup,
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup,
+ completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(8, GetOrderOfRequest(3));
+ EXPECT_EQ(6, GetOrderOfRequest(4));
+ EXPECT_EQ(4, GetOrderOfRequest(5));
+ EXPECT_EQ(3, GetOrderOfRequest(6));
+ EXPECT_EQ(5, GetOrderOfRequest(7));
+ EXPECT_EQ(7, GetOrderOfRequest(8));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(9));
+}
+
+TEST_F(ClientSocketPoolBaseTest, PendingRequests_NoKeepAlive) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_size(); ++i)
+ EXPECT_EQ(OK, request(i)->WaitForResult());
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup,
+ completion_count());
+}
+
+// This test will start up a RequestSocket() and then immediately Cancel() it.
+// The pending connect job will be cancelled and should not call back into
+// ClientSocketPoolBase.
+TEST_F(ClientSocketPoolBaseTest, CancelRequestClearGroup) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ handle.Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, ConnectCancelConnect) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ handle.Reset();
+
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(callback.have_result());
+
+ handle.Reset();
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelRequest) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+
+ // Cancel a request.
+ size_t index_to_cancel = kDefaultMaxSocketsPerGroup + 2;
+ EXPECT_FALSE((*requests())[index_to_cancel]->handle()->is_initialized());
+ (*requests())[index_to_cancel]->handle()->Reset();
+
+ ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE);
+
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup,
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup - 1,
+ completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(5, GetOrderOfRequest(3));
+ EXPECT_EQ(3, GetOrderOfRequest(4));
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound,
+ GetOrderOfRequest(5)); // Canceled request.
+ EXPECT_EQ(4, GetOrderOfRequest(6));
+ EXPECT_EQ(6, GetOrderOfRequest(7));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8));
+}
+
+class RequestSocketCallback : public TestCompletionCallbackBase {
+ public:
+ RequestSocketCallback(ClientSocketHandle* handle,
+ TestClientSocketPool* pool,
+ TestConnectJobFactory* test_connect_job_factory,
+ TestConnectJob::JobType next_job_type)
+ : handle_(handle),
+ pool_(pool),
+ within_callback_(false),
+ test_connect_job_factory_(test_connect_job_factory),
+ next_job_type_(next_job_type),
+ callback_(base::Bind(&RequestSocketCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~RequestSocketCallback() {}
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ SetResult(result);
+ ASSERT_EQ(OK, result);
+
+ if (!within_callback_) {
+ test_connect_job_factory_->set_job_type(next_job_type_);
+
+ // Don't allow reuse of the socket. Disconnect it and then release it and
+ // run through the MessageLoop once to get it completely released.
+ handle_->socket()->Disconnect();
+ handle_->Reset();
+ {
+ // TODO: Resolve conflicting intentions of stopping recursion with the
+ // |!within_callback_| test (above) and the call to |RunUntilIdle()|
+ // below. http://crbug.com/114130.
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ within_callback_ = true;
+ TestCompletionCallback next_job_callback;
+ scoped_refptr<TestSocketParams> params(new TestSocketParams());
+ int rv = handle_->Init("a",
+ params,
+ kDefaultPriority,
+ next_job_callback.callback(),
+ pool_,
+ BoundNetLog());
+ switch (next_job_type_) {
+ case TestConnectJob::kMockJob:
+ EXPECT_EQ(OK, rv);
+ break;
+ case TestConnectJob::kMockPendingJob:
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // For pending jobs, wait for new socket to be created. This makes
+ // sure there are no more pending operations nor any unclosed sockets
+ // when the test finishes.
+ // We need to give it a little bit of time to run, so that all the
+ // operations that happen on timers (e.g. cleanup of idle
+ // connections) can execute.
+ {
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ EXPECT_EQ(OK, next_job_callback.WaitForResult());
+ }
+ break;
+ default:
+ FAIL() << "Unexpected job type: " << next_job_type_;
+ break;
+ }
+ }
+ }
+
+ ClientSocketHandle* const handle_;
+ TestClientSocketPool* const pool_;
+ bool within_callback_;
+ TestConnectJobFactory* const test_connect_job_factory_;
+ TestConnectJob::JobType next_job_type_;
+ CompletionCallback callback_;
+};
+
+TEST_F(ClientSocketPoolBaseTest, RequestPendingJobTwice) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ RequestSocketCallback callback(
+ &handle, pool_.get(), connect_job_factory_,
+ TestConnectJob::kMockPendingJob);
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestPendingJobThenSynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ RequestSocketCallback callback(
+ &handle, pool_.get(), connect_job_factory_, TestConnectJob::kMockJob);
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Make sure that pending requests get serviced after active requests get
+// cancelled.
+TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestWithPendingRequests) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ // Now, kDefaultMaxSocketsPerGroup requests should be active.
+ // Let's cancel them.
+ for (int i = 0; i < kDefaultMaxSocketsPerGroup; ++i) {
+ ASSERT_FALSE(request(i)->handle()->is_initialized());
+ request(i)->handle()->Reset();
+ }
+
+ // Let's wait for the rest to complete now.
+ for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_size(); ++i) {
+ EXPECT_EQ(OK, request(i)->WaitForResult());
+ request(i)->handle()->Reset();
+ }
+
+ EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup,
+ completion_count());
+}
+
+// Make sure that pending requests get serviced after active requests fail.
+TEST_F(ClientSocketPoolBaseTest, FailingActiveRequestWithPendingRequests) {
+ const size_t kMaxSockets = 5;
+ CreatePool(kMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+
+ const size_t kNumberOfRequests = 2 * kDefaultMaxSocketsPerGroup + 1;
+ ASSERT_LE(kNumberOfRequests, kMaxSockets); // Otherwise the test will hang.
+
+ // Queue up all the requests
+ for (size_t i = 0; i < kNumberOfRequests; ++i)
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ for (size_t i = 0; i < kNumberOfRequests; ++i)
+ EXPECT_EQ(ERR_CONNECTION_FAILED, request(i)->WaitForResult());
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestThenRequestSocket) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Cancel the active request.
+ handle.Reset();
+
+ rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_FALSE(handle.is_reused());
+ TestLoadTimingInfoConnectedNotReused(handle);
+ EXPECT_EQ(2, client_socket_factory_.allocation_count());
+}
+
+// Regression test for http://crbug.com/17985.
+TEST_F(ClientSocketPoolBaseTest, GroupWithPendingRequestsIsNotEmpty) {
+ const int kMaxSockets = 3;
+ const int kMaxSocketsPerGroup = 2;
+ CreatePool(kMaxSockets, kMaxSocketsPerGroup);
+
+ const RequestPriority kHighPriority = HIGHEST;
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ // This is going to be a pending request in an otherwise empty group.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ // Reach the maximum socket limit.
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+
+ // Create a stalled group with high priorities.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
+
+ // Release the first two sockets from "a". Because this is a keepalive,
+ // the first release will unblock the pending request for "a". The
+ // second release will unblock a request for "c", becaue it is the next
+ // high priority socket.
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE));
+
+ // Closing idle sockets should not get us into trouble, but in the bug
+ // we were hitting a CHECK here.
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ pool_->CloseIdleSockets();
+
+ // Run the released socket wakeups.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ CapturingBoundNetLog log;
+ int rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ log.bound());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+ TestLoadTimingInfoNotConnected(handle);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoConnectedNotReused(handle);
+
+ handle.Reset();
+ TestLoadTimingInfoNotConnected(handle);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(4u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 3, NetLog::TYPE_SOCKET_POOL));
+}
+
+TEST_F(ClientSocketPoolBaseTest,
+ InitConnectionAsynchronousFailure) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ CapturingBoundNetLog log;
+ // Set the additional error state members to ensure that they get cleared.
+ handle.set_is_ssl_error(true);
+ HttpResponseInfo info;
+ info.headers = new HttpResponseHeaders(std::string());
+ handle.set_ssl_error_response_info(info);
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ log.bound()));
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_ssl_error());
+ EXPECT_TRUE(handle.ssl_error_response_info().headers.get() == NULL);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_EQ(3u, entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKET_POOL));
+ EXPECT_TRUE(LogContainsEvent(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, 2, NetLog::TYPE_SOCKET_POOL));
+}
+
+TEST_F(ClientSocketPoolBaseTest, TwoRequestsCancelOne) {
+ // TODO(eroman): Add back the log expectations! Removed them because the
+ // ordering is difficult, and some may fire during destructor.
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ CapturingBoundNetLog log2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ handle.Reset();
+
+
+ // At this point, request 2 is just waiting for the connect job to finish.
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ handle2.Reset();
+
+ // Now request 2 has actually finished.
+ // TODO(eroman): Add back log expectations.
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelRequestLimitsJobs) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
+ (*requests())[2]->handle()->Reset();
+ (*requests())[3]->handle()->Reset();
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
+
+ (*requests())[1]->handle()->Reset();
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
+
+ (*requests())[0]->handle()->Reset();
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
+}
+
+// When requests and ConnectJobs are not coupled, the request will get serviced
+// by whatever comes first.
+TEST_F(ClientSocketPoolBaseTest, ReleaseSockets) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ // Start job 1 (async OK)
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ std::vector<TestSocketRequest*> request_order;
+ size_t completion_count; // unused
+ TestSocketRequest req1(&request_order, &completion_count);
+ int rv = req1.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req1.callback(), pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, req1.WaitForResult());
+
+ // Job 1 finished OK. Start job 2 (also async OK). Request 3 is pending
+ // without a job.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ TestSocketRequest req2(&request_order, &completion_count);
+ rv = req2.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ TestSocketRequest req3(&request_order, &completion_count);
+ rv = req3.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req3.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Both Requests 2 and 3 are pending. We release socket 1 which should
+ // service request 2. Request 3 should still be waiting.
+ req1.handle()->Reset();
+ // Run the released socket wakeups.
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(req2.handle()->socket());
+ EXPECT_EQ(OK, req2.WaitForResult());
+ EXPECT_FALSE(req3.handle()->socket());
+
+ // Signal job 2, which should service request 3.
+
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(OK, req3.WaitForResult());
+
+ ASSERT_EQ(3U, request_order.size());
+ EXPECT_EQ(&req1, request_order[0]);
+ EXPECT_EQ(&req2, request_order[1]);
+ EXPECT_EQ(&req3, request_order[2]);
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+}
+
+// The requests are not coupled to the jobs. So, the requests should finish in
+// their priority / insertion order.
+TEST_F(ClientSocketPoolBaseTest, PendingJobCompletionOrder) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ // First two jobs are async.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+
+ std::vector<TestSocketRequest*> request_order;
+ size_t completion_count; // unused
+ TestSocketRequest req1(&request_order, &completion_count);
+ int rv = req1.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req1.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TestSocketRequest req2(&request_order, &completion_count);
+ rv = req2.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The pending job is sync.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ TestSocketRequest req3(&request_order, &completion_count);
+ rv = req3.handle()->Init("a",
+ params_,
+ kDefaultPriority,
+ req3.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req1.WaitForResult());
+ EXPECT_EQ(OK, req2.WaitForResult());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req3.WaitForResult());
+
+ ASSERT_EQ(3U, request_order.size());
+ EXPECT_EQ(&req1, request_order[0]);
+ EXPECT_EQ(&req2, request_order[1]);
+ EXPECT_EQ(&req3, request_order[2]);
+}
+
+// Test GetLoadState in the case there's only one socket request.
+TEST_F(ClientSocketPoolBaseTest, LoadStateOneRequest) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState());
+
+ client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE);
+ EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle.GetLoadState());
+
+ // No point in completing the connection, since ClientSocketHandles only
+ // expect the LoadState to be checked while connecting.
+}
+
+// Test GetLoadState in the case there are two socket requests.
+TEST_F(ClientSocketPoolBaseTest, LoadStateTwoRequests) {
+ CreatePool(2, 2);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // If the first Job is in an earlier state than the second, the state of
+ // the second job should be used for both handles.
+ client_socket_factory_.SetJobLoadState(0, LOAD_STATE_RESOLVING_HOST);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle2.GetLoadState());
+
+ // If the second Job is in an earlier state than the second, the state of
+ // the first job should be used for both handles.
+ client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE);
+ // One request is farther
+ EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle2.GetLoadState());
+
+ // Farthest along job connects and the first request gets the socket. The
+ // second handle switches to the state of the remaining ConnectJob.
+ client_socket_factory_.SignalJob(0);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle2.GetLoadState());
+}
+
+// Test GetLoadState in the case the per-group limit is reached.
+TEST_F(ClientSocketPoolBaseTest, LoadStateGroupLimit) {
+ CreatePool(2, 1);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ MEDIUM,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState());
+
+ // Request another socket from the same pool, buth with a higher priority.
+ // The first request should now be stalled at the socket group limit.
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("a",
+ params_,
+ HIGHEST,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle2.GetLoadState());
+
+ // The first handle should remain stalled as the other socket goes through
+ // the connect process.
+
+ client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE);
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle2.GetLoadState());
+
+ client_socket_factory_.SignalJob(0);
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState());
+
+ // Closing the second socket should cause the stalled handle to finally get a
+ // ConnectJob.
+ handle2.socket()->Disconnect();
+ handle2.Reset();
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState());
+}
+
+// Test GetLoadState in the case the per-pool limit is reached.
+TEST_F(ClientSocketPoolBaseTest, LoadStatePoolLimit) {
+ CreatePool(2, 2);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Request for socket from another pool.
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("b",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Request another socket from the first pool. Request should stall at the
+ // socket pool limit.
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ rv = handle3.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // The third handle should remain stalled as the other sockets in its group
+ // goes through the connect process.
+
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState());
+
+ client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE);
+ EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle.GetLoadState());
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState());
+
+ client_socket_factory_.SignalJob(0);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState());
+
+ // Closing a socket should allow the stalled handle to finally get a new
+ // ConnectJob.
+ handle.socket()->Disconnect();
+ handle.Reset();
+ EXPECT_EQ(LOAD_STATE_CONNECTING, handle3.GetLoadState());
+}
+
+TEST_F(ClientSocketPoolBaseTest, Recoverable) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockRecoverableJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ pool_.get(), BoundNetLog()));
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(ClientSocketPoolBaseTest, AsyncRecoverable) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingRecoverableJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+}
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateSynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockAdditionalErrorStateJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_CONNECTION_FAILED,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+ EXPECT_FALSE(handle.ssl_error_response_info().headers.get() == NULL);
+}
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateAsynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingAdditionalErrorStateJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+ EXPECT_FALSE(handle.ssl_error_response_info().headers.get() == NULL);
+}
+
+// Make sure we can reuse sockets when the cleanup timer is disabled.
+TEST_F(ClientSocketPoolBaseTest, DisableCleanupTimerReuse) {
+ // Disable cleanup timer.
+ internal::ClientSocketPoolBaseHelper::set_cleanup_timer_enabled(false);
+
+ CreatePoolWithIdleTimeouts(
+ kDefaultMaxSockets, kDefaultMaxSocketsPerGroup,
+ base::TimeDelta(), // Time out unused sockets immediately.
+ base::TimeDelta::FromDays(1)); // Don't time out used sockets.
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+ ASSERT_EQ(OK, callback.WaitForResult());
+
+ // Use and release the socket.
+ EXPECT_EQ(1, handle.socket()->Write(NULL, 1, CompletionCallback()));
+ TestLoadTimingInfoConnectedNotReused(handle);
+ handle.Reset();
+
+ // Should now have one idle socket.
+ ASSERT_EQ(1, pool_->IdleSocketCount());
+
+ // Request a new socket. This should reuse the old socket and complete
+ // synchronously.
+ CapturingBoundNetLog log;
+ rv = handle.Init("a",
+ params_,
+ LOWEST,
+ CompletionCallback(),
+ pool_.get(),
+ log.bound());
+ ASSERT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_reused());
+ TestLoadTimingInfoConnectedReused(handle);
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a"));
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEntryWithType(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
+}
+
+// Make sure we cleanup old unused sockets when the cleanup timer is disabled.
+TEST_F(ClientSocketPoolBaseTest, DisableCleanupTimerNoReuse) {
+ // Disable cleanup timer.
+ internal::ClientSocketPoolBaseHelper::set_cleanup_timer_enabled(false);
+
+ CreatePoolWithIdleTimeouts(
+ kDefaultMaxSockets, kDefaultMaxSocketsPerGroup,
+ base::TimeDelta(), // Time out unused sockets immediately
+ base::TimeDelta()); // Time out used sockets immediately
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ // Startup two mock pending connect jobs, which will sit in the MessageLoop.
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("a",
+ params_,
+ LOWEST,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle2));
+
+ // Cancel one of the requests. Wait for the other, which will get the first
+ // job. Release the socket. Run the loop again to make sure the second
+ // socket is sitting idle and the first one is released (since ReleaseSocket()
+ // just posts a DoReleaseSocket() task).
+
+ handle.Reset();
+ ASSERT_EQ(OK, callback2.WaitForResult());
+ // Use the socket.
+ EXPECT_EQ(1, handle2.socket()->Write(NULL, 1, CompletionCallback()));
+ handle2.Reset();
+
+ // We post all of our delayed tasks with a 2ms delay. I.e. they don't
+ // actually become pending until 2ms after they have been created. In order
+ // to flush all tasks, we need to wait so that we know there are no
+ // soon-to-be-pending tasks waiting.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Both sockets should now be idle.
+ ASSERT_EQ(2, pool_->IdleSocketCount());
+
+ // Request a new socket. This should cleanup the unused and timed out ones.
+ // A new socket will be created rather than reusing the idle one.
+ CapturingBoundNetLog log;
+ TestCompletionCallback callback3;
+ rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback3.callback(),
+ pool_.get(),
+ log.bound());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_EQ(OK, callback3.WaitForResult());
+ EXPECT_FALSE(handle.is_reused());
+
+ // Make sure the idle socket is closed.
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a"));
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_FALSE(LogContainsEntryWithType(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
+}
+
+TEST_F(ClientSocketPoolBaseTest, CleanupTimedOutIdleSockets) {
+ CreatePoolWithIdleTimeouts(
+ kDefaultMaxSockets, kDefaultMaxSocketsPerGroup,
+ base::TimeDelta(), // Time out unused sockets immediately.
+ base::TimeDelta::FromDays(1)); // Don't time out used sockets.
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ // Startup two mock pending connect jobs, which will sit in the MessageLoop.
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("a",
+ params_,
+ LOWEST,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", &handle2));
+
+ // Cancel one of the requests. Wait for the other, which will get the first
+ // job. Release the socket. Run the loop again to make sure the second
+ // socket is sitting idle and the first one is released (since ReleaseSocket()
+ // just posts a DoReleaseSocket() task).
+
+ handle.Reset();
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ // Use the socket.
+ EXPECT_EQ(1, handle2.socket()->Write(NULL, 1, CompletionCallback()));
+ handle2.Reset();
+
+ // We post all of our delayed tasks with a 2ms delay. I.e. they don't
+ // actually become pending until 2ms after they have been created. In order
+ // to flush all tasks, we need to wait so that we know there are no
+ // soon-to-be-pending tasks waiting.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_EQ(2, pool_->IdleSocketCount());
+
+ // Invoke the idle socket cleanup check. Only one socket should be left, the
+ // used socket. Request it to make sure that it's used.
+
+ pool_->CleanupTimedOutIdleSockets();
+ CapturingBoundNetLog log;
+ rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ log.bound());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_reused());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEntryWithType(
+ entries, 1, NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
+}
+
+// Make sure that we process all pending requests even when we're stalling
+// because of multiple releasing disconnected sockets.
+TEST_F(ClientSocketPoolBaseTest, MultipleReleasingDisconnectedSockets) {
+ CreatePoolWithIdleTimeouts(
+ kDefaultMaxSockets, kDefaultMaxSocketsPerGroup,
+ base::TimeDelta(), // Time out unused sockets immediately.
+ base::TimeDelta::FromDays(1)); // Don't time out used sockets.
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ // Startup 4 connect jobs. Two of them will be pending.
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init("a",
+ params_,
+ LOWEST,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ rv = handle2.Init("a",
+ params_,
+ LOWEST,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ rv = handle3.Init("a",
+ params_,
+ LOWEST,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ ClientSocketHandle handle4;
+ TestCompletionCallback callback4;
+ rv = handle4.Init("a",
+ params_,
+ LOWEST,
+ callback4.callback(),
+ pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Release two disconnected sockets.
+
+ handle.socket()->Disconnect();
+ handle.Reset();
+ handle2.socket()->Disconnect();
+ handle2.Reset();
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_FALSE(handle3.is_reused());
+ EXPECT_EQ(OK, callback4.WaitForResult());
+ EXPECT_FALSE(handle4.is_reused());
+}
+
+// Regression test for http://crbug.com/42267.
+// When DoReleaseSocket() is processed for one socket, it is blocked because the
+// other stalled groups all have releasing sockets, so no progress can be made.
+TEST_F(ClientSocketPoolBaseTest, SocketLimitReleasingSockets) {
+ CreatePoolWithIdleTimeouts(
+ 4 /* socket limit */, 4 /* socket limit per group */,
+ base::TimeDelta(), // Time out unused sockets immediately.
+ base::TimeDelta::FromDays(1)); // Don't time out used sockets.
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ // Max out the socket limit with 2 per group.
+
+ ClientSocketHandle handle_a[4];
+ TestCompletionCallback callback_a[4];
+ ClientSocketHandle handle_b[4];
+ TestCompletionCallback callback_b[4];
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(OK, handle_a[i].Init("a",
+ params_,
+ LOWEST,
+ callback_a[i].callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, handle_b[i].Init("b",
+ params_,
+ LOWEST,
+ callback_b[i].callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // Make 4 pending requests, 2 per group.
+
+ for (int i = 2; i < 4; ++i) {
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle_a[i].Init("a",
+ params_,
+ LOWEST,
+ callback_a[i].callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle_b[i].Init("b",
+ params_,
+ LOWEST,
+ callback_b[i].callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ // Release b's socket first. The order is important, because in
+ // DoReleaseSocket(), we'll process b's released socket, and since both b and
+ // a are stalled, but 'a' is lower lexicographically, we'll process group 'a'
+ // first, which has a releasing socket, so it refuses to start up another
+ // ConnectJob. So, we used to infinite loop on this.
+ handle_b[0].socket()->Disconnect();
+ handle_b[0].Reset();
+ handle_a[0].socket()->Disconnect();
+ handle_a[0].Reset();
+
+ // Used to get stuck here.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ handle_b[1].socket()->Disconnect();
+ handle_b[1].Reset();
+ handle_a[1].socket()->Disconnect();
+ handle_a[1].Reset();
+
+ for (int i = 2; i < 4; ++i) {
+ EXPECT_EQ(OK, callback_b[i].WaitForResult());
+ EXPECT_EQ(OK, callback_a[i].WaitForResult());
+ }
+}
+
+TEST_F(ClientSocketPoolBaseTest,
+ ReleasingDisconnectedSocketsMaintainsPriorityOrder) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ EXPECT_EQ(OK, (*requests())[0]->WaitForResult());
+ EXPECT_EQ(OK, (*requests())[1]->WaitForResult());
+ EXPECT_EQ(2u, completion_count());
+
+ // Releases one connection.
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE));
+ EXPECT_EQ(OK, (*requests())[2]->WaitForResult());
+
+ EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE));
+ EXPECT_EQ(OK, (*requests())[3]->WaitForResult());
+ EXPECT_EQ(4u, completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(5));
+}
+
+class TestReleasingSocketRequest : public TestCompletionCallbackBase {
+ public:
+ TestReleasingSocketRequest(TestClientSocketPool* pool,
+ int expected_result,
+ bool reset_releasing_handle)
+ : pool_(pool),
+ expected_result_(expected_result),
+ reset_releasing_handle_(reset_releasing_handle),
+ callback_(base::Bind(&TestReleasingSocketRequest::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~TestReleasingSocketRequest() {}
+
+ ClientSocketHandle* handle() { return &handle_; }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ SetResult(result);
+ if (reset_releasing_handle_)
+ handle_.Reset();
+
+ scoped_refptr<TestSocketParams> con_params(new TestSocketParams());
+ EXPECT_EQ(expected_result_,
+ handle2_.Init("a", con_params, kDefaultPriority,
+ callback2_.callback(), pool_, BoundNetLog()));
+ }
+
+ TestClientSocketPool* const pool_;
+ int expected_result_;
+ bool reset_releasing_handle_;
+ ClientSocketHandle handle_;
+ ClientSocketHandle handle2_;
+ CompletionCallback callback_;
+ TestCompletionCallback callback2_;
+};
+
+
+TEST_F(ClientSocketPoolBaseTest, AdditionalErrorSocketsDontUseSlot) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
+
+ EXPECT_EQ(static_cast<int>(requests_size()),
+ client_socket_factory_.allocation_count());
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockPendingAdditionalErrorStateJob);
+ TestReleasingSocketRequest req(pool_.get(), OK, false);
+ EXPECT_EQ(ERR_IO_PENDING,
+ req.handle()->Init("a", params_, kDefaultPriority, req.callback(),
+ pool_.get(), BoundNetLog()));
+ // The next job should complete synchronously
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
+ EXPECT_FALSE(req.handle()->is_initialized());
+ EXPECT_FALSE(req.handle()->socket());
+ EXPECT_TRUE(req.handle()->is_ssl_error());
+ EXPECT_FALSE(req.handle()->ssl_error_response_info().headers.get() == NULL);
+}
+
+// http://crbug.com/44724 regression test.
+// We start releasing the pool when we flush on network change. When that
+// happens, the only active references are in the ClientSocketHandles. When a
+// ConnectJob completes and calls back into the last ClientSocketHandle, that
+// callback can release the last reference and delete the pool. After the
+// callback finishes, we go back to the stack frame within the now-deleted pool.
+// Executing any code that refers to members of the now-deleted pool can cause
+// crashes.
+TEST_F(ClientSocketPoolBaseTest, CallbackThatReleasesPool) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ pool_->FlushWithError(ERR_NETWORK_CHANGED);
+
+ // We'll call back into this now.
+ callback.WaitForResult();
+}
+
+TEST_F(ClientSocketPoolBaseTest, DoNotReuseSocketAfterFlush) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+
+ pool_->FlushWithError(ERR_NETWORK_CHANGED);
+
+ handle.Reset();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+}
+
+class ConnectWithinCallback : public TestCompletionCallbackBase {
+ public:
+ ConnectWithinCallback(
+ const std::string& group_name,
+ const scoped_refptr<TestSocketParams>& params,
+ TestClientSocketPool* pool)
+ : group_name_(group_name),
+ params_(params),
+ pool_(pool),
+ callback_(base::Bind(&ConnectWithinCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~ConnectWithinCallback() {}
+
+ int WaitForNestedResult() {
+ return nested_callback_.WaitForResult();
+ }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ SetResult(result);
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle_.Init(group_name_,
+ params_,
+ kDefaultPriority,
+ nested_callback_.callback(),
+ pool_,
+ BoundNetLog()));
+ }
+
+ const std::string group_name_;
+ const scoped_refptr<TestSocketParams> params_;
+ TestClientSocketPool* const pool_;
+ ClientSocketHandle handle_;
+ CompletionCallback callback_;
+ TestCompletionCallback nested_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectWithinCallback);
+};
+
+TEST_F(ClientSocketPoolBaseTest, AbortAllRequestsOnFlush) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+ // First job will be waiting until it gets aborted.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+
+ ClientSocketHandle handle;
+ ConnectWithinCallback callback("a", params_, pool_.get());
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ // Second job will be started during the first callback, and will
+ // asynchronously complete with OK.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ pool_->FlushWithError(ERR_NETWORK_CHANGED);
+ EXPECT_EQ(ERR_NETWORK_CHANGED, callback.WaitForResult());
+ EXPECT_EQ(OK, callback.WaitForNestedResult());
+}
+
+// Cancel a pending socket request while we're at max sockets,
+// and verify that the backup socket firing doesn't cause a crash.
+TEST_F(ClientSocketPoolBaseTest, BackupSocketCancelAtMaxSockets) {
+ // Max 4 sockets globally, max 4 sockets per group.
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ pool_->EnableConnectBackupJobs();
+
+ // Create the first socket and set to ERR_IO_PENDING. This starts the backup
+ // timer.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("bar",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ // Start (MaxSockets - 1) connected sockets to reach max sockets.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+ ClientSocketHandle handles[kDefaultMaxSockets];
+ for (int i = 1; i < kDefaultMaxSockets; ++i) {
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handles[i].Init("bar",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ }
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Cancel the pending request.
+ handle.Reset();
+
+ // Wait for the backup timer to fire (add some slop to ensure it fires)
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3));
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelBackupSocketAfterCancelingAllRequests) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ pool_->EnableConnectBackupJobs();
+
+ // Create the first socket and set to ERR_IO_PENDING. This starts the backup
+ // timer.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("bar",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ASSERT_TRUE(pool_->HasGroup("bar"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("bar"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("bar"));
+
+ // Cancel the socket request. This should cancel the backup timer. Wait for
+ // the backup time to see if it indeed got canceled.
+ handle.Reset();
+ // Wait for the backup timer to fire (add some slop to ensure it fires)
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3));
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(pool_->HasGroup("bar"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("bar"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, CancelBackupSocketAfterFinishingAllRequests) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ pool_->EnableConnectBackupJobs();
+
+ // Create the first socket and set to ERR_IO_PENDING. This starts the backup
+ // timer.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("bar",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("bar",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ASSERT_TRUE(pool_->HasGroup("bar"));
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("bar"));
+
+ // Cancel request 1 and then complete request 2. With the requests finished,
+ // the backup timer should be cancelled.
+ handle.Reset();
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ // Wait for the backup timer to fire (add some slop to ensure it fires)
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3));
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test delayed socket binding for the case where we have two connects,
+// and while one is waiting on a connect, the other frees up.
+// The socket waiting on a connect should switch immediately to the freed
+// up socket.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingWaitingForConnect) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test delayed socket binding when a group is at capacity and one
+// of the group's sockets frees up.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtGroupCapacity) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Test out the case where we have one socket connected, one
+// connecting, when the first socket finishes and goes idle.
+// Although the second connection is pending, the second request
+// should complete, by taking the first socket's idle socket.
+TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtStall) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // No idle sockets, no pending jobs.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ // Create a second socket to the same host, but this one will wait.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ ClientSocketHandle handle2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ // No idle sockets, and one connecting job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Return the first handle to the pool. This will initiate the delayed
+ // binding.
+ handle1.Reset();
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Still no idle sockets, still one pending connect job.
+ EXPECT_EQ(0, pool_->IdleSocketCount());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // The second socket connected, even though it was a Waiting Job.
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And we can see there is still one job waiting.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Finally, signal the waiting Connect.
+ client_socket_factory_.SignalJobs();
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Cover the case where on an available socket slot, we have one pending
+// request that completes synchronously, thereby making the Group empty.
+TEST_F(ClientSocketPoolBaseTest, SynchronouslyProcessOnePendingRequest) {
+ const int kUnlimitedSockets = 100;
+ const int kOneSocketPerGroup = 1;
+ CreatePool(kUnlimitedSockets, kOneSocketPerGroup);
+
+ // Make the first request asynchronous fail.
+ // This will free up a socket slot later.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Make the second request synchronously fail. This should make the Group
+ // empty.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob);
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ // It'll be ERR_IO_PENDING now, but the TestConnectJob will synchronously fail
+ // when created.
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback1.WaitForResult());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback2.WaitForResult());
+ EXPECT_FALSE(pool_->HasGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, PreferUsedSocketToUnusedSocket) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ EXPECT_EQ(ERR_IO_PENDING, handle3.Init("a",
+ params_,
+ kDefaultPriority,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(OK, callback3.WaitForResult());
+
+ // Use the socket.
+ EXPECT_EQ(1, handle1.socket()->Write(NULL, 1, CompletionCallback()));
+ EXPECT_EQ(1, handle3.socket()->Write(NULL, 1, CompletionCallback()));
+
+ handle1.Reset();
+ handle2.Reset();
+ handle3.Reset();
+
+ EXPECT_EQ(OK, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, handle3.Init("a",
+ params_,
+ kDefaultPriority,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_TRUE(handle1.socket()->WasEverUsed());
+ EXPECT_TRUE(handle2.socket()->WasEverUsed());
+ EXPECT_FALSE(handle3.socket()->WasEverUsed());
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSockets) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ handle1.Reset();
+ handle2.Reset();
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsWhenAlreadyHaveAConnectJob) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ handle1.Reset();
+ handle2.Reset();
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest,
+ RequestSocketsWhenAlreadyHaveMultipleConnectJob) {
+ CreatePool(4, 4);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ EXPECT_EQ(ERR_IO_PENDING, handle3.Init("a",
+ params_,
+ kDefaultPriority,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(3, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ EXPECT_EQ(3, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ EXPECT_EQ(OK, callback1.WaitForResult());
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ handle1.Reset();
+ handle2.Reset();
+ handle3.Reset();
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(3, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsAtMaxSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ASSERT_FALSE(pool_->HasGroup("a"));
+
+ pool_->RequestSockets("a", &params_, kDefaultMaxSockets,
+ BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(kDefaultMaxSockets, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(kDefaultMaxSockets, pool_->NumUnassignedConnectJobsInGroup("a"));
+
+ ASSERT_FALSE(pool_->HasGroup("b"));
+
+ pool_->RequestSockets("b", &params_, kDefaultMaxSockets,
+ BoundNetLog());
+
+ ASSERT_FALSE(pool_->HasGroup("b"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsHitMaxSocketLimit) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ASSERT_FALSE(pool_->HasGroup("a"));
+
+ pool_->RequestSockets("a", &params_, kDefaultMaxSockets - 1,
+ BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(kDefaultMaxSockets - 1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(kDefaultMaxSockets - 1,
+ pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_FALSE(pool_->IsStalled());
+
+ ASSERT_FALSE(pool_->HasGroup("b"));
+
+ pool_->RequestSockets("b", &params_, kDefaultMaxSockets,
+ BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("b"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("b"));
+ EXPECT_FALSE(pool_->IsStalled());
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsCountIdleSockets) {
+ CreatePool(4, 4);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ASSERT_EQ(OK, callback1.WaitForResult());
+ handle1.Reset();
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsCountActiveSockets) {
+ CreatePool(4, 4);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ASSERT_EQ(OK, callback1.WaitForResult());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsSynchronous) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ pool_->RequestSockets("a", &params_, kDefaultMaxSocketsPerGroup,
+ BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("b", &params_, kDefaultMaxSocketsPerGroup,
+ BoundNetLog());
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("b"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("b"));
+ EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->IdleSocketCountInGroup("b"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsSynchronousError) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob);
+
+ pool_->RequestSockets("a", &params_, kDefaultMaxSocketsPerGroup,
+ BoundNetLog());
+
+ ASSERT_FALSE(pool_->HasGroup("a"));
+
+ connect_job_factory_->set_job_type(
+ TestConnectJob::kMockAdditionalErrorStateJob);
+ pool_->RequestSockets("a", &params_, kDefaultMaxSocketsPerGroup,
+ BoundNetLog());
+
+ ASSERT_FALSE(pool_->HasGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsMultipleTimesDoesNothing) {
+ CreatePool(4, 4);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ ASSERT_EQ(OK, callback1.WaitForResult());
+
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ int rv = handle2.Init("a",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog());
+ if (rv != OK) {
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ }
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->NumActiveSocketsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ handle1.Reset();
+ handle2.Reset();
+
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, RequestSocketsDifferentNumSockets) {
+ CreatePool(4, 4);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+ EXPECT_EQ(2, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(2, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 3, BoundNetLog());
+ EXPECT_EQ(3, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(3, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+ EXPECT_EQ(3, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(3, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, PreconnectJobsTakenByNormalRequests) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ ASSERT_EQ(OK, callback1.WaitForResult());
+
+ // Make sure if a preconneced socket is not fully connected when a request
+ // starts, it has a connect start time.
+ TestLoadTimingInfoConnectedNotReused(handle1);
+ handle1.Reset();
+
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+}
+
+// Checks that fully connected preconnect jobs have no connect times, and are
+// marked as reused.
+TEST_F(ClientSocketPoolBaseTest, ConnectedPreconnectJobsHaveNoConnectTimes) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+
+ ASSERT_TRUE(pool_->HasGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(OK, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ // Make sure the idle socket was used.
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ TestLoadTimingInfoConnectedReused(handle);
+ handle.Reset();
+ TestLoadTimingInfoNotConnected(handle);
+}
+
+// http://crbug.com/64940 regression test.
+TEST_F(ClientSocketPoolBaseTest, PreconnectClosesIdleSocketRemovesGroup) {
+ const int kMaxTotalSockets = 3;
+ const int kMaxSocketsPerGroup = 2;
+ CreatePool(kMaxTotalSockets, kMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ // Note that group name ordering matters here. "a" comes before "b", so
+ // CloseOneIdleSocket() will try to close "a"'s idle socket.
+
+ // Set up one idle socket in "a".
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("a",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ASSERT_EQ(OK, callback1.WaitForResult());
+ handle1.Reset();
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+
+ // Set up two active sockets in "b".
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle1.Init("b",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(ERR_IO_PENDING, handle2.Init("b",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ ASSERT_EQ(OK, callback1.WaitForResult());
+ ASSERT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("b"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("b"));
+ EXPECT_EQ(2, pool_->NumActiveSocketsInGroup("b"));
+
+ // Now we have 1 idle socket in "a" and 2 active sockets in "b". This means
+ // we've maxed out on sockets, since we set |kMaxTotalSockets| to 3.
+ // Requesting 2 preconnected sockets for "a" should fail to allocate any more
+ // sockets for "a", and "b" should still have 2 active sockets.
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(0, pool_->NumActiveSocketsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("b"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("b"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("b"));
+ EXPECT_EQ(2, pool_->NumActiveSocketsInGroup("b"));
+
+ // Now release the 2 active sockets for "b". This will give us 1 idle socket
+ // in "a" and 2 idle sockets in "b". Requesting 2 preconnected sockets for
+ // "a" should result in closing 1 for "b".
+ handle1.Reset();
+ handle2.Reset();
+ EXPECT_EQ(2, pool_->IdleSocketCountInGroup("b"));
+ EXPECT_EQ(0, pool_->NumActiveSocketsInGroup("b"));
+
+ pool_->RequestSockets("a", &params_, 2, BoundNetLog());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(0, pool_->NumActiveSocketsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("b"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("b"));
+ EXPECT_EQ(1, pool_->IdleSocketCountInGroup("b"));
+ EXPECT_EQ(0, pool_->NumActiveSocketsInGroup("b"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, PreconnectWithoutBackupJob) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ pool_->EnableConnectBackupJobs();
+
+ // Make the ConnectJob hang until it times out, shorten the timeout.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ connect_job_factory_->set_timeout_duration(
+ base::TimeDelta::FromMilliseconds(500));
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+
+ // Verify the backup timer doesn't create a backup job, by making
+ // the backup job a pending job instead of a waiting job, so it
+ // *would* complete if it were created.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromSeconds(1));
+ base::MessageLoop::current()->Run();
+ EXPECT_FALSE(pool_->HasGroup("a"));
+}
+
+TEST_F(ClientSocketPoolBaseTest, PreconnectWithBackupJob) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ pool_->EnableConnectBackupJobs();
+
+ // Make the ConnectJob hang forever.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
+ pool_->RequestSockets("a", &params_, 1, BoundNetLog());
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(1, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Make the backup job be a pending job, so it completes normally.
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ // Timer has started, but the backup connect job shouldn't be created yet.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(0, pool_->NumActiveSocketsInGroup("a"));
+ ASSERT_EQ(OK, callback.WaitForResult());
+
+ // The hung connect job should still be there, but everything else should be
+ // complete.
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->NumUnassignedConnectJobsInGroup("a"));
+ EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
+ EXPECT_EQ(1, pool_->NumActiveSocketsInGroup("a"));
+}
+
+class MockLayeredPool : public LayeredPool {
+ public:
+ MockLayeredPool(TestClientSocketPool* pool,
+ const std::string& group_name)
+ : pool_(pool),
+ params_(new TestSocketParams),
+ group_name_(group_name),
+ can_release_connection_(true) {
+ pool_->AddLayeredPool(this);
+ }
+
+ ~MockLayeredPool() {
+ pool_->RemoveLayeredPool(this);
+ }
+
+ int RequestSocket(TestClientSocketPool* pool) {
+ return handle_.Init(group_name_, params_, kDefaultPriority,
+ callback_.callback(), pool, BoundNetLog());
+ }
+
+ int RequestSocketWithoutLimits(TestClientSocketPool* pool) {
+ params_->set_ignore_limits(true);
+ return handle_.Init(group_name_, params_, kDefaultPriority,
+ callback_.callback(), pool, BoundNetLog());
+ }
+
+ bool ReleaseOneConnection() {
+ if (!handle_.is_initialized() || !can_release_connection_) {
+ return false;
+ }
+ handle_.socket()->Disconnect();
+ handle_.Reset();
+ return true;
+ }
+
+ void set_can_release_connection(bool can_release_connection) {
+ can_release_connection_ = can_release_connection;
+ }
+
+ MOCK_METHOD0(CloseOneIdleConnection, bool());
+
+ private:
+ TestClientSocketPool* const pool_;
+ scoped_refptr<TestSocketParams> params_;
+ ClientSocketHandle handle_;
+ TestCompletionCallback callback_;
+ const std::string group_name_;
+ bool can_release_connection_;
+};
+
+TEST_F(ClientSocketPoolBaseTest, FailToCloseIdleSocketsNotHeldByLayeredPool) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "foo");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillOnce(Return(false));
+ EXPECT_FALSE(pool_->CloseOneIdleConnectionInLayeredPool());
+}
+
+TEST_F(ClientSocketPoolBaseTest, ForciblyCloseIdleSocketsHeldByLayeredPool) {
+ CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "foo");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillOnce(Invoke(&mock_layered_pool,
+ &MockLayeredPool::ReleaseOneConnection));
+ EXPECT_TRUE(pool_->CloseOneIdleConnectionInLayeredPool());
+}
+
+// Tests the basic case of closing an idle socket in a higher layered pool when
+// a new request is issued and the lower layer pool is stalled.
+TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolWhenNeeded) {
+ CreatePool(1, 1);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "foo");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillOnce(Invoke(&mock_layered_pool,
+ &MockLayeredPool::ReleaseOneConnection));
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Same as above, but the idle socket is in the same group as the stalled
+// socket, and closes the only other request in its group when closing requests
+// in higher layered pools. This generally shouldn't happen, but it may be
+// possible if a higher level pool issues a request and the request is
+// subsequently cancelled. Even if it's not possible, best not to crash.
+TEST_F(ClientSocketPoolBaseTest,
+ CloseIdleSocketsHeldByLayeredPoolWhenNeededSameGroup) {
+ CreatePool(2, 2);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ // Need a socket in another group for the pool to be stalled (If a group
+ // has the maximum number of connections already, it's not stalled).
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, handle1.Init("group1",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "group2");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillOnce(Invoke(&mock_layered_pool,
+ &MockLayeredPool::ReleaseOneConnection));
+ ClientSocketHandle handle;
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("group2",
+ params_,
+ kDefaultPriority,
+ callback2.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback2.WaitForResult());
+}
+
+// Tests the case when an idle socket can be closed when a new request is
+// issued, and the new request belongs to a group that was previously stalled.
+TEST_F(ClientSocketPoolBaseTest,
+ CloseIdleSocketsHeldByLayeredPoolInSameGroupWhenNeeded) {
+ CreatePool(2, 2);
+ std::list<TestConnectJob::JobType> job_types;
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ connect_job_factory_->set_job_types(&job_types);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, handle1.Init("group1",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "group2");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillRepeatedly(Invoke(&mock_layered_pool,
+ &MockLayeredPool::ReleaseOneConnection));
+ mock_layered_pool.set_can_release_connection(false);
+
+ // The third request is made when the socket pool is in a stalled state.
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ EXPECT_EQ(ERR_IO_PENDING, handle3.Init("group3",
+ params_,
+ kDefaultPriority,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(callback3.have_result());
+
+ // The fourth request is made when the pool is no longer stalled. The third
+ // request should be serviced first, since it was issued first and has the
+ // same priority.
+ mock_layered_pool.set_can_release_connection(true);
+ ClientSocketHandle handle4;
+ TestCompletionCallback callback4;
+ EXPECT_EQ(ERR_IO_PENDING, handle4.Init("group3",
+ params_,
+ kDefaultPriority,
+ callback4.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_FALSE(callback4.have_result());
+
+ // Closing a handle should free up another socket slot.
+ handle1.Reset();
+ EXPECT_EQ(OK, callback4.WaitForResult());
+}
+
+// Tests the case when an idle socket can be closed when a new request is
+// issued, and the new request belongs to a group that was previously stalled.
+//
+// The two differences from the above test are that the stalled requests are not
+// in the same group as the layered pool's request, and the the fourth request
+// has a higher priority than the third one, so gets a socket first.
+TEST_F(ClientSocketPoolBaseTest,
+ CloseIdleSocketsHeldByLayeredPoolInSameGroupWhenNeeded2) {
+ CreatePool(2, 2);
+ std::list<TestConnectJob::JobType> job_types;
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ job_types.push_back(TestConnectJob::kMockJob);
+ connect_job_factory_->set_job_types(&job_types);
+
+ ClientSocketHandle handle1;
+ TestCompletionCallback callback1;
+ EXPECT_EQ(OK, handle1.Init("group1",
+ params_,
+ kDefaultPriority,
+ callback1.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ MockLayeredPool mock_layered_pool(pool_.get(), "group2");
+ EXPECT_EQ(OK, mock_layered_pool.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection())
+ .WillRepeatedly(Invoke(&mock_layered_pool,
+ &MockLayeredPool::ReleaseOneConnection));
+ mock_layered_pool.set_can_release_connection(false);
+
+ // The third request is made when the socket pool is in a stalled state.
+ ClientSocketHandle handle3;
+ TestCompletionCallback callback3;
+ EXPECT_EQ(ERR_IO_PENDING, handle3.Init("group3",
+ params_,
+ MEDIUM,
+ callback3.callback(),
+ pool_.get(),
+ BoundNetLog()));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(callback3.have_result());
+
+ // The fourth request is made when the pool is no longer stalled. This
+ // request has a higher priority than the third request, so is serviced first.
+ mock_layered_pool.set_can_release_connection(true);
+ ClientSocketHandle handle4;
+ TestCompletionCallback callback4;
+ EXPECT_EQ(ERR_IO_PENDING, handle4.Init("group3",
+ params_,
+ HIGHEST,
+ callback4.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback4.WaitForResult());
+ EXPECT_FALSE(callback3.have_result());
+
+ // Closing a handle should free up another socket slot.
+ handle1.Reset();
+ EXPECT_EQ(OK, callback3.WaitForResult());
+}
+
+TEST_F(ClientSocketPoolBaseTest,
+ CloseMultipleIdleSocketsHeldByLayeredPoolWhenNeeded) {
+ CreatePool(1, 1);
+ connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
+
+ MockLayeredPool mock_layered_pool1(pool_.get(), "foo");
+ EXPECT_EQ(OK, mock_layered_pool1.RequestSocket(pool_.get()));
+ EXPECT_CALL(mock_layered_pool1, CloseOneIdleConnection())
+ .WillRepeatedly(Invoke(&mock_layered_pool1,
+ &MockLayeredPool::ReleaseOneConnection));
+ MockLayeredPool mock_layered_pool2(pool_.get(), "bar");
+ EXPECT_EQ(OK, mock_layered_pool2.RequestSocketWithoutLimits(pool_.get()));
+ EXPECT_CALL(mock_layered_pool2, CloseOneIdleConnection())
+ .WillRepeatedly(Invoke(&mock_layered_pool2,
+ &MockLayeredPool::ReleaseOneConnection));
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, handle.Init("a",
+ params_,
+ kDefaultPriority,
+ callback.callback(),
+ pool_.get(),
+ BoundNetLog()));
+ EXPECT_EQ(OK, callback.WaitForResult());
+}
+
+// Test that when a socket pool and group are at their limits, a request
+// with |ignore_limits| triggers creation of a new socket, and gets the socket
+// instead of a request with the same priority that was issued earlier, but
+// that does not have |ignore_limits| set.
+TEST_F(ClientSocketPoolBaseTest, IgnoreLimits) {
+ scoped_refptr<TestSocketParams> params_ignore_limits(new TestSocketParams());
+ params_ignore_limits->set_ignore_limits(true);
+ CreatePool(1, 1);
+
+ // Issue a request to reach the socket pool limit.
+ EXPECT_EQ(OK, StartRequestWithParams("a", kDefaultPriority, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", kDefaultPriority,
+ params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", kDefaultPriority,
+ params_ignore_limits));
+ ASSERT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(OK, request(2)->WaitForResult());
+ EXPECT_FALSE(request(1)->have_result());
+}
+
+// Test that when a socket pool and group are at their limits, a request with
+// |ignore_limits| set triggers creation of a new socket, and gets the socket
+// instead of a request with a higher priority that was issued earlier, but
+// that does not have |ignore_limits| set.
+TEST_F(ClientSocketPoolBaseTest, IgnoreLimitsLowPriority) {
+ scoped_refptr<TestSocketParams> params_ignore_limits(new TestSocketParams());
+ params_ignore_limits->set_ignore_limits(true);
+ CreatePool(1, 1);
+
+ // Issue a request to reach the socket pool limit.
+ EXPECT_EQ(OK, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", LOW,
+ params_ignore_limits));
+ ASSERT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(OK, request(2)->WaitForResult());
+ EXPECT_FALSE(request(1)->have_result());
+}
+
+// Test that when a socket pool and group are at their limits, a request with
+// |ignore_limits| set triggers creation of a new socket, and gets the socket
+// instead of a request with a higher priority that was issued later and
+// does not have |ignore_limits| set.
+TEST_F(ClientSocketPoolBaseTest, IgnoreLimitsLowPriority2) {
+ scoped_refptr<TestSocketParams> params_ignore_limits(new TestSocketParams());
+ params_ignore_limits->set_ignore_limits(true);
+ CreatePool(1, 1);
+
+ // Issue a request to reach the socket pool limit.
+ EXPECT_EQ(OK, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", LOW,
+ params_ignore_limits));
+ ASSERT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(OK, request(1)->WaitForResult());
+ EXPECT_FALSE(request(2)->have_result());
+}
+
+// Test that when a socket pool and group are at their limits, a ConnectJob
+// issued for a request with |ignore_limits| set is not cancelled when a request
+// without |ignore_limits| issued to the same group is cancelled.
+TEST_F(ClientSocketPoolBaseTest, IgnoreLimitsCancelOtherJob) {
+ scoped_refptr<TestSocketParams> params_ignore_limits(new TestSocketParams());
+ params_ignore_limits->set_ignore_limits(true);
+ CreatePool(1, 1);
+
+ // Issue a request to reach the socket pool limit.
+ EXPECT_EQ(OK, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST,
+ params_ignore_limits));
+ ASSERT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ // Cancel the pending request without ignore_limits set. The ConnectJob
+ // should not be cancelled.
+ request(1)->handle()->Reset();
+ ASSERT_EQ(1, pool_->NumConnectJobsInGroup("a"));
+
+ EXPECT_EQ(OK, request(2)->WaitForResult());
+ EXPECT_FALSE(request(1)->have_result());
+}
+
+// More involved test of ignore limits. Issues a bunch of requests and later
+// checks the order in which they receive sockets.
+TEST_F(ClientSocketPoolBaseTest, IgnoreLimitsOrder) {
+ scoped_refptr<TestSocketParams> params_ignore_limits(new TestSocketParams());
+ params_ignore_limits->set_ignore_limits(true);
+ CreatePool(1, 1);
+
+ connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
+
+ // Requests 0 and 1 do not have ignore_limits set, so they finish last. Since
+ // the maximum number of sockets per pool is 1, the second requests does not
+ // trigger a ConnectJob.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST, params_));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequestWithParams("a", HIGHEST, params_));
+
+ // Requests 2 and 3 have ignore_limits set, but have a low priority, so they
+ // finish just before the first two.
+ EXPECT_EQ(ERR_IO_PENDING,
+ StartRequestWithParams("a", LOW, params_ignore_limits));
+ EXPECT_EQ(ERR_IO_PENDING,
+ StartRequestWithParams("a", LOW, params_ignore_limits));
+
+ // Request 4 finishes first, since it is high priority and ignores limits.
+ EXPECT_EQ(ERR_IO_PENDING,
+ StartRequestWithParams("a", HIGHEST, params_ignore_limits));
+
+ // Request 5 and 6 are cancelled right after starting. This should result in
+ // creating two ConnectJobs. Since only one request (Request 1) did not
+ // result in creating a ConnectJob, only one of the ConnectJobs should be
+ // cancelled when the requests are.
+ EXPECT_EQ(ERR_IO_PENDING,
+ StartRequestWithParams("a", HIGHEST, params_ignore_limits));
+ EXPECT_EQ(ERR_IO_PENDING,
+ StartRequestWithParams("a", HIGHEST, params_ignore_limits));
+ EXPECT_EQ(6, pool_->NumConnectJobsInGroup("a"));
+ request(5)->handle()->Reset();
+ EXPECT_EQ(6, pool_->NumConnectJobsInGroup("a"));
+ request(6)->handle()->Reset();
+ ASSERT_EQ(5, pool_->NumConnectJobsInGroup("a"));
+
+ // Wait for the last request to get a socket.
+ EXPECT_EQ(OK, request(1)->WaitForResult());
+
+ // Check order in which requests received sockets.
+ // These are 1-based indices, while request(x) uses 0-based indices.
+ EXPECT_EQ(1, GetOrderOfRequest(5));
+ EXPECT_EQ(2, GetOrderOfRequest(3));
+ EXPECT_EQ(3, GetOrderOfRequest(4));
+ EXPECT_EQ(4, GetOrderOfRequest(1));
+ EXPECT_EQ(5, GetOrderOfRequest(2));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool_histograms.cc b/chromium/net/socket/client_socket_pool_histograms.cc
new file mode 100644
index 00000000000..9af8649c48b
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_histograms.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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/socket/client_socket_pool_histograms.h"
+
+#include <string>
+
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+using base::Histogram;
+using base::HistogramBase;
+using base::LinearHistogram;
+using base::CustomHistogram;
+
+ClientSocketPoolHistograms::ClientSocketPoolHistograms(
+ const std::string& pool_name)
+ : is_http_proxy_connection_(false),
+ is_socks_connection_(false) {
+ // UMA_HISTOGRAM_ENUMERATION
+ socket_type_ = LinearHistogram::FactoryGet("Net.SocketType_" + pool_name, 1,
+ ClientSocketHandle::NUM_TYPES, ClientSocketHandle::NUM_TYPES + 1,
+ HistogramBase::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ request_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketRequestTime_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100, HistogramBase::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ unused_idle_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketIdleTimeBeforeNextUse_UnusedSocket_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100, HistogramBase::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_TIMES
+ reused_idle_time_ = Histogram::FactoryTimeGet(
+ "Net.SocketIdleTimeBeforeNextUse_ReusedSocket_" + pool_name,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(6),
+ 100, HistogramBase::kUmaTargetedHistogramFlag);
+ // UMA_HISTOGRAM_CUSTOM_ENUMERATION
+ error_code_ = CustomHistogram::FactoryGet(
+ "Net.SocketInitErrorCodes_" + pool_name,
+ GetAllErrorCodesForUma(),
+ HistogramBase::kUmaTargetedHistogramFlag);
+
+ if (pool_name == "HTTPProxy")
+ is_http_proxy_connection_ = true;
+ else if (pool_name == "SOCK")
+ is_socks_connection_ = true;
+}
+
+ClientSocketPoolHistograms::~ClientSocketPoolHistograms() {
+}
+
+void ClientSocketPoolHistograms::AddSocketType(int type) const {
+ socket_type_->Add(type);
+}
+
+void ClientSocketPoolHistograms::AddRequestTime(base::TimeDelta time) const {
+ request_time_->AddTime(time);
+}
+
+void ClientSocketPoolHistograms::AddUnusedIdleTime(base::TimeDelta time) const {
+ unused_idle_time_->AddTime(time);
+}
+
+void ClientSocketPoolHistograms::AddReusedIdleTime(base::TimeDelta time) const {
+ reused_idle_time_->AddTime(time);
+}
+
+void ClientSocketPoolHistograms::AddErrorCode(int error_code) const {
+ // Error codes are positive (since histograms expect positive sample values).
+ error_code_->Add(-error_code);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool_histograms.h b/chromium/net/socket/client_socket_pool_histograms.h
new file mode 100644
index 00000000000..26a406362bd
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_histograms.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class HistogramBase;
+}
+
+namespace net {
+
+class NET_EXPORT_PRIVATE ClientSocketPoolHistograms {
+ public:
+ ClientSocketPoolHistograms(const std::string& pool_name);
+ ~ClientSocketPoolHistograms();
+
+ void AddSocketType(int socket_reuse_type) const;
+ void AddRequestTime(base::TimeDelta time) const;
+ void AddUnusedIdleTime(base::TimeDelta time) const;
+ void AddReusedIdleTime(base::TimeDelta time) const;
+ void AddErrorCode(int error_code) const;
+
+ private:
+ base::HistogramBase* socket_type_;
+ base::HistogramBase* request_time_;
+ base::HistogramBase* unused_idle_time_;
+ base::HistogramBase* reused_idle_time_;
+ base::HistogramBase* error_code_;
+
+ bool is_http_proxy_connection_;
+ bool is_socks_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolHistograms);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_HISTOGRAMS_H_
diff --git a/chromium/net/socket/client_socket_pool_manager.cc b/chromium/net/socket/client_socket_pool_manager.cc
new file mode 100644
index 00000000000..71496d28646
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_manager.cc
@@ -0,0 +1,467 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_pool_manager.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_stream_factory.h"
+#include "net/proxy/proxy_info.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+
+namespace net {
+
+namespace {
+
+// Limit of sockets of each socket pool.
+int g_max_sockets_per_pool[] = {
+ 256, // NORMAL_SOCKET_POOL
+ 256 // WEBSOCKET_SOCKET_POOL
+};
+
+COMPILE_ASSERT(arraysize(g_max_sockets_per_pool) ==
+ HttpNetworkSession::NUM_SOCKET_POOL_TYPES,
+ max_sockets_per_pool_length_mismatch);
+
+// Default to allow up to 6 connections per host. Experiment and tuning may
+// try other values (greater than 0). Too large may cause many problems, such
+// as home routers blocking the connections!?!? See http://crbug.com/12066.
+//
+// WebSocket connections are long-lived, and should be treated differently
+// than normal other connections. 6 connections per group sounded too small
+// for such use, thus we use a larger limit which was determined somewhat
+// arbitrarily.
+// TODO(yutak): Look at the usage and determine the right value after
+// WebSocket protocol stack starts to work.
+int g_max_sockets_per_group[] = {
+ 6, // NORMAL_SOCKET_POOL
+ 30 // WEBSOCKET_SOCKET_POOL
+};
+
+COMPILE_ASSERT(arraysize(g_max_sockets_per_group) ==
+ HttpNetworkSession::NUM_SOCKET_POOL_TYPES,
+ max_sockets_per_group_length_mismatch);
+
+// The max number of sockets to allow per proxy server. This applies both to
+// http and SOCKS proxies. See http://crbug.com/12066 and
+// http://crbug.com/44501 for details about proxy server connection limits.
+int g_max_sockets_per_proxy_server[] = {
+ kDefaultMaxSocketsPerProxyServer, // NORMAL_SOCKET_POOL
+ kDefaultMaxSocketsPerProxyServer // WEBSOCKET_SOCKET_POOL
+};
+
+COMPILE_ASSERT(arraysize(g_max_sockets_per_proxy_server) ==
+ HttpNetworkSession::NUM_SOCKET_POOL_TYPES,
+ max_sockets_per_proxy_server_length_mismatch);
+
+// The meat of the implementation for the InitSocketHandleForHttpRequest,
+// InitSocketHandleForRawConnect and PreconnectSocketsForHttpRequest methods.
+int InitSocketPoolHelper(const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ bool force_tunnel,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ int num_preconnect_streams,
+ ClientSocketHandle* socket_handle,
+ HttpNetworkSession::SocketPoolType socket_pool_type,
+ const OnHostResolutionCallback& resolution_callback,
+ const CompletionCallback& callback) {
+ scoped_refptr<TransportSocketParams> tcp_params;
+ scoped_refptr<HttpProxySocketParams> http_proxy_params;
+ scoped_refptr<SOCKSSocketParams> socks_params;
+ scoped_ptr<HostPortPair> proxy_host_port;
+
+ bool using_ssl = request_url.SchemeIs("https") ||
+ request_url.SchemeIs("wss") || force_spdy_over_ssl;
+
+ HostPortPair origin_host_port =
+ HostPortPair(request_url.HostNoBrackets(),
+ request_url.EffectiveIntPort());
+
+ if (!using_ssl && session->params().testing_fixed_http_port != 0) {
+ origin_host_port.set_port(session->params().testing_fixed_http_port);
+ } else if (using_ssl && session->params().testing_fixed_https_port != 0) {
+ origin_host_port.set_port(session->params().testing_fixed_https_port);
+ }
+
+ bool disable_resolver_cache =
+ request_load_flags & LOAD_BYPASS_CACHE ||
+ request_load_flags & LOAD_VALIDATE_CACHE ||
+ request_load_flags & LOAD_DISABLE_CACHE;
+
+ int load_flags = request_load_flags;
+ if (session->params().ignore_certificate_errors)
+ load_flags |= LOAD_IGNORE_ALL_CERT_ERRORS;
+
+ // Build the string used to uniquely identify connections of this type.
+ // Determine the host and port to connect to.
+ std::string connection_group = origin_host_port.ToString();
+ DCHECK(!connection_group.empty());
+ if (request_url.SchemeIs("ftp")) {
+ // Combining FTP with forced SPDY over SSL would be a "path to madness".
+ // Make sure we never do that.
+ DCHECK(!using_ssl);
+ connection_group = "ftp/" + connection_group;
+ }
+ if (using_ssl) {
+ // All connections in a group should use the same SSLConfig settings.
+ // Encode version_max in the connection group's name, unless it's the
+ // default version_max. (We want the common case to use the shortest
+ // encoding). A version_max of TLS 1.1 is encoded as "ssl(max:3.2)/"
+ // rather than "tlsv1.1/" because the actual protocol version, which
+ // is selected by the server, may not be TLS 1.1. Do not encode
+ // version_min in the connection group's name because version_min
+ // should be the same for all connections, whereas version_max may
+ // change for version fallbacks.
+ std::string prefix = "ssl/";
+ if (ssl_config_for_origin.version_max !=
+ SSLConfigService::default_version_max()) {
+ switch (ssl_config_for_origin.version_max) {
+ case SSL_PROTOCOL_VERSION_TLS1_2:
+ prefix = "ssl(max:3.3)/";
+ break;
+ case SSL_PROTOCOL_VERSION_TLS1_1:
+ prefix = "ssl(max:3.2)/";
+ break;
+ case SSL_PROTOCOL_VERSION_TLS1:
+ prefix = "ssl(max:3.1)/";
+ break;
+ case SSL_PROTOCOL_VERSION_SSL3:
+ prefix = "sslv3/";
+ break;
+ default:
+ CHECK(false);
+ break;
+ }
+ }
+ connection_group = prefix + connection_group;
+ }
+
+ bool ignore_limits = (request_load_flags & LOAD_IGNORE_LIMITS) != 0;
+ if (proxy_info.is_direct()) {
+ tcp_params = new TransportSocketParams(origin_host_port,
+ request_priority,
+ disable_resolver_cache,
+ ignore_limits,
+ resolution_callback);
+ } else {
+ ProxyServer proxy_server = proxy_info.proxy_server();
+ proxy_host_port.reset(new HostPortPair(proxy_server.host_port_pair()));
+ scoped_refptr<TransportSocketParams> proxy_tcp_params(
+ new TransportSocketParams(*proxy_host_port,
+ request_priority,
+ disable_resolver_cache,
+ ignore_limits,
+ resolution_callback));
+
+ if (proxy_info.is_http() || proxy_info.is_https()) {
+ std::string user_agent;
+ request_extra_headers.GetHeader(HttpRequestHeaders::kUserAgent,
+ &user_agent);
+ scoped_refptr<SSLSocketParams> ssl_params;
+ if (proxy_info.is_https()) {
+ // Set ssl_params, and unset proxy_tcp_params
+ ssl_params = new SSLSocketParams(proxy_tcp_params,
+ NULL,
+ NULL,
+ ProxyServer::SCHEME_DIRECT,
+ *proxy_host_port.get(),
+ ssl_config_for_proxy,
+ kPrivacyModeDisabled,
+ load_flags,
+ force_spdy_over_ssl,
+ want_spdy_over_npn);
+ proxy_tcp_params = NULL;
+ }
+
+ http_proxy_params =
+ new HttpProxySocketParams(proxy_tcp_params,
+ ssl_params,
+ request_url,
+ user_agent,
+ origin_host_port,
+ session->http_auth_cache(),
+ session->http_auth_handler_factory(),
+ session->spdy_session_pool(),
+ force_tunnel || using_ssl);
+ } else {
+ DCHECK(proxy_info.is_socks());
+ char socks_version;
+ if (proxy_server.scheme() == ProxyServer::SCHEME_SOCKS5)
+ socks_version = '5';
+ else
+ socks_version = '4';
+ connection_group = base::StringPrintf(
+ "socks%c/%s", socks_version, connection_group.c_str());
+
+ socks_params = new SOCKSSocketParams(proxy_tcp_params,
+ socks_version == '5',
+ origin_host_port,
+ request_priority);
+ }
+ }
+
+ // Change group name if privacy mode is enabled.
+ if (privacy_mode == kPrivacyModeEnabled)
+ connection_group = "pm/" + connection_group;
+
+ // Deal with SSL - which layers on top of any given proxy.
+ if (using_ssl) {
+ scoped_refptr<SSLSocketParams> ssl_params =
+ new SSLSocketParams(tcp_params,
+ socks_params,
+ http_proxy_params,
+ proxy_info.proxy_server().scheme(),
+ origin_host_port,
+ ssl_config_for_origin,
+ privacy_mode,
+ load_flags,
+ force_spdy_over_ssl,
+ want_spdy_over_npn);
+ SSLClientSocketPool* ssl_pool = NULL;
+ if (proxy_info.is_direct()) {
+ ssl_pool = session->GetSSLSocketPool(socket_pool_type);
+ } else {
+ ssl_pool = session->GetSocketPoolForSSLWithProxy(socket_pool_type,
+ *proxy_host_port);
+ }
+
+ if (num_preconnect_streams) {
+ RequestSocketsForPool(ssl_pool, connection_group, ssl_params,
+ num_preconnect_streams, net_log);
+ return OK;
+ }
+
+ return socket_handle->Init(connection_group, ssl_params,
+ request_priority, callback, ssl_pool,
+ net_log);
+ }
+
+ // Finally, get the connection started.
+
+ if (proxy_info.is_http() || proxy_info.is_https()) {
+ HttpProxyClientSocketPool* pool =
+ session->GetSocketPoolForHTTPProxy(socket_pool_type, *proxy_host_port);
+ if (num_preconnect_streams) {
+ RequestSocketsForPool(pool, connection_group, http_proxy_params,
+ num_preconnect_streams, net_log);
+ return OK;
+ }
+
+ return socket_handle->Init(connection_group, http_proxy_params,
+ request_priority, callback,
+ pool, net_log);
+ }
+
+ if (proxy_info.is_socks()) {
+ SOCKSClientSocketPool* pool =
+ session->GetSocketPoolForSOCKSProxy(socket_pool_type, *proxy_host_port);
+ if (num_preconnect_streams) {
+ RequestSocketsForPool(pool, connection_group, socks_params,
+ num_preconnect_streams, net_log);
+ return OK;
+ }
+
+ return socket_handle->Init(connection_group, socks_params,
+ request_priority, callback, pool,
+ net_log);
+ }
+
+ DCHECK(proxy_info.is_direct());
+
+ TransportClientSocketPool* pool =
+ session->GetTransportSocketPool(socket_pool_type);
+ if (num_preconnect_streams) {
+ RequestSocketsForPool(pool, connection_group, tcp_params,
+ num_preconnect_streams, net_log);
+ return OK;
+ }
+
+ return socket_handle->Init(connection_group, tcp_params,
+ request_priority, callback,
+ pool, net_log);
+}
+
+} // namespace
+
+ClientSocketPoolManager::ClientSocketPoolManager() {}
+ClientSocketPoolManager::~ClientSocketPoolManager() {}
+
+// static
+int ClientSocketPoolManager::max_sockets_per_pool(
+ HttpNetworkSession::SocketPoolType pool_type) {
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ return g_max_sockets_per_pool[pool_type];
+}
+
+// static
+void ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count) {
+ DCHECK_LT(0, socket_count);
+ DCHECK_GT(1000, socket_count); // Sanity check.
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ g_max_sockets_per_pool[pool_type] = socket_count;
+ DCHECK_GE(g_max_sockets_per_pool[pool_type],
+ g_max_sockets_per_group[pool_type]);
+}
+
+// static
+int ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::SocketPoolType pool_type) {
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ return g_max_sockets_per_group[pool_type];
+}
+
+// static
+void ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count) {
+ DCHECK_LT(0, socket_count);
+ // The following is a sanity check... but we should NEVER be near this value.
+ DCHECK_GT(100, socket_count);
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ g_max_sockets_per_group[pool_type] = socket_count;
+
+ DCHECK_GE(g_max_sockets_per_pool[pool_type],
+ g_max_sockets_per_group[pool_type]);
+ DCHECK_GE(g_max_sockets_per_proxy_server[pool_type],
+ g_max_sockets_per_group[pool_type]);
+}
+
+// static
+int ClientSocketPoolManager::max_sockets_per_proxy_server(
+ HttpNetworkSession::SocketPoolType pool_type) {
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ return g_max_sockets_per_proxy_server[pool_type];
+}
+
+// static
+void ClientSocketPoolManager::set_max_sockets_per_proxy_server(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count) {
+ DCHECK_LT(0, socket_count);
+ DCHECK_GT(100, socket_count); // Sanity check.
+ DCHECK_LT(pool_type, HttpNetworkSession::NUM_SOCKET_POOL_TYPES);
+ // Assert this case early on. The max number of sockets per group cannot
+ // exceed the max number of sockets per proxy server.
+ DCHECK_LE(g_max_sockets_per_group[pool_type], socket_count);
+ g_max_sockets_per_proxy_server[pool_type] = socket_count;
+}
+
+int InitSocketHandleForHttpRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const OnHostResolutionCallback& resolution_callback,
+ const CompletionCallback& callback) {
+ DCHECK(socket_handle);
+ return InitSocketPoolHelper(
+ request_url, request_extra_headers, request_load_flags, request_priority,
+ session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn,
+ ssl_config_for_origin, ssl_config_for_proxy, false, privacy_mode, net_log,
+ 0, socket_handle, HttpNetworkSession::NORMAL_SOCKET_POOL,
+ resolution_callback, callback);
+}
+
+int InitSocketHandleForWebSocketRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const OnHostResolutionCallback& resolution_callback,
+ const CompletionCallback& callback) {
+ DCHECK(socket_handle);
+ return InitSocketPoolHelper(
+ request_url, request_extra_headers, request_load_flags, request_priority,
+ session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn,
+ ssl_config_for_origin, ssl_config_for_proxy, true, privacy_mode, net_log,
+ 0, socket_handle, HttpNetworkSession::WEBSOCKET_SOCKET_POOL,
+ resolution_callback, callback);
+}
+
+int InitSocketHandleForRawConnect(
+ const HostPortPair& host_port_pair,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const CompletionCallback& callback) {
+ DCHECK(socket_handle);
+ // Synthesize an HttpRequestInfo.
+ GURL request_url = GURL("http://" + host_port_pair.ToString());
+ HttpRequestHeaders request_extra_headers;
+ int request_load_flags = 0;
+ RequestPriority request_priority = MEDIUM;
+
+ return InitSocketPoolHelper(
+ request_url, request_extra_headers, request_load_flags, request_priority,
+ session, proxy_info, false, false, ssl_config_for_origin,
+ ssl_config_for_proxy, true, privacy_mode, net_log, 0, socket_handle,
+ HttpNetworkSession::NORMAL_SOCKET_POOL, OnHostResolutionCallback(),
+ callback);
+}
+
+int PreconnectSocketsForHttpRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ int num_preconnect_streams) {
+ return InitSocketPoolHelper(
+ request_url, request_extra_headers, request_load_flags, request_priority,
+ session, proxy_info, force_spdy_over_ssl, want_spdy_over_npn,
+ ssl_config_for_origin, ssl_config_for_proxy, false, privacy_mode, net_log,
+ num_preconnect_streams, NULL, HttpNetworkSession::NORMAL_SOCKET_POOL,
+ OnHostResolutionCallback(), CompletionCallback());
+}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool_manager.h b/chromium/net/socket/client_socket_pool_manager.h
new file mode 100644
index 00000000000..1b78324f233
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_manager.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+//
+// ClientSocketPoolManager manages access to all ClientSocketPools. It's a
+// simple container for all of them. Most importantly, it handles the lifetime
+// and destruction order properly.
+
+#ifndef NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/http/http_network_session.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+typedef base::Callback<int(const AddressList&, const BoundNetLog& net_log)>
+OnHostResolutionCallback;
+
+class BoundNetLog;
+class ClientSocketHandle;
+class HostPortPair;
+class HttpNetworkSession;
+class HttpProxyClientSocketPool;
+class HttpRequestHeaders;
+class ProxyInfo;
+class TransportClientSocketPool;
+class SOCKSClientSocketPool;
+class SSLClientSocketPool;
+
+struct SSLConfig;
+
+// This should rather be a simple constant but Windows shared libs doesn't
+// really offer much flexiblity in exporting contants.
+enum DefaultMaxValues { kDefaultMaxSocketsPerProxyServer = 32 };
+
+class NET_EXPORT_PRIVATE ClientSocketPoolManager {
+ public:
+ ClientSocketPoolManager();
+ virtual ~ClientSocketPoolManager();
+
+ // The setter methods below affect only newly created socket pools after the
+ // methods are called. Normally they should be called at program startup
+ // before any ClientSocketPoolManagerImpl is created.
+ static int max_sockets_per_pool(HttpNetworkSession::SocketPoolType pool_type);
+ static void set_max_sockets_per_pool(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count);
+
+ static int max_sockets_per_group(
+ HttpNetworkSession::SocketPoolType pool_type);
+ static void set_max_sockets_per_group(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count);
+
+ static int max_sockets_per_proxy_server(
+ HttpNetworkSession::SocketPoolType pool_type);
+ static void set_max_sockets_per_proxy_server(
+ HttpNetworkSession::SocketPoolType pool_type,
+ int socket_count);
+
+ virtual void FlushSocketPoolsWithError(int error) = 0;
+ virtual void CloseIdleSockets() = 0;
+ virtual TransportClientSocketPool* GetTransportSocketPool() = 0;
+ virtual SSLClientSocketPool* GetSSLSocketPool() = 0;
+ virtual SOCKSClientSocketPool* GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) = 0;
+ virtual HttpProxyClientSocketPool* GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy) = 0;
+ virtual SSLClientSocketPool* GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) = 0;
+ // Creates a Value summary of the state of the socket pools. The caller is
+ // responsible for deleting the returned value.
+ virtual base::Value* SocketPoolInfoToValue() const = 0;
+};
+
+// A helper method that uses the passed in proxy information to initialize a
+// ClientSocketHandle with the relevant socket pool. Use this method for
+// HTTP/HTTPS requests. |ssl_config_for_origin| is only used if the request
+// uses SSL and |ssl_config_for_proxy| is used if the proxy server is HTTPS.
+// |resolution_callback| will be invoked after the the hostname is
+// resolved. If |resolution_callback| does not return OK, then the
+// connection will be aborted with that value.
+int InitSocketHandleForHttpRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const OnHostResolutionCallback& resolution_callback,
+ const CompletionCallback& callback);
+
+// A helper method that uses the passed in proxy information to initialize a
+// ClientSocketHandle with the relevant socket pool. Use this method for
+// HTTP/HTTPS requests for WebSocket handshake.
+// |ssl_config_for_origin| is only used if the request
+// uses SSL and |ssl_config_for_proxy| is used if the proxy server is HTTPS.
+// |resolution_callback| will be invoked after the the hostname is
+// resolved. If |resolution_callback| does not return OK, then the
+// connection will be aborted with that value.
+// This function uses WEBSOCKET_SOCKET_POOL socket pools.
+int InitSocketHandleForWebSocketRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const OnHostResolutionCallback& resolution_callback,
+ const CompletionCallback& callback);
+
+// A helper method that uses the passed in proxy information to initialize a
+// ClientSocketHandle with the relevant socket pool. Use this method for
+// a raw socket connection to a host-port pair (that needs to tunnel through
+// the proxies).
+NET_EXPORT int InitSocketHandleForRawConnect(
+ const HostPortPair& host_port_pair,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ ClientSocketHandle* socket_handle,
+ const CompletionCallback& callback);
+
+// Similar to InitSocketHandleForHttpRequest except that it initiates the
+// desired number of preconnect streams from the relevant socket pool.
+int PreconnectSocketsForHttpRequest(
+ const GURL& request_url,
+ const HttpRequestHeaders& request_extra_headers,
+ int request_load_flags,
+ RequestPriority request_priority,
+ HttpNetworkSession* session,
+ const ProxyInfo& proxy_info,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn,
+ const SSLConfig& ssl_config_for_origin,
+ const SSLConfig& ssl_config_for_proxy,
+ PrivacyMode privacy_mode,
+ const BoundNetLog& net_log,
+ int num_preconnect_streams);
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_H_
diff --git a/chromium/net/socket/client_socket_pool_manager_impl.cc b/chromium/net/socket/client_socket_pool_manager_impl.cc
new file mode 100644
index 00000000000..b557874d011
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_manager_impl.cc
@@ -0,0 +1,392 @@
+// Copyright (c) 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.
+
+#include "net/socket/client_socket_pool_manager_impl.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+namespace {
+
+// Appends information about all |socket_pools| to the end of |list|.
+template <class MapType>
+void AddSocketPoolsToList(base::ListValue* list,
+ const MapType& socket_pools,
+ const std::string& type,
+ bool include_nested_pools) {
+ for (typename MapType::const_iterator it = socket_pools.begin();
+ it != socket_pools.end(); it++) {
+ list->Append(it->second->GetInfoAsValue(it->first.ToString(),
+ type,
+ include_nested_pools));
+ }
+}
+
+} // namespace
+
+ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl(
+ NetLog* net_log,
+ ClientSocketFactory* socket_factory,
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier,
+ ServerBoundCertService* server_bound_cert_service,
+ TransportSecurityState* transport_security_state,
+ const std::string& ssl_session_cache_shard,
+ ProxyService* proxy_service,
+ SSLConfigService* ssl_config_service,
+ HttpNetworkSession::SocketPoolType pool_type)
+ : net_log_(net_log),
+ socket_factory_(socket_factory),
+ host_resolver_(host_resolver),
+ cert_verifier_(cert_verifier),
+ server_bound_cert_service_(server_bound_cert_service),
+ transport_security_state_(transport_security_state),
+ ssl_session_cache_shard_(ssl_session_cache_shard),
+ proxy_service_(proxy_service),
+ ssl_config_service_(ssl_config_service),
+ pool_type_(pool_type),
+ transport_pool_histograms_("TCP"),
+ transport_socket_pool_(new TransportClientSocketPool(
+ max_sockets_per_pool(pool_type), max_sockets_per_group(pool_type),
+ &transport_pool_histograms_,
+ host_resolver,
+ socket_factory_,
+ net_log)),
+ ssl_pool_histograms_("SSL2"),
+ ssl_socket_pool_(new SSLClientSocketPool(
+ max_sockets_per_pool(pool_type), max_sockets_per_group(pool_type),
+ &ssl_pool_histograms_,
+ host_resolver,
+ cert_verifier,
+ server_bound_cert_service,
+ transport_security_state,
+ ssl_session_cache_shard,
+ socket_factory,
+ transport_socket_pool_.get(),
+ NULL /* no socks proxy */,
+ NULL /* no http proxy */,
+ ssl_config_service,
+ net_log)),
+ transport_for_socks_pool_histograms_("TCPforSOCKS"),
+ socks_pool_histograms_("SOCK"),
+ transport_for_http_proxy_pool_histograms_("TCPforHTTPProxy"),
+ transport_for_https_proxy_pool_histograms_("TCPforHTTPSProxy"),
+ ssl_for_https_proxy_pool_histograms_("SSLforHTTPSProxy"),
+ http_proxy_pool_histograms_("HTTPProxy"),
+ ssl_socket_pool_for_proxies_histograms_("SSLForProxies") {
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+ClientSocketPoolManagerImpl::~ClientSocketPoolManagerImpl() {
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+void ClientSocketPoolManagerImpl::FlushSocketPoolsWithError(int error) {
+ // Flush the highest level pools first, since higher level pools may release
+ // stuff to the lower level pools.
+
+ for (SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.begin();
+ it != ssl_socket_pools_for_proxies_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.begin();
+ it != http_proxy_socket_pools_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_https_proxies_.begin();
+ it != ssl_socket_pools_for_https_proxies_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_https_proxies_.begin();
+ it != transport_socket_pools_for_https_proxies_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_http_proxies_.begin();
+ it != transport_socket_pools_for_http_proxies_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (SOCKSSocketPoolMap::const_iterator it =
+ socks_socket_pools_.begin();
+ it != socks_socket_pools_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_socks_proxies_.begin();
+ it != transport_socket_pools_for_socks_proxies_.end();
+ ++it)
+ it->second->FlushWithError(error);
+
+ ssl_socket_pool_->FlushWithError(error);
+ transport_socket_pool_->FlushWithError(error);
+}
+
+void ClientSocketPoolManagerImpl::CloseIdleSockets() {
+ // Close sockets in the highest level pools first, since higher level pools'
+ // sockets may release stuff to the lower level pools.
+ for (SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.begin();
+ it != ssl_socket_pools_for_proxies_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.begin();
+ it != http_proxy_socket_pools_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_https_proxies_.begin();
+ it != ssl_socket_pools_for_https_proxies_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_https_proxies_.begin();
+ it != transport_socket_pools_for_https_proxies_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_http_proxies_.begin();
+ it != transport_socket_pools_for_http_proxies_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (SOCKSSocketPoolMap::const_iterator it =
+ socks_socket_pools_.begin();
+ it != socks_socket_pools_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ for (TransportSocketPoolMap::const_iterator it =
+ transport_socket_pools_for_socks_proxies_.begin();
+ it != transport_socket_pools_for_socks_proxies_.end();
+ ++it)
+ it->second->CloseIdleSockets();
+
+ ssl_socket_pool_->CloseIdleSockets();
+ transport_socket_pool_->CloseIdleSockets();
+}
+
+TransportClientSocketPool*
+ClientSocketPoolManagerImpl::GetTransportSocketPool() {
+ return transport_socket_pool_.get();
+}
+
+SSLClientSocketPool* ClientSocketPoolManagerImpl::GetSSLSocketPool() {
+ return ssl_socket_pool_.get();
+}
+
+SOCKSClientSocketPool* ClientSocketPoolManagerImpl::GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) {
+ SOCKSSocketPoolMap::const_iterator it = socks_socket_pools_.find(socks_proxy);
+ if (it != socks_socket_pools_.end()) {
+ DCHECK(ContainsKey(transport_socket_pools_for_socks_proxies_, socks_proxy));
+ return it->second;
+ }
+
+ DCHECK(!ContainsKey(transport_socket_pools_for_socks_proxies_, socks_proxy));
+
+ std::pair<TransportSocketPoolMap::iterator, bool> tcp_ret =
+ transport_socket_pools_for_socks_proxies_.insert(
+ std::make_pair(
+ socks_proxy,
+ new TransportClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &transport_for_socks_pool_histograms_,
+ host_resolver_,
+ socket_factory_,
+ net_log_)));
+ DCHECK(tcp_ret.second);
+
+ std::pair<SOCKSSocketPoolMap::iterator, bool> ret =
+ socks_socket_pools_.insert(
+ std::make_pair(socks_proxy, new SOCKSClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &socks_pool_histograms_,
+ host_resolver_,
+ tcp_ret.first->second,
+ net_log_)));
+
+ return ret.first->second;
+}
+
+HttpProxyClientSocketPool*
+ClientSocketPoolManagerImpl::GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy) {
+ HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.find(http_proxy);
+ if (it != http_proxy_socket_pools_.end()) {
+ DCHECK(ContainsKey(transport_socket_pools_for_http_proxies_, http_proxy));
+ DCHECK(ContainsKey(transport_socket_pools_for_https_proxies_, http_proxy));
+ DCHECK(ContainsKey(ssl_socket_pools_for_https_proxies_, http_proxy));
+ return it->second;
+ }
+
+ DCHECK(!ContainsKey(transport_socket_pools_for_http_proxies_, http_proxy));
+ DCHECK(!ContainsKey(transport_socket_pools_for_https_proxies_, http_proxy));
+ DCHECK(!ContainsKey(ssl_socket_pools_for_https_proxies_, http_proxy));
+
+ std::pair<TransportSocketPoolMap::iterator, bool> tcp_http_ret =
+ transport_socket_pools_for_http_proxies_.insert(
+ std::make_pair(
+ http_proxy,
+ new TransportClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &transport_for_http_proxy_pool_histograms_,
+ host_resolver_,
+ socket_factory_,
+ net_log_)));
+ DCHECK(tcp_http_ret.second);
+
+ std::pair<TransportSocketPoolMap::iterator, bool> tcp_https_ret =
+ transport_socket_pools_for_https_proxies_.insert(
+ std::make_pair(
+ http_proxy,
+ new TransportClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &transport_for_https_proxy_pool_histograms_,
+ host_resolver_,
+ socket_factory_,
+ net_log_)));
+ DCHECK(tcp_https_ret.second);
+
+ std::pair<SSLSocketPoolMap::iterator, bool> ssl_https_ret =
+ ssl_socket_pools_for_https_proxies_.insert(std::make_pair(
+ http_proxy,
+ new SSLClientSocketPool(max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &ssl_for_https_proxy_pool_histograms_,
+ host_resolver_,
+ cert_verifier_,
+ server_bound_cert_service_,
+ transport_security_state_,
+ ssl_session_cache_shard_,
+ socket_factory_,
+ tcp_https_ret.first->second /* https proxy */,
+ NULL /* no socks proxy */,
+ NULL /* no http proxy */,
+ ssl_config_service_.get(),
+ net_log_)));
+ DCHECK(tcp_https_ret.second);
+
+ std::pair<HTTPProxySocketPoolMap::iterator, bool> ret =
+ http_proxy_socket_pools_.insert(
+ std::make_pair(
+ http_proxy,
+ new HttpProxyClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &http_proxy_pool_histograms_,
+ host_resolver_,
+ tcp_http_ret.first->second,
+ ssl_https_ret.first->second,
+ net_log_)));
+
+ return ret.first->second;
+}
+
+SSLClientSocketPool* ClientSocketPoolManagerImpl::GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) {
+ SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.find(proxy_server);
+ if (it != ssl_socket_pools_for_proxies_.end())
+ return it->second;
+
+ SSLClientSocketPool* new_pool = new SSLClientSocketPool(
+ max_sockets_per_proxy_server(pool_type_),
+ max_sockets_per_group(pool_type_),
+ &ssl_pool_histograms_,
+ host_resolver_,
+ cert_verifier_,
+ server_bound_cert_service_,
+ transport_security_state_,
+ ssl_session_cache_shard_,
+ socket_factory_,
+ NULL, /* no tcp pool, we always go through a proxy */
+ GetSocketPoolForSOCKSProxy(proxy_server),
+ GetSocketPoolForHTTPProxy(proxy_server),
+ ssl_config_service_.get(),
+ net_log_);
+
+ std::pair<SSLSocketPoolMap::iterator, bool> ret =
+ ssl_socket_pools_for_proxies_.insert(std::make_pair(proxy_server,
+ new_pool));
+
+ return ret.first->second;
+}
+
+base::Value* ClientSocketPoolManagerImpl::SocketPoolInfoToValue() const {
+ base::ListValue* list = new base::ListValue();
+ list->Append(transport_socket_pool_->GetInfoAsValue("transport_socket_pool",
+ "transport_socket_pool",
+ false));
+ // Third parameter is false because |ssl_socket_pool_| uses
+ // |transport_socket_pool_| internally, and do not want to add it a second
+ // time.
+ list->Append(ssl_socket_pool_->GetInfoAsValue("ssl_socket_pool",
+ "ssl_socket_pool",
+ false));
+ AddSocketPoolsToList(list,
+ http_proxy_socket_pools_,
+ "http_proxy_socket_pool",
+ true);
+ AddSocketPoolsToList(list,
+ socks_socket_pools_,
+ "socks_socket_pool",
+ true);
+
+ // Third parameter is false because |ssl_socket_pools_for_proxies_| use
+ // socket pools in |http_proxy_socket_pools_| and |socks_socket_pools_|.
+ AddSocketPoolsToList(list,
+ ssl_socket_pools_for_proxies_,
+ "ssl_socket_pool_for_proxies",
+ false);
+ return list;
+}
+
+void ClientSocketPoolManagerImpl::OnCertAdded(const X509Certificate* cert) {
+ FlushSocketPoolsWithError(ERR_NETWORK_CHANGED);
+}
+
+void ClientSocketPoolManagerImpl::OnCertTrustChanged(
+ const X509Certificate* cert) {
+ // We should flush the socket pools if we removed trust from a
+ // cert, because a previously trusted server may have become
+ // untrusted.
+ //
+ // We should not flush the socket pools if we added trust to a
+ // cert.
+ //
+ // Since the OnCertTrustChanged method doesn't tell us what
+ // kind of trust change it is, we have to flush the socket
+ // pools to be safe.
+ FlushSocketPoolsWithError(ERR_NETWORK_CHANGED);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/client_socket_pool_manager_impl.h b/chromium/net/socket/client_socket_pool_manager_impl.h
new file mode 100644
index 00000000000..8f6e618d2e1
--- /dev/null
+++ b/chromium/net/socket/client_socket_pool_manager_impl.h
@@ -0,0 +1,150 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_IMPL_H_
+#define NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_IMPL_H_
+
+#include <map>
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/template_util.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/cert/cert_database.h"
+#include "net/http/http_network_session.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/client_socket_pool_manager.h"
+
+namespace net {
+
+class CertVerifier;
+class ClientSocketFactory;
+class ClientSocketPoolHistograms;
+class HttpProxyClientSocketPool;
+class HostResolver;
+class NetLog;
+class ServerBoundCertService;
+class ProxyService;
+class SOCKSClientSocketPool;
+class SSLClientSocketPool;
+class SSLConfigService;
+class TransportClientSocketPool;
+class TransportSecurityState;
+
+namespace internal {
+
+// A helper class for auto-deleting Values in the destructor.
+template <typename Key, typename Value>
+class OwnedPoolMap : public std::map<Key, Value> {
+ public:
+ OwnedPoolMap() {
+ COMPILE_ASSERT(base::is_pointer<Value>::value,
+ value_must_be_a_pointer);
+ }
+
+ ~OwnedPoolMap() {
+ STLDeleteValues(this);
+ }
+};
+
+} // namespace internal
+
+class ClientSocketPoolManagerImpl : public base::NonThreadSafe,
+ public ClientSocketPoolManager,
+ public CertDatabase::Observer {
+ public:
+ ClientSocketPoolManagerImpl(NetLog* net_log,
+ ClientSocketFactory* socket_factory,
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier,
+ ServerBoundCertService* server_bound_cert_service,
+ TransportSecurityState* transport_security_state,
+ const std::string& ssl_session_cache_shard,
+ ProxyService* proxy_service,
+ SSLConfigService* ssl_config_service,
+ HttpNetworkSession::SocketPoolType pool_type);
+ virtual ~ClientSocketPoolManagerImpl();
+
+ virtual void FlushSocketPoolsWithError(int error) OVERRIDE;
+ virtual void CloseIdleSockets() OVERRIDE;
+
+ virtual TransportClientSocketPool* GetTransportSocketPool() OVERRIDE;
+
+ virtual SSLClientSocketPool* GetSSLSocketPool() OVERRIDE;
+
+ virtual SOCKSClientSocketPool* GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) OVERRIDE;
+
+ virtual HttpProxyClientSocketPool* GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy) OVERRIDE;
+
+ virtual SSLClientSocketPool* GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) OVERRIDE;
+
+ // Creates a Value summary of the state of the socket pools. The caller is
+ // responsible for deleting the returned value.
+ virtual base::Value* SocketPoolInfoToValue() const OVERRIDE;
+
+ // CertDatabase::Observer methods:
+ virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE;
+ virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE;
+
+ private:
+ typedef internal::OwnedPoolMap<HostPortPair, TransportClientSocketPool*>
+ TransportSocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, SOCKSClientSocketPool*>
+ SOCKSSocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, HttpProxyClientSocketPool*>
+ HTTPProxySocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, SSLClientSocketPool*>
+ SSLSocketPoolMap;
+
+ NetLog* const net_log_;
+ ClientSocketFactory* const socket_factory_;
+ HostResolver* const host_resolver_;
+ CertVerifier* const cert_verifier_;
+ ServerBoundCertService* const server_bound_cert_service_;
+ TransportSecurityState* const transport_security_state_;
+ const std::string ssl_session_cache_shard_;
+ ProxyService* const proxy_service_;
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+ const HttpNetworkSession::SocketPoolType pool_type_;
+
+ // Note: this ordering is important.
+
+ ClientSocketPoolHistograms transport_pool_histograms_;
+ scoped_ptr<TransportClientSocketPool> transport_socket_pool_;
+
+ ClientSocketPoolHistograms ssl_pool_histograms_;
+ scoped_ptr<SSLClientSocketPool> ssl_socket_pool_;
+
+ ClientSocketPoolHistograms transport_for_socks_pool_histograms_;
+ TransportSocketPoolMap transport_socket_pools_for_socks_proxies_;
+
+ ClientSocketPoolHistograms socks_pool_histograms_;
+ SOCKSSocketPoolMap socks_socket_pools_;
+
+ ClientSocketPoolHistograms transport_for_http_proxy_pool_histograms_;
+ TransportSocketPoolMap transport_socket_pools_for_http_proxies_;
+
+ ClientSocketPoolHistograms transport_for_https_proxy_pool_histograms_;
+ TransportSocketPoolMap transport_socket_pools_for_https_proxies_;
+
+ ClientSocketPoolHistograms ssl_for_https_proxy_pool_histograms_;
+ SSLSocketPoolMap ssl_socket_pools_for_https_proxies_;
+
+ ClientSocketPoolHistograms http_proxy_pool_histograms_;
+ HTTPProxySocketPoolMap http_proxy_socket_pools_;
+
+ ClientSocketPoolHistograms ssl_socket_pool_for_proxies_histograms_;
+ SSLSocketPoolMap ssl_socket_pools_for_proxies_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolManagerImpl);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_IMPL_H_
diff --git a/chromium/net/socket/deterministic_socket_data_unittest.cc b/chromium/net/socket/deterministic_socket_data_unittest.cc
new file mode 100644
index 00000000000..eba01b5e9cc
--- /dev/null
+++ b/chromium/net/socket/deterministic_socket_data_unittest.cc
@@ -0,0 +1,621 @@
+// Copyright (c) 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.
+
+#include "net/socket/socket_test_util.h"
+
+#include <string.h>
+
+#include "base/memory/ref_counted.h"
+#include "testing/platform_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+static const char kMsg1[] = "\0hello!\xff";
+static const int kLen1 = arraysize(kMsg1);
+static const char kMsg2[] = "\012345678\0";
+static const int kLen2 = arraysize(kMsg2);
+static const char kMsg3[] = "bye!";
+static const int kLen3 = arraysize(kMsg3);
+
+} // anonymous namespace
+
+namespace net {
+
+class DeterministicSocketDataTest : public PlatformTest {
+ public:
+ DeterministicSocketDataTest();
+
+ virtual void TearDown();
+
+ void ReentrantReadCallback(int len, int rv);
+ void ReentrantWriteCallback(const char* data, int len, int rv);
+
+ protected:
+ void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes,
+ size_t writes_count);
+
+ void AssertSyncReadEquals(const char* data, int len);
+ void AssertAsyncReadEquals(const char* data, int len);
+ void AssertReadReturns(const char* data, int len, int rv);
+ void AssertReadBufferEquals(const char* data, int len);
+
+ void AssertSyncWriteEquals(const char* data, int len);
+ void AssertAsyncWriteEquals(const char* data, int len);
+ void AssertWriteReturns(const char* data, int len, int rv);
+
+ TestCompletionCallback read_callback_;
+ TestCompletionCallback write_callback_;
+ StreamSocket* sock_;
+ scoped_ptr<DeterministicSocketData> data_;
+
+ private:
+ scoped_refptr<IOBuffer> read_buf_;
+ MockConnect connect_data_;
+
+ HostPortPair endpoint_;
+ scoped_refptr<TransportSocketParams> tcp_params_;
+ ClientSocketPoolHistograms histograms_;
+ DeterministicMockClientSocketFactory socket_factory_;
+ MockTransportClientSocketPool socket_pool_;
+ ClientSocketHandle connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeterministicSocketDataTest);
+};
+
+DeterministicSocketDataTest::DeterministicSocketDataTest()
+ : sock_(NULL),
+ read_buf_(NULL),
+ connect_data_(SYNCHRONOUS, OK),
+ endpoint_("www.google.com", 443),
+ tcp_params_(new TransportSocketParams(endpoint_,
+ LOWEST,
+ false,
+ false,
+ OnHostResolutionCallback())),
+ histograms_(std::string()),
+ socket_pool_(10, 10, &histograms_, &socket_factory_) {}
+
+void DeterministicSocketDataTest::TearDown() {
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+}
+
+void DeterministicSocketDataTest::Initialize(MockRead* reads,
+ size_t reads_count,
+ MockWrite* writes,
+ size_t writes_count) {
+ data_.reset(new DeterministicSocketData(reads, reads_count,
+ writes, writes_count));
+ data_->set_connect_data(connect_data_);
+ socket_factory_.AddSocketDataProvider(data_.get());
+
+ // Perform the TCP connect
+ EXPECT_EQ(OK,
+ connection_.Init(endpoint_.ToString(),
+ tcp_params_,
+ LOWEST,
+ CompletionCallback(),
+ reinterpret_cast<TransportClientSocketPool*>(&socket_pool_),
+ BoundNetLog()));
+ sock_ = connection_.socket();
+}
+
+void DeterministicSocketDataTest::AssertSyncReadEquals(const char* data,
+ int len) {
+ // Issue the read, which will complete immediately
+ AssertReadReturns(data, len, len);
+ AssertReadBufferEquals(data, len);
+}
+
+void DeterministicSocketDataTest::AssertAsyncReadEquals(const char* data,
+ int len) {
+ // Issue the read, which will be completed asynchronously
+ AssertReadReturns(data, len, ERR_IO_PENDING);
+
+ EXPECT_FALSE(read_callback_.have_result());
+ EXPECT_TRUE(sock_->IsConnected());
+ data_->RunFor(1); // Runs 1 step, to cause the callbacks to be invoked
+
+ // Now the read should complete
+ ASSERT_EQ(len, read_callback_.WaitForResult());
+ AssertReadBufferEquals(data, len);
+}
+
+void DeterministicSocketDataTest::AssertReadReturns(const char* data,
+ int len, int rv) {
+ read_buf_ = new IOBuffer(len);
+ ASSERT_EQ(rv, sock_->Read(read_buf_.get(), len, read_callback_.callback()));
+}
+
+void DeterministicSocketDataTest::AssertReadBufferEquals(const char* data,
+ int len) {
+ ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len));
+}
+
+void DeterministicSocketDataTest::AssertSyncWriteEquals(const char* data,
+ int len) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ memcpy(buf->data(), data, len);
+
+ // Issue the write, which will complete immediately
+ ASSERT_EQ(len, sock_->Write(buf.get(), len, write_callback_.callback()));
+}
+
+void DeterministicSocketDataTest::AssertAsyncWriteEquals(const char* data,
+ int len) {
+ // Issue the read, which will be completed asynchronously
+ AssertWriteReturns(data, len, ERR_IO_PENDING);
+
+ EXPECT_FALSE(read_callback_.have_result());
+ EXPECT_TRUE(sock_->IsConnected());
+ data_->RunFor(1); // Runs 1 step, to cause the callbacks to be invoked
+
+ ASSERT_EQ(len, write_callback_.WaitForResult());
+}
+
+void DeterministicSocketDataTest::AssertWriteReturns(const char* data,
+ int len, int rv) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ memcpy(buf->data(), data, len);
+
+ // Issue the read, which will complete asynchronously
+ ASSERT_EQ(rv, sock_->Write(buf.get(), len, write_callback_.callback()));
+}
+
+void DeterministicSocketDataTest::ReentrantReadCallback(int len, int rv) {
+ scoped_refptr<IOBuffer> read_buf(new IOBuffer(len));
+ EXPECT_EQ(len,
+ sock_->Read(
+ read_buf.get(),
+ len,
+ base::Bind(&DeterministicSocketDataTest::ReentrantReadCallback,
+ base::Unretained(this),
+ len)));
+}
+
+void DeterministicSocketDataTest::ReentrantWriteCallback(
+ const char* data, int len, int rv) {
+ scoped_refptr<IOBuffer> write_buf(new IOBuffer(len));
+ memcpy(write_buf->data(), data, len);
+ EXPECT_EQ(len,
+ sock_->Write(
+ write_buf.get(),
+ len,
+ base::Bind(&DeterministicSocketDataTest::ReentrantWriteCallback,
+ base::Unretained(this),
+ data,
+ len)));
+}
+
+// ----------- Read
+
+TEST_F(DeterministicSocketDataTest, SingleSyncReadWhileStopped) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 1), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ data_->SetStopped(true);
+ AssertReadReturns(kMsg1, kLen1, ERR_UNEXPECTED);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleSyncReadTooEarly) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 1), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, 0)
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ data_->StopAfter(2);
+ ASSERT_FALSE(data_->stopped());
+ AssertReadReturns(kMsg1, kLen1, ERR_UNEXPECTED);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleSyncRead) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 1), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+ // Make sure we don't stop before we've read all the data
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MultipleSyncReads) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg2, kLen2, 1), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg3, kLen3, 3), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg2, kLen2, 4), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg3, kLen3, 5), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 6), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 7), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ // Make sure we don't stop before we've read all the data
+ data_->StopAfter(10);
+ AssertSyncReadEquals(kMsg1, kLen1);
+ AssertSyncReadEquals(kMsg2, kLen2);
+ AssertSyncReadEquals(kMsg3, kLen3);
+ AssertSyncReadEquals(kMsg3, kLen3);
+ AssertSyncReadEquals(kMsg2, kLen2);
+ AssertSyncReadEquals(kMsg3, kLen3);
+ AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleAsyncRead) {
+ MockRead reads[] = {
+ MockRead(ASYNC, kMsg1, kLen1, 0), // Async Read
+ MockRead(SYNCHRONOUS, 0, 1), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ AssertAsyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MultipleAsyncReads) {
+ MockRead reads[] = {
+ MockRead(ASYNC, kMsg1, kLen1, 0), // Async Read
+ MockRead(ASYNC, kMsg2, kLen2, 1), // Async Read
+ MockRead(ASYNC, kMsg3, kLen3, 2), // Async Read
+ MockRead(ASYNC, kMsg3, kLen3, 3), // Async Read
+ MockRead(ASYNC, kMsg2, kLen2, 4), // Async Read
+ MockRead(ASYNC, kMsg3, kLen3, 5), // Async Read
+ MockRead(ASYNC, kMsg1, kLen1, 6), // Async Read
+ MockRead(SYNCHRONOUS, 0, 7), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ AssertAsyncReadEquals(kMsg1, kLen1);
+ AssertAsyncReadEquals(kMsg2, kLen2);
+ AssertAsyncReadEquals(kMsg3, kLen3);
+ AssertAsyncReadEquals(kMsg3, kLen3);
+ AssertAsyncReadEquals(kMsg2, kLen2);
+ AssertAsyncReadEquals(kMsg3, kLen3);
+ AssertAsyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MixedReads) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(ASYNC, kMsg2, kLen2, 1), // Async Read
+ MockRead(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Read
+ MockRead(ASYNC, kMsg3, kLen3, 3), // Async Read
+ MockRead(SYNCHRONOUS, kMsg2, kLen2, 4), // Sync Read
+ MockRead(ASYNC, kMsg3, kLen3, 5), // Async Read
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 6), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 7), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+ AssertAsyncReadEquals(kMsg2, kLen2);
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg3, kLen3);
+ AssertAsyncReadEquals(kMsg3, kLen3);
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg2, kLen2);
+ AssertAsyncReadEquals(kMsg3, kLen3);
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, SyncReadFromCompletionCallback) {
+ MockRead reads[] = {
+ MockRead(ASYNC, kMsg1, kLen1, 0), // Async Read
+ MockRead(SYNCHRONOUS, kMsg2, kLen2, 1), // Sync Read
+ };
+
+ Initialize(reads, arraysize(reads), NULL, 0);
+
+ data_->StopAfter(2);
+
+ scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(
+ read_buf.get(),
+ kLen1,
+ base::Bind(&DeterministicSocketDataTest::ReentrantReadCallback,
+ base::Unretained(this),
+ kLen2)));
+ data_->Run();
+}
+
+// ----------- Write
+
+TEST_F(DeterministicSocketDataTest, SingleSyncWriteWhileStopped) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ data_->SetStopped(true);
+ AssertWriteReturns(kMsg1, kLen1, ERR_UNEXPECTED);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleSyncWriteTooEarly) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 1), // Sync Write
+ };
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 0, 0)
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ data_->StopAfter(2);
+ ASSERT_FALSE(data_->stopped());
+ AssertWriteReturns(kMsg1, kLen1, ERR_UNEXPECTED);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleSyncWrite) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ // Make sure we don't stop before we've read all the data
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MultipleSyncWrites) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg2, kLen2, 1), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 3), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg2, kLen2, 4), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 5), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 6), // Sync Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ // Make sure we don't stop before we've read all the data
+ data_->StopAfter(10);
+ AssertSyncWriteEquals(kMsg1, kLen1);
+ AssertSyncWriteEquals(kMsg2, kLen2);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+ AssertSyncWriteEquals(kMsg2, kLen2);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+ AssertSyncWriteEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, SingleAsyncWrite) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg1, kLen1, 0), // Async Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ AssertAsyncWriteEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MultipleAsyncWrites) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg1, kLen1, 0), // Async Write
+ MockWrite(ASYNC, kMsg2, kLen2, 1), // Async Write
+ MockWrite(ASYNC, kMsg3, kLen3, 2), // Async Write
+ MockWrite(ASYNC, kMsg3, kLen3, 3), // Async Write
+ MockWrite(ASYNC, kMsg2, kLen2, 4), // Async Write
+ MockWrite(ASYNC, kMsg3, kLen3, 5), // Async Write
+ MockWrite(ASYNC, kMsg1, kLen1, 6), // Async Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ AssertAsyncWriteEquals(kMsg1, kLen1);
+ AssertAsyncWriteEquals(kMsg2, kLen2);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ AssertAsyncWriteEquals(kMsg2, kLen2);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ AssertAsyncWriteEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, MixedWrites) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Write
+ MockWrite(ASYNC, kMsg2, kLen2, 1), // Async Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Write
+ MockWrite(ASYNC, kMsg3, kLen3, 3), // Async Write
+ MockWrite(SYNCHRONOUS, kMsg2, kLen2, 4), // Sync Write
+ MockWrite(ASYNC, kMsg3, kLen3, 5), // Async Write
+ MockWrite(SYNCHRONOUS, kMsg1, kLen1, 6), // Sync Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg1, kLen1);
+ AssertAsyncWriteEquals(kMsg2, kLen2);
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg2, kLen2);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg1, kLen1);
+}
+
+TEST_F(DeterministicSocketDataTest, SyncWriteFromCompletionCallback) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg1, kLen1, 0), // Async Write
+ MockWrite(SYNCHRONOUS, kMsg2, kLen2, 1), // Sync Write
+ };
+
+ Initialize(NULL, 0, writes, arraysize(writes));
+
+ data_->StopAfter(2);
+
+ scoped_refptr<IOBuffer> write_buf(new IOBuffer(kLen1));
+ memcpy(write_buf->data(), kMsg1, kLen1);
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Write(
+ write_buf.get(),
+ kLen1,
+ base::Bind(&DeterministicSocketDataTest::ReentrantWriteCallback,
+ base::Unretained(this),
+ kMsg2,
+ kLen2)));
+ data_->Run();
+}
+
+// ----------- Mixed Reads and Writes
+
+TEST_F(DeterministicSocketDataTest, MixedSyncOperations) {
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(SYNCHRONOUS, kMsg2, kLen2, 3), // Sync Read
+ MockRead(SYNCHRONOUS, 0, 4), // EOF
+ };
+
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, kMsg2, kLen2, 1), // Sync Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Write
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ // Make sure we don't stop before we've read/written everything
+ data_->StopAfter(10);
+ AssertSyncReadEquals(kMsg1, kLen1);
+ AssertSyncWriteEquals(kMsg2, kLen2);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+ AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(DeterministicSocketDataTest, MixedAsyncOperations) {
+ MockRead reads[] = {
+ MockRead(ASYNC, kMsg1, kLen1, 0), // Sync Read
+ MockRead(ASYNC, kMsg2, kLen2, 3), // Sync Read
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg2, kLen2, 1), // Sync Write
+ MockWrite(ASYNC, kMsg3, kLen3, 2), // Sync Write
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertAsyncReadEquals(kMsg1, kLen1);
+ AssertAsyncWriteEquals(kMsg2, kLen2);
+ AssertAsyncWriteEquals(kMsg3, kLen3);
+ AssertAsyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(DeterministicSocketDataTest, InterleavedAsyncOperations) {
+ // Order of completion is read, write, write, read
+ MockRead reads[] = {
+ MockRead(ASYNC, kMsg1, kLen1, 0), // Async Read
+ MockRead(ASYNC, kMsg2, kLen2, 3), // Async Read
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg2, kLen2, 1), // Async Write
+ MockWrite(ASYNC, kMsg3, kLen3, 2), // Async Write
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ // Issue the write, which will block until the read completes
+ AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+ // Issue the read which will return first
+ AssertReadReturns(kMsg1, kLen1, ERR_IO_PENDING);
+
+ data_->RunFor(1);
+ ASSERT_TRUE(read_callback_.have_result());
+ ASSERT_EQ(kLen1, read_callback_.WaitForResult());
+ AssertReadBufferEquals(kMsg1, kLen1);
+
+ data_->RunFor(1);
+ ASSERT_TRUE(write_callback_.have_result());
+ ASSERT_EQ(kLen2, write_callback_.WaitForResult());
+
+ data_->StopAfter(1);
+ // Issue the read, which will block until the write completes
+ AssertReadReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+ // Issue the writes which will return first
+ AssertWriteReturns(kMsg3, kLen3, ERR_IO_PENDING);
+
+ data_->RunFor(1);
+ ASSERT_TRUE(write_callback_.have_result());
+ ASSERT_EQ(kLen3, write_callback_.WaitForResult());
+
+ data_->RunFor(1);
+ ASSERT_TRUE(read_callback_.have_result());
+ ASSERT_EQ(kLen2, read_callback_.WaitForResult());
+ AssertReadBufferEquals(kMsg2, kLen2);
+}
+
+TEST_F(DeterministicSocketDataTest, InterleavedMixedOperations) {
+ // Order of completion is read, write, write, read
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, kMsg1, kLen1, 0), // Sync Read
+ MockRead(ASYNC, kMsg2, kLen2, 3), // Async Read
+ MockRead(SYNCHRONOUS, 0, 4), // EOF
+ };
+
+ MockWrite writes[] = {
+ MockWrite(ASYNC, kMsg2, kLen2, 1), // Async Write
+ MockWrite(SYNCHRONOUS, kMsg3, kLen3, 2), // Sync Write
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ // Issue the write, which will block until the read completes
+ AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+ // Issue the writes which will complete immediately
+ data_->StopAfter(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+
+ data_->RunFor(1);
+ ASSERT_TRUE(write_callback_.have_result());
+ ASSERT_EQ(kLen2, write_callback_.WaitForResult());
+
+ // Issue the read, which will block until the write completes
+ AssertReadReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+ // Issue the writes which will complete immediately
+ data_->StopAfter(1);
+ AssertSyncWriteEquals(kMsg3, kLen3);
+
+ data_->RunFor(1);
+ ASSERT_TRUE(read_callback_.have_result());
+ ASSERT_EQ(kLen2, read_callback_.WaitForResult());
+ AssertReadBufferEquals(kMsg2, kLen2);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/mock_client_socket_pool_manager.cc b/chromium/net/socket/mock_client_socket_pool_manager.cc
new file mode 100644
index 00000000000..0496adb9636
--- /dev/null
+++ b/chromium/net/socket/mock_client_socket_pool_manager.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 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.
+
+#include "net/socket/mock_client_socket_pool_manager.h"
+
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+
+namespace net {
+
+MockClientSocketPoolManager::MockClientSocketPoolManager() {}
+MockClientSocketPoolManager::~MockClientSocketPoolManager() {}
+
+void MockClientSocketPoolManager::SetTransportSocketPool(
+ TransportClientSocketPool* pool) {
+ transport_socket_pool_.reset(pool);
+}
+
+void MockClientSocketPoolManager::SetSSLSocketPool(
+ SSLClientSocketPool* pool) {
+ ssl_socket_pool_.reset(pool);
+}
+
+void MockClientSocketPoolManager::SetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy,
+ SOCKSClientSocketPool* pool) {
+ socks_socket_pools_[socks_proxy] = pool;
+}
+
+void MockClientSocketPoolManager::SetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy,
+ HttpProxyClientSocketPool* pool) {
+ http_proxy_socket_pools_[http_proxy] = pool;
+}
+
+void MockClientSocketPoolManager::SetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server,
+ SSLClientSocketPool* pool) {
+ ssl_socket_pools_for_proxies_[proxy_server] = pool;
+}
+
+void MockClientSocketPoolManager::FlushSocketPoolsWithError(int error) {
+ NOTIMPLEMENTED();
+}
+
+void MockClientSocketPoolManager::CloseIdleSockets() {
+ NOTIMPLEMENTED();
+}
+
+TransportClientSocketPool*
+MockClientSocketPoolManager::GetTransportSocketPool() {
+ return transport_socket_pool_.get();
+}
+
+SSLClientSocketPool* MockClientSocketPoolManager::GetSSLSocketPool() {
+ return ssl_socket_pool_.get();
+}
+
+SOCKSClientSocketPool* MockClientSocketPoolManager::GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) {
+ SOCKSSocketPoolMap::const_iterator it = socks_socket_pools_.find(socks_proxy);
+ if (it != socks_socket_pools_.end())
+ return it->second;
+ return NULL;
+}
+
+HttpProxyClientSocketPool*
+MockClientSocketPoolManager::GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy) {
+ HTTPProxySocketPoolMap::const_iterator it =
+ http_proxy_socket_pools_.find(http_proxy);
+ if (it != http_proxy_socket_pools_.end())
+ return it->second;
+ return NULL;
+}
+
+SSLClientSocketPool* MockClientSocketPoolManager::GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) {
+ SSLSocketPoolMap::const_iterator it =
+ ssl_socket_pools_for_proxies_.find(proxy_server);
+ if (it != ssl_socket_pools_for_proxies_.end())
+ return it->second;
+ return NULL;
+}
+
+base::Value* MockClientSocketPoolManager::SocketPoolInfoToValue() const {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/mock_client_socket_pool_manager.h b/chromium/net/socket/mock_client_socket_pool_manager.h
new file mode 100644
index 00000000000..c2c3792a4f6
--- /dev/null
+++ b/chromium/net/socket/mock_client_socket_pool_manager.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 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_SOCKET_MOCK_CLIENT_SOCKET_POOL_MANAGER_H_
+#define NET_SOCKET_MOCK_CLIENT_SOCKET_POOL_MANAGER_H_
+
+#include "base/basictypes.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/client_socket_pool_manager_impl.h"
+
+namespace net {
+
+class MockClientSocketPoolManager : public ClientSocketPoolManager {
+ public:
+ MockClientSocketPoolManager();
+ virtual ~MockClientSocketPoolManager();
+
+ // Sets "override" socket pools that get used instead.
+ void SetTransportSocketPool(TransportClientSocketPool* pool);
+ void SetSSLSocketPool(SSLClientSocketPool* pool);
+ void SetSocketPoolForSOCKSProxy(const HostPortPair& socks_proxy,
+ SOCKSClientSocketPool* pool);
+ void SetSocketPoolForHTTPProxy(const HostPortPair& http_proxy,
+ HttpProxyClientSocketPool* pool);
+ void SetSocketPoolForSSLWithProxy(const HostPortPair& proxy_server,
+ SSLClientSocketPool* pool);
+
+ // ClientSocketPoolManager methods:
+ virtual void FlushSocketPoolsWithError(int error) OVERRIDE;
+ virtual void CloseIdleSockets() OVERRIDE;
+ virtual TransportClientSocketPool* GetTransportSocketPool() OVERRIDE;
+ virtual SSLClientSocketPool* GetSSLSocketPool() OVERRIDE;
+ virtual SOCKSClientSocketPool* GetSocketPoolForSOCKSProxy(
+ const HostPortPair& socks_proxy) OVERRIDE;
+ virtual HttpProxyClientSocketPool* GetSocketPoolForHTTPProxy(
+ const HostPortPair& http_proxy) OVERRIDE;
+ virtual SSLClientSocketPool* GetSocketPoolForSSLWithProxy(
+ const HostPortPair& proxy_server) OVERRIDE;
+ virtual base::Value* SocketPoolInfoToValue() const OVERRIDE;
+
+ private:
+ typedef internal::OwnedPoolMap<HostPortPair, TransportClientSocketPool*>
+ TransportSocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, SOCKSClientSocketPool*>
+ SOCKSSocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, HttpProxyClientSocketPool*>
+ HTTPProxySocketPoolMap;
+ typedef internal::OwnedPoolMap<HostPortPair, SSLClientSocketPool*>
+ SSLSocketPoolMap;
+
+ scoped_ptr<TransportClientSocketPool> transport_socket_pool_;
+ scoped_ptr<SSLClientSocketPool> ssl_socket_pool_;
+ SOCKSSocketPoolMap socks_socket_pools_;
+ HTTPProxySocketPoolMap http_proxy_socket_pools_;
+ SSLSocketPoolMap ssl_socket_pools_for_proxies_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockClientSocketPoolManager);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_MOCK_CLIENT_SOCKET_POOL_MANAGER_H_
diff --git a/chromium/net/socket/next_proto.h b/chromium/net/socket/next_proto.h
new file mode 100644
index 00000000000..0bd307a3778
--- /dev/null
+++ b/chromium/net/socket/next_proto.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_NEXT_PROTO_H_
+#define NET_SOCKET_NEXT_PROTO_H_
+
+namespace net {
+
+// Next Protocol Negotiation (NPN), if successful, results in agreement on an
+// application-level string that specifies the application level protocol to
+// use over the TLS connection. NextProto enumerates the application level
+// protocols that we recognise.
+enum NextProto {
+ kProtoUnknown = 0,
+ kProtoHTTP11 = 1,
+ kProtoMinimumVersion = kProtoHTTP11,
+
+ // TODO(akalin): Stop advertising SPDY/1 and remove this.
+ kProtoSPDY1 = 2,
+ kProtoSPDYMinimumVersion = kProtoSPDY1,
+ kProtoSPDY2 = 3,
+ // TODO(akalin): Stop adverising SPDY/2.1, too.
+ kProtoSPDY21 = 4,
+ kProtoSPDY3 = 5,
+ kProtoSPDY31 = 6,
+ kProtoSPDY4a2 = 7,
+ // We lump in HTTP/2 with the SPDY protocols for now.
+ kProtoHTTP2Draft04 = 8,
+ kProtoSPDYMaximumVersion = kProtoHTTP2Draft04,
+
+ kProtoQUIC1SPDY3 = 9,
+
+ kProtoMaximumVersion = kProtoQUIC1SPDY3,
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_NEXT_PROTO_H_
diff --git a/chromium/net/socket/nss_ssl_util.cc b/chromium/net/socket/nss_ssl_util.cc
new file mode 100644
index 00000000000..ae037b17c1e
--- /dev/null
+++ b/chromium/net/socket/nss_ssl_util.cc
@@ -0,0 +1,276 @@
+// Copyright (c) 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.
+
+#include "net/socket/nss_ssl_util.h"
+
+#include <nss.h>
+#include <secerr.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "crypto/nss_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace net {
+
+class NSSSSLInitSingleton {
+ public:
+ NSSSSLInitSingleton() {
+ crypto::EnsureNSSInit();
+
+ NSS_SetDomesticPolicy();
+
+ const PRUint16* const ssl_ciphers = SSL_GetImplementedCiphers();
+ const PRUint16 num_ciphers = SSL_GetNumImplementedCiphers();
+
+ // Disable ECDSA cipher suites on platforms that do not support ECDSA
+ // signed certificates, as servers may use the presence of such
+ // ciphersuites as a hint to send an ECDSA certificate.
+ bool disableECDSA = false;
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ disableECDSA = true;
+#endif
+
+ // Explicitly enable exactly those ciphers with keys of at least 80 bits
+ for (int i = 0; i < num_ciphers; i++) {
+ SSLCipherSuiteInfo info;
+ if (SSL_GetCipherSuiteInfo(ssl_ciphers[i], &info,
+ sizeof(info)) == SECSuccess) {
+ bool enabled = info.effectiveKeyBits >= 80;
+ if (info.authAlgorithm == ssl_auth_ecdsa && disableECDSA)
+ enabled = false;
+
+ // Trim the list of cipher suites in order to keep the size of the
+ // ClientHello down. DSS, ECDH, CAMELLIA, SEED, ECC+3DES, and
+ // HMAC-SHA256 cipher suites are disabled.
+ if (info.symCipher == ssl_calg_camellia ||
+ info.symCipher == ssl_calg_seed ||
+ (info.symCipher == ssl_calg_3des && info.keaType != ssl_kea_rsa) ||
+ info.authAlgorithm == ssl_auth_dsa ||
+ info.macAlgorithm == ssl_hmac_sha256 ||
+ info.nonStandard ||
+ strcmp(info.keaTypeName, "ECDH") == 0) {
+ enabled = false;
+ }
+
+ if (ssl_ciphers[i] == TLS_DHE_DSS_WITH_AES_128_CBC_SHA) {
+ // Enabled to allow servers with only a DSA certificate to function.
+ enabled = true;
+ }
+ SSL_CipherPrefSetDefault(ssl_ciphers[i], enabled);
+ }
+ }
+
+ // Enable SSL.
+ SSL_OptionSetDefault(SSL_SECURITY, PR_TRUE);
+
+ // All other SSL options are set per-session by SSLClientSocket and
+ // SSLServerSocket.
+ }
+
+ ~NSSSSLInitSingleton() {
+ // Have to clear the cache, or NSS_Shutdown fails with SEC_ERROR_BUSY.
+ SSL_ClearSessionCache();
+ }
+};
+
+static base::LazyInstance<NSSSSLInitSingleton> g_nss_ssl_init_singleton =
+ LAZY_INSTANCE_INITIALIZER;
+
+// Initialize the NSS SSL library if it isn't already initialized. This must
+// be called before any other NSS SSL functions. This function is
+// thread-safe, and the NSS SSL library will only ever be initialized once.
+// The NSS SSL library will be properly shut down on program exit.
+void EnsureNSSSSLInit() {
+ // Initializing SSL causes us to do blocking IO.
+ // Temporarily allow it until we fix
+ // http://code.google.com/p/chromium/issues/detail?id=59847
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ g_nss_ssl_init_singleton.Get();
+}
+
+// Map a Chromium net error code to an NSS error code.
+// See _MD_unix_map_default_error in the NSS source
+// tree for inspiration.
+PRErrorCode MapErrorToNSS(int result) {
+ if (result >=0)
+ return result;
+
+ switch (result) {
+ case ERR_IO_PENDING:
+ return PR_WOULD_BLOCK_ERROR;
+ case ERR_ACCESS_DENIED:
+ case ERR_NETWORK_ACCESS_DENIED:
+ // For connect, this could be mapped to PR_ADDRESS_NOT_SUPPORTED_ERROR.
+ return PR_NO_ACCESS_RIGHTS_ERROR;
+ case ERR_NOT_IMPLEMENTED:
+ return PR_NOT_IMPLEMENTED_ERROR;
+ case ERR_SOCKET_NOT_CONNECTED:
+ return PR_NOT_CONNECTED_ERROR;
+ case ERR_INTERNET_DISCONNECTED: // Equivalent to ENETDOWN.
+ return PR_NETWORK_UNREACHABLE_ERROR; // Best approximation.
+ case ERR_CONNECTION_TIMED_OUT:
+ case ERR_TIMED_OUT:
+ return PR_IO_TIMEOUT_ERROR;
+ case ERR_CONNECTION_RESET:
+ return PR_CONNECT_RESET_ERROR;
+ case ERR_CONNECTION_ABORTED:
+ return PR_CONNECT_ABORTED_ERROR;
+ case ERR_CONNECTION_REFUSED:
+ return PR_CONNECT_REFUSED_ERROR;
+ case ERR_ADDRESS_UNREACHABLE:
+ return PR_HOST_UNREACHABLE_ERROR; // Also PR_NETWORK_UNREACHABLE_ERROR.
+ case ERR_ADDRESS_INVALID:
+ return PR_ADDRESS_NOT_AVAILABLE_ERROR;
+ case ERR_NAME_NOT_RESOLVED:
+ return PR_DIRECTORY_LOOKUP_ERROR;
+ default:
+ LOG(WARNING) << "MapErrorToNSS " << result
+ << " mapped to PR_UNKNOWN_ERROR";
+ return PR_UNKNOWN_ERROR;
+ }
+}
+
+// The default error mapping function.
+// Maps an NSS error code to a network error code.
+int MapNSSError(PRErrorCode err) {
+ // TODO(port): fill this out as we learn what's important
+ switch (err) {
+ case PR_WOULD_BLOCK_ERROR:
+ return ERR_IO_PENDING;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR: // For connect.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ return ERR_ACCESS_DENIED;
+ case PR_IO_TIMEOUT_ERROR:
+ return ERR_TIMED_OUT;
+ case PR_CONNECT_RESET_ERROR:
+ return ERR_CONNECTION_RESET;
+ case PR_CONNECT_ABORTED_ERROR:
+ return ERR_CONNECTION_ABORTED;
+ case PR_CONNECT_REFUSED_ERROR:
+ return ERR_CONNECTION_REFUSED;
+ case PR_NOT_CONNECTED_ERROR:
+ return ERR_SOCKET_NOT_CONNECTED;
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ return ERR_ADDRESS_UNREACHABLE;
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ return ERR_ADDRESS_INVALID;
+ case PR_INVALID_ARGUMENT_ERROR:
+ return ERR_INVALID_ARGUMENT;
+ case PR_END_OF_FILE_ERROR:
+ return ERR_CONNECTION_CLOSED;
+ case PR_NOT_IMPLEMENTED_ERROR:
+ return ERR_NOT_IMPLEMENTED;
+
+ case SEC_ERROR_LIBRARY_FAILURE:
+ return ERR_UNEXPECTED;
+ case SEC_ERROR_INVALID_ARGS:
+ return ERR_INVALID_ARGUMENT;
+ case SEC_ERROR_NO_MEMORY:
+ return ERR_OUT_OF_MEMORY;
+ case SEC_ERROR_NO_KEY:
+ return ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY;
+ case SEC_ERROR_INVALID_KEY:
+ case SSL_ERROR_SIGN_HASHES_FAILURE:
+ LOG(ERROR) << "ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED: NSS error " << err
+ << ", OS error " << PR_GetOSError();
+ return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED;
+ // A handshake (initial or renegotiation) may fail because some signature
+ // (for example, the signature in the ServerKeyExchange message for an
+ // ephemeral Diffie-Hellman cipher suite) is invalid.
+ case SEC_ERROR_BAD_SIGNATURE:
+ return ERR_SSL_PROTOCOL_ERROR;
+
+ case SSL_ERROR_SSL_DISABLED:
+ return ERR_NO_SSL_VERSIONS_ENABLED;
+ case SSL_ERROR_NO_CYPHER_OVERLAP:
+ case SSL_ERROR_PROTOCOL_VERSION_ALERT:
+ case SSL_ERROR_UNSUPPORTED_VERSION:
+ return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
+ case SSL_ERROR_HANDSHAKE_FAILURE_ALERT:
+ case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT:
+ case SSL_ERROR_ILLEGAL_PARAMETER_ALERT:
+ return ERR_SSL_PROTOCOL_ERROR;
+ case SSL_ERROR_DECOMPRESSION_FAILURE_ALERT:
+ return ERR_SSL_DECOMPRESSION_FAILURE_ALERT;
+ case SSL_ERROR_BAD_MAC_ALERT:
+ return ERR_SSL_BAD_RECORD_MAC_ALERT;
+ case SSL_ERROR_DECRYPT_ERROR_ALERT:
+ return ERR_SSL_DECRYPT_ERROR_ALERT;
+ case SSL_ERROR_UNSAFE_NEGOTIATION:
+ return ERR_SSL_UNSAFE_NEGOTIATION;
+ case SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY:
+ return ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY;
+ case SSL_ERROR_HANDSHAKE_NOT_COMPLETED:
+ return ERR_SSL_HANDSHAKE_NOT_COMPLETED;
+ case SEC_ERROR_BAD_KEY:
+ case SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE:
+ // TODO(wtc): the following errors may also occur in contexts unrelated
+ // to the peer's public key. We should add new error codes for them, or
+ // map them to ERR_SSL_BAD_PEER_PUBLIC_KEY only in the right context.
+ // General unsupported/unknown key algorithm error.
+ case SEC_ERROR_UNSUPPORTED_KEYALG:
+ // General DER decoding errors.
+ case SEC_ERROR_BAD_DER:
+ case SEC_ERROR_EXTRA_INPUT:
+ return ERR_SSL_BAD_PEER_PUBLIC_KEY;
+
+ default: {
+ if (IS_SSL_ERROR(err)) {
+ LOG(WARNING) << "Unknown SSL error " << err
+ << " mapped to net::ERR_SSL_PROTOCOL_ERROR";
+ return ERR_SSL_PROTOCOL_ERROR;
+ }
+ LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED";
+ return ERR_FAILED;
+ }
+ }
+}
+
+// Returns parameters to attach to the NetLog when we receive an error in
+// response to a call to an NSS function. Used instead of
+// NetLogSSLErrorCallback with events of type TYPE_SSL_NSS_ERROR.
+base::Value* NetLogSSLFailedNSSFunctionCallback(
+ const char* function,
+ const char* param,
+ int ssl_lib_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("function", function);
+ if (param[0] != '\0')
+ dict->SetString("param", param);
+ dict->SetInteger("ssl_lib_error", ssl_lib_error);
+ return dict;
+}
+
+void LogFailedNSSFunction(const BoundNetLog& net_log,
+ const char* function,
+ const char* param) {
+ DCHECK(function);
+ DCHECK(param);
+ net_log.AddEvent(
+ NetLog::TYPE_SSL_NSS_ERROR,
+ base::Bind(&NetLogSSLFailedNSSFunctionCallback,
+ function, param, PR_GetError()));
+}
+
+} // namespace net
diff --git a/chromium/net/socket/nss_ssl_util.h b/chromium/net/socket/nss_ssl_util.h
new file mode 100644
index 00000000000..09ae3562cd7
--- /dev/null
+++ b/chromium/net/socket/nss_ssl_util.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+// This file is only included in ssl_client_socket_nss.cc and
+// ssl_server_socket_nss.cc to share common functions of NSS.
+
+#ifndef NET_SOCKET_NSS_SSL_UTIL_H_
+#define NET_SOCKET_NSS_SSL_UTIL_H_
+
+#include <prerror.h>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class BoundNetLog;
+
+// Initalize NSS SSL library.
+NET_EXPORT void EnsureNSSSSLInit();
+
+// Log a failed NSS funcion call.
+void LogFailedNSSFunction(const BoundNetLog& net_log,
+ const char* function,
+ const char* param);
+
+// Map network error code to NSS error code.
+PRErrorCode MapErrorToNSS(int result);
+
+// Map NSS error code to network error code.
+int MapNSSError(PRErrorCode err);
+
+} // namespace net
+
+#endif // NET_SOCKET_NSS_SSL_UTIL_H_
diff --git a/chromium/net/socket/server_socket.h b/chromium/net/socket/server_socket.h
new file mode 100644
index 00000000000..11151eea153
--- /dev/null
+++ b/chromium/net/socket/server_socket.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2011 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_SOCKET_SERVER_SOCKET_H_
+#define NET_SOCKET_SERVER_SOCKET_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IPEndPoint;
+class StreamSocket;
+
+class NET_EXPORT ServerSocket {
+ public:
+ ServerSocket() { }
+ virtual ~ServerSocket() { }
+
+ // Bind the socket and start listening. Destroy the socket to stop
+ // listening.
+ virtual int Listen(const net::IPEndPoint& address, int backlog) = 0;
+
+ // Gets current address the socket is bound to.
+ virtual int GetLocalAddress(IPEndPoint* address) const = 0;
+
+ // Accept connection. Callback is called when new connection is
+ // accepted.
+ virtual int Accept(scoped_ptr<StreamSocket>* socket,
+ const CompletionCallback& callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ServerSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SERVER_SOCKET_H_
diff --git a/chromium/net/socket/socket.h b/chromium/net/socket/socket.h
new file mode 100644
index 00000000000..fccb25873a4
--- /dev/null
+++ b/chromium/net/socket/socket.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_SOCKET_SOCKET_H_
+#define NET_SOCKET_SOCKET_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IOBuffer;
+
+// Represents a read/write socket.
+class NET_EXPORT Socket {
+ public:
+ virtual ~Socket() {}
+
+ // Reads data, up to |buf_len| bytes, from the socket. The number of bytes
+ // read is returned, or an error is returned upon failure.
+ // ERR_SOCKET_NOT_CONNECTED should be returned if the socket is not currently
+ // connected. Zero is returned once to indicate end-of-file; the return value
+ // of subsequent calls is undefined, and may be OS dependent. ERR_IO_PENDING
+ // is returned if the operation could not be completed synchronously, in which
+ // case the result will be passed to the callback when available. If the
+ // operation is not completed immediately, the socket acquires a reference to
+ // the provided buffer until the callback is invoked or the socket is
+ // closed. If the socket is Disconnected before the read completes, the
+ // callback will not be invoked.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Writes data, up to |buf_len| bytes, to the socket. Note: data may be
+ // written partially. The number of bytes written is returned, or an error
+ // is returned upon failure. ERR_SOCKET_NOT_CONNECTED should be returned if
+ // the socket is not currently connected. The return value when the
+ // connection is closed is undefined, and may be OS dependent. ERR_IO_PENDING
+ // is returned if the operation could not be completed synchronously, in which
+ // case the result will be passed to the callback when available. If the
+ // operation is not completed immediately, the socket acquires a reference to
+ // the provided buffer until the callback is invoked or the socket is
+ // closed. Implementations of this method should not modify the contents
+ // of the actual buffer that is written to the socket. If the socket is
+ // Disconnected before the write completes, the callback will not be invoked.
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+
+ // Set the receive buffer size (in bytes) for the socket.
+ // Note: changing this value can affect the TCP window size on some platforms.
+ // Returns true on success, or false on failure.
+ virtual bool SetReceiveBufferSize(int32 size) = 0;
+
+ // Set the send buffer size (in bytes) for the socket.
+ // Note: changing this value can affect the TCP window size on some platforms.
+ // Returns true on success, or false on failure.
+ virtual bool SetSendBufferSize(int32 size) = 0;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKET_H_
diff --git a/chromium/net/socket/socket_net_log_params.cc b/chromium/net/socket/socket_net_log_params.cc
new file mode 100644
index 00000000000..bcc12c86bcf
--- /dev/null
+++ b/chromium/net/socket/socket_net_log_params.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 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.
+
+#include "net/socket/socket_net_log_params.h"
+
+#include "base/bind.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogSocketErrorCallback(int net_error,
+ int os_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ dict->SetInteger("os_error", os_error);
+ return dict;
+}
+
+base::Value* NetLogHostPortPairCallback(const HostPortPair* host_and_port,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("host_and_port", host_and_port->ToString());
+ return dict;
+}
+
+base::Value* NetLogIPEndPointCallback(const IPEndPoint* address,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("address", address->ToString());
+ return dict;
+}
+
+base::Value* NetLogSourceAddressCallback(const struct sockaddr* net_address,
+ socklen_t address_len,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("source_address",
+ NetAddressToStringWithPort(net_address, address_len));
+ return dict;
+}
+
+} // namespace
+
+NetLog::ParametersCallback CreateNetLogSocketErrorCallback(int net_error,
+ int os_error) {
+ return base::Bind(&NetLogSocketErrorCallback, net_error, os_error);
+}
+
+NetLog::ParametersCallback CreateNetLogHostPortPairCallback(
+ const HostPortPair* host_and_port) {
+ return base::Bind(&NetLogHostPortPairCallback, host_and_port);
+}
+
+NetLog::ParametersCallback CreateNetLogIPEndPointCallback(
+ const IPEndPoint* address) {
+ return base::Bind(&NetLogIPEndPointCallback, address);
+}
+
+NetLog::ParametersCallback CreateNetLogSourceAddressCallback(
+ const struct sockaddr* net_address,
+ socklen_t address_len) {
+ return base::Bind(&NetLogSourceAddressCallback, net_address, address_len);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/socket_net_log_params.h b/chromium/net/socket/socket_net_log_params.h
new file mode 100644
index 00000000000..f5fe652d125
--- /dev/null
+++ b/chromium/net/socket/socket_net_log_params.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SOCKET_NET_LOG_PARAMS_H_
+#define NET_SOCKET_SOCKET_NET_LOG_PARAMS_H_
+
+#include "net/base/net_log.h"
+#include "net/base/sys_addrinfo.h"
+
+namespace net {
+
+class HostPortPair;
+class IPEndPoint;
+
+// Creates a NetLog callback for socket error events.
+NetLog::ParametersCallback CreateNetLogSocketErrorCallback(int net_error,
+ int os_error);
+
+// Creates a NetLog callback for a HostPortPair.
+// |host_and_port| must remain valid for the lifetime of the returned callback.
+NetLog::ParametersCallback CreateNetLogHostPortPairCallback(
+ const HostPortPair* host_and_port);
+
+// Creates a NetLog callback for an IPEndPoint.
+// |address| must remain valid for the lifetime of the returned callback.
+NetLog::ParametersCallback CreateNetLogIPEndPointCallback(
+ const IPEndPoint* address);
+
+// Creates a NetLog callback for the source sockaddr on connect events.
+// |net_address| must remain valid for the lifetime of the returned callback.
+NetLog::ParametersCallback CreateNetLogSourceAddressCallback(
+ const struct sockaddr* net_address,
+ socklen_t address_len);
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKET_NET_LOG_PARAMS_H_
diff --git a/chromium/net/socket/socket_test_util.cc b/chromium/net/socket/socket_test_util.cc
new file mode 100644
index 00000000000..159f62e42c6
--- /dev/null
+++ b/chromium/net/socket/socket_test_util.cc
@@ -0,0 +1,1888 @@
+// Copyright (c) 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.
+
+#include "net/socket/socket_test_util.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "net/base/address_family.h"
+#include "net/base/address_list.h"
+#include "net/base/auth.h"
+#include "net/base/load_timing_info.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Socket events are easier to debug if you log individual reads and writes.
+// Enable these if locally debugging, but they are too noisy for the waterfall.
+#if 0
+#define NET_TRACE(level, s) DLOG(level) << s << __FUNCTION__ << "() "
+#else
+#define NET_TRACE(level, s) EAT_STREAM_PARAMETERS
+#endif
+
+namespace net {
+
+namespace {
+
+inline char AsciifyHigh(char x) {
+ char nybble = static_cast<char>((x >> 4) & 0x0F);
+ return nybble + ((nybble < 0x0A) ? '0' : 'A' - 10);
+}
+
+inline char AsciifyLow(char x) {
+ char nybble = static_cast<char>((x >> 0) & 0x0F);
+ return nybble + ((nybble < 0x0A) ? '0' : 'A' - 10);
+}
+
+inline char Asciify(char x) {
+ if ((x < 0) || !isprint(x))
+ return '.';
+ return x;
+}
+
+void DumpData(const char* data, int data_len) {
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+ DVLOG(1) << "Length: " << data_len;
+ const char* pfx = "Data: ";
+ if (!data || (data_len <= 0)) {
+ DVLOG(1) << pfx << "<None>";
+ } else {
+ int i;
+ for (i = 0; i <= (data_len - 4); i += 4) {
+ DVLOG(1) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << AsciifyHigh(data[i + 2]) << AsciifyLow(data[i + 2])
+ << AsciifyHigh(data[i + 3]) << AsciifyLow(data[i + 3])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << Asciify(data[i + 2])
+ << Asciify(data[i + 3])
+ << "'";
+ pfx = " ";
+ }
+ // Take care of any 'trailing' bytes, if data_len was not a multiple of 4.
+ switch (data_len - i) {
+ case 3:
+ DVLOG(1) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << AsciifyHigh(data[i + 2]) << AsciifyLow(data[i + 2])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << Asciify(data[i + 2])
+ << " '";
+ break;
+ case 2:
+ DVLOG(1) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << AsciifyHigh(data[i + 1]) << AsciifyLow(data[i + 1])
+ << " '"
+ << Asciify(data[i + 0])
+ << Asciify(data[i + 1])
+ << " '";
+ break;
+ case 1:
+ DVLOG(1) << pfx
+ << AsciifyHigh(data[i + 0]) << AsciifyLow(data[i + 0])
+ << " '"
+ << Asciify(data[i + 0])
+ << " '";
+ break;
+ }
+ }
+}
+
+template <MockReadWriteType type>
+void DumpMockReadWrite(const MockReadWrite<type>& r) {
+ if (logging::LOG_INFO < logging::GetMinLogLevel())
+ return;
+ DVLOG(1) << "Async: " << (r.mode == ASYNC)
+ << "\nResult: " << r.result;
+ DumpData(r.data, r.data_len);
+ const char* stop = (r.sequence_number & MockRead::STOPLOOP) ? " (STOP)" : "";
+ DVLOG(1) << "Stage: " << (r.sequence_number & ~MockRead::STOPLOOP) << stop
+ << "\nTime: " << r.time_stamp.ToInternalValue();
+}
+
+} // namespace
+
+MockConnect::MockConnect() : mode(ASYNC), result(OK) {
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ peer_addr = IPEndPoint(ip, 0);
+}
+
+MockConnect::MockConnect(IoMode io_mode, int r) : mode(io_mode), result(r) {
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ peer_addr = IPEndPoint(ip, 0);
+}
+
+MockConnect::MockConnect(IoMode io_mode, int r, IPEndPoint addr) :
+ mode(io_mode),
+ result(r),
+ peer_addr(addr) {
+}
+
+MockConnect::~MockConnect() {}
+
+StaticSocketDataProvider::StaticSocketDataProvider()
+ : reads_(NULL),
+ read_index_(0),
+ read_count_(0),
+ writes_(NULL),
+ write_index_(0),
+ write_count_(0) {
+}
+
+StaticSocketDataProvider::StaticSocketDataProvider(MockRead* reads,
+ size_t reads_count,
+ MockWrite* writes,
+ size_t writes_count)
+ : reads_(reads),
+ read_index_(0),
+ read_count_(reads_count),
+ writes_(writes),
+ write_index_(0),
+ write_count_(writes_count) {
+}
+
+StaticSocketDataProvider::~StaticSocketDataProvider() {}
+
+const MockRead& StaticSocketDataProvider::PeekRead() const {
+ DCHECK(!at_read_eof());
+ return reads_[read_index_];
+}
+
+const MockWrite& StaticSocketDataProvider::PeekWrite() const {
+ DCHECK(!at_write_eof());
+ return writes_[write_index_];
+}
+
+const MockRead& StaticSocketDataProvider::PeekRead(size_t index) const {
+ DCHECK_LT(index, read_count_);
+ return reads_[index];
+}
+
+const MockWrite& StaticSocketDataProvider::PeekWrite(size_t index) const {
+ DCHECK_LT(index, write_count_);
+ return writes_[index];
+}
+
+MockRead StaticSocketDataProvider::GetNextRead() {
+ DCHECK(!at_read_eof());
+ reads_[read_index_].time_stamp = base::Time::Now();
+ return reads_[read_index_++];
+}
+
+MockWriteResult StaticSocketDataProvider::OnWrite(const std::string& data) {
+ if (!writes_) {
+ // Not using mock writes; succeed synchronously.
+ return MockWriteResult(SYNCHRONOUS, data.length());
+ }
+ DCHECK(!at_write_eof());
+
+ // Check that what we are writing matches the expectation.
+ // Then give the mocked return value.
+ MockWrite* w = &writes_[write_index_++];
+ w->time_stamp = base::Time::Now();
+ int result = w->result;
+ if (w->data) {
+ // Note - we can simulate a partial write here. If the expected data
+ // is a match, but shorter than the write actually written, that is legal.
+ // Example:
+ // Application writes "foobarbaz" (9 bytes)
+ // Expected write was "foo" (3 bytes)
+ // This is a success, and we return 3 to the application.
+ std::string expected_data(w->data, w->data_len);
+ EXPECT_GE(data.length(), expected_data.length());
+ std::string actual_data(data.substr(0, w->data_len));
+ EXPECT_EQ(expected_data, actual_data);
+ if (expected_data != actual_data)
+ return MockWriteResult(SYNCHRONOUS, ERR_UNEXPECTED);
+ if (result == OK)
+ result = w->data_len;
+ }
+ return MockWriteResult(w->mode, result);
+}
+
+void StaticSocketDataProvider::Reset() {
+ read_index_ = 0;
+ write_index_ = 0;
+}
+
+DynamicSocketDataProvider::DynamicSocketDataProvider()
+ : short_read_limit_(0),
+ allow_unconsumed_reads_(false) {
+}
+
+DynamicSocketDataProvider::~DynamicSocketDataProvider() {}
+
+MockRead DynamicSocketDataProvider::GetNextRead() {
+ if (reads_.empty())
+ return MockRead(SYNCHRONOUS, ERR_UNEXPECTED);
+ MockRead result = reads_.front();
+ if (short_read_limit_ == 0 || result.data_len <= short_read_limit_) {
+ reads_.pop_front();
+ } else {
+ result.data_len = short_read_limit_;
+ reads_.front().data += result.data_len;
+ reads_.front().data_len -= result.data_len;
+ }
+ return result;
+}
+
+void DynamicSocketDataProvider::Reset() {
+ reads_.clear();
+}
+
+void DynamicSocketDataProvider::SimulateRead(const char* data,
+ const size_t length) {
+ if (!allow_unconsumed_reads_) {
+ EXPECT_TRUE(reads_.empty()) << "Unconsumed read: " << reads_.front().data;
+ }
+ reads_.push_back(MockRead(ASYNC, data, length));
+}
+
+SSLSocketDataProvider::SSLSocketDataProvider(IoMode mode, int result)
+ : connect(mode, result),
+ next_proto_status(SSLClientSocket::kNextProtoUnsupported),
+ was_npn_negotiated(false),
+ protocol_negotiated(kProtoUnknown),
+ client_cert_sent(false),
+ cert_request_info(NULL),
+ channel_id_sent(false) {
+}
+
+SSLSocketDataProvider::~SSLSocketDataProvider() {
+}
+
+void SSLSocketDataProvider::SetNextProto(NextProto proto) {
+ was_npn_negotiated = true;
+ next_proto_status = SSLClientSocket::kNextProtoNegotiated;
+ protocol_negotiated = proto;
+ next_proto = SSLClientSocket::NextProtoToString(proto);
+}
+
+DelayedSocketData::DelayedSocketData(
+ int write_delay, MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ write_delay_(write_delay),
+ read_in_progress_(false),
+ weak_factory_(this) {
+ DCHECK_GE(write_delay_, 0);
+}
+
+DelayedSocketData::DelayedSocketData(
+ const MockConnect& connect, int write_delay, MockRead* reads,
+ size_t reads_count, MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ write_delay_(write_delay),
+ read_in_progress_(false),
+ weak_factory_(this) {
+ DCHECK_GE(write_delay_, 0);
+ set_connect_data(connect);
+}
+
+DelayedSocketData::~DelayedSocketData() {
+}
+
+void DelayedSocketData::ForceNextRead() {
+ DCHECK(read_in_progress_);
+ write_delay_ = 0;
+ CompleteRead();
+}
+
+MockRead DelayedSocketData::GetNextRead() {
+ MockRead out = MockRead(ASYNC, ERR_IO_PENDING);
+ if (write_delay_ <= 0)
+ out = StaticSocketDataProvider::GetNextRead();
+ read_in_progress_ = (out.result == ERR_IO_PENDING);
+ return out;
+}
+
+MockWriteResult DelayedSocketData::OnWrite(const std::string& data) {
+ MockWriteResult rv = StaticSocketDataProvider::OnWrite(data);
+ // Now that our write has completed, we can allow reads to continue.
+ if (!--write_delay_ && read_in_progress_)
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&DelayedSocketData::CompleteRead,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(100));
+ return rv;
+}
+
+void DelayedSocketData::Reset() {
+ set_socket(NULL);
+ read_in_progress_ = false;
+ weak_factory_.InvalidateWeakPtrs();
+ StaticSocketDataProvider::Reset();
+}
+
+void DelayedSocketData::CompleteRead() {
+ if (socket() && read_in_progress_)
+ socket()->OnReadComplete(GetNextRead());
+}
+
+OrderedSocketData::OrderedSocketData(
+ MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ sequence_number_(0), loop_stop_stage_(0),
+ blocked_(false), weak_factory_(this) {
+}
+
+OrderedSocketData::OrderedSocketData(
+ const MockConnect& connect,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ sequence_number_(0), loop_stop_stage_(0),
+ blocked_(false), weak_factory_(this) {
+ set_connect_data(connect);
+}
+
+void OrderedSocketData::EndLoop() {
+ // If we've already stopped the loop, don't do it again until we've advanced
+ // to the next sequence_number.
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ << ": EndLoop()";
+ if (loop_stop_stage_ > 0) {
+ const MockRead& next_read = StaticSocketDataProvider::PeekRead();
+ if ((next_read.sequence_number & ~MockRead::STOPLOOP) >
+ loop_stop_stage_) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Clearing stop index";
+ loop_stop_stage_ = 0;
+ } else {
+ return;
+ }
+ }
+ // Record the sequence_number at which we stopped the loop.
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Posting Quit at read " << read_index();
+ loop_stop_stage_ = sequence_number_;
+}
+
+MockRead OrderedSocketData::GetNextRead() {
+ weak_factory_.InvalidateWeakPtrs();
+ blocked_ = false;
+ const MockRead& next_read = StaticSocketDataProvider::PeekRead();
+ if (next_read.sequence_number & MockRead::STOPLOOP)
+ EndLoop();
+ if ((next_read.sequence_number & ~MockRead::STOPLOOP) <=
+ sequence_number_++) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ - 1
+ << ": Read " << read_index();
+ DumpMockReadWrite(next_read);
+ blocked_ = (next_read.result == ERR_IO_PENDING);
+ return StaticSocketDataProvider::GetNextRead();
+ }
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_ - 1
+ << ": I/O Pending";
+ MockRead result = MockRead(ASYNC, ERR_IO_PENDING);
+ DumpMockReadWrite(result);
+ blocked_ = true;
+ return result;
+}
+
+MockWriteResult OrderedSocketData::OnWrite(const std::string& data) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Write " << write_index();
+ DumpMockReadWrite(PeekWrite());
+ ++sequence_number_;
+ if (blocked_) {
+ // TODO(willchan): This 100ms delay seems to work around some weirdness. We
+ // should probably fix the weirdness. One example is in SpdyStream,
+ // DoSendRequest() will return ERR_IO_PENDING, and there's a race. If the
+ // SYN_REPLY causes OnResponseReceived() to get called before
+ // SpdyStream::ReadResponseHeaders() is called, we hit a NOTREACHED().
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&OrderedSocketData::CompleteRead,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(100));
+ }
+ return StaticSocketDataProvider::OnWrite(data);
+}
+
+void OrderedSocketData::Reset() {
+ NET_TRACE(INFO, " *** ") << "Stage "
+ << sequence_number_ << ": Reset()";
+ sequence_number_ = 0;
+ loop_stop_stage_ = 0;
+ set_socket(NULL);
+ weak_factory_.InvalidateWeakPtrs();
+ StaticSocketDataProvider::Reset();
+}
+
+void OrderedSocketData::CompleteRead() {
+ if (socket() && blocked_) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_;
+ socket()->OnReadComplete(GetNextRead());
+ }
+}
+
+OrderedSocketData::~OrderedSocketData() {}
+
+DeterministicSocketData::DeterministicSocketData(MockRead* reads,
+ size_t reads_count, MockWrite* writes, size_t writes_count)
+ : StaticSocketDataProvider(reads, reads_count, writes, writes_count),
+ sequence_number_(0),
+ current_read_(),
+ current_write_(),
+ stopping_sequence_number_(0),
+ stopped_(false),
+ print_debug_(false),
+ is_running_(false) {
+ VerifyCorrectSequenceNumbers(reads, reads_count, writes, writes_count);
+}
+
+DeterministicSocketData::~DeterministicSocketData() {}
+
+void DeterministicSocketData::Run() {
+ DCHECK(!is_running_);
+ is_running_ = true;
+
+ SetStopped(false);
+ int counter = 0;
+ // Continue to consume data until all data has run out, or the stopped_ flag
+ // has been set. Consuming data requires two separate operations -- running
+ // the tasks in the message loop, and explicitly invoking the read/write
+ // callbacks (simulating network I/O). We check our conditions between each,
+ // since they can change in either.
+ while ((!at_write_eof() || !at_read_eof()) && !stopped()) {
+ if (counter % 2 == 0)
+ base::RunLoop().RunUntilIdle();
+ if (counter % 2 == 1) {
+ InvokeCallbacks();
+ }
+ counter++;
+ }
+ // We're done consuming new data, but it is possible there are still some
+ // pending callbacks which we expect to complete before returning.
+ while (delegate_.get() &&
+ (delegate_->WritePending() || delegate_->ReadPending()) &&
+ !stopped()) {
+ InvokeCallbacks();
+ base::RunLoop().RunUntilIdle();
+ }
+ SetStopped(false);
+ is_running_ = false;
+}
+
+void DeterministicSocketData::RunFor(int steps) {
+ StopAfter(steps);
+ Run();
+}
+
+void DeterministicSocketData::SetStop(int seq) {
+ DCHECK_LT(sequence_number_, seq);
+ stopping_sequence_number_ = seq;
+ stopped_ = false;
+}
+
+void DeterministicSocketData::StopAfter(int seq) {
+ SetStop(sequence_number_ + seq);
+}
+
+MockRead DeterministicSocketData::GetNextRead() {
+ current_read_ = StaticSocketDataProvider::PeekRead();
+
+ // Synchronous read while stopped is an error
+ if (stopped() && current_read_.mode == SYNCHRONOUS) {
+ LOG(ERROR) << "Unable to perform synchronous IO while stopped";
+ return MockRead(SYNCHRONOUS, ERR_UNEXPECTED);
+ }
+
+ // Async read which will be called back in a future step.
+ if (sequence_number_ < current_read_.sequence_number) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": I/O Pending";
+ MockRead result = MockRead(SYNCHRONOUS, ERR_IO_PENDING);
+ if (current_read_.mode == SYNCHRONOUS) {
+ LOG(ERROR) << "Unable to perform synchronous read: "
+ << current_read_.sequence_number
+ << " at stage: " << sequence_number_;
+ result = MockRead(SYNCHRONOUS, ERR_UNEXPECTED);
+ }
+ if (print_debug_)
+ DumpMockReadWrite(result);
+ return result;
+ }
+
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Read " << read_index();
+ if (print_debug_)
+ DumpMockReadWrite(current_read_);
+
+ // Increment the sequence number if IO is complete
+ if (current_read_.mode == SYNCHRONOUS)
+ NextStep();
+
+ DCHECK_NE(ERR_IO_PENDING, current_read_.result);
+ StaticSocketDataProvider::GetNextRead();
+
+ return current_read_;
+}
+
+MockWriteResult DeterministicSocketData::OnWrite(const std::string& data) {
+ const MockWrite& next_write = StaticSocketDataProvider::PeekWrite();
+ current_write_ = next_write;
+
+ // Synchronous write while stopped is an error
+ if (stopped() && next_write.mode == SYNCHRONOUS) {
+ LOG(ERROR) << "Unable to perform synchronous IO while stopped";
+ return MockWriteResult(SYNCHRONOUS, ERR_UNEXPECTED);
+ }
+
+ // Async write which will be called back in a future step.
+ if (sequence_number_ < next_write.sequence_number) {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": I/O Pending";
+ if (next_write.mode == SYNCHRONOUS) {
+ LOG(ERROR) << "Unable to perform synchronous write: "
+ << next_write.sequence_number << " at stage: " << sequence_number_;
+ return MockWriteResult(SYNCHRONOUS, ERR_UNEXPECTED);
+ }
+ } else {
+ NET_TRACE(INFO, " *** ") << "Stage " << sequence_number_
+ << ": Write " << write_index();
+ }
+
+ if (print_debug_)
+ DumpMockReadWrite(next_write);
+
+ // Move to the next step if I/O is synchronous, since the operation will
+ // complete when this method returns.
+ if (next_write.mode == SYNCHRONOUS)
+ NextStep();
+
+ // This is either a sync write for this step, or an async write.
+ return StaticSocketDataProvider::OnWrite(data);
+}
+
+void DeterministicSocketData::Reset() {
+ NET_TRACE(INFO, " *** ") << "Stage "
+ << sequence_number_ << ": Reset()";
+ sequence_number_ = 0;
+ StaticSocketDataProvider::Reset();
+ NOTREACHED();
+}
+
+void DeterministicSocketData::InvokeCallbacks() {
+ if (delegate_.get() && delegate_->WritePending() &&
+ (current_write().sequence_number == sequence_number())) {
+ NextStep();
+ delegate_->CompleteWrite();
+ return;
+ }
+ if (delegate_.get() && delegate_->ReadPending() &&
+ (current_read().sequence_number == sequence_number())) {
+ NextStep();
+ delegate_->CompleteRead();
+ return;
+ }
+}
+
+void DeterministicSocketData::NextStep() {
+ // Invariant: Can never move *past* the stopping step.
+ DCHECK_LT(sequence_number_, stopping_sequence_number_);
+ sequence_number_++;
+ if (sequence_number_ == stopping_sequence_number_)
+ SetStopped(true);
+}
+
+void DeterministicSocketData::VerifyCorrectSequenceNumbers(
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count) {
+ size_t read = 0;
+ size_t write = 0;
+ int expected = 0;
+ while (read < reads_count || write < writes_count) {
+ // Check to see that we have a read or write at the expected
+ // state.
+ if (read < reads_count && reads[read].sequence_number == expected) {
+ ++read;
+ ++expected;
+ continue;
+ }
+ if (write < writes_count && writes[write].sequence_number == expected) {
+ ++write;
+ ++expected;
+ continue;
+ }
+ NOTREACHED() << "Missing sequence number: " << expected;
+ return;
+ }
+ DCHECK_EQ(read, reads_count);
+ DCHECK_EQ(write, writes_count);
+}
+
+MockClientSocketFactory::MockClientSocketFactory() {}
+
+MockClientSocketFactory::~MockClientSocketFactory() {}
+
+void MockClientSocketFactory::AddSocketDataProvider(
+ SocketDataProvider* data) {
+ mock_data_.Add(data);
+}
+
+void MockClientSocketFactory::AddSSLSocketDataProvider(
+ SSLSocketDataProvider* data) {
+ mock_ssl_data_.Add(data);
+}
+
+void MockClientSocketFactory::ResetNextMockIndexes() {
+ mock_data_.ResetNextIndex();
+ mock_ssl_data_.ResetNextIndex();
+}
+
+scoped_ptr<DatagramClientSocket>
+MockClientSocketFactory::CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) {
+ SocketDataProvider* data_provider = mock_data_.GetNext();
+ scoped_ptr<MockUDPClientSocket> socket(
+ new MockUDPClientSocket(data_provider, net_log));
+ data_provider->set_socket(socket.get());
+ return socket.PassAs<DatagramClientSocket>();
+}
+
+scoped_ptr<StreamSocket> MockClientSocketFactory::CreateTransportClientSocket(
+ const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) {
+ SocketDataProvider* data_provider = mock_data_.GetNext();
+ scoped_ptr<MockTCPClientSocket> socket(
+ new MockTCPClientSocket(addresses, net_log, data_provider));
+ data_provider->set_socket(socket.get());
+ return socket.PassAs<StreamSocket>();
+}
+
+scoped_ptr<SSLClientSocket> MockClientSocketFactory::CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) {
+ return scoped_ptr<SSLClientSocket>(
+ new MockSSLClientSocket(transport_socket.Pass(),
+ host_and_port, ssl_config,
+ mock_ssl_data_.GetNext()));
+}
+
+void MockClientSocketFactory::ClearSSLSessionCache() {
+}
+
+const char MockClientSocket::kTlsUnique[] = "MOCK_TLSUNIQ";
+
+MockClientSocket::MockClientSocket(const BoundNetLog& net_log)
+ : weak_factory_(this),
+ connected_(false),
+ net_log_(net_log) {
+ IPAddressNumber ip;
+ CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip));
+ peer_addr_ = IPEndPoint(ip, 0);
+}
+
+bool MockClientSocket::SetReceiveBufferSize(int32 size) {
+ return true;
+}
+
+bool MockClientSocket::SetSendBufferSize(int32 size) {
+ return true;
+}
+
+void MockClientSocket::Disconnect() {
+ connected_ = false;
+}
+
+bool MockClientSocket::IsConnected() const {
+ return connected_;
+}
+
+bool MockClientSocket::IsConnectedAndIdle() const {
+ return connected_;
+}
+
+int MockClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ *address = peer_addr_;
+ return OK;
+}
+
+int MockClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ IPAddressNumber ip;
+ bool rv = ParseIPLiteralToNumber("192.0.2.33", &ip);
+ CHECK(rv);
+ *address = IPEndPoint(ip, 123);
+ return OK;
+}
+
+const BoundNetLog& MockClientSocket::NetLog() const {
+ return net_log_;
+}
+
+void MockClientSocket::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+}
+
+int MockClientSocket::ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) {
+ memset(out, 'A', outlen);
+ return OK;
+}
+
+int MockClientSocket::GetTLSUniqueChannelBinding(std::string* out) {
+ out->assign(MockClientSocket::kTlsUnique);
+ return OK;
+}
+
+ServerBoundCertService* MockClientSocket::GetServerBoundCertService() const {
+ NOTREACHED();
+ return NULL;
+}
+
+SSLClientSocket::NextProtoStatus
+MockClientSocket::GetNextProto(std::string* proto, std::string* server_protos) {
+ proto->clear();
+ server_protos->clear();
+ return SSLClientSocket::kNextProtoUnsupported;
+}
+
+MockClientSocket::~MockClientSocket() {}
+
+void MockClientSocket::RunCallbackAsync(const CompletionCallback& callback,
+ int result) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockClientSocket::RunCallback,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ result));
+}
+
+void MockClientSocket::RunCallback(const net::CompletionCallback& callback,
+ int result) {
+ if (!callback.is_null())
+ callback.Run(result);
+}
+
+MockTCPClientSocket::MockTCPClientSocket(const AddressList& addresses,
+ net::NetLog* net_log,
+ SocketDataProvider* data)
+ : MockClientSocket(BoundNetLog::Make(net_log, net::NetLog::SOURCE_NONE)),
+ addresses_(addresses),
+ data_(data),
+ read_offset_(0),
+ read_data_(SYNCHRONOUS, ERR_UNEXPECTED),
+ need_read_data_(true),
+ peer_closed_connection_(false),
+ pending_buf_(NULL),
+ pending_buf_len_(0),
+ was_used_to_convey_data_(false) {
+ DCHECK(data_);
+ peer_addr_ = data->connect_data().peer_addr;
+ data_->Reset();
+}
+
+MockTCPClientSocket::~MockTCPClientSocket() {}
+
+int MockTCPClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ // If the buffer is already in use, a read is already in progress!
+ DCHECK(pending_buf_ == NULL);
+
+ // Store our async IO data.
+ pending_buf_ = buf;
+ pending_buf_len_ = buf_len;
+ pending_callback_ = callback;
+
+ if (need_read_data_) {
+ read_data_ = data_->GetNextRead();
+ if (read_data_.result == ERR_CONNECTION_CLOSED) {
+ // This MockRead is just a marker to instruct us to set
+ // peer_closed_connection_.
+ peer_closed_connection_ = true;
+ }
+ if (read_data_.result == ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) {
+ // This MockRead is just a marker to instruct us to set
+ // peer_closed_connection_. Skip it and get the next one.
+ read_data_ = data_->GetNextRead();
+ peer_closed_connection_ = true;
+ }
+ // ERR_IO_PENDING means that the SocketDataProvider is taking responsibility
+ // to complete the async IO manually later (via OnReadComplete).
+ if (read_data_.result == ERR_IO_PENDING) {
+ // We need to be using async IO in this case.
+ DCHECK(!callback.is_null());
+ return ERR_IO_PENDING;
+ }
+ need_read_data_ = false;
+ }
+
+ return CompleteRead();
+}
+
+int MockTCPClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ std::string data(buf->data(), buf_len);
+ MockWriteResult write_result = data_->OnWrite(data);
+
+ was_used_to_convey_data_ = true;
+
+ if (write_result.mode == ASYNC) {
+ RunCallbackAsync(callback, write_result.result);
+ return ERR_IO_PENDING;
+ }
+
+ return write_result.result;
+}
+
+int MockTCPClientSocket::Connect(const CompletionCallback& callback) {
+ if (connected_)
+ return OK;
+ connected_ = true;
+ peer_closed_connection_ = false;
+ if (data_->connect_data().mode == ASYNC) {
+ if (data_->connect_data().result == ERR_IO_PENDING)
+ pending_callback_ = callback;
+ else
+ RunCallbackAsync(callback, data_->connect_data().result);
+ return ERR_IO_PENDING;
+ }
+ return data_->connect_data().result;
+}
+
+void MockTCPClientSocket::Disconnect() {
+ MockClientSocket::Disconnect();
+ pending_callback_.Reset();
+}
+
+bool MockTCPClientSocket::IsConnected() const {
+ return connected_ && !peer_closed_connection_;
+}
+
+bool MockTCPClientSocket::IsConnectedAndIdle() const {
+ return IsConnected();
+}
+
+int MockTCPClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ if (addresses_.empty())
+ return MockClientSocket::GetPeerAddress(address);
+
+ *address = addresses_[0];
+ return OK;
+}
+
+bool MockTCPClientSocket::WasEverUsed() const {
+ return was_used_to_convey_data_;
+}
+
+bool MockTCPClientSocket::UsingTCPFastOpen() const {
+ return false;
+}
+
+bool MockTCPClientSocket::WasNpnNegotiated() const {
+ return false;
+}
+
+bool MockTCPClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ return false;
+}
+
+void MockTCPClientSocket::OnReadComplete(const MockRead& data) {
+ // There must be a read pending.
+ DCHECK(pending_buf_);
+ // You can't complete a read with another ERR_IO_PENDING status code.
+ DCHECK_NE(ERR_IO_PENDING, data.result);
+ // Since we've been waiting for data, need_read_data_ should be true.
+ DCHECK(need_read_data_);
+
+ read_data_ = data;
+ need_read_data_ = false;
+
+ // The caller is simulating that this IO completes right now. Don't
+ // let CompleteRead() schedule a callback.
+ read_data_.mode = SYNCHRONOUS;
+
+ CompletionCallback callback = pending_callback_;
+ int rv = CompleteRead();
+ RunCallback(callback, rv);
+}
+
+void MockTCPClientSocket::OnConnectComplete(const MockConnect& data) {
+ CompletionCallback callback = pending_callback_;
+ RunCallback(callback, data.result);
+}
+
+int MockTCPClientSocket::CompleteRead() {
+ DCHECK(pending_buf_);
+ DCHECK(pending_buf_len_ > 0);
+
+ was_used_to_convey_data_ = true;
+
+ // Save the pending async IO data and reset our |pending_| state.
+ IOBuffer* buf = pending_buf_;
+ int buf_len = pending_buf_len_;
+ CompletionCallback callback = pending_callback_;
+ pending_buf_ = NULL;
+ pending_buf_len_ = 0;
+ pending_callback_.Reset();
+
+ int result = read_data_.result;
+ DCHECK(result != ERR_IO_PENDING);
+
+ if (read_data_.data) {
+ if (read_data_.data_len - read_offset_ > 0) {
+ result = std::min(buf_len, read_data_.data_len - read_offset_);
+ memcpy(buf->data(), read_data_.data + read_offset_, result);
+ read_offset_ += result;
+ if (read_offset_ == read_data_.data_len) {
+ need_read_data_ = true;
+ read_offset_ = 0;
+ }
+ } else {
+ result = 0; // EOF
+ }
+ }
+
+ if (read_data_.mode == ASYNC) {
+ DCHECK(!callback.is_null());
+ RunCallbackAsync(callback, result);
+ return ERR_IO_PENDING;
+ }
+ return result;
+}
+
+DeterministicSocketHelper::DeterministicSocketHelper(
+ net::NetLog* net_log,
+ DeterministicSocketData* data)
+ : write_pending_(false),
+ write_result_(0),
+ read_data_(),
+ read_buf_(NULL),
+ read_buf_len_(0),
+ read_pending_(false),
+ data_(data),
+ was_used_to_convey_data_(false),
+ peer_closed_connection_(false),
+ net_log_(BoundNetLog::Make(net_log, net::NetLog::SOURCE_NONE)) {
+}
+
+DeterministicSocketHelper::~DeterministicSocketHelper() {}
+
+void DeterministicSocketHelper::CompleteWrite() {
+ was_used_to_convey_data_ = true;
+ write_pending_ = false;
+ write_callback_.Run(write_result_);
+}
+
+int DeterministicSocketHelper::CompleteRead() {
+ DCHECK_GT(read_buf_len_, 0);
+ DCHECK_LE(read_data_.data_len, read_buf_len_);
+ DCHECK(read_buf_);
+
+ was_used_to_convey_data_ = true;
+
+ if (read_data_.result == ERR_IO_PENDING)
+ read_data_ = data_->GetNextRead();
+ DCHECK_NE(ERR_IO_PENDING, read_data_.result);
+ // If read_data_.mode is ASYNC, we do not need to wait, since this is already
+ // the callback. Therefore we don't even bother to check it.
+ int result = read_data_.result;
+
+ if (read_data_.data_len > 0) {
+ DCHECK(read_data_.data);
+ result = std::min(read_buf_len_, read_data_.data_len);
+ memcpy(read_buf_->data(), read_data_.data, result);
+ }
+
+ if (read_pending_) {
+ read_pending_ = false;
+ read_callback_.Run(result);
+ }
+
+ return result;
+}
+
+int DeterministicSocketHelper::Write(
+ IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+
+ std::string data(buf->data(), buf_len);
+ MockWriteResult write_result = data_->OnWrite(data);
+
+ if (write_result.mode == ASYNC) {
+ write_callback_ = callback;
+ write_result_ = write_result.result;
+ DCHECK(!write_callback_.is_null());
+ write_pending_ = true;
+ return ERR_IO_PENDING;
+ }
+
+ was_used_to_convey_data_ = true;
+ write_pending_ = false;
+ return write_result.result;
+}
+
+int DeterministicSocketHelper::Read(
+ IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+
+ read_data_ = data_->GetNextRead();
+ // The buffer should always be big enough to contain all the MockRead data. To
+ // use small buffers, split the data into multiple MockReads.
+ DCHECK_LE(read_data_.data_len, buf_len);
+
+ if (read_data_.result == ERR_CONNECTION_CLOSED) {
+ // This MockRead is just a marker to instruct us to set
+ // peer_closed_connection_.
+ peer_closed_connection_ = true;
+ }
+ if (read_data_.result == ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ) {
+ // This MockRead is just a marker to instruct us to set
+ // peer_closed_connection_. Skip it and get the next one.
+ read_data_ = data_->GetNextRead();
+ peer_closed_connection_ = true;
+ }
+
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+ read_callback_ = callback;
+
+ if (read_data_.mode == ASYNC || (read_data_.result == ERR_IO_PENDING)) {
+ read_pending_ = true;
+ DCHECK(!read_callback_.is_null());
+ return ERR_IO_PENDING;
+ }
+
+ was_used_to_convey_data_ = true;
+ return CompleteRead();
+}
+
+DeterministicMockUDPClientSocket::DeterministicMockUDPClientSocket(
+ net::NetLog* net_log,
+ DeterministicSocketData* data)
+ : connected_(false),
+ helper_(net_log, data) {
+}
+
+DeterministicMockUDPClientSocket::~DeterministicMockUDPClientSocket() {}
+
+bool DeterministicMockUDPClientSocket::WritePending() const {
+ return helper_.write_pending();
+}
+
+bool DeterministicMockUDPClientSocket::ReadPending() const {
+ return helper_.read_pending();
+}
+
+void DeterministicMockUDPClientSocket::CompleteWrite() {
+ helper_.CompleteWrite();
+}
+
+int DeterministicMockUDPClientSocket::CompleteRead() {
+ return helper_.CompleteRead();
+}
+
+int DeterministicMockUDPClientSocket::Connect(const IPEndPoint& address) {
+ if (connected_)
+ return OK;
+ connected_ = true;
+ peer_address_ = address;
+ return helper_.data()->connect_data().result;
+};
+
+int DeterministicMockUDPClientSocket::Write(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ return helper_.Write(buf, buf_len, callback);
+}
+
+int DeterministicMockUDPClientSocket::Read(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ return helper_.Read(buf, buf_len, callback);
+}
+
+bool DeterministicMockUDPClientSocket::SetReceiveBufferSize(int32 size) {
+ return true;
+}
+
+bool DeterministicMockUDPClientSocket::SetSendBufferSize(int32 size) {
+ return true;
+}
+
+void DeterministicMockUDPClientSocket::Close() {
+ connected_ = false;
+}
+
+int DeterministicMockUDPClientSocket::GetPeerAddress(
+ IPEndPoint* address) const {
+ *address = peer_address_;
+ return OK;
+}
+
+int DeterministicMockUDPClientSocket::GetLocalAddress(
+ IPEndPoint* address) const {
+ IPAddressNumber ip;
+ bool rv = ParseIPLiteralToNumber("192.0.2.33", &ip);
+ CHECK(rv);
+ *address = IPEndPoint(ip, 123);
+ return OK;
+}
+
+const BoundNetLog& DeterministicMockUDPClientSocket::NetLog() const {
+ return helper_.net_log();
+}
+
+void DeterministicMockUDPClientSocket::OnReadComplete(const MockRead& data) {}
+
+void DeterministicMockUDPClientSocket::OnConnectComplete(
+ const MockConnect& data) {
+ NOTIMPLEMENTED();
+}
+
+DeterministicMockTCPClientSocket::DeterministicMockTCPClientSocket(
+ net::NetLog* net_log,
+ DeterministicSocketData* data)
+ : MockClientSocket(BoundNetLog::Make(net_log, net::NetLog::SOURCE_NONE)),
+ helper_(net_log, data) {
+ peer_addr_ = data->connect_data().peer_addr;
+}
+
+DeterministicMockTCPClientSocket::~DeterministicMockTCPClientSocket() {}
+
+bool DeterministicMockTCPClientSocket::WritePending() const {
+ return helper_.write_pending();
+}
+
+bool DeterministicMockTCPClientSocket::ReadPending() const {
+ return helper_.read_pending();
+}
+
+void DeterministicMockTCPClientSocket::CompleteWrite() {
+ helper_.CompleteWrite();
+}
+
+int DeterministicMockTCPClientSocket::CompleteRead() {
+ return helper_.CompleteRead();
+}
+
+int DeterministicMockTCPClientSocket::Write(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ return helper_.Write(buf, buf_len, callback);
+}
+
+int DeterministicMockTCPClientSocket::Read(
+ IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ return helper_.Read(buf, buf_len, callback);
+}
+
+// TODO(erikchen): Support connect sequencing.
+int DeterministicMockTCPClientSocket::Connect(
+ const CompletionCallback& callback) {
+ if (connected_)
+ return OK;
+ connected_ = true;
+ if (helper_.data()->connect_data().mode == ASYNC) {
+ RunCallbackAsync(callback, helper_.data()->connect_data().result);
+ return ERR_IO_PENDING;
+ }
+ return helper_.data()->connect_data().result;
+}
+
+void DeterministicMockTCPClientSocket::Disconnect() {
+ MockClientSocket::Disconnect();
+}
+
+bool DeterministicMockTCPClientSocket::IsConnected() const {
+ return connected_ && !helper_.peer_closed_connection();
+}
+
+bool DeterministicMockTCPClientSocket::IsConnectedAndIdle() const {
+ return IsConnected();
+}
+
+bool DeterministicMockTCPClientSocket::WasEverUsed() const {
+ return helper_.was_used_to_convey_data();
+}
+
+bool DeterministicMockTCPClientSocket::UsingTCPFastOpen() const {
+ return false;
+}
+
+bool DeterministicMockTCPClientSocket::WasNpnNegotiated() const {
+ return false;
+}
+
+bool DeterministicMockTCPClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ return false;
+}
+
+void DeterministicMockTCPClientSocket::OnReadComplete(const MockRead& data) {}
+
+void DeterministicMockTCPClientSocket::OnConnectComplete(
+ const MockConnect& data) {}
+
+// static
+void MockSSLClientSocket::ConnectCallback(
+ MockSSLClientSocket* ssl_client_socket,
+ const CompletionCallback& callback,
+ int rv) {
+ if (rv == OK)
+ ssl_client_socket->connected_ = true;
+ callback.Run(rv);
+}
+
+MockSSLClientSocket::MockSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_port_pair,
+ const SSLConfig& ssl_config,
+ SSLSocketDataProvider* data)
+ : MockClientSocket(
+ // Have to use the right BoundNetLog for LoadTimingInfo regression
+ // tests.
+ transport_socket->socket()->NetLog()),
+ transport_(transport_socket.Pass()),
+ data_(data),
+ is_npn_state_set_(false),
+ new_npn_value_(false),
+ is_protocol_negotiated_set_(false),
+ protocol_negotiated_(kProtoUnknown) {
+ DCHECK(data_);
+ peer_addr_ = data->connect.peer_addr;
+}
+
+MockSSLClientSocket::~MockSSLClientSocket() {
+ Disconnect();
+}
+
+int MockSSLClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+int MockSSLClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+int MockSSLClientSocket::Connect(const CompletionCallback& callback) {
+ int rv = transport_->socket()->Connect(
+ base::Bind(&ConnectCallback, base::Unretained(this), callback));
+ if (rv == OK) {
+ if (data_->connect.result == OK)
+ connected_ = true;
+ if (data_->connect.mode == ASYNC) {
+ RunCallbackAsync(callback, data_->connect.result);
+ return ERR_IO_PENDING;
+ }
+ return data_->connect.result;
+ }
+ return rv;
+}
+
+void MockSSLClientSocket::Disconnect() {
+ MockClientSocket::Disconnect();
+ if (transport_->socket() != NULL)
+ transport_->socket()->Disconnect();
+}
+
+bool MockSSLClientSocket::IsConnected() const {
+ return transport_->socket()->IsConnected();
+}
+
+bool MockSSLClientSocket::WasEverUsed() const {
+ return transport_->socket()->WasEverUsed();
+}
+
+bool MockSSLClientSocket::UsingTCPFastOpen() const {
+ return transport_->socket()->UsingTCPFastOpen();
+}
+
+int MockSSLClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+bool MockSSLClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ ssl_info->Reset();
+ ssl_info->cert = data_->cert;
+ ssl_info->client_cert_sent = data_->client_cert_sent;
+ ssl_info->channel_id_sent = data_->channel_id_sent;
+ return true;
+}
+
+void MockSSLClientSocket::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK(cert_request_info);
+ if (data_->cert_request_info) {
+ cert_request_info->host_and_port =
+ data_->cert_request_info->host_and_port;
+ cert_request_info->client_certs = data_->cert_request_info->client_certs;
+ } else {
+ cert_request_info->Reset();
+ }
+}
+
+SSLClientSocket::NextProtoStatus MockSSLClientSocket::GetNextProto(
+ std::string* proto, std::string* server_protos) {
+ *proto = data_->next_proto;
+ *server_protos = data_->server_protos;
+ return data_->next_proto_status;
+}
+
+bool MockSSLClientSocket::set_was_npn_negotiated(bool negotiated) {
+ is_npn_state_set_ = true;
+ return new_npn_value_ = negotiated;
+}
+
+bool MockSSLClientSocket::WasNpnNegotiated() const {
+ if (is_npn_state_set_)
+ return new_npn_value_;
+ return data_->was_npn_negotiated;
+}
+
+NextProto MockSSLClientSocket::GetNegotiatedProtocol() const {
+ if (is_protocol_negotiated_set_)
+ return protocol_negotiated_;
+ return data_->protocol_negotiated;
+}
+
+void MockSSLClientSocket::set_protocol_negotiated(
+ NextProto protocol_negotiated) {
+ is_protocol_negotiated_set_ = true;
+ protocol_negotiated_ = protocol_negotiated;
+}
+
+bool MockSSLClientSocket::WasChannelIDSent() const {
+ return data_->channel_id_sent;
+}
+
+void MockSSLClientSocket::set_channel_id_sent(bool channel_id_sent) {
+ data_->channel_id_sent = channel_id_sent;
+}
+
+ServerBoundCertService* MockSSLClientSocket::GetServerBoundCertService() const {
+ return data_->server_bound_cert_service;
+}
+
+void MockSSLClientSocket::OnReadComplete(const MockRead& data) {
+ NOTIMPLEMENTED();
+}
+
+void MockSSLClientSocket::OnConnectComplete(const MockConnect& data) {
+ NOTIMPLEMENTED();
+}
+
+MockUDPClientSocket::MockUDPClientSocket(SocketDataProvider* data,
+ net::NetLog* net_log)
+ : connected_(false),
+ data_(data),
+ read_offset_(0),
+ read_data_(SYNCHRONOUS, ERR_UNEXPECTED),
+ need_read_data_(true),
+ pending_buf_(NULL),
+ pending_buf_len_(0),
+ net_log_(BoundNetLog::Make(net_log, net::NetLog::SOURCE_NONE)),
+ weak_factory_(this) {
+ DCHECK(data_);
+ data_->Reset();
+ peer_addr_ = data->connect_data().peer_addr;
+}
+
+MockUDPClientSocket::~MockUDPClientSocket() {}
+
+int MockUDPClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ // If the buffer is already in use, a read is already in progress!
+ DCHECK(pending_buf_ == NULL);
+
+ // Store our async IO data.
+ pending_buf_ = buf;
+ pending_buf_len_ = buf_len;
+ pending_callback_ = callback;
+
+ if (need_read_data_) {
+ read_data_ = data_->GetNextRead();
+ // ERR_IO_PENDING means that the SocketDataProvider is taking responsibility
+ // to complete the async IO manually later (via OnReadComplete).
+ if (read_data_.result == ERR_IO_PENDING) {
+ // We need to be using async IO in this case.
+ DCHECK(!callback.is_null());
+ return ERR_IO_PENDING;
+ }
+ need_read_data_ = false;
+ }
+
+ return CompleteRead();
+}
+
+int MockUDPClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+
+ if (!connected_)
+ return ERR_UNEXPECTED;
+
+ std::string data(buf->data(), buf_len);
+ MockWriteResult write_result = data_->OnWrite(data);
+
+ if (write_result.mode == ASYNC) {
+ RunCallbackAsync(callback, write_result.result);
+ return ERR_IO_PENDING;
+ }
+ return write_result.result;
+}
+
+bool MockUDPClientSocket::SetReceiveBufferSize(int32 size) {
+ return true;
+}
+
+bool MockUDPClientSocket::SetSendBufferSize(int32 size) {
+ return true;
+}
+
+void MockUDPClientSocket::Close() {
+ connected_ = false;
+}
+
+int MockUDPClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ *address = peer_addr_;
+ return OK;
+}
+
+int MockUDPClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ IPAddressNumber ip;
+ bool rv = ParseIPLiteralToNumber("192.0.2.33", &ip);
+ CHECK(rv);
+ *address = IPEndPoint(ip, 123);
+ return OK;
+}
+
+const BoundNetLog& MockUDPClientSocket::NetLog() const {
+ return net_log_;
+}
+
+int MockUDPClientSocket::Connect(const IPEndPoint& address) {
+ connected_ = true;
+ peer_addr_ = address;
+ return OK;
+}
+
+void MockUDPClientSocket::OnReadComplete(const MockRead& data) {
+ // There must be a read pending.
+ DCHECK(pending_buf_);
+ // You can't complete a read with another ERR_IO_PENDING status code.
+ DCHECK_NE(ERR_IO_PENDING, data.result);
+ // Since we've been waiting for data, need_read_data_ should be true.
+ DCHECK(need_read_data_);
+
+ read_data_ = data;
+ need_read_data_ = false;
+
+ // The caller is simulating that this IO completes right now. Don't
+ // let CompleteRead() schedule a callback.
+ read_data_.mode = SYNCHRONOUS;
+
+ net::CompletionCallback callback = pending_callback_;
+ int rv = CompleteRead();
+ RunCallback(callback, rv);
+}
+
+void MockUDPClientSocket::OnConnectComplete(const MockConnect& data) {
+ NOTIMPLEMENTED();
+}
+
+int MockUDPClientSocket::CompleteRead() {
+ DCHECK(pending_buf_);
+ DCHECK(pending_buf_len_ > 0);
+
+ // Save the pending async IO data and reset our |pending_| state.
+ IOBuffer* buf = pending_buf_;
+ int buf_len = pending_buf_len_;
+ CompletionCallback callback = pending_callback_;
+ pending_buf_ = NULL;
+ pending_buf_len_ = 0;
+ pending_callback_.Reset();
+
+ int result = read_data_.result;
+ DCHECK(result != ERR_IO_PENDING);
+
+ if (read_data_.data) {
+ if (read_data_.data_len - read_offset_ > 0) {
+ result = std::min(buf_len, read_data_.data_len - read_offset_);
+ memcpy(buf->data(), read_data_.data + read_offset_, result);
+ read_offset_ += result;
+ if (read_offset_ == read_data_.data_len) {
+ need_read_data_ = true;
+ read_offset_ = 0;
+ }
+ } else {
+ result = 0; // EOF
+ }
+ }
+
+ if (read_data_.mode == ASYNC) {
+ DCHECK(!callback.is_null());
+ RunCallbackAsync(callback, result);
+ return ERR_IO_PENDING;
+ }
+ return result;
+}
+
+void MockUDPClientSocket::RunCallbackAsync(const CompletionCallback& callback,
+ int result) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockUDPClientSocket::RunCallback,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ result));
+}
+
+void MockUDPClientSocket::RunCallback(const CompletionCallback& callback,
+ int result) {
+ if (!callback.is_null())
+ callback.Run(result);
+}
+
+TestSocketRequest::TestSocketRequest(
+ std::vector<TestSocketRequest*>* request_order, size_t* completion_count)
+ : request_order_(request_order),
+ completion_count_(completion_count),
+ callback_(base::Bind(&TestSocketRequest::OnComplete,
+ base::Unretained(this))) {
+ DCHECK(request_order);
+ DCHECK(completion_count);
+}
+
+TestSocketRequest::~TestSocketRequest() {
+}
+
+void TestSocketRequest::OnComplete(int result) {
+ SetResult(result);
+ (*completion_count_)++;
+ request_order_->push_back(this);
+}
+
+// static
+const int ClientSocketPoolTest::kIndexOutOfBounds = -1;
+
+// static
+const int ClientSocketPoolTest::kRequestNotFound = -2;
+
+ClientSocketPoolTest::ClientSocketPoolTest() : completion_count_(0) {}
+ClientSocketPoolTest::~ClientSocketPoolTest() {}
+
+int ClientSocketPoolTest::GetOrderOfRequest(size_t index) const {
+ index--;
+ if (index >= requests_.size())
+ return kIndexOutOfBounds;
+
+ for (size_t i = 0; i < request_order_.size(); i++)
+ if (requests_[index] == request_order_[i])
+ return i + 1;
+
+ return kRequestNotFound;
+}
+
+bool ClientSocketPoolTest::ReleaseOneConnection(KeepAlive keep_alive) {
+ ScopedVector<TestSocketRequest>::iterator i;
+ for (i = requests_.begin(); i != requests_.end(); ++i) {
+ if ((*i)->handle()->is_initialized()) {
+ if (keep_alive == NO_KEEP_ALIVE)
+ (*i)->handle()->socket()->Disconnect();
+ (*i)->handle()->Reset();
+ base::RunLoop().RunUntilIdle();
+ return true;
+ }
+ }
+ return false;
+}
+
+void ClientSocketPoolTest::ReleaseAllConnections(KeepAlive keep_alive) {
+ bool released_one;
+ do {
+ released_one = ReleaseOneConnection(keep_alive);
+ } while (released_one);
+}
+
+MockTransportClientSocketPool::MockConnectJob::MockConnectJob(
+ scoped_ptr<StreamSocket> socket,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback)
+ : socket_(socket.Pass()),
+ handle_(handle),
+ user_callback_(callback) {
+}
+
+MockTransportClientSocketPool::MockConnectJob::~MockConnectJob() {}
+
+int MockTransportClientSocketPool::MockConnectJob::Connect() {
+ int rv = socket_->Connect(base::Bind(&MockConnectJob::OnConnect,
+ base::Unretained(this)));
+ if (rv == OK) {
+ user_callback_.Reset();
+ OnConnect(OK);
+ }
+ return rv;
+}
+
+bool MockTransportClientSocketPool::MockConnectJob::CancelHandle(
+ const ClientSocketHandle* handle) {
+ if (handle != handle_)
+ return false;
+ socket_.reset();
+ handle_ = NULL;
+ user_callback_.Reset();
+ return true;
+}
+
+void MockTransportClientSocketPool::MockConnectJob::OnConnect(int rv) {
+ if (!socket_.get())
+ return;
+ if (rv == OK) {
+ handle_->SetSocket(socket_.Pass());
+
+ // Needed for socket pool tests that layer other sockets on top of mock
+ // sockets.
+ LoadTimingInfo::ConnectTiming connect_timing;
+ base::TimeTicks now = base::TimeTicks::Now();
+ connect_timing.dns_start = now;
+ connect_timing.dns_end = now;
+ connect_timing.connect_start = now;
+ connect_timing.connect_end = now;
+ handle_->set_connect_timing(connect_timing);
+ } else {
+ socket_.reset();
+ }
+
+ handle_ = NULL;
+
+ if (!user_callback_.is_null()) {
+ CompletionCallback callback = user_callback_;
+ user_callback_.Reset();
+ callback.Run(rv);
+ }
+}
+
+MockTransportClientSocketPool::MockTransportClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ ClientSocketFactory* socket_factory)
+ : TransportClientSocketPool(max_sockets, max_sockets_per_group, histograms,
+ NULL, NULL, NULL),
+ client_socket_factory_(socket_factory),
+ release_count_(0),
+ cancel_count_(0) {
+}
+
+MockTransportClientSocketPool::~MockTransportClientSocketPool() {}
+
+int MockTransportClientSocketPool::RequestSocket(
+ const std::string& group_name, const void* socket_params,
+ RequestPriority priority, ClientSocketHandle* handle,
+ const CompletionCallback& callback, const BoundNetLog& net_log) {
+ scoped_ptr<StreamSocket> socket =
+ client_socket_factory_->CreateTransportClientSocket(
+ AddressList(), net_log.net_log(), net::NetLog::Source());
+ MockConnectJob* job = new MockConnectJob(socket.Pass(), handle, callback);
+ job_list_.push_back(job);
+ handle->set_pool_id(1);
+ return job->Connect();
+}
+
+void MockTransportClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ std::vector<MockConnectJob*>::iterator i;
+ for (i = job_list_.begin(); i != job_list_.end(); ++i) {
+ if ((*i)->CancelHandle(handle)) {
+ cancel_count_++;
+ break;
+ }
+ }
+}
+
+void MockTransportClientSocketPool::ReleaseSocket(
+ const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ EXPECT_EQ(1, id);
+ release_count_++;
+}
+
+DeterministicMockClientSocketFactory::DeterministicMockClientSocketFactory() {}
+
+DeterministicMockClientSocketFactory::~DeterministicMockClientSocketFactory() {}
+
+void DeterministicMockClientSocketFactory::AddSocketDataProvider(
+ DeterministicSocketData* data) {
+ mock_data_.Add(data);
+}
+
+void DeterministicMockClientSocketFactory::AddSSLSocketDataProvider(
+ SSLSocketDataProvider* data) {
+ mock_ssl_data_.Add(data);
+}
+
+void DeterministicMockClientSocketFactory::ResetNextMockIndexes() {
+ mock_data_.ResetNextIndex();
+ mock_ssl_data_.ResetNextIndex();
+}
+
+MockSSLClientSocket* DeterministicMockClientSocketFactory::
+ GetMockSSLClientSocket(size_t index) const {
+ DCHECK_LT(index, ssl_client_sockets_.size());
+ return ssl_client_sockets_[index];
+}
+
+scoped_ptr<DatagramClientSocket>
+DeterministicMockClientSocketFactory::CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const NetLog::Source& source) {
+ DeterministicSocketData* data_provider = mock_data().GetNext();
+ scoped_ptr<DeterministicMockUDPClientSocket> socket(
+ new DeterministicMockUDPClientSocket(net_log, data_provider));
+ data_provider->set_delegate(socket->AsWeakPtr());
+ udp_client_sockets().push_back(socket.get());
+ return socket.PassAs<DatagramClientSocket>();
+}
+
+scoped_ptr<StreamSocket>
+DeterministicMockClientSocketFactory::CreateTransportClientSocket(
+ const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source) {
+ DeterministicSocketData* data_provider = mock_data().GetNext();
+ scoped_ptr<DeterministicMockTCPClientSocket> socket(
+ new DeterministicMockTCPClientSocket(net_log, data_provider));
+ data_provider->set_delegate(socket->AsWeakPtr());
+ tcp_client_sockets().push_back(socket.get());
+ return socket.PassAs<StreamSocket>();
+}
+
+scoped_ptr<SSLClientSocket>
+DeterministicMockClientSocketFactory::CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) {
+ scoped_ptr<MockSSLClientSocket> socket(
+ new MockSSLClientSocket(transport_socket.Pass(),
+ host_and_port, ssl_config,
+ mock_ssl_data_.GetNext()));
+ ssl_client_sockets_.push_back(socket.get());
+ return socket.PassAs<SSLClientSocket>();
+}
+
+void DeterministicMockClientSocketFactory::ClearSSLSessionCache() {
+}
+
+MockSOCKSClientSocketPool::MockSOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ TransportClientSocketPool* transport_pool)
+ : SOCKSClientSocketPool(max_sockets, max_sockets_per_group, histograms,
+ NULL, transport_pool, NULL),
+ transport_pool_(transport_pool) {
+}
+
+MockSOCKSClientSocketPool::~MockSOCKSClientSocketPool() {}
+
+int MockSOCKSClientSocketPool::RequestSocket(
+ const std::string& group_name, const void* socket_params,
+ RequestPriority priority, ClientSocketHandle* handle,
+ const CompletionCallback& callback, const BoundNetLog& net_log) {
+ return transport_pool_->RequestSocket(
+ group_name, socket_params, priority, handle, callback, net_log);
+}
+
+void MockSOCKSClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) {
+ return transport_pool_->CancelRequest(group_name, handle);
+}
+
+void MockSOCKSClientSocketPool::ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ return transport_pool_->ReleaseSocket(group_name, socket.Pass(), id);
+}
+
+const char kSOCKS5GreetRequest[] = { 0x05, 0x01, 0x00 };
+const int kSOCKS5GreetRequestLength = arraysize(kSOCKS5GreetRequest);
+
+const char kSOCKS5GreetResponse[] = { 0x05, 0x00 };
+const int kSOCKS5GreetResponseLength = arraysize(kSOCKS5GreetResponse);
+
+const char kSOCKS5OkRequest[] =
+ { 0x05, 0x01, 0x00, 0x03, 0x04, 'h', 'o', 's', 't', 0x00, 0x50 };
+const int kSOCKS5OkRequestLength = arraysize(kSOCKS5OkRequest);
+
+const char kSOCKS5OkResponse[] =
+ { 0x05, 0x00, 0x00, 0x01, 127, 0, 0, 1, 0x00, 0x50 };
+const int kSOCKS5OkResponseLength = arraysize(kSOCKS5OkResponse);
+
+} // namespace net
diff --git a/chromium/net/socket/socket_test_util.h b/chromium/net/socket/socket_test_util.h
new file mode 100644
index 00000000000..a888249654c
--- /dev/null
+++ b/chromium/net/socket/socket_test_util.h
@@ -0,0 +1,1198 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SOCKET_TEST_UTIL_H_
+#define NET_SOCKET_SOCKET_TEST_UTIL_H_
+
+#include <cstring>
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_completion_callback.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_pool.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/udp/datagram_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+enum {
+ // A private network error code used by the socket test utility classes.
+ // If the |result| member of a MockRead is
+ // ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ, that MockRead is just a
+ // marker that indicates the peer will close the connection after the next
+ // MockRead. The other members of that MockRead are ignored.
+ ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ = -10000,
+};
+
+class AsyncSocket;
+class MockClientSocket;
+class ServerBoundCertService;
+class SSLClientSocket;
+class StreamSocket;
+
+enum IoMode {
+ ASYNC,
+ SYNCHRONOUS
+};
+
+struct MockConnect {
+ // Asynchronous connection success.
+ // Creates a MockConnect with |mode| ASYC, |result| OK, and
+ // |peer_addr| 192.0.2.33.
+ MockConnect();
+ // Creates a MockConnect with the specified mode and result, with
+ // |peer_addr| 192.0.2.33.
+ MockConnect(IoMode io_mode, int r);
+ MockConnect(IoMode io_mode, int r, IPEndPoint addr);
+ ~MockConnect();
+
+ IoMode mode;
+ int result;
+ IPEndPoint peer_addr;
+};
+
+// MockRead and MockWrite shares the same interface and members, but we'd like
+// to have distinct types because we don't want to have them used
+// interchangably. To do this, a struct template is defined, and MockRead and
+// MockWrite are instantiated by using this template. Template parameter |type|
+// is not used in the struct definition (it purely exists for creating a new
+// type).
+//
+// |data| in MockRead and MockWrite has different meanings: |data| in MockRead
+// is the data returned from the socket when MockTCPClientSocket::Read() is
+// attempted, while |data| in MockWrite is the expected data that should be
+// given in MockTCPClientSocket::Write().
+enum MockReadWriteType {
+ MOCK_READ,
+ MOCK_WRITE
+};
+
+template <MockReadWriteType type>
+struct MockReadWrite {
+ // Flag to indicate that the message loop should be terminated.
+ enum {
+ STOPLOOP = 1 << 31
+ };
+
+ // Default
+ MockReadWrite() : mode(SYNCHRONOUS), result(0), data(NULL), data_len(0),
+ sequence_number(0), time_stamp(base::Time::Now()) {}
+
+ // Read/write failure (no data).
+ MockReadWrite(IoMode io_mode, int result) : mode(io_mode), result(result),
+ data(NULL), data_len(0), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
+
+ // Read/write failure (no data), with sequence information.
+ MockReadWrite(IoMode io_mode, int result, int seq) : mode(io_mode),
+ result(result), data(NULL), data_len(0), sequence_number(seq),
+ time_stamp(base::Time::Now()) { }
+
+ // Asynchronous read/write success (inferred data length).
+ explicit MockReadWrite(const char* data) : mode(ASYNC), result(0),
+ data(data), data_len(strlen(data)), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
+
+ // Read/write success (inferred data length).
+ MockReadWrite(IoMode io_mode, const char* data) : mode(io_mode), result(0),
+ data(data), data_len(strlen(data)), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
+
+ // Read/write success.
+ MockReadWrite(IoMode io_mode, const char* data, int data_len) : mode(io_mode),
+ result(0), data(data), data_len(data_len), sequence_number(0),
+ time_stamp(base::Time::Now()) { }
+
+ // Read/write success (inferred data length) with sequence information.
+ MockReadWrite(IoMode io_mode, int seq, const char* data) : mode(io_mode),
+ result(0), data(data), data_len(strlen(data)), sequence_number(seq),
+ time_stamp(base::Time::Now()) { }
+
+ // Read/write success with sequence information.
+ MockReadWrite(IoMode io_mode, const char* data, int data_len, int seq) :
+ mode(io_mode), result(0), data(data), data_len(data_len),
+ sequence_number(seq), time_stamp(base::Time::Now()) { }
+
+ IoMode mode;
+ int result;
+ const char* data;
+ int data_len;
+
+ // For OrderedSocketData, which only allows reads to occur in a particular
+ // sequence. If a read occurs before the given |sequence_number| is reached,
+ // an ERR_IO_PENDING is returned.
+ int sequence_number; // The sequence number at which a read is allowed
+ // to occur.
+ base::Time time_stamp; // The time stamp at which the operation occurred.
+};
+
+typedef MockReadWrite<MOCK_READ> MockRead;
+typedef MockReadWrite<MOCK_WRITE> MockWrite;
+
+struct MockWriteResult {
+ MockWriteResult(IoMode io_mode, int result)
+ : mode(io_mode),
+ result(result) {}
+
+ IoMode mode;
+ int result;
+};
+
+// The SocketDataProvider is an interface used by the MockClientSocket
+// for getting data about individual reads and writes on the socket.
+class SocketDataProvider {
+ public:
+ SocketDataProvider() : socket_(NULL) {}
+
+ virtual ~SocketDataProvider() {}
+
+ // Returns the buffer and result code for the next simulated read.
+ // If the |MockRead.result| is ERR_IO_PENDING, it informs the caller
+ // that it will be called via the AsyncSocket::OnReadComplete()
+ // function at a later time.
+ virtual MockRead GetNextRead() = 0;
+ virtual MockWriteResult OnWrite(const std::string& data) = 0;
+ virtual void Reset() = 0;
+
+ // Accessor for the socket which is using the SocketDataProvider.
+ AsyncSocket* socket() { return socket_; }
+ void set_socket(AsyncSocket* socket) { socket_ = socket; }
+
+ MockConnect connect_data() const { return connect_; }
+ void set_connect_data(const MockConnect& connect) { connect_ = connect; }
+
+ private:
+ MockConnect connect_;
+ AsyncSocket* socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketDataProvider);
+};
+
+// The AsyncSocket is an interface used by the SocketDataProvider to
+// complete the asynchronous read operation.
+class AsyncSocket {
+ public:
+ // If an async IO is pending because the SocketDataProvider returned
+ // ERR_IO_PENDING, then the AsyncSocket waits until this OnReadComplete
+ // is called to complete the asynchronous read operation.
+ // data.async is ignored, and this read is completed synchronously as
+ // part of this call.
+ virtual void OnReadComplete(const MockRead& data) = 0;
+ virtual void OnConnectComplete(const MockConnect& data) = 0;
+};
+
+// SocketDataProvider which responds based on static tables of mock reads and
+// writes.
+class StaticSocketDataProvider : public SocketDataProvider {
+ public:
+ StaticSocketDataProvider();
+ StaticSocketDataProvider(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+ virtual ~StaticSocketDataProvider();
+
+ // These functions get access to the next available read and write data.
+ const MockRead& PeekRead() const;
+ const MockWrite& PeekWrite() const;
+ // These functions get random access to the read and write data, for timing.
+ const MockRead& PeekRead(size_t index) const;
+ const MockWrite& PeekWrite(size_t index) const;
+ size_t read_index() const { return read_index_; }
+ size_t write_index() const { return write_index_; }
+ size_t read_count() const { return read_count_; }
+ size_t write_count() const { return write_count_; }
+
+ bool at_read_eof() const { return read_index_ >= read_count_; }
+ bool at_write_eof() const { return write_index_ >= write_count_; }
+
+ virtual void CompleteRead() {}
+
+ // SocketDataProvider implementation.
+ virtual MockRead GetNextRead() OVERRIDE;
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE;
+ ; virtual void Reset() OVERRIDE;
+
+ private:
+ MockRead* reads_;
+ size_t read_index_;
+ size_t read_count_;
+ MockWrite* writes_;
+ size_t write_index_;
+ size_t write_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(StaticSocketDataProvider);
+};
+
+// SocketDataProvider which can make decisions about next mock reads based on
+// received writes. It can also be used to enforce order of operations, for
+// example that tested code must send the "Hello!" message before receiving
+// response. This is useful for testing conversation-like protocols like FTP.
+class DynamicSocketDataProvider : public SocketDataProvider {
+ public:
+ DynamicSocketDataProvider();
+ virtual ~DynamicSocketDataProvider();
+
+ int short_read_limit() const { return short_read_limit_; }
+ void set_short_read_limit(int limit) { short_read_limit_ = limit; }
+
+ void allow_unconsumed_reads(bool allow) { allow_unconsumed_reads_ = allow; }
+
+ // SocketDataProvider implementation.
+ virtual MockRead GetNextRead() OVERRIDE;
+ virtual MockWriteResult OnWrite(const std::string& data) = 0;
+ virtual void Reset() OVERRIDE;
+
+ protected:
+ // The next time there is a read from this socket, it will return |data|.
+ // Before calling SimulateRead next time, the previous data must be consumed.
+ void SimulateRead(const char* data, size_t length);
+ void SimulateRead(const char* data) {
+ SimulateRead(data, std::strlen(data));
+ }
+
+ private:
+ std::deque<MockRead> reads_;
+
+ // Max number of bytes we will read at a time. 0 means no limit.
+ int short_read_limit_;
+
+ // If true, we'll not require the client to consume all data before we
+ // mock the next read.
+ bool allow_unconsumed_reads_;
+
+ DISALLOW_COPY_AND_ASSIGN(DynamicSocketDataProvider);
+};
+
+// SSLSocketDataProviders only need to keep track of the return code from calls
+// to Connect().
+struct SSLSocketDataProvider {
+ SSLSocketDataProvider(IoMode mode, int result);
+ ~SSLSocketDataProvider();
+
+ void SetNextProto(NextProto proto);
+
+ MockConnect connect;
+ SSLClientSocket::NextProtoStatus next_proto_status;
+ std::string next_proto;
+ std::string server_protos;
+ bool was_npn_negotiated;
+ NextProto protocol_negotiated;
+ bool client_cert_sent;
+ SSLCertRequestInfo* cert_request_info;
+ scoped_refptr<X509Certificate> cert;
+ bool channel_id_sent;
+ ServerBoundCertService* server_bound_cert_service;
+};
+
+// A DataProvider where the client must write a request before the reads (e.g.
+// the response) will complete.
+class DelayedSocketData : public StaticSocketDataProvider {
+ public:
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: For stream sockets, the MockRead list must end with a EOF, e.g., a
+ // MockRead(true, 0, 0);
+ DelayedSocketData(int write_delay,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ // |connect| the result for the connect phase.
+ // |reads| the list of MockRead completions.
+ // |write_delay| the number of MockWrites to complete before allowing
+ // a MockRead to complete.
+ // |writes| the list of MockWrite completions.
+ // Note: For stream sockets, the MockRead list must end with a EOF, e.g., a
+ // MockRead(true, 0, 0);
+ DelayedSocketData(const MockConnect& connect, int write_delay,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+ virtual ~DelayedSocketData();
+
+ void ForceNextRead();
+
+ // StaticSocketDataProvider:
+ virtual MockRead GetNextRead() OVERRIDE;
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void CompleteRead() OVERRIDE;
+
+ private:
+ int write_delay_;
+ bool read_in_progress_;
+ base::WeakPtrFactory<DelayedSocketData> weak_factory_;
+};
+
+// A DataProvider where the reads are ordered.
+// If a read is requested before its sequence number is reached, we return an
+// ERR_IO_PENDING (that way we don't have to explicitly add a MockRead just to
+// wait).
+// The sequence number is incremented on every read and write operation.
+// The message loop may be interrupted by setting the high bit of the sequence
+// number in the MockRead's sequence number. When that MockRead is reached,
+// we post a Quit message to the loop. This allows us to interrupt the reading
+// of data before a complete message has arrived, and provides support for
+// testing server push when the request is issued while the response is in the
+// middle of being received.
+class OrderedSocketData : public StaticSocketDataProvider {
+ public:
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: For stream sockets, the MockRead list must end with a EOF, e.g., a
+ // MockRead(true, 0, 0);
+ OrderedSocketData(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+ virtual ~OrderedSocketData();
+
+ // |connect| the result for the connect phase.
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ // Note: All MockReads and MockWrites must be async.
+ // Note: For stream sockets, the MockRead list must end with a EOF, e.g., a
+ // MockRead(true, 0, 0);
+ OrderedSocketData(const MockConnect& connect,
+ MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ // Posts a quit message to the current message loop, if one is running.
+ void EndLoop();
+
+ // StaticSocketDataProvider:
+ virtual MockRead GetNextRead() OVERRIDE;
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void CompleteRead() OVERRIDE;
+
+ private:
+ int sequence_number_;
+ int loop_stop_stage_;
+ bool blocked_;
+ base::WeakPtrFactory<OrderedSocketData> weak_factory_;
+};
+
+class DeterministicMockTCPClientSocket;
+
+// This class gives the user full control over the network activity,
+// specifically the timing of the COMPLETION of I/O operations. Regardless of
+// the order in which I/O operations are initiated, this class ensures that they
+// complete in the correct order.
+//
+// Network activity is modeled as a sequence of numbered steps which is
+// incremented whenever an I/O operation completes. This can happen under two
+// different circumstances:
+//
+// 1) Performing a synchronous I/O operation. (Invoking Read() or Write()
+// when the corresponding MockRead or MockWrite is marked !async).
+// 2) Running the Run() method of this class. The run method will invoke
+// the current MessageLoop, running all pending events, and will then
+// invoke any pending IO callbacks.
+//
+// In addition, this class allows for I/O processing to "stop" at a specified
+// step, by calling SetStop(int) or StopAfter(int). Initiating an I/O operation
+// by calling Read() or Write() while stopped is permitted if the operation is
+// asynchronous. It is an error to perform synchronous I/O while stopped.
+//
+// When creating the MockReads and MockWrites, note that the sequence number
+// refers to the number of the step in which the I/O will complete. In the
+// case of synchronous I/O, this will be the same step as the I/O is initiated.
+// However, in the case of asynchronous I/O, this I/O may be initiated in
+// a much earlier step. Furthermore, when the a Read() or Write() is separated
+// from its completion by other Read() or Writes()'s, it can not be marked
+// synchronous. If it is, ERR_UNUEXPECTED will be returned indicating that a
+// synchronous Read() or Write() could not be completed synchronously because of
+// the specific ordering constraints.
+//
+// Sequence numbers are preserved across both reads and writes. There should be
+// no gaps in sequence numbers, and no repeated sequence numbers. i.e.
+// MockRead reads[] = {
+// MockRead(false, "first read", length, 0) // sync
+// MockRead(true, "second read", length, 2) // async
+// };
+// MockWrite writes[] = {
+// MockWrite(true, "first write", length, 1), // async
+// MockWrite(false, "second write", length, 3), // sync
+// };
+//
+// Example control flow:
+// Read() is called. The current step is 0. The first available read is
+// synchronous, so the call to Read() returns length. The current step is
+// now 1. Next, Read() is called again. The next available read can
+// not be completed until step 2, so Read() returns ERR_IO_PENDING. The current
+// step is still 1. Write is called(). The first available write is able to
+// complete in this step, but is marked asynchronous. Write() returns
+// ERR_IO_PENDING. The current step is still 1. At this point RunFor(1) is
+// called which will cause the write callback to be invoked, and will then
+// stop. The current state is now 2. RunFor(1) is called again, which
+// causes the read callback to be invoked, and will then stop. Then current
+// step is 2. Write() is called again. Then next available write is
+// synchronous so the call to Write() returns length.
+//
+// For examples of how to use this class, see:
+// deterministic_socket_data_unittests.cc
+class DeterministicSocketData
+ : public StaticSocketDataProvider {
+ public:
+ // The Delegate is an abstract interface which handles the communication from
+ // the DeterministicSocketData to the Deterministic MockSocket. The
+ // MockSockets directly store a pointer to the DeterministicSocketData,
+ // whereas the DeterministicSocketData only stores a pointer to the
+ // abstract Delegate interface.
+ class Delegate {
+ public:
+ // Returns true if there is currently a write pending. That is to say, if
+ // an asynchronous write has been started but the callback has not been
+ // invoked.
+ virtual bool WritePending() const = 0;
+ // Returns true if there is currently a read pending. That is to say, if
+ // an asynchronous read has been started but the callback has not been
+ // invoked.
+ virtual bool ReadPending() const = 0;
+ // Called to complete an asynchronous write to execute the write callback.
+ virtual void CompleteWrite() = 0;
+ // Called to complete an asynchronous read to execute the read callback.
+ virtual int CompleteRead() = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // |reads| the list of MockRead completions.
+ // |writes| the list of MockWrite completions.
+ DeterministicSocketData(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+ virtual ~DeterministicSocketData();
+
+ // Consume all the data up to the give stop point (via SetStop()).
+ void Run();
+
+ // Set the stop point to be |steps| from now, and then invoke Run().
+ void RunFor(int steps);
+
+ // Stop at step |seq|, which must be in the future.
+ virtual void SetStop(int seq);
+
+ // Stop |seq| steps after the current step.
+ virtual void StopAfter(int seq);
+ bool stopped() const { return stopped_; }
+ void SetStopped(bool val) { stopped_ = val; }
+ MockRead& current_read() { return current_read_; }
+ MockWrite& current_write() { return current_write_; }
+ int sequence_number() const { return sequence_number_; }
+ void set_delegate(base::WeakPtr<Delegate> delegate) {
+ delegate_ = delegate;
+ }
+
+ // StaticSocketDataProvider:
+
+ // When the socket calls Read(), that calls GetNextRead(), and expects either
+ // ERR_IO_PENDING or data.
+ virtual MockRead GetNextRead() OVERRIDE;
+
+ // When the socket calls Write(), it always completes synchronously. OnWrite()
+ // checks to make sure the written data matches the expected data. The
+ // callback will not be invoked until its sequence number is reached.
+ virtual MockWriteResult OnWrite(const std::string& data) OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void CompleteRead() OVERRIDE {}
+
+ private:
+ // Invoke the read and write callbacks, if the timing is appropriate.
+ void InvokeCallbacks();
+
+ void NextStep();
+
+ void VerifyCorrectSequenceNumbers(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count);
+
+ int sequence_number_;
+ MockRead current_read_;
+ MockWrite current_write_;
+ int stopping_sequence_number_;
+ bool stopped_;
+ base::WeakPtr<Delegate> delegate_;
+ bool print_debug_;
+ bool is_running_;
+};
+
+// Holds an array of SocketDataProvider elements. As Mock{TCP,SSL}StreamSocket
+// objects get instantiated, they take their data from the i'th element of this
+// array.
+template<typename T>
+class SocketDataProviderArray {
+ public:
+ SocketDataProviderArray() : next_index_(0) {}
+
+ T* GetNext() {
+ DCHECK_LT(next_index_, data_providers_.size());
+ return data_providers_[next_index_++];
+ }
+
+ void Add(T* data_provider) {
+ DCHECK(data_provider);
+ data_providers_.push_back(data_provider);
+ }
+
+ size_t next_index() { return next_index_; }
+
+ void ResetNextIndex() {
+ next_index_ = 0;
+ }
+
+ private:
+ // Index of the next |data_providers_| element to use. Not an iterator
+ // because those are invalidated on vector reallocation.
+ size_t next_index_;
+
+ // SocketDataProviders to be returned.
+ std::vector<T*> data_providers_;
+};
+
+class MockUDPClientSocket;
+class MockTCPClientSocket;
+class MockSSLClientSocket;
+
+// ClientSocketFactory which contains arrays of sockets of each type.
+// You should first fill the arrays using AddMock{SSL,}Socket. When the factory
+// is asked to create a socket, it takes next entry from appropriate array.
+// You can use ResetNextMockIndexes to reset that next entry index for all mock
+// socket types.
+class MockClientSocketFactory : public ClientSocketFactory {
+ public:
+ MockClientSocketFactory();
+ virtual ~MockClientSocketFactory();
+
+ void AddSocketDataProvider(SocketDataProvider* socket);
+ void AddSSLSocketDataProvider(SSLSocketDataProvider* socket);
+ void ResetNextMockIndexes();
+
+ SocketDataProviderArray<SocketDataProvider>& mock_data() {
+ return mock_data_;
+ }
+
+ // ClientSocketFactory
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE;
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE;
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE;
+ virtual void ClearSSLSessionCache() OVERRIDE;
+
+ private:
+ SocketDataProviderArray<SocketDataProvider> mock_data_;
+ SocketDataProviderArray<SSLSocketDataProvider> mock_ssl_data_;
+};
+
+class MockClientSocket : public SSLClientSocket {
+ public:
+ // Value returned by GetTLSUniqueChannelBinding().
+ static const char kTlsUnique[];
+
+ // The BoundNetLog is needed to test LoadTimingInfo, which uses NetLog IDs as
+ // unique socket IDs.
+ explicit MockClientSocket(const BoundNetLog& net_log);
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) = 0;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) = 0;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+
+ // SSLClientSocket implementation.
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual int ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) OVERRIDE;
+ virtual int GetTLSUniqueChannelBinding(std::string* out) OVERRIDE;
+ virtual NextProtoStatus GetNextProto(std::string* proto,
+ std::string* server_protos) OVERRIDE;
+ virtual ServerBoundCertService* GetServerBoundCertService() const OVERRIDE;
+
+ protected:
+ virtual ~MockClientSocket();
+ void RunCallbackAsync(const CompletionCallback& callback, int result);
+ void RunCallback(const CompletionCallback& callback, int result);
+
+ base::WeakPtrFactory<MockClientSocket> weak_factory_;
+
+ // True if Connect completed successfully and Disconnect hasn't been called.
+ bool connected_;
+
+ // Address of the "remote" peer we're connected to.
+ IPEndPoint peer_addr_;
+
+ BoundNetLog net_log_;
+};
+
+class MockTCPClientSocket : public MockClientSocket, public AsyncSocket {
+ public:
+ MockTCPClientSocket(const AddressList& addresses, net::NetLog* net_log,
+ SocketDataProvider* socket);
+ virtual ~MockTCPClientSocket();
+
+ const AddressList& addresses() const { return addresses_; }
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // AsyncSocket:
+ virtual void OnReadComplete(const MockRead& data) OVERRIDE;
+ virtual void OnConnectComplete(const MockConnect& data) OVERRIDE;
+
+ private:
+ int CompleteRead();
+
+ AddressList addresses_;
+
+ SocketDataProvider* data_;
+ int read_offset_;
+ MockRead read_data_;
+ bool need_read_data_;
+
+ // True if the peer has closed the connection. This allows us to simulate
+ // the recv(..., MSG_PEEK) call in the IsConnectedAndIdle method of the real
+ // TCPClientSocket.
+ bool peer_closed_connection_;
+
+ // While an asynchronous IO is pending, we save our user-buffer state.
+ IOBuffer* pending_buf_;
+ int pending_buf_len_;
+ CompletionCallback pending_callback_;
+ bool was_used_to_convey_data_;
+};
+
+// DeterministicSocketHelper is a helper class that can be used
+// to simulate net::Socket::Read() and net::Socket::Write()
+// using deterministic |data|.
+// Note: This is provided as a common helper class because
+// of the inheritance hierarchy of DeterministicMock[UDP,TCP]ClientSocket and a
+// desire not to introduce an additional common base class.
+class DeterministicSocketHelper {
+ public:
+ DeterministicSocketHelper(net::NetLog* net_log,
+ DeterministicSocketData* data);
+ virtual ~DeterministicSocketHelper();
+
+ bool write_pending() const { return write_pending_; }
+ bool read_pending() const { return read_pending_; }
+
+ void CompleteWrite();
+ int CompleteRead();
+
+ int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ bool was_used_to_convey_data() const { return was_used_to_convey_data_; }
+
+ bool peer_closed_connection() const { return peer_closed_connection_; }
+
+ DeterministicSocketData* data() const { return data_; }
+
+ private:
+ bool write_pending_;
+ CompletionCallback write_callback_;
+ int write_result_;
+
+ MockRead read_data_;
+
+ IOBuffer* read_buf_;
+ int read_buf_len_;
+ bool read_pending_;
+ CompletionCallback read_callback_;
+ DeterministicSocketData* data_;
+ bool was_used_to_convey_data_;
+ bool peer_closed_connection_;
+ BoundNetLog net_log_;
+};
+
+// Mock UDP socket to be used in conjunction with DeterministicSocketData.
+class DeterministicMockUDPClientSocket
+ : public DatagramClientSocket,
+ public AsyncSocket,
+ public DeterministicSocketData::Delegate,
+ public base::SupportsWeakPtr<DeterministicMockUDPClientSocket> {
+ public:
+ DeterministicMockUDPClientSocket(net::NetLog* net_log,
+ DeterministicSocketData* data);
+ virtual ~DeterministicMockUDPClientSocket();
+
+ // DeterministicSocketData::Delegate:
+ virtual bool WritePending() const OVERRIDE;
+ virtual bool ReadPending() const OVERRIDE;
+ virtual void CompleteWrite() OVERRIDE;
+ virtual int CompleteRead() OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ // DatagramSocket implementation.
+ virtual void Close() OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+
+ // DatagramClientSocket implementation.
+ virtual int Connect(const IPEndPoint& address) OVERRIDE;
+
+ // AsyncSocket implementation.
+ virtual void OnReadComplete(const MockRead& data) OVERRIDE;
+ virtual void OnConnectComplete(const MockConnect& data) OVERRIDE;
+
+ private:
+ bool connected_;
+ IPEndPoint peer_address_;
+ DeterministicSocketHelper helper_;
+};
+
+// Mock TCP socket to be used in conjunction with DeterministicSocketData.
+class DeterministicMockTCPClientSocket
+ : public MockClientSocket,
+ public AsyncSocket,
+ public DeterministicSocketData::Delegate,
+ public base::SupportsWeakPtr<DeterministicMockTCPClientSocket> {
+ public:
+ DeterministicMockTCPClientSocket(net::NetLog* net_log,
+ DeterministicSocketData* data);
+ virtual ~DeterministicMockTCPClientSocket();
+
+ // DeterministicSocketData::Delegate:
+ virtual bool WritePending() const OVERRIDE;
+ virtual bool ReadPending() const OVERRIDE;
+ virtual void CompleteWrite() OVERRIDE;
+ virtual int CompleteRead() OVERRIDE;
+
+ // Socket:
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // StreamSocket:
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // AsyncSocket:
+ virtual void OnReadComplete(const MockRead& data) OVERRIDE;
+ virtual void OnConnectComplete(const MockConnect& data) OVERRIDE;
+
+ private:
+ DeterministicSocketHelper helper_;
+};
+
+class MockSSLClientSocket : public MockClientSocket, public AsyncSocket {
+ public:
+ MockSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ SSLSocketDataProvider* socket);
+ virtual ~MockSSLClientSocket();
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // SSLClientSocket implementation.
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual NextProtoStatus GetNextProto(std::string* proto,
+ std::string* server_protos) OVERRIDE;
+ virtual bool set_was_npn_negotiated(bool negotiated) OVERRIDE;
+ virtual void set_protocol_negotiated(
+ NextProto protocol_negotiated) OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+
+ // This MockSocket does not implement the manual async IO feature.
+ virtual void OnReadComplete(const MockRead& data) OVERRIDE;
+ virtual void OnConnectComplete(const MockConnect& data) OVERRIDE;
+
+ virtual bool WasChannelIDSent() const OVERRIDE;
+ virtual void set_channel_id_sent(bool channel_id_sent) OVERRIDE;
+ virtual ServerBoundCertService* GetServerBoundCertService() const OVERRIDE;
+
+ private:
+ static void ConnectCallback(MockSSLClientSocket *ssl_client_socket,
+ const CompletionCallback& callback,
+ int rv);
+
+ scoped_ptr<ClientSocketHandle> transport_;
+ SSLSocketDataProvider* data_;
+ bool is_npn_state_set_;
+ bool new_npn_value_;
+ bool is_protocol_negotiated_set_;
+ NextProto protocol_negotiated_;
+};
+
+class MockUDPClientSocket
+ : public DatagramClientSocket,
+ public AsyncSocket {
+ public:
+ MockUDPClientSocket(SocketDataProvider* data, net::NetLog* net_log);
+ virtual ~MockUDPClientSocket();
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ // DatagramSocket implementation.
+ virtual void Close() OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+
+ // DatagramClientSocket implementation.
+ virtual int Connect(const IPEndPoint& address) OVERRIDE;
+
+ // AsyncSocket implementation.
+ virtual void OnReadComplete(const MockRead& data) OVERRIDE;
+ virtual void OnConnectComplete(const MockConnect& data) OVERRIDE;
+
+ private:
+ int CompleteRead();
+
+ void RunCallbackAsync(const CompletionCallback& callback, int result);
+ void RunCallback(const CompletionCallback& callback, int result);
+
+ bool connected_;
+ SocketDataProvider* data_;
+ int read_offset_;
+ MockRead read_data_;
+ bool need_read_data_;
+
+ // Address of the "remote" peer we're connected to.
+ IPEndPoint peer_addr_;
+
+ // While an asynchronous IO is pending, we save our user-buffer state.
+ IOBuffer* pending_buf_;
+ int pending_buf_len_;
+ CompletionCallback pending_callback_;
+
+ BoundNetLog net_log_;
+
+ base::WeakPtrFactory<MockUDPClientSocket> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockUDPClientSocket);
+};
+
+class TestSocketRequest : public TestCompletionCallbackBase {
+ public:
+ TestSocketRequest(std::vector<TestSocketRequest*>* request_order,
+ size_t* completion_count);
+ virtual ~TestSocketRequest();
+
+ ClientSocketHandle* handle() { return &handle_; }
+
+ const net::CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result);
+
+ ClientSocketHandle handle_;
+ std::vector<TestSocketRequest*>* request_order_;
+ size_t* completion_count_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestSocketRequest);
+};
+
+class ClientSocketPoolTest {
+ public:
+ enum KeepAlive {
+ KEEP_ALIVE,
+
+ // A socket will be disconnected in addition to handle being reset.
+ NO_KEEP_ALIVE,
+ };
+
+ static const int kIndexOutOfBounds;
+ static const int kRequestNotFound;
+
+ ClientSocketPoolTest();
+ ~ClientSocketPoolTest();
+
+ template <typename PoolType, typename SocketParams>
+ int StartRequestUsingPool(PoolType* socket_pool,
+ const std::string& group_name,
+ RequestPriority priority,
+ const scoped_refptr<SocketParams>& socket_params) {
+ DCHECK(socket_pool);
+ TestSocketRequest* request = new TestSocketRequest(&request_order_,
+ &completion_count_);
+ requests_.push_back(request);
+ int rv = request->handle()->Init(
+ group_name, socket_params, priority, request->callback(),
+ socket_pool, BoundNetLog());
+ if (rv != ERR_IO_PENDING)
+ request_order_.push_back(request);
+ return rv;
+ }
+
+ // Provided there were n requests started, takes |index| in range 1..n
+ // and returns order in which that request completed, in range 1..n,
+ // or kIndexOutOfBounds if |index| is out of bounds, or kRequestNotFound
+ // if that request did not complete (for example was canceled).
+ int GetOrderOfRequest(size_t index) const;
+
+ // Resets first initialized socket handle from |requests_|. If found such
+ // a handle, returns true.
+ bool ReleaseOneConnection(KeepAlive keep_alive);
+
+ // Releases connections until there is nothing to release.
+ void ReleaseAllConnections(KeepAlive keep_alive);
+
+ // Note that this uses 0-based indices, while GetOrderOfRequest takes and
+ // returns 0-based indices.
+ TestSocketRequest* request(int i) { return requests_[i]; }
+
+ size_t requests_size() const { return requests_.size(); }
+ ScopedVector<TestSocketRequest>* requests() { return &requests_; }
+ size_t completion_count() const { return completion_count_; }
+
+ private:
+ ScopedVector<TestSocketRequest> requests_;
+ std::vector<TestSocketRequest*> request_order_;
+ size_t completion_count_;
+};
+
+class MockTransportClientSocketPool : public TransportClientSocketPool {
+ public:
+ class MockConnectJob {
+ public:
+ MockConnectJob(scoped_ptr<StreamSocket> socket, ClientSocketHandle* handle,
+ const CompletionCallback& callback);
+ ~MockConnectJob();
+
+ int Connect();
+ bool CancelHandle(const ClientSocketHandle* handle);
+
+ private:
+ void OnConnect(int rv);
+
+ scoped_ptr<StreamSocket> socket_;
+ ClientSocketHandle* handle_;
+ CompletionCallback user_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnectJob);
+ };
+
+ MockTransportClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ ClientSocketFactory* socket_factory);
+
+ virtual ~MockTransportClientSocketPool();
+
+ int release_count() const { return release_count_; }
+ int cancel_count() const { return cancel_count_; }
+
+ // TransportClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+
+ private:
+ ClientSocketFactory* client_socket_factory_;
+ ScopedVector<MockConnectJob> job_list_;
+ int release_count_;
+ int cancel_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTransportClientSocketPool);
+};
+
+class DeterministicMockClientSocketFactory : public ClientSocketFactory {
+ public:
+ DeterministicMockClientSocketFactory();
+ virtual ~DeterministicMockClientSocketFactory();
+
+ void AddSocketDataProvider(DeterministicSocketData* socket);
+ void AddSSLSocketDataProvider(SSLSocketDataProvider* socket);
+ void ResetNextMockIndexes();
+
+ // Return |index|-th MockSSLClientSocket (starting from 0) that the factory
+ // created.
+ MockSSLClientSocket* GetMockSSLClientSocket(size_t index) const;
+
+ SocketDataProviderArray<DeterministicSocketData>& mock_data() {
+ return mock_data_;
+ }
+ std::vector<DeterministicMockTCPClientSocket*>& tcp_client_sockets() {
+ return tcp_client_sockets_;
+ }
+ std::vector<DeterministicMockUDPClientSocket*>& udp_client_sockets() {
+ return udp_client_sockets_;
+ }
+
+ // ClientSocketFactory
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE;
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE;
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE;
+ virtual void ClearSSLSessionCache() OVERRIDE;
+
+ private:
+ SocketDataProviderArray<DeterministicSocketData> mock_data_;
+ SocketDataProviderArray<SSLSocketDataProvider> mock_ssl_data_;
+
+ // Store pointers to handed out sockets in case the test wants to get them.
+ std::vector<DeterministicMockTCPClientSocket*> tcp_client_sockets_;
+ std::vector<DeterministicMockUDPClientSocket*> udp_client_sockets_;
+ std::vector<MockSSLClientSocket*> ssl_client_sockets_;
+};
+
+class MockSOCKSClientSocketPool : public SOCKSClientSocketPool {
+ public:
+ MockSOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ TransportClientSocketPool* transport_pool);
+
+ virtual ~MockSOCKSClientSocketPool();
+
+ // SOCKSClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+
+ private:
+ TransportClientSocketPool* const transport_pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockSOCKSClientSocketPool);
+};
+
+// Constants for a successful SOCKS v5 handshake.
+extern const char kSOCKS5GreetRequest[];
+extern const int kSOCKS5GreetRequestLength;
+
+extern const char kSOCKS5GreetResponse[];
+extern const int kSOCKS5GreetResponseLength;
+
+extern const char kSOCKS5OkRequest[];
+extern const int kSOCKS5OkRequestLength;
+
+extern const char kSOCKS5OkResponse[];
+extern const int kSOCKS5OkResponseLength;
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKET_TEST_UTIL_H_
diff --git a/chromium/net/socket/socks5_client_socket.cc b/chromium/net/socket/socks5_client_socket.cc
new file mode 100644
index 00000000000..537b584a932
--- /dev/null
+++ b/chromium/net/socket/socks5_client_socket.cc
@@ -0,0 +1,487 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks5_client_socket.h"
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/debug/trace_event.h"
+#include "base/format_macros.h"
+#include "base/strings/string_util.h"
+#include "base/sys_byteorder.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+const unsigned int SOCKS5ClientSocket::kGreetReadHeaderSize = 2;
+const unsigned int SOCKS5ClientSocket::kWriteHeaderSize = 10;
+const unsigned int SOCKS5ClientSocket::kReadHeaderSize = 5;
+const uint8 SOCKS5ClientSocket::kSOCKS5Version = 0x05;
+const uint8 SOCKS5ClientSocket::kTunnelCommand = 0x01;
+const uint8 SOCKS5ClientSocket::kNullByte = 0x00;
+
+COMPILE_ASSERT(sizeof(struct in_addr) == 4, incorrect_system_size_of_IPv4);
+COMPILE_ASSERT(sizeof(struct in6_addr) == 16, incorrect_system_size_of_IPv6);
+
+SOCKS5ClientSocket::SOCKS5ClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostResolver::RequestInfo& req_info)
+ : io_callback_(base::Bind(&SOCKS5ClientSocket::OnIOComplete,
+ base::Unretained(this))),
+ transport_(transport_socket.Pass()),
+ next_state_(STATE_NONE),
+ completed_handshake_(false),
+ bytes_sent_(0),
+ bytes_received_(0),
+ read_header_size(kReadHeaderSize),
+ host_request_info_(req_info),
+ net_log_(transport_->socket()->NetLog()) {
+}
+
+SOCKS5ClientSocket::~SOCKS5ClientSocket() {
+ Disconnect();
+}
+
+int SOCKS5ClientSocket::Connect(const CompletionCallback& callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->socket());
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ // If already connected, then just return OK.
+ if (completed_handshake_)
+ return OK;
+
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_CONNECT);
+
+ next_state_ = STATE_GREET_WRITE;
+ buffer_.clear();
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_callback_ = callback;
+ } else {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SOCKS5_CONNECT, rv);
+ }
+ return rv;
+}
+
+void SOCKS5ClientSocket::Disconnect() {
+ completed_handshake_ = false;
+ transport_->socket()->Disconnect();
+
+ // Reset other states to make sure they aren't mistakenly used later.
+ // These are the states initialized by Connect().
+ next_state_ = STATE_NONE;
+ user_callback_.Reset();
+}
+
+bool SOCKS5ClientSocket::IsConnected() const {
+ return completed_handshake_ && transport_->socket()->IsConnected();
+}
+
+bool SOCKS5ClientSocket::IsConnectedAndIdle() const {
+ return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+}
+
+const BoundNetLog& SOCKS5ClientSocket::NetLog() const {
+ return net_log_;
+}
+
+void SOCKS5ClientSocket::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void SOCKS5ClientSocket::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SOCKS5ClientSocket::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SOCKS5ClientSocket::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SOCKS5ClientSocket::WasNpnNegotiated() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasNpnNegotiated();
+ }
+ NOTREACHED();
+ return false;
+}
+
+NextProto SOCKS5ClientSocket::GetNegotiatedProtocol() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetNegotiatedProtocol();
+ }
+ NOTREACHED();
+ return kProtoUnknown;
+}
+
+bool SOCKS5ClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetSSLInfo(ssl_info);
+ }
+ NOTREACHED();
+ return false;
+
+}
+
+// Read is called by the transport layer above to read. This can only be done
+// if the SOCKS handshake is complete.
+int SOCKS5ClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+// Write is called by the transport layer. This can only be done if the
+// SOCKS handshake is complete.
+int SOCKS5ClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+bool SOCKS5ClientSocket::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool SOCKS5ClientSocket::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+void SOCKS5ClientSocket::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(!user_callback_.is_null());
+
+ // Since Run() may result in Read being called,
+ // clear user_callback_ up front.
+ CompletionCallback c = user_callback_;
+ user_callback_.Reset();
+ c.Run(result);
+}
+
+void SOCKS5ClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEvent(NetLog::TYPE_SOCKS5_CONNECT);
+ DoCallback(rv);
+ }
+}
+
+int SOCKS5ClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GREET_WRITE:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_GREET_WRITE);
+ rv = DoGreetWrite();
+ break;
+ case STATE_GREET_WRITE_COMPLETE:
+ rv = DoGreetWriteComplete(rv);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SOCKS5_GREET_WRITE, rv);
+ break;
+ case STATE_GREET_READ:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_GREET_READ);
+ rv = DoGreetRead();
+ break;
+ case STATE_GREET_READ_COMPLETE:
+ rv = DoGreetReadComplete(rv);
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SOCKS5_GREET_READ, rv);
+ break;
+ case STATE_HANDSHAKE_WRITE:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_WRITE);
+ rv = DoHandshakeWrite();
+ break;
+ case STATE_HANDSHAKE_WRITE_COMPLETE:
+ rv = DoHandshakeWriteComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_SOCKS5_HANDSHAKE_WRITE, rv);
+ break;
+ case STATE_HANDSHAKE_READ:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS5_HANDSHAKE_READ);
+ rv = DoHandshakeRead();
+ break;
+ case STATE_HANDSHAKE_READ_COMPLETE:
+ rv = DoHandshakeReadComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_SOCKS5_HANDSHAKE_READ, rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+const char kSOCKS5GreetWriteData[] = { 0x05, 0x01, 0x00 }; // no authentication
+const char kSOCKS5GreetReadData[] = { 0x05, 0x00 };
+
+int SOCKS5ClientSocket::DoGreetWrite() {
+ // Since we only have 1 byte to send the hostname length in, if the
+ // URL has a hostname longer than 255 characters we can't send it.
+ if (0xFF < host_request_info_.hostname().size()) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_HOSTNAME_TOO_BIG);
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ if (buffer_.empty()) {
+ buffer_ = std::string(kSOCKS5GreetWriteData,
+ arraysize(kSOCKS5GreetWriteData));
+ bytes_sent_ = 0;
+ }
+
+ next_state_ = STATE_GREET_WRITE_COMPLETE;
+ size_t handshake_buf_len = buffer_.size() - bytes_sent_;
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
+ handshake_buf_len);
+ return transport_->socket()
+ ->Write(handshake_buf_.get(), handshake_buf_len, io_callback_);
+}
+
+int SOCKS5ClientSocket::DoGreetWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ bytes_sent_ += result;
+ if (bytes_sent_ == buffer_.size()) {
+ buffer_.clear();
+ bytes_received_ = 0;
+ next_state_ = STATE_GREET_READ;
+ } else {
+ next_state_ = STATE_GREET_WRITE;
+ }
+ return OK;
+}
+
+int SOCKS5ClientSocket::DoGreetRead() {
+ next_state_ = STATE_GREET_READ_COMPLETE;
+ size_t handshake_buf_len = kGreetReadHeaderSize - bytes_received_;
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ return transport_->socket()
+ ->Read(handshake_buf_.get(), handshake_buf_len, io_callback_);
+}
+
+int SOCKS5ClientSocket::DoGreetReadComplete(int result) {
+ if (result < 0)
+ return result;
+
+ if (result == 0) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING);
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ bytes_received_ += result;
+ buffer_.append(handshake_buf_->data(), result);
+ if (bytes_received_ < kGreetReadHeaderSize) {
+ next_state_ = STATE_GREET_READ;
+ return OK;
+ }
+
+ // Got the greet data.
+ if (buffer_[0] != kSOCKS5Version) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_VERSION,
+ NetLog::IntegerCallback("version", buffer_[0]));
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+ if (buffer_[1] != 0x00) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_AUTH,
+ NetLog::IntegerCallback("method", buffer_[1]));
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ buffer_.clear();
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ return OK;
+}
+
+int SOCKS5ClientSocket::BuildHandshakeWriteBuffer(std::string* handshake)
+ const {
+ DCHECK(handshake->empty());
+
+ handshake->push_back(kSOCKS5Version);
+ handshake->push_back(kTunnelCommand); // Connect command
+ handshake->push_back(kNullByte); // Reserved null
+
+ handshake->push_back(kEndPointDomain); // The type of the address.
+
+ DCHECK_GE(static_cast<size_t>(0xFF), host_request_info_.hostname().size());
+
+ // First add the size of the hostname, followed by the hostname.
+ handshake->push_back(static_cast<unsigned char>(
+ host_request_info_.hostname().size()));
+ handshake->append(host_request_info_.hostname());
+
+ uint16 nw_port = base::HostToNet16(host_request_info_.port());
+ handshake->append(reinterpret_cast<char*>(&nw_port), sizeof(nw_port));
+ return OK;
+}
+
+// Writes the SOCKS handshake data to the underlying socket connection.
+int SOCKS5ClientSocket::DoHandshakeWrite() {
+ next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
+
+ if (buffer_.empty()) {
+ int rv = BuildHandshakeWriteBuffer(&buffer_);
+ if (rv != OK)
+ return rv;
+ bytes_sent_ = 0;
+ }
+
+ int handshake_buf_len = buffer_.size() - bytes_sent_;
+ DCHECK_LT(0, handshake_buf_len);
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ memcpy(handshake_buf_->data(), &buffer_[bytes_sent_],
+ handshake_buf_len);
+ return transport_->socket()
+ ->Write(handshake_buf_.get(), handshake_buf_len, io_callback_);
+}
+
+int SOCKS5ClientSocket::DoHandshakeWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // We ignore the case when result is 0, since the underlying Write
+ // may return spurious writes while waiting on the socket.
+
+ bytes_sent_ += result;
+ if (bytes_sent_ == buffer_.size()) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ buffer_.clear();
+ } else if (bytes_sent_ < buffer_.size()) {
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ } else {
+ NOTREACHED();
+ }
+
+ return OK;
+}
+
+int SOCKS5ClientSocket::DoHandshakeRead() {
+ next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
+
+ if (buffer_.empty()) {
+ bytes_received_ = 0;
+ read_header_size = kReadHeaderSize;
+ }
+
+ int handshake_buf_len = read_header_size - bytes_received_;
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ return transport_->socket()
+ ->Read(handshake_buf_.get(), handshake_buf_len, io_callback_);
+}
+
+int SOCKS5ClientSocket::DoHandshakeReadComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // The underlying socket closed unexpectedly.
+ if (result == 0) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE);
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ buffer_.append(handshake_buf_->data(), result);
+ bytes_received_ += result;
+
+ // When the first few bytes are read, check how many more are required
+ // and accordingly increase them
+ if (bytes_received_ == kReadHeaderSize) {
+ if (buffer_[0] != kSOCKS5Version || buffer_[2] != kNullByte) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNEXPECTED_VERSION,
+ NetLog::IntegerCallback("version", buffer_[0]));
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+ if (buffer_[1] != 0x00) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_SERVER_ERROR,
+ NetLog::IntegerCallback("error_code", buffer_[1]));
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ // We check the type of IP/Domain the server returns and accordingly
+ // increase the size of the response. For domains, we need to read the
+ // size of the domain, so the initial request size is upto the domain
+ // size. Since for IPv4/IPv6 the size is fixed and hence no 'size' is
+ // read, we substract 1 byte from the additional request size.
+ SocksEndPointAddressType address_type =
+ static_cast<SocksEndPointAddressType>(buffer_[3]);
+ if (address_type == kEndPointDomain)
+ read_header_size += static_cast<uint8>(buffer_[4]);
+ else if (address_type == kEndPointResolvedIPv4)
+ read_header_size += sizeof(struct in_addr) - 1;
+ else if (address_type == kEndPointResolvedIPv6)
+ read_header_size += sizeof(struct in6_addr) - 1;
+ else {
+ net_log_.AddEvent(NetLog::TYPE_SOCKS_UNKNOWN_ADDRESS_TYPE,
+ NetLog::IntegerCallback("address_type", buffer_[3]));
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ read_header_size += 2; // for the port.
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+ }
+
+ // When the final bytes are read, setup handshake. We ignore the rest
+ // of the response since they represent the SOCKSv5 endpoint and have
+ // no use when doing a tunnel connection.
+ if (bytes_received_ == read_header_size) {
+ completed_handshake_ = true;
+ buffer_.clear();
+ next_state_ = STATE_NONE;
+ return OK;
+ }
+
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+}
+
+int SOCKS5ClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+int SOCKS5ClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetLocalAddress(address);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/socks5_client_socket.h b/chromium/net/socket/socks5_client_socket.h
new file mode 100644
index 00000000000..45216244f10
--- /dev/null
+++ b/chromium/net/socket/socks5_client_socket.h
@@ -0,0 +1,155 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SOCKS5_CLIENT_SOCKET_H_
+#define NET_SOCKET_SOCKS5_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/dns/host_resolver.h"
+#include "net/socket/stream_socket.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class BoundNetLog;
+
+// This StreamSocket is used to setup a SOCKSv5 handshake with a socks proxy.
+// Currently no SOCKSv5 authentication is supported.
+class NET_EXPORT_PRIVATE SOCKS5ClientSocket : public StreamSocket {
+ public:
+ // |req_info| contains the hostname and port to which the socket above will
+ // communicate to via the SOCKS layer.
+ //
+ // Although SOCKS 5 supports 3 different modes of addressing, we will
+ // always pass it a hostname. This means the DNS resolving is done
+ // proxy side.
+ SOCKS5ClientSocket(scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostResolver::RequestInfo& req_info);
+
+ // On destruction Disconnect() is called.
+ virtual ~SOCKS5ClientSocket();
+
+ // StreamSocket implementation.
+
+ // Does the SOCKS handshake and completes the protocol.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+
+ private:
+ enum State {
+ STATE_GREET_WRITE,
+ STATE_GREET_WRITE_COMPLETE,
+ STATE_GREET_READ,
+ STATE_GREET_READ_COMPLETE,
+ STATE_HANDSHAKE_WRITE,
+ STATE_HANDSHAKE_WRITE_COMPLETE,
+ STATE_HANDSHAKE_READ,
+ STATE_HANDSHAKE_READ_COMPLETE,
+ STATE_NONE,
+ };
+
+ // Addressing type that can be specified in requests or responses.
+ enum SocksEndPointAddressType {
+ kEndPointDomain = 0x03,
+ kEndPointResolvedIPv4 = 0x01,
+ kEndPointResolvedIPv6 = 0x04,
+ };
+
+ static const unsigned int kGreetReadHeaderSize;
+ static const unsigned int kWriteHeaderSize;
+ static const unsigned int kReadHeaderSize;
+ static const uint8 kSOCKS5Version;
+ static const uint8 kTunnelCommand;
+ static const uint8 kNullByte;
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoHandshakeRead();
+ int DoHandshakeReadComplete(int result);
+ int DoHandshakeWrite();
+ int DoHandshakeWriteComplete(int result);
+ int DoGreetRead();
+ int DoGreetReadComplete(int result);
+ int DoGreetWrite();
+ int DoGreetWriteComplete(int result);
+
+ // Writes the SOCKS handshake buffer into |handshake|
+ // and return OK on success.
+ int BuildHandshakeWriteBuffer(std::string* handshake) const;
+
+ CompletionCallback io_callback_;
+
+ // Stores the underlying socket.
+ scoped_ptr<ClientSocketHandle> transport_;
+
+ State next_state_;
+
+ // Stores the callback to the layer above, called on completing Connect().
+ CompletionCallback user_callback_;
+
+ // This IOBuffer is used by the class to read and write
+ // SOCKS handshake data. The length contains the expected size to
+ // read or write.
+ scoped_refptr<IOBuffer> handshake_buf_;
+
+ // While writing, this buffer stores the complete write handshake data.
+ // While reading, it stores the handshake information received so far.
+ std::string buffer_;
+
+ // This becomes true when the SOCKS handshake has completed and the
+ // overlying connection is free to communicate.
+ bool completed_handshake_;
+
+ // These contain the bytes sent / received by the SOCKS handshake.
+ size_t bytes_sent_;
+ size_t bytes_received_;
+
+ size_t read_header_size;
+
+ HostResolver::RequestInfo host_request_info_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKS5ClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKS5_CLIENT_SOCKET_H_
diff --git a/chromium/net/socket/socks5_client_socket_unittest.cc b/chromium/net/socket/socks5_client_socket_unittest.cc
new file mode 100644
index 00000000000..4c9240ff5c1
--- /dev/null
+++ b/chromium/net/socket/socks5_client_socket_unittest.cc
@@ -0,0 +1,375 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks5_client_socket.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+
+#include "base/sys_byteorder.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+namespace {
+
+// Base class to test SOCKS5ClientSocket
+class SOCKS5ClientSocketTest : public PlatformTest {
+ public:
+ SOCKS5ClientSocketTest();
+ // Create a SOCKSClientSocket on top of a MockSocket.
+ scoped_ptr<SOCKS5ClientSocket> BuildMockSocket(MockRead reads[],
+ size_t reads_count,
+ MockWrite writes[],
+ size_t writes_count,
+ const std::string& hostname,
+ int port,
+ NetLog* net_log);
+
+ virtual void SetUp();
+
+ protected:
+ const uint16 kNwPort;
+ CapturingNetLog net_log_;
+ scoped_ptr<SOCKS5ClientSocket> user_sock_;
+ AddressList address_list_;
+ // Filled in by BuildMockSocket() and owned by its return value
+ // (which |user_sock| is set to).
+ StreamSocket* tcp_sock_;
+ TestCompletionCallback callback_;
+ scoped_ptr<MockHostResolver> host_resolver_;
+ scoped_ptr<SocketDataProvider> data_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SOCKS5ClientSocketTest);
+};
+
+SOCKS5ClientSocketTest::SOCKS5ClientSocketTest()
+ : kNwPort(base::HostToNet16(80)),
+ host_resolver_(new MockHostResolver) {
+}
+
+// Set up platform before every test case
+void SOCKS5ClientSocketTest::SetUp() {
+ PlatformTest::SetUp();
+
+ // Resolve the "localhost" AddressList used by the TCP connection to connect.
+ HostResolver::RequestInfo info(HostPortPair("www.socks-proxy.com", 1080));
+ TestCompletionCallback callback;
+ int rv = host_resolver_->Resolve(info, &address_list_, callback.callback(),
+ NULL, BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ ASSERT_EQ(OK, rv);
+}
+
+scoped_ptr<SOCKS5ClientSocket> SOCKS5ClientSocketTest::BuildMockSocket(
+ MockRead reads[],
+ size_t reads_count,
+ MockWrite writes[],
+ size_t writes_count,
+ const std::string& hostname,
+ int port,
+ NetLog* net_log) {
+ TestCompletionCallback callback;
+ data_.reset(new StaticSocketDataProvider(reads, reads_count,
+ writes, writes_count));
+ tcp_sock_ = new MockTCPClientSocket(address_list_, net_log, data_.get());
+
+ int rv = tcp_sock_->Connect(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(tcp_sock_->IsConnected());
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ // |connection| takes ownership of |tcp_sock_|, but keep a
+ // non-owning pointer to it.
+ connection->SetSocket(scoped_ptr<StreamSocket>(tcp_sock_));
+ return scoped_ptr<SOCKS5ClientSocket>(new SOCKS5ClientSocket(
+ connection.Pass(),
+ HostResolver::RequestInfo(HostPortPair(hostname, port))));
+}
+
+// Tests a complete SOCKS5 handshake and the disconnection.
+TEST_F(SOCKS5ClientSocketTest, CompleteHandshake) {
+ const std::string payload_write = "random data";
+ const std::string payload_read = "moar random data";
+
+ const char kOkRequest[] = {
+ 0x05, // Version
+ 0x01, // Command (CONNECT)
+ 0x00, // Reserved.
+ 0x03, // Address type (DOMAINNAME).
+ 0x09, // Length of domain (9)
+ // Domain string:
+ 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't',
+ 0x00, 0x50, // 16-bit port (80)
+ };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(ASYNC, kOkRequest, arraysize(kOkRequest)),
+ MockWrite(ASYNC, payload_write.data(), payload_write.size()) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength),
+ MockRead(ASYNC, payload_read.data(), payload_read.size()) };
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ "localhost", 80, &net_log_);
+
+ // At this state the TCP connection is completed but not the SOCKS handshake.
+ EXPECT_TRUE(tcp_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnected());
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(user_sock_->IsConnected());
+
+ CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
+
+ rv = callback_.WaitForResult();
+
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
+
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(payload_write.size()));
+ memcpy(buffer->data(), payload_write.data(), payload_write.size());
+ rv = user_sock_->Write(
+ buffer.get(), payload_write.size(), callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(static_cast<int>(payload_write.size()), rv);
+
+ buffer = new IOBuffer(payload_read.size());
+ rv =
+ user_sock_->Read(buffer.get(), payload_read.size(), callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(static_cast<int>(payload_read.size()), rv);
+ EXPECT_EQ(payload_read, std::string(buffer->data(), payload_read.size()));
+
+ user_sock_->Disconnect();
+ EXPECT_FALSE(tcp_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnected());
+}
+
+// Test that you can call Connect() again after having called Disconnect().
+TEST_F(SOCKS5ClientSocketTest, ConnectAndDisconnectTwice) {
+ const std::string hostname = "my-host-name";
+ const char kSOCKS5DomainRequest[] = {
+ 0x05, // VER
+ 0x01, // CMD
+ 0x00, // RSV
+ 0x03, // ATYPE
+ };
+
+ std::string request(kSOCKS5DomainRequest, arraysize(kSOCKS5DomainRequest));
+ request.push_back(hostname.size());
+ request.append(hostname);
+ request.append(reinterpret_cast<const char*>(&kNwPort), sizeof(kNwPort));
+
+ for (int i = 0; i < 2; ++i) {
+ MockWrite data_writes[] = {
+ MockWrite(SYNCHRONOUS, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(SYNCHRONOUS, request.data(), request.size())
+ };
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(SYNCHRONOUS, kSOCKS5OkResponse, kSOCKS5OkResponseLength)
+ };
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, NULL);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+
+ user_sock_->Disconnect();
+ EXPECT_FALSE(user_sock_->IsConnected());
+ }
+}
+
+// Test that we fail trying to connect to a hosname longer than 255 bytes.
+TEST_F(SOCKS5ClientSocketTest, LargeHostNameFails) {
+ // Create a string of length 256, where each character is 'x'.
+ std::string large_host_name;
+ std::fill_n(std::back_inserter(large_host_name), 256, 'x');
+
+ // Create a SOCKS socket, with mock transport socket.
+ MockWrite data_writes[] = {MockWrite()};
+ MockRead data_reads[] = {MockRead()};
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ large_host_name, 80, NULL);
+
+ // Try to connect -- should fail (without having read/written anything to
+ // the transport socket first) because the hostname is too long.
+ TestCompletionCallback callback;
+ int rv = user_sock_->Connect(callback.callback());
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, rv);
+}
+
+TEST_F(SOCKS5ClientSocketTest, PartialReadWrites) {
+ const std::string hostname = "www.google.com";
+
+ const char kOkRequest[] = {
+ 0x05, // Version
+ 0x01, // Command (CONNECT)
+ 0x00, // Reserved.
+ 0x03, // Address type (DOMAINNAME).
+ 0x0E, // Length of domain (14)
+ // Domain string:
+ 'w', 'w', 'w', '.', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',
+ 0x00, 0x50, // 16-bit port (80)
+ };
+
+ // Test for partial greet request write
+ {
+ const char partial1[] = { 0x05, 0x01 };
+ const char partial2[] = { 0x00 };
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, arraysize(partial1)),
+ MockWrite(ASYNC, partial2, arraysize(partial2)),
+ MockWrite(ASYNC, kOkRequest, arraysize(kOkRequest)) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_);
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ }
+
+ // Test for partial greet response read
+ {
+ const char partial1[] = { 0x05 };
+ const char partial2[] = { 0x00 };
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(ASYNC, kOkRequest, arraysize(kOkRequest)) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, partial1, arraysize(partial1)),
+ MockRead(ASYNC, partial2, arraysize(partial2)),
+ MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_);
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ }
+
+ // Test for partial handshake request write.
+ {
+ const int kSplitPoint = 3; // Break handshake write into two parts.
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(ASYNC, kOkRequest, kSplitPoint),
+ MockWrite(ASYNC, kOkRequest + kSplitPoint,
+ arraysize(kOkRequest) - kSplitPoint)
+ };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(ASYNC, kSOCKS5OkResponse, kSOCKS5OkResponseLength) };
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_);
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ }
+
+ // Test for partial handshake response read
+ {
+ const int kSplitPoint = 6; // Break the handshake read into two parts.
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKS5GreetRequest, kSOCKS5GreetRequestLength),
+ MockWrite(ASYNC, kOkRequest, arraysize(kOkRequest))
+ };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKS5GreetResponse, kSOCKS5GreetResponseLength),
+ MockRead(ASYNC, kSOCKS5OkResponse, kSplitPoint),
+ MockRead(ASYNC, kSOCKS5OkResponse + kSplitPoint,
+ kSOCKS5OkResponseLength - kSplitPoint)
+ };
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hostname, 80, &net_log_);
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsBeginEvent(net_log_entries, 0,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(LogContainsEndEvent(net_log_entries, -1,
+ NetLog::TYPE_SOCKS5_CONNECT));
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/socks_client_socket.cc b/chromium/net/socket/socks_client_socket.cc
new file mode 100644
index 00000000000..1941fdbfd95
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket.cc
@@ -0,0 +1,432 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks_client_socket.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/sys_byteorder.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/socket/client_socket_handle.h"
+
+namespace net {
+
+// Every SOCKS server requests a user-id from the client. It is optional
+// and we send an empty string.
+static const char kEmptyUserId[] = "";
+
+// For SOCKS4, the client sends 8 bytes plus the size of the user-id.
+static const unsigned int kWriteHeaderSize = 8;
+
+// For SOCKS4 the server sends 8 bytes for acknowledgement.
+static const unsigned int kReadHeaderSize = 8;
+
+// Server Response codes for SOCKS.
+static const uint8 kServerResponseOk = 0x5A;
+static const uint8 kServerResponseRejected = 0x5B;
+static const uint8 kServerResponseNotReachable = 0x5C;
+static const uint8 kServerResponseMismatchedUserId = 0x5D;
+
+static const uint8 kSOCKSVersion4 = 0x04;
+static const uint8 kSOCKSStreamRequest = 0x01;
+
+// A struct holding the essential details of the SOCKS4 Server Request.
+// The port in the header is stored in network byte order.
+struct SOCKS4ServerRequest {
+ uint8 version;
+ uint8 command;
+ uint16 nw_port;
+ uint8 ip[4];
+};
+COMPILE_ASSERT(sizeof(SOCKS4ServerRequest) == kWriteHeaderSize,
+ socks4_server_request_struct_wrong_size);
+
+// A struct holding details of the SOCKS4 Server Response.
+struct SOCKS4ServerResponse {
+ uint8 reserved_null;
+ uint8 code;
+ uint16 port;
+ uint8 ip[4];
+};
+COMPILE_ASSERT(sizeof(SOCKS4ServerResponse) == kReadHeaderSize,
+ socks4_server_response_struct_wrong_size);
+
+SOCKSClientSocket::SOCKSClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver)
+ : transport_(transport_socket.Pass()),
+ next_state_(STATE_NONE),
+ completed_handshake_(false),
+ bytes_sent_(0),
+ bytes_received_(0),
+ host_resolver_(host_resolver),
+ host_request_info_(req_info),
+ net_log_(transport_->socket()->NetLog()) {
+}
+
+SOCKSClientSocket::~SOCKSClientSocket() {
+ Disconnect();
+}
+
+int SOCKSClientSocket::Connect(const CompletionCallback& callback) {
+ DCHECK(transport_.get());
+ DCHECK(transport_->socket());
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ // If already connected, then just return OK.
+ if (completed_handshake_)
+ return OK;
+
+ next_state_ = STATE_RESOLVE_HOST;
+
+ net_log_.BeginEvent(NetLog::TYPE_SOCKS_CONNECT);
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_callback_ = callback;
+ } else {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SOCKS_CONNECT, rv);
+ }
+ return rv;
+}
+
+void SOCKSClientSocket::Disconnect() {
+ completed_handshake_ = false;
+ host_resolver_.Cancel();
+ transport_->socket()->Disconnect();
+
+ // Reset other states to make sure they aren't mistakenly used later.
+ // These are the states initialized by Connect().
+ next_state_ = STATE_NONE;
+ user_callback_.Reset();
+}
+
+bool SOCKSClientSocket::IsConnected() const {
+ return completed_handshake_ && transport_->socket()->IsConnected();
+}
+
+bool SOCKSClientSocket::IsConnectedAndIdle() const {
+ return completed_handshake_ && transport_->socket()->IsConnectedAndIdle();
+}
+
+const BoundNetLog& SOCKSClientSocket::NetLog() const {
+ return net_log_;
+}
+
+void SOCKSClientSocket::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void SOCKSClientSocket::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SOCKSClientSocket::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SOCKSClientSocket::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SOCKSClientSocket::WasNpnNegotiated() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasNpnNegotiated();
+ }
+ NOTREACHED();
+ return false;
+}
+
+NextProto SOCKSClientSocket::GetNegotiatedProtocol() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetNegotiatedProtocol();
+ }
+ NOTREACHED();
+ return kProtoUnknown;
+}
+
+bool SOCKSClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->GetSSLInfo(ssl_info);
+ }
+ NOTREACHED();
+ return false;
+
+}
+
+// Read is called by the transport layer above to read. This can only be done
+// if the SOCKS handshake is complete.
+int SOCKSClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ return transport_->socket()->Read(buf, buf_len, callback);
+}
+
+// Write is called by the transport layer. This can only be done if the
+// SOCKS handshake is complete.
+int SOCKSClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(completed_handshake_);
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(user_callback_.is_null());
+
+ return transport_->socket()->Write(buf, buf_len, callback);
+}
+
+bool SOCKSClientSocket::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool SOCKSClientSocket::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+void SOCKSClientSocket::DoCallback(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(!user_callback_.is_null());
+
+ // Since Run() may result in Read being called,
+ // clear user_callback_ up front.
+ CompletionCallback c = user_callback_;
+ user_callback_.Reset();
+ DVLOG(1) << "Finished setting up SOCKS handshake";
+ c.Run(result);
+}
+
+void SOCKSClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_NONE, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SOCKS_CONNECT, rv);
+ DoCallback(rv);
+ }
+}
+
+int SOCKSClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_RESOLVE_HOST:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ rv = DoResolveHostComplete(rv);
+ break;
+ case STATE_HANDSHAKE_WRITE:
+ DCHECK_EQ(OK, rv);
+ rv = DoHandshakeWrite();
+ break;
+ case STATE_HANDSHAKE_WRITE_COMPLETE:
+ rv = DoHandshakeWriteComplete(rv);
+ break;
+ case STATE_HANDSHAKE_READ:
+ DCHECK_EQ(OK, rv);
+ rv = DoHandshakeRead();
+ break;
+ case STATE_HANDSHAKE_READ_COMPLETE:
+ rv = DoHandshakeReadComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+ return rv;
+}
+
+int SOCKSClientSocket::DoResolveHost() {
+ next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+ // SOCKS4 only supports IPv4 addresses, so only try getting the IPv4
+ // addresses for the target host.
+ host_request_info_.set_address_family(ADDRESS_FAMILY_IPV4);
+ return host_resolver_.Resolve(
+ host_request_info_, &addresses_,
+ base::Bind(&SOCKSClientSocket::OnIOComplete, base::Unretained(this)),
+ net_log_);
+}
+
+int SOCKSClientSocket::DoResolveHostComplete(int result) {
+ if (result != OK) {
+ // Resolving the hostname failed; fail the request rather than automatically
+ // falling back to SOCKS4a (since it can be confusing to see invalid IP
+ // addresses being sent to the SOCKS4 server when it doesn't support 4A.)
+ return result;
+ }
+
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ return OK;
+}
+
+// Builds the buffer that is to be sent to the server.
+const std::string SOCKSClientSocket::BuildHandshakeWriteBuffer() const {
+ SOCKS4ServerRequest request;
+ request.version = kSOCKSVersion4;
+ request.command = kSOCKSStreamRequest;
+ request.nw_port = base::HostToNet16(host_request_info_.port());
+
+ DCHECK(!addresses_.empty());
+ const IPEndPoint& endpoint = addresses_.front();
+
+ // We disabled IPv6 results when resolving the hostname, so none of the
+ // results in the list will be IPv6.
+ // TODO(eroman): we only ever use the first address in the list. It would be
+ // more robust to try all the IP addresses we have before
+ // failing the connect attempt.
+ CHECK_EQ(ADDRESS_FAMILY_IPV4, endpoint.GetFamily());
+ CHECK_LE(endpoint.address().size(), sizeof(request.ip));
+ memcpy(&request.ip, &endpoint.address()[0], endpoint.address().size());
+
+ DVLOG(1) << "Resolved Host is : " << endpoint.ToStringWithoutPort();
+
+ std::string handshake_data(reinterpret_cast<char*>(&request),
+ sizeof(request));
+ handshake_data.append(kEmptyUserId, arraysize(kEmptyUserId));
+
+ return handshake_data;
+}
+
+// Writes the SOCKS handshake data to the underlying socket connection.
+int SOCKSClientSocket::DoHandshakeWrite() {
+ next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
+
+ if (buffer_.empty()) {
+ buffer_ = BuildHandshakeWriteBuffer();
+ bytes_sent_ = 0;
+ }
+
+ int handshake_buf_len = buffer_.size() - bytes_sent_;
+ DCHECK_GT(handshake_buf_len, 0);
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ memcpy(handshake_buf_->data(), &buffer_[bytes_sent_],
+ handshake_buf_len);
+ return transport_->socket()->Write(
+ handshake_buf_.get(),
+ handshake_buf_len,
+ base::Bind(&SOCKSClientSocket::OnIOComplete, base::Unretained(this)));
+}
+
+int SOCKSClientSocket::DoHandshakeWriteComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // We ignore the case when result is 0, since the underlying Write
+ // may return spurious writes while waiting on the socket.
+
+ bytes_sent_ += result;
+ if (bytes_sent_ == buffer_.size()) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ buffer_.clear();
+ } else if (bytes_sent_ < buffer_.size()) {
+ next_state_ = STATE_HANDSHAKE_WRITE;
+ } else {
+ return ERR_UNEXPECTED;
+ }
+
+ return OK;
+}
+
+int SOCKSClientSocket::DoHandshakeRead() {
+ next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
+
+ if (buffer_.empty()) {
+ bytes_received_ = 0;
+ }
+
+ int handshake_buf_len = kReadHeaderSize - bytes_received_;
+ handshake_buf_ = new IOBuffer(handshake_buf_len);
+ return transport_->socket()->Read(
+ handshake_buf_.get(),
+ handshake_buf_len,
+ base::Bind(&SOCKSClientSocket::OnIOComplete, base::Unretained(this)));
+}
+
+int SOCKSClientSocket::DoHandshakeReadComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // The underlying socket closed unexpectedly.
+ if (result == 0)
+ return ERR_CONNECTION_CLOSED;
+
+ if (bytes_received_ + result > kReadHeaderSize) {
+ // TODO(eroman): Describe failure in NetLog.
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ buffer_.append(handshake_buf_->data(), result);
+ bytes_received_ += result;
+ if (bytes_received_ < kReadHeaderSize) {
+ next_state_ = STATE_HANDSHAKE_READ;
+ return OK;
+ }
+
+ const SOCKS4ServerResponse* response =
+ reinterpret_cast<const SOCKS4ServerResponse*>(buffer_.data());
+
+ if (response->reserved_null != 0x00) {
+ LOG(ERROR) << "Unknown response from SOCKS server.";
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ switch (response->code) {
+ case kServerResponseOk:
+ completed_handshake_ = true;
+ return OK;
+ case kServerResponseRejected:
+ LOG(ERROR) << "SOCKS request rejected or failed";
+ return ERR_SOCKS_CONNECTION_FAILED;
+ case kServerResponseNotReachable:
+ LOG(ERROR) << "SOCKS request failed because client is not running "
+ << "identd (or not reachable from the server)";
+ return ERR_SOCKS_CONNECTION_HOST_UNREACHABLE;
+ case kServerResponseMismatchedUserId:
+ LOG(ERROR) << "SOCKS request failed because client's identd could "
+ << "not confirm the user ID string in the request";
+ return ERR_SOCKS_CONNECTION_FAILED;
+ default:
+ LOG(ERROR) << "SOCKS server sent unknown response";
+ return ERR_SOCKS_CONNECTION_FAILED;
+ }
+
+ // Note: we ignore the last 6 bytes as specified by the SOCKS protocol
+}
+
+int SOCKSClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+int SOCKSClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetLocalAddress(address);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/socks_client_socket.h b/chromium/net/socket/socks_client_socket.h
new file mode 100644
index 00000000000..285c75ec295
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket.h
@@ -0,0 +1,134 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SOCKS_CLIENT_SOCKET_H_
+#define NET_SOCKET_SOCKS_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class BoundNetLog;
+
+// The SOCKS client socket implementation
+class NET_EXPORT_PRIVATE SOCKSClientSocket : public StreamSocket {
+ public:
+ // |req_info| contains the hostname and port to which the socket above will
+ // communicate to via the socks layer. For testing the referrer is optional.
+ SOCKSClientSocket(scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostResolver::RequestInfo& req_info,
+ HostResolver* host_resolver);
+
+ // On destruction Disconnect() is called.
+ virtual ~SOCKSClientSocket();
+
+ // StreamSocket implementation.
+
+ // Does the SOCKS handshake and completes the protocol.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SOCKSClientSocketTest, CompleteHandshake);
+ FRIEND_TEST_ALL_PREFIXES(SOCKSClientSocketTest, SOCKS4AFailedDNS);
+ FRIEND_TEST_ALL_PREFIXES(SOCKSClientSocketTest, SOCKS4AIfDomainInIPv6);
+
+ enum State {
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_HANDSHAKE_WRITE,
+ STATE_HANDSHAKE_WRITE_COMPLETE,
+ STATE_HANDSHAKE_READ,
+ STATE_HANDSHAKE_READ_COMPLETE,
+ STATE_NONE,
+ };
+
+ void DoCallback(int result);
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoResolveHost();
+ int DoResolveHostComplete(int result);
+ int DoHandshakeRead();
+ int DoHandshakeReadComplete(int result);
+ int DoHandshakeWrite();
+ int DoHandshakeWriteComplete(int result);
+
+ const std::string BuildHandshakeWriteBuffer() const;
+
+ // Stores the underlying socket.
+ scoped_ptr<ClientSocketHandle> transport_;
+
+ State next_state_;
+
+ // Stores the callback to the layer above, called on completing Connect().
+ CompletionCallback user_callback_;
+
+ // This IOBuffer is used by the class to read and write
+ // SOCKS handshake data. The length contains the expected size to
+ // read or write.
+ scoped_refptr<IOBuffer> handshake_buf_;
+
+ // While writing, this buffer stores the complete write handshake data.
+ // While reading, it stores the handshake information received so far.
+ std::string buffer_;
+
+ // This becomes true when the SOCKS handshake has completed and the
+ // overlying connection is free to communicate.
+ bool completed_handshake_;
+
+ // These contain the bytes sent / received by the SOCKS handshake.
+ size_t bytes_sent_;
+ size_t bytes_received_;
+
+ // Used to resolve the hostname to which the SOCKS proxy will connect.
+ SingleRequestHostResolver host_resolver_;
+ AddressList addresses_;
+ HostResolver::RequestInfo host_request_info_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKS_CLIENT_SOCKET_H_
diff --git a/chromium/net/socket/socks_client_socket_pool.cc b/chromium/net/socket/socks_client_socket_pool.cc
new file mode 100644
index 00000000000..e49eabaa84c
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket_pool.cc
@@ -0,0 +1,310 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks_client_socket_pool.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/socks5_client_socket.h"
+#include "net/socket/socks_client_socket.h"
+#include "net/socket/transport_client_socket_pool.h"
+
+namespace net {
+
+SOCKSSocketParams::SOCKSSocketParams(
+ const scoped_refptr<TransportSocketParams>& proxy_server,
+ bool socks_v5,
+ const HostPortPair& host_port_pair,
+ RequestPriority priority)
+ : transport_params_(proxy_server),
+ destination_(host_port_pair),
+ socks_v5_(socks_v5) {
+ if (transport_params_.get())
+ ignore_limits_ = transport_params_->ignore_limits();
+ else
+ ignore_limits_ = false;
+ destination_.set_priority(priority);
+}
+
+SOCKSSocketParams::~SOCKSSocketParams() {}
+
+// SOCKSConnectJobs will time out after this many seconds. Note this is on
+// top of the timeout for the transport socket.
+static const int kSOCKSConnectJobTimeoutInSeconds = 30;
+
+SOCKSConnectJob::SOCKSConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ socks_params_(socks_params),
+ transport_pool_(transport_pool),
+ resolver_(host_resolver),
+ callback_(base::Bind(&SOCKSConnectJob::OnIOComplete,
+ base::Unretained(this))) {
+}
+
+SOCKSConnectJob::~SOCKSConnectJob() {
+ // We don't worry about cancelling the tcp socket since the destructor in
+ // scoped_ptr<ClientSocketHandle> transport_socket_handle_ will take care of
+ // it.
+}
+
+LoadState SOCKSConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_TRANSPORT_CONNECT:
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ return transport_socket_handle_->GetLoadState();
+ case STATE_SOCKS_CONNECT:
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ return LOAD_STATE_CONNECTING;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+void SOCKSConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|
+}
+
+int SOCKSConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_TRANSPORT_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTransportConnect();
+ break;
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ rv = DoTransportConnectComplete(rv);
+ break;
+ case STATE_SOCKS_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSOCKSConnect();
+ break;
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ rv = DoSOCKSConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int SOCKSConnectJob::DoTransportConnect() {
+ next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ return transport_socket_handle_->Init(
+ group_name(), socks_params_->transport_params(),
+ socks_params_->destination().priority(), callback_, transport_pool_,
+ net_log());
+}
+
+int SOCKSConnectJob::DoTransportConnectComplete(int result) {
+ if (result != OK)
+ return ERR_PROXY_CONNECTION_FAILED;
+
+ // Reset the timer to just the length of time allowed for SOCKS handshake
+ // so that a fast TCP connection plus a slow SOCKS failure doesn't take
+ // longer to timeout than it should.
+ ResetTimer(base::TimeDelta::FromSeconds(kSOCKSConnectJobTimeoutInSeconds));
+ next_state_ = STATE_SOCKS_CONNECT;
+ return result;
+}
+
+int SOCKSConnectJob::DoSOCKSConnect() {
+ next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
+
+ // Add a SOCKS connection on top of the tcp socket.
+ if (socks_params_->is_socks_v5()) {
+ socket_.reset(new SOCKS5ClientSocket(transport_socket_handle_.Pass(),
+ socks_params_->destination()));
+ } else {
+ socket_.reset(new SOCKSClientSocket(transport_socket_handle_.Pass(),
+ socks_params_->destination(),
+ resolver_));
+ }
+ return socket_->Connect(
+ base::Bind(&SOCKSConnectJob::OnIOComplete, base::Unretained(this)));
+}
+
+int SOCKSConnectJob::DoSOCKSConnectComplete(int result) {
+ if (result != OK) {
+ socket_->Disconnect();
+ return result;
+ }
+
+ SetSocket(socket_.Pass());
+ return result;
+}
+
+int SOCKSConnectJob::ConnectInternal() {
+ next_state_ = STATE_TRANSPORT_CONNECT;
+ return DoLoop(OK);
+}
+
+scoped_ptr<ConnectJob>
+SOCKSClientSocketPool::SOCKSConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return scoped_ptr<ConnectJob>(new SOCKSConnectJob(group_name,
+ request.params(),
+ ConnectionTimeout(),
+ transport_pool_,
+ host_resolver_,
+ delegate,
+ net_log_));
+}
+
+base::TimeDelta
+SOCKSClientSocketPool::SOCKSConnectJobFactory::ConnectionTimeout() const {
+ return transport_pool_->ConnectionTimeout() +
+ base::TimeDelta::FromSeconds(kSOCKSConnectJobTimeoutInSeconds);
+}
+
+SOCKSClientSocketPool::SOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ TransportClientSocketPool* transport_pool,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ base_(max_sockets, max_sockets_per_group, histograms,
+ ClientSocketPool::unused_idle_socket_timeout(),
+ ClientSocketPool::used_idle_socket_timeout(),
+ new SOCKSConnectJobFactory(transport_pool,
+ host_resolver,
+ net_log)) {
+ // We should always have a |transport_pool_| except in unit tests.
+ if (transport_pool_)
+ transport_pool_->AddLayeredPool(this);
+}
+
+SOCKSClientSocketPool::~SOCKSClientSocketPool() {
+ // We should always have a |transport_pool_| except in unit tests.
+ if (transport_pool_)
+ transport_pool_->RemoveLayeredPool(this);
+}
+
+int SOCKSClientSocketPool::RequestSocket(
+ const std::string& group_name, const void* socket_params,
+ RequestPriority priority, ClientSocketHandle* handle,
+ const CompletionCallback& callback, const BoundNetLog& net_log) {
+ const scoped_refptr<SOCKSSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<SOCKSSocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void SOCKSClientSocketPool::RequestSockets(
+ const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<SOCKSSocketParams>* casted_params =
+ static_cast<const scoped_refptr<SOCKSSocketParams>*>(params);
+
+ base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
+}
+
+void SOCKSClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void SOCKSClientSocketPool::ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket.Pass(), id);
+}
+
+void SOCKSClientSocketPool::FlushWithError(int error) {
+ base_.FlushWithError(error);
+}
+
+bool SOCKSClientSocketPool::IsStalled() const {
+ return base_.IsStalled() || transport_pool_->IsStalled();
+}
+
+void SOCKSClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int SOCKSClientSocketPool::IdleSocketCount() const {
+ return base_.idle_socket_count();
+}
+
+int SOCKSClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState SOCKSClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+void SOCKSClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
+ base_.AddLayeredPool(layered_pool);
+}
+
+void SOCKSClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
+ base_.RemoveLayeredPool(layered_pool);
+}
+
+base::DictionaryValue* SOCKSClientSocketPool::GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const {
+ base::DictionaryValue* dict = base_.GetInfoAsValue(name, type);
+ if (include_nested_pools) {
+ base::ListValue* list = new base::ListValue();
+ list->Append(transport_pool_->GetInfoAsValue("transport_socket_pool",
+ "transport_socket_pool",
+ false));
+ dict->Set("nested_pools", list);
+ }
+ return dict;
+}
+
+base::TimeDelta SOCKSClientSocketPool::ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+}
+
+ClientSocketPoolHistograms* SOCKSClientSocketPool::histograms() const {
+ return base_.histograms();
+};
+
+bool SOCKSClientSocketPool::CloseOneIdleConnection() {
+ if (base_.CloseOneIdleSocket())
+ return true;
+ return base_.CloseOneIdleConnectionInLayeredPool();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/socks_client_socket_pool.h b/chromium/net/socket/socks_client_socket_pool.h
new file mode 100644
index 00000000000..fe69a78df69
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket_pool.h
@@ -0,0 +1,211 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/host_port_pair.h"
+#include "net/dns/host_resolver.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+
+namespace net {
+
+class ConnectJobFactory;
+class TransportClientSocketPool;
+class TransportSocketParams;
+
+class NET_EXPORT_PRIVATE SOCKSSocketParams
+ : public base::RefCounted<SOCKSSocketParams> {
+ public:
+ SOCKSSocketParams(const scoped_refptr<TransportSocketParams>& proxy_server,
+ bool socks_v5, const HostPortPair& host_port_pair,
+ RequestPriority priority);
+
+ const scoped_refptr<TransportSocketParams>& transport_params() const {
+ return transport_params_;
+ }
+ const HostResolver::RequestInfo& destination() const { return destination_; }
+ bool is_socks_v5() const { return socks_v5_; }
+ bool ignore_limits() const { return ignore_limits_; }
+
+ private:
+ friend class base::RefCounted<SOCKSSocketParams>;
+ ~SOCKSSocketParams();
+
+ // The transport (likely TCP) connection must point toward the proxy server.
+ const scoped_refptr<TransportSocketParams> transport_params_;
+ // This is the HTTP destination.
+ HostResolver::RequestInfo destination_;
+ const bool socks_v5_;
+ bool ignore_limits_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSSocketParams);
+};
+
+// SOCKSConnectJob handles the handshake to a socks server after setting up
+// an underlying transport socket.
+class SOCKSConnectJob : public ConnectJob {
+ public:
+ SOCKSConnectJob(const std::string& group_name,
+ const scoped_refptr<SOCKSSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~SOCKSConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const OVERRIDE;
+
+ private:
+ enum State {
+ STATE_TRANSPORT_CONNECT,
+ STATE_TRANSPORT_CONNECT_COMPLETE,
+ STATE_SOCKS_CONNECT,
+ STATE_SOCKS_CONNECT_COMPLETE,
+ STATE_NONE,
+ };
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoTransportConnect();
+ int DoTransportConnectComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
+
+ // Begins the transport connection and the SOCKS handshake. Returns OK on
+ // success and ERR_IO_PENDING if it cannot immediately service the request.
+ // Otherwise, it returns a net error code.
+ virtual int ConnectInternal() OVERRIDE;
+
+ scoped_refptr<SOCKSSocketParams> socks_params_;
+ TransportClientSocketPool* const transport_pool_;
+ HostResolver* const resolver_;
+
+ State next_state_;
+ CompletionCallback callback_;
+ scoped_ptr<ClientSocketHandle> transport_socket_handle_;
+ scoped_ptr<StreamSocket> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSConnectJob);
+};
+
+class NET_EXPORT_PRIVATE SOCKSClientSocketPool
+ : public ClientSocketPool, public LayeredPool {
+ public:
+ SOCKSClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ TransportClientSocketPool* transport_pool,
+ NetLog* net_log);
+
+ virtual ~SOCKSClientSocketPool();
+
+ // ClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+
+ virtual void FlushWithError(int error) OVERRIDE;
+
+ virtual bool IsStalled() const OVERRIDE;
+
+ virtual void CloseIdleSockets() OVERRIDE;
+
+ virtual int IdleSocketCount() const OVERRIDE;
+
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE;
+
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE;
+
+ virtual void AddLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual void RemoveLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ virtual ClientSocketPoolHistograms* histograms() const OVERRIDE;
+
+ // LayeredPool implementation.
+ virtual bool CloseOneIdleConnection() OVERRIDE;
+
+ private:
+ typedef ClientSocketPoolBase<SOCKSSocketParams> PoolBase;
+
+ class SOCKSConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ SOCKSConnectJobFactory(TransportClientSocketPool* transport_pool,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {}
+
+ virtual ~SOCKSConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ private:
+ TransportClientSocketPool* const transport_pool_;
+ HostResolver* const host_resolver_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSConnectJobFactory);
+ };
+
+ TransportClientSocketPool* const transport_pool_;
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(SOCKSClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(SOCKSClientSocketPool, SOCKSSocketParams);
+
+} // namespace net
+
+#endif // NET_SOCKET_SOCKS_CLIENT_SOCKET_POOL_H_
diff --git a/chromium/net/socket/socks_client_socket_pool_unittest.cc b/chromium/net/socket/socks_client_socket_pool_unittest.cc
new file mode 100644
index 00000000000..77440d36a19
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket_pool_unittest.cc
@@ -0,0 +1,297 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+
+// Make sure |handle|'s load times are set correctly. Only connect times should
+// be set.
+void TestLoadTimingInfo(const ClientSocketHandle& handle) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ // None of these tests use a NetLog.
+ EXPECT_EQ(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+class SOCKSClientSocketPoolTest : public testing::Test {
+ protected:
+ class SOCKS5MockData {
+ public:
+ explicit SOCKS5MockData(IoMode mode) {
+ writes_.reset(new MockWrite[3]);
+ writes_[0] = MockWrite(mode, kSOCKS5GreetRequest,
+ kSOCKS5GreetRequestLength);
+ writes_[1] = MockWrite(mode, kSOCKS5OkRequest, kSOCKS5OkRequestLength);
+ writes_[2] = MockWrite(mode, 0);
+
+ reads_.reset(new MockRead[3]);
+ reads_[0] = MockRead(mode, kSOCKS5GreetResponse,
+ kSOCKS5GreetResponseLength);
+ reads_[1] = MockRead(mode, kSOCKS5OkResponse, kSOCKS5OkResponseLength);
+ reads_[2] = MockRead(mode, 0);
+
+ data_.reset(new StaticSocketDataProvider(reads_.get(), 3,
+ writes_.get(), 3));
+ }
+
+ SocketDataProvider* data_provider() { return data_.get(); }
+
+ private:
+ scoped_ptr<StaticSocketDataProvider> data_;
+ scoped_ptr<MockWrite[]> writes_;
+ scoped_ptr<MockRead[]> reads_;
+ };
+
+ SOCKSClientSocketPoolTest()
+ : ignored_transport_socket_params_(new TransportSocketParams(
+ HostPortPair("proxy", 80), MEDIUM, false, false,
+ OnHostResolutionCallback())),
+ transport_histograms_("MockTCP"),
+ transport_socket_pool_(
+ kMaxSockets, kMaxSocketsPerGroup,
+ &transport_histograms_,
+ &transport_client_socket_factory_),
+ ignored_socket_params_(new SOCKSSocketParams(
+ ignored_transport_socket_params_, true, HostPortPair("host", 80),
+ MEDIUM)),
+ socks_histograms_("SOCKSUnitTest"),
+ pool_(kMaxSockets, kMaxSocketsPerGroup,
+ &socks_histograms_,
+ NULL,
+ &transport_socket_pool_,
+ NULL) {
+ }
+
+ virtual ~SOCKSClientSocketPoolTest() {}
+
+ int StartRequest(const std::string& group_name, RequestPriority priority) {
+ return test_base_.StartRequestUsingPool(
+ &pool_, group_name, priority, ignored_socket_params_);
+ }
+
+ int GetOrderOfRequest(size_t index) const {
+ return test_base_.GetOrderOfRequest(index);
+ }
+
+ ScopedVector<TestSocketRequest>* requests() { return test_base_.requests(); }
+
+ scoped_refptr<TransportSocketParams> ignored_transport_socket_params_;
+ ClientSocketPoolHistograms transport_histograms_;
+ MockClientSocketFactory transport_client_socket_factory_;
+ MockTransportClientSocketPool transport_socket_pool_;
+
+ scoped_refptr<SOCKSSocketParams> ignored_socket_params_;
+ ClientSocketPoolHistograms socks_histograms_;
+ SOCKSClientSocketPool pool_;
+ ClientSocketPoolTest test_base_;
+};
+
+TEST_F(SOCKSClientSocketPoolTest, Simple) {
+ SOCKS5MockData data(SYNCHRONOUS);
+ data.data_provider()->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ transport_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, CompletionCallback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+}
+
+TEST_F(SOCKSClientSocketPoolTest, Async) {
+ SOCKS5MockData data(ASYNC);
+ transport_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, callback.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+}
+
+TEST_F(SOCKSClientSocketPoolTest, TransportConnectError) {
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(SYNCHRONOUS,
+ ERR_CONNECTION_REFUSED));
+ transport_client_socket_factory_.AddSocketDataProvider(&socket_data);
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, CompletionCallback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, AsyncTransportConnectError) {
+ StaticSocketDataProvider socket_data;
+ socket_data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED));
+ transport_client_socket_factory_.AddSocketDataProvider(&socket_data);
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", ignored_socket_params_, LOW, callback.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, SOCKSConnectError) {
+ MockRead failed_read[] = {
+ MockRead(SYNCHRONOUS, 0),
+ };
+ StaticSocketDataProvider socket_data(
+ failed_read, arraysize(failed_read), NULL, 0);
+ socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ transport_client_socket_factory_.AddSocketDataProvider(&socket_data);
+
+ ClientSocketHandle handle;
+ EXPECT_EQ(0, transport_socket_pool_.release_count());
+ int rv = handle.Init("a", ignored_socket_params_, LOW, CompletionCallback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(1, transport_socket_pool_.release_count());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, AsyncSOCKSConnectError) {
+ MockRead failed_read[] = {
+ MockRead(ASYNC, 0),
+ };
+ StaticSocketDataProvider socket_data(
+ failed_read, arraysize(failed_read), NULL, 0);
+ socket_data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ transport_client_socket_factory_.AddSocketDataProvider(&socket_data);
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ EXPECT_EQ(0, transport_socket_pool_.release_count());
+ int rv = handle.Init("a", ignored_socket_params_, LOW, callback.callback(),
+ &pool_, BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_SOCKS_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(1, transport_socket_pool_.release_count());
+}
+
+TEST_F(SOCKSClientSocketPoolTest, CancelDuringTransportConnect) {
+ SOCKS5MockData data(SYNCHRONOUS);
+ transport_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+ // We need two connections because the pool base lets one cancelled
+ // connect job proceed for potential future use.
+ SOCKS5MockData data2(SYNCHRONOUS);
+ transport_client_socket_factory_.AddSocketDataProvider(data2.data_provider());
+
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+ int rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ pool_.CancelRequest("a", (*requests())[0]->handle());
+ pool_.CancelRequest("a", (*requests())[1]->handle());
+ // Requests in the connect phase don't actually get cancelled.
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+
+ // Now wait for the TCP sockets to connect.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound, GetOrderOfRequest(1));
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound, GetOrderOfRequest(2));
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+ EXPECT_EQ(2, pool_.IdleSocketCount());
+
+ (*requests())[0]->handle()->Reset();
+ (*requests())[1]->handle()->Reset();
+}
+
+TEST_F(SOCKSClientSocketPoolTest, CancelDuringSOCKSConnect) {
+ SOCKS5MockData data(ASYNC);
+ data.data_provider()->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ transport_client_socket_factory_.AddSocketDataProvider(data.data_provider());
+ // We need two connections because the pool base lets one cancelled
+ // connect job proceed for potential future use.
+ SOCKS5MockData data2(ASYNC);
+ data2.data_provider()->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ transport_client_socket_factory_.AddSocketDataProvider(data2.data_provider());
+
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+ EXPECT_EQ(0, transport_socket_pool_.release_count());
+ int rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = StartRequest("a", LOW);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ pool_.CancelRequest("a", (*requests())[0]->handle());
+ pool_.CancelRequest("a", (*requests())[1]->handle());
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+ // Requests in the connect phase don't actually get cancelled.
+ EXPECT_EQ(0, transport_socket_pool_.release_count());
+
+ // Now wait for the async data to reach the SOCKS connect jobs.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound, GetOrderOfRequest(1));
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound, GetOrderOfRequest(2));
+ EXPECT_EQ(0, transport_socket_pool_.cancel_count());
+ EXPECT_EQ(0, transport_socket_pool_.release_count());
+ EXPECT_EQ(2, pool_.IdleSocketCount());
+
+ (*requests())[0]->handle()->Reset();
+ (*requests())[1]->handle()->Reset();
+}
+
+// It would be nice to also test the timeouts in SOCKSClientSocketPool.
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/socks_client_socket_unittest.cc b/chromium/net/socket/socks_client_socket_unittest.cc
new file mode 100644
index 00000000000..8c30838959d
--- /dev/null
+++ b/chromium/net/socket/socks_client_socket_unittest.cc
@@ -0,0 +1,415 @@
+// Copyright (c) 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.
+
+#include "net/socket/socks_client_socket.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+const char kSOCKSOkRequest[] = { 0x04, 0x01, 0x00, 0x50, 127, 0, 0, 1, 0 };
+const char kSOCKSOkReply[] = { 0x00, 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+class SOCKSClientSocketTest : public PlatformTest {
+ public:
+ SOCKSClientSocketTest();
+ // Create a SOCKSClientSocket on top of a MockSocket.
+ scoped_ptr<SOCKSClientSocket> BuildMockSocket(
+ MockRead reads[], size_t reads_count,
+ MockWrite writes[], size_t writes_count,
+ HostResolver* host_resolver,
+ const std::string& hostname, int port,
+ NetLog* net_log);
+ virtual void SetUp();
+
+ protected:
+ scoped_ptr<SOCKSClientSocket> user_sock_;
+ AddressList address_list_;
+ // Filled in by BuildMockSocket() and owned by its return value
+ // (which |user_sock| is set to).
+ StreamSocket* tcp_sock_;
+ TestCompletionCallback callback_;
+ scoped_ptr<MockHostResolver> host_resolver_;
+ scoped_ptr<SocketDataProvider> data_;
+};
+
+SOCKSClientSocketTest::SOCKSClientSocketTest()
+ : host_resolver_(new MockHostResolver) {
+}
+
+// Set up platform before every test case
+void SOCKSClientSocketTest::SetUp() {
+ PlatformTest::SetUp();
+}
+
+scoped_ptr<SOCKSClientSocket> SOCKSClientSocketTest::BuildMockSocket(
+ MockRead reads[],
+ size_t reads_count,
+ MockWrite writes[],
+ size_t writes_count,
+ HostResolver* host_resolver,
+ const std::string& hostname,
+ int port,
+ NetLog* net_log) {
+
+ TestCompletionCallback callback;
+ data_.reset(new StaticSocketDataProvider(reads, reads_count,
+ writes, writes_count));
+ tcp_sock_ = new MockTCPClientSocket(address_list_, net_log, data_.get());
+
+ int rv = tcp_sock_->Connect(callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(tcp_sock_->IsConnected());
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ // |connection| takes ownership of |tcp_sock_|, but keep a
+ // non-owning pointer to it.
+ connection->SetSocket(scoped_ptr<StreamSocket>(tcp_sock_));
+ return scoped_ptr<SOCKSClientSocket>(new SOCKSClientSocket(
+ connection.Pass(),
+ HostResolver::RequestInfo(HostPortPair(hostname, port)),
+ host_resolver));
+}
+
+// Implementation of HostResolver that never completes its resolve request.
+// We use this in the test "DisconnectWhileHostResolveInProgress" to make
+// sure that the outstanding resolve request gets cancelled.
+class HangingHostResolverWithCancel : public HostResolver {
+ public:
+ HangingHostResolverWithCancel() : outstanding_request_(NULL) {}
+
+ virtual int Resolve(const RequestInfo& info,
+ AddressList* addresses,
+ const CompletionCallback& callback,
+ RequestHandle* out_req,
+ const BoundNetLog& net_log) OVERRIDE {
+ DCHECK(addresses);
+ DCHECK_EQ(false, callback.is_null());
+ EXPECT_FALSE(HasOutstandingRequest());
+ outstanding_request_ = reinterpret_cast<RequestHandle>(1);
+ *out_req = outstanding_request_;
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ResolveFromCache(const RequestInfo& info,
+ AddressList* addresses,
+ const BoundNetLog& net_log) OVERRIDE {
+ NOTIMPLEMENTED();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual void CancelRequest(RequestHandle req) OVERRIDE {
+ EXPECT_TRUE(HasOutstandingRequest());
+ EXPECT_EQ(outstanding_request_, req);
+ outstanding_request_ = NULL;
+ }
+
+ bool HasOutstandingRequest() {
+ return outstanding_request_ != NULL;
+ }
+
+ private:
+ RequestHandle outstanding_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(HangingHostResolverWithCancel);
+};
+
+// Tests a complete handshake and the disconnection.
+TEST_F(SOCKSClientSocketTest, CompleteHandshake) {
+ const std::string payload_write = "random data";
+ const std::string payload_read = "moar random data";
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKSOkRequest, arraysize(kSOCKSOkRequest)),
+ MockWrite(ASYNC, payload_write.data(), payload_write.size()) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKSOkReply, arraysize(kSOCKSOkReply)),
+ MockRead(ASYNC, payload_read.data(), payload_read.size()) };
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_.get(),
+ "localhost", 80,
+ &log);
+
+ // At this state the TCP connection is completed but not the SOCKS handshake.
+ EXPECT_TRUE(tcp_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnected());
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(
+ LogContainsBeginEvent(entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+ EXPECT_FALSE(user_sock_->IsConnected());
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(payload_write.size()));
+ memcpy(buffer->data(), payload_write.data(), payload_write.size());
+ rv = user_sock_->Write(
+ buffer.get(), payload_write.size(), callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(static_cast<int>(payload_write.size()), rv);
+
+ buffer = new IOBuffer(payload_read.size());
+ rv =
+ user_sock_->Read(buffer.get(), payload_read.size(), callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(static_cast<int>(payload_read.size()), rv);
+ EXPECT_EQ(payload_read, std::string(buffer->data(), payload_read.size()));
+
+ user_sock_->Disconnect();
+ EXPECT_FALSE(tcp_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnected());
+}
+
+// List of responses from the socks server and the errors they should
+// throw up are tested here.
+TEST_F(SOCKSClientSocketTest, HandshakeFailures) {
+ const struct {
+ const char fail_reply[8];
+ Error fail_code;
+ } tests[] = {
+ // Failure of the server response code
+ {
+ { 0x01, 0x5A, 0x00, 0x00, 0, 0, 0, 0 },
+ ERR_SOCKS_CONNECTION_FAILED,
+ },
+ // Failure of the null byte
+ {
+ { 0x00, 0x5B, 0x00, 0x00, 0, 0, 0, 0 },
+ ERR_SOCKS_CONNECTION_FAILED,
+ },
+ };
+
+ //---------------------------------------
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ MockWrite data_writes[] = {
+ MockWrite(SYNCHRONOUS, kSOCKSOkRequest, arraysize(kSOCKSOkRequest)) };
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS, tests[i].fail_reply,
+ arraysize(tests[i].fail_reply)) };
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_.get(),
+ "localhost", 80,
+ &log);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(tests[i].fail_code, rv);
+ EXPECT_FALSE(user_sock_->IsConnected());
+ EXPECT_TRUE(tcp_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+ }
+}
+
+// Tests scenario when the server sends the handshake response in
+// more than one packet.
+TEST_F(SOCKSClientSocketTest, PartialServerReads) {
+ const char kSOCKSPartialReply1[] = { 0x00 };
+ const char kSOCKSPartialReply2[] = { 0x5A, 0x00, 0x00, 0, 0, 0, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKSOkRequest, arraysize(kSOCKSOkRequest)) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKSPartialReply1, arraysize(kSOCKSPartialReply1)),
+ MockRead(ASYNC, kSOCKSPartialReply2, arraysize(kSOCKSPartialReply2)) };
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_.get(),
+ "localhost", 80,
+ &log);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+}
+
+// Tests scenario when the client sends the handshake request in
+// more than one packet.
+TEST_F(SOCKSClientSocketTest, PartialClientWrites) {
+ const char kSOCKSPartialRequest1[] = { 0x04, 0x01 };
+ const char kSOCKSPartialRequest2[] = { 0x00, 0x50, 127, 0, 0, 1, 0 };
+
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, arraysize(kSOCKSPartialRequest1)),
+ // simulate some empty writes
+ MockWrite(ASYNC, 0),
+ MockWrite(ASYNC, 0),
+ MockWrite(ASYNC, kSOCKSPartialRequest2,
+ arraysize(kSOCKSPartialRequest2)) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKSOkReply, arraysize(kSOCKSOkReply)) };
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_.get(),
+ "localhost", 80,
+ &log);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(user_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+}
+
+// Tests the case when the server sends a smaller sized handshake data
+// and closes the connection.
+TEST_F(SOCKSClientSocketTest, FailedSocketRead) {
+ MockWrite data_writes[] = {
+ MockWrite(ASYNC, kSOCKSOkRequest, arraysize(kSOCKSOkRequest)) };
+ MockRead data_reads[] = {
+ MockRead(ASYNC, kSOCKSOkReply, arraysize(kSOCKSOkReply) - 2),
+ // close connection unexpectedly
+ MockRead(SYNCHRONOUS, 0) };
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ host_resolver_.get(),
+ "localhost", 80,
+ &log);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+ EXPECT_FALSE(user_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+}
+
+// Tries to connect to an unknown hostname. Should fail rather than
+// falling back to SOCKS4a.
+TEST_F(SOCKSClientSocketTest, FailedDNS) {
+ const char hostname[] = "unresolved.ipv4.address";
+
+ host_resolver_->rules()->AddSimulatedFailure(hostname);
+
+ CapturingNetLog log;
+
+ user_sock_ = BuildMockSocket(NULL, 0,
+ NULL, 0,
+ host_resolver_.get(),
+ hostname, 80,
+ &log);
+
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(
+ entries, 0, NetLog::TYPE_SOCKS_CONNECT));
+
+ rv = callback_.WaitForResult();
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, rv);
+ EXPECT_FALSE(user_sock_->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsEndEvent(
+ entries, -1, NetLog::TYPE_SOCKS_CONNECT));
+}
+
+// Calls Disconnect() while a host resolve is in progress. The outstanding host
+// resolve should be cancelled.
+TEST_F(SOCKSClientSocketTest, DisconnectWhileHostResolveInProgress) {
+ scoped_ptr<HangingHostResolverWithCancel> hanging_resolver(
+ new HangingHostResolverWithCancel());
+
+ // Doesn't matter what the socket data is, we will never use it -- garbage.
+ MockWrite data_writes[] = { MockWrite(SYNCHRONOUS, "", 0) };
+ MockRead data_reads[] = { MockRead(SYNCHRONOUS, "", 0) };
+
+ user_sock_ = BuildMockSocket(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes),
+ hanging_resolver.get(),
+ "foo", 80,
+ NULL);
+
+ // Start connecting (will get stuck waiting for the host to resolve).
+ int rv = user_sock_->Connect(callback_.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ EXPECT_FALSE(user_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnectedAndIdle());
+
+ // The host resolver should have received the resolve request.
+ EXPECT_TRUE(hanging_resolver->HasOutstandingRequest());
+
+ // Disconnect the SOCKS socket -- this should cancel the outstanding resolve.
+ user_sock_->Disconnect();
+
+ EXPECT_FALSE(hanging_resolver->HasOutstandingRequest());
+
+ EXPECT_FALSE(user_sock_->IsConnected());
+ EXPECT_FALSE(user_sock_->IsConnectedAndIdle());
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket.cc b/chromium/net/socket/ssl_client_socket.cc
new file mode 100644
index 00000000000..54f66a1f681
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_client_socket.h"
+
+#include "base/strings/string_util.h"
+
+namespace net {
+
+SSLClientSocket::SSLClientSocket()
+ : was_npn_negotiated_(false),
+ was_spdy_negotiated_(false),
+ protocol_negotiated_(kProtoUnknown),
+ channel_id_sent_(false) {
+}
+
+// static
+NextProto SSLClientSocket::NextProtoFromString(
+ const std::string& proto_string) {
+ if (proto_string == "http1.1" || proto_string == "http/1.1") {
+ return kProtoHTTP11;
+ } else if (proto_string == "spdy/1") {
+ return kProtoSPDY1;
+ } else if (proto_string == "spdy/2") {
+ return kProtoSPDY2;
+ } else if (proto_string == "spdy/3") {
+ return kProtoSPDY3;
+ } else if (proto_string == "spdy/3.1") {
+ return kProtoSPDY31;
+ } else if (proto_string == "spdy/4a2") {
+ return kProtoSPDY4a2;
+ } else if (proto_string == "HTTP-draft-04/2.0") {
+ return kProtoHTTP2Draft04;
+ } else if (proto_string == "quic/1+spdy/3") {
+ return kProtoQUIC1SPDY3;
+ } else {
+ return kProtoUnknown;
+ }
+}
+
+// static
+const char* SSLClientSocket::NextProtoToString(NextProto next_proto) {
+ switch (next_proto) {
+ case kProtoHTTP11:
+ return "http/1.1";
+ case kProtoSPDY1:
+ return "spdy/1";
+ case kProtoSPDY2:
+ return "spdy/2";
+ case kProtoSPDY3:
+ return "spdy/3";
+ case kProtoSPDY31:
+ return "spdy/3.1";
+ case kProtoSPDY4a2:
+ return "spdy/4a2";
+ case kProtoHTTP2Draft04:
+ return "HTTP-draft-04/2.0";
+ case kProtoQUIC1SPDY3:
+ return "quic/1+spdy/3";
+ case kProtoSPDY21:
+ case kProtoUnknown:
+ break;
+ }
+ return "unknown";
+}
+
+// static
+const char* SSLClientSocket::NextProtoStatusToString(
+ const SSLClientSocket::NextProtoStatus status) {
+ switch (status) {
+ case kNextProtoUnsupported:
+ return "unsupported";
+ case kNextProtoNegotiated:
+ return "negotiated";
+ case kNextProtoNoOverlap:
+ return "no-overlap";
+ }
+ return NULL;
+}
+
+// static
+std::string SSLClientSocket::ServerProtosToString(
+ const std::string& server_protos) {
+ const char* protos = server_protos.c_str();
+ size_t protos_len = server_protos.length();
+ std::vector<std::string> server_protos_with_commas;
+ for (size_t i = 0; i < protos_len; ) {
+ const size_t len = protos[i];
+ std::string proto_str(&protos[i + 1], len);
+ server_protos_with_commas.push_back(proto_str);
+ i += len + 1;
+ }
+ return JoinString(server_protos_with_commas, ',');
+}
+
+bool SSLClientSocket::WasNpnNegotiated() const {
+ return was_npn_negotiated_;
+}
+
+NextProto SSLClientSocket::GetNegotiatedProtocol() const {
+ return protocol_negotiated_;
+}
+
+bool SSLClientSocket::IgnoreCertError(int error, int load_flags) {
+ if (error == OK || load_flags & LOAD_IGNORE_ALL_CERT_ERRORS)
+ return true;
+
+ if (error == ERR_CERT_COMMON_NAME_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID))
+ return true;
+
+ if (error == ERR_CERT_DATE_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_DATE_INVALID))
+ return true;
+
+ if (error == ERR_CERT_AUTHORITY_INVALID &&
+ (load_flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID))
+ return true;
+
+ return false;
+}
+
+bool SSLClientSocket::set_was_npn_negotiated(bool negotiated) {
+ return was_npn_negotiated_ = negotiated;
+}
+
+bool SSLClientSocket::was_spdy_negotiated() const {
+ return was_spdy_negotiated_;
+}
+
+bool SSLClientSocket::set_was_spdy_negotiated(bool negotiated) {
+ return was_spdy_negotiated_ = negotiated;
+}
+
+void SSLClientSocket::set_protocol_negotiated(NextProto protocol_negotiated) {
+ protocol_negotiated_ = protocol_negotiated;
+}
+
+bool SSLClientSocket::WasChannelIDSent() const {
+ return channel_id_sent_;
+}
+
+void SSLClientSocket::set_channel_id_sent(bool channel_id_sent) {
+ channel_id_sent_ = channel_id_sent;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket.h b/chromium/net/socket/ssl_client_socket.h
new file mode 100644
index 00000000000..41ee0873347
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket.h
@@ -0,0 +1,141 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_H_
+
+#include <string>
+
+#include "net/base/completion_callback.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/socket/ssl_socket.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+class CertVerifier;
+class ServerBoundCertService;
+class SSLCertRequestInfo;
+class SSLInfo;
+class TransportSecurityState;
+
+// This struct groups together several fields which are used by various
+// classes related to SSLClientSocket.
+struct SSLClientSocketContext {
+ SSLClientSocketContext()
+ : cert_verifier(NULL),
+ server_bound_cert_service(NULL),
+ transport_security_state(NULL) {}
+
+ SSLClientSocketContext(CertVerifier* cert_verifier_arg,
+ ServerBoundCertService* server_bound_cert_service_arg,
+ TransportSecurityState* transport_security_state_arg,
+ const std::string& ssl_session_cache_shard_arg)
+ : cert_verifier(cert_verifier_arg),
+ server_bound_cert_service(server_bound_cert_service_arg),
+ transport_security_state(transport_security_state_arg),
+ ssl_session_cache_shard(ssl_session_cache_shard_arg) {}
+
+ CertVerifier* cert_verifier;
+ ServerBoundCertService* server_bound_cert_service;
+ TransportSecurityState* transport_security_state;
+ // ssl_session_cache_shard is an opaque string that identifies a shard of the
+ // SSL session cache. SSL sockets with the same ssl_session_cache_shard may
+ // resume each other's SSL sessions but we'll never sessions between shards.
+ const std::string ssl_session_cache_shard;
+};
+
+// A client socket that uses SSL as the transport layer.
+//
+// NOTE: The SSL handshake occurs within the Connect method after a TCP
+// connection is established. If a SSL error occurs during the handshake,
+// Connect will fail.
+//
+class NET_EXPORT SSLClientSocket : public SSLSocket {
+ public:
+ SSLClientSocket();
+
+ // Next Protocol Negotiation (NPN) allows a TLS client and server to come to
+ // an agreement about the application level protocol to speak over a
+ // connection.
+ enum NextProtoStatus {
+ // WARNING: These values are serialized to disk. Don't change them.
+
+ kNextProtoUnsupported = 0, // The server doesn't support NPN.
+ kNextProtoNegotiated = 1, // We agreed on a protocol.
+ kNextProtoNoOverlap = 2, // No protocols in common. We requested
+ // the first protocol in our list.
+ };
+
+ // StreamSocket:
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+
+ // Gets the SSL CertificateRequest info of the socket after Connect failed
+ // with ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) = 0;
+
+ // Get the application level protocol that we negotiated with the server.
+ // *proto is set to the resulting protocol (n.b. that the string may have
+ // embedded NULs).
+ // kNextProtoUnsupported: *proto is cleared.
+ // kNextProtoNegotiated: *proto is set to the negotiated protocol.
+ // kNextProtoNoOverlap: *proto is set to the first protocol in the
+ // supported list.
+ // *server_protos is set to the server advertised protocols.
+ virtual NextProtoStatus GetNextProto(std::string* proto,
+ std::string* server_protos) = 0;
+
+ static NextProto NextProtoFromString(const std::string& proto_string);
+
+ static const char* NextProtoToString(NextProto next_proto);
+
+ static const char* NextProtoStatusToString(const NextProtoStatus status);
+
+ // Can be used with the second argument(|server_protos|) of |GetNextProto| to
+ // construct a comma separated string of server advertised protocols.
+ static std::string ServerProtosToString(const std::string& server_protos);
+
+ static bool IgnoreCertError(int error, int load_flags);
+
+ // ClearSessionCache clears the SSL session cache, used to resume SSL
+ // sessions.
+ static void ClearSessionCache();
+
+ virtual bool set_was_npn_negotiated(bool negotiated);
+
+ virtual bool was_spdy_negotiated() const;
+
+ virtual bool set_was_spdy_negotiated(bool negotiated);
+
+ virtual void set_protocol_negotiated(NextProto protocol_negotiated);
+
+ // Returns the ServerBoundCertService used by this socket, or NULL if
+ // server bound certificates are not supported.
+ virtual ServerBoundCertService* GetServerBoundCertService() const = 0;
+
+ // Returns true if a channel ID was sent on this connection.
+ // This may be useful for protocols, like SPDY, which allow the same
+ // connection to be shared between multiple domains, each of which need
+ // a channel ID.
+ virtual bool WasChannelIDSent() const;
+
+ virtual void set_channel_id_sent(bool channel_id_sent);
+
+ private:
+ // True if NPN was responded to, independent of selecting SPDY or HTTP.
+ bool was_npn_negotiated_;
+ // True if NPN successfully negotiated SPDY.
+ bool was_spdy_negotiated_;
+ // Protocol that we negotiated with the server.
+ NextProto protocol_negotiated_;
+ // True if a channel ID was sent.
+ bool channel_id_sent_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_H_
diff --git a/chromium/net/socket/ssl_client_socket_nss.cc b/chromium/net/socket/ssl_client_socket_nss.cc
new file mode 100644
index 00000000000..acc1b0dee2e
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_nss.cc
@@ -0,0 +1,3504 @@
+// Copyright (c) 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.
+
+// This file includes code SSLClientSocketNSS::DoVerifyCertComplete() derived
+// from AuthCertificateCallback() in
+// mozilla/security/manager/ssl/src/nsNSSCallbacks.cpp.
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ian McGreer <mcgreer@netscape.com>
+ * Javier Delgadillo <javi@netscape.com>
+ * Kai Engert <kengert@redhat.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/socket/ssl_client_socket_nss.h"
+
+#include <certdb.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <nspr.h>
+#include <nss.h>
+#include <ocsp.h>
+#include <pk11pub.h>
+#include <secerr.h>
+#include <sechash.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include <algorithm>
+#include <limits>
+#include <map>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/address_list.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/single_request_cert_verifier.h"
+#include "net/cert/x509_certificate_net_log_param.h"
+#include "net/cert/x509_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/ocsp/nss_ocsp.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/nss_ssl_util.h"
+#include "net/socket/ssl_error_params.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <wincrypt.h>
+
+#include "base/win/windows_version.h"
+#elif defined(OS_MACOSX)
+#include <Security/SecBase.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecIdentity.h>
+
+#include "base/mac/mac_logging.h"
+#include "base/synchronization/lock.h"
+#include "crypto/mac_security_services_lock.h"
+#elif defined(USE_NSS)
+#include <dlfcn.h>
+#endif
+
+namespace net {
+
+// State machines are easier to debug if you log state transitions.
+// Enable these if you want to see what's going on.
+#if 1
+#define EnterFunction(x)
+#define LeaveFunction(x)
+#define GotoState(s) next_handshake_state_ = s
+#else
+#define EnterFunction(x)\
+ VLOG(1) << (void *)this << " " << __FUNCTION__ << " enter " << x\
+ << "; next_handshake_state " << next_handshake_state_
+#define LeaveFunction(x)\
+ VLOG(1) << (void *)this << " " << __FUNCTION__ << " leave " << x\
+ << "; next_handshake_state " << next_handshake_state_
+#define GotoState(s)\
+ do {\
+ VLOG(1) << (void *)this << " " << __FUNCTION__ << " jump to state " << s;\
+ next_handshake_state_ = s;\
+ } while (0)
+#endif
+
+namespace {
+
+// SSL plaintext fragments are shorter than 16KB. Although the record layer
+// overhead is allowed to be 2K + 5 bytes, in practice the overhead is much
+// smaller than 1KB. So a 17KB buffer should be large enough to hold an
+// entire SSL record.
+const int kRecvBufferSize = 17 * 1024;
+const int kSendBufferSize = 17 * 1024;
+
+// Used by SSLClientSocketNSS::Core to indicate there is no read result
+// obtained by a previous operation waiting to be returned to the caller.
+// This constant can be any non-negative/non-zero value (eg: it does not
+// overlap with any value of the net::Error range, including net::OK).
+const int kNoPendingReadResult = 1;
+
+#if defined(OS_WIN)
+// CERT_OCSP_RESPONSE_PROP_ID is only implemented on Vista+, but it can be
+// set on Windows XP without error. There is some overhead from the server
+// sending the OCSP response if it supports the extension, for the subset of
+// XP clients who will request it but be unable to use it, but this is an
+// acceptable trade-off for simplicity of implementation.
+bool IsOCSPStaplingSupported() {
+ return true;
+}
+#elif defined(USE_NSS)
+typedef SECStatus
+(*CacheOCSPResponseFromSideChannelFunction)(
+ CERTCertDBHandle *handle, CERTCertificate *cert, PRTime time,
+ SECItem *encodedResponse, void *pwArg);
+
+// On Linux, we dynamically link against the system version of libnss3.so. In
+// order to continue working on systems without up-to-date versions of NSS we
+// lookup CERT_CacheOCSPResponseFromSideChannel with dlsym.
+
+// RuntimeLibNSSFunctionPointers is a singleton which caches the results of any
+// runtime symbol resolution that we need.
+class RuntimeLibNSSFunctionPointers {
+ public:
+ CacheOCSPResponseFromSideChannelFunction
+ GetCacheOCSPResponseFromSideChannelFunction() {
+ return cache_ocsp_response_from_side_channel_;
+ }
+
+ static RuntimeLibNSSFunctionPointers* GetInstance() {
+ return Singleton<RuntimeLibNSSFunctionPointers>::get();
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<RuntimeLibNSSFunctionPointers>;
+
+ RuntimeLibNSSFunctionPointers() {
+ cache_ocsp_response_from_side_channel_ =
+ (CacheOCSPResponseFromSideChannelFunction)
+ dlsym(RTLD_DEFAULT, "CERT_CacheOCSPResponseFromSideChannel");
+ }
+
+ CacheOCSPResponseFromSideChannelFunction
+ cache_ocsp_response_from_side_channel_;
+};
+
+CacheOCSPResponseFromSideChannelFunction
+GetCacheOCSPResponseFromSideChannelFunction() {
+ return RuntimeLibNSSFunctionPointers::GetInstance()
+ ->GetCacheOCSPResponseFromSideChannelFunction();
+}
+
+bool IsOCSPStaplingSupported() {
+ return GetCacheOCSPResponseFromSideChannelFunction() != NULL;
+}
+#else
+// TODO(agl): Figure out if we can plumb the OCSP response into Mac's system
+// certificate validation functions.
+bool IsOCSPStaplingSupported() {
+ return false;
+}
+#endif
+
+class FreeCERTCertificate {
+ public:
+ inline void operator()(CERTCertificate* x) const {
+ CERT_DestroyCertificate(x);
+ }
+};
+typedef scoped_ptr_malloc<CERTCertificate, FreeCERTCertificate>
+ ScopedCERTCertificate;
+
+#if defined(OS_WIN)
+
+// This callback is intended to be used with CertFindChainInStore. In addition
+// to filtering by extended/enhanced key usage, we do not show expired
+// certificates and require digital signature usage in the key usage
+// extension.
+//
+// This matches our behavior on Mac OS X and that of NSS. It also matches the
+// default behavior of IE8. See http://support.microsoft.com/kb/890326 and
+// http://blogs.msdn.com/b/askie/archive/2009/06/09/my-expired-client-certificates-no-longer-display-when-connecting-to-my-web-server-using-ie8.aspx
+BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context,
+ void* find_arg) {
+ VLOG(1) << "Calling ClientCertFindCallback from _nss";
+ // Verify the certificate's KU is good.
+ BYTE key_usage;
+ if (CertGetIntendedKeyUsage(X509_ASN_ENCODING, cert_context->pCertInfo,
+ &key_usage, 1)) {
+ if (!(key_usage & CERT_DIGITAL_SIGNATURE_KEY_USAGE))
+ return FALSE;
+ } else {
+ DWORD err = GetLastError();
+ // If |err| is non-zero, it's an actual error. Otherwise the extension
+ // just isn't present, and we treat it as if everything was allowed.
+ if (err) {
+ DLOG(ERROR) << "CertGetIntendedKeyUsage failed: " << err;
+ return FALSE;
+ }
+ }
+
+ // Verify the current time is within the certificate's validity period.
+ if (CertVerifyTimeValidity(NULL, cert_context->pCertInfo) != 0)
+ return FALSE;
+
+ // Verify private key metadata is associated with this certificate.
+ DWORD size = 0;
+ if (!CertGetCertificateContextProperty(
+ cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+void DestroyCertificates(CERTCertificate** certs, size_t len) {
+ for (size_t i = 0; i < len; i++)
+ CERT_DestroyCertificate(certs[i]);
+}
+
+// Helper functions to make it possible to log events from within the
+// SSLClientSocketNSS::Core.
+void AddLogEvent(const base::WeakPtr<BoundNetLog>& net_log,
+ NetLog::EventType event_type) {
+ if (!net_log)
+ return;
+ net_log->AddEvent(event_type);
+}
+
+// Helper function to make it possible to log events from within the
+// SSLClientSocketNSS::Core.
+void AddLogEventWithCallback(const base::WeakPtr<BoundNetLog>& net_log,
+ NetLog::EventType event_type,
+ const NetLog::ParametersCallback& callback) {
+ if (!net_log)
+ return;
+ net_log->AddEvent(event_type, callback);
+}
+
+// Helper function to make it easier to call BoundNetLog::AddByteTransferEvent
+// from within the SSLClientSocketNSS::Core.
+// AddByteTransferEvent expects to receive a const char*, which within the
+// Core is backed by an IOBuffer. If the "const char*" is bound via
+// base::Bind and posted to another thread, and the IOBuffer that backs that
+// pointer then goes out of scope on the origin thread, this would result in
+// an invalid read of a stale pointer.
+// Instead, provide a signature that accepts an IOBuffer*, so that a reference
+// to the owning IOBuffer can be bound to the Callback. This ensures that the
+// IOBuffer will stay alive long enough to cross threads if needed.
+void LogByteTransferEvent(
+ const base::WeakPtr<BoundNetLog>& net_log, NetLog::EventType event_type,
+ int len, IOBuffer* buffer) {
+ if (!net_log)
+ return;
+ net_log->AddByteTransferEvent(event_type, len, buffer->data());
+}
+
+// PeerCertificateChain is a helper object which extracts the certificate
+// chain, as given by the server, from an NSS socket and performs the needed
+// resource management. The first element of the chain is the leaf certificate
+// and the other elements are in the order given by the server.
+class PeerCertificateChain {
+ public:
+ PeerCertificateChain() {}
+ PeerCertificateChain(const PeerCertificateChain& other);
+ ~PeerCertificateChain();
+ PeerCertificateChain& operator=(const PeerCertificateChain& other);
+
+ // Resets the current chain, freeing any resources, and updates the current
+ // chain to be a copy of the chain stored in |nss_fd|.
+ // If |nss_fd| is NULL, then the current certificate chain will be freed.
+ void Reset(PRFileDesc* nss_fd);
+
+ // Returns the current certificate chain as a vector of DER-encoded
+ // base::StringPieces. The returned vector remains valid until Reset is
+ // called.
+ std::vector<base::StringPiece> AsStringPieceVector() const;
+
+ bool empty() const { return certs_.empty(); }
+ size_t size() const { return certs_.size(); }
+
+ CERTCertificate* operator[](size_t index) const {
+ DCHECK_LT(index, certs_.size());
+ return certs_[index];
+ }
+
+ private:
+ std::vector<CERTCertificate*> certs_;
+};
+
+PeerCertificateChain::PeerCertificateChain(
+ const PeerCertificateChain& other) {
+ *this = other;
+}
+
+PeerCertificateChain::~PeerCertificateChain() {
+ Reset(NULL);
+}
+
+PeerCertificateChain& PeerCertificateChain::operator=(
+ const PeerCertificateChain& other) {
+ if (this == &other)
+ return *this;
+
+ Reset(NULL);
+ certs_.reserve(other.certs_.size());
+ for (size_t i = 0; i < other.certs_.size(); ++i)
+ certs_.push_back(CERT_DupCertificate(other.certs_[i]));
+
+ return *this;
+}
+
+void PeerCertificateChain::Reset(PRFileDesc* nss_fd) {
+ for (size_t i = 0; i < certs_.size(); ++i)
+ CERT_DestroyCertificate(certs_[i]);
+ certs_.clear();
+
+ if (nss_fd == NULL)
+ return;
+
+ unsigned int num_certs = 0;
+ SECStatus rv = SSL_PeerCertificateChain(nss_fd, NULL, &num_certs, 0);
+ DCHECK_EQ(SECSuccess, rv);
+
+ // The handshake on |nss_fd| may not have completed.
+ if (num_certs == 0)
+ return;
+
+ certs_.resize(num_certs);
+ const unsigned int expected_num_certs = num_certs;
+ rv = SSL_PeerCertificateChain(nss_fd, vector_as_array(&certs_),
+ &num_certs, expected_num_certs);
+ DCHECK_EQ(SECSuccess, rv);
+ DCHECK_EQ(expected_num_certs, num_certs);
+}
+
+std::vector<base::StringPiece>
+PeerCertificateChain::AsStringPieceVector() const {
+ std::vector<base::StringPiece> v(certs_.size());
+ for (unsigned i = 0; i < certs_.size(); i++) {
+ v[i] = base::StringPiece(
+ reinterpret_cast<const char*>(certs_[i]->derCert.data),
+ certs_[i]->derCert.len);
+ }
+
+ return v;
+}
+
+// HandshakeState is a helper struct used to pass handshake state between
+// the NSS task runner and the network task runner.
+//
+// It contains members that may be read or written on the NSS task runner,
+// but which also need to be read from the network task runner. The NSS task
+// runner will notify the network task runner whenever this state changes, so
+// that the network task runner can safely make a copy, which avoids the need
+// for locking.
+struct HandshakeState {
+ HandshakeState() { Reset(); }
+
+ void Reset() {
+ next_proto_status = SSLClientSocket::kNextProtoUnsupported;
+ next_proto.clear();
+ server_protos.clear();
+ channel_id_sent = false;
+ server_cert_chain.Reset(NULL);
+ server_cert = NULL;
+ resumed_handshake = false;
+ ssl_connection_status = 0;
+ }
+
+ // Set to kNextProtoNegotiated if NPN was successfully negotiated, with the
+ // negotiated protocol stored in |next_proto|.
+ SSLClientSocket::NextProtoStatus next_proto_status;
+ std::string next_proto;
+ // If the server supports NPN, the protocols supported by the server.
+ std::string server_protos;
+
+ // True if a channel ID was sent.
+ bool channel_id_sent;
+
+ // List of DER-encoded X.509 DistinguishedName of certificate authorities
+ // allowed by the server.
+ std::vector<std::string> cert_authorities;
+
+ // Set when the handshake fully completes.
+ //
+ // The server certificate is first received from NSS as an NSS certificate
+ // chain (|server_cert_chain|) and then converted into a platform-specific
+ // X509Certificate object (|server_cert|). It's possible for some
+ // certificates to be successfully parsed by NSS, and not by the platform
+ // libraries (i.e.: when running within a sandbox, different parsing
+ // algorithms, etc), so it's not safe to assume that |server_cert| will
+ // always be non-NULL.
+ PeerCertificateChain server_cert_chain;
+ scoped_refptr<X509Certificate> server_cert;
+
+ // True if the current handshake was the result of TLS session resumption.
+ bool resumed_handshake;
+
+ // The negotiated security parameters (TLS version, cipher, extensions) of
+ // the SSL connection.
+ int ssl_connection_status;
+};
+
+// Client-side error mapping functions.
+
+// Map NSS error code to network error code.
+int MapNSSClientError(PRErrorCode err) {
+ switch (err) {
+ case SSL_ERROR_BAD_CERT_ALERT:
+ case SSL_ERROR_UNSUPPORTED_CERT_ALERT:
+ case SSL_ERROR_REVOKED_CERT_ALERT:
+ case SSL_ERROR_EXPIRED_CERT_ALERT:
+ case SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT:
+ case SSL_ERROR_UNKNOWN_CA_ALERT:
+ case SSL_ERROR_ACCESS_DENIED_ALERT:
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+ default:
+ return MapNSSError(err);
+ }
+}
+
+// Map NSS error code from the first SSL handshake to network error code.
+int MapNSSClientHandshakeError(PRErrorCode err) {
+ switch (err) {
+ // If the server closed on us, it is a protocol error.
+ // Some TLS-intolerant servers do this when we request TLS.
+ case PR_END_OF_FILE_ERROR:
+ return ERR_SSL_PROTOCOL_ERROR;
+ default:
+ return MapNSSClientError(err);
+ }
+}
+
+} // namespace
+
+// SSLClientSocketNSS::Core provides a thread-safe, ref-counted core that is
+// able to marshal data between NSS functions and an underlying transport
+// socket.
+//
+// All public functions are meant to be called from the network task runner,
+// and any callbacks supplied will be invoked there as well, provided that
+// Detach() has not been called yet.
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Threading within SSLClientSocketNSS and SSLClientSocketNSS::Core:
+//
+// Because NSS may block on either hardware or user input during operations
+// such as signing, creating certificates, or locating private keys, the Core
+// handles all of the interactions with the underlying NSS SSL socket, so
+// that these blocking calls can be executed on a dedicated task runner.
+//
+// Note that the network task runner and the NSS task runner may be executing
+// on the same thread. If that happens, then it's more performant to try to
+// complete as much work as possible synchronously, even if it might block,
+// rather than continually PostTask-ing to the same thread.
+//
+// Because NSS functions should only be called on the NSS task runner, while
+// I/O resources should only be accessed on the network task runner, most
+// public functions are implemented via three methods, each with different
+// task runner affinities.
+//
+// In the single-threaded mode (where the network and NSS task runners run on
+// the same thread), these are all attempted synchronously, while in the
+// multi-threaded mode, message passing is used.
+//
+// 1) NSS Task Runner: Execute NSS function (DoPayloadRead, DoPayloadWrite,
+// DoHandshake)
+// 2) NSS Task Runner: Prepare data to go from NSS to an IO function:
+// (BufferRecv, BufferSend)
+// 3) Network Task Runner: Perform IO on that data (DoBufferRecv,
+// DoBufferSend, DoGetDomainBoundCert, OnGetDomainBoundCertComplete)
+// 4) Both Task Runners: Callback for asynchronous completion or to marshal
+// data from the network task runner back to NSS (BufferRecvComplete,
+// BufferSendComplete, OnHandshakeIOComplete)
+//
+/////////////////////////////////////////////////////////////////////////////
+// Single-threaded example
+//
+// |--------------------------Network Task Runner--------------------------|
+// SSLClientSocketNSS Core (Transport Socket)
+// Read()
+// |-------------------------V
+// Read()
+// |
+// DoPayloadRead()
+// |
+// BufferRecv()
+// |
+// DoBufferRecv()
+// |-------------------------V
+// Read()
+// V-------------------------|
+// BufferRecvComplete()
+// |
+// PostOrRunCallback()
+// V-------------------------|
+// (Read Callback)
+//
+/////////////////////////////////////////////////////////////////////////////
+// Multi-threaded example:
+//
+// |--------------------Network Task Runner-------------|--NSS Task Runner--|
+// SSLClientSocketNSS Core Socket Core
+// Read()
+// |---------------------V
+// Read()
+// |-------------------------------V
+// Read()
+// |
+// DoPayloadRead()
+// |
+// BufferRecv
+// V-------------------------------|
+// DoBufferRecv
+// |----------------V
+// Read()
+// V----------------|
+// BufferRecvComplete()
+// |-------------------------------V
+// BufferRecvComplete()
+// |
+// PostOrRunCallback()
+// V-------------------------------|
+// PostOrRunCallback()
+// V---------------------|
+// (Read Callback)
+//
+/////////////////////////////////////////////////////////////////////////////
+class SSLClientSocketNSS::Core : public base::RefCountedThreadSafe<Core> {
+ public:
+ // Creates a new Core.
+ //
+ // Any calls to NSS are executed on the |nss_task_runner|, while any calls
+ // that need to operate on the underlying transport, net log, or server
+ // bound certificate fetching will happen on the |network_task_runner|, so
+ // that their lifetimes match that of the owning SSLClientSocketNSS.
+ //
+ // The caller retains ownership of |transport|, |net_log|, and
+ // |server_bound_cert_service|, and they will not be accessed once Detach()
+ // has been called.
+ Core(base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* nss_task_runner,
+ ClientSocketHandle* transport,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ BoundNetLog* net_log,
+ ServerBoundCertService* server_bound_cert_service);
+
+ // Called on the network task runner.
+ // Transfers ownership of |socket|, an NSS SSL socket, and |buffers|, the
+ // underlying memio implementation, to the Core. Returns true if the Core
+ // was successfully registered with the socket.
+ bool Init(PRFileDesc* socket, memio_Private* buffers);
+
+ // Called on the network task runner.
+ // Sets the predicted certificate chain that the peer will send, for use
+ // with the TLS CachedInfo extension. If called, it must not be called
+ // before Init() or after Connect().
+ void SetPredictedCertificates(
+ const std::vector<std::string>& predicted_certificates);
+
+ // Called on the network task runner.
+ //
+ // Attempts to perform an SSL handshake. If the handshake cannot be
+ // completed synchronously, returns ERR_IO_PENDING, invoking |callback| on
+ // the network task runner once the handshake has completed. Otherwise,
+ // returns OK on success or a network error code on failure.
+ int Connect(const CompletionCallback& callback);
+
+ // Called on the network task runner.
+ // Signals that the resources owned by the network task runner are going
+ // away. No further callbacks will be invoked on the network task runner.
+ // May be called at any time.
+ void Detach();
+
+ // Called on the network task runner.
+ // Returns the current state of the underlying SSL socket. May be called at
+ // any time.
+ const HandshakeState& state() const { return network_handshake_state_; }
+
+ // Called on the network task runner.
+ // Read() and Write() mirror the net::Socket functions of the same name.
+ // If ERR_IO_PENDING is returned, |callback| will be invoked on the network
+ // task runner at a later point, unless the caller calls Detach().
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+ int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Called on the network task runner.
+ bool IsConnected();
+ bool HasPendingAsyncOperation();
+ bool HasUnhandledReceivedData();
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+ ~Core();
+
+ enum State {
+ STATE_NONE,
+ STATE_HANDSHAKE,
+ STATE_GET_DOMAIN_BOUND_CERT_COMPLETE,
+ };
+
+ bool OnNSSTaskRunner() const;
+ bool OnNetworkTaskRunner() const;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are ONLY called on the NSS task runner:
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Called by NSS during full handshakes to allow the application to
+ // verify the certificate. Instead of verifying the certificate in the midst
+ // of the handshake, SECSuccess is always returned and the peer's certificate
+ // is verified afterwards.
+ // This behaviour is an artifact of the original SSLClientSocketWin
+ // implementation, which could not verify the peer's certificate until after
+ // the handshake had completed, as well as bugs in NSS that prevent
+ // SSL_RestartHandshakeAfterCertReq from working.
+ static SECStatus OwnAuthCertHandler(void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server);
+
+ // Callbacks called by NSS when the peer requests client certificate
+ // authentication.
+ // See the documentation in third_party/nss/ssl/ssl.h for the meanings of
+ // the arguments.
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+ // When NSS has been integrated with awareness of the underlying system
+ // cryptographic libraries, this callback allows the caller to supply a
+ // native platform certificate and key for use by NSS. At most, one of
+ // either (result_certs, result_private_key) or (result_nss_certificate,
+ // result_nss_private_key) should be set.
+ // |arg| contains a pointer to the current SSLClientSocketNSS::Core.
+ static SECStatus PlatformClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertList** result_certs,
+ void** result_private_key,
+ CERTCertificate** result_nss_certificate,
+ SECKEYPrivateKey** result_nss_private_key);
+#else
+ static SECStatus ClientAuthHandler(void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key);
+#endif
+
+ // Called by NSS once the handshake has completed.
+ // |arg| contains a pointer to the current SSLClientSocketNSS::Core.
+ static void HandshakeCallback(PRFileDesc* socket, void* arg);
+
+ // Handles an NSS error generated while handshaking or performing IO.
+ // Returns a network error code mapped from the original NSS error.
+ int HandleNSSError(PRErrorCode error, bool handshake_error);
+
+ int DoHandshakeLoop(int last_io_result);
+ int DoReadLoop(int result);
+ int DoWriteLoop(int result);
+
+ int DoHandshake();
+ int DoGetDBCertComplete(int result);
+
+ int DoPayloadRead();
+ int DoPayloadWrite();
+
+ bool DoTransportIO();
+ int BufferRecv();
+ int BufferSend();
+
+ void OnRecvComplete(int result);
+ void OnSendComplete(int result);
+
+ void DoConnectCallback(int result);
+ void DoReadCallback(int result);
+ void DoWriteCallback(int result);
+
+ // Client channel ID handler.
+ static SECStatus ClientChannelIDHandler(
+ void* arg,
+ PRFileDesc* socket,
+ SECKEYPublicKey **out_public_key,
+ SECKEYPrivateKey **out_private_key);
+
+ // ImportChannelIDKeys is a helper function for turning a DER-encoded cert and
+ // key into a SECKEYPublicKey and SECKEYPrivateKey. Returns OK upon success
+ // and an error code otherwise.
+ // Requires |domain_bound_private_key_| and |domain_bound_cert_| to have been
+ // set by a call to ServerBoundCertService->GetDomainBoundCert. The caller
+ // takes ownership of the |*cert| and |*key|.
+ int ImportChannelIDKeys(SECKEYPublicKey** public_key, SECKEYPrivateKey** key);
+
+ // Updates the NSS and platform specific certificates.
+ void UpdateServerCert();
+ // Updates the nss_handshake_state_ with the negotiated security parameters.
+ void UpdateConnectionStatus();
+ // Record histograms for channel id support during full handshakes - resumed
+ // handshakes are ignored.
+ void RecordChannelIDSupport();
+ // UpdateNextProto gets any application-layer protocol that may have been
+ // negotiated by the TLS connection.
+ void UpdateNextProto();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are ONLY called on the network task runner:
+ ////////////////////////////////////////////////////////////////////////////
+ int DoBufferRecv(IOBuffer* buffer, int len);
+ int DoBufferSend(IOBuffer* buffer, int len);
+ int DoGetDomainBoundCert(const std::string& host);
+
+ void OnGetDomainBoundCertComplete(int result);
+ void OnHandshakeStateUpdated(const HandshakeState& state);
+ void OnNSSBufferUpdated(int amount_in_read_buffer);
+ void DidNSSRead(int result);
+ void DidNSSWrite(int result);
+ void RecordChannelIDSupportOnNetworkTaskRunner(
+ bool negotiated_channel_id,
+ bool channel_id_enabled,
+ bool supports_ecc) const;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods that are called on both the network task runner and the NSS
+ // task runner.
+ ////////////////////////////////////////////////////////////////////////////
+ void OnHandshakeIOComplete(int result);
+ void BufferRecvComplete(IOBuffer* buffer, int result);
+ void BufferSendComplete(int result);
+
+ // PostOrRunCallback is a helper function to ensure that |callback| is
+ // invoked on the network task runner, but only if Detach() has not yet
+ // been called.
+ void PostOrRunCallback(const tracked_objects::Location& location,
+ const base::Closure& callback);
+
+ // Uses PostOrRunCallback and |weak_net_log_| to try and log a
+ // SSL_CLIENT_CERT_PROVIDED event, with the indicated count.
+ void AddCertProvidedEvent(int cert_count);
+
+ // Sets the handshake state |channel_id_sent| flag and logs the
+ // SSL_CHANNEL_ID_PROVIDED event.
+ void SetChannelIDProvided();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are ONLY accessed on the network task runner:
+ ////////////////////////////////////////////////////////////////////////////
+
+ // True if the owning SSLClientSocketNSS has called Detach(). No further
+ // callbacks will be invoked nor access to members owned by the network
+ // task runner.
+ bool detached_;
+
+ // The underlying transport to use for network IO.
+ ClientSocketHandle* transport_;
+ base::WeakPtrFactory<BoundNetLog> weak_net_log_factory_;
+
+ // The current handshake state. Mirrors |nss_handshake_state_|.
+ HandshakeState network_handshake_state_;
+
+ // The service for retrieving Channel ID keys. May be NULL.
+ ServerBoundCertService* server_bound_cert_service_;
+ ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+
+ // The information about NSS task runner.
+ int unhandled_buffer_size_;
+ bool nss_waiting_read_;
+ bool nss_waiting_write_;
+ bool nss_is_closed_;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are ONLY accessed on the NSS task runner:
+ ////////////////////////////////////////////////////////////////////////////
+ HostPortPair host_and_port_;
+ SSLConfig ssl_config_;
+
+ // NSS SSL socket.
+ PRFileDesc* nss_fd_;
+
+ // Buffers for the network end of the SSL state machine
+ memio_Private* nss_bufs_;
+
+ // Used by DoPayloadRead() when attempting to fill the caller's buffer with
+ // as much data as possible, without blocking.
+ // If DoPayloadRead() encounters an error after having read some data, stores
+ // the results to return on the *next* call to DoPayloadRead(). A value of
+ // kNoPendingReadResult indicates there is no pending result, otherwise 0
+ // indicates EOF and < 0 indicates an error.
+ int pending_read_result_;
+ // Contains the previously observed NSS error. Only valid when
+ // pending_read_result_ != kNoPendingReadResult.
+ PRErrorCode pending_read_nss_error_;
+
+ // The certificate chain, in DER form, that is expected to be received from
+ // the server.
+ std::vector<std::string> predicted_certs_;
+
+ State next_handshake_state_;
+
+ // True if channel ID extension was negotiated.
+ bool channel_id_xtn_negotiated_;
+ // True if the handshake state machine was interrupted for channel ID.
+ bool channel_id_needed_;
+ // True if the handshake state machine was interrupted for client auth.
+ bool client_auth_cert_needed_;
+ // True if NSS has called HandshakeCallback.
+ bool handshake_callback_called_;
+
+ HandshakeState nss_handshake_state_;
+
+ bool transport_recv_busy_;
+ bool transport_recv_eof_;
+ bool transport_send_busy_;
+
+ // Used by Read function.
+ scoped_refptr<IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ // Used by Write function.
+ scoped_refptr<IOBuffer> user_write_buf_;
+ int user_write_buf_len_;
+
+ CompletionCallback user_connect_callback_;
+ CompletionCallback user_read_callback_;
+ CompletionCallback user_write_callback_;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Members that are accessed on both the network task runner and the NSS
+ // task runner.
+ ////////////////////////////////////////////////////////////////////////////
+ scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> nss_task_runner_;
+
+ // Dereferenced only on the network task runner, but bound to tasks destined
+ // for the network task runner from the NSS task runner.
+ base::WeakPtr<BoundNetLog> weak_net_log_;
+
+ // Written on the network task runner by the |server_bound_cert_service_|,
+ // prior to invoking OnHandshakeIOComplete.
+ // Read on the NSS task runner when once OnHandshakeIOComplete is invoked
+ // on the NSS task runner.
+ std::string domain_bound_private_key_;
+ std::string domain_bound_cert_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+SSLClientSocketNSS::Core::Core(
+ base::SequencedTaskRunner* network_task_runner,
+ base::SequencedTaskRunner* nss_task_runner,
+ ClientSocketHandle* transport,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ BoundNetLog* net_log,
+ ServerBoundCertService* server_bound_cert_service)
+ : detached_(false),
+ transport_(transport),
+ weak_net_log_factory_(net_log),
+ server_bound_cert_service_(server_bound_cert_service),
+ unhandled_buffer_size_(0),
+ nss_waiting_read_(false),
+ nss_waiting_write_(false),
+ nss_is_closed_(false),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ nss_fd_(NULL),
+ nss_bufs_(NULL),
+ pending_read_result_(kNoPendingReadResult),
+ pending_read_nss_error_(0),
+ next_handshake_state_(STATE_NONE),
+ channel_id_xtn_negotiated_(false),
+ channel_id_needed_(false),
+ client_auth_cert_needed_(false),
+ handshake_callback_called_(false),
+ transport_recv_busy_(false),
+ transport_recv_eof_(false),
+ transport_send_busy_(false),
+ user_read_buf_len_(0),
+ user_write_buf_len_(0),
+ network_task_runner_(network_task_runner),
+ nss_task_runner_(nss_task_runner),
+ weak_net_log_(weak_net_log_factory_.GetWeakPtr()) {
+}
+
+SSLClientSocketNSS::Core::~Core() {
+ // TODO(wtc): Send SSL close_notify alert.
+ if (nss_fd_ != NULL) {
+ PR_Close(nss_fd_);
+ nss_fd_ = NULL;
+ }
+}
+
+bool SSLClientSocketNSS::Core::Init(PRFileDesc* socket,
+ memio_Private* buffers) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!nss_fd_);
+ DCHECK(!nss_bufs_);
+
+ nss_fd_ = socket;
+ nss_bufs_ = buffers;
+
+ SECStatus rv = SECSuccess;
+
+ if (!ssl_config_.next_protos.empty()) {
+ size_t wire_length = 0;
+ for (std::vector<std::string>::const_iterator
+ i = ssl_config_.next_protos.begin();
+ i != ssl_config_.next_protos.end(); ++i) {
+ if (i->size() > 255) {
+ LOG(WARNING) << "Ignoring overlong NPN/ALPN protocol: " << *i;
+ continue;
+ }
+ wire_length += i->size();
+ wire_length++;
+ }
+ scoped_ptr<uint8[]> wire_protos(new uint8[wire_length]);
+ uint8* dst = wire_protos.get();
+ for (std::vector<std::string>::const_iterator
+ i = ssl_config_.next_protos.begin();
+ i != ssl_config_.next_protos.end(); i++) {
+ if (i->size() > 255)
+ continue;
+ *dst++ = i->size();
+ memcpy(dst, i->data(), i->size());
+ dst += i->size();
+ }
+ DCHECK_EQ(dst, wire_protos.get() + wire_length);
+ rv = SSL_SetNextProtoNego(nss_fd_, wire_protos.get(), wire_length);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(*weak_net_log_, "SSL_SetNextProtoCallback", "");
+ }
+
+ rv = SSL_AuthCertificateHook(
+ nss_fd_, SSLClientSocketNSS::Core::OwnAuthCertHandler, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_AuthCertificateHook", "");
+ return false;
+ }
+
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+ rv = SSL_GetPlatformClientAuthDataHook(
+ nss_fd_, SSLClientSocketNSS::Core::PlatformClientAuthHandler,
+ this);
+#else
+ rv = SSL_GetClientAuthDataHook(
+ nss_fd_, SSLClientSocketNSS::Core::ClientAuthHandler, this);
+#endif
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_GetClientAuthDataHook", "");
+ return false;
+ }
+
+ if (ssl_config_.channel_id_enabled) {
+ if (!server_bound_cert_service_) {
+ DVLOG(1) << "NULL server_bound_cert_service_, not enabling channel ID.";
+ } else if (!crypto::ECPrivateKey::IsSupported()) {
+ DVLOG(1) << "Elliptic Curve not supported, not enabling channel ID.";
+ } else if (!server_bound_cert_service_->IsSystemTimeValid()) {
+ DVLOG(1) << "System time is weird, not enabling channel ID.";
+ } else {
+ rv = SSL_SetClientChannelIDCallback(
+ nss_fd_, SSLClientSocketNSS::Core::ClientChannelIDHandler, this);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(*weak_net_log_, "SSL_SetClientChannelIDCallback",
+ "");
+ }
+ }
+
+ rv = SSL_HandshakeCallback(
+ nss_fd_, SSLClientSocketNSS::Core::HandshakeCallback, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(*weak_net_log_, "SSL_HandshakeCallback", "");
+ return false;
+ }
+
+ return true;
+}
+
+void SSLClientSocketNSS::Core::SetPredictedCertificates(
+ const std::vector<std::string>& predicted_certs) {
+ if (predicted_certs.empty())
+ return;
+
+ if (!OnNSSTaskRunner()) {
+ DCHECK(!detached_);
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::SetPredictedCertificates, this, predicted_certs));
+ return;
+ }
+
+ DCHECK(nss_fd_);
+
+ predicted_certs_ = predicted_certs;
+
+ scoped_ptr<CERTCertificate*[]> certs(
+ new CERTCertificate*[predicted_certs.size()]);
+
+ for (size_t i = 0; i < predicted_certs.size(); i++) {
+ SECItem derCert;
+ derCert.data = const_cast<uint8*>(reinterpret_cast<const uint8*>(
+ predicted_certs[i].data()));
+ derCert.len = predicted_certs[i].size();
+ certs[i] = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &derCert, NULL /* no nickname given */,
+ PR_FALSE /* not permanent */, PR_TRUE /* copy DER data */);
+ if (!certs[i]) {
+ DestroyCertificates(&certs[0], i);
+ NOTREACHED();
+ return;
+ }
+ }
+
+ SECStatus rv;
+#ifdef SSL_ENABLE_CACHED_INFO
+ rv = SSL_SetPredictedPeerCertificates(nss_fd_, certs.get(),
+ predicted_certs.size());
+ DCHECK_EQ(SECSuccess, rv);
+#else
+ rv = SECFailure; // Not implemented.
+#endif
+ DestroyCertificates(&certs[0], predicted_certs.size());
+
+ if (rv != SECSuccess) {
+ LOG(WARNING) << "SetPredictedCertificates failed: "
+ << host_and_port_.ToString();
+ }
+}
+
+int SSLClientSocketNSS::Core::Connect(const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(!detached_);
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Connect), this, callback));
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_read_callback_.is_null());
+ DCHECK(user_write_callback_.is_null());
+ DCHECK(user_connect_callback_.is_null());
+ DCHECK(!user_read_buf_.get());
+ DCHECK(!user_write_buf_.get());
+
+ next_handshake_state_ = STATE_HANDSHAKE;
+ int rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_connect_callback_ = callback;
+ } else if (rv > OK) {
+ rv = OK;
+ }
+ if (rv != ERR_IO_PENDING && !OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ }
+
+ return rv;
+}
+
+void SSLClientSocketNSS::Core::Detach() {
+ DCHECK(OnNetworkTaskRunner());
+
+ detached_ = true;
+ transport_ = NULL;
+ weak_net_log_factory_.InvalidateWeakPtrs();
+
+ network_handshake_state_.Reset();
+
+ domain_bound_cert_request_handle_.Cancel();
+}
+
+int SSLClientSocketNSS::Core::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!detached_);
+ DCHECK(transport_);
+ DCHECK(!nss_waiting_read_);
+
+ nss_waiting_read_ = true;
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Read), this, make_scoped_refptr(buf),
+ buf_len, callback));
+ if (!posted) {
+ nss_is_closed_ = true;
+ nss_waiting_read_ = false;
+ }
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_read_callback_.is_null());
+ DCHECK(user_connect_callback_.is_null());
+ DCHECK(!user_read_buf_.get());
+ DCHECK(nss_bufs_);
+
+ user_read_buf_ = buf;
+ user_read_buf_len_ = buf_len;
+
+ int rv = DoReadLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ if (OnNetworkTaskRunner())
+ nss_waiting_read_ = true;
+ user_read_callback_ = callback;
+ } else {
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+
+ if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(&Core::DidNSSRead, this, rv));
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ } else {
+ DCHECK(!nss_waiting_read_);
+ if (rv <= 0)
+ nss_is_closed_ = true;
+ }
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (!OnNSSTaskRunner()) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(!detached_);
+ DCHECK(transport_);
+ DCHECK(!nss_waiting_write_);
+
+ nss_waiting_write_ = true;
+ bool posted = nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::Write), this, make_scoped_refptr(buf),
+ buf_len, callback));
+ if (!posted) {
+ nss_is_closed_ = true;
+ nss_waiting_write_ = false;
+ }
+ return posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_write_callback_.is_null());
+ DCHECK(user_connect_callback_.is_null());
+ DCHECK(!user_write_buf_.get());
+ DCHECK(nss_bufs_);
+
+ user_write_buf_ = buf;
+ user_write_buf_len_ = buf_len;
+
+ int rv = DoWriteLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ if (OnNetworkTaskRunner())
+ nss_waiting_write_ = true;
+ user_write_callback_ = callback;
+ } else {
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+
+ if (!OnNetworkTaskRunner()) {
+ PostOrRunCallback(FROM_HERE, base::Bind(&Core::DidNSSWrite, this, rv));
+ PostOrRunCallback(FROM_HERE, base::Bind(callback, rv));
+ return ERR_IO_PENDING;
+ } else {
+ DCHECK(!nss_waiting_write_);
+ if (rv < 0)
+ nss_is_closed_ = true;
+ }
+ }
+
+ return rv;
+}
+
+bool SSLClientSocketNSS::Core::IsConnected() {
+ DCHECK(OnNetworkTaskRunner());
+ return !nss_is_closed_;
+}
+
+bool SSLClientSocketNSS::Core::HasPendingAsyncOperation() {
+ DCHECK(OnNetworkTaskRunner());
+ return nss_waiting_read_ || nss_waiting_write_;
+}
+
+bool SSLClientSocketNSS::Core::HasUnhandledReceivedData() {
+ DCHECK(OnNetworkTaskRunner());
+ return unhandled_buffer_size_ != 0;
+}
+
+bool SSLClientSocketNSS::Core::OnNSSTaskRunner() const {
+ return nss_task_runner_->RunsTasksOnCurrentThread();
+}
+
+bool SSLClientSocketNSS::Core::OnNetworkTaskRunner() const {
+ return network_task_runner_->RunsTasksOnCurrentThread();
+}
+
+// static
+SECStatus SSLClientSocketNSS::Core::OwnAuthCertHandler(
+ void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ if (!core->handshake_callback_called_) {
+ // Only need to turn off False Start in the initial handshake. Also, it is
+ // unsafe to call SSL_OptionSet in a renegotiation because the "first
+ // handshake" lock isn't already held, which will result in an assertion
+ // failure in the ssl_Get1stHandshakeLock call in SSL_OptionSet.
+ PRBool negotiated_extension;
+ SECStatus rv = SSL_HandshakeNegotiatedExtension(socket,
+ ssl_app_layer_protocol_xtn,
+ &negotiated_extension);
+ if (rv != SECSuccess || !negotiated_extension) {
+ rv = SSL_HandshakeNegotiatedExtension(socket,
+ ssl_next_proto_nego_xtn,
+ &negotiated_extension);
+ }
+ if (rv != SECSuccess || !negotiated_extension) {
+ // If the server doesn't support NPN or ALPN, then we don't do False
+ // Start with it.
+ SSL_OptionSet(socket, SSL_ENABLE_FALSE_START, PR_FALSE);
+ }
+ }
+
+ // Tell NSS to not verify the certificate.
+ return SECSuccess;
+}
+
+#if defined(NSS_PLATFORM_CLIENT_AUTH)
+// static
+SECStatus SSLClientSocketNSS::Core::PlatformClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertList** result_certs,
+ void** result_private_key,
+ CERTCertificate** result_nss_certificate,
+ SECKEYPrivateKey** result_nss_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED));
+
+ core->client_auth_cert_needed_ = !core->ssl_config_.send_client_cert;
+#if defined(OS_WIN)
+ if (core->ssl_config_.send_client_cert) {
+ if (core->ssl_config_.client_cert) {
+ PCCERT_CONTEXT cert_context =
+ core->ssl_config_.client_cert->os_cert_handle();
+
+ HCRYPTPROV_OR_NCRYPT_KEY_HANDLE crypt_prov = 0;
+ DWORD key_spec = 0;
+ BOOL must_free = FALSE;
+ DWORD flags = 0;
+ if (base::win::GetVersion() >= base::win::VERSION_VISTA)
+ flags |= CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG;
+
+ BOOL acquired_key = CryptAcquireCertificatePrivateKey(
+ cert_context, flags, NULL, &crypt_prov, &key_spec, &must_free);
+
+ if (acquired_key) {
+ // Should never get a cached handle back - ownership must always be
+ // transferred.
+ CHECK_EQ(must_free, TRUE);
+
+ SECItem der_cert;
+ der_cert.type = siDERCertBuffer;
+ der_cert.data = cert_context->pbCertEncoded;
+ der_cert.len = cert_context->cbCertEncoded;
+
+ // TODO(rsleevi): Error checking for NSS allocation errors.
+ CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB();
+ CERTCertificate* user_cert = CERT_NewTempCertificate(
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!user_cert) {
+ // Importing the certificate can fail for reasons including a serial
+ // number collision. See crbug.com/97355.
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+ }
+ CERTCertList* cert_chain = CERT_NewCertList();
+ CERT_AddCertToListTail(cert_chain, user_cert);
+
+ // Add the intermediates.
+ X509Certificate::OSCertHandles intermediates =
+ core->ssl_config_.client_cert->GetIntermediateCertificates();
+ for (X509Certificate::OSCertHandles::const_iterator it =
+ intermediates.begin(); it != intermediates.end(); ++it) {
+ der_cert.data = (*it)->pbCertEncoded;
+ der_cert.len = (*it)->cbCertEncoded;
+
+ CERTCertificate* intermediate = CERT_NewTempCertificate(
+ db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!intermediate) {
+ CERT_DestroyCertList(cert_chain);
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+ }
+ CERT_AddCertToListTail(cert_chain, intermediate);
+ }
+ PCERT_KEY_CONTEXT key_context = reinterpret_cast<PCERT_KEY_CONTEXT>(
+ PORT_ZAlloc(sizeof(CERT_KEY_CONTEXT)));
+ key_context->cbSize = sizeof(*key_context);
+ // NSS will free this context when no longer in use.
+ key_context->hCryptProv = crypt_prov;
+ key_context->dwKeySpec = key_spec;
+ *result_private_key = key_context;
+ *result_certs = cert_chain;
+
+ int cert_count = 1 + intermediates.size();
+ core->AddCertProvidedEvent(cert_count);
+ return SECSuccess;
+ }
+ LOG(WARNING) << "Client cert found without private key";
+ }
+
+ // Send no client certificate.
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+ }
+
+ core->nss_handshake_state_.cert_authorities.clear();
+
+ std::vector<CERT_NAME_BLOB> issuer_list(ca_names->nnames);
+ for (int i = 0; i < ca_names->nnames; ++i) {
+ issuer_list[i].cbData = ca_names->names[i].len;
+ issuer_list[i].pbData = ca_names->names[i].data;
+ core->nss_handshake_state_.cert_authorities.push_back(std::string(
+ reinterpret_cast<const char*>(ca_names->names[i].data),
+ static_cast<size_t>(ca_names->names[i].len)));
+ }
+
+ // Update the network task runner's view of the handshake state now that
+ // server certificate request has been recorded.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+#elif defined(OS_MACOSX)
+ if (core->ssl_config_.send_client_cert) {
+ if (core->ssl_config_.client_cert.get()) {
+ OSStatus os_error = noErr;
+ SecIdentityRef identity = NULL;
+ SecKeyRef private_key = NULL;
+ X509Certificate::OSCertHandles chain;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ os_error = SecIdentityCreateWithCertificate(
+ NULL, core->ssl_config_.client_cert->os_cert_handle(), &identity);
+ }
+ if (os_error == noErr) {
+ os_error = SecIdentityCopyPrivateKey(identity, &private_key);
+ CFRelease(identity);
+ }
+
+ if (os_error == noErr) {
+ // TODO(rsleevi): Error checking for NSS allocation errors.
+ *result_certs = CERT_NewCertList();
+ *result_private_key = private_key;
+
+ chain.push_back(core->ssl_config_.client_cert->os_cert_handle());
+ const X509Certificate::OSCertHandles& intermediates =
+ core->ssl_config_.client_cert->GetIntermediateCertificates();
+ if (!intermediates.empty())
+ chain.insert(chain.end(), intermediates.begin(), intermediates.end());
+
+ for (size_t i = 0, chain_count = chain.size(); i < chain_count; ++i) {
+ CSSM_DATA cert_data;
+ SecCertificateRef cert_ref = chain[i];
+ os_error = SecCertificateGetData(cert_ref, &cert_data);
+ if (os_error != noErr)
+ break;
+
+ SECItem der_cert;
+ der_cert.type = siDERCertBuffer;
+ der_cert.data = cert_data.Data;
+ der_cert.len = cert_data.Length;
+ CERTCertificate* nss_cert = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!nss_cert) {
+ // In the event of an NSS error, make up an OS error and reuse
+ // the error handling below.
+ os_error = errSecCreateChainFailed;
+ break;
+ }
+ CERT_AddCertToListTail(*result_certs, nss_cert);
+ }
+ }
+
+ if (os_error == noErr) {
+ core->AddCertProvidedEvent(chain.size());
+ return SECSuccess;
+ }
+
+ OSSTATUS_LOG(WARNING, os_error)
+ << "Client cert found, but could not be used";
+ if (*result_certs) {
+ CERT_DestroyCertList(*result_certs);
+ *result_certs = NULL;
+ }
+ if (*result_private_key)
+ *result_private_key = NULL;
+ if (private_key)
+ CFRelease(private_key);
+ }
+
+ // Send no client certificate.
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+ }
+
+ core->nss_handshake_state_.cert_authorities.clear();
+
+ // Retrieve the cert issuers accepted by the server.
+ std::vector<CertPrincipal> valid_issuers;
+ int n = ca_names->nnames;
+ for (int i = 0; i < n; i++) {
+ core->nss_handshake_state_.cert_authorities.push_back(std::string(
+ reinterpret_cast<const char*>(ca_names->names[i].data),
+ static_cast<size_t>(ca_names->names[i].len)));
+ }
+
+ // Update the network task runner's view of the handshake state now that
+ // server certificate request has been recorded.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+#else
+ return SECFailure;
+#endif
+}
+
+#elif defined(OS_IOS)
+
+SECStatus SSLClientSocketNSS::Core::ClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED));
+
+ // TODO(droger): Support client auth on iOS. See http://crbug.com/145954).
+ LOG(WARNING) << "Client auth is not supported";
+
+ // Never send a certificate.
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+}
+
+#else // NSS_PLATFORM_CLIENT_AUTH
+
+// static
+// Based on Mozilla's NSS_GetClientAuthData.
+SECStatus SSLClientSocketNSS::Core::ClientAuthHandler(
+ void* arg,
+ PRFileDesc* socket,
+ CERTDistNames* ca_names,
+ CERTCertificate** result_certificate,
+ SECKEYPrivateKey** result_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_REQUESTED));
+
+ // Regular client certificate requested.
+ core->client_auth_cert_needed_ = !core->ssl_config_.send_client_cert;
+ void* wincx = SSL_RevealPinArg(socket);
+
+ if (core->ssl_config_.send_client_cert) {
+ // Second pass: a client certificate should have been selected.
+ if (core->ssl_config_.client_cert.get()) {
+ CERTCertificate* cert =
+ CERT_DupCertificate(core->ssl_config_.client_cert->os_cert_handle());
+ SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx);
+ if (privkey) {
+ // TODO(jsorianopastor): We should wait for server certificate
+ // verification before sending our credentials. See
+ // http://crbug.com/13934.
+ *result_certificate = cert;
+ *result_private_key = privkey;
+ // A cert_count of -1 means the number of certificates is unknown.
+ // NSS will construct the certificate chain.
+ core->AddCertProvidedEvent(-1);
+
+ return SECSuccess;
+ }
+ LOG(WARNING) << "Client cert found without private key";
+ }
+ // Send no client certificate.
+ core->AddCertProvidedEvent(0);
+ return SECFailure;
+ }
+
+ // First pass: client certificate is needed.
+ core->nss_handshake_state_.cert_authorities.clear();
+
+ // Retrieve the DER-encoded DistinguishedName of the cert issuers accepted by
+ // the server and save them in |cert_authorities|.
+ for (int i = 0; i < ca_names->nnames; i++) {
+ core->nss_handshake_state_.cert_authorities.push_back(std::string(
+ reinterpret_cast<const char*>(ca_names->names[i].data),
+ static_cast<size_t>(ca_names->names[i].len)));
+ }
+
+ // Update the network task runner's view of the handshake state now that
+ // server certificate request has been recorded.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ core->nss_handshake_state_));
+
+ // Tell NSS to suspend the client authentication. We will then abort the
+ // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED.
+ return SECWouldBlock;
+}
+#endif // NSS_PLATFORM_CLIENT_AUTH
+
+// static
+void SSLClientSocketNSS::Core::HandshakeCallback(
+ PRFileDesc* socket,
+ void* arg) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->handshake_callback_called_ = true;
+
+ HandshakeState* nss_state = &core->nss_handshake_state_;
+
+ PRBool last_handshake_resumed;
+ SECStatus rv = SSL_HandshakeResumedSession(socket, &last_handshake_resumed);
+ if (rv == SECSuccess && last_handshake_resumed) {
+ nss_state->resumed_handshake = true;
+ } else {
+ nss_state->resumed_handshake = false;
+ }
+
+ core->RecordChannelIDSupport();
+ core->UpdateServerCert();
+ core->UpdateConnectionStatus();
+ core->UpdateNextProto();
+
+ // Update the network task runners view of the handshake state whenever
+ // a handshake has completed.
+ core->PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, core,
+ *nss_state));
+}
+
+int SSLClientSocketNSS::Core::HandleNSSError(PRErrorCode nss_error,
+ bool handshake_error) {
+ DCHECK(OnNSSTaskRunner());
+
+ int net_error = handshake_error ? MapNSSClientHandshakeError(nss_error) :
+ MapNSSClientError(nss_error);
+
+#if defined(OS_WIN)
+ // On Windows, a handle to the HCRYPTPROV is cached in the X509Certificate
+ // os_cert_handle() as an optimization. However, if the certificate
+ // private key is stored on a smart card, and the smart card is removed,
+ // the cached HCRYPTPROV will not be able to obtain the HCRYPTKEY again,
+ // preventing client certificate authentication. Because the
+ // X509Certificate may outlive the individual SSLClientSocketNSS, due to
+ // caching in X509Certificate, this failure ends up preventing client
+ // certificate authentication with the same certificate for all future
+ // attempts, even after the smart card has been re-inserted. By setting
+ // the CERT_KEY_PROV_HANDLE_PROP_ID to NULL, the cached HCRYPTPROV will
+ // typically be freed. This allows a new HCRYPTPROV to be obtained from
+ // the certificate on the next attempt, which should succeed if the smart
+ // card has been re-inserted, or will typically prompt the user to
+ // re-insert the smart card if not.
+ if ((net_error == ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY ||
+ net_error == ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED) &&
+ ssl_config_.send_client_cert && ssl_config_.client_cert) {
+ CertSetCertificateContextProperty(
+ ssl_config_.client_cert->os_cert_handle(),
+ CERT_KEY_PROV_HANDLE_PROP_ID, 0, NULL);
+ }
+#endif
+
+ return net_error;
+}
+
+int SSLClientSocketNSS::Core::DoHandshakeLoop(int last_io_result) {
+ DCHECK(OnNSSTaskRunner());
+
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+
+ switch (state) {
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
+ rv = DoGetDBCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
+ break;
+ }
+
+ // Do the actual network I/O
+ bool network_moved = DoTransportIO();
+ if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
+ // In general we exit the loop if rv is ERR_IO_PENDING. In this
+ // special case we keep looping even if rv is ERR_IO_PENDING because
+ // the transport IO may allow DoHandshake to make progress.
+ DCHECK(rv == OK || rv == ERR_IO_PENDING);
+ rv = OK; // This causes us to stay in the loop.
+ }
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoReadLoop(int result) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, 0)));
+ return rv;
+ }
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadRead();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoWriteLoop(int result) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(handshake_callback_called_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, 0)));
+ return rv;
+ }
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ LeaveFunction(rv);
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoHandshake() {
+ DCHECK(OnNSSTaskRunner());
+
+ int net_error = net::OK;
+ SECStatus rv = SSL_ForceHandshake(nss_fd_);
+
+ // Note: this function may be called multiple times during the handshake, so
+ // even though channel id and client auth are separate else cases, they can
+ // both be used during a single SSL handshake.
+ if (channel_id_needed_) {
+ GotoState(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE);
+ net_error = ERR_IO_PENDING;
+ } else if (client_auth_cert_needed_) {
+ net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ CreateNetLogSSLErrorCallback(net_error, 0)));
+
+ // If the handshake already succeeded (because the server requests but
+ // doesn't require a client cert), we need to invalidate the SSL session
+ // so that we won't try to resume the non-client-authenticated session in
+ // the next handshake. This will cause the server to ask for a client
+ // cert again.
+ if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess)
+ LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError();
+ } else if (rv == SECSuccess) {
+ if (!handshake_callback_called_) {
+ // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=562434 -
+ // SSL_ForceHandshake returned SECSuccess prematurely.
+ rv = SECFailure;
+ net_error = ERR_SSL_PROTOCOL_ERROR;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ CreateNetLogSSLErrorCallback(net_error, 0)));
+ } else {
+ #if defined(SSL_ENABLE_OCSP_STAPLING)
+ // TODO(agl): figure out how to plumb an OCSP response into the Mac
+ // system library and update IsOCSPStaplingSupported for Mac.
+ if (IsOCSPStaplingSupported()) {
+ const SECItemArray* ocsp_responses =
+ SSL_PeerStapledOCSPResponses(nss_fd_);
+ if (ocsp_responses->len) {
+ #if defined(OS_WIN)
+ if (nss_handshake_state_.server_cert) {
+ CRYPT_DATA_BLOB ocsp_response_blob;
+ ocsp_response_blob.cbData = ocsp_responses->items[0].len;
+ ocsp_response_blob.pbData = ocsp_responses->items[0].data;
+ BOOL ok = CertSetCertificateContextProperty(
+ nss_handshake_state_.server_cert->os_cert_handle(),
+ CERT_OCSP_RESPONSE_PROP_ID,
+ CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
+ &ocsp_response_blob);
+ if (!ok) {
+ VLOG(1) << "Failed to set OCSP response property: "
+ << GetLastError();
+ }
+ }
+ #elif defined(USE_NSS)
+ CacheOCSPResponseFromSideChannelFunction cache_ocsp_response =
+ GetCacheOCSPResponseFromSideChannelFunction();
+
+ cache_ocsp_response(
+ CERT_GetDefaultCertDB(),
+ nss_handshake_state_.server_cert_chain[0], PR_Now(),
+ &ocsp_responses->items[0], NULL);
+ #endif
+ }
+ }
+ #endif
+ }
+ // Done!
+ } else {
+ PRErrorCode prerr = PR_GetError();
+ net_error = HandleNSSError(prerr, true);
+
+ // Some network devices that inspect application-layer packets seem to
+ // inject TCP reset packets to break the connections when they see
+ // TLS 1.1 in ClientHello or ServerHello. See http://crbug.com/130293.
+ //
+ // Only allow ERR_CONNECTION_RESET to trigger a fallback from TLS 1.1 or
+ // 1.2. We don't lose much in this fallback because the explicit IV for CBC
+ // mode in TLS 1.1 is approximated by record splitting in TLS 1.0. The
+ // fallback will be more painful for TLS 1.2 when we have GCM support.
+ //
+ // ERR_CONNECTION_RESET is a common network error, so we don't want it
+ // to trigger a version fallback in general, especially the TLS 1.0 ->
+ // SSL 3.0 fallback, which would drop TLS extensions.
+ if (prerr == PR_CONNECT_RESET_ERROR &&
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1_1) {
+ net_error = ERR_SSL_PROTOCOL_ERROR;
+ }
+
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE);
+ } else {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ CreateNetLogSSLErrorCallback(net_error, prerr)));
+ }
+ }
+
+ return net_error;
+}
+
+int SSLClientSocketNSS::Core::DoGetDBCertComplete(int result) {
+ SECStatus rv;
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::EndEventWithNetErrorCode, weak_net_log_,
+ NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, result));
+
+ channel_id_needed_ = false;
+
+ if (result != OK)
+ return result;
+
+ SECKEYPublicKey* public_key;
+ SECKEYPrivateKey* private_key;
+ int error = ImportChannelIDKeys(&public_key, &private_key);
+ if (error != OK)
+ return error;
+
+ rv = SSL_RestartHandshakeAfterChannelIDReq(nss_fd_, public_key, private_key);
+ if (rv != SECSuccess)
+ return MapNSSError(PORT_GetError());
+
+ SetChannelIDProvided();
+ GotoState(STATE_HANDSHAKE);
+ return OK;
+}
+
+int SSLClientSocketNSS::Core::DoPayloadRead() {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK(user_read_buf_.get());
+ DCHECK_GT(user_read_buf_len_, 0);
+
+ int rv;
+ // If a previous greedy read resulted in an error that was not consumed (eg:
+ // due to the caller having read some data successfully), then return that
+ // pending error now.
+ if (pending_read_result_ != kNoPendingReadResult) {
+ rv = pending_read_result_;
+ PRErrorCode prerr = pending_read_nss_error_;
+ pending_read_result_ = kNoPendingReadResult;
+ pending_read_nss_error_ = 0;
+
+ if (rv == 0) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&LogByteTransferEvent, weak_net_log_,
+ NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED, rv,
+ scoped_refptr<IOBuffer>(user_read_buf_)));
+ } else {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, prerr)));
+ }
+ return rv;
+ }
+
+ // Perform a greedy read, attempting to read as much as the caller has
+ // requested. In the current NSS implementation, PR_Read will return
+ // exactly one SSL application data record's worth of data per invocation.
+ // The record size is dictated by the server, and may be noticeably smaller
+ // than the caller's buffer. This may be as little as a single byte, if the
+ // server is performing 1/n-1 record splitting.
+ //
+ // However, this greedy read may result in renegotiations/re-handshakes
+ // happening or may lead to some data being read, followed by an EOF (such as
+ // a TLS close-notify). If at least some data was read, then that result
+ // should be deferred until the next call to DoPayloadRead(). Otherwise, if no
+ // data was read, it's safe to return the error or EOF immediately.
+ int total_bytes_read = 0;
+ do {
+ rv = PR_Read(nss_fd_, user_read_buf_->data() + total_bytes_read,
+ user_read_buf_len_ - total_bytes_read);
+ if (rv > 0)
+ total_bytes_read += rv;
+ } while (total_bytes_read < user_read_buf_len_ && rv > 0);
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ PostOrRunCallback(FROM_HERE, base::Bind(&Core::OnNSSBufferUpdated, this,
+ amount_in_read_buffer));
+
+ if (total_bytes_read == user_read_buf_len_) {
+ // The caller's entire request was satisfied without error. No further
+ // processing needed.
+ rv = total_bytes_read;
+ } else {
+ // Otherwise, an error occurred (rv <= 0). The error needs to be handled
+ // immediately, while the NSPR/NSS errors are still available in
+ // thread-local storage. However, the handled/remapped error code should
+ // only be returned if no application data was already read; if it was, the
+ // error code should be deferred until the next call of DoPayloadRead.
+ //
+ // If no data was read, |*next_result| will point to the return value of
+ // this function. If at least some data was read, |*next_result| will point
+ // to |pending_read_error_|, to be returned in a future call to
+ // DoPayloadRead() (e.g.: after the current data is handled).
+ int* next_result = &rv;
+ if (total_bytes_read > 0) {
+ pending_read_result_ = rv;
+ rv = total_bytes_read;
+ next_result = &pending_read_result_;
+ }
+
+ if (client_auth_cert_needed_) {
+ *next_result = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ pending_read_nss_error_ = 0;
+ } else if (*next_result < 0) {
+ // If *next_result == 0, then that indicates EOF, and no special error
+ // handling is needed.
+ pending_read_nss_error_ = PR_GetError();
+ *next_result = HandleNSSError(pending_read_nss_error_, false);
+ if (rv > 0 && *next_result == ERR_IO_PENDING) {
+ // If at least some data was read from PR_Read(), do not treat
+ // insufficient data as an error to return in the next call to
+ // DoPayloadRead() - instead, let the call fall through to check
+ // PR_Read() again. This is because DoTransportIO() may complete
+ // in between the next call to DoPayloadRead(), and thus it is
+ // important to check PR_Read() on subsequent invocations to see
+ // if a complete record may now be read.
+ pending_read_nss_error_ = 0;
+ pending_read_result_ = kNoPendingReadResult;
+ }
+ }
+ }
+
+ DCHECK_NE(ERR_IO_PENDING, pending_read_result_);
+
+ if (rv >= 0) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&LogByteTransferEvent, weak_net_log_,
+ NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED, rv,
+ scoped_refptr<IOBuffer>(user_read_buf_)));
+ } else if (rv != ERR_IO_PENDING) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, pending_read_nss_error_)));
+ pending_read_nss_error_ = 0;
+ }
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoPayloadWrite() {
+ DCHECK(OnNSSTaskRunner());
+
+ DCHECK(user_write_buf_.get());
+
+ int old_amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_);
+ int new_amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // PR_Write could potentially consume the unhandled data in the memio read
+ // buffer if a renegotiation is in progress. If the buffer is consumed,
+ // notify the latest buffer size to NetworkRunner.
+ if (old_amount_in_read_buffer != new_amount_in_read_buffer) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, new_amount_in_read_buffer));
+ }
+ if (rv >= 0) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&LogByteTransferEvent, weak_net_log_,
+ NetLog::TYPE_SSL_SOCKET_BYTES_SENT, rv,
+ scoped_refptr<IOBuffer>(user_write_buf_)));
+ return rv;
+ }
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR)
+ return ERR_IO_PENDING;
+
+ rv = HandleNSSError(prerr, false);
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_WRITE_ERROR,
+ CreateNetLogSSLErrorCallback(rv, prerr)));
+ return rv;
+}
+
+// Do as much network I/O as possible between the buffer and the
+// transport socket. Return true if some I/O performed, false
+// otherwise (error or ERR_IO_PENDING).
+bool SSLClientSocketNSS::Core::DoTransportIO() {
+ DCHECK(OnNSSTaskRunner());
+
+ bool network_moved = false;
+ if (nss_bufs_ != NULL) {
+ int rv;
+ // Read and write as much data as we can. The loop is neccessary
+ // because Write() may return synchronously.
+ do {
+ rv = BufferSend();
+ if (rv != ERR_IO_PENDING && rv != 0)
+ network_moved = true;
+ } while (rv > 0);
+ if (!transport_recv_eof_ && BufferRecv() != ERR_IO_PENDING)
+ network_moved = true;
+ }
+ return network_moved;
+}
+
+int SSLClientSocketNSS::Core::BufferRecv() {
+ DCHECK(OnNSSTaskRunner());
+
+ if (transport_recv_busy_)
+ return ERR_IO_PENDING;
+
+ // If NSS is blocked on reading from |nss_bufs_|, because it is empty,
+ // determine how much data NSS wants to read. If NSS was not blocked,
+ // this will return 0.
+ int requested = memio_GetReadRequest(nss_bufs_);
+ if (requested == 0) {
+ // This is not a perfect match of error codes, as no operation is
+ // actually pending. However, returning 0 would be interpreted as a
+ // possible sign of EOF, which is also an inappropriate match.
+ return ERR_IO_PENDING;
+ }
+
+ char* buf;
+ int nb = memio_GetReadParams(nss_bufs_, &buf);
+ int rv;
+ if (!nb) {
+ // buffer too full to read into, so no I/O possible at moment
+ rv = ERR_IO_PENDING;
+ } else {
+ scoped_refptr<IOBuffer> read_buffer(new IOBuffer(nb));
+ if (OnNetworkTaskRunner()) {
+ rv = DoBufferRecv(read_buffer.get(), nb);
+ } else {
+ bool posted = network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::DoBufferRecv), this, read_buffer,
+ nb));
+ rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ transport_recv_busy_ = true;
+ } else {
+ if (rv > 0) {
+ memcpy(buf, read_buffer->data(), rv);
+ } else if (rv == 0) {
+ transport_recv_eof_ = true;
+ }
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
+ }
+ }
+ return rv;
+}
+
+// Return 0 if nss_bufs_ was empty,
+// > 0 for bytes transferred immediately,
+// < 0 for error (or the non-error ERR_IO_PENDING).
+int SSLClientSocketNSS::Core::BufferSend() {
+ DCHECK(OnNSSTaskRunner());
+
+ if (transport_send_busy_)
+ return ERR_IO_PENDING;
+
+ const char* buf1;
+ const char* buf2;
+ unsigned int len1, len2;
+ memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2);
+ const unsigned int len = len1 + len2;
+
+ int rv = 0;
+ if (len) {
+ scoped_refptr<IOBuffer> send_buffer(new IOBuffer(len));
+ memcpy(send_buffer->data(), buf1, len1);
+ memcpy(send_buffer->data() + len1, buf2, len2);
+
+ if (OnNetworkTaskRunner()) {
+ rv = DoBufferSend(send_buffer.get(), len);
+ } else {
+ bool posted = network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(IgnoreResult(&Core::DoBufferSend), this, send_buffer,
+ len));
+ rv = posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ transport_send_busy_ = true;
+ } else {
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
+ }
+ }
+
+ return rv;
+}
+
+void SSLClientSocketNSS::Core::OnRecvComplete(int result) {
+ DCHECK(OnNSSTaskRunner());
+
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ // Network layer received some data, check if client requested to read
+ // decrypted data.
+ if (!user_read_buf_.get())
+ return;
+
+ int rv = DoReadLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoReadCallback(rv);
+}
+
+void SSLClientSocketNSS::Core::OnSendComplete(int result) {
+ DCHECK(OnNSSTaskRunner());
+
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ // OnSendComplete may need to call DoPayloadRead while the renegotiation
+ // handshake is in progress.
+ int rv_read = ERR_IO_PENDING;
+ int rv_write = ERR_IO_PENDING;
+ bool network_moved;
+ do {
+ if (user_read_buf_.get())
+ rv_read = DoPayloadRead();
+ if (user_write_buf_.get())
+ rv_write = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
+ (user_read_buf_.get() || user_write_buf_.get()) && network_moved);
+
+ // If the parent SSLClientSocketNSS is deleted during the processing of the
+ // Read callback and OnNSSTaskRunner() == OnNetworkTaskRunner(), then the Core
+ // will be detached (and possibly deleted). Guard against deletion by taking
+ // an extra reference, then check if the Core was detached before invoking the
+ // next callback.
+ scoped_refptr<Core> guard(this);
+ if (user_read_buf_.get() && rv_read != ERR_IO_PENDING)
+ DoReadCallback(rv_read);
+
+ if (OnNetworkTaskRunner() && detached_)
+ return;
+
+ if (user_write_buf_.get() && rv_write != ERR_IO_PENDING)
+ DoWriteCallback(rv_write);
+}
+
+// As part of Connect(), the SSLClientSocketNSS object performs an SSL
+// handshake. This requires network IO, which in turn calls
+// BufferRecvComplete() with a non-zero byte count. This byte count eventually
+// winds its way through the state machine and ends up being passed to the
+// callback. For Read() and Write(), that's what we want. But for Connect(),
+// the caller expects OK (i.e. 0) for success.
+void SSLClientSocketNSS::Core::DoConnectCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!user_connect_callback_.is_null());
+
+ base::Closure c = base::Bind(
+ base::ResetAndReturn(&user_connect_callback_),
+ rv > OK ? OK : rv);
+ PostOrRunCallback(FROM_HERE, c);
+}
+
+void SSLClientSocketNSS::Core::DoReadCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_read_callback_.is_null());
+
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // This is used to curry the |amount_int_read_buffer| and |user_cb| back to
+ // the network task runner.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, amount_in_read_buffer));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::DidNSSRead, this, rv));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(base::ResetAndReturn(&user_read_callback_), rv));
+}
+
+void SSLClientSocketNSS::Core::DoWriteCallback(int rv) {
+ DCHECK(OnNSSTaskRunner());
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_write_callback_.is_null());
+
+ // Since Run may result in Write being called, clear |user_write_callback_|
+ // up front.
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ // Update buffer status because DoWriteLoop called DoTransportIO which may
+ // perform read operations.
+ int amount_in_read_buffer = memio_GetReadableBufferSize(nss_bufs_);
+ // This is used to curry the |amount_int_read_buffer| and |user_cb| back to
+ // the network task runner.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::OnNSSBufferUpdated, this, amount_in_read_buffer));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::DidNSSWrite, this, rv));
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(base::ResetAndReturn(&user_write_callback_), rv));
+}
+
+SECStatus SSLClientSocketNSS::Core::ClientChannelIDHandler(
+ void* arg,
+ PRFileDesc* socket,
+ SECKEYPublicKey **out_public_key,
+ SECKEYPrivateKey **out_private_key) {
+ Core* core = reinterpret_cast<Core*>(arg);
+ DCHECK(core->OnNSSTaskRunner());
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEvent, core->weak_net_log_,
+ NetLog::TYPE_SSL_CHANNEL_ID_REQUESTED));
+
+ // We have negotiated the TLS channel ID extension.
+ core->channel_id_xtn_negotiated_ = true;
+ std::string host = core->host_and_port_.host();
+ int error = ERR_UNEXPECTED;
+ if (core->OnNetworkTaskRunner()) {
+ error = core->DoGetDomainBoundCert(host);
+ } else {
+ bool posted = core->network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ IgnoreResult(&Core::DoGetDomainBoundCert),
+ core, host));
+ error = posted ? ERR_IO_PENDING : ERR_ABORTED;
+ }
+
+ if (error == ERR_IO_PENDING) {
+ // Asynchronous case.
+ core->channel_id_needed_ = true;
+ return SECWouldBlock;
+ }
+
+ core->PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&BoundNetLog::EndEventWithNetErrorCode, core->weak_net_log_,
+ NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT, error));
+ SECStatus rv = SECSuccess;
+ if (error == OK) {
+ // Synchronous success.
+ int result = core->ImportChannelIDKeys(out_public_key, out_private_key);
+ if (result == OK)
+ core->SetChannelIDProvided();
+ else
+ rv = SECFailure;
+ } else {
+ rv = SECFailure;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::ImportChannelIDKeys(SECKEYPublicKey** public_key,
+ SECKEYPrivateKey** key) {
+ // Set the certificate.
+ SECItem cert_item;
+ cert_item.data = (unsigned char*) domain_bound_cert_.data();
+ cert_item.len = domain_bound_cert_.size();
+ ScopedCERTCertificate cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+ &cert_item,
+ NULL,
+ PR_FALSE,
+ PR_TRUE));
+ if (cert == NULL)
+ return MapNSSError(PORT_GetError());
+
+ // Set the private key.
+ if (!crypto::ECPrivateKey::ImportFromEncryptedPrivateKeyInfo(
+ ServerBoundCertService::kEPKIPassword,
+ reinterpret_cast<const unsigned char*>(
+ domain_bound_private_key_.data()),
+ domain_bound_private_key_.size(),
+ &cert->subjectPublicKeyInfo,
+ false,
+ false,
+ key,
+ public_key)) {
+ int error = MapNSSError(PORT_GetError());
+ return error;
+ }
+
+ return OK;
+}
+
+void SSLClientSocketNSS::Core::UpdateServerCert() {
+ nss_handshake_state_.server_cert_chain.Reset(nss_fd_);
+ nss_handshake_state_.server_cert = X509Certificate::CreateFromDERCertChain(
+ nss_handshake_state_.server_cert_chain.AsStringPieceVector());
+ if (nss_handshake_state_.server_cert.get()) {
+ // Since this will be called asynchronously on another thread, it needs to
+ // own a reference to the certificate.
+ NetLog::ParametersCallback net_log_callback =
+ base::Bind(&NetLogX509CertificateCallback,
+ nss_handshake_state_.server_cert);
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
+ net_log_callback));
+ }
+}
+
+void SSLClientSocketNSS::Core::UpdateConnectionStatus() {
+ SSLChannelInfo channel_info;
+ SECStatus ok = SSL_GetChannelInfo(nss_fd_,
+ &channel_info, sizeof(channel_info));
+ if (ok == SECSuccess &&
+ channel_info.length == sizeof(channel_info) &&
+ channel_info.cipherSuite) {
+ nss_handshake_state_.ssl_connection_status |=
+ (static_cast<int>(channel_info.cipherSuite) &
+ SSL_CONNECTION_CIPHERSUITE_MASK) <<
+ SSL_CONNECTION_CIPHERSUITE_SHIFT;
+
+ nss_handshake_state_.ssl_connection_status |=
+ (static_cast<int>(channel_info.compressionMethod) &
+ SSL_CONNECTION_COMPRESSION_MASK) <<
+ SSL_CONNECTION_COMPRESSION_SHIFT;
+
+ // NSS 3.14.x doesn't have a version macro for TLS 1.2 (because NSS didn't
+ // support it yet), so use 0x0303 directly.
+ int version = SSL_CONNECTION_VERSION_UNKNOWN;
+ if (channel_info.protocolVersion < SSL_LIBRARY_VERSION_3_0) {
+ // All versions less than SSL_LIBRARY_VERSION_3_0 are treated as SSL
+ // version 2.
+ version = SSL_CONNECTION_VERSION_SSL2;
+ } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_0) {
+ version = SSL_CONNECTION_VERSION_SSL3;
+ } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_1_TLS) {
+ version = SSL_CONNECTION_VERSION_TLS1;
+ } else if (channel_info.protocolVersion == SSL_LIBRARY_VERSION_TLS_1_1) {
+ version = SSL_CONNECTION_VERSION_TLS1_1;
+ } else if (channel_info.protocolVersion == 0x0303) {
+ version = SSL_CONNECTION_VERSION_TLS1_2;
+ }
+ nss_handshake_state_.ssl_connection_status |=
+ (version & SSL_CONNECTION_VERSION_MASK) <<
+ SSL_CONNECTION_VERSION_SHIFT;
+ }
+
+ PRBool peer_supports_renego_ext;
+ ok = SSL_HandshakeNegotiatedExtension(nss_fd_, ssl_renegotiation_info_xtn,
+ &peer_supports_renego_ext);
+ if (ok == SECSuccess) {
+ if (!peer_supports_renego_ext) {
+ nss_handshake_state_.ssl_connection_status |=
+ SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
+ // Log an informational message if the server does not support secure
+ // renegotiation (RFC 5746).
+ VLOG(1) << "The server " << host_and_port_.ToString()
+ << " does not support the TLS renegotiation_info extension.";
+ }
+ UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported",
+ peer_supports_renego_ext, 2);
+
+ // We would like to eliminate fallback to SSLv3 for non-buggy servers
+ // because of security concerns. For example, Google offers forward
+ // secrecy with ECDHE but that requires TLS 1.0. An attacker can block
+ // TLSv1 connections and force us to downgrade to SSLv3 and remove forward
+ // secrecy.
+ //
+ // Yngve from Opera has suggested using the renegotiation extension as an
+ // indicator that SSLv3 fallback was mistaken:
+ // tools.ietf.org/html/draft-pettersen-tls-version-rollback-removal-00 .
+ //
+ // As a first step, measure how often clients perform version fallback
+ // while the server advertises support secure renegotiation.
+ if (ssl_config_.version_fallback &&
+ channel_info.protocolVersion == SSL_LIBRARY_VERSION_3_0) {
+ UMA_HISTOGRAM_BOOLEAN("Net.SSLv3FallbackToRenegoPatchedServer",
+ peer_supports_renego_ext == PR_TRUE);
+ }
+ }
+
+ if (ssl_config_.version_fallback) {
+ nss_handshake_state_.ssl_connection_status |=
+ SSL_CONNECTION_VERSION_FALLBACK;
+ }
+}
+
+void SSLClientSocketNSS::Core::UpdateNextProto() {
+ uint8 buf[256];
+ SSLNextProtoState state;
+ unsigned buf_len;
+
+ SECStatus rv = SSL_GetNextProto(nss_fd_, &state, buf, &buf_len, sizeof(buf));
+ if (rv != SECSuccess)
+ return;
+
+ nss_handshake_state_.next_proto =
+ std::string(reinterpret_cast<char*>(buf), buf_len);
+ switch (state) {
+ case SSL_NEXT_PROTO_NEGOTIATED:
+ case SSL_NEXT_PROTO_SELECTED:
+ nss_handshake_state_.next_proto_status = kNextProtoNegotiated;
+ break;
+ case SSL_NEXT_PROTO_NO_OVERLAP:
+ nss_handshake_state_.next_proto_status = kNextProtoNoOverlap;
+ break;
+ case SSL_NEXT_PROTO_NO_SUPPORT:
+ nss_handshake_state_.next_proto_status = kNextProtoUnsupported;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void SSLClientSocketNSS::Core::RecordChannelIDSupport() {
+ DCHECK(OnNSSTaskRunner());
+ if (nss_handshake_state_.resumed_handshake)
+ return;
+
+ // Copy the NSS task runner-only state to the network task runner and
+ // log histograms from there, since the histograms also need access to the
+ // network task runner state.
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&Core::RecordChannelIDSupportOnNetworkTaskRunner,
+ this,
+ channel_id_xtn_negotiated_,
+ ssl_config_.channel_id_enabled,
+ crypto::ECPrivateKey::IsSupported()));
+}
+
+void SSLClientSocketNSS::Core::RecordChannelIDSupportOnNetworkTaskRunner(
+ bool negotiated_channel_id,
+ bool channel_id_enabled,
+ bool supports_ecc) const {
+ DCHECK(OnNetworkTaskRunner());
+
+ // Since this enum is used for a histogram, do not change or re-use values.
+ enum {
+ DISABLED = 0,
+ CLIENT_ONLY = 1,
+ CLIENT_AND_SERVER = 2,
+ CLIENT_NO_ECC = 3,
+ CLIENT_BAD_SYSTEM_TIME = 4,
+ CLIENT_NO_SERVER_BOUND_CERT_SERVICE = 5,
+ DOMAIN_BOUND_CERT_USAGE_MAX
+ } supported = DISABLED;
+ if (negotiated_channel_id) {
+ supported = CLIENT_AND_SERVER;
+ } else if (channel_id_enabled) {
+ if (!server_bound_cert_service_)
+ supported = CLIENT_NO_SERVER_BOUND_CERT_SERVICE;
+ else if (!supports_ecc)
+ supported = CLIENT_NO_ECC;
+ else if (!server_bound_cert_service_->IsSystemTimeValid())
+ supported = CLIENT_BAD_SYSTEM_TIME;
+ else
+ supported = CLIENT_ONLY;
+ }
+ UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.Support", supported,
+ DOMAIN_BOUND_CERT_USAGE_MAX);
+}
+
+int SSLClientSocketNSS::Core::DoBufferRecv(IOBuffer* read_buffer, int len) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK_GT(len, 0);
+
+ if (detached_)
+ return ERR_ABORTED;
+
+ int rv = transport_->socket()->Read(
+ read_buffer, len,
+ base::Bind(&Core::BufferRecvComplete, base::Unretained(this),
+ scoped_refptr<IOBuffer>(read_buffer)));
+
+ if (!OnNSSTaskRunner() && rv != ERR_IO_PENDING) {
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferRecvComplete, this,
+ scoped_refptr<IOBuffer>(read_buffer), rv));
+ return rv;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoBufferSend(IOBuffer* send_buffer, int len) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK_GT(len, 0);
+
+ if (detached_)
+ return ERR_ABORTED;
+
+ int rv = transport_->socket()->Write(
+ send_buffer, len,
+ base::Bind(&Core::BufferSendComplete,
+ base::Unretained(this)));
+
+ if (!OnNSSTaskRunner() && rv != ERR_IO_PENDING) {
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::BufferSendComplete, this, rv));
+ return rv;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Core::DoGetDomainBoundCert(const std::string& host) {
+ DCHECK(OnNetworkTaskRunner());
+
+ if (detached_)
+ return ERR_FAILED;
+
+ weak_net_log_->BeginEvent(NetLog::TYPE_SSL_GET_DOMAIN_BOUND_CERT);
+
+ int rv = server_bound_cert_service_->GetOrCreateDomainBoundCert(
+ host,
+ &domain_bound_private_key_,
+ &domain_bound_cert_,
+ base::Bind(&Core::OnGetDomainBoundCertComplete, base::Unretained(this)),
+ &domain_bound_cert_request_handle_);
+
+ if (rv != ERR_IO_PENDING && !OnNSSTaskRunner()) {
+ nss_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::OnHandshakeIOComplete, this, rv));
+ return ERR_IO_PENDING;
+ }
+
+ return rv;
+}
+
+void SSLClientSocketNSS::Core::OnHandshakeStateUpdated(
+ const HandshakeState& state) {
+ DCHECK(OnNetworkTaskRunner());
+ network_handshake_state_ = state;
+}
+
+void SSLClientSocketNSS::Core::OnNSSBufferUpdated(int amount_in_read_buffer) {
+ DCHECK(OnNetworkTaskRunner());
+ unhandled_buffer_size_ = amount_in_read_buffer;
+}
+
+void SSLClientSocketNSS::Core::DidNSSRead(int result) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(nss_waiting_read_);
+ nss_waiting_read_ = false;
+ if (result <= 0)
+ nss_is_closed_ = true;
+}
+
+void SSLClientSocketNSS::Core::DidNSSWrite(int result) {
+ DCHECK(OnNetworkTaskRunner());
+ DCHECK(nss_waiting_write_);
+ nss_waiting_write_ = false;
+ if (result < 0)
+ nss_is_closed_ = true;
+}
+
+void SSLClientSocketNSS::Core::BufferSendComplete(int result) {
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferSendComplete, this, result));
+ return;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result));
+ transport_send_busy_ = false;
+ OnSendComplete(result);
+}
+
+void SSLClientSocketNSS::Core::OnHandshakeIOComplete(int result) {
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::OnHandshakeIOComplete, this, result));
+ return;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoConnectCallback(rv);
+}
+
+void SSLClientSocketNSS::Core::OnGetDomainBoundCertComplete(int result) {
+ DVLOG(1) << __FUNCTION__ << " " << result;
+ DCHECK(OnNetworkTaskRunner());
+
+ OnHandshakeIOComplete(result);
+}
+
+void SSLClientSocketNSS::Core::BufferRecvComplete(
+ IOBuffer* read_buffer,
+ int result) {
+ DCHECK(read_buffer);
+
+ if (!OnNSSTaskRunner()) {
+ if (detached_)
+ return;
+
+ nss_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&Core::BufferRecvComplete, this,
+ scoped_refptr<IOBuffer>(read_buffer), result));
+ return;
+ }
+
+ DCHECK(OnNSSTaskRunner());
+
+ if (result > 0) {
+ char* buf;
+ int nb = memio_GetReadParams(nss_bufs_, &buf);
+ CHECK_GE(nb, result);
+ memcpy(buf, read_buffer->data(), result);
+ } else if (result == 0) {
+ transport_recv_eof_ = true;
+ }
+
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(result));
+ transport_recv_busy_ = false;
+ OnRecvComplete(result);
+}
+
+void SSLClientSocketNSS::Core::PostOrRunCallback(
+ const tracked_objects::Location& location,
+ const base::Closure& task) {
+ if (!OnNetworkTaskRunner()) {
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&Core::PostOrRunCallback, this, location, task));
+ return;
+ }
+
+ if (detached_ || task.is_null())
+ return;
+ task.Run();
+}
+
+void SSLClientSocketNSS::Core::AddCertProvidedEvent(int cert_count) {
+ PostOrRunCallback(
+ FROM_HERE,
+ base::Bind(&AddLogEventWithCallback, weak_net_log_,
+ NetLog::TYPE_SSL_CLIENT_CERT_PROVIDED,
+ NetLog::IntegerCallback("cert_count", cert_count)));
+}
+
+void SSLClientSocketNSS::Core::SetChannelIDProvided() {
+ PostOrRunCallback(
+ FROM_HERE, base::Bind(&AddLogEvent, weak_net_log_,
+ NetLog::TYPE_SSL_CHANNEL_ID_PROVIDED));
+ nss_handshake_state_.channel_id_sent = true;
+ // Update the network task runner's view of the handshake state now that
+ // channel id has been sent.
+ PostOrRunCallback(
+ FROM_HERE, base::Bind(&Core::OnHandshakeStateUpdated, this,
+ nss_handshake_state_));
+}
+
+SSLClientSocketNSS::SSLClientSocketNSS(
+ base::SequencedTaskRunner* nss_task_runner,
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context)
+ : nss_task_runner_(nss_task_runner),
+ transport_(transport_socket.Pass()),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ cert_verifier_(context.cert_verifier),
+ server_bound_cert_service_(context.server_bound_cert_service),
+ ssl_session_cache_shard_(context.ssl_session_cache_shard),
+ completed_handshake_(false),
+ next_handshake_state_(STATE_NONE),
+ nss_fd_(NULL),
+ net_log_(transport_->socket()->NetLog()),
+ transport_security_state_(context.transport_security_state),
+ valid_thread_id_(base::kInvalidThreadId) {
+ EnterFunction("");
+ InitCore();
+ LeaveFunction("");
+}
+
+SSLClientSocketNSS::~SSLClientSocketNSS() {
+ EnterFunction("");
+ Disconnect();
+ LeaveFunction("");
+}
+
+// static
+void SSLClientSocket::ClearSessionCache() {
+ // SSL_ClearSessionCache can't be called before NSS is initialized. Don't
+ // bother initializing NSS just to clear an empty SSL session cache.
+ if (!NSS_IsInitialized())
+ return;
+
+ SSL_ClearSessionCache();
+}
+
+bool SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
+ EnterFunction("");
+ ssl_info->Reset();
+ if (core_->state().server_cert_chain.empty() ||
+ !core_->state().server_cert_chain[0]) {
+ return false;
+ }
+
+ ssl_info->cert_status = server_cert_verify_result_.cert_status;
+ ssl_info->cert = server_cert_verify_result_.verified_cert;
+ ssl_info->connection_status =
+ core_->state().ssl_connection_status;
+ ssl_info->public_key_hashes = server_cert_verify_result_.public_key_hashes;
+ for (HashValueVector::const_iterator i = side_pinned_public_keys_.begin();
+ i != side_pinned_public_keys_.end(); ++i) {
+ ssl_info->public_key_hashes.push_back(*i);
+ }
+ ssl_info->is_issued_by_known_root =
+ server_cert_verify_result_.is_issued_by_known_root;
+ ssl_info->client_cert_sent =
+ ssl_config_.send_client_cert && ssl_config_.client_cert.get();
+ ssl_info->channel_id_sent = WasChannelIDSent();
+
+ PRUint16 cipher_suite = SSLConnectionStatusToCipherSuite(
+ core_->state().ssl_connection_status);
+ SSLCipherSuiteInfo cipher_info;
+ SECStatus ok = SSL_GetCipherSuiteInfo(cipher_suite,
+ &cipher_info, sizeof(cipher_info));
+ if (ok == SECSuccess) {
+ ssl_info->security_bits = cipher_info.effectiveKeyBits;
+ } else {
+ ssl_info->security_bits = -1;
+ LOG(DFATAL) << "SSL_GetCipherSuiteInfo returned " << PR_GetError()
+ << " for cipherSuite " << cipher_suite;
+ }
+
+ ssl_info->handshake_type = core_->state().resumed_handshake ?
+ SSLInfo::HANDSHAKE_RESUME : SSLInfo::HANDSHAKE_FULL;
+
+ LeaveFunction("");
+ return true;
+}
+
+void SSLClientSocketNSS::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ EnterFunction("");
+ // TODO(rch): switch SSLCertRequestInfo.host_and_port to a HostPortPair
+ cert_request_info->host_and_port = host_and_port_.ToString();
+ cert_request_info->cert_authorities = core_->state().cert_authorities;
+ LeaveFunction("");
+}
+
+int SSLClientSocketNSS::ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ // SSL_ExportKeyingMaterial may block the current thread if |core_| is in
+ // the midst of a handshake.
+ SECStatus result = SSL_ExportKeyingMaterial(
+ nss_fd_, label.data(), label.size(), has_context,
+ reinterpret_cast<const unsigned char*>(context.data()),
+ context.length(), out, outlen);
+ if (result != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_ExportKeyingMaterial", "");
+ return MapNSSError(PORT_GetError());
+ }
+ return OK;
+}
+
+int SSLClientSocketNSS::GetTLSUniqueChannelBinding(std::string* out) {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ unsigned char buf[64];
+ unsigned int len;
+ SECStatus result = SSL_GetChannelBinding(nss_fd_,
+ SSL_CHANNEL_BINDING_TLS_UNIQUE,
+ buf, &len, arraysize(buf));
+ if (result != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_GetChannelBinding", "");
+ return MapNSSError(PORT_GetError());
+ }
+ out->assign(reinterpret_cast<char*>(buf), len);
+ return OK;
+}
+
+SSLClientSocket::NextProtoStatus
+SSLClientSocketNSS::GetNextProto(std::string* proto,
+ std::string* server_protos) {
+ *proto = core_->state().next_proto;
+ *server_protos = core_->state().server_protos;
+ return core_->state().next_proto_status;
+}
+
+int SSLClientSocketNSS::Connect(const CompletionCallback& callback) {
+ EnterFunction("");
+ DCHECK(transport_.get());
+ // It is an error to create an SSLClientSocket whose context has no
+ // TransportSecurityState.
+ DCHECK(transport_security_state_);
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ DCHECK(user_connect_callback_.is_null());
+ DCHECK(!callback.is_null());
+
+ EnsureThreadIdAssigned();
+
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT);
+
+ int rv = Init();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
+ }
+
+ rv = InitializeSSLOptions();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
+ }
+
+ rv = InitializeSSLPeerName();
+ if (rv != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ return rv;
+ }
+
+ GotoState(STATE_HANDSHAKE);
+
+ rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_connect_callback_ = callback;
+ } else {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ }
+
+ LeaveFunction("");
+ return rv > OK ? OK : rv;
+}
+
+void SSLClientSocketNSS::Disconnect() {
+ EnterFunction("");
+
+ CHECK(CalledOnValidThread());
+
+ // Shut down anything that may call us back.
+ core_->Detach();
+ verifier_.reset();
+ transport_->socket()->Disconnect();
+
+ // Reset object state.
+ user_connect_callback_.Reset();
+ server_cert_verify_result_.Reset();
+ completed_handshake_ = false;
+ start_cert_verification_time_ = base::TimeTicks();
+ InitCore();
+
+ LeaveFunction("");
+}
+
+bool SSLClientSocketNSS::IsConnected() const {
+ EnterFunction("");
+ bool ret = completed_handshake_ &&
+ (core_->HasPendingAsyncOperation() ||
+ (core_->IsConnected() && core_->HasUnhandledReceivedData()) ||
+ transport_->socket()->IsConnected());
+ LeaveFunction("");
+ return ret;
+}
+
+bool SSLClientSocketNSS::IsConnectedAndIdle() const {
+ EnterFunction("");
+ bool ret = completed_handshake_ &&
+ !core_->HasPendingAsyncOperation() &&
+ !(core_->IsConnected() && core_->HasUnhandledReceivedData()) &&
+ transport_->socket()->IsConnectedAndIdle();
+ LeaveFunction("");
+ return ret;
+}
+
+int SSLClientSocketNSS::GetPeerAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetPeerAddress(address);
+}
+
+int SSLClientSocketNSS::GetLocalAddress(IPEndPoint* address) const {
+ return transport_->socket()->GetLocalAddress(address);
+}
+
+const BoundNetLog& SSLClientSocketNSS::NetLog() const {
+ return net_log_;
+}
+
+void SSLClientSocketNSS::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void SSLClientSocketNSS::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SSLClientSocketNSS::WasEverUsed() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->WasEverUsed();
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SSLClientSocketNSS::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket()) {
+ return transport_->socket()->UsingTCPFastOpen();
+ }
+ NOTREACHED();
+ return false;
+}
+
+int SSLClientSocketNSS::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(core_.get());
+ DCHECK(!callback.is_null());
+
+ EnterFunction(buf_len);
+ int rv = core_->Read(buf, buf_len, callback);
+ LeaveFunction(rv);
+
+ return rv;
+}
+
+int SSLClientSocketNSS::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(core_.get());
+ DCHECK(!callback.is_null());
+
+ EnterFunction(buf_len);
+ int rv = core_->Write(buf, buf_len, callback);
+ LeaveFunction(rv);
+
+ return rv;
+}
+
+bool SSLClientSocketNSS::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool SSLClientSocketNSS::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+int SSLClientSocketNSS::Init() {
+ EnterFunction("");
+ // Initialize the NSS SSL library in a threadsafe way. This also
+ // initializes the NSS base library.
+ EnsureNSSSSLInit();
+ if (!NSS_IsInitialized())
+ return ERR_UNEXPECTED;
+#if defined(USE_NSS) || defined(OS_IOS)
+ if (ssl_config_.cert_io_enabled) {
+ // We must call EnsureNSSHttpIOInit() here, on the IO thread, to get the IO
+ // loop by MessageLoopForIO::current().
+ // X509Certificate::Verify() runs on a worker thread of CertVerifier.
+ EnsureNSSHttpIOInit();
+ }
+#endif
+
+ LeaveFunction("");
+ return OK;
+}
+
+void SSLClientSocketNSS::InitCore() {
+ core_ = new Core(base::ThreadTaskRunnerHandle::Get().get(),
+ nss_task_runner_.get(),
+ transport_.get(),
+ host_and_port_,
+ ssl_config_,
+ &net_log_,
+ server_bound_cert_service_);
+}
+
+int SSLClientSocketNSS::InitializeSSLOptions() {
+ // Transport connected, now hook it up to nss
+ nss_fd_ = memio_CreateIOLayer(kRecvBufferSize, kSendBufferSize);
+ if (nss_fd_ == NULL) {
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code.
+ }
+
+ // Grab pointer to buffers
+ memio_Private* nss_bufs = memio_GetSecret(nss_fd_);
+
+ /* Create SSL state machine */
+ /* Push SSL onto our fake I/O socket */
+ nss_fd_ = SSL_ImportFD(NULL, nss_fd_);
+ if (nss_fd_ == NULL) {
+ LogFailedNSSFunction(net_log_, "SSL_ImportFD", "");
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code.
+ }
+ // TODO(port): set more ssl options! Check errors!
+
+ int rv;
+
+ rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
+ return ERR_UNEXPECTED;
+ }
+
+ // Don't do V2 compatible hellos because they don't support TLS extensions.
+ rv = SSL_OptionSet(nss_fd_, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_V2_COMPATIBLE_HELLO");
+ return ERR_UNEXPECTED;
+ }
+
+ SSLVersionRange version_range;
+ version_range.min = ssl_config_.version_min;
+ version_range.max = ssl_config_.version_max;
+ rv = SSL_VersionRangeSet(nss_fd_, &version_range);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_VersionRangeSet", "");
+ return ERR_NO_SSL_VERSIONS_ENABLED;
+ }
+
+ for (std::vector<uint16>::const_iterator it =
+ ssl_config_.disabled_cipher_suites.begin();
+ it != ssl_config_.disabled_cipher_suites.end(); ++it) {
+ // This will fail if the specified cipher is not implemented by NSS, but
+ // the failure is harmless.
+ SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE);
+ }
+
+ // Support RFC 5077
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS");
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_FALSE_START,
+ ssl_config_.false_start_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_FALSE_START");
+
+ // We allow servers to request renegotiation. Since we're a client,
+ // prohibiting this is rather a waste of time. Only servers are in a
+ // position to prevent renegotiation attacks.
+ // http://extendedsubset.com/?p=8
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_RENEGOTIATION,
+ SSL_RENEGOTIATE_TRANSITIONAL);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_RENEGOTIATION");
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_CBC_RANDOM_IV, PR_TRUE);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_CBC_RANDOM_IV");
+
+// Added in NSS 3.15
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if (IsOCSPStaplingSupported()) {
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_OCSP_STAPLING, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet",
+ "SSL_ENABLE_OCSP_STAPLING");
+ }
+ }
+#endif
+
+// Chromium patch to libssl
+#ifdef SSL_ENABLE_CACHED_INFO
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_CACHED_INFO,
+ ssl_config_.cached_info_enabled);
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_CACHED_INFO");
+#endif
+
+ rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_HANDSHAKE_AS_CLIENT");
+ return ERR_UNEXPECTED;
+ }
+
+ if (!core_->Init(nss_fd_, nss_bufs))
+ return ERR_UNEXPECTED;
+
+ // Tell SSL the hostname we're trying to connect to.
+ SSL_SetURL(nss_fd_, host_and_port_.host().c_str());
+
+ // Tell SSL we're a client; needed if not letting NSPR do socket I/O
+ SSL_ResetHandshake(nss_fd_, PR_FALSE);
+
+ return OK;
+}
+
+int SSLClientSocketNSS::InitializeSSLPeerName() {
+ // Tell NSS who we're connected to
+ IPEndPoint peer_address;
+ int err = transport_->socket()->GetPeerAddress(&peer_address);
+ if (err != OK)
+ return err;
+
+ SockaddrStorage storage;
+ if (!peer_address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_UNEXPECTED;
+
+ PRNetAddr peername;
+ memset(&peername, 0, sizeof(peername));
+ DCHECK_LE(static_cast<size_t>(storage.addr_len), sizeof(peername));
+ size_t len = std::min(static_cast<size_t>(storage.addr_len),
+ sizeof(peername));
+ memcpy(&peername, storage.addr, len);
+
+ // Adjust the address family field for BSD, whose sockaddr
+ // structure has a one-byte length and one-byte address family
+ // field at the beginning. PRNetAddr has a two-byte address
+ // family field at the beginning.
+ peername.raw.family = storage.addr->sa_family;
+
+ memio_SetPeerName(nss_fd_, &peername);
+
+ // Set the peer ID for session reuse. This is necessary when we create an
+ // SSL tunnel through a proxy -- GetPeerName returns the proxy's address
+ // rather than the destination server's address in that case.
+ std::string peer_id = host_and_port_.ToString();
+ // If the ssl_session_cache_shard_ is non-empty, we append it to the peer id.
+ // This will cause session cache misses between sockets with different values
+ // of ssl_session_cache_shard_ and this is used to partition the session cache
+ // for incognito mode.
+ if (!ssl_session_cache_shard_.empty()) {
+ peer_id += "/" + ssl_session_cache_shard_;
+ }
+ SECStatus rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str()));
+ if (rv != SECSuccess)
+ LogFailedNSSFunction(net_log_, "SSL_SetSockPeerID", peer_id.c_str());
+
+ return OK;
+}
+
+void SSLClientSocketNSS::DoConnectCallback(int rv) {
+ EnterFunction(rv);
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!user_connect_callback_.is_null());
+
+ base::ResetAndReturn(&user_connect_callback_).Run(rv > OK ? OK : rv);
+ LeaveFunction("");
+}
+
+void SSLClientSocketNSS::OnHandshakeIOComplete(int result) {
+ EnterFunction(result);
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ DoConnectCallback(rv);
+ }
+ LeaveFunction("");
+}
+
+int SSLClientSocketNSS::DoHandshakeLoop(int last_io_result) {
+ EnterFunction(last_io_result);
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ // (This is a quirk carried over from the windows
+ // implementation. It makes reading the logs a bit harder.)
+ // State handlers can and often do call GotoState just
+ // to stay in the current state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+ switch (state) {
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_HANDSHAKE_COMPLETE:
+ rv = DoHandshakeComplete(rv);
+ break;
+ case STATE_VERIFY_CERT:
+ DCHECK(rv == OK);
+ rv = DoVerifyCert(rv);
+ break;
+ case STATE_VERIFY_CERT_COMPLETE:
+ rv = DoVerifyCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ LeaveFunction("");
+ return rv;
+}
+
+int SSLClientSocketNSS::DoHandshake() {
+ EnterFunction("");
+ int rv = core_->Connect(
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)));
+ GotoState(STATE_HANDSHAKE_COMPLETE);
+
+ LeaveFunction(rv);
+ return rv;
+}
+
+int SSLClientSocketNSS::DoHandshakeComplete(int result) {
+ EnterFunction(result);
+
+ if (result == OK) {
+ // SSL handshake is completed. Let's verify the certificate.
+ GotoState(STATE_VERIFY_CERT);
+ // Done!
+ }
+ set_channel_id_sent(core_->state().channel_id_sent);
+
+ LeaveFunction(result);
+ return result;
+}
+
+
+int SSLClientSocketNSS::DoVerifyCert(int result) {
+ DCHECK(!core_->state().server_cert_chain.empty());
+ DCHECK(core_->state().server_cert_chain[0]);
+
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+
+ // If the certificate is expected to be bad we can use the expectation as
+ // the cert status.
+ base::StringPiece der_cert(
+ reinterpret_cast<char*>(
+ core_->state().server_cert_chain[0]->derCert.data),
+ core_->state().server_cert_chain[0]->derCert.len);
+ CertStatus cert_status;
+ if (ssl_config_.IsAllowedBadCert(der_cert, &cert_status)) {
+ DCHECK(start_cert_verification_time_.is_null());
+ VLOG(1) << "Received an expected bad cert with status: " << cert_status;
+ server_cert_verify_result_.Reset();
+ server_cert_verify_result_.cert_status = cert_status;
+ server_cert_verify_result_.verified_cert = core_->state().server_cert;
+ return OK;
+ }
+
+ // We may have failed to create X509Certificate object if we are
+ // running inside sandbox.
+ if (!core_->state().server_cert.get()) {
+ server_cert_verify_result_.Reset();
+ server_cert_verify_result_.cert_status = CERT_STATUS_INVALID;
+ return ERR_CERT_INVALID;
+ }
+
+ start_cert_verification_time_ = base::TimeTicks::Now();
+
+ int flags = 0;
+ if (ssl_config_.rev_checking_enabled)
+ flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED;
+ if (ssl_config_.verify_ev_cert)
+ flags |= CertVerifier::VERIFY_EV_CERT;
+ if (ssl_config_.cert_io_enabled)
+ flags |= CertVerifier::VERIFY_CERT_IO_ENABLED;
+ if (ssl_config_.rev_checking_required_local_anchors)
+ flags |= CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
+ verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
+ return verifier_->Verify(
+ core_->state().server_cert.get(),
+ host_and_port_.host(),
+ flags,
+ SSLConfigService::GetCRLSet().get(),
+ &server_cert_verify_result_,
+ base::Bind(&SSLClientSocketNSS::OnHandshakeIOComplete,
+ base::Unretained(this)),
+ net_log_);
+}
+
+// Derived from AuthCertificateCallback() in
+// mozilla/source/security/manager/ssl/src/nsNSSCallbacks.cpp.
+int SSLClientSocketNSS::DoVerifyCertComplete(int result) {
+ verifier_.reset();
+
+ if (!start_cert_verification_time_.is_null()) {
+ base::TimeDelta verify_time =
+ base::TimeTicks::Now() - start_cert_verification_time_;
+ if (result == OK)
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTime", verify_time);
+ else
+ UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time);
+ }
+
+ // We used to remember the intermediate CA certs in the NSS database
+ // persistently. However, NSS opens a connection to the SQLite database
+ // during NSS initialization and doesn't close the connection until NSS
+ // shuts down. If the file system where the database resides is gone,
+ // the database connection goes bad. What's worse, the connection won't
+ // recover when the file system comes back. Until this NSS or SQLite bug
+ // is fixed, we need to avoid using the NSS database for non-essential
+ // purposes. See https://bugzilla.mozilla.org/show_bug.cgi?id=508081 and
+ // http://crbug.com/15630 for more info.
+
+ // TODO(hclam): Skip logging if server cert was expected to be bad because
+ // |server_cert_verify_result_| doesn't contain all the information about
+ // the cert.
+ if (result == OK)
+ LogConnectionTypeMetrics();
+
+ completed_handshake_ = true;
+
+#if defined(OFFICIAL_BUILD) && !defined(OS_ANDROID) && !defined(OS_IOS)
+ // Take care of any mandates for public key pinning.
+ //
+ // Pinning is only enabled for official builds to make sure that others don't
+ // end up with pins that cannot be easily updated.
+ //
+ // TODO(agl): We might have an issue here where a request for foo.example.com
+ // merges into a SPDY connection to www.example.com, and gets a different
+ // certificate.
+
+ // Perform pin validation if, and only if, all these conditions obtain:
+ //
+ // * a TransportSecurityState object is available;
+ // * the server's certificate chain is valid (or suffers from only a minor
+ // error);
+ // * the server's certificate chain chains up to a known root (i.e. not a
+ // user-installed trust anchor); and
+ // * the build is recent (very old builds should fail open so that users
+ // have some chance to recover).
+ //
+ const CertStatus cert_status = server_cert_verify_result_.cert_status;
+ if (transport_security_state_ &&
+ (result == OK ||
+ (IsCertificateError(result) && IsCertStatusMinorError(cert_status))) &&
+ server_cert_verify_result_.is_issued_by_known_root &&
+ TransportSecurityState::IsBuildTimely()) {
+ bool sni_available =
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1 ||
+ ssl_config_.version_fallback;
+ const std::string& host = host_and_port_.host();
+
+ TransportSecurityState::DomainState domain_state;
+ if (transport_security_state_->GetDomainState(host, sni_available,
+ &domain_state) &&
+ domain_state.HasPublicKeyPins()) {
+ if (!domain_state.CheckPublicKeyPins(
+ server_cert_verify_result_.public_key_hashes)) {
+ result = ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN;
+ UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", false);
+ TransportSecurityState::ReportUMAOnPinFailure(host);
+ } else {
+ UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess", true);
+ }
+ }
+ }
+#endif
+
+ // Exit DoHandshakeLoop and return the result to the caller to Connect.
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ return result;
+}
+
+void SSLClientSocketNSS::LogConnectionTypeMetrics() const {
+ UpdateConnectionTypeHistograms(CONNECTION_SSL);
+ int ssl_version = SSLConnectionStatusToVersion(
+ core_->state().ssl_connection_status);
+ switch (ssl_version) {
+ case SSL_CONNECTION_VERSION_SSL2:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL2);
+ break;
+ case SSL_CONNECTION_VERSION_SSL3:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_SSL3);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_1:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_1);
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_2:
+ UpdateConnectionTypeHistograms(CONNECTION_SSL_TLS1_2);
+ break;
+ };
+}
+
+void SSLClientSocketNSS::EnsureThreadIdAssigned() const {
+ base::AutoLock auto_lock(lock_);
+ if (valid_thread_id_ != base::kInvalidThreadId)
+ return;
+ valid_thread_id_ = base::PlatformThread::CurrentId();
+}
+
+bool SSLClientSocketNSS::CalledOnValidThread() const {
+ EnsureThreadIdAssigned();
+ base::AutoLock auto_lock(lock_);
+ return valid_thread_id_ == base::PlatformThread::CurrentId();
+}
+
+ServerBoundCertService* SSLClientSocketNSS::GetServerBoundCertService() const {
+ return server_bound_cert_service_;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket_nss.h b/chromium/net/socket/ssl_client_socket_nss.h
new file mode 100644
index 00000000000..b41d28d74a8
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_nss.h
@@ -0,0 +1,196 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_NSS_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_NSS_H_
+
+#include <certt.h>
+#include <keyt.h>
+#include <nspr.h>
+#include <nss.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/nss_memio.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace net {
+
+class BoundNetLog;
+class CertVerifier;
+class ClientSocketHandle;
+class ServerBoundCertService;
+class SingleRequestCertVerifier;
+class TransportSecurityState;
+class X509Certificate;
+
+// An SSL client socket implemented with Mozilla NSS.
+class SSLClientSocketNSS : public SSLClientSocket {
+ public:
+ // Takes ownership of the |transport_socket|, which must already be connected.
+ // The hostname specified in |host_and_port| will be compared with the name(s)
+ // in the server's certificate during the SSL handshake. If SSL client
+ // authentication is requested, the host_and_port field of SSLCertRequestInfo
+ // will be populated with |host_and_port|. |ssl_config| specifies
+ // the SSL settings.
+ //
+ // Because calls to NSS may block, such as due to needing to access slow
+ // hardware or needing to synchronously unlock protected tokens, calls to
+ // NSS may optionally be run on a dedicated thread. If synchronous/blocking
+ // behaviour is desired, for performance or compatibility, the current task
+ // runner should be supplied instead.
+ SSLClientSocketNSS(base::SequencedTaskRunner* nss_task_runner,
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context);
+ virtual ~SSLClientSocketNSS();
+
+ // SSLClientSocket implementation.
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual NextProtoStatus GetNextProto(std::string* proto,
+ std::string* server_protos) OVERRIDE;
+
+ // SSLSocket implementation.
+ virtual int ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) OVERRIDE;
+ virtual int GetTLSUniqueChannelBinding(std::string* out) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual ServerBoundCertService* GetServerBoundCertService() const OVERRIDE;
+
+ private:
+ // Helper class to handle marshalling any NSS interaction to and from the
+ // NSS and network task runners. Not every call needs to happen on the Core
+ class Core;
+
+ enum State {
+ STATE_NONE,
+ STATE_HANDSHAKE,
+ STATE_HANDSHAKE_COMPLETE,
+ STATE_VERIFY_CERT,
+ STATE_VERIFY_CERT_COMPLETE,
+ };
+
+ int Init();
+ void InitCore();
+
+ // Initializes NSS SSL options. Returns a net error code.
+ int InitializeSSLOptions();
+
+ // Initializes the socket peer name in SSL. Returns a net error code.
+ int InitializeSSLPeerName();
+
+ void DoConnectCallback(int result);
+ void OnHandshakeIOComplete(int result);
+
+ int DoHandshakeLoop(int last_io_result);
+ int DoHandshake();
+ int DoHandshakeComplete(int result);
+ int DoVerifyCert(int result);
+ int DoVerifyCertComplete(int result);
+
+ void LogConnectionTypeMetrics() const;
+
+ // The following methods are for debugging bug 65948. Will remove this code
+ // after fixing bug 65948.
+ void EnsureThreadIdAssigned() const;
+ bool CalledOnValidThread() const;
+
+ // The task runner used to perform NSS operations.
+ scoped_refptr<base::SequencedTaskRunner> nss_task_runner_;
+ scoped_ptr<ClientSocketHandle> transport_;
+ HostPortPair host_and_port_;
+ SSLConfig ssl_config_;
+
+ scoped_refptr<Core> core_;
+
+ CompletionCallback user_connect_callback_;
+
+ CertVerifyResult server_cert_verify_result_;
+ HashValueVector side_pinned_public_keys_;
+
+ CertVerifier* const cert_verifier_;
+ scoped_ptr<SingleRequestCertVerifier> verifier_;
+
+ // The service for retrieving Channel ID keys. May be NULL.
+ ServerBoundCertService* server_bound_cert_service_;
+
+ // ssl_session_cache_shard_ is an opaque string that partitions the SSL
+ // session cache. i.e. sessions created with one value will not attempt to
+ // resume on the socket with a different value.
+ const std::string ssl_session_cache_shard_;
+
+ // True if the SSL handshake has been completed.
+ bool completed_handshake_;
+
+ State next_handshake_state_;
+
+ // The NSS SSL state machine. This is owned by |core_|.
+ // TODO(rsleevi): http://crbug.com/130616 - Remove this member once
+ // ExportKeyingMaterial is updated to be asynchronous.
+ PRFileDesc* nss_fd_;
+
+ BoundNetLog net_log_;
+
+ base::TimeTicks start_cert_verification_time_;
+
+ TransportSecurityState* transport_security_state_;
+
+ // The following two variables are added for debugging bug 65948. Will
+ // remove this code after fixing bug 65948.
+ // Added the following code Debugging in release mode.
+ mutable base::Lock lock_;
+ // This is mutable so that CalledOnValidThread can set it.
+ // It's guarded by |lock_|.
+ mutable base::PlatformThreadId valid_thread_id_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_NSS_H_
diff --git a/chromium/net/socket/ssl_client_socket_openssl.cc b/chromium/net/socket/ssl_client_socket_openssl.cc
new file mode 100644
index 00000000000..4591cec5b9d
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_openssl.cc
@@ -0,0 +1,1435 @@
+// Copyright (c) 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.
+
+// OpenSSL binding for SSLClientSocket. The class layout and general principle
+// of operation is derived from SSLClientSocketNSS.
+
+#include "net/socket/ssl_client_socket_openssl.h"
+
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram.h"
+#include "base/synchronization/lock.h"
+#include "crypto/openssl_util.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert/single_request_cert_verifier.h"
+#include "net/cert/x509_certificate_net_log_param.h"
+#include "net/socket/ssl_error_params.h"
+#include "net/ssl/openssl_client_key_store.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+
+namespace {
+
+// Enable this to see logging for state machine state transitions.
+#if 0
+#define GotoState(s) do { DVLOG(2) << (void *)this << " " << __FUNCTION__ << \
+ " jump to state " << s; \
+ next_handshake_state_ = s; } while (0)
+#else
+#define GotoState(s) next_handshake_state_ = s
+#endif
+
+const int kSessionCacheTimeoutSeconds = 60 * 60;
+const size_t kSessionCacheMaxEntires = 1024;
+
+// This constant can be any non-negative/non-zero value (eg: it does not
+// overlap with any value of the net::Error range, including net::OK).
+const int kNoPendingReadResult = 1;
+
+// If a client doesn't have a list of protocols that it supports, but
+// the server supports NPN, choosing "http/1.1" is the best answer.
+const char kDefaultSupportedNPNProtocol[] = "http/1.1";
+
+#if OPENSSL_VERSION_NUMBER < 0x1000103fL
+// This method doesn't seem to have made it into the OpenSSL headers.
+unsigned long SSL_CIPHER_get_id(const SSL_CIPHER* cipher) { return cipher->id; }
+#endif
+
+// Used for encoding the |connection_status| field of an SSLInfo object.
+int EncodeSSLConnectionStatus(int cipher_suite,
+ int compression,
+ int version) {
+ return ((cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) <<
+ SSL_CONNECTION_CIPHERSUITE_SHIFT) |
+ ((compression & SSL_CONNECTION_COMPRESSION_MASK) <<
+ SSL_CONNECTION_COMPRESSION_SHIFT) |
+ ((version & SSL_CONNECTION_VERSION_MASK) <<
+ SSL_CONNECTION_VERSION_SHIFT);
+}
+
+// Returns the net SSL version number (see ssl_connection_status_flags.h) for
+// this SSL connection.
+int GetNetSSLVersion(SSL* ssl) {
+ switch (SSL_version(ssl)) {
+ case SSL2_VERSION:
+ return SSL_CONNECTION_VERSION_SSL2;
+ case SSL3_VERSION:
+ return SSL_CONNECTION_VERSION_SSL3;
+ case TLS1_VERSION:
+ return SSL_CONNECTION_VERSION_TLS1;
+ case 0x0302:
+ return SSL_CONNECTION_VERSION_TLS1_1;
+ case 0x0303:
+ return SSL_CONNECTION_VERSION_TLS1_2;
+ default:
+ return SSL_CONNECTION_VERSION_UNKNOWN;
+ }
+}
+
+int MapOpenSSLErrorSSL() {
+ // Walk down the error stack to find the SSLerr generated reason.
+ unsigned long error_code;
+ do {
+ error_code = ERR_get_error();
+ if (error_code == 0)
+ return ERR_SSL_PROTOCOL_ERROR;
+ } while (ERR_GET_LIB(error_code) != ERR_LIB_SSL);
+
+ DVLOG(1) << "OpenSSL SSL error, reason: " << ERR_GET_REASON(error_code)
+ << ", name: " << ERR_error_string(error_code, NULL);
+ switch (ERR_GET_REASON(error_code)) {
+ case SSL_R_READ_TIMEOUT_EXPIRED:
+ return ERR_TIMED_OUT;
+ case SSL_R_BAD_RESPONSE_ARGUMENT:
+ return ERR_INVALID_ARGUMENT;
+ case SSL_R_UNKNOWN_CERTIFICATE_TYPE:
+ case SSL_R_UNKNOWN_CIPHER_TYPE:
+ case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE:
+ case SSL_R_UNKNOWN_PKEY_TYPE:
+ case SSL_R_UNKNOWN_REMOTE_ERROR_TYPE:
+ case SSL_R_UNKNOWN_SSL_VERSION:
+ return ERR_NOT_IMPLEMENTED;
+ case SSL_R_UNSUPPORTED_SSL_VERSION:
+ case SSL_R_NO_CIPHER_MATCH:
+ case SSL_R_NO_SHARED_CIPHER:
+ case SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY:
+ case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
+ case SSL_R_UNSUPPORTED_PROTOCOL:
+ return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
+ case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:
+ case SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE:
+ case SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED:
+ case SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED:
+ case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:
+ case SSL_R_TLSV1_ALERT_ACCESS_DENIED:
+ case SSL_R_TLSV1_ALERT_UNKNOWN_CA:
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+ case SSL_R_BAD_DECOMPRESSION:
+ case SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE:
+ return ERR_SSL_DECOMPRESSION_FAILURE_ALERT;
+ case SSL_R_SSLV3_ALERT_BAD_RECORD_MAC:
+ return ERR_SSL_BAD_RECORD_MAC_ALERT;
+ case SSL_R_TLSV1_ALERT_DECRYPT_ERROR:
+ return ERR_SSL_DECRYPT_ERROR_ALERT;
+ case SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED:
+ return ERR_SSL_UNSAFE_NEGOTIATION;
+ case SSL_R_WRONG_NUMBER_OF_KEY_BITS:
+ return ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY;
+ // SSL_R_UNKNOWN_PROTOCOL is reported if premature application data is
+ // received (see http://crbug.com/42538), and also if all the protocol
+ // versions supported by the server were disabled in this socket instance.
+ // Mapped to ERR_SSL_PROTOCOL_ERROR for compatibility with other SSL sockets
+ // in the former scenario.
+ case SSL_R_UNKNOWN_PROTOCOL:
+ case SSL_R_SSL_HANDSHAKE_FAILURE:
+ case SSL_R_DECRYPTION_FAILED:
+ case SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC:
+ case SSL_R_DH_PUBLIC_VALUE_LENGTH_IS_WRONG:
+ case SSL_R_DIGEST_CHECK_FAILED:
+ case SSL_R_DUPLICATE_COMPRESSION_ID:
+ case SSL_R_ECGROUP_TOO_LARGE_FOR_CIPHER:
+ case SSL_R_ENCRYPTED_LENGTH_TOO_LONG:
+ case SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST:
+ case SSL_R_EXCESSIVE_MESSAGE_SIZE:
+ case SSL_R_EXTRA_DATA_IN_MESSAGE:
+ case SSL_R_GOT_A_FIN_BEFORE_A_CCS:
+ case SSL_R_ILLEGAL_PADDING:
+ case SSL_R_INVALID_CHALLENGE_LENGTH:
+ case SSL_R_INVALID_COMMAND:
+ case SSL_R_INVALID_PURPOSE:
+ case SSL_R_INVALID_STATUS_RESPONSE:
+ case SSL_R_INVALID_TICKET_KEYS_LENGTH:
+ case SSL_R_KEY_ARG_TOO_LONG:
+ case SSL_R_READ_WRONG_PACKET_TYPE:
+ case SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE:
+ // TODO(joth): SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE may be returned from the
+ // server after receiving ClientHello if there's no common supported cipher.
+ // Ideally we'd map that specific case to ERR_SSL_VERSION_OR_CIPHER_MISMATCH
+ // to match the NSS implementation. See also http://goo.gl/oMtZW
+ case SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE:
+ case SSL_R_SSLV3_ALERT_NO_CERTIFICATE:
+ case SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER:
+ case SSL_R_TLSV1_ALERT_DECODE_ERROR:
+ case SSL_R_TLSV1_ALERT_DECRYPTION_FAILED:
+ case SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION:
+ case SSL_R_TLSV1_ALERT_INTERNAL_ERROR:
+ case SSL_R_TLSV1_ALERT_NO_RENEGOTIATION:
+ case SSL_R_TLSV1_ALERT_RECORD_OVERFLOW:
+ case SSL_R_TLSV1_ALERT_USER_CANCELLED:
+ return ERR_SSL_PROTOCOL_ERROR;
+ default:
+ LOG(WARNING) << "Unmapped error reason: " << ERR_GET_REASON(error_code);
+ return ERR_FAILED;
+ }
+}
+
+// Converts an OpenSSL error code into a net error code, walking the OpenSSL
+// error stack if needed. Note that |tracer| is not currently used in the
+// implementation, but is passed in anyway as this ensures the caller will clear
+// any residual codes left on the error stack.
+int MapOpenSSLError(int err, const crypto::OpenSSLErrStackTracer& tracer) {
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ return ERR_IO_PENDING;
+ case SSL_ERROR_SYSCALL:
+ LOG(ERROR) << "OpenSSL SYSCALL error, earliest error code in "
+ "error queue: " << ERR_peek_error() << ", errno: "
+ << errno;
+ return ERR_SSL_PROTOCOL_ERROR;
+ case SSL_ERROR_SSL:
+ return MapOpenSSLErrorSSL();
+ default:
+ // TODO(joth): Implement full mapping.
+ LOG(WARNING) << "Unknown OpenSSL error " << err;
+ return ERR_SSL_PROTOCOL_ERROR;
+ }
+}
+
+// We do certificate verification after handshake, so we disable the default
+// by registering a no-op verify function.
+int NoOpVerifyCallback(X509_STORE_CTX*, void *) {
+ DVLOG(3) << "skipping cert verify";
+ return 1;
+}
+
+// OpenSSL manages a cache of SSL_SESSION, this class provides the application
+// side policy for that cache about session re-use: we retain one session per
+// unique HostPortPair, per shard.
+class SSLSessionCache {
+ public:
+ SSLSessionCache() {}
+
+ void OnSessionAdded(const HostPortPair& host_and_port,
+ const std::string& shard,
+ SSL_SESSION* session) {
+ // Declare the session cleaner-upper before the lock, so any call into
+ // OpenSSL to free the session will happen after the lock is released.
+ crypto::ScopedOpenSSL<SSL_SESSION, SSL_SESSION_free> session_to_free;
+ base::AutoLock lock(lock_);
+
+ DCHECK_EQ(0U, session_map_.count(session));
+ const std::string cache_key = GetCacheKey(host_and_port, shard);
+
+ std::pair<HostPortMap::iterator, bool> res =
+ host_port_map_.insert(std::make_pair(cache_key, session));
+ if (!res.second) { // Already exists: replace old entry.
+ session_to_free.reset(res.first->second);
+ session_map_.erase(session_to_free.get());
+ res.first->second = session;
+ }
+ DVLOG(2) << "Adding session " << session << " => "
+ << cache_key << ", new entry = " << res.second;
+ DCHECK(host_port_map_[cache_key] == session);
+ session_map_[session] = res.first;
+ DCHECK_EQ(host_port_map_.size(), session_map_.size());
+ DCHECK_LE(host_port_map_.size(), kSessionCacheMaxEntires);
+ }
+
+ void OnSessionRemoved(SSL_SESSION* session) {
+ // Declare the session cleaner-upper before the lock, so any call into
+ // OpenSSL to free the session will happen after the lock is released.
+ crypto::ScopedOpenSSL<SSL_SESSION, SSL_SESSION_free> session_to_free;
+ base::AutoLock lock(lock_);
+
+ SessionMap::iterator it = session_map_.find(session);
+ if (it == session_map_.end())
+ return;
+ DVLOG(2) << "Remove session " << session << " => " << it->second->first;
+ DCHECK(it->second->second == session);
+ host_port_map_.erase(it->second);
+ session_map_.erase(it);
+ session_to_free.reset(session);
+ DCHECK_EQ(host_port_map_.size(), session_map_.size());
+ }
+
+ // Looks up the host:port in the cache, and if a session is found it is added
+ // to |ssl|, returning true on success.
+ bool SetSSLSession(SSL* ssl, const HostPortPair& host_and_port,
+ const std::string& shard) {
+ base::AutoLock lock(lock_);
+ const std::string cache_key = GetCacheKey(host_and_port, shard);
+ HostPortMap::iterator it = host_port_map_.find(cache_key);
+ if (it == host_port_map_.end())
+ return false;
+ DVLOG(2) << "Lookup session: " << it->second << " => " << cache_key;
+ SSL_SESSION* session = it->second;
+ DCHECK(session);
+ DCHECK(session_map_[session] == it);
+ // Ideally we'd release |lock_| before calling into OpenSSL here, however
+ // that opens a small risk |session| will go out of scope before it is used.
+ // Alternatively we would take a temporary local refcount on |session|,
+ // except OpenSSL does not provide a public API for adding a ref (c.f.
+ // SSL_SESSION_free which decrements the ref).
+ return SSL_set_session(ssl, session) == 1;
+ }
+
+ // Flush removes all entries from the cache. This is called when a client
+ // certificate is added.
+ void Flush() {
+ for (HostPortMap::iterator i = host_port_map_.begin();
+ i != host_port_map_.end(); i++) {
+ SSL_SESSION_free(i->second);
+ }
+ host_port_map_.clear();
+ session_map_.clear();
+ }
+
+ private:
+ static std::string GetCacheKey(const HostPortPair& host_and_port,
+ const std::string& shard) {
+ return host_and_port.ToString() + "/" + shard;
+ }
+
+ // A pair of maps to allow bi-directional lookups between host:port and an
+ // associated session.
+ typedef std::map<std::string, SSL_SESSION*> HostPortMap;
+ typedef std::map<SSL_SESSION*, HostPortMap::iterator> SessionMap;
+ HostPortMap host_port_map_;
+ SessionMap session_map_;
+
+ // Protects access to both the above maps.
+ base::Lock lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLSessionCache);
+};
+
+class SSLContext {
+ public:
+ static SSLContext* GetInstance() { return Singleton<SSLContext>::get(); }
+ SSL_CTX* ssl_ctx() { return ssl_ctx_.get(); }
+ SSLSessionCache* session_cache() { return &session_cache_; }
+
+ SSLClientSocketOpenSSL* GetClientSocketFromSSL(SSL* ssl) {
+ DCHECK(ssl);
+ SSLClientSocketOpenSSL* socket = static_cast<SSLClientSocketOpenSSL*>(
+ SSL_get_ex_data(ssl, ssl_socket_data_index_));
+ DCHECK(socket);
+ return socket;
+ }
+
+ bool SetClientSocketForSSL(SSL* ssl, SSLClientSocketOpenSSL* socket) {
+ return SSL_set_ex_data(ssl, ssl_socket_data_index_, socket) != 0;
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<SSLContext>;
+
+ SSLContext() {
+ crypto::EnsureOpenSSLInit();
+ ssl_socket_data_index_ = SSL_get_ex_new_index(0, 0, 0, 0, 0);
+ DCHECK_NE(ssl_socket_data_index_, -1);
+ ssl_ctx_.reset(SSL_CTX_new(SSLv23_client_method()));
+ SSL_CTX_set_cert_verify_callback(ssl_ctx_.get(), NoOpVerifyCallback, NULL);
+ SSL_CTX_set_session_cache_mode(ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT);
+ SSL_CTX_sess_set_new_cb(ssl_ctx_.get(), NewSessionCallbackStatic);
+ SSL_CTX_sess_set_remove_cb(ssl_ctx_.get(), RemoveSessionCallbackStatic);
+ SSL_CTX_set_timeout(ssl_ctx_.get(), kSessionCacheTimeoutSeconds);
+ SSL_CTX_sess_set_cache_size(ssl_ctx_.get(), kSessionCacheMaxEntires);
+ SSL_CTX_set_client_cert_cb(ssl_ctx_.get(), ClientCertCallback);
+#if defined(OPENSSL_NPN_NEGOTIATED)
+ // TODO(kristianm): Only select this if ssl_config_.next_proto is not empty.
+ // It would be better if the callback were not a global setting,
+ // but that is an OpenSSL issue.
+ SSL_CTX_set_next_proto_select_cb(ssl_ctx_.get(), SelectNextProtoCallback,
+ NULL);
+#endif
+ }
+
+ static int NewSessionCallbackStatic(SSL* ssl, SSL_SESSION* session) {
+ return GetInstance()->NewSessionCallback(ssl, session);
+ }
+
+ int NewSessionCallback(SSL* ssl, SSL_SESSION* session) {
+ SSLClientSocketOpenSSL* socket = GetClientSocketFromSSL(ssl);
+ session_cache_.OnSessionAdded(socket->host_and_port(),
+ socket->ssl_session_cache_shard(),
+ session);
+ return 1; // 1 => We took ownership of |session|.
+ }
+
+ static void RemoveSessionCallbackStatic(SSL_CTX* ctx, SSL_SESSION* session) {
+ return GetInstance()->RemoveSessionCallback(ctx, session);
+ }
+
+ void RemoveSessionCallback(SSL_CTX* ctx, SSL_SESSION* session) {
+ DCHECK(ctx == ssl_ctx());
+ session_cache_.OnSessionRemoved(session);
+ }
+
+ static int ClientCertCallback(SSL* ssl, X509** x509, EVP_PKEY** pkey) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ CHECK(socket);
+ return socket->ClientCertRequestCallback(ssl, x509, pkey);
+ }
+
+ static int SelectNextProtoCallback(SSL* ssl,
+ unsigned char** out, unsigned char* outlen,
+ const unsigned char* in,
+ unsigned int inlen, void* arg) {
+ SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
+ return socket->SelectNextProtoCallback(out, outlen, in, inlen);
+ }
+
+ // This is the index used with SSL_get_ex_data to retrieve the owner
+ // SSLClientSocketOpenSSL object from an SSL instance.
+ int ssl_socket_data_index_;
+
+ // session_cache_ must appear before |ssl_ctx_| because the destruction of
+ // |ssl_ctx_| may trigger callbacks into |session_cache_|. Therefore,
+ // |session_cache_| must be destructed after |ssl_ctx_|.
+ SSLSessionCache session_cache_;
+ crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free> ssl_ctx_;
+};
+
+// Utility to construct the appropriate set & clear masks for use the OpenSSL
+// options and mode configuration functions. (SSL_set_options etc)
+struct SslSetClearMask {
+ SslSetClearMask() : set_mask(0), clear_mask(0) {}
+ void ConfigureFlag(long flag, bool state) {
+ (state ? set_mask : clear_mask) |= flag;
+ // Make sure we haven't got any intersection in the set & clear options.
+ DCHECK_EQ(0, set_mask & clear_mask) << flag << ":" << state;
+ }
+ long set_mask;
+ long clear_mask;
+};
+
+} // namespace
+
+// static
+void SSLClientSocket::ClearSessionCache() {
+ SSLContext* context = SSLContext::GetInstance();
+ context->session_cache()->Flush();
+}
+
+SSLClientSocketOpenSSL::SSLClientSocketOpenSSL(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context)
+ : transport_send_busy_(false),
+ transport_recv_busy_(false),
+ transport_recv_eof_(false),
+ weak_factory_(this),
+ pending_read_error_(kNoPendingReadResult),
+ completed_handshake_(false),
+ client_auth_cert_needed_(false),
+ cert_verifier_(context.cert_verifier),
+ ssl_(NULL),
+ transport_bio_(NULL),
+ transport_(transport_socket.Pass()),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ ssl_session_cache_shard_(context.ssl_session_cache_shard),
+ trying_cached_session_(false),
+ next_handshake_state_(STATE_NONE),
+ npn_status_(kNextProtoUnsupported),
+ net_log_(transport_->socket()->NetLog()) {
+}
+
+SSLClientSocketOpenSSL::~SSLClientSocketOpenSSL() {
+ Disconnect();
+}
+
+bool SSLClientSocketOpenSSL::Init() {
+ DCHECK(!ssl_);
+ DCHECK(!transport_bio_);
+
+ SSLContext* context = SSLContext::GetInstance();
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ ssl_ = SSL_new(context->ssl_ctx());
+ if (!ssl_ || !context->SetClientSocketForSSL(ssl_, this))
+ return false;
+
+ if (!SSL_set_tlsext_host_name(ssl_, host_and_port_.host().c_str()))
+ return false;
+
+ trying_cached_session_ =
+ context->session_cache()->SetSSLSession(ssl_, host_and_port_,
+ ssl_session_cache_shard_);
+
+ BIO* ssl_bio = NULL;
+ // 0 => use default buffer sizes.
+ if (!BIO_new_bio_pair(&ssl_bio, 0, &transport_bio_, 0))
+ return false;
+ DCHECK(ssl_bio);
+ DCHECK(transport_bio_);
+
+ SSL_set_bio(ssl_, ssl_bio, ssl_bio);
+
+ // OpenSSL defaults some options to on, others to off. To avoid ambiguity,
+ // set everything we care about to an absolute value.
+ SslSetClearMask options;
+ options.ConfigureFlag(SSL_OP_NO_SSLv2, true);
+ bool ssl3_enabled = (ssl_config_.version_min == SSL_PROTOCOL_VERSION_SSL3);
+ options.ConfigureFlag(SSL_OP_NO_SSLv3, !ssl3_enabled);
+ bool tls1_enabled = (ssl_config_.version_min <= SSL_PROTOCOL_VERSION_TLS1 &&
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1);
+ options.ConfigureFlag(SSL_OP_NO_TLSv1, !tls1_enabled);
+#if defined(SSL_OP_NO_TLSv1_1)
+ bool tls1_1_enabled =
+ (ssl_config_.version_min <= SSL_PROTOCOL_VERSION_TLS1_1 &&
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1_1);
+ options.ConfigureFlag(SSL_OP_NO_TLSv1_1, !tls1_1_enabled);
+#endif
+#if defined(SSL_OP_NO_TLSv1_2)
+ bool tls1_2_enabled =
+ (ssl_config_.version_min <= SSL_PROTOCOL_VERSION_TLS1_2 &&
+ ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1_2);
+ options.ConfigureFlag(SSL_OP_NO_TLSv1_2, !tls1_2_enabled);
+#endif
+
+#if defined(SSL_OP_NO_COMPRESSION)
+ options.ConfigureFlag(SSL_OP_NO_COMPRESSION, true);
+#endif
+
+ // TODO(joth): Set this conditionally, see http://crbug.com/55410
+ options.ConfigureFlag(SSL_OP_LEGACY_SERVER_CONNECT, true);
+
+ SSL_set_options(ssl_, options.set_mask);
+ SSL_clear_options(ssl_, options.clear_mask);
+
+ // Same as above, this time for the SSL mode.
+ SslSetClearMask mode;
+
+#if defined(SSL_MODE_RELEASE_BUFFERS)
+ mode.ConfigureFlag(SSL_MODE_RELEASE_BUFFERS, true);
+#endif
+
+#if defined(SSL_MODE_SMALL_BUFFERS)
+ mode.ConfigureFlag(SSL_MODE_SMALL_BUFFERS, true);
+#endif
+
+ SSL_set_mode(ssl_, mode.set_mask);
+ SSL_clear_mode(ssl_, mode.clear_mask);
+
+ // Removing ciphers by ID from OpenSSL is a bit involved as we must use the
+ // textual name with SSL_set_cipher_list because there is no public API to
+ // directly remove a cipher by ID.
+ STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl_);
+ DCHECK(ciphers);
+ // See SSLConfig::disabled_cipher_suites for description of the suites
+ // disabled by default. Note that !SHA384 only removes HMAC-SHA384 cipher
+ // suites, not GCM cipher suites with SHA384 as the handshake hash.
+ std::string command("DEFAULT:!NULL:!aNULL:!IDEA:!FZA:!SRP:!SHA384:!aECDH");
+ // Walk through all the installed ciphers, seeing if any need to be
+ // appended to the cipher removal |command|.
+ for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
+ const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
+ const uint16 id = SSL_CIPHER_get_id(cipher);
+ // Remove any ciphers with a strength of less than 80 bits. Note the NSS
+ // implementation uses "effective" bits here but OpenSSL does not provide
+ // this detail. This only impacts Triple DES: reports 112 vs. 168 bits,
+ // both of which are greater than 80 anyway.
+ bool disable = SSL_CIPHER_get_bits(cipher, NULL) < 80;
+ if (!disable) {
+ disable = std::find(ssl_config_.disabled_cipher_suites.begin(),
+ ssl_config_.disabled_cipher_suites.end(), id) !=
+ ssl_config_.disabled_cipher_suites.end();
+ }
+ if (disable) {
+ const char* name = SSL_CIPHER_get_name(cipher);
+ DVLOG(3) << "Found cipher to remove: '" << name << "', ID: " << id
+ << " strength: " << SSL_CIPHER_get_bits(cipher, NULL);
+ command.append(":!");
+ command.append(name);
+ }
+ }
+ int rv = SSL_set_cipher_list(ssl_, command.c_str());
+ // If this fails (rv = 0) it means there are no ciphers enabled on this SSL.
+ // This will almost certainly result in the socket failing to complete the
+ // handshake at which point the appropriate error is bubbled up to the client.
+ LOG_IF(WARNING, rv != 1) << "SSL_set_cipher_list('" << command << "') "
+ "returned " << rv;
+ return true;
+}
+
+int SSLClientSocketOpenSSL::ClientCertRequestCallback(SSL* ssl,
+ X509** x509,
+ EVP_PKEY** pkey) {
+ DVLOG(3) << "OpenSSL ClientCertRequestCallback called";
+ DCHECK(ssl == ssl_);
+ DCHECK(*x509 == NULL);
+ DCHECK(*pkey == NULL);
+
+ if (!ssl_config_.send_client_cert) {
+ // First pass: we know that a client certificate is needed, but we do not
+ // have one at hand.
+ client_auth_cert_needed_ = true;
+ STACK_OF(X509_NAME) *authorities = SSL_get_client_CA_list(ssl);
+ for (int i = 0; i < sk_X509_NAME_num(authorities); i++) {
+ X509_NAME *ca_name = (X509_NAME *)sk_X509_NAME_value(authorities, i);
+ unsigned char* str = NULL;
+ int length = i2d_X509_NAME(ca_name, &str);
+ cert_authorities_.push_back(std::string(
+ reinterpret_cast<const char*>(str),
+ static_cast<size_t>(length)));
+ OPENSSL_free(str);
+ }
+
+ return -1; // Suspends handshake.
+ }
+
+ // Second pass: a client certificate should have been selected.
+ if (ssl_config_.client_cert.get()) {
+ // A note about ownership: FetchClientCertPrivateKey() increments
+ // the reference count of the EVP_PKEY. Ownership of this reference
+ // is passed directly to OpenSSL, which will release the reference
+ // using EVP_PKEY_free() when the SSL object is destroyed.
+ OpenSSLClientKeyStore::ScopedEVP_PKEY privkey;
+ if (OpenSSLClientKeyStore::GetInstance()->FetchClientCertPrivateKey(
+ ssl_config_.client_cert.get(), &privkey)) {
+ // TODO(joth): (copied from NSS) We should wait for server certificate
+ // verification before sending our credentials. See http://crbug.com/13934
+ *x509 = X509Certificate::DupOSCertHandle(
+ ssl_config_.client_cert->os_cert_handle());
+ *pkey = privkey.release();
+ return 1;
+ }
+ LOG(WARNING) << "Client cert found without private key";
+ }
+
+ // Send no client certificate.
+ return 0;
+}
+
+// SSLClientSocket methods
+
+bool SSLClientSocketOpenSSL::GetSSLInfo(SSLInfo* ssl_info) {
+ ssl_info->Reset();
+ if (!server_cert_.get())
+ return false;
+
+ ssl_info->cert = server_cert_verify_result_.verified_cert;
+ ssl_info->cert_status = server_cert_verify_result_.cert_status;
+ ssl_info->is_issued_by_known_root =
+ server_cert_verify_result_.is_issued_by_known_root;
+ ssl_info->public_key_hashes =
+ server_cert_verify_result_.public_key_hashes;
+ ssl_info->client_cert_sent =
+ ssl_config_.send_client_cert && ssl_config_.client_cert.get();
+ ssl_info->channel_id_sent = WasChannelIDSent();
+
+ const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_);
+ CHECK(cipher);
+ ssl_info->security_bits = SSL_CIPHER_get_bits(cipher, NULL);
+ const COMP_METHOD* compression = SSL_get_current_compression(ssl_);
+
+ ssl_info->connection_status = EncodeSSLConnectionStatus(
+ SSL_CIPHER_get_id(cipher),
+ compression ? compression->type : 0,
+ GetNetSSLVersion(ssl_));
+
+ bool peer_supports_renego_ext = !!SSL_get_secure_renegotiation_support(ssl_);
+ if (!peer_supports_renego_ext)
+ ssl_info->connection_status |= SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION;
+ UMA_HISTOGRAM_ENUMERATION("Net.RenegotiationExtensionSupported",
+ implicit_cast<int>(peer_supports_renego_ext), 2);
+
+ if (ssl_config_.version_fallback)
+ ssl_info->connection_status |= SSL_CONNECTION_VERSION_FALLBACK;
+
+ ssl_info->handshake_type = SSL_session_reused(ssl_) ?
+ SSLInfo::HANDSHAKE_RESUME : SSLInfo::HANDSHAKE_FULL;
+
+ DVLOG(3) << "Encoded connection status: cipher suite = "
+ << SSLConnectionStatusToCipherSuite(ssl_info->connection_status)
+ << " version = "
+ << SSLConnectionStatusToVersion(ssl_info->connection_status);
+ return true;
+}
+
+void SSLClientSocketOpenSSL::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ cert_request_info->host_and_port = host_and_port_.ToString();
+ cert_request_info->cert_authorities = cert_authorities_;
+}
+
+int SSLClientSocketOpenSSL::ExportKeyingMaterial(
+ const base::StringPiece& label,
+ bool has_context, const base::StringPiece& context,
+ unsigned char* out, unsigned int outlen) {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ int rv = SSL_export_keying_material(
+ ssl_, out, outlen, const_cast<char*>(label.data()),
+ label.size(),
+ reinterpret_cast<unsigned char*>(const_cast<char*>(context.data())),
+ context.length(),
+ context.length() > 0);
+
+ if (rv != 1) {
+ int ssl_error = SSL_get_error(ssl_, rv);
+ LOG(ERROR) << "Failed to export keying material;"
+ << " returned " << rv
+ << ", SSL error code " << ssl_error;
+ return MapOpenSSLError(ssl_error, err_tracer);
+ }
+ return OK;
+}
+
+int SSLClientSocketOpenSSL::GetTLSUniqueChannelBinding(std::string* out) {
+ return ERR_NOT_IMPLEMENTED;
+}
+
+SSLClientSocket::NextProtoStatus SSLClientSocketOpenSSL::GetNextProto(
+ std::string* proto, std::string* server_protos) {
+ *proto = npn_proto_;
+ *server_protos = server_protos_;
+ return npn_status_;
+}
+
+ServerBoundCertService*
+SSLClientSocketOpenSSL::GetServerBoundCertService() const {
+ return NULL;
+}
+
+void SSLClientSocketOpenSSL::DoReadCallback(int rv) {
+ // Since Run may result in Read being called, clear |user_read_callback_|
+ // up front.
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ base::ResetAndReturn(&user_read_callback_).Run(rv);
+}
+
+void SSLClientSocketOpenSSL::DoWriteCallback(int rv) {
+ // Since Run may result in Write being called, clear |user_write_callback_|
+ // up front.
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ base::ResetAndReturn(&user_write_callback_).Run(rv);
+}
+
+// StreamSocket implementation.
+int SSLClientSocketOpenSSL::Connect(const CompletionCallback& callback) {
+ net_log_.BeginEvent(NetLog::TYPE_SSL_CONNECT);
+
+ // Set up new ssl object.
+ if (!Init()) {
+ int result = ERR_UNEXPECTED;
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, result);
+ return result;
+ }
+
+ // Set SSL to client mode. Handshake happens in the loop below.
+ SSL_set_connect_state(ssl_);
+
+ GotoState(STATE_HANDSHAKE);
+ int rv = DoHandshakeLoop(net::OK);
+ if (rv == ERR_IO_PENDING) {
+ user_connect_callback_ = callback;
+ } else {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ }
+
+ return rv > OK ? OK : rv;
+}
+
+void SSLClientSocketOpenSSL::Disconnect() {
+ if (ssl_) {
+ // Calling SSL_shutdown prevents the session from being marked as
+ // unresumable.
+ SSL_shutdown(ssl_);
+ SSL_free(ssl_);
+ ssl_ = NULL;
+ }
+ if (transport_bio_) {
+ BIO_free_all(transport_bio_);
+ transport_bio_ = NULL;
+ }
+
+ // Shut down anything that may call us back.
+ verifier_.reset();
+ transport_->socket()->Disconnect();
+
+ // Null all callbacks, delete all buffers.
+ transport_send_busy_ = false;
+ send_buffer_ = NULL;
+ transport_recv_busy_ = false;
+ transport_recv_eof_ = false;
+ recv_buffer_ = NULL;
+
+ user_connect_callback_.Reset();
+ user_read_callback_.Reset();
+ user_write_callback_.Reset();
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+
+ server_cert_verify_result_.Reset();
+ completed_handshake_ = false;
+
+ cert_authorities_.clear();
+ client_auth_cert_needed_ = false;
+}
+
+int SSLClientSocketOpenSSL::DoHandshakeLoop(int last_io_result) {
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ // (This is a quirk carried over from the windows
+ // implementation. It makes reading the logs a bit harder.)
+ // State handlers can and often do call GotoState just
+ // to stay in the current state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+ switch (state) {
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_VERIFY_CERT:
+ DCHECK(rv == OK);
+ rv = DoVerifyCert(rv);
+ break;
+ case STATE_VERIFY_CERT_COMPLETE:
+ rv = DoVerifyCertComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ NOTREACHED() << "unexpected state" << state;
+ break;
+ }
+
+ bool network_moved = DoTransportIO();
+ if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
+ // In general we exit the loop if rv is ERR_IO_PENDING. In this
+ // special case we keep looping even if rv is ERR_IO_PENDING because
+ // the transport IO may allow DoHandshake to make progress.
+ rv = OK; // This causes us to stay in the loop.
+ }
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ return rv;
+}
+
+int SSLClientSocketOpenSSL::DoHandshake() {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+ int net_error = net::OK;
+ int rv = SSL_do_handshake(ssl_);
+
+ if (client_auth_cert_needed_) {
+ net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ // If the handshake already succeeded (because the server requests but
+ // doesn't require a client cert), we need to invalidate the SSL session
+ // so that we won't try to resume the non-client-authenticated session in
+ // the next handshake. This will cause the server to ask for a client
+ // cert again.
+ if (rv == 1) {
+ // Remove from session cache but don't clear this connection.
+ SSL_SESSION* session = SSL_get_session(ssl_);
+ if (session) {
+ int rv = SSL_CTX_remove_session(SSL_get_SSL_CTX(ssl_), session);
+ LOG_IF(WARNING, !rv) << "Couldn't invalidate SSL session: " << session;
+ }
+ }
+ } else if (rv == 1) {
+ if (trying_cached_session_ && logging::DEBUG_MODE) {
+ DVLOG(2) << "Result of session reuse for " << host_and_port_.ToString()
+ << " is: " << (SSL_session_reused(ssl_) ? "Success" : "Fail");
+ }
+ // SSL handshake is completed. Let's verify the certificate.
+ const bool got_cert = !!UpdateServerCert();
+ DCHECK(got_cert);
+ net_log_.AddEvent(
+ NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
+ base::Bind(&NetLogX509CertificateCallback,
+ base::Unretained(server_cert_.get())));
+ GotoState(STATE_VERIFY_CERT);
+ } else {
+ int ssl_error = SSL_get_error(ssl_, rv);
+ net_error = MapOpenSSLError(ssl_error, err_tracer);
+
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE);
+ } else {
+ LOG(ERROR) << "handshake failed; returned " << rv
+ << ", SSL error code " << ssl_error
+ << ", net_error " << net_error;
+ net_log_.AddEvent(
+ NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ CreateNetLogSSLErrorCallback(net_error, ssl_error));
+ }
+ }
+ return net_error;
+}
+
+// SelectNextProtoCallback is called by OpenSSL during the handshake. If the
+// server supports NPN, selects a protocol from the list that the server
+// provides. According to third_party/openssl/openssl/ssl/ssl_lib.c, the
+// callback can assume that |in| is syntactically valid.
+int SSLClientSocketOpenSSL::SelectNextProtoCallback(unsigned char** out,
+ unsigned char* outlen,
+ const unsigned char* in,
+ unsigned int inlen) {
+#if defined(OPENSSL_NPN_NEGOTIATED)
+ if (ssl_config_.next_protos.empty()) {
+ *out = reinterpret_cast<uint8*>(
+ const_cast<char*>(kDefaultSupportedNPNProtocol));
+ *outlen = arraysize(kDefaultSupportedNPNProtocol) - 1;
+ npn_status_ = kNextProtoUnsupported;
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ // Assume there's no overlap between our protocols and the server's list.
+ npn_status_ = kNextProtoNoOverlap;
+
+ // For each protocol in server preference order, see if we support it.
+ for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
+ for (std::vector<std::string>::const_iterator
+ j = ssl_config_.next_protos.begin();
+ j != ssl_config_.next_protos.end(); ++j) {
+ if (in[i] == j->size() &&
+ memcmp(&in[i + 1], j->data(), in[i]) == 0) {
+ // We found a match.
+ *out = const_cast<unsigned char*>(in) + i + 1;
+ *outlen = in[i];
+ npn_status_ = kNextProtoNegotiated;
+ break;
+ }
+ }
+ if (npn_status_ == kNextProtoNegotiated)
+ break;
+ }
+
+ // If we didn't find a protocol, we select the first one from our list.
+ if (npn_status_ == kNextProtoNoOverlap) {
+ *out = reinterpret_cast<uint8*>(const_cast<char*>(
+ ssl_config_.next_protos[0].data()));
+ *outlen = ssl_config_.next_protos[0].size();
+ }
+
+ npn_proto_.assign(reinterpret_cast<const char*>(*out), *outlen);
+ server_protos_.assign(reinterpret_cast<const char*>(in), inlen);
+ DVLOG(2) << "next protocol: '" << npn_proto_ << "' status: " << npn_status_;
+#endif
+ return SSL_TLSEXT_ERR_OK;
+}
+
+int SSLClientSocketOpenSSL::DoVerifyCert(int result) {
+ DCHECK(server_cert_.get());
+ GotoState(STATE_VERIFY_CERT_COMPLETE);
+
+ CertStatus cert_status;
+ if (ssl_config_.IsAllowedBadCert(server_cert_.get(), &cert_status)) {
+ VLOG(1) << "Received an expected bad cert with status: " << cert_status;
+ server_cert_verify_result_.Reset();
+ server_cert_verify_result_.cert_status = cert_status;
+ server_cert_verify_result_.verified_cert = server_cert_;
+ return OK;
+ }
+
+ int flags = 0;
+ if (ssl_config_.rev_checking_enabled)
+ flags |= CertVerifier::VERIFY_REV_CHECKING_ENABLED;
+ if (ssl_config_.verify_ev_cert)
+ flags |= CertVerifier::VERIFY_EV_CERT;
+ if (ssl_config_.cert_io_enabled)
+ flags |= CertVerifier::VERIFY_CERT_IO_ENABLED;
+ if (ssl_config_.rev_checking_required_local_anchors)
+ flags |= CertVerifier::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS;
+ verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
+ return verifier_->Verify(
+ server_cert_.get(),
+ host_and_port_.host(),
+ flags,
+ NULL /* no CRL set */,
+ &server_cert_verify_result_,
+ base::Bind(&SSLClientSocketOpenSSL::OnHandshakeIOComplete,
+ base::Unretained(this)),
+ net_log_);
+}
+
+int SSLClientSocketOpenSSL::DoVerifyCertComplete(int result) {
+ verifier_.reset();
+
+ if (result == OK) {
+ // TODO(joth): Work out if we need to remember the intermediate CA certs
+ // when the server sends them to us, and do so here.
+ } else {
+ DVLOG(1) << "DoVerifyCertComplete error " << ErrorToString(result)
+ << " (" << result << ")";
+ }
+
+ completed_handshake_ = true;
+ // Exit DoHandshakeLoop and return the result to the caller to Connect.
+ DCHECK_EQ(STATE_NONE, next_handshake_state_);
+ return result;
+}
+
+X509Certificate* SSLClientSocketOpenSSL::UpdateServerCert() {
+ if (server_cert_.get())
+ return server_cert_.get();
+
+ crypto::ScopedOpenSSL<X509, X509_free> cert(SSL_get_peer_certificate(ssl_));
+ if (!cert.get()) {
+ LOG(WARNING) << "SSL_get_peer_certificate returned NULL";
+ return NULL;
+ }
+
+ // Unlike SSL_get_peer_certificate, SSL_get_peer_cert_chain does not
+ // increment the reference so sk_X509_free does not need to be called.
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl_);
+ X509Certificate::OSCertHandles intermediates;
+ if (chain) {
+ for (int i = 0; i < sk_X509_num(chain); ++i)
+ intermediates.push_back(sk_X509_value(chain, i));
+ }
+ server_cert_ = X509Certificate::CreateFromHandle(cert.get(), intermediates);
+ DCHECK(server_cert_.get());
+
+ return server_cert_.get();
+}
+
+bool SSLClientSocketOpenSSL::DoTransportIO() {
+ bool network_moved = false;
+ int rv;
+ // Read and write as much data as possible. The loop is necessary because
+ // Write() may return synchronously.
+ do {
+ rv = BufferSend();
+ if (rv != ERR_IO_PENDING && rv != 0)
+ network_moved = true;
+ } while (rv > 0);
+ if (!transport_recv_eof_ && BufferRecv() != ERR_IO_PENDING)
+ network_moved = true;
+ return network_moved;
+}
+
+int SSLClientSocketOpenSSL::BufferSend(void) {
+ if (transport_send_busy_)
+ return ERR_IO_PENDING;
+
+ if (!send_buffer_.get()) {
+ // Get a fresh send buffer out of the send BIO.
+ size_t max_read = BIO_ctrl_pending(transport_bio_);
+ if (!max_read)
+ return 0; // Nothing pending in the OpenSSL write BIO.
+ send_buffer_ = new DrainableIOBuffer(new IOBuffer(max_read), max_read);
+ int read_bytes = BIO_read(transport_bio_, send_buffer_->data(), max_read);
+ DCHECK_GT(read_bytes, 0);
+ CHECK_EQ(static_cast<int>(max_read), read_bytes);
+ }
+
+ int rv = transport_->socket()->Write(
+ send_buffer_.get(),
+ send_buffer_->BytesRemaining(),
+ base::Bind(&SSLClientSocketOpenSSL::BufferSendComplete,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ transport_send_busy_ = true;
+ } else {
+ TransportWriteComplete(rv);
+ }
+ return rv;
+}
+
+void SSLClientSocketOpenSSL::BufferSendComplete(int result) {
+ transport_send_busy_ = false;
+ TransportWriteComplete(result);
+ OnSendComplete(result);
+}
+
+void SSLClientSocketOpenSSL::TransportWriteComplete(int result) {
+ DCHECK(ERR_IO_PENDING != result);
+ if (result < 0) {
+ // Got a socket write error; close the BIO to indicate this upward.
+ DVLOG(1) << "TransportWriteComplete error " << result;
+ (void)BIO_shutdown_wr(transport_bio_);
+ BIO_set_mem_eof_return(transport_bio_, 0);
+ send_buffer_ = NULL;
+ } else {
+ DCHECK(send_buffer_.get());
+ send_buffer_->DidConsume(result);
+ DCHECK_GE(send_buffer_->BytesRemaining(), 0);
+ if (send_buffer_->BytesRemaining() <= 0)
+ send_buffer_ = NULL;
+ }
+}
+
+int SSLClientSocketOpenSSL::BufferRecv(void) {
+ if (transport_recv_busy_)
+ return ERR_IO_PENDING;
+
+ // Determine how much was requested from |transport_bio_| that was not
+ // actually available.
+ size_t requested = BIO_ctrl_get_read_request(transport_bio_);
+ if (requested == 0) {
+ // This is not a perfect match of error codes, as no operation is
+ // actually pending. However, returning 0 would be interpreted as
+ // a possible sign of EOF, which is also an inappropriate match.
+ return ERR_IO_PENDING;
+ }
+
+ // Known Issue: While only reading |requested| data is the more correct
+ // implementation, it has the downside of resulting in frequent reads:
+ // One read for the SSL record header (~5 bytes) and one read for the SSL
+ // record body. Rather than issuing these reads to the underlying socket
+ // (and constantly allocating new IOBuffers), a single Read() request to
+ // fill |transport_bio_| is issued. As long as an SSL client socket cannot
+ // be gracefully shutdown (via SSL close alerts) and re-used for non-SSL
+ // traffic, this over-subscribed Read()ing will not cause issues.
+ size_t max_write = BIO_ctrl_get_write_guarantee(transport_bio_);
+ if (!max_write)
+ return ERR_IO_PENDING;
+
+ recv_buffer_ = new IOBuffer(max_write);
+ int rv = transport_->socket()->Read(
+ recv_buffer_.get(),
+ max_write,
+ base::Bind(&SSLClientSocketOpenSSL::BufferRecvComplete,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ transport_recv_busy_ = true;
+ } else {
+ TransportReadComplete(rv);
+ }
+ return rv;
+}
+
+void SSLClientSocketOpenSSL::BufferRecvComplete(int result) {
+ TransportReadComplete(result);
+ OnRecvComplete(result);
+}
+
+void SSLClientSocketOpenSSL::TransportReadComplete(int result) {
+ DCHECK(ERR_IO_PENDING != result);
+ if (result <= 0) {
+ DVLOG(1) << "TransportReadComplete result " << result;
+ // Received 0 (end of file) or an error. Either way, bubble it up to the
+ // SSL layer via the BIO. TODO(joth): consider stashing the error code, to
+ // relay up to the SSL socket client (i.e. via DoReadCallback).
+ if (result == 0)
+ transport_recv_eof_ = true;
+ BIO_set_mem_eof_return(transport_bio_, 0);
+ (void)BIO_shutdown_wr(transport_bio_);
+ } else {
+ DCHECK(recv_buffer_.get());
+ int ret = BIO_write(transport_bio_, recv_buffer_->data(), result);
+ // A write into a memory BIO should always succeed.
+ CHECK_EQ(result, ret);
+ }
+ recv_buffer_ = NULL;
+ transport_recv_busy_ = false;
+}
+
+void SSLClientSocketOpenSSL::DoConnectCallback(int rv) {
+ if (!user_connect_callback_.is_null()) {
+ CompletionCallback c = user_connect_callback_;
+ user_connect_callback_.Reset();
+ c.Run(rv > OK ? OK : rv);
+ }
+}
+
+void SSLClientSocketOpenSSL::OnHandshakeIOComplete(int result) {
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_CONNECT, rv);
+ DoConnectCallback(rv);
+ }
+}
+
+void SSLClientSocketOpenSSL::OnSendComplete(int result) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ // In handshake phase.
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ // OnSendComplete may need to call DoPayloadRead while the renegotiation
+ // handshake is in progress.
+ int rv_read = ERR_IO_PENDING;
+ int rv_write = ERR_IO_PENDING;
+ bool network_moved;
+ do {
+ if (user_read_buf_.get())
+ rv_read = DoPayloadRead();
+ if (user_write_buf_.get())
+ rv_write = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
+ (user_read_buf_.get() || user_write_buf_.get()) && network_moved);
+
+ // Performing the Read callback may cause |this| to be deleted. If this
+ // happens, the Write callback should not be invoked. Guard against this by
+ // holding a WeakPtr to |this| and ensuring it's still valid.
+ base::WeakPtr<SSLClientSocketOpenSSL> guard(weak_factory_.GetWeakPtr());
+ if (user_read_buf_.get() && rv_read != ERR_IO_PENDING)
+ DoReadCallback(rv_read);
+
+ if (!guard.get())
+ return;
+
+ if (user_write_buf_.get() && rv_write != ERR_IO_PENDING)
+ DoWriteCallback(rv_write);
+}
+
+void SSLClientSocketOpenSSL::OnRecvComplete(int result) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ // In handshake phase.
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ // Network layer received some data, check if client requested to read
+ // decrypted data.
+ if (!user_read_buf_.get())
+ return;
+
+ int rv = DoReadLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoReadCallback(rv);
+}
+
+bool SSLClientSocketOpenSSL::IsConnected() const {
+ // If the handshake has not yet completed.
+ if (!completed_handshake_)
+ return false;
+ // If an asynchronous operation is still pending.
+ if (user_read_buf_.get() || user_write_buf_.get())
+ return true;
+
+ return transport_->socket()->IsConnected();
+}
+
+bool SSLClientSocketOpenSSL::IsConnectedAndIdle() const {
+ // If the handshake has not yet completed.
+ if (!completed_handshake_)
+ return false;
+ // If an asynchronous operation is still pending.
+ if (user_read_buf_.get() || user_write_buf_.get())
+ return false;
+ // If there is data waiting to be sent, or data read from the network that
+ // has not yet been consumed.
+ if (BIO_ctrl_pending(transport_bio_) > 0 ||
+ BIO_ctrl_wpending(transport_bio_) > 0) {
+ return false;
+ }
+
+ return transport_->socket()->IsConnectedAndIdle();
+}
+
+int SSLClientSocketOpenSSL::GetPeerAddress(IPEndPoint* addressList) const {
+ return transport_->socket()->GetPeerAddress(addressList);
+}
+
+int SSLClientSocketOpenSSL::GetLocalAddress(IPEndPoint* addressList) const {
+ return transport_->socket()->GetLocalAddress(addressList);
+}
+
+const BoundNetLog& SSLClientSocketOpenSSL::NetLog() const {
+ return net_log_;
+}
+
+void SSLClientSocketOpenSSL::SetSubresourceSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetSubresourceSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+void SSLClientSocketOpenSSL::SetOmniboxSpeculation() {
+ if (transport_.get() && transport_->socket()) {
+ transport_->socket()->SetOmniboxSpeculation();
+ } else {
+ NOTREACHED();
+ }
+}
+
+bool SSLClientSocketOpenSSL::WasEverUsed() const {
+ if (transport_.get() && transport_->socket())
+ return transport_->socket()->WasEverUsed();
+
+ NOTREACHED();
+ return false;
+}
+
+bool SSLClientSocketOpenSSL::UsingTCPFastOpen() const {
+ if (transport_.get() && transport_->socket())
+ return transport_->socket()->UsingTCPFastOpen();
+
+ NOTREACHED();
+ return false;
+}
+
+// Socket methods
+
+int SSLClientSocketOpenSSL::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ user_read_buf_ = buf;
+ user_read_buf_len_ = buf_len;
+
+ int rv = DoReadLoop(OK);
+
+ if (rv == ERR_IO_PENDING) {
+ user_read_callback_ = callback;
+ } else {
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketOpenSSL::DoReadLoop(int result) {
+ if (result < 0)
+ return result;
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadRead();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ return rv;
+}
+
+int SSLClientSocketOpenSSL::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ user_write_buf_ = buf;
+ user_write_buf_len_ = buf_len;
+
+ int rv = DoWriteLoop(OK);
+
+ if (rv == ERR_IO_PENDING) {
+ user_write_callback_ = callback;
+ } else {
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ }
+
+ return rv;
+}
+
+int SSLClientSocketOpenSSL::DoWriteLoop(int result) {
+ if (result < 0)
+ return result;
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+
+ return rv;
+}
+
+bool SSLClientSocketOpenSSL::SetReceiveBufferSize(int32 size) {
+ return transport_->socket()->SetReceiveBufferSize(size);
+}
+
+bool SSLClientSocketOpenSSL::SetSendBufferSize(int32 size) {
+ return transport_->socket()->SetSendBufferSize(size);
+}
+
+int SSLClientSocketOpenSSL::DoPayloadRead() {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+
+ int rv;
+ if (pending_read_error_ != kNoPendingReadResult) {
+ rv = pending_read_error_;
+ pending_read_error_ = kNoPendingReadResult;
+ if (rv == 0) {
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED,
+ rv, user_read_buf_->data());
+ }
+ return rv;
+ }
+
+ int total_bytes_read = 0;
+ do {
+ rv = SSL_read(ssl_, user_read_buf_->data() + total_bytes_read,
+ user_read_buf_len_ - total_bytes_read);
+ if (rv > 0)
+ total_bytes_read += rv;
+ } while (total_bytes_read < user_read_buf_len_ && rv > 0);
+
+ if (total_bytes_read == user_read_buf_len_) {
+ rv = total_bytes_read;
+ } else {
+ // Otherwise, an error occurred (rv <= 0). The error needs to be handled
+ // immediately, while the OpenSSL errors are still available in
+ // thread-local storage. However, the handled/remapped error code should
+ // only be returned if no application data was already read; if it was, the
+ // error code should be deferred until the next call of DoPayloadRead.
+ //
+ // If no data was read, |*next_result| will point to the return value of
+ // this function. If at least some data was read, |*next_result| will point
+ // to |pending_read_error_|, to be returned in a future call to
+ // DoPayloadRead() (e.g.: after the current data is handled).
+ int *next_result = &rv;
+ if (total_bytes_read > 0) {
+ pending_read_error_ = rv;
+ rv = total_bytes_read;
+ next_result = &pending_read_error_;
+ }
+
+ if (client_auth_cert_needed_) {
+ *next_result = ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ } else if (*next_result < 0) {
+ int err = SSL_get_error(ssl_, *next_result);
+ *next_result = MapOpenSSLError(err, err_tracer);
+ if (rv > 0 && *next_result == ERR_IO_PENDING) {
+ // If at least some data was read from SSL_read(), do not treat
+ // insufficient data as an error to return in the next call to
+ // DoPayloadRead() - instead, let the call fall through to check
+ // SSL_read() again. This is because DoTransportIO() may complete
+ // in between the next call to DoPayloadRead(), and thus it is
+ // important to check SSL_read() on subsequent invocations to see
+ // if a complete record may now be read.
+ *next_result = kNoPendingReadResult;
+ }
+ }
+ }
+
+ if (rv >= 0) {
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED, rv,
+ user_read_buf_->data());
+ }
+ return rv;
+}
+
+int SSLClientSocketOpenSSL::DoPayloadWrite() {
+ crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
+ int rv = SSL_write(ssl_, user_write_buf_->data(), user_write_buf_len_);
+
+ if (rv >= 0) {
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SSL_SOCKET_BYTES_SENT, rv,
+ user_write_buf_->data());
+ return rv;
+ }
+
+ int err = SSL_get_error(ssl_, rv);
+ return MapOpenSSLError(err, err_tracer);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket_openssl.h b/chromium/net/socket/ssl_client_socket_openssl.h
new file mode 100644
index 00000000000..f66d95cc69d
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_openssl.h
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_OPENSSL_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_OPENSSL_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/ssl/ssl_config_service.h"
+
+// Avoid including misc OpenSSL headers, i.e.:
+// <openssl/bio.h>
+typedef struct bio_st BIO;
+// <openssl/evp.h>
+typedef struct evp_pkey_st EVP_PKEY;
+// <openssl/ssl.h>
+typedef struct ssl_st SSL;
+// <openssl/x509.h>
+typedef struct x509_st X509;
+
+namespace net {
+
+class CertVerifier;
+class SingleRequestCertVerifier;
+class SSLCertRequestInfo;
+class SSLInfo;
+
+// An SSL client socket implemented with OpenSSL.
+class SSLClientSocketOpenSSL : public SSLClientSocket {
+ public:
+ // Takes ownership of the transport_socket, which may already be connected.
+ // The given hostname will be compared with the name(s) in the server's
+ // certificate during the SSL handshake. ssl_config specifies the SSL
+ // settings.
+ SSLClientSocketOpenSSL(scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context);
+ virtual ~SSLClientSocketOpenSSL();
+
+ const HostPortPair& host_and_port() const { return host_and_port_; }
+ const std::string& ssl_session_cache_shard() const {
+ return ssl_session_cache_shard_;
+ }
+
+ // Callback from the SSL layer that indicates the remote server is requesting
+ // a certificate for this client.
+ int ClientCertRequestCallback(SSL* ssl, X509** x509, EVP_PKEY** pkey);
+
+ // Callback from the SSL layer to check which NPN protocol we are supporting
+ int SelectNextProtoCallback(unsigned char** out, unsigned char* outlen,
+ const unsigned char* in, unsigned int inlen);
+
+ // SSLClientSocket implementation.
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual NextProtoStatus GetNextProto(std::string* proto,
+ std::string* server_protos) OVERRIDE;
+ virtual ServerBoundCertService* GetServerBoundCertService() const OVERRIDE;
+
+ // SSLSocket implementation.
+ virtual int ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) OVERRIDE;
+ virtual int GetTLSUniqueChannelBinding(std::string* out) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ private:
+ bool Init();
+ void DoReadCallback(int result);
+ void DoWriteCallback(int result);
+
+ bool DoTransportIO();
+ int DoHandshake();
+ int DoVerifyCert(int result);
+ int DoVerifyCertComplete(int result);
+ void DoConnectCallback(int result);
+ X509Certificate* UpdateServerCert();
+
+ void OnHandshakeIOComplete(int result);
+ void OnSendComplete(int result);
+ void OnRecvComplete(int result);
+
+ int DoHandshakeLoop(int last_io_result);
+ int DoReadLoop(int result);
+ int DoWriteLoop(int result);
+ int DoPayloadRead();
+ int DoPayloadWrite();
+
+ int BufferSend();
+ int BufferRecv();
+ void BufferSendComplete(int result);
+ void BufferRecvComplete(int result);
+ void TransportWriteComplete(int result);
+ void TransportReadComplete(int result);
+
+ bool transport_send_busy_;
+ bool transport_recv_busy_;
+ bool transport_recv_eof_;
+
+ scoped_refptr<DrainableIOBuffer> send_buffer_;
+ scoped_refptr<IOBuffer> recv_buffer_;
+
+ CompletionCallback user_connect_callback_;
+ CompletionCallback user_read_callback_;
+ CompletionCallback user_write_callback_;
+
+ base::WeakPtrFactory<SSLClientSocketOpenSSL> weak_factory_;
+
+ // Used by Read function.
+ scoped_refptr<IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ // Used by Write function.
+ scoped_refptr<IOBuffer> user_write_buf_;
+ int user_write_buf_len_;
+
+ // Used by DoPayloadRead() when attempting to fill the caller's buffer with
+ // as much data as possible without blocking.
+ // If DoPayloadRead() encounters an error after having read some data, stores
+ // the result to return on the *next* call to DoPayloadRead(). A value > 0
+ // indicates there is no pending result, otherwise 0 indicates EOF and < 0
+ // indicates an error.
+ int pending_read_error_;
+
+ // Set when handshake finishes.
+ scoped_refptr<X509Certificate> server_cert_;
+ CertVerifyResult server_cert_verify_result_;
+ bool completed_handshake_;
+
+ // Stores client authentication information between ClientAuthHandler and
+ // GetSSLCertRequestInfo calls.
+ bool client_auth_cert_needed_;
+ // List of DER-encoded X.509 DistinguishedName of certificate authorities
+ // allowed by the server.
+ std::vector<std::string> cert_authorities_;
+
+ CertVerifier* const cert_verifier_;
+ scoped_ptr<SingleRequestCertVerifier> verifier_;
+
+ // OpenSSL stuff
+ SSL* ssl_;
+ BIO* transport_bio_;
+
+ scoped_ptr<ClientSocketHandle> transport_;
+ const HostPortPair host_and_port_;
+ SSLConfig ssl_config_;
+ // ssl_session_cache_shard_ is an opaque string that partitions the SSL
+ // session cache. i.e. sessions created with one value will not attempt to
+ // resume on the socket with a different value.
+ const std::string ssl_session_cache_shard_;
+
+ // Used for session cache diagnostics.
+ bool trying_cached_session_;
+
+ enum State {
+ STATE_NONE,
+ STATE_HANDSHAKE,
+ STATE_VERIFY_CERT,
+ STATE_VERIFY_CERT_COMPLETE,
+ };
+ State next_handshake_state_;
+ NextProtoStatus npn_status_;
+ std::string npn_proto_;
+ std::string server_protos_;
+ BoundNetLog net_log_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_OPENSSL_H_
diff --git a/chromium/net/socket/ssl_client_socket_openssl_unittest.cc b/chromium/net/socket/ssl_client_socket_openssl_unittest.cc
new file mode 100644
index 00000000000..04f899903ac
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_openssl_unittest.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_client_socket.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_handle.h"
+#include "base/values.h"
+#include "crypto/openssl_util.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/cert/test_root_certs.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/ssl/openssl_client_key_store.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+typedef OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY;
+
+// BIO_free is a macro, it can't be used as a template parameter.
+void BIO_free_func(BIO* bio) {
+ BIO_free(bio);
+}
+
+typedef crypto::ScopedOpenSSL<BIO, BIO_free_func> ScopedBIO;
+typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA;
+typedef crypto::ScopedOpenSSL<BIGNUM, BN_free> ScopedBIGNUM;
+
+const SSLConfig kDefaultSSLConfig;
+
+// Loads a PEM-encoded private key file into a scoped EVP_PKEY object.
+// |filepath| is the private key file path.
+// |*pkey| is reset to the new EVP_PKEY on success, untouched otherwise.
+// Returns true on success, false on failure.
+bool LoadPrivateKeyOpenSSL(
+ const base::FilePath& filepath,
+ OpenSSLClientKeyStore::ScopedEVP_PKEY* pkey) {
+ std::string data;
+ if (!file_util::ReadFileToString(filepath, &data)) {
+ LOG(ERROR) << "Could not read private key file: "
+ << filepath.value() << ": " << strerror(errno);
+ return false;
+ }
+ ScopedBIO bio(
+ BIO_new_mem_buf(
+ const_cast<char*>(reinterpret_cast<const char*>(data.data())),
+ static_cast<int>(data.size())));
+ if (!bio.get()) {
+ LOG(ERROR) << "Could not allocate BIO for buffer?";
+ return false;
+ }
+ EVP_PKEY* result = PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL);
+ if (result == NULL) {
+ LOG(ERROR) << "Could not decode private key file: "
+ << filepath.value();
+ return false;
+ }
+ pkey->reset(result);
+ return true;
+}
+
+class SSLClientSocketOpenSSLClientAuthTest : public PlatformTest {
+ public:
+ SSLClientSocketOpenSSLClientAuthTest()
+ : socket_factory_(net::ClientSocketFactory::GetDefaultFactory()),
+ cert_verifier_(new net::MockCertVerifier),
+ transport_security_state_(new net::TransportSecurityState) {
+ cert_verifier_->set_default_result(net::OK);
+ context_.cert_verifier = cert_verifier_.get();
+ context_.transport_security_state = transport_security_state_.get();
+ key_store_ = net::OpenSSLClientKeyStore::GetInstance();
+ }
+
+ virtual ~SSLClientSocketOpenSSLClientAuthTest() {
+ key_store_->Flush();
+ }
+
+ protected:
+ scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<StreamSocket> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config) {
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ connection->SetSocket(transport_socket.Pass());
+ return socket_factory_->CreateSSLClientSocket(connection.Pass(),
+ host_and_port,
+ ssl_config,
+ context_);
+ }
+
+ // Connect to a HTTPS test server.
+ bool ConnectToTestServer(SpawnedTestServer::SSLOptions& ssl_options) {
+ test_server_.reset(new SpawnedTestServer(SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath()));
+ if (!test_server_->Start()) {
+ LOG(ERROR) << "Could not start SpawnedTestServer";
+ return false;
+ }
+
+ if (!test_server_->GetAddressList(&addr_)) {
+ LOG(ERROR) << "Could not get SpawnedTestServer address list";
+ return false;
+ }
+
+ transport_.reset(new TCPClientSocket(
+ addr_, &log_, NetLog::Source()));
+ int rv = callback_.GetResult(
+ transport_->Connect(callback_.callback()));
+ if (rv != OK) {
+ LOG(ERROR) << "Could not connect to SpawnedTestServer";
+ return false;
+ }
+ return true;
+ }
+
+ // Record a certificate's private key to ensure it can be used
+ // by the OpenSSL-based SSLClientSocket implementation.
+ // |ssl_config| provides a client certificate.
+ // |private_key| must be an EVP_PKEY for the corresponding private key.
+ // Returns true on success, false on failure.
+ bool RecordPrivateKey(SSLConfig& ssl_config,
+ EVP_PKEY* private_key) {
+ return key_store_->RecordClientCertPrivateKey(
+ ssl_config.client_cert.get(), private_key);
+ }
+
+ // Create an SSLClientSocket object and use it to connect to a test
+ // server, then wait for connection results. This must be called after
+ // a succesful ConnectToTestServer() call.
+ // |ssl_config| the SSL configuration to use.
+ // |result| will retrieve the ::Connect() result value.
+ // Returns true on succes, false otherwise. Success means that the socket
+ // could be created and its Connect() was called, not that the connection
+ // itself was a success.
+ bool CreateAndConnectSSLClientSocket(SSLConfig& ssl_config,
+ int* result) {
+ sock_ = CreateSSLClientSocket(transport_.Pass(),
+ test_server_->host_port_pair(),
+ ssl_config);
+
+ if (sock_->IsConnected()) {
+ LOG(ERROR) << "SSL Socket prematurely connected";
+ return false;
+ }
+
+ *result = callback_.GetResult(sock_->Connect(callback_.callback()));
+ return true;
+ }
+
+
+ // Check that the client certificate was sent.
+ // Returns true on success.
+ bool CheckSSLClientSocketSentCert() {
+ SSLInfo ssl_info;
+ sock_->GetSSLInfo(&ssl_info);
+ return ssl_info.client_cert_sent;
+ }
+
+ ClientSocketFactory* socket_factory_;
+ scoped_ptr<MockCertVerifier> cert_verifier_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+ SSLClientSocketContext context_;
+ OpenSSLClientKeyStore* key_store_;
+ scoped_ptr<SpawnedTestServer> test_server_;
+ AddressList addr_;
+ TestCompletionCallback callback_;
+ CapturingNetLog log_;
+ scoped_ptr<StreamSocket> transport_;
+ scoped_ptr<SSLClientSocket> sock_;
+};
+
+// Connect to a server requesting client authentication, do not send
+// any client certificates. It should refuse the connection.
+TEST_F(SSLClientSocketOpenSSLClientAuthTest, NoCert) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+
+ ASSERT_TRUE(ConnectToTestServer(ssl_options));
+
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ SSLConfig ssl_config = kDefaultSSLConfig;
+
+ int rv;
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+
+ EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
+ EXPECT_FALSE(sock_->IsConnected());
+}
+
+// Connect to a server requesting client authentication, and send it
+// an empty certificate. It should refuse the connection.
+TEST_F(SSLClientSocketOpenSSLClientAuthTest, SendEmptyCert) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ssl_options.client_authorities.push_back(
+ GetTestClientCertsDirectory().AppendASCII("client_1_ca.pem"));
+
+ ASSERT_TRUE(ConnectToTestServer(ssl_options));
+
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ SSLConfig ssl_config = kDefaultSSLConfig;
+ ssl_config.send_client_cert = true;
+ ssl_config.client_cert = NULL;
+
+ int rv;
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock_->IsConnected());
+}
+
+// Connect to a server requesting client authentication. Send it a
+// matching certificate. It should allow the connection.
+TEST_F(SSLClientSocketOpenSSLClientAuthTest, SendGoodCert) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ssl_options.client_authorities.push_back(
+ GetTestClientCertsDirectory().AppendASCII("client_1_ca.pem"));
+
+ ASSERT_TRUE(ConnectToTestServer(ssl_options));
+
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ SSLConfig ssl_config = kDefaultSSLConfig;
+ ssl_config.send_client_cert = true;
+ ssl_config.client_cert = ImportCertFromFile(certs_dir, "client_1.pem");
+
+ // This is required to ensure that signing works with the client
+ // certificate's private key.
+ OpenSSLClientKeyStore::ScopedEVP_PKEY client_private_key;
+ ASSERT_TRUE(LoadPrivateKeyOpenSSL(certs_dir.AppendASCII("client_1.key"),
+ &client_private_key));
+ EXPECT_TRUE(RecordPrivateKey(ssl_config, client_private_key.get()));
+
+ int rv;
+ ASSERT_TRUE(CreateAndConnectSSLClientSocket(ssl_config, &rv));
+
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock_->IsConnected());
+
+ EXPECT_TRUE(CheckSSLClientSocketSentCert());
+
+ sock_->Disconnect();
+ EXPECT_FALSE(sock_->IsConnected());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket_pool.cc b/chromium/net/socket/ssl_client_socket_pool.cc
new file mode 100644
index 00000000000..d07c76ffb49
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_pool.cc
@@ -0,0 +1,664 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_client_socket_pool.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_proxy_client_socket.h"
+#include "net/http/http_proxy_client_socket_pool.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socks_client_socket_pool.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+
+SSLSocketParams::SSLSocketParams(
+ const scoped_refptr<TransportSocketParams>& transport_params,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params,
+ ProxyServer::Scheme proxy,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ PrivacyMode privacy_mode,
+ int load_flags,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn)
+ : transport_params_(transport_params),
+ http_proxy_params_(http_proxy_params),
+ socks_params_(socks_params),
+ proxy_(proxy),
+ host_and_port_(host_and_port),
+ ssl_config_(ssl_config),
+ privacy_mode_(privacy_mode),
+ load_flags_(load_flags),
+ force_spdy_over_ssl_(force_spdy_over_ssl),
+ want_spdy_over_npn_(want_spdy_over_npn),
+ ignore_limits_(false) {
+ switch (proxy_) {
+ case ProxyServer::SCHEME_DIRECT:
+ DCHECK(transport_params_.get() != NULL);
+ DCHECK(http_proxy_params_.get() == NULL);
+ DCHECK(socks_params_.get() == NULL);
+ ignore_limits_ = transport_params_->ignore_limits();
+ break;
+ case ProxyServer::SCHEME_HTTP:
+ case ProxyServer::SCHEME_HTTPS:
+ DCHECK(transport_params_.get() == NULL);
+ DCHECK(http_proxy_params_.get() != NULL);
+ DCHECK(socks_params_.get() == NULL);
+ ignore_limits_ = http_proxy_params_->ignore_limits();
+ break;
+ case ProxyServer::SCHEME_SOCKS4:
+ case ProxyServer::SCHEME_SOCKS5:
+ DCHECK(transport_params_.get() == NULL);
+ DCHECK(http_proxy_params_.get() == NULL);
+ DCHECK(socks_params_.get() != NULL);
+ ignore_limits_ = socks_params_->ignore_limits();
+ break;
+ default:
+ LOG(DFATAL) << "unknown proxy type";
+ break;
+ }
+}
+
+SSLSocketParams::~SSLSocketParams() {}
+
+// Timeout for the SSL handshake portion of the connect.
+static const int kSSLHandshakeTimeoutInSeconds = 30;
+
+SSLConnectJob::SSLConnectJob(const std::string& group_name,
+ const scoped_refptr<SSLSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ const SSLClientSocketContext& context,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name,
+ timeout_duration,
+ delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ params_(params),
+ transport_pool_(transport_pool),
+ socks_pool_(socks_pool),
+ http_proxy_pool_(http_proxy_pool),
+ client_socket_factory_(client_socket_factory),
+ host_resolver_(host_resolver),
+ context_(context.cert_verifier,
+ context.server_bound_cert_service,
+ context.transport_security_state,
+ (params->privacy_mode() == kPrivacyModeEnabled
+ ? "pm/" + context.ssl_session_cache_shard
+ : context.ssl_session_cache_shard)),
+ callback_(base::Bind(&SSLConnectJob::OnIOComplete,
+ base::Unretained(this))) {}
+
+SSLConnectJob::~SSLConnectJob() {}
+
+LoadState SSLConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_TUNNEL_CONNECT_COMPLETE:
+ if (transport_socket_handle_->socket())
+ return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
+ // else, fall through.
+ case STATE_TRANSPORT_CONNECT:
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ case STATE_SOCKS_CONNECT:
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ case STATE_TUNNEL_CONNECT:
+ return transport_socket_handle_->GetLoadState();
+ case STATE_SSL_CONNECT:
+ case STATE_SSL_CONNECT_COMPLETE:
+ return LOAD_STATE_SSL_HANDSHAKE;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+void SSLConnectJob::GetAdditionalErrorState(ClientSocketHandle* handle) {
+ // Headers in |error_response_info_| indicate a proxy tunnel setup
+ // problem. See DoTunnelConnectComplete.
+ if (error_response_info_.headers.get()) {
+ handle->set_pending_http_proxy_connection(
+ transport_socket_handle_.release());
+ }
+ handle->set_ssl_error_response_info(error_response_info_);
+ if (!connect_timing_.ssl_start.is_null())
+ handle->set_is_ssl_error(true);
+}
+
+void SSLConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|.
+}
+
+int SSLConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_TRANSPORT_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTransportConnect();
+ break;
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ rv = DoTransportConnectComplete(rv);
+ break;
+ case STATE_SOCKS_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSOCKSConnect();
+ break;
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ rv = DoSOCKSConnectComplete(rv);
+ break;
+ case STATE_TUNNEL_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTunnelConnect();
+ break;
+ case STATE_TUNNEL_CONNECT_COMPLETE:
+ rv = DoTunnelConnectComplete(rv);
+ break;
+ case STATE_SSL_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoSSLConnect();
+ break;
+ case STATE_SSL_CONNECT_COMPLETE:
+ rv = DoSSLConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int SSLConnectJob::DoTransportConnect() {
+ DCHECK(transport_pool_);
+
+ next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<TransportSocketParams> transport_params =
+ params_->transport_params();
+ return transport_socket_handle_->Init(
+ group_name(), transport_params,
+ transport_params->destination().priority(), callback_, transport_pool_,
+ net_log());
+}
+
+int SSLConnectJob::DoTransportConnectComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_SSL_CONNECT;
+
+ return result;
+}
+
+int SSLConnectJob::DoSOCKSConnect() {
+ DCHECK(socks_pool_);
+ next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<SOCKSSocketParams> socks_params = params_->socks_params();
+ return transport_socket_handle_->Init(
+ group_name(), socks_params, socks_params->destination().priority(),
+ callback_, socks_pool_, net_log());
+}
+
+int SSLConnectJob::DoSOCKSConnectComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_SSL_CONNECT;
+
+ return result;
+}
+
+int SSLConnectJob::DoTunnelConnect() {
+ DCHECK(http_proxy_pool_);
+ next_state_ = STATE_TUNNEL_CONNECT_COMPLETE;
+
+ transport_socket_handle_.reset(new ClientSocketHandle());
+ scoped_refptr<HttpProxySocketParams> http_proxy_params =
+ params_->http_proxy_params();
+ return transport_socket_handle_->Init(
+ group_name(), http_proxy_params,
+ http_proxy_params->destination().priority(), callback_, http_proxy_pool_,
+ net_log());
+}
+
+int SSLConnectJob::DoTunnelConnectComplete(int result) {
+ // Extract the information needed to prompt for appropriate proxy
+ // authentication so that when ClientSocketPoolBaseHelper calls
+ // |GetAdditionalErrorState|, we can easily set the state.
+ if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ error_response_info_ = transport_socket_handle_->ssl_error_response_info();
+ } else if (result == ERR_PROXY_AUTH_REQUESTED ||
+ result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
+ StreamSocket* socket = transport_socket_handle_->socket();
+ HttpProxyClientSocket* tunnel_socket =
+ static_cast<HttpProxyClientSocket*>(socket);
+ error_response_info_ = *tunnel_socket->GetConnectResponseInfo();
+ }
+ if (result < 0)
+ return result;
+
+ next_state_ = STATE_SSL_CONNECT;
+ return result;
+}
+
+int SSLConnectJob::DoSSLConnect() {
+ next_state_ = STATE_SSL_CONNECT_COMPLETE;
+ // Reset the timeout to just the time allowed for the SSL handshake.
+ ResetTimer(base::TimeDelta::FromSeconds(kSSLHandshakeTimeoutInSeconds));
+
+ // If the handle has a fresh socket, get its connect start and DNS times.
+ // This should always be the case.
+ const LoadTimingInfo::ConnectTiming& socket_connect_timing =
+ transport_socket_handle_->connect_timing();
+ if (!transport_socket_handle_->is_reused() &&
+ !socket_connect_timing.connect_start.is_null()) {
+ // Overwriting |connect_start| serves two purposes - it adjusts timing so
+ // |connect_start| doesn't include dns times, and it adjusts the time so
+ // as not to include time spent waiting for an idle socket.
+ connect_timing_.connect_start = socket_connect_timing.connect_start;
+ connect_timing_.dns_start = socket_connect_timing.dns_start;
+ connect_timing_.dns_end = socket_connect_timing.dns_end;
+ }
+
+ connect_timing_.ssl_start = base::TimeTicks::Now();
+
+ ssl_socket_ = client_socket_factory_->CreateSSLClientSocket(
+ transport_socket_handle_.Pass(),
+ params_->host_and_port(),
+ params_->ssl_config(),
+ context_);
+ return ssl_socket_->Connect(callback_);
+}
+
+int SSLConnectJob::DoSSLConnectComplete(int result) {
+ connect_timing_.ssl_end = base::TimeTicks::Now();
+
+ SSLClientSocket::NextProtoStatus status =
+ SSLClientSocket::kNextProtoUnsupported;
+ std::string proto;
+ std::string server_protos;
+ // GetNextProto will fail and and trigger a NOTREACHED if we pass in a socket
+ // that hasn't had SSL_ImportFD called on it. If we get a certificate error
+ // here, then we know that we called SSL_ImportFD.
+ if (result == OK || IsCertificateError(result))
+ status = ssl_socket_->GetNextProto(&proto, &server_protos);
+
+ // If we want spdy over npn, make sure it succeeded.
+ if (status == SSLClientSocket::kNextProtoNegotiated) {
+ ssl_socket_->set_was_npn_negotiated(true);
+ NextProto protocol_negotiated =
+ SSLClientSocket::NextProtoFromString(proto);
+ ssl_socket_->set_protocol_negotiated(protocol_negotiated);
+ // If we negotiated a SPDY version, it must have been present in
+ // SSLConfig::next_protos.
+ // TODO(mbelshe): Verify this.
+ if (protocol_negotiated >= kProtoSPDYMinimumVersion &&
+ protocol_negotiated <= kProtoSPDYMaximumVersion) {
+ ssl_socket_->set_was_spdy_negotiated(true);
+ }
+ }
+ if (params_->want_spdy_over_npn() && !ssl_socket_->was_spdy_negotiated())
+ return ERR_NPN_NEGOTIATION_FAILED;
+
+ // Spdy might be turned on by default, or it might be over npn.
+ bool using_spdy = params_->force_spdy_over_ssl() ||
+ params_->want_spdy_over_npn();
+
+ if (result == OK ||
+ ssl_socket_->IgnoreCertError(result, params_->load_flags())) {
+ DCHECK(!connect_timing_.ssl_start.is_null());
+ base::TimeDelta connect_duration =
+ connect_timing_.ssl_end - connect_timing_.ssl_start;
+ if (using_spdy) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SpdyConnectionLatency_2",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ }
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+ bool using_data_reduction_proxy = params_->host_and_port().Equals(
+ HostPortPair::FromURL(GURL(SPDY_PROXY_AUTH_ORIGIN)));
+ if (using_data_reduction_proxy) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.SSL_Connection_Latency_DataReductionProxy",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ }
+#endif
+
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_2",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+
+ SSLInfo ssl_info;
+ ssl_socket_->GetSSLInfo(&ssl_info);
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.SSL_CipherSuite",
+ SSLConnectionStatusToCipherSuite(
+ ssl_info.connection_status));
+
+ if (ssl_info.handshake_type == SSLInfo::HANDSHAKE_RESUME) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_Resume_Handshake",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ } else if (ssl_info.handshake_type == SSLInfo::HANDSHAKE_FULL) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_Full_Handshake",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ }
+
+ const std::string& host = params_->host_and_port().host();
+ bool is_google = host == "google.com" ||
+ (host.size() > 11 &&
+ host.rfind(".google.com") == host.size() - 11);
+ if (is_google) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_Google2",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ if (ssl_info.handshake_type == SSLInfo::HANDSHAKE_RESUME) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_Google_"
+ "Resume_Handshake",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ } else if (ssl_info.handshake_type == SSLInfo::HANDSHAKE_FULL) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.SSL_Connection_Latency_Google_"
+ "Full_Handshake",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 100);
+ }
+ }
+ }
+
+ if (result == OK || IsCertificateError(result)) {
+ SetSocket(ssl_socket_.PassAs<StreamSocket>());
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ error_response_info_.cert_request_info = new SSLCertRequestInfo;
+ ssl_socket_->GetSSLCertRequestInfo(
+ error_response_info_.cert_request_info.get());
+ }
+
+ return result;
+}
+
+int SSLConnectJob::ConnectInternal() {
+ switch (params_->proxy()) {
+ case ProxyServer::SCHEME_DIRECT:
+ next_state_ = STATE_TRANSPORT_CONNECT;
+ break;
+ case ProxyServer::SCHEME_HTTP:
+ case ProxyServer::SCHEME_HTTPS:
+ next_state_ = STATE_TUNNEL_CONNECT;
+ break;
+ case ProxyServer::SCHEME_SOCKS4:
+ case ProxyServer::SCHEME_SOCKS5:
+ next_state_ = STATE_SOCKS_CONNECT;
+ break;
+ default:
+ NOTREACHED() << "unknown proxy type";
+ break;
+ }
+ return DoLoop(OK);
+}
+
+SSLClientSocketPool::SSLConnectJobFactory::SSLConnectJobFactory(
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ const SSLClientSocketContext& context,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ socks_pool_(socks_pool),
+ http_proxy_pool_(http_proxy_pool),
+ client_socket_factory_(client_socket_factory),
+ host_resolver_(host_resolver),
+ context_(context),
+ net_log_(net_log) {
+ base::TimeDelta max_transport_timeout = base::TimeDelta();
+ base::TimeDelta pool_timeout;
+ if (transport_pool_)
+ max_transport_timeout = transport_pool_->ConnectionTimeout();
+ if (socks_pool_) {
+ pool_timeout = socks_pool_->ConnectionTimeout();
+ if (pool_timeout > max_transport_timeout)
+ max_transport_timeout = pool_timeout;
+ }
+ if (http_proxy_pool_) {
+ pool_timeout = http_proxy_pool_->ConnectionTimeout();
+ if (pool_timeout > max_transport_timeout)
+ max_transport_timeout = pool_timeout;
+ }
+ timeout_ = max_transport_timeout +
+ base::TimeDelta::FromSeconds(kSSLHandshakeTimeoutInSeconds);
+}
+
+SSLClientSocketPool::SSLClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier,
+ ServerBoundCertService* server_bound_cert_service,
+ TransportSecurityState* transport_security_state,
+ const std::string& ssl_session_cache_shard,
+ ClientSocketFactory* client_socket_factory,
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ SSLConfigService* ssl_config_service,
+ NetLog* net_log)
+ : transport_pool_(transport_pool),
+ socks_pool_(socks_pool),
+ http_proxy_pool_(http_proxy_pool),
+ base_(max_sockets, max_sockets_per_group, histograms,
+ ClientSocketPool::unused_idle_socket_timeout(),
+ ClientSocketPool::used_idle_socket_timeout(),
+ new SSLConnectJobFactory(transport_pool,
+ socks_pool,
+ http_proxy_pool,
+ client_socket_factory,
+ host_resolver,
+ SSLClientSocketContext(
+ cert_verifier,
+ server_bound_cert_service,
+ transport_security_state,
+ ssl_session_cache_shard),
+ net_log)),
+ ssl_config_service_(ssl_config_service) {
+ if (ssl_config_service_.get())
+ ssl_config_service_->AddObserver(this);
+ if (transport_pool_)
+ transport_pool_->AddLayeredPool(this);
+ if (socks_pool_)
+ socks_pool_->AddLayeredPool(this);
+ if (http_proxy_pool_)
+ http_proxy_pool_->AddLayeredPool(this);
+}
+
+SSLClientSocketPool::~SSLClientSocketPool() {
+ if (http_proxy_pool_)
+ http_proxy_pool_->RemoveLayeredPool(this);
+ if (socks_pool_)
+ socks_pool_->RemoveLayeredPool(this);
+ if (transport_pool_)
+ transport_pool_->RemoveLayeredPool(this);
+ if (ssl_config_service_.get())
+ ssl_config_service_->RemoveObserver(this);
+}
+
+scoped_ptr<ConnectJob>
+SSLClientSocketPool::SSLConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return scoped_ptr<ConnectJob>(
+ new SSLConnectJob(group_name, request.params(), ConnectionTimeout(),
+ transport_pool_, socks_pool_, http_proxy_pool_,
+ client_socket_factory_, host_resolver_,
+ context_, delegate, net_log_));
+}
+
+base::TimeDelta
+SSLClientSocketPool::SSLConnectJobFactory::ConnectionTimeout() const {
+ return timeout_;
+}
+
+int SSLClientSocketPool::RequestSocket(const std::string& group_name,
+ const void* socket_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<SSLSocketParams>* casted_socket_params =
+ static_cast<const scoped_refptr<SSLSocketParams>*>(socket_params);
+
+ return base_.RequestSocket(group_name, *casted_socket_params, priority,
+ handle, callback, net_log);
+}
+
+void SSLClientSocketPool::RequestSockets(
+ const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<SSLSocketParams>* casted_params =
+ static_cast<const scoped_refptr<SSLSocketParams>*>(params);
+
+ base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
+}
+
+void SSLClientSocketPool::CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void SSLClientSocketPool::ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket.Pass(), id);
+}
+
+void SSLClientSocketPool::FlushWithError(int error) {
+ base_.FlushWithError(error);
+}
+
+bool SSLClientSocketPool::IsStalled() const {
+ return base_.IsStalled() ||
+ (transport_pool_ && transport_pool_->IsStalled()) ||
+ (socks_pool_ && socks_pool_->IsStalled()) ||
+ (http_proxy_pool_ && http_proxy_pool_->IsStalled());
+}
+
+void SSLClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int SSLClientSocketPool::IdleSocketCount() const {
+ return base_.idle_socket_count();
+}
+
+int SSLClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState SSLClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+void SSLClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
+ base_.AddLayeredPool(layered_pool);
+}
+
+void SSLClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
+ base_.RemoveLayeredPool(layered_pool);
+}
+
+base::DictionaryValue* SSLClientSocketPool::GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const {
+ base::DictionaryValue* dict = base_.GetInfoAsValue(name, type);
+ if (include_nested_pools) {
+ base::ListValue* list = new base::ListValue();
+ if (transport_pool_) {
+ list->Append(transport_pool_->GetInfoAsValue("transport_socket_pool",
+ "transport_socket_pool",
+ false));
+ }
+ if (socks_pool_) {
+ list->Append(socks_pool_->GetInfoAsValue("socks_pool",
+ "socks_pool",
+ true));
+ }
+ if (http_proxy_pool_) {
+ list->Append(http_proxy_pool_->GetInfoAsValue("http_proxy_pool",
+ "http_proxy_pool",
+ true));
+ }
+ dict->Set("nested_pools", list);
+ }
+ return dict;
+}
+
+base::TimeDelta SSLClientSocketPool::ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+}
+
+ClientSocketPoolHistograms* SSLClientSocketPool::histograms() const {
+ return base_.histograms();
+}
+
+void SSLClientSocketPool::OnSSLConfigChanged() {
+ FlushWithError(ERR_NETWORK_CHANGED);
+}
+
+bool SSLClientSocketPool::CloseOneIdleConnection() {
+ if (base_.CloseOneIdleSocket())
+ return true;
+ return base_.CloseOneIdleConnectionInLayeredPool();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket_pool.h b/chromium/net/socket/ssl_client_socket_pool.h
new file mode 100644
index 00000000000..431a1b7ceea
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_pool.h
@@ -0,0 +1,297 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "net/base/privacy_mode.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_response_info.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class CertVerifier;
+class ClientSocketFactory;
+class ConnectJobFactory;
+class HostPortPair;
+class HttpProxyClientSocketPool;
+class HttpProxySocketParams;
+class SOCKSClientSocketPool;
+class SOCKSSocketParams;
+class SSLClientSocket;
+class TransportClientSocketPool;
+class TransportSecurityState;
+class TransportSocketParams;
+
+// SSLSocketParams only needs the socket params for the transport socket
+// that will be used (denoted by |proxy|).
+class NET_EXPORT_PRIVATE SSLSocketParams
+ : public base::RefCounted<SSLSocketParams> {
+ public:
+ SSLSocketParams(const scoped_refptr<TransportSocketParams>& transport_params,
+ const scoped_refptr<SOCKSSocketParams>& socks_params,
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params,
+ ProxyServer::Scheme proxy,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ PrivacyMode privacy_mode,
+ int load_flags,
+ bool force_spdy_over_ssl,
+ bool want_spdy_over_npn);
+
+ const scoped_refptr<TransportSocketParams>& transport_params() {
+ return transport_params_;
+ }
+ const scoped_refptr<HttpProxySocketParams>& http_proxy_params() {
+ return http_proxy_params_;
+ }
+ const scoped_refptr<SOCKSSocketParams>& socks_params() {
+ return socks_params_;
+ }
+ ProxyServer::Scheme proxy() const { return proxy_; }
+ const HostPortPair& host_and_port() const { return host_and_port_; }
+ const SSLConfig& ssl_config() const { return ssl_config_; }
+ PrivacyMode privacy_mode() const { return privacy_mode_; }
+ int load_flags() const { return load_flags_; }
+ bool force_spdy_over_ssl() const { return force_spdy_over_ssl_; }
+ bool want_spdy_over_npn() const { return want_spdy_over_npn_; }
+ bool ignore_limits() const { return ignore_limits_; }
+
+ private:
+ friend class base::RefCounted<SSLSocketParams>;
+ ~SSLSocketParams();
+
+ const scoped_refptr<TransportSocketParams> transport_params_;
+ const scoped_refptr<HttpProxySocketParams> http_proxy_params_;
+ const scoped_refptr<SOCKSSocketParams> socks_params_;
+ const ProxyServer::Scheme proxy_;
+ const HostPortPair host_and_port_;
+ const SSLConfig ssl_config_;
+ const PrivacyMode privacy_mode_;
+ const int load_flags_;
+ const bool force_spdy_over_ssl_;
+ const bool want_spdy_over_npn_;
+ bool ignore_limits_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLSocketParams);
+};
+
+// SSLConnectJob handles the SSL handshake after setting up the underlying
+// connection as specified in the params.
+class SSLConnectJob : public ConnectJob {
+ public:
+ SSLConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<SSLSocketParams>& params,
+ const base::TimeDelta& timeout_duration,
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ const SSLClientSocketContext& context,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~SSLConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const OVERRIDE;
+
+ virtual void GetAdditionalErrorState(ClientSocketHandle * handle) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_TRANSPORT_CONNECT,
+ STATE_TRANSPORT_CONNECT_COMPLETE,
+ STATE_SOCKS_CONNECT,
+ STATE_SOCKS_CONNECT_COMPLETE,
+ STATE_TUNNEL_CONNECT,
+ STATE_TUNNEL_CONNECT_COMPLETE,
+ STATE_SSL_CONNECT,
+ STATE_SSL_CONNECT_COMPLETE,
+ STATE_NONE,
+ };
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoTransportConnect();
+ int DoTransportConnectComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
+ int DoTunnelConnect();
+ int DoTunnelConnectComplete(int result);
+ int DoSSLConnect();
+ int DoSSLConnectComplete(int result);
+
+ // Starts the SSL connection process. Returns OK on success and
+ // ERR_IO_PENDING if it cannot immediately service the request.
+ // Otherwise, it returns a net error code.
+ virtual int ConnectInternal() OVERRIDE;
+
+ scoped_refptr<SSLSocketParams> params_;
+ TransportClientSocketPool* const transport_pool_;
+ SOCKSClientSocketPool* const socks_pool_;
+ HttpProxyClientSocketPool* const http_proxy_pool_;
+ ClientSocketFactory* const client_socket_factory_;
+ HostResolver* const host_resolver_;
+
+ const SSLClientSocketContext context_;
+
+ State next_state_;
+ CompletionCallback callback_;
+ scoped_ptr<ClientSocketHandle> transport_socket_handle_;
+ scoped_ptr<SSLClientSocket> ssl_socket_;
+
+ HttpResponseInfo error_response_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLConnectJob);
+};
+
+class NET_EXPORT_PRIVATE SSLClientSocketPool
+ : public ClientSocketPool,
+ public LayeredPool,
+ public SSLConfigService::Observer {
+ public:
+ // Only the pools that will be used are required. i.e. if you never
+ // try to create an SSL over SOCKS socket, |socks_pool| may be NULL.
+ SSLClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ CertVerifier* cert_verifier,
+ ServerBoundCertService* server_bound_cert_service,
+ TransportSecurityState* transport_security_state,
+ const std::string& ssl_session_cache_shard,
+ ClientSocketFactory* client_socket_factory,
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ SSLConfigService* ssl_config_service,
+ NetLog* net_log);
+
+ virtual ~SSLClientSocketPool();
+
+ // ClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* connect_params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE;
+
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+
+ virtual void FlushWithError(int error) OVERRIDE;
+
+ virtual bool IsStalled() const OVERRIDE;
+
+ virtual void CloseIdleSockets() OVERRIDE;
+
+ virtual int IdleSocketCount() const OVERRIDE;
+
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE;
+
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE;
+
+ virtual void AddLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual void RemoveLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ virtual ClientSocketPoolHistograms* histograms() const OVERRIDE;
+
+ // LayeredPool implementation.
+ virtual bool CloseOneIdleConnection() OVERRIDE;
+
+ private:
+ typedef ClientSocketPoolBase<SSLSocketParams> PoolBase;
+
+ // SSLConfigService::Observer implementation.
+
+ // When the user changes the SSL config, we flush all idle sockets so they
+ // won't get re-used.
+ virtual void OnSSLConfigChanged() OVERRIDE;
+
+ class SSLConnectJobFactory : public PoolBase::ConnectJobFactory {
+ public:
+ SSLConnectJobFactory(
+ TransportClientSocketPool* transport_pool,
+ SOCKSClientSocketPool* socks_pool,
+ HttpProxyClientSocketPool* http_proxy_pool,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ const SSLClientSocketContext& context,
+ NetLog* net_log);
+
+ virtual ~SSLConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ private:
+ TransportClientSocketPool* const transport_pool_;
+ SOCKSClientSocketPool* const socks_pool_;
+ HttpProxyClientSocketPool* const http_proxy_pool_;
+ ClientSocketFactory* const client_socket_factory_;
+ HostResolver* const host_resolver_;
+ const SSLClientSocketContext context_;
+ base::TimeDelta timeout_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLConnectJobFactory);
+ };
+
+ TransportClientSocketPool* const transport_pool_;
+ SOCKSClientSocketPool* const socks_pool_;
+ HttpProxyClientSocketPool* const http_proxy_pool_;
+ PoolBase base_;
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(SSLClientSocketPool, SSLSocketParams);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_CLIENT_SOCKET_POOL_H_
diff --git a/chromium/net/socket/ssl_client_socket_pool_unittest.cc b/chromium/net/socket/ssl_client_socket_pool_unittest.cc
new file mode 100644
index 00000000000..280f6e7af1a
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_pool_unittest.cc
@@ -0,0 +1,857 @@
+// Copyright (c) 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.
+
+#include "net/http/http_proxy_client_socket_pool.h"
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/base/auth.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/test/test_certificate_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+
+// Make sure |handle|'s load times are set correctly. DNS and connect start
+// times comes from mock client sockets in these tests, so primarily serves to
+// check those times were copied, and ssl times / connect end are set correctly.
+void TestLoadTimingInfo(const ClientSocketHandle& handle) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ // None of these tests use a NetLog.
+ EXPECT_EQ(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasTimes(
+ load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_SSL_TIMES | CONNECT_TIMING_HAS_DNS_TIMES);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+// Just like TestLoadTimingInfo, except DNS times are expected to be null, for
+// tests over proxies that do DNS lookups themselves.
+void TestLoadTimingInfoNoDns(const ClientSocketHandle& handle) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ // None of these tests use a NetLog.
+ EXPECT_EQ(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+class SSLClientSocketPoolTest
+ : public testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ SSLClientSocketPoolTest()
+ : proxy_service_(ProxyService::CreateDirect()),
+ ssl_config_service_(new SSLConfigServiceDefaults),
+ http_auth_handler_factory_(
+ HttpAuthHandlerFactory::CreateDefault(&host_resolver_)),
+ session_(CreateNetworkSession()),
+ direct_transport_socket_params_(
+ new TransportSocketParams(HostPortPair("host", 443),
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback())),
+ transport_histograms_("MockTCP"),
+ transport_socket_pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ &transport_histograms_,
+ &socket_factory_),
+ proxy_transport_socket_params_(
+ new TransportSocketParams(HostPortPair("proxy", 443),
+ MEDIUM,
+ false,
+ false,
+ OnHostResolutionCallback())),
+ socks_socket_params_(
+ new SOCKSSocketParams(proxy_transport_socket_params_,
+ true,
+ HostPortPair("sockshost", 443),
+ MEDIUM)),
+ socks_histograms_("MockSOCKS"),
+ socks_socket_pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ &socks_histograms_,
+ &transport_socket_pool_),
+ http_proxy_socket_params_(
+ new HttpProxySocketParams(proxy_transport_socket_params_,
+ NULL,
+ GURL("http://host"),
+ std::string(),
+ HostPortPair("host", 80),
+ session_->http_auth_cache(),
+ session_->http_auth_handler_factory(),
+ session_->spdy_session_pool(),
+ true)),
+ http_proxy_histograms_("MockHttpProxy"),
+ http_proxy_socket_pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ &http_proxy_histograms_,
+ &host_resolver_,
+ &transport_socket_pool_,
+ NULL,
+ NULL) {
+ scoped_refptr<SSLConfigService> ssl_config_service(
+ new SSLConfigServiceDefaults);
+ ssl_config_service->GetSSLConfig(&ssl_config_);
+ }
+
+ void CreatePool(bool transport_pool, bool http_proxy_pool, bool socks_pool) {
+ ssl_histograms_.reset(new ClientSocketPoolHistograms("SSLUnitTest"));
+ pool_.reset(new SSLClientSocketPool(
+ kMaxSockets,
+ kMaxSocketsPerGroup,
+ ssl_histograms_.get(),
+ NULL /* host_resolver */,
+ NULL /* cert_verifier */,
+ NULL /* server_bound_cert_service */,
+ NULL /* transport_security_state */,
+ std::string() /* ssl_session_cache_shard */,
+ &socket_factory_,
+ transport_pool ? &transport_socket_pool_ : NULL,
+ socks_pool ? &socks_socket_pool_ : NULL,
+ http_proxy_pool ? &http_proxy_socket_pool_ : NULL,
+ NULL,
+ NULL));
+ }
+
+ scoped_refptr<SSLSocketParams> SSLParams(ProxyServer::Scheme proxy,
+ bool want_spdy_over_npn) {
+ return make_scoped_refptr(new SSLSocketParams(
+ proxy == ProxyServer::SCHEME_DIRECT ? direct_transport_socket_params_
+ : NULL,
+ proxy == ProxyServer::SCHEME_SOCKS5 ? socks_socket_params_ : NULL,
+ proxy == ProxyServer::SCHEME_HTTP ? http_proxy_socket_params_ : NULL,
+ proxy,
+ HostPortPair("host", 443),
+ ssl_config_,
+ kPrivacyModeDisabled,
+ 0,
+ false,
+ want_spdy_over_npn));
+ }
+
+ void AddAuthToCache() {
+ const base::string16 kFoo(ASCIIToUTF16("foo"));
+ const base::string16 kBar(ASCIIToUTF16("bar"));
+ session_->http_auth_cache()->Add(GURL("http://proxy:443/"),
+ "MyRealm1",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "Basic realm=MyRealm1",
+ AuthCredentials(kFoo, kBar),
+ "/");
+ }
+
+ HttpNetworkSession* CreateNetworkSession() {
+ HttpNetworkSession::Params params;
+ params.host_resolver = &host_resolver_;
+ params.cert_verifier = cert_verifier_.get();
+ params.transport_security_state = transport_security_state_.get();
+ params.proxy_service = proxy_service_.get();
+ params.client_socket_factory = &socket_factory_;
+ params.ssl_config_service = ssl_config_service_.get();
+ params.http_auth_handler_factory = http_auth_handler_factory_.get();
+ params.http_server_properties =
+ http_server_properties_.GetWeakPtr();
+ params.enable_spdy_compression = false;
+ params.spdy_default_protocol = GetParam();
+ return new HttpNetworkSession(params);
+ }
+
+ void TestIPPoolingDisabled(SSLSocketDataProvider* ssl);
+
+ MockClientSocketFactory socket_factory_;
+ MockCachingHostResolver host_resolver_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+ const scoped_ptr<ProxyService> proxy_service_;
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+ const scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_;
+ HttpServerPropertiesImpl http_server_properties_;
+ const scoped_refptr<HttpNetworkSession> session_;
+
+ scoped_refptr<TransportSocketParams> direct_transport_socket_params_;
+ ClientSocketPoolHistograms transport_histograms_;
+ MockTransportClientSocketPool transport_socket_pool_;
+
+ scoped_refptr<TransportSocketParams> proxy_transport_socket_params_;
+
+ scoped_refptr<SOCKSSocketParams> socks_socket_params_;
+ ClientSocketPoolHistograms socks_histograms_;
+ MockSOCKSClientSocketPool socks_socket_pool_;
+
+ scoped_refptr<HttpProxySocketParams> http_proxy_socket_params_;
+ ClientSocketPoolHistograms http_proxy_histograms_;
+ HttpProxyClientSocketPool http_proxy_socket_pool_;
+
+ SSLConfig ssl_config_;
+ scoped_ptr<ClientSocketPoolHistograms> ssl_histograms_;
+ scoped_ptr<SSLClientSocketPool> pool_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SSLClientSocketPoolTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(SSLClientSocketPoolTest, TCPFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", params, MEDIUM, CompletionCallback(), pool_.get(),
+ BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, TCPFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, BasicDirect) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, BasicDirectAsync) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectCertError) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, ERR_CERT_COMMON_NAME_INVALID);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectSSLError) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, ERR_SSL_PROTOCOL_ERROR);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectWithNPN) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(kProtoHTTP11);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->WasNpnNegotiated());
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectNoSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(kProtoHTTP11);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ true);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_NPN_NEGOTIATION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_TRUE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectGotSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ true);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->WasNpnNegotiated());
+ std::string proto;
+ std::string server_protos;
+ ssl_socket->GetNextProto(&proto, &server_protos);
+ EXPECT_EQ(GetParam(), SSLClientSocket::NextProtoFromString(proto));
+}
+
+TEST_P(SSLClientSocketPoolTest, DirectGotBonusSPDY) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.SetNextProto(GetParam());
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_DIRECT,
+ true);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfo(handle);
+
+ SSLClientSocket* ssl_socket = static_cast<SSLClientSocket*>(handle.socket());
+ EXPECT_TRUE(ssl_socket->WasNpnNegotiated());
+ std::string proto;
+ std::string server_protos;
+ ssl_socket->GetNextProto(&proto, &server_protos);
+ EXPECT_EQ(GetParam(), SSLClientSocket::NextProtoFromString(proto));
+}
+
+TEST_P(SSLClientSocketPoolTest, SOCKSFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, SOCKSFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, SOCKSBasic) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ // SOCKS5 generally has no DNS times, but the mock SOCKS5 sockets used here
+ // don't go through the real logic, unlike in the HTTP proxy tests.
+ TestLoadTimingInfo(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, SOCKSBasicAsync) {
+ StaticSocketDataProvider data;
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_SOCKS5,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ // SOCKS5 generally has no DNS times, but the mock SOCKS5 sockets used here
+ // don't go through the real logic, unlike in the HTTP proxy tests.
+ TestLoadTimingInfo(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, HttpProxyFail) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, HttpProxyFailAsync) {
+ StaticSocketDataProvider data;
+ data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_FAILED));
+ socket_factory_.AddSocketDataProvider(&data);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+}
+
+TEST_P(SSLClientSocketPoolTest, HttpProxyBasic) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS,
+ "CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, "HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ socket_factory_.AddSocketDataProvider(&data);
+ AddAuthToCache();
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoNoDns(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, HttpProxyBasicAsync) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&data);
+ AddAuthToCache();
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoNoDns(handle);
+}
+
+TEST_P(SSLClientSocketPoolTest, NeedProxyAuth) {
+ MockWrite writes[] = {
+ MockWrite("CONNECT host:80 HTTP/1.1\r\n"
+ "Host: host\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), writes,
+ arraysize(writes));
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(false, true /* http proxy pool */, true /* socks pool */);
+ scoped_refptr<SSLSocketParams> params = SSLParams(ProxyServer::SCHEME_HTTP,
+ false);
+
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ int rv = handle.Init(
+ "a", params, MEDIUM, callback.callback(), pool_.get(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_FALSE(handle.is_ssl_error());
+ const HttpResponseInfo& tunnel_info = handle.ssl_error_response_info();
+ EXPECT_EQ(tunnel_info.headers->response_code(), 407);
+ scoped_ptr<ClientSocketHandle> tunnel_handle(
+ handle.release_pending_http_proxy_connection());
+ EXPECT_TRUE(tunnel_handle->socket());
+ EXPECT_FALSE(tunnel_handle->socket()->IsConnected());
+}
+
+TEST_P(SSLClientSocketPoolTest, IPPooling) {
+ const int kTestPort = 80;
+ struct TestHosts {
+ std::string name;
+ std::string iplist;
+ SpdySessionKey key;
+ AddressList addresses;
+ } test_hosts[] = {
+ { "www.webkit.org", "192.0.2.33,192.168.0.1,192.168.0.5" },
+ { "code.google.com", "192.168.0.2,192.168.0.3,192.168.0.5" },
+ { "js.webkit.org", "192.168.0.4,192.168.0.1,192.0.2.33" },
+ };
+
+ host_resolver_.set_synchronous_mode(true);
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
+ host_resolver_.rules()->AddIPLiteralRule(
+ test_hosts[i].name, test_hosts[i].iplist, std::string());
+
+ // This test requires that the HostResolver cache be populated. Normal
+ // code would have done this already, but we do it manually.
+ HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
+ host_resolver_.Resolve(info, &test_hosts[i].addresses, CompletionCallback(),
+ NULL, BoundNetLog());
+
+ // Setup a SpdySessionKey
+ test_hosts[i].key = SpdySessionKey(
+ HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ }
+
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_IO_PENDING),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.cert = X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
+ ssl.SetNextProto(GetParam());
+ socket_factory_.AddSSLSocketDataProvider(&ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ base::WeakPtr<SpdySession> spdy_session =
+ CreateSecureSpdySession(session_, test_hosts[0].key, BoundNetLog());
+
+ EXPECT_TRUE(
+ HasSpdySession(session_->spdy_session_pool(), test_hosts[0].key));
+ EXPECT_FALSE(
+ HasSpdySession(session_->spdy_session_pool(), test_hosts[1].key));
+ EXPECT_TRUE(
+ HasSpdySession(session_->spdy_session_pool(), test_hosts[2].key));
+
+ session_->spdy_session_pool()->CloseAllSessions();
+}
+
+void SSLClientSocketPoolTest::TestIPPoolingDisabled(
+ SSLSocketDataProvider* ssl) {
+ const int kTestPort = 80;
+ struct TestHosts {
+ std::string name;
+ std::string iplist;
+ SpdySessionKey key;
+ AddressList addresses;
+ } test_hosts[] = {
+ { "www.webkit.org", "192.0.2.33,192.168.0.1,192.168.0.5" },
+ { "js.webkit.com", "192.168.0.4,192.168.0.1,192.0.2.33" },
+ };
+
+ TestCompletionCallback callback;
+ int rv;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
+ host_resolver_.rules()->AddIPLiteralRule(
+ test_hosts[i].name, test_hosts[i].iplist, std::string());
+
+ // This test requires that the HostResolver cache be populated. Normal
+ // code would have done this already, but we do it manually.
+ HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
+ rv = host_resolver_.Resolve(info, &test_hosts[i].addresses,
+ callback.callback(), NULL, BoundNetLog());
+ EXPECT_EQ(OK, callback.GetResult(rv));
+
+ // Setup a SpdySessionKey
+ test_hosts[i].key = SpdySessionKey(
+ HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ }
+
+ MockRead reads[] = {
+ MockRead(ASYNC, ERR_IO_PENDING),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ socket_factory_.AddSocketDataProvider(&data);
+ socket_factory_.AddSSLSocketDataProvider(ssl);
+
+ CreatePool(true /* tcp pool */, false, false);
+ base::WeakPtr<SpdySession> spdy_session =
+ CreateSecureSpdySession(session_, test_hosts[0].key, BoundNetLog());
+
+ EXPECT_TRUE(
+ HasSpdySession(session_->spdy_session_pool(), test_hosts[0].key));
+ EXPECT_FALSE(
+ HasSpdySession(session_->spdy_session_pool(), test_hosts[1].key));
+
+ session_->spdy_session_pool()->CloseAllSessions();
+}
+
+// Verifies that an SSL connection with client authentication disables SPDY IP
+// pooling.
+TEST_P(SSLClientSocketPoolTest, IPPoolingClientCert) {
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.cert = X509Certificate::CreateFromBytes(
+ reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
+ ssl.client_cert_sent = true;
+ ssl.SetNextProto(GetParam());
+ TestIPPoolingDisabled(&ssl);
+}
+
+// Verifies that an SSL connection with channel ID disables SPDY IP pooling.
+TEST_P(SSLClientSocketPoolTest, IPPoolingChannelID) {
+ SSLSocketDataProvider ssl(ASYNC, OK);
+ ssl.channel_id_sent = true;
+ ssl.SetNextProto(GetParam());
+ TestIPPoolingDisabled(&ssl);
+}
+
+// It would be nice to also test the timeouts in SSLClientSocketPool.
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_client_socket_unittest.cc b/chromium/net/socket/ssl_client_socket_unittest.cc
new file mode 100644
index 00000000000..f791928580f
--- /dev/null
+++ b/chromium/net/socket/ssl_client_socket_unittest.cc
@@ -0,0 +1,1798 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_client_socket.h"
+
+#include "base/callback_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/cert/test_root_certs.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+namespace {
+
+const SSLConfig kDefaultSSLConfig;
+
+// WrappedStreamSocket is a base class that wraps an existing StreamSocket,
+// forwarding the Socket and StreamSocket interfaces to the underlying
+// transport.
+// This is to provide a common base class for subclasses to override specific
+// StreamSocket methods for testing, while still communicating with a 'real'
+// StreamSocket.
+class WrappedStreamSocket : public StreamSocket {
+ public:
+ explicit WrappedStreamSocket(scoped_ptr<StreamSocket> transport)
+ : transport_(transport.Pass()) {}
+ virtual ~WrappedStreamSocket() {}
+
+ // StreamSocket implementation:
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ return transport_->Connect(callback);
+ }
+ virtual void Disconnect() OVERRIDE { transport_->Disconnect(); }
+ virtual bool IsConnected() const OVERRIDE {
+ return transport_->IsConnected();
+ }
+ virtual bool IsConnectedAndIdle() const OVERRIDE {
+ return transport_->IsConnectedAndIdle();
+ }
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ return transport_->GetPeerAddress(address);
+ }
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ return transport_->GetLocalAddress(address);
+ }
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return transport_->NetLog();
+ }
+ virtual void SetSubresourceSpeculation() OVERRIDE {
+ transport_->SetSubresourceSpeculation();
+ }
+ virtual void SetOmniboxSpeculation() OVERRIDE {
+ transport_->SetOmniboxSpeculation();
+ }
+ virtual bool WasEverUsed() const OVERRIDE {
+ return transport_->WasEverUsed();
+ }
+ virtual bool UsingTCPFastOpen() const OVERRIDE {
+ return transport_->UsingTCPFastOpen();
+ }
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return transport_->WasNpnNegotiated();
+ }
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return transport_->GetNegotiatedProtocol();
+ }
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return transport_->GetSSLInfo(ssl_info);
+ }
+
+ // Socket implementation:
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return transport_->Read(buf, buf_len, callback);
+ }
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return transport_->Write(buf, buf_len, callback);
+ }
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE {
+ return transport_->SetReceiveBufferSize(size);
+ }
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE {
+ return transport_->SetSendBufferSize(size);
+ }
+
+ protected:
+ scoped_ptr<StreamSocket> transport_;
+};
+
+// ReadBufferingStreamSocket is a wrapper for an existing StreamSocket that
+// will ensure a certain amount of data is internally buffered before
+// satisfying a Read() request. It exists to mimic OS-level internal
+// buffering, but in a way to guarantee that X number of bytes will be
+// returned to callers of Read(), regardless of how quickly the OS receives
+// them from the TestServer.
+class ReadBufferingStreamSocket : public WrappedStreamSocket {
+ public:
+ explicit ReadBufferingStreamSocket(scoped_ptr<StreamSocket> transport);
+ virtual ~ReadBufferingStreamSocket() {}
+
+ // Socket implementation:
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // Sets the internal buffer to |size|. This must not be greater than
+ // the largest value supplied to Read() - that is, it does not handle
+ // having "leftovers" at the end of Read().
+ // Each call to Read() will be prevented from completion until at least
+ // |size| data has been read.
+ // Set to 0 to turn off buffering, causing Read() to transparently
+ // read via the underlying transport.
+ void SetBufferSize(int size);
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_READ,
+ STATE_READ_COMPLETE,
+ };
+
+ int DoLoop(int result);
+ int DoRead();
+ int DoReadComplete(int result);
+ void OnReadCompleted(int result);
+
+ State state_;
+ scoped_refptr<GrowableIOBuffer> read_buffer_;
+ int buffer_size_;
+
+ scoped_refptr<IOBuffer> user_read_buf_;
+ CompletionCallback user_read_callback_;
+};
+
+ReadBufferingStreamSocket::ReadBufferingStreamSocket(
+ scoped_ptr<StreamSocket> transport)
+ : WrappedStreamSocket(transport.Pass()),
+ read_buffer_(new GrowableIOBuffer()),
+ buffer_size_(0) {}
+
+void ReadBufferingStreamSocket::SetBufferSize(int size) {
+ DCHECK(!user_read_buf_.get());
+ buffer_size_ = size;
+ read_buffer_->SetCapacity(size);
+}
+
+int ReadBufferingStreamSocket::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (buffer_size_ == 0)
+ return transport_->Read(buf, buf_len, callback);
+
+ if (buf_len < buffer_size_)
+ return ERR_UNEXPECTED;
+
+ state_ = STATE_READ;
+ user_read_buf_ = buf;
+ int result = DoLoop(OK);
+ if (result == ERR_IO_PENDING)
+ user_read_callback_ = callback;
+ else
+ user_read_buf_ = NULL;
+ return result;
+}
+
+int ReadBufferingStreamSocket::DoLoop(int result) {
+ int rv = result;
+ do {
+ State current_state = state_;
+ state_ = STATE_NONE;
+ switch (current_state) {
+ case STATE_READ:
+ rv = DoRead();
+ break;
+ case STATE_READ_COMPLETE:
+ rv = DoReadComplete(rv);
+ break;
+ case STATE_NONE:
+ default:
+ NOTREACHED() << "Unexpected state: " << current_state;
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && state_ != STATE_NONE);
+ return rv;
+}
+
+int ReadBufferingStreamSocket::DoRead() {
+ state_ = STATE_READ_COMPLETE;
+ int rv =
+ transport_->Read(read_buffer_.get(),
+ read_buffer_->RemainingCapacity(),
+ base::Bind(&ReadBufferingStreamSocket::OnReadCompleted,
+ base::Unretained(this)));
+ return rv;
+}
+
+int ReadBufferingStreamSocket::DoReadComplete(int result) {
+ state_ = STATE_NONE;
+ if (result <= 0)
+ return result;
+
+ read_buffer_->set_offset(read_buffer_->offset() + result);
+ if (read_buffer_->RemainingCapacity() > 0) {
+ state_ = STATE_READ;
+ return OK;
+ }
+
+ memcpy(user_read_buf_->data(),
+ read_buffer_->StartOfBuffer(),
+ read_buffer_->capacity());
+ read_buffer_->set_offset(0);
+ return read_buffer_->capacity();
+}
+
+void ReadBufferingStreamSocket::OnReadCompleted(int result) {
+ result = DoLoop(result);
+ if (result == ERR_IO_PENDING)
+ return;
+
+ user_read_buf_ = NULL;
+ base::ResetAndReturn(&user_read_callback_).Run(result);
+}
+
+// Simulates synchronously receiving an error during Read() or Write()
+class SynchronousErrorStreamSocket : public WrappedStreamSocket {
+ public:
+ explicit SynchronousErrorStreamSocket(scoped_ptr<StreamSocket> transport);
+ virtual ~SynchronousErrorStreamSocket() {}
+
+ // Socket implementation:
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // Sets the next Read() call and all future calls to return |error|.
+ // If there is already a pending asynchronous read, the configured error
+ // will not be returned until that asynchronous read has completed and Read()
+ // is called again.
+ void SetNextReadError(Error error) {
+ DCHECK_GE(0, error);
+ have_read_error_ = true;
+ pending_read_error_ = error;
+ }
+
+ // Sets the next Write() call and all future calls to return |error|.
+ // If there is already a pending asynchronous write, the configured error
+ // will not be returned until that asynchronous write has completed and
+ // Write() is called again.
+ void SetNextWriteError(Error error) {
+ DCHECK_GE(0, error);
+ have_write_error_ = true;
+ pending_write_error_ = error;
+ }
+
+ private:
+ bool have_read_error_;
+ int pending_read_error_;
+
+ bool have_write_error_;
+ int pending_write_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SynchronousErrorStreamSocket);
+};
+
+SynchronousErrorStreamSocket::SynchronousErrorStreamSocket(
+ scoped_ptr<StreamSocket> transport)
+ : WrappedStreamSocket(transport.Pass()),
+ have_read_error_(false),
+ pending_read_error_(OK),
+ have_write_error_(false),
+ pending_write_error_(OK) {}
+
+int SynchronousErrorStreamSocket::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (have_read_error_)
+ return pending_read_error_;
+ return transport_->Read(buf, buf_len, callback);
+}
+
+int SynchronousErrorStreamSocket::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ if (have_write_error_)
+ return pending_write_error_;
+ return transport_->Write(buf, buf_len, callback);
+}
+
+// FakeBlockingStreamSocket wraps an existing StreamSocket and simulates the
+// underlying transport needing to complete things asynchronously in a
+// deterministic manner (e.g.: independent of the TestServer and the OS's
+// semantics).
+class FakeBlockingStreamSocket : public WrappedStreamSocket {
+ public:
+ explicit FakeBlockingStreamSocket(scoped_ptr<StreamSocket> transport);
+ virtual ~FakeBlockingStreamSocket() {}
+
+ // Socket implementation:
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return read_state_.RunWrappedFunction(buf, buf_len, callback);
+ }
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return write_state_.RunWrappedFunction(buf, buf_len, callback);
+ }
+
+ // Causes the next call to Read() to return ERR_IO_PENDING, not completing
+ // (invoking the callback) until UnblockRead() has been called and the
+ // underlying transport has completed.
+ void SetNextReadShouldBlock() { read_state_.SetShouldBlock(); }
+ void UnblockRead() { read_state_.Unblock(); }
+
+ // Causes the next call to Write() to return ERR_IO_PENDING, not completing
+ // (invoking the callback) until UnblockWrite() has been called and the
+ // underlying transport has completed.
+ void SetNextWriteShouldBlock() { write_state_.SetShouldBlock(); }
+ void UnblockWrite() { write_state_.Unblock(); }
+
+ private:
+ // Tracks the state for simulating a blocking Read/Write operation.
+ class BlockingState {
+ public:
+ // Wrapper for the underlying Socket function to call (ie: Read/Write).
+ typedef base::Callback<int(IOBuffer*, int, const CompletionCallback&)>
+ WrappedSocketFunction;
+
+ explicit BlockingState(const WrappedSocketFunction& function);
+ ~BlockingState() {}
+
+ // Sets the next call to RunWrappedFunction() to block, returning
+ // ERR_IO_PENDING and not invoking the user callback until Unblock() is
+ // called.
+ void SetShouldBlock();
+
+ // Unblocks the currently blocked pending function, invoking the user
+ // callback if the results are immediately available.
+ // Note: It's not valid to call this unless SetShouldBlock() has been
+ // called beforehand.
+ void Unblock();
+
+ // Performs the wrapped socket function on the underlying transport. If
+ // configured to block via SetShouldBlock(), then |user_callback| will not
+ // be invoked until Unblock() has been called.
+ int RunWrappedFunction(IOBuffer* buf,
+ int len,
+ const CompletionCallback& user_callback);
+
+ private:
+ // Handles completion from the underlying wrapped socket function.
+ void OnCompleted(int result);
+
+ WrappedSocketFunction wrapped_function_;
+ bool should_block_;
+ bool have_result_;
+ int pending_result_;
+ CompletionCallback user_callback_;
+ };
+
+ BlockingState read_state_;
+ BlockingState write_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeBlockingStreamSocket);
+};
+
+FakeBlockingStreamSocket::FakeBlockingStreamSocket(
+ scoped_ptr<StreamSocket> transport)
+ : WrappedStreamSocket(transport.Pass()),
+ read_state_(base::Bind(&Socket::Read,
+ base::Unretained(transport_.get()))),
+ write_state_(base::Bind(&Socket::Write,
+ base::Unretained(transport_.get()))) {}
+
+FakeBlockingStreamSocket::BlockingState::BlockingState(
+ const WrappedSocketFunction& function)
+ : wrapped_function_(function),
+ should_block_(false),
+ have_result_(false),
+ pending_result_(OK) {}
+
+void FakeBlockingStreamSocket::BlockingState::SetShouldBlock() {
+ DCHECK(!should_block_);
+ should_block_ = true;
+}
+
+void FakeBlockingStreamSocket::BlockingState::Unblock() {
+ DCHECK(should_block_);
+ should_block_ = false;
+
+ // If the operation is still pending in the underlying transport, immediately
+ // return - OnCompleted() will handle invoking the callback once the transport
+ // has completed.
+ if (!have_result_)
+ return;
+
+ have_result_ = false;
+
+ base::ResetAndReturn(&user_callback_).Run(pending_result_);
+}
+
+int FakeBlockingStreamSocket::BlockingState::RunWrappedFunction(
+ IOBuffer* buf,
+ int len,
+ const CompletionCallback& callback) {
+
+ // The callback to be called by the underlying transport. Either forward
+ // directly to the user's callback if not set to block, or intercept it with
+ // OnCompleted so that the user's callback is not invoked until Unblock() is
+ // called.
+ CompletionCallback transport_callback =
+ !should_block_ ? callback : base::Bind(&BlockingState::OnCompleted,
+ base::Unretained(this));
+ int rv = wrapped_function_.Run(buf, len, transport_callback);
+ if (should_block_) {
+ user_callback_ = callback;
+ // May have completed synchronously.
+ have_result_ = (rv != ERR_IO_PENDING);
+ pending_result_ = rv;
+ return ERR_IO_PENDING;
+ }
+
+ return rv;
+}
+
+void FakeBlockingStreamSocket::BlockingState::OnCompleted(int result) {
+ if (should_block_) {
+ // Store the result so that the callback can be invoked once Unblock() is
+ // called.
+ have_result_ = true;
+ pending_result_ = result;
+ return;
+ }
+
+ // Otherwise, the Unblock() function was called before the underlying
+ // transport completed, so run the user's callback immediately.
+ base::ResetAndReturn(&user_callback_).Run(result);
+}
+
+// CompletionCallback that will delete the associated StreamSocket when
+// the callback is invoked.
+class DeleteSocketCallback : public TestCompletionCallbackBase {
+ public:
+ explicit DeleteSocketCallback(StreamSocket* socket)
+ : socket_(socket),
+ callback_(base::Bind(&DeleteSocketCallback::OnComplete,
+ base::Unretained(this))) {}
+ virtual ~DeleteSocketCallback() {}
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ if (socket_) {
+ delete socket_;
+ socket_ = NULL;
+ } else {
+ ADD_FAILURE() << "Deleting socket twice";
+ }
+ SetResult(result);
+ }
+
+ StreamSocket* socket_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteSocketCallback);
+};
+
+class SSLClientSocketTest : public PlatformTest {
+ public:
+ SSLClientSocketTest()
+ : socket_factory_(ClientSocketFactory::GetDefaultFactory()),
+ cert_verifier_(new MockCertVerifier),
+ transport_security_state_(new TransportSecurityState) {
+ cert_verifier_->set_default_result(OK);
+ context_.cert_verifier = cert_verifier_.get();
+ context_.transport_security_state = transport_security_state_.get();
+ }
+
+ protected:
+ scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<StreamSocket> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config) {
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ connection->SetSocket(transport_socket.Pass());
+ return socket_factory_->CreateSSLClientSocket(
+ connection.Pass(), host_and_port, ssl_config, context_);
+ }
+
+ ClientSocketFactory* socket_factory_;
+ scoped_ptr<MockCertVerifier> cert_verifier_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+ SSLClientSocketContext context_;
+};
+
+//-----------------------------------------------------------------------------
+
+// LogContainsSSLConnectEndEvent returns true if the given index in the given
+// log is an SSL connect end event. The NSS sockets will cork in an attempt to
+// merge the first application data record with the Finished message when false
+// starting. However, in order to avoid the server timing out the handshake,
+// they'll give up waiting for application data and send the Finished after a
+// timeout. This means that an SSL connect end event may appear as a socket
+// write.
+static bool LogContainsSSLConnectEndEvent(
+ const CapturingNetLog::CapturedEntryList& log,
+ int i) {
+ return LogContainsEndEvent(log, i, NetLog::TYPE_SSL_CONNECT) ||
+ LogContainsEvent(
+ log, i, NetLog::TYPE_SOCKET_BYTES_SENT, NetLog::PHASE_NONE);
+}
+;
+
+TEST_F(SSLClientSocketTest, Connect) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+
+ sock->Disconnect();
+ EXPECT_FALSE(sock->IsConnected());
+}
+
+TEST_F(SSLClientSocketTest, ConnectExpired) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_EXPIRED);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ cert_verifier_->set_default_result(ERR_CERT_DATE_INVALID);
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_EQ(ERR_CERT_DATE_INVALID, rv);
+
+ // Rather than testing whether or not the underlying socket is connected,
+ // test that the handshake has finished. This is because it may be
+ // desirable to disconnect the socket before showing a user prompt, since
+ // the user may take indefinitely long to respond.
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+}
+
+TEST_F(SSLClientSocketTest, ConnectMismatched) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ cert_verifier_->set_default_result(ERR_CERT_COMMON_NAME_INVALID);
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, rv);
+
+ // Rather than testing whether or not the underlying socket is connected,
+ // test that the handshake has finished. This is because it may be
+ // desirable to disconnect the socket before showing a user prompt, since
+ // the user may take indefinitely long to respond.
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+}
+
+// Attempt to connect to a page which requests a client certificate. It should
+// return an error code on connect.
+TEST_F(SSLClientSocketTest, ConnectClientAuthCertRequested) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ log.GetEntries(&entries);
+ // Because we prematurely kill the handshake at CertificateRequest,
+ // the server may still send data (notably the ServerHelloDone)
+ // after the error is returned. As a result, the SSL_CONNECT may not
+ // be the last entry. See http://crbug.com/54445. We use
+ // ExpectLogContainsSomewhere instead of
+ // LogContainsSSLConnectEndEvent to avoid assuming, e.g., only one
+ // extra read instead of two. This occurs before the handshake ends,
+ // so the corking logic of LogContainsSSLConnectEndEvent isn't
+ // necessary.
+ //
+ // TODO(davidben): When SSL_RestartHandshakeAfterCertReq in NSS is
+ // fixed and we can respond to the first CertificateRequest
+ // without closing the socket, add a unit test for sending the
+ // certificate. This test may still be useful as we'll want to close
+ // the socket on a timeout if the user takes a long time to pick a
+ // cert. Related bug: https://bugzilla.mozilla.org/show_bug.cgi?id=542832
+ ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_SSL_CONNECT, NetLog::PHASE_END);
+ EXPECT_EQ(ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
+ EXPECT_FALSE(sock->IsConnected());
+}
+
+// Connect to a server requesting optional client authentication. Send it a
+// null certificate. It should allow the connection.
+//
+// TODO(davidben): Also test providing an actual certificate.
+TEST_F(SSLClientSocketTest, ConnectClientAuthSendNullCert) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ SSLConfig ssl_config = kDefaultSSLConfig;
+ ssl_config.send_client_cert = true;
+ ssl_config.client_cert = NULL;
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), ssl_config));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ // Our test server accepts certificate-less connections.
+ // TODO(davidben): Add a test which requires them and verify the error.
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+
+ // We responded to the server's certificate request with a Certificate
+ // message with no client certificate in it. ssl_info.client_cert_sent
+ // should be false in this case.
+ SSLInfo ssl_info;
+ sock->GetSSLInfo(&ssl_info);
+ EXPECT_FALSE(ssl_info.client_cert_sent);
+
+ sock->Disconnect();
+ EXPECT_FALSE(sock->IsConnected());
+}
+
+// TODO(wtc): Add unit tests for IsConnectedAndIdle:
+// - Server closes an SSL connection (with a close_notify alert message).
+// - Server closes the underlying TCP connection directly.
+// - Server sends data unexpectedly.
+
+TEST_F(SSLClientSocketTest, Read) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+
+ rv = sock->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv);
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ for (;;) {
+ rv = sock->Read(buf.get(), 4096, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_GE(rv, 0);
+ if (rv <= 0)
+ break;
+ }
+}
+
+// Tests that the SSLClientSocket properly handles when the underlying transport
+// synchronously returns an error code - such as if an intermediary terminates
+// the socket connection uncleanly.
+// This is a regression test for http://crbug.com/238536
+TEST_F(SSLClientSocketTest, Read_WithSynchronousError) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ scoped_ptr<SynchronousErrorStreamSocket> transport(
+ new SynchronousErrorStreamSocket(real_transport.Pass()));
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+
+ // Disable TLS False Start to avoid handshake non-determinism.
+ SSLConfig ssl_config;
+ ssl_config.false_start_enabled = false;
+
+ SynchronousErrorStreamSocket* raw_transport = transport.get();
+ scoped_ptr<SSLClientSocket> sock(
+ CreateSSLClientSocket(transport.PassAs<StreamSocket>(),
+ test_server.host_port_pair(),
+ ssl_config));
+
+ rv = callback.GetResult(sock->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ static const int kRequestTextSize =
+ static_cast<int>(arraysize(request_text) - 1);
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kRequestTextSize));
+ memcpy(request_buffer->data(), request_text, kRequestTextSize);
+
+ rv = callback.GetResult(
+ sock->Write(request_buffer.get(), kRequestTextSize, callback.callback()));
+ EXPECT_EQ(kRequestTextSize, rv);
+
+ // Simulate an unclean/forcible shutdown.
+ raw_transport->SetNextReadError(ERR_CONNECTION_RESET);
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+
+ // Note: This test will hang if this bug has regressed. Simply checking that
+ // rv != ERR_IO_PENDING is insufficient, as ERR_IO_PENDING is a legitimate
+ // result when using a dedicated task runner for NSS.
+ rv = callback.GetResult(sock->Read(buf.get(), 4096, callback.callback()));
+
+#if !defined(USE_OPENSSL)
+ // SSLClientSocketNSS records the error exactly
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+#else
+ // SSLClientSocketOpenSSL treats any errors as a simple EOF.
+ EXPECT_EQ(0, rv);
+#endif
+}
+
+// Tests that the SSLClientSocket properly handles when the underlying transport
+// asynchronously returns an error code while writing data - such as if an
+// intermediary terminates the socket connection uncleanly.
+// This is a regression test for http://crbug.com/249848
+TEST_F(SSLClientSocketTest, Write_WithSynchronousError) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ // Note: |error_socket|'s ownership is handed to |transport|, but a pointer
+ // is retained in order to configure additional errors.
+ scoped_ptr<SynchronousErrorStreamSocket> error_socket(
+ new SynchronousErrorStreamSocket(real_transport.Pass()));
+ SynchronousErrorStreamSocket* raw_error_socket = error_socket.get();
+ scoped_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(error_socket.PassAs<StreamSocket>()));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+
+ // Disable TLS False Start to avoid handshake non-determinism.
+ SSLConfig ssl_config;
+ ssl_config.false_start_enabled = false;
+
+ scoped_ptr<SSLClientSocket> sock(
+ CreateSSLClientSocket(transport.PassAs<StreamSocket>(),
+ test_server.host_port_pair(),
+ ssl_config));
+
+ rv = callback.GetResult(sock->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ static const int kRequestTextSize =
+ static_cast<int>(arraysize(request_text) - 1);
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kRequestTextSize));
+ memcpy(request_buffer->data(), request_text, kRequestTextSize);
+
+ // Simulate an unclean/forcible shutdown on the underlying socket.
+ // However, simulate this error asynchronously.
+ raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET);
+ raw_transport->SetNextWriteShouldBlock();
+
+ // This write should complete synchronously, because the TLS ciphertext
+ // can be created and placed into the outgoing buffers independent of the
+ // underlying transport.
+ rv = callback.GetResult(
+ sock->Write(request_buffer.get(), kRequestTextSize, callback.callback()));
+ EXPECT_EQ(kRequestTextSize, rv);
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+
+ rv = sock->Read(buf.get(), 4096, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Now unblock the outgoing request, having it fail with the connection
+ // being reset.
+ raw_transport->UnblockWrite();
+
+ // Note: This will cause an inifite loop if this bug has regressed. Simply
+ // checking that rv != ERR_IO_PENDING is insufficient, as ERR_IO_PENDING
+ // is a legitimate result when using a dedicated task runner for NSS.
+ rv = callback.GetResult(rv);
+
+#if !defined(USE_OPENSSL)
+ // SSLClientSocketNSS records the error exactly
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+#else
+ // SSLClientSocketOpenSSL treats any errors as a simple EOF.
+ EXPECT_EQ(0, rv);
+#endif
+}
+
+// Test the full duplex mode, with Read and Write pending at the same time.
+// This test also serves as a regression test for http://crbug.com/29815.
+TEST_F(SSLClientSocketTest, Read_FullDuplex) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback; // Used for everything except Write.
+
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ // Issue a "hanging" Read first.
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ rv = sock->Read(buf.get(), 4096, callback.callback());
+ // We haven't written the request, so there should be no response yet.
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ // Write the request.
+ // The request is padded with a User-Agent header to a size that causes the
+ // memio circular buffer (4k bytes) in SSLClientSocketNSS to wrap around.
+ // This tests the fix for http://crbug.com/29815.
+ std::string request_text = "GET / HTTP/1.1\r\nUser-Agent: long browser name ";
+ for (int i = 0; i < 3770; ++i)
+ request_text.push_back('*');
+ request_text.append("\r\n\r\n");
+ scoped_refptr<IOBuffer> request_buffer(new StringIOBuffer(request_text));
+
+ TestCompletionCallback callback2; // Used for Write only.
+ rv = sock->Write(
+ request_buffer.get(), request_text.size(), callback2.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(static_cast<int>(request_text.size()), rv);
+
+ // Now get the Read result.
+ rv = callback.WaitForResult();
+ EXPECT_GT(rv, 0);
+}
+
+// Attempts to Read() and Write() from an SSLClientSocketNSS in full duplex
+// mode when the underlying transport is blocked on sending data. When the
+// underlying transport completes due to an error, it should invoke both the
+// Read() and Write() callbacks. If the socket is deleted by the Read()
+// callback, the Write() callback should not be invoked.
+// Regression test for http://crbug.com/232633
+TEST_F(SSLClientSocketTest, Read_DeleteWhilePendingFullDuplex) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ // Note: |error_socket|'s ownership is handed to |transport|, but a pointer
+ // is retained in order to configure additional errors.
+ scoped_ptr<SynchronousErrorStreamSocket> error_socket(
+ new SynchronousErrorStreamSocket(real_transport.Pass()));
+ SynchronousErrorStreamSocket* raw_error_socket = error_socket.get();
+ scoped_ptr<FakeBlockingStreamSocket> transport(
+ new FakeBlockingStreamSocket(error_socket.PassAs<StreamSocket>()));
+ FakeBlockingStreamSocket* raw_transport = transport.get();
+
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+
+ // Disable TLS False Start to avoid handshake non-determinism.
+ SSLConfig ssl_config;
+ ssl_config.false_start_enabled = false;
+
+ scoped_ptr<SSLClientSocket> sock =
+ CreateSSLClientSocket(transport.PassAs<StreamSocket>(),
+ test_server.host_port_pair(),
+ ssl_config);
+
+ rv = callback.GetResult(sock->Connect(callback.callback()));
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ std::string request_text = "GET / HTTP/1.1\r\nUser-Agent: long browser name ";
+ request_text.append(20 * 1024, '*');
+ request_text.append("\r\n\r\n");
+ scoped_refptr<DrainableIOBuffer> request_buffer(new DrainableIOBuffer(
+ new StringIOBuffer(request_text), request_text.size()));
+
+ // Simulate errors being returned from the underlying Read() and Write() ...
+ raw_error_socket->SetNextReadError(ERR_CONNECTION_RESET);
+ raw_error_socket->SetNextWriteError(ERR_CONNECTION_RESET);
+ // ... but have those errors returned asynchronously. Because the Write() will
+ // return first, this will trigger the error.
+ raw_transport->SetNextReadShouldBlock();
+ raw_transport->SetNextWriteShouldBlock();
+
+ // Enqueue a Read() before calling Write(), which should "hang" due to
+ // the ERR_IO_PENDING caused by SetReadShouldBlock() and thus return.
+ SSLClientSocket* raw_sock = sock.get();
+ DeleteSocketCallback read_callback(sock.release());
+ scoped_refptr<IOBuffer> read_buf(new IOBuffer(4096));
+ rv = raw_sock->Read(read_buf.get(), 4096, read_callback.callback());
+
+ // Ensure things didn't complete synchronously, otherwise |sock| is invalid.
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_FALSE(read_callback.have_result());
+
+#if !defined(USE_OPENSSL)
+ // NSS follows a pattern where a call to PR_Write will only consume as
+ // much data as it can encode into application data records before the
+ // internal memio buffer is full, which should only fill if writing a large
+ // amount of data and the underlying transport is blocked. Once this happens,
+ // NSS will return (total size of all application data records it wrote) - 1,
+ // with the caller expected to resume with the remaining unsent data.
+ //
+ // This causes SSLClientSocketNSS::Write to return that it wrote some data
+ // before it will return ERR_IO_PENDING, so make an extra call to Write() to
+ // get the socket in the state needed for the test below.
+ //
+ // This is not needed for OpenSSL, because for OpenSSL,
+ // SSL_MODE_ENABLE_PARTIAL_WRITE is not specified - thus
+ // SSLClientSocketOpenSSL::Write() will not return until all of
+ // |request_buffer| has been written to the underlying BIO (although not
+ // necessarily the underlying transport).
+ rv = callback.GetResult(raw_sock->Write(request_buffer.get(),
+ request_buffer->BytesRemaining(),
+ callback.callback()));
+ ASSERT_LT(0, rv);
+ request_buffer->DidConsume(rv);
+
+ // Guard to ensure that |request_buffer| was larger than all of the internal
+ // buffers (transport, memio, NSS) along the way - otherwise the next call
+ // to Write() will crash with an invalid buffer.
+ ASSERT_LT(0, request_buffer->BytesRemaining());
+#endif
+
+ // Attempt to write the remaining data. NSS will not be able to consume the
+ // application data because the internal buffers are full, while OpenSSL will
+ // return that its blocked because the underlying transport is blocked.
+ rv = raw_sock->Write(request_buffer.get(),
+ request_buffer->BytesRemaining(),
+ callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ ASSERT_FALSE(callback.have_result());
+
+ // Now unblock Write(), which will invoke OnSendComplete and (eventually)
+ // call the Read() callback, deleting the socket and thus aborting calling
+ // the Write() callback.
+ raw_transport->UnblockWrite();
+
+ rv = read_callback.WaitForResult();
+
+#if !defined(USE_OPENSSL)
+ // NSS records the error exactly.
+ EXPECT_EQ(ERR_CONNECTION_RESET, rv);
+#else
+ // OpenSSL treats any errors as a simple EOF.
+ EXPECT_EQ(0, rv);
+#endif
+
+ // The Write callback should not have been called.
+ EXPECT_FALSE(callback.have_result());
+}
+
+TEST_F(SSLClientSocketTest, Read_SmallChunks) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+
+ rv = sock->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv);
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(1));
+ for (;;) {
+ rv = sock->Read(buf.get(), 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_GE(rv, 0);
+ if (rv <= 0)
+ break;
+ }
+}
+
+TEST_F(SSLClientSocketTest, Read_ManySmallRecords) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<StreamSocket> real_transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ scoped_ptr<ReadBufferingStreamSocket> transport(
+ new ReadBufferingStreamSocket(real_transport.Pass()));
+ ReadBufferingStreamSocket* raw_transport = transport.get();
+ int rv = callback.GetResult(transport->Connect(callback.callback()));
+ ASSERT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(
+ CreateSSLClientSocket(transport.PassAs<StreamSocket>(),
+ test_server.host_port_pair(),
+ kDefaultSSLConfig));
+
+ rv = callback.GetResult(sock->Connect(callback.callback()));
+ ASSERT_EQ(OK, rv);
+ ASSERT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET /ssl-many-small-records HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+
+ rv = callback.GetResult(sock->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback()));
+ ASSERT_GT(rv, 0);
+ ASSERT_EQ(static_cast<int>(arraysize(request_text) - 1), rv);
+
+ // Note: This relies on SSLClientSocketNSS attempting to read up to 17K of
+ // data (the max SSL record size) at a time. Ensure that at least 15K worth
+ // of SSL data is buffered first. The 15K of buffered data is made up of
+ // many smaller SSL records (the TestServer writes along 1350 byte
+ // plaintext boundaries), although there may also be a few records that are
+ // smaller or larger, due to timing and SSL False Start.
+ // 15K was chosen because 15K is smaller than the 17K (max) read issued by
+ // the SSLClientSocket implementation, and larger than the minimum amount
+ // of ciphertext necessary to contain the 8K of plaintext requested below.
+ raw_transport->SetBufferSize(15000);
+
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(8192));
+ rv = callback.GetResult(sock->Read(buffer.get(), 8192, callback.callback()));
+ ASSERT_EQ(rv, 8192);
+}
+
+TEST_F(SSLClientSocketTest, Read_Interrupted) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+
+ rv = sock->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv);
+
+ // Do a partial read and then exit. This test should not crash!
+ scoped_refptr<IOBuffer> buf(new IOBuffer(512));
+ rv = sock->Read(buf.get(), 512, callback.callback());
+ EXPECT_TRUE(rv > 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_GT(rv, 0);
+}
+
+TEST_F(SSLClientSocketTest, Read_FullLogging) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ log.SetLogLevel(NetLog::LOG_ALL);
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+
+ rv = sock->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(static_cast<int>(arraysize(request_text) - 1), rv);
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ size_t last_index = ExpectLogContainsSomewhereAfter(
+ entries, 5, NetLog::TYPE_SSL_SOCKET_BYTES_SENT, NetLog::PHASE_NONE);
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ for (;;) {
+ rv = sock->Read(buf.get(), 4096, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_GE(rv, 0);
+ if (rv <= 0)
+ break;
+
+ log.GetEntries(&entries);
+ last_index =
+ ExpectLogContainsSomewhereAfter(entries,
+ last_index + 1,
+ NetLog::TYPE_SSL_SOCKET_BYTES_RECEIVED,
+ NetLog::PHASE_NONE);
+ }
+}
+
+// Regression test for http://crbug.com/42538
+TEST_F(SSLClientSocketTest, PrematureApplicationData) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ TestCompletionCallback callback;
+
+ static const unsigned char application_data[] = {
+ 0x17, 0x03, 0x01, 0x00, 0x4a, 0x02, 0x00, 0x00, 0x46, 0x03, 0x01, 0x4b,
+ 0xc2, 0xf8, 0xb2, 0xc1, 0x56, 0x42, 0xb9, 0x57, 0x7f, 0xde, 0x87, 0x46,
+ 0xf7, 0xa3, 0x52, 0x42, 0x21, 0xf0, 0x13, 0x1c, 0x9c, 0x83, 0x88, 0xd6,
+ 0x93, 0x0c, 0xf6, 0x36, 0x30, 0x05, 0x7e, 0x20, 0xb5, 0xb5, 0x73, 0x36,
+ 0x53, 0x83, 0x0a, 0xfc, 0x17, 0x63, 0xbf, 0xa0, 0xe4, 0x42, 0x90, 0x0d,
+ 0x2f, 0x18, 0x6d, 0x20, 0xd8, 0x36, 0x3f, 0xfc, 0xe6, 0x01, 0xfa, 0x0f,
+ 0xa5, 0x75, 0x7f, 0x09, 0x00, 0x04, 0x00, 0x16, 0x03, 0x01, 0x11, 0x57,
+ 0x0b, 0x00, 0x11, 0x53, 0x00, 0x11, 0x50, 0x00, 0x06, 0x22, 0x30, 0x82,
+ 0x06, 0x1e, 0x30, 0x82, 0x05, 0x06, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
+ 0x0a};
+
+ // All reads and writes complete synchronously (async=false).
+ MockRead data_reads[] = {
+ MockRead(SYNCHRONOUS,
+ reinterpret_cast<const char*>(application_data),
+ arraysize(application_data)),
+ MockRead(SYNCHRONOUS, OK), };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0);
+
+ scoped_ptr<StreamSocket> transport(
+ new MockTCPClientSocket(addr, NULL, &data));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, rv);
+}
+
+TEST_F(SSLClientSocketTest, CipherSuiteDisables) {
+ // Rather than exhaustively disabling every RC4 ciphersuite defined at
+ // http://www.iana.org/assignments/tls-parameters/tls-parameters.xml,
+ // only disabling those cipher suites that the test server actually
+ // implements.
+ const uint16 kCiphersToDisable[] = {0x0005, // TLS_RSA_WITH_RC4_128_SHA
+ };
+
+ SpawnedTestServer::SSLOptions ssl_options;
+ // Enable only RC4 on the test server.
+ ssl_options.bulk_ciphers = SpawnedTestServer::SSLOptions::BULK_CIPHER_RC4;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ SSLConfig ssl_config;
+ for (size_t i = 0; i < arraysize(kCiphersToDisable); ++i)
+ ssl_config.disabled_cipher_suites.push_back(kCiphersToDisable[i]);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), ssl_config));
+
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+
+ // NSS has special handling that maps a handshake_failure alert received
+ // immediately after a client_hello to be a mismatched cipher suite error,
+ // leading to ERR_SSL_VERSION_OR_CIPHER_MISMATCH. When using OpenSSL or
+ // Secure Transport (OS X), the handshake_failure is bubbled up without any
+ // interpretation, leading to ERR_SSL_PROTOCOL_ERROR. Either way, a failure
+ // indicates that no cipher suite was negotiated with the test server.
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_TRUE(rv == ERR_SSL_VERSION_OR_CIPHER_MISMATCH ||
+ rv == ERR_SSL_PROTOCOL_ERROR);
+ // The exact ordering differs between SSLClientSocketNSS (which issues an
+ // extra read) and SSLClientSocketMac (which does not). Just make sure the
+ // error appears somewhere in the log.
+ log.GetEntries(&entries);
+ ExpectLogContainsSomewhere(
+ entries, 0, NetLog::TYPE_SSL_HANDSHAKE_ERROR, NetLog::PHASE_NONE);
+
+ // We cannot test sock->IsConnected(), as the NSS implementation disconnects
+ // the socket when it encounters an error, whereas other implementations
+ // leave it connected.
+ // Because this an error that the test server is mutually aware of, as opposed
+ // to being an error such as a certificate name mismatch, which is
+ // client-only, the exact index of the SSL connect end depends on how
+ // quickly the test server closes the underlying socket. If the test server
+ // closes before the IO message loop pumps messages, there may be a 0-byte
+ // Read event in the NetLog due to TCPClientSocket picking up the EOF. As a
+ // result, the SSL connect end event will be the second-to-last entry,
+ // rather than the last entry.
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1) ||
+ LogContainsSSLConnectEndEvent(entries, -2));
+}
+
+// When creating an SSLClientSocket, it is allowed to pass in a
+// ClientSocketHandle that is not obtained from a client socket pool.
+// Here we verify that such a simple ClientSocketHandle, not associated with any
+// client socket pool, can be destroyed safely.
+TEST_F(SSLClientSocketTest, ClientSocketHandleNotFromPool) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle());
+ socket_handle->SetSocket(transport.Pass());
+
+ scoped_ptr<SSLClientSocket> sock(
+ socket_factory_->CreateSSLClientSocket(socket_handle.Pass(),
+ test_server.host_port_pair(),
+ kDefaultSSLConfig,
+ context_));
+
+ EXPECT_FALSE(sock->IsConnected());
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+}
+
+// Verifies that SSLClientSocket::ExportKeyingMaterial return a success
+// code and different keying label results in different keying material.
+TEST_F(SSLClientSocketTest, ExportKeyingMaterial) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath());
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, NULL, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+
+ const int kKeyingMaterialSize = 32;
+ const char* kKeyingLabel1 = "client-socket-test-1";
+ const char* kKeyingContext = "";
+ unsigned char client_out1[kKeyingMaterialSize];
+ memset(client_out1, 0, sizeof(client_out1));
+ rv = sock->ExportKeyingMaterial(
+ kKeyingLabel1, false, kKeyingContext, client_out1, sizeof(client_out1));
+ EXPECT_EQ(rv, OK);
+
+ const char* kKeyingLabel2 = "client-socket-test-2";
+ unsigned char client_out2[kKeyingMaterialSize];
+ memset(client_out2, 0, sizeof(client_out2));
+ rv = sock->ExportKeyingMaterial(
+ kKeyingLabel2, false, kKeyingContext, client_out2, sizeof(client_out2));
+ EXPECT_EQ(rv, OK);
+ EXPECT_NE(memcmp(client_out1, client_out2, kKeyingMaterialSize), 0);
+}
+
+// Verifies that SSLClientSocket::ClearSessionCache can be called without
+// explicit NSS initialization.
+TEST(SSLClientSocket, ClearSessionCache) {
+ SSLClientSocket::ClearSessionCache();
+}
+
+// This tests that SSLInfo contains a properly re-constructed certificate
+// chain. That, in turn, verifies that GetSSLInfo is giving us the chain as
+// verified, not the chain as served by the server. (They may be different.)
+//
+// CERT_CHAIN_WRONG_ROOT is redundant-server-chain.pem. It contains A
+// (end-entity) -> B -> C, and C is signed by D. redundant-validated-chain.pem
+// contains a chain of A -> B -> C2, where C2 is the same public key as C, but
+// a self-signed root. Such a situation can occur when a new root (C2) is
+// cross-certified by an old root (D) and has two different versions of its
+// floating around. Servers may supply C2 as an intermediate, but the
+// SSLClientSocket should return the chain that was verified, from
+// verify_result, instead.
+TEST_F(SSLClientSocketTest, VerifyReturnChainProperlyOrdered) {
+ // By default, cause the CertVerifier to treat all certificates as
+ // expired.
+ cert_verifier_->set_default_result(ERR_CERT_DATE_INVALID);
+
+ // We will expect SSLInfo to ultimately contain this chain.
+ CertificateList certs =
+ CreateCertificateListFromFile(GetTestCertsDirectory(),
+ "redundant-validated-chain.pem",
+ X509Certificate::FORMAT_AUTO);
+ ASSERT_EQ(3U, certs.size());
+
+ X509Certificate::OSCertHandles temp_intermediates;
+ temp_intermediates.push_back(certs[1]->os_cert_handle());
+ temp_intermediates.push_back(certs[2]->os_cert_handle());
+
+ CertVerifyResult verify_result;
+ verify_result.verified_cert = X509Certificate::CreateFromHandle(
+ certs[0]->os_cert_handle(), temp_intermediates);
+
+ // Add a rule that maps the server cert (A) to the chain of A->B->C2
+ // rather than A->B->C.
+ cert_verifier_->AddResultForCert(certs[0].get(), verify_result, OK);
+
+ // Load and install the root for the validated chain.
+ scoped_refptr<X509Certificate> root_cert = ImportCertFromFile(
+ GetTestCertsDirectory(), "redundant-validated-chain-root.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ ScopedTestRoot scoped_root(root_cert.get());
+
+ // Set up a test server with CERT_CHAIN_WRONG_ROOT.
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_CHAIN_WRONG_ROOT);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ AddressList addr;
+ ASSERT_TRUE(test_server.GetAddressList(&addr));
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+ EXPECT_FALSE(sock->IsConnected());
+ rv = sock->Connect(callback.callback());
+
+ CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsBeginEvent(entries, 5, NetLog::TYPE_SSL_CONNECT));
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_EQ(OK, rv);
+ EXPECT_TRUE(sock->IsConnected());
+ log.GetEntries(&entries);
+ EXPECT_TRUE(LogContainsSSLConnectEndEvent(entries, -1));
+
+ SSLInfo ssl_info;
+ sock->GetSSLInfo(&ssl_info);
+
+ // Verify that SSLInfo contains the corrected re-constructed chain A -> B
+ // -> C2.
+ const X509Certificate::OSCertHandles& intermediates =
+ ssl_info.cert->GetIntermediateCertificates();
+ ASSERT_EQ(2U, intermediates.size());
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(ssl_info.cert->os_cert_handle(),
+ certs[0]->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(intermediates[0],
+ certs[1]->os_cert_handle()));
+ EXPECT_TRUE(X509Certificate::IsSameOSCert(intermediates[1],
+ certs[2]->os_cert_handle()));
+
+ sock->Disconnect();
+ EXPECT_FALSE(sock->IsConnected());
+}
+
+// Verifies the correctness of GetSSLCertRequestInfo.
+class SSLClientSocketCertRequestInfoTest : public SSLClientSocketTest {
+ protected:
+ // Creates a test server with the given SSLOptions, connects to it and returns
+ // the SSLCertRequestInfo reported by the socket.
+ scoped_refptr<SSLCertRequestInfo> GetCertRequest(
+ SpawnedTestServer::SSLOptions ssl_options) {
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
+ if (!test_server.Start())
+ return NULL;
+
+ AddressList addr;
+ if (!test_server.GetAddressList(&addr))
+ return NULL;
+
+ TestCompletionCallback callback;
+ CapturingNetLog log;
+ scoped_ptr<StreamSocket> transport(
+ new TCPClientSocket(addr, &log, NetLog::Source()));
+ int rv = transport->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ scoped_ptr<SSLClientSocket> sock(CreateSSLClientSocket(
+ transport.Pass(), test_server.host_port_pair(), kDefaultSSLConfig));
+ EXPECT_FALSE(sock->IsConnected());
+
+ rv = sock->Connect(callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ scoped_refptr<SSLCertRequestInfo> request_info = new SSLCertRequestInfo();
+ sock->GetSSLCertRequestInfo(request_info.get());
+ sock->Disconnect();
+ EXPECT_FALSE(sock->IsConnected());
+
+ return request_info;
+ }
+};
+
+TEST_F(SSLClientSocketCertRequestInfoTest, NoAuthorities) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ scoped_refptr<SSLCertRequestInfo> request_info = GetCertRequest(ssl_options);
+ ASSERT_TRUE(request_info.get());
+ EXPECT_EQ(0u, request_info->cert_authorities.size());
+}
+
+TEST_F(SSLClientSocketCertRequestInfoTest, TwoAuthorities) {
+ const base::FilePath::CharType kThawteFile[] =
+ FILE_PATH_LITERAL("thawte.single.pem");
+ const unsigned char kThawteDN[] = {
+ 0x30, 0x4c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x5a, 0x41, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x1c, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e,
+ 0x73, 0x75, 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x28, 0x50, 0x74, 0x79,
+ 0x29, 0x20, 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x0d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+ 0x53, 0x47, 0x43, 0x20, 0x43, 0x41};
+ const size_t kThawteLen = sizeof(kThawteDN);
+
+ const base::FilePath::CharType kDiginotarFile[] =
+ FILE_PATH_LITERAL("diginotar_root_ca.pem");
+ const unsigned char kDiginotarDN[] = {
+ 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x4e, 0x4c, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x09, 0x44, 0x69, 0x67, 0x69, 0x4e, 0x6f, 0x74, 0x61, 0x72, 0x31,
+ 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x11, 0x44, 0x69,
+ 0x67, 0x69, 0x4e, 0x6f, 0x74, 0x61, 0x72, 0x20, 0x52, 0x6f, 0x6f, 0x74,
+ 0x20, 0x43, 0x41, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x69, 0x6e, 0x66, 0x6f,
+ 0x40, 0x64, 0x69, 0x67, 0x69, 0x6e, 0x6f, 0x74, 0x61, 0x72, 0x2e, 0x6e,
+ 0x6c};
+ const size_t kDiginotarLen = sizeof(kDiginotarDN);
+
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ ssl_options.client_authorities.push_back(
+ GetTestClientCertsDirectory().Append(kThawteFile));
+ ssl_options.client_authorities.push_back(
+ GetTestClientCertsDirectory().Append(kDiginotarFile));
+ scoped_refptr<SSLCertRequestInfo> request_info = GetCertRequest(ssl_options);
+ ASSERT_TRUE(request_info.get());
+ ASSERT_EQ(2u, request_info->cert_authorities.size());
+ EXPECT_EQ(std::string(reinterpret_cast<const char*>(kThawteDN), kThawteLen),
+ request_info->cert_authorities[0]);
+ EXPECT_EQ(
+ std::string(reinterpret_cast<const char*>(kDiginotarDN), kDiginotarLen),
+ request_info->cert_authorities[1]);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_error_params.cc b/chromium/net/socket/ssl_error_params.cc
new file mode 100644
index 00000000000..37561f0de48
--- /dev/null
+++ b/chromium/net/socket/ssl_error_params.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_error_params.h"
+
+#include "base/bind.h"
+#include "base/values.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogSSLErrorCallback(int net_error,
+ int ssl_lib_error,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ if (ssl_lib_error)
+ dict->SetInteger("ssl_lib_error", ssl_lib_error);
+ return dict;
+}
+
+} // namespace
+
+NetLog::ParametersCallback CreateNetLogSSLErrorCallback(int net_error,
+ int ssl_lib_error) {
+ return base::Bind(&NetLogSSLErrorCallback, net_error, ssl_lib_error);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_error_params.h b/chromium/net/socket/ssl_error_params.h
new file mode 100644
index 00000000000..07a1c4d99d9
--- /dev/null
+++ b/chromium/net/socket/ssl_error_params.h
@@ -0,0 +1,18 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_ERROR_PARAMS_H_
+#define NET_SOCKET_SSL_ERROR_PARAMS_H_
+
+#include "net/base/net_log.h"
+
+namespace net {
+
+// Creates NetLog callback for when we receive an SSL error.
+NetLog::ParametersCallback CreateNetLogSSLErrorCallback(int net_error,
+ int ssl_lib_error);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_ERROR_PARAMS_H_
diff --git a/chromium/net/socket/ssl_server_socket.h b/chromium/net/socket/ssl_server_socket.h
new file mode 100644
index 00000000000..8b607bf80cf
--- /dev/null
+++ b/chromium/net/socket/ssl_server_socket.h
@@ -0,0 +1,64 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_SERVER_SOCKET_H_
+#define NET_SOCKET_SSL_SERVER_SOCKET_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/socket/ssl_socket.h"
+#include "net/socket/stream_socket.h"
+
+namespace crypto {
+class RSAPrivateKey;
+} // namespace crypto
+
+namespace net {
+
+struct SSLConfig;
+class X509Certificate;
+
+class SSLServerSocket : public SSLSocket {
+ public:
+ virtual ~SSLServerSocket() {}
+
+ // Perform the SSL server handshake, and notify the supplied callback
+ // if the process completes asynchronously. If Disconnect is called before
+ // completion then the callback will be silently, as for other StreamSocket
+ // calls.
+ virtual int Handshake(const CompletionCallback& callback) = 0;
+};
+
+// Configures the underlying SSL library for the use of SSL server sockets.
+//
+// Due to the requirements of the underlying libraries, this should be called
+// early in process initialization, before any SSL socket, client or server,
+// has been used.
+//
+// Note: If a process does not use SSL server sockets, this call may be
+// omitted.
+NET_EXPORT void EnableSSLServerSockets();
+
+// Creates an SSL server socket over an already-connected transport socket.
+// The caller must provide the server certificate and private key to use.
+//
+// The returned SSLServerSocket takes ownership of |socket|. Stubbed versions
+// of CreateSSLServerSocket will delete |socket| and return NULL.
+// It takes a reference to |certificate|.
+// The |key| and |ssl_config| parameters are copied. |key| cannot be const
+// because the methods used to copy its contents are non-const.
+//
+// The caller starts the SSL server handshake by calling Handshake on the
+// returned socket.
+NET_EXPORT scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
+ scoped_ptr<StreamSocket> socket,
+ X509Certificate* certificate,
+ crypto::RSAPrivateKey* key,
+ const SSLConfig& ssl_config);
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_SERVER_SOCKET_H_
diff --git a/chromium/net/socket/ssl_server_socket_nss.cc b/chromium/net/socket/ssl_server_socket_nss.cc
new file mode 100644
index 00000000000..7e5d70118ac
--- /dev/null
+++ b/chromium/net/socket/ssl_server_socket_nss.cc
@@ -0,0 +1,828 @@
+// Copyright (c) 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.
+
+#include "net/socket/ssl_server_socket_nss.h"
+
+#if defined(OS_WIN)
+#include <winsock2.h>
+#endif
+
+#if defined(USE_SYSTEM_SSL)
+#include <dlfcn.h>
+#endif
+#if defined(OS_MACOSX)
+#include <Security/Security.h>
+#endif
+#include <certdb.h>
+#include <cryptohi.h>
+#include <hasht.h>
+#include <keyhi.h>
+#include <nspr.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <secerr.h>
+#include <sechash.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <sslproto.h>
+
+#include <limits>
+
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "crypto/rsa_private_key.h"
+#include "crypto/nss_util_internal.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/nss_ssl_util.h"
+#include "net/socket/ssl_error_params.h"
+
+// SSL plaintext fragments are shorter than 16KB. Although the record layer
+// overhead is allowed to be 2K + 5 bytes, in practice the overhead is much
+// smaller than 1KB. So a 17KB buffer should be large enough to hold an
+// entire SSL record.
+static const int kRecvBufferSize = 17 * 1024;
+static const int kSendBufferSize = 17 * 1024;
+
+#define GotoState(s) next_handshake_state_ = s
+
+namespace net {
+
+namespace {
+
+bool g_nss_server_sockets_init = false;
+
+class NSSSSLServerInitSingleton {
+ public:
+ NSSSSLServerInitSingleton() {
+ EnsureNSSSSLInit();
+
+ SSL_ConfigServerSessionIDCache(1024, 5, 5, NULL);
+ g_nss_server_sockets_init = true;
+ }
+
+ ~NSSSSLServerInitSingleton() {
+ SSL_ShutdownServerSessionIDCache();
+ g_nss_server_sockets_init = false;
+ }
+};
+
+static base::LazyInstance<NSSSSLServerInitSingleton>
+ g_nss_ssl_server_init_singleton = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+void EnableSSLServerSockets() {
+ g_nss_ssl_server_init_singleton.Get();
+}
+
+scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
+ scoped_ptr<StreamSocket> socket,
+ X509Certificate* cert,
+ crypto::RSAPrivateKey* key,
+ const SSLConfig& ssl_config) {
+ DCHECK(g_nss_server_sockets_init) << "EnableSSLServerSockets() has not been"
+ << "called yet!";
+
+ return scoped_ptr<SSLServerSocket>(
+ new SSLServerSocketNSS(socket.Pass(), cert, key, ssl_config));
+}
+
+SSLServerSocketNSS::SSLServerSocketNSS(
+ scoped_ptr<StreamSocket> transport_socket,
+ scoped_refptr<X509Certificate> cert,
+ crypto::RSAPrivateKey* key,
+ const SSLConfig& ssl_config)
+ : transport_send_busy_(false),
+ transport_recv_busy_(false),
+ user_read_buf_len_(0),
+ user_write_buf_len_(0),
+ nss_fd_(NULL),
+ nss_bufs_(NULL),
+ transport_socket_(transport_socket.Pass()),
+ ssl_config_(ssl_config),
+ cert_(cert),
+ next_handshake_state_(STATE_NONE),
+ completed_handshake_(false) {
+ ssl_config_.false_start_enabled = false;
+ ssl_config_.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ ssl_config_.version_max = SSL_PROTOCOL_VERSION_TLS1_1;
+
+ // TODO(hclam): Need a better way to clone a key.
+ std::vector<uint8> key_bytes;
+ CHECK(key->ExportPrivateKey(&key_bytes));
+ key_.reset(crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_bytes));
+ CHECK(key_.get());
+}
+
+SSLServerSocketNSS::~SSLServerSocketNSS() {
+ if (nss_fd_ != NULL) {
+ PR_Close(nss_fd_);
+ nss_fd_ = NULL;
+ }
+}
+
+int SSLServerSocketNSS::Handshake(const CompletionCallback& callback) {
+ net_log_.BeginEvent(NetLog::TYPE_SSL_SERVER_HANDSHAKE);
+
+ int rv = Init();
+ if (rv != OK) {
+ LOG(ERROR) << "Failed to initialize NSS";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_SERVER_HANDSHAKE, rv);
+ return rv;
+ }
+
+ rv = InitializeSSLOptions();
+ if (rv != OK) {
+ LOG(ERROR) << "Failed to initialize SSL options";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_SERVER_HANDSHAKE, rv);
+ return rv;
+ }
+
+ // Set peer address. TODO(hclam): This should be in a separate method.
+ PRNetAddr peername;
+ memset(&peername, 0, sizeof(peername));
+ peername.raw.family = AF_INET;
+ memio_SetPeerName(nss_fd_, &peername);
+
+ GotoState(STATE_HANDSHAKE);
+ rv = DoHandshakeLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ user_handshake_callback_ = callback;
+ } else {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_SERVER_HANDSHAKE, rv);
+ }
+
+ return rv > OK ? OK : rv;
+}
+
+int SSLServerSocketNSS::ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ SECStatus result = SSL_ExportKeyingMaterial(
+ nss_fd_, label.data(), label.size(), has_context,
+ reinterpret_cast<const unsigned char*>(context.data()),
+ context.length(), out, outlen);
+ if (result != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_ExportKeyingMaterial", "");
+ return MapNSSError(PORT_GetError());
+ }
+ return OK;
+}
+
+int SSLServerSocketNSS::GetTLSUniqueChannelBinding(std::string* out) {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ unsigned char buf[64];
+ unsigned int len;
+ SECStatus result = SSL_GetChannelBinding(nss_fd_,
+ SSL_CHANNEL_BINDING_TLS_UNIQUE,
+ buf, &len, arraysize(buf));
+ if (result != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_GetChannelBinding", "");
+ return MapNSSError(PORT_GetError());
+ }
+ out->assign(reinterpret_cast<char*>(buf), len);
+ return OK;
+}
+
+int SSLServerSocketNSS::Connect(const CompletionCallback& callback) {
+ NOTIMPLEMENTED();
+ return ERR_NOT_IMPLEMENTED;
+}
+
+int SSLServerSocketNSS::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(user_read_callback_.is_null());
+ DCHECK(user_handshake_callback_.is_null());
+ DCHECK(!user_read_buf_.get());
+ DCHECK(nss_bufs_);
+ DCHECK(!callback.is_null());
+
+ user_read_buf_ = buf;
+ user_read_buf_len_ = buf_len;
+
+ DCHECK(completed_handshake_);
+
+ int rv = DoReadLoop(OK);
+
+ if (rv == ERR_IO_PENDING) {
+ user_read_callback_ = callback;
+ } else {
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ }
+ return rv;
+}
+
+int SSLServerSocketNSS::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(user_write_callback_.is_null());
+ DCHECK(!user_write_buf_.get());
+ DCHECK(nss_bufs_);
+ DCHECK(!callback.is_null());
+
+ user_write_buf_ = buf;
+ user_write_buf_len_ = buf_len;
+
+ int rv = DoWriteLoop(OK);
+
+ if (rv == ERR_IO_PENDING) {
+ user_write_callback_ = callback;
+ } else {
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ }
+ return rv;
+}
+
+bool SSLServerSocketNSS::SetReceiveBufferSize(int32 size) {
+ return transport_socket_->SetReceiveBufferSize(size);
+}
+
+bool SSLServerSocketNSS::SetSendBufferSize(int32 size) {
+ return transport_socket_->SetSendBufferSize(size);
+}
+
+bool SSLServerSocketNSS::IsConnected() const {
+ return completed_handshake_;
+}
+
+void SSLServerSocketNSS::Disconnect() {
+ transport_socket_->Disconnect();
+}
+
+bool SSLServerSocketNSS::IsConnectedAndIdle() const {
+ return completed_handshake_ && transport_socket_->IsConnectedAndIdle();
+}
+
+int SSLServerSocketNSS::GetPeerAddress(IPEndPoint* address) const {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ return transport_socket_->GetPeerAddress(address);
+}
+
+int SSLServerSocketNSS::GetLocalAddress(IPEndPoint* address) const {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ return transport_socket_->GetLocalAddress(address);
+}
+
+const BoundNetLog& SSLServerSocketNSS::NetLog() const {
+ return net_log_;
+}
+
+void SSLServerSocketNSS::SetSubresourceSpeculation() {
+ transport_socket_->SetSubresourceSpeculation();
+}
+
+void SSLServerSocketNSS::SetOmniboxSpeculation() {
+ transport_socket_->SetOmniboxSpeculation();
+}
+
+bool SSLServerSocketNSS::WasEverUsed() const {
+ return transport_socket_->WasEverUsed();
+}
+
+bool SSLServerSocketNSS::UsingTCPFastOpen() const {
+ return transport_socket_->UsingTCPFastOpen();
+}
+
+bool SSLServerSocketNSS::WasNpnNegotiated() const {
+ return false;
+}
+
+NextProto SSLServerSocketNSS::GetNegotiatedProtocol() const {
+ // NPN is not supported by this class.
+ return kProtoUnknown;
+}
+
+bool SSLServerSocketNSS::GetSSLInfo(SSLInfo* ssl_info) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+int SSLServerSocketNSS::InitializeSSLOptions() {
+ // Transport connected, now hook it up to nss
+ nss_fd_ = memio_CreateIOLayer(kRecvBufferSize, kSendBufferSize);
+ if (nss_fd_ == NULL) {
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR error code.
+ }
+
+ // Grab pointer to buffers
+ nss_bufs_ = memio_GetSecret(nss_fd_);
+
+ /* Create SSL state machine */
+ /* Push SSL onto our fake I/O socket */
+ nss_fd_ = SSL_ImportFD(NULL, nss_fd_);
+ if (nss_fd_ == NULL) {
+ LogFailedNSSFunction(net_log_, "SSL_ImportFD", "");
+ return ERR_OUT_OF_MEMORY; // TODO(port): map NSPR/NSS error code.
+ }
+ // TODO(port): set more ssl options! Check errors!
+
+ int rv;
+
+ rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SSL2, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_ENABLE_SSL2");
+ return ERR_UNEXPECTED;
+ }
+
+ SSLVersionRange version_range;
+ version_range.min = ssl_config_.version_min;
+ version_range.max = ssl_config_.version_max;
+ rv = SSL_VersionRangeSet(nss_fd_, &version_range);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_VersionRangeSet", "");
+ return ERR_NO_SSL_VERSIONS_ENABLED;
+ }
+
+ for (std::vector<uint16>::const_iterator it =
+ ssl_config_.disabled_cipher_suites.begin();
+ it != ssl_config_.disabled_cipher_suites.end(); ++it) {
+ // This will fail if the specified cipher is not implemented by NSS, but
+ // the failure is harmless.
+ SSL_CipherPrefSet(nss_fd_, *it, PR_FALSE);
+ }
+
+ // Server socket doesn't need session tickets.
+ rv = SSL_OptionSet(nss_fd_, SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(
+ net_log_, "SSL_OptionSet", "SSL_ENABLE_SESSION_TICKETS");
+ }
+
+ // Doing this will force PR_Accept perform handshake as server.
+ rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_HANDSHAKE_AS_CLIENT");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_HANDSHAKE_AS_SERVER, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_HANDSHAKE_AS_SERVER");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_REQUEST_CERTIFICATE, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_REQUEST_CERTIFICATE");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_OptionSet(nss_fd_, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_REQUIRE_CERTIFICATE");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_AuthCertificateHook(nss_fd_, OwnAuthCertHandler, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_AuthCertificateHook", "");
+ return ERR_UNEXPECTED;
+ }
+
+ rv = SSL_HandshakeCallback(nss_fd_, HandshakeCallback, this);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_HandshakeCallback", "");
+ return ERR_UNEXPECTED;
+ }
+
+ // Get a certificate of CERTCertificate structure.
+ std::string der_string;
+ if (!X509Certificate::GetDEREncoded(cert_->os_cert_handle(), &der_string))
+ return ERR_UNEXPECTED;
+
+ SECItem der_cert;
+ der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>(
+ der_string.data()));
+ der_cert.len = der_string.length();
+ der_cert.type = siDERCertBuffer;
+
+ // Parse into a CERTCertificate structure.
+ CERTCertificate* cert = CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE);
+ if (!cert) {
+ LogFailedNSSFunction(net_log_, "CERT_NewTempCertificate", "");
+ return MapNSSError(PORT_GetError());
+ }
+
+ // Get a key of SECKEYPrivateKey* structure.
+ std::vector<uint8> key_vector;
+ if (!key_->ExportPrivateKey(&key_vector)) {
+ CERT_DestroyCertificate(cert);
+ return ERR_UNEXPECTED;
+ }
+
+ SECKEYPrivateKeyStr* private_key = NULL;
+ PK11SlotInfo* slot = crypto::GetPrivateNSSKeySlot();
+ if (!slot) {
+ CERT_DestroyCertificate(cert);
+ return ERR_UNEXPECTED;
+ }
+
+ SECItem der_private_key_info;
+ der_private_key_info.data =
+ const_cast<unsigned char*>(&key_vector.front());
+ der_private_key_info.len = key_vector.size();
+ // The server's RSA private key must be imported into NSS with the
+ // following key usage bits:
+ // - KU_KEY_ENCIPHERMENT, required for the RSA key exchange algorithm.
+ // - KU_DIGITAL_SIGNATURE, required for the DHE_RSA and ECDHE_RSA key
+ // exchange algorithms.
+ const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DIGITAL_SIGNATURE;
+ rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ slot, &der_private_key_info, NULL, NULL, PR_FALSE, PR_FALSE,
+ key_usage, &private_key, NULL);
+ PK11_FreeSlot(slot);
+ if (rv != SECSuccess) {
+ CERT_DestroyCertificate(cert);
+ return ERR_UNEXPECTED;
+ }
+
+ // Assign server certificate and private key.
+ SSLKEAType cert_kea = NSS_FindCertKEAType(cert);
+ rv = SSL_ConfigSecureServer(nss_fd_, cert, private_key, cert_kea);
+ CERT_DestroyCertificate(cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ if (rv != SECSuccess) {
+ PRErrorCode prerr = PR_GetError();
+ LOG(ERROR) << "Failed to config SSL server: " << prerr;
+ LogFailedNSSFunction(net_log_, "SSL_ConfigureSecureServer", "");
+ return ERR_UNEXPECTED;
+ }
+
+ // Tell SSL we're a server; needed if not letting NSPR do socket I/O
+ rv = SSL_ResetHandshake(nss_fd_, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_ResetHandshake", "");
+ return ERR_UNEXPECTED;
+ }
+
+ return OK;
+}
+
+void SSLServerSocketNSS::OnSendComplete(int result) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ // In handshake phase.
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ if (!completed_handshake_)
+ return;
+
+ if (user_write_buf_.get()) {
+ int rv = DoWriteLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoWriteCallback(rv);
+ } else {
+ // Ensure that any queued ciphertext is flushed.
+ DoTransportIO();
+ }
+}
+
+void SSLServerSocketNSS::OnRecvComplete(int result) {
+ if (next_handshake_state_ == STATE_HANDSHAKE) {
+ // In handshake phase.
+ OnHandshakeIOComplete(result);
+ return;
+ }
+
+ // Network layer received some data, check if client requested to read
+ // decrypted data.
+ if (!user_read_buf_.get() || !completed_handshake_)
+ return;
+
+ int rv = DoReadLoop(result);
+ if (rv != ERR_IO_PENDING)
+ DoReadCallback(rv);
+}
+
+void SSLServerSocketNSS::OnHandshakeIOComplete(int result) {
+ int rv = DoHandshakeLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_SSL_SERVER_HANDSHAKE, rv);
+ if (!user_handshake_callback_.is_null())
+ DoHandshakeCallback(rv);
+ }
+}
+
+// Return 0 for EOF,
+// > 0 for bytes transferred immediately,
+// < 0 for error (or the non-error ERR_IO_PENDING).
+int SSLServerSocketNSS::BufferSend(void) {
+ if (transport_send_busy_)
+ return ERR_IO_PENDING;
+
+ const char* buf1;
+ const char* buf2;
+ unsigned int len1, len2;
+ memio_GetWriteParams(nss_bufs_, &buf1, &len1, &buf2, &len2);
+ const unsigned int len = len1 + len2;
+
+ int rv = 0;
+ if (len) {
+ scoped_refptr<IOBuffer> send_buffer(new IOBuffer(len));
+ memcpy(send_buffer->data(), buf1, len1);
+ memcpy(send_buffer->data() + len1, buf2, len2);
+ rv = transport_socket_->Write(
+ send_buffer.get(),
+ len,
+ base::Bind(&SSLServerSocketNSS::BufferSendComplete,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ transport_send_busy_ = true;
+ } else {
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(rv));
+ }
+ }
+
+ return rv;
+}
+
+void SSLServerSocketNSS::BufferSendComplete(int result) {
+ memio_PutWriteResult(nss_bufs_, MapErrorToNSS(result));
+ transport_send_busy_ = false;
+ OnSendComplete(result);
+}
+
+int SSLServerSocketNSS::BufferRecv(void) {
+ if (transport_recv_busy_) return ERR_IO_PENDING;
+
+ char* buf;
+ int nb = memio_GetReadParams(nss_bufs_, &buf);
+ int rv;
+ if (!nb) {
+ // buffer too full to read into, so no I/O possible at moment
+ rv = ERR_IO_PENDING;
+ } else {
+ recv_buffer_ = new IOBuffer(nb);
+ rv = transport_socket_->Read(
+ recv_buffer_.get(),
+ nb,
+ base::Bind(&SSLServerSocketNSS::BufferRecvComplete,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING) {
+ transport_recv_busy_ = true;
+ } else {
+ if (rv > 0)
+ memcpy(buf, recv_buffer_->data(), rv);
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(rv));
+ recv_buffer_ = NULL;
+ }
+ }
+ return rv;
+}
+
+void SSLServerSocketNSS::BufferRecvComplete(int result) {
+ if (result > 0) {
+ char* buf;
+ memio_GetReadParams(nss_bufs_, &buf);
+ memcpy(buf, recv_buffer_->data(), result);
+ }
+ recv_buffer_ = NULL;
+ memio_PutReadResult(nss_bufs_, MapErrorToNSS(result));
+ transport_recv_busy_ = false;
+ OnRecvComplete(result);
+}
+
+// Do as much network I/O as possible between the buffer and the
+// transport socket. Return true if some I/O performed, false
+// otherwise (error or ERR_IO_PENDING).
+bool SSLServerSocketNSS::DoTransportIO() {
+ bool network_moved = false;
+ if (nss_bufs_ != NULL) {
+ int rv;
+ // Read and write as much data as we can. The loop is neccessary
+ // because Write() may return synchronously.
+ do {
+ rv = BufferSend();
+ if (rv > 0)
+ network_moved = true;
+ } while (rv > 0);
+ if (BufferRecv() >= 0)
+ network_moved = true;
+ }
+ return network_moved;
+}
+
+int SSLServerSocketNSS::DoPayloadRead() {
+ DCHECK(user_read_buf_.get());
+ DCHECK_GT(user_read_buf_len_, 0);
+ int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_);
+ if (rv >= 0)
+ return rv;
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
+ return ERR_IO_PENDING;
+ }
+ rv = MapNSSError(prerr);
+ net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, prerr));
+ return rv;
+}
+
+int SSLServerSocketNSS::DoPayloadWrite() {
+ DCHECK(user_write_buf_.get());
+ int rv = PR_Write(nss_fd_, user_write_buf_->data(), user_write_buf_len_);
+ if (rv >= 0)
+ return rv;
+ PRErrorCode prerr = PR_GetError();
+ if (prerr == PR_WOULD_BLOCK_ERROR) {
+ return ERR_IO_PENDING;
+ }
+ rv = MapNSSError(prerr);
+ net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
+ CreateNetLogSSLErrorCallback(rv, prerr));
+ return rv;
+}
+
+int SSLServerSocketNSS::DoHandshakeLoop(int last_io_result) {
+ int rv = last_io_result;
+ do {
+ // Default to STATE_NONE for next state.
+ // (This is a quirk carried over from the windows
+ // implementation. It makes reading the logs a bit harder.)
+ // State handlers can and often do call GotoState just
+ // to stay in the current state.
+ State state = next_handshake_state_;
+ GotoState(STATE_NONE);
+ switch (state) {
+ case STATE_HANDSHAKE:
+ rv = DoHandshake();
+ break;
+ case STATE_NONE:
+ default:
+ rv = ERR_UNEXPECTED;
+ LOG(DFATAL) << "unexpected state " << state;
+ break;
+ }
+
+ // Do the actual network I/O
+ bool network_moved = DoTransportIO();
+ if (network_moved && next_handshake_state_ == STATE_HANDSHAKE) {
+ // In general we exit the loop if rv is ERR_IO_PENDING. In this
+ // special case we keep looping even if rv is ERR_IO_PENDING because
+ // the transport IO may allow DoHandshake to make progress.
+ rv = OK; // This causes us to stay in the loop.
+ }
+ } while (rv != ERR_IO_PENDING && next_handshake_state_ != STATE_NONE);
+ return rv;
+}
+
+int SSLServerSocketNSS::DoReadLoop(int result) {
+ DCHECK(completed_handshake_);
+ DCHECK(next_handshake_state_ == STATE_NONE);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ net_log_.AddEvent(NetLog::TYPE_SSL_READ_ERROR,
+ CreateNetLogSSLErrorCallback(rv, 0));
+ return rv;
+ }
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadRead();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+ return rv;
+}
+
+int SSLServerSocketNSS::DoWriteLoop(int result) {
+ DCHECK(completed_handshake_);
+ DCHECK(next_handshake_state_ == STATE_NONE);
+
+ if (result < 0)
+ return result;
+
+ if (!nss_bufs_) {
+ LOG(DFATAL) << "!nss_bufs_";
+ int rv = ERR_UNEXPECTED;
+ net_log_.AddEvent(NetLog::TYPE_SSL_WRITE_ERROR,
+ CreateNetLogSSLErrorCallback(rv, 0));
+ return rv;
+ }
+
+ bool network_moved;
+ int rv;
+ do {
+ rv = DoPayloadWrite();
+ network_moved = DoTransportIO();
+ } while (rv == ERR_IO_PENDING && network_moved);
+ return rv;
+}
+
+int SSLServerSocketNSS::DoHandshake() {
+ int net_error = OK;
+ SECStatus rv = SSL_ForceHandshake(nss_fd_);
+
+ if (rv == SECSuccess) {
+ completed_handshake_ = true;
+ } else {
+ PRErrorCode prerr = PR_GetError();
+ net_error = MapNSSError(prerr);
+
+ // If not done, stay in this state
+ if (net_error == ERR_IO_PENDING) {
+ GotoState(STATE_HANDSHAKE);
+ } else {
+ LOG(ERROR) << "handshake failed; NSS error code " << prerr
+ << ", net_error " << net_error;
+ net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+ CreateNetLogSSLErrorCallback(net_error, prerr));
+ }
+ }
+ return net_error;
+}
+
+void SSLServerSocketNSS::DoHandshakeCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+
+ CompletionCallback c = user_handshake_callback_;
+ user_handshake_callback_.Reset();
+ c.Run(rv > OK ? OK : rv);
+}
+
+void SSLServerSocketNSS::DoReadCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!user_read_callback_.is_null());
+
+ // Since Run may result in Read being called, clear |user_read_callback_|
+ // up front.
+ CompletionCallback c = user_read_callback_;
+ user_read_callback_.Reset();
+ user_read_buf_ = NULL;
+ user_read_buf_len_ = 0;
+ c.Run(rv);
+}
+
+void SSLServerSocketNSS::DoWriteCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(!user_write_callback_.is_null());
+
+ // Since Run may result in Write being called, clear |user_write_callback_|
+ // up front.
+ CompletionCallback c = user_write_callback_;
+ user_write_callback_.Reset();
+ user_write_buf_ = NULL;
+ user_write_buf_len_ = 0;
+ c.Run(rv);
+}
+
+// static
+// NSS calls this if an incoming certificate needs to be verified.
+// Do nothing but return SECSuccess.
+// This is called only in full handshake mode.
+// Peer certificate is retrieved in HandshakeCallback() later, which is called
+// in full handshake mode or in resumption handshake mode.
+SECStatus SSLServerSocketNSS::OwnAuthCertHandler(void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server) {
+ // TODO(hclam): Implement.
+ // Tell NSS to not verify the certificate.
+ return SECSuccess;
+}
+
+// static
+// NSS calls this when handshake is completed.
+// After the SSL handshake is finished we need to verify the certificate.
+void SSLServerSocketNSS::HandshakeCallback(PRFileDesc* socket,
+ void* arg) {
+ // TODO(hclam): Implement.
+}
+
+int SSLServerSocketNSS::Init() {
+ // Initialize the NSS SSL library in a threadsafe way. This also
+ // initializes the NSS base library.
+ EnsureNSSSSLInit();
+ if (!NSS_IsInitialized())
+ return ERR_UNEXPECTED;
+
+ EnableSSLServerSockets();
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_server_socket_nss.h b/chromium/net/socket/ssl_server_socket_nss.h
new file mode 100644
index 00000000000..8bbb0e338ac
--- /dev/null
+++ b/chromium/net/socket/ssl_server_socket_nss.h
@@ -0,0 +1,150 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_SERVER_SOCKET_NSS_H_
+#define NET_SOCKET_SSL_SERVER_SOCKET_NSS_H_
+
+#include <certt.h>
+#include <keyt.h>
+#include <nspr.h>
+#include <nss.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/base/nss_memio.h"
+#include "net/socket/ssl_server_socket.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class SSLServerSocketNSS : public SSLServerSocket {
+ public:
+ // See comments on CreateSSLServerSocket for details of how these
+ // parameters are used.
+ SSLServerSocketNSS(scoped_ptr<StreamSocket> socket,
+ scoped_refptr<X509Certificate> certificate,
+ crypto::RSAPrivateKey* key,
+ const SSLConfig& ssl_config);
+ virtual ~SSLServerSocketNSS();
+
+ // SSLServerSocket interface.
+ virtual int Handshake(const CompletionCallback& callback) OVERRIDE;
+
+ // SSLSocket interface.
+ virtual int ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) OVERRIDE;
+ virtual int GetTLSUniqueChannelBinding(std::string* out) OVERRIDE;
+
+ // Socket interface (via StreamSocket).
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_HANDSHAKE,
+ };
+
+ int InitializeSSLOptions();
+
+ void OnSendComplete(int result);
+ void OnRecvComplete(int result);
+ void OnHandshakeIOComplete(int result);
+
+ int BufferSend();
+ void BufferSendComplete(int result);
+ int BufferRecv();
+ void BufferRecvComplete(int result);
+ bool DoTransportIO();
+ int DoPayloadRead();
+ int DoPayloadWrite();
+
+ int DoHandshakeLoop(int last_io_result);
+ int DoReadLoop(int result);
+ int DoWriteLoop(int result);
+ int DoHandshake();
+ void DoHandshakeCallback(int result);
+ void DoReadCallback(int result);
+ void DoWriteCallback(int result);
+
+ static SECStatus OwnAuthCertHandler(void* arg,
+ PRFileDesc* socket,
+ PRBool checksig,
+ PRBool is_server);
+ static void HandshakeCallback(PRFileDesc* socket, void* arg);
+
+ virtual int Init();
+
+ // Members used to send and receive buffer.
+ bool transport_send_busy_;
+ bool transport_recv_busy_;
+
+ scoped_refptr<IOBuffer> recv_buffer_;
+
+ BoundNetLog net_log_;
+
+ CompletionCallback user_handshake_callback_;
+ CompletionCallback user_read_callback_;
+ CompletionCallback user_write_callback_;
+
+ // Used by Read function.
+ scoped_refptr<IOBuffer> user_read_buf_;
+ int user_read_buf_len_;
+
+ // Used by Write function.
+ scoped_refptr<IOBuffer> user_write_buf_;
+ int user_write_buf_len_;
+
+ // The NSS SSL state machine
+ PRFileDesc* nss_fd_;
+
+ // Buffers for the network end of the SSL state machine
+ memio_Private* nss_bufs_;
+
+ // StreamSocket for sending and receiving data.
+ scoped_ptr<StreamSocket> transport_socket_;
+
+ // Options for the SSL socket.
+ SSLConfig ssl_config_;
+
+ // Certificate for the server.
+ scoped_refptr<X509Certificate> cert_;
+
+ // Private key used by the server.
+ scoped_ptr<crypto::RSAPrivateKey> key_;
+
+ State next_handshake_state_;
+ bool completed_handshake_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLServerSocketNSS);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_SERVER_SOCKET_NSS_H_
diff --git a/chromium/net/socket/ssl_server_socket_openssl.cc b/chromium/net/socket/ssl_server_socket_openssl.cc
new file mode 100644
index 00000000000..c327f2caf10
--- /dev/null
+++ b/chromium/net/socket/ssl_server_socket_openssl.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 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.
+
+#include "base/logging.h"
+#include "net/socket/ssl_server_socket.h"
+
+// TODO(bulach): Provide simple stubs for EnableSSLServerSockets and
+// CreateSSLServerSocket so that when building for OpenSSL rather than NSS,
+// so that the code using SSL server sockets can be compiled and disabled
+// programatically rather than requiring to be carved out from the compile.
+
+namespace net {
+
+void EnableSSLServerSockets() {
+ NOTIMPLEMENTED();
+}
+
+scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
+ scoped_ptr<StreamSocket> socket,
+ X509Certificate* certificate,
+ crypto::RSAPrivateKey* key,
+ const SSLConfig& ssl_config) {
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLServerSocket>();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_server_socket_unittest.cc b/chromium/net/socket/ssl_server_socket_unittest.cc
new file mode 100644
index 00000000000..64c85490b29
--- /dev/null
+++ b/chromium/net/socket/ssl_server_socket_unittest.cc
@@ -0,0 +1,588 @@
+// Copyright (c) 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.
+
+// This test suite uses SSLClientSocket to test the implementation of
+// SSLServerSocket. In order to establish connections between the sockets
+// we need two additional classes:
+// 1. FakeSocket
+// Connects SSL socket to FakeDataChannel. This class is just a stub.
+//
+// 2. FakeDataChannel
+// Implements the actual exchange of data between two FakeSockets.
+//
+// Implementations of these two classes are included in this file.
+
+#include "net/socket/ssl_server_socket.h"
+
+#include <stdlib.h>
+
+#include <queue>
+
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "crypto/nss_util.h"
+#include "crypto/rsa_private_key.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/cert/x509_certificate.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_info.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+class FakeDataChannel {
+ public:
+ FakeDataChannel()
+ : read_buf_len_(0),
+ weak_factory_(this),
+ closed_(false),
+ write_called_after_close_(false) {
+ }
+
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+ if (closed_)
+ return 0;
+ if (data_.empty()) {
+ read_callback_ = callback;
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+ return net::ERR_IO_PENDING;
+ }
+ return PropogateData(buf, buf_len);
+ }
+
+ int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+ if (closed_) {
+ if (write_called_after_close_)
+ return net::ERR_CONNECTION_RESET;
+ write_called_after_close_ = true;
+ write_callback_ = callback;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&FakeDataChannel::DoWriteCallback,
+ weak_factory_.GetWeakPtr()));
+ return net::ERR_IO_PENDING;
+ }
+ data_.push(new net::DrainableIOBuffer(buf, buf_len));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&FakeDataChannel::DoReadCallback,
+ weak_factory_.GetWeakPtr()));
+ return buf_len;
+ }
+
+ // Closes the FakeDataChannel. After Close() is called, Read() returns 0,
+ // indicating EOF, and Write() fails with ERR_CONNECTION_RESET. Note that
+ // after the FakeDataChannel is closed, the first Write() call completes
+ // asynchronously, which is necessary to reproduce bug 127822.
+ void Close() {
+ closed_ = true;
+ }
+
+ private:
+ void DoReadCallback() {
+ if (read_callback_.is_null() || data_.empty())
+ return;
+
+ int copied = PropogateData(read_buf_, read_buf_len_);
+ CompletionCallback callback = read_callback_;
+ read_callback_.Reset();
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ callback.Run(copied);
+ }
+
+ void DoWriteCallback() {
+ if (write_callback_.is_null())
+ return;
+
+ CompletionCallback callback = write_callback_;
+ write_callback_.Reset();
+ callback.Run(net::ERR_CONNECTION_RESET);
+ }
+
+ int PropogateData(scoped_refptr<net::IOBuffer> read_buf, int read_buf_len) {
+ scoped_refptr<net::DrainableIOBuffer> buf = data_.front();
+ int copied = std::min(buf->BytesRemaining(), read_buf_len);
+ memcpy(read_buf->data(), buf->data(), copied);
+ buf->DidConsume(copied);
+
+ if (!buf->BytesRemaining())
+ data_.pop();
+ return copied;
+ }
+
+ CompletionCallback read_callback_;
+ scoped_refptr<net::IOBuffer> read_buf_;
+ int read_buf_len_;
+
+ CompletionCallback write_callback_;
+
+ std::queue<scoped_refptr<net::DrainableIOBuffer> > data_;
+
+ base::WeakPtrFactory<FakeDataChannel> weak_factory_;
+
+ // True if Close() has been called.
+ bool closed_;
+
+ // Controls the completion of Write() after the FakeDataChannel is closed.
+ // After the FakeDataChannel is closed, the first Write() call completes
+ // asynchronously.
+ bool write_called_after_close_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDataChannel);
+};
+
+class FakeSocket : public StreamSocket {
+ public:
+ FakeSocket(FakeDataChannel* incoming_channel,
+ FakeDataChannel* outgoing_channel)
+ : incoming_(incoming_channel),
+ outgoing_(outgoing_channel) {
+ }
+
+ virtual ~FakeSocket() {
+ }
+
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ // Read random number of bytes.
+ buf_len = rand() % buf_len + 1;
+ return incoming_->Read(buf, buf_len, callback);
+ }
+
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ // Write random number of bytes.
+ buf_len = rand() % buf_len + 1;
+ return outgoing_->Write(buf, buf_len, callback);
+ }
+
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE {
+ return true;
+ }
+
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE {
+ return true;
+ }
+
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ return net::OK;
+ }
+
+ virtual void Disconnect() OVERRIDE {
+ incoming_->Close();
+ outgoing_->Close();
+ }
+
+ virtual bool IsConnected() const OVERRIDE {
+ return true;
+ }
+
+ virtual bool IsConnectedAndIdle() const OVERRIDE {
+ return true;
+ }
+
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ net::IPAddressNumber ip_address(net::kIPv4AddressSize);
+ *address = net::IPEndPoint(ip_address, 0 /*port*/);
+ return net::OK;
+ }
+
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ net::IPAddressNumber ip_address(4);
+ *address = net::IPEndPoint(ip_address, 0);
+ return net::OK;
+ }
+
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+
+ virtual bool WasEverUsed() const OVERRIDE {
+ return true;
+ }
+
+ virtual bool UsingTCPFastOpen() const OVERRIDE {
+ return false;
+ }
+
+
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return false;
+ }
+
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return false;
+ }
+
+ private:
+ net::BoundNetLog net_log_;
+ FakeDataChannel* incoming_;
+ FakeDataChannel* outgoing_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeSocket);
+};
+
+} // namespace
+
+// Verify the correctness of the test helper classes first.
+TEST(FakeSocketTest, DataTransfer) {
+ // Establish channels between two sockets.
+ FakeDataChannel channel_1;
+ FakeDataChannel channel_2;
+ FakeSocket client(&channel_1, &channel_2);
+ FakeSocket server(&channel_2, &channel_1);
+
+ const char kTestData[] = "testing123";
+ const int kTestDataSize = strlen(kTestData);
+ const int kReadBufSize = 1024;
+ scoped_refptr<net::IOBuffer> write_buf = new net::StringIOBuffer(kTestData);
+ scoped_refptr<net::IOBuffer> read_buf = new net::IOBuffer(kReadBufSize);
+
+ // Write then read.
+ int written =
+ server.Write(write_buf.get(), kTestDataSize, CompletionCallback());
+ EXPECT_GT(written, 0);
+ EXPECT_LE(written, kTestDataSize);
+
+ int read = client.Read(read_buf.get(), kReadBufSize, CompletionCallback());
+ EXPECT_GT(read, 0);
+ EXPECT_LE(read, written);
+ EXPECT_EQ(0, memcmp(kTestData, read_buf->data(), read));
+
+ // Read then write.
+ TestCompletionCallback callback;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ server.Read(read_buf.get(), kReadBufSize, callback.callback()));
+
+ written = client.Write(write_buf.get(), kTestDataSize, CompletionCallback());
+ EXPECT_GT(written, 0);
+ EXPECT_LE(written, kTestDataSize);
+
+ read = callback.WaitForResult();
+ EXPECT_GT(read, 0);
+ EXPECT_LE(read, written);
+ EXPECT_EQ(0, memcmp(kTestData, read_buf->data(), read));
+}
+
+class SSLServerSocketTest : public PlatformTest {
+ public:
+ SSLServerSocketTest()
+ : socket_factory_(net::ClientSocketFactory::GetDefaultFactory()),
+ cert_verifier_(new MockCertVerifier()),
+ transport_security_state_(new TransportSecurityState) {
+ cert_verifier_->set_default_result(net::CERT_STATUS_AUTHORITY_INVALID);
+ }
+
+ protected:
+ void Initialize() {
+ scoped_ptr<ClientSocketHandle> client_connection(new ClientSocketHandle);
+ client_connection->SetSocket(
+ scoped_ptr<StreamSocket>(new FakeSocket(&channel_1_, &channel_2_)));
+ scoped_ptr<StreamSocket> server_socket(
+ new FakeSocket(&channel_2_, &channel_1_));
+
+ base::FilePath certs_dir(GetTestCertsDirectory());
+
+ base::FilePath cert_path = certs_dir.AppendASCII("unittest.selfsigned.der");
+ std::string cert_der;
+ ASSERT_TRUE(file_util::ReadFileToString(cert_path, &cert_der));
+
+ scoped_refptr<net::X509Certificate> cert =
+ X509Certificate::CreateFromBytes(cert_der.data(), cert_der.size());
+
+ base::FilePath key_path = certs_dir.AppendASCII("unittest.key.bin");
+ std::string key_string;
+ ASSERT_TRUE(file_util::ReadFileToString(key_path, &key_string));
+ std::vector<uint8> key_vector(
+ reinterpret_cast<const uint8*>(key_string.data()),
+ reinterpret_cast<const uint8*>(key_string.data() +
+ key_string.length()));
+
+ scoped_ptr<crypto::RSAPrivateKey> private_key(
+ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_vector));
+
+ net::SSLConfig ssl_config;
+ ssl_config.cached_info_enabled = false;
+ ssl_config.false_start_enabled = false;
+ ssl_config.channel_id_enabled = false;
+ ssl_config.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ ssl_config.version_max = SSL_PROTOCOL_VERSION_TLS1_1;
+
+ // Certificate provided by the host doesn't need authority.
+ net::SSLConfig::CertAndStatus cert_and_status;
+ cert_and_status.cert_status = CERT_STATUS_AUTHORITY_INVALID;
+ cert_and_status.der_cert = cert_der;
+ ssl_config.allowed_bad_certs.push_back(cert_and_status);
+
+ net::HostPortPair host_and_pair("unittest", 0);
+ net::SSLClientSocketContext context;
+ context.cert_verifier = cert_verifier_.get();
+ context.transport_security_state = transport_security_state_.get();
+ client_socket_ =
+ socket_factory_->CreateSSLClientSocket(
+ client_connection.Pass(), host_and_pair, ssl_config, context);
+ server_socket_ = net::CreateSSLServerSocket(
+ server_socket.Pass(),
+ cert.get(), private_key.get(), net::SSLConfig());
+ }
+
+ FakeDataChannel channel_1_;
+ FakeDataChannel channel_2_;
+ scoped_ptr<net::SSLClientSocket> client_socket_;
+ scoped_ptr<net::SSLServerSocket> server_socket_;
+ net::ClientSocketFactory* socket_factory_;
+ scoped_ptr<net::MockCertVerifier> cert_verifier_;
+ scoped_ptr<net::TransportSecurityState> transport_security_state_;
+};
+
+// SSLServerSocket is only implemented using NSS.
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX)
+
+// This test only executes creation of client and server sockets. This is to
+// test that creation of sockets doesn't crash and have minimal code to run
+// under valgrind in order to help debugging memory problems.
+TEST_F(SSLServerSocketTest, Initialize) {
+ Initialize();
+}
+
+// This test executes Connect() on SSLClientSocket and Handshake() on
+// SSLServerSocket to make sure handshaking between the two sockets is
+// completed successfully.
+TEST_F(SSLServerSocketTest, Handshake) {
+ Initialize();
+
+ TestCompletionCallback connect_callback;
+ TestCompletionCallback handshake_callback;
+
+ int server_ret = server_socket_->Handshake(handshake_callback.callback());
+ EXPECT_TRUE(server_ret == net::OK || server_ret == net::ERR_IO_PENDING);
+
+ int client_ret = client_socket_->Connect(connect_callback.callback());
+ EXPECT_TRUE(client_ret == net::OK || client_ret == net::ERR_IO_PENDING);
+
+ if (client_ret == net::ERR_IO_PENDING) {
+ EXPECT_EQ(net::OK, connect_callback.WaitForResult());
+ }
+ if (server_ret == net::ERR_IO_PENDING) {
+ EXPECT_EQ(net::OK, handshake_callback.WaitForResult());
+ }
+
+ // Make sure the cert status is expected.
+ SSLInfo ssl_info;
+ client_socket_->GetSSLInfo(&ssl_info);
+ EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, ssl_info.cert_status);
+}
+
+TEST_F(SSLServerSocketTest, DataTransfer) {
+ Initialize();
+
+ TestCompletionCallback connect_callback;
+ TestCompletionCallback handshake_callback;
+
+ // Establish connection.
+ int client_ret = client_socket_->Connect(connect_callback.callback());
+ ASSERT_TRUE(client_ret == net::OK || client_ret == net::ERR_IO_PENDING);
+
+ int server_ret = server_socket_->Handshake(handshake_callback.callback());
+ ASSERT_TRUE(server_ret == net::OK || server_ret == net::ERR_IO_PENDING);
+
+ client_ret = connect_callback.GetResult(client_ret);
+ ASSERT_EQ(net::OK, client_ret);
+ server_ret = handshake_callback.GetResult(server_ret);
+ ASSERT_EQ(net::OK, server_ret);
+
+ const int kReadBufSize = 1024;
+ scoped_refptr<net::StringIOBuffer> write_buf =
+ new net::StringIOBuffer("testing123");
+ scoped_refptr<net::DrainableIOBuffer> read_buf =
+ new net::DrainableIOBuffer(new net::IOBuffer(kReadBufSize),
+ kReadBufSize);
+
+ // Write then read.
+ TestCompletionCallback write_callback;
+ TestCompletionCallback read_callback;
+ server_ret = server_socket_->Write(
+ write_buf.get(), write_buf->size(), write_callback.callback());
+ EXPECT_TRUE(server_ret > 0 || server_ret == net::ERR_IO_PENDING);
+ client_ret = client_socket_->Read(
+ read_buf.get(), read_buf->BytesRemaining(), read_callback.callback());
+ EXPECT_TRUE(client_ret > 0 || client_ret == net::ERR_IO_PENDING);
+
+ server_ret = write_callback.GetResult(server_ret);
+ EXPECT_GT(server_ret, 0);
+ client_ret = read_callback.GetResult(client_ret);
+ ASSERT_GT(client_ret, 0);
+
+ read_buf->DidConsume(client_ret);
+ while (read_buf->BytesConsumed() < write_buf->size()) {
+ client_ret = client_socket_->Read(
+ read_buf.get(), read_buf->BytesRemaining(), read_callback.callback());
+ EXPECT_TRUE(client_ret > 0 || client_ret == net::ERR_IO_PENDING);
+ client_ret = read_callback.GetResult(client_ret);
+ ASSERT_GT(client_ret, 0);
+ read_buf->DidConsume(client_ret);
+ }
+ EXPECT_EQ(write_buf->size(), read_buf->BytesConsumed());
+ read_buf->SetOffset(0);
+ EXPECT_EQ(0, memcmp(write_buf->data(), read_buf->data(), write_buf->size()));
+
+ // Read then write.
+ write_buf = new net::StringIOBuffer("hello123");
+ server_ret = server_socket_->Read(
+ read_buf.get(), read_buf->BytesRemaining(), read_callback.callback());
+ EXPECT_TRUE(server_ret > 0 || server_ret == net::ERR_IO_PENDING);
+ client_ret = client_socket_->Write(
+ write_buf.get(), write_buf->size(), write_callback.callback());
+ EXPECT_TRUE(client_ret > 0 || client_ret == net::ERR_IO_PENDING);
+
+ server_ret = read_callback.GetResult(server_ret);
+ ASSERT_GT(server_ret, 0);
+ client_ret = write_callback.GetResult(client_ret);
+ EXPECT_GT(client_ret, 0);
+
+ read_buf->DidConsume(server_ret);
+ while (read_buf->BytesConsumed() < write_buf->size()) {
+ server_ret = server_socket_->Read(
+ read_buf.get(), read_buf->BytesRemaining(), read_callback.callback());
+ EXPECT_TRUE(server_ret > 0 || server_ret == net::ERR_IO_PENDING);
+ server_ret = read_callback.GetResult(server_ret);
+ ASSERT_GT(server_ret, 0);
+ read_buf->DidConsume(server_ret);
+ }
+ EXPECT_EQ(write_buf->size(), read_buf->BytesConsumed());
+ read_buf->SetOffset(0);
+ EXPECT_EQ(0, memcmp(write_buf->data(), read_buf->data(), write_buf->size()));
+}
+
+// A regression test for bug 127822 (http://crbug.com/127822).
+// If the server closes the connection after the handshake is finished,
+// the client's Write() call should not cause an infinite loop.
+// NOTE: this is a test for SSLClientSocket rather than SSLServerSocket.
+TEST_F(SSLServerSocketTest, ClientWriteAfterServerClose) {
+ Initialize();
+
+ TestCompletionCallback connect_callback;
+ TestCompletionCallback handshake_callback;
+
+ // Establish connection.
+ int client_ret = client_socket_->Connect(connect_callback.callback());
+ ASSERT_TRUE(client_ret == net::OK || client_ret == net::ERR_IO_PENDING);
+
+ int server_ret = server_socket_->Handshake(handshake_callback.callback());
+ ASSERT_TRUE(server_ret == net::OK || server_ret == net::ERR_IO_PENDING);
+
+ client_ret = connect_callback.GetResult(client_ret);
+ ASSERT_EQ(net::OK, client_ret);
+ server_ret = handshake_callback.GetResult(server_ret);
+ ASSERT_EQ(net::OK, server_ret);
+
+ scoped_refptr<net::StringIOBuffer> write_buf =
+ new net::StringIOBuffer("testing123");
+
+ // The server closes the connection. The server needs to write some
+ // data first so that the client's Read() calls from the transport
+ // socket won't return ERR_IO_PENDING. This ensures that the client
+ // will call Read() on the transport socket again.
+ TestCompletionCallback write_callback;
+
+ server_ret = server_socket_->Write(
+ write_buf.get(), write_buf->size(), write_callback.callback());
+ EXPECT_TRUE(server_ret > 0 || server_ret == net::ERR_IO_PENDING);
+
+ server_ret = write_callback.GetResult(server_ret);
+ EXPECT_GT(server_ret, 0);
+
+ server_socket_->Disconnect();
+
+ // The client writes some data. This should not cause an infinite loop.
+ client_ret = client_socket_->Write(
+ write_buf.get(), write_buf->size(), write_callback.callback());
+ EXPECT_TRUE(client_ret > 0 || client_ret == net::ERR_IO_PENDING);
+
+ client_ret = write_callback.GetResult(client_ret);
+ EXPECT_GT(client_ret, 0);
+
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromMilliseconds(10));
+ base::MessageLoop::current()->Run();
+}
+
+// This test executes ExportKeyingMaterial() on the client and server sockets,
+// after connecting them, and verifies that the results match.
+// This test will fail if False Start is enabled (see crbug.com/90208).
+TEST_F(SSLServerSocketTest, ExportKeyingMaterial) {
+ Initialize();
+
+ TestCompletionCallback connect_callback;
+ TestCompletionCallback handshake_callback;
+
+ int client_ret = client_socket_->Connect(connect_callback.callback());
+ ASSERT_TRUE(client_ret == net::OK || client_ret == net::ERR_IO_PENDING);
+
+ int server_ret = server_socket_->Handshake(handshake_callback.callback());
+ ASSERT_TRUE(server_ret == net::OK || server_ret == net::ERR_IO_PENDING);
+
+ if (client_ret == net::ERR_IO_PENDING) {
+ ASSERT_EQ(net::OK, connect_callback.WaitForResult());
+ }
+ if (server_ret == net::ERR_IO_PENDING) {
+ ASSERT_EQ(net::OK, handshake_callback.WaitForResult());
+ }
+
+ const int kKeyingMaterialSize = 32;
+ const char* kKeyingLabel = "EXPERIMENTAL-server-socket-test";
+ const char* kKeyingContext = "";
+ unsigned char server_out[kKeyingMaterialSize];
+ int rv = server_socket_->ExportKeyingMaterial(kKeyingLabel,
+ false, kKeyingContext,
+ server_out, sizeof(server_out));
+ ASSERT_EQ(net::OK, rv);
+
+ unsigned char client_out[kKeyingMaterialSize];
+ rv = client_socket_->ExportKeyingMaterial(kKeyingLabel,
+ false, kKeyingContext,
+ client_out, sizeof(client_out));
+ ASSERT_EQ(net::OK, rv);
+ EXPECT_EQ(0, memcmp(server_out, client_out, sizeof(server_out)));
+
+ const char* kKeyingLabelBad = "EXPERIMENTAL-server-socket-test-bad";
+ unsigned char client_bad[kKeyingMaterialSize];
+ rv = client_socket_->ExportKeyingMaterial(kKeyingLabelBad,
+ false, kKeyingContext,
+ client_bad, sizeof(client_bad));
+ ASSERT_EQ(rv, net::OK);
+ EXPECT_NE(0, memcmp(server_out, client_bad, sizeof(server_out)));
+}
+#endif
+
+} // namespace net
diff --git a/chromium/net/socket/ssl_socket.h b/chromium/net/socket/ssl_socket.h
new file mode 100644
index 00000000000..68d1e4a2bfe
--- /dev/null
+++ b/chromium/net/socket/ssl_socket.h
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_SSL_SOCKET_H_
+#define NET_SOCKET_SSL_SOCKET_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+// SSLSocket interface defines method that are common between client
+// and server SSL sockets.
+class NET_EXPORT SSLSocket : public StreamSocket {
+public:
+ virtual ~SSLSocket() {}
+
+ // Exports data derived from the SSL master-secret (see RFC 5705).
+ // If |has_context| is false, uses the no-context construction from the
+ // RFC and |context| is ignored. The call will fail with an error if
+ // the socket is not connected or the SSL implementation does not
+ // support the operation.
+ virtual int ExportKeyingMaterial(const base::StringPiece& label,
+ bool has_context,
+ const base::StringPiece& context,
+ unsigned char* out,
+ unsigned int outlen) = 0;
+
+ // Stores the the tls-unique channel binding (see RFC 5929) in |*out|.
+ virtual int GetTLSUniqueChannelBinding(std::string* out) = 0;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_SSL_SOCKET_H_
diff --git a/chromium/net/socket/stream_listen_socket.cc b/chromium/net/socket/stream_listen_socket.cc
new file mode 100644
index 00000000000..c85c671800d
--- /dev/null
+++ b/chromium/net/socket/stream_listen_socket.cc
@@ -0,0 +1,308 @@
+// Copyright (c) 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.
+
+#include "net/socket/stream_listen_socket.h"
+
+#if defined(OS_WIN)
+// winsock2.h must be included first in order to ensure it is included before
+// windows.h.
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include "net/base/net_errors.h"
+#endif
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/sys_byteorder.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+using std::string;
+
+#if defined(OS_WIN)
+typedef int socklen_t;
+#endif // defined(OS_WIN)
+
+namespace net {
+
+namespace {
+
+const int kReadBufSize = 4096;
+
+} // namespace
+
+#if defined(OS_WIN)
+const SocketDescriptor StreamListenSocket::kInvalidSocket = INVALID_SOCKET;
+const int StreamListenSocket::kSocketError = SOCKET_ERROR;
+#elif defined(OS_POSIX)
+const SocketDescriptor StreamListenSocket::kInvalidSocket = -1;
+const int StreamListenSocket::kSocketError = -1;
+#endif
+
+StreamListenSocket::StreamListenSocket(SocketDescriptor s,
+ StreamListenSocket::Delegate* del)
+ : socket_delegate_(del),
+ socket_(s),
+ reads_paused_(false),
+ has_pending_reads_(false) {
+#if defined(OS_WIN)
+ socket_event_ = WSACreateEvent();
+ // TODO(ibrar): error handling in case of socket_event_ == WSA_INVALID_EVENT.
+ WatchSocket(NOT_WAITING);
+#elif defined(OS_POSIX)
+ wait_state_ = NOT_WAITING;
+#endif
+}
+
+StreamListenSocket::~StreamListenSocket() {
+#if defined(OS_WIN)
+ if (socket_event_) {
+ WSACloseEvent(socket_event_);
+ socket_event_ = WSA_INVALID_EVENT;
+ }
+#endif
+ CloseSocket(socket_);
+}
+
+void StreamListenSocket::Send(const char* bytes, int len,
+ bool append_linefeed) {
+ SendInternal(bytes, len);
+ if (append_linefeed)
+ SendInternal("\r\n", 2);
+}
+
+void StreamListenSocket::Send(const string& str, bool append_linefeed) {
+ Send(str.data(), static_cast<int>(str.length()), append_linefeed);
+}
+
+int StreamListenSocket::GetLocalAddress(IPEndPoint* address) {
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len)) {
+#if defined(OS_WIN)
+ int err = WSAGetLastError();
+#else
+ int err = errno;
+#endif
+ return MapSystemError(err);
+ }
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+ return OK;
+}
+
+SocketDescriptor StreamListenSocket::AcceptSocket() {
+ SocketDescriptor conn = HANDLE_EINTR(accept(socket_, NULL, NULL));
+ if (conn == kInvalidSocket)
+ LOG(ERROR) << "Error accepting connection.";
+ else
+ SetNonBlocking(conn);
+ return conn;
+}
+
+void StreamListenSocket::SendInternal(const char* bytes, int len) {
+ char* send_buf = const_cast<char *>(bytes);
+ int len_left = len;
+ while (true) {
+ int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0));
+ if (sent == len_left) { // A shortcut to avoid extraneous checks.
+ break;
+ }
+ if (sent == kSocketError) {
+#if defined(OS_WIN)
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError();
+#elif defined(OS_POSIX)
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ LOG(ERROR) << "send failed: errno==" << errno;
+#endif
+ break;
+ }
+ // Otherwise we would block, and now we have to wait for a retry.
+ // Fall through to PlatformThread::YieldCurrentThread()
+ } else {
+ // sent != len_left according to the shortcut above.
+ // Shift the buffer start and send the remainder after a short while.
+ send_buf += sent;
+ len_left -= sent;
+ }
+ base::PlatformThread::YieldCurrentThread();
+ }
+}
+
+void StreamListenSocket::Listen() {
+ int backlog = 10; // TODO(erikkay): maybe don't allow any backlog?
+ if (listen(socket_, backlog) == -1) {
+ // TODO(erikkay): error handling.
+ LOG(ERROR) << "Could not listen on socket.";
+ return;
+ }
+#if defined(OS_POSIX)
+ WatchSocket(WAITING_ACCEPT);
+#endif
+}
+
+void StreamListenSocket::Read() {
+ char buf[kReadBufSize + 1]; // +1 for null termination.
+ int len;
+ do {
+ len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0));
+ if (len == kSocketError) {
+#if defined(OS_WIN)
+ int err = WSAGetLastError();
+ if (err == WSAEWOULDBLOCK) {
+#elif defined(OS_POSIX)
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+#endif
+ break;
+ } else {
+ // TODO(ibrar): some error handling required here.
+ break;
+ }
+ } else if (len == 0) {
+ // In Windows, Close() is called by OnObjectSignaled. In POSIX, we need
+ // to call it here.
+#if defined(OS_POSIX)
+ Close();
+#endif
+ } else {
+ // TODO(ibrar): maybe change DidRead to take a length instead.
+ DCHECK_GT(len, 0);
+ DCHECK_LE(len, kReadBufSize);
+ buf[len] = 0; // Already create a buffer with +1 length.
+ socket_delegate_->DidRead(this, buf, len);
+ }
+ } while (len == kReadBufSize);
+}
+
+void StreamListenSocket::Close() {
+#if defined(OS_POSIX)
+ if (wait_state_ == NOT_WAITING)
+ return;
+ wait_state_ = NOT_WAITING;
+#endif
+ UnwatchSocket();
+ socket_delegate_->DidClose(this);
+}
+
+void StreamListenSocket::CloseSocket(SocketDescriptor s) {
+ if (s && s != kInvalidSocket) {
+ UnwatchSocket();
+#if defined(OS_WIN)
+ closesocket(s);
+#elif defined(OS_POSIX)
+ close(s);
+#endif
+ }
+}
+
+void StreamListenSocket::WatchSocket(WaitState state) {
+#if defined(OS_WIN)
+ WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ);
+ watcher_.StartWatching(socket_event_, this);
+#elif defined(OS_POSIX)
+ // Implicitly calls StartWatchingFileDescriptor().
+ base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_READ, &watcher_, this);
+ wait_state_ = state;
+#endif
+}
+
+void StreamListenSocket::UnwatchSocket() {
+#if defined(OS_WIN)
+ watcher_.StopWatching();
+#elif defined(OS_POSIX)
+ watcher_.StopWatchingFileDescriptor();
+#endif
+}
+
+// TODO(ibrar): We can add these functions into OS dependent files.
+#if defined(OS_WIN)
+// MessageLoop watcher callback.
+void StreamListenSocket::OnObjectSignaled(HANDLE object) {
+ WSANETWORKEVENTS ev;
+ if (kSocketError == WSAEnumNetworkEvents(socket_, socket_event_, &ev)) {
+ // TODO
+ return;
+ }
+
+ if (ev.lNetworkEvents & FD_CLOSE) {
+ Close();
+ // Close might have deleted this object. We should return immediately.
+ return;
+ }
+
+ // The object was reset by WSAEnumNetworkEvents. Watch for the next signal.
+ watcher_.StartWatching(object, this);
+
+ if (ev.lNetworkEvents == 0) {
+ // Occasionally the event is set even though there is no new data.
+ // The net seems to think that this is ignorable.
+ return;
+ }
+ if (ev.lNetworkEvents & FD_ACCEPT) {
+ Accept();
+ }
+ if (ev.lNetworkEvents & FD_READ) {
+ if (reads_paused_) {
+ has_pending_reads_ = true;
+ } else {
+ Read();
+ // Read() might call Close() internally and 'this' can be invalid here
+ return;
+ }
+ }
+}
+#elif defined(OS_POSIX)
+void StreamListenSocket::OnFileCanReadWithoutBlocking(int fd) {
+ switch (wait_state_) {
+ case WAITING_ACCEPT:
+ Accept();
+ break;
+ case WAITING_READ:
+ if (reads_paused_) {
+ has_pending_reads_ = true;
+ } else {
+ Read();
+ }
+ break;
+ default:
+ // Close() is called by Read() in the Linux case.
+ NOTREACHED();
+ break;
+ }
+}
+
+void StreamListenSocket::OnFileCanWriteWithoutBlocking(int fd) {
+ // MessagePumpLibevent callback, we don't listen for write events
+ // so we shouldn't ever reach here.
+ NOTREACHED();
+}
+
+#endif
+
+void StreamListenSocket::PauseReads() {
+ DCHECK(!reads_paused_);
+ reads_paused_ = true;
+}
+
+void StreamListenSocket::ResumeReads() {
+ DCHECK(reads_paused_);
+ reads_paused_ = false;
+ if (has_pending_reads_) {
+ has_pending_reads_ = false;
+ Read();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/socket/stream_listen_socket.h b/chromium/net/socket/stream_listen_socket.h
new file mode 100644
index 00000000000..6f03eefaca2
--- /dev/null
+++ b/chromium/net/socket/stream_listen_socket.h
@@ -0,0 +1,155 @@
+// Copyright (c) 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.
+
+// Stream-based listen socket implementation that handles reading and writing
+// to the socket, but does not handle creating the socket nor connecting
+// sockets, which are handled by subclasses on creation and in Accept,
+// respectively.
+
+// StreamListenSocket handles IO asynchronously in the specified MessageLoop.
+// This class is NOT thread safe. It uses WSAEVENT handles to monitor activity
+// in a given MessageLoop. This means that callbacks will happen in that loop's
+// thread always and that all other methods (including constructor and
+// destructor) should also be called from the same thread.
+
+#ifndef NET_SOCKET_STREAM_LISTEN_SOCKET_H_
+#define NET_SOCKET_STREAM_LISTEN_SOCKET_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <winsock2.h>
+#endif
+#include <string>
+#if defined(OS_WIN)
+#include "base/win/object_watcher.h"
+#elif defined(OS_POSIX)
+#include "base/message_loop/message_loop.h"
+#endif
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/socket/stream_listen_socket.h"
+
+#if defined(OS_POSIX)
+typedef int SocketDescriptor;
+#else
+typedef SOCKET SocketDescriptor;
+#endif
+
+namespace net {
+
+class IPEndPoint;
+
+class NET_EXPORT StreamListenSocket
+ : public base::RefCountedThreadSafe<StreamListenSocket>,
+#if defined(OS_WIN)
+ public base::win::ObjectWatcher::Delegate {
+#elif defined(OS_POSIX)
+ public base::MessageLoopForIO::Watcher {
+#endif
+
+ public:
+ // TODO(erikkay): this delegate should really be split into two parts
+ // to split up the listener from the connected socket. Perhaps this class
+ // should be split up similarly.
+ class Delegate {
+ public:
+ // |server| is the original listening Socket, connection is the new
+ // Socket that was created. Ownership of |connection| is transferred
+ // to the delegate with this call.
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) = 0;
+ virtual void DidRead(StreamListenSocket* connection,
+ const char* data,
+ int len) = 0;
+ virtual void DidClose(StreamListenSocket* sock) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // Send data to the socket.
+ void Send(const char* bytes, int len, bool append_linefeed = false);
+ void Send(const std::string& str, bool append_linefeed = false);
+
+ // Copies the local address to |address|. Returns a network error code.
+ int GetLocalAddress(IPEndPoint* address);
+
+ static const SocketDescriptor kInvalidSocket;
+ static const int kSocketError;
+
+ protected:
+ enum WaitState {
+ NOT_WAITING = 0,
+ WAITING_ACCEPT = 1,
+ WAITING_READ = 2
+ };
+
+ StreamListenSocket(SocketDescriptor s, Delegate* del);
+ virtual ~StreamListenSocket();
+
+ SocketDescriptor AcceptSocket();
+ virtual void Accept() = 0;
+
+ void Listen();
+ void Read();
+ void Close();
+ void CloseSocket(SocketDescriptor s);
+
+ // Pass any value in case of Windows, because in Windows
+ // we are not using state.
+ void WatchSocket(WaitState state);
+ void UnwatchSocket();
+
+ Delegate* const socket_delegate_;
+
+ private:
+ friend class base::RefCountedThreadSafe<StreamListenSocket>;
+ friend class TransportClientSocketTest;
+
+ void SendInternal(const char* bytes, int len);
+
+#if defined(OS_WIN)
+ // ObjectWatcher delegate.
+ virtual void OnObjectSignaled(HANDLE object);
+ base::win::ObjectWatcher watcher_;
+ HANDLE socket_event_;
+#elif defined(OS_POSIX)
+ // Called by MessagePumpLibevent when the socket is ready to do I/O.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+ WaitState wait_state_;
+ // The socket's libevent wrapper.
+ base::MessageLoopForIO::FileDescriptorWatcher watcher_;
+#endif
+
+ // NOTE: This is for unit test use only!
+ // Pause/Resume calling Read(). Note that ResumeReads() will also call
+ // Read() if there is anything to read.
+ void PauseReads();
+ void ResumeReads();
+
+ const SocketDescriptor socket_;
+ bool reads_paused_;
+ bool has_pending_reads_;
+
+ DISALLOW_COPY_AND_ASSIGN(StreamListenSocket);
+};
+
+// Abstract factory that must be subclassed for each subclass of
+// StreamListenSocket.
+class NET_EXPORT StreamListenSocketFactory {
+ public:
+ virtual ~StreamListenSocketFactory() {}
+
+ // Returns a new instance of StreamListenSocket or NULL if an error occurred.
+ virtual scoped_refptr<StreamListenSocket> CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const = 0;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_LISTEN_SOCKET_H_
diff --git a/chromium/net/socket/stream_socket.cc b/chromium/net/socket/stream_socket.cc
new file mode 100644
index 00000000000..fb194f64625
--- /dev/null
+++ b/chromium/net/socket/stream_socket.cc
@@ -0,0 +1,101 @@
+// Copyright (c) 2011 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/socket/stream_socket.h"
+
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+
+namespace net {
+
+StreamSocket::UseHistory::UseHistory()
+ : was_ever_connected_(false),
+ was_used_to_convey_data_(false),
+ omnibox_speculation_(false),
+ subresource_speculation_(false) {
+}
+
+StreamSocket::UseHistory::~UseHistory() {
+ EmitPreconnectionHistograms();
+}
+
+void StreamSocket::UseHistory::Reset() {
+ EmitPreconnectionHistograms();
+ was_ever_connected_ = false;
+ was_used_to_convey_data_ = false;
+ // omnibox_speculation_ and subresource_speculation_ values
+ // are intentionally preserved.
+}
+
+void StreamSocket::UseHistory::set_was_ever_connected() {
+ DCHECK(!was_used_to_convey_data_);
+ was_ever_connected_ = true;
+}
+
+void StreamSocket::UseHistory::set_was_used_to_convey_data() {
+ DCHECK(was_ever_connected_);
+ was_used_to_convey_data_ = true;
+}
+
+
+void StreamSocket::UseHistory::set_subresource_speculation() {
+ DCHECK(was_ever_connected_);
+ // TODO(jar): We should transition to marking a socket (or stream) at
+ // construction time as being created for speculative reasons. This current
+ // approach of trying to track use of a socket to convey data can make
+ // mistakes when other sockets (such as ones sitting in the pool for a long
+ // time) are issued. Unused sockets can be left over when a when a set of
+ // connections to a host are made, and one is "unlucky" and takes so long to
+ // complete a connection, that another socket is used, and recycled before a
+ // second connection comes available. Similarly, re-try connections can leave
+ // an original (slow to connect socket) in the pool, and that can be issued
+ // to a speculative requester. In any cases such old sockets will fail when an
+ // attempt is made to used them!... and then it will look like a speculative
+ // socket was discarded without any user!?!?!
+ if (was_used_to_convey_data_)
+ return;
+ subresource_speculation_ = true;
+}
+
+void StreamSocket::UseHistory::set_omnibox_speculation() {
+ DCHECK(was_ever_connected_);
+ if (was_used_to_convey_data_)
+ return;
+ omnibox_speculation_ = true;
+}
+
+bool StreamSocket::UseHistory::was_used_to_convey_data() const {
+ DCHECK(!was_used_to_convey_data_ || was_ever_connected_);
+ return was_used_to_convey_data_;
+}
+
+void StreamSocket::UseHistory::EmitPreconnectionHistograms() const {
+ DCHECK(!subresource_speculation_ || !omnibox_speculation_);
+ // 0 ==> non-speculative, never connected.
+ // 1 ==> non-speculative never used (but connected).
+ // 2 ==> non-speculative and used.
+ // 3 ==> omnibox_speculative never connected.
+ // 4 ==> omnibox_speculative never used (but connected).
+ // 5 ==> omnibox_speculative and used.
+ // 6 ==> subresource_speculative never connected.
+ // 7 ==> subresource_speculative never used (but connected).
+ // 8 ==> subresource_speculative and used.
+ int result;
+ if (was_used_to_convey_data_)
+ result = 2;
+ else if (was_ever_connected_)
+ result = 1;
+ else
+ result = 0; // Never used, and not really connected.
+
+ if (omnibox_speculation_)
+ result += 3;
+ else if (subresource_speculation_)
+ result += 6;
+ UMA_HISTOGRAM_ENUMERATION("Net.PreconnectUtilization2", result, 9);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/stream_socket.h b/chromium/net/socket/stream_socket.h
new file mode 100644
index 00000000000..38eec86dd92
--- /dev/null
+++ b/chromium/net/socket/stream_socket.h
@@ -0,0 +1,138 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_STREAM_SOCKET_H_
+#define NET_SOCKET_STREAM_SOCKET_H_
+
+#include "net/base/net_log.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket.h"
+
+namespace net {
+
+class AddressList;
+class IPEndPoint;
+class SSLInfo;
+
+class NET_EXPORT_PRIVATE StreamSocket : public Socket {
+ public:
+ virtual ~StreamSocket() {}
+
+ // Called to establish a connection. Returns OK if the connection could be
+ // established synchronously. Otherwise, ERR_IO_PENDING is returned and the
+ // given callback will run asynchronously when the connection is established
+ // or when an error occurs. The result is some other error code if the
+ // connection could not be established.
+ //
+ // The socket's Read and Write methods may not be called until Connect
+ // succeeds.
+ //
+ // It is valid to call Connect on an already connected socket, in which case
+ // OK is simply returned.
+ //
+ // Connect may also be called again after a call to the Disconnect method.
+ //
+ virtual int Connect(const CompletionCallback& callback) = 0;
+
+ // Called to disconnect a socket. Does nothing if the socket is already
+ // disconnected. After calling Disconnect it is possible to call Connect
+ // again to establish a new connection.
+ //
+ // If IO (Connect, Read, or Write) is pending when the socket is
+ // disconnected, the pending IO is cancelled, and the completion callback
+ // will not be called.
+ virtual void Disconnect() = 0;
+
+ // Called to test if the connection is still alive. Returns false if a
+ // connection wasn't established or the connection is dead.
+ virtual bool IsConnected() const = 0;
+
+ // Called to test if the connection is still alive and idle. Returns false
+ // if a connection wasn't established, the connection is dead, or some data
+ // have been received.
+ virtual bool IsConnectedAndIdle() const = 0;
+
+ // Copies the peer address to |address| and returns a network error code.
+ // ERR_SOCKET_NOT_CONNECTED will be returned if the socket is not connected.
+ virtual int GetPeerAddress(IPEndPoint* address) const = 0;
+
+ // Copies the local address to |address| and returns a network error code.
+ // ERR_SOCKET_NOT_CONNECTED will be returned if the socket is not bound.
+ virtual int GetLocalAddress(IPEndPoint* address) const = 0;
+
+ // Gets the NetLog for this socket.
+ virtual const BoundNetLog& NetLog() const = 0;
+
+ // Set the annotation to indicate this socket was created for speculative
+ // reasons. This call is generally forwarded to a basic TCPClientSocket*,
+ // where a UseHistory can be updated.
+ virtual void SetSubresourceSpeculation() = 0;
+ virtual void SetOmniboxSpeculation() = 0;
+
+ // Returns true if the underlying transport socket ever had any reads or
+ // writes. StreamSockets layered on top of transport sockets should forward
+ // this call to the transport socket.
+ virtual bool WasEverUsed() const = 0;
+
+ // Returns true if the underlying transport socket is using TCP FastOpen.
+ // TCP FastOpen is an experiment with sending data in the TCP SYN packet.
+ virtual bool UsingTCPFastOpen() const = 0;
+
+ // Returns true if NPN was negotiated during the connection of this socket.
+ virtual bool WasNpnNegotiated() const = 0;
+
+ // Returns the protocol negotiated via NPN for this socket, or
+ // kProtoUnknown will be returned if NPN is not applicable.
+ virtual NextProto GetNegotiatedProtocol() const = 0;
+
+ // Gets the SSL connection information of the socket. Returns false if
+ // SSL was not used by this socket.
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) = 0;
+
+ protected:
+ // The following class is only used to gather statistics about the history of
+ // a socket. It is only instantiated and used in basic sockets, such as
+ // TCPClientSocket* instances. Other classes that are derived from
+ // StreamSocket should forward any potential settings to their underlying
+ // transport sockets.
+ class UseHistory {
+ public:
+ UseHistory();
+ ~UseHistory();
+
+ // Resets the state of UseHistory and emits histograms for the
+ // current state.
+ void Reset();
+
+ void set_was_ever_connected();
+ void set_was_used_to_convey_data();
+
+ // The next two setters only have any impact if the socket has not yet been
+ // used to transmit data. If called later, we assume that the socket was
+ // reused from the pool, and was NOT constructed to service a speculative
+ // request.
+ void set_subresource_speculation();
+ void set_omnibox_speculation();
+
+ bool was_used_to_convey_data() const;
+
+ private:
+ // Summarize the statistics for this socket.
+ void EmitPreconnectionHistograms() const;
+ // Indicate if this was ever connected.
+ bool was_ever_connected_;
+ // Indicate if this socket was ever used to transmit or receive data.
+ bool was_used_to_convey_data_;
+
+ // Indicate if this socket was first created for speculative use, and
+ // identify the motivation.
+ bool omnibox_speculation_;
+ bool subresource_speculation_;
+ DISALLOW_COPY_AND_ASSIGN(UseHistory);
+ };
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_H_
diff --git a/chromium/net/socket/tcp_client_socket.cc b/chromium/net/socket/tcp_client_socket.cc
new file mode 100644
index 00000000000..dbd21056f39
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2013 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/socket/tcp_client_socket.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+
+namespace net {
+
+namespace {
+
+#if defined(OS_LINUX)
+
+// Checks to see if the system supports TCP FastOpen. Notably, it requires
+// kernel support. Additionally, this checks system configuration to ensure that
+// it's enabled.
+bool SystemSupportsTCPFastOpen() {
+ static const base::FilePath::CharType kTCPFastOpenProcFilePath[] =
+ "/proc/sys/net/ipv4/tcp_fastopen";
+ std::string system_enabled_tcp_fastopen;
+ if (!file_util::ReadFileToString(
+ base::FilePath(kTCPFastOpenProcFilePath),
+ &system_enabled_tcp_fastopen)) {
+ return false;
+ }
+
+ // As per http://lxr.linux.no/linux+v3.7.7/include/net/tcp.h#L225
+ // TFO_CLIENT_ENABLE is the LSB
+ if (system_enabled_tcp_fastopen.empty() ||
+ (system_enabled_tcp_fastopen[0] & 0x1) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+#else
+
+bool SystemSupportsTCPFastOpen() {
+ return false;
+}
+
+#endif
+
+}
+
+static bool g_tcp_fastopen_enabled = false;
+
+void SetTCPFastOpenEnabled(bool value) {
+ g_tcp_fastopen_enabled = value && SystemSupportsTCPFastOpen();
+}
+
+bool IsTCPFastOpenEnabled() {
+ return g_tcp_fastopen_enabled;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_client_socket.h b/chromium/net/socket/tcp_client_socket.h
new file mode 100644
index 00000000000..8a2c0cd73f0
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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_SOCKET_TCP_CLIENT_SOCKET_H_
+#define NET_SOCKET_TCP_CLIENT_SOCKET_H_
+
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+#if defined(OS_WIN)
+#include "net/socket/tcp_client_socket_win.h"
+#elif defined(OS_POSIX)
+#include "net/socket/tcp_client_socket_libevent.h"
+#endif
+
+namespace net {
+
+// A client socket that uses TCP as the transport layer.
+#if defined(OS_WIN)
+typedef TCPClientSocketWin TCPClientSocket;
+#elif defined(OS_POSIX)
+typedef TCPClientSocketLibevent TCPClientSocket;
+#endif
+
+// Enable/disable experimental TCP FastOpen option.
+// Not thread safe. Must be called during initialization/startup only.
+NET_EXPORT void SetTCPFastOpenEnabled(bool value);
+
+// Check if the TCP FastOpen option is enabled.
+bool IsTCPFastOpenEnabled();
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_CLIENT_SOCKET_H_
diff --git a/chromium/net/socket/tcp_client_socket_libevent.cc b/chromium/net/socket/tcp_client_socket_libevent.cc
new file mode 100644
index 00000000000..2f7e4b4b255
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket_libevent.cc
@@ -0,0 +1,830 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_client_socket.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#if defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
+
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/string_util.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/socket/socket_net_log_params.h"
+
+// If we don't have a definition for TCPI_OPT_SYN_DATA, create one.
+#ifndef TCPI_OPT_SYN_DATA
+#define TCPI_OPT_SYN_DATA 32
+#endif
+
+namespace net {
+
+namespace {
+
+const int kInvalidSocket = -1;
+const int kTCPKeepAliveSeconds = 45;
+
+// SetTCPNoDelay turns on/off buffering in the kernel. By default, TCP sockets
+// will wait up to 200ms for more data to complete a packet before transmitting.
+// After calling this function, the kernel will not wait. See TCP_NODELAY in
+// `man 7 tcp`.
+bool SetTCPNoDelay(int fd, bool no_delay) {
+ int on = no_delay ? 1 : 0;
+ int error = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on,
+ sizeof(on));
+ return error == 0;
+}
+
+// SetTCPKeepAlive sets SO_KEEPALIVE.
+bool SetTCPKeepAlive(int fd, bool enable, int delay) {
+ int on = enable ? 1 : 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))) {
+ PLOG(ERROR) << "Failed to set SO_KEEPALIVE on fd: " << fd;
+ return false;
+ }
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // Set seconds until first TCP keep alive.
+ if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &delay, sizeof(delay))) {
+ PLOG(ERROR) << "Failed to set TCP_KEEPIDLE on fd: " << fd;
+ return false;
+ }
+ // Set seconds between TCP keep alives.
+ if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &delay, sizeof(delay))) {
+ PLOG(ERROR) << "Failed to set TCP_KEEPINTVL on fd: " << fd;
+ return false;
+ }
+#endif
+ return true;
+}
+
+// Sets socket parameters. Returns the OS error code (or 0 on
+// success).
+int SetupSocket(int socket) {
+ if (SetNonBlocking(socket))
+ return errno;
+
+ // This mirrors the behaviour on Windows. See the comment in
+ // tcp_client_socket_win.cc after searching for "NODELAY".
+ SetTCPNoDelay(socket, true); // If SetTCPNoDelay fails, we don't care.
+ SetTCPKeepAlive(socket, true, kTCPKeepAliveSeconds);
+
+ return 0;
+}
+
+// Creates a new socket and sets default parameters for it. Returns
+// the OS error code (or 0 on success).
+int CreateSocket(int family, int* socket) {
+ *socket = ::socket(family, SOCK_STREAM, IPPROTO_TCP);
+ if (*socket == kInvalidSocket)
+ return errno;
+ int error = SetupSocket(*socket);
+ if (error) {
+ if (HANDLE_EINTR(close(*socket)) < 0)
+ PLOG(ERROR) << "close";
+ *socket = kInvalidSocket;
+ return error;
+ }
+ return 0;
+}
+
+int MapConnectError(int os_error) {
+ switch (os_error) {
+ case EACCES:
+ return ERR_NETWORK_ACCESS_DENIED;
+ case ETIMEDOUT:
+ return ERR_CONNECTION_TIMED_OUT;
+ default: {
+ int net_error = MapSystemError(os_error);
+ if (net_error == ERR_FAILED)
+ return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED.
+
+ // Give a more specific error when the user is offline.
+ if (net_error == ERR_ADDRESS_UNREACHABLE &&
+ NetworkChangeNotifier::IsOffline()) {
+ return ERR_INTERNET_DISCONNECTED;
+ }
+ return net_error;
+ }
+ }
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+TCPClientSocketLibevent::TCPClientSocketLibevent(
+ const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(kInvalidSocket),
+ bound_socket_(kInvalidSocket),
+ addresses_(addresses),
+ current_address_index_(-1),
+ read_watcher_(this),
+ write_watcher_(this),
+ next_connect_state_(CONNECT_STATE_NONE),
+ connect_os_error_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)),
+ previously_disconnected_(false),
+ use_tcp_fastopen_(IsTCPFastOpenEnabled()),
+ tcp_fastopen_connected_(false),
+ fast_open_status_(FAST_OPEN_STATUS_UNKNOWN) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+}
+
+TCPClientSocketLibevent::~TCPClientSocketLibevent() {
+ Disconnect();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+ if (tcp_fastopen_connected_) {
+ UMA_HISTOGRAM_ENUMERATION("Net.TcpFastOpenSocketConnection",
+ fast_open_status_, FAST_OPEN_MAX_VALUE);
+ }
+}
+
+int TCPClientSocketLibevent::AdoptSocket(int socket) {
+ DCHECK_EQ(socket_, kInvalidSocket);
+
+ int error = SetupSocket(socket);
+ if (error)
+ return MapSystemError(error);
+
+ socket_ = socket;
+
+ // This is to make GetPeerAddress() work. It's up to the caller ensure
+ // that |address_| contains a reasonable address for this
+ // socket. (i.e. at least match IPv4 vs IPv6!).
+ current_address_index_ = 0;
+ use_history_.set_was_ever_connected();
+
+ return OK;
+}
+
+int TCPClientSocketLibevent::Bind(const IPEndPoint& address) {
+ if (current_address_index_ >= 0 || bind_address_.get()) {
+ // Cannot bind the socket if we are already bound connected or
+ // connecting.
+ return ERR_UNEXPECTED;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+
+ // Create |bound_socket_| and try to bind it to |address|.
+ int error = CreateSocket(address.GetSockAddrFamily(), &bound_socket_);
+ if (error)
+ return MapSystemError(error);
+
+ if (HANDLE_EINTR(bind(bound_socket_, storage.addr, storage.addr_len))) {
+ error = errno;
+ if (HANDLE_EINTR(close(bound_socket_)) < 0)
+ PLOG(ERROR) << "close";
+ bound_socket_ = kInvalidSocket;
+ return MapSystemError(error);
+ }
+
+ bind_address_.reset(new IPEndPoint(address));
+
+ return 0;
+}
+
+int TCPClientSocketLibevent::Connect(const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+
+ // If already connected, then just return OK.
+ if (socket_ != kInvalidSocket)
+ return OK;
+
+ base::StatsCounter connects("tcp.connect");
+ connects.Increment();
+
+ DCHECK(!waiting_connect());
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT,
+ addresses_.CreateNetLogCallback());
+
+ // We will try to connect to each address in addresses_. Start with the
+ // first one in the list.
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_address_index_ = 0;
+
+ int rv = DoConnectLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ // Synchronous operation not supported.
+ DCHECK(!callback.is_null());
+ write_callback_ = callback;
+ } else {
+ LogConnectCompletion(rv);
+ }
+
+ return rv;
+}
+
+int TCPClientSocketLibevent::DoConnectLoop(int result) {
+ DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);
+
+ int rv = result;
+ do {
+ ConnectState state = next_connect_state_;
+ next_connect_state_ = CONNECT_STATE_NONE;
+ switch (state) {
+ case CONNECT_STATE_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoConnect();
+ break;
+ case CONNECT_STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ default:
+ LOG(DFATAL) << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE);
+
+ return rv;
+}
+
+int TCPClientSocketLibevent::DoConnect() {
+ DCHECK_GE(current_address_index_, 0);
+ DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size()));
+ DCHECK_EQ(0, connect_os_error_);
+
+ const IPEndPoint& endpoint = addresses_[current_address_index_];
+
+ if (previously_disconnected_) {
+ use_history_.Reset();
+ previously_disconnected_ = false;
+ }
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ CreateNetLogIPEndPointCallback(&endpoint));
+
+ next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
+
+ if (bound_socket_ != kInvalidSocket) {
+ DCHECK(bind_address_.get());
+ socket_ = bound_socket_;
+ bound_socket_ = kInvalidSocket;
+ } else {
+ // Create a non-blocking socket.
+ connect_os_error_ = CreateSocket(endpoint.GetSockAddrFamily(), &socket_);
+ if (connect_os_error_)
+ return MapSystemError(connect_os_error_);
+
+ if (bind_address_.get()) {
+ SockaddrStorage storage;
+ if (!bind_address_->ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+ if (HANDLE_EINTR(bind(socket_, storage.addr, storage.addr_len)))
+ return MapSystemError(errno);
+ }
+ }
+
+ // Connect the socket.
+ if (!use_tcp_fastopen_) {
+ SockaddrStorage storage;
+ if (!endpoint.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+
+ if (!HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len))) {
+ // Connected without waiting!
+ return OK;
+ }
+ } else {
+ // With TCP FastOpen, we pretend that the socket is connected.
+ DCHECK(!tcp_fastopen_connected_);
+ return OK;
+ }
+
+ // Check if the connect() failed synchronously.
+ connect_os_error_ = errno;
+ if (connect_os_error_ != EINPROGRESS)
+ return MapConnectError(connect_os_error_);
+
+ // Otherwise the connect() is going to complete asynchronously, so watch
+ // for its completion.
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_WRITE,
+ &write_socket_watcher_, &write_watcher_)) {
+ connect_os_error_ = errno;
+ DVLOG(1) << "WatchFileDescriptor failed: " << connect_os_error_;
+ return MapSystemError(connect_os_error_);
+ }
+
+ return ERR_IO_PENDING;
+}
+
+int TCPClientSocketLibevent::DoConnectComplete(int result) {
+ // Log the end of this attempt (and any OS error it threw).
+ int os_error = connect_os_error_;
+ connect_os_error_ = 0;
+ if (result != OK) {
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ NetLog::IntegerCallback("os_error", os_error));
+ } else {
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT);
+ }
+
+ if (result == OK) {
+ write_socket_watcher_.StopWatchingFileDescriptor();
+ use_history_.set_was_ever_connected();
+ return OK; // Done!
+ }
+
+ // Close whatever partially connected socket we currently have.
+ DoDisconnect();
+
+ // Try to fall back to the next address in the list.
+ if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) {
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ ++current_address_index_;
+ return OK;
+ }
+
+ // Otherwise there is nothing to fall back to, so give up.
+ return result;
+}
+
+void TCPClientSocketLibevent::Disconnect() {
+ DCHECK(CalledOnValidThread());
+
+ DoDisconnect();
+ current_address_index_ = -1;
+ bind_address_.reset();
+}
+
+void TCPClientSocketLibevent::DoDisconnect() {
+ if (socket_ == kInvalidSocket)
+ return;
+
+ bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ ok = write_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ if (HANDLE_EINTR(close(socket_)) < 0)
+ PLOG(ERROR) << "close";
+ socket_ = kInvalidSocket;
+ previously_disconnected_ = true;
+}
+
+bool TCPClientSocketLibevent::IsConnected() const {
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == kInvalidSocket || waiting_connect())
+ return false;
+
+ if (use_tcp_fastopen_ && !tcp_fastopen_connected_) {
+ // With TCP FastOpen, we pretend that the socket is connected.
+ // This allows GetPeerAddress() to return current_ai_ as the peer
+ // address. Since we don't fail over to the next address if
+ // sendto() fails, current_ai_ is the only possible peer address.
+ CHECK_LT(current_address_index_, static_cast<int>(addresses_.size()));
+ return true;
+ }
+
+ // Check if connection is alive.
+ char c;
+ int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
+ if (rv == 0)
+ return false;
+ if (rv == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+bool TCPClientSocketLibevent::IsConnectedAndIdle() const {
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == kInvalidSocket || waiting_connect())
+ return false;
+
+ // TODO(wtc): should we also handle the TCP FastOpen case here,
+ // as we do in IsConnected()?
+
+ // Check if connection is alive and we haven't received any data
+ // unexpectedly.
+ char c;
+ int rv = HANDLE_EINTR(recv(socket_, &c, 1, MSG_PEEK));
+ if (rv >= 0)
+ return false;
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+int TCPClientSocketLibevent::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_);
+ DCHECK(!waiting_connect());
+ DCHECK(read_callback_.is_null());
+ // Synchronous operation not supported
+ DCHECK(!callback.is_null());
+ DCHECK_GT(buf_len, 0);
+
+ int nread = HANDLE_EINTR(read(socket_, buf->data(), buf_len));
+ if (nread >= 0) {
+ base::StatsCounter read_bytes("tcp.read_bytes");
+ read_bytes.Add(nread);
+ if (nread > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, nread,
+ buf->data());
+ RecordFastOpenStatus();
+ return nread;
+ }
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ int net_error = MapSystemError(errno);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR,
+ CreateNetLogSocketErrorCallback(net_error, errno));
+ return net_error;
+ }
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_READ,
+ &read_socket_watcher_, &read_watcher_)) {
+ DVLOG(1) << "WatchFileDescriptor failed on read, errno " << errno;
+ return MapSystemError(errno);
+ }
+
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int TCPClientSocketLibevent::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_);
+ DCHECK(!waiting_connect());
+ DCHECK(write_callback_.is_null());
+ // Synchronous operation not supported
+ DCHECK(!callback.is_null());
+ DCHECK_GT(buf_len, 0);
+
+ int nwrite = InternalWrite(buf, buf_len);
+ if (nwrite >= 0) {
+ base::StatsCounter write_bytes("tcp.write_bytes");
+ write_bytes.Add(nwrite);
+ if (nwrite > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, nwrite,
+ buf->data());
+ return nwrite;
+ }
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ int net_error = MapSystemError(errno);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR,
+ CreateNetLogSocketErrorCallback(net_error, errno));
+ return net_error;
+ }
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_WRITE,
+ &write_socket_watcher_, &write_watcher_)) {
+ DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno;
+ return MapSystemError(errno);
+ }
+
+ write_buf_ = buf;
+ write_buf_len_ = buf_len;
+ write_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int TCPClientSocketLibevent::InternalWrite(IOBuffer* buf, int buf_len) {
+ int nwrite;
+ if (use_tcp_fastopen_ && !tcp_fastopen_connected_) {
+ SockaddrStorage storage;
+ if (!addresses_[current_address_index_].ToSockAddr(storage.addr,
+ &storage.addr_len)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int flags = 0x20000000; // Magic flag to enable TCP_FASTOPEN.
+#if defined(OS_LINUX)
+ // sendto() will fail with EPIPE when the system doesn't support TCP Fast
+ // Open. Theoretically that shouldn't happen since the caller should check
+ // for system support on startup, but users may dynamically disable TCP Fast
+ // Open via sysctl.
+ flags |= MSG_NOSIGNAL;
+#endif // defined(OS_LINUX)
+ nwrite = HANDLE_EINTR(sendto(socket_,
+ buf->data(),
+ buf_len,
+ flags,
+ storage.addr,
+ storage.addr_len));
+ tcp_fastopen_connected_ = true;
+
+ if (nwrite < 0) {
+ DCHECK_NE(EPIPE, errno);
+
+ // If errno == EINPROGRESS, that means the kernel didn't have a cookie
+ // and would block. The kernel is internally doing a connect() though.
+ // Remap EINPROGRESS to EAGAIN so we treat this the same as our other
+ // asynchronous cases. Note that the user buffer has not been copied to
+ // kernel space.
+ if (errno == EINPROGRESS) {
+ errno = EAGAIN;
+ fast_open_status_ = FAST_OPEN_SLOW_CONNECT_RETURN;
+ } else {
+ fast_open_status_ = FAST_OPEN_ERROR;
+ }
+ } else {
+ fast_open_status_ = FAST_OPEN_FAST_CONNECT_RETURN;
+ }
+ } else {
+ nwrite = HANDLE_EINTR(write(socket_, buf->data(), buf_len));
+ }
+ return nwrite;
+}
+
+bool TCPClientSocketLibevent::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
+ reinterpret_cast<const char*>(&size),
+ sizeof(size));
+ DCHECK(!rv) << "Could not set socket receive buffer size: " << errno;
+ return rv == 0;
+}
+
+bool TCPClientSocketLibevent::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
+ reinterpret_cast<const char*>(&size),
+ sizeof(size));
+ DCHECK(!rv) << "Could not set socket send buffer size: " << errno;
+ return rv == 0;
+}
+
+bool TCPClientSocketLibevent::SetKeepAlive(bool enable, int delay) {
+ int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_;
+ return SetTCPKeepAlive(socket, enable, delay);
+}
+
+bool TCPClientSocketLibevent::SetNoDelay(bool no_delay) {
+ int socket = socket_ != kInvalidSocket ? socket_ : bound_socket_;
+ return SetTCPNoDelay(socket, no_delay);
+}
+
+void TCPClientSocketLibevent::ReadWatcher::OnFileCanReadWithoutBlocking(int) {
+ socket_->RecordFastOpenStatus();
+ if (!socket_->read_callback_.is_null())
+ socket_->DidCompleteRead();
+}
+
+void TCPClientSocketLibevent::WriteWatcher::OnFileCanWriteWithoutBlocking(int) {
+ if (socket_->waiting_connect()) {
+ socket_->DidCompleteConnect();
+ } else if (!socket_->write_callback_.is_null()) {
+ socket_->DidCompleteWrite();
+ }
+}
+
+void TCPClientSocketLibevent::LogConnectCompletion(int net_error) {
+ if (net_error == OK)
+ UpdateConnectionTypeHistograms(CONNECTION_ANY);
+
+ if (net_error != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error);
+ return;
+ }
+
+ SockaddrStorage storage;
+ int rv = getsockname(socket_, storage.addr, &storage.addr_len);
+ if (rv != 0) {
+ PLOG(ERROR) << "getsockname() [rv: " << rv << "] error: ";
+ NOTREACHED();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv);
+ return;
+ }
+
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT,
+ CreateNetLogSourceAddressCallback(storage.addr,
+ storage.addr_len));
+}
+
+void TCPClientSocketLibevent::DoReadCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!read_callback_.is_null());
+
+ // since Run may result in Read being called, clear read_callback_ up front.
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ c.Run(rv);
+}
+
+void TCPClientSocketLibevent::DoWriteCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!write_callback_.is_null());
+
+ // since Run may result in Write being called, clear write_callback_ up front.
+ CompletionCallback c = write_callback_;
+ write_callback_.Reset();
+ c.Run(rv);
+}
+
+void TCPClientSocketLibevent::DidCompleteConnect() {
+ DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
+
+ // Get the error that connect() completed with.
+ int os_error = 0;
+ socklen_t len = sizeof(os_error);
+ if (getsockopt(socket_, SOL_SOCKET, SO_ERROR, &os_error, &len) < 0)
+ os_error = errno;
+
+ // TODO(eroman): Is this check really necessary?
+ if (os_error == EINPROGRESS || os_error == EALREADY) {
+ NOTREACHED(); // This indicates a bug in libevent or our code.
+ return;
+ }
+
+ connect_os_error_ = os_error;
+ int rv = DoConnectLoop(MapConnectError(os_error));
+ if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
+ DoWriteCallback(rv);
+ }
+}
+
+void TCPClientSocketLibevent::DidCompleteRead() {
+ int bytes_transferred;
+ bytes_transferred = HANDLE_EINTR(read(socket_, read_buf_->data(),
+ read_buf_len_));
+
+ int result;
+ if (bytes_transferred >= 0) {
+ result = bytes_transferred;
+ base::StatsCounter read_bytes("tcp.read_bytes");
+ read_bytes.Add(bytes_transferred);
+ if (bytes_transferred > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, result,
+ read_buf_->data());
+ } else {
+ result = MapSystemError(errno);
+ if (result != ERR_IO_PENDING) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR,
+ CreateNetLogSocketErrorCallback(result, errno));
+ }
+ }
+
+ if (result != ERR_IO_PENDING) {
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ DoReadCallback(result);
+ }
+}
+
+void TCPClientSocketLibevent::DidCompleteWrite() {
+ int bytes_transferred;
+ bytes_transferred = HANDLE_EINTR(write(socket_, write_buf_->data(),
+ write_buf_len_));
+
+ int result;
+ if (bytes_transferred >= 0) {
+ result = bytes_transferred;
+ base::StatsCounter write_bytes("tcp.write_bytes");
+ write_bytes.Add(bytes_transferred);
+ if (bytes_transferred > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, result,
+ write_buf_->data());
+ } else {
+ result = MapSystemError(errno);
+ if (result != ERR_IO_PENDING) {
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR,
+ CreateNetLogSocketErrorCallback(result, errno));
+ }
+ }
+
+ if (result != ERR_IO_PENDING) {
+ write_buf_ = NULL;
+ write_buf_len_ = 0;
+ write_socket_watcher_.StopWatchingFileDescriptor();
+ DoWriteCallback(result);
+ }
+}
+
+int TCPClientSocketLibevent::GetPeerAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ *address = addresses_[current_address_index_];
+ return OK;
+}
+
+int TCPClientSocketLibevent::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (socket_ == kInvalidSocket) {
+ if (bind_address_.get()) {
+ *address = *bind_address_;
+ return OK;
+ }
+ return ERR_SOCKET_NOT_CONNECTED;
+ }
+
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(errno);
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+
+ return OK;
+}
+
+void TCPClientSocketLibevent::RecordFastOpenStatus() {
+ if (use_tcp_fastopen_ &&
+ (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ||
+ fast_open_status_ == FAST_OPEN_SLOW_CONNECT_RETURN)) {
+ DCHECK_NE(FAST_OPEN_STATUS_UNKNOWN, fast_open_status_);
+ bool getsockopt_success(false);
+ bool server_acked_data(false);
+#if defined(TCP_INFO)
+ // Probe to see the if the socket used TCP Fast Open.
+ tcp_info info;
+ socklen_t info_len = sizeof(tcp_info);
+ getsockopt_success =
+ getsockopt(socket_, IPPROTO_TCP, TCP_INFO, &info, &info_len) == 0 &&
+ info_len == sizeof(tcp_info);
+ server_acked_data = getsockopt_success &&
+ (info.tcpi_options & TCPI_OPT_SYN_DATA);
+#endif
+ if (getsockopt_success) {
+ if (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN) {
+ fast_open_status_ = (server_acked_data ? FAST_OPEN_SYN_DATA_ACK :
+ FAST_OPEN_SYN_DATA_NACK);
+ } else {
+ fast_open_status_ = (server_acked_data ? FAST_OPEN_NO_SYN_DATA_ACK :
+ FAST_OPEN_NO_SYN_DATA_NACK);
+ }
+ } else {
+ fast_open_status_ = (fast_open_status_ == FAST_OPEN_FAST_CONNECT_RETURN ?
+ FAST_OPEN_SYN_DATA_FAILED :
+ FAST_OPEN_NO_SYN_DATA_FAILED);
+ }
+ }
+}
+
+const BoundNetLog& TCPClientSocketLibevent::NetLog() const {
+ return net_log_;
+}
+
+void TCPClientSocketLibevent::SetSubresourceSpeculation() {
+ use_history_.set_subresource_speculation();
+}
+
+void TCPClientSocketLibevent::SetOmniboxSpeculation() {
+ use_history_.set_omnibox_speculation();
+}
+
+bool TCPClientSocketLibevent::WasEverUsed() const {
+ return use_history_.was_used_to_convey_data();
+}
+
+bool TCPClientSocketLibevent::UsingTCPFastOpen() const {
+ return use_tcp_fastopen_;
+}
+
+bool TCPClientSocketLibevent::WasNpnNegotiated() const {
+ return false;
+}
+
+NextProto TCPClientSocketLibevent::GetNegotiatedProtocol() const {
+ return kProtoUnknown;
+}
+
+bool TCPClientSocketLibevent::GetSSLInfo(SSLInfo* ssl_info) {
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_client_socket_libevent.h b/chromium/net/socket/tcp_client_socket_libevent.h
new file mode 100644
index 00000000000..e5a0d8deab4
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket_libevent.h
@@ -0,0 +1,256 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_
+#define NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+class BoundNetLog;
+
+// A client socket that uses TCP as the transport layer.
+class NET_EXPORT_PRIVATE TCPClientSocketLibevent : public StreamSocket,
+ public base::NonThreadSafe {
+ public:
+ // The IP address(es) and port number to connect to. The TCP socket will try
+ // each IP address in the list until it succeeds in establishing a
+ // connection.
+ TCPClientSocketLibevent(const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source);
+
+ virtual ~TCPClientSocketLibevent();
+
+ // AdoptSocket causes the given, connected socket to be adopted as a TCP
+ // socket. This object must not be connected. This object takes ownership of
+ // the given socket and then acts as if Connect() had been called. This
+ // function is used by TCPServerSocket() to adopt accepted connections
+ // and for testing.
+ int AdoptSocket(int socket);
+
+ // Binds the socket to a local IP address and port.
+ int Bind(const IPEndPoint& address);
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ // Multiple outstanding requests are not supported.
+ // Full duplex mode (reading and writing at the same time) is supported
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+
+ virtual bool SetKeepAlive(bool enable, int delay);
+ virtual bool SetNoDelay(bool no_delay);
+
+ private:
+ // State machine for connecting the socket.
+ enum ConnectState {
+ CONNECT_STATE_CONNECT,
+ CONNECT_STATE_CONNECT_COMPLETE,
+ CONNECT_STATE_NONE,
+ };
+
+ // States that a fast open socket attempt can result in.
+ enum FastOpenStatus {
+ FAST_OPEN_STATUS_UNKNOWN,
+
+ // The initial fast open connect attempted returned synchronously,
+ // indicating that we had and sent a cookie along with the initial data.
+ FAST_OPEN_FAST_CONNECT_RETURN,
+
+ // The initial fast open connect attempted returned asynchronously,
+ // indicating that we did not have a cookie for the server.
+ FAST_OPEN_SLOW_CONNECT_RETURN,
+
+ // Some other error occurred on connection, so we couldn't tell if
+ // fast open would have worked.
+ FAST_OPEN_ERROR,
+
+ // An attempt to do a fast open succeeded immediately
+ // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server
+ // had acked the data we sent.
+ FAST_OPEN_SYN_DATA_ACK,
+
+ // An attempt to do a fast open succeeded immediately
+ // (FAST_OPEN_FAST_CONNECT_RETURN) and we later confirmed that the server
+ // had nacked the data we sent.
+ FAST_OPEN_SYN_DATA_NACK,
+
+ // An attempt to do a fast open succeeded immediately
+ // (FAST_OPEN_FAST_CONNECT_RETURN) and our probe to determine if the
+ // socket was using fast open failed.
+ FAST_OPEN_SYN_DATA_FAILED,
+
+ // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN)
+ // and we later confirmed that the server had acked initial data. This
+ // should never happen (we didn't send data, so it shouldn't have
+ // been acked).
+ FAST_OPEN_NO_SYN_DATA_ACK,
+
+ // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN)
+ // and we later discovered that the server had nacked initial data. This
+ // is the expected case results for FAST_OPEN_SLOW_CONNECT_RETURN.
+ FAST_OPEN_NO_SYN_DATA_NACK,
+
+ // An attempt to do a fast open failed (FAST_OPEN_SLOW_CONNECT_RETURN)
+ // and our later probe for ack/nack state failed.
+ FAST_OPEN_NO_SYN_DATA_FAILED,
+
+ FAST_OPEN_MAX_VALUE
+ };
+
+ class ReadWatcher : public base::MessageLoopForIO::Watcher {
+ public:
+ explicit ReadWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {}
+
+ // MessageLoopForIO::Watcher methods
+
+ virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE;
+
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE {}
+
+ private:
+ TCPClientSocketLibevent* const socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadWatcher);
+ };
+
+ class WriteWatcher : public base::MessageLoopForIO::Watcher {
+ public:
+ explicit WriteWatcher(TCPClientSocketLibevent* socket) : socket_(socket) {}
+
+ // MessageLoopForIO::Watcher implementation.
+ virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE {}
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE;
+
+ private:
+ TCPClientSocketLibevent* const socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteWatcher);
+ };
+
+ // State machine used by Connect().
+ int DoConnectLoop(int result);
+ int DoConnect();
+ int DoConnectComplete(int result);
+
+ // Helper used by Disconnect(), which disconnects minus the logging and
+ // resetting of current_address_index_.
+ void DoDisconnect();
+
+ void DoReadCallback(int rv);
+ void DoWriteCallback(int rv);
+ void DidCompleteRead();
+ void DidCompleteWrite();
+ void DidCompleteConnect();
+
+ // Returns true if a Connect() is in progress.
+ bool waiting_connect() const {
+ return next_connect_state_ != CONNECT_STATE_NONE;
+ }
+
+ // Helper to add a TCP_CONNECT (end) event to the NetLog.
+ void LogConnectCompletion(int net_error);
+
+ // Internal function to write to a socket.
+ int InternalWrite(IOBuffer* buf, int buf_len);
+
+ // Called when the socket is known to be in a connected state.
+ void RecordFastOpenStatus();
+
+ int socket_;
+
+ // Local IP address and port we are bound to. Set to NULL if Bind()
+ // was't called (in that cases OS chooses address/port).
+ scoped_ptr<IPEndPoint> bind_address_;
+
+ // Stores bound socket between Bind() and Connect() calls.
+ int bound_socket_;
+
+ // The list of addresses we should try in order to establish a connection.
+ AddressList addresses_;
+
+ // Where we are in above list. Set to -1 if uninitialized.
+ int current_address_index_;
+
+ // The socket's libevent wrappers
+ base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_;
+ base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_;
+
+ // The corresponding watchers for reads and writes.
+ ReadWatcher read_watcher_;
+ WriteWatcher write_watcher_;
+
+ // The buffer used by OnSocketReady to retry Read requests
+ scoped_refptr<IOBuffer> read_buf_;
+ int read_buf_len_;
+
+ // The buffer used by OnSocketReady to retry Write requests
+ scoped_refptr<IOBuffer> write_buf_;
+ int write_buf_len_;
+
+ // External callback; called when read is complete.
+ CompletionCallback read_callback_;
+
+ // External callback; called when write is complete.
+ CompletionCallback write_callback_;
+
+ // The next state for the Connect() state machine.
+ ConnectState next_connect_state_;
+
+ // The OS error that CONNECT_STATE_CONNECT last completed with.
+ int connect_os_error_;
+
+ BoundNetLog net_log_;
+
+ // This socket was previously disconnected and has not been re-connected.
+ bool previously_disconnected_;
+
+ // Record of connectivity and transmissions, for use in speculative connection
+ // histograms.
+ UseHistory use_history_;
+
+ // Enables experimental TCP FastOpen option.
+ const bool use_tcp_fastopen_;
+
+ // True when TCP FastOpen is in use and we have done the connect.
+ bool tcp_fastopen_connected_;
+
+ enum FastOpenStatus fast_open_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(TCPClientSocketLibevent);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_CLIENT_SOCKET_LIBEVENT_H_
diff --git a/chromium/net/socket/tcp_client_socket_unittest.cc b/chromium/net/socket/tcp_client_socket_unittest.cc
new file mode 100644
index 00000000000..ce0c53559f8
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket_unittest.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 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.
+
+// This file contains some tests for TCPClientSocket.
+// transport_client_socket_unittest.cc contans some other tests that
+// are common for TCP and other types of sockets.
+
+#include "net/socket/tcp_client_socket.h"
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/tcp_server_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Try binding a socket to loopback interface and verify that we can
+// still connect to a server on the same interface.
+TEST(TCPClientSocketTest, BindLoopbackToLoopback) {
+ IPAddressNumber lo_address;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &lo_address));
+
+ TCPServerSocket server(NULL, NetLog::Source());
+ ASSERT_EQ(OK, server.Listen(IPEndPoint(lo_address, 0), 1));
+ IPEndPoint server_address;
+ ASSERT_EQ(OK, server.GetLocalAddress(&server_address));
+
+ TCPClientSocket socket(AddressList(server_address), NULL, NetLog::Source());
+
+ EXPECT_EQ(OK, socket.Bind(IPEndPoint(lo_address, 0)));
+
+ IPEndPoint local_address_result;
+ EXPECT_EQ(OK, socket.GetLocalAddress(&local_address_result));
+ EXPECT_EQ(lo_address, local_address_result.address());
+
+ TestCompletionCallback connect_callback;
+ EXPECT_EQ(ERR_IO_PENDING, socket.Connect(connect_callback.callback()));
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+ int result = server.Accept(&accepted_socket, accept_callback.callback());
+ if (result == ERR_IO_PENDING)
+ result = accept_callback.WaitForResult();
+ ASSERT_EQ(OK, result);
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+
+ EXPECT_TRUE(socket.IsConnected());
+ socket.Disconnect();
+ EXPECT_FALSE(socket.IsConnected());
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+ socket.GetLocalAddress(&local_address_result));
+}
+
+// Try to bind socket to the loopback interface and connect to an
+// external address, verify that connection fails.
+TEST(TCPClientSocketTest, BindLoopbackToExternal) {
+ IPAddressNumber external_ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("72.14.213.105", &external_ip));
+ TCPClientSocket socket(AddressList::CreateFromIPAddress(external_ip, 80),
+ NULL, NetLog::Source());
+
+ IPAddressNumber lo_address;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &lo_address));
+ EXPECT_EQ(OK, socket.Bind(IPEndPoint(lo_address, 0)));
+
+ TestCompletionCallback connect_callback;
+ int result = socket.Connect(connect_callback.callback());
+ if (result == ERR_IO_PENDING)
+ result = connect_callback.WaitForResult();
+
+ // We may get different errors here on different system, but
+ // connect() is not expected to succeed.
+ EXPECT_NE(OK, result);
+}
+
+// Bind a socket to the IPv4 loopback interface and try to connect to
+// the IPv6 loopback interface, verify that connection fails.
+TEST(TCPClientSocketTest, BindLoopbackToIPv6) {
+ IPAddressNumber ipv6_lo_ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("::1", &ipv6_lo_ip));
+ TCPServerSocket server(NULL, NetLog::Source());
+ int listen_result = server.Listen(IPEndPoint(ipv6_lo_ip, 0), 1);
+ if (listen_result != OK) {
+ LOG(ERROR) << "Failed to listen on ::1 - probably because IPv6 is disabled."
+ " Skipping the test";
+ return;
+ }
+
+ IPEndPoint server_address;
+ ASSERT_EQ(OK, server.GetLocalAddress(&server_address));
+ TCPClientSocket socket(AddressList(server_address), NULL, NetLog::Source());
+
+ IPAddressNumber ipv4_lo_ip;
+ ASSERT_TRUE(ParseIPLiteralToNumber("127.0.0.1", &ipv4_lo_ip));
+ EXPECT_EQ(OK, socket.Bind(IPEndPoint(ipv4_lo_ip, 0)));
+
+ TestCompletionCallback connect_callback;
+ int result = socket.Connect(connect_callback.callback());
+ if (result == ERR_IO_PENDING)
+ result = connect_callback.WaitForResult();
+
+ EXPECT_NE(OK, result);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_client_socket_win.cc b/chromium/net/socket/tcp_client_socket_win.cc
new file mode 100644
index 00000000000..9b0a5b50bf1
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket_win.cc
@@ -0,0 +1,1045 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_client_socket_win.h"
+
+#include <mstcpip.h>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/stats_counters.h"
+#include "base/strings/string_util.h"
+#include "base/win/object_watcher.h"
+#include "base/win/windows_version.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/winsock_init.h"
+#include "net/base/winsock_util.h"
+#include "net/socket/socket_net_log_params.h"
+
+namespace net {
+
+namespace {
+
+const int kTCPKeepAliveSeconds = 45;
+bool g_disable_overlapped_reads = false;
+
+bool SetSocketReceiveBufferSize(SOCKET socket, int32 size) {
+ int rv = setsockopt(socket, SOL_SOCKET, SO_RCVBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket receive buffer size: " << GetLastError();
+ return rv == 0;
+}
+
+bool SetSocketSendBufferSize(SOCKET socket, int32 size) {
+ int rv = setsockopt(socket, SOL_SOCKET, SO_SNDBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket send buffer size: " << GetLastError();
+ return rv == 0;
+}
+
+// Disable Nagle.
+// The Nagle implementation on windows is governed by RFC 896. The idea
+// behind Nagle is to reduce small packets on the network. When Nagle is
+// enabled, if a partial packet has been sent, the TCP stack will disallow
+// further *partial* packets until an ACK has been received from the other
+// side. Good applications should always strive to send as much data as
+// possible and avoid partial-packet sends. However, in most real world
+// applications, there are edge cases where this does not happen, and two
+// partial packets may be sent back to back. For a browser, it is NEVER
+// a benefit to delay for an RTT before the second packet is sent.
+//
+// As a practical example in Chromium today, consider the case of a small
+// POST. I have verified this:
+// Client writes 649 bytes of header (partial packet #1)
+// Client writes 50 bytes of POST data (partial packet #2)
+// In the above example, with Nagle, a RTT delay is inserted between these
+// two sends due to nagle. RTTs can easily be 100ms or more. The best
+// fix is to make sure that for POSTing data, we write as much data as
+// possible and minimize partial packets. We will fix that. But disabling
+// Nagle also ensure we don't run into this delay in other edge cases.
+// See also:
+// http://technet.microsoft.com/en-us/library/bb726981.aspx
+bool DisableNagle(SOCKET socket, bool disable) {
+ BOOL val = disable ? TRUE : FALSE;
+ int rv = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<const char*>(&val),
+ sizeof(val));
+ DCHECK(!rv) << "Could not disable nagle";
+ return rv == 0;
+}
+
+// Enable TCP Keep-Alive to prevent NAT routers from timing out TCP
+// connections. See http://crbug.com/27400 for details.
+bool SetTCPKeepAlive(SOCKET socket, BOOL enable, int delay_secs) {
+ int delay = delay_secs * 1000;
+ struct tcp_keepalive keepalive_vals = {
+ enable ? 1 : 0, // TCP keep-alive on.
+ delay, // Delay seconds before sending first TCP keep-alive packet.
+ delay, // Delay seconds between sending TCP keep-alive packets.
+ };
+ DWORD bytes_returned = 0xABAB;
+ int rv = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &keepalive_vals,
+ sizeof(keepalive_vals), NULL, 0,
+ &bytes_returned, NULL, NULL);
+ DCHECK(!rv) << "Could not enable TCP Keep-Alive for socket: " << socket
+ << " [error: " << WSAGetLastError() << "].";
+
+ // Disregard any failure in disabling nagle or enabling TCP Keep-Alive.
+ return rv == 0;
+}
+
+// Sets socket parameters. Returns the OS error code (or 0 on
+// success).
+int SetupSocket(SOCKET socket) {
+ // Increase the socket buffer sizes from the default sizes for WinXP. In
+ // performance testing, there is substantial benefit by increasing from 8KB
+ // to 64KB.
+ // See also:
+ // http://support.microsoft.com/kb/823764/EN-US
+ // On Vista, if we manually set these sizes, Vista turns off its receive
+ // window auto-tuning feature.
+ // http://blogs.msdn.com/wndp/archive/2006/05/05/Winhec-blog-tcpip-2.aspx
+ // Since Vista's auto-tune is better than any static value we can could set,
+ // only change these on pre-vista machines.
+ if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ const int32 kSocketBufferSize = 64 * 1024;
+ SetSocketReceiveBufferSize(socket, kSocketBufferSize);
+ SetSocketSendBufferSize(socket, kSocketBufferSize);
+ }
+
+ DisableNagle(socket, true);
+ SetTCPKeepAlive(socket, true, kTCPKeepAliveSeconds);
+ return 0;
+}
+
+// Creates a new socket and sets default parameters for it. Returns
+// the OS error code (or 0 on success).
+int CreateSocket(int family, SOCKET* socket) {
+ *socket = CreatePlatformSocket(family, SOCK_STREAM, IPPROTO_TCP);
+ if (*socket == INVALID_SOCKET) {
+ int os_error = WSAGetLastError();
+ LOG(ERROR) << "CreatePlatformSocket failed: " << os_error;
+ return os_error;
+ }
+ int error = SetupSocket(*socket);
+ if (error) {
+ if (closesocket(*socket) < 0)
+ PLOG(ERROR) << "closesocket";
+ *socket = INVALID_SOCKET;
+ return error;
+ }
+ return 0;
+}
+
+int MapConnectError(int os_error) {
+ switch (os_error) {
+ // connect fails with WSAEACCES when Windows Firewall blocks the
+ // connection.
+ case WSAEACCES:
+ return ERR_NETWORK_ACCESS_DENIED;
+ case WSAETIMEDOUT:
+ return ERR_CONNECTION_TIMED_OUT;
+ default: {
+ int net_error = MapSystemError(os_error);
+ if (net_error == ERR_FAILED)
+ return ERR_CONNECTION_FAILED; // More specific than ERR_FAILED.
+
+ // Give a more specific error when the user is offline.
+ if (net_error == ERR_ADDRESS_UNREACHABLE &&
+ NetworkChangeNotifier::IsOffline()) {
+ return ERR_INTERNET_DISCONNECTED;
+ }
+
+ return net_error;
+ }
+ }
+}
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+// This class encapsulates all the state that has to be preserved as long as
+// there is a network IO operation in progress. If the owner TCPClientSocketWin
+// is destroyed while an operation is in progress, the Core is detached and it
+// lives until the operation completes and the OS doesn't reference any resource
+// declared on this class anymore.
+class TCPClientSocketWin::Core : public base::RefCounted<Core> {
+ public:
+ explicit Core(TCPClientSocketWin* socket);
+
+ // Start watching for the end of a read or write operation.
+ void WatchForRead();
+ void WatchForWrite();
+
+ // The TCPClientSocketWin is going away.
+ void Detach() { socket_ = NULL; }
+
+ // Throttle the read size based on our current slow start state.
+ // Returns the throttled read size.
+ int ThrottleReadSize(int size) {
+ if (slow_start_throttle_ < kMaxSlowStartThrottle) {
+ size = std::min(size, slow_start_throttle_);
+ slow_start_throttle_ *= 2;
+ }
+ return size;
+ }
+
+ // The separate OVERLAPPED variables for asynchronous operation.
+ // |read_overlapped_| is used for both Connect() and Read().
+ // |write_overlapped_| is only used for Write();
+ OVERLAPPED read_overlapped_;
+ OVERLAPPED write_overlapped_;
+
+ // The buffers used in Read() and Write().
+ scoped_refptr<IOBuffer> read_iobuffer_;
+ scoped_refptr<IOBuffer> write_iobuffer_;
+ int read_buffer_length_;
+ int write_buffer_length_;
+
+ // Remember the state of g_disable_overlapped_reads for the duration of the
+ // socket based on what it was when the socket was created.
+ bool disable_overlapped_reads_;
+ bool non_blocking_reads_initialized_;
+
+ private:
+ friend class base::RefCounted<Core>;
+
+ class ReadDelegate : public base::win::ObjectWatcher::Delegate {
+ public:
+ explicit ReadDelegate(Core* core) : core_(core) {}
+ virtual ~ReadDelegate() {}
+
+ // base::ObjectWatcher::Delegate methods:
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ Core* const core_;
+ };
+
+ class WriteDelegate : public base::win::ObjectWatcher::Delegate {
+ public:
+ explicit WriteDelegate(Core* core) : core_(core) {}
+ virtual ~WriteDelegate() {}
+
+ // base::ObjectWatcher::Delegate methods:
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ Core* const core_;
+ };
+
+ ~Core();
+
+ // The socket that created this object.
+ TCPClientSocketWin* socket_;
+
+ // |reader_| handles the signals from |read_watcher_|.
+ ReadDelegate reader_;
+ // |writer_| handles the signals from |write_watcher_|.
+ WriteDelegate writer_;
+
+ // |read_watcher_| watches for events from Connect() and Read().
+ base::win::ObjectWatcher read_watcher_;
+ // |write_watcher_| watches for events from Write();
+ base::win::ObjectWatcher write_watcher_;
+
+ // When doing reads from the socket, we try to mirror TCP's slow start.
+ // We do this because otherwise the async IO subsystem artifically delays
+ // returning data to the application.
+ static const int kInitialSlowStartThrottle = 1 * 1024;
+ static const int kMaxSlowStartThrottle = 32 * kInitialSlowStartThrottle;
+ int slow_start_throttle_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+TCPClientSocketWin::Core::Core(
+ TCPClientSocketWin* socket)
+ : read_buffer_length_(0),
+ write_buffer_length_(0),
+ disable_overlapped_reads_(g_disable_overlapped_reads),
+ non_blocking_reads_initialized_(false),
+ socket_(socket),
+ reader_(this),
+ writer_(this),
+ slow_start_throttle_(kInitialSlowStartThrottle) {
+ memset(&read_overlapped_, 0, sizeof(read_overlapped_));
+ memset(&write_overlapped_, 0, sizeof(write_overlapped_));
+
+ read_overlapped_.hEvent = WSACreateEvent();
+ write_overlapped_.hEvent = WSACreateEvent();
+}
+
+TCPClientSocketWin::Core::~Core() {
+ // Make sure the message loop is not watching this object anymore.
+ read_watcher_.StopWatching();
+ write_watcher_.StopWatching();
+
+ WSACloseEvent(read_overlapped_.hEvent);
+ memset(&read_overlapped_, 0xaf, sizeof(read_overlapped_));
+ WSACloseEvent(write_overlapped_.hEvent);
+ memset(&write_overlapped_, 0xaf, sizeof(write_overlapped_));
+}
+
+void TCPClientSocketWin::Core::WatchForRead() {
+ // We grab an extra reference because there is an IO operation in progress.
+ // Balanced in ReadDelegate::OnObjectSignaled().
+ AddRef();
+ read_watcher_.StartWatching(read_overlapped_.hEvent, &reader_);
+}
+
+void TCPClientSocketWin::Core::WatchForWrite() {
+ // We grab an extra reference because there is an IO operation in progress.
+ // Balanced in WriteDelegate::OnObjectSignaled().
+ AddRef();
+ write_watcher_.StartWatching(write_overlapped_.hEvent, &writer_);
+}
+
+void TCPClientSocketWin::Core::ReadDelegate::OnObjectSignaled(
+ HANDLE object) {
+ DCHECK_EQ(object, core_->read_overlapped_.hEvent);
+ if (core_->socket_) {
+ if (core_->socket_->waiting_connect()) {
+ core_->socket_->DidCompleteConnect();
+ } else if (core_->disable_overlapped_reads_) {
+ core_->socket_->DidSignalRead();
+ } else {
+ core_->socket_->DidCompleteRead();
+ }
+ }
+
+ core_->Release();
+}
+
+void TCPClientSocketWin::Core::WriteDelegate::OnObjectSignaled(
+ HANDLE object) {
+ DCHECK_EQ(object, core_->write_overlapped_.hEvent);
+ if (core_->socket_)
+ core_->socket_->DidCompleteWrite();
+
+ core_->Release();
+}
+
+//-----------------------------------------------------------------------------
+
+TCPClientSocketWin::TCPClientSocketWin(const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(INVALID_SOCKET),
+ bound_socket_(INVALID_SOCKET),
+ addresses_(addresses),
+ current_address_index_(-1),
+ waiting_read_(false),
+ waiting_write_(false),
+ next_connect_state_(CONNECT_STATE_NONE),
+ connect_os_error_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)),
+ previously_disconnected_(false) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+ EnsureWinsockInit();
+}
+
+TCPClientSocketWin::~TCPClientSocketWin() {
+ Disconnect();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+int TCPClientSocketWin::AdoptSocket(SOCKET socket) {
+ DCHECK_EQ(socket_, INVALID_SOCKET);
+
+ int error = SetupSocket(socket);
+ if (error)
+ return MapSystemError(error);
+
+ socket_ = socket;
+ SetNonBlocking(socket_);
+
+ core_ = new Core(this);
+ current_address_index_ = 0;
+ use_history_.set_was_ever_connected();
+
+ return OK;
+}
+
+int TCPClientSocketWin::Bind(const IPEndPoint& address) {
+ if (current_address_index_ >= 0 || bind_address_.get()) {
+ // Cannot bind the socket if we are already connected or connecting.
+ return ERR_UNEXPECTED;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+
+ // Create |bound_socket_| and try to bind it to |address|.
+ int error = CreateSocket(address.GetSockAddrFamily(), &bound_socket_);
+ if (error)
+ return MapSystemError(error);
+
+ if (bind(bound_socket_, storage.addr, storage.addr_len)) {
+ error = errno;
+ if (closesocket(bound_socket_) < 0)
+ PLOG(ERROR) << "closesocket";
+ bound_socket_ = INVALID_SOCKET;
+ return MapSystemError(error);
+ }
+
+ bind_address_.reset(new IPEndPoint(address));
+
+ return 0;
+}
+
+
+int TCPClientSocketWin::Connect(const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+
+ // If already connected, then just return OK.
+ if (socket_ != INVALID_SOCKET)
+ return OK;
+
+ base::StatsCounter connects("tcp.connect");
+ connects.Increment();
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT,
+ addresses_.CreateNetLogCallback());
+
+ // We will try to connect to each address in addresses_. Start with the
+ // first one in the list.
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ current_address_index_ = 0;
+
+ int rv = DoConnectLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ // Synchronous operation not supported.
+ DCHECK(!callback.is_null());
+ // TODO(ajwong): Is setting read_callback_ the right thing to do here??
+ read_callback_ = callback;
+ } else {
+ LogConnectCompletion(rv);
+ }
+
+ return rv;
+}
+
+int TCPClientSocketWin::DoConnectLoop(int result) {
+ DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE);
+
+ int rv = result;
+ do {
+ ConnectState state = next_connect_state_;
+ next_connect_state_ = CONNECT_STATE_NONE;
+ switch (state) {
+ case CONNECT_STATE_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoConnect();
+ break;
+ case CONNECT_STATE_CONNECT_COMPLETE:
+ rv = DoConnectComplete(rv);
+ break;
+ default:
+ LOG(DFATAL) << "bad state " << state;
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE);
+
+ return rv;
+}
+
+int TCPClientSocketWin::DoConnect() {
+ DCHECK_GE(current_address_index_, 0);
+ DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size()));
+ DCHECK_EQ(0, connect_os_error_);
+
+ const IPEndPoint& endpoint = addresses_[current_address_index_];
+
+ if (previously_disconnected_) {
+ use_history_.Reset();
+ previously_disconnected_ = false;
+ }
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ CreateNetLogIPEndPointCallback(&endpoint));
+
+ next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE;
+
+ if (bound_socket_ != INVALID_SOCKET) {
+ DCHECK(bind_address_.get());
+ socket_ = bound_socket_;
+ bound_socket_ = INVALID_SOCKET;
+ } else {
+ connect_os_error_ = CreateSocket(endpoint.GetSockAddrFamily(), &socket_);
+ if (connect_os_error_ != 0)
+ return MapSystemError(connect_os_error_);
+
+ if (bind_address_.get()) {
+ SockaddrStorage storage;
+ if (!bind_address_->ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+ if (bind(socket_, storage.addr, storage.addr_len))
+ return MapSystemError(errno);
+ }
+ }
+
+ DCHECK(!core_);
+ core_ = new Core(this);
+ // WSAEventSelect sets the socket to non-blocking mode as a side effect.
+ // Our connect() and recv() calls require that the socket be non-blocking.
+ WSAEventSelect(socket_, core_->read_overlapped_.hEvent, FD_CONNECT);
+
+ SockaddrStorage storage;
+ if (!endpoint.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_INVALID_ARGUMENT;
+ if (!connect(socket_, storage.addr, storage.addr_len)) {
+ // Connected without waiting!
+ //
+ // The MSDN page for connect says:
+ // With a nonblocking socket, the connection attempt cannot be completed
+ // immediately. In this case, connect will return SOCKET_ERROR, and
+ // WSAGetLastError will return WSAEWOULDBLOCK.
+ // which implies that for a nonblocking socket, connect never returns 0.
+ // It's not documented whether the event object will be signaled or not
+ // if connect does return 0. So the code below is essentially dead code
+ // and we don't know if it's correct.
+ NOTREACHED();
+
+ if (ResetEventIfSignaled(core_->read_overlapped_.hEvent))
+ return OK;
+ } else {
+ int os_error = WSAGetLastError();
+ if (os_error != WSAEWOULDBLOCK) {
+ LOG(ERROR) << "connect failed: " << os_error;
+ connect_os_error_ = os_error;
+ return MapConnectError(os_error);
+ }
+ }
+
+ core_->WatchForRead();
+ return ERR_IO_PENDING;
+}
+
+int TCPClientSocketWin::DoConnectComplete(int result) {
+ // Log the end of this attempt (and any OS error it threw).
+ int os_error = connect_os_error_;
+ connect_os_error_ = 0;
+ if (result != OK) {
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT,
+ NetLog::IntegerCallback("os_error", os_error));
+ } else {
+ net_log_.EndEvent(NetLog::TYPE_TCP_CONNECT_ATTEMPT);
+ }
+
+ if (result == OK) {
+ use_history_.set_was_ever_connected();
+ return OK; // Done!
+ }
+
+ // Close whatever partially connected socket we currently have.
+ DoDisconnect();
+
+ // Try to fall back to the next address in the list.
+ if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) {
+ next_connect_state_ = CONNECT_STATE_CONNECT;
+ ++current_address_index_;
+ return OK;
+ }
+
+ // Otherwise there is nothing to fall back to, so give up.
+ return result;
+}
+
+void TCPClientSocketWin::Disconnect() {
+ DCHECK(CalledOnValidThread());
+
+ DoDisconnect();
+ current_address_index_ = -1;
+ bind_address_.reset();
+}
+
+void TCPClientSocketWin::DoDisconnect() {
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == INVALID_SOCKET)
+ return;
+
+ // Note: don't use CancelIo to cancel pending IO because it doesn't work
+ // when there is a Winsock layered service provider.
+
+ // In most socket implementations, closing a socket results in a graceful
+ // connection shutdown, but in Winsock we have to call shutdown explicitly.
+ // See the MSDN page "Graceful Shutdown, Linger Options, and Socket Closure"
+ // at http://msdn.microsoft.com/en-us/library/ms738547.aspx
+ shutdown(socket_, SD_SEND);
+
+ // This cancels any pending IO.
+ closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+
+ if (waiting_connect()) {
+ // We closed the socket, so this notification will never come.
+ // From MSDN' WSAEventSelect documentation:
+ // "Closing a socket with closesocket also cancels the association and
+ // selection of network events specified in WSAEventSelect for the socket".
+ core_->Release();
+ }
+
+ waiting_read_ = false;
+ waiting_write_ = false;
+
+ core_->Detach();
+ core_ = NULL;
+
+ previously_disconnected_ = true;
+}
+
+bool TCPClientSocketWin::IsConnected() const {
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == INVALID_SOCKET || waiting_connect())
+ return false;
+
+ if (waiting_read_)
+ return true;
+
+ // Check if connection is alive.
+ char c;
+ int rv = recv(socket_, &c, 1, MSG_PEEK);
+ if (rv == 0)
+ return false;
+ if (rv == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+bool TCPClientSocketWin::IsConnectedAndIdle() const {
+ DCHECK(CalledOnValidThread());
+
+ if (socket_ == INVALID_SOCKET || waiting_connect())
+ return false;
+
+ if (waiting_read_)
+ return true;
+
+ // Check if connection is alive and we haven't received any data
+ // unexpectedly.
+ char c;
+ int rv = recv(socket_, &c, 1, MSG_PEEK);
+ if (rv >= 0)
+ return false;
+ if (WSAGetLastError() != WSAEWOULDBLOCK)
+ return false;
+
+ return true;
+}
+
+int TCPClientSocketWin::GetPeerAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ *address = addresses_[current_address_index_];
+ return OK;
+}
+
+int TCPClientSocketWin::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (socket_ == INVALID_SOCKET) {
+ if (bind_address_.get()) {
+ *address = *bind_address_;
+ return OK;
+ }
+ return ERR_SOCKET_NOT_CONNECTED;
+ }
+
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_len = sizeof(addr_storage);
+ struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&addr_storage);
+ if (getsockname(socket_, addr, &addr_len))
+ return MapSystemError(WSAGetLastError());
+ if (!address->FromSockAddr(addr, addr_len))
+ return ERR_FAILED;
+ return OK;
+}
+
+void TCPClientSocketWin::SetSubresourceSpeculation() {
+ use_history_.set_subresource_speculation();
+}
+
+void TCPClientSocketWin::SetOmniboxSpeculation() {
+ use_history_.set_omnibox_speculation();
+}
+
+bool TCPClientSocketWin::WasEverUsed() const {
+ return use_history_.was_used_to_convey_data();
+}
+
+bool TCPClientSocketWin::UsingTCPFastOpen() const {
+ // Not supported on windows.
+ return false;
+}
+
+bool TCPClientSocketWin::WasNpnNegotiated() const {
+ return false;
+}
+
+NextProto TCPClientSocketWin::GetNegotiatedProtocol() const {
+ return kProtoUnknown;
+}
+
+bool TCPClientSocketWin::GetSSLInfo(SSLInfo* ssl_info) {
+ return false;
+}
+
+int TCPClientSocketWin::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(socket_, INVALID_SOCKET);
+ DCHECK(!waiting_read_);
+ DCHECK(read_callback_.is_null());
+ DCHECK(!core_->read_iobuffer_);
+
+ return DoRead(buf, buf_len, callback);
+}
+
+int TCPClientSocketWin::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(socket_, INVALID_SOCKET);
+ DCHECK(!waiting_write_);
+ DCHECK(write_callback_.is_null());
+ DCHECK_GT(buf_len, 0);
+ DCHECK(!core_->write_iobuffer_);
+
+ base::StatsCounter writes("tcp.writes");
+ writes.Increment();
+
+ WSABUF write_buffer;
+ write_buffer.len = buf_len;
+ write_buffer.buf = buf->data();
+
+ // TODO(wtc): Remove the assertion after enough testing.
+ AssertEventNotSignaled(core_->write_overlapped_.hEvent);
+ DWORD num;
+ int rv = WSASend(socket_, &write_buffer, 1, &num, 0,
+ &core_->write_overlapped_, NULL);
+ if (rv == 0) {
+ if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
+ rv = static_cast<int>(num);
+ if (rv > buf_len || rv < 0) {
+ // It seems that some winsock interceptors report that more was written
+ // than was available. Treat this as an error. http://crbug.com/27870
+ LOG(ERROR) << "Detected broken LSP: Asked to write " << buf_len
+ << " bytes, but " << rv << " bytes reported.";
+ return ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES;
+ }
+ base::StatsCounter write_bytes("tcp.write_bytes");
+ write_bytes.Add(rv);
+ if (rv > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, rv,
+ buf->data());
+ return rv;
+ }
+ } else {
+ int os_error = WSAGetLastError();
+ if (os_error != WSA_IO_PENDING) {
+ int net_error = MapSystemError(os_error);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR,
+ CreateNetLogSocketErrorCallback(net_error, os_error));
+ return net_error;
+ }
+ }
+ waiting_write_ = true;
+ write_callback_ = callback;
+ core_->write_iobuffer_ = buf;
+ core_->write_buffer_length_ = buf_len;
+ core_->WatchForWrite();
+ return ERR_IO_PENDING;
+}
+
+bool TCPClientSocketWin::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ return SetSocketReceiveBufferSize(socket_, size);
+}
+
+bool TCPClientSocketWin::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ return SetSocketSendBufferSize(socket_, size);
+}
+
+bool TCPClientSocketWin::SetKeepAlive(bool enable, int delay) {
+ return SetTCPKeepAlive(socket_, enable, delay);
+}
+
+bool TCPClientSocketWin::SetNoDelay(bool no_delay) {
+ return DisableNagle(socket_, no_delay);
+}
+
+void TCPClientSocketWin::DisableOverlappedReads() {
+ g_disable_overlapped_reads = true;
+}
+
+void TCPClientSocketWin::LogConnectCompletion(int net_error) {
+ if (net_error == OK)
+ UpdateConnectionTypeHistograms(CONNECTION_ANY);
+
+ if (net_error != OK) {
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, net_error);
+ return;
+ }
+
+ struct sockaddr_storage source_address;
+ socklen_t addrlen = sizeof(source_address);
+ int rv = getsockname(
+ socket_, reinterpret_cast<struct sockaddr*>(&source_address), &addrlen);
+ if (rv != 0) {
+ LOG(ERROR) << "getsockname() [rv: " << rv
+ << "] error: " << WSAGetLastError();
+ NOTREACHED();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_CONNECT, rv);
+ return;
+ }
+
+ net_log_.EndEvent(
+ NetLog::TYPE_TCP_CONNECT,
+ CreateNetLogSourceAddressCallback(
+ reinterpret_cast<const struct sockaddr*>(&source_address),
+ sizeof(source_address)));
+}
+
+int TCPClientSocketWin::DoRead(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ if (core_->disable_overlapped_reads_) {
+ if (!core_->non_blocking_reads_initialized_) {
+ WSAEventSelect(socket_, core_->read_overlapped_.hEvent,
+ FD_READ | FD_CLOSE);
+ core_->non_blocking_reads_initialized_ = true;
+ }
+ int rv = recv(socket_, buf->data(), buf_len, 0);
+ if (rv == SOCKET_ERROR) {
+ int os_error = WSAGetLastError();
+ if (os_error != WSAEWOULDBLOCK) {
+ int net_error = MapSystemError(os_error);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR,
+ CreateNetLogSocketErrorCallback(net_error, os_error));
+ return net_error;
+ }
+ } else {
+ base::StatsCounter read_bytes("tcp.read_bytes");
+ if (rv > 0) {
+ use_history_.set_was_used_to_convey_data();
+ read_bytes.Add(rv);
+ }
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, rv,
+ buf->data());
+ return rv;
+ }
+ } else {
+ buf_len = core_->ThrottleReadSize(buf_len);
+
+ WSABUF read_buffer;
+ read_buffer.len = buf_len;
+ read_buffer.buf = buf->data();
+
+ // TODO(wtc): Remove the assertion after enough testing.
+ AssertEventNotSignaled(core_->read_overlapped_.hEvent);
+ DWORD num;
+ DWORD flags = 0;
+ int rv = WSARecv(socket_, &read_buffer, 1, &num, &flags,
+ &core_->read_overlapped_, NULL);
+ if (rv == 0) {
+ if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
+ base::StatsCounter read_bytes("tcp.read_bytes");
+ if (num > 0) {
+ use_history_.set_was_used_to_convey_data();
+ read_bytes.Add(num);
+ }
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, num,
+ buf->data());
+ return static_cast<int>(num);
+ }
+ } else {
+ int os_error = WSAGetLastError();
+ if (os_error != WSA_IO_PENDING) {
+ int net_error = MapSystemError(os_error);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR,
+ CreateNetLogSocketErrorCallback(net_error, os_error));
+ return net_error;
+ }
+ }
+ }
+
+ waiting_read_ = true;
+ read_callback_ = callback;
+ core_->read_iobuffer_ = buf;
+ core_->read_buffer_length_ = buf_len;
+ core_->WatchForRead();
+ return ERR_IO_PENDING;
+}
+
+void TCPClientSocketWin::DoReadCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!read_callback_.is_null());
+
+ // Since Run may result in Read being called, clear read_callback_ up front.
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ c.Run(rv);
+}
+
+void TCPClientSocketWin::DoWriteCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!write_callback_.is_null());
+
+ // since Run may result in Write being called, clear write_callback_ up front.
+ CompletionCallback c = write_callback_;
+ write_callback_.Reset();
+ c.Run(rv);
+}
+
+void TCPClientSocketWin::DidCompleteConnect() {
+ DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE);
+ int result;
+
+ WSANETWORKEVENTS events;
+ int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent,
+ &events);
+ int os_error = 0;
+ if (rv == SOCKET_ERROR) {
+ NOTREACHED();
+ os_error = WSAGetLastError();
+ result = MapSystemError(os_error);
+ } else if (events.lNetworkEvents & FD_CONNECT) {
+ os_error = events.iErrorCode[FD_CONNECT_BIT];
+ result = MapConnectError(os_error);
+ } else {
+ NOTREACHED();
+ result = ERR_UNEXPECTED;
+ }
+
+ connect_os_error_ = os_error;
+ rv = DoConnectLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ LogConnectCompletion(rv);
+ DoReadCallback(rv);
+ }
+}
+
+void TCPClientSocketWin::DidCompleteRead() {
+ DCHECK(waiting_read_);
+ DWORD num_bytes, flags;
+ BOOL ok = WSAGetOverlappedResult(socket_, &core_->read_overlapped_,
+ &num_bytes, FALSE, &flags);
+ waiting_read_ = false;
+ int rv;
+ if (ok) {
+ base::StatsCounter read_bytes("tcp.read_bytes");
+ read_bytes.Add(num_bytes);
+ if (num_bytes > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ num_bytes, core_->read_iobuffer_->data());
+ rv = static_cast<int>(num_bytes);
+ } else {
+ int os_error = WSAGetLastError();
+ rv = MapSystemError(os_error);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_READ_ERROR,
+ CreateNetLogSocketErrorCallback(rv, os_error));
+ }
+ WSAResetEvent(core_->read_overlapped_.hEvent);
+ core_->read_iobuffer_ = NULL;
+ core_->read_buffer_length_ = 0;
+ DoReadCallback(rv);
+}
+
+void TCPClientSocketWin::DidCompleteWrite() {
+ DCHECK(waiting_write_);
+
+ DWORD num_bytes, flags;
+ BOOL ok = WSAGetOverlappedResult(socket_, &core_->write_overlapped_,
+ &num_bytes, FALSE, &flags);
+ WSAResetEvent(core_->write_overlapped_.hEvent);
+ waiting_write_ = false;
+ int rv;
+ if (!ok) {
+ int os_error = WSAGetLastError();
+ rv = MapSystemError(os_error);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_WRITE_ERROR,
+ CreateNetLogSocketErrorCallback(rv, os_error));
+ } else {
+ rv = static_cast<int>(num_bytes);
+ if (rv > core_->write_buffer_length_ || rv < 0) {
+ // It seems that some winsock interceptors report that more was written
+ // than was available. Treat this as an error. http://crbug.com/27870
+ LOG(ERROR) << "Detected broken LSP: Asked to write "
+ << core_->write_buffer_length_ << " bytes, but " << rv
+ << " bytes reported.";
+ rv = ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES;
+ } else {
+ base::StatsCounter write_bytes("tcp.write_bytes");
+ write_bytes.Add(num_bytes);
+ if (num_bytes > 0)
+ use_history_.set_was_used_to_convey_data();
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT, num_bytes,
+ core_->write_iobuffer_->data());
+ }
+ }
+ core_->write_iobuffer_ = NULL;
+ DoWriteCallback(rv);
+}
+
+void TCPClientSocketWin::DidSignalRead() {
+ DCHECK(waiting_read_);
+ int os_error = 0;
+ WSANETWORKEVENTS network_events;
+ int rv = WSAEnumNetworkEvents(socket_, core_->read_overlapped_.hEvent,
+ &network_events);
+ if (rv == SOCKET_ERROR) {
+ os_error = WSAGetLastError();
+ rv = MapSystemError(os_error);
+ } else if (network_events.lNetworkEvents) {
+ DCHECK_EQ(network_events.lNetworkEvents & ~(FD_READ | FD_CLOSE), 0);
+ // If network_events.lNetworkEvents is FD_CLOSE and
+ // network_events.iErrorCode[FD_CLOSE_BIT] is 0, it is a graceful
+ // connection closure. It is tempting to directly set rv to 0 in
+ // this case, but the MSDN pages for WSAEventSelect and
+ // WSAAsyncSelect recommend we still call DoRead():
+ // FD_CLOSE should only be posted after all data is read from a
+ // socket, but an application should check for remaining data upon
+ // receipt of FD_CLOSE to avoid any possibility of losing data.
+ //
+ // If network_events.iErrorCode[FD_READ_BIT] or
+ // network_events.iErrorCode[FD_CLOSE_BIT] is nonzero, still call
+ // DoRead() because recv() reports a more accurate error code
+ // (WSAECONNRESET vs. WSAECONNABORTED) when the connection was
+ // reset.
+ rv = DoRead(core_->read_iobuffer_, core_->read_buffer_length_,
+ read_callback_);
+ if (rv == ERR_IO_PENDING)
+ return;
+ } else {
+ // This may happen because Read() may succeed synchronously and
+ // consume all the received data without resetting the event object.
+ core_->WatchForRead();
+ return;
+ }
+ waiting_read_ = false;
+ core_->read_iobuffer_ = NULL;
+ core_->read_buffer_length_ = 0;
+ DoReadCallback(rv);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_client_socket_win.h b/chromium/net/socket/tcp_client_socket_win.h
new file mode 100644
index 00000000000..26c8b9feff2
--- /dev/null
+++ b/chromium/net/socket/tcp_client_socket_win.h
@@ -0,0 +1,162 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_
+#define NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_
+
+#include <winsock2.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/socket/stream_socket.h"
+
+namespace net {
+
+class BoundNetLog;
+
+class NET_EXPORT TCPClientSocketWin : public StreamSocket,
+ NON_EXPORTED_BASE(base::NonThreadSafe) {
+ public:
+ // The IP address(es) and port number to connect to. The TCP socket will try
+ // each IP address in the list until it succeeds in establishing a
+ // connection.
+ TCPClientSocketWin(const AddressList& addresses,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source);
+
+ virtual ~TCPClientSocketWin();
+
+ // AdoptSocket causes the given, connected socket to be adopted as a TCP
+ // socket. This object must not be connected. This object takes ownership of
+ // the given socket and then acts as if Connect() had been called. This
+ // function is used by TCPServerSocket() to adopt accepted connections
+ // and for testing.
+ int AdoptSocket(SOCKET socket);
+
+ // Binds the socket to a local IP address and port.
+ int Bind(const IPEndPoint& address);
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback);
+ virtual void Disconnect();
+ virtual bool IsConnected() const;
+ virtual bool IsConnectedAndIdle() const;
+ virtual int GetPeerAddress(IPEndPoint* address) const;
+ virtual int GetLocalAddress(IPEndPoint* address) const;
+ virtual const BoundNetLog& NetLog() const { return net_log_; }
+ virtual void SetSubresourceSpeculation();
+ virtual void SetOmniboxSpeculation();
+ virtual bool WasEverUsed() const;
+ virtual bool UsingTCPFastOpen() const;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ // Multiple outstanding requests are not supported.
+ // Full duplex mode (reading and writing at the same time) is supported
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback);
+
+ virtual bool SetReceiveBufferSize(int32 size);
+ virtual bool SetSendBufferSize(int32 size);
+
+ virtual bool SetKeepAlive(bool enable, int delay);
+ virtual bool SetNoDelay(bool no_delay);
+
+ // Perform reads in non-blocking mode instead of overlapped mode.
+ // Used for experiments.
+ static void DisableOverlappedReads();
+
+ private:
+ // State machine for connecting the socket.
+ enum ConnectState {
+ CONNECT_STATE_CONNECT,
+ CONNECT_STATE_CONNECT_COMPLETE,
+ CONNECT_STATE_NONE,
+ };
+
+ class Core;
+
+ // State machine used by Connect().
+ int DoConnectLoop(int result);
+ int DoConnect();
+ int DoConnectComplete(int result);
+
+ // Helper used by Disconnect(), which disconnects minus the logging and
+ // resetting of current_address_index_.
+ void DoDisconnect();
+
+ // Returns true if a Connect() is in progress.
+ bool waiting_connect() const {
+ return next_connect_state_ != CONNECT_STATE_NONE;
+ }
+
+ // Called after Connect() has completed with |net_error|.
+ void LogConnectCompletion(int net_error);
+
+ int DoRead(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+ void DoReadCallback(int rv);
+ void DoWriteCallback(int rv);
+ void DidCompleteConnect();
+ void DidCompleteRead();
+ void DidCompleteWrite();
+ void DidSignalRead();
+
+ SOCKET socket_;
+
+ // Local IP address and port we are bound to. Set to NULL if Bind()
+ // was't called (in that cases OS chooses address/port).
+ scoped_ptr<IPEndPoint> bind_address_;
+
+ // Stores bound socket between Bind() and Connect() calls.
+ SOCKET bound_socket_;
+
+ // The list of addresses we should try in order to establish a connection.
+ AddressList addresses_;
+
+ // Where we are in above list. Set to -1 if uninitialized.
+ int current_address_index_;
+
+ // The various states that the socket could be in.
+ bool waiting_read_;
+ bool waiting_write_;
+
+ // The core of the socket that can live longer than the socket itself. We pass
+ // resources to the Windows async IO functions and we have to make sure that
+ // they are not destroyed while the OS still references them.
+ scoped_refptr<Core> core_;
+
+ // External callback; called when connect or read is complete.
+ CompletionCallback read_callback_;
+
+ // External callback; called when write is complete.
+ CompletionCallback write_callback_;
+
+ // The next state for the Connect() state machine.
+ ConnectState next_connect_state_;
+
+ // The OS error that CONNECT_STATE_CONNECT last completed with.
+ int connect_os_error_;
+
+ BoundNetLog net_log_;
+
+ // This socket was previously disconnected and has not been re-connected.
+ bool previously_disconnected_;
+
+ // Record of connectivity and transmissions, for use in speculative connection
+ // histograms.
+ UseHistory use_history_;
+
+ DISALLOW_COPY_AND_ASSIGN(TCPClientSocketWin);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_CLIENT_SOCKET_WIN_H_
diff --git a/chromium/net/socket/tcp_listen_socket.cc b/chromium/net/socket/tcp_listen_socket.cc
new file mode 100644
index 00000000000..aab2e45d0e9
--- /dev/null
+++ b/chromium/net/socket/tcp_listen_socket.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_listen_socket.h"
+
+#if defined(OS_WIN)
+// winsock2.h must be included first in order to ensure it is included before
+// windows.h.
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include "net/base/net_errors.h"
+#endif
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "net/base/net_util.h"
+#include "net/base/winsock_init.h"
+
+using std::string;
+
+namespace net {
+
+// static
+scoped_refptr<TCPListenSocket> TCPListenSocket::CreateAndListen(
+ const string& ip, int port, StreamListenSocket::Delegate* del) {
+ SocketDescriptor s = CreateAndBind(ip, port);
+ if (s == kInvalidSocket)
+ return NULL;
+ scoped_refptr<TCPListenSocket> sock(new TCPListenSocket(s, del));
+ sock->Listen();
+ return sock;
+}
+
+TCPListenSocket::TCPListenSocket(SocketDescriptor s,
+ StreamListenSocket::Delegate* del)
+ : StreamListenSocket(s, del) {
+}
+
+TCPListenSocket::~TCPListenSocket() {}
+
+SocketDescriptor TCPListenSocket::CreateAndBind(const string& ip, int port) {
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+
+ SocketDescriptor s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s != kInvalidSocket) {
+#if defined(OS_POSIX)
+ // Allow rapid reuse.
+ static const int kOn = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
+#endif
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(ip.c_str());
+ addr.sin_port = base::HostToNet16(port);
+ if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))) {
+#if defined(OS_WIN)
+ closesocket(s);
+#elif defined(OS_POSIX)
+ close(s);
+#endif
+ LOG(ERROR) << "Could not bind socket to " << ip << ":" << port;
+ s = kInvalidSocket;
+ }
+ }
+ return s;
+}
+
+SocketDescriptor TCPListenSocket::CreateAndBindAnyPort(const string& ip,
+ int* port) {
+ SocketDescriptor s = CreateAndBind(ip, 0);
+ if (s == kInvalidSocket)
+ return kInvalidSocket;
+ sockaddr_in addr;
+ socklen_t addr_size = sizeof(addr);
+ bool failed = getsockname(s, reinterpret_cast<struct sockaddr*>(&addr),
+ &addr_size) != 0;
+ if (addr_size != sizeof(addr))
+ failed = true;
+ if (failed) {
+ LOG(ERROR) << "Could not determine bound port, getsockname() failed";
+#if defined(OS_WIN)
+ closesocket(s);
+#elif defined(OS_POSIX)
+ close(s);
+#endif
+ return kInvalidSocket;
+ }
+ *port = base::NetToHost16(addr.sin_port);
+ return s;
+}
+
+void TCPListenSocket::Accept() {
+ SocketDescriptor conn = AcceptSocket();
+ if (conn == kInvalidSocket)
+ return;
+ scoped_refptr<TCPListenSocket> sock(
+ new TCPListenSocket(conn, socket_delegate_));
+ // It's up to the delegate to AddRef if it wants to keep it around.
+#if defined(OS_POSIX)
+ sock->WatchSocket(WAITING_READ);
+#endif
+ socket_delegate_->DidAccept(this, sock.get());
+}
+
+TCPListenSocketFactory::TCPListenSocketFactory(const string& ip, int port)
+ : ip_(ip),
+ port_(port) {
+}
+
+TCPListenSocketFactory::~TCPListenSocketFactory() {}
+
+scoped_refptr<StreamListenSocket> TCPListenSocketFactory::CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const {
+ return TCPListenSocket::CreateAndListen(ip_, port_, delegate);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_listen_socket.h b/chromium/net/socket/tcp_listen_socket.h
new file mode 100644
index 00000000000..dbc5347e945
--- /dev/null
+++ b/chromium/net/socket/tcp_listen_socket.h
@@ -0,0 +1,64 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_TCP_LISTEN_SOCKET_H_
+#define NET_SOCKET_TCP_LISTEN_SOCKET_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/socket/stream_listen_socket.h"
+
+namespace net {
+
+// Implements a TCP socket. Note that this is ref counted.
+class NET_EXPORT TCPListenSocket : public StreamListenSocket {
+ public:
+ // Listen on port for the specified IP address. Use 127.0.0.1 to only
+ // accept local connections.
+ static scoped_refptr<TCPListenSocket> CreateAndListen(
+ const std::string& ip, int port, StreamListenSocket::Delegate* del);
+
+ // Get raw TCP socket descriptor bound to ip:port.
+ static SocketDescriptor CreateAndBind(const std::string& ip, int port);
+
+ // Get raw TCP socket descriptor bound to ip and return port it is bound to.
+ static SocketDescriptor CreateAndBindAnyPort(const std::string& ip,
+ int* port);
+
+ protected:
+ friend class scoped_refptr<TCPListenSocket>;
+
+ TCPListenSocket(SocketDescriptor s, StreamListenSocket::Delegate* del);
+ virtual ~TCPListenSocket();
+
+ // Implements StreamListenSocket::Accept.
+ virtual void Accept() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TCPListenSocket);
+};
+
+// Factory that can be used to instantiate TCPListenSocket.
+class NET_EXPORT TCPListenSocketFactory : public StreamListenSocketFactory {
+ public:
+ TCPListenSocketFactory(const std::string& ip, int port);
+ virtual ~TCPListenSocketFactory();
+
+ // StreamListenSocketFactory overrides.
+ virtual scoped_refptr<StreamListenSocket> CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const OVERRIDE;
+
+ private:
+ const std::string ip_;
+ const int port_;
+
+ DISALLOW_COPY_AND_ASSIGN(TCPListenSocketFactory);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_LISTEN_SOCKET_H_
diff --git a/chromium/net/socket/tcp_listen_socket_unittest.cc b/chromium/net/socket/tcp_listen_socket_unittest.cc
new file mode 100644
index 00000000000..d13b784cbdc
--- /dev/null
+++ b/chromium/net/socket/tcp_listen_socket_unittest.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_listen_socket_unittest.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include "base/bind.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_util.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+const int TCPListenSocketTester::kTestPort = 9999;
+
+static const int kReadBufSize = 1024;
+static const char kHelloWorld[] = "HELLO, WORLD";
+static const int kMaxQueueSize = 20;
+static const char kLoopback[] = "127.0.0.1";
+static const int kDefaultTimeoutMs = 5000;
+
+TCPListenSocketTester::TCPListenSocketTester()
+ : loop_(NULL), server_(NULL), connection_(NULL), cv_(&lock_) {}
+
+void TCPListenSocketTester::SetUp() {
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ thread_.reset(new base::Thread("socketio_test"));
+ thread_->StartWithOptions(options);
+ loop_ = reinterpret_cast<base::MessageLoopForIO*>(thread_->message_loop());
+
+ loop_->PostTask(FROM_HERE, base::Bind(
+ &TCPListenSocketTester::Listen, this));
+
+ // verify Listen succeeded
+ NextAction();
+ ASSERT_FALSE(server_.get() == NULL);
+ ASSERT_EQ(ACTION_LISTEN, last_action_.type());
+
+ // verify the connect/accept and setup test_socket_
+ test_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ ASSERT_NE(StreamListenSocket::kInvalidSocket, test_socket_);
+ struct sockaddr_in client;
+ client.sin_family = AF_INET;
+ client.sin_addr.s_addr = inet_addr(kLoopback);
+ client.sin_port = base::HostToNet16(kTestPort);
+ int ret = HANDLE_EINTR(
+ connect(test_socket_, reinterpret_cast<sockaddr*>(&client),
+ sizeof(client)));
+#if defined(OS_POSIX)
+ // The connect() call may be interrupted by a signal. When connect()
+ // is retried on EINTR, it fails with EISCONN.
+ if (ret == StreamListenSocket::kSocketError)
+ ASSERT_EQ(EISCONN, errno);
+#else
+ // Don't have signals.
+ ASSERT_NE(StreamListenSocket::kSocketError, ret);
+#endif
+
+ NextAction();
+ ASSERT_EQ(ACTION_ACCEPT, last_action_.type());
+}
+
+void TCPListenSocketTester::TearDown() {
+#if defined(OS_WIN)
+ ASSERT_EQ(0, closesocket(test_socket_));
+#elif defined(OS_POSIX)
+ ASSERT_EQ(0, HANDLE_EINTR(close(test_socket_)));
+#endif
+ NextAction();
+ ASSERT_EQ(ACTION_CLOSE, last_action_.type());
+
+ loop_->PostTask(FROM_HERE, base::Bind(
+ &TCPListenSocketTester::Shutdown, this));
+ NextAction();
+ ASSERT_EQ(ACTION_SHUTDOWN, last_action_.type());
+
+ thread_.reset();
+ loop_ = NULL;
+}
+
+void TCPListenSocketTester::ReportAction(
+ const TCPListenSocketTestAction& action) {
+ base::AutoLock locked(lock_);
+ queue_.push_back(action);
+ cv_.Broadcast();
+}
+
+void TCPListenSocketTester::NextAction() {
+ base::AutoLock locked(lock_);
+ while (queue_.empty())
+ cv_.Wait();
+ last_action_ = queue_.front();
+ queue_.pop_front();
+}
+
+int TCPListenSocketTester::ClearTestSocket() {
+ char buf[kReadBufSize];
+ int len_ret = 0;
+ do {
+ int len = HANDLE_EINTR(recv(test_socket_, buf, kReadBufSize, 0));
+ if (len == StreamListenSocket::kSocketError || len == 0) {
+ break;
+ } else {
+ len_ret += len;
+ }
+ } while (true);
+ return len_ret;
+}
+
+void TCPListenSocketTester::Shutdown() {
+ connection_->Release();
+ connection_ = NULL;
+ server_->Release();
+ server_ = NULL;
+ ReportAction(TCPListenSocketTestAction(ACTION_SHUTDOWN));
+}
+
+void TCPListenSocketTester::Listen() {
+ server_ = DoListen();
+ ASSERT_TRUE(server_.get());
+ server_->AddRef();
+ ReportAction(TCPListenSocketTestAction(ACTION_LISTEN));
+}
+
+void TCPListenSocketTester::SendFromTester() {
+ connection_->Send(kHelloWorld);
+ ReportAction(TCPListenSocketTestAction(ACTION_SEND));
+}
+
+void TCPListenSocketTester::TestClientSend() {
+ ASSERT_TRUE(Send(test_socket_, kHelloWorld));
+ NextAction();
+ ASSERT_EQ(ACTION_READ, last_action_.type());
+ ASSERT_EQ(last_action_.data(), kHelloWorld);
+}
+
+void TCPListenSocketTester::TestClientSendLong() {
+ size_t hello_len = strlen(kHelloWorld);
+ std::string long_string;
+ size_t long_len = 0;
+ for (int i = 0; i < 200; i++) {
+ long_string += kHelloWorld;
+ long_len += hello_len;
+ }
+ ASSERT_TRUE(Send(test_socket_, long_string));
+ size_t read_len = 0;
+ while (read_len < long_len) {
+ NextAction();
+ ASSERT_EQ(ACTION_READ, last_action_.type());
+ std::string last_data = last_action_.data();
+ size_t len = last_data.length();
+ if (long_string.compare(read_len, len, last_data)) {
+ ASSERT_EQ(long_string.compare(read_len, len, last_data), 0);
+ }
+ read_len += last_data.length();
+ }
+ ASSERT_EQ(read_len, long_len);
+}
+
+void TCPListenSocketTester::TestServerSend() {
+ loop_->PostTask(FROM_HERE, base::Bind(
+ &TCPListenSocketTester::SendFromTester, this));
+ NextAction();
+ ASSERT_EQ(ACTION_SEND, last_action_.type());
+ const int buf_len = 200;
+ char buf[buf_len+1];
+ unsigned recv_len = 0;
+ while (recv_len < strlen(kHelloWorld)) {
+ int r = HANDLE_EINTR(recv(test_socket_,
+ buf + recv_len, buf_len - recv_len, 0));
+ ASSERT_GE(r, 0);
+ recv_len += static_cast<unsigned>(r);
+ if (!r)
+ break;
+ }
+ buf[recv_len] = 0;
+ ASSERT_STREQ(kHelloWorld, buf);
+}
+
+void TCPListenSocketTester::TestServerSendMultiple() {
+ // Send enough data to exceed the socket receive window. 20kb is probably a
+ // safe bet.
+ int send_count = (1024*20) / (sizeof(kHelloWorld)-1);
+
+ // Send multiple writes. Since no reading is occurring the data should be
+ // buffered in TCPListenSocket.
+ for (int i = 0; i < send_count; ++i) {
+ loop_->PostTask(FROM_HERE, base::Bind(
+ &TCPListenSocketTester::SendFromTester, this));
+ NextAction();
+ ASSERT_EQ(ACTION_SEND, last_action_.type());
+ }
+
+ // Make multiple reads. All of the data should eventually be returned.
+ char buf[sizeof(kHelloWorld)];
+ const int buf_len = sizeof(kHelloWorld);
+ for (int i = 0; i < send_count; ++i) {
+ unsigned recv_len = 0;
+ while (recv_len < buf_len-1) {
+ int r = HANDLE_EINTR(recv(test_socket_,
+ buf + recv_len, buf_len - 1 - recv_len, 0));
+ ASSERT_GE(r, 0);
+ recv_len += static_cast<unsigned>(r);
+ if (!r)
+ break;
+ }
+ buf[recv_len] = 0;
+ ASSERT_STREQ(kHelloWorld, buf);
+ }
+}
+
+bool TCPListenSocketTester::Send(SocketDescriptor sock,
+ const std::string& str) {
+ int len = static_cast<int>(str.length());
+ int send_len = HANDLE_EINTR(send(sock, str.data(), len, 0));
+ if (send_len == StreamListenSocket::kSocketError) {
+ LOG(ERROR) << "send failed: " << errno;
+ return false;
+ } else if (send_len != len) {
+ return false;
+ }
+ return true;
+}
+
+void TCPListenSocketTester::DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) {
+ connection_ = connection;
+ connection_->AddRef();
+ ReportAction(TCPListenSocketTestAction(ACTION_ACCEPT));
+}
+
+void TCPListenSocketTester::DidRead(StreamListenSocket* connection,
+ const char* data,
+ int len) {
+ std::string str(data, len);
+ ReportAction(TCPListenSocketTestAction(ACTION_READ, str));
+}
+
+void TCPListenSocketTester::DidClose(StreamListenSocket* sock) {
+ ReportAction(TCPListenSocketTestAction(ACTION_CLOSE));
+}
+
+TCPListenSocketTester::~TCPListenSocketTester() {}
+
+scoped_refptr<TCPListenSocket> TCPListenSocketTester::DoListen() {
+ return TCPListenSocket::CreateAndListen(kLoopback, kTestPort, this);
+}
+
+class TCPListenSocketTest: public PlatformTest {
+ public:
+ TCPListenSocketTest() {
+ tester_ = NULL;
+ }
+
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+ tester_ = new TCPListenSocketTester();
+ tester_->SetUp();
+ }
+
+ virtual void TearDown() {
+ PlatformTest::TearDown();
+ tester_->TearDown();
+ tester_ = NULL;
+ }
+
+ scoped_refptr<TCPListenSocketTester> tester_;
+};
+
+TEST_F(TCPListenSocketTest, ClientSend) {
+ tester_->TestClientSend();
+}
+
+TEST_F(TCPListenSocketTest, ClientSendLong) {
+ tester_->TestClientSendLong();
+}
+
+TEST_F(TCPListenSocketTest, ServerSend) {
+ tester_->TestServerSend();
+}
+
+TEST_F(TCPListenSocketTest, ServerSendMultiple) {
+ tester_->TestServerSendMultiple();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_listen_socket_unittest.h b/chromium/net/socket/tcp_listen_socket_unittest.h
new file mode 100644
index 00000000000..048a0186705
--- /dev/null
+++ b/chromium/net/socket/tcp_listen_socket_unittest.h
@@ -0,0 +1,122 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_LISTEN_SOCKET_UNITTEST_H_
+#define NET_BASE_LISTEN_SOCKET_UNITTEST_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <winsock2.h>
+#elif defined(OS_POSIX)
+#include <arpa/inet.h>
+#include <errno.h>
+#include <sys/socket.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread.h"
+#include "net/base/net_util.h"
+#include "net/base/winsock_init.h"
+#include "net/socket/tcp_listen_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+enum ActionType {
+ ACTION_NONE = 0,
+ ACTION_LISTEN = 1,
+ ACTION_ACCEPT = 2,
+ ACTION_READ = 3,
+ ACTION_SEND = 4,
+ ACTION_CLOSE = 5,
+ ACTION_SHUTDOWN = 6
+};
+
+class TCPListenSocketTestAction {
+ public:
+ TCPListenSocketTestAction() : action_(ACTION_NONE) {}
+ explicit TCPListenSocketTestAction(ActionType action) : action_(action) {}
+ TCPListenSocketTestAction(ActionType action, std::string data)
+ : action_(action),
+ data_(data) {}
+
+ const std::string data() const { return data_; }
+ ActionType type() const { return action_; }
+
+ private:
+ ActionType action_;
+ std::string data_;
+};
+
+
+// This had to be split out into a separate class because I couldn't
+// make the testing::Test class refcounted.
+class TCPListenSocketTester :
+ public StreamListenSocket::Delegate,
+ public base::RefCountedThreadSafe<TCPListenSocketTester> {
+
+ public:
+ TCPListenSocketTester();
+
+ void SetUp();
+ void TearDown();
+
+ void ReportAction(const TCPListenSocketTestAction& action);
+ void NextAction();
+
+ // read all pending data from the test socket
+ int ClearTestSocket();
+ // Release the connection and server sockets
+ void Shutdown();
+ void Listen();
+ void SendFromTester();
+ // verify the send/read from client to server
+ void TestClientSend();
+ // verify send/read of a longer string
+ void TestClientSendLong();
+ // verify a send/read from server to client
+ void TestServerSend();
+ // verify multiple sends and reads from server to client.
+ void TestServerSendMultiple();
+
+ virtual bool Send(SocketDescriptor sock, const std::string& str);
+
+ // StreamListenSocket::Delegate:
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) OVERRIDE;
+ virtual void DidRead(StreamListenSocket* connection, const char* data,
+ int len) OVERRIDE;
+ virtual void DidClose(StreamListenSocket* sock) OVERRIDE;
+
+ scoped_ptr<base::Thread> thread_;
+ base::MessageLoopForIO* loop_;
+ scoped_refptr<TCPListenSocket> server_;
+ StreamListenSocket* connection_;
+ TCPListenSocketTestAction last_action_;
+
+ SocketDescriptor test_socket_;
+ static const int kTestPort;
+
+ base::Lock lock_; // protects |queue_| and wraps |cv_|
+ base::ConditionVariable cv_;
+ std::deque<TCPListenSocketTestAction> queue_;
+
+ protected:
+ friend class base::RefCountedThreadSafe<TCPListenSocketTester>;
+
+ virtual ~TCPListenSocketTester();
+
+ virtual scoped_refptr<TCPListenSocket> DoListen();
+};
+
+} // namespace net
+
+#endif // NET_BASE_LISTEN_SOCKET_UNITTEST_H_
diff --git a/chromium/net/socket/tcp_server_socket.h b/chromium/net/socket/tcp_server_socket.h
new file mode 100644
index 00000000000..4970a150e8d
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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_SOCKET_TCP_SERVER_SOCKET_H_
+#define NET_SOCKET_TCP_SERVER_SOCKET_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "net/socket/tcp_server_socket_win.h"
+#elif defined(OS_POSIX)
+#include "net/socket/tcp_server_socket_libevent.h"
+#endif
+
+namespace net {
+
+#if defined(OS_WIN)
+typedef TCPServerSocketWin TCPServerSocket;
+#elif defined(OS_POSIX)
+typedef TCPServerSocketLibevent TCPServerSocket;
+#endif
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_SERVER_SOCKET_H_
diff --git a/chromium/net/socket/tcp_server_socket_libevent.cc b/chromium/net/socket/tcp_server_socket_libevent.cc
new file mode 100644
index 00000000000..38dda962f46
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket_libevent.cc
@@ -0,0 +1,223 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_server_socket_libevent.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "build/build_config.h"
+
+#if defined(OS_POSIX)
+#include <netinet/in.h>
+#endif
+
+#include "base/posix/eintr_wrapper.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/socket/socket_net_log_params.h"
+#include "net/socket/tcp_client_socket.h"
+
+namespace net {
+
+namespace {
+
+const int kInvalidSocket = -1;
+
+} // namespace
+
+TCPServerSocketLibevent::TCPServerSocketLibevent(
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(kInvalidSocket),
+ accept_socket_(NULL),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+}
+
+TCPServerSocketLibevent::~TCPServerSocketLibevent() {
+ if (socket_ != kInvalidSocket)
+ Close();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+int TCPServerSocketLibevent::Listen(const IPEndPoint& address, int backlog) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_GT(backlog, 0);
+ DCHECK_EQ(socket_, kInvalidSocket);
+
+ socket_ = socket(address.GetSockAddrFamily(), SOCK_STREAM, IPPROTO_TCP);
+ if (socket_ < 0) {
+ PLOG(ERROR) << "socket() returned an error";
+ return MapSystemError(errno);
+ }
+
+ if (SetNonBlocking(socket_)) {
+ int result = MapSystemError(errno);
+ Close();
+ return result;
+ }
+
+ int result = SetSocketOptions();
+ if (result != OK) {
+ Close();
+ return result;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len)) {
+ Close();
+ return ERR_ADDRESS_INVALID;
+ }
+
+ result = bind(socket_, storage.addr, storage.addr_len);
+ if (result < 0) {
+ PLOG(ERROR) << "bind() returned an error";
+ result = MapSystemError(errno);
+ Close();
+ return result;
+ }
+
+ result = listen(socket_, backlog);
+ if (result < 0) {
+ PLOG(ERROR) << "listen() returned an error";
+ result = MapSystemError(errno);
+ Close();
+ return result;
+ }
+
+ return OK;
+}
+
+int TCPServerSocketLibevent::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len) < 0)
+ return MapSystemError(errno);
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+
+ return OK;
+}
+
+int TCPServerSocketLibevent::Accept(
+ scoped_ptr<StreamSocket>* socket, const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(socket);
+ DCHECK(!callback.is_null());
+ DCHECK(accept_callback_.is_null());
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_ACCEPT);
+
+ int result = AcceptInternal(socket);
+
+ if (result == ERR_IO_PENDING) {
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_READ,
+ &accept_socket_watcher_, this)) {
+ PLOG(ERROR) << "WatchFileDescriptor failed on read";
+ return MapSystemError(errno);
+ }
+
+ accept_socket_ = socket;
+ accept_callback_ = callback;
+ }
+
+ return result;
+}
+
+int TCPServerSocketLibevent::SetSocketOptions() {
+ // SO_REUSEADDR is useful for server sockets to bind to a recently unbound
+ // port. When a socket is closed, the end point changes its state to TIME_WAIT
+ // and wait for 2 MSL (maximum segment lifetime) to ensure the remote peer
+ // acknowledges its closure. For server sockets, it is usually safe to
+ // bind to a TIME_WAIT end point immediately, which is a widely adopted
+ // behavior.
+ //
+ // Note that on *nix, SO_REUSEADDR does not enable the TCP socket to bind to
+ // an end point that is already bound by another socket. To do that one must
+ // set SO_REUSEPORT instead. This option is not provided on Linux prior
+ // to 3.9.
+ //
+ // SO_REUSEPORT is provided in MacOS X and iOS.
+ int true_value = 1;
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &true_value,
+ sizeof(true_value));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+}
+
+int TCPServerSocketLibevent::AcceptInternal(
+ scoped_ptr<StreamSocket>* socket) {
+ SockaddrStorage storage;
+ int new_socket = HANDLE_EINTR(accept(socket_,
+ storage.addr,
+ &storage.addr_len));
+ if (new_socket < 0) {
+ int net_error = MapSystemError(errno);
+ if (net_error != ERR_IO_PENDING)
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, net_error);
+ return net_error;
+ }
+
+ IPEndPoint address;
+ if (!address.FromSockAddr(storage.addr, storage.addr_len)) {
+ NOTREACHED();
+ if (HANDLE_EINTR(close(new_socket)) < 0)
+ PLOG(ERROR) << "close";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, ERR_FAILED);
+ return ERR_FAILED;
+ }
+ scoped_ptr<TCPClientSocket> tcp_socket(new TCPClientSocket(
+ AddressList(address),
+ net_log_.net_log(), net_log_.source()));
+ int adopt_result = tcp_socket->AdoptSocket(new_socket);
+ if (adopt_result != OK) {
+ if (HANDLE_EINTR(close(new_socket)) < 0)
+ PLOG(ERROR) << "close";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, adopt_result);
+ return adopt_result;
+ }
+ socket->reset(tcp_socket.release());
+ net_log_.EndEvent(NetLog::TYPE_TCP_ACCEPT,
+ CreateNetLogIPEndPointCallback(&address));
+ return OK;
+}
+
+void TCPServerSocketLibevent::Close() {
+ if (socket_ != kInvalidSocket) {
+ bool ok = accept_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ if (HANDLE_EINTR(close(socket_)) < 0)
+ PLOG(ERROR) << "close";
+ socket_ = kInvalidSocket;
+ }
+}
+
+void TCPServerSocketLibevent::OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(CalledOnValidThread());
+
+ int result = AcceptInternal(accept_socket_);
+ if (result != ERR_IO_PENDING) {
+ accept_socket_ = NULL;
+ bool ok = accept_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ CompletionCallback callback = accept_callback_;
+ accept_callback_.Reset();
+ callback.Run(result);
+ }
+}
+
+void TCPServerSocketLibevent::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_server_socket_libevent.h b/chromium/net/socket/tcp_server_socket_libevent.h
new file mode 100644
index 00000000000..fe69472a653
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket_libevent.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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_SOCKET_TCP_SERVER_SOCKET_LIBEVENT_H_
+#define NET_SOCKET_TCP_SERVER_SOCKET_LIBEVENT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/socket/server_socket.h"
+
+namespace net {
+
+class IPEndPoint;
+
+class NET_EXPORT_PRIVATE TCPServerSocketLibevent :
+ public ServerSocket,
+ public base::NonThreadSafe,
+ public base::MessageLoopForIO::Watcher {
+ public:
+ TCPServerSocketLibevent(net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ virtual ~TCPServerSocketLibevent();
+
+ // net::ServerSocket implementation.
+ virtual int Listen(const net::IPEndPoint& address, int backlog) OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int Accept(scoped_ptr<StreamSocket>* socket,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // MessageLoopForIO::Watcher implementation.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+ int SetSocketOptions();
+ int AcceptInternal(scoped_ptr<StreamSocket>* socket);
+ void Close();
+
+ int socket_;
+
+ base::MessageLoopForIO::FileDescriptorWatcher accept_socket_watcher_;
+
+ scoped_ptr<StreamSocket>* accept_socket_;
+ CompletionCallback accept_callback_;
+
+ BoundNetLog net_log_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_SERVER_SOCKET_LIBEVENT_H_
diff --git a/chromium/net/socket/tcp_server_socket_unittest.cc b/chromium/net/socket/tcp_server_socket_unittest.cc
new file mode 100644
index 00000000000..fd81e550d08
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_server_socket.h"
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket/tcp_client_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+const int kListenBacklog = 5;
+
+class TCPServerSocketTest : public PlatformTest {
+ protected:
+ TCPServerSocketTest()
+ : socket_(NULL, NetLog::Source()) {
+ }
+
+ void SetUpIPv4() {
+ IPEndPoint address;
+ ParseAddress("127.0.0.1", 0, &address);
+ ASSERT_EQ(OK, socket_.Listen(address, kListenBacklog));
+ ASSERT_EQ(OK, socket_.GetLocalAddress(&local_address_));
+ }
+
+ void SetUpIPv6(bool* success) {
+ *success = false;
+ IPEndPoint address;
+ ParseAddress("::1", 0, &address);
+ if (socket_.Listen(address, kListenBacklog) != 0) {
+ LOG(ERROR) << "Failed to listen on ::1 - probably because IPv6 is "
+ "disabled. Skipping the test";
+ return;
+ }
+ ASSERT_EQ(OK, socket_.GetLocalAddress(&local_address_));
+ *success = true;
+ }
+
+ void ParseAddress(std::string ip_str, int port, IPEndPoint* address) {
+ IPAddressNumber ip_number;
+ bool rv = ParseIPLiteralToNumber(ip_str, &ip_number);
+ if (!rv)
+ return;
+ *address = IPEndPoint(ip_number, port);
+ }
+
+ static IPEndPoint GetPeerAddress(StreamSocket* socket) {
+ IPEndPoint address;
+ EXPECT_EQ(OK, socket->GetPeerAddress(&address));
+ return address;
+ }
+
+ AddressList local_address_list() const {
+ return AddressList(local_address_);
+ }
+
+ TCPServerSocket socket_;
+ IPEndPoint local_address_;
+};
+
+TEST_F(TCPServerSocketTest, Accept) {
+ ASSERT_NO_FATAL_FAILURE(SetUpIPv4());
+
+ TestCompletionCallback connect_callback;
+ TCPClientSocket connecting_socket(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket.Connect(connect_callback.callback());
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+ int result = socket_.Accept(&accepted_socket, accept_callback.callback());
+ if (result == ERR_IO_PENDING)
+ result = accept_callback.WaitForResult();
+ ASSERT_EQ(OK, result);
+
+ ASSERT_TRUE(accepted_socket.get() != NULL);
+
+ // Both sockets should be on the loopback network interface.
+ EXPECT_EQ(GetPeerAddress(accepted_socket.get()).address(),
+ local_address_.address());
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+}
+
+// Test Accept() callback.
+TEST_F(TCPServerSocketTest, AcceptAsync) {
+ ASSERT_NO_FATAL_FAILURE(SetUpIPv4());
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ socket_.Accept(&accepted_socket, accept_callback.callback()));
+
+ TestCompletionCallback connect_callback;
+ TCPClientSocket connecting_socket(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket.Connect(connect_callback.callback());
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+ EXPECT_EQ(OK, accept_callback.WaitForResult());
+
+ EXPECT_TRUE(accepted_socket != NULL);
+
+ // Both sockets should be on the loopback network interface.
+ EXPECT_EQ(GetPeerAddress(accepted_socket.get()).address(),
+ local_address_.address());
+}
+
+// Accept two connections simultaneously.
+TEST_F(TCPServerSocketTest, Accept2Connections) {
+ ASSERT_NO_FATAL_FAILURE(SetUpIPv4());
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+
+ ASSERT_EQ(ERR_IO_PENDING,
+ socket_.Accept(&accepted_socket, accept_callback.callback()));
+
+ TestCompletionCallback connect_callback;
+ TCPClientSocket connecting_socket(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket.Connect(connect_callback.callback());
+
+ TestCompletionCallback connect_callback2;
+ TCPClientSocket connecting_socket2(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket2.Connect(connect_callback2.callback());
+
+ EXPECT_EQ(OK, accept_callback.WaitForResult());
+
+ TestCompletionCallback accept_callback2;
+ scoped_ptr<StreamSocket> accepted_socket2;
+ int result = socket_.Accept(&accepted_socket2, accept_callback2.callback());
+ if (result == ERR_IO_PENDING)
+ result = accept_callback2.WaitForResult();
+ ASSERT_EQ(OK, result);
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+
+ EXPECT_TRUE(accepted_socket != NULL);
+ EXPECT_TRUE(accepted_socket2 != NULL);
+ EXPECT_NE(accepted_socket.get(), accepted_socket2.get());
+
+ EXPECT_EQ(GetPeerAddress(accepted_socket.get()).address(),
+ local_address_.address());
+ EXPECT_EQ(GetPeerAddress(accepted_socket2.get()).address(),
+ local_address_.address());
+}
+
+TEST_F(TCPServerSocketTest, AcceptIPv6) {
+ bool initialized = false;
+ ASSERT_NO_FATAL_FAILURE(SetUpIPv6(&initialized));
+ if (!initialized)
+ return;
+
+ TestCompletionCallback connect_callback;
+ TCPClientSocket connecting_socket(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket.Connect(connect_callback.callback());
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+ int result = socket_.Accept(&accepted_socket, accept_callback.callback());
+ if (result == ERR_IO_PENDING)
+ result = accept_callback.WaitForResult();
+ ASSERT_EQ(OK, result);
+
+ ASSERT_TRUE(accepted_socket.get() != NULL);
+
+ // Both sockets should be on the loopback network interface.
+ EXPECT_EQ(GetPeerAddress(accepted_socket.get()).address(),
+ local_address_.address());
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+}
+
+TEST_F(TCPServerSocketTest, AcceptIO) {
+ ASSERT_NO_FATAL_FAILURE(SetUpIPv4());
+
+ TestCompletionCallback connect_callback;
+ TCPClientSocket connecting_socket(local_address_list(),
+ NULL, NetLog::Source());
+ connecting_socket.Connect(connect_callback.callback());
+
+ TestCompletionCallback accept_callback;
+ scoped_ptr<StreamSocket> accepted_socket;
+ int result = socket_.Accept(&accepted_socket, accept_callback.callback());
+ ASSERT_EQ(OK, accept_callback.GetResult(result));
+
+ ASSERT_TRUE(accepted_socket.get() != NULL);
+
+ // Both sockets should be on the loopback network interface.
+ EXPECT_EQ(GetPeerAddress(accepted_socket.get()).address(),
+ local_address_.address());
+
+ EXPECT_EQ(OK, connect_callback.WaitForResult());
+
+ const std::string message("test message");
+ std::vector<char> buffer(message.size());
+
+ size_t bytes_written = 0;
+ while (bytes_written < message.size()) {
+ scoped_refptr<net::IOBufferWithSize> write_buffer(
+ new net::IOBufferWithSize(message.size() - bytes_written));
+ memmove(write_buffer->data(), message.data(), message.size());
+
+ TestCompletionCallback write_callback;
+ int write_result = accepted_socket->Write(
+ write_buffer.get(), write_buffer->size(), write_callback.callback());
+ write_result = write_callback.GetResult(write_result);
+ ASSERT_TRUE(write_result >= 0);
+ ASSERT_TRUE(bytes_written + write_result <= message.size());
+ bytes_written += write_result;
+ }
+
+ size_t bytes_read = 0;
+ while (bytes_read < message.size()) {
+ scoped_refptr<net::IOBufferWithSize> read_buffer(
+ new net::IOBufferWithSize(message.size() - bytes_read));
+ TestCompletionCallback read_callback;
+ int read_result = connecting_socket.Read(
+ read_buffer.get(), read_buffer->size(), read_callback.callback());
+ read_result = read_callback.GetResult(read_result);
+ ASSERT_TRUE(read_result >= 0);
+ ASSERT_TRUE(bytes_read + read_result <= message.size());
+ memmove(&buffer[bytes_read], read_buffer->data(), read_result);
+ bytes_read += read_result;
+ }
+
+ std::string received_message(buffer.begin(), buffer.end());
+ ASSERT_EQ(message, received_message);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_server_socket_win.cc b/chromium/net/socket/tcp_server_socket_win.cc
new file mode 100644
index 00000000000..0ac77be5e81
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket_win.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_server_socket_win.h"
+
+#include <mstcpip.h>
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/winsock_init.h"
+#include "net/base/winsock_util.h"
+#include "net/socket/socket_net_log_params.h"
+#include "net/socket/tcp_client_socket.h"
+
+namespace net {
+
+TCPServerSocketWin::TCPServerSocketWin(net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(INVALID_SOCKET),
+ socket_event_(WSA_INVALID_EVENT),
+ accept_socket_(NULL),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+ EnsureWinsockInit();
+}
+
+TCPServerSocketWin::~TCPServerSocketWin() {
+ Close();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+int TCPServerSocketWin::Listen(const IPEndPoint& address, int backlog) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_GT(backlog, 0);
+ DCHECK_EQ(socket_, INVALID_SOCKET);
+ DCHECK_EQ(socket_event_, WSA_INVALID_EVENT);
+
+ socket_event_ = WSACreateEvent();
+ if (socket_event_ == WSA_INVALID_EVENT) {
+ PLOG(ERROR) << "WSACreateEvent()";
+ return ERR_FAILED;
+ }
+
+ socket_ = socket(address.GetSockAddrFamily(), SOCK_STREAM, IPPROTO_TCP);
+ if (socket_ == INVALID_SOCKET) {
+ PLOG(ERROR) << "socket() returned an error";
+ return MapSystemError(WSAGetLastError());
+ }
+
+ if (SetNonBlocking(socket_)) {
+ int result = MapSystemError(WSAGetLastError());
+ Close();
+ return result;
+ }
+
+ int result = SetSocketOptions();
+ if (result != OK) {
+ Close();
+ return result;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len)) {
+ Close();
+ return ERR_ADDRESS_INVALID;
+ }
+
+ result = bind(socket_, storage.addr, storage.addr_len);
+ if (result < 0) {
+ PLOG(ERROR) << "bind() returned an error";
+ result = MapSystemError(WSAGetLastError());
+ Close();
+ return result;
+ }
+
+ result = listen(socket_, backlog);
+ if (result < 0) {
+ PLOG(ERROR) << "listen() returned an error";
+ result = MapSystemError(WSAGetLastError());
+ Close();
+ return result;
+ }
+
+ return OK;
+}
+
+int TCPServerSocketWin::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(WSAGetLastError());
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+
+ return OK;
+}
+
+int TCPServerSocketWin::Accept(
+ scoped_ptr<StreamSocket>* socket, const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(socket);
+ DCHECK(!callback.is_null());
+ DCHECK(accept_callback_.is_null());
+
+ net_log_.BeginEvent(NetLog::TYPE_TCP_ACCEPT);
+
+ int result = AcceptInternal(socket);
+
+ if (result == ERR_IO_PENDING) {
+ // Start watching
+ WSAEventSelect(socket_, socket_event_, FD_ACCEPT);
+ accept_watcher_.StartWatching(socket_event_, this);
+
+ accept_socket_ = socket;
+ accept_callback_ = callback;
+ }
+
+ return result;
+}
+
+int TCPServerSocketWin::SetSocketOptions() {
+ // On Windows, a bound end point can be hijacked by another process by
+ // setting SO_REUSEADDR. Therefore a Windows-only option SO_EXCLUSIVEADDRUSE
+ // was introduced in Windows NT 4.0 SP4. If the socket that is bound to the
+ // end point has SO_EXCLUSIVEADDRUSE enabled, it is not possible for another
+ // socket to forcibly bind to the end point until the end point is unbound.
+ // It is recommend that all server applications must use SO_EXCLUSIVEADDRUSE.
+ // MSDN: http://goo.gl/M6fjQ.
+ //
+ // Unlike on *nix, on Windows a TCP server socket can always bind to an end
+ // point in TIME_WAIT state without setting SO_REUSEADDR, therefore it is not
+ // needed here.
+ //
+ // SO_EXCLUSIVEADDRUSE will prevent a TCP client socket from binding to an end
+ // point in TIME_WAIT status. It does not have this effect for a TCP server
+ // socket.
+
+ BOOL true_value = 1;
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+ reinterpret_cast<const char*>(&true_value),
+ sizeof(true_value));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+}
+
+int TCPServerSocketWin::AcceptInternal(scoped_ptr<StreamSocket>* socket) {
+ SockaddrStorage storage;
+ int new_socket = accept(socket_, storage.addr, &storage.addr_len);
+ if (new_socket < 0) {
+ int net_error = MapSystemError(WSAGetLastError());
+ if (net_error != ERR_IO_PENDING)
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, net_error);
+ return net_error;
+ }
+
+ IPEndPoint address;
+ if (!address.FromSockAddr(storage.addr, storage.addr_len)) {
+ NOTREACHED();
+ if (closesocket(new_socket) < 0)
+ PLOG(ERROR) << "closesocket";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, ERR_FAILED);
+ return ERR_FAILED;
+ }
+ scoped_ptr<TCPClientSocket> tcp_socket(new TCPClientSocket(
+ AddressList(address),
+ net_log_.net_log(), net_log_.source()));
+ int adopt_result = tcp_socket->AdoptSocket(new_socket);
+ if (adopt_result != OK) {
+ if (closesocket(new_socket) < 0)
+ PLOG(ERROR) << "closesocket";
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_TCP_ACCEPT, adopt_result);
+ return adopt_result;
+ }
+ socket->reset(tcp_socket.release());
+ net_log_.EndEvent(NetLog::TYPE_TCP_ACCEPT,
+ CreateNetLogIPEndPointCallback(&address));
+ return OK;
+}
+
+void TCPServerSocketWin::Close() {
+ if (socket_ != INVALID_SOCKET) {
+ if (closesocket(socket_) < 0)
+ PLOG(ERROR) << "closesocket";
+ socket_ = INVALID_SOCKET;
+ }
+
+ if (socket_event_) {
+ WSACloseEvent(socket_event_);
+ socket_event_ = WSA_INVALID_EVENT;
+ }
+}
+
+void TCPServerSocketWin::OnObjectSignaled(HANDLE object) {
+ WSANETWORKEVENTS ev;
+ if (WSAEnumNetworkEvents(socket_, socket_event_, &ev) == SOCKET_ERROR) {
+ PLOG(ERROR) << "WSAEnumNetworkEvents()";
+ return;
+ }
+
+ if (ev.lNetworkEvents & FD_ACCEPT) {
+ int result = AcceptInternal(accept_socket_);
+ if (result != ERR_IO_PENDING) {
+ accept_socket_ = NULL;
+ CompletionCallback callback = accept_callback_;
+ accept_callback_.Reset();
+ callback.Run(result);
+ }
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/socket/tcp_server_socket_win.h b/chromium/net/socket/tcp_server_socket_win.h
new file mode 100644
index 00000000000..5a1d378ad9b
--- /dev/null
+++ b/chromium/net/socket/tcp_server_socket_win.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 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_SOCKET_TCP_SERVER_SOCKET_WIN_H_
+#define NET_SOCKET_TCP_SERVER_SOCKET_WIN_H_
+
+#include <winsock2.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/win/object_watcher.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/socket/server_socket.h"
+
+namespace net {
+
+class IPEndPoint;
+
+class NET_EXPORT_PRIVATE TCPServerSocketWin
+ : public ServerSocket,
+ NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public base::win::ObjectWatcher::Delegate {
+ public:
+ TCPServerSocketWin(net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ ~TCPServerSocketWin();
+
+ // net::ServerSocket implementation.
+ virtual int Listen(const net::IPEndPoint& address, int backlog) OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int Accept(scoped_ptr<StreamSocket>* socket,
+ const CompletionCallback& callback) OVERRIDE;
+
+ // base::ObjectWatcher::Delegate implementation.
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ int SetSocketOptions();
+ int AcceptInternal(scoped_ptr<StreamSocket>* socket);
+ void Close();
+
+ SOCKET socket_;
+ HANDLE socket_event_;
+
+ base::win::ObjectWatcher accept_watcher_;
+
+ scoped_ptr<StreamSocket>* accept_socket_;
+ CompletionCallback accept_callback_;
+
+ BoundNetLog net_log_;
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_TCP_SERVER_SOCKET_WIN_H_
diff --git a/chromium/net/socket/transport_client_socket_pool.cc b/chromium/net/socket/transport_client_socket_pool.cc
new file mode 100644
index 00000000000..6d0afac59fb
--- /dev/null
+++ b/chromium/net/socket/transport_client_socket_pool.cc
@@ -0,0 +1,477 @@
+// Copyright (c) 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.
+
+#include "net/socket/transport_client_socket_pool.h"
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/socket_net_log_params.h"
+#include "net/socket/tcp_client_socket.h"
+
+using base::TimeDelta;
+
+namespace net {
+
+// TODO(willchan): Base this off RTT instead of statically setting it. Note we
+// choose a timeout that is different from the backup connect job timer so they
+// don't synchronize.
+const int TransportConnectJob::kIPv6FallbackTimerInMs = 300;
+
+namespace {
+
+// Returns true iff all addresses in |list| are in the IPv6 family.
+bool AddressListOnlyContainsIPv6(const AddressList& list) {
+ DCHECK(!list.empty());
+ for (AddressList::const_iterator iter = list.begin(); iter != list.end();
+ ++iter) {
+ if (iter->GetFamily() != ADDRESS_FAMILY_IPV6)
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+TransportSocketParams::TransportSocketParams(
+ const HostPortPair& host_port_pair,
+ RequestPriority priority,
+ bool disable_resolver_cache,
+ bool ignore_limits,
+ const OnHostResolutionCallback& host_resolution_callback)
+ : destination_(host_port_pair),
+ ignore_limits_(ignore_limits),
+ host_resolution_callback_(host_resolution_callback) {
+ Initialize(priority, disable_resolver_cache);
+}
+
+TransportSocketParams::~TransportSocketParams() {}
+
+void TransportSocketParams::Initialize(RequestPriority priority,
+ bool disable_resolver_cache) {
+ destination_.set_priority(priority);
+ if (disable_resolver_cache)
+ destination_.set_allow_cached_response(false);
+}
+
+// TransportConnectJobs will time out after this many seconds. Note this is
+// the total time, including both host resolution and TCP connect() times.
+//
+// TODO(eroman): The use of this constant needs to be re-evaluated. The time
+// needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since
+// the address list may contain many alternatives, and most of those may
+// timeout. Even worse, the per-connect timeout threshold varies greatly
+// between systems (anywhere from 20 seconds to 190 seconds).
+// See comment #12 at http://crbug.com/23364 for specifics.
+static const int kTransportConnectJobTimeoutInSeconds = 240; // 4 minutes.
+
+TransportConnectJob::TransportConnectJob(
+ const std::string& group_name,
+ const scoped_refptr<TransportSocketParams>& params,
+ base::TimeDelta timeout_duration,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log)
+ : ConnectJob(group_name, timeout_duration, delegate,
+ BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
+ params_(params),
+ client_socket_factory_(client_socket_factory),
+ resolver_(host_resolver),
+ next_state_(STATE_NONE) {
+}
+
+TransportConnectJob::~TransportConnectJob() {
+ // We don't worry about cancelling the host resolution and TCP connect, since
+ // ~SingleRequestHostResolver and ~StreamSocket will take care of it.
+}
+
+LoadState TransportConnectJob::GetLoadState() const {
+ switch (next_state_) {
+ case STATE_RESOLVE_HOST:
+ case STATE_RESOLVE_HOST_COMPLETE:
+ return LOAD_STATE_RESOLVING_HOST;
+ case STATE_TRANSPORT_CONNECT:
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ return LOAD_STATE_CONNECTING;
+ default:
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+ }
+}
+
+// static
+void TransportConnectJob::MakeAddressListStartWithIPv4(AddressList* list) {
+ for (AddressList::iterator i = list->begin(); i != list->end(); ++i) {
+ if (i->GetFamily() == ADDRESS_FAMILY_IPV4) {
+ std::rotate(list->begin(), i, list->end());
+ break;
+ }
+ }
+}
+
+void TransportConnectJob::OnIOComplete(int result) {
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING)
+ NotifyDelegateOfCompletion(rv); // Deletes |this|
+}
+
+int TransportConnectJob::DoLoop(int result) {
+ DCHECK_NE(next_state_, STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_RESOLVE_HOST:
+ DCHECK_EQ(OK, rv);
+ rv = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ rv = DoResolveHostComplete(rv);
+ break;
+ case STATE_TRANSPORT_CONNECT:
+ DCHECK_EQ(OK, rv);
+ rv = DoTransportConnect();
+ break;
+ case STATE_TRANSPORT_CONNECT_COMPLETE:
+ rv = DoTransportConnectComplete(rv);
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ return rv;
+}
+
+int TransportConnectJob::DoResolveHost() {
+ next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+ connect_timing_.dns_start = base::TimeTicks::Now();
+
+ return resolver_.Resolve(
+ params_->destination(), &addresses_,
+ base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)),
+ net_log());
+}
+
+int TransportConnectJob::DoResolveHostComplete(int result) {
+ connect_timing_.dns_end = base::TimeTicks::Now();
+ // Overwrite connection start time, since for connections that do not go
+ // through proxies, |connect_start| should not include dns lookup time.
+ connect_timing_.connect_start = connect_timing_.dns_end;
+
+ if (result == OK) {
+ // Invoke callback, and abort if it fails.
+ if (!params_->host_resolution_callback().is_null())
+ result = params_->host_resolution_callback().Run(addresses_, net_log());
+
+ if (result == OK)
+ next_state_ = STATE_TRANSPORT_CONNECT;
+ }
+ return result;
+}
+
+int TransportConnectJob::DoTransportConnect() {
+ next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
+ transport_socket_ = client_socket_factory_->CreateTransportClientSocket(
+ addresses_, net_log().net_log(), net_log().source());
+ int rv = transport_socket_->Connect(
+ base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)));
+ if (rv == ERR_IO_PENDING &&
+ addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV6 &&
+ !AddressListOnlyContainsIPv6(addresses_)) {
+ fallback_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs),
+ this, &TransportConnectJob::DoIPv6FallbackTransportConnect);
+ }
+ return rv;
+}
+
+int TransportConnectJob::DoTransportConnectComplete(int result) {
+ if (result == OK) {
+ bool is_ipv4 = addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV4;
+ DCHECK(!connect_timing_.connect_start.is_null());
+ DCHECK(!connect_timing_.dns_start.is_null());
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta total_duration = now - connect_timing_.dns_start;
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.DNS_Resolution_And_TCP_Connection_Latency2",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ base::TimeDelta connect_duration = now - connect_timing_.connect_start;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ if (is_ipv4) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ } else {
+ if (AddressListOnlyContainsIPv6(addresses_)) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+ }
+ SetSocket(transport_socket_.Pass());
+ fallback_timer_.Stop();
+ } else {
+ // Be a bit paranoid and kill off the fallback members to prevent reuse.
+ fallback_transport_socket_.reset();
+ fallback_addresses_.reset();
+ }
+
+ return result;
+}
+
+void TransportConnectJob::DoIPv6FallbackTransportConnect() {
+ // The timer should only fire while we're waiting for the main connect to
+ // succeed.
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(!fallback_transport_socket_.get());
+ DCHECK(!fallback_addresses_.get());
+
+ fallback_addresses_.reset(new AddressList(addresses_));
+ MakeAddressListStartWithIPv4(fallback_addresses_.get());
+ fallback_transport_socket_ =
+ client_socket_factory_->CreateTransportClientSocket(
+ *fallback_addresses_, net_log().net_log(), net_log().source());
+ fallback_connect_start_time_ = base::TimeTicks::Now();
+ int rv = fallback_transport_socket_->Connect(
+ base::Bind(
+ &TransportConnectJob::DoIPv6FallbackTransportConnectComplete,
+ base::Unretained(this)));
+ if (rv != ERR_IO_PENDING)
+ DoIPv6FallbackTransportConnectComplete(rv);
+}
+
+void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) {
+ // This should only happen when we're waiting for the main connect to succeed.
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(fallback_transport_socket_.get());
+ DCHECK(fallback_addresses_.get());
+
+ if (result == OK) {
+ DCHECK(!fallback_connect_start_time_.is_null());
+ DCHECK(!connect_timing_.dns_start.is_null());
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta total_duration = now - connect_timing_.dns_start;
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Net.DNS_Resolution_And_TCP_Connection_Latency2",
+ total_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ base::TimeDelta connect_duration = now - fallback_connect_start_time_;
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ SetSocket(fallback_transport_socket_.Pass());
+ next_state_ = STATE_NONE;
+ transport_socket_.reset();
+ } else {
+ // Be a bit paranoid and kill off the fallback members to prevent reuse.
+ fallback_transport_socket_.reset();
+ fallback_addresses_.reset();
+ }
+ NotifyDelegateOfCompletion(result); // Deletes |this|
+}
+
+int TransportConnectJob::ConnectInternal() {
+ next_state_ = STATE_RESOLVE_HOST;
+ return DoLoop(OK);
+}
+
+scoped_ptr<ConnectJob>
+ TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const {
+ return scoped_ptr<ConnectJob>(
+ new TransportConnectJob(group_name,
+ request.params(),
+ ConnectionTimeout(),
+ client_socket_factory_,
+ host_resolver_,
+ delegate,
+ net_log_));
+}
+
+base::TimeDelta
+ TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
+ const {
+ return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
+}
+
+TransportClientSocketPool::TransportClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ NetLog* net_log)
+ : base_(max_sockets, max_sockets_per_group, histograms,
+ ClientSocketPool::unused_idle_socket_timeout(),
+ ClientSocketPool::used_idle_socket_timeout(),
+ new TransportConnectJobFactory(client_socket_factory,
+ host_resolver, net_log)) {
+ base_.EnableConnectBackupJobs();
+}
+
+TransportClientSocketPool::~TransportClientSocketPool() {}
+
+int TransportClientSocketPool::RequestSocket(
+ const std::string& group_name,
+ const void* params,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<TransportSocketParams>* casted_params =
+ static_cast<const scoped_refptr<TransportSocketParams>*>(params);
+
+ if (net_log.IsLoggingAllEvents()) {
+ // TODO(eroman): Split out the host and port parameters.
+ net_log.AddEvent(
+ NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET,
+ CreateNetLogHostPortPairCallback(
+ &casted_params->get()->destination().host_port_pair()));
+ }
+
+ return base_.RequestSocket(group_name, *casted_params, priority, handle,
+ callback, net_log);
+}
+
+void TransportClientSocketPool::RequestSockets(
+ const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) {
+ const scoped_refptr<TransportSocketParams>* casted_params =
+ static_cast<const scoped_refptr<TransportSocketParams>*>(params);
+
+ if (net_log.IsLoggingAllEvents()) {
+ // TODO(eroman): Split out the host and port parameters.
+ net_log.AddEvent(
+ NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS,
+ CreateNetLogHostPortPairCallback(
+ &casted_params->get()->destination().host_port_pair()));
+ }
+
+ base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
+}
+
+void TransportClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ ClientSocketHandle* handle) {
+ base_.CancelRequest(group_name, handle);
+}
+
+void TransportClientSocketPool::ReleaseSocket(
+ const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) {
+ base_.ReleaseSocket(group_name, socket.Pass(), id);
+}
+
+void TransportClientSocketPool::FlushWithError(int error) {
+ base_.FlushWithError(error);
+}
+
+bool TransportClientSocketPool::IsStalled() const {
+ return base_.IsStalled();
+}
+
+void TransportClientSocketPool::CloseIdleSockets() {
+ base_.CloseIdleSockets();
+}
+
+int TransportClientSocketPool::IdleSocketCount() const {
+ return base_.idle_socket_count();
+}
+
+int TransportClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_.IdleSocketCountInGroup(group_name);
+}
+
+LoadState TransportClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_.GetLoadState(group_name, handle);
+}
+
+void TransportClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
+ base_.AddLayeredPool(layered_pool);
+}
+
+void TransportClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
+ base_.RemoveLayeredPool(layered_pool);
+}
+
+base::DictionaryValue* TransportClientSocketPool::GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const {
+ return base_.GetInfoAsValue(name, type);
+}
+
+base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
+ return base_.ConnectionTimeout();
+}
+
+ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
+ return base_.histograms();
+}
+
+} // namespace net
diff --git a/chromium/net/socket/transport_client_socket_pool.h b/chromium/net/socket/transport_client_socket_pool.h
new file mode 100644
index 00000000000..f07dc1f5675
--- /dev/null
+++ b/chromium/net/socket/transport_client_socket_pool.h
@@ -0,0 +1,221 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_
+#define NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "net/base/host_port_pair.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/single_request_host_resolver.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/client_socket_pool_histograms.h"
+
+namespace net {
+
+class ClientSocketFactory;
+
+typedef base::Callback<int(const AddressList&, const BoundNetLog& net_log)>
+OnHostResolutionCallback;
+
+class NET_EXPORT_PRIVATE TransportSocketParams
+ : public base::RefCounted<TransportSocketParams> {
+ public:
+ // |host_resolution_callback| will be invoked after the the hostname is
+ // resolved. If |host_resolution_callback| does not return OK, then the
+ // connection will be aborted with that value.
+ TransportSocketParams(
+ const HostPortPair& host_port_pair,
+ RequestPriority priority,
+ bool disable_resolver_cache,
+ bool ignore_limits,
+ const OnHostResolutionCallback& host_resolution_callback);
+
+ const HostResolver::RequestInfo& destination() const { return destination_; }
+ bool ignore_limits() const { return ignore_limits_; }
+ const OnHostResolutionCallback& host_resolution_callback() const {
+ return host_resolution_callback_;
+ }
+
+ private:
+ friend class base::RefCounted<TransportSocketParams>;
+ ~TransportSocketParams();
+
+ void Initialize(RequestPriority priority, bool disable_resolver_cache);
+
+ HostResolver::RequestInfo destination_;
+ bool ignore_limits_;
+ const OnHostResolutionCallback host_resolution_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportSocketParams);
+};
+
+// TransportConnectJob handles the host resolution necessary for socket creation
+// and the transport (likely TCP) connect. TransportConnectJob also has fallback
+// logic for IPv6 connect() timeouts (which may happen due to networks / routers
+// with broken IPv6 support). Those timeouts take 20s, so rather than make the
+// user wait 20s for the timeout to fire, we use a fallback timer
+// (kIPv6FallbackTimerInMs) and start a connect() to a IPv4 address if the timer
+// fires. Then we race the IPv4 connect() against the IPv6 connect() (which has
+// a headstart) and return the one that completes first to the socket pool.
+class NET_EXPORT_PRIVATE TransportConnectJob : public ConnectJob {
+ public:
+ TransportConnectJob(const std::string& group_name,
+ const scoped_refptr<TransportSocketParams>& params,
+ base::TimeDelta timeout_duration,
+ ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ Delegate* delegate,
+ NetLog* net_log);
+ virtual ~TransportConnectJob();
+
+ // ConnectJob methods.
+ virtual LoadState GetLoadState() const OVERRIDE;
+
+ // Rolls |addrlist| forward until the first IPv4 address, if any.
+ // WARNING: this method should only be used to implement the prefer-IPv4 hack.
+ static void MakeAddressListStartWithIPv4(AddressList* addrlist);
+
+ static const int kIPv6FallbackTimerInMs;
+
+ private:
+ enum State {
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_TRANSPORT_CONNECT,
+ STATE_TRANSPORT_CONNECT_COMPLETE,
+ STATE_NONE,
+ };
+
+ void OnIOComplete(int result);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ int DoResolveHost();
+ int DoResolveHostComplete(int result);
+ int DoTransportConnect();
+ int DoTransportConnectComplete(int result);
+
+ // Not part of the state machine.
+ void DoIPv6FallbackTransportConnect();
+ void DoIPv6FallbackTransportConnectComplete(int result);
+
+ // Begins the host resolution and the TCP connect. Returns OK on success
+ // and ERR_IO_PENDING if it cannot immediately service the request.
+ // Otherwise, it returns a net error code.
+ virtual int ConnectInternal() OVERRIDE;
+
+ scoped_refptr<TransportSocketParams> params_;
+ ClientSocketFactory* const client_socket_factory_;
+ SingleRequestHostResolver resolver_;
+ AddressList addresses_;
+ State next_state_;
+
+ scoped_ptr<StreamSocket> transport_socket_;
+
+ scoped_ptr<StreamSocket> fallback_transport_socket_;
+ scoped_ptr<AddressList> fallback_addresses_;
+ base::TimeTicks fallback_connect_start_time_;
+ base::OneShotTimer<TransportConnectJob> fallback_timer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportConnectJob);
+};
+
+class NET_EXPORT_PRIVATE TransportClientSocketPool : public ClientSocketPool {
+ public:
+ TransportClientSocketPool(
+ int max_sockets,
+ int max_sockets_per_group,
+ ClientSocketPoolHistograms* histograms,
+ HostResolver* host_resolver,
+ ClientSocketFactory* client_socket_factory,
+ NetLog* net_log);
+
+ virtual ~TransportClientSocketPool();
+
+ // ClientSocketPool implementation.
+ virtual int RequestSocket(const std::string& group_name,
+ const void* resolve_info,
+ RequestPriority priority,
+ ClientSocketHandle* handle,
+ const CompletionCallback& callback,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void RequestSockets(const std::string& group_name,
+ const void* params,
+ int num_sockets,
+ const BoundNetLog& net_log) OVERRIDE;
+ virtual void CancelRequest(const std::string& group_name,
+ ClientSocketHandle* handle) OVERRIDE;
+ virtual void ReleaseSocket(const std::string& group_name,
+ scoped_ptr<StreamSocket> socket,
+ int id) OVERRIDE;
+ virtual void FlushWithError(int error) OVERRIDE;
+ virtual bool IsStalled() const OVERRIDE;
+ virtual void CloseIdleSockets() OVERRIDE;
+ virtual int IdleSocketCount() const OVERRIDE;
+ virtual int IdleSocketCountInGroup(
+ const std::string& group_name) const OVERRIDE;
+ virtual LoadState GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const OVERRIDE;
+ virtual void AddLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+ virtual void RemoveLayeredPool(LayeredPool* layered_pool) OVERRIDE;
+ virtual base::DictionaryValue* GetInfoAsValue(
+ const std::string& name,
+ const std::string& type,
+ bool include_nested_pools) const OVERRIDE;
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+ virtual ClientSocketPoolHistograms* histograms() const OVERRIDE;
+
+ private:
+ typedef ClientSocketPoolBase<TransportSocketParams> PoolBase;
+
+ class TransportConnectJobFactory
+ : public PoolBase::ConnectJobFactory {
+ public:
+ TransportConnectJobFactory(ClientSocketFactory* client_socket_factory,
+ HostResolver* host_resolver,
+ NetLog* net_log)
+ : client_socket_factory_(client_socket_factory),
+ host_resolver_(host_resolver),
+ net_log_(net_log) {}
+
+ virtual ~TransportConnectJobFactory() {}
+
+ // ClientSocketPoolBase::ConnectJobFactory methods.
+
+ virtual scoped_ptr<ConnectJob> NewConnectJob(
+ const std::string& group_name,
+ const PoolBase::Request& request,
+ ConnectJob::Delegate* delegate) const OVERRIDE;
+
+ virtual base::TimeDelta ConnectionTimeout() const OVERRIDE;
+
+ private:
+ ClientSocketFactory* const client_socket_factory_;
+ HostResolver* const host_resolver_;
+ NetLog* net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportConnectJobFactory);
+ };
+
+ PoolBase base_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransportClientSocketPool);
+};
+
+REGISTER_SOCKET_PARAMS_FOR_POOL(TransportClientSocketPool,
+ TransportSocketParams);
+
+} // namespace net
+
+#endif // NET_SOCKET_TRANSPORT_CLIENT_SOCKET_POOL_H_
diff --git a/chromium/net/socket/transport_client_socket_pool_unittest.cc b/chromium/net/socket/transport_client_socket_pool_unittest.cc
new file mode 100644
index 00000000000..c607a38b78a
--- /dev/null
+++ b/chromium/net/socket/transport_client_socket_pool_unittest.cc
@@ -0,0 +1,1355 @@
+// Copyright (c) 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.
+
+#include "net/socket/transport_client_socket_pool.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_histograms.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+using internal::ClientSocketPoolBaseHelper;
+
+namespace {
+
+const int kMaxSockets = 32;
+const int kMaxSocketsPerGroup = 6;
+const net::RequestPriority kDefaultPriority = LOW;
+
+// Make sure |handle| sets load times correctly when it has been assigned a
+// reused socket.
+void TestLoadTimingInfoConnectedReused(const ClientSocketHandle& handle) {
+ LoadTimingInfo load_timing_info;
+ // Only pass true in as |is_reused|, as in general, HttpStream types should
+ // have stricter concepts of reuse than socket pools.
+ EXPECT_TRUE(handle.GetLoadTimingInfo(true, &load_timing_info));
+
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+// Make sure |handle| sets load times correctly when it has been assigned a
+// fresh socket. Also runs TestLoadTimingInfoConnectedReused, since the owner
+// of a connection where |is_reused| is false may consider the connection
+// reused.
+void TestLoadTimingInfoConnectedNotReused(const ClientSocketHandle& handle) {
+ EXPECT_FALSE(handle.is_reused());
+
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+
+ TestLoadTimingInfoConnectedReused(handle);
+}
+
+void SetIPv4Address(IPEndPoint* address) {
+ IPAddressNumber number;
+ CHECK(ParseIPLiteralToNumber("1.1.1.1", &number));
+ *address = IPEndPoint(number, 80);
+}
+
+void SetIPv6Address(IPEndPoint* address) {
+ IPAddressNumber number;
+ CHECK(ParseIPLiteralToNumber("1:abcd::3:4:ff", &number));
+ *address = IPEndPoint(number, 80);
+}
+
+class MockClientSocket : public StreamSocket {
+ public:
+ MockClientSocket(const AddressList& addrlist, net::NetLog* net_log)
+ : connected_(false),
+ addrlist_(addrlist),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ }
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ connected_ = true;
+ return OK;
+ }
+ virtual void Disconnect() OVERRIDE {
+ connected_ = false;
+ }
+ virtual bool IsConnected() const OVERRIDE {
+ return connected_;
+ }
+ virtual bool IsConnectedAndIdle() const OVERRIDE {
+ return connected_;
+ }
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ if (!connected_)
+ return ERR_SOCKET_NOT_CONNECTED;
+ if (addrlist_.front().GetFamily() == ADDRESS_FAMILY_IPV4)
+ SetIPv4Address(address);
+ else
+ SetIPv6Address(address);
+ return OK;
+ }
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+ virtual bool WasEverUsed() const OVERRIDE { return false; }
+ virtual bool UsingTCPFastOpen() const OVERRIDE { return false; }
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return false;
+ }
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return false;
+ }
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE { return true; }
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE { return true; }
+
+ private:
+ bool connected_;
+ const AddressList addrlist_;
+ BoundNetLog net_log_;
+};
+
+class MockFailingClientSocket : public StreamSocket {
+ public:
+ MockFailingClientSocket(const AddressList& addrlist, net::NetLog* net_log)
+ : addrlist_(addrlist),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ }
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ return ERR_CONNECTION_FAILED;
+ }
+
+ virtual void Disconnect() OVERRIDE {}
+
+ virtual bool IsConnected() const OVERRIDE {
+ return false;
+ }
+ virtual bool IsConnectedAndIdle() const OVERRIDE {
+ return false;
+ }
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+ virtual bool WasEverUsed() const OVERRIDE { return false; }
+ virtual bool UsingTCPFastOpen() const OVERRIDE { return false; }
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return false;
+ }
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return false;
+ }
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE { return true; }
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE { return true; }
+
+ private:
+ const AddressList addrlist_;
+ BoundNetLog net_log_;
+};
+
+class MockPendingClientSocket : public StreamSocket {
+ public:
+ // |should_connect| indicates whether the socket should successfully complete
+ // or fail.
+ // |should_stall| indicates that this socket should never connect.
+ // |delay_ms| is the delay, in milliseconds, before simulating a connect.
+ MockPendingClientSocket(
+ const AddressList& addrlist,
+ bool should_connect,
+ bool should_stall,
+ base::TimeDelta delay,
+ net::NetLog* net_log)
+ : weak_factory_(this),
+ should_connect_(should_connect),
+ should_stall_(should_stall),
+ delay_(delay),
+ is_connected_(false),
+ addrlist_(addrlist),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SOCKET)) {
+ }
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockPendingClientSocket::DoCallback,
+ weak_factory_.GetWeakPtr(), callback),
+ delay_);
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Disconnect() OVERRIDE {}
+
+ virtual bool IsConnected() const OVERRIDE {
+ return is_connected_;
+ }
+ virtual bool IsConnectedAndIdle() const OVERRIDE {
+ return is_connected_;
+ }
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE {
+ return ERR_UNEXPECTED;
+ }
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE {
+ if (!is_connected_)
+ return ERR_SOCKET_NOT_CONNECTED;
+ if (addrlist_.front().GetFamily() == ADDRESS_FAMILY_IPV4)
+ SetIPv4Address(address);
+ else
+ SetIPv6Address(address);
+ return OK;
+ }
+ virtual const BoundNetLog& NetLog() const OVERRIDE {
+ return net_log_;
+ }
+
+ virtual void SetSubresourceSpeculation() OVERRIDE {}
+ virtual void SetOmniboxSpeculation() OVERRIDE {}
+ virtual bool WasEverUsed() const OVERRIDE { return false; }
+ virtual bool UsingTCPFastOpen() const OVERRIDE { return false; }
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ return false;
+ }
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ return false;
+ }
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_FAILED;
+ }
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE { return true; }
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE { return true; }
+
+ private:
+ void DoCallback(const CompletionCallback& callback) {
+ if (should_stall_)
+ return;
+
+ if (should_connect_) {
+ is_connected_ = true;
+ callback.Run(OK);
+ } else {
+ is_connected_ = false;
+ callback.Run(ERR_CONNECTION_FAILED);
+ }
+ }
+
+ base::WeakPtrFactory<MockPendingClientSocket> weak_factory_;
+ bool should_connect_;
+ bool should_stall_;
+ base::TimeDelta delay_;
+ bool is_connected_;
+ const AddressList addrlist_;
+ BoundNetLog net_log_;
+};
+
+class MockClientSocketFactory : public ClientSocketFactory {
+ public:
+ enum ClientSocketType {
+ MOCK_CLIENT_SOCKET,
+ MOCK_FAILING_CLIENT_SOCKET,
+ MOCK_PENDING_CLIENT_SOCKET,
+ MOCK_PENDING_FAILING_CLIENT_SOCKET,
+ // A delayed socket will pause before connecting through the message loop.
+ MOCK_DELAYED_CLIENT_SOCKET,
+ // A stalled socket that never connects at all.
+ MOCK_STALLED_CLIENT_SOCKET,
+ };
+
+ explicit MockClientSocketFactory(NetLog* net_log)
+ : net_log_(net_log), allocation_count_(0),
+ client_socket_type_(MOCK_CLIENT_SOCKET), client_socket_types_(NULL),
+ client_socket_index_(0), client_socket_index_max_(0),
+ delay_(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs)) {}
+
+ virtual scoped_ptr<DatagramClientSocket> CreateDatagramClientSocket(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ NetLog* net_log,
+ const NetLog::Source& source) OVERRIDE {
+ NOTREACHED();
+ return scoped_ptr<DatagramClientSocket>();
+ }
+
+ virtual scoped_ptr<StreamSocket> CreateTransportClientSocket(
+ const AddressList& addresses,
+ NetLog* /* net_log */,
+ const NetLog::Source& /* source */) OVERRIDE {
+ allocation_count_++;
+
+ ClientSocketType type = client_socket_type_;
+ if (client_socket_types_ &&
+ client_socket_index_ < client_socket_index_max_) {
+ type = client_socket_types_[client_socket_index_++];
+ }
+
+ switch (type) {
+ case MOCK_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockClientSocket(addresses, net_log_));
+ case MOCK_FAILING_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockFailingClientSocket(addresses, net_log_));
+ case MOCK_PENDING_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockPendingClientSocket(
+ addresses, true, false, base::TimeDelta(), net_log_));
+ case MOCK_PENDING_FAILING_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockPendingClientSocket(
+ addresses, false, false, base::TimeDelta(), net_log_));
+ case MOCK_DELAYED_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockPendingClientSocket(
+ addresses, true, false, delay_, net_log_));
+ case MOCK_STALLED_CLIENT_SOCKET:
+ return scoped_ptr<StreamSocket>(
+ new MockPendingClientSocket(
+ addresses, true, true, base::TimeDelta(), net_log_));
+ default:
+ NOTREACHED();
+ return scoped_ptr<StreamSocket>(
+ new MockClientSocket(addresses, net_log_));
+ }
+ }
+
+ virtual scoped_ptr<SSLClientSocket> CreateSSLClientSocket(
+ scoped_ptr<ClientSocketHandle> transport_socket,
+ const HostPortPair& host_and_port,
+ const SSLConfig& ssl_config,
+ const SSLClientSocketContext& context) OVERRIDE {
+ NOTIMPLEMENTED();
+ return scoped_ptr<SSLClientSocket>();
+ }
+
+ virtual void ClearSSLSessionCache() OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ int allocation_count() const { return allocation_count_; }
+
+ // Set the default ClientSocketType.
+ void set_client_socket_type(ClientSocketType type) {
+ client_socket_type_ = type;
+ }
+
+ // Set a list of ClientSocketTypes to be used.
+ void set_client_socket_types(ClientSocketType* type_list, int num_types) {
+ DCHECK_GT(num_types, 0);
+ client_socket_types_ = type_list;
+ client_socket_index_ = 0;
+ client_socket_index_max_ = num_types;
+ }
+
+ void set_delay(base::TimeDelta delay) { delay_ = delay; }
+
+ private:
+ NetLog* net_log_;
+ int allocation_count_;
+ ClientSocketType client_socket_type_;
+ ClientSocketType* client_socket_types_;
+ int client_socket_index_;
+ int client_socket_index_max_;
+ base::TimeDelta delay_;
+};
+
+class TransportClientSocketPoolTest : public testing::Test {
+ protected:
+ TransportClientSocketPoolTest()
+ : connect_backup_jobs_enabled_(
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(true)),
+ params_(
+ new TransportSocketParams(HostPortPair("www.google.com", 80),
+ kDefaultPriority, false, false,
+ OnHostResolutionCallback())),
+ low_params_(
+ new TransportSocketParams(HostPortPair("www.google.com", 80),
+ LOW, false, false,
+ OnHostResolutionCallback())),
+ histograms_(new ClientSocketPoolHistograms("TCPUnitTest")),
+ host_resolver_(new MockHostResolver),
+ client_socket_factory_(&net_log_),
+ pool_(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL) {
+ }
+
+ virtual ~TransportClientSocketPoolTest() {
+ internal::ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(
+ connect_backup_jobs_enabled_);
+ }
+
+ int StartRequest(const std::string& group_name, RequestPriority priority) {
+ scoped_refptr<TransportSocketParams> params(new TransportSocketParams(
+ HostPortPair("www.google.com", 80), MEDIUM, false, false,
+ OnHostResolutionCallback()));
+ return test_base_.StartRequestUsingPool(
+ &pool_, group_name, priority, params);
+ }
+
+ int GetOrderOfRequest(size_t index) {
+ return test_base_.GetOrderOfRequest(index);
+ }
+
+ bool ReleaseOneConnection(ClientSocketPoolTest::KeepAlive keep_alive) {
+ return test_base_.ReleaseOneConnection(keep_alive);
+ }
+
+ void ReleaseAllConnections(ClientSocketPoolTest::KeepAlive keep_alive) {
+ test_base_.ReleaseAllConnections(keep_alive);
+ }
+
+ ScopedVector<TestSocketRequest>* requests() { return test_base_.requests(); }
+ size_t completion_count() const { return test_base_.completion_count(); }
+
+ bool connect_backup_jobs_enabled_;
+ CapturingNetLog net_log_;
+ scoped_refptr<TransportSocketParams> params_;
+ scoped_refptr<TransportSocketParams> low_params_;
+ scoped_ptr<ClientSocketPoolHistograms> histograms_;
+ scoped_ptr<MockHostResolver> host_resolver_;
+ MockClientSocketFactory client_socket_factory_;
+ TransportClientSocketPool pool_;
+ ClientSocketPoolTest test_base_;
+};
+
+TEST(TransportConnectJobTest, MakeAddrListStartWithIPv4) {
+ IPAddressNumber ip_number;
+ ASSERT_TRUE(ParseIPLiteralToNumber("192.168.1.1", &ip_number));
+ IPEndPoint addrlist_v4_1(ip_number, 80);
+ ASSERT_TRUE(ParseIPLiteralToNumber("192.168.1.2", &ip_number));
+ IPEndPoint addrlist_v4_2(ip_number, 80);
+ ASSERT_TRUE(ParseIPLiteralToNumber("2001:4860:b006::64", &ip_number));
+ IPEndPoint addrlist_v6_1(ip_number, 80);
+ ASSERT_TRUE(ParseIPLiteralToNumber("2001:4860:b006::66", &ip_number));
+ IPEndPoint addrlist_v6_2(ip_number, 80);
+
+ AddressList addrlist;
+
+ // Test 1: IPv4 only. Expect no change.
+ addrlist.clear();
+ addrlist.push_back(addrlist_v4_1);
+ addrlist.push_back(addrlist_v4_2);
+ TransportConnectJob::MakeAddressListStartWithIPv4(&addrlist);
+ ASSERT_EQ(2u, addrlist.size());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[0].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[1].GetFamily());
+
+ // Test 2: IPv6 only. Expect no change.
+ addrlist.clear();
+ addrlist.push_back(addrlist_v6_1);
+ addrlist.push_back(addrlist_v6_2);
+ TransportConnectJob::MakeAddressListStartWithIPv4(&addrlist);
+ ASSERT_EQ(2u, addrlist.size());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[0].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[1].GetFamily());
+
+ // Test 3: IPv4 then IPv6. Expect no change.
+ addrlist.clear();
+ addrlist.push_back(addrlist_v4_1);
+ addrlist.push_back(addrlist_v4_2);
+ addrlist.push_back(addrlist_v6_1);
+ addrlist.push_back(addrlist_v6_2);
+ TransportConnectJob::MakeAddressListStartWithIPv4(&addrlist);
+ ASSERT_EQ(4u, addrlist.size());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[0].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[1].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[2].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[3].GetFamily());
+
+ // Test 4: IPv6, IPv4, IPv6, IPv4. Expect first IPv6 moved to the end.
+ addrlist.clear();
+ addrlist.push_back(addrlist_v6_1);
+ addrlist.push_back(addrlist_v4_1);
+ addrlist.push_back(addrlist_v6_2);
+ addrlist.push_back(addrlist_v4_2);
+ TransportConnectJob::MakeAddressListStartWithIPv4(&addrlist);
+ ASSERT_EQ(4u, addrlist.size());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[0].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[1].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[2].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[3].GetFamily());
+
+ // Test 5: IPv6, IPv6, IPv4, IPv4. Expect first two IPv6's moved to the end.
+ addrlist.clear();
+ addrlist.push_back(addrlist_v6_1);
+ addrlist.push_back(addrlist_v6_2);
+ addrlist.push_back(addrlist_v4_1);
+ addrlist.push_back(addrlist_v4_2);
+ TransportConnectJob::MakeAddressListStartWithIPv4(&addrlist);
+ ASSERT_EQ(4u, addrlist.size());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[0].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV4, addrlist[1].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[2].GetFamily());
+ EXPECT_EQ(ADDRESS_FAMILY_IPV6, addrlist[3].GetFamily());
+}
+
+TEST_F(TransportClientSocketPoolTest, Basic) {
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoConnectedNotReused(handle);
+}
+
+TEST_F(TransportClientSocketPoolTest, InitHostResolutionFailure) {
+ host_resolver_->rules()->AddSimulatedFailure("unresolvable.host.name");
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ HostPortPair host_port_pair("unresolvable.host.name", 80);
+ scoped_refptr<TransportSocketParams> dest(new TransportSocketParams(
+ host_port_pair, kDefaultPriority, false, false,
+ OnHostResolutionCallback()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", dest, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+ EXPECT_EQ(ERR_NAME_NOT_RESOLVED, callback.WaitForResult());
+}
+
+TEST_F(TransportClientSocketPoolTest, InitConnectionFailure) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET);
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+
+ // Make the host resolutions complete synchronously this time.
+ host_resolver_->set_synchronous_mode(true);
+ EXPECT_EQ(ERR_CONNECTION_FAILED,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+}
+
+TEST_F(TransportClientSocketPoolTest, PendingRequests) {
+ // First request finishes asynchronously.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, (*requests())[0]->WaitForResult());
+
+ // Make all subsequent host resolutions complete synchronously.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Rest of them finish synchronously, until we reach the per-group limit.
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ // The rest are pending since we've used all active sockets.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+
+ ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE);
+
+ EXPECT_EQ(kMaxSocketsPerGroup, client_socket_factory_.allocation_count());
+
+ // One initial asynchronous request and then 10 pending requests.
+ EXPECT_EQ(11U, completion_count());
+
+ // First part of requests, all with the same priority, finishes in FIFO order.
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+ EXPECT_EQ(6, GetOrderOfRequest(6));
+
+ // Make sure that rest of the requests complete in the order of priority.
+ EXPECT_EQ(7, GetOrderOfRequest(7));
+ EXPECT_EQ(14, GetOrderOfRequest(8));
+ EXPECT_EQ(15, GetOrderOfRequest(9));
+ EXPECT_EQ(10, GetOrderOfRequest(10));
+ EXPECT_EQ(13, GetOrderOfRequest(11));
+ EXPECT_EQ(8, GetOrderOfRequest(12));
+ EXPECT_EQ(16, GetOrderOfRequest(13));
+ EXPECT_EQ(11, GetOrderOfRequest(14));
+ EXPECT_EQ(12, GetOrderOfRequest(15));
+ EXPECT_EQ(9, GetOrderOfRequest(16));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(17));
+}
+
+TEST_F(TransportClientSocketPoolTest, PendingRequests_NoKeepAlive) {
+ // First request finishes asynchronously.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, (*requests())[0]->WaitForResult());
+
+ // Make all subsequent host resolutions complete synchronously.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Rest of them finish synchronously, until we reach the per-group limit.
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ // The rest are pending since we've used all active sockets.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+ // The pending requests should finish successfully.
+ EXPECT_EQ(OK, (*requests())[6]->WaitForResult());
+ EXPECT_EQ(OK, (*requests())[7]->WaitForResult());
+ EXPECT_EQ(OK, (*requests())[8]->WaitForResult());
+ EXPECT_EQ(OK, (*requests())[9]->WaitForResult());
+ EXPECT_EQ(OK, (*requests())[10]->WaitForResult());
+
+ EXPECT_EQ(static_cast<int>(requests()->size()),
+ client_socket_factory_.allocation_count());
+
+ // First asynchronous request, and then last 5 pending requests.
+ EXPECT_EQ(6U, completion_count());
+}
+
+// This test will start up a RequestSocket() and then immediately Cancel() it.
+// The pending host resolution will eventually complete, and destroy the
+// ClientSocketPool which will crash if the group was not cleared properly.
+TEST_F(TransportClientSocketPoolTest, CancelRequestClearGroup) {
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+ handle.Reset();
+}
+
+TEST_F(TransportClientSocketPoolTest, TwoRequestsCancelOne) {
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ ClientSocketHandle handle2;
+ TestCompletionCallback callback2;
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle2.Init("a", params_, kDefaultPriority, callback2.callback(),
+ &pool_, BoundNetLog()));
+
+ handle.Reset();
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ handle2.Reset();
+}
+
+TEST_F(TransportClientSocketPoolTest, ConnectCancelConnect) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_PENDING_CLIENT_SOCKET);
+ ClientSocketHandle handle;
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", params_, kDefaultPriority, callback.callback(),
+ &pool_, BoundNetLog()));
+
+ handle.Reset();
+
+ TestCompletionCallback callback2;
+ EXPECT_EQ(ERR_IO_PENDING,
+ handle.Init("a", params_, kDefaultPriority, callback2.callback(),
+ &pool_, BoundNetLog()));
+
+ host_resolver_->set_synchronous_mode(true);
+ // At this point, handle has two ConnectingSockets out for it. Due to the
+ // setting the mock resolver into synchronous mode, the host resolution for
+ // both will return in the same loop of the MessageLoop. The client socket
+ // is a pending socket, so the Connect() will asynchronously complete on the
+ // next loop of the MessageLoop. That means that the first
+ // ConnectingSocket will enter OnIOComplete, and then the second one will.
+ // If the first one is not cancelled, it will advance the load state, and
+ // then the second one will crash.
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(callback.have_result());
+
+ handle.Reset();
+}
+
+TEST_F(TransportClientSocketPoolTest, CancelRequest) {
+ // First request finishes asynchronously.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, (*requests())[0]->WaitForResult());
+
+ // Make all subsequent host resolutions complete synchronously.
+ host_resolver_->set_synchronous_mode(true);
+
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
+
+ // Reached per-group limit, queue up requests.
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
+
+ // Cancel a request.
+ size_t index_to_cancel = kMaxSocketsPerGroup + 2;
+ EXPECT_FALSE((*requests())[index_to_cancel]->handle()->is_initialized());
+ (*requests())[index_to_cancel]->handle()->Reset();
+
+ ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE);
+
+ EXPECT_EQ(kMaxSocketsPerGroup,
+ client_socket_factory_.allocation_count());
+ EXPECT_EQ(requests()->size() - kMaxSocketsPerGroup, completion_count());
+
+ EXPECT_EQ(1, GetOrderOfRequest(1));
+ EXPECT_EQ(2, GetOrderOfRequest(2));
+ EXPECT_EQ(3, GetOrderOfRequest(3));
+ EXPECT_EQ(4, GetOrderOfRequest(4));
+ EXPECT_EQ(5, GetOrderOfRequest(5));
+ EXPECT_EQ(6, GetOrderOfRequest(6));
+ EXPECT_EQ(14, GetOrderOfRequest(7));
+ EXPECT_EQ(7, GetOrderOfRequest(8));
+ EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound,
+ GetOrderOfRequest(9)); // Canceled request.
+ EXPECT_EQ(9, GetOrderOfRequest(10));
+ EXPECT_EQ(10, GetOrderOfRequest(11));
+ EXPECT_EQ(11, GetOrderOfRequest(12));
+ EXPECT_EQ(8, GetOrderOfRequest(13));
+ EXPECT_EQ(12, GetOrderOfRequest(14));
+ EXPECT_EQ(13, GetOrderOfRequest(15));
+ EXPECT_EQ(15, GetOrderOfRequest(16));
+
+ // Make sure we test order of all requests made.
+ EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(17));
+}
+
+class RequestSocketCallback : public TestCompletionCallbackBase {
+ public:
+ RequestSocketCallback(ClientSocketHandle* handle,
+ TransportClientSocketPool* pool)
+ : handle_(handle),
+ pool_(pool),
+ within_callback_(false),
+ callback_(base::Bind(&RequestSocketCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~RequestSocketCallback() {}
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ SetResult(result);
+ ASSERT_EQ(OK, result);
+
+ if (!within_callback_) {
+ // Don't allow reuse of the socket. Disconnect it and then release it and
+ // run through the MessageLoop once to get it completely released.
+ handle_->socket()->Disconnect();
+ handle_->Reset();
+ {
+ base::MessageLoop::ScopedNestableTaskAllower allow(
+ base::MessageLoop::current());
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+ within_callback_ = true;
+ scoped_refptr<TransportSocketParams> dest(new TransportSocketParams(
+ HostPortPair("www.google.com", 80), LOWEST, false, false,
+ OnHostResolutionCallback()));
+ int rv = handle_->Init("a", dest, LOWEST, callback(), pool_,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ }
+ }
+
+ ClientSocketHandle* const handle_;
+ TransportClientSocketPool* const pool_;
+ bool within_callback_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestSocketCallback);
+};
+
+TEST_F(TransportClientSocketPoolTest, RequestTwice) {
+ ClientSocketHandle handle;
+ RequestSocketCallback callback(&handle, &pool_);
+ scoped_refptr<TransportSocketParams> dest(new TransportSocketParams(
+ HostPortPair("www.google.com", 80), LOWEST, false, false,
+ OnHostResolutionCallback()));
+ int rv = handle.Init("a", dest, LOWEST, callback.callback(), &pool_,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ // The callback is going to request "www.google.com". We want it to complete
+ // synchronously this time.
+ host_resolver_->set_synchronous_mode(true);
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ handle.Reset();
+}
+
+// Make sure that pending requests get serviced after active requests get
+// cancelled.
+TEST_F(TransportClientSocketPoolTest, CancelActiveRequestWithPendingRequests) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_PENDING_CLIENT_SOCKET);
+
+ // Queue up all the requests
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ // Now, kMaxSocketsPerGroup requests should be active. Let's cancel them.
+ ASSERT_LE(kMaxSocketsPerGroup, static_cast<int>(requests()->size()));
+ for (int i = 0; i < kMaxSocketsPerGroup; i++)
+ (*requests())[i]->handle()->Reset();
+
+ // Let's wait for the rest to complete now.
+ for (size_t i = kMaxSocketsPerGroup; i < requests()->size(); ++i) {
+ EXPECT_EQ(OK, (*requests())[i]->WaitForResult());
+ (*requests())[i]->handle()->Reset();
+ }
+
+ EXPECT_EQ(requests()->size() - kMaxSocketsPerGroup, completion_count());
+}
+
+// Make sure that pending requests get serviced after active requests fail.
+TEST_F(TransportClientSocketPoolTest, FailingActiveRequestWithPendingRequests) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_PENDING_FAILING_CLIENT_SOCKET);
+
+ const int kNumRequests = 2 * kMaxSocketsPerGroup + 1;
+ ASSERT_LE(kNumRequests, kMaxSockets); // Otherwise the test will hang.
+
+ // Queue up all the requests
+ for (int i = 0; i < kNumRequests; i++)
+ EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
+
+ for (int i = 0; i < kNumRequests; i++)
+ EXPECT_EQ(ERR_CONNECTION_FAILED, (*requests())[i]->WaitForResult());
+}
+
+TEST_F(TransportClientSocketPoolTest, IdleSocketLoadTiming) {
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ TestLoadTimingInfoConnectedNotReused(handle);
+
+ handle.Reset();
+ // Need to run all pending to release the socket back to the pool.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now we should have 1 idle socket.
+ EXPECT_EQ(1, pool_.IdleSocketCount());
+
+ rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+ TestLoadTimingInfoConnectedReused(handle);
+}
+
+TEST_F(TransportClientSocketPoolTest, ResetIdleSocketsOnIPAddressChange) {
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+
+ handle.Reset();
+
+ // Need to run all pending to release the socket back to the pool.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now we should have 1 idle socket.
+ EXPECT_EQ(1, pool_.IdleSocketCount());
+
+ // After an IP address change, we should have 0 idle sockets.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle(); // Notification happens async.
+
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+}
+
+TEST_F(TransportClientSocketPoolTest, BackupSocketConnect) {
+ // Case 1 tests the first socket stalling, and the backup connecting.
+ MockClientSocketFactory::ClientSocketType case1_types[] = {
+ // The first socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // The second socket will connect more quickly.
+ MockClientSocketFactory::MOCK_CLIENT_SOCKET
+ };
+
+ // Case 2 tests the first socket being slow, so that we start the
+ // second connect, but the second connect stalls, and we still
+ // complete the first.
+ MockClientSocketFactory::ClientSocketType case2_types[] = {
+ // The first socket will connect, although delayed.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // The second socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET
+ };
+
+ MockClientSocketFactory::ClientSocketType* cases[2] = {
+ case1_types,
+ case2_types
+ };
+
+ for (size_t index = 0; index < arraysize(cases); ++index) {
+ client_socket_factory_.set_client_socket_types(cases[index], 2);
+
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Wait for the backup socket timer to fire.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs + 50));
+
+ // Let the appropriate socket connect.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+
+ // One socket is stalled, the other is active.
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+ handle.Reset();
+
+ // Close all pending connect jobs and existing sockets.
+ pool_.FlushWithError(ERR_NETWORK_CHANGED);
+ }
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket, but then we cancelled the request after that.
+TEST_F(TransportClientSocketPoolTest, BackupSocketCancel) {
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET);
+
+ enum { CANCEL_BEFORE_WAIT, CANCEL_AFTER_WAIT };
+
+ for (int index = CANCEL_BEFORE_WAIT; index < CANCEL_AFTER_WAIT; ++index) {
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("c", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ if (index == CANCEL_AFTER_WAIT) {
+ // Wait for the backup socket timer to fire.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs));
+ }
+
+ // Let the appropriate socket connect.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ handle.Reset();
+
+ EXPECT_FALSE(callback.have_result());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // One socket is stalled, the other is active.
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+ }
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket and never completes, and then the backup
+// connection fails.
+TEST_F(TransportClientSocketPoolTest, BackupSocketFailAfterStall) {
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // The first socket will not connect.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // The second socket will fail immediately.
+ MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Wait for the backup socket timer to fire.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs));
+
+ // Let the second connect be synchronous. Otherwise, the emulated
+ // host resolution takes an extra trip through the message loop.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Let the appropriate socket connect.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+ handle.Reset();
+
+ // Reset for the next case.
+ host_resolver_->set_synchronous_mode(false);
+}
+
+// Test the case where a socket took long enough to start the creation
+// of the backup socket and eventually completes, but the backup socket
+// fails.
+TEST_F(TransportClientSocketPoolTest, BackupSocketFailAfterDelay) {
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // The first socket will connect, although delayed.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // The second socket will not connect.
+ MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+ client_socket_factory_.set_delay(base::TimeDelta::FromSeconds(5));
+
+ EXPECT_EQ(0, pool_.IdleSocketCount());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("b", low_params_, LOW, callback.callback(), &pool_,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ // Create the first socket, set the timer.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Wait for the backup socket timer to fire.
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(
+ ClientSocketPool::kMaxConnectRetryIntervalMs));
+
+ // Let the second connect be synchronous. Otherwise, the emulated
+ // host resolution takes an extra trip through the message loop.
+ host_resolver_->set_synchronous_mode(true);
+
+ // Let the appropriate socket connect.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(ERR_CONNECTION_FAILED, callback.WaitForResult());
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+ handle.Reset();
+
+ // Reset for the next case.
+ host_resolver_->set_synchronous_mode(false);
+}
+
+// Test the case of the IPv6 address stalling, and falling back to the IPv4
+// socket which finishes first.
+TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv4FinishesFirst) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // This is the IPv6 socket.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET,
+ // This is the IPv4 socket.
+ MockClientSocketFactory::MOCK_PENDING_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+
+ // Resolve an AddressList with a IPv6 address first and then a IPv4 address.
+ host_resolver_->rules()
+ ->AddIPLiteralRule("*", "2:abcd::3:4:ff,2.2.2.2", std::string());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv4AddressSize, endpoint.address().size());
+ EXPECT_EQ(2, client_socket_factory_.allocation_count());
+}
+
+// Test the case of the IPv6 address being slow, thus falling back to trying to
+// connect to the IPv4 address, but having the connect to the IPv6 address
+// finish first.
+TEST_F(TransportClientSocketPoolTest, IPv6FallbackSocketIPv6FinishesFirst) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ MockClientSocketFactory::ClientSocketType case_types[] = {
+ // This is the IPv6 socket.
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET,
+ // This is the IPv4 socket.
+ MockClientSocketFactory::MOCK_STALLED_CLIENT_SOCKET
+ };
+
+ client_socket_factory_.set_client_socket_types(case_types, 2);
+ client_socket_factory_.set_delay(base::TimeDelta::FromMilliseconds(
+ TransportConnectJob::kIPv6FallbackTimerInMs + 50));
+
+ // Resolve an AddressList with a IPv6 address first and then a IPv4 address.
+ host_resolver_->rules()
+ ->AddIPLiteralRule("*", "2:abcd::3:4:ff,2.2.2.2", std::string());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv6AddressSize, endpoint.address().size());
+ EXPECT_EQ(2, client_socket_factory_.allocation_count());
+}
+
+TEST_F(TransportClientSocketPoolTest, IPv6NoIPv4AddressesToFallbackTo) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET);
+
+ // Resolve an AddressList with only IPv6 addresses.
+ host_resolver_->rules()
+ ->AddIPLiteralRule("*", "2:abcd::3:4:ff,3:abcd::3:4:ff", std::string());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv6AddressSize, endpoint.address().size());
+ EXPECT_EQ(1, client_socket_factory_.allocation_count());
+}
+
+TEST_F(TransportClientSocketPoolTest, IPv4HasNoFallback) {
+ // Create a pool without backup jobs.
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+ TransportClientSocketPool pool(kMaxSockets,
+ kMaxSocketsPerGroup,
+ histograms_.get(),
+ host_resolver_.get(),
+ &client_socket_factory_,
+ NULL);
+
+ client_socket_factory_.set_client_socket_type(
+ MockClientSocketFactory::MOCK_DELAYED_CLIENT_SOCKET);
+
+ // Resolve an AddressList with only IPv4 addresses.
+ host_resolver_->rules()->AddIPLiteralRule("*", "1.1.1.1", std::string());
+
+ TestCompletionCallback callback;
+ ClientSocketHandle handle;
+ int rv = handle.Init("a", low_params_, LOW, callback.callback(), &pool,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_FALSE(handle.is_initialized());
+ EXPECT_FALSE(handle.socket());
+
+ EXPECT_EQ(OK, callback.WaitForResult());
+ EXPECT_TRUE(handle.is_initialized());
+ EXPECT_TRUE(handle.socket());
+ IPEndPoint endpoint;
+ handle.socket()->GetLocalAddress(&endpoint);
+ EXPECT_EQ(kIPv4AddressSize, endpoint.address().size());
+ EXPECT_EQ(1, client_socket_factory_.allocation_count());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/socket/transport_client_socket_unittest.cc b/chromium/net/socket/transport_client_socket_unittest.cc
new file mode 100644
index 00000000000..5c5a303b82f
--- /dev/null
+++ b/chromium/net/socket/transport_client_socket_unittest.cc
@@ -0,0 +1,449 @@
+// Copyright (c) 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.
+
+#include "net/socket/tcp_client_socket.h"
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/tcp_listen_socket.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+const char kServerReply[] = "HTTP/1.1 404 Not Found";
+
+enum ClientSocketTestTypes {
+ TCP,
+ SCTP
+};
+
+} // namespace
+
+class TransportClientSocketTest
+ : public StreamListenSocket::Delegate,
+ public ::testing::TestWithParam<ClientSocketTestTypes> {
+ public:
+ TransportClientSocketTest()
+ : listen_port_(0),
+ socket_factory_(ClientSocketFactory::GetDefaultFactory()),
+ close_server_socket_on_next_send_(false) {
+ }
+
+ virtual ~TransportClientSocketTest() {
+ }
+
+ // Implement StreamListenSocket::Delegate methods
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) OVERRIDE {
+ connected_sock_ = reinterpret_cast<TCPListenSocket*>(connection);
+ }
+ virtual void DidRead(StreamListenSocket*, const char* str, int len) OVERRIDE {
+ // TODO(dkegel): this might not be long enough to tickle some bugs.
+ connected_sock_->Send(kServerReply, arraysize(kServerReply) - 1,
+ false /* Don't append line feed */);
+ if (close_server_socket_on_next_send_)
+ CloseServerSocket();
+ }
+ virtual void DidClose(StreamListenSocket* sock) OVERRIDE {}
+
+ // Testcase hooks
+ virtual void SetUp();
+
+ void CloseServerSocket() {
+ // delete the connected_sock_, which will close it.
+ connected_sock_ = NULL;
+ }
+
+ void PauseServerReads() {
+ connected_sock_->PauseReads();
+ }
+
+ void ResumeServerReads() {
+ connected_sock_->ResumeReads();
+ }
+
+ int DrainClientSocket(IOBuffer* buf,
+ uint32 buf_len,
+ uint32 bytes_to_read,
+ TestCompletionCallback* callback);
+
+ void SendClientRequest();
+
+ void set_close_server_socket_on_next_send(bool close) {
+ close_server_socket_on_next_send_ = close;
+ }
+
+ protected:
+ int listen_port_;
+ CapturingNetLog net_log_;
+ ClientSocketFactory* const socket_factory_;
+ scoped_ptr<StreamSocket> sock_;
+
+ private:
+ scoped_refptr<TCPListenSocket> listen_sock_;
+ scoped_refptr<TCPListenSocket> connected_sock_;
+ bool close_server_socket_on_next_send_;
+};
+
+void TransportClientSocketTest::SetUp() {
+ ::testing::TestWithParam<ClientSocketTestTypes>::SetUp();
+
+ // Find a free port to listen on
+ scoped_refptr<TCPListenSocket> sock;
+ int port;
+ // Range of ports to listen on. Shouldn't need to try many.
+ const int kMinPort = 10100;
+ const int kMaxPort = 10200;
+#if defined(OS_WIN)
+ EnsureWinsockInit();
+#endif
+ for (port = kMinPort; port < kMaxPort; port++) {
+ sock = TCPListenSocket::CreateAndListen("127.0.0.1", port, this);
+ if (sock.get())
+ break;
+ }
+ ASSERT_TRUE(sock.get() != NULL);
+ listen_sock_ = sock;
+ listen_port_ = port;
+
+ AddressList addr;
+ // MockHostResolver resolves everything to 127.0.0.1.
+ scoped_ptr<HostResolver> resolver(new MockHostResolver());
+ HostResolver::RequestInfo info(HostPortPair("localhost", listen_port_));
+ TestCompletionCallback callback;
+ int rv = resolver->Resolve(info, &addr, callback.callback(), NULL,
+ BoundNetLog());
+ CHECK_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ CHECK_EQ(rv, OK);
+ sock_ =
+ socket_factory_->CreateTransportClientSocket(addr,
+ &net_log_,
+ NetLog::Source());
+}
+
+int TransportClientSocketTest::DrainClientSocket(
+ IOBuffer* buf, uint32 buf_len,
+ uint32 bytes_to_read, TestCompletionCallback* callback) {
+ int rv = OK;
+ uint32 bytes_read = 0;
+
+ while (bytes_read < bytes_to_read) {
+ rv = sock_->Read(buf, buf_len, callback->callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback->WaitForResult();
+
+ EXPECT_GE(rv, 0);
+ bytes_read += rv;
+ }
+
+ return static_cast<int>(bytes_read);
+}
+
+void TransportClientSocketTest::SendClientRequest() {
+ const char request_text[] = "GET / HTTP/1.0\r\n\r\n";
+ scoped_refptr<IOBuffer> request_buffer(
+ new IOBuffer(arraysize(request_text) - 1));
+ TestCompletionCallback callback;
+ int rv;
+
+ memcpy(request_buffer->data(), request_text, arraysize(request_text) - 1);
+ rv = sock_->Write(
+ request_buffer.get(), arraysize(request_text) - 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, static_cast<int>(arraysize(request_text) - 1));
+}
+
+// TODO(leighton): Add SCTP to this list when it is ready.
+INSTANTIATE_TEST_CASE_P(StreamSocket,
+ TransportClientSocketTest,
+ ::testing::Values(TCP));
+
+TEST_P(TransportClientSocketTest, Connect) {
+ TestCompletionCallback callback;
+ EXPECT_FALSE(sock_->IsConnected());
+
+ int rv = sock_->Connect(callback.callback());
+
+ net::CapturingNetLog::CapturedEntryList net_log_entries;
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ net_log_entries, 0, net::NetLog::TYPE_SOCKET_ALIVE));
+ EXPECT_TRUE(net::LogContainsBeginEvent(
+ net_log_entries, 1, net::NetLog::TYPE_TCP_CONNECT));
+ if (rv != OK) {
+ ASSERT_EQ(rv, ERR_IO_PENDING);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+
+ EXPECT_TRUE(sock_->IsConnected());
+ net_log_.GetEntries(&net_log_entries);
+ EXPECT_TRUE(net::LogContainsEndEvent(
+ net_log_entries, -1, net::NetLog::TYPE_TCP_CONNECT));
+
+ sock_->Disconnect();
+ EXPECT_FALSE(sock_->IsConnected());
+}
+
+TEST_P(TransportClientSocketTest, IsConnected) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ TestCompletionCallback callback;
+ uint32 bytes_read;
+
+ EXPECT_FALSE(sock_->IsConnected());
+ EXPECT_FALSE(sock_->IsConnectedAndIdle());
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(rv, ERR_IO_PENDING);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_TRUE(sock_->IsConnectedAndIdle());
+
+ // Send the request and wait for the server to respond.
+ SendClientRequest();
+
+ // Drain a single byte so we know we've received some data.
+ bytes_read = DrainClientSocket(buf.get(), 1, 1, &callback);
+ ASSERT_EQ(bytes_read, 1u);
+
+ // Socket should be considered connected, but not idle, due to
+ // pending data.
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_FALSE(sock_->IsConnectedAndIdle());
+
+ bytes_read = DrainClientSocket(
+ buf.get(), 4096, arraysize(kServerReply) - 2, &callback);
+ ASSERT_EQ(bytes_read, arraysize(kServerReply) - 2);
+
+ // After draining the data, the socket should be back to connected
+ // and idle.
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_TRUE(sock_->IsConnectedAndIdle());
+
+ // This time close the server socket immediately after the server response.
+ set_close_server_socket_on_next_send(true);
+ SendClientRequest();
+
+ bytes_read = DrainClientSocket(buf.get(), 1, 1, &callback);
+ ASSERT_EQ(bytes_read, 1u);
+
+ // As above because of data.
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_FALSE(sock_->IsConnectedAndIdle());
+
+ bytes_read = DrainClientSocket(
+ buf.get(), 4096, arraysize(kServerReply) - 2, &callback);
+ ASSERT_EQ(bytes_read, arraysize(kServerReply) - 2);
+
+ // Once the data is drained, the socket should now be seen as not
+ // connected.
+ if (sock_->IsConnected()) {
+ // In the unlikely event that the server's connection closure is not
+ // processed in time, wait for the connection to be closed.
+ rv = sock_->Read(buf.get(), 4096, callback.callback());
+ EXPECT_EQ(0, callback.GetResult(rv));
+ EXPECT_FALSE(sock_->IsConnected());
+ }
+ EXPECT_FALSE(sock_->IsConnectedAndIdle());
+}
+
+TEST_P(TransportClientSocketTest, Read) {
+ TestCompletionCallback callback;
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(rv, ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+ SendClientRequest();
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(4096));
+ uint32 bytes_read = DrainClientSocket(
+ buf.get(), 4096, arraysize(kServerReply) - 1, &callback);
+ ASSERT_EQ(bytes_read, arraysize(kServerReply) - 1);
+
+ // All data has been read now. Read once more to force an ERR_IO_PENDING, and
+ // then close the server socket, and note the close.
+
+ rv = sock_->Read(buf.get(), 4096, callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ CloseServerSocket();
+ EXPECT_EQ(0, callback.WaitForResult());
+}
+
+TEST_P(TransportClientSocketTest, Read_SmallChunks) {
+ TestCompletionCallback callback;
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(rv, ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+ SendClientRequest();
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(1));
+ uint32 bytes_read = 0;
+ while (bytes_read < arraysize(kServerReply) - 1) {
+ rv = sock_->Read(buf.get(), 1, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ ASSERT_EQ(1, rv);
+ bytes_read += rv;
+ }
+
+ // All data has been read now. Read once more to force an ERR_IO_PENDING, and
+ // then close the server socket, and note the close.
+
+ rv = sock_->Read(buf.get(), 1, callback.callback());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ CloseServerSocket();
+ EXPECT_EQ(0, callback.WaitForResult());
+}
+
+TEST_P(TransportClientSocketTest, Read_Interrupted) {
+ TestCompletionCallback callback;
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+ SendClientRequest();
+
+ // Do a partial read and then exit. This test should not crash!
+ scoped_refptr<IOBuffer> buf(new IOBuffer(16));
+ rv = sock_->Read(buf.get(), 16, callback.callback());
+ EXPECT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_NE(0, rv);
+}
+
+TEST_P(TransportClientSocketTest, DISABLED_FullDuplex_ReadFirst) {
+ TestCompletionCallback callback;
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(rv, ERR_IO_PENDING);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(rv, OK);
+ }
+
+ // Read first. There's no data, so it should return ERR_IO_PENDING.
+ const int kBufLen = 4096;
+ scoped_refptr<IOBuffer> buf(new IOBuffer(kBufLen));
+ rv = sock_->Read(buf.get(), kBufLen, callback.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ PauseServerReads();
+ const int kWriteBufLen = 64 * 1024;
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kWriteBufLen));
+ char* request_data = request_buffer->data();
+ memset(request_data, 'A', kWriteBufLen);
+ TestCompletionCallback write_callback;
+
+ while (true) {
+ rv = sock_->Write(
+ request_buffer.get(), kWriteBufLen, write_callback.callback());
+ ASSERT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING) {
+ ResumeServerReads();
+ rv = write_callback.WaitForResult();
+ break;
+ }
+ }
+
+ // At this point, both read and write have returned ERR_IO_PENDING, and the
+ // write callback has executed. We wait for the read callback to run now to
+ // make sure that the socket can handle full duplex communications.
+
+ rv = callback.WaitForResult();
+ EXPECT_GE(rv, 0);
+}
+
+TEST_P(TransportClientSocketTest, DISABLED_FullDuplex_WriteFirst) {
+ TestCompletionCallback callback;
+ int rv = sock_->Connect(callback.callback());
+ if (rv != OK) {
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ }
+
+ PauseServerReads();
+ const int kWriteBufLen = 64 * 1024;
+ scoped_refptr<IOBuffer> request_buffer(new IOBuffer(kWriteBufLen));
+ char* request_data = request_buffer->data();
+ memset(request_data, 'A', kWriteBufLen);
+ TestCompletionCallback write_callback;
+
+ while (true) {
+ rv = sock_->Write(
+ request_buffer.get(), kWriteBufLen, write_callback.callback());
+ ASSERT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+
+ if (rv == ERR_IO_PENDING)
+ break;
+ }
+
+ // Now we have the Write() blocked on ERR_IO_PENDING. It's time to force the
+ // Read() to block on ERR_IO_PENDING too.
+
+ const int kBufLen = 4096;
+ scoped_refptr<IOBuffer> buf(new IOBuffer(kBufLen));
+ while (true) {
+ rv = sock_->Read(buf.get(), kBufLen, callback.callback());
+ ASSERT_TRUE(rv >= 0 || rv == ERR_IO_PENDING);
+ if (rv == ERR_IO_PENDING)
+ break;
+ }
+
+ // At this point, both read and write have returned ERR_IO_PENDING. Now we
+ // run the write and read callbacks to make sure they can handle full duplex
+ // communications.
+
+ ResumeServerReads();
+ rv = write_callback.WaitForResult();
+ EXPECT_GE(rv, 0);
+
+ // It's possible the read is blocked because it's already read all the data.
+ // Close the server socket, so there will at least be a 0-byte read.
+ CloseServerSocket();
+
+ rv = callback.WaitForResult();
+ EXPECT_GE(rv, 0);
+}
+
+} // namespace net
diff --git a/chromium/net/socket/unix_domain_socket_posix.cc b/chromium/net/socket/unix_domain_socket_posix.cc
new file mode 100644
index 00000000000..5b6b2498245
--- /dev/null
+++ b/chromium/net/socket/unix_domain_socket_posix.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 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.
+
+#include "net/socket/unix_domain_socket_posix.h"
+
+#include <cstring>
+#include <string>
+
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+namespace {
+
+bool NoAuthenticationCallback(uid_t, gid_t) {
+ return true;
+}
+
+bool GetPeerIds(int socket, uid_t* user_id, gid_t* group_id) {
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ struct ucred user_cred;
+ socklen_t len = sizeof(user_cred);
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &user_cred, &len) == -1)
+ return false;
+ *user_id = user_cred.uid;
+ *group_id = user_cred.gid;
+#else
+ if (getpeereid(socket, user_id, group_id) == -1)
+ return false;
+#endif
+ return true;
+}
+
+} // namespace
+
+// static
+UnixDomainSocket::AuthCallback NoAuthentication() {
+ return base::Bind(NoAuthenticationCallback);
+}
+
+// static
+UnixDomainSocket* UnixDomainSocket::CreateAndListenInternal(
+ const std::string& path,
+ const std::string& fallback_path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback,
+ bool use_abstract_namespace) {
+ SocketDescriptor s = CreateAndBind(path, use_abstract_namespace);
+ if (s == kInvalidSocket && !fallback_path.empty())
+ s = CreateAndBind(fallback_path, use_abstract_namespace);
+ if (s == kInvalidSocket)
+ return NULL;
+ UnixDomainSocket* sock = new UnixDomainSocket(s, del, auth_callback);
+ sock->Listen();
+ return sock;
+}
+
+// static
+scoped_refptr<UnixDomainSocket> UnixDomainSocket::CreateAndListen(
+ const std::string& path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback) {
+ return CreateAndListenInternal(path, "", del, auth_callback, false);
+}
+
+#if defined(SOCKET_ABSTRACT_NAMESPACE_SUPPORTED)
+// static
+scoped_refptr<UnixDomainSocket>
+UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ const std::string& path,
+ const std::string& fallback_path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback) {
+ return make_scoped_refptr(
+ CreateAndListenInternal(path, fallback_path, del, auth_callback, true));
+}
+#endif
+
+UnixDomainSocket::UnixDomainSocket(
+ SocketDescriptor s,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback)
+ : StreamListenSocket(s, del),
+ auth_callback_(auth_callback) {}
+
+UnixDomainSocket::~UnixDomainSocket() {}
+
+// static
+SocketDescriptor UnixDomainSocket::CreateAndBind(const std::string& path,
+ bool use_abstract_namespace) {
+ sockaddr_un addr;
+ static const size_t kPathMax = sizeof(addr.sun_path);
+ if (use_abstract_namespace + path.size() + 1 /* '\0' */ > kPathMax)
+ return kInvalidSocket;
+ const SocketDescriptor s = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (s == kInvalidSocket)
+ return kInvalidSocket;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ socklen_t addr_len;
+ if (use_abstract_namespace) {
+ // Convert the path given into abstract socket name. It must start with
+ // the '\0' character, so we are adding it. |addr_len| must specify the
+ // length of the structure exactly, as potentially the socket name may
+ // have '\0' characters embedded (although we don't support this).
+ // Note that addr.sun_path is already zero initialized.
+ memcpy(addr.sun_path + 1, path.c_str(), path.size());
+ addr_len = path.size() + offsetof(struct sockaddr_un, sun_path) + 1;
+ } else {
+ memcpy(addr.sun_path, path.c_str(), path.size());
+ addr_len = sizeof(sockaddr_un);
+ }
+ if (bind(s, reinterpret_cast<sockaddr*>(&addr), addr_len)) {
+ LOG(ERROR) << "Could not bind unix domain socket to " << path;
+ if (use_abstract_namespace)
+ LOG(ERROR) << " (with abstract namespace enabled)";
+ if (HANDLE_EINTR(close(s)) < 0)
+ LOG(ERROR) << "close() error";
+ return kInvalidSocket;
+ }
+ return s;
+}
+
+void UnixDomainSocket::Accept() {
+ SocketDescriptor conn = StreamListenSocket::AcceptSocket();
+ if (conn == kInvalidSocket)
+ return;
+ uid_t user_id;
+ gid_t group_id;
+ if (!GetPeerIds(conn, &user_id, &group_id) ||
+ !auth_callback_.Run(user_id, group_id)) {
+ if (HANDLE_EINTR(close(conn)) < 0)
+ LOG(ERROR) << "close() error";
+ return;
+ }
+ scoped_refptr<UnixDomainSocket> sock(
+ new UnixDomainSocket(conn, socket_delegate_, auth_callback_));
+ // It's up to the delegate to AddRef if it wants to keep it around.
+ sock->WatchSocket(WAITING_READ);
+ socket_delegate_->DidAccept(this, sock.get());
+}
+
+UnixDomainSocketFactory::UnixDomainSocketFactory(
+ const std::string& path,
+ const UnixDomainSocket::AuthCallback& auth_callback)
+ : path_(path),
+ auth_callback_(auth_callback) {}
+
+UnixDomainSocketFactory::~UnixDomainSocketFactory() {}
+
+scoped_refptr<StreamListenSocket> UnixDomainSocketFactory::CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const {
+ return UnixDomainSocket::CreateAndListen(
+ path_, delegate, auth_callback_);
+}
+
+#if defined(SOCKET_ABSTRACT_NAMESPACE_SUPPORTED)
+
+UnixDomainSocketWithAbstractNamespaceFactory::
+UnixDomainSocketWithAbstractNamespaceFactory(
+ const std::string& path,
+ const std::string& fallback_path,
+ const UnixDomainSocket::AuthCallback& auth_callback)
+ : UnixDomainSocketFactory(path, auth_callback),
+ fallback_path_(fallback_path) {}
+
+UnixDomainSocketWithAbstractNamespaceFactory::
+~UnixDomainSocketWithAbstractNamespaceFactory() {}
+
+scoped_refptr<StreamListenSocket>
+UnixDomainSocketWithAbstractNamespaceFactory::CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const {
+ return UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ path_, fallback_path_, delegate, auth_callback_);
+}
+
+#endif
+
+} // namespace net
diff --git a/chromium/net/socket/unix_domain_socket_posix.h b/chromium/net/socket/unix_domain_socket_posix.h
new file mode 100644
index 00000000000..2ef06803d24
--- /dev/null
+++ b/chromium/net/socket/unix_domain_socket_posix.h
@@ -0,0 +1,126 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_UNIX_DOMAIN_SOCKET_POSIX_H_
+#define NET_SOCKET_UNIX_DOMAIN_SOCKET_POSIX_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+#include "net/socket/stream_listen_socket.h"
+
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+// Feature only supported on Linux currently. This lets the Unix Domain Socket
+// not be backed by the file system.
+#define SOCKET_ABSTRACT_NAMESPACE_SUPPORTED
+#endif
+
+namespace net {
+
+// Unix Domain Socket Implementation. Supports abstract namespaces on Linux.
+class NET_EXPORT UnixDomainSocket : public StreamListenSocket {
+ public:
+ // Callback that returns whether the already connected client, identified by
+ // its process |user_id| and |group_id|, is allowed to keep the connection
+ // open. Note that the socket is closed immediately in case the callback
+ // returns false.
+ typedef base::Callback<bool (uid_t user_id, gid_t group_id)> AuthCallback;
+
+ // Returns an authentication callback that always grants access for
+ // convenience in case you don't want to use authentication.
+ static AuthCallback NoAuthentication();
+
+ // Note that the returned UnixDomainSocket instance does not take ownership of
+ // |del|.
+ static scoped_refptr<UnixDomainSocket> CreateAndListen(
+ const std::string& path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback);
+
+#if defined(SOCKET_ABSTRACT_NAMESPACE_SUPPORTED)
+ // Same as above except that the created socket uses the abstract namespace
+ // which is a Linux-only feature. If |fallback_path| is not empty,
+ // make the second attempt with the provided fallback name.
+ static scoped_refptr<UnixDomainSocket> CreateAndListenWithAbstractNamespace(
+ const std::string& path,
+ const std::string& fallback_path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback);
+#endif
+
+ private:
+ UnixDomainSocket(SocketDescriptor s,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback);
+ virtual ~UnixDomainSocket();
+
+ static UnixDomainSocket* CreateAndListenInternal(
+ const std::string& path,
+ const std::string& fallback_path,
+ StreamListenSocket::Delegate* del,
+ const AuthCallback& auth_callback,
+ bool use_abstract_namespace);
+
+ static SocketDescriptor CreateAndBind(const std::string& path,
+ bool use_abstract_namespace);
+
+ // StreamListenSocket:
+ virtual void Accept() OVERRIDE;
+
+ AuthCallback auth_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainSocket);
+};
+
+// Factory that can be used to instantiate UnixDomainSocket.
+class NET_EXPORT UnixDomainSocketFactory : public StreamListenSocketFactory {
+ public:
+ // Note that this class does not take ownership of the provided delegate.
+ UnixDomainSocketFactory(const std::string& path,
+ const UnixDomainSocket::AuthCallback& auth_callback);
+ virtual ~UnixDomainSocketFactory();
+
+ // StreamListenSocketFactory:
+ virtual scoped_refptr<StreamListenSocket> CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const OVERRIDE;
+
+ protected:
+ const std::string path_;
+ const UnixDomainSocket::AuthCallback auth_callback_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainSocketFactory);
+};
+
+#if defined(SOCKET_ABSTRACT_NAMESPACE_SUPPORTED)
+// Use this factory to instantiate UnixDomainSocket using the abstract
+// namespace feature (only supported on Linux).
+class NET_EXPORT UnixDomainSocketWithAbstractNamespaceFactory
+ : public UnixDomainSocketFactory {
+ public:
+ UnixDomainSocketWithAbstractNamespaceFactory(
+ const std::string& path,
+ const std::string& fallback_path,
+ const UnixDomainSocket::AuthCallback& auth_callback);
+ virtual ~UnixDomainSocketWithAbstractNamespaceFactory();
+
+ // UnixDomainSocketFactory:
+ virtual scoped_refptr<StreamListenSocket> CreateAndListen(
+ StreamListenSocket::Delegate* delegate) const OVERRIDE;
+
+ private:
+ std::string fallback_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnixDomainSocketWithAbstractNamespaceFactory);
+};
+#endif
+
+} // namespace net
+
+#endif // NET_SOCKET_UNIX_DOMAIN_SOCKET_POSIX_H_
diff --git a/chromium/net/socket/unix_domain_socket_posix_unittest.cc b/chromium/net/socket/unix_domain_socket_posix_unittest.cc
new file mode 100644
index 00000000000..5abe03b4ae3
--- /dev/null
+++ b/chromium/net/socket/unix_domain_socket_posix_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright (c) 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.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <queue>
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "net/socket/unix_domain_socket_posix.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::queue;
+using std::string;
+
+namespace net {
+namespace {
+
+const char kSocketFilename[] = "unix_domain_socket_for_testing";
+const char kFallbackSocketName[] = "unix_domain_socket_for_testing_2";
+const char kInvalidSocketPath[] = "/invalid/path";
+const char kMsg[] = "hello";
+
+enum EventType {
+ EVENT_ACCEPT,
+ EVENT_AUTH_DENIED,
+ EVENT_AUTH_GRANTED,
+ EVENT_CLOSE,
+ EVENT_LISTEN,
+ EVENT_READ,
+};
+
+string MakeSocketPath(const string& socket_file_name) {
+ base::FilePath temp_dir;
+ file_util::GetTempDir(&temp_dir);
+ return temp_dir.Append(socket_file_name).value();
+}
+
+string MakeSocketPath() {
+ return MakeSocketPath(kSocketFilename);
+}
+
+class EventManager : public base::RefCounted<EventManager> {
+ public:
+ EventManager() : condition_(&mutex_) {}
+
+ bool HasPendingEvent() {
+ base::AutoLock lock(mutex_);
+ return !events_.empty();
+ }
+
+ void Notify(EventType event) {
+ base::AutoLock lock(mutex_);
+ events_.push(event);
+ condition_.Broadcast();
+ }
+
+ EventType WaitForEvent() {
+ base::AutoLock lock(mutex_);
+ while (events_.empty())
+ condition_.Wait();
+ EventType event = events_.front();
+ events_.pop();
+ return event;
+ }
+
+ private:
+ friend class base::RefCounted<EventManager>;
+ virtual ~EventManager() {}
+
+ queue<EventType> events_;
+ base::Lock mutex_;
+ base::ConditionVariable condition_;
+};
+
+class TestListenSocketDelegate : public StreamListenSocket::Delegate {
+ public:
+ explicit TestListenSocketDelegate(
+ const scoped_refptr<EventManager>& event_manager)
+ : event_manager_(event_manager) {}
+
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) OVERRIDE {
+ LOG(ERROR) << __PRETTY_FUNCTION__;
+ connection_ = connection;
+ Notify(EVENT_ACCEPT);
+ }
+
+ virtual void DidRead(StreamListenSocket* connection,
+ const char* data,
+ int len) OVERRIDE {
+ {
+ base::AutoLock lock(mutex_);
+ DCHECK(len);
+ data_.assign(data, len - 1);
+ }
+ Notify(EVENT_READ);
+ }
+
+ virtual void DidClose(StreamListenSocket* sock) OVERRIDE {
+ Notify(EVENT_CLOSE);
+ }
+
+ void OnListenCompleted() {
+ Notify(EVENT_LISTEN);
+ }
+
+ string ReceivedData() {
+ base::AutoLock lock(mutex_);
+ return data_;
+ }
+
+ private:
+ void Notify(EventType event) {
+ event_manager_->Notify(event);
+ }
+
+ const scoped_refptr<EventManager> event_manager_;
+ scoped_refptr<StreamListenSocket> connection_;
+ base::Lock mutex_;
+ string data_;
+};
+
+bool UserCanConnectCallback(
+ bool allow_user, const scoped_refptr<EventManager>& event_manager,
+ uid_t, gid_t) {
+ event_manager->Notify(
+ allow_user ? EVENT_AUTH_GRANTED : EVENT_AUTH_DENIED);
+ return allow_user;
+}
+
+class UnixDomainSocketTestHelper : public testing::Test {
+ public:
+ void CreateAndListen() {
+ socket_ = UnixDomainSocket::CreateAndListen(
+ file_path_.value(), socket_delegate_.get(), MakeAuthCallback());
+ socket_delegate_->OnListenCompleted();
+ }
+
+ protected:
+ UnixDomainSocketTestHelper(const string& path, bool allow_user)
+ : file_path_(path),
+ allow_user_(allow_user) {}
+
+ virtual void SetUp() OVERRIDE {
+ event_manager_ = new EventManager();
+ socket_delegate_.reset(new TestListenSocketDelegate(event_manager_));
+ DeleteSocketFile();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ DeleteSocketFile();
+ socket_ = NULL;
+ socket_delegate_.reset();
+ event_manager_ = NULL;
+ }
+
+ UnixDomainSocket::AuthCallback MakeAuthCallback() {
+ return base::Bind(&UserCanConnectCallback, allow_user_, event_manager_);
+ }
+
+ void DeleteSocketFile() {
+ ASSERT_FALSE(file_path_.empty());
+ base::DeleteFile(file_path_, false /* not recursive */);
+ }
+
+ SocketDescriptor CreateClientSocket() {
+ const SocketDescriptor sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ LOG(ERROR) << "socket() error";
+ return StreamListenSocket::kInvalidSocket;
+ }
+ sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ socklen_t addr_len;
+ strncpy(addr.sun_path, file_path_.value().c_str(), sizeof(addr.sun_path));
+ addr_len = sizeof(sockaddr_un);
+ if (connect(sock, reinterpret_cast<sockaddr*>(&addr), addr_len) != 0) {
+ LOG(ERROR) << "connect() error";
+ return StreamListenSocket::kInvalidSocket;
+ }
+ return sock;
+ }
+
+ scoped_ptr<base::Thread> CreateAndRunServerThread() {
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ scoped_ptr<base::Thread> thread(new base::Thread("socketio_test"));
+ thread->StartWithOptions(options);
+ thread->message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UnixDomainSocketTestHelper::CreateAndListen,
+ base::Unretained(this)));
+ return thread.Pass();
+ }
+
+ const base::FilePath file_path_;
+ const bool allow_user_;
+ scoped_refptr<EventManager> event_manager_;
+ scoped_ptr<TestListenSocketDelegate> socket_delegate_;
+ scoped_refptr<UnixDomainSocket> socket_;
+};
+
+class UnixDomainSocketTest : public UnixDomainSocketTestHelper {
+ protected:
+ UnixDomainSocketTest()
+ : UnixDomainSocketTestHelper(MakeSocketPath(), true /* allow user */) {}
+};
+
+class UnixDomainSocketTestWithInvalidPath : public UnixDomainSocketTestHelper {
+ protected:
+ UnixDomainSocketTestWithInvalidPath()
+ : UnixDomainSocketTestHelper(kInvalidSocketPath, true) {}
+};
+
+class UnixDomainSocketTestWithForbiddenUser
+ : public UnixDomainSocketTestHelper {
+ protected:
+ UnixDomainSocketTestWithForbiddenUser()
+ : UnixDomainSocketTestHelper(MakeSocketPath(), false /* forbid user */) {}
+};
+
+TEST_F(UnixDomainSocketTest, CreateAndListen) {
+ CreateAndListen();
+ EXPECT_FALSE(socket_.get() == NULL);
+}
+
+TEST_F(UnixDomainSocketTestWithInvalidPath, CreateAndListenWithInvalidPath) {
+ CreateAndListen();
+ EXPECT_TRUE(socket_.get() == NULL);
+}
+
+#ifdef SOCKET_ABSTRACT_NAMESPACE_SUPPORTED
+// Test with an invalid path to make sure that the socket is not backed by a
+// file.
+TEST_F(UnixDomainSocketTestWithInvalidPath,
+ CreateAndListenWithAbstractNamespace) {
+ socket_ = UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ file_path_.value(), "", socket_delegate_.get(), MakeAuthCallback());
+ EXPECT_FALSE(socket_.get() == NULL);
+}
+
+TEST_F(UnixDomainSocketTest, TestFallbackName) {
+ scoped_refptr<UnixDomainSocket> existing_socket =
+ UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ file_path_.value(), "", socket_delegate_.get(), MakeAuthCallback());
+ EXPECT_FALSE(existing_socket.get() == NULL);
+ // First, try to bind socket with the same name with no fallback name.
+ socket_ =
+ UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ file_path_.value(), "", socket_delegate_.get(), MakeAuthCallback());
+ EXPECT_TRUE(socket_.get() == NULL);
+ // Now with a fallback name.
+ socket_ = UnixDomainSocket::CreateAndListenWithAbstractNamespace(
+ file_path_.value(),
+ MakeSocketPath(kFallbackSocketName),
+ socket_delegate_.get(),
+ MakeAuthCallback());
+ EXPECT_FALSE(socket_.get() == NULL);
+ existing_socket = NULL;
+}
+#endif
+
+TEST_F(UnixDomainSocketTest, TestWithClient) {
+ const scoped_ptr<base::Thread> server_thread = CreateAndRunServerThread();
+ EventType event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_LISTEN, event);
+
+ // Create the client socket.
+ const SocketDescriptor sock = CreateClientSocket();
+ ASSERT_NE(StreamListenSocket::kInvalidSocket, sock);
+ event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_AUTH_GRANTED, event);
+ event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_ACCEPT, event);
+
+ // Send a message from the client to the server.
+ ssize_t ret = HANDLE_EINTR(send(sock, kMsg, sizeof(kMsg), 0));
+ ASSERT_NE(-1, ret);
+ ASSERT_EQ(sizeof(kMsg), static_cast<size_t>(ret));
+ event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_READ, event);
+ ASSERT_EQ(kMsg, socket_delegate_->ReceivedData());
+
+ // Close the client socket.
+ ret = HANDLE_EINTR(close(sock));
+ event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_CLOSE, event);
+}
+
+TEST_F(UnixDomainSocketTestWithForbiddenUser, TestWithForbiddenUser) {
+ const scoped_ptr<base::Thread> server_thread = CreateAndRunServerThread();
+ EventType event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_LISTEN, event);
+ const SocketDescriptor sock = CreateClientSocket();
+ ASSERT_NE(StreamListenSocket::kInvalidSocket, sock);
+
+ event = event_manager_->WaitForEvent();
+ ASSERT_EQ(EVENT_AUTH_DENIED, event);
+
+ // Wait until the file descriptor is closed by the server.
+ struct pollfd poll_fd;
+ poll_fd.fd = sock;
+ poll_fd.events = POLLIN;
+ poll(&poll_fd, 1, -1 /* rely on GTest for timeout handling */);
+
+ // Send() must fail.
+ ssize_t ret = HANDLE_EINTR(send(sock, kMsg, sizeof(kMsg), 0));
+ ASSERT_EQ(-1, ret);
+ ASSERT_EQ(EPIPE, errno);
+ ASSERT_FALSE(event_manager_->HasPendingEvent());
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/socket_stream/OWNERS b/chromium/net/socket_stream/OWNERS
new file mode 100644
index 00000000000..8ef489a002e
--- /dev/null
+++ b/chromium/net/socket_stream/OWNERS
@@ -0,0 +1,8 @@
+tyoshino@chromium.org
+
+# Have been inactive for a while.
+yutak@chromium.org
+toyoshim@chromium.org
+
+# On leave
+bashi@chromium.org
diff --git a/chromium/net/socket_stream/socket_stream.cc b/chromium/net/socket_stream/socket_stream.cc
new file mode 100644
index 00000000000..c549fcb9768
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream.cc
@@ -0,0 +1,1336 @@
+// Copyright (c) 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.
+//
+// TODO(ukai): code is similar with http_network_transaction.cc. We should
+// think about ways to share code, if possible.
+
+#include "net/socket_stream/socket_stream.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/http_util.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/socks5_client_socket.h"
+#include "net/socket/socks_client_socket.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket_stream/socket_stream_metrics.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_info.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes.
+static const int kReadBufferSize = 4096;
+
+namespace net {
+
+int SocketStream::Delegate::OnStartOpenConnection(
+ SocketStream* socket, const CompletionCallback& callback) {
+ return OK;
+}
+
+void SocketStream::Delegate::OnAuthRequired(SocketStream* socket,
+ AuthChallengeInfo* auth_info) {
+ // By default, no credential is available and close the connection.
+ socket->Close();
+}
+
+void SocketStream::Delegate::OnSSLCertificateError(
+ SocketStream* socket,
+ const SSLInfo& ssl_info,
+ bool fatal) {
+ socket->CancelWithSSLError(ssl_info);
+}
+
+bool SocketStream::Delegate::CanGetCookies(SocketStream* socket,
+ const GURL& url) {
+ return true;
+}
+
+bool SocketStream::Delegate::CanSetCookie(SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ CookieOptions* options) {
+ return true;
+}
+
+SocketStream::ResponseHeaders::ResponseHeaders() : IOBuffer() {}
+
+void SocketStream::ResponseHeaders::Realloc(size_t new_size) {
+ headers_.reset(static_cast<char*>(realloc(headers_.release(), new_size)));
+}
+
+SocketStream::ResponseHeaders::~ResponseHeaders() { data_ = NULL; }
+
+SocketStream::SocketStream(const GURL& url, Delegate* delegate)
+ : delegate_(delegate),
+ url_(url),
+ max_pending_send_allowed_(kMaxPendingSendAllowed),
+ context_(NULL),
+ next_state_(STATE_NONE),
+ factory_(ClientSocketFactory::GetDefaultFactory()),
+ proxy_mode_(kDirectConnection),
+ proxy_url_(url),
+ pac_request_(NULL),
+ connection_(new ClientSocketHandle),
+ privacy_mode_(kPrivacyModeDisabled),
+ // Unretained() is required; without it, Bind() creates a circular
+ // dependency and the SocketStream object will not be freed.
+ io_callback_(base::Bind(&SocketStream::OnIOCompleted,
+ base::Unretained(this))),
+ read_buf_(NULL),
+ current_write_buf_(NULL),
+ waiting_for_write_completion_(false),
+ closing_(false),
+ server_closed_(false),
+ metrics_(new SocketStreamMetrics(url)) {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ DCHECK(delegate_);
+}
+
+SocketStream::UserData* SocketStream::GetUserData(
+ const void* key) const {
+ UserDataMap::const_iterator found = user_data_.find(key);
+ if (found != user_data_.end())
+ return found->second.get();
+ return NULL;
+}
+
+void SocketStream::SetUserData(const void* key, UserData* data) {
+ user_data_[key] = linked_ptr<UserData>(data);
+}
+
+bool SocketStream::is_secure() const {
+ return url_.SchemeIs("wss");
+}
+
+void SocketStream::set_context(URLRequestContext* context) {
+ const URLRequestContext* prev_context = context_;
+
+ context_ = context;
+
+ if (prev_context != context) {
+ if (prev_context && pac_request_) {
+ prev_context->proxy_service()->CancelPacRequest(pac_request_);
+ pac_request_ = NULL;
+ }
+
+ net_log_.EndEvent(NetLog::TYPE_REQUEST_ALIVE);
+ net_log_ = BoundNetLog();
+
+ if (context) {
+ net_log_ = BoundNetLog::Make(
+ context->net_log(),
+ NetLog::SOURCE_SOCKET_STREAM);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE);
+ }
+ }
+}
+
+void SocketStream::CheckPrivacyMode() {
+ if (context_ && context_->network_delegate()) {
+ bool enable = context_->network_delegate()->CanEnablePrivacyMode(url_,
+ url_);
+ privacy_mode_ = enable ? kPrivacyModeEnabled : kPrivacyModeDisabled;
+ // Disable Channel ID if privacy mode is enabled.
+ if (enable)
+ server_ssl_config_.channel_id_enabled = false;
+ }
+}
+
+void SocketStream::Connect() {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ if (context_) {
+ context_->ssl_config_service()->GetSSLConfig(&server_ssl_config_);
+ proxy_ssl_config_ = server_ssl_config_;
+ }
+ CheckPrivacyMode();
+
+ DCHECK_EQ(next_state_, STATE_NONE);
+
+ AddRef(); // Released in Finish()
+ // Open a connection asynchronously, so that delegate won't be called
+ // back before returning Connect().
+ next_state_ = STATE_BEFORE_CONNECT;
+ net_log_.BeginEvent(
+ NetLog::TYPE_SOCKET_STREAM_CONNECT,
+ NetLog::StringCallback("url", &url_.possibly_invalid_spec()));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoLoop, this, OK));
+}
+
+size_t SocketStream::GetTotalSizeOfPendingWriteBufs() const {
+ size_t total_size = 0;
+ for (PendingDataQueue::const_iterator iter = pending_write_bufs_.begin();
+ iter != pending_write_bufs_.end();
+ ++iter)
+ total_size += (*iter)->size();
+ return total_size;
+}
+
+bool SocketStream::SendData(const char* data, int len) {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ DCHECK_GT(len, 0);
+
+ if (!connection_->socket() ||
+ !connection_->socket()->IsConnected() || next_state_ == STATE_NONE) {
+ return false;
+ }
+
+ int total_buffered_bytes = len;
+ if (current_write_buf_.get()) {
+ // Since
+ // - the purpose of this check is to limit the amount of buffer used by
+ // this instance.
+ // - the DrainableIOBuffer doesn't release consumed memory.
+ // we need to use not BytesRemaining() but size() here.
+ total_buffered_bytes += current_write_buf_->size();
+ }
+ total_buffered_bytes += GetTotalSizeOfPendingWriteBufs();
+ if (total_buffered_bytes > max_pending_send_allowed_)
+ return false;
+
+ // TODO(tyoshino): Split data into smaller chunks e.g. 8KiB to free consumed
+ // buffer progressively
+ pending_write_bufs_.push_back(make_scoped_refptr(
+ new IOBufferWithSize(len)));
+ memcpy(pending_write_bufs_.back()->data(), data, len);
+
+ // If current_write_buf_ is not NULL, it means that a) there's ongoing write
+ // operation or b) the connection is being closed. If a), the buffer we just
+ // pushed will be automatically handled when the completion callback runs
+ // the loop, and therefore we don't need to enqueue DoLoop(). If b), it's ok
+ // to do nothing. If current_write_buf_ is NULL, to make sure DoLoop() is
+ // ran soon, enequeue it.
+ if (!current_write_buf_.get()) {
+ // Send pending data asynchronously, so that delegate won't be called
+ // back before returning from SendData().
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoLoop, this, OK));
+ }
+
+ return true;
+}
+
+void SocketStream::Close() {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ // If next_state_ is STATE_NONE, the socket was not opened, or already
+ // closed. So, return immediately.
+ // Otherwise, it might call Finish() more than once, so breaks balance
+ // of AddRef() and Release() in Connect() and Finish(), respectively.
+ if (next_state_ == STATE_NONE)
+ return;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoClose, this));
+}
+
+void SocketStream::RestartWithAuth(const AuthCredentials& credentials) {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ DCHECK(proxy_auth_controller_.get());
+ if (!connection_->socket()) {
+ DVLOG(1) << "Socket is closed before restarting with auth.";
+ return;
+ }
+
+ proxy_auth_controller_->ResetAuth(credentials);
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoRestartWithAuth, this));
+}
+
+void SocketStream::DetachDelegate() {
+ if (!delegate_)
+ return;
+ delegate_ = NULL;
+ // Prevent the rest of the function from executing if we are being called from
+ // within Finish().
+ if (next_state_ == STATE_NONE)
+ return;
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED);
+ // We don't need to send pending data when client detach the delegate.
+ pending_write_bufs_.clear();
+ Close();
+}
+
+const ProxyServer& SocketStream::proxy_server() const {
+ return proxy_info_.proxy_server();
+}
+
+void SocketStream::SetClientSocketFactory(
+ ClientSocketFactory* factory) {
+ DCHECK(factory);
+ factory_ = factory;
+}
+
+void SocketStream::CancelWithError(int error) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoLoop, this, error));
+}
+
+void SocketStream::CancelWithSSLError(const SSLInfo& ssl_info) {
+ CancelWithError(MapCertStatusToNetError(ssl_info.cert_status));
+}
+
+void SocketStream::ContinueDespiteError() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoLoop, this, OK));
+}
+
+SocketStream::~SocketStream() {
+ set_context(NULL);
+ DCHECK(!delegate_);
+ DCHECK(!pac_request_);
+}
+
+SocketStream::RequestHeaders::~RequestHeaders() { data_ = NULL; }
+
+void SocketStream::set_addresses(const AddressList& addresses) {
+ addresses_ = addresses;
+}
+
+void SocketStream::DoClose() {
+ closing_ = true;
+ // If next_state_ is STATE_TCP_CONNECT, it's waiting other socket
+ // establishing connection. If next_state_ is STATE_AUTH_REQUIRED, it's
+ // waiting for restarting. In these states, we'll close the SocketStream
+ // now.
+ if (next_state_ == STATE_TCP_CONNECT || next_state_ == STATE_AUTH_REQUIRED) {
+ DoLoop(ERR_ABORTED);
+ return;
+ }
+ // If next_state_ is STATE_READ_WRITE, we'll run DoLoop and close
+ // the SocketStream.
+ // If it's writing now, we should defer the closing after the current
+ // writing is completed.
+ if (next_state_ == STATE_READ_WRITE && !current_write_buf_.get())
+ DoLoop(ERR_ABORTED);
+
+ // In other next_state_, we'll wait for callback of other APIs, such as
+ // ResolveProxy().
+}
+
+void SocketStream::Finish(int result) {
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+ DCHECK_EQ(base::MessageLoop::TYPE_IO, base::MessageLoop::current()->type())
+ << "The current base::MessageLoop must be TYPE_IO";
+ DCHECK_LE(result, OK);
+ if (result == OK)
+ result = ERR_CONNECTION_CLOSED;
+ DCHECK_EQ(next_state_, STATE_NONE);
+ DVLOG(1) << "Finish result=" << ErrorToString(result);
+
+ metrics_->OnClose();
+
+ if (result != ERR_CONNECTION_CLOSED && delegate_)
+ delegate_->OnError(this, result);
+ if (result != ERR_PROTOCOL_SWITCHED && delegate_)
+ delegate_->OnClose(this);
+ delegate_ = NULL;
+
+ Release();
+}
+
+int SocketStream::DidEstablishConnection() {
+ if (!connection_->socket() || !connection_->socket()->IsConnected()) {
+ next_state_ = STATE_CLOSE;
+ return ERR_CONNECTION_FAILED;
+ }
+ next_state_ = STATE_READ_WRITE;
+ metrics_->OnConnected();
+
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_STREAM_CONNECT);
+ if (delegate_)
+ delegate_->OnConnected(this, max_pending_send_allowed_);
+
+ return OK;
+}
+
+int SocketStream::DidReceiveData(int result) {
+ DCHECK(read_buf_.get());
+ DCHECK_GT(result, 0);
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_RECEIVED);
+ int len = result;
+ metrics_->OnRead(len);
+ if (delegate_) {
+ // Notify recevied data to delegate.
+ delegate_->OnReceivedData(this, read_buf_->data(), len);
+ }
+ read_buf_ = NULL;
+ return OK;
+}
+
+void SocketStream::DidSendData(int result) {
+ DCHECK_GT(result, 0);
+ DCHECK(current_write_buf_.get());
+ net_log_.AddEvent(NetLog::TYPE_SOCKET_STREAM_SENT);
+
+ int bytes_sent = result;
+
+ metrics_->OnWrite(bytes_sent);
+
+ current_write_buf_->DidConsume(result);
+
+ if (current_write_buf_->BytesRemaining())
+ return;
+
+ size_t bytes_freed = current_write_buf_->size();
+
+ current_write_buf_ = NULL;
+
+ // We freed current_write_buf_ and this instance is now able to accept more
+ // data via SendData() (note that DidConsume() doesn't free consumed memory).
+ // We can tell that to delegate_ by calling OnSentData().
+ if (delegate_)
+ delegate_->OnSentData(this, bytes_freed);
+}
+
+void SocketStream::OnIOCompleted(int result) {
+ DoLoop(result);
+}
+
+void SocketStream::OnReadCompleted(int result) {
+ if (result == 0) {
+ // 0 indicates end-of-file, so socket was closed.
+ // Don't close the socket if it's still writing.
+ server_closed_ = true;
+ } else if (result > 0 && read_buf_.get()) {
+ result = DidReceiveData(result);
+ }
+ DoLoop(result);
+}
+
+void SocketStream::OnWriteCompleted(int result) {
+ waiting_for_write_completion_ = false;
+ if (result > 0) {
+ DidSendData(result);
+ result = OK;
+ }
+ DoLoop(result);
+}
+
+void SocketStream::DoLoop(int result) {
+ // If context was not set, close immediately.
+ if (!context_)
+ next_state_ = STATE_CLOSE;
+
+ if (next_state_ == STATE_NONE)
+ return;
+
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_BEFORE_CONNECT:
+ DCHECK_EQ(OK, result);
+ result = DoBeforeConnect();
+ break;
+ case STATE_BEFORE_CONNECT_COMPLETE:
+ result = DoBeforeConnectComplete(result);
+ break;
+ case STATE_RESOLVE_PROXY:
+ DCHECK_EQ(OK, result);
+ result = DoResolveProxy();
+ break;
+ case STATE_RESOLVE_PROXY_COMPLETE:
+ result = DoResolveProxyComplete(result);
+ break;
+ case STATE_RESOLVE_HOST:
+ DCHECK_EQ(OK, result);
+ result = DoResolveHost();
+ break;
+ case STATE_RESOLVE_HOST_COMPLETE:
+ result = DoResolveHostComplete(result);
+ break;
+ case STATE_RESOLVE_PROTOCOL:
+ result = DoResolveProtocol(result);
+ break;
+ case STATE_RESOLVE_PROTOCOL_COMPLETE:
+ result = DoResolveProtocolComplete(result);
+ break;
+ case STATE_TCP_CONNECT:
+ result = DoTcpConnect(result);
+ break;
+ case STATE_TCP_CONNECT_COMPLETE:
+ result = DoTcpConnectComplete(result);
+ break;
+ case STATE_GENERATE_PROXY_AUTH_TOKEN:
+ result = DoGenerateProxyAuthToken();
+ break;
+ case STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE:
+ result = DoGenerateProxyAuthTokenComplete(result);
+ break;
+ case STATE_WRITE_TUNNEL_HEADERS:
+ DCHECK_EQ(OK, result);
+ result = DoWriteTunnelHeaders();
+ break;
+ case STATE_WRITE_TUNNEL_HEADERS_COMPLETE:
+ result = DoWriteTunnelHeadersComplete(result);
+ break;
+ case STATE_READ_TUNNEL_HEADERS:
+ DCHECK_EQ(OK, result);
+ result = DoReadTunnelHeaders();
+ break;
+ case STATE_READ_TUNNEL_HEADERS_COMPLETE:
+ result = DoReadTunnelHeadersComplete(result);
+ break;
+ case STATE_SOCKS_CONNECT:
+ DCHECK_EQ(OK, result);
+ result = DoSOCKSConnect();
+ break;
+ case STATE_SOCKS_CONNECT_COMPLETE:
+ result = DoSOCKSConnectComplete(result);
+ break;
+ case STATE_SECURE_PROXY_CONNECT:
+ DCHECK_EQ(OK, result);
+ result = DoSecureProxyConnect();
+ break;
+ case STATE_SECURE_PROXY_CONNECT_COMPLETE:
+ result = DoSecureProxyConnectComplete(result);
+ break;
+ case STATE_SECURE_PROXY_HANDLE_CERT_ERROR:
+ result = DoSecureProxyHandleCertError(result);
+ break;
+ case STATE_SECURE_PROXY_HANDLE_CERT_ERROR_COMPLETE:
+ result = DoSecureProxyHandleCertErrorComplete(result);
+ break;
+ case STATE_SSL_CONNECT:
+ DCHECK_EQ(OK, result);
+ result = DoSSLConnect();
+ break;
+ case STATE_SSL_CONNECT_COMPLETE:
+ result = DoSSLConnectComplete(result);
+ break;
+ case STATE_SSL_HANDLE_CERT_ERROR:
+ result = DoSSLHandleCertError(result);
+ break;
+ case STATE_SSL_HANDLE_CERT_ERROR_COMPLETE:
+ result = DoSSLHandleCertErrorComplete(result);
+ break;
+ case STATE_READ_WRITE:
+ result = DoReadWrite(result);
+ break;
+ case STATE_AUTH_REQUIRED:
+ // It might be called when DoClose is called while waiting in
+ // STATE_AUTH_REQUIRED.
+ Finish(result);
+ return;
+ case STATE_CLOSE:
+ DCHECK_LE(result, OK);
+ Finish(result);
+ return;
+ default:
+ NOTREACHED() << "bad state " << state;
+ Finish(result);
+ return;
+ }
+ if (state == STATE_RESOLVE_PROTOCOL && result == ERR_PROTOCOL_SWITCHED)
+ continue;
+ // If the connection is not established yet and had actual errors,
+ // record the error. In next iteration, it will close the connection.
+ if (state != STATE_READ_WRITE && result < ERR_IO_PENDING) {
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_SOCKET_STREAM_CONNECT, result);
+ }
+ } while (result != ERR_IO_PENDING);
+}
+
+int SocketStream::DoBeforeConnect() {
+ next_state_ = STATE_BEFORE_CONNECT_COMPLETE;
+ if (!context_ || !context_->network_delegate())
+ return OK;
+
+ int result = context_->network_delegate()->NotifyBeforeSocketStreamConnect(
+ this, io_callback_);
+ if (result != OK && result != ERR_IO_PENDING)
+ next_state_ = STATE_CLOSE;
+
+ return result;
+}
+
+int SocketStream::DoBeforeConnectComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ if (result == OK)
+ next_state_ = STATE_RESOLVE_PROXY;
+ else
+ next_state_ = STATE_CLOSE;
+
+ return result;
+}
+
+int SocketStream::DoResolveProxy() {
+ DCHECK(context_);
+ DCHECK(!pac_request_);
+ next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
+
+ if (!proxy_url_.is_valid()) {
+ next_state_ = STATE_CLOSE;
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ // TODO(toyoshim): Check server advertisement of SPDY through the HTTP
+ // Alternate-Protocol header, then switch to SPDY if SPDY is available.
+ // Usually we already have a session to the SPDY server because JavaScript
+ // running WebSocket itself would be served by SPDY. But, in some situation
+ // (E.g. Used by Chrome Extensions or used for cross origin connection), this
+ // connection might be the first one. At that time, we should check
+ // Alternate-Protocol header here for ws:// or TLS NPN extension for wss:// .
+
+ return context_->proxy_service()->ResolveProxy(
+ proxy_url_, &proxy_info_, io_callback_, &pac_request_, net_log_);
+}
+
+int SocketStream::DoResolveProxyComplete(int result) {
+ pac_request_ = NULL;
+ if (result != OK) {
+ DVLOG(1) << "Failed to resolve proxy: " << result;
+ if (delegate_)
+ delegate_->OnError(this, result);
+ proxy_info_.UseDirect();
+ }
+ if (proxy_info_.is_direct()) {
+ // If proxy was not found for original URL (i.e. websocket URL),
+ // try again with https URL, like Safari implementation.
+ // Note that we don't want to use http proxy, because we'll use tunnel
+ // proxy using CONNECT method, which is used by https proxy.
+ if (!proxy_url_.SchemeIs("https")) {
+ const std::string scheme = "https";
+ GURL::Replacements repl;
+ repl.SetSchemeStr(scheme);
+ proxy_url_ = url_.ReplaceComponents(repl);
+ DVLOG(1) << "Try https proxy: " << proxy_url_;
+ next_state_ = STATE_RESOLVE_PROXY;
+ return OK;
+ }
+ }
+
+ if (proxy_info_.is_empty()) {
+ // No proxies/direct to choose from. This happens when we don't support any
+ // of the proxies in the returned list.
+ return ERR_NO_SUPPORTED_PROXIES;
+ }
+
+ next_state_ = STATE_RESOLVE_HOST;
+ return OK;
+}
+
+int SocketStream::DoResolveHost() {
+ next_state_ = STATE_RESOLVE_HOST_COMPLETE;
+
+ DCHECK(!proxy_info_.is_empty());
+ if (proxy_info_.is_direct())
+ proxy_mode_ = kDirectConnection;
+ else if (proxy_info_.proxy_server().is_socks())
+ proxy_mode_ = kSOCKSProxy;
+ else
+ proxy_mode_ = kTunnelProxy;
+
+ // Determine the host and port to connect to.
+ HostPortPair host_port_pair;
+ if (proxy_mode_ != kDirectConnection) {
+ host_port_pair = proxy_info_.proxy_server().host_port_pair();
+ } else {
+ host_port_pair = HostPortPair::FromURL(url_);
+ }
+
+ HostResolver::RequestInfo resolve_info(host_port_pair);
+
+ DCHECK(context_->host_resolver());
+ resolver_.reset(new SingleRequestHostResolver(context_->host_resolver()));
+ return resolver_->Resolve(
+ resolve_info, &addresses_, base::Bind(&SocketStream::OnIOCompleted, this),
+ net_log_);
+}
+
+int SocketStream::DoResolveHostComplete(int result) {
+ if (result == OK)
+ next_state_ = STATE_RESOLVE_PROTOCOL;
+ else
+ next_state_ = STATE_CLOSE;
+ // TODO(ukai): if error occured, reconsider proxy after error.
+ return result;
+}
+
+int SocketStream::DoResolveProtocol(int result) {
+ DCHECK_EQ(OK, result);
+
+ if (!delegate_) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+
+ next_state_ = STATE_RESOLVE_PROTOCOL_COMPLETE;
+ result = delegate_->OnStartOpenConnection(this, io_callback_);
+ if (result == ERR_IO_PENDING)
+ metrics_->OnWaitConnection();
+ else if (result != OK && result != ERR_PROTOCOL_SWITCHED)
+ next_state_ = STATE_CLOSE;
+ return result;
+}
+
+int SocketStream::DoResolveProtocolComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ if (result == ERR_PROTOCOL_SWITCHED) {
+ next_state_ = STATE_CLOSE;
+ metrics_->OnCountWireProtocolType(
+ SocketStreamMetrics::WIRE_PROTOCOL_SPDY);
+ } else if (result == OK) {
+ next_state_ = STATE_TCP_CONNECT;
+ metrics_->OnCountWireProtocolType(
+ SocketStreamMetrics::WIRE_PROTOCOL_WEBSOCKET);
+ } else {
+ next_state_ = STATE_CLOSE;
+ }
+ return result;
+}
+
+int SocketStream::DoTcpConnect(int result) {
+ if (result != OK) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+ next_state_ = STATE_TCP_CONNECT_COMPLETE;
+ DCHECK(factory_);
+ connection_->SetSocket(
+ factory_->CreateTransportClientSocket(addresses_,
+ net_log_.net_log(),
+ net_log_.source()));
+ metrics_->OnStartConnection();
+ return connection_->socket()->Connect(io_callback_);
+}
+
+int SocketStream::DoTcpConnectComplete(int result) {
+ // TODO(ukai): if error occured, reconsider proxy after error.
+ if (result != OK) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+
+ if (proxy_mode_ == kTunnelProxy) {
+ if (proxy_info_.is_https())
+ next_state_ = STATE_SECURE_PROXY_CONNECT;
+ else
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ } else if (proxy_mode_ == kSOCKSProxy) {
+ next_state_ = STATE_SOCKS_CONNECT;
+ } else if (is_secure()) {
+ next_state_ = STATE_SSL_CONNECT;
+ } else {
+ result = DidEstablishConnection();
+ }
+ return result;
+}
+
+int SocketStream::DoGenerateProxyAuthToken() {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE;
+ if (!proxy_auth_controller_.get()) {
+ DCHECK(context_);
+ DCHECK(context_->http_transaction_factory());
+ DCHECK(context_->http_transaction_factory()->GetSession());
+ HttpNetworkSession* session =
+ context_->http_transaction_factory()->GetSession();
+ const char* scheme = proxy_info_.is_https() ? "https://" : "http://";
+ GURL auth_url(scheme +
+ proxy_info_.proxy_server().host_port_pair().ToString());
+ proxy_auth_controller_ =
+ new HttpAuthController(HttpAuth::AUTH_PROXY,
+ auth_url,
+ session->http_auth_cache(),
+ session->http_auth_handler_factory());
+ }
+ HttpRequestInfo request_info;
+ request_info.url = url_;
+ request_info.method = "CONNECT";
+ return proxy_auth_controller_->MaybeGenerateAuthToken(
+ &request_info, io_callback_, net_log_);
+}
+
+int SocketStream::DoGenerateProxyAuthTokenComplete(int result) {
+ if (result != OK) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+
+ next_state_ = STATE_WRITE_TUNNEL_HEADERS;
+ return result;
+}
+
+int SocketStream::DoWriteTunnelHeaders() {
+ DCHECK_EQ(kTunnelProxy, proxy_mode_);
+
+ next_state_ = STATE_WRITE_TUNNEL_HEADERS_COMPLETE;
+
+ if (!tunnel_request_headers_.get()) {
+ metrics_->OnCountConnectionType(SocketStreamMetrics::TUNNEL_CONNECTION);
+ tunnel_request_headers_ = new RequestHeaders();
+ tunnel_request_headers_bytes_sent_ = 0;
+ }
+ if (tunnel_request_headers_->headers_.empty()) {
+ HttpRequestHeaders request_headers;
+ request_headers.SetHeader("Host", GetHostAndOptionalPort(url_));
+ request_headers.SetHeader("Proxy-Connection", "keep-alive");
+ if (proxy_auth_controller_.get() && proxy_auth_controller_->HaveAuth())
+ proxy_auth_controller_->AddAuthorizationHeader(&request_headers);
+ tunnel_request_headers_->headers_ = base::StringPrintf(
+ "CONNECT %s HTTP/1.1\r\n"
+ "%s",
+ GetHostAndPort(url_).c_str(),
+ request_headers.ToString().c_str());
+ }
+ tunnel_request_headers_->SetDataOffset(tunnel_request_headers_bytes_sent_);
+ int buf_len = static_cast<int>(tunnel_request_headers_->headers_.size() -
+ tunnel_request_headers_bytes_sent_);
+ DCHECK_GT(buf_len, 0);
+ return connection_->socket()->Write(
+ tunnel_request_headers_.get(), buf_len, io_callback_);
+}
+
+int SocketStream::DoWriteTunnelHeadersComplete(int result) {
+ DCHECK_EQ(kTunnelProxy, proxy_mode_);
+
+ if (result < 0) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+
+ tunnel_request_headers_bytes_sent_ += result;
+ if (tunnel_request_headers_bytes_sent_ <
+ tunnel_request_headers_->headers_.size()) {
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ } else {
+ // Handling a cert error or a client cert request requires reconnection.
+ // DoWriteTunnelHeaders() will be called again.
+ // Thus |tunnel_request_headers_bytes_sent_| should be reset to 0 for
+ // sending |tunnel_request_headers_| correctly.
+ tunnel_request_headers_bytes_sent_ = 0;
+ next_state_ = STATE_READ_TUNNEL_HEADERS;
+ }
+ return OK;
+}
+
+int SocketStream::DoReadTunnelHeaders() {
+ DCHECK_EQ(kTunnelProxy, proxy_mode_);
+
+ next_state_ = STATE_READ_TUNNEL_HEADERS_COMPLETE;
+
+ if (!tunnel_response_headers_.get()) {
+ tunnel_response_headers_ = new ResponseHeaders();
+ tunnel_response_headers_capacity_ = kMaxTunnelResponseHeadersSize;
+ tunnel_response_headers_->Realloc(tunnel_response_headers_capacity_);
+ tunnel_response_headers_len_ = 0;
+ }
+
+ int buf_len = tunnel_response_headers_capacity_ -
+ tunnel_response_headers_len_;
+ tunnel_response_headers_->SetDataOffset(tunnel_response_headers_len_);
+ CHECK(tunnel_response_headers_->data());
+
+ return connection_->socket()->Read(
+ tunnel_response_headers_.get(), buf_len, io_callback_);
+}
+
+int SocketStream::DoReadTunnelHeadersComplete(int result) {
+ DCHECK_EQ(kTunnelProxy, proxy_mode_);
+
+ if (result < 0) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+
+ if (result == 0) {
+ // 0 indicates end-of-file, so socket was closed.
+ next_state_ = STATE_CLOSE;
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ tunnel_response_headers_len_ += result;
+ DCHECK(tunnel_response_headers_len_ <= tunnel_response_headers_capacity_);
+
+ int eoh = HttpUtil::LocateEndOfHeaders(
+ tunnel_response_headers_->headers(), tunnel_response_headers_len_, 0);
+ if (eoh == -1) {
+ if (tunnel_response_headers_len_ >= kMaxTunnelResponseHeadersSize) {
+ next_state_ = STATE_CLOSE;
+ return ERR_RESPONSE_HEADERS_TOO_BIG;
+ }
+
+ next_state_ = STATE_READ_TUNNEL_HEADERS;
+ return OK;
+ }
+ // DidReadResponseHeaders
+ scoped_refptr<HttpResponseHeaders> headers;
+ headers = new HttpResponseHeaders(
+ HttpUtil::AssembleRawHeaders(tunnel_response_headers_->headers(), eoh));
+ if (headers->GetParsedHttpVersion() < HttpVersion(1, 0)) {
+ // Require the "HTTP/1.x" status line.
+ next_state_ = STATE_CLOSE;
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+ switch (headers->response_code()) {
+ case 200: // OK
+ if (is_secure()) {
+ DCHECK_EQ(eoh, tunnel_response_headers_len_);
+ next_state_ = STATE_SSL_CONNECT;
+ } else {
+ result = DidEstablishConnection();
+ if (result < 0) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+ if ((eoh < tunnel_response_headers_len_) && delegate_)
+ delegate_->OnReceivedData(
+ this, tunnel_response_headers_->headers() + eoh,
+ tunnel_response_headers_len_ - eoh);
+ }
+ return OK;
+ case 407: // Proxy Authentication Required.
+ if (proxy_mode_ != kTunnelProxy)
+ return ERR_UNEXPECTED_PROXY_AUTH;
+
+ result = proxy_auth_controller_->HandleAuthChallenge(
+ headers, false, true, net_log_);
+ if (result != OK)
+ return result;
+ DCHECK(!proxy_info_.is_empty());
+ next_state_ = STATE_AUTH_REQUIRED;
+ if (proxy_auth_controller_->HaveAuth()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoRestartWithAuth, this));
+ return ERR_IO_PENDING;
+ }
+ if (delegate_) {
+ // Wait until RestartWithAuth or Close is called.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&SocketStream::DoAuthRequired, this));
+ return ERR_IO_PENDING;
+ }
+ break;
+ default:
+ break;
+ }
+ next_state_ = STATE_CLOSE;
+ return ERR_TUNNEL_CONNECTION_FAILED;
+}
+
+int SocketStream::DoSOCKSConnect() {
+ DCHECK_EQ(kSOCKSProxy, proxy_mode_);
+
+ next_state_ = STATE_SOCKS_CONNECT_COMPLETE;
+
+ HostResolver::RequestInfo req_info(HostPortPair::FromURL(url_));
+
+ DCHECK(!proxy_info_.is_empty());
+ scoped_ptr<StreamSocket> s;
+ if (proxy_info_.proxy_server().scheme() == ProxyServer::SCHEME_SOCKS5) {
+ s.reset(new SOCKS5ClientSocket(connection_.Pass(), req_info));
+ } else {
+ s.reset(new SOCKSClientSocket(
+ connection_.Pass(), req_info, context_->host_resolver()));
+ }
+ connection_.reset(new ClientSocketHandle);
+ connection_->SetSocket(s.Pass());
+ metrics_->OnCountConnectionType(SocketStreamMetrics::SOCKS_CONNECTION);
+ return connection_->socket()->Connect(io_callback_);
+}
+
+int SocketStream::DoSOCKSConnectComplete(int result) {
+ DCHECK_EQ(kSOCKSProxy, proxy_mode_);
+
+ if (result == OK) {
+ if (is_secure())
+ next_state_ = STATE_SSL_CONNECT;
+ else
+ result = DidEstablishConnection();
+ } else {
+ next_state_ = STATE_CLOSE;
+ }
+ return result;
+}
+
+int SocketStream::DoSecureProxyConnect() {
+ DCHECK(factory_);
+ SSLClientSocketContext ssl_context;
+ ssl_context.cert_verifier = context_->cert_verifier();
+ ssl_context.transport_security_state = context_->transport_security_state();
+ ssl_context.server_bound_cert_service = context_->server_bound_cert_service();
+ scoped_ptr<StreamSocket> socket(factory_->CreateSSLClientSocket(
+ connection_.Pass(),
+ proxy_info_.proxy_server().host_port_pair(),
+ proxy_ssl_config_,
+ ssl_context));
+ connection_.reset(new ClientSocketHandle);
+ connection_->SetSocket(socket.Pass());
+ next_state_ = STATE_SECURE_PROXY_CONNECT_COMPLETE;
+ metrics_->OnCountConnectionType(SocketStreamMetrics::SECURE_PROXY_CONNECTION);
+ return connection_->socket()->Connect(io_callback_);
+}
+
+int SocketStream::DoSecureProxyConnectComplete(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ // Reconnect with client authentication.
+ if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED)
+ return HandleCertificateRequest(result, &proxy_ssl_config_);
+
+ if (IsCertificateError(result))
+ next_state_ = STATE_SECURE_PROXY_HANDLE_CERT_ERROR;
+ else if (result == OK)
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ else
+ next_state_ = STATE_CLOSE;
+ return result;
+}
+
+int SocketStream::DoSecureProxyHandleCertError(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(IsCertificateError(result));
+ result = HandleCertificateError(result);
+ if (result == ERR_IO_PENDING)
+ next_state_ = STATE_SECURE_PROXY_HANDLE_CERT_ERROR_COMPLETE;
+ else
+ next_state_ = STATE_CLOSE;
+ return result;
+}
+
+int SocketStream::DoSecureProxyHandleCertErrorComplete(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ if (result == OK) {
+ if (!connection_->socket()->IsConnectedAndIdle())
+ return AllowCertErrorForReconnection(&proxy_ssl_config_);
+ next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN;
+ } else {
+ next_state_ = STATE_CLOSE;
+ }
+ return result;
+}
+
+int SocketStream::DoSSLConnect() {
+ DCHECK(factory_);
+ SSLClientSocketContext ssl_context;
+ ssl_context.cert_verifier = context_->cert_verifier();
+ ssl_context.transport_security_state = context_->transport_security_state();
+ ssl_context.server_bound_cert_service = context_->server_bound_cert_service();
+ scoped_ptr<StreamSocket> socket(
+ factory_->CreateSSLClientSocket(connection_.Pass(),
+ HostPortPair::FromURL(url_),
+ server_ssl_config_,
+ ssl_context));
+ connection_.reset(new ClientSocketHandle);
+ connection_->SetSocket(socket.Pass());
+ next_state_ = STATE_SSL_CONNECT_COMPLETE;
+ metrics_->OnCountConnectionType(SocketStreamMetrics::SSL_CONNECTION);
+ return connection_->socket()->Connect(io_callback_);
+}
+
+int SocketStream::DoSSLConnectComplete(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ // Reconnect with client authentication.
+ if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED)
+ return HandleCertificateRequest(result, &server_ssl_config_);
+
+ if (IsCertificateError(result))
+ next_state_ = STATE_SSL_HANDLE_CERT_ERROR;
+ else if (result == OK)
+ result = DidEstablishConnection();
+ else
+ next_state_ = STATE_CLOSE;
+ return result;
+}
+
+int SocketStream::DoSSLHandleCertError(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ DCHECK(IsCertificateError(result));
+ result = HandleCertificateError(result);
+ if (result == OK || result == ERR_IO_PENDING)
+ next_state_ = STATE_SSL_HANDLE_CERT_ERROR_COMPLETE;
+ else
+ next_state_ = STATE_CLOSE;
+ return result;
+}
+
+int SocketStream::DoSSLHandleCertErrorComplete(int result) {
+ DCHECK_EQ(STATE_NONE, next_state_);
+ // TODO(toyoshim): Upgrade to SPDY through TLS NPN extension if possible.
+ // If we use HTTPS and this is the first connection to the SPDY server,
+ // we should take care of TLS NPN extension here.
+
+ if (result == OK) {
+ if (!connection_->socket()->IsConnectedAndIdle())
+ return AllowCertErrorForReconnection(&server_ssl_config_);
+ result = DidEstablishConnection();
+ } else {
+ next_state_ = STATE_CLOSE;
+ }
+ return result;
+}
+
+int SocketStream::DoReadWrite(int result) {
+ if (result < OK) {
+ next_state_ = STATE_CLOSE;
+ return result;
+ }
+ if (!connection_->socket() || !connection_->socket()->IsConnected()) {
+ next_state_ = STATE_CLOSE;
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ // If client has requested close(), and there's nothing to write, then
+ // let's close the socket.
+ // We don't care about receiving data after the socket is closed.
+ if (closing_ && !current_write_buf_.get() && pending_write_bufs_.empty()) {
+ connection_->socket()->Disconnect();
+ next_state_ = STATE_CLOSE;
+ return OK;
+ }
+
+ next_state_ = STATE_READ_WRITE;
+
+ // If server already closed the socket, we don't try to read.
+ if (!server_closed_) {
+ if (!read_buf_.get()) {
+ // No read pending and server didn't close the socket.
+ read_buf_ = new IOBuffer(kReadBufferSize);
+ result = connection_->socket()->Read(
+ read_buf_.get(),
+ kReadBufferSize,
+ base::Bind(&SocketStream::OnReadCompleted, base::Unretained(this)));
+ if (result > 0) {
+ return DidReceiveData(result);
+ } else if (result == 0) {
+ // 0 indicates end-of-file, so socket was closed.
+ next_state_ = STATE_CLOSE;
+ server_closed_ = true;
+ return ERR_CONNECTION_CLOSED;
+ }
+ // If read is pending, try write as well.
+ // Otherwise, return the result and do next loop (to close the
+ // connection).
+ if (result != ERR_IO_PENDING) {
+ next_state_ = STATE_CLOSE;
+ server_closed_ = true;
+ return result;
+ }
+ }
+ // Read is pending.
+ DCHECK(read_buf_.get());
+ }
+
+ if (waiting_for_write_completion_)
+ return ERR_IO_PENDING;
+
+ if (!current_write_buf_.get()) {
+ if (pending_write_bufs_.empty()) {
+ // Nothing buffered for send.
+ return ERR_IO_PENDING;
+ }
+
+ current_write_buf_ = new DrainableIOBuffer(
+ pending_write_bufs_.front().get(), pending_write_bufs_.front()->size());
+ pending_write_bufs_.pop_front();
+ }
+
+ result = connection_->socket()->Write(
+ current_write_buf_.get(),
+ current_write_buf_->BytesRemaining(),
+ base::Bind(&SocketStream::OnWriteCompleted, base::Unretained(this)));
+
+ if (result == ERR_IO_PENDING) {
+ waiting_for_write_completion_ = true;
+ } else if (result < 0) {
+ // Shortcut. Enter STATE_CLOSE now by changing next_state_ here than by
+ // calling DoReadWrite() again with the error code.
+ next_state_ = STATE_CLOSE;
+ } else if (result > 0) {
+ // Write is not pending. Return OK and do next loop.
+ DidSendData(result);
+ result = OK;
+ }
+
+ return result;
+}
+
+GURL SocketStream::ProxyAuthOrigin() const {
+ DCHECK(!proxy_info_.is_empty());
+ return GURL("http://" +
+ proxy_info_.proxy_server().host_port_pair().ToString());
+}
+
+int SocketStream::HandleCertificateRequest(int result, SSLConfig* ssl_config) {
+ if (ssl_config->send_client_cert) {
+ // We already have performed SSL client authentication once and failed.
+ return result;
+ }
+
+ DCHECK(connection_->socket());
+ scoped_refptr<SSLCertRequestInfo> cert_request_info = new SSLCertRequestInfo;
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ ssl_socket->GetSSLCertRequestInfo(cert_request_info.get());
+
+ HttpTransactionFactory* factory = context_->http_transaction_factory();
+ if (!factory)
+ return result;
+ scoped_refptr<HttpNetworkSession> session = factory->GetSession();
+ if (!session.get())
+ return result;
+
+ // If the user selected one of the certificates in client_certs or declined
+ // to provide one for this server before, use the past decision
+ // automatically.
+ scoped_refptr<X509Certificate> client_cert;
+ if (!session->ssl_client_auth_cache()->Lookup(
+ cert_request_info->host_and_port, &client_cert)) {
+ return result;
+ }
+
+ // Note: |client_cert| may be NULL, indicating that the caller
+ // wishes to proceed anonymously (eg: continue the handshake
+ // without sending a client cert)
+ //
+ // Check that the certificate selected is still a certificate the server
+ // is likely to accept, based on the criteria supplied in the
+ // CertificateRequest message.
+ const std::vector<std::string>& cert_authorities =
+ cert_request_info->cert_authorities;
+ if (client_cert.get() && !cert_authorities.empty() &&
+ !client_cert->IsIssuedByEncoded(cert_authorities)) {
+ return result;
+ }
+
+ ssl_config->send_client_cert = true;
+ ssl_config->client_cert = client_cert;
+ next_state_ = STATE_TCP_CONNECT;
+ return OK;
+}
+
+int SocketStream::AllowCertErrorForReconnection(SSLConfig* ssl_config) {
+ DCHECK(ssl_config);
+ // The SSL handshake didn't finish, or the server closed the SSL connection.
+ // So, we should restart establishing connection with the certificate in
+ // allowed bad certificates in |ssl_config|.
+ // See also net/http/http_network_transaction.cc HandleCertificateError() and
+ // RestartIgnoringLastError().
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ SSLInfo ssl_info;
+ ssl_socket->GetSSLInfo(&ssl_info);
+ if (ssl_info.cert.get() == NULL ||
+ ssl_config->IsAllowedBadCert(ssl_info.cert.get(), NULL)) {
+ // If we already have the certificate in the set of allowed bad
+ // certificates, we did try it and failed again, so we should not
+ // retry again: the connection should fail at last.
+ next_state_ = STATE_CLOSE;
+ return ERR_UNEXPECTED;
+ }
+ // Add the bad certificate to the set of allowed certificates in the
+ // SSL config object.
+ SSLConfig::CertAndStatus bad_cert;
+ if (!X509Certificate::GetDEREncoded(ssl_info.cert->os_cert_handle(),
+ &bad_cert.der_cert)) {
+ next_state_ = STATE_CLOSE;
+ return ERR_UNEXPECTED;
+ }
+ bad_cert.cert_status = ssl_info.cert_status;
+ ssl_config->allowed_bad_certs.push_back(bad_cert);
+ // Restart connection ignoring the bad certificate.
+ connection_->socket()->Disconnect();
+ connection_->SetSocket(scoped_ptr<StreamSocket>());
+ next_state_ = STATE_TCP_CONNECT;
+ return OK;
+}
+
+void SocketStream::DoAuthRequired() {
+ if (delegate_ && proxy_auth_controller_.get())
+ delegate_->OnAuthRequired(this, proxy_auth_controller_->auth_info().get());
+ else
+ DoLoop(ERR_UNEXPECTED);
+}
+
+void SocketStream::DoRestartWithAuth() {
+ DCHECK_EQ(next_state_, STATE_AUTH_REQUIRED);
+ tunnel_request_headers_ = NULL;
+ tunnel_request_headers_bytes_sent_ = 0;
+ tunnel_response_headers_ = NULL;
+ tunnel_response_headers_capacity_ = 0;
+ tunnel_response_headers_len_ = 0;
+
+ next_state_ = STATE_TCP_CONNECT;
+ DoLoop(OK);
+}
+
+int SocketStream::HandleCertificateError(int result) {
+ DCHECK(IsCertificateError(result));
+ SSLClientSocket* ssl_socket =
+ static_cast<SSLClientSocket*>(connection_->socket());
+ DCHECK(ssl_socket);
+
+ if (!context_)
+ return result;
+
+ if (SSLClientSocket::IgnoreCertError(result, LOAD_IGNORE_ALL_CERT_ERRORS)) {
+ const HttpNetworkSession::Params* session_params =
+ context_->GetNetworkSessionParams();
+ if (session_params && session_params->ignore_certificate_errors)
+ return OK;
+ }
+
+ if (!delegate_)
+ return result;
+
+ SSLInfo ssl_info;
+ ssl_socket->GetSSLInfo(&ssl_info);
+
+ TransportSecurityState::DomainState domain_state;
+ const bool fatal = context_->transport_security_state() &&
+ context_->transport_security_state()->GetDomainState(url_.host(),
+ SSLConfigService::IsSNIAvailable(context_->ssl_config_service()),
+ &domain_state) &&
+ domain_state.ShouldSSLErrorsBeFatal();
+
+ delegate_->OnSSLCertificateError(this, ssl_info, fatal);
+ return ERR_IO_PENDING;
+}
+
+} // namespace net
diff --git a/chromium/net/socket_stream/socket_stream.h b/chromium/net/socket_stream/socket_stream.h
new file mode 100644
index 00000000000..90aeb8c54b9
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream.h
@@ -0,0 +1,395 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_STREAM_SOCKET_STREAM_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_H_
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/privacy_mode.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+class AuthChallengeInfo;
+class CertVerifier;
+class ClientSocketFactory;
+class ClientSocketHandle;
+class CookieOptions;
+class HostResolver;
+class HttpAuthController;
+class SSLInfo;
+class ServerBoundCertService;
+class SingleRequestHostResolver;
+class SocketStreamMetrics;
+class TransportSecurityState;
+class URLRequestContext;
+
+// SocketStream is used to implement Web Sockets.
+// It provides plain full-duplex stream with proxy and SSL support.
+// For proxy authentication, only basic mechanisum is supported. It will try
+// authentication identity for proxy URL first. If server requires proxy
+// authentication, it will try authentication identity for realm that server
+// requests.
+class NET_EXPORT SocketStream
+ : public base::RefCountedThreadSafe<SocketStream> {
+ public:
+ // Derive from this class and add your own data members to associate extra
+ // information with a SocketStream. Use GetUserData(key) and
+ // SetUserData(key, data).
+ class UserData {
+ public:
+ UserData() {}
+ virtual ~UserData() {}
+ };
+
+ class NET_EXPORT Delegate {
+ public:
+ virtual int OnStartOpenConnection(SocketStream* socket,
+ const CompletionCallback& callback);
+
+ // Called when a socket stream has been connected. The socket stream is
+ // allowed to buffer pending send data at most |max_pending_send_allowed|
+ // bytes. A client of the socket stream should keep track of how much
+ // pending send data it has and must not call SendData() if the pending
+ // data goes over |max_pending_send_allowed| bytes.
+ virtual void OnConnected(SocketStream* socket,
+ int max_pending_send_allowed) = 0;
+
+ // Called when |amount_sent| bytes of data are sent.
+ virtual void OnSentData(SocketStream* socket,
+ int amount_sent) = 0;
+
+ // Called when |len| bytes of |data| are received.
+ virtual void OnReceivedData(SocketStream* socket,
+ const char* data, int len) = 0;
+
+ // Called when the socket stream has been closed.
+ virtual void OnClose(SocketStream* socket) = 0;
+
+ // Called when proxy authentication required.
+ // The delegate should call RestartWithAuth() if credential for |auth_info|
+ // is found in password database, or call Close() to close the connection.
+ virtual void OnAuthRequired(SocketStream* socket,
+ AuthChallengeInfo* auth_info);
+
+ // Called when using SSL and the server responds with a certificate with an
+ // error. The delegate should call CancelBecauseOfCertError() or
+ // ContinueDespiteCertError() to resume connection handling.
+ virtual void OnSSLCertificateError(SocketStream* socket,
+ const SSLInfo& ssl_info,
+ bool fatal);
+
+ // Called when an error occured.
+ // This is only for error reporting to the delegate.
+ // |error| is net::Error.
+ virtual void OnError(const SocketStream* socket, int error) {}
+
+ // Called when reading cookies to allow the delegate to block access to the
+ // cookie.
+ virtual bool CanGetCookies(SocketStream* socket, const GURL& url);
+
+ // Called when a cookie is set to allow the delegate to block access to the
+ // cookie.
+ virtual bool CanSetCookie(SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ CookieOptions* options);
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ SocketStream(const GURL& url, Delegate* delegate);
+
+ // The user data allows the clients to associate data with this job.
+ // Multiple user data values can be stored under different keys.
+ // This job will TAKE OWNERSHIP of the given data pointer, and will
+ // delete the object if it is changed or the job is destroyed.
+ UserData* GetUserData(const void* key) const;
+ void SetUserData(const void* key, UserData* data);
+
+ const GURL& url() const { return url_; }
+ bool is_secure() const;
+ const AddressList& address_list() const { return addresses_; }
+ Delegate* delegate() const { return delegate_; }
+ int max_pending_send_allowed() const { return max_pending_send_allowed_; }
+
+ URLRequestContext* context() { return context_; }
+ // There're some asynchronous operations and members that are constructed from
+ // |context|. Be careful when you use this for the second time or more.
+ void set_context(URLRequestContext* context);
+
+ const SSLConfig& server_ssl_config() const { return server_ssl_config_; }
+ PrivacyMode privacy_mode() const { return privacy_mode_; }
+ void CheckPrivacyMode();
+
+ BoundNetLog* net_log() { return &net_log_; }
+
+ // Opens the connection on the IO thread.
+ // Once the connection is established, calls delegate's OnConnected.
+ virtual void Connect();
+
+ // Buffers |data| of |len| bytes for send and returns true if successful.
+ // If size of buffered data exceeds |max_pending_send_allowed_|, sends no
+ // data and returns false. |len| must be positive.
+ virtual bool SendData(const char* data, int len);
+
+ // Requests to close the connection.
+ // Once the connection is closed, calls delegate's OnClose.
+ virtual void Close();
+
+ // Restarts with authentication info.
+ // Should be used for response of OnAuthRequired.
+ virtual void RestartWithAuth(const AuthCredentials& credentials);
+
+ // Detach delegate. Call before delegate is deleted.
+ // Once delegate is detached, close the socket stream and never call delegate
+ // back.
+ virtual void DetachDelegate();
+
+ const ProxyServer& proxy_server() const;
+
+ // Sets an alternative ClientSocketFactory. Doesn't take ownership of
+ // |factory|. For testing purposes only.
+ void SetClientSocketFactory(ClientSocketFactory* factory);
+
+ // Cancels the connection because of an error.
+ // |error| is net::Error which represents the error.
+ void CancelWithError(int error);
+
+ // Cancels the connection because of receiving a certificate with an error.
+ void CancelWithSSLError(const SSLInfo& ssl_info);
+
+ // Continues to establish the connection in spite of an error. Usually this
+ // case happens because users allow certificate with an error by manual
+ // actions on alert dialog or browser cached such kinds of user actions.
+ void ContinueDespiteError();
+
+ protected:
+ friend class base::RefCountedThreadSafe<SocketStream>;
+ virtual ~SocketStream();
+
+ Delegate* delegate_;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SocketStreamTest, IOPending);
+ FRIEND_TEST_ALL_PREFIXES(SocketStreamTest, SwitchAfterPending);
+
+ friend class WebSocketThrottleTest;
+
+ typedef std::map<const void*, linked_ptr<UserData> > UserDataMap;
+ typedef std::deque< scoped_refptr<IOBufferWithSize> > PendingDataQueue;
+
+ class RequestHeaders : public IOBuffer {
+ public:
+ RequestHeaders() : IOBuffer() {}
+
+ void SetDataOffset(size_t offset) {
+ data_ = const_cast<char*>(headers_.data()) + offset;
+ }
+
+ std::string headers_;
+
+ private:
+ virtual ~RequestHeaders();
+ };
+
+ class ResponseHeaders : public IOBuffer {
+ public:
+ ResponseHeaders();
+
+ void SetDataOffset(size_t offset) { data_ = headers_.get() + offset; }
+ char* headers() const { return headers_.get(); }
+ void Reset() { headers_.reset(); }
+ void Realloc(size_t new_size);
+
+ private:
+ virtual ~ResponseHeaders();
+
+ scoped_ptr_malloc<char> headers_;
+ };
+
+ enum State {
+ STATE_NONE,
+ STATE_BEFORE_CONNECT,
+ STATE_BEFORE_CONNECT_COMPLETE,
+ STATE_RESOLVE_PROXY,
+ STATE_RESOLVE_PROXY_COMPLETE,
+ STATE_RESOLVE_HOST,
+ STATE_RESOLVE_HOST_COMPLETE,
+ STATE_RESOLVE_PROTOCOL,
+ STATE_RESOLVE_PROTOCOL_COMPLETE,
+ STATE_TCP_CONNECT,
+ STATE_TCP_CONNECT_COMPLETE,
+ STATE_GENERATE_PROXY_AUTH_TOKEN,
+ STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE,
+ STATE_WRITE_TUNNEL_HEADERS,
+ STATE_WRITE_TUNNEL_HEADERS_COMPLETE,
+ STATE_READ_TUNNEL_HEADERS,
+ STATE_READ_TUNNEL_HEADERS_COMPLETE,
+ STATE_SOCKS_CONNECT,
+ STATE_SOCKS_CONNECT_COMPLETE,
+ STATE_SECURE_PROXY_CONNECT,
+ STATE_SECURE_PROXY_CONNECT_COMPLETE,
+ STATE_SECURE_PROXY_HANDLE_CERT_ERROR,
+ STATE_SECURE_PROXY_HANDLE_CERT_ERROR_COMPLETE,
+ STATE_SSL_CONNECT,
+ STATE_SSL_CONNECT_COMPLETE,
+ STATE_SSL_HANDLE_CERT_ERROR,
+ STATE_SSL_HANDLE_CERT_ERROR_COMPLETE,
+ STATE_READ_WRITE,
+ STATE_AUTH_REQUIRED,
+ STATE_CLOSE,
+ };
+
+ enum ProxyMode {
+ kDirectConnection, // If using a direct connection
+ kTunnelProxy, // If using a tunnel (CONNECT method as HTTPS)
+ kSOCKSProxy, // If using a SOCKS proxy
+ };
+
+ // Use the same number as HttpNetworkTransaction::kMaxHeaderBufSize.
+ enum { kMaxTunnelResponseHeadersSize = 32768 }; // 32 kilobytes.
+
+ // Used for WebSocketThrottleTest.
+ void set_addresses(const AddressList& addresses);
+
+ void DoClose();
+
+ // Finishes the job.
+ // Calls OnError and OnClose of delegate, and no more
+ // notifications will be sent to delegate.
+ void Finish(int result);
+
+ int DidEstablishConnection();
+ int DidReceiveData(int result);
+ // Given the number of bytes sent,
+ // - notifies the |delegate_| and |metrics_| of this event.
+ // - drains sent data from |current_write_buf_|.
+ // - if |current_write_buf_| has been fully sent, sets NULL to
+ // |current_write_buf_| to get ready for next write.
+ // and then, returns OK.
+ void DidSendData(int result);
+
+ void OnIOCompleted(int result);
+ void OnReadCompleted(int result);
+ void OnWriteCompleted(int result);
+
+ void DoLoop(int result);
+
+ int DoBeforeConnect();
+ int DoBeforeConnectComplete(int result);
+ int DoResolveProxy();
+ int DoResolveProxyComplete(int result);
+ int DoResolveHost();
+ int DoResolveHostComplete(int result);
+ int DoResolveProtocol(int result);
+ int DoResolveProtocolComplete(int result);
+ int DoTcpConnect(int result);
+ int DoTcpConnectComplete(int result);
+ int DoGenerateProxyAuthToken();
+ int DoGenerateProxyAuthTokenComplete(int result);
+ int DoWriteTunnelHeaders();
+ int DoWriteTunnelHeadersComplete(int result);
+ int DoReadTunnelHeaders();
+ int DoReadTunnelHeadersComplete(int result);
+ int DoSOCKSConnect();
+ int DoSOCKSConnectComplete(int result);
+ int DoSecureProxyConnect();
+ int DoSecureProxyConnectComplete(int result);
+ int DoSecureProxyHandleCertError(int result);
+ int DoSecureProxyHandleCertErrorComplete(int result);
+ int DoSSLConnect();
+ int DoSSLConnectComplete(int result);
+ int DoSSLHandleCertError(int result);
+ int DoSSLHandleCertErrorComplete(int result);
+ int DoReadWrite(int result);
+
+ GURL ProxyAuthOrigin() const;
+ int HandleAuthChallenge(const HttpResponseHeaders* headers);
+ int HandleCertificateRequest(int result, SSLConfig* ssl_config);
+ void DoAuthRequired();
+ void DoRestartWithAuth();
+
+ int HandleCertificateError(int result);
+ int AllowCertErrorForReconnection(SSLConfig* ssl_config);
+
+ // Returns the sum of the size of buffers in |pending_write_bufs_|.
+ size_t GetTotalSizeOfPendingWriteBufs() const;
+
+ BoundNetLog net_log_;
+
+ GURL url_;
+ // The number of bytes allowed to be buffered in this object. If the size of
+ // buffered data which is
+ // current_write_buf_.BytesRemaining() +
+ // sum of the size of buffers in |pending_write_bufs_|
+ // exceeds this limit, SendData() fails.
+ int max_pending_send_allowed_;
+ URLRequestContext* context_;
+
+ UserDataMap user_data_;
+
+ State next_state_;
+ ClientSocketFactory* factory_;
+
+ ProxyMode proxy_mode_;
+
+ GURL proxy_url_;
+ ProxyService::PacRequest* pac_request_;
+ ProxyInfo proxy_info_;
+
+ scoped_refptr<HttpAuthController> proxy_auth_controller_;
+
+ scoped_refptr<RequestHeaders> tunnel_request_headers_;
+ size_t tunnel_request_headers_bytes_sent_;
+ scoped_refptr<ResponseHeaders> tunnel_response_headers_;
+ int tunnel_response_headers_capacity_;
+ int tunnel_response_headers_len_;
+
+ scoped_ptr<SingleRequestHostResolver> resolver_;
+ AddressList addresses_;
+ scoped_ptr<ClientSocketHandle> connection_;
+
+ SSLConfig server_ssl_config_;
+ SSLConfig proxy_ssl_config_;
+ PrivacyMode privacy_mode_;
+
+ CompletionCallback io_callback_;
+
+ scoped_refptr<IOBuffer> read_buf_;
+ int read_buf_size_;
+
+ // Buffer to hold data to pass to socket_.
+ scoped_refptr<DrainableIOBuffer> current_write_buf_;
+ // True iff there's no error and this instance is waiting for completion of
+ // Write operation by socket_.
+ bool waiting_for_write_completion_;
+ PendingDataQueue pending_write_bufs_;
+
+ bool closing_;
+ bool server_closed_;
+
+ scoped_ptr<SocketStreamMetrics> metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStream);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_H_
diff --git a/chromium/net/socket_stream/socket_stream_job.cc b/chromium/net/socket_stream/socket_stream_job.cc
new file mode 100644
index 00000000000..9c13a8f3a66
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_job.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+
+#include "net/socket_stream/socket_stream_job.h"
+
+#include "base/memory/singleton.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket_stream/socket_stream_job_manager.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/url_request_context.h"
+
+namespace net {
+
+// static
+SocketStreamJob::ProtocolFactory* SocketStreamJob::RegisterProtocolFactory(
+ const std::string& scheme, ProtocolFactory* factory) {
+ return SocketStreamJobManager::GetInstance()->RegisterProtocolFactory(
+ scheme, factory);
+}
+
+// static
+SocketStreamJob* SocketStreamJob::CreateSocketStreamJob(
+ const GURL& url,
+ SocketStream::Delegate* delegate,
+ TransportSecurityState* sts,
+ SSLConfigService* ssl) {
+ GURL socket_url(url);
+ TransportSecurityState::DomainState domain_state;
+ if (url.scheme() == "ws" && sts && sts->GetDomainState(
+ url.host(), SSLConfigService::IsSNIAvailable(ssl), &domain_state) &&
+ domain_state.ShouldUpgradeToSSL()) {
+ url_canon::Replacements<char> replacements;
+ static const char kNewScheme[] = "wss";
+ replacements.SetScheme(kNewScheme,
+ url_parse::Component(0, strlen(kNewScheme)));
+ socket_url = url.ReplaceComponents(replacements);
+ }
+ return SocketStreamJobManager::GetInstance()->CreateJob(socket_url, delegate);
+}
+
+SocketStreamJob::SocketStreamJob() {}
+
+SocketStream::UserData* SocketStreamJob::GetUserData(const void* key) const {
+ return socket_->GetUserData(key);
+}
+
+void SocketStreamJob::SetUserData(const void* key,
+ SocketStream::UserData* data) {
+ socket_->SetUserData(key, data);
+}
+
+void SocketStreamJob::Connect() {
+ socket_->Connect();
+}
+
+bool SocketStreamJob::SendData(const char* data, int len) {
+ return socket_->SendData(data, len);
+}
+
+void SocketStreamJob::Close() {
+ socket_->Close();
+}
+
+void SocketStreamJob::RestartWithAuth(const AuthCredentials& credentials) {
+ socket_->RestartWithAuth(credentials);
+}
+
+void SocketStreamJob::CancelWithError(int error) {
+ socket_->CancelWithError(error);
+}
+
+void SocketStreamJob::CancelWithSSLError(const net::SSLInfo& ssl_info) {
+ socket_->CancelWithSSLError(ssl_info);
+}
+
+void SocketStreamJob::ContinueDespiteError() {
+ socket_->ContinueDespiteError();
+}
+
+void SocketStreamJob::DetachDelegate() {
+ socket_->DetachDelegate();
+}
+
+SocketStreamJob::~SocketStreamJob() {}
+
+} // namespace net
diff --git a/chromium/net/socket_stream/socket_stream_job.h b/chromium/net/socket_stream/socket_stream_job.h
new file mode 100644
index 00000000000..d12e73aff31
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_job.h
@@ -0,0 +1,88 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/socket_stream/socket_stream.h"
+
+class GURL;
+
+namespace net {
+
+class SSLConfigService;
+class SSLInfo;
+class TransportSecurityState;
+
+// SocketStreamJob represents full-duplex communication over SocketStream.
+// If a protocol (e.g. WebSocket protocol) needs to inspect/modify data
+// over SocketStream, you can implement protocol specific job (e.g.
+// WebSocketJob) to do some work on data over SocketStream.
+// Registers the protocol specific SocketStreamJob by RegisterProtocolFactory
+// and call CreateSocketStreamJob to create SocketStreamJob for the URL.
+class NET_EXPORT SocketStreamJob
+ : public base::RefCountedThreadSafe<SocketStreamJob> {
+ public:
+ // Callback function implemented by protocol handlers to create new jobs.
+ typedef SocketStreamJob* (ProtocolFactory)(const GURL& url,
+ SocketStream::Delegate* delegate);
+
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ static SocketStreamJob* CreateSocketStreamJob(
+ const GURL& url,
+ SocketStream::Delegate* delegate,
+ TransportSecurityState* sts,
+ SSLConfigService* ssl);
+
+ SocketStreamJob();
+ void InitSocketStream(SocketStream* socket) {
+ socket_ = socket;
+ }
+
+ virtual SocketStream::UserData* GetUserData(const void* key) const;
+ virtual void SetUserData(const void* key, SocketStream::UserData* data);
+
+ URLRequestContext* context() const {
+ return socket_.get() ? socket_->context() : 0;
+ }
+ void set_context(URLRequestContext* context) {
+ if (socket_.get())
+ socket_->set_context(context);
+ }
+
+ virtual void Connect();
+
+ virtual bool SendData(const char* data, int len);
+
+ virtual void Close();
+
+ virtual void RestartWithAuth(const AuthCredentials& credentials);
+
+ virtual void CancelWithError(int error);
+
+ virtual void CancelWithSSLError(const net::SSLInfo& ssl_info);
+
+ virtual void ContinueDespiteError();
+
+ virtual void DetachDelegate();
+
+ protected:
+ friend class WebSocketJobTest;
+ friend class base::RefCountedThreadSafe<SocketStreamJob>;
+ virtual ~SocketStreamJob();
+
+ scoped_refptr<SocketStream> socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamJob);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_JOB_H_
diff --git a/chromium/net/socket_stream/socket_stream_job_manager.cc b/chromium/net/socket_stream/socket_stream_job_manager.cc
new file mode 100644
index 00000000000..7f66a4a4fd3
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_job_manager.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2011 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/socket_stream/socket_stream_job_manager.h"
+
+#include "base/memory/singleton.h"
+
+namespace net {
+
+SocketStreamJobManager::SocketStreamJobManager() {
+}
+
+SocketStreamJobManager::~SocketStreamJobManager() {
+}
+
+// static
+SocketStreamJobManager* SocketStreamJobManager::GetInstance() {
+ return Singleton<SocketStreamJobManager>::get();
+}
+
+SocketStreamJob* SocketStreamJobManager::CreateJob(
+ const GURL& url, SocketStream::Delegate* delegate) const {
+ // If url is invalid, create plain SocketStreamJob, which will close
+ // the socket immediately.
+ if (!url.is_valid()) {
+ SocketStreamJob* job = new SocketStreamJob();
+ job->InitSocketStream(new SocketStream(url, delegate));
+ return job;
+ }
+
+ const std::string& scheme = url.scheme(); // already lowercase
+
+ base::AutoLock locked(lock_);
+ FactoryMap::const_iterator found = factories_.find(scheme);
+ if (found != factories_.end()) {
+ SocketStreamJob* job = found->second(url, delegate);
+ if (job)
+ return job;
+ }
+ SocketStreamJob* job = new SocketStreamJob();
+ job->InitSocketStream(new SocketStream(url, delegate));
+ return job;
+}
+
+SocketStreamJob::ProtocolFactory*
+SocketStreamJobManager::RegisterProtocolFactory(
+ const std::string& scheme, SocketStreamJob::ProtocolFactory* factory) {
+ base::AutoLock locked(lock_);
+
+ SocketStreamJob::ProtocolFactory* old_factory;
+ FactoryMap::iterator found = factories_.find(scheme);
+ if (found != factories_.end()) {
+ old_factory = found->second;
+ } else {
+ old_factory = NULL;
+ }
+ if (factory) {
+ factories_[scheme] = factory;
+ } else if (found != factories_.end()) {
+ factories_.erase(found);
+ }
+ return old_factory;
+}
+
+} // namespace net
diff --git a/chromium/net/socket_stream/socket_stream_job_manager.h b/chromium/net/socket_stream/socket_stream_job_manager.h
new file mode 100644
index 00000000000..aa0cd409344
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_job_manager.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2010 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_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "net/socket_stream/socket_stream.h"
+#include "net/socket_stream/socket_stream_job.h"
+
+template <typename T> struct DefaultSingletonTraits;
+class GURL;
+
+namespace net {
+
+class SocketStreamJobManager {
+ public:
+ // Returns the singleton instance.
+ static SocketStreamJobManager* GetInstance();
+
+ SocketStreamJob* CreateJob(
+ const GURL& url, SocketStream::Delegate* delegate) const;
+
+ SocketStreamJob::ProtocolFactory* RegisterProtocolFactory(
+ const std::string& scheme, SocketStreamJob::ProtocolFactory* factory);
+
+ private:
+ friend struct DefaultSingletonTraits<SocketStreamJobManager>;
+ typedef std::map<std::string, SocketStreamJob::ProtocolFactory*> FactoryMap;
+
+ SocketStreamJobManager();
+ ~SocketStreamJobManager();
+
+ mutable base::Lock lock_;
+ FactoryMap factories_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamJobManager);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_JOB_MANAGER_H_
diff --git a/chromium/net/socket_stream/socket_stream_metrics.cc b/chromium/net/socket_stream/socket_stream_metrics.cc
new file mode 100644
index 00000000000..e0268877533
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_metrics.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2011 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/socket_stream/socket_stream_metrics.h"
+
+#include <string.h>
+
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace net {
+
+SocketStreamMetrics::SocketStreamMetrics(const GURL& url)
+ : received_bytes_(0),
+ received_counts_(0),
+ sent_bytes_(0),
+ sent_counts_(0) {
+ ProtocolType protocol_type = PROTOCOL_UNKNOWN;
+ if (url.SchemeIs("ws"))
+ protocol_type = PROTOCOL_WEBSOCKET;
+ else if (url.SchemeIs("wss"))
+ protocol_type = PROTOCOL_WEBSOCKET_SECURE;
+
+ UMA_HISTOGRAM_ENUMERATION("Net.SocketStream.ProtocolType",
+ protocol_type, NUM_PROTOCOL_TYPES);
+}
+
+SocketStreamMetrics::~SocketStreamMetrics() {}
+
+void SocketStreamMetrics::OnWaitConnection() {
+ wait_start_time_ = base::TimeTicks::Now();
+}
+
+void SocketStreamMetrics::OnStartConnection() {
+ connect_start_time_ = base::TimeTicks::Now();
+ if (!wait_start_time_.is_null())
+ UMA_HISTOGRAM_TIMES("Net.SocketStream.ConnectionLatency",
+ connect_start_time_ - wait_start_time_);
+ OnCountConnectionType(ALL_CONNECTIONS);
+}
+
+void SocketStreamMetrics::OnConnected() {
+ connect_establish_time_ = base::TimeTicks::Now();
+ UMA_HISTOGRAM_TIMES("Net.SocketStream.ConnectionEstablish",
+ connect_establish_time_ - connect_start_time_);
+}
+
+void SocketStreamMetrics::OnRead(int len) {
+ received_bytes_ += len;
+ ++received_counts_;
+}
+
+void SocketStreamMetrics::OnWrite(int len) {
+ sent_bytes_ += len;
+ ++sent_counts_;
+}
+
+void SocketStreamMetrics::OnClose() {
+ base::TimeTicks closed_time = base::TimeTicks::Now();
+ if (!connect_establish_time_.is_null()) {
+ UMA_HISTOGRAM_LONG_TIMES("Net.SocketStream.Duration",
+ closed_time - connect_establish_time_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedBytes",
+ received_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.ReceivedCounts",
+ received_counts_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentBytes",
+ sent_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SocketStream.SentCounts",
+ sent_counts_);
+ }
+}
+
+void SocketStreamMetrics::OnCountConnectionType(ConnectionType type) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SocketStream.ConnectionType", type,
+ NUM_CONNECTION_TYPES);
+}
+
+void SocketStreamMetrics::OnCountWireProtocolType(WireProtocolType type) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SocketStream.WireProtocolType", type,
+ NUM_WIRE_PROTOCOL_TYPES);
+}
+
+} // namespace net
diff --git a/chromium/net/socket_stream/socket_stream_metrics.h b/chromium/net/socket_stream/socket_stream_metrics.h
new file mode 100644
index 00000000000..0040a9a00be
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_metrics.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 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.
+//
+// Collect metrics of SocketStream usage.
+// TODO(ukai): collect WebSocket specific metrics (e.g. handshake time, etc).
+
+#ifndef NET_SOCKET_STREAM_SOCKET_STREAM_METRICS_H_
+#define NET_SOCKET_STREAM_SOCKET_STREAM_METRICS_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class NET_EXPORT_PRIVATE SocketStreamMetrics {
+ public:
+ enum ProtocolType {
+ PROTOCOL_UNKNOWN,
+ PROTOCOL_WEBSOCKET,
+ PROTOCOL_WEBSOCKET_SECURE,
+ NUM_PROTOCOL_TYPES,
+ };
+
+ enum ConnectionType {
+ CONNECTION_NONE,
+ ALL_CONNECTIONS,
+ TUNNEL_CONNECTION,
+ SOCKS_CONNECTION,
+ SSL_CONNECTION,
+ SECURE_PROXY_CONNECTION,
+ NUM_CONNECTION_TYPES,
+ };
+
+ enum WireProtocolType {
+ WIRE_PROTOCOL_WEBSOCKET,
+ WIRE_PROTOCOL_SPDY,
+ NUM_WIRE_PROTOCOL_TYPES,
+ };
+
+ explicit SocketStreamMetrics(const GURL& url);
+ ~SocketStreamMetrics();
+
+ void OnWaitConnection();
+ void OnStartConnection();
+ void OnConnected();
+ void OnRead(int len);
+ void OnWrite(int len);
+ void OnClose();
+ void OnCountConnectionType(ConnectionType type);
+ void OnCountWireProtocolType(WireProtocolType type);
+
+ private:
+ base::TimeTicks wait_start_time_;
+ base::TimeTicks connect_start_time_;
+ base::TimeTicks connect_establish_time_;
+ int received_bytes_;
+ int received_counts_;
+ int sent_bytes_;
+ int sent_counts_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamMetrics);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_STREAM_SOCKET_STREAM_METRICS_H_
diff --git a/chromium/net/socket_stream/socket_stream_metrics_unittest.cc b/chromium/net/socket_stream/socket_stream_metrics_unittest.cc
new file mode 100644
index 00000000000..219e692a4e8
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_metrics_unittest.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 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.
+
+#include "net/socket_stream/socket_stream_metrics.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::StatisticsRecorder;
+
+namespace net {
+
+TEST(SocketStreamMetricsTest, ProtocolType) {
+ // First we'll preserve the original values. We need to do this
+ // as histograms can get affected by other tests. In particular,
+ // SocketStreamTest and WebSocketTest can affect the histograms.
+ scoped_ptr<HistogramSamples> original;
+ HistogramBase* histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ProtocolType");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ }
+
+ SocketStreamMetrics unknown(GURL("unknown://www.example.com/"));
+ SocketStreamMetrics ws1(GURL("ws://www.example.com/"));
+ SocketStreamMetrics ws2(GURL("ws://www.example.com/"));
+ SocketStreamMetrics wss1(GURL("wss://www.example.com/"));
+ SocketStreamMetrics wss2(GURL("wss://www.example.com/"));
+ SocketStreamMetrics wss3(GURL("wss://www.example.com/"));
+
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ProtocolType");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+
+ scoped_ptr<HistogramSamples> samples(histogram->SnapshotSamples());
+ if (original.get()) {
+ samples->Subtract(*original); // Cancel the original values.
+ }
+ EXPECT_EQ(1, samples->GetCount(SocketStreamMetrics::PROTOCOL_UNKNOWN));
+ EXPECT_EQ(2, samples->GetCount(SocketStreamMetrics::PROTOCOL_WEBSOCKET));
+ EXPECT_EQ(3,
+ samples->GetCount(SocketStreamMetrics::PROTOCOL_WEBSOCKET_SECURE));
+}
+
+TEST(SocketStreamMetricsTest, ConnectionType) {
+ // First we'll preserve the original values.
+ scoped_ptr<HistogramSamples> original;
+ HistogramBase* histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ConnectionType");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ }
+
+ SocketStreamMetrics metrics(GURL("ws://www.example.com/"));
+ for (int i = 0; i < 1; ++i)
+ metrics.OnStartConnection();
+ for (int i = 0; i < 2; ++i)
+ metrics.OnCountConnectionType(SocketStreamMetrics::TUNNEL_CONNECTION);
+ for (int i = 0; i < 3; ++i)
+ metrics.OnCountConnectionType(SocketStreamMetrics::SOCKS_CONNECTION);
+ for (int i = 0; i < 4; ++i)
+ metrics.OnCountConnectionType(SocketStreamMetrics::SSL_CONNECTION);
+
+
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ConnectionType");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+
+ scoped_ptr<HistogramSamples> samples(histogram->SnapshotSamples());
+ if (original.get()) {
+ samples->Subtract(*original); // Cancel the original values.
+ }
+ EXPECT_EQ(1, samples->GetCount(SocketStreamMetrics::ALL_CONNECTIONS));
+ EXPECT_EQ(2, samples->GetCount(SocketStreamMetrics::TUNNEL_CONNECTION));
+ EXPECT_EQ(3, samples->GetCount(SocketStreamMetrics::SOCKS_CONNECTION));
+ EXPECT_EQ(4, samples->GetCount(SocketStreamMetrics::SSL_CONNECTION));
+}
+
+TEST(SocketStreamMetricsTest, WireProtocolType) {
+ // First we'll preserve the original values.
+ scoped_ptr<HistogramSamples> original;
+ HistogramBase* histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.WireProtocolType");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ }
+
+ SocketStreamMetrics metrics(GURL("ws://www.example.com/"));
+ for (int i = 0; i < 3; ++i)
+ metrics.OnCountWireProtocolType(
+ SocketStreamMetrics::WIRE_PROTOCOL_WEBSOCKET);
+ for (int i = 0; i < 7; ++i)
+ metrics.OnCountWireProtocolType(SocketStreamMetrics::WIRE_PROTOCOL_SPDY);
+
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.WireProtocolType");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+
+ scoped_ptr<HistogramSamples> samples(histogram->SnapshotSamples());
+ if (original.get()) {
+ samples->Subtract(*original); // Cancel the original values.
+ }
+ EXPECT_EQ(3, samples->GetCount(SocketStreamMetrics::WIRE_PROTOCOL_WEBSOCKET));
+ EXPECT_EQ(7, samples->GetCount(SocketStreamMetrics::WIRE_PROTOCOL_SPDY));
+}
+
+TEST(SocketStreamMetricsTest, OtherNumbers) {
+ // First we'll preserve the original values.
+ int64 original_received_bytes = 0;
+ int64 original_received_counts = 0;
+ int64 original_sent_bytes = 0;
+ int64 original_sent_counts = 0;
+
+ scoped_ptr<HistogramSamples> original;
+
+ HistogramBase* histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ReceivedBytes");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ original_received_bytes = original->sum();
+ }
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ReceivedCounts");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ original_received_counts = original->sum();
+ }
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.SentBytes");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ original_sent_bytes = original->sum();
+ }
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.SentCounts");
+ if (histogram) {
+ original = histogram->SnapshotSamples();
+ original_sent_counts = original->sum();
+ }
+
+ SocketStreamMetrics metrics(GURL("ws://www.example.com/"));
+ metrics.OnWaitConnection();
+ metrics.OnStartConnection();
+ metrics.OnConnected();
+ metrics.OnRead(1);
+ metrics.OnRead(10);
+ metrics.OnWrite(2);
+ metrics.OnWrite(20);
+ metrics.OnWrite(200);
+ metrics.OnClose();
+
+ scoped_ptr<HistogramSamples> samples;
+
+ // ConnectionLatency.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ConnectionLatency");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ // We don't check the contents of the histogram as it's time sensitive.
+
+ // ConnectionEstablish.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ConnectionEstablish");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ // We don't check the contents of the histogram as it's time sensitive.
+
+ // Duration.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.Duration");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ // We don't check the contents of the histogram as it's time sensitive.
+
+ // ReceivedBytes.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ReceivedBytes");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ samples = histogram->SnapshotSamples();
+ EXPECT_EQ(11, samples->sum() - original_received_bytes); // 11 bytes read.
+
+ // ReceivedCounts.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.ReceivedCounts");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ samples = histogram->SnapshotSamples();
+ EXPECT_EQ(2, samples->sum() - original_received_counts); // 2 read requests.
+
+ // SentBytes.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.SentBytes");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ samples = histogram->SnapshotSamples();
+ EXPECT_EQ(222, samples->sum() - original_sent_bytes); // 222 bytes sent.
+
+ // SentCounts.
+ histogram =
+ StatisticsRecorder::FindHistogram("Net.SocketStream.SentCounts");
+ ASSERT_TRUE(histogram != NULL);
+ EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+ samples = histogram->SnapshotSamples();
+ EXPECT_EQ(3, samples->sum() - original_sent_counts); // 3 write requests.
+}
+
+} // namespace net
diff --git a/chromium/net/socket_stream/socket_stream_unittest.cc b/chromium/net/socket_stream/socket_stream_unittest.cc
new file mode 100644
index 00000000000..bd0a348b0ef
--- /dev/null
+++ b/chromium/net/socket_stream/socket_stream_unittest.cc
@@ -0,0 +1,965 @@
+// Copyright (c) 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.
+
+#include "net/socket_stream/socket_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_network_session.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+struct SocketStreamEvent {
+ enum EventType {
+ EVENT_START_OPEN_CONNECTION, EVENT_CONNECTED, EVENT_SENT_DATA,
+ EVENT_RECEIVED_DATA, EVENT_CLOSE, EVENT_AUTH_REQUIRED, EVENT_ERROR,
+ };
+
+ SocketStreamEvent(EventType type,
+ SocketStream* socket_stream,
+ int num,
+ const std::string& str,
+ AuthChallengeInfo* auth_challenge_info,
+ int error)
+ : event_type(type), socket(socket_stream), number(num), data(str),
+ auth_info(auth_challenge_info), error_code(error) {}
+
+ EventType event_type;
+ SocketStream* socket;
+ int number;
+ std::string data;
+ scoped_refptr<AuthChallengeInfo> auth_info;
+ int error_code;
+};
+
+class SocketStreamEventRecorder : public SocketStream::Delegate {
+ public:
+ // |callback| will be run when the OnClose() or OnError() method is called.
+ // For OnClose(), |callback| is called with OK. For OnError(), it's called
+ // with the error code.
+ explicit SocketStreamEventRecorder(const CompletionCallback& callback)
+ : callback_(callback) {}
+ virtual ~SocketStreamEventRecorder() {}
+
+ void SetOnStartOpenConnection(
+ const base::Callback<int(SocketStreamEvent*)>& callback) {
+ on_start_open_connection_ = callback;
+ }
+ void SetOnConnected(
+ const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_connected_ = callback;
+ }
+ void SetOnSentData(
+ const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_sent_data_ = callback;
+ }
+ void SetOnReceivedData(
+ const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_received_data_ = callback;
+ }
+ void SetOnClose(const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_close_ = callback;
+ }
+ void SetOnAuthRequired(
+ const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_auth_required_ = callback;
+ }
+ void SetOnError(const base::Callback<void(SocketStreamEvent*)>& callback) {
+ on_error_ = callback;
+ }
+
+ virtual int OnStartOpenConnection(
+ SocketStream* socket,
+ const CompletionCallback& callback) OVERRIDE {
+ connection_callback_ = callback;
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ socket, 0, std::string(), NULL, OK));
+ if (!on_start_open_connection_.is_null())
+ return on_start_open_connection_.Run(&events_.back());
+ return OK;
+ }
+ virtual void OnConnected(SocketStream* socket,
+ int num_pending_send_allowed) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_CONNECTED,
+ socket, num_pending_send_allowed, std::string(),
+ NULL, OK));
+ if (!on_connected_.is_null())
+ on_connected_.Run(&events_.back());
+ }
+ virtual void OnSentData(SocketStream* socket,
+ int amount_sent) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_SENT_DATA, socket,
+ amount_sent, std::string(), NULL, OK));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual void OnReceivedData(SocketStream* socket,
+ const char* data, int len) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_RECEIVED_DATA, socket, len,
+ std::string(data, len), NULL, OK));
+ if (!on_received_data_.is_null())
+ on_received_data_.Run(&events_.back());
+ }
+ virtual void OnClose(SocketStream* socket) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_CLOSE, socket, 0,
+ std::string(), NULL, OK));
+ if (!on_close_.is_null())
+ on_close_.Run(&events_.back());
+ if (!callback_.is_null())
+ callback_.Run(OK);
+ }
+ virtual void OnAuthRequired(SocketStream* socket,
+ AuthChallengeInfo* auth_info) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_AUTH_REQUIRED, socket, 0,
+ std::string(), auth_info, OK));
+ if (!on_auth_required_.is_null())
+ on_auth_required_.Run(&events_.back());
+ }
+ virtual void OnError(const SocketStream* socket, int error) OVERRIDE {
+ events_.push_back(
+ SocketStreamEvent(SocketStreamEvent::EVENT_ERROR, NULL, 0,
+ std::string(), NULL, error));
+ if (!on_error_.is_null())
+ on_error_.Run(&events_.back());
+ if (!callback_.is_null())
+ callback_.Run(error);
+ }
+
+ void DoClose(SocketStreamEvent* event) {
+ event->socket->Close();
+ }
+ void DoRestartWithAuth(SocketStreamEvent* event) {
+ VLOG(1) << "RestartWithAuth username=" << credentials_.username()
+ << " password=" << credentials_.password();
+ event->socket->RestartWithAuth(credentials_);
+ }
+ void SetAuthInfo(const AuthCredentials& credentials) {
+ credentials_ = credentials;
+ }
+ void CompleteConnection(int result) {
+ connection_callback_.Run(result);
+ }
+
+ const std::vector<SocketStreamEvent>& GetSeenEvents() const {
+ return events_;
+ }
+
+ private:
+ std::vector<SocketStreamEvent> events_;
+ base::Callback<int(SocketStreamEvent*)> on_start_open_connection_;
+ base::Callback<void(SocketStreamEvent*)> on_connected_;
+ base::Callback<void(SocketStreamEvent*)> on_sent_data_;
+ base::Callback<void(SocketStreamEvent*)> on_received_data_;
+ base::Callback<void(SocketStreamEvent*)> on_close_;
+ base::Callback<void(SocketStreamEvent*)> on_auth_required_;
+ base::Callback<void(SocketStreamEvent*)> on_error_;
+ const CompletionCallback callback_;
+ CompletionCallback connection_callback_;
+ AuthCredentials credentials_;
+
+ DISALLOW_COPY_AND_ASSIGN(SocketStreamEventRecorder);
+};
+
+// This is used for the test OnErrorDetachDelegate.
+class SelfDeletingDelegate : public SocketStream::Delegate {
+ public:
+ // |callback| must cause the test message loop to exit when called.
+ explicit SelfDeletingDelegate(const CompletionCallback& callback)
+ : socket_stream_(), callback_(callback) {}
+
+ virtual ~SelfDeletingDelegate() {}
+
+ // Call DetachDelegate(), delete |this|, then run the callback.
+ virtual void OnError(const SocketStream* socket, int error) OVERRIDE {
+ // callback_ will be deleted when we delete |this|, so copy it to call it
+ // afterwards.
+ CompletionCallback callback = callback_;
+ socket_stream_->DetachDelegate();
+ delete this;
+ callback.Run(OK);
+ }
+
+ // This can't be passed in the constructor because this object needs to be
+ // created before SocketStream.
+ void set_socket_stream(const scoped_refptr<SocketStream>& socket_stream) {
+ socket_stream_ = socket_stream;
+ EXPECT_EQ(socket_stream_->delegate(), this);
+ }
+
+ virtual void OnConnected(SocketStream* socket, int max_pending_send_allowed)
+ OVERRIDE {
+ ADD_FAILURE() << "OnConnected() should not be called";
+ }
+ virtual void OnSentData(SocketStream* socket, int amount_sent) OVERRIDE {
+ ADD_FAILURE() << "OnSentData() should not be called";
+ }
+ virtual void OnReceivedData(SocketStream* socket, const char* data, int len)
+ OVERRIDE {
+ ADD_FAILURE() << "OnReceivedData() should not be called";
+ }
+ virtual void OnClose(SocketStream* socket) OVERRIDE {
+ ADD_FAILURE() << "OnClose() should not be called";
+ }
+
+ private:
+ scoped_refptr<SocketStream> socket_stream_;
+ const CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelfDeletingDelegate);
+};
+
+class TestURLRequestContextWithProxy : public TestURLRequestContext {
+ public:
+ explicit TestURLRequestContextWithProxy(const std::string& proxy)
+ : TestURLRequestContext(true) {
+ context_storage_.set_proxy_service(ProxyService::CreateFixed(proxy));
+ Init();
+ }
+ virtual ~TestURLRequestContextWithProxy() {}
+};
+
+class TestSocketStreamNetworkDelegate : public TestNetworkDelegate {
+ public:
+ TestSocketStreamNetworkDelegate()
+ : before_connect_result_(OK) {}
+ virtual ~TestSocketStreamNetworkDelegate() {}
+
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE {
+ return before_connect_result_;
+ }
+
+ void SetBeforeConnectResult(int result) {
+ before_connect_result_ = result;
+ }
+
+ private:
+ int before_connect_result_;
+};
+
+} // namespace
+
+class SocketStreamTest : public PlatformTest {
+ public:
+ virtual ~SocketStreamTest() {}
+ virtual void SetUp() {
+ mock_socket_factory_.reset();
+ handshake_request_ = kWebSocketHandshakeRequest;
+ handshake_response_ = kWebSocketHandshakeResponse;
+ }
+ virtual void TearDown() {
+ mock_socket_factory_.reset();
+ }
+
+ virtual void SetWebSocketHandshakeMessage(
+ const char* request, const char* response) {
+ handshake_request_ = request;
+ handshake_response_ = response;
+ }
+ virtual void AddWebSocketMessage(const std::string& message) {
+ messages_.push_back(message);
+ }
+
+ virtual MockClientSocketFactory* GetMockClientSocketFactory() {
+ mock_socket_factory_.reset(new MockClientSocketFactory);
+ return mock_socket_factory_.get();
+ }
+
+ virtual void DoSendWebSocketHandshake(SocketStreamEvent* event) {
+ event->socket->SendData(
+ handshake_request_.data(), handshake_request_.size());
+ }
+
+ virtual void DoCloseFlushPendingWriteTest(SocketStreamEvent* event) {
+ // handshake response received.
+ for (size_t i = 0; i < messages_.size(); i++) {
+ std::vector<char> frame;
+ frame.push_back('\0');
+ frame.insert(frame.end(), messages_[i].begin(), messages_[i].end());
+ frame.push_back('\xff');
+ EXPECT_TRUE(event->socket->SendData(&frame[0], frame.size()));
+ }
+ // Actual StreamSocket close must happen after all frames queued by
+ // SendData above are sent out.
+ event->socket->Close();
+ }
+
+ virtual void DoFailByTooBigDataAndClose(SocketStreamEvent* event) {
+ std::string frame(event->number + 1, 0x00);
+ VLOG(1) << event->number;
+ EXPECT_FALSE(event->socket->SendData(&frame[0], frame.size()));
+ event->socket->Close();
+ }
+
+ virtual int DoSwitchToSpdyTest(SocketStreamEvent* event) {
+ return ERR_PROTOCOL_SWITCHED;
+ }
+
+ virtual int DoIOPending(SocketStreamEvent* event) {
+ io_test_callback_.callback().Run(OK);
+ return ERR_IO_PENDING;
+ }
+
+ static const char kWebSocketHandshakeRequest[];
+ static const char kWebSocketHandshakeResponse[];
+
+ protected:
+ TestCompletionCallback io_test_callback_;
+
+ private:
+ std::string handshake_request_;
+ std::string handshake_response_;
+ std::vector<std::string> messages_;
+
+ scoped_ptr<MockClientSocketFactory> mock_socket_factory_;
+};
+
+const char SocketStreamTest::kWebSocketHandshakeRequest[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+const char SocketStreamTest::kWebSocketHandshakeResponse[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+TEST_F(SocketStreamTest, CloseFlushPendingWrite) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(
+ &SocketStreamTest::DoSendWebSocketHandshake, base::Unretained(this)));
+ delegate->SetOnReceivedData(base::Bind(
+ &SocketStreamTest::DoCloseFlushPendingWriteTest,
+ base::Unretained(this)));
+
+ TestURLRequestContext context;
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ MockWrite data_writes[] = {
+ MockWrite(SocketStreamTest::kWebSocketHandshakeRequest),
+ MockWrite(ASYNC, "\0message1\xff", 10),
+ MockWrite(ASYNC, "\0message2\xff", 10)
+ };
+ MockRead data_reads[] = {
+ MockRead(SocketStreamTest::kWebSocketHandshakeResponse),
+ // Server doesn't close the connection after handshake.
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ AddWebSocketMessage("message1");
+ AddWebSocketMessage("message2");
+
+ DelayedSocketData data_provider(
+ 1, data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+
+ MockClientSocketFactory* mock_socket_factory =
+ GetMockClientSocketFactory();
+ mock_socket_factory->AddSocketDataProvider(&data_provider);
+
+ socket_stream->SetClientSocketFactory(mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(7U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[2].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_RECEIVED_DATA, events[3].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[4].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[5].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[6].event_type);
+}
+
+TEST_F(SocketStreamTest, ResolveFailure) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ // Make resolver fail.
+ TestURLRequestContext context;
+ scoped_ptr<MockHostResolver> mock_host_resolver(
+ new MockHostResolver());
+ mock_host_resolver->rules()->AddSimulatedFailure("example.com");
+ context.set_host_resolver(mock_host_resolver.get());
+ socket_stream->set_context(&context);
+
+ // No read/write on socket is expected.
+ StaticSocketDataProvider data_provider(NULL, 0, NULL, 0);
+ MockClientSocketFactory* mock_socket_factory =
+ GetMockClientSocketFactory();
+ mock_socket_factory->AddSocketDataProvider(&data_provider);
+ socket_stream->SetClientSocketFactory(mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(2U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[1].event_type);
+}
+
+TEST_F(SocketStreamTest, ExceedMaxPendingSendAllowed) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(
+ &SocketStreamTest::DoFailByTooBigDataAndClose, base::Unretained(this)));
+
+ TestURLRequestContext context;
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ DelayedSocketData data_provider(1, NULL, 0, NULL, 0);
+
+ MockClientSocketFactory* mock_socket_factory =
+ GetMockClientSocketFactory();
+ mock_socket_factory->AddSocketDataProvider(&data_provider);
+
+ socket_stream->SetClientSocketFactory(mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(4U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[2].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type);
+}
+
+TEST_F(SocketStreamTest, BasicAuthProxy) {
+ MockClientSocketFactory mock_socket_factory;
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT example.com:80 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("\r\n"),
+ };
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ mock_socket_factory.AddSocketDataProvider(&data1);
+
+ MockWrite data_writes2[] = {
+ MockWrite("CONNECT example.com:80 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead data_reads2[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n"),
+ MockRead("Proxy-agent: Apache/2.2.8\r\n"),
+ MockRead("\r\n"),
+ // SocketStream::DoClose is run asynchronously. Socket can be read after
+ // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate
+ // server doesn't close the connection.
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
+ data_writes2, arraysize(data_writes2));
+ mock_socket_factory.AddSocketDataProvider(&data2);
+
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose,
+ base::Unretained(delegate.get())));
+ delegate->SetAuthInfo(AuthCredentials(ASCIIToUTF16("foo"),
+ ASCIIToUTF16("bar")));
+ delegate->SetOnAuthRequired(base::Bind(
+ &SocketStreamEventRecorder::DoRestartWithAuth,
+ base::Unretained(delegate.get())));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ TestURLRequestContextWithProxy context("myproxy:70");
+
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(5U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_AUTH_REQUIRED, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[2].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[3].event_type);
+ EXPECT_EQ(ERR_ABORTED, events[3].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[4].event_type);
+
+ // TODO(eroman): Add back NetLogTest here...
+}
+
+TEST_F(SocketStreamTest, BasicAuthProxyWithAuthCache) {
+ MockClientSocketFactory mock_socket_factory;
+ MockWrite data_writes[] = {
+ // WebSocket(SocketStream) always uses CONNECT when it is configured to use
+ // proxy so the port may not be 443.
+ MockWrite("CONNECT example.com:80 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n"),
+ MockRead("Proxy-agent: Apache/2.2.8\r\n"),
+ MockRead("\r\n"),
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory.AddSocketDataProvider(&data);
+
+ TestCompletionCallback test_callback;
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose,
+ base::Unretained(delegate.get())));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ TestURLRequestContextWithProxy context("myproxy:70");
+ HttpAuthCache* auth_cache =
+ context.http_transaction_factory()->GetSession()->http_auth_cache();
+ auth_cache->Add(GURL("http://myproxy:70"),
+ "MyRealm1",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "Basic realm=MyRealm1",
+ AuthCredentials(ASCIIToUTF16("foo"),
+ ASCIIToUTF16("bar")),
+ "/");
+
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(4U, events.size());
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(ERR_ABORTED, events[2].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type);
+}
+
+TEST_F(SocketStreamTest, WSSBasicAuthProxyWithAuthCache) {
+ MockClientSocketFactory mock_socket_factory;
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT example.com:443 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n"),
+ MockRead("Proxy-agent: Apache/2.2.8\r\n"),
+ MockRead("\r\n"),
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1),
+ data_writes1, arraysize(data_writes1));
+ mock_socket_factory.AddSocketDataProvider(&data1);
+
+ SSLSocketDataProvider data2(ASYNC, OK);
+ mock_socket_factory.AddSSLSocketDataProvider(&data2);
+
+ TestCompletionCallback test_callback;
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose,
+ base::Unretained(delegate.get())));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("wss://example.com/demo"), delegate.get()));
+
+ TestURLRequestContextWithProxy context("myproxy:70");
+ HttpAuthCache* auth_cache =
+ context.http_transaction_factory()->GetSession()->http_auth_cache();
+ auth_cache->Add(GURL("http://myproxy:70"),
+ "MyRealm1",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "Basic realm=MyRealm1",
+ AuthCredentials(ASCIIToUTF16("foo"),
+ ASCIIToUTF16("bar")),
+ "/");
+
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(4U, events.size());
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(ERR_ABORTED, events[2].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type);
+}
+
+TEST_F(SocketStreamTest, IOPending) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(
+ &SocketStreamTest::DoSendWebSocketHandshake, base::Unretained(this)));
+ delegate->SetOnReceivedData(base::Bind(
+ &SocketStreamTest::DoCloseFlushPendingWriteTest,
+ base::Unretained(this)));
+ delegate->SetOnStartOpenConnection(base::Bind(
+ &SocketStreamTest::DoIOPending, base::Unretained(this)));
+
+ TestURLRequestContext context;
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ MockWrite data_writes[] = {
+ MockWrite(SocketStreamTest::kWebSocketHandshakeRequest),
+ MockWrite(ASYNC, "\0message1\xff", 10),
+ MockWrite(ASYNC, "\0message2\xff", 10)
+ };
+ MockRead data_reads[] = {
+ MockRead(SocketStreamTest::kWebSocketHandshakeResponse),
+ // Server doesn't close the connection after handshake.
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ AddWebSocketMessage("message1");
+ AddWebSocketMessage("message2");
+
+ DelayedSocketData data_provider(
+ 1, data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+
+ MockClientSocketFactory* mock_socket_factory =
+ GetMockClientSocketFactory();
+ mock_socket_factory->AddSocketDataProvider(&data_provider);
+
+ socket_stream->SetClientSocketFactory(mock_socket_factory);
+
+ socket_stream->Connect();
+ io_test_callback_.WaitForResult();
+ EXPECT_EQ(SocketStream::STATE_RESOLVE_PROTOCOL_COMPLETE,
+ socket_stream->next_state_);
+ delegate->CompleteConnection(OK);
+
+ EXPECT_EQ(OK, test_callback.WaitForResult());
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(7U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[2].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_RECEIVED_DATA, events[3].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[4].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[5].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[6].event_type);
+}
+
+TEST_F(SocketStreamTest, SwitchToSpdy) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnStartOpenConnection(base::Bind(
+ &SocketStreamTest::DoSwitchToSpdyTest, base::Unretained(this)));
+
+ TestURLRequestContext context;
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ socket_stream->Connect();
+
+ EXPECT_EQ(ERR_PROTOCOL_SWITCHED, test_callback.WaitForResult());
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(2U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type);
+ EXPECT_EQ(ERR_PROTOCOL_SWITCHED, events[1].error_code);
+}
+
+TEST_F(SocketStreamTest, SwitchAfterPending) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnStartOpenConnection(base::Bind(
+ &SocketStreamTest::DoIOPending, base::Unretained(this)));
+
+ TestURLRequestContext context;
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ socket_stream->Connect();
+ io_test_callback_.WaitForResult();
+ EXPECT_EQ(SocketStream::STATE_RESOLVE_PROTOCOL_COMPLETE,
+ socket_stream->next_state_);
+ delegate->CompleteConnection(ERR_PROTOCOL_SWITCHED);
+
+ EXPECT_EQ(ERR_PROTOCOL_SWITCHED, test_callback.WaitForResult());
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(2U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type);
+ EXPECT_EQ(ERR_PROTOCOL_SWITCHED, events[1].error_code);
+}
+
+// Test a connection though a secure proxy.
+TEST_F(SocketStreamTest, SecureProxyConnectError) {
+ MockClientSocketFactory mock_socket_factory;
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT example.com:80 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n")
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n"),
+ MockRead("Proxy-agent: Apache/2.2.8\r\n"),
+ MockRead("\r\n"),
+ // SocketStream::DoClose is run asynchronously. Socket can be read after
+ // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate
+ // server doesn't close the connection.
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(SYNCHRONOUS, ERR_SSL_PROTOCOL_ERROR);
+ mock_socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback test_callback;
+ TestURLRequestContextWithProxy context("https://myproxy:70");
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose,
+ base::Unretained(delegate.get())));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(3U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type);
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, events[1].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[2].event_type);
+}
+
+// Test a connection though a secure proxy.
+TEST_F(SocketStreamTest, SecureProxyConnect) {
+ MockClientSocketFactory mock_socket_factory;
+ MockWrite data_writes[] = {
+ MockWrite("CONNECT example.com:80 HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n")
+ };
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.1 200 Connection Established\r\n"),
+ MockRead("Proxy-agent: Apache/2.2.8\r\n"),
+ MockRead("\r\n"),
+ // SocketStream::DoClose is run asynchronously. Socket can be read after
+ // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate
+ // server doesn't close the connection.
+ MockRead(ASYNC, ERR_IO_PENDING)
+ };
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads),
+ data_writes, arraysize(data_writes));
+ mock_socket_factory.AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ mock_socket_factory.AddSSLSocketDataProvider(&ssl);
+
+ TestCompletionCallback test_callback;
+ TestURLRequestContextWithProxy context("https://myproxy:70");
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+ delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose,
+ base::Unretained(delegate.get())));
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(4U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION,
+ events[0].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type);
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[2].event_type);
+ EXPECT_EQ(ERR_ABORTED, events[2].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type);
+}
+
+TEST_F(SocketStreamTest, BeforeConnectFailed) {
+ TestCompletionCallback test_callback;
+
+ scoped_ptr<SocketStreamEventRecorder> delegate(
+ new SocketStreamEventRecorder(test_callback.callback()));
+
+ TestURLRequestContext context;
+ TestSocketStreamNetworkDelegate network_delegate;
+ network_delegate.SetBeforeConnectResult(ERR_ACCESS_DENIED);
+ context.set_network_delegate(&network_delegate);
+
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://example.com/demo"), delegate.get()));
+
+ socket_stream->set_context(&context);
+
+ socket_stream->Connect();
+
+ test_callback.WaitForResult();
+
+ const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents();
+ ASSERT_EQ(2U, events.size());
+
+ EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[0].event_type);
+ EXPECT_EQ(ERR_ACCESS_DENIED, events[0].error_code);
+ EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[1].event_type);
+}
+
+// Check that a connect failure, followed by the delegate calling DetachDelegate
+// and deleting itself in the OnError callback, is handled correctly.
+TEST_F(SocketStreamTest, OnErrorDetachDelegate) {
+ MockClientSocketFactory mock_socket_factory;
+ TestCompletionCallback test_callback;
+
+ // SelfDeletingDelegate is self-owning; we just need a pointer to it to
+ // connect it and the SocketStream.
+ SelfDeletingDelegate* delegate =
+ new SelfDeletingDelegate(test_callback.callback());
+ MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
+ StaticSocketDataProvider data;
+ data.set_connect_data(mock_connect);
+ mock_socket_factory.AddSocketDataProvider(&data);
+
+ TestURLRequestContext context;
+ scoped_refptr<SocketStream> socket_stream(
+ new SocketStream(GURL("ws://localhost:9998/echo"), delegate));
+ socket_stream->set_context(&context);
+ socket_stream->SetClientSocketFactory(&mock_socket_factory);
+ delegate->set_socket_stream(socket_stream);
+ // The delegate pointer will become invalid during the test. Set it to NULL to
+ // avoid holding a dangling pointer.
+ delegate = NULL;
+
+ socket_stream->Connect();
+
+ EXPECT_EQ(OK, test_callback.WaitForResult());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/buffered_spdy_framer.cc b/chromium/net/spdy/buffered_spdy_framer.cc
new file mode 100644
index 00000000000..46d028404fe
--- /dev/null
+++ b/chromium/net/spdy/buffered_spdy_framer.cc
@@ -0,0 +1,331 @@
+// Copyright (c) 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.
+
+#include "net/spdy/buffered_spdy_framer.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+SpdyMajorVersion NextProtoToSpdyMajorVersion(NextProto next_proto) {
+ switch (next_proto) {
+ case kProtoSPDY2:
+ case kProtoSPDY21:
+ return SPDY2;
+ case kProtoSPDY3:
+ case kProtoSPDY31:
+ return SPDY3;
+ // SPDY/4 and HTTP/2 share the same framing for now.
+ case kProtoSPDY4a2:
+ case kProtoHTTP2Draft04:
+ return SPDY4;
+ case kProtoUnknown:
+ case kProtoHTTP11:
+ case kProtoSPDY1:
+ case kProtoQUIC1SPDY3:
+ break;
+ }
+ NOTREACHED();
+ return SPDY2;
+}
+
+BufferedSpdyFramer::BufferedSpdyFramer(SpdyMajorVersion version,
+ bool enable_compression)
+ : spdy_framer_(version),
+ visitor_(NULL),
+ header_buffer_used_(0),
+ header_buffer_valid_(false),
+ header_stream_id_(SpdyFramer::kInvalidStream),
+ frames_received_(0) {
+ spdy_framer_.set_enable_compression(enable_compression);
+ memset(header_buffer_, 0, sizeof(header_buffer_));
+}
+
+BufferedSpdyFramer::~BufferedSpdyFramer() {
+}
+
+void BufferedSpdyFramer::set_visitor(
+ BufferedSpdyFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+ spdy_framer_.set_visitor(this);
+}
+
+void BufferedSpdyFramer::set_debug_visitor(
+ SpdyFramerDebugVisitorInterface* debug_visitor) {
+ spdy_framer_.set_debug_visitor(debug_visitor);
+}
+
+void BufferedSpdyFramer::OnError(SpdyFramer* spdy_framer) {
+ DCHECK(spdy_framer);
+ visitor_->OnError(spdy_framer->error_code());
+}
+
+void BufferedSpdyFramer::OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) {
+ frames_received_++;
+ DCHECK(!control_frame_fields_.get());
+ control_frame_fields_.reset(new ControlFrameFields());
+ control_frame_fields_->type = SYN_STREAM;
+ control_frame_fields_->stream_id = stream_id;
+ control_frame_fields_->associated_stream_id = associated_stream_id;
+ control_frame_fields_->priority = priority;
+ control_frame_fields_->credential_slot = credential_slot;
+ control_frame_fields_->fin = fin;
+ control_frame_fields_->unidirectional = unidirectional;
+
+ InitHeaderStreaming(stream_id);
+}
+
+void BufferedSpdyFramer::OnHeaders(SpdyStreamId stream_id,
+ bool fin) {
+ frames_received_++;
+ DCHECK(!control_frame_fields_.get());
+ control_frame_fields_.reset(new ControlFrameFields());
+ control_frame_fields_->type = HEADERS;
+ control_frame_fields_->stream_id = stream_id;
+ control_frame_fields_->fin = fin;
+
+ InitHeaderStreaming(stream_id);
+}
+
+void BufferedSpdyFramer::OnSynReply(SpdyStreamId stream_id,
+ bool fin) {
+ frames_received_++;
+ DCHECK(!control_frame_fields_.get());
+ control_frame_fields_.reset(new ControlFrameFields());
+ control_frame_fields_->type = SYN_REPLY;
+ control_frame_fields_->stream_id = stream_id;
+ control_frame_fields_->fin = fin;
+
+ InitHeaderStreaming(stream_id);
+}
+
+bool BufferedSpdyFramer::OnCredentialFrameData(const char* frame_data,
+ size_t len) {
+ DCHECK(false);
+ return false;
+}
+
+bool BufferedSpdyFramer::OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) {
+ CHECK_EQ(header_stream_id_, stream_id);
+
+ if (len == 0) {
+ // Indicates end-of-header-block.
+ CHECK(header_buffer_valid_);
+
+ SpdyHeaderBlock headers;
+ size_t parsed_len = spdy_framer_.ParseHeaderBlockInBuffer(
+ header_buffer_, header_buffer_used_, &headers);
+ // TODO(rch): this really should be checking parsed_len != len,
+ // but a bunch of tests fail. Need to figure out why.
+ if (parsed_len == 0) {
+ visitor_->OnStreamError(
+ stream_id, "Could not parse Spdy Control Frame Header.");
+ return false;
+ }
+ DCHECK(control_frame_fields_.get());
+ switch (control_frame_fields_->type) {
+ case SYN_STREAM:
+ visitor_->OnSynStream(control_frame_fields_->stream_id,
+ control_frame_fields_->associated_stream_id,
+ control_frame_fields_->priority,
+ control_frame_fields_->credential_slot,
+ control_frame_fields_->fin,
+ control_frame_fields_->unidirectional,
+ headers);
+ break;
+ case SYN_REPLY:
+ visitor_->OnSynReply(control_frame_fields_->stream_id,
+ control_frame_fields_->fin,
+ headers);
+ break;
+ case HEADERS:
+ visitor_->OnHeaders(control_frame_fields_->stream_id,
+ control_frame_fields_->fin,
+ headers);
+ break;
+ default:
+ DCHECK(false) << "Unexpect control frame type: "
+ << control_frame_fields_->type;
+ break;
+ }
+ control_frame_fields_.reset(NULL);
+ return true;
+ }
+
+ const size_t available = kHeaderBufferSize - header_buffer_used_;
+ if (len > available) {
+ header_buffer_valid_ = false;
+ visitor_->OnStreamError(
+ stream_id, "Received more data than the allocated size.");
+ return false;
+ }
+ memcpy(header_buffer_ + header_buffer_used_, header_data, len);
+ header_buffer_used_ += len;
+ return true;
+}
+
+void BufferedSpdyFramer::OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) {
+ frames_received_++;
+ header_stream_id_ = stream_id;
+}
+
+void BufferedSpdyFramer::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) {
+ visitor_->OnStreamFrameData(stream_id, data, len, fin);
+}
+
+void BufferedSpdyFramer::OnSettings(bool clear_persisted) {
+ visitor_->OnSettings(clear_persisted);
+}
+
+void BufferedSpdyFramer::OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) {
+ visitor_->OnSetting(id, flags, value);
+}
+
+void BufferedSpdyFramer::OnPing(uint32 unique_id) {
+ visitor_->OnPing(unique_id);
+}
+
+void BufferedSpdyFramer::OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) {
+ visitor_->OnRstStream(stream_id, status);
+}
+void BufferedSpdyFramer::OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {
+ visitor_->OnGoAway(last_accepted_stream_id, status);
+}
+
+void BufferedSpdyFramer::OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) {
+ visitor_->OnWindowUpdate(stream_id, delta_window_size);
+}
+
+void BufferedSpdyFramer::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) {
+ visitor_->OnPushPromise(stream_id, promised_stream_id);
+}
+
+int BufferedSpdyFramer::protocol_version() {
+ return spdy_framer_.protocol_version();
+}
+
+size_t BufferedSpdyFramer::ProcessInput(const char* data, size_t len) {
+ return spdy_framer_.ProcessInput(data, len);
+}
+
+void BufferedSpdyFramer::Reset() {
+ spdy_framer_.Reset();
+}
+
+SpdyFramer::SpdyError BufferedSpdyFramer::error_code() const {
+ return spdy_framer_.error_code();
+}
+
+SpdyFramer::SpdyState BufferedSpdyFramer::state() const {
+ return spdy_framer_.state();
+}
+
+bool BufferedSpdyFramer::MessageFullyRead() {
+ return state() == SpdyFramer::SPDY_AUTO_RESET;
+}
+
+bool BufferedSpdyFramer::HasError() {
+ return spdy_framer_.HasError();
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateSynStream(
+ SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ return spdy_framer_.CreateSynStream(stream_id, associated_stream_id, priority,
+ credential_slot, flags, compressed,
+ headers);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateSynReply(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ return spdy_framer_.CreateSynReply(stream_id, flags, compressed, headers);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateRstStream(
+ SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const {
+ return spdy_framer_.CreateRstStream(stream_id, status);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateSettings(
+ const SettingsMap& values) const {
+ return spdy_framer_.CreateSettings(values);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreatePingFrame(
+ uint32 unique_id) const {
+ return spdy_framer_.CreatePingFrame(unique_id);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const {
+ return spdy_framer_.CreateGoAway(last_accepted_stream_id, status);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateHeaders(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ return spdy_framer_.CreateHeaders(stream_id, flags, compressed, headers);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const {
+ return spdy_framer_.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateCredentialFrame(
+ const SpdyCredential& credential) const {
+ return spdy_framer_.CreateCredentialFrame(credential);
+}
+
+SpdyFrame* BufferedSpdyFramer::CreateDataFrame(SpdyStreamId stream_id,
+ const char* data,
+ uint32 len,
+ SpdyDataFlags flags) {
+ return spdy_framer_.CreateDataFrame(stream_id, data, len, flags);
+}
+
+SpdyPriority BufferedSpdyFramer::GetHighestPriority() const {
+ return spdy_framer_.GetHighestPriority();
+}
+
+void BufferedSpdyFramer::InitHeaderStreaming(SpdyStreamId stream_id) {
+ memset(header_buffer_, 0, kHeaderBufferSize);
+ header_buffer_used_ = 0;
+ header_buffer_valid_ = true;
+ header_stream_id_ = stream_id;
+ DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/buffered_spdy_framer.h b/chromium/net/spdy/buffered_spdy_framer.h
new file mode 100644
index 00000000000..1786067d6a3
--- /dev/null
+++ b/chromium/net/spdy/buffered_spdy_framer.h
@@ -0,0 +1,262 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_BUFFERED_SPDY_FRAMER_H_
+#define NET_SPDY_BUFFERED_SPDY_FRAMER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+// Returns the SPDY major version corresponding to the given NextProto
+// value, which must represent a SPDY-like protocol.
+NET_EXPORT_PRIVATE SpdyMajorVersion NextProtoToSpdyMajorVersion(
+ NextProto next_proto);
+
+class NET_EXPORT_PRIVATE BufferedSpdyFramerVisitorInterface {
+ public:
+ BufferedSpdyFramerVisitorInterface() {}
+
+ // Called if an error is detected in the SpdyFrame protocol.
+ virtual void OnError(SpdyFramer::SpdyError error_code) = 0;
+
+ // Called if an error is detected in a SPDY stream.
+ virtual void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) = 0;
+
+ // Called after all the header data for SYN_STREAM control frame is received.
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) = 0;
+
+ // Called after all the header data for SYN_REPLY control frame is received.
+ virtual void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) = 0;
+
+ // Called after all the header data for HEADERS control frame is received.
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) = 0;
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer (at most 2^24 - 1 for SPDY/3,
+ // but 2^16 - 1 - 8 for SPDY/4).
+ // When the other side has finished sending data on this stream,
+ // this method will be called with a zero-length buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) = 0;
+
+ // Called when a SETTINGS frame is received.
+ // |clear_persisted| True if the respective flag is set on the SETTINGS frame.
+ virtual void OnSettings(bool clear_persisted) = 0;
+
+ // Called when an individual setting within a SETTINGS frame has been parsed
+ // and validated.
+ virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0;
+
+ // Called when a PING frame has been parsed.
+ virtual void OnPing(uint32 unique_id) = 0;
+
+ // Called when a RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) = 0;
+
+ // Called when a GOAWAY frame has been parsed.
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) = 0;
+
+ // Called when a WINDOW_UPDATE frame has been parsed.
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) = 0;
+
+ // Called when a PUSH_PROMISE frame has been parsed.
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) = 0;
+
+ protected:
+ virtual ~BufferedSpdyFramerVisitorInterface() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramerVisitorInterface);
+};
+
+class NET_EXPORT_PRIVATE BufferedSpdyFramer
+ : public SpdyFramerVisitorInterface {
+ public:
+ BufferedSpdyFramer(SpdyMajorVersion version,
+ bool enable_compression);
+ virtual ~BufferedSpdyFramer();
+
+ // Sets callbacks to be called from the buffered spdy framer. A visitor must
+ // be set, or else the framer will likely crash. It is acceptable for the
+ // visitor to do nothing. If this is called multiple times, only the last
+ // visitor will be used.
+ void set_visitor(BufferedSpdyFramerVisitorInterface* visitor);
+
+ // Set debug callbacks to be called from the framer. The debug visitor is
+ // completely optional and need not be set in order for normal operation.
+ // If this is called multiple times, only the last visitor will be used.
+ void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor);
+
+ // SpdyFramerVisitorInterface
+ virtual void OnError(SpdyFramer* spdy_framer) OVERRIDE;
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) OVERRIDE;
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE;
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE;
+ virtual bool OnCredentialFrameData(const char* frame_data,
+ size_t len) OVERRIDE;
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) OVERRIDE;
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE;
+ virtual void OnSettings(bool clear_persisted) OVERRIDE;
+ virtual void OnSetting(
+ SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+ virtual void OnPing(uint32 unique_id) OVERRIDE;
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE;
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE;
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE;
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE;
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE;
+
+ // SpdyFramer methods.
+ size_t ProcessInput(const char* data, size_t len);
+ int protocol_version();
+ void Reset();
+ SpdyFramer::SpdyError error_code() const;
+ SpdyFramer::SpdyState state() const;
+ bool MessageFullyRead();
+ bool HasError();
+ SpdyFrame* CreateSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdyFrame* CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdyFrame* CreateRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const;
+ SpdyFrame* CreateSettings(const SettingsMap& values) const;
+ SpdyFrame* CreatePingFrame(uint32 unique_id) const;
+ SpdyFrame* CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const;
+ SpdyFrame* CreateHeaders(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdyFrame* CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const;
+ SpdyFrame* CreateCredentialFrame(
+ const SpdyCredential& credential) const;
+ SpdyFrame* CreateDataFrame(SpdyStreamId stream_id,
+ const char* data,
+ uint32 len,
+ SpdyDataFlags flags);
+
+ // Serialize a frame of unknown type.
+ SpdySerializedFrame* SerializeFrame(const SpdyFrameIR& frame) {
+ return spdy_framer_.SerializeFrame(frame);
+ }
+
+ SpdyPriority GetHighestPriority() const;
+
+ size_t GetDataFrameMinimumSize() const {
+ return spdy_framer_.GetDataFrameMinimumSize();
+ }
+
+ size_t GetControlFrameHeaderSize() const {
+ return spdy_framer_.GetControlFrameHeaderSize();
+ }
+
+ size_t GetSynStreamMinimumSize() const {
+ return spdy_framer_.GetSynStreamMinimumSize();
+ }
+
+ size_t GetFrameMinimumSize() const {
+ return spdy_framer_.GetFrameMinimumSize();
+ }
+
+ size_t GetFrameMaximumSize() const {
+ return spdy_framer_.GetFrameMaximumSize();
+ }
+
+ size_t GetDataFrameMaximumPayload() const {
+ return spdy_framer_.GetDataFrameMaximumPayload();
+ }
+
+ int frames_received() const { return frames_received_; }
+
+ private:
+ // The size of the header_buffer_.
+ enum { kHeaderBufferSize = 32 * 1024 };
+
+ void InitHeaderStreaming(SpdyStreamId stream_id);
+
+ SpdyFramer spdy_framer_;
+ BufferedSpdyFramerVisitorInterface* visitor_;
+
+ // Header block streaming state:
+ char header_buffer_[kHeaderBufferSize];
+ size_t header_buffer_used_;
+ bool header_buffer_valid_;
+ SpdyStreamId header_stream_id_;
+ int frames_received_;
+
+ // Collection of fields from control frames that we need to
+ // buffer up from the spdy framer.
+ struct ControlFrameFields {
+ SpdyFrameType type;
+ SpdyStreamId stream_id;
+ SpdyStreamId associated_stream_id;
+ SpdyPriority priority;
+ uint8 credential_slot;
+ bool fin;
+ bool unidirectional;
+ };
+ scoped_ptr<ControlFrameFields> control_frame_fields_;
+
+ DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramer);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_BUFFERED_SPDY_FRAMER_H_
diff --git a/chromium/net/spdy/buffered_spdy_framer_unittest.cc b/chromium/net/spdy/buffered_spdy_framer_unittest.cc
new file mode 100644
index 00000000000..849138f98eb
--- /dev/null
+++ b/chromium/net/spdy/buffered_spdy_framer_unittest.cc
@@ -0,0 +1,283 @@
+// Copyright (c) 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.
+
+#include "net/spdy/buffered_spdy_framer.h"
+
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+ explicit TestBufferedSpdyVisitor(SpdyMajorVersion spdy_version)
+ : buffered_spdy_framer_(spdy_version, true),
+ error_count_(0),
+ setting_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ headers_frame_count_(0),
+ header_stream_id_(-1) {
+ }
+
+ virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE {
+ LOG(INFO) << "SpdyFramer Error: " << error_code;
+ error_count_++;
+ }
+
+ virtual void OnStreamError(
+ SpdyStreamId stream_id,
+ const std::string& description) OVERRIDE {
+ LOG(INFO) << "SpdyFramer Error on stream: " << stream_id << " "
+ << description;
+ error_count_++;
+ }
+
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) OVERRIDE {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_frame_count_++;
+ headers_ = headers;
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ syn_reply_frame_count_++;
+ headers_ = headers;
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE {
+ header_stream_id_ = stream_id;
+ EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ headers_frame_count_++;
+ headers_ = headers;
+ }
+
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {
+ LOG(FATAL) << "Unexpected OnStreamFrameData call.";
+ }
+
+ virtual void OnSettings(bool clear_persisted) OVERRIDE {}
+
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {
+ setting_count_++;
+ }
+
+ virtual void OnPing(uint32 unique_id) OVERRIDE {}
+
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {
+ }
+
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {
+ }
+
+ bool OnCredentialFrameData(const char*, size_t) {
+ LOG(FATAL) << "Unexpected OnCredentialFrameData call.";
+ return false;
+ }
+
+ void OnDataFrameHeader(const SpdyFrame* frame) {
+ LOG(FATAL) << "Unexpected OnDataFrameHeader call.";
+ }
+
+ void OnRstStream(const SpdyFrame& frame) {}
+ void OnGoAway(const SpdyFrame& frame) {}
+ void OnPing(const SpdyFrame& frame) {}
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {}
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {}
+ void OnCredential(const SpdyFrame& frame) {}
+
+ // Convenience function which runs a framer simulation with particular input.
+ void SimulateInFramer(const unsigned char* input, size_t size) {
+ buffered_spdy_framer_.set_visitor(this);
+ size_t input_remaining = size;
+ const char* input_ptr = reinterpret_cast<const char*>(input);
+ while (input_remaining > 0 &&
+ buffered_spdy_framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) {
+ // To make the tests more interesting, we feed random (amd small) chunks
+ // into the framer. This simulates getting strange-sized reads from
+ // the socket.
+ const size_t kMaxReadSize = 32;
+ size_t bytes_read =
+ (rand() % std::min(input_remaining, kMaxReadSize)) + 1;
+ size_t bytes_processed =
+ buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read);
+ input_remaining -= bytes_processed;
+ input_ptr += bytes_processed;
+ }
+ }
+
+ BufferedSpdyFramer buffered_spdy_framer_;
+
+ // Counters from the visitor callbacks.
+ int error_count_;
+ int setting_count_;
+ int syn_frame_count_;
+ int syn_reply_frame_count_;
+ int headers_frame_count_;
+
+ // Header block streaming state:
+ SpdyStreamId header_stream_id_;
+
+ // Headers from OnSyn, OnSynReply and OnHeaders for verification.
+ SpdyHeaderBlock headers_;
+};
+
+} // namespace
+
+class BufferedSpdyFramerTest
+ : public PlatformTest,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ // Returns true if the two header blocks have equivalent content.
+ bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
+ const SpdyHeaderBlock* actual) {
+ if (expected->size() != actual->size()) {
+ LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
+ << actual->size() << ".";
+ return false;
+ }
+ for (SpdyHeaderBlock::const_iterator it = expected->begin();
+ it != expected->end();
+ ++it) {
+ SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
+ if (it2 == actual->end()) {
+ LOG(ERROR) << "Expected header name '" << it->first << "'.";
+ return false;
+ }
+ if (it->second.compare(it2->second) != 0) {
+ LOG(ERROR) << "Expected header named '" << it->first
+ << "' to have a value of '" << it->second
+ << "'. The actual value received was '" << it2->second
+ << "'.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ SpdyMajorVersion spdy_version() {
+ return NextProtoToSpdyMajorVersion(GetParam());
+ }
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ BufferedSpdyFramerTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(BufferedSpdyFramerTest, OnSetting) {
+ SpdyFramer framer(spdy_version());
+ SettingsMap settings;
+ settings[SETTINGS_UPLOAD_BANDWIDTH] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000002);
+ settings[SETTINGS_DOWNLOAD_BANDWIDTH] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000003);
+
+ scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+ TestBufferedSpdyVisitor visitor(spdy_version());
+
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.setting_count_);
+}
+
+TEST_P(BufferedSpdyFramerTest, ReadSynStreamHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "vv";
+ headers["bb"] = "ww";
+ BufferedSpdyFramer framer(spdy_version(), true);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor(spdy_version());
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(BufferedSpdyFramerTest, ReadSynReplyHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(spdy_version(), true);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynReply(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor(spdy_version());
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(0, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(BufferedSpdyFramerTest, ReadHeadersHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ BufferedSpdyFramer framer(spdy_version(), true);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateHeaders(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+
+ TestBufferedSpdyVisitor visitor(spdy_version());
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+ control_frame.get()->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(0, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_bitmasks.h b/chromium/net/spdy/spdy_bitmasks.h
new file mode 100644
index 00000000000..72fb9484103
--- /dev/null
+++ b/chromium/net/spdy/spdy_bitmasks.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_BITMASKS_H_
+#define NET_SPDY_SPDY_BITMASKS_H_
+
+namespace net {
+
+// StreamId mask from the SpdyHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Control flag mask from the SpdyHeader
+const unsigned int kControlFlagMask = 0x8000;
+
+// Priority mask from the SYN_FRAME
+const unsigned int kSpdy3PriorityMask = 0xe0;
+const unsigned int kSpdy2PriorityMask = 0xc0;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+// Legal flags on data packets.
+const int kDataFlagsMask = 0x01;
+
+// Legal flags on control packets.
+const int kControlFlagsMask = 0x03;
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_BITMASKS_H_
diff --git a/chromium/net/spdy/spdy_buffer.cc b/chromium/net/spdy/spdy_buffer.cc
new file mode 100644
index 00000000000..b93ae0a6428
--- /dev/null
+++ b/chromium/net/spdy/spdy_buffer.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2013 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/spdy/spdy_buffer.h"
+
+#include <cstring>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "net/base/io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace {
+
+// Makes a SpdyFrame with |size| bytes of data copied from
+// |data|. |data| must be non-NULL and |size| must be positive.
+scoped_ptr<SpdyFrame> MakeSpdyFrame(const char* data, size_t size) {
+ DCHECK(data);
+ DCHECK_GT(size, 0u);
+ scoped_ptr<char[]> frame_data(new char[size]);
+ std::memcpy(frame_data.get(), data, size);
+ scoped_ptr<SpdyFrame> frame(
+ new SpdyFrame(frame_data.release(), size, true /* owns_buffer */));
+ return frame.Pass();
+}
+
+} // namespace
+
+// This class is an IOBuffer implementation that simply holds a
+// reference to a SharedFrame object and a fixed offset. Used by
+// SpdyBuffer::GetIOBufferForRemainingData().
+class SpdyBuffer::SharedFrameIOBuffer : public IOBuffer {
+ public:
+ SharedFrameIOBuffer(const scoped_refptr<SharedFrame>& shared_frame,
+ size_t offset)
+ : IOBuffer(shared_frame->data->data() + offset),
+ shared_frame_(shared_frame),
+ offset_(offset) {}
+
+ private:
+ virtual ~SharedFrameIOBuffer() {
+ // Prevent ~IOBuffer() from trying to delete |data_|.
+ data_ = NULL;
+ }
+
+ const scoped_refptr<SharedFrame> shared_frame_;
+ const size_t offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(SharedFrameIOBuffer);
+};
+
+SpdyBuffer::SpdyBuffer(scoped_ptr<SpdyFrame> frame)
+ : shared_frame_(new SharedFrame()),
+ offset_(0) {
+ shared_frame_->data = frame.Pass();
+}
+
+// The given data may not be strictly a SPDY frame; we (ab)use
+// |frame_| just as a container.
+SpdyBuffer::SpdyBuffer(const char* data, size_t size) :
+ shared_frame_(new SharedFrame()),
+ offset_(0) {
+ shared_frame_->data = MakeSpdyFrame(data, size);
+}
+
+SpdyBuffer::~SpdyBuffer() {
+ if (GetRemainingSize() > 0)
+ ConsumeHelper(GetRemainingSize(), DISCARD);
+}
+
+const char* SpdyBuffer::GetRemainingData() const {
+ return shared_frame_->data->data() + offset_;
+}
+
+size_t SpdyBuffer::GetRemainingSize() const {
+ return shared_frame_->data->size() - offset_;
+}
+
+void SpdyBuffer::AddConsumeCallback(const ConsumeCallback& consume_callback) {
+ consume_callbacks_.push_back(consume_callback);
+}
+
+void SpdyBuffer::Consume(size_t consume_size) {
+ ConsumeHelper(consume_size, CONSUME);
+};
+
+IOBuffer* SpdyBuffer::GetIOBufferForRemainingData() {
+ return new SharedFrameIOBuffer(shared_frame_, offset_);
+}
+
+void SpdyBuffer::ConsumeHelper(size_t consume_size,
+ ConsumeSource consume_source) {
+ DCHECK_GE(consume_size, 1u);
+ DCHECK_LE(consume_size, GetRemainingSize());
+ offset_ += consume_size;
+ for (std::vector<ConsumeCallback>::const_iterator it =
+ consume_callbacks_.begin(); it != consume_callbacks_.end(); ++it) {
+ it->Run(consume_size, consume_source);
+ }
+};
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_buffer.h b/chromium/net/spdy/spdy_buffer.h
new file mode 100644
index 00000000000..60bdab7465d
--- /dev/null
+++ b/chromium/net/spdy/spdy_buffer.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2013 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_SPDY_SPDY_BUFFER_H_
+#define NET_SPDY_SPDY_BUFFER_H_
+
+#include <cstddef>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IOBuffer;
+class SpdyFrame;
+
+// SpdyBuffer is a class to hold data read from or to be written to a
+// SPDY connection. It is similar to a DrainableIOBuffer but is not
+// ref-counted and will include a way to get notified when Consume()
+// is called.
+//
+// NOTE(akalin): This explicitly does not inherit from IOBuffer to
+// avoid the needless ref-counting and to avoid working around the
+// fact that IOBuffer member functions are not virtual.
+class NET_EXPORT_PRIVATE SpdyBuffer {
+ public:
+ // The source of a call to a ConsumeCallback.
+ enum ConsumeSource {
+ // Called via a call to Consume().
+ CONSUME,
+ // Called via the SpdyBuffer being destroyed.
+ DISCARD
+ };
+
+ // A Callback that gets called when bytes are consumed with the
+ // (non-zero) number of bytes consumed and the source of the
+ // consume. May be called any number of times with CONSUME as the
+ // source followed by at most one call with DISCARD as the
+ // source. The sum of the number of bytes consumed equals the total
+ // size of the buffer.
+ typedef base::Callback<void(size_t, ConsumeSource)> ConsumeCallback;
+
+ // Construct with the data in the given frame. Assumes that data is
+ // owned by |frame| or outlives it.
+ explicit SpdyBuffer(scoped_ptr<SpdyFrame> frame);
+
+ // Construct with a copy of the given raw data. |data| must be
+ // non-NULL and |size| must be non-zero.
+ SpdyBuffer(const char* data, size_t size);
+
+ // If there are bytes remaining in the buffer, triggers a call to
+ // any consume callbacks with a DISCARD source.
+ ~SpdyBuffer();
+
+ // Returns the remaining (unconsumed) data.
+ const char* GetRemainingData() const;
+
+ // Returns the number of remaining (unconsumed) bytes.
+ size_t GetRemainingSize() const;
+
+ // Add a callback to be called when bytes are consumed. The
+ // ConsumeCallback should not do anything complicated; ideally it
+ // should only update a counter. In particular, it must *not* cause
+ // the SpdyBuffer itself to be destroyed.
+ void AddConsumeCallback(const ConsumeCallback& consume_callback);
+
+ // Consume the given number of bytes, which must be positive but not
+ // greater than GetRemainingSize().
+ void Consume(size_t consume_size);
+
+ // Returns an IOBuffer pointing to the data starting at
+ // GetRemainingData(). Use with care; the returned IOBuffer is not
+ // updated when Consume() is called. However, it may still be used
+ // past the lifetime of this object.
+ //
+ // This is used with Socket::Write(), which takes an IOBuffer* that
+ // may be written to even after the socket itself is destroyed. (See
+ // http://crbug.com/249725 .)
+ IOBuffer* GetIOBufferForRemainingData();
+
+ private:
+ void ConsumeHelper(size_t consume_size, ConsumeSource consume_source);
+
+ // Ref-count the passed-in SpdyFrame to support the semantics of
+ // |GetIOBufferForRemainingData()|.
+ typedef base::RefCountedData<scoped_ptr<SpdyFrame> > SharedFrame;
+
+ class SharedFrameIOBuffer;
+
+ const scoped_refptr<SharedFrame> shared_frame_;
+ std::vector<ConsumeCallback> consume_callbacks_;
+ size_t offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyBuffer);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_BUFFER_H_
diff --git a/chromium/net/spdy/spdy_buffer_producer.cc b/chromium/net/spdy/spdy_buffer_producer.cc
new file mode 100644
index 00000000000..3a9598c859f
--- /dev/null
+++ b/chromium/net/spdy/spdy_buffer_producer.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 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/spdy/spdy_buffer_producer.h"
+
+#include "base/logging.h"
+#include "net/spdy/spdy_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+SpdyBufferProducer::SpdyBufferProducer() {}
+
+SpdyBufferProducer::~SpdyBufferProducer() {}
+
+SimpleBufferProducer::SimpleBufferProducer(scoped_ptr<SpdyBuffer> buffer)
+ : buffer_(buffer.Pass()) {}
+
+SimpleBufferProducer::~SimpleBufferProducer() {}
+
+scoped_ptr<SpdyBuffer> SimpleBufferProducer::ProduceBuffer() {
+ DCHECK(buffer_);
+ return buffer_.Pass();
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_buffer_producer.h b/chromium/net/spdy/spdy_buffer_producer.h
new file mode 100644
index 00000000000..fe82b1ac31d
--- /dev/null
+++ b/chromium/net/spdy/spdy_buffer_producer.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 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_SPDY_SPDY_BUFFER_PRODUCER_H_
+#define NET_SPDY_SPDY_BUFFER_PRODUCER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class SpdyBuffer;
+
+// An object which provides a SpdyBuffer for writing. We pass these
+// around instead of SpdyBuffers since some buffers have to be
+// generated "just in time".
+class NET_EXPORT_PRIVATE SpdyBufferProducer {
+ public:
+ SpdyBufferProducer();
+
+ // Produces the buffer to be written. Will be called at most once.
+ virtual scoped_ptr<SpdyBuffer> ProduceBuffer() = 0;
+
+ virtual ~SpdyBufferProducer();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyBufferProducer);
+};
+
+// A simple wrapper around a single SpdyBuffer.
+class NET_EXPORT_PRIVATE SimpleBufferProducer : public SpdyBufferProducer {
+ public:
+ explicit SimpleBufferProducer(scoped_ptr<SpdyBuffer> buffer);
+
+ virtual ~SimpleBufferProducer();
+
+ virtual scoped_ptr<SpdyBuffer> ProduceBuffer() OVERRIDE;
+
+ private:
+ scoped_ptr<SpdyBuffer> buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleBufferProducer);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_BUFFER_PRODUCER_H_
diff --git a/chromium/net/spdy/spdy_buffer_unittest.cc b/chromium/net/spdy/spdy_buffer_unittest.cc
new file mode 100644
index 00000000000..7c14ee9d8f1
--- /dev/null
+++ b/chromium/net/spdy/spdy_buffer_unittest.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2013 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/spdy/spdy_buffer.h"
+
+#include <cstddef>
+#include <cstring>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const char kData[] = "hello!\0hi.";
+const size_t kDataSize = arraysize(kData);
+
+class SpdyBufferTest : public ::testing::Test {};
+
+// Make a string from the data remaining in |buffer|.
+std::string BufferToString(const SpdyBuffer& buffer) {
+ return std::string(buffer.GetRemainingData(), buffer.GetRemainingSize());
+}
+
+// Construct a SpdyBuffer from a SpdyFrame and make sure its data
+// points to the frame's underlying data.
+TEST_F(SpdyBufferTest, FrameConstructor) {
+ SpdyBuffer buffer(
+ scoped_ptr<SpdyFrame>(
+ new SpdyFrame(const_cast<char*>(kData), kDataSize,
+ false /* owns_buffer */)));
+
+ EXPECT_EQ(kData, buffer.GetRemainingData());
+ EXPECT_EQ(kDataSize, buffer.GetRemainingSize());
+}
+
+// Construct a SpdyBuffer from a const char*/size_t pair and make sure
+// it makes a copy of the data.
+TEST_F(SpdyBufferTest, DataConstructor) {
+ std::string data(kData, kDataSize);
+ SpdyBuffer buffer(data.data(), data.size());
+ // This mutation shouldn't affect |buffer|'s data.
+ data[0] = 'H';
+
+ EXPECT_NE(kData, buffer.GetRemainingData());
+ EXPECT_EQ(kDataSize, buffer.GetRemainingSize());
+ EXPECT_EQ(std::string(kData, kDataSize), BufferToString(buffer));
+}
+
+void IncrementBy(size_t* x,
+ SpdyBuffer::ConsumeSource expected_consume_source,
+ size_t delta,
+ SpdyBuffer::ConsumeSource consume_source) {
+ EXPECT_EQ(expected_consume_source, consume_source);
+ *x += delta;
+}
+
+// Construct a SpdyBuffer and call Consume() on it, which should
+// update the remaining data pointer and size appropriately, as well
+// as calling the consume callbacks.
+TEST_F(SpdyBufferTest, Consume) {
+ SpdyBuffer buffer(kData, kDataSize);
+
+ size_t x1 = 0;
+ size_t x2 = 0;
+ buffer.AddConsumeCallback(
+ base::Bind(&IncrementBy, &x1, SpdyBuffer::CONSUME));
+ buffer.AddConsumeCallback(
+ base::Bind(&IncrementBy, &x2, SpdyBuffer::CONSUME));
+
+ EXPECT_EQ(std::string(kData, kDataSize), BufferToString(buffer));
+
+ buffer.Consume(5);
+ EXPECT_EQ(std::string(kData + 5, kDataSize - 5), BufferToString(buffer));
+ EXPECT_EQ(5u, x1);
+ EXPECT_EQ(5u, x2);
+
+ buffer.Consume(kDataSize - 5);
+ EXPECT_EQ(0u, buffer.GetRemainingSize());
+ EXPECT_EQ(kDataSize, x1);
+ EXPECT_EQ(kDataSize, x2);
+}
+
+// Construct a SpdyBuffer and attach a ConsumeCallback to it. The
+// callback should be called when the SpdyBuffer is destroyed.
+TEST_F(SpdyBufferTest, ConsumeOnDestruction) {
+ size_t x = 0;
+
+ {
+ SpdyBuffer buffer(kData, kDataSize);
+ buffer.AddConsumeCallback(
+ base::Bind(&IncrementBy, &x, SpdyBuffer::DISCARD));
+ }
+
+ EXPECT_EQ(kDataSize, x);
+}
+
+// Make sure the IOBuffer returned by GetIOBufferForRemainingData()
+// points to the buffer's remaining data and isn't updated by
+// Consume().
+TEST_F(SpdyBufferTest, GetIOBufferForRemainingData) {
+ SpdyBuffer buffer(kData, kDataSize);
+
+ buffer.Consume(5);
+ scoped_refptr<IOBuffer> io_buffer = buffer.GetIOBufferForRemainingData();
+ size_t io_buffer_size = buffer.GetRemainingSize();
+ const std::string expectedData(kData + 5, kDataSize - 5);
+ EXPECT_EQ(expectedData, std::string(io_buffer->data(), io_buffer_size));
+
+ buffer.Consume(kDataSize - 5);
+ EXPECT_EQ(expectedData, std::string(io_buffer->data(), io_buffer_size));
+}
+
+// Make sure the IOBuffer returned by GetIOBufferForRemainingData()
+// outlives the buffer itself.
+TEST_F(SpdyBufferTest, IOBufferForRemainingDataOutlivesBuffer) {
+ scoped_ptr<SpdyBuffer> buffer(new SpdyBuffer(kData, kDataSize));
+
+ scoped_refptr<IOBuffer> io_buffer = buffer->GetIOBufferForRemainingData();
+ buffer.reset();
+
+ // This will cause a use-after-free error if |io_buffer| doesn't
+ // outlive |buffer|.
+ std::memcpy(io_buffer->data(), kData, kDataSize);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_credential_builder.cc b/chromium/net/spdy/spdy_credential_builder.cc
new file mode 100644
index 00000000000..79567b668af
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_builder.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_credential_builder.h"
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/net_errors.h"
+#include "net/cert/asn1_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/ssl/server_bound_cert_service.h"
+
+namespace net {
+
+namespace {
+
+std::vector<uint8> ToVector(base::StringPiece piece) {
+ return std::vector<uint8>(piece.data(), piece.data() + piece.length());
+}
+
+} // namespace
+
+// static
+int SpdyCredentialBuilder::Build(const std::string& tls_unique,
+ const std::string& key,
+ const std::string& cert,
+ size_t slot,
+ SpdyCredential* credential) {
+ std::string secret = SpdyCredentialBuilder::GetCredentialSecret(tls_unique);
+
+ // Extract the SubjectPublicKeyInfo from the certificate.
+ base::StringPiece public_key_info;
+ if(!asn1::ExtractSPKIFromDERCert(cert, &public_key_info))
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
+ // Next, extract the SubjectPublicKey data, which will actually
+ // be stored in the cert field of the credential frame.
+ base::StringPiece public_key;
+ if (!asn1::ExtractSubjectPublicKeyFromSPKI(public_key_info, &public_key))
+ return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+ // Drop one byte of padding bits count from the BIT STRING
+ // (this will always be zero). Drop one byte of X9.62 format specification
+ // (this will always be 4 to indicated an uncompressed point).
+ DCHECK_GT(public_key.length(), 2u);
+ DCHECK_EQ(0, static_cast<int>(public_key[0]));
+ DCHECK_EQ(4, static_cast<int>(public_key[1]));
+ public_key = public_key.substr(2, public_key.length());
+
+ // Convert the strings into a vector<unit8>
+ std::vector<uint8> der_signature;
+ scoped_ptr<crypto::ECPrivateKey> private_key(
+ crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+ ServerBoundCertService::kEPKIPassword,
+ ToVector(key), ToVector(public_key_info)));
+ scoped_ptr<crypto::ECSignatureCreator> creator(
+ crypto::ECSignatureCreator::Create(private_key.get()));
+ creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()),
+ secret.length(), &der_signature);
+
+ std::vector<uint8> proof_vector;
+ if (!creator->DecodeSignature(der_signature, &proof_vector)) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ credential->slot = slot;
+ credential->certs.push_back(public_key.as_string());
+ credential->proof.assign(proof_vector.begin(), proof_vector.end());
+ return OK;
+}
+
+// static
+std::string SpdyCredentialBuilder::GetCredentialSecret(
+ const std::string& tls_unique) {
+ const char prefix[] = "SPDY CREDENTIAL ChannelID\0client -> server";
+ std::string secret(prefix, arraysize(prefix));
+ secret.append(tls_unique);
+
+ return secret;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_credential_builder.h b/chromium/net/spdy/spdy_credential_builder.h
new file mode 100644
index 00000000000..3bdc0a1171e
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_builder.h
@@ -0,0 +1,36 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
+#define NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class SSLClientSocket;
+struct SpdyCredential;
+
+// This class provides facilities for building the various fields of
+// SPDY CREDENTIAL frames.
+class NET_EXPORT_PRIVATE SpdyCredentialBuilder {
+ public:
+ static int Build(const std::string& tls_unique,
+ const std::string& key,
+ const std::string& cert,
+ size_t slot,
+ SpdyCredential* credential);
+
+ private:
+ friend class SpdyCredentialBuilderTest;
+
+ // Returns the secret data to be signed as part of a credential frame.
+ static std::string GetCredentialSecret(const std::string& tls_unique);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
diff --git a/chromium/net/spdy/spdy_credential_builder_unittest.cc b/chromium/net/spdy/spdy_credential_builder_unittest.cc
new file mode 100644
index 00000000000..f82f2f5860f
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_builder_unittest.cc
@@ -0,0 +1,149 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_credential_builder.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/cert/asn1_util.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/ssl/default_server_bound_cert_store.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+const static size_t kSlot = 2;
+const static char kSecretPrefix[] =
+ "SPDY CREDENTIAL ChannelID\0client -> server";
+
+void CreateCertAndKey(std::string* cert, std::string* key) {
+ // TODO(rch): Share this code with ServerBoundCertServiceTest.
+ scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool =
+ new base::SequencedWorkerPool(1, "CreateCertAndKey");
+ scoped_ptr<ServerBoundCertService> server_bound_cert_service(
+ new ServerBoundCertService(new DefaultServerBoundCertStore(NULL),
+ sequenced_worker_pool));
+
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+ int rv = server_bound_cert_service->GetOrCreateDomainBoundCert(
+ "www.google.com", key, cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ sequenced_worker_pool->Shutdown();
+}
+
+} // namespace
+
+class SpdyCredentialBuilderTest : public testing::Test {
+ public:
+ SpdyCredentialBuilderTest() {
+ CreateCertAndKey(&cert_, &key_);
+ }
+
+ protected:
+ int Build() {
+ return SpdyCredentialBuilder::Build(
+ MockClientSocket::kTlsUnique, key_, cert_, kSlot, &credential_);
+ }
+
+ std::string GetCredentialSecret() {
+ return SpdyCredentialBuilder::GetCredentialSecret(
+ MockClientSocket::kTlsUnique);
+ }
+
+ std::string cert_;
+ std::string key_;
+ SpdyCredential credential_;
+ MockECSignatureCreatorFactory ec_signature_creator_factory_;
+};
+
+// http://crbug.com/142833, http://crbug.com/140991. The following tests fail
+// with OpenSSL due to the unimplemented ec_private_key_openssl.cc.
+#if defined(USE_OPENSSL)
+#define MAYBE_GetCredentialSecret DISABLED_GetCredentialSecret
+#else
+#define MAYBE_GetCredentialSecret GetCredentialSecret
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_GetCredentialSecret) {
+ std::string secret_str(kSecretPrefix, arraysize(kSecretPrefix));
+ secret_str.append(MockClientSocket::kTlsUnique);
+
+ EXPECT_EQ(secret_str, GetCredentialSecret());
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_Succeeds DISABLED_Succeeds
+#else
+#define MAYBE_Succeeds Succeeds
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_Succeeds) {
+ EXPECT_EQ(OK, Build());
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsSlotCorrectly DISABLED_SetsSlotCorrectly
+#else
+#define MAYBE_SetsSlotCorrectly SetsSlotCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsSlotCorrectly) {
+ ASSERT_EQ(OK, Build());
+ EXPECT_EQ(kSlot, credential_.slot);
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsCertCorrectly DISABLED_SetsCertCorrectly
+#else
+#define MAYBE_SetsCertCorrectly SetsCertCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsCertCorrectly) {
+ ASSERT_EQ(OK, Build());
+ base::StringPiece spki;
+ ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki));
+ base::StringPiece spk;
+ ASSERT_TRUE(asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk));
+ EXPECT_EQ(1u, credential_.certs.size());
+ EXPECT_EQ(0, (int)spk[0]);
+ EXPECT_EQ(4, (int)spk[1]);
+ EXPECT_EQ(spk.substr(2, spk.length()).as_string(), credential_.certs[0]);
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsProofCorrectly DISABLED_SetsProofCorrectly
+#else
+#define MAYBE_SetsProofCorrectly SetsProofCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsProofCorrectly) {
+ ASSERT_EQ(OK, Build());
+ base::StringPiece spki;
+ ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki));
+ std::vector<uint8> spki_data(spki.data(),
+ spki.data() + spki.size());
+ std::vector<uint8> key_data(key_.data(),
+ key_.data() + key_.length());
+ std::vector<uint8> proof_data;
+ scoped_ptr<crypto::ECPrivateKey> private_key(
+ crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+ ServerBoundCertService::kEPKIPassword, key_data, spki_data));
+ scoped_ptr<crypto::ECSignatureCreator> creator(
+ crypto::ECSignatureCreator::Create(private_key.get()));
+ std::string secret = GetCredentialSecret();
+ creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()),
+ secret.length(), &proof_data);
+
+ std::string proof(proof_data.begin(), proof_data.end());
+ EXPECT_EQ(proof, credential_.proof);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_credential_state.cc b/chromium/net/spdy/spdy_credential_state.cc
new file mode 100644
index 00000000000..4549b3727f3
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_state.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_credential_state.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "net/ssl/server_bound_cert_service.h"
+
+namespace net {
+
+namespace {
+
+GURL GetCanonicalOrigin(const GURL& url) {
+ std::string domain =
+ ServerBoundCertService::GetDomainForHost(url.host());
+ DCHECK(!domain.empty());
+ if (domain == url.host())
+ return url.GetOrigin();
+ return GURL(url.scheme() + "://" + domain + ":" + url.port());
+}
+
+} // namespace
+
+const size_t SpdyCredentialState::kDefaultNumSlots = 8;
+const size_t SpdyCredentialState::kNoEntry = 0;
+
+SpdyCredentialState::SpdyCredentialState(size_t num_slots)
+ : slots_(num_slots),
+ last_added_(-1) {}
+
+SpdyCredentialState::~SpdyCredentialState() {}
+
+bool SpdyCredentialState::HasCredential(const GURL& origin) const {
+ return FindCredentialSlot(origin) != kNoEntry;
+}
+
+size_t SpdyCredentialState::SetHasCredential(const GURL& origin) {
+ size_t i = FindCredentialSlot(origin);
+ if (i != kNoEntry)
+ return i;
+ // Add the new entry at the next index following the index of the last
+ // entry added, or at index 0 if the last added index is the last index.
+ if (last_added_ + 1 == slots_.size()) {
+ last_added_ = 0;
+ } else {
+ last_added_++;
+ }
+ slots_[last_added_] = GetCanonicalOrigin(origin);
+ return last_added_ + 1;
+}
+
+size_t SpdyCredentialState::FindCredentialSlot(const GURL& origin) const {
+ GURL url = GetCanonicalOrigin(origin);
+ for (size_t i = 0; i < slots_.size(); i++) {
+ if (url == slots_[i])
+ return i + 1;
+ }
+ return kNoEntry;
+}
+
+void SpdyCredentialState::Resize(size_t size) {
+ slots_.resize(size);
+ if (last_added_ >= slots_.size())
+ last_added_ = slots_.size() - 1;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_credential_state.h b/chromium/net/spdy/spdy_credential_state.h
new file mode 100644
index 00000000000..505b012c07e
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_state.h
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_CREDENTIAL_STATE_H_
+#define NET_SPDY_SPDY_CREDENTIAL_STATE_H_
+
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// A class for tracking the credentials associated with a SPDY session.
+class NET_EXPORT_PRIVATE SpdyCredentialState {
+ public:
+ explicit SpdyCredentialState(size_t num_slots);
+ ~SpdyCredentialState();
+
+ // Changes the number of credentials being tracked. If the new size is
+ // larger, then empty slots will be added to the end. If the new size is
+ // smaller than the current size, then the extra slots will be truncated
+ // from the end.
+ void Resize(size_t size);
+
+ // Returns the one-based index in |slots_| for |url| or kNoEntry, if no entry
+ // for |url| exists.
+ size_t FindCredentialSlot(const GURL& url) const;
+
+ // Returns true if there is a credential associated with |url|.
+ bool HasCredential(const GURL& url) const;
+
+ // Adds the new credentials to be associated with all origins matching
+ // |url|. If there is space, then it will add in the first available
+ // position. Otherwise, an existing credential will be evicted. Returns
+ // the slot in which this domain was added.
+ size_t SetHasCredential(const GURL& url);
+
+ // This value is defined as the default initial value in the SPDY spec unless
+ // otherwise negotiated via SETTINGS.
+ static const size_t kDefaultNumSlots;
+
+ // Sentinel value to be returned by FindCredentialSlot when no entry exists.
+ static const size_t kNoEntry;
+
+ private:
+ // Vector of origins that have credentials.
+ std::vector<GURL> slots_;
+ // Index of the last origin added to |slots_|.
+ size_t last_added_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_CREDENTIAL_STATE_H_
diff --git a/chromium/net/spdy/spdy_credential_state_unittest.cc b/chromium/net/spdy/spdy_credential_state_unittest.cc
new file mode 100644
index 00000000000..b5129217560
--- /dev/null
+++ b/chromium/net/spdy/spdy_credential_state_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_credential_state.h"
+
+#include "net/base/host_port_pair.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class SpdyCredentialStateTest : public PlatformTest {
+ public:
+ SpdyCredentialStateTest()
+ : state_(4),
+ origin1_("https://1.com"),
+ origin2_("https://2.com"),
+ origin3_("https://3.com"),
+ origin4_("https://4.com"),
+ origin5_("https://5.com"),
+ origin6_("https://6.com"),
+ origin11_("https://11.com"),
+ host1_("https://www.1.com:443") {
+ }
+
+ protected:
+ SpdyCredentialState state_;
+ const GURL origin1_;
+ const GURL origin2_;
+ const GURL origin3_;
+ const GURL origin4_;
+ const GURL origin5_;
+ const GURL origin6_;
+ const GURL origin11_;
+ const GURL host1_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyCredentialStateTest);
+};
+
+TEST_F(SpdyCredentialStateTest, HasCredentialReturnsFalseWhenEmpty) {
+ EXPECT_FALSE(state_.HasCredential(origin1_));
+ EXPECT_FALSE(state_.HasCredential(origin2_));
+ EXPECT_FALSE(state_.HasCredential(origin3_));
+}
+
+TEST_F(SpdyCredentialStateTest, HasCredentialReturnsTrueWhenAdded) {
+ state_.SetHasCredential(origin1_);
+ EXPECT_TRUE(state_.HasCredential(origin1_));
+ EXPECT_TRUE(state_.HasCredential(host1_));
+ EXPECT_FALSE(state_.HasCredential(origin11_));
+ EXPECT_FALSE(state_.HasCredential(origin2_));
+ EXPECT_FALSE(state_.HasCredential(origin3_));
+}
+
+TEST_F(SpdyCredentialStateTest, SetCredentialAddsToEndOfList) {
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+}
+
+TEST_F(SpdyCredentialStateTest, SetReturnsPositionIfAlreadyInList) {
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+}
+
+TEST_F(SpdyCredentialStateTest, SetReplacesOldestElementWhenFull) {
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+ EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin5_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin6_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(4u, (state_.SetHasCredential(origin2_)));
+}
+
+TEST_F(SpdyCredentialStateTest, ResizeAddsEmptySpaceAtEnd) {
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+ EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+ state_.Resize(6);
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+ EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+ EXPECT_EQ(5u, (state_.SetHasCredential(origin5_)));
+ EXPECT_EQ(6u, (state_.SetHasCredential(origin6_)));
+}
+
+TEST_F(SpdyCredentialStateTest, ResizeTrunatesFromEnd) {
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+ EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+ EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+ state_.Resize(2);
+ EXPECT_TRUE(state_.HasCredential(origin1_));
+ EXPECT_TRUE(state_.HasCredential(origin2_));
+ EXPECT_FALSE(state_.HasCredential(origin3_));
+ EXPECT_FALSE(state_.HasCredential(origin4_));
+ EXPECT_EQ(1u, (state_.SetHasCredential(origin5_)));
+ EXPECT_EQ(2u, (state_.SetHasCredential(origin6_)));
+}
+
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_frame_builder.cc b/chromium/net/spdy/spdy_frame_builder.cc
new file mode 100644
index 00000000000..9e779ff4594
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_builder.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_frame_builder.h"
+
+#include <limits>
+
+#include "base/logging.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace {
+
+// A special structure for the 8 bit flags and 24 bit length fields.
+union FlagsAndLength {
+ uint8 flags_[4]; // 8 bits
+ uint32 length_; // 24 bits
+};
+
+// Creates a FlagsAndLength.
+FlagsAndLength CreateFlagsAndLength(uint8 flags, size_t length) {
+ DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(static_cast<uint32>(length));
+ DCHECK_EQ(0, flags & ~kControlFlagsMask);
+ flags_length.flags_[0] = flags;
+ return flags_length;
+}
+
+} // namespace
+
+SpdyFrameBuilder::SpdyFrameBuilder(size_t size)
+ : buffer_(new char[size]),
+ capacity_(size),
+ length_(0) {
+}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() {
+}
+
+char* SpdyFrameBuilder::GetWritableBuffer(size_t length) {
+ if (!CanWrite(length)) {
+ return NULL;
+ }
+ return buffer_.get() + length_;
+}
+
+bool SpdyFrameBuilder::Seek(size_t length) {
+ if (!CanWrite(length)) {
+ return false;
+ }
+
+ length_ += length;
+ return true;
+}
+
+bool SpdyFrameBuilder::WriteControlFrameHeader(const SpdyFramer& framer,
+ SpdyFrameType type,
+ uint8 flags) {
+ DCHECK_GE(type, FIRST_CONTROL_TYPE);
+ DCHECK_LE(type, LAST_CONTROL_TYPE);
+ DCHECK_GT(4, framer.protocol_version());
+ bool success = true;
+ FlagsAndLength flags_length = CreateFlagsAndLength(
+ flags, capacity_ - framer.GetControlFrameHeaderSize());
+ success &= WriteUInt16(kControlFlagMask | framer.protocol_version());
+ success &= WriteUInt16(type);
+ success &= WriteBytes(&flags_length, sizeof(flags_length));
+ DCHECK_EQ(framer.GetControlFrameHeaderSize(), length());
+ return success;
+}
+
+bool SpdyFrameBuilder::WriteDataFrameHeader(const SpdyFramer& framer,
+ SpdyStreamId stream_id,
+ SpdyDataFlags flags) {
+ if (framer.protocol_version() >= 4) {
+ return WriteFramePrefix(framer, DATA, flags, stream_id);
+ }
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ bool success = true;
+ success &= WriteUInt32(stream_id);
+ size_t length_field = capacity_ - framer.GetDataFrameMinimumSize();
+ DCHECK_EQ(0u, length_field & ~static_cast<size_t>(kLengthMask));
+ FlagsAndLength flags_length;
+ flags_length.length_ = htonl(length_field);
+ DCHECK_EQ(0, flags & ~kDataFlagsMask);
+ flags_length.flags_[0] = flags;
+ success &= WriteBytes(&flags_length, sizeof(flags_length));
+ DCHECK_EQ(framer.GetDataFrameMinimumSize(), length());
+ return success;
+}
+
+bool SpdyFrameBuilder::WriteFramePrefix(const SpdyFramer& framer,
+ SpdyFrameType type,
+ uint8 flags,
+ SpdyStreamId stream_id) {
+ DCHECK_LE(DATA, type);
+ DCHECK_GE(LAST_CONTROL_TYPE, type);
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ DCHECK_LE(4, framer.protocol_version());
+ bool success = true;
+ DCHECK_GT(1u<<16, capacity_); // Make sure length fits in 2B.
+ success &= WriteUInt16(capacity_);
+ success &= WriteUInt8(type);
+ success &= WriteUInt8(flags);
+ success &= WriteUInt32(stream_id);
+ DCHECK_EQ(framer.GetDataFrameMinimumSize(), length());
+ return success;
+}
+
+bool SpdyFrameBuilder::WriteString(const std::string& value) {
+ if (value.size() > 0xffff) {
+ DCHECK(false) << "Tried to write string with length > 16bit.";
+ return false;
+ }
+
+ if (!WriteUInt16(static_cast<int>(value.size())))
+ return false;
+
+ return WriteBytes(value.data(), static_cast<uint16>(value.size()));
+}
+
+bool SpdyFrameBuilder::WriteStringPiece32(const base::StringPiece& value) {
+ if (!WriteUInt32(value.size())) {
+ return false;
+ }
+
+ return WriteBytes(value.data(), value.size());
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint32 data_len) {
+ if (!CanWrite(data_len)) {
+ return false;
+ }
+
+ char* dest = GetWritableBuffer(data_len);
+ memcpy(dest, data, data_len);
+ Seek(data_len);
+ return true;
+}
+
+bool SpdyFrameBuilder::RewriteLength(const SpdyFramer& framer) {
+ if (framer.protocol_version() < 4) {
+ return OverwriteLength(framer,
+ length_ - framer.GetControlFrameHeaderSize());
+ } else {
+ return OverwriteLength(framer, length_);
+ }
+}
+
+bool SpdyFrameBuilder::OverwriteLength(const SpdyFramer& framer,
+ size_t length) {
+ bool success = false;
+ const size_t old_length = length_;
+
+ if (framer.protocol_version() < 4) {
+ FlagsAndLength flags_length = CreateFlagsAndLength(
+ 0, // We're not writing over the flags value anyway.
+ length);
+
+ // Write into the correct location by temporarily faking the offset.
+ length_ = 5; // Offset at which the length field occurs.
+ success = WriteBytes(reinterpret_cast<char*>(&flags_length) + 1,
+ sizeof(flags_length) - 1);
+ } else {
+ length_ = 0;
+ success = WriteUInt16(length);
+ }
+
+ length_ = old_length;
+ return success;
+}
+
+bool SpdyFrameBuilder::CanWrite(size_t length) const {
+ if (length > kLengthMask) {
+ DCHECK(false);
+ return false;
+ }
+
+ if (length_ + length > capacity_) {
+ DCHECK(false);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_frame_builder.h b/chromium/net/spdy/spdy_frame_builder.h
new file mode 100644
index 00000000000..825254de416
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_builder.h
@@ -0,0 +1,129 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_FRAME_BUILDER_H_
+#define NET_SPDY_SPDY_FRAME_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+class SpdyFramer;
+
+// This class provides facilities for basic binary value packing
+// into Spdy frames.
+//
+// The SpdyFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance. The SpdyFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values. The internal memory
+// buffer is exposed as the "data" of the SpdyFrameBuilder.
+class NET_EXPORT_PRIVATE SpdyFrameBuilder {
+ public:
+ // Initializes a SpdyFrameBuilder with a buffer of given size
+ explicit SpdyFrameBuilder(size_t size);
+
+ ~SpdyFrameBuilder();
+
+ // Returns the size of the SpdyFrameBuilder's data.
+ size_t length() const { return length_; }
+
+ // Returns a writeable buffer of given size in bytes, to be appended to the
+ // currently written frame. Does bounds checking on length but does not
+ // increment the underlying iterator. To do so, consumers should subsequently
+ // call Seek().
+ // In general, consumers should use Write*() calls instead of this.
+ // Returns NULL on failure.
+ char* GetWritableBuffer(size_t length);
+
+ // Seeks forward by the given number of bytes. Useful in conjunction with
+ // GetWriteableBuffer() above.
+ bool Seek(size_t length);
+
+ // Populates this frame with a SPDY control frame header using
+ // version-specific information from the |framer| and length information from
+ // capacity_. The given type must be a control frame type.
+ // Used only for SPDY versions <4.
+ bool WriteControlFrameHeader(const SpdyFramer& framer,
+ SpdyFrameType type,
+ uint8 flags);
+
+ // Populates this frame with a SPDY data frame header using version-specific
+ // information from the |framer| and length information from capacity_.
+ bool WriteDataFrameHeader(const SpdyFramer& framer,
+ SpdyStreamId stream_id,
+ SpdyDataFlags flags);
+
+ // Populates this frame with a SPDY4/HTTP2 frame prefix using
+ // version-specific information from the |framer| and length information from
+ // capacity_. The given type must be a control frame type.
+ // Used only for SPDY versions >=4.
+ bool WriteFramePrefix(const SpdyFramer& framer,
+ SpdyFrameType type,
+ uint8 flags,
+ SpdyStreamId stream_id);
+
+ // Takes the buffer from the SpdyFrameBuilder.
+ SpdyFrame* take() {
+ SpdyFrame* rv = new SpdyFrame(buffer_.release(), length_, true);
+ capacity_ = 0;
+ length_ = 0;
+ return rv;
+ }
+
+ // Methods for adding to the payload. These values are appended to the end
+ // of the SpdyFrameBuilder payload. Note - binary integers are converted from
+ // host to network form.
+ bool WriteUInt8(uint8 value) {
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt16(uint16 value) {
+ value = htons(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ bool WriteUInt32(uint32 value) {
+ value = htonl(value);
+ return WriteBytes(&value, sizeof(value));
+ }
+ // TODO(hkhalil) Rename to WriteStringPiece16().
+ bool WriteString(const std::string& value);
+ bool WriteStringPiece32(const base::StringPiece& value);
+ bool WriteBytes(const void* data, uint32 data_len);
+
+ // Update (in-place) the length field in the frame being built to reflect the
+ // current actual length of bytes written to said frame through this builder.
+ // The framer parameter is used to determine version-specific location and
+ // size information of the length field to be written, and must be initialized
+ // with the correct version for the frame being written.
+ bool RewriteLength(const SpdyFramer& framer);
+
+ // Update (in-place) the length field in the frame being built to reflect the
+ // given length.
+ // The framer parameter is used to determine version-specific location and
+ // size information of the length field to be written, and must be initialized
+ // with the correct version for the frame being written.
+ bool OverwriteLength(const SpdyFramer& framer, size_t length);
+
+ protected:
+ const char* end_of_payload() const { return buffer_.get() + length_; }
+
+ private:
+ // Checks to make sure that there is an appropriate amount of space for a
+ // write of given size, in bytes.
+ bool CanWrite(size_t length) const;
+
+ scoped_ptr<char[]> buffer_;
+ size_t capacity_; // Allocation size of payload, set by constructor.
+ size_t length_; // Current length of the buffer.
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_FRAME_BUILDER_H_
diff --git a/chromium/net/spdy/spdy_frame_builder_test.cc b/chromium/net/spdy/spdy_frame_builder_test.cc
new file mode 100644
index 00000000000..014449b2e57
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_builder_test.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2013 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/spdy/spdy_frame_builder.h"
+
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(SpdyFrameBuilderTestVersionAgnostic, GetWritableBuffer) {
+ const size_t builder_size = 10;
+ SpdyFrameBuilder builder(builder_size);
+ char* writable_buffer = builder.GetWritableBuffer(builder_size);
+ memset(writable_buffer, ~1, builder_size);
+ EXPECT_TRUE(builder.Seek(builder_size));
+ scoped_ptr<SpdyFrame> frame(builder.take());
+ char expected[builder_size];
+ memset(expected, ~1, builder_size);
+ EXPECT_EQ(base::StringPiece(expected, builder_size),
+ base::StringPiece(frame->data(), builder_size));
+}
+
+class SpdyFrameBuilderTest : public ::testing::TestWithParam<SpdyMajorVersion> {
+ protected:
+ virtual void SetUp() {
+ spdy_version_ = GetParam();
+ }
+
+ // Major version of SPDY protocol to be used.
+ SpdyMajorVersion spdy_version_;
+};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyFrameBuilderTests,
+ SpdyFrameBuilderTest,
+ ::testing::Values(SPDY2, SPDY3, SPDY4));
+
+TEST_P(SpdyFrameBuilderTest, RewriteLength) {
+ // Create an empty SETTINGS frame both via framer and manually via builder.
+ // The one created via builder is initially given the incorrect length, but
+ // then is corrected via RewriteLength().
+ SpdyFramer framer(spdy_version_);
+ SettingsMap settings;
+ scoped_ptr<SpdyFrame> expected(framer.CreateSettings(settings));
+ SpdyFrameBuilder builder(expected->size() + 1);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(framer, SETTINGS, 0);
+ } else {
+ builder.WriteFramePrefix(framer, SETTINGS, 0, 0);
+ }
+ builder.WriteUInt32(0); // Write the number of settings.
+ EXPECT_TRUE(builder.GetWritableBuffer(1) != NULL);
+ builder.RewriteLength(framer);
+ scoped_ptr<SpdyFrame> built(builder.take());
+ EXPECT_EQ(base::StringPiece(expected->data(), expected->size()),
+ base::StringPiece(built->data(), expected->size()));
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_frame_reader.cc b/chromium/net/spdy/spdy_frame_reader.cc
new file mode 100644
index 00000000000..0ab1f2f55e2
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_reader.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+
+#include <limits>
+
+#include "base/sys_byteorder.h"
+#include "net/spdy/spdy_frame_reader.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len)
+ : data_(data),
+ len_(len),
+ ofs_(0) {
+}
+
+bool SpdyFrameReader::ReadUInt8(uint8* result) {
+ // Make sure that we have the whole uint8.
+ if (!CanRead(1)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = *reinterpret_cast<const uint8*>(data_ + ofs_);
+
+ // Iterate.
+ ofs_ += 1;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt16(uint16* result) {
+ // Make sure that we have the whole uint16.
+ if (!CanRead(2)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = ntohs(*(reinterpret_cast<const uint16*>(data_ + ofs_)));
+
+ // Iterate.
+ ofs_ += 2;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt32(uint32* result) {
+ // Make sure that we have the whole uint32.
+ if (!CanRead(4)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = ntohl(*(reinterpret_cast<const uint32*>(data_ + ofs_)));
+
+ // Iterate.
+ ofs_ += 4;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadUInt31(uint32* result) {
+ bool success = ReadUInt32(result);
+
+ // Zero out highest-order bit.
+ if (success) {
+ *result &= 0x7fffffff;
+ }
+
+ return success;
+}
+
+bool SpdyFrameReader::ReadUInt24(uint32* result) {
+ // Make sure that we have the whole uint24.
+ if (!CanRead(3)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ *result = 0;
+ memcpy(reinterpret_cast<char*>(result) + 1, data_ + ofs_, 3);
+ *result = ntohl(*result);
+
+ // Iterate.
+ ofs_ += 3;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece16(base::StringPiece* result) {
+ // Read resultant length.
+ uint16 result_len;
+ if (!ReadUInt16(&result_len)) {
+ // OnFailure() already called.
+ return false;
+ }
+
+ // Make sure that we have the whole string.
+ if (!CanRead(result_len)) {
+ OnFailure();
+ return false;
+ }
+
+ // Set result.
+ result->set(data_ + ofs_, result_len);
+
+ // Iterate.
+ ofs_ += result_len;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece32(base::StringPiece* result) {
+ // Read resultant length.
+ uint32 result_len;
+ if (!ReadUInt32(&result_len)) {
+ // OnFailure() already called.
+ return false;
+ }
+
+ // Make sure that we have the whole string.
+ if (!CanRead(result_len)) {
+ OnFailure();
+ return false;
+ }
+
+ // Set result.
+ result->set(data_ + ofs_, result_len);
+
+ // Iterate.
+ ofs_ += result_len;
+
+ return true;
+}
+
+bool SpdyFrameReader::ReadBytes(void* result, size_t size) {
+ // Make sure that we have enough data to read.
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Read into result.
+ memcpy(result, data_ + ofs_, size);
+
+ // Iterate.
+ ofs_ += size;
+
+ return true;
+}
+
+bool SpdyFrameReader::Seek(size_t size) {
+ if (!CanRead(size)) {
+ OnFailure();
+ return false;
+ }
+
+ // Iterate.
+ ofs_ += size;
+
+ return true;
+}
+
+bool SpdyFrameReader::IsDoneReading() const {
+ return len_ == ofs_;
+}
+
+bool SpdyFrameReader::CanRead(size_t bytes) const {
+ return bytes <= (len_ - ofs_);
+}
+
+void SpdyFrameReader::OnFailure() {
+ // Set our iterator to the end of the buffer so that further reads fail
+ // immediately.
+ ofs_ = len_;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_frame_reader.h b/chromium/net/spdy/spdy_frame_reader.h
new file mode 100644
index 00000000000..886f5be7a47
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_reader.h
@@ -0,0 +1,123 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_FRAME_READER_H_
+#define NET_SPDY_SPDY_FRAME_READER_H_
+
+#include "base/basictypes.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Used for reading SPDY frames. Though there isn't really anything terribly
+// SPDY-specific here, it's a helper class that's useful when doing SPDY
+// framing.
+//
+// To use, simply construct a SpdyFramerReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the SpdyFrameReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class NET_EXPORT_PRIVATE SpdyFrameReader {
+ public:
+ // Caller must provide an underlying buffer to work on.
+ SpdyFrameReader(const char* data, const size_t len);
+
+ // Empty destructor.
+ ~SpdyFrameReader() {}
+
+ // Reads an 8-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt8(uint8* result);
+
+ // Reads a 16-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt16(uint16* result);
+
+ // Reads a 32-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt32(uint32* result);
+
+ // Reads a 31-bit unsigned integer into the given output parameter. This is
+ // equivalent to ReadUInt32() above except that the highest-order bit is
+ // discarded.
+ // Forwards the internal iterater (by 4B) on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt31(uint32* result);
+
+ // Reads a 24-bit unsigned integer into the given output parameter.
+ // Forwards the internal iterater (by 3B) on success.
+ // Returns true on success, false otherwise.
+ bool ReadUInt24(uint32* result);
+
+ // Reads a string prefixed with 16-bit length into the given output parameter.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece16(base::StringPiece* result);
+
+ // Reads a string prefixed with 32-bit length into the given output parameter.
+ //
+ // NOTE: Does not copy but rather references strings in the underlying buffer.
+ // This should be kept in mind when handling memory management!
+ //
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadStringPiece32(base::StringPiece* result);
+
+ // Reads a given number of bytes into the given buffer. The buffer
+ // must be of adequate size.
+ // Forwards the internal iterater on success.
+ // Returns true on success, false otherwise.
+ bool ReadBytes(void* result, size_t size);
+
+ // Seeks a given number of bytes into the buffer from the current offset.
+ // Equivelant to an empty read.
+ // Forwards the internal iterator.
+ // Returns true on success, false otherwise.
+ bool Seek(size_t size);
+
+ // Rewinds this reader to the beginning of the frame.
+ void Rewind() { ofs_ = 0; }
+
+ // Returns true if the entirety of the underlying buffer has been read via
+ // Read*() calls.
+ bool IsDoneReading() const;
+
+ // Returns the number of bytes that have been consumed by the reader so far.
+ size_t GetBytesConsumed() const { return ofs_; }
+
+ private:
+ // Returns true if the underlying buffer has enough room to read the given
+ // amount of bytes.
+ bool CanRead(size_t bytes) const;
+
+ // To be called when a read fails for any reason.
+ void OnFailure();
+
+ // The data buffer that we're reading from.
+ const char* data_;
+
+ // The length of the data buffer that we're reading from.
+ const size_t len_;
+
+ // The location of the next read from our data buffer.
+ size_t ofs_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_FRAME_READER_H_
diff --git a/chromium/net/spdy/spdy_frame_reader_test.cc b/chromium/net/spdy/spdy_frame_reader_test.cc
new file mode 100644
index 00000000000..c72e5bebac0
--- /dev/null
+++ b/chromium/net/spdy/spdy_frame_reader_test.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <iostream>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/sys_byteorder.h"
+#include "net/spdy/spdy_frame_reader.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(SpdyFrameReaderTest, ReadUInt16) {
+ // Frame data in network byte order.
+ const uint16 kFrameData[] = {
+ htons(1), htons(1<<15),
+ };
+
+ SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData),
+ arraysize(kFrameData) * sizeof(uint16));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint16 uint16_val;
+ EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1, uint16_val);
+
+ EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1<<15, uint16_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32) {
+ // Frame data in network byte order.
+ const uint32 kFrameData[] = {
+ htonl(1), htonl(1<<31),
+ };
+
+ SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData),
+ arraysize(kFrameData) * sizeof(uint32));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint32 uint32_val;
+ EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1u, uint32_val);
+
+ EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(1u<<31, uint32_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece16) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x02, // uint16(2)
+ 0x48, 0x69, // "Hi"
+ 0x00, 0x10, // uint16(16)
+ 0x54, 0x65, 0x73, 0x74,
+ 0x69, 0x6e, 0x67, 0x2c,
+ 0x20, 0x31, 0x2c, 0x20,
+ 0x32, 0x2c, 0x20, 0x33, // "Testing, 1, 2, 3"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Hi"));
+
+ EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3"));
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece32) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x03, // uint32(3)
+ 0x66, 0x6f, 0x6f, // "foo"
+ 0x00, 0x00, 0x00, 0x10, // uint32(16)
+ 0x54, 0x65, 0x73, 0x74,
+ 0x69, 0x6e, 0x67, 0x2c,
+ 0x20, 0x34, 0x2c, 0x20,
+ 0x35, 0x2c, 0x20, 0x36, // "Testing, 4, 5, 6"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("foo"));
+
+ EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6"));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, // part of a uint16
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, // part of a uint32
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ uint32 uint32_val;
+ EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val));
+
+ // Also make sure that trying to read a uint16, which technically could work,
+ // fails immediately due to previously encountered failed read.
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x03, // uint16(3)
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16, which technically could work,
+ // fails immediately due to previously encountered failed read.
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, // part of a uint16
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16, which technically could work,
+ // fails immediately due to previously encountered failed read.
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, 0x03, // uint32(3)
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16, which technically could work,
+ // fails immediately due to previously encountered failed read.
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x00, 0x00, 0x00, // part of a uint32
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ base::StringPiece stringpiece_val;
+ EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+ // Also make sure that trying to read a uint16, which technically could work,
+ // fails immediately due to previously encountered failed read.
+ uint16 uint16_val;
+ EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytes) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x66, 0x6f, 0x6f, // "foo"
+ 0x48, 0x69, // "Hi"
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ char dest1[3] = {};
+ EXPECT_TRUE(frame_reader.ReadBytes(&dest1, arraysize(dest1)));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+ EXPECT_EQ("foo", base::StringPiece(dest1, arraysize(dest1)));
+
+ char dest2[2] = {};
+ EXPECT_TRUE(frame_reader.ReadBytes(&dest2, arraysize(dest2)));
+ EXPECT_TRUE(frame_reader.IsDoneReading());
+ EXPECT_EQ("Hi", base::StringPiece(dest2, arraysize(dest2)));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) {
+ // Frame data in network byte order.
+ const char kFrameData[] = {
+ 0x01,
+ };
+
+ SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+ EXPECT_FALSE(frame_reader.IsDoneReading());
+
+ char dest[arraysize(kFrameData) + 2] = {};
+ EXPECT_FALSE(frame_reader.ReadBytes(&dest, arraysize(kFrameData) + 1));
+ EXPECT_STREQ("", dest);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_framer.cc b/chromium/net/spdy/spdy_framer.cc
new file mode 100644
index 00000000000..89a8309ea44
--- /dev/null
+++ b/chromium/net/spdy/spdy_framer.cc
@@ -0,0 +1,2361 @@
+// Copyright (c) 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.
+
+// TODO(rtenhove) clean up frame buffer size calculations so that we aren't
+// constantly adding and subtracting header sizes; this is ugly and error-
+// prone.
+
+#include "net/spdy/spdy_framer.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/stats_counters.h"
+#include "base/third_party/valgrind/memcheck.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_frame_reader.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "third_party/zlib/zlib.h"
+
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// Compute the id of our dictionary so that we know we're using the
+// right one when asked for it.
+uLong CalculateDictionaryId(const char* dictionary,
+ const size_t dictionary_size) {
+ uLong initial_value = adler32(0L, Z_NULL, 0);
+ return adler32(initial_value,
+ reinterpret_cast<const Bytef*>(dictionary),
+ dictionary_size);
+}
+
+struct DictionaryIds {
+ DictionaryIds()
+ : v2_dictionary_id(CalculateDictionaryId(kV2Dictionary, kV2DictionarySize)),
+ v3_dictionary_id(CalculateDictionaryId(kV3Dictionary, kV3DictionarySize))
+ {}
+ const uLong v2_dictionary_id;
+ const uLong v3_dictionary_id;
+};
+
+// Adler ID for the SPDY header compressor dictionaries. Note that they are
+// initialized lazily to avoid static initializers.
+base::LazyInstance<DictionaryIds>::Leaky g_dictionary_ids;
+
+// Used to indicate no flags in a SPDY flags field.
+const uint8 kNoFlags = 0;
+
+} // namespace
+
+const SpdyStreamId SpdyFramer::kInvalidStream = -1;
+const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
+// The size of the control frame buffer. Must be >= the minimum size of the
+// largest control frame, which is SYN_STREAM. See GetSynStreamMinimumSize() for
+// calculation details.
+const size_t SpdyFramer::kControlFrameBufferSize = 18;
+
+#ifdef DEBUG_SPDY_STATE_CHANGES
+#define CHANGE_STATE(newstate) \
+ do { \
+ LOG(INFO) << "Changing state from: " \
+ << StateToString(state_) \
+ << " to " << StateToString(newstate) << "\n"; \
+ DCHECK(state_ != SPDY_ERROR); \
+ DCHECK_EQ(previous_state_, state_); \
+ previous_state_ = state_; \
+ state_ = newstate; \
+ } while (false)
+#else
+#define CHANGE_STATE(newstate) \
+ do { \
+ DCHECK(state_ != SPDY_ERROR); \
+ DCHECK_EQ(previous_state_, state_); \
+ previous_state_ = state_; \
+ state_ = newstate; \
+ } while (false)
+#endif
+
+SettingsFlagsAndId SettingsFlagsAndId::FromWireFormat(int version,
+ uint32 wire) {
+ if (version < 3) {
+ ConvertFlagsAndIdForSpdy2(&wire);
+ }
+ return SettingsFlagsAndId(ntohl(wire) >> 24, ntohl(wire) & 0x00ffffff);
+}
+
+SettingsFlagsAndId::SettingsFlagsAndId(uint8 flags, uint32 id)
+ : flags_(flags), id_(id & 0x00ffffff) {
+ DCHECK_GT(1u << 24, id) << "SPDY setting ID too large.";
+}
+
+uint32 SettingsFlagsAndId::GetWireFormat(int version) const {
+ uint32 wire = htonl(id_ & 0x00ffffff) | htonl(flags_ << 24);
+ if (version < 3) {
+ ConvertFlagsAndIdForSpdy2(&wire);
+ }
+ return wire;
+}
+
+// SPDY 2 had a bug in it with respect to byte ordering of id/flags field.
+// This method is used to preserve buggy behavior and works on both
+// little-endian and big-endian hosts.
+// This method is also bidirectional (can be used to translate SPDY 2 to SPDY 3
+// as well as vice versa).
+void SettingsFlagsAndId::ConvertFlagsAndIdForSpdy2(uint32* val) {
+ uint8* wire_array = reinterpret_cast<uint8*>(val);
+ std::swap(wire_array[0], wire_array[3]);
+ std::swap(wire_array[1], wire_array[2]);
+}
+
+SpdyCredential::SpdyCredential() : slot(0) {}
+SpdyCredential::~SpdyCredential() {}
+
+SpdyFramer::SpdyFramer(SpdyMajorVersion version)
+ : current_frame_buffer_(new char[kControlFrameBufferSize]),
+ enable_compression_(true),
+ visitor_(NULL),
+ debug_visitor_(NULL),
+ display_protocol_("SPDY"),
+ spdy_version_(version),
+ syn_frame_processed_(false),
+ probable_http_response_(false) {
+ DCHECK_GE(spdy_version_, SPDY_MIN_VERSION);
+ DCHECK_LE(spdy_version_, SPDY_MAX_VERSION);
+ Reset();
+}
+
+SpdyFramer::~SpdyFramer() {
+ if (header_compressor_.get()) {
+ deflateEnd(header_compressor_.get());
+ }
+ if (header_decompressor_.get()) {
+ inflateEnd(header_decompressor_.get());
+ }
+}
+
+void SpdyFramer::Reset() {
+ state_ = SPDY_RESET;
+ previous_state_ = SPDY_RESET;
+ error_code_ = SPDY_NO_ERROR;
+ remaining_data_length_ = 0;
+ remaining_control_header_ = 0;
+ current_frame_buffer_length_ = 0;
+ current_frame_type_ = DATA;
+ current_frame_flags_ = 0;
+ current_frame_length_ = 0;
+ current_frame_stream_id_ = kInvalidStream;
+ settings_scratch_.Reset();
+}
+
+size_t SpdyFramer::GetDataFrameMinimumSize() const {
+ // Size, in bytes, of the data frame header. Future versions of SPDY
+ // will likely vary this, so we allow for the flexibility of a function call
+ // for this value as opposed to a constant.
+ return 8;
+}
+
+// Size, in bytes, of the control frame header.
+size_t SpdyFramer::GetControlFrameHeaderSize() const {
+ switch (protocol_version()) {
+ case SPDY2:
+ case SPDY3:
+ case SPDY4:
+ return 8;
+ }
+ LOG(DFATAL) << "Unhandled SPDY version.";
+ return 0;
+}
+
+size_t SpdyFramer::GetSynStreamMinimumSize() const {
+ // Size, in bytes, of a SYN_STREAM frame not including the variable-length
+ // name-value block.
+ if (spdy_version_ < 4) {
+ // Calculated as:
+ // control frame header + 2 * 4 (stream IDs) + 1 (priority) + 1 (slot)
+ return GetControlFrameHeaderSize() + 10;
+ } else {
+ // Calculated as:
+ // frame prefix + 4 (associated stream ID) + 1 (priority) + 1 (slot)
+ return GetControlFrameHeaderSize() + 6;
+ }
+}
+
+size_t SpdyFramer::GetSynReplyMinimumSize() const {
+ // Size, in bytes, of a SYN_REPLY frame not including the variable-length
+ // name-value block.
+ size_t size = GetControlFrameHeaderSize();
+ if (spdy_version_ < 4) {
+ // Calculated as:
+ // control frame header + 4 (stream IDs)
+ size += 4;
+ }
+
+ // In SPDY 2, there were 2 unused bytes before payload.
+ if (protocol_version() < 3) {
+ size += 2;
+ }
+
+ return size;
+}
+
+size_t SpdyFramer::GetRstStreamSize() const {
+ // Size, in bytes, of a RST_STREAM frame.
+ if (spdy_version_ < 4) {
+ // Calculated as:
+ // control frame header + 4 (stream id) + 4 (status code)
+ return GetControlFrameHeaderSize() + 8;
+ } else {
+ // Calculated as:
+ // frame prefix + 4 (status code)
+ return GetControlFrameHeaderSize() + 4;
+ }
+}
+
+size_t SpdyFramer::GetSettingsMinimumSize() const {
+ // Size, in bytes, of a SETTINGS frame not including the IDs and values
+ // from the variable-length value block. Calculated as:
+ // control frame header + 4 (number of ID/value pairs)
+ return GetControlFrameHeaderSize() + 4;
+}
+
+size_t SpdyFramer::GetPingSize() const {
+ // Size, in bytes, of this PING frame. Calculated as:
+ // control frame header + 4 (id)
+ return GetControlFrameHeaderSize() + 4;
+}
+
+size_t SpdyFramer::GetGoAwaySize() const {
+ // Size, in bytes, of this GOAWAY frame. Calculated as:
+ // control frame header + 4 (last good stream id)
+ size_t size = GetControlFrameHeaderSize() + 4;
+
+ // SPDY 3+ GOAWAY frames also contain a status.
+ if (protocol_version() >= 3) {
+ size += 4;
+ }
+
+ return size;
+}
+
+size_t SpdyFramer::GetHeadersMinimumSize() const {
+ // Size, in bytes, of a HEADERS frame not including the variable-length
+ // name-value block.
+ size_t size = GetControlFrameHeaderSize();
+ if (spdy_version_ < 4) {
+ // Calculated as:
+ // control frame header + 4 (stream IDs)
+ size += 4;
+ }
+
+ // In SPDY 2, there were 2 unused bytes before payload.
+ if (protocol_version() < 3) {
+ size += 2;
+ }
+
+ return size;
+}
+
+size_t SpdyFramer::GetWindowUpdateSize() const {
+ // Size, in bytes, of a WINDOW_UPDATE frame.
+ if (spdy_version_ < 4) {
+ // Calculated as:
+ // control frame header + 4 (stream id) + 4 (delta)
+ return GetControlFrameHeaderSize() + 8;
+ } else {
+ // Calculated as:
+ // frame prefix + 4 (delta)
+ return GetControlFrameHeaderSize() + 4;
+ }
+}
+
+size_t SpdyFramer::GetCredentialMinimumSize() const {
+ // Size, in bytes, of a CREDENTIAL frame sans variable-length certificate list
+ // and proof. Calculated as:
+ // control frame header + 2 (slot)
+ return GetControlFrameHeaderSize() + 2;
+}
+
+size_t SpdyFramer::GetBlockedSize() const {
+ DCHECK_LE(4, protocol_version());
+ // Size, in bytes, of a BLOCKED frame.
+ // The BLOCKED frame has no payload beyond the control frame header.
+ return GetControlFrameHeaderSize();
+}
+
+size_t SpdyFramer::GetPushPromiseMinimumSize() const {
+ DCHECK_LE(4, protocol_version());
+ // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block.
+ // Calculated as frame prefix + 4 (promised stream id).
+ return GetControlFrameHeaderSize() + 4;
+}
+
+size_t SpdyFramer::GetFrameMinimumSize() const {
+ return std::min(GetDataFrameMinimumSize(), GetControlFrameHeaderSize());
+}
+
+size_t SpdyFramer::GetFrameMaximumSize() const {
+ return (protocol_version() < 4) ? 0xffffff : 0xffff;
+}
+
+size_t SpdyFramer::GetDataFrameMaximumPayload() const {
+ return GetFrameMaximumSize() - GetDataFrameMinimumSize();
+}
+
+const char* SpdyFramer::StateToString(int state) {
+ switch (state) {
+ case SPDY_ERROR:
+ return "ERROR";
+ case SPDY_AUTO_RESET:
+ return "AUTO_RESET";
+ case SPDY_RESET:
+ return "RESET";
+ case SPDY_READING_COMMON_HEADER:
+ return "READING_COMMON_HEADER";
+ case SPDY_CONTROL_FRAME_PAYLOAD:
+ return "CONTROL_FRAME_PAYLOAD";
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ return "IGNORE_REMAINING_PAYLOAD";
+ case SPDY_FORWARD_STREAM_FRAME:
+ return "FORWARD_STREAM_FRAME";
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK";
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+ return "SPDY_CONTROL_FRAME_HEADER_BLOCK";
+ case SPDY_CREDENTIAL_FRAME_PAYLOAD:
+ return "SPDY_CREDENTIAL_FRAME_PAYLOAD";
+ case SPDY_SETTINGS_FRAME_PAYLOAD:
+ return "SPDY_SETTINGS_FRAME_PAYLOAD";
+ }
+ return "UNKNOWN_STATE";
+}
+
+void SpdyFramer::set_error(SpdyError error) {
+ DCHECK(visitor_);
+ error_code_ = error;
+ CHANGE_STATE(SPDY_ERROR);
+ visitor_->OnError(this);
+}
+
+const char* SpdyFramer::ErrorCodeToString(int error_code) {
+ switch (error_code) {
+ case SPDY_NO_ERROR:
+ return "NO_ERROR";
+ case SPDY_INVALID_CONTROL_FRAME:
+ return "INVALID_CONTROL_FRAME";
+ case SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+ return "CONTROL_PAYLOAD_TOO_LARGE";
+ case SPDY_ZLIB_INIT_FAILURE:
+ return "ZLIB_INIT_FAILURE";
+ case SPDY_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case SPDY_DECOMPRESS_FAILURE:
+ return "DECOMPRESS_FAILURE";
+ case SPDY_COMPRESS_FAILURE:
+ return "COMPRESS_FAILURE";
+ case SPDY_INVALID_DATA_FRAME_FLAGS:
+ return "SPDY_INVALID_DATA_FRAME_FLAGS";
+ case SPDY_INVALID_CONTROL_FRAME_FLAGS:
+ return "SPDY_INVALID_CONTROL_FRAME_FLAGS";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+const char* SpdyFramer::StatusCodeToString(int status_code) {
+ switch (status_code) {
+ case RST_STREAM_INVALID:
+ return "INVALID";
+ case RST_STREAM_PROTOCOL_ERROR:
+ return "PROTOCOL_ERROR";
+ case RST_STREAM_INVALID_STREAM:
+ return "INVALID_STREAM";
+ case RST_STREAM_REFUSED_STREAM:
+ return "REFUSED_STREAM";
+ case RST_STREAM_UNSUPPORTED_VERSION:
+ return "UNSUPPORTED_VERSION";
+ case RST_STREAM_CANCEL:
+ return "CANCEL";
+ case RST_STREAM_INTERNAL_ERROR:
+ return "INTERNAL_ERROR";
+ case RST_STREAM_FLOW_CONTROL_ERROR:
+ return "FLOW_CONTROL_ERROR";
+ case RST_STREAM_STREAM_IN_USE:
+ return "STREAM_IN_USE";
+ case RST_STREAM_STREAM_ALREADY_CLOSED:
+ return "STREAM_ALREADY_CLOSED";
+ case RST_STREAM_INVALID_CREDENTIALS:
+ return "INVALID_CREDENTIALS";
+ case RST_STREAM_FRAME_TOO_LARGE:
+ return "FRAME_TOO_LARGE";
+ }
+ return "UNKNOWN_STATUS";
+}
+
+const char* SpdyFramer::FrameTypeToString(SpdyFrameType type) {
+ switch (type) {
+ case DATA:
+ return "DATA";
+ case SYN_STREAM:
+ return "SYN_STREAM";
+ case SYN_REPLY:
+ return "SYN_REPLY";
+ case RST_STREAM:
+ return "RST_STREAM";
+ case SETTINGS:
+ return "SETTINGS";
+ case NOOP:
+ return "NOOP";
+ case PING:
+ return "PING";
+ case GOAWAY:
+ return "GOAWAY";
+ case HEADERS:
+ return "HEADERS";
+ case WINDOW_UPDATE:
+ return "WINDOW_UPDATE";
+ case CREDENTIAL:
+ return "CREDENTIAL";
+ case BLOCKED:
+ return "BLOCKED";
+ case PUSH_PROMISE:
+ return "PUSH_PROMISE";
+ }
+ return "UNKNOWN_CONTROL_TYPE";
+}
+
+size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
+ DCHECK(visitor_);
+ DCHECK(data);
+
+ size_t original_len = len;
+ do {
+ previous_state_ = state_;
+ switch (state_) {
+ case SPDY_ERROR:
+ goto bottom;
+
+ case SPDY_AUTO_RESET:
+ case SPDY_RESET:
+ Reset();
+ if (len > 0) {
+ CHANGE_STATE(SPDY_READING_COMMON_HEADER);
+ }
+ break;
+
+ case SPDY_READING_COMMON_HEADER: {
+ size_t bytes_read = ProcessCommonHeader(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: {
+ // Control frames that contain header blocks
+ // (SYN_STREAM, SYN_REPLY, HEADERS, PUSH_PROMISE)
+ // take a different path through the state machine - they
+ // will go:
+ // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+ // 2. SPDY_CONTROL_FRAME_HEADER_BLOCK
+ //
+ // SETTINGS frames take a slightly modified route:
+ // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+ // 2. SPDY_SETTINGS_FRAME_PAYLOAD
+ //
+ // All other control frames will use the alternate route directly to
+ // SPDY_CONTROL_FRAME_PAYLOAD
+ int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_SETTINGS_FRAME_PAYLOAD: {
+ int bytes_read = ProcessSettingsFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_CONTROL_FRAME_HEADER_BLOCK: {
+ int bytes_read = ProcessControlFrameHeaderBlock(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_CREDENTIAL_FRAME_PAYLOAD: {
+ size_t bytes_read = ProcessCredentialFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_CONTROL_FRAME_PAYLOAD: {
+ size_t bytes_read = ProcessControlFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+
+ case SPDY_IGNORE_REMAINING_PAYLOAD:
+ // control frame has too-large payload
+ // intentional fallthrough
+ case SPDY_FORWARD_STREAM_FRAME: {
+ size_t bytes_read = ProcessDataFramePayload(data, len);
+ len -= bytes_read;
+ data += bytes_read;
+ break;
+ }
+ default:
+ LOG(DFATAL) << "Invalid value for " << display_protocol_
+ << " framer state: " << state_;
+ // This ensures that we don't infinite-loop if state_ gets an
+ // invalid value somehow, such as due to a SpdyFramer getting deleted
+ // from a callback it calls.
+ goto bottom;
+ }
+ } while (state_ != previous_state_);
+ bottom:
+ DCHECK(len == 0 || state_ == SPDY_ERROR);
+ if (current_frame_buffer_length_ == 0 &&
+ remaining_data_length_ == 0 &&
+ remaining_control_header_ == 0) {
+ DCHECK(state_ == SPDY_RESET || state_ == SPDY_ERROR)
+ << "State: " << StateToString(state_);
+ }
+
+ return original_len - len;
+}
+
+size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) {
+ // This should only be called when we're in the SPDY_READING_COMMON_HEADER
+ // state.
+ DCHECK_EQ(state_, SPDY_READING_COMMON_HEADER);
+
+ size_t original_len = len;
+
+ // Update current frame buffer as needed.
+ if (current_frame_buffer_length_ < GetControlFrameHeaderSize()) {
+ size_t bytes_desired =
+ GetControlFrameHeaderSize() - current_frame_buffer_length_;
+ UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
+ }
+
+ if (current_frame_buffer_length_ < GetControlFrameHeaderSize()) {
+ // Not enough information to do anything meaningful.
+ return original_len - len;
+ }
+
+ // Using a scoped_ptr here since we may need to create a new SpdyFrameReader
+ // when processing DATA frames below.
+ scoped_ptr<SpdyFrameReader> reader(
+ new SpdyFrameReader(current_frame_buffer_.get(),
+ current_frame_buffer_length_));
+
+ uint16 version = 0;
+ bool is_control_frame = false;
+
+ uint16 control_frame_type_field = DATA;
+ // ProcessControlFrameHeader() will set current_frame_type_ to the
+ // correct value if this is a valid control frame.
+ current_frame_type_ = DATA;
+ if (protocol_version() < 4) {
+ bool successful_read = reader->ReadUInt16(&version);
+ DCHECK(successful_read);
+ is_control_frame = (version & kControlFlagMask) != 0;
+ version &= ~kControlFlagMask; // Only valid for control frames.
+
+ if (is_control_frame) {
+ // We check control_frame_type_field's validity in
+ // ProcessControlFrameHeader().
+ successful_read = reader->ReadUInt16(&control_frame_type_field);
+ } else {
+ reader->Rewind();
+ successful_read = reader->ReadUInt31(&current_frame_stream_id_);
+ }
+ DCHECK(successful_read);
+
+ successful_read = reader->ReadUInt8(&current_frame_flags_);
+ DCHECK(successful_read);
+
+ uint32 length_field = 0;
+ successful_read = reader->ReadUInt24(&length_field);
+ DCHECK(successful_read);
+ remaining_data_length_ = length_field;
+ current_frame_length_ = remaining_data_length_ + reader->GetBytesConsumed();
+ } else {
+ version = protocol_version();
+ uint16 length_field = 0;
+ bool successful_read = reader->ReadUInt16(&length_field);
+ DCHECK(successful_read);
+ current_frame_length_ = length_field;
+
+ uint8 control_frame_type_field_uint8 = DATA;
+ successful_read = reader->ReadUInt8(&control_frame_type_field_uint8);
+ DCHECK(successful_read);
+ // We check control_frame_type_field's validity in
+ // ProcessControlFrameHeader().
+ control_frame_type_field = control_frame_type_field_uint8;
+ is_control_frame = (control_frame_type_field != DATA);
+
+ successful_read = reader->ReadUInt8(&current_frame_flags_);
+ DCHECK(successful_read);
+
+ successful_read = reader->ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+
+ remaining_data_length_ = current_frame_length_ - reader->GetBytesConsumed();
+ }
+ DCHECK_EQ(is_control_frame ? GetControlFrameHeaderSize()
+ : GetDataFrameMinimumSize(),
+ reader->GetBytesConsumed());
+ DCHECK_EQ(current_frame_length_,
+ remaining_data_length_ + reader->GetBytesConsumed());
+
+ // This is just a sanity check for help debugging early frame errors.
+ if (remaining_data_length_ > 1000000u) {
+ // The strncmp for 5 is safe because we only hit this point if we
+ // have kMinCommonHeader (8) bytes
+ if (!syn_frame_processed_ &&
+ strncmp(current_frame_buffer_.get(), "HTTP/", 5) == 0) {
+ LOG(WARNING) << "Unexpected HTTP response to " << display_protocol_
+ << " request";
+ probable_http_response_ = true;
+ } else {
+ LOG(WARNING) << "Unexpectedly large frame. " << display_protocol_
+ << " session is likely corrupt.";
+ }
+ }
+
+ // if we're here, then we have the common header all received.
+ if (!is_control_frame) {
+ if (current_frame_flags_ & ~DATA_FLAG_FIN) {
+ set_error(SPDY_INVALID_DATA_FRAME_FLAGS);
+ } else {
+ visitor_->OnDataFrameHeader(current_frame_stream_id_,
+ remaining_data_length_,
+ current_frame_flags_ & DATA_FLAG_FIN);
+ if (remaining_data_length_ > 0) {
+ CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
+ } else {
+ // Empty data frame.
+ if (current_frame_flags_ & DATA_FLAG_FIN) {
+ visitor_->OnStreamFrameData(
+ current_frame_stream_id_, NULL, 0, true);
+ }
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ }
+ } else if (version != spdy_version_) {
+ // We check version before we check validity: version can never be
+ // 'invalid', it can only be unsupported.
+ DLOG(INFO) << "Unsupported SPDY version " << version
+ << " (expected " << spdy_version_ << ")";
+ set_error(SPDY_UNSUPPORTED_VERSION);
+ } else {
+ ProcessControlFrameHeader(control_frame_type_field);
+ }
+
+ return original_len - len;
+}
+
+void SpdyFramer::ProcessControlFrameHeader(uint16 control_frame_type_field) {
+ DCHECK_EQ(SPDY_NO_ERROR, error_code_);
+ DCHECK_LE(GetControlFrameHeaderSize(), current_frame_buffer_length_);
+
+ if (control_frame_type_field < FIRST_CONTROL_TYPE ||
+ control_frame_type_field > LAST_CONTROL_TYPE) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ return;
+ }
+
+ current_frame_type_ = static_cast<SpdyFrameType>(control_frame_type_field);
+
+ if (current_frame_type_ == NOOP) {
+ DLOG(INFO) << "NOOP control frame found. Ignoring.";
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ return;
+ }
+
+ // Do some sanity checking on the control frame sizes and flags.
+ switch (current_frame_type_) {
+ case SYN_STREAM:
+ if (current_frame_length_ < GetSynStreamMinimumSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ &
+ ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case SYN_REPLY:
+ if (current_frame_length_ < GetSynReplyMinimumSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case RST_STREAM:
+ if (current_frame_length_ != GetRstStreamSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case SETTINGS:
+ // Make sure that we have an integral number of 8-byte key/value pairs,
+ // plus a 4-byte length field.
+ if (current_frame_length_ < GetSettingsMinimumSize() ||
+ (current_frame_length_ - GetControlFrameHeaderSize()) % 8 != 4) {
+ DLOG(WARNING) << "Invalid length for SETTINGS frame: "
+ << current_frame_length_;
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ &
+ ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case PING:
+ if (current_frame_length_ != GetPingSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case GOAWAY:
+ {
+ if (current_frame_length_ != GetGoAwaySize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ }
+ case HEADERS:
+ if (current_frame_length_ < GetHeadersMinimumSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case WINDOW_UPDATE:
+ if (current_frame_length_ != GetWindowUpdateSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case CREDENTIAL:
+ if (current_frame_length_ < GetCredentialMinimumSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case BLOCKED:
+ if (current_frame_length_ != GetBlockedSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ case PUSH_PROMISE:
+ if (current_frame_length_ < GetPushPromiseMinimumSize()) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ } else if (current_frame_flags_ != 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS);
+ }
+ break;
+ default:
+ LOG(WARNING) << "Valid " << display_protocol_
+ << " control frame with unhandled type: "
+ << current_frame_type_;
+ // This branch should be unreachable because of the frame type bounds
+ // check above. However, we DLOG(FATAL) here in an effort to painfully
+ // club the head of the developer who failed to keep this file in sync
+ // with spdy_protocol.h.
+ DLOG(FATAL);
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+
+ if (state_ == SPDY_ERROR) {
+ return;
+ }
+
+ if (current_frame_length_ > GetControlFrameBufferMaxSize()) {
+ DLOG(WARNING) << "Received control frame with way too big of a payload: "
+ << current_frame_length_;
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ return;
+ }
+
+ if (current_frame_type_ == CREDENTIAL) {
+ CHANGE_STATE(SPDY_CREDENTIAL_FRAME_PAYLOAD);
+ return;
+ }
+
+ // Determine the frame size without variable-length data.
+ int32 frame_size_without_variable_data;
+ switch (current_frame_type_) {
+ case SYN_STREAM:
+ syn_frame_processed_ = true;
+ frame_size_without_variable_data = GetSynStreamMinimumSize();
+ break;
+ case SYN_REPLY:
+ syn_frame_processed_ = true;
+ frame_size_without_variable_data = GetSynReplyMinimumSize();
+ break;
+ case SETTINGS:
+ frame_size_without_variable_data = GetSettingsMinimumSize();
+ break;
+ case HEADERS:
+ frame_size_without_variable_data = GetHeadersMinimumSize();
+ break;
+ case PUSH_PROMISE:
+ frame_size_without_variable_data = GetPushPromiseMinimumSize();
+ break;
+ default:
+ frame_size_without_variable_data = -1;
+ break;
+ }
+
+ if ((frame_size_without_variable_data == -1) &&
+ (current_frame_length_ > kControlFrameBufferSize)) {
+ // We should already be in an error state. Double-check.
+ DCHECK_EQ(SPDY_ERROR, state_);
+ if (state_ != SPDY_ERROR) {
+ LOG(DFATAL) << display_protocol_
+ << " control frame buffer too small for fixed-length frame.";
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ return;
+ }
+
+ if (frame_size_without_variable_data > 0) {
+ // We have a control frame with a header block. We need to parse the
+ // remainder of the control frame's header before we can parse the header
+ // block. The start of the header block varies with the control type.
+ DCHECK_GE(frame_size_without_variable_data,
+ static_cast<int32>(current_frame_buffer_length_));
+ remaining_control_header_ = frame_size_without_variable_data -
+ current_frame_buffer_length_;
+
+ CHANGE_STATE(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK);
+ return;
+ }
+
+ CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD);
+}
+
+size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len,
+ size_t max_bytes) {
+ size_t bytes_to_read = std::min(*len, max_bytes);
+ if (bytes_to_read > 0) {
+ DCHECK_GE(kControlFrameBufferSize,
+ current_frame_buffer_length_ + bytes_to_read);
+ memcpy(current_frame_buffer_.get() + current_frame_buffer_length_,
+ *data,
+ bytes_to_read);
+ current_frame_buffer_length_ += bytes_to_read;
+ *data += bytes_to_read;
+ *len -= bytes_to_read;
+ }
+ return bytes_to_read;
+}
+
+size_t SpdyFramer::GetSerializedLength(const int spdy_version,
+ const SpdyHeaderBlock* headers) {
+ const size_t num_name_value_pairs_size
+ = (spdy_version < 3) ? sizeof(uint16) : sizeof(uint32);
+ const size_t length_of_name_size = num_name_value_pairs_size;
+ const size_t length_of_value_size = num_name_value_pairs_size;
+
+ size_t total_length = num_name_value_pairs_size;
+ for (SpdyHeaderBlock::const_iterator it = headers->begin();
+ it != headers->end();
+ ++it) {
+ // We add space for the length of the name and the length of the value as
+ // well as the length of the name and the length of the value.
+ total_length += length_of_name_size + it->first.size() +
+ length_of_value_size + it->second.size();
+ }
+ return total_length;
+}
+
+void SpdyFramer::WriteHeaderBlock(SpdyFrameBuilder* frame,
+ const int spdy_version,
+ const SpdyHeaderBlock* headers) {
+ if (spdy_version < 3) {
+ frame->WriteUInt16(headers->size()); // Number of headers.
+ } else {
+ frame->WriteUInt32(headers->size()); // Number of headers.
+ }
+ SpdyHeaderBlock::const_iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ if (spdy_version < 3) {
+ frame->WriteString(it->first);
+ frame->WriteString(it->second);
+ } else {
+ frame->WriteStringPiece32(it->first);
+ frame->WriteStringPiece32(it->second);
+ }
+ }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+
+// These constants are used by zlib to differentiate between normal data and
+// cookie data. Cookie data is handled specially by zlib when compressing.
+enum ZDataClass {
+ // kZStandardData is compressed normally, save that it will never match
+ // against any other class of data in the window.
+ kZStandardData = Z_CLASS_STANDARD,
+ // kZCookieData is compressed in its own Huffman blocks and only matches in
+ // its entirety and only against other kZCookieData blocks. Any matches must
+ // be preceeded by a kZStandardData byte, or a semicolon to prevent matching
+ // a suffix. It's assumed that kZCookieData ends in a semicolon to prevent
+ // prefix matches.
+ kZCookieData = Z_CLASS_COOKIE,
+ // kZHuffmanOnlyData is only Huffman compressed - no matches are performed
+ // against the window.
+ kZHuffmanOnlyData = Z_CLASS_HUFFMAN_ONLY,
+};
+
+// WriteZ writes |data| to the deflate context |out|. WriteZ will flush as
+// needed when switching between classes of data.
+static void WriteZ(const base::StringPiece& data,
+ ZDataClass clas,
+ z_stream* out) {
+ int rv;
+
+ // If we are switching from standard to non-standard data then we need to end
+ // the current Huffman context to avoid it leaking between them.
+ if (out->clas == kZStandardData &&
+ clas != kZStandardData) {
+ out->avail_in = 0;
+ rv = deflate(out, Z_PARTIAL_FLUSH);
+ DCHECK_EQ(Z_OK, rv);
+ DCHECK_EQ(0u, out->avail_in);
+ DCHECK_LT(0u, out->avail_out);
+ }
+
+ out->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.data()));
+ out->avail_in = data.size();
+ out->clas = clas;
+ if (clas == kZStandardData) {
+ rv = deflate(out, Z_NO_FLUSH);
+ } else {
+ rv = deflate(out, Z_PARTIAL_FLUSH);
+ }
+ if (!data.empty()) {
+ // If we didn't provide any data then zlib will return Z_BUF_ERROR.
+ DCHECK_EQ(Z_OK, rv);
+ }
+ DCHECK_EQ(0u, out->avail_in);
+ DCHECK_LT(0u, out->avail_out);
+}
+
+// WriteLengthZ writes |n| as a |length|-byte, big-endian number to |out|.
+static void WriteLengthZ(size_t n,
+ unsigned length,
+ ZDataClass clas,
+ z_stream* out) {
+ char buf[4];
+ DCHECK_LE(length, sizeof(buf));
+ for (unsigned i = 1; i <= length; i++) {
+ buf[length - i] = n;
+ n >>= 8;
+ }
+ WriteZ(base::StringPiece(buf, length), clas, out);
+}
+
+// WriteHeaderBlockToZ serialises |headers| to the deflate context |z| in a
+// manner that resists the length of the compressed data from compromising
+// cookie data.
+void SpdyFramer::WriteHeaderBlockToZ(const SpdyHeaderBlock* headers,
+ z_stream* z) const {
+ unsigned length_length = 4;
+ if (spdy_version_ < 3)
+ length_length = 2;
+
+ WriteLengthZ(headers->size(), length_length, kZStandardData, z);
+
+ std::map<std::string, std::string>::const_iterator it;
+ for (it = headers->begin(); it != headers->end(); ++it) {
+ WriteLengthZ(it->first.size(), length_length, kZStandardData, z);
+ WriteZ(it->first, kZStandardData, z);
+
+ if (it->first == "cookie") {
+ // We require the cookie values (save for the last) to end with a
+ // semicolon and (save for the first) to start with a space. This is
+ // typically the format that we are given them in but we reserialize them
+ // to be sure.
+
+ std::vector<base::StringPiece> cookie_values;
+ size_t cookie_length = 0;
+ base::StringPiece cookie_data(it->second);
+
+ for (;;) {
+ while (!cookie_data.empty() &&
+ (cookie_data[0] == ' ' || cookie_data[0] == '\t')) {
+ cookie_data.remove_prefix(1);
+ }
+ if (cookie_data.empty())
+ break;
+
+ size_t i;
+ for (i = 0; i < cookie_data.size(); i++) {
+ if (cookie_data[i] == ';')
+ break;
+ }
+ if (i < cookie_data.size()) {
+ cookie_values.push_back(cookie_data.substr(0, i));
+ cookie_length += i + 2 /* semicolon and space */;
+ cookie_data.remove_prefix(i + 1);
+ } else {
+ cookie_values.push_back(cookie_data);
+ cookie_length += cookie_data.size();
+ cookie_data.remove_prefix(i);
+ }
+ }
+
+ WriteLengthZ(cookie_length, length_length, kZStandardData, z);
+ for (size_t i = 0; i < cookie_values.size(); i++) {
+ std::string cookie;
+ // Since zlib will only back-reference complete cookies, a cookie that
+ // is currently last (and so doesn't have a trailing semicolon) won't
+ // match if it's later in a non-final position. The same is true of
+ // the first cookie.
+ if (i == 0 && cookie_values.size() == 1) {
+ cookie = cookie_values[i].as_string();
+ } else if (i == 0) {
+ cookie = cookie_values[i].as_string() + ";";
+ } else if (i < cookie_values.size() - 1) {
+ cookie = " " + cookie_values[i].as_string() + ";";
+ } else {
+ cookie = " " + cookie_values[i].as_string();
+ }
+ WriteZ(cookie, kZCookieData, z);
+ }
+ } else if (it->first == "accept" ||
+ it->first == "accept-charset" ||
+ it->first == "accept-encoding" ||
+ it->first == "accept-language" ||
+ it->first == "host" ||
+ it->first == "version" ||
+ it->first == "method" ||
+ it->first == "scheme" ||
+ it->first == ":host" ||
+ it->first == ":version" ||
+ it->first == ":method" ||
+ it->first == ":scheme" ||
+ it->first == "user-agent") {
+ WriteLengthZ(it->second.size(), length_length, kZStandardData, z);
+ WriteZ(it->second, kZStandardData, z);
+ } else {
+ // Non-whitelisted headers are Huffman compressed in their own block, but
+ // don't match against the window.
+ WriteLengthZ(it->second.size(), length_length, kZStandardData, z);
+ WriteZ(it->second, kZHuffmanOnlyData, z);
+ }
+ }
+
+ z->avail_in = 0;
+ int rv = deflate(z, Z_SYNC_FLUSH);
+ DCHECK_EQ(Z_OK, rv);
+ z->clas = kZStandardData;
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data,
+ size_t len) {
+ DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_);
+ size_t original_len = len;
+
+ if (remaining_control_header_ > 0) {
+ size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+ remaining_control_header_);
+ remaining_control_header_ -= bytes_read;
+ remaining_data_length_ -= bytes_read;
+ }
+
+ if (remaining_control_header_ == 0) {
+ SpdyFrameReader reader(current_frame_buffer_.get(),
+ current_frame_buffer_length_);
+ reader.Seek(GetControlFrameHeaderSize()); // Seek past frame header.
+
+ switch (current_frame_type_) {
+ case SYN_STREAM:
+ {
+ bool successful_read = true;
+ if (spdy_version_ < 4) {
+ successful_read = reader.ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+ }
+ if (current_frame_stream_id_ == 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+
+ SpdyStreamId associated_to_stream_id = kInvalidStream;
+ successful_read = reader.ReadUInt31(&associated_to_stream_id);
+ DCHECK(successful_read);
+
+ SpdyPriority priority = 0;
+ successful_read = reader.ReadUInt8(&priority);
+ DCHECK(successful_read);
+ if (protocol_version() < 3) {
+ priority = priority >> 6;
+ } else {
+ priority = priority >> 5;
+ }
+
+ uint8 slot = 0;
+ if (protocol_version() < 3) {
+ // SPDY 2 had an unused byte here. Seek past it.
+ reader.Seek(1);
+ } else {
+ successful_read = reader.ReadUInt8(&slot);
+ DCHECK(successful_read);
+ }
+
+ DCHECK(reader.IsDoneReading());
+ if (debug_visitor_) {
+ debug_visitor_->OnReceiveCompressedFrame(
+ current_frame_stream_id_,
+ current_frame_type_,
+ current_frame_length_);
+ }
+ visitor_->OnSynStream(
+ current_frame_stream_id_,
+ associated_to_stream_id,
+ priority,
+ slot,
+ (current_frame_flags_ & CONTROL_FLAG_FIN) != 0,
+ (current_frame_flags_ & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ case SETTINGS:
+ visitor_->OnSettings(current_frame_flags_ &
+ SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS);
+ CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD);
+ break;
+ case SYN_REPLY:
+ case HEADERS:
+ // SYN_REPLY and HEADERS are the same, save for the visitor call.
+ {
+ bool successful_read = true;
+ if (spdy_version_ < 4) {
+ successful_read = reader.ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+ }
+ if (current_frame_stream_id_ == 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+ if (protocol_version() < 3) {
+ // SPDY 2 had two unused bytes here. Seek past them.
+ reader.Seek(2);
+ }
+ DCHECK(reader.IsDoneReading());
+ if (debug_visitor_) {
+ debug_visitor_->OnReceiveCompressedFrame(
+ current_frame_stream_id_,
+ current_frame_type_,
+ current_frame_length_);
+ }
+ if (current_frame_type_ == SYN_REPLY) {
+ visitor_->OnSynReply(
+ current_frame_stream_id_,
+ (current_frame_flags_ & CONTROL_FLAG_FIN) != 0);
+ } else {
+ visitor_->OnHeaders(
+ current_frame_stream_id_,
+ (current_frame_flags_ & CONTROL_FLAG_FIN) != 0);
+ }
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ case PUSH_PROMISE:
+ {
+ DCHECK_LE(4, protocol_version());
+ if (current_frame_stream_id_ == 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+ SpdyStreamId promised_stream_id = kInvalidStream;
+ bool successful_read = reader.ReadUInt31(&promised_stream_id);
+ DCHECK(successful_read);
+ DCHECK(reader.IsDoneReading());
+ if (promised_stream_id == 0) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ break;
+ }
+ if (debug_visitor_) {
+ debug_visitor_->OnReceiveCompressedFrame(
+ current_frame_stream_id_,
+ current_frame_type_,
+ current_frame_length_);
+ }
+ visitor_->OnPushPromise(current_frame_stream_id_, promised_stream_id);
+ }
+ CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+ break;
+ default:
+ DCHECK(false);
+ }
+ }
+ return original_len - len;
+}
+
+// Does not buffer the control payload. Instead, either passes directly to the
+// visitor or decompresses and then passes directly to the visitor, via
+// IncrementallyDeliverControlFrameHeaderData() or
+// IncrementallyDecompressControlFrameHeaderData() respectively.
+size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data,
+ size_t data_len) {
+ DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
+
+ bool processed_successfully = true;
+ if (current_frame_type_ != SYN_STREAM &&
+ current_frame_type_ != SYN_REPLY &&
+ current_frame_type_ != HEADERS &&
+ current_frame_type_ != PUSH_PROMISE) {
+ LOG(DFATAL) << "Unhandled frame type in ProcessControlFrameHeaderBlock.";
+ }
+ size_t process_bytes = std::min(data_len, remaining_data_length_);
+ if (process_bytes > 0) {
+ if (enable_compression_) {
+ processed_successfully = IncrementallyDecompressControlFrameHeaderData(
+ current_frame_stream_id_, data, process_bytes);
+ } else {
+ processed_successfully = IncrementallyDeliverControlFrameHeaderData(
+ current_frame_stream_id_, data, process_bytes);
+ }
+
+ remaining_data_length_ -= process_bytes;
+ }
+
+ // Handle the case that there is no futher data in this frame.
+ if (remaining_data_length_ == 0 && processed_successfully) {
+ // The complete header block has been delivered. We send a zero-length
+ // OnControlFrameHeaderData() to indicate this.
+ visitor_->OnControlFrameHeaderData(current_frame_stream_id_, NULL, 0);
+
+ // If this is a FIN, tell the caller.
+ if (current_frame_flags_ & CONTROL_FLAG_FIN) {
+ visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true);
+ }
+
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+
+ // Handle error.
+ if (!processed_successfully) {
+ return data_len;
+ }
+
+ // Return amount processed.
+ return process_bytes;
+}
+
+size_t SpdyFramer::ProcessSettingsFramePayload(const char* data,
+ size_t data_len) {
+ DCHECK_EQ(SPDY_SETTINGS_FRAME_PAYLOAD, state_);
+ DCHECK_EQ(SETTINGS, current_frame_type_);
+ size_t unprocessed_bytes = std::min(data_len, remaining_data_length_);
+ size_t processed_bytes = 0;
+
+ // Loop over our incoming data.
+ while (unprocessed_bytes > 0) {
+ // Process up to one setting at a time.
+ size_t processing = std::min(
+ unprocessed_bytes,
+ static_cast<size_t>(8 - settings_scratch_.setting_buf_len));
+
+ // Check if we have a complete setting in our input.
+ if (processing == 8) {
+ // Parse the setting directly out of the input without buffering.
+ if (!ProcessSetting(data + processed_bytes)) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ return processed_bytes;
+ }
+ } else {
+ // Continue updating settings_scratch_.setting_buf.
+ memcpy(settings_scratch_.setting_buf + settings_scratch_.setting_buf_len,
+ data + processed_bytes,
+ processing);
+ settings_scratch_.setting_buf_len += processing;
+
+ // Check if we have a complete setting buffered.
+ if (settings_scratch_.setting_buf_len == 8) {
+ if (!ProcessSetting(settings_scratch_.setting_buf)) {
+ set_error(SPDY_INVALID_CONTROL_FRAME);
+ return processed_bytes;
+ }
+ // Reset settings_scratch_.setting_buf for our next setting.
+ settings_scratch_.setting_buf_len = 0;
+ }
+ }
+
+ // Iterate.
+ unprocessed_bytes -= processing;
+ processed_bytes += processing;
+ }
+
+ // Check if we're done handling this SETTINGS frame.
+ remaining_data_length_ -= processed_bytes;
+ if (remaining_data_length_ == 0) {
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+
+ return processed_bytes;
+}
+
+bool SpdyFramer::ProcessSetting(const char* data) {
+ // Extract fields.
+ // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id.
+ const uint32 id_and_flags_wire = *(reinterpret_cast<const uint32*>(data));
+ SettingsFlagsAndId id_and_flags =
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, id_and_flags_wire);
+ uint8 flags = id_and_flags.flags();
+ uint32 value = ntohl(*(reinterpret_cast<const uint32*>(data + 4)));
+
+ // Validate id.
+ switch (id_and_flags.id()) {
+ case SETTINGS_UPLOAD_BANDWIDTH:
+ case SETTINGS_DOWNLOAD_BANDWIDTH:
+ case SETTINGS_ROUND_TRIP_TIME:
+ case SETTINGS_MAX_CONCURRENT_STREAMS:
+ case SETTINGS_CURRENT_CWND:
+ case SETTINGS_DOWNLOAD_RETRANS_RATE:
+ case SETTINGS_INITIAL_WINDOW_SIZE:
+ // Valid values.
+ break;
+ default:
+ DLOG(WARNING) << "Unknown SETTINGS ID: " << id_and_flags.id();
+ return false;
+ }
+ SpdySettingsIds id = static_cast<SpdySettingsIds>(id_and_flags.id());
+
+ // Detect duplciates.
+ if (static_cast<uint32>(id) <= settings_scratch_.last_setting_id) {
+ DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id
+ << " in " << display_protocol_ << " SETTINGS frame "
+ << "(last settikng id was "
+ << settings_scratch_.last_setting_id << ").";
+ return false;
+ }
+ settings_scratch_.last_setting_id = id;
+
+ // Validate flags.
+ uint8 kFlagsMask = SETTINGS_FLAG_PLEASE_PERSIST | SETTINGS_FLAG_PERSISTED;
+ if ((flags & ~(kFlagsMask)) != 0) {
+ DLOG(WARNING) << "Unknown SETTINGS flags provided for id " << id << ": "
+ << flags;
+ return false;
+ }
+
+ // Validation succeeded. Pass on to visitor.
+ visitor_->OnSetting(id, flags, value);
+ return true;
+}
+
+size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+ size_t bytes_read =
+ UpdateCurrentFrameBuffer(&data, &len, remaining_data_length_);
+ remaining_data_length_ -= bytes_read;
+ if (remaining_data_length_ == 0) {
+ SpdyFrameReader reader(current_frame_buffer_.get(),
+ current_frame_buffer_length_);
+ reader.Seek(GetControlFrameHeaderSize()); // Skip frame header.
+
+ // Use frame-specific handlers.
+ switch (current_frame_type_) {
+ case RST_STREAM: {
+ bool successful_read = true;
+ if (spdy_version_ < 4) {
+ successful_read = reader.ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+ }
+ SpdyRstStreamStatus status = RST_STREAM_INVALID;
+ uint32 status_raw = status;
+ successful_read = reader.ReadUInt32(&status_raw);
+ DCHECK(successful_read);
+ if (status_raw > RST_STREAM_INVALID &&
+ status_raw < RST_STREAM_NUM_STATUS_CODES) {
+ status = static_cast<SpdyRstStreamStatus>(status_raw);
+ } else {
+ // TODO(hkhalil): Probably best to OnError here, depending on
+ // our interpretation of the spec. Keeping with existing liberal
+ // behavior for now.
+ }
+ DCHECK(reader.IsDoneReading());
+ visitor_->OnRstStream(current_frame_stream_id_, status);
+ }
+ break;
+ case PING: {
+ SpdyPingId id = 0;
+ bool successful_read = reader.ReadUInt32(&id);
+ DCHECK(successful_read);
+ DCHECK(reader.IsDoneReading());
+ visitor_->OnPing(id);
+ }
+ break;
+ case GOAWAY: {
+ bool successful_read = reader.ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+ SpdyGoAwayStatus status = GOAWAY_OK;
+ if (spdy_version_ >= 3) {
+ uint32 status_raw = GOAWAY_OK;
+ successful_read = reader.ReadUInt32(&status_raw);
+ DCHECK(successful_read);
+ if (status_raw >= GOAWAY_OK &&
+ status_raw < static_cast<uint32>(GOAWAY_NUM_STATUS_CODES)) {
+ status = static_cast<SpdyGoAwayStatus>(status_raw);
+ } else {
+ // TODO(hkhalil): Probably best to OnError here, depending on
+ // our interpretation of the spec. Keeping with existing liberal
+ // behavior for now.
+ }
+ }
+ DCHECK(reader.IsDoneReading());
+ visitor_->OnGoAway(current_frame_stream_id_, status);
+ }
+ break;
+ case WINDOW_UPDATE: {
+ uint32 delta_window_size = 0;
+ bool successful_read = true;
+ if (spdy_version_ < 4) {
+ successful_read = reader.ReadUInt31(&current_frame_stream_id_);
+ DCHECK(successful_read);
+ }
+ successful_read = reader.ReadUInt32(&delta_window_size);
+ DCHECK(successful_read);
+ DCHECK(reader.IsDoneReading());
+ visitor_->OnWindowUpdate(current_frame_stream_id_,
+ delta_window_size);
+ }
+ break;
+ case BLOCKED: {
+ DCHECK_LE(4, protocol_version());
+ DCHECK(reader.IsDoneReading());
+ visitor_->OnBlocked(current_frame_stream_id_);
+ }
+ break;
+ default:
+ // Unreachable.
+ LOG(FATAL) << "Unhandled control frame " << current_frame_type_;
+ }
+
+ CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD);
+ }
+ return original_len - len;
+}
+
+size_t SpdyFramer::ProcessCredentialFramePayload(const char* data, size_t len) {
+ if (len > 0) {
+ // Clamp to the actual remaining payload.
+ if (len > remaining_data_length_) {
+ len = remaining_data_length_;
+ }
+ bool processed_succesfully = visitor_->OnCredentialFrameData(data, len);
+ remaining_data_length_ -= len;
+ if (!processed_succesfully) {
+ set_error(SPDY_CREDENTIAL_FRAME_CORRUPT);
+ } else if (remaining_data_length_ == 0) {
+ visitor_->OnCredentialFrameData(NULL, 0);
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ }
+ return len;
+}
+
+size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
+ size_t original_len = len;
+
+ if (remaining_data_length_ > 0) {
+ size_t amount_to_forward = std::min(remaining_data_length_, len);
+ if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) {
+ // Only inform the visitor if there is data.
+ if (amount_to_forward) {
+ visitor_->OnStreamFrameData(
+ current_frame_stream_id_, data, amount_to_forward, false);
+ }
+ }
+ data += amount_to_forward;
+ len -= amount_to_forward;
+ remaining_data_length_ -= amount_to_forward;
+
+ // If the FIN flag is set, and there is no more data in this data
+ // frame, inform the visitor of EOF via a 0-length data frame.
+ if (!remaining_data_length_ && current_frame_flags_ & DATA_FLAG_FIN) {
+ visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true);
+ }
+ }
+
+ if (remaining_data_length_ == 0) {
+ CHANGE_STATE(SPDY_AUTO_RESET);
+ }
+ return original_len - len;
+}
+
+size_t SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
+ size_t header_length,
+ SpdyHeaderBlock* block) const {
+ SpdyFrameReader reader(header_data, header_length);
+
+ // Read number of headers.
+ uint32 num_headers;
+ if (spdy_version_ < 3) {
+ uint16 temp;
+ if (!reader.ReadUInt16(&temp)) {
+ DLOG(INFO) << "Unable to read number of headers.";
+ return 0;
+ }
+ num_headers = temp;
+ } else {
+ if (!reader.ReadUInt32(&num_headers)) {
+ DLOG(INFO) << "Unable to read number of headers.";
+ return 0;
+ }
+ }
+
+ // Read each header.
+ for (uint32 index = 0; index < num_headers; ++index) {
+ base::StringPiece temp;
+
+ // Read header name.
+ if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp)
+ : !reader.ReadStringPiece32(&temp)) {
+ DLOG(INFO) << "Unable to read header name (" << index + 1 << " of "
+ << num_headers << ").";
+ return 0;
+ }
+ std::string name = temp.as_string();
+
+ // Read header value.
+ if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp)
+ : !reader.ReadStringPiece32(&temp)) {
+ DLOG(INFO) << "Unable to read header value (" << index + 1 << " of "
+ << num_headers << ").";
+ return 0;
+ }
+ std::string value = temp.as_string();
+
+ // Ensure no duplicates.
+ if (block->find(name) != block->end()) {
+ DLOG(INFO) << "Duplicate header '" << name << "' (" << index + 1 << " of "
+ << num_headers << ").";
+ return 0;
+ }
+
+ // Store header.
+ (*block)[name] = value;
+ }
+ return reader.GetBytesConsumed();
+}
+
+/* static */
+bool SpdyFramer::ParseCredentialData(const char* data, size_t len,
+ SpdyCredential* credential) {
+ DCHECK(credential);
+
+ SpdyFrameReader parser(data, len);
+ base::StringPiece temp;
+ if (!parser.ReadUInt16(&credential->slot)) {
+ return false;
+ }
+
+ if (!parser.ReadStringPiece32(&temp)) {
+ return false;
+ }
+ credential->proof = temp.as_string();
+
+ while (!parser.IsDoneReading()) {
+ if (!parser.ReadStringPiece32(&temp)) {
+ return false;
+ }
+ credential->certs.push_back(temp.as_string());
+ }
+ return true;
+}
+
+SpdyFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id,
+ const char* data,
+ uint32 len, SpdyDataFlags flags) const {
+ DCHECK_EQ(0, flags & (!DATA_FLAG_FIN));
+
+ SpdyDataIR data_ir(stream_id, base::StringPiece(data, len));
+ data_ir.set_fin(flags & DATA_FLAG_FIN);
+ return SerializeData(data_ir);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeData(const SpdyDataIR& data) const {
+ const size_t kSize = GetDataFrameMinimumSize() + data.data().length();
+
+ SpdyDataFlags flags = DATA_FLAG_NONE;
+ if (data.fin()) {
+ flags = DATA_FLAG_FIN;
+ }
+
+ SpdyFrameBuilder builder(kSize);
+ builder.WriteDataFrameHeader(*this, data.stream_id(), flags);
+ builder.WriteBytes(data.data().data(), data.data().length());
+ DCHECK_EQ(kSize, builder.length());
+ return builder.take();
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeDataFrameHeader(
+ const SpdyDataIR& data) const {
+ const size_t kSize = GetDataFrameMinimumSize();
+
+ SpdyDataFlags flags = DATA_FLAG_NONE;
+ if (data.fin()) {
+ flags = DATA_FLAG_FIN;
+ }
+
+ SpdyFrameBuilder builder(kSize);
+ builder.WriteDataFrameHeader(*this, data.stream_id(), flags);
+ if (protocol_version() < 4) {
+ builder.OverwriteLength(*this, data.data().length());
+ } else {
+ builder.OverwriteLength(*this, data.data().length() + kSize);
+ }
+ DCHECK_EQ(kSize, builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateSynStream(
+ SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN & ~CONTROL_FLAG_UNIDIRECTIONAL);
+ DCHECK_EQ(enable_compression_, compressed);
+
+ SpdySynStreamIR syn_stream(stream_id);
+ syn_stream.set_associated_to_stream_id(associated_stream_id);
+ syn_stream.set_priority(priority);
+ syn_stream.set_slot(credential_slot);
+ syn_stream.set_fin((flags & CONTROL_FLAG_FIN) != 0);
+ syn_stream.set_unidirectional((flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+ // TODO(hkhalil): Avoid copy here.
+ *(syn_stream.GetMutableNameValueBlock()) = *headers;
+
+ return SerializeSynStream(syn_stream);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeSynStream(
+ const SpdySynStreamIR& syn_stream) {
+ uint8 flags = 0;
+ if (syn_stream.fin()) {
+ flags |= CONTROL_FLAG_FIN;
+ }
+ if (syn_stream.unidirectional()) {
+ flags |= CONTROL_FLAG_UNIDIRECTIONAL;
+ }
+
+ // The size of this frame, including variable-length name-value block.
+ const size_t size = GetSynStreamMinimumSize()
+ + GetSerializedLength(syn_stream.name_value_block());
+
+ SpdyFrameBuilder builder(size);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, SYN_STREAM, flags);
+ builder.WriteUInt32(syn_stream.stream_id());
+ } else {
+ builder.WriteFramePrefix(*this,
+ SYN_STREAM,
+ flags,
+ syn_stream.stream_id());
+ }
+ builder.WriteUInt32(syn_stream.associated_to_stream_id());
+ uint8 priority = syn_stream.priority();
+ if (priority > GetLowestPriority()) {
+ DLOG(DFATAL) << "Priority out-of-bounds.";
+ priority = GetLowestPriority();
+ }
+ builder.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5));
+ builder.WriteUInt8(syn_stream.slot());
+ DCHECK_EQ(GetSynStreamMinimumSize(), builder.length());
+ SerializeNameValueBlock(&builder, syn_stream);
+
+ if (debug_visitor_) {
+ const size_t payload_len = GetSerializedLength(
+ protocol_version(), &(syn_stream.name_value_block()));
+ debug_visitor_->OnSendCompressedFrame(syn_stream.stream_id(),
+ SYN_STREAM,
+ payload_len,
+ builder.length());
+ }
+
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateSynReply(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers) {
+ DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN);
+ DCHECK_EQ(enable_compression_, compressed);
+
+ SpdySynReplyIR syn_reply(stream_id);
+ syn_reply.set_fin(flags & CONTROL_FLAG_FIN);
+ // TODO(hkhalil): Avoid copy here.
+ *(syn_reply.GetMutableNameValueBlock()) = *headers;
+
+ return SerializeSynReply(syn_reply);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeSynReply(
+ const SpdySynReplyIR& syn_reply) {
+ uint8 flags = 0;
+ if (syn_reply.fin()) {
+ flags |= CONTROL_FLAG_FIN;
+ }
+
+ // The size of this frame, including variable-length name-value block.
+ size_t size = GetSynReplyMinimumSize()
+ + GetSerializedLength(syn_reply.name_value_block());
+
+ SpdyFrameBuilder builder(size);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, SYN_REPLY, flags);
+ builder.WriteUInt32(syn_reply.stream_id());
+ } else {
+ builder.WriteFramePrefix(*this,
+ SYN_REPLY,
+ flags,
+ syn_reply.stream_id());
+ }
+ if (protocol_version() < 3) {
+ builder.WriteUInt16(0); // Unused.
+ }
+ DCHECK_EQ(GetSynReplyMinimumSize(), builder.length());
+ SerializeNameValueBlock(&builder, syn_reply);
+
+ if (debug_visitor_) {
+ const size_t payload_len = GetSerializedLength(
+ protocol_version(), &(syn_reply.name_value_block()));
+ debug_visitor_->OnSendCompressedFrame(syn_reply.stream_id(),
+ SYN_REPLY,
+ payload_len,
+ builder.length());
+ }
+
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateRstStream(
+ SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const {
+ SpdyRstStreamIR rst_stream(stream_id, status);
+ return SerializeRstStream(rst_stream);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeRstStream(
+ const SpdyRstStreamIR& rst_stream) const {
+ SpdyFrameBuilder builder(GetRstStreamSize());
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, RST_STREAM, 0);
+ builder.WriteUInt32(rst_stream.stream_id());
+ } else {
+ builder.WriteFramePrefix(*this,
+ RST_STREAM,
+ 0,
+ rst_stream.stream_id());
+ }
+ builder.WriteUInt32(rst_stream.status());
+ DCHECK_EQ(GetRstStreamSize(), builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateSettings(
+ const SettingsMap& values) const {
+ SpdySettingsIR settings;
+ for (SettingsMap::const_iterator it = values.begin();
+ it != values.end();
+ ++it) {
+ settings.AddSetting(it->first,
+ (it->second.first & SETTINGS_FLAG_PLEASE_PERSIST) != 0,
+ (it->second.first & SETTINGS_FLAG_PERSISTED) != 0,
+ it->second.second);
+ }
+ return SerializeSettings(settings);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeSettings(
+ const SpdySettingsIR& settings) const {
+ uint8 flags = 0;
+ if (settings.clear_settings()) {
+ flags |= SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS;
+ }
+ const SpdySettingsIR::ValueMap* values = &(settings.values());
+
+ // Size, in bytes, of this SETTINGS frame.
+ const size_t size = GetSettingsMinimumSize() + (values->size() * 8);
+
+ SpdyFrameBuilder builder(size);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, SETTINGS, flags);
+ } else {
+ builder.WriteFramePrefix(*this, SETTINGS, flags, 0);
+ }
+ builder.WriteUInt32(values->size());
+ DCHECK_EQ(GetSettingsMinimumSize(), builder.length());
+ for (SpdySettingsIR::ValueMap::const_iterator it = values->begin();
+ it != values->end();
+ ++it) {
+ uint8 setting_flags = 0;
+ if (it->second.persist_value) {
+ setting_flags |= SETTINGS_FLAG_PLEASE_PERSIST;
+ }
+ if (it->second.persisted) {
+ setting_flags |= SETTINGS_FLAG_PERSISTED;
+ }
+ SettingsFlagsAndId flags_and_id(setting_flags, it->first);
+ uint32 id_and_flags_wire = flags_and_id.GetWireFormat(protocol_version());
+ builder.WriteBytes(&id_and_flags_wire, 4);
+ builder.WriteUInt32(it->second.value);
+ }
+ DCHECK_EQ(size, builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::SerializeBlocked(const SpdyBlockedIR& blocked) const {
+ DCHECK_LE(4, protocol_version());
+ SpdyFrameBuilder builder(GetBlockedSize());
+ builder.WriteFramePrefix(*this, BLOCKED, kNoFlags, blocked.stream_id());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const {
+ SpdyPingIR ping(unique_id);
+ return SerializePing(ping);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializePing(const SpdyPingIR& ping) const {
+ SpdyFrameBuilder builder(GetPingSize());
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, PING, kNoFlags);
+ } else {
+ builder.WriteFramePrefix(*this, PING, 0, 0);
+ }
+ builder.WriteUInt32(ping.id());
+ DCHECK_EQ(GetPingSize(), builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateGoAway(
+ SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const {
+ SpdyGoAwayIR goaway(last_accepted_stream_id, status);
+ return SerializeGoAway(goaway);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeGoAway(
+ const SpdyGoAwayIR& goaway) const {
+ SpdyFrameBuilder builder(GetGoAwaySize());
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, GOAWAY, kNoFlags);
+ } else {
+ builder.WriteFramePrefix(*this, GOAWAY, 0, 0);
+ }
+ builder.WriteUInt32(goaway.last_good_stream_id());
+ if (protocol_version() >= 3) {
+ builder.WriteUInt32(goaway.status());
+ }
+ DCHECK_EQ(GetGoAwaySize(), builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateHeaders(
+ SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* header_block) {
+ // Basically the same as CreateSynReply().
+ DCHECK_EQ(0, flags & (!CONTROL_FLAG_FIN));
+ DCHECK_EQ(enable_compression_, compressed);
+
+ SpdyHeadersIR headers(stream_id);
+ headers.set_fin(flags & CONTROL_FLAG_FIN);
+ // TODO(hkhalil): Avoid copy here.
+ *(headers.GetMutableNameValueBlock()) = *header_block;
+
+ return SerializeHeaders(headers);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeHeaders(
+ const SpdyHeadersIR& headers) {
+ uint8 flags = 0;
+ if (headers.fin()) {
+ flags |= CONTROL_FLAG_FIN;
+ }
+
+ // The size of this frame, including variable-length name-value block.
+ size_t size = GetHeadersMinimumSize()
+ + GetSerializedLength(headers.name_value_block());
+
+ SpdyFrameBuilder builder(size);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, HEADERS, flags);
+ builder.WriteUInt32(headers.stream_id());
+ } else {
+ builder.WriteFramePrefix(*this,
+ HEADERS,
+ flags,
+ headers.stream_id());
+ }
+ if (protocol_version() < 3) {
+ builder.WriteUInt16(0); // Unused.
+ }
+ DCHECK_EQ(GetHeadersMinimumSize(), builder.length());
+
+ SerializeNameValueBlock(&builder, headers);
+
+ if (debug_visitor_) {
+ const size_t payload_len = GetSerializedLength(
+ protocol_version(), &(headers.name_value_block()));
+ debug_visitor_->OnSendCompressedFrame(headers.stream_id(),
+ HEADERS,
+ payload_len,
+ builder.length());
+ }
+
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const {
+ SpdyWindowUpdateIR window_update(stream_id, delta_window_size);
+ return SerializeWindowUpdate(window_update);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeWindowUpdate(
+ const SpdyWindowUpdateIR& window_update) const {
+ SpdyFrameBuilder builder(GetWindowUpdateSize());
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, WINDOW_UPDATE, kNoFlags);
+ builder.WriteUInt32(window_update.stream_id());
+ } else {
+ builder.WriteFramePrefix(*this,
+ WINDOW_UPDATE,
+ kNoFlags,
+ window_update.stream_id());
+ }
+ builder.WriteUInt32(window_update.delta());
+ DCHECK_EQ(GetWindowUpdateSize(), builder.length());
+ return builder.take();
+}
+
+// TODO(hkhalil): Gut with SpdyCredential removal.
+SpdyFrame* SpdyFramer::CreateCredentialFrame(
+ const SpdyCredential& credential) const {
+ SpdyCredentialIR credential_ir(credential.slot);
+ credential_ir.set_proof(credential.proof);
+ for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
+ cert != credential.certs.end();
+ ++cert) {
+ credential_ir.AddCertificate(*cert);
+ }
+ return SerializeCredential(credential_ir);
+}
+
+SpdySerializedFrame* SpdyFramer::SerializeCredential(
+ const SpdyCredentialIR& credential) const {
+ size_t size = GetCredentialMinimumSize();
+ size += 4 + credential.proof().length(); // Room for proof.
+ for (SpdyCredentialIR::CertificateList::const_iterator it =
+ credential.certificates()->begin();
+ it != credential.certificates()->end();
+ ++it) {
+ size += 4 + it->length(); // Room for certificate.
+ }
+
+ SpdyFrameBuilder builder(size);
+ if (spdy_version_ < 4) {
+ builder.WriteControlFrameHeader(*this, CREDENTIAL, kNoFlags);
+ } else {
+ builder.WriteFramePrefix(*this, CREDENTIAL, kNoFlags, 0);
+ }
+ builder.WriteUInt16(credential.slot());
+ DCHECK_EQ(GetCredentialMinimumSize(), builder.length());
+ builder.WriteStringPiece32(credential.proof());
+ for (SpdyCredentialIR::CertificateList::const_iterator it =
+ credential.certificates()->begin();
+ it != credential.certificates()->end();
+ ++it) {
+ builder.WriteStringPiece32(*it);
+ }
+ DCHECK_EQ(size, builder.length());
+ return builder.take();
+}
+
+SpdyFrame* SpdyFramer::CreatePushPromise(
+ SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ const SpdyHeaderBlock* header_block) {
+ SpdyPushPromiseIR push_promise(stream_id, promised_stream_id);
+ // TODO(hkhalil): Avoid copy here.
+ *(push_promise.GetMutableNameValueBlock()) = *header_block;
+
+ return SerializePushPromise(push_promise);
+}
+
+SpdyFrame* SpdyFramer::SerializePushPromise(
+ const SpdyPushPromiseIR& push_promise) {
+ DCHECK_LE(4, protocol_version());
+ // The size of this frame, including variable-length name-value block.
+ size_t size = GetPushPromiseMinimumSize()
+ + GetSerializedLength(push_promise.name_value_block());
+
+ SpdyFrameBuilder builder(size);
+ builder.WriteFramePrefix(*this, PUSH_PROMISE, kNoFlags,
+ push_promise.stream_id());
+ builder.WriteUInt32(push_promise.promised_stream_id());
+ DCHECK_EQ(GetPushPromiseMinimumSize(), builder.length());
+
+ SerializeNameValueBlock(&builder, push_promise);
+
+ if (debug_visitor_) {
+ const size_t payload_len = GetSerializedLength(
+ protocol_version(), &(push_promise.name_value_block()));
+ debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(),
+ PUSH_PROMISE, payload_len, builder.length());
+ }
+
+ return builder.take();
+}
+
+namespace {
+
+class FrameSerializationVisitor : public SpdyFrameVisitor {
+ public:
+ explicit FrameSerializationVisitor(SpdyFramer* framer) : framer_(framer) {}
+ virtual ~FrameSerializationVisitor() {}
+
+ SpdySerializedFrame* ReleaseSerializedFrame() { return frame_.release(); }
+
+ virtual void VisitData(const SpdyDataIR& data) OVERRIDE {
+ frame_.reset(framer_->SerializeData(data));
+ }
+ virtual void VisitSynStream(const SpdySynStreamIR& syn_stream) OVERRIDE {
+ frame_.reset(framer_->SerializeSynStream(syn_stream));
+ }
+ virtual void VisitSynReply(const SpdySynReplyIR& syn_reply) OVERRIDE {
+ frame_.reset(framer_->SerializeSynReply(syn_reply));
+ }
+ virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) OVERRIDE {
+ frame_.reset(framer_->SerializeRstStream(rst_stream));
+ }
+ virtual void VisitSettings(const SpdySettingsIR& settings) OVERRIDE {
+ frame_.reset(framer_->SerializeSettings(settings));
+ }
+ virtual void VisitPing(const SpdyPingIR& ping) OVERRIDE {
+ frame_.reset(framer_->SerializePing(ping));
+ }
+ virtual void VisitGoAway(const SpdyGoAwayIR& goaway) OVERRIDE {
+ frame_.reset(framer_->SerializeGoAway(goaway));
+ }
+ virtual void VisitHeaders(const SpdyHeadersIR& headers) OVERRIDE {
+ frame_.reset(framer_->SerializeHeaders(headers));
+ }
+ virtual void VisitWindowUpdate(
+ const SpdyWindowUpdateIR& window_update) OVERRIDE {
+ frame_.reset(framer_->SerializeWindowUpdate(window_update));
+ }
+ virtual void VisitCredential(const SpdyCredentialIR& credential) OVERRIDE {
+ frame_.reset(framer_->SerializeCredential(credential));
+ }
+ virtual void VisitBlocked(const SpdyBlockedIR& blocked) OVERRIDE {
+ frame_.reset(framer_->SerializeBlocked(blocked));
+ }
+ virtual void VisitPushPromise(
+ const SpdyPushPromiseIR& push_promise) OVERRIDE {
+ frame_.reset(framer_->SerializePushPromise(push_promise));
+ }
+
+ private:
+ SpdyFramer* framer_;
+ scoped_ptr<SpdySerializedFrame> frame_;
+};
+
+} // namespace
+
+SpdySerializedFrame* SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) {
+ FrameSerializationVisitor visitor(this);
+ frame.Visit(&visitor);
+ return visitor.ReleaseSerializedFrame();
+}
+
+size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock& headers) {
+ const size_t uncompressed_length =
+ GetSerializedLength(protocol_version(), &headers);
+ if (!enable_compression_) {
+ return uncompressed_length;
+ }
+ z_stream* compressor = GetHeaderCompressor();
+ // Since we'll be performing lots of flushes when compressing the data,
+ // zlib's lower bounds may be insufficient.
+ return 2 * deflateBound(compressor, uncompressed_length);
+}
+
+// The following compression setting are based on Brian Olson's analysis. See
+// https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792
+// for more details.
+#if defined(USE_SYSTEM_ZLIB)
+// System zlib is not expected to have workaround for http://crbug.com/139744,
+// so disable compression in that case.
+// TODO(phajdan.jr): Remove the special case when it's no longer necessary.
+static const int kCompressorLevel = 0;
+#else // !defined(USE_SYSTEM_ZLIB)
+static const int kCompressorLevel = 9;
+#endif // !defined(USE_SYSTEM_ZLIB)
+static const int kCompressorWindowSizeInBits = 11;
+static const int kCompressorMemLevel = 1;
+
+z_stream* SpdyFramer::GetHeaderCompressor() {
+ if (header_compressor_.get())
+ return header_compressor_.get(); // Already initialized.
+
+ header_compressor_.reset(new z_stream);
+ memset(header_compressor_.get(), 0, sizeof(z_stream));
+
+ int success = deflateInit2(header_compressor_.get(),
+ kCompressorLevel,
+ Z_DEFLATED,
+ kCompressorWindowSizeInBits,
+ kCompressorMemLevel,
+ Z_DEFAULT_STRATEGY);
+ if (success == Z_OK) {
+ const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary
+ : kV3Dictionary;
+ const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize
+ : kV3DictionarySize;
+ success = deflateSetDictionary(header_compressor_.get(),
+ reinterpret_cast<const Bytef*>(dictionary),
+ dictionary_size);
+ }
+ if (success != Z_OK) {
+ LOG(WARNING) << "deflateSetDictionary failure: " << success;
+ header_compressor_.reset(NULL);
+ return NULL;
+ }
+ return header_compressor_.get();
+}
+
+z_stream* SpdyFramer::GetHeaderDecompressor() {
+ if (header_decompressor_.get())
+ return header_decompressor_.get(); // Already initialized.
+
+ header_decompressor_.reset(new z_stream);
+ memset(header_decompressor_.get(), 0, sizeof(z_stream));
+
+ int success = inflateInit(header_decompressor_.get());
+ if (success != Z_OK) {
+ LOG(WARNING) << "inflateInit failure: " << success;
+ header_decompressor_.reset(NULL);
+ return NULL;
+ }
+ return header_decompressor_.get();
+}
+
+// Incrementally decompress the control frame's header block, feeding the
+// result to the visitor in chunks. Continue this until the visitor
+// indicates that it cannot process any more data, or (more commonly) we
+// run out of data to deliver.
+bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData(
+ SpdyStreamId stream_id,
+ const char* data,
+ size_t len) {
+ // Get a decompressor or set error.
+ z_stream* decomp = GetHeaderDecompressor();
+ if (decomp == NULL) {
+ LOG(DFATAL) << "Couldn't get decompressor for handling compressed headers.";
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ return false;
+ }
+
+ bool processed_successfully = true;
+ char buffer[kHeaderDataChunkMaxSize];
+
+ decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data));
+ decomp->avail_in = len;
+ // If we get a SYN_STREAM/SYN_REPLY/HEADERS frame with stream ID zero, we
+ // signal an error back in ProcessControlFrameBeforeHeaderBlock. So if we've
+ // reached this method successfully, stream_id should be nonzero.
+ DCHECK_LT(0u, stream_id);
+ while (decomp->avail_in > 0 && processed_successfully) {
+ decomp->next_out = reinterpret_cast<Bytef*>(buffer);
+ decomp->avail_out = arraysize(buffer);
+
+ int rv = inflate(decomp, Z_SYNC_FLUSH);
+ if (rv == Z_NEED_DICT) {
+ const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary
+ : kV3Dictionary;
+ const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize
+ : kV3DictionarySize;
+ const DictionaryIds& ids = g_dictionary_ids.Get();
+ const uLong dictionary_id = (spdy_version_ < 3) ? ids.v2_dictionary_id
+ : ids.v3_dictionary_id;
+ // Need to try again with the right dictionary.
+ if (decomp->adler == dictionary_id) {
+ rv = inflateSetDictionary(decomp,
+ reinterpret_cast<const Bytef*>(dictionary),
+ dictionary_size);
+ if (rv == Z_OK)
+ rv = inflate(decomp, Z_SYNC_FLUSH);
+ }
+ }
+
+ // Inflate will generate a Z_BUF_ERROR if it runs out of input
+ // without producing any output. The input is consumed and
+ // buffered internally by zlib so we can detect this condition by
+ // checking if avail_in is 0 after the call to inflate.
+ bool input_exhausted = ((rv == Z_BUF_ERROR) && (decomp->avail_in == 0));
+ if ((rv == Z_OK) || input_exhausted) {
+ size_t decompressed_len = arraysize(buffer) - decomp->avail_out;
+ if (decompressed_len > 0) {
+ processed_successfully = visitor_->OnControlFrameHeaderData(
+ stream_id, buffer, decompressed_len);
+ }
+ if (!processed_successfully) {
+ // Assume that the problem was the header block was too large for the
+ // visitor.
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ } else {
+ DLOG(WARNING) << "inflate failure: " << rv << " " << len;
+ set_error(SPDY_DECOMPRESS_FAILURE);
+ processed_successfully = false;
+ }
+ }
+ return processed_successfully;
+}
+
+bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData(
+ SpdyStreamId stream_id, const char* data, size_t len) {
+ bool read_successfully = true;
+ while (read_successfully && len > 0) {
+ size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize);
+ read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data,
+ bytes_to_deliver);
+ data += bytes_to_deliver;
+ len -= bytes_to_deliver;
+ if (!read_successfully) {
+ // Assume that the problem was the header block was too large for the
+ // visitor.
+ set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+ }
+ }
+ return read_successfully;
+}
+
+void SpdyFramer::SerializeNameValueBlockWithoutCompression(
+ SpdyFrameBuilder* builder,
+ const SpdyNameValueBlock& name_value_block) const {
+ // Serialize number of headers.
+ if (protocol_version() < 3) {
+ builder->WriteUInt16(name_value_block.size());
+ } else {
+ builder->WriteUInt32(name_value_block.size());
+ }
+
+ // Serialize each header.
+ for (SpdyHeaderBlock::const_iterator it = name_value_block.begin();
+ it != name_value_block.end();
+ ++it) {
+ if (protocol_version() < 3) {
+ builder->WriteString(it->first);
+ builder->WriteString(it->second);
+ } else {
+ builder->WriteStringPiece32(it->first);
+ builder->WriteStringPiece32(it->second);
+ }
+ }
+}
+
+void SpdyFramer::SerializeNameValueBlock(
+ SpdyFrameBuilder* builder,
+ const SpdyFrameWithNameValueBlockIR& frame) {
+ if (!enable_compression_) {
+ return SerializeNameValueBlockWithoutCompression(builder,
+ frame.name_value_block());
+ }
+
+ // First build an uncompressed version to be fed into the compressor.
+ const size_t uncompressed_len = GetSerializedLength(
+ protocol_version(), &(frame.name_value_block()));
+ SpdyFrameBuilder uncompressed_builder(uncompressed_len);
+ SerializeNameValueBlockWithoutCompression(&uncompressed_builder,
+ frame.name_value_block());
+ scoped_ptr<SpdyFrame> uncompressed_payload(uncompressed_builder.take());
+
+ z_stream* compressor = GetHeaderCompressor();
+ if (!compressor) {
+ LOG(DFATAL) << "Could not obtain compressor.";
+ return;
+ }
+
+ base::StatsCounter compressed_frames("spdy.CompressedFrames");
+ base::StatsCounter pre_compress_bytes("spdy.PreCompressSize");
+ base::StatsCounter post_compress_bytes("spdy.PostCompressSize");
+
+ // Create an output frame.
+ // Since we'll be performing lots of flushes when compressing the data,
+ // zlib's lower bounds may be insufficient.
+ //
+ // TODO(akalin): Avoid the duplicate calculation with
+ // GetSerializedLength(const SpdyHeaderBlock&).
+ const int compressed_max_size =
+ 2 * deflateBound(compressor, uncompressed_len);
+
+ // TODO(phajdan.jr): Clean up after we no longer need
+ // to workaround http://crbug.com/139744.
+#if defined(USE_SYSTEM_ZLIB)
+ compressor->next_in = reinterpret_cast<Bytef*>(uncompressed_payload->data());
+ compressor->avail_in = uncompressed_len;
+#endif // defined(USE_SYSTEM_ZLIB)
+ compressor->next_out = reinterpret_cast<Bytef*>(
+ builder->GetWritableBuffer(compressed_max_size));
+ compressor->avail_out = compressed_max_size;
+
+ // TODO(phajdan.jr): Clean up after we no longer need
+ // to workaround http://crbug.com/139744.
+#if defined(USE_SYSTEM_ZLIB)
+ int rv = deflate(compressor, Z_SYNC_FLUSH);
+ if (rv != Z_OK) { // How can we know that it compressed everything?
+ // This shouldn't happen, right?
+ LOG(WARNING) << "deflate failure: " << rv;
+ // TODO(akalin): Upstream this return.
+ return;
+ }
+#else
+ WriteHeaderBlockToZ(&frame.name_value_block(), compressor);
+#endif // defined(USE_SYSTEM_ZLIB)
+
+ int compressed_size = compressed_max_size - compressor->avail_out;
+ builder->Seek(compressed_size);
+ builder->RewriteLength(*this);
+
+ pre_compress_bytes.Add(uncompressed_len);
+ post_compress_bytes.Add(compressed_size);
+
+ compressed_frames.Increment();
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_framer.h b/chromium/net/spdy/spdy_framer.h
new file mode 100644
index 00000000000..fa004696a4c
--- /dev/null
+++ b/chromium/net/spdy/spdy_framer.h
@@ -0,0 +1,716 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_FRAMER_H_
+#define NET_SPDY_SPDY_FRAMER_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+
+typedef struct z_stream_s z_stream; // Forward declaration for zlib.
+
+namespace net {
+
+class HttpProxyClientSocketPoolTest;
+class HttpNetworkLayer;
+class HttpNetworkTransactionTest;
+class SpdyHttpStreamTest;
+class SpdyNetworkTransactionTest;
+class SpdyProxyClientSocketTest;
+class SpdySessionTest;
+class SpdyStreamTest;
+class SpdyWebSocketStreamTest;
+class WebSocketJobTest;
+
+class SpdyFramer;
+class SpdyFrameBuilder;
+class SpdyFramerTest;
+
+namespace test {
+
+class TestSpdyVisitor;
+
+} // namespace test
+
+// A datastructure for holding a set of headers from a HEADERS, PUSH_PROMISE,
+// SYN_STREAM, or SYN_REPLY frame.
+typedef std::map<std::string, std::string> SpdyHeaderBlock;
+
+// A datastructure for holding the ID and flag fields for SETTINGS.
+// Conveniently handles converstion to/from wire format.
+class NET_EXPORT_PRIVATE SettingsFlagsAndId {
+ public:
+ static SettingsFlagsAndId FromWireFormat(int version, uint32 wire);
+
+ SettingsFlagsAndId() : flags_(0), id_(0) {}
+
+ // TODO(hkhalil): restrict to enums instead of free-form ints.
+ SettingsFlagsAndId(uint8 flags, uint32 id);
+
+ uint32 GetWireFormat(int version) const;
+
+ uint32 id() const { return id_; }
+ uint8 flags() const { return flags_; }
+
+ private:
+ static void ConvertFlagsAndIdForSpdy2(uint32* val);
+
+ uint8 flags_;
+ uint32 id_;
+};
+
+// SettingsMap has unique (flags, value) pair for given SpdySettingsIds ID.
+typedef std::pair<SpdySettingsFlags, uint32> SettingsFlagsAndValue;
+typedef std::map<SpdySettingsIds, SettingsFlagsAndValue> SettingsMap;
+
+// A datastrcture for holding the contents of a CREDENTIAL frame.
+// TODO(hkhalil): Remove, use SpdyCredentialIR instead.
+struct NET_EXPORT_PRIVATE SpdyCredential {
+ SpdyCredential();
+ ~SpdyCredential();
+
+ uint16 slot;
+ std::vector<std::string> certs;
+ std::string proof;
+};
+
+// Scratch space necessary for processing SETTINGS frames.
+struct NET_EXPORT_PRIVATE SpdySettingsScratch {
+ SpdySettingsScratch() { Reset(); }
+
+ void Reset() {
+ setting_buf_len = 0;
+ last_setting_id = 0;
+ }
+
+ // Buffer contains up to one complete key/value pair.
+ char setting_buf[8];
+
+ // The amount of the buffer that is filled with valid data.
+ size_t setting_buf_len;
+
+ // The ID of the last setting that was processed in the current SETTINGS
+ // frame. Used for detecting out-of-order or duplicate keys within a settings
+ // frame. Set to 0 before first key/value pair is processed.
+ uint32 last_setting_id;
+};
+
+// SpdyFramerVisitorInterface is a set of callbacks for the SpdyFramer.
+// Implement this interface to receive event callbacks as frames are
+// decoded from the framer.
+//
+// Control frames that contain SPDY header blocks (SYN_STREAM, SYN_REPLY,
+// HEADER, and PUSH_PROMISE) are processed in fashion that allows the
+// decompressed header block to be delivered in chunks to the visitor.
+// The following steps are followed:
+// 1. OnSynStream, OnSynReply, OnHeaders, or OnPushPromise is called.
+// 2. Repeated: OnControlFrameHeaderData is called with chunks of the
+// decompressed header block. In each call the len parameter is greater
+// than zero.
+// 3. OnControlFrameHeaderData is called with len set to zero, indicating
+// that the full header block has been delivered for the control frame.
+// During step 2 the visitor may return false, indicating that the chunk of
+// header data could not be handled by the visitor (typically this indicates
+// resource exhaustion). If this occurs the framer will discontinue
+// delivering chunks to the visitor, set a SPDY_CONTROL_PAYLOAD_TOO_LARGE
+// error, and clean up appropriately. Note that this will cause the header
+// decompressor to lose synchronization with the sender's header compressor,
+// making the SPDY session unusable for future work. The visitor's OnError
+// function should deal with this condition by closing the SPDY connection.
+class NET_EXPORT_PRIVATE SpdyFramerVisitorInterface {
+ public:
+ virtual ~SpdyFramerVisitorInterface() {}
+
+ // Called if an error is detected in the SpdyFrame protocol.
+ virtual void OnError(SpdyFramer* framer) = 0;
+
+ // Called when a data frame header is received. The frame's data
+ // payload will be provided via subsequent calls to
+ // OnStreamFrameData().
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) = 0;
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ // When the other side has finished sending data on this stream,
+ // this method will be called with a zero-length buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) = 0;
+
+ // Called when a chunk of header data is available. This is called
+ // after OnSynStream, OnSynReply, OnHeaders(), or OnPushPromise.
+ // |stream_id| The stream receiving the header data.
+ // |header_data| A buffer containing the header data chunk received.
+ // |len| The length of the header data buffer. A length of zero indicates
+ // that the header data block has been completely sent.
+ // When this function returns true the visitor indicates that it accepted
+ // all of the data. Returning false indicates that that an unrecoverable
+ // error has occurred, such as bad header data or resource exhaustion.
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) = 0;
+
+ // Called when a SYN_STREAM frame is received.
+ // Note that header block data is not included. See
+ // OnControlFrameHeaderData().
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) = 0;
+
+ // Called when a SYN_REPLY frame is received.
+ // Note that header block data is not included. See
+ // OnControlFrameHeaderData().
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) = 0;
+
+ // Called when a RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) = 0;
+
+ // Called when a SETTINGS frame is received.
+ // |clear_persisted| True if the respective flag is set on the SETTINGS frame.
+ virtual void OnSettings(bool clear_persisted) {}
+
+ // Called when a complete setting within a SETTINGS frame has been parsed and
+ // validated.
+ virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0;
+
+ // Called when a PING frame has been parsed.
+ virtual void OnPing(uint32 unique_id) = 0;
+
+ // Called when a GOAWAY frame has been parsed.
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) = 0;
+
+ // Called when a HEADERS frame is received.
+ // Note that header block data is not included. See
+ // OnControlFrameHeaderData().
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) = 0;
+
+ // Called when a WINDOW_UPDATE frame has been parsed.
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) = 0;
+
+ // Called when a chunk of payload data for a credential frame is available.
+ // |header_data| A buffer containing the header data chunk received.
+ // |len| The length of the header data buffer. A length of zero indicates
+ // that the header data block has been completely sent.
+ // When this function returns true the visitor indicates that it accepted
+ // all of the data. Returning false indicates that that an unrecoverable
+ // error has occurred, such as bad header data or resource exhaustion.
+ virtual bool OnCredentialFrameData(const char* credential_data,
+ size_t len) = 0;
+
+ // Called when a BLOCKED frame has been parsed.
+ virtual void OnBlocked(SpdyStreamId stream_id) {}
+
+ // Called when a PUSH_PROMISE frame is received.
+ // Note that header block data is not included. See
+ // OnControlFrameHeaderData().
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) = 0;
+};
+
+// Optionally, and in addition to SpdyFramerVisitorInterface, a class supporting
+// SpdyFramerDebugVisitorInterface may be used in conjunction with SpdyFramer in
+// order to extract debug/internal information about the SpdyFramer as it
+// operates.
+//
+// Most SPDY implementations need not bother with this interface at all.
+class NET_EXPORT_PRIVATE SpdyFramerDebugVisitorInterface {
+ public:
+ virtual ~SpdyFramerDebugVisitorInterface() {}
+
+ // Called after compressing a frame with a payload of
+ // a list of name-value pairs.
+ // |payload_len| is the uncompressed payload size.
+ // |frame_len| is the compressed frame size.
+ virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) {}
+
+ // Called when a frame containing a compressed payload of
+ // name-value pairs is received.
+ // |frame_len| is the compressed frame size.
+ virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) {}
+};
+
+class NET_EXPORT_PRIVATE SpdyFramer {
+ public:
+ // SPDY states.
+ // TODO(mbelshe): Can we move these into the implementation
+ // and avoid exposing through the header. (Needed for test)
+ enum SpdyState {
+ SPDY_ERROR,
+ SPDY_RESET,
+ SPDY_AUTO_RESET,
+ SPDY_READING_COMMON_HEADER,
+ SPDY_CONTROL_FRAME_PAYLOAD,
+ SPDY_IGNORE_REMAINING_PAYLOAD,
+ SPDY_FORWARD_STREAM_FRAME,
+ SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK,
+ SPDY_CONTROL_FRAME_HEADER_BLOCK,
+ SPDY_CREDENTIAL_FRAME_PAYLOAD,
+ SPDY_SETTINGS_FRAME_PAYLOAD,
+ };
+
+ // SPDY error codes.
+ enum SpdyError {
+ SPDY_NO_ERROR,
+ SPDY_INVALID_CONTROL_FRAME, // Control frame is mal-formatted.
+ SPDY_CONTROL_PAYLOAD_TOO_LARGE, // Control frame payload was too large.
+ SPDY_ZLIB_INIT_FAILURE, // The Zlib library could not initialize.
+ SPDY_UNSUPPORTED_VERSION, // Control frame has unsupported version.
+ SPDY_DECOMPRESS_FAILURE, // There was an error decompressing.
+ SPDY_COMPRESS_FAILURE, // There was an error compressing.
+ SPDY_CREDENTIAL_FRAME_CORRUPT, // CREDENTIAL frame could not be parsed.
+ SPDY_INVALID_DATA_FRAME_FLAGS, // Data frame has invalid flags.
+ SPDY_INVALID_CONTROL_FRAME_FLAGS, // Control frame has invalid flags.
+
+ LAST_ERROR, // Must be the last entry in the enum.
+ };
+
+ // Constant for invalid (or unknown) stream IDs.
+ static const SpdyStreamId kInvalidStream;
+
+ // The maximum size of header data chunks delivered to the framer visitor
+ // through OnControlFrameHeaderData. (It is exposed here for unit test
+ // purposes.)
+ static const size_t kHeaderDataChunkMaxSize;
+
+ // Serializes a SpdyHeaderBlock.
+ static void WriteHeaderBlock(SpdyFrameBuilder* frame,
+ const int spdy_version,
+ const SpdyHeaderBlock* headers);
+
+ // Retrieve serialized length of SpdyHeaderBlock.
+ // TODO(hkhalil): Remove, or move to quic code.
+ static size_t GetSerializedLength(const int spdy_version,
+ const SpdyHeaderBlock* headers);
+
+ // Create a new Framer, provided a SPDY version.
+ explicit SpdyFramer(SpdyMajorVersion version);
+ virtual ~SpdyFramer();
+
+ // Set callbacks to be called from the framer. A visitor must be set, or
+ // else the framer will likely crash. It is acceptable for the visitor
+ // to do nothing. If this is called multiple times, only the last visitor
+ // will be used.
+ void set_visitor(SpdyFramerVisitorInterface* visitor) {
+ visitor_ = visitor;
+ }
+
+ // Set debug callbacks to be called from the framer. The debug visitor is
+ // completely optional and need not be set in order for normal operation.
+ // If this is called multiple times, only the last visitor will be used.
+ void set_debug_visitor(SpdyFramerDebugVisitorInterface* debug_visitor) {
+ debug_visitor_ = debug_visitor;
+ }
+
+ // Pass data into the framer for parsing.
+ // Returns the number of bytes consumed. It is safe to pass more bytes in
+ // than may be consumed.
+ size_t ProcessInput(const char* data, size_t len);
+
+ // Resets the framer state after a frame has been successfully decoded.
+ // TODO(mbelshe): can we make this private?
+ void Reset();
+
+ // Check the state of the framer.
+ SpdyError error_code() const { return error_code_; }
+ SpdyState state() const { return state_; }
+ bool HasError() const { return state_ == SPDY_ERROR; }
+
+ // Given a buffer containing a decompressed header block in SPDY
+ // serialized format, parse out a SpdyHeaderBlock, putting the results
+ // in the given header block.
+ // Returns number of bytes consumed if successfully parsed, 0 otherwise.
+ size_t ParseHeaderBlockInBuffer(const char* header_data,
+ size_t header_length,
+ SpdyHeaderBlock* block) const;
+
+ // Create a data frame.
+ // |stream_id| is the stream for this frame
+ // |data| is the data to be included in the frame.
+ // |len| is the length of the data
+ // |flags| is the flags to use with the data.
+ // To mark this frame as the last data frame, enable DATA_FLAG_FIN.
+ SpdyFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data,
+ uint32 len, SpdyDataFlags flags) const;
+ SpdySerializedFrame* SerializeData(const SpdyDataIR& data) const;
+ // Serializes just the data frame header, excluding actual data payload.
+ SpdySerializedFrame* SerializeDataFrameHeader(const SpdyDataIR& data) const;
+
+ // Creates and serializes a SYN_STREAM frame.
+ // |stream_id| is the id for this stream.
+ // |associated_stream_id| is the associated stream id for this stream.
+ // |priority| is the priority (GetHighestPriority()-GetLowestPriority) for
+ // this stream.
+ // |credential_slot| is the CREDENTIAL slot to be used for this request.
+ // |flags| is the flags to use with the data.
+ // To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+ // |compressed| specifies whether the frame should be compressed.
+ // |headers| is the header block to include in the frame.
+ SpdyFrame* CreateSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdySerializedFrame* SerializeSynStream(const SpdySynStreamIR& syn_stream);
+
+ // Create a SYN_REPLY SpdyFrame.
+ // |stream_id| is the stream for this frame.
+ // |flags| is the flags to use with the data.
+ // To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+ // |compressed| specifies whether the frame should be compressed.
+ // |headers| is the header block to include in the frame.
+ SpdyFrame* CreateSynReply(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdySerializedFrame* SerializeSynReply(const SpdySynReplyIR& syn_reply);
+
+ SpdyFrame* CreateRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const;
+ SpdySerializedFrame* SerializeRstStream(
+ const SpdyRstStreamIR& rst_stream) const;
+
+ // Creates and serializes a SETTINGS frame. The SETTINGS frame is
+ // used to communicate name/value pairs relevant to the communication channel.
+ SpdyFrame* CreateSettings(const SettingsMap& values) const;
+ SpdySerializedFrame* SerializeSettings(const SpdySettingsIR& settings) const;
+
+ // Creates and serializes a PING frame. The unique_id is used to
+ // identify the ping request/response.
+ SpdyFrame* CreatePingFrame(uint32 unique_id) const;
+ SpdySerializedFrame* SerializePing(const SpdyPingIR& ping) const;
+
+ // Creates and serializes a GOAWAY frame. The GOAWAY frame is used
+ // prior to the shutting down of the TCP connection, and includes the
+ // stream_id of the last stream the sender of the frame is willing to process
+ // to completion.
+ SpdyFrame* CreateGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) const;
+ SpdySerializedFrame* SerializeGoAway(const SpdyGoAwayIR& goaway) const;
+
+ // Creates and serializes a HEADERS frame. The HEADERS frame is used
+ // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The
+ // arguments are the same as for CreateSynReply.
+ SpdyFrame* CreateHeaders(SpdyStreamId stream_id,
+ SpdyControlFlags flags,
+ bool compressed,
+ const SpdyHeaderBlock* headers);
+ SpdySerializedFrame* SerializeHeaders(const SpdyHeadersIR& headers);
+
+ // Creates and serializes a WINDOW_UPDATE frame. The WINDOW_UPDATE
+ // frame is used to implement per stream flow control in SPDY.
+ SpdyFrame* CreateWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const;
+ SpdySerializedFrame* SerializeWindowUpdate(
+ const SpdyWindowUpdateIR& window_update) const;
+
+ // Creates and serializes a CREDENTIAL frame. The CREDENTIAL
+ // frame is used to send a client certificate to the server when
+ // request more than one origin are sent over the same SPDY session.
+ SpdyFrame* CreateCredentialFrame(const SpdyCredential& credential) const;
+ SpdySerializedFrame* SerializeCredential(
+ const SpdyCredentialIR& credential) const;
+
+ // Serializes a BLOCKED frame. The BLOCKED frame is used to indicate to the
+ // remote endpoint that this endpoint believes itself to be flow-control
+ // blocked but otherwise ready to send data. The BLOCKED frame is purely
+ // advisory and optional.
+ SpdySerializedFrame* SerializeBlocked(const SpdyBlockedIR& blocked) const;
+
+ // Creates and serializes a PUSH_PROMISE frame. The PUSH_PROMISE frame is used
+ // to inform the client that it will be receiving an additional stream
+ // in response to the original request. The frame includes synthesized
+ // headers to explain the upcoming data.
+ SpdyFrame* CreatePushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ const SpdyHeaderBlock* headers);
+ SpdySerializedFrame* SerializePushPromise(
+ const SpdyPushPromiseIR& push_promise);
+
+ // Given a CREDENTIAL frame's payload, extract the credential.
+ // Returns true on successful parse, false otherwise.
+ // TODO(hkhalil): Implement CREDENTIAL frame parsing in SpdyFramer
+ // and eliminate this method.
+ static bool ParseCredentialData(const char* data, size_t len,
+ SpdyCredential* credential);
+
+ // Serialize a frame of unknown type.
+ SpdySerializedFrame* SerializeFrame(const SpdyFrameIR& frame);
+
+ // NOTES about frame compression.
+ // We want spdy to compress headers across the entire session. As long as
+ // the session is over TCP, frames are sent serially. The client & server
+ // can each compress frames in the same order and then compress them in that
+ // order, and the remote can do the reverse. However, we ultimately want
+ // the creation of frames to be less sensitive to order so that they can be
+ // placed over a UDP based protocol and yet still benefit from some
+ // compression. We don't know of any good compression protocol which does
+ // not build its state in a serial (stream based) manner.... For now, we're
+ // using zlib anyway.
+
+ // Compresses a SpdyFrame.
+ // On success, returns a new SpdyFrame with the payload compressed.
+ // Compression state is maintained as part of the SpdyFramer.
+ // Returned frame must be freed with "delete".
+ // On failure, returns NULL.
+ SpdyFrame* CompressFrame(const SpdyFrame& frame);
+
+ // For ease of testing and experimentation we can tweak compression on/off.
+ void set_enable_compression(bool value) {
+ enable_compression_ = value;
+ }
+
+ // Used only in log messages.
+ void set_display_protocol(const std::string& protocol) {
+ display_protocol_ = protocol;
+ }
+
+ // Returns the (minimum) size of frames (sans variable-length portions).
+ size_t GetDataFrameMinimumSize() const;
+ size_t GetControlFrameHeaderSize() const;
+ size_t GetSynStreamMinimumSize() const;
+ size_t GetSynReplyMinimumSize() const;
+ size_t GetRstStreamSize() const;
+ size_t GetSettingsMinimumSize() const;
+ size_t GetPingSize() const;
+ size_t GetGoAwaySize() const;
+ size_t GetHeadersMinimumSize() const;
+ size_t GetWindowUpdateSize() const;
+ size_t GetCredentialMinimumSize() const;
+ size_t GetBlockedSize() const;
+ size_t GetPushPromiseMinimumSize() const;
+
+ // Returns the minimum size a frame can be (data or control).
+ size_t GetFrameMinimumSize() const;
+
+ // Returns the maximum size a frame can be (data or control).
+ size_t GetFrameMaximumSize() const;
+
+ // Returns the maximum payload size of a DATA frame.
+ size_t GetDataFrameMaximumPayload() const;
+
+ // For debugging.
+ static const char* StateToString(int state);
+ static const char* ErrorCodeToString(int error_code);
+ static const char* StatusCodeToString(int status_code);
+ static const char* FrameTypeToString(SpdyFrameType type);
+
+ SpdyMajorVersion protocol_version() const { return spdy_version_; }
+
+ bool probable_http_response() const { return probable_http_response_; }
+
+ SpdyPriority GetLowestPriority() const { return spdy_version_ < 3 ? 3 : 7; }
+ SpdyPriority GetHighestPriority() const { return 0; }
+
+ // Deliver the given control frame's compressed headers block to the visitor
+ // in decompressed form, in chunks. Returns true if the visitor has
+ // accepted all of the chunks.
+ bool IncrementallyDecompressControlFrameHeaderData(
+ SpdyStreamId stream_id,
+ const char* data,
+ size_t len);
+
+ protected:
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, BasicCompression);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameSizesAreValidated);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HeaderCompression);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, DecompressUncompressedFrame);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ExpandBuffer_HeapSmash);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HugeHeaderBlock);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+ UnclosedStreamDataCompressorsOneByteAtATime);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+ UncompressLargerThanFrameBufferInitialSize);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ReadLargeSettingsFrame);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+ ReadLargeSettingsFrameInSmallChunks);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameAtMaxSizeLimit);
+ FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameTooLarge);
+ friend class net::HttpNetworkLayer; // This is temporary for the server.
+ friend class net::HttpNetworkTransactionTest;
+ friend class net::HttpProxyClientSocketPoolTest;
+ friend class net::SpdyHttpStreamTest;
+ friend class net::SpdyNetworkTransactionTest;
+ friend class net::SpdyProxyClientSocketTest;
+ friend class net::SpdySessionTest;
+ friend class net::SpdyStreamTest;
+ friend class net::SpdyWebSocketStreamTest;
+ friend class net::WebSocketJobTest;
+ friend class test::TestSpdyVisitor;
+
+ private:
+ // Internal breakouts from ProcessInput. Each returns the number of bytes
+ // consumed from the data.
+ size_t ProcessCommonHeader(const char* data, size_t len);
+ size_t ProcessControlFramePayload(const char* data, size_t len);
+ size_t ProcessCredentialFramePayload(const char* data, size_t len);
+ size_t ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len);
+ size_t ProcessControlFrameHeaderBlock(const char* data, size_t len);
+ size_t ProcessSettingsFramePayload(const char* data, size_t len);
+ size_t ProcessDataFramePayload(const char* data, size_t len);
+
+ // Helpers for above internal breakouts from ProcessInput.
+ void ProcessControlFrameHeader(uint16 control_frame_type_field);
+ bool ProcessSetting(const char* data); // Always passed exactly 8 bytes.
+
+ // Retrieve serialized length of SpdyHeaderBlock. If compression is enabled, a
+ // maximum estimate is returned.
+ size_t GetSerializedLength(const SpdyHeaderBlock& headers);
+
+ // Get (and lazily initialize) the ZLib state.
+ z_stream* GetHeaderCompressor();
+ z_stream* GetHeaderDecompressor();
+
+ private:
+ // Deliver the given control frame's uncompressed headers block to the
+ // visitor in chunks. Returns true if the visitor has accepted all of the
+ // chunks.
+ bool IncrementallyDeliverControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len);
+
+ // Utility to copy the given data block to the current frame buffer, up
+ // to the given maximum number of bytes, and update the buffer
+ // data (pointer and length). Returns the number of bytes
+ // read, and:
+ // *data is advanced the number of bytes read.
+ // *len is reduced by the number of bytes read.
+ size_t UpdateCurrentFrameBuffer(const char** data, size_t* len,
+ size_t max_bytes);
+
+ void WriteHeaderBlockToZ(const SpdyHeaderBlock* headers,
+ z_stream* out) const;
+
+ void SerializeNameValueBlockWithoutCompression(
+ SpdyFrameBuilder* builder,
+ const SpdyNameValueBlock& name_value_block) const;
+
+ // Compresses automatically according to enable_compression_.
+ void SerializeNameValueBlock(
+ SpdyFrameBuilder* builder,
+ const SpdyFrameWithNameValueBlockIR& frame);
+
+ // Set the error code and moves the framer into the error state.
+ void set_error(SpdyError error);
+
+ size_t GoAwaySize() const;
+
+ // The maximum size of the control frames that we support.
+ // This limit is arbitrary. We can enforce it here or at the application
+ // layer. We chose the framing layer, but this can be changed (or removed)
+ // if necessary later down the line.
+ size_t GetControlFrameBufferMaxSize() const {
+ // The theoretical maximum for SPDY3 and earlier is (2^24 - 1) +
+ // 8, since the length field does not count the size of the
+ // header.
+ if (spdy_version_ == SPDY2) {
+ return 64 * 1024;
+ }
+ if (spdy_version_ == SPDY3) {
+ return 16 * 1024 * 1024;
+ }
+ // The theoretical maximum for SPDY4 is 2^16 - 1, as the length
+ // field does count the size of the header.
+ return 16 * 1024;
+ }
+
+ // The size of the control frame buffer.
+ // Since this is only used for control frame headers, the maximum control
+ // frame header size (SYN_STREAM) is sufficient; all remaining control
+ // frame data is streamed to the visitor.
+ static const size_t kControlFrameBufferSize;
+
+ SpdyState state_;
+ SpdyState previous_state_;
+ SpdyError error_code_;
+ size_t remaining_data_length_;
+
+ // The number of bytes remaining to read from the current control frame's
+ // headers. Note that header data blocks (for control types that have them)
+ // are part of the frame's payload, and not the frame's headers.
+ size_t remaining_control_header_;
+
+ scoped_ptr<char[]> current_frame_buffer_;
+ // Number of bytes read into the current_frame_buffer_.
+ size_t current_frame_buffer_length_;
+
+ // The type of the frame currently being read.
+ SpdyFrameType current_frame_type_;
+
+ // The flags field of the frame currently being read.
+ uint8 current_frame_flags_;
+
+ // The total length of the frame currently being read, including frame header.
+ uint32 current_frame_length_;
+
+ // The stream ID field of the frame currently being read, if applicable.
+ SpdyStreamId current_frame_stream_id_;
+
+ // Scratch space for handling SETTINGS frames.
+ // TODO(hkhalil): Unify memory for this scratch space with
+ // current_frame_buffer_.
+ SpdySettingsScratch settings_scratch_;
+
+ bool enable_compression_; // Controls all compression
+ // SPDY header compressors.
+ scoped_ptr<z_stream> header_compressor_;
+ scoped_ptr<z_stream> header_decompressor_;
+
+ SpdyFramerVisitorInterface* visitor_;
+ SpdyFramerDebugVisitorInterface* debug_visitor_;
+
+ std::string display_protocol_;
+
+ // The major SPDY version to be spoken/understood by this framer.
+ const SpdyMajorVersion spdy_version_;
+
+ // Tracks if we've ever gotten far enough in framing to see a control frame of
+ // type SYN_STREAM or SYN_REPLY.
+ //
+ // If we ever get something which looks like a data frame before we've had a
+ // SYN, we explicitly check to see if it looks like we got an HTTP response to
+ // a SPDY request. This boolean lets us do that.
+ bool syn_frame_processed_;
+
+ // If we ever get a data frame before a SYN frame, we check to see if it
+ // starts with HTTP. If it does, we likely have an HTTP response. This
+ // isn't guaranteed though: we could have gotten a settings frame and then
+ // corrupt data that just looks like HTTP, but deterministic checking requires
+ // a lot more state.
+ bool probable_http_response_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_FRAMER_H_
diff --git a/chromium/net/spdy/spdy_framer_test.cc b/chromium/net/spdy/spdy_framer_test.cc
new file mode 100644
index 00000000000..d731b3adae9
--- /dev/null
+++ b/chromium/net/spdy/spdy_framer_test.cc
@@ -0,0 +1,4592 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <iostream>
+#include <limits>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+using std::string;
+using std::max;
+using std::min;
+using std::numeric_limits;
+using testing::_;
+
+namespace net {
+
+namespace test {
+
+static const size_t kMaxDecompressedSize = 1024;
+
+// TODO(akalin): Make sure expectations on mocks are set before mock
+// functions are called, as interleaving expectations and calls is
+// undefined.
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+ MOCK_METHOD1(OnError, void(SpdyFramer* framer));
+ MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId stream_id,
+ size_t length,
+ bool fin));
+ MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin));
+ MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len));
+ MOCK_METHOD6(OnSynStream, void(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 slot,
+ bool fin,
+ bool unidirectional));
+ MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin));
+ MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status));
+ MOCK_METHOD1(OnSettings, void(bool clear_persisted));
+ MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
+ MOCK_METHOD1(OnPing, void(uint32 unique_id));
+ MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status));
+ MOCK_METHOD2(OnHeaders, void(SpdyStreamId stream_id, bool fin));
+ MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
+ uint32 delta_window_size));
+ MOCK_METHOD2(OnCredentialFrameData, bool(const char* credential_data,
+ size_t len));
+ MOCK_METHOD1(OnBlocked, void(SpdyStreamId stream_id));
+ MOCK_METHOD2(OnPushPromise, void(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id));
+};
+
+class MockDebugVisitor : public SpdyFramerDebugVisitorInterface {
+ public:
+ MOCK_METHOD4(OnSendCompressedFrame, void(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len));
+
+ MOCK_METHOD3(OnReceiveCompressedFrame, void(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len));
+};
+
+class SpdyFramerTestUtil {
+ public:
+ // Decompress a single frame using the decompression context held by
+ // the SpdyFramer. The implemention is meant for use only in tests
+ // and will CHECK fail if the input is anything other than a single,
+ // well-formed compressed frame.
+ //
+ // Returns a new decompressed SpdyFrame.
+ template<class SpdyFrameType> static SpdyFrame* DecompressFrame(
+ SpdyFramer* framer, const SpdyFrameType& frame) {
+ DecompressionVisitor visitor(framer->protocol_version());
+ framer->set_visitor(&visitor);
+ CHECK_EQ(frame.size(), framer->ProcessInput(frame.data(), frame.size()));
+ CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state());
+ framer->set_visitor(NULL);
+
+ char* buffer = visitor.ReleaseBuffer();
+ CHECK(buffer != NULL);
+ SpdyFrame* decompressed_frame = new SpdyFrame(buffer, visitor.size(), true);
+ if (framer->protocol_version() == 4) {
+ SetFrameLength(decompressed_frame,
+ visitor.size(),
+ framer->protocol_version());
+ } else {
+ SetFrameLength(decompressed_frame,
+ visitor.size() - framer->GetControlFrameHeaderSize(),
+ framer->protocol_version());
+ }
+ return decompressed_frame;
+ }
+
+ class DecompressionVisitor : public SpdyFramerVisitorInterface {
+ public:
+ explicit DecompressionVisitor(SpdyMajorVersion version)
+ : version_(version), size_(0), finished_(false) {}
+
+ void ResetBuffer() {
+ CHECK(buffer_.get() == NULL);
+ CHECK_EQ(0u, size_);
+ CHECK(!finished_);
+ buffer_.reset(new char[kMaxDecompressedSize]);
+ }
+
+ virtual void OnError(SpdyFramer* framer) OVERRIDE { LOG(FATAL); }
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE {
+ LOG(FATAL) << "Unexpected data frame header";
+ }
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {
+ LOG(FATAL);
+ }
+
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) OVERRIDE {
+ CHECK(buffer_.get() != NULL);
+ CHECK_GE(kMaxDecompressedSize, size_ + len);
+ CHECK(!finished_);
+ if (len != 0) {
+ memcpy(buffer_.get() + size_, header_data, len);
+ size_ += len;
+ } else {
+ // Done.
+ finished_ = true;
+ }
+ return true;
+ }
+
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 slot,
+ bool fin,
+ bool unidirectional) OVERRIDE {
+ SpdyFramer framer(version_);
+ framer.set_enable_compression(false);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ if (unidirectional) {
+ flags &= CONTROL_FLAG_UNIDIRECTIONAL;
+ }
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(stream_id,
+ associated_stream_id,
+ priority,
+ slot,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), framer.GetSynStreamMinimumSize());
+ size_ += framer.GetSynStreamMinimumSize();
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ SpdyFramer framer(version_);
+ framer.set_enable_compression(false);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateHeaders(stream_id,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize());
+ size_ += framer.GetSynStreamMinimumSize();
+ }
+
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {
+ LOG(FATAL);
+ }
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {
+ LOG(FATAL);
+ }
+ virtual void OnPing(uint32 unique_id) OVERRIDE {
+ LOG(FATAL);
+ }
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {
+ LOG(FATAL);
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ SpdyFramer framer(version_);
+ framer.set_enable_compression(false);
+ const SpdyHeaderBlock null_headers;
+ int flags = CONTROL_FLAG_NONE;
+ if (fin) {
+ flags &= CONTROL_FLAG_FIN;
+ }
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateHeaders(stream_id,
+ static_cast<SpdyControlFlags>(flags),
+ false,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize());
+ size_ += framer.GetHeadersMinimumSize();
+ }
+
+ virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
+ LOG(FATAL);
+ }
+ virtual bool OnCredentialFrameData(const char* /*credential_data*/,
+ size_t /*len*/) OVERRIDE {
+ LOG(FATAL) << "Unexpected CREDENTIAL Frame";
+ return false;
+ }
+
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {
+ SpdyFramer framer(version_);
+ framer.set_enable_compression(false);
+ const SpdyHeaderBlock null_headers;
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreatePushPromise(stream_id, promised_stream_id,
+ &null_headers));
+ ResetBuffer();
+ memcpy(buffer_.get(), frame->data(), framer.GetPushPromiseMinimumSize());
+ size_ += framer.GetPushPromiseMinimumSize();
+ }
+
+ char* ReleaseBuffer() {
+ CHECK(finished_);
+ return buffer_.release();
+ }
+
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {
+ LOG(FATAL);
+ }
+
+ size_t size() const {
+ CHECK(finished_);
+ return size_;
+ }
+
+ private:
+ SpdyMajorVersion version_;
+ scoped_ptr<char[]> buffer_;
+ size_t size_;
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
+ };
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
+};
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface {
+ public:
+ static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024;
+ static const size_t kDefaultCredentialBufferSize = 16 * 1024;
+
+ explicit TestSpdyVisitor(SpdyMajorVersion version)
+ : framer_(version),
+ use_compression_(false),
+ error_count_(0),
+ syn_frame_count_(0),
+ syn_reply_frame_count_(0),
+ headers_frame_count_(0),
+ goaway_count_(0),
+ setting_count_(0),
+ last_window_update_stream_(0),
+ last_window_update_delta_(0),
+ last_push_promise_stream_(0),
+ last_push_promise_promised_stream_(0),
+ data_bytes_(0),
+ fin_frame_count_(0),
+ fin_flag_count_(0),
+ zero_length_data_frame_count_(0),
+ control_frame_header_data_count_(0),
+ zero_length_control_frame_header_data_count_(0),
+ data_frame_count_(0),
+ last_payload_len_(0),
+ last_frame_len_(0),
+ header_buffer_(new char[kDefaultHeaderBufferSize]),
+ header_buffer_length_(0),
+ header_buffer_size_(kDefaultHeaderBufferSize),
+ header_stream_id_(-1),
+ header_control_type_(DATA),
+ header_buffer_valid_(false),
+ credential_buffer_(new char[kDefaultCredentialBufferSize]),
+ credential_buffer_length_(0),
+ credential_buffer_size_(kDefaultCredentialBufferSize) {
+ }
+
+ virtual void OnError(SpdyFramer* f) OVERRIDE {
+ LOG(INFO) << "SpdyFramer Error: "
+ << SpdyFramer::ErrorCodeToString(f->error_code());
+ error_count_++;
+ }
+
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE {
+ data_frame_count_++;
+ header_stream_id_ = stream_id;
+ }
+
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {
+ EXPECT_EQ(header_stream_id_, stream_id);
+ if (len == 0)
+ ++zero_length_data_frame_count_;
+
+ data_bytes_ += len;
+ std::cerr << "OnStreamFrameData(" << stream_id << ", \"";
+ if (len > 0) {
+ for (size_t i = 0 ; i < len; ++i) {
+ std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec;
+ }
+ }
+ std::cerr << "\", " << len << ")\n";
+ }
+
+ virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+ const char* header_data,
+ size_t len) OVERRIDE {
+ ++control_frame_header_data_count_;
+ CHECK_EQ(header_stream_id_, stream_id);
+ if (len == 0) {
+ ++zero_length_control_frame_header_data_count_;
+ // Indicates end-of-header-block.
+ CHECK(header_buffer_valid_);
+ size_t parsed_length = framer_.ParseHeaderBlockInBuffer(
+ header_buffer_.get(), header_buffer_length_, &headers_);
+ DCHECK_EQ(header_buffer_length_, parsed_length);
+ return true;
+ }
+ const size_t available = header_buffer_size_ - header_buffer_length_;
+ if (len > available) {
+ header_buffer_valid_ = false;
+ return false;
+ }
+ memcpy(header_buffer_.get() + header_buffer_length_, header_data, len);
+ header_buffer_length_ += len;
+ return true;
+ }
+
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional) OVERRIDE {
+ syn_frame_count_++;
+ InitHeaderStreaming(SYN_STREAM, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ syn_reply_frame_count_++;
+ InitHeaderStreaming(SYN_REPLY, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {
+ fin_frame_count_++;
+ }
+
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {
+ setting_count_++;
+ }
+
+ virtual void OnPing(uint32 unique_id) OVERRIDE {
+ DLOG(FATAL);
+ }
+
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {
+ goaway_count_++;
+ }
+
+ virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE {
+ headers_frame_count_++;
+ InitHeaderStreaming(HEADERS, stream_id);
+ if (fin) {
+ fin_flag_count_++;
+ }
+ }
+
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {
+ last_window_update_stream_ = stream_id;
+ last_window_update_delta_ = delta_window_size;
+ }
+
+ virtual bool OnCredentialFrameData(const char* credential_data,
+ size_t len) OVERRIDE {
+ if (len == 0) {
+ if (!framer_.ParseCredentialData(credential_buffer_.get(),
+ credential_buffer_length_,
+ &credential_)) {
+ LOG(INFO) << "Error parsing credential data.";
+ ++error_count_;
+ }
+ return true;
+ }
+ const size_t available =
+ credential_buffer_size_ - credential_buffer_length_;
+ if (len > available) {
+ return false;
+ }
+ memcpy(credential_buffer_.get() + credential_buffer_length_,
+ credential_data, len);
+ credential_buffer_length_ += len;
+ return true;
+ }
+
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {
+ InitHeaderStreaming(PUSH_PROMISE, stream_id);
+ last_push_promise_stream_ = stream_id;
+ last_push_promise_promised_stream_ = promised_stream_id;
+ }
+
+ virtual void OnSendCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) OVERRIDE {
+ last_payload_len_ = payload_len;
+ last_frame_len_ = frame_len;
+ }
+
+ virtual void OnReceiveCompressedFrame(SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) OVERRIDE {
+ last_frame_len_ = frame_len;
+ }
+
+ // Convenience function which runs a framer simulation with particular input.
+ void SimulateInFramer(const unsigned char* input, size_t size) {
+ framer_.set_enable_compression(use_compression_);
+ framer_.set_visitor(this);
+ size_t input_remaining = size;
+ const char* input_ptr = reinterpret_cast<const char*>(input);
+ while (input_remaining > 0 &&
+ framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) {
+ // To make the tests more interesting, we feed random (amd small) chunks
+ // into the framer. This simulates getting strange-sized reads from
+ // the socket.
+ const size_t kMaxReadSize = 32;
+ size_t bytes_read =
+ (rand() % min(input_remaining, kMaxReadSize)) + 1;
+ size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read);
+ input_remaining -= bytes_processed;
+ input_ptr += bytes_processed;
+ }
+ }
+
+ void InitHeaderStreaming(SpdyFrameType header_control_type,
+ SpdyStreamId stream_id) {
+ DCHECK_GE(header_control_type, FIRST_CONTROL_TYPE);
+ DCHECK_LE(header_control_type, LAST_CONTROL_TYPE);
+ memset(header_buffer_.get(), 0, header_buffer_size_);
+ header_buffer_length_ = 0;
+ header_stream_id_ = stream_id;
+ header_control_type_ = header_control_type;
+ header_buffer_valid_ = true;
+ DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+ }
+
+ // Override the default buffer size (16K). Call before using the framer!
+ void set_header_buffer_size(size_t header_buffer_size) {
+ header_buffer_size_ = header_buffer_size;
+ header_buffer_.reset(new char[header_buffer_size]);
+ }
+
+ static size_t header_data_chunk_max_size() {
+ return SpdyFramer::kHeaderDataChunkMaxSize;
+ }
+
+ SpdyFramer framer_;
+ bool use_compression_;
+
+ // Counters from the visitor callbacks.
+ int error_count_;
+ int syn_frame_count_;
+ int syn_reply_frame_count_;
+ int headers_frame_count_;
+ int goaway_count_;
+ int setting_count_;
+ SpdyStreamId last_window_update_stream_;
+ uint32 last_window_update_delta_;
+ SpdyStreamId last_push_promise_stream_;
+ SpdyStreamId last_push_promise_promised_stream_;
+ int data_bytes_;
+ int fin_frame_count_; // The count of RST_STREAM type frames received.
+ int fin_flag_count_; // The count of frames with the FIN flag set.
+ int zero_length_data_frame_count_; // The count of zero-length data frames.
+ int control_frame_header_data_count_; // The count of chunks received.
+ // The count of zero-length control frame header data chunks received.
+ int zero_length_control_frame_header_data_count_;
+ int data_frame_count_;
+ size_t last_payload_len_;
+ size_t last_frame_len_;
+
+ // Header block streaming state:
+ scoped_ptr<char[]> header_buffer_;
+ size_t header_buffer_length_;
+ size_t header_buffer_size_;
+ SpdyStreamId header_stream_id_;
+ SpdyFrameType header_control_type_;
+ bool header_buffer_valid_;
+ SpdyHeaderBlock headers_;
+
+ scoped_ptr<char[]> credential_buffer_;
+ size_t credential_buffer_length_;
+ size_t credential_buffer_size_;
+ SpdyCredential credential_;
+};
+
+// Retrieves serialized headers from SYN_STREAM frame.
+// Does not check that the given frame is a SYN_STREAM.
+base::StringPiece GetSerializedHeaders(const SpdyFrame* frame,
+ const SpdyFramer& framer) {
+ return base::StringPiece(frame->data() + framer.GetSynStreamMinimumSize(),
+ frame->size() - framer.GetSynStreamMinimumSize());
+}
+
+} // namespace test
+
+} // namespace net
+
+using net::test::SetFrameLength;
+using net::test::SetFrameFlags;
+using net::test::CompareCharArraysWithHexError;
+using net::test::SpdyFramerTestUtil;
+using net::test::TestSpdyVisitor;
+using net::test::GetSerializedHeaders;
+
+namespace net {
+
+class SpdyFramerTest : public ::testing::TestWithParam<SpdyMajorVersion> {
+ protected:
+ virtual void SetUp() {
+ spdy_version_ = GetParam();
+ spdy_version_ch_ = static_cast<unsigned char>(spdy_version_);
+ }
+
+ void CompareFrame(const string& description,
+ const SpdyFrame& actual_frame,
+ const unsigned char* expected,
+ const int expected_len) {
+ const unsigned char* actual =
+ reinterpret_cast<const unsigned char*>(actual_frame.data());
+ CompareCharArraysWithHexError(
+ description, actual, actual_frame.size(), expected, expected_len);
+ }
+
+ void CompareFrames(const string& description,
+ const SpdyFrame& expected_frame,
+ const SpdyFrame& actual_frame) {
+ CompareCharArraysWithHexError(
+ description,
+ reinterpret_cast<const unsigned char*>(expected_frame.data()),
+ expected_frame.size(),
+ reinterpret_cast<const unsigned char*>(actual_frame.data()),
+ actual_frame.size());
+ }
+
+ // Returns true if the two header blocks have equivalent content.
+ bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
+ const SpdyHeaderBlock* actual) {
+ if (expected->size() != actual->size()) {
+ LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
+ << actual->size() << ".";
+ return false;
+ }
+ for (SpdyHeaderBlock::const_iterator it = expected->begin();
+ it != expected->end();
+ ++it) {
+ SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
+ if (it2 == actual->end()) {
+ LOG(ERROR) << "Expected header name '" << it->first << "'.";
+ return false;
+ }
+ if (it->second.compare(it2->second) != 0) {
+ LOG(ERROR) << "Expected header named '" << it->first
+ << "' to have a value of '" << it->second
+ << "'. The actual value received was '" << it2->second
+ << "'.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void AddSpdySettingFromWireFormat(SettingsMap* settings,
+ uint32 key,
+ uint32 value) {
+ SettingsFlagsAndId flags_and_id =
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, key);
+ SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+ SpdySettingsFlags flags =
+ static_cast<SpdySettingsFlags>(flags_and_id.flags());
+ CHECK(settings->find(id) == settings->end());
+ settings->insert(std::make_pair(id, SettingsFlagsAndValue(flags, value)));
+ }
+
+ bool IsSpdy2() { return spdy_version_ == SPDY2; }
+ bool IsSpdy3() { return spdy_version_ == SPDY3; }
+ bool IsSpdy4() { return spdy_version_ == SPDY4; }
+
+ // Version of SPDY protocol to be used.
+ SpdyMajorVersion spdy_version_;
+ unsigned char spdy_version_ch_;
+};
+
+// All tests are run with 3 different SPDY versions: SPDY/2, SPDY/3, SPDY/4.
+INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
+ SpdyFramerTest,
+ ::testing::Values(SPDY2, SPDY3, SPDY4));
+
+// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
+TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "charlie";
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ EXPECT_TRUE(frame.get() != NULL);
+ base::StringPiece serialized_headers =
+ GetSerializedHeaders(frame.get(), framer);
+ SpdyHeaderBlock new_headers;
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &new_headers));
+
+ EXPECT_EQ(headers.size(), new_headers.size());
+ EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
+ EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
+}
+
+// Test that if there's not a full frame, we fail to parse it.
+TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "charlie";
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ // Encode the header block into a SynStream frame.
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ EXPECT_TRUE(frame.get() != NULL);
+
+ base::StringPiece serialized_headers =
+ GetSerializedHeaders(frame.get(), framer);
+ SpdyHeaderBlock new_headers;
+ EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size() - 2,
+ &new_headers));
+}
+
+TEST_P(SpdyFramerTest, OutOfOrderHeaders) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(1024);
+ if (spdy_version_ < 4) {
+ frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE);
+ frame.WriteUInt32(3); // stream_id
+ } else {
+ frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3);
+ }
+
+ frame.WriteUInt32(0); // Associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ if (IsSpdy2()) {
+ frame.WriteUInt16(2); // Number of headers.
+ frame.WriteString("gamma");
+ frame.WriteString("gamma");
+ frame.WriteString("alpha");
+ frame.WriteString("alpha");
+ } else {
+ frame.WriteUInt32(2); // Number of headers.
+ frame.WriteStringPiece32("gamma");
+ frame.WriteStringPiece32("gamma");
+ frame.WriteStringPiece32("alpha");
+ frame.WriteStringPiece32("alpha");
+ }
+ // write the length
+ frame.RewriteLength(framer);
+
+ SpdyHeaderBlock new_headers;
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ base::StringPiece serialized_headers =
+ GetSerializedHeaders(control_frame.get(), framer);
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &new_headers));
+}
+
+// Test that if we receive a SYN_STREAM with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, SynStreamWithStreamIdZero) {
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ scoped_ptr<SpdySerializedFrame> frame(
+ framer.CreateSynStream(0, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ ASSERT_TRUE(frame.get() != NULL);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
+ EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
+ EXPECT_TRUE(framer.HasError());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+// Test that if we receive a SYN_REPLY with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, SynReplyWithStreamIdZero) {
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ scoped_ptr<SpdySerializedFrame> frame(
+ framer.CreateSynReply(0, // stream id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ ASSERT_TRUE(frame.get() != NULL);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
+ EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
+ EXPECT_TRUE(framer.HasError());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+// Test that if we receive a HEADERS with stream ID zero, we signal an error
+// (but don't crash).
+TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) {
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ scoped_ptr<SpdySerializedFrame> frame(
+ framer.CreateHeaders(0, // stream id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ ASSERT_TRUE(frame.get() != NULL);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
+ EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
+ EXPECT_TRUE(framer.HasError());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal an
+// error (but don't crash).
+TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ scoped_ptr<SpdySerializedFrame> frame(
+ framer.CreatePushPromise(0, // stream id
+ 4, // promised stream id
+ &headers));
+ ASSERT_TRUE(frame.get() != NULL);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
+ EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
+ EXPECT_TRUE(framer.HasError());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we
+// signal an error (but don't crash).
+TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ scoped_ptr<SpdySerializedFrame> frame(
+ framer.CreatePushPromise(3, // stream id
+ 0, // promised stream id
+ &headers));
+ ASSERT_TRUE(frame.get() != NULL);
+
+ // We shouldn't have to read the whole frame before we signal an error.
+ EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
+ EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
+ EXPECT_TRUE(framer.HasError());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+TEST_P(SpdyFramerTest, CreateCredential) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "CREDENTIAL frame";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x0A,
+ 0x00, 0x00, 0x00, 0x33,
+ 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x05, 'p', 'r',
+ 'o', 'o', 'f', 0x00,
+ 0x00, 0x00, 0x06, 'a',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0C, 'a', 'n', 'o',
+ 't', 'h', 'e', 'r',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0A, 'f', 'i', 'n',
+ 'a', 'l', ' ', 'c',
+ 'e', 'r', 't',
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x3b, 0x0A, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x05, 'p', 'r',
+ 'o', 'o', 'f', 0x00,
+ 0x00, 0x00, 0x06, 'a',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0C, 'a', 'n', 'o',
+ 't', 'h', 'e', 'r',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0A, 'f', 'i', 'n',
+ 'a', 'l', ' ', 'c',
+ 'e', 'r', 't',
+ };
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, ParseCredentialFrameData) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x0A,
+ 0x00, 0x00, 0x00, 0x33,
+ 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x05, 'p', 'r',
+ 'o', 'o', 'f', 0x00,
+ 0x00, 0x00, 0x06, 'a',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0C, 'a', 'n', 'o',
+ 't', 'h', 'e', 'r',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0A, 'f', 'i', 'n',
+ 'a', 'l', ' ', 'c',
+ 'e', 'r', 't',
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x37, 0x0A, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x05, 'p', 'r',
+ 'o', 'o', 'f', 0x00,
+ 0x00, 0x00, 0x06, 'a',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0C, 'a', 'n', 'o',
+ 't', 'h', 'e', 'r',
+ ' ', 'c', 'e', 'r',
+ 't', 0x00, 0x00, 0x00,
+ 0x0A, 'f', 'i', 'n',
+ 'a', 'l', ' ', 'c',
+ 'e', 'r', 't',
+ };
+
+ SpdyCredential credential;
+ if (IsSpdy4()) {
+ EXPECT_TRUE(SpdyFramer::ParseCredentialData(
+ reinterpret_cast<const char*>(kV4FrameData) +
+ framer.GetControlFrameHeaderSize(),
+ arraysize(kV4FrameData) - framer.GetControlFrameHeaderSize(),
+ &credential));
+ } else {
+ EXPECT_TRUE(SpdyFramer::ParseCredentialData(
+ reinterpret_cast<const char*>(kV3FrameData) +
+ framer.GetControlFrameHeaderSize(),
+ arraysize(kV3FrameData) - framer.GetControlFrameHeaderSize(),
+ &credential));
+ }
+ EXPECT_EQ(3u, credential.slot);
+ EXPECT_EQ("proof", credential.proof);
+ EXPECT_EQ("a cert", credential.certs.front());
+ credential.certs.erase(credential.certs.begin());
+ EXPECT_EQ("another cert", credential.certs.front());
+ credential.certs.erase(credential.certs.begin());
+ EXPECT_EQ("final cert", credential.certs.front());
+ credential.certs.erase(credential.certs.begin());
+ EXPECT_TRUE(credential.certs.empty());
+ }
+}
+
+TEST_P(SpdyFramerTest, DuplicateHeader) {
+ SpdyFramer framer(spdy_version_);
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(1024);
+ if (spdy_version_ < 4) {
+ frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE);
+ frame.WriteUInt32(3); // stream_id
+ } else {
+ frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3);
+ }
+
+ frame.WriteUInt32(0); // associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ if (IsSpdy2()) {
+ frame.WriteUInt16(2); // Number of headers.
+ frame.WriteString("name");
+ frame.WriteString("value1");
+ frame.WriteString("name");
+ frame.WriteString("value2");
+ } else {
+ frame.WriteUInt32(2); // Number of headers.
+ frame.WriteStringPiece32("name");
+ frame.WriteStringPiece32("value1");
+ frame.WriteStringPiece32("name");
+ frame.WriteStringPiece32("value2");
+ }
+ // write the length
+ frame.RewriteLength(framer);
+
+ SpdyHeaderBlock new_headers;
+ framer.set_enable_compression(false);
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ base::StringPiece serialized_headers =
+ GetSerializedHeaders(control_frame.get(), framer);
+ // This should fail because duplicate headers are verboten by the spec.
+ EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &new_headers));
+}
+
+TEST_P(SpdyFramerTest, MultiValueHeader) {
+ SpdyFramer framer(spdy_version_);
+ // Frame builder with plentiful buffer size.
+ SpdyFrameBuilder frame(1024);
+ if (spdy_version_ < 4) {
+ frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE);
+ frame.WriteUInt32(3); // stream_id
+ } else {
+ frame.WriteFramePrefix(framer, SYN_STREAM, CONTROL_FLAG_NONE, 3);
+ }
+
+ frame.WriteUInt32(0); // associated stream id
+ frame.WriteUInt16(0); // Priority.
+
+ string value("value1\0value2");
+ if (IsSpdy2()) {
+ frame.WriteUInt16(1); // Number of headers.
+ frame.WriteString("name");
+ frame.WriteString(value);
+ } else {
+ frame.WriteUInt32(1); // Number of headers.
+ frame.WriteStringPiece32("name");
+ frame.WriteStringPiece32(value);
+ }
+ // write the length
+ frame.RewriteLength(framer);
+
+ SpdyHeaderBlock new_headers;
+ framer.set_enable_compression(false);
+ scoped_ptr<SpdyFrame> control_frame(frame.take());
+ base::StringPiece serialized_headers =
+ GetSerializedHeaders(control_frame.get(), framer);
+ EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &new_headers));
+ EXPECT_TRUE(new_headers.find("name") != new_headers.end());
+ EXPECT_EQ(value, new_headers.find("name")->second);
+}
+
+TEST_P(SpdyFramerTest, BasicCompression) {
+ SpdyHeaderBlock headers;
+ headers["server"] = "SpdyServer 1.0";
+ headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
+ headers["status"] = "200";
+ headers["version"] = "HTTP/1.1";
+ headers["content-type"] = "text/html";
+ headers["content-length"] = "12";
+
+ scoped_ptr<TestSpdyVisitor> visitor(new TestSpdyVisitor(spdy_version_));
+ SpdyFramer framer(spdy_version_);
+ framer.set_debug_visitor(visitor.get());
+ scoped_ptr<SpdyFrame> frame1(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ size_t uncompressed_size1 = visitor->last_payload_len_;
+ size_t compressed_size1 =
+ visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
+ if (IsSpdy2()) {
+ EXPECT_EQ(139u, uncompressed_size1);
+#if defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(155u, compressed_size1);
+#else // !defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(135u, compressed_size1);
+#endif // !defined(USE_SYSTEM_ZLIB)
+ } else {
+ EXPECT_EQ(165u, uncompressed_size1);
+#if defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(181u, compressed_size1);
+#else // !defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(117u, compressed_size1);
+#endif // !defined(USE_SYSTEM_ZLIB)
+ }
+ scoped_ptr<SpdyFrame> frame2(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ size_t uncompressed_size2 = visitor->last_payload_len_;
+ size_t compressed_size2 =
+ visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
+
+ // Expect the second frame to be more compact than the first.
+ EXPECT_LE(frame2->size(), frame1->size());
+
+ // Decompress the first frame
+ scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame(
+ &framer, *frame1.get()));
+
+ // Decompress the second frame
+ visitor.reset(new TestSpdyVisitor(spdy_version_));
+ framer.set_debug_visitor(visitor.get());
+ scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame(
+ &framer, *frame2.get()));
+ size_t uncompressed_size4 =
+ frame4->size() - framer.GetSynStreamMinimumSize();
+ size_t compressed_size4 =
+ visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
+ if (IsSpdy2()) {
+ EXPECT_EQ(139u, uncompressed_size4);
+#if defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(149u, compressed_size4);
+#else // !defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(101u, compressed_size4);
+#endif // !defined(USE_SYSTEM_ZLIB)
+ } else {
+ EXPECT_EQ(165u, uncompressed_size4);
+#if defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(175u, compressed_size4);
+#else // !defined(USE_SYSTEM_ZLIB)
+ EXPECT_EQ(102u, compressed_size4);
+#endif // !defined(USE_SYSTEM_ZLIB)
+ }
+
+ EXPECT_EQ(uncompressed_size1, uncompressed_size2);
+ EXPECT_EQ(uncompressed_size1, uncompressed_size4);
+ EXPECT_EQ(compressed_size2, compressed_size4);
+
+ // Expect frames 3 & 4 to be the same.
+ CompareFrames("Uncompressed SYN_STREAM", *frame3, *frame4);
+
+ // Expect frames 3 to be the same as a uncompressed frame created
+ // from scratch.
+ framer.set_enable_compression(false);
+ scoped_ptr<SpdyFrame> uncompressed_frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ CompareFrames("Uncompressed SYN_STREAM", *frame3, *uncompressed_frame);
+}
+
+TEST_P(SpdyFramerTest, CompressEmptyHeaders) {
+ // See crbug.com/172383
+ SpdyHeaderBlock headers;
+ headers["server"] = "SpdyServer 1.0";
+ headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
+ headers["status"] = "200";
+ headers["version"] = "HTTP/1.1";
+ headers["content-type"] = "text/html";
+ headers["content-length"] = "12";
+ headers["x-empty-header"] = "";
+
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+ scoped_ptr<SpdyFrame> frame1(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+}
+
+TEST_P(SpdyFramerTest, Basic) {
+ const unsigned char kV2Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x08, // HEADERS on Stream #1
+ 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x02, 'h', '2',
+ 0x00, 0x02, 'v', '2',
+ 0x00, 0x02, 'h', '3',
+ 0x00, 0x02, 'v', '3',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #3
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #1
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ const unsigned char kV3Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x1a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x08, // HEADERS on Stream #1
+ 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x02,
+ 'h', '2',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', '2', 0x00, 0x00,
+ 0x00, 0x02, 'h', '3',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', '3',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #3
+ 0x00, 0x00, 0x00, 0x0e,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #1
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ const unsigned char kV4Input[] = {
+ 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x00, 0x24, 0x08, 0x00, // HEADERS on Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x02,
+ 'h', '2', 0x00, 0x00,
+ 0x00, 0x02, 'v', '2',
+ 0x00, 0x00, 0x00, 0x02,
+ 'h', '3', 0x00, 0x00,
+ 0x00, 0x02, 'v', '3',
+
+ 0x00, 0x14, 0x00, 0x00, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x12, 0x01, 0x00, // SYN Stream #3
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+
+ 0x00, 0x10, 0x00, 0x00, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x03,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x0c, 0x00, 0x00, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x0c, 0x03, 0x00, // RST_STREAM on Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x08, 0x00, 0x00, // DATA on Stream #3
+ 0x00, 0x00, 0x00, 0x03,
+
+ 0x00, 0x0c, 0x03, 0x00, // RST_STREAM on Stream #3
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+ } else if (IsSpdy3()) {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ } else {
+ visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
+ }
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(2, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ EXPECT_EQ(24, visitor.data_bytes_);
+ EXPECT_EQ(2, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
+ EXPECT_EQ(4, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnDataFrame) {
+ const unsigned char kV2Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF
+ 0x01, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+ };
+ const unsigned char kV3Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x1a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 'a', 'a', 0x00, 0x00,
+ 0x00, 0x02, 'b', 'b',
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x0c,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF
+ 0x01, 0x00, 0x00, 0x04,
+ 0xde, 0xad, 0xbe, 0xef,
+ };
+ const unsigned char kV4Input[] = {
+ 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x00, 0x18, 0x02, 0x00, // SYN REPLY Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 'a', 'a', 0x00, 0x00,
+ 0x00, 0x02, 'b', 'b',
+
+ 0x00, 0x14, 0x00, 0x00, // DATA on Stream #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+ 0xde, 0xad, 0xbe, 0xef,
+
+ 0x00, 0x0c, 0x00, 0x01, // DATA on Stream #1, with FIN
+ 0x00, 0x00, 0x00, 0x01,
+ 0xde, 0xad, 0xbe, 0xef,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+ } else if (IsSpdy3()) {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ } else {
+ visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
+ }
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_EQ(16, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+ EXPECT_EQ(2, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a SYN reply frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnSynReplyFrame) {
+ const unsigned char kV2Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x02, 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x02, 'a', 'a',
+ 0x00, 0x02, 'b', 'b',
+ };
+ const unsigned char kV3Input[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01, // SYN Stream #1
+ 0x00, 0x00, 0x00, 0x1a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x80, spdy_version_ch_, 0x00, 0x02, // SYN REPLY Stream #1
+ 0x01, 0x00, 0x00, 0x14,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 'a', 'a', 0x00, 0x00,
+ 0x00, 0x02, 'b', 'b',
+ };
+ const unsigned char kV4Input[] = {
+ 0x00, 0x1e, 0x01, 0x00, // SYN_STREAM #1
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 'h', 'h',
+ 0x00, 0x00, 0x00, 0x02,
+ 'v', 'v',
+
+ 0x00, 0x18, 0x02, 0x01, // SYN_REPLY #1, with FIN
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 'a', 'a', 0x00, 0x00,
+ 0x00, 0x02, 'b', 'b',
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+ } else if (IsSpdy3()) {
+ visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+ } else {
+ visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
+ }
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_EQ(0, visitor.data_bytes_);
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(1, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+ EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, HeaderCompression) {
+ SpdyFramer send_framer(spdy_version_);
+ SpdyFramer recv_framer(spdy_version_);
+
+ send_framer.set_enable_compression(true);
+ recv_framer.set_enable_compression(true);
+
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kHeader3[] = "header3";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+ const char kValue3[] = "value3";
+
+ // SYN_STREAM #1
+ SpdyHeaderBlock block;
+ block[kHeader1] = kValue1;
+ block[kHeader2] = kValue2;
+ SpdyControlFlags flags(CONTROL_FLAG_NONE);
+ SpdySynStreamIR syn_ir_1(1);
+ syn_ir_1.SetHeader(kHeader1, kValue1);
+ syn_ir_1.SetHeader(kHeader2, kValue2);
+ scoped_ptr<SpdyFrame> syn_frame_1(send_framer.SerializeFrame(syn_ir_1));
+ EXPECT_TRUE(syn_frame_1.get() != NULL);
+
+ // SYN_STREAM #2
+ block[kHeader3] = kValue3;
+ scoped_ptr<SpdyFrame> syn_frame_2(
+ send_framer.CreateSynStream(3, // stream id
+ 0, // associated stream id
+ 0, // priority
+ 0, // credential slot
+ flags,
+ true, // compress
+ &block));
+ EXPECT_TRUE(syn_frame_2.get() != NULL);
+
+ // Now start decompressing
+ scoped_ptr<SpdyFrame> decompressed;
+ scoped_ptr<SpdyFrame> uncompressed;
+ base::StringPiece serialized_headers;
+ SpdyHeaderBlock decompressed_headers;
+
+ // Decompress SYN_STREAM #1
+ decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
+ &recv_framer, *syn_frame_1.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer);
+ EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &decompressed_headers));
+ EXPECT_EQ(2u, decompressed_headers.size());
+ EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+ EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+
+ // Decompress SYN_STREAM #2
+ decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
+ &recv_framer, *syn_frame_2.get()));
+ EXPECT_TRUE(decompressed.get() != NULL);
+ serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer);
+ decompressed_headers.clear();
+ EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
+ serialized_headers.size(),
+ &decompressed_headers));
+ EXPECT_EQ(3u, decompressed_headers.size());
+ EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+ EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+ EXPECT_EQ(kValue3, decompressed_headers[kHeader3]);
+}
+
+// Verify we don't leak when we leave streams unclosed
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressors) {
+ SpdyFramer send_framer(spdy_version_);
+
+ send_framer.set_enable_compression(true);
+
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+
+ SpdyHeaderBlock block;
+ block[kHeader1] = kValue1;
+ block[kHeader2] = kValue2;
+ SpdyControlFlags flags(CONTROL_FLAG_NONE);
+ scoped_ptr<SpdyFrame> syn_frame(
+ send_framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 0, // priority
+ 0, // credential slot
+ flags,
+ true, // compress
+ &block));
+ EXPECT_TRUE(syn_frame.get() != NULL);
+
+ const char bytes[] = "this is a test test test test test!";
+ scoped_ptr<SpdyFrame> send_frame(
+ send_framer.CreateDataFrame(
+ 1, bytes, arraysize(bytes),
+ static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
+ EXPECT_TRUE(send_frame.get() != NULL);
+
+ // Run the inputs through the framer.
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ const unsigned char* data;
+ data = reinterpret_cast<const unsigned char*>(syn_frame->data());
+ visitor.SimulateInFramer(data, syn_frame->size());
+ data = reinterpret_cast<const unsigned char*>(send_frame->data());
+ visitor.SimulateInFramer(data, send_frame->size());
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+ EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+// Verify we can decompress the stream even if handed over to the
+// framer 1 byte at a time.
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) {
+ SpdyFramer send_framer(spdy_version_);
+
+ send_framer.set_enable_compression(true);
+
+ const char kHeader1[] = "header1";
+ const char kHeader2[] = "header2";
+ const char kValue1[] = "value1";
+ const char kValue2[] = "value2";
+
+ SpdyHeaderBlock block;
+ block[kHeader1] = kValue1;
+ block[kHeader2] = kValue2;
+ SpdyControlFlags flags(CONTROL_FLAG_NONE);
+ scoped_ptr<SpdyFrame> syn_frame(
+ send_framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ 0, // priority
+ 0, // credential slot
+ flags,
+ true, // compress
+ &block));
+ EXPECT_TRUE(syn_frame.get() != NULL);
+
+ const char bytes[] = "this is a test test test test test!";
+ scoped_ptr<SpdyFrame> send_frame(
+ send_framer.CreateDataFrame(
+ 1, bytes, arraysize(bytes),
+ static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
+ EXPECT_TRUE(send_frame.get() != NULL);
+
+ // Run the inputs through the framer.
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ const unsigned char* data;
+ data = reinterpret_cast<const unsigned char*>(syn_frame->data());
+ for (size_t idx = 0; idx < syn_frame->size(); ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+ data = reinterpret_cast<const unsigned char*>(send_frame->data());
+ for (size_t idx = 0; idx < send_frame->size(); ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+ EXPECT_EQ(0, visitor.headers_frame_count_);
+ EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
+ EXPECT_EQ(0, visitor.fin_frame_count_);
+ EXPECT_EQ(0, visitor.fin_flag_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+ EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrame) {
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x12345678));
+
+ const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x12, 0x34, 0x56, 0x78
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x09, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x12, 0x34, 0x56, 0x78
+ };
+
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateDataFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "'hello' data frame, no FIN";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0d, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ const char bytes[] = "hello";
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, bytes, strlen(bytes), DATA_FLAG_NONE));
+ if (IsSpdy4()) {
+ CompareFrame(
+ kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(
+ kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+
+ SpdyDataIR data_ir(1);
+ data_ir.SetDataShallow(base::StringPiece(bytes, strlen(bytes)));
+ frame.reset(framer.SerializeDataFrameHeader(data_ir));
+ CompareCharArraysWithHexError(
+ kDescription,
+ reinterpret_cast<const unsigned char*>(frame->data()),
+ framer.GetDataFrameMinimumSize(),
+ IsSpdy4() ? kV4FrameData : kV3FrameData,
+ framer.GetDataFrameMinimumSize());
+ }
+
+ {
+ const char kDescription[] = "Data frame with negative data byte, no FIN";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 0xff
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x09, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "\xff", 1, DATA_FLAG_NONE));
+ if (IsSpdy4()) {
+ CompareFrame(
+ kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(
+ kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "'hello' data frame, with FIN";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0d, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "hello", 5, DATA_FLAG_FIN));
+ if (IsSpdy4()) {
+ CompareFrame(
+ kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(
+ kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "Empty data frame";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, "", 0, DATA_FLAG_NONE));
+ if (IsSpdy4()) {
+ CompareFrame(
+ kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(
+ kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "Data frame with max stream ID";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x01, 0x00, 0x00, 0x05,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0d, 0x00, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 'h', 'e', 'l', 'l',
+ 'o'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 0x7fffffff, "hello", 5, DATA_FLAG_FIN));
+ if (IsSpdy4()) {
+ CompareFrame(
+ kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(
+ kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ if (!IsSpdy4()) {
+ // This test does not apply to SPDY 4 because the max frame size is smaller
+ // than 4MB.
+ const char kDescription[] = "Large data frame";
+ const int kDataSize = 4 * 1024 * 1024; // 4 MB
+ const string kData(kDataSize, 'A');
+ const unsigned char kFrameHeader[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x40, 0x00, 0x00,
+ };
+
+ const int kFrameSize = arraysize(kFrameHeader) + kDataSize;
+ scoped_ptr<unsigned char[]> expected_frame_data(
+ new unsigned char[kFrameSize]);
+ memcpy(expected_frame_data.get(), kFrameHeader, arraysize(kFrameHeader));
+ memset(expected_frame_data.get() + arraysize(kFrameHeader), 'A', kDataSize);
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+ 1, kData.data(), kData.size(), DATA_FLAG_FIN));
+ CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize);
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ {
+ const char kDescription[] = "SYN_STREAM frame, lowest pri, slot 2, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kPri = IsSpdy2() ? 0xC0 : 0xE0;
+ const unsigned char kCre = IsSpdy2() ? 0 : 2;
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x20,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ kPri, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x2a,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ kPri, kCre, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'b',
+ 'a', 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x2e, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ kPri, kCre, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'b',
+ 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ framer.GetLowestPriority(),
+ kCre, // credential slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
+ "max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[std::string()] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x1D,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x27,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x2b, 0x01, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(0x7fffffff, // stream id
+ 0x7fffffff, // associated stream id
+ framer.GetHighestPriority(),
+ 0, // credential slot
+ CONTROL_FLAG_FIN,
+ false, // compress
+ &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame with a 0-length header val, high pri, FIN, "
+ "max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "";
+
+ const unsigned char kPri = IsSpdy2() ? 0x40 : 0x20;
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x1D,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ kPri, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x27,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ kPri, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x00
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x2b, 0x01, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff,
+ kPri, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x00, 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x00
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(0x7fffffff, // stream id
+ 0x7fffffff, // associated stream id
+ 1, // priority
+ 0, // credential slot
+ CONTROL_FLAG_FIN,
+ false, // compress
+ &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateSynStreamCompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+
+ {
+ const char kDescription[] =
+ "SYN_STREAM frame, low pri, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const SpdyPriority priority = IsSpdy2() ? 2 : 4;
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x36,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x38, 0xea,
+ 0xdf, 0xa2, 0x51, 0xb2,
+ 0x62, 0x60, 0x62, 0x60,
+ 0x4e, 0x4a, 0x2c, 0x62,
+ 0x60, 0x06, 0x08, 0xa0,
+ 0xb4, 0xfc, 0x7c, 0x80,
+ 0x00, 0x62, 0x60, 0x4e,
+ 0xcb, 0xcf, 0x67, 0x60,
+ 0x06, 0x08, 0xa0, 0xa4,
+ 0xc4, 0x22, 0x80, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x37,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x38, 0xEA,
+ 0xE3, 0xC6, 0xA7, 0xC2,
+ 0x02, 0xE5, 0x0E, 0x50,
+ 0xC2, 0x4B, 0x4A, 0x04,
+ 0xE5, 0x0B, 0x66, 0x80,
+ 0x00, 0x4A, 0xCB, 0xCF,
+ 0x07, 0x08, 0x20, 0x10,
+ 0x95, 0x96, 0x9F, 0x0F,
+ 0xA2, 0x00, 0x02, 0x28,
+ 0x29, 0xB1, 0x08, 0x20,
+ 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0xFF, 0xFF,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x3b, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x80, 0x00, 0x38, 0xea,
+ 0xe3, 0xc6, 0xa7, 0xc2,
+ 0x02, 0xe5, 0x0e, 0x50,
+ 0xc2, 0x4b, 0x4a, 0x04,
+ 0xe5, 0x0b, 0x66, 0x80,
+ 0x00, 0x4a, 0xcb, 0xcf,
+ 0x07, 0x08, 0x20, 0x10,
+ 0x95, 0x96, 0x9f, 0x0f,
+ 0xa2, 0x00, 0x02, 0x28,
+ 0x29, 0xb1, 0x08, 0x20,
+ 0x80, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(1, // stream id
+ 0, // associated stream id
+ priority,
+ 0, // credential slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateSynReplyUncompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ {
+ const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x24,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x28, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_REPLY frame with a 0-length header name, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[std::string()] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x21,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x25, 0x02, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "SYN_REPLY frame with a 0-length header val, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x21,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x00
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x25, 0x02, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x00
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateSynReplyCompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+
+ {
+ const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x32,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x38, 0xea,
+ 0xdf, 0xa2, 0x51, 0xb2,
+ 0x62, 0x60, 0x62, 0x60,
+ 0x4e, 0x4a, 0x2c, 0x62,
+ 0x60, 0x06, 0x08, 0xa0,
+ 0xb4, 0xfc, 0x7c, 0x80,
+ 0x00, 0x62, 0x60, 0x4e,
+ 0xcb, 0xcf, 0x67, 0x60,
+ 0x06, 0x08, 0xa0, 0xa4,
+ 0xc4, 0x22, 0x80, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x31,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x38, 0xea, 0xe3, 0xc6,
+ 0xa7, 0xc2, 0x02, 0xe5,
+ 0x0e, 0x50, 0xc2, 0x4b,
+ 0x4a, 0x04, 0xe5, 0x0b,
+ 0x66, 0x80, 0x00, 0x4a,
+ 0xcb, 0xcf, 0x07, 0x08,
+ 0x20, 0x10, 0x95, 0x96,
+ 0x9f, 0x0f, 0xa2, 0x00,
+ 0x02, 0x28, 0x29, 0xb1,
+ 0x08, 0x20, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x35, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x38, 0xea, 0xe3, 0xc6,
+ 0xa7, 0xc2, 0x02, 0xe5,
+ 0x0e, 0x50, 0xc2, 0x4b,
+ 0x4a, 0x04, 0xe5, 0x0b,
+ 0x66, 0x80, 0x00, 0x4a,
+ 0xcb, 0xcf, 0x07, 0x08,
+ 0x20, 0x10, 0x95, 0x96,
+ 0x9f, 0x0f, 0xa2, 0x00,
+ 0x02, 0x28, 0x29, 0xb1,
+ 0x08, 0x20, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+ 1, CONTROL_FLAG_NONE, true, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateRstStream) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "RST_STREAM frame";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max stream ID";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x03, 0x00,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(
+ 0x7FFFFFFF, RST_STREAM_PROTOCOL_ERROR));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "RST_STREAM frame with max status code";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x06,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x03, 0x00,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x06,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(
+ 0x7FFFFFFF, RST_STREAM_INTERNAL_ERROR));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateSettings) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "Network byte order SETTINGS frame";
+
+ uint32 kValue = 0x0a0b0c0d;
+ SpdySettingsFlags kFlags = static_cast<SpdySettingsFlags>(0x01);
+ SpdySettingsIds kId = static_cast<SpdySettingsIds>(0x020304);
+
+ SettingsMap settings;
+ settings[kId] = SettingsFlagsAndValue(kFlags, kValue);
+
+ EXPECT_EQ(kFlags, settings[kId].first);
+ EXPECT_EQ(kValue, settings[kId].second);
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x04, 0x03, 0x02, 0x01,
+ 0x0a, 0x0b, 0x0c, 0x0d,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04,
+ 0x0a, 0x0b, 0x0c, 0x0d,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x14, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x02, 0x03, 0x04,
+ 0x0a, 0x0b, 0x0c, 0x0d,
+ };
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "Basic SETTINGS frame";
+
+ SettingsMap settings;
+ AddSpdySettingFromWireFormat(
+ &settings, 0x00000000, 0x00000001); // 1st Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x01000001, 0x00000002); // 2nd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x02000002, 0x00000003); // 3rd Setting
+ AddSpdySettingFromWireFormat(
+ &settings, 0x03000003, 0xff000004); // 4th Setting
+
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x24,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00, // 1st Setting
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, // 2nd Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x02, 0x00, 0x00, 0x02, // 3rd Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x03, 0x00, 0x00, 0x03, // 4th Setting
+ 0xff, 0x00, 0x00, 0x04,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x2c, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00, // 1st Setting
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x01, // 2nd Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x02, 0x00, 0x00, 0x02, // 3rd Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x03, 0x00, 0x00, 0x03, // 4th Setting
+ 0xff, 0x00, 0x00, 0x04,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "Empty SETTINGS frame";
+
+ SettingsMap settings;
+
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, CreatePingFrame) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "PING frame";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x06,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x12, 0x34, 0x56, 0x78,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x12, 0x34, 0x56, 0x78,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreatePingFrame(0x12345678u));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateGoAway) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "GOAWAY frame";
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x10, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0, GOAWAY_OK));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "GOAWAY frame with max stream ID, status";
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x10, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF,
+ GOAWAY_INTERNAL_ERROR));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, CreateHeadersUncompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ {
+ const char kDescription[] = "HEADERS frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x24,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x28, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'b', 'a', 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 1, CONTROL_FLAG_NONE, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header name, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers[std::string()] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x03, 'f', 'o', 'o',
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x01, 0x00, 0x00, 0x21,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x25, 0x08, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'b', 'a',
+ 'r'
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] =
+ "HEADERS frame with a 0-length header val, FIN, max stream ID";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x01, 0x00, 0x00, 0x19,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x03, 'b', 'a',
+ 'r', 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x03,
+ 'f', 'o', 'o', 0x00,
+ 0x00
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x01, 0x00, 0x00, 0x21,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x00
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x25, 0x08, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x00
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateHeadersCompressed) {
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+
+ {
+ const char kDescription[] = "HEADERS frame, no FIN";
+
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x32,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x38, 0xea,
+ 0xdf, 0xa2, 0x51, 0xb2,
+ 0x62, 0x60, 0x62, 0x60,
+ 0x4e, 0x4a, 0x2c, 0x62,
+ 0x60, 0x06, 0x08, 0xa0,
+ 0xb4, 0xfc, 0x7c, 0x80,
+ 0x00, 0x62, 0x60, 0x4e,
+ 0xcb, 0xcf, 0x67, 0x60,
+ 0x06, 0x08, 0xa0, 0xa4,
+ 0xc4, 0x22, 0x80, 0x00,
+ 0x02, 0x00, 0x00, 0x00,
+ 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x31,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x38, 0xea, 0xe3, 0xc6,
+ 0xa7, 0xc2, 0x02, 0xe5,
+ 0x0e, 0x50, 0xc2, 0x4b,
+ 0x4a, 0x04, 0xe5, 0x0b,
+ 0x66, 0x80, 0x00, 0x4a,
+ 0xcb, 0xcf, 0x07, 0x08,
+ 0x20, 0x10, 0x95, 0x96,
+ 0x9f, 0x0f, 0xa2, 0x00,
+ 0x02, 0x28, 0x29, 0xb1,
+ 0x08, 0x20, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x35, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x38, 0xea, 0xe3, 0xc6,
+ 0xa7, 0xc2, 0x02, 0xe5,
+ 0x0e, 0x50, 0xc2, 0x4b,
+ 0x4a, 0x04, 0xe5, 0x0b,
+ 0x66, 0x80, 0x00, 0x4a,
+ 0xcb, 0xcf, 0x07, 0x08,
+ 0x20, 0x10, 0x95, 0x96,
+ 0x9f, 0x0f, 0xa2, 0x00,
+ 0x02, 0x28, 0x29, 0xb1,
+ 0x08, 0x20, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+ 1, CONTROL_FLAG_NONE, true, &headers));
+ if (IsSpdy2()) {
+ CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ }
+ }
+}
+#endif // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateWindowUpdate) {
+ SpdyFramer framer(spdy_version_);
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x09, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateWindowUpdate(1, 1));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x09, 0x00,
+ 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+
+ {
+ const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x09,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x0c, 0x09, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x7f, 0xff, 0xff, 0xff,
+ };
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF));
+ if (IsSpdy4()) {
+ CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
+ } else {
+ CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, SerializeBlocked) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ SpdyFramer framer(spdy_version_);
+
+ const char kDescription[] = "BLOCKED frame";
+ const unsigned char kFrameData[] = {
+ 0x00, 0x08, 0x0b, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ SpdyBlockedIR blocked_ir(0);
+ scoped_ptr<SpdySerializedFrame> frame(framer.SerializeFrame(blocked_ir));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseUncompressed) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+
+ const char kDescription[] = "PUSH_PROMISE frame";
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x00, 0x2C, 0x0C, 0x00, // length = 44, type = 12, flags = 0
+ 0x00, 0x00, 0x00, 0x2A, // stream id = 42
+ 0x00, 0x00, 0x00, 0x39, // promised stream id = 57
+ 0x00, 0x00, 0x00, 0x02, // start of uncompressed header block
+ 0x00, 0x00, 0x00, 0x03,
+ 'b', 'a', 'r', 0x00,
+ 0x00, 0x00, 0x03, 'f',
+ 'o', 'o', 0x00, 0x00,
+ 0x00, 0x03, 'f', 'o',
+ 'o', 0x00, 0x00, 0x00,
+ 0x03, 'b', 'a', 'r' // end of uncompressed header block
+ };
+
+ scoped_ptr<SpdySerializedFrame> frame(framer.CreatePushPromise(
+ 42, 57, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, CreatePushPromiseCompressed) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(true);
+
+ const char kDescription[] = "PUSH_PROMISE frame";
+ SpdyHeaderBlock headers;
+ headers["bar"] = "foo";
+ headers["foo"] = "bar";
+
+ const unsigned char kFrameData[] = {
+ 0x00, 0x39, 0x0C, 0x00, // length = 57, type = 12, flags = 0
+ 0x00, 0x00, 0x00, 0x2A, // stream id = 42
+ 0x00, 0x00, 0x00, 0x39, // promised stream id = 57
+ 0x38, 0xea, 0xe3, 0xc6, // start of compressed header block
+ 0xa7, 0xc2, 0x02, 0xe5,
+ 0x0e, 0x50, 0xc2, 0x4b,
+ 0x4a, 0x04, 0xe5, 0x0b,
+ 0x66, 0x80, 0x00, 0x4a,
+ 0xcb, 0xcf, 0x07, 0x08,
+ 0x20, 0x10, 0x95, 0x96,
+ 0x9f, 0x0f, 0xa2, 0x00,
+ 0x02, 0x28, 0x29, 0xb1,
+ 0x08, 0x20, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xff,
+ 0xff // end of compressed header block
+ };
+
+ scoped_ptr<SpdySerializedFrame> frame(framer.CreatePushPromise(
+ 42, 57, &headers));
+ CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedSynStreamHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "vv";
+ headers["bb"] = "ww";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedSynReplyHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynReply(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateHeaders(1, // stream_id
+ CONTROL_FLAG_NONE,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ // control_frame_header_data_count_ depends on the random sequence
+ // produced by rand(), so adding, removing or running single tests
+ // alters this value. The best we can do is assert that it happens
+ // at least twice.
+ EXPECT_LE(2, visitor.control_frame_header_data_count_);
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) {
+ SpdyHeaderBlock headers;
+ headers["alpha"] = "beta";
+ headers["gamma"] = "delta";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateHeaders(1, // stream_id
+ CONTROL_FLAG_FIN,
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1, visitor.headers_frame_count_);
+ // control_frame_header_data_count_ depends on the random sequence
+ // produced by rand(), so adding, removing or running single tests
+ // alters this value. The best we can do is assert that it happens
+ // at least twice.
+ EXPECT_LE(2, visitor.control_frame_header_data_count_);
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(SpdyFramerTest, ControlFrameAtMaxSizeLimit) {
+ // First find the size of the header value in order to just reach the control
+ // frame max size.
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ SpdyHeaderBlock headers;
+ headers["aa"] = "";
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ const size_t kBigValueSize =
+ framer.GetControlFrameBufferMaxSize() - control_frame->size();
+
+ // Create a frame at exatly that size.
+ string big_value(kBigValueSize, 'x');
+ headers["aa"] = big_value.c_str();
+ control_frame.reset(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ EXPECT_EQ(framer.GetControlFrameBufferMaxSize(), control_frame->size());
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_TRUE(visitor.header_buffer_valid_);
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(1, visitor.syn_frame_count_);
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
+ EXPECT_LT(kBigValueSize, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ControlFrameTooLarge) {
+ // First find the size of the header value in order to just reach the control
+ // frame max size.
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ SpdyHeaderBlock headers;
+ headers["aa"] = "";
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ const size_t kBigValueSize =
+ framer.GetControlFrameBufferMaxSize() - control_frame->size() + 1;
+
+ // Create a frame at exatly that size.
+ string big_value(kBigValueSize, 'x');
+ headers["aa"] = big_value.c_str();
+ control_frame.reset(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ EXPECT_EQ(framer.GetControlFrameBufferMaxSize() + 1,
+ control_frame->size());
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_FALSE(visitor.header_buffer_valid_);
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE,
+ visitor.framer_.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ EXPECT_EQ(0, visitor.syn_frame_count_);
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+// Check that the framer stops delivering header data chunks once the visitor
+// declares it doesn't want any more. This is important to guard against
+// "zip bomb" types of attacks.
+TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) {
+ SpdyHeaderBlock headers;
+ const size_t kHeaderBufferChunks = 4;
+ const size_t kHeaderBufferSize =
+ TestSpdyVisitor::header_data_chunk_max_size() * kHeaderBufferChunks;
+ const size_t kBigValueSize = kHeaderBufferSize * 2;
+ string big_value(kBigValueSize, 'x');
+ headers["aa"] = big_value.c_str();
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_FIN, // half close
+ true, // compress
+ &headers));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.set_header_buffer_size(kHeaderBufferSize);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_FALSE(visitor.header_buffer_valid_);
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE,
+ visitor.framer_.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+
+ // The framer should have stoped delivering chunks after the visitor
+ // signaled "stop" by returning false from OnControlFrameHeaderData().
+ //
+ // control_frame_header_data_count_ depends on the random sequence
+ // produced by rand(), so adding, removing or running single tests
+ // alters this value. The best we can do is assert that it happens
+ // at least kHeaderBufferChunks + 1.
+ EXPECT_LE(kHeaderBufferChunks + 1,
+ static_cast<unsigned>(visitor.control_frame_header_data_count_));
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
+
+ // The framer should not have sent half-close to the visitor.
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, DecompressCorruptHeaderBlock) {
+ SpdyHeaderBlock headers;
+ headers["aa"] = "alpha beta gamma delta";
+ SpdyFramer framer(spdy_version_);
+ framer.set_enable_compression(false);
+ // Construct a SYN_STREAM control frame without compressing the header block,
+ // and have the framer try to decompress it. This will cause the framer to
+ // deal with a decompression error.
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateSynStream(1, // stream_id
+ 0, // associated_stream_id
+ 1, // priority
+ 0, // credential_slot
+ CONTROL_FLAG_NONE,
+ false, // compress
+ &headers));
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(SpdyFramer::SPDY_DECOMPRESS_FAILURE, visitor.framer_.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ EXPECT_EQ(0u, visitor.header_buffer_length_);
+}
+
+TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) {
+ // Create a GoAway frame that has a few extra bytes at the end.
+ // We create enough overhead to overflow the framer's control frame buffer.
+ ASSERT_GE(250u, SpdyFramer::kControlFrameBufferSize);
+ const unsigned char length = 1 + SpdyFramer::kControlFrameBufferSize;
+ const unsigned char kV3FrameData[] = { // Also applies for V2.
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, length,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, static_cast<uint8>(length + 4), 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ SpdyFramer framer(spdy_version_);
+ const size_t pad_length =
+ length + framer.GetControlFrameHeaderSize() -
+ (IsSpdy4() ? sizeof(kV4FrameData) : sizeof(kV3FrameData));
+ string pad('A', pad_length);
+ TestSpdyVisitor visitor(spdy_version_);
+
+ if (IsSpdy4()) {
+ visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData));
+ } else {
+ visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+ }
+ visitor.SimulateInFramer(
+ reinterpret_cast<const unsigned char*>(pad.c_str()),
+ pad.length());
+
+ EXPECT_EQ(1, visitor.error_count_); // This generated an error.
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME,
+ visitor.framer_.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ EXPECT_EQ(0, visitor.goaway_count_); // Frame not parsed.
+}
+
+TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) {
+ SpdyFramer framer(spdy_version_);
+ SettingsMap settings;
+ scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+ SetFrameLength(control_frame.get(), 0, spdy_version_);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ framer.GetControlFrameHeaderSize());
+ // Should generate an error, since zero-len settings frames are unsupported.
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames with invalid length.
+TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) {
+ SpdyFramer framer(spdy_version_);
+ SettingsMap settings;
+ // Add a setting to pad the frame so that we don't get a buffer overflow when
+ // calling SimulateInFramer() below.
+ settings[SETTINGS_UPLOAD_BANDWIDTH] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, 0x00000002);
+ scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+ const size_t kNewLength = 5;
+ SetFrameLength(control_frame.get(), kNewLength, spdy_version_);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ framer.GetControlFrameHeaderSize() + kNewLength);
+ // Should generate an error, since zero-len settings frames are unsupported.
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames larger than the frame buffer size.
+TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) {
+ SpdyFramer framer(spdy_version_);
+ SettingsMap settings;
+ SpdySettingsFlags flags = SETTINGS_FLAG_PLEASE_PERSIST;
+ settings[SETTINGS_UPLOAD_BANDWIDTH] =
+ SettingsFlagsAndValue(flags, 0x00000002);
+ settings[SETTINGS_DOWNLOAD_BANDWIDTH] =
+ SettingsFlagsAndValue(flags, 0x00000003);
+ settings[SETTINGS_ROUND_TRIP_TIME] = SettingsFlagsAndValue(flags, 0x00000004);
+ scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+ EXPECT_LT(SpdyFramer::kControlFrameBufferSize,
+ control_frame->size());
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+
+ // Read all at once.
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(settings.size(), static_cast<unsigned>(visitor.setting_count_));
+
+ // Read data in small chunks.
+ size_t framed_data = 0;
+ size_t unframed_data = control_frame->size();
+ size_t kReadChunkSize = 5; // Read five bytes at a time.
+ while (unframed_data > 0) {
+ size_t to_read = min(kReadChunkSize, unframed_data);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data() + framed_data),
+ to_read);
+ unframed_data -= to_read;
+ framed_data += to_read;
+ }
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(settings.size() * 2, static_cast<unsigned>(visitor.setting_count_));
+}
+
+// Tests handling of SETTINGS frame with duplicate entries.
+TEST_P(SpdyFramerTest, ReadDuplicateSettings) {
+ SpdyFramer framer(spdy_version_);
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x00, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x00, // 2nd (duplicate) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x03, 0x00, 0x00, 0x00, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, // 2nd (duplicate) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x03, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x24, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, // 2nd (duplicate) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x03, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+ } else if (IsSpdy3()) {
+ visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+ } else {
+ visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData));
+ }
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(1, visitor.setting_count_);
+}
+
+// Tests handling of SETTINGS frame with entries out of order.
+TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) {
+ SpdyFramer framer(spdy_version_);
+
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x02, 0x00, 0x00, 0x00, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x01, 0x00, 0x00, 0x00, // 2nd (out of order) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x03, 0x00, 0x00, 0x00, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x1C,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x02, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, // 2nd (out of order) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x01, 0x03, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x24, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x02, // 1st Setting
+ 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, // 2nd (out of order) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x01, 0x03, // 3rd (unprocessed) Setting
+ 0x00, 0x00, 0x00, 0x03,
+ };
+
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ if (IsSpdy2()) {
+ visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+ } else if (IsSpdy3()) {
+ visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+ } else {
+ visitor.SimulateInFramer(kV4FrameData, sizeof(kV4FrameData));
+ }
+ EXPECT_EQ(1, visitor.error_count_);
+ EXPECT_EQ(1, visitor.setting_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadWindowUpdate) {
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateWindowUpdate(1, 2));
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(1u, visitor.last_window_update_stream_);
+ EXPECT_EQ(2u, visitor.last_window_update_delta_);
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrame) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(control_frame->data()),
+ control_frame->size());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(control_frame->size() - framer.GetControlFrameHeaderSize(),
+ visitor.credential_buffer_length_);
+ EXPECT_EQ(credential.slot, visitor.credential_.slot);
+ EXPECT_EQ(credential.proof, visitor.credential_.proof);
+ EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size());
+ for (size_t i = 0; i < credential.certs.size(); i++) {
+ EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]);
+ }
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrameOneByteAtATime) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ // Read one byte at a time to make sure we handle edge cases
+ unsigned char* data =
+ reinterpret_cast<unsigned char*>(control_frame->data());
+ for (size_t idx = 0; idx < control_frame->size(); ++idx) {
+ visitor.SimulateInFramer(data + idx, 1);
+ ASSERT_EQ(0, visitor.error_count_);
+ }
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(control_frame->size() - framer.GetControlFrameHeaderSize(),
+ visitor.credential_buffer_length_);
+ EXPECT_EQ(credential.slot, visitor.credential_.slot);
+ EXPECT_EQ(credential.proof, visitor.credential_.proof);
+ EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size());
+ for (size_t i = 0; i < credential.certs.size(); i++) {
+ EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]);
+ }
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrameWithNoPayload) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ SetFrameLength(control_frame.get(), 0, spdy_version_);
+ unsigned char* data =
+ reinterpret_cast<unsigned char*>(control_frame->data());
+ visitor.SimulateInFramer(data, framer.GetControlFrameHeaderSize());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptProof) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ unsigned char* data =
+ reinterpret_cast<unsigned char*>(control_frame->data());
+ size_t offset = framer.GetControlFrameHeaderSize() + 4;
+ data[offset] = 0xFF; // Proof length is past the end of the frame
+ visitor.SimulateInFramer(
+ data, control_frame->size());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptCertificate) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> control_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(control_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ unsigned char* data =
+ reinterpret_cast<unsigned char*>(control_frame->data());
+ size_t offset = framer.GetCredentialMinimumSize() + 1;
+ data[offset] = 0xFF; // Proof length is past the end of the frame
+ visitor.SimulateInFramer(
+ data, control_frame->size());
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+// Regression test for parsing issue found in b/8278897.
+TEST_P(SpdyFramerTest, ReadCredentialFrameFollowedByAnotherFrame) {
+ SpdyCredential credential;
+ credential.slot = 3;
+ credential.proof = "proof";
+ credential.certs.push_back("a cert");
+ credential.certs.push_back("another cert");
+ credential.certs.push_back("final cert");
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> credential_frame(
+ framer.CreateCredentialFrame(credential));
+ EXPECT_TRUE(credential_frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ string multiple_frame_data(credential_frame->data(),
+ credential_frame->size());
+ scoped_ptr<SpdyFrame> goaway_frame(framer.CreateGoAway(0, GOAWAY_OK));
+ multiple_frame_data.append(string(goaway_frame->data(),
+ goaway_frame->size()));
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned const char*>(multiple_frame_data.data()),
+ multiple_frame_data.length());
+ EXPECT_EQ(0, visitor.error_count_);
+ EXPECT_EQ(credential_frame->size() - framer.GetControlFrameHeaderSize(),
+ visitor.credential_buffer_length_);
+ EXPECT_EQ(credential.slot, visitor.credential_.slot);
+ EXPECT_EQ(credential.proof, visitor.credential_.proof);
+ EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size());
+ for (size_t i = 0; i < credential.certs.size(); i++) {
+ EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]);
+ }
+}
+
+TEST_P(SpdyFramerTest, ReadCompressedPushPromise) {
+ if (spdy_version_ < 4) {
+ return;
+ }
+
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ headers["bar"] = "foofoo";
+ SpdyFramer framer(spdy_version_);
+ scoped_ptr<SpdyFrame> frame(framer.CreatePushPromise(42, 57, &headers));
+ EXPECT_TRUE(frame.get() != NULL);
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = true;
+ visitor.SimulateInFramer(
+ reinterpret_cast<unsigned char*>(frame->data()),
+ frame->size());
+ EXPECT_EQ(42u, visitor.last_push_promise_stream_);
+ EXPECT_EQ(57u, visitor.last_push_promise_promised_stream_);
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_P(SpdyFramerTest, ReadGarbage) {
+ SpdyFramer framer(spdy_version_);
+ unsigned char garbage_frame[256];
+ memset(garbage_frame, ~0, sizeof(garbage_frame));
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageWithValidVersion) {
+ if (IsSpdy4()) {
+ // Not valid for SPDY 4 since there is no version field.
+ return;
+ }
+ SpdyFramer framer(spdy_version_);
+ const unsigned char kFrameData[] = {
+ 0x80, spdy_version_ch_, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ };
+ TestSpdyVisitor visitor(spdy_version_);
+ visitor.use_compression_ = false;
+ visitor.SimulateInFramer(kFrameData, arraysize(kFrameData));
+ EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, SizesTest) {
+ SpdyFramer framer(spdy_version_);
+ EXPECT_EQ(8u, framer.GetDataFrameMinimumSize());
+ if (IsSpdy4()) {
+ EXPECT_EQ(8u, framer.GetSynReplyMinimumSize());
+ EXPECT_EQ(12u, framer.GetRstStreamSize());
+ EXPECT_EQ(12u, framer.GetSettingsMinimumSize());
+ EXPECT_EQ(12u, framer.GetPingSize());
+ EXPECT_EQ(16u, framer.GetGoAwaySize());
+ EXPECT_EQ(8u, framer.GetHeadersMinimumSize());
+ EXPECT_EQ(12u, framer.GetWindowUpdateSize());
+ EXPECT_EQ(10u, framer.GetCredentialMinimumSize());
+ EXPECT_EQ(8u, framer.GetBlockedSize());
+ EXPECT_EQ(12u, framer.GetPushPromiseMinimumSize());
+ EXPECT_EQ(8u, framer.GetFrameMinimumSize());
+ EXPECT_EQ(65535u, framer.GetFrameMaximumSize());
+ EXPECT_EQ(65527u, framer.GetDataFrameMaximumPayload());
+ } else {
+ EXPECT_EQ(8u, framer.GetControlFrameHeaderSize());
+ EXPECT_EQ(18u, framer.GetSynStreamMinimumSize());
+ EXPECT_EQ(IsSpdy2() ? 14u : 12u, framer.GetSynReplyMinimumSize());
+ EXPECT_EQ(16u, framer.GetRstStreamSize());
+ EXPECT_EQ(12u, framer.GetSettingsMinimumSize());
+ EXPECT_EQ(12u, framer.GetPingSize());
+ EXPECT_EQ(IsSpdy2() ? 12u : 16u, framer.GetGoAwaySize());
+ EXPECT_EQ(IsSpdy2() ? 14u : 12u, framer.GetHeadersMinimumSize());
+ EXPECT_EQ(16u, framer.GetWindowUpdateSize());
+ EXPECT_EQ(10u, framer.GetCredentialMinimumSize());
+ EXPECT_EQ(8u, framer.GetFrameMinimumSize());
+ EXPECT_EQ(16777215u, framer.GetFrameMaximumSize());
+ EXPECT_EQ(16777207u, framer.GetDataFrameMaximumPayload());
+ }
+}
+
+TEST_P(SpdyFramerTest, StateToStringTest) {
+ EXPECT_STREQ("ERROR",
+ SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR));
+ EXPECT_STREQ("AUTO_RESET",
+ SpdyFramer::StateToString(SpdyFramer::SPDY_AUTO_RESET));
+ EXPECT_STREQ("RESET",
+ SpdyFramer::StateToString(SpdyFramer::SPDY_RESET));
+ EXPECT_STREQ("READING_COMMON_HEADER",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_READING_COMMON_HEADER));
+ EXPECT_STREQ("CONTROL_FRAME_PAYLOAD",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_CONTROL_FRAME_PAYLOAD));
+ EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_IGNORE_REMAINING_PAYLOAD));
+ EXPECT_STREQ("FORWARD_STREAM_FRAME",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_FORWARD_STREAM_FRAME));
+ EXPECT_STREQ("SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK));
+ EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_CONTROL_FRAME_HEADER_BLOCK));
+ EXPECT_STREQ("SPDY_CREDENTIAL_FRAME_PAYLOAD",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD));
+ EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD));
+ EXPECT_STREQ("UNKNOWN_STATE",
+ SpdyFramer::StateToString(
+ SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD + 1));
+}
+
+TEST_P(SpdyFramerTest, ErrorCodeToStringTest) {
+ EXPECT_STREQ("NO_ERROR",
+ SpdyFramer::ErrorCodeToString(SpdyFramer::SPDY_NO_ERROR));
+ EXPECT_STREQ("INVALID_CONTROL_FRAME",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_INVALID_CONTROL_FRAME));
+ EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE));
+ EXPECT_STREQ("ZLIB_INIT_FAILURE",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_ZLIB_INIT_FAILURE));
+ EXPECT_STREQ("UNSUPPORTED_VERSION",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_UNSUPPORTED_VERSION));
+ EXPECT_STREQ("DECOMPRESS_FAILURE",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_DECOMPRESS_FAILURE));
+ EXPECT_STREQ("COMPRESS_FAILURE",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_COMPRESS_FAILURE));
+ EXPECT_STREQ("SPDY_INVALID_DATA_FRAME_FLAGS",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS));
+ EXPECT_STREQ("SPDY_INVALID_CONTROL_FRAME_FLAGS",
+ SpdyFramer::ErrorCodeToString(
+ SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS));
+ EXPECT_STREQ("UNKNOWN_ERROR",
+ SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR));
+}
+
+TEST_P(SpdyFramerTest, StatusCodeToStringTest) {
+ EXPECT_STREQ("INVALID",
+ SpdyFramer::StatusCodeToString(RST_STREAM_INVALID));
+ EXPECT_STREQ("PROTOCOL_ERROR",
+ SpdyFramer::StatusCodeToString(RST_STREAM_PROTOCOL_ERROR));
+ EXPECT_STREQ("INVALID_STREAM",
+ SpdyFramer::StatusCodeToString(RST_STREAM_INVALID_STREAM));
+ EXPECT_STREQ("REFUSED_STREAM",
+ SpdyFramer::StatusCodeToString(RST_STREAM_REFUSED_STREAM));
+ EXPECT_STREQ("UNSUPPORTED_VERSION",
+ SpdyFramer::StatusCodeToString(RST_STREAM_UNSUPPORTED_VERSION));
+ EXPECT_STREQ("CANCEL",
+ SpdyFramer::StatusCodeToString(RST_STREAM_CANCEL));
+ EXPECT_STREQ("INTERNAL_ERROR",
+ SpdyFramer::StatusCodeToString(RST_STREAM_INTERNAL_ERROR));
+ EXPECT_STREQ("FLOW_CONTROL_ERROR",
+ SpdyFramer::StatusCodeToString(RST_STREAM_FLOW_CONTROL_ERROR));
+ EXPECT_STREQ("UNKNOWN_STATUS",
+ SpdyFramer::StatusCodeToString(RST_STREAM_NUM_STATUS_CODES));
+}
+
+TEST_P(SpdyFramerTest, FrameTypeToStringTest) {
+ EXPECT_STREQ("DATA",
+ SpdyFramer::FrameTypeToString(DATA));
+ EXPECT_STREQ("SYN_STREAM",
+ SpdyFramer::FrameTypeToString(SYN_STREAM));
+ EXPECT_STREQ("SYN_REPLY",
+ SpdyFramer::FrameTypeToString(SYN_REPLY));
+ EXPECT_STREQ("RST_STREAM",
+ SpdyFramer::FrameTypeToString(RST_STREAM));
+ EXPECT_STREQ("SETTINGS",
+ SpdyFramer::FrameTypeToString(SETTINGS));
+ EXPECT_STREQ("NOOP",
+ SpdyFramer::FrameTypeToString(NOOP));
+ EXPECT_STREQ("PING",
+ SpdyFramer::FrameTypeToString(PING));
+ EXPECT_STREQ("GOAWAY",
+ SpdyFramer::FrameTypeToString(GOAWAY));
+ EXPECT_STREQ("HEADERS",
+ SpdyFramer::FrameTypeToString(HEADERS));
+ EXPECT_STREQ("WINDOW_UPDATE",
+ SpdyFramer::FrameTypeToString(WINDOW_UPDATE));
+ EXPECT_STREQ("PUSH_PROMISE",
+ SpdyFramer::FrameTypeToString(PUSH_PROMISE));
+ EXPECT_STREQ("CREDENTIAL",
+ SpdyFramer::FrameTypeToString(CREDENTIAL));
+}
+
+TEST_P(SpdyFramerTest, CatchProbableHttpResponse) {
+ if (IsSpdy4()) {
+ // TODO(hkhalil): catch probable HTTP response in SPDY 4?
+ return;
+ }
+ {
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnError(_));
+ framer.ProcessInput("HTTP/1.1", 8);
+ EXPECT_TRUE(framer.probable_http_response());
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ {
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnError(_));
+ framer.ProcessInput("HTTP/1.0", 8);
+ EXPECT_TRUE(framer.probable_http_response());
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+}
+
+TEST_P(SpdyFramerTest, DataFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateDataFrame(1, "hello", 5, DATA_FLAG_NONE));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags & ~DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, flags & DATA_FLAG_FIN));
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5, false));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true));
+ }
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags & ~DATA_FLAG_FIN) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, SynStreamFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+ framer.set_debug_visitor(&debug_visitor);
+
+ EXPECT_CALL(debug_visitor, OnSendCompressedFrame(8, SYN_STREAM, _, _));
+
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynStream(8, 3, 1, 0, CONTROL_FLAG_NONE, true, &headers));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags & ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(8, SYN_STREAM, _));
+ EXPECT_CALL(visitor, OnSynStream(8, 3, 1, 0, flags & CONTROL_FLAG_FIN,
+ flags & CONTROL_FLAG_UNIDIRECTIONAL));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(8, _, _))
+ .WillRepeatedly(testing::Return(true));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true));
+ }
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags & ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, SynReplyFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateSynReply(37, CONTROL_FLAG_NONE, true, &headers));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags & ~CONTROL_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnSynReply(37, flags & CONTROL_FLAG_FIN));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(37, _, _))
+ .WillRepeatedly(testing::Return(true));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true));
+ }
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags & ~CONTROL_FLAG_FIN) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, RstStreamFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(13, RST_STREAM_CANCEL));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnRstStream(13, RST_STREAM_CANCEL));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, SettingsFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SettingsMap settings;
+ settings[SETTINGS_UPLOAD_BANDWIDTH] =
+ std::make_pair(SETTINGS_FLAG_NONE, 54321);
+ scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags & ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnSettings(
+ flags & SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS));
+ EXPECT_CALL(visitor, OnSetting(SETTINGS_UPLOAD_BANDWIDTH,
+ SETTINGS_FLAG_NONE, 54321));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags & ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, GoawayFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(97, GOAWAY_OK));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnGoAway(97, GOAWAY_OK));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, HeadersFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ scoped_ptr<SpdyFrame> frame(
+ framer.CreateHeaders(57, CONTROL_FLAG_NONE, true, &headers));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags & ~CONTROL_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnHeaders(57, flags & CONTROL_FLAG_FIN));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(57, _, _))
+ .WillRepeatedly(testing::Return(true));
+ if (flags & DATA_FLAG_FIN) {
+ EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, true));
+ }
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags & ~CONTROL_FLAG_FIN) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, PingFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ scoped_ptr<SpdyFrame> frame(framer.CreatePingFrame(42));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnPing(42));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(4, 1024));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnWindowUpdate(4, 1024));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, PushPromiseFrameFlags) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<net::test::MockVisitor> visitor;
+ testing::StrictMock<net::test::MockDebugVisitor> debug_visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+ framer.set_debug_visitor(&debug_visitor);
+
+ EXPECT_CALL(debug_visitor, OnSendCompressedFrame(42, PUSH_PROMISE, _, _));
+
+ SpdyHeaderBlock headers;
+ headers["foo"] = "bar";
+ scoped_ptr<SpdyFrame> frame(framer.CreatePushPromise(42, 57, &headers));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(42, PUSH_PROMISE, _));
+ EXPECT_CALL(visitor, OnPushPromise(42, 57));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(42, _, _))
+ .WillRepeatedly(testing::Return(true));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, CredentialFrameFlags) {
+ for (int flags = 0; flags < 256; ++flags) {
+ SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ SpdyCredential credential;
+ scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential));
+ SetFrameFlags(frame.get(), flags, spdy_version_);
+
+ if (flags != 0) {
+ EXPECT_CALL(visitor, OnError(_));
+ } else {
+ EXPECT_CALL(visitor, OnCredentialFrameData(_, _))
+ .WillRepeatedly(testing::Return(true));
+ }
+
+ framer.ProcessInput(frame->data(), frame->size());
+ if (flags != 0) {
+ EXPECT_EQ(SpdyFramer::SPDY_ERROR, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME_FLAGS,
+ framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ } else {
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+ }
+ }
+}
+
+TEST_P(SpdyFramerTest, EmptySynStream) {
+ SpdyHeaderBlock headers;
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ testing::StrictMock<test::MockDebugVisitor> debug_visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+ framer.set_debug_visitor(&debug_visitor);
+
+ EXPECT_CALL(debug_visitor, OnSendCompressedFrame(1, SYN_STREAM, _, _));
+
+ scoped_ptr<SpdyFrame>
+ frame(framer.CreateSynStream(1, 0, 1, 0, CONTROL_FLAG_NONE, true,
+ &headers));
+ // Adjust size to remove the name/value block.
+ if (IsSpdy4()) {
+ SetFrameLength(
+ frame.get(),
+ framer.GetSynStreamMinimumSize(),
+ spdy_version_);
+ } else {
+ SetFrameLength(
+ frame.get(),
+ framer.GetSynStreamMinimumSize() - framer.GetControlFrameHeaderSize(),
+ spdy_version_);
+ }
+
+ EXPECT_CALL(debug_visitor, OnReceiveCompressedFrame(1, SYN_STREAM, _));
+ EXPECT_CALL(visitor, OnSynStream(1, 0, 1, 0, false, false));
+ EXPECT_CALL(visitor, OnControlFrameHeaderData(1, NULL, 0));
+
+ framer.ProcessInput(frame->data(), framer.GetSynStreamMinimumSize());
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+TEST_P(SpdyFramerTest, SettingsFlagsAndId) {
+ const uint32 kId = 0x020304;
+ const uint32 kFlags = 0x01;
+ const uint32 kWireFormat = htonl(IsSpdy2() ? 0x04030201 : 0x01020304);
+
+ SettingsFlagsAndId id_and_flags =
+ SettingsFlagsAndId::FromWireFormat(spdy_version_, kWireFormat);
+ EXPECT_EQ(kId, id_and_flags.id());
+ EXPECT_EQ(kFlags, id_and_flags.flags());
+ EXPECT_EQ(kWireFormat, id_and_flags.GetWireFormat(spdy_version_));
+}
+
+// Test handling of a RST_STREAM with out-of-bounds status codes.
+TEST_P(SpdyFramerTest, RstStreamStatusBounds) {
+ DCHECK_GE(0xff, RST_STREAM_NUM_STATUS_CODES);
+
+ const unsigned char kV3RstStreamInvalid[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, RST_STREAM_INVALID
+ };
+ const unsigned char kV4RstStreamInvalid[] = {
+ 0x00, 0x0c, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, RST_STREAM_INVALID
+ };
+
+ const unsigned char kV3RstStreamNumStatusCodes[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, RST_STREAM_NUM_STATUS_CODES
+ };
+ const unsigned char kV4RstStreamNumStatusCodes[] = {
+ 0x00, 0x0c, 0x03, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, RST_STREAM_NUM_STATUS_CODES
+ };
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnRstStream(1, RST_STREAM_INVALID));
+ if (IsSpdy4()) {
+ framer.ProcessInput(reinterpret_cast<const char*>(kV4RstStreamInvalid),
+ arraysize(kV4RstStreamInvalid));
+ } else {
+ framer.ProcessInput(reinterpret_cast<const char*>(kV3RstStreamInvalid),
+ arraysize(kV3RstStreamInvalid));
+ }
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+
+ EXPECT_CALL(visitor, OnRstStream(1, RST_STREAM_INVALID));
+ if (IsSpdy4()) {
+ framer.ProcessInput(
+ reinterpret_cast<const char*>(kV4RstStreamNumStatusCodes),
+ arraysize(kV4RstStreamNumStatusCodes));
+ } else {
+ framer.ProcessInput(
+ reinterpret_cast<const char*>(kV3RstStreamNumStatusCodes),
+ arraysize(kV3RstStreamNumStatusCodes));
+ }
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+// Tests handling of a GOAWAY frame with out-of-bounds stream ID.
+TEST_P(SpdyFramerTest, GoAwayStreamIdBounds) {
+ const unsigned char kV2FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x04,
+ 0xff, 0xff, 0xff, 0xff,
+ };
+ const unsigned char kV3FrameData[] = {
+ 0x80, spdy_version_ch_, 0x00, 0x07,
+ 0x00, 0x00, 0x00, 0x08,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+ const unsigned char kV4FrameData[] = {
+ 0x00, 0x10, 0x07, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00,
+ };
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnGoAway(0x7fffffff, GOAWAY_OK));
+ if (IsSpdy2()) {
+ framer.ProcessInput(reinterpret_cast<const char*>(kV2FrameData),
+ arraysize(kV2FrameData));
+ } else if (IsSpdy3()) {
+ framer.ProcessInput(reinterpret_cast<const char*>(kV3FrameData),
+ arraysize(kV3FrameData));
+ } else {
+ framer.ProcessInput(reinterpret_cast<const char*>(kV4FrameData),
+ arraysize(kV4FrameData));
+ }
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+TEST_P(SpdyFramerTest, OnBlocked) {
+ if (spdy_version_ < SPDY4) {
+ return;
+ }
+
+ const SpdyStreamId kStreamId = 0;
+
+ testing::StrictMock<test::MockVisitor> visitor;
+ SpdyFramer framer(spdy_version_);
+ framer.set_visitor(&visitor);
+
+ EXPECT_CALL(visitor, OnBlocked(kStreamId));
+
+ SpdyBlockedIR blocked_ir(0);
+ scoped_ptr<SpdySerializedFrame> frame(framer.SerializeFrame(blocked_ir));
+ framer.ProcessInput(frame->data(), framer.GetBlockedSize());
+
+ EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+ EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
+ << SpdyFramer::ErrorCodeToString(framer.error_code());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_header_block.cc b/chromium/net/spdy/spdy_header_block.cc
new file mode 100644
index 00000000000..ecc22a45324
--- /dev/null
+++ b/chromium/net/spdy/spdy_header_block.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_header_block.h"
+
+#include "base/values.h"
+#include "net/spdy/spdy_http_utils.h"
+
+namespace net {
+
+base::Value* SpdyHeaderBlockNetLogCallback(
+ const SpdyHeaderBlock* headers,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::DictionaryValue* headers_dict = new base::DictionaryValue();
+ for (SpdyHeaderBlock::const_iterator it = headers->begin();
+ it != headers->end(); ++it) {
+ headers_dict->SetWithoutPathExpansion(
+ it->first,
+ new base::StringValue(
+ ShouldShowHttpHeaderValue(it->first) ? it->second : "[elided]"));
+ }
+ dict->Set("headers", headers_dict);
+ return dict;
+}
+
+bool SpdyHeaderBlockFromNetLogParam(
+ const base::Value* event_param,
+ SpdyHeaderBlock* headers) {
+ headers->clear();
+
+ const base::DictionaryValue* dict = NULL;
+ const base::DictionaryValue* header_dict = NULL;
+
+ if (!event_param ||
+ !event_param->GetAsDictionary(&dict) ||
+ !dict->GetDictionary("headers", &header_dict)) {
+ return false;
+ }
+
+ for (base::DictionaryValue::Iterator it(*header_dict); !it.IsAtEnd();
+ it.Advance()) {
+ if (!it.value().GetAsString(&(*headers)[it.key()])) {
+ headers->clear();
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_header_block.h b/chromium/net/spdy/spdy_header_block.h
new file mode 100644
index 00000000000..8ae8fa385e8
--- /dev/null
+++ b/chromium/net/spdy/spdy_header_block.h
@@ -0,0 +1,36 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_HEADER_BLOCK_H_
+#define NET_SPDY_SPDY_HEADER_BLOCK_H_
+
+#include <map>
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// A data structure for holding a set of headers from either a
+// SYN_STREAM or SYN_REPLY frame.
+typedef std::map<std::string, std::string> SpdyHeaderBlock;
+
+// Converts a SpdyHeaderBlock into NetLog event parameters. Caller takes
+// ownership of returned value.
+NET_EXPORT base::Value* SpdyHeaderBlockNetLogCallback(
+ const SpdyHeaderBlock* headers,
+ NetLog::LogLevel log_level);
+
+// Converts NetLog event parameters into a SPDY header block and writes them
+// to |headers|. |event_param| must have been created by
+// SpdyHeaderBlockNetLogCallback. On failure, returns false and clears
+// |headers|.
+NET_EXPORT bool SpdyHeaderBlockFromNetLogParam(
+ const base::Value* event_param,
+ SpdyHeaderBlock* headers);
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_HEADER_BLOCK_H_
diff --git a/chromium/net/spdy/spdy_header_block_unittest.cc b/chromium/net/spdy/spdy_header_block_unittest.cc
new file mode 100644
index 00000000000..3cfef16e999
--- /dev/null
+++ b/chromium/net/spdy/spdy_header_block_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_header_block.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "net/base/net_log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(SpdyHeaderBlockTest, ToNetLogParamAndBackAgain) {
+ SpdyHeaderBlock headers;
+ headers["A"] = "a";
+ headers["B"] = "b";
+
+ scoped_ptr<Value> event_param(
+ SpdyHeaderBlockNetLogCallback(&headers, NetLog::LOG_ALL_BUT_BYTES));
+
+ SpdyHeaderBlock headers2;
+ ASSERT_TRUE(SpdyHeaderBlockFromNetLogParam(event_param.get(), &headers2));
+ EXPECT_EQ(headers, headers2);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_http_stream.cc b/chromium/net/spdy/spdy_http_stream.cc
new file mode 100644
index 00000000000..4d9117514ab
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_stream.cc
@@ -0,0 +1,532 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_http_stream.h"
+
+#include <algorithm>
+#include <list>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+SpdyHttpStream::SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session,
+ bool direct)
+ : weak_factory_(this),
+ spdy_session_(spdy_session),
+ is_reused_(spdy_session_->IsReused()),
+ stream_closed_(false),
+ closed_stream_status_(ERR_FAILED),
+ closed_stream_id_(0),
+ request_info_(NULL),
+ response_info_(NULL),
+ response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE),
+ user_buffer_len_(0),
+ request_body_buf_size_(0),
+ buffered_read_callback_pending_(false),
+ more_read_data_pending_(false),
+ direct_(direct) {
+ DCHECK(spdy_session_.get());
+}
+
+SpdyHttpStream::~SpdyHttpStream() {
+ if (stream_.get()) {
+ stream_->DetachDelegate();
+ DCHECK(!stream_.get());
+ }
+}
+
+int SpdyHttpStream::InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& stream_net_log,
+ const CompletionCallback& callback) {
+ DCHECK(!stream_);
+ if (!spdy_session_)
+ return ERR_CONNECTION_CLOSED;
+
+ request_info_ = request_info;
+ if (request_info_->method == "GET") {
+ int error = spdy_session_->GetPushStream(request_info_->url, &stream_,
+ stream_net_log);
+ if (error != OK)
+ return error;
+
+ // |stream_| may be NULL even if OK was returned.
+ if (stream_.get()) {
+ DCHECK_EQ(stream_->type(), SPDY_PUSH_STREAM);
+ stream_->SetDelegate(this);
+ return OK;
+ }
+ }
+
+ int rv = stream_request_.StartRequest(
+ SPDY_REQUEST_RESPONSE_STREAM, spdy_session_, request_info_->url,
+ priority, stream_net_log,
+ base::Bind(&SpdyHttpStream::OnStreamCreated,
+ weak_factory_.GetWeakPtr(), callback));
+
+ if (rv == OK) {
+ stream_ = stream_request_.ReleaseStream();
+ stream_->SetDelegate(this);
+ }
+
+ return rv;
+}
+
+const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const {
+ return response_info_;
+}
+
+UploadProgress SpdyHttpStream::GetUploadProgress() const {
+ if (!request_info_ || !HasUploadData())
+ return UploadProgress();
+
+ return UploadProgress(request_info_->upload_data_stream->position(),
+ request_info_->upload_data_stream->size());
+}
+
+int SpdyHttpStream::ReadResponseHeaders(const CompletionCallback& callback) {
+ CHECK(!callback.is_null());
+ if (stream_closed_)
+ return closed_stream_status_;
+
+ CHECK(stream_.get());
+
+ // Check if we already have the response headers. If so, return synchronously.
+ if (response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) {
+ CHECK(stream_->IsIdle());
+ return OK;
+ }
+
+ // Still waiting for the response, return IO_PENDING.
+ CHECK(callback_.is_null());
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int SpdyHttpStream::ReadResponseBody(
+ IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+ if (stream_.get())
+ CHECK(stream_->IsIdle());
+
+ CHECK(buf);
+ CHECK(buf_len);
+ CHECK(!callback.is_null());
+
+ // If we have data buffered, complete the IO immediately.
+ if (!response_body_queue_.IsEmpty()) {
+ return response_body_queue_.Dequeue(buf->data(), buf_len);
+ } else if (stream_closed_) {
+ return closed_stream_status_;
+ }
+
+ CHECK(callback_.is_null());
+ CHECK(!user_buffer_.get());
+ CHECK_EQ(0, user_buffer_len_);
+
+ callback_ = callback;
+ user_buffer_ = buf;
+ user_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+}
+
+void SpdyHttpStream::Close(bool not_reusable) {
+ // Note: the not_reusable flag has no meaning for SPDY streams.
+
+ Cancel();
+ DCHECK(!stream_.get());
+}
+
+HttpStream* SpdyHttpStream::RenewStreamForAuth() {
+ return NULL;
+}
+
+bool SpdyHttpStream::IsResponseBodyComplete() const {
+ return stream_closed_;
+}
+
+bool SpdyHttpStream::CanFindEndOfResponse() const {
+ return true;
+}
+
+bool SpdyHttpStream::IsConnectionReused() const {
+ return is_reused_;
+}
+
+void SpdyHttpStream::SetConnectionReused() {
+ // SPDY doesn't need an indicator here.
+}
+
+bool SpdyHttpStream::IsConnectionReusable() const {
+ // SPDY streams aren't considered reusable.
+ return false;
+}
+
+bool SpdyHttpStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ if (stream_closed_) {
+ if (!closed_stream_has_load_timing_info_)
+ return false;
+ *load_timing_info = closed_stream_load_timing_info_;
+ return true;
+ }
+
+ // If |stream_| has yet to be created, or does not yet have an ID, fail.
+ // The reused flag can only be correctly set once a stream has an ID. Streams
+ // get their IDs once the request has been successfully sent, so this does not
+ // behave that differently from other stream types.
+ if (!stream_ || stream_->stream_id() == 0)
+ return false;
+
+ return stream_->GetLoadTimingInfo(load_timing_info);
+}
+
+int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) {
+ if (stream_closed_) {
+ if (stream_->type() == SPDY_PUSH_STREAM)
+ return closed_stream_status_;
+
+ return (closed_stream_status_ == OK) ? ERR_FAILED : closed_stream_status_;
+ }
+
+ base::Time request_time = base::Time::Now();
+ CHECK(stream_.get());
+
+ stream_->SetRequestTime(request_time);
+ // This should only get called in the case of a request occurring
+ // during server push that has already begun but hasn't finished,
+ // so we set the response's request time to be the actual one
+ if (response_info_)
+ response_info_->request_time = request_time;
+
+ CHECK(!request_body_buf_.get());
+ if (HasUploadData()) {
+ // Use kMaxSpdyFrameChunkSize as the buffer size, since the request
+ // body data is written with this size at a time.
+ request_body_buf_ = new IOBufferWithSize(kMaxSpdyFrameChunkSize);
+ // The request body buffer is empty at first.
+ request_body_buf_size_ = 0;
+ }
+
+ CHECK(!callback.is_null());
+ CHECK(response);
+
+ // SendRequest can be called in two cases.
+ //
+ // a) A client initiated request. In this case, |response_info_| should be
+ // NULL to start with.
+ // b) A client request which matches a response that the server has already
+ // pushed.
+ if (push_response_info_.get()) {
+ *response = *(push_response_info_.get());
+ push_response_info_.reset();
+ } else {
+ DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_);
+ }
+
+ response_info_ = response;
+
+ // Put the peer's IP address and port into the response.
+ IPEndPoint address;
+ int result = stream_->GetPeerAddress(&address);
+ if (result != OK)
+ return result;
+ response_info_->socket_address = HostPortPair::FromIPEndPoint(address);
+
+ if (stream_->type() == SPDY_PUSH_STREAM) {
+ // Pushed streams do not send any data, and should always be
+ // idle. However, we still want to return ERR_IO_PENDING to mimic
+ // non-push behavior. The callback will be called when the
+ // response is received.
+ result = ERR_IO_PENDING;
+ } else {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ CreateSpdyHeadersFromHttpRequest(
+ *request_info_, request_headers,
+ headers.get(), stream_->GetProtocolVersion(),
+ direct_);
+ stream_->net_log().AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS,
+ base::Bind(&SpdyHeaderBlockNetLogCallback, headers.get()));
+ result =
+ stream_->SendRequestHeaders(
+ headers.Pass(),
+ HasUploadData() ? MORE_DATA_TO_SEND : NO_MORE_DATA_TO_SEND);
+ }
+
+ if (result == ERR_IO_PENDING) {
+ CHECK(callback_.is_null());
+ callback_ = callback;
+ }
+ return result;
+}
+
+void SpdyHttpStream::Cancel() {
+ callback_.Reset();
+ if (stream_.get()) {
+ stream_->Cancel();
+ DCHECK(!stream_.get());
+ }
+}
+
+void SpdyHttpStream::OnRequestHeadersSent() {
+ if (!callback_.is_null())
+ DoCallback(OK);
+
+ // TODO(akalin): Do this immediately after sending the request
+ // headers.
+ if (HasUploadData())
+ ReadAndSendRequestBodyData();
+}
+
+SpdyResponseHeadersStatus SpdyHttpStream::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_INCOMPLETE);
+
+ if (!response_info_) {
+ DCHECK_EQ(stream_->type(), SPDY_PUSH_STREAM);
+ push_response_info_.reset(new HttpResponseInfo);
+ response_info_ = push_response_info_.get();
+ }
+
+ if (!SpdyHeadersToHttpResponse(
+ response_headers, stream_->GetProtocolVersion(), response_info_)) {
+ // We do not have complete headers yet.
+ return RESPONSE_HEADERS_ARE_INCOMPLETE;
+ }
+
+ response_info_->response_time = stream_->response_time();
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
+ // Don't store the SSLInfo in the response here, HttpNetworkTransaction
+ // will take care of that part.
+ SSLInfo ssl_info;
+ NextProto protocol_negotiated = kProtoUnknown;
+ stream_->GetSSLInfo(&ssl_info,
+ &response_info_->was_npn_negotiated,
+ &protocol_negotiated);
+ response_info_->npn_negotiated_protocol =
+ SSLClientSocket::NextProtoToString(protocol_negotiated);
+ response_info_->request_time = stream_->GetRequestTime();
+ response_info_->connection_info =
+ HttpResponseInfo::ConnectionInfoFromNextProto(stream_->GetProtocol());
+ response_info_->vary_data
+ .Init(*request_info_, *response_info_->headers.get());
+
+ if (!callback_.is_null())
+ DoCallback(OK);
+
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+}
+
+void SpdyHttpStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_COMPLETE);
+
+ // Note that data may be received for a SpdyStream prior to the user calling
+ // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often
+ // happen for server initiated streams.
+ DCHECK(stream_.get());
+ DCHECK(!stream_->IsClosed() || stream_->type() == SPDY_PUSH_STREAM);
+ if (buffer) {
+ response_body_queue_.Enqueue(buffer.Pass());
+
+ if (user_buffer_.get()) {
+ // Handing small chunks of data to the caller creates measurable overhead.
+ // We buffer data in short time-spans and send a single read notification.
+ ScheduleBufferedReadCallback();
+ }
+ }
+}
+
+void SpdyHttpStream::OnDataSent() {
+ request_body_buf_size_ = 0;
+ ReadAndSendRequestBodyData();
+}
+
+void SpdyHttpStream::OnClose(int status) {
+ if (stream_.get()) {
+ stream_closed_ = true;
+ closed_stream_status_ = status;
+ closed_stream_id_ = stream_->stream_id();
+ closed_stream_has_load_timing_info_ =
+ stream_->GetLoadTimingInfo(&closed_stream_load_timing_info_);
+ }
+ stream_.reset();
+ bool invoked_callback = false;
+ if (status == net::OK) {
+ // We need to complete any pending buffered read now.
+ invoked_callback = DoBufferedReadCallback();
+ }
+ if (!invoked_callback && !callback_.is_null())
+ DoCallback(status);
+}
+
+bool SpdyHttpStream::HasUploadData() const {
+ CHECK(request_info_);
+ return
+ request_info_->upload_data_stream &&
+ ((request_info_->upload_data_stream->size() > 0) ||
+ request_info_->upload_data_stream->is_chunked());
+}
+
+void SpdyHttpStream::OnStreamCreated(
+ const CompletionCallback& callback,
+ int rv) {
+ if (rv == OK) {
+ stream_ = stream_request_.ReleaseStream();
+ stream_->SetDelegate(this);
+ }
+ callback.Run(rv);
+}
+
+void SpdyHttpStream::ReadAndSendRequestBodyData() {
+ CHECK(HasUploadData());
+ CHECK_EQ(request_body_buf_size_, 0);
+
+ if (request_info_->upload_data_stream->IsEOF())
+ return;
+
+ // Read the data from the request body stream.
+ const int rv = request_info_->upload_data_stream
+ ->Read(request_body_buf_.get(),
+ request_body_buf_->size(),
+ base::Bind(&SpdyHttpStream::OnRequestBodyReadCompleted,
+ weak_factory_.GetWeakPtr()));
+
+ if (rv != ERR_IO_PENDING) {
+ // ERR_IO_PENDING is the only possible error.
+ CHECK_GE(rv, 0);
+ OnRequestBodyReadCompleted(rv);
+ }
+}
+
+void SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
+ CHECK_GE(status, 0);
+ request_body_buf_size_ = status;
+ const bool eof = request_info_->upload_data_stream->IsEOF();
+ if (eof) {
+ CHECK_GE(request_body_buf_size_, 0);
+ } else {
+ CHECK_GT(request_body_buf_size_, 0);
+ }
+ stream_->SendData(request_body_buf_.get(),
+ request_body_buf_size_,
+ eof ? NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND);
+}
+
+void SpdyHttpStream::ScheduleBufferedReadCallback() {
+ // If there is already a scheduled DoBufferedReadCallback, don't issue
+ // another one. Mark that we have received more data and return.
+ if (buffered_read_callback_pending_) {
+ more_read_data_pending_ = true;
+ return;
+ }
+
+ more_read_data_pending_ = false;
+ buffered_read_callback_pending_ = true;
+ const base::TimeDelta kBufferTime = base::TimeDelta::FromMilliseconds(1);
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(&SpdyHttpStream::DoBufferedReadCallback),
+ weak_factory_.GetWeakPtr()),
+ kBufferTime);
+}
+
+// Checks to see if we should wait for more buffered data before notifying
+// the caller. Returns true if we should wait, false otherwise.
+bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const {
+ // If the response is complete, there is no point in waiting.
+ if (stream_closed_)
+ return false;
+
+ DCHECK_GT(user_buffer_len_, 0);
+ return response_body_queue_.GetTotalSize() <
+ static_cast<size_t>(user_buffer_len_);
+}
+
+bool SpdyHttpStream::DoBufferedReadCallback() {
+ buffered_read_callback_pending_ = false;
+
+ // If the transaction is cancelled or errored out, we don't need to complete
+ // the read.
+ if (!stream_.get() && !stream_closed_)
+ return false;
+
+ int stream_status =
+ stream_closed_ ? closed_stream_status_ : stream_->response_status();
+ if (stream_status != OK)
+ return false;
+
+ // When more_read_data_pending_ is true, it means that more data has
+ // arrived since we started waiting. Wait a little longer and continue
+ // to buffer.
+ if (more_read_data_pending_ && ShouldWaitForMoreBufferedData()) {
+ ScheduleBufferedReadCallback();
+ return false;
+ }
+
+ int rv = 0;
+ if (user_buffer_.get()) {
+ rv = ReadResponseBody(user_buffer_.get(), user_buffer_len_, callback_);
+ CHECK_NE(rv, ERR_IO_PENDING);
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ DoCallback(rv);
+ return true;
+ }
+ return false;
+}
+
+void SpdyHttpStream::DoCallback(int rv) {
+ CHECK_NE(rv, ERR_IO_PENDING);
+ CHECK(!callback_.is_null());
+
+ // Since Run may result in being called back, clear user_callback_ in advance.
+ CompletionCallback c = callback_;
+ callback_.Reset();
+ c.Run(rv);
+}
+
+void SpdyHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+ DCHECK(stream_.get());
+ bool using_npn;
+ NextProto protocol_negotiated = kProtoUnknown;
+ stream_->GetSSLInfo(ssl_info, &using_npn, &protocol_negotiated);
+}
+
+void SpdyHttpStream::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK(stream_.get());
+ stream_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+bool SpdyHttpStream::IsSpdyHttpStream() const {
+ return true;
+}
+
+void SpdyHttpStream::Drain(HttpNetworkSession* session) {
+ Close(false);
+ delete this;
+}
+
+void SpdyHttpStream::SetPriority(RequestPriority priority) {
+ // TODO(akalin): Plumb this through to |stream_request_| and
+ // |stream_|.
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_http_stream.h b/chromium/net/spdy/spdy_http_stream.h
new file mode 100644
index 00000000000..f6e39cd102e
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_stream.h
@@ -0,0 +1,167 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_HTTP_STREAM_H_
+#define NET_SPDY_SPDY_HTTP_STREAM_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/http/http_stream.h"
+#include "net/spdy/spdy_read_queue.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+class DrainableIOBuffer;
+struct HttpRequestInfo;
+class HttpResponseInfo;
+class IOBuffer;
+class SpdySession;
+class UploadDataStream;
+
+// The SpdyHttpStream is a HTTP-specific type of stream known to a SpdySession.
+class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
+ public HttpStream {
+ public:
+ // |spdy_session| must not be NULL.
+ SpdyHttpStream(const base::WeakPtr<SpdySession>& spdy_session, bool direct);
+ virtual ~SpdyHttpStream();
+
+ SpdyStream* stream() { return stream_.get(); }
+
+ // Cancels any callbacks from being invoked and deletes the stream.
+ void Cancel();
+
+ // HttpStream implementation.
+
+ virtual int InitializeStream(const HttpRequestInfo* request_info,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) OVERRIDE;
+
+ virtual int SendRequest(const HttpRequestHeaders& headers,
+ HttpResponseInfo* response,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+ virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+ virtual int ReadResponseBody(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Close(bool not_reusable) OVERRIDE;
+ virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+ virtual bool IsResponseBodyComplete() const OVERRIDE;
+ virtual bool CanFindEndOfResponse() const OVERRIDE;
+
+ // Must not be called if a NULL SpdySession was pssed into the
+ // constructor.
+ virtual bool IsConnectionReused() const OVERRIDE;
+
+ virtual void SetConnectionReused() OVERRIDE;
+ virtual bool IsConnectionReusable() const OVERRIDE;
+ virtual bool GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+ virtual void GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual bool IsSpdyHttpStream() const OVERRIDE;
+ virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+
+ // SpdyStream::Delegate implementation.
+ virtual void OnRequestHeadersSent() OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnDataSent() OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ private:
+ // Must be called only when |request_info_| is non-NULL.
+ bool HasUploadData() const;
+
+ void OnStreamCreated(const CompletionCallback& callback, int rv);
+
+ // Reads the remaining data (whether chunked or not) from the
+ // request body stream and sends it if there's any. The read and
+ // subsequent sending may happen asynchronously. Must be called only
+ // when HasUploadData() is true.
+ void ReadAndSendRequestBodyData();
+
+ // Called when data has just been read from the request body stream;
+ // does the actual sending of data.
+ void OnRequestBodyReadCompleted(int status);
+
+ // Call the user callback.
+ void DoCallback(int rv);
+
+ void ScheduleBufferedReadCallback();
+
+ // Returns true if the callback is invoked.
+ bool DoBufferedReadCallback();
+ bool ShouldWaitForMoreBufferedData() const;
+
+ base::WeakPtrFactory<SpdyHttpStream> weak_factory_;
+
+ const base::WeakPtr<SpdySession> spdy_session_;
+ bool is_reused_;
+ SpdyStreamRequest stream_request_;
+ base::WeakPtr<SpdyStream> stream_;
+
+ bool stream_closed_;
+
+ // Set only when |stream_closed_| is true.
+ int closed_stream_status_;
+ SpdyStreamId closed_stream_id_;
+ bool closed_stream_has_load_timing_info_;
+ LoadTimingInfo closed_stream_load_timing_info_;
+
+ // The request to send.
+ const HttpRequestInfo* request_info_;
+
+ // |response_info_| is the HTTP response data object which is filled in
+ // when a SYN_REPLY comes in for the stream.
+ // It is not owned by this stream object, or point to |push_response_info_|.
+ HttpResponseInfo* response_info_;
+
+ scoped_ptr<HttpResponseInfo> push_response_info_;
+
+ // We don't use SpdyStream's |response_header_status_| as we
+ // sometimes call back into our delegate before it is updated.
+ SpdyResponseHeadersStatus response_headers_status_;
+
+ // We buffer the response body as it arrives asynchronously from the stream.
+ SpdyReadQueue response_body_queue_;
+
+ CompletionCallback callback_;
+
+ // User provided buffer for the ReadResponseBody() response.
+ scoped_refptr<IOBuffer> user_buffer_;
+ int user_buffer_len_;
+
+ // Temporary buffer used to read the request body from UploadDataStream.
+ scoped_refptr<IOBufferWithSize> request_body_buf_;
+ int request_body_buf_size_;
+
+ // Is there a scheduled read callback pending.
+ bool buffered_read_callback_pending_;
+ // Has more data been received from the network during the wait for the
+ // scheduled read callback.
+ bool more_read_data_pending_;
+
+ // Is this spdy stream direct to the origin server (or to a proxy).
+ bool direct_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyHttpStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_HTTP_STREAM_H_
diff --git a/chromium/net/spdy/spdy_http_stream_unittest.cc b/chromium/net/spdy/spdy_http_stream_unittest.cc
new file mode 100644
index 00000000000..d88c963cd9a
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_stream_unittest.cc
@@ -0,0 +1,898 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_http_stream.h"
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/signature_creator.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_element_reader.h"
+#include "net/cert/asn1_util.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_credential_builder.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/ssl/default_server_bound_cert_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Tests the load timing of a stream that's connected and is not the first
+// request sent on a connection.
+void TestLoadTimingReused(const HttpStream& stream) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+// Tests the load timing of a stream that's connected and using a fresh
+// connection.
+void TestLoadTimingNotReused(const HttpStream& stream) {
+ LoadTimingInfo load_timing_info;
+ EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+ ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info);
+}
+
+} // namespace
+
+class SpdyHttpStreamTest : public testing::Test,
+ public testing::WithParamInterface<NextProto> {
+ public:
+ SpdyHttpStreamTest()
+ : spdy_util_(GetParam()),
+ session_deps_(GetParam()) {
+ session_deps_.net_log = &net_log_;
+ }
+
+ DeterministicSocketData* deterministic_data() {
+ return deterministic_data_.get();
+ }
+
+ OrderedSocketData* data() { return data_.get(); }
+
+ protected:
+ virtual void TearDown() OVERRIDE {
+ crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ // Initializes the session using DeterministicSocketData. It's advisable
+ // to use this function rather than the OrderedSocketData, since the
+ // DeterministicSocketData behaves in a reasonable manner.
+ void InitSessionDeterministic(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ const SpdySessionKey& key) {
+ deterministic_data_.reset(
+ new DeterministicSocketData(reads, reads_count, writes, writes_count));
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ deterministic_data_.get());
+ http_session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+ session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog());
+ }
+
+ // Initializes the session using the finicky OrderedSocketData class.
+ void InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count,
+ const SpdySessionKey& key) {
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog());
+ }
+
+ void TestSendCredentials(
+ ServerBoundCertService* server_bound_cert_service,
+ const std::string& cert,
+ const std::string& proof);
+
+ SpdyTestUtil spdy_util_;
+ CapturingNetLog net_log_;
+ SpdySessionDependencies session_deps_;
+ scoped_ptr<OrderedSocketData> data_;
+ scoped_ptr<DeterministicSocketData> deterministic_data_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ base::WeakPtr<SpdySession> session_;
+
+ private:
+ MockECSignatureCreatorFactory ec_signature_creator_factory_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdyHttpStreamTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+// SpdyHttpStream::GetUploadProgress() should still work even before the
+// stream is initialized.
+TEST_P(SpdyHttpStreamTest, GetUploadProgressBeforeInitialization) {
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSession(reads, arraysize(reads), NULL, 0, key);
+
+ SpdyHttpStream stream(session_, false);
+ UploadProgress progress = stream.GetUploadProgress();
+ EXPECT_EQ(0u, progress.size());
+ EXPECT_EQ(0u, progress.position());
+}
+
+TEST_P(SpdyHttpStreamTest, SendRequest) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSession(reads, arraysize(reads), writes, arraysize(writes), key);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ // Make sure getting load timing information the stream early does not crash.
+ LoadTimingInfo load_timing_info;
+ EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
+
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+ EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+ EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info));
+
+ // This triggers the MockWrite and read 2
+ callback.WaitForResult();
+
+ // Can get timing information once the stream connects.
+ TestLoadTimingNotReused(*http_stream);
+
+ // This triggers read 3. The empty read causes the session to shut down.
+ data()->CompleteRead();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+
+ TestLoadTimingNotReused(*http_stream);
+ http_stream->Close(true);
+ // Test that there's no crash when trying to get the load timing after the
+ // stream has been closed.
+ TestLoadTimingNotReused(*http_stream);
+}
+
+TEST_P(SpdyHttpStreamTest, LoadTimingTwoRequests) {
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+ scoped_ptr<SpdyFrame> resp1(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, "", 0, true));
+ scoped_ptr<SpdyFrame> resp2(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(3, "", 0, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 2),
+ CreateMockRead(*body1, 3),
+ CreateMockRead(*resp2, 4),
+ CreateMockRead(*body2, 5),
+ MockRead(ASYNC, 0, 6) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSessionDeterministic(reads, arraysize(reads),
+ writes, arraysize(writes),
+ key);
+
+ HttpRequestInfo request1;
+ request1.method = "GET";
+ request1.url = GURL("http://www.google.com/");
+ TestCompletionCallback callback1;
+ HttpResponseInfo response1;
+ HttpRequestHeaders headers1;
+ scoped_ptr<SpdyHttpStream> http_stream1(new SpdyHttpStream(session_, true));
+
+ HttpRequestInfo request2;
+ request2.method = "GET";
+ request2.url = GURL("http://www.google.com/");
+ TestCompletionCallback callback2;
+ HttpResponseInfo response2;
+ HttpRequestHeaders headers2;
+ scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true));
+
+ // First write.
+ ASSERT_EQ(OK,
+ http_stream1->InitializeStream(&request1, DEFAULT_PRIORITY,
+ BoundNetLog(),
+ CompletionCallback()));
+ EXPECT_EQ(ERR_IO_PENDING, http_stream1->SendRequest(headers1, &response1,
+ callback1.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ deterministic_data()->RunFor(1);
+ EXPECT_LE(0, callback1.WaitForResult());
+
+ TestLoadTimingNotReused(*http_stream1);
+ LoadTimingInfo load_timing_info1;
+ LoadTimingInfo load_timing_info2;
+ EXPECT_TRUE(http_stream1->GetLoadTimingInfo(&load_timing_info1));
+ EXPECT_FALSE(http_stream2->GetLoadTimingInfo(&load_timing_info2));
+
+ // Second write.
+ ASSERT_EQ(OK,
+ http_stream2->InitializeStream(&request2, DEFAULT_PRIORITY,
+ BoundNetLog(),
+ CompletionCallback()));
+ EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers2, &response2,
+ callback2.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ deterministic_data()->RunFor(1);
+ EXPECT_LE(0, callback2.WaitForResult());
+ TestLoadTimingReused(*http_stream2);
+ EXPECT_TRUE(http_stream2->GetLoadTimingInfo(&load_timing_info2));
+ EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
+
+ // All the reads.
+ deterministic_data()->RunFor(6);
+
+ // Read stream 1 to completion, before making sure we can still read load
+ // timing from both streams.
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(1));
+ ASSERT_EQ(
+ 0, http_stream1->ReadResponseBody(buf1.get(), 1, callback1.callback()));
+
+ // Stream 1 has been read to completion.
+ TestLoadTimingNotReused(*http_stream1);
+ // Stream 2 still has queued body data.
+ TestLoadTimingReused(*http_stream2);
+}
+
+TEST_P(SpdyHttpStreamTest, SendChunkedPost) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ framer.CreateDataFrame(1, kUploadData, kUploadDataSize, DATA_FLAG_FIN));
+ std::vector<MockWrite> writes;
+ int seq = 0;
+ writes.push_back(CreateMockWrite(*req, seq++));
+ writes.push_back(CreateMockWrite(*body, seq++)); // POST upload frame
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ std::vector<MockRead> reads;
+ reads.push_back(CreateMockRead(*resp, seq++));
+ reads.push_back(CreateMockRead(*body, seq++));
+ reads.push_back(MockRead(SYNCHRONOUS, 0, seq++)); // EOF
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSession(vector_as_array(&reads), reads.size(),
+ vector_as_array(&writes), writes.size(),
+ key);
+ EXPECT_EQ(spdy_util_.spdy_version(), session_->GetProtocolVersion());
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+ const int kFirstChunkSize = kUploadDataSize/2;
+ upload_stream.AppendChunk(kUploadData, kFirstChunkSize, false);
+ upload_stream.AppendChunk(kUploadData + kFirstChunkSize,
+ kUploadDataSize - kFirstChunkSize, true);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_stream;
+
+ ASSERT_EQ(OK, upload_stream.Init(CompletionCallback()));
+
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ SpdyHttpStream http_stream(session_, true);
+ ASSERT_EQ(
+ OK,
+ http_stream.InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+ headers, &response, callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ // This results in writing the post body and reading the response headers.
+ callback.WaitForResult();
+
+ // This triggers reading the body and the EOF, causing the session to shut
+ // down.
+ data()->CompleteRead();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+// Test to ensure the SpdyStream state machine does not get confused when a
+// chunk becomes available while a write is pending.
+TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPost) {
+ const char kUploadData1[] = "12345678";
+ const int kUploadData1Size = arraysize(kUploadData1)-1;
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, kUploadData1, kUploadData1Size, false));
+ scoped_ptr<SpdyFrame> chunk3(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*chunk1, 1), // POST upload frames
+ CreateMockWrite(*chunk2, 2),
+ CreateMockWrite(*chunk3, 3),
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 4),
+ CreateMockRead(*chunk1, 5),
+ CreateMockRead(*chunk2, 6),
+ CreateMockRead(*chunk3, 7),
+ MockRead(ASYNC, 0, 8) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSessionDeterministic(reads, arraysize(reads),
+ writes, arraysize(writes),
+ key);
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_stream;
+
+ ASSERT_EQ(OK, upload_stream.Init(CompletionCallback()));
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+
+ TestCompletionCallback callback;
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ // Complete the initial request write and the first chunk.
+ deterministic_data()->RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Now append the final two chunks which will enqueue two more writes.
+ upload_stream.AppendChunk(kUploadData1, kUploadData1Size, false);
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+ // Finish writing all the chunks.
+ deterministic_data()->RunFor(2);
+
+ // Read response headers.
+ deterministic_data()->RunFor(1);
+ ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+ // Read and check |chunk1| response.
+ deterministic_data()->RunFor(1);
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(
+ buf1.get(), kUploadDataSize, callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+ // Read and check |chunk2| response.
+ deterministic_data()->RunFor(1);
+ scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
+ ASSERT_EQ(kUploadData1Size,
+ http_stream->ReadResponseBody(
+ buf2.get(), kUploadData1Size, callback.callback()));
+ EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
+
+ // Read and check |chunk3| response.
+ deterministic_data()->RunFor(1);
+ scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(
+ buf3.get(), kUploadDataSize, callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
+
+ // Finish reading the |EOF|.
+ deterministic_data()->RunFor(1);
+ ASSERT_TRUE(response.headers.get());
+ ASSERT_EQ(200, response.headers->response_code());
+ EXPECT_TRUE(deterministic_data()->at_read_eof());
+ EXPECT_TRUE(deterministic_data()->at_write_eof());
+}
+
+// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
+TEST_P(SpdyHttpStreamTest, SpdyURLTest) {
+ const char * const full_url = "http://www.google.com/foo?query=what#anchor";
+ const char * const base_url = "http://www.google.com/foo?query=what";
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(base_url, false, 1, LOWEST));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 1),
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(SYNCHRONOUS, 0, 3) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ InitSession(reads, arraysize(reads), writes, arraysize(writes), key);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(full_url);
+ TestCompletionCallback callback;
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ ASSERT_EQ(OK,
+ http_stream->InitializeStream(
+ &request, DEFAULT_PRIORITY, net_log, CompletionCallback()));
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+
+ EXPECT_EQ(base_url, http_stream->stream()->GetUrlFromHeaders().spec());
+
+ // This triggers the MockWrite and read 2
+ callback.WaitForResult();
+
+ // This triggers read 3. The empty read causes the session to shut down.
+ data()->CompleteRead();
+
+ // Because we abandoned the stream, we don't expect to find a session in the
+ // pool anymore.
+ EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+namespace {
+
+void GetECServerBoundCertAndProof(
+ const std::string& host,
+ ServerBoundCertService* server_bound_cert_service,
+ std::string* cert,
+ std::string* proof) {
+ TestCompletionCallback callback;
+ std::string key;
+ ServerBoundCertService::RequestHandle request_handle;
+ int rv = server_bound_cert_service->GetOrCreateDomainBoundCert(
+ host, &key, cert, callback.callback(),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ SpdyCredential credential;
+ EXPECT_EQ(OK,
+ SpdyCredentialBuilder::Build(
+ MockClientSocket::kTlsUnique, key, *cert, 2, &credential));
+
+ ASSERT_FALSE(credential.certs.empty());
+ cert->assign(credential.certs[0]);
+ proof->assign(credential.proof);
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a GET request with
+// a credential set.
+SpdyFrame* ConstructCredentialRequestFrame(NextProto next_proto,
+ size_t slot, const GURL& url,
+ SpdyStreamId stream_id) {
+ SpdyTestUtil util(next_proto);
+
+ const SpdyHeaderInfo syn_headers = {
+ SYN_STREAM,
+ stream_id,
+ 0,
+ ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+ slot,
+ CONTROL_FLAG_FIN,
+ false,
+ RST_STREAM_INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+
+ scoped_ptr<SpdyHeaderBlock> headers(util.ConstructGetHeaderBlock(url.spec()));
+ return util.ConstructSpdyFrame(syn_headers, headers.Pass());
+}
+
+} // namespace
+
+// TODO(rch): When openssl supports server bound certifictes, this
+// guard can be removed
+#if !defined(USE_OPENSSL)
+// Test that if we request a resource for a new origin on a session that
+// used domain bound certificates, that we send a CREDENTIAL frame for
+// the new domain before we send the new request.
+void SpdyHttpStreamTest::TestSendCredentials(
+ ServerBoundCertService* server_bound_cert_service,
+ const std::string& cert,
+ const std::string& proof) {
+ const char* kUrl1 = "https://www.google.com/";
+ const char* kUrl2 = "https://www.gmail.com/";
+
+ SpdyCredential cred;
+ cred.slot = 2;
+ cred.proof = proof;
+ cred.certs.push_back(cert);
+
+ scoped_ptr<SpdyFrame> req(ConstructCredentialRequestFrame(
+ GetParam(), 1, GURL(kUrl1), 1));
+ scoped_ptr<SpdyFrame> credential(
+ spdy_util_.ConstructSpdyCredential(cred));
+ scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame(
+ GetParam(), 2, GURL(kUrl2), 3));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*credential.get(), 2),
+ CreateMockWrite(*req2.get(), 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*resp2, 4),
+ MockRead(SYNCHRONOUS, 0, 5) // EOF
+ };
+
+ HostPortPair host_port_pair(HostPortPair::FromURL(GURL(kUrl1)));
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ DeterministicMockClientSocketFactory* socket_factory =
+ session_deps_.deterministic_socket_factory.get();
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ socket_factory->AddSocketDataProvider(&data);
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ ssl.channel_id_sent = true;
+ ssl.server_bound_cert_service = server_bound_cert_service;
+ ssl.protocol_negotiated = GetParam();
+ socket_factory->AddSSLSocketDataProvider(&ssl);
+ http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+ session_ = CreateSecureSpdySession(http_session_, key, BoundNetLog());
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(kUrl1);
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+
+ // EXPECT_FALSE(session_->NeedsCredentials(request.url));
+ // GURL new_origin(kUrl2);
+ // EXPECT_TRUE(session_->NeedsCredentials(new_origin));
+
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ data.RunFor(2);
+ callback.WaitForResult();
+
+ // Start up second request for resource on a new origin.
+ scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true));
+ request.url = GURL(kUrl2);
+ ASSERT_EQ(
+ OK,
+ http_stream2->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+ EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, &response,
+ callback.callback()));
+ data.RunFor(2);
+ callback.WaitForResult();
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders(
+ callback.callback()));
+ data.RunFor(1);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ ASSERT_TRUE(response.headers.get() != NULL);
+ ASSERT_EQ(200, response.headers->response_code());
+}
+
+// The tests below are only for SPDY/3 and above.
+
+// Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
+// made available is handled correctly.
+TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPostWithWindowUpdate) {
+ if (GetParam() < kProtoSPDY3)
+ return;
+
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*chunk1, 1),
+ };
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize));
+ MockRead reads[] = {
+ CreateMockRead(*window_update, 2),
+ CreateMockRead(*resp, 3),
+ CreateMockRead(*chunk1, 4),
+ MockRead(ASYNC, 0, 5) // EOF
+ };
+
+ HostPortPair host_port_pair("www.google.com", 80);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ InitSessionDeterministic(reads, arraysize(reads),
+ writes, arraysize(writes),
+ key);
+
+ UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_stream;
+
+ ASSERT_EQ(OK, upload_stream.Init(CompletionCallback()));
+ upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+
+ HttpRequestHeaders headers;
+ HttpResponseInfo response;
+ // This will attempt to Write() the initial request and headers, which will
+ // complete asynchronously.
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ // Complete the initial request write and first chunk.
+ deterministic_data_->RunFor(2);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Verify that the window size has decreased.
+ ASSERT_TRUE(http_stream->stream() != NULL);
+ EXPECT_NE(static_cast<int>(kSpdyStreamInitialWindowSize),
+ http_stream->stream()->send_window_size());
+
+ // Read window update.
+ deterministic_data_->RunFor(1);
+
+ // Verify the window update.
+ ASSERT_TRUE(http_stream->stream() != NULL);
+ EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize),
+ http_stream->stream()->send_window_size());
+
+ // Read response headers.
+ deterministic_data_->RunFor(1);
+ ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+ // Read and check |chunk1| response.
+ deterministic_data_->RunFor(1);
+ scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+ ASSERT_EQ(kUploadDataSize,
+ http_stream->ReadResponseBody(
+ buf1.get(), kUploadDataSize, callback.callback()));
+ EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+ // Finish reading the |EOF|.
+ deterministic_data_->RunFor(1);
+ ASSERT_TRUE(response.headers.get());
+ ASSERT_EQ(200, response.headers->response_code());
+ EXPECT_TRUE(deterministic_data_->at_read_eof());
+ EXPECT_TRUE(deterministic_data_->at_write_eof());
+}
+
+TEST_P(SpdyHttpStreamTest, SendCredentialsEC) {
+ if (GetParam() < kProtoSPDY3)
+ return;
+
+ scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool =
+ new base::SequencedWorkerPool(1, "SpdyHttpStreamSpdy3Test");
+ scoped_ptr<ServerBoundCertService> server_bound_cert_service(
+ new ServerBoundCertService(new DefaultServerBoundCertStore(NULL),
+ sequenced_worker_pool));
+ std::string cert;
+ std::string proof;
+ GetECServerBoundCertAndProof("www.gmail.com",
+ server_bound_cert_service.get(),
+ &cert, &proof);
+
+ TestSendCredentials(server_bound_cert_service.get(), cert, proof);
+
+ sequenced_worker_pool->Shutdown();
+}
+
+TEST_P(SpdyHttpStreamTest, DontSendCredentialsForHttpUrlsEC) {
+ if (GetParam() < kProtoSPDY3)
+ return;
+
+ scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool =
+ new base::SequencedWorkerPool(1, "SpdyHttpStreamSpdy3Test");
+ scoped_ptr<ServerBoundCertService> server_bound_cert_service(
+ new ServerBoundCertService(new DefaultServerBoundCertStore(NULL),
+ sequenced_worker_pool));
+ std::string cert;
+ std::string proof;
+ GetECServerBoundCertAndProof("proxy.google.com",
+ server_bound_cert_service.get(),
+ &cert, &proof);
+
+ const char* kUrl1 = "http://www.google.com/";
+ const char* kUrl2 = "http://www.gmail.com/";
+
+ SpdyCredential cred;
+ cred.slot = 2;
+ cred.proof = proof;
+ cred.certs.push_back(cert);
+
+ scoped_ptr<SpdyFrame> req(ConstructCredentialRequestFrame(
+ GetParam(), 0, GURL(kUrl1), 1));
+ scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame(
+ GetParam(), 0, GURL(kUrl2), 3));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0),
+ CreateMockWrite(*req2.get(), 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*resp2, 3),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+
+ HostPortPair host_port_pair(HostPortPair::FromURL(GURL(kUrl1)));
+ SpdySessionKey key(host_port_pair,
+ ProxyServer::FromURI("proxy.google.com",
+ ProxyServer::SCHEME_HTTPS),
+ kPrivacyModeDisabled);
+ InitSessionDeterministic(reads, arraysize(reads),
+ writes, arraysize(writes),
+ key);
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(kUrl1);
+ HttpResponseInfo response;
+ HttpRequestHeaders headers;
+ BoundNetLog net_log;
+ scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+ ASSERT_EQ(
+ OK,
+ http_stream->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+
+ TestCompletionCallback callback;
+ EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+ callback.callback()));
+ EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key));
+
+ deterministic_data_->RunFor(2);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // Start up second request for resource on a new origin.
+ scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true));
+ request.url = GURL(kUrl2);
+ ASSERT_EQ(
+ OK,
+ http_stream2->InitializeStream(&request, DEFAULT_PRIORITY,
+ net_log, CompletionCallback()));
+ EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, &response,
+ callback.callback()));
+ deterministic_data_->RunFor(1);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders(
+ callback.callback()));
+ deterministic_data_->RunFor(1);
+ EXPECT_EQ(OK, callback.WaitForResult());
+ ASSERT_TRUE(response.headers.get() != NULL);
+ ASSERT_EQ(200, response.headers->response_code());
+ deterministic_data_->RunFor(1);
+ sequenced_worker_pool->Shutdown();
+}
+
+#endif // !defined(USE_OPENSSL)
+
+// TODO(willchan): Write a longer test for SpdyStream that exercises all
+// methods.
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_http_utils.cc b/chromium/net/spdy/spdy_http_utils.cc
new file mode 100644
index 00000000000..21012f23fc1
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_utils.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_http_utils.h"
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
+ int protocol_version,
+ HttpResponseInfo* response) {
+ std::string status_key = (protocol_version >= 3) ? ":status" : "status";
+ std::string version_key = (protocol_version >= 3) ? ":version" : "version";
+ std::string version;
+ std::string status;
+
+ // The "status" and "version" headers are required.
+ SpdyHeaderBlock::const_iterator it;
+ it = headers.find(status_key);
+ if (it == headers.end())
+ return false;
+ status = it->second;
+
+ it = headers.find(version_key);
+ if (it == headers.end())
+ return false;
+ version = it->second;
+
+ std::string raw_headers(version);
+ raw_headers.push_back(' ');
+ raw_headers.append(status);
+ raw_headers.push_back('\0');
+ for (it = headers.begin(); it != headers.end(); ++it) {
+ // For each value, if the server sends a NUL-separated
+ // list of values, we separate that back out into
+ // individual headers for each value in the list.
+ // e.g.
+ // Set-Cookie "foo\0bar"
+ // becomes
+ // Set-Cookie: foo\0
+ // Set-Cookie: bar\0
+ std::string value = it->second;
+ size_t start = 0;
+ size_t end = 0;
+ do {
+ end = value.find('\0', start);
+ std::string tval;
+ if (end != value.npos)
+ tval = value.substr(start, (end - start));
+ else
+ tval = value.substr(start);
+ if (protocol_version >= 3 && it->first[0] == ':')
+ raw_headers.append(it->first.substr(1));
+ else
+ raw_headers.append(it->first);
+ raw_headers.push_back(':');
+ raw_headers.append(tval);
+ raw_headers.push_back('\0');
+ start = end + 1;
+ } while (end != value.npos);
+ }
+
+ response->headers = new HttpResponseHeaders(raw_headers);
+ response->was_fetched_via_spdy = true;
+ return true;
+}
+
+void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
+ const HttpRequestHeaders& request_headers,
+ SpdyHeaderBlock* headers,
+ int protocol_version,
+ bool direct) {
+
+ HttpRequestHeaders::Iterator it(request_headers);
+ while (it.GetNext()) {
+ std::string name = StringToLowerASCII(it.name());
+ if (name == "connection" || name == "proxy-connection" ||
+ name == "transfer-encoding") {
+ continue;
+ }
+ if (headers->find(name) == headers->end()) {
+ (*headers)[name] = it.value();
+ } else {
+ std::string new_value = (*headers)[name];
+ new_value.append(1, '\0'); // +=() doesn't append 0's
+ new_value += it.value();
+ (*headers)[name] = new_value;
+ }
+ }
+ static const char kHttpProtocolVersion[] = "HTTP/1.1";
+
+ if (protocol_version < 3) {
+ (*headers)["version"] = kHttpProtocolVersion;
+ (*headers)["method"] = info.method;
+ (*headers)["host"] = GetHostAndOptionalPort(info.url);
+ (*headers)["scheme"] = info.url.scheme();
+ if (direct)
+ (*headers)["url"] = HttpUtil::PathForRequest(info.url);
+ else
+ (*headers)["url"] = HttpUtil::SpecForRequest(info.url);
+ } else {
+ (*headers)[":version"] = kHttpProtocolVersion;
+ (*headers)[":method"] = info.method;
+ (*headers)[":host"] = GetHostAndOptionalPort(info.url);
+ (*headers)[":scheme"] = info.url.scheme();
+ (*headers)[":path"] = HttpUtil::PathForRequest(info.url);
+ headers->erase("host"); // this is kinda insane, spdy 3 spec.
+ }
+
+}
+
+COMPILE_ASSERT(HIGHEST - LOWEST < 4 &&
+ HIGHEST - MINIMUM_PRIORITY < 5,
+ request_priority_incompatible_with_spdy);
+
+SpdyPriority ConvertRequestPriorityToSpdyPriority(
+ const RequestPriority priority,
+ int protocol_version) {
+ DCHECK_GE(priority, MINIMUM_PRIORITY);
+ DCHECK_LT(priority, NUM_PRIORITIES);
+ if (protocol_version == 2) {
+ // SPDY 2 only has 2 bits of priority, but we have 5 RequestPriorities.
+ // Map IDLE => 3, LOWEST => 2, LOW => 2, MEDIUM => 1, HIGHEST => 0.
+ if (priority > LOWEST) {
+ return static_cast<SpdyPriority>(HIGHEST - priority);
+ } else {
+ return static_cast<SpdyPriority>(HIGHEST - priority - 1);
+ }
+ } else {
+ return static_cast<SpdyPriority>(HIGHEST - priority);
+ }
+}
+
+NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority(
+ SpdyPriority priority,
+ int protocol_version) {
+ // Handle invalid values gracefully, and pick LOW to map 2 back
+ // to for SPDY/2.
+ SpdyPriority idle_cutoff = (protocol_version == 2) ? 3 : 5;
+ return (priority >= idle_cutoff) ?
+ IDLE : static_cast<RequestPriority>(HIGHEST - priority);
+}
+
+GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers,
+ int protocol_version,
+ bool pushed) {
+ // SPDY 2 server push urls are specified in a single "url" header.
+ if (pushed && protocol_version == 2) {
+ std::string url;
+ SpdyHeaderBlock::const_iterator it;
+ it = headers.find("url");
+ if (it != headers.end())
+ url = it->second;
+ return GURL(url);
+ }
+
+ const char* scheme_header = protocol_version >= 3 ? ":scheme" : "scheme";
+ const char* host_header = protocol_version >= 3 ? ":host" : "host";
+ const char* path_header = protocol_version >= 3 ? ":path" : "url";
+
+ std::string scheme;
+ std::string host_port;
+ std::string path;
+ SpdyHeaderBlock::const_iterator it;
+ it = headers.find(scheme_header);
+ if (it != headers.end())
+ scheme = it->second;
+ it = headers.find(host_header);
+ if (it != headers.end())
+ host_port = it->second;
+ it = headers.find(path_header);
+ if (it != headers.end())
+ path = it->second;
+
+ std::string url = (scheme.empty() || host_port.empty() || path.empty())
+ ? std::string()
+ : scheme + "://" + host_port + path;
+ return GURL(url);
+}
+
+bool ShouldShowHttpHeaderValue(const std::string& header_name) {
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+ if (header_name == "proxy-authorization")
+ return false;
+#endif
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_http_utils.h b/chromium/net/spdy/spdy_http_utils.h
new file mode 100644
index 00000000000..26511a77834
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_utils.h
@@ -0,0 +1,59 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_HTTP_UTILS_H_
+#define NET_SPDY_SPDY_HTTP_UTILS_H_
+
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class HttpResponseInfo;
+struct HttpRequestInfo;
+class HttpRequestHeaders;
+
+// Convert a SpdyHeaderBlock into an HttpResponseInfo.
+// |headers| input parameter with the SpdyHeaderBlock.
+// |response| output parameter for the HttpResponseInfo.
+// Returns true if successfully converted. False if the SpdyHeaderBlock is
+// incomplete (e.g. missing 'status' or 'version').
+bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
+ int protocol_version,
+ HttpResponseInfo* response);
+
+// Create a SpdyHeaderBlock for a Spdy SYN_STREAM Frame from
+// HttpRequestInfo and HttpRequestHeaders.
+void NET_EXPORT_PRIVATE CreateSpdyHeadersFromHttpRequest(
+ const HttpRequestInfo& info,
+ const HttpRequestHeaders& request_headers,
+ SpdyHeaderBlock* headers,
+ int protocol_version,
+ bool direct);
+
+// Returns the URL associated with the |headers| by assembling the
+// scheme, host and path from the protocol specific keys.
+GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers,
+ int protocol_version,
+ bool pushed);
+
+// Returns true if the value of this header should be displayed.
+NET_EXPORT_PRIVATE bool ShouldShowHttpHeaderValue(
+ const std::string& header_name);
+
+NET_EXPORT_PRIVATE SpdyPriority ConvertRequestPriorityToSpdyPriority(
+ RequestPriority priority,
+ int protocol_version);
+
+NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority(
+ SpdyPriority priority,
+ int protocol_version);
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_HTTP_UTILS_H_
diff --git a/chromium/net/spdy/spdy_http_utils_unittest.cc b/chromium/net/spdy/spdy_http_utils_unittest.cc
new file mode 100644
index 00000000000..b198808c9b1
--- /dev/null
+++ b/chromium/net/spdy/spdy_http_utils_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_http_utils.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy2Priority) {
+ EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 2));
+ EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 2));
+ EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 2));
+ EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOWEST, 2));
+ EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(IDLE, 2));
+}
+
+TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy3Priority) {
+ EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 3));
+ EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 3));
+ EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 3));
+ EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(LOWEST, 3));
+ EXPECT_EQ(4, ConvertRequestPriorityToSpdyPriority(IDLE, 3));
+}
+
+TEST(SpdyHttpUtilsTest, ConvertSpdy2PriorityToRequestPriority) {
+ EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0, 2));
+ EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1, 2));
+ EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2, 2));
+ EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(3, 2));
+ // These are invalid values, but we should still handle them
+ // gracefully.
+ for (int i = 4; i < kuint8max; ++i) {
+ EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i, 2));
+ }
+}
+
+TEST(SpdyHttpUtilsTest, ConvertSpdy3PriorityToRequestPriority) {
+ EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0, 3));
+ EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1, 3));
+ EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2, 3));
+ EXPECT_EQ(LOWEST, ConvertSpdyPriorityToRequestPriority(3, 3));
+ EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(4, 3));
+ // These are invalid values, but we should still handle them
+ // gracefully.
+ for (int i = 5; i < kuint8max; ++i) {
+ EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i, 3));
+ }
+}
+
+TEST(SpdyHttpUtilsTest, ShowHttpHeaderValue){
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+ EXPECT_FALSE(ShouldShowHttpHeaderValue("proxy-authorization"));
+ EXPECT_TRUE(ShouldShowHttpHeaderValue("accept-encoding"));
+#else
+ EXPECT_TRUE(ShouldShowHttpHeaderValue("proxy-authorization"));
+ EXPECT_TRUE(ShouldShowHttpHeaderValue("accept-encoding"));
+#endif
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_network_transaction_unittest.cc b/chromium/net/spdy/spdy_network_transaction_unittest.cc
new file mode 100644
index 00000000000..f3f9f344995
--- /dev/null
+++ b/chromium/net/spdy/spdy_network_transaction_unittest.cc
@@ -0,0 +1,6405 @@
+// Copyright (c) 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.
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_vector.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "net/base/auth.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/http/http_network_session_peer.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/spdy/spdy_test_utils.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+namespace {
+const char kRequestUrl[] = "http://www.google.com/";
+
+enum SpdyNetworkTransactionTestSSLType {
+ SPDYNPN,
+ SPDYNOSSL,
+ SPDYSSL,
+};
+
+struct SpdyNetworkTransactionTestParams {
+ SpdyNetworkTransactionTestParams()
+ : protocol(kProtoSPDY2),
+ ssl_type(SPDYNPN) {}
+
+ SpdyNetworkTransactionTestParams(
+ NextProto protocol,
+ SpdyNetworkTransactionTestSSLType ssl_type)
+ : protocol(protocol),
+ ssl_type(ssl_type) {}
+
+ NextProto protocol;
+ SpdyNetworkTransactionTestSSLType ssl_type;
+};
+
+SpdySessionDependencies* CreateSpdySessionDependencies(
+ SpdyNetworkTransactionTestParams test_params) {
+ return new SpdySessionDependencies(test_params.protocol);
+}
+
+SpdySessionDependencies* CreateSpdySessionDependencies(
+ SpdyNetworkTransactionTestParams test_params,
+ ProxyService* proxy_service) {
+ return new SpdySessionDependencies(test_params.protocol, proxy_service);
+}
+
+} // namespace
+
+class SpdyNetworkTransactionTest
+ : public ::testing::TestWithParam<SpdyNetworkTransactionTestParams> {
+ protected:
+ SpdyNetworkTransactionTest() : spdy_util_(GetParam().protocol) {
+ }
+
+ virtual ~SpdyNetworkTransactionTest() {
+ // UploadDataStream posts deletion tasks back to the message loop on
+ // destruction.
+ upload_data_stream_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ virtual void SetUp() {
+ google_get_request_initialized_ = false;
+ google_post_request_initialized_ = false;
+ google_chunked_post_request_initialized_ = false;
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ struct TransactionHelperResult {
+ int rv;
+ std::string status_line;
+ std::string response_data;
+ HttpResponseInfo response_info;
+ };
+
+ // A helper class that handles all the initial npn/ssl setup.
+ class NormalSpdyTransactionHelper {
+ public:
+ NormalSpdyTransactionHelper(const HttpRequestInfo& request,
+ RequestPriority priority,
+ const BoundNetLog& log,
+ SpdyNetworkTransactionTestParams test_params,
+ SpdySessionDependencies* session_deps)
+ : request_(request),
+ priority_(priority),
+ session_deps_(session_deps == NULL ?
+ CreateSpdySessionDependencies(test_params) :
+ session_deps),
+ session_(SpdySessionDependencies::SpdyCreateSession(
+ session_deps_.get())),
+ log_(log),
+ test_params_(test_params),
+ deterministic_(false),
+ spdy_enabled_(true) {
+ switch (test_params_.ssl_type) {
+ case SPDYNOSSL:
+ case SPDYSSL:
+ port_ = 80;
+ break;
+ case SPDYNPN:
+ port_ = 443;
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ ~NormalSpdyTransactionHelper() {
+ // Any test which doesn't close the socket by sending it an EOF will
+ // have a valid session left open, which leaks the entire session pool.
+ // This is just fine - in fact, some of our tests intentionally do this
+ // so that we can check consistency of the SpdySessionPool as the test
+ // finishes. If we had put an EOF on the socket, the SpdySession would
+ // have closed and we wouldn't be able to check the consistency.
+
+ // Forcefully close existing sessions here.
+ session()->spdy_session_pool()->CloseAllSessions();
+ }
+
+ void SetDeterministic() {
+ session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ session_deps_.get());
+ deterministic_ = true;
+ }
+
+ void SetSpdyDisabled() {
+ spdy_enabled_ = false;
+ port_ = 80;
+ }
+
+ void RunPreTestSetup() {
+ if (!session_deps_.get())
+ session_deps_.reset(CreateSpdySessionDependencies(test_params_));
+ if (!session_.get())
+ session_ = SpdySessionDependencies::SpdyCreateSession(
+ session_deps_.get());
+ HttpStreamFactory::set_use_alternate_protocols(false);
+ HttpStreamFactory::set_force_spdy_over_ssl(false);
+ HttpStreamFactory::set_force_spdy_always(false);
+
+ std::vector<NextProto> next_protos = SpdyNextProtos();
+
+ switch (test_params_.ssl_type) {
+ case SPDYNPN:
+ session_->http_server_properties()->SetAlternateProtocol(
+ HostPortPair("www.google.com", 80), 443,
+ AlternateProtocolFromNextProto(test_params_.protocol));
+ HttpStreamFactory::set_use_alternate_protocols(true);
+ HttpStreamFactory::SetNextProtos(next_protos);
+ break;
+ case SPDYNOSSL:
+ HttpStreamFactory::set_force_spdy_over_ssl(false);
+ HttpStreamFactory::set_force_spdy_always(true);
+ break;
+ case SPDYSSL:
+ HttpStreamFactory::set_force_spdy_over_ssl(true);
+ HttpStreamFactory::set_force_spdy_always(true);
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ // We're now ready to use SSL-npn SPDY.
+ trans_.reset(new HttpNetworkTransaction(priority_, session_.get()));
+ }
+
+ // Start the transaction, read some data, finish.
+ void RunDefaultTest() {
+ if (!StartDefaultTest())
+ return;
+ FinishDefaultTest();
+ }
+
+ bool StartDefaultTest() {
+ output_.rv = trans_->Start(&request_, callback.callback(), log_);
+
+ // We expect an IO Pending or some sort of error.
+ EXPECT_LT(output_.rv, 0);
+ return output_.rv == ERR_IO_PENDING;
+ }
+
+ void FinishDefaultTest() {
+ output_.rv = callback.WaitForResult();
+ if (output_.rv != OK) {
+ session_->spdy_session_pool()->CloseCurrentSessions(net::ERR_ABORTED);
+ return;
+ }
+
+ // Verify responses.
+ const HttpResponseInfo* response = trans_->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy);
+ if (HttpStreamFactory::spdy_enabled()) {
+ EXPECT_EQ(
+ HttpResponseInfo::ConnectionInfoFromNextProto(
+ test_params_.protocol),
+ response->connection_info);
+ } else {
+ EXPECT_EQ(HttpResponseInfo::CONNECTION_INFO_HTTP1,
+ response->connection_info);
+ }
+ if (test_params_.ssl_type == SPDYNPN && spdy_enabled_) {
+ EXPECT_TRUE(response->was_npn_negotiated);
+ } else {
+ EXPECT_TRUE(!response->was_npn_negotiated);
+ }
+ // If SPDY is not enabled, a HTTP request should not be diverted
+ // over a SSL session.
+ if (!spdy_enabled_) {
+ EXPECT_EQ(request_.url.SchemeIs("https"),
+ response->was_npn_negotiated);
+ }
+ EXPECT_EQ("127.0.0.1", response->socket_address.host());
+ EXPECT_EQ(port_, response->socket_address.port());
+ output_.status_line = response->headers->GetStatusLine();
+ output_.response_info = *response; // Make a copy so we can verify.
+ output_.rv = ReadTransaction(trans_.get(), &output_.response_data);
+ }
+
+ // Most tests will want to call this function. In particular, the MockReads
+ // should end with an empty read, and that read needs to be processed to
+ // ensure proper deletion of the spdy_session_pool.
+ void VerifyDataConsumed() {
+ for (DataVector::iterator it = data_vector_.begin();
+ it != data_vector_.end(); ++it) {
+ EXPECT_TRUE((*it)->at_read_eof()) << "Read count: "
+ << (*it)->read_count()
+ << " Read index: "
+ << (*it)->read_index();
+ EXPECT_TRUE((*it)->at_write_eof()) << "Write count: "
+ << (*it)->write_count()
+ << " Write index: "
+ << (*it)->write_index();
+ }
+ }
+
+ // Occasionally a test will expect to error out before certain reads are
+ // processed. In that case we want to explicitly ensure that the reads were
+ // not processed.
+ void VerifyDataNotConsumed() {
+ for (DataVector::iterator it = data_vector_.begin();
+ it != data_vector_.end(); ++it) {
+ EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: "
+ << (*it)->read_count()
+ << " Read index: "
+ << (*it)->read_index();
+ EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: "
+ << (*it)->write_count()
+ << " Write index: "
+ << (*it)->write_index();
+ }
+ }
+
+ void RunToCompletion(StaticSocketDataProvider* data) {
+ RunPreTestSetup();
+ AddData(data);
+ RunDefaultTest();
+ VerifyDataConsumed();
+ }
+
+ void AddData(StaticSocketDataProvider* data) {
+ DCHECK(!deterministic_);
+ data_vector_.push_back(data);
+ SSLSocketDataProvider* ssl_provider =
+ new SSLSocketDataProvider(ASYNC, OK);
+ if (test_params_.ssl_type == SPDYNPN)
+ ssl_provider->SetNextProto(test_params_.protocol);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_params_.ssl_type == SPDYNPN || test_params_.ssl_type == SPDYSSL)
+ session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider);
+
+ session_deps_->socket_factory->AddSocketDataProvider(data);
+ if (test_params_.ssl_type == SPDYNPN) {
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ StaticSocketDataProvider* hanging_non_alternate_protocol_socket =
+ new StaticSocketDataProvider(NULL, 0, NULL, 0);
+ hanging_non_alternate_protocol_socket->set_connect_data(
+ never_finishing_connect);
+ session_deps_->socket_factory->AddSocketDataProvider(
+ hanging_non_alternate_protocol_socket);
+ alternate_vector_.push_back(hanging_non_alternate_protocol_socket);
+ }
+ }
+
+ void AddDeterministicData(DeterministicSocketData* data) {
+ DCHECK(deterministic_);
+ data_vector_.push_back(data);
+ SSLSocketDataProvider* ssl_provider =
+ new SSLSocketDataProvider(ASYNC, OK);
+ if (test_params_.ssl_type == SPDYNPN)
+ ssl_provider->SetNextProto(test_params_.protocol);
+
+ ssl_vector_.push_back(ssl_provider);
+ if (test_params_.ssl_type == SPDYNPN ||
+ test_params_.ssl_type == SPDYSSL) {
+ session_deps_->deterministic_socket_factory->
+ AddSSLSocketDataProvider(ssl_provider);
+ }
+ session_deps_->deterministic_socket_factory->AddSocketDataProvider(data);
+ if (test_params_.ssl_type == SPDYNPN) {
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+ DeterministicSocketData* hanging_non_alternate_protocol_socket =
+ new DeterministicSocketData(NULL, 0, NULL, 0);
+ hanging_non_alternate_protocol_socket->set_connect_data(
+ never_finishing_connect);
+ session_deps_->deterministic_socket_factory->AddSocketDataProvider(
+ hanging_non_alternate_protocol_socket);
+ alternate_deterministic_vector_.push_back(
+ hanging_non_alternate_protocol_socket);
+ }
+ }
+
+ void SetSession(const scoped_refptr<HttpNetworkSession>& session) {
+ session_ = session;
+ }
+ HttpNetworkTransaction* trans() { return trans_.get(); }
+ void ResetTrans() { trans_.reset(); }
+ TransactionHelperResult& output() { return output_; }
+ const HttpRequestInfo& request() const { return request_; }
+ const scoped_refptr<HttpNetworkSession>& session() const {
+ return session_;
+ }
+ scoped_ptr<SpdySessionDependencies>& session_deps() {
+ return session_deps_;
+ }
+ int port() const { return port_; }
+ SpdyNetworkTransactionTestParams test_params() const {
+ return test_params_;
+ }
+
+ private:
+ typedef std::vector<StaticSocketDataProvider*> DataVector;
+ typedef ScopedVector<SSLSocketDataProvider> SSLVector;
+ typedef ScopedVector<StaticSocketDataProvider> AlternateVector;
+ typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector;
+ HttpRequestInfo request_;
+ RequestPriority priority_;
+ scoped_ptr<SpdySessionDependencies> session_deps_;
+ scoped_refptr<HttpNetworkSession> session_;
+ TransactionHelperResult output_;
+ scoped_ptr<StaticSocketDataProvider> first_transaction_;
+ SSLVector ssl_vector_;
+ TestCompletionCallback callback;
+ scoped_ptr<HttpNetworkTransaction> trans_;
+ scoped_ptr<HttpNetworkTransaction> trans_http_;
+ DataVector data_vector_;
+ AlternateVector alternate_vector_;
+ AlternateDeterministicVector alternate_deterministic_vector_;
+ const BoundNetLog& log_;
+ SpdyNetworkTransactionTestParams test_params_;
+ int port_;
+ bool deterministic_;
+ bool spdy_enabled_;
+ };
+
+ void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+ int expected_status);
+
+ void ConnectStatusHelper(const MockRead& status);
+
+ const HttpRequestInfo& CreateGetPushRequest() {
+ google_get_push_request_.method = "GET";
+ google_get_push_request_.url = GURL("http://www.google.com/foo.dat");
+ google_get_push_request_.load_flags = 0;
+ return google_get_push_request_;
+ }
+
+ const HttpRequestInfo& CreateGetRequest() {
+ if (!google_get_request_initialized_) {
+ google_get_request_.method = "GET";
+ google_get_request_.url = GURL(kDefaultURL);
+ google_get_request_.load_flags = 0;
+ google_get_request_initialized_ = true;
+ }
+ return google_get_request_;
+ }
+
+ const HttpRequestInfo& CreateGetRequestWithUserAgent() {
+ if (!google_get_request_initialized_) {
+ google_get_request_.method = "GET";
+ google_get_request_.url = GURL(kDefaultURL);
+ google_get_request_.load_flags = 0;
+ google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome");
+ google_get_request_initialized_ = true;
+ }
+ return google_get_request_;
+ }
+
+ const HttpRequestInfo& CreatePostRequest() {
+ if (!google_post_request_initialized_) {
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadBytesElementReader(kUploadData, kUploadDataSize));
+ upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+ google_post_request_.method = "POST";
+ google_post_request_.url = GURL(kDefaultURL);
+ google_post_request_.upload_data_stream = upload_data_stream_.get();
+ google_post_request_initialized_ = true;
+ }
+ return google_post_request_;
+ }
+
+ const HttpRequestInfo& CreateFilePostRequest() {
+ if (!google_post_request_initialized_) {
+ base::FilePath file_path;
+ CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+ CHECK_EQ(static_cast<int>(kUploadDataSize),
+ file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ file_path,
+ 0,
+ kUploadDataSize,
+ base::Time()));
+ upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+ google_post_request_.method = "POST";
+ google_post_request_.url = GURL(kDefaultURL);
+ google_post_request_.upload_data_stream = upload_data_stream_.get();
+ google_post_request_initialized_ = true;
+ }
+ return google_post_request_;
+ }
+
+ const HttpRequestInfo& CreateComplexPostRequest() {
+ if (!google_post_request_initialized_) {
+ const int kFileRangeOffset = 1;
+ const int kFileRangeLength = 3;
+ CHECK_LT(kFileRangeOffset + kFileRangeLength, kUploadDataSize);
+
+ base::FilePath file_path;
+ CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+ CHECK_EQ(static_cast<int>(kUploadDataSize),
+ file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadBytesElementReader(kUploadData, kFileRangeOffset));
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ file_path,
+ kFileRangeOffset,
+ kFileRangeLength,
+ base::Time()));
+ element_readers.push_back(new UploadBytesElementReader(
+ kUploadData + kFileRangeOffset + kFileRangeLength,
+ kUploadDataSize - (kFileRangeOffset + kFileRangeLength)));
+ upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+ google_post_request_.method = "POST";
+ google_post_request_.url = GURL(kDefaultURL);
+ google_post_request_.upload_data_stream = upload_data_stream_.get();
+ google_post_request_initialized_ = true;
+ }
+ return google_post_request_;
+ }
+
+ const HttpRequestInfo& CreateChunkedPostRequest() {
+ if (!google_chunked_post_request_initialized_) {
+ upload_data_stream_.reset(
+ new UploadDataStream(UploadDataStream::CHUNKED, 0));
+ google_chunked_post_request_.method = "POST";
+ google_chunked_post_request_.url = GURL(kDefaultURL);
+ google_chunked_post_request_.upload_data_stream =
+ upload_data_stream_.get();
+ google_chunked_post_request_initialized_ = true;
+ }
+ return google_chunked_post_request_;
+ }
+
+ // Read the result of a particular transaction, knowing that we've got
+ // multiple transactions in the read pipeline; so as we read, we may have
+ // to skip over data destined for other transactions while we consume
+ // the data for |trans|.
+ int ReadResult(HttpNetworkTransaction* trans,
+ StaticSocketDataProvider* data,
+ std::string* result) {
+ const int kSize = 3000;
+
+ int bytes_read = 0;
+ scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize));
+ TestCompletionCallback callback;
+ while (true) {
+ int rv = trans->Read(buf.get(), kSize, callback.callback());
+ if (rv == ERR_IO_PENDING) {
+ // Multiple transactions may be in the data set. Keep pulling off
+ // reads until we complete our callback.
+ while (!callback.have_result()) {
+ data->CompleteRead();
+ base::RunLoop().RunUntilIdle();
+ }
+ rv = callback.WaitForResult();
+ } else if (rv <= 0) {
+ break;
+ }
+ result->append(buf->data(), rv);
+ bytes_read += rv;
+ }
+ return bytes_read;
+ }
+
+ void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) {
+ // This lengthy block is reaching into the pool to dig out the active
+ // session. Once we have the session, we verify that the streams are
+ // all closed and not leaked at this point.
+ const GURL& url = helper.request().url;
+ int port = helper.test_params().ssl_type == SPDYNPN ? 443 : 80;
+ HostPortPair host_port_pair(url.host(), port);
+ SpdySessionKey key(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ BoundNetLog log;
+ const scoped_refptr<HttpNetworkSession>& session = helper.session();
+ base::WeakPtr<SpdySession> spdy_session =
+ session->spdy_session_pool()->FindAvailableSession(key, log);
+ ASSERT_TRUE(spdy_session != NULL);
+ EXPECT_EQ(0u, spdy_session->num_active_streams());
+ EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams());
+ }
+
+ void RunServerPushTest(OrderedSocketData* data,
+ HttpResponseInfo* response,
+ HttpResponseInfo* push_response,
+ const std::string& expected) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // Request the pushed path.
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ base::RunLoop().RunUntilIdle();
+
+ // The data for the pushed path may be coming in more than 1 frame. Compile
+ // the results into a single string.
+
+ // Read the server push body.
+ std::string result2;
+ ReadResult(trans2.get(), data, &result2);
+ // Read the response body.
+ std::string result;
+ ReadResult(trans, data, &result);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data->at_read_eof());
+ EXPECT_TRUE(data->at_write_eof());
+
+ // Verify that the received push data is same as the expected push data.
+ EXPECT_EQ(result2.compare(expected), 0) << "Received data: "
+ << result2
+ << "||||| Expected data: "
+ << expected;
+
+ // Verify the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ *response = *trans->GetResponseInfo();
+ *push_response = *trans2->GetResponseInfo();
+
+ VerifyStreamsClosed(helper);
+ }
+
+ static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper,
+ int result) {
+ helper->ResetTrans();
+ }
+
+ static void StartTransactionCallback(
+ const scoped_refptr<HttpNetworkSession>& session,
+ int result) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+ TestCompletionCallback callback;
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ callback.WaitForResult();
+ }
+
+ SpdyTestUtil spdy_util_;
+
+ private:
+ scoped_ptr<UploadDataStream> upload_data_stream_;
+ bool google_get_request_initialized_;
+ bool google_post_request_initialized_;
+ bool google_chunked_post_request_initialized_;
+ HttpRequestInfo google_get_request_;
+ HttpRequestInfo google_post_request_;
+ HttpRequestInfo google_chunked_post_request_;
+ HttpRequestInfo google_get_push_request_;
+ base::ScopedTempDir temp_dir_;
+};
+
+//-----------------------------------------------------------------------------
+// All tests are run with three different connection types: SPDY after NPN
+// negotiation, SPDY without SSL, and SPDY with SSL.
+//
+// TODO(akalin): Use ::testing::Combine() when we are able to use
+// <tr1/tuple>.
+INSTANTIATE_TEST_CASE_P(
+ Spdy,
+ SpdyNetworkTransactionTest,
+ ::testing::Values(
+ SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYNOSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY2, SPDYNPN),
+ SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYNOSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY3, SPDYNPN),
+ SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYNOSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY31, SPDYNPN),
+ SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYNOSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYSSL),
+ SpdyNetworkTransactionTestParams(kProtoSPDY4a2, SPDYNPN),
+ SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYNOSSL),
+ SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYSSL),
+ SpdyNetworkTransactionTestParams(kProtoHTTP2Draft04, SPDYNPN)));
+
+// Verify HttpNetworkTransaction constructor.
+TEST_P(SpdyNetworkTransactionTest, Constructor) {
+ scoped_ptr<SpdySessionDependencies> session_deps(
+ CreateSpdySessionDependencies(GetParam()));
+ scoped_refptr<HttpNetworkSession> session(
+ SpdySessionDependencies::SpdyCreateSession(session_deps.get()));
+ scoped_ptr<HttpTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
+}
+
+TEST_P(SpdyNetworkTransactionTest, Get) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionTest, GetAtEachPriority) {
+ for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES;
+ p = RequestPriority(p + 1)) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, p, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ SpdyPriority spdy_prio = 0;
+ EXPECT_TRUE(GetSpdyPriority(spdy_util_.spdy_version(), *req, &spdy_prio));
+ // this repeats the RequestPriority-->SpdyPriority mapping from
+ // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make
+ // sure it's being done right.
+ if (spdy_util_.spdy_version() < SPDY3) {
+ switch(p) {
+ case HIGHEST:
+ EXPECT_EQ(0, spdy_prio);
+ break;
+ case MEDIUM:
+ EXPECT_EQ(1, spdy_prio);
+ break;
+ case LOW:
+ case LOWEST:
+ EXPECT_EQ(2, spdy_prio);
+ break;
+ case IDLE:
+ EXPECT_EQ(3, spdy_prio);
+ break;
+ default:
+ FAIL();
+ }
+ } else {
+ switch(p) {
+ case HIGHEST:
+ EXPECT_EQ(0, spdy_prio);
+ break;
+ case MEDIUM:
+ EXPECT_EQ(1, spdy_prio);
+ break;
+ case LOW:
+ EXPECT_EQ(2, spdy_prio);
+ break;
+ case LOWEST:
+ EXPECT_EQ(3, spdy_prio);
+ break;
+ case IDLE:
+ EXPECT_EQ(4, spdy_prio);
+ break;
+ default:
+ FAIL();
+ }
+ }
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ HttpRequestInfo http_req = CreateGetRequest();
+
+ NormalSpdyTransactionHelper helper(http_req, p, BoundNetLog(),
+ GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+ }
+}
+
+// Start three gets simultaniously; making sure that multiplexed
+// streams work properly.
+
+// This can't use the TransactionHelper method, since it only
+// handles a single transaction, and finishes them as soon
+// as it launches them.
+
+// TODO(gavinp): create a working generalized TransactionHelper that
+// can allow multiple streams in flight.
+
+TEST_P(SpdyNetworkTransactionTest, ThreeGets) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(5, true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req3),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*body),
+ CreateMockRead(*resp2, 4),
+ CreateMockRead(*body2),
+ CreateMockRead(*resp3, 7),
+ CreateMockRead(*body3),
+
+ CreateMockRead(*fbody),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*fbody3),
+
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because three get requests are sent out, so
+ // there needs to be three sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+
+ trans2->GetResponseInfo();
+
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ helper.VerifyDataConsumed();
+ EXPECT_EQ(OK, out.rv);
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBinding) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*body),
+ CreateMockRead(*resp2, 4),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody),
+ CreateMockRead(*fbody2),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+ data_placeholder.set_connect_data(never_finishing_connect);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because two get requests are sent out, so
+ // there needs to be two sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ EXPECT_TRUE(response2->headers.get() != NULL);
+ EXPECT_TRUE(response2->was_fetched_via_spdy);
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionTest, TwoGetsLateBindingFromPreconnect) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*body),
+ CreateMockRead(*resp2, 4),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody),
+ CreateMockRead(*fbody2),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+ OrderedSocketData preconnect_data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING);
+
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+ data_placeholder.set_connect_data(never_finishing_connect);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&preconnect_data);
+ // We require placeholder data because 3 connections are attempted (first is
+ // the preconnect, 2nd and 3rd are the never finished connections.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+
+ HttpRequestInfo httpreq = CreateGetRequest();
+
+ // Preconnect the first.
+ SSLConfig preconnect_ssl_config;
+ helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config);
+ HttpStreamFactory* http_stream_factory =
+ helper.session()->http_stream_factory();
+ if (http_stream_factory->has_next_protos()) {
+ preconnect_ssl_config.next_protos = http_stream_factory->next_protos();
+ }
+
+ http_stream_factory->PreconnectStreams(
+ 1, httpreq, DEFAULT_PRIORITY,
+ preconnect_ssl_config, preconnect_ssl_config);
+
+ out.rv = trans1->Start(&httpreq, callback1.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans2->Start(&httpreq, callback2.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ EXPECT_TRUE(response2->headers.get() != NULL);
+ EXPECT_TRUE(response2->was_fetched_via_spdy);
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ helper.VerifyDataConsumed();
+}
+
+// Similar to ThreeGets above, however this test adds a SETTINGS
+// frame. The SETTINGS frame is read during the IO loop waiting on
+// the first transaction completion, and sets a maximum concurrent
+// stream limit of 1. This means that our IO loop exists after the
+// second transaction completes, so we can assert on read_index().
+TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrent) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, false));
+ scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(5, true));
+
+ SettingsMap settings;
+ const uint32 max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req3),
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 1),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 7),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*resp3, 12),
+ CreateMockRead(*body3),
+ CreateMockRead(*fbody3),
+
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because three get requests are sent out, so
+ // there needs to be three sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ // Run transaction 1 through quickly to force a read of our SETTINGS
+ // frame.
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ EXPECT_EQ(7U, data.read_index()); // i.e. the third trans was queued
+
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ ASSERT_TRUE(response1 != NULL);
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+ out.status_line = response3->headers->GetStatusLine();
+ out.response_info = *response3;
+ out.rv = ReadTransaction(trans3.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ helper.VerifyDataConsumed();
+ }
+ EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsWithMaxConcurrent above, however this test adds
+// a fourth transaction. The third and fourth transactions have
+// different data ("hello!" vs "hello!hello!") and because of the
+// user specified priority, we expect to see them inverted in
+// the response from the server.
+TEST_P(SpdyNetworkTransactionTest, FourGetsWithMaxConcurrentPriority) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> req4(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, HIGHEST, true));
+ scoped_ptr<SpdyFrame> resp4(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> fbody4(spdy_util_.ConstructSpdyBodyFrame(5, true));
+
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 7, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 7));
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(7, false));
+ scoped_ptr<SpdyFrame> fbody3(spdy_util_.ConstructSpdyBodyFrame(7, true));
+
+ SettingsMap settings;
+ const uint32 max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ CreateMockWrite(*req4),
+ CreateMockWrite(*req3),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 1),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 7),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ CreateMockRead(*resp4, 13),
+ CreateMockRead(*fbody4),
+ CreateMockRead(*resp3, 16),
+ CreateMockRead(*body3),
+ CreateMockRead(*fbody3),
+
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because four get requests are sent out, so
+ // there needs to be four sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans4(
+ new HttpNetworkTransaction(HIGHEST, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+ TestCompletionCallback callback4;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+ HttpRequestInfo httpreq4 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+ out.rv = trans4->Start(&httpreq4, callback4.callback(), log);
+ ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+ EXPECT_EQ(data.read_index(), 7U); // i.e. the third & fourth trans queued
+
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ // notice: response3 gets two hellos, response4 gets one
+ // hello, so we know dequeuing priority was respected.
+ const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+ out.status_line = response3->headers->GetStatusLine();
+ out.response_info = *response3;
+ out.rv = ReadTransaction(trans3.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ out.rv = callback4.WaitForResult();
+ EXPECT_EQ(OK, out.rv);
+ const HttpResponseInfo* response4 = trans4->GetResponseInfo();
+ out.status_line = response4->headers->GetStatusLine();
+ out.response_info = *response4;
+ out.rv = ReadTransaction(trans4.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+ helper.VerifyDataConsumed();
+ EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsMaxConcurrrent above, however, this test
+// deletes a session in the middle of the transaction to insure
+// that we properly remove pendingcreatestream objects from
+// the spdy_session
+TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentDelete) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fbody(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, false));
+ scoped_ptr<SpdyFrame> fbody2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ SettingsMap settings;
+ const uint32 max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 1),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fbody),
+ CreateMockRead(*resp2, 7),
+ CreateMockRead(*body2),
+ CreateMockRead(*fbody2),
+ MockRead(ASYNC, 0, 0), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because three get requests are sent out, so
+ // there needs to be three sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ scoped_ptr<HttpNetworkTransaction> trans1(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+ delete trans3.release();
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback2.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ EXPECT_EQ(8U, data.read_index());
+
+ const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+ ASSERT_TRUE(response1 != NULL);
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(trans1.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+
+ const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+ ASSERT_TRUE(response2 != NULL);
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(trans2.get(), &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!hello!", out.response_data);
+ helper.VerifyDataConsumed();
+ EXPECT_EQ(OK, out.rv);
+}
+
+namespace {
+
+// The KillerCallback will delete the transaction on error as part of the
+// callback.
+class KillerCallback : public TestCompletionCallbackBase {
+ public:
+ explicit KillerCallback(HttpNetworkTransaction* transaction)
+ : transaction_(transaction),
+ callback_(base::Bind(&KillerCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~KillerCallback() {}
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ if (result < 0)
+ delete transaction_;
+
+ SetResult(result);
+ }
+
+ HttpNetworkTransaction* transaction_;
+ CompletionCallback callback_;
+};
+
+} // namespace
+
+// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test
+// closes the socket while we have a pending transaction waiting for
+// a pending stream creation. http://crbug.com/52901
+TEST_P(SpdyNetworkTransactionTest, ThreeGetsWithMaxConcurrentSocketClose) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> fin_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+
+ SettingsMap settings;
+ const uint32 max_concurrent_streams = 1;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ MockWrite writes[] = { CreateMockWrite(*req),
+ CreateMockWrite(*req2),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 1),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ CreateMockRead(*fin_body),
+ CreateMockRead(*resp2, 7),
+ MockRead(ASYNC, ERR_CONNECTION_RESET, 0), // Abort!
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+ BoundNetLog log;
+ TransactionHelperResult out;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ // We require placeholder data because three get requests are sent out, so
+ // there needs to be three sets of SSL connection data.
+ helper.AddData(&data_placeholder);
+ helper.AddData(&data_placeholder);
+ HttpNetworkTransaction trans1(DEFAULT_PRIORITY, helper.session().get());
+ HttpNetworkTransaction trans2(DEFAULT_PRIORITY, helper.session().get());
+ HttpNetworkTransaction* trans3(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback1;
+ TestCompletionCallback callback2;
+ KillerCallback callback3(trans3);
+
+ HttpRequestInfo httpreq1 = CreateGetRequest();
+ HttpRequestInfo httpreq2 = CreateGetRequest();
+ HttpRequestInfo httpreq3 = CreateGetRequest();
+
+ out.rv = trans1.Start(&httpreq1, callback1.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+ out.rv = callback1.WaitForResult();
+ ASSERT_EQ(OK, out.rv);
+
+ out.rv = trans2.Start(&httpreq2, callback2.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+ ASSERT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback3.WaitForResult();
+ ASSERT_EQ(ERR_ABORTED, out.rv);
+
+ EXPECT_EQ(6U, data.read_index());
+
+ const HttpResponseInfo* response1 = trans1.GetResponseInfo();
+ ASSERT_TRUE(response1 != NULL);
+ EXPECT_TRUE(response1->headers.get() != NULL);
+ EXPECT_TRUE(response1->was_fetched_via_spdy);
+ out.status_line = response1->headers->GetStatusLine();
+ out.response_info = *response1;
+ out.rv = ReadTransaction(&trans1, &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+
+ const HttpResponseInfo* response2 = trans2.GetResponseInfo();
+ ASSERT_TRUE(response2 != NULL);
+ out.status_line = response2->headers->GetStatusLine();
+ out.response_info = *response2;
+ out.rv = ReadTransaction(&trans2, &out.response_data);
+ EXPECT_EQ(ERR_CONNECTION_RESET, out.rv);
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that a simple PUT request works.
+TEST_P(SpdyNetworkTransactionTest, Put) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "PUT";
+ request.url = GURL("http://www.google.com/");
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM, // Kind = Syn
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ scoped_ptr<SpdyHeaderBlock> put_headers(
+ spdy_util_.ConstructPutHeaderBlock("http://www.google.com", 0));
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, put_headers.Pass()));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock());
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ (*reply_headers)["content-length"] = "1234";
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyFrame(
+ kSynReplyHeader, reply_headers.Pass()));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple HEAD request works.
+TEST_P(SpdyNetworkTransactionTest, Head) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "HEAD";
+ request.url = GURL("http://www.google.com/");
+
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM, // Kind = Syn
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ scoped_ptr<SpdyHeaderBlock> head_headers(
+ spdy_util_.ConstructHeadHeaderBlock("http://www.google.com", 0));
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, head_headers.Pass()));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock());
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ (*reply_headers)["content-length"] = "1234";
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyFrame(
+ kSynReplyHeader,
+ reply_headers.Pass()));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionTest, Post) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreatePostRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a POST with a file works.
+TEST_P(SpdyNetworkTransactionTest, FilePost) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateFilePostRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a complex POST works.
+TEST_P(SpdyNetworkTransactionTest, ComplexPost) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body), // POST upload frame
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateComplexPostRequest(),
+ DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a chunked POST works.
+TEST_P(SpdyNetworkTransactionTest, ChunkedPost) {
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*body),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+ DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ // These chunks get merged into a single frame when being sent.
+ const int kFirstChunkSize = kUploadDataSize/2;
+ helper.request().upload_data_stream->AppendChunk(
+ kUploadData, kFirstChunkSize, false);
+ helper.request().upload_data_stream->AppendChunk(
+ kUploadData + kFirstChunkSize, kUploadDataSize - kFirstChunkSize, true);
+
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ(kUploadData, out.response_data);
+}
+
+// Test that a chunked POST works with chunks appended after transaction starts.
+TEST_P(SpdyNetworkTransactionTest, DelayedChunkedPost) {
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0));
+ scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk2(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> chunk3(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*chunk1),
+ CreateMockWrite(*chunk2),
+ CreateMockWrite(*chunk3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*chunk1),
+ CreateMockRead(*chunk2),
+ CreateMockRead(*chunk3),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(4, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+ DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.request().upload_data_stream->AppendChunk(
+ kUploadData, kUploadDataSize, false);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ ASSERT_TRUE(helper.StartDefaultTest());
+
+ base::RunLoop().RunUntilIdle();
+ helper.request().upload_data_stream->AppendChunk(
+ kUploadData, kUploadDataSize, false);
+ base::RunLoop().RunUntilIdle();
+ helper.request().upload_data_stream->AppendChunk(
+ kUploadData, kUploadDataSize, true);
+
+ helper.FinishDefaultTest();
+ helper.VerifyDataConsumed();
+
+ std::string expected_response;
+ expected_response += kUploadData;
+ expected_response += kUploadData;
+ expected_response += kUploadData;
+
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ(expected_response, out.response_data);
+}
+
+// Test that a POST without any post data works.
+TEST_P(SpdyNetworkTransactionTest, NullPost) {
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL(kRequestUrl);
+ // Create an empty UploadData.
+ request.upload_data_stream = NULL;
+
+ // When request.upload_data_stream is NULL for post, content-length is
+ // expected to be 0.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(kRequestUrl, 1, 0, LOWEST, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ test::SetFrameFlags(req.get(), CONTROL_FLAG_FIN, spdy_util_.spdy_version());
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionTest, EmptyPost) {
+ // Create an empty UploadDataStream.
+ ScopedVector<UploadElementReader> element_readers;
+ UploadDataStream stream(&element_readers, 0);
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL(kRequestUrl);
+ request.upload_data_stream = &stream;
+
+ const uint64 kContentLength = 0;
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kContentLength, LOWEST, NULL, 0));
+ // Set the FIN bit since there will be no body.
+ test::SetFrameFlags(req.get(), CONTROL_FLAG_FIN, spdy_util_.spdy_version());
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads), writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// While we're doing a post, the server sends back a SYN_REPLY.
+TEST_P(SpdyNetworkTransactionTest, PostWithEarlySynReply) {
+ static const char upload[] = { "hello!" };
+ ScopedVector<UploadElementReader> element_readers;
+ element_readers.push_back(
+ new UploadBytesElementReader(upload, sizeof(upload)));
+ UploadDataStream stream(&element_readers, 0);
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL(kRequestUrl);
+ request.upload_data_stream = &stream;
+
+ scoped_ptr<SpdyFrame> stream_reply(
+ spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*stream_reply, 1),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kUploadDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 2),
+ CreateMockWrite(*rst, 3)
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreatePostRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreatePostRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.RunFor(4);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+ data.RunFor(1);
+}
+
+// The client upon cancellation tries to send a RST_STREAM frame. The mock
+// socket causes the TCP write to return zero. This test checks that the client
+// tries to queue up the RST_STREAM frame again.
+TEST_P(SpdyNetworkTransactionTest, SocketWriteReturnsZero) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req.get(), 0, SYNCHRONOUS),
+ MockWrite(SYNCHRONOUS, 0, 0, 2),
+ CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp.get(), 1, ASYNC),
+ MockRead(ASYNC, 0, 0, 4) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.SetStop(2);
+ data.Run();
+ helper.ResetTrans();
+ data.SetStop(20);
+ data.Run();
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that the transaction doesn't crash when we don't have a reply.
+TEST_P(SpdyNetworkTransactionTest, ResponseWithoutSynReply) {
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads), NULL, 0);
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Test that the transaction doesn't crash when we get two replies on the same
+// stream ID. See http://crbug.com/45639.
+TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_STREAM_IN_USE));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ std::string response_data;
+ rv = ReadTransaction(trans, &response_data);
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+
+ helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionTest, ResetReplyWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ const char* const headers[] = {
+ "transfer-encoding", "chunked"
+ };
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(headers, 1, 1));
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+
+ helper.session()->spdy_session_pool()->CloseAllSessions();
+ helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionTest, ResetPushWithTransferEncoding) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char* const headers[] = {
+ "transfer-encoding", "chunked"
+ };
+ scoped_ptr<SpdyFrame> push(
+ spdy_util_.ConstructSpdyPush(headers, arraysize(headers) / 2,
+ 2, 1, "http://www.google.com/1"));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*push),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ helper.session()->spdy_session_pool()->CloseAllSessions();
+ helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ // This following read isn't used by the test, except during the
+ // RunUntilIdle() call at the end since the SpdySession survives the
+ // HttpNetworkTransaction and still tries to continue Read()'ing. Any
+ // MockRead will do here.
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ helper.ResetTrans(); // Cancel the transaction.
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ base::RunLoop().RunUntilIdle();
+ helper.VerifyDataNotConsumed();
+}
+
+// Verify that the client sends a Rst Frame upon cancelling the stream.
+TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0, SYNCHRONOUS),
+ CreateMockWrite(*rst, 2, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 0, 3) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(),
+ GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.SetStop(2);
+ data.Run();
+ helper.ResetTrans();
+ data.SetStop(20);
+ data.Run();
+
+ helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback attempting
+// to start another transaction on a session that is closing down. See
+// http://crbug.com/47455
+TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+ MockWrite writes2[] = { CreateMockWrite(*req) };
+
+ // The indicated length of this frame is longer than its actual length. When
+ // the session receives an empty frame after this one, it shuts down the
+ // session, and calls the read callback with the incomplete data.
+ const uint8 kGetBodyFrame2[] = {
+ 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x07,
+ 'h', 'e', 'l', 'l', 'o', '!',
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2),
+ arraysize(kGetBodyFrame2), 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause
+ MockRead(ASYNC, 0, 0, 6), // EOF
+ };
+ MockRead reads2[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(ASYNC, 0, 0, 3), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ DelayedSocketData data2(1, reads2, arraysize(reads2),
+ writes2, arraysize(writes2));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ helper.AddData(&data2);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ const int kSize = 3000;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ rv = trans->Read(
+ buf.get(),
+ kSize,
+ base::Bind(&SpdyNetworkTransactionTest::StartTransactionCallback,
+ helper.session()));
+ // This forces an err_IO_pending, which sets the callback.
+ data.CompleteRead();
+ // This finishes the read.
+ data.CompleteRead();
+ helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback deleting the
+// transaction. Failures will usually be valgrind errors. See
+// http://crbug.com/46925
+TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp.get(), 2),
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
+ CreateMockRead(*body.get(), 4),
+ MockRead(ASYNC, 0, 0, 5), // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // Setup a user callback which will delete the session, and clear out the
+ // memory holding the stream object. Note that the callback deletes trans.
+ const int kSize = 3000;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+ rv = trans->Read(
+ buf.get(),
+ kSize,
+ base::Bind(&SpdyNetworkTransactionTest::DeleteSessionCallback,
+ base::Unretained(&helper)));
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+ data.CompleteRead();
+
+ // Finish running rest of tasks.
+ base::RunLoop().RunUntilIdle();
+ helper.VerifyDataConsumed();
+}
+
+// Send a spdy request to www.google.com that gets redirected to www.foo.com.
+TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) {
+ const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM);
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock("http://www.google.com/"));
+ (*headers)["user-agent"] = "";
+ (*headers)["accept-encoding"] = "gzip,deflate";
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php"));
+ (*headers2)["user-agent"] = "";
+ (*headers2)["accept-encoding"] = "gzip,deflate";
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, headers.Pass()));
+ scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, headers2.Pass()));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReplyRedirect(1));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(ASYNC, 0, 0, 3) // EOF
+ };
+
+ // Setup writes/reads to www.foo.com
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes2[] = {
+ CreateMockWrite(*req2, 1),
+ };
+ MockRead reads2[] = {
+ CreateMockRead(*resp2, 2),
+ CreateMockRead(*body2, 3),
+ MockRead(ASYNC, 0, 0, 4) // EOF
+ };
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data2(reads2, arraysize(reads2),
+ writes2, arraysize(writes2));
+
+ // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+ HttpStreamFactory::set_force_spdy_over_ssl(false);
+ HttpStreamFactory::set_force_spdy_always(true);
+ TestDelegate d;
+ {
+ SpdyURLRequestContext spdy_url_request_context(GetParam().protocol);
+ net::URLRequest r(
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data2);
+
+ d.set_quit_on_redirect(true);
+ r.Start();
+ base::RunLoop().Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ r.FollowDeferredRedirect();
+ base::RunLoop().Run();
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
+ std::string contents("hello!");
+ EXPECT_EQ(contents, d.data_received());
+ }
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data2.at_read_eof());
+ EXPECT_TRUE(data2.at_write_eof());
+}
+
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to
+// www.foo.com.
+TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) {
+ const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock("http://www.google.com/"));
+ (*headers)["user-agent"] = "";
+ (*headers)["accept-encoding"] = "gzip,deflate";
+
+ // Setup writes/reads to www.google.com
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers.Pass()));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> rep(
+ spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat",
+ "301 Moved Permanently",
+ "http://www.foo.com/index.php"));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ CreateMockWrite(*rst, 6),
+ };
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ CreateMockRead(*rep, 3),
+ CreateMockRead(*body, 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause
+ MockRead(ASYNC, 0, 0, 7) // EOF
+ };
+
+ // Setup writes/reads to www.foo.com
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php"));
+ (*headers2)["user-agent"] = "";
+ (*headers2)["accept-encoding"] = "gzip,deflate";
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers2.Pass()));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes2[] = {
+ CreateMockWrite(*req2, 1),
+ };
+ MockRead reads2[] = {
+ CreateMockRead(*resp2, 2),
+ CreateMockRead(*body2, 3),
+ MockRead(ASYNC, 0, 0, 5) // EOF
+ };
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ OrderedSocketData data2(reads2, arraysize(reads2),
+ writes2, arraysize(writes2));
+
+ // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+ HttpStreamFactory::set_force_spdy_over_ssl(false);
+ HttpStreamFactory::set_force_spdy_always(true);
+ TestDelegate d;
+ TestDelegate d2;
+ SpdyURLRequestContext spdy_url_request_context(GetParam().protocol);
+ {
+ net::URLRequest r(
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data);
+
+ r.Start();
+ base::RunLoop().Run();
+
+ EXPECT_EQ(0, d.received_redirect_count());
+ std::string contents("hello!");
+ EXPECT_EQ(contents, d.data_received());
+
+ net::URLRequest r2(
+ GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context);
+ spdy_url_request_context.socket_factory().
+ AddSocketDataProvider(&data2);
+
+ d2.set_quit_on_redirect(true);
+ r2.Start();
+ base::RunLoop().Run();
+ EXPECT_EQ(1, d2.received_redirect_count());
+
+ r2.FollowDeferredRedirect();
+ base::RunLoop().Run();
+ EXPECT_EQ(1, d2.response_started_count());
+ EXPECT_FALSE(d2.received_data_before_response());
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status());
+ std::string contents2("hello!");
+ EXPECT_EQ(contents2, d2.data_received());
+ }
+ data.CompleteRead();
+ data2.CompleteRead();
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data2.at_read_eof());
+ EXPECT_TRUE(data2.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ CreateMockRead(*stream2_body, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data,
+ &response,
+ &response2,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushBeforeSynReply) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_reply, 3),
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+ CreateMockRead(*stream2_body, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data,
+ &response,
+ &response2,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*stream1_syn, 1), };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ scoped_ptr<SpdyFrame>
+ stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_body, 4),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data,
+ &response,
+ &response2,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ scoped_ptr<SpdyFrame> stream2_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_rst, 4),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+ << data.read_count()
+ << " Read index: "
+ << data.read_index();
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+ << data.write_count()
+ << " Write index: "
+ << data.write_index();
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that we don't leak streams and that we properly send a reset
+// if the server pushes the same stream twice.
+TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> stream3_rst(
+ spdy_util_.ConstructSpdyRstStream(4, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream3_rst, 5),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ scoped_ptr<SpdyFrame>
+ stream3_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 4,
+ 1,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream3_syn, 4),
+ CreateMockRead(*stream1_body, 6, SYNCHRONOUS),
+ CreateMockRead(*stream2_body, 7),
+ MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data,
+ &response,
+ &response2,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ static const char kPushedData[] = "pushed my darling hello my baby";
+ scoped_ptr<SpdyFrame> stream2_body_base(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ const size_t kChunkSize = strlen(kPushedData) / 4;
+ scoped_ptr<SpdyFrame> stream2_body1(
+ new SpdyFrame(stream2_body_base->data(), kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body2(
+ new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body3(
+ new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize,
+ kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body4(
+ new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize,
+ stream2_body_base->size() - 3 * kChunkSize, false));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_body1, 4),
+ CreateMockRead(*stream2_body2, 5),
+ CreateMockRead(*stream2_body3, 6),
+ CreateMockRead(*stream2_body4, 7),
+ CreateMockRead(*stream1_body, 8, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed my darling hello my baby");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data, &response, &response2, kPushedData);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ "http://www.google.com/foo.dat"));
+ static const char kPushedData[] = "pushed my darling hello my baby";
+ scoped_ptr<SpdyFrame> stream2_body_base(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ const size_t kChunkSize = strlen(kPushedData) / 4;
+ scoped_ptr<SpdyFrame> stream2_body1(
+ new SpdyFrame(stream2_body_base->data(), kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body2(
+ new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body3(
+ new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize,
+ kChunkSize, false));
+ scoped_ptr<SpdyFrame> stream2_body4(
+ new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize,
+ stream2_body_base->size() - 3 * kChunkSize, false));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_body1, 4),
+ CreateMockRead(*stream2_body2, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause
+ CreateMockRead(*stream2_body3, 7),
+ CreateMockRead(*stream2_body4, 8),
+ CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS),
+ MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause.
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data, &response, &response2, kPushedData);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> stream2_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 0,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+ << data.read_count()
+ << " Read index: "
+ << data.read_index();
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+ << data.write_count()
+ << " Write index: "
+ << data.write_index();
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> stream2_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_INVALID_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 9,
+ "http://www.google.com/foo.dat"));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+ << data.read_count()
+ << " Read index: "
+ << data.read_index();
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+ << data.write_count()
+ << " Write index: "
+ << data.write_index();
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> stream2_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*stream2_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyHeaderBlock> incomplete_headers(new SpdyHeaderBlock());
+ (*incomplete_headers)["hello"] = "bye";
+ (*incomplete_headers)[spdy_util_.GetStatusKey()] = "200 OK";
+ (*incomplete_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream2_syn(
+ spdy_util_.ConstructSpdyControlFrame(incomplete_headers.Pass(),
+ false,
+ 2, // Stream ID
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ // Associated stream ID
+ 1));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+ << data.read_count()
+ << " Read index: "
+ << data.read_index();
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+ << data.write_count()
+ << " Write index: "
+ << data.write_index();
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) {
+ struct SynReplyHeadersTests {
+ int num_headers;
+ const char* extra_headers[5];
+ SpdyHeaderBlock expected_headers;
+ } test_cases[] = {
+ // This uses a multi-valued cookie header.
+ { 2,
+ { "cookie", "val1",
+ "cookie", "val2", // will get appended separated by NULL
+ NULL
+ },
+ },
+ // This is the minimalist set of headers.
+ { 0,
+ { NULL },
+ },
+ // Headers with a comma separated list.
+ { 1,
+ { "cookie", "val1,val2",
+ NULL
+ },
+ }
+ };
+
+ test_cases[0].expected_headers["cookie"] = "val1";
+ test_cases[0].expected_headers["cookie"] += '\0';
+ test_cases[0].expected_headers["cookie"] += "val2";
+ test_cases[0].expected_headers["hello"] = "bye";
+ test_cases[0].expected_headers["status"] = "200";
+ test_cases[0].expected_headers["version"] = "HTTP/1.1";
+
+ test_cases[1].expected_headers["hello"] = "bye";
+ test_cases[1].expected_headers["status"] = "200";
+ test_cases[1].expected_headers["version"] = "HTTP/1.1";
+
+ test_cases[2].expected_headers["cookie"] = "val1,val2";
+ test_cases[2].expected_headers["hello"] = "bye";
+ test_cases[2].expected_headers["status"] = "200";
+ test_cases[2].expected_headers["version"] = "HTTP/1.1";
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+ test_cases[i].num_headers,
+ 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+ EXPECT_TRUE(headers.get() != NULL);
+ void* iter = NULL;
+ std::string name, value;
+ SpdyHeaderBlock header_block;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ if (header_block[name].empty()) {
+ header_block[name] = value;
+ } else {
+ header_block[name] += '\0';
+ header_block[name] += value;
+ }
+ }
+ EXPECT_EQ(test_cases[i].expected_headers, header_block);
+ }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) {
+ static const SpdyHeaderInfo syn_reply_info = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+ // Modify the following data to change/add test cases:
+ struct SynReplyTests {
+ const SpdyHeaderInfo* syn_reply;
+ bool vary_matches;
+ int num_headers[2];
+ const char* extra_headers[2][16];
+ } test_cases[] = {
+ // Test the case of a multi-valued cookie. When the value is delimited
+ // with NUL characters, it needs to be unfolded into multiple headers.
+ {
+ &syn_reply_info,
+ true,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { "vary", "cookie",
+ spdy_util_.GetStatusKey(), "200",
+ spdy_util_.GetPathKey(), "/index.php",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 5 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend",
+ "vary", "enemy",
+ spdy_util_.GetStatusKey(), "200",
+ spdy_util_.GetPathKey(), "/index.php",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Test a '*' vary field.
+ &syn_reply_info,
+ false,
+ { 1, 4 },
+ { { "cookie", "val1,val2",
+ NULL
+ },
+ { "vary", "*",
+ spdy_util_.GetStatusKey(), "200",
+ spdy_util_.GetPathKey(), "/index.php",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ NULL
+ }
+ }
+ }, { // Multiple comma-separated vary fields.
+ &syn_reply_info,
+ true,
+ { 2, 4 },
+ { { "friend", "barney",
+ "enemy", "snaggletooth",
+ NULL
+ },
+ { "vary", "friend,enemy",
+ spdy_util_.GetStatusKey(), "200",
+ spdy_util_.GetPathKey(), "/index.php",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ NULL
+ }
+ }
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> frame_req(
+ spdy_util_.ConstructSpdyGet(test_cases[i].extra_headers[0],
+ test_cases[i].num_headers[0],
+ false, 1, LOWEST, true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*frame_req),
+ };
+
+ // Construct the reply.
+ scoped_ptr<SpdyFrame> frame_reply(
+ spdy_util_.ConstructSpdyFrame(*test_cases[i].syn_reply,
+ test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ NULL,
+ 0));
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*frame_reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // Attach the headers to the request.
+ int header_count = test_cases[i].num_headers[0];
+
+ HttpRequestInfo request = CreateGetRequest();
+ for (int ct = 0; ct < header_count; ct++) {
+ const char* header_key = test_cases[i].extra_headers[0][ct * 2];
+ const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1];
+ request.extra_headers.SetHeader(header_key, header_value);
+ }
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+
+ EXPECT_EQ(OK, out.rv) << i;
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i;
+ EXPECT_EQ("hello!", out.response_data) << i;
+
+ // Test the response information.
+ EXPECT_TRUE(out.response_info.response_time >
+ out.response_info.request_time) << i;
+ base::TimeDelta test_delay = out.response_info.response_time -
+ out.response_info.request_time;
+ base::TimeDelta min_expected_delay;
+ min_expected_delay.FromMilliseconds(10);
+ EXPECT_GT(test_delay.InMillisecondsF(),
+ min_expected_delay.InMillisecondsF()) << i;
+ EXPECT_EQ(out.response_info.vary_data.is_valid(),
+ test_cases[i].vary_matches) << i;
+
+ // Check the headers.
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+ ASSERT_TRUE(headers.get() != NULL) << i;
+ void* iter = NULL;
+ std::string name, value, lines;
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ lines.append(name);
+ lines.append(": ");
+ lines.append(value);
+ lines.append("\n");
+ }
+
+ // Construct the expected header reply string.
+ SpdyHeaderBlock reply_headers;
+ AppendToHeaderBlock(test_cases[i].extra_headers[1],
+ test_cases[i].num_headers[1],
+ &reply_headers);
+ std::string expected_reply =
+ spdy_util_.ConstructSpdyReplyString(reply_headers);
+ EXPECT_EQ(expected_reply, lines) << i;
+ }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_REPLY, // Kind = SynReply
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE // Data Flags
+ };
+
+ struct InvalidSynReplyTests {
+ int num_headers;
+ const char* headers[10];
+ } test_cases[] = {
+ // SYN_REPLY missing status header
+ { 4,
+ { "cookie", "val1",
+ "cookie", "val2",
+ spdy_util_.GetPathKey(), "/index.php",
+ spdy_util_.GetVersionKey(), "HTTP/1.1",
+ NULL
+ },
+ },
+ // SYN_REPLY missing version header
+ { 2,
+ { "status", "200",
+ spdy_util_.GetPathKey(), "/index.php",
+ NULL
+ },
+ },
+ // SYN_REPLY with no headers
+ { 0, { NULL }, },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader,
+ NULL, 0,
+ test_cases[i].headers,
+ test_cases[i].num_headers));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+ }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) {
+ // This is the length field that's too short.
+ scoped_ptr<SpdyFrame> syn_reply_wrong_length(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+ size_t right_size =
+ (spdy_util_.spdy_version() < SPDY4) ?
+ syn_reply_wrong_length->size() - framer.GetControlFrameHeaderSize() :
+ syn_reply_wrong_length->size();
+ size_t wrong_size = right_size - 4;
+ test::SetFrameLength(syn_reply_wrong_length.get(),
+ wrong_size,
+ spdy_util_.spdy_version());
+
+ struct SynReplyTests {
+ const SpdyFrame* syn_reply;
+ } test_cases[] = {
+ { syn_reply_wrong_length.get(), },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ MockRead(ASYNC, test_cases[i].syn_reply->data(), wrong_size),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+ }
+}
+
+// Test that we shutdown correctly on write errors.
+TEST_P(SpdyNetworkTransactionTest, WriteError) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ // We'll write 10 bytes successfully
+ MockWrite(ASYNC, req->data(), 10),
+ // Followed by ERROR!
+ MockWrite(ASYNC, ERR_FAILED),
+ };
+
+ DelayedSocketData data(2, NULL, 0,
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_FAILED, out.rv);
+ data.Reset();
+}
+
+// Test that partial writes work.
+TEST_P(SpdyNetworkTransactionTest, PartialWrite) {
+ // Chop the SYN_STREAM frame into 5 chunks.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ const int kChunks = 5;
+ scoped_ptr<MockWrite[]> writes(ChopWriteFrame(*req.get(), kChunks));
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(kChunks, reads, arraysize(reads),
+ writes.get(), kChunks);
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+}
+
+// In this test, we enable compression, but get a uncompressed SynReply from
+// the server. Verify that teardown is all clean.
+TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) {
+ scoped_ptr<SpdyFrame> compressed(
+ spdy_util_.ConstructSpdyGet(NULL, 0, true, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*compressed),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ SpdySessionDependencies* session_deps =
+ CreateSpdySessionDependencies(GetParam());
+ session_deps->enable_compression = true;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), session_deps);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+ data.Reset();
+}
+
+// Test that the NetLog contains good data for a simple GET request.
+TEST_P(SpdyNetworkTransactionTest, NetLog) {
+ static const char* const kExtraHeaders[] = {
+ "user-agent", "Chrome",
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(kExtraHeaders, 1, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ CapturingBoundNetLog log;
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(),
+ DEFAULT_PRIORITY,
+ log.bound(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ // Check that the NetLog was filled reasonably.
+ // This test is intentionally non-specific about the exact ordering of the
+ // log; instead we just check to make sure that certain events exist, and that
+ // they are in the right order.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+
+ EXPECT_LT(0u, entries.size());
+ int pos = 0;
+ pos = net::ExpectLogContainsSomewhere(entries, 0,
+ net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+ net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+ net::NetLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+ net::NetLog::PHASE_END);
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+ net::NetLog::PHASE_BEGIN);
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+ net::NetLog::PHASE_END);
+
+ // Check that we logged all the headers correctly
+ pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+ net::NetLog::PHASE_NONE);
+
+ base::ListValue* header_list;
+ ASSERT_TRUE(entries[pos].params.get());
+ ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list));
+
+ std::vector<std::string> expected;
+ expected.push_back(std::string(spdy_util_.GetHostKey()) + ": www.google.com");
+ expected.push_back(std::string(spdy_util_.GetPathKey()) + ": /");
+ expected.push_back(std::string(spdy_util_.GetSchemeKey()) + ": http");
+ expected.push_back(std::string(spdy_util_.GetVersionKey()) + ": HTTP/1.1");
+ expected.push_back(std::string(spdy_util_.GetMethodKey()) + ": GET");
+ expected.push_back("user-agent: Chrome");
+ EXPECT_EQ(expected.size(), header_list->GetSize());
+ for (std::vector<std::string>::const_iterator it = expected.begin();
+ it != expected.end();
+ ++it) {
+ base::StringValue header(*it);
+ EXPECT_NE(header_list->end(), header_list->Find(header)) <<
+ "Header not found: " << *it;
+ }
+}
+
+// Since we buffer the IO from the stream to the renderer, this test verifies
+// that when we read out the maximum amount of data (e.g. we received 50 bytes
+// on the network, but issued a Read for only 5 of those bytes) that the data
+// flow still works correctly.
+TEST_P(SpdyNetworkTransactionTest, BufferFull) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 2 data frames in a single read.
+ scoped_ptr<SpdyFrame> data_frame_1(
+ framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> data_frame_2(
+ framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE));
+ const SpdyFrame* data_frames[2] = {
+ data_frame_1.get(),
+ data_frame_2.get(),
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+ scoped_ptr<SpdyFrame> last_frame(
+ framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN));
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause
+ CreateMockRead(*last_frame),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ TestCompletionCallback callback;
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 3;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback());
+ if (rv == net::ERR_IO_PENDING) {
+ data.CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ NOTREACHED();
+ }
+ } while (rv > 0);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("goodbye world", out.response_data);
+}
+
+// Verify that basic buffering works; when multiple data frames arrive
+// at the same time, ensure that we don't notify a read completion for
+// each data frame individually.
+TEST_P(SpdyNetworkTransactionTest, Buffering) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 4 data frames in a single read.
+ scoped_ptr<SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> data_frame_fin(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+ const SpdyFrame* data_frames[4] = {
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame_fin.get()
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback());
+ if (rv == net::ERR_IO_PENDING) {
+ data.CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ EXPECT_EQ(kSmallReadSize, rv);
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ FAIL() << "Unexpected read error: " << rv;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes.
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data but read it after it has been buffered.
+TEST_P(SpdyNetworkTransactionTest, BufferedAll) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // 5 data frames in a single read.
+ scoped_ptr<SpdyFrame> syn_reply(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ // turn off FIN bit
+ test::SetFrameFlags(
+ syn_reply.get(), CONTROL_FLAG_NONE, spdy_util_.spdy_version());
+ scoped_ptr<SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> data_frame_fin(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+ const SpdyFrame* frames[5] = {
+ syn_reply.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame_fin.get()
+ };
+ char combined_frames[200];
+ int combined_frames_len =
+ CombineFrames(frames, arraysize(frames),
+ combined_frames, arraysize(combined_frames));
+
+ MockRead reads[] = {
+ MockRead(ASYNC, combined_frames, combined_frames_len),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback());
+ if (rv > 0) {
+ EXPECT_EQ(kSmallReadSize, rv);
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ FAIL() << "Unexpected read error: " << rv;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(3, reads_completed);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data and close the connection.
+TEST_P(SpdyNetworkTransactionTest, BufferedClosed) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // All data frames in a single read.
+ // NOTE: We don't FIN the stream.
+ scoped_ptr<SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+ const SpdyFrame* data_frames[4] = {
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get(),
+ data_frame.get()
+ };
+ char combined_data_frames[100];
+ int combined_data_frames_len =
+ CombineFrames(data_frames, arraysize(data_frames),
+ combined_data_frames, arraysize(combined_data_frames));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a wait
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ std::string content;
+ int reads_completed = 0;
+ do {
+ // Read small chunks at a time.
+ const int kSmallReadSize = 14;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback());
+ if (rv == net::ERR_IO_PENDING) {
+ data.CompleteRead();
+ rv = read_callback.WaitForResult();
+ }
+ if (rv > 0) {
+ content.append(buf->data(), rv);
+ } else if (rv < 0) {
+ // This test intentionally closes the connection, and will get an error.
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+ break;
+ }
+ reads_completed++;
+ } while (rv > 0);
+
+ EXPECT_EQ(0, reads_completed);
+
+ out.response_data.swap(content);
+
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+ // MockClientSocketFactory) are still alive.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) {
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // NOTE: We don't FIN the stream.
+ scoped_ptr<SpdyFrame> data_frame(
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a wait
+ CreateMockRead(*data_frame),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ TransactionHelperResult out = helper.output();
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.status_line = response->headers->GetStatusLine();
+ out.response_info = *response; // Make a copy so we can verify.
+
+ // Read Data
+ TestCompletionCallback read_callback;
+
+ do {
+ const int kReadSize = 256;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize));
+ rv = trans->Read(buf.get(), kReadSize, read_callback.callback());
+ if (rv == net::ERR_IO_PENDING) {
+ // Complete the read now, which causes buffering to start.
+ data.CompleteRead();
+ // Destroy the transaction, causing the stream to get cancelled
+ // and orphaning the buffered IO task.
+ helper.ResetTrans();
+ break;
+ }
+ // We shouldn't get here in this test.
+ FAIL() << "Unexpected read: " << rv;
+ } while (rv > 0);
+
+ // Flush the MessageLoop; this will cause the buffered IO task
+ // to run for the final time.
+ base::RunLoop().RunUntilIdle();
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Test that if the server requests persistence of settings, that we save
+// the settings in the HttpServerProperties.
+TEST_P(SpdyNetworkTransactionTest, SettingsSaved) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ net_log, GetParam(), NULL);
+ helper.RunPreTestSetup();
+
+ // Verify that no settings exist initially.
+ HostPortPair host_port_pair("www.google.com", helper.port());
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+ EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair).empty());
+
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ // Construct the reply.
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock());
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> reply(
+ spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass()));
+
+ const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+ unsigned int kSampleValue1 = 0x0a0a0a0a;
+ const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH;
+ unsigned int kSampleValue2 = 0x0b0b0b0b;
+ const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME;
+ unsigned int kSampleValue3 = 0x0c0c0c0c;
+ scoped_ptr<SpdyFrame> settings_frame;
+ {
+ // Construct the SETTINGS frame.
+ SettingsMap settings;
+ // First add a persisted setting.
+ settings[kSampleId1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1);
+ // Next add a non-persisted setting.
+ settings[kSampleId2] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2);
+ // Next add another persisted setting.
+ settings[kSampleId3] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3);
+ settings_frame.reset(spdy_util_.ConstructSpdySettings(settings));
+ }
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ CreateMockRead(*settings_frame),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ helper.AddData(&data);
+ helper.RunDefaultTest();
+ helper.VerifyDataConsumed();
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ {
+ // Verify we had two persisted settings.
+ const SettingsMap& settings_map =
+ spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair);
+ ASSERT_EQ(2u, settings_map.size());
+
+ // Verify the first persisted setting.
+ SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+ EXPECT_TRUE(it1 != settings_map.end());
+ SettingsFlagsAndValue flags_and_value1 = it1->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+ EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+ // Verify the second persisted setting.
+ SettingsMap::const_iterator it3 = settings_map.find(kSampleId3);
+ EXPECT_TRUE(it3 != settings_map.end());
+ SettingsFlagsAndValue flags_and_value3 = it3->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first);
+ EXPECT_EQ(kSampleValue3, flags_and_value3.second);
+ }
+}
+
+// Test that when there are settings saved that they are sent back to the
+// server upon session establishment.
+TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) {
+ static const SpdyHeaderInfo kSynReplyInfo = {
+ SYN_REPLY, // Syn Reply
+ 1, // Stream ID
+ 0, // Associated Stream ID
+ ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID, // Status
+ NULL, // Data
+ 0, // Data Length
+ DATA_FLAG_NONE // Data Flags
+ };
+
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ net_log, GetParam(), NULL);
+ helper.RunPreTestSetup();
+
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+
+ SpdySessionPoolPeer pool_peer(spdy_session_pool);
+ pool_peer.SetEnableSendingInitialData(true);
+
+ // Verify that no settings exist initially.
+ HostPortPair host_port_pair("www.google.com", helper.port());
+ EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair).empty());
+
+ const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+ unsigned int kSampleValue1 = 0x0a0a0a0a;
+ const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME;
+ unsigned int kSampleValue2 = 0x0c0c0c0c;
+
+ // First add a persisted setting.
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ host_port_pair,
+ kSampleId1,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ kSampleValue1);
+
+ // Next add another persisted setting.
+ spdy_session_pool->http_server_properties()->SetSpdySetting(
+ host_port_pair,
+ kSampleId2,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ kSampleValue2);
+
+ EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair).size());
+
+ // Construct the initial SETTINGS frame.
+ SettingsMap initial_settings;
+ initial_settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+ scoped_ptr<SpdyFrame> initial_settings_frame(
+ spdy_util_.ConstructSpdySettings(initial_settings));
+
+ // Construct the initial window update.
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+
+ // Construct the persisted SETTINGS frame.
+ const SettingsMap& settings =
+ spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+
+ std::vector<MockWrite> writes;
+ if (GetParam().protocol == kProtoHTTP2Draft04) {
+ writes.push_back(
+ MockWrite(ASYNC,
+ kHttp2ConnectionHeaderPrefix,
+ kHttp2ConnectionHeaderPrefixSize));
+ }
+ writes.push_back(CreateMockWrite(*initial_settings_frame));
+ if (GetParam().protocol >= kProtoSPDY31) {
+ writes.push_back(CreateMockWrite(*initial_window_update));
+ };
+ writes.push_back(CreateMockWrite(*settings_frame));
+ writes.push_back(CreateMockWrite(*req));
+
+ // Construct the reply.
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock());
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> reply(
+ spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass()));
+
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*reply),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(2, reads, arraysize(reads),
+ vector_as_array(&writes), writes.size());
+ helper.AddData(&data);
+ helper.RunDefaultTest();
+ helper.VerifyDataConsumed();
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ {
+ // Verify we had two persisted settings.
+ const SettingsMap& settings_map =
+ spdy_session_pool->http_server_properties()->GetSpdySettings(
+ host_port_pair);
+ ASSERT_EQ(2u, settings_map.size());
+
+ // Verify the first persisted setting.
+ SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+ EXPECT_TRUE(it1 != settings_map.end());
+ SettingsFlagsAndValue flags_and_value1 = it1->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+ EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+ // Verify the second persisted setting.
+ SettingsMap::const_iterator it2 = settings_map.find(kSampleId2);
+ EXPECT_TRUE(it2 != settings_map.end());
+ SettingsFlagsAndValue flags_and_value2 = it2->second;
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first);
+ EXPECT_EQ(kSampleValue2, flags_and_value2.second);
+ }
+}
+
+TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> go_away(spdy_util_.ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*go_away),
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_ABORTED, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ BoundNetLog log;
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ log, GetParam(), NULL);
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ TransactionHelperResult out;
+ out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log);
+
+ EXPECT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.rv = ReadTransaction(trans, &out.response_data);
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv);
+
+ // Verify that we consumed all test data.
+ helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy.
+TEST_P(SpdyNetworkTransactionTest, ProxyConnect) {
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.session_deps().reset(CreateSpdySessionDependencies(
+ GetParam(),
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")));
+ helper.SetSession(make_scoped_refptr(
+ SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"};
+ const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"};
+ const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ MockWrite writes_SPDYNPN[] = {
+ MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+ CreateMockWrite(*req, 2),
+ };
+ MockRead reads_SPDYNPN[] = {
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+ CreateMockRead(*resp, 3),
+ CreateMockRead(*body.get(), 4),
+ MockRead(ASYNC, 0, 0, 5),
+ };
+
+ MockWrite writes_SPDYSSL[] = {
+ MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+ CreateMockWrite(*req, 2),
+ };
+ MockRead reads_SPDYSSL[] = {
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+ CreateMockRead(*resp, 3),
+ CreateMockRead(*body.get(), 4),
+ MockRead(ASYNC, 0, 0, 5),
+ };
+
+ MockWrite writes_SPDYNOSSL[] = {
+ CreateMockWrite(*req, 0),
+ };
+
+ MockRead reads_SPDYNOSSL[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*body.get(), 2),
+ MockRead(ASYNC, 0, 0, 3),
+ };
+
+ scoped_ptr<OrderedSocketData> data;
+ switch(GetParam().ssl_type) {
+ case SPDYNOSSL:
+ data.reset(new OrderedSocketData(reads_SPDYNOSSL,
+ arraysize(reads_SPDYNOSSL),
+ writes_SPDYNOSSL,
+ arraysize(writes_SPDYNOSSL)));
+ break;
+ case SPDYSSL:
+ data.reset(new OrderedSocketData(reads_SPDYSSL,
+ arraysize(reads_SPDYSSL),
+ writes_SPDYSSL,
+ arraysize(writes_SPDYSSL)));
+ break;
+ case SPDYNPN:
+ data.reset(new OrderedSocketData(reads_SPDYNPN,
+ arraysize(reads_SPDYNPN),
+ writes_SPDYNPN,
+ arraysize(writes_SPDYNPN)));
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ helper.AddData(data.get());
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans, &response_data));
+ EXPECT_EQ("hello!", response_data);
+ helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy to www.google.com,
+// if there already exists a direct spdy connection to www.google.com. See
+// http://crbug.com/49874
+TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) {
+ // When setting up the first transaction, we store the SpdySessionPool so that
+ // we can use the same pool in the second transaction.
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ // Use a proxy service which returns a proxy fallback list from DIRECT to
+ // myproxy:70. For this test there will be no fallback, so it is equivalent
+ // to simply DIRECT. The reason for appending the second proxy is to verify
+ // that the session pool key used does is just "DIRECT".
+ helper.session_deps().reset(CreateSpdySessionDependencies(
+ GetParam(),
+ ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70")));
+ helper.SetSession(make_scoped_refptr(
+ SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+ helper.RunPreTestSetup();
+
+ // Construct and send a simple GET request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ CreateMockRead(*body, 3),
+ MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause
+ MockRead(ASYNC, 0, 5) // EOF
+ };
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ TransactionHelperResult out;
+ out.rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(out.rv, ERR_IO_PENDING);
+ out.rv = callback.WaitForResult();
+ EXPECT_EQ(out.rv, OK);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ out.rv = ReadTransaction(trans, &out.response_data);
+ EXPECT_EQ(OK, out.rv);
+ out.status_line = response->headers->GetStatusLine();
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ // Check that the SpdySession is still in the SpdySessionPool.
+ HostPortPair host_port_pair("www.google.com", helper.port());
+ SpdySessionKey session_pool_key_direct(
+ host_port_pair, ProxyServer::Direct(), kPrivacyModeDisabled);
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool, session_pool_key_direct));
+ SpdySessionKey session_pool_key_proxy(
+ host_port_pair,
+ ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP),
+ kPrivacyModeDisabled);
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool, session_pool_key_proxy));
+
+ // Set up data for the proxy connection.
+ const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"};
+ const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"};
+ const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+ scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyGet(
+ "http://www.google.com/foo.dat", false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ MockWrite writes_SPDYNPN[] = {
+ MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+ CreateMockWrite(*req2, 2),
+ };
+ MockRead reads_SPDYNPN[] = {
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+ CreateMockRead(*resp2, 3),
+ CreateMockRead(*body2, 4),
+ MockRead(ASYNC, 0, 5) // EOF
+ };
+
+ MockWrite writes_SPDYNOSSL[] = {
+ CreateMockWrite(*req2, 0),
+ };
+ MockRead reads_SPDYNOSSL[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ MockWrite writes_SPDYSSL[] = {
+ MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+ CreateMockWrite(*req2, 2),
+ };
+ MockRead reads_SPDYSSL[] = {
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+ CreateMockRead(*resp2, 3),
+ CreateMockRead(*body2, 4),
+ MockRead(ASYNC, 0, 0, 5),
+ };
+
+ scoped_ptr<OrderedSocketData> data_proxy;
+ switch(GetParam().ssl_type) {
+ case SPDYNPN:
+ data_proxy.reset(new OrderedSocketData(reads_SPDYNPN,
+ arraysize(reads_SPDYNPN),
+ writes_SPDYNPN,
+ arraysize(writes_SPDYNPN)));
+ break;
+ case SPDYNOSSL:
+ data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL,
+ arraysize(reads_SPDYNOSSL),
+ writes_SPDYNOSSL,
+ arraysize(writes_SPDYNOSSL)));
+ break;
+ case SPDYSSL:
+ data_proxy.reset(new OrderedSocketData(reads_SPDYSSL,
+ arraysize(reads_SPDYSSL),
+ writes_SPDYSSL,
+ arraysize(writes_SPDYSSL)));
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ // Create another request to www.google.com, but this time through a proxy.
+ HttpRequestInfo request_proxy;
+ request_proxy.method = "GET";
+ request_proxy.url = GURL("http://www.google.com/foo.dat");
+ request_proxy.load_flags = 0;
+ scoped_ptr<SpdySessionDependencies> ssd_proxy(
+ CreateSpdySessionDependencies(GetParam()));
+ // Ensure that this transaction uses the same SpdySessionPool.
+ scoped_refptr<HttpNetworkSession> session_proxy(
+ SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get()));
+ NormalSpdyTransactionHelper helper_proxy(request_proxy, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ HttpNetworkSessionPeer session_peer(session_proxy);
+ scoped_ptr<net::ProxyService> proxy_service(
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+ session_peer.SetProxyService(proxy_service.get());
+ helper_proxy.session_deps().swap(ssd_proxy);
+ helper_proxy.SetSession(session_proxy);
+ helper_proxy.RunPreTestSetup();
+ helper_proxy.AddData(data_proxy.get());
+
+ HttpNetworkTransaction* trans_proxy = helper_proxy.trans();
+ TestCompletionCallback callback_proxy;
+ int rv = trans_proxy->Start(
+ &request_proxy, callback_proxy.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback_proxy.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo();
+ EXPECT_TRUE(response_proxy.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine());
+
+ std::string response_data;
+ ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data));
+ EXPECT_EQ("hello!", response_data);
+
+ data.CompleteRead();
+ helper_proxy.VerifyDataConsumed();
+}
+
+// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction
+// on a new connection, if the connection was previously known to be good.
+// This can happen when a server reboots without saying goodbye, or when
+// we're behind a NAT that masked the RST.
+TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) {
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, ERR_IO_PENDING),
+ MockRead(ASYNC, ERR_CONNECTION_RESET),
+ };
+
+ MockRead reads2[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // This test has a couple of variants.
+ enum {
+ // Induce the RST while waiting for our transaction to send.
+ VARIANT_RST_DURING_SEND_COMPLETION,
+ // Induce the RST while waiting for our transaction to read.
+ // In this case, the send completed - everything copied into the SNDBUF.
+ VARIANT_RST_DURING_READ_COMPLETION
+ };
+
+ for (int variant = VARIANT_RST_DURING_SEND_COMPLETION;
+ variant <= VARIANT_RST_DURING_READ_COMPLETION;
+ ++variant) {
+ DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0);
+
+ DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0);
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data1);
+ helper.AddData(&data2);
+ helper.RunPreTestSetup();
+
+ for (int i = 0; i < 2; ++i) {
+ scoped_ptr<HttpNetworkTransaction> trans(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ // On the second transaction, we trigger the RST.
+ if (i == 1) {
+ if (variant == VARIANT_RST_DURING_READ_COMPLETION) {
+ // Writes to the socket complete asynchronously on SPDY by running
+ // through the message loop. Complete the write here.
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Now schedule the ERR_CONNECTION_RESET.
+ EXPECT_EQ(3u, data1.read_index());
+ data1.CompleteRead();
+ EXPECT_EQ(4u, data1.read_index());
+ }
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ EXPECT_TRUE(response->headers.get() != NULL);
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+ std::string response_data;
+ rv = ReadTransaction(trans.get(), &response_data);
+ EXPECT_EQ(OK, rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_EQ("hello!", response_data);
+ }
+
+ helper.VerifyDataConsumed();
+ }
+}
+
+// Test that turning SPDY on and off works properly.
+TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) {
+ net::HttpStreamFactory::set_spdy_enabled(true);
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(OK, out.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+ EXPECT_EQ("hello!", out.response_data);
+
+ net::HttpStreamFactory::set_spdy_enabled(false);
+ MockRead http_reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+ MockRead("hello from http"),
+ MockRead(SYNCHRONOUS, OK),
+ };
+ DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0);
+ NormalSpdyTransactionHelper helper2(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper2.SetSpdyDisabled();
+ helper2.RunToCompletion(&data2);
+ TransactionHelperResult out2 = helper2.output();
+ EXPECT_EQ(OK, out2.rv);
+ EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line);
+ EXPECT_EQ("hello from http", out2.response_data);
+
+ net::HttpStreamFactory::set_spdy_enabled(true);
+}
+
+// Tests that Basic authentication works over SPDY
+TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) {
+ net::HttpStreamFactory::set_spdy_enabled(true);
+
+ // The first request will be a bare GET, the second request will be a
+ // GET with an Authorization header.
+ scoped_ptr<SpdyFrame> req_get(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ const char* const kExtraAuthorizationHeaders[] = {
+ "authorization", "Basic Zm9vOmJhcg=="
+ };
+ scoped_ptr<SpdyFrame> req_get_authorization(
+ spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders,
+ arraysize(kExtraAuthorizationHeaders) / 2,
+ false, 3, LOWEST, true));
+ MockWrite spdy_writes[] = {
+ CreateMockWrite(*req_get, 1),
+ CreateMockWrite(*req_get_authorization, 4),
+ };
+
+ // The first response is a 401 authentication challenge, and the second
+ // response will be a 200 response since the second request includes a valid
+ // Authorization header.
+ const char* const kExtraAuthenticationHeaders[] = {
+ "www-authenticate",
+ "Basic realm=\"MyRealm\""
+ };
+ scoped_ptr<SpdyFrame> resp_authentication(
+ spdy_util_.ConstructSpdySynReplyError(
+ "401 Authentication Required",
+ kExtraAuthenticationHeaders,
+ arraysize(kExtraAuthenticationHeaders) / 2,
+ 1));
+ scoped_ptr<SpdyFrame> body_authentication(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp_data(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead spdy_reads[] = {
+ CreateMockRead(*resp_authentication, 2),
+ CreateMockRead(*body_authentication, 3),
+ CreateMockRead(*resp_data, 5),
+ CreateMockRead(*body_data, 6),
+ MockRead(ASYNC, 0, 7),
+ };
+
+ OrderedSocketData data(spdy_reads, arraysize(spdy_reads),
+ spdy_writes, arraysize(spdy_writes));
+ HttpRequestInfo request(CreateGetRequest());
+ BoundNetLog net_log;
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ net_log, GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+ TestCompletionCallback callback;
+ const int rv_start = trans->Start(&request, callback.callback(), net_log);
+ EXPECT_EQ(ERR_IO_PENDING, rv_start);
+ const int rv_start_complete = callback.WaitForResult();
+ EXPECT_EQ(OK, rv_start_complete);
+
+ // Make sure the response has an auth challenge.
+ const HttpResponseInfo* const response_start = trans->GetResponseInfo();
+ ASSERT_TRUE(response_start != NULL);
+ ASSERT_TRUE(response_start->headers.get() != NULL);
+ EXPECT_EQ(401, response_start->headers->response_code());
+ EXPECT_TRUE(response_start->was_fetched_via_spdy);
+ AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get();
+ ASSERT_TRUE(auth_challenge != NULL);
+ EXPECT_FALSE(auth_challenge->is_proxy);
+ EXPECT_EQ("basic", auth_challenge->scheme);
+ EXPECT_EQ("MyRealm", auth_challenge->realm);
+
+ // Restart with a username/password.
+ AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"));
+ TestCompletionCallback callback_restart;
+ const int rv_restart = trans->RestartWithAuth(
+ credentials, callback_restart.callback());
+ EXPECT_EQ(ERR_IO_PENDING, rv_restart);
+ const int rv_restart_complete = callback_restart.WaitForResult();
+ EXPECT_EQ(OK, rv_restart_complete);
+ // TODO(cbentzel): This is actually the same response object as before, but
+ // data has changed.
+ const HttpResponseInfo* const response_restart = trans->GetResponseInfo();
+ ASSERT_TRUE(response_restart != NULL);
+ ASSERT_TRUE(response_restart->headers.get() != NULL);
+ EXPECT_EQ(200, response_restart->headers->response_code());
+ EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) {
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ spdy_util_.AddUrlToHeaderBlock(
+ "http://www.google.com/foo.dat", initial_headers.get());
+ scoped_ptr<SpdyFrame> stream2_syn(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ 1));
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["hello"] = "bye";
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream2_headers(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream2_headers, 4),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ CreateMockRead(*stream2_body, 5),
+ MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ RunServerPushTest(&data,
+ &response,
+ &response2,
+ expected_push_result);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) {
+ // We push a stream and attempt to claim it before the headers come down.
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ spdy_util_.AddUrlToHeaderBlock(
+ "http://www.google.com/foo.dat", initial_headers.get());
+ scoped_ptr<SpdyFrame> stream2_syn(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ 1));
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["hello"] = "bye";
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream2_headers(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers, 4),
+ CreateMockRead(*stream2_body, 5),
+ MockRead(ASYNC, 0, 6), // EOF
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.AddDeterministicData(&data);
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+ // and the body of the primary stream, but before we've received the HEADERS
+ // for the pushed stream.
+ data.SetStop(3);
+
+ // Start the transaction.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.Run();
+ rv = callback.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ // Request the pushed path. At this point, we've received the push, but the
+ // headers are not yet complete.
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ base::RunLoop().RunUntilIdle();
+
+ // Read the server push body.
+ std::string result2;
+ ReadResult(trans2.get(), &data, &result2);
+ // Read the response body.
+ std::string result;
+ ReadResult(trans, &data, &result);
+
+ // Verify that the received push data is same as the expected push data.
+ EXPECT_EQ(result2.compare(expected_push_result), 0)
+ << "Received data: "
+ << result2
+ << "||||| Expected data: "
+ << expected_push_result;
+
+ // Verify the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ response = *trans->GetResponseInfo();
+ response2 = *trans2->GetResponseInfo();
+
+ VerifyStreamsClosed(helper);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+ // Read the final EOF (which will close the session)
+ data.RunFor(1);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ spdy_util_.AddUrlToHeaderBlock(
+ "http://www.google.com/foo.dat", initial_headers.get());
+ scoped_ptr<SpdyFrame> stream2_syn(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ 1));
+
+ scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock());
+ (*middle_headers)["hello"] = "bye";
+ scoped_ptr<SpdyFrame> stream2_headers1(
+ spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200";
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream2_headers2(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ CreateMockRead(*stream2_headers2, 5),
+ CreateMockRead(*stream2_body, 6),
+ MockRead(ASYNC, 0, 7), // EOF
+ };
+
+ HttpResponseInfo response;
+ HttpResponseInfo response2;
+ std::string expected_push_result("pushed");
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.AddDeterministicData(&data);
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+ // the first HEADERS frame, and the body of the primary stream, but before
+ // we've received the final HEADERS for the pushed stream.
+ data.SetStop(4);
+
+ // Start the transaction.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.Run();
+ rv = callback.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ // Request the pushed path. At this point, we've received the push, but the
+ // headers are not yet complete.
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(3);
+ base::RunLoop().RunUntilIdle();
+
+ // Read the server push body.
+ std::string result2;
+ ReadResult(trans2.get(), &data, &result2);
+ // Read the response body.
+ std::string result;
+ ReadResult(trans, &data, &result);
+
+ // Verify that the received push data is same as the expected push data.
+ EXPECT_EQ(expected_push_result, result2);
+
+ // Verify the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ response = *trans->GetResponseInfo();
+ response2 = *trans2->GetResponseInfo();
+
+ VerifyStreamsClosed(helper);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Verify the pushed stream.
+ EXPECT_TRUE(response2.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+ // Verify we got all the headers
+ if (spdy_util_.spdy_version() < SPDY3) {
+ EXPECT_TRUE(response2.headers->HasHeaderValue(
+ "url",
+ "http://www.google.com/foo.dat"));
+ } else {
+ EXPECT_TRUE(response2.headers->HasHeaderValue(
+ "scheme", "http"));
+ EXPECT_TRUE(response2.headers->HasHeaderValue(
+ "host", "www.google.com"));
+ EXPECT_TRUE(response2.headers->HasHeaderValue(
+ "path", "/foo.dat"));
+ }
+ EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye"));
+ EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200"));
+ EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1"));
+
+ // Read the final EOF (which will close the session)
+ data.RunFor(1);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithNoStatusHeaderFrames) {
+ // We push a stream and attempt to claim it before the headers come down.
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ spdy_util_.AddUrlToHeaderBlock(
+ "http://www.google.com/foo.dat", initial_headers.get());
+ scoped_ptr<SpdyFrame> stream2_syn(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ 1));
+
+ scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock());
+ (*middle_headers)["hello"] = "bye";
+ scoped_ptr<SpdyFrame> stream2_headers1(
+ spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 1),
+ CreateMockRead(*stream2_syn, 2),
+ CreateMockRead(*stream1_body, 3),
+ CreateMockRead(*stream2_headers1, 4),
+ CreateMockRead(*stream2_body, 5),
+ MockRead(ASYNC, 0, 6), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.AddDeterministicData(&data);
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+ // the first HEADERS frame, and the body of the primary stream, but before
+ // we've received the final HEADERS for the pushed stream.
+ data.SetStop(4);
+
+ // Start the transaction.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.Run();
+ rv = callback.WaitForResult();
+ EXPECT_EQ(0, rv);
+
+ // Request the pushed path. At this point, we've received the push, but the
+ // headers are not yet complete.
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get()));
+ rv = trans2->Start(
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ data.RunFor(2);
+ base::RunLoop().RunUntilIdle();
+
+ // Read the server push body.
+ std::string result2;
+ ReadResult(trans2.get(), &data, &result2);
+ // Read the response body.
+ std::string result;
+ ReadResult(trans, &data, &result);
+ EXPECT_EQ("hello!", result);
+
+ // Verify that we haven't received any push data.
+ EXPECT_EQ("", result2);
+
+ // Verify the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ ASSERT_TRUE(trans2->GetResponseInfo() == NULL);
+
+ VerifyStreamsClosed(helper);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+ // Read the final EOF (which will close the session).
+ data.RunFor(1);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK";
+ (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream1_reply(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["hello"] = "bye";
+ scoped_ptr<SpdyFrame> stream1_headers(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply),
+ CreateMockRead(*stream1_headers),
+ CreateMockRead(*stream1_body),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) {
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ MockWrite writes[] = {
+ CreateMockWrite(*req),
+ CreateMockWrite(*rst),
+ };
+
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
+ (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK";
+ (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
+ scoped_ptr<SpdyFrame> stream1_reply(
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ 0));
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["hello"] = "bye";
+ scoped_ptr<SpdyFrame> stream1_headers(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 1,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, false));
+ scoped_ptr<SpdyFrame> stream1_body2(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply),
+ CreateMockRead(*stream1_body),
+ CreateMockRead(*stream1_headers),
+ CreateMockRead(*stream1_body2),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.RunToCompletion(&data);
+ TransactionHelperResult out = helper.output();
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) {
+ // In this test we want to verify that we can't accidentally push content
+ // which can't be pushed by this content server.
+ // This test assumes that:
+ // - if we're requesting http://www.foo.com/barbaz
+ // - the browser has made a connection to "www.foo.com".
+
+ // A list of the URL to fetch, followed by the URL being pushed.
+ static const char* const kTestCases[] = {
+ "http://www.google.com/foo.html",
+ "http://www.google.com:81/foo.js", // Bad port
+
+ "http://www.google.com/foo.html",
+ "https://www.google.com/foo.js", // Bad protocol
+
+ "http://www.google.com/foo.html",
+ "ftp://www.google.com/foo.js", // Invalid Protocol
+
+ "http://www.google.com/foo.html",
+ "http://blat.www.google.com/foo.js", // Cross subdomain
+
+ "http://www.google.com/foo.html",
+ "http://www.foo.com/foo.js", // Cross domain
+ };
+
+ for (size_t index = 0; index < arraysize(kTestCases); index += 2) {
+ const char* url_to_fetch = kTestCases[index];
+ const char* url_to_push = kTestCases[index + 1];
+
+ scoped_ptr<SpdyFrame> stream1_syn(
+ spdy_util_.ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+ scoped_ptr<SpdyFrame> stream1_body(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> push_rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*stream1_syn, 1),
+ CreateMockWrite(*push_rst, 4),
+ };
+
+ scoped_ptr<SpdyFrame>
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame>
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL,
+ 0,
+ 2,
+ 1,
+ url_to_push));
+ const char kPushedData[] = "pushed";
+ scoped_ptr<SpdyFrame> stream2_body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 2, kPushedData, strlen(kPushedData), true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL));
+
+ MockRead reads[] = {
+ CreateMockRead(*stream1_reply, 2),
+ CreateMockRead(*stream2_syn, 3),
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+ CreateMockRead(*stream2_body, 6),
+ MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause
+ };
+
+ HttpResponseInfo response;
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL(url_to_fetch);
+ request.load_flags = 0;
+
+ // Enable cross-origin push. Since we are not using a proxy, this should
+ // not actually enable cross-origin SPDY push.
+ scoped_ptr<SpdySessionDependencies> session_deps(
+ CreateSpdySessionDependencies(GetParam()));
+ session_deps->trusted_spdy_proxy = "123.45.67.89:8080";
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(),
+ session_deps.release());
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+
+ // Read the response body.
+ std::string result;
+ ReadResult(trans, &data, &result);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+
+ // Verify the SYN_REPLY.
+ // Copy the response info, because trans goes away.
+ response = *trans->GetResponseInfo();
+
+ VerifyStreamsClosed(helper);
+
+ // Verify the SYN_REPLY.
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+ }
+}
+
+TEST_P(SpdyNetworkTransactionTest, RetryAfterRefused) {
+ // Construct the request.
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 1),
+ CreateMockWrite(*req2, 3),
+ };
+
+ scoped_ptr<SpdyFrame> refused(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead reads[] = {
+ CreateMockRead(*refused, 2),
+ CreateMockRead(*resp, 4),
+ CreateMockRead(*body, 5),
+ MockRead(ASYNC, 0, 6) // EOF
+ };
+
+ OrderedSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+
+ helper.RunPreTestSetup();
+ helper.AddData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ // Start the transaction with basic parameters.
+ TestCompletionCallback callback;
+ int rv = trans->Start(
+ &CreateGetRequest(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ // Verify that we consumed all test data.
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+ << data.read_count()
+ << " Read index: "
+ << data.read_index();
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+ << data.write_count()
+ << " Write index: "
+ << data.write_index();
+
+ // Verify the SYN_REPLY.
+ HttpResponseInfo response = *trans->GetResponseInfo();
+ EXPECT_TRUE(response.headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionTest, OutOfOrderSynStream) {
+ // This first request will start to establish the SpdySession.
+ // Then we will start the second (MEDIUM priority) and then third
+ // (HIGHEST priority) request in such a way that the third will actually
+ // start before the second, causing the second to be numbered differently
+ // than the order they were created.
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, HIGHEST, true));
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 3),
+ CreateMockWrite(*req3, 4),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*body1, 2),
+ CreateMockRead(*resp2, 5),
+ CreateMockRead(*body2, 6),
+ CreateMockRead(*resp3, 7),
+ CreateMockRead(*body3, 8),
+ MockRead(ASYNC, 0, 9) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), LOWEST,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+
+ // Start the first transaction to set up the SpdySession
+ HttpNetworkTransaction* trans = helper.trans();
+ TestCompletionCallback callback;
+ HttpRequestInfo info1 = CreateGetRequest();
+ int rv = trans->Start(&info1, callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ // Run the message loop, but do not allow the write to complete.
+ // This leaves the SpdySession with a write pending, which prevents
+ // SpdySession from attempting subsequent writes until this write completes.
+ base::RunLoop().RunUntilIdle();
+
+ // Now, start both new transactions
+ HttpRequestInfo info2 = CreateGetRequest();
+ TestCompletionCallback callback2;
+ scoped_ptr<HttpNetworkTransaction> trans2(
+ new HttpNetworkTransaction(MEDIUM, helper.session().get()));
+ rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ base::RunLoop().RunUntilIdle();
+
+ HttpRequestInfo info3 = CreateGetRequest();
+ TestCompletionCallback callback3;
+ scoped_ptr<HttpNetworkTransaction> trans3(
+ new HttpNetworkTransaction(HIGHEST, helper.session().get()));
+ rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ base::RunLoop().RunUntilIdle();
+
+ // We now have two SYN_STREAM frames queued up which will be
+ // dequeued only once the first write completes, which we
+ // now allow to happen.
+ data.RunFor(2);
+ EXPECT_EQ(OK, callback.WaitForResult());
+
+ // And now we can allow everything else to run to completion.
+ data.SetStop(10);
+ data.Run();
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(OK, callback3.WaitForResult());
+
+ helper.VerifyDataConsumed();
+}
+
+// The tests below are only for SPDY/3 and above.
+
+// Test that sent data frames and received WINDOW_UPDATE frames change
+// the send_window_size_ correctly.
+
+// WINDOW_UPDATE is different than most other frames in that it can arrive
+// while the client is still sending the request body. In order to enforce
+// this scenario, we feed a couple of dummy frames and give a delay of 0 to
+// socket data provider, so that initial read that is done as soon as the
+// stream is created, succeeds and schedules another read. This way reads
+// and writes are interleaved; after doing a full frame write, SpdyStream
+// will break out of DoLoop and will read and process a WINDOW_UPDATE.
+// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away
+// since request has not been completely written, therefore we feed
+// enough number of WINDOW_UPDATEs to finish the first read and cause a
+// write, leading to a complete write of request body; after that we send
+// a reply with a body, to cause a graceful shutdown.
+
+// TODO(agayev): develop a socket data provider where both, reads and
+// writes are ordered so that writing tests like these are easy and rewrite
+// all these tests using it. Right now we are working around the
+// limitations as described above and it's not deterministic, tests may
+// fail under specific circumstances.
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ static int kFrameCount = 2;
+ scoped_ptr<std::string> content(
+ new std::string(kMaxSpdyFrameChunkSize, 'a'));
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content->c_str(), content->size(), false));
+ scoped_ptr<SpdyFrame> body_end(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content->c_str(), content->size(), true));
+
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 1),
+ CreateMockWrite(*body_end, 2),
+ };
+
+ static const int32 kDeltaWindowSize = 0xff;
+ static const int kDeltaCount = 4;
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+ scoped_ptr<SpdyFrame> window_update_dummy(
+ spdy_util_.ConstructSpdyWindowUpdate(2, kDeltaWindowSize));
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*window_update_dummy, 3),
+ CreateMockRead(*window_update_dummy, 4),
+ CreateMockRead(*window_update_dummy, 5),
+ CreateMockRead(*window_update, 6), // Four updates, therefore window
+ CreateMockRead(*window_update, 7), // size should increase by
+ CreateMockRead(*window_update, 8), // kDeltaWindowSize * 4
+ CreateMockRead(*window_update, 9),
+ CreateMockRead(*resp, 10),
+ CreateMockRead(*body_end, 11),
+ MockRead(ASYNC, 0, 0, 12) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ ScopedVector<UploadElementReader> element_readers;
+ for (int i = 0; i < kFrameCount; ++i) {
+ element_readers.push_back(
+ new UploadBytesElementReader(content->c_str(), content->size()));
+ }
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL(kDefaultURL);
+ request.upload_data_stream = &upload_data_stream;
+
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.AddDeterministicData(&data);
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.RunFor(11);
+
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+ ASSERT_TRUE(stream != NULL);
+ ASSERT_TRUE(stream->stream() != NULL);
+ EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize) +
+ kDeltaWindowSize * kDeltaCount -
+ kMaxSpdyFrameChunkSize * kFrameCount,
+ stream->stream()->send_window_size());
+
+ data.RunFor(1);
+
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that received data frames and sent WINDOW_UPDATE frames change
+// the recv_window_size_ correctly.
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ // Set the data in the body frame large enough to trigger sending a
+ // WINDOW_UPDATE by the stream.
+ const std::string body_data(kSpdyStreamInitialWindowSize / 2 + 1, 'x');
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> session_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(0, body_data.size()));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, body_data.size()));
+
+ std::vector<MockWrite> writes;
+ writes.push_back(CreateMockWrite(*req));
+ if (GetParam().protocol >= kProtoSPDY31)
+ writes.push_back(CreateMockWrite(*session_window_update));
+ writes.push_back(CreateMockWrite(*window_update));
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body_no_fin(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, body_data.data(), body_data.size(), false));
+ scoped_ptr<SpdyFrame> body_fin(
+ spdy_util_.ConstructSpdyBodyFrame(1, NULL, 0, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp),
+ CreateMockRead(*body_no_fin),
+ MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause
+ CreateMockRead(*body_fin),
+ MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ DelayedSocketData data(1, reads, arraysize(reads),
+ vector_as_array(&writes), writes.size());
+
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data);
+ helper.RunPreTestSetup();
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+ rv = callback.WaitForResult();
+ EXPECT_EQ(OK, rv);
+
+ SpdyHttpStream* stream =
+ static_cast<SpdyHttpStream*>(trans->stream_.get());
+ ASSERT_TRUE(stream != NULL);
+ ASSERT_TRUE(stream->stream() != NULL);
+
+ EXPECT_EQ(
+ static_cast<int>(kSpdyStreamInitialWindowSize - body_data.size()),
+ stream->stream()->recv_window_size());
+
+ const HttpResponseInfo* response = trans->GetResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_TRUE(response->headers.get() != NULL);
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+ EXPECT_TRUE(response->was_fetched_via_spdy);
+
+ // Issue a read which will cause a WINDOW_UPDATE to be sent and window
+ // size increased to default.
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(body_data.size()));
+ rv = trans->Read(buf.get(), body_data.size(), CompletionCallback());
+ EXPECT_EQ(static_cast<int>(body_data.size()), rv);
+ std::string content(buf->data(), buf->data() + body_data.size());
+ EXPECT_EQ(body_data, content);
+
+ // Schedule the reading of empty data frame with FIN
+ data.CompleteRead();
+
+ // Force write of WINDOW_UPDATE which was scheduled during the above
+ // read.
+ base::RunLoop().RunUntilIdle();
+
+ // Read EOF.
+ data.CompleteRead();
+
+ helper.VerifyDataConsumed();
+}
+
+// Test that WINDOW_UPDATE frame causing overflow is handled correctly.
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ // Number of full frames we hope to write (but will not, used to
+ // set content-length header correctly)
+ static int kFrameCount = 3;
+
+ scoped_ptr<std::string> content(
+ new std::string(kMaxSpdyFrameChunkSize, 'a'));
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content->c_str(), content->size(), false));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR));
+
+ // We're not going to write a data frame with FIN, we'll receive a bad
+ // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame.
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 2),
+ CreateMockWrite(*rst, 3),
+ };
+
+ static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+ MockRead reads[] = {
+ CreateMockRead(*window_update, 1),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+
+ ScopedVector<UploadElementReader> element_readers;
+ for (int i = 0; i < kFrameCount; ++i) {
+ element_readers.push_back(
+ new UploadBytesElementReader(content->c_str(), content->size()));
+ }
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ // Setup the request
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_data_stream;
+
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ data.RunFor(5);
+ ASSERT_TRUE(callback.have_result());
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, callback.WaitForResult());
+ helper.VerifyDataConsumed();
+}
+
+// Test that after hitting a send window size of 0, the write process
+// stalls and upon receiving WINDOW_UPDATE frame write resumes.
+
+// This test constructs a POST request followed by enough data frames
+// containing 'a' that would make the window size 0, followed by another
+// data frame containing default content (which is "hello!") and this frame
+// also contains a FIN flag. DelayedSocketData is used to enforce all
+// writes go through before a read could happen. However, the last frame
+// ("hello!") is not supposed to go through since by the time its turn
+// arrives, window size is 0. At this point MessageLoop::Run() called via
+// callback would block. Therefore we call MessageLoop::RunUntilIdle()
+// which returns after performing all possible writes. We use DCHECKS to
+// ensure that last data frame is still there and stream has stalled.
+// After that, next read is artifically enforced, which causes a
+// WINDOW_UPDATE to be read and I/O process resumes.
+TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ // Number of frames we need to send to zero out the window size: data
+ // frames plus SYN_STREAM plus the last data frame; also we need another
+ // data frame that we will send once the WINDOW_UPDATE is received,
+ // therefore +3.
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+ // Calculate last frame's size; 0 size data frame is legal.
+ size_t last_frame_size =
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+ // Construct content for a data frame of maximum size.
+ std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize,
+ LOWEST, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once WINDOW_UPDATE frame is received.
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock writes.
+ scoped_ptr<MockWrite[]> writes(new MockWrite[num_writes]);
+ size_t i = 0;
+ writes[i] = CreateMockWrite(*req);
+ for (i = 1; i < num_writes - 2; i++)
+ writes[i] = CreateMockWrite(*body1);
+ writes[i++] = CreateMockWrite(*body2);
+ writes[i] = CreateMockWrite(*body3);
+
+ // Construct read frame, give enough space to upload the rest of the
+ // data.
+ scoped_ptr<SpdyFrame> session_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize));
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ MockRead reads[] = {
+ CreateMockRead(*session_window_update),
+ CreateMockRead(*session_window_update),
+ CreateMockRead(*window_update),
+ CreateMockRead(*window_update),
+ CreateMockRead(*reply),
+ CreateMockRead(*body2),
+ CreateMockRead(*body3),
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ // Skip the session window updates unless we're using SPDY/3.1 and
+ // above.
+ size_t read_offset = (GetParam().protocol >= kProtoSPDY31) ? 0 : 2;
+ size_t num_reads = arraysize(reads) - read_offset;
+
+ // Force all writes to happen before any read, last write will not
+ // actually queue a frame, due to window size being 0.
+ DelayedSocketData data(num_writes, reads + read_offset, num_reads,
+ writes.get(), num_writes);
+
+ ScopedVector<UploadElementReader> element_readers;
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+ upload_data_string.append(kUploadData, kUploadDataSize);
+ element_readers.push_back(new UploadBytesElementReader(
+ upload_data_string.c_str(), upload_data_string.size()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_data_stream;
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.AddData(&data);
+ helper.RunPreTestSetup();
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ base::RunLoop().RunUntilIdle(); // Write as much as we can.
+
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+ ASSERT_TRUE(stream != NULL);
+ ASSERT_TRUE(stream->stream() != NULL);
+ EXPECT_EQ(0, stream->stream()->send_window_size());
+ // All the body data should have been read.
+ // TODO(satorux): This is because of the weirdness in reading the request
+ // body in OnSendBodyComplete(). See crbug.com/113107.
+ EXPECT_TRUE(upload_data_stream.IsEOF());
+ // But the body is not yet fully sent (kUploadData is not yet sent)
+ // since we're send-stalled.
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control());
+
+ data.ForceNextRead(); // Read in WINDOW_UPDATE frame.
+ rv = callback.WaitForResult();
+ helper.VerifyDataConsumed();
+}
+
+// Test we correctly handle the case where the SETTINGS frame results in
+// unstalling the send window.
+TEST_P(SpdyNetworkTransactionTest, FlowControlStallResumeAfterSettings) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ // Number of frames we need to send to zero out the window size: data
+ // frames plus SYN_STREAM plus the last data frame; also we need another
+ // data frame that we will send once the SETTING is received, therefore +3.
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+ // Calculate last frame's size; 0 size data frame is legal.
+ size_t last_frame_size =
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+ // Construct content for a data frame of maximum size.
+ std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize,
+ LOWEST, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once SETTINGS frame is received.
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock reads/writes.
+ std::vector<MockRead> reads;
+ std::vector<MockWrite> writes;
+ size_t i = 0;
+ writes.push_back(CreateMockWrite(*req, i++));
+ while (i < num_writes - 2)
+ writes.push_back(CreateMockWrite(*body1, i++));
+ writes.push_back(CreateMockWrite(*body2, i++));
+
+ // Construct read frame for SETTINGS that gives enough space to upload the
+ // rest of the data.
+ SettingsMap settings;
+ settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+ SettingsFlagsAndValue(
+ SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize * 2);
+ scoped_ptr<SpdyFrame> settings_frame_large(
+ spdy_util_.ConstructSpdySettings(settings));
+
+ reads.push_back(CreateMockRead(*settings_frame_large, i++));
+
+ scoped_ptr<SpdyFrame> session_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize));
+ if (GetParam().protocol >= kProtoSPDY31)
+ reads.push_back(CreateMockRead(*session_window_update, i++));
+
+ writes.push_back(CreateMockWrite(*body3, i++));
+
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ reads.push_back(CreateMockRead(*reply, i++));
+ reads.push_back(CreateMockRead(*body2, i++));
+ reads.push_back(CreateMockRead(*body3, i++));
+ reads.push_back(MockRead(ASYNC, 0, i++)); // EOF
+
+ // Force all writes to happen before any read, last write will not
+ // actually queue a frame, due to window size being 0.
+ DeterministicSocketData data(vector_as_array(&reads), reads.size(),
+ vector_as_array(&writes), writes.size());
+
+ ScopedVector<UploadElementReader> element_readers;
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+ upload_data_string.append(kUploadData, kUploadDataSize);
+ element_readers.push_back(new UploadBytesElementReader(
+ upload_data_string.c_str(), upload_data_string.size()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_data_stream;
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.RunFor(num_writes - 1); // Write as much as we can.
+
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+ ASSERT_TRUE(stream != NULL);
+ ASSERT_TRUE(stream->stream() != NULL);
+ EXPECT_EQ(0, stream->stream()->send_window_size());
+
+ // All the body data should have been read.
+ // TODO(satorux): This is because of the weirdness in reading the request
+ // body in OnSendBodyComplete(). See crbug.com/113107.
+ EXPECT_TRUE(upload_data_stream.IsEOF());
+ // But the body is not yet fully sent (kUploadData is not yet sent)
+ // since we're send-stalled.
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control());
+
+ data.RunFor(6); // Read in SETTINGS frame to unstall.
+ rv = callback.WaitForResult();
+ helper.VerifyDataConsumed();
+ // If stream is NULL, that means it was unstalled and closed.
+ EXPECT_TRUE(stream->stream() == NULL);
+}
+
+// Test we correctly handle the case where the SETTINGS frame results in a
+// negative send window size.
+TEST_P(SpdyNetworkTransactionTest, FlowControlNegativeSendWindowSize) {
+ if (GetParam().protocol < kProtoSPDY3)
+ return;
+
+ // Number of frames we need to send to zero out the window size: data
+ // frames plus SYN_STREAM plus the last data frame; also we need another
+ // data frame that we will send once the SETTING is received, therefore +3.
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+ // Calculate last frame's size; 0 size data frame is legal.
+ size_t last_frame_size =
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+ // Construct content for a data frame of maximum size.
+ std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost(
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize,
+ LOWEST, NULL, 0));
+
+ // Full frames.
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), content.size(), false));
+
+ // Last frame to zero out the window size.
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, content.c_str(), last_frame_size, false));
+
+ // Data frame to be sent once SETTINGS frame is received.
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ // Fill in mock reads/writes.
+ std::vector<MockRead> reads;
+ std::vector<MockWrite> writes;
+ size_t i = 0;
+ writes.push_back(CreateMockWrite(*req, i++));
+ while (i < num_writes - 2)
+ writes.push_back(CreateMockWrite(*body1, i++));
+ writes.push_back(CreateMockWrite(*body2, i++));
+
+ // Construct read frame for SETTINGS that makes the send_window_size
+ // negative.
+ SettingsMap new_settings;
+ new_settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+ SettingsFlagsAndValue(
+ SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize / 2);
+ scoped_ptr<SpdyFrame> settings_frame_small(
+ spdy_util_.ConstructSpdySettings(new_settings));
+ // Construct read frames for WINDOW_UPDATE that makes the send_window_size
+ // positive.
+ scoped_ptr<SpdyFrame> session_window_update_init_size(
+ spdy_util_.ConstructSpdyWindowUpdate(0, kSpdyStreamInitialWindowSize));
+ scoped_ptr<SpdyFrame> window_update_init_size(
+ spdy_util_.ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize));
+
+ reads.push_back(CreateMockRead(*settings_frame_small, i++));
+
+ if (GetParam().protocol >= kProtoSPDY3)
+ reads.push_back(CreateMockRead(*session_window_update_init_size, i++));
+
+ reads.push_back(CreateMockRead(*window_update_init_size, i++));
+
+ writes.push_back(CreateMockWrite(*body3, i++));
+
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ reads.push_back(CreateMockRead(*reply, i++));
+ reads.push_back(CreateMockRead(*body2, i++));
+ reads.push_back(CreateMockRead(*body3, i++));
+ reads.push_back(MockRead(ASYNC, 0, i++)); // EOF
+
+ // Force all writes to happen before any read, last write will not
+ // actually queue a frame, due to window size being 0.
+ DeterministicSocketData data(vector_as_array(&reads), reads.size(),
+ vector_as_array(&writes), writes.size());
+
+ ScopedVector<UploadElementReader> element_readers;
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+ upload_data_string.append(kUploadData, kUploadDataSize);
+ element_readers.push_back(new UploadBytesElementReader(
+ upload_data_string.c_str(), upload_data_string.size()));
+ UploadDataStream upload_data_stream(&element_readers, 0);
+
+ HttpRequestInfo request;
+ request.method = "POST";
+ request.url = GURL("http://www.google.com/");
+ request.upload_data_stream = &upload_data_stream;
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY,
+ BoundNetLog(), GetParam(), NULL);
+ helper.SetDeterministic();
+ helper.RunPreTestSetup();
+ helper.AddDeterministicData(&data);
+
+ HttpNetworkTransaction* trans = helper.trans();
+
+ TestCompletionCallback callback;
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, rv);
+
+ data.RunFor(num_writes - 1); // Write as much as we can.
+
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+ ASSERT_TRUE(stream != NULL);
+ ASSERT_TRUE(stream->stream() != NULL);
+ EXPECT_EQ(0, stream->stream()->send_window_size());
+
+ // All the body data should have been read.
+ // TODO(satorux): This is because of the weirdness in reading the request
+ // body in OnSendBodyComplete(). See crbug.com/113107.
+ EXPECT_TRUE(upload_data_stream.IsEOF());
+ // But the body is not yet fully sent (kUploadData is not yet sent)
+ // since we're send-stalled.
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control());
+
+ // Read in WINDOW_UPDATE or SETTINGS frame.
+ data.RunFor((GetParam().protocol >= kProtoSPDY31) ? 8 : 7);
+ rv = callback.WaitForResult();
+ helper.VerifyDataConsumed();
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_priority_forest.h b/chromium/net/spdy/spdy_priority_forest.h
new file mode 100644
index 00000000000..4f00f566caf
--- /dev/null
+++ b/chromium/net/spdy/spdy_priority_forest.h
@@ -0,0 +1,527 @@
+// Copyright (c) 2013 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_SPDY_SPDY_PRIORITY_FOREST_H_
+#define NET_SPDY_SPDY_PRIORITY_FOREST_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/rand_util.h"
+
+namespace net {
+
+// This data structure implements the SPDY prioriziation data structures
+// defined in this document: http://go/spdy4-prioritization
+//
+// Nodes can be added and removed, and dependencies between them defined. Each
+// node can have at most one parent and at most one child (forming a list), but
+// there can be multiple lists, with each list root having its own priority.
+// Individual nodes can also be marked as ready to read/write, and then the
+// whole structure can be queried to pick the next node to read/write out of
+// those ready.
+//
+// The NodeId and Priority types must be POD that support comparison (most
+// likely, they will be numbers).
+template <typename NodeId, typename Priority>
+class SpdyPriorityForest {
+ public:
+ SpdyPriorityForest();
+ ~SpdyPriorityForest();
+
+ // Return the number of nodes currently in the forest.
+ int num_nodes() const;
+
+ // Return true if the forest contains a node with the given ID.
+ bool NodeExists(NodeId node_id) const;
+
+ // Add a new root node to the forest, with the given priority. Returns true
+ // on success, or false if the node_id already exists within the forest.
+ bool AddRootNode(NodeId node_id, Priority priority);
+
+ // Add a new node to the forest, with the given parent. Returns true on
+ // success. Returns false and has no effect if the new node already exists,
+ // or if the parent doesn't exist, or if the parent already has a child.
+ bool AddNonRootNode(NodeId node_id, NodeId parent_id, bool unordered);
+
+ // Remove an existing node from the forest. Returns true on success, or
+ // false if the node doesn't exist.
+ bool RemoveNode(NodeId node_id);
+
+ // Get the priority of the given node. If the node doesn't exist, or is not
+ // a root node (and thus has no priority), returns Priority().
+ Priority GetPriority(NodeId node_id) const;
+
+ // Get the parent of the given node. If the node doesn't exist, or is a root
+ // node (and thus has no parent), returns NodeId().
+ NodeId GetParent(NodeId node_id) const;
+
+ // Determine if the given node is unordered with respect to its parent. If
+ // the node doesn't exist, or is a root node (and thus has no parent),
+ // returns false.
+ bool IsNodeUnordered(NodeId node_id) const;
+
+ // Get the child of the given node. If the node doesn't exist, or has no
+ // child, returns NodeId().
+ NodeId GetChild(NodeId node_id) const;
+
+ // Set the priority of the given node. If the node was not already a root
+ // node, this makes it a root node. Returns true on success, or false if the
+ // node doesn't exist.
+ bool SetPriority(NodeId node_id, Priority priority);
+
+ // Set the parent of the given node. If the node was a root node, this makes
+ // it no longer a root. Returns true on success. Returns false and has no
+ // effect if (1) the node and/or the parent doesn't exist, (2) the new parent
+ // already has a different child than the node, or (3) if the new parent is a
+ // descendant of the node (so this would have created a cycle).
+ bool SetParent(NodeId node_id, NodeId parent_id, bool unordered);
+
+ // Check if a node is marked as ready to read. Returns false if the node
+ // doesn't exist.
+ bool IsMarkedReadyToRead(NodeId node_id) const;
+ // Mark the node as ready or not ready to read. Returns true on success, or
+ // false if the node doesn't exist.
+ bool MarkReadyToRead(NodeId node_id);
+ bool MarkNoLongerReadyToRead(NodeId node_id);
+ // Return the ID of the next node that we should read, or return NodeId() if
+ // no node in the forest is ready to read.
+ NodeId NextNodeToRead();
+
+ // Check if a node is marked as ready to write. Returns false if the node
+ // doesn't exist.
+ bool IsMarkedReadyToWrite(NodeId node_id) const;
+ // Mark the node as ready or not ready to write. Returns true on success, or
+ // false if the node doesn't exist.
+ bool MarkReadyToWrite(NodeId node_id);
+ bool MarkNoLongerReadyToWrite(NodeId node_id);
+ // Return the ID of the next node that we should write, or return NodeId() if
+ // no node in the forest is ready to write.
+ NodeId NextNodeToWrite();
+
+ // Return true if all internal invariants hold (useful for unit tests).
+ // Unless there are bugs, this should always return true.
+ bool ValidateInvariantsForTests() const;
+
+ private:
+ enum NodeType { ROOT_NODE, NONROOT_ORDERED, NONROOT_UNORDERED };
+ struct Node {
+ Node() : type(ROOT_NODE), flags(0), child() {
+ depends_on.priority = Priority();
+ }
+ NodeType type;
+ unsigned int flags; // bitfield of flags
+ union {
+ Priority priority; // used for root nodes
+ NodeId parent_id; // used for non-root nodes
+ } depends_on;
+ NodeId child; // node ID of child (or NodeId() for no child)
+ };
+
+ typedef base::hash_map<NodeId, Node> NodeMap;
+
+ // Constants for the Node.flags bitset:
+ // kReadToRead: set for nodes that are ready for reading
+ static const unsigned int kReadyToRead = (1 << 0);
+ // kReadToWrite: set for nodes that are ready for writing
+ static const unsigned int kReadyToWrite = (1 << 1);
+
+ // Common code for IsMarkedReadyToRead and IsMarkedReadyToWrite.
+ bool IsMarked(NodeId node_id, unsigned int flag) const;
+ // Common code for MarkReadyToRead and MarkReadyToWrite.
+ bool Mark(NodeId node_id, unsigned int flag);
+ // Common code for MarkNoLongerReadyToRead and MarkNoLongerReadyToWrite.
+ bool Unmark(NodeId node_id, unsigned int flag);
+ // Common code for NextNodeToRead and NextNodeToWrite;
+ NodeId FirstMarkedNode(unsigned int flag);
+ // Get the given node, or return NULL if it doesn't exist.
+ const Node* FindNode(NodeId node_id) const;
+
+ NodeMap all_nodes_; // maps from node IDs to Node objects
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyPriorityForest);
+};
+
+template <typename NodeId, typename Priority>
+SpdyPriorityForest<NodeId, Priority>::SpdyPriorityForest() {}
+
+template <typename NodeId, typename Priority>
+SpdyPriorityForest<NodeId, Priority>::~SpdyPriorityForest() {}
+
+template <typename NodeId, typename Priority>
+int SpdyPriorityForest<NodeId, Priority>::num_nodes() const {
+ return all_nodes_.size();
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::NodeExists(NodeId node_id) const {
+ return all_nodes_.count(node_id) != 0;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::AddRootNode(
+ NodeId node_id, Priority priority) {
+ if (NodeExists(node_id)) {
+ return false;
+ }
+ Node* new_node = &all_nodes_[node_id];
+ new_node->type = ROOT_NODE;
+ new_node->depends_on.priority = priority;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::AddNonRootNode(
+ NodeId node_id, NodeId parent_id, bool unordered) {
+ if (NodeExists(node_id) || !NodeExists(parent_id)) {
+ return false;
+ }
+
+ Node* parent = &all_nodes_[parent_id];
+ if (parent->child != NodeId()) {
+ return false;
+ }
+
+ Node* new_node = &all_nodes_[node_id];
+ new_node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED);
+ new_node->depends_on.parent_id = parent_id;
+ parent->child = node_id;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::RemoveNode(NodeId node_id) {
+ if (!NodeExists(node_id)) {
+ return false;
+ }
+ const Node& node = all_nodes_[node_id];
+
+ // If the node to be removed is not a root node, we need to change its
+ // parent's child ID.
+ if (node.type != ROOT_NODE) {
+ DCHECK(NodeExists(node.depends_on.parent_id));
+ Node* parent = &all_nodes_[node.depends_on.parent_id];
+ DCHECK_EQ(node_id, parent->child);
+ parent->child = node.child;
+ }
+
+ // If the node has a child, we need to change the child's priority or parent.
+ if (node.child != NodeId()) {
+ DCHECK(NodeExists(node.child));
+ Node* child = &all_nodes_[node.child];
+ DCHECK_NE(ROOT_NODE, child->type);
+ DCHECK_EQ(node_id, child->depends_on.parent_id);
+ // Make the child's new depends_on be the node's depends_on (whether that
+ // be a priority or a parent node ID).
+ child->depends_on = node.depends_on;
+ // If the removed node was a root, its child is now a root. Otherwise, the
+ // child will be be unordered if and only if it was already unordered and
+ // the removed not is also not ordered.
+ if (node.type == ROOT_NODE) {
+ child->type = ROOT_NODE;
+ } else if (node.type == NONROOT_ORDERED) {
+ child->type = NONROOT_ORDERED;
+ }
+ }
+
+ // Delete the node.
+ all_nodes_.erase(node_id);
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+Priority SpdyPriorityForest<NodeId, Priority>::GetPriority(
+ NodeId node_id) const {
+ const Node* node = FindNode(node_id);
+ if (node != NULL && node->type == ROOT_NODE) {
+ return node->depends_on.priority;
+ } else {
+ return Priority();
+ }
+}
+
+template <typename NodeId, typename Priority>
+NodeId SpdyPriorityForest<NodeId, Priority>::GetParent(NodeId node_id) const {
+ const Node* node = FindNode(node_id);
+ if (node != NULL && node->type != ROOT_NODE) {
+ return node->depends_on.parent_id;
+ } else {
+ return NodeId();
+ }
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::IsNodeUnordered(
+ NodeId node_id) const {
+ const Node* node = FindNode(node_id);
+ return node != NULL && node->type == NONROOT_UNORDERED;
+}
+
+template <typename NodeId, typename Priority>
+NodeId SpdyPriorityForest<NodeId, Priority>::GetChild(NodeId node_id) const {
+ const Node* node = FindNode(node_id);
+ if (node != NULL) {
+ return node->child;
+ } else {
+ return NodeId();
+ }
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::SetPriority(
+ NodeId node_id, Priority priority) {
+ if (!NodeExists(node_id)) {
+ return false;
+ }
+
+ Node* node = &all_nodes_[node_id];
+ // If this is not already a root node, we need to make it be a root node.
+ if (node->type != ROOT_NODE) {
+ DCHECK(NodeExists(node->depends_on.parent_id));
+ Node* parent = &all_nodes_[node->depends_on.parent_id];
+ parent->child = NodeId();
+ node->type = ROOT_NODE;
+ }
+
+ node->depends_on.priority = priority;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::SetParent(
+ NodeId node_id, NodeId parent_id, bool unordered) {
+ if (!NodeExists(node_id) || !NodeExists(parent_id)) {
+ return false;
+ }
+
+ Node* node = &all_nodes_[node_id];
+ Node* new_parent = &all_nodes_[parent_id];
+ // If the new parent is already the node's parent, all we have to do is
+ // update the node type and we're done.
+ if (new_parent->child == node_id) {
+ node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED);
+ return true;
+ }
+ // Otherwise, if the new parent already has a child, we fail.
+ if (new_parent->child != NodeId()) {
+ return false;
+ }
+
+ // Next, make sure we won't create a cycle.
+ if (node_id == parent_id) return false;
+ Node* last = node;
+ NodeId last_id = node_id;
+ while (last->child != NodeId()) {
+ if (last->child == parent_id) return false;
+ last_id = last->child;
+ DCHECK(NodeExists(last_id));
+ last = &all_nodes_[last_id];
+ }
+
+ // If the node is not a root, we need clear its old parent's child field
+ // (unless the old parent is the same as the new parent).
+ if (node->type != ROOT_NODE) {
+ const NodeId old_parent_id = node->depends_on.parent_id;
+ DCHECK(NodeExists(old_parent_id));
+ DCHECK(old_parent_id != parent_id);
+ Node* old_parent = &all_nodes_[old_parent_id];
+ DCHECK_EQ(node_id, old_parent->child);
+ old_parent->child = NodeId();
+ }
+
+ // Make the change.
+ node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED);
+ node->depends_on.parent_id = parent_id;
+ new_parent->child = node_id;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToRead(
+ NodeId node_id) const {
+ return IsMarked(node_id, kReadyToRead);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToRead(NodeId node_id) {
+ return Mark(node_id, kReadyToRead);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToRead(
+ NodeId node_id) {
+ return Unmark(node_id, kReadyToRead);
+}
+
+template <typename NodeId, typename Priority>
+NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToRead() {
+ return FirstMarkedNode(kReadyToRead);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToWrite(
+ NodeId node_id) const {
+ return IsMarked(node_id, kReadyToWrite);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToWrite(NodeId node_id) {
+ return Mark(node_id, kReadyToWrite);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToWrite(
+ NodeId node_id) {
+ return Unmark(node_id, kReadyToWrite);
+}
+
+template <typename NodeId, typename Priority>
+NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToWrite() {
+ return FirstMarkedNode(kReadyToWrite);
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::IsMarked(
+ NodeId node_id, unsigned int flag) const {
+ const Node* node = FindNode(node_id);
+ return node != NULL && (node->flags & flag) != 0;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::Mark(
+ NodeId node_id, unsigned int flag) {
+ if (!NodeExists(node_id)) {
+ return false;
+ }
+ all_nodes_[node_id].flags |= flag;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::Unmark(
+ NodeId node_id, unsigned int flag) {
+ if (!NodeExists(node_id)) {
+ return false;
+ }
+ all_nodes_[node_id].flags &= ~flag;
+ return true;
+}
+
+template <typename NodeId, typename Priority>
+NodeId SpdyPriorityForest<NodeId, Priority>::FirstMarkedNode(
+ unsigned int flag) {
+ // TODO(mdsteele): This is an *incredibly* stupid brute force solution.
+
+ // Get all root nodes that have at least one marked child.
+ uint64 total_weight = 0;
+ std::map<uint64, NodeId> roots; // maps cumulative weight to root node ID
+ for (typename NodeMap::const_iterator iter = all_nodes_.begin();
+ iter != all_nodes_.end(); ++iter) {
+ const NodeId root_id = iter->first;
+ const Node& root = iter->second;
+ if (root.type == ROOT_NODE) {
+ // See if there is at least one marked node in this root's chain.
+ for (const Node* node = &root; ; node = &all_nodes_[node->child]) {
+ if ((node->flags & flag) != 0) {
+ total_weight += static_cast<uint64>(root.depends_on.priority);
+ roots[total_weight] = root_id;
+ break;
+ }
+ if (node->child == NodeId()) {
+ break;
+ }
+ DCHECK(NodeExists(node->child));
+ }
+ }
+ }
+
+ // If there are no ready nodes, then return NodeId().
+ if (total_weight == 0) {
+ DCHECK(roots.empty());
+ return NodeId();
+ } else {
+ DCHECK(!roots.empty());
+ }
+
+ // Randomly select a tree to use.
+ typename std::map<uint64, NodeId>::const_iterator root_iter =
+ roots.upper_bound(base::RandGenerator(total_weight));
+ DCHECK(root_iter != roots.end());
+ const NodeId root_id = root_iter->second;
+
+ // Find the first node in the chain that is ready.
+ NodeId node_id = root_id;
+ while (true) {
+ DCHECK(NodeExists(node_id));
+ Node* node = &all_nodes_[node_id];
+ if ((node->flags & flag) != 0) {
+ // There might be more nodes that are ready and that are linked to this
+ // one in an unordered chain. Find all of them, then pick one randomly.
+ std::vector<NodeId> group;
+ group.push_back(node_id);
+ for (Node* next = node; next->child != NodeId();) {
+ DCHECK(NodeExists(next->child));
+ Node *child = &all_nodes_[next->child];
+ DCHECK_NE(ROOT_NODE, child->type);
+ if (child->type != NONROOT_UNORDERED) {
+ break;
+ }
+ if ((child->flags & flag) != 0) {
+ group.push_back(next->child);
+ }
+ next = child;
+ }
+ return group[base::RandGenerator(group.size())];
+ }
+ node_id = node->child;
+ }
+}
+
+template <typename NodeId, typename Priority>
+const typename SpdyPriorityForest<NodeId, Priority>::Node*
+SpdyPriorityForest<NodeId, Priority>::FindNode(NodeId node_id) const {
+ typename NodeMap::const_iterator iter = all_nodes_.find(node_id);
+ if (iter == all_nodes_.end()) {
+ return NULL;
+ }
+ return &iter->second;
+}
+
+template <typename NodeId, typename Priority>
+bool SpdyPriorityForest<NodeId, Priority>::ValidateInvariantsForTests() const {
+ for (typename NodeMap::const_iterator iter = all_nodes_.begin();
+ iter != all_nodes_.end(); ++iter) {
+ const NodeId node_id = iter->first;
+ const Node& node = iter->second;
+ if (node.type != ROOT_NODE &&
+ (!NodeExists(node.depends_on.parent_id) ||
+ GetChild(node.depends_on.parent_id) != node_id)) {
+ return false;
+ }
+ if (node.child != NodeId()) {
+ if (!NodeExists(node.child) || node_id != GetParent(node.child)) {
+ return false;
+ }
+ }
+
+ NodeId child_id = node.child;
+ int count = 0;
+ while (child_id != NodeId()) {
+ if (count > num_nodes() || node_id == child_id) {
+ return false;
+ }
+ child_id = GetChild(child_id);
+ ++count;
+ }
+ }
+ return true;
+}
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_PRIORITY_FOREST_H_
diff --git a/chromium/net/spdy/spdy_priority_forest_test.cc b/chromium/net/spdy/spdy_priority_forest_test.cc
new file mode 100644
index 00000000000..58b21d469e3
--- /dev/null
+++ b/chromium/net/spdy/spdy_priority_forest_test.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2013 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/spdy/spdy_priority_forest.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(SpdyPriorityForestTest, AddAndRemoveNodes) {
+ SpdyPriorityForest<uint32,int16> forest;
+ EXPECT_EQ(0, forest.num_nodes());
+ EXPECT_FALSE(forest.NodeExists(1));
+
+ EXPECT_TRUE(forest.AddRootNode(1, 1000));
+ EXPECT_EQ(1, forest.num_nodes());
+ ASSERT_TRUE(forest.NodeExists(1));
+ EXPECT_EQ(1000, forest.GetPriority(1));
+ EXPECT_FALSE(forest.NodeExists(5));
+
+ EXPECT_TRUE(forest.AddRootNode(5, 50));
+ EXPECT_FALSE(forest.AddRootNode(5, 500));
+ EXPECT_EQ(2, forest.num_nodes());
+ EXPECT_TRUE(forest.NodeExists(1));
+ ASSERT_TRUE(forest.NodeExists(5));
+ EXPECT_EQ(50, forest.GetPriority(5));
+ EXPECT_FALSE(forest.NodeExists(13));
+
+ EXPECT_TRUE(forest.AddRootNode(13, 130));
+ EXPECT_EQ(3, forest.num_nodes());
+ EXPECT_TRUE(forest.NodeExists(1));
+ EXPECT_TRUE(forest.NodeExists(5));
+ ASSERT_TRUE(forest.NodeExists(13));
+ EXPECT_EQ(130, forest.GetPriority(13));
+
+ EXPECT_TRUE(forest.RemoveNode(5));
+ EXPECT_FALSE(forest.RemoveNode(5));
+ EXPECT_EQ(2, forest.num_nodes());
+ EXPECT_TRUE(forest.NodeExists(1));
+ EXPECT_FALSE(forest.NodeExists(5));
+ EXPECT_TRUE(forest.NodeExists(13));
+
+ // The parent node 19 doesn't exist, so this should fail:
+ EXPECT_FALSE(forest.AddNonRootNode(7, 19, false));
+ // This should succed, creating node 7:
+ EXPECT_TRUE(forest.AddNonRootNode(7, 13, false));
+ // Now node 7 already exists, so this should fail:
+ EXPECT_FALSE(forest.AddNonRootNode(7, 1, false));
+ // Node 13 already has a child (7), so this should fail:
+ EXPECT_FALSE(forest.AddNonRootNode(17, 13, false));
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, SetParent) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 1000);
+ forest.AddNonRootNode(2, 1, false);
+ forest.AddNonRootNode(3, 2, false);
+ forest.AddNonRootNode(5, 3, false);
+ forest.AddNonRootNode(7, 5, false);
+ forest.AddNonRootNode(9, 7, false);
+ forest.AddRootNode(11, 2000);
+ forest.AddNonRootNode(13, 11, false);
+ // We can't set the parent of a nonexistent node, or set the parent of an
+ // existing node to a nonexistent node.
+ EXPECT_FALSE(forest.SetParent(99, 13, false));
+ EXPECT_FALSE(forest.SetParent(5, 99, false));
+ // We can't make a node a child a node that already has a child:
+ EXPECT_FALSE(forest.SetParent(13, 7, false));
+ EXPECT_FALSE(forest.SetParent(3, 11, false));
+ // These would create cycles:
+ EXPECT_FALSE(forest.SetParent(11, 13, false));
+ EXPECT_FALSE(forest.SetParent(1, 9, false));
+ EXPECT_FALSE(forest.SetParent(3, 9, false));
+ // But this change is legit:
+ EXPECT_EQ(7u, forest.GetChild(5));
+ EXPECT_TRUE(forest.SetParent(7, 13, false));
+ EXPECT_EQ(0u, forest.GetChild(5));
+ EXPECT_EQ(13u, forest.GetParent(7));
+ EXPECT_EQ(7u, forest.GetChild(13));
+ // So is this change (now that 9 is no longer a descendant of 1):
+ EXPECT_TRUE(forest.SetParent(1, 9, false));
+ EXPECT_EQ(9u, forest.GetParent(1));
+ EXPECT_EQ(1u, forest.GetChild(9));
+ // We must allow setting the parent of a node to its same parent (even though
+ // that parent of course has a child already), so that we can change
+ // orderedness.
+ EXPECT_EQ(1u, forest.GetParent(2));
+ EXPECT_EQ(2u, forest.GetChild(1));
+ EXPECT_FALSE(forest.IsNodeUnordered(2));
+ EXPECT_TRUE(forest.SetParent(2, 1, true));
+ EXPECT_EQ(1u, forest.GetParent(2));
+ EXPECT_EQ(2u, forest.GetChild(1));
+ EXPECT_TRUE(forest.IsNodeUnordered(2));
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, RemoveNodesFromMiddleOfChain) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 1000);
+ forest.AddNonRootNode(2, 1, false);
+ forest.AddNonRootNode(3, 2, true);
+ forest.AddNonRootNode(5, 3, false);
+ forest.AddNonRootNode(7, 5, true);
+ forest.AddNonRootNode(9, 7, true);
+
+ // Remove a node from the middle, with unordered links on both sides. The
+ // new merged link should also be unordered.
+ EXPECT_TRUE(forest.NodeExists(7));
+ EXPECT_EQ(7u, forest.GetChild(5));
+ EXPECT_EQ(7u, forest.GetParent(9));
+ EXPECT_TRUE(forest.IsNodeUnordered(9));
+ EXPECT_TRUE(forest.RemoveNode(7));
+ EXPECT_FALSE(forest.NodeExists(7));
+ EXPECT_EQ(9u, forest.GetChild(5));
+ EXPECT_EQ(5u, forest.GetParent(9));
+ EXPECT_TRUE(forest.IsNodeUnordered(9));
+
+ // Remove another node from the middle, with an unordered link on only one
+ // side. The new merged link should be ordered.
+ EXPECT_TRUE(forest.NodeExists(2));
+ EXPECT_EQ(2u, forest.GetChild(1));
+ EXPECT_EQ(2u, forest.GetParent(3));
+ EXPECT_FALSE(forest.IsNodeUnordered(2));
+ EXPECT_TRUE(forest.IsNodeUnordered(3));
+ EXPECT_TRUE(forest.RemoveNode(2));
+ EXPECT_FALSE(forest.NodeExists(2));
+ EXPECT_EQ(3u, forest.GetChild(1));
+ EXPECT_EQ(1u, forest.GetParent(3));
+ EXPECT_FALSE(forest.IsNodeUnordered(3));
+
+ // Try removing the root.
+ EXPECT_TRUE(forest.NodeExists(1));
+ EXPECT_EQ(0u, forest.GetParent(1));
+ EXPECT_EQ(1000, forest.GetPriority(1));
+ EXPECT_EQ(1u, forest.GetParent(3));
+ EXPECT_EQ(0, forest.GetPriority(3));
+ EXPECT_TRUE(forest.RemoveNode(1));
+ EXPECT_FALSE(forest.NodeExists(1));
+ EXPECT_EQ(0u, forest.GetParent(3));
+ EXPECT_EQ(1000, forest.GetPriority(3));
+
+ // Now try removing the tail.
+ EXPECT_TRUE(forest.NodeExists(9));
+ EXPECT_EQ(9u, forest.GetChild(5));
+ EXPECT_TRUE(forest.RemoveNode(9));
+ EXPECT_FALSE(forest.NodeExists(9));
+ EXPECT_EQ(0u, forest.GetChild(5));
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, MergeOrderedAndUnorderedLinks1) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 1000);
+ forest.AddNonRootNode(2, 1, true);
+ forest.AddNonRootNode(3, 2, false);
+
+ EXPECT_EQ(2u, forest.GetChild(1));
+ EXPECT_EQ(3u, forest.GetChild(2));
+ EXPECT_EQ(1u, forest.GetParent(2));
+ EXPECT_EQ(2u, forest.GetParent(3));
+ EXPECT_TRUE(forest.IsNodeUnordered(2));
+ EXPECT_FALSE(forest.IsNodeUnordered(3));
+ EXPECT_TRUE(forest.RemoveNode(2));
+ EXPECT_FALSE(forest.NodeExists(2));
+ EXPECT_EQ(3u, forest.GetChild(1));
+ EXPECT_EQ(1u, forest.GetParent(3));
+ EXPECT_FALSE(forest.IsNodeUnordered(3));
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, MergeOrderedAndUnorderedLinks2) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 1000);
+ forest.AddNonRootNode(2, 1, false);
+ forest.AddNonRootNode(3, 2, true);
+
+ EXPECT_EQ(2u, forest.GetChild(1));
+ EXPECT_EQ(3u, forest.GetChild(2));
+ EXPECT_EQ(1u, forest.GetParent(2));
+ EXPECT_EQ(2u, forest.GetParent(3));
+ EXPECT_FALSE(forest.IsNodeUnordered(2));
+ EXPECT_TRUE(forest.IsNodeUnordered(3));
+ EXPECT_TRUE(forest.RemoveNode(2));
+ EXPECT_FALSE(forest.NodeExists(2));
+ EXPECT_EQ(3u, forest.GetChild(1));
+ EXPECT_EQ(1u, forest.GetParent(3));
+ EXPECT_FALSE(forest.IsNodeUnordered(3));
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, WeightedSelectionOfForests) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 10);
+ forest.AddRootNode(3, 20);
+ forest.AddRootNode(5, 70);
+ EXPECT_EQ(70, forest.GetPriority(5));
+ EXPECT_TRUE(forest.SetPriority(5, 40));
+ EXPECT_FALSE(forest.SetPriority(7, 80));
+ EXPECT_EQ(40, forest.GetPriority(5));
+ forest.AddNonRootNode(7, 3, false);
+ EXPECT_FALSE(forest.IsMarkedReadyToRead(1));
+ EXPECT_TRUE(forest.MarkReadyToRead(1));
+ EXPECT_TRUE(forest.IsMarkedReadyToRead(1));
+ EXPECT_TRUE(forest.MarkReadyToRead(5));
+ EXPECT_TRUE(forest.MarkReadyToRead(7));
+ EXPECT_FALSE(forest.MarkReadyToRead(99));
+
+ int counts[8] = {0};
+ for (int i = 0; i < 7000; ++i) {
+ const uint32 node_id = forest.NextNodeToRead();
+ ASSERT_TRUE(node_id == 1 || node_id == 5 || node_id == 7)
+ << "node_id is " << node_id;
+ ++counts[node_id];
+ }
+
+ // In theory, these could fail even if the weighted random selection is
+ // implemented correctly, but it's very unlikely.
+ EXPECT_GE(counts[1], 800); EXPECT_LE(counts[1], 1200);
+ EXPECT_GE(counts[7], 1600); EXPECT_LE(counts[7], 2400);
+ EXPECT_GE(counts[5], 3200); EXPECT_LE(counts[5], 4800);
+
+ // If we unmark all but one node, then we know for sure that that node will
+ // be selected next.
+ EXPECT_TRUE(forest.MarkNoLongerReadyToRead(1));
+ EXPECT_TRUE(forest.MarkNoLongerReadyToRead(7));
+ EXPECT_FALSE(forest.MarkNoLongerReadyToRead(99));
+ EXPECT_EQ(5u, forest.NextNodeToRead());
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+TEST(SpdyPriorityForestTest, SelectionBetweenUnorderedNodes) {
+ SpdyPriorityForest<uint32,int16> forest;
+ forest.AddRootNode(1, 1000);
+ forest.AddNonRootNode(2, 1, false);
+ forest.AddNonRootNode(3, 2, true);
+ forest.AddNonRootNode(4, 3, true);
+ forest.AddNonRootNode(5, 4, true);
+ forest.AddNonRootNode(6, 5, true);
+ forest.AddNonRootNode(7, 6, false);
+ EXPECT_FALSE(forest.IsMarkedReadyToWrite(2));
+ EXPECT_TRUE(forest.MarkReadyToWrite(2));
+ EXPECT_TRUE(forest.IsMarkedReadyToWrite(2));
+ EXPECT_TRUE(forest.MarkReadyToWrite(4));
+ EXPECT_TRUE(forest.MarkReadyToWrite(6));
+ EXPECT_TRUE(forest.MarkReadyToWrite(7));
+ EXPECT_FALSE(forest.MarkReadyToWrite(99));
+
+ int counts[8] = {0};
+ for (int i = 0; i < 6000; ++i) {
+ const uint32 node_id = forest.NextNodeToWrite();
+ ASSERT_TRUE(node_id == 2 || node_id == 4 || node_id == 6)
+ << "node_id is " << node_id;
+ ++counts[node_id];
+ }
+
+ // In theory, these could fail even if the random selection is implemented
+ // correctly, but it's very unlikely.
+ EXPECT_GE(counts[2], 1600); EXPECT_LE(counts[2], 2400);
+ EXPECT_GE(counts[4], 1600); EXPECT_LE(counts[4], 2400);
+ EXPECT_GE(counts[6], 1600); EXPECT_LE(counts[6], 2400);
+
+ // Once we unmark that group of nodes, the next node up should be node 7,
+ // which has an ordered dependency on said group.
+ EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(2));
+ EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(4));
+ EXPECT_TRUE(forest.MarkNoLongerReadyToWrite(6));
+ EXPECT_FALSE(forest.MarkNoLongerReadyToWrite(99));
+ EXPECT_EQ(7u, forest.NextNodeToWrite());
+
+ ASSERT_TRUE(forest.ValidateInvariantsForTests());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_protocol.cc b/chromium/net/spdy/spdy_protocol.cc
new file mode 100644
index 00000000000..39031ebd9f5
--- /dev/null
+++ b/chromium/net/spdy/spdy_protocol.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+SpdyFrameWithNameValueBlockIR::SpdyFrameWithNameValueBlockIR(
+ SpdyStreamId stream_id) : SpdyFrameWithFinIR(stream_id) {}
+
+SpdyFrameWithNameValueBlockIR::~SpdyFrameWithNameValueBlockIR() {}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id, const base::StringPiece& data)
+ : SpdyFrameWithFinIR(stream_id) {
+ SetDataDeep(data);
+}
+
+SpdyDataIR::SpdyDataIR(SpdyStreamId stream_id)
+ : SpdyFrameWithFinIR(stream_id) {}
+
+SpdyDataIR::~SpdyDataIR() {}
+
+void SpdyDataIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitData(*this);
+}
+
+void SpdySynStreamIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitSynStream(*this);
+}
+
+void SpdySynReplyIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitSynReply(*this);
+}
+
+void SpdyRstStreamIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitRstStream(*this);
+}
+
+SpdySettingsIR::SpdySettingsIR() : clear_settings_(false) {}
+
+SpdySettingsIR::~SpdySettingsIR() {}
+
+void SpdySettingsIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitSettings(*this);
+}
+
+void SpdyPingIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitPing(*this);
+}
+
+void SpdyGoAwayIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitGoAway(*this);
+}
+
+void SpdyHeadersIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitHeaders(*this);
+}
+
+void SpdyWindowUpdateIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitWindowUpdate(*this);
+}
+
+SpdyCredentialIR::SpdyCredentialIR(int16 slot) {
+ set_slot(slot);
+}
+
+SpdyCredentialIR::~SpdyCredentialIR() {}
+
+void SpdyCredentialIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitCredential(*this);
+}
+
+void SpdyBlockedIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitBlocked(*this);
+}
+
+void SpdyPushPromiseIR::Visit(SpdyFrameVisitor* visitor) const {
+ return visitor->VisitPushPromise(*this);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_protocol.h b/chromium/net/spdy/spdy_protocol.h
new file mode 100644
index 00000000000..438d9d7f517
--- /dev/null
+++ b/chromium/net/spdy/spdy_protocol.h
@@ -0,0 +1,804 @@
+// Copyright (c) 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.
+
+// This file contains some protocol structures for use with SPDY 2 and 3
+// The SPDY 2 spec can be found at:
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
+// The SPDY 3 spec can be found at:
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3
+
+#ifndef NET_SPDY_SPDY_PROTOCOL_H_
+#define NET_SPDY_SPDY_PROTOCOL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+namespace net {
+
+// The major versions of SPDY. Major version differences indicate
+// framer-layer incompatibility, as opposed to minor version numbers
+// which indicate application-layer incompatibility. It is guaranteed
+// that the enum value SPDYn maps to the integer n.
+enum SpdyMajorVersion {
+ SPDY2 = 2,
+ SPDY_MIN_VERSION = SPDY2,
+ SPDY3 = 3,
+ SPDY4 = 4,
+ SPDY_MAX_VERSION = SPDY4
+};
+
+// A SPDY stream id is a 31 bit entity.
+typedef uint32 SpdyStreamId;
+
+// Specifies the stream ID used to denote the current session (for
+// flow control).
+const SpdyStreamId kSessionFlowControlStreamId = 0;
+
+// Initial window size for a Spdy stream.
+const int32 kSpdyStreamInitialWindowSize = 64 * 1024; // 64 KBytes
+
+// Initial window size for a Spdy session.
+const int32 kSpdySessionInitialWindowSize = 64 * 1024; // 64 KBytes
+
+// Maximum window size for a Spdy stream or session.
+const int32 kSpdyMaximumWindowSize = 0x7FFFFFFF; // Max signed 32bit int
+
+// SPDY 2 dictionary.
+// This is just a hacked dictionary to use for shrinking HTTP-like headers.
+const char kV2Dictionary[] =
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+ ".1statusversionurl";
+const int kV2DictionarySize = arraysize(kV2Dictionary);
+
+// SPDY 3 dictionary.
+const char kV3Dictionary[] = {
+ 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti
+ 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h
+ 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p
+ 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de
+ 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete....
+ 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace...
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept.
+ 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
+ 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse
+ 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc
+ 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding....
+ 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage.
+ 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
+ 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges
+ 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age.
+ 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow
+ 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth
+ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio
+ 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac
+ 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr
+ 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection
+ 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+ 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base
+ 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+ 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding....
+ 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content-
+ 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language
+ 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont
+ 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng
+ 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation..
+ 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
+ 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5...
+ 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content
+ 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range..
+ 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type..
+ 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date..
+ 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag..
+ 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect
+ 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi
+ 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f
+ 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i
+ 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match.
+ 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo
+ 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s
+ 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince....
+ 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none-
+ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match...
+ 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang
+ 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if-
+ 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi
+ 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since
+ 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last
+ 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie
+ 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation...
+ 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for
+ 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards...
+ 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma.
+ 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy
+ 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate...
+ 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion....
+ 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range...
+ 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer
+ 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr
+ 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after.
+ 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te.
+ 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr
+ 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e
+ 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding.
+ 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra
+ 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us
+ 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary
+ 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via.
+ 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni
+ 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww
+ 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen
+ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate..
+ 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method
+ 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get.
+ 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu
+ 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200
+ 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v
+ 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion..
+ 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1
+ 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur
+ 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub
+ 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s
+ 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki
+ 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee
+ 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive.
+ 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi
+ 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012
+ 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205
+ 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030
+ 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043
+ 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307
+ 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540
+ 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084
+ 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411
+ 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341
+ 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164
+ 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504
+ 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N
+ 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho
+ 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative
+ 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa
+ 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204.
+ 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte
+ 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo
+ 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm
+ 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4
+ 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40
+ 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth
+ 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40
+ 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid
+ 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N
+ 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found
+ 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte
+ 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser
+ 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro
+ 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not
+ 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme
+ 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503.
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service.
+ 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila
+ 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F
+ 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A
+ 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J
+ 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A
+ 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept.
+ 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov.
+ 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0
+ 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon
+ 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W
+ 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu.
+ 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa
+ 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun..
+ 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk
+ 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text.
+ 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima
+ 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg
+ 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g
+ 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
+ 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
+ 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml
+ 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl
+ 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text
+ 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr
+ 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ
+ 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat
+ 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age
+ 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de
+ 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd
+ 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i
+ 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859-
+ 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-..
+ 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0.
+};
+const int kV3DictionarySize = arraysize(kV3Dictionary);
+
+// The HTTP/2 connection header prefix, which must be the first bytes
+// sent by the client upon starting an HTTP/2 connection, and which
+// must be followed by a SETTINGS frame.
+//
+// Equivalent to the string "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+// (without the null terminator).
+const char kHttp2ConnectionHeaderPrefix[] = {
+ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, // PRI * HT
+ 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a, // TP/2.0..
+ 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a // ..SM....
+};
+const int kHttp2ConnectionHeaderPrefixSize =
+ arraysize(kHttp2ConnectionHeaderPrefix);
+
+// Types of SPDY frames.
+enum SpdyFrameType {
+ DATA = 0,
+ SYN_STREAM = 1,
+ FIRST_CONTROL_TYPE = SYN_STREAM,
+ SYN_REPLY,
+ RST_STREAM,
+ SETTINGS,
+ NOOP, // Because it is valid in SPDY/2, kept for identifiability/enum order.
+ PING,
+ GOAWAY,
+ HEADERS,
+ WINDOW_UPDATE,
+ CREDENTIAL,
+ BLOCKED,
+ PUSH_PROMISE,
+ LAST_CONTROL_TYPE = PUSH_PROMISE
+};
+
+// Flags on data packets.
+enum SpdyDataFlags {
+ DATA_FLAG_NONE = 0,
+ DATA_FLAG_FIN = 1,
+};
+
+// Flags on control packets
+enum SpdyControlFlags {
+ CONTROL_FLAG_NONE = 0,
+ CONTROL_FLAG_FIN = 1,
+ CONTROL_FLAG_UNIDIRECTIONAL = 2
+};
+
+// Flags on the SETTINGS control frame.
+enum SpdySettingsControlFlags {
+ SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1
+};
+
+// Flags for settings within a SETTINGS frame.
+enum SpdySettingsFlags {
+ SETTINGS_FLAG_NONE = 0x0,
+ SETTINGS_FLAG_PLEASE_PERSIST = 0x1,
+ SETTINGS_FLAG_PERSISTED = 0x2
+};
+
+// List of known settings.
+enum SpdySettingsIds {
+ SETTINGS_UPLOAD_BANDWIDTH = 0x1,
+ SETTINGS_DOWNLOAD_BANDWIDTH = 0x2,
+ // Network round trip time in milliseconds.
+ SETTINGS_ROUND_TRIP_TIME = 0x3,
+ SETTINGS_MAX_CONCURRENT_STREAMS = 0x4,
+ // TCP congestion window in packets.
+ SETTINGS_CURRENT_CWND = 0x5,
+ // Downstream byte retransmission rate in percentage.
+ SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6,
+ // Initial window size in bytes
+ SETTINGS_INITIAL_WINDOW_SIZE = 0x7
+};
+
+// Status codes for RST_STREAM frames.
+enum SpdyRstStreamStatus {
+ RST_STREAM_INVALID = 0,
+ RST_STREAM_PROTOCOL_ERROR = 1,
+ RST_STREAM_INVALID_STREAM = 2,
+ RST_STREAM_REFUSED_STREAM = 3,
+ RST_STREAM_UNSUPPORTED_VERSION = 4,
+ RST_STREAM_CANCEL = 5,
+ RST_STREAM_INTERNAL_ERROR = 6,
+ RST_STREAM_FLOW_CONTROL_ERROR = 7,
+ RST_STREAM_STREAM_IN_USE = 8,
+ RST_STREAM_STREAM_ALREADY_CLOSED = 9,
+ RST_STREAM_INVALID_CREDENTIALS = 10,
+ RST_STREAM_FRAME_TOO_LARGE = 11,
+ RST_STREAM_NUM_STATUS_CODES = 12
+};
+
+// Status codes for GOAWAY frames.
+enum SpdyGoAwayStatus {
+ GOAWAY_INVALID = -1,
+ GOAWAY_OK = 0,
+ GOAWAY_PROTOCOL_ERROR = 1,
+ GOAWAY_INTERNAL_ERROR = 2,
+ GOAWAY_NUM_STATUS_CODES = 3
+};
+
+// A SPDY priority is a number between 0 and 7 (inclusive).
+// SPDY priority range is version-dependant. For SPDY 2 and below, priority is a
+// number between 0 and 3.
+typedef uint8 SpdyPriority;
+
+typedef uint8 SpdyCredentialSlot;
+
+typedef std::map<std::string, std::string> SpdyNameValueBlock;
+
+typedef uint32 SpdyPingId;
+
+class SpdyFrame;
+typedef SpdyFrame SpdySerializedFrame;
+
+class SpdyFrameVisitor;
+
+// Intermediate representation for SPDY frames.
+// TODO(hkhalil): Rename this class to SpdyFrame when the existing SpdyFrame is
+// gone.
+class SpdyFrameIR {
+ public:
+ virtual ~SpdyFrameIR() {}
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const = 0;
+
+ protected:
+ SpdyFrameIR() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameIR);
+};
+
+// Abstract class intended to be inherited by IRs that have a stream associated
+// to them.
+class SpdyFrameWithStreamIdIR : public SpdyFrameIR {
+ public:
+ virtual ~SpdyFrameWithStreamIdIR() {}
+ SpdyStreamId stream_id() const { return stream_id_; }
+ void set_stream_id(SpdyStreamId stream_id) {
+ DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+ stream_id_ = stream_id;
+ }
+
+ protected:
+ explicit SpdyFrameWithStreamIdIR(SpdyStreamId stream_id) {
+ set_stream_id(stream_id);
+ }
+
+ private:
+ SpdyStreamId stream_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithStreamIdIR);
+};
+
+// Abstract class intended to be inherited by IRs that have the option of a FIN
+// flag. Implies SpdyFrameWithStreamIdIR.
+class SpdyFrameWithFinIR : public SpdyFrameWithStreamIdIR {
+ public:
+ virtual ~SpdyFrameWithFinIR() {}
+ bool fin() const { return fin_; }
+ void set_fin(bool fin) { fin_ = fin; }
+
+ protected:
+ explicit SpdyFrameWithFinIR(SpdyStreamId stream_id)
+ : SpdyFrameWithStreamIdIR(stream_id),
+ fin_(false) {}
+
+ private:
+ bool fin_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithFinIR);
+};
+
+// Abstract class intended to be inherited by IRs that contain a name-value
+// block. Implies SpdyFrameWithFinIR.
+class NET_EXPORT_PRIVATE SpdyFrameWithNameValueBlockIR
+ : public NON_EXPORTED_BASE(SpdyFrameWithFinIR) {
+ public:
+ const SpdyNameValueBlock& name_value_block() const {
+ return name_value_block_;
+ }
+ SpdyNameValueBlock* GetMutableNameValueBlock() { return &name_value_block_; }
+ void SetHeader(const base::StringPiece& name,
+ const base::StringPiece& value) {
+ name_value_block_[name.as_string()] = value.as_string();
+ }
+
+ protected:
+ explicit SpdyFrameWithNameValueBlockIR(SpdyStreamId stream_id);
+ virtual ~SpdyFrameWithNameValueBlockIR();
+
+ private:
+ SpdyNameValueBlock name_value_block_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameWithNameValueBlockIR);
+};
+
+class NET_EXPORT_PRIVATE SpdyDataIR
+ : public NON_EXPORTED_BASE(SpdyFrameWithFinIR) {
+ public:
+ // Performs deep copy on data.
+ SpdyDataIR(SpdyStreamId stream_id, const base::StringPiece& data);
+
+ // Use in conjunction with SetDataShallow() for shallow-copy on data.
+ explicit SpdyDataIR(SpdyStreamId stream_id);
+
+ virtual ~SpdyDataIR();
+
+ base::StringPiece data() const { return data_; }
+
+ // Deep-copy of data (keep private copy).
+ void SetDataDeep(const base::StringPiece& data) {
+ data_store_.reset(new std::string(data.data(), data.length()));
+ data_ = *(data_store_.get());
+ }
+
+ // Shallow-copy of data (do not keep private copy).
+ void SetDataShallow(const base::StringPiece& data) {
+ data_store_.reset();
+ data_ = data;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ // Used to store data that this SpdyDataIR should own.
+ scoped_ptr<std::string> data_store_;
+ base::StringPiece data_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyDataIR);
+};
+
+class NET_EXPORT_PRIVATE SpdySynStreamIR
+ : public SpdyFrameWithNameValueBlockIR {
+ public:
+ explicit SpdySynStreamIR(SpdyStreamId stream_id)
+ : SpdyFrameWithNameValueBlockIR(stream_id),
+ associated_to_stream_id_(0),
+ priority_(0),
+ slot_(0),
+ unidirectional_(false) {}
+ SpdyStreamId associated_to_stream_id() const {
+ return associated_to_stream_id_;
+ }
+ void set_associated_to_stream_id(SpdyStreamId stream_id) {
+ associated_to_stream_id_ = stream_id;
+ }
+ SpdyPriority priority() const { return priority_; }
+ void set_priority(SpdyPriority priority) { priority_ = priority; }
+ SpdyCredentialSlot slot() const { return slot_; }
+ void set_slot(SpdyCredentialSlot slot) { slot_ = slot; }
+ bool unidirectional() const { return unidirectional_; }
+ void set_unidirectional(bool unidirectional) {
+ unidirectional_ = unidirectional;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ SpdyStreamId associated_to_stream_id_;
+ SpdyPriority priority_;
+ SpdyCredentialSlot slot_;
+ bool unidirectional_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySynStreamIR);
+};
+
+class SpdySynReplyIR : public SpdyFrameWithNameValueBlockIR {
+ public:
+ explicit SpdySynReplyIR(SpdyStreamId stream_id)
+ : SpdyFrameWithNameValueBlockIR(stream_id) {}
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdySynReplyIR);
+};
+
+class SpdyRstStreamIR : public SpdyFrameWithStreamIdIR {
+ public:
+ SpdyRstStreamIR(SpdyStreamId stream_id, SpdyRstStreamStatus status)
+ : SpdyFrameWithStreamIdIR(stream_id) {
+ set_status(status);
+ }
+ SpdyRstStreamStatus status() const {
+ return status_;
+ }
+ void set_status(SpdyRstStreamStatus status) {
+ DCHECK_NE(status, RST_STREAM_INVALID);
+ DCHECK_LT(status, RST_STREAM_NUM_STATUS_CODES);
+ status_ = status;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ SpdyRstStreamStatus status_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamIR);
+};
+
+class SpdySettingsIR : public SpdyFrameIR {
+ public:
+ // Associates flags with a value.
+ struct Value {
+ Value() : persist_value(false),
+ persisted(false),
+ value(0) {}
+ bool persist_value;
+ bool persisted;
+ int32 value;
+ };
+ typedef std::map<SpdySettingsIds, Value> ValueMap;
+
+ SpdySettingsIR();
+
+ virtual ~SpdySettingsIR();
+
+ // Overwrites as appropriate.
+ const ValueMap& values() const { return values_; }
+ void AddSetting(SpdySettingsIds id,
+ bool persist_value,
+ bool persisted,
+ int32 value) {
+ // TODO(hkhalil): DCHECK_LE(SETTINGS_UPLOAD_BANDWIDTH, id);
+ // TODO(hkhalil): DCHECK_GE(SETTINGS_INITIAL_WINDOW_SIZE, id);
+ values_[id].persist_value = persist_value;
+ values_[id].persisted = persisted;
+ values_[id].value = value;
+ }
+ bool clear_settings() const { return clear_settings_; }
+ void set_clear_settings(bool clear_settings) {
+ clear_settings_ = clear_settings;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ ValueMap values_;
+ bool clear_settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySettingsIR);
+};
+
+class SpdyPingIR : public SpdyFrameIR {
+ public:
+ explicit SpdyPingIR(SpdyPingId id) : id_(id) {}
+ SpdyPingId id() const { return id_; }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ SpdyPingId id_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyPingIR);
+};
+
+class SpdyGoAwayIR : public SpdyFrameIR {
+ public:
+ SpdyGoAwayIR(SpdyStreamId last_good_stream_id, SpdyGoAwayStatus status) {
+ set_last_good_stream_id(last_good_stream_id);
+ set_status(status);
+ }
+ SpdyStreamId last_good_stream_id() const { return last_good_stream_id_; }
+ void set_last_good_stream_id(SpdyStreamId last_good_stream_id) {
+ DCHECK_LE(0u, last_good_stream_id);
+ DCHECK_EQ(0u, last_good_stream_id & ~kStreamIdMask);
+ last_good_stream_id_ = last_good_stream_id;
+ }
+ SpdyGoAwayStatus status() const { return status_; }
+ void set_status(SpdyGoAwayStatus status) {
+ // TODO(hkhalil): Check valid ranges of status?
+ status_ = status;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ SpdyStreamId last_good_stream_id_;
+ SpdyGoAwayStatus status_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayIR);
+};
+
+class SpdyHeadersIR : public SpdyFrameWithNameValueBlockIR {
+ public:
+ explicit SpdyHeadersIR(SpdyStreamId stream_id)
+ : SpdyFrameWithNameValueBlockIR(stream_id) {}
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyHeadersIR);
+};
+
+class SpdyWindowUpdateIR : public SpdyFrameWithStreamIdIR {
+ public:
+ SpdyWindowUpdateIR(SpdyStreamId stream_id, int32 delta)
+ : SpdyFrameWithStreamIdIR(stream_id) {
+ set_delta(delta);
+ }
+ int32 delta() const { return delta_; }
+ void set_delta(int32 delta) {
+ DCHECK_LT(0, delta);
+ DCHECK_LE(delta, kSpdyMaximumWindowSize);
+ delta_ = delta;
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ int32 delta_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateIR);
+};
+
+class SpdyCredentialIR : public SpdyFrameIR {
+ public:
+ typedef std::vector<std::string> CertificateList;
+
+ explicit SpdyCredentialIR(int16 slot);
+ virtual ~SpdyCredentialIR();
+
+ int16 slot() const { return slot_; }
+ void set_slot(int16 slot) {
+ // TODO(hkhalil): Verify valid slot range?
+ slot_ = slot;
+ }
+ base::StringPiece proof() const { return proof_; }
+ void set_proof(const base::StringPiece& proof) {
+ proof.CopyToString(&proof_);
+ }
+ const CertificateList* certificates() const { return &certificates_; }
+ void AddCertificate(const base::StringPiece& certificate) {
+ certificates_.push_back(certificate.as_string());
+ }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ int16 slot_;
+ std::string proof_;
+ CertificateList certificates_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyCredentialIR);
+};
+
+class NET_EXPORT_PRIVATE SpdyBlockedIR
+ : public NON_EXPORTED_BASE(SpdyFrameWithStreamIdIR) {
+ public:
+ explicit SpdyBlockedIR(SpdyStreamId stream_id)
+ : SpdyFrameWithStreamIdIR(stream_id) {}
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyBlockedIR);
+};
+
+class SpdyPushPromiseIR : public SpdyFrameWithNameValueBlockIR {
+ public:
+ SpdyPushPromiseIR(SpdyStreamId stream_id, SpdyStreamId promised_stream_id)
+ : SpdyFrameWithNameValueBlockIR(stream_id),
+ promised_stream_id_(promised_stream_id) {}
+ SpdyStreamId promised_stream_id() const { return promised_stream_id_; }
+ void set_promised_stream_id(SpdyStreamId id) { promised_stream_id_ = id; }
+
+ virtual void Visit(SpdyFrameVisitor* visitor) const OVERRIDE;
+
+ private:
+ SpdyStreamId promised_stream_id_;
+ DISALLOW_COPY_AND_ASSIGN(SpdyPushPromiseIR);
+};
+
+
+// -------------------------------------------------------------------------
+// Wrapper classes for various SPDY frames.
+
+// All Spdy Frame types derive from this SpdyFrame class.
+class SpdyFrame {
+ public:
+ // Create a SpdyFrame using a pre-created buffer.
+ // If |owns_buffer| is true, this class takes ownership of the buffer
+ // and will delete it on cleanup. The buffer must have been created using
+ // new char[].
+ // If |owns_buffer| is false, the caller retains ownership of the buffer and
+ // is responsible for making sure the buffer outlives this frame. In other
+ // words, this class does NOT create a copy of the buffer.
+ SpdyFrame(char* data, size_t size, bool owns_buffer)
+ : frame_(data),
+ size_(size),
+ owns_buffer_(owns_buffer) {
+ DCHECK(frame_);
+ }
+
+ ~SpdyFrame() {
+ if (owns_buffer_) {
+ delete [] frame_;
+ }
+ frame_ = NULL;
+ }
+
+ // Provides access to the frame bytes, which is a buffer containing
+ // the frame packed as expected for sending over the wire.
+ char* data() const { return frame_; }
+
+ // Returns the actual size of the underlying buffer.
+ size_t size() const { return size_; }
+
+ protected:
+ char* frame_;
+
+ private:
+ size_t size_;
+ bool owns_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrame);
+};
+
+// This interface is for classes that want to process SpdyFrameIRs without
+// having to know what type they are. An instance of this interface can be
+// passed to a SpdyFrameIR's Visit method, and the appropriate type-specific
+// method of this class will be called.
+class SpdyFrameVisitor {
+ public:
+ virtual void VisitSynStream(const SpdySynStreamIR& syn_stream) = 0;
+ virtual void VisitSynReply(const SpdySynReplyIR& syn_reply) = 0;
+ virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) = 0;
+ virtual void VisitSettings(const SpdySettingsIR& settings) = 0;
+ virtual void VisitPing(const SpdyPingIR& ping) = 0;
+ virtual void VisitGoAway(const SpdyGoAwayIR& goaway) = 0;
+ virtual void VisitHeaders(const SpdyHeadersIR& headers) = 0;
+ virtual void VisitWindowUpdate(const SpdyWindowUpdateIR& window_update) = 0;
+ virtual void VisitCredential(const SpdyCredentialIR& credential) = 0;
+ virtual void VisitBlocked(const SpdyBlockedIR& blocked) = 0;
+ virtual void VisitPushPromise(const SpdyPushPromiseIR& push_promise) = 0;
+ virtual void VisitData(const SpdyDataIR& data) = 0;
+
+ protected:
+ SpdyFrameVisitor() {}
+ virtual ~SpdyFrameVisitor() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SpdyFrameVisitor);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_PROTOCOL_H_
diff --git a/chromium/net/spdy/spdy_protocol_test.cc b/chromium/net/spdy/spdy_protocol_test.cc
new file mode 100644
index 00000000000..006dfe15ca5
--- /dev/null
+++ b/chromium/net/spdy/spdy_protocol_test.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_protocol.h"
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+enum SpdyProtocolTestTypes {
+ SPDY2 = 2,
+ SPDY3 = 3,
+};
+
+} // namespace
+
+namespace net {
+
+class SpdyProtocolTest
+ : public ::testing::TestWithParam<SpdyProtocolTestTypes> {
+ protected:
+ virtual void SetUp() {
+ spdy_version_ = GetParam();
+ }
+
+ bool IsSpdy2() { return spdy_version_ == SPDY2; }
+
+ // Version of SPDY protocol to be used.
+ int spdy_version_;
+};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyProtocolTests,
+ SpdyProtocolTest,
+ ::testing::Values(SPDY2, SPDY3));
+
+// Test our protocol constants
+TEST_P(SpdyProtocolTest, ProtocolConstants) {
+ EXPECT_EQ(1, SYN_STREAM);
+ EXPECT_EQ(2, SYN_REPLY);
+ EXPECT_EQ(3, RST_STREAM);
+ EXPECT_EQ(4, SETTINGS);
+ EXPECT_EQ(5, NOOP);
+ EXPECT_EQ(6, PING);
+ EXPECT_EQ(7, GOAWAY);
+ EXPECT_EQ(8, HEADERS);
+ EXPECT_EQ(9, WINDOW_UPDATE);
+ EXPECT_EQ(10, CREDENTIAL);
+ EXPECT_EQ(11, BLOCKED);
+ EXPECT_EQ(12, PUSH_PROMISE);
+ EXPECT_EQ(12, LAST_CONTROL_TYPE);
+ EXPECT_EQ(std::numeric_limits<int32>::max(), kSpdyMaximumWindowSize);
+}
+
+class SpdyProtocolDeathTest : public SpdyProtocolTest {};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyProtocolDeathTests,
+ SpdyProtocolDeathTest,
+ ::testing::Values(SPDY2, SPDY3));
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST_P(SpdyProtocolDeathTest, TestSpdySettingsAndIdOutOfBounds) {
+ scoped_ptr<SettingsFlagsAndId> flags_and_id;
+
+ EXPECT_DEBUG_DEATH(
+ {
+ flags_and_id.reset(new SettingsFlagsAndId(1, ~0));
+ },
+ "SPDY setting ID too large.");
+ // Make sure that we get expected values in opt mode.
+ if (flags_and_id.get() != NULL) {
+ EXPECT_EQ(1, flags_and_id->flags());
+ EXPECT_EQ(static_cast<SpdyPingId>(0xffffff), flags_and_id->id());
+ }
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_proxy_client_socket.cc b/chromium/net/spdy/spdy_proxy_client_socket.cc
new file mode 100644
index 00000000000..fa3300f3d49
--- /dev/null
+++ b/chromium/net/spdy/spdy_proxy_client_socket.cc
@@ -0,0 +1,520 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_proxy_client_socket.h"
+
+#include <algorithm> // min
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "net/base/auth.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/proxy_connect_redirect_http_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "url/gurl.h"
+
+namespace net {
+
+SpdyProxyClientSocket::SpdyProxyClientSocket(
+ const base::WeakPtr<SpdyStream>& spdy_stream,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const GURL& url,
+ const HostPortPair& proxy_server,
+ const BoundNetLog& source_net_log,
+ HttpAuthCache* auth_cache,
+ HttpAuthHandlerFactory* auth_handler_factory)
+ : next_state_(STATE_DISCONNECTED),
+ spdy_stream_(spdy_stream),
+ endpoint_(endpoint),
+ auth_(
+ new HttpAuthController(HttpAuth::AUTH_PROXY,
+ GURL("https://" + proxy_server.ToString()),
+ auth_cache,
+ auth_handler_factory)),
+ user_buffer_len_(0),
+ write_buffer_len_(0),
+ was_ever_used_(false),
+ redirect_has_load_timing_info_(false),
+ weak_factory_(this),
+ net_log_(BoundNetLog::Make(spdy_stream->net_log().net_log(),
+ NetLog::SOURCE_PROXY_CLIENT_SOCKET)) {
+ request_.method = "CONNECT";
+ request_.url = url;
+ if (!user_agent.empty())
+ request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+ user_agent);
+
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source_net_log.source().ToEventParametersCallback());
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_PROXY_CLIENT_SESSION,
+ spdy_stream->net_log().source().ToEventParametersCallback());
+
+ spdy_stream_->SetDelegate(this);
+ was_ever_used_ = spdy_stream_->WasEverUsed();
+}
+
+SpdyProxyClientSocket::~SpdyProxyClientSocket() {
+ Disconnect();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const {
+ return response_.headers.get() ? &response_ : NULL;
+}
+
+const scoped_refptr<HttpAuthController>&
+SpdyProxyClientSocket::GetAuthController() const {
+ return auth_;
+}
+
+int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) {
+ // A SPDY Stream can only handle a single request, so the underlying
+ // stream may not be reused and a new SpdyProxyClientSocket must be
+ // created (possibly on top of the same SPDY Session).
+ next_state_ = STATE_DISCONNECTED;
+ return OK;
+}
+
+bool SpdyProxyClientSocket::IsUsingSpdy() const {
+ return true;
+}
+
+NextProto SpdyProxyClientSocket::GetProtocolNegotiated() const {
+ // Save the negotiated protocol
+ SSLInfo ssl_info;
+ bool was_npn_negotiated;
+ NextProto protocol_negotiated;
+ spdy_stream_->GetSSLInfo(&ssl_info, &was_npn_negotiated,
+ &protocol_negotiated);
+ return protocol_negotiated;
+}
+
+HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() {
+ return new ProxyConnectRedirectHttpStream(
+ redirect_has_load_timing_info_ ? &redirect_load_timing_info_ : NULL);
+}
+
+// Sends a SYN_STREAM frame to the proxy with a CONNECT request
+// for the specified endpoint. Waits for the server to send back
+// a SYN_REPLY frame. OK will be returned if the status is 200.
+// ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status.
+// In any of these cases, Read() may be called to retrieve the HTTP
+// response body. Any other return values should be considered fatal.
+// TODO(rch): handle 407 proxy auth requested correctly, perhaps
+// by creating a new stream for the subsequent request.
+// TODO(rch): create a more appropriate error code to disambiguate
+// the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure.
+int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) {
+ DCHECK(read_callback_.is_null());
+ if (next_state_ == STATE_OPEN)
+ return OK;
+
+ DCHECK_EQ(STATE_DISCONNECTED, next_state_);
+ next_state_ = STATE_GENERATE_AUTH_TOKEN;
+
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING)
+ read_callback_ = callback;
+ return rv;
+}
+
+void SpdyProxyClientSocket::Disconnect() {
+ read_buffer_queue_.Clear();
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ read_callback_.Reset();
+
+ write_buffer_len_ = 0;
+ write_callback_.Reset();
+
+ next_state_ = STATE_DISCONNECTED;
+
+ if (spdy_stream_.get()) {
+ // This will cause OnClose to be invoked, which takes care of
+ // cleaning up all the internal state.
+ spdy_stream_->Cancel();
+ DCHECK(!spdy_stream_.get());
+ }
+}
+
+bool SpdyProxyClientSocket::IsConnected() const {
+ return next_state_ == STATE_OPEN;
+}
+
+bool SpdyProxyClientSocket::IsConnectedAndIdle() const {
+ return IsConnected() && read_buffer_queue_.IsEmpty() &&
+ spdy_stream_->IsIdle();
+}
+
+const BoundNetLog& SpdyProxyClientSocket::NetLog() const {
+ return net_log_;
+}
+
+void SpdyProxyClientSocket::SetSubresourceSpeculation() {
+ // TODO(rch): what should this implementation be?
+}
+
+void SpdyProxyClientSocket::SetOmniboxSpeculation() {
+ // TODO(rch): what should this implementation be?
+}
+
+bool SpdyProxyClientSocket::WasEverUsed() const {
+ return was_ever_used_ || (spdy_stream_.get() && spdy_stream_->WasEverUsed());
+}
+
+bool SpdyProxyClientSocket::UsingTCPFastOpen() const {
+ return false;
+}
+
+bool SpdyProxyClientSocket::WasNpnNegotiated() const {
+ return false;
+}
+
+NextProto SpdyProxyClientSocket::GetNegotiatedProtocol() const {
+ return kProtoUnknown;
+}
+
+bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+ bool was_npn_negotiated;
+ NextProto protocol_negotiated;
+ return spdy_stream_->GetSSLInfo(ssl_info, &was_npn_negotiated,
+ &protocol_negotiated);
+}
+
+int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(read_callback_.is_null());
+ DCHECK(!user_buffer_.get());
+
+ if (next_state_ == STATE_DISCONNECTED)
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ if (next_state_ == STATE_CLOSED && read_buffer_queue_.IsEmpty()) {
+ return 0;
+ }
+
+ DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED);
+ DCHECK(buf);
+ size_t result = PopulateUserReadBuffer(buf->data(), buf_len);
+ if (result == 0) {
+ user_buffer_ = buf;
+ user_buffer_len_ = static_cast<size_t>(buf_len);
+ DCHECK(!callback.is_null());
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+ user_buffer_ = NULL;
+ return result;
+}
+
+size_t SpdyProxyClientSocket::PopulateUserReadBuffer(char* data, size_t len) {
+ return read_buffer_queue_.Dequeue(data, len);
+}
+
+int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) {
+ DCHECK(write_callback_.is_null());
+ if (next_state_ != STATE_OPEN)
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ DCHECK(spdy_stream_.get());
+ spdy_stream_->SendData(buf, buf_len, MORE_DATA_TO_SEND);
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_SENT,
+ buf_len, buf->data());
+ write_callback_ = callback;
+ write_buffer_len_ = buf_len;
+ return ERR_IO_PENDING;
+}
+
+bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) {
+ // Since this StreamSocket sits on top of a shared SpdySession, it
+ // is not safe for callers to set change this underlying socket.
+ return false;
+}
+
+bool SpdyProxyClientSocket::SetSendBufferSize(int32 size) {
+ // Since this StreamSocket sits on top of a shared SpdySession, it
+ // is not safe for callers to set change this underlying socket.
+ return false;
+}
+
+int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ return spdy_stream_->GetPeerAddress(address);
+}
+
+int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ if (!IsConnected())
+ return ERR_SOCKET_NOT_CONNECTED;
+ return spdy_stream_->GetLocalAddress(address);
+}
+
+void SpdyProxyClientSocket::LogBlockedTunnelResponse() const {
+ ProxyClientSocket::LogBlockedTunnelResponse(
+ response_.headers->response_code(),
+ request_.url,
+ /* is_https_proxy = */ true);
+}
+
+void SpdyProxyClientSocket::OnIOComplete(int result) {
+ DCHECK_NE(STATE_DISCONNECTED, next_state_);
+ int rv = DoLoop(result);
+ if (rv != ERR_IO_PENDING) {
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ c.Run(rv);
+ }
+}
+
+int SpdyProxyClientSocket::DoLoop(int last_io_result) {
+ DCHECK_NE(next_state_, STATE_DISCONNECTED);
+ int rv = last_io_result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_DISCONNECTED;
+ switch (state) {
+ case STATE_GENERATE_AUTH_TOKEN:
+ DCHECK_EQ(OK, rv);
+ rv = DoGenerateAuthToken();
+ break;
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+ rv = DoGenerateAuthTokenComplete(rv);
+ break;
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
+ rv = DoSendRequestComplete(rv);
+ if (rv >= 0 || rv == ERR_IO_PENDING) {
+ // Emit extra event so can use the same events as
+ // HttpProxyClientSocket.
+ net_log_.BeginEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
+ }
+ break;
+ case STATE_READ_REPLY_COMPLETE:
+ rv = DoReadReplyComplete(rv);
+ net_log_.EndEventWithNetErrorCode(
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_UNEXPECTED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED &&
+ next_state_ != STATE_OPEN);
+ return rv;
+}
+
+int SpdyProxyClientSocket::DoGenerateAuthToken() {
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+ return auth_->MaybeGenerateAuthToken(
+ &request_,
+ base::Bind(&SpdyProxyClientSocket::OnIOComplete,
+ weak_factory_.GetWeakPtr()),
+ net_log_);
+}
+
+int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ if (result == OK)
+ next_state_ = STATE_SEND_REQUEST;
+ return result;
+}
+
+int SpdyProxyClientSocket::DoSendRequest() {
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+ // Add Proxy-Authentication header if necessary.
+ HttpRequestHeaders authorization_headers;
+ if (auth_->HaveAuth()) {
+ auth_->AddAuthorizationHeader(&authorization_headers);
+ }
+
+ std::string request_line;
+ HttpRequestHeaders request_headers;
+ BuildTunnelRequest(request_, authorization_headers, endpoint_, &request_line,
+ &request_headers);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ base::Bind(&HttpRequestHeaders::NetLogCallback,
+ base::Unretained(&request_headers),
+ &request_line));
+
+ request_.extra_headers.MergeFrom(request_headers);
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ CreateSpdyHeadersFromHttpRequest(request_, request_headers, headers.get(),
+ spdy_stream_->GetProtocolVersion(), true);
+ // Reset the URL to be the endpoint of the connection
+ if (spdy_stream_->GetProtocolVersion() > 2) {
+ (*headers)[":path"] = endpoint_.ToString();
+ headers->erase(":scheme");
+ } else {
+ (*headers)["url"] = endpoint_.ToString();
+ headers->erase("scheme");
+ }
+
+ return spdy_stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND);
+}
+
+int SpdyProxyClientSocket::DoSendRequestComplete(int result) {
+ if (result < 0)
+ return result;
+
+ // Wait for SYN_REPLY frame from the server
+ next_state_ = STATE_READ_REPLY_COMPLETE;
+ return ERR_IO_PENDING;
+}
+
+int SpdyProxyClientSocket::DoReadReplyComplete(int result) {
+ // We enter this method directly from DoSendRequestComplete, since
+ // we are notified by a callback when the SYN_REPLY frame arrives
+
+ if (result < 0)
+ return result;
+
+ // Require the "HTTP/1.x" status line for SSL CONNECT.
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0))
+ return ERR_TUNNEL_CONNECTION_FAILED;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));
+
+ switch (response_.headers->response_code()) {
+ case 200: // OK
+ next_state_ = STATE_OPEN;
+ return OK;
+
+ case 302: // Found / Moved Temporarily
+ // Try to return a sanitized response so we can follow auth redirects.
+ // If we can't, fail the tunnel connection.
+ if (SanitizeProxyRedirect(&response_, request_.url)) {
+ redirect_has_load_timing_info_ =
+ spdy_stream_->GetLoadTimingInfo(&redirect_load_timing_info_);
+ spdy_stream_->DetachDelegate();
+ next_state_ = STATE_DISCONNECTED;
+ return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
+ } else {
+ LogBlockedTunnelResponse();
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+
+ case 407: // Proxy Authentication Required
+ next_state_ = STATE_OPEN;
+ return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_);
+
+ default:
+ // Ignore response to avoid letting the proxy impersonate the target
+ // server. (See http://crbug.com/137891.)
+ LogBlockedTunnelResponse();
+ return ERR_TUNNEL_CONNECTION_FAILED;
+ }
+}
+
+// SpdyStream::Delegate methods:
+// Called when SYN frame has been sent.
+// Returns true if no more data to be sent after SYN frame.
+void SpdyProxyClientSocket::OnRequestHeadersSent() {
+ DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE);
+
+ OnIOComplete(OK);
+}
+
+SpdyResponseHeadersStatus SpdyProxyClientSocket::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ // If we've already received the reply, existing headers are too late.
+ // TODO(mbelshe): figure out a way to make HEADERS frames useful after the
+ // initial response.
+ if (next_state_ != STATE_READ_REPLY_COMPLETE)
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+
+ // Save the response
+ if (!SpdyHeadersToHttpResponse(
+ response_headers, spdy_stream_->GetProtocolVersion(), &response_))
+ return RESPONSE_HEADERS_ARE_INCOMPLETE;
+
+ OnIOComplete(OK);
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+}
+
+// Called when data is received or on EOF (if |buffer| is NULL).
+void SpdyProxyClientSocket::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ if (buffer) {
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ buffer->GetRemainingSize(),
+ buffer->GetRemainingData());
+ read_buffer_queue_.Enqueue(buffer.Pass());
+ } else {
+ net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED, 0, NULL);
+ }
+
+ if (!read_callback_.is_null()) {
+ int rv = PopulateUserReadBuffer(user_buffer_->data(), user_buffer_len_);
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ user_buffer_ = NULL;
+ user_buffer_len_ = 0;
+ c.Run(rv);
+ }
+}
+
+void SpdyProxyClientSocket::OnDataSent() {
+ DCHECK(!write_callback_.is_null());
+
+ int rv = write_buffer_len_;
+ write_buffer_len_ = 0;
+ ResetAndReturn(&write_callback_).Run(rv);
+}
+
+void SpdyProxyClientSocket::OnClose(int status) {
+ was_ever_used_ = spdy_stream_->WasEverUsed();
+ spdy_stream_.reset();
+
+ bool connecting = next_state_ != STATE_DISCONNECTED &&
+ next_state_ < STATE_OPEN;
+ if (next_state_ == STATE_OPEN)
+ next_state_ = STATE_CLOSED;
+ else
+ next_state_ = STATE_DISCONNECTED;
+
+ base::WeakPtr<SpdyProxyClientSocket> weak_ptr = weak_factory_.GetWeakPtr();
+ CompletionCallback write_callback = write_callback_;
+ write_callback_.Reset();
+ write_buffer_len_ = 0;
+
+ // If we're in the middle of connecting, we need to make sure
+ // we invoke the connect callback.
+ if (connecting) {
+ DCHECK(!read_callback_.is_null());
+ CompletionCallback read_callback = read_callback_;
+ read_callback_.Reset();
+ read_callback.Run(status);
+ } else if (!read_callback_.is_null()) {
+ // If we have a read_callback_, the we need to make sure we call it back.
+ OnDataReceived(scoped_ptr<SpdyBuffer>());
+ }
+ // This may have been deleted by read_callback_, so check first.
+ if (weak_ptr.get() && !write_callback.is_null())
+ write_callback.Run(ERR_CONNECTION_CLOSED);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_proxy_client_socket.h b/chromium/net/spdy/spdy_proxy_client_socket.h
new file mode 100644
index 00000000000..3d7933110b4
--- /dev/null
+++ b/chromium/net/spdy/spdy_proxy_client_socket.h
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
+#define NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
+
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/proxy_client_socket.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_read_queue.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+
+
+class GURL;
+
+namespace net {
+
+class AddressList;
+class HttpStream;
+class IOBuffer;
+class SpdyStream;
+
+class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket,
+ public SpdyStream::Delegate {
+ public:
+ // Create a socket on top of the |spdy_stream| by sending a SYN_STREAM
+ // CONNECT frame for |endpoint|. After the SYN_REPLY is received,
+ // any data read/written to the socket will be transferred in data
+ // frames. This object will set itself as |spdy_stream|'s delegate.
+ SpdyProxyClientSocket(const base::WeakPtr<SpdyStream>& spdy_stream,
+ const std::string& user_agent,
+ const HostPortPair& endpoint,
+ const GURL& url,
+ const HostPortPair& proxy_server,
+ const BoundNetLog& source_net_log,
+ HttpAuthCache* auth_cache,
+ HttpAuthHandlerFactory* auth_handler_factory);
+
+
+ // On destruction Disconnect() is called.
+ virtual ~SpdyProxyClientSocket();
+
+ // ProxyClientSocket methods:
+ virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE;
+ virtual HttpStream* CreateConnectResponseStream() OVERRIDE;
+ virtual const scoped_refptr<HttpAuthController>& GetAuthController() const
+ OVERRIDE;
+ virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE;
+ virtual bool IsUsingSpdy() const OVERRIDE;
+ virtual NextProto GetProtocolNegotiated() const OVERRIDE;
+
+ // StreamSocket implementation.
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+ virtual void Disconnect() OVERRIDE;
+ virtual bool IsConnected() const OVERRIDE;
+ virtual bool IsConnectedAndIdle() const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void SetSubresourceSpeculation() OVERRIDE;
+ virtual void SetOmniboxSpeculation() OVERRIDE;
+ virtual bool WasEverUsed() const OVERRIDE;
+ virtual bool UsingTCPFastOpen() const OVERRIDE;
+ virtual bool WasNpnNegotiated() const OVERRIDE;
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+ // Socket implementation.
+ virtual int Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+
+ // SpdyStream::Delegate implementation.
+ virtual void OnRequestHeadersSent() OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnDataSent() OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ private:
+ enum State {
+ STATE_DISCONNECTED,
+ STATE_GENERATE_AUTH_TOKEN,
+ STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+ STATE_SEND_REQUEST,
+ STATE_SEND_REQUEST_COMPLETE,
+ STATE_READ_REPLY_COMPLETE,
+ STATE_OPEN,
+ STATE_CLOSED
+ };
+
+ void LogBlockedTunnelResponse() const;
+
+ void OnIOComplete(int result);
+
+ int DoLoop(int last_io_result);
+ int DoGenerateAuthToken();
+ int DoGenerateAuthTokenComplete(int result);
+ int DoSendRequest();
+ int DoSendRequestComplete(int result);
+ int DoReadReplyComplete(int result);
+
+ // Populates |user_buffer_| with as much read data as possible
+ // and returns the number of bytes read.
+ size_t PopulateUserReadBuffer(char* out, size_t len);
+
+ State next_state_;
+
+ // Pointer to the SPDY Stream that this sits on top of.
+ base::WeakPtr<SpdyStream> spdy_stream_;
+
+ // Stores the callback to the layer above, called on completing Read() or
+ // Connect().
+ CompletionCallback read_callback_;
+ // Stores the callback to the layer above, called on completing Write().
+ CompletionCallback write_callback_;
+
+ // CONNECT request and response.
+ HttpRequestInfo request_;
+ HttpResponseInfo response_;
+
+ // The hostname and port of the endpoint. This is not necessarily the one
+ // specified by the URL, due to Alternate-Protocol or fixed testing ports.
+ const HostPortPair endpoint_;
+ scoped_refptr<HttpAuthController> auth_;
+
+ // We buffer the response body as it arrives asynchronously from the stream.
+ SpdyReadQueue read_buffer_queue_;
+
+ // User provided buffer for the Read() response.
+ scoped_refptr<IOBuffer> user_buffer_;
+ size_t user_buffer_len_;
+
+ // User specified number of bytes to be written.
+ int write_buffer_len_;
+
+ // True if the transport socket has ever sent data.
+ bool was_ever_used_;
+
+ // Used only for redirects.
+ bool redirect_has_load_timing_info_;
+ LoadTimingInfo redirect_load_timing_info_;
+
+ base::WeakPtrFactory<SpdyProxyClientSocket> weak_factory_;
+
+ const BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
diff --git a/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc b/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc
new file mode 100644
index 00000000000..c128d9cdace
--- /dev/null
+++ b/chromium/net/spdy/spdy_proxy_client_socket_unittest.cc
@@ -0,0 +1,1435 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_proxy_client_socket.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+static const char kRequestUrl[] = "https://www.google.com/";
+static const char kOriginHost[] = "www.google.com";
+static const int kOriginPort = 443;
+static const char kOriginHostPort[] = "www.google.com:443";
+static const char kProxyUrl[] = "https://myproxy:6121/";
+static const char kProxyHost[] = "myproxy";
+static const int kProxyPort = 6121;
+static const char kUserAgent[] = "Mozilla/1.0";
+
+static const int kStreamId = 1;
+
+static const char kMsg1[] = "\0hello!\xff";
+static const int kLen1 = 8;
+static const char kMsg2[] = "\00012345678\0";
+static const int kLen2 = 10;
+static const char kMsg3[] = "bye!";
+static const int kLen3 = 4;
+static const char kMsg33[] = "bye!bye!";
+static const int kLen33 = kLen3 + kLen3;
+static const char kMsg333[] = "bye!bye!bye!";
+static const int kLen333 = kLen3 + kLen3 + kLen3;
+
+static const char kRedirectUrl[] = "https://example.com/";
+
+} // anonymous namespace
+
+namespace net {
+
+class SpdyProxyClientSocketTest
+ : public PlatformTest,
+ public testing::WithParamInterface<NextProto> {
+ public:
+ SpdyProxyClientSocketTest();
+
+ virtual void TearDown();
+
+ protected:
+ void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes,
+ size_t writes_count);
+ SpdyFrame* ConstructConnectRequestFrame();
+ SpdyFrame* ConstructConnectAuthRequestFrame();
+ SpdyFrame* ConstructConnectReplyFrame();
+ SpdyFrame* ConstructConnectAuthReplyFrame();
+ SpdyFrame* ConstructConnectRedirectReplyFrame();
+ SpdyFrame* ConstructConnectErrorReplyFrame();
+ SpdyFrame* ConstructBodyFrame(const char* data, int length);
+ scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size);
+ void AssertConnectSucceeds();
+ void AssertConnectFails(int result);
+ void AssertConnectionEstablished();
+ void AssertSyncReadEquals(const char* data, int len);
+ void AssertAsyncReadEquals(const char* data, int len);
+ void AssertReadStarts(const char* data, int len);
+ void AssertReadReturns(const char* data, int len);
+ void AssertAsyncWriteSucceeds(const char* data, int len);
+ void AssertWriteReturns(const char* data, int len, int rv);
+ void AssertWriteLength(int len);
+ void AssertAsyncWriteWithReadsSucceeds(const char* data, int len,
+ int num_reads);
+
+ void AddAuthToCache() {
+ const base::string16 kFoo(ASCIIToUTF16("foo"));
+ const base::string16 kBar(ASCIIToUTF16("bar"));
+ session_->http_auth_cache()->Add(GURL(kProxyUrl),
+ "MyRealm1",
+ HttpAuth::AUTH_SCHEME_BASIC,
+ "Basic realm=MyRealm1",
+ AuthCredentials(kFoo, kBar),
+ "/");
+ }
+
+ void Run(int steps) {
+ data_->StopAfter(steps);
+ data_->Run();
+ }
+
+ void CloseSpdySession(net::Error error, const std::string& description) {
+ spdy_session_->CloseSessionOnError(error, description);
+ }
+
+ SpdyTestUtil spdy_util_;
+ scoped_ptr<SpdyProxyClientSocket> sock_;
+ TestCompletionCallback read_callback_;
+ TestCompletionCallback write_callback_;
+ scoped_ptr<DeterministicSocketData> data_;
+ CapturingBoundNetLog net_log_;
+
+ private:
+ scoped_refptr<HttpNetworkSession> session_;
+ scoped_refptr<IOBuffer> read_buf_;
+ SpdySessionDependencies session_deps_;
+ MockConnect connect_data_;
+ base::WeakPtr<SpdySession> spdy_session_;
+ BufferedSpdyFramer framer_;
+
+ std::string user_agent_;
+ GURL url_;
+ HostPortPair proxy_host_port_;
+ HostPortPair endpoint_host_port_pair_;
+ ProxyServer proxy_;
+ SpdySessionKey endpoint_spdy_session_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest);
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdyProxyClientSocketTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+SpdyProxyClientSocketTest::SpdyProxyClientSocketTest()
+ : spdy_util_(GetParam()),
+ session_(NULL),
+ read_buf_(NULL),
+ session_deps_(GetParam()),
+ connect_data_(SYNCHRONOUS, OK),
+ framer_(spdy_util_.spdy_version(), false),
+ user_agent_(kUserAgent),
+ url_(kRequestUrl),
+ proxy_host_port_(kProxyHost, kProxyPort),
+ endpoint_host_port_pair_(kOriginHost, kOriginPort),
+ proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_),
+ endpoint_spdy_session_key_(endpoint_host_port_pair_,
+ proxy_,
+ kPrivacyModeDisabled) {
+ session_deps_.net_log = net_log_.bound().net_log();
+}
+
+void SpdyProxyClientSocketTest::TearDown() {
+ sock_.reset(NULL);
+ if (session_.get() != NULL)
+ session_->spdy_session_pool()->CloseAllSessions();
+
+ // Empty the current queue.
+ base::MessageLoop::current()->RunUntilIdle();
+ PlatformTest::TearDown();
+}
+
+void SpdyProxyClientSocketTest::Initialize(MockRead* reads,
+ size_t reads_count,
+ MockWrite* writes,
+ size_t writes_count) {
+ data_.reset(new DeterministicSocketData(reads, reads_count,
+ writes, writes_count));
+ data_->set_connect_data(connect_data_);
+ data_->SetStop(2);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+ data_.get());
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ &session_deps_);
+
+ // Creates the SPDY session and stream.
+ spdy_session_ =
+ CreateInsecureSpdySession(
+ session_, endpoint_spdy_session_key_, BoundNetLog());
+ base::WeakPtr<SpdyStream> spdy_stream(
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url_, LOWEST,
+ net_log_.bound()));
+ ASSERT_TRUE(spdy_stream.get() != NULL);
+
+ // Create the SpdyProxyClientSocket.
+ sock_.reset(
+ new SpdyProxyClientSocket(spdy_stream, user_agent_,
+ endpoint_host_port_pair_, url_,
+ proxy_host_port_, net_log_.bound(),
+ session_->http_auth_cache(),
+ session_->http_auth_handler_factory()));
+}
+
+scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer(
+ const char* data, int size) {
+ scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size));
+ memcpy(buf->data(), data, size);
+ return buf;
+}
+
+void SpdyProxyClientSocketTest::AssertConnectSucceeds() {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(OK, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketTest::AssertConnectFails(int result) {
+ ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+ data_->Run();
+ ASSERT_EQ(result, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketTest::AssertConnectionEstablished() {
+ const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_EQ(200, response->headers->response_code());
+ ASSERT_EQ("Connection Established", response->headers->GetStatusText());
+}
+
+void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data,
+ int len) {
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ ASSERT_EQ(len, sock_->Read(buf.get(), len, CompletionCallback()));
+ ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data,
+ int len) {
+ data_->StopAfter(1);
+ // Issue the read, which will be completed asynchronously
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(buf.get(), len, read_callback_.callback()));
+ EXPECT_TRUE(sock_->IsConnected());
+ data_->Run();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ // Now the read will return
+ EXPECT_EQ(len, read_callback_.WaitForResult());
+ ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+}
+
+void SpdyProxyClientSocketTest::AssertReadStarts(const char* data,
+ int len) {
+ data_->StopAfter(1);
+ // Issue the read, which will be completed asynchronously
+ read_buf_ = new IOBuffer(len);
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(read_buf_.get(), len, read_callback_.callback()));
+ EXPECT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketTest::AssertReadReturns(const char* data,
+ int len) {
+ EXPECT_TRUE(sock_->IsConnected());
+
+ // Now the read will return
+ EXPECT_EQ(len, read_callback_.WaitForResult());
+ ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len));
+}
+
+void SpdyProxyClientSocketTest::AssertAsyncWriteSucceeds(const char* data,
+ int len) {
+ AssertWriteReturns(data, len, ERR_IO_PENDING);
+ data_->RunFor(1);
+ AssertWriteLength(len);
+}
+
+void SpdyProxyClientSocketTest::AssertWriteReturns(const char* data,
+ int len,
+ int rv) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+ EXPECT_EQ(rv,
+ sock_->Write(buf.get(), buf->size(), write_callback_.callback()));
+}
+
+void SpdyProxyClientSocketTest::AssertWriteLength(int len) {
+ EXPECT_EQ(len, write_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketTest::AssertAsyncWriteWithReadsSucceeds(
+ const char* data, int len, int num_reads) {
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(buf.get(), buf->size(), write_callback_.callback()));
+
+ for (int i = 0; i < num_reads; i++) {
+ Run(1);
+ AssertSyncReadEquals(kMsg2, kLen2);
+ }
+
+ write_callback_.WaitForResult();
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame*
+SpdyProxyClientSocketTest::ConstructConnectRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ RST_STREAM_INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ bool spdy2 = spdy_util_.is_spdy2();
+ const char* const kConnectHeaders[] = {
+ spdy2 ? "method" : ":method", "CONNECT",
+ spdy2 ? "url" : ":path", kOriginHostPort,
+ spdy2 ? "host" : ":host", kOriginHost,
+ "user-agent", kUserAgent,
+ spdy2 ? "version" : ":version", "HTTP/1.1",
+ };
+ return spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes
+// Proxy-Authorization headers.
+SpdyFrame*
+SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ kStreamId,
+ 0,
+ net::ConvertRequestPriorityToSpdyPriority(
+ LOWEST, spdy_util_.spdy_version()),
+ 0,
+ CONTROL_FLAG_NONE,
+ false,
+ RST_STREAM_INVALID,
+ NULL,
+ 0,
+ DATA_FLAG_NONE
+ };
+ bool spdy2 = spdy_util_.is_spdy2();
+ const char* const kConnectHeaders[] = {
+ spdy2 ? "method" : ":method", "CONNECT",
+ spdy2 ? "url" : ":path", kOriginHostPort,
+ spdy2 ? "host" : ":host", kOriginHost,
+ "user-agent", kUserAgent,
+ spdy2 ? "version" : ":version", "HTTP/1.1",
+ "proxy-authorization", "Basic Zm9vOmJhcg==",
+ };
+ return spdy_util_.ConstructSpdyFrame(
+ kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectReplyFrame() {
+ bool spdy2 = spdy_util_.is_spdy2();
+ const char* const kStandardReplyHeaders[] = {
+ spdy2 ? "status" : ":status", "200 Connection Established",
+ spdy2 ? "version" : ":version", "HTTP/1.1"
+ };
+ return spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders),
+ 0);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame*
+SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() {
+ bool spdy2 = spdy_util_.is_spdy2();
+
+ const char* const kStandardReplyHeaders[] = {
+ spdy2 ? "status" : ":status", "407 Proxy Authentication Required",
+ spdy2 ? "version" : ":version", "HTTP/1.1",
+ "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+ };
+
+ return spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders),
+ 0);
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect.
+SpdyFrame*
+SpdyProxyClientSocketTest::ConstructConnectRedirectReplyFrame() {
+ bool spdy2 = spdy_util_.is_spdy2();
+
+ const char* const kStandardReplyHeaders[] = {
+ spdy2 ? "status" : ":status", "302 Found",
+ spdy2 ? "version" : ":version", "HTTP/1.1",
+ "location", kRedirectUrl,
+ "set-cookie", "foo=bar"
+ };
+
+ return spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders),
+ 0);
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error.
+SpdyFrame*
+SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() {
+ bool spdy2 = spdy_util_.is_spdy2();
+
+ const char* const kStandardReplyHeaders[] = {
+ spdy2 ? "status" : ":status", "500 Internal Server Error",
+ spdy2 ? "version" : ":version", "HTTP/1.1",
+ };
+
+ return spdy_util_.ConstructSpdyControlFrame(NULL,
+ 0,
+ false,
+ kStreamId,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardReplyHeaders,
+ arraysize(kStandardReplyHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyProxyClientSocketTest::ConstructBodyFrame(
+ const char* data,
+ int length) {
+ return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE);
+}
+
+// ----------- Connect
+
+TEST_P(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ ASSERT_FALSE(sock_->IsConnected());
+
+ AssertConnectSucceeds();
+
+ AssertConnectionEstablished();
+}
+
+TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthRequested) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+ const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+ ASSERT_TRUE(response != NULL);
+ ASSERT_EQ(407, response->headers->response_code());
+ ASSERT_EQ("Proxy Authentication Required",
+ response->headers->GetStatusText());
+}
+
+TEST_P(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectAuthRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+ AddAuthToCache();
+
+ AssertConnectSucceeds();
+
+ AssertConnectionEstablished();
+}
+
+TEST_P(SpdyProxyClientSocketTest, ConnectRedirects) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectRedirectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectFails(ERR_HTTPS_PROXY_TUNNEL_RESPONSE);
+
+ const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+ ASSERT_TRUE(response != NULL);
+
+ const HttpResponseHeaders* headers = response->headers.get();
+ ASSERT_EQ(302, headers->response_code());
+ ASSERT_FALSE(headers->HasHeader("set-cookie"));
+ ASSERT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+ std::string location;
+ ASSERT_TRUE(headers->IsRedirect(&location));
+ ASSERT_EQ(location, kRedirectUrl);
+}
+
+TEST_P(SpdyProxyClientSocketTest, ConnectFails) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 1), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ ASSERT_FALSE(sock_->IsConnected());
+
+ AssertConnectFails(ERR_CONNECTION_CLOSED);
+
+ ASSERT_FALSE(sock_->IsConnected());
+}
+
+// ----------- WasEverUsed
+
+TEST_P(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ EXPECT_FALSE(sock_->WasEverUsed());
+ AssertConnectSucceeds();
+ EXPECT_TRUE(sock_->WasEverUsed());
+ sock_->Disconnect();
+ EXPECT_TRUE(sock_->WasEverUsed());
+}
+
+// ----------- GetPeerAddress
+
+TEST_P(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ net::IPEndPoint addr;
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+ AssertConnectSucceeds();
+ EXPECT_TRUE(sock_->IsConnected());
+ EXPECT_EQ(OK, sock_->GetPeerAddress(&addr));
+
+ Run(1);
+
+ EXPECT_FALSE(sock_->IsConnected());
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+ sock_->Disconnect();
+
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+}
+
+// ----------- Write
+
+TEST_P(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ CreateMockWrite(*msg1, 2, SYNCHRONOUS),
+ CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ AssertAsyncWriteSucceeds(kMsg1, kLen1);
+ AssertAsyncWriteSucceeds(kMsg2, kLen2);
+}
+
+TEST_P(SpdyProxyClientSocketTest, WriteSplitsLargeDataIntoMultipleFrames) {
+ std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ scoped_ptr<SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(),
+ chunk_data.length()));
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ CreateMockWrite(*chunk, 2, SYNCHRONOUS),
+ CreateMockWrite(*chunk, 3, SYNCHRONOUS),
+ CreateMockWrite(*chunk, 4, SYNCHRONOUS)
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 5), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x');
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(),
+ big_data.length()));
+
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(buf.get(), buf->size(), write_callback_.callback()));
+ data_->RunFor(3);
+
+ EXPECT_EQ(buf->size(), write_callback_.WaitForResult());
+}
+
+// ----------- Read
+
+TEST_P(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1); // SpdySession consumes the next read and sends it to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_P(SpdyProxyClientSocketTest, ReadDataFromBufferedFrames) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg2, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1); // SpdySession consumes the next read and sends it to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+ Run(1); // SpdySession consumes the next read and sends it to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_P(SpdyProxyClientSocketTest, ReadDataMultipleBufferedFrames) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg2, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(2); // SpdySession consumes the next two reads and sends then to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+ AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_P(SpdyProxyClientSocketTest,
+ LargeReadWillMergeDataFromDifferentFrames) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg3, 2, ASYNC),
+ CreateMockRead(*msg3, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(2); // SpdySession consumes the next two reads and sends then to
+ // sock_ to be buffered.
+ // The payload from two data frames, each with kMsg3 will be combined
+ // together into a single read().
+ AssertSyncReadEquals(kMsg33, kLen33);
+}
+
+TEST_P(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg3, 3, ASYNC),
+ CreateMockRead(*msg3, 4, ASYNC),
+ CreateMockRead(*msg2, 5, ASYNC),
+ MockRead(ASYNC, 0, 6), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(4); // SpdySession consumes the next four reads and sends then to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+ // The payload from two data frames, each with kMsg3 will be combined
+ // together into a single read().
+ AssertSyncReadEquals(kMsg33, kLen33);
+ AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_P(SpdyProxyClientSocketTest, ReadWillSplitDataFromLargeFrame) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg33, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(2); // SpdySession consumes the next two reads and sends then to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+ // The payload from the single large data frame will be read across
+ // two different reads.
+ AssertSyncReadEquals(kMsg3, kLen3);
+ AssertSyncReadEquals(kMsg3, kLen3);
+}
+
+TEST_P(SpdyProxyClientSocketTest, MultipleReadsFromSameLargeFrame) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg333(ConstructBodyFrame(kMsg333, kLen333));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg333, 2, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1); // SpdySession consumes the next read and sends it to
+ // sock_ to be buffered.
+ // The payload from the single large data frame will be read across
+ // two different reads.
+ AssertSyncReadEquals(kMsg33, kLen33);
+
+ // Now attempt to do a read of more data than remains buffered
+ scoped_refptr<IOBuffer> buf(new IOBuffer(kLen33));
+ ASSERT_EQ(kLen3, sock_->Read(buf.get(), kLen33, read_callback_.callback()));
+ ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3));
+ ASSERT_TRUE(sock_->IsConnected());
+}
+
+TEST_P(SpdyProxyClientSocketTest, ReadAuthResponseBody) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg2, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+ Run(2); // SpdySession consumes the next two reads and sends then to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+ AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_P(SpdyProxyClientSocketTest, ReadErrorResponseBody) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectErrorReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg2, 3, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED);
+}
+
+// ----------- Reads and Writes
+
+TEST_P(SpdyProxyClientSocketTest, AsyncReadAroundWrite) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC), // sync read
+ CreateMockRead(*msg3, 4, ASYNC), // async read
+ MockRead(ASYNC, 0, 5), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+
+ AssertReadStarts(kMsg3, kLen3);
+ // Read should block until after the write succeeds
+
+ AssertAsyncWriteSucceeds(kMsg2, kLen2); // Runs 1 step
+
+ ASSERT_FALSE(read_callback_.have_result());
+ Run(1);
+ // Now the read will return
+ AssertReadReturns(kMsg3, kLen3);
+}
+
+TEST_P(SpdyProxyClientSocketTest, AsyncWriteAroundReads) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ CreateMockWrite(*msg2, 4, ASYNC),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ CreateMockRead(*msg3, 3, ASYNC),
+ MockRead(ASYNC, 0, 5), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1);
+ AssertSyncReadEquals(kMsg1, kLen1);
+ // Write should block until the read completes
+ AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+ AssertAsyncReadEquals(kMsg3, kLen3);
+
+ ASSERT_FALSE(write_callback_.have_result());
+
+ // Now the write will complete
+ Run(1);
+ AssertWriteLength(kLen2);
+}
+
+// ----------- Reading/Writing on Closed socket
+
+// Reading from an already closed socket should return 0
+TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1);
+
+ ASSERT_FALSE(sock_->IsConnected());
+ ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+ ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+ ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+ ASSERT_FALSE(sock_->IsConnectedAndIdle());
+}
+
+// Read pending when socket is closed should return 0
+TEST_P(SpdyProxyClientSocketTest, PendingReadOnCloseReturnsZero) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ AssertReadStarts(kMsg1, kLen1);
+
+ Run(1);
+
+ ASSERT_EQ(0, read_callback_.WaitForResult());
+}
+
+// Reading from a disconnected socket is an error
+TEST_P(SpdyProxyClientSocketTest,
+ ReadOnDisconnectSocketReturnsNotConnected) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ sock_->Disconnect();
+
+ ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+ sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Reading buffered data from an already closed socket should return
+// buffered data, then 0.
+TEST_P(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsBufferedData) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(2);
+
+ ASSERT_FALSE(sock_->IsConnected());
+ scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+ ASSERT_EQ(kLen1, sock_->Read(buf.get(), kLen1, CompletionCallback()));
+ ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1));
+
+ ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+ ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+ sock_->Disconnect();
+ ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+ sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Calling Write() on a closed socket is an error
+TEST_P(SpdyProxyClientSocketTest, WriteOnClosedStream) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1); // Read EOF which will close the stream
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+ sock_->Write(buf.get(), buf->size(), CompletionCallback()));
+}
+
+// Calling Write() on a disconnected socket is an error
+TEST_P(SpdyProxyClientSocketTest, WriteOnDisconnectedSocket) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ sock_->Disconnect();
+
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+ sock_->Write(buf.get(), buf->size(), CompletionCallback()));
+}
+
+// If the socket is closed with a pending Write(), the callback
+// should be called with ERR_CONNECTION_CLOSED.
+TEST_P(SpdyProxyClientSocketTest, WritePendingOnClose) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_ABORTED, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(buf.get(), buf->size(), write_callback_.callback()));
+
+ CloseSpdySession(ERR_ABORTED, std::string());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult());
+}
+
+// If the socket is Disconnected with a pending Write(), the callback
+// should not be called.
+TEST_P(SpdyProxyClientSocketTest, DisconnectWithWritePending) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(SYNCHRONOUS, 0, 2), // EOF
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(ERR_IO_PENDING,
+ sock_->Write(buf.get(), buf->size(), write_callback_.callback()));
+
+ sock_->Disconnect();
+
+ EXPECT_FALSE(sock_->IsConnected());
+ EXPECT_FALSE(write_callback_.have_result());
+}
+
+// If the socket is Disconnected with a pending Read(), the callback
+// should not be called.
+TEST_P(SpdyProxyClientSocketTest, DisconnectWithReadPending) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ MockRead(ASYNC, 0, 2), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(buf.get(), kLen1, read_callback_.callback()));
+
+ sock_->Disconnect();
+
+ EXPECT_FALSE(sock_->IsConnected());
+ EXPECT_FALSE(read_callback_.have_result());
+}
+
+// If the socket is Reset when both a read and write are pending,
+// both should be called back.
+TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePending) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_ABORTED, 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 2, ASYNC),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(read_buf.get(), kLen1, read_callback_.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(
+ ERR_IO_PENDING,
+ sock_->Write(
+ write_buf.get(), write_buf->size(), write_callback_.callback()));
+
+ Run(2);
+
+ EXPECT_TRUE(sock_.get());
+ EXPECT_TRUE(read_callback_.have_result());
+ EXPECT_TRUE(write_callback_.have_result());
+}
+
+// Makes sure the proxy client socket's source gets the expected NetLog events
+// and only the expected NetLog events (No SpdySession events).
+TEST_P(SpdyProxyClientSocketTest, NetLog) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*msg1, 2, ASYNC),
+ MockRead(ASYNC, 0, 3), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ Run(1); // SpdySession consumes the next read and sends it to
+ // sock_ to be buffered.
+ AssertSyncReadEquals(kMsg1, kLen1);
+
+ NetLog::Source sock_source = sock_->NetLog().source();
+ sock_.reset();
+
+ CapturingNetLog::CapturedEntryList entry_list;
+ net_log_.GetEntriesForSource(sock_source, &entry_list);
+
+ ASSERT_EQ(entry_list.size(), 10u);
+ EXPECT_TRUE(LogContainsBeginEvent(entry_list, 0, NetLog::TYPE_SOCKET_ALIVE));
+ EXPECT_TRUE(LogContainsEvent(entry_list, 1,
+ NetLog::TYPE_SPDY_PROXY_CLIENT_SESSION,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsBeginEvent(entry_list, 2,
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST));
+ EXPECT_TRUE(LogContainsEvent(entry_list, 3,
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(entry_list, 4,
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST));
+ EXPECT_TRUE(LogContainsBeginEvent(entry_list, 5,
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS));
+ EXPECT_TRUE(LogContainsEvent(entry_list, 6,
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(entry_list, 7,
+ NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS));
+ EXPECT_TRUE(LogContainsEvent(entry_list, 8,
+ NetLog::TYPE_SOCKET_BYTES_RECEIVED,
+ NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(entry_list, 9, NetLog::TYPE_SOCKET_ALIVE));
+}
+
+// CompletionCallback that causes the SpdyProxyClientSocket to be
+// deleted when Run is invoked.
+class DeleteSockCallback : public TestCompletionCallbackBase {
+ public:
+ explicit DeleteSockCallback(scoped_ptr<SpdyProxyClientSocket>* sock)
+ : sock_(sock),
+ callback_(base::Bind(&DeleteSockCallback::OnComplete,
+ base::Unretained(this))) {
+ }
+
+ virtual ~DeleteSockCallback() {
+ }
+
+ const CompletionCallback& callback() const { return callback_; }
+
+ private:
+ void OnComplete(int result) {
+ sock_->reset(NULL);
+ SetResult(result);
+ }
+
+ scoped_ptr<SpdyProxyClientSocket>* sock_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback);
+};
+
+// If the socket is Reset when both a read and write are pending, and the
+// read callback causes the socket to be deleted, the write callback should
+// not be called.
+TEST_P(SpdyProxyClientSocketTest, RstWithReadAndWritePendingDelete) {
+ scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+ MockWrite writes[] = {
+ CreateMockWrite(*conn, 0, SYNCHRONOUS),
+ MockWrite(ASYNC, ERR_ABORTED, 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1, ASYNC),
+ CreateMockRead(*rst, 2, ASYNC),
+ MockRead(ASYNC, 0, 4), // EOF
+ };
+
+ Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+ AssertConnectSucceeds();
+
+ EXPECT_TRUE(sock_->IsConnected());
+
+ DeleteSockCallback read_callback(&sock_);
+
+ scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+ ASSERT_EQ(ERR_IO_PENDING,
+ sock_->Read(read_buf.get(), kLen1, read_callback.callback()));
+
+ scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+ EXPECT_EQ(
+ ERR_IO_PENDING,
+ sock_->Write(
+ write_buf.get(), write_buf->size(), write_callback_.callback()));
+
+ Run(1);
+
+ EXPECT_FALSE(sock_.get());
+ EXPECT_TRUE(read_callback.have_result());
+ EXPECT_FALSE(write_callback_.have_result());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_read_queue.cc b/chromium/net/spdy/spdy_read_queue.cc
new file mode 100644
index 00000000000..36f1e06c4cc
--- /dev/null
+++ b/chromium/net/spdy/spdy_read_queue.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2013 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/spdy/spdy_read_queue.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/spdy/spdy_buffer.h"
+
+namespace net {
+
+SpdyReadQueue::SpdyReadQueue() : total_size_(0) {}
+
+SpdyReadQueue::~SpdyReadQueue() {
+ Clear();
+}
+
+bool SpdyReadQueue::IsEmpty() const {
+ DCHECK_EQ(queue_.empty(), total_size_ == 0);
+ return queue_.empty();
+}
+
+size_t SpdyReadQueue::GetTotalSize() const {
+ return total_size_;
+}
+
+void SpdyReadQueue::Enqueue(scoped_ptr<SpdyBuffer> buffer) {
+ DCHECK_GT(buffer->GetRemainingSize(), 0u);
+ total_size_ += buffer->GetRemainingSize();
+ queue_.push_back(buffer.release());
+}
+
+size_t SpdyReadQueue::Dequeue(char* out, size_t len) {
+ DCHECK_GT(len, 0u);
+ size_t bytes_copied = 0;
+ while (!queue_.empty() && bytes_copied < len) {
+ SpdyBuffer* buffer = queue_.front();
+ size_t bytes_to_copy =
+ std::min(len - bytes_copied, buffer->GetRemainingSize());
+ memcpy(out + bytes_copied, buffer->GetRemainingData(), bytes_to_copy);
+ bytes_copied += bytes_to_copy;
+ if (bytes_to_copy == buffer->GetRemainingSize()) {
+ delete queue_.front();
+ queue_.pop_front();
+ } else {
+ buffer->Consume(bytes_to_copy);
+ }
+ }
+ total_size_ -= bytes_copied;
+ return bytes_copied;
+}
+
+void SpdyReadQueue::Clear() {
+ STLDeleteElements(&queue_);
+ queue_.clear();
+}
+
+} // namespace
diff --git a/chromium/net/spdy/spdy_read_queue.h b/chromium/net/spdy/spdy_read_queue.h
new file mode 100644
index 00000000000..65f3dafc93f
--- /dev/null
+++ b/chromium/net/spdy/spdy_read_queue.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 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_SPDY_SPDY_BUFFER_QUEUE_H_
+#define NET_SPDY_SPDY_BUFFER_QUEUE_H_
+
+#include <cstddef>
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class SpdyBuffer;
+
+// A FIFO queue of incoming data from a SPDY connection. Useful for
+// SpdyStream delegates.
+class NET_EXPORT_PRIVATE SpdyReadQueue {
+ public:
+ SpdyReadQueue();
+ ~SpdyReadQueue();
+
+ // Returns whether there's anything in the queue.
+ bool IsEmpty() const;
+
+ // Returns the total number of bytes in the queue.
+ size_t GetTotalSize() const;
+
+ // Enqueues the bytes in |buffer|.
+ void Enqueue(scoped_ptr<SpdyBuffer> buffer);
+
+ // Dequeues up to |len| (which must be positive) bytes into
+ // |out|. Returns the number of bytes dequeued.
+ size_t Dequeue(char* out, size_t len);
+
+ // Removes all bytes from the queue.
+ void Clear();
+
+ private:
+ std::deque<SpdyBuffer*> queue_;
+ size_t total_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyReadQueue);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_BUFFER_QUEUE_H_
diff --git a/chromium/net/spdy/spdy_read_queue_unittest.cc b/chromium/net/spdy/spdy_read_queue_unittest.cc
new file mode 100644
index 00000000000..7281f6857cf
--- /dev/null
+++ b/chromium/net/spdy/spdy_read_queue_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (c) 2013 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/spdy/spdy_read_queue.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/spdy/spdy_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const char kData[] = "SPDY read queue test data.\0Some more data.";
+const size_t kDataSize = arraysize(kData);
+
+// Enqueues |data| onto |queue| in chunks of at most |max_buffer_size|
+// bytes.
+void EnqueueString(const std::string& data,
+ size_t max_buffer_size,
+ SpdyReadQueue* queue) {
+ ASSERT_GT(data.size(), 0u);
+ ASSERT_GT(max_buffer_size, 0u);
+ size_t old_total_size = queue->GetTotalSize();
+ for (size_t i = 0; i < data.size();) {
+ size_t buffer_size = std::min(data.size() - i, max_buffer_size);
+ queue->Enqueue(
+ scoped_ptr<SpdyBuffer>(new SpdyBuffer(data.data() + i, buffer_size)));
+ i += buffer_size;
+ EXPECT_FALSE(queue->IsEmpty());
+ EXPECT_EQ(old_total_size + i, queue->GetTotalSize());
+ }
+}
+
+// Dequeues all bytes in |queue| in chunks of at most
+// |max_buffer_size| bytes and returns the data as a string.
+std::string DrainToString(size_t max_buffer_size, SpdyReadQueue* queue) {
+ std::string data;
+
+ // Pad the buffer so we can detect out-of-bound writes.
+ size_t padding = std::max(static_cast<size_t>(4096), queue->GetTotalSize());
+ size_t buffer_size_with_padding = padding + max_buffer_size + padding;
+ scoped_ptr<char[]> buffer(new char[buffer_size_with_padding]);
+ std::memset(buffer.get(), 0, buffer_size_with_padding);
+ char* buffer_data = buffer.get() + padding;
+
+ while (!queue->IsEmpty()) {
+ size_t old_total_size = queue->GetTotalSize();
+ EXPECT_GT(old_total_size, 0u);
+ size_t dequeued_bytes = queue->Dequeue(buffer_data, max_buffer_size);
+
+ // Make sure |queue| doesn't write past either end of its given
+ // boundaries.
+ for (int i = 1; i <= static_cast<int>(padding); ++i) {
+ EXPECT_EQ('\0', buffer_data[-i]) << -i;
+ }
+ for (size_t i = 0; i < padding; ++i) {
+ EXPECT_EQ('\0', buffer_data[max_buffer_size + i]) << i;
+ }
+
+ data.append(buffer_data, dequeued_bytes);
+ EXPECT_EQ(dequeued_bytes, std::min(max_buffer_size, dequeued_bytes));
+ EXPECT_EQ(queue->GetTotalSize(), old_total_size - dequeued_bytes);
+ }
+ EXPECT_TRUE(queue->IsEmpty());
+ return data;
+}
+
+// Enqueue a test string with the given enqueue/dequeue max buffer
+// sizes.
+void RunEnqueueDequeueTest(size_t enqueue_max_buffer_size,
+ size_t dequeue_max_buffer_size) {
+ std::string data(kData, kDataSize);
+ SpdyReadQueue read_queue;
+ EnqueueString(data, enqueue_max_buffer_size, &read_queue);
+ const std::string& drained_data =
+ DrainToString(dequeue_max_buffer_size, &read_queue);
+ EXPECT_EQ(data, drained_data);
+}
+
+class SpdyReadQueueTest : public ::testing::Test {};
+
+// Call RunEnqueueDequeueTest() with various buffer size combinatinos.
+
+TEST_F(SpdyReadQueueTest, LargeEnqueueAndDequeueBuffers) {
+ RunEnqueueDequeueTest(2 * kDataSize, 2 * kDataSize);
+}
+
+TEST_F(SpdyReadQueueTest, OneByteEnqueueAndDequeueBuffers) {
+ RunEnqueueDequeueTest(1, 1);
+}
+
+TEST_F(SpdyReadQueueTest, CoprimeBufferSizes) {
+ RunEnqueueDequeueTest(2, 3);
+ RunEnqueueDequeueTest(3, 2);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_session.cc b/chromium/net/spdy/spdy_session.cc
new file mode 100644
index 00000000000..baef1952652
--- /dev/null
+++ b/chromium/net/spdy/spdy_session.cc
@@ -0,0 +1,2917 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_session.h"
+
+#include <algorithm>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/cert/asn1_util.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/spdy/spdy_buffer_producer.h"
+#include "net/spdy/spdy_credential_builder.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/ssl/server_bound_cert_service.h"
+
+namespace net {
+
+namespace {
+
+const int kReadBufferSize = 8 * 1024;
+const int kDefaultConnectionAtRiskOfLossSeconds = 10;
+const int kHungIntervalSeconds = 10;
+
+// Always start at 1 for the first stream id.
+const SpdyStreamId kFirstStreamId = 1;
+
+// Minimum seconds that unclaimed pushed streams will be kept in memory.
+const int kMinPushedStreamLifetimeSeconds = 300;
+
+base::Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers,
+ bool fin,
+ bool unidirectional,
+ SpdyStreamId stream_id,
+ SpdyStreamId associated_stream,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* headers_list = new base::ListValue();
+ for (SpdyHeaderBlock::const_iterator it = headers->begin();
+ it != headers->end(); ++it) {
+ headers_list->Append(new base::StringValue(base::StringPrintf(
+ "%s: %s", it->first.c_str(),
+ (ShouldShowHttpHeaderValue(
+ it->first) ? it->second : "[elided]").c_str())));
+ }
+ dict->SetBoolean("fin", fin);
+ dict->SetBoolean("unidirectional", unidirectional);
+ dict->Set("headers", headers_list);
+ dict->SetInteger("stream_id", stream_id);
+ if (associated_stream)
+ dict->SetInteger("associated_stream", associated_stream);
+ return dict;
+}
+
+base::Value* NetLogSpdyCredentialCallback(size_t slot,
+ const std::string* origin,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("slot", slot);
+ dict->SetString("origin", *origin);
+ return dict;
+}
+
+base::Value* NetLogSpdySessionCloseCallback(int net_error,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("net_error", net_error);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+base::Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("host", host_pair->first.ToString());
+ dict->SetString("proxy", host_pair->second.ToPacString());
+ return dict;
+}
+
+base::Value* NetLogSpdySettingsCallback(const HostPortPair& host_port_pair,
+ bool clear_persisted,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("host", host_port_pair.ToString());
+ dict->SetBoolean("clear_persisted", clear_persisted);
+ return dict;
+}
+
+base::Value* NetLogSpdySettingCallback(SpdySettingsIds id,
+ SpdySettingsFlags flags,
+ uint32 value,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("id", id);
+ dict->SetInteger("flags", flags);
+ dict->SetInteger("value", value);
+ return dict;
+}
+
+base::Value* NetLogSpdySendSettingsCallback(const SettingsMap* settings,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* settings_list = new base::ListValue();
+ for (SettingsMap::const_iterator it = settings->begin();
+ it != settings->end(); ++it) {
+ const SpdySettingsIds id = it->first;
+ const SpdySettingsFlags flags = it->second.first;
+ const uint32 value = it->second.second;
+ settings_list->Append(new base::StringValue(
+ base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value)));
+ }
+ dict->Set("settings", settings_list);
+ return dict;
+}
+
+base::Value* NetLogSpdyWindowUpdateFrameCallback(
+ SpdyStreamId stream_id,
+ uint32 delta,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("delta", delta);
+ return dict;
+}
+
+base::Value* NetLogSpdySessionWindowUpdateCallback(
+ int32 delta,
+ int32 window_size,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("delta", delta);
+ dict->SetInteger("window_size", window_size);
+ return dict;
+}
+
+base::Value* NetLogSpdyDataCallback(SpdyStreamId stream_id,
+ int size,
+ bool fin,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("size", size);
+ dict->SetBoolean("fin", fin);
+ return dict;
+}
+
+base::Value* NetLogSpdyRstCallback(SpdyStreamId stream_id,
+ int status,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("status", status);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+base::Value* NetLogSpdyPingCallback(uint32 unique_id,
+ const char* type,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("unique_id", unique_id);
+ dict->SetString("type", type);
+ return dict;
+}
+
+base::Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id,
+ int active_streams,
+ int unclaimed_streams,
+ SpdyGoAwayStatus status,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("last_accepted_stream_id",
+ static_cast<int>(last_stream_id));
+ dict->SetInteger("active_streams", active_streams);
+ dict->SetInteger("unclaimed_streams", unclaimed_streams);
+ dict->SetInteger("status", static_cast<int>(status));
+ return dict;
+}
+
+// The maximum number of concurrent streams we will ever create. Even if
+// the server permits more, we will never exceed this limit.
+const size_t kMaxConcurrentStreamLimit = 256;
+
+} // namespace
+
+SpdyStreamRequest::SpdyStreamRequest() {
+ Reset();
+}
+
+SpdyStreamRequest::~SpdyStreamRequest() {
+ CancelRequest();
+}
+
+int SpdyStreamRequest::StartRequest(
+ SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback) {
+ DCHECK(session.get());
+ DCHECK(!session_.get());
+ DCHECK(!stream_.get());
+ DCHECK(callback_.is_null());
+
+ type_ = type;
+ session_ = session;
+ url_ = url;
+ priority_ = priority;
+ net_log_ = net_log;
+ callback_ = callback;
+
+ base::WeakPtr<SpdyStream> stream;
+ int rv = session->TryCreateStream(this, &stream);
+ if (rv == OK) {
+ Reset();
+ stream_ = stream;
+ }
+ return rv;
+}
+
+void SpdyStreamRequest::CancelRequest() {
+ if (session_.get())
+ session_->CancelStreamRequest(this);
+ Reset();
+}
+
+base::WeakPtr<SpdyStream> SpdyStreamRequest::ReleaseStream() {
+ DCHECK(!session_.get());
+ base::WeakPtr<SpdyStream> stream = stream_;
+ DCHECK(stream.get());
+ Reset();
+ return stream;
+}
+
+void SpdyStreamRequest::OnRequestCompleteSuccess(
+ base::WeakPtr<SpdyStream>* stream) {
+ DCHECK(session_.get());
+ DCHECK(!stream_.get());
+ DCHECK(!callback_.is_null());
+ CompletionCallback callback = callback_;
+ Reset();
+ DCHECK(*stream);
+ stream_ = *stream;
+ callback.Run(OK);
+}
+
+void SpdyStreamRequest::OnRequestCompleteFailure(int rv) {
+ DCHECK(session_.get());
+ DCHECK(!stream_.get());
+ DCHECK(!callback_.is_null());
+ CompletionCallback callback = callback_;
+ Reset();
+ DCHECK_NE(rv, OK);
+ callback.Run(rv);
+}
+
+void SpdyStreamRequest::Reset() {
+ type_ = SPDY_BIDIRECTIONAL_STREAM;
+ session_.reset();
+ stream_.reset();
+ url_ = GURL();
+ priority_ = MINIMUM_PRIORITY;
+ net_log_ = BoundNetLog();
+ callback_.Reset();
+}
+
+SpdySession::ActiveStreamInfo::ActiveStreamInfo()
+ : stream(NULL),
+ waiting_for_syn_reply(false) {}
+
+SpdySession::ActiveStreamInfo::ActiveStreamInfo(SpdyStream* stream)
+ : stream(stream),
+ waiting_for_syn_reply(stream->type() != SPDY_PUSH_STREAM) {}
+
+SpdySession::ActiveStreamInfo::~ActiveStreamInfo() {}
+
+SpdySession::PushedStreamInfo::PushedStreamInfo() : stream_id(0) {}
+
+SpdySession::PushedStreamInfo::PushedStreamInfo(
+ SpdyStreamId stream_id,
+ base::TimeTicks creation_time)
+ : stream_id(stream_id),
+ creation_time(creation_time) {}
+
+SpdySession::PushedStreamInfo::~PushedStreamInfo() {}
+
+SpdySession::SpdySession(
+ const SpdySessionKey& spdy_session_key,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool verify_domain_authentication,
+ bool enable_sending_initial_data,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol,
+ size_t stream_initial_recv_window_size,
+ size_t initial_max_concurrent_streams,
+ size_t max_concurrent_streams_limit,
+ TimeFunc time_func,
+ const HostPortPair& trusted_spdy_proxy,
+ NetLog* net_log)
+ : weak_factory_(this),
+ in_io_loop_(false),
+ spdy_session_key_(spdy_session_key),
+ pool_(NULL),
+ http_server_properties_(http_server_properties),
+ read_buffer_(new IOBuffer(kReadBufferSize)),
+ stream_hi_water_mark_(kFirstStreamId),
+ in_flight_write_frame_type_(DATA),
+ in_flight_write_frame_size_(0),
+ is_secure_(false),
+ certificate_error_code_(OK),
+ availability_state_(STATE_AVAILABLE),
+ read_state_(READ_STATE_DO_READ),
+ write_state_(WRITE_STATE_IDLE),
+ error_on_close_(OK),
+ max_concurrent_streams_(initial_max_concurrent_streams == 0 ?
+ kInitialMaxConcurrentStreams :
+ initial_max_concurrent_streams),
+ max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 ?
+ kMaxConcurrentStreamLimit :
+ max_concurrent_streams_limit),
+ streams_initiated_count_(0),
+ streams_pushed_count_(0),
+ streams_pushed_and_claimed_count_(0),
+ streams_abandoned_count_(0),
+ total_bytes_received_(0),
+ sent_settings_(false),
+ received_settings_(false),
+ stalled_streams_(0),
+ pings_in_flight_(0),
+ next_ping_id_(1),
+ last_activity_time_(time_func()),
+ check_ping_status_pending_(false),
+ send_connection_header_prefix_(false),
+ flow_control_state_(FLOW_CONTROL_NONE),
+ stream_initial_send_window_size_(kSpdyStreamInitialWindowSize),
+ stream_initial_recv_window_size_(stream_initial_recv_window_size == 0 ?
+ kDefaultInitialRecvWindowSize :
+ stream_initial_recv_window_size),
+ session_send_window_size_(0),
+ session_recv_window_size_(0),
+ session_unacked_recv_window_bytes_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)),
+ verify_domain_authentication_(verify_domain_authentication),
+ enable_sending_initial_data_(enable_sending_initial_data),
+ enable_credential_frames_(enable_credential_frames),
+ enable_compression_(enable_compression),
+ enable_ping_based_connection_checking_(
+ enable_ping_based_connection_checking),
+ protocol_(default_protocol),
+ credential_state_(SpdyCredentialState::kDefaultNumSlots),
+ connection_at_risk_of_loss_time_(
+ base::TimeDelta::FromSeconds(kDefaultConnectionAtRiskOfLossSeconds)),
+ hung_interval_(
+ base::TimeDelta::FromSeconds(kHungIntervalSeconds)),
+ trusted_spdy_proxy_(trusted_spdy_proxy),
+ time_func_(time_func) {
+ // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we
+ // stop supporting SPDY/1.
+ DCHECK_GE(protocol_, kProtoSPDY2);
+ DCHECK_LE(protocol_, kProtoSPDYMaximumVersion);
+ DCHECK(HttpStreamFactory::spdy_enabled());
+ net_log_.BeginEvent(
+ NetLog::TYPE_SPDY_SESSION,
+ base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair()));
+ next_unclaimed_push_stream_sweep_time_ = time_func_() +
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+ // TODO(mbelshe): consider randomization of the stream_hi_water_mark.
+}
+
+SpdySession::~SpdySession() {
+ CHECK(!in_io_loop_);
+ DCHECK(!pool_);
+ DcheckClosed();
+
+ // TODO(akalin): Check connection->is_initialized() instead. This
+ // requires re-working CreateFakeSpdySession(), though.
+ DCHECK(connection_->socket());
+ // With SPDY we can't recycle sockets.
+ connection_->socket()->Disconnect();
+
+ RecordHistograms();
+
+ net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION);
+}
+
+Error SpdySession::InitializeWithSocket(
+ scoped_ptr<ClientSocketHandle> connection,
+ SpdySessionPool* pool,
+ bool is_secure,
+ int certificate_error_code) {
+ CHECK(!in_io_loop_);
+ DCHECK_EQ(availability_state_, STATE_AVAILABLE);
+ DCHECK_EQ(read_state_, READ_STATE_DO_READ);
+ DCHECK_EQ(write_state_, WRITE_STATE_IDLE);
+ DCHECK(!connection_);
+
+ DCHECK(certificate_error_code == OK ||
+ certificate_error_code < ERR_IO_PENDING);
+ // TODO(akalin): Check connection->is_initialized() instead. This
+ // requires re-working CreateFakeSpdySession(), though.
+ DCHECK(connection->socket());
+
+ base::StatsCounter spdy_sessions("spdy.sessions");
+ spdy_sessions.Increment();
+
+ connection_ = connection.Pass();
+ is_secure_ = is_secure;
+ certificate_error_code_ = certificate_error_code;
+
+ NextProto protocol_negotiated =
+ connection_->socket()->GetNegotiatedProtocol();
+ if (protocol_negotiated != kProtoUnknown) {
+ protocol_ = protocol_negotiated;
+ }
+ // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we
+ // stop supporting SPDY/1.
+ DCHECK_GE(protocol_, kProtoSPDY2);
+ DCHECK_LE(protocol_, kProtoSPDYMaximumVersion);
+
+ SSLClientSocket* ssl_socket = GetSSLClientSocket();
+ if (ssl_socket && ssl_socket->WasChannelIDSent()) {
+ // According to the SPDY spec, the credential associated with the TLS
+ // connection is stored in slot[1].
+ credential_state_.SetHasCredential(GURL("https://" +
+ host_port_pair().ToString()));
+ }
+
+ if (protocol_ == kProtoHTTP2Draft04)
+ send_connection_header_prefix_ = true;
+
+ if (protocol_ >= kProtoSPDY31) {
+ flow_control_state_ = FLOW_CONTROL_STREAM_AND_SESSION;
+ session_send_window_size_ = kSpdySessionInitialWindowSize;
+ session_recv_window_size_ = kSpdySessionInitialWindowSize;
+ } else if (protocol_ >= kProtoSPDY3) {
+ flow_control_state_ = FLOW_CONTROL_STREAM;
+ } else {
+ flow_control_state_ = FLOW_CONTROL_NONE;
+ }
+
+ buffered_spdy_framer_.reset(
+ new BufferedSpdyFramer(NextProtoToSpdyMajorVersion(protocol_),
+ enable_compression_));
+ buffered_spdy_framer_->set_visitor(this);
+ buffered_spdy_framer_->set_debug_visitor(this);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol_, kProtoMaximumVersion);
+#if defined(SPDY_PROXY_AUTH_ORIGIN)
+ UMA_HISTOGRAM_BOOLEAN("Net.SpdySessions_DataReductionProxy",
+ host_port_pair().Equals(HostPortPair::FromURL(
+ GURL(SPDY_PROXY_AUTH_ORIGIN))));
+#endif
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_INITIALIZED,
+ connection_->socket()->NetLog().source().ToEventParametersCallback());
+
+ int error = DoReadLoop(READ_STATE_DO_READ, OK);
+ if (error == ERR_IO_PENDING)
+ error = OK;
+ if (error == OK) {
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ connection_->AddLayeredPool(this);
+ if (enable_sending_initial_data_)
+ SendInitialData();
+ pool_ = pool;
+ } else {
+ DcheckClosed();
+ }
+ return static_cast<Error>(error);
+}
+
+bool SpdySession::VerifyDomainAuthentication(const std::string& domain) {
+ if (!verify_domain_authentication_)
+ return true;
+
+ if (availability_state_ == STATE_CLOSED)
+ return false;
+
+ SSLInfo ssl_info;
+ bool was_npn_negotiated;
+ NextProto protocol_negotiated = kProtoUnknown;
+ if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated))
+ return true; // This is not a secure session, so all domains are okay.
+
+ return !ssl_info.client_cert_sent &&
+ (enable_credential_frames_ || !ssl_info.channel_id_sent ||
+ ServerBoundCertService::GetDomainForHost(domain) ==
+ ServerBoundCertService::GetDomainForHost(host_port_pair().host())) &&
+ ssl_info.cert->VerifyNameMatch(domain);
+}
+
+int SpdySession::GetPushStream(
+ const GURL& url,
+ base::WeakPtr<SpdyStream>* stream,
+ const BoundNetLog& stream_net_log) {
+ CHECK(!in_io_loop_);
+
+ stream->reset();
+
+ // TODO(akalin): Add unit test exercising this code path.
+ if (availability_state_ == STATE_CLOSED)
+ return ERR_CONNECTION_CLOSED;
+
+ Error err = TryAccessStream(url);
+ if (err != OK)
+ return err;
+
+ *stream = GetActivePushStream(url);
+ if (*stream) {
+ DCHECK_LT(streams_pushed_and_claimed_count_, streams_pushed_count_);
+ streams_pushed_and_claimed_count_++;
+ }
+ return OK;
+}
+
+// {,Try}CreateStream() and TryAccessStream() can be called with
+// |in_io_loop_| set if a stream is being created in response to
+// another being closed due to received data.
+
+Error SpdySession::TryAccessStream(const GURL& url) {
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ if (is_secure_ && certificate_error_code_ != OK &&
+ (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+ RecordProtocolErrorHistogram(
+ PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION);
+ CloseSessionResult result = DoCloseSession(
+ static_cast<Error>(certificate_error_code_),
+ "Tried to get SPDY stream for secure content over an unauthenticated "
+ "session.");
+ DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED);
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+ return OK;
+}
+
+int SpdySession::TryCreateStream(SpdyStreamRequest* request,
+ base::WeakPtr<SpdyStream>* stream) {
+ CHECK(request);
+
+ if (availability_state_ == STATE_GOING_AWAY)
+ return ERR_FAILED;
+
+ // TODO(akalin): Add unit test exercising this code path.
+ if (availability_state_ == STATE_CLOSED)
+ return ERR_CONNECTION_CLOSED;
+
+ Error err = TryAccessStream(request->url());
+ if (err != OK)
+ return err;
+
+ if (!max_concurrent_streams_ ||
+ (active_streams_.size() + created_streams_.size() <
+ max_concurrent_streams_)) {
+ return CreateStream(*request, stream);
+ }
+
+ stalled_streams_++;
+ net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_STALLED_MAX_STREAMS);
+ pending_create_stream_queues_[request->priority()].push_back(request);
+ return ERR_IO_PENDING;
+}
+
+int SpdySession::CreateStream(const SpdyStreamRequest& request,
+ base::WeakPtr<SpdyStream>* stream) {
+ DCHECK_GE(request.priority(), MINIMUM_PRIORITY);
+ DCHECK_LT(request.priority(), NUM_PRIORITIES);
+
+ if (availability_state_ == STATE_GOING_AWAY)
+ return ERR_FAILED;
+
+ // TODO(akalin): Add unit test exercising this code path.
+ if (availability_state_ == STATE_CLOSED)
+ return ERR_CONNECTION_CLOSED;
+
+ Error err = TryAccessStream(request.url());
+ if (err != OK) {
+ // This should have been caught in TryCreateStream().
+ NOTREACHED();
+ return err;
+ }
+
+ DCHECK(connection_->socket());
+ DCHECK(connection_->socket()->IsConnected());
+ if (connection_->socket()) {
+ UMA_HISTOGRAM_BOOLEAN("Net.SpdySession.CreateStreamWithSocketConnected",
+ connection_->socket()->IsConnected());
+ if (!connection_->socket()->IsConnected()) {
+ CloseSessionResult result = DoCloseSession(
+ ERR_CONNECTION_CLOSED,
+ "Tried to create SPDY stream for a closed socket connection.");
+ DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED);
+ return ERR_CONNECTION_CLOSED;
+ }
+ }
+
+ scoped_ptr<SpdyStream> new_stream(
+ new SpdyStream(request.type(), GetWeakPtr(), request.url(),
+ request.priority(),
+ stream_initial_send_window_size_,
+ stream_initial_recv_window_size_,
+ request.net_log()));
+ *stream = new_stream->GetWeakPtr();
+ InsertCreatedStream(new_stream.Pass());
+
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Net.SpdyPriorityCount",
+ static_cast<int>(request.priority()), 0, 10, 11);
+
+ return OK;
+}
+
+void SpdySession::CancelStreamRequest(SpdyStreamRequest* request) {
+ CHECK(request);
+
+ if (DCHECK_IS_ON()) {
+ // |request| should not be in a queue not matching its priority.
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ if (request->priority() == i)
+ continue;
+ PendingStreamRequestQueue* queue = &pending_create_stream_queues_[i];
+ DCHECK(std::find(queue->begin(), queue->end(), request) == queue->end());
+ }
+ }
+
+ PendingStreamRequestQueue* queue =
+ &pending_create_stream_queues_[request->priority()];
+ // Remove |request| from |queue| while preserving the order of the
+ // other elements.
+ PendingStreamRequestQueue::iterator it =
+ std::find(queue->begin(), queue->end(), request);
+ if (it != queue->end()) {
+ it = queue->erase(it);
+ // |request| should be in the queue at most once, and if it is
+ // present, should not be pending completion.
+ DCHECK(std::find(it, queue->end(), request) == queue->end());
+ DCHECK(!ContainsKey(pending_stream_request_completions_,
+ request));
+ return;
+ }
+
+ pending_stream_request_completions_.erase(request);
+}
+
+void SpdySession::ProcessPendingStreamRequests() {
+ // Like |max_concurrent_streams_|, 0 means infinite for
+ // |max_requests_to_process|.
+ size_t max_requests_to_process = 0;
+ if (max_concurrent_streams_ != 0) {
+ max_requests_to_process =
+ max_concurrent_streams_ -
+ (active_streams_.size() + created_streams_.size());
+ }
+ for (size_t i = 0;
+ max_requests_to_process == 0 || i < max_requests_to_process; ++i) {
+ bool processed_request = false;
+ for (int j = NUM_PRIORITIES - 1; j >= MINIMUM_PRIORITY; --j) {
+ if (pending_create_stream_queues_[j].empty())
+ continue;
+
+ SpdyStreamRequest* pending_request =
+ pending_create_stream_queues_[j].front();
+ CHECK(pending_request);
+ pending_create_stream_queues_[j].pop_front();
+ processed_request = true;
+ DCHECK(!ContainsKey(pending_stream_request_completions_,
+ pending_request));
+ pending_stream_request_completions_.insert(pending_request);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::CompleteStreamRequest,
+ weak_factory_.GetWeakPtr(), pending_request));
+ break;
+ }
+ if (!processed_request)
+ break;
+ }
+}
+
+bool SpdySession::NeedsCredentials() const {
+ if (!is_secure_)
+ return false;
+ SSLClientSocket* ssl_socket = GetSSLClientSocket();
+ if (ssl_socket->GetNegotiatedProtocol() < kProtoSPDY3)
+ return false;
+ return ssl_socket->WasChannelIDSent();
+}
+
+void SpdySession::AddPooledAlias(const SpdySessionKey& alias_key) {
+ pooled_aliases_.insert(alias_key);
+}
+
+int SpdySession::GetProtocolVersion() const {
+ DCHECK(buffered_spdy_framer_.get());
+ return buffered_spdy_framer_->protocol_version();
+}
+
+base::WeakPtr<SpdySession> SpdySession::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
+bool SpdySession::CloseOneIdleConnection() {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK(pool_);
+ if (!active_streams_.empty())
+ return false;
+ CloseSessionResult result =
+ DoCloseSession(ERR_CONNECTION_CLOSED, "Closing one idle connection.");
+ if (result != SESSION_CLOSED_AND_REMOVED) {
+ NOTREACHED();
+ return false;
+ }
+ return true;
+}
+
+void SpdySession::EnqueueStreamWrite(
+ const base::WeakPtr<SpdyStream>& stream,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> producer) {
+ DCHECK(frame_type == HEADERS ||
+ frame_type == DATA ||
+ frame_type == CREDENTIAL ||
+ frame_type == SYN_STREAM);
+ EnqueueWrite(stream->priority(), frame_type, producer.Pass(), stream);
+}
+
+scoped_ptr<SpdyFrame> SpdySession::CreateSynStream(
+ SpdyStreamId stream_id,
+ RequestPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ const SpdyHeaderBlock& headers) {
+ ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
+ CHECK(it != active_streams_.end());
+ CHECK_EQ(it->second.stream->stream_id(), stream_id);
+
+ SendPrefacePingIfNoneInFlight();
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> syn_frame(
+ buffered_spdy_framer_->CreateSynStream(
+ stream_id, 0,
+ ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()),
+ credential_slot, flags, enable_compression_, &headers));
+
+ base::StatsCounter spdy_requests("spdy.requests");
+ spdy_requests.Increment();
+ streams_initiated_count_++;
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+ base::Bind(&NetLogSpdySynCallback, &headers,
+ (flags & CONTROL_FLAG_FIN) != 0,
+ (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
+ stream_id, 0));
+ }
+
+ return syn_frame.Pass();
+}
+
+int SpdySession::CreateCredentialFrame(
+ const std::string& origin,
+ const std::string& key,
+ const std::string& cert,
+ RequestPriority priority,
+ scoped_ptr<SpdyFrame>* credential_frame) {
+ DCHECK(is_secure_);
+ SSLClientSocket* ssl_socket = GetSSLClientSocket();
+ DCHECK(ssl_socket);
+ DCHECK(ssl_socket->WasChannelIDSent());
+
+ SpdyCredential credential;
+ std::string tls_unique;
+ ssl_socket->GetTLSUniqueChannelBinding(&tls_unique);
+ size_t slot = credential_state_.SetHasCredential(GURL(origin));
+ int rv = SpdyCredentialBuilder::Build(tls_unique, key, cert, slot,
+ &credential);
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ if (rv != OK)
+ return rv;
+
+ DCHECK(buffered_spdy_framer_.get());
+ credential_frame->reset(
+ buffered_spdy_framer_->CreateCredentialFrame(credential));
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_CREDENTIAL,
+ base::Bind(&NetLogSpdyCredentialCallback, credential.slot, &origin));
+ }
+ return OK;
+}
+
+scoped_ptr<SpdyBuffer> SpdySession::CreateDataBuffer(SpdyStreamId stream_id,
+ IOBuffer* data,
+ int len,
+ SpdyDataFlags flags) {
+ if (availability_state_ == STATE_CLOSED) {
+ NOTREACHED();
+ return scoped_ptr<SpdyBuffer>();
+ }
+
+ ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
+ CHECK(it != active_streams_.end());
+ SpdyStream* stream = it->second.stream;
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ if (len < 0) {
+ NOTREACHED();
+ return scoped_ptr<SpdyBuffer>();
+ }
+
+ int effective_len = std::min(len, kMaxSpdyFrameChunkSize);
+
+ bool send_stalled_by_stream =
+ (flow_control_state_ >= FLOW_CONTROL_STREAM) &&
+ (stream->send_window_size() <= 0);
+ bool send_stalled_by_session = IsSendStalled();
+
+ // NOTE: There's an enum of the same name in histograms.xml.
+ enum SpdyFrameFlowControlState {
+ SEND_NOT_STALLED,
+ SEND_STALLED_BY_STREAM,
+ SEND_STALLED_BY_SESSION,
+ SEND_STALLED_BY_STREAM_AND_SESSION,
+ };
+
+ SpdyFrameFlowControlState frame_flow_control_state = SEND_NOT_STALLED;
+ if (send_stalled_by_stream) {
+ if (send_stalled_by_session) {
+ frame_flow_control_state = SEND_STALLED_BY_STREAM_AND_SESSION;
+ } else {
+ frame_flow_control_state = SEND_STALLED_BY_STREAM;
+ }
+ } else if (send_stalled_by_session) {
+ frame_flow_control_state = SEND_STALLED_BY_SESSION;
+ }
+
+ if (flow_control_state_ == FLOW_CONTROL_STREAM) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.SpdyFrameStreamFlowControlState",
+ frame_flow_control_state,
+ SEND_STALLED_BY_STREAM + 1);
+ } else if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.SpdyFrameStreamAndSessionFlowControlState",
+ frame_flow_control_state,
+ SEND_STALLED_BY_STREAM_AND_SESSION + 1);
+ }
+
+ // Obey send window size of the stream if stream flow control is
+ // enabled.
+ if (flow_control_state_ >= FLOW_CONTROL_STREAM) {
+ if (send_stalled_by_stream) {
+ stream->set_send_stalled_by_flow_control(true);
+ // Even though we're currently stalled only by the stream, we
+ // might end up being stalled by the session also.
+ QueueSendStalledStream(*stream);
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW,
+ NetLog::IntegerCallback("stream_id", stream_id));
+ return scoped_ptr<SpdyBuffer>();
+ }
+
+ effective_len = std::min(effective_len, stream->send_window_size());
+ }
+
+ // Obey send window size of the session if session flow control is
+ // enabled.
+ if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ if (send_stalled_by_session) {
+ stream->set_send_stalled_by_flow_control(true);
+ QueueSendStalledStream(*stream);
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW,
+ NetLog::IntegerCallback("stream_id", stream_id));
+ return scoped_ptr<SpdyBuffer>();
+ }
+
+ effective_len = std::min(effective_len, session_send_window_size_);
+ }
+
+ DCHECK_GE(effective_len, 0);
+
+ // Clear FIN flag if only some of the data will be in the data
+ // frame.
+ if (effective_len < len)
+ flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN);
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_DATA,
+ base::Bind(&NetLogSpdyDataCallback, stream_id, effective_len,
+ (flags & DATA_FLAG_FIN) != 0));
+ }
+
+ // Send PrefacePing for DATA_FRAMEs with nonzero payload size.
+ if (effective_len > 0)
+ SendPrefacePingIfNoneInFlight();
+
+ // TODO(mbelshe): reduce memory copies here.
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> frame(
+ buffered_spdy_framer_->CreateDataFrame(
+ stream_id, data->data(),
+ static_cast<uint32>(effective_len), flags));
+
+ scoped_ptr<SpdyBuffer> data_buffer(new SpdyBuffer(frame.Pass()));
+
+ if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ DecreaseSendWindowSize(static_cast<int32>(effective_len));
+ data_buffer->AddConsumeCallback(
+ base::Bind(&SpdySession::OnWriteBufferConsumed,
+ weak_factory_.GetWeakPtr(),
+ static_cast<size_t>(effective_len)));
+ }
+
+ return data_buffer.Pass();
+}
+
+void SpdySession::CloseActiveStream(SpdyStreamId stream_id, int status) {
+ DCHECK_NE(stream_id, 0u);
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ CloseActiveStreamIterator(it, status);
+}
+
+void SpdySession::CloseCreatedStream(
+ const base::WeakPtr<SpdyStream>& stream, int status) {
+ DCHECK_EQ(stream->stream_id(), 0u);
+
+ CreatedStreamSet::iterator it = created_streams_.find(stream.get());
+ if (it == created_streams_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ CloseCreatedStreamIterator(it, status);
+}
+
+void SpdySession::ResetStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status,
+ const std::string& description) {
+ DCHECK_NE(stream_id, 0u);
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ ResetStreamIterator(it, status, description);
+}
+
+bool SpdySession::IsStreamActive(SpdyStreamId stream_id) const {
+ return ContainsKey(active_streams_, stream_id);
+}
+
+LoadState SpdySession::GetLoadState() const {
+ // Just report that we're idle since the session could be doing
+ // many things concurrently.
+ return LOAD_STATE_IDLE;
+}
+
+void SpdySession::CloseActiveStreamIterator(ActiveStreamMap::iterator it,
+ int status) {
+ // TODO(mbelshe): We should send a RST_STREAM control frame here
+ // so that the server can cancel a large send.
+
+ scoped_ptr<SpdyStream> owned_stream(it->second.stream);
+ active_streams_.erase(it);
+
+ // TODO(akalin): When SpdyStream was ref-counted (and
+ // |unclaimed_pushed_streams_| held scoped_refptr<SpdyStream>), this
+ // was only done when status was not OK. This meant that pushed
+ // streams can still be claimed after they're closed. This is
+ // probably something that we still want to support, although server
+ // push is hardly used. Write tests for this and fix this. (See
+ // http://crbug.com/261712 .)
+ if (owned_stream->type() == SPDY_PUSH_STREAM)
+ unclaimed_pushed_streams_.erase(owned_stream->url());
+
+ DeleteStream(owned_stream.Pass(), status);
+}
+
+void SpdySession::CloseCreatedStreamIterator(CreatedStreamSet::iterator it,
+ int status) {
+ scoped_ptr<SpdyStream> owned_stream(*it);
+ created_streams_.erase(it);
+ DeleteStream(owned_stream.Pass(), status);
+}
+
+void SpdySession::ResetStreamIterator(ActiveStreamMap::iterator it,
+ SpdyRstStreamStatus status,
+ const std::string& description) {
+ // Send the RST_STREAM frame first as CloseActiveStreamIterator()
+ // may close us.
+ SpdyStreamId stream_id = it->first;
+ RequestPriority priority = it->second.stream->priority();
+ EnqueueResetStreamFrame(stream_id, priority, status, description);
+
+ // Removes any pending writes for the stream except for possibly an
+ // in-flight one.
+ CloseActiveStreamIterator(it, ERR_SPDY_PROTOCOL_ERROR);
+}
+
+void SpdySession::EnqueueResetStreamFrame(SpdyStreamId stream_id,
+ RequestPriority priority,
+ SpdyRstStreamStatus status,
+ const std::string& description) {
+ DCHECK_NE(stream_id, 0u);
+
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_RST_STREAM,
+ base::Bind(&NetLogSpdyRstCallback, stream_id, status, &description));
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> rst_frame(
+ buffered_spdy_framer_->CreateRstStream(stream_id, status));
+
+ EnqueueSessionWrite(priority, RST_STREAM, rst_frame.Pass());
+ RecordProtocolErrorHistogram(
+ static_cast<SpdyProtocolErrorDetails>(status + STATUS_CODE_INVALID));
+}
+
+void SpdySession::PumpReadLoop(ReadState expected_read_state, int result) {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(read_state_, expected_read_state);
+
+ result = DoReadLoop(expected_read_state, result);
+
+ if (availability_state_ == STATE_CLOSED) {
+ DCHECK_EQ(result, error_on_close_);
+ DCHECK_LT(error_on_close_, ERR_IO_PENDING);
+ RemoveFromPool();
+ return;
+ }
+
+ DCHECK(result == OK || result == ERR_IO_PENDING);
+}
+
+int SpdySession::DoReadLoop(ReadState expected_read_state, int result) {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(read_state_, expected_read_state);
+
+ in_io_loop_ = true;
+
+ int bytes_read_without_yielding = 0;
+
+ // Loop until the session is closed, the read becomes blocked, or
+ // the read limit is exceeded.
+ while (true) {
+ switch (read_state_) {
+ case READ_STATE_DO_READ:
+ DCHECK_EQ(result, OK);
+ result = DoRead();
+ break;
+ case READ_STATE_DO_READ_COMPLETE:
+ if (result > 0)
+ bytes_read_without_yielding += result;
+ result = DoReadComplete(result);
+ break;
+ default:
+ NOTREACHED() << "read_state_: " << read_state_;
+ break;
+ }
+
+ if (availability_state_ == STATE_CLOSED) {
+ DCHECK_EQ(result, error_on_close_);
+ DCHECK_LT(result, ERR_IO_PENDING);
+ break;
+ }
+
+ if (result == ERR_IO_PENDING)
+ break;
+
+ if (bytes_read_without_yielding > kMaxReadBytesWithoutYielding) {
+ read_state_ = READ_STATE_DO_READ;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::PumpReadLoop,
+ weak_factory_.GetWeakPtr(), READ_STATE_DO_READ, OK));
+ result = ERR_IO_PENDING;
+ break;
+ }
+ }
+
+ CHECK(in_io_loop_);
+ in_io_loop_ = false;
+
+ return result;
+}
+
+int SpdySession::DoRead() {
+ CHECK(in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ CHECK(connection_);
+ CHECK(connection_->socket());
+ read_state_ = READ_STATE_DO_READ_COMPLETE;
+ return connection_->socket()->Read(
+ read_buffer_.get(),
+ kReadBufferSize,
+ base::Bind(&SpdySession::PumpReadLoop,
+ weak_factory_.GetWeakPtr(), READ_STATE_DO_READ_COMPLETE));
+}
+
+int SpdySession::DoReadComplete(int result) {
+ CHECK(in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ // Parse a frame. For now this code requires that the frame fit into our
+ // buffer (kReadBufferSize).
+ // TODO(mbelshe): support arbitrarily large frames!
+
+ if (result == 0) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySession.BytesRead.EOF",
+ total_bytes_received_, 1, 100000000, 50);
+ CloseSessionResult close_session_result =
+ DoCloseSession(ERR_CONNECTION_CLOSED, "Connection closed");
+ DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ DCHECK_EQ(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(error_on_close_, ERR_CONNECTION_CLOSED);
+ return ERR_CONNECTION_CLOSED;
+ }
+
+ if (result < 0) {
+ CloseSessionResult close_session_result =
+ DoCloseSession(static_cast<Error>(result), "result is < 0.");
+ DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ DCHECK_EQ(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(error_on_close_, result);
+ return result;
+ }
+
+ total_bytes_received_ += result;
+
+ last_activity_time_ = time_func_();
+
+ DCHECK(buffered_spdy_framer_.get());
+ char* data = read_buffer_->data();
+ while (result > 0) {
+ uint32 bytes_processed = buffered_spdy_framer_->ProcessInput(data, result);
+ result -= bytes_processed;
+ data += bytes_processed;
+
+ if (availability_state_ == STATE_CLOSED) {
+ DCHECK_LT(error_on_close_, ERR_IO_PENDING);
+ return error_on_close_;
+ }
+
+ DCHECK_EQ(buffered_spdy_framer_->error_code(), SpdyFramer::SPDY_NO_ERROR);
+ }
+
+ read_state_ = READ_STATE_DO_READ;
+ return OK;
+}
+
+void SpdySession::PumpWriteLoop(WriteState expected_write_state, int result) {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(write_state_, expected_write_state);
+
+ result = DoWriteLoop(expected_write_state, result);
+
+ if (availability_state_ == STATE_CLOSED) {
+ DCHECK_EQ(result, error_on_close_);
+ DCHECK_LT(error_on_close_, ERR_IO_PENDING);
+ RemoveFromPool();
+ return;
+ }
+
+ DCHECK(result == OK || result == ERR_IO_PENDING);
+}
+
+int SpdySession::DoWriteLoop(WriteState expected_write_state, int result) {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_NE(write_state_, WRITE_STATE_IDLE);
+ DCHECK_EQ(write_state_, expected_write_state);
+
+ in_io_loop_ = true;
+
+ // Loop until the session is closed or the write becomes blocked.
+ while (true) {
+ switch (write_state_) {
+ case WRITE_STATE_DO_WRITE:
+ DCHECK_EQ(result, OK);
+ result = DoWrite();
+ break;
+ case WRITE_STATE_DO_WRITE_COMPLETE:
+ result = DoWriteComplete(result);
+ break;
+ case WRITE_STATE_IDLE:
+ default:
+ NOTREACHED() << "write_state_: " << write_state_;
+ break;
+ }
+
+ if (availability_state_ == STATE_CLOSED) {
+ DCHECK_EQ(result, error_on_close_);
+ DCHECK_LT(result, ERR_IO_PENDING);
+ break;
+ }
+
+ if (write_state_ == WRITE_STATE_IDLE) {
+ DCHECK_EQ(result, ERR_IO_PENDING);
+ break;
+ }
+
+ if (result == ERR_IO_PENDING)
+ break;
+ }
+
+ CHECK(in_io_loop_);
+ in_io_loop_ = false;
+
+ return result;
+}
+
+int SpdySession::DoWrite() {
+ CHECK(in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ DCHECK(buffered_spdy_framer_);
+ if (in_flight_write_) {
+ DCHECK_GT(in_flight_write_->GetRemainingSize(), 0u);
+ } else {
+ // Grab the next frame to send.
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> producer;
+ base::WeakPtr<SpdyStream> stream;
+ if (!write_queue_.Dequeue(&frame_type, &producer, &stream)) {
+ write_state_ = WRITE_STATE_IDLE;
+ return ERR_IO_PENDING;
+ }
+
+ if (stream.get())
+ DCHECK(!stream->IsClosed());
+
+ // Activate the stream only when sending the SYN_STREAM frame to
+ // guarantee monotonically-increasing stream IDs.
+ if (frame_type == SYN_STREAM) {
+ if (stream.get() && stream->stream_id() == 0) {
+ scoped_ptr<SpdyStream> owned_stream =
+ ActivateCreatedStream(stream.get());
+ InsertActivatedStream(owned_stream.Pass());
+ } else {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ }
+
+ in_flight_write_ = producer->ProduceBuffer();
+ if (!in_flight_write_) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ in_flight_write_frame_type_ = frame_type;
+ in_flight_write_frame_size_ = in_flight_write_->GetRemainingSize();
+ DCHECK_GE(in_flight_write_frame_size_,
+ buffered_spdy_framer_->GetFrameMinimumSize());
+ in_flight_write_stream_ = stream;
+ }
+
+ write_state_ = WRITE_STATE_DO_WRITE_COMPLETE;
+
+ // Explicitly store in a scoped_refptr<IOBuffer> to avoid problems
+ // with Socket implementations that don't store their IOBuffer
+ // argument in a scoped_refptr<IOBuffer> (see crbug.com/232345).
+ scoped_refptr<IOBuffer> write_io_buffer =
+ in_flight_write_->GetIOBufferForRemainingData();
+ return connection_->socket()->Write(
+ write_io_buffer.get(),
+ in_flight_write_->GetRemainingSize(),
+ base::Bind(&SpdySession::PumpWriteLoop,
+ weak_factory_.GetWeakPtr(), WRITE_STATE_DO_WRITE_COMPLETE));
+}
+
+int SpdySession::DoWriteComplete(int result) {
+ CHECK(in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_NE(result, ERR_IO_PENDING);
+ DCHECK_GT(in_flight_write_->GetRemainingSize(), 0u);
+
+ last_activity_time_ = time_func_();
+
+ if (result < 0) {
+ DCHECK_NE(result, ERR_IO_PENDING);
+ in_flight_write_.reset();
+ in_flight_write_frame_type_ = DATA;
+ in_flight_write_frame_size_ = 0;
+ in_flight_write_stream_.reset();
+ CloseSessionResult close_session_result =
+ DoCloseSession(static_cast<Error>(result), "Write error");
+ DCHECK_EQ(close_session_result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ DCHECK_EQ(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(error_on_close_, result);
+ return result;
+ }
+
+ // It should not be possible to have written more bytes than our
+ // in_flight_write_.
+ DCHECK_LE(static_cast<size_t>(result),
+ in_flight_write_->GetRemainingSize());
+
+ if (result > 0) {
+ in_flight_write_->Consume(static_cast<size_t>(result));
+
+ // We only notify the stream when we've fully written the pending frame.
+ if (in_flight_write_->GetRemainingSize() == 0) {
+ // It is possible that the stream was cancelled while we were
+ // writing to the socket.
+ if (in_flight_write_stream_.get()) {
+ DCHECK_GT(in_flight_write_frame_size_, 0u);
+ in_flight_write_stream_->OnFrameWriteComplete(
+ in_flight_write_frame_type_,
+ in_flight_write_frame_size_);
+ }
+
+ // Cleanup the write which just completed.
+ in_flight_write_.reset();
+ in_flight_write_frame_type_ = DATA;
+ in_flight_write_frame_size_ = 0;
+ in_flight_write_stream_.reset();
+ }
+ }
+
+ write_state_ = WRITE_STATE_DO_WRITE;
+ return OK;
+}
+
+void SpdySession::DcheckGoingAway() const {
+ DCHECK_GE(availability_state_, STATE_GOING_AWAY);
+ if (DCHECK_IS_ON()) {
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ DCHECK(pending_create_stream_queues_[i].empty());
+ }
+ }
+ DCHECK(pending_stream_request_completions_.empty());
+ DCHECK(created_streams_.empty());
+}
+
+void SpdySession::DcheckClosed() const {
+ DcheckGoingAway();
+ DCHECK_EQ(availability_state_, STATE_CLOSED);
+ DCHECK_LT(error_on_close_, ERR_IO_PENDING);
+ DCHECK(active_streams_.empty());
+ DCHECK(unclaimed_pushed_streams_.empty());
+ DCHECK(write_queue_.IsEmpty());
+}
+
+void SpdySession::StartGoingAway(SpdyStreamId last_good_stream_id,
+ Error status) {
+ DCHECK_GE(availability_state_, STATE_GOING_AWAY);
+
+ // The loops below are carefully written to avoid reentrancy problems.
+ //
+ // TODO(akalin): Any of the functions below can cause |this| to be
+ // deleted, so handle that below (and add tests for it).
+
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ PendingStreamRequestQueue queue;
+ queue.swap(pending_create_stream_queues_[i]);
+ for (PendingStreamRequestQueue::const_iterator it = queue.begin();
+ it != queue.end(); ++it) {
+ CHECK(*it);
+ (*it)->OnRequestCompleteFailure(ERR_ABORTED);
+ }
+ }
+
+ PendingStreamRequestCompletionSet pending_completions;
+ pending_completions.swap(pending_stream_request_completions_);
+ for (PendingStreamRequestCompletionSet::const_iterator it =
+ pending_completions.begin();
+ it != pending_completions.end(); ++it) {
+ (*it)->OnRequestCompleteFailure(ERR_ABORTED);
+ }
+
+ while (true) {
+ ActiveStreamMap::iterator it =
+ active_streams_.lower_bound(last_good_stream_id + 1);
+ if (it == active_streams_.end())
+ break;
+ LogAbandonedActiveStream(it, status);
+ CloseActiveStreamIterator(it, status);
+ }
+
+ while (!created_streams_.empty()) {
+ CreatedStreamSet::iterator it = created_streams_.begin();
+ LogAbandonedStream(*it, status);
+ CloseCreatedStreamIterator(it, status);
+ }
+
+ write_queue_.RemovePendingWritesForStreamsAfter(last_good_stream_id);
+
+ DcheckGoingAway();
+}
+
+void SpdySession::MaybeFinishGoingAway() {
+ DcheckGoingAway();
+ if (active_streams_.empty() && availability_state_ != STATE_CLOSED) {
+ CloseSessionResult result =
+ DoCloseSession(ERR_CONNECTION_CLOSED, "Finished going away");
+ DCHECK_NE(result, SESSION_ALREADY_CLOSED);
+ }
+}
+
+SpdySession::CloseSessionResult SpdySession::DoCloseSession(
+ Error err,
+ const std::string& description) {
+ DCHECK_LT(err, ERR_IO_PENDING);
+
+ if (availability_state_ == STATE_CLOSED)
+ return SESSION_ALREADY_CLOSED;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_CLOSE,
+ base::Bind(&NetLogSpdySessionCloseCallback, err, &description));
+
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Net.SpdySession.ClosedOnError", -err);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySession.BytesRead.OtherErrors",
+ total_bytes_received_, 1, 100000000, 50);
+
+ // |pool_| will be NULL when |InitializeWithSocket()| is in the
+ // call stack.
+ if (pool_ && availability_state_ != STATE_GOING_AWAY)
+ pool_->MakeSessionUnavailable(GetWeakPtr());
+
+ availability_state_ = STATE_CLOSED;
+ error_on_close_ = err;
+
+ StartGoingAway(0, err);
+ write_queue_.Clear();
+
+ DcheckClosed();
+
+ if (in_io_loop_)
+ return SESSION_CLOSED_BUT_NOT_REMOVED;
+
+ RemoveFromPool();
+ return SESSION_CLOSED_AND_REMOVED;
+}
+
+void SpdySession::RemoveFromPool() {
+ DcheckClosed();
+ CHECK(pool_);
+
+ SpdySessionPool* pool = pool_;
+ pool_ = NULL;
+ pool->RemoveUnavailableSession(GetWeakPtr());
+}
+
+void SpdySession::LogAbandonedStream(SpdyStream* stream, Error status) {
+ DCHECK(stream);
+ std::string description = base::StringPrintf(
+ "ABANDONED (stream_id=%d): ", stream->stream_id()) +
+ stream->url().spec();
+ stream->LogStreamError(status, description);
+ // We don't increment the streams abandoned counter here. If the
+ // stream isn't active (i.e., it hasn't written anything to the wire
+ // yet) then it's as if it never existed. If it is active, then
+ // LogAbandonedActiveStream() will increment the counters.
+}
+
+void SpdySession::LogAbandonedActiveStream(ActiveStreamMap::const_iterator it,
+ Error status) {
+ DCHECK_GT(it->first, 0u);
+ LogAbandonedStream(it->second.stream, status);
+ ++streams_abandoned_count_;
+ base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+ abandoned_streams.Increment();
+ if (it->second.stream->type() == SPDY_PUSH_STREAM &&
+ unclaimed_pushed_streams_.find(it->second.stream->url()) !=
+ unclaimed_pushed_streams_.end()) {
+ base::StatsCounter abandoned_push_streams("spdy.abandoned_push_streams");
+ abandoned_push_streams.Increment();
+ }
+}
+
+int SpdySession::GetNewStreamId() {
+ int id = stream_hi_water_mark_;
+ stream_hi_water_mark_ += 2;
+ if (stream_hi_water_mark_ > 0x7fff)
+ stream_hi_water_mark_ = 1;
+ return id;
+}
+
+void SpdySession::CloseSessionOnError(Error err,
+ const std::string& description) {
+ // We may be called from anywhere, so we can't expect a particular
+ // return value.
+ ignore_result(DoCloseSession(err, description));
+}
+
+base::Value* SpdySession::GetInfoAsValue() const {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+
+ dict->SetInteger("source_id", net_log_.source().id);
+
+ dict->SetString("host_port_pair", host_port_pair().ToString());
+ if (!pooled_aliases_.empty()) {
+ base::ListValue* alias_list = new base::ListValue();
+ for (std::set<SpdySessionKey>::const_iterator it =
+ pooled_aliases_.begin();
+ it != pooled_aliases_.end(); it++) {
+ alias_list->Append(new base::StringValue(
+ it->host_port_pair().ToString()));
+ }
+ dict->Set("aliases", alias_list);
+ }
+ dict->SetString("proxy", host_port_proxy_pair().second.ToURI());
+
+ dict->SetInteger("active_streams", active_streams_.size());
+
+ dict->SetInteger("unclaimed_pushed_streams",
+ unclaimed_pushed_streams_.size());
+
+ dict->SetBoolean("is_secure", is_secure_);
+
+ dict->SetString("protocol_negotiated",
+ SSLClientSocket::NextProtoToString(
+ connection_->socket()->GetNegotiatedProtocol()));
+
+ dict->SetInteger("error", error_on_close_);
+ dict->SetInteger("max_concurrent_streams", max_concurrent_streams_);
+
+ dict->SetInteger("streams_initiated_count", streams_initiated_count_);
+ dict->SetInteger("streams_pushed_count", streams_pushed_count_);
+ dict->SetInteger("streams_pushed_and_claimed_count",
+ streams_pushed_and_claimed_count_);
+ dict->SetInteger("streams_abandoned_count", streams_abandoned_count_);
+ DCHECK(buffered_spdy_framer_.get());
+ dict->SetInteger("frames_received", buffered_spdy_framer_->frames_received());
+
+ dict->SetBoolean("sent_settings", sent_settings_);
+ dict->SetBoolean("received_settings", received_settings_);
+
+ dict->SetInteger("send_window_size", session_send_window_size_);
+ dict->SetInteger("recv_window_size", session_recv_window_size_);
+ dict->SetInteger("unacked_recv_window_bytes",
+ session_unacked_recv_window_bytes_);
+ return dict;
+}
+
+bool SpdySession::IsReused() const {
+ return buffered_spdy_framer_->frames_received() > 0;
+}
+
+bool SpdySession::GetLoadTimingInfo(SpdyStreamId stream_id,
+ LoadTimingInfo* load_timing_info) const {
+ return connection_->GetLoadTimingInfo(stream_id != kFirstStreamId,
+ load_timing_info);
+}
+
+int SpdySession::GetPeerAddress(IPEndPoint* address) const {
+ int rv = ERR_SOCKET_NOT_CONNECTED;
+ if (connection_->socket()) {
+ rv = connection_->socket()->GetPeerAddress(address);
+ }
+
+ UMA_HISTOGRAM_BOOLEAN("Net.SpdySessionSocketNotConnectedGetPeerAddress",
+ rv == ERR_SOCKET_NOT_CONNECTED);
+
+ return rv;
+}
+
+int SpdySession::GetLocalAddress(IPEndPoint* address) const {
+ int rv = ERR_SOCKET_NOT_CONNECTED;
+ if (connection_->socket()) {
+ rv = connection_->socket()->GetLocalAddress(address);
+ }
+
+ UMA_HISTOGRAM_BOOLEAN("Net.SpdySessionSocketNotConnectedGetLocalAddress",
+ rv == ERR_SOCKET_NOT_CONNECTED);
+
+ return rv;
+}
+
+void SpdySession::EnqueueSessionWrite(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyFrame> frame) {
+ DCHECK(frame_type == RST_STREAM ||
+ frame_type == SETTINGS ||
+ frame_type == WINDOW_UPDATE ||
+ frame_type == PING);
+ EnqueueWrite(
+ priority, frame_type,
+ scoped_ptr<SpdyBufferProducer>(
+ new SimpleBufferProducer(
+ scoped_ptr<SpdyBuffer>(new SpdyBuffer(frame.Pass())))),
+ base::WeakPtr<SpdyStream>());
+}
+
+void SpdySession::EnqueueWrite(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> producer,
+ const base::WeakPtr<SpdyStream>& stream) {
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ bool was_idle = write_queue_.IsEmpty();
+ write_queue_.Enqueue(priority, frame_type, producer.Pass(), stream);
+ if (write_state_ == WRITE_STATE_IDLE) {
+ DCHECK(was_idle);
+ DCHECK(!in_flight_write_);
+ write_state_ = WRITE_STATE_DO_WRITE;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::PumpWriteLoop,
+ weak_factory_.GetWeakPtr(), WRITE_STATE_DO_WRITE, OK));
+ }
+}
+
+void SpdySession::InsertCreatedStream(scoped_ptr<SpdyStream> stream) {
+ DCHECK_EQ(stream->stream_id(), 0u);
+ DCHECK(created_streams_.find(stream.get()) == created_streams_.end());
+ created_streams_.insert(stream.release());
+}
+
+scoped_ptr<SpdyStream> SpdySession::ActivateCreatedStream(SpdyStream* stream) {
+ DCHECK_EQ(stream->stream_id(), 0u);
+ DCHECK(created_streams_.find(stream) != created_streams_.end());
+ stream->set_stream_id(GetNewStreamId());
+ scoped_ptr<SpdyStream> owned_stream(stream);
+ created_streams_.erase(stream);
+ return owned_stream.Pass();
+}
+
+void SpdySession::InsertActivatedStream(scoped_ptr<SpdyStream> stream) {
+ SpdyStreamId stream_id = stream->stream_id();
+ DCHECK_NE(stream_id, 0u);
+ std::pair<ActiveStreamMap::iterator, bool> result =
+ active_streams_.insert(
+ std::make_pair(stream_id, ActiveStreamInfo(stream.get())));
+ if (result.second) {
+ ignore_result(stream.release());
+ } else {
+ NOTREACHED();
+ }
+}
+
+void SpdySession::DeleteStream(scoped_ptr<SpdyStream> stream, int status) {
+ if (in_flight_write_stream_.get() == stream.get()) {
+ // If we're deleting the stream for the in-flight write, we still
+ // need to let the write complete, so we clear
+ // |in_flight_write_stream_| and let the write finish on its own
+ // without notifying |in_flight_write_stream_|.
+ in_flight_write_stream_.reset();
+ }
+
+ write_queue_.RemovePendingWritesForStream(stream->GetWeakPtr());
+
+ // |stream->OnClose()| may end up closing |this|, so detect that.
+ base::WeakPtr<SpdySession> weak_this = GetWeakPtr();
+
+ stream->OnClose(status);
+
+ if (!weak_this)
+ return;
+
+ switch (availability_state_) {
+ case STATE_AVAILABLE:
+ ProcessPendingStreamRequests();
+ break;
+ case STATE_GOING_AWAY:
+ DcheckGoingAway();
+ MaybeFinishGoingAway();
+ break;
+ case STATE_CLOSED:
+ // Do nothing.
+ break;
+ }
+}
+
+base::WeakPtr<SpdyStream> SpdySession::GetActivePushStream(const GURL& url) {
+ base::StatsCounter used_push_streams("spdy.claimed_push_streams");
+
+ PushedStreamMap::iterator unclaimed_it = unclaimed_pushed_streams_.find(url);
+ if (unclaimed_it == unclaimed_pushed_streams_.end())
+ return base::WeakPtr<SpdyStream>();
+
+ SpdyStreamId stream_id = unclaimed_it->second.stream_id;
+ unclaimed_pushed_streams_.erase(unclaimed_it);
+
+ ActiveStreamMap::iterator active_it = active_streams_.find(stream_id);
+ if (active_it == active_streams_.end()) {
+ NOTREACHED();
+ return base::WeakPtr<SpdyStream>();
+ }
+
+ net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM);
+ used_push_streams.Increment();
+ return active_it->second.stream->GetWeakPtr();
+}
+
+bool SpdySession::GetSSLInfo(SSLInfo* ssl_info,
+ bool* was_npn_negotiated,
+ NextProto* protocol_negotiated) {
+ *was_npn_negotiated = connection_->socket()->WasNpnNegotiated();
+ *protocol_negotiated = connection_->socket()->GetNegotiatedProtocol();
+ return connection_->socket()->GetSSLInfo(ssl_info);
+}
+
+bool SpdySession::GetSSLCertRequestInfo(
+ SSLCertRequestInfo* cert_request_info) {
+ if (!is_secure_)
+ return false;
+ GetSSLClientSocket()->GetSSLCertRequestInfo(cert_request_info);
+ return true;
+}
+
+ServerBoundCertService* SpdySession::GetServerBoundCertService() const {
+ if (!is_secure_)
+ return NULL;
+ return GetSSLClientSocket()->GetServerBoundCertService();
+}
+
+void SpdySession::OnError(SpdyFramer::SpdyError error_code) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ RecordProtocolErrorHistogram(
+ static_cast<SpdyProtocolErrorDetails>(error_code));
+ std::string description = base::StringPrintf(
+ "SPDY_ERROR error_code: %d.", error_code);
+ CloseSessionResult result =
+ DoCloseSession(ERR_SPDY_PROTOCOL_ERROR, description);
+ DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED);
+}
+
+void SpdySession::OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ // We still want to send a frame to reset the stream even if we
+ // don't know anything about it.
+ EnqueueResetStreamFrame(
+ stream_id, IDLE, RST_STREAM_PROTOCOL_ERROR, description);
+ return;
+ }
+
+ ResetStreamIterator(it, RST_STREAM_PROTOCOL_ERROR, description);
+}
+
+void SpdySession::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ DCHECK_LT(len, 1u << 24);
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_DATA,
+ base::Bind(&NetLogSpdyDataCallback, stream_id, len, fin));
+ }
+
+ // Build the buffer as early as possible so that we go through the
+ // session flow control checks and update
+ // |unacked_recv_window_bytes_| properly even when the stream is
+ // inactive (since the other side has still reduced its session send
+ // window).
+ scoped_ptr<SpdyBuffer> buffer;
+ if (data) {
+ DCHECK_GT(len, 0u);
+ buffer.reset(new SpdyBuffer(data, len));
+
+ if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ DecreaseRecvWindowSize(static_cast<int32>(len));
+ buffer->AddConsumeCallback(
+ base::Bind(&SpdySession::OnReadBufferConsumed,
+ weak_factory_.GetWeakPtr()));
+ }
+ } else {
+ DCHECK_EQ(len, 0u);
+ }
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+
+ // By the time data comes in, the stream may already be inactive.
+ if (it == active_streams_.end())
+ return;
+
+ SpdyStream* stream = it->second.stream;
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ if (it->second.waiting_for_syn_reply) {
+ const std::string& error = "Data received before SYN_REPLY.";
+ stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR, error);
+ ResetStreamIterator(it, RST_STREAM_PROTOCOL_ERROR, error);
+ return;
+ }
+
+ stream->OnDataReceived(buffer.Pass());
+}
+
+void SpdySession::OnSettings(bool clear_persisted) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ if (clear_persisted)
+ http_server_properties_->ClearSpdySettings(host_port_pair());
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_SETTINGS,
+ base::Bind(&NetLogSpdySettingsCallback, host_port_pair(),
+ clear_persisted));
+ }
+}
+
+void SpdySession::OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ HandleSetting(id, value);
+ http_server_properties_->SetSpdySetting(
+ host_port_pair(),
+ id,
+ static_cast<SpdySettingsFlags>(flags),
+ value);
+ received_settings_ = true;
+
+ // Log the setting.
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
+ base::Bind(&NetLogSpdySettingCallback,
+ id, static_cast<SpdySettingsFlags>(flags), value));
+}
+
+void SpdySession::OnSendCompressedFrame(
+ SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) {
+ if (type != SYN_STREAM)
+ return;
+
+ DCHECK(buffered_spdy_framer_.get());
+ size_t compressed_len =
+ frame_len - buffered_spdy_framer_->GetSynStreamMinimumSize();
+
+ if (payload_len) {
+ // Make sure we avoid early decimal truncation.
+ int compression_pct = 100 - (100 * compressed_len) / payload_len;
+ UMA_HISTOGRAM_PERCENTAGE("Net.SpdySynStreamCompressionPercentage",
+ compression_pct);
+ }
+}
+
+int SpdySession::OnInitialResponseHeadersReceived(
+ const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time,
+ SpdyStream* stream) {
+ CHECK(in_io_loop_);
+ SpdyStreamId stream_id = stream->stream_id();
+ // May invalidate |stream|.
+ int rv = stream->OnInitialResponseHeadersReceived(
+ response_headers, response_time, recv_first_byte_time);
+ if (rv < 0) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(active_streams_.find(stream_id) == active_streams_.end());
+ }
+ return rv;
+}
+
+void SpdySession::OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ base::Time response_time = base::Time::Now();
+ base::TimeTicks recv_first_byte_time = time_func_();
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, unidirectional,
+ stream_id, associated_stream_id));
+ }
+
+ // Server-initiated streams should have even sequence numbers.
+ if ((stream_id & 0x1) != 0) {
+ LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id;
+ return;
+ }
+
+ if (IsStreamActive(stream_id)) {
+ LOG(WARNING) << "Received OnSyn for active stream " << stream_id;
+ return;
+ }
+
+ RequestPriority request_priority =
+ ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion());
+
+ if (availability_state_ == STATE_GOING_AWAY) {
+ // TODO(akalin): This behavior isn't in the SPDY spec, although it
+ // probably should be.
+ EnqueueResetStreamFrame(stream_id, request_priority,
+ RST_STREAM_REFUSED_STREAM,
+ "OnSyn received when going away");
+ return;
+ }
+
+ if (associated_stream_id == 0) {
+ std::string description = base::StringPrintf(
+ "Received invalid OnSyn associated stream id %d for stream %d",
+ associated_stream_id, stream_id);
+ EnqueueResetStreamFrame(stream_id, request_priority,
+ RST_STREAM_REFUSED_STREAM, description);
+ return;
+ }
+
+ streams_pushed_count_++;
+
+ // TODO(mbelshe): DCHECK that this is a GET method?
+
+ // Verify that the response had a URL for us.
+ GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true);
+ if (!gurl.is_valid()) {
+ EnqueueResetStreamFrame(
+ stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR,
+ "Pushed stream url was invalid: " + gurl.spec());
+ return;
+ }
+
+ // Verify we have a valid stream association.
+ ActiveStreamMap::iterator associated_it =
+ active_streams_.find(associated_stream_id);
+ if (associated_it == active_streams_.end()) {
+ EnqueueResetStreamFrame(
+ stream_id, request_priority, RST_STREAM_INVALID_STREAM,
+ base::StringPrintf(
+ "Received OnSyn with inactive associated stream %d",
+ associated_stream_id));
+ return;
+ }
+
+ // Check that the SYN advertises the same origin as its associated stream.
+ // Bypass this check if and only if this session is with a SPDY proxy that
+ // is trusted explicitly via the --trusted-spdy-proxy switch.
+ if (trusted_spdy_proxy_.Equals(host_port_pair())) {
+ // Disallow pushing of HTTPS content.
+ if (gurl.SchemeIs("https")) {
+ EnqueueResetStreamFrame(
+ stream_id, request_priority, RST_STREAM_REFUSED_STREAM,
+ base::StringPrintf(
+ "Rejected push of Cross Origin HTTPS content %d",
+ associated_stream_id));
+ }
+ } else {
+ GURL associated_url(associated_it->second.stream->GetUrlFromHeaders());
+ if (associated_url.GetOrigin() != gurl.GetOrigin()) {
+ EnqueueResetStreamFrame(
+ stream_id, request_priority, RST_STREAM_REFUSED_STREAM,
+ base::StringPrintf(
+ "Rejected Cross Origin Push Stream %d",
+ associated_stream_id));
+ return;
+ }
+ }
+
+ // There should not be an existing pushed stream with the same path.
+ PushedStreamMap::iterator pushed_it =
+ unclaimed_pushed_streams_.lower_bound(gurl);
+ if (pushed_it != unclaimed_pushed_streams_.end() &&
+ pushed_it->first == gurl) {
+ EnqueueResetStreamFrame(
+ stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR,
+ "Received duplicate pushed stream with url: " +
+ gurl.spec());
+ return;
+ }
+
+ scoped_ptr<SpdyStream> stream(
+ new SpdyStream(SPDY_PUSH_STREAM, GetWeakPtr(), gurl,
+ request_priority,
+ stream_initial_send_window_size_,
+ stream_initial_recv_window_size_,
+ net_log_));
+ stream->set_stream_id(stream_id);
+
+ DeleteExpiredPushedStreams();
+ PushedStreamMap::iterator inserted_pushed_it =
+ unclaimed_pushed_streams_.insert(
+ pushed_it,
+ std::make_pair(gurl, PushedStreamInfo(stream_id, time_func_())));
+ DCHECK(inserted_pushed_it != pushed_it);
+
+ InsertActivatedStream(stream.Pass());
+
+ ActiveStreamMap::iterator active_it = active_streams_.find(stream_id);
+ if (active_it == active_streams_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ // Parse the headers.
+ if (OnInitialResponseHeadersReceived(
+ headers, response_time,
+ recv_first_byte_time, active_it->second.stream) != OK)
+ return;
+
+ base::StatsCounter push_requests("spdy.pushed_streams");
+ push_requests.Increment();
+}
+
+void SpdySession::DeleteExpiredPushedStreams() {
+ if (unclaimed_pushed_streams_.empty())
+ return;
+
+ // Check that adequate time has elapsed since the last sweep.
+ if (time_func_() < next_unclaimed_push_stream_sweep_time_)
+ return;
+
+ // Gather old streams to delete.
+ base::TimeTicks minimum_freshness = time_func_() -
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+ std::vector<SpdyStreamId> streams_to_close;
+ for (PushedStreamMap::iterator it = unclaimed_pushed_streams_.begin();
+ it != unclaimed_pushed_streams_.end(); ++it) {
+ if (minimum_freshness > it->second.creation_time)
+ streams_to_close.push_back(it->second.stream_id);
+ }
+
+ for (std::vector<SpdyStreamId>::const_iterator to_close_it =
+ streams_to_close.begin();
+ to_close_it != streams_to_close.end(); ++to_close_it) {
+ ActiveStreamMap::iterator active_it = active_streams_.find(*to_close_it);
+ if (active_it == active_streams_.end())
+ continue;
+
+ LogAbandonedActiveStream(active_it, ERR_INVALID_SPDY_STREAM);
+ // CloseActiveStreamIterator() will remove the stream from
+ // |unclaimed_pushed_streams_|.
+ CloseActiveStreamIterator(active_it, ERR_INVALID_SPDY_STREAM);
+ }
+
+ next_unclaimed_push_stream_sweep_time_ = time_func_() +
+ base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+}
+
+void SpdySession::OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ base::Time response_time = base::Time::Now();
+ base::TimeTicks recv_first_byte_time = time_func_();
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SYN_REPLY,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, false, // not unidirectional
+ stream_id, 0));
+ }
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ // NOTE: it may just be that the stream was cancelled.
+ return;
+ }
+
+ SpdyStream* stream = it->second.stream;
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ if (!it->second.waiting_for_syn_reply) {
+ const std::string& error =
+ "Received duplicate SYN_REPLY for stream.";
+ stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR, error);
+ ResetStreamIterator(it, RST_STREAM_STREAM_IN_USE, error);
+ return;
+ }
+ it->second.waiting_for_syn_reply = false;
+
+ ignore_result(OnInitialResponseHeadersReceived(
+ headers, response_time, recv_first_byte_time, stream));
+}
+
+void SpdySession::OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECV_HEADERS,
+ base::Bind(&NetLogSpdySynCallback,
+ &headers, fin, /*unidirectional=*/false,
+ stream_id, 0));
+ }
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id;
+ return;
+ }
+
+ SpdyStream* stream = it->second.stream;
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ int rv = stream->OnAdditionalResponseHeadersReceived(headers);
+ if (rv < 0) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(active_streams_.find(stream_id) == active_streams_.end());
+ }
+}
+
+void SpdySession::OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ std::string description;
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RST_STREAM,
+ base::Bind(&NetLogSpdyRstCallback,
+ stream_id, status, &description));
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+ if (it == active_streams_.end()) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received RST for invalid stream" << stream_id;
+ return;
+ }
+
+ CHECK_EQ(it->second.stream->stream_id(), stream_id);
+
+ if (status == 0) {
+ it->second.stream->OnDataReceived(scoped_ptr<SpdyBuffer>());
+ } else if (status == RST_STREAM_REFUSED_STREAM) {
+ CloseActiveStreamIterator(it, ERR_SPDY_SERVER_REFUSED_STREAM);
+ } else {
+ RecordProtocolErrorHistogram(
+ PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM);
+ it->second.stream->LogStreamError(
+ ERR_SPDY_PROTOCOL_ERROR,
+ base::StringPrintf("SPDY stream closed with status: %d", status));
+ // TODO(mbelshe): Map from Spdy-protocol errors to something sensical.
+ // For now, it doesn't matter much - it is a protocol error.
+ CloseActiveStreamIterator(it, ERR_SPDY_PROTOCOL_ERROR);
+ }
+}
+
+void SpdySession::OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_GOAWAY,
+ base::Bind(&NetLogSpdyGoAwayCallback,
+ last_accepted_stream_id,
+ active_streams_.size(),
+ unclaimed_pushed_streams_.size(),
+ status));
+ if (availability_state_ < STATE_GOING_AWAY) {
+ availability_state_ = STATE_GOING_AWAY;
+ // |pool_| will be NULL when |InitializeWithSocket()| is in the
+ // call stack.
+ if (pool_)
+ pool_->MakeSessionUnavailable(GetWeakPtr());
+ }
+ StartGoingAway(last_accepted_stream_id, ERR_ABORTED);
+ // This is to handle the case when we already don't have any active
+ // streams (i.e., StartGoingAway() did nothing). Otherwise, we have
+ // active streams and so the last one being closed will finish the
+ // going away process (see DeleteStream()).
+ MaybeFinishGoingAway();
+}
+
+void SpdySession::OnPing(uint32 unique_id) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PING,
+ base::Bind(&NetLogSpdyPingCallback, unique_id, "received"));
+
+ // Send response to a PING from server.
+ if (unique_id % 2 == 0) {
+ WritePingFrame(unique_id);
+ return;
+ }
+
+ --pings_in_flight_;
+ if (pings_in_flight_ < 0) {
+ RecordProtocolErrorHistogram(PROTOCOL_ERROR_UNEXPECTED_PING);
+ CloseSessionResult result =
+ DoCloseSession(ERR_SPDY_PROTOCOL_ERROR, "pings_in_flight_ is < 0.");
+ DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ pings_in_flight_ = 0;
+ return;
+ }
+
+ if (pings_in_flight_ > 0)
+ return;
+
+ // We will record RTT in histogram when there are no more client sent
+ // pings_in_flight_.
+ RecordPingRTTHistogram(time_func_() - last_ping_sent_time_);
+}
+
+void SpdySession::OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) {
+ CHECK(in_io_loop_);
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ DCHECK_LE(delta_window_size, static_cast<uint32>(kint32max));
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_RECEIVED_WINDOW_UPDATE_FRAME,
+ base::Bind(&NetLogSpdyWindowUpdateFrameCallback,
+ stream_id, delta_window_size));
+
+ if (stream_id == kSessionFlowControlStreamId) {
+ // WINDOW_UPDATE for the session.
+ if (flow_control_state_ < FLOW_CONTROL_STREAM_AND_SESSION) {
+ LOG(WARNING) << "Received WINDOW_UPDATE for session when "
+ << "session flow control is not turned on";
+ // TODO(akalin): Record an error and close the session.
+ return;
+ }
+
+ if (delta_window_size < 1u) {
+ RecordProtocolErrorHistogram(PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE);
+ CloseSessionResult result = DoCloseSession(
+ ERR_SPDY_PROTOCOL_ERROR,
+ "Received WINDOW_UPDATE with an invalid delta_window_size " +
+ base::UintToString(delta_window_size));
+ DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ return;
+ }
+
+ IncreaseSendWindowSize(static_cast<int32>(delta_window_size));
+ } else {
+ // WINDOW_UPDATE for a stream.
+ if (flow_control_state_ < FLOW_CONTROL_STREAM) {
+ // TODO(akalin): Record an error and close the session.
+ LOG(WARNING) << "Received WINDOW_UPDATE for stream " << stream_id
+ << " when flow control is not turned on";
+ return;
+ }
+
+ ActiveStreamMap::iterator it = active_streams_.find(stream_id);
+
+ if (it == active_streams_.end()) {
+ // NOTE: it may just be that the stream was cancelled.
+ LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id;
+ return;
+ }
+
+ SpdyStream* stream = it->second.stream;
+ CHECK_EQ(stream->stream_id(), stream_id);
+
+ if (delta_window_size < 1u) {
+ ResetStreamIterator(it,
+ RST_STREAM_FLOW_CONTROL_ERROR,
+ base::StringPrintf(
+ "Received WINDOW_UPDATE with an invalid "
+ "delta_window_size %ud", delta_window_size));
+ return;
+ }
+
+ CHECK_EQ(it->second.stream->stream_id(), stream_id);
+ it->second.stream->IncreaseSendWindowSize(
+ static_cast<int32>(delta_window_size));
+ }
+}
+
+void SpdySession::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) {
+ // TODO(akalin): Handle PUSH_PROMISE frames.
+}
+
+void SpdySession::SendStreamWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) {
+ CHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM);
+ ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
+ CHECK(it != active_streams_.end());
+ CHECK_EQ(it->second.stream->stream_id(), stream_id);
+ SendWindowUpdateFrame(
+ stream_id, delta_window_size, it->second.stream->priority());
+}
+
+void SpdySession::SendInitialData() {
+ DCHECK(enable_sending_initial_data_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ if (send_connection_header_prefix_) {
+ DCHECK_EQ(protocol_, kProtoHTTP2Draft04);
+ scoped_ptr<SpdyFrame> connection_header_prefix_frame(
+ new SpdyFrame(const_cast<char*>(kHttp2ConnectionHeaderPrefix),
+ kHttp2ConnectionHeaderPrefixSize,
+ false /* take_ownership */));
+ // Count the prefix as part of the subsequent SETTINGS frame.
+ EnqueueSessionWrite(HIGHEST, SETTINGS,
+ connection_header_prefix_frame.Pass());
+ }
+
+ // First, notify the server about the settings they should use when
+ // communicating with us.
+ SettingsMap settings_map;
+ // Create a new settings frame notifying the server of our
+ // max concurrent streams and initial window size.
+ settings_map[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+ if (flow_control_state_ >= FLOW_CONTROL_STREAM &&
+ stream_initial_recv_window_size_ != kSpdyStreamInitialWindowSize) {
+ settings_map[SETTINGS_INITIAL_WINDOW_SIZE] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE,
+ stream_initial_recv_window_size_);
+ }
+ SendSettings(settings_map);
+
+ // Next, notify the server about our initial recv window size.
+ if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ // Bump up the receive window size to the real initial value. This
+ // has to go here since the WINDOW_UPDATE frame sent by
+ // IncreaseRecvWindowSize() call uses |buffered_spdy_framer_|.
+ DCHECK_GT(kDefaultInitialRecvWindowSize, session_recv_window_size_);
+ // This condition implies that |kDefaultInitialRecvWindowSize| -
+ // |session_recv_window_size_| doesn't overflow.
+ DCHECK_GT(session_recv_window_size_, 0);
+ IncreaseRecvWindowSize(
+ kDefaultInitialRecvWindowSize - session_recv_window_size_);
+ }
+
+ // Finally, notify the server about the settings they have
+ // previously told us to use when communicating with them (after
+ // applying them).
+ const SettingsMap& server_settings_map =
+ http_server_properties_->GetSpdySettings(host_port_pair());
+ if (server_settings_map.empty())
+ return;
+
+ SettingsMap::const_iterator it =
+ server_settings_map.find(SETTINGS_CURRENT_CWND);
+ uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
+
+ for (SettingsMap::const_iterator it = server_settings_map.begin();
+ it != server_settings_map.end(); ++it) {
+ const SpdySettingsIds new_id = it->first;
+ const uint32 new_val = it->second.second;
+ HandleSetting(new_id, new_val);
+ }
+
+ SendSettings(server_settings_map);
+}
+
+
+void SpdySession::SendSettings(const SettingsMap& settings) {
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
+ base::Bind(&NetLogSpdySendSettingsCallback, &settings));
+
+ // Create the SETTINGS frame and send it.
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> settings_frame(
+ buffered_spdy_framer_->CreateSettings(settings));
+ sent_settings_ = true;
+ EnqueueSessionWrite(HIGHEST, SETTINGS, settings_frame.Pass());
+}
+
+void SpdySession::HandleSetting(uint32 id, uint32 value) {
+ switch (id) {
+ case SETTINGS_MAX_CONCURRENT_STREAMS:
+ max_concurrent_streams_ = std::min(static_cast<size_t>(value),
+ kMaxConcurrentStreamLimit);
+ ProcessPendingStreamRequests();
+ break;
+ case SETTINGS_INITIAL_WINDOW_SIZE: {
+ if (flow_control_state_ < FLOW_CONTROL_STREAM) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_INITIAL_WINDOW_SIZE_NO_FLOW_CONTROL);
+ return;
+ }
+
+ if (value > static_cast<uint32>(kint32max)) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_INITIAL_WINDOW_SIZE_OUT_OF_RANGE,
+ NetLog::IntegerCallback("initial_window_size", value));
+ return;
+ }
+
+ // SETTINGS_INITIAL_WINDOW_SIZE updates initial_send_window_size_ only.
+ int32 delta_window_size =
+ static_cast<int32>(value) - stream_initial_send_window_size_;
+ stream_initial_send_window_size_ = static_cast<int32>(value);
+ UpdateStreamsSendWindowSize(delta_window_size);
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE,
+ NetLog::IntegerCallback("delta_window_size", delta_window_size));
+ break;
+ }
+ }
+}
+
+void SpdySession::UpdateStreamsSendWindowSize(int32 delta_window_size) {
+ DCHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM);
+ for (ActiveStreamMap::iterator it = active_streams_.begin();
+ it != active_streams_.end(); ++it) {
+ it->second.stream->AdjustSendWindowSize(delta_window_size);
+ }
+
+ for (CreatedStreamSet::const_iterator it = created_streams_.begin();
+ it != created_streams_.end(); it++) {
+ (*it)->AdjustSendWindowSize(delta_window_size);
+ }
+}
+
+void SpdySession::SendPrefacePingIfNoneInFlight() {
+ if (pings_in_flight_ || !enable_ping_based_connection_checking_)
+ return;
+
+ base::TimeTicks now = time_func_();
+ // If there is no activity in the session, then send a preface-PING.
+ if ((now - last_activity_time_) > connection_at_risk_of_loss_time_)
+ SendPrefacePing();
+}
+
+void SpdySession::SendPrefacePing() {
+ WritePingFrame(next_ping_id_);
+}
+
+void SpdySession::SendWindowUpdateFrame(SpdyStreamId stream_id,
+ uint32 delta_window_size,
+ RequestPriority priority) {
+ CHECK_GE(flow_control_state_, FLOW_CONTROL_STREAM);
+ ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
+ if (it != active_streams_.end()) {
+ CHECK_EQ(it->second.stream->stream_id(), stream_id);
+ } else {
+ CHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+ CHECK_EQ(stream_id, kSessionFlowControlStreamId);
+ }
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_SENT_WINDOW_UPDATE_FRAME,
+ base::Bind(&NetLogSpdyWindowUpdateFrameCallback,
+ stream_id, delta_window_size));
+
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> window_update_frame(
+ buffered_spdy_framer_->CreateWindowUpdate(stream_id, delta_window_size));
+ EnqueueSessionWrite(priority, WINDOW_UPDATE, window_update_frame.Pass());
+}
+
+void SpdySession::WritePingFrame(uint32 unique_id) {
+ DCHECK(buffered_spdy_framer_.get());
+ scoped_ptr<SpdyFrame> ping_frame(
+ buffered_spdy_framer_->CreatePingFrame(unique_id));
+ EnqueueSessionWrite(HIGHEST, PING, ping_frame.Pass());
+
+ if (net_log().IsLoggingAllEvents()) {
+ net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_PING,
+ base::Bind(&NetLogSpdyPingCallback, unique_id, "sent"));
+ }
+ if (unique_id % 2 != 0) {
+ next_ping_id_ += 2;
+ ++pings_in_flight_;
+ PlanToCheckPingStatus();
+ last_ping_sent_time_ = time_func_();
+ }
+}
+
+void SpdySession::PlanToCheckPingStatus() {
+ if (check_ping_status_pending_)
+ return;
+
+ check_ping_status_pending_ = true;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(),
+ time_func_()), hung_interval_);
+}
+
+void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) {
+ CHECK(!in_io_loop_);
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+
+ // Check if we got a response back for all PINGs we had sent.
+ if (pings_in_flight_ == 0) {
+ check_ping_status_pending_ = false;
+ return;
+ }
+
+ DCHECK(check_ping_status_pending_);
+
+ base::TimeTicks now = time_func_();
+ base::TimeDelta delay = hung_interval_ - (now - last_activity_time_);
+
+ if (delay.InMilliseconds() < 0 || last_activity_time_ < last_check_time) {
+ // Track all failed PING messages in a separate bucket.
+ const base::TimeDelta kFailedPing =
+ base::TimeDelta::FromInternalValue(INT_MAX);
+ RecordPingRTTHistogram(kFailedPing);
+ CloseSessionResult result =
+ DoCloseSession(ERR_SPDY_PING_FAILED, "Failed ping.");
+ DCHECK_EQ(result, SESSION_CLOSED_AND_REMOVED);
+ return;
+ }
+
+ // Check the status of connection after a delay.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(),
+ now),
+ delay);
+}
+
+void SpdySession::RecordPingRTTHistogram(base::TimeDelta duration) {
+ UMA_HISTOGRAM_TIMES("Net.SpdyPing.RTT", duration);
+}
+
+void SpdySession::RecordProtocolErrorHistogram(
+ SpdyProtocolErrorDetails details) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails2", details,
+ NUM_SPDY_PROTOCOL_ERROR_DETAILS);
+ if (EndsWith(host_port_pair().host(), "google.com", false)) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails_Google2", details,
+ NUM_SPDY_PROTOCOL_ERROR_DETAILS);
+ }
+}
+
+void SpdySession::RecordHistograms() {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession",
+ streams_initiated_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedPerSession",
+ streams_pushed_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedAndClaimedPerSession",
+ streams_pushed_and_claimed_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsAbandonedPerSession",
+ streams_abandoned_count_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsSent",
+ sent_settings_ ? 1 : 0, 2);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsReceived",
+ received_settings_ ? 1 : 0, 2);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamStallsPerSession",
+ stalled_streams_,
+ 0, 300, 50);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionsWithStalls",
+ stalled_streams_ > 0 ? 1 : 0, 2);
+
+ if (received_settings_) {
+ // Enumerate the saved settings, and set histograms for it.
+ const SettingsMap& settings_map =
+ http_server_properties_->GetSpdySettings(host_port_pair());
+
+ SettingsMap::const_iterator it;
+ for (it = settings_map.begin(); it != settings_map.end(); ++it) {
+ const SpdySettingsIds id = it->first;
+ const uint32 val = it->second.second;
+ switch (id) {
+ case SETTINGS_CURRENT_CWND:
+ // Record several different histograms to see if cwnd converges
+ // for larger volumes of data being sent.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd",
+ val, 1, 200, 100);
+ if (total_bytes_received_ > 10 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd10K",
+ val, 1, 200, 100);
+ if (total_bytes_received_ > 25 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd25K",
+ val, 1, 200, 100);
+ if (total_bytes_received_ > 50 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd50K",
+ val, 1, 200, 100);
+ if (total_bytes_received_ > 100 * 1024) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd100K",
+ val, 1, 200, 100);
+ }
+ }
+ }
+ }
+ break;
+ case SETTINGS_ROUND_TRIP_TIME:
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRTT",
+ val, 1, 1200, 100);
+ break;
+ case SETTINGS_DOWNLOAD_RETRANS_RATE:
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRetransRate",
+ val, 1, 100, 50);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void SpdySession::CompleteStreamRequest(SpdyStreamRequest* pending_request) {
+ CHECK(pending_request);
+
+ PendingStreamRequestCompletionSet::iterator it =
+ pending_stream_request_completions_.find(pending_request);
+
+ // Abort if the request has already been cancelled.
+ if (it == pending_stream_request_completions_.end())
+ return;
+
+ base::WeakPtr<SpdyStream> stream;
+ int rv = CreateStream(*pending_request, &stream);
+ pending_stream_request_completions_.erase(it);
+
+ if (rv == OK) {
+ DCHECK(stream.get());
+ pending_request->OnRequestCompleteSuccess(&stream);
+ } else {
+ DCHECK(!stream.get());
+ pending_request->OnRequestCompleteFailure(rv);
+ }
+}
+
+SSLClientSocket* SpdySession::GetSSLClientSocket() const {
+ if (!is_secure_)
+ return NULL;
+ SSLClientSocket* ssl_socket =
+ reinterpret_cast<SSLClientSocket*>(connection_->socket());
+ DCHECK(ssl_socket);
+ return ssl_socket;
+}
+
+void SpdySession::OnWriteBufferConsumed(
+ size_t frame_payload_size,
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source) {
+ // We can be called with |in_io_loop_| set if a write SpdyBuffer is
+ // deleted (e.g., a stream is closed due to incoming data).
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+
+ if (consume_source == SpdyBuffer::DISCARD) {
+ // If we're discarding a frame or part of it, increase the send
+ // window by the number of discarded bytes. (Although if we're
+ // discarding part of a frame, it's probably because of a write
+ // error and we'll be tearing down the session soon.)
+ size_t remaining_payload_bytes = std::min(consume_size, frame_payload_size);
+ DCHECK_GT(remaining_payload_bytes, 0u);
+ IncreaseSendWindowSize(static_cast<int32>(remaining_payload_bytes));
+ }
+ // For consumed bytes, the send window is increased when we receive
+ // a WINDOW_UPDATE frame.
+}
+
+void SpdySession::IncreaseSendWindowSize(int32 delta_window_size) {
+ // We can be called with |in_io_loop_| set if a SpdyBuffer is
+ // deleted (e.g., a stream is closed due to incoming data).
+
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+ DCHECK_GE(delta_window_size, 1);
+
+ // Check for overflow.
+ int32 max_delta_window_size = kint32max - session_send_window_size_;
+ if (delta_window_size > max_delta_window_size) {
+ RecordProtocolErrorHistogram(PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE);
+ CloseSessionResult result = DoCloseSession(
+ ERR_SPDY_PROTOCOL_ERROR,
+ "Received WINDOW_UPDATE [delta: " +
+ base::IntToString(delta_window_size) +
+ "] for session overflows session_send_window_size_ [current: " +
+ base::IntToString(session_send_window_size_) + "]");
+ DCHECK_NE(result, SESSION_ALREADY_CLOSED);
+ return;
+ }
+
+ session_send_window_size_ += delta_window_size;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_UPDATE_SEND_WINDOW,
+ base::Bind(&NetLogSpdySessionWindowUpdateCallback,
+ delta_window_size, session_send_window_size_));
+
+ DCHECK(!IsSendStalled());
+ ResumeSendStalledStreams();
+}
+
+void SpdySession::DecreaseSendWindowSize(int32 delta_window_size) {
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+
+ // We only call this method when sending a frame. Therefore,
+ // |delta_window_size| should be within the valid frame size range.
+ DCHECK_GE(delta_window_size, 1);
+ DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize);
+
+ // |send_window_size_| should have been at least |delta_window_size| for
+ // this call to happen.
+ DCHECK_GE(session_send_window_size_, delta_window_size);
+
+ session_send_window_size_ -= delta_window_size;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_UPDATE_SEND_WINDOW,
+ base::Bind(&NetLogSpdySessionWindowUpdateCallback,
+ -delta_window_size, session_send_window_size_));
+}
+
+void SpdySession::OnReadBufferConsumed(
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source) {
+ // We can be called with |in_io_loop_| set if a read SpdyBuffer is
+ // deleted (e.g., discarded by a SpdyReadQueue).
+
+ if (availability_state_ == STATE_CLOSED)
+ return;
+
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+ DCHECK_GE(consume_size, 1u);
+ DCHECK_LE(consume_size, static_cast<size_t>(kint32max));
+
+ IncreaseRecvWindowSize(static_cast<int32>(consume_size));
+}
+
+void SpdySession::IncreaseRecvWindowSize(int32 delta_window_size) {
+ DCHECK_NE(availability_state_, STATE_CLOSED);
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+ DCHECK_GE(session_unacked_recv_window_bytes_, 0);
+ DCHECK_GE(session_recv_window_size_, session_unacked_recv_window_bytes_);
+ DCHECK_GE(delta_window_size, 1);
+ // Check for overflow.
+ DCHECK_LE(delta_window_size, kint32max - session_recv_window_size_);
+
+ session_recv_window_size_ += delta_window_size;
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
+ base::Bind(&NetLogSpdySessionWindowUpdateCallback,
+ delta_window_size, session_recv_window_size_));
+
+ session_unacked_recv_window_bytes_ += delta_window_size;
+ if (session_unacked_recv_window_bytes_ > kSpdySessionInitialWindowSize / 2) {
+ SendWindowUpdateFrame(kSessionFlowControlStreamId,
+ session_unacked_recv_window_bytes_,
+ HIGHEST);
+ session_unacked_recv_window_bytes_ = 0;
+ }
+}
+
+void SpdySession::DecreaseRecvWindowSize(int32 delta_window_size) {
+ CHECK(in_io_loop_);
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+ DCHECK_GE(delta_window_size, 1);
+
+ // Since we never decrease the initial receive window size,
+ // |delta_window_size| should never cause |recv_window_size_| to go
+ // negative. If we do, the receive window isn't being respected.
+ if (delta_window_size > session_recv_window_size_) {
+ RecordProtocolErrorHistogram(PROTOCOL_ERROR_RECEIVE_WINDOW_VIOLATION);
+ CloseSessionResult result = DoCloseSession(
+ ERR_SPDY_PROTOCOL_ERROR,
+ "delta_window_size is " + base::IntToString(delta_window_size) +
+ " in DecreaseRecvWindowSize, which is larger than the receive " +
+ "window size of " + base::IntToString(session_recv_window_size_));
+ DCHECK_EQ(result, SESSION_CLOSED_BUT_NOT_REMOVED);
+ return;
+ }
+
+ session_recv_window_size_ -= delta_window_size;
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_UPDATE_RECV_WINDOW,
+ base::Bind(&NetLogSpdySessionWindowUpdateCallback,
+ -delta_window_size, session_recv_window_size_));
+}
+
+void SpdySession::QueueSendStalledStream(const SpdyStream& stream) {
+ DCHECK(stream.send_stalled_by_flow_control());
+ stream_send_unstall_queue_[stream.priority()].push_back(stream.stream_id());
+}
+
+namespace {
+
+// Helper function to return the total size of an array of objects
+// with .size() member functions.
+template <typename T, size_t N> size_t GetTotalSize(const T (&arr)[N]) {
+ size_t total_size = 0;
+ for (size_t i = 0; i < N; ++i) {
+ total_size += arr[i].size();
+ }
+ return total_size;
+}
+
+} // namespace
+
+void SpdySession::ResumeSendStalledStreams() {
+ DCHECK_EQ(flow_control_state_, FLOW_CONTROL_STREAM_AND_SESSION);
+
+ // We don't have to worry about new streams being queued, since
+ // doing so would cause IsSendStalled() to return true. But we do
+ // have to worry about streams being closed, as well as ourselves
+ // being closed.
+
+ while (availability_state_ != STATE_CLOSED && !IsSendStalled()) {
+ size_t old_size = 0;
+ if (DCHECK_IS_ON())
+ old_size = GetTotalSize(stream_send_unstall_queue_);
+
+ SpdyStreamId stream_id = PopStreamToPossiblyResume();
+ if (stream_id == 0)
+ break;
+ ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
+ // The stream may actually still be send-stalled after this (due
+ // to its own send window) but that's okay -- it'll then be
+ // resumed once its send window increases.
+ if (it != active_streams_.end())
+ it->second.stream->PossiblyResumeIfSendStalled();
+
+ // The size should decrease unless we got send-stalled again.
+ if (!IsSendStalled())
+ DCHECK_LT(GetTotalSize(stream_send_unstall_queue_), old_size);
+ }
+}
+
+SpdyStreamId SpdySession::PopStreamToPossiblyResume() {
+ for (int i = NUM_PRIORITIES - 1; i >= 0; --i) {
+ std::deque<SpdyStreamId>* queue = &stream_send_unstall_queue_[i];
+ if (!queue->empty()) {
+ SpdyStreamId stream_id = queue->front();
+ queue->pop_front();
+ return stream_id;
+ }
+ }
+ return 0;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_session.h b/chromium/net/spdy/spdy_session.h
new file mode 100644
index 00000000000..819db111b26
--- /dev/null
+++ b/chromium/net/spdy/spdy_session.h
@@ -0,0 +1,1143 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_H_
+#define NET_SPDY_SPDY_SESSION_H_
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_buffer.h"
+#include "net/spdy/spdy_credential_state.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_write_queue.h"
+#include "net/ssl/ssl_config_service.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// This is somewhat arbitrary and not really fixed, but it will always work
+// reasonably with ethernet. Chop the world into 2-packet chunks. This is
+// somewhat arbitrary, but is reasonably small and ensures that we elicit
+// ACKs quickly from TCP (because TCP tries to only ACK every other packet).
+const int kMss = 1430;
+// The 8 is the size of the SPDY frame header.
+const int kMaxSpdyFrameChunkSize = (2 * kMss) - 8;
+
+// Maximum number of concurrent streams we will create, unless the server
+// sends a SETTINGS frame with a different value.
+const size_t kInitialMaxConcurrentStreams = 100;
+
+// Specifies the maxiumum concurrent streams server could send (via push).
+const int kMaxConcurrentPushedStreams = 1000;
+
+// Specifies the maximum number of bytes to read synchronously before
+// yielding.
+const int kMaxReadBytesWithoutYielding = 32 * 1024;
+
+// The initial receive window size for both streams and sessions.
+const int32 kDefaultInitialRecvWindowSize = 10 * 1024 * 1024; // 10MB
+
+class BoundNetLog;
+struct LoadTimingInfo;
+class SpdyStream;
+class SSLInfo;
+
+// NOTE: There's an enum of the same name (also with numeric suffixes)
+// in histograms.xml.
+//
+// WARNING: DO NOT INSERT ENUMS INTO THIS LIST! Add only to the end.
+enum SpdyProtocolErrorDetails {
+ // SpdyFramer::SpdyErrors
+ SPDY_ERROR_NO_ERROR,
+ SPDY_ERROR_INVALID_CONTROL_FRAME,
+ SPDY_ERROR_CONTROL_PAYLOAD_TOO_LARGE,
+ SPDY_ERROR_ZLIB_INIT_FAILURE,
+ SPDY_ERROR_UNSUPPORTED_VERSION,
+ SPDY_ERROR_DECOMPRESS_FAILURE,
+ SPDY_ERROR_COMPRESS_FAILURE,
+ SPDY_ERROR_CREDENTIAL_FRAME_CORRUPT,
+ SPDY_ERROR_INVALID_DATA_FRAME_FLAGS,
+ SPDY_ERROR_INVALID_CONTROL_FRAME_FLAGS,
+ // SpdyRstStreamStatus
+ STATUS_CODE_INVALID,
+ STATUS_CODE_PROTOCOL_ERROR,
+ STATUS_CODE_INVALID_STREAM,
+ STATUS_CODE_REFUSED_STREAM,
+ STATUS_CODE_UNSUPPORTED_VERSION,
+ STATUS_CODE_CANCEL,
+ STATUS_CODE_INTERNAL_ERROR,
+ STATUS_CODE_FLOW_CONTROL_ERROR,
+ STATUS_CODE_STREAM_IN_USE,
+ STATUS_CODE_STREAM_ALREADY_CLOSED,
+ STATUS_CODE_INVALID_CREDENTIALS,
+ STATUS_CODE_FRAME_TOO_LARGE,
+ // SpdySession errors
+ PROTOCOL_ERROR_UNEXPECTED_PING,
+ PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM,
+ PROTOCOL_ERROR_SPDY_COMPRESSION_FAILURE,
+ PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION,
+ PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED,
+ PROTOCOL_ERROR_INVALID_WINDOW_UPDATE_SIZE,
+ PROTOCOL_ERROR_RECEIVE_WINDOW_VIOLATION,
+ NUM_SPDY_PROTOCOL_ERROR_DETAILS
+};
+
+COMPILE_ASSERT(STATUS_CODE_INVALID ==
+ static_cast<SpdyProtocolErrorDetails>(SpdyFramer::LAST_ERROR),
+ SpdyProtocolErrorDetails_SpdyErrors_mismatch);
+
+COMPILE_ASSERT(PROTOCOL_ERROR_UNEXPECTED_PING ==
+ static_cast<SpdyProtocolErrorDetails>(
+ RST_STREAM_NUM_STATUS_CODES + STATUS_CODE_INVALID),
+ SpdyProtocolErrorDetails_SpdyErrors_mismatch);
+
+// A helper class used to manage a request to create a stream.
+class NET_EXPORT_PRIVATE SpdyStreamRequest {
+ public:
+ SpdyStreamRequest();
+ // Calls CancelRequest().
+ ~SpdyStreamRequest();
+
+ // Starts the request to create a stream. If OK is returned, then
+ // ReleaseStream() may be called. If ERR_IO_PENDING is returned,
+ // then when the stream is created, |callback| will be called, at
+ // which point ReleaseStream() may be called. Otherwise, the stream
+ // is not created, an error is returned, and ReleaseStream() may not
+ // be called.
+ //
+ // If OK is returned, must not be called again without
+ // ReleaseStream() being called first. If ERR_IO_PENDING is
+ // returned, must not be called again without CancelRequest() or
+ // ReleaseStream() being called first. Otherwise, in case of an
+ // immediate error, this may be called again.
+ int StartRequest(SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ const BoundNetLog& net_log,
+ const CompletionCallback& callback);
+
+ // Cancels any pending stream creation request. May be called
+ // repeatedly.
+ void CancelRequest();
+
+ // Transfers the created stream (guaranteed to not be NULL) to the
+ // caller. Must be called at most once after StartRequest() returns
+ // OK or |callback| is called with OK. The caller must immediately
+ // set a delegate for the returned stream (except for test code).
+ base::WeakPtr<SpdyStream> ReleaseStream();
+
+ private:
+ friend class SpdySession;
+
+ // Called by |session_| when the stream attempt has finished
+ // successfully.
+ void OnRequestCompleteSuccess(base::WeakPtr<SpdyStream>* stream);
+
+ // Called by |session_| when the stream attempt has finished with an
+ // error. Also called with ERR_ABORTED if |session_| is destroyed
+ // while the stream attempt is still pending.
+ void OnRequestCompleteFailure(int rv);
+
+ // Accessors called by |session_|.
+ SpdyStreamType type() const { return type_; }
+ const GURL& url() const { return url_; }
+ RequestPriority priority() const { return priority_; }
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ void Reset();
+
+ SpdyStreamType type_;
+ base::WeakPtr<SpdySession> session_;
+ base::WeakPtr<SpdyStream> stream_;
+ GURL url_;
+ RequestPriority priority_;
+ BoundNetLog net_log_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStreamRequest);
+};
+
+class NET_EXPORT SpdySession : public BufferedSpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface,
+ public LayeredPool {
+ public:
+ // TODO(akalin): Use base::TickClock when it becomes available.
+ typedef base::TimeTicks (*TimeFunc)(void);
+
+ // How we handle flow control (version-dependent).
+ enum FlowControlState {
+ FLOW_CONTROL_NONE,
+ FLOW_CONTROL_STREAM,
+ FLOW_CONTROL_STREAM_AND_SESSION
+ };
+
+ // Create a new SpdySession.
+ // |spdy_session_key| is the host/port that this session connects to, privacy
+ // and proxy configuration settings that it's using.
+ // |session| is the HttpNetworkSession. |net_log| is the NetLog that we log
+ // network events to.
+ SpdySession(const SpdySessionKey& spdy_session_key,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool verify_domain_authentication,
+ bool enable_sending_initial_data,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol,
+ size_t stream_initial_recv_window_size,
+ size_t initial_max_concurrent_streams,
+ size_t max_concurrent_streams_limit,
+ TimeFunc time_func,
+ const HostPortPair& trusted_spdy_proxy,
+ NetLog* net_log);
+
+ virtual ~SpdySession();
+
+ const HostPortPair& host_port_pair() const {
+ return spdy_session_key_.host_port_proxy_pair().first;
+ }
+ const HostPortProxyPair& host_port_proxy_pair() const {
+ return spdy_session_key_.host_port_proxy_pair();
+ }
+ const SpdySessionKey& spdy_session_key() const {
+ return spdy_session_key_;
+ }
+ // Get a pushed stream for a given |url|. If the server initiates a
+ // stream, it might already exist for a given path. The server
+ // might also not have initiated the stream yet, but indicated it
+ // will via X-Associated-Content. Returns OK if a stream was found
+ // and put into |spdy_stream|, or if one was not found but it is
+ // okay to create a new stream (in which case |spdy_stream| is
+ // reset). Returns an error (not ERR_IO_PENDING) otherwise, and
+ // resets |spdy_stream|.
+ int GetPushStream(
+ const GURL& url,
+ base::WeakPtr<SpdyStream>* spdy_stream,
+ const BoundNetLog& stream_net_log);
+
+ // Initialize the session with the given connection. |is_secure|
+ // must indicate whether |connection| uses an SSL socket or not; it
+ // is usually true, but it can be false for testing or when SPDY is
+ // configured to work with non-secure sockets.
+ //
+ // |pool| is the SpdySessionPool that owns us. Its lifetime must
+ // strictly be greater than |this|.
+ //
+ // |certificate_error_code| must either be OK or less than
+ // ERR_IO_PENDING.
+ //
+ // Returns OK on success, or an error on failure. Never returns
+ // ERR_IO_PENDING. If an error is returned, the session must be
+ // destroyed immediately.
+ Error InitializeWithSocket(scoped_ptr<ClientSocketHandle> connection,
+ SpdySessionPool* pool,
+ bool is_secure,
+ int certificate_error_code);
+
+ // Returns the protocol used by this session. Always between
+ // kProtoSPDY2 and kProtoSPDYMaximumVersion.
+ //
+ // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion
+ // once we stop supporting SPDY/1.
+ NextProto protocol() const { return protocol_; }
+
+ // Check to see if this SPDY session can support an additional domain.
+ // If the session is un-authenticated, then this call always returns true.
+ // For SSL-based sessions, verifies that the server certificate in use by
+ // this session provides authentication for the domain and no client
+ // certificate or channel ID was sent to the original server during the SSL
+ // handshake. NOTE: This function can have false negatives on some
+ // platforms.
+ // TODO(wtc): rename this function and the Net.SpdyIPPoolDomainMatch
+ // histogram because this function does more than verifying domain
+ // authentication now.
+ bool VerifyDomainAuthentication(const std::string& domain);
+
+ // Pushes the given producer into the write queue for
+ // |stream|. |stream| is guaranteed to be activated before the
+ // producer is used to produce its frame.
+ void EnqueueStreamWrite(const base::WeakPtr<SpdyStream>& stream,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> producer);
+
+ // Creates and returns a SYN frame for |stream_id|.
+ scoped_ptr<SpdyFrame> CreateSynStream(
+ SpdyStreamId stream_id,
+ RequestPriority priority,
+ uint8 credential_slot,
+ SpdyControlFlags flags,
+ const SpdyHeaderBlock& headers);
+
+ // Tries to create a CREDENTIAL frame. If successful, fills in
+ // |credential_frame| and returns OK. Returns the error (guaranteed
+ // to not be ERR_IO_PENDING) otherwise.
+ int CreateCredentialFrame(const std::string& origin,
+ const std::string& key,
+ const std::string& cert,
+ RequestPriority priority,
+ scoped_ptr<SpdyFrame>* credential_frame);
+
+ // Creates and returns a SpdyBuffer holding a data frame with the
+ // given data. May return NULL if stalled by flow control.
+ scoped_ptr<SpdyBuffer> CreateDataBuffer(SpdyStreamId stream_id,
+ IOBuffer* data,
+ int len,
+ SpdyDataFlags flags);
+
+ // Close the stream with the given ID, which must exist and be
+ // active. Note that that stream may hold the last reference to the
+ // session.
+ void CloseActiveStream(SpdyStreamId stream_id, int status);
+
+ // Close the given created stream, which must exist but not yet be
+ // active. Note that |stream| may hold the last reference to the
+ // session.
+ void CloseCreatedStream(const base::WeakPtr<SpdyStream>& stream, int status);
+
+ // Send a RST_STREAM frame with the given status code and close the
+ // stream with the given ID, which must exist and be active. Note
+ // that that stream may hold the last reference to the session.
+ void ResetStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status,
+ const std::string& description);
+
+ // Check if a stream is active.
+ bool IsStreamActive(SpdyStreamId stream_id) const;
+
+ // The LoadState is used for informing the user of the current network
+ // status, such as "resolving host", "connecting", etc.
+ LoadState GetLoadState() const;
+
+ // Fills SSL info in |ssl_info| and returns true when SSL is in use.
+ bool GetSSLInfo(SSLInfo* ssl_info,
+ bool* was_npn_negotiated,
+ NextProto* protocol_negotiated);
+
+ // Fills SSL Certificate Request info |cert_request_info| and returns
+ // true when SSL is in use.
+ bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
+
+ // Returns the ServerBoundCertService used by this Socket, or NULL
+ // if server bound certs are not supported in this session.
+ ServerBoundCertService* GetServerBoundCertService() const;
+
+ // Send a WINDOW_UPDATE frame for a stream. Called by a stream
+ // whenever receive window size is increased.
+ void SendStreamWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size);
+
+ // Whether the stream is closed, i.e. it has stopped processing data
+ // and is about to be destroyed.
+ //
+ // TODO(akalin): This is only used in tests. Remove this function
+ // and have tests test the WeakPtr instead.
+ bool IsClosed() const { return availability_state_ == STATE_CLOSED; }
+
+ // Closes this session. This will close all active streams and mark
+ // the session as permanently closed. Callers must assume that the
+ // session is destroyed after this is called. (However, it may not
+ // be destroyed right away, e.g. when a SpdySession function is
+ // present in the call stack.)
+ //
+ // |err| should be < ERR_IO_PENDING; this function is intended to be
+ // called on error.
+ // |description| indicates the reason for the error.
+ void CloseSessionOnError(Error err, const std::string& description);
+
+ // Retrieves information on the current state of the SPDY session as a
+ // Value. Caller takes possession of the returned value.
+ base::Value* GetInfoAsValue() const;
+
+ // Indicates whether the session is being reused after having successfully
+ // used to send/receive data in the past.
+ bool IsReused() const;
+
+ // Returns true if the underlying transport socket ever had any reads or
+ // writes.
+ bool WasEverUsed() const {
+ return connection_->socket()->WasEverUsed();
+ }
+
+ // Returns the load timing information from the perspective of the given
+ // stream. If it's not the first stream, the connection is considered reused
+ // for that stream.
+ //
+ // This uses a different notion of reuse than IsReused(). This function
+ // sets |socket_reused| to false only if |stream_id| is the ID of the first
+ // stream using the session. IsReused(), on the other hand, indicates if the
+ // session has been used to send/receive data at all.
+ bool GetLoadTimingInfo(SpdyStreamId stream_id,
+ LoadTimingInfo* load_timing_info) const;
+
+ // Returns true if session is not currently active
+ bool is_active() const {
+ return !active_streams_.empty() || !created_streams_.empty();
+ }
+
+ // Access to the number of active and pending streams. These are primarily
+ // available for testing and diagnostics.
+ size_t num_active_streams() const { return active_streams_.size(); }
+ size_t num_unclaimed_pushed_streams() const {
+ return unclaimed_pushed_streams_.size();
+ }
+ size_t num_created_streams() const { return created_streams_.size(); }
+
+ size_t pending_create_stream_queue_size(RequestPriority priority) const {
+ DCHECK_LT(priority, NUM_PRIORITIES);
+ return pending_create_stream_queues_[priority].size();
+ }
+
+ // Returns the (version-dependent) flow control state.
+ FlowControlState flow_control_state() const {
+ return flow_control_state_;
+ }
+
+ // Returns the current |stream_initial_send_window_size_|.
+ int32 stream_initial_send_window_size() const {
+ return stream_initial_send_window_size_;
+ }
+
+ // Returns the current |stream_initial_recv_window_size_|.
+ int32 stream_initial_recv_window_size() const {
+ return stream_initial_recv_window_size_;
+ }
+
+ // Returns true if no stream in the session can send data due to
+ // session flow control.
+ bool IsSendStalled() const {
+ return
+ flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION &&
+ session_send_window_size_ == 0;
+ }
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ int GetPeerAddress(IPEndPoint* address) const;
+ int GetLocalAddress(IPEndPoint* address) const;
+
+ // Returns true if requests on this session require credentials.
+ bool NeedsCredentials() const;
+
+ SpdyCredentialState* credential_state() { return &credential_state_; }
+
+ // Adds |alias| to set of aliases associated with this session.
+ void AddPooledAlias(const SpdySessionKey& alias_key);
+
+ // Returns the set of aliases associated with this session.
+ const std::set<SpdySessionKey>& pooled_aliases() const {
+ return pooled_aliases_;
+ }
+
+ int GetProtocolVersion() const;
+
+ size_t GetDataFrameMinimumSize() const {
+ return buffered_spdy_framer_->GetDataFrameMinimumSize();
+ }
+
+ size_t GetControlFrameHeaderSize() const {
+ return buffered_spdy_framer_->GetControlFrameHeaderSize();
+ }
+
+ size_t GetFrameMinimumSize() const {
+ return buffered_spdy_framer_->GetFrameMinimumSize();
+ }
+
+ size_t GetFrameMaximumSize() const {
+ return buffered_spdy_framer_->GetFrameMaximumSize();
+ }
+
+ size_t GetDataFrameMaximumPayload() const {
+ return buffered_spdy_framer_->GetDataFrameMaximumPayload();
+ }
+
+ // Must be used only by |pool_|.
+ base::WeakPtr<SpdySession> GetWeakPtr();
+
+ // LayeredPool implementation:
+ virtual bool CloseOneIdleConnection() OVERRIDE;
+
+ private:
+ friend class base::RefCounted<SpdySession>;
+ friend class SpdyStreamRequest;
+ friend class SpdySessionTest;
+
+ // Allow tests to access our innards for testing purposes.
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ClientPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, FailedPing);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, DeleteExpiredPushStreams);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ProtocolNegotiation);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, ClearSettings);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, AdjustRecvWindowSize);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, AdjustSendWindowSize);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlInactiveStream);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoReceiveLeaks);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlNoSendLeaks);
+ FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, SessionFlowControlEndToEnd);
+
+ typedef std::deque<SpdyStreamRequest*> PendingStreamRequestQueue;
+ typedef std::set<SpdyStreamRequest*> PendingStreamRequestCompletionSet;
+
+ struct ActiveStreamInfo {
+ ActiveStreamInfo();
+ explicit ActiveStreamInfo(SpdyStream* stream);
+ ~ActiveStreamInfo();
+
+ SpdyStream* stream;
+ bool waiting_for_syn_reply;
+ };
+ typedef std::map<SpdyStreamId, ActiveStreamInfo> ActiveStreamMap;
+
+ struct PushedStreamInfo {
+ PushedStreamInfo();
+ PushedStreamInfo(SpdyStreamId stream_id, base::TimeTicks creation_time);
+ ~PushedStreamInfo();
+
+ SpdyStreamId stream_id;
+ base::TimeTicks creation_time;
+ };
+ typedef std::map<GURL, PushedStreamInfo> PushedStreamMap;
+
+ typedef std::set<SpdyStream*> CreatedStreamSet;
+
+ enum AvailabilityState {
+ // The session is available in its socket pool and can be used
+ // freely.
+ STATE_AVAILABLE,
+ // The session can process data on existing streams but will
+ // refuse to create new ones.
+ STATE_GOING_AWAY,
+ // The session has been closed, is waiting to be deleted, and will
+ // refuse to process any more data.
+ STATE_CLOSED
+ };
+
+ enum ReadState {
+ READ_STATE_DO_READ,
+ READ_STATE_DO_READ_COMPLETE,
+ };
+
+ enum WriteState {
+ // There is no in-flight write and the write queue is empty.
+ WRITE_STATE_IDLE,
+ WRITE_STATE_DO_WRITE,
+ WRITE_STATE_DO_WRITE_COMPLETE,
+ };
+
+ // The return value of DoCloseSession() describing what was done.
+ enum CloseSessionResult {
+ // The session was already closed so nothing was done.
+ SESSION_ALREADY_CLOSED,
+ // The session was moved into the closed state but was not removed
+ // from |pool_| (because we're in an IO loop).
+ SESSION_CLOSED_BUT_NOT_REMOVED,
+ // The session was moved into the closed state and removed from
+ // |pool_|.
+ SESSION_CLOSED_AND_REMOVED,
+ };
+
+ // Checks whether a stream for the given |url| can be created or
+ // retrieved from the set of unclaimed push streams. Returns OK if
+ // so. Otherwise, the session is closed and an error <
+ // ERR_IO_PENDING is returned.
+ Error TryAccessStream(const GURL& url);
+
+ // Called by SpdyStreamRequest to start a request to create a
+ // stream. If OK is returned, then |stream| will be filled in with a
+ // valid stream. If ERR_IO_PENDING is returned, then
+ // |request->OnRequestComplete{Success,Failure}()| will be called
+ // when the stream is created (unless it is cancelled). Otherwise,
+ // no stream is created and the error is returned.
+ int TryCreateStream(SpdyStreamRequest* request,
+ base::WeakPtr<SpdyStream>* stream);
+
+ // Actually create a stream into |stream|. Returns OK if successful;
+ // otherwise, returns an error and |stream| is not filled.
+ int CreateStream(const SpdyStreamRequest& request,
+ base::WeakPtr<SpdyStream>* stream);
+
+ // Called by SpdyStreamRequest to remove |request| from the stream
+ // creation queue.
+ void CancelStreamRequest(SpdyStreamRequest* request);
+
+ // Called when there is room to create more streams (e.g., a stream
+ // was closed). Processes as many pending stream requests as
+ // possible.
+ void ProcessPendingStreamRequests();
+
+ // Close the stream pointed to by the given iterator. Note that that
+ // stream may hold the last reference to the session.
+ void CloseActiveStreamIterator(ActiveStreamMap::iterator it, int status);
+
+ // Close the stream pointed to by the given iterator. Note that that
+ // stream may hold the last reference to the session.
+ void CloseCreatedStreamIterator(CreatedStreamSet::iterator it, int status);
+
+ // Calls EnqueueResetStreamFrame() and then
+ // CloseActiveStreamIterator().
+ void ResetStreamIterator(ActiveStreamMap::iterator it,
+ SpdyRstStreamStatus status,
+ const std::string& description);
+
+ // Send a RST_STREAM frame with the given parameters. There should
+ // either be no active stream with the given ID, or that active
+ // stream should be closed shortly after this function is called.
+ //
+ // TODO(akalin): Rename this to EnqueueResetStreamFrame().
+ void EnqueueResetStreamFrame(SpdyStreamId stream_id,
+ RequestPriority priority,
+ SpdyRstStreamStatus status,
+ const std::string& description);
+
+ // Calls DoReadLoop and then if |availability_state_| is
+ // STATE_CLOSED, calls RemoveFromPool().
+ //
+ // Use this function instead of DoReadLoop when posting a task to
+ // pump the read loop.
+ void PumpReadLoop(ReadState expected_read_state, int result);
+
+ // Advance the ReadState state machine. |expected_read_state| is the
+ // expected starting read state.
+ //
+ // This function must always be called via PumpReadLoop() except for
+ // from InitializeWithSocket().
+ int DoReadLoop(ReadState expected_read_state, int result);
+ // The implementations of the states of the ReadState state machine.
+ int DoRead();
+ int DoReadComplete(int result);
+
+ // Calls DoWriteLoop and then if |availability_state_| is
+ // STATE_CLOSED, calls RemoveFromPool().
+ //
+ // Use this function instead of DoWriteLoop when posting a task to
+ // pump the write loop.
+ void PumpWriteLoop(WriteState expected_write_state, int result);
+
+ // Advance the WriteState state machine. |expected_write_state| is
+ // the expected starting write state.
+ //
+ // This function must always be called via PumpWriteLoop().
+ int DoWriteLoop(WriteState expected_write_state, int result);
+ // The implementations of the states of the WriteState state machine.
+ int DoWrite();
+ int DoWriteComplete(int result);
+
+ // TODO(akalin): Rename the Send* and Write* functions below to
+ // Enqueue*.
+
+ // Send initial data. Called when a connection is successfully
+ // established in InitializeWithSocket() and
+ // |enable_sending_initial_data_| is true.
+ void SendInitialData();
+
+ // Helper method to send a SETTINGS frame.
+ void SendSettings(const SettingsMap& settings);
+
+ // Handle SETTING. Either when we send settings, or when we receive a
+ // SETTINGS control frame, update our SpdySession accordingly.
+ void HandleSetting(uint32 id, uint32 value);
+
+ // Adjust the send window size of all ActiveStreams and PendingStreamRequests.
+ void UpdateStreamsSendWindowSize(int32 delta_window_size);
+
+ // Send the PING (preface-PING) frame.
+ void SendPrefacePingIfNoneInFlight();
+
+ // Send PING if there are no PINGs in flight and we haven't heard from server.
+ void SendPrefacePing();
+
+ // Send a single WINDOW_UPDATE frame.
+ void SendWindowUpdateFrame(SpdyStreamId stream_id, uint32 delta_window_size,
+ RequestPriority priority);
+
+ // Send the PING frame.
+ void WritePingFrame(uint32 unique_id);
+
+ // Post a CheckPingStatus call after delay. Don't post if there is already
+ // CheckPingStatus running.
+ void PlanToCheckPingStatus();
+
+ // Check the status of the connection. It calls |CloseSessionOnError| if we
+ // haven't received any data in |kHungInterval| time period.
+ void CheckPingStatus(base::TimeTicks last_check_time);
+
+ // Get a new stream id.
+ int GetNewStreamId();
+
+ // Pushes the given frame with the given priority into the write
+ // queue for the session.
+ void EnqueueSessionWrite(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyFrame> frame);
+
+ // Puts |producer| associated with |stream| onto the write queue
+ // with the given priority.
+ void EnqueueWrite(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> producer,
+ const base::WeakPtr<SpdyStream>& stream);
+
+ // Inserts a newly-created stream into |created_streams_|.
+ void InsertCreatedStream(scoped_ptr<SpdyStream> stream);
+
+ // Activates |stream| (which must be in |created_streams_|) by
+ // assigning it an ID and returns it.
+ scoped_ptr<SpdyStream> ActivateCreatedStream(SpdyStream* stream);
+
+ // Inserts a newly-activated stream into |active_streams_|.
+ void InsertActivatedStream(scoped_ptr<SpdyStream> stream);
+
+ // Remove all internal references to |stream|, call OnClose() on it,
+ // and process any pending stream requests before deleting it. Note
+ // that |stream| may hold the last reference to the session.
+ void DeleteStream(scoped_ptr<SpdyStream> stream, int status);
+
+ // Check if we have a pending pushed-stream for this url
+ // Returns the stream if found (and returns it from the pending
+ // list). Returns NULL otherwise.
+ base::WeakPtr<SpdyStream> GetActivePushStream(const GURL& url);
+
+ // Delegates to |stream->OnInitialResponseHeadersReceived()|. If an
+ // error is returned, the last reference to |this| may have been
+ // released.
+ int OnInitialResponseHeadersReceived(const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time,
+ SpdyStream* stream);
+
+ void RecordPingRTTHistogram(base::TimeDelta duration);
+ void RecordHistograms();
+ void RecordProtocolErrorHistogram(SpdyProtocolErrorDetails details);
+
+ // DCHECKs that |availability_state_| >= STATE_GOING_AWAY, that
+ // there are no pending stream creation requests, and that there are
+ // no created streams.
+ void DcheckGoingAway() const;
+
+ // Calls DcheckGoingAway(), then DCHECKs that |availability_state_|
+ // == STATE_CLOSED, |error_on_close_| has a valid value, that there
+ // are no active streams or unclaimed pushed streams, and that the
+ // write queue is empty.
+ void DcheckClosed() const;
+
+ // Closes all active streams with stream id's greater than
+ // |last_good_stream_id|, as well as any created or pending
+ // streams. Must be called only when |availability_state_| >=
+ // STATE_GOING_AWAY. After this function, DcheckGoingAway() will
+ // pass. May be called multiple times.
+ void StartGoingAway(SpdyStreamId last_good_stream_id, Error status);
+
+ // Must be called only when going away (i.e., DcheckGoingAway()
+ // passes). If there are no more active streams and the session
+ // isn't closed yet, close it.
+ void MaybeFinishGoingAway();
+
+ // If the stream is already closed, does nothing. Otherwise, moves
+ // the session to a closed state. Then, if we're in an IO loop,
+ // returns (as the IO loop will do the pool removal itself when its
+ // done). Otherwise, also removes |this| from |pool_|. The returned
+ // result describes what was done.
+ CloseSessionResult DoCloseSession(Error err, const std::string& description);
+
+ // Remove this session from its pool, which must exist. Must be
+ // called only when the session is closed.
+ //
+ // Must be called only via Pump{Read,Write}Loop() or
+ // DoCloseSession().
+ void RemoveFromPool();
+
+ // Called right before closing a (possibly-inactive) stream for a
+ // reason other than being requested to by the stream.
+ void LogAbandonedStream(SpdyStream* stream, Error status);
+
+ // Called right before closing an active stream for a reason other
+ // than being requested to by the stream.
+ void LogAbandonedActiveStream(ActiveStreamMap::const_iterator it,
+ Error status);
+
+ // Invokes a user callback for stream creation. We provide this method so it
+ // can be deferred to the MessageLoop, so we avoid re-entrancy problems.
+ void CompleteStreamRequest(SpdyStreamRequest* pending_request);
+
+ // Remove old unclaimed pushed streams.
+ void DeleteExpiredPushedStreams();
+
+ // BufferedSpdyFramerVisitorInterface:
+ virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE;
+ virtual void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) OVERRIDE;
+ virtual void OnPing(uint32 unique_id) OVERRIDE;
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE;
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE;
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE;
+ virtual void OnSettings(bool clear_persisted) OVERRIDE;
+ virtual void OnSetting(
+ SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE;
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE;
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+ virtual void OnSynReply(
+ SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+ virtual void OnHeaders(
+ SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // SpdyFramerDebugVisitorInterface
+ virtual void OnSendCompressedFrame(
+ SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t payload_len,
+ size_t frame_len) OVERRIDE;
+ virtual void OnReceiveCompressedFrame(
+ SpdyStreamId stream_id,
+ SpdyFrameType type,
+ size_t frame_len) OVERRIDE {}
+
+ // Called when bytes are consumed from a SpdyBuffer for a DATA frame
+ // that is to be written or is being written. Increases the send
+ // window size accordingly if some or all of the SpdyBuffer is being
+ // discarded.
+ //
+ // If session flow control is turned off, this must not be called.
+ void OnWriteBufferConsumed(size_t frame_payload_size,
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source);
+
+ // Called by OnWindowUpdate() (which is in turn called by the
+ // framer) to increase this session's send window size by
+ // |delta_window_size| from a WINDOW_UPDATE frome, which must be at
+ // least 1. If |delta_window_size| would cause this session's send
+ // window size to overflow, does nothing.
+ //
+ // If session flow control is turned off, this must not be called.
+ void IncreaseSendWindowSize(int32 delta_window_size);
+
+ // If session flow control is turned on, called by CreateDataFrame()
+ // (which is in turn called by a stream) to decrease this session's
+ // send window size by |delta_window_size|, which must be at least 1
+ // and at most kMaxSpdyFrameChunkSize. |delta_window_size| must not
+ // cause this session's send window size to go negative.
+ //
+ // If session flow control is turned off, this must not be called.
+ void DecreaseSendWindowSize(int32 delta_window_size);
+
+ // Called when bytes are consumed by the delegate from a SpdyBuffer
+ // containing received data. Increases the receive window size
+ // accordingly.
+ //
+ // If session flow control is turned off, this must not be called.
+ void OnReadBufferConsumed(size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source);
+
+ // Called by OnReadBufferConsume to increase this session's receive
+ // window size by |delta_window_size|, which must be at least 1 and
+ // must not cause this session's receive window size to overflow,
+ // possibly also sending a WINDOW_UPDATE frame. Also called during
+ // initialization to set the initial receive window size.
+ //
+ // If session flow control is turned off, this must not be called.
+ void IncreaseRecvWindowSize(int32 delta_window_size);
+
+ // Called by OnStreamFrameData (which is in turn called by the
+ // framer) to decrease this session's receive window size by
+ // |delta_window_size|, which must be at least 1 and must not cause
+ // this session's receive window size to go negative.
+ //
+ // If session flow control is turned off, this must not be called.
+ void DecreaseRecvWindowSize(int32 delta_window_size);
+
+ // Queue a send-stalled stream for possibly resuming once we're not
+ // send-stalled anymore.
+ void QueueSendStalledStream(const SpdyStream& stream);
+
+ // Go through the queue of send-stalled streams and try to resume as
+ // many as possible.
+ void ResumeSendStalledStreams();
+
+ // Returns the next stream to possibly resume, or 0 if the queue is
+ // empty.
+ SpdyStreamId PopStreamToPossiblyResume();
+
+ // --------------------------
+ // Helper methods for testing
+ // --------------------------
+
+ void set_connection_at_risk_of_loss_time(base::TimeDelta duration) {
+ connection_at_risk_of_loss_time_ = duration;
+ }
+
+ void set_hung_interval(base::TimeDelta duration) {
+ hung_interval_ = duration;
+ }
+
+ int64 pings_in_flight() const { return pings_in_flight_; }
+
+ uint32 next_ping_id() const { return next_ping_id_; }
+
+ base::TimeTicks last_activity_time() const { return last_activity_time_; }
+
+ bool check_ping_status_pending() const { return check_ping_status_pending_; }
+
+ size_t max_concurrent_streams() const { return max_concurrent_streams_; }
+
+ // Returns the SSLClientSocket that this SPDY session sits on top of,
+ // or NULL, if the transport is not SSL.
+ SSLClientSocket* GetSSLClientSocket() const;
+
+ // Used for posting asynchronous IO tasks. We use this even though
+ // SpdySession is refcounted because we don't need to keep the SpdySession
+ // alive if the last reference is within a RunnableMethod. Just revoke the
+ // method.
+ base::WeakPtrFactory<SpdySession> weak_factory_;
+
+ // Whether Do{Read,Write}Loop() is in the call stack. Useful for
+ // making sure we don't destroy ourselves prematurely in that case.
+ bool in_io_loop_;
+
+ // The key used to identify this session.
+ const SpdySessionKey spdy_session_key_;
+
+ // Set set of SpdySessionKeys for which this session has serviced
+ // requests.
+ std::set<SpdySessionKey> pooled_aliases_;
+
+ // |pool_| owns us, therefore its lifetime must exceed ours. We set
+ // this to NULL after we are removed from the pool.
+ SpdySessionPool* pool_;
+ const base::WeakPtr<HttpServerProperties> http_server_properties_;
+
+ // The socket handle for this session.
+ scoped_ptr<ClientSocketHandle> connection_;
+
+ // The read buffer used to read data from the socket.
+ scoped_refptr<IOBuffer> read_buffer_;
+
+ int stream_hi_water_mark_; // The next stream id to use.
+
+ // Queue, for each priority, of pending stream requests that have
+ // not yet been satisfied.
+ PendingStreamRequestQueue pending_create_stream_queues_[NUM_PRIORITIES];
+
+ // A set of requests that are waiting to be completed (i.e., for the
+ // stream to actually be created). This is necessary since we kick
+ // off the stream creation asynchronously, and so the request may be
+ // cancelled before the asynchronous task to create the stream runs.
+ PendingStreamRequestCompletionSet pending_stream_request_completions_;
+
+ // Map from stream id to all active streams. Streams are active in the sense
+ // that they have a consumer (typically SpdyNetworkTransaction and regardless
+ // of whether or not there is currently any ongoing IO [might be waiting for
+ // the server to start pushing the stream]) or there are still network events
+ // incoming even though the consumer has already gone away (cancellation).
+ //
+ // |active_streams_| owns all its SpdyStream objects.
+ //
+ // TODO(willchan): Perhaps we should separate out cancelled streams and move
+ // them into a separate ActiveStreamMap, and not deliver network events to
+ // them?
+ ActiveStreamMap active_streams_;
+
+ // (Bijective) map from the URL to the ID of the streams that have
+ // already started to be pushed by the server, but do not have
+ // consumers yet. Contains a subset of |active_streams_|.
+ PushedStreamMap unclaimed_pushed_streams_;
+
+ // Set of all created streams but that have not yet sent any frames.
+ //
+ // |created_streams_| owns all its SpdyStream objects.
+ CreatedStreamSet created_streams_;
+
+ // The write queue.
+ SpdyWriteQueue write_queue_;
+
+ // Data for the frame we are currently sending.
+
+ // The buffer we're currently writing.
+ scoped_ptr<SpdyBuffer> in_flight_write_;
+ // The type of the frame in |in_flight_write_|.
+ SpdyFrameType in_flight_write_frame_type_;
+ // The size of the frame in |in_flight_write_|.
+ size_t in_flight_write_frame_size_;
+ // The stream to notify when |in_flight_write_| has been written to
+ // the socket completely.
+ base::WeakPtr<SpdyStream> in_flight_write_stream_;
+
+ // Flag if we're using an SSL connection for this SpdySession.
+ bool is_secure_;
+
+ // Certificate error code when using a secure connection.
+ int certificate_error_code_;
+
+ // Spdy Frame state.
+ scoped_ptr<BufferedSpdyFramer> buffered_spdy_framer_;
+
+ // The state variables.
+ AvailabilityState availability_state_;
+ ReadState read_state_;
+ WriteState write_state_;
+
+ // If the session was closed (i.e., |availability_state_| is
+ // STATE_CLOSED), then |error_on_close_| holds the error with which
+ // it was closed, which is < ERR_IO_PENDING. Otherwise, it is set to
+ // OK.
+ Error error_on_close_;
+
+ // Limits
+ size_t max_concurrent_streams_; // 0 if no limit
+ size_t max_concurrent_streams_limit_;
+
+ // Some statistics counters for the session.
+ int streams_initiated_count_;
+ int streams_pushed_count_;
+ int streams_pushed_and_claimed_count_;
+ int streams_abandoned_count_;
+
+ // |total_bytes_received_| keeps track of all the bytes read by the
+ // SpdySession. It is used by the |Net.SpdySettingsCwnd...| histograms.
+ int total_bytes_received_;
+
+ bool sent_settings_; // Did this session send settings when it started.
+ bool received_settings_; // Did this session receive at least one settings
+ // frame.
+ int stalled_streams_; // Count of streams that were ever stalled.
+
+ // Count of all pings on the wire, for which we have not gotten a response.
+ int64 pings_in_flight_;
+
+ // This is the next ping_id (unique_id) to be sent in PING frame.
+ uint32 next_ping_id_;
+
+ // This is the last time we have sent a PING.
+ base::TimeTicks last_ping_sent_time_;
+
+ // This is the last time we had activity in the session.
+ base::TimeTicks last_activity_time_;
+
+ // This is the next time that unclaimed push streams should be checked for
+ // expirations.
+ base::TimeTicks next_unclaimed_push_stream_sweep_time_;
+
+ // Indicate if we have already scheduled a delayed task to check the ping
+ // status.
+ bool check_ping_status_pending_;
+
+ // Whether to send the (HTTP/2) connection header prefix.
+ bool send_connection_header_prefix_;
+
+ // The (version-dependent) flow control state.
+ FlowControlState flow_control_state_;
+
+ // Initial send window size for this session's streams. Can be
+ // changed by an arriving SETTINGS frame. Newly created streams use
+ // this value for the initial send window size.
+ int32 stream_initial_send_window_size_;
+
+ // Initial receive window size for this session's streams. There are
+ // plans to add a command line switch that would cause a SETTINGS
+ // frame with window size announcement to be sent on startup. Newly
+ // created streams will use this value for the initial receive
+ // window size.
+ int32 stream_initial_recv_window_size_;
+
+ // Session flow control variables. All zero unless session flow
+ // control is turned on.
+ int32 session_send_window_size_;
+ int32 session_recv_window_size_;
+ int32 session_unacked_recv_window_bytes_;
+
+ // A queue of stream IDs that have been send-stalled at some point
+ // in the past.
+ std::deque<SpdyStreamId> stream_send_unstall_queue_[NUM_PRIORITIES];
+
+ BoundNetLog net_log_;
+
+ // Outside of tests, these should always be true.
+ bool verify_domain_authentication_;
+ bool enable_sending_initial_data_;
+ bool enable_credential_frames_;
+ bool enable_compression_;
+ bool enable_ping_based_connection_checking_;
+
+ // The SPDY protocol used. Always between kProtoSPDY2 and
+ // kProtoSPDYMaximumVersion.
+ //
+ // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion
+ // once we stop supporting SPDY/1.
+ NextProto protocol_;
+
+ SpdyCredentialState credential_state_;
+
+ // |connection_at_risk_of_loss_time_| is an optimization to avoid sending
+ // wasteful preface pings (when we just got some data).
+ //
+ // If it is zero (the most conservative figure), then we always send the
+ // preface ping (when none are in flight).
+ //
+ // It is common for TCP/IP sessions to time out in about 3-5 minutes.
+ // Certainly if it has been more than 3 minutes, we do want to send a preface
+ // ping.
+ //
+ // We don't think any connection will time out in under about 10 seconds. So
+ // this might as well be set to something conservative like 10 seconds. Later,
+ // we could adjust it to send fewer pings perhaps.
+ base::TimeDelta connection_at_risk_of_loss_time_;
+
+ // The amount of time that we are willing to tolerate with no activity (of any
+ // form), while there is a ping in flight, before we declare the connection to
+ // be hung. TODO(rtenneti): When hung, instead of resetting connection, race
+ // to build a new connection, and see if that completes before we (finally)
+ // get a PING response (http://crbug.com/127812).
+ base::TimeDelta hung_interval_;
+
+ // This SPDY proxy is allowed to push resources from origins that are
+ // different from those of their associated streams.
+ HostPortPair trusted_spdy_proxy_;
+
+ TimeFunc time_func_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_H_
diff --git a/chromium/net/spdy/spdy_session_key.cc b/chromium/net/spdy/spdy_session_key.cc
new file mode 100644
index 00000000000..3d6cdeafad1
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_key.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_session_key.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+SpdySessionKey::SpdySessionKey() : privacy_mode_(kPrivacyModeDisabled) {
+}
+
+SpdySessionKey::SpdySessionKey(const HostPortPair& host_port_pair,
+ const ProxyServer& proxy_server,
+ PrivacyMode privacy_mode)
+ : host_port_proxy_pair_(host_port_pair, proxy_server),
+ privacy_mode_(privacy_mode) {
+ DVLOG(1) << "SpdySessionKey(host=" << host_port_pair.ToString()
+ << ", proxy=" << proxy_server.ToURI()
+ << ", privacy=" << privacy_mode;
+}
+
+SpdySessionKey::SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair,
+ PrivacyMode privacy_mode)
+ : host_port_proxy_pair_(host_port_proxy_pair),
+ privacy_mode_(privacy_mode) {
+ DVLOG(1) << "SpdySessionKey(hppp=" << host_port_proxy_pair.first.ToString()
+ << "," << host_port_proxy_pair.second.ToURI()
+ << ", privacy=" << privacy_mode;
+}
+
+SpdySessionKey::~SpdySessionKey() {}
+
+bool SpdySessionKey::operator<(const SpdySessionKey& other) const {
+ if (privacy_mode_ != other.privacy_mode_)
+ return privacy_mode_ < other.privacy_mode_;
+ if (!host_port_proxy_pair_.first.Equals(other.host_port_proxy_pair_.first))
+ return host_port_proxy_pair_.first < other.host_port_proxy_pair_.first;
+ return host_port_proxy_pair_.second < other.host_port_proxy_pair_.second;
+}
+
+bool SpdySessionKey::Equals(const SpdySessionKey& other) const {
+ return privacy_mode_ == other.privacy_mode_ &&
+ host_port_proxy_pair_.first.Equals(other.host_port_proxy_pair_.first) &&
+ host_port_proxy_pair_.second == other.host_port_proxy_pair_.second;
+}
+
+} // namespace net
+
diff --git a/chromium/net/spdy/spdy_session_key.h b/chromium/net/spdy/spdy_session_key.h
new file mode 100644
index 00000000000..59c832bb52b
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_key.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2013 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_SPDY_SPDY_SESSION_KEY_H_
+#define NET_SPDY_SPDY_SESSION_KEY_H_
+
+#include "net/base/privacy_mode.h"
+#include "net/proxy/proxy_server.h"
+
+namespace net {
+
+// SpdySessionKey is used as unique index for SpdySessionPool.
+class NET_EXPORT_PRIVATE SpdySessionKey {
+ public:
+ SpdySessionKey();
+ SpdySessionKey(const HostPortPair& host_port_pair,
+ const ProxyServer& proxy_server,
+ PrivacyMode privacy_mode);
+
+ // Temporary hack for implicit copy constructor
+ SpdySessionKey(const HostPortProxyPair& host_port_proxy_pair,
+ PrivacyMode privacy_mode);
+
+ ~SpdySessionKey();
+
+ // Comparator function so this can be placed in a std::map.
+ bool operator<(const SpdySessionKey& other) const;
+
+ // Equality test of contents. (Probably another violation of style guide).
+ bool Equals(const SpdySessionKey& other) const;
+
+ const HostPortProxyPair& host_port_proxy_pair() const {
+ return host_port_proxy_pair_;
+ }
+
+ const HostPortPair& host_port_pair() const {
+ return host_port_proxy_pair_.first;
+ }
+
+ const ProxyServer& proxy_server() const {
+ return host_port_proxy_pair_.second;
+ }
+
+ PrivacyMode privacy_mode() const {
+ return privacy_mode_;
+ }
+
+ private:
+ HostPortProxyPair host_port_proxy_pair_;
+ // If enabled, then session cannot be tracked by the server.
+ PrivacyMode privacy_mode_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_KEY_H_
+
diff --git a/chromium/net/spdy/spdy_session_pool.cc b/chromium/net/spdy/spdy_session_pool.cc
new file mode 100644
index 00000000000..9e47f3f4e89
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_pool.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_session_pool.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/spdy/spdy_session.h"
+
+
+namespace net {
+
+namespace {
+
+enum SpdySessionGetTypes {
+ CREATED_NEW = 0,
+ FOUND_EXISTING = 1,
+ FOUND_EXISTING_FROM_IP_POOL = 2,
+ IMPORTED_FROM_SOCKET = 3,
+ SPDY_SESSION_GET_MAX = 4
+};
+
+} // namespace
+
+SpdySessionPool::SpdySessionPool(
+ HostResolver* resolver,
+ SSLConfigService* ssl_config_service,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool force_single_domain,
+ bool enable_ip_pooling,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol,
+ size_t stream_initial_recv_window_size,
+ size_t initial_max_concurrent_streams,
+ size_t max_concurrent_streams_limit,
+ SpdySessionPool::TimeFunc time_func,
+ const std::string& trusted_spdy_proxy)
+ : http_server_properties_(http_server_properties),
+ ssl_config_service_(ssl_config_service),
+ resolver_(resolver),
+ verify_domain_authentication_(true),
+ enable_sending_initial_data_(true),
+ force_single_domain_(force_single_domain),
+ enable_ip_pooling_(enable_ip_pooling),
+ enable_credential_frames_(enable_credential_frames),
+ enable_compression_(enable_compression),
+ enable_ping_based_connection_checking_(
+ enable_ping_based_connection_checking),
+ // TODO(akalin): Force callers to have a valid value of
+ // |default_protocol_|. Or at least make the default be
+ // kProtoSPDY3.
+ default_protocol_(
+ (default_protocol == kProtoUnknown) ?
+ kProtoSPDY2 : default_protocol),
+ stream_initial_recv_window_size_(stream_initial_recv_window_size),
+ initial_max_concurrent_streams_(initial_max_concurrent_streams),
+ max_concurrent_streams_limit_(max_concurrent_streams_limit),
+ time_func_(time_func),
+ trusted_spdy_proxy_(
+ HostPortPair::FromString(trusted_spdy_proxy)) {
+ // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we
+ // stop supporting SPDY/1.
+ DCHECK(default_protocol_ >= kProtoSPDY2 &&
+ default_protocol_ <= kProtoSPDYMaximumVersion);
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ if (ssl_config_service_.get())
+ ssl_config_service_->AddObserver(this);
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+SpdySessionPool::~SpdySessionPool() {
+ CloseAllSessions();
+
+ if (ssl_config_service_.get())
+ ssl_config_service_->RemoveObserver(this);
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+net::Error SpdySessionPool::CreateAvailableSessionFromSocket(
+ const SpdySessionKey& key,
+ scoped_ptr<ClientSocketHandle> connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ base::WeakPtr<SpdySession>* available_session,
+ bool is_secure) {
+ // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we
+ // stop supporting SPDY/1.
+ DCHECK_GE(default_protocol_, kProtoSPDY2);
+ DCHECK_LE(default_protocol_, kProtoSPDYMaximumVersion);
+
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX);
+
+ scoped_ptr<SpdySession> new_session(
+ new SpdySession(key,
+ http_server_properties_,
+ verify_domain_authentication_,
+ enable_sending_initial_data_,
+ enable_credential_frames_,
+ enable_compression_,
+ enable_ping_based_connection_checking_,
+ default_protocol_,
+ stream_initial_recv_window_size_,
+ initial_max_concurrent_streams_,
+ max_concurrent_streams_limit_,
+ time_func_,
+ trusted_spdy_proxy_,
+ net_log.net_log()));
+
+ Error error = new_session->InitializeWithSocket(
+ connection.Pass(), this, is_secure, certificate_error_code);
+ DCHECK_NE(error, ERR_IO_PENDING);
+
+ if (error != OK) {
+ available_session->reset();
+ return error;
+ }
+
+ *available_session = new_session->GetWeakPtr();
+ sessions_.insert(new_session.release());
+ MapKeyToAvailableSession(key, *available_session);
+
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
+ (*available_session)->net_log().source().ToEventParametersCallback());
+
+ // Look up the IP address for this session so that we can match
+ // future sessions (potentially to different domains) which can
+ // potentially be pooled with this one. Because GetPeerAddress()
+ // reports the proxy's address instead of the origin server, check
+ // to see if this is a direct connection.
+ if (enable_ip_pooling_ && key.proxy_server().is_direct()) {
+ IPEndPoint address;
+ if ((*available_session)->GetPeerAddress(&address) == OK)
+ aliases_[address] = key;
+ }
+
+ return error;
+}
+
+base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession(
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log) {
+ AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key);
+ if (it != available_sessions_.end()) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.SpdySessionGet", FOUND_EXISTING, SPDY_SESSION_GET_MAX);
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
+ it->second->net_log().source().ToEventParametersCallback());
+ return it->second;
+ }
+
+ if (!enable_ip_pooling_)
+ return base::WeakPtr<SpdySession>();
+
+ // Look up the key's from the resolver's cache.
+ net::HostResolver::RequestInfo resolve_info(key.host_port_pair());
+ AddressList addresses;
+ int rv = resolver_->ResolveFromCache(resolve_info, &addresses, net_log);
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ if (rv != OK)
+ return base::WeakPtr<SpdySession>();
+
+ // Check if we have a session through a domain alias.
+ for (AddressList::const_iterator address_it = addresses.begin();
+ address_it != addresses.end();
+ ++address_it) {
+ AliasMap::const_iterator alias_it = aliases_.find(*address_it);
+ if (alias_it == aliases_.end())
+ continue;
+
+ // We found an alias.
+ const SpdySessionKey& alias_key = alias_it->second;
+
+ // We can reuse this session only if the proxy and privacy
+ // settings match.
+ if (!(alias_key.proxy_server() == key.proxy_server()) ||
+ !(alias_key.privacy_mode() == key.privacy_mode()))
+ continue;
+
+ AvailableSessionMap::iterator available_session_it =
+ LookupAvailableSessionByKey(alias_key);
+ if (available_session_it == available_sessions_.end()) {
+ NOTREACHED(); // It shouldn't be in the aliases table if we can't get it!
+ continue;
+ }
+
+ const base::WeakPtr<SpdySession>& available_session =
+ available_session_it->second;
+ DCHECK(ContainsKey(sessions_, available_session.get()));
+ // If the session is a secure one, we need to verify that the
+ // server is authenticated to serve traffic for |host_port_proxy_pair| too.
+ if (!available_session->VerifyDomainAuthentication(
+ key.host_port_pair().host())) {
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
+ continue;
+ }
+
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
+ UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+ FOUND_EXISTING_FROM_IP_POOL,
+ SPDY_SESSION_GET_MAX);
+ net_log.AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
+ available_session->net_log().source().ToEventParametersCallback());
+ // Add this session to the map so that we can find it next time.
+ MapKeyToAvailableSession(key, available_session);
+ available_session->AddPooledAlias(key);
+ return available_session;
+ }
+
+ return base::WeakPtr<SpdySession>();
+}
+
+void SpdySessionPool::MakeSessionUnavailable(
+ const base::WeakPtr<SpdySession>& available_session) {
+ UnmapKey(available_session->spdy_session_key());
+ RemoveAliases(available_session->spdy_session_key());
+ const std::set<SpdySessionKey>& aliases = available_session->pooled_aliases();
+ for (std::set<SpdySessionKey>::const_iterator it = aliases.begin();
+ it != aliases.end(); ++it) {
+ UnmapKey(*it);
+ RemoveAliases(*it);
+ }
+ DCHECK(!IsSessionAvailable(available_session));
+}
+
+void SpdySessionPool::RemoveUnavailableSession(
+ const base::WeakPtr<SpdySession>& unavailable_session) {
+ DCHECK(!IsSessionAvailable(unavailable_session));
+
+ unavailable_session->net_log().AddEvent(
+ NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
+ unavailable_session->net_log().source().ToEventParametersCallback());
+
+ SessionSet::iterator it = sessions_.find(unavailable_session.get());
+ CHECK(it != sessions_.end());
+ scoped_ptr<SpdySession> owned_session(*it);
+ sessions_.erase(it);
+}
+
+// Make a copy of |sessions_| in the Close* functions below to avoid
+// reentrancy problems. Since arbitrary functions get called by close
+// handlers, it doesn't suffice to simply increment the iterator
+// before closing.
+
+void SpdySessionPool::CloseCurrentSessions(net::Error error) {
+ CloseCurrentSessionsHelper(error, "Closing current sessions.",
+ false /* idle_only */);
+}
+
+void SpdySessionPool::CloseCurrentIdleSessions() {
+ CloseCurrentSessionsHelper(ERR_ABORTED, "Closing idle sessions.",
+ true /* idle_only */);
+}
+
+void SpdySessionPool::CloseAllSessions() {
+ while (!sessions_.empty()) {
+ CloseCurrentSessionsHelper(ERR_ABORTED, "Closing all sessions.",
+ false /* idle_only */);
+ }
+}
+
+base::Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
+ base::ListValue* list = new base::ListValue();
+
+ for (AvailableSessionMap::const_iterator it = available_sessions_.begin();
+ it != available_sessions_.end(); ++it) {
+ // Only add the session if the key in the map matches the main
+ // host_port_proxy_pair (not an alias).
+ const SpdySessionKey& key = it->first;
+ const SpdySessionKey& session_key = it->second->spdy_session_key();
+ if (key.Equals(session_key))
+ list->Append(it->second->GetInfoAsValue());
+ }
+ return list;
+}
+
+void SpdySessionPool::OnIPAddressChanged() {
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+ http_server_properties_->ClearAllSpdySettings();
+}
+
+void SpdySessionPool::OnSSLConfigChanged() {
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+void SpdySessionPool::OnCertAdded(const X509Certificate* cert) {
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) {
+ // Per wtc, we actually only need to CloseCurrentSessions when trust is
+ // reduced. CloseCurrentSessions now because OnCertTrustChanged does not
+ // tell us this.
+ // See comments in ClientSocketPoolManager::OnCertTrustChanged.
+ CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+bool SpdySessionPool::IsSessionAvailable(
+ const base::WeakPtr<SpdySession>& session) const {
+ for (AvailableSessionMap::const_iterator it = available_sessions_.begin();
+ it != available_sessions_.end(); ++it) {
+ if (it->second.get() == session.get())
+ return true;
+ }
+ return false;
+}
+
+const SpdySessionKey& SpdySessionPool::NormalizeListKey(
+ const SpdySessionKey& key) const {
+ if (!force_single_domain_)
+ return key;
+
+ static SpdySessionKey* single_domain_key = NULL;
+ if (!single_domain_key) {
+ HostPortPair single_domain = HostPortPair("singledomain.com", 80);
+ single_domain_key = new SpdySessionKey(single_domain,
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ }
+ return *single_domain_key;
+}
+
+void SpdySessionPool::MapKeyToAvailableSession(
+ const SpdySessionKey& key,
+ const base::WeakPtr<SpdySession>& session) {
+ DCHECK(ContainsKey(sessions_, session.get()));
+ const SpdySessionKey& normalized_key = NormalizeListKey(key);
+ std::pair<AvailableSessionMap::iterator, bool> result =
+ available_sessions_.insert(std::make_pair(normalized_key, session));
+ CHECK(result.second);
+}
+
+SpdySessionPool::AvailableSessionMap::iterator
+SpdySessionPool::LookupAvailableSessionByKey(
+ const SpdySessionKey& key) {
+ const SpdySessionKey& normalized_key = NormalizeListKey(key);
+ return available_sessions_.find(normalized_key);
+}
+
+void SpdySessionPool::UnmapKey(const SpdySessionKey& key) {
+ AvailableSessionMap::iterator it = LookupAvailableSessionByKey(key);
+ CHECK(it != available_sessions_.end());
+ available_sessions_.erase(it);
+}
+
+void SpdySessionPool::RemoveAliases(const SpdySessionKey& key) {
+ // Walk the aliases map, find references to this pair.
+ // TODO(mbelshe): Figure out if this is too expensive.
+ for (AliasMap::iterator it = aliases_.begin(); it != aliases_.end(); ) {
+ if (it->second.Equals(key)) {
+ AliasMap::iterator old_it = it;
+ ++it;
+ aliases_.erase(old_it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+SpdySessionPool::WeakSessionList SpdySessionPool::GetCurrentSessions() const {
+ WeakSessionList current_sessions;
+ for (SessionSet::const_iterator it = sessions_.begin();
+ it != sessions_.end(); ++it) {
+ current_sessions.push_back((*it)->GetWeakPtr());
+ }
+ return current_sessions;
+}
+
+void SpdySessionPool::CloseCurrentSessionsHelper(
+ Error error,
+ const std::string& description,
+ bool idle_only) {
+ WeakSessionList current_sessions = GetCurrentSessions();
+ for (WeakSessionList::const_iterator it = current_sessions.begin();
+ it != current_sessions.end(); ++it) {
+ if (!*it)
+ continue;
+
+ if (idle_only && (*it)->is_active())
+ continue;
+
+ (*it)->CloseSessionOnError(error, description);
+ DCHECK(!IsSessionAvailable(*it));
+ DCHECK(!*it);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_session_pool.h b/chromium/net/spdy/spdy_session_pool.h
new file mode 100644
index 00000000000..812b7bd8890
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_pool.h
@@ -0,0 +1,235 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_POOL_H_
+#define NET_SPDY_SPDY_SESSION_POOL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "net/cert/cert_database.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_session_key.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+class AddressList;
+class BoundNetLog;
+class ClientSocketHandle;
+class HostResolver;
+class HttpServerProperties;
+class SpdySession;
+
+// This is a very simple pool for open SpdySessions.
+class NET_EXPORT SpdySessionPool
+ : public NetworkChangeNotifier::IPAddressObserver,
+ public SSLConfigService::Observer,
+ public CertDatabase::Observer {
+ public:
+ typedef base::TimeTicks (*TimeFunc)(void);
+
+ // |default_protocol| may be kProtoUnknown (e.g., if SPDY is
+ // disabled), in which case it's set to a default value. Otherwise,
+ // it must be a SPDY protocol.
+ SpdySessionPool(
+ HostResolver* host_resolver,
+ SSLConfigService* ssl_config_service,
+ const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ bool force_single_domain,
+ bool enable_ip_pooling,
+ bool enable_credential_frames,
+ bool enable_compression,
+ bool enable_ping_based_connection_checking,
+ NextProto default_protocol,
+ size_t stream_initial_recv_window_size,
+ size_t initial_max_concurrent_streams,
+ size_t max_concurrent_streams_limit,
+ SpdySessionPool::TimeFunc time_func,
+ const std::string& trusted_spdy_proxy);
+ virtual ~SpdySessionPool();
+
+ // In the functions below, a session is "available" if this pool has
+ // a reference to it and there is some SpdySessionKey for which
+ // FindAvailableSession() will return it. A session is "unavailable"
+ // if this pool has a reference to it but it won't be returned by
+ // FindAvailableSession() for any SpdySessionKey; for example, this
+ // can happen when a session receives a GOAWAY frame and is still
+ // processing existing streams.
+
+ // Create a new SPDY session from an existing socket. There must
+ // not already be a session for the given key. This pool must have
+ // been constructed with a valid |default_protocol| value.
+ //
+ // |is_secure| can be false for testing or when SPDY is configured
+ // to work with non-secure sockets. If |is_secure| is true,
+ // |certificate_error_code| indicates that the certificate error
+ // encountered when connecting the SSL socket, with OK meaning there
+ // was no error.
+ //
+ // If successful, OK is returned and |available_session| will be
+ // non-NULL and available. Otherwise, an error is returned and
+ // |available_session| will be NULL.
+ net::Error CreateAvailableSessionFromSocket(
+ const SpdySessionKey& key,
+ scoped_ptr<ClientSocketHandle> connection,
+ const BoundNetLog& net_log,
+ int certificate_error_code,
+ base::WeakPtr<SpdySession>* available_session,
+ bool is_secure);
+
+ // Find an available session for the given key, or NULL if there isn't one.
+ base::WeakPtr<SpdySession> FindAvailableSession(const SpdySessionKey& key,
+ const BoundNetLog& net_log);
+
+ // Remove all mappings and aliases for the given session, which must
+ // still be available. Except for in tests, this must be called by
+ // the given session itself.
+ void MakeSessionUnavailable(
+ const base::WeakPtr<SpdySession>& available_session);
+
+ // Removes an unavailable session from the pool. Except for in
+ // tests, this must be called by the given session itself.
+ void RemoveUnavailableSession(
+ const base::WeakPtr<SpdySession>& unavailable_session);
+
+ // Close only the currently existing SpdySessions with |error|.
+ // Let any new ones created while this method is running continue to
+ // live.
+ void CloseCurrentSessions(net::Error error);
+
+ // Close only the currently existing SpdySessions that are idle.
+ // Let any new ones created while this method is running continue to
+ // live.
+ void CloseCurrentIdleSessions();
+
+ // Close all SpdySessions, including any new ones created in the process of
+ // closing the current ones.
+ void CloseAllSessions();
+
+ // Creates a Value summary of the state of the spdy session pool. The caller
+ // responsible for deleting the returned value.
+ base::Value* SpdySessionPoolInfoToValue() const;
+
+ base::WeakPtr<HttpServerProperties> http_server_properties() {
+ return http_server_properties_;
+ }
+
+ // NetworkChangeNotifier::IPAddressObserver methods:
+
+ // We flush all idle sessions and release references to the active ones so
+ // they won't get re-used. The active ones will either complete successfully
+ // or error out due to the IP address change.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // SSLConfigService::Observer methods:
+
+ // We perform the same flushing as described above when SSL settings change.
+ virtual void OnSSLConfigChanged() OVERRIDE;
+
+ // CertDatabase::Observer methods:
+ virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE;
+ virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE;
+
+ private:
+ friend class SpdySessionPoolPeer; // For testing.
+
+ typedef std::set<SpdySession*> SessionSet;
+ typedef std::vector<base::WeakPtr<SpdySession> > WeakSessionList;
+ typedef std::map<SpdySessionKey, base::WeakPtr<SpdySession> >
+ AvailableSessionMap;
+ typedef std::map<IPEndPoint, SpdySessionKey> AliasMap;
+
+ // Returns true iff |session| is in |available_sessions_|.
+ bool IsSessionAvailable(const base::WeakPtr<SpdySession>& session) const;
+
+ // Returns a normalized version of the given key suitable for lookup
+ // into |available_sessions_|.
+ const SpdySessionKey& NormalizeListKey(const SpdySessionKey& key) const;
+
+ // Map the given key to the given session. There must not already be
+ // a mapping for |key|.
+ void MapKeyToAvailableSession(const SpdySessionKey& key,
+ const base::WeakPtr<SpdySession>& session);
+
+ // Returns an iterator into |available_sessions_| for the given key,
+ // which may be equal to |available_sessions_.end()|.
+ AvailableSessionMap::iterator LookupAvailableSessionByKey(
+ const SpdySessionKey& key);
+
+ // Remove the mapping of the given key, which must exist.
+ void UnmapKey(const SpdySessionKey& key);
+
+ // Remove all aliases for |key| from the aliases table.
+ void RemoveAliases(const SpdySessionKey& key);
+
+ // Get a copy of the current sessions as a list of WeakPtrs. Used by
+ // CloseCurrentSessionsHelper() below.
+ WeakSessionList GetCurrentSessions() const;
+
+ // Close only the currently existing SpdySessions with |error|. Let
+ // any new ones created while this method is running continue to
+ // live. If |idle_only| is true only idle sessions are closed.
+ void CloseCurrentSessionsHelper(
+ Error error,
+ const std::string& description,
+ bool idle_only);
+
+ const base::WeakPtr<HttpServerProperties> http_server_properties_;
+
+ // The set of all sessions. This is a superset of the sessions in
+ // |available_sessions_|.
+ //
+ // |sessions_| owns all its SpdySession objects.
+ SessionSet sessions_;
+
+ // This is a map of available sessions by key. A session may appear
+ // more than once in this map if it has aliases.
+ AvailableSessionMap available_sessions_;
+
+ // A map of IPEndPoint aliases for sessions.
+ AliasMap aliases_;
+
+ static bool g_force_single_domain;
+
+ const scoped_refptr<SSLConfigService> ssl_config_service_;
+ HostResolver* const resolver_;
+
+ // Defaults to true. May be controlled via SpdySessionPoolPeer for tests.
+ bool verify_domain_authentication_;
+ bool enable_sending_initial_data_;
+ bool force_single_domain_;
+ bool enable_ip_pooling_;
+ bool enable_credential_frames_;
+ bool enable_compression_;
+ bool enable_ping_based_connection_checking_;
+ const NextProto default_protocol_;
+ size_t stream_initial_recv_window_size_;
+ size_t initial_max_concurrent_streams_;
+ size_t max_concurrent_streams_limit_;
+ TimeFunc time_func_;
+
+ // This SPDY proxy is allowed to push resources from origins that are
+ // different from those of their associated streams.
+ HostPortPair trusted_spdy_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPool);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_POOL_H_
diff --git a/chromium/net/spdy/spdy_session_pool_unittest.cc b/chromium/net/spdy/spdy_session_pool_unittest.cc
new file mode 100644
index 00000000000..9d0679d4d4b
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_pool_unittest.cc
@@ -0,0 +1,494 @@
+// Copyright (c) 2013 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/spdy/spdy_session_pool.h"
+
+#include <cstddef>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/dns/host_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class SpdySessionPoolTest : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ // Used by RunIPPoolingTest().
+ enum SpdyPoolCloseSessionsType {
+ SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
+ SPDY_POOL_CLOSE_CURRENT_SESSIONS,
+ SPDY_POOL_CLOSE_IDLE_SESSIONS,
+ };
+
+ SpdySessionPoolTest()
+ : session_deps_(GetParam()),
+ spdy_session_pool_(NULL) {}
+
+ void CreateNetworkSession() {
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ spdy_session_pool_ = http_session_->spdy_session_pool();
+ }
+
+ void RunIPPoolingTest(SpdyPoolCloseSessionsType close_sessions_type);
+
+ SpdySessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ SpdySessionPool* spdy_session_pool_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdySessionPoolTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+// A delegate that opens a new session when it is closed.
+class SessionOpeningDelegate : public SpdyStream::Delegate {
+ public:
+ SessionOpeningDelegate(SpdySessionPool* spdy_session_pool,
+ const SpdySessionKey& key)
+ : spdy_session_pool_(spdy_session_pool),
+ key_(key) {}
+
+ virtual ~SessionOpeningDelegate() {}
+
+ virtual void OnRequestHeadersSent() OVERRIDE {}
+
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE {
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+ }
+
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {}
+
+ virtual void OnDataSent() OVERRIDE {}
+
+ virtual void OnClose(int status) OVERRIDE {
+ ignore_result(CreateFakeSpdySession(spdy_session_pool_, key_));
+ }
+
+ private:
+ SpdySessionPool* const spdy_session_pool_;
+ const SpdySessionKey key_;
+};
+
+// Set up a SpdyStream to create a new session when it is closed.
+// CloseCurrentSessions should not close the newly-created session.
+TEST_P(SpdySessionPoolTest, CloseCurrentSessions) {
+ const char kTestHost[] = "www.foo.com";
+ const int kTestPort = 80;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ SpdySessionKey test_key =
+ SpdySessionKey(
+ test_host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ // Setup the first session to the first host.
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, test_key, BoundNetLog());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify that we have sessions for everything.
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
+
+ // Set the stream to create a new session when it is closed.
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, GURL("http://www.foo.com"),
+ MEDIUM, BoundNetLog());
+ SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
+ spdy_stream->SetDelegate(&delegate);
+
+ // Close the current session.
+ spdy_session_pool_->CloseCurrentSessions(net::ERR_ABORTED);
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
+}
+
+TEST_P(SpdySessionPoolTest, CloseCurrentIdleSessions) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ // Set up session 1
+ const std::string kTestHost1("http://www.a.com");
+ HostPortPair test_host_port_pair1(kTestHost1, 80);
+ SpdySessionKey key1(test_host_port_pair1, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session1 =
+ CreateInsecureSpdySession(http_session_, key1, BoundNetLog());
+ GURL url1(kTestHost1);
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session1, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+
+ // Set up session 2
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ const std::string kTestHost2("http://www.b.com");
+ HostPortPair test_host_port_pair2(kTestHost2, 80);
+ SpdySessionKey key2(test_host_port_pair2, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session2 =
+ CreateInsecureSpdySession(http_session_, key2, BoundNetLog());
+ GURL url2(kTestHost2);
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session2, url2, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+
+ // Set up session 3
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ const std::string kTestHost3("http://www.c.com");
+ HostPortPair test_host_port_pair3(kTestHost3, 80);
+ SpdySessionKey key3(test_host_port_pair3, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session3 =
+ CreateInsecureSpdySession(http_session_, key3, BoundNetLog());
+ GURL url3(kTestHost3);
+ base::WeakPtr<SpdyStream> spdy_stream3 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session3, url3, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream3.get() != NULL);
+
+ // All sessions are active and not closed
+ EXPECT_TRUE(session1->is_active());
+ EXPECT_FALSE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_TRUE(session3->is_active());
+ EXPECT_FALSE(session3->IsClosed());
+
+ // Should not do anything, all are active
+ spdy_session_pool_->CloseCurrentIdleSessions();
+ EXPECT_TRUE(session1->is_active());
+ EXPECT_FALSE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_TRUE(session3->is_active());
+ EXPECT_FALSE(session3->IsClosed());
+
+ // Make sessions 1 and 3 inactive, but keep them open.
+ // Session 2 still open and active
+ session1->CloseCreatedStream(spdy_stream1, OK);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ session3->CloseCreatedStream(spdy_stream3, OK);
+ EXPECT_EQ(NULL, spdy_stream3.get());
+ EXPECT_FALSE(session1->is_active());
+ EXPECT_FALSE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_FALSE(session3->is_active());
+ EXPECT_FALSE(session3->IsClosed());
+
+ // Should close session 1 and 3, 2 should be left open
+ spdy_session_pool_->CloseCurrentIdleSessions();
+ EXPECT_TRUE(session1 == NULL);
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+ EXPECT_TRUE(session3 == NULL);
+
+ // Should not do anything
+ spdy_session_pool_->CloseCurrentIdleSessions();
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // Make 2 not active
+ session2->CloseCreatedStream(spdy_stream2, OK);
+ EXPECT_EQ(NULL, spdy_stream2.get());
+ EXPECT_FALSE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // This should close session 2
+ spdy_session_pool_->CloseCurrentIdleSessions();
+ EXPECT_TRUE(session2 == NULL);
+}
+
+// Set up a SpdyStream to create a new session when it is closed.
+// CloseAllSessions should close the newly-created session.
+TEST_P(SpdySessionPoolTest, CloseAllSessions) {
+ const char kTestHost[] = "www.foo.com";
+ const int kTestPort = 80;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ HostPortPair test_host_port_pair(kTestHost, kTestPort);
+ SpdySessionKey test_key =
+ SpdySessionKey(
+ test_host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ // Setup the first session to the first host.
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, test_key, BoundNetLog());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Verify that we have sessions for everything.
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
+
+ // Set the stream to create a new session when it is closed.
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, GURL("http://www.foo.com"),
+ MEDIUM, BoundNetLog());
+ SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
+ spdy_stream->SetDelegate(&delegate);
+
+ // Close the current session.
+ spdy_session_pool_->CloseAllSessions();
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_key));
+}
+
+// This test has three variants, one for each style of closing the connection.
+// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
+// the sessions are closed manually, calling SpdySessionPool::Remove() directly.
+// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_CURRENT_SESSIONS,
+// sessions are closed with SpdySessionPool::CloseCurrentSessions().
+// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_IDLE_SESSIONS,
+// sessions are closed with SpdySessionPool::CloseIdleSessions().
+void SpdySessionPoolTest::RunIPPoolingTest(
+ SpdyPoolCloseSessionsType close_sessions_type) {
+ const int kTestPort = 80;
+ struct TestHosts {
+ std::string url;
+ std::string name;
+ std::string iplist;
+ SpdySessionKey key;
+ AddressList addresses;
+ } test_hosts[] = {
+ { "http:://www.foo.com",
+ "www.foo.com",
+ "192.0.2.33,192.168.0.1,192.168.0.5"
+ },
+ { "http://js.foo.com",
+ "js.foo.com",
+ "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33"
+ },
+ { "http://images.foo.com",
+ "images.foo.com",
+ "192.168.0.4,192.168.0.3"
+ },
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
+ session_deps_.host_resolver->rules()->AddIPLiteralRule(
+ test_hosts[i].name, test_hosts[i].iplist, std::string());
+
+ // This test requires that the HostResolver cache be populated. Normal
+ // code would have done this already, but we do it manually.
+ HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
+ session_deps_.host_resolver->Resolve(
+ info, &test_hosts[i].addresses, CompletionCallback(), NULL,
+ BoundNetLog());
+
+ // Setup a SpdySessionKey
+ test_hosts[i].key = SpdySessionKey(
+ HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ }
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ // Setup the first session to the first host.
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(
+ http_session_, test_hosts[0].key, BoundNetLog());
+
+ // Flush the SpdySession::OnReadComplete() task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The third host has no overlap with the first, so it can't pool IPs.
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
+
+ // The second host overlaps with the first, and should IP pool.
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
+
+ // Verify that the second host, through a proxy, won't share the IP.
+ SpdySessionKey proxy_key(test_hosts[1].key.host_port_pair(),
+ ProxyServer::FromPacString("HTTP http://proxy.foo.com/"),
+ kPrivacyModeDisabled);
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, proxy_key));
+
+ // Overlap between 2 and 3 does is not transitive to 1.
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
+
+ // Create a new session to host 2.
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ base::WeakPtr<SpdySession> session2 =
+ CreateInsecureSpdySession(
+ http_session_, test_hosts[2].key, BoundNetLog());
+
+ // Verify that we have sessions for everything.
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
+
+ // Grab the session to host 1 and verify that it is the same session
+ // we got with host 0, and that is a different from host 2's session.
+ base::WeakPtr<SpdySession> session1 =
+ spdy_session_pool_->FindAvailableSession(
+ test_hosts[1].key, BoundNetLog());
+ EXPECT_EQ(session.get(), session1.get());
+ EXPECT_NE(session2.get(), session1.get());
+
+ // Remove the aliases and observe that we still have a session for host1.
+ SpdySessionPoolPeer pool_peer(spdy_session_pool_);
+ pool_peer.RemoveAliases(test_hosts[0].key);
+ pool_peer.RemoveAliases(test_hosts[1].key);
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
+
+ // Expire the host cache
+ session_deps_.host_resolver->GetHostCache()->clear();
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
+
+ // Cleanup the sessions.
+ switch (close_sessions_type) {
+ case SPDY_POOL_CLOSE_SESSIONS_MANUALLY:
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_TRUE(session == NULL);
+ session2->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_TRUE(session2 == NULL);
+ break;
+ case SPDY_POOL_CLOSE_CURRENT_SESSIONS:
+ spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
+ break;
+ case SPDY_POOL_CLOSE_IDLE_SESSIONS:
+ GURL url(test_hosts[0].url);
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ GURL url1(test_hosts[1].url);
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session1, url1, MEDIUM, BoundNetLog());
+ GURL url2(test_hosts[2].url);
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session2, url2, MEDIUM, BoundNetLog());
+
+ // Close streams to make spdy_session and spdy_session1 inactive.
+ session->CloseCreatedStream(spdy_stream, OK);
+ EXPECT_EQ(NULL, spdy_stream.get());
+ session1->CloseCreatedStream(spdy_stream1, OK);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ // Check spdy_session and spdy_session1 are not closed.
+ EXPECT_FALSE(session->is_active());
+ EXPECT_FALSE(session->IsClosed());
+ EXPECT_FALSE(session1->is_active());
+ EXPECT_FALSE(session1->IsClosed());
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ // Test that calling CloseIdleSessions, does not cause a crash.
+ // http://crbug.com/181400
+ spdy_session_pool_->CloseCurrentIdleSessions();
+
+ // Verify spdy_session and spdy_session1 are closed.
+ EXPECT_TRUE(session == NULL);
+ EXPECT_TRUE(session1 == NULL);
+ EXPECT_TRUE(session2->is_active());
+ EXPECT_FALSE(session2->IsClosed());
+
+ spdy_stream2->Cancel();
+ EXPECT_EQ(NULL, spdy_stream.get());
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(NULL, spdy_stream2.get());
+ session2->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_TRUE(session2 == NULL);
+ break;
+ }
+
+ // Verify that the map is all cleaned up.
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
+}
+
+TEST_P(SpdySessionPoolTest, IPPooling) {
+ RunIPPoolingTest(SPDY_POOL_CLOSE_SESSIONS_MANUALLY);
+}
+
+TEST_P(SpdySessionPoolTest, IPPoolingCloseCurrentSessions) {
+ RunIPPoolingTest(SPDY_POOL_CLOSE_CURRENT_SESSIONS);
+}
+
+TEST_P(SpdySessionPoolTest, IPPoolingCloseIdleSessions) {
+ RunIPPoolingTest(SPDY_POOL_CLOSE_IDLE_SESSIONS);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_session_test_util.cc b/chromium/net/spdy/spdy_session_test_util.cc
new file mode 100644
index 00000000000..d901cf64c41
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_test_util.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2013 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/spdy/spdy_session_test_util.h"
+
+#include "base/location.h"
+#include "base/strings/string_util.h"
+
+namespace net {
+
+SpdySessionTestTaskObserver::SpdySessionTestTaskObserver(
+ const std::string& file_name,
+ const std::string& function_name)
+ : executed_count_(0),
+ file_name_(file_name),
+ function_name_(function_name) {
+ base::MessageLoop::current()->AddTaskObserver(this);
+}
+
+SpdySessionTestTaskObserver::~SpdySessionTestTaskObserver() {
+ base::MessageLoop::current()->RemoveTaskObserver(this);
+}
+
+void SpdySessionTestTaskObserver::WillProcessTask(
+ const base::PendingTask& pending_task) {
+}
+
+void SpdySessionTestTaskObserver::DidProcessTask(
+ const base::PendingTask& pending_task) {
+ if (EndsWith(pending_task.posted_from.file_name(), file_name_, true) &&
+ EndsWith(pending_task.posted_from.function_name(), function_name_,
+ true)) {
+ ++executed_count_;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_session_test_util.h b/chromium/net/spdy/spdy_session_test_util.h
new file mode 100644
index 00000000000..08a82ee69dd
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_test_util.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 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_SPDY_SPDY_SESSION_TEST_UTIL_H_
+#define NET_SPDY_SPDY_SESSION_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/message_loop/message_loop.h"
+#include "base/pending_task.h"
+
+namespace net {
+
+// SpdySessionTestTaskObserver is a MessageLoop::TaskObserver that monitors the
+// completion of all tasks executed by the current MessageLoop, recording the
+// number of tasks that refer to a specific function and filename.
+class SpdySessionTestTaskObserver : public base::MessageLoop::TaskObserver {
+ public:
+ // Creates a SpdySessionTaskObserver that will record all tasks that are
+ // executed that were posted by the function named by |function_name|, located
+ // in the file |file_name|.
+ // Example:
+ // file_name = "foo.cc"
+ // function = "DoFoo"
+ SpdySessionTestTaskObserver(const std::string& file_name,
+ const std::string& function_name);
+ virtual ~SpdySessionTestTaskObserver();
+
+ // Implements MessageLoop::TaskObserver.
+ virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE;
+ virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE;
+
+ // Returns the number of tasks posted by the given function and file.
+ uint16 executed_count() const { return executed_count_; }
+
+ private:
+ uint16 executed_count_;
+ std::string file_name_;
+ std::string function_name_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_SESSION_TEST_UTIL_H_
diff --git a/chromium/net/spdy/spdy_session_unittest.cc b/chromium/net/spdy/spdy_session_unittest.cc
new file mode 100644
index 00000000000..f0d448cd700
--- /dev/null
+++ b/chromium/net/spdy/spdy_session_unittest.cc
@@ -0,0 +1,4116 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_session.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_data_directory.h"
+#include "net/base/test_data_stream.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_session_test_util.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "net/spdy/spdy_test_utils.h"
+#include "net/test/cert_test_util.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+static const char kTestUrl[] = "http://www.example.org/";
+static const char kTestHost[] = "www.example.org";
+static const int kTestPort = 80;
+
+const char kBodyData[] = "Body data";
+const size_t kBodyDataSize = arraysize(kBodyData);
+const base::StringPiece kBodyDataStringPiece(kBodyData, kBodyDataSize);
+
+static base::TimeDelta g_time_delta;
+base::TimeTicks TheNearFuture() {
+ return base::TimeTicks::Now() + g_time_delta;
+}
+
+} // namespace
+
+class SpdySessionTest : public PlatformTest,
+ public ::testing::WithParamInterface<NextProto> {
+ public:
+ // Functions used with RunResumeAfterUnstallTest().
+
+ void StallSessionOnly(SpdySession* session, SpdyStream* stream) {
+ StallSessionSend(session);
+ }
+
+ void StallStreamOnly(SpdySession* session, SpdyStream* stream) {
+ StallStreamSend(stream);
+ }
+
+ void StallSessionStream(SpdySession* session, SpdyStream* stream) {
+ StallSessionSend(session);
+ StallStreamSend(stream);
+ }
+
+ void StallStreamSession(SpdySession* session, SpdyStream* stream) {
+ StallStreamSend(stream);
+ StallSessionSend(session);
+ }
+
+ void UnstallSessionOnly(SpdySession* session,
+ SpdyStream* stream,
+ int32 delta_window_size) {
+ UnstallSessionSend(session, delta_window_size);
+ }
+
+ void UnstallStreamOnly(SpdySession* session,
+ SpdyStream* stream,
+ int32 delta_window_size) {
+ UnstallStreamSend(stream, delta_window_size);
+ }
+
+ void UnstallSessionStream(SpdySession* session,
+ SpdyStream* stream,
+ int32 delta_window_size) {
+ UnstallSessionSend(session, delta_window_size);
+ UnstallStreamSend(stream, delta_window_size);
+ }
+
+ void UnstallStreamSession(SpdySession* session,
+ SpdyStream* stream,
+ int32 delta_window_size) {
+ UnstallStreamSend(stream, delta_window_size);
+ UnstallSessionSend(session, delta_window_size);
+ }
+
+ protected:
+ SpdySessionTest()
+ : old_max_group_sockets_(ClientSocketPoolManager::max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL)),
+ old_max_pool_sockets_(ClientSocketPoolManager::max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL)),
+ spdy_util_(GetParam()),
+ session_deps_(GetParam()),
+ spdy_session_pool_(NULL),
+ test_url_(kTestUrl),
+ test_host_port_pair_(kTestHost, kTestPort),
+ key_(test_host_port_pair_, ProxyServer::Direct(),
+ kPrivacyModeDisabled) {
+ }
+
+ virtual ~SpdySessionTest() {
+ // Important to restore the per-pool limit first, since the pool limit must
+ // always be greater than group limit, and the tests reduce both limits.
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_pool_sockets_);
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_group_sockets_);
+ }
+
+ virtual void SetUp() OVERRIDE {
+ g_time_delta = base::TimeDelta();
+ }
+
+ void CreateDeterministicNetworkSession() {
+ http_session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+ spdy_session_pool_ = http_session_->spdy_session_pool();
+ }
+
+ void CreateNetworkSession() {
+ http_session_ =
+ SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ spdy_session_pool_ = http_session_->spdy_session_pool();
+ }
+
+ void StallSessionSend(SpdySession* session) {
+ // Reduce the send window size to 0 to stall.
+ while (session->session_send_window_size_ > 0) {
+ session->DecreaseSendWindowSize(
+ std::min(kMaxSpdyFrameChunkSize, session->session_send_window_size_));
+ }
+ }
+
+ void UnstallSessionSend(SpdySession* session, int32 delta_window_size) {
+ session->IncreaseSendWindowSize(delta_window_size);
+ }
+
+ void StallStreamSend(SpdyStream* stream) {
+ // Reduce the send window size to 0 to stall.
+ while (stream->send_window_size() > 0) {
+ stream->DecreaseSendWindowSize(
+ std::min(kMaxSpdyFrameChunkSize, stream->send_window_size()));
+ }
+ }
+
+ void UnstallStreamSend(SpdyStream* stream, int32 delta_window_size) {
+ stream->IncreaseSendWindowSize(delta_window_size);
+ }
+
+ void RunResumeAfterUnstallTest(
+ const base::Callback<void(SpdySession*, SpdyStream*)>& stall_function,
+ const base::Callback<void(SpdySession*, SpdyStream*, int32)>&
+ unstall_function);
+
+ // Original socket limits. Some tests set these. Safest to always restore
+ // them once each test has been run.
+ int old_max_group_sockets_;
+ int old_max_pool_sockets_;
+
+ SpdyTestUtil spdy_util_;
+ SpdySessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ SpdySessionPool* spdy_session_pool_;
+ GURL test_url_;
+ HostPortPair test_host_port_pair_;
+ SpdySessionKey key_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdySessionTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+// Try to create a SPDY session that will fail during
+// initialization. Nothing should blow up.
+TEST_P(SpdySessionTest, InitialReadError) {
+ CreateDeterministicNetworkSession();
+
+ TryCreateFakeSpdySessionExpectingFailure(
+ spdy_session_pool_, key_, ERR_FAILED);
+}
+
+// A session receiving a GOAWAY frame with no active streams should
+// immediately close.
+TEST_P(SpdySessionTest, GoAwayWithNoActiveStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 0),
+ };
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// A session receiving a GOAWAY frame immediately with no active
+// streams should then close.
+TEST_P(SpdySessionTest, GoAwayImmediatelyWithNoActiveStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 0, SYNCHRONOUS),
+ };
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ data.StopAfter(1);
+
+ TryCreateInsecureSpdySessionExpectingFailure(
+ http_session_, key_, ERR_CONNECTION_CLOSED, BoundNetLog());
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+}
+
+// A session receiving a GOAWAY frame with active streams should close
+// when the last active stream is closed.
+TEST_P(SpdySessionTest, GoAwayWithActiveStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers));
+
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_FALSE(session->IsStreamActive(3));
+ EXPECT_EQ(NULL, spdy_stream2.get());
+ EXPECT_TRUE(session->IsStreamActive(1));
+
+ EXPECT_FALSE(session->IsClosed());
+
+ // Should close the session.
+ spdy_stream1->Close();
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Have a session receive two GOAWAY frames, with the last one causing
+// the last active stream to be closed. The session should then be
+// closed after the second GOAWAY frame.
+TEST_P(SpdySessionTest, GoAwayTwice) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway1(spdy_util_.ConstructSpdyGoAway(1));
+ scoped_ptr<SpdyFrame> goaway2(spdy_util_.ConstructSpdyGoAway(0));
+ MockRead reads[] = {
+ CreateMockRead(*goaway1, 2),
+ CreateMockRead(*goaway2, 3),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers));
+
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the first GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_FALSE(session->IsStreamActive(3));
+ EXPECT_EQ(NULL, spdy_stream2.get());
+ EXPECT_TRUE(session->IsStreamActive(1));
+
+ EXPECT_FALSE(session->IsClosed());
+
+ // Read and process the second GOAWAY frame, which should close the
+ // session.
+ data.RunFor(1);
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Have a session with active streams receive a GOAWAY frame and then
+// close it. It should handle the close properly (i.e., not try to
+// make itself unavailable in its pool twice).
+TEST_P(SpdySessionTest, GoAwayWithActiveStreamsThenClose) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock(*headers));
+
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_FALSE(session->IsStreamActive(3));
+ EXPECT_EQ(NULL, spdy_stream2.get());
+ EXPECT_TRUE(session->IsStreamActive(1));
+
+ EXPECT_FALSE(session->IsClosed());
+
+ session->CloseSessionOnError(ERR_ABORTED, "Aborting session");
+
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_TRUE(session == NULL);
+}
+
+// Try to create a stream after receiving a GOAWAY frame. It should
+// fail.
+TEST_P(SpdySessionTest, CreateStreamAfterGoAway) {
+ const char kStreamUrl[] = "http://www.google.com";
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 1),
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ GURL url(kStreamUrl);
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate(spdy_stream);
+ spdy_stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream->HasUrlFromHeaders());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(1u, spdy_stream->stream_id());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+ EXPECT_TRUE(session->IsStreamActive(1));
+
+ SpdyStreamRequest stream_request;
+ int rv = stream_request.StartRequest(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, MEDIUM, BoundNetLog(),
+ CompletionCallback());
+ EXPECT_EQ(ERR_FAILED, rv);
+
+ // Read and process EOF.
+ data.RunFor(1);
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Receiving a SYN_STREAM frame after a GOAWAY frame should result in
+// the stream being refused.
+TEST_P(SpdySessionTest, SynStreamAfterGoAway) {
+ const char kStreamUrl[] = "http://www.google.com";
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway(1));
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ MockRead reads[] = {
+ CreateMockRead(*goaway, 1),
+ CreateMockRead(*push, 2),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*rst, 3)
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version(), session->GetProtocolVersion());
+
+ GURL url(kStreamUrl);
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate(spdy_stream);
+ spdy_stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream->HasUrlFromHeaders());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(1u, spdy_stream->stream_id());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Read and process the GOAWAY frame.
+ data.RunFor(1);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+ EXPECT_TRUE(session->IsStreamActive(1));
+
+ // Read and process the SYN_STREAM frame, the subsequent RST_STREAM,
+ // and EOF.
+ data.RunFor(3);
+
+ EXPECT_TRUE(session == NULL);
+}
+
+TEST_P(SpdySessionTest, ClientPing) {
+ session_deps_.enable_ping = true;
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(spdy_util_.ConstructSpdyPing(1));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping, 1),
+ MockRead(ASYNC, 0, 0, 2) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1));
+ MockWrite writes[] = {
+ CreateMockWrite(*write_ping, 0),
+ };
+ DeterministicSocketData data(
+ reads, arraysize(reads), writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL);
+ spdy_stream1->SetDelegate(&delegate);
+
+ base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+ session->set_connection_at_risk_of_loss_time(
+ base::TimeDelta::FromSeconds(-1));
+ session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
+
+ session->SendPrefacePingIfNoneInFlight();
+
+ data.RunFor(2);
+
+ session->CheckPingStatus(before_ping_time);
+
+ EXPECT_EQ(0, session->pings_in_flight());
+ EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+ EXPECT_FALSE(session->check_ping_status_pending());
+ EXPECT_GE(session->last_activity_time(), before_ping_time);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+ EXPECT_TRUE(session == NULL);
+}
+
+TEST_P(SpdySessionTest, ServerPing) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> read_ping(spdy_util_.ConstructSpdyPing(2));
+ MockRead reads[] = {
+ CreateMockRead(*read_ping),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(2));
+ MockWrite writes[] = {
+ CreateMockWrite(*write_ping),
+ };
+ StaticSocketDataProvider data(
+ reads, arraysize(reads), writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL);
+ spdy_stream1->SetDelegate(&delegate);
+
+ // Flush the read completion task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_TRUE(session == NULL);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+}
+
+// Cause a ping to be sent out while producing a write. The write loop
+// should handle this properly, i.e. another DoWriteLoop task should
+// not be posted. This is a regression test for
+// http://crbug.com/261043 .
+TEST_P(SpdySessionTest, PingAndWriteLoop) {
+ session_deps_.enable_ping = true;
+ session_deps_.time_func = TheNearFuture;
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1));
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*write_ping, 1),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ test::StreamDelegateDoNothing delegate(spdy_stream);
+ spdy_stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+
+ // Shift time so that a ping will be sent out.
+ g_time_delta = base::TimeDelta::FromSeconds(11);
+
+ data.RunFor(2);
+
+ session->CloseSessionOnError(ERR_ABORTED, "Aborting");
+}
+
+TEST_P(SpdySessionTest, DeleteExpiredPushStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+ session_deps_.time_func = TheNearFuture;
+
+ CreateNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateFakeSpdySession(spdy_session_pool_, key_);
+
+ session->buffered_spdy_framer_.reset(
+ new BufferedSpdyFramer(spdy_util_.spdy_version(), false));
+
+ // Create the associated stream and add to active streams.
+ scoped_ptr<SpdyHeaderBlock> request_headers(
+ spdy_util_.ConstructGetHeaderBlock("http://www.google.com/"));
+
+ scoped_ptr<SpdyStream> stream(new SpdyStream(SPDY_REQUEST_RESPONSE_STREAM,
+ session,
+ GURL(),
+ DEFAULT_PRIORITY,
+ kSpdyStreamInitialWindowSize,
+ kSpdyStreamInitialWindowSize,
+ session->net_log_));
+ stream->SendRequestHeaders(request_headers.Pass(), NO_MORE_DATA_TO_SEND);
+ SpdyStream* stream_ptr = stream.get();
+ session->InsertCreatedStream(stream.Pass());
+ stream = session->ActivateCreatedStream(stream_ptr);
+ session->InsertActivatedStream(stream.Pass());
+
+ SpdyHeaderBlock headers;
+ spdy_util_.AddUrlToHeaderBlock("http://www.google.com/a.dat", &headers);
+
+ // OnSynStream() expects |in_io_loop_| to be true.
+ session->in_io_loop_ = true;
+ session->OnSynStream(2, 1, 0, 0, true, false, headers);
+ session->in_io_loop_ = false;
+
+ // Verify that there is one unclaimed push stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ SpdySession::PushedStreamMap::iterator iter =
+ session->unclaimed_pushed_streams_.find(
+ GURL("http://www.google.com/a.dat"));
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+ // Shift time to expire the push stream.
+ g_time_delta = base::TimeDelta::FromSeconds(301);
+
+ spdy_util_.AddUrlToHeaderBlock("http://www.google.com/b.dat", &headers);
+ session->in_io_loop_ = true;
+ session->OnSynStream(4, 1, 0, 0, true, false, headers);
+ session->in_io_loop_ = false;
+
+ // Verify that the second pushed stream evicted the first pushed stream.
+ EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+ iter = session->unclaimed_pushed_streams_.find(
+ GURL("http://www.google.com/b.dat"));
+ EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+}
+
+TEST_P(SpdySessionTest, FailedPing) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0, 0) // EOF
+ };
+ scoped_ptr<SpdyFrame> write_ping(spdy_util_.ConstructSpdyPing(1));
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ test::StreamDelegateSendImmediate delegate(spdy_stream1, NULL);
+ spdy_stream1->SetDelegate(&delegate);
+
+ session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+ session->set_hung_interval(base::TimeDelta::FromSeconds(0));
+
+ // Send a PING frame.
+ session->WritePingFrame(1);
+ EXPECT_LT(0, session->pings_in_flight());
+ EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+ EXPECT_TRUE(session->check_ping_status_pending());
+
+ // Assert session is not closed.
+ EXPECT_FALSE(session->IsClosed());
+ EXPECT_LT(0u, session->num_active_streams() + session->num_created_streams());
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // We set last time we have received any data in 1 sec less than now.
+ // CheckPingStatus will trigger timeout because hung interval is zero.
+ base::TimeTicks now = base::TimeTicks::Now();
+ session->last_activity_time_ = now - base::TimeDelta::FromSeconds(1);
+ session->CheckPingStatus(now);
+
+ EXPECT_TRUE(session == NULL);
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ data.RunFor(1);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+}
+
+// Request kInitialMaxConcurrentStreams + 1 streams. Receive a
+// settings frame increasing the max concurrent streams by 1. Make
+// sure nothing blows up. This is a regression test for
+// http://crbug.com/57331 .
+TEST_P(SpdySessionTest, OnSettings) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ const SpdySettingsIds kSpdySettingsIds = SETTINGS_MAX_CONCURRENT_STREAMS;
+
+ SettingsMap new_settings;
+ const uint32 max_concurrent_streams = kInitialMaxConcurrentStreams + 1;
+ new_settings[kSpdySettingsIds] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(new_settings));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ MockRead(ASYNC, 0, 1),
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ // Create the maximum number of concurrent streams.
+ for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) {
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream != NULL);
+ }
+
+ StreamReleaserCallback stream_releaser;
+ SpdyStreamRequest request;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM,
+ BoundNetLog(),
+ stream_releaser.MakeCallback(&request)));
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, stream_releaser.WaitForResult());
+
+ data.RunFor(1);
+ EXPECT_TRUE(session == NULL);
+}
+
+// Start with a persisted value for max concurrent streams. Receive a
+// settings frame increasing the max concurrent streams by 1 and which
+// also clears the persisted data. Verify that persisted data is
+// correct.
+TEST_P(SpdySessionTest, ClearSettings) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ SettingsMap new_settings;
+ const uint32 max_concurrent_streams = kInitialMaxConcurrentStreams + 1;
+ new_settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(new_settings));
+ uint8 flags = SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS;
+ test::SetFrameFlags(settings_frame.get(), flags, spdy_util_.spdy_version());
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ MockRead(ASYNC, 0, 1),
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ // Initialize the SpdySetting with the default.
+ spdy_session_pool_->http_server_properties()->SetSpdySetting(
+ test_host_port_pair_,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ kInitialMaxConcurrentStreams);
+
+ EXPECT_FALSE(
+ spdy_session_pool_->http_server_properties()->GetSpdySettings(
+ test_host_port_pair_).empty());
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ // Create the maximum number of concurrent streams.
+ for (size_t i = 0; i < kInitialMaxConcurrentStreams; ++i) {
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream != NULL);
+ }
+
+ StreamReleaserCallback stream_releaser;
+
+ SpdyStreamRequest request;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM,
+ BoundNetLog(),
+ stream_releaser.MakeCallback(&request)));
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, stream_releaser.WaitForResult());
+
+ // Make sure that persisted data is cleared.
+ EXPECT_TRUE(
+ spdy_session_pool_->http_server_properties()->GetSpdySettings(
+ test_host_port_pair_).empty());
+
+ // Make sure session's max_concurrent_streams is correct.
+ EXPECT_EQ(kInitialMaxConcurrentStreams + 1,
+ session->max_concurrent_streams());
+
+ data.RunFor(1);
+ EXPECT_TRUE(session == NULL);
+}
+
+// Start with max concurrent streams set to 1. Request two streams.
+// When the first completes, have the callback close its stream, which
+// should trigger the second stream creation. Then cancel that one
+// immediately. Don't crash. This is a regression test for
+// http://crbug.com/63532 .
+TEST_P(SpdySessionTest, CancelPendingCreateStream) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ // Initialize the SpdySetting with 1 max concurrent streams.
+ spdy_session_pool_->http_server_properties()->SetSpdySetting(
+ test_host_port_pair_,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 1);
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ // Leave room for only one more stream to be created.
+ for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) {
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream != NULL);
+ }
+
+ // Create 2 more streams. First will succeed. Second will be pending.
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+
+ // Use scoped_ptr to let us invalidate the memory when we want to, to trigger
+ // a valgrind error if the callback is invoked when it's not supposed to be.
+ scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback);
+
+ SpdyStreamRequest request;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, session, test_url_, MEDIUM,
+ BoundNetLog(),
+ callback->callback()));
+
+ // Release the first one, this will allow the second to be created.
+ spdy_stream1->Cancel();
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ request.CancelRequest();
+ callback.reset();
+
+ // Should not crash when running the pending callback.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_P(SpdySessionTest, SendInitialDataOnNewSession) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ SettingsMap settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+ const SpdySettingsIds kSpdySettingsIds2 = SETTINGS_INITIAL_WINDOW_SIZE;
+ const uint32 kInitialRecvWindowSize = 10 * 1024 * 1024;
+ settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+ if (spdy_util_.spdy_version() >= SPDY3) {
+ settings[kSpdySettingsIds2] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kInitialRecvWindowSize);
+ }
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ std::vector<MockWrite> writes;
+ if (GetParam() == kProtoHTTP2Draft04) {
+ writes.push_back(
+ MockWrite(ASYNC,
+ kHttp2ConnectionHeaderPrefix,
+ kHttp2ConnectionHeaderPrefixSize));
+ }
+ writes.push_back(CreateMockWrite(*settings_frame));
+ if (GetParam() >= kProtoSPDY31) {
+ writes.push_back(CreateMockWrite(*initial_window_update));
+ };
+
+ SettingsMap server_settings;
+ const uint32 initial_max_concurrent_streams = 1;
+ server_settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED,
+ initial_max_concurrent_streams);
+ scoped_ptr<SpdyFrame> server_settings_frame(
+ spdy_util_.ConstructSpdySettings(server_settings));
+ writes.push_back(CreateMockWrite(*server_settings_frame));
+
+ session_deps_.stream_initial_recv_window_size = kInitialRecvWindowSize;
+
+ StaticSocketDataProvider data(reads, arraysize(reads),
+ vector_as_array(&writes), writes.size());
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ spdy_session_pool_->http_server_properties()->SetSpdySetting(
+ test_host_port_pair_,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ initial_max_concurrent_streams);
+
+ SpdySessionPoolPeer pool_peer(spdy_session_pool_);
+ pool_peer.SetEnableSendingInitialData(true);
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdySessionTest, ClearSettingsStorageOnIPAddressChanged) {
+ CreateNetworkSession();
+
+ base::WeakPtr<HttpServerProperties> test_http_server_properties =
+ spdy_session_pool_->http_server_properties();
+ SettingsFlagsAndValue flags_and_value1(SETTINGS_FLAG_PLEASE_PERSIST, 2);
+ test_http_server_properties->SetSpdySetting(
+ test_host_port_pair_,
+ SETTINGS_MAX_CONCURRENT_STREAMS,
+ SETTINGS_FLAG_PLEASE_PERSIST,
+ 2);
+ EXPECT_NE(0u, test_http_server_properties->GetSpdySettings(
+ test_host_port_pair_).size());
+ spdy_session_pool_->OnIPAddressChanged();
+ EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings(
+ test_host_port_pair_).size());
+}
+
+TEST_P(SpdySessionTest, Initialize) {
+ CapturingBoundNetLog log;
+ session_deps_.net_log = log.bound().net_log();
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, log.bound());
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Flush the read completion task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_LT(0u, entries.size());
+
+ // Check that we logged TYPE_SPDY_SESSION_INITIALIZED correctly.
+ int pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_SPDY_SESSION_INITIALIZED,
+ net::NetLog::PHASE_NONE);
+ EXPECT_LT(0, pos);
+
+ CapturingNetLog::CapturedEntry entry = entries[pos];
+ NetLog::Source socket_source;
+ EXPECT_TRUE(NetLog::Source::FromEventParameters(entry.params.get(),
+ &socket_source));
+ EXPECT_TRUE(socket_source.IsValid());
+ EXPECT_NE(log.bound().source().id, socket_source.id);
+}
+
+TEST_P(SpdySessionTest, CloseSessionOnError) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway());
+ MockRead reads[] = {
+ CreateMockRead(*goaway),
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ CapturingBoundNetLog log;
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, log.bound());
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Flush the read completion task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+ EXPECT_TRUE(session == NULL);
+
+ // Check that the NetLog was filled reasonably.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_LT(0u, entries.size());
+
+ // Check that we logged SPDY_SESSION_CLOSE correctly.
+ int pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_SPDY_SESSION_CLOSE,
+ net::NetLog::PHASE_NONE);
+
+ if (pos < static_cast<int>(entries.size())) {
+ CapturingNetLog::CapturedEntry entry = entries[pos];
+ int error_code = 0;
+ ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
+ } else {
+ ADD_FAILURE();
+ }
+}
+
+// Queue up a low-priority SYN_STREAM followed by a high-priority
+// one. The high priority one should still send first and receive
+// first.
+TEST_P(SpdySessionTest, OutOfOrderSynStreams) {
+ // Construct the request.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> req_highest(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, HIGHEST, true));
+ scoped_ptr<SpdyFrame> req_lowest(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req_highest, 0),
+ CreateMockWrite(*req_lowest, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp_highest(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body_highest(
+ spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> resp_lowest(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body_lowest(
+ spdy_util_.ConstructSpdyBodyFrame(3, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp_highest, 2),
+ CreateMockRead(*body_highest, 3),
+ CreateMockRead(*resp_lowest, 4),
+ CreateMockRead(*body_lowest, 5),
+ MockRead(ASYNC, 0, 6) // EOF
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url("http://www.google.com");
+
+ base::WeakPtr<SpdyStream> spdy_stream_lowest =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream_lowest);
+ EXPECT_EQ(0u, spdy_stream_lowest->stream_id());
+ test::StreamDelegateDoNothing delegate_lowest(spdy_stream_lowest);
+ spdy_stream_lowest->SetDelegate(&delegate_lowest);
+
+ base::WeakPtr<SpdyStream> spdy_stream_highest =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, HIGHEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream_highest);
+ EXPECT_EQ(0u, spdy_stream_highest->stream_id());
+ test::StreamDelegateDoNothing delegate_highest(spdy_stream_highest);
+ spdy_stream_highest->SetDelegate(&delegate_highest);
+
+ // Queue the lower priority one first.
+
+ scoped_ptr<SpdyHeaderBlock> headers_lowest(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream_lowest->SendRequestHeaders(
+ headers_lowest.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream_lowest->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers_highest(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream_highest->SendRequestHeaders(
+ headers_highest.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream_highest->HasUrlFromHeaders());
+
+ data.RunFor(7);
+
+ EXPECT_FALSE(spdy_stream_lowest);
+ EXPECT_FALSE(spdy_stream_highest);
+ EXPECT_EQ(3u, delegate_lowest.stream_id());
+ EXPECT_EQ(1u, delegate_highest.stream_id());
+}
+
+TEST_P(SpdySessionTest, CancelStream) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ // Request 1, at HIGHEST priority, will be cancelled before it writes data.
+ // Request 2, at LOWEST priority, will be a full request and will be id 1.
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req2, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ MockRead reads[] = {
+ CreateMockRead(*resp2, 1),
+ CreateMockRead(*body2, 2),
+ MockRead(ASYNC, 0, 3) // EOF
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, HIGHEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ GURL url2("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url2, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+ test::StreamDelegateDoNothing delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ spdy_stream1->Cancel();
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ EXPECT_EQ(0u, delegate1.stream_id());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(0u, delegate1.stream_id());
+ EXPECT_EQ(1u, delegate2.stream_id());
+
+ spdy_stream2->Cancel();
+ EXPECT_EQ(NULL, spdy_stream2.get());
+}
+
+// Create two streams that are set to re-close themselves on close,
+// and then close the session. Nothing should blow up. Also a
+// regression test for http://crbug.com/139518 .
+TEST_P(SpdySessionTest, CloseSessionWithTwoCreatedSelfClosingStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ // No actual data will be sent.
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, 1) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url1, HIGHEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ GURL url2("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url2, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ test::ClosingDelegate delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ test::ClosingDelegate delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ // Ensure that the streams have not yet been activated and assigned an id.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ // Ensure we don't crash while closing the session.
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(NULL, spdy_stream2.get());
+
+ EXPECT_TRUE(delegate1.StreamIsClosed());
+ EXPECT_TRUE(delegate2.StreamIsClosed());
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Create two streams that are set to close each other on close, and
+// then close the session. Nothing should blow up.
+TEST_P(SpdySessionTest, CloseSessionWithTwoCreatedMutuallyClosingStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ // No actual data will be sent.
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, 1) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url1, HIGHEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ GURL url2("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url2, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ // Make |spdy_stream1| close |spdy_stream2|.
+ test::ClosingDelegate delegate1(spdy_stream2);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ // Make |spdy_stream2| close |spdy_stream1|.
+ test::ClosingDelegate delegate2(spdy_stream1);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ // Ensure that the streams have not yet been activated and assigned an id.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ // Ensure we don't crash while closing the session.
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(NULL, spdy_stream2.get());
+
+ EXPECT_TRUE(delegate1.StreamIsClosed());
+ EXPECT_TRUE(delegate2.StreamIsClosed());
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Create two streams that are set to re-close themselves on close,
+// activate them, and then close the session. Nothing should blow up.
+TEST_P(SpdySessionTest, CloseSessionWithTwoActivatedSelfClosingStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ GURL url2("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url2, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ test::ClosingDelegate delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ test::ClosingDelegate delegate2(spdy_stream2);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ // Ensure that the streams have not yet been activated and assigned an id.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+
+ // Ensure we don't crash while closing the session.
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(NULL, spdy_stream2.get());
+
+ EXPECT_TRUE(delegate1.StreamIsClosed());
+ EXPECT_TRUE(delegate2.StreamIsClosed());
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Create two streams that are set to close each other on close,
+// activate them, and then close the session. Nothing should blow up.
+TEST_P(SpdySessionTest, CloseSessionWithTwoActivatedMutuallyClosingStreams) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ GURL url2("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url2, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ // Make |spdy_stream1| close |spdy_stream2|.
+ test::ClosingDelegate delegate1(spdy_stream2);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ // Make |spdy_stream2| close |spdy_stream1|.
+ test::ClosingDelegate delegate2(spdy_stream1);
+ spdy_stream2->SetDelegate(&delegate2);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ spdy_stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream2->HasUrlFromHeaders());
+
+ // Ensure that the streams have not yet been activated and assigned an id.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+ EXPECT_EQ(3u, spdy_stream2->stream_id());
+
+ // Ensure we don't crash while closing the session.
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(NULL, spdy_stream2.get());
+
+ EXPECT_TRUE(delegate1.StreamIsClosed());
+ EXPECT_TRUE(delegate2.StreamIsClosed());
+
+ EXPECT_TRUE(session == NULL);
+}
+
+// Delegate that closes a given session when the stream is closed.
+class SessionClosingDelegate : public test::StreamDelegateDoNothing {
+ public:
+ SessionClosingDelegate(const base::WeakPtr<SpdyStream>& stream,
+ const base::WeakPtr<SpdySession>& session_to_close)
+ : StreamDelegateDoNothing(stream),
+ session_to_close_(session_to_close) {}
+
+ virtual ~SessionClosingDelegate() {}
+
+ virtual void OnClose(int status) OVERRIDE {
+ session_to_close_->CloseSessionOnError(ERR_ABORTED, "Aborted");
+ }
+
+ private:
+ base::WeakPtr<SpdySession> session_to_close_;
+};
+
+// Close an activated stream that closes its session. Nothing should
+// blow up. This is a regression test for http://crbug.com/263691 .
+TEST_P(SpdySessionTest, CloseActivatedStreamThatClosesSession) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 1) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream->stream_id());
+
+ SessionClosingDelegate delegate(spdy_stream, session);
+ spdy_stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream->HasUrlFromHeaders());
+
+ EXPECT_EQ(0u, spdy_stream->stream_id());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(1u, spdy_stream->stream_id());
+
+ // Ensure we don't crash while closing the stream (which closes the
+ // session).
+ spdy_stream->Cancel();
+
+ EXPECT_EQ(NULL, spdy_stream.get());
+ EXPECT_TRUE(delegate.StreamIsClosed());
+ EXPECT_TRUE(session == NULL);
+}
+
+TEST_P(SpdySessionTest, VerifyDomainAuthentication) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ // No actual data will be sent.
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, 1) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ // Load a cert that is valid for:
+ // www.example.org
+ // mail.example.org
+ // www.example.com
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ ssl.cert = test_cert;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateSecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
+ EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
+ EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.com"));
+ EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
+}
+
+TEST_P(SpdySessionTest, ConnectionPooledWithTlsChannelId) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ // No actual data will be sent.
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, 1) // EOF
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ // Load a cert that is valid for:
+ // www.example.org
+ // mail.example.org
+ // www.example.com
+ base::FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> test_cert(
+ ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ ssl.channel_id_sent = true;
+ ssl.cert = test_cert;
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateSecureSpdySession(http_session_, key_, BoundNetLog());
+
+ EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
+ EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
+ EXPECT_FALSE(session->VerifyDomainAuthentication("mail.example.com"));
+ EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
+}
+
+TEST_P(SpdySessionTest, CloseTwoStalledCreateStream) {
+ // TODO(rtenneti): Define a helper class/methods and move the common code in
+ // this file.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ SettingsMap new_settings;
+ const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+ const uint32 max_concurrent_streams = 1;
+ new_settings[kSpdySettingsIds1] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true));
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, LOWEST, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 1),
+ CreateMockWrite(*req2, 4),
+ CreateMockWrite(*req3, 7),
+ };
+
+ // Set up the socket so we read a SETTINGS frame that sets max concurrent
+ // streams to 1.
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(new_settings));
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true));
+
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5));
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true));
+
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame),
+ CreateMockRead(*resp1, 2),
+ CreateMockRead(*body1, 3),
+ CreateMockRead(*resp2, 5),
+ CreateMockRead(*body2, 6),
+ CreateMockRead(*resp3, 8),
+ CreateMockRead(*body3, 9),
+ MockRead(ASYNC, 0, 10) // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ // Read the settings frame.
+ data.RunFor(1);
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ SpdyStreamRequest request2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request2.StartRequest(
+ SPDY_REQUEST_RESPONSE_STREAM,
+ session, url2, LOWEST, BoundNetLog(), callback2.callback()));
+
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ SpdyStreamRequest request3;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request3.StartRequest(
+ SPDY_REQUEST_RESPONSE_STREAM,
+ session, url3, LOWEST, BoundNetLog(), callback3.callback()));
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(1u, session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queue_size(LOWEST));
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ // Run until 1st stream is activated and then closed.
+ EXPECT_EQ(0u, delegate1.stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_EQ(1u, delegate1.stream_id());
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(0u, session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST));
+
+ // Pump loop for SpdySession::ProcessPendingStreamRequests() to
+ // create the 2nd stream.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(1u, session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST));
+
+ base::WeakPtr<SpdyStream> stream2 = request2.ReleaseStream();
+ test::StreamDelegateDoNothing delegate2(stream2);
+ stream2->SetDelegate(&delegate2);
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructGetHeaderBlock(url2.spec()));
+ stream2->SendRequestHeaders(headers2.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(stream2->HasUrlFromHeaders());
+
+ // Run until 2nd stream is activated and then closed.
+ EXPECT_EQ(0u, delegate2.stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(NULL, stream2.get());
+ EXPECT_EQ(3u, delegate2.stream_id());
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(0u, session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST));
+
+ // Pump loop for SpdySession::ProcessPendingStreamRequests() to
+ // create the 3rd stream.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(1u, session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST));
+
+ base::WeakPtr<SpdyStream> stream3 = request3.ReleaseStream();
+ test::StreamDelegateDoNothing delegate3(stream3);
+ stream3->SetDelegate(&delegate3);
+ scoped_ptr<SpdyHeaderBlock> headers3(
+ spdy_util_.ConstructGetHeaderBlock(url3.spec()));
+ stream3->SendRequestHeaders(headers3.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(stream3->HasUrlFromHeaders());
+
+ // Run until 2nd stream is activated and then closed.
+ EXPECT_EQ(0u, delegate3.stream_id());
+ data.RunFor(3);
+ EXPECT_EQ(NULL, stream3.get());
+ EXPECT_EQ(5u, delegate3.stream_id());
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(0u, session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST));
+
+ data.RunFor(1);
+}
+
+TEST_P(SpdySessionTest, CancelTwoStalledCreateStream) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ // Leave room for only one more stream to be created.
+ for (size_t i = 0; i < kInitialMaxConcurrentStreams - 1; ++i) {
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream != NULL);
+ }
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url1, LOWEST, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ TestCompletionCallback callback2;
+ GURL url2("http://www.google.com");
+ SpdyStreamRequest request2;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request2.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, session, url2, LOWEST, BoundNetLog(),
+ callback2.callback()));
+
+ TestCompletionCallback callback3;
+ GURL url3("http://www.google.com");
+ SpdyStreamRequest request3;
+ ASSERT_EQ(ERR_IO_PENDING,
+ request3.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, session, url3, LOWEST, BoundNetLog(),
+ callback3.callback()));
+
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams());
+ EXPECT_EQ(2u, session->pending_create_stream_queue_size(LOWEST));
+
+ // Cancel the first stream; this will allow the second stream to be created.
+ EXPECT_TRUE(spdy_stream1.get() != NULL);
+ spdy_stream1->Cancel();
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams());
+ EXPECT_EQ(1u, session->pending_create_stream_queue_size(LOWEST));
+
+ // Cancel the second stream; this will allow the third stream to be created.
+ base::WeakPtr<SpdyStream> spdy_stream2 = request2.ReleaseStream();
+ spdy_stream2->Cancel();
+ EXPECT_EQ(NULL, spdy_stream2.get());
+
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(kInitialMaxConcurrentStreams, session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST));
+
+ // Cancel the third stream.
+ base::WeakPtr<SpdyStream> spdy_stream3 = request3.ReleaseStream();
+ spdy_stream3->Cancel();
+ EXPECT_EQ(NULL, spdy_stream3.get());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(kInitialMaxConcurrentStreams - 1, session->num_created_streams());
+ EXPECT_EQ(0u, session->pending_create_stream_queue_size(LOWEST));
+}
+
+TEST_P(SpdySessionTest, NeedsCredentials) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ ssl.channel_id_sent = true;
+ ssl.protocol_negotiated = GetParam();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ const GURL url("https://www.foo.com");
+ HostPortPair test_host_port_pair(url.host(), 443);
+ SpdySessionKey key(test_host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ base::WeakPtr<SpdySession> session =
+ CreateSecureSpdySession(http_session_, key, BoundNetLog());
+
+ EXPECT_EQ(spdy_util_.spdy_version() >= SPDY3, session->NeedsCredentials());
+
+ // Flush the read completion task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+}
+
+// Test that SpdySession::DoReadLoop reads data from the socket
+// without yielding. This test makes 32k - 1 bytes of data available
+// on the socket for reading. It then verifies that it has read all
+// the available data without yielding.
+TEST_P(SpdySessionTest, ReadDataWithoutYielding) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ // Build buffer of size kMaxReadBytesWithoutYielding / 4
+ // (-spdy_data_frame_size).
+ ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding);
+ const int kPayloadSize =
+ kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize();
+ TestDataStream test_stream;
+ scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize));
+ char* payload_data = payload->data();
+ test_stream.GetBytes(payload_data, kPayloadSize);
+
+ scoped_ptr<SpdyFrame> partial_data_frame(
+ framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> finish_data_frame(
+ framer.CreateDataFrame(1, payload_data, kPayloadSize - 1, DATA_FLAG_FIN));
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ // Write 1 byte less than kMaxReadBytes to check that DoRead reads up to 32k
+ // bytes.
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*partial_data_frame, 2),
+ CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS),
+ CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS),
+ CreateMockRead(*finish_data_frame, 5, SYNCHRONOUS),
+ MockRead(ASYNC, 0, 6) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ // Set up the TaskObserver to verify SpdySession::DoReadLoop doesn't
+ // post a task.
+ SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop");
+
+ // Run until 1st read.
+ EXPECT_EQ(0u, delegate1.stream_id());
+ data.RunFor(2);
+ EXPECT_EQ(1u, delegate1.stream_id());
+ EXPECT_EQ(0u, observer.executed_count());
+
+ // Read all the data and verify SpdySession::DoReadLoop has not
+ // posted a task.
+ data.RunFor(4);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ // Verify task observer's executed_count is zero, which indicates DoRead read
+ // all the available data.
+ EXPECT_EQ(0u, observer.executed_count());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+}
+
+// Test that SpdySession::DoReadLoop yields while reading the
+// data. This test makes 32k + 1 bytes of data available on the socket
+// for reading. It then verifies that DoRead has yielded even though
+// there is data available for it to read (i.e, socket()->Read didn't
+// return ERR_IO_PENDING during socket reads).
+TEST_P(SpdySessionTest, TestYieldingDuringReadData) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ // Build buffer of size kMaxReadBytesWithoutYielding / 4
+ // (-spdy_data_frame_size).
+ ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding);
+ const int kPayloadSize =
+ kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize();
+ TestDataStream test_stream;
+ scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize));
+ char* payload_data = payload->data();
+ test_stream.GetBytes(payload_data, kPayloadSize);
+
+ scoped_ptr<SpdyFrame> partial_data_frame(
+ framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> finish_data_frame(
+ framer.CreateDataFrame(1, "h", 1, DATA_FLAG_FIN));
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ // Write 1 byte more than kMaxReadBytes to check that DoRead yields.
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*partial_data_frame, 2),
+ CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS),
+ CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS),
+ CreateMockRead(*partial_data_frame, 5, SYNCHRONOUS),
+ CreateMockRead(*finish_data_frame, 6, SYNCHRONOUS),
+ MockRead(ASYNC, 0, 7) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ // Set up the TaskObserver to verify SpdySession::DoReadLoop posts a
+ // task.
+ SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop");
+
+ // Run until 1st read.
+ EXPECT_EQ(0u, delegate1.stream_id());
+ data.RunFor(2);
+ EXPECT_EQ(1u, delegate1.stream_id());
+ EXPECT_EQ(0u, observer.executed_count());
+
+ // Read all the data and verify SpdySession::DoReadLoop has posted a
+ // task.
+ data.RunFor(6);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ // Verify task observer's executed_count is 1, which indicates DoRead has
+ // posted only one task and thus yielded though there is data available for it
+ // to read.
+ EXPECT_EQ(1u, observer.executed_count());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+}
+
+// Test that SpdySession::DoReadLoop() tests interactions of yielding
+// + async, by doing the following MockReads.
+//
+// MockRead of SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K
+// ASYNC 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K.
+//
+// The above reads 26K synchronously. Since that is less that 32K, we
+// will attempt to read again. However, that DoRead() will return
+// ERR_IO_PENDING (because of async read), so DoReadLoop() will
+// yield. When we come back, DoRead() will read the results from the
+// async read, and rest of the data synchronously.
+TEST_P(SpdySessionTest, TestYieldingDuringAsyncReadData) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ // Build buffer of size kMaxReadBytesWithoutYielding / 4
+ // (-spdy_data_frame_size).
+ ASSERT_EQ(32 * 1024, kMaxReadBytesWithoutYielding);
+ TestDataStream test_stream;
+ const int kEightKPayloadSize =
+ kMaxReadBytesWithoutYielding / 4 - framer.GetControlFrameHeaderSize();
+ scoped_refptr<net::IOBuffer> eightk_payload(
+ new net::IOBuffer(kEightKPayloadSize));
+ char* eightk_payload_data = eightk_payload->data();
+ test_stream.GetBytes(eightk_payload_data, kEightKPayloadSize);
+
+ // Build buffer of 2k size.
+ TestDataStream test_stream2;
+ const int kTwoKPayloadSize = kEightKPayloadSize - 6 * 1024;
+ scoped_refptr<net::IOBuffer> twok_payload(
+ new net::IOBuffer(kTwoKPayloadSize));
+ char* twok_payload_data = twok_payload->data();
+ test_stream2.GetBytes(twok_payload_data, kTwoKPayloadSize);
+
+ scoped_ptr<SpdyFrame> eightk_data_frame(framer.CreateDataFrame(
+ 1, eightk_payload_data, kEightKPayloadSize, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> twok_data_frame(framer.CreateDataFrame(
+ 1, twok_payload_data, kTwoKPayloadSize, DATA_FLAG_NONE));
+ scoped_ptr<SpdyFrame> finish_data_frame(framer.CreateDataFrame(
+ 1, "h", 1, DATA_FLAG_FIN));
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*eightk_data_frame, 2),
+ CreateMockRead(*eightk_data_frame, 3, SYNCHRONOUS),
+ CreateMockRead(*eightk_data_frame, 4, SYNCHRONOUS),
+ CreateMockRead(*twok_data_frame, 5, SYNCHRONOUS),
+ CreateMockRead(*eightk_data_frame, 6, ASYNC),
+ CreateMockRead(*eightk_data_frame, 7, SYNCHRONOUS),
+ CreateMockRead(*eightk_data_frame, 8, SYNCHRONOUS),
+ CreateMockRead(*eightk_data_frame, 9, SYNCHRONOUS),
+ CreateMockRead(*twok_data_frame, 10, SYNCHRONOUS),
+ CreateMockRead(*finish_data_frame, 11, SYNCHRONOUS),
+ MockRead(ASYNC, 0, 12) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ // Set up the TaskObserver to monitor SpdySession::DoReadLoop
+ // posting of tasks.
+ SpdySessionTestTaskObserver observer("spdy_session.cc", "DoReadLoop");
+
+ // Run until 1st read.
+ EXPECT_EQ(0u, delegate1.stream_id());
+ data.RunFor(2);
+ EXPECT_EQ(1u, delegate1.stream_id());
+ EXPECT_EQ(0u, observer.executed_count());
+
+ // Read all the data and verify SpdySession::DoReadLoop has posted a
+ // task.
+ data.RunFor(12);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ // Verify task observer's executed_count is 1, which indicates DoRead has
+ // posted only one task and thus yielded though there is data available for
+ // it to read.
+ EXPECT_EQ(1u, observer.executed_count());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+}
+
+// Send a GoAway frame when SpdySession is in DoReadLoop. Make sure
+// nothing blows up.
+TEST_P(SpdySessionTest, GoAwayWhileInDoReadLoop) {
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true));
+ scoped_ptr<SpdyFrame> goaway(spdy_util_.ConstructSpdyGoAway());
+
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 1),
+ CreateMockRead(*body1, 2),
+ CreateMockRead(*goaway, 3),
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url1("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url1, MEDIUM, BoundNetLog());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ spdy_stream1->SendRequestHeaders(headers1.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ // Run until 1st read.
+ EXPECT_EQ(0u, spdy_stream1->stream_id());
+ data.RunFor(1);
+ EXPECT_EQ(1u, spdy_stream1->stream_id());
+
+ // Run until GoAway.
+ data.RunFor(3);
+ EXPECT_EQ(NULL, spdy_stream1.get());
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(session == NULL);
+}
+
+// Within this framework, a SpdySession should be initialized with
+// flow control disabled for protocol version 2, with flow control
+// enabled only for streams for protocol version 3, and with flow
+// control enabled for streams and sessions for higher versions.
+TEST_P(SpdySessionTest, ProtocolNegotiation) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ CreateNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateFakeSpdySession(spdy_session_pool_, key_);
+
+ EXPECT_EQ(spdy_util_.spdy_version(),
+ session->buffered_spdy_framer_->protocol_version());
+ if (GetParam() == kProtoSPDY2) {
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_NONE, session->flow_control_state());
+ EXPECT_EQ(0, session->session_send_window_size_);
+ EXPECT_EQ(0, session->session_recv_window_size_);
+ } else if (GetParam() == kProtoSPDY3) {
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM, session->flow_control_state());
+ EXPECT_EQ(0, session->session_send_window_size_);
+ EXPECT_EQ(0, session->session_recv_window_size_);
+ } else {
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+ EXPECT_EQ(kSpdySessionInitialWindowSize,
+ session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize,
+ session->session_recv_window_size_);
+ }
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+}
+
+// Tests the case of a non-SPDY request closing an idle SPDY session when no
+// pointers to the idle session are currently held.
+TEST_P(SpdySessionTest, CloseOneIdleConnection) {
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ CreateNetworkSession();
+
+ TransportClientSocketPool* pool =
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+
+ // Create an idle SPDY session.
+ SpdySessionKey key1(HostPortPair("1.com", 80), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session1 =
+ CreateInsecureSpdySession(http_session_, key1, BoundNetLog());
+ EXPECT_FALSE(pool->IsStalled());
+
+ // Trying to create a new connection should cause the pool to be stalled, and
+ // post a task asynchronously to try and close the session.
+ TestCompletionCallback callback2;
+ HostPortPair host_port2("2.com", 80);
+ scoped_refptr<TransportSocketParams> params2(
+ new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY,
+ callback2.callback(), pool, BoundNetLog()));
+ EXPECT_TRUE(pool->IsStalled());
+
+ // The socket pool should close the connection asynchronously and establish a
+ // new connection.
+ EXPECT_EQ(OK, callback2.WaitForResult());
+ EXPECT_FALSE(pool->IsStalled());
+ EXPECT_TRUE(session1 == NULL);
+}
+
+// Tests the case of a non-SPDY request closing an idle SPDY session when no
+// pointers to the idle session are currently held, in the case the SPDY session
+// has an alias.
+TEST_P(SpdySessionTest, CloseOneIdleConnectionWithAlias) {
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.host_resolver->rules()->AddIPLiteralRule(
+ "1.com", "192.168.0.2", std::string());
+ session_deps_.host_resolver->rules()->AddIPLiteralRule(
+ "2.com", "192.168.0.2", std::string());
+ // Not strictly needed.
+ session_deps_.host_resolver->rules()->AddIPLiteralRule(
+ "3.com", "192.168.0.3", std::string());
+
+ CreateNetworkSession();
+
+ TransportClientSocketPool* pool =
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+
+ // Create an idle SPDY session.
+ SpdySessionKey key1(HostPortPair("1.com", 80), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session1 =
+ CreateInsecureSpdySession(http_session_, key1, BoundNetLog());
+ EXPECT_FALSE(pool->IsStalled());
+
+ // Set up an alias for the idle SPDY session, increasing its ref count to 2.
+ SpdySessionKey key2(HostPortPair("2.com", 80), ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ HostResolver::RequestInfo info(key2.host_port_pair());
+ AddressList addresses;
+ // Pre-populate the DNS cache, since a synchronous resolution is required in
+ // order to create the alias.
+ session_deps_.host_resolver->Resolve(
+ info, &addresses, CompletionCallback(), NULL, BoundNetLog());
+ // Get a session for |key2|, which should return the session created earlier.
+ base::WeakPtr<SpdySession> session2 =
+ spdy_session_pool_->FindAvailableSession(key2, BoundNetLog());
+ ASSERT_EQ(session1.get(), session2.get());
+ EXPECT_FALSE(pool->IsStalled());
+
+ // Trying to create a new connection should cause the pool to be stalled, and
+ // post a task asynchronously to try and close the session.
+ TestCompletionCallback callback3;
+ HostPortPair host_port3("3.com", 80);
+ scoped_refptr<TransportSocketParams> params3(
+ new TransportSocketParams(host_port3, DEFAULT_PRIORITY, false, false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection3(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection3->Init(host_port3.ToString(), params3, DEFAULT_PRIORITY,
+ callback3.callback(), pool, BoundNetLog()));
+ EXPECT_TRUE(pool->IsStalled());
+
+ // The socket pool should close the connection asynchronously and establish a
+ // new connection.
+ EXPECT_EQ(OK, callback3.WaitForResult());
+ EXPECT_FALSE(pool->IsStalled());
+ EXPECT_TRUE(session1 == NULL);
+ EXPECT_TRUE(session2 == NULL);
+}
+
+// Tests that a non-SPDY request can't close a SPDY session that's currently in
+// use.
+TEST_P(SpdySessionTest, CloseOneIdleConnectionFailsWhenSessionInUse) {
+ ClientSocketPoolManager::set_max_sockets_per_group(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+ ClientSocketPoolManager::set_max_sockets_per_pool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL, 1);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ scoped_ptr<SpdyFrame> cancel1(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 1),
+ CreateMockWrite(*cancel1, 1),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ CreateNetworkSession();
+
+ TransportClientSocketPool* pool =
+ http_session_->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL);
+
+ // Create a SPDY session.
+ GURL url1("http://www.google.com");
+ SpdySessionKey key1(HostPortPair(url1.host(), 80),
+ ProxyServer::Direct(), kPrivacyModeDisabled);
+ base::WeakPtr<SpdySession> session1 =
+ CreateInsecureSpdySession(http_session_, key1, BoundNetLog());
+ EXPECT_FALSE(pool->IsStalled());
+
+ // Create a stream using the session, and send a request.
+
+ TestCompletionCallback callback1;
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session1, url1, DEFAULT_PRIORITY,
+ BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get());
+ test::StreamDelegateDoNothing delegate1(spdy_stream1);
+ spdy_stream1->SetDelegate(&delegate1);
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructGetHeaderBlock(url1.spec()));
+ EXPECT_EQ(ERR_IO_PENDING,
+ spdy_stream1->SendRequestHeaders(
+ headers1.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(spdy_stream1->HasUrlFromHeaders());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Trying to create a new connection should cause the pool to be stalled, and
+ // post a task asynchronously to try and close the session.
+ TestCompletionCallback callback2;
+ HostPortPair host_port2("2.com", 80);
+ scoped_refptr<TransportSocketParams> params2(
+ new TransportSocketParams(host_port2, DEFAULT_PRIORITY, false, false,
+ OnHostResolutionCallback()));
+ scoped_ptr<ClientSocketHandle> connection2(new ClientSocketHandle);
+ EXPECT_EQ(ERR_IO_PENDING,
+ connection2->Init(host_port2.ToString(), params2, DEFAULT_PRIORITY,
+ callback2.callback(), pool, BoundNetLog()));
+ EXPECT_TRUE(pool->IsStalled());
+
+ // Running the message loop should cause the socket pool to ask the SPDY
+ // session to close an idle socket, but since the socket is in use, nothing
+ // happens.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(pool->IsStalled());
+ EXPECT_FALSE(callback2.have_result());
+
+ // Cancelling the request should still not release the session's socket,
+ // since the session is still kept alive by the SpdySessionPool.
+ ASSERT_TRUE(spdy_stream1.get());
+ spdy_stream1->Cancel();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(pool->IsStalled());
+ EXPECT_FALSE(callback2.have_result());
+ EXPECT_TRUE(session1 != NULL);
+}
+
+// Verify that SpdySessionKey and therefore SpdySession is different when
+// privacy mode is enabled or disabled.
+TEST_P(SpdySessionTest, SpdySessionKeyPrivacyMode) {
+ CreateDeterministicNetworkSession();
+
+ HostPortPair host_port_pair("www.google.com", 443);
+ SpdySessionKey key_privacy_enabled(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeEnabled);
+ SpdySessionKey key_privacy_disabled(host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled));
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled));
+
+ // Add SpdySession with PrivacyMode Enabled to the pool.
+ base::WeakPtr<SpdySession> session_privacy_enabled =
+ CreateFakeSpdySession(spdy_session_pool_, key_privacy_enabled);
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled));
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled));
+
+ // Add SpdySession with PrivacyMode Disabled to the pool.
+ base::WeakPtr<SpdySession> session_privacy_disabled =
+ CreateFakeSpdySession(spdy_session_pool_, key_privacy_disabled);
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_enabled));
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled));
+
+ session_privacy_enabled->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled));
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_privacy_disabled));
+
+ session_privacy_disabled->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_enabled));
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_privacy_disabled));
+}
+
+// Delegate that creates another stream when its stream is closed.
+class StreamCreatingDelegate : public test::StreamDelegateDoNothing {
+ public:
+ StreamCreatingDelegate(const base::WeakPtr<SpdyStream>& stream,
+ const base::WeakPtr<SpdySession>& session)
+ : StreamDelegateDoNothing(stream),
+ session_(session) {}
+
+ virtual ~StreamCreatingDelegate() {}
+
+ virtual void OnClose(int status) OVERRIDE {
+ GURL url("http://www.google.com");
+ ignore_result(
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session_, url, MEDIUM, BoundNetLog()));
+ }
+
+ private:
+ const base::WeakPtr<SpdySession> session_;
+};
+
+// Create another stream in response to a stream being reset. Nothing
+// should blow up. This is a regression test for
+// http://crbug.com/263690 .
+TEST_P(SpdySessionTest, CreateStreamOnStreamReset) {
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, MEDIUM, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ };
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM));
+ MockRead reads[] = {
+ CreateMockRead(*rst, 1),
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url("http://www.google.com");
+ base::WeakPtr<SpdyStream> spdy_stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream.get() != NULL);
+ EXPECT_EQ(0u, spdy_stream->stream_id());
+
+ StreamCreatingDelegate delegate(spdy_stream, session);
+ spdy_stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(url.spec()));
+ spdy_stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND);
+ EXPECT_TRUE(spdy_stream->HasUrlFromHeaders());
+
+ EXPECT_EQ(0u, spdy_stream->stream_id());
+
+ data.RunFor(1);
+
+ EXPECT_EQ(1u, spdy_stream->stream_id());
+
+ // Cause the stream to be reset, which should cause another stream
+ // to be created.
+ data.RunFor(1);
+
+ EXPECT_EQ(NULL, spdy_stream.get());
+ EXPECT_TRUE(delegate.StreamIsClosed());
+ EXPECT_EQ(0u, session->num_active_streams());
+ EXPECT_EQ(1u, session->num_created_streams());
+}
+
+// The tests below are only for SPDY/3 and above.
+
+TEST_P(SpdySessionTest, SendCredentials) {
+ if (GetParam() < kProtoSPDY3)
+ return;
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
+ };
+ SettingsMap settings;
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(settings));
+ MockWrite writes[] = {
+ CreateMockWrite(*settings_frame),
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ ssl.channel_id_sent = true;
+ ssl.protocol_negotiated = GetParam();
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateNetworkSession();
+
+ const GURL kTestUrl("https://www.foo.com");
+ HostPortPair test_host_port_pair(kTestUrl.host(), 443);
+ SpdySessionKey key(test_host_port_pair, ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ base::WeakPtr<SpdySession> session =
+ CreateSecureSpdySession(http_session_, key, BoundNetLog());
+
+ EXPECT_TRUE(session->NeedsCredentials());
+
+ // Flush the read completion task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ session->CloseSessionOnError(ERR_ABORTED, std::string());
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key));
+}
+
+TEST_P(SpdySessionTest, UpdateStreamsSendWindowSize) {
+ if (GetParam() < kProtoSPDY3)
+ return;
+
+ // Set SETTINGS_INITIAL_WINDOW_SIZE to a small number so that WINDOW_UPDATE
+ // gets sent.
+ SettingsMap new_settings;
+ int32 window_size = 1;
+ new_settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, window_size);
+
+ // Set up the socket so we read a SETTINGS frame that sets
+ // INITIAL_WINDOW_SIZE.
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(new_settings));
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame, 0),
+ MockRead(ASYNC, 0, 1) // EOF
+ };
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ scoped_ptr<DeterministicSocketData> data(
+ new DeterministicSocketData(reads, arraysize(reads), NULL, 0));
+ data->set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(data.get());
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ base::WeakPtr<SpdyStream> spdy_stream1 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream1.get() != NULL);
+ TestCompletionCallback callback1;
+ EXPECT_NE(spdy_stream1->send_window_size(), window_size);
+
+ data->RunFor(1); // Process the SETTINGS frame, but not the EOF
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(session->stream_initial_send_window_size(), window_size);
+ EXPECT_EQ(spdy_stream1->send_window_size(), window_size);
+
+ // Release the first one, this will allow the second to be created.
+ spdy_stream1->Cancel();
+ EXPECT_EQ(NULL, spdy_stream1.get());
+
+ base::WeakPtr<SpdyStream> spdy_stream2 =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, test_url_, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(spdy_stream2.get() != NULL);
+ EXPECT_EQ(spdy_stream2->send_window_size(), window_size);
+ spdy_stream2->Cancel();
+ EXPECT_EQ(NULL, spdy_stream2.get());
+}
+
+// The tests below are only for SPDY/3.1 and above.
+
+// SpdySession::{Increase,Decrease}RecvWindowSize should properly
+// adjust the session receive window size for SPDY 3.1 and higher. In
+// addition, SpdySession::IncreaseRecvWindowSize should trigger
+// sending a WINDOW_UPDATE frame for a large enough delta.
+TEST_P(SpdySessionTest, AdjustRecvWindowSize) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ const int32 delta_window_size = 100;
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 1) // EOF
+ };
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kSpdySessionInitialWindowSize + delta_window_size));
+ MockWrite writes[] = {
+ CreateMockWrite(*window_update, 0),
+ };
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ session->IncreaseRecvWindowSize(delta_window_size);
+ EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size,
+ session->session_recv_window_size_);
+ EXPECT_EQ(delta_window_size, session->session_unacked_recv_window_bytes_);
+
+ // Should trigger sending a WINDOW_UPDATE frame.
+ session->IncreaseRecvWindowSize(kSpdySessionInitialWindowSize);
+ EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size +
+ kSpdySessionInitialWindowSize,
+ session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ // DecreaseRecvWindowSize() expects |in_io_loop_| to be true.
+ session->in_io_loop_ = true;
+ session->DecreaseRecvWindowSize(
+ kSpdySessionInitialWindowSize + delta_window_size +
+ kSpdySessionInitialWindowSize);
+ session->in_io_loop_ = false;
+ EXPECT_EQ(0, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+}
+
+// SpdySession::{Increase,Decrease}SendWindowSize should properly
+// adjust the session send window size when the "enable_spdy_31" flag
+// is set.
+TEST_P(SpdySessionTest, AdjustSendWindowSize) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 0, 0) // EOF
+ };
+ StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ CreateNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateFakeSpdySession(spdy_session_pool_, key_);
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ const int32 delta_window_size = 100;
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+
+ session->IncreaseSendWindowSize(delta_window_size);
+ EXPECT_EQ(kSpdySessionInitialWindowSize + delta_window_size,
+ session->session_send_window_size_);
+
+ session->DecreaseSendWindowSize(delta_window_size);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+}
+
+// Incoming data for an inactive stream should not cause the session
+// receive window size to decrease, but it should cause the unacked
+// bytes to increase.
+TEST_P(SpdySessionTest, SessionFlowControlInactiveStream) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyBodyFrame(1, false));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 0),
+ MockRead(ASYNC, 0, 1) // EOF
+ };
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0);
+ data.set_connect_data(connect_data);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(kUploadDataSize, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+}
+
+// A delegate that drops any received data.
+class DropReceivedDataDelegate : public test::StreamDelegateSendImmediate {
+ public:
+ DropReceivedDataDelegate(const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data)
+ : StreamDelegateSendImmediate(stream, data) {}
+
+ virtual ~DropReceivedDataDelegate() {}
+
+ // Drop any received data.
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {}
+};
+
+// Send data back and forth but use a delegate that drops its received
+// data. The receive window should still increase to its original
+// value, i.e. we shouldn't "leak" receive window bytes.
+TEST_P(SpdySessionTest, SessionFlowControlNoReceiveLeaks) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ const int32 msg_data_size = 100;
+ const std::string msg_data(msg_data_size, 'a');
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0));
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, msg_data.data(), msg_data_size, false));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*msg, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, msg_data.data(), msg_data_size, false));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId, msg_data_size));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*echo, 3),
+ MockRead(ASYNC, 0, 4) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url(kStreamUrl);
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+ EXPECT_EQ(0u, stream->stream_id());
+
+ DropReceivedDataDelegate delegate(stream, msg_data);
+ stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(4);
+
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_);
+
+ stream->Close();
+ EXPECT_EQ(NULL, stream.get());
+
+ EXPECT_EQ(OK, delegate.WaitForClose());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_);
+}
+
+// Send data back and forth but close the stream before its data frame
+// can be written to the socket. The send window should then increase
+// to its original value, i.e. we shouldn't "leak" send window bytes.
+TEST_P(SpdySessionTest, SessionFlowControlNoSendLeaks) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ const int32 msg_data_size = 100;
+ const std::string msg_data(msg_data_size, 'a');
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ MockRead(ASYNC, 0, 2) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url(kStreamUrl);
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+ EXPECT_EQ(0u, stream->stream_id());
+
+ test::StreamDelegateSendImmediate delegate(stream, msg_data);
+ stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_send_window_size_);
+
+ // Closing the stream should increase the session's send window.
+ stream->Close();
+ EXPECT_EQ(NULL, stream.get());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+
+ EXPECT_EQ(OK, delegate.WaitForClose());
+}
+
+// Send data back and forth; the send and receive windows should
+// change appropriately.
+TEST_P(SpdySessionTest, SessionFlowControlEndToEnd) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+
+ const int32 msg_data_size = 100;
+ const std::string msg_data(msg_data_size, 'a');
+
+ MockConnect connect_data(SYNCHRONOUS, OK);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, msg_data_size, MEDIUM, NULL, 0));
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, msg_data.data(), msg_data_size, false));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*msg, 2),
+ };
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, msg_data.data(), msg_data_size, false));
+ scoped_ptr<SpdyFrame> window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId, msg_data_size));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 1),
+ CreateMockRead(*echo, 3),
+ CreateMockRead(*window_update, 4),
+ MockRead(ASYNC, 0, 5) // EOF
+ };
+
+ // Create SpdySession and SpdyStream and send the request.
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ data.set_connect_data(connect_data);
+ session_deps_.host_resolver->set_synchronous_mode(true);
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+ session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+ CreateDeterministicNetworkSession();
+
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+
+ GURL url(kStreamUrl);
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+ EXPECT_EQ(0u, stream->stream_id());
+
+ test::StreamDelegateSendImmediate delegate(stream, msg_data);
+ stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(url.spec(), msg_data_size));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize - msg_data_size,
+ session->session_recv_window_size_);
+ EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
+
+ EXPECT_TRUE(data.at_write_eof());
+ EXPECT_TRUE(data.at_read_eof());
+
+ EXPECT_EQ(msg_data, delegate.TakeReceivedData());
+
+ // Draining the delegate's read queue should increase the session's
+ // receive window.
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_);
+
+ stream->Close();
+ EXPECT_EQ(NULL, stream.get());
+
+ EXPECT_EQ(OK, delegate.WaitForClose());
+
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_send_window_size_);
+ EXPECT_EQ(kSpdySessionInitialWindowSize, session->session_recv_window_size_);
+ EXPECT_EQ(msg_data_size, session->session_unacked_recv_window_bytes_);
+}
+
+// Given a stall function and an unstall function, runs a test to make
+// sure that a stream resumes after unstall.
+void SpdySessionTest::RunResumeAfterUnstallTest(
+ const base::Callback<void(SpdySession*, SpdyStream*)>& stall_function,
+ const base::Callback<void(SpdySession*, SpdyStream*, int32)>&
+ unstall_function) {
+ const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req, 0),
+ CreateMockWrite(*body, 1),
+ };
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, false));
+ MockRead reads[] = {
+ CreateMockRead(*resp, 2),
+ MockRead(ASYNC, 0, 0, 3), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ test::StreamDelegateWithBody delegate(stream, kBodyDataStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ stall_function.Run(session.get(), stream.get());
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+
+ unstall_function.Run(session.get(), stream.get(), kBodyDataSize);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ data.RunFor(3);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Run the resume-after-unstall test with all possible stall and
+// unstall sequences.
+
+TEST_P(SpdySessionTest, ResumeAfterUnstallSession) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallSessionOnly,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallSessionOnly,
+ base::Unretained(this)));
+}
+
+// Equivalent to
+// SpdyStreamTest.ResumeAfterSendWindowSizeIncrease.
+TEST_P(SpdySessionTest, ResumeAfterUnstallStream) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallStreamOnly,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallStreamOnly,
+ base::Unretained(this)));
+}
+
+TEST_P(SpdySessionTest, StallSessionStreamResumeAfterUnstallSessionStream) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallSessionStream,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallSessionStream,
+ base::Unretained(this)));
+}
+
+TEST_P(SpdySessionTest, StallStreamSessionResumeAfterUnstallSessionStream) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallStreamSession,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallSessionStream,
+ base::Unretained(this)));
+}
+
+TEST_P(SpdySessionTest, StallStreamSessionResumeAfterUnstallStreamSession) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallStreamSession,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallStreamSession,
+ base::Unretained(this)));
+}
+
+TEST_P(SpdySessionTest, StallSessionStreamResumeAfterUnstallStreamSession) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ RunResumeAfterUnstallTest(
+ base::Bind(&SpdySessionTest::StallSessionStream,
+ base::Unretained(this)),
+ base::Bind(&SpdySessionTest::UnstallStreamSession,
+ base::Unretained(this)));
+}
+
+// Cause a stall by reducing the flow control send window to 0. The
+// streams should resume in priority order when that window is then
+// increased.
+TEST_P(SpdySessionTest, ResumeByPriorityAfterSendWindowSizeIncrease) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 3, kBodyDataSize, MEDIUM, NULL, 0));
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, true));
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(3, kBodyData, kBodyDataSize, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ CreateMockWrite(*body2, 2),
+ CreateMockWrite(*body1, 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ MockRead reads[] = {
+ CreateMockRead(*resp1, 4),
+ CreateMockRead(*resp2, 5),
+ MockRead(ASYNC, 0, 0, 6), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ base::WeakPtr<SpdyStream> stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream1.get() != NULL);
+
+ test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece);
+ stream1->SetDelegate(&delegate1);
+
+ EXPECT_FALSE(stream1->HasUrlFromHeaders());
+
+ base::WeakPtr<SpdyStream> stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, MEDIUM, BoundNetLog());
+ ASSERT_TRUE(stream2.get() != NULL);
+
+ test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece);
+ stream2->SetDelegate(&delegate2);
+
+ EXPECT_FALSE(stream2->HasUrlFromHeaders());
+
+ EXPECT_FALSE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+
+ StallSessionSend(session.get());
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream1->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(1u, stream1->stream_id());
+ EXPECT_TRUE(stream1->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream2->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(3u, stream2->stream_id());
+ EXPECT_TRUE(stream2->send_stalled_by_flow_control());
+
+ // This should unstall only stream2.
+ UnstallSessionSend(session.get(), kBodyDataSize);
+
+ EXPECT_TRUE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+
+ // This should then unstall stream1.
+ UnstallSessionSend(session.get(), kBodyDataSize);
+
+ EXPECT_FALSE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+
+ data.RunFor(4);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose());
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose());
+
+ EXPECT_TRUE(delegate1.send_headers_completed());
+ EXPECT_EQ("200", delegate1.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate1.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate1.TakeReceivedData());
+
+ EXPECT_TRUE(delegate2.send_headers_completed());
+ EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate2.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate2.TakeReceivedData());
+
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Delegate that closes a given stream after sending its body.
+class StreamClosingDelegate : public test::StreamDelegateWithBody {
+ public:
+ StreamClosingDelegate(const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data)
+ : StreamDelegateWithBody(stream, data) {}
+
+ virtual ~StreamClosingDelegate() {}
+
+ void set_stream_to_close(const base::WeakPtr<SpdyStream>& stream_to_close) {
+ stream_to_close_ = stream_to_close;
+ }
+
+ virtual void OnDataSent() OVERRIDE {
+ test::StreamDelegateWithBody::OnDataSent();
+ if (stream_to_close_.get()) {
+ stream_to_close_->Close();
+ EXPECT_EQ(NULL, stream_to_close_.get());
+ }
+ }
+
+ private:
+ base::WeakPtr<SpdyStream> stream_to_close_;
+};
+
+// Cause a stall by reducing the flow control send window to
+// 0. Unstalling the session should properly handle deleted streams.
+TEST_P(SpdySessionTest, SendWindowSizeIncreaseWithDeletedStreams) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 3, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> req3(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 5, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body2(
+ spdy_util_.ConstructSpdyBodyFrame(3, kBodyData, kBodyDataSize, true));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ CreateMockWrite(*req3, 2),
+ CreateMockWrite(*body2, 3),
+ };
+
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
+ MockRead reads[] = {
+ CreateMockRead(*resp2, 4),
+ MockRead(ASYNC, 0, 0, 5), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ base::WeakPtr<SpdyStream> stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream1.get() != NULL);
+
+ test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece);
+ stream1->SetDelegate(&delegate1);
+
+ EXPECT_FALSE(stream1->HasUrlFromHeaders());
+
+ base::WeakPtr<SpdyStream> stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream2.get() != NULL);
+
+ StreamClosingDelegate delegate2(stream2, kBodyDataStringPiece);
+ stream2->SetDelegate(&delegate2);
+
+ EXPECT_FALSE(stream2->HasUrlFromHeaders());
+
+ base::WeakPtr<SpdyStream> stream3 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream3.get() != NULL);
+
+ test::StreamDelegateWithBody delegate3(stream3, kBodyDataStringPiece);
+ stream3->SetDelegate(&delegate3);
+
+ EXPECT_FALSE(stream3->HasUrlFromHeaders());
+
+ EXPECT_FALSE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream3->send_stalled_by_flow_control());
+
+ StallSessionSend(session.get());
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream1->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(1u, stream1->stream_id());
+ EXPECT_TRUE(stream1->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream2->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(3u, stream2->stream_id());
+ EXPECT_TRUE(stream2->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers3(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream3->SendRequestHeaders(headers3.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream3->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream3->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(5u, stream3->stream_id());
+ EXPECT_TRUE(stream3->send_stalled_by_flow_control());
+
+ SpdyStreamId stream_id1 = stream1->stream_id();
+ SpdyStreamId stream_id2 = stream2->stream_id();
+ SpdyStreamId stream_id3 = stream3->stream_id();
+
+ // Close stream1 preemptively.
+ session->CloseActiveStream(stream_id1, ERR_CONNECTION_CLOSED);
+ EXPECT_EQ(NULL, stream1.get());
+
+ EXPECT_FALSE(session->IsStreamActive(stream_id1));
+ EXPECT_TRUE(session->IsStreamActive(stream_id2));
+ EXPECT_TRUE(session->IsStreamActive(stream_id3));
+
+ // Unstall stream2, which should then close stream3.
+ delegate2.set_stream_to_close(stream3);
+ UnstallSessionSend(session.get(), kBodyDataSize);
+
+ data.RunFor(1);
+ EXPECT_EQ(NULL, stream3.get());
+
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+ EXPECT_FALSE(session->IsStreamActive(stream_id1));
+ EXPECT_TRUE(session->IsStreamActive(stream_id2));
+ EXPECT_FALSE(session->IsStreamActive(stream_id3));
+
+ data.RunFor(2);
+ EXPECT_EQ(NULL, stream2.get());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose());
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose());
+ EXPECT_EQ(OK, delegate3.WaitForClose());
+
+ EXPECT_TRUE(delegate1.send_headers_completed());
+ EXPECT_EQ(std::string(), delegate1.TakeReceivedData());
+
+ EXPECT_TRUE(delegate2.send_headers_completed());
+ EXPECT_EQ("200", delegate2.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate2.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate2.TakeReceivedData());
+
+ EXPECT_TRUE(delegate3.send_headers_completed());
+ EXPECT_EQ(std::string(), delegate3.TakeReceivedData());
+
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Cause a stall by reducing the flow control send window to
+// 0. Unstalling the session should properly handle the session itself
+// being closed.
+TEST_P(SpdySessionTest, SendWindowSizeIncreaseWithDeletedSession) {
+ if (GetParam() < kProtoSPDY31)
+ return;
+
+ const char kStreamUrl[] = "http://www.google.com/";
+ GURL url(kStreamUrl);
+
+ session_deps_.host_resolver->set_synchronous_mode(true);
+
+ scoped_ptr<SpdyFrame> req1(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> req2(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 3, kBodyDataSize, LOWEST, NULL, 0));
+ scoped_ptr<SpdyFrame> body1(
+ spdy_util_.ConstructSpdyBodyFrame(1, kBodyData, kBodyDataSize, false));
+ MockWrite writes[] = {
+ CreateMockWrite(*req1, 0),
+ CreateMockWrite(*req2, 1),
+ };
+
+ MockRead reads[] = {
+ MockRead(ASYNC, 0, 0, 2), // EOF
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ CreateDeterministicNetworkSession();
+ base::WeakPtr<SpdySession> session =
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog());
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION,
+ session->flow_control_state());
+
+ base::WeakPtr<SpdyStream> stream1 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream1.get() != NULL);
+
+ test::StreamDelegateWithBody delegate1(stream1, kBodyDataStringPiece);
+ stream1->SetDelegate(&delegate1);
+
+ EXPECT_FALSE(stream1->HasUrlFromHeaders());
+
+ base::WeakPtr<SpdyStream> stream2 =
+ CreateStreamSynchronously(SPDY_REQUEST_RESPONSE_STREAM,
+ session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream2.get() != NULL);
+
+ test::StreamDelegateWithBody delegate2(stream2, kBodyDataStringPiece);
+ stream2->SetDelegate(&delegate2);
+
+ EXPECT_FALSE(stream2->HasUrlFromHeaders());
+
+ EXPECT_FALSE(stream1->send_stalled_by_flow_control());
+ EXPECT_FALSE(stream2->send_stalled_by_flow_control());
+
+ StallSessionSend(session.get());
+
+ scoped_ptr<SpdyHeaderBlock> headers1(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream1->SendRequestHeaders(headers1.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream1->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream1->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(1u, stream1->stream_id());
+ EXPECT_TRUE(stream1->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers2(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kBodyDataSize));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream2->SendRequestHeaders(headers2.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream2->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream2->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+ EXPECT_EQ(3u, stream2->stream_id());
+ EXPECT_TRUE(stream2->send_stalled_by_flow_control());
+
+ EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key_));
+
+ // Unstall stream1.
+ UnstallSessionSend(session.get(), kBodyDataSize);
+
+ // Close the session (since we can't do it from within the delegate
+ // method, since it's in the stream's loop).
+ session->CloseSessionOnError(ERR_CONNECTION_CLOSED, "Closing session");
+ EXPECT_TRUE(session == NULL);
+
+ EXPECT_FALSE(HasSpdySession(spdy_session_pool_, key_));
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate1.WaitForClose());
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate2.WaitForClose());
+
+ EXPECT_TRUE(delegate1.send_headers_completed());
+ EXPECT_EQ(std::string(), delegate1.TakeReceivedData());
+
+ EXPECT_TRUE(delegate2.send_headers_completed());
+ EXPECT_EQ(std::string(), delegate2.TakeReceivedData());
+
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_stream.cc b/chromium/net/spdy/spdy_stream.cc
new file mode 100644
index 00000000000..a603a6c93e0
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream.cc
@@ -0,0 +1,1035 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_stream.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "net/spdy/spdy_buffer_producer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogSpdyStreamErrorCallback(SpdyStreamId stream_id,
+ int status,
+ const std::string* description,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", static_cast<int>(stream_id));
+ dict->SetInteger("status", status);
+ dict->SetString("description", *description);
+ return dict;
+}
+
+base::Value* NetLogSpdyStreamWindowUpdateCallback(
+ SpdyStreamId stream_id,
+ int32 delta,
+ int32 window_size,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("stream_id", stream_id);
+ dict->SetInteger("delta", delta);
+ dict->SetInteger("window_size", window_size);
+ return dict;
+}
+
+bool ContainsUppercaseAscii(const std::string& str) {
+ for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) {
+ if (*i >= 'A' && *i <= 'Z') {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+// A wrapper around a stream that calls into ProduceSynStreamFrame().
+class SpdyStream::SynStreamBufferProducer : public SpdyBufferProducer {
+ public:
+ SynStreamBufferProducer(const base::WeakPtr<SpdyStream>& stream)
+ : stream_(stream) {
+ DCHECK(stream_.get());
+ }
+
+ virtual ~SynStreamBufferProducer() {}
+
+ virtual scoped_ptr<SpdyBuffer> ProduceBuffer() OVERRIDE {
+ if (!stream_.get()) {
+ NOTREACHED();
+ return scoped_ptr<SpdyBuffer>();
+ }
+ DCHECK_GT(stream_->stream_id(), 0u);
+ return scoped_ptr<SpdyBuffer>(
+ new SpdyBuffer(stream_->ProduceSynStreamFrame()));
+ }
+
+ private:
+ const base::WeakPtr<SpdyStream> stream_;
+};
+
+SpdyStream::SpdyStream(SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ int32 initial_send_window_size,
+ int32 initial_recv_window_size,
+ const BoundNetLog& net_log)
+ : type_(type),
+ weak_ptr_factory_(this),
+ in_do_loop_(false),
+ continue_buffering_data_(type_ == SPDY_PUSH_STREAM),
+ stream_id_(0),
+ url_(url),
+ priority_(priority),
+ slot_(0),
+ send_stalled_by_flow_control_(false),
+ send_window_size_(initial_send_window_size),
+ recv_window_size_(initial_recv_window_size),
+ unacked_recv_window_bytes_(0),
+ session_(session),
+ delegate_(NULL),
+ send_status_(
+ (type_ == SPDY_PUSH_STREAM) ?
+ NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND),
+ request_time_(base::Time::Now()),
+ response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE),
+ io_state_((type_ == SPDY_PUSH_STREAM) ? STATE_IDLE : STATE_NONE),
+ response_status_(OK),
+ net_log_(net_log),
+ send_bytes_(0),
+ recv_bytes_(0),
+ just_completed_frame_type_(DATA),
+ just_completed_frame_size_(0) {
+ CHECK(type_ == SPDY_BIDIRECTIONAL_STREAM ||
+ type_ == SPDY_REQUEST_RESPONSE_STREAM ||
+ type_ == SPDY_PUSH_STREAM);
+}
+
+SpdyStream::~SpdyStream() {
+ CHECK(!in_do_loop_);
+ UpdateHistograms();
+}
+
+void SpdyStream::SetDelegate(Delegate* delegate) {
+ CHECK(!delegate_);
+ CHECK(delegate);
+ delegate_ = delegate;
+
+ if (type_ == SPDY_PUSH_STREAM) {
+ DCHECK(continue_buffering_data_);
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&SpdyStream::PushedStreamReplayData, GetWeakPtr()));
+ }
+}
+
+void SpdyStream::PushedStreamReplayData() {
+ DCHECK_EQ(type_, SPDY_PUSH_STREAM);
+ DCHECK_NE(stream_id_, 0u);
+ DCHECK(continue_buffering_data_);
+
+ continue_buffering_data_ = false;
+
+ // The delegate methods called below may delete |this|, so use
+ // |weak_this| to detect that.
+ base::WeakPtr<SpdyStream> weak_this = GetWeakPtr();
+
+ CHECK(delegate_);
+ SpdyResponseHeadersStatus status =
+ delegate_->OnResponseHeadersUpdated(response_headers_);
+ if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not
+ // have been closed. Since we don't have complete headers, assume
+ // we're waiting for another HEADERS frame, and we had better not
+ // have any pending data frames.
+ CHECK(weak_this);
+ if (!pending_buffers_.empty()) {
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+ "Data received with incomplete headers.");
+ session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+ }
+ return;
+ }
+
+ // OnResponseHeadersUpdated() may have closed |this|.
+ if (!weak_this)
+ return;
+
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
+
+ while (!pending_buffers_.empty()) {
+ // Take ownership of the first element of |pending_buffers_|.
+ scoped_ptr<SpdyBuffer> buffer(pending_buffers_.front());
+ pending_buffers_.weak_erase(pending_buffers_.begin());
+
+ bool eof = (buffer == NULL);
+
+ CHECK(delegate_);
+ delegate_->OnDataReceived(buffer.Pass());
+
+ // OnDataReceived() may have closed |this|.
+ if (!weak_this)
+ return;
+
+ if (eof) {
+ DCHECK(pending_buffers_.empty());
+ session_->CloseActiveStream(stream_id_, OK);
+ DCHECK(!weak_this);
+ // |pending_buffers_| is invalid at this point.
+ break;
+ }
+ }
+}
+
+scoped_ptr<SpdyFrame> SpdyStream::ProduceSynStreamFrame() {
+ CHECK_EQ(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE);
+ CHECK(request_headers_);
+ CHECK_GT(stream_id_, 0u);
+
+ SpdyControlFlags flags =
+ (send_status_ == NO_MORE_DATA_TO_SEND) ?
+ CONTROL_FLAG_FIN : CONTROL_FLAG_NONE;
+ scoped_ptr<SpdyFrame> frame(session_->CreateSynStream(
+ stream_id_, priority_, slot_, flags, *request_headers_));
+ send_time_ = base::TimeTicks::Now();
+ return frame.Pass();
+}
+
+void SpdyStream::DetachDelegate() {
+ CHECK(!in_do_loop_);
+ DCHECK(!IsClosed());
+ delegate_ = NULL;
+ Cancel();
+}
+
+void SpdyStream::AdjustSendWindowSize(int32 delta_window_size) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+
+ if (IsClosed())
+ return;
+
+ // Check for wraparound.
+ if (send_window_size_ > 0) {
+ DCHECK_LE(delta_window_size, kint32max - send_window_size_);
+ }
+ if (send_window_size_ < 0) {
+ DCHECK_GE(delta_window_size, kint32min - send_window_size_);
+ }
+ send_window_size_ += delta_window_size;
+ PossiblyResumeIfSendStalled();
+}
+
+void SpdyStream::OnWriteBufferConsumed(
+ size_t frame_payload_size,
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+ if (consume_source == SpdyBuffer::DISCARD) {
+ // If we're discarding a frame or part of it, increase the send
+ // window by the number of discarded bytes. (Although if we're
+ // discarding part of a frame, it's probably because of a write
+ // error and we'll be tearing down the stream soon.)
+ size_t remaining_payload_bytes = std::min(consume_size, frame_payload_size);
+ DCHECK_GT(remaining_payload_bytes, 0u);
+ IncreaseSendWindowSize(static_cast<int32>(remaining_payload_bytes));
+ }
+ // For consumed bytes, the send window is increased when we receive
+ // a WINDOW_UPDATE frame.
+}
+
+void SpdyStream::IncreaseSendWindowSize(int32 delta_window_size) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+ DCHECK_GE(delta_window_size, 1);
+
+ // Ignore late WINDOW_UPDATEs.
+ if (IsClosed())
+ return;
+
+ if (send_window_size_ > 0) {
+ // Check for overflow.
+ int32 max_delta_window_size = kint32max - send_window_size_;
+ if (delta_window_size > max_delta_window_size) {
+ std::string desc = base::StringPrintf(
+ "Received WINDOW_UPDATE [delta: %d] for stream %d overflows "
+ "send_window_size_ [current: %d]", delta_window_size, stream_id_,
+ send_window_size_);
+ session_->ResetStream(stream_id_, RST_STREAM_FLOW_CONTROL_ERROR, desc);
+ return;
+ }
+ }
+
+ send_window_size_ += delta_window_size;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
+ base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+ stream_id_, delta_window_size, send_window_size_));
+
+ PossiblyResumeIfSendStalled();
+}
+
+void SpdyStream::DecreaseSendWindowSize(int32 delta_window_size) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+
+ if (IsClosed())
+ return;
+
+ // We only call this method when sending a frame. Therefore,
+ // |delta_window_size| should be within the valid frame size range.
+ DCHECK_GE(delta_window_size, 1);
+ DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize);
+
+ // |send_window_size_| should have been at least |delta_window_size| for
+ // this call to happen.
+ DCHECK_GE(send_window_size_, delta_window_size);
+
+ send_window_size_ -= delta_window_size;
+
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
+ base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+ stream_id_, -delta_window_size, send_window_size_));
+}
+
+void SpdyStream::OnReadBufferConsumed(
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+ DCHECK_GE(consume_size, 1u);
+ DCHECK_LE(consume_size, static_cast<size_t>(kint32max));
+ IncreaseRecvWindowSize(static_cast<int32>(consume_size));
+}
+
+void SpdyStream::IncreaseRecvWindowSize(int32 delta_window_size) {
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+
+ // By the time a read is processed by the delegate, this stream may
+ // already be inactive.
+ if (!session_->IsStreamActive(stream_id_))
+ return;
+
+ DCHECK_GE(unacked_recv_window_bytes_, 0);
+ DCHECK_GE(recv_window_size_, unacked_recv_window_bytes_);
+ DCHECK_GE(delta_window_size, 1);
+ // Check for overflow.
+ DCHECK_LE(delta_window_size, kint32max - recv_window_size_);
+
+ recv_window_size_ += delta_window_size;
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
+ base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+ stream_id_, delta_window_size, recv_window_size_));
+
+ unacked_recv_window_bytes_ += delta_window_size;
+ if (unacked_recv_window_bytes_ >
+ session_->stream_initial_recv_window_size() / 2) {
+ session_->SendStreamWindowUpdate(
+ stream_id_, static_cast<uint32>(unacked_recv_window_bytes_));
+ unacked_recv_window_bytes_ = 0;
+ }
+}
+
+void SpdyStream::DecreaseRecvWindowSize(int32 delta_window_size) {
+ DCHECK(session_->IsStreamActive(stream_id_));
+ DCHECK_GE(session_->flow_control_state(), SpdySession::FLOW_CONTROL_STREAM);
+ DCHECK_GE(delta_window_size, 1);
+
+ // Since we never decrease the initial receive window size,
+ // |delta_window_size| should never cause |recv_window_size_| to go
+ // negative. If we do, the receive window isn't being respected.
+ if (delta_window_size > recv_window_size_) {
+ session_->ResetStream(
+ stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "delta_window_size is " + base::IntToString(delta_window_size) +
+ " in DecreaseRecvWindowSize, which is larger than the receive " +
+ "window size of " + base::IntToString(recv_window_size_));
+ return;
+ }
+
+ recv_window_size_ -= delta_window_size;
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
+ base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+ stream_id_, -delta_window_size, recv_window_size_));
+}
+
+int SpdyStream::GetPeerAddress(IPEndPoint* address) const {
+ return session_->GetPeerAddress(address);
+}
+
+int SpdyStream::GetLocalAddress(IPEndPoint* address) const {
+ return session_->GetLocalAddress(address);
+}
+
+bool SpdyStream::WasEverUsed() const {
+ return session_->WasEverUsed();
+}
+
+base::Time SpdyStream::GetRequestTime() const {
+ return request_time_;
+}
+
+void SpdyStream::SetRequestTime(base::Time t) {
+ request_time_ = t;
+}
+
+int SpdyStream::OnInitialResponseHeadersReceived(
+ const SpdyHeaderBlock& initial_response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time) {
+ // SpdySession guarantees that this is called at most once.
+ CHECK(response_headers_.empty());
+
+ // Check to make sure that we don't receive the response headers
+ // before we're ready for it.
+ switch (type_) {
+ case SPDY_BIDIRECTIONAL_STREAM:
+ // For a bidirectional stream, we're ready for the response
+ // headers once we've finished sending the request headers.
+ if (io_state_ < STATE_IDLE) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Response received before request sent");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+ break;
+
+ case SPDY_REQUEST_RESPONSE_STREAM:
+ // For a request/response stream, we're ready for the response
+ // headers once we've finished sending the request headers and
+ // the request body (if we have one).
+ if ((io_state_ < STATE_IDLE) || (send_status_ == MORE_DATA_TO_SEND) ||
+ pending_send_data_.get()) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Response received before request sent");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+ break;
+
+ case SPDY_PUSH_STREAM:
+ // For a push stream, we're ready immediately.
+ DCHECK_EQ(send_status_, NO_MORE_DATA_TO_SEND);
+ DCHECK_EQ(io_state_, STATE_IDLE);
+ break;
+ }
+
+ metrics_.StartStream();
+
+ DCHECK_EQ(io_state_, STATE_IDLE);
+
+ response_time_ = response_time;
+ recv_first_byte_time_ = recv_first_byte_time;
+ return MergeWithResponseHeaders(initial_response_headers);
+}
+
+int SpdyStream::OnAdditionalResponseHeadersReceived(
+ const SpdyHeaderBlock& additional_response_headers) {
+ if (type_ == SPDY_REQUEST_RESPONSE_STREAM) {
+ session_->ResetStream(
+ stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Additional headers received for request/response stream");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ } else if (type_ == SPDY_PUSH_STREAM &&
+ response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) {
+ session_->ResetStream(
+ stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Additional headers received for push stream");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+ return MergeWithResponseHeaders(additional_response_headers);
+}
+
+void SpdyStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ DCHECK(session_->IsStreamActive(stream_id_));
+
+ // If we're still buffering data for a push stream, we will do the
+ // check for data received with incomplete headers in
+ // PushedStreamReplayData().
+ if (!delegate_ || continue_buffering_data_) {
+ DCHECK_EQ(type_, SPDY_PUSH_STREAM);
+ // It should be valid for this to happen in the server push case.
+ // We'll return received data when delegate gets attached to the stream.
+ if (buffer) {
+ pending_buffers_.push_back(buffer.release());
+ } else {
+ pending_buffers_.push_back(NULL);
+ metrics_.StopStream();
+ // Note: we leave the stream open in the session until the stream
+ // is claimed.
+ }
+ return;
+ }
+
+ // If we have response headers but the delegate has indicated that
+ // it's still incomplete, then that's a protocol error.
+ if (response_headers_status_ == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+ "Data received with incomplete headers.");
+ session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+
+ CHECK(!IsClosed());
+
+ if (!buffer) {
+ metrics_.StopStream();
+ // Deletes |this|.
+ session_->CloseActiveStream(stream_id_, OK);
+ return;
+ }
+
+ size_t length = buffer->GetRemainingSize();
+ DCHECK_LE(length, session_->GetDataFrameMaximumPayload());
+ if (session_->flow_control_state() >= SpdySession::FLOW_CONTROL_STREAM) {
+ DecreaseRecvWindowSize(static_cast<int32>(length));
+ buffer->AddConsumeCallback(
+ base::Bind(&SpdyStream::OnReadBufferConsumed, GetWeakPtr()));
+ }
+
+ // Track our bandwidth.
+ metrics_.RecordBytes(length);
+ recv_bytes_ += length;
+ recv_last_byte_time_ = base::TimeTicks::Now();
+
+ // May close |this|.
+ delegate_->OnDataReceived(buffer.Pass());
+}
+
+void SpdyStream::OnFrameWriteComplete(SpdyFrameType frame_type,
+ size_t frame_size) {
+ if (frame_size < session_->GetFrameMinimumSize() ||
+ frame_size > session_->GetFrameMaximumSize()) {
+ NOTREACHED();
+ return;
+ }
+ if (IsClosed())
+ return;
+ just_completed_frame_type_ = frame_type;
+ just_completed_frame_size_ = frame_size;
+ DoLoop(OK);
+}
+
+int SpdyStream::GetProtocolVersion() const {
+ return session_->GetProtocolVersion();
+}
+
+void SpdyStream::LogStreamError(int status, const std::string& description) {
+ net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ERROR,
+ base::Bind(&NetLogSpdyStreamErrorCallback,
+ stream_id_, status, &description));
+}
+
+void SpdyStream::OnClose(int status) {
+ CHECK(!in_do_loop_);
+ io_state_ = STATE_CLOSED;
+ response_status_ = status;
+ Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ if (delegate)
+ delegate->OnClose(status);
+ // Unset |stream_id_| last so that the delegate can look it up.
+ stream_id_ = 0;
+}
+
+void SpdyStream::Cancel() {
+ CHECK(!in_do_loop_);
+ // We may be called again from a delegate's OnClose().
+ if (io_state_ == STATE_CLOSED)
+ return;
+
+ if (stream_id_ != 0) {
+ session_->ResetStream(stream_id_, RST_STREAM_CANCEL, std::string());
+ } else {
+ session_->CloseCreatedStream(GetWeakPtr(), RST_STREAM_CANCEL);
+ }
+ // |this| is invalid at this point.
+}
+
+void SpdyStream::Close() {
+ CHECK(!in_do_loop_);
+ // We may be called again from a delegate's OnClose().
+ if (io_state_ == STATE_CLOSED)
+ return;
+
+ if (stream_id_ != 0) {
+ session_->CloseActiveStream(stream_id_, OK);
+ } else {
+ session_->CloseCreatedStream(GetWeakPtr(), OK);
+ }
+ // |this| is invalid at this point.
+}
+
+base::WeakPtr<SpdyStream> SpdyStream::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+int SpdyStream::SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers,
+ SpdySendStatus send_status) {
+ CHECK_NE(type_, SPDY_PUSH_STREAM);
+ CHECK_EQ(send_status_, MORE_DATA_TO_SEND);
+ CHECK(!request_headers_);
+ CHECK(!pending_send_data_.get());
+ CHECK_EQ(io_state_, STATE_NONE);
+ request_headers_ = request_headers.Pass();
+ send_status_ = send_status;
+ io_state_ = STATE_GET_DOMAIN_BOUND_CERT;
+ return DoLoop(OK);
+}
+
+void SpdyStream::SendData(IOBuffer* data,
+ int length,
+ SpdySendStatus send_status) {
+ CHECK_NE(type_, SPDY_PUSH_STREAM);
+ CHECK_EQ(send_status_, MORE_DATA_TO_SEND);
+ CHECK_GE(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE);
+ CHECK(!pending_send_data_.get());
+ pending_send_data_ = new DrainableIOBuffer(data, length);
+ send_status_ = send_status;
+ QueueNextDataFrame();
+}
+
+bool SpdyStream::GetSSLInfo(SSLInfo* ssl_info,
+ bool* was_npn_negotiated,
+ NextProto* protocol_negotiated) {
+ return session_->GetSSLInfo(
+ ssl_info, was_npn_negotiated, protocol_negotiated);
+}
+
+bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) {
+ return session_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+void SpdyStream::PossiblyResumeIfSendStalled() {
+ DCHECK(!IsClosed());
+
+ if (send_stalled_by_flow_control_ && !session_->IsSendStalled() &&
+ send_window_size_ > 0) {
+ net_log_.AddEvent(
+ NetLog::TYPE_SPDY_STREAM_FLOW_CONTROL_UNSTALLED,
+ NetLog::IntegerCallback("stream_id", stream_id_));
+ send_stalled_by_flow_control_ = false;
+ QueueNextDataFrame();
+ }
+}
+
+bool SpdyStream::IsClosed() const {
+ return io_state_ == STATE_CLOSED;
+}
+
+bool SpdyStream::IsIdle() const {
+ return io_state_ == STATE_IDLE;
+}
+
+NextProto SpdyStream::GetProtocol() const {
+ return session_->protocol();
+}
+
+bool SpdyStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ if (stream_id_ == 0)
+ return false;
+
+ return session_->GetLoadTimingInfo(stream_id_, load_timing_info);
+}
+
+GURL SpdyStream::GetUrlFromHeaders() const {
+ if (type_ != SPDY_PUSH_STREAM && !request_headers_)
+ return GURL();
+
+ const SpdyHeaderBlock& headers =
+ (type_ == SPDY_PUSH_STREAM) ? response_headers_ : *request_headers_;
+ return GetUrlFromHeaderBlock(headers, GetProtocolVersion(),
+ type_ == SPDY_PUSH_STREAM);
+}
+
+bool SpdyStream::HasUrlFromHeaders() const {
+ return !GetUrlFromHeaders().is_empty();
+}
+
+void SpdyStream::OnGetDomainBoundCertComplete(int result) {
+ DCHECK_EQ(io_state_, STATE_GET_DOMAIN_BOUND_CERT_COMPLETE);
+ DoLoop(result);
+}
+
+int SpdyStream::DoLoop(int result) {
+ CHECK(!in_do_loop_);
+ in_do_loop_ = true;
+
+ do {
+ State state = io_state_;
+ io_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GET_DOMAIN_BOUND_CERT:
+ CHECK_EQ(result, OK);
+ result = DoGetDomainBoundCert();
+ break;
+ case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
+ result = DoGetDomainBoundCertComplete(result);
+ break;
+ case STATE_SEND_DOMAIN_BOUND_CERT:
+ CHECK_EQ(result, OK);
+ result = DoSendDomainBoundCert();
+ break;
+ case STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE:
+ result = DoSendDomainBoundCertComplete(result);
+ break;
+ case STATE_SEND_REQUEST_HEADERS:
+ CHECK_EQ(result, OK);
+ result = DoSendRequestHeaders();
+ break;
+ case STATE_SEND_REQUEST_HEADERS_COMPLETE:
+ CHECK_EQ(result, OK);
+ result = DoSendRequestHeadersComplete();
+ break;
+
+ // For request/response streams, no data is sent from the client
+ // while in the OPEN state, so OnFrameWriteComplete is never
+ // called here. The HTTP body is handled in the OnDataReceived
+ // callback, which does not call into DoLoop.
+ //
+ // For bidirectional streams, we'll send and receive data once
+ // the connection is established. Received data is handled in
+ // OnDataReceived. Sent data is handled in
+ // OnFrameWriteComplete, which calls DoOpen().
+ case STATE_IDLE:
+ CHECK_EQ(result, OK);
+ result = DoOpen();
+ break;
+
+ case STATE_CLOSED:
+ DCHECK_NE(result, ERR_IO_PENDING);
+ break;
+ default:
+ NOTREACHED() << io_state_;
+ break;
+ }
+ } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE &&
+ io_state_ != STATE_IDLE);
+
+ CHECK(in_do_loop_);
+ in_do_loop_ = false;
+
+ return result;
+}
+
+int SpdyStream::DoGetDomainBoundCert() {
+ CHECK(request_headers_);
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ GURL url = GetUrlFromHeaders();
+ if (!session_->NeedsCredentials() || !url.SchemeIs("https")) {
+ // Proceed directly to sending the request headers
+ io_state_ = STATE_SEND_REQUEST_HEADERS;
+ return OK;
+ }
+
+ slot_ = session_->credential_state()->FindCredentialSlot(GetUrlFromHeaders());
+ if (slot_ != SpdyCredentialState::kNoEntry) {
+ // Proceed directly to sending the request headers
+ io_state_ = STATE_SEND_REQUEST_HEADERS;
+ return OK;
+ }
+
+ io_state_ = STATE_GET_DOMAIN_BOUND_CERT_COMPLETE;
+ ServerBoundCertService* sbc_service = session_->GetServerBoundCertService();
+ DCHECK(sbc_service != NULL);
+ int rv = sbc_service->GetOrCreateDomainBoundCert(
+ url.GetOrigin().host(),
+ &domain_bound_private_key_,
+ &domain_bound_cert_,
+ base::Bind(&SpdyStream::OnGetDomainBoundCertComplete, GetWeakPtr()),
+ &domain_bound_cert_request_handle_);
+ return rv;
+}
+
+int SpdyStream::DoGetDomainBoundCertComplete(int result) {
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ if (result != OK)
+ return result;
+
+ io_state_ = STATE_SEND_DOMAIN_BOUND_CERT;
+ slot_ = session_->credential_state()->SetHasCredential(GetUrlFromHeaders());
+ return OK;
+}
+
+int SpdyStream::DoSendDomainBoundCert() {
+ CHECK(request_headers_);
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE;
+
+ std::string origin = GetUrlFromHeaders().GetOrigin().spec();
+ DCHECK(origin[origin.length() - 1] == '/');
+ origin.erase(origin.length() - 1); // Trim trailing slash.
+ scoped_ptr<SpdyFrame> frame;
+ int rv = session_->CreateCredentialFrame(
+ origin,
+ domain_bound_private_key_,
+ domain_bound_cert_,
+ priority_,
+ &frame);
+ if (rv != OK) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ return rv;
+ }
+
+ DCHECK(frame);
+ // TODO(akalin): Fix the following race condition:
+ //
+ // Since this is decoupled from sending the SYN_STREAM frame, it is
+ // possible that other domain-bound cert frames will clobber ours
+ // before our SYN_STREAM frame gets sent. This can be solved by
+ // immediately enqueueing the SYN_STREAM frame here and adjusting
+ // the state machine appropriately.
+ session_->EnqueueStreamWrite(
+ GetWeakPtr(), CREDENTIAL,
+ scoped_ptr<SpdyBufferProducer>(
+ new SimpleBufferProducer(
+ scoped_ptr<SpdyBuffer>(new SpdyBuffer(frame.Pass())))));
+ return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendDomainBoundCertComplete(int result) {
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ if (result != OK)
+ return result;
+
+ DCHECK_EQ(just_completed_frame_type_, CREDENTIAL);
+ io_state_ = STATE_SEND_REQUEST_HEADERS;
+ return OK;
+}
+
+int SpdyStream::DoSendRequestHeaders() {
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ io_state_ = STATE_SEND_REQUEST_HEADERS_COMPLETE;
+
+ session_->EnqueueStreamWrite(
+ GetWeakPtr(), SYN_STREAM,
+ scoped_ptr<SpdyBufferProducer>(
+ new SynStreamBufferProducer(GetWeakPtr())));
+ return ERR_IO_PENDING;
+}
+
+namespace {
+
+// Assuming we're in STATE_IDLE, maps the given type (which must not
+// be SPDY_PUSH_STREAM) and send status to a result to return from
+// DoSendRequestHeadersComplete() or DoOpen().
+int GetOpenStateResult(SpdyStreamType type, SpdySendStatus send_status) {
+ switch (type) {
+ case SPDY_BIDIRECTIONAL_STREAM:
+ // For bidirectional streams, there's nothing else to do.
+ DCHECK_EQ(send_status, MORE_DATA_TO_SEND);
+ return OK;
+
+ case SPDY_REQUEST_RESPONSE_STREAM:
+ // For request/response streams, wait for the delegate to send
+ // data if there's request data to send; we'll get called back
+ // when the send finishes.
+ if (send_status == MORE_DATA_TO_SEND)
+ return ERR_IO_PENDING;
+
+ return OK;
+
+ case SPDY_PUSH_STREAM:
+ // This should never be called for push streams.
+ break;
+ }
+
+ CHECK(false);
+ return ERR_UNEXPECTED;
+}
+
+} // namespace
+
+int SpdyStream::DoSendRequestHeadersComplete() {
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+ DCHECK_EQ(just_completed_frame_type_, SYN_STREAM);
+ DCHECK_NE(stream_id_, 0u);
+
+ io_state_ = STATE_IDLE;
+
+ CHECK(delegate_);
+ // Must not close |this|; if it does, it will trigger the |in_do_loop_|
+ // check in the destructor.
+ delegate_->OnRequestHeadersSent();
+
+ return GetOpenStateResult(type_, send_status_);
+}
+
+int SpdyStream::DoOpen() {
+ DCHECK_NE(type_, SPDY_PUSH_STREAM);
+
+ if (just_completed_frame_type_ != DATA) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ if (just_completed_frame_size_ < session_->GetDataFrameMinimumSize()) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ size_t frame_payload_size =
+ just_completed_frame_size_ - session_->GetDataFrameMinimumSize();
+ if (frame_payload_size > session_->GetDataFrameMaximumPayload()) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ // Set |io_state_| first as |delegate_| may check it.
+ io_state_ = STATE_IDLE;
+
+ send_bytes_ += frame_payload_size;
+
+ pending_send_data_->DidConsume(frame_payload_size);
+ if (pending_send_data_->BytesRemaining() > 0) {
+ QueueNextDataFrame();
+ return ERR_IO_PENDING;
+ }
+
+ pending_send_data_ = NULL;
+
+ CHECK(delegate_);
+ // Must not close |this|; if it does, it will trigger the
+ // |in_do_loop_| check in the destructor.
+ delegate_->OnDataSent();
+
+ return GetOpenStateResult(type_, send_status_);
+}
+
+void SpdyStream::UpdateHistograms() {
+ // We need at least the receive timers to be filled in, as otherwise
+ // metrics can be bogus.
+ if (recv_first_byte_time_.is_null() || recv_last_byte_time_.is_null())
+ return;
+
+ base::TimeTicks effective_send_time;
+ if (type_ == SPDY_PUSH_STREAM) {
+ // Push streams shouldn't have |send_time_| filled in.
+ DCHECK(send_time_.is_null());
+ effective_send_time = recv_first_byte_time_;
+ } else {
+ // For non-push streams, we also need |send_time_| to be filled
+ // in.
+ if (send_time_.is_null())
+ return;
+ effective_send_time = send_time_;
+ }
+
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte",
+ recv_first_byte_time_ - effective_send_time);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime",
+ recv_last_byte_time_ - recv_first_byte_time_);
+ UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime",
+ recv_last_byte_time_ - effective_send_time);
+
+ UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_);
+ UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_);
+}
+
+void SpdyStream::QueueNextDataFrame() {
+ // Until the request has been completely sent, we cannot be sure
+ // that our stream_id is correct.
+ DCHECK_GT(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE);
+ CHECK_GT(stream_id_, 0u);
+ CHECK(pending_send_data_.get());
+ CHECK_GT(pending_send_data_->BytesRemaining(), 0);
+
+ SpdyDataFlags flags =
+ (send_status_ == NO_MORE_DATA_TO_SEND) ?
+ DATA_FLAG_FIN : DATA_FLAG_NONE;
+ scoped_ptr<SpdyBuffer> data_buffer(
+ session_->CreateDataBuffer(stream_id_,
+ pending_send_data_.get(),
+ pending_send_data_->BytesRemaining(),
+ flags));
+ // We'll get called again by PossiblyResumeIfSendStalled().
+ if (!data_buffer)
+ return;
+
+ if (session_->flow_control_state() >= SpdySession::FLOW_CONTROL_STREAM) {
+ DCHECK_GE(data_buffer->GetRemainingSize(),
+ session_->GetDataFrameMinimumSize());
+ size_t payload_size =
+ data_buffer->GetRemainingSize() - session_->GetDataFrameMinimumSize();
+ DCHECK_LE(payload_size, session_->GetDataFrameMaximumPayload());
+ DecreaseSendWindowSize(static_cast<int32>(payload_size));
+ // This currently isn't strictly needed, since write frames are
+ // discarded only if the stream is about to be closed. But have it
+ // here anyway just in case this changes.
+ data_buffer->AddConsumeCallback(
+ base::Bind(&SpdyStream::OnWriteBufferConsumed,
+ GetWeakPtr(), payload_size));
+ }
+
+ session_->EnqueueStreamWrite(
+ GetWeakPtr(), DATA,
+ scoped_ptr<SpdyBufferProducer>(
+ new SimpleBufferProducer(data_buffer.Pass())));
+}
+
+int SpdyStream::MergeWithResponseHeaders(
+ const SpdyHeaderBlock& new_response_headers) {
+ if (new_response_headers.find("transfer-encoding") !=
+ new_response_headers.end()) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Received transfer-encoding header");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ for (SpdyHeaderBlock::const_iterator it = new_response_headers.begin();
+ it != new_response_headers.end(); ++it) {
+ // Disallow uppercase headers.
+ if (ContainsUppercaseAscii(it->first)) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Upper case characters in header: " + it->first);
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ SpdyHeaderBlock::iterator it2 = response_headers_.lower_bound(it->first);
+ // Disallow duplicate headers. This is just to be conservative.
+ if (it2 != response_headers_.end() && it2->first == it->first) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Duplicate header: " + it->first);
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ response_headers_.insert(it2, *it);
+ }
+
+ // If delegate_ is not yet attached, we'll call
+ // OnResponseHeadersUpdated() after the delegate gets attached to
+ // the stream.
+ if (delegate_) {
+ // The call to OnResponseHeadersUpdated() below may delete |this|,
+ // so use |weak_this| to detect that.
+ base::WeakPtr<SpdyStream> weak_this = GetWeakPtr();
+
+ SpdyResponseHeadersStatus status =
+ delegate_->OnResponseHeadersUpdated(response_headers_);
+ if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not
+ // have been closed.
+ CHECK(weak_this);
+ // Incomplete headers are OK only for push streams.
+ if (type_ != SPDY_PUSH_STREAM) {
+ session_->ResetStream(stream_id_, RST_STREAM_PROTOCOL_ERROR,
+ "Incomplete headers");
+ return ERR_INCOMPLETE_SPDY_HEADERS;
+ }
+ } else if (weak_this) {
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
+ }
+ }
+
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_stream.h b/chromium/net/spdy/spdy_stream.h
new file mode 100644
index 00000000000..4d18e3eff38
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream.h
@@ -0,0 +1,557 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_STREAM_H_
+#define NET_SPDY_SPDY_STREAM_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/bandwidth_metrics.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_buffer.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "net/ssl/ssl_client_cert_type.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class AddressList;
+class IPEndPoint;
+struct LoadTimingInfo;
+class SSLCertRequestInfo;
+class SSLInfo;
+class SpdySession;
+
+enum SpdyStreamType {
+ // The most general type of stream; there are no restrictions on
+ // when data can be sent and received.
+ SPDY_BIDIRECTIONAL_STREAM,
+ // A stream where the client sends a request with possibly a body,
+ // and the server then sends a response with a body.
+ SPDY_REQUEST_RESPONSE_STREAM,
+ // A server-initiated stream where the server just sends a response
+ // with a body and the client does not send anything.
+ SPDY_PUSH_STREAM
+};
+
+// Passed to some SpdyStream functions to indicate whether there's
+// more data to send.
+enum SpdySendStatus {
+ MORE_DATA_TO_SEND,
+ NO_MORE_DATA_TO_SEND
+};
+
+// Returned by SpdyStream::OnResponseHeadersUpdated() to indicate
+// whether the current response headers are complete or not.
+enum SpdyResponseHeadersStatus {
+ RESPONSE_HEADERS_ARE_INCOMPLETE,
+ RESPONSE_HEADERS_ARE_COMPLETE
+};
+
+// The SpdyStream is used by the SpdySession to represent each stream known
+// on the SpdySession. This class provides interfaces for SpdySession to use.
+// Streams can be created either by the client or by the server. When they
+// are initiated by the client, both the SpdySession and client object (such as
+// a SpdyNetworkTransaction) will maintain a reference to the stream. When
+// initiated by the server, only the SpdySession will maintain any reference,
+// until such a time as a client object requests a stream for the path.
+class NET_EXPORT_PRIVATE SpdyStream {
+ public:
+ // Delegate handles protocol specific behavior of spdy stream.
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ Delegate() {}
+
+ // Called when the request headers have been sent. Never called
+ // for push streams. Must not cause the stream to be closed.
+ virtual void OnRequestHeadersSent() = 0;
+
+ // WARNING: This function is complicated! Be sure to read the
+ // whole comment below if you're working with code that implements
+ // or calls this function.
+ //
+ // Called when the response headers are updated from the
+ // server. |response_headers| contains the set of all headers
+ // received up to this point; delegates can assume that any
+ // headers previously received remain unchanged.
+ //
+ // This is called at least once before any data is received. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this will be
+ // called again when more headers are received until
+ // RESPONSE_HEADERS_ARE_COMPLETE is returned, and any data
+ // received before then will be treated as a protocol error.
+ //
+ // If RESPONSE_HEADERS_ARE_INCOMPLETE is returned, the delegate
+ // must not have closed the stream. Otherwise, if
+ // RESPONSE_HEADERS_ARE_COMPLETE is returned, the delegate has
+ // processed the headers successfully. However, it still may have
+ // closed the stream, e.g. if the headers indicated an error
+ // condition.
+ //
+ // Some type-specific behavior:
+ //
+ // - For bidirectional streams, this may be called even after
+ // data is received, but it is expected that
+ // RESPONSE_HEADERS_ARE_COMPLETE is always returned. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is
+ // treated as a protocol error.
+ //
+ // - For request/response streams, this function is called
+ // exactly once before data is received, and it is expected
+ // that RESPONSE_HEADERS_ARE_COMPLETE is returned. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is
+ // treated as a protocol error.
+ //
+ // - For push streams, it is expected that this function will be
+ // called until RESPONSE_HEADERS_ARE_COMPLETE is returned
+ // before any data is received; any deviation from this is
+ // treated as a protocol error.
+ //
+ // TODO(akalin): Treat headers received after data has been
+ // received as a protocol error for non-bidirectional streams.
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) = 0;
+
+ // Called when data is received after all required response
+ // headers have been received. |buffer| may be NULL, which signals
+ // EOF. Must return OK if the data was received successfully, or
+ // a network error code otherwise.
+ //
+ // May cause the stream to be closed.
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) = 0;
+
+ // Called when data is sent. Must not cause the stream to be
+ // closed.
+ virtual void OnDataSent() = 0;
+
+ // Called when SpdyStream is closed. No other delegate functions
+ // will be called after this is called, and the delegate must not
+ // access the stream after this is called. Must not cause the
+ // stream to be be (re-)closed.
+ //
+ // TODO(akalin): Allow this function to re-close the stream and
+ // handle it gracefully.
+ virtual void OnClose(int status) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Delegate);
+ };
+
+ // SpdyStream constructor
+ SpdyStream(SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ int32 initial_send_window_size,
+ int32 initial_recv_window_size,
+ const BoundNetLog& net_log);
+
+ ~SpdyStream();
+
+ // Set the delegate, which must not be NULL. Must not be called more
+ // than once. For push streams, calling this may cause buffered data
+ // to be sent to the delegate (from a posted task).
+ void SetDelegate(Delegate* delegate);
+
+ // Detach the delegate from the stream, which must not yet be
+ // closed, and cancel it.
+ void DetachDelegate();
+
+ // The time at which the first bytes of the response were received
+ // from the server, or null if the response hasn't been received
+ // yet.
+ base::Time response_time() const { return response_time_; }
+
+ SpdyStreamType type() const { return type_; }
+
+ SpdyStreamId stream_id() const { return stream_id_; }
+ void set_stream_id(SpdyStreamId stream_id) { stream_id_ = stream_id; }
+
+ const GURL& url() const { return url_; }
+
+ RequestPriority priority() const { return priority_; }
+
+ int32 send_window_size() const { return send_window_size_; }
+
+ int32 recv_window_size() const { return recv_window_size_; }
+
+ bool send_stalled_by_flow_control() const {
+ return send_stalled_by_flow_control_;
+ }
+
+ void set_send_stalled_by_flow_control(bool stalled) {
+ send_stalled_by_flow_control_ = stalled;
+ }
+
+ // Called by the session to adjust this stream's send window size by
+ // |delta_window_size|, which is the difference between the
+ // SETTINGS_INITIAL_WINDOW_SIZE in the most recent SETTINGS frame
+ // and the previous initial send window size, possibly unstalling
+ // this stream. Although |delta_window_size| may cause this stream's
+ // send window size to go negative, it must not cause it to wrap
+ // around in either direction. Does nothing if the stream is already
+ // closed.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void AdjustSendWindowSize(int32 delta_window_size);
+
+ // Called when bytes are consumed from a SpdyBuffer for a DATA frame
+ // that is to be written or is being written. Increases the send
+ // window size accordingly if some or all of the SpdyBuffer is being
+ // discarded.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void OnWriteBufferConsumed(size_t frame_payload_size,
+ size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source);
+
+ // Called by the session to increase this stream's send window size
+ // by |delta_window_size| (which must be at least 1) from a received
+ // WINDOW_UPDATE frame or from a dropped DATA frame that was
+ // intended to be sent, possibly unstalling this stream. If
+ // |delta_window_size| would cause this stream's send window size to
+ // overflow, calls into the session to reset this stream. Does
+ // nothing if the stream is already closed.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void IncreaseSendWindowSize(int32 delta_window_size);
+
+ // If stream flow control is turned on, called by the session to
+ // decrease this stream's send window size by |delta_window_size|,
+ // which must be at least 0 and at most kMaxSpdyFrameChunkSize.
+ // |delta_window_size| must not cause this stream's send window size
+ // to go negative. Does nothing if the stream is already closed.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void DecreaseSendWindowSize(int32 delta_window_size);
+
+ // Called when bytes are consumed by the delegate from a SpdyBuffer
+ // containing received data. Increases the receive window size
+ // accordingly.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void OnReadBufferConsumed(size_t consume_size,
+ SpdyBuffer::ConsumeSource consume_source);
+
+ // Called by OnReadBufferConsume to increase this stream's receive
+ // window size by |delta_window_size|, which must be at least 1 and
+ // must not cause this stream's receive window size to overflow,
+ // possibly also sending a WINDOW_UPDATE frame. Does nothing if the
+ // stream is not active.
+ //
+ // If stream flow control is turned off, this must not be called.
+ void IncreaseRecvWindowSize(int32 delta_window_size);
+
+ // Called by OnDataReceived (which is in turn called by the session)
+ // to decrease this stream's receive window size by
+ // |delta_window_size|, which must be at least 1 and must not cause
+ // this stream's receive window size to go negative.
+ //
+ // If stream flow control is turned off or the stream is not active,
+ // this must not be called.
+ void DecreaseRecvWindowSize(int32 delta_window_size);
+
+ int GetPeerAddress(IPEndPoint* address) const;
+ int GetLocalAddress(IPEndPoint* address) const;
+
+ // Returns true if the underlying transport socket ever had any reads or
+ // writes.
+ bool WasEverUsed() const;
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ base::Time GetRequestTime() const;
+ void SetRequestTime(base::Time t);
+
+ // Called at most once by the SpdySession when the initial response
+ // headers have been received for this stream, i.e., a SYN_REPLY (or
+ // SYN_STREAM for push streams) frame has been received. This is the
+ // entry point for a push stream. Returns a status code; if it is
+ // an error, the stream was closed by this function.
+ int OnInitialResponseHeadersReceived(const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time);
+
+ // Called by the SpdySession (only after
+ // OnInitialResponseHeadersReceived() has been called) when
+ // late-bound headers are received for a stream. Returns a status
+ // code; if it is an error, the stream was closed by this function.
+ int OnAdditionalResponseHeadersReceived(
+ const SpdyHeaderBlock& additional_response_headers);
+
+ // Called by the SpdySession when response data has been received
+ // for this stream. This callback may be called multiple times as
+ // data arrives from the network, and will never be called prior to
+ // OnResponseHeadersReceived.
+ //
+ // |buffer| contains the data received, or NULL if the stream is
+ // being closed. The stream must copy any data from this
+ // buffer before returning from this callback.
+ //
+ // |length| is the number of bytes received (at most 2^24 - 1) or 0 if
+ // the stream is being closed.
+ void OnDataReceived(scoped_ptr<SpdyBuffer> buffer);
+
+ // Called by the SpdySession when a frame has been successfully and
+ // completely written. |frame_size| is the total size of the frame
+ // in bytes, including framing overhead.
+ void OnFrameWriteComplete(SpdyFrameType frame_type, size_t frame_size);
+
+ // Called by the SpdySession when the request is finished. This callback
+ // will always be called at the end of the request and signals to the
+ // stream that the stream has no more network events. No further callbacks
+ // to the stream will be made after this call.
+ // |status| is an error code or OK.
+ void OnClose(int status);
+
+ // Called by the SpdySession to log stream related errors.
+ void LogStreamError(int status, const std::string& description);
+
+ // If this stream is active, reset it, and close it otherwise. In
+ // either case the stream is deleted.
+ void Cancel();
+
+ // Close this stream without sending a RST_STREAM and delete
+ // it.
+ void Close();
+
+ // Must be used only by |session_|.
+ base::WeakPtr<SpdyStream> GetWeakPtr();
+
+ // Interface for the delegate to use.
+
+ // Only one send can be in flight at a time, except for push
+ // streams, which must not send anything.
+
+ // Sends the request headers. The delegate is called back via
+ // OnRequestHeadersSent() when the request headers have completed
+ // sending. |send_status| must be MORE_DATA_TO_SEND for
+ // bidirectional streams; for request/response streams, it must be
+ // MORE_DATA_TO_SEND if the request has data to upload, or
+ // NO_MORE_DATA_TO_SEND if not.
+ int SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers,
+ SpdySendStatus send_status);
+
+ // Sends a DATA frame. The delegate will be notified via
+ // OnDataSent() when the send is complete. |send_status| must be
+ // MORE_DATA_TO_SEND for bidirectional streams; for request/response
+ // streams, it must be MORE_DATA_TO_SEND if there is more data to
+ // upload, or NO_MORE_DATA_TO_SEND if not.
+ void SendData(IOBuffer* data, int length, SpdySendStatus send_status);
+
+ // Fills SSL info in |ssl_info| and returns true when SSL is in use.
+ bool GetSSLInfo(SSLInfo* ssl_info,
+ bool* was_npn_negotiated,
+ NextProto* protocol_negotiated);
+
+ // Fills SSL Certificate Request info |cert_request_info| and returns
+ // true when SSL is in use.
+ bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
+
+ // If the stream is stalled on sending data, but the session is not
+ // stalled on sending data and |send_window_size_| is positive, then
+ // set |send_stalled_by_flow_control_| to false and unstall the data
+ // sending. Called by the session or by the stream itself. Must be
+ // called only when the stream is still open.
+ void PossiblyResumeIfSendStalled();
+
+ // Returns whether or not this stream is closed. Note that the only
+ // time a stream is closed and not deleted is in its delegate's
+ // OnClose() method.
+ bool IsClosed() const;
+
+ // Returns whether or not this stream has finished sending its
+ // request headers and is ready to send/receive more data.
+ bool IsIdle() const;
+
+ // Returns the protocol used by this stream. Always between
+ // kProtoSPDY2 and kProtoSPDYMaximumVersion.
+ //
+ // TODO(akalin): Change the lower bound to kProtoSPDYMinimumVersion
+ // once we stop supporting SPDY/1.
+ NextProto GetProtocol() const;
+
+ int response_status() const { return response_status_; }
+
+ bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const;
+
+ // Get the URL from the appropriate stream headers, or the empty
+ // GURL() if it is unknown.
+ //
+ // TODO(akalin): Figure out if we really need this function,
+ // i.e. can we just use the URL this stream was created with and/or
+ // one we receive headers validate that the URL from them is the
+ // same.
+ GURL GetUrlFromHeaders() const;
+
+ // Returns whether the URL for this stream is known.
+ //
+ // TODO(akalin): Remove this, as it's only used in tests.
+ bool HasUrlFromHeaders() const;
+
+ int GetProtocolVersion() const;
+
+ private:
+ class SynStreamBufferProducer;
+ class HeaderBufferProducer;
+
+ enum State {
+ STATE_NONE,
+ STATE_GET_DOMAIN_BOUND_CERT,
+ STATE_GET_DOMAIN_BOUND_CERT_COMPLETE,
+ STATE_SEND_DOMAIN_BOUND_CERT,
+ STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE,
+ STATE_SEND_REQUEST_HEADERS,
+ STATE_SEND_REQUEST_HEADERS_COMPLETE,
+ STATE_IDLE,
+ STATE_CLOSED
+ };
+
+ void OnGetDomainBoundCertComplete(int result);
+
+ // Try to make progress sending/receiving the request/response.
+ int DoLoop(int result);
+
+ // The implementations of each state of the state machine.
+ int DoGetDomainBoundCert();
+ int DoGetDomainBoundCertComplete(int result);
+ int DoSendDomainBoundCert();
+ int DoSendDomainBoundCertComplete(int result);
+ int DoSendRequestHeaders();
+ int DoSendRequestHeadersComplete();
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int result);
+ int DoOpen();
+
+ // Update the histograms. Can safely be called repeatedly, but should only
+ // be called after the stream has completed.
+ void UpdateHistograms();
+
+ // When a server-pushed stream is first created, this function is
+ // posted on the current MessageLoop to replay the data that the
+ // server has already sent.
+ void PushedStreamReplayData();
+
+ // Produces the SYN_STREAM frame for the stream. The stream must
+ // already be activated.
+ scoped_ptr<SpdyFrame> ProduceSynStreamFrame();
+
+ // Produce the initial HEADER frame for the stream with the given
+ // block. The stream must already be activated.
+ scoped_ptr<SpdyFrame> ProduceHeaderFrame(
+ scoped_ptr<SpdyHeaderBlock> header_block);
+
+ // Queues the send for next frame of the remaining data in
+ // |pending_send_data_|. Must be called only when
+ // |pending_send_data_| is set.
+ void QueueNextDataFrame();
+
+ // Merge the given headers into |response_headers_| and calls
+ // OnResponseHeadersUpdated() on the delegate (if attached).
+ // Returns a status code; if it is an error, the stream was closed
+ // by this function.
+ int MergeWithResponseHeaders(const SpdyHeaderBlock& new_response_headers);
+
+ const SpdyStreamType type_;
+
+ base::WeakPtrFactory<SpdyStream> weak_ptr_factory_;
+
+ // Sentinel variable used to make sure we don't get destroyed by a
+ // function called from DoLoop().
+ bool in_do_loop_;
+
+ // There is a small period of time between when a server pushed stream is
+ // first created, and the pushed data is replayed. Any data received during
+ // this time should continue to be buffered.
+ bool continue_buffering_data_;
+
+ SpdyStreamId stream_id_;
+ const GURL url_;
+ const RequestPriority priority_;
+ size_t slot_;
+
+ // Flow control variables.
+ bool send_stalled_by_flow_control_;
+ int32 send_window_size_;
+ int32 recv_window_size_;
+ int32 unacked_recv_window_bytes_;
+
+ ScopedBandwidthMetrics metrics_;
+
+ const base::WeakPtr<SpdySession> session_;
+
+ // The transaction should own the delegate.
+ SpdyStream::Delegate* delegate_;
+
+ // Whether or not we have more data to send on this stream.
+ SpdySendStatus send_status_;
+
+ // The headers for the request to send.
+ //
+ // TODO(akalin): Hang onto this only until we send it. This
+ // necessitates stashing the URL separately.
+ scoped_ptr<SpdyHeaderBlock> request_headers_;
+
+ // The data waiting to be sent.
+ scoped_refptr<DrainableIOBuffer> pending_send_data_;
+
+ // The time at which the request was made that resulted in this response.
+ // For cached responses, this time could be "far" in the past.
+ base::Time request_time_;
+
+ SpdyHeaderBlock response_headers_;
+ SpdyResponseHeadersStatus response_headers_status_;
+ base::Time response_time_;
+
+ State io_state_;
+
+ // Since we buffer the response, we also buffer the response status.
+ // Not valid until the stream is closed.
+ int response_status_;
+
+ BoundNetLog net_log_;
+
+ base::TimeTicks send_time_;
+ base::TimeTicks recv_first_byte_time_;
+ base::TimeTicks recv_last_byte_time_;
+
+ // Number of data bytes that have been sent/received on this stream, not
+ // including frame overhead. Note that this does not count headers.
+ int send_bytes_;
+ int recv_bytes_;
+
+ // Data received before delegate is attached.
+ ScopedVector<SpdyBuffer> pending_buffers_;
+
+ std::string domain_bound_private_key_;
+ std::string domain_bound_cert_;
+ ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+
+ // When OnFrameWriteComplete() is called, these variables are set.
+ SpdyFrameType just_completed_frame_type_;
+ size_t just_completed_frame_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_H_
diff --git a/chromium/net/spdy/spdy_stream_test_util.cc b/chromium/net/spdy/spdy_stream_test_util.cc
new file mode 100644
index 00000000000..835ac848b5b
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream_test_util.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_stream_test_util.h"
+
+#include <cstddef>
+
+#include "base/stl_util.h"
+#include "net/base/completion_callback.h"
+#include "net/spdy/spdy_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+ClosingDelegate::ClosingDelegate(
+ const base::WeakPtr<SpdyStream>& stream) : stream_(stream) {
+ DCHECK(stream_);
+}
+
+ClosingDelegate::~ClosingDelegate() {}
+
+void ClosingDelegate::OnRequestHeadersSent() {}
+
+SpdyResponseHeadersStatus ClosingDelegate::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+}
+
+void ClosingDelegate::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {}
+
+void ClosingDelegate::OnDataSent() {}
+
+void ClosingDelegate::OnClose(int status) {
+ DCHECK(stream_);
+ stream_->Close();
+ // The |stream_| may still be alive (if it is our delegate).
+}
+
+StreamDelegateBase::StreamDelegateBase(
+ const base::WeakPtr<SpdyStream>& stream)
+ : stream_(stream),
+ stream_id_(0),
+ send_headers_completed_(false) {
+}
+
+StreamDelegateBase::~StreamDelegateBase() {
+}
+
+void StreamDelegateBase::OnRequestHeadersSent() {
+ stream_id_ = stream_->stream_id();
+ EXPECT_NE(stream_id_, 0u);
+ send_headers_completed_ = true;
+}
+
+SpdyResponseHeadersStatus StreamDelegateBase::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ EXPECT_EQ(stream_->type() != SPDY_PUSH_STREAM, send_headers_completed_);
+ response_headers_ = response_headers;
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+}
+
+void StreamDelegateBase::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ if (buffer)
+ received_data_queue_.Enqueue(buffer.Pass());
+}
+
+void StreamDelegateBase::OnDataSent() {}
+
+void StreamDelegateBase::OnClose(int status) {
+ if (!stream_.get())
+ return;
+ stream_id_ = stream_->stream_id();
+ stream_.reset();
+ callback_.callback().Run(status);
+}
+
+int StreamDelegateBase::WaitForClose() {
+ int result = callback_.WaitForResult();
+ EXPECT_TRUE(!stream_.get());
+ return result;
+}
+
+std::string StreamDelegateBase::TakeReceivedData() {
+ size_t len = received_data_queue_.GetTotalSize();
+ std::string received_data(len, '\0');
+ if (len > 0) {
+ EXPECT_EQ(
+ len,
+ received_data_queue_.Dequeue(string_as_array(&received_data), len));
+ }
+ return received_data;
+}
+
+std::string StreamDelegateBase::GetResponseHeaderValue(
+ const std::string& name) const {
+ SpdyHeaderBlock::const_iterator it = response_headers_.find(name);
+ return (it == response_headers_.end()) ? std::string() : it->second;
+}
+
+StreamDelegateDoNothing::StreamDelegateDoNothing(
+ const base::WeakPtr<SpdyStream>& stream)
+ : StreamDelegateBase(stream) {}
+
+StreamDelegateDoNothing::~StreamDelegateDoNothing() {
+}
+
+StreamDelegateSendImmediate::StreamDelegateSendImmediate(
+ const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data)
+ : StreamDelegateBase(stream),
+ data_(data) {}
+
+StreamDelegateSendImmediate::~StreamDelegateSendImmediate() {
+}
+
+SpdyResponseHeadersStatus StreamDelegateSendImmediate::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ SpdyResponseHeadersStatus status =
+ StreamDelegateBase::OnResponseHeadersUpdated(response_headers);
+ if (data_.data()) {
+ scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(data_.as_string()));
+ stream()->SendData(buf.get(), buf->size(), MORE_DATA_TO_SEND);
+ }
+ return status;
+}
+
+StreamDelegateWithBody::StreamDelegateWithBody(
+ const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data)
+ : StreamDelegateBase(stream),
+ buf_(new StringIOBuffer(data.as_string())) {}
+
+StreamDelegateWithBody::~StreamDelegateWithBody() {
+}
+
+void StreamDelegateWithBody::OnRequestHeadersSent() {
+ StreamDelegateBase::OnRequestHeadersSent();
+ stream()->SendData(buf_.get(), buf_->size(), NO_MORE_DATA_TO_SEND);
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_stream_test_util.h b/chromium/net/spdy/spdy_stream_test_util.h
new file mode 100644
index 00000000000..2d0a198ffad
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream_test_util.h
@@ -0,0 +1,127 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
+#define NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/base/io_buffer.h"
+#include "net/base/test_completion_callback.h"
+#include "net/spdy/spdy_read_queue.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+namespace test {
+
+// Delegate that calls Close() on |stream_| on OnClose. Used by tests
+// to make sure that such an action is harmless.
+class ClosingDelegate : public SpdyStream::Delegate {
+ public:
+ explicit ClosingDelegate(const base::WeakPtr<SpdyStream>& stream);
+ virtual ~ClosingDelegate();
+
+ // SpdyStream::Delegate implementation.
+ virtual void OnRequestHeadersSent() OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnDataSent() OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ // Returns whether or not the stream is closed.
+ bool StreamIsClosed() const { return !stream_.get(); }
+
+ private:
+ base::WeakPtr<SpdyStream> stream_;
+};
+
+// Base class with shared functionality for test delegate
+// implementations below.
+class StreamDelegateBase : public SpdyStream::Delegate {
+ public:
+ explicit StreamDelegateBase(const base::WeakPtr<SpdyStream>& stream);
+ virtual ~StreamDelegateBase();
+
+ virtual void OnRequestHeadersSent() OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnDataSent() OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ // Waits for the stream to be closed and returns the status passed
+ // to OnClose().
+ int WaitForClose();
+
+ // Drains all data from the underlying read queue and returns it as
+ // a string.
+ std::string TakeReceivedData();
+
+ // Returns whether or not the stream is closed.
+ bool StreamIsClosed() const { return !stream_.get(); }
+
+ // Returns the stream's ID. If called when the stream is closed,
+ // returns the stream's ID when it was open.
+ SpdyStreamId stream_id() const { return stream_id_; }
+
+ std::string GetResponseHeaderValue(const std::string& name) const;
+ bool send_headers_completed() const { return send_headers_completed_; }
+
+ protected:
+ const base::WeakPtr<SpdyStream>& stream() { return stream_; }
+
+ private:
+ base::WeakPtr<SpdyStream> stream_;
+ SpdyStreamId stream_id_;
+ TestCompletionCallback callback_;
+ bool send_headers_completed_;
+ SpdyHeaderBlock response_headers_;
+ SpdyReadQueue received_data_queue_;
+};
+
+// Test delegate that does nothing. Used to capture data about the
+// stream, e.g. its id when it was open.
+class StreamDelegateDoNothing : public StreamDelegateBase {
+ public:
+ StreamDelegateDoNothing(const base::WeakPtr<SpdyStream>& stream);
+ virtual ~StreamDelegateDoNothing();
+};
+
+// Test delegate that sends data immediately in OnResponseHeadersUpdated().
+class StreamDelegateSendImmediate : public StreamDelegateBase {
+ public:
+ // |data| can be NULL.
+ StreamDelegateSendImmediate(const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data);
+ virtual ~StreamDelegateSendImmediate();
+
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+
+ private:
+ base::StringPiece data_;
+};
+
+// Test delegate that sends body data.
+class StreamDelegateWithBody : public StreamDelegateBase {
+ public:
+ StreamDelegateWithBody(const base::WeakPtr<SpdyStream>& stream,
+ base::StringPiece data);
+ virtual ~StreamDelegateWithBody();
+
+ virtual void OnRequestHeadersSent() OVERRIDE;
+
+ private:
+ scoped_refptr<StringIOBuffer> buf_;
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
diff --git a/chromium/net/spdy/spdy_stream_unittest.cc b/chromium/net/spdy/spdy_stream_unittest.cc
new file mode 100644
index 00000000000..c98ba937be0
--- /dev/null
+++ b/chromium/net/spdy/spdy_stream_unittest.cc
@@ -0,0 +1,999 @@
+// Copyright 2013 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 <cstddef>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/request_priority.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace test {
+
+namespace {
+
+const char kStreamUrl[] = "http://www.google.com/";
+const char kPostBody[] = "\0hello!\xff";
+const size_t kPostBodyLength = arraysize(kPostBody);
+const base::StringPiece kPostBodyStringPiece(kPostBody, kPostBodyLength);
+
+class SpdyStreamTest : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ // A function that takes a SpdyStream and the number of bytes which
+ // will unstall the next frame completely.
+ typedef base::Callback<void(const base::WeakPtr<SpdyStream>&, int32)>
+ UnstallFunction;
+
+ SpdyStreamTest()
+ : spdy_util_(GetParam()),
+ session_deps_(GetParam()),
+ offset_(0) {}
+
+ base::WeakPtr<SpdySession> CreateDefaultSpdySession() {
+ SpdySessionKey key(HostPortPair("www.google.com", 80),
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ return CreateInsecureSpdySession(session_, key, BoundNetLog());
+ }
+
+ virtual void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void RunResumeAfterUnstallRequestResponseTest(
+ const UnstallFunction& unstall_function);
+
+ void RunResumeAfterUnstallBidirectionalTest(
+ const UnstallFunction& unstall_function);
+
+ // Add{Read,Write}() populates lists that are eventually passed to a
+ // SocketData class. |frame| must live for the whole test.
+
+ void AddRead(const SpdyFrame& frame) {
+ reads_.push_back(CreateMockRead(frame, offset_++));
+ }
+
+ void AddWrite(const SpdyFrame& frame) {
+ writes_.push_back(CreateMockWrite(frame, offset_++));
+ }
+
+ void AddReadEOF() {
+ reads_.push_back(MockRead(ASYNC, 0, offset_++));
+ }
+
+ MockRead* GetReads() {
+ return vector_as_array(&reads_);
+ }
+
+ size_t GetNumReads() const {
+ return reads_.size();
+ }
+
+ MockWrite* GetWrites() {
+ return vector_as_array(&writes_);
+ }
+
+ int GetNumWrites() const {
+ return writes_.size();
+ }
+
+ SpdyTestUtil spdy_util_;
+ SpdySessionDependencies session_deps_;
+ scoped_refptr<HttpNetworkSession> session_;
+
+ private:
+ // Used by Add{Read,Write}() above.
+ std::vector<MockWrite> writes_;
+ std::vector<MockRead> reads_;
+ int offset_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdyStreamTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(SpdyStreamTest, SendDataAfterOpen) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, PushedStream) {
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> spdy_session(CreateDefaultSpdySession());
+
+ // Conjure up a stream.
+ SpdyStream stream(SPDY_PUSH_STREAM,
+ spdy_session,
+ GURL(),
+ DEFAULT_PRIORITY,
+ kSpdyStreamInitialWindowSize,
+ kSpdyStreamInitialWindowSize,
+ BoundNetLog());
+ stream.set_stream_id(2);
+ EXPECT_FALSE(stream.HasUrlFromHeaders());
+
+ // Set a couple of headers.
+ SpdyHeaderBlock response;
+ spdy_util_.AddUrlToHeaderBlock(kStreamUrl, &response);
+ stream.OnInitialResponseHeadersReceived(
+ response, base::Time::Now(), base::TimeTicks::Now());
+
+ // Send some basic headers.
+ SpdyHeaderBlock headers;
+ headers[spdy_util_.GetStatusKey()] = "200";
+ headers[spdy_util_.GetVersionKey()] = "OK";
+ stream.OnAdditionalResponseHeadersReceived(headers);
+
+ EXPECT_TRUE(stream.HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream.GetUrlFromHeaders().spec());
+
+ StreamDelegateDoNothing delegate(stream.GetWeakPtr());
+ stream.SetDelegate(&delegate);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+
+ EXPECT_TRUE(spdy_session == NULL);
+}
+
+TEST_P(SpdyStreamTest, StreamError) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ CapturingBoundNetLog log;
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ const SpdyStreamId stream_id = delegate.stream_id();
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+
+ // Check that the NetLog was filled reasonably.
+ net::CapturingNetLog::CapturedEntryList entries;
+ log.GetEntries(&entries);
+ EXPECT_LT(0u, entries.size());
+
+ // Check that we logged SPDY_STREAM_ERROR correctly.
+ int pos = net::ExpectLogContainsSomewhere(
+ entries, 0,
+ net::NetLog::TYPE_SPDY_STREAM_ERROR,
+ net::NetLog::PHASE_NONE);
+
+ int stream_id2;
+ ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &stream_id2));
+ EXPECT_EQ(static_cast<int>(stream_id), stream_id2);
+}
+
+// Make sure that large blocks of data are properly split up into
+// frame-sized chunks for a request/response (i.e., an HTTP-like)
+// stream.
+TEST_P(SpdyStreamTest, SendLargeDataAfterOpenRequestResponse) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+ scoped_ptr<SpdyFrame> chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), false));
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+
+ scoped_ptr<SpdyFrame> last_chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), true));
+ AddWrite(*last_chunk);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x');
+ StreamDelegateWithBody delegate(stream, body_data);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Make sure that large blocks of data are properly split up into
+// frame-sized chunks for a bidirectional (i.e., non-HTTP-like)
+// stream.
+TEST_P(SpdyStreamTest, SendLargeDataAfterOpenBidirectional) {
+ GURL url(kStreamUrl);
+
+ session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0));
+ AddRead(*resp);
+
+ std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+ scoped_ptr<SpdyFrame> chunk(
+ spdy_util_.ConstructSpdyBodyFrame(
+ 1, chunk_data.data(), chunk_data.length(), false));
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+ AddWrite(*chunk);
+
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ std::string body_data(3 * kMaxSpdyFrameChunkSize, 'x');
+ StreamDelegateSendImmediate delegate(stream, body_data);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+ EXPECT_EQ("HTTP/1.1",
+ delegate.GetResponseHeaderValue(spdy_util_.GetVersionKey()));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+// Receiving a header with uppercase ASCII should result in a protocol
+// error.
+TEST_P(SpdyStreamTest, UpperCaseHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(4);
+
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII should result in a protocol
+// error even for a push stream.
+TEST_P(SpdyStreamTest, UpperCaseHeadersOnPush) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ const char* const extra_headers[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(extra_headers, 1, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(4);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII in a HEADERS frame should
+// result in a protocol error.
+TEST_P(SpdyStreamTest, UpperCaseHeadersInHeadersFrame) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["X-UpperCase"] = "yes";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a duplicate header in a HEADERS frame should result in a
+// protocol error.
+TEST_P(SpdyStreamTest, DuplicateHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)[spdy_util_.GetStatusKey()] = "500 Server Error";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// The tests below are only for SPDY/3 and above.
+
+// Call IncreaseSendWindowSize on a stream with a large enough delta
+// to overflow an int32. The SpdyStream should handle that case
+// gracefully.
+TEST_P(SpdyStreamTest, IncreaseSendWindowSizeOverflow) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ // Triggered by the overflowing call to IncreaseSendWindowSize
+ // below.
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ CapturingBoundNetLog log;
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+ GURL url(kStreamUrl);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, log.bound());
+ ASSERT_TRUE(stream.get() != NULL);
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+
+ int32 old_send_window_size = stream->send_window_size();
+ ASSERT_GT(old_send_window_size, 0);
+ int32 delta_window_size = kint32max - old_send_window_size + 1;
+ stream->IncreaseSendWindowSize(delta_window_size);
+ EXPECT_EQ(NULL, stream.get());
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose());
+}
+
+// Functions used with
+// RunResumeAfterUnstall{RequestResponse,Bidirectional}Test().
+
+void StallStream(const base::WeakPtr<SpdyStream>& stream) {
+ // Reduce the send window size to 0 to stall.
+ while (stream->send_window_size() > 0) {
+ stream->DecreaseSendWindowSize(
+ std::min(kMaxSpdyFrameChunkSize, stream->send_window_size()));
+ }
+}
+
+void IncreaseStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream,
+ int32 delta_window_size) {
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->IncreaseSendWindowSize(delta_window_size);
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+}
+
+void AdjustStreamSendWindowSize(const base::WeakPtr<SpdyStream>& stream,
+ int32 delta_window_size) {
+ // Make sure that negative adjustments are handled properly.
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(-delta_window_size);
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(+delta_window_size);
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+ stream->AdjustSendWindowSize(+delta_window_size);
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+}
+
+// Given an unstall function, runs a test to make sure that a
+// request/response (i.e., an HTTP-like) stream resumes after a stall
+// and unstall.
+void SpdyStreamTest::RunResumeAfterUnstallRequestResponseTest(
+ const UnstallFunction& unstall_function) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> body(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, true));
+ AddWrite(*body);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateWithBody delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ StallStream(stream);
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+
+ unstall_function.Run(stream, kPostBodyLength);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ data.RunFor(3);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(), delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseRequestResponse) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallRequestResponseTest(
+ base::Bind(&IncreaseStreamSendWindowSize));
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustRequestResponse) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallRequestResponseTest(
+ base::Bind(&AdjustStreamSendWindowSize));
+}
+
+// Given an unstall function, runs a test to make sure that a
+// bidirectional (i.e., non-HTTP-like) stream resumes after a stall
+// and unstall.
+void SpdyStreamTest::RunResumeAfterUnstallBidirectionalTest(
+ const UnstallFunction& unstall_function) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> req(
+ spdy_util_.ConstructSpdyPost(
+ kStreamUrl, 1, kPostBodyLength, LOWEST, NULL, 0));
+ AddWrite(*req);
+
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*resp);
+
+ scoped_ptr<SpdyFrame> msg(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddWrite(*msg);
+
+ scoped_ptr<SpdyFrame> echo(
+ spdy_util_.ConstructSpdyBodyFrame(1, kPostBody, kPostBodyLength, false));
+ AddRead(*echo);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ base::WeakPtr<SpdySession> session(CreateDefaultSpdySession());
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateSendImmediate delegate(stream, kPostBodyStringPiece);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrlFromHeaders());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructPostHeaderBlock(kStreamUrl, kPostBodyLength));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrlFromHeaders());
+ EXPECT_EQ(kStreamUrl, stream->GetUrlFromHeaders().spec());
+
+ data.RunFor(1);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ StallStream(stream);
+
+ data.RunFor(1);
+
+ EXPECT_TRUE(stream->send_stalled_by_flow_control());
+
+ unstall_function.Run(stream, kPostBodyLength);
+
+ EXPECT_FALSE(stream->send_stalled_by_flow_control());
+
+ data.RunFor(3);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+
+ EXPECT_TRUE(delegate.send_headers_completed());
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(":status"));
+ EXPECT_EQ("HTTP/1.1", delegate.GetResponseHeaderValue(":version"));
+ EXPECT_EQ(std::string(kPostBody, kPostBodyLength),
+ delegate.TakeReceivedData());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeIncreaseBidirectional) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallBidirectionalTest(
+ base::Bind(&IncreaseStreamSendWindowSize));
+}
+
+TEST_P(SpdyStreamTest, ResumeAfterSendWindowSizeAdjustBidirectional) {
+ if (spdy_util_.protocol() < kProtoSPDY3)
+ return;
+
+ RunResumeAfterUnstallBidirectionalTest(
+ base::Bind(&AdjustStreamSendWindowSize));
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_test_util_common.cc b/chromium/net/spdy/spdy_test_util_common.cc
new file mode 100644
index 00000000000..95466847b93
--- /dev/null
+++ b/chromium/net/spdy/spdy_test_util_common.cc
@@ -0,0 +1,1235 @@
+// Copyright (c) 2013 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/spdy/spdy_test_util_common.h"
+
+#include <cstddef>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "net/cert/mock_cert_verifier.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/transport_client_socket_pool.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+namespace {
+
+bool next_proto_is_spdy(NextProto next_proto) {
+ // TODO(akalin): Change this to kProtoSPDYMinimumVersion once we
+ // stop supporting SPDY/1.
+ return next_proto >= kProtoSPDY2 && next_proto <= kProtoSPDYMaximumVersion;
+}
+
+// Parses a URL into the scheme, host, and path components required for a
+// SPDY request.
+void ParseUrl(base::StringPiece url, std::string* scheme, std::string* host,
+ std::string* path) {
+ GURL gurl(url.as_string());
+ path->assign(gurl.PathForRequest());
+ scheme->assign(gurl.scheme());
+ host->assign(gurl.host());
+ if (gurl.has_port()) {
+ host->append(":");
+ host->append(gurl.port());
+ }
+}
+
+} // namespace
+
+std::vector<NextProto> SpdyNextProtos() {
+ std::vector<NextProto> next_protos;
+ for (int i = kProtoMinimumVersion; i <= kProtoMaximumVersion; ++i) {
+ NextProto proto = static_cast<NextProto>(i);
+ if (proto != kProtoSPDY1 && proto != kProtoSPDY21)
+ next_protos.push_back(proto);
+ }
+ return next_protos;
+}
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks) {
+ MockWrite* chunks = new MockWrite[num_chunks];
+ int chunk_size = length / num_chunks;
+ for (int index = 0; index < num_chunks; index++) {
+ const char* ptr = data + (index * chunk_size);
+ if (index == num_chunks - 1)
+ chunk_size += length % chunk_size; // The last chunk takes the remainder.
+ chunks[index] = MockWrite(ASYNC, ptr, chunk_size);
+ }
+ return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks) {
+ return ChopWriteFrame(frame.data(), frame.size(), num_chunks);
+}
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks) {
+ MockRead* chunks = new MockRead[num_chunks];
+ int chunk_size = length / num_chunks;
+ for (int index = 0; index < num_chunks; index++) {
+ const char* ptr = data + (index * chunk_size);
+ if (index == num_chunks - 1)
+ chunk_size += length % chunk_size; // The last chunk takes the remainder.
+ chunks[index] = MockRead(ASYNC, ptr, chunk_size);
+ }
+ return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks) {
+ return ChopReadFrame(frame.data(), frame.size(), num_chunks);
+}
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendToHeaderBlock(const char* const extra_headers[],
+ int extra_header_count,
+ SpdyHeaderBlock* headers) {
+ std::string this_header;
+ std::string this_value;
+
+ if (!extra_header_count)
+ return;
+
+ // Sanity check: Non-NULL header list.
+ DCHECK(NULL != extra_headers) << "NULL header value pair list";
+ // Sanity check: Non-NULL header map.
+ DCHECK(NULL != headers) << "NULL header map";
+ // Copy in the headers.
+ for (int i = 0; i < extra_header_count; i++) {
+ // Sanity check: Non-empty header.
+ DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair";
+ this_header = extra_headers[i * 2];
+ std::string::size_type header_len = this_header.length();
+ if (!header_len)
+ continue;
+ this_value = extra_headers[1 + (i * 2)];
+ std::string new_value;
+ if (headers->find(this_header) != headers->end()) {
+ // More than one entry in the header.
+ // Don't add the header again, just the append to the value,
+ // separated by a NULL character.
+
+ // Adjust the value.
+ new_value = (*headers)[this_header];
+ // Put in a NULL separator.
+ new_value.append(1, '\0');
+ // Append the new value.
+ new_value += this_value;
+ } else {
+ // Not a duplicate, just write the value.
+ new_value = this_value;
+ }
+ (*headers)[this_header] = new_value;
+ }
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req) {
+ return MockWrite(ASYNC, req.data(), req.size());
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq) {
+ return CreateMockWrite(req, seq, ASYNC);
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode) {
+ return MockWrite(mode, req.data(), req.size(), seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp) {
+ return MockRead(ASYNC, resp.data(), resp.size());
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq) {
+ return CreateMockRead(resp, seq, ASYNC);
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode) {
+ return MockRead(mode, resp.data(), resp.size(), seq);
+}
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+ char* buff, int buff_len) {
+ int total_len = 0;
+ for (int i = 0; i < num_frames; ++i) {
+ total_len += frames[i]->size();
+ }
+ DCHECK_LE(total_len, buff_len);
+ char* ptr = buff;
+ for (int i = 0; i < num_frames; ++i) {
+ int len = frames[i]->size();
+ memcpy(ptr, frames[i]->data(), len);
+ ptr += len;
+ }
+ return total_len;
+}
+
+namespace {
+
+class PriorityGetter : public BufferedSpdyFramerVisitorInterface {
+ public:
+ PriorityGetter() : priority_(0) {}
+ virtual ~PriorityGetter() {}
+
+ SpdyPriority priority() const {
+ return priority_;
+ }
+
+ virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE {}
+ virtual void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) OVERRIDE {}
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) OVERRIDE {
+ priority_ = priority;
+ }
+ virtual void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE {}
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE {}
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE {}
+ virtual void OnSettings(bool clear_persisted) OVERRIDE {}
+ virtual void OnSetting(
+ SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE {}
+ virtual void OnPing(uint32 unique_id) OVERRIDE {}
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE {}
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {}
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {}
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {}
+
+ private:
+ SpdyPriority priority_;
+};
+
+} // namespace
+
+bool GetSpdyPriority(SpdyMajorVersion version,
+ const SpdyFrame& frame,
+ SpdyPriority* priority) {
+ BufferedSpdyFramer framer(version, false);
+ PriorityGetter priority_getter;
+ framer.set_visitor(&priority_getter);
+ size_t frame_size = frame.size();
+ if (framer.ProcessInput(frame.data(), frame_size) != frame_size) {
+ return false;
+ }
+ *priority = priority_getter.priority();
+ return true;
+}
+
+base::WeakPtr<SpdyStream> CreateStreamSynchronously(
+ SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ const BoundNetLog& net_log) {
+ SpdyStreamRequest stream_request;
+ int rv = stream_request.StartRequest(type, session, url, priority, net_log,
+ CompletionCallback());
+ return
+ (rv == OK) ? stream_request.ReleaseStream() : base::WeakPtr<SpdyStream>();
+}
+
+StreamReleaserCallback::StreamReleaserCallback() {}
+
+StreamReleaserCallback::~StreamReleaserCallback() {}
+
+CompletionCallback StreamReleaserCallback::MakeCallback(
+ SpdyStreamRequest* request) {
+ return base::Bind(&StreamReleaserCallback::OnComplete,
+ base::Unretained(this),
+ request);
+}
+
+void StreamReleaserCallback::OnComplete(
+ SpdyStreamRequest* request, int result) {
+ if (result == OK)
+ request->ReleaseStream()->Cancel();
+ SetResult(result);
+}
+
+MockECSignatureCreator::MockECSignatureCreator(crypto::ECPrivateKey* key)
+ : key_(key) {
+}
+
+bool MockECSignatureCreator::Sign(const uint8* data,
+ int data_len,
+ std::vector<uint8>* signature) {
+ std::vector<uint8> private_key_value;
+ key_->ExportValue(&private_key_value);
+ std::string head = "fakesignature";
+ std::string tail = "/fakesignature";
+
+ signature->clear();
+ signature->insert(signature->end(), head.begin(), head.end());
+ signature->insert(signature->end(), private_key_value.begin(),
+ private_key_value.end());
+ signature->insert(signature->end(), '-');
+ signature->insert(signature->end(), data, data + data_len);
+ signature->insert(signature->end(), tail.begin(), tail.end());
+ return true;
+}
+
+bool MockECSignatureCreator::DecodeSignature(
+ const std::vector<uint8>& signature,
+ std::vector<uint8>* out_raw_sig) {
+ *out_raw_sig = signature;
+ return true;
+}
+
+MockECSignatureCreatorFactory::MockECSignatureCreatorFactory() {
+ crypto::ECSignatureCreator::SetFactoryForTesting(this);
+}
+
+MockECSignatureCreatorFactory::~MockECSignatureCreatorFactory() {
+ crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
+}
+
+crypto::ECSignatureCreator* MockECSignatureCreatorFactory::Create(
+ crypto::ECPrivateKey* key) {
+ return new MockECSignatureCreator(key);
+}
+
+SpdySessionDependencies::SpdySessionDependencies(NextProto protocol)
+ : host_resolver(new MockCachingHostResolver),
+ cert_verifier(new MockCertVerifier),
+ transport_security_state(new TransportSecurityState),
+ proxy_service(ProxyService::CreateDirect()),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ socket_factory(new MockClientSocketFactory),
+ deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+ http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+ enable_ip_pooling(true),
+ enable_compression(false),
+ enable_ping(false),
+ enable_user_alternate_protocol_ports(false),
+ protocol(protocol),
+ stream_initial_recv_window_size(kSpdyStreamInitialWindowSize),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {
+ DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol;
+
+ // Note: The CancelledTransaction test does cleanup by running all
+ // tasks in the message loop (RunAllPending). Unfortunately, that
+ // doesn't clean up tasks on the host resolver thread; and
+ // TCPConnectJob is currently not cancellable. Using synchronous
+ // lookups allows the test to shutdown cleanly. Until we have
+ // cancellable TCPConnectJobs, use synchronous lookups.
+ host_resolver->set_synchronous_mode(true);
+}
+
+SpdySessionDependencies::SpdySessionDependencies(
+ NextProto protocol, ProxyService* proxy_service)
+ : host_resolver(new MockHostResolver),
+ cert_verifier(new MockCertVerifier),
+ transport_security_state(new TransportSecurityState),
+ proxy_service(proxy_service),
+ ssl_config_service(new SSLConfigServiceDefaults),
+ socket_factory(new MockClientSocketFactory),
+ deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+ http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+ enable_ip_pooling(true),
+ enable_compression(false),
+ enable_ping(false),
+ enable_user_alternate_protocol_ports(false),
+ protocol(protocol),
+ stream_initial_recv_window_size(kSpdyStreamInitialWindowSize),
+ time_func(&base::TimeTicks::Now),
+ net_log(NULL) {
+ DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol;
+}
+
+SpdySessionDependencies::~SpdySessionDependencies() {}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSession(
+ SpdySessionDependencies* session_deps) {
+ net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+ params.client_socket_factory = session_deps->socket_factory.get();
+ HttpNetworkSession* http_session = new HttpNetworkSession(params);
+ SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+ pool_peer.SetEnableSendingInitialData(false);
+ return http_session;
+}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSessionDeterministic(
+ SpdySessionDependencies* session_deps) {
+ net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+ params.client_socket_factory =
+ session_deps->deterministic_socket_factory.get();
+ HttpNetworkSession* http_session = new HttpNetworkSession(params);
+ SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+ pool_peer.SetEnableSendingInitialData(false);
+ return http_session;
+}
+
+// static
+net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams(
+ SpdySessionDependencies* session_deps) {
+ DCHECK(next_proto_is_spdy(session_deps->protocol)) <<
+ "Invalid protocol: " << session_deps->protocol;
+
+ net::HttpNetworkSession::Params params;
+ params.host_resolver = session_deps->host_resolver.get();
+ params.cert_verifier = session_deps->cert_verifier.get();
+ params.transport_security_state =
+ session_deps->transport_security_state.get();
+ params.proxy_service = session_deps->proxy_service.get();
+ params.ssl_config_service = session_deps->ssl_config_service.get();
+ params.http_auth_handler_factory =
+ session_deps->http_auth_handler_factory.get();
+ params.http_server_properties =
+ session_deps->http_server_properties.GetWeakPtr();
+ params.enable_spdy_compression = session_deps->enable_compression;
+ params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping;
+ params.enable_user_alternate_protocol_ports =
+ session_deps->enable_user_alternate_protocol_ports;
+ params.spdy_default_protocol = session_deps->protocol;
+ params.spdy_stream_initial_recv_window_size =
+ session_deps->stream_initial_recv_window_size;
+ params.time_func = session_deps->time_func;
+ params.trusted_spdy_proxy = session_deps->trusted_spdy_proxy;
+ params.net_log = session_deps->net_log;
+ return params;
+}
+
+SpdyURLRequestContext::SpdyURLRequestContext(NextProto protocol)
+ : storage_(this) {
+ DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol;
+
+ storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+ storage_.set_cert_verifier(new MockCertVerifier);
+ storage_.set_transport_security_state(new TransportSecurityState);
+ storage_.set_proxy_service(ProxyService::CreateDirect());
+ storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+ storage_.set_http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault(
+ host_resolver()));
+ storage_.set_http_server_properties(
+ scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));
+ net::HttpNetworkSession::Params params;
+ params.client_socket_factory = &socket_factory_;
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ params.transport_security_state = transport_security_state();
+ params.proxy_service = proxy_service();
+ params.ssl_config_service = ssl_config_service();
+ params.http_auth_handler_factory = http_auth_handler_factory();
+ params.network_delegate = network_delegate();
+ params.enable_spdy_compression = false;
+ params.enable_spdy_ping_based_connection_checking = false;
+ params.spdy_default_protocol = protocol;
+ params.http_server_properties = http_server_properties();
+ scoped_refptr<HttpNetworkSession> network_session(
+ new HttpNetworkSession(params));
+ SpdySessionPoolPeer pool_peer(network_session->spdy_session_pool());
+ pool_peer.SetEnableSendingInitialData(false);
+ storage_.set_http_transaction_factory(new HttpCache(
+ network_session.get(), HttpCache::DefaultBackend::InMemory(0)));
+}
+
+SpdyURLRequestContext::~SpdyURLRequestContext() {
+}
+
+bool HasSpdySession(SpdySessionPool* pool, const SpdySessionKey& key) {
+ return pool->FindAvailableSession(key, BoundNetLog()) != NULL;
+}
+
+namespace {
+
+base::WeakPtr<SpdySession> CreateSpdySessionHelper(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log,
+ Error expected_status,
+ bool is_secure) {
+ EXPECT_FALSE(HasSpdySession(http_session->spdy_session_pool(), key));
+
+ scoped_refptr<TransportSocketParams> transport_params(
+ new TransportSocketParams(
+ key.host_port_pair(), MEDIUM, false, false,
+ OnHostResolutionCallback()));
+
+ scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+ TestCompletionCallback callback;
+
+ int rv = ERR_UNEXPECTED;
+ if (is_secure) {
+ SSLConfig ssl_config;
+ scoped_refptr<SOCKSSocketParams> socks_params;
+ scoped_refptr<HttpProxySocketParams> http_proxy_params;
+ scoped_refptr<SSLSocketParams> ssl_params(
+ new SSLSocketParams(transport_params,
+ socks_params,
+ http_proxy_params,
+ ProxyServer::SCHEME_DIRECT,
+ key.host_port_pair(),
+ ssl_config,
+ key.privacy_mode(),
+ 0,
+ false,
+ false));
+ rv = connection->Init(key.host_port_pair().ToString(),
+ ssl_params,
+ MEDIUM,
+ callback.callback(),
+ http_session->GetSSLSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ net_log);
+ } else {
+ rv = connection->Init(key.host_port_pair().ToString(),
+ transport_params,
+ MEDIUM,
+ callback.callback(),
+ http_session->GetTransportSocketPool(
+ HttpNetworkSession::NORMAL_SOCKET_POOL),
+ net_log);
+ }
+
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+
+ EXPECT_EQ(OK, rv);
+
+ base::WeakPtr<SpdySession> spdy_session;
+ EXPECT_EQ(
+ expected_status,
+ http_session->spdy_session_pool()->CreateAvailableSessionFromSocket(
+ key, connection.Pass(), net_log, OK, &spdy_session,
+ is_secure));
+ EXPECT_EQ(expected_status == OK, spdy_session != NULL);
+ EXPECT_EQ(expected_status == OK,
+ HasSpdySession(http_session->spdy_session_pool(), key));
+ return spdy_session;
+}
+
+} // namespace
+
+base::WeakPtr<SpdySession> CreateInsecureSpdySession(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log) {
+ return CreateSpdySessionHelper(http_session, key, net_log,
+ OK, false /* is_secure */);
+}
+
+void TryCreateInsecureSpdySessionExpectingFailure(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ Error expected_error,
+ const BoundNetLog& net_log) {
+ DCHECK_LT(expected_error, ERR_IO_PENDING);
+ CreateSpdySessionHelper(http_session, key, net_log,
+ expected_error, false /* is_secure */);
+}
+
+base::WeakPtr<SpdySession> CreateSecureSpdySession(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log) {
+ return CreateSpdySessionHelper(http_session, key, net_log,
+ OK, true /* is_secure */);
+}
+
+namespace {
+
+// A ClientSocket used for CreateFakeSpdySession() below.
+class FakeSpdySessionClientSocket : public MockClientSocket {
+ public:
+ FakeSpdySessionClientSocket(int read_result)
+ : MockClientSocket(BoundNetLog()),
+ read_result_(read_result) {}
+
+ virtual ~FakeSpdySessionClientSocket() {}
+
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return read_result_;
+ }
+
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ // Return kProtoUnknown to use the pool's default protocol.
+ virtual NextProto GetNegotiatedProtocol() const OVERRIDE {
+ return kProtoUnknown;
+ }
+
+ // The functions below are not expected to be called.
+
+ virtual int Connect(const CompletionCallback& callback) OVERRIDE {
+ ADD_FAILURE();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual bool WasEverUsed() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool UsingTCPFastOpen() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool WasNpnNegotiated() const OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE {
+ ADD_FAILURE();
+ return false;
+ }
+
+ private:
+ int read_result_;
+};
+
+base::WeakPtr<SpdySession> CreateFakeSpdySessionHelper(
+ SpdySessionPool* pool,
+ const SpdySessionKey& key,
+ Error expected_status) {
+ EXPECT_NE(expected_status, ERR_IO_PENDING);
+ EXPECT_FALSE(HasSpdySession(pool, key));
+ base::WeakPtr<SpdySession> spdy_session;
+ scoped_ptr<ClientSocketHandle> handle(new ClientSocketHandle());
+ handle->SetSocket(scoped_ptr<StreamSocket>(new FakeSpdySessionClientSocket(
+ expected_status == OK ? ERR_IO_PENDING : expected_status)));
+ EXPECT_EQ(
+ expected_status,
+ pool->CreateAvailableSessionFromSocket(
+ key, handle.Pass(), BoundNetLog(), OK, &spdy_session,
+ true /* is_secure */));
+ EXPECT_EQ(expected_status == OK, spdy_session != NULL);
+ EXPECT_EQ(expected_status == OK, HasSpdySession(pool, key));
+ return spdy_session;
+}
+
+} // namespace
+
+base::WeakPtr<SpdySession> CreateFakeSpdySession(SpdySessionPool* pool,
+ const SpdySessionKey& key) {
+ return CreateFakeSpdySessionHelper(pool, key, OK);
+}
+
+void TryCreateFakeSpdySessionExpectingFailure(SpdySessionPool* pool,
+ const SpdySessionKey& key,
+ Error expected_error) {
+ DCHECK_LT(expected_error, ERR_IO_PENDING);
+ CreateFakeSpdySessionHelper(pool, key, expected_error);
+}
+
+SpdySessionPoolPeer::SpdySessionPoolPeer(SpdySessionPool* pool) : pool_(pool) {
+}
+
+void SpdySessionPoolPeer::RemoveAliases(const SpdySessionKey& key) {
+ pool_->RemoveAliases(key);
+}
+
+void SpdySessionPoolPeer::DisableDomainAuthenticationVerification() {
+ pool_->verify_domain_authentication_ = false;
+}
+
+void SpdySessionPoolPeer::SetEnableSendingInitialData(bool enabled) {
+ pool_->enable_sending_initial_data_ = enabled;
+}
+
+SpdyTestUtil::SpdyTestUtil(NextProto protocol)
+ : protocol_(protocol),
+ spdy_version_(NextProtoToSpdyMajorVersion(protocol)) {
+ DCHECK(next_proto_is_spdy(protocol)) << "Invalid protocol: " << protocol;
+}
+
+void SpdyTestUtil::AddUrlToHeaderBlock(base::StringPiece url,
+ SpdyHeaderBlock* headers) const {
+ if (is_spdy2()) {
+ (*headers)["url"] = url.as_string();
+ } else {
+ std::string scheme, host, path;
+ ParseUrl(url, &scheme, &host, &path);
+ (*headers)[GetSchemeKey()] = scheme;
+ (*headers)[GetHostKey()] = host;
+ (*headers)[GetPathKey()] = path;
+ }
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructGetHeaderBlock(
+ base::StringPiece url) const {
+ return ConstructHeaderBlock("GET", url, NULL);
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructGetHeaderBlockForProxy(
+ base::StringPiece url) const {
+ scoped_ptr<SpdyHeaderBlock> headers(ConstructGetHeaderBlock(url));
+ if (is_spdy2())
+ (*headers)[GetPathKey()] = url.data();
+ return headers.Pass();
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructHeadHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const {
+ return ConstructHeaderBlock("HEAD", url, &content_length);
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructPostHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const {
+ return ConstructHeaderBlock("POST", url, &content_length);
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructPutHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const {
+ return ConstructHeaderBlock("PUT", url, &content_length);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyFrame(
+ const SpdyHeaderInfo& header_info,
+ scoped_ptr<SpdyHeaderBlock> headers) const {
+ BufferedSpdyFramer framer(spdy_version_, header_info.compressed);
+ SpdyFrame* frame = NULL;
+ switch (header_info.kind) {
+ case DATA:
+ frame = framer.CreateDataFrame(header_info.id, header_info.data,
+ header_info.data_length,
+ header_info.data_flags);
+ break;
+ case SYN_STREAM:
+ {
+ size_t credential_slot = is_spdy2() ? 0 : header_info.credential_slot;
+ frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+ header_info.priority,
+ credential_slot,
+ header_info.control_flags,
+ header_info.compressed, headers.get());
+ }
+ break;
+ case SYN_REPLY:
+ frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+ header_info.compressed, headers.get());
+ break;
+ case RST_STREAM:
+ frame = framer.CreateRstStream(header_info.id, header_info.status);
+ break;
+ case HEADERS:
+ frame = framer.CreateHeaders(header_info.id, header_info.control_flags,
+ header_info.compressed, headers.get());
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+ return frame;
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyFrame(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail_headers[],
+ int tail_header_count) const {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ AppendToHeaderBlock(extra_headers, extra_header_count, headers.get());
+ if (tail_headers && tail_header_count)
+ AppendToHeaderBlock(tail_headers, tail_header_count, headers.get());
+ return ConstructSpdyFrame(header_info, headers.Pass());
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyControlFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority,
+ SpdyFrameType type,
+ SpdyControlFlags flags,
+ SpdyStreamId associated_stream_id) const {
+ EXPECT_GE(type, FIRST_CONTROL_TYPE);
+ EXPECT_LE(type, LAST_CONTROL_TYPE);
+ const SpdyHeaderInfo header_info = {
+ type,
+ stream_id,
+ associated_stream_id,
+ ConvertRequestPriorityToSpdyPriority(request_priority, spdy_version_),
+ 0, // credential slot
+ flags,
+ compressed,
+ RST_STREAM_INVALID, // status
+ NULL, // data
+ 0, // length
+ DATA_FLAG_NONE
+ };
+ return ConstructSpdyFrame(header_info, headers.Pass());
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyControlFrame(
+ const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority,
+ SpdyFrameType type,
+ SpdyControlFlags flags,
+ const char* const* tail_headers,
+ int tail_header_size,
+ SpdyStreamId associated_stream_id) const {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ AppendToHeaderBlock(extra_headers, extra_header_count, headers.get());
+ if (tail_headers && tail_header_size)
+ AppendToHeaderBlock(tail_headers, tail_header_size / 2, headers.get());
+ return ConstructSpdyControlFrame(
+ headers.Pass(), compressed, stream_id,
+ request_priority, type, flags, associated_stream_id);
+}
+
+std::string SpdyTestUtil::ConstructSpdyReplyString(
+ const SpdyHeaderBlock& headers) const {
+ std::string reply_string;
+ for (SpdyHeaderBlock::const_iterator it = headers.begin();
+ it != headers.end(); ++it) {
+ std::string key = it->first;
+ // Remove leading colon from "special" headers (for SPDY3 and
+ // above).
+ if (spdy_version() >= SPDY3 && key[0] == ':')
+ key = key.substr(1);
+ std::vector<std::string> values;
+ base::SplitString(it->second, '\0', &values);
+ for (std::vector<std::string>::const_iterator it2 = values.begin();
+ it2 != values.end(); ++it2) {
+ reply_string += key + ": " + *it2 + "\n";
+ }
+ }
+ return reply_string;
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdySettings(
+ const SettingsMap& settings) const {
+ return CreateFramer()->CreateSettings(settings);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyCredential(
+ const SpdyCredential& credential) const {
+ return CreateFramer()->CreateCredentialFrame(credential);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPing(uint32 ping_id) const {
+ return CreateFramer()->CreatePingFrame(ping_id);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGoAway() const {
+ return ConstructSpdyGoAway(0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGoAway(
+ SpdyStreamId last_good_stream_id) const {
+ return CreateFramer()->CreateGoAway(last_good_stream_id, GOAWAY_OK);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyWindowUpdate(
+ const SpdyStreamId stream_id, uint32 delta_window_size) const {
+ return CreateFramer()->CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyRstStream(
+ SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const {
+ return CreateFramer()->CreateRstStream(stream_id, status);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGet(
+ const char* const url,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) const {
+ const SpdyHeaderInfo header_info = {
+ SYN_STREAM,
+ stream_id,
+ 0, // associated stream ID
+ ConvertRequestPriorityToSpdyPriority(request_priority, spdy_version_),
+ 0, // credential slot
+ CONTROL_FLAG_FIN,
+ compressed,
+ RST_STREAM_INVALID, // status
+ NULL, // data
+ 0, // length
+ DATA_FLAG_NONE
+ };
+ return ConstructSpdyFrame(header_info, ConstructGetHeaderBlock(url));
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ bool direct) const {
+ const bool spdy2 = is_spdy2();
+ const char* url = (spdy2 && !direct) ? "http://www.google.com/" : "/";
+ const char* const kStandardGetHeaders[] = {
+ GetMethodKey(), "GET",
+ GetHostKey(), "www.google.com",
+ GetSchemeKey(), "http",
+ GetVersionKey(), "HTTP/1.1",
+ GetPathKey(), url
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ compressed,
+ stream_id,
+ request_priority,
+ SYN_STREAM,
+ CONTROL_FLAG_FIN,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyConnect(
+ const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id) const {
+ const char* const kConnectHeaders[] = {
+ GetMethodKey(), "CONNECT",
+ GetPathKey(), "www.google.com:443",
+ GetHostKey(), "www.google.com",
+ GetVersionKey(), "HTTP/1.1",
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ /*compressed*/ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ kConnectHeaders,
+ arraysize(kConnectHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ (*headers)["hello"] = "bye";
+ (*headers)[GetStatusKey()] = "200 OK";
+ (*headers)[GetVersionKey()] = "HTTP/1.1";
+ AddUrlToHeaderBlock(url, headers.get());
+ AppendToHeaderBlock(extra_headers, extra_header_count, headers.get());
+ return ConstructSpdyControlFrame(headers.Pass(),
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ associated_stream_id);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url,
+ const char* status,
+ const char* location) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ (*headers)["hello"] = "bye";
+ (*headers)[GetStatusKey()] = status;
+ (*headers)[GetVersionKey()] = "HTTP/1.1";
+ (*headers)["location"] = location;
+ AddUrlToHeaderBlock(url, headers.get());
+ AppendToHeaderBlock(extra_headers, extra_header_count, headers.get());
+ return ConstructSpdyControlFrame(headers.Pass(),
+ false,
+ stream_id,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ associated_stream_id);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPushHeaders(
+ int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const char* const kStandardGetHeaders[] = {
+ GetStatusKey(), "200 OK",
+ GetVersionKey(), "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdySynReplyError(
+ const char* const status,
+ const char* const* const extra_headers,
+ int extra_header_count,
+ int stream_id) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ GetStatusKey(), status,
+ GetVersionKey(), "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGetSynReplyRedirect(int stream_id) {
+ static const char* const kExtraHeaders[] = {
+ "location", "http://www.foo.com/index.php",
+ };
+ return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders,
+ arraysize(kExtraHeaders)/2, stream_id);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdySynReplyError(int stream_id) {
+ return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyGetSynReply(
+ const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ GetStatusKey(), "200",
+ GetVersionKey(), "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPost(const char* url,
+ SpdyStreamId stream_id,
+ int64 content_length,
+ RequestPriority priority,
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const SpdyHeaderInfo kSynStartHeader = {
+ SYN_STREAM,
+ stream_id,
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(priority, spdy_version_),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE,
+ false, // Compressed
+ RST_STREAM_INVALID,
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE
+ };
+ return ConstructSpdyFrame(
+ kSynStartHeader, ConstructPostHeaderBlock(url, content_length));
+}
+
+SpdyFrame* SpdyTestUtil::ConstructChunkedSpdyPost(
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const char* post_headers[] = {
+ GetMethodKey(), "POST",
+ GetPathKey(), "/",
+ GetHostKey(), "www.google.com",
+ GetSchemeKey(), "http",
+ GetVersionKey(), "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ post_headers,
+ arraysize(post_headers),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyPostSynReply(
+ const char* const extra_headers[],
+ int extra_header_count) {
+ const char* const kStandardGetHeaders[] = {
+ "hello", "bye",
+ GetStatusKey(), "200",
+ GetPathKey(), "/index.php",
+ GetVersionKey(), "HTTP/1.1"
+ };
+ return ConstructSpdyControlFrame(extra_headers,
+ extra_header_count,
+ false,
+ 1,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ kStandardGetHeaders,
+ arraysize(kStandardGetHeaders),
+ 0);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyBodyFrame(int stream_id, bool fin) {
+ SpdyFramer framer(spdy_version_);
+ return framer.CreateDataFrame(
+ stream_id, kUploadData, kUploadDataSize,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructSpdyBodyFrame(int stream_id,
+ const char* data,
+ uint32 len,
+ bool fin) {
+ SpdyFramer framer(spdy_version_);
+ return framer.CreateDataFrame(
+ stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+SpdyFrame* SpdyTestUtil::ConstructWrappedSpdyFrame(
+ const scoped_ptr<SpdyFrame>& frame,
+ int stream_id) {
+ return ConstructSpdyBodyFrame(stream_id, frame->data(),
+ frame->size(), false);
+}
+
+const SpdyHeaderInfo SpdyTestUtil::MakeSpdyHeader(SpdyFrameType type) {
+ const SpdyHeaderInfo kHeader = {
+ type,
+ 1, // Stream ID
+ 0, // Associated stream ID
+ ConvertRequestPriorityToSpdyPriority(LOWEST, spdy_version_),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_FIN, // Control Flags
+ false, // Compressed
+ RST_STREAM_INVALID,
+ NULL, // Data
+ 0, // Length
+ DATA_FLAG_NONE
+ };
+ return kHeader;
+}
+
+scoped_ptr<SpdyFramer> SpdyTestUtil::CreateFramer() const {
+ return scoped_ptr<SpdyFramer>(new SpdyFramer(spdy_version_));
+}
+
+const char* SpdyTestUtil::GetMethodKey() const {
+ return is_spdy2() ? "method" : ":method";
+}
+
+const char* SpdyTestUtil::GetStatusKey() const {
+ return is_spdy2() ? "status" : ":status";
+}
+
+const char* SpdyTestUtil::GetHostKey() const {
+ return is_spdy2() ? "host" : ":host";
+}
+
+const char* SpdyTestUtil::GetSchemeKey() const {
+ return is_spdy2() ? "scheme" : ":scheme";
+}
+
+const char* SpdyTestUtil::GetVersionKey() const {
+ return is_spdy2() ? "version" : ":version";
+}
+
+const char* SpdyTestUtil::GetPathKey() const {
+ return is_spdy2() ? "url" : ":path";
+}
+
+scoped_ptr<SpdyHeaderBlock> SpdyTestUtil::ConstructHeaderBlock(
+ base::StringPiece method,
+ base::StringPiece url,
+ int64* content_length) const {
+ std::string scheme, host, path;
+ ParseUrl(url.data(), &scheme, &host, &path);
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ (*headers)[GetMethodKey()] = method.as_string();
+ (*headers)[GetPathKey()] = path.c_str();
+ (*headers)[GetHostKey()] = host.c_str();
+ (*headers)[GetSchemeKey()] = scheme.c_str();
+ (*headers)[GetVersionKey()] = "HTTP/1.1";
+ if (content_length) {
+ std::string length_str = base::Int64ToString(*content_length);
+ (*headers)["content-length"] = length_str;
+ }
+ return headers.Pass();
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_test_util_common.h b/chromium/net/spdy/spdy_test_util_common.h
new file mode 100644
index 00000000000..aa833ff91f6
--- /dev/null
+++ b/chromium/net/spdy/spdy_test_util_common.h
@@ -0,0 +1,538 @@
+// Copyright (c) 2013 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_SPDY_SPDY_TEST_UTIL_COMMON_H_
+#define NET_SPDY_SPDY_TEST_UTIL_COMMON_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/completion_callback.h"
+#include "net/base/request_priority.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GURL;
+
+namespace net {
+
+class BoundNetLog;
+class SpdySession;
+class SpdySessionKey;
+class SpdySessionPool;
+class SpdyStream;
+class SpdyStreamRequest;
+
+// Default upload data used by both, mock objects and framer when creating
+// data frames.
+const char kDefaultURL[] = "http://www.google.com";
+const char kUploadData[] = "hello!";
+const int kUploadDataSize = arraysize(kUploadData)-1;
+
+// SpdyNextProtos returns a vector of next protocols for negotiating
+// SPDY.
+std::vector<NextProto> SpdyNextProtos();
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks);
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks);
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendToHeaderBlock(const char* const extra_headers[],
+ int extra_header_count,
+ SpdyHeaderBlock* headers);
+
+// Create an async MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req);
+
+// Create an async MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq);
+
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode);
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp);
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq);
+
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode);
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+ char* buff, int buff_len);
+
+// Returns the SpdyPriority embedded in the given frame. Returns true
+// and fills in |priority| on success.
+bool GetSpdyPriority(SpdyMajorVersion version,
+ const SpdyFrame& frame,
+ SpdyPriority* priority);
+
+// Tries to create a stream in |session| synchronously. Returns NULL
+// on failure.
+base::WeakPtr<SpdyStream> CreateStreamSynchronously(
+ SpdyStreamType type,
+ const base::WeakPtr<SpdySession>& session,
+ const GURL& url,
+ RequestPriority priority,
+ const BoundNetLog& net_log);
+
+// Helper class used by some tests to release a stream as soon as it's
+// created.
+class StreamReleaserCallback : public TestCompletionCallbackBase {
+ public:
+ StreamReleaserCallback();
+
+ virtual ~StreamReleaserCallback();
+
+ // Returns a callback that releases |request|'s stream.
+ CompletionCallback MakeCallback(SpdyStreamRequest* request);
+
+ private:
+ void OnComplete(SpdyStreamRequest* request, int result);
+};
+
+const size_t kSpdyCredentialSlotUnused = 0;
+
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+ SpdyFrameType kind;
+ SpdyStreamId id;
+ SpdyStreamId assoc_id;
+ SpdyPriority priority;
+ size_t credential_slot; // SPDY3 only
+ SpdyControlFlags control_flags;
+ bool compressed;
+ SpdyRstStreamStatus status;
+ const char* data;
+ uint32 data_length;
+ SpdyDataFlags data_flags;
+};
+
+// An ECSignatureCreator that returns deterministic signatures.
+class MockECSignatureCreator : public crypto::ECSignatureCreator {
+ public:
+ explicit MockECSignatureCreator(crypto::ECPrivateKey* key);
+
+ // crypto::ECSignatureCreator
+ virtual bool Sign(const uint8* data,
+ int data_len,
+ std::vector<uint8>* signature) OVERRIDE;
+ virtual bool DecodeSignature(const std::vector<uint8>& signature,
+ std::vector<uint8>* out_raw_sig) OVERRIDE;
+
+ private:
+ crypto::ECPrivateKey* key_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator);
+};
+
+// An ECSignatureCreatorFactory creates MockECSignatureCreator.
+class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory {
+ public:
+ MockECSignatureCreatorFactory();
+ virtual ~MockECSignatureCreatorFactory();
+
+ // crypto::ECSignatureCreatorFactory
+ virtual crypto::ECSignatureCreator* Create(
+ crypto::ECPrivateKey* key) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory);
+};
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+struct SpdySessionDependencies {
+ // Default set of dependencies -- "null" proxy service.
+ explicit SpdySessionDependencies(NextProto protocol);
+
+ // Custom proxy service dependency.
+ SpdySessionDependencies(NextProto protocol, ProxyService* proxy_service);
+
+ ~SpdySessionDependencies();
+
+ static HttpNetworkSession* SpdyCreateSession(
+ SpdySessionDependencies* session_deps);
+ static HttpNetworkSession* SpdyCreateSessionDeterministic(
+ SpdySessionDependencies* session_deps);
+ static HttpNetworkSession::Params CreateSessionParams(
+ SpdySessionDependencies* session_deps);
+
+ // NOTE: host_resolver must be ordered before http_auth_handler_factory.
+ scoped_ptr<MockHostResolverBase> host_resolver;
+ scoped_ptr<CertVerifier> cert_verifier;
+ scoped_ptr<TransportSecurityState> transport_security_state;
+ scoped_ptr<ProxyService> proxy_service;
+ scoped_refptr<SSLConfigService> ssl_config_service;
+ scoped_ptr<MockClientSocketFactory> socket_factory;
+ scoped_ptr<DeterministicMockClientSocketFactory> deterministic_socket_factory;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory;
+ HttpServerPropertiesImpl http_server_properties;
+ bool enable_ip_pooling;
+ bool enable_compression;
+ bool enable_ping;
+ bool enable_user_alternate_protocol_ports;
+ NextProto protocol;
+ size_t stream_initial_recv_window_size;
+ SpdySession::TimeFunc time_func;
+ std::string trusted_spdy_proxy;
+ NetLog* net_log;
+};
+
+class SpdyURLRequestContext : public URLRequestContext {
+ public:
+ explicit SpdyURLRequestContext(NextProto protocol);
+ virtual ~SpdyURLRequestContext();
+
+ MockClientSocketFactory& socket_factory() { return socket_factory_; }
+
+ private:
+ MockClientSocketFactory socket_factory_;
+ net::URLRequestContextStorage storage_;
+};
+
+// Equivalent to pool->GetIfExists(spdy_session_key, BoundNetLog()) != NULL.
+bool HasSpdySession(SpdySessionPool* pool, const SpdySessionKey& key);
+
+// Creates a SPDY session for the given key and puts it in the SPDY
+// session pool in |http_session|. A SPDY session for |key| must not
+// already exist.
+base::WeakPtr<SpdySession> CreateInsecureSpdySession(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log);
+
+// Tries to create a SPDY session for the given key but expects the
+// attempt to fail with the given error. A SPDY session for |key| must
+// not already exist.
+void TryCreateInsecureSpdySessionExpectingFailure(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ Error expected_error,
+ const BoundNetLog& net_log);
+
+// Like CreateInsecureSpdySession(), but uses TLS.
+base::WeakPtr<SpdySession> CreateSecureSpdySession(
+ const scoped_refptr<HttpNetworkSession>& http_session,
+ const SpdySessionKey& key,
+ const BoundNetLog& net_log);
+
+// Creates an insecure SPDY session for the given key and puts it in
+// |pool|. The returned session will neither receive nor send any
+// data. A SPDY session for |key| must not already exist.
+base::WeakPtr<SpdySession> CreateFakeSpdySession(SpdySessionPool* pool,
+ const SpdySessionKey& key);
+
+// Tries to create an insecure SPDY session for the given key but
+// expects the attempt to fail with the given error. The session will
+// neither receive nor send any data. A SPDY session for |key| must
+// not already exist.
+void TryCreateFakeSpdySessionExpectingFailure(SpdySessionPool* pool,
+ const SpdySessionKey& key,
+ Error expected_error);
+
+class SpdySessionPoolPeer {
+ public:
+ explicit SpdySessionPoolPeer(SpdySessionPool* pool);
+
+ void RemoveAliases(const SpdySessionKey& key);
+ void DisableDomainAuthenticationVerification();
+ void SetEnableSendingInitialData(bool enabled);
+
+ private:
+ SpdySessionPool* const pool_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+class SpdyTestUtil {
+ public:
+ explicit SpdyTestUtil(NextProto protocol);
+
+ // Add the appropriate headers to put |url| into |block|.
+ void AddUrlToHeaderBlock(base::StringPiece url,
+ SpdyHeaderBlock* headers) const;
+
+ scoped_ptr<SpdyHeaderBlock> ConstructGetHeaderBlock(
+ base::StringPiece url) const;
+ scoped_ptr<SpdyHeaderBlock> ConstructGetHeaderBlockForProxy(
+ base::StringPiece url) const;
+ scoped_ptr<SpdyHeaderBlock> ConstructHeadHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const;
+ scoped_ptr<SpdyHeaderBlock> ConstructPostHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const;
+ scoped_ptr<SpdyHeaderBlock> ConstructPutHeaderBlock(
+ base::StringPiece url,
+ int64 content_length) const;
+
+ // Construct a SPDY frame. If it is a SYN_STREAM or SYN_REPLY frame (as
+ // specified in header_info.kind), the provided headers are included in the
+ // frame.
+ SpdyFrame* ConstructSpdyFrame(
+ const SpdyHeaderInfo& header_info,
+ scoped_ptr<SpdyHeaderBlock> headers) const;
+
+ // Construct a SPDY frame. If it is a SYN_STREAM or SYN_REPLY frame (as
+ // specified in header_info.kind), the headers provided in extra_headers and
+ // (if non-NULL) tail_headers are concatenated and included in the frame.
+ // (extra_headers must always be non-NULL.)
+ SpdyFrame* ConstructSpdyFrame(const SpdyHeaderInfo& header_info,
+ const char* const extra_headers[],
+ int extra_header_count,
+ const char* const tail_headers[],
+ int tail_header_count) const;
+
+ // Construct a generic SpdyControlFrame.
+ SpdyFrame* ConstructSpdyControlFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority,
+ SpdyFrameType type,
+ SpdyControlFlags flags,
+ SpdyStreamId associated_stream_id) const;
+
+ // Construct a generic SpdyControlFrame.
+ //
+ // Warning: extra_header_count is the number of header-value pairs in
+ // extra_headers (so half the number of elements), but tail_headers_size is
+ // the actual number of elements (both keys and values) in tail_headers.
+ // TODO(ttuttle): Fix this inconsistency.
+ SpdyFrame* ConstructSpdyControlFrame(
+ const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority,
+ SpdyFrameType type,
+ SpdyControlFlags flags,
+ const char* const* tail_headers,
+ int tail_headers_size,
+ SpdyStreamId associated_stream_id) const;
+
+ // Construct an expected SPDY reply string from the given headers.
+ std::string ConstructSpdyReplyString(const SpdyHeaderBlock& headers) const;
+
+ // Construct an expected SPDY SETTINGS frame.
+ // |settings| are the settings to set.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) const;
+
+ // Construct an expected SPDY CREDENTIAL frame.
+ // |credential| is the credential to send.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyCredential(const SpdyCredential& credential) const;
+
+ // Construct a SPDY PING frame.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyPing(uint32 ping_id) const;
+
+ // Construct a SPDY GOAWAY frame with last_good_stream_id = 0.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyGoAway() const;
+
+ // Construct a SPDY GOAWAY frame with the specified last_good_stream_id.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyGoAway(SpdyStreamId last_good_stream_id) const;
+
+ // Construct a SPDY WINDOW_UPDATE frame.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyWindowUpdate(
+ SpdyStreamId stream_id,
+ uint32 delta_window_size) const;
+
+ // Construct a SPDY RST_STREAM frame.
+ // Returns the constructed frame. The caller takes ownership of the frame.
+ SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) const;
+
+ // Constructs a standard SPDY GET SYN frame, optionally compressed
+ // for the url |url|.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyGet(const char* const url,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) const;
+
+ SpdyFrame* ConstructSpdyGetForProxy(const char* const url,
+ bool compressed,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) const;
+
+ // Constructs a standard SPDY GET SYN frame, optionally compressed.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls. If |direct| is false, the
+ // the full url will be used instead of simply the path.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+ int extra_header_count,
+ bool compressed,
+ int stream_id,
+ RequestPriority request_priority,
+ bool direct) const;
+
+ // Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+ SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id) const;
+
+ // Constructs a standard SPDY push SYN frame.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url);
+ SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id,
+ int associated_stream_id,
+ const char* url,
+ const char* status,
+ const char* location);
+
+ SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+ // Constructs a standard SPDY SYN_REPLY frame to match the SPDY GET.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+ int extra_header_count,
+ int stream_id);
+
+ // Constructs a standard SPDY SYN_REPLY frame to match the SPDY GET.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id);
+
+ // Constructs a standard SPDY SYN_REPLY frame with an Internal Server
+ // Error status code.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdySynReplyError(int stream_id);
+
+ // Constructs a standard SPDY SYN_REPLY frame with the specified status code.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdySynReplyError(const char* const status,
+ const char* const* const extra_headers,
+ int extra_header_count,
+ int stream_id);
+
+ // Constructs a standard SPDY POST SYN frame.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyPost(const char* url,
+ SpdyStreamId stream_id,
+ int64 content_length,
+ RequestPriority priority,
+ const char* const extra_headers[],
+ int extra_header_count);
+
+ // Constructs a chunked transfer SPDY POST SYN frame.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+ int extra_header_count);
+
+ // Constructs a standard SPDY SYN_REPLY frame to match the SPDY POST.
+ // |extra_headers| are the extra header-value pairs, which typically
+ // will vary the most between calls.
+ // Returns a SpdyFrame.
+ SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+ int extra_header_count);
+
+ // Constructs a single SPDY data frame with the contents "hello!"
+ SpdyFrame* ConstructSpdyBodyFrame(int stream_id,
+ bool fin);
+
+ // Constructs a single SPDY data frame with the given content.
+ SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+ uint32 len, bool fin);
+
+ // Wraps |frame| in the payload of a data frame in stream |stream_id|.
+ SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+ int stream_id);
+
+ const SpdyHeaderInfo MakeSpdyHeader(SpdyFrameType type);
+
+ NextProto protocol() const { return protocol_; }
+ SpdyMajorVersion spdy_version() const { return spdy_version_; }
+ bool is_spdy2() const { return protocol_ < kProtoSPDY3; }
+ scoped_ptr<SpdyFramer> CreateFramer() const;
+
+ const char* GetMethodKey() const;
+ const char* GetStatusKey() const;
+ const char* GetHostKey() const;
+ const char* GetSchemeKey() const;
+ const char* GetVersionKey() const;
+ const char* GetPathKey() const;
+
+ private:
+ // |content_length| may be NULL, in which case the content-length
+ // header will be omitted.
+ scoped_ptr<SpdyHeaderBlock> ConstructHeaderBlock(
+ base::StringPiece method,
+ base::StringPiece url,
+ int64* content_length) const;
+
+ const NextProto protocol_;
+ const SpdyMajorVersion spdy_version_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_TEST_UTIL_COMMON_H_
diff --git a/chromium/net/spdy/spdy_test_utils.cc b/chromium/net/spdy/spdy_test_utils.cc
new file mode 100644
index 00000000000..e7eb8aca91b
--- /dev/null
+++ b/chromium/net/spdy/spdy_test_utils.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_test_utils.h"
+
+#include <cstring>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sys_byteorder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+std::string HexDumpWithMarks(const unsigned char* data, int length,
+ const bool* marks, int mark_length) {
+ static const char kHexChars[] = "0123456789abcdef";
+ static const int kColumns = 4;
+
+ const int kSizeLimit = 1024;
+ if (length > kSizeLimit || mark_length > kSizeLimit) {
+ LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+ length = std::min(length, kSizeLimit);
+ mark_length = std::min(mark_length, kSizeLimit);
+ }
+
+ std::string hex;
+ for (const unsigned char* row = data; length > 0;
+ row += kColumns, length -= kColumns) {
+ for (const unsigned char *p = row; p < row + 4; ++p) {
+ if (p < row + length) {
+ const bool mark =
+ (marks && (p - data) < mark_length && marks[p - data]);
+ hex += mark ? '*' : ' ';
+ hex += kHexChars[(*p & 0xf0) >> 4];
+ hex += kHexChars[*p & 0x0f];
+ hex += mark ? '*' : ' ';
+ } else {
+ hex += " ";
+ }
+ }
+ hex = hex + " ";
+
+ for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p)
+ hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
+
+ hex = hex + '\n';
+ }
+ return hex;
+}
+
+void CompareCharArraysWithHexError(
+ const std::string& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len) {
+ const int min_len = std::min(actual_len, expected_len);
+ const int max_len = std::max(actual_len, expected_len);
+ scoped_ptr<bool[]> marks(new bool[max_len]);
+ bool identical = (actual_len == expected_len);
+ for (int i = 0; i < min_len; ++i) {
+ if (actual[i] != expected[i]) {
+ marks[i] = true;
+ identical = false;
+ } else {
+ marks[i] = false;
+ }
+ }
+ for (int i = min_len; i < max_len; ++i) {
+ marks[i] = true;
+ }
+ if (identical) return;
+ ADD_FAILURE()
+ << "Description:\n"
+ << description
+ << "\n\nExpected:\n"
+ << HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
+ << "\nActual:\n"
+ << HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
+}
+
+void SetFrameFlags(SpdyFrame* frame, uint8 flags, int spdy_version) {
+ switch (spdy_version) {
+ case 2:
+ case 3:
+ frame->data()[4] = flags;
+ break;
+ case 4:
+ frame->data()[3] = flags;
+ break;
+ default:
+ LOG(FATAL) << "Unsupported SPDY version.";
+ }
+}
+
+void SetFrameLength(SpdyFrame* frame, size_t length, int spdy_version) {
+ switch (spdy_version) {
+ case 2:
+ case 3:
+ CHECK_EQ(0u, length & ~kLengthMask);
+ {
+ int32 wire_length = base::HostToNet32(length);
+ // The length field in SPDY 2 and 3 is a 24-bit (3B) integer starting at
+ // offset 5.
+ memcpy(frame->data() + 5, reinterpret_cast<char*>(&wire_length) + 1, 3);
+ }
+ break;
+ case 4:
+ CHECK_GT(1u<<16, length);
+ {
+ int32 wire_length = base::HostToNet16(static_cast<uint16>(length));
+ memcpy(frame->data(),
+ reinterpret_cast<char*>(&wire_length),
+ sizeof(uint16));
+ }
+ break;
+ default:
+ LOG(FATAL) << "Unsupported SPDY version.";
+ }
+}
+
+
+} // namespace test
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_test_utils.h b/chromium/net/spdy/spdy_test_utils.h
new file mode 100644
index 00000000000..b6e03936fae
--- /dev/null
+++ b/chromium/net/spdy/spdy_test_utils.h
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_TEST_UTILS_H_
+#define NET_SPDY_TEST_UTILS_H_
+
+#include <string>
+
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace test {
+
+std::string HexDumpWithMarks(const unsigned char* data, int length,
+ const bool* marks, int mark_length);
+
+void CompareCharArraysWithHexError(
+ const std::string& description,
+ const unsigned char* actual,
+ const int actual_len,
+ const unsigned char* expected,
+ const int expected_len);
+
+void SetFrameFlags(SpdyFrame* frame, uint8 flags, int spdy_version);
+
+void SetFrameLength(SpdyFrame* frame, size_t length, int spdy_version);
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_SPDY_TEST_UTILS_H_
diff --git a/chromium/net/spdy/spdy_websocket_stream.cc b/chromium/net/spdy/spdy_websocket_stream.cc
new file mode 100644
index 00000000000..c0eac28b6fb
--- /dev/null
+++ b/chromium/net/spdy/spdy_websocket_stream.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 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.
+
+#include "net/spdy/spdy_websocket_stream.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+#include "url/gurl.h"
+
+namespace net {
+
+SpdyWebSocketStream::SpdyWebSocketStream(
+ const base::WeakPtr<SpdySession>& spdy_session, Delegate* delegate)
+ : weak_ptr_factory_(this),
+ spdy_session_(spdy_session),
+ pending_send_data_length_(0),
+ delegate_(delegate) {
+ DCHECK(spdy_session_.get());
+ DCHECK(delegate_);
+}
+
+SpdyWebSocketStream::~SpdyWebSocketStream() {
+ delegate_ = NULL;
+ Close();
+}
+
+int SpdyWebSocketStream::InitializeStream(const GURL& url,
+ RequestPriority request_priority,
+ const BoundNetLog& net_log) {
+ if (!spdy_session_)
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ int rv = stream_request_.StartRequest(
+ SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url, request_priority, net_log,
+ base::Bind(&SpdyWebSocketStream::OnSpdyStreamCreated,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ if (rv == OK) {
+ stream_ = stream_request_.ReleaseStream();
+ DCHECK(stream_.get());
+ stream_->SetDelegate(this);
+ }
+ return rv;
+}
+
+int SpdyWebSocketStream::SendRequest(scoped_ptr<SpdyHeaderBlock> headers) {
+ if (!stream_.get()) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ int result = stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND);
+ if (result < OK && result != ERR_IO_PENDING)
+ Close();
+ return result;
+}
+
+int SpdyWebSocketStream::SendData(const char* data, int length) {
+ if (!stream_.get()) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+ DCHECK_GE(length, 0);
+ pending_send_data_length_ = static_cast<size_t>(length);
+ scoped_refptr<IOBuffer> buf(new IOBuffer(length));
+ memcpy(buf->data(), data, length);
+ stream_->SendData(buf.get(), length, MORE_DATA_TO_SEND);
+ return ERR_IO_PENDING;
+}
+
+void SpdyWebSocketStream::Close() {
+ if (stream_.get()) {
+ stream_->Close();
+ DCHECK(!stream_.get());
+ }
+}
+
+void SpdyWebSocketStream::OnRequestHeadersSent() {
+ DCHECK(delegate_);
+ delegate_->OnSentSpdyHeaders();
+}
+
+SpdyResponseHeadersStatus SpdyWebSocketStream::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ DCHECK(delegate_);
+ delegate_->OnSpdyResponseHeadersUpdated(response_headers);
+ return RESPONSE_HEADERS_ARE_COMPLETE;
+}
+
+void SpdyWebSocketStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ DCHECK(delegate_);
+ delegate_->OnReceivedSpdyData(buffer.Pass());
+}
+
+void SpdyWebSocketStream::OnDataSent() {
+ DCHECK(delegate_);
+ delegate_->OnSentSpdyData(pending_send_data_length_);
+ pending_send_data_length_ = 0;
+}
+
+void SpdyWebSocketStream::OnClose(int status) {
+ stream_.reset();
+
+ // Destruction without Close() call OnClose() with delegate_ being NULL.
+ if (!delegate_)
+ return;
+ Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ delegate->OnCloseSpdyStream();
+}
+
+void SpdyWebSocketStream::OnSpdyStreamCreated(int result) {
+ DCHECK_NE(ERR_IO_PENDING, result);
+ if (result == OK) {
+ stream_ = stream_request_.ReleaseStream();
+ DCHECK(stream_.get());
+ stream_->SetDelegate(this);
+ }
+ DCHECK(delegate_);
+ delegate_->OnCreatedSpdyStream(result);
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_websocket_stream.h b/chromium/net/spdy/spdy_websocket_stream.h
new file mode 100644
index 00000000000..169d1e481a8
--- /dev/null
+++ b/chromium/net/spdy/spdy_websocket_stream.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#ifndef NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
+#define NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+// The SpdyWebSocketStream is a WebSocket-specific type of stream known to a
+// SpdySession. WebSocket's opening handshake is converted to SPDY's
+// SYN_STREAM/SYN_REPLY. WebSocket frames are encapsulated as SPDY data frames.
+class NET_EXPORT_PRIVATE SpdyWebSocketStream
+ : public SpdyStream::Delegate {
+ public:
+ // Delegate handles asynchronous events.
+ class NET_EXPORT_PRIVATE Delegate {
+ public:
+ // Called when InitializeStream() finishes asynchronously. This delegate is
+ // called if InitializeStream() returns ERR_IO_PENDING. |status| indicates
+ // network error.
+ virtual void OnCreatedSpdyStream(int status) = 0;
+
+ // Called on corresponding to OnSendHeadersComplete() or SPDY's SYN frame
+ // has been sent.
+ virtual void OnSentSpdyHeaders() = 0;
+
+ // Called on corresponding to OnResponseHeadersUpdated() or
+ // SPDY's SYN_STREAM, SYN_REPLY, or HEADERS frames are
+ // received. This callback may be called multiple times as SPDY's
+ // delegate does.
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) = 0;
+
+ // Called when data is sent.
+ virtual void OnSentSpdyData(size_t bytes_sent) = 0;
+
+ // Called when data is received.
+ virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) = 0;
+
+ // Called when SpdyStream is closed.
+ virtual void OnCloseSpdyStream() = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ SpdyWebSocketStream(const base::WeakPtr<SpdySession>& spdy_session,
+ Delegate* delegate);
+ virtual ~SpdyWebSocketStream();
+
+ // Initializes SPDY stream for the WebSocket.
+ // It might create SPDY stream asynchronously. In this case, this method
+ // returns ERR_IO_PENDING and call OnCreatedSpdyStream delegate with result
+ // after completion. In other cases, delegate does not be called.
+ int InitializeStream(const GURL& url,
+ RequestPriority request_priority,
+ const BoundNetLog& stream_net_log);
+
+ int SendRequest(scoped_ptr<SpdyHeaderBlock> headers);
+ int SendData(const char* data, int length);
+ void Close();
+
+ // SpdyStream::Delegate
+ virtual void OnRequestHeadersSent() OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnDataSent() OVERRIDE;
+ virtual void OnClose(int status) OVERRIDE;
+
+ private:
+ friend class SpdyWebSocketStreamTest;
+ FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamTest, Basic);
+
+ void OnSpdyStreamCreated(int status);
+
+ base::WeakPtrFactory<SpdyWebSocketStream> weak_ptr_factory_;
+ SpdyStreamRequest stream_request_;
+ base::WeakPtr<SpdyStream> stream_;
+ const base::WeakPtr<SpdySession> spdy_session_;
+ size_t pending_send_data_length_;
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStream);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
diff --git a/chromium/net/spdy/spdy_websocket_stream_unittest.cc b/chromium/net/spdy/spdy_websocket_stream_unittest.cc
new file mode 100644
index 00000000000..e0661a05407
--- /dev/null
+++ b/chromium/net/spdy/spdy_websocket_stream_unittest.cc
@@ -0,0 +1,606 @@
+// Copyright 2013 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/spdy/spdy_websocket_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_websocket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+struct SpdyWebSocketStreamEvent {
+ enum EventType {
+ EVENT_CREATED,
+ EVENT_SENT_HEADERS,
+ EVENT_RECEIVED_HEADER,
+ EVENT_SENT_DATA,
+ EVENT_RECEIVED_DATA,
+ EVENT_CLOSE,
+ };
+ SpdyWebSocketStreamEvent(EventType type,
+ const SpdyHeaderBlock& headers,
+ int result,
+ const std::string& data)
+ : event_type(type),
+ headers(headers),
+ result(result),
+ data(data) {}
+
+ EventType event_type;
+ SpdyHeaderBlock headers;
+ int result;
+ std::string data;
+};
+
+class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate {
+ public:
+ explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback)
+ : callback_(callback) {}
+ virtual ~SpdyWebSocketStreamEventRecorder() {}
+
+ typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback;
+
+ void SetOnCreated(const StreamEventCallback& callback) {
+ on_created_ = callback;
+ }
+ void SetOnSentHeaders(const StreamEventCallback& callback) {
+ on_sent_headers_ = callback;
+ }
+ void SetOnReceivedHeader(const StreamEventCallback& callback) {
+ on_received_header_ = callback;
+ }
+ void SetOnSentData(const StreamEventCallback& callback) {
+ on_sent_data_ = callback;
+ }
+ void SetOnReceivedData(const StreamEventCallback& callback) {
+ on_received_data_ = callback;
+ }
+ void SetOnClose(const StreamEventCallback& callback) {
+ on_close_ = callback;
+ }
+
+ virtual void OnCreatedSpdyStream(int result) OVERRIDE {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
+ SpdyHeaderBlock(),
+ result,
+ std::string()));
+ if (!on_created_.is_null())
+ on_created_.Run(&events_.back());
+ }
+ virtual void OnSentSpdyHeaders() OVERRIDE {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ SpdyHeaderBlock(),
+ OK,
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ response_headers,
+ OK,
+ std::string()));
+ if (!on_received_header_.is_null())
+ on_received_header_.Run(&events_.back());
+ }
+ virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ SpdyHeaderBlock(),
+ static_cast<int>(bytes_sent),
+ std::string()));
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run(&events_.back());
+ }
+ virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {
+ std::string buffer_data;
+ size_t buffer_len = 0;
+ if (buffer) {
+ buffer_len = buffer->GetRemainingSize();
+ buffer_data.append(buffer->GetRemainingData(), buffer_len);
+ }
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ SpdyHeaderBlock(),
+ buffer_len,
+ buffer_data));
+ if (!on_received_data_.is_null())
+ on_received_data_.Run(&events_.back());
+ }
+ virtual void OnCloseSpdyStream() OVERRIDE {
+ events_.push_back(
+ SpdyWebSocketStreamEvent(
+ SpdyWebSocketStreamEvent::EVENT_CLOSE,
+ SpdyHeaderBlock(),
+ OK,
+ std::string()));
+ if (!on_close_.is_null())
+ on_close_.Run(&events_.back());
+ if (!callback_.is_null())
+ callback_.Run(OK);
+ }
+
+ const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const {
+ return events_;
+ }
+
+ private:
+ std::vector<SpdyWebSocketStreamEvent> events_;
+ StreamEventCallback on_created_;
+ StreamEventCallback on_sent_headers_;
+ StreamEventCallback on_received_header_;
+ StreamEventCallback on_sent_data_;
+ StreamEventCallback on_received_data_;
+ StreamEventCallback on_close_;
+ CompletionCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder);
+};
+
+} // namespace
+
+class SpdyWebSocketStreamTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ public:
+ OrderedSocketData* data() { return data_.get(); }
+
+ void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) {
+ // Record the actual stream_id.
+ created_stream_id_ = websocket_stream_->stream_->stream_id();
+ websocket_stream_->SendData(kMessageFrame, kMessageFrameLength);
+ }
+
+ void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) {
+ websocket_stream_->SendData(kClosingFrame, kClosingFrameLength);
+ }
+
+ void DoClose(SpdyWebSocketStreamEvent* event) {
+ websocket_stream_->Close();
+ }
+
+ void DoSync(SpdyWebSocketStreamEvent* event) {
+ sync_callback_.callback().Run(OK);
+ }
+
+ protected:
+ SpdyWebSocketStreamTest()
+ : spdy_util_(GetParam()),
+ spdy_settings_id_to_set_(SETTINGS_MAX_CONCURRENT_STREAMS),
+ spdy_settings_flags_to_set_(SETTINGS_FLAG_PLEASE_PERSIST),
+ spdy_settings_value_to_set_(1),
+ session_deps_(GetParam()),
+ stream_id_(0),
+ created_stream_id_(0) {}
+ virtual ~SpdyWebSocketStreamTest() {}
+
+ virtual void SetUp() {
+ host_port_pair_.set_host("example.com");
+ host_port_pair_.set_port(80);
+ spdy_session_key_ = SpdySessionKey(host_port_pair_,
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+
+ spdy_settings_to_send_[spdy_settings_id_to_set_] =
+ SettingsFlagsAndValue(
+ SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_);
+ }
+
+ virtual void TearDown() {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void Prepare(SpdyStreamId stream_id) {
+ stream_id_ = stream_id;
+
+ request_frame_.reset(spdy_util_.ConstructSpdyWebSocketSynStream(
+ stream_id_,
+ "/echo",
+ "example.com",
+ "http://example.com/wsdemo"));
+
+ response_frame_.reset(
+ spdy_util_.ConstructSpdyWebSocketSynReply(stream_id_));
+
+ message_frame_.reset(spdy_util_.ConstructSpdyWebSocketDataFrame(
+ kMessageFrame,
+ kMessageFrameLength,
+ stream_id_,
+ false));
+
+ closing_frame_.reset(spdy_util_.ConstructSpdyWebSocketDataFrame(
+ kClosingFrame,
+ kClosingFrameLength,
+ stream_id_,
+ false));
+ }
+
+ void InitSession(MockRead* reads, size_t reads_count,
+ MockWrite* writes, size_t writes_count) {
+ data_.reset(new OrderedSocketData(reads, reads_count,
+ writes, writes_count));
+ session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+ http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+ session_ = CreateInsecureSpdySession(
+ http_session_, spdy_session_key_, BoundNetLog());
+ }
+
+ void SendRequest() {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ spdy_util_.SetHeader("path", "/echo", headers.get());
+ spdy_util_.SetHeader("host", "example.com", headers.get());
+ spdy_util_.SetHeader("version", "WebSocket/13", headers.get());
+ spdy_util_.SetHeader("scheme", "ws", headers.get());
+ spdy_util_.SetHeader("origin", "http://example.com/wsdemo", headers.get());
+ websocket_stream_->SendRequest(headers.Pass());
+ }
+
+ SpdyWebSocketTestUtil spdy_util_;
+ SpdySettingsIds spdy_settings_id_to_set_;
+ SpdySettingsFlags spdy_settings_flags_to_set_;
+ uint32 spdy_settings_value_to_set_;
+ SettingsMap spdy_settings_to_send_;
+ SpdySessionDependencies session_deps_;
+ scoped_ptr<OrderedSocketData> data_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ base::WeakPtr<SpdySession> session_;
+ scoped_ptr<SpdyWebSocketStream> websocket_stream_;
+ SpdyStreamId stream_id_;
+ SpdyStreamId created_stream_id_;
+ scoped_ptr<SpdyFrame> request_frame_;
+ scoped_ptr<SpdyFrame> response_frame_;
+ scoped_ptr<SpdyFrame> message_frame_;
+ scoped_ptr<SpdyFrame> closing_frame_;
+ HostPortPair host_port_pair_;
+ SpdySessionKey spdy_session_key_;
+ TestCompletionCallback completion_callback_;
+ TestCompletionCallback sync_callback_;
+
+ static const char kMessageFrame[];
+ static const char kClosingFrame[];
+ static const size_t kMessageFrameLength;
+ static const size_t kClosingFrameLength;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ SpdyWebSocketStreamTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and
+// data frames.
+const char SpdyWebSocketStreamTest::kMessageFrame[] = "\x81\x05hello";
+const char SpdyWebSocketStreamTest::kClosingFrame[] = "\x88\0";
+const size_t SpdyWebSocketStreamTest::kMessageFrameLength =
+ arraysize(SpdyWebSocketStreamTest::kMessageFrame) - 1;
+const size_t SpdyWebSocketStreamTest::kClosingFrameLength =
+ arraysize(SpdyWebSocketStreamTest::kClosingFrame) - 1;
+
+TEST_P(SpdyWebSocketStreamTest, Basic) {
+ Prepare(1);
+ MockWrite writes[] = {
+ CreateMockWrite(*request_frame_.get(), 1),
+ CreateMockWrite(*message_frame_.get(), 3),
+ CreateMockWrite(*closing_frame_.get(), 5)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*response_frame_.get(), 2),
+ CreateMockRead(*message_frame_.get(), 4),
+ // Skip sequence 6 to notify closing has been sent.
+ CreateMockRead(*closing_frame_.get(), 7),
+ MockRead(SYNCHRONOUS, 0, 8) // EOF cause OnCloseSpdyStream event.
+ };
+
+ InitSession(reads, arraysize(reads), writes, arraysize(writes));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame,
+ base::Unretained(this)));
+
+ websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+ BoundNetLog net_log;
+ GURL url("ws://example.com/echo");
+ ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+ ASSERT_TRUE(websocket_stream_->stream_.get());
+
+ SendRequest();
+
+ completion_callback_.WaitForResult();
+
+ EXPECT_EQ(stream_id_, created_stream_id_);
+
+ websocket_stream_.reset();
+
+ const std::vector<SpdyWebSocketStreamEvent>& events =
+ delegate.GetSeenEvents();
+ ASSERT_EQ(7U, events.size());
+
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ events[0].event_type);
+ EXPECT_EQ(OK, events[0].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ events[1].event_type);
+ EXPECT_EQ(OK, events[1].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[2].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[3].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[4].event_type);
+ EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[5].event_type);
+ EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+ events[6].event_type);
+ EXPECT_EQ(OK, events[6].result);
+
+ // EOF close SPDY session.
+ EXPECT_FALSE(
+ HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_P(SpdyWebSocketStreamTest, DestructionBeforeClose) {
+ Prepare(1);
+ MockWrite writes[] = {
+ CreateMockWrite(*request_frame_.get(), 1),
+ CreateMockWrite(*message_frame_.get(), 3)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*response_frame_.get(), 2),
+ CreateMockRead(*message_frame_.get(), 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 5)
+ };
+
+ InitSession(reads, arraysize(reads), writes, arraysize(writes));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamTest::DoSync,
+ base::Unretained(this)));
+
+ websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+ BoundNetLog net_log;
+ GURL url("ws://example.com/echo");
+ ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+ SendRequest();
+
+ sync_callback_.WaitForResult();
+
+ // WebSocketStream destruction remove its SPDY stream from the session.
+ EXPECT_TRUE(session_->IsStreamActive(stream_id_));
+ websocket_stream_.reset();
+ EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+
+ const std::vector<SpdyWebSocketStreamEvent>& events =
+ delegate.GetSeenEvents();
+ ASSERT_GE(4U, events.size());
+
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ events[0].event_type);
+ EXPECT_EQ(OK, events[0].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ events[1].event_type);
+ EXPECT_EQ(OK, events[1].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[2].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[3].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+
+ EXPECT_TRUE(
+ HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_));
+ EXPECT_TRUE(data()->at_read_eof());
+ EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_P(SpdyWebSocketStreamTest, DestructionAfterExplicitClose) {
+ Prepare(1);
+ MockWrite writes[] = {
+ CreateMockWrite(*request_frame_.get(), 1),
+ CreateMockWrite(*message_frame_.get(), 3),
+ CreateMockWrite(*closing_frame_.get(), 5)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*response_frame_.get(), 2),
+ CreateMockRead(*message_frame_.get(), 4),
+ MockRead(ASYNC, ERR_IO_PENDING, 6)
+ };
+
+ InitSession(reads, arraysize(reads), writes, arraysize(writes));
+
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamTest::DoClose,
+ base::Unretained(this)));
+
+ websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+ BoundNetLog net_log;
+ GURL url("ws://example.com/echo");
+ ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+ SendRequest();
+
+ completion_callback_.WaitForResult();
+
+ // SPDY stream has already been removed from the session by Close().
+ EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+ websocket_stream_.reset();
+
+ const std::vector<SpdyWebSocketStreamEvent>& events =
+ delegate.GetSeenEvents();
+ ASSERT_EQ(5U, events.size());
+
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ events[0].event_type);
+ EXPECT_EQ(OK, events[0].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ events[1].event_type);
+ EXPECT_EQ(OK, events[1].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[2].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[3].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type);
+
+ EXPECT_TRUE(
+ HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_));
+}
+
+TEST_P(SpdyWebSocketStreamTest, IOPending) {
+ Prepare(1);
+ scoped_ptr<SpdyFrame> settings_frame(
+ spdy_util_.ConstructSpdySettings(spdy_settings_to_send_));
+ MockWrite writes[] = {
+ CreateMockWrite(*request_frame_.get(), 1),
+ CreateMockWrite(*message_frame_.get(), 3),
+ CreateMockWrite(*closing_frame_.get(), 5)
+ };
+
+ MockRead reads[] = {
+ CreateMockRead(*settings_frame.get(), 0),
+ CreateMockRead(*response_frame_.get(), 2),
+ CreateMockRead(*message_frame_.get(), 4),
+ CreateMockRead(*closing_frame_.get(), 6),
+ MockRead(SYNCHRONOUS, 0, 7) // EOF cause OnCloseSpdyStream event.
+ };
+
+ DeterministicSocketData data(reads, arraysize(reads),
+ writes, arraysize(writes));
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+ http_session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ session_ = CreateInsecureSpdySession(
+ http_session_, spdy_session_key_, BoundNetLog());
+
+ // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another
+ // WebSocketStream under test.
+ SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback()));
+
+ scoped_ptr<SpdyWebSocketStream> block_stream(
+ new SpdyWebSocketStream(session_, &block_delegate));
+ BoundNetLog block_net_log;
+ GURL block_url("ws://example.com/block");
+ ASSERT_EQ(OK,
+ block_stream->InitializeStream(block_url, HIGHEST, block_net_log));
+
+ data.RunFor(1);
+
+ // Create a WebSocketStream under test.
+ SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+ delegate.SetOnCreated(
+ base::Bind(&SpdyWebSocketStreamTest::DoSync,
+ base::Unretained(this)));
+ delegate.SetOnReceivedHeader(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendHelloFrame,
+ base::Unretained(this)));
+ delegate.SetOnReceivedData(
+ base::Bind(&SpdyWebSocketStreamTest::DoSendClosingFrame,
+ base::Unretained(this)));
+
+ websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+ BoundNetLog net_log;
+ GURL url("ws://example.com/echo");
+ ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream(
+ url, HIGHEST, net_log));
+
+ // Delete the fist stream to allow create the second stream.
+ block_stream.reset();
+ ASSERT_EQ(OK, sync_callback_.WaitForResult());
+
+ SendRequest();
+
+ data.RunFor(7);
+ completion_callback_.WaitForResult();
+
+ websocket_stream_.reset();
+
+ const std::vector<SpdyWebSocketStreamEvent>& block_events =
+ block_delegate.GetSeenEvents();
+ ASSERT_EQ(0U, block_events.size());
+
+ const std::vector<SpdyWebSocketStreamEvent>& events =
+ delegate.GetSeenEvents();
+ ASSERT_EQ(8U, events.size());
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED,
+ events[0].event_type);
+ EXPECT_EQ(0, events[0].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+ events[1].event_type);
+ EXPECT_EQ(OK, events[1].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+ events[2].event_type);
+ EXPECT_EQ(OK, events[2].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[3].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[4].event_type);
+ EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+ events[5].event_type);
+ EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+ events[6].event_type);
+ EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result);
+ EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+ events[7].event_type);
+ EXPECT_EQ(OK, events[7].result);
+
+ // EOF close SPDY session.
+ EXPECT_FALSE(
+ HasSpdySession(http_session_->spdy_session_pool(), spdy_session_key_));
+ EXPECT_TRUE(data.at_read_eof());
+ EXPECT_TRUE(data.at_write_eof());
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_websocket_test_util.cc b/chromium/net/spdy/spdy_websocket_test_util.cc
new file mode 100644
index 00000000000..0872e3f116e
--- /dev/null
+++ b/chromium/net/spdy/spdy_websocket_test_util.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2013 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/spdy/spdy_websocket_test_util.h"
+
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+
+namespace net {
+
+static const int kDefaultAssociatedStreamId = 0;
+static const bool kDefaultCompressed = false;
+static const char* const kDefaultDataPointer = NULL;
+static const uint32 kDefaultDataLength = 0;
+static const char** const kDefaultExtraHeaders = NULL;
+static const int kDefaultExtraHeaderCount = 0;
+
+SpdyWebSocketTestUtil::SpdyWebSocketTestUtil(
+ NextProto protocol) : spdy_util_(protocol) {}
+
+std::string SpdyWebSocketTestUtil::GetHeader(const SpdyHeaderBlock& headers,
+ const std::string& key) const {
+ SpdyHeaderBlock::const_iterator it = headers.find(GetHeaderKey(key));
+ return (it == headers.end()) ? "" : it->second;
+}
+
+void SpdyWebSocketTestUtil::SetHeader(
+ const std::string& key,
+ const std::string& value,
+ SpdyHeaderBlock* headers) const {
+ (*headers)[GetHeaderKey(key)] = value;
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketSynStream(
+ int stream_id,
+ const char* path,
+ const char* host,
+ const char* origin) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ SetHeader("path", path, headers.get());
+ SetHeader("host", host, headers.get());
+ SetHeader("version", "WebSocket/13", headers.get());
+ SetHeader("scheme", "ws", headers.get());
+ SetHeader("origin", origin, headers.get());
+ return spdy_util_.ConstructSpdyControlFrame(headers.Pass(),
+ /*compressed*/ false,
+ stream_id,
+ HIGHEST,
+ SYN_STREAM,
+ CONTROL_FLAG_NONE,
+ 0);
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketSynReply(
+ int stream_id) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ SetHeader("status", "101", headers.get());
+ return spdy_util_.ConstructSpdyControlFrame(headers.Pass(),
+ false,
+ stream_id,
+ LOWEST,
+ SYN_REPLY,
+ CONTROL_FLAG_NONE,
+ 0);
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHandshakeRequestFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+ // SPDY SYN_STREAM control frame header.
+ const SpdyHeaderInfo kSynStreamHeader = {
+ SYN_STREAM,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ RST_STREAM_INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_STREAM control frame.
+ return spdy_util_.ConstructSpdyFrame(
+ kSynStreamHeader,
+ headers.Pass());
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHandshakeResponseFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority) {
+ // SPDY SYN_REPLY control frame header.
+ const SpdyHeaderInfo kSynReplyHeader = {
+ SYN_REPLY,
+ stream_id,
+ kDefaultAssociatedStreamId,
+ ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+ kSpdyCredentialSlotUnused,
+ CONTROL_FLAG_NONE,
+ kDefaultCompressed,
+ RST_STREAM_INVALID,
+ kDefaultDataPointer,
+ kDefaultDataLength,
+ DATA_FLAG_NONE
+ };
+
+ // Construct SPDY SYN_REPLY control frame.
+ return spdy_util_.ConstructSpdyFrame(
+ kSynReplyHeader,
+ headers.Pass());
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketHeadersFrame(
+ int stream_id,
+ const char* length,
+ bool fin) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+ SetHeader("opcode", "1", headers.get()); // text frame
+ SetHeader("length", length, headers.get());
+ SetHeader("fin", fin ? "1" : "0", headers.get());
+ return spdy_util_.ConstructSpdyControlFrame(headers.Pass(),
+ /*compression*/ false,
+ stream_id,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0);
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdyWebSocketDataFrame(
+ const char* data,
+ int len,
+ SpdyStreamId stream_id,
+ bool fin) {
+
+ // Construct SPDY data frame.
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false);
+ return framer.CreateDataFrame(
+ stream_id,
+ data,
+ len,
+ fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+SpdyFrame* SpdyWebSocketTestUtil::ConstructSpdySettings(
+ const SettingsMap& settings) const {
+ return spdy_util_.ConstructSpdySettings(settings);
+}
+
+SpdyMajorVersion SpdyWebSocketTestUtil::spdy_version() const {
+ return spdy_util_.spdy_version();
+}
+
+std::string SpdyWebSocketTestUtil::GetHeaderKey(
+ const std::string& key) const {
+ return (spdy_util_.is_spdy2() ? "" : ":") + key;
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_websocket_test_util.h b/chromium/net/spdy/spdy_websocket_test_util.h
new file mode 100644
index 00000000000..7a9a59e96e8
--- /dev/null
+++ b/chromium/net/spdy/spdy_websocket_test_util.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2013 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_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_test_util_common.h"
+
+namespace net {
+
+class SpdyWebSocketTestUtil {
+ public:
+ explicit SpdyWebSocketTestUtil(NextProto protocol);
+
+ // Returns the value corresponding to the given key (passed through
+ // GetHeaderKey()), or the empty string if none exists.
+ std::string GetHeader(const SpdyHeaderBlock& headers,
+ const std::string& key) const;
+
+ // Adds the given key/value pair to |headers|, passing the key
+ // through GetHeaderKey().
+ void SetHeader(const std::string& key,
+ const std::string& value,
+ SpdyHeaderBlock* headers) const;
+
+ // Constructs a standard SPDY SYN_STREAM frame for WebSocket over
+ // SPDY opening handshake.
+ SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+ const char* path,
+ const char* host,
+ const char* origin);
+
+ // Constructs a standard SPDY SYN_REPLY packet to match the
+ // WebSocket over SPDY opening handshake.
+ SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id);
+
+ // Constructs a WebSocket over SPDY handshake request packet.
+ SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority);
+
+ // Constructs a WebSocket over SPDY handshake response packet.
+ SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+ scoped_ptr<SpdyHeaderBlock> headers,
+ SpdyStreamId stream_id,
+ RequestPriority request_priority);
+
+ // Constructs a SPDY HEADERS frame for a WebSocket frame over SPDY.
+ SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+ const char* length,
+ bool fin);
+
+ // Constructs a WebSocket over SPDY data packet.
+ SpdyFrame* ConstructSpdyWebSocketDataFrame(const char* data,
+ int len,
+ SpdyStreamId stream_id,
+ bool fin);
+
+ // Forwards to |spdy_util_|.
+ SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) const;
+ SpdyMajorVersion spdy_version() const;
+
+ private:
+ // Modify the header key based on the SPDY version and return it.
+ std::string GetHeaderKey(const std::string& key) const;
+
+ SpdyTestUtil spdy_util_;
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
diff --git a/chromium/net/spdy/spdy_write_queue.cc b/chromium/net/spdy/spdy_write_queue.cc
new file mode 100644
index 00000000000..2ac42413232
--- /dev/null
+++ b/chromium/net/spdy/spdy_write_queue.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2013 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/spdy/spdy_write_queue.h"
+
+#include <cstddef>
+
+#include "base/logging.h"
+#include "net/spdy/spdy_buffer.h"
+#include "net/spdy/spdy_buffer_producer.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+SpdyWriteQueue::PendingWrite::PendingWrite() : frame_producer(NULL) {}
+
+SpdyWriteQueue::PendingWrite::PendingWrite(
+ SpdyFrameType frame_type,
+ SpdyBufferProducer* frame_producer,
+ const base::WeakPtr<SpdyStream>& stream)
+ : frame_type(frame_type),
+ frame_producer(frame_producer),
+ stream(stream),
+ has_stream(stream.get() != NULL) {}
+
+SpdyWriteQueue::PendingWrite::~PendingWrite() {}
+
+SpdyWriteQueue::SpdyWriteQueue() {}
+
+SpdyWriteQueue::~SpdyWriteQueue() {
+ Clear();
+}
+
+bool SpdyWriteQueue::IsEmpty() const {
+ for (int i = 0; i < NUM_PRIORITIES; i++) {
+ if (!queue_[i].empty())
+ return false;
+ }
+ return true;
+}
+
+void SpdyWriteQueue::Enqueue(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> frame_producer,
+ const base::WeakPtr<SpdyStream>& stream) {
+ if (stream.get())
+ DCHECK_EQ(stream->priority(), priority);
+ queue_[priority].push_back(
+ PendingWrite(frame_type, frame_producer.release(), stream));
+}
+
+bool SpdyWriteQueue::Dequeue(SpdyFrameType* frame_type,
+ scoped_ptr<SpdyBufferProducer>* frame_producer,
+ base::WeakPtr<SpdyStream>* stream) {
+ for (int i = NUM_PRIORITIES - 1; i >= 0; --i) {
+ if (!queue_[i].empty()) {
+ PendingWrite pending_write = queue_[i].front();
+ queue_[i].pop_front();
+ *frame_type = pending_write.frame_type;
+ frame_producer->reset(pending_write.frame_producer);
+ *stream = pending_write.stream;
+ if (pending_write.has_stream)
+ DCHECK(stream->get());
+ return true;
+ }
+ }
+ return false;
+}
+
+void SpdyWriteQueue::RemovePendingWritesForStream(
+ const base::WeakPtr<SpdyStream>& stream) {
+ DCHECK(stream.get());
+ if (DCHECK_IS_ON()) {
+ // |stream| should not have pending writes in a queue not matching
+ // its priority.
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ if (stream->priority() == i)
+ continue;
+ for (std::deque<PendingWrite>::const_iterator it = queue_[i].begin();
+ it != queue_[i].end(); ++it) {
+ DCHECK_NE(it->stream.get(), stream.get());
+ }
+ }
+ }
+
+ // Do the actual deletion and removal, preserving FIFO-ness.
+ std::deque<PendingWrite>* queue = &queue_[stream->priority()];
+ std::deque<PendingWrite>::iterator out_it = queue->begin();
+ for (std::deque<PendingWrite>::const_iterator it = queue->begin();
+ it != queue->end(); ++it) {
+ if (it->stream.get() == stream.get()) {
+ delete it->frame_producer;
+ } else {
+ *out_it = *it;
+ ++out_it;
+ }
+ }
+ queue->erase(out_it, queue->end());
+}
+
+void SpdyWriteQueue::RemovePendingWritesForStreamsAfter(
+ SpdyStreamId last_good_stream_id) {
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ // Do the actual deletion and removal, preserving FIFO-ness.
+ std::deque<PendingWrite>* queue = &queue_[i];
+ std::deque<PendingWrite>::iterator out_it = queue->begin();
+ for (std::deque<PendingWrite>::const_iterator it = queue->begin();
+ it != queue->end(); ++it) {
+ if (it->stream.get() && (it->stream->stream_id() > last_good_stream_id ||
+ it->stream->stream_id() == 0)) {
+ delete it->frame_producer;
+ } else {
+ *out_it = *it;
+ ++out_it;
+ }
+ }
+ queue->erase(out_it, queue->end());
+ }
+}
+
+void SpdyWriteQueue::Clear() {
+ for (int i = 0; i < NUM_PRIORITIES; ++i) {
+ for (std::deque<PendingWrite>::iterator it = queue_[i].begin();
+ it != queue_[i].end(); ++it) {
+ delete it->frame_producer;
+ }
+ queue_[i].clear();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/spdy/spdy_write_queue.h b/chromium/net/spdy/spdy_write_queue.h
new file mode 100644
index 00000000000..3bceb298c01
--- /dev/null
+++ b/chromium/net/spdy/spdy_write_queue.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2013 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_SPDY_SPDY_WRITE_QUEUE_H_
+#define NET_SPDY_SPDY_WRITE_QUEUE_H_
+
+#include <deque>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+class SpdyBuffer;
+class SpdyBufferProducer;
+class SpdyStream;
+
+// A queue of SpdyBufferProducers to produce frames to write. Ordered
+// by priority, and then FIFO.
+class NET_EXPORT_PRIVATE SpdyWriteQueue {
+ public:
+ SpdyWriteQueue();
+ ~SpdyWriteQueue();
+
+ // Returns whether there is anything in the write queue,
+ // i.e. whether the next call to Dequeue will return true.
+ bool IsEmpty() const;
+
+ // Enqueues the given frame producer of the given type at the given
+ // priority associated with the given stream, which may be NULL if
+ // the frame producer is not associated with a stream. If |stream|
+ // is non-NULL, its priority must be equal to |priority|, and it
+ // must remain non-NULL until the write is dequeued or removed.
+ void Enqueue(RequestPriority priority,
+ SpdyFrameType frame_type,
+ scoped_ptr<SpdyBufferProducer> frame_producer,
+ const base::WeakPtr<SpdyStream>& stream);
+
+ // Dequeues the frame producer with the highest priority that was
+ // enqueued the earliest and its associated stream. Returns true and
+ // fills in |frame_type|, |frame_producer|, and |stream| if
+ // successful -- otherwise, just returns false.
+ bool Dequeue(SpdyFrameType* frame_type,
+ scoped_ptr<SpdyBufferProducer>* frame_producer,
+ base::WeakPtr<SpdyStream>* stream);
+
+ // Removes all pending writes for the given stream, which must be
+ // non-NULL.
+ void RemovePendingWritesForStream(const base::WeakPtr<SpdyStream>& stream);
+
+ // Removes all pending writes for streams after |last_good_stream_id|
+ // and streams with no stream id.
+ void RemovePendingWritesForStreamsAfter(SpdyStreamId last_good_stream_id);
+
+ // Removes all pending writes.
+ void Clear();
+
+ private:
+ // A struct holding a frame producer and its associated stream.
+ struct PendingWrite {
+ SpdyFrameType frame_type;
+ // This has to be a raw pointer since we store this in an STL
+ // container.
+ SpdyBufferProducer* frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ // Whether |stream| was non-NULL when enqueued.
+ bool has_stream;
+
+ PendingWrite();
+ PendingWrite(SpdyFrameType frame_type,
+ SpdyBufferProducer* frame_producer,
+ const base::WeakPtr<SpdyStream>& stream);
+ ~PendingWrite();
+ };
+
+ // The actual write queue, binned by priority.
+ std::deque<PendingWrite> queue_[NUM_PRIORITIES];
+
+ DISALLOW_COPY_AND_ASSIGN(SpdyWriteQueue);
+};
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_WRITE_QUEUE_H_
diff --git a/chromium/net/spdy/spdy_write_queue_unittest.cc b/chromium/net/spdy/spdy_write_queue_unittest.cc
new file mode 100644
index 00000000000..6d6cb3cd3b5
--- /dev/null
+++ b/chromium/net/spdy/spdy_write_queue_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2013 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/spdy/spdy_write_queue.h"
+
+#include <cstddef>
+#include <cstring>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_buffer_producer.h"
+#include "net/spdy/spdy_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+class SpdyWriteQueueTest : public ::testing::Test {};
+
+// Makes a SpdyFrameProducer producing a frame with the data in the
+// given string.
+scoped_ptr<SpdyBufferProducer> StringToProducer(const std::string& s) {
+ scoped_ptr<char[]> data(new char[s.size()]);
+ std::memcpy(data.get(), s.data(), s.size());
+ return scoped_ptr<SpdyBufferProducer>(
+ new SimpleBufferProducer(
+ scoped_ptr<SpdyBuffer>(
+ new SpdyBuffer(
+ scoped_ptr<SpdyFrame>(
+ new SpdyFrame(data.release(), s.size(), true))))));
+}
+
+// Makes a SpdyBufferProducer producing a frame with the data in the
+// given int (converted to a string).
+scoped_ptr<SpdyBufferProducer> IntToProducer(int i) {
+ return StringToProducer(base::IntToString(i));
+}
+
+// Produces a frame with the given producer and returns a copy of its
+// data as a string.
+std::string ProducerToString(scoped_ptr<SpdyBufferProducer> producer) {
+ scoped_ptr<SpdyBuffer> buffer = producer->ProduceBuffer();
+ return std::string(buffer->GetRemainingData(), buffer->GetRemainingSize());
+}
+
+// Produces a frame with the given producer and returns a copy of its
+// data as an int (converted from a string).
+int ProducerToInt(scoped_ptr<SpdyBufferProducer> producer) {
+ int i = 0;
+ EXPECT_TRUE(base::StringToInt(ProducerToString(producer.Pass()), &i));
+ return i;
+}
+
+// Makes a SpdyStream with the given priority and a NULL SpdySession
+// -- be careful to not call any functions that expect the session to
+// be there.
+SpdyStream* MakeTestStream(RequestPriority priority) {
+ return new SpdyStream(
+ SPDY_BIDIRECTIONAL_STREAM, base::WeakPtr<SpdySession>(),
+ GURL(), priority, 0, 0, BoundNetLog());
+}
+
+// Add some frame producers of different priority. The producers
+// should be dequeued in priority order with their associated stream.
+TEST_F(SpdyWriteQueueTest, DequeuesByPriority) {
+ SpdyWriteQueue write_queue;
+
+ scoped_ptr<SpdyBufferProducer> producer_low = StringToProducer("LOW");
+ scoped_ptr<SpdyBufferProducer> producer_medium = StringToProducer("MEDIUM");
+ scoped_ptr<SpdyBufferProducer> producer_highest = StringToProducer("HIGHEST");
+
+ scoped_ptr<SpdyStream> stream_medium(MakeTestStream(MEDIUM));
+ scoped_ptr<SpdyStream> stream_highest(MakeTestStream(HIGHEST));
+
+ // A NULL stream should still work.
+ write_queue.Enqueue(
+ LOW, SYN_STREAM, producer_low.Pass(), base::WeakPtr<SpdyStream>());
+ write_queue.Enqueue(
+ MEDIUM, SYN_REPLY, producer_medium.Pass(), stream_medium->GetWeakPtr());
+ write_queue.Enqueue(
+ HIGHEST, RST_STREAM, producer_highest.Pass(),
+ stream_highest->GetWeakPtr());
+
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(RST_STREAM, frame_type);
+ EXPECT_EQ("HIGHEST", ProducerToString(frame_producer.Pass()));
+ EXPECT_EQ(stream_highest, stream.get());
+
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(SYN_REPLY, frame_type);
+ EXPECT_EQ("MEDIUM", ProducerToString(frame_producer.Pass()));
+ EXPECT_EQ(stream_medium, stream.get());
+
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(SYN_STREAM, frame_type);
+ EXPECT_EQ("LOW", ProducerToString(frame_producer.Pass()));
+ EXPECT_EQ(NULL, stream.get());
+
+ EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+}
+
+// Add some frame producers with the same priority. The producers
+// should be dequeued in FIFO order with their associated stream.
+TEST_F(SpdyWriteQueueTest, DequeuesFIFO) {
+ SpdyWriteQueue write_queue;
+
+ scoped_ptr<SpdyBufferProducer> producer1 = IntToProducer(1);
+ scoped_ptr<SpdyBufferProducer> producer2 = IntToProducer(2);
+ scoped_ptr<SpdyBufferProducer> producer3 = IntToProducer(3);
+
+ scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
+ scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
+ scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY));
+
+ write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, producer1.Pass(),
+ stream1->GetWeakPtr());
+ write_queue.Enqueue(DEFAULT_PRIORITY, SYN_REPLY, producer2.Pass(),
+ stream2->GetWeakPtr());
+ write_queue.Enqueue(DEFAULT_PRIORITY, RST_STREAM, producer3.Pass(),
+ stream3->GetWeakPtr());
+
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(SYN_STREAM, frame_type);
+ EXPECT_EQ(1, ProducerToInt(frame_producer.Pass()));
+ EXPECT_EQ(stream1, stream.get());
+
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(SYN_REPLY, frame_type);
+ EXPECT_EQ(2, ProducerToInt(frame_producer.Pass()));
+ EXPECT_EQ(stream2, stream.get());
+
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(RST_STREAM, frame_type);
+ EXPECT_EQ(3, ProducerToInt(frame_producer.Pass()));
+ EXPECT_EQ(stream3, stream.get());
+
+ EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+}
+
+// Enqueue a bunch of writes and then call
+// RemovePendingWritesForStream() on one of the streams. No dequeued
+// write should be for that stream.
+TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStream) {
+ SpdyWriteQueue write_queue;
+
+ scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
+ scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
+
+ for (int i = 0; i < 100; ++i) {
+ base::WeakPtr<SpdyStream> stream =
+ (((i % 3) == 0) ? stream1 : stream2)->GetWeakPtr();
+ write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i), stream);
+ }
+
+ write_queue.RemovePendingWritesForStream(stream2->GetWeakPtr());
+
+ for (int i = 0; i < 100; i += 3) {
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+ EXPECT_EQ(SYN_STREAM, frame_type);
+ EXPECT_EQ(i, ProducerToInt(frame_producer.Pass()));
+ EXPECT_EQ(stream1, stream.get());
+ }
+
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+}
+
+// Enqueue a bunch of writes and then call
+// RemovePendingWritesForStreamsAfter(). No dequeued write should be for
+// those streams without a stream id, or with a stream_id after that
+// argument.
+TEST_F(SpdyWriteQueueTest, RemovePendingWritesForStreamsAfter) {
+ SpdyWriteQueue write_queue;
+
+ scoped_ptr<SpdyStream> stream1(MakeTestStream(DEFAULT_PRIORITY));
+ stream1->set_stream_id(1);
+ scoped_ptr<SpdyStream> stream2(MakeTestStream(DEFAULT_PRIORITY));
+ stream2->set_stream_id(3);
+ scoped_ptr<SpdyStream> stream3(MakeTestStream(DEFAULT_PRIORITY));
+ stream3->set_stream_id(5);
+ // No stream id assigned.
+ scoped_ptr<SpdyStream> stream4(MakeTestStream(DEFAULT_PRIORITY));
+ base::WeakPtr<SpdyStream> streams[] = {
+ stream1->GetWeakPtr(), stream2->GetWeakPtr(),
+ stream3->GetWeakPtr(), stream4->GetWeakPtr()
+ };
+
+ for (int i = 0; i < 100; ++i) {
+ write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i),
+ streams[i % arraysize(streams)]);
+ }
+
+ write_queue.RemovePendingWritesForStreamsAfter(stream1->stream_id());
+
+ for (int i = 0; i < 100; i += arraysize(streams)) {
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ ASSERT_TRUE(write_queue.Dequeue(&frame_type, &frame_producer, &stream))
+ << "Unable to Dequeue i: " << i;
+ EXPECT_EQ(SYN_STREAM, frame_type);
+ EXPECT_EQ(i, ProducerToInt(frame_producer.Pass()));
+ EXPECT_EQ(stream1, stream.get());
+ }
+
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+}
+
+// Enqueue a bunch of writes and then call Clear(). The write queue
+// should clean up the memory properly, and Dequeue() should return
+// false.
+TEST_F(SpdyWriteQueueTest, Clear) {
+ SpdyWriteQueue write_queue;
+
+ for (int i = 0; i < 100; ++i) {
+ write_queue.Enqueue(DEFAULT_PRIORITY, SYN_STREAM, IntToProducer(i),
+ base::WeakPtr<SpdyStream>());
+ }
+
+ write_queue.Clear();
+
+ SpdyFrameType frame_type = DATA;
+ scoped_ptr<SpdyBufferProducer> frame_producer;
+ base::WeakPtr<SpdyStream> stream;
+ EXPECT_FALSE(write_queue.Dequeue(&frame_type, &frame_producer, &stream));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ssl/client_cert_store.h b/chromium/net/ssl/client_cert_store.h
new file mode 100644
index 00000000000..46ef3362b3b
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_CLIENT_CERT_STORE_H_
+#define NET_SSL_CLIENT_CERT_STORE_H_
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+class SSLCertRequestInfo;
+
+class NET_EXPORT ClientCertStore {
+ public:
+ virtual ~ClientCertStore() {}
+
+ virtual bool GetClientCerts(const SSLCertRequestInfo& cert_request_info,
+ CertificateList* selected_certs) = 0;
+ protected:
+ ClientCertStore() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ClientCertStore);
+};
+
+} // namespace net
+
+#endif // NET_SSL_CLIENT_CERT_STORE_H_
diff --git a/chromium/net/ssl/client_cert_store_impl.h b/chromium/net/ssl/client_cert_store_impl.h
new file mode 100644
index 00000000000..e02cd1c7935
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store_impl.h
@@ -0,0 +1,57 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_CLIENT_CERT_STORE_IMPL_H_
+#define NET_SSL_CLIENT_CERT_STORE_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "net/base/net_export.h"
+#include "net/ssl/client_cert_store.h"
+#include "net/ssl/ssl_cert_request_info.h"
+
+namespace net {
+
+class NET_EXPORT ClientCertStoreImpl : public ClientCertStore {
+ public:
+ ClientCertStoreImpl() {}
+ virtual ~ClientCertStoreImpl() {}
+
+ // ClientCertStore:
+ virtual bool GetClientCerts(const SSLCertRequestInfo& cert_request_info,
+ CertificateList* selected_certs) OVERRIDE;
+
+ private:
+ friend class ClientCertStoreImplTest;
+
+ // A hook for testing. Filters |input_certs| using the logic being used to
+ // filter the system store when GetClientCerts() is called. Depending on the
+ // implementation, this might be:
+ // - Implemented by creating a temporary in-memory store and filtering it
+ // using the common logic (preferable, currently on Windows).
+ // - Implemented by creating a list of certificates that otherwise would be
+ // extracted from the system store and filtering it using the common logic
+ // (less adequate, currently on NSS and Mac).
+ bool SelectClientCertsForTesting(const CertificateList& input_certs,
+ const SSLCertRequestInfo& cert_request_info,
+ CertificateList* selected_certs);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Testing hook specific to Mac, where the internal logic recognizes preferred
+ // certificates for particular domains. If the preferred certificate is
+ // present in the output list (i.e. it doesn't get filtered out), it should
+ // always come first.
+ bool SelectClientCertsGivenPreferredForTesting(
+ const scoped_refptr<X509Certificate>& preferred_cert,
+ const CertificateList& regular_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs);
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ClientCertStoreImpl);
+};
+
+} // namespace net
+
+#endif // NET_SSL_CLIENT_CERT_STORE_IMPL_H_
diff --git a/chromium/net/ssl/client_cert_store_impl_mac.cc b/chromium/net/ssl/client_cert_store_impl_mac.cc
new file mode 100644
index 00000000000..33457358f1e
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store_impl_mac.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 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.
+
+#include "net/ssl/client_cert_store_impl.h"
+
+#include <CommonCrypto/CommonDigest.h>
+#include <CoreFoundation/CFArray.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/SecBase.h>
+#include <Security/Security.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "crypto/mac_security_services_lock.h"
+#include "net/base/host_port_pair.h"
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_mac.h"
+
+using base::ScopedCFTypeRef;
+
+namespace net {
+
+namespace {
+
+// Gets the issuer for a given cert, starting with the cert itself and
+// including the intermediate and finally root certificates (if any).
+// This function calls SecTrust but doesn't actually pay attention to the trust
+// result: it shouldn't be used to determine trust, just to traverse the chain.
+// Caller is responsible for releasing the value stored into *out_cert_chain.
+OSStatus CopyCertChain(SecCertificateRef cert_handle,
+ CFArrayRef* out_cert_chain) {
+ DCHECK(cert_handle);
+ DCHECK(out_cert_chain);
+
+ // Create an SSL policy ref configured for client cert evaluation.
+ SecPolicyRef ssl_policy;
+ OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy);
+ if (result)
+ return result;
+ ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
+
+ // Create a SecTrustRef.
+ ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate(
+ NULL, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)),
+ 1, &kCFTypeArrayCallBacks));
+ SecTrustRef trust_ref = NULL;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ result = SecTrustCreateWithCertificates(input_certs, ssl_policy,
+ &trust_ref);
+ }
+ if (result)
+ return result;
+ ScopedCFTypeRef<SecTrustRef> trust(trust_ref);
+
+ // Evaluate trust, which creates the cert chain.
+ SecTrustResultType status;
+ CSSM_TP_APPLE_EVIDENCE_INFO* status_chain;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ result = SecTrustEvaluate(trust, &status);
+ }
+ if (result)
+ return result;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain);
+ }
+ return result;
+}
+
+// Returns true if |*cert| is issued by an authority in |valid_issuers|
+// according to Keychain Services, rather than using |cert|'s intermediate
+// certificates. If it is, |*cert| is updated to point to the completed
+// certificate
+bool IsIssuedByInKeychain(const std::vector<std::string>& valid_issuers,
+ scoped_refptr<X509Certificate>* cert) {
+ DCHECK(cert);
+ DCHECK(cert->get());
+
+ X509Certificate::OSCertHandle cert_handle = (*cert)->os_cert_handle();
+ CFArrayRef cert_chain = NULL;
+ OSStatus result = CopyCertChain(cert_handle, &cert_chain);
+ if (result) {
+ OSSTATUS_LOG(ERROR, result) << "CopyCertChain error";
+ return false;
+ }
+
+ if (!cert_chain)
+ return false;
+
+ X509Certificate::OSCertHandles intermediates;
+ for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain);
+ i < chain_count; ++i) {
+ SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
+ const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
+ intermediates.push_back(cert);
+ }
+
+ scoped_refptr<X509Certificate> new_cert(X509Certificate::CreateFromHandle(
+ cert_handle, intermediates));
+ CFRelease(cert_chain); // Also frees |intermediates|.
+
+ if (!new_cert->IsIssuedByEncoded(valid_issuers))
+ return false;
+
+ cert->swap(new_cert);
+ return true;
+}
+
+// Examines the certificates in |preferred_cert| and |regular_certs| to find
+// all certificates that match the client certificate request in |request|,
+// storing the matching certificates in |selected_certs|.
+// If |query_keychain| is true, Keychain Services will be queried to construct
+// full certificate chains. If it is false, only the the certificates and their
+// intermediates (available via X509Certificate::GetIntermediateCertificates())
+// will be considered.
+bool GetClientCertsImpl(const scoped_refptr<X509Certificate>& preferred_cert,
+ const CertificateList& regular_certs,
+ const SSLCertRequestInfo& request,
+ bool query_keychain,
+ CertificateList* selected_certs) {
+ CertificateList preliminary_list;
+ if (preferred_cert.get())
+ preliminary_list.push_back(preferred_cert);
+ preliminary_list.insert(preliminary_list.end(), regular_certs.begin(),
+ regular_certs.end());
+
+ selected_certs->clear();
+ for (size_t i = 0; i < preliminary_list.size(); ++i) {
+ scoped_refptr<X509Certificate>& cert = preliminary_list[i];
+ if (cert->HasExpired() || !cert->SupportsSSLClientAuth())
+ continue;
+
+ // Skip duplicates (a cert may be in multiple keychains).
+ const SHA1HashValue& fingerprint = cert->fingerprint();
+ size_t pos;
+ for (pos = 0; pos < selected_certs->size(); ++pos) {
+ if ((*selected_certs)[pos]->fingerprint().Equals(fingerprint))
+ break;
+ }
+ if (pos < selected_certs->size())
+ continue;
+
+ // Check if the certificate issuer is allowed by the server.
+ if (request.cert_authorities.empty() ||
+ cert->IsIssuedByEncoded(request.cert_authorities) ||
+ (query_keychain &&
+ IsIssuedByInKeychain(request.cert_authorities, &cert))) {
+ selected_certs->push_back(cert);
+ }
+ }
+
+ // Preferred cert should appear first in the ui, so exclude it from the
+ // sorting.
+ CertificateList::iterator sort_begin = selected_certs->begin();
+ CertificateList::iterator sort_end = selected_certs->end();
+ if (preferred_cert.get() && sort_begin != sort_end &&
+ sort_begin->get() == preferred_cert.get()) {
+ ++sort_begin;
+ }
+ sort(sort_begin, sort_end, x509_util::ClientCertSorter());
+ return true;
+}
+
+} // namespace
+
+bool ClientCertStoreImpl::GetClientCerts(const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ std::string server_domain =
+ HostPortPair::FromString(request.host_and_port).host();
+
+ ScopedCFTypeRef<SecIdentityRef> preferred_identity;
+ if (!server_domain.empty()) {
+ // See if there's an identity preference for this domain:
+ ScopedCFTypeRef<CFStringRef> domain_str(
+ base::SysUTF8ToCFStringRef("https://" + server_domain));
+ SecIdentityRef identity = NULL;
+ // While SecIdentityCopyPreferences appears to take a list of CA issuers
+ // to restrict the identity search to, within Security.framework the
+ // argument is ignored and filtering unimplemented. See
+ // SecIdentity.cpp in libsecurity_keychain, specifically
+ // _SecIdentityCopyPreferenceMatchingName().
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr)
+ preferred_identity.reset(identity);
+ }
+ }
+
+ // Now enumerate the identities in the available keychains.
+ scoped_refptr<X509Certificate> preferred_cert = NULL;
+ CertificateList regular_certs;
+
+ SecIdentitySearchRef search = NULL;
+ OSStatus err;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
+ }
+ if (err)
+ return false;
+ ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
+ while (!err) {
+ SecIdentityRef identity = NULL;
+ {
+ base::AutoLock lock(crypto::GetMacSecurityServicesLock());
+ err = SecIdentitySearchCopyNext(search, &identity);
+ }
+ if (err)
+ break;
+ ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity);
+
+ SecCertificateRef cert_handle;
+ err = SecIdentityCopyCertificate(identity, &cert_handle);
+ if (err != noErr)
+ continue;
+ ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle);
+
+ scoped_refptr<X509Certificate> cert(
+ X509Certificate::CreateFromHandle(cert_handle,
+ X509Certificate::OSCertHandles()));
+
+ if (preferred_identity && CFEqual(preferred_identity, identity)) {
+ // Only one certificate should match.
+ DCHECK(!preferred_cert.get());
+ preferred_cert = cert;
+ } else {
+ regular_certs.push_back(cert);
+ }
+ }
+
+ if (err != errSecItemNotFound) {
+ OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
+ return false;
+ }
+
+ return GetClientCertsImpl(preferred_cert, regular_certs, request, true,
+ selected_certs);
+}
+
+bool ClientCertStoreImpl::SelectClientCertsForTesting(
+ const CertificateList& input_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ return GetClientCertsImpl(NULL, input_certs, request, false,
+ selected_certs);
+}
+
+#if !defined(OS_IOS)
+bool ClientCertStoreImpl::SelectClientCertsGivenPreferredForTesting(
+ const scoped_refptr<X509Certificate>& preferred_cert,
+ const CertificateList& regular_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ return GetClientCertsImpl(preferred_cert, regular_certs, request, false,
+ selected_certs);
+}
+#endif
+
+} // namespace net
diff --git a/chromium/net/ssl/client_cert_store_impl_nss.cc b/chromium/net/ssl/client_cert_store_impl_nss.cc
new file mode 100644
index 00000000000..ffab2680c68
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store_impl_nss.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 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.
+
+#include "net/ssl/client_cert_store_impl.h"
+
+#include <nss.h>
+#include <ssl.h>
+
+#include "base/logging.h"
+#include "net/cert/x509_util.h"
+
+namespace net {
+
+namespace {
+
+// Examines the certificates in |cert_list| to find all certificates that match
+// the client certificate request in |request|, storing the matching
+// certificates in |selected_certs|.
+// If |query_nssdb| is true, NSS will be queried to construct full certificate
+// chains. If it is false, only the certificate will be considered.
+bool GetClientCertsImpl(CERTCertList* cert_list,
+ const SSLCertRequestInfo& request,
+ bool query_nssdb,
+ CertificateList* selected_certs) {
+ DCHECK(cert_list);
+ DCHECK(selected_certs);
+
+ selected_certs->clear();
+
+ // Create a "fake" CERTDistNames structure. No public API exists to create
+ // one from a list of issuers.
+ CERTDistNames ca_names;
+ ca_names.arena = NULL;
+ ca_names.nnames = 0;
+ ca_names.names = NULL;
+ ca_names.head = NULL;
+
+ std::vector<SECItem> ca_names_items(request.cert_authorities.size());
+ for (size_t i = 0; i < request.cert_authorities.size(); ++i) {
+ const std::string& authority = request.cert_authorities[i];
+ ca_names_items[i].type = siBuffer;
+ ca_names_items[i].data =
+ reinterpret_cast<unsigned char*>(const_cast<char*>(authority.data()));
+ ca_names_items[i].len = static_cast<unsigned int>(authority.size());
+ }
+ ca_names.nnames = static_cast<int>(ca_names_items.size());
+ if (!ca_names_items.empty())
+ ca_names.names = &ca_names_items[0];
+
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ // Only offer unexpired certificates.
+ if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) !=
+ secCertTimeValid) {
+ continue;
+ }
+
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ node->cert, X509Certificate::OSCertHandles());
+
+ // Check if the certificate issuer is allowed by the server.
+ if (request.cert_authorities.empty() ||
+ (!query_nssdb &&
+ cert->IsIssuedByEncoded(request.cert_authorities)) ||
+ (query_nssdb &&
+ NSS_CmpCertChainWCANames(node->cert, &ca_names) == SECSuccess)) {
+ selected_certs->push_back(cert);
+ }
+ }
+
+ std::sort(selected_certs->begin(), selected_certs->end(),
+ x509_util::ClientCertSorter());
+ return true;
+}
+
+} // namespace
+
+bool ClientCertStoreImpl::GetClientCerts(const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ CERTCertList* client_certs = CERT_FindUserCertsByUsage(
+ CERT_GetDefaultCertDB(), certUsageSSLClient,
+ PR_FALSE, PR_FALSE, NULL);
+ // It is ok for a user not to have any client certs.
+ if (!client_certs)
+ return true;
+
+ bool rv = GetClientCertsImpl(client_certs, request, true, selected_certs);
+ CERT_DestroyCertList(client_certs);
+ return rv;
+}
+
+bool ClientCertStoreImpl::SelectClientCertsForTesting(
+ const CertificateList& input_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ CERTCertList* cert_list = CERT_NewCertList();
+ if (!cert_list)
+ return false;
+ for (size_t i = 0; i < input_certs.size(); ++i) {
+ CERT_AddCertToListTail(
+ cert_list, CERT_DupCertificate(input_certs[i]->os_cert_handle()));
+ }
+
+ bool rv = GetClientCertsImpl(cert_list, request, false, selected_certs);
+ CERT_DestroyCertList(cert_list);
+ return rv;
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/client_cert_store_impl_unittest.cc b/chromium/net/ssl/client_cert_store_impl_unittest.cc
new file mode 100644
index 00000000000..500e156169d
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store_impl_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#include "net/ssl/client_cert_store_impl.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/test_data_directory.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// "CN=B CA" - DER encoded DN of the issuer of client_1.pem
+const unsigned char kAuthority1DN[] = {
+ 0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x04, 0x42, 0x20, 0x43, 0x41
+};
+
+// "CN=E CA" - DER encoded DN of the issuer of client_2.pem
+unsigned char kAuthority2DN[] = {
+ 0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
+ 0x04, 0x45, 0x20, 0x43, 0x41
+};
+
+} // namespace
+
+class ClientCertStoreImplTest : public ::testing::Test {
+ protected:
+ bool SelectClientCerts(const CertificateList& input_certs,
+ const SSLCertRequestInfo& cert_request_info,
+ CertificateList* selected_certs) {
+ return store_.SelectClientCertsForTesting(
+ input_certs, cert_request_info, selected_certs);
+ }
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ bool SelectClientCertsGivenPreferred(
+ const scoped_refptr<X509Certificate>& preferred_cert,
+ const CertificateList& regular_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ return store_.SelectClientCertsGivenPreferredForTesting(
+ preferred_cert, regular_certs, request, selected_certs);
+ }
+#endif
+
+ private:
+ ClientCertStoreImpl store_;
+};
+
+TEST_F(ClientCertStoreImplTest, EmptyQuery) {
+ std::vector<scoped_refptr<X509Certificate> > certs;
+ scoped_refptr<SSLCertRequestInfo> request(new SSLCertRequestInfo());
+
+ std::vector<scoped_refptr<X509Certificate> > selected_certs;
+ bool rv = SelectClientCerts(certs, *request.get(), &selected_certs);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(0u, selected_certs.size());
+}
+
+// Verify that CertRequestInfo with empty |cert_authorities| matches all
+// issuers, rather than no issuers.
+TEST_F(ClientCertStoreImplTest, AllIssuersAllowed) {
+ scoped_refptr<X509Certificate> cert(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert.get());
+
+ std::vector<scoped_refptr<X509Certificate> > certs;
+ certs.push_back(cert);
+ scoped_refptr<SSLCertRequestInfo> request(new SSLCertRequestInfo());
+
+ std::vector<scoped_refptr<X509Certificate> > selected_certs;
+ bool rv = SelectClientCerts(certs, *request.get(), &selected_certs);
+ EXPECT_TRUE(rv);
+ ASSERT_EQ(1u, selected_certs.size());
+ EXPECT_TRUE(selected_certs[0]->Equals(cert.get()));
+}
+
+// Verify that certificates are correctly filtered against CertRequestInfo with
+// |cert_authorities| containing only |authority_1_DN|.
+TEST_F(ClientCertStoreImplTest, CertAuthorityFiltering) {
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+ scoped_refptr<X509Certificate> cert_2(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_2.pem"));
+ ASSERT_TRUE(cert_2.get());
+
+ std::vector<std::string> authority_1(
+ 1, std::string(reinterpret_cast<const char*>(kAuthority1DN),
+ sizeof(kAuthority1DN)));
+ std::vector<std::string> authority_2(
+ 1, std::string(reinterpret_cast<const char*>(kAuthority2DN),
+ sizeof(kAuthority2DN)));
+ EXPECT_TRUE(cert_1->IsIssuedByEncoded(authority_1));
+ EXPECT_FALSE(cert_1->IsIssuedByEncoded(authority_2));
+ EXPECT_TRUE(cert_2->IsIssuedByEncoded(authority_2));
+ EXPECT_FALSE(cert_2->IsIssuedByEncoded(authority_1));
+
+ std::vector<scoped_refptr<X509Certificate> > certs;
+ certs.push_back(cert_1);
+ certs.push_back(cert_2);
+ scoped_refptr<SSLCertRequestInfo> request(new SSLCertRequestInfo());
+ request->cert_authorities = authority_1;
+
+ std::vector<scoped_refptr<X509Certificate> > selected_certs;
+ bool rv = SelectClientCerts(certs, *request.get(), &selected_certs);
+ EXPECT_TRUE(rv);
+ ASSERT_EQ(1u, selected_certs.size());
+ EXPECT_TRUE(selected_certs[0]->Equals(cert_1.get()));
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+// Verify that the preferred cert gets filtered out when it doesn't match the
+// server criteria.
+TEST_F(ClientCertStoreImplTest, FilterOutThePreferredCert) {
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+
+ std::vector<std::string> authority_2(
+ 1, std::string(reinterpret_cast<const char*>(kAuthority2DN),
+ sizeof(kAuthority2DN)));
+ EXPECT_FALSE(cert_1->IsIssuedByEncoded(authority_2));
+
+ std::vector<scoped_refptr<X509Certificate> > certs;
+ scoped_refptr<SSLCertRequestInfo> request(new SSLCertRequestInfo());
+ request->cert_authorities = authority_2;
+
+ std::vector<scoped_refptr<X509Certificate> > selected_certs;
+ bool rv = SelectClientCertsGivenPreferred(
+ cert_1, certs, *request.get(), &selected_certs);
+ EXPECT_TRUE(rv);
+ EXPECT_EQ(0u, selected_certs.size());
+}
+
+// Verify that the preferred cert takes the first position in the output list,
+// when it does not get filtered out.
+TEST_F(ClientCertStoreImplTest, PreferredCertGoesFirst) {
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+ scoped_refptr<X509Certificate> cert_2(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_2.pem"));
+ ASSERT_TRUE(cert_2.get());
+
+ std::vector<scoped_refptr<X509Certificate> > certs;
+ certs.push_back(cert_2);
+ scoped_refptr<SSLCertRequestInfo> request(new SSLCertRequestInfo());
+
+ std::vector<scoped_refptr<X509Certificate> > selected_certs;
+ bool rv = SelectClientCertsGivenPreferred(
+ cert_1, certs, *request.get(), &selected_certs);
+ EXPECT_TRUE(rv);
+ ASSERT_EQ(2u, selected_certs.size());
+ EXPECT_TRUE(selected_certs[0]->Equals(cert_1.get()));
+ EXPECT_TRUE(selected_certs[1]->Equals(cert_2.get()));
+}
+#endif
+
+} // namespace net
diff --git a/chromium/net/ssl/client_cert_store_impl_win.cc b/chromium/net/ssl/client_cert_store_impl_win.cc
new file mode 100644
index 00000000000..63ea6e4a875
--- /dev/null
+++ b/chromium/net/ssl/client_cert_store_impl_win.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 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.
+
+#include "net/ssl/client_cert_store_impl.h"
+
+#include <algorithm>
+#include <string>
+
+#define SECURITY_WIN32 // Needs to be defined before including security.h
+#include <windows.h>
+#include <wincrypt.h>
+#include <security.h>
+
+#include "base/logging.h"
+#include "crypto/scoped_capi_types.h"
+#include "net/cert/x509_util.h"
+
+namespace net {
+
+namespace {
+
+// Callback required by Windows API function CertFindChainInStore(). In addition
+// to filtering by extended/enhanced key usage, we do not show expired
+// certificates and require digital signature usage in the key usage extension.
+//
+// This matches our behavior on Mac OS X and that of NSS. It also matches the
+// default behavior of IE8. See http://support.microsoft.com/kb/890326 and
+// http://blogs.msdn.com/b/askie/archive/2009/06/09/my-expired-client-certifica
+// tes-no-longer-display-when-connecting-to-my-web-server-using-ie8.aspx
+static BOOL WINAPI ClientCertFindCallback(PCCERT_CONTEXT cert_context,
+ void* find_arg) {
+ // Verify the certificate key usage is appropriate or not specified.
+ BYTE key_usage;
+ if (CertGetIntendedKeyUsage(X509_ASN_ENCODING, cert_context->pCertInfo,
+ &key_usage, 1)) {
+ if (!(key_usage & CERT_DIGITAL_SIGNATURE_KEY_USAGE))
+ return FALSE;
+ } else {
+ DWORD err = GetLastError();
+ // If |err| is non-zero, it's an actual error. Otherwise the extension
+ // just isn't present, and we treat it as if everything was allowed.
+ if (err) {
+ DLOG(ERROR) << "CertGetIntendedKeyUsage failed: " << err;
+ return FALSE;
+ }
+ }
+
+ // Verify the current time is within the certificate's validity period.
+ if (CertVerifyTimeValidity(NULL, cert_context->pCertInfo) != 0)
+ return FALSE;
+
+ // Verify private key metadata is associated with this certificate.
+ // TODO(ppi): Is this really needed? Isn't it equivalent to leaving
+ // CERT_CHAIN_FIND_BY_ISSUER_NO_KEY_FLAG not set in |find_flags| argument of
+ // CertFindChainInStore()?
+ DWORD size = 0;
+ if (!CertGetCertificateContextProperty(
+ cert_context, CERT_KEY_PROV_INFO_PROP_ID, NULL, &size)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+bool GetClientCertsImpl(HCERTSTORE cert_store,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ selected_certs->clear();
+
+ const size_t auth_count = request.cert_authorities.size();
+ std::vector<CERT_NAME_BLOB> issuers(auth_count);
+ for (size_t i = 0; i < auth_count; ++i) {
+ issuers[i].cbData = static_cast<DWORD>(request.cert_authorities[i].size());
+ issuers[i].pbData = reinterpret_cast<BYTE*>(
+ const_cast<char*>(request.cert_authorities[i].data()));
+ }
+
+ // Enumerate the client certificates.
+ CERT_CHAIN_FIND_BY_ISSUER_PARA find_by_issuer_para;
+ memset(&find_by_issuer_para, 0, sizeof(find_by_issuer_para));
+ find_by_issuer_para.cbSize = sizeof(find_by_issuer_para);
+ find_by_issuer_para.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
+ find_by_issuer_para.cIssuer = static_cast<DWORD>(auth_count);
+ find_by_issuer_para.rgIssuer =
+ reinterpret_cast<CERT_NAME_BLOB*>(issuers.data());
+ find_by_issuer_para.pfnFindCallback = ClientCertFindCallback;
+
+ PCCERT_CHAIN_CONTEXT chain_context = NULL;
+ DWORD find_flags = CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG |
+ CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG;
+ for (;;) {
+ // Find a certificate chain.
+ chain_context = CertFindChainInStore(cert_store,
+ X509_ASN_ENCODING,
+ find_flags,
+ CERT_CHAIN_FIND_BY_ISSUER,
+ &find_by_issuer_para,
+ chain_context);
+ if (!chain_context) {
+ if (GetLastError() != CRYPT_E_NOT_FOUND)
+ DPLOG(ERROR) << "CertFindChainInStore failed: ";
+ break;
+ }
+
+ // Get the leaf certificate.
+ PCCERT_CONTEXT cert_context =
+ chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
+ // Copy the certificate, so that it is valid after |cert_store| is closed.
+ PCCERT_CONTEXT cert_context2 = NULL;
+ BOOL ok = CertAddCertificateContextToStore(NULL, cert_context,
+ CERT_STORE_ADD_USE_EXISTING,
+ &cert_context2);
+ if (!ok) {
+ NOTREACHED();
+ continue;
+ }
+
+ // Grab the intermediates, if any.
+ X509Certificate::OSCertHandles intermediates;
+ for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; ++i) {
+ PCCERT_CONTEXT chain_intermediate =
+ chain_context->rgpChain[0]->rgpElement[i]->pCertContext;
+ PCCERT_CONTEXT copied_intermediate = NULL;
+ ok = CertAddCertificateContextToStore(NULL, chain_intermediate,
+ CERT_STORE_ADD_USE_EXISTING,
+ &copied_intermediate);
+ if (ok)
+ intermediates.push_back(copied_intermediate);
+ }
+ scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle(
+ cert_context2, intermediates);
+ selected_certs->push_back(cert);
+ CertFreeCertificateContext(cert_context2);
+ for (size_t i = 0; i < intermediates.size(); ++i)
+ CertFreeCertificateContext(intermediates[i]);
+ }
+
+ std::sort(selected_certs->begin(), selected_certs->end(),
+ x509_util::ClientCertSorter());
+ return true;
+}
+
+} // namespace
+
+bool ClientCertStoreImpl::GetClientCerts(const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ // Client certificates of the user are in the "MY" system certificate store.
+ HCERTSTORE my_cert_store = CertOpenSystemStore(NULL, L"MY");
+ if (!my_cert_store) {
+ PLOG(ERROR) << "Could not open the \"MY\" system certificate store: ";
+ return false;
+ }
+
+ bool rv = GetClientCertsImpl(my_cert_store, request, selected_certs);
+ if (!CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG)) {
+ PLOG(ERROR) << "Could not close the \"MY\" system certificate store: ";
+ return false;
+ }
+ return rv;
+}
+
+bool ClientCertStoreImpl::SelectClientCertsForTesting(
+ const CertificateList& input_certs,
+ const SSLCertRequestInfo& request,
+ CertificateList* selected_certs) {
+ typedef crypto::ScopedCAPIHandle<
+ HCERTSTORE,
+ crypto::CAPIDestroyerWithFlags<HCERTSTORE,
+ CertCloseStore, 0> > ScopedHCERTSTORE;
+
+ ScopedHCERTSTORE test_store(CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0,
+ NULL));
+ if (!test_store)
+ return false;
+
+ // Add available certificates to the test store.
+ for (size_t i = 0; i < input_certs.size(); ++i) {
+ // Add the certificate to the test store.
+ PCCERT_CONTEXT cert = NULL;
+ if (!CertAddCertificateContextToStore(test_store,
+ input_certs[i]->os_cert_handle(),
+ CERT_STORE_ADD_NEW, &cert)) {
+ return false;
+ }
+ // Add dummy private key data to the certificate - otherwise the certificate
+ // would be discarded by the filtering routines.
+ CRYPT_KEY_PROV_INFO private_key_data;
+ memset(&private_key_data, 0, sizeof(private_key_data));
+ if (!CertSetCertificateContextProperty(cert,
+ CERT_KEY_PROV_INFO_PROP_ID,
+ 0, &private_key_data)) {
+ return false;
+ }
+ // Decrement the reference count of the certificate (since we requested a
+ // copy).
+ if (!CertFreeCertificateContext(cert))
+ return false;
+ }
+
+ bool rv = GetClientCertsImpl(test_store.get(), request, selected_certs);
+ return rv;
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/default_server_bound_cert_store.cc b/chromium/net/ssl/default_server_bound_cert_store.cc
new file mode 100644
index 00000000000..6fb9180e875
--- /dev/null
+++ b/chromium/net/ssl/default_server_bound_cert_store.cc
@@ -0,0 +1,473 @@
+// Copyright (c) 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.
+
+#include "net/ssl/default_server_bound_cert_store.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// --------------------------------------------------------------------------
+// Task
+class DefaultServerBoundCertStore::Task {
+ public:
+ virtual ~Task();
+
+ // Runs the task and invokes the client callback on the thread that
+ // originally constructed the task.
+ virtual void Run(DefaultServerBoundCertStore* store) = 0;
+
+ protected:
+ void InvokeCallback(base::Closure callback) const;
+};
+
+DefaultServerBoundCertStore::Task::~Task() {
+}
+
+void DefaultServerBoundCertStore::Task::InvokeCallback(
+ base::Closure callback) const {
+ if (!callback.is_null())
+ callback.Run();
+}
+
+// --------------------------------------------------------------------------
+// GetServerBoundCertTask
+class DefaultServerBoundCertStore::GetServerBoundCertTask
+ : public DefaultServerBoundCertStore::Task {
+ public:
+ GetServerBoundCertTask(const std::string& server_identifier,
+ const GetCertCallback& callback);
+ virtual ~GetServerBoundCertTask();
+ virtual void Run(DefaultServerBoundCertStore* store) OVERRIDE;
+
+ private:
+ std::string server_identifier_;
+ GetCertCallback callback_;
+};
+
+DefaultServerBoundCertStore::GetServerBoundCertTask::GetServerBoundCertTask(
+ const std::string& server_identifier,
+ const GetCertCallback& callback)
+ : server_identifier_(server_identifier),
+ callback_(callback) {
+}
+
+DefaultServerBoundCertStore::GetServerBoundCertTask::~GetServerBoundCertTask() {
+}
+
+void DefaultServerBoundCertStore::GetServerBoundCertTask::Run(
+ DefaultServerBoundCertStore* store) {
+ base::Time expiration_time;
+ std::string private_key_result;
+ std::string cert_result;
+ int err = store->GetServerBoundCert(
+ server_identifier_, &expiration_time, &private_key_result,
+ &cert_result, GetCertCallback());
+ DCHECK(err != ERR_IO_PENDING);
+
+ InvokeCallback(base::Bind(callback_, err, server_identifier_,
+ expiration_time, private_key_result, cert_result));
+}
+
+// --------------------------------------------------------------------------
+// SetServerBoundCertTask
+class DefaultServerBoundCertStore::SetServerBoundCertTask
+ : public DefaultServerBoundCertStore::Task {
+ public:
+ SetServerBoundCertTask(const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert);
+ virtual ~SetServerBoundCertTask();
+ virtual void Run(DefaultServerBoundCertStore* store) OVERRIDE;
+
+ private:
+ std::string server_identifier_;
+ base::Time creation_time_;
+ base::Time expiration_time_;
+ std::string private_key_;
+ std::string cert_;
+};
+
+DefaultServerBoundCertStore::SetServerBoundCertTask::SetServerBoundCertTask(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert)
+ : server_identifier_(server_identifier),
+ creation_time_(creation_time),
+ expiration_time_(expiration_time),
+ private_key_(private_key),
+ cert_(cert) {
+}
+
+DefaultServerBoundCertStore::SetServerBoundCertTask::~SetServerBoundCertTask() {
+}
+
+void DefaultServerBoundCertStore::SetServerBoundCertTask::Run(
+ DefaultServerBoundCertStore* store) {
+ store->SyncSetServerBoundCert(server_identifier_, creation_time_,
+ expiration_time_, private_key_, cert_);
+}
+
+// --------------------------------------------------------------------------
+// DeleteServerBoundCertTask
+class DefaultServerBoundCertStore::DeleteServerBoundCertTask
+ : public DefaultServerBoundCertStore::Task {
+ public:
+ DeleteServerBoundCertTask(const std::string& server_identifier,
+ const base::Closure& callback);
+ virtual ~DeleteServerBoundCertTask();
+ virtual void Run(DefaultServerBoundCertStore* store) OVERRIDE;
+
+ private:
+ std::string server_identifier_;
+ base::Closure callback_;
+};
+
+DefaultServerBoundCertStore::DeleteServerBoundCertTask::
+ DeleteServerBoundCertTask(
+ const std::string& server_identifier,
+ const base::Closure& callback)
+ : server_identifier_(server_identifier),
+ callback_(callback) {
+}
+
+DefaultServerBoundCertStore::DeleteServerBoundCertTask::
+ ~DeleteServerBoundCertTask() {
+}
+
+void DefaultServerBoundCertStore::DeleteServerBoundCertTask::Run(
+ DefaultServerBoundCertStore* store) {
+ store->SyncDeleteServerBoundCert(server_identifier_);
+
+ InvokeCallback(callback_);
+}
+
+// --------------------------------------------------------------------------
+// DeleteAllCreatedBetweenTask
+class DefaultServerBoundCertStore::DeleteAllCreatedBetweenTask
+ : public DefaultServerBoundCertStore::Task {
+ public:
+ DeleteAllCreatedBetweenTask(base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback);
+ virtual ~DeleteAllCreatedBetweenTask();
+ virtual void Run(DefaultServerBoundCertStore* store) OVERRIDE;
+
+ private:
+ base::Time delete_begin_;
+ base::Time delete_end_;
+ base::Closure callback_;
+};
+
+DefaultServerBoundCertStore::DeleteAllCreatedBetweenTask::
+ DeleteAllCreatedBetweenTask(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback)
+ : delete_begin_(delete_begin),
+ delete_end_(delete_end),
+ callback_(callback) {
+}
+
+DefaultServerBoundCertStore::DeleteAllCreatedBetweenTask::
+ ~DeleteAllCreatedBetweenTask() {
+}
+
+void DefaultServerBoundCertStore::DeleteAllCreatedBetweenTask::Run(
+ DefaultServerBoundCertStore* store) {
+ store->SyncDeleteAllCreatedBetween(delete_begin_, delete_end_);
+
+ InvokeCallback(callback_);
+}
+
+// --------------------------------------------------------------------------
+// GetAllServerBoundCertsTask
+class DefaultServerBoundCertStore::GetAllServerBoundCertsTask
+ : public DefaultServerBoundCertStore::Task {
+ public:
+ explicit GetAllServerBoundCertsTask(const GetCertListCallback& callback);
+ virtual ~GetAllServerBoundCertsTask();
+ virtual void Run(DefaultServerBoundCertStore* store) OVERRIDE;
+
+ private:
+ std::string server_identifier_;
+ GetCertListCallback callback_;
+};
+
+DefaultServerBoundCertStore::GetAllServerBoundCertsTask::
+ GetAllServerBoundCertsTask(const GetCertListCallback& callback)
+ : callback_(callback) {
+}
+
+DefaultServerBoundCertStore::GetAllServerBoundCertsTask::
+ ~GetAllServerBoundCertsTask() {
+}
+
+void DefaultServerBoundCertStore::GetAllServerBoundCertsTask::Run(
+ DefaultServerBoundCertStore* store) {
+ ServerBoundCertList cert_list;
+ store->SyncGetAllServerBoundCerts(&cert_list);
+
+ InvokeCallback(base::Bind(callback_, cert_list));
+}
+
+// --------------------------------------------------------------------------
+// DefaultServerBoundCertStore
+
+// static
+const size_t DefaultServerBoundCertStore::kMaxCerts = 3300;
+
+DefaultServerBoundCertStore::DefaultServerBoundCertStore(
+ PersistentStore* store)
+ : initialized_(false),
+ loaded_(false),
+ store_(store),
+ weak_ptr_factory_(this) {}
+
+int DefaultServerBoundCertStore::GetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time* expiration_time,
+ std::string* private_key_result,
+ std::string* cert_result,
+ const GetCertCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ InitIfNecessary();
+
+ if (!loaded_) {
+ EnqueueTask(scoped_ptr<Task>(
+ new GetServerBoundCertTask(server_identifier, callback)));
+ return ERR_IO_PENDING;
+ }
+
+ ServerBoundCertMap::iterator it = server_bound_certs_.find(server_identifier);
+
+ if (it == server_bound_certs_.end())
+ return ERR_FILE_NOT_FOUND;
+
+ ServerBoundCert* cert = it->second;
+ *expiration_time = cert->expiration_time();
+ *private_key_result = cert->private_key();
+ *cert_result = cert->cert();
+
+ return OK;
+}
+
+void DefaultServerBoundCertStore::SetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) {
+ RunOrEnqueueTask(scoped_ptr<Task>(new SetServerBoundCertTask(
+ server_identifier, creation_time, expiration_time, private_key,
+ cert)));
+}
+
+void DefaultServerBoundCertStore::DeleteServerBoundCert(
+ const std::string& server_identifier,
+ const base::Closure& callback) {
+ RunOrEnqueueTask(scoped_ptr<Task>(
+ new DeleteServerBoundCertTask(server_identifier, callback)));
+}
+
+void DefaultServerBoundCertStore::DeleteAllCreatedBetween(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) {
+ RunOrEnqueueTask(scoped_ptr<Task>(
+ new DeleteAllCreatedBetweenTask(delete_begin, delete_end, callback)));
+}
+
+void DefaultServerBoundCertStore::DeleteAll(
+ const base::Closure& callback) {
+ DeleteAllCreatedBetween(base::Time(), base::Time(), callback);
+}
+
+void DefaultServerBoundCertStore::GetAllServerBoundCerts(
+ const GetCertListCallback& callback) {
+ RunOrEnqueueTask(scoped_ptr<Task>(new GetAllServerBoundCertsTask(callback)));
+}
+
+int DefaultServerBoundCertStore::GetCertCount() {
+ DCHECK(CalledOnValidThread());
+
+ return server_bound_certs_.size();
+}
+
+void DefaultServerBoundCertStore::SetForceKeepSessionState() {
+ DCHECK(CalledOnValidThread());
+ InitIfNecessary();
+
+ if (store_.get())
+ store_->SetForceKeepSessionState();
+}
+
+DefaultServerBoundCertStore::~DefaultServerBoundCertStore() {
+ DeleteAllInMemory();
+}
+
+void DefaultServerBoundCertStore::DeleteAllInMemory() {
+ DCHECK(CalledOnValidThread());
+
+ for (ServerBoundCertMap::iterator it = server_bound_certs_.begin();
+ it != server_bound_certs_.end(); ++it) {
+ delete it->second;
+ }
+ server_bound_certs_.clear();
+}
+
+void DefaultServerBoundCertStore::InitStore() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(store_.get()) << "Store must exist to initialize";
+ DCHECK(!loaded_);
+
+ store_->Load(base::Bind(&DefaultServerBoundCertStore::OnLoaded,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DefaultServerBoundCertStore::OnLoaded(
+ scoped_ptr<ScopedVector<ServerBoundCert> > certs) {
+ DCHECK(CalledOnValidThread());
+
+ for (std::vector<ServerBoundCert*>::const_iterator it = certs->begin();
+ it != certs->end(); ++it) {
+ DCHECK(server_bound_certs_.find((*it)->server_identifier()) ==
+ server_bound_certs_.end());
+ server_bound_certs_[(*it)->server_identifier()] = *it;
+ }
+ certs->weak_clear();
+
+ loaded_ = true;
+
+ base::TimeDelta wait_time;
+ if (!waiting_tasks_.empty())
+ wait_time = base::TimeTicks::Now() - waiting_tasks_start_time_;
+ DVLOG(1) << "Task delay " << wait_time.InMilliseconds();
+ UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.TaskMaxWaitTime",
+ wait_time,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(1),
+ 50);
+ UMA_HISTOGRAM_COUNTS_100("DomainBoundCerts.TaskWaitCount",
+ waiting_tasks_.size());
+
+
+ for (ScopedVector<Task>::iterator i = waiting_tasks_.begin();
+ i != waiting_tasks_.end(); ++i)
+ (*i)->Run(this);
+ waiting_tasks_.clear();
+}
+
+void DefaultServerBoundCertStore::SyncSetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+
+ InternalDeleteServerBoundCert(server_identifier);
+ InternalInsertServerBoundCert(
+ server_identifier,
+ new ServerBoundCert(
+ server_identifier, creation_time, expiration_time, private_key,
+ cert));
+}
+
+void DefaultServerBoundCertStore::SyncDeleteServerBoundCert(
+ const std::string& server_identifier) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+ InternalDeleteServerBoundCert(server_identifier);
+}
+
+void DefaultServerBoundCertStore::SyncDeleteAllCreatedBetween(
+ base::Time delete_begin,
+ base::Time delete_end) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+ for (ServerBoundCertMap::iterator it = server_bound_certs_.begin();
+ it != server_bound_certs_.end();) {
+ ServerBoundCertMap::iterator cur = it;
+ ++it;
+ ServerBoundCert* cert = cur->second;
+ if ((delete_begin.is_null() || cert->creation_time() >= delete_begin) &&
+ (delete_end.is_null() || cert->creation_time() < delete_end)) {
+ if (store_.get())
+ store_->DeleteServerBoundCert(*cert);
+ delete cert;
+ server_bound_certs_.erase(cur);
+ }
+ }
+}
+
+void DefaultServerBoundCertStore::SyncGetAllServerBoundCerts(
+ ServerBoundCertList* cert_list) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+ for (ServerBoundCertMap::iterator it = server_bound_certs_.begin();
+ it != server_bound_certs_.end(); ++it)
+ cert_list->push_back(*it->second);
+}
+
+void DefaultServerBoundCertStore::EnqueueTask(scoped_ptr<Task> task) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!loaded_);
+ if (waiting_tasks_.empty())
+ waiting_tasks_start_time_ = base::TimeTicks::Now();
+ waiting_tasks_.push_back(task.release());
+}
+
+void DefaultServerBoundCertStore::RunOrEnqueueTask(scoped_ptr<Task> task) {
+ DCHECK(CalledOnValidThread());
+ InitIfNecessary();
+
+ if (!loaded_) {
+ EnqueueTask(task.Pass());
+ return;
+ }
+
+ task->Run(this);
+}
+
+void DefaultServerBoundCertStore::InternalDeleteServerBoundCert(
+ const std::string& server_identifier) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+
+ ServerBoundCertMap::iterator it = server_bound_certs_.find(server_identifier);
+ if (it == server_bound_certs_.end())
+ return; // There is nothing to delete.
+
+ ServerBoundCert* cert = it->second;
+ if (store_.get())
+ store_->DeleteServerBoundCert(*cert);
+ server_bound_certs_.erase(it);
+ delete cert;
+}
+
+void DefaultServerBoundCertStore::InternalInsertServerBoundCert(
+ const std::string& server_identifier,
+ ServerBoundCert* cert) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(loaded_);
+
+ if (store_.get())
+ store_->AddServerBoundCert(*cert);
+ server_bound_certs_[server_identifier] = cert;
+}
+
+DefaultServerBoundCertStore::PersistentStore::PersistentStore() {}
+
+DefaultServerBoundCertStore::PersistentStore::~PersistentStore() {}
+
+} // namespace net
diff --git a/chromium/net/ssl/default_server_bound_cert_store.h b/chromium/net/ssl/default_server_bound_cert_store.h
new file mode 100644
index 00000000000..61282182f8e
--- /dev/null
+++ b/chromium/net/ssl/default_server_bound_cert_store.h
@@ -0,0 +1,192 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_DEFAULT_SERVER_BOUND_CERT_STORE_H_
+#define NET_SSL_DEFAULT_SERVER_BOUND_CERT_STORE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/ssl/server_bound_cert_store.h"
+
+namespace net {
+
+// This class is the system for storing and retrieving server bound certs.
+// Modeled after the CookieMonster class, it has an in-memory cert store,
+// and synchronizes server bound certs to an optional permanent storage that
+// implements the PersistentStore interface. The use case is described in
+// http://balfanz.github.com/tls-obc-spec/draft-balfanz-tls-obc-00.html
+class NET_EXPORT DefaultServerBoundCertStore : public ServerBoundCertStore {
+ public:
+ class PersistentStore;
+
+ // The key for each ServerBoundCert* in ServerBoundCertMap is the
+ // corresponding server.
+ typedef std::map<std::string, ServerBoundCert*> ServerBoundCertMap;
+
+ // The store passed in should not have had Init() called on it yet. This
+ // class will take care of initializing it. The backing store is NOT owned by
+ // this class, but it must remain valid for the duration of the
+ // DefaultServerBoundCertStore's existence. If |store| is NULL, then no
+ // backing store will be updated.
+ explicit DefaultServerBoundCertStore(PersistentStore* store);
+
+ virtual ~DefaultServerBoundCertStore();
+
+ // ServerBoundCertStore implementation.
+ virtual int GetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time* expiration_time,
+ std::string* private_key_result,
+ std::string* cert_result,
+ const GetCertCallback& callback) OVERRIDE;
+ virtual void SetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) OVERRIDE;
+ virtual void DeleteServerBoundCert(
+ const std::string& server_identifier,
+ const base::Closure& callback) OVERRIDE;
+ virtual void DeleteAllCreatedBetween(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& callback) OVERRIDE;
+ virtual void DeleteAll(const base::Closure& callback) OVERRIDE;
+ virtual void GetAllServerBoundCerts(
+ const GetCertListCallback& callback) OVERRIDE;
+ virtual int GetCertCount() OVERRIDE;
+ virtual void SetForceKeepSessionState() OVERRIDE;
+
+ private:
+ class Task;
+ class GetServerBoundCertTask;
+ class SetServerBoundCertTask;
+ class DeleteServerBoundCertTask;
+ class DeleteAllCreatedBetweenTask;
+ class GetAllServerBoundCertsTask;
+
+ static const size_t kMaxCerts;
+
+ // Deletes all of the certs. Does not delete them from |store_|.
+ void DeleteAllInMemory();
+
+ // Called by all non-static functions to ensure that the cert store has
+ // been initialized.
+ // TODO(mattm): since we load asynchronously now, maybe we should start
+ // loading immediately on construction, or provide some method to initiate
+ // loading?
+ void InitIfNecessary() {
+ if (!initialized_) {
+ if (store_.get()) {
+ InitStore();
+ } else {
+ loaded_ = true;
+ }
+ initialized_ = true;
+ }
+ }
+
+ // Initializes the backing store and reads existing certs from it.
+ // Should only be called by InitIfNecessary().
+ void InitStore();
+
+ // Callback for backing store loading completion.
+ void OnLoaded(scoped_ptr<ScopedVector<ServerBoundCert> > certs);
+
+ // Syncronous methods which do the actual work. Can only be called after
+ // initialization is complete.
+ void SyncSetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert);
+ void SyncDeleteServerBoundCert(const std::string& server_identifier);
+ void SyncDeleteAllCreatedBetween(base::Time delete_begin,
+ base::Time delete_end);
+ void SyncGetAllServerBoundCerts(ServerBoundCertList* cert_list);
+
+ // Add |task| to |waiting_tasks_|.
+ void EnqueueTask(scoped_ptr<Task> task);
+ // If already initialized, run |task| immediately. Otherwise add it to
+ // |waiting_tasks_|.
+ void RunOrEnqueueTask(scoped_ptr<Task> task);
+
+ // Deletes the cert for the specified server, if such a cert exists, from the
+ // in-memory store. Deletes it from |store_| if |store_| is not NULL.
+ void InternalDeleteServerBoundCert(const std::string& server);
+
+ // Takes ownership of *cert.
+ // Adds the cert for the specified server to the in-memory store. Deletes it
+ // from |store_| if |store_| is not NULL.
+ void InternalInsertServerBoundCert(const std::string& server_identifier,
+ ServerBoundCert* cert);
+
+ // Indicates whether the cert store has been initialized. This happens
+ // lazily in InitIfNecessary().
+ bool initialized_;
+
+ // Indicates whether loading from the backend store is completed and
+ // calls may be immediately processed.
+ bool loaded_;
+
+ // Tasks that are waiting to be run once we finish loading.
+ ScopedVector<Task> waiting_tasks_;
+ base::TimeTicks waiting_tasks_start_time_;
+
+ scoped_refptr<PersistentStore> store_;
+
+ ServerBoundCertMap server_bound_certs_;
+
+ base::WeakPtrFactory<DefaultServerBoundCertStore> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultServerBoundCertStore);
+};
+
+typedef base::RefCountedThreadSafe<DefaultServerBoundCertStore::PersistentStore>
+ RefcountedPersistentStore;
+
+class NET_EXPORT DefaultServerBoundCertStore::PersistentStore
+ : public RefcountedPersistentStore {
+ public:
+ typedef base::Callback<void(scoped_ptr<ScopedVector<ServerBoundCert> >)>
+ LoadedCallback;
+
+ // Initializes the store and retrieves the existing certs. This will be
+ // called only once at startup. Note that the certs are individually allocated
+ // and that ownership is transferred to the caller upon return.
+ // The |loaded_callback| must not be called synchronously.
+ virtual void Load(const LoadedCallback& loaded_callback) = 0;
+
+ virtual void AddServerBoundCert(const ServerBoundCert& cert) = 0;
+
+ virtual void DeleteServerBoundCert(const ServerBoundCert& cert) = 0;
+
+ // When invoked, instructs the store to keep session related data on
+ // destruction.
+ virtual void SetForceKeepSessionState() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<PersistentStore>;
+
+ PersistentStore();
+ virtual ~PersistentStore();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PersistentStore);
+};
+
+} // namespace net
+
+#endif // NET_SSL_DEFAULT_SERVER_BOUND_CERT_STORE_H_
diff --git a/chromium/net/ssl/default_server_bound_cert_store_unittest.cc b/chromium/net/ssl/default_server_bound_cert_store_unittest.cc
new file mode 100644
index 00000000000..c3f8452fa9a
--- /dev/null
+++ b/chromium/net/ssl/default_server_bound_cert_store_unittest.cc
@@ -0,0 +1,559 @@
+// Copyright (c) 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.
+
+#include "net/ssl/default_server_bound_cert_store.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+void CallCounter(int* counter) {
+ (*counter)++;
+}
+
+void NotCalled() {
+ ADD_FAILURE() << "Unexpected callback execution.";
+}
+
+void GetCertCallbackNotCalled(int err,
+ const std::string& server_identifier,
+ base::Time expiration_time,
+ const std::string& private_key_result,
+ const std::string& cert_result) {
+ ADD_FAILURE() << "Unexpected callback execution.";
+}
+
+class AsyncGetCertHelper {
+ public:
+ AsyncGetCertHelper() : called_(false) {}
+
+ void Callback(int err,
+ const std::string& server_identifier,
+ base::Time expiration_time,
+ const std::string& private_key_result,
+ const std::string& cert_result) {
+ err_ = err;
+ server_identifier_ = server_identifier;
+ expiration_time_ = expiration_time;
+ private_key_ = private_key_result;
+ cert_ = cert_result;
+ called_ = true;
+ }
+
+ int err_;
+ std::string server_identifier_;
+ base::Time expiration_time_;
+ std::string private_key_;
+ std::string cert_;
+ bool called_;
+};
+
+void GetAllCallback(
+ ServerBoundCertStore::ServerBoundCertList* dest,
+ const ServerBoundCertStore::ServerBoundCertList& result) {
+ *dest = result;
+}
+
+class MockPersistentStore
+ : public DefaultServerBoundCertStore::PersistentStore {
+ public:
+ MockPersistentStore();
+
+ // DefaultServerBoundCertStore::PersistentStore implementation.
+ virtual void Load(const LoadedCallback& loaded_callback) OVERRIDE;
+ virtual void AddServerBoundCert(
+ const DefaultServerBoundCertStore::ServerBoundCert& cert) OVERRIDE;
+ virtual void DeleteServerBoundCert(
+ const DefaultServerBoundCertStore::ServerBoundCert& cert) OVERRIDE;
+ virtual void SetForceKeepSessionState() OVERRIDE;
+
+ protected:
+ virtual ~MockPersistentStore();
+
+ private:
+ typedef std::map<std::string, DefaultServerBoundCertStore::ServerBoundCert>
+ ServerBoundCertMap;
+
+ ServerBoundCertMap origin_certs_;
+};
+
+MockPersistentStore::MockPersistentStore() {}
+
+void MockPersistentStore::Load(const LoadedCallback& loaded_callback) {
+ scoped_ptr<ScopedVector<DefaultServerBoundCertStore::ServerBoundCert> >
+ certs(new ScopedVector<DefaultServerBoundCertStore::ServerBoundCert>());
+ ServerBoundCertMap::iterator it;
+
+ for (it = origin_certs_.begin(); it != origin_certs_.end(); ++it) {
+ certs->push_back(
+ new DefaultServerBoundCertStore::ServerBoundCert(it->second));
+ }
+
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(loaded_callback, base::Passed(&certs)));
+}
+
+void MockPersistentStore::AddServerBoundCert(
+ const DefaultServerBoundCertStore::ServerBoundCert& cert) {
+ origin_certs_[cert.server_identifier()] = cert;
+}
+
+void MockPersistentStore::DeleteServerBoundCert(
+ const DefaultServerBoundCertStore::ServerBoundCert& cert) {
+ origin_certs_.erase(cert.server_identifier());
+}
+
+void MockPersistentStore::SetForceKeepSessionState() {}
+
+MockPersistentStore::~MockPersistentStore() {}
+
+} // namespace
+
+TEST(DefaultServerBoundCertStoreTest, TestLoading) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+
+ persistent_store->AddServerBoundCert(
+ DefaultServerBoundCertStore::ServerBoundCert(
+ "google.com",
+ base::Time(),
+ base::Time(),
+ "a", "b"));
+ persistent_store->AddServerBoundCert(
+ DefaultServerBoundCertStore::ServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "c", "d"));
+
+ // Make sure certs load properly.
+ DefaultServerBoundCertStore store(persistent_store.get());
+ // Load has not occurred yet.
+ EXPECT_EQ(0, store.GetCertCount());
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "e", "f");
+ // Wait for load & queued set task.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2, store.GetCertCount());
+ store.SetServerBoundCert(
+ "twitter.com",
+ base::Time(),
+ base::Time(),
+ "g", "h");
+ // Set should be synchronous now that load is done.
+ EXPECT_EQ(3, store.GetCertCount());
+}
+
+//TODO(mattm): add more tests of without a persistent store?
+TEST(DefaultServerBoundCertStoreTest, TestSettingAndGetting) {
+ // No persistent store, all calls will be synchronous.
+ DefaultServerBoundCertStore store(NULL);
+ base::Time expiration_time;
+ std::string private_key, cert;
+ EXPECT_EQ(0, store.GetCertCount());
+ EXPECT_EQ(ERR_FILE_NOT_FOUND,
+ store.GetServerBoundCert("verisign.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+ EXPECT_TRUE(private_key.empty());
+ EXPECT_TRUE(cert.empty());
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time::FromInternalValue(123),
+ base::Time::FromInternalValue(456),
+ "i", "j");
+ EXPECT_EQ(OK,
+ store.GetServerBoundCert("verisign.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+ EXPECT_EQ(456, expiration_time.ToInternalValue());
+ EXPECT_EQ("i", private_key);
+ EXPECT_EQ("j", cert);
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestDuplicateCerts) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ base::Time expiration_time;
+ std::string private_key, cert;
+ EXPECT_EQ(0, store.GetCertCount());
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time::FromInternalValue(123),
+ base::Time::FromInternalValue(1234),
+ "a", "b");
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time::FromInternalValue(456),
+ base::Time::FromInternalValue(4567),
+ "c", "d");
+
+ // Wait for load & queued set tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, store.GetCertCount());
+ EXPECT_EQ(OK,
+ store.GetServerBoundCert("verisign.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+ EXPECT_EQ(4567, expiration_time.ToInternalValue());
+ EXPECT_EQ("c", private_key);
+ EXPECT_EQ("d", cert);
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestAsyncGet) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "verisign.com",
+ base::Time::FromInternalValue(123),
+ base::Time::FromInternalValue(1234),
+ "a", "b"));
+
+ DefaultServerBoundCertStore store(persistent_store.get());
+ AsyncGetCertHelper helper;
+ base::Time expiration_time;
+ std::string private_key;
+ std::string cert = "not set";
+ EXPECT_EQ(0, store.GetCertCount());
+ EXPECT_EQ(ERR_IO_PENDING,
+ store.GetServerBoundCert("verisign.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&AsyncGetCertHelper::Callback,
+ base::Unretained(&helper))));
+
+ // Wait for load & queued get tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, store.GetCertCount());
+ EXPECT_EQ("not set", cert);
+ EXPECT_TRUE(helper.called_);
+ EXPECT_EQ(OK, helper.err_);
+ EXPECT_EQ("verisign.com", helper.server_identifier_);
+ EXPECT_EQ(1234, helper.expiration_time_.ToInternalValue());
+ EXPECT_EQ("a", helper.private_key_);
+ EXPECT_EQ("b", helper.cert_);
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestDeleteAll) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "a", "b");
+ store.SetServerBoundCert(
+ "google.com",
+ base::Time(),
+ base::Time(),
+ "c", "d");
+ store.SetServerBoundCert(
+ "harvard.com",
+ base::Time(),
+ base::Time(),
+ "e", "f");
+ // Wait for load & queued set tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(3, store.GetCertCount());
+ int delete_finished = 0;
+ store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
+ ASSERT_EQ(1, delete_finished);
+ EXPECT_EQ(0, store.GetCertCount());
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestAsyncGetAndDeleteAll) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "a", "b"));
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "google.com",
+ base::Time(),
+ base::Time(),
+ "c", "d"));
+
+ ServerBoundCertStore::ServerBoundCertList pre_certs;
+ ServerBoundCertStore::ServerBoundCertList post_certs;
+ int delete_finished = 0;
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &pre_certs));
+ store.DeleteAll(base::Bind(&CallCounter, &delete_finished));
+ store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &post_certs));
+ // Tasks have not run yet.
+ EXPECT_EQ(0u, pre_certs.size());
+ // Wait for load & queued tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, store.GetCertCount());
+ EXPECT_EQ(2u, pre_certs.size());
+ EXPECT_EQ(0u, post_certs.size());
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestDelete) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ base::Time expiration_time;
+ std::string private_key, cert;
+ EXPECT_EQ(0, store.GetCertCount());
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "a", "b");
+ // Wait for load & queued set task.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ store.SetServerBoundCert(
+ "google.com",
+ base::Time(),
+ base::Time(),
+ "c", "d");
+
+ EXPECT_EQ(2, store.GetCertCount());
+ int delete_finished = 0;
+ store.DeleteServerBoundCert("verisign.com",
+ base::Bind(&CallCounter, &delete_finished));
+ ASSERT_EQ(1, delete_finished);
+ EXPECT_EQ(1, store.GetCertCount());
+ EXPECT_EQ(ERR_FILE_NOT_FOUND,
+ store.GetServerBoundCert("verisign.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+ EXPECT_EQ(OK,
+ store.GetServerBoundCert("google.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+ int delete2_finished = 0;
+ store.DeleteServerBoundCert("google.com",
+ base::Bind(&CallCounter, &delete2_finished));
+ ASSERT_EQ(1, delete2_finished);
+ EXPECT_EQ(0, store.GetCertCount());
+ EXPECT_EQ(ERR_FILE_NOT_FOUND,
+ store.GetServerBoundCert("google.com",
+ &expiration_time,
+ &private_key,
+ &cert,
+ base::Bind(&GetCertCallbackNotCalled)));
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestAsyncDelete) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "a.com",
+ base::Time::FromInternalValue(1),
+ base::Time::FromInternalValue(2),
+ "a", "b"));
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "b.com",
+ base::Time::FromInternalValue(3),
+ base::Time::FromInternalValue(4),
+ "c", "d"));
+ DefaultServerBoundCertStore store(persistent_store.get());
+ int delete_finished = 0;
+ store.DeleteServerBoundCert("a.com",
+ base::Bind(&CallCounter, &delete_finished));
+
+ AsyncGetCertHelper a_helper;
+ AsyncGetCertHelper b_helper;
+ base::Time expiration_time;
+ std::string private_key;
+ std::string cert = "not set";
+ EXPECT_EQ(0, store.GetCertCount());
+ EXPECT_EQ(ERR_IO_PENDING,
+ store.GetServerBoundCert(
+ "a.com", &expiration_time, &private_key, &cert,
+ base::Bind(&AsyncGetCertHelper::Callback,
+ base::Unretained(&a_helper))));
+ EXPECT_EQ(ERR_IO_PENDING,
+ store.GetServerBoundCert(
+ "b.com", &expiration_time, &private_key, &cert,
+ base::Bind(&AsyncGetCertHelper::Callback,
+ base::Unretained(&b_helper))));
+
+ EXPECT_EQ(0, delete_finished);
+ EXPECT_FALSE(a_helper.called_);
+ EXPECT_FALSE(b_helper.called_);
+ // Wait for load & queued tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, delete_finished);
+ EXPECT_EQ(1, store.GetCertCount());
+ EXPECT_EQ("not set", cert);
+ EXPECT_TRUE(a_helper.called_);
+ EXPECT_EQ(ERR_FILE_NOT_FOUND, a_helper.err_);
+ EXPECT_EQ("a.com", a_helper.server_identifier_);
+ EXPECT_EQ(0, a_helper.expiration_time_.ToInternalValue());
+ EXPECT_EQ("", a_helper.private_key_);
+ EXPECT_EQ("", a_helper.cert_);
+ EXPECT_TRUE(b_helper.called_);
+ EXPECT_EQ(OK, b_helper.err_);
+ EXPECT_EQ("b.com", b_helper.server_identifier_);
+ EXPECT_EQ(4, b_helper.expiration_time_.ToInternalValue());
+ EXPECT_EQ("c", b_helper.private_key_);
+ EXPECT_EQ("d", b_helper.cert_);
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestGetAll) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ EXPECT_EQ(0, store.GetCertCount());
+ store.SetServerBoundCert(
+ "verisign.com",
+ base::Time(),
+ base::Time(),
+ "a", "b");
+ store.SetServerBoundCert(
+ "google.com",
+ base::Time(),
+ base::Time(),
+ "c", "d");
+ store.SetServerBoundCert(
+ "harvard.com",
+ base::Time(),
+ base::Time(),
+ "e", "f");
+ store.SetServerBoundCert(
+ "mit.com",
+ base::Time(),
+ base::Time(),
+ "g", "h");
+ // Wait for load & queued set tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(4, store.GetCertCount());
+ ServerBoundCertStore::ServerBoundCertList certs;
+ store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
+ EXPECT_EQ(4u, certs.size());
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestInitializeFrom) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ DefaultServerBoundCertStore store(persistent_store.get());
+
+ store.SetServerBoundCert(
+ "preexisting.com",
+ base::Time(),
+ base::Time(),
+ "a", "b");
+ store.SetServerBoundCert(
+ "both.com",
+ base::Time(),
+ base::Time(),
+ "c", "d");
+ // Wait for load & queued set tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2, store.GetCertCount());
+
+ ServerBoundCertStore::ServerBoundCertList source_certs;
+ source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
+ "both.com",
+ base::Time(),
+ base::Time(),
+ // Key differs from above to test that existing entries are overwritten.
+ "e", "f"));
+ source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
+ "copied.com",
+ base::Time(),
+ base::Time(),
+ "g", "h"));
+ store.InitializeFrom(source_certs);
+ EXPECT_EQ(3, store.GetCertCount());
+
+ ServerBoundCertStore::ServerBoundCertList certs;
+ store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
+ ASSERT_EQ(3u, certs.size());
+
+ ServerBoundCertStore::ServerBoundCertList::iterator cert = certs.begin();
+ EXPECT_EQ("both.com", cert->server_identifier());
+ EXPECT_EQ("e", cert->private_key());
+
+ ++cert;
+ EXPECT_EQ("copied.com", cert->server_identifier());
+ EXPECT_EQ("g", cert->private_key());
+
+ ++cert;
+ EXPECT_EQ("preexisting.com", cert->server_identifier());
+ EXPECT_EQ("a", cert->private_key());
+}
+
+TEST(DefaultServerBoundCertStoreTest, TestAsyncInitializeFrom) {
+ scoped_refptr<MockPersistentStore> persistent_store(new MockPersistentStore);
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "preexisting.com",
+ base::Time(),
+ base::Time(),
+ "a", "b"));
+ persistent_store->AddServerBoundCert(ServerBoundCertStore::ServerBoundCert(
+ "both.com",
+ base::Time(),
+ base::Time(),
+ "c", "d"));
+
+ DefaultServerBoundCertStore store(persistent_store.get());
+ ServerBoundCertStore::ServerBoundCertList source_certs;
+ source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
+ "both.com",
+ base::Time(),
+ base::Time(),
+ // Key differs from above to test that existing entries are overwritten.
+ "e", "f"));
+ source_certs.push_back(ServerBoundCertStore::ServerBoundCert(
+ "copied.com",
+ base::Time(),
+ base::Time(),
+ "g", "h"));
+ store.InitializeFrom(source_certs);
+ EXPECT_EQ(0, store.GetCertCount());
+ // Wait for load & queued tasks.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(3, store.GetCertCount());
+
+ ServerBoundCertStore::ServerBoundCertList certs;
+ store.GetAllServerBoundCerts(base::Bind(GetAllCallback, &certs));
+ ASSERT_EQ(3u, certs.size());
+
+ ServerBoundCertStore::ServerBoundCertList::iterator cert = certs.begin();
+ EXPECT_EQ("both.com", cert->server_identifier());
+ EXPECT_EQ("e", cert->private_key());
+
+ ++cert;
+ EXPECT_EQ("copied.com", cert->server_identifier());
+ EXPECT_EQ("g", cert->private_key());
+
+ ++cert;
+ EXPECT_EQ("preexisting.com", cert->server_identifier());
+ EXPECT_EQ("a", cert->private_key());
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/openssl_client_key_store.cc b/chromium/net/ssl/openssl_client_key_store.cc
new file mode 100644
index 00000000000..9ea044e3fde
--- /dev/null
+++ b/chromium/net/ssl/openssl_client_key_store.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 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/ssl/openssl_client_key_store.h"
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+namespace {
+
+typedef OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY;
+
+// Increment the reference count of a given EVP_PKEY. This function
+// is similar to EVP_PKEY_dup which is not available from the OpenSSL
+// version used by Chromium at the moment. Its name is distinct to
+// avoid compiler warnings about ambiguous function calls at caller
+// sites.
+EVP_PKEY* CopyEVP_PKEY(EVP_PKEY* key) {
+ if (key)
+ CRYPTO_add(&key->references, 1, CRYPTO_LOCK_EVP_PKEY);
+ return key;
+}
+
+// Return the EVP_PKEY holding the public key of a given certificate.
+// |cert| is a certificate.
+// Returns a scoped EVP_PKEY for it.
+ScopedEVP_PKEY GetOpenSSLPublicKey(const X509Certificate* cert) {
+ // X509_PUBKEY_get() increments the reference count of its result.
+ // Unlike X509_get_X509_PUBKEY() which simply returns a direct pointer.
+ EVP_PKEY* pkey =
+ X509_PUBKEY_get(X509_get_X509_PUBKEY(cert->os_cert_handle()));
+ if (!pkey)
+ LOG(ERROR) << "Can't extract private key from certificate!";
+ return ScopedEVP_PKEY(pkey);
+}
+
+} // namespace
+
+OpenSSLClientKeyStore::OpenSSLClientKeyStore() {
+}
+
+OpenSSLClientKeyStore::~OpenSSLClientKeyStore() {
+}
+
+OpenSSLClientKeyStore::KeyPair::KeyPair(EVP_PKEY* pub_key,
+ EVP_PKEY* priv_key) {
+ public_key = CopyEVP_PKEY(pub_key);
+ private_key = CopyEVP_PKEY(priv_key);
+}
+
+OpenSSLClientKeyStore::KeyPair::~KeyPair() {
+ EVP_PKEY_free(public_key);
+ EVP_PKEY_free(private_key);
+}
+
+OpenSSLClientKeyStore::KeyPair::KeyPair(const KeyPair& other) {
+ public_key = CopyEVP_PKEY(other.public_key);
+ private_key = CopyEVP_PKEY(other.private_key);
+}
+
+void OpenSSLClientKeyStore::KeyPair::operator=(const KeyPair& other) {
+ EVP_PKEY* old_public_key = public_key;
+ EVP_PKEY* old_private_key = private_key;
+ public_key = CopyEVP_PKEY(other.public_key);
+ private_key = CopyEVP_PKEY(other.private_key);
+ EVP_PKEY_free(old_private_key);
+ EVP_PKEY_free(old_public_key);
+}
+
+int OpenSSLClientKeyStore::FindKeyPairIndex(EVP_PKEY* public_key) {
+ if (!public_key)
+ return -1;
+ for (size_t n = 0; n < pairs_.size(); ++n) {
+ if (EVP_PKEY_cmp(pairs_[n].public_key, public_key) == 1)
+ return static_cast<int>(n);
+ }
+ return -1;
+}
+
+void OpenSSLClientKeyStore::AddKeyPair(EVP_PKEY* pub_key,
+ EVP_PKEY* private_key) {
+ int index = FindKeyPairIndex(pub_key);
+ if (index < 0)
+ pairs_.push_back(KeyPair(pub_key, private_key));
+}
+
+// Common code for OpenSSLClientKeyStore. Shared by all OpenSSL-based
+// builds.
+bool OpenSSLClientKeyStore::RecordClientCertPrivateKey(
+ const X509Certificate* client_cert,
+ EVP_PKEY* private_key) {
+ // Sanity check.
+ if (!client_cert || !private_key)
+ return false;
+
+ // Get public key from certificate.
+ ScopedEVP_PKEY pub_key(GetOpenSSLPublicKey(client_cert));
+ if (!pub_key.get())
+ return false;
+
+ AddKeyPair(pub_key.get(), private_key);
+ return true;
+}
+
+bool OpenSSLClientKeyStore::FetchClientCertPrivateKey(
+ const X509Certificate* client_cert,
+ ScopedEVP_PKEY* private_key) {
+ if (!client_cert)
+ return false;
+
+ ScopedEVP_PKEY pub_key(GetOpenSSLPublicKey(client_cert));
+ if (!pub_key.get())
+ return false;
+
+ int index = FindKeyPairIndex(pub_key.get());
+ if (index < 0)
+ return false;
+
+ private_key->reset(CopyEVP_PKEY(pairs_[index].private_key));
+ return true;
+}
+
+void OpenSSLClientKeyStore::Flush() {
+ pairs_.clear();
+}
+
+OpenSSLClientKeyStore* OpenSSLClientKeyStore::GetInstance() {
+ return Singleton<OpenSSLClientKeyStore>::get();
+}
+
+} // namespace net
+
+
diff --git a/chromium/net/ssl/openssl_client_key_store.h b/chromium/net/ssl/openssl_client_key_store.h
new file mode 100644
index 00000000000..6d90253ff14
--- /dev/null
+++ b/chromium/net/ssl/openssl_client_key_store.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2013 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_SSL_OPENSSL_CLIENT_KEY_STORE_H_
+#define NET_SSL_OPENSSL_CLIENT_KEY_STORE_H_
+
+#include <openssl/evp.h>
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "crypto/openssl_util.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class X509Certificate;
+
+// OpenSSLClientKeyStore implements an in-memory store for client
+// certificate private keys, because the platforms where OpenSSL is
+// used do not provide a way to retrieve the private key of a known
+// certificate.
+//
+// This class is not thread-safe and should only be used from the network
+// thread.
+class NET_EXPORT OpenSSLClientKeyStore {
+ public:
+ // Platforms must define this factory function as appropriate.
+ static OpenSSLClientKeyStore* GetInstance();
+
+ struct EVP_PKEY_Deleter {
+ inline void operator()(EVP_PKEY* ptr) const {
+ EVP_PKEY_free(ptr);
+ }
+ };
+
+ typedef scoped_ptr<EVP_PKEY, EVP_PKEY_Deleter> ScopedEVP_PKEY;
+
+ // Record the association between a certificate and its
+ // private key. This method should be called _before_
+ // FetchClientCertPrivateKey to ensure that the private key is returned
+ // when it is called later. The association is recorded in memory
+ // exclusively.
+ // |cert| is a handle to a certificate object.
+ // |private_key| is an OpenSSL EVP_PKEY that corresponds to the
+ // certificate's private key.
+ // Returns false if an error occured.
+ // This function does not take ownership of the private_key, but may
+ // increment its internal reference count.
+ NET_EXPORT bool RecordClientCertPrivateKey(const X509Certificate* cert,
+ EVP_PKEY* private_key);
+
+ // Given a certificate's |public_key|, return the corresponding private
+ // key that has been recorded previously by RecordClientCertPrivateKey().
+ // |cert| is a client certificate.
+ // |*private_key| will be reset to its matching private key on success.
+ // Returns true on success, false otherwise. This increments the reference
+ // count of the private key on success.
+ bool FetchClientCertPrivateKey(const X509Certificate* cert,
+ ScopedEVP_PKEY* private_key);
+
+ // Flush all recorded keys. Used only during testing.
+ void Flush();
+
+ protected:
+ OpenSSLClientKeyStore();
+
+ ~OpenSSLClientKeyStore();
+
+ // Adds a given public/private key pair.
+ // |pub_key| and |private_key| can point to the same object.
+ // This increments the reference count on both objects, caller
+ // must still call EVP_PKEY_free on them.
+ void AddKeyPair(EVP_PKEY* pub_key, EVP_PKEY* private_key);
+
+ private:
+ // KeyPair is an internal class used to hold a pair of private / public
+ // EVP_PKEY objects, with appropriate ownership.
+ class KeyPair {
+ public:
+ explicit KeyPair(EVP_PKEY* pub_key, EVP_PKEY* priv_key);
+ KeyPair(const KeyPair& other);
+ void operator=(const KeyPair& other);
+ ~KeyPair();
+
+ EVP_PKEY* public_key;
+ EVP_PKEY* private_key;
+
+ private:
+ KeyPair(); // intentionally not implemented.
+ };
+
+ // Returns the index of the keypair for |public_key|. or -1 if not found.
+ int FindKeyPairIndex(EVP_PKEY* public_key);
+
+ std::vector<KeyPair> pairs_;
+
+ friend struct DefaultSingletonTraits<OpenSSLClientKeyStore>;
+
+ DISALLOW_COPY_AND_ASSIGN(OpenSSLClientKeyStore);
+};
+
+} // namespace net
+
+#endif // NET_SSL_OPENSSL_CLIENT_KEY_STORE_H_
diff --git a/chromium/net/ssl/openssl_client_key_store_unittest.cc b/chromium/net/ssl/openssl_client_key_store_unittest.cc
new file mode 100644
index 00000000000..70d2d7a8cb3
--- /dev/null
+++ b/chromium/net/ssl/openssl_client_key_store_unittest.cc
@@ -0,0 +1,173 @@
+// Copyright (c) 2013 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/ssl/openssl_client_key_store.h"
+
+#include "base/memory/ref_counted.h"
+#include "net/base/test_data_directory.h"
+#include "net/test/cert_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+typedef OpenSSLClientKeyStore::ScopedEVP_PKEY ScopedEVP_PKEY;
+
+// Return the internal reference count of a given EVP_PKEY.
+int EVP_PKEY_get_refcount(EVP_PKEY* pkey) {
+ return pkey->references;
+}
+
+// A common test class to ensure that the store is flushed after
+// each test.
+class OpenSSLClientKeyStoreTest : public ::testing::Test {
+ public:
+ OpenSSLClientKeyStoreTest()
+ : store_(OpenSSLClientKeyStore::GetInstance()) {
+ }
+
+ virtual ~OpenSSLClientKeyStoreTest() {
+ if (store_)
+ store_->Flush();
+ }
+
+ protected:
+ OpenSSLClientKeyStore* store_;
+};
+
+// Check that GetInstance() returns non-null
+TEST_F(OpenSSLClientKeyStoreTest, GetInstance) {
+ ASSERT_TRUE(store_);
+}
+
+// Check that Flush() works correctly.
+TEST_F(OpenSSLClientKeyStoreTest, Flush) {
+ ASSERT_TRUE(store_);
+
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+
+ ScopedEVP_PKEY priv_key(EVP_PKEY_new());
+ ASSERT_TRUE(priv_key.get());
+
+ ASSERT_TRUE(store_->RecordClientCertPrivateKey(cert_1.get(),
+ priv_key.get()));
+
+ store_->Flush();
+
+ // Retrieve the private key. This should fail because the store
+ // was flushed.
+ ScopedEVP_PKEY pkey;
+ ASSERT_FALSE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey));
+ ASSERT_FALSE(pkey.get());
+}
+
+// Check that trying to retrieve the private key of an unknown certificate
+// simply fails by returning null.
+TEST_F(OpenSSLClientKeyStoreTest, FetchEmptyPrivateKey) {
+ ASSERT_TRUE(store_);
+
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+
+ // Retrieve the private key now. This should fail because it was
+ // never recorded in the store.
+ ScopedEVP_PKEY pkey;
+ ASSERT_FALSE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey));
+ ASSERT_FALSE(pkey.get());
+}
+
+// Check that any private key recorded through RecordClientCertPrivateKey
+// can be retrieved with FetchClientCertPrivateKey.
+TEST_F(OpenSSLClientKeyStoreTest, RecordAndFetchPrivateKey) {
+ ASSERT_TRUE(store_);
+
+ // Any certificate / key pair will do, the store is not supposed to
+ // check that the private and certificate public keys match. This is
+ // by design since the private EVP_PKEY could be a wrapper around a
+ // JNI reference, with no way to access the real private key bits.
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+
+ ScopedEVP_PKEY priv_key(EVP_PKEY_new());
+ ASSERT_TRUE(priv_key.get());
+ ASSERT_EQ(1, EVP_PKEY_get_refcount(priv_key.get()));
+
+ // Add the key a first time, this should increment its reference count.
+ ASSERT_TRUE(store_->RecordClientCertPrivateKey(cert_1.get(),
+ priv_key.get()));
+ ASSERT_EQ(2, EVP_PKEY_get_refcount(priv_key.get()));
+
+ // Two successive calls with the same certificate / private key shall
+ // also succeed, but the key's reference count should not be incremented.
+ ASSERT_TRUE(store_->RecordClientCertPrivateKey(cert_1.get(),
+ priv_key.get()));
+ ASSERT_EQ(2, EVP_PKEY_get_refcount(priv_key.get()));
+
+ // Retrieve the private key. This should increment the private key's
+ // reference count.
+ ScopedEVP_PKEY pkey2;
+ ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_1.get(), &pkey2));
+ ASSERT_EQ(pkey2.get(), priv_key.get());
+ ASSERT_EQ(3, EVP_PKEY_get_refcount(priv_key.get()));
+
+ // Flush the store explicitely, this should decrement the private
+ // key's reference count.
+ store_->Flush();
+ ASSERT_EQ(2, EVP_PKEY_get_refcount(priv_key.get()));
+}
+
+// Same test, but with two certificates / private keys.
+TEST_F(OpenSSLClientKeyStoreTest, RecordAndFetchTwoPrivateKeys) {
+ scoped_refptr<X509Certificate> cert_1(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_1.pem"));
+ ASSERT_TRUE(cert_1.get());
+
+ scoped_refptr<X509Certificate> cert_2(
+ ImportCertFromFile(GetTestCertsDirectory(), "client_2.pem"));
+ ASSERT_TRUE(cert_2.get());
+
+ ScopedEVP_PKEY priv_key1(EVP_PKEY_new());
+ ASSERT_TRUE(priv_key1.get());
+ ASSERT_EQ(1, EVP_PKEY_get_refcount(priv_key1.get()));
+
+ ScopedEVP_PKEY priv_key2(EVP_PKEY_new());
+ ASSERT_TRUE(priv_key2.get());
+ ASSERT_EQ(1, EVP_PKEY_get_refcount(priv_key2.get()));
+
+ ASSERT_NE(priv_key1.get(), priv_key2.get());
+
+ // Add the key a first time, this shall succeed, and increment the
+ // reference count.
+ EXPECT_TRUE(store_->RecordClientCertPrivateKey(cert_1.get(),
+ priv_key1.get()));
+ EXPECT_TRUE(store_->RecordClientCertPrivateKey(cert_2.get(),
+ priv_key2.get()));
+ EXPECT_EQ(2, EVP_PKEY_get_refcount(priv_key1.get()));
+ EXPECT_EQ(2, EVP_PKEY_get_refcount(priv_key2.get()));
+
+ // Retrieve the private key now. This shall succeed and increment
+ // the private key's reference count.
+ ScopedEVP_PKEY fetch_key1;
+ ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_1.get(),
+ &fetch_key1));
+ ScopedEVP_PKEY fetch_key2;
+ ASSERT_TRUE(store_->FetchClientCertPrivateKey(cert_2.get(),
+ &fetch_key2));
+ EXPECT_TRUE(fetch_key1.get());
+ EXPECT_TRUE(fetch_key2.get());
+
+ EXPECT_EQ(fetch_key1.get(), priv_key1.get());
+ EXPECT_EQ(fetch_key2.get(), priv_key2.get());
+
+ EXPECT_EQ(3, EVP_PKEY_get_refcount(priv_key1.get()));
+ EXPECT_EQ(3, EVP_PKEY_get_refcount(priv_key2.get()));
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/ssl/server_bound_cert_service.cc b/chromium/net/ssl/server_bound_cert_service.cc
new file mode 100644
index 00000000000..2bbcbc79e6b
--- /dev/null
+++ b/chromium/net/ssl/server_bound_cert_service.cc
@@ -0,0 +1,679 @@
+// Copyright (c) 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.
+
+#include "net/ssl/server_bound_cert_service.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/task_runner.h"
+#include "crypto/ec_private_key.h"
+#include "net/base/net_errors.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "url/gurl.h"
+
+#if defined(USE_NSS)
+#include <private/pprthred.h> // PR_DetachThread
+#endif
+
+namespace net {
+
+namespace {
+
+const int kKeySizeInBits = 1024;
+const int kValidityPeriodInDays = 365;
+// When we check the system time, we add this many days to the end of the check
+// so the result will still hold even after chrome has been running for a
+// while.
+const int kSystemTimeValidityBufferInDays = 90;
+
+// Used by the GetDomainBoundCertResult histogram to record the final
+// outcome of each GetDomainBoundCert or GetOrCreateDomainBoundCert call.
+// Do not re-use values.
+enum GetCertResult {
+ // Synchronously found and returned an existing domain bound cert.
+ SYNC_SUCCESS = 0,
+ // Retrieved or generated and returned a domain bound cert asynchronously.
+ ASYNC_SUCCESS = 1,
+ // Retrieval/generation request was cancelled before the cert generation
+ // completed.
+ ASYNC_CANCELLED = 2,
+ // Cert generation failed.
+ ASYNC_FAILURE_KEYGEN = 3,
+ ASYNC_FAILURE_CREATE_CERT = 4,
+ ASYNC_FAILURE_EXPORT_KEY = 5,
+ ASYNC_FAILURE_UNKNOWN = 6,
+ // GetDomainBoundCert or GetOrCreateDomainBoundCert was called with
+ // invalid arguments.
+ INVALID_ARGUMENT = 7,
+ // We don't support any of the cert types the server requested.
+ UNSUPPORTED_TYPE = 8,
+ // Server asked for a different type of certs while we were generating one.
+ TYPE_MISMATCH = 9,
+ // Couldn't start a worker to generate a cert.
+ WORKER_FAILURE = 10,
+ GET_CERT_RESULT_MAX
+};
+
+void RecordGetDomainBoundCertResult(GetCertResult result) {
+ UMA_HISTOGRAM_ENUMERATION("DomainBoundCerts.GetDomainBoundCertResult", result,
+ GET_CERT_RESULT_MAX);
+}
+
+void RecordGetCertTime(base::TimeDelta request_time) {
+ UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTime",
+ request_time,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(5),
+ 50);
+}
+
+// On success, returns a ServerBoundCert object and sets |*error| to OK.
+// Otherwise, returns NULL, and |*error| will be set to a net error code.
+// |serial_number| is passed in because base::RandInt cannot be called from an
+// unjoined thread, due to relying on a non-leaked LazyInstance
+scoped_ptr<ServerBoundCertStore::ServerBoundCert> GenerateCert(
+ const std::string& server_identifier,
+ uint32 serial_number,
+ int* error) {
+ scoped_ptr<ServerBoundCertStore::ServerBoundCert> result;
+
+ base::TimeTicks start = base::TimeTicks::Now();
+ base::Time not_valid_before = base::Time::Now();
+ base::Time not_valid_after =
+ not_valid_before + base::TimeDelta::FromDays(kValidityPeriodInDays);
+ std::string der_cert;
+ std::vector<uint8> private_key_info;
+ scoped_ptr<crypto::ECPrivateKey> key(crypto::ECPrivateKey::Create());
+ if (!key.get()) {
+ DLOG(ERROR) << "Unable to create key pair for client";
+ *error = ERR_KEY_GENERATION_FAILED;
+ return result.Pass();
+ }
+ if (!x509_util::CreateDomainBoundCertEC(key.get(), server_identifier,
+ serial_number, not_valid_before,
+ not_valid_after, &der_cert)) {
+ DLOG(ERROR) << "Unable to create x509 cert for client";
+ *error = ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED;
+ return result.Pass();
+ }
+
+ if (!key->ExportEncryptedPrivateKey(ServerBoundCertService::kEPKIPassword,
+ 1, &private_key_info)) {
+ DLOG(ERROR) << "Unable to export private key";
+ *error = ERR_PRIVATE_KEY_EXPORT_FAILED;
+ return result.Pass();
+ }
+
+ // TODO(rkn): Perhaps ExportPrivateKey should be changed to output a
+ // std::string* to prevent this copying.
+ std::string key_out(private_key_info.begin(), private_key_info.end());
+
+ result.reset(new ServerBoundCertStore::ServerBoundCert(
+ server_identifier,
+ not_valid_before,
+ not_valid_after,
+ key_out,
+ der_cert));
+ UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GenerateCertTime",
+ base::TimeTicks::Now() - start,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(5),
+ 50);
+ *error = OK;
+ return result.Pass();
+}
+
+} // namespace
+
+// Represents the output and result callback of a request.
+class ServerBoundCertServiceRequest {
+ public:
+ ServerBoundCertServiceRequest(base::TimeTicks request_start,
+ const CompletionCallback& callback,
+ std::string* private_key,
+ std::string* cert)
+ : request_start_(request_start),
+ callback_(callback),
+ private_key_(private_key),
+ cert_(cert) {
+ }
+
+ // Ensures that the result callback will never be made.
+ void Cancel() {
+ RecordGetDomainBoundCertResult(ASYNC_CANCELLED);
+ callback_.Reset();
+ private_key_ = NULL;
+ cert_ = NULL;
+ }
+
+ // Copies the contents of |private_key| and |cert| to the caller's output
+ // arguments and calls the callback.
+ void Post(int error,
+ const std::string& private_key,
+ const std::string& cert) {
+ switch (error) {
+ case OK: {
+ base::TimeDelta request_time = base::TimeTicks::Now() - request_start_;
+ UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.GetCertTimeAsync",
+ request_time,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(5),
+ 50);
+ RecordGetCertTime(request_time);
+ RecordGetDomainBoundCertResult(ASYNC_SUCCESS);
+ break;
+ }
+ case ERR_KEY_GENERATION_FAILED:
+ RecordGetDomainBoundCertResult(ASYNC_FAILURE_KEYGEN);
+ break;
+ case ERR_ORIGIN_BOUND_CERT_GENERATION_FAILED:
+ RecordGetDomainBoundCertResult(ASYNC_FAILURE_CREATE_CERT);
+ break;
+ case ERR_PRIVATE_KEY_EXPORT_FAILED:
+ RecordGetDomainBoundCertResult(ASYNC_FAILURE_EXPORT_KEY);
+ break;
+ case ERR_INSUFFICIENT_RESOURCES:
+ RecordGetDomainBoundCertResult(WORKER_FAILURE);
+ break;
+ default:
+ RecordGetDomainBoundCertResult(ASYNC_FAILURE_UNKNOWN);
+ break;
+ }
+ if (!callback_.is_null()) {
+ *private_key_ = private_key;
+ *cert_ = cert;
+ callback_.Run(error);
+ }
+ delete this;
+ }
+
+ bool canceled() const { return callback_.is_null(); }
+
+ private:
+ base::TimeTicks request_start_;
+ CompletionCallback callback_;
+ std::string* private_key_;
+ std::string* cert_;
+};
+
+// ServerBoundCertServiceWorker runs on a worker thread and takes care of the
+// blocking process of performing key generation. Will take care of deleting
+// itself once Start() is called.
+class ServerBoundCertServiceWorker {
+ public:
+ typedef base::Callback<void(
+ const std::string&,
+ int,
+ scoped_ptr<ServerBoundCertStore::ServerBoundCert>)> WorkerDoneCallback;
+
+ ServerBoundCertServiceWorker(
+ const std::string& server_identifier,
+ const WorkerDoneCallback& callback)
+ : server_identifier_(server_identifier),
+ serial_number_(base::RandInt(0, std::numeric_limits<int>::max())),
+ origin_loop_(base::MessageLoopProxy::current()),
+ callback_(callback) {
+ }
+
+ // Starts the worker on |task_runner|. If the worker fails to start, such as
+ // if the task runner is shutting down, then it will take care of deleting
+ // itself.
+ bool Start(const scoped_refptr<base::TaskRunner>& task_runner) {
+ DCHECK(origin_loop_->RunsTasksOnCurrentThread());
+
+ return task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&ServerBoundCertServiceWorker::Run, base::Owned(this)));
+ }
+
+ private:
+ void Run() {
+ // Runs on a worker thread.
+ int error = ERR_FAILED;
+ scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert =
+ GenerateCert(server_identifier_, serial_number_, &error);
+ DVLOG(1) << "GenerateCert " << server_identifier_ << " returned " << error;
+#if defined(USE_NSS)
+ // Detach the thread from NSPR.
+ // Calling NSS functions attaches the thread to NSPR, which stores
+ // the NSPR thread ID in thread-specific data.
+ // The threads in our thread pool terminate after we have called
+ // PR_Cleanup. Unless we detach them from NSPR, net_unittests gets
+ // segfaults on shutdown when the threads' thread-specific data
+ // destructors run.
+ PR_DetachThread();
+#endif
+ origin_loop_->PostTask(FROM_HERE,
+ base::Bind(callback_, server_identifier_, error,
+ base::Passed(&cert)));
+ }
+
+ const std::string server_identifier_;
+ // Note that serial_number_ must be initialized on a non-worker thread
+ // (see documentation for GenerateCert).
+ uint32 serial_number_;
+ scoped_refptr<base::SequencedTaskRunner> origin_loop_;
+ WorkerDoneCallback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerBoundCertServiceWorker);
+};
+
+// A ServerBoundCertServiceJob is a one-to-one counterpart of an
+// ServerBoundCertServiceWorker. It lives only on the ServerBoundCertService's
+// origin message loop.
+class ServerBoundCertServiceJob {
+ public:
+ ServerBoundCertServiceJob(bool create_if_missing)
+ : create_if_missing_(create_if_missing) {
+ }
+
+ ~ServerBoundCertServiceJob() {
+ if (!requests_.empty())
+ DeleteAllCanceled();
+ }
+
+ void AddRequest(ServerBoundCertServiceRequest* request,
+ bool create_if_missing = false) {
+ create_if_missing_ |= create_if_missing;
+ requests_.push_back(request);
+ }
+
+ void HandleResult(int error,
+ const std::string& private_key,
+ const std::string& cert) {
+ PostAll(error, private_key, cert);
+ }
+
+ bool CreateIfMissing() const { return create_if_missing_; }
+
+ private:
+ void PostAll(int error,
+ const std::string& private_key,
+ const std::string& cert) {
+ std::vector<ServerBoundCertServiceRequest*> requests;
+ requests_.swap(requests);
+
+ for (std::vector<ServerBoundCertServiceRequest*>::iterator
+ i = requests.begin(); i != requests.end(); i++) {
+ (*i)->Post(error, private_key, cert);
+ // Post() causes the ServerBoundCertServiceRequest to delete itself.
+ }
+ }
+
+ void DeleteAllCanceled() {
+ for (std::vector<ServerBoundCertServiceRequest*>::iterator
+ i = requests_.begin(); i != requests_.end(); i++) {
+ if ((*i)->canceled()) {
+ delete *i;
+ } else {
+ LOG(DFATAL) << "ServerBoundCertServiceRequest leaked!";
+ }
+ }
+ }
+
+ std::vector<ServerBoundCertServiceRequest*> requests_;
+ bool create_if_missing_;
+};
+
+// static
+const char ServerBoundCertService::kEPKIPassword[] = "";
+
+ServerBoundCertService::RequestHandle::RequestHandle()
+ : service_(NULL),
+ request_(NULL) {}
+
+ServerBoundCertService::RequestHandle::~RequestHandle() {
+ Cancel();
+}
+
+void ServerBoundCertService::RequestHandle::Cancel() {
+ if (request_) {
+ service_->CancelRequest(request_);
+ request_ = NULL;
+ callback_.Reset();
+ }
+}
+
+void ServerBoundCertService::RequestHandle::RequestStarted(
+ ServerBoundCertService* service,
+ ServerBoundCertServiceRequest* request,
+ const CompletionCallback& callback) {
+ DCHECK(request_ == NULL);
+ service_ = service;
+ request_ = request;
+ callback_ = callback;
+}
+
+void ServerBoundCertService::RequestHandle::OnRequestComplete(int result) {
+ request_ = NULL;
+ // Running the callback might delete |this|, so we can't touch any of our
+ // members afterwards. Reset callback_ first.
+ base::ResetAndReturn(&callback_).Run(result);
+}
+
+ServerBoundCertService::ServerBoundCertService(
+ ServerBoundCertStore* server_bound_cert_store,
+ const scoped_refptr<base::TaskRunner>& task_runner)
+ : server_bound_cert_store_(server_bound_cert_store),
+ task_runner_(task_runner),
+ weak_ptr_factory_(this),
+ requests_(0),
+ cert_store_hits_(0),
+ inflight_joins_(0),
+ workers_created_(0) {
+ base::Time start = base::Time::Now();
+ base::Time end = start + base::TimeDelta::FromDays(
+ kValidityPeriodInDays + kSystemTimeValidityBufferInDays);
+ is_system_time_valid_ = x509_util::IsSupportedValidityRange(start, end);
+}
+
+ServerBoundCertService::~ServerBoundCertService() {
+ STLDeleteValues(&inflight_);
+}
+
+//static
+std::string ServerBoundCertService::GetDomainForHost(const std::string& host) {
+ std::string domain =
+ registry_controlled_domains::GetDomainAndRegistry(
+ host, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
+ if (domain.empty())
+ return host;
+ return domain;
+}
+
+int ServerBoundCertService::GetOrCreateDomainBoundCert(
+ const std::string& host,
+ std::string* private_key,
+ std::string* cert,
+ const CompletionCallback& callback,
+ RequestHandle* out_req) {
+ DVLOG(1) << __FUNCTION__ << " " << host;
+ DCHECK(CalledOnValidThread());
+ base::TimeTicks request_start = base::TimeTicks::Now();
+
+ if (callback.is_null() || !private_key || !cert || host.empty()) {
+ RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ std::string domain = GetDomainForHost(host);
+ if (domain.empty()) {
+ RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ requests_++;
+
+ // See if a request for the same domain is currently in flight.
+ bool create_if_missing = true;
+ if (JoinToInFlightRequest(request_start, domain, private_key, cert,
+ create_if_missing, callback, out_req)) {
+ return ERR_IO_PENDING;
+ }
+
+ int err = LookupDomainBoundCert(request_start, domain, private_key, cert,
+ create_if_missing, callback, out_req);
+ if (err == ERR_FILE_NOT_FOUND) {
+ // Sync lookup did not find a valid cert. Start generating a new one.
+ workers_created_++;
+ ServerBoundCertServiceWorker* worker = new ServerBoundCertServiceWorker(
+ domain,
+ base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (!worker->Start(task_runner_)) {
+ // TODO(rkn): Log to the NetLog.
+ LOG(ERROR) << "ServerBoundCertServiceWorker couldn't be started.";
+ RecordGetDomainBoundCertResult(WORKER_FAILURE);
+ return ERR_INSUFFICIENT_RESOURCES;
+ }
+ // We are waiting for cert generation. Create a job & request to track it.
+ ServerBoundCertServiceJob* job =
+ new ServerBoundCertServiceJob(create_if_missing);
+ inflight_[domain] = job;
+
+ ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
+ request_start,
+ base::Bind(&RequestHandle::OnRequestComplete,
+ base::Unretained(out_req)),
+ private_key,
+ cert);
+ job->AddRequest(request);
+ out_req->RequestStarted(this, request, callback);
+ return ERR_IO_PENDING;
+ }
+
+ return err;
+}
+
+int ServerBoundCertService::GetDomainBoundCert(
+ const std::string& host,
+ std::string* private_key,
+ std::string* cert,
+ const CompletionCallback& callback,
+ RequestHandle* out_req) {
+ DVLOG(1) << __FUNCTION__ << " " << host;
+ DCHECK(CalledOnValidThread());
+ base::TimeTicks request_start = base::TimeTicks::Now();
+
+ if (callback.is_null() || !private_key || !cert || host.empty()) {
+ RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ std::string domain = GetDomainForHost(host);
+ if (domain.empty()) {
+ RecordGetDomainBoundCertResult(INVALID_ARGUMENT);
+ return ERR_INVALID_ARGUMENT;
+ }
+
+ requests_++;
+
+ // See if a request for the same domain currently in flight.
+ bool create_if_missing = false;
+ if (JoinToInFlightRequest(request_start, domain, private_key, cert,
+ create_if_missing, callback, out_req)) {
+ return ERR_IO_PENDING;
+ }
+
+ int err = LookupDomainBoundCert(request_start, domain, private_key, cert,
+ create_if_missing, callback, out_req);
+ return err;
+}
+
+void ServerBoundCertService::GotServerBoundCert(
+ int err,
+ const std::string& server_identifier,
+ base::Time expiration_time,
+ const std::string& key,
+ const std::string& cert) {
+ DCHECK(CalledOnValidThread());
+
+ std::map<std::string, ServerBoundCertServiceJob*>::iterator j;
+ j = inflight_.find(server_identifier);
+ if (j == inflight_.end()) {
+ NOTREACHED();
+ return;
+ }
+
+ if (err == OK) {
+ // Async DB lookup found a valid cert.
+ DVLOG(1) << "Cert store had valid cert for " << server_identifier;
+ cert_store_hits_++;
+ // ServerBoundCertServiceRequest::Post will do the histograms and stuff.
+ HandleResult(OK, server_identifier, key, cert);
+ return;
+ }
+ // Async lookup did not find a valid cert. If no request asked to create one,
+ // return the error directly.
+ if (!j->second->CreateIfMissing()) {
+ HandleResult(err, server_identifier, key, cert);
+ return;
+ }
+ // At least one request asked to create a cert => start generating a new one.
+ workers_created_++;
+ ServerBoundCertServiceWorker* worker = new ServerBoundCertServiceWorker(
+ server_identifier,
+ base::Bind(&ServerBoundCertService::GeneratedServerBoundCert,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (!worker->Start(task_runner_)) {
+ // TODO(rkn): Log to the NetLog.
+ LOG(ERROR) << "ServerBoundCertServiceWorker couldn't be started.";
+ HandleResult(ERR_INSUFFICIENT_RESOURCES,
+ server_identifier,
+ std::string(),
+ std::string());
+ }
+}
+
+ServerBoundCertStore* ServerBoundCertService::GetCertStore() {
+ return server_bound_cert_store_.get();
+}
+
+void ServerBoundCertService::CancelRequest(ServerBoundCertServiceRequest* req) {
+ DCHECK(CalledOnValidThread());
+ req->Cancel();
+}
+
+void ServerBoundCertService::GeneratedServerBoundCert(
+ const std::string& server_identifier,
+ int error,
+ scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert) {
+ DCHECK(CalledOnValidThread());
+
+ if (error == OK) {
+ // TODO(mattm): we should just Pass() the cert object to
+ // SetServerBoundCert().
+ server_bound_cert_store_->SetServerBoundCert(
+ cert->server_identifier(),
+ cert->creation_time(),
+ cert->expiration_time(),
+ cert->private_key(),
+ cert->cert());
+
+ HandleResult(error, server_identifier, cert->private_key(), cert->cert());
+ } else {
+ HandleResult(error, server_identifier, std::string(), std::string());
+ }
+}
+
+void ServerBoundCertService::HandleResult(
+ int error,
+ const std::string& server_identifier,
+ const std::string& private_key,
+ const std::string& cert) {
+ DCHECK(CalledOnValidThread());
+
+ std::map<std::string, ServerBoundCertServiceJob*>::iterator j;
+ j = inflight_.find(server_identifier);
+ if (j == inflight_.end()) {
+ NOTREACHED();
+ return;
+ }
+ ServerBoundCertServiceJob* job = j->second;
+ inflight_.erase(j);
+
+ job->HandleResult(error, private_key, cert);
+ delete job;
+}
+
+bool ServerBoundCertService::JoinToInFlightRequest(
+ const base::TimeTicks& request_start,
+ const std::string& domain,
+ std::string* private_key,
+ std::string* cert,
+ bool create_if_missing,
+ const CompletionCallback& callback,
+ RequestHandle* out_req) {
+ ServerBoundCertServiceJob* job = NULL;
+ std::map<std::string, ServerBoundCertServiceJob*>::const_iterator j =
+ inflight_.find(domain);
+ if (j != inflight_.end()) {
+ // A request for the same domain is in flight already. We'll attach our
+ // callback, but we'll also mark it as requiring a cert if one's mising.
+ job = j->second;
+ inflight_joins_++;
+
+ ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
+ request_start,
+ base::Bind(&RequestHandle::OnRequestComplete,
+ base::Unretained(out_req)),
+ private_key,
+ cert);
+ job->AddRequest(request, create_if_missing);
+ out_req->RequestStarted(this, request, callback);
+ return true;
+ }
+ return false;
+}
+
+int ServerBoundCertService::LookupDomainBoundCert(
+ const base::TimeTicks& request_start,
+ const std::string& domain,
+ std::string* private_key,
+ std::string* cert,
+ bool create_if_missing,
+ const CompletionCallback& callback,
+ RequestHandle* out_req) {
+ // Check if a domain bound cert already exists for this domain. Note that
+ // |expiration_time| is ignored, and expired certs are considered valid.
+ base::Time expiration_time;
+ int err = server_bound_cert_store_->GetServerBoundCert(
+ domain,
+ &expiration_time /* ignored */,
+ private_key,
+ cert,
+ base::Bind(&ServerBoundCertService::GotServerBoundCert,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ if (err == OK) {
+ // Sync lookup found a valid cert.
+ DVLOG(1) << "Cert store had valid cert for " << domain;
+ cert_store_hits_++;
+ RecordGetDomainBoundCertResult(SYNC_SUCCESS);
+ base::TimeDelta request_time = base::TimeTicks::Now() - request_start;
+ UMA_HISTOGRAM_TIMES("DomainBoundCerts.GetCertTimeSync", request_time);
+ RecordGetCertTime(request_time);
+ return OK;
+ }
+
+ if (err == ERR_IO_PENDING) {
+ // We are waiting for async DB lookup. Create a job & request to track it.
+ ServerBoundCertServiceJob* job =
+ new ServerBoundCertServiceJob(create_if_missing);
+ inflight_[domain] = job;
+
+ ServerBoundCertServiceRequest* request = new ServerBoundCertServiceRequest(
+ request_start,
+ base::Bind(&RequestHandle::OnRequestComplete,
+ base::Unretained(out_req)),
+ private_key,
+ cert);
+ job->AddRequest(request);
+ out_req->RequestStarted(this, request, callback);
+ return ERR_IO_PENDING;
+ }
+
+ return err;
+}
+
+int ServerBoundCertService::cert_count() {
+ return server_bound_cert_store_->GetCertCount();
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/server_bound_cert_service.h b/chromium/net/ssl/server_bound_cert_service.h
new file mode 100644
index 00000000000..0dc7f4ae390
--- /dev/null
+++ b/chromium/net/ssl/server_bound_cert_service.h
@@ -0,0 +1,214 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_SERVER_BOUND_CERT_SERVICE_H_
+#define NET_SSL_SERVER_BOUND_CERT_SERVICE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/ssl/server_bound_cert_store.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+
+class ServerBoundCertServiceJob;
+class ServerBoundCertServiceRequest;
+class ServerBoundCertServiceWorker;
+
+// A class for creating and fetching server bound certs. These certs are used
+// to identify users' machines; their public keys are used as channel IDs in
+// http://tools.ietf.org/html/draft-balfanz-tls-channelid-00.
+// As a result although certs are set to be invalid after one year, we don't
+// actually expire them. Once generated, certs are valid as long as the users
+// want. Users can delete existing certs, and new certs will be generated
+// automatically.
+
+// Inherits from NonThreadSafe in order to use the function
+// |CalledOnValidThread|.
+class NET_EXPORT ServerBoundCertService
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ class NET_EXPORT RequestHandle {
+ public:
+ RequestHandle();
+ ~RequestHandle();
+
+ // Cancel the request. Does nothing if the request finished or was already
+ // cancelled.
+ void Cancel();
+
+ bool is_active() const { return request_ != NULL; }
+
+ private:
+ friend class ServerBoundCertService;
+
+ void RequestStarted(ServerBoundCertService* service,
+ ServerBoundCertServiceRequest* request,
+ const CompletionCallback& callback);
+
+ void OnRequestComplete(int result);
+
+ ServerBoundCertService* service_;
+ ServerBoundCertServiceRequest* request_;
+ CompletionCallback callback_;
+ };
+
+ // Password used on EncryptedPrivateKeyInfo data stored in EC private_key
+ // values. (This is not used to provide any security, but to workaround NSS
+ // being unable to import unencrypted PrivateKeyInfo for EC keys.)
+ static const char kEPKIPassword[];
+
+ // This object owns |server_bound_cert_store|. |task_runner| will
+ // be used to post certificate generation worker tasks. The tasks are
+ // safe for use with WorkerPool and SequencedWorkerPool::CONTINUE_ON_SHUTDOWN.
+ ServerBoundCertService(
+ ServerBoundCertStore* server_bound_cert_store,
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ ~ServerBoundCertService();
+
+ // Returns the domain to be used for |host|. The domain is the
+ // "registry controlled domain", or the "ETLD + 1" where one exists, or
+ // the origin otherwise.
+ static std::string GetDomainForHost(const std::string& host);
+
+ // Tests whether the system time is within the supported range for
+ // certificate generation. This value is cached when ServerBoundCertService
+ // is created, so if the system time is changed by a huge amount, this may no
+ // longer hold.
+ bool IsSystemTimeValid() const { return is_system_time_valid_; }
+
+ // Fetches the domain bound cert for the specified host if one exists and
+ // creates one otherwise. Returns OK if successful or an error code upon
+ // failure.
+ //
+ // On successful completion, |private_key| stores a DER-encoded
+ // PrivateKeyInfo struct, and |cert| stores a DER-encoded certificate.
+ // The PrivateKeyInfo is always an ECDSA private key.
+ //
+ // |callback| must not be null. ERR_IO_PENDING is returned if the operation
+ // could not be completed immediately, in which case the result code will
+ // be passed to the callback when available.
+ //
+ // |*out_req| will be initialized with a handle to the async request. This
+ // RequestHandle object must be cancelled or destroyed before the
+ // ServerBoundCertService is destroyed.
+ int GetOrCreateDomainBoundCert(
+ const std::string& host,
+ std::string* private_key,
+ std::string* cert,
+ const CompletionCallback& callback,
+ RequestHandle* out_req);
+
+ // Fetches the domain bound cert for the specified host if one exists.
+ // Returns OK if successful, ERR_FILE_NOT_FOUND if none exists, or an error
+ // code upon failure.
+ //
+ // On successful completion, |private_key| stores a DER-encoded
+ // PrivateKeyInfo struct, and |cert| stores a DER-encoded certificate.
+ // The PrivateKeyInfo is always an ECDSA private key.
+ //
+ // |callback| must not be null. ERR_IO_PENDING is returned if the operation
+ // could not be completed immediately, in which case the result code will
+ // be passed to the callback when available. If an in-flight
+ // GetDomainBoundCert is pending, and a new GetOrCreateDomainBoundCert
+ // request arrives for the same domain, the GetDomainBoundCert request will
+ // not complete until a new cert is created.
+ //
+ // |*out_req| will be initialized with a handle to the async request. This
+ // RequestHandle object must be cancelled or destroyed before the
+ // ServerBoundCertService is destroyed.
+ int GetDomainBoundCert(
+ const std::string& host,
+ std::string* private_key,
+ std::string* cert,
+ const CompletionCallback& callback,
+ RequestHandle* out_req);
+
+ // Returns the backing ServerBoundCertStore.
+ ServerBoundCertStore* GetCertStore();
+
+ // Public only for unit testing.
+ int cert_count();
+ uint64 requests() const { return requests_; }
+ uint64 cert_store_hits() const { return cert_store_hits_; }
+ uint64 inflight_joins() const { return inflight_joins_; }
+ uint64 workers_created() const { return workers_created_; }
+
+ private:
+ // Cancels the specified request. |req| is the handle stored by
+ // GetDomainBoundCert(). After a request is canceled, its completion
+ // callback will not be called.
+ void CancelRequest(ServerBoundCertServiceRequest* req);
+
+ void GotServerBoundCert(int err,
+ const std::string& server_identifier,
+ base::Time expiration_time,
+ const std::string& key,
+ const std::string& cert);
+ void GeneratedServerBoundCert(
+ const std::string& server_identifier,
+ int error,
+ scoped_ptr<ServerBoundCertStore::ServerBoundCert> cert);
+ void HandleResult(int error,
+ const std::string& server_identifier,
+ const std::string& private_key,
+ const std::string& cert);
+
+ // Searches for an in-flight request for the same domain. If found,
+ // attaches to the request and returns true. Returns false if no in-flight
+ // request is found.
+ bool JoinToInFlightRequest(const base::TimeTicks& request_start,
+ const std::string& domain,
+ std::string* private_key,
+ std::string* cert,
+ bool create_if_missing,
+ const CompletionCallback& callback,
+ RequestHandle* out_req);
+
+ // Looks for the domain bound cert for |domain| in this service's store.
+ // Returns OK if it can be found synchronously, ERR_IO_PENDING if the
+ // result cannot be obtained synchronously, or a network error code on
+ // failure (including failure to find a domain-bound cert of |domain|).
+ int LookupDomainBoundCert(const base::TimeTicks& request_start,
+ const std::string& domain,
+ std::string* private_key,
+ std::string* cert,
+ bool create_if_missing,
+ const CompletionCallback& callback,
+ RequestHandle* out_req);
+
+ scoped_ptr<ServerBoundCertStore> server_bound_cert_store_;
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // inflight_ maps from a server to an active generation which is taking
+ // place.
+ std::map<std::string, ServerBoundCertServiceJob*> inflight_;
+ base::WeakPtrFactory<ServerBoundCertService> weak_ptr_factory_;
+
+ uint64 requests_;
+ uint64 cert_store_hits_;
+ uint64 inflight_joins_;
+ uint64 workers_created_;
+
+ bool is_system_time_valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerBoundCertService);
+};
+
+} // namespace net
+
+#endif // NET_SSL_SERVER_BOUND_CERT_SERVICE_H_
diff --git a/chromium/net/ssl/server_bound_cert_service_unittest.cc b/chromium/net/ssl/server_bound_cert_service_unittest.cc
new file mode 100644
index 00000000000..b8ca1fa54d0
--- /dev/null
+++ b/chromium/net/ssl/server_bound_cert_service_unittest.cc
@@ -0,0 +1,774 @@
+// Copyright (c) 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.
+
+#include "net/ssl/server_bound_cert_service.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "crypto/ec_private_key.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/asn1_util.h"
+#include "net/cert/x509_certificate.h"
+#include "net/ssl/default_server_bound_cert_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+void FailTest(int /* result */) {
+ FAIL();
+}
+
+class ServerBoundCertServiceTest : public testing::Test {
+ public:
+ ServerBoundCertServiceTest()
+ : sequenced_worker_pool_(new base::SequencedWorkerPool(
+ 3, "ServerBoundCertServiceTest")),
+ service_(new ServerBoundCertService(
+ new DefaultServerBoundCertStore(NULL),
+ sequenced_worker_pool_)) {
+ }
+
+ virtual ~ServerBoundCertServiceTest() {
+ if (sequenced_worker_pool_.get())
+ sequenced_worker_pool_->Shutdown();
+ }
+
+ protected:
+ scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool_;
+ scoped_ptr<ServerBoundCertService> service_;
+};
+
+class MockServerBoundCertStoreWithAsyncGet
+ : public DefaultServerBoundCertStore {
+ public:
+ MockServerBoundCertStoreWithAsyncGet()
+ : DefaultServerBoundCertStore(NULL), cert_count_(0) {}
+
+ virtual int GetServerBoundCert(const std::string& server_identifier,
+ base::Time* expiration_time,
+ std::string* private_key_result,
+ std::string* cert_result,
+ const GetCertCallback& callback) OVERRIDE;
+
+ virtual void SetServerBoundCert(const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) OVERRIDE {
+ cert_count_ = 1;
+ }
+
+ virtual int GetCertCount() OVERRIDE { return cert_count_; }
+
+ void CallGetServerBoundCertCallbackWithResult(int err,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert);
+
+ private:
+ GetCertCallback callback_;
+ std::string server_identifier_;
+ int cert_count_;
+};
+
+int MockServerBoundCertStoreWithAsyncGet::GetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time* expiration_time,
+ std::string* private_key_result,
+ std::string* cert_result,
+ const GetCertCallback& callback) {
+ server_identifier_ = server_identifier;
+ callback_ = callback;
+ // Reset the cert count, it'll get incremented in either SetServerBoundCert or
+ // CallGetServerBoundCertCallbackWithResult.
+ cert_count_ = 0;
+ // Do nothing else: the results to be provided will be specified through
+ // CallGetServerBoundCertCallbackWithResult.
+ return ERR_IO_PENDING;
+}
+
+void
+MockServerBoundCertStoreWithAsyncGet::CallGetServerBoundCertCallbackWithResult(
+ int err,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) {
+ if (err == OK)
+ cert_count_ = 1;
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::Bind(callback_,
+ err,
+ server_identifier_,
+ expiration_time,
+ private_key,
+ cert));
+}
+
+TEST_F(ServerBoundCertServiceTest, GetDomainForHost) {
+ EXPECT_EQ("google.com",
+ ServerBoundCertService::GetDomainForHost("google.com"));
+ EXPECT_EQ("google.com",
+ ServerBoundCertService::GetDomainForHost("www.google.com"));
+ EXPECT_EQ("foo.appspot.com",
+ ServerBoundCertService::GetDomainForHost("foo.appspot.com"));
+ EXPECT_EQ("bar.appspot.com",
+ ServerBoundCertService::GetDomainForHost("foo.bar.appspot.com"));
+ EXPECT_EQ("appspot.com",
+ ServerBoundCertService::GetDomainForHost("appspot.com"));
+ EXPECT_EQ("google.com",
+ ServerBoundCertService::GetDomainForHost("www.mail.google.com"));
+ EXPECT_EQ("goto",
+ ServerBoundCertService::GetDomainForHost("goto"));
+ EXPECT_EQ("127.0.0.1",
+ ServerBoundCertService::GetDomainForHost("127.0.0.1"));
+}
+
+// See http://crbug.com/91512 - implement OpenSSL version of CreateSelfSigned.
+#if !defined(USE_OPENSSL)
+
+TEST_F(ServerBoundCertServiceTest, GetCacheMiss) {
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Synchronous completion, because the store is initialized.
+ std::string private_key, der_cert;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetDomainBoundCert(
+ host, &private_key, &der_cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_FILE_NOT_FOUND, error);
+ EXPECT_FALSE(request_handle.is_active());
+ EXPECT_EQ(0, service_->cert_count());
+ EXPECT_TRUE(der_cert.empty());
+}
+
+TEST_F(ServerBoundCertServiceTest, CacheHit) {
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Asynchronous completion.
+ std::string private_key_info1, der_cert1;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info1, &der_cert1,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_FALSE(private_key_info1.empty());
+ EXPECT_FALSE(der_cert1.empty());
+ EXPECT_FALSE(request_handle.is_active());
+
+ // Synchronous completion.
+ std::string private_key_info2, der_cert2;
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info2, &der_cert2,
+ callback.callback(), &request_handle);
+ EXPECT_FALSE(request_handle.is_active());
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_EQ(private_key_info1, private_key_info2);
+ EXPECT_EQ(der_cert1, der_cert2);
+
+ // Synchronous get.
+ std::string private_key_info3, der_cert3;
+ error = service_->GetDomainBoundCert(
+ host, &private_key_info3, &der_cert3, callback.callback(),
+ &request_handle);
+ EXPECT_FALSE(request_handle.is_active());
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_EQ(der_cert1, der_cert3);
+ EXPECT_EQ(private_key_info1, private_key_info3);
+
+ EXPECT_EQ(3u, service_->requests());
+ EXPECT_EQ(2u, service_->cert_store_hits());
+ EXPECT_EQ(0u, service_->inflight_joins());
+}
+
+TEST_F(ServerBoundCertServiceTest, StoreCerts) {
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ std::string host1("encrypted.google.com");
+ std::string private_key_info1, der_cert1;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetOrCreateDomainBoundCert(
+ host1, &private_key_info1, &der_cert1,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+
+ std::string host2("www.verisign.com");
+ std::string private_key_info2, der_cert2;
+ error = service_->GetOrCreateDomainBoundCert(
+ host2, &private_key_info2, &der_cert2,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(2, service_->cert_count());
+
+ std::string host3("www.twitter.com");
+ std::string private_key_info3, der_cert3;
+ error = service_->GetOrCreateDomainBoundCert(
+ host3, &private_key_info3, &der_cert3,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(3, service_->cert_count());
+
+ EXPECT_NE(private_key_info1, private_key_info2);
+ EXPECT_NE(der_cert1, der_cert2);
+ EXPECT_NE(private_key_info1, private_key_info3);
+ EXPECT_NE(der_cert1, der_cert3);
+ EXPECT_NE(private_key_info2, private_key_info3);
+ EXPECT_NE(der_cert2, der_cert3);
+}
+
+// Tests an inflight join.
+TEST_F(ServerBoundCertServiceTest, InflightJoin) {
+ std::string host("encrypted.google.com");
+ int error;
+
+ std::string private_key_info1, der_cert1;
+ TestCompletionCallback callback1;
+ ServerBoundCertService::RequestHandle request_handle1;
+
+ std::string private_key_info2, der_cert2;
+ TestCompletionCallback callback2;
+ ServerBoundCertService::RequestHandle request_handle2;
+
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info1, &der_cert1,
+ callback1.callback(), &request_handle1);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle1.is_active());
+ // Should join with the original request.
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info2, &der_cert2,
+ callback2.callback(), &request_handle2);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2.is_active());
+
+ error = callback1.WaitForResult();
+ EXPECT_EQ(OK, error);
+ error = callback2.WaitForResult();
+ EXPECT_EQ(OK, error);
+
+ EXPECT_EQ(2u, service_->requests());
+ EXPECT_EQ(0u, service_->cert_store_hits());
+ EXPECT_EQ(1u, service_->inflight_joins());
+ EXPECT_EQ(1u, service_->workers_created());
+}
+
+// Tests an inflight join of a Get request to a GetOrCreate request.
+TEST_F(ServerBoundCertServiceTest, InflightJoinGetOrCreateAndGet) {
+ std::string host("encrypted.google.com");
+ int error;
+
+ std::string private_key_info1, der_cert1;
+ TestCompletionCallback callback1;
+ ServerBoundCertService::RequestHandle request_handle1;
+
+ std::string private_key_info2;
+ std::string der_cert2;
+ TestCompletionCallback callback2;
+ ServerBoundCertService::RequestHandle request_handle2;
+
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info1, &der_cert1,
+ callback1.callback(), &request_handle1);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle1.is_active());
+ // Should join with the original request.
+ error = service_->GetDomainBoundCert(
+ host, &private_key_info2, &der_cert2, callback2.callback(),
+ &request_handle2);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2.is_active());
+
+ error = callback1.WaitForResult();
+ EXPECT_EQ(OK, error);
+ error = callback2.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(der_cert1, der_cert2);
+
+ EXPECT_EQ(2u, service_->requests());
+ EXPECT_EQ(0u, service_->cert_store_hits());
+ EXPECT_EQ(1u, service_->inflight_joins());
+ EXPECT_EQ(1u, service_->workers_created());
+}
+
+TEST_F(ServerBoundCertServiceTest, ExtractValuesFromBytesEC) {
+ std::string host("encrypted.google.com");
+ std::string private_key_info, der_cert;
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info, &der_cert, callback.callback(),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+
+ base::StringPiece spki_piece;
+ ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_cert, &spki_piece));
+ std::vector<uint8> spki(
+ spki_piece.data(),
+ spki_piece.data() + spki_piece.size());
+
+ // Check that we can retrieve the key from the bytes.
+ std::vector<uint8> key_vec(private_key_info.begin(), private_key_info.end());
+ scoped_ptr<crypto::ECPrivateKey> private_key(
+ crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+ ServerBoundCertService::kEPKIPassword, key_vec, spki));
+ EXPECT_TRUE(private_key != NULL);
+
+ // Check that we can retrieve the cert from the bytes.
+ scoped_refptr<X509Certificate> x509cert(
+ X509Certificate::CreateFromBytes(der_cert.data(), der_cert.size()));
+ EXPECT_TRUE(x509cert.get() != NULL);
+}
+
+// Tests that the callback of a canceled request is never made.
+TEST_F(ServerBoundCertServiceTest, CancelRequest) {
+ std::string host("encrypted.google.com");
+ std::string private_key_info, der_cert;
+ int error;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ error = service_->GetOrCreateDomainBoundCert(host,
+ &private_key_info,
+ &der_cert,
+ base::Bind(&FailTest),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ request_handle.Cancel();
+ EXPECT_FALSE(request_handle.is_active());
+
+ // Wait for generation to finish.
+ sequenced_worker_pool_->FlushForTesting();
+ // Wait for reply from ServerBoundCertServiceWorker to be posted back to the
+ // ServerBoundCertService.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Even though the original request was cancelled, the service will still
+ // store the result, it just doesn't call the callback.
+ EXPECT_EQ(1, service_->cert_count());
+}
+
+// Tests that destructing the RequestHandle cancels the request.
+TEST_F(ServerBoundCertServiceTest, CancelRequestByHandleDestruction) {
+ std::string host("encrypted.google.com");
+ std::string private_key_info, der_cert;
+ int error;
+ {
+ ServerBoundCertService::RequestHandle request_handle;
+
+ error = service_->GetOrCreateDomainBoundCert(host,
+ &private_key_info,
+ &der_cert,
+ base::Bind(&FailTest),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+ }
+
+ // Wait for generation to finish.
+ sequenced_worker_pool_->FlushForTesting();
+ // Wait for reply from ServerBoundCertServiceWorker to be posted back to the
+ // ServerBoundCertService.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Even though the original request was cancelled, the service will still
+ // store the result, it just doesn't call the callback.
+ EXPECT_EQ(1, service_->cert_count());
+}
+
+TEST_F(ServerBoundCertServiceTest, DestructionWithPendingRequest) {
+ std::string host("encrypted.google.com");
+ std::string private_key_info, der_cert;
+ int error;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ error = service_->GetOrCreateDomainBoundCert(host,
+ &private_key_info,
+ &der_cert,
+ base::Bind(&FailTest),
+ &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+
+ // Cancel request and destroy the ServerBoundCertService.
+ request_handle.Cancel();
+ service_.reset();
+
+ // Wait for generation to finish.
+ sequenced_worker_pool_->FlushForTesting();
+ // ServerBoundCertServiceWorker should not post anything back to the
+ // non-existant ServerBoundCertService, but run the loop just to be sure it
+ // doesn't.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // If we got here without crashing or a valgrind error, it worked.
+}
+
+// Tests that shutting down the sequenced worker pool and then making new
+// requests gracefully fails.
+// This is a regression test for http://crbug.com/236387
+TEST_F(ServerBoundCertServiceTest, RequestAfterPoolShutdown) {
+ // Shutdown the pool immediately.
+ sequenced_worker_pool_->Shutdown();
+ sequenced_worker_pool_ = NULL;
+
+ // Ensure any shutdown code is processed.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Make a request that will force synchronous completion.
+ std::string host("encrypted.google.com");
+ std::string private_key_info, der_cert;
+ int error;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ error = service_->GetOrCreateDomainBoundCert(host,
+ &private_key_info,
+ &der_cert,
+ base::Bind(&FailTest),
+ &request_handle);
+ // If we got here without crashing or a valgrind error, it worked.
+ ASSERT_EQ(ERR_INSUFFICIENT_RESOURCES, error);
+ EXPECT_FALSE(request_handle.is_active());
+}
+
+// Tests that simultaneous creation of different certs works.
+TEST_F(ServerBoundCertServiceTest, SimultaneousCreation) {
+ int error;
+
+ std::string host1("encrypted.google.com");
+ std::string private_key_info1, der_cert1;
+ TestCompletionCallback callback1;
+ ServerBoundCertService::RequestHandle request_handle1;
+
+ std::string host2("foo.com");
+ std::string private_key_info2, der_cert2;
+ TestCompletionCallback callback2;
+ ServerBoundCertService::RequestHandle request_handle2;
+
+ std::string host3("bar.com");
+ std::string private_key_info3, der_cert3;
+ TestCompletionCallback callback3;
+ ServerBoundCertService::RequestHandle request_handle3;
+
+ error = service_->GetOrCreateDomainBoundCert(host1,
+ &private_key_info1,
+ &der_cert1,
+ callback1.callback(),
+ &request_handle1);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle1.is_active());
+
+ error = service_->GetOrCreateDomainBoundCert(host2,
+ &private_key_info2,
+ &der_cert2,
+ callback2.callback(),
+ &request_handle2);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2.is_active());
+
+ error = service_->GetOrCreateDomainBoundCert(host3,
+ &private_key_info3,
+ &der_cert3,
+ callback3.callback(),
+ &request_handle3);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle3.is_active());
+
+ error = callback1.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(private_key_info1.empty());
+ EXPECT_FALSE(der_cert1.empty());
+
+ error = callback2.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(private_key_info2.empty());
+ EXPECT_FALSE(der_cert2.empty());
+
+ error = callback3.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(private_key_info3.empty());
+ EXPECT_FALSE(der_cert3.empty());
+
+ EXPECT_NE(private_key_info1, private_key_info2);
+ EXPECT_NE(der_cert1, der_cert2);
+
+ EXPECT_NE(private_key_info1, private_key_info3);
+ EXPECT_NE(der_cert1, der_cert3);
+
+ EXPECT_NE(private_key_info2, private_key_info3);
+ EXPECT_NE(der_cert2, der_cert3);
+
+ EXPECT_EQ(3, service_->cert_count());
+}
+
+TEST_F(ServerBoundCertServiceTest, Expiration) {
+ ServerBoundCertStore* store = service_->GetCertStore();
+ base::Time now = base::Time::Now();
+ store->SetServerBoundCert("good",
+ now,
+ now + base::TimeDelta::FromDays(1),
+ "a",
+ "b");
+ store->SetServerBoundCert("expired",
+ now - base::TimeDelta::FromDays(2),
+ now - base::TimeDelta::FromDays(1),
+ "c",
+ "d");
+ EXPECT_EQ(2, service_->cert_count());
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Cert is valid - synchronous completion.
+ std::string private_key_info1, der_cert1;
+ error = service_->GetOrCreateDomainBoundCert(
+ "good", &private_key_info1, &der_cert1,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(request_handle.is_active());
+ EXPECT_EQ(2, service_->cert_count());
+ EXPECT_STREQ("a", private_key_info1.c_str());
+ EXPECT_STREQ("b", der_cert1.c_str());
+
+ // Expired cert is valid as well - synchronous completion.
+ std::string private_key_info2, der_cert2;
+ error = service_->GetOrCreateDomainBoundCert(
+ "expired", &private_key_info2, &der_cert2,
+ callback.callback(), &request_handle);
+ EXPECT_EQ(OK, error);
+ EXPECT_FALSE(request_handle.is_active());
+ EXPECT_EQ(2, service_->cert_count());
+ EXPECT_STREQ("c", private_key_info2.c_str());
+ EXPECT_STREQ("d", der_cert2.c_str());
+}
+
+TEST_F(ServerBoundCertServiceTest, AsyncStoreGetOrCreateNoCertsInStore) {
+ MockServerBoundCertStoreWithAsyncGet* mock_store =
+ new MockServerBoundCertStoreWithAsyncGet();
+ service_ = scoped_ptr<ServerBoundCertService>(
+ new ServerBoundCertService(mock_store, sequenced_worker_pool_));
+
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Asynchronous completion with no certs in the store.
+ std::string private_key_info, der_cert;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info, &der_cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+
+ mock_store->CallGetServerBoundCertCallbackWithResult(
+ ERR_FILE_NOT_FOUND, base::Time(), std::string(), std::string());
+
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_FALSE(private_key_info.empty());
+ EXPECT_FALSE(der_cert.empty());
+ EXPECT_FALSE(request_handle.is_active());
+}
+
+TEST_F(ServerBoundCertServiceTest, AsyncStoreGetNoCertsInStore) {
+ MockServerBoundCertStoreWithAsyncGet* mock_store =
+ new MockServerBoundCertStoreWithAsyncGet();
+ service_ = scoped_ptr<ServerBoundCertService>(
+ new ServerBoundCertService(mock_store, sequenced_worker_pool_));
+
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Asynchronous completion with no certs in the store.
+ std::string private_key, der_cert;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetDomainBoundCert(
+ host, &private_key, &der_cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+
+ mock_store->CallGetServerBoundCertCallbackWithResult(
+ ERR_FILE_NOT_FOUND, base::Time(), std::string(), std::string());
+
+ error = callback.WaitForResult();
+ EXPECT_EQ(ERR_FILE_NOT_FOUND, error);
+ EXPECT_EQ(0, service_->cert_count());
+ EXPECT_EQ(0u, service_->workers_created());
+ EXPECT_TRUE(der_cert.empty());
+ EXPECT_FALSE(request_handle.is_active());
+}
+
+TEST_F(ServerBoundCertServiceTest, AsyncStoreGetOrCreateOneCertInStore) {
+ MockServerBoundCertStoreWithAsyncGet* mock_store =
+ new MockServerBoundCertStoreWithAsyncGet();
+ service_ = scoped_ptr<ServerBoundCertService>(
+ new ServerBoundCertService(mock_store, sequenced_worker_pool_));
+
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Asynchronous completion with a cert in the store.
+ std::string private_key_info, der_cert;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key_info, &der_cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+
+ mock_store->CallGetServerBoundCertCallbackWithResult(
+ OK, base::Time(), "ab", "cd");
+
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_EQ(1u, service_->requests());
+ EXPECT_EQ(1u, service_->cert_store_hits());
+ // Because the cert was found in the store, no new workers should have been
+ // created.
+ EXPECT_EQ(0u, service_->workers_created());
+ EXPECT_STREQ("ab", private_key_info.c_str());
+ EXPECT_STREQ("cd", der_cert.c_str());
+ EXPECT_FALSE(request_handle.is_active());
+}
+
+TEST_F(ServerBoundCertServiceTest, AsyncStoreGetOneCertInStore) {
+ MockServerBoundCertStoreWithAsyncGet* mock_store =
+ new MockServerBoundCertStoreWithAsyncGet();
+ service_ = scoped_ptr<ServerBoundCertService>(
+ new ServerBoundCertService(mock_store, sequenced_worker_pool_));
+
+ std::string host("encrypted.google.com");
+
+ int error;
+ TestCompletionCallback callback;
+ ServerBoundCertService::RequestHandle request_handle;
+
+ // Asynchronous completion with a cert in the store.
+ std::string private_key, der_cert;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetDomainBoundCert(
+ host, &private_key, &der_cert, callback.callback(), &request_handle);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle.is_active());
+
+ mock_store->CallGetServerBoundCertCallbackWithResult(
+ OK, base::Time(), "ab", "cd");
+
+ error = callback.WaitForResult();
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_EQ(1u, service_->requests());
+ EXPECT_EQ(1u, service_->cert_store_hits());
+ // Because the cert was found in the store, no new workers should have been
+ // created.
+ EXPECT_EQ(0u, service_->workers_created());
+ EXPECT_STREQ("cd", der_cert.c_str());
+ EXPECT_FALSE(request_handle.is_active());
+}
+
+TEST_F(ServerBoundCertServiceTest, AsyncStoreGetThenCreateNoCertsInStore) {
+ MockServerBoundCertStoreWithAsyncGet* mock_store =
+ new MockServerBoundCertStoreWithAsyncGet();
+ service_ = scoped_ptr<ServerBoundCertService>(
+ new ServerBoundCertService(mock_store, sequenced_worker_pool_));
+
+ std::string host("encrypted.google.com");
+
+ int error;
+
+ // Asynchronous get with no certs in the store.
+ TestCompletionCallback callback1;
+ ServerBoundCertService::RequestHandle request_handle1;
+ std::string private_key1, der_cert1;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetDomainBoundCert(
+ host, &private_key1, &der_cert1, callback1.callback(), &request_handle1);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle1.is_active());
+
+ // Asynchronous get/create with no certs in the store.
+ TestCompletionCallback callback2;
+ ServerBoundCertService::RequestHandle request_handle2;
+ std::string private_key2, der_cert2;
+ EXPECT_EQ(0, service_->cert_count());
+ error = service_->GetOrCreateDomainBoundCert(
+ host, &private_key2, &der_cert2, callback2.callback(), &request_handle2);
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2.is_active());
+
+ mock_store->CallGetServerBoundCertCallbackWithResult(
+ ERR_FILE_NOT_FOUND, base::Time(), std::string(), std::string());
+
+ // Even though the first request didn't ask to create a cert, it gets joined
+ // by the second, which does, so both succeed.
+ error = callback1.WaitForResult();
+ EXPECT_EQ(OK, error);
+ error = callback2.WaitForResult();
+ EXPECT_EQ(OK, error);
+
+ // One cert is created, one request is joined.
+ EXPECT_EQ(2U, service_->requests());
+ EXPECT_EQ(1, service_->cert_count());
+ EXPECT_EQ(1u, service_->workers_created());
+ EXPECT_EQ(1u, service_->inflight_joins());
+ EXPECT_FALSE(der_cert1.empty());
+ EXPECT_EQ(der_cert1, der_cert2);
+ EXPECT_FALSE(private_key1.empty());
+ EXPECT_EQ(private_key1, private_key2);
+ EXPECT_FALSE(request_handle1.is_active());
+ EXPECT_FALSE(request_handle2.is_active());
+}
+
+#endif // !defined(USE_OPENSSL)
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/ssl/server_bound_cert_store.cc b/chromium/net/ssl/server_bound_cert_store.cc
new file mode 100644
index 00000000000..e778362c0ba
--- /dev/null
+++ b/chromium/net/ssl/server_bound_cert_store.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+#include "net/ssl/server_bound_cert_store.h"
+
+namespace net {
+
+ServerBoundCertStore::ServerBoundCert::ServerBoundCert() {
+}
+
+ServerBoundCertStore::ServerBoundCert::ServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert)
+ : server_identifier_(server_identifier),
+ creation_time_(creation_time),
+ expiration_time_(expiration_time),
+ private_key_(private_key),
+ cert_(cert) {}
+
+ServerBoundCertStore::ServerBoundCert::~ServerBoundCert() {}
+
+void ServerBoundCertStore::InitializeFrom(const ServerBoundCertList& list) {
+ for (ServerBoundCertList::const_iterator i = list.begin(); i != list.end();
+ ++i) {
+ SetServerBoundCert(i->server_identifier(), i->creation_time(),
+ i->expiration_time(), i->private_key(), i->cert());
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/server_bound_cert_store.h b/chromium/net/ssl/server_bound_cert_store.h
new file mode 100644
index 00000000000..0de0f3eebd8
--- /dev/null
+++ b/chromium/net/ssl/server_bound_cert_store.h
@@ -0,0 +1,131 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_SERVER_BOUND_CERT_STORE_H_
+#define NET_SSL_SERVER_BOUND_CERT_STORE_H_
+
+#include <list>
+#include <string>
+
+#include "base/callback.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// An interface for storing and retrieving server bound certs.
+// There isn't a domain bound certs spec yet, but the old origin bound
+// certificates are specified in
+// http://balfanz.github.com/tls-obc-spec/draft-balfanz-tls-obc-01.html.
+
+// Owned only by a single ServerBoundCertService object, which is responsible
+// for deleting it.
+class NET_EXPORT ServerBoundCertStore
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // The ServerBoundCert class contains a private key in addition to the server
+ // cert.
+ class NET_EXPORT ServerBoundCert {
+ public:
+ ServerBoundCert();
+ ServerBoundCert(const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert);
+ ~ServerBoundCert();
+
+ // Server identifier. For domain bound certs, for instance "verisign.com".
+ const std::string& server_identifier() const { return server_identifier_; }
+ // The time the certificate was created, also the start of the certificate
+ // validity period.
+ base::Time creation_time() const { return creation_time_; }
+ // The time after which this certificate is no longer valid.
+ base::Time expiration_time() const { return expiration_time_; }
+ // The encoding of the private key depends on the type.
+ // rsa_sign: DER-encoded PrivateKeyInfo struct.
+ // ecdsa_sign: DER-encoded EncryptedPrivateKeyInfo struct.
+ const std::string& private_key() const { return private_key_; }
+ // DER-encoded certificate.
+ const std::string& cert() const { return cert_; }
+
+ private:
+ std::string server_identifier_;
+ base::Time creation_time_;
+ base::Time expiration_time_;
+ std::string private_key_;
+ std::string cert_;
+ };
+
+ typedef std::list<ServerBoundCert> ServerBoundCertList;
+
+ typedef base::Callback<void(
+ int,
+ const std::string&,
+ base::Time,
+ const std::string&,
+ const std::string&)> GetCertCallback;
+ typedef base::Callback<void(const ServerBoundCertList&)> GetCertListCallback;
+
+ virtual ~ServerBoundCertStore() {}
+
+ // GetServerBoundCert may return the result synchronously through the
+ // output parameters, in which case it will return either OK if a cert is
+ // found in the store, or ERR_FILE_NOT_FOUND if none is found. If the
+ // result cannot be returned synchronously, GetServerBoundCert will
+ // return ERR_IO_PENDING and the callback will be called with the result
+ // asynchronously.
+ virtual int GetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time* expiration_time,
+ std::string* private_key_result,
+ std::string* cert_result,
+ const GetCertCallback& callback) = 0;
+
+ // Adds a server bound cert and the corresponding private key to the store.
+ virtual void SetServerBoundCert(
+ const std::string& server_identifier,
+ base::Time creation_time,
+ base::Time expiration_time,
+ const std::string& private_key,
+ const std::string& cert) = 0;
+
+ // Removes a server bound cert and the corresponding private key from the
+ // store.
+ virtual void DeleteServerBoundCert(
+ const std::string& server_identifier,
+ const base::Closure& completion_callback) = 0;
+
+ // Deletes all of the server bound certs that have a creation_date greater
+ // than or equal to |delete_begin| and less than |delete_end|. If a
+ // base::Time value is_null, that side of the comparison is unbounded.
+ virtual void DeleteAllCreatedBetween(
+ base::Time delete_begin,
+ base::Time delete_end,
+ const base::Closure& completion_callback) = 0;
+
+ // Removes all server bound certs and the corresponding private keys from
+ // the store.
+ virtual void DeleteAll(const base::Closure& completion_callback) = 0;
+
+ // Returns all server bound certs and the corresponding private keys.
+ virtual void GetAllServerBoundCerts(const GetCertListCallback& callback) = 0;
+
+ // Helper function that adds all certs from |list| into this instance.
+ void InitializeFrom(const ServerBoundCertList& list);
+
+ // Returns the number of certs in the store. May return 0 if the backing
+ // store is not loaded yet.
+ // Public only for unit testing.
+ virtual int GetCertCount() = 0;
+
+ // When invoked, instructs the store to keep session related data on
+ // destruction.
+ virtual void SetForceKeepSessionState() = 0;
+};
+
+} // namespace net
+
+#endif // NET_SSL_SERVER_BOUND_CERT_STORE_H_
diff --git a/chromium/net/ssl/ssl_cert_request_info.cc b/chromium/net/ssl/ssl_cert_request_info.cc
new file mode 100644
index 00000000000..10750b6a44c
--- /dev/null
+++ b/chromium/net/ssl/ssl_cert_request_info.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 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/ssl/ssl_cert_request_info.h"
+
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+SSLCertRequestInfo::SSLCertRequestInfo() : is_proxy(false) {
+}
+
+void SSLCertRequestInfo::Reset() {
+ host_and_port.clear();
+ is_proxy = false;
+ cert_authorities.clear();
+ cert_key_types.clear();
+ client_certs.clear();
+}
+
+SSLCertRequestInfo::~SSLCertRequestInfo() {
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_cert_request_info.h b/chromium/net/ssl/ssl_cert_request_info.h
new file mode 100644
index 00000000000..fecffb8f31d
--- /dev/null
+++ b/chromium/net/ssl/ssl_cert_request_info.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 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_SSL_SSL_CERT_REQUEST_INFO_H_
+#define NET_SSL_SSL_CERT_REQUEST_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/ssl/ssl_client_cert_type.h"
+
+namespace net {
+
+class X509Certificate;
+
+// The SSLCertRequestInfo class represents server criteria regarding client
+// certificate required for a secure connection.
+//
+// In TLS 1.1, the CertificateRequest
+// message is defined as:
+// enum {
+// rsa_sign(1), dss_sign(2), rsa_fixed_dh(3), dss_fixed_dh(4),
+// rsa_ephemeral_dh_RESERVED(5), dss_ephemeral_dh_RESERVED(6),
+// fortezza_dms_RESERVED(20), (255)
+// } ClientCertificateType;
+//
+// opaque DistinguishedName<1..2^16-1>;
+//
+// struct {
+// ClientCertificateType certificate_types<1..2^8-1>;
+// DistinguishedName certificate_authorities<3..2^16-1>;
+// } CertificateRequest;
+class NET_EXPORT SSLCertRequestInfo
+ : public base::RefCountedThreadSafe<SSLCertRequestInfo> {
+ public:
+ SSLCertRequestInfo();
+
+ void Reset();
+
+ // The host and port of the SSL server that requested client authentication.
+ std::string host_and_port;
+
+ // True if the server that issues this request was the HTTPS proxy used in
+ // the request. False, if the server was the origin server.
+ bool is_proxy;
+
+ // List of DER-encoded X.509 DistinguishedName of certificate authorities
+ // allowed by the server.
+ std::vector<std::string> cert_authorities;
+
+ std::vector<SSLClientCertType> cert_key_types;
+
+ // Client certificates matching the server criteria. This should be removed
+ // soon as being tracked in http://crbug.com/166642.
+ std::vector<scoped_refptr<X509Certificate> > client_certs;
+
+ private:
+ friend class base::RefCountedThreadSafe<SSLCertRequestInfo>;
+
+ ~SSLCertRequestInfo();
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CERT_REQUEST_INFO_H_
diff --git a/chromium/net/ssl/ssl_cipher_suite_names.cc b/chromium/net/ssl/ssl_cipher_suite_names.cc
new file mode 100644
index 00000000000..f12d017fe76
--- /dev/null
+++ b/chromium/net/ssl/ssl_cipher_suite_names.cc
@@ -0,0 +1,342 @@
+// Copyright (c) 2011 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/ssl/ssl_cipher_suite_names.h"
+
+#include <stdlib.h>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+
+// Rather than storing the names of all the ciphersuites we eliminate the
+// redundancy and break each cipher suite into a key exchange method, cipher
+// and mac. For all the ciphersuites in the IANA registry, we extract each of
+// those components from the name, number them and pack the result into a
+// 16-bit number thus:
+// (MSB to LSB)
+// <3 bits> unused
+// <5 bits> key exchange
+// <5 bits> cipher
+// <3 bits> mac
+
+// The following tables were generated by ssl_cipher_suite_names_generate.go,
+// found in the same directory as this file.
+
+struct CipherSuite {
+ uint16 cipher_suite, encoded;
+};
+
+static const struct CipherSuite kCipherSuites[] = {
+ {0x0, 0x0}, // TLS_NULL_WITH_NULL_NULL
+ {0x1, 0x101}, // TLS_RSA_WITH_NULL_MD5
+ {0x2, 0x102}, // TLS_RSA_WITH_NULL_SHA
+ {0x3, 0x209}, // TLS_RSA_EXPORT_WITH_RC4_40_MD5
+ {0x4, 0x111}, // TLS_RSA_WITH_RC4_128_MD5
+ {0x5, 0x112}, // TLS_RSA_WITH_RC4_128_SHA
+ {0x6, 0x219}, // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ {0x7, 0x122}, // TLS_RSA_WITH_IDEA_CBC_SHA
+ {0x8, 0x22a}, // TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0x9, 0x132}, // TLS_RSA_WITH_DES_CBC_SHA
+ {0xa, 0x13a}, // TLS_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xb, 0x32a}, // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA
+ {0xc, 0x432}, // TLS_DH_DSS_WITH_DES_CBC_SHA
+ {0xd, 0x43a}, // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA
+ {0xe, 0x52a}, // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0xf, 0x632}, // TLS_DH_RSA_WITH_DES_CBC_SHA
+ {0x10, 0x63a}, // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA
+ {0x11, 0x72a}, // TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ {0x12, 0x832}, // TLS_DHE_DSS_WITH_DES_CBC_SHA
+ {0x13, 0x83a}, // TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ {0x14, 0x92a}, // TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ {0x15, 0xa32}, // TLS_DHE_RSA_WITH_DES_CBC_SHA
+ {0x16, 0xa3a}, // TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ {0x17, 0xb09}, // TLS_DH_anon_EXPORT_WITH_RC4_40_MD5
+ {0x18, 0xc11}, // TLS_DH_anon_WITH_RC4_128_MD5
+ {0x19, 0xb2a}, // TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA
+ {0x1a, 0xc32}, // TLS_DH_anon_WITH_DES_CBC_SHA
+ {0x1b, 0xc3a}, // TLS_DH_anon_WITH_3DES_EDE_CBC_SHA
+ {0x2f, 0x142}, // TLS_RSA_WITH_AES_128_CBC_SHA
+ {0x30, 0x442}, // TLS_DH_DSS_WITH_AES_128_CBC_SHA
+ {0x31, 0x642}, // TLS_DH_RSA_WITH_AES_128_CBC_SHA
+ {0x32, 0x842}, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA
+ {0x33, 0xa42}, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA
+ {0x34, 0xc42}, // TLS_DH_anon_WITH_AES_128_CBC_SHA
+ {0x35, 0x14a}, // TLS_RSA_WITH_AES_256_CBC_SHA
+ {0x36, 0x44a}, // TLS_DH_DSS_WITH_AES_256_CBC_SHA
+ {0x37, 0x64a}, // TLS_DH_RSA_WITH_AES_256_CBC_SHA
+ {0x38, 0x84a}, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA
+ {0x39, 0xa4a}, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ {0x3a, 0xc4a}, // TLS_DH_anon_WITH_AES_256_CBC_SHA
+ {0x3b, 0x103}, // TLS_RSA_WITH_NULL_SHA256
+ {0x3c, 0x143}, // TLS_RSA_WITH_AES_128_CBC_SHA256
+ {0x3d, 0x14b}, // TLS_RSA_WITH_AES_256_CBC_SHA256
+ {0x3e, 0x443}, // TLS_DH_DSS_WITH_AES_128_CBC_SHA256
+ {0x3f, 0x643}, // TLS_DH_RSA_WITH_AES_128_CBC_SHA256
+ {0x40, 0x843}, // TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
+ {0x41, 0x152}, // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x42, 0x452}, // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA
+ {0x43, 0x652}, // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x44, 0x852}, // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
+ {0x45, 0xa52}, // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
+ {0x46, 0xc52}, // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA
+ {0x67, 0xa43}, // TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
+ {0x68, 0x44b}, // TLS_DH_DSS_WITH_AES_256_CBC_SHA256
+ {0x69, 0x64b}, // TLS_DH_RSA_WITH_AES_256_CBC_SHA256
+ {0x6a, 0x84b}, // TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
+ {0x6b, 0xa4b}, // TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
+ {0x6c, 0xc43}, // TLS_DH_anon_WITH_AES_128_CBC_SHA256
+ {0x6d, 0xc4b}, // TLS_DH_anon_WITH_AES_256_CBC_SHA256
+ {0x84, 0x15a}, // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x85, 0x45a}, // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA
+ {0x86, 0x65a}, // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x87, 0x85a}, // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
+ {0x88, 0xa5a}, // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
+ {0x89, 0xc5a}, // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA
+ {0x96, 0x162}, // TLS_RSA_WITH_SEED_CBC_SHA
+ {0x97, 0x462}, // TLS_DH_DSS_WITH_SEED_CBC_SHA
+ {0x98, 0x662}, // TLS_DH_RSA_WITH_SEED_CBC_SHA
+ {0x99, 0x862}, // TLS_DHE_DSS_WITH_SEED_CBC_SHA
+ {0x9a, 0xa62}, // TLS_DHE_RSA_WITH_SEED_CBC_SHA
+ {0x9b, 0xc62}, // TLS_DH_anon_WITH_SEED_CBC_SHA
+ {0x9c, 0x16f}, // TLS_RSA_WITH_AES_128_GCM_SHA256
+ {0x9d, 0x177}, // TLS_RSA_WITH_AES_256_GCM_SHA384
+ {0x9e, 0xa6f}, // TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
+ {0x9f, 0xa77}, // TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
+ {0xa0, 0x66f}, // TLS_DH_RSA_WITH_AES_128_GCM_SHA256
+ {0xa1, 0x677}, // TLS_DH_RSA_WITH_AES_256_GCM_SHA384
+ {0xa2, 0x86f}, // TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
+ {0xa3, 0x877}, // TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
+ {0xa4, 0x46f}, // TLS_DH_DSS_WITH_AES_128_GCM_SHA256
+ {0xa5, 0x477}, // TLS_DH_DSS_WITH_AES_256_GCM_SHA384
+ {0xa6, 0xc6f}, // TLS_DH_anon_WITH_AES_128_GCM_SHA256
+ {0xa7, 0xc77}, // TLS_DH_anon_WITH_AES_256_GCM_SHA384
+ {0xba, 0x153}, // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbb, 0x453}, // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbc, 0x653}, // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbd, 0x853}, // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbe, 0xa53}, // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xbf, 0xc53}, // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc0, 0x15b}, // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc1, 0x45b}, // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc2, 0x65b}, // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc3, 0x85b}, // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc4, 0xa5b}, // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc5, 0xc5b}, // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256
+ {0xc001, 0xd02}, // TLS_ECDH_ECDSA_WITH_NULL_SHA
+ {0xc002, 0xd12}, // TLS_ECDH_ECDSA_WITH_RC4_128_SHA
+ {0xc003, 0xd3a}, // TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
+ {0xc004, 0xd42}, // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
+ {0xc005, 0xd4a}, // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
+ {0xc006, 0xe02}, // TLS_ECDHE_ECDSA_WITH_NULL_SHA
+ {0xc007, 0xe12}, // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
+ {0xc008, 0xe3a}, // TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
+ {0xc009, 0xe42}, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
+ {0xc00a, 0xe4a}, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
+ {0xc00b, 0xf02}, // TLS_ECDH_RSA_WITH_NULL_SHA
+ {0xc00c, 0xf12}, // TLS_ECDH_RSA_WITH_RC4_128_SHA
+ {0xc00d, 0xf3a}, // TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xc00e, 0xf42}, // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
+ {0xc00f, 0xf4a}, // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
+ {0xc010, 0x1002}, // TLS_ECDHE_RSA_WITH_NULL_SHA
+ {0xc011, 0x1012}, // TLS_ECDHE_RSA_WITH_RC4_128_SHA
+ {0xc012, 0x103a}, // TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
+ {0xc013, 0x1042}, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
+ {0xc014, 0x104a}, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
+ {0xc015, 0x1102}, // TLS_ECDH_anon_WITH_NULL_SHA
+ {0xc016, 0x1112}, // TLS_ECDH_anon_WITH_RC4_128_SHA
+ {0xc017, 0x113a}, // TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA
+ {0xc018, 0x1142}, // TLS_ECDH_anon_WITH_AES_128_CBC_SHA
+ {0xc019, 0x114a}, // TLS_ECDH_anon_WITH_AES_256_CBC_SHA
+ {0xc023, 0xe43}, // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
+ {0xc024, 0xe4c}, // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
+ {0xc025, 0xd43}, // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
+ {0xc026, 0xd4c}, // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
+ {0xc027, 0x1043}, // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
+ {0xc028, 0x104c}, // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
+ {0xc029, 0xf43}, // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
+ {0xc02a, 0xf4c}, // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
+ {0xc02b, 0xe6f}, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+ {0xc02c, 0xe77}, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ {0xc02d, 0xd6f}, // TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
+ {0xc02e, 0xd77}, // TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
+ {0xc02f, 0x106f}, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ {0xc030, 0x1077}, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ {0xc031, 0xf6f}, // TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
+ {0xc032, 0xf77}, // TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
+ {0xc072, 0xe53}, // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc073, 0xe5c}, // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384
+ {0xc074, 0xd53}, // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc075, 0xd5c}, // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384
+ {0xc076, 0x1053}, // TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc077, 0x105c}, // TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384
+ {0xc078, 0xf53}, // TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ {0xc079, 0xf5c}, // TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384
+ {0xc07a, 0x17f}, // TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc07b, 0x187}, // TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc07c, 0xa7f}, // TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc07d, 0xa87}, // TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc07e, 0x67f}, // TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc07f, 0x687}, // TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc080, 0x87f}, // TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc081, 0x887}, // TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc082, 0x47f}, // TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc083, 0x487}, // TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc084, 0xc7f}, // TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc085, 0xc87}, // TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc086, 0xe7f}, // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc087, 0xe87}, // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc088, 0xd7f}, // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc089, 0xd87}, // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc08a, 0x107f}, // TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc08b, 0x1087}, // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384
+ {0xc08c, 0xf7f}, // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256
+ {0xc08d, 0xf87}, // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384
+};
+
+static const struct {
+ char name[15];
+} kKeyExchangeNames[18] = {
+ {"NULL"}, // 0
+ {"RSA"}, // 1
+ {"RSA_EXPORT"}, // 2
+ {"DH_DSS_EXPORT"}, // 3
+ {"DH_DSS"}, // 4
+ {"DH_RSA_EXPORT"}, // 5
+ {"DH_RSA"}, // 6
+ {"DHE_DSS_EXPORT"}, // 7
+ {"DHE_DSS"}, // 8
+ {"DHE_RSA_EXPORT"}, // 9
+ {"DHE_RSA"}, // 10
+ {"DH_anon_EXPORT"}, // 11
+ {"DH_anon"}, // 12
+ {"ECDH_ECDSA"}, // 13
+ {"ECDHE_ECDSA"}, // 14
+ {"ECDH_RSA"}, // 15
+ {"ECDHE_RSA"}, // 16
+ {"ECDH_anon"}, // 17
+};
+
+static const struct {
+ char name[17];
+} kCipherNames[17] = {
+ {"NULL"}, // 0
+ {"RC4_40"}, // 1
+ {"RC4_128"}, // 2
+ {"RC2_CBC_40"}, // 3
+ {"IDEA_CBC"}, // 4
+ {"DES40_CBC"}, // 5
+ {"DES_CBC"}, // 6
+ {"3DES_EDE_CBC"}, // 7
+ {"AES_128_CBC"}, // 8
+ {"AES_256_CBC"}, // 9
+ {"CAMELLIA_128_CBC"}, // 10
+ {"CAMELLIA_256_CBC"}, // 11
+ {"SEED_CBC"}, // 12
+ {"AES_128_GCM"}, // 13
+ {"AES_256_GCM"}, // 14
+ {"CAMELLIA_128_GCM"}, // 15
+ {"CAMELLIA_256_GCM"}, // 16
+};
+
+static const struct {
+ char name[7];
+} kMacNames[5] = {
+ {"NULL"}, // 0
+ {"MD5"}, // 1
+ {"SHA1"}, // 2
+ {"SHA256"}, // 3
+ {"SHA384"}, // 4
+ // 7 is reserved to indicate an AEAD cipher suite.
+};
+
+static const int kAEADMACValue = 7;
+
+namespace net {
+
+static int CipherSuiteCmp(const void* ia, const void* ib) {
+ const CipherSuite* a = static_cast<const CipherSuite*>(ia);
+ const CipherSuite* b = static_cast<const CipherSuite*>(ib);
+
+ if (a->cipher_suite < b->cipher_suite) {
+ return -1;
+ } else if (a->cipher_suite == b->cipher_suite) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+void SSLCipherSuiteToStrings(const char** key_exchange_str,
+ const char** cipher_str,
+ const char** mac_str,
+ bool *is_aead,
+ uint16 cipher_suite) {
+ *key_exchange_str = *cipher_str = *mac_str = "???";
+ *is_aead = false;
+
+ struct CipherSuite desired = {0};
+ desired.cipher_suite = cipher_suite;
+
+ void* r = bsearch(&desired, kCipherSuites,
+ arraysize(kCipherSuites), sizeof(kCipherSuites[0]),
+ CipherSuiteCmp);
+
+ if (!r)
+ return;
+
+ const CipherSuite* cs = static_cast<CipherSuite*>(r);
+
+ const int key_exchange = cs->encoded >> 8;
+ const int cipher = (cs->encoded >> 3) & 0x1f;
+ const int mac = cs->encoded & 0x7;
+
+ *key_exchange_str = kKeyExchangeNames[key_exchange].name;
+ *cipher_str = kCipherNames[cipher].name;
+ if (mac == kAEADMACValue) {
+ *is_aead = true;
+ *mac_str = NULL;
+ } else {
+ *mac_str = kMacNames[mac].name;
+ }
+}
+
+void SSLVersionToString(const char** name, int ssl_version) {
+ switch (ssl_version) {
+ case SSL_CONNECTION_VERSION_SSL2:
+ *name = "SSL 2.0";
+ break;
+ case SSL_CONNECTION_VERSION_SSL3:
+ *name = "SSL 3.0";
+ break;
+ case SSL_CONNECTION_VERSION_TLS1:
+ *name = "TLS 1.0";
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_1:
+ *name = "TLS 1.1";
+ break;
+ case SSL_CONNECTION_VERSION_TLS1_2:
+ *name = "TLS 1.2";
+ break;
+ default:
+ NOTREACHED() << ssl_version;
+ *name = "???";
+ break;
+ }
+}
+
+bool ParseSSLCipherString(const std::string& cipher_string,
+ uint16* cipher_suite) {
+ int value = 0;
+ if (cipher_string.size() == 6 &&
+ StartsWithASCII(cipher_string, "0x", false /* case insensitive */) &&
+ base::HexStringToInt(cipher_string, &value)) {
+ *cipher_suite = static_cast<uint16>(value);
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_cipher_suite_names.h b/chromium/net/ssl/ssl_cipher_suite_names.h
new file mode 100644
index 00000000000..5145fb24c5e
--- /dev/null
+++ b/chromium/net/ssl/ssl_cipher_suite_names.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 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_SSL_SSL_CIPHER_SUITE_NAMES_H_
+#define NET_SSL_SSL_CIPHER_SUITE_NAMES_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// SSLCipherSuiteToStrings returns three strings for a given cipher suite
+// number, the name of the key exchange algorithm, the name of the cipher and
+// the name of the MAC. The cipher suite number is the number as sent on the
+// wire and recorded at
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
+// If the cipher suite is unknown, the strings are set to "???".
+// In the case of an AEAD cipher suite, *mac_str is NULL and *is_aead is true.
+NET_EXPORT void SSLCipherSuiteToStrings(const char** key_exchange_str,
+ const char** cipher_str,
+ const char** mac_str,
+ bool* is_aead,
+ uint16 cipher_suite);
+
+// SSLVersionToString returns the name of the SSL protocol version
+// specified by |ssl_version|, which is defined in
+// net/ssl/ssl_connection_status_flags.h.
+// If the version is unknown, |name| is set to "???".
+NET_EXPORT void SSLVersionToString(const char** name, int ssl_version);
+
+// Parses a string literal that represents a SSL/TLS cipher suite.
+//
+// Supported literal forms:
+// 0xAABB, where AA is cipher_suite[0] and BB is cipher_suite[1], as
+// defined in RFC 2246, Section 7.4.1.2. Unrecognized but parsable cipher
+// suites in this form will not return an error.
+//
+// Returns true if the cipher suite was successfully parsed, storing the
+// result in |cipher_suite|.
+//
+// TODO(rsleevi): Support the full strings defined in the IANA TLS parameters
+// list.
+NET_EXPORT bool ParseSSLCipherString(const std::string& cipher_string,
+ uint16* cipher_suite);
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CIPHER_SUITE_NAMES_H_
diff --git a/chromium/net/ssl/ssl_cipher_suite_names_unittest.cc b/chromium/net/ssl/ssl_cipher_suite_names_unittest.cc
new file mode 100644
index 00000000000..a9133fb67ea
--- /dev/null
+++ b/chromium/net/ssl/ssl_cipher_suite_names_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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/ssl/ssl_cipher_suite_names.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(CipherSuiteNamesTest, Basic) {
+ const char *key_exchange, *cipher, *mac;
+ bool is_aead;
+
+ SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead, 0xc001);
+ EXPECT_STREQ("ECDH_ECDSA", key_exchange);
+ EXPECT_STREQ("NULL", cipher);
+ EXPECT_STREQ("SHA1", mac);
+ EXPECT_FALSE(is_aead);
+
+ SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead, 0x009f);
+ EXPECT_STREQ("DHE_RSA", key_exchange);
+ EXPECT_STREQ("AES_256_GCM", cipher);
+ EXPECT_TRUE(is_aead);
+ EXPECT_EQ(NULL, mac);
+
+ SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead, 0xff31);
+ EXPECT_STREQ("???", key_exchange);
+ EXPECT_STREQ("???", cipher);
+ EXPECT_STREQ("???", mac);
+ EXPECT_FALSE(is_aead);
+}
+
+TEST(CipherSuiteNamesTest, ParseSSLCipherString) {
+ uint16 cipher_suite = 0;
+ EXPECT_TRUE(ParseSSLCipherString("0x0004", &cipher_suite));
+ EXPECT_EQ(0x00004u, cipher_suite);
+
+ EXPECT_TRUE(ParseSSLCipherString("0xBEEF", &cipher_suite));
+ EXPECT_EQ(0xBEEFu, cipher_suite);
+}
+
+TEST(CipherSuiteNamesTest, ParseSSLCipherStringFails) {
+ const char* const cipher_strings[] = {
+ "0004",
+ "0x004",
+ "0xBEEFY",
+ };
+
+ for (size_t i = 0; i < arraysize(cipher_strings); ++i) {
+ uint16 cipher_suite = 0;
+ EXPECT_FALSE(ParseSSLCipherString(cipher_strings[i], &cipher_suite));
+ }
+}
+
+} // anonymous namespace
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_client_auth_cache.cc b/chromium/net/ssl/ssl_client_auth_cache.cc
new file mode 100644
index 00000000000..0c0704e6208
--- /dev/null
+++ b/chromium/net/ssl/ssl_client_auth_cache.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 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/ssl/ssl_client_auth_cache.h"
+
+#include "base/logging.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+SSLClientAuthCache::SSLClientAuthCache() {
+ CertDatabase::GetInstance()->AddObserver(this);
+}
+
+SSLClientAuthCache::~SSLClientAuthCache() {
+ CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+bool SSLClientAuthCache::Lookup(
+ const std::string& server,
+ scoped_refptr<X509Certificate>* certificate) {
+ DCHECK(certificate);
+
+ AuthCacheMap::iterator iter = cache_.find(server);
+ if (iter == cache_.end())
+ return false;
+
+ *certificate = iter->second;
+ return true;
+}
+
+void SSLClientAuthCache::Add(const std::string& server,
+ X509Certificate* value) {
+ cache_[server] = value;
+
+ // TODO(wtc): enforce a maximum number of entries.
+}
+
+void SSLClientAuthCache::Remove(const std::string& server) {
+ cache_.erase(server);
+}
+
+void SSLClientAuthCache::OnCertAdded(const X509Certificate* cert) {
+ cache_.clear();
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_client_auth_cache.h b/chromium/net/ssl/ssl_client_auth_cache.h
new file mode 100644
index 00000000000..250841b1b25
--- /dev/null
+++ b/chromium/net/ssl/ssl_client_auth_cache.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 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_SSL_SSL_CLIENT_AUTH_CACHE_H_
+#define NET_SSL_SSL_CLIENT_AUTH_CACHE_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_database.h"
+
+namespace net {
+
+class X509Certificate;
+
+// The SSLClientAuthCache class is a simple cache structure to store SSL
+// client certificates. Provides lookup, insertion, and deletion of entries.
+// The parameter for doing lookups, insertions, and deletions is the server's
+// host and port.
+//
+// TODO(wtc): This class is based on FtpAuthCache. We can extract the common
+// code to a template class.
+class NET_EXPORT_PRIVATE SSLClientAuthCache : public CertDatabase::Observer {
+ public:
+ SSLClientAuthCache();
+ virtual ~SSLClientAuthCache();
+
+ // Checks for a client certificate preference for SSL server at |server|.
+ // Returns true if a preference is found, and sets |*certificate| to the
+ // desired client certificate. The desired certificate may be NULL, which
+ // indicates a preference to not send any certificate to |server|.
+ // If a certificate preference is not found, returns false.
+ bool Lookup(const std::string& server,
+ scoped_refptr<X509Certificate>* certificate);
+
+ // Add a client certificate for |server| to the cache. If there is already
+ // a client certificate for |server|, it will be overwritten. A NULL
+ // |client_cert| indicates a preference that no client certificate should
+ // be sent to |server|.
+ void Add(const std::string& server, X509Certificate* client_cert);
+
+ // Remove the client certificate for |server| from the cache, if one exists.
+ void Remove(const std::string& server);
+
+ // CertDatabase::Observer methods:
+ virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE;
+
+ private:
+ typedef std::string AuthCacheKey;
+ typedef scoped_refptr<X509Certificate> AuthCacheValue;
+ typedef std::map<AuthCacheKey, AuthCacheValue> AuthCacheMap;
+
+ // internal representation of cache, an STL map.
+ AuthCacheMap cache_;
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CLIENT_AUTH_CACHE_H_
diff --git a/chromium/net/ssl/ssl_client_auth_cache_unittest.cc b/chromium/net/ssl/ssl_client_auth_cache_unittest.cc
new file mode 100644
index 00000000000..6b4b8c3b4b9
--- /dev/null
+++ b/chromium/net/ssl/ssl_client_auth_cache_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2009 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/ssl/ssl_client_auth_cache.h"
+
+#include "base/time/time.h"
+#include "net/cert/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(SSLClientAuthCacheTest, LookupAddRemove) {
+ SSLClientAuthCache cache;
+
+ base::Time start_date = base::Time::Now();
+ base::Time expiration_date = start_date + base::TimeDelta::FromDays(1);
+
+ std::string server1("foo1:443");
+ scoped_refptr<X509Certificate> cert1(
+ new X509Certificate("foo1", "CA", start_date, expiration_date));
+
+ std::string server2("foo2:443");
+ scoped_refptr<X509Certificate> cert2(
+ new X509Certificate("foo2", "CA", start_date, expiration_date));
+
+ std::string server3("foo3:443");
+ scoped_refptr<X509Certificate> cert3(
+ new X509Certificate("foo3", "CA", start_date, expiration_date));
+
+ scoped_refptr<X509Certificate> cached_cert;
+ // Lookup non-existent client certificate.
+ cached_cert = NULL;
+ EXPECT_FALSE(cache.Lookup(server1, &cached_cert));
+
+ // Add client certificate for server1.
+ cache.Add(server1, cert1.get());
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert1, cached_cert);
+
+ // Add client certificate for server2.
+ cache.Add(server2, cert2.get());
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert1, cached_cert.get());
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(cert2, cached_cert);
+
+ // Overwrite the client certificate for server1.
+ cache.Add(server1, cert3.get());
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert3, cached_cert);
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(cert2, cached_cert);
+
+ // Remove client certificate of server1.
+ cache.Remove(server1);
+ cached_cert = NULL;
+ EXPECT_FALSE(cache.Lookup(server1, &cached_cert));
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(cert2, cached_cert);
+
+ // Remove non-existent client certificate.
+ cache.Remove(server1);
+ cached_cert = NULL;
+ EXPECT_FALSE(cache.Lookup(server1, &cached_cert));
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(cert2, cached_cert);
+}
+
+// Check that if the server differs only by port number, it is considered
+// a separate server.
+TEST(SSLClientAuthCacheTest, LookupWithPort) {
+ SSLClientAuthCache cache;
+
+ base::Time start_date = base::Time::Now();
+ base::Time expiration_date = start_date + base::TimeDelta::FromDays(1);
+
+ std::string server1("foo:443");
+ scoped_refptr<X509Certificate> cert1(
+ new X509Certificate("foo", "CA", start_date, expiration_date));
+
+ std::string server2("foo:8443");
+ scoped_refptr<X509Certificate> cert2(
+ new X509Certificate("foo", "CA", start_date, expiration_date));
+
+ cache.Add(server1, cert1.get());
+ cache.Add(server2, cert2.get());
+
+ scoped_refptr<X509Certificate> cached_cert;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert1.get(), cached_cert);
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(cert2.get(), cached_cert);
+}
+
+// Check that the a NULL certificate, indicating the user has declined to send
+// a certificate, is properly cached.
+TEST(SSLClientAuthCacheTest, LookupNullPreference) {
+ SSLClientAuthCache cache;
+ base::Time start_date = base::Time::Now();
+ base::Time expiration_date = start_date + base::TimeDelta::FromDays(1);
+
+ std::string server1("foo:443");
+ scoped_refptr<X509Certificate> cert1(
+ new X509Certificate("foo", "CA", start_date, expiration_date));
+
+ cache.Add(server1, NULL);
+
+ scoped_refptr<X509Certificate> cached_cert(cert1);
+ // Make sure that |cached_cert| is updated to NULL, indicating the user
+ // declined to send a certificate to |server1|.
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(NULL, cached_cert.get());
+
+ // Remove the existing cached certificate.
+ cache.Remove(server1);
+ cached_cert = NULL;
+ EXPECT_FALSE(cache.Lookup(server1, &cached_cert));
+
+ // Add a new preference for a specific certificate.
+ cache.Add(server1, cert1.get());
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert1, cached_cert);
+
+ // Replace the specific preference with a NULL certificate.
+ cache.Add(server1, NULL);
+ cached_cert = NULL;
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(NULL, cached_cert.get());
+}
+
+// Check that the OnCertAdded() method removes all cache entries.
+TEST(SSLClientAuthCacheTest, OnCertAdded) {
+ SSLClientAuthCache cache;
+ base::Time start_date = base::Time::Now();
+ base::Time expiration_date = start_date + base::TimeDelta::FromDays(1);
+
+ std::string server1("foo:443");
+ scoped_refptr<X509Certificate> cert1(
+ new X509Certificate("foo", "CA", start_date, expiration_date));
+
+ cache.Add(server1, cert1.get());
+
+ std::string server2("foo2:443");
+ cache.Add(server2, NULL);
+
+ scoped_refptr<X509Certificate> cached_cert;
+
+ // Demonstrate the set up is correct.
+ EXPECT_TRUE(cache.Lookup(server1, &cached_cert));
+ EXPECT_EQ(cert1, cached_cert);
+
+ EXPECT_TRUE(cache.Lookup(server2, &cached_cert));
+ EXPECT_EQ(NULL, cached_cert.get());
+
+ cache.OnCertAdded(NULL);
+
+ // Check that we no longer have entries for either server.
+ EXPECT_FALSE(cache.Lookup(server1, &cached_cert));
+ EXPECT_FALSE(cache.Lookup(server2, &cached_cert));
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_client_cert_type.h b/chromium/net/ssl/ssl_client_cert_type.h
new file mode 100644
index 00000000000..dd6e997a1c5
--- /dev/null
+++ b/chromium/net/ssl/ssl_client_cert_type.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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_SSL_SSL_CLIENT_CERT_TYPE_H_
+#define NET_SSL_SSL_CLIENT_CERT_TYPE_H_
+
+namespace net {
+
+// TLS ClientCertificateType Identifiers
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-1
+enum SSLClientCertType {
+ CLIENT_CERT_RSA_SIGN = 1,
+ CLIENT_CERT_DSS_SIGN = 2,
+ CLIENT_CERT_ECDSA_SIGN = 64,
+ // 224-255 are Reserved for Private Use, we pick one to use as "invalid".
+ CLIENT_CERT_INVALID_TYPE = 255,
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CLIENT_CERT_TYPE_H_
diff --git a/chromium/net/ssl/ssl_config_service.cc b/chromium/net/ssl/ssl_config_service.cc
new file mode 100644
index 00000000000..a2c34a26852
--- /dev/null
+++ b/chromium/net/ssl/ssl_config_service.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 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.
+
+#include "net/ssl/ssl_config_service.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "net/cert/crl_set.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+
+#if defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+#endif
+
+namespace net {
+
+static uint16 g_default_version_min = SSL_PROTOCOL_VERSION_SSL3;
+
+static uint16 g_default_version_max =
+#if defined(USE_OPENSSL)
+#if defined(SSL_OP_NO_TLSv1_2)
+ SSL_PROTOCOL_VERSION_TLS1_2;
+#elif defined(SSL_OP_NO_TLSv1_1)
+ SSL_PROTOCOL_VERSION_TLS1_1;
+#else
+ SSL_PROTOCOL_VERSION_TLS1;
+#endif
+#else
+ SSL_PROTOCOL_VERSION_TLS1_2;
+#endif
+
+SSLConfig::CertAndStatus::CertAndStatus() : cert_status(0) {}
+
+SSLConfig::CertAndStatus::~CertAndStatus() {}
+
+SSLConfig::SSLConfig()
+ : rev_checking_enabled(false),
+ rev_checking_required_local_anchors(false),
+ version_min(g_default_version_min),
+ version_max(g_default_version_max),
+ cached_info_enabled(false),
+ channel_id_enabled(true),
+ false_start_enabled(true),
+ unrestricted_ssl3_fallback_enabled(false),
+ send_client_cert(false),
+ verify_ev_cert(false),
+ version_fallback(false),
+ cert_io_enabled(true) {
+}
+
+SSLConfig::~SSLConfig() {
+}
+
+bool SSLConfig::IsAllowedBadCert(X509Certificate* cert,
+ CertStatus* cert_status) const {
+ std::string der_cert;
+ if (!X509Certificate::GetDEREncoded(cert->os_cert_handle(), &der_cert))
+ return false;
+ return IsAllowedBadCert(der_cert, cert_status);
+}
+
+bool SSLConfig::IsAllowedBadCert(const base::StringPiece& der_cert,
+ CertStatus* cert_status) const {
+ for (size_t i = 0; i < allowed_bad_certs.size(); ++i) {
+ if (der_cert == allowed_bad_certs[i].der_cert) {
+ if (cert_status)
+ *cert_status = allowed_bad_certs[i].cert_status;
+ return true;
+ }
+ }
+ return false;
+}
+
+SSLConfigService::SSLConfigService()
+ : observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) {
+}
+
+static bool g_cached_info_enabled = false;
+
+// GlobalCRLSet holds a reference to the global CRLSet. It simply wraps a lock
+// around a scoped_refptr so that getting a reference doesn't race with
+// updating the CRLSet.
+class GlobalCRLSet {
+ public:
+ void Set(const scoped_refptr<CRLSet>& new_crl_set) {
+ base::AutoLock locked(lock_);
+ crl_set_ = new_crl_set;
+ }
+
+ scoped_refptr<CRLSet> Get() const {
+ base::AutoLock locked(lock_);
+ return crl_set_;
+ }
+
+ private:
+ scoped_refptr<CRLSet> crl_set_;
+ mutable base::Lock lock_;
+};
+
+base::LazyInstance<GlobalCRLSet>::Leaky g_crl_set = LAZY_INSTANCE_INITIALIZER;
+
+// static
+void SSLConfigService::SetCRLSet(scoped_refptr<CRLSet> crl_set) {
+ // Note: this can be called concurently with GetCRLSet().
+ g_crl_set.Get().Set(crl_set);
+}
+
+// static
+scoped_refptr<CRLSet> SSLConfigService::GetCRLSet() {
+ return g_crl_set.Get().Get();
+}
+
+void SSLConfigService::EnableCachedInfo() {
+ g_cached_info_enabled = true;
+}
+
+// static
+bool SSLConfigService::cached_info_enabled() {
+ return g_cached_info_enabled;
+}
+
+// static
+uint16 SSLConfigService::default_version_min() {
+ return g_default_version_min;
+}
+
+// static
+uint16 SSLConfigService::default_version_max() {
+ return g_default_version_max;
+}
+
+void SSLConfigService::AddObserver(Observer* observer) {
+ observer_list_.AddObserver(observer);
+}
+
+void SSLConfigService::RemoveObserver(Observer* observer) {
+ observer_list_.RemoveObserver(observer);
+}
+
+void SSLConfigService::NotifySSLConfigChange() {
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnSSLConfigChanged());
+}
+
+SSLConfigService::~SSLConfigService() {
+}
+
+// static
+void SSLConfigService::SetSSLConfigFlags(SSLConfig* ssl_config) {
+ ssl_config->cached_info_enabled = g_cached_info_enabled;
+}
+
+void SSLConfigService::ProcessConfigUpdate(const SSLConfig& orig_config,
+ const SSLConfig& new_config) {
+ bool config_changed =
+ (orig_config.rev_checking_enabled != new_config.rev_checking_enabled) ||
+ (orig_config.rev_checking_required_local_anchors !=
+ new_config.rev_checking_required_local_anchors) ||
+ (orig_config.version_min != new_config.version_min) ||
+ (orig_config.version_max != new_config.version_max) ||
+ (orig_config.disabled_cipher_suites !=
+ new_config.disabled_cipher_suites) ||
+ (orig_config.channel_id_enabled != new_config.channel_id_enabled) ||
+ (orig_config.false_start_enabled != new_config.false_start_enabled) ||
+ (orig_config.unrestricted_ssl3_fallback_enabled !=
+ new_config.unrestricted_ssl3_fallback_enabled);
+
+ if (config_changed)
+ NotifySSLConfigChange();
+}
+
+// static
+bool SSLConfigService::IsSNIAvailable(SSLConfigService* service) {
+ if (!service)
+ return false;
+
+ SSLConfig ssl_config;
+ service->GetSSLConfig(&ssl_config);
+ return ssl_config.version_max >= SSL_PROTOCOL_VERSION_TLS1;
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_config_service.h b/chromium/net/ssl/ssl_config_service.h
new file mode 100644
index 00000000000..a6a2bc7cbfb
--- /dev/null
+++ b/chromium/net/ssl/ssl_config_service.h
@@ -0,0 +1,230 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_SSL_CONFIG_SERVICE_H_
+#define NET_SSL_SSL_CONFIG_SERVICE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/crl_set.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+// Various TLS/SSL ProtocolVersion values encoded as uint16
+// struct {
+// uint8 major;
+// uint8 minor;
+// } ProtocolVersion;
+// The most significant byte is |major|, and the least significant byte
+// is |minor|.
+enum {
+ SSL_PROTOCOL_VERSION_SSL3 = 0x0300,
+ SSL_PROTOCOL_VERSION_TLS1 = 0x0301,
+ SSL_PROTOCOL_VERSION_TLS1_1 = 0x0302,
+ SSL_PROTOCOL_VERSION_TLS1_2 = 0x0303,
+};
+
+// A collection of SSL-related configuration settings.
+struct NET_EXPORT SSLConfig {
+ // Default to revocation checking.
+ // Default to SSL 3.0 ~ default_version_max() on.
+ SSLConfig();
+ ~SSLConfig();
+
+ // Returns true if |cert| is one of the certs in |allowed_bad_certs|.
+ // The expected cert status is written to |cert_status|. |*cert_status| can
+ // be NULL if user doesn't care about the cert status.
+ bool IsAllowedBadCert(X509Certificate* cert, CertStatus* cert_status) const;
+
+ // Same as above except works with DER encoded certificates instead
+ // of X509Certificate.
+ bool IsAllowedBadCert(const base::StringPiece& der_cert,
+ CertStatus* cert_status) const;
+
+ // rev_checking_enabled is true if online certificate revocation checking is
+ // enabled (i.e. OCSP and CRL fetching).
+ //
+ // Regardless of this flag, CRLSet checking is always enabled and locally
+ // cached revocation information will be considered.
+ bool rev_checking_enabled;
+
+ // rev_checking_required_local_anchors is true if revocation checking is
+ // required to succeed when certificates chain to local trust anchors (that
+ // is, non-public CAs). If revocation information cannot be obtained, such
+ // certificates will be treated as revoked ("hard-fail").
+ // Note: This is distinct from rev_checking_enabled. If true, it is
+ // equivalent to also setting rev_checking_enabled, but only when the
+ // certificate chain chains to a local (non-public) trust anchor.
+ bool rev_checking_required_local_anchors;
+
+ // The minimum and maximum protocol versions that are enabled.
+ // SSL 3.0 is 0x0300, TLS 1.0 is 0x0301, TLS 1.1 is 0x0302, and so on.
+ // (Use the SSL_PROTOCOL_VERSION_xxx enumerators defined above.)
+ // SSL 2.0 is not supported. If version_max < version_min, it means no
+ // protocol versions are enabled.
+ uint16 version_min;
+ uint16 version_max;
+
+ // Presorted list of cipher suites which should be explicitly prevented from
+ // being used in addition to those disabled by the net built-in policy.
+ //
+ // By default, all cipher suites supported by the underlying SSL
+ // implementation will be enabled except for:
+ // - Null encryption cipher suites.
+ // - Weak cipher suites: < 80 bits of security strength.
+ // - FORTEZZA cipher suites (obsolete).
+ // - IDEA cipher suites (RFC 5469 explains why).
+ // - Anonymous cipher suites.
+ // - ECDSA cipher suites on platforms that do not support ECDSA signed
+ // certificates, as servers may use the presence of such ciphersuites as a
+ // hint to send an ECDSA certificate.
+ // The ciphers listed in |disabled_cipher_suites| will be removed in addition
+ // to the above list.
+ //
+ // Though cipher suites are sent in TLS as "uint8 CipherSuite[2]", in
+ // big-endian form, they should be declared in host byte order, with the
+ // first uint8 occupying the most significant byte.
+ // Ex: To disable TLS_RSA_WITH_RC4_128_MD5, specify 0x0004, while to
+ // disable TLS_ECDH_ECDSA_WITH_RC4_128_SHA, specify 0xC002.
+ std::vector<uint16> disabled_cipher_suites;
+
+ bool cached_info_enabled; // True if TLS cached info extension is enabled.
+ bool channel_id_enabled; // True if TLS channel ID extension is enabled.
+ bool false_start_enabled; // True if we'll use TLS False Start.
+
+ // If |unrestricted_ssl3_fallback_enabled| is true, SSL 3.0 fallback will be
+ // enabled for all sites.
+ // If |unrestricted_ssl3_fallback_enabled| is false, SSL 3.0 fallback will be
+ // disabled for a site pinned to the Google pin list (indicating that it is a
+ // Google site).
+ bool unrestricted_ssl3_fallback_enabled;
+
+ // TODO(wtc): move the following members to a new SSLParams structure. They
+ // are not SSL configuration settings.
+
+ struct NET_EXPORT CertAndStatus {
+ CertAndStatus();
+ ~CertAndStatus();
+
+ std::string der_cert;
+ CertStatus cert_status;
+ };
+
+ // Add any known-bad SSL certificate (with its cert status) to
+ // |allowed_bad_certs| that should not trigger an ERR_CERT_* error when
+ // calling SSLClientSocket::Connect. This would normally be done in
+ // response to the user explicitly accepting the bad certificate.
+ std::vector<CertAndStatus> allowed_bad_certs;
+
+ // True if we should send client_cert to the server.
+ bool send_client_cert;
+
+ bool verify_ev_cert; // True if we should verify the certificate for EV.
+
+ bool version_fallback; // True if we are falling back to an older protocol
+ // version (one still needs to decrement
+ // version_max).
+
+ // If cert_io_enabled is false, then certificate verification will not
+ // result in additional HTTP requests. (For example: to fetch missing
+ // intermediates or to perform OCSP/CRL fetches.) It also implies that online
+ // revocation checking is disabled.
+ // NOTE: Only used by NSS.
+ bool cert_io_enabled;
+
+ // The list of application level protocols supported. If set, this will
+ // enable Next Protocol Negotiation (if supported). The order of the
+ // protocols doesn't matter expect for one case: if the server supports Next
+ // Protocol Negotiation, but there is no overlap between the server's and
+ // client's protocol sets, then the first protocol in this list will be
+ // requested by the client.
+ std::vector<std::string> next_protos;
+
+ scoped_refptr<X509Certificate> client_cert;
+};
+
+// The interface for retrieving the SSL configuration. This interface
+// does not cover setting the SSL configuration, as on some systems, the
+// SSLConfigService objects may not have direct access to the configuration, or
+// live longer than the configuration preferences.
+class NET_EXPORT SSLConfigService
+ : public base::RefCountedThreadSafe<SSLConfigService> {
+ public:
+ // Observer is notified when SSL config settings have changed.
+ class NET_EXPORT Observer {
+ public:
+ // Notify observers if SSL settings have changed. We don't check all of the
+ // data in SSLConfig, just those that qualify as a user config change.
+ // The following settings are considered user changes:
+ // rev_checking_enabled
+ // version_min
+ // version_max
+ // disabled_cipher_suites
+ // channel_id_enabled
+ // false_start_enabled
+ virtual void OnSSLConfigChanged() = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ SSLConfigService();
+
+ // May not be thread-safe, should only be called on the IO thread.
+ virtual void GetSSLConfig(SSLConfig* config) = 0;
+
+ // Sets and gets the current, global CRL set.
+ static void SetCRLSet(scoped_refptr<CRLSet> crl_set);
+ static scoped_refptr<CRLSet> GetCRLSet();
+
+ // Enables the TLS cached info extension, which allows the server to send
+ // just a digest of its certificate chain.
+ static void EnableCachedInfo();
+ static bool cached_info_enabled();
+
+ // Gets the default minimum protocol version.
+ static uint16 default_version_min();
+
+ // Gets the default maximum protocol version.
+ static uint16 default_version_max();
+
+ // Is SNI available in this configuration?
+ static bool IsSNIAvailable(SSLConfigService* service);
+
+ // Add an observer of this service.
+ void AddObserver(Observer* observer);
+
+ // Remove an observer of this service.
+ void RemoveObserver(Observer* observer);
+
+ // Calls the OnSSLConfigChanged method of registered observers. Should only be
+ // called on the IO thread.
+ void NotifySSLConfigChange();
+
+ protected:
+ friend class base::RefCountedThreadSafe<SSLConfigService>;
+
+ virtual ~SSLConfigService();
+
+ // SetFlags sets the values of several flags based on global configuration.
+ static void SetSSLConfigFlags(SSLConfig* ssl_config);
+
+ // Process before/after config update.
+ void ProcessConfigUpdate(const SSLConfig& orig_config,
+ const SSLConfig& new_config);
+
+ private:
+ ObserverList<Observer> observer_list_;
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CONFIG_SERVICE_H_
diff --git a/chromium/net/ssl/ssl_config_service_defaults.cc b/chromium/net/ssl/ssl_config_service_defaults.cc
new file mode 100644
index 00000000000..1d96977baa1
--- /dev/null
+++ b/chromium/net/ssl/ssl_config_service_defaults.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 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.
+
+#include "net/ssl/ssl_config_service_defaults.h"
+
+namespace net {
+
+SSLConfigServiceDefaults::SSLConfigServiceDefaults() {
+}
+
+void SSLConfigServiceDefaults::GetSSLConfig(SSLConfig* config) {
+ *config = default_config_;
+ SetSSLConfigFlags(config);
+}
+
+SSLConfigServiceDefaults::~SSLConfigServiceDefaults() {
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_config_service_defaults.h b/chromium/net/ssl/ssl_config_service_defaults.h
new file mode 100644
index 00000000000..85123dad02e
--- /dev/null
+++ b/chromium/net/ssl/ssl_config_service_defaults.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 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_SSL_SSL_CONFIG_SERVICE_DEFAULTS_H_
+#define NET_SSL_SSL_CONFIG_SERVICE_DEFAULTS_H_
+
+#include "net/base/net_export.h"
+#include "net/ssl/ssl_config_service.h"
+
+namespace net {
+
+// This SSLConfigService always returns the default SSLConfig settings. It is
+// mainly useful for unittests, or for platforms that do not have a native
+// implementation of SSLConfigService yet.
+class NET_EXPORT SSLConfigServiceDefaults : public SSLConfigService {
+ public:
+ SSLConfigServiceDefaults();
+
+ // Store default SSL config settings in |config|.
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE;
+
+ private:
+ virtual ~SSLConfigServiceDefaults();
+
+ // Default value of prefs.
+ const SSLConfig default_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(SSLConfigServiceDefaults);
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CONFIG_SERVICE_DEFAULTS_H_
diff --git a/chromium/net/ssl/ssl_config_service_unittest.cc b/chromium/net/ssl/ssl_config_service_unittest.cc
new file mode 100644
index 00000000000..42c8ae47b9f
--- /dev/null
+++ b/chromium/net/ssl/ssl_config_service_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 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.
+
+#include "net/ssl/ssl_config_service.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockSSLConfigService : public SSLConfigService {
+ public:
+ explicit MockSSLConfigService(const SSLConfig& config) : config_(config) {}
+
+ // SSLConfigService implementation
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {
+ *config = config_;
+ }
+
+ // Sets the SSLConfig to be returned by GetSSLConfig and processes any
+ // updates.
+ void SetSSLConfig(const SSLConfig& config) {
+ SSLConfig old_config = config_;
+ config_ = config;
+ ProcessConfigUpdate(old_config, config_);
+ }
+
+ private:
+ virtual ~MockSSLConfigService() {}
+
+ SSLConfig config_;
+};
+
+class MockSSLConfigServiceObserver : public SSLConfigService::Observer {
+ public:
+ MockSSLConfigServiceObserver() {}
+ virtual ~MockSSLConfigServiceObserver() {}
+
+ MOCK_METHOD0(OnSSLConfigChanged, void());
+};
+
+} // namespace
+
+TEST(SSLConfigServiceTest, NoChangesWontNotifyObservers) {
+ SSLConfig initial_config;
+ initial_config.rev_checking_enabled = true;
+ initial_config.false_start_enabled = false;
+ initial_config.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ initial_config.version_max = SSL_PROTOCOL_VERSION_TLS1_1;
+
+ scoped_refptr<MockSSLConfigService> mock_service(
+ new MockSSLConfigService(initial_config));
+ MockSSLConfigServiceObserver observer;
+ mock_service->AddObserver(&observer);
+
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(0);
+ mock_service->SetSSLConfig(initial_config);
+
+ mock_service->RemoveObserver(&observer);
+}
+
+TEST(SSLConfigServiceTest, ConfigUpdatesNotifyObservers) {
+ SSLConfig initial_config;
+ initial_config.rev_checking_enabled = true;
+ initial_config.false_start_enabled = false;
+ initial_config.unrestricted_ssl3_fallback_enabled = false;
+ initial_config.version_min = SSL_PROTOCOL_VERSION_SSL3;
+ initial_config.version_max = SSL_PROTOCOL_VERSION_TLS1_1;
+
+ scoped_refptr<MockSSLConfigService> mock_service(
+ new MockSSLConfigService(initial_config));
+ MockSSLConfigServiceObserver observer;
+ mock_service->AddObserver(&observer);
+
+ // Test that the basic boolean preferences trigger updates.
+ initial_config.rev_checking_enabled = false;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ initial_config.false_start_enabled = true;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ initial_config.unrestricted_ssl3_fallback_enabled = true;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ // Test that changing the SSL version range triggers updates.
+ initial_config.version_min = SSL_PROTOCOL_VERSION_TLS1;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ initial_config.version_max = SSL_PROTOCOL_VERSION_SSL3;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ // Test that disabling certain cipher suites triggers an update.
+ std::vector<uint16> disabled_ciphers;
+ disabled_ciphers.push_back(0x0004u);
+ disabled_ciphers.push_back(0xBEEFu);
+ disabled_ciphers.push_back(0xDEADu);
+ initial_config.disabled_cipher_suites = disabled_ciphers;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ // Ensure that changing a disabled cipher suite, while still maintaining
+ // sorted order, triggers an update.
+ disabled_ciphers[1] = 0xCAFEu;
+ initial_config.disabled_cipher_suites = disabled_ciphers;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ // Ensure that removing a disabled cipher suite, while still keeping some
+ // cipher suites disabled, triggers an update.
+ disabled_ciphers.pop_back();
+ initial_config.disabled_cipher_suites = disabled_ciphers;
+ EXPECT_CALL(observer, OnSSLConfigChanged()).Times(1);
+ mock_service->SetSSLConfig(initial_config);
+
+ mock_service->RemoveObserver(&observer);
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_connection_status_flags.h b/chromium/net/ssl/ssl_connection_status_flags.h
new file mode 100644
index 00000000000..08d585f7ebc
--- /dev/null
+++ b/chromium/net/ssl/ssl_connection_status_flags.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_SSL_CONNECTION_STATUS_FLAGS_H_
+#define NET_SSL_SSL_CONNECTION_STATUS_FLAGS_H_
+
+namespace net {
+
+// Status flags for SSLInfo::connection_status.
+enum {
+ // The lower 16 bits are reserved for the TLS ciphersuite id.
+ SSL_CONNECTION_CIPHERSUITE_SHIFT = 0,
+ SSL_CONNECTION_CIPHERSUITE_MASK = 0xffff,
+
+ // The next two bits are reserved for the compression used.
+ SSL_CONNECTION_COMPRESSION_SHIFT = 16,
+ SSL_CONNECTION_COMPRESSION_MASK = 3,
+
+ // We fell back to an older protocol version for this connection.
+ SSL_CONNECTION_VERSION_FALLBACK = 1 << 18,
+
+ // The server doesn't support the renegotiation_info extension. If this bit
+ // is not set then either the extension isn't supported, or we don't have any
+ // knowledge either way. (The latter case will occur when we use an SSL
+ // library that doesn't report it, like SChannel.)
+ SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION = 1 << 19,
+
+ // The next three bits are reserved for the SSL version.
+ SSL_CONNECTION_VERSION_SHIFT = 20,
+ SSL_CONNECTION_VERSION_MASK = 7,
+
+ // 1 << 31 (the sign bit) is reserved so that the SSL connection status will
+ // never be negative.
+};
+
+// NOTE: the SSL version enum constants must be between 0 and
+// SSL_CONNECTION_VERSION_MASK, inclusive.
+enum {
+ SSL_CONNECTION_VERSION_UNKNOWN = 0, // Unknown SSL version.
+ SSL_CONNECTION_VERSION_SSL2 = 1,
+ SSL_CONNECTION_VERSION_SSL3 = 2,
+ SSL_CONNECTION_VERSION_TLS1 = 3,
+ SSL_CONNECTION_VERSION_TLS1_1 = 4,
+ SSL_CONNECTION_VERSION_TLS1_2 = 5,
+ SSL_CONNECTION_VERSION_MAX,
+};
+COMPILE_ASSERT(SSL_CONNECTION_VERSION_MAX - 1 <= SSL_CONNECTION_VERSION_MASK,
+ SSL_CONNECTION_VERSION_MASK_too_small);
+
+inline int SSLConnectionStatusToCipherSuite(int connection_status) {
+ return (connection_status >> SSL_CONNECTION_CIPHERSUITE_SHIFT) &
+ SSL_CONNECTION_CIPHERSUITE_MASK;
+}
+
+inline int SSLConnectionStatusToVersion(int connection_status) {
+ return (connection_status >> SSL_CONNECTION_VERSION_SHIFT) &
+ SSL_CONNECTION_VERSION_MASK;
+}
+
+} // namespace net
+
+#endif // NET_SSL_SSL_CONNECTION_STATUS_FLAGS_H_
diff --git a/chromium/net/ssl/ssl_info.cc b/chromium/net/ssl/ssl_info.cc
new file mode 100644
index 00000000000..6c3485bac3a
--- /dev/null
+++ b/chromium/net/ssl/ssl_info.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 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.
+
+#include "net/ssl/ssl_info.h"
+
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_certificate.h"
+
+namespace net {
+
+SSLInfo::SSLInfo() {
+ Reset();
+}
+
+SSLInfo::SSLInfo(const SSLInfo& info) {
+ *this = info;
+}
+
+SSLInfo::~SSLInfo() {
+}
+
+SSLInfo& SSLInfo::operator=(const SSLInfo& info) {
+ cert = info.cert;
+ cert_status = info.cert_status;
+ security_bits = info.security_bits;
+ connection_status = info.connection_status;
+ is_issued_by_known_root = info.is_issued_by_known_root;
+ client_cert_sent = info.client_cert_sent;
+ channel_id_sent = info.channel_id_sent;
+ handshake_type = info.handshake_type;
+ public_key_hashes = info.public_key_hashes;
+
+ return *this;
+}
+
+void SSLInfo::Reset() {
+ cert = NULL;
+ cert_status = 0;
+ security_bits = -1;
+ connection_status = 0;
+ is_issued_by_known_root = false;
+ client_cert_sent = false;
+ channel_id_sent = false;
+ handshake_type = HANDSHAKE_UNKNOWN;
+
+ public_key_hashes.clear();
+}
+
+void SSLInfo::SetCertError(int error) {
+ cert_status |= MapNetErrorToCertStatus(error);
+}
+
+} // namespace net
diff --git a/chromium/net/ssl/ssl_info.h b/chromium/net/ssl/ssl_info.h
new file mode 100644
index 00000000000..3f1dd2df4e1
--- /dev/null
+++ b/chromium/net/ssl/ssl_info.h
@@ -0,0 +1,81 @@
+// Copyright (c) 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.
+
+#ifndef NET_SSL_SSL_INFO_H_
+#define NET_SSL_SSL_INFO_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/base/net_export.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cert/x509_cert_types.h"
+
+namespace net {
+
+class X509Certificate;
+
+// SSL connection info.
+// This is really a struct. All members are public.
+class NET_EXPORT SSLInfo {
+ public:
+ // HandshakeType enumerates the possible resumption cases after an SSL
+ // handshake.
+ enum HandshakeType {
+ HANDSHAKE_UNKNOWN = 0,
+ HANDSHAKE_RESUME, // we resumed a previous session.
+ HANDSHAKE_FULL, // we negotiated a new session.
+ };
+
+ SSLInfo();
+ SSLInfo(const SSLInfo& info);
+ ~SSLInfo();
+ SSLInfo& operator=(const SSLInfo& info);
+
+ void Reset();
+
+ bool is_valid() const { return cert.get() != NULL; }
+
+ // Adds the specified |error| to the cert status.
+ void SetCertError(int error);
+
+ // The SSL certificate.
+ scoped_refptr<X509Certificate> cert;
+
+ // Bitmask of status info of |cert|, representing, for example, known errors
+ // and extended validation (EV) status.
+ // See cert_status_flags.h for values.
+ CertStatus cert_status;
+
+ // The security strength, in bits, of the SSL cipher suite.
+ // 0 means the connection is not encrypted.
+ // -1 means the security strength is unknown.
+ int security_bits;
+
+ // Information about the SSL connection itself. See
+ // ssl_connection_status_flags.h for values. The protocol version,
+ // ciphersuite, and compression in use are encoded within.
+ int connection_status;
+
+ // If the certificate is valid, then this is true iff it was rooted at a
+ // standard CA root. (As opposed to a user-installed root.)
+ bool is_issued_by_known_root;
+
+ // True if a client certificate was sent to the server. Note that sending
+ // a Certificate message with no client certificate in it does not count.
+ bool client_cert_sent;
+
+ // True if a channel ID was sent to the server.
+ bool channel_id_sent;
+
+ HandshakeType handshake_type;
+
+ // The hashes, in several algorithms, of the SubjectPublicKeyInfos from
+ // each certificate in the chain.
+ HashValueVector public_key_hashes;
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_INFO_H_
diff --git a/chromium/net/test/OWNERS b/chromium/net/test/OWNERS
new file mode 100644
index 00000000000..5b08e349893
--- /dev/null
+++ b/chromium/net/test/OWNERS
@@ -0,0 +1,5 @@
+# General reviewer, except sync-specific bits.
+phajdan.jr@chromium.org
+
+# For changes to local_sync_test_server.{h|cc}.
+akalin@chromium.org
diff --git a/chromium/net/test/android/OWNERS b/chromium/net/test/android/OWNERS
new file mode 100644
index 00000000000..4bcb60f934c
--- /dev/null
+++ b/chromium/net/test/android/OWNERS
@@ -0,0 +1,3 @@
+digit@chromium.org
+pliard@chromium.org
+yfriedman@chromium.org
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..9e60a43fa41
--- /dev/null
+++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java
@@ -0,0 +1,557 @@
+// Copyright (c) 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.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.RequestLine;
+import org.apache.http.StatusLine;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.cookie.DateUtils;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * 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.
+ *
+ * Based heavily on the CTSWebServer in Android.
+ */
+public class TestWebServer {
+ private static final String TAG = "TestWebServer";
+ private static final int SERVER_PORT = 4444;
+ private static final int SSL_SERVER_PORT = 4445;
+
+ public static final String SHUTDOWN_PREFIX = "/shutdown";
+
+ private static TestWebServer sInstance;
+ private static Hashtable<Integer, String> sReasons;
+
+ private final ServerThread mServerThread;
+ private String mServerUri;
+ private final boolean mSsl;
+
+ private static class Response {
+ final byte[] mResponseData;
+ final List<Pair<String, String>> mResponseHeaders;
+ final boolean mIsRedirect;
+
+ Response(byte[] resposneData, List<Pair<String, String>> responseHeaders,
+ boolean isRedirect) {
+ mIsRedirect = isRedirect;
+ mResponseData = resposneData;
+ mResponseHeaders = responseHeaders == null ?
+ new ArrayList<Pair<String, String>>() : responseHeaders;
+ }
+ }
+
+ // 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<String, Response> mResponseMap = new HashMap<String, Response>();
+ private final Map<String, Integer> mResponseCountMap = new HashMap<String, Integer>();
+ private final Map<String, HttpRequest> mLastRequestMap = new HashMap<String, HttpRequest>();
+
+ /**
+ * Create and start a local HTTP server instance.
+ * @param ssl True if the server should be using secure sockets.
+ * @throws Exception
+ */
+ public TestWebServer(boolean ssl) throws Exception {
+ if (sInstance != null) {
+ // attempt to start a new instance while one is still running
+ // shut down the old instance first
+ sInstance.shutdown();
+ }
+ setStaticInstance(this);
+ mSsl = ssl;
+ if (mSsl) {
+ mServerUri = "https://localhost:" + SSL_SERVER_PORT;
+ } else {
+ mServerUri = "http://localhost:" + SERVER_PORT;
+ }
+ mServerThread = new ServerThread(this, mSsl);
+ mServerThread.start();
+ }
+
+ private static void setStaticInstance(TestWebServer instance) {
+ sInstance = instance;
+ }
+
+ /**
+ * Terminate the http server.
+ */
+ public void shutdown() {
+ try {
+ // Avoid a deadlock between two threads where one is trying to call
+ // close() and the other one is calling accept() by sending a GET
+ // request for shutdown and having the server's one thread
+ // sequentially call accept() and close().
+ URL url = new URL(mServerUri + SHUTDOWN_PREFIX);
+ URLConnection connection = openConnection(url);
+ connection.connect();
+
+ // Read the input from the stream to send the request.
+ InputStream is = connection.getInputStream();
+ is.close();
+
+ // Block until the server thread is done shutting down.
+ mServerThread.join();
+
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ } catch (KeyManagementException e) {
+ throw new IllegalStateException(e);
+ }
+
+ setStaticInstance(null);
+ }
+
+ private final static int RESPONSE_STATUS_NORMAL = 0;
+ private final static int RESPONSE_STATUS_MOVED_TEMPORARILY = 1;
+
+ private String setResponseInternal(
+ String requestPath, byte[] responseData,
+ List<Pair<String, String>> responseHeaders,
+ int status) {
+ final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY);
+
+ synchronized (mLock) {
+ mResponseMap.put(requestPath, new Response(responseData, responseHeaders, isRedirect));
+ mResponseCountMap.put(requestPath, Integer.valueOf(0));
+ mLastRequestMap.put(requestPath, null);
+ }
+ return getResponseUrl(requestPath);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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<Pair<String, String>> responseHeaders) {
+ return setResponseInternal(requestPath, responseString.getBytes(), responseHeaders,
+ 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<Pair<String, String>> responseHeaders = new ArrayList<Pair<String, String>>();
+ responseHeaders.add(Pair.create("Location", targetPath));
+
+ return setResponseInternal(requestPath, targetPath.getBytes(), responseHeaders,
+ 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<Pair<String, String>> responseHeaders) {
+ return setResponseInternal(requestPath,
+ Base64.decode(base64EncodedResponse, Base64.DEFAULT),
+ responseHeaders,
+ RESPONSE_STATUS_NORMAL);
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ public String getBaseUrl() {
+ return mServerUri + "/";
+ }
+
+ private URLConnection openConnection(URL url)
+ throws IOException, NoSuchAlgorithmException, KeyManagementException {
+ if (mSsl) {
+ // Install hostname verifiers and trust managers that don't do
+ // anything in order to get around the client not trusting
+ // the test server due to a lack of certificates.
+
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setHostnameVerifier(new TestHostnameVerifier());
+
+ SSLContext context = SSLContext.getInstance("TLS");
+ TestTrustManager trustManager = new TestTrustManager();
+ context.init(null, new TestTrustManager[] {trustManager}, null);
+ connection.setSSLSocketFactory(context.getSocketFactory());
+
+ return connection;
+ } else {
+ return url.openConnection();
+ }
+ }
+
+ /**
+ * {@link X509TrustManager} that trusts everybody. This is used so that
+ * the client calling {@link TestWebServer#shutdown()} can issue a request
+ * for shutdown by blindly trusting the {@link TestWebServer}'s
+ * credentials.
+ */
+ private static class TestTrustManager implements X509TrustManager {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ // Trust the TestWebServer...
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ // Trust the TestWebServer...
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }
+
+ /**
+ * {@link HostnameVerifier} that verifies everybody. This permits
+ * the client to trust the web server and call
+ * {@link TestWebServer#shutdown()}.
+ */
+ private static class TestHostnameVerifier implements HostnameVerifier {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ }
+
+ private void servedResponseFor(String path, HttpRequest request) {
+ synchronized (mLock) {
+ mResponseCountMap.put(path, Integer.valueOf(
+ mResponseCountMap.get(path).intValue() + 1));
+ mLastRequestMap.put(path, request);
+ }
+ }
+
+ /**
+ * Generate a response to the given request.
+ * @throws InterruptedException
+ */
+ private HttpResponse getResponse(HttpRequest request) throws InterruptedException {
+ RequestLine requestLine = request.getRequestLine();
+ HttpResponse httpResponse = null;
+ Log.i(TAG, requestLine.getMethod() + ": " + requestLine.getUri());
+ String uriString = requestLine.getUri();
+ URI uri = URI.create(uriString);
+ String path = uri.getPath();
+
+ Response response = null;
+ synchronized (mLock) {
+ response = mResponseMap.get(path);
+ }
+ if (path.equals(SHUTDOWN_PREFIX)) {
+ httpResponse = createResponse(HttpStatus.SC_OK);
+ } else if (response == null) {
+ httpResponse = createResponse(HttpStatus.SC_NOT_FOUND);
+ } else if (response.mIsRedirect) {
+ httpResponse = createResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+ for (Pair<String, String> header : response.mResponseHeaders) {
+ httpResponse.addHeader(header.first, header.second);
+ }
+ servedResponseFor(path, request);
+ } else {
+ httpResponse = createResponse(HttpStatus.SC_OK);
+ httpResponse.setEntity(createEntity(response.mResponseData));
+ for (Pair<String, String> header : response.mResponseHeaders) {
+ httpResponse.addHeader(header.first, header.second);
+ }
+ servedResponseFor(path, request);
+ }
+ StatusLine sl = httpResponse.getStatusLine();
+ Log.i(TAG, sl.getStatusCode() + "(" + sl.getReasonPhrase() + ")");
+ setDateHeaders(httpResponse);
+ return httpResponse;
+ }
+
+ private void setDateHeaders(HttpResponse response) {
+ response.addHeader("Date", DateUtils.formatDate(new Date(), DateUtils.PATTERN_RFC1123));
+ }
+
+ /**
+ * Create an empty response with the given status.
+ */
+ private HttpResponse createResponse(int status) {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_0, status, null);
+ String reason = null;
+
+ // This synchronized silences findbugs.
+ synchronized (TestWebServer.class) {
+ if (sReasons == null) {
+ sReasons = new Hashtable<Integer, String>();
+ sReasons.put(HttpStatus.SC_UNAUTHORIZED, "Unauthorized");
+ sReasons.put(HttpStatus.SC_NOT_FOUND, "Not Found");
+ sReasons.put(HttpStatus.SC_FORBIDDEN, "Forbidden");
+ sReasons.put(HttpStatus.SC_MOVED_TEMPORARILY, "Moved Temporarily");
+ }
+ // Fill in error reason. Avoid use of the ReasonPhraseCatalog, which is
+ // Locale-dependent.
+ reason = sReasons.get(status);
+ }
+
+ if (reason != null) {
+ StringBuffer buf = new StringBuffer("<html><head><title>");
+ buf.append(reason);
+ buf.append("</title></head><body>");
+ buf.append(reason);
+ buf.append("</body></html>");
+ response.setEntity(createEntity(buf.toString().getBytes()));
+ }
+ return response;
+ }
+
+ /**
+ * Create a string entity for the given content.
+ */
+ private ByteArrayEntity createEntity(byte[] data) {
+ ByteArrayEntity entity = new ByteArrayEntity(data);
+ entity.setContentType("text/html");
+ return entity;
+ }
+
+ private static class ServerThread extends Thread {
+ private TestWebServer mServer;
+ private ServerSocket mSocket;
+ private boolean mIsSsl;
+ private boolean mIsCancelled;
+ private SSLContext mSslContext;
+
+ /**
+ * 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();
+ }
+
+
+ public ServerThread(TestWebServer server, boolean ssl) throws Exception {
+ super("ServerThread");
+ mServer = server;
+ mIsSsl = ssl;
+ int retry = 3;
+ while (true) {
+ try {
+ if (mIsSsl) {
+ mSslContext = SSLContext.getInstance("TLS");
+ mSslContext.init(getKeyManagers(), null, null);
+ mSocket = mSslContext.getServerSocketFactory().createServerSocket(
+ SSL_SERVER_PORT);
+ } else {
+ mSocket = new ServerSocket(SERVER_PORT);
+ }
+ return;
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ if (--retry == 0) {
+ throw e;
+ }
+ // sleep in case server socket is still being closed
+ Thread.sleep(1000);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ HttpParams params = new BasicHttpParams();
+ params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_0);
+ while (!mIsCancelled) {
+ try {
+ Socket socket = mSocket.accept();
+ DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
+ conn.bind(socket, params);
+
+ // Determine whether we need to shutdown early before
+ // parsing the response since conn.close() will crash
+ // for SSL requests due to UnsupportedOperationException.
+ HttpRequest request = conn.receiveRequestHeader();
+ if (isShutdownRequest(request)) {
+ mIsCancelled = true;
+ }
+
+ HttpResponse response = mServer.getResponse(request);
+ conn.sendResponseHeader(response);
+ conn.sendResponseEntity(response);
+ conn.close();
+
+ } catch (IOException e) {
+ // normal during shutdown, ignore
+ Log.w(TAG, e);
+ } catch (HttpException e) {
+ Log.w(TAG, e);
+ } catch (InterruptedException e) {
+ Log.w(TAG, e);
+ } catch (UnsupportedOperationException e) {
+ // DefaultHttpServerConnection's close() throws an
+ // UnsupportedOperationException.
+ Log.w(TAG, e);
+ }
+ }
+ try {
+ mSocket.close();
+ } catch (IOException ignored) {
+ // safe to ignore
+ }
+ }
+
+ private boolean isShutdownRequest(HttpRequest request) {
+ RequestLine requestLine = request.getRequestLine();
+ String uriString = requestLine.getUri();
+ URI uri = URI.create(uriString);
+ String path = uri.getPath();
+ return path.equals(SHUTDOWN_PREFIX);
+ }
+ }
+}
diff --git a/chromium/net/test/cert_test_util.cc b/chromium/net/test/cert_test_util.cc
new file mode 100644
index 00000000000..085a4594c76
--- /dev/null
+++ b/chromium/net/test/cert_test_util.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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.
+
+#include "net/test/cert_test_util.h"
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/cert/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+CertificateList CreateCertificateListFromFile(
+ const base::FilePath& certs_dir,
+ const std::string& cert_file,
+ int format) {
+ base::FilePath cert_path = certs_dir.AppendASCII(cert_file);
+ std::string cert_data;
+ if (!file_util::ReadFileToString(cert_path, &cert_data))
+ return CertificateList();
+ return X509Certificate::CreateCertificateListFromBytes(cert_data.data(),
+ cert_data.size(),
+ format);
+}
+
+scoped_refptr<X509Certificate> ImportCertFromFile(
+ const base::FilePath& certs_dir,
+ const std::string& cert_file) {
+ base::FilePath cert_path = certs_dir.AppendASCII(cert_file);
+ std::string cert_data;
+ if (!file_util::ReadFileToString(cert_path, &cert_data))
+ return NULL;
+
+ CertificateList certs_in_file =
+ X509Certificate::CreateCertificateListFromBytes(
+ cert_data.data(), cert_data.size(), X509Certificate::FORMAT_AUTO);
+ if (certs_in_file.empty())
+ return NULL;
+ return certs_in_file[0];
+}
+
+ScopedTestEVPolicy::ScopedTestEVPolicy(EVRootCAMetadata* ev_root_ca_metadata,
+ const SHA1HashValue& fingerprint,
+ const char* policy)
+ : fingerprint_(fingerprint),
+ ev_root_ca_metadata_(ev_root_ca_metadata) {
+ EXPECT_TRUE(ev_root_ca_metadata->AddEVCA(fingerprint, policy));
+}
+
+ScopedTestEVPolicy::~ScopedTestEVPolicy() {
+ EXPECT_TRUE(ev_root_ca_metadata_->RemoveEVCA(fingerprint_));
+}
+
+} // namespace net
diff --git a/chromium/net/test/cert_test_util.h b/chromium/net/test/cert_test_util.h
new file mode 100644
index 00000000000..d4aa4d7d325
--- /dev/null
+++ b/chromium/net/test/cert_test_util.h
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_CERT_TEST_UTIL_H_
+#define NET_TEST_CERT_TEST_UTIL_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "net/cert/x509_cert_types.h"
+#include "net/cert/x509_certificate.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+
+class EVRootCAMetadata;
+
+CertificateList CreateCertificateListFromFile(const base::FilePath& certs_dir,
+ const std::string& cert_file,
+ int format);
+
+// Imports a certificate file in the directory net::GetTestCertsDirectory()
+// returns.
+// |certs_dir| represents the test certificates directory. |cert_file| is the
+// name of the certificate file. If cert_file contains multiple certificates,
+// the first certificate found will be returned.
+scoped_refptr<X509Certificate> ImportCertFromFile(const base::FilePath& certs_dir,
+ const std::string& cert_file);
+
+// ScopedTestEVPolicy causes certificates marked with |policy|, issued from a
+// root with the given fingerprint, to be treated as EV. |policy| is expressed
+// as a string of dotted numbers: i.e. "1.2.3.4".
+// This should only be used in unittests as adding a CA twice causes a CHECK
+// failure.
+class ScopedTestEVPolicy {
+ public:
+ ScopedTestEVPolicy(EVRootCAMetadata* ev_root_ca_metadata,
+ const SHA1HashValue& fingerprint,
+ const char* policy);
+ ~ScopedTestEVPolicy();
+
+ private:
+ SHA1HashValue fingerprint_;
+ EVRootCAMetadata* const ev_root_ca_metadata_;
+};
+
+} // namespace net
+
+#endif // NET_TEST_CERT_TEST_UTIL_H_
diff --git a/chromium/net/test/embedded_test_server/OWNERS b/chromium/net/test/embedded_test_server/OWNERS
new file mode 100644
index 00000000000..e0abbce9eac
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/OWNERS
@@ -0,0 +1,2 @@
+mtomasz@chromium.org
+satorux@chromium.org
diff --git a/chromium/net/test/embedded_test_server/embedded_test_server.cc b/chromium/net/test/embedded_test_server/embedded_test_server.cc
new file mode 100644
index 00000000000..9175d6ca894
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/embedded_test_server.cc
@@ -0,0 +1,276 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/test/embedded_test_server/http_connection.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/tools/fetch/http_listen_socket.h"
+
+namespace net {
+namespace test_server {
+
+namespace {
+
+class CustomHttpResponse : public HttpResponse {
+ public:
+ CustomHttpResponse(const std::string& headers, const std::string& contents)
+ : headers_(headers), contents_(contents) {
+ }
+
+ virtual std::string ToResponseString() const OVERRIDE {
+ return headers_ + "\r\n" + contents_;
+ }
+
+ private:
+ std::string headers_;
+ std::string contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(CustomHttpResponse);
+};
+
+// Handles |request| by serving a file from under |server_root|.
+scoped_ptr<HttpResponse> HandleFileRequest(
+ const base::FilePath& server_root,
+ const HttpRequest& request) {
+ // This is a test-only server. Ignore I/O thread restrictions.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Trim the first byte ('/').
+ std::string request_path(request.relative_url.substr(1));
+
+ // Remove the query string if present.
+ size_t query_pos = request_path.find('?');
+ if (query_pos != std::string::npos)
+ request_path = request_path.substr(0, query_pos);
+
+ base::FilePath file_path(server_root.AppendASCII(request_path));
+ std::string file_contents;
+ if (!file_util::ReadFileToString(file_path, &file_contents))
+ return scoped_ptr<HttpResponse>();
+
+ base::FilePath headers_path(
+ file_path.AddExtension(FILE_PATH_LITERAL("mock-http-headers")));
+
+ if (base::PathExists(headers_path)) {
+ std::string headers_contents;
+ if (!file_util::ReadFileToString(headers_path, &headers_contents))
+ return scoped_ptr<HttpResponse>();
+
+ scoped_ptr<CustomHttpResponse> http_response(
+ new CustomHttpResponse(headers_contents, file_contents));
+ return http_response.PassAs<HttpResponse>();
+ }
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content(file_contents);
+ return http_response.PassAs<HttpResponse>();
+}
+
+} // namespace
+
+HttpListenSocket::HttpListenSocket(const SocketDescriptor socket_descriptor,
+ StreamListenSocket::Delegate* delegate)
+ : TCPListenSocket(socket_descriptor, delegate) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void HttpListenSocket::Listen() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TCPListenSocket::Listen();
+}
+
+HttpListenSocket::~HttpListenSocket() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+EmbeddedTestServer::EmbeddedTestServer(
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread)
+ : io_thread_(io_thread),
+ port_(-1),
+ weak_factory_(this) {
+ DCHECK(io_thread_.get());
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+EmbeddedTestServer::~EmbeddedTestServer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (Started() && !ShutdownAndWaitUntilComplete()) {
+ LOG(ERROR) << "EmbeddedTestServer failed to shut down.";
+ }
+}
+
+bool EmbeddedTestServer::InitializeAndWaitUntilReady() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::RunLoop run_loop;
+ if (!io_thread_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&EmbeddedTestServer::InitializeOnIOThread,
+ base::Unretained(this)),
+ run_loop.QuitClosure())) {
+ return false;
+ }
+ run_loop.Run();
+
+ return Started() && base_url_.is_valid();
+}
+
+bool EmbeddedTestServer::ShutdownAndWaitUntilComplete() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::RunLoop run_loop;
+ if (!io_thread_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&EmbeddedTestServer::ShutdownOnIOThread,
+ base::Unretained(this)),
+ run_loop.QuitClosure())) {
+ return false;
+ }
+ run_loop.Run();
+
+ return true;
+}
+
+void EmbeddedTestServer::InitializeOnIOThread() {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ DCHECK(!Started());
+
+ SocketDescriptor socket_descriptor =
+ TCPListenSocket::CreateAndBindAnyPort("127.0.0.1", &port_);
+ if (socket_descriptor == TCPListenSocket::kInvalidSocket)
+ return;
+
+ listen_socket_ = new HttpListenSocket(socket_descriptor, this);
+ listen_socket_->Listen();
+
+ IPEndPoint address;
+ int result = listen_socket_->GetLocalAddress(&address);
+ if (result == OK) {
+ base_url_ = GURL(std::string("http://") + address.ToString());
+ } else {
+ LOG(ERROR) << "GetLocalAddress failed: " << ErrorToString(result);
+ }
+}
+
+void EmbeddedTestServer::ShutdownOnIOThread() {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ listen_socket_ = NULL; // Release the listen socket.
+ STLDeleteContainerPairSecondPointers(connections_.begin(),
+ connections_.end());
+ connections_.clear();
+}
+
+void EmbeddedTestServer::HandleRequest(HttpConnection* connection,
+ scoped_ptr<HttpRequest> request) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ bool request_handled = false;
+
+ for (size_t i = 0; i < request_handlers_.size(); ++i) {
+ scoped_ptr<HttpResponse> response =
+ request_handlers_[i].Run(*request.get());
+ if (response.get()) {
+ connection->SendResponse(response.Pass());
+ request_handled = true;
+ break;
+ }
+ }
+
+ if (!request_handled) {
+ LOG(WARNING) << "Request not handled. Returning 404: "
+ << request->relative_url;
+ scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
+ not_found_response->set_code(HTTP_NOT_FOUND);
+ connection->SendResponse(
+ not_found_response.PassAs<HttpResponse>());
+ }
+
+ // Drop the connection, since we do not support multiple requests per
+ // connection.
+ connections_.erase(connection->socket_.get());
+ delete connection;
+}
+
+GURL EmbeddedTestServer::GetURL(const std::string& relative_url) const {
+ DCHECK(StartsWithASCII(relative_url, "/", true /* case_sensitive */))
+ << relative_url;
+ return base_url_.Resolve(relative_url);
+}
+
+void EmbeddedTestServer::ServeFilesFromDirectory(
+ const base::FilePath& directory) {
+ RegisterRequestHandler(base::Bind(&HandleFileRequest, directory));
+}
+
+void EmbeddedTestServer::RegisterRequestHandler(
+ const HandleRequestCallback& callback) {
+ request_handlers_.push_back(callback);
+}
+
+void EmbeddedTestServer::DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ HttpConnection* http_connection = new HttpConnection(
+ connection,
+ base::Bind(&EmbeddedTestServer::HandleRequest,
+ weak_factory_.GetWeakPtr()));
+ connections_[connection] = http_connection;
+}
+
+void EmbeddedTestServer::DidRead(StreamListenSocket* connection,
+ const char* data,
+ int length) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ HttpConnection* http_connection = FindConnection(connection);
+ if (http_connection == NULL) {
+ LOG(WARNING) << "Unknown connection.";
+ return;
+ }
+ http_connection->ReceiveData(std::string(data, length));
+}
+
+void EmbeddedTestServer::DidClose(StreamListenSocket* connection) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ HttpConnection* http_connection = FindConnection(connection);
+ if (http_connection == NULL) {
+ LOG(WARNING) << "Unknown connection.";
+ return;
+ }
+ delete http_connection;
+ connections_.erase(connection);
+}
+
+HttpConnection* EmbeddedTestServer::FindConnection(
+ StreamListenSocket* socket) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ std::map<StreamListenSocket*, HttpConnection*>::iterator it =
+ connections_.find(socket);
+ if (it == connections_.end()) {
+ return NULL;
+ }
+ return it->second;
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/embedded_test_server.h b/chromium/net/test/embedded_test_server/embedded_test_server.h
new file mode 100644
index 00000000000..879c4a947f9
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/embedded_test_server.h
@@ -0,0 +1,172 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
+#define NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "net/socket/tcp_listen_socket.h"
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace net {
+namespace test_server {
+
+class HttpConnection;
+class HttpResponse;
+struct HttpRequest;
+
+// This class is required to be able to have composition instead of inheritance,
+class HttpListenSocket : public TCPListenSocket {
+ public:
+ HttpListenSocket(const SocketDescriptor socket_descriptor,
+ StreamListenSocket::Delegate* delegate);
+ virtual void Listen();
+
+ private:
+ virtual ~HttpListenSocket();
+
+ base::ThreadChecker thread_checker_;
+};
+
+// Class providing an HTTP server for testing purpose. This is a basic server
+// providing only an essential subset of HTTP/1.1 protocol. Especially,
+// it assumes that the request syntax is correct. It *does not* support
+// a Chunked Transfer Encoding.
+//
+// The common use case is below:
+//
+// base::Thread io_thread_;
+// scoped_ptr<EmbeddedTestServer> test_server_;
+//
+// void SetUp() {
+// base::Thread::Options thread_options;
+// thread_options.message_loop_type = MessageLoop::TYPE_IO;
+// ASSERT_TRUE(io_thread_.StartWithOptions(thread_options));
+//
+// test_server_.reset(
+// new EmbeddedTestServer(io_thread_.message_loop_proxy()));
+// ASSERT_TRUE(test_server_.InitializeAndWaitUntilReady());
+// test_server_->RegisterRequestHandler(
+// base::Bind(&FooTest::HandleRequest, base::Unretained(this)));
+// }
+//
+// scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
+// GURL absolute_url = test_server_->GetURL(request.relative_url);
+// if (absolute_url.path() != "/test")
+// return scoped_ptr<HttpResponse>();
+//
+// scoped_ptr<HttpResponse> http_response(new HttpResponse());
+// http_response->set_code(test_server::SUCCESS);
+// http_response->set_content("hello");
+// http_response->set_content_type("text/plain");
+// return http_response.Pass();
+// }
+//
+class EmbeddedTestServer : public StreamListenSocket::Delegate {
+ public:
+ typedef base::Callback<scoped_ptr<HttpResponse>(
+ const HttpRequest& request)> HandleRequestCallback;
+
+ // Creates a http test server. |io_thread| is a task runner
+ // with IO message loop, used as a backend thread.
+ // InitializeAndWaitUntilReady() must be called to start the server.
+ explicit EmbeddedTestServer(
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread);
+ virtual ~EmbeddedTestServer();
+
+ // Initializes and waits until the server is ready to accept requests.
+ bool InitializeAndWaitUntilReady() WARN_UNUSED_RESULT;
+
+ // Shuts down the http server and waits until the shutdown is complete.
+ bool ShutdownAndWaitUntilComplete() WARN_UNUSED_RESULT;
+
+ // Checks if the server is started.
+ bool Started() const {
+ return listen_socket_.get() != NULL;
+ }
+
+ // Returns the base URL to the server, which looks like
+ // http://127.0.0.1:<port>/, where <port> is the actual port number used by
+ // the server.
+ const GURL& base_url() const { return base_url_; }
+
+ // Returns a URL to the server based on the given relative URL, which
+ // should start with '/'. For example: GetURL("/path?query=foo") =>
+ // http://127.0.0.1:<port>/path?query=foo.
+ GURL GetURL(const std::string& relative_url) const;
+
+ // Returns the port number used by the server.
+ int port() const { return port_; }
+
+ // Registers request handler which serves files from |directory|.
+ // For instance, a request to "/foo.html" is served by "foo.html" under
+ // |directory|. Files under sub directories are also handled in the same way
+ // (i.e. "/foo/bar.html" is served by "foo/bar.html" under |directory|).
+ void ServeFilesFromDirectory(const base::FilePath& directory);
+
+ // The most general purpose method. Any request processing can be added using
+ // this method. Takes ownership of the object. The |callback| is called
+ // on UI thread.
+ void RegisterRequestHandler(const HandleRequestCallback& callback);
+
+ private:
+ // Initializes and starts the server. If initialization succeeds, Starts()
+ // will return true.
+ void InitializeOnIOThread();
+
+ // Shuts down the server.
+ void ShutdownOnIOThread();
+
+ // Handles a request when it is parsed. It passes the request to registed
+ // request handlers and sends a http response.
+ void HandleRequest(HttpConnection* connection,
+ scoped_ptr<HttpRequest> request);
+
+ // StreamListenSocket::Delegate overrides:
+ virtual void DidAccept(StreamListenSocket* server,
+ StreamListenSocket* connection) OVERRIDE;
+ virtual void DidRead(StreamListenSocket* connection,
+ const char* data,
+ int length) OVERRIDE;
+ virtual void DidClose(StreamListenSocket* connection) OVERRIDE;
+
+ HttpConnection* FindConnection(StreamListenSocket* socket);
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
+
+ scoped_refptr<HttpListenSocket> listen_socket_;
+ int port_;
+ GURL base_url_;
+
+ // Owns the HttpConnection objects.
+ std::map<StreamListenSocket*, HttpConnection*> connections_;
+
+ // Vector of registered request handlers.
+ std::vector<HandleRequestCallback> request_handlers_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<EmbeddedTestServer> weak_factory_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(EmbeddedTestServer);
+};
+
+} // namespace test_servers
+} // namespace net
+
+#endif // NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
diff --git a/chromium/net/test/embedded_test_server/embedded_test_server_unittest.cc b/chromium/net/test/embedded_test_server/embedded_test_server_unittest.cc
new file mode 100644
index 00000000000..35d0fd414e1
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/embedded_test_server_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread.h"
+#include "net/http/http_response_headers.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test_server {
+
+namespace {
+
+// Gets the content from the given URLFetcher.
+std::string GetContentFromFetcher(const URLFetcher& fetcher) {
+ std::string result;
+ const bool success = fetcher.GetResponseAsString(&result);
+ EXPECT_TRUE(success);
+ return result;
+}
+
+// Gets the content type from the given URLFetcher.
+std::string GetContentTypeFromFetcher(const URLFetcher& fetcher) {
+ const HttpResponseHeaders* headers = fetcher.GetResponseHeaders();
+ if (headers) {
+ std::string content_type;
+ if (headers->GetMimeType(&content_type))
+ return content_type;
+ }
+ return std::string();
+}
+
+} // namespace
+
+class EmbeddedTestServerTest : public testing::Test,
+ public URLFetcherDelegate {
+ public:
+ EmbeddedTestServerTest()
+ : num_responses_received_(0),
+ num_responses_expected_(0),
+ io_thread_("io_thread") {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ base::Thread::Options thread_options;
+ thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
+ ASSERT_TRUE(io_thread_.StartWithOptions(thread_options));
+
+ request_context_getter_ = new TestURLRequestContextGetter(
+ io_thread_.message_loop_proxy());
+
+ server_.reset(new EmbeddedTestServer(io_thread_.message_loop_proxy()));
+ ASSERT_TRUE(server_->InitializeAndWaitUntilReady());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ ASSERT_TRUE(server_->ShutdownAndWaitUntilComplete());
+ }
+
+ // URLFetcherDelegate override.
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE {
+ ++num_responses_received_;
+ if (num_responses_received_ == num_responses_expected_)
+ base::MessageLoop::current()->Quit();
+ }
+
+ // Waits until the specified number of responses are received.
+ void WaitForResponses(int num_responses) {
+ num_responses_received_ = 0;
+ num_responses_expected_ = num_responses;
+ // Will be terminated in OnURLFetchComplete().
+ base::MessageLoop::current()->Run();
+ }
+
+ // Handles |request| sent to |path| and returns the response per |content|,
+ // |content type|, and |code|. Saves the request URL for verification.
+ scoped_ptr<HttpResponse> HandleRequest(const std::string& path,
+ const std::string& content,
+ const std::string& content_type,
+ HttpStatusCode code,
+ const HttpRequest& request) {
+ request_relative_url_ = request.relative_url;
+
+ GURL absolute_url = server_->GetURL(request.relative_url);
+ if (absolute_url.path() == path) {
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(code);
+ http_response->set_content(content);
+ http_response->set_content_type(content_type);
+ return http_response.PassAs<HttpResponse>();
+ }
+
+ return scoped_ptr<HttpResponse>();
+ }
+
+ protected:
+ int num_responses_received_;
+ int num_responses_expected_;
+ std::string request_relative_url_;
+ base::Thread io_thread_;
+ scoped_refptr<TestURLRequestContextGetter> request_context_getter_;
+ scoped_ptr<EmbeddedTestServer> server_;
+};
+
+TEST_F(EmbeddedTestServerTest, GetBaseURL) {
+ EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%d/", server_->port()),
+ server_->base_url().spec());
+}
+
+TEST_F(EmbeddedTestServerTest, GetURL) {
+ EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%d/path?query=foo",
+ server_->port()),
+ server_->GetURL("/path?query=foo").spec());
+}
+
+TEST_F(EmbeddedTestServerTest, RegisterRequestHandler) {
+ server_->RegisterRequestHandler(
+ base::Bind(&EmbeddedTestServerTest::HandleRequest,
+ base::Unretained(this),
+ "/test",
+ "<b>Worked!</b>",
+ "text/html",
+ HTTP_OK));
+
+ scoped_ptr<URLFetcher> fetcher(
+ URLFetcher::Create(server_->GetURL("/test?q=foo"),
+ URLFetcher::GET,
+ this));
+ fetcher->SetRequestContext(request_context_getter_.get());
+ fetcher->Start();
+ WaitForResponses(1);
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher->GetStatus().status());
+ EXPECT_EQ(HTTP_OK, fetcher->GetResponseCode());
+ EXPECT_EQ("<b>Worked!</b>", GetContentFromFetcher(*fetcher));
+ EXPECT_EQ("text/html", GetContentTypeFromFetcher(*fetcher));
+
+ EXPECT_EQ("/test?q=foo", request_relative_url_);
+}
+
+TEST_F(EmbeddedTestServerTest, ServeFilesFromDirectory) {
+ base::FilePath src_dir;
+ ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
+ server_->ServeFilesFromDirectory(
+ src_dir.AppendASCII("net").AppendASCII("data"));
+
+ scoped_ptr<URLFetcher> fetcher(
+ URLFetcher::Create(server_->GetURL("/test.html"),
+ URLFetcher::GET,
+ this));
+ fetcher->SetRequestContext(request_context_getter_.get());
+ fetcher->Start();
+ WaitForResponses(1);
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher->GetStatus().status());
+ EXPECT_EQ(HTTP_OK, fetcher->GetResponseCode());
+ EXPECT_EQ("<p>Hello World!</p>", GetContentFromFetcher(*fetcher));
+ EXPECT_EQ("", GetContentTypeFromFetcher(*fetcher));
+}
+
+TEST_F(EmbeddedTestServerTest, DefaultNotFoundResponse) {
+ scoped_ptr<URLFetcher> fetcher(
+ URLFetcher::Create(server_->GetURL("/non-existent"),
+ URLFetcher::GET,
+ this));
+ fetcher->SetRequestContext(request_context_getter_.get());
+
+ fetcher->Start();
+ WaitForResponses(1);
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher->GetStatus().status());
+ EXPECT_EQ(HTTP_NOT_FOUND, fetcher->GetResponseCode());
+}
+
+TEST_F(EmbeddedTestServerTest, ConcurrentFetches) {
+ server_->RegisterRequestHandler(
+ base::Bind(&EmbeddedTestServerTest::HandleRequest,
+ base::Unretained(this),
+ "/test1",
+ "Raspberry chocolate",
+ "text/html",
+ HTTP_OK));
+ server_->RegisterRequestHandler(
+ base::Bind(&EmbeddedTestServerTest::HandleRequest,
+ base::Unretained(this),
+ "/test2",
+ "Vanilla chocolate",
+ "text/html",
+ HTTP_OK));
+ server_->RegisterRequestHandler(
+ base::Bind(&EmbeddedTestServerTest::HandleRequest,
+ base::Unretained(this),
+ "/test3",
+ "No chocolates",
+ "text/plain",
+ HTTP_NOT_FOUND));
+
+ scoped_ptr<URLFetcher> fetcher1 = scoped_ptr<URLFetcher>(
+ URLFetcher::Create(server_->GetURL("/test1"),
+ URLFetcher::GET,
+ this));
+ fetcher1->SetRequestContext(request_context_getter_.get());
+ scoped_ptr<URLFetcher> fetcher2 = scoped_ptr<URLFetcher>(
+ URLFetcher::Create(server_->GetURL("/test2"),
+ URLFetcher::GET,
+ this));
+ fetcher2->SetRequestContext(request_context_getter_.get());
+ scoped_ptr<URLFetcher> fetcher3 = scoped_ptr<URLFetcher>(
+ URLFetcher::Create(server_->GetURL("/test3"),
+ URLFetcher::GET,
+ this));
+ fetcher3->SetRequestContext(request_context_getter_.get());
+
+ // Fetch the three URLs concurrently.
+ fetcher1->Start();
+ fetcher2->Start();
+ fetcher3->Start();
+ WaitForResponses(3);
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher1->GetStatus().status());
+ EXPECT_EQ(HTTP_OK, fetcher1->GetResponseCode());
+ EXPECT_EQ("Raspberry chocolate", GetContentFromFetcher(*fetcher1));
+ EXPECT_EQ("text/html", GetContentTypeFromFetcher(*fetcher1));
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher2->GetStatus().status());
+ EXPECT_EQ(HTTP_OK, fetcher2->GetResponseCode());
+ EXPECT_EQ("Vanilla chocolate", GetContentFromFetcher(*fetcher2));
+ EXPECT_EQ("text/html", GetContentTypeFromFetcher(*fetcher2));
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher3->GetStatus().status());
+ EXPECT_EQ(HTTP_NOT_FOUND, fetcher3->GetResponseCode());
+ EXPECT_EQ("No chocolates", GetContentFromFetcher(*fetcher3));
+ EXPECT_EQ("text/plain", GetContentTypeFromFetcher(*fetcher3));
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/http_connection.cc b/chromium/net/test/embedded_test_server/http_connection.cc
new file mode 100644
index 00000000000..8b5317e320b
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_connection.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/http_connection.h"
+
+#include "net/socket/stream_listen_socket.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace net {
+namespace test_server {
+
+HttpConnection::HttpConnection(StreamListenSocket* socket,
+ const HandleRequestCallback& callback)
+ : socket_(socket),
+ callback_(callback) {
+}
+
+HttpConnection::~HttpConnection() {
+}
+
+void HttpConnection::SendResponse(scoped_ptr<HttpResponse> response) const {
+ const std::string response_string = response->ToResponseString();
+ socket_->Send(response_string.c_str(), response_string.length());
+}
+
+void HttpConnection::ReceiveData(const base::StringPiece& data) {
+ request_parser_.ProcessChunk(data);
+ if (request_parser_.ParseRequest() == HttpRequestParser::ACCEPTED) {
+ callback_.Run(this, request_parser_.GetRequest());
+ }
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/http_connection.h b/chromium/net/test/embedded_test_server/http_connection.h
new file mode 100644
index 00000000000..da9353404c6
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_connection.h
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_EMBEDDED_TEST_SERVER_HTTP_CONNECTION_H_
+#define NET_TEST_EMBEDDED_TEST_SERVER_HTTP_CONNECTION_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_piece.h"
+#include "net/test/embedded_test_server/http_request.h"
+
+namespace net {
+
+class StreamListenSocket;
+
+namespace test_server {
+
+class HttpConnection;
+class HttpResponse;
+
+// Calblack called when a request is parsed. Response should be sent
+// using HttpConnection::SendResponse() on the |connection| argument.
+typedef base::Callback<void(HttpConnection* connection,
+ scoped_ptr<HttpRequest> request)>
+ HandleRequestCallback;
+
+// Wraps the connection socket. Accepts incoming data and sends responses.
+// If a valid request is parsed, then |callback_| is invoked.
+class HttpConnection {
+ public:
+ HttpConnection(StreamListenSocket* socket,
+ const HandleRequestCallback& callback);
+ ~HttpConnection();
+
+ // Sends the HTTP response to the client.
+ void SendResponse(scoped_ptr<HttpResponse> response) const;
+
+ private:
+ friend class EmbeddedTestServer;
+
+ // Accepts raw chunk of data from the client. Internally, passes it to the
+ // HttpRequestParser class. If a request is parsed, then |callback_| is
+ // called.
+ void ReceiveData(const base::StringPiece& data);
+
+ scoped_refptr<StreamListenSocket> socket_;
+ const HandleRequestCallback callback_;
+ HttpRequestParser request_parser_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpConnection);
+};
+
+} // namespace test_server
+} // namespace net
+
+#endif // NET_TEST_EMBEDDED_TEST_SERVER_HTTP_CONNECTION_H_
diff --git a/chromium/net/test/embedded_test_server/http_request.cc b/chromium/net/test/embedded_test_server/http_request.cc
new file mode 100644
index 00000000000..8ac76d35923
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_request.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/http_request.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+
+namespace net {
+namespace test_server {
+
+namespace {
+
+size_t kRequestSizeLimit = 64 * 1024 * 1024; // 64 mb.
+
+// Helper function used to trim tokens in http request headers.
+std::string Trim(const std::string& value) {
+ std::string result;
+ TrimString(value, " \t", &result);
+ return result;
+}
+
+} // namespace
+
+HttpRequest::HttpRequest() : method(METHOD_UNKNOWN),
+ has_content(false) {
+}
+
+HttpRequest::~HttpRequest() {
+}
+
+HttpRequestParser::HttpRequestParser()
+ : http_request_(new HttpRequest()),
+ buffer_position_(0),
+ state_(STATE_HEADERS),
+ declared_content_length_(0) {
+}
+
+HttpRequestParser::~HttpRequestParser() {
+}
+
+void HttpRequestParser::ProcessChunk(const base::StringPiece& data) {
+ data.AppendToString(&buffer_);
+ DCHECK_LE(buffer_.size() + data.size(), kRequestSizeLimit) <<
+ "The HTTP request is too large.";
+}
+
+std::string HttpRequestParser::ShiftLine() {
+ size_t eoln_position = buffer_.find("\r\n", buffer_position_);
+ DCHECK_NE(std::string::npos, eoln_position);
+ const int line_length = eoln_position - buffer_position_;
+ std::string result = buffer_.substr(buffer_position_, line_length);
+ buffer_position_ += line_length + 2;
+ return result;
+}
+
+HttpRequestParser::ParseResult HttpRequestParser::ParseRequest() {
+ DCHECK_NE(STATE_ACCEPTED, state_);
+ // Parse the request from beginning. However, entire request may not be
+ // available in the buffer.
+ if (state_ == STATE_HEADERS) {
+ if (ParseHeaders() == ACCEPTED)
+ return ACCEPTED;
+ }
+ // This should not be 'else if' of the previous block, as |state_| can be
+ // changed in ParseHeaders().
+ if (state_ == STATE_CONTENT) {
+ if (ParseContent() == ACCEPTED)
+ return ACCEPTED;
+ }
+ return WAITING;
+}
+
+HttpRequestParser::ParseResult HttpRequestParser::ParseHeaders() {
+ // Check if the all request headers are available.
+ if (buffer_.find("\r\n\r\n", buffer_position_) == std::string::npos)
+ return WAITING;
+
+ // Parse request's the first header line.
+ // Request main main header, eg. GET /foobar.html HTTP/1.1
+ {
+ const std::string header_line = ShiftLine();
+ std::vector<std::string> header_line_tokens;
+ base::SplitString(header_line, ' ', &header_line_tokens);
+ DCHECK_EQ(3u, header_line_tokens.size());
+ // Method.
+ http_request_->method = GetMethodType(StringToLowerASCII(
+ header_line_tokens[0]));
+ // Address.
+ // Don't build an absolute URL as the parser does not know (should not
+ // know) anything about the server address.
+ http_request_->relative_url = header_line_tokens[1];
+ // Protocol.
+ const std::string protocol = StringToLowerASCII(header_line_tokens[2]);
+ CHECK(protocol == "http/1.0" || protocol == "http/1.1") <<
+ "Protocol not supported: " << protocol;
+ }
+
+ // Parse further headers.
+ {
+ std::string header_name;
+ while (true) {
+ std::string header_line = ShiftLine();
+ if (header_line.empty())
+ break;
+
+ if (header_line[0] == ' ' || header_line[0] == '\t') {
+ // Continuation of the previous multi-line header.
+ std::string header_value =
+ Trim(header_line.substr(1, header_line.size() - 1));
+ http_request_->headers[header_name] += " " + header_value;
+ } else {
+ // New header.
+ size_t delimiter_pos = header_line.find(":");
+ DCHECK_NE(std::string::npos, delimiter_pos) << "Syntax error.";
+ header_name = Trim(header_line.substr(0, delimiter_pos));
+ std::string header_value = Trim(header_line.substr(
+ delimiter_pos + 1,
+ header_line.size() - delimiter_pos - 1));
+ http_request_->headers[header_name] = header_value;
+ }
+ }
+ }
+
+ // Headers done. Is any content data attached to the request?
+ declared_content_length_ = 0;
+ if (http_request_->headers.count("Content-Length") > 0) {
+ http_request_->has_content = true;
+ const bool success = base::StringToSizeT(
+ http_request_->headers["Content-Length"],
+ &declared_content_length_);
+ DCHECK(success) << "Malformed Content-Length header's value.";
+ }
+ if (declared_content_length_ == 0) {
+ // No content data, so parsing is finished.
+ state_ = STATE_ACCEPTED;
+ return ACCEPTED;
+ }
+
+ // The request has not yet been parsed yet, content data is still to be
+ // processed.
+ state_ = STATE_CONTENT;
+ return WAITING;
+}
+
+HttpRequestParser::ParseResult HttpRequestParser::ParseContent() {
+ const size_t available_bytes = buffer_.size() - buffer_position_;
+ const size_t fetch_bytes = std::min(
+ available_bytes,
+ declared_content_length_ - http_request_->content.size());
+ http_request_->content.append(buffer_.data() + buffer_position_,
+ fetch_bytes);
+ buffer_position_ += fetch_bytes;
+
+ if (declared_content_length_ == http_request_->content.size()) {
+ state_ = STATE_ACCEPTED;
+ return ACCEPTED;
+ }
+
+ state_ = STATE_CONTENT;
+ return WAITING;
+}
+
+scoped_ptr<HttpRequest> HttpRequestParser::GetRequest() {
+ DCHECK_EQ(STATE_ACCEPTED, state_);
+ scoped_ptr<HttpRequest> result = http_request_.Pass();
+
+ // Prepare for parsing a new request.
+ state_ = STATE_HEADERS;
+ http_request_.reset(new HttpRequest());
+ buffer_.clear();
+ buffer_position_ = 0;
+ declared_content_length_ = 0;
+
+ return result.Pass();
+}
+
+HttpMethod HttpRequestParser::GetMethodType(const std::string& token) const {
+ if (token == "get") {
+ return METHOD_GET;
+ } else if (token == "head") {
+ return METHOD_HEAD;
+ } else if (token == "post") {
+ return METHOD_POST;
+ } else if (token == "put") {
+ return METHOD_PUT;
+ } else if (token == "delete") {
+ return METHOD_DELETE;
+ } else if (token == "patch") {
+ return METHOD_PATCH;
+ }
+ NOTREACHED() << "Method not implemented: " << token;
+ return METHOD_UNKNOWN;
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/http_request.h b/chromium/net/test/embedded_test_server/http_request.h
new file mode 100644
index 00000000000..1e849d87d25
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_request.h
@@ -0,0 +1,115 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_EMBEDDED_TEST_SERVER_HTTP_REQUEST_H_
+#define NET_TEST_EMBEDDED_TEST_SERVER_HTTP_REQUEST_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+
+namespace net {
+namespace test_server {
+
+// Methods of HTTP requests supported by the test HTTP server.
+enum HttpMethod {
+ METHOD_UNKNOWN,
+ METHOD_GET,
+ METHOD_HEAD,
+ METHOD_POST,
+ METHOD_PUT,
+ METHOD_DELETE,
+ METHOD_PATCH,
+};
+
+// Represents a HTTP request. Since it can be big, use scoped_ptr to pass it
+// instead of copying. However, the struct is copyable so tests can save and
+// examine a HTTP request.
+struct HttpRequest {
+ HttpRequest();
+ ~HttpRequest();
+
+ std::string relative_url; // Starts with '/'. Example: "/test?query=foo"
+ HttpMethod method;
+ std::map<std::string, std::string> headers;
+ std::string content;
+ bool has_content;
+};
+
+// Parses the input data and produces a valid HttpRequest object. If there is
+// more than one request in one chunk, then only the first one will be parsed.
+// The common use is as below:
+// HttpRequestParser parser;
+// (...)
+// void OnDataChunkReceived(Socket* socket, const char* data, int size) {
+// parser.ProcessChunk(std::string(data, size));
+// if (parser.ParseRequest() == HttpRequestParser::ACCEPTED) {
+// scoped_ptr<HttpRequest> request = parser.GetRequest();
+// (... process the request ...)
+// }
+class HttpRequestParser {
+ public:
+ // Parsing result.
+ enum ParseResult {
+ WAITING, // A request is not completed yet, waiting for more data.
+ ACCEPTED, // A request has been parsed and it is ready to be processed.
+ };
+
+ // Parser state.
+ enum State {
+ STATE_HEADERS, // Waiting for a request headers.
+ STATE_CONTENT, // Waiting for content data.
+ STATE_ACCEPTED, // Request has been parsed.
+ };
+
+ HttpRequestParser();
+ ~HttpRequestParser();
+
+ // Adds chunk of data into the internal buffer.
+ void ProcessChunk(const base::StringPiece& data);
+
+ // Parses the http request (including data - if provided).
+ // If returns ACCEPTED, then it means that the whole request has been found
+ // in the internal buffer (and parsed). After calling GetRequest(), it will be
+ // ready to parse another request.
+ ParseResult ParseRequest();
+
+ // Retrieves parsed request. Can be only called, when the parser is in
+ // STATE_ACCEPTED state. After calling it, the parser is ready to parse
+ // another request.
+ scoped_ptr<HttpRequest> GetRequest();
+
+ private:
+ HttpMethod GetMethodType(const std::string& token) const;
+
+ // Parses headers and returns ACCEPTED if whole request was parsed. Otherwise
+ // returns WAITING.
+ ParseResult ParseHeaders();
+
+ // Parses request's content data and returns ACCEPTED if all of it have been
+ // processed. Chunked Transfer Encoding *is not* supported.
+ ParseResult ParseContent();
+
+ // Fetches the next line from the buffer. Result does not contain \r\n.
+ // Returns an empty string for an empty line. It will assert if there is
+ // no line available.
+ std::string ShiftLine();
+
+ scoped_ptr<HttpRequest> http_request_;
+ std::string buffer_;
+ size_t buffer_position_; // Current position in the internal buffer.
+ State state_;
+ // Content length of the request currently being parsed.
+ size_t declared_content_length_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpRequestParser);
+};
+
+} // namespace test_server
+} // namespace net
+
+#endif // NET_TEST_EMBEDDED_TEST_SERVER_HTTP_REQUEST_H_
diff --git a/chromium/net/test/embedded_test_server/http_request_unittest.cc b/chromium/net/test/embedded_test_server/http_request_unittest.cc
new file mode 100644
index 00000000000..b45742cf37b
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_request_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/http_request.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test_server {
+
+TEST(HttpRequestTest, ParseRequest) {
+ HttpRequestParser parser;
+
+ // Process request in chunks to check if the parser deals with border cases.
+ // Also, check multi-line headers as well as multiple requests in the same
+ // chunk. This basically should cover all the simplest border cases.
+ parser.ProcessChunk("POST /foobar.html HTTP/1.1\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ parser.ProcessChunk("Host: localhost:1234\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ parser.ProcessChunk("Multi-line-header: abcd\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ parser.ProcessChunk(" efgh\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ parser.ProcessChunk(" ijkl\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ parser.ProcessChunk("Content-Length: 10\r\n\r\n");
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+ // Content data and another request in the same chunk (possible in http/1.1).
+ parser.ProcessChunk("1234567890GET /another.html HTTP/1.1\r\n\r\n");
+ ASSERT_EQ(HttpRequestParser::ACCEPTED, parser.ParseRequest());
+
+ // Fetch the first request and validate it.
+ {
+ scoped_ptr<HttpRequest> request = parser.GetRequest();
+ EXPECT_EQ("/foobar.html", request->relative_url);
+ EXPECT_EQ(METHOD_POST, request->method);
+ EXPECT_EQ("1234567890", request->content);
+ ASSERT_EQ(3u, request->headers.size());
+
+ EXPECT_EQ(1u, request->headers.count("Host"));
+ EXPECT_EQ(1u, request->headers.count("Multi-line-header"));
+ EXPECT_EQ(1u, request->headers.count("Content-Length"));
+
+ EXPECT_EQ("localhost:1234", request->headers["Host"]);
+ EXPECT_EQ("abcd efgh ijkl", request->headers["Multi-line-header"]);
+ EXPECT_EQ("10", request->headers["Content-Length"]);
+ }
+
+ // No other request available yet since we do not support multiple requests
+ // per connection.
+ EXPECT_EQ(HttpRequestParser::WAITING, parser.ParseRequest());
+}
+
+TEST(HttpRequestTest, ParseRequestWithEmptyBody) {
+ HttpRequestParser parser;
+
+ parser.ProcessChunk("POST /foobar.html HTTP/1.1\r\n");
+ parser.ProcessChunk("Content-Length: 0\r\n\r\n");
+ ASSERT_EQ(HttpRequestParser::ACCEPTED, parser.ParseRequest());
+
+ scoped_ptr<HttpRequest> request = parser.GetRequest();
+ EXPECT_EQ("", request->content);
+ EXPECT_TRUE(request->has_content);
+ EXPECT_EQ(1u, request->headers.count("Content-Length"));
+ EXPECT_EQ("0", request->headers["Content-Length"]);
+}
+
+TEST(HttpRequestTest, ParseRequestWithoutBody) {
+ HttpRequestParser parser;
+
+ parser.ProcessChunk("POST /foobar.html HTTP/1.1\r\n\r\n");
+ ASSERT_EQ(HttpRequestParser::ACCEPTED, parser.ParseRequest());
+
+ scoped_ptr<HttpRequest> request = parser.GetRequest();
+ EXPECT_EQ("", request->content);
+ EXPECT_FALSE(request->has_content);
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/http_response.cc b/chromium/net/test/embedded_test_server/http_response.cc
new file mode 100644
index 00000000000..04155b5ffa5
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_response.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/http_response.h"
+
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_status_code.h"
+
+namespace net {
+namespace test_server {
+
+HttpResponse::~HttpResponse() {
+}
+
+BasicHttpResponse::BasicHttpResponse() : code_(HTTP_OK) {
+}
+
+BasicHttpResponse::~BasicHttpResponse() {
+}
+
+std::string BasicHttpResponse::ToResponseString() const {
+ // Response line with headers.
+ std::string response_builder;
+
+ std::string http_reason_phrase(GetHttpReasonPhrase(code_));
+
+ // TODO(mtomasz): For http/1.0 requests, send http/1.0.
+ base::StringAppendF(&response_builder,
+ "HTTP/1.1 %d %s\r\n",
+ code_,
+ http_reason_phrase.c_str());
+ base::StringAppendF(&response_builder, "Connection: close\r\n");
+ base::StringAppendF(&response_builder,
+ "Content-Length: %" PRIuS "\r\n",
+ content_.size());
+ base::StringAppendF(&response_builder,
+ "Content-Type: %s\r\n",
+ content_type_.c_str());
+ for (size_t i = 0; i < custom_headers_.size(); ++i) {
+ const std::string& header_name = custom_headers_[i].first;
+ const std::string& header_value = custom_headers_[i].second;
+ DCHECK(header_value.find_first_of("\n\r") == std::string::npos) <<
+ "Malformed header value.";
+ base::StringAppendF(&response_builder,
+ "%s: %s\r\n",
+ header_name.c_str(),
+ header_value.c_str());
+ }
+ base::StringAppendF(&response_builder, "\r\n");
+
+ return response_builder + content_;
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/embedded_test_server/http_response.h b/chromium/net/test/embedded_test_server/http_response.h
new file mode 100644
index 00000000000..422a52553ea
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_response.h
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_EMBEDDED_TEST_SERVER_HTTP_RESPONSE_H_
+#define NET_TEST_EMBEDDED_TEST_SERVER_HTTP_RESPONSE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/http/http_status_code.h"
+
+namespace net {
+namespace test_server {
+
+// Interface for HTTP response implementations.
+class HttpResponse{
+ public:
+ virtual ~HttpResponse();
+
+ // Returns raw contents to be written to the network socket
+ // in response. If you intend to make this a valid HTTP response,
+ // it should start with "HTTP/x.x" line, followed by response headers.
+ virtual std::string ToResponseString() const = 0;
+};
+
+// This class is used to handle basic HTTP responses with commonly used
+// response headers such as "Content-Type".
+class BasicHttpResponse : public HttpResponse {
+ public:
+ BasicHttpResponse();
+ virtual ~BasicHttpResponse();
+
+ // The response code.
+ HttpStatusCode code() const { return code_; }
+ void set_code(HttpStatusCode code) { code_ = code; }
+
+ // The content of the response.
+ const std::string& content() const { return content_; }
+ void set_content(const std::string& content) { content_ = content; }
+
+ // The content type.
+ const std::string& content_type() const { return content_type_; }
+ void set_content_type(const std::string& content_type) {
+ content_type_ = content_type;
+ }
+
+ // Adds a custom header.
+ void AddCustomHeader(const std::string& key, const std::string& value) {
+ custom_headers_.push_back(std::make_pair(key, value));
+ }
+
+ // Generates and returns a http response string.
+ virtual std::string ToResponseString() const OVERRIDE;
+
+ private:
+ HttpStatusCode code_;
+ std::string content_;
+ std::string content_type_;
+ std::vector<std::pair<std::string, std::string> > custom_headers_;
+
+ DISALLOW_COPY_AND_ASSIGN(BasicHttpResponse);
+};
+
+} // namespace test_server
+} // namespace net
+
+#endif // NET_TEST_EMBEDDED_TEST_SERVER_HTTP_RESPONSE_H_
diff --git a/chromium/net/test/embedded_test_server/http_response_unittest.cc b/chromium/net/test/embedded_test_server/http_response_unittest.cc
new file mode 100644
index 00000000000..83b767f5fa8
--- /dev/null
+++ b/chromium/net/test/embedded_test_server/http_response_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#include "net/test/embedded_test_server/http_response.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace test_server {
+
+TEST(HttpResponseTest, GenerateResponse) {
+ BasicHttpResponse response;
+ response.set_code(HTTP_OK);
+ response.set_content("Sample content - Hello world!");
+ response.set_content_type("text/plain");
+ response.AddCustomHeader("Simple-Header", "Simple value.");
+
+ std::string kExpectedResponseString =
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Length: 29\r\n"
+ "Content-Type: text/plain\r\n"
+ "Simple-Header: Simple value.\r\n\r\n"
+ "Sample content - Hello world!";
+
+ EXPECT_EQ(kExpectedResponseString, response.ToResponseString());
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/chromium/net/test/net_test_suite.cc b/chromium/net/test/net_test_suite.cc
new file mode 100644
index 00000000000..175cec29754
--- /dev/null
+++ b/chromium/net/test/net_test_suite.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 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.
+
+#include "net/test/net_test_suite.h"
+
+#include "base/message_loop/message_loop.h"
+#include "net/base/network_change_notifier.h"
+#include "net/http/http_stream_factory.h"
+#include "net/spdy/spdy_session.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include "net/ocsp/nss_ocsp.h"
+#endif
+
+class StaticReset : public ::testing::EmptyTestEventListener {
+ virtual void OnTestStart(const ::testing::TestInfo& test_info) OVERRIDE {
+ net::HttpStreamFactory::ResetStaticSettingsToInit();
+ }
+};
+
+NetTestSuite::NetTestSuite(int argc, char** argv)
+ : TestSuite(argc, argv) {
+}
+
+NetTestSuite::NetTestSuite(int argc, char** argv,
+ bool create_at_exit_manager)
+ : TestSuite(argc, argv, create_at_exit_manager) {
+}
+
+NetTestSuite::~NetTestSuite() {}
+
+void NetTestSuite::Initialize() {
+ TestSuite::Initialize();
+ ::testing::UnitTest::GetInstance()->listeners().Append(new StaticReset());
+ InitializeTestThread();
+}
+
+void NetTestSuite::Shutdown() {
+#if defined(USE_NSS) || defined(OS_IOS)
+ net::ShutdownNSSHttpIO();
+#endif
+
+ // We want to destroy this here before the TestSuite continues to tear down
+ // the environment.
+ message_loop_.reset();
+
+ TestSuite::Shutdown();
+}
+
+void NetTestSuite::InitializeTestThread() {
+ network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock());
+
+ InitializeTestThreadNoNetworkChangeNotifier();
+}
+
+void NetTestSuite::InitializeTestThreadNoNetworkChangeNotifier() {
+ host_resolver_proc_ = new net::RuleBasedHostResolverProc(NULL);
+ scoped_host_resolver_proc_.Init(host_resolver_proc_.get());
+ // In case any attempts are made to resolve host names, force them all to
+ // be mapped to localhost. This prevents DNS queries from being sent in
+ // the process of running these unit tests.
+ host_resolver_proc_->AddRule("*", "127.0.0.1");
+
+ message_loop_.reset(new base::MessageLoopForIO());
+}
diff --git a/chromium/net/test/net_test_suite.h b/chromium/net/test/net_test_suite.h
new file mode 100644
index 00000000000..c8479d724ed
--- /dev/null
+++ b/chromium/net/test/net_test_suite.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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_NET_TEST_SUITE_H_
+#define NET_TEST_NET_TEST_SUITE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+#include "net/dns/mock_host_resolver.h"
+
+namespace base {
+class MessageLoop;
+}
+
+namespace net {
+class NetworkChangeNotifier;
+}
+
+class NetTestSuite : public base::TestSuite {
+ public:
+ NetTestSuite(int argc, char** argv);
+ virtual ~NetTestSuite();
+
+ virtual void Initialize() OVERRIDE;
+
+ virtual void Shutdown() OVERRIDE;
+
+ protected:
+ // This constructor is only accessible to specialized net test
+ // implementations which need to control the creation of an AtExitManager
+ // instance for the duration of the test.
+ NetTestSuite(int argc, char** argv, bool create_at_exit_manager);
+
+ // Called from within Initialize(), but separate so that derived classes
+ // can initialize the NetTestSuite instance only and not
+ // TestSuite::Initialize(). TestSuite::Initialize() performs some global
+ // initialization that can only be done once.
+ void InitializeTestThread();
+
+ // Same as above, except it does not create a mock
+ // NetworkChangeNotifier. Use this if your test needs to create and
+ // manage its own mock NetworkChangeNotifier, or if your test uses
+ // the production NetworkChangeNotifier.
+ void InitializeTestThreadNoNetworkChangeNotifier();
+
+ private:
+ scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
+ scoped_ptr<base::MessageLoop> message_loop_;
+ scoped_refptr<net::RuleBasedHostResolverProc> host_resolver_proc_;
+ net::ScopedDefaultHostResolverProc scoped_host_resolver_proc_;
+};
+
+#endif // NET_TEST_NET_TEST_SUITE_H_
diff --git a/chromium/net/test/openssl_helper.cc b/chromium/net/test/openssl_helper.cc
new file mode 100644
index 00000000000..25989cb6016
--- /dev/null
+++ b/chromium/net/test/openssl_helper.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2010 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.
+
+// This helper binary is only used for testing Chrome's SSL stack.
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+static const char kDefaultPEMFile[] = "net/data/ssl/certificates/ok_cert.pem";
+
+// Server Name Indication callback from OpenSSL
+static int sni_cb(SSL *s, int *ad, void *arg) {
+ const char* servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+ if (servername && strcmp(servername, "test.example.com") == 0)
+ *reinterpret_cast<bool*>(arg) = true;
+
+ return SSL_TLSEXT_ERR_OK;
+}
+
+// Client certificate verification callback from OpenSSL
+static int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
+ return 1;
+}
+
+// Next Protocol Negotiation callback from OpenSSL
+static int next_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned int *outlen, void *arg) {
+ bool* npn_mispredict = reinterpret_cast<bool*>(arg);
+ static char kProtos[] = "\003foo\003bar";
+ static char kProtos2[] = "\003baz\003boo";
+ static unsigned count = 0;
+
+ if (!*npn_mispredict || count == 0) {
+ *out = (const unsigned char*) kProtos;
+ *outlen = sizeof(kProtos) - 1;
+ } else {
+ *out = (const unsigned char*) kProtos2;
+ *outlen = sizeof(kProtos2) - 1;
+ }
+ count++;
+ return SSL_TLSEXT_ERR_OK;
+}
+
+int
+main(int argc, char **argv) {
+ SSL_library_init();
+ ERR_load_crypto_strings();
+ OpenSSL_add_all_algorithms();
+ SSL_load_error_strings();
+
+ bool sni = false, sni_good = false, snap_start = false;
+ bool snap_start_recovery = false, sslv3 = false, session_tickets = false;
+ bool fail_resume = false, client_cert = false, npn = false;
+ bool npn_mispredict = false;
+
+ const char* key_file = kDefaultPEMFile;
+ const char* cert_file = kDefaultPEMFile;
+
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "sni") == 0) {
+ // Require SNI
+ sni = true;
+ } else if (strcmp(argv[i], "snap-start") == 0) {
+ // Support Snap Start
+ snap_start = true;
+ } else if (strcmp(argv[i], "snap-start-recovery") == 0) {
+ // Support Snap Start, but always trigger a recovery
+ snap_start = true;
+ snap_start_recovery = true;
+ } else if (strcmp(argv[i], "sslv3") == 0) {
+ // Use SSLv3
+ sslv3 = true;
+ } else if (strcmp(argv[i], "session-tickets") == 0) {
+ // Enable Session Tickets
+ session_tickets = true;
+ } else if (strcmp(argv[i], "fail-resume") == 0) {
+ // Always fail to resume sessions
+ fail_resume = true;
+ } else if (strcmp(argv[i], "client-cert") == 0) {
+ // Request a client certificate
+ client_cert = true;
+ } else if (strcmp(argv[i], "npn") == 0) {
+ // Advertise NPN
+ npn = true;
+ } else if (strcmp(argv[i], "npn-mispredict") == 0) {
+ // Advertise NPN
+ npn = true;
+ npn_mispredict = true;
+ } else if (strcmp(argv[i], "--key-file") == 0) {
+ // Use alternative key file
+ i++;
+ if (i == argc) {
+ fprintf(stderr, "Missing argument to --key-file\n");
+ return 1;
+ }
+ key_file = argv[i];
+ } else if (strcmp(argv[i], "--cert-file") == 0) {
+ // Use alternative certificate file
+ i++;
+ if (i == argc) {
+ fprintf(stderr, "Missing argument to --cert-file\n");
+ return 1;
+ }
+ cert_file = argv[i];
+ } else {
+ fprintf(stderr, "Unknown argument: %s\n", argv[i]);
+ return 1;
+ }
+ }
+
+ SSL_CTX* ctx;
+
+ if (sslv3) {
+ ctx = SSL_CTX_new(SSLv3_server_method());
+ } else {
+ ctx = SSL_CTX_new(TLSv1_server_method());
+ }
+
+ if (sni) {
+ SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb);
+ SSL_CTX_set_tlsext_servername_arg(ctx, &sni_good);
+ }
+
+ BIO* key = BIO_new(BIO_s_file());
+ if (BIO_read_filename(key, key_file) <= 0) {
+ fprintf(stderr, "Failed to read %s\n", key_file);
+ return 1;
+ }
+
+ EVP_PKEY *pkey = PEM_read_bio_PrivateKey(key, NULL, NULL, NULL);
+ if (!pkey) {
+ fprintf(stderr, "Failed to parse %s\n", key_file);
+ return 1;
+ }
+ BIO_free(key);
+
+
+ BIO* cert = BIO_new(BIO_s_file());
+ if (BIO_read_filename(cert, cert_file) <= 0) {
+ fprintf(stderr, "Failed to read %s\n", cert_file);
+ return 1;
+ }
+
+ X509 *pcert = PEM_read_bio_X509_AUX(cert, NULL, NULL, NULL);
+ if (!pcert) {
+ fprintf(stderr, "Failed to parse %s\n", cert_file);
+ return 1;
+ }
+ BIO_free(cert);
+
+ if (SSL_CTX_use_certificate(ctx, pcert) <= 0) {
+ fprintf(stderr, "Failed to load %s\n", cert_file);
+ return 1;
+ }
+
+ if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0) {
+ fprintf(stderr, "Failed to load %s\n", key_file);
+ return 1;
+ }
+
+ if (!SSL_CTX_check_private_key(ctx)) {
+ fprintf(stderr, "Public and private keys don't match\n");
+ return 1;
+ }
+
+ if (client_cert)
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb);
+
+ if (session_tickets)
+ SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
+
+ if (snap_start) {
+ static const unsigned char orbit[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+ SSL_CTX_set_snap_start_orbit(ctx, orbit);
+ }
+
+ if (npn)
+ SSL_CTX_set_next_protos_advertised_cb(ctx, next_proto_cb, &npn_mispredict);
+
+ unsigned connection_limit = 1;
+ if (snap_start || session_tickets)
+ connection_limit = 2;
+ if (npn_mispredict)
+ connection_limit = 3;
+
+ for (unsigned connections = 0; connections < connection_limit;
+ connections++) {
+ const int fd = accept(3, NULL, NULL);
+
+ SSL* server = SSL_new(ctx);
+ BIO* bio = BIO_new_socket(fd, 1 /* take ownership of fd */);
+ SSL_set_bio(server, bio, bio);
+
+ if (fail_resume) {
+ SSL_set_session_id_context(server, (unsigned char*) &connections,
+ sizeof(connections));
+ }
+
+ int err;
+ for (;;) {
+ const int ret = SSL_accept(server);
+ if (ret == 1)
+ break;
+
+ err = SSL_get_error(server, ret);
+ if (err == SSL_ERROR_WANT_READ)
+ continue;
+ if (err == SSL_ERROR_SERVER_RANDOM_VALIDATION_PENDING && snap_start) {
+ SSL_set_suggested_server_random_validity(
+ server, !snap_start_recovery);
+ continue;
+ }
+ ERR_print_errors_fp(stderr);
+ fprintf(stderr, "SSL_accept failed: %d\n", err);
+ return 1;
+ }
+
+ if (sni && !sni_good) {
+ fprintf(stderr, "SNI failed\n");
+ return 1;
+ }
+
+ if (npn) {
+ const unsigned char *data, *expected_data;
+ unsigned len, expected_len;
+ SSL_get0_next_proto_negotiated(server, &data, &len);
+ if (!npn_mispredict || connections == 0) {
+ expected_data = (unsigned char*) "foo";
+ expected_len = 3;
+ } else {
+ expected_data = (unsigned char*) "baz";
+ expected_len = 3;
+ }
+ if (len != expected_len || memcmp(data, expected_data, len) != 0) {
+ fprintf(stderr, "Bad NPN: %d\n", len);
+ return 1;
+ }
+ }
+
+ unsigned char buffer[6];
+
+ int ret = SSL_read(server, buffer, sizeof(buffer));
+ if (ret == -1) {
+ err = SSL_get_error(server, ret);
+ ERR_print_errors_fp(stderr);
+ fprintf(stderr, "SSL_read failed: %d\n", err);
+ }
+ if (memcmp(buffer, "hello!", sizeof(buffer)) == 0) {
+ SSL_write(server, "goodbye!", 8);
+ }
+
+ SSL_shutdown(server);
+ SSL_shutdown(server);
+ }
+
+ SSL_CTX_free(ctx);
+
+ return 0;
+}
diff --git a/chromium/net/test/python_utils.cc b/chromium/net/test/python_utils.cc
new file mode 100644
index 00000000000..30c5f679a8a
--- /dev/null
+++ b/chromium/net/test/python_utils.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2011 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/python_utils.h"
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+
+const char kPythonPathEnv[] = "PYTHONPATH";
+
+void AppendToPythonPath(const base::FilePath& dir) {
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ std::string old_path;
+ std::string dir_path;
+#if defined(OS_WIN)
+ dir_path = WideToUTF8(dir.value());
+#elif defined(OS_POSIX)
+ dir_path = dir.value();
+#endif
+ if (!env->GetVar(kPythonPathEnv, &old_path)) {
+ env->SetVar(kPythonPathEnv, dir_path.c_str());
+ } else if (old_path.find(dir_path) == std::string::npos) {
+ std::string new_path(old_path);
+#if defined(OS_WIN)
+ new_path.append(";");
+#elif defined(OS_POSIX)
+ new_path.append(":");
+#endif
+ new_path.append(dir_path.c_str());
+ env->SetVar(kPythonPathEnv, new_path);
+ }
+}
+
+namespace {
+
+// Search for |to_try|, rolling up the directory tree from
+// |start_dir|. If found, return true and put the path to |to_try| in
+// |out_dir|. If not, return false and leave |out_dir| untouched.
+bool TryRelativeToDir(const base::FilePath& start_dir,
+ const base::FilePath& to_try,
+ base::FilePath* out_dir) {
+ base::FilePath dir(start_dir);
+ while (!base::DirectoryExists(dir.Append(to_try))) {
+ base::FilePath parent = dir.DirName();
+ if (parent == dir) {
+ // We hit the root directory.
+ return false;
+ }
+ dir = parent;
+ }
+ *out_dir = dir;
+ return true;
+}
+
+} // namespace
+
+bool GetPyProtoPath(base::FilePath* dir) {
+ // Locate the Python code generated by the protocol buffers compiler.
+ base::FilePath generated_code_dir;
+ if (!PathService::Get(base::DIR_EXE, &generated_code_dir)) {
+ LOG(ERROR) << "Can't find " << generated_code_dir.value();
+ return false;
+ }
+
+ const base::FilePath kPyProto(FILE_PATH_LITERAL("pyproto"));
+
+#if defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ base::FilePath source_dir;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &source_dir)) {
+ LOG(ERROR) << "Can't find " << source_dir.value();
+ return false;
+ }
+ // On Mac, and possibly Chrome OS, DIR_EXE might be pointing deep
+ // into the Release/ (or Debug/) directory and we can't depend on
+ // how far down it goes. So we walk upwards from DIR_EXE until we
+ // find a likely looking spot.
+ if (!TryRelativeToDir(generated_code_dir, kPyProto, dir)) {
+ LOG(WARNING) << "Can't find " << kPyProto.value()
+ << " next to " << generated_code_dir.value();
+ // On Chrome OS, we may have installed the test binaries and support tools
+ // in a wholly separate location, relative to DIR_SOURCE_ROOT. We'll want
+ // to do a similar investigation from that point as well.
+ generated_code_dir = source_dir
+ .Append(FILE_PATH_LITERAL("out"))
+ .Append(FILE_PATH_LITERAL("Release"));
+ if (!TryRelativeToDir(generated_code_dir, kPyProto, dir)) {
+ LOG(WARNING) << "Can't find " << kPyProto.value()
+ << " next to " << generated_code_dir.value();
+ return false;
+ }
+ }
+ generated_code_dir = *dir;
+#endif
+ *dir = generated_code_dir.Append(kPyProto);
+ VLOG(2) << "Found " << kPyProto.value() << " in " << dir->value();
+ return true;
+}
+
+bool GetPythonCommand(CommandLine* python_cmd) {
+ DCHECK(python_cmd);
+ base::FilePath dir;
+#if defined(OS_WIN)
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &dir))
+ return false;
+ dir = dir.Append(FILE_PATH_LITERAL("third_party"))
+ .Append(FILE_PATH_LITERAL("python_26"))
+ .Append(FILE_PATH_LITERAL("python.exe"));
+#elif defined(OS_POSIX)
+ dir = base::FilePath("python");
+#endif
+
+ python_cmd->SetProgram(dir);
+
+ // Launch python in unbuffered mode, so that python output doesn't mix with
+ // gtest output in buildbot log files. See http://crbug.com/147368.
+ python_cmd->AppendArg("-u");
+
+ return true;
+}
diff --git a/chromium/net/test/python_utils.h b/chromium/net/test/python_utils.h
new file mode 100644
index 00000000000..30776456503
--- /dev/null
+++ b/chromium/net/test/python_utils.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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_PYTHON_UTILS_H_
+#define NET_TEST_PYTHON_UTILS_H_
+
+#include "base/compiler_specific.h"
+
+class CommandLine;
+
+namespace base {
+class FilePath;
+}
+
+// This is the python path variable name.
+extern const char kPythonPathEnv[];
+
+// Appends the dir to python path environment variable.
+void AppendToPythonPath(const base::FilePath& dir);
+
+// Return the location of the compiler-generated python protobuf.
+bool GetPyProtoPath(base::FilePath* dir);
+
+// Returns the command that should be used to launch Python.
+bool GetPythonCommand(CommandLine* python_cmd) WARN_UNUSED_RESULT;
+
+#endif // NET_TEST_PYTHON_UTILS_H_
diff --git a/chromium/net/test/python_utils_unittest.cc b/chromium/net/test/python_utils_unittest.cc
new file mode 100644
index 00000000000..04f11ec266d
--- /dev/null
+++ b/chromium/net/test/python_utils_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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 <string>
+
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/launch.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/test/python_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(PythonUtils, Append) {
+ const base::FilePath::CharType kAppendDir1[] =
+ FILE_PATH_LITERAL("test/path_append1");
+ const base::FilePath::CharType kAppendDir2[] =
+ FILE_PATH_LITERAL("test/path_append2");
+
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+
+ std::string python_path;
+ base::FilePath append_path1(kAppendDir1);
+ base::FilePath append_path2(kAppendDir2);
+
+ // Get a clean start
+ env->UnSetVar(kPythonPathEnv);
+
+ // Append the path
+ AppendToPythonPath(append_path1);
+ env->GetVar(kPythonPathEnv, &python_path);
+ ASSERT_EQ(python_path, "test/path_append1");
+
+ // Append the safe path again, nothing changes
+ AppendToPythonPath(append_path2);
+ env->GetVar(kPythonPathEnv, &python_path);
+#if defined(OS_WIN)
+ ASSERT_EQ(std::string("test/path_append1;test/path_append2"), python_path);
+#elif defined(OS_POSIX)
+ ASSERT_EQ(std::string("test/path_append1:test/path_append2"), python_path);
+#endif
+}
+
+TEST(PythonUtils, PythonRunTime) {
+ CommandLine cmd_line(CommandLine::NO_PROGRAM);
+ EXPECT_TRUE(GetPythonCommand(&cmd_line));
+
+ // Run a python command to print a string and make sure the output is what
+ // we want.
+ cmd_line.AppendArg("-c");
+ std::string input("PythonUtilsTest");
+ std::string python_cmd = base::StringPrintf("print '%s';", input.c_str());
+ cmd_line.AppendArg(python_cmd);
+ std::string output;
+ EXPECT_TRUE(base::GetAppOutput(cmd_line, &output));
+ TrimWhitespace(output, TRIM_TRAILING, &output);
+ EXPECT_EQ(input, output);
+}
diff --git a/chromium/net/test/run_all_unittests.cc b/chromium/net/test/run_all_unittests.cc
new file mode 100644
index 00000000000..d8392ff0e2a
--- /dev/null
+++ b/chromium/net/test/run_all_unittests.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#include "base/metrics/statistics_recorder.h"
+#include "build/build_config.h"
+#include "crypto/nss_util.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/socket/ssl_server_socket.h"
+#include "net/spdy/spdy_session.h"
+#include "net/test/net_test_suite.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "net/android/net_jni_registrar.h"
+#endif
+
+#if !defined(OS_IOS)
+#include "net/proxy/proxy_resolver_v8.h"
+#endif
+
+using net::internal::ClientSocketPoolBaseHelper;
+using net::SpdySession;
+
+int main(int argc, char** argv) {
+ // Record histograms, so we can get histograms data in tests.
+ base::StatisticsRecorder::Initialize();
+
+#if defined(OS_ANDROID)
+ // Register JNI bindings for android. Doing it early as the test suite setup
+ // may initiate a call to Java.
+ net::android::RegisterJni(base::android::AttachCurrentThread());
+#endif
+
+ NetTestSuite test_suite(argc, argv);
+ ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false);
+
+#if defined(OS_WIN)
+ // We want to be sure to init NSPR on the main thread.
+ crypto::EnsureNSPRInit();
+#endif
+
+ // Enable support for SSL server sockets, which must be done while
+ // single-threaded.
+ net::EnableSSLServerSockets();
+
+#if !defined(OS_IOS)
+ // This has to be done on the main thread.
+ net::ProxyResolverV8::RememberDefaultIsolate();
+#endif
+
+ return test_suite.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
new file mode 100644
index 00000000000..c13745bf51e
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/base_test_server.cc
@@ -0,0 +1,407 @@
+// Copyright 2013 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/spawned_test_server/base_test_server.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cert/test_root_certs.h"
+#include "net/dns/host_resolver.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+std::string GetHostname(BaseTestServer::Type type,
+ const BaseTestServer::SSLOptions& options) {
+ if (BaseTestServer::UsingSSL(type) &&
+ options.server_certificate ==
+ BaseTestServer::SSLOptions::CERT_MISMATCHED_NAME) {
+ // Return a different hostname string that resolves to the same hostname.
+ return "localhost";
+ }
+
+ // Use the 127.0.0.1 as default.
+ return BaseTestServer::kLocalhost;
+}
+
+void GetCiphersList(int cipher, base::ListValue* values) {
+ if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_RC4)
+ values->Append(new base::StringValue("rc4"));
+ if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_AES128)
+ values->Append(new base::StringValue("aes128"));
+ if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_AES256)
+ values->Append(new base::StringValue("aes256"));
+ if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_3DES)
+ values->Append(new base::StringValue("3des"));
+}
+
+} // namespace
+
+BaseTestServer::SSLOptions::SSLOptions()
+ : server_certificate(CERT_OK),
+ ocsp_status(OCSP_OK),
+ cert_serial(0),
+ request_client_certificate(false),
+ bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
+ record_resume(false),
+ tls_intolerant(TLS_INTOLERANT_NONE) {}
+
+BaseTestServer::SSLOptions::SSLOptions(
+ BaseTestServer::SSLOptions::ServerCertificate cert)
+ : server_certificate(cert),
+ ocsp_status(OCSP_OK),
+ cert_serial(0),
+ request_client_certificate(false),
+ bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
+ record_resume(false),
+ tls_intolerant(TLS_INTOLERANT_NONE) {}
+
+BaseTestServer::SSLOptions::~SSLOptions() {}
+
+base::FilePath BaseTestServer::SSLOptions::GetCertificateFile() const {
+ switch (server_certificate) {
+ case CERT_OK:
+ case CERT_MISMATCHED_NAME:
+ return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
+ case CERT_EXPIRED:
+ return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
+ case CERT_CHAIN_WRONG_ROOT:
+ // This chain uses its own dedicated test root certificate to avoid
+ // side-effects that may affect testing.
+ return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
+ case CERT_AUTO:
+ return base::FilePath();
+ default:
+ NOTREACHED();
+ }
+ return base::FilePath();
+}
+
+std::string BaseTestServer::SSLOptions::GetOCSPArgument() const {
+ if (server_certificate != CERT_AUTO)
+ return std::string();
+
+ switch (ocsp_status) {
+ case OCSP_OK:
+ return "ok";
+ case OCSP_REVOKED:
+ return "revoked";
+ case OCSP_INVALID:
+ return "invalid";
+ case OCSP_UNAUTHORIZED:
+ return "unauthorized";
+ case OCSP_UNKNOWN:
+ return "unknown";
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+const char BaseTestServer::kLocalhost[] = "127.0.0.1";
+
+BaseTestServer::BaseTestServer(Type type, const std::string& host)
+ : type_(type),
+ started_(false),
+ log_to_console_(false) {
+ Init(host);
+}
+
+BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options)
+ : ssl_options_(ssl_options),
+ type_(type),
+ started_(false),
+ log_to_console_(false) {
+ DCHECK(UsingSSL(type));
+ Init(GetHostname(type, ssl_options));
+}
+
+BaseTestServer::~BaseTestServer() {}
+
+const HostPortPair& BaseTestServer::host_port_pair() const {
+ DCHECK(started_);
+ return host_port_pair_;
+}
+
+const base::DictionaryValue& BaseTestServer::server_data() const {
+ DCHECK(started_);
+ DCHECK(server_data_.get());
+ return *server_data_;
+}
+
+std::string BaseTestServer::GetScheme() const {
+ switch (type_) {
+ case TYPE_FTP:
+ return "ftp";
+ case TYPE_HTTP:
+ return "http";
+ case TYPE_HTTPS:
+ return "https";
+ case TYPE_WS:
+ return "ws";
+ case TYPE_WSS:
+ return "wss";
+ case TYPE_TCP_ECHO:
+ case TYPE_UDP_ECHO:
+ default:
+ NOTREACHED();
+ }
+ return std::string();
+}
+
+bool BaseTestServer::GetAddressList(AddressList* address_list) const {
+ DCHECK(address_list);
+
+ scoped_ptr<HostResolver> resolver(HostResolver::CreateDefaultResolver(NULL));
+ HostResolver::RequestInfo info(host_port_pair_);
+ TestCompletionCallback callback;
+ int rv = resolver->Resolve(info, address_list, callback.callback(), NULL,
+ BoundNetLog());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv != net::OK) {
+ LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host();
+ return false;
+ }
+ return true;
+}
+
+uint16 BaseTestServer::GetPort() {
+ return host_port_pair_.port();
+}
+
+void BaseTestServer::SetPort(uint16 port) {
+ host_port_pair_.set_port(port);
+}
+
+GURL BaseTestServer::GetURL(const std::string& path) const {
+ return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path);
+}
+
+GURL BaseTestServer::GetURLWithUser(const std::string& path,
+ const std::string& user) const {
+ return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() +
+ "/" + path);
+}
+
+GURL BaseTestServer::GetURLWithUserAndPassword(const std::string& path,
+ const std::string& user,
+ const std::string& password) const {
+ return GURL(GetScheme() + "://" + user + ":" + password + "@" +
+ host_port_pair_.ToString() + "/" + path);
+}
+
+// static
+bool BaseTestServer::GetFilePathWithReplacements(
+ const std::string& original_file_path,
+ const std::vector<StringPair>& text_to_replace,
+ std::string* replacement_path) {
+ std::string new_file_path = original_file_path;
+ bool first_query_parameter = true;
+ const std::vector<StringPair>::const_iterator end = text_to_replace.end();
+ for (std::vector<StringPair>::const_iterator 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;
+ std::string base64_new;
+ if (!base::Base64Encode(old_text, &base64_old))
+ return false;
+ if (!base::Base64Encode(new_text, &base64_new))
+ return false;
+ if (first_query_parameter) {
+ new_file_path += "?";
+ first_query_parameter = false;
+ } else {
+ new_file_path += "&";
+ }
+ new_file_path += "replace_text=";
+ new_file_path += base64_old;
+ new_file_path += ":";
+ new_file_path += base64_new;
+ }
+
+ *replacement_path = new_file_path;
+ return true;
+}
+
+void BaseTestServer::Init(const std::string& host) {
+ host_port_pair_ = HostPortPair(host, 0);
+
+ // TODO(battre) Remove this after figuring out why the TestServer is flaky.
+ // http://crbug.com/96594
+ log_to_console_ = true;
+}
+
+void BaseTestServer::SetResourcePath(const base::FilePath& document_root,
+ const base::FilePath& certificates_dir) {
+ // This method shouldn't get called twice.
+ DCHECK(certificates_dir_.empty());
+ document_root_ = document_root;
+ certificates_dir_ = certificates_dir;
+ DCHECK(!certificates_dir_.empty());
+}
+
+bool BaseTestServer::ParseServerData(const std::string& server_data) {
+ VLOG(1) << "Server data: " << server_data;
+ base::JSONReader json_reader;
+ scoped_ptr<base::Value> value(json_reader.ReadToValue(server_data));
+ if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) {
+ LOG(ERROR) << "Could not parse server data: "
+ << json_reader.GetErrorMessage();
+ return false;
+ }
+
+ server_data_.reset(static_cast<base::DictionaryValue*>(value.release()));
+ int port = 0;
+ if (!server_data_->GetInteger("port", &port)) {
+ LOG(ERROR) << "Could not find port value";
+ return false;
+ }
+ if ((port <= 0) || (port > kuint16max)) {
+ LOG(ERROR) << "Invalid port value: " << port;
+ return false;
+ }
+ host_port_pair_.set_port(port);
+
+ return true;
+}
+
+bool BaseTestServer::LoadTestRootCert() const {
+ TestRootCerts* root_certs = TestRootCerts::GetInstance();
+ if (!root_certs)
+ return false;
+
+ // Should always use absolute path to load the root certificate.
+ base::FilePath root_certificate_path = certificates_dir_;
+ if (!certificates_dir_.IsAbsolute()) {
+ base::FilePath src_dir;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
+ return false;
+ root_certificate_path = src_dir.Append(certificates_dir_);
+ }
+
+ return root_certs->AddFromFile(
+ root_certificate_path.AppendASCII("root_ca_cert.pem"));
+}
+
+bool BaseTestServer::SetupWhenServerStarted() {
+ DCHECK(host_port_pair_.port());
+
+ if (UsingSSL(type_) && !LoadTestRootCert())
+ return false;
+
+ started_ = true;
+ allowed_port_.reset(new ScopedPortException(host_port_pair_.port()));
+ return true;
+}
+
+void BaseTestServer::CleanUpWhenStoppingServer() {
+ TestRootCerts* root_certs = TestRootCerts::GetInstance();
+ root_certs->Clear();
+
+ host_port_pair_.set_port(0);
+ allowed_port_.reset();
+ started_ = false;
+}
+
+// Generates a dictionary of arguments to pass to the Python test server via
+// the test server spawner, in the form of
+// { argument-name: argument-value, ... }
+// Returns false if an invalid configuration is specified.
+bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const {
+ DCHECK(arguments);
+
+ arguments->SetString("host", host_port_pair_.host());
+ arguments->SetInteger("port", host_port_pair_.port());
+ arguments->SetString("data-dir", document_root_.value());
+
+ if (VLOG_IS_ON(1) || log_to_console_)
+ arguments->Set("log-to-console", base::Value::CreateNullValue());
+
+ if (UsingSSL(type_)) {
+ // Check the certificate arguments of the HTTPS server.
+ base::FilePath certificate_path(certificates_dir_);
+ base::FilePath certificate_file(ssl_options_.GetCertificateFile());
+ if (!certificate_file.value().empty()) {
+ certificate_path = certificate_path.Append(certificate_file);
+ if (certificate_path.IsAbsolute() &&
+ !base::PathExists(certificate_path)) {
+ LOG(ERROR) << "Certificate path " << certificate_path.value()
+ << " doesn't exist. Can't launch https server.";
+ return false;
+ }
+ arguments->SetString("cert-and-key-file", certificate_path.value());
+ }
+
+ // Check the client certificate related arguments.
+ if (ssl_options_.request_client_certificate)
+ arguments->Set("ssl-client-auth", base::Value::CreateNullValue());
+ scoped_ptr<base::ListValue> ssl_client_certs(new base::ListValue());
+
+ std::vector<base::FilePath>::const_iterator it;
+ for (it = ssl_options_.client_authorities.begin();
+ it != ssl_options_.client_authorities.end(); ++it) {
+ if (it->IsAbsolute() && !base::PathExists(*it)) {
+ LOG(ERROR) << "Client authority path " << it->value()
+ << " doesn't exist. Can't launch https server.";
+ return false;
+ }
+ ssl_client_certs->Append(new base::StringValue(it->value()));
+ }
+
+ if (ssl_client_certs->GetSize())
+ arguments->Set("ssl-client-ca", ssl_client_certs.release());
+ }
+
+ if (type_ == TYPE_HTTPS) {
+ arguments->Set("https", base::Value::CreateNullValue());
+
+ std::string ocsp_arg = ssl_options_.GetOCSPArgument();
+ if (!ocsp_arg.empty())
+ arguments->SetString("ocsp", ocsp_arg);
+
+ if (ssl_options_.cert_serial != 0) {
+ arguments->Set("cert-serial",
+ base::Value::CreateIntegerValue(ssl_options_.cert_serial));
+ }
+
+ // Check bulk cipher argument.
+ scoped_ptr<base::ListValue> bulk_cipher_values(new base::ListValue());
+ GetCiphersList(ssl_options_.bulk_ciphers, bulk_cipher_values.get());
+ if (bulk_cipher_values->GetSize())
+ arguments->Set("ssl-bulk-cipher", bulk_cipher_values.release());
+ if (ssl_options_.record_resume)
+ arguments->Set("https-record-resume", base::Value::CreateNullValue());
+ if (ssl_options_.tls_intolerant != SSLOptions::TLS_INTOLERANT_NONE) {
+ arguments->Set("tls-intolerant",
+ new base::FundamentalValue(ssl_options_.tls_intolerant));
+ }
+ }
+
+ return GenerateAdditionalArguments(arguments);
+}
+
+bool BaseTestServer::GenerateAdditionalArguments(
+ base::DictionaryValue* arguments) const {
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/test/spawned_test_server/base_test_server.h b/chromium/net/test/spawned_test_server/base_test_server.h
new file mode 100644
index 00000000000..ff395c56f8b
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/base_test_server.h
@@ -0,0 +1,263 @@
+// Copyright 2013 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_SPAWNED_TEST_SERVER_BASE_TEST_SERVER_H_
+#define NET_TEST_SPAWNED_TEST_SERVER_BASE_TEST_SERVER_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/host_port_pair.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace net {
+
+class AddressList;
+class ScopedPortException;
+
+// The base class of Test server implementation.
+class BaseTestServer {
+ public:
+ typedef std::pair<std::string, std::string> StringPair;
+
+ // Following types represent protocol schemes. See also
+ // http://www.iana.org/assignments/uri-schemes.html
+ enum Type {
+ TYPE_BASIC_AUTH_PROXY,
+ TYPE_FTP,
+ TYPE_HTTP,
+ TYPE_HTTPS,
+ TYPE_WS,
+ TYPE_WSS,
+ TYPE_TCP_ECHO,
+ TYPE_UDP_ECHO,
+ };
+
+ // Container for various options to control how the HTTPS or WSS server is
+ // initialized.
+ struct SSLOptions {
+ enum ServerCertificate {
+ CERT_OK,
+
+ // CERT_AUTO causes the testserver to generate a test certificate issued
+ // by "Testing CA" (see net/data/ssl/certificates/ocsp-test-root.pem).
+ CERT_AUTO,
+
+ CERT_MISMATCHED_NAME,
+ CERT_EXPIRED,
+ // Cross-signed certificate to test PKIX path building. Contains an
+ // intermediate cross-signed by an unknown root, while the client (via
+ // TestRootStore) is expected to have a self-signed version of the
+ // intermediate.
+ CERT_CHAIN_WRONG_ROOT,
+ };
+
+ // OCSPStatus enumerates the types of OCSP response that the testserver
+ // can produce.
+ enum OCSPStatus {
+ OCSP_OK,
+ OCSP_REVOKED,
+ OCSP_INVALID,
+ OCSP_UNAUTHORIZED,
+ OCSP_UNKNOWN,
+ };
+
+ // Bitmask of bulk encryption algorithms that the test server supports
+ // and that can be selectively enabled or disabled.
+ enum BulkCipher {
+ // Special value used to indicate that any algorithm the server supports
+ // is acceptable. Preferred over explicitly OR-ing all ciphers.
+ BULK_CIPHER_ANY = 0,
+
+ BULK_CIPHER_RC4 = (1 << 0),
+ BULK_CIPHER_AES128 = (1 << 1),
+ BULK_CIPHER_AES256 = (1 << 2),
+
+ // NOTE: 3DES support in the Python test server has external
+ // dependencies and not be available on all machines. Clients may not
+ // be able to connect if only 3DES is specified.
+ BULK_CIPHER_3DES = (1 << 3),
+ };
+
+ // NOTE: the values of these enumerators are passed to the the Python test
+ // server. Do not change them.
+ enum TLSIntolerantLevel {
+ TLS_INTOLERANT_NONE = 0,
+ TLS_INTOLERANT_ALL = 1, // Intolerant of all TLS versions.
+ TLS_INTOLERANT_TLS1_1 = 2, // Intolerant of TLS 1.1 or higher.
+ TLS_INTOLERANT_TLS1_2 = 3, // Intolerant of TLS 1.2 or higher.
+ };
+
+ // Initialize a new SSLOptions using CERT_OK as the certificate.
+ SSLOptions();
+
+ // Initialize a new SSLOptions that will use the specified certificate.
+ explicit SSLOptions(ServerCertificate cert);
+ ~SSLOptions();
+
+ // Returns the relative filename of the file that contains the
+ // |server_certificate|.
+ base::FilePath GetCertificateFile() const;
+
+ // GetOCSPArgument returns the value of any OCSP argument to testserver or
+ // the empty string if there is none.
+ std::string GetOCSPArgument() const;
+
+ // The certificate to use when serving requests.
+ ServerCertificate server_certificate;
+
+ // If |server_certificate==CERT_AUTO| then this determines the type of OCSP
+ // response returned.
+ OCSPStatus ocsp_status;
+
+ // If not zero, |cert_serial| will be the serial number of the
+ // auto-generated leaf certificate when |server_certificate==CERT_AUTO|.
+ uint64 cert_serial;
+
+ // True if a CertificateRequest should be sent to the client during
+ // handshaking.
+ bool request_client_certificate;
+
+ // If |request_client_certificate| is true, an optional list of files,
+ // each containing a single, PEM-encoded X.509 certificates. The subject
+ // from each certificate will be added to the certificate_authorities
+ // field of the CertificateRequest.
+ std::vector<base::FilePath> client_authorities;
+
+ // A bitwise-OR of BulkCipher that should be used by the
+ // HTTPS server, or BULK_CIPHER_ANY to indicate that all implemented
+ // ciphers are acceptable.
+ int bulk_ciphers;
+
+ // If true, pass the --https-record-resume argument to testserver.py which
+ // causes it to log session cache actions and echo the log on
+ // /ssl-session-cache.
+ bool record_resume;
+
+ // If not TLS_INTOLERANT_NONE, the server will abort any handshake that
+ // negotiates an intolerant TLS version in order to test version fallback.
+ TLSIntolerantLevel tls_intolerant;
+ };
+
+ // Pass as the 'host' parameter during construction to server on 127.0.0.1
+ static const char kLocalhost[];
+
+ // Initialize a TestServer listening on a specific host (IP or hostname).
+ BaseTestServer(Type type, const std::string& host);
+
+ // Initialize a TestServer with a specific set of SSLOptions for HTTPS or WSS.
+ explicit BaseTestServer(Type type, const SSLOptions& ssl_options);
+
+ // Returns the host port pair used by current Python based test server only
+ // if the server is started.
+ const HostPortPair& host_port_pair() const;
+
+ const base::FilePath& document_root() const { return document_root_; }
+ const base::DictionaryValue& server_data() const;
+ std::string GetScheme() const;
+ bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT;
+
+ GURL GetURL(const std::string& path) const;
+
+ GURL GetURLWithUser(const std::string& path,
+ const std::string& user) const;
+
+ GURL GetURLWithUserAndPassword(const std::string& path,
+ const std::string& user,
+ const std::string& password) const;
+
+ static bool GetFilePathWithReplacements(
+ const std::string& original_path,
+ const std::vector<StringPair>& text_to_replace,
+ std::string* replacement_path);
+
+ static bool UsingSSL(Type type) {
+ return type == BaseTestServer::TYPE_HTTPS ||
+ type == BaseTestServer::TYPE_WSS;
+ }
+
+ protected:
+ virtual ~BaseTestServer();
+ Type type() const { return type_; }
+
+ // Gets port currently assigned to host_port_pair_ without checking
+ // whether it's available (server started) or not.
+ uint16 GetPort();
+
+ // Sets |port| as the actual port used by Python based test server.
+ void SetPort(uint16 port);
+
+ // Set up internal status when the server is started.
+ bool SetupWhenServerStarted() WARN_UNUSED_RESULT;
+
+ // Clean up internal status when starting to stop server.
+ void CleanUpWhenStoppingServer();
+
+ // Set path of test resources.
+ void SetResourcePath(const base::FilePath& document_root,
+ const base::FilePath& certificates_dir);
+
+ // Parses the server data read from the test server. Returns true
+ // on success.
+ bool ParseServerData(const std::string& server_data) WARN_UNUSED_RESULT;
+
+ // Generates a DictionaryValue with the arguments for launching the external
+ // Python test server.
+ bool GenerateArguments(base::DictionaryValue* arguments) const
+ WARN_UNUSED_RESULT;
+
+ // Subclasses can override this to add arguments that are specific to their
+ // own test servers.
+ virtual bool GenerateAdditionalArguments(
+ base::DictionaryValue* arguments) const WARN_UNUSED_RESULT;
+
+ private:
+ void Init(const std::string& host);
+
+ // Marks the root certificate of an HTTPS test server as trusted for
+ // the duration of tests.
+ bool LoadTestRootCert() const WARN_UNUSED_RESULT;
+
+ // Document root of the test server.
+ base::FilePath document_root_;
+
+ // Directory that contains the SSL certificates.
+ base::FilePath certificates_dir_;
+
+ // Address the test server listens on.
+ HostPortPair host_port_pair_;
+
+ // Holds the data sent from the server (e.g., port number).
+ scoped_ptr<base::DictionaryValue> server_data_;
+
+ // If |type_| is TYPE_HTTPS or TYPE_WSS, the TLS settings to use for the test
+ // server.
+ SSLOptions ssl_options_;
+
+ Type type_;
+
+ // Has the server been started?
+ bool started_;
+
+ // Enables logging of the server to the console.
+ bool log_to_console_;
+
+ scoped_ptr<ScopedPortException> allowed_port_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseTestServer);
+};
+
+} // namespace net
+
+#endif // NET_TEST_SPAWNED_TEST_SERVER_BASE_TEST_SERVER_H_
+
diff --git a/chromium/net/test/spawned_test_server/local_test_server.cc b/chromium/net/test/spawned_test_server/local_test_server.cc
new file mode 100644
index 00000000000..bf1b0590786
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/local_test_server.cc
@@ -0,0 +1,252 @@
+// Copyright 2013 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/spawned_test_server/local_test_server.h"
+
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process/kill.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/test/python_utils.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+bool AppendArgumentFromJSONValue(const std::string& key,
+ const base::Value& value_node,
+ CommandLine* command_line) {
+ std::string argument_name = "--" + key;
+ switch (value_node.GetType()) {
+ case base::Value::TYPE_NULL:
+ command_line->AppendArg(argument_name);
+ break;
+ case base::Value::TYPE_INTEGER: {
+ int value;
+ bool result = value_node.GetAsInteger(&value);
+ DCHECK(result);
+ command_line->AppendArg(argument_name + "=" + base::IntToString(value));
+ break;
+ }
+ case Value::TYPE_STRING: {
+ std::string value;
+ bool result = value_node.GetAsString(&value);
+ if (!result || value.empty())
+ return false;
+ command_line->AppendArg(argument_name + "=" + value);
+ break;
+ }
+ case base::Value::TYPE_BOOLEAN:
+ case base::Value::TYPE_DOUBLE:
+ case base::Value::TYPE_LIST:
+ case base::Value::TYPE_DICTIONARY:
+ case base::Value::TYPE_BINARY:
+ default:
+ NOTREACHED() << "improper json type";
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+LocalTestServer::LocalTestServer(Type type,
+ const std::string& host,
+ const base::FilePath& document_root)
+ : BaseTestServer(type, host) {
+ if (!Init(document_root))
+ NOTREACHED();
+}
+
+LocalTestServer::LocalTestServer(Type type,
+ const SSLOptions& ssl_options,
+ const base::FilePath& document_root)
+ : BaseTestServer(type, ssl_options) {
+ if (!Init(document_root))
+ NOTREACHED();
+}
+
+LocalTestServer::~LocalTestServer() {
+ Stop();
+}
+
+bool LocalTestServer::GetTestServerPath(base::FilePath* testserver_path) const {
+ base::FilePath testserver_dir;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_dir)) {
+ LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
+ return false;
+ }
+ testserver_dir = testserver_dir.Append(FILE_PATH_LITERAL("net"))
+ .Append(FILE_PATH_LITERAL("tools"))
+ .Append(FILE_PATH_LITERAL("testserver"));
+ *testserver_path = testserver_dir.Append(FILE_PATH_LITERAL("testserver.py"));
+ return true;
+}
+
+bool LocalTestServer::Start() {
+ return StartInBackground() && BlockUntilStarted();
+}
+
+bool LocalTestServer::StartInBackground() {
+ // Get path to Python server script.
+ base::FilePath testserver_path;
+ if (!GetTestServerPath(&testserver_path))
+ return false;
+
+ if (!SetPythonPath())
+ return false;
+
+ if (!LaunchPython(testserver_path))
+ return false;
+
+ return true;
+}
+
+bool LocalTestServer::BlockUntilStarted() {
+ if (!WaitToStart()) {
+ Stop();
+ return false;
+ }
+
+ return SetupWhenServerStarted();
+}
+
+bool LocalTestServer::Stop() {
+ CleanUpWhenStoppingServer();
+
+ if (!process_handle_)
+ return true;
+
+ // First check if the process has already terminated.
+ bool ret = base::WaitForSingleProcess(process_handle_, base::TimeDelta());
+ if (!ret)
+ ret = base::KillProcess(process_handle_, 1, true);
+
+ if (ret) {
+ base::CloseProcessHandle(process_handle_);
+ process_handle_ = base::kNullProcessHandle;
+ } else {
+ VLOG(1) << "Kill failed?";
+ }
+
+ return ret;
+}
+
+bool LocalTestServer::Init(const base::FilePath& document_root) {
+ if (document_root.IsAbsolute())
+ return false;
+
+ // At this point, the port that the test server will listen on is unknown.
+ // The test server will listen on an ephemeral port, and write the port
+ // number out over a pipe that this TestServer object will read from. Once
+ // that is complete, the host port pair will contain the actual port.
+ DCHECK(!GetPort());
+ process_handle_ = base::kNullProcessHandle;
+
+ base::FilePath src_dir;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
+ return false;
+ SetResourcePath(src_dir.Append(document_root),
+ src_dir.AppendASCII("net")
+ .AppendASCII("data")
+ .AppendASCII("ssl")
+ .AppendASCII("certificates"));
+ return true;
+}
+
+bool LocalTestServer::SetPythonPath() const {
+ base::FilePath third_party_dir;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) {
+ LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
+ return false;
+ }
+ third_party_dir = third_party_dir.AppendASCII("third_party");
+
+ // For simplejson. (simplejson, unlike all the other Python modules
+ // we include, doesn't have an extra 'simplejson' directory, so we
+ // need to include its parent directory, i.e. third_party_dir).
+ AppendToPythonPath(third_party_dir);
+
+ AppendToPythonPath(third_party_dir.AppendASCII("tlslite"));
+ AppendToPythonPath(
+ third_party_dir.AppendASCII("pyftpdlib").AppendASCII("src"));
+ AppendToPythonPath(
+ third_party_dir.AppendASCII("pywebsocket").AppendASCII("src"));
+
+ // Locate the Python code generated by the protocol buffers compiler.
+ base::FilePath pyproto_dir;
+ if (!GetPyProtoPath(&pyproto_dir)) {
+ LOG(WARNING) << "Cannot find pyproto dir for generated code. "
+ << "Testserver features that rely on it will not work";
+ return true;
+ }
+ AppendToPythonPath(pyproto_dir);
+
+ return true;
+}
+
+bool LocalTestServer::AddCommandLineArguments(CommandLine* command_line) const {
+ base::DictionaryValue arguments_dict;
+ if (!GenerateArguments(&arguments_dict))
+ return false;
+
+ // Serialize the argument dictionary into CommandLine.
+ for (base::DictionaryValue::Iterator it(arguments_dict); !it.IsAtEnd();
+ it.Advance()) {
+ const base::Value& value = it.value();
+ const std::string& key = it.key();
+
+ // Add arguments from a list.
+ if (value.IsType(Value::TYPE_LIST)) {
+ 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) {
+ if (!AppendArgumentFromJSONValue(key, *(*list_it), command_line))
+ return false;
+ }
+ } else if (!AppendArgumentFromJSONValue(key, value, command_line)) {
+ return false;
+ }
+ }
+
+ // Append the appropriate server type argument.
+ switch (type()) {
+ case TYPE_HTTP: // The default type is HTTP, no argument required.
+ break;
+ case TYPE_HTTPS:
+ command_line->AppendArg("--https");
+ break;
+ case TYPE_WS:
+ case TYPE_WSS:
+ command_line->AppendArg("--websocket");
+ break;
+ case TYPE_FTP:
+ command_line->AppendArg("--ftp");
+ break;
+ case TYPE_TCP_ECHO:
+ command_line->AppendArg("--tcp-echo");
+ break;
+ case TYPE_UDP_ECHO:
+ command_line->AppendArg("--udp-echo");
+ break;
+ case TYPE_BASIC_AUTH_PROXY:
+ command_line->AppendArg("--basic-auth-proxy");
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/test/spawned_test_server/local_test_server.h b/chromium/net/test/spawned_test_server/local_test_server.h
new file mode 100644
index 00000000000..cfd2eb3baee
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/local_test_server.h
@@ -0,0 +1,117 @@
+// Copyright 2013 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_SPAWNED_TEST_SERVER_LOCAL_TEST_SERVER_H_
+#define NET_TEST_SPAWNED_TEST_SERVER_LOCAL_TEST_SERVER_H_
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/process/process_handle.h"
+#include "net/test/spawned_test_server/base_test_server.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+class CommandLine;
+
+namespace net {
+
+// The LocalTestServer runs an external Python-based test server in the
+// same machine in which the LocalTestServer runs.
+class LocalTestServer : public BaseTestServer {
+ public:
+ // Initialize a TestServer listening on a specific host (IP or hostname).
+ // |document_root| must be a relative path under the root tree.
+ LocalTestServer(Type type,
+ const std::string& host,
+ const base::FilePath& document_root);
+
+ // Initialize a TestServer with a specific set of SSLOptions.
+ // |document_root| must be a relative path under the root tree.
+ LocalTestServer(Type type,
+ const SSLOptions& ssl_options,
+ const base::FilePath& document_root);
+
+ virtual ~LocalTestServer();
+
+ // Start the test server and block until it's ready. Returns true on success.
+ bool Start() WARN_UNUSED_RESULT;
+
+ // Start the test server without blocking. Use this if you need multiple test
+ // servers (such as WebSockets and HTTP, or HTTP and HTTPS). You must call
+ // BlockUntilStarted on all servers your test requires before executing the
+ // test. For example:
+ //
+ // // Start the servers in parallel.
+ // ASSERT_TRUE(http_server.StartInBackground());
+ // ASSERT_TRUE(websocket_server.StartInBackground());
+ // // Wait for both servers to be ready.
+ // ASSERT_TRUE(http_server.BlockUntilStarted());
+ // ASSERT_TRUE(websocket_server.BlockUntilStarted());
+ // RunMyTest();
+ //
+ // Returns true on success.
+ bool StartInBackground() WARN_UNUSED_RESULT;
+
+ // Block until ths test server is ready. Returns true on success. See
+ // StartInBackground() documentation for more information.
+ bool BlockUntilStarted() WARN_UNUSED_RESULT;
+
+ // Stop the server started by Start().
+ bool Stop();
+
+ // Modify PYTHONPATH to contain libraries we need.
+ virtual bool SetPythonPath() const WARN_UNUSED_RESULT;
+
+ // Returns true if the base::FilePath for the testserver python script is
+ // successfully stored in |*testserver_path|.
+ virtual bool GetTestServerPath(base::FilePath* testserver_path) const
+ WARN_UNUSED_RESULT;
+
+ // Adds the command line arguments for the Python test server to
+ // |command_line|. Returns true on success.
+ virtual bool AddCommandLineArguments(CommandLine* command_line) const
+ WARN_UNUSED_RESULT;
+
+ // Returns the actual path of document root for test cases. This function
+ // should be called by test cases to retrieve the actual document root path.
+ base::FilePath GetDocumentRoot() const { return document_root(); };
+
+ private:
+ bool Init(const base::FilePath& document_root);
+
+ // Launches the Python test server. Returns true on success.
+ bool LaunchPython(const base::FilePath& testserver_path) WARN_UNUSED_RESULT;
+
+ // Waits for the server to start. Returns true on success.
+ bool WaitToStart() WARN_UNUSED_RESULT;
+
+ // Handle of the Python process running the test server.
+ base::ProcessHandle process_handle_;
+
+#if defined(OS_WIN)
+ // JobObject used to clean up orphaned child processes.
+ base::win::ScopedHandle job_handle_;
+
+ // The pipe file handle we read from.
+ base::win::ScopedHandle child_read_fd_;
+
+ // The pipe file handle the child and we write to.
+ base::win::ScopedHandle child_write_fd_;
+#endif
+
+#if defined(OS_POSIX)
+ // The file descriptor the child writes to when it starts.
+ int child_fd_;
+ file_util::ScopedFD child_fd_closer_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(LocalTestServer);
+};
+
+} // namespace net
+
+#endif // NET_TEST_SPAWNED_TEST_SERVER_LOCAL_TEST_SERVER_H_
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
new file mode 100644
index 00000000000..10c2d0f9932
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/local_test_server_posix.cc
@@ -0,0 +1,179 @@
+// Copyright (c) 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.
+
+#include "net/test/spawned_test_server/local_test_server.h"
+
+#include <poll.h>
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/process/process_iterator.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/test/test_timeouts.h"
+#include "net/test/python_utils.h"
+
+namespace {
+
+// Helper class used to detect and kill orphaned python test server processes.
+// Checks if the command line of a process contains |path_string| (the path
+// from which the test server was launched) and |port_string| (the port used by
+// the test server), and if the parent pid of the process is 1 (indicating that
+// it is an orphaned process).
+class OrphanedTestServerFilter : public base::ProcessFilter {
+ public:
+ OrphanedTestServerFilter(
+ const std::string& path_string, const std::string& port_string)
+ : path_string_(path_string),
+ port_string_(port_string) {}
+
+ virtual bool Includes(const base::ProcessEntry& entry) const OVERRIDE {
+ if (entry.parent_pid() != 1)
+ return false;
+ bool found_path_string = false;
+ bool found_port_string = false;
+ for (std::vector<std::string>::const_iterator 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)
+ found_port_string = true;
+ }
+ return found_path_string && found_port_string;
+ }
+
+ private:
+ std::string path_string_;
+ std::string port_string_;
+ DISALLOW_COPY_AND_ASSIGN(OrphanedTestServerFilter);
+};
+
+// Given a file descriptor, reads into |buffer| until |bytes_max|
+// bytes has been read or an error has been encountered. Returns true
+// if the read was successful. |remaining_time| is used as a timeout.
+bool ReadData(int fd, ssize_t bytes_max, uint8* buffer,
+ base::TimeDelta* remaining_time) {
+ ssize_t bytes_read = 0;
+ base::TimeTicks previous_time = base::TimeTicks::Now();
+ while (bytes_read < bytes_max) {
+ struct pollfd poll_fds[1];
+
+ poll_fds[0].fd = fd;
+ poll_fds[0].events = POLLIN | POLLPRI;
+ poll_fds[0].revents = 0;
+
+ int rv = HANDLE_EINTR(poll(poll_fds, 1,
+ remaining_time->InMilliseconds()));
+ if (rv == 0) {
+ LOG(ERROR) << "poll() timed out; bytes_read=" << bytes_read;
+ return false;
+ } else if (rv < 0) {
+ PLOG(ERROR) << "poll() failed for child file descriptor; bytes_read="
+ << bytes_read;
+ return false;
+ }
+
+ base::TimeTicks current_time = base::TimeTicks::Now();
+ base::TimeDelta elapsed_time_cycle = current_time - previous_time;
+ DCHECK_GE(elapsed_time_cycle.InMilliseconds(), 0);
+ *remaining_time -= elapsed_time_cycle;
+ previous_time = current_time;
+
+ ssize_t num_bytes = HANDLE_EINTR(read(fd, buffer + bytes_read,
+ bytes_max - bytes_read));
+ if (num_bytes <= 0)
+ return false;
+ bytes_read += num_bytes;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+bool LocalTestServer::LaunchPython(const base::FilePath& testserver_path) {
+ // Log is useful in the event you want to run a nearby script (e.g. a test) in
+ // the same environment as the TestServer.
+ VLOG(1) << "LaunchPython called with PYTHONPATH = " << getenv(kPythonPathEnv);
+
+ CommandLine python_command(CommandLine::NO_PROGRAM);
+ if (!GetPythonCommand(&python_command))
+ return false;
+
+ python_command.AppendArgPath(testserver_path);
+ if (!AddCommandLineArguments(&python_command))
+ return false;
+
+ int pipefd[2];
+ if (pipe(pipefd) != 0) {
+ PLOG(ERROR) << "Could not create pipe.";
+ return false;
+ }
+
+ // Save the read half. The write half is sent to the child.
+ child_fd_ = pipefd[0];
+ child_fd_closer_.reset(&child_fd_);
+ file_util::ScopedFD write_closer(&pipefd[1]);
+ base::FileHandleMappingVector map_write_fd;
+ map_write_fd.push_back(std::make_pair(pipefd[1], pipefd[1]));
+
+ python_command.AppendArg("--startup-pipe=" + base::IntToString(pipefd[1]));
+
+ // Try to kill any orphaned testserver processes that may be running.
+ OrphanedTestServerFilter filter(testserver_path.value(),
+ base::IntToString(GetPort()));
+ if (!base::KillProcesses("python", -1, &filter)) {
+ LOG(WARNING) << "Failed to clean up older orphaned testserver instances.";
+ }
+
+ // Launch a new testserver process.
+ base::LaunchOptions options;
+
+ options.fds_to_remap = &map_write_fd;
+ if (!base::LaunchProcess(python_command, options, &process_handle_)) {
+ LOG(ERROR) << "Failed to launch " << python_command.GetCommandLineString();
+ return false;
+ }
+
+ return true;
+}
+
+bool LocalTestServer::WaitToStart() {
+ file_util::ScopedFD child_fd_closer(child_fd_closer_.release());
+
+ base::TimeDelta remaining_time = TestTimeouts::action_timeout();
+
+ uint32 server_data_len = 0;
+ if (!ReadData(child_fd_, sizeof(server_data_len),
+ reinterpret_cast<uint8*>(&server_data_len),
+ &remaining_time)) {
+ LOG(ERROR) << "Could not read server_data_len";
+ return false;
+ }
+ std::string server_data(server_data_len, '\0');
+ if (!ReadData(child_fd_, server_data_len,
+ reinterpret_cast<uint8*>(&server_data[0]),
+ &remaining_time)) {
+ LOG(ERROR) << "Could not read server_data (" << server_data_len
+ << " bytes)";
+ return false;
+ }
+
+ if (!ParseServerData(server_data)) {
+ LOG(ERROR) << "Could not parse server_data: " << server_data;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/test/spawned_test_server/local_test_server_win.cc b/chromium/net/test/spawned_test_server/local_test_server_win.cc
new file mode 100644
index 00000000000..fd26483ed47
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/local_test_server_win.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#include "net/test/spawned_test_server/local_test_server.h"
+
+#include <windows.h>
+#include <wincrypt.h>
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread.h"
+#include "base/win/scoped_handle.h"
+#include "net/test/python_utils.h"
+
+#pragma comment(lib, "crypt32.lib")
+
+namespace {
+
+// Writes |size| bytes to |handle| and sets |*unblocked| to true.
+// Used as a crude timeout mechanism by ReadData().
+void UnblockPipe(HANDLE handle, DWORD size, bool* unblocked) {
+ std::string unblock_data(size, '\0');
+ // Unblock the ReadFile in LocalTestServer::WaitToStart by writing to the
+ // pipe. Make sure the call succeeded, otherwise we are very likely to hang.
+ DWORD bytes_written = 0;
+ LOG(WARNING) << "Timeout reached; unblocking pipe by writing "
+ << size << " bytes";
+ CHECK(WriteFile(handle, unblock_data.data(), size, &bytes_written,
+ NULL));
+ CHECK_EQ(size, bytes_written);
+ *unblocked = true;
+}
+
+// Given a file handle, reads into |buffer| until |bytes_max| bytes
+// has been read or an error has been encountered. Returns
+// true if the read was successful.
+bool ReadData(HANDLE read_fd, HANDLE write_fd,
+ DWORD bytes_max, uint8* buffer) {
+ base::Thread thread("test_server_watcher");
+ if (!thread.Start())
+ return false;
+
+ // Prepare a timeout in case the server fails to start.
+ bool unblocked = false;
+ thread.message_loop()->PostDelayedTask(
+ FROM_HERE, base::Bind(UnblockPipe, write_fd, bytes_max, &unblocked),
+ TestTimeouts::action_max_timeout());
+
+ DWORD bytes_read = 0;
+ while (bytes_read < bytes_max) {
+ DWORD num_bytes;
+ if (!ReadFile(read_fd, buffer + bytes_read, bytes_max - bytes_read,
+ &num_bytes, NULL)) {
+ PLOG(ERROR) << "ReadFile failed";
+ return false;
+ }
+ if (num_bytes <= 0) {
+ LOG(ERROR) << "ReadFile returned invalid byte count: " << num_bytes;
+ return false;
+ }
+ bytes_read += num_bytes;
+ }
+
+ thread.Stop();
+ // If the timeout kicked in, abort.
+ if (unblocked) {
+ LOG(ERROR) << "Timeout exceeded for ReadData";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+namespace net {
+
+bool LocalTestServer::LaunchPython(const base::FilePath& testserver_path) {
+ CommandLine python_command(CommandLine::NO_PROGRAM);
+ if (!GetPythonCommand(&python_command))
+ return false;
+
+ python_command.AppendArgPath(testserver_path);
+ if (!AddCommandLineArguments(&python_command))
+ return false;
+
+ HANDLE child_read = NULL;
+ HANDLE child_write = NULL;
+ if (!CreatePipe(&child_read, &child_write, NULL, 0)) {
+ PLOG(ERROR) << "Failed to create pipe";
+ return false;
+ }
+ child_read_fd_.Set(child_read);
+ child_write_fd_.Set(child_write);
+
+ // Have the child inherit the write half.
+ if (!SetHandleInformation(child_write, HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT)) {
+ PLOG(ERROR) << "Failed to enable pipe inheritance";
+ return false;
+ }
+
+ // Pass the handle on the command-line. Although HANDLE is a
+ // pointer, truncating it on 64-bit machines is okay. See
+ // http://msdn.microsoft.com/en-us/library/aa384203.aspx
+ //
+ // "64-bit versions of Windows use 32-bit handles for
+ // interoperability. When sharing a handle between 32-bit and 64-bit
+ // applications, only the lower 32 bits are significant, so it is
+ // safe to truncate the handle (when passing it from 64-bit to
+ // 32-bit) or sign-extend the handle (when passing it from 32-bit to
+ // 64-bit)."
+ python_command.AppendArg("--startup-pipe=" +
+ base::IntToString(reinterpret_cast<uintptr_t>(child_write)));
+
+ job_handle_.Set(CreateJobObject(NULL, NULL));
+ if (!job_handle_.IsValid()) {
+ LOG(ERROR) << "Could not create JobObject.";
+ return false;
+ }
+
+ if (!base::SetJobObjectAsKillOnJobClose(job_handle_.Get())) {
+ LOG(ERROR) << "Could not SetInformationJobObject.";
+ return false;
+ }
+
+ base::LaunchOptions launch_options;
+ launch_options.inherit_handles = true;
+ launch_options.job_handle = job_handle_.Get();
+ if (!base::LaunchProcess(python_command, launch_options, &process_handle_)) {
+ LOG(ERROR) << "Failed to launch " << python_command.GetCommandLineString();
+ return false;
+ }
+
+ return true;
+}
+
+bool LocalTestServer::WaitToStart() {
+ base::win::ScopedHandle read_fd(child_read_fd_.Take());
+ base::win::ScopedHandle write_fd(child_write_fd_.Take());
+
+ uint32 server_data_len = 0;
+ if (!ReadData(read_fd.Get(), write_fd.Get(), sizeof(server_data_len),
+ reinterpret_cast<uint8*>(&server_data_len))) {
+ LOG(ERROR) << "Could not read server_data_len";
+ return false;
+ }
+ std::string server_data(server_data_len, '\0');
+ if (!ReadData(read_fd.Get(), write_fd.Get(), server_data_len,
+ reinterpret_cast<uint8*>(&server_data[0]))) {
+ LOG(ERROR) << "Could not read server_data (" << server_data_len
+ << " bytes)";
+ return false;
+ }
+
+ if (!ParseServerData(server_data)) {
+ LOG(ERROR) << "Could not parse server_data: " << server_data;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace net
+
diff --git a/chromium/net/test/spawned_test_server/remote_test_server.cc b/chromium/net/test/spawned_test_server/remote_test_server.cc
new file mode 100644
index 00000000000..a3b4ef3fdf5
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/remote_test_server.cc
@@ -0,0 +1,204 @@
+// Copyright 2013 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/spawned_test_server/remote_test_server.h"
+
+#include <vector>
+
+#include "base/base_paths.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/test/spawned_test_server/spawner_communicator.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+// To reduce the running time of tests, tests may be sharded across several
+// devices. This means that it may be necessary to support multiple instances
+// of the test server spawner and the Python test server simultaneously on the
+// same host. Each pair of (test server spawner, Python test server) correspond
+// to a single testing device.
+// The mapping between the test server spawner and the individual Python test
+// servers is written to a file on the device prior to executing any tests.
+base::FilePath GetTestServerPortInfoFile() {
+#if !defined(OS_ANDROID)
+ return base::FilePath("/tmp/net-test-server-ports");
+#else
+ base::FilePath test_data_dir;
+ PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &test_data_dir);
+ return test_data_dir.Append("net-test-server-ports");
+#endif
+}
+
+// Please keep it sync with dictionary SERVER_TYPES in testserver.py
+std::string GetServerTypeString(BaseTestServer::Type type) {
+ switch (type) {
+ case BaseTestServer::TYPE_FTP:
+ return "ftp";
+ case BaseTestServer::TYPE_HTTP:
+ case BaseTestServer::TYPE_HTTPS:
+ return "http";
+ case BaseTestServer::TYPE_WS:
+ case BaseTestServer::TYPE_WSS:
+ return "ws";
+ case BaseTestServer::TYPE_TCP_ECHO:
+ return "tcpecho";
+ case BaseTestServer::TYPE_UDP_ECHO:
+ return "udpecho";
+ default:
+ NOTREACHED();
+ }
+ return std::string();
+}
+
+} // namespace
+
+RemoteTestServer::RemoteTestServer(Type type,
+ const std::string& host,
+ const base::FilePath& document_root)
+ : BaseTestServer(type, host),
+ spawner_server_port_(0) {
+ if (!Init(document_root))
+ NOTREACHED();
+}
+
+RemoteTestServer::RemoteTestServer(Type type,
+ const SSLOptions& ssl_options,
+ const base::FilePath& document_root)
+ : BaseTestServer(type, ssl_options),
+ spawner_server_port_(0) {
+ if (!Init(document_root))
+ NOTREACHED();
+}
+
+RemoteTestServer::~RemoteTestServer() {
+ Stop();
+}
+
+bool RemoteTestServer::Start() {
+ if (spawner_communicator_.get())
+ return true;
+ spawner_communicator_.reset(new SpawnerCommunicator(spawner_server_port_));
+
+ base::DictionaryValue arguments_dict;
+ if (!GenerateArguments(&arguments_dict))
+ return false;
+
+ // Append the 'server-type' argument which is used by spawner server to
+ // pass right server type to Python test server.
+ arguments_dict.SetString("server-type", GetServerTypeString(type()));
+
+ // Generate JSON-formatted argument string.
+ std::string arguments_string;
+ base::JSONWriter::Write(&arguments_dict, &arguments_string);
+ if (arguments_string.empty())
+ return false;
+
+ // Start the Python test server on the remote machine.
+ uint16 test_server_port;
+ if (!spawner_communicator_->StartServer(arguments_string,
+ &test_server_port)) {
+ return false;
+ }
+ if (0 == test_server_port)
+ return false;
+
+ // Construct server data to initialize BaseTestServer::server_data_.
+ base::DictionaryValue server_data_dict;
+ // At this point, the test server should be spawned on the host. Update the
+ // local port to real port of Python test server, which will be forwarded to
+ // the remote server.
+ server_data_dict.SetInteger("port", test_server_port);
+ std::string server_data;
+ base::JSONWriter::Write(&server_data_dict, &server_data);
+ if (server_data.empty() || !ParseServerData(server_data)) {
+ LOG(ERROR) << "Could not parse server_data: " << server_data;
+ return false;
+ }
+
+ return SetupWhenServerStarted();
+}
+
+bool RemoteTestServer::StartInBackground() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RemoteTestServer::BlockUntilStarted() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool RemoteTestServer::Stop() {
+ if (!spawner_communicator_.get())
+ return true;
+ CleanUpWhenStoppingServer();
+ bool stopped = spawner_communicator_->StopServer();
+ // Explicitly reset |spawner_communicator_| to avoid reusing the stopped one.
+ spawner_communicator_.reset(NULL);
+ return stopped;
+}
+
+// On Android, the document root in the device is not the same as the document
+// root in the host machine where the test server is launched. So prepend
+// DIR_SOURCE_ROOT here to get the actual path of document root on the Android
+// device.
+base::FilePath RemoteTestServer::GetDocumentRoot() const {
+ base::FilePath src_dir;
+ PathService::Get(base::DIR_SOURCE_ROOT, &src_dir);
+ return src_dir.Append(document_root());
+}
+
+bool RemoteTestServer::Init(const base::FilePath& document_root) {
+ if (document_root.IsAbsolute())
+ return false;
+
+ // Gets ports information used by test server spawner and Python test server.
+ int test_server_port = 0;
+
+ // Parse file to extract the ports information.
+ std::string port_info;
+ if (!file_util::ReadFileToString(GetTestServerPortInfoFile(),
+ &port_info) ||
+ port_info.empty()) {
+ return false;
+ }
+
+ std::vector<std::string> ports;
+ base::SplitString(port_info, ':', &ports);
+ if (ports.size() != 2u)
+ return false;
+
+ // Verify the ports information.
+ base::StringToInt(ports[0], &spawner_server_port_);
+ if (!spawner_server_port_ ||
+ static_cast<uint32>(spawner_server_port_) >= kuint16max)
+ return false;
+
+ // Allow the test_server_port to be 0, which means the test server spawner
+ // will pick up a random port to run the test server.
+ base::StringToInt(ports[1], &test_server_port);
+ if (static_cast<uint32>(test_server_port) >= kuint16max)
+ return false;
+ SetPort(test_server_port);
+
+ SetResourcePath(document_root, base::FilePath().AppendASCII("net")
+ .AppendASCII("data")
+ .AppendASCII("ssl")
+ .AppendASCII("certificates"));
+ return true;
+}
+
+} // namespace net
+
diff --git a/chromium/net/test/spawned_test_server/remote_test_server.h b/chromium/net/test/spawned_test_server/remote_test_server.h
new file mode 100644
index 00000000000..de12e4ebe44
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/remote_test_server.h
@@ -0,0 +1,71 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_SPAWNED_TEST_SERVER_REMOTE_TEST_SERVER_H_
+#define NET_TEST_SPAWNED_TEST_SERVER_REMOTE_TEST_SERVER_H_
+
+#include <string>
+
+#include "net/test/spawned_test_server/base_test_server.h"
+
+namespace net {
+
+class SpawnerCommunicator;
+
+// The RemoteTestServer runs an external Python-based test server in another
+// machine that is different from the machine in which RemoteTestServer runs.
+class RemoteTestServer : public BaseTestServer {
+ public:
+ // Initialize a TestServer listening on a specific host (IP or hostname).
+ // |document_root| must be a relative path under the root tree.
+ RemoteTestServer(Type type,
+ const std::string& host,
+ const base::FilePath& document_root);
+
+ // Initialize a TestServer with a specific set of SSLOptions.
+ // |document_root| must be a relative path under the root tree.
+ RemoteTestServer(Type type,
+ const SSLOptions& ssl_options,
+ const base::FilePath& document_root);
+
+ virtual ~RemoteTestServer();
+
+ // Starts the Python test server on the host, instead of on the device, and
+ // blocks until the server is ready.
+ bool Start() WARN_UNUSED_RESULT;
+
+ // These are currently unused and unimplemented for RemoteTestServer. See
+ // the same methods in LocalTestServer for more information.
+ bool StartInBackground() WARN_UNUSED_RESULT;
+ bool BlockUntilStarted() WARN_UNUSED_RESULT;
+
+ // Stops the Python test server that is running on the host machine.
+ bool Stop();
+
+ // Returns the actual path of document root for the test cases. This function
+ // should be called by test cases to retrieve the actual document root path
+ // on the Android device, otherwise document_root() function is used to get
+ // the document root.
+ base::FilePath GetDocumentRoot() const;
+
+ private:
+ bool Init(const base::FilePath& document_root);
+
+ // The local port used to communicate with the TestServer spawner. This is
+ // used to control the startup and shutdown of the Python TestServer running
+ // on the remote machine. On Android, this port will be redirected to the
+ // same port on the host machine.
+ int spawner_server_port_;
+
+ // Helper to start and stop instances of the Python test server that runs on
+ // the host machine.
+ scoped_ptr<SpawnerCommunicator> spawner_communicator_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoteTestServer);
+};
+
+} // namespace net
+
+#endif // NET_TEST_SPAWNED_TEST_SERVER_REMOTE_TEST_SERVER_H_
+
diff --git a/chromium/net/test/spawned_test_server/spawned_test_server.h b/chromium/net/test/spawned_test_server/spawned_test_server.h
new file mode 100644
index 00000000000..9bf5831d503
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/spawned_test_server.h
@@ -0,0 +1,27 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_SPAWNED_TEST_SERVER_SPAWNED_TEST_SERVER_H_
+#define NET_TEST_SPAWNED_TEST_SERVER_SPAWNED_TEST_SERVER_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include "net/test/spawned_test_server/remote_test_server.h"
+#else
+#include "net/test/spawned_test_server/local_test_server.h"
+#endif
+
+namespace net {
+
+#if defined(OS_ANDROID)
+typedef RemoteTestServer SpawnedTestServer;
+#else
+typedef LocalTestServer SpawnedTestServer;
+#endif
+
+} // namespace net
+
+#endif // NET_TEST_SPAWNED_TEST_SERVER_SPAWNED_TEST_SERVER_H_
+
diff --git a/chromium/net/test/spawned_test_server/spawner_communicator.cc b/chromium/net/test/spawned_test_server/spawner_communicator.cc
new file mode 100644
index 00000000000..f93ff971d6f
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/spawner_communicator.cc
@@ -0,0 +1,379 @@
+// Copyright (c) 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.
+
+#include "net/test/spawned_test_server/spawner_communicator.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/supports_user_data.h"
+#include "base/test/test_timeouts.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request_test_util.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+GURL GenerateSpawnerCommandURL(const std::string& command, uint16 port) {
+ // Always performs HTTP request for sending command to the spawner server.
+ return GURL(base::StringPrintf("%s:%u/%s", "http://127.0.0.1", port,
+ command.c_str()));
+}
+
+int kBufferSize = 2048;
+
+// A class to hold all data needed to send a command to spawner server.
+class SpawnerRequestData : public base::SupportsUserData::Data {
+ public:
+ SpawnerRequestData(int id, int* result_code, std::string* data_received)
+ : request_id_(id),
+ buf_(new IOBuffer(kBufferSize)),
+ result_code_(result_code),
+ data_received_(data_received),
+ response_started_count_(0) {
+ DCHECK(result_code);
+ *result_code_ = OK;
+ DCHECK(data_received);
+ data_received_->clear();
+ }
+
+ virtual ~SpawnerRequestData() {}
+
+ bool DoesRequestIdMatch(int request_id) const {
+ return request_id_ == request_id;
+ }
+
+ IOBuffer* buf() const { return buf_.get(); }
+
+ bool IsResultOK() const { return *result_code_ == OK; }
+
+ void ClearReceivedData() { data_received_->clear(); }
+
+ void SetResultCode(int result_code) { *result_code_ = result_code; }
+
+ void IncreaseResponseStartedCount() { response_started_count_++; }
+
+ int response_started_count() const { return response_started_count_; }
+
+ // Write data read from URLRequest::Read() to |data_received_|. Returns true
+ // if |num_bytes| is great than 0. |num_bytes| is 0 for EOF, < 0 on errors.
+ bool ConsumeBytesRead(int num_bytes) {
+ // Error while reading, or EOF.
+ if (num_bytes <= 0)
+ return false;
+
+ data_received_->append(buf_->data(), num_bytes);
+ return true;
+ }
+
+ private:
+ // Unique ID for the current request.
+ int request_id_;
+
+ // Buffer that URLRequest writes into.
+ scoped_refptr<IOBuffer> buf_;
+
+ // Holds the error condition that was hit on the current request, or OK.
+ int* result_code_;
+
+ // Data received from server;
+ std::string* data_received_;
+
+ // Used to track how many times the OnResponseStarted get called after
+ // sending a command to spawner server.
+ int response_started_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpawnerRequestData);
+};
+
+} // namespace
+
+SpawnerCommunicator::SpawnerCommunicator(uint16 port)
+ : io_thread_("spawner_communicator"),
+ event_(false, false),
+ port_(port),
+ next_id_(0),
+ weak_factory_(this),
+ is_running_(false) {}
+
+SpawnerCommunicator::~SpawnerCommunicator() {
+ DCHECK(!is_running_);
+}
+
+void SpawnerCommunicator::WaitForResponse() {
+ DCHECK_NE(base::MessageLoop::current(), io_thread_.message_loop());
+ event_.Wait();
+ event_.Reset();
+}
+
+void SpawnerCommunicator::StartIOThread() {
+ DCHECK_NE(base::MessageLoop::current(), io_thread_.message_loop());
+ if (is_running_)
+ return;
+
+ allowed_port_.reset(new ScopedPortException(port_));
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ is_running_ = io_thread_.StartWithOptions(options);
+ DCHECK(is_running_);
+}
+
+void SpawnerCommunicator::Shutdown() {
+ DCHECK_NE(base::MessageLoop::current(), io_thread_.message_loop());
+ DCHECK(is_running_);
+ // The request and its context should be created and destroyed only on the
+ // IO thread.
+ DCHECK(!cur_request_.get());
+ DCHECK(!context_.get());
+ is_running_ = false;
+ io_thread_.Stop();
+ allowed_port_.reset();
+}
+
+void SpawnerCommunicator::SendCommandAndWaitForResult(
+ const std::string& command,
+ const std::string& post_data,
+ int* result_code,
+ std::string* data_received) {
+ if (!result_code || !data_received)
+ return;
+ // Start the communicator thread to talk to test server spawner.
+ StartIOThread();
+ DCHECK(io_thread_.message_loop());
+
+ // Since the method will be blocked until SpawnerCommunicator gets result
+ // from the spawner server or timed-out. It's safe to use base::Unretained
+ // when using base::Bind.
+ io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(
+ &SpawnerCommunicator::SendCommandAndWaitForResultOnIOThread,
+ base::Unretained(this), command, post_data, result_code, data_received));
+ WaitForResponse();
+}
+
+void SpawnerCommunicator::SendCommandAndWaitForResultOnIOThread(
+ const std::string& command,
+ const std::string& post_data,
+ int* result_code,
+ std::string* data_received) {
+ base::MessageLoop* loop = io_thread_.message_loop();
+ DCHECK(loop);
+ DCHECK_EQ(base::MessageLoop::current(), loop);
+
+ // Prepare the URLRequest for sending the command.
+ DCHECK(!cur_request_.get());
+ context_.reset(new TestURLRequestContext);
+ cur_request_.reset(context_->CreateRequest(
+ GenerateSpawnerCommandURL(command, port_), this));
+ DCHECK(cur_request_.get());
+ int current_request_id = ++next_id_;
+ SpawnerRequestData* data = new SpawnerRequestData(current_request_id,
+ result_code,
+ data_received);
+ DCHECK(data);
+ cur_request_->SetUserData(this, data);
+
+ if (post_data.empty()) {
+ cur_request_->set_method("GET");
+ } else {
+ cur_request_->set_method("POST");
+ scoped_ptr<UploadElementReader> reader(
+ UploadOwnedBytesElementReader::CreateWithString(post_data));
+ cur_request_->set_upload(make_scoped_ptr(
+ UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kContentType,
+ "application/json");
+ cur_request_->SetExtraRequestHeaders(headers);
+ }
+
+ // Post a task to timeout this request if it takes too long.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SpawnerCommunicator::OnTimeout,
+ weak_factory_.GetWeakPtr(),
+ current_request_id),
+ TestTimeouts::action_max_timeout());
+
+ // Start the request.
+ cur_request_->Start();
+}
+
+void SpawnerCommunicator::OnTimeout(int id) {
+ // Timeout tasks may outlive the URLRequest they reference. Make sure it
+ // is still applicable.
+ if (!cur_request_.get())
+ return;
+ SpawnerRequestData* data =
+ static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this));
+ DCHECK(data);
+
+ if (!data->DoesRequestIdMatch(id))
+ return;
+ // Set the result code and cancel the timed-out task.
+ data->SetResultCode(ERR_TIMED_OUT);
+ cur_request_->Cancel();
+ OnSpawnerCommandCompleted(cur_request_.get());
+}
+
+void SpawnerCommunicator::OnSpawnerCommandCompleted(URLRequest* request) {
+ if (!cur_request_.get())
+ return;
+ DCHECK_EQ(request, cur_request_.get());
+ SpawnerRequestData* data =
+ static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this));
+ DCHECK(data);
+
+ // If request is faild,return the error code.
+ if (!cur_request_->status().is_success())
+ data->SetResultCode(cur_request_->status().error());
+
+ if (!data->IsResultOK()) {
+ LOG(ERROR) << "request failed, status: "
+ << static_cast<int>(request->status().status())
+ << ", error: " << request->status().error();
+ // Clear the buffer of received data if any net error happened.
+ data->ClearReceivedData();
+ } else {
+ DCHECK_EQ(1, data->response_started_count());
+ }
+
+ // Clear current request to indicate the completion of sending a command
+ // to spawner server and getting the result.
+ cur_request_.reset();
+ context_.reset();
+ // Invalidate the weak pointers on the IO thread.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // Wakeup the caller in user thread.
+ event_.Signal();
+}
+
+void SpawnerCommunicator::ReadResult(URLRequest* request) {
+ DCHECK_EQ(request, cur_request_.get());
+ SpawnerRequestData* data =
+ static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this));
+ DCHECK(data);
+
+ IOBuffer* buf = data->buf();
+ // Read as many bytes as are available synchronously.
+ while (true) {
+ int num_bytes;
+ if (!request->Read(buf, kBufferSize, &num_bytes)) {
+ // Check whether the read failed synchronously.
+ if (!request->status().is_io_pending())
+ OnSpawnerCommandCompleted(request);
+ return;
+ }
+ if (!data->ConsumeBytesRead(num_bytes)) {
+ OnSpawnerCommandCompleted(request);
+ return;
+ }
+ }
+}
+
+void SpawnerCommunicator::OnResponseStarted(URLRequest* request) {
+ DCHECK_EQ(request, cur_request_.get());
+ SpawnerRequestData* data =
+ static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this));
+ DCHECK(data);
+
+ data->IncreaseResponseStartedCount();
+
+ if (!request->status().is_success()) {
+ OnSpawnerCommandCompleted(request);
+ return;
+ }
+
+ // Require HTTP responses to have a success status code.
+ if (request->GetResponseCode() != 200) {
+ LOG(ERROR) << "Spawner server returned bad status: "
+ << request->response_headers()->GetStatusLine();
+ data->SetResultCode(ERR_FAILED);
+ request->Cancel();
+ OnSpawnerCommandCompleted(request);
+ return;
+ }
+
+ ReadResult(request);
+}
+
+void SpawnerCommunicator::OnReadCompleted(URLRequest* request, int num_bytes) {
+ if (!cur_request_.get())
+ return;
+ DCHECK_EQ(request, cur_request_.get());
+ SpawnerRequestData* data =
+ static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this));
+ DCHECK(data);
+
+ if (data->ConsumeBytesRead(num_bytes)) {
+ // Keep reading.
+ ReadResult(request);
+ } else {
+ OnSpawnerCommandCompleted(request);
+ }
+}
+
+bool SpawnerCommunicator::StartServer(const std::string& arguments,
+ uint16* port) {
+ *port = 0;
+ // Send the start command to spawner server to start the Python test server
+ // on remote machine.
+ std::string server_return_data;
+ int result_code;
+ SendCommandAndWaitForResult("start", arguments, &result_code,
+ &server_return_data);
+ if (OK != result_code || server_return_data.empty())
+ return false;
+
+ // Check whether the data returned from spawner server is JSON-formatted.
+ scoped_ptr<base::Value> value(base::JSONReader::Read(server_return_data));
+ if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) {
+ LOG(ERROR) << "Invalid server data: " << server_return_data.c_str();
+ return false;
+ }
+
+ // Check whether spawner server returns valid data.
+ base::DictionaryValue* server_data =
+ static_cast<base::DictionaryValue*>(value.get());
+ std::string message;
+ if (!server_data->GetString("message", &message) || message != "started") {
+ LOG(ERROR) << "Invalid message in server data: ";
+ return false;
+ }
+ int int_port;
+ if (!server_data->GetInteger("port", &int_port) || int_port <= 0 ||
+ int_port > kuint16max) {
+ LOG(ERROR) << "Invalid port value: " << int_port;
+ return false;
+ }
+ *port = static_cast<uint16>(int_port);
+ return true;
+}
+
+bool SpawnerCommunicator::StopServer() {
+ // It's OK to stop the SpawnerCommunicator without starting it. Some tests
+ // have test server on their test fixture but do not actually use it.
+ if (!is_running_)
+ return true;
+
+ // When the test is done, ask the test server spawner to kill the test server
+ // on the remote machine.
+ std::string server_return_data;
+ int result_code;
+ SendCommandAndWaitForResult("kill", "", &result_code, &server_return_data);
+ Shutdown();
+ if (OK != result_code || server_return_data != "killed")
+ return false;
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/test/spawned_test_server/spawner_communicator.h b/chromium/net/test/spawned_test_server/spawner_communicator.h
new file mode 100644
index 00000000000..bf426e63502
--- /dev/null
+++ b/chromium/net/test/spawned_test_server/spawner_communicator.h
@@ -0,0 +1,151 @@
+// Copyright (c) 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.
+
+#ifndef NET_TEST_SPAWNED_TEST_SERVER_SPAWNER_COMMUNICATOR_H_
+#define NET_TEST_SPAWNED_TEST_SERVER_SPAWNER_COMMUNICATOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+class ScopedPortException;
+
+// SpawnerCommunicator communicates with a spawner server that runs on a
+// remote system.
+//
+// The test server used by unit tests is written in Python. However, Android
+// does not support running Python code, so the test server cannot run on the
+// same device running unit tests.
+//
+// The actual test server is executed on the host machine, while the unit tests
+// themselves continue running on the device. To control the test server on the
+// host machine, a second HTTP server is started, the spawner server, which
+// controls the life cycle of remote test servers. Calls to start/kill the
+// net::SpawnedTestServer are then redirected to the spawner server via
+// this spawner communicator.
+//
+// Currently only three commands are supported by spawner.
+//
+// (1) Start Python test server, format is:
+// Path: "/start".
+// Method: "POST".
+// Data to server: all arguments needed to launch the Python test server, in
+// JSON format.
+// Data from server: a JSON dict includes the following two field if success,
+// "port": the port the Python test server actually listen on that.
+// "message": must be "started".
+//
+// (2) Kill Python test server, format is:
+// Path: "/kill".
+// Method: "GET".
+// Data to server: None.
+// Data from server: String "killed" returned if success.
+//
+// (3) Ping Python test server to see whether it is alive, format is:
+// Path: "/ping".
+// Method: "GET".
+// Data to server: None.
+// Data from server: String "ready" returned if success.
+//
+// The internal I/O thread is required by net stack to perform net I/O.
+// The Start/StopServer methods block the caller thread until result is
+// fetched from spawner server or timed-out.
+class SpawnerCommunicator : public net::URLRequest::Delegate {
+ public:
+ explicit SpawnerCommunicator(uint16 port);
+ virtual ~SpawnerCommunicator();
+
+ // Starts an instance of the Python test server on the host/ machine.
+ // If successfully started, returns true, setting |*port| to the port
+ // on the local machine that can be used to communicate with the remote
+ // test server.
+ bool StartServer(const std::string& arguments,
+ uint16* port) WARN_UNUSED_RESULT;
+
+ bool StopServer() WARN_UNUSED_RESULT;
+
+ private:
+ // Starts the IO thread. Called on the user thread.
+ void StartIOThread();
+
+ // Shuts down the remote test server spawner. Called on the user thread.
+ void Shutdown();
+
+ // Waits for the server response on IO thread. Called on the user thread.
+ void WaitForResponse();
+
+ // Sends a command to the test server over HTTP, returning the result code
+ // |*result_code| and response data in |*data_received|, those two arguments
+ // must be not NULL, otherwise the method returns immediately without sending
+ // the |command|. If |post_data| is empty, HTTP GET will be used to send
+ // |command|. If |post_data| is non-empty, performs an HTTP POST.
+ // This method is called on the user thread.
+ void SendCommandAndWaitForResult(const std::string& command,
+ const std::string& post_data,
+ int* result_code,
+ std::string* data_received);
+
+ // Performs the command sending on the IO thread. Called on the IO thread.
+ void SendCommandAndWaitForResultOnIOThread(const std::string& command,
+ const std::string& post_data,
+ int* result_code,
+ std::string* data_received);
+
+ // URLRequest::Delegate methods. Called on the IO thread.
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request, int num_bytes) OVERRIDE;
+
+ // Reads Result from the response. Called on the IO thread.
+ void ReadResult(URLRequest* request);
+
+ // Called on the IO thread upon completion of the spawner command.
+ void OnSpawnerCommandCompleted(URLRequest* request);
+
+ // Callback on the IO thread for time-out task of request with id |id|.
+ void OnTimeout(int id);
+
+ // A thread to communicate with test_spawner server.
+ base::Thread io_thread_;
+
+ // WaitableEvent to notify whether the communication is done.
+ base::WaitableEvent event_;
+
+ // The local port used to communicate with the TestServer spawner. This is
+ // used to control the startup and shutdown of the Python TestServer running
+ // on the remote machine. On Android, this port will be redirected to the
+ // same port on the host machine.
+ const uint16 port_;
+
+ // Helper to add |port_| to the list of the globally explicitly allowed ports.
+ scoped_ptr<ScopedPortException> allowed_port_;
+
+ // The next ID to use for |cur_request_| (monotonically increasing).
+ int next_id_;
+
+ // Factory for creating the time-out task. This takes care of revoking
+ // outstanding tasks when |this| is deleted.
+ base::WeakPtrFactory<SpawnerCommunicator> weak_factory_;
+
+ // Request context used by |cur_request_|.
+ scoped_ptr<URLRequestContext> context_;
+
+ // The current (in progress) request, or NULL.
+ scoped_ptr<URLRequest> cur_request_;
+
+ // Only gets/sets |is_running_| on user's thread to avoid race-condition.
+ bool is_running_;
+
+ DISALLOW_COPY_AND_ASSIGN(SpawnerCommunicator);
+};
+
+} // namespace net
+
+#endif // NET_TEST_SPAWNED_TEST_SERVER_SPAWNER_COMMUNICATOR_H_
diff --git a/chromium/net/test/test_certificate_data.h b/chromium/net/test/test_certificate_data.h
new file mode 100644
index 00000000000..3ccda5e34cb
--- /dev/null
+++ b/chromium/net/test/test_certificate_data.h
@@ -0,0 +1,786 @@
+// Copyright (c) 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.
+
+namespace {
+
+// This is the SHA1 hash of the SubjectPublicKeyInfo of nist.der.
+static const char kNistSPKIHash[] =
+ "\x15\x60\xde\x65\x4e\x03\x9f\xd0\x08\x82"
+ "\xa9\x6a\xc4\x65\x8e\x6f\x92\x06\x84\x35";
+
+// kSatvedaSPKIs contains the SHA1 hashes of the SPKIs of the satveda.pem
+// certificate chain, in order.
+static const char kSatvedaSPKIs[2][21] = {
+ "\xd6\x2d\x7a\x12\x02\x7f\x9b\x8e\x4f\x2b"
+ "\x07\xc5\xfb\xf9\x2a\x2e\x9a\xcc\x0e\xe3",
+ "\xba\x2e\xb5\xa8\x3e\x13\x23\xd9\x53\x4b"
+ "\x5e\x65\xbc\xe7\xa3\x13\x5d\xd0\xa9\x96",
+};
+
+// kSatvedaSPKIsSHA256 contains the SHA256 hashes of the SPKIs of the
+// satveda.pem certificate chain, in order.
+static const char kSatvedaSPKIsSHA256[2][33] = {
+ "\xb9\x42\xab\xf2\x08\x63\xef\x81\x70\x88\x45\xc4\x39\xa2\x6e\x9c"
+ "\x2f\x9a\xf9\xf4\xcb\x23\x61\xd4\x83\x97\x61\x6d\xf2\x5b\x27\xa8",
+ "\x32\xb6\x4b\x66\x72\x7a\x20\x63\xe4\x06\x6f\x3b\x95\x8c\xb0\xaa"
+ "\xee\x57\x6a\x5e\xce\xfd\x95\x33\x99\xbb\x88\x74\x73\x1d\x95\x87",
+};
+
+// Certificates for test data. They're obtained with:
+//
+// $ openssl s_client -connect [host]:443 -showcerts > /tmp/host.pem < /dev/null
+// $ openssl x509 -inform PEM -outform DER < /tmp/host.pem > /tmp/host.der
+// $ xxd -i /tmp/host.der
+//
+// TODO(wtc): move these certificates to data files in the
+// src/net/data/ssl/certificates directory.
+
+// The linux compiler is nitty about unused variables. Declaring variables
+// in headers is not generally a good idea, but for our test data it is not
+// a big deal. Mark these as potentially unused so that the compiler won't
+// complain.
+#ifdef __GNUC__
+#define VARIABLE_IS_NOT_USED __attribute__ ((unused))
+#else
+#define VARIABLE_IS_NOT_USED
+#endif
+
+// Google's cert.
+
+unsigned char VARIABLE_IS_NOT_USED google_der[] = {
+ 0x30, 0x82, 0x03, 0x21, 0x30, 0x82, 0x02, 0x8a, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x01, 0x2a, 0x39, 0x76, 0x0d, 0x3f, 0x4f, 0xc9, 0x0b,
+ 0xe7, 0xbd, 0x2b, 0xcf, 0x95, 0x2e, 0x7a, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x4c,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x5a,
+ 0x41, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c,
+ 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x75,
+ 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x28, 0x50, 0x74, 0x79, 0x29, 0x20,
+ 0x4c, 0x74, 0x64, 0x2e, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x0d, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x53, 0x47,
+ 0x43, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x33,
+ 0x32, 0x37, 0x32, 0x32, 0x32, 0x30, 0x30, 0x37, 0x5a, 0x17, 0x0d, 0x31,
+ 0x30, 0x30, 0x33, 0x32, 0x37, 0x32, 0x32, 0x32, 0x30, 0x30, 0x37, 0x5a,
+ 0x30, 0x68, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61,
+ 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x4d,
+ 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x47,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x17, 0x30,
+ 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0e, 0x77, 0x77, 0x77, 0x2e,
+ 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x81,
+ 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02,
+ 0x81, 0x81, 0x00, 0xd6, 0xb9, 0xe1, 0xad, 0xb8, 0x61, 0x0b, 0x1f, 0x4e,
+ 0xb6, 0x3c, 0x09, 0x3d, 0xab, 0xe8, 0xe3, 0x2b, 0xb6, 0xe8, 0xa4, 0x3a,
+ 0x78, 0x2f, 0xd3, 0x51, 0x20, 0x22, 0x45, 0x95, 0xd8, 0x00, 0x91, 0x33,
+ 0x9a, 0xa7, 0xa2, 0x48, 0xea, 0x30, 0x57, 0x26, 0x97, 0x66, 0xc7, 0x5a,
+ 0xef, 0xf1, 0x9b, 0x0c, 0x3f, 0xe1, 0xb9, 0x7f, 0x7b, 0xc3, 0xc7, 0xcc,
+ 0xaf, 0x9c, 0xd0, 0x1f, 0x3c, 0x81, 0x15, 0x10, 0x58, 0xfc, 0x06, 0xb3,
+ 0xbf, 0xbc, 0x9c, 0x02, 0xb9, 0x51, 0xdc, 0xfb, 0xa6, 0xb9, 0x17, 0x42,
+ 0xe6, 0x46, 0xe7, 0x22, 0xcf, 0x6c, 0x27, 0x10, 0xfe, 0x54, 0xe6, 0x92,
+ 0x6c, 0x0c, 0x60, 0x76, 0x9a, 0xce, 0xf8, 0x7f, 0xac, 0xb8, 0x5a, 0x08,
+ 0x4a, 0xdc, 0xb1, 0x64, 0xbd, 0xa0, 0x74, 0x41, 0xb2, 0xac, 0x8f, 0x86,
+ 0x9d, 0x1a, 0xde, 0x58, 0x09, 0xfd, 0x6c, 0x0a, 0x25, 0xe0, 0x79, 0x02,
+ 0x03, 0x01, 0x00, 0x01, 0xa3, 0x81, 0xe7, 0x30, 0x81, 0xe4, 0x30, 0x28,
+ 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x21, 0x30, 0x1f, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x42, 0x04, 0x01, 0x30, 0x36, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x2f, 0x30, 0x2d, 0x30, 0x2b, 0xa0, 0x29, 0xa0, 0x27, 0x86, 0x25, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x74, 0x68,
+ 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x61,
+ 0x77, 0x74, 0x65, 0x53, 0x47, 0x43, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x6c,
+ 0x30, 0x72, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01,
+ 0x04, 0x66, 0x30, 0x64, 0x30, 0x22, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x3e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x32, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,
+ 0x72, 0x79, 0x2f, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x5f, 0x53, 0x47,
+ 0x43, 0x5f, 0x43, 0x41, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0c, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05,
+ 0x00, 0x03, 0x81, 0x81, 0x00, 0x39, 0xb6, 0xfb, 0x11, 0xbc, 0x33, 0x2c,
+ 0xc3, 0x90, 0x48, 0xe3, 0x6e, 0xc3, 0x9b, 0x38, 0xb1, 0x42, 0xd1, 0x00,
+ 0x09, 0x58, 0x63, 0xa0, 0xe1, 0x98, 0x1c, 0x85, 0xf2, 0xef, 0x10, 0x1d,
+ 0x60, 0x4e, 0x51, 0x09, 0x62, 0xf5, 0x05, 0xbd, 0x9d, 0x4f, 0x87, 0x6c,
+ 0x98, 0x72, 0x07, 0x80, 0xc3, 0x59, 0x48, 0x14, 0xe2, 0xd6, 0xef, 0xd0,
+ 0x8f, 0x33, 0x6a, 0x68, 0x31, 0xfa, 0xb7, 0xbb, 0x85, 0xcc, 0xf7, 0xc7,
+ 0x47, 0x7b, 0x67, 0x93, 0x3c, 0xc3, 0x16, 0x51, 0x9b, 0x6f, 0x87, 0x20,
+ 0xfd, 0x67, 0x4c, 0x2b, 0xea, 0x6a, 0x49, 0xdb, 0x11, 0xd1, 0xbd, 0xd7,
+ 0x95, 0x22, 0x43, 0x7a, 0x06, 0x7b, 0x4e, 0xf6, 0x37, 0x8e, 0xa2, 0xb9,
+ 0xcf, 0x1f, 0xa5, 0xd2, 0xbd, 0x3b, 0x04, 0x97, 0x39, 0xb3, 0x0f, 0xfa,
+ 0x38, 0xb5, 0xaf, 0x55, 0x20, 0x88, 0x60, 0x93, 0xf2, 0xde, 0xdb, 0xff,
+ 0xdf
+};
+
+// webkit.org's cert.
+
+unsigned char VARIABLE_IS_NOT_USED webkit_der[] = {
+ 0x30, 0x82, 0x05, 0x0d, 0x30, 0x82, 0x03, 0xf5, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x43, 0xdd, 0x63, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81, 0xca,
+ 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
+ 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x07,
+ 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, 0x13, 0x30, 0x11, 0x06,
+ 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, 0x6f, 0x74, 0x74, 0x73,
+ 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04,
+ 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x33, 0x30, 0x31,
+ 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,
+ 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27,
+ 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, 0x20, 0x53, 0x65, 0x63,
+ 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+ 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x05,
+ 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, 0x38, 0x37, 0x30, 0x1e,
+ 0x17, 0x0d, 0x30, 0x38, 0x30, 0x33, 0x31, 0x38, 0x32, 0x33, 0x33, 0x35,
+ 0x31, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x33, 0x31, 0x38, 0x32,
+ 0x33, 0x33, 0x35, 0x31, 0x39, 0x5a, 0x30, 0x79, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30,
+ 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69,
+ 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03,
+ 0x55, 0x04, 0x07, 0x13, 0x09, 0x43, 0x75, 0x70, 0x65, 0x72, 0x74, 0x69,
+ 0x6e, 0x6f, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31,
+ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0c, 0x4d, 0x61,
+ 0x63, 0x20, 0x4f, 0x53, 0x20, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x31, 0x15,
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x0c, 0x2a, 0x2e, 0x77,
+ 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0x81, 0x9f,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
+ 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81,
+ 0x81, 0x00, 0xa7, 0x62, 0x79, 0x41, 0xda, 0x28, 0xf2, 0xc0, 0x4f, 0xe0,
+ 0x25, 0xaa, 0xa1, 0x2e, 0x3b, 0x30, 0x94, 0xb5, 0xc9, 0x26, 0x3a, 0x1b,
+ 0xe2, 0xd0, 0xcc, 0xa2, 0x95, 0xe2, 0x91, 0xc0, 0xf0, 0x40, 0x9e, 0x27,
+ 0x6e, 0xbd, 0x6e, 0xde, 0x7c, 0xb6, 0x30, 0x5c, 0xb8, 0x9b, 0x01, 0x2f,
+ 0x92, 0x04, 0xa1, 0xef, 0x4a, 0xb1, 0x6c, 0xb1, 0x7e, 0x8e, 0xcd, 0xa6,
+ 0xf4, 0x40, 0x73, 0x1f, 0x2c, 0x96, 0xad, 0xff, 0x2a, 0x6d, 0x0e, 0xba,
+ 0x52, 0x84, 0x83, 0xb0, 0x39, 0xee, 0xc9, 0x39, 0xdc, 0x1e, 0x34, 0xd0,
+ 0xd8, 0x5d, 0x7a, 0x09, 0xac, 0xa9, 0xee, 0xca, 0x65, 0xf6, 0x85, 0x3a,
+ 0x6b, 0xee, 0xe4, 0x5c, 0x5e, 0xf8, 0xda, 0xd1, 0xce, 0x88, 0x47, 0xcd,
+ 0x06, 0x21, 0xe0, 0xb9, 0x4b, 0xe4, 0x07, 0xcb, 0x57, 0xdc, 0xca, 0x99,
+ 0x54, 0xf7, 0x0e, 0xd5, 0x17, 0x95, 0x05, 0x2e, 0xe9, 0xb1, 0x02, 0x03,
+ 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xce, 0x30, 0x82, 0x01, 0xca, 0x30,
+ 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0b,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x05, 0xa0, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x57, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x50, 0x30, 0x4e, 0x30, 0x4c, 0xa0, 0x4a, 0xa0, 0x48, 0x86,
+ 0x46, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64,
+ 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x6f, 0x64, 0x61,
+ 0x64, 0x64, 0x79, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x69,
+ 0x73, 0x73, 0x75, 0x69, 0x6e, 0x67, 0x33, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, 0x30, 0x49, 0x30, 0x47,
+ 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, 0x6d, 0x01, 0x07, 0x17,
+ 0x02, 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73,
+ 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x30,
+ 0x7f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x73, 0x30, 0x71, 0x30, 0x23, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
+ 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05,
+ 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
+ 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,
+ 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64,
+ 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x48, 0xdf, 0x60, 0x32, 0xcc,
+ 0x89, 0x01, 0xb6, 0xdc, 0x2f, 0xe3, 0x73, 0xb5, 0x9c, 0x16, 0x58, 0x32,
+ 0x68, 0xa9, 0xc3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, 0x93, 0x6c, 0x45, 0xd6,
+ 0xe2, 0xee, 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, 0x99, 0x68, 0xcc, 0xe7,
+ 0x30, 0x23, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x1c, 0x30, 0x1a, 0x82,
+ 0x0c, 0x2a, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6f, 0x72,
+ 0x67, 0x82, 0x0a, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6f, 0x72,
+ 0x67, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x1e, 0x6a, 0xe7,
+ 0xe0, 0x4f, 0xe7, 0x4d, 0xd0, 0x69, 0x7c, 0xf8, 0x8f, 0x99, 0xb4, 0x18,
+ 0x95, 0x36, 0x24, 0x0f, 0x0e, 0xa3, 0xea, 0x34, 0x37, 0xf4, 0x7d, 0xd5,
+ 0x92, 0x35, 0x53, 0x72, 0x76, 0x3f, 0x69, 0xf0, 0x82, 0x56, 0xe3, 0x94,
+ 0x7a, 0x1d, 0x1a, 0x81, 0xaf, 0x9f, 0xc7, 0x43, 0x01, 0x64, 0xd3, 0x7c,
+ 0x0d, 0xc8, 0x11, 0x4e, 0x4a, 0xe6, 0x1a, 0xc3, 0x01, 0x74, 0xe8, 0x35,
+ 0x87, 0x5c, 0x61, 0xaa, 0x8a, 0x46, 0x06, 0xbe, 0x98, 0x95, 0x24, 0x9e,
+ 0x01, 0xe3, 0xe6, 0xa0, 0x98, 0xee, 0x36, 0x44, 0x56, 0x8d, 0x23, 0x9c,
+ 0x65, 0xea, 0x55, 0x6a, 0xdf, 0x66, 0xee, 0x45, 0xe8, 0xa0, 0xe9, 0x7d,
+ 0x9a, 0xba, 0x94, 0xc5, 0xc8, 0xc4, 0x4b, 0x98, 0xff, 0x9a, 0x01, 0x31,
+ 0x6d, 0xf9, 0x2b, 0x58, 0xe7, 0xe7, 0x2a, 0xc5, 0x4d, 0xbb, 0xbb, 0xcd,
+ 0x0d, 0x70, 0xe1, 0xad, 0x03, 0xf5, 0xfe, 0xf4, 0x84, 0x71, 0x08, 0xd2,
+ 0xbc, 0x04, 0x7b, 0x26, 0x1c, 0xa8, 0x0f, 0x9c, 0xd8, 0x12, 0x6a, 0x6f,
+ 0x2b, 0x67, 0xa1, 0x03, 0x80, 0x9a, 0x11, 0x0b, 0xe9, 0xe0, 0xb5, 0xb3,
+ 0xb8, 0x19, 0x4e, 0x0c, 0xa4, 0xd9, 0x2b, 0x3b, 0xc2, 0xca, 0x20, 0xd3,
+ 0x0c, 0xa4, 0xff, 0x93, 0x13, 0x1f, 0xfc, 0xba, 0x94, 0x93, 0x8c, 0x64,
+ 0x15, 0x2e, 0x28, 0xa9, 0x55, 0x8c, 0x2c, 0x48, 0xd3, 0xd3, 0xc1, 0x50,
+ 0x69, 0x19, 0xe8, 0x34, 0xd3, 0xf1, 0x04, 0x9f, 0x0a, 0x7a, 0x21, 0x87,
+ 0xbf, 0xb9, 0x59, 0x37, 0x2e, 0xf4, 0x71, 0xa5, 0x3e, 0xbe, 0xcd, 0x70,
+ 0x83, 0x18, 0xf8, 0x8a, 0x72, 0x85, 0x45, 0x1f, 0x08, 0x01, 0x6f, 0x37,
+ 0xf5, 0x2b, 0x7b, 0xea, 0xb9, 0x8b, 0xa3, 0xcc, 0xfd, 0x35, 0x52, 0xdd,
+ 0x66, 0xde, 0x4f, 0x30, 0xc5, 0x73, 0x81, 0xb6, 0xe8, 0x3c, 0xd8, 0x48,
+ 0x8a
+};
+
+// thawte.com's cert (it's EV-licious!).
+unsigned char VARIABLE_IS_NOT_USED thawte_der[] = {
+ 0x30, 0x82, 0x04, 0xa5, 0x30, 0x82, 0x03, 0x8d, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x10, 0x17, 0x76, 0x05, 0x88, 0x95, 0x58, 0xee, 0xbb, 0x00,
+ 0xda, 0x10, 0xe5, 0xf0, 0xf3, 0x9c, 0xf0, 0x30, 0x0d, 0x06, 0x09, 0x2a,
+ 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x81,
+ 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x0c, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2c, 0x20, 0x49, 0x6e, 0x63,
+ 0x2e, 0x31, 0x39, 0x30, 0x37, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x30,
+ 0x54, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65,
+ 0x20, 0x61, 0x74, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x2f, 0x63, 0x70, 0x73, 0x20, 0x28, 0x63, 0x29, 0x30, 0x36,
+ 0x31, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x21, 0x74,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64,
+ 0x65, 0x64, 0x20, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d,
+ 0x30, 0x38, 0x31, 0x31, 0x31, 0x39, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x5a, 0x17, 0x0d, 0x31, 0x30, 0x30, 0x31, 0x31, 0x37, 0x32, 0x33, 0x35,
+ 0x39, 0x35, 0x39, 0x5a, 0x30, 0x81, 0xc7, 0x31, 0x13, 0x30, 0x11, 0x06,
+ 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, 0x03,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x19, 0x30, 0x17, 0x06, 0x0b, 0x2b, 0x06,
+ 0x01, 0x04, 0x01, 0x82, 0x37, 0x3c, 0x02, 0x01, 0x02, 0x14, 0x08, 0x44,
+ 0x65, 0x6c, 0x61, 0x77, 0x61, 0x72, 0x65, 0x31, 0x1b, 0x30, 0x19, 0x06,
+ 0x03, 0x55, 0x04, 0x0f, 0x13, 0x12, 0x56, 0x31, 0x2e, 0x30, 0x2c, 0x20,
+ 0x43, 0x6c, 0x61, 0x75, 0x73, 0x65, 0x20, 0x35, 0x2e, 0x28, 0x62, 0x29,
+ 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x14, 0x0a, 0x54,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x10, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x07, 0x33, 0x38, 0x39, 0x38,
+ 0x32, 0x36, 0x31, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69,
+ 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x14, 0x0d,
+ 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65,
+ 0x77, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x03, 0x14, 0x0e,
+ 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00,
+ 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xe7, 0x89, 0x68, 0xb5, 0x6e,
+ 0x1d, 0x38, 0x19, 0xf6, 0x2d, 0x61, 0xc2, 0x00, 0xba, 0x6e, 0xab, 0x66,
+ 0x92, 0xd6, 0x85, 0x87, 0x2d, 0xd5, 0xa8, 0x58, 0xa9, 0x7a, 0x75, 0x27,
+ 0x9d, 0xed, 0x9e, 0xfe, 0x06, 0x71, 0x70, 0x2d, 0x21, 0x70, 0x4c, 0x3e,
+ 0x9c, 0xb6, 0xd5, 0x5d, 0x44, 0x92, 0xb4, 0xe0, 0xee, 0x7c, 0x0a, 0x50,
+ 0x4c, 0x0d, 0x67, 0x98, 0xaa, 0x01, 0x0e, 0x37, 0xa3, 0x2a, 0xef, 0xe6,
+ 0xe0, 0x11, 0x7b, 0xee, 0xb0, 0xa2, 0xb4, 0x32, 0x64, 0xa7, 0x0d, 0xda,
+ 0x6c, 0x15, 0xf8, 0xc5, 0xa5, 0x5a, 0x2c, 0xfc, 0xc9, 0xa6, 0x3c, 0x88,
+ 0x88, 0xbf, 0xdf, 0xa7, 0x38, 0xf0, 0x78, 0xed, 0x81, 0x93, 0x29, 0x0c,
+ 0xae, 0xc7, 0xab, 0x51, 0x21, 0x5e, 0xca, 0x95, 0xe5, 0x48, 0x52, 0x41,
+ 0xb6, 0x18, 0x60, 0x04, 0x19, 0x6f, 0x3d, 0x80, 0x14, 0xd3, 0xaf, 0x23,
+ 0x03, 0x10, 0x95, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x49,
+ 0x30, 0x82, 0x01, 0x45, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x39, 0x06, 0x03, 0x55, 0x1d,
+ 0x1f, 0x04, 0x32, 0x30, 0x30, 0x30, 0x2e, 0xa0, 0x2c, 0xa0, 0x2a, 0x86,
+ 0x28, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e,
+ 0x74, 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x45, 0x56, 0x43, 0x41, 0x32, 0x30, 0x30,
+ 0x36, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x42, 0x06, 0x03, 0x55, 0x1d, 0x20,
+ 0x04, 0x3b, 0x30, 0x39, 0x30, 0x37, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01,
+ 0x86, 0xf8, 0x45, 0x01, 0x07, 0x30, 0x01, 0x30, 0x28, 0x30, 0x26, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x1a, 0x68,
+ 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x70,
+ 0x73, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, 0x30, 0x14,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x1f, 0x06, 0x03,
+ 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xcd, 0x32, 0xe2,
+ 0xf2, 0x5d, 0x25, 0x47, 0x02, 0xaa, 0x8f, 0x79, 0x4b, 0x32, 0xee, 0x03,
+ 0x99, 0xfd, 0x30, 0x49, 0xd1, 0x30, 0x76, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6a, 0x30, 0x68, 0x30, 0x22, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x74,
+ 0x68, 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x42, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x36, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x74, 0x68,
+ 0x61, 0x77, 0x74, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70,
+ 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x54, 0x68, 0x61, 0x77,
+ 0x74, 0x65, 0x5f, 0x45, 0x56, 0x5f, 0x43, 0x41, 0x5f, 0x32, 0x30, 0x30,
+ 0x36, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01,
+ 0x00, 0xb2, 0xa0, 0x96, 0xdd, 0xec, 0x04, 0x38, 0x6b, 0xc3, 0x7a, 0xad,
+ 0x23, 0x44, 0x91, 0xe5, 0x62, 0x8c, 0xb1, 0xf6, 0x9c, 0x03, 0x21, 0x1f,
+ 0xef, 0x03, 0xd9, 0xca, 0x63, 0xb2, 0xf8, 0xdb, 0x5a, 0x93, 0xc2, 0xcc,
+ 0xf1, 0x7c, 0x6f, 0xeb, 0x0f, 0x51, 0x7b, 0x4b, 0xe7, 0xb5, 0xfc, 0xbc,
+ 0x9b, 0x87, 0x48, 0xcc, 0x5b, 0xf9, 0xc8, 0x66, 0xa4, 0x40, 0xac, 0xe9,
+ 0x42, 0x5d, 0xed, 0xf3, 0x53, 0x13, 0xe7, 0xbd, 0x6e, 0x7f, 0x50, 0x53,
+ 0x64, 0xb3, 0x95, 0xf1, 0x42, 0x4f, 0x36, 0x54, 0xb4, 0x1e, 0x7f, 0x18,
+ 0x37, 0x39, 0x3b, 0x06, 0x5b, 0xe5, 0x13, 0xd9, 0x57, 0xbc, 0xd5, 0x68,
+ 0xe3, 0x71, 0x5f, 0x5f, 0x2b, 0xf5, 0xa6, 0xc2, 0x8f, 0x67, 0x81, 0x3a,
+ 0x44, 0x63, 0x8c, 0x36, 0xfa, 0xa8, 0xed, 0xfd, 0xd7, 0x5e, 0xa2, 0x9f,
+ 0xb0, 0x9d, 0x47, 0x86, 0xfb, 0x71, 0x60, 0x8e, 0xc8, 0xd3, 0x45, 0x19,
+ 0xb7, 0xda, 0xcd, 0x9e, 0xea, 0x70, 0x10, 0x87, 0x37, 0x10, 0xdd, 0x2c,
+ 0x11, 0xdf, 0xee, 0x02, 0x21, 0xa6, 0x75, 0xe6, 0xd6, 0x9f, 0x54, 0x72,
+ 0x61, 0xe6, 0x5c, 0x1e, 0x6e, 0x16, 0xf6, 0x8e, 0xb8, 0xfc, 0x47, 0x80,
+ 0x05, 0x4b, 0xf7, 0x2d, 0x02, 0xee, 0x50, 0x26, 0xd1, 0x48, 0x01, 0x60,
+ 0xdc, 0x3c, 0xa7, 0xdb, 0xeb, 0xca, 0x8b, 0xa6, 0xff, 0x9e, 0x47, 0x5d,
+ 0x87, 0x40, 0xf8, 0xd2, 0x82, 0xd7, 0x13, 0x64, 0x0e, 0xd4, 0xb3, 0x29,
+ 0x22, 0xa7, 0xe0, 0xc8, 0xcd, 0x8c, 0x4d, 0xf5, 0x11, 0x21, 0x26, 0x02,
+ 0x43, 0x33, 0x8e, 0xa9, 0x3f, 0x91, 0xd4, 0x05, 0x97, 0xc9, 0xd3, 0x42,
+ 0x6b, 0x05, 0x99, 0xf6, 0x16, 0x71, 0x67, 0x65, 0xc7, 0x96, 0xdf, 0x2a,
+ 0xd7, 0x54, 0x63, 0x25, 0xc0, 0x28, 0xf7, 0x1c, 0xee, 0xcd, 0x8b, 0xe4,
+ 0x9d, 0x32, 0xa3, 0x81, 0x55
+};
+
+// A certificate for www.paypal.com with a NULL byte in the common name.
+// From http://www.gossamer-threads.com/lists/fulldisc/full-disclosure/70363
+unsigned char VARIABLE_IS_NOT_USED paypal_null_der[] = {
+ 0x30, 0x82, 0x06, 0x44, 0x30, 0x82, 0x05, 0xad, 0xa0, 0x03, 0x02, 0x01,
+ 0x02, 0x02, 0x03, 0x00, 0xf0, 0x9b, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x82, 0x01,
+ 0x12, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02,
+ 0x45, 0x53, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13,
+ 0x09, 0x42, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, 0x31, 0x12,
+ 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x42, 0x61, 0x72,
+ 0x63, 0x65, 0x6c, 0x6f, 0x6e, 0x61, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x20, 0x49, 0x50, 0x53, 0x20, 0x43, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x73, 0x2e, 0x6c,
+ 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x14, 0x25,
+ 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63,
+ 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x43, 0x2e, 0x49, 0x2e, 0x46, 0x2e,
+ 0x20, 0x20, 0x42, 0x2d, 0x42, 0x36, 0x32, 0x32, 0x31, 0x30, 0x36, 0x39,
+ 0x35, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x25,
+ 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41,
+ 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25,
+ 0x69, 0x70, 0x73, 0x43, 0x41, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41,
+ 0x31, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
+ 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30,
+ 0x1e, 0x17, 0x0d, 0x30, 0x39, 0x30, 0x32, 0x32, 0x34, 0x32, 0x33, 0x30,
+ 0x34, 0x31, 0x37, 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x32, 0x32, 0x34,
+ 0x32, 0x33, 0x30, 0x34, 0x31, 0x37, 0x5a, 0x30, 0x81, 0x94, 0x31, 0x0b,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61,
+ 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x53, 0x61, 0x6e, 0x20, 0x46,
+ 0x72, 0x61, 0x6e, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x31, 0x11, 0x30, 0x0f,
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x53, 0x65, 0x63, 0x75, 0x72,
+ 0x69, 0x74, 0x79, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x0b, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x55, 0x6e, 0x69,
+ 0x74, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x26,
+ 0x77, 0x77, 0x77, 0x2e, 0x70, 0x61, 0x79, 0x70, 0x61, 0x6c, 0x2e, 0x63,
+ 0x6f, 0x6d, 0x00, 0x73, 0x73, 0x6c, 0x2e, 0x73, 0x65, 0x63, 0x75, 0x72,
+ 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
+ 0x63, 0x63, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00,
+ 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd2, 0x69, 0xfa, 0x6f, 0x3a,
+ 0x00, 0xb4, 0x21, 0x1b, 0xc8, 0xb1, 0x02, 0xd7, 0x3f, 0x19, 0xb2, 0xc4,
+ 0x6d, 0xb4, 0x54, 0xf8, 0x8b, 0x8a, 0xcc, 0xdb, 0x72, 0xc2, 0x9e, 0x3c,
+ 0x60, 0xb9, 0xc6, 0x91, 0x3d, 0x82, 0xb7, 0x7d, 0x99, 0xff, 0xd1, 0x29,
+ 0x84, 0xc1, 0x73, 0x53, 0x9c, 0x82, 0xdd, 0xfc, 0x24, 0x8c, 0x77, 0xd5,
+ 0x41, 0xf3, 0xe8, 0x1e, 0x42, 0xa1, 0xad, 0x2d, 0x9e, 0xff, 0x5b, 0x10,
+ 0x26, 0xce, 0x9d, 0x57, 0x17, 0x73, 0x16, 0x23, 0x38, 0xc8, 0xd6, 0xf1,
+ 0xba, 0xa3, 0x96, 0x5b, 0x16, 0x67, 0x4a, 0x4f, 0x73, 0x97, 0x3a, 0x4d,
+ 0x14, 0xa4, 0xf4, 0xe2, 0x3f, 0x8b, 0x05, 0x83, 0x42, 0xd1, 0xd0, 0xdc,
+ 0x2f, 0x7a, 0xe5, 0xb6, 0x10, 0xb2, 0x11, 0xc0, 0xdc, 0x21, 0x2a, 0x90,
+ 0xff, 0xae, 0x97, 0x71, 0x5a, 0x49, 0x81, 0xac, 0x40, 0xf3, 0x3b, 0xb8,
+ 0x59, 0xb2, 0x4f, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x03, 0x21,
+ 0x30, 0x82, 0x03, 0x1d, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04,
+ 0x02, 0x30, 0x00, 0x30, 0x11, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86,
+ 0xf8, 0x42, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, 0x06, 0x40, 0x30, 0x0b,
+ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x03, 0xf8, 0x30,
+ 0x13, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30, 0x1d, 0x06, 0x03,
+ 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x61, 0x8f, 0x61, 0x34, 0x43,
+ 0x55, 0x14, 0x7f, 0x27, 0x09, 0xce, 0x4c, 0x8b, 0xea, 0x9b, 0x7b, 0x19,
+ 0x25, 0xbc, 0x6e, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x0e, 0x07, 0x60, 0xd4, 0x39, 0xc9, 0x1b, 0x5b,
+ 0x5d, 0x90, 0x7b, 0x23, 0xc8, 0xd2, 0x34, 0x9d, 0x4a, 0x9a, 0x46, 0x39,
+ 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, 0x02, 0x30, 0x00, 0x30,
+ 0x1c, 0x06, 0x03, 0x55, 0x1d, 0x12, 0x04, 0x15, 0x30, 0x13, 0x81, 0x11,
+ 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x40, 0x69, 0x70, 0x73, 0x63,
+ 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x72, 0x06, 0x09, 0x60, 0x86, 0x48,
+ 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x04, 0x65, 0x16, 0x63, 0x4f, 0x72,
+ 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x49,
+ 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x4e,
+ 0x4f, 0x54, 0x20, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x45, 0x44,
+ 0x2e, 0x20, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x20, 0x53, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x65, 0x20, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x20,
+ 0x62, 0x79, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x30, 0x2f, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42,
+ 0x01, 0x02, 0x04, 0x22, 0x16, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30,
+ 0x32, 0x2f, 0x30, 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8,
+ 0x42, 0x01, 0x04, 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73,
+ 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61,
+ 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30,
+ 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32,
+ 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30,
+ 0x46, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x03,
+ 0x04, 0x39, 0x16, 0x37, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f,
+ 0x72, 0x65, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x4c,
+ 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3f, 0x30,
+ 0x43, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x07,
+ 0x04, 0x36, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f,
+ 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f,
+ 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x43, 0x4c, 0x41, 0x53, 0x45,
+ 0x41, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3f, 0x30, 0x41, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x08, 0x04, 0x34, 0x16,
+ 0x32, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+ 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69,
+ 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x70, 0x6f, 0x6c,
+ 0x69, 0x63, 0x79, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x68,
+ 0x74, 0x6d, 0x6c, 0x30, 0x81, 0x83, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x7c, 0x30, 0x7a, 0x30, 0x39, 0xa0, 0x37, 0xa0, 0x35, 0x86, 0x33, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x70,
+ 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69, 0x70, 0x73, 0x63,
+ 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73, 0x63, 0x61, 0x32,
+ 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41, 0x31, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x3d, 0xa0, 0x3b, 0xa0, 0x39, 0x86, 0x37, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x62, 0x61, 0x63, 0x6b,
+ 0x2e, 0x69, 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x69,
+ 0x70, 0x73, 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x2f, 0x69, 0x70, 0x73,
+ 0x63, 0x61, 0x32, 0x30, 0x30, 0x32, 0x43, 0x4c, 0x41, 0x53, 0x45, 0x41,
+ 0x31, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x32, 0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x26, 0x30, 0x24, 0x30, 0x22, 0x06,
+ 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x16, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x69,
+ 0x70, 0x73, 0x63, 0x61, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
+ 0x03, 0x81, 0x81, 0x00, 0x68, 0xee, 0x79, 0x97, 0x97, 0xdd, 0x3b, 0xef,
+ 0x16, 0x6a, 0x06, 0xf2, 0x14, 0x9a, 0x6e, 0xcd, 0x9e, 0x12, 0xf7, 0xaa,
+ 0x83, 0x10, 0xbd, 0xd1, 0x7c, 0x98, 0xfa, 0xc7, 0xae, 0xd4, 0x0e, 0x2c,
+ 0x9e, 0x38, 0x05, 0x9d, 0x52, 0x60, 0xa9, 0x99, 0x0a, 0x81, 0xb4, 0x98,
+ 0x90, 0x1d, 0xae, 0xbb, 0x4a, 0xd7, 0xb9, 0xdc, 0x88, 0x9e, 0x37, 0x78,
+ 0x41, 0x5b, 0xf7, 0x82, 0xa5, 0xf2, 0xba, 0x41, 0x25, 0x5a, 0x90, 0x1a,
+ 0x1e, 0x45, 0x38, 0xa1, 0x52, 0x58, 0x75, 0x94, 0x26, 0x44, 0xfb, 0x20,
+ 0x07, 0xba, 0x44, 0xcc, 0xe5, 0x4a, 0x2d, 0x72, 0x3f, 0x98, 0x47, 0xf6,
+ 0x26, 0xdc, 0x05, 0x46, 0x05, 0x07, 0x63, 0x21, 0xab, 0x46, 0x9b, 0x9c,
+ 0x78, 0xd5, 0x54, 0x5b, 0x3d, 0x0c, 0x1e, 0xc8, 0x64, 0x8c, 0xb5, 0x50,
+ 0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43, 0x96, 0x07, 0xa8, 0xbb
+};
+
+// DER-encoded X.509 DistinguishedNames.
+//
+// To output the subject or issuer of a certificate:
+//
+// openssl asn1parse -i -inform DER -in <cert>
+//
+// The output will contain
+// SEQUENCE [This is the issuer name]
+// ...
+// SEQUENCE [This is the validity period]
+// UTCTIME (or GENERALTIME)
+// UTCTIME
+// SEQUENCE [This is the subject]
+// ...
+//
+// The OFFSET is the first column before the column, e.g. for '21:d=2', the
+// offset is 21 for the SEQUENCE you're interested in.
+// The LENGTH is 'hl + l'.
+//
+// To generate the table, then use the following for a DER-encoded
+// certificate:
+//
+// xxd -i -s $OFFSET -l $LENGTH <cert>
+//
+// For PEM certificates, convert them to DER before, as in:
+//
+// openssl x509 -inform PEM -outform DER -in <cert> |
+// xxd -i -s $OFFSET -l $LENGTH
+//
+
+// 0:d=0 hl=2 l= 95 cons: SEQUENCE
+// 2:d=1 hl=2 l= 11 cons: SET
+// 4:d=2 hl=2 l= 9 cons: SEQUENCE
+// 6:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :US
+// 15:d=1 hl=2 l= 23 cons: SET
+// 17:d=2 hl=2 l= 21 cons: SEQUENCE
+// 19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 24:d=3 hl=2 l= 14 prim: PRINTABLESTRING :VeriSign, Inc.
+// 40:d=1 hl=2 l= 55 cons: SET
+// 42:d=2 hl=2 l= 53 cons: SEQUENCE
+// 44:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 49:d=3 hl=2 l= 46 prim: PRINTABLESTRING :
+// Class 1 Public Primary Certification Authority
+const uint8 VARIABLE_IS_NOT_USED VerisignDN[] = {
+ 0x30, 0x5f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x53, 0x69, 0x67, 0x6e, 0x2c, 0x20,
+ 0x49, 0x6e, 0x63, 0x2e, 0x31, 0x37, 0x30, 0x35, 0x06, 0x03, 0x55, 0x04,
+ 0x0b, 0x13, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x31, 0x20, 0x50,
+ 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72,
+ 0x79, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79
+};
+
+// 0:d=0 hl=2 l= 125 cons: SEQUENCE
+// 2:d=1 hl=2 l= 11 cons: SET
+// 4:d=2 hl=2 l= 9 cons: SEQUENCE
+// 6:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 11:d=3 hl=2 l= 2 prim: PRINTABLESTRING :IL
+// 15:d=1 hl=2 l= 22 cons: SET
+// 17:d=2 hl=2 l= 20 cons: SEQUENCE
+// 19:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 24:d=3 hl=2 l= 13 prim: PRINTABLESTRING :StartCom Ltd.
+// 39:d=1 hl=2 l= 43 cons: SET
+// 41:d=2 hl=2 l= 41 cons: SEQUENCE
+// 43:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 48:d=3 hl=2 l= 34 prim: PRINTABLESTRING :
+// Secure Digital Certificate Signing
+// 84:d=1 hl=2 l= 41 cons: SET
+// 86:d=2 hl=2 l= 39 cons: SEQUENCE
+// 88:d=3 hl=2 l= 3 prim: OBJECT :commonName
+// 93:d=3 hl=2 l= 32 prim: PRINTABLESTRING :
+// StartCom Certification Authority
+const uint8 VARIABLE_IS_NOT_USED StartComDN[] = {
+ 0x30, 0x7d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x49, 0x4c, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x4c,
+ 0x74, 0x64, 0x2e, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0b,
+ 0x13, 0x22, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67,
+ 0x69, 0x74, 0x61, 0x6c, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
+ 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67,
+ 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x20, 0x53,
+ 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x6d, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79
+};
+
+// 0:d=0 hl=3 l= 174 cons: SEQUENCE
+// 3:d=1 hl=2 l= 11 cons: SET
+// 5:d=2 hl=2 l= 9 cons: SEQUENCE
+// 7:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 12:d=3 hl=2 l= 2 prim: PRINTABLESTRING :US
+// 16:d=1 hl=2 l= 11 cons: SET
+// 18:d=2 hl=2 l= 9 cons: SEQUENCE
+// 20:d=3 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
+// 25:d=3 hl=2 l= 2 prim: PRINTABLESTRING :UT
+// 29:d=1 hl=2 l= 23 cons: SET
+// 31:d=2 hl=2 l= 21 cons: SEQUENCE
+// 33:d=3 hl=2 l= 3 prim: OBJECT :localityName
+// 38:d=3 hl=2 l= 14 prim: PRINTABLESTRING :Salt Lake City
+// 54:d=1 hl=2 l= 30 cons: SET
+// 56:d=2 hl=2 l= 28 cons: SEQUENCE
+// 58:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+// 63:d=3 hl=2 l= 21 prim: PRINTABLESTRING :The USERTRUST Network
+// 86:d=1 hl=2 l= 33 cons: SET
+// 88:d=2 hl=2 l= 31 cons: SEQUENCE
+// 90:d=3 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 95:d=3 hl=2 l= 24 prim: PRINTABLESTRING :http://www.usertrust.com
+//121:d=1 hl=2 l= 54 cons: SET
+//123:d=2 hl=2 l= 52 cons: SEQUENCE
+//125:d=3 hl=2 l= 3 prim: OBJECT :commonName
+//130:d=3 hl=2 l= 45 prim: PRINTABLESTRING :
+// UTN-USERFirst-Client Authentication and Email
+const uint8 VARIABLE_IS_NOT_USED UserTrustDN[] = {
+ 0x30, 0x81, 0xae, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55,
+ 0x04, 0x07, 0x13, 0x0e, 0x53, 0x61, 0x6c, 0x74, 0x20, 0x4c, 0x61, 0x6b,
+ 0x65, 0x20, 0x43, 0x69, 0x74, 0x79, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x13, 0x15, 0x54, 0x68, 0x65, 0x20, 0x55, 0x53, 0x45,
+ 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f,
+ 0x72, 0x6b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e,
+ 0x75, 0x73, 0x65, 0x72, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x31, 0x36, 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2d,
+ 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x69, 0x72, 0x73,
+ 0x74, 0x2d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x41, 0x75, 0x74,
+ 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20,
+ 0x61, 0x6e, 0x64, 0x20, 0x45, 0x6d, 0x61, 0x69, 0x6c
+};
+
+// 0:d=0 hl=3 l= 190 cons: SEQUENCE
+// 3:d=1 hl=2 l= 63 cons: SET
+// 5:d=2 hl=2 l= 61 cons: SEQUENCE
+// 7:d=3 hl=2 l= 3 prim: OBJECT :commonName
+// 12:d=3 hl=2 l= 54 prim: UTF8STRING :
+// TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı
+// 68:d=1 hl=2 l= 11 cons: SET
+// 70:d=2 hl=2 l= 9 cons: SEQUENCE
+// 72:d=3 hl=2 l= 3 prim: OBJECT :countryName
+// 77:d=3 hl=2 l= 2 prim: PRINTABLESTRING :TR
+// 81:d=1 hl=2 l= 15 cons: SET
+// 83:d=2 hl=2 l= 13 cons: SEQUENCE
+// 85:d=3 hl=2 l= 3 prim: OBJECT :localityName
+// 90:d=3 hl=2 l= 6 prim: UTF8STRING :Ankara
+// 98:d=1 hl=2 l= 93 cons: SET
+//100:d=2 hl=2 l= 91 cons: SEQUENCE
+//102:d=3 hl=2 l= 3 prim: OBJECT :organizationName
+//107:d=3 hl=2 l= 84 prim: UTF8STRING :
+// TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş.
+// (c) Kasım 2005
+const uint8 VARIABLE_IS_NOT_USED TurkTrustDN[] = {
+ 0x30, 0x81, 0xbe, 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x0c, 0x36, 0x54, 0xc3, 0x9c, 0x52, 0x4b, 0x54, 0x52, 0x55, 0x53, 0x54,
+ 0x20, 0x45, 0x6c, 0x65, 0x6b, 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x6b, 0x20,
+ 0x53, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x6b, 0x61, 0x20, 0x48, 0x69,
+ 0x7a, 0x6d, 0x65, 0x74, 0x20, 0x53, 0x61, 0xc4, 0x9f, 0x6c, 0x61, 0x79,
+ 0xc4, 0xb1, 0x63, 0xc4, 0xb1, 0x73, 0xc4, 0xb1, 0x31, 0x0b, 0x30, 0x09,
+ 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x54, 0x52, 0x31, 0x0f, 0x30,
+ 0x0d, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x06, 0x41, 0x6e, 0x6b, 0x61,
+ 0x72, 0x61, 0x31, 0x5d, 0x30, 0x5b, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c,
+ 0x54, 0x54, 0xc3, 0x9c, 0x52, 0x4b, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20,
+ 0x42, 0x69, 0x6c, 0x67, 0x69, 0x20, 0xc4, 0xb0, 0x6c, 0x65, 0x74, 0x69,
+ 0xc5, 0x9f, 0x69, 0x6d, 0x20, 0x76, 0x65, 0x20, 0x42, 0x69, 0x6c, 0x69,
+ 0xc5, 0x9f, 0x69, 0x6d, 0x20, 0x47, 0xc3, 0xbc, 0x76, 0x65, 0x6e, 0x6c,
+ 0x69, 0xc4, 0x9f, 0x69, 0x20, 0x48, 0x69, 0x7a, 0x6d, 0x65, 0x74, 0x6c,
+ 0x65, 0x72, 0x69, 0x20, 0x41, 0x2e, 0xc5, 0x9e, 0x2e, 0x20, 0x28, 0x63,
+ 0x29, 0x20, 0x4b, 0x61, 0x73, 0xc4, 0xb1, 0x6d, 0x20, 0x32, 0x30, 0x30,
+ 0x35, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x35, 0x31, 0x31, 0x30, 0x37, 0x31,
+ 0x30, 0x30, 0x37, 0x35, 0x37
+};
+
+// 33:d=2 hl=3 l= 207 cons: SEQUENCE
+// 36:d=3 hl=2 l= 11 cons: SET
+// 38:d=4 hl=2 l= 9 cons: SEQUENCE
+// 40:d=5 hl=2 l= 3 prim: OBJECT :countryName
+// 45:d=5 hl=2 l= 2 prim: PRINTABLESTRING :AT
+// 49:d=3 hl=3 l= 139 cons: SET
+// 52:d=4 hl=3 l= 136 cons: SEQUENCE
+// 55:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 60:d=5 hl=3 l= 128 prim: BMPSTRING :
+// A-Trust Ges. für Sicherheitssysteme im elektr. Datenverkehr GmbH
+//191:d=3 hl=2 l= 24 cons: SET
+//193:d=4 hl=2 l= 22 cons: SEQUENCE
+//195:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+//200:d=5 hl=2 l= 15 prim: PRINTABLESTRING :A-Trust-Qual-01
+//217:d=3 hl=2 l= 24 cons: SET
+//219:d=4 hl=2 l= 22 cons: SEQUENCE
+//221:d=5 hl=2 l= 3 prim: OBJECT :commonName
+//226:d=5 hl=2 l= 15 prim: PRINTABLESTRING :A-Trust-Qual-01
+const uint8 VARIABLE_IS_NOT_USED ATrustQual01DN[] = {
+ 0x30, 0x81, 0xcf, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
+ 0x13, 0x02, 0x41, 0x54, 0x31, 0x81, 0x8b, 0x30, 0x81, 0x88, 0x06, 0x03,
+ 0x55, 0x04, 0x0a, 0x1e, 0x81, 0x80, 0x00, 0x41, 0x00, 0x2d, 0x00, 0x54,
+ 0x00, 0x72, 0x00, 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x20, 0x00, 0x47,
+ 0x00, 0x65, 0x00, 0x73, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x66, 0x00, 0xfc,
+ 0x00, 0x72, 0x00, 0x20, 0x00, 0x53, 0x00, 0x69, 0x00, 0x63, 0x00, 0x68,
+ 0x00, 0x65, 0x00, 0x72, 0x00, 0x68, 0x00, 0x65, 0x00, 0x69, 0x00, 0x74,
+ 0x00, 0x73, 0x00, 0x73, 0x00, 0x79, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65,
+ 0x00, 0x6d, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x20,
+ 0x00, 0x65, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x6b, 0x00, 0x74, 0x00, 0x72,
+ 0x00, 0x2e, 0x00, 0x20, 0x00, 0x44, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65,
+ 0x00, 0x6e, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6b, 0x00, 0x65,
+ 0x00, 0x68, 0x00, 0x72, 0x00, 0x20, 0x00, 0x47, 0x00, 0x6d, 0x00, 0x62,
+ 0x00, 0x48, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
+ 0x0f, 0x41, 0x2d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x51, 0x75, 0x61,
+ 0x6c, 0x2d, 0x30, 0x31, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x0f, 0x41, 0x2d, 0x54, 0x72, 0x75, 0x73, 0x74, 0x2d, 0x51,
+ 0x75, 0x61, 0x6c, 0x2d, 0x30, 0x31, 0x30, 0x1e, 0x17
+};
+
+// 34:d=2 hl=3 l= 180 cons: SEQUENCE
+// 37:d=3 hl=2 l= 20 cons: SET
+// 39:d=4 hl=2 l= 18 cons: SEQUENCE
+// 41:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 46:d=5 hl=2 l= 11 prim: PRINTABLESTRING :Entrust.net
+// 59:d=3 hl=2 l= 64 cons: SET
+// 61:d=4 hl=2 l= 62 cons: SEQUENCE
+// 63:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 68:d=5 hl=2 l= 55 prim: T61STRING :
+// www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)
+//125:d=3 hl=2 l= 37 cons: SET
+//127:d=4 hl=2 l= 35 cons: SEQUENCE
+//129:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+//134:d=5 hl=2 l= 28 prim: PRINTABLESTRING :
+// (c) 1999 Entrust.net Limited
+//164:d=3 hl=2 l= 51 cons: SET
+//166:d=4 hl=2 l= 49 cons: SEQUENCE
+//168:d=5 hl=2 l= 3 prim: OBJECT :commonName
+//173:d=5 hl=2 l= 42 prim: PRINTABLESTRING :
+// Entrust.net Certification Authority (2048)
+const uint8 VARIABLE_IS_NOT_USED EntrustDN[] = {
+ 0x30, 0x81, 0xb4, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, 0x0a,
+ 0x13, 0x0b, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65,
+ 0x74, 0x31, 0x40, 0x30, 0x3e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x14, 0x37,
+ 0x77, 0x77, 0x77, 0x2e, 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e,
+ 0x6e, 0x65, 0x74, 0x2f, 0x43, 0x50, 0x53, 0x5f, 0x32, 0x30, 0x34, 0x38,
+ 0x20, 0x69, 0x6e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x20, 0x62, 0x79, 0x20,
+ 0x72, 0x65, 0x66, 0x2e, 0x20, 0x28, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x73,
+ 0x20, 0x6c, 0x69, 0x61, 0x62, 0x2e, 0x29, 0x31, 0x25, 0x30, 0x23, 0x06,
+ 0x03, 0x55, 0x04, 0x0b, 0x13, 0x1c, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39,
+ 0x39, 0x39, 0x20, 0x45, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e,
+ 0x65, 0x74, 0x20, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x31, 0x33,
+ 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x2a, 0x45, 0x6e, 0x74,
+ 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x20, 0x43, 0x65, 0x72,
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x28, 0x32, 0x30,
+ 0x34, 0x38, 0x29
+};
+
+// 46:d=2 hl=2 l= 76 cons: SEQUENCE
+// 48:d=3 hl=2 l= 11 cons: SET
+// 50:d=4 hl=2 l= 9 cons: SEQUENCE
+// 52:d=5 hl=2 l= 3 prim: OBJECT :countryName
+// 57:d=5 hl=2 l= 2 prim: PRINTABLESTRING :ZA
+// 61:d=3 hl=2 l= 37 cons: SET
+// 63:d=4 hl=2 l= 35 cons: SEQUENCE
+// 65:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 70:d=5 hl=2 l= 28 prim: PRINTABLESTRING :
+// Thawte Consulting (Pty) Ltd.
+// 100:d=3 hl=2 l= 22 cons: SET
+// 102:d=4 hl=2 l= 20 cons: SEQUENCE
+// 104:d=5 hl=2 l= 3 prim: OBJECT :commonName
+// 109:d=5 hl=2 l= 13 prim: PRINTABLESTRING :Thawte SGC CA
+const uint8 VARIABLE_IS_NOT_USED ThawteDN[] = {
+ 0x30, 0x4C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x5A, 0x41, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, 0x55, 0x04, 0x0A,
+ 0x13, 0x1C, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20, 0x43, 0x6F, 0x6E,
+ 0x73, 0x75, 0x6C, 0x74, 0x69, 0x6E, 0x67, 0x20, 0x28, 0x50, 0x74, 0x79,
+ 0x29, 0x20, 0x4C, 0x74, 0x64, 0x2E, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x0D, 0x54, 0x68, 0x61, 0x77, 0x74, 0x65, 0x20,
+ 0x53, 0x47, 0x43, 0x20, 0x43, 0x41
+};
+
+// 47:d=2 hl=2 l= 108 cons: SEQUENCE
+// 49:d=3 hl=2 l= 11 cons: SET
+// 51:d=4 hl=2 l= 9 cons: SEQUENCE
+// 53:d=5 hl=2 l= 3 prim: OBJECT :countryName
+// 58:d=5 hl=2 l= 2 prim: PRINTABLESTRING :US
+// 62:d=3 hl=2 l= 22 cons: SET
+// 64:d=4 hl=2 l= 20 cons: SEQUENCE
+// 66:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName
+// 71:d=5 hl=2 l= 13 prim: PRINTABLESTRING :Massachusetts
+// 86:d=3 hl=2 l= 46 cons: SET
+// 88:d=4 hl=2 l= 44 cons: SEQUENCE
+// 90:d=5 hl=2 l= 3 prim: OBJECT :organizationName
+// 95:d=5 hl=2 l= 37 prim: PRINTABLESTRING :
+// Massachusetts Institute of Technology
+// 134:d=3 hl=2 l= 21 cons: SET
+// 136:d=4 hl=2 l= 19 cons: SEQUENCE
+// 138:d=5 hl=2 l= 3 prim: OBJECT :organizationalUnitName
+// 143:d=5 hl=2 l= 12 prim: PRINTABLESTRING :Client CA v1
+const uint8 VARIABLE_IS_NOT_USED MITDN[] = {
+ 0x30, 0x6C, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x0D, 0x4D, 0x61, 0x73, 0x73, 0x61, 0x63, 0x68, 0x75, 0x73, 0x65,
+ 0x74, 0x74, 0x73, 0x31, 0x2E, 0x30, 0x2C, 0x06, 0x03, 0x55, 0x04, 0x0A,
+ 0x13, 0x25, 0x4D, 0x61, 0x73, 0x73, 0x61, 0x63, 0x68, 0x75, 0x73, 0x65,
+ 0x74, 0x74, 0x73, 0x20, 0x49, 0x6E, 0x73, 0x74, 0x69, 0x74, 0x75, 0x74,
+ 0x65, 0x20, 0x6F, 0x66, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6E, 0x6F, 0x6C,
+ 0x6F, 0x67, 0x79, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0B,
+ 0x13, 0x0C, 0x43, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x43, 0x41, 0x20,
+ 0x76, 0x31
+};
+
+} // namespace
diff --git a/chromium/net/third_party/mozilla_security_manager/LICENSE b/chromium/net/third_party/mozilla_security_manager/LICENSE
new file mode 100644
index 00000000000..17de8fbeb1b
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/LICENSE
@@ -0,0 +1,35 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
diff --git a/chromium/net/third_party/mozilla_security_manager/README.chromium b/chromium/net/third_party/mozilla_security_manager/README.chromium
new file mode 100644
index 00000000000..2c4039fb6aa
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/README.chromium
@@ -0,0 +1,17 @@
+Name: Mozilla Personal Security Manager
+URL: http://mxr.mozilla.org/mozilla1.9.2/source/security/manager/
+InfoURL: http://www.mozilla.org/
+Version: Mozilla 1.9.2
+License: MPL 1.1 / GPL 2.0 / LGPL 2.1
+License File: NOT_SHIPPED
+
+Description:
+This is selected code bits from Mozilla's Personal Security Manager.
+
+The same module appears in chrome/third_party/mozilla_security_manager, so we
+don't repeat the license file here.
+
+Local Modifications:
+Files are forked from Mozilla's because of the heavy adaptations necessary.
+Differences are using Chromium localization and other libraries instead of
+Mozilla's, matching the Chromium style, etc.
diff --git a/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp b/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp
new file mode 100644
index 00000000000..4cf47c42c17
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.cpp
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Vipul Gupta <vipul.gupta@sun.com>
+ * Douglas Stebila <douglas@stebila.ca>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/third_party/mozilla_security_manager/nsKeygenHandler.h"
+
+#include <pk11pub.h>
+#include <prerror.h> // PR_GetError()
+#include <secmod.h>
+#include <secder.h> // DER_Encode()
+#include <cryptohi.h> // SEC_DerSignData()
+#include <keyhi.h> // SECKEY_CreateSubjectPublicKeyInfo()
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "crypto/nss_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+// Template for creating the signed public key structure to be sent to the CA.
+DERTemplate SECAlgorithmIDTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(SECAlgorithmID) },
+ { DER_OBJECT_ID,
+ offsetof(SECAlgorithmID, algorithm), },
+ { DER_OPTIONAL | DER_ANY,
+ offsetof(SECAlgorithmID, parameters), },
+ { 0, }
+};
+
+DERTemplate CERTSubjectPublicKeyInfoTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(CERTSubjectPublicKeyInfo) },
+ { DER_INLINE,
+ offsetof(CERTSubjectPublicKeyInfo, algorithm),
+ SECAlgorithmIDTemplate, },
+ { DER_BIT_STRING,
+ offsetof(CERTSubjectPublicKeyInfo, subjectPublicKey), },
+ { 0, }
+};
+
+DERTemplate CERTPublicKeyAndChallengeTemplate[] = {
+ { DER_SEQUENCE,
+ 0, NULL, sizeof(CERTPublicKeyAndChallenge) },
+ { DER_ANY,
+ offsetof(CERTPublicKeyAndChallenge, spki), },
+ { DER_IA5_STRING,
+ offsetof(CERTPublicKeyAndChallenge, challenge), },
+ { 0, }
+};
+
+} // namespace
+
+namespace mozilla_security_manager {
+
+// This function is based on the nsKeygenFormProcessor::GetPublicKey function
+// in mozilla/security/manager/ssl/src/nsKeygenHandler.cpp.
+std::string GenKeyAndSignChallenge(int key_size_in_bits,
+ const std::string& challenge,
+ const GURL& url,
+ PK11SlotInfo* slot,
+ bool stores_key) {
+ // Key pair generation mechanism - only RSA is supported at present.
+ PRUint32 keyGenMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; // from nss/pkcs11t.h
+
+ // Temporary structures used for generating the result
+ // in the right format.
+ PK11RSAGenParams rsaKeyGenParams; // Keygen parameters.
+ SECOidTag algTag; // used by SEC_DerSignData().
+ SECKEYPrivateKey *privateKey = NULL;
+ SECKEYPublicKey *publicKey = NULL;
+ CERTSubjectPublicKeyInfo *spkInfo = NULL;
+ PLArenaPool *arena = NULL;
+ SECStatus sec_rv =SECFailure;
+ SECItem spkiItem;
+ SECItem pkacItem;
+ SECItem signedItem;
+ CERTPublicKeyAndChallenge pkac;
+ void *keyGenParams;
+ bool isSuccess = true; // Set to false as soon as a step fails.
+
+ std::string result_blob; // the result.
+
+ switch (keyGenMechanism) {
+ case CKM_RSA_PKCS_KEY_PAIR_GEN:
+ rsaKeyGenParams.keySizeInBits = key_size_in_bits;
+ rsaKeyGenParams.pe = DEFAULT_RSA_KEYGEN_PE;
+ keyGenParams = &rsaKeyGenParams;
+
+ algTag = DEFAULT_RSA_KEYGEN_ALG;
+ break;
+ default:
+ // TODO(gauravsh): If we ever support other mechanisms,
+ // this can be changed.
+ LOG(ERROR) << "Only RSA keygen mechanism is supported";
+ isSuccess = false;
+ goto failure;
+ }
+
+ VLOG(1) << "Creating key pair...";
+ {
+ crypto::AutoNSSWriteLock lock;
+ privateKey = PK11_GenerateKeyPair(slot,
+ keyGenMechanism,
+ keyGenParams,
+ &publicKey,
+ PR_TRUE, // isPermanent?
+ PR_TRUE, // isSensitive?
+ NULL);
+ }
+ VLOG(1) << "done.";
+
+ if (!privateKey) {
+ LOG(ERROR) << "Generation of Keypair failed!";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Set friendly names for the keys.
+ if (url.has_host()) {
+ // TODO(davidben): Use something like "Key generated for
+ // example.com", but localize it.
+ const std::string& label = url.host();
+ {
+ crypto::AutoNSSWriteLock lock;
+ PK11_SetPublicKeyNickname(publicKey, label.c_str());
+ PK11_SetPrivateKeyNickname(privateKey, label.c_str());
+ }
+ }
+
+ // The CA expects the signed public key in a specific format
+ // Let's create that now.
+
+ // Create a subject public key info from the public key.
+ spkInfo = SECKEY_CreateSubjectPublicKeyInfo(publicKey);
+ if (!spkInfo) {
+ LOG(ERROR) << "Couldn't create SubjectPublicKeyInfo from public key";
+ isSuccess = false;
+ goto failure;
+ }
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena) {
+ LOG(ERROR) << "PORT_NewArena: Couldn't allocate memory";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // DER encode the whole subjectPublicKeyInfo.
+ sec_rv = DER_Encode(arena, &spkiItem, CERTSubjectPublicKeyInfoTemplate,
+ spkInfo);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't DER Encode subjectPublicKeyInfo";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Set up the PublicKeyAndChallenge data structure, then DER encode it.
+ pkac.spki = spkiItem;
+ pkac.challenge.type = siBuffer;
+ pkac.challenge.len = challenge.length();
+ pkac.challenge.data = (unsigned char *)challenge.data();
+ sec_rv = DER_Encode(arena, &pkacItem, CERTPublicKeyAndChallengeTemplate,
+ &pkac);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't DER Encode PublicKeyAndChallenge";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Sign the DER encoded PublicKeyAndChallenge.
+ sec_rv = SEC_DerSignData(arena, &signedItem, pkacItem.data, pkacItem.len,
+ privateKey, algTag);
+ if (SECSuccess != sec_rv) {
+ LOG(ERROR) << "Couldn't sign the DER encoded PublicKeyandChallenge";
+ isSuccess = false;
+ goto failure;
+ }
+
+ // Convert the signed public key and challenge into base64/ascii.
+ if (!base::Base64Encode(std::string(reinterpret_cast<char*>(signedItem.data),
+ signedItem.len),
+ &result_blob)) {
+ LOG(ERROR) << "Couldn't convert signed public key into base64";
+ isSuccess = false;
+ goto failure;
+ }
+
+ failure:
+ if (!isSuccess) {
+ LOG(ERROR) << "SSL Keygen failed! (NSS error code " << PR_GetError() << ")";
+ } else {
+ VLOG(1) << "SSL Keygen succeeded!";
+ }
+
+ // Do cleanups
+ if (privateKey) {
+ // On successful keygen we need to keep the private key, of course,
+ // or we won't be able to use the client certificate.
+ if (!isSuccess || !stores_key) {
+ crypto::AutoNSSWriteLock lock;
+ PK11_DestroyTokenObject(privateKey->pkcs11Slot, privateKey->pkcs11ID);
+ }
+ SECKEY_DestroyPrivateKey(privateKey);
+ }
+
+ if (publicKey) {
+ if (!isSuccess || !stores_key) {
+ crypto::AutoNSSWriteLock lock;
+ PK11_DestroyTokenObject(publicKey->pkcs11Slot, publicKey->pkcs11ID);
+ }
+ SECKEY_DestroyPublicKey(publicKey);
+ }
+ if (spkInfo) {
+ SECKEY_DestroySubjectPublicKeyInfo(spkInfo);
+ }
+ if (arena) {
+ PORT_FreeArena(arena, PR_TRUE);
+ }
+
+ return (isSuccess ? result_blob : std::string());
+}
+
+} // namespace mozilla_security_manager
diff --git a/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.h b/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.h
new file mode 100644
index 00000000000..4c20e561710
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsKeygenHandler.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * David Drinan. (ddrinan@netscape.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _NSKEYGENHANDLER_H_
+#define _NSKEYGENHANDLER_H_
+
+#include <string>
+
+class GURL;
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+
+namespace mozilla_security_manager {
+
+#define DEFAULT_RSA_KEYGEN_PE 65537L
+#define DEFAULT_RSA_KEYGEN_ALG SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION
+
+// Generates the key pair and the cert request (SPKAC), and returns a
+// base64-encoded string suitable for use as the form value of <keygen>.
+// Parameters:
+// key_size_in_bits: key size in bits (usually 2048)
+// challenge: challenge string sent by server
+// url: the URL which requested the SPKAC
+// slot: a slot to generate the key in, should be authenticated
+// stores_key: should the generated key pair be stored persistently?
+std::string GenKeyAndSignChallenge(int key_size_in_bits,
+ const std::string& challenge,
+ const GURL& url,
+ PK11SlotInfo* slot,
+ bool stores_key);
+
+} // namespace mozilla_security_manager
+
+#endif //_NSKEYGENHANDLER_H_
diff --git a/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.cpp b/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.cpp
new file mode 100644
index 00000000000..6db75f3662a
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.cpp
@@ -0,0 +1,287 @@
+ /* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ian McGreer <mcgreer@netscape.com>
+ * Javier Delgadillo <javi@netscape.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
+
+#include <cert.h>
+#include <certdb.h>
+#include <pk11pub.h>
+#include <secerr.h>
+
+#include "base/logging.h"
+#include "crypto/nss_util_internal.h"
+#include "crypto/scoped_nss_types.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util_nss.h"
+
+#if !defined(CERTDB_TERMINAL_RECORD)
+/* NSS 3.13 renames CERTDB_VALID_PEER to CERTDB_TERMINAL_RECORD
+ * and marks CERTDB_VALID_PEER as deprecated.
+ * If we're using an older version, rename it ourselves.
+ */
+#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
+#endif
+
+namespace mozilla_security_manager {
+
+// Based on nsNSSCertificateDB::handleCACertDownload, minus the UI bits.
+bool ImportCACerts(const net::CertificateList& certificates,
+ net::X509Certificate* root,
+ net::NSSCertDatabase::TrustBits trustBits,
+ net::NSSCertDatabase::ImportCertFailureList* not_imported) {
+ if (certificates.empty() || !root)
+ return false;
+
+ crypto::ScopedPK11Slot slot(crypto::GetPublicNSSKeySlot());
+ if (!slot.get()) {
+ LOG(ERROR) << "Couldn't get internal key slot!";
+ return false;
+ }
+
+ // Mozilla had some code here to check if a perm version of the cert exists
+ // already and use that, but CERT_NewTempCertificate actually does that
+ // itself, so we skip it here.
+
+ if (!CERT_IsCACert(root->os_cert_handle(), NULL)) {
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ root, net::ERR_IMPORT_CA_CERT_NOT_CA));
+ } else if (root->os_cert_handle()->isperm) {
+ // Mozilla just returns here, but we continue in case there are other certs
+ // in the list which aren't already imported.
+ // TODO(mattm): should we set/add trust if it differs from the present
+ // settings?
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ root, net::ERR_IMPORT_CERT_ALREADY_EXISTS));
+ } else {
+ // Mozilla uses CERT_AddTempCertToPerm, however it is privately exported,
+ // and it doesn't take the slot as an argument either. Instead, we use
+ // PK11_ImportCert and CERT_ChangeCertTrust.
+ SECStatus srv = PK11_ImportCert(
+ slot.get(),
+ root->os_cert_handle(),
+ CK_INVALID_HANDLE,
+ net::x509_util::GetUniqueNicknameForSlot(
+ root->GetDefaultNickname(net::CA_CERT),
+ &root->os_cert_handle()->derSubject,
+ slot.get()).c_str(),
+ PR_FALSE /* includeTrust (unused) */);
+ if (srv != SECSuccess) {
+ LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
+ return false;
+ }
+ if (!SetCertTrust(root, net::CA_CERT, trustBits))
+ return false;
+ }
+
+ PRTime now = PR_Now();
+ // Import additional delivered certificates that can be verified.
+ // This is sort of merged in from Mozilla's ImportValidCACertsInList. Mozilla
+ // uses CERT_FilterCertListByUsage to filter out non-ca certs, but we want to
+ // keep using X509Certificates, so that we can use them to build the
+ // |not_imported| result. So, we keep using our net::CertificateList and
+ // filter it ourself.
+ for (size_t i = 0; i < certificates.size(); i++) {
+ const scoped_refptr<net::X509Certificate>& cert = certificates[i];
+ if (cert == root) {
+ // we already processed that one
+ continue;
+ }
+
+ // Mozilla uses CERT_FilterCertListByUsage(certList, certUsageAnyCA,
+ // PR_TRUE). Afaict, checking !CERT_IsCACert on each cert is equivalent.
+ if (!CERT_IsCACert(cert->os_cert_handle(), NULL)) {
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ cert, net::ERR_IMPORT_CA_CERT_NOT_CA));
+ VLOG(1) << "skipping cert (non-ca)";
+ continue;
+ }
+
+ if (cert->os_cert_handle()->isperm) {
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ cert, net::ERR_IMPORT_CERT_ALREADY_EXISTS));
+ VLOG(1) << "skipping cert (perm)";
+ continue;
+ }
+
+ if (CERT_VerifyCert(CERT_GetDefaultCertDB(), cert->os_cert_handle(),
+ PR_TRUE, certUsageVerifyCA, now, NULL, NULL) != SECSuccess) {
+ // TODO(mattm): use better error code (map PORT_GetError to an appropriate
+ // error value). (maybe make MapSecurityError or MapCertErrorToCertStatus
+ // public.)
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ cert, net::ERR_FAILED));
+ VLOG(1) << "skipping cert (verify) " << PORT_GetError();
+ continue;
+ }
+
+ // Mozilla uses CERT_ImportCerts, which doesn't take a slot arg. We use
+ // PK11_ImportCert instead.
+ SECStatus srv = PK11_ImportCert(
+ slot.get(),
+ cert->os_cert_handle(),
+ CK_INVALID_HANDLE,
+ net::x509_util::GetUniqueNicknameForSlot(
+ cert->GetDefaultNickname(net::CA_CERT),
+ &cert->os_cert_handle()->derSubject,
+ slot.get()).c_str(),
+ PR_FALSE /* includeTrust (unused) */);
+ if (srv != SECSuccess) {
+ LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
+ // TODO(mattm): Should we bail or continue on error here? Mozilla doesn't
+ // check error code at all.
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ cert, net::ERR_IMPORT_CA_CERT_FAILED));
+ }
+ }
+
+ // Any errors importing individual certs will be in listed in |not_imported|.
+ return true;
+}
+
+// Based on nsNSSCertificateDB::ImportServerCertificate.
+bool ImportServerCert(
+ const net::CertificateList& certificates,
+ net::NSSCertDatabase::TrustBits trustBits,
+ net::NSSCertDatabase::ImportCertFailureList* not_imported) {
+ if (certificates.empty())
+ return false;
+
+ crypto::ScopedPK11Slot slot(crypto::GetPublicNSSKeySlot());
+ if (!slot.get()) {
+ LOG(ERROR) << "Couldn't get internal key slot!";
+ return false;
+ }
+
+ for (size_t i = 0; i < certificates.size(); ++i) {
+ const scoped_refptr<net::X509Certificate>& cert = certificates[i];
+
+ // Mozilla uses CERT_ImportCerts, which doesn't take a slot arg. We use
+ // PK11_ImportCert instead.
+ SECStatus srv = PK11_ImportCert(
+ slot.get(),
+ cert->os_cert_handle(),
+ CK_INVALID_HANDLE,
+ net::x509_util::GetUniqueNicknameForSlot(
+ cert->GetDefaultNickname(net::SERVER_CERT),
+ &cert->os_cert_handle()->derSubject,
+ slot.get()).c_str(),
+ PR_FALSE /* includeTrust (unused) */);
+ if (srv != SECSuccess) {
+ LOG(ERROR) << "PK11_ImportCert failed with error " << PORT_GetError();
+ not_imported->push_back(net::NSSCertDatabase::ImportCertFailure(
+ cert, net::ERR_IMPORT_SERVER_CERT_FAILED));
+ continue;
+ }
+ }
+
+ SetCertTrust(certificates[0].get(), net::SERVER_CERT, trustBits);
+ // TODO(mattm): Report SetCertTrust result? Putting in not_imported
+ // wouldn't quite match up since it was imported...
+
+ // Any errors importing individual certs will be in listed in |not_imported|.
+ return true;
+}
+
+// Based on nsNSSCertificateDB::SetCertTrust.
+bool
+SetCertTrust(const net::X509Certificate* cert,
+ net::CertType type,
+ net::NSSCertDatabase::TrustBits trustBits)
+{
+ const unsigned kSSLTrustBits = net::NSSCertDatabase::TRUSTED_SSL |
+ net::NSSCertDatabase::DISTRUSTED_SSL;
+ const unsigned kEmailTrustBits = net::NSSCertDatabase::TRUSTED_EMAIL |
+ net::NSSCertDatabase::DISTRUSTED_EMAIL;
+ const unsigned kObjSignTrustBits = net::NSSCertDatabase::TRUSTED_OBJ_SIGN |
+ net::NSSCertDatabase::DISTRUSTED_OBJ_SIGN;
+ if ((trustBits & kSSLTrustBits) == kSSLTrustBits ||
+ (trustBits & kEmailTrustBits) == kEmailTrustBits ||
+ (trustBits & kObjSignTrustBits) == kObjSignTrustBits) {
+ LOG(ERROR) << "SetCertTrust called with conflicting trust bits "
+ << trustBits;
+ NOTREACHED();
+ return false;
+ }
+
+ SECStatus srv;
+ CERTCertificate *nsscert = cert->os_cert_handle();
+ if (type == net::CA_CERT) {
+ // Note that we start with CERTDB_VALID_CA for default trust and explicit
+ // trust, but explicitly distrusted usages will be set to
+ // CERTDB_TERMINAL_RECORD only.
+ CERTCertTrust trust = {CERTDB_VALID_CA, CERTDB_VALID_CA, CERTDB_VALID_CA};
+
+ if (trustBits & net::NSSCertDatabase::DISTRUSTED_SSL)
+ trust.sslFlags = CERTDB_TERMINAL_RECORD;
+ else if (trustBits & net::NSSCertDatabase::TRUSTED_SSL)
+ trust.sslFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
+
+ if (trustBits & net::NSSCertDatabase::DISTRUSTED_EMAIL)
+ trust.emailFlags = CERTDB_TERMINAL_RECORD;
+ else if (trustBits & net::NSSCertDatabase::TRUSTED_EMAIL)
+ trust.emailFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
+
+ if (trustBits & net::NSSCertDatabase::DISTRUSTED_OBJ_SIGN)
+ trust.objectSigningFlags = CERTDB_TERMINAL_RECORD;
+ else if (trustBits & net::NSSCertDatabase::TRUSTED_OBJ_SIGN)
+ trust.objectSigningFlags |= CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
+
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nsscert, &trust);
+ } else if (type == net::SERVER_CERT) {
+ CERTCertTrust trust = {0};
+ // We only modify the sslFlags, so copy the other flags.
+ CERT_GetCertTrust(nsscert, &trust);
+ trust.sslFlags = 0;
+
+ if (trustBits & net::NSSCertDatabase::DISTRUSTED_SSL)
+ trust.sslFlags |= CERTDB_TERMINAL_RECORD;
+ else if (trustBits & net::NSSCertDatabase::TRUSTED_SSL)
+ trust.sslFlags |= CERTDB_TRUSTED | CERTDB_TERMINAL_RECORD;
+
+ srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nsscert, &trust);
+ } else {
+ // ignore user and email/unknown certs
+ return true;
+ }
+ if (srv != SECSuccess)
+ LOG(ERROR) << "SetCertTrust failed with error " << PORT_GetError();
+ return srv == SECSuccess;
+}
+
+} // namespace mozilla_security_manager
diff --git a/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.h b/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.h
new file mode 100644
index 00000000000..aac991255f0
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsNSSCertificateDB.h
@@ -0,0 +1,71 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ian McGreer <mcgreer@netscape.com>
+ * Javier Delgadillo <javi@netscape.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSNSSCERTIFICATEDB_H_
+#define NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSNSSCERTIFICATEDB_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "net/cert/nss_cert_database.h"
+
+typedef struct CERTCertificateStr CERTCertificate;
+namespace net {
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+} // namespace net
+
+namespace mozilla_security_manager {
+
+bool ImportCACerts(const net::CertificateList& certificates,
+ net::X509Certificate* root,
+ net::NSSCertDatabase::TrustBits trustBits,
+ net::NSSCertDatabase::ImportCertFailureList* not_imported);
+
+bool ImportServerCert(
+ const net::CertificateList& certificates,
+ net::NSSCertDatabase::TrustBits trustBits,
+ net::NSSCertDatabase::ImportCertFailureList* not_imported);
+
+bool SetCertTrust(const net::X509Certificate* cert,
+ net::CertType type,
+ net::NSSCertDatabase::TrustBits trustBits);
+
+} // namespace mozilla_security_manager
+
+#endif // NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSNSSCERTIFICATEDB_H_
diff --git a/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.cpp b/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.cpp
new file mode 100644
index 00000000000..793d8f73a74
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.cpp
@@ -0,0 +1,486 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ian McGreer <mcgreer@netscape.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
+
+#include <pk11pub.h>
+#include <pkcs12.h>
+#include <p12plcy.h>
+#include <secerr.h>
+
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "crypto/nss_util_internal.h"
+#include "net/base/net_errors.h"
+#include "net/cert/x509_certificate.h"
+
+namespace mozilla_security_manager {
+
+namespace {
+
+// unicodeToItem
+//
+// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to
+// a buffer of octets. Must handle byte order correctly.
+// TODO: Is there a Mozilla way to do this? In the string lib?
+void unicodeToItem(const PRUnichar *uni, SECItem *item)
+{
+ int len = 0;
+ while (uni[len++] != 0);
+ SECITEM_AllocItem(NULL, item, sizeof(PRUnichar) * len);
+#ifdef IS_LITTLE_ENDIAN
+ int i = 0;
+ for (i=0; i<len; i++) {
+ item->data[2*i ] = (unsigned char )(uni[i] << 8);
+ item->data[2*i+1] = (unsigned char )(uni[i]);
+ }
+#else
+ memcpy(item->data, uni, item->len);
+#endif
+}
+
+// write_export_data
+// write bytes to the exported PKCS#12 data buffer
+void write_export_data(void* arg, const char* buf, unsigned long len) {
+ std::string* dest = reinterpret_cast<std::string*>(arg);
+ dest->append(buf, len);
+}
+
+// nickname_collision
+// what to do when the nickname collides with one already in the db.
+// Based on P12U_NicknameCollisionCallback from nss/cmd/pk12util/pk12util.c
+SECItem* PR_CALLBACK
+nickname_collision(SECItem *old_nick, PRBool *cancel, void *wincx)
+{
+ char *nick = NULL;
+ SECItem *ret_nick = NULL;
+ CERTCertificate* cert = (CERTCertificate*)wincx;
+
+ if (!cancel || !cert) {
+ // pk12util calls this error user cancelled?
+ return NULL;
+ }
+
+ if (!old_nick)
+ VLOG(1) << "no nickname for cert in PKCS12 file.";
+
+ nick = CERT_MakeCANickname(cert);
+ if (!nick) {
+ return NULL;
+ }
+
+ if(old_nick && old_nick->data && old_nick->len &&
+ PORT_Strlen(nick) == old_nick->len &&
+ !PORT_Strncmp((char *)old_nick->data, nick, old_nick->len)) {
+ PORT_Free(nick);
+ PORT_SetError(SEC_ERROR_IO);
+ return NULL;
+ }
+
+ VLOG(1) << "using nickname " << nick;
+ ret_nick = PORT_ZNew(SECItem);
+ if(ret_nick == NULL) {
+ PORT_Free(nick);
+ return NULL;
+ }
+
+ ret_nick->data = (unsigned char *)nick;
+ ret_nick->len = PORT_Strlen(nick);
+
+ return ret_nick;
+}
+
+// pip_ucs2_ascii_conversion_fn
+// required to be set by NSS (to do PKCS#12), but since we've already got
+// unicode make this a no-op.
+PRBool
+pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
+ unsigned char *inBuf,
+ unsigned int inBufLen,
+ unsigned char *outBuf,
+ unsigned int maxOutBufLen,
+ unsigned int *outBufLen,
+ PRBool swapBytes)
+{
+ CHECK_GE(maxOutBufLen, inBufLen);
+ // do a no-op, since I've already got Unicode. Hah!
+ *outBufLen = inBufLen;
+ memcpy(outBuf, inBuf, inBufLen);
+ return PR_TRUE;
+}
+
+// Based on nsPKCS12Blob::ImportFromFileHelper.
+int
+nsPKCS12Blob_ImportHelper(const char* pkcs12_data,
+ size_t pkcs12_len,
+ const base::string16& password,
+ bool is_extractable,
+ bool try_zero_length_secitem,
+ PK11SlotInfo *slot,
+ net::CertificateList* imported_certs)
+{
+ DCHECK(pkcs12_data);
+ DCHECK(slot);
+ int import_result = net::ERR_PKCS12_IMPORT_FAILED;
+ SECStatus srv = SECSuccess;
+ SEC_PKCS12DecoderContext *dcx = NULL;
+ SECItem unicodePw;
+ SECItem attribute_value;
+ CK_BBOOL attribute_data = CK_FALSE;
+ const SEC_PKCS12DecoderItem* decoder_item = NULL;
+
+ unicodePw.type = siBuffer;
+ unicodePw.len = 0;
+ unicodePw.data = NULL;
+ if (!try_zero_length_secitem) {
+ unicodeToItem(password.c_str(), &unicodePw);
+ }
+
+ // Initialize the decoder
+ dcx = SEC_PKCS12DecoderStart(&unicodePw, slot,
+ // wincx
+ NULL,
+ // dOpen, dClose, dRead, dWrite, dArg: NULL
+ // specifies default impl using memory buffer.
+ NULL, NULL, NULL, NULL, NULL);
+ if (!dcx) {
+ srv = SECFailure;
+ goto finish;
+ }
+ // feed input to the decoder
+ srv = SEC_PKCS12DecoderUpdate(dcx,
+ (unsigned char*)pkcs12_data,
+ pkcs12_len);
+ if (srv) goto finish;
+ // verify the blob
+ srv = SEC_PKCS12DecoderVerify(dcx);
+ if (srv) goto finish;
+ // validate bags
+ srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision);
+ if (srv) goto finish;
+ // import certificate and key
+ srv = SEC_PKCS12DecoderImportBags(dcx);
+ if (srv) goto finish;
+
+ attribute_value.data = &attribute_data;
+ attribute_value.len = sizeof(attribute_data);
+
+ srv = SEC_PKCS12DecoderIterateInit(dcx);
+ if (srv) goto finish;
+
+ if (imported_certs)
+ imported_certs->clear();
+
+ // Collect the list of decoded certificates, and mark private keys
+ // non-extractable if needed.
+ while (SEC_PKCS12DecoderIterateNext(dcx, &decoder_item) == SECSuccess) {
+ if (decoder_item->type != SEC_OID_PKCS12_V1_CERT_BAG_ID)
+ continue;
+
+ CERTCertificate* cert = PK11_FindCertFromDERCertItem(
+ slot, decoder_item->der,
+ NULL); // wincx
+ if (!cert) {
+ LOG(ERROR) << "Could not grab a handle to the certificate in the slot "
+ << "from the corresponding PKCS#12 DER certificate.";
+ continue;
+ }
+
+ // Add the cert to the list
+ if (imported_certs) {
+ // Empty list of intermediates.
+ net::X509Certificate::OSCertHandles intermediates;
+ imported_certs->push_back(
+ net::X509Certificate::CreateFromHandle(cert, intermediates));
+ }
+
+ // Once we have determined that the imported certificate has an
+ // associated private key too, only then can we mark the key as
+ // non-extractable.
+ if (!decoder_item->hasKey) {
+ CERT_DestroyCertificate(cert);
+ continue;
+ }
+
+ // Iterate through all the imported PKCS12 items and mark any accompanying
+ // private keys as non-extractable.
+ if (!is_extractable) {
+ SECKEYPrivateKey* privKey = PK11_FindPrivateKeyFromCert(slot, cert,
+ NULL); // wincx
+ if (privKey) {
+ // Mark the private key as non-extractable.
+ srv = PK11_WriteRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE,
+ &attribute_value);
+ SECKEY_DestroyPrivateKey(privKey);
+ if (srv) {
+ LOG(ERROR) << "Could not set CKA_EXTRACTABLE attribute on private "
+ << "key.";
+ CERT_DestroyCertificate(cert);
+ break;
+ }
+ }
+ }
+ CERT_DestroyCertificate(cert);
+ if (srv) goto finish;
+ }
+ import_result = net::OK;
+finish:
+ // If srv != SECSuccess, NSS probably set a specific error code.
+ // We should use that error code instead of inventing a new one
+ // for every error possible.
+ if (srv != SECSuccess) {
+ int error = PORT_GetError();
+ LOG(ERROR) << "PKCS#12 import failed with error " << error;
+ switch (error) {
+ case SEC_ERROR_BAD_PASSWORD:
+ case SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT:
+ import_result = net::ERR_PKCS12_IMPORT_BAD_PASSWORD;
+ break;
+ case SEC_ERROR_PKCS12_INVALID_MAC:
+ import_result = net::ERR_PKCS12_IMPORT_INVALID_MAC;
+ break;
+ case SEC_ERROR_BAD_DER:
+ case SEC_ERROR_PKCS12_DECODING_PFX:
+ case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
+ import_result = net::ERR_PKCS12_IMPORT_INVALID_FILE;
+ break;
+ case SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM:
+ case SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE:
+ case SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM:
+ case SEC_ERROR_PKCS12_UNSUPPORTED_VERSION:
+ import_result = net::ERR_PKCS12_IMPORT_UNSUPPORTED;
+ break;
+ default:
+ import_result = net::ERR_PKCS12_IMPORT_FAILED;
+ break;
+ }
+ }
+ // Finish the decoder
+ if (dcx)
+ SEC_PKCS12DecoderFinish(dcx);
+ SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
+ return import_result;
+}
+
+
+// Attempt to read the CKA_EXTRACTABLE attribute on a private key inside
+// a token. On success, store the attribute in |extractable| and return
+// SECSuccess.
+SECStatus
+isExtractable(SECKEYPrivateKey *privKey, PRBool *extractable)
+{
+ SECItem value;
+ SECStatus rv;
+
+ rv=PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value);
+ if (rv != SECSuccess)
+ return rv;
+
+ if ((value.len == 1) && (value.data != NULL))
+ *extractable = !!(*(CK_BBOOL*)value.data);
+ else
+ rv = SECFailure;
+ SECITEM_FreeItem(&value, PR_FALSE);
+ return rv;
+}
+
+class PKCS12InitSingleton {
+ public:
+ // From the PKCS#12 section of nsNSSComponent::InitializeNSS in
+ // nsNSSComponent.cpp.
+ PKCS12InitSingleton() {
+ // Enable ciphers for PKCS#12
+ SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
+ SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
+ SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
+ SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
+ SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
+ SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
+ SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
+
+ // Set no-op ascii-ucs2 conversion function to work around weird NSS
+ // interface. Thankfully, PKCS12 appears to be the only thing in NSS that
+ // uses PORT_UCS2_ASCIIConversion, so this doesn't break anything else.
+ PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
+ }
+};
+
+static base::LazyInstance<PKCS12InitSingleton> g_pkcs12_init_singleton =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+void EnsurePKCS12Init() {
+ g_pkcs12_init_singleton.Get();
+}
+
+// Based on nsPKCS12Blob::ImportFromFile.
+int nsPKCS12Blob_Import(PK11SlotInfo* slot,
+ const char* pkcs12_data,
+ size_t pkcs12_len,
+ const base::string16& password,
+ bool is_extractable,
+ net::CertificateList* imported_certs) {
+
+ int rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
+ is_extractable, false, slot,
+ imported_certs);
+
+ // When the user entered a zero length password:
+ // An empty password should be represented as an empty
+ // string (a SECItem that contains a single terminating
+ // NULL UTF16 character), but some applications use a
+ // zero length SECItem.
+ // We try both variations, zero length item and empty string,
+ // without giving a user prompt when trying the different empty password
+ // flavors.
+ if (rv == net::ERR_PKCS12_IMPORT_BAD_PASSWORD && password.empty()) {
+ rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password,
+ is_extractable, true, slot, imported_certs);
+ }
+ return rv;
+}
+
+// Based on nsPKCS12Blob::ExportToFile
+//
+// Having already loaded the certs, form them into a blob (loading the keys
+// also), encode the blob, and stuff it into the file.
+//
+// TODO: handle slots correctly
+// mirror "slotToUse" behavior from PSM 1.x
+// verify the cert array to start off with?
+// set appropriate error codes
+int
+nsPKCS12Blob_Export(std::string* output,
+ const net::CertificateList& certs,
+ const base::string16& password)
+{
+ int return_count = 0;
+ SECStatus srv = SECSuccess;
+ SEC_PKCS12ExportContext *ecx = NULL;
+ SEC_PKCS12SafeInfo *certSafe = NULL, *keySafe = NULL;
+ SECItem unicodePw;
+ unicodePw.type = siBuffer;
+ unicodePw.len = 0;
+ unicodePw.data = NULL;
+
+ int numCertsExported = 0;
+
+ // get file password (unicode)
+ unicodeToItem(password.c_str(), &unicodePw);
+
+ // what about slotToUse in psm 1.x ???
+ // create export context
+ ecx = SEC_PKCS12CreateExportContext(NULL, NULL, NULL /*slot*/, NULL);
+ if (!ecx) {
+ srv = SECFailure;
+ goto finish;
+ }
+ // add password integrity
+ srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1);
+ if (srv) goto finish;
+
+ for (size_t i=0; i<certs.size(); i++) {
+ DCHECK(certs[i].get());
+ CERTCertificate* nssCert = certs[i]->os_cert_handle();
+ DCHECK(nssCert);
+
+ // We only allow certificate and private key extraction if the corresponding
+ // CKA_EXTRACTABLE private key attribute is set to CK_TRUE. Most hardware
+ // tokens including smartcards enforce this behavior. An internal (soft)
+ // token may ignore this attribute (and hence still be able to export) but
+ // we still refuse to attempt an export.
+ // In addition, some tokens may not support this attribute, in which case
+ // we still attempt the export and let the token implementation dictate
+ // the export behavior.
+ if (nssCert->slot) {
+ SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot,
+ nssCert,
+ NULL); // wincx
+ if (privKey) {
+ PRBool privKeyIsExtractable = PR_FALSE;
+ SECStatus rv = isExtractable(privKey, &privKeyIsExtractable);
+ SECKEY_DestroyPrivateKey(privKey);
+
+ if (rv == SECSuccess && !privKeyIsExtractable) {
+ LOG(ERROR) << "Private key is not extractable";
+ continue;
+ }
+ }
+ }
+
+ // XXX this is why, to verify the slot is the same
+ // PK11_FindObjectForCert(nssCert, NULL, slot);
+ // create the cert and key safes
+ keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx);
+ if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
+ certSafe = keySafe;
+ } else {
+ certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx, &unicodePw,
+ SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
+ }
+ if (!certSafe || !keySafe) {
+ LOG(ERROR) << "!certSafe || !keySafe " << certSafe << " " << keySafe;
+ srv = SECFailure;
+ goto finish;
+ }
+ // add the cert and key to the blob
+ srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, NULL, nssCert,
+ CERT_GetDefaultCertDB(),
+ keySafe, NULL, PR_TRUE, &unicodePw,
+ SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
+ if (srv) goto finish;
+ ++numCertsExported;
+ }
+
+ if (!numCertsExported) goto finish;
+
+ // encode and write
+ srv = SEC_PKCS12Encode(ecx, write_export_data, output);
+ if (srv) goto finish;
+ return_count = numCertsExported;
+finish:
+ if (srv)
+ LOG(ERROR) << "PKCS#12 export failed with error " << PORT_GetError();
+ if (ecx)
+ SEC_PKCS12DestroyExportContext(ecx);
+ SECITEM_ZfreeItem(&unicodePw, PR_FALSE);
+ return return_count;
+}
+
+} // namespace mozilla_security_manager
diff --git a/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.h b/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.h
new file mode 100644
index 00000000000..a2f2a490ab6
--- /dev/null
+++ b/chromium/net/third_party/mozilla_security_manager/nsPKCS12Blob.h
@@ -0,0 +1,79 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ian McGreer <mcgreer@netscape.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSPKCS12BLOB_H_
+#define NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSPKCS12BLOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+
+typedef struct CERTCertificateStr CERTCertificate;
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+namespace net {
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+} // namespace net
+
+namespace mozilla_security_manager {
+
+// Initialize NSS PKCS#12 libs.
+void EnsurePKCS12Init();
+
+// Import the private key and certificate from a PKCS#12 blob into the slot.
+// If |is_extractable| is false, mark the private key as non-extractable.
+// Returns a net error code. |imported_certs|, if non-NULL, returns a list of
+// certs that were imported.
+int nsPKCS12Blob_Import(PK11SlotInfo* slot,
+ const char* pkcs12_data,
+ size_t pkcs12_len,
+ const base::string16& password,
+ bool is_extractable,
+ net::CertificateList* imported_certs);
+
+// Export the given certificates into a PKCS#12 blob, storing into output.
+// Returns the number of certificates exported.
+// TODO(mattm): provide better error return status?
+int nsPKCS12Blob_Export(std::string* output,
+ const net::CertificateList& certs,
+ const base::string16& password);
+
+} // namespace mozilla_security_manager
+
+#endif // NET_THIRD_PARTY_MOZILLA_SECURITY_MANAGER_NSPKCS12BLOB_H_
diff --git a/chromium/net/third_party/nss/LICENSE b/chromium/net/third_party/nss/LICENSE
new file mode 100644
index 00000000000..03671648c17
--- /dev/null
+++ b/chromium/net/third_party/nss/LICENSE
@@ -0,0 +1,35 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1994-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
diff --git a/chromium/net/third_party/nss/README.chromium b/chromium/net/third_party/nss/README.chromium
new file mode 100644
index 00000000000..11a85102d93
--- /dev/null
+++ b/chromium/net/third_party/nss/README.chromium
@@ -0,0 +1,114 @@
+Name: Network Security Services (NSS)
+URL: http://www.mozilla.org/projects/security/pki/nss/
+Version: 3.15.1
+Security Critical: Yes
+License: MPL 2
+License File: NOT_SHIPPED
+
+This directory includes a copy of NSS's libssl from the hg repo at:
+ https://hg.mozilla.org/projects/nss
+
+The same module appears in crypto/third_party/nss (and third_party/nss on some
+platforms), so we don't repeat the license file here.
+
+The snapshot was updated to the hg tag: NSS_3_15_1_RTM
+
+Patches:
+
+ * Commenting out a couple of functions because they need NSS symbols
+ which may not exist in the system NSS library.
+ patches/versionskew.patch
+
+ * Send empty renegotiation info extension instead of SCSV unless TLS is
+ disabled.
+ patches/renegoscsv.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=549042
+
+ * Cache the peer's intermediate CA certificates in session ID, so that
+ they're available when we resume a session.
+ patches/cachecerts.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=731478
+
+ * Add the SSL_PeerCertificateChain function
+ patches/peercertchain.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=731485
+
+ * Add support for client auth with native crypto APIs on Mac and Windows
+ patches/clientauth.patch
+ ssl/sslplatf.c
+
+ * Add a function to export whether the last handshake on a socket resumed a
+ previous session.
+ patches/didhandshakeresume.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=731798
+
+ * Allow SSL_HandshakeNegotiatedExtension to be called before the handshake
+ is finished.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=681839
+ patches/negotiatedextension.patch
+
+ * Add function to retrieve TLS client cert types requested by server.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=51413
+ patches/getrequestedclientcerttypes.patch
+
+ * Add a function to restart a handshake after a client certificate request.
+ patches/restartclientauth.patch
+
+ * Add support for TLS Channel IDs
+ patches/channelid.patch
+
+ * Add support for extracting the tls-unique channel binding value
+ patches/tlsunique.patch
+ https://bugzilla.mozilla.org/show_bug.cgi?id=563276
+
+ * Define the EC_POINT_FORM_UNCOMPRESSED macro. In NSS 3.13.2 the macro
+ definition was moved from the internal header ec.h to blapit.h. When
+ compiling against older system NSS headers, we need to define the macro.
+ patches/ecpointform.patch
+
+ * SSL_ExportKeyingMaterial should get the RecvBufLock and SSL3HandshakeLock.
+ This change was made in https://chromiumcodereview.appspot.com/10454066.
+ patches/secretexporterlocks.patch
+
+ * Allow the constant-time CBC processing code to be compiled against older
+ NSS that doesn't contain the CBC constant-time changes.
+ patches/cbc.patch
+ https://code.google.com/p/chromium/issues/detail?id=172658#c12
+ TODO(wtc): remove this patch now that NSS 3.14.3 is the minimum
+ compile-time and run-time version.
+
+ * Change ssl3_SuiteBOnly to always return PR_TRUE. The softoken in NSS
+ versions older than 3.15 report an EC key size range of 112 bits to 571
+ bits, even when it is compiled to support only the NIST P-256, P-384, and
+ P-521 curves. Remove this patch when all system NSS softoken packages are
+ NSS 3.15 or later.
+ patches/suitebonly.patch
+
+ * Define the SECItemArray type and declare the SECItemArray handling
+ functions, which were added in NSS 3.15. Remove this patch when all system
+ NSS packages are NSS 3.15 or later.
+ patches/secitemarray.patch
+
+ * Update Chromium-specific code for TLS 1.2.
+ patches/tls12chromium.patch
+
+ * Add the Application Layer Protocol Negotiation extension.
+ patches/alpn.patch
+
+ * Fix an issue with allocating an SSL socket when under memory pressure.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=903565
+ patches/sslsock_903565.patch
+
+ * Implement the AES GCM cipher suites.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=880543
+ patches/aesgcm.patch
+
+ * Add Chromium-specific code to detect AES GCM support in the system NSS
+ libraries at run time.
+ patches/aesgcmchromium.patch
+
+Apply the patches to NSS by running the patches/applypatches.sh script. Read
+the comments at the top of patches/applypatches.sh for instructions.
+
+The ssl/bodge directory contains files taken from the NSS repo that we required
+for building libssl outside of its usual build environment.
diff --git a/chromium/net/third_party/nss/patches/aesgcm.patch b/chromium/net/third_party/nss/patches/aesgcm.patch
new file mode 100644
index 00000000000..8de0a69d718
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/aesgcm.patch
@@ -0,0 +1,1179 @@
+Index: net/third_party/nss/ssl/sslinfo.c
+===================================================================
+--- net/third_party/nss/ssl/sslinfo.c (revision 215189)
++++ net/third_party/nss/ssl/sslinfo.c (working copy)
+@@ -109,7 +109,7 @@
+ #define K_ECDHE "ECDHE", kt_ecdh
+
+ #define C_SEED "SEED", calg_seed
+-#define C_CAMELLIA "CAMELLIA", calg_camellia
++#define C_CAMELLIA "CAMELLIA", calg_camellia
+ #define C_AES "AES", calg_aes
+ #define C_RC4 "RC4", calg_rc4
+ #define C_RC2 "RC2", calg_rc2
+@@ -117,6 +117,7 @@
+ #define C_3DES "3DES", calg_3des
+ #define C_NULL "NULL", calg_null
+ #define C_SJ "SKIPJACK", calg_sj
++#define C_AESGCM "AES-GCM", calg_aes_gcm
+
+ #define B_256 256, 256, 256
+ #define B_128 128, 128, 128
+@@ -130,9 +131,12 @@
+ #define M_SHA256 "SHA256", ssl_hmac_sha256, 256
+ #define M_SHA "SHA1", ssl_mac_sha, 160
+ #define M_MD5 "MD5", ssl_mac_md5, 128
++#define M_NULL "NULL", ssl_mac_null, 0
+
+ static const SSLCipherSuiteInfo suiteInfo[] = {
+ /* <------ Cipher suite --------------------> <auth> <KEA> <bulk cipher> <MAC> <FIPS> */
++{0,CS(TLS_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
++
+ {0,CS(TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, 1, 0, 0, },
+@@ -146,6 +150,7 @@
+ {0,CS(TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, 1, 0, 0, },
++{0,CS(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
+ {0,CS(TLS_DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+ {0,CS(TLS_DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+ {0,CS(TLS_RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED,B_128, M_SHA, 1, 0, 0, },
+@@ -175,6 +180,9 @@
+
+ #ifdef NSS_ENABLE_ECC
+ /* ECC cipher suites */
++{0,CS(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
++{0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
++
+ {0,CS(TLS_ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, 0, 0, 0, },
+ {0,CS(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, 1, 0, 0, },
+Index: net/third_party/nss/ssl/sslimpl.h
+===================================================================
+--- net/third_party/nss/ssl/sslimpl.h (revision 215189)
++++ net/third_party/nss/ssl/sslimpl.h (working copy)
+@@ -64,6 +64,7 @@
+ #define calg_aes ssl_calg_aes
+ #define calg_camellia ssl_calg_camellia
+ #define calg_seed ssl_calg_seed
++#define calg_aes_gcm ssl_calg_aes_gcm
+
+ #define mac_null ssl_mac_null
+ #define mac_md5 ssl_mac_md5
+@@ -290,9 +291,9 @@
+ } ssl3CipherSuiteCfg;
+
+ #ifdef NSS_ENABLE_ECC
+-#define ssl_V3_SUITES_IMPLEMENTED 57
++#define ssl_V3_SUITES_IMPLEMENTED 61
+ #else
+-#define ssl_V3_SUITES_IMPLEMENTED 35
++#define ssl_V3_SUITES_IMPLEMENTED 37
+ #endif /* NSS_ENABLE_ECC */
+
+ #define MAX_DTLS_SRTP_CIPHER_SUITES 4
+@@ -440,20 +441,6 @@
+ #define GS_DATA 3
+ #define GS_PAD 4
+
+-typedef SECStatus (*SSLCipher)(void * context,
+- unsigned char * out,
+- int * outlen,
+- int maxout,
+- const unsigned char *in,
+- int inlen);
+-typedef SECStatus (*SSLCompressor)(void * context,
+- unsigned char * out,
+- int * outlen,
+- int maxout,
+- const unsigned char *in,
+- int inlen);
+-typedef SECStatus (*SSLDestroy)(void *context, PRBool freeit);
+-
+ #if defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_WIN32)
+ typedef PCERT_KEY_CONTEXT PlatformKey;
+ #elif defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_MACOSX)
+@@ -485,11 +472,12 @@
+ cipher_camellia_128,
+ cipher_camellia_256,
+ cipher_seed,
++ cipher_aes_128_gcm,
+ cipher_missing /* reserved for no such supported cipher */
+ /* This enum must match ssl3_cipherName[] in ssl3con.c. */
+ } SSL3BulkCipher;
+
+-typedef enum { type_stream, type_block } CipherType;
++typedef enum { type_stream, type_block, type_aead } CipherType;
+
+ #define MAX_IV_LENGTH 24
+
+@@ -531,6 +519,31 @@
+ PRUint64 cipher_context[MAX_CIPHER_CONTEXT_LLONGS];
+ } ssl3KeyMaterial;
+
++typedef SECStatus (*SSLCipher)(void * context,
++ unsigned char * out,
++ int * outlen,
++ int maxout,
++ const unsigned char *in,
++ int inlen);
++typedef SECStatus (*SSLAEADCipher)(
++ ssl3KeyMaterial * keys,
++ PRBool doDecrypt,
++ unsigned char * out,
++ int * outlen,
++ int maxout,
++ const unsigned char *in,
++ int inlen,
++ SSL3ContentType type,
++ SSL3ProtocolVersion version,
++ SSL3SequenceNumber seqnum);
++typedef SECStatus (*SSLCompressor)(void * context,
++ unsigned char * out,
++ int * outlen,
++ int maxout,
++ const unsigned char *in,
++ int inlen);
++typedef SECStatus (*SSLDestroy)(void *context, PRBool freeit);
++
+ /* The DTLS anti-replay window. Defined here because we need it in
+ * the cipher spec. Note that this is a ring buffer but left and
+ * right represent the true window, with modular arithmetic used to
+@@ -557,6 +570,7 @@
+ int mac_size;
+ SSLCipher encode;
+ SSLCipher decode;
++ SSLAEADCipher aead;
+ SSLDestroy destroy;
+ void * encodeContext;
+ void * decodeContext;
+@@ -706,8 +720,6 @@
+ PRBool tls_keygen;
+ } ssl3KEADef;
+
+-typedef enum { kg_null, kg_strong, kg_export } SSL3KeyGenMode;
+-
+ /*
+ ** There are tables of these, all const.
+ */
+@@ -719,7 +731,8 @@
+ CipherType type;
+ int iv_size;
+ int block_size;
+- SSL3KeyGenMode keygen_mode;
++ int tag_size; /* authentication tag size for AEAD ciphers. */
++ int explicit_nonce_size; /* for AEAD ciphers. */
+ };
+
+ /*
+Index: net/third_party/nss/ssl/ssl3ecc.c
+===================================================================
+--- net/third_party/nss/ssl/ssl3ecc.c (revision 215189)
++++ net/third_party/nss/ssl/ssl3ecc.c (working copy)
+@@ -911,6 +911,7 @@
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
++ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+@@ -921,6 +922,7 @@
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
++ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+@@ -932,12 +934,14 @@
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
++ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
++ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+Index: net/third_party/nss/ssl/sslsock.c
+===================================================================
+--- net/third_party/nss/ssl/sslsock.c (revision 215189)
++++ net/third_party/nss/ssl/sslsock.c (working copy)
+@@ -67,8 +67,10 @@
+ { TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
++ { TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
++ { TLS_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+@@ -94,6 +96,7 @@
+ { TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
++ { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+@@ -105,6 +108,7 @@
+ { TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
++ { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ #endif /* NSS_ENABLE_ECC */
+ { 0, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }
+Index: net/third_party/nss/ssl/ssl3con.c
+===================================================================
+--- net/third_party/nss/ssl/ssl3con.c (revision 215189)
++++ net/third_party/nss/ssl/ssl3con.c (working copy)
+@@ -78,6 +78,14 @@
+ static SECStatus Null_Cipher(void *ctx, unsigned char *output, int *outputLen,
+ int maxOutputLen, const unsigned char *input,
+ int inputLen);
++#ifndef NO_PKCS11_BYPASS
++static SECStatus ssl3_AESGCMBypass(ssl3KeyMaterial *keys, PRBool doDecrypt,
++ unsigned char *out, int *outlen, int maxout,
++ const unsigned char *in, int inlen,
++ SSL3ContentType type,
++ SSL3ProtocolVersion version,
++ SSL3SequenceNumber seq_num);
++#endif
+
+ #define MAX_SEND_BUF_LENGTH 32000 /* watch for 16-bit integer overflow */
+ #define MIN_SEND_BUF_LENGTH 4000
+@@ -90,6 +98,13 @@
+ static ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED] = {
+ /* cipher_suite policy enabled is_present*/
+ #ifdef NSS_ENABLE_ECC
++ { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
++ { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
++#endif /* NSS_ENABLE_ECC */
++ { TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
++ { TLS_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
++
++#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ #endif /* NSS_ENABLE_ECC */
+@@ -233,23 +248,30 @@
+
+ /* indexed by SSL3BulkCipher */
+ static const ssl3BulkCipherDef bulk_cipher_defs[] = {
+- /* cipher calg keySz secretSz type ivSz BlkSz keygen */
+- {cipher_null, calg_null, 0, 0, type_stream, 0, 0, kg_null},
+- {cipher_rc4, calg_rc4, 16, 16, type_stream, 0, 0, kg_strong},
+- {cipher_rc4_40, calg_rc4, 16, 5, type_stream, 0, 0, kg_export},
+- {cipher_rc4_56, calg_rc4, 16, 7, type_stream, 0, 0, kg_export},
+- {cipher_rc2, calg_rc2, 16, 16, type_block, 8, 8, kg_strong},
+- {cipher_rc2_40, calg_rc2, 16, 5, type_block, 8, 8, kg_export},
+- {cipher_des, calg_des, 8, 8, type_block, 8, 8, kg_strong},
+- {cipher_3des, calg_3des, 24, 24, type_block, 8, 8, kg_strong},
+- {cipher_des40, calg_des, 8, 5, type_block, 8, 8, kg_export},
+- {cipher_idea, calg_idea, 16, 16, type_block, 8, 8, kg_strong},
+- {cipher_aes_128, calg_aes, 16, 16, type_block, 16,16, kg_strong},
+- {cipher_aes_256, calg_aes, 32, 32, type_block, 16,16, kg_strong},
+- {cipher_camellia_128, calg_camellia,16, 16, type_block, 16,16, kg_strong},
+- {cipher_camellia_256, calg_camellia,32, 32, type_block, 16,16, kg_strong},
+- {cipher_seed, calg_seed, 16, 16, type_block, 16,16, kg_strong},
+- {cipher_missing, calg_null, 0, 0, type_stream, 0, 0, kg_null},
++ /* |--------- Lengths --------| */
++ /* cipher calg k s type i b t n */
++ /* e e v l a o */
++ /* y c | o g n */
++ /* | r | c | c */
++ /* | e | k | e */
++ /* | t | | | | */
++ {cipher_null, calg_null, 0, 0, type_stream, 0, 0, 0, 0},
++ {cipher_rc4, calg_rc4, 16,16, type_stream, 0, 0, 0, 0},
++ {cipher_rc4_40, calg_rc4, 16, 5, type_stream, 0, 0, 0, 0},
++ {cipher_rc4_56, calg_rc4, 16, 7, type_stream, 0, 0, 0, 0},
++ {cipher_rc2, calg_rc2, 16,16, type_block, 8, 8, 0, 0},
++ {cipher_rc2_40, calg_rc2, 16, 5, type_block, 8, 8, 0, 0},
++ {cipher_des, calg_des, 8, 8, type_block, 8, 8, 0, 0},
++ {cipher_3des, calg_3des, 24,24, type_block, 8, 8, 0, 0},
++ {cipher_des40, calg_des, 8, 5, type_block, 8, 8, 0, 0},
++ {cipher_idea, calg_idea, 16,16, type_block, 8, 8, 0, 0},
++ {cipher_aes_128, calg_aes, 16,16, type_block, 16,16, 0, 0},
++ {cipher_aes_256, calg_aes, 32,32, type_block, 16,16, 0, 0},
++ {cipher_camellia_128, calg_camellia, 16,16, type_block, 16,16, 0, 0},
++ {cipher_camellia_256, calg_camellia, 32,32, type_block, 16,16, 0, 0},
++ {cipher_seed, calg_seed, 16,16, type_block, 16,16, 0, 0},
++ {cipher_aes_128_gcm, calg_aes_gcm, 16,16, type_aead, 4, 0,16, 8},
++ {cipher_missing, calg_null, 0, 0, type_stream, 0, 0, 0, 0},
+ };
+
+ static const ssl3KEADef kea_defs[] =
+@@ -371,6 +393,11 @@
+ {SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_rsa_fips},
+ {SSL_RSA_FIPS_WITH_DES_CBC_SHA, cipher_des, mac_sha, kea_rsa_fips},
+
++ {TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_dhe_rsa},
++ {TLS_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_rsa},
++ {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_ecdhe_rsa},
++ {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_ecdhe_ecdsa},
++
+ #ifdef NSS_ENABLE_ECC
+ {TLS_ECDH_ECDSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_ecdsa},
+ {TLS_ECDH_ECDSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdh_ecdsa},
+@@ -434,6 +461,7 @@
+ { calg_aes , CKM_AES_CBC },
+ { calg_camellia , CKM_CAMELLIA_CBC },
+ { calg_seed , CKM_SEED_CBC },
++ { calg_aes_gcm , CKM_AES_GCM },
+ /* { calg_init , (CK_MECHANISM_TYPE)0x7fffffffL } */
+ };
+
+@@ -472,6 +500,7 @@
+ "Camellia-128",
+ "Camellia-256",
+ "SEED-CBC",
++ "AES-128-GCM",
+ "missing"
+ };
+
+@@ -598,9 +627,13 @@
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
++ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
++ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
++ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
++ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_WITH_NULL_SHA256:
+ return version >= SSL_LIBRARY_VERSION_TLS_1_2;
+ default:
+@@ -1360,7 +1393,7 @@
+ cipher = suite_def->bulk_cipher_alg;
+ kea = suite_def->key_exchange_alg;
+ mac = suite_def->mac_alg;
+- if (mac <= ssl_mac_sha && isTLS)
++ if (mac <= ssl_mac_sha && mac != ssl_mac_null && isTLS)
+ mac += 2;
+
+ ss->ssl3.hs.suite_def = suite_def;
+@@ -1554,7 +1587,6 @@
+ unsigned int optArg2 = 0;
+ PRBool server_encrypts = ss->sec.isServer;
+ SSLCipherAlgorithm calg;
+- SSLCompressionMethod compression_method;
+ SECStatus rv;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+@@ -1565,8 +1597,18 @@
+ cipher_def = pwSpec->cipher_def;
+
+ calg = cipher_def->calg;
+- compression_method = pwSpec->compression_method;
+
++ if (calg == calg_aes_gcm) {
++ pwSpec->encode = NULL;
++ pwSpec->decode = NULL;
++ pwSpec->destroy = NULL;
++ pwSpec->encodeContext = NULL;
++ pwSpec->decodeContext = NULL;
++ pwSpec->aead = ssl3_AESGCMBypass;
++ ssl3_InitCompressionContext(pwSpec);
++ return SECSuccess;
++ }
++
+ serverContext = pwSpec->server.cipher_context;
+ clientContext = pwSpec->client.cipher_context;
+
+@@ -1721,6 +1763,207 @@
+ return param;
+ }
+
++/* ssl3_BuildRecordPseudoHeader writes the TLS pseudo-header (the data which
++ * is included in the MAC) to |out| and returns its length. */
++static unsigned int
++ssl3_BuildRecordPseudoHeader(unsigned char *out,
++ SSL3SequenceNumber seq_num,
++ SSL3ContentType type,
++ PRBool includesVersion,
++ SSL3ProtocolVersion version,
++ PRBool isDTLS,
++ int length)
++{
++ out[0] = (unsigned char)(seq_num.high >> 24);
++ out[1] = (unsigned char)(seq_num.high >> 16);
++ out[2] = (unsigned char)(seq_num.high >> 8);
++ out[3] = (unsigned char)(seq_num.high >> 0);
++ out[4] = (unsigned char)(seq_num.low >> 24);
++ out[5] = (unsigned char)(seq_num.low >> 16);
++ out[6] = (unsigned char)(seq_num.low >> 8);
++ out[7] = (unsigned char)(seq_num.low >> 0);
++ out[8] = type;
++
++ /* SSL3 MAC doesn't include the record's version field. */
++ if (!includesVersion) {
++ out[9] = MSB(length);
++ out[10] = LSB(length);
++ return 11;
++ }
++
++ /* TLS MAC and AEAD additional data include version. */
++ if (isDTLS) {
++ SSL3ProtocolVersion dtls_version;
++
++ dtls_version = dtls_TLSVersionToDTLSVersion(version);
++ out[9] = MSB(dtls_version);
++ out[10] = LSB(dtls_version);
++ } else {
++ out[9] = MSB(version);
++ out[10] = LSB(version);
++ }
++ out[11] = MSB(length);
++ out[12] = LSB(length);
++ return 13;
++}
++
++static SECStatus
++ssl3_AESGCM(ssl3KeyMaterial *keys,
++ PRBool doDecrypt,
++ unsigned char *out,
++ int *outlen,
++ int maxout,
++ const unsigned char *in,
++ int inlen,
++ SSL3ContentType type,
++ SSL3ProtocolVersion version,
++ SSL3SequenceNumber seq_num)
++{
++ SECItem param;
++ SECStatus rv = SECFailure;
++ unsigned char nonce[12];
++ unsigned char additionalData[13];
++ unsigned int additionalDataLen;
++ unsigned int uOutLen;
++ CK_GCM_PARAMS gcmParams;
++
++ static const int tagSize = 16;
++ static const int explicitNonceLen = 8;
++
++ /* See https://tools.ietf.org/html/rfc5246#section-6.2.3.3 for the
++ * definition of the AEAD additional data. */
++ additionalDataLen = ssl3_BuildRecordPseudoHeader(
++ additionalData, seq_num, type, PR_TRUE /* includes version */,
++ version, PR_FALSE /* not DTLS */,
++ inlen - (doDecrypt ? explicitNonceLen + tagSize : 0));
++ PORT_Assert(additionalDataLen <= sizeof(additionalData));
++
++ /* See https://tools.ietf.org/html/rfc5288#section-3 for details of how the
++ * nonce is formed. */
++ memcpy(nonce, keys->write_iv, 4);
++ if (doDecrypt) {
++ memcpy(nonce + 4, in, explicitNonceLen);
++ in += explicitNonceLen;
++ inlen -= explicitNonceLen;
++ *outlen = 0;
++ } else {
++ if (maxout < explicitNonceLen) {
++ PORT_SetError(SEC_ERROR_INPUT_LEN);
++ return SECFailure;
++ }
++ /* Use the 64-bit sequence number as the explicit nonce. */
++ memcpy(nonce + 4, additionalData, explicitNonceLen);
++ memcpy(out, additionalData, explicitNonceLen);
++ out += explicitNonceLen;
++ maxout -= explicitNonceLen;
++ *outlen = explicitNonceLen;
++ }
++
++ param.type = siBuffer;
++ param.data = (unsigned char *) &gcmParams;
++ param.len = sizeof(gcmParams);
++ gcmParams.pIv = nonce;
++ gcmParams.ulIvLen = sizeof(nonce);
++ gcmParams.pAAD = additionalData;
++ gcmParams.ulAADLen = additionalDataLen;
++ gcmParams.ulTagBits = tagSize * 8;
++
++ if (doDecrypt) {
++ rv = PK11_Decrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
++ maxout, in, inlen);
++ } else {
++ rv = PK11_Encrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
++ maxout, in, inlen);
++ }
++ *outlen += (int) uOutLen;
++
++ return rv;
++}
++
++#ifndef NO_PKCS11_BYPASS
++static SECStatus
++ssl3_AESGCMBypass(ssl3KeyMaterial *keys,
++ PRBool doDecrypt,
++ unsigned char *out,
++ int *outlen,
++ int maxout,
++ const unsigned char *in,
++ int inlen,
++ SSL3ContentType type,
++ SSL3ProtocolVersion version,
++ SSL3SequenceNumber seq_num)
++{
++ SECStatus rv = SECFailure;
++ unsigned char nonce[12];
++ unsigned char additionalData[13];
++ unsigned int additionalDataLen;
++ unsigned int uOutLen;
++ AESContext *cx;
++ CK_GCM_PARAMS gcmParams;
++
++ static const int tagSize = 16;
++ static const int explicitNonceLen = 8;
++
++ /* See https://tools.ietf.org/html/rfc5246#section-6.2.3.3 for the
++ * definition of the AEAD additional data. */
++ additionalDataLen = ssl3_BuildRecordPseudoHeader(
++ additionalData, seq_num, type, PR_TRUE /* includes version */,
++ version, PR_FALSE /* not DTLS */,
++ inlen - (doDecrypt ? explicitNonceLen + tagSize : 0));
++ PORT_Assert(additionalDataLen <= sizeof(additionalData));
++
++ /* See https://tools.ietf.org/html/rfc5288#section-3 for details of how the
++ * nonce is formed. */
++ PORT_Assert(keys->write_iv_item.len == 4);
++ if (keys->write_iv_item.len != 4) {
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return SECFailure;
++ }
++ memcpy(nonce, keys->write_iv_item.data, 4);
++ if (doDecrypt) {
++ memcpy(nonce + 4, in, explicitNonceLen);
++ in += explicitNonceLen;
++ inlen -= explicitNonceLen;
++ *outlen = 0;
++ } else {
++ if (maxout < explicitNonceLen) {
++ PORT_SetError(SEC_ERROR_INPUT_LEN);
++ return SECFailure;
++ }
++ /* Use the 64-bit sequence number as the explicit nonce. */
++ memcpy(nonce + 4, additionalData, explicitNonceLen);
++ memcpy(out, additionalData, explicitNonceLen);
++ out += explicitNonceLen;
++ maxout -= explicitNonceLen;
++ *outlen = explicitNonceLen;
++ }
++
++ gcmParams.pIv = nonce;
++ gcmParams.ulIvLen = sizeof(nonce);
++ gcmParams.pAAD = additionalData;
++ gcmParams.ulAADLen = additionalDataLen;
++ gcmParams.ulTagBits = tagSize * 8;
++
++ cx = (AESContext *)keys->cipher_context;
++ rv = AES_InitContext(cx, keys->write_key_item.data,
++ keys->write_key_item.len,
++ (unsigned char *)&gcmParams, NSS_AES_GCM, !doDecrypt,
++ AES_BLOCK_SIZE);
++ if (rv != SECSuccess) {
++ return rv;
++ }
++ if (doDecrypt) {
++ rv = AES_Decrypt(cx, out, &uOutLen, maxout, in, inlen);
++ } else {
++ rv = AES_Encrypt(cx, out, &uOutLen, maxout, in, inlen);
++ }
++ AES_DestroyContext(cx, PR_FALSE);
++ *outlen += (int) uOutLen;
++
++ return rv;
++}
++#endif
++
+ /* Initialize encryption and MAC contexts for pending spec.
+ * Master Secret already is derived.
+ * Caller holds Spec write lock.
+@@ -1748,14 +1991,27 @@
+ pwSpec = ss->ssl3.pwSpec;
+ cipher_def = pwSpec->cipher_def;
+ macLength = pwSpec->mac_size;
++ calg = cipher_def->calg;
++ PORT_Assert(alg2Mech[calg].calg == calg);
+
++ pwSpec->client.write_mac_context = NULL;
++ pwSpec->server.write_mac_context = NULL;
++
++ if (calg == calg_aes_gcm) {
++ pwSpec->encode = NULL;
++ pwSpec->decode = NULL;
++ pwSpec->destroy = NULL;
++ pwSpec->encodeContext = NULL;
++ pwSpec->decodeContext = NULL;
++ pwSpec->aead = ssl3_AESGCM;
++ return SECSuccess;
++ }
++
+ /*
+ ** Now setup the MAC contexts,
+ ** crypto contexts are setup below.
+ */
+
+- pwSpec->client.write_mac_context = NULL;
+- pwSpec->server.write_mac_context = NULL;
+ mac_mech = pwSpec->mac_def->mmech;
+ mac_param.data = (unsigned char *)&macLength;
+ mac_param.len = sizeof(macLength);
+@@ -1778,9 +2034,6 @@
+ ** Now setup the crypto contexts.
+ */
+
+- calg = cipher_def->calg;
+- PORT_Assert(alg2Mech[calg].calg == calg);
+-
+ if (calg == calg_null) {
+ pwSpec->encode = Null_Cipher;
+ pwSpec->decode = Null_Cipher;
+@@ -1999,55 +2252,21 @@
+ {
+ const ssl3MACDef * mac_def;
+ SECStatus rv;
+-#ifndef NO_PKCS11_BYPASS
+ PRBool isTLS;
+-#endif
+ unsigned int tempLen;
+ unsigned char temp[MAX_MAC_LENGTH];
+
+- temp[0] = (unsigned char)(seq_num.high >> 24);
+- temp[1] = (unsigned char)(seq_num.high >> 16);
+- temp[2] = (unsigned char)(seq_num.high >> 8);
+- temp[3] = (unsigned char)(seq_num.high >> 0);
+- temp[4] = (unsigned char)(seq_num.low >> 24);
+- temp[5] = (unsigned char)(seq_num.low >> 16);
+- temp[6] = (unsigned char)(seq_num.low >> 8);
+- temp[7] = (unsigned char)(seq_num.low >> 0);
+- temp[8] = type;
+-
+ /* TLS MAC includes the record's version field, SSL's doesn't.
+ ** We decide which MAC defintiion to use based on the version of
+ ** the protocol that was negotiated when the spec became current,
+ ** NOT based on the version value in the record itself.
+- ** But, we use the record'v version value in the computation.
++ ** But, we use the record's version value in the computation.
+ */
+- if (spec->version <= SSL_LIBRARY_VERSION_3_0) {
+- temp[9] = MSB(inputLength);
+- temp[10] = LSB(inputLength);
+- tempLen = 11;
+-#ifndef NO_PKCS11_BYPASS
+- isTLS = PR_FALSE;
+-#endif
+- } else {
+- /* New TLS hash includes version. */
+- if (isDTLS) {
+- SSL3ProtocolVersion dtls_version;
++ isTLS = spec->version > SSL_LIBRARY_VERSION_3_0;
++ tempLen = ssl3_BuildRecordPseudoHeader(temp, seq_num, type, isTLS,
++ version, isDTLS, inputLength);
++ PORT_Assert(tempLen <= sizeof(temp));
+
+- dtls_version = dtls_TLSVersionToDTLSVersion(version);
+- temp[9] = MSB(dtls_version);
+- temp[10] = LSB(dtls_version);
+- } else {
+- temp[9] = MSB(version);
+- temp[10] = LSB(version);
+- }
+- temp[11] = MSB(inputLength);
+- temp[12] = LSB(inputLength);
+- tempLen = 13;
+-#ifndef NO_PKCS11_BYPASS
+- isTLS = PR_TRUE;
+-#endif
+- }
+-
+ PRINT_BUF(95, (NULL, "frag hash1: temp", temp, tempLen));
+ PRINT_BUF(95, (NULL, "frag hash1: input", input, inputLength));
+
+@@ -2390,86 +2609,112 @@
+ contentLen = outlen;
+ }
+
+- /*
+- * Add the MAC
+- */
+- rv = ssl3_ComputeRecordMAC( cwSpec, isServer, isDTLS,
+- type, cwSpec->version, cwSpec->write_seq_num, pIn, contentLen,
+- wrBuf->buf + headerLen + ivLen + contentLen, &macLen);
+- if (rv != SECSuccess) {
+- ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+- return SECFailure;
+- }
+- p1Len = contentLen;
+- p2Len = macLen;
+- fragLen = contentLen + macLen; /* needs to be encrypted */
+- PORT_Assert(fragLen <= MAX_FRAGMENT_LENGTH + 1024);
++ if (cipher_def->type == type_aead) {
++ const int nonceLen = cipher_def->explicit_nonce_size;
++ const int tagLen = cipher_def->tag_size;
+
+- /*
+- * Pad the text (if we're doing a block cipher)
+- * then Encrypt it
+- */
+- if (cipher_def->type == type_block) {
+- unsigned char * pBuf;
+- int padding_length;
+- int i;
++ if (headerLen + nonceLen + contentLen + tagLen > wrBuf->space) {
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return SECFailure;
++ }
+
+- oddLen = contentLen % cipher_def->block_size;
+- /* Assume blockSize is a power of two */
+- padding_length = cipher_def->block_size - 1 -
+- ((fragLen) & (cipher_def->block_size - 1));
+- fragLen += padding_length + 1;
+- PORT_Assert((fragLen % cipher_def->block_size) == 0);
+-
+- /* Pad according to TLS rules (also acceptable to SSL3). */
+- pBuf = &wrBuf->buf[headerLen + ivLen + fragLen - 1];
+- for (i = padding_length + 1; i > 0; --i) {
+- *pBuf-- = padding_length;
++ cipherBytes = contentLen;
++ rv = cwSpec->aead(
++ isServer ? &cwSpec->server : &cwSpec->client,
++ PR_FALSE, /* do encrypt */
++ wrBuf->buf + headerLen, /* output */
++ &cipherBytes, /* out len */
++ wrBuf->space - headerLen, /* max out */
++ pIn, contentLen, /* input */
++ type, cwSpec->version, cwSpec->write_seq_num);
++ if (rv != SECSuccess) {
++ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
++ return SECFailure;
+ }
+- /* now, if contentLen is not a multiple of block size, fix it */
+- p2Len = fragLen - p1Len;
+- }
+- if (p1Len < 256) {
+- oddLen = p1Len;
+- p1Len = 0;
+ } else {
+- p1Len -= oddLen;
+- }
+- if (oddLen) {
+- p2Len += oddLen;
+- PORT_Assert( (cipher_def->block_size < 2) || \
+- (p2Len % cipher_def->block_size) == 0);
+- memmove(wrBuf->buf + headerLen + ivLen + p1Len, pIn + p1Len, oddLen);
+- }
+- if (p1Len > 0) {
+- int cipherBytesPart1 = -1;
+- rv = cwSpec->encode( cwSpec->encodeContext,
+- wrBuf->buf + headerLen + ivLen, /* output */
+- &cipherBytesPart1, /* actual outlen */
+- p1Len, /* max outlen */
+- pIn, p1Len); /* input, and inputlen */
+- PORT_Assert(rv == SECSuccess && cipherBytesPart1 == (int) p1Len);
+- if (rv != SECSuccess || cipherBytesPart1 != (int) p1Len) {
+- PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
++ /*
++ * Add the MAC
++ */
++ rv = ssl3_ComputeRecordMAC( cwSpec, isServer, isDTLS,
++ type, cwSpec->version, cwSpec->write_seq_num, pIn, contentLen,
++ wrBuf->buf + headerLen + ivLen + contentLen, &macLen);
++ if (rv != SECSuccess) {
++ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+ return SECFailure;
+ }
+- cipherBytes += cipherBytesPart1;
++ p1Len = contentLen;
++ p2Len = macLen;
++ fragLen = contentLen + macLen; /* needs to be encrypted */
++ PORT_Assert(fragLen <= MAX_FRAGMENT_LENGTH + 1024);
++
++ /*
++ * Pad the text (if we're doing a block cipher)
++ * then Encrypt it
++ */
++ if (cipher_def->type == type_block) {
++ unsigned char * pBuf;
++ int padding_length;
++ int i;
++
++ oddLen = contentLen % cipher_def->block_size;
++ /* Assume blockSize is a power of two */
++ padding_length = cipher_def->block_size - 1 -
++ ((fragLen) & (cipher_def->block_size - 1));
++ fragLen += padding_length + 1;
++ PORT_Assert((fragLen % cipher_def->block_size) == 0);
++
++ /* Pad according to TLS rules (also acceptable to SSL3). */
++ pBuf = &wrBuf->buf[headerLen + ivLen + fragLen - 1];
++ for (i = padding_length + 1; i > 0; --i) {
++ *pBuf-- = padding_length;
++ }
++ /* now, if contentLen is not a multiple of block size, fix it */
++ p2Len = fragLen - p1Len;
++ }
++ if (p1Len < 256) {
++ oddLen = p1Len;
++ p1Len = 0;
++ } else {
++ p1Len -= oddLen;
++ }
++ if (oddLen) {
++ p2Len += oddLen;
++ PORT_Assert( (cipher_def->block_size < 2) || \
++ (p2Len % cipher_def->block_size) == 0);
++ memmove(wrBuf->buf + headerLen + ivLen + p1Len, pIn + p1Len,
++ oddLen);
++ }
++ if (p1Len > 0) {
++ int cipherBytesPart1 = -1;
++ rv = cwSpec->encode( cwSpec->encodeContext,
++ wrBuf->buf + headerLen + ivLen, /* output */
++ &cipherBytesPart1, /* actual outlen */
++ p1Len, /* max outlen */
++ pIn, p1Len); /* input, and inputlen */
++ PORT_Assert(rv == SECSuccess && cipherBytesPart1 == (int) p1Len);
++ if (rv != SECSuccess || cipherBytesPart1 != (int) p1Len) {
++ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
++ return SECFailure;
++ }
++ cipherBytes += cipherBytesPart1;
++ }
++ if (p2Len > 0) {
++ int cipherBytesPart2 = -1;
++ rv = cwSpec->encode( cwSpec->encodeContext,
++ wrBuf->buf + headerLen + ivLen + p1Len,
++ &cipherBytesPart2, /* output and actual outLen */
++ p2Len, /* max outlen */
++ wrBuf->buf + headerLen + ivLen + p1Len,
++ p2Len); /* input and inputLen*/
++ PORT_Assert(rv == SECSuccess && cipherBytesPart2 == (int) p2Len);
++ if (rv != SECSuccess || cipherBytesPart2 != (int) p2Len) {
++ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
++ return SECFailure;
++ }
++ cipherBytes += cipherBytesPart2;
++ }
+ }
+- if (p2Len > 0) {
+- int cipherBytesPart2 = -1;
+- rv = cwSpec->encode( cwSpec->encodeContext,
+- wrBuf->buf + headerLen + ivLen + p1Len,
+- &cipherBytesPart2, /* output and actual outLen */
+- p2Len, /* max outlen */
+- wrBuf->buf + headerLen + ivLen + p1Len,
+- p2Len); /* input and inputLen*/
+- PORT_Assert(rv == SECSuccess && cipherBytesPart2 == (int) p2Len);
+- if (rv != SECSuccess || cipherBytesPart2 != (int) p2Len) {
+- PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
+- return SECFailure;
+- }
+- cipherBytes += cipherBytesPart2;
+- }
++
+ PORT_Assert(cipherBytes <= MAX_FRAGMENT_LENGTH + 1024);
+
+ wrBuf->len = cipherBytes + headerLen;
+@@ -3012,9 +3257,6 @@
+ static SECStatus
+ ssl3_IllegalParameter(sslSocket *ss)
+ {
+- PRBool isTLS;
+-
+- isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ (void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
+ PORT_SetError(ss->sec.isServer ? SSL_ERROR_BAD_CLIENT
+ : SSL_ERROR_BAD_SERVER );
+@@ -3538,7 +3780,6 @@
+ }
+
+ key_material_params.bIsExport = (CK_BBOOL)(kea_def->is_limited);
+- /* was: (CK_BBOOL)(cipher_def->keygen_mode != kg_strong); */
+
+ key_material_params.RandomInfo.pClientRandom = cr;
+ key_material_params.RandomInfo.ulClientRandomLen = SSL3_RANDOM_LENGTH;
+@@ -9946,7 +10187,6 @@
+ static void
+ ssl3_RecordKeyLog(sslSocket *ss)
+ {
+- sslSessionID *sid;
+ SECStatus rv;
+ SECItem *keyData;
+ char buf[14 /* "CLIENT_RANDOM " */ +
+@@ -9958,8 +10198,6 @@
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+- sid = ss->sec.ci.sid;
+-
+ if (!ssl_keylog_iob)
+ return;
+
+@@ -11171,12 +11409,14 @@
+ /* With >= TLS 1.1, CBC records have an explicit IV. */
+ minLength += cipher_def->iv_size;
+ }
++ } else if (cipher_def->type == type_aead) {
++ minLength = cipher_def->explicit_nonce_size + cipher_def->tag_size;
+ }
+
+ /* We can perform this test in variable time because the record's total
+ * length and the ciphersuite are both public knowledge. */
+ if (cText->buf->len < minLength) {
+- goto decrypt_loser;
++ goto decrypt_loser;
+ }
+
+ if (cipher_def->type == type_block &&
+@@ -11244,78 +11484,95 @@
+ return SECFailure;
+ }
+
+- if (cipher_def->type == type_block &&
+- ((cText->buf->len - ivLen) % cipher_def->block_size) != 0) {
+- goto decrypt_loser;
+- }
++ rType = cText->type;
++ if (cipher_def->type == type_aead) {
++ rv = crSpec->aead(
++ ss->sec.isServer ? &crSpec->client : &crSpec->server,
++ PR_TRUE, /* do decrypt */
++ plaintext->buf, /* out */
++ (int*) &plaintext->len, /* outlen */
++ plaintext->space, /* maxout */
++ cText->buf->buf, /* in */
++ cText->buf->len, /* inlen */
++ rType, /* record type */
++ cText->version,
++ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num);
++ if (rv != SECSuccess) {
++ good = 0;
++ }
++ } else {
++ if (cipher_def->type == type_block &&
++ ((cText->buf->len - ivLen) % cipher_def->block_size) != 0) {
++ goto decrypt_loser;
++ }
+
+- /* decrypt from cText buf to plaintext. */
+- rv = crSpec->decode(
+- crSpec->decodeContext, plaintext->buf, (int *)&plaintext->len,
+- plaintext->space, cText->buf->buf + ivLen, cText->buf->len - ivLen);
+- if (rv != SECSuccess) {
+- goto decrypt_loser;
+- }
++ /* decrypt from cText buf to plaintext. */
++ rv = crSpec->decode(
++ crSpec->decodeContext, plaintext->buf, (int *)&plaintext->len,
++ plaintext->space, cText->buf->buf + ivLen, cText->buf->len - ivLen);
++ if (rv != SECSuccess) {
++ goto decrypt_loser;
++ }
+
+- PRINT_BUF(80, (ss, "cleartext:", plaintext->buf, plaintext->len));
++ PRINT_BUF(80, (ss, "cleartext:", plaintext->buf, plaintext->len));
+
+- originalLen = plaintext->len;
++ originalLen = plaintext->len;
+
+- /* If it's a block cipher, check and strip the padding. */
+- if (cipher_def->type == type_block) {
+- const unsigned int blockSize = cipher_def->block_size;
+- const unsigned int macSize = crSpec->mac_size;
++ /* If it's a block cipher, check and strip the padding. */
++ if (cipher_def->type == type_block) {
++ const unsigned int blockSize = cipher_def->block_size;
++ const unsigned int macSize = crSpec->mac_size;
+
+- if (crSpec->version <= SSL_LIBRARY_VERSION_3_0) {
+- good &= SECStatusToMask(ssl_RemoveSSLv3CBCPadding(
+- plaintext, blockSize, macSize));
+- } else {
+- good &= SECStatusToMask(ssl_RemoveTLSCBCPadding(
+- plaintext, macSize));
++ if (crSpec->version <= SSL_LIBRARY_VERSION_3_0) {
++ good &= SECStatusToMask(ssl_RemoveSSLv3CBCPadding(
++ plaintext, blockSize, macSize));
++ } else {
++ good &= SECStatusToMask(ssl_RemoveTLSCBCPadding(
++ plaintext, macSize));
++ }
+ }
+- }
+
+- /* compute the MAC */
+- rType = cText->type;
+- if (cipher_def->type == type_block) {
+- rv = ssl3_ComputeRecordMACConstantTime(
+- crSpec, (PRBool)(!ss->sec.isServer),
+- IS_DTLS(ss), rType, cText->version,
+- IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
+- plaintext->buf, plaintext->len, originalLen,
+- hash, &hashBytes);
++ /* compute the MAC */
++ if (cipher_def->type == type_block) {
++ rv = ssl3_ComputeRecordMACConstantTime(
++ crSpec, (PRBool)(!ss->sec.isServer),
++ IS_DTLS(ss), rType, cText->version,
++ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
++ plaintext->buf, plaintext->len, originalLen,
++ hash, &hashBytes);
+
+- ssl_CBCExtractMAC(plaintext, originalLen, givenHashBuf,
+- crSpec->mac_size);
+- givenHash = givenHashBuf;
++ ssl_CBCExtractMAC(plaintext, originalLen, givenHashBuf,
++ crSpec->mac_size);
++ givenHash = givenHashBuf;
+
+- /* plaintext->len will always have enough space to remove the MAC
+- * because in ssl_Remove{SSLv3|TLS}CBCPadding we only adjust
+- * plaintext->len if the result has enough space for the MAC and we
+- * tested the unadjusted size against minLength, above. */
+- plaintext->len -= crSpec->mac_size;
+- } else {
+- /* This is safe because we checked the minLength above. */
+- plaintext->len -= crSpec->mac_size;
++ /* plaintext->len will always have enough space to remove the MAC
++ * because in ssl_Remove{SSLv3|TLS}CBCPadding we only adjust
++ * plaintext->len if the result has enough space for the MAC and we
++ * tested the unadjusted size against minLength, above. */
++ plaintext->len -= crSpec->mac_size;
++ } else {
++ /* This is safe because we checked the minLength above. */
++ plaintext->len -= crSpec->mac_size;
+
+- rv = ssl3_ComputeRecordMAC(
+- crSpec, (PRBool)(!ss->sec.isServer),
+- IS_DTLS(ss), rType, cText->version,
+- IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
+- plaintext->buf, plaintext->len,
+- hash, &hashBytes);
++ rv = ssl3_ComputeRecordMAC(
++ crSpec, (PRBool)(!ss->sec.isServer),
++ IS_DTLS(ss), rType, cText->version,
++ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
++ plaintext->buf, plaintext->len,
++ hash, &hashBytes);
+
+- /* We can read the MAC directly from the record because its location is
+- * public when a stream cipher is used. */
+- givenHash = plaintext->buf + plaintext->len;
+- }
++ /* We can read the MAC directly from the record because its location
++ * is public when a stream cipher is used. */
++ givenHash = plaintext->buf + plaintext->len;
++ }
+
+- good &= SECStatusToMask(rv);
++ good &= SECStatusToMask(rv);
+
+- if (hashBytes != (unsigned)crSpec->mac_size ||
+- NSS_SecureMemcmp(givenHash, hash, crSpec->mac_size) != 0) {
+- /* We're allowed to leak whether or not the MAC check was correct */
+- good = 0;
++ if (hashBytes != (unsigned)crSpec->mac_size ||
++ NSS_SecureMemcmp(givenHash, hash, crSpec->mac_size) != 0) {
++ /* We're allowed to leak whether or not the MAC check was correct */
++ good = 0;
++ }
+ }
+
+ if (good == 0) {
+Index: net/third_party/nss/ssl/sslenum.c
+===================================================================
+--- net/third_party/nss/ssl/sslenum.c (revision 215189)
++++ net/third_party/nss/ssl/sslenum.c (working copy)
+@@ -29,6 +29,14 @@
+ * Finally, update the ssl_V3_SUITES_IMPLEMENTED macro in sslimpl.h.
+ */
+ const PRUint16 SSL_ImplementedCiphers[] = {
++ /* AES-GCM */
++#ifdef NSS_ENABLE_ECC
++ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
++ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
++#endif /* NSS_ENABLE_ECC */
++ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
++ TLS_RSA_WITH_AES_128_GCM_SHA256,
++
+ /* 256-bit */
+ #ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+Index: net/third_party/nss/ssl/sslproto.h
+===================================================================
+--- net/third_party/nss/ssl/sslproto.h (revision 215189)
++++ net/third_party/nss/ssl/sslproto.h (working copy)
+@@ -162,6 +162,10 @@
+
+ #define TLS_RSA_WITH_SEED_CBC_SHA 0x0096
+
++#define TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C
++#define TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E
++#define TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 0x00A2
++
+ /* TLS "Signaling Cipher Suite Value" (SCSV). May be requested by client.
+ * Must NEVER be chosen by server. SSL 3.0 server acknowledges by sending
+ * back an empty Renegotiation Info (RI) server hello extension.
+@@ -204,6 +208,11 @@
+ #define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023
+ #define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027
+
++#define TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B
++#define TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0xC02D
++#define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F
++#define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031
++
+ /* Netscape "experimental" cipher suites. */
+ #define SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA 0xffe0
+ #define SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA 0xffe1
+Index: net/third_party/nss/ssl/sslt.h
+===================================================================
+--- net/third_party/nss/ssl/sslt.h (revision 215189)
++++ net/third_party/nss/ssl/sslt.h (working copy)
+@@ -91,9 +91,10 @@
+ ssl_calg_3des = 4,
+ ssl_calg_idea = 5,
+ ssl_calg_fortezza = 6, /* deprecated, now unused */
+- ssl_calg_aes = 7, /* coming soon */
++ ssl_calg_aes = 7,
+ ssl_calg_camellia = 8,
+- ssl_calg_seed = 9
++ ssl_calg_seed = 9,
++ ssl_calg_aes_gcm = 10
+ } SSLCipherAlgorithm;
+
+ typedef enum {
+Index: net/third_party/nss/ssl/dtlscon.c
+===================================================================
+--- net/third_party/nss/ssl/dtlscon.c (revision 215189)
++++ net/third_party/nss/ssl/dtlscon.c (working copy)
+@@ -30,7 +30,14 @@
+
+ /* List copied from ssl3con.c:cipherSuites */
+ static const ssl3CipherSuite nonDTLSSuites[] = {
++ /* XXX Make AES-GCM work with DTLS. */
+ #ifdef NSS_ENABLE_ECC
++ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
++ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
++#endif /* NSS_ENABLE_ECC */
++ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
++ TLS_RSA_WITH_AES_128_GCM_SHA256,
++#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ #endif /* NSS_ENABLE_ECC */
diff --git a/chromium/net/third_party/nss/patches/aesgcmchromium.patch b/chromium/net/third_party/nss/patches/aesgcmchromium.patch
new file mode 100644
index 00000000000..8cd72bb9d9f
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/aesgcmchromium.patch
@@ -0,0 +1,117 @@
+--- net/third_party/nss/ssl/ssl3con.c.orig 2013-08-14 14:22:50.479780305 -0700
++++ net/third_party/nss/ssl/ssl3con.c 2013-08-14 14:23:57.670788603 -0700
+@@ -44,6 +44,9 @@
+ #ifdef NSS_ENABLE_ZLIB
+ #include "zlib.h"
+ #endif
++#ifdef LINUX
++#include <dlfcn.h>
++#endif
+
+ #ifndef PK11_SETATTRS
+ #define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \
+@@ -1807,6 +1810,69 @@ ssl3_BuildRecordPseudoHeader(unsigned ch
+ return 13;
+ }
+
++typedef SECStatus (*PK11CryptFcn)(
++ PK11SymKey *symKey, CK_MECHANISM_TYPE mechanism, SECItem *param,
++ unsigned char *out, unsigned int *outLen, unsigned int maxLen,
++ const unsigned char *in, unsigned int inLen);
++
++static PK11CryptFcn pk11_encrypt = NULL;
++static PK11CryptFcn pk11_decrypt = NULL;
++
++static PRCallOnceType resolvePK11CryptOnce;
++
++static PRStatus
++ssl3_ResolvePK11CryptFunctions(void)
++{
++#ifdef LINUX
++ /* On Linux we use the system NSS libraries. Look up the PK11_Encrypt and
++ * PK11_Decrypt functions at run time. */
++ void *handle = dlopen(NULL, RTLD_LAZY);
++ if (!handle) {
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return PR_FAILURE;
++ }
++ pk11_encrypt = (PK11CryptFcn)dlsym(handle, "PK11_Encrypt");
++ pk11_decrypt = (PK11CryptFcn)dlsym(handle, "PK11_Decrypt");
++ dlclose(handle);
++ return PR_SUCCESS;
++#else
++ /* On other platforms we use our own copy of NSS. PK11_Encrypt and
++ * PK11_Decrypt are known to be available. */
++ pk11_encrypt = PK11_Encrypt;
++ pk11_decrypt = PK11_Decrypt;
++ return PR_SUCCESS;
++#endif
++}
++
++/*
++ * In NSS 3.15, PK11_Encrypt and PK11_Decrypt were added to provide access
++ * to the AES GCM implementation in the NSS softoken. So the presence of
++ * these two functions implies the NSS version supports AES GCM.
++ */
++static PRBool
++ssl3_HasGCMSupport(void)
++{
++ (void)PR_CallOnce(&resolvePK11CryptOnce, ssl3_ResolvePK11CryptFunctions);
++ return pk11_encrypt != NULL;
++}
++
++/* On this socket, disable the GCM cipher suites */
++SECStatus
++ssl3_DisableGCMSuites(sslSocket * ss)
++{
++ unsigned int i;
++
++ for (i = 0; i < PR_ARRAY_SIZE(cipher_suite_defs); i++) {
++ const ssl3CipherSuiteDef *cipher_def = &cipher_suite_defs[i];
++ if (cipher_def->bulk_cipher_alg == cipher_aes_128_gcm) {
++ SECStatus rv = ssl3_CipherPrefSet(ss, cipher_def->cipher_suite,
++ PR_FALSE);
++ PORT_Assert(rv == SECSuccess); /* else is coding error */
++ }
++ }
++ return SECSuccess;
++}
++
+ static SECStatus
+ ssl3_AESGCM(ssl3KeyMaterial *keys,
+ PRBool doDecrypt,
+@@ -1869,10 +1935,10 @@ ssl3_AESGCM(ssl3KeyMaterial *keys,
+ gcmParams.ulTagBits = tagSize * 8;
+
+ if (doDecrypt) {
+- rv = PK11_Decrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
++ rv = pk11_decrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
+ maxout, in, inlen);
+ } else {
+- rv = PK11_Encrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
++ rv = pk11_encrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
+ maxout, in, inlen);
+ }
+ *outlen += (int) uOutLen;
+@@ -5071,6 +5137,10 @@ ssl3_SendClientHello(sslSocket *ss, PRBo
+ ssl3_DisableNonDTLSSuites(ss);
+ }
+
++ if (!ssl3_HasGCMSupport()) {
++ ssl3_DisableGCMSuites(ss);
++ }
++
+ /* how many suites are permitted by policy and user preference? */
+ num_suites = count_cipher_suites(ss, ss->ssl3.policy, PR_TRUE);
+ if (!num_suites)
+@@ -7776,6 +7846,10 @@ ssl3_HandleClientHello(sslSocket *ss, SS
+ ssl3_DisableNonDTLSSuites(ss);
+ }
+
++ if (!ssl3_HasGCMSupport()) {
++ ssl3_DisableGCMSuites(ss);
++ }
++
+ #ifdef PARANOID
+ /* Look for a matching cipher suite. */
+ j = ssl3_config_match_init(ss);
diff --git a/chromium/net/third_party/nss/patches/alpn.patch b/chromium/net/third_party/nss/patches/alpn.patch
new file mode 100644
index 00000000000..ad217982f8e
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/alpn.patch
@@ -0,0 +1,245 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 14:17:20.669282120 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 14:28:56.549496061 -0700
+@@ -9912,8 +9912,10 @@ ssl3_SendNextProto(sslSocket *ss)
+ int padding_len;
+ static const unsigned char padding[32] = {0};
+
+- if (ss->ssl3.nextProto.len == 0)
++ if (ss->ssl3.nextProto.len == 0 ||
++ ss->ssl3.nextProtoState == SSL_NEXT_PROTO_SELECTED) {
+ return SECSuccess;
++ }
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+diff -pu a/nss/lib/ssl/ssl3ext.c b/nss/lib/ssl/ssl3ext.c
+--- a/nss/lib/ssl/ssl3ext.c 2013-07-31 14:10:00.342814862 -0700
++++ b/nss/lib/ssl/ssl3ext.c 2013-07-31 14:28:56.549496061 -0700
+@@ -53,8 +53,12 @@ static SECStatus ssl3_HandleRenegotiationInfoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+ static SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
++static SECStatus ssl3_ClientHandleAppProtoXtn(sslSocket *ss,
++ PRUint16 ex_type, SECItem *data);
+ static SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
++static PRInt32 ssl3_ClientSendAppProtoXtn(sslSocket *ss, PRBool append,
++ PRUint32 maxBytes);
+ static PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+ static PRInt32 ssl3_SendUseSRTPXtn(sslSocket *ss, PRBool append,
+@@ -252,6 +256,7 @@ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
+ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
++ { ssl_app_layer_protocol_xtn, &ssl3_ClientHandleAppProtoXtn },
+ { ssl_use_srtp_xtn, &ssl3_HandleUseSRTPXtn },
+ { ssl_channel_id_xtn, &ssl3_ClientHandleChannelIDXtn },
+ { ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
+@@ -271,18 +276,19 @@ static const ssl3HelloExtensionHandler serverHelloHandlersSSL3[] = {
+ */
+ static const
+ ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
+- { ssl_server_name_xtn, &ssl3_SendServerNameXtn },
+- { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn },
++ { ssl_server_name_xtn, &ssl3_SendServerNameXtn },
++ { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn },
+ #ifdef NSS_ENABLE_ECC
+- { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
+- { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
++ { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
++ { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
+ #endif
+- { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
+- { ssl_next_proto_nego_xtn, &ssl3_ClientSendNextProtoNegoXtn },
+- { ssl_use_srtp_xtn, &ssl3_SendUseSRTPXtn },
+- { ssl_channel_id_xtn, &ssl3_ClientSendChannelIDXtn },
+- { ssl_cert_status_xtn, &ssl3_ClientSendStatusRequestXtn },
+- { ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn }
++ { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
++ { ssl_next_proto_nego_xtn, &ssl3_ClientSendNextProtoNegoXtn },
++ { ssl_app_layer_protocol_xtn, &ssl3_ClientSendAppProtoXtn },
++ { ssl_use_srtp_xtn, &ssl3_SendUseSRTPXtn },
++ { ssl_channel_id_xtn, &ssl3_ClientSendChannelIDXtn },
++ { ssl_cert_status_xtn, &ssl3_ClientSendStatusRequestXtn },
++ { ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn }
+ /* any extra entries will appear as { 0, NULL } */
+ };
+
+@@ -606,6 +612,11 @@ ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
+
+ PORT_Assert(!ss->firstHsDone);
+
++ if (ssl3_ExtensionNegotiated(ss, ssl_app_layer_protocol_xtn)) {
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return SECFailure;
++ }
++
+ rv = ssl3_ValidateNextProtoNego(data->data, data->len);
+ if (rv != SECSuccess)
+ return rv;
+@@ -639,6 +650,44 @@ ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
+ return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &result);
+ }
+
++static SECStatus
++ssl3_ClientHandleAppProtoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
++{
++ const unsigned char* d = data->data;
++ PRUint16 name_list_len;
++ SECItem protocol_name;
++
++ if (ssl3_ExtensionNegotiated(ss, ssl_next_proto_nego_xtn)) {
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return SECFailure;
++ }
++
++ /* The extension data from the server has the following format:
++ * uint16 name_list_len;
++ * uint8 len;
++ * uint8 protocol_name[len]; */
++ if (data->len < 4 || data->len > 2 + 1 + 255) {
++ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
++ return SECFailure;
++ }
++
++ name_list_len = ((PRUint16) d[0]) << 8 |
++ ((PRUint16) d[1]);
++ if (name_list_len != data->len - 2 ||
++ d[2] != data->len - 3) {
++ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
++ return SECFailure;
++ }
++
++ protocol_name.data = data->data + 3;
++ protocol_name.len = data->len - 3;
++
++ SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE);
++ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_SELECTED;
++ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
++ return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &protocol_name);
++}
++
+ static PRInt32
+ ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
+@@ -672,6 +721,70 @@ loser:
+ return -1;
+ }
+
++static PRInt32
++ssl3_ClientSendAppProtoXtn(sslSocket * ss, PRBool append, PRUint32 maxBytes)
++{
++ PRInt32 extension_length;
++ unsigned char *alpn_protos = NULL;
++
++ /* Renegotiations do not send this extension. */
++ if (!ss->opt.nextProtoNego.data || ss->firstHsDone) {
++ return 0;
++ }
++
++ extension_length = 2 /* extension type */ + 2 /* extension length */ +
++ 2 /* protocol name list length */ +
++ ss->opt.nextProtoNego.len;
++
++ if (append && maxBytes >= extension_length) {
++ /* NPN requires that the client's fallback protocol is first in the
++ * list. However, ALPN sends protocols in preference order. So we
++ * allocate a buffer and move the first protocol to the end of the
++ * list. */
++ SECStatus rv;
++ const unsigned int len = ss->opt.nextProtoNego.len;
++
++ alpn_protos = PORT_Alloc(len);
++ if (alpn_protos == NULL) {
++ return SECFailure;
++ }
++ if (len > 0) {
++ /* Each protocol string is prefixed with a single byte length. */
++ unsigned int i = ss->opt.nextProtoNego.data[0] + 1;
++ if (i <= len) {
++ memcpy(alpn_protos, &ss->opt.nextProtoNego.data[i], len - i);
++ memcpy(alpn_protos + len - i, ss->opt.nextProtoNego.data, i);
++ } else {
++ /* This seems to be invalid data so we'll send as-is. */
++ memcpy(alpn_protos, ss->opt.nextProtoNego.data, len);
++ }
++ }
++
++ rv = ssl3_AppendHandshakeNumber(ss, ssl_app_layer_protocol_xtn, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ rv = ssl3_AppendHandshakeVariable(ss, alpn_protos, len, 2);
++ PORT_Free(alpn_protos);
++ alpn_protos = NULL;
++ if (rv != SECSuccess)
++ goto loser;
++ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
++ ssl_app_layer_protocol_xtn;
++ } else if (maxBytes < extension_length) {
++ return 0;
++ }
++
++ return extension_length;
++
++loser:
++ if (alpn_protos)
++ PORT_Free(alpn_protos);
++ return -1;
++}
++
+ static SECStatus
+ ssl3_ClientHandleChannelIDXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 14:10:35.113325316 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 14:28:56.589496647 -0700
+@@ -203,6 +203,16 @@ SSL_IMPORT SECStatus SSL_SetNextProtoCal
+ * protocol in server-preference order. If no matching protocol is found it
+ * selects the first supported protocol.
+ *
++ * Using this function also allows the client to transparently support ALPN.
++ * The same set of protocols will be advertised via ALPN and, if the server
++ * uses ALPN to select a protocol, SSL_GetNextProto will return
++ * SSL_NEXT_PROTO_SELECTED as the state.
++ *
++ * Since NPN uses the first protocol as the fallback protocol, when sending an
++ * ALPN extension, the first protocol is moved to the end of the list. This
++ * indicates that the fallback protocol is the least preferred. The other
++ * protocols should be in preference order.
++ *
+ * The supported protocols are specified in |data| in wire-format (8-bit
+ * length-prefixed). For example: "\010http/1.1\006spdy/2". */
+ SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd,
+@@ -212,7 +217,8 @@ SSL_IMPORT SECStatus SSL_SetNextProtoNeg
+ typedef enum SSLNextProtoState {
+ SSL_NEXT_PROTO_NO_SUPPORT = 0, /* No peer support */
+ SSL_NEXT_PROTO_NEGOTIATED = 1, /* Mutual agreement */
+- SSL_NEXT_PROTO_NO_OVERLAP = 2 /* No protocol overlap found */
++ SSL_NEXT_PROTO_NO_OVERLAP = 2, /* No protocol overlap found */
++ SSL_NEXT_PROTO_SELECTED = 3 /* Server selected proto (ALPN) */
+ } SSLNextProtoState;
+
+ /* SSL_GetNextProto can be used in the HandshakeCallback or any time after
+diff -pu a/nss/lib/ssl/sslt.h b/nss/lib/ssl/sslt.h
+--- a/nss/lib/ssl/sslt.h 2013-07-31 14:13:43.806096237 -0700
++++ b/nss/lib/ssl/sslt.h 2013-07-31 14:28:56.609496941 -0700
+@@ -195,12 +195,13 @@ typedef enum {
+ #endif
+ ssl_signature_algorithms_xtn = 13,
+ ssl_use_srtp_xtn = 14,
++ ssl_app_layer_protocol_xtn = 16,
+ ssl_session_ticket_xtn = 35,
+ ssl_next_proto_nego_xtn = 13172,
+ ssl_channel_id_xtn = 30031,
+ ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
+ } SSLExtensionType;
+
+-#define SSL_MAX_EXTENSIONS 10
++#define SSL_MAX_EXTENSIONS 11
+
+ #endif /* __sslt_h_ */
diff --git a/chromium/net/third_party/nss/patches/applypatches.sh b/chromium/net/third_party/nss/patches/applypatches.sh
new file mode 100755
index 00000000000..14377acbe46
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/applypatches.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Copyright (c) 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.
+
+# Run this script in the mozilla/security/nss/lib/ssl directory in a NSS
+# source tree.
+#
+# Point patches_dir to the src/net/third_party/nss/patches directory in a
+# chromium source tree.
+patches_dir=/Users/wtc/chrome1/src/net/third_party/nss/patches
+
+patch -p4 < $patches_dir/versionskew.patch
+
+patch -p4 < $patches_dir/renegoscsv.patch
+
+patch -p4 < $patches_dir/cachecerts.patch
+
+patch -p4 < $patches_dir/peercertchain.patch
+
+patch -p4 < $patches_dir/clientauth.patch
+
+patch -p4 < $patches_dir/didhandshakeresume.patch
+
+patch -p4 < $patches_dir/negotiatedextension.patch
+
+patch -p4 < $patches_dir/getrequestedclientcerttypes.patch
+
+patch -p4 < $patches_dir/restartclientauth.patch
+
+patch -p4 < $patches_dir/channelid.patch
+
+patch -p4 < $patches_dir/tlsunique.patch
+
+patch -p4 < $patches_dir/ecpointform.patch
+
+patch -p4 < $patches_dir/secretexporterlocks.patch
+
+patch -p4 < $patches_dir/cbc.patch
+
+patch -p4 < $patches_dir/suitebonly.patch
+
+patch -p4 < $patches_dir/secitemarray.patch
+
+patch -p4 < $patches_dir/tls12chromium.patch
+
+patch -p4 < $patches_dir/alpn.patch
+
+patch -p5 < $patches_dir/sslsock_903565.patch
+
+patch -p4 < $patches_dir/aesgcm.patch
+
+patch -p4 < $patches_dir/aesgcmchromium.patch
diff --git a/chromium/net/third_party/nss/patches/cachecerts.patch b/chromium/net/third_party/nss/patches/cachecerts.patch
new file mode 100644
index 00000000000..6e55deab5df
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/cachecerts.patch
@@ -0,0 +1,118 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:29:35.584231452 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:31:22.785789376 -0700
+@@ -43,6 +43,7 @@
+
+ static SECStatus ssl3_AuthCertificate(sslSocket *ss);
+ static void ssl3_CleanupPeerCerts(sslSocket *ss);
++static void ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid);
+ static PK11SymKey *ssl3_GenerateRSAPMS(sslSocket *ss, ssl3CipherSpec *spec,
+ PK11SlotInfo * serverKeySlot);
+ static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms);
+@@ -6141,6 +6142,7 @@ ssl3_HandleServerHello(sslSocket *ss, SS
+ /* copy the peer cert from the SID */
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
++ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+ /* NULL value for PMS signifies re-use of the old MS */
+@@ -7538,6 +7540,7 @@ compression_found:
+ ss->sec.ci.sid = sid;
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
++ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+ /*
+@@ -9147,6 +9150,44 @@ ssl3_CleanupPeerCerts(sslSocket *ss)
+ ss->ssl3.peerCertChain = NULL;
+ }
+
++static void
++ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid)
++{
++ PLArenaPool *arena;
++ ssl3CertNode *lastCert = NULL;
++ ssl3CertNode *certs = NULL;
++ int i;
++
++ if (!sid->peerCertChain[0])
++ return;
++ PORT_Assert(!ss->ssl3.peerCertArena);
++ PORT_Assert(!ss->ssl3.peerCertChain);
++ ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
++ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
++ ssl3CertNode *c = PORT_ArenaNew(arena, ssl3CertNode);
++ c->cert = CERT_DupCertificate(sid->peerCertChain[i]);
++ c->next = NULL;
++ if (lastCert) {
++ lastCert->next = c;
++ } else {
++ certs = c;
++ }
++ lastCert = c;
++ }
++ ss->ssl3.peerCertChain = certs;
++}
++
++static void
++ssl3_CopyPeerCertsToSID(ssl3CertNode *certs, sslSessionID *sid)
++{
++ int i = 0;
++ ssl3CertNode *c = certs;
++ for (; i < MAX_PEER_CERT_CHAIN_SIZE && c; i++, c = c->next) {
++ PORT_Assert(!sid->peerCertChain[i]);
++ sid->peerCertChain[i] = CERT_DupCertificate(c->cert);
++ }
++}
++
+ /* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 CertificateStatus message.
+ * Caller must hold Handshake and RecvBuf locks.
+@@ -9432,6 +9473,7 @@ ssl3_AuthCertificate(sslSocket *ss)
+ }
+
+ ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
++ ssl3_CopyPeerCertsToSID(ss->ssl3.peerCertChain, ss->sec.ci.sid);
+
+ if (!ss->sec.isServer) {
+ CERTCertificate *cert = ss->sec.peerCert;
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 12:31:22.785789376 -0700
+@@ -572,10 +572,13 @@ typedef enum { never_cached,
+ invalid_cache /* no longer in any cache. */
+ } Cached;
+
++#define MAX_PEER_CERT_CHAIN_SIZE 8
++
+ struct sslSessionIDStr {
+ sslSessionID * next; /* chain used for client sockets, only */
+
+ CERTCertificate * peerCert;
++ CERTCertificate * peerCertChain[MAX_PEER_CERT_CHAIN_SIZE];
+ SECItemArray peerCertStatus; /* client only */
+ const char * peerID; /* client only */
+ const char * urlSvrName; /* client only */
+diff -pu a/nss/lib/ssl/sslnonce.c b/nss/lib/ssl/sslnonce.c
+--- a/nss/lib/ssl/sslnonce.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslnonce.c 2013-07-31 12:31:22.785789376 -0700
+@@ -164,6 +164,7 @@ lock_cache(void)
+ static void
+ ssl_DestroySID(sslSessionID *sid)
+ {
++ int i;
+ SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
+ PORT_Assert((sid->references == 0));
+
+@@ -183,6 +184,9 @@ ssl_DestroySID(sslSessionID *sid)
+ if ( sid->peerCert ) {
+ CERT_DestroyCertificate(sid->peerCert);
+ }
++ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
++ CERT_DestroyCertificate(sid->peerCertChain[i]);
++ }
+ if (sid->peerCertStatus.items) {
+ SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
+ }
diff --git a/chromium/net/third_party/nss/patches/cbc.patch b/chromium/net/third_party/nss/patches/cbc.patch
new file mode 100644
index 00000000000..032e5c184af
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/cbc.patch
@@ -0,0 +1,81 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 14:10:35.113325316 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 14:12:00.254575103 -0700
+@@ -2157,6 +2157,20 @@ ssl3_ComputeRecordMAC(
+ return rv;
+ }
+
++/* This is a bodge to allow this code to be compiled against older NSS headers
++ * that don't contain the CBC constant-time changes. */
++#ifndef CKM_NSS_HMAC_CONSTANT_TIME
++#define CKM_NSS_HMAC_CONSTANT_TIME (CKM_NSS + 19)
++#define CKM_NSS_SSL3_MAC_CONSTANT_TIME (CKM_NSS + 20)
++
++typedef struct CK_NSS_MAC_CONSTANT_TIME_PARAMS {
++ CK_MECHANISM_TYPE macAlg; /* in */
++ CK_ULONG ulBodyTotalLen; /* in */
++ CK_BYTE * pHeader; /* in */
++ CK_ULONG ulHeaderLen; /* in */
++} CK_NSS_MAC_CONSTANT_TIME_PARAMS;
++#endif
++
+ /* Called from: ssl3_HandleRecord()
+ * Caller must already hold the SpecReadLock. (wish we could assert that!)
+ *
+@@ -2179,7 +2193,8 @@ ssl3_ComputeRecordMACConstantTime(
+ {
+ CK_MECHANISM_TYPE macType;
+ CK_NSS_MAC_CONSTANT_TIME_PARAMS params;
+- SECItem param, inputItem, outputItem;
++ PK11Context * mac_context;
++ SECItem param;
+ SECStatus rv;
+ unsigned char header[13];
+ PK11SymKey * key;
+@@ -2240,34 +2255,27 @@ ssl3_ComputeRecordMACConstantTime(
+ param.len = sizeof(params);
+ param.type = 0;
+
+- inputItem.data = (unsigned char *) input;
+- inputItem.len = inputLen;
+- inputItem.type = 0;
+-
+- outputItem.data = outbuf;
+- outputItem.len = *outLen;
+- outputItem.type = 0;
+-
+ key = spec->server.write_mac_key;
+ if (!useServerMacKey) {
+ key = spec->client.write_mac_key;
+ }
++ mac_context = PK11_CreateContextBySymKey(macType, CKA_SIGN, key, &param);
++ if (mac_context == NULL) {
++ /* Older versions of NSS may not support constant-time MAC. */
++ goto fallback;
++ }
+
+- rv = PK11_SignWithSymKey(key, macType, &param, &outputItem, &inputItem);
+- if (rv != SECSuccess) {
+- if (PORT_GetError() == SEC_ERROR_INVALID_ALGORITHM) {
+- goto fallback;
+- }
++ rv = PK11_DigestBegin(mac_context);
++ rv |= PK11_DigestOp(mac_context, input, inputLen);
++ rv |= PK11_DigestFinal(mac_context, outbuf, outLen, spec->mac_size);
++ PK11_DestroyContext(mac_context, PR_TRUE);
+
+- *outLen = 0;
++ PORT_Assert(rv != SECSuccess || *outLen == (unsigned)spec->mac_size);
++
++ if (rv != SECSuccess) {
+ rv = SECFailure;
+ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+- return rv;
+ }
+-
+- PORT_Assert(outputItem.len == (unsigned)spec->mac_size);
+- *outLen = outputItem.len;
+-
+ return rv;
+
+ fallback:
diff --git a/chromium/net/third_party/nss/patches/channelid.patch b/chromium/net/third_party/nss/patches/channelid.patch
new file mode 100644
index 00000000000..46554f9014f
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/channelid.patch
@@ -0,0 +1,593 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:45:11.497944276 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:51:32.663550380 -0700
+@@ -55,6 +55,7 @@ static SECStatus ssl3_SendCertificateSta
+ static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss);
+ static SECStatus ssl3_SendCertificateRequest(sslSocket *ss);
+ static SECStatus ssl3_SendNextProto( sslSocket *ss);
++static SECStatus ssl3_SendEncryptedExtensions(sslSocket *ss);
+ static SECStatus ssl3_SendFinished( sslSocket *ss, PRInt32 flags);
+ static SECStatus ssl3_SendServerHello( sslSocket *ss);
+ static SECStatus ssl3_SendServerHelloDone( sslSocket *ss);
+@@ -5891,6 +5892,15 @@ ssl3_HandleServerHello(sslSocket *ss, SS
+ }
+ #endif /* NSS_PLATFORM_CLIENT_AUTH */
+
++ if (ss->ssl3.channelID != NULL) {
++ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
++ ss->ssl3.channelID = NULL;
++ }
++ if (ss->ssl3.channelIDPub != NULL) {
++ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
++ ss->ssl3.channelIDPub = NULL;
++ }
++
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (temp < 0) {
+ goto loser; /* alert has been sent */
+@@ -6170,7 +6180,7 @@ ssl3_HandleServerHello(sslSocket *ss, SS
+ if (rv != SECSuccess) {
+ goto alert_loser; /* err code was set */
+ }
+- return SECSuccess;
++ goto winner;
+ } while (0);
+
+ if (sid_match)
+@@ -6196,6 +6206,27 @@ ssl3_HandleServerHello(sslSocket *ss, SS
+
+ ss->ssl3.hs.isResuming = PR_FALSE;
+ ss->ssl3.hs.ws = wait_server_cert;
++
++winner:
++ /* If we will need a ChannelID key then we make the callback now. This
++ * allows the handshake to be restarted cleanly if the callback returns
++ * SECWouldBlock. */
++ if (ssl3_ExtensionNegotiated(ss, ssl_channel_id_xtn)) {
++ rv = ss->getChannelID(ss->getChannelIDArg, ss->fd,
++ &ss->ssl3.channelIDPub, &ss->ssl3.channelID);
++ if (rv == SECWouldBlock) {
++ ssl3_SetAlwaysBlock(ss);
++ return rv;
++ }
++ if (rv != SECSuccess ||
++ ss->ssl3.channelIDPub == NULL ||
++ ss->ssl3.channelID == NULL) {
++ PORT_SetError(SSL_ERROR_GET_CHANNEL_ID_FAILED);
++ desc = internal_error;
++ goto alert_loser;
++ }
++ }
++
+ return SECSuccess;
+
+ alert_loser:
+@@ -6993,6 +7024,10 @@ ssl3_SendClientSecondRound(sslSocket *ss
+ goto loser; /* err code was set. */
+ }
+ }
++ rv = ssl3_SendEncryptedExtensions(ss);
++ if (rv != SECSuccess) {
++ goto loser; /* err code was set. */
++ }
+
+ rv = ssl3_SendFinished(ss, 0);
+ if (rv != SECSuccess) {
+@@ -9947,6 +9982,165 @@ ssl3_RecordKeyLog(sslSocket *ss)
+ return;
+ }
+
++/* called from ssl3_SendClientSecondRound
++ * ssl3_HandleFinished
++ */
++static SECStatus
++ssl3_SendEncryptedExtensions(sslSocket *ss)
++{
++ static const char CHANNEL_ID_MAGIC[] = "TLS Channel ID signature";
++ /* This is the ASN.1 prefix for a P-256 public key. Specifically it's:
++ * SEQUENCE
++ * SEQUENCE
++ * OID id-ecPublicKey
++ * OID prime256v1
++ * BIT STRING, length 66, 0 trailing bits: 0x04
++ *
++ * The 0x04 in the BIT STRING is the prefix for an uncompressed, X9.62
++ * public key. Following that are the two field elements as 32-byte,
++ * big-endian numbers, as required by the Channel ID. */
++ static const unsigned char P256_SPKI_PREFIX[] = {
++ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
++ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
++ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
++ 0x42, 0x00, 0x04
++ };
++ /* ChannelIDs are always 128 bytes long: 64 bytes of P-256 public key and 64
++ * bytes of ECDSA signature. */
++ static const int CHANNEL_ID_PUBLIC_KEY_LENGTH = 64;
++ static const int CHANNEL_ID_LENGTH = 128;
++
++ SECStatus rv = SECFailure;
++ SECItem *spki = NULL;
++ SSL3Hashes hashes;
++ const unsigned char *pub_bytes;
++ unsigned char signed_data[sizeof(CHANNEL_ID_MAGIC) + sizeof(SSL3Hashes)];
++ unsigned char digest[SHA256_LENGTH];
++ SECItem digest_item;
++ unsigned char signature[64];
++ SECItem signature_item;
++
++ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
++ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
++
++ if (ss->ssl3.channelID == NULL)
++ return SECSuccess;
++
++ PORT_Assert(ssl3_ExtensionNegotiated(ss, ssl_channel_id_xtn));
++
++ if (SECKEY_GetPrivateKeyType(ss->ssl3.channelID) != ecKey ||
++ PK11_SignatureLen(ss->ssl3.channelID) != sizeof(signature)) {
++ PORT_SetError(SSL_ERROR_INVALID_CHANNEL_ID_KEY);
++ rv = SECFailure;
++ goto loser;
++ }
++
++ ssl_GetSpecReadLock(ss);
++ rv = ssl3_ComputeHandshakeHashes(ss, ss->ssl3.cwSpec, &hashes, 0);
++ ssl_ReleaseSpecReadLock(ss);
++
++ if (rv != SECSuccess)
++ goto loser;
++
++ rv = ssl3_AppendHandshakeHeader(ss, encrypted_extensions,
++ 2 + 2 + CHANNEL_ID_LENGTH);
++ if (rv != SECSuccess)
++ goto loser; /* error code set by AppendHandshakeHeader */
++ rv = ssl3_AppendHandshakeNumber(ss, ssl_channel_id_xtn, 2);
++ if (rv != SECSuccess)
++ goto loser; /* error code set by AppendHandshake */
++ rv = ssl3_AppendHandshakeNumber(ss, CHANNEL_ID_LENGTH, 2);
++ if (rv != SECSuccess)
++ goto loser; /* error code set by AppendHandshake */
++
++ spki = SECKEY_EncodeDERSubjectPublicKeyInfo(ss->ssl3.channelIDPub);
++
++ if (spki->len != sizeof(P256_SPKI_PREFIX) + CHANNEL_ID_PUBLIC_KEY_LENGTH ||
++ memcmp(spki->data, P256_SPKI_PREFIX, sizeof(P256_SPKI_PREFIX) != 0)) {
++ PORT_SetError(SSL_ERROR_INVALID_CHANNEL_ID_KEY);
++ rv = SECFailure;
++ goto loser;
++ }
++
++ pub_bytes = spki->data + sizeof(P256_SPKI_PREFIX);
++
++ memcpy(signed_data, CHANNEL_ID_MAGIC, sizeof(CHANNEL_ID_MAGIC));
++ memcpy(signed_data + sizeof(CHANNEL_ID_MAGIC), hashes.u.raw, hashes.len);
++
++ rv = PK11_HashBuf(SEC_OID_SHA256, digest, signed_data,
++ sizeof(CHANNEL_ID_MAGIC) + hashes.len);
++ if (rv != SECSuccess)
++ goto loser;
++
++ digest_item.data = digest;
++ digest_item.len = sizeof(digest);
++
++ signature_item.data = signature;
++ signature_item.len = sizeof(signature);
++
++ rv = PK11_Sign(ss->ssl3.channelID, &signature_item, &digest_item);
++ if (rv != SECSuccess)
++ goto loser;
++
++ rv = ssl3_AppendHandshake(ss, pub_bytes, CHANNEL_ID_PUBLIC_KEY_LENGTH);
++ if (rv != SECSuccess)
++ goto loser;
++ rv = ssl3_AppendHandshake(ss, signature, sizeof(signature));
++
++loser:
++ if (spki)
++ SECITEM_FreeItem(spki, PR_TRUE);
++ if (ss->ssl3.channelID) {
++ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
++ ss->ssl3.channelID = NULL;
++ }
++ if (ss->ssl3.channelIDPub) {
++ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
++ ss->ssl3.channelIDPub = NULL;
++ }
++
++ return rv;
++}
++
++/* ssl3_RestartHandshakeAfterChannelIDReq is called to restart a handshake
++ * after a ChannelID callback returned SECWouldBlock. At this point we have
++ * processed the server's ServerHello but not yet any further messages. We will
++ * always get a message from the server after a ServerHello so either they are
++ * waiting in the buffer or we'll get network I/O. */
++SECStatus
++ssl3_RestartHandshakeAfterChannelIDReq(sslSocket *ss,
++ SECKEYPublicKey *channelIDPub,
++ SECKEYPrivateKey *channelID)
++{
++ if (ss->handshake == 0) {
++ SECKEY_DestroyPublicKey(channelIDPub);
++ SECKEY_DestroyPrivateKey(channelID);
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ return SECFailure;
++ }
++
++ if (channelIDPub == NULL ||
++ channelID == NULL) {
++ if (channelIDPub)
++ SECKEY_DestroyPublicKey(channelIDPub);
++ if (channelID)
++ SECKEY_DestroyPrivateKey(channelID);
++ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
++ return SECFailure;
++ }
++
++ if (ss->ssl3.channelID)
++ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
++ if (ss->ssl3.channelIDPub)
++ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
++
++ ss->handshake = ssl_GatherRecord1stHandshake;
++ ss->ssl3.channelID = channelID;
++ ss->ssl3.channelIDPub = channelIDPub;
++
++ return SECSuccess;
++}
++
+ /* called from ssl3_HandleServerHelloDone
+ * ssl3_HandleClientHello
+ * ssl3_HandleFinished
+@@ -10202,11 +10396,16 @@ ssl3_HandleFinished(sslSocket *ss, SSL3O
+ flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER;
+ }
+
+- if (!isServer && !ss->firstHsDone) {
+- rv = ssl3_SendNextProto(ss);
+- if (rv != SECSuccess) {
+- goto xmit_loser; /* err code was set. */
++ if (!isServer) {
++ if (!ss->firstHsDone) {
++ rv = ssl3_SendNextProto(ss);
++ if (rv != SECSuccess) {
++ goto xmit_loser; /* err code was set. */
++ }
+ }
++ rv = ssl3_SendEncryptedExtensions(ss);
++ if (rv != SECSuccess)
++ goto xmit_loser; /* err code was set. */
+ }
+
+ if (IS_DTLS(ss)) {
+@@ -11635,6 +11834,11 @@ ssl3_DestroySSL3Info(sslSocket *ss)
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+ #endif /* NSS_PLATFORM_CLIENT_AUTH */
+
++ if (ss->ssl3.channelID)
++ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
++ if (ss->ssl3.channelIDPub)
++ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
++
+ if (ss->ssl3.peerCertArena != NULL)
+ ssl3_CleanupPeerCerts(ss);
+
+diff -pu a/nss/lib/ssl/ssl3ext.c b/nss/lib/ssl/ssl3ext.c
+--- a/nss/lib/ssl/ssl3ext.c 2013-07-31 12:40:14.493586151 -0700
++++ b/nss/lib/ssl/ssl3ext.c 2013-07-31 12:45:50.338515793 -0700
+@@ -60,6 +60,10 @@ static PRInt32 ssl3_SendUseSRTPXtn(sslSo
+ PRUint32 maxBytes);
+ static SECStatus ssl3_HandleUseSRTPXtn(sslSocket * ss, PRUint16 ex_type,
+ SECItem *data);
++static SECStatus ssl3_ClientHandleChannelIDXtn(sslSocket *ss,
++ PRUint16 ex_type, SECItem *data);
++static PRInt32 ssl3_ClientSendChannelIDXtn(sslSocket *ss, PRBool append,
++ PRUint32 maxBytes);
+ static SECStatus ssl3_ServerSendStatusRequestXtn(sslSocket * ss,
+ PRBool append, PRUint32 maxBytes);
+ static SECStatus ssl3_ServerHandleStatusRequestXtn(sslSocket *ss,
+@@ -248,6 +252,7 @@ static const ssl3HelloExtensionHandler s
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
+ { ssl_use_srtp_xtn, &ssl3_HandleUseSRTPXtn },
++ { ssl_channel_id_xtn, &ssl3_ClientHandleChannelIDXtn },
+ { ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
+ { -1, NULL }
+ };
+@@ -274,6 +279,7 @@ ssl3HelloExtensionSender clientHelloSend
+ { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ClientSendNextProtoNegoXtn },
+ { ssl_use_srtp_xtn, &ssl3_SendUseSRTPXtn },
++ { ssl_channel_id_xtn, &ssl3_ClientSendChannelIDXtn },
+ { ssl_cert_status_xtn, &ssl3_ClientSendStatusRequestXtn },
+ { ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn }
+ /* any extra entries will appear as { 0, NULL } */
+@@ -660,6 +666,52 @@ ssl3_ClientSendNextProtoNegoXtn(sslSocke
+ }
+
+ return extension_length;
++
++loser:
++ return -1;
++}
++
++static SECStatus
++ssl3_ClientHandleChannelIDXtn(sslSocket *ss, PRUint16 ex_type,
++ SECItem *data)
++{
++ PORT_Assert(ss->getChannelID != NULL);
++
++ if (data->len) {
++ PORT_SetError(SSL_ERROR_BAD_CHANNEL_ID_DATA);
++ return SECFailure;
++ }
++ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
++ return SECSuccess;
++}
++
++static PRInt32
++ssl3_ClientSendChannelIDXtn(sslSocket * ss, PRBool append,
++ PRUint32 maxBytes)
++{
++ PRInt32 extension_length = 4;
++
++ if (!ss->getChannelID)
++ return 0;
++
++ if (maxBytes < extension_length) {
++ PORT_Assert(0);
++ return 0;
++ }
++
++ if (append) {
++ SECStatus rv;
++ rv = ssl3_AppendHandshakeNumber(ss, ssl_channel_id_xtn, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
++ if (rv != SECSuccess)
++ goto loser;
++ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
++ ssl_channel_id_xtn;
++ }
++
++ return extension_length;
+
+ loser:
+ return -1;
+diff -pu a/nss/lib/ssl/ssl3prot.h b/nss/lib/ssl/ssl3prot.h
+--- a/nss/lib/ssl/ssl3prot.h 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/ssl3prot.h 2013-07-31 12:45:50.338515793 -0700
+@@ -129,7 +129,8 @@ typedef enum {
+ client_key_exchange = 16,
+ finished = 20,
+ certificate_status = 22,
+- next_proto = 67
++ next_proto = 67,
++ encrypted_extensions= 203
+ } SSL3HandshakeType;
+
+ typedef struct {
+diff -pu a/nss/lib/ssl/sslauth.c b/nss/lib/ssl/sslauth.c
+--- a/nss/lib/ssl/sslauth.c 2013-07-31 12:40:14.503586299 -0700
++++ b/nss/lib/ssl/sslauth.c 2013-07-31 12:45:50.338515793 -0700
+@@ -219,6 +219,24 @@ SSL_GetClientAuthDataHook(PRFileDesc *s,
+ return SECSuccess;
+ }
+
++SECStatus
++SSL_SetClientChannelIDCallback(PRFileDesc *fd,
++ SSLClientChannelIDCallback callback,
++ void *arg) {
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetClientChannelIDCallback",
++ SSL_GETPID(), fd));
++ return SECFailure;
++ }
++
++ ss->getChannelID = callback;
++ ss->getChannelIDArg = arg;
++
++ return SECSuccess;
++}
++
+ #ifdef NSS_PLATFORM_CLIENT_AUTH
+ /* NEED LOCKS IN HERE. */
+ SECStatus
+diff -pu a/nss/lib/ssl/sslerr.h b/nss/lib/ssl/sslerr.h
+--- a/nss/lib/ssl/sslerr.h 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslerr.h 2013-07-31 12:45:50.338515793 -0700
+@@ -193,6 +193,10 @@ SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM = (
+ SSL_ERROR_DIGEST_FAILURE = (SSL_ERROR_BASE + 127),
+ SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM = (SSL_ERROR_BASE + 128),
+
++SSL_ERROR_BAD_CHANNEL_ID_DATA = (SSL_ERROR_BASE + 129),
++SSL_ERROR_INVALID_CHANNEL_ID_KEY = (SSL_ERROR_BASE + 130),
++SSL_ERROR_GET_CHANNEL_ID_FAILED = (SSL_ERROR_BASE + 131),
++
+ SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */
+ } SSLErrorCodes;
+ #endif /* NO_SECURITY_ERROR_ENUM */
+diff -pu a/nss/lib/ssl/SSLerrs.h b/nss/lib/ssl/SSLerrs.h
+--- a/nss/lib/ssl/SSLerrs.h 2013-07-31 12:07:10.964699464 -0700
++++ b/nss/lib/ssl/SSLerrs.h 2013-07-31 12:45:50.338515793 -0700
+@@ -412,3 +412,12 @@ ER3(SSL_ERROR_DIGEST_FAILURE, (SSL_ERROR
+
+ ER3(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM, (SSL_ERROR_BASE + 128),
+ "Incorrect signature algorithm specified in a digitally-signed element.")
++
++ER3(SSL_ERROR_BAD_CHANNEL_ID_DATA, (SSL_ERROR_BASE + 129),
++"SSL received a malformed TLS Channel ID extension.")
++
++ER3(SSL_ERROR_INVALID_CHANNEL_ID_KEY, (SSL_ERROR_BASE + 130),
++"The application provided an invalid TLS Channel ID key.")
++
++ER3(SSL_ERROR_GET_CHANNEL_ID_FAILED, (SSL_ERROR_BASE + 131),
++"The application could not get a TLS Channel ID.")
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:45:11.497944276 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:45:50.338515793 -0700
+@@ -958,6 +958,34 @@ SSL_IMPORT SECStatus SSL_HandshakeNegoti
+ SSL_IMPORT SECStatus SSL_HandshakeResumedSession(PRFileDesc *fd,
+ PRBool *last_handshake_resumed);
+
++/* See SSL_SetClientChannelIDCallback for usage. If the callback returns
++ * SECWouldBlock then SSL_RestartHandshakeAfterChannelIDReq should be called in
++ * the future to restart the handshake. On SECSuccess, the callback must have
++ * written a P-256, EC key pair to |*out_public_key| and |*out_private_key|. */
++typedef SECStatus (PR_CALLBACK *SSLClientChannelIDCallback)(
++ void *arg,
++ PRFileDesc *fd,
++ SECKEYPublicKey **out_public_key,
++ SECKEYPrivateKey **out_private_key);
++
++/* SSL_RestartHandshakeAfterChannelIDReq attempts to restart the handshake
++ * after a ChannelID callback returned SECWouldBlock.
++ *
++ * This function takes ownership of |channelIDPub| and |channelID|. */
++SSL_IMPORT SECStatus SSL_RestartHandshakeAfterChannelIDReq(
++ PRFileDesc *fd,
++ SECKEYPublicKey *channelIDPub,
++ SECKEYPrivateKey *channelID);
++
++/* SSL_SetClientChannelIDCallback sets a callback function that will be called
++ * once the server's ServerHello has been processed. This is only applicable to
++ * a client socket and setting this callback causes the TLS Channel ID
++ * extension to be advertised. */
++SSL_IMPORT SECStatus SSL_SetClientChannelIDCallback(
++ PRFileDesc *fd,
++ SSLClientChannelIDCallback callback,
++ void *arg);
++
+ /*
+ ** How long should we wait before retransmitting the next flight of
+ ** the DTLS handshake? Returns SECFailure if not DTLS or not in a
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 12:45:11.497944276 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 12:45:50.338515793 -0700
+@@ -921,6 +921,9 @@ struct ssl3StateStr {
+ CERTCertificateList *clientCertChain; /* used by client */
+ PRBool sendEmptyCert; /* used by client */
+
++ SECKEYPrivateKey *channelID; /* used by client */
++ SECKEYPublicKey *channelIDPub; /* used by client */
++
+ int policy;
+ /* This says what cipher suites we can do, and should
+ * be either SSL_ALLOWED or SSL_RESTRICTED
+@@ -1192,6 +1195,8 @@ const unsigned char * preferredCipher;
+ void *pkcs11PinArg;
+ SSLNextProtoCallback nextProtoCallback;
+ void *nextProtoArg;
++ SSLClientChannelIDCallback getChannelID;
++ void *getChannelIDArg;
+
+ PRIntervalTime rTimeout; /* timeout for NSPR I/O */
+ PRIntervalTime wTimeout; /* timeout for NSPR I/O */
+@@ -1524,6 +1529,11 @@ extern SECStatus ssl3_RestartHandshakeAf
+ SECKEYPrivateKey * key,
+ CERTCertificateList *certChain);
+
++extern SECStatus ssl3_RestartHandshakeAfterChannelIDReq(
++ sslSocket *ss,
++ SECKEYPublicKey *channelIDPub,
++ SECKEYPrivateKey *channelID);
++
+ extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error);
+
+ /*
+diff -pu a/nss/lib/ssl/sslsecur.c b/nss/lib/ssl/sslsecur.c
+--- a/nss/lib/ssl/sslsecur.c 2013-07-31 12:45:11.497944276 -0700
++++ b/nss/lib/ssl/sslsecur.c 2013-07-31 12:45:50.338515793 -0700
+@@ -1502,6 +1502,42 @@ SSL_RestartHandshakeAfterCertReq(PRFileD
+ return ret;
+ }
+
++SECStatus
++SSL_RestartHandshakeAfterChannelIDReq(PRFileDesc * fd,
++ SECKEYPublicKey * channelIDPub,
++ SECKEYPrivateKey *channelID)
++{
++ sslSocket * ss = ssl_FindSocket(fd);
++ SECStatus ret;
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in"
++ " SSL_RestartHandshakeAfterChannelIDReq",
++ SSL_GETPID(), fd));
++ goto loser;
++ }
++
++
++ ssl_Get1stHandshakeLock(ss);
++
++ if (ss->version < SSL_LIBRARY_VERSION_3_0) {
++ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
++ ssl_Release1stHandshakeLock(ss);
++ goto loser;
++ }
++
++ ret = ssl3_RestartHandshakeAfterChannelIDReq(ss, channelIDPub,
++ channelID);
++ ssl_Release1stHandshakeLock(ss);
++
++ return ret;
++
++loser:
++ SECKEY_DestroyPublicKey(channelIDPub);
++ SECKEY_DestroyPrivateKey(channelID);
++ return SECFailure;
++}
++
+ /* DO NOT USE. This function was exported in ssl.def with the wrong signature;
+ * this implementation exists to maintain link-time compatibility.
+ */
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 12:44:32.017363288 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 12:45:50.348515937 -0700
+@@ -354,6 +354,8 @@ ssl_DupSocket(sslSocket *os)
+ ss->handshakeCallback = os->handshakeCallback;
+ ss->handshakeCallbackData = os->handshakeCallbackData;
+ ss->pkcs11PinArg = os->pkcs11PinArg;
++ ss->getChannelID = os->getChannelID;
++ ss->getChannelIDArg = os->getChannelIDArg;
+
+ /* Create security data */
+ rv = ssl_CopySecurityInfo(ss, os);
+@@ -1754,6 +1756,10 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
+ ss->handshakeCallbackData = sm->handshakeCallbackData;
+ if (sm->pkcs11PinArg)
+ ss->pkcs11PinArg = sm->pkcs11PinArg;
++ if (sm->getChannelID)
++ ss->getChannelID = sm->getChannelID;
++ if (sm->getChannelIDArg)
++ ss->getChannelIDArg = sm->getChannelIDArg;
+ return fd;
+ loser:
+ return NULL;
+@@ -3027,6 +3033,8 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
+ ss->badCertArg = NULL;
+ ss->pkcs11PinArg = NULL;
+ ss->ephemeralECDHKeyPair = NULL;
++ ss->getChannelID = NULL;
++ ss->getChannelIDArg = NULL;
+
+ ssl_ChooseOps(ss);
+ ssl2_InitSocketPolicy(ss);
+diff -pu a/nss/lib/ssl/sslt.h b/nss/lib/ssl/sslt.h
+--- a/nss/lib/ssl/sslt.h 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslt.h 2013-07-31 12:45:50.348515937 -0700
+@@ -184,9 +184,10 @@ typedef enum {
+ ssl_use_srtp_xtn = 14,
+ ssl_session_ticket_xtn = 35,
+ ssl_next_proto_nego_xtn = 13172,
++ ssl_channel_id_xtn = 30031,
+ ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
+ } SSLExtensionType;
+
+-#define SSL_MAX_EXTENSIONS 9
++#define SSL_MAX_EXTENSIONS 10
+
+ #endif /* __sslt_h_ */
diff --git a/chromium/net/third_party/nss/patches/clientauth.patch b/chromium/net/third_party/nss/patches/clientauth.patch
new file mode 100644
index 00000000000..a2c7299c37a
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/clientauth.patch
@@ -0,0 +1,437 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:31:45.326118409 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:35:27.189373289 -0700
+@@ -2284,6 +2284,9 @@ ssl3_ClientAuthTokenPresent(sslSessionID
+ PRBool isPresent = PR_TRUE;
+
+ /* we only care if we are doing client auth */
++ /* If NSS_PLATFORM_CLIENT_AUTH is defined and a platformClientKey is being
++ * used, u.ssl3.clAuthValid will be false and this function will always
++ * return PR_TRUE. */
+ if (!sid || !sid->u.ssl3.clAuthValid) {
+ return PR_TRUE;
+ }
+@@ -5768,25 +5771,36 @@ ssl3_SendCertificateVerify(sslSocket *ss
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+- keyType = ss->ssl3.clientPrivateKey->keyType;
+- rv = ssl3_SignHashes(&hashes, ss->ssl3.clientPrivateKey, &buf, isTLS);
+- if (rv == SECSuccess) {
+- PK11SlotInfo * slot;
+- sslSessionID * sid = ss->sec.ci.sid;
++ if (ss->ssl3.platformClientKey) {
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ keyType = CERT_GetCertKeyType(
++ &ss->ssl3.clientCertificate->subjectPublicKeyInfo);
++ rv = ssl3_PlatformSignHashes(
++ &hashes, ss->ssl3.platformClientKey, &buf, isTLS, keyType);
++ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
++ ss->ssl3.platformClientKey = (PlatformKey)NULL;
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
++ } else {
++ keyType = ss->ssl3.clientPrivateKey->keyType;
++ rv = ssl3_SignHashes(&hashes, ss->ssl3.clientPrivateKey, &buf, isTLS);
++ if (rv == SECSuccess) {
++ PK11SlotInfo * slot;
++ sslSessionID * sid = ss->sec.ci.sid;
+
+- /* Remember the info about the slot that did the signing.
+- ** Later, when doing an SSL restart handshake, verify this.
+- ** These calls are mere accessors, and can't fail.
+- */
+- slot = PK11_GetSlotFromPrivateKey(ss->ssl3.clientPrivateKey);
+- sid->u.ssl3.clAuthSeries = PK11_GetSlotSeries(slot);
+- sid->u.ssl3.clAuthSlotID = PK11_GetSlotID(slot);
+- sid->u.ssl3.clAuthModuleID = PK11_GetModuleID(slot);
+- sid->u.ssl3.clAuthValid = PR_TRUE;
+- PK11_FreeSlot(slot);
++ /* Remember the info about the slot that did the signing.
++ ** Later, when doing an SSL restart handshake, verify this.
++ ** These calls are mere accessors, and can't fail.
++ */
++ slot = PK11_GetSlotFromPrivateKey(ss->ssl3.clientPrivateKey);
++ sid->u.ssl3.clAuthSeries = PK11_GetSlotSeries(slot);
++ sid->u.ssl3.clAuthSlotID = PK11_GetSlotID(slot);
++ sid->u.ssl3.clAuthModuleID = PK11_GetModuleID(slot);
++ sid->u.ssl3.clAuthValid = PR_TRUE;
++ PK11_FreeSlot(slot);
++ }
++ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
++ ss->ssl3.clientPrivateKey = NULL;
+ }
+- SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+- ss->ssl3.clientPrivateKey = NULL;
+ if (rv != SECSuccess) {
+ goto done; /* err code was set by ssl3_SignHashes */
+ }
+@@ -5870,6 +5884,12 @@ ssl3_HandleServerHello(sslSocket *ss, SS
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (ss->ssl3.platformClientKey) {
++ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
++ ss->ssl3.platformClientKey = (PlatformKey)NULL;
++ }
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (temp < 0) {
+@@ -6496,6 +6516,10 @@ ssl3_HandleCertificateRequest(sslSocket
+ SECItem cert_types = {siBuffer, NULL, 0};
+ SECItem algorithms = {siBuffer, NULL, 0};
+ CERTDistNames ca_list;
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ CERTCertList * platform_cert_list = NULL;
++ CERTCertListNode * certNode = NULL;
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle certificate_request handshake",
+ SSL_GETPID(), ss->fd));
+@@ -6512,6 +6536,7 @@ ssl3_HandleCertificateRequest(sslSocket
+ PORT_Assert(ss->ssl3.clientCertChain == NULL);
+ PORT_Assert(ss->ssl3.clientCertificate == NULL);
+ PORT_Assert(ss->ssl3.clientPrivateKey == NULL);
++ PORT_Assert(ss->ssl3.platformClientKey == (PlatformKey)NULL);
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+@@ -6591,6 +6616,18 @@ ssl3_HandleCertificateRequest(sslSocket
+ desc = no_certificate;
+ ss->ssl3.hs.ws = wait_hello_done;
+
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (ss->getPlatformClientAuthData != NULL) {
++ /* XXX Should pass cert_types and algorithms in this call!! */
++ rv = (SECStatus)(*ss->getPlatformClientAuthData)(
++ ss->getPlatformClientAuthDataArg,
++ ss->fd, &ca_list,
++ &platform_cert_list,
++ (void**)&ss->ssl3.platformClientKey,
++ &ss->ssl3.clientCertificate,
++ &ss->ssl3.clientPrivateKey);
++ } else
++#endif
+ if (ss->getClientAuthData != NULL) {
+ /* XXX Should pass cert_types and algorithms in this call!! */
+ rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg,
+@@ -6600,12 +6637,52 @@ ssl3_HandleCertificateRequest(sslSocket
+ } else {
+ rv = SECFailure; /* force it to send a no_certificate alert */
+ }
++
+ switch (rv) {
+ case SECWouldBlock: /* getClientAuthData has put up a dialog box. */
+ ssl3_SetAlwaysBlock(ss);
+ break; /* not an error */
+
+ case SECSuccess:
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (!platform_cert_list || CERT_LIST_EMPTY(platform_cert_list) ||
++ !ss->ssl3.platformClientKey) {
++ if (platform_cert_list) {
++ CERT_DestroyCertList(platform_cert_list);
++ platform_cert_list = NULL;
++ }
++ if (ss->ssl3.platformClientKey) {
++ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
++ ss->ssl3.platformClientKey = (PlatformKey)NULL;
++ }
++ /* Fall through to NSS client auth check */
++ } else {
++ certNode = CERT_LIST_HEAD(platform_cert_list);
++ ss->ssl3.clientCertificate = CERT_DupCertificate(certNode->cert);
++
++ /* Setting ssl3.clientCertChain non-NULL will cause
++ * ssl3_HandleServerHelloDone to call SendCertificate.
++ * Note: clientCertChain should include the EE cert as
++ * clientCertificate is ignored during the actual sending
++ */
++ ss->ssl3.clientCertChain =
++ hack_NewCertificateListFromCertList(platform_cert_list);
++ CERT_DestroyCertList(platform_cert_list);
++ platform_cert_list = NULL;
++ if (ss->ssl3.clientCertChain == NULL) {
++ if (ss->ssl3.clientCertificate != NULL) {
++ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
++ ss->ssl3.clientCertificate = NULL;
++ }
++ if (ss->ssl3.platformClientKey) {
++ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
++ ss->ssl3.platformClientKey = (PlatformKey)NULL;
++ }
++ goto send_no_certificate;
++ }
++ break; /* not an error */
++ }
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ /* check what the callback function returned */
+ if ((!ss->ssl3.clientCertificate) || (!ss->ssl3.clientPrivateKey)) {
+ /* we are missing either the key or cert */
+@@ -6668,6 +6745,10 @@ loser:
+ done:
+ if (arena != NULL)
+ PORT_FreeArena(arena, PR_FALSE);
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (platform_cert_list)
++ CERT_DestroyCertList(platform_cert_list);
++#endif
+ return rv;
+ }
+
+@@ -6749,7 +6830,8 @@ ssl3_SendClientSecondRound(sslSocket *ss
+
+ sendClientCert = !ss->ssl3.sendEmptyCert &&
+ ss->ssl3.clientCertChain != NULL &&
+- ss->ssl3.clientPrivateKey != NULL;
++ (ss->ssl3.platformClientKey ||
++ ss->ssl3.clientPrivateKey != NULL);
+
+ /* We must wait for the server's certificate to be authenticated before
+ * sending the client certificate in order to disclosing the client
+@@ -11465,6 +11547,10 @@ ssl3_DestroySSL3Info(sslSocket *ss)
+
+ if (ss->ssl3.clientPrivateKey != NULL)
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (ss->ssl3.platformClientKey)
++ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ if (ss->ssl3.peerCertArena != NULL)
+ ssl3_CleanupPeerCerts(ss);
+diff -pu a/nss/lib/ssl/ssl3ext.c b/nss/lib/ssl/ssl3ext.c
+--- a/nss/lib/ssl/ssl3ext.c 2013-07-31 12:07:10.964699464 -0700
++++ b/nss/lib/ssl/ssl3ext.c 2013-07-31 12:35:27.189373289 -0700
+@@ -10,8 +10,8 @@
+ #include "nssrenam.h"
+ #include "nss.h"
+ #include "ssl.h"
+-#include "sslproto.h"
+ #include "sslimpl.h"
++#include "sslproto.h"
+ #include "pk11pub.h"
+ #ifdef NO_PKCS11_BYPASS
+ #include "blapit.h"
+diff -pu a/nss/lib/ssl/sslauth.c b/nss/lib/ssl/sslauth.c
+--- a/nss/lib/ssl/sslauth.c 2013-07-31 12:32:29.076760372 -0700
++++ b/nss/lib/ssl/sslauth.c 2013-07-31 12:35:27.189373289 -0700
+@@ -219,6 +219,28 @@ SSL_GetClientAuthDataHook(PRFileDesc *s,
+ return SECSuccess;
+ }
+
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++/* NEED LOCKS IN HERE. */
++SECStatus
++SSL_GetPlatformClientAuthDataHook(PRFileDesc *s,
++ SSLGetPlatformClientAuthData func,
++ void *arg)
++{
++ sslSocket *ss;
++
++ ss = ssl_FindSocket(s);
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in GetPlatformClientAuthDataHook",
++ SSL_GETPID(), s));
++ return SECFailure;
++ }
++
++ ss->getPlatformClientAuthData = func;
++ ss->getPlatformClientAuthDataArg = arg;
++ return SECSuccess;
++}
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
++
+ /* NEED LOCKS IN HERE. */
+ SECStatus
+ SSL_SetPKCS11PinArg(PRFileDesc *s, void *arg)
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:32:29.076760372 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:35:27.199373436 -0700
+@@ -503,6 +503,48 @@ typedef SECStatus (PR_CALLBACK *SSLGetCl
+ SSL_IMPORT SECStatus SSL_GetClientAuthDataHook(PRFileDesc *fd,
+ SSLGetClientAuthData f, void *a);
+
++/*
++ * Prototype for SSL callback to get client auth data from the application,
++ * optionally using the underlying platform's cryptographic primitives.
++ * To use the platform cryptographic primitives, caNames and pRetCerts
++ * should be set. To use NSS, pRetNSSCert and pRetNSSKey should be set.
++ * Returning SECFailure will cause the socket to send no client certificate.
++ * arg - application passed argument
++ * caNames - pointer to distinguished names of CAs that the server likes
++ * pRetCerts - pointer to pointer to list of certs, with the first being
++ * the client cert, and any following being used for chain
++ * building
++ * pRetKey - pointer to native key pointer, for return of key
++ * - Windows: A pointer to a PCERT_KEY_CONTEXT that was allocated
++ * via PORT_Alloc(). Ownership of the PCERT_KEY_CONTEXT
++ * is transferred to NSS, which will free via
++ * PORT_Free().
++ * - Mac OS X: A pointer to a SecKeyRef. Ownership is
++ * transferred to NSS, which will free via CFRelease().
++ * pRetNSSCert - pointer to pointer to NSS cert, for return of cert.
++ * pRetNSSKey - pointer to NSS key pointer, for return of key.
++ */
++typedef SECStatus (PR_CALLBACK *SSLGetPlatformClientAuthData)(void *arg,
++ PRFileDesc *fd,
++ CERTDistNames *caNames,
++ CERTCertList **pRetCerts,/*return */
++ void **pRetKey,/* return */
++ CERTCertificate **pRetNSSCert,/*return */
++ SECKEYPrivateKey **pRetNSSKey);/* return */
++
++/*
++ * Set the client side callback for SSL to retrieve user's private key
++ * and certificate.
++ * Note: If a platform client auth callback is set, the callback configured by
++ * SSL_GetClientAuthDataHook, if any, will not be called.
++ *
++ * fd - the file descriptor for the connection in question
++ * f - the application's callback that delivers the key and cert
++ * a - application specific data
++ */
++SSL_IMPORT SECStatus
++SSL_GetPlatformClientAuthDataHook(PRFileDesc *fd,
++ SSLGetPlatformClientAuthData f, void *a);
+
+ /*
+ ** SNI extension processing callback function.
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 12:31:45.326118409 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 12:35:27.199373436 -0700
+@@ -20,6 +20,7 @@
+ #include "sslerr.h"
+ #include "ssl3prot.h"
+ #include "hasht.h"
++#include "keythi.h"
+ #include "nssilock.h"
+ #include "pkcs11t.h"
+ #if defined(XP_UNIX) || defined(XP_BEOS)
+@@ -31,6 +32,15 @@
+
+ #include "sslt.h" /* for some formerly private types, now public */
+
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++#if defined(XP_WIN32)
++#include <windows.h>
++#include <wincrypt.h>
++#elif defined(XP_MACOSX)
++#include <Security/Security.h>
++#endif
++#endif
++
+ /* to make some of these old enums public without namespace pollution,
+ ** it was necessary to prepend ssl_ to the names.
+ ** These #defines preserve compatibility with the old code here in libssl.
+@@ -444,6 +454,14 @@ typedef SECStatus (*SSLCompressor)(void
+ int inlen);
+ typedef SECStatus (*SSLDestroy)(void *context, PRBool freeit);
+
++#if defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_WIN32)
++typedef PCERT_KEY_CONTEXT PlatformKey;
++#elif defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_MACOSX)
++typedef SecKeyRef PlatformKey;
++#else
++typedef void *PlatformKey;
++#endif
++
+
+
+ /*
+@@ -896,6 +914,10 @@ struct ssl3StateStr {
+
+ CERTCertificate * clientCertificate; /* used by client */
+ SECKEYPrivateKey * clientPrivateKey; /* used by client */
++ /* platformClientKey is present even when NSS_PLATFORM_CLIENT_AUTH is not
++ * defined in order to allow cleaner conditional code.
++ * At most one of clientPrivateKey and platformClientKey may be set. */
++ PlatformKey platformClientKey; /* used by client */
+ CERTCertificateList *clientCertChain; /* used by client */
+ PRBool sendEmptyCert; /* used by client */
+
+@@ -1153,6 +1175,10 @@ const unsigned char * preferredCipher;
+ void *authCertificateArg;
+ SSLGetClientAuthData getClientAuthData;
+ void *getClientAuthDataArg;
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ SSLGetPlatformClientAuthData getPlatformClientAuthData;
++ void *getPlatformClientAuthDataArg;
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ SSLSNISocketConfig sniSocketConfig;
+ void *sniSocketConfigArg;
+ SSLBadCertHandler handleBadCert;
+@@ -1737,7 +1763,6 @@ extern void ssl_FreePRSocket(PRFileDesc
+ * various ciphers */
+ extern int ssl3_config_match_init(sslSocket *);
+
+-
+ /* Create a new ref counted key pair object from two keys. */
+ extern ssl3KeyPair * ssl3_NewKeyPair( SECKEYPrivateKey * privKey,
+ SECKEYPublicKey * pubKey);
+@@ -1777,6 +1802,26 @@ extern SECStatus ssl_InitSessionCacheLoc
+
+ extern SECStatus ssl_FreeSessionCacheLocks(void);
+
++/***************** platform client auth ****************/
++
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++// Releases the platform key.
++extern void ssl_FreePlatformKey(PlatformKey key);
++
++// Implement the client CertificateVerify message for SSL3/TLS1.0
++extern SECStatus ssl3_PlatformSignHashes(SSL3Hashes *hash,
++ PlatformKey key, SECItem *buf,
++ PRBool isTLS, KeyType keyType);
++
++// Converts a CERTCertList* (A collection of CERTCertificates) into a
++// CERTCertificateList* (A collection of SECItems), or returns NULL if
++// it cannot be converted.
++// This is to allow the platform-supplied chain to be created with purely
++// public API functions, using the preferred CERTCertList mutators, rather
++// pushing this hack to clients.
++extern CERTCertificateList* hack_NewCertificateListFromCertList(
++ CERTCertList* list);
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ /**************** DTLS-specific functions **************/
+ extern void dtls_FreeQueuedMessage(DTLSQueuedMessage *msg);
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 12:28:39.283413269 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 12:35:27.199373436 -0700
+@@ -343,6 +343,10 @@ ssl_DupSocket(sslSocket *os)
+ ss->authCertificateArg = os->authCertificateArg;
+ ss->getClientAuthData = os->getClientAuthData;
+ ss->getClientAuthDataArg = os->getClientAuthDataArg;
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ ss->getPlatformClientAuthData = os->getPlatformClientAuthData;
++ ss->getPlatformClientAuthDataArg = os->getPlatformClientAuthDataArg;
++#endif
+ ss->sniSocketConfig = os->sniSocketConfig;
+ ss->sniSocketConfigArg = os->sniSocketConfigArg;
+ ss->handleBadCert = os->handleBadCert;
+@@ -1730,6 +1734,12 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
+ ss->getClientAuthData = sm->getClientAuthData;
+ if (sm->getClientAuthDataArg)
+ ss->getClientAuthDataArg = sm->getClientAuthDataArg;
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ if (sm->getPlatformClientAuthData)
++ ss->getPlatformClientAuthData = sm->getPlatformClientAuthData;
++ if (sm->getPlatformClientAuthDataArg)
++ ss->getPlatformClientAuthDataArg = sm->getPlatformClientAuthDataArg;
++#endif
+ if (sm->sniSocketConfig)
+ ss->sniSocketConfig = sm->sniSocketConfig;
+ if (sm->sniSocketConfigArg)
+@@ -2980,6 +2990,10 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
+ ss->sniSocketConfig = NULL;
+ ss->sniSocketConfigArg = NULL;
+ ss->getClientAuthData = NULL;
++#ifdef NSS_PLATFORM_CLIENT_AUTH
++ ss->getPlatformClientAuthData = NULL;
++ ss->getPlatformClientAuthDataArg = NULL;
++#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ ss->handleBadCert = NULL;
+ ss->badCertArg = NULL;
+ ss->pkcs11PinArg = NULL;
diff --git a/chromium/net/third_party/nss/patches/didhandshakeresume.patch b/chromium/net/third_party/nss/patches/didhandshakeresume.patch
new file mode 100644
index 00000000000..fa681ebb3d8
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/didhandshakeresume.patch
@@ -0,0 +1,37 @@
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:40:14.503586299 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:40:37.913929474 -0700
+@@ -940,6 +940,9 @@ SSL_IMPORT SECStatus SSL_HandshakeNegoti
+ SSLExtensionType extId,
+ PRBool *yes);
+
++SSL_IMPORT SECStatus SSL_HandshakeResumedSession(PRFileDesc *fd,
++ PRBool *last_handshake_resumed);
++
+ /*
+ ** How long should we wait before retransmitting the next flight of
+ ** the DTLS handshake? Returns SECFailure if not DTLS or not in a
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 12:40:14.503586299 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 12:40:37.913929474 -0700
+@@ -1919,6 +1919,20 @@ SSL_PeerStapledOCSPResponses(PRFileDesc
+ return &ss->sec.ci.sid->peerCertStatus;
+ }
+
++SECStatus
++SSL_HandshakeResumedSession(PRFileDesc *fd, PRBool *handshake_resumed) {
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_HandshakeResumedSession",
++ SSL_GETPID(), fd));
++ return SECFailure;
++ }
++
++ *handshake_resumed = ss->ssl3.hs.isResuming;
++ return SECSuccess;
++}
++
+ /************************************************************************/
+ /* The following functions are the TOP LEVEL SSL functions.
+ ** They all get called through the NSPRIOMethods table below.
diff --git a/chromium/net/third_party/nss/patches/ecpointform.patch b/chromium/net/third_party/nss/patches/ecpointform.patch
new file mode 100644
index 00000000000..b63c58db289
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/ecpointform.patch
@@ -0,0 +1,19 @@
+diff -pu a/nss/lib/ssl/ssl3ecc.c b/nss/lib/ssl/ssl3ecc.c
+--- a/nss/lib/ssl/ssl3ecc.c 2013-07-31 12:07:10.964699464 -0700
++++ b/nss/lib/ssl/ssl3ecc.c 2013-07-31 14:10:54.313607174 -0700
+@@ -32,6 +32,15 @@
+
+ #ifdef NSS_ENABLE_ECC
+
++/*
++ * In NSS 3.13.2 the definition of the EC_POINT_FORM_UNCOMPRESSED macro
++ * was moved from the internal header ec.h to the public header blapit.h.
++ * Define the macro here when compiling against older system NSS headers.
++ */
++#ifndef EC_POINT_FORM_UNCOMPRESSED
++#define EC_POINT_FORM_UNCOMPRESSED 0x04
++#endif
++
+ #ifndef PK11_SETATTRS
+ #define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \
+ (x)->pValue=(v); (x)->ulValueLen = (l);
diff --git a/chromium/net/third_party/nss/patches/getrequestedclientcerttypes.patch b/chromium/net/third_party/nss/patches/getrequestedclientcerttypes.patch
new file mode 100644
index 00000000000..7b8cd061e2a
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/getrequestedclientcerttypes.patch
@@ -0,0 +1,87 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:40:14.493586151 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:42:42.035748760 -0700
+@@ -6544,6 +6544,9 @@ ssl3_HandleCertificateRequest(sslSocket
+ if (rv != SECSuccess)
+ goto loser; /* malformed, alert has been sent */
+
++ PORT_Assert(!ss->requestedCertTypes);
++ ss->requestedCertTypes = &cert_types;
++
+ if (isTLS12) {
+ rv = ssl3_ConsumeHandshakeVariable(ss, &algorithms, 2, &b, &length);
+ if (rv != SECSuccess)
+@@ -6743,6 +6746,7 @@ loser:
+ PORT_SetError(errCode);
+ rv = SECFailure;
+ done:
++ ss->requestedCertTypes = NULL;
+ if (arena != NULL)
+ PORT_FreeArena(arena, PR_FALSE);
+ #ifdef NSS_PLATFORM_CLIENT_AUTH
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:40:53.784162112 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:41:57.515096255 -0700
+@@ -732,6 +732,16 @@ SSL_IMPORT SECStatus SSL_ReHandshakeWith
+ PRBool flushCache,
+ PRIntervalTime timeout);
+
++/* Returns a SECItem containing the certificate_types field of the
++** CertificateRequest message. Each byte of the data is a TLS
++** ClientCertificateType value, and they are ordered from most preferred to
++** least. This function should only be called from the
++** SSL_GetClientAuthDataHook callback, and will return NULL if called at any
++** other time. The returned value is valid only until the callback returns, and
++** should not be freed.
++*/
++SSL_IMPORT const SECItem *
++SSL_GetRequestedClientCertificateTypes(PRFileDesc *fd);
+
+ #ifdef SSL_DEPRECATED_FUNCTION
+ /* deprecated!
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 12:40:14.503586299 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 12:41:57.515096255 -0700
+@@ -1168,6 +1168,10 @@ struct sslSocketStr {
+ unsigned int sizeCipherSpecs;
+ const unsigned char * preferredCipher;
+
++ /* TLS ClientCertificateTypes requested during HandleCertificateRequest. */
++ /* Will be NULL at all other times. */
++ const SECItem *requestedCertTypes;
++
+ ssl3KeyPair * stepDownKeyPair; /* RSA step down keys */
+
+ /* Callbacks */
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 12:40:53.784162112 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 12:41:57.515096255 -0700
+@@ -1933,6 +1933,20 @@ SSL_HandshakeResumedSession(PRFileDesc *
+ return SECSuccess;
+ }
+
++const SECItem *
++SSL_GetRequestedClientCertificateTypes(PRFileDesc *fd)
++{
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in "
++ "SSL_GetRequestedClientCertificateTypes", SSL_GETPID(), fd));
++ return NULL;
++ }
++
++ return ss->requestedCertTypes;
++}
++
+ /************************************************************************/
+ /* The following functions are the TOP LEVEL SSL functions.
+ ** They all get called through the NSPRIOMethods table below.
+@@ -2995,6 +3009,7 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
+ sc->serverKeyBits = 0;
+ ss->certStatusArray[i] = NULL;
+ }
++ ss->requestedCertTypes = NULL;
+ ss->stepDownKeyPair = NULL;
+ ss->dbHandle = CERT_GetDefaultCertDB();
+
diff --git a/chromium/net/third_party/nss/patches/negotiatedextension.patch b/chromium/net/third_party/nss/patches/negotiatedextension.patch
new file mode 100644
index 00000000000..bbcd2e52f63
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/negotiatedextension.patch
@@ -0,0 +1,27 @@
+diff -pu a/nss/lib/ssl/sslreveal.c b/nss/lib/ssl/sslreveal.c
+--- a/nss/lib/ssl/sslreveal.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslreveal.c 2013-07-31 12:41:08.684380521 -0700
+@@ -77,7 +77,6 @@ SSL_HandshakeNegotiatedExtension(PRFileD
+ {
+ /* some decisions derived from SSL_GetChannelInfo */
+ sslSocket * sslsocket = NULL;
+- PRBool enoughFirstHsDone = PR_FALSE;
+
+ if (!pYes) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+@@ -93,14 +92,8 @@ SSL_HandshakeNegotiatedExtension(PRFileD
+
+ *pYes = PR_FALSE;
+
+- if (sslsocket->firstHsDone) {
+- enoughFirstHsDone = PR_TRUE;
+- } else if (sslsocket->ssl3.initialized && ssl3_CanFalseStart(sslsocket)) {
+- enoughFirstHsDone = PR_TRUE;
+- }
+-
+ /* according to public API SSL_GetChannelInfo, this doesn't need a lock */
+- if (sslsocket->opt.useSecurity && enoughFirstHsDone) {
++ if (sslsocket->opt.useSecurity) {
+ if (sslsocket->ssl3.initialized) { /* SSL3 and TLS */
+ /* now we know this socket went through ssl3_InitState() and
+ * ss->xtnData got initialized, which is the only member accessed by
diff --git a/chromium/net/third_party/nss/patches/peercertchain.patch b/chromium/net/third_party/nss/patches/peercertchain.patch
new file mode 100644
index 00000000000..0ddd316f85f
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/peercertchain.patch
@@ -0,0 +1,67 @@
+diff -pu a/nss/lib/ssl/sslauth.c b/nss/lib/ssl/sslauth.c
+--- a/nss/lib/ssl/sslauth.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslauth.c 2013-07-31 12:32:07.996451064 -0700
+@@ -28,6 +28,41 @@ SSL_PeerCertificate(PRFileDesc *fd)
+ }
+
+ /* NEED LOCKS IN HERE. */
++SECStatus
++SSL_PeerCertificateChain(PRFileDesc *fd, CERTCertificate **certs,
++ unsigned int *numCerts, unsigned int maxNumCerts)
++{
++ sslSocket *ss;
++ ssl3CertNode* cur;
++
++ ss = ssl_FindSocket(fd);
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in PeerCertificateChain",
++ SSL_GETPID(), fd));
++ return SECFailure;
++ }
++ if (!ss->opt.useSecurity)
++ return SECFailure;
++
++ if (ss->sec.peerCert == NULL) {
++ *numCerts = 0;
++ return SECSuccess;
++ }
++
++ *numCerts = 1; /* for the leaf certificate */
++ if (maxNumCerts > 0)
++ certs[0] = CERT_DupCertificate(ss->sec.peerCert);
++
++ for (cur = ss->ssl3.peerCertChain; cur; cur = cur->next) {
++ if (*numCerts < maxNumCerts)
++ certs[*numCerts] = CERT_DupCertificate(cur->cert);
++ (*numCerts)++;
++ }
++
++ return SECSuccess;
++}
++
++/* NEED LOCKS IN HERE. */
+ CERTCertificate *
+ SSL_LocalCertificate(PRFileDesc *fd)
+ {
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:07:10.964699464 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:32:07.996451065 -0700
+@@ -426,6 +426,18 @@ SSL_SetStapledOCSPResponses(PRFileDesc *
+ SSLKEAType kea);
+
+ /*
++** Return references to the certificates presented by the SSL peer.
++** |maxNumCerts| must contain the size of the |certs| array. On successful
++** return, |*numCerts| contains the number of certificates available and
++** |certs| will contain references to as many certificates as would fit.
++** Therefore if |*numCerts| contains a value less than or equal to
++** |maxNumCerts|, then all certificates were returned.
++*/
++SSL_IMPORT SECStatus SSL_PeerCertificateChain(
++ PRFileDesc *fd, CERTCertificate **certs,
++ unsigned int *numCerts, unsigned int maxNumCerts);
++
++/*
+ ** Authenticate certificate hook. Called when a certificate comes in
+ ** (because of SSL_REQUIRE_CERTIFICATE in SSL_Enable) to authenticate the
+ ** certificate.
diff --git a/chromium/net/third_party/nss/patches/renegoscsv.patch b/chromium/net/third_party/nss/patches/renegoscsv.patch
new file mode 100644
index 00000000000..ed3b96de203
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/renegoscsv.patch
@@ -0,0 +1,15 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:07:10.964699464 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:29:07.903829188 -0700
+@@ -4773,9 +4773,9 @@ ssl3_SendClientHello(sslSocket *ss, PRBo
+ return SECFailure; /* ssl3_config_match_init has set error code. */
+
+ /* HACK for SCSV in SSL 3.0. On initial handshake, prepend SCSV,
+- * only if we're willing to complete an SSL 3.0 handshake.
++ * only if TLS is disabled.
+ */
+- if (!ss->firstHsDone && ss->vrange.min == SSL_LIBRARY_VERSION_3_0) {
++ if (!ss->firstHsDone && !isTLS) {
+ /* Must set this before calling Hello Extension Senders,
+ * to suppress sending of empty RI extension.
+ */
diff --git a/chromium/net/third_party/nss/patches/restartclientauth.patch b/chromium/net/third_party/nss/patches/restartclientauth.patch
new file mode 100644
index 00000000000..84f18f391e0
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/restartclientauth.patch
@@ -0,0 +1,209 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 12:44:31.987362835 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 12:44:50.987642452 -0700
+@@ -6756,6 +6756,85 @@ done:
+ return rv;
+ }
+
++/*
++ * attempt to restart the handshake after asynchronously handling
++ * a request for the client's certificate.
++ *
++ * inputs:
++ * cert Client cert chosen by application.
++ * Note: ssl takes this reference, and does not bump the
++ * reference count. The caller should drop its reference
++ * without calling CERT_DestroyCert after calling this function.
++ *
++ * key Private key associated with cert. This function takes
++ * ownership of the private key, so the caller should drop its
++ * reference without destroying the private key after this
++ * function returns.
++ *
++ * certChain DER-encoded certs, client cert and its signers.
++ * Note: ssl takes this reference, and does not copy the chain.
++ * The caller should drop its reference without destroying the
++ * chain. SSL will free the chain when it is done with it.
++ *
++ * Return value: XXX
++ *
++ * XXX This code only works on the initial handshake on a connection, XXX
++ * It does not work on a subsequent handshake (redo).
++ *
++ * Caller holds 1stHandshakeLock.
++ */
++SECStatus
++ssl3_RestartHandshakeAfterCertReq(sslSocket * ss,
++ CERTCertificate * cert,
++ SECKEYPrivateKey * key,
++ CERTCertificateList *certChain)
++{
++ SECStatus rv = SECSuccess;
++
++ /* XXX This code only works on the initial handshake on a connection,
++ ** XXX It does not work on a subsequent handshake (redo).
++ */
++ if (ss->handshake != 0) {
++ ss->handshake = ssl_GatherRecord1stHandshake;
++ ss->ssl3.clientCertificate = cert;
++ ss->ssl3.clientPrivateKey = key;
++ ss->ssl3.clientCertChain = certChain;
++ if (!cert || !key || !certChain) {
++ /* we are missing the key, cert, or cert chain */
++ if (ss->ssl3.clientCertificate) {
++ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
++ ss->ssl3.clientCertificate = NULL;
++ }
++ if (ss->ssl3.clientPrivateKey) {
++ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
++ ss->ssl3.clientPrivateKey = NULL;
++ }
++ if (ss->ssl3.clientCertChain != NULL) {
++ CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
++ ss->ssl3.clientCertChain = NULL;
++ }
++ if (ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0) {
++ ss->ssl3.sendEmptyCert = PR_TRUE;
++ } else {
++ (void)SSL3_SendAlert(ss, alert_warning, no_certificate);
++ }
++ }
++ } else {
++ if (cert) {
++ CERT_DestroyCertificate(cert);
++ }
++ if (key) {
++ SECKEY_DestroyPrivateKey(key);
++ }
++ if (certChain) {
++ CERT_DestroyCertificateList(certChain);
++ }
++ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
++ rv = SECFailure;
++ }
++ return rv;
++}
++
+ PRBool
+ ssl3_CanFalseStart(sslSocket *ss) {
+ PRBool rv;
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 12:44:31.987362835 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 12:44:50.987642452 -0700
+@@ -366,6 +366,11 @@ SSL_IMPORT SECStatus SSL_ForceHandshake(
+ SSL_IMPORT SECStatus SSL_ForceHandshakeWithTimeout(PRFileDesc *fd,
+ PRIntervalTime timeout);
+
++SSL_IMPORT SECStatus SSL_RestartHandshakeAfterCertReq(PRFileDesc *fd,
++ CERTCertificate *cert,
++ SECKEYPrivateKey *key,
++ CERTCertificateList *certChain);
++
+ /*
+ ** Query security status of socket. *on is set to one if security is
+ ** enabled. *keySize will contain the stream key size used. *issuer will
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 12:44:31.997362988 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 12:44:50.987642452 -0700
+@@ -1513,16 +1513,17 @@ extern SECStatus ssl3_MasterKeyDeriveBy
+ /* These functions are called from secnav, even though they're "private". */
+
+ extern int ssl2_SendErrorMessage(struct sslSocketStr *ss, int error);
+-extern int SSL_RestartHandshakeAfterCertReq(struct sslSocketStr *ss,
+- CERTCertificate *cert,
+- SECKEYPrivateKey *key,
+- CERTCertificateList *certChain);
+ extern sslSocket *ssl_FindSocket(PRFileDesc *fd);
+ extern void ssl_FreeSocket(struct sslSocketStr *ssl);
+ extern SECStatus SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level,
+ SSL3AlertDescription desc);
+ extern SECStatus ssl3_DecodeError(sslSocket *ss);
+
++extern SECStatus ssl3_RestartHandshakeAfterCertReq(sslSocket * ss,
++ CERTCertificate * cert,
++ SECKEYPrivateKey * key,
++ CERTCertificateList *certChain);
++
+ extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error);
+
+ /*
+diff -pu a/nss/lib/ssl/sslsecur.c b/nss/lib/ssl/sslsecur.c
+--- a/nss/lib/ssl/sslsecur.c 2013-07-31 12:28:39.283413269 -0700
++++ b/nss/lib/ssl/sslsecur.c 2013-07-31 12:44:50.987642452 -0700
+@@ -1436,17 +1436,70 @@ SSL_CertDBHandleSet(PRFileDesc *fd, CERT
+ return SECSuccess;
+ }
+
+-/* DO NOT USE. This function was exported in ssl.def with the wrong signature;
+- * this implementation exists to maintain link-time compatibility.
++/*
++ * attempt to restart the handshake after asynchronously handling
++ * a request for the client's certificate.
++ *
++ * inputs:
++ * cert Client cert chosen by application.
++ * Note: ssl takes this reference, and does not bump the
++ * reference count. The caller should drop its reference
++ * without calling CERT_DestroyCertificate after calling this
++ * function.
++ *
++ * key Private key associated with cert. This function takes
++ * ownership of the private key, so the caller should drop its
++ * reference without destroying the private key after this
++ * function returns.
++ *
++ * certChain Chain of signers for cert.
++ * Note: ssl takes this reference, and does not copy the chain.
++ * The caller should drop its reference without destroying the
++ * chain. SSL will free the chain when it is done with it.
++ *
++ * Return value: XXX
++ *
++ * XXX This code only works on the initial handshake on a connection, XXX
++ * It does not work on a subsequent handshake (redo).
+ */
+-int
+-SSL_RestartHandshakeAfterCertReq(sslSocket * ss,
++SECStatus
++SSL_RestartHandshakeAfterCertReq(PRFileDesc * fd,
+ CERTCertificate * cert,
+ SECKEYPrivateKey * key,
+ CERTCertificateList *certChain)
+ {
+- PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+- return -1;
++ sslSocket * ss = ssl_FindSocket(fd);
++ SECStatus ret;
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_RestartHandshakeAfterCertReq",
++ SSL_GETPID(), fd));
++ if (cert) {
++ CERT_DestroyCertificate(cert);
++ }
++ if (key) {
++ SECKEY_DestroyPrivateKey(key);
++ }
++ if (certChain) {
++ CERT_DestroyCertificateList(certChain);
++ }
++ return SECFailure;
++ }
++
++ ssl_Get1stHandshakeLock(ss); /************************************/
++
++ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
++ ret = ssl3_RestartHandshakeAfterCertReq(ss, cert, key, certChain);
++ } else {
++ if (certChain != NULL) {
++ CERT_DestroyCertificateList(certChain);
++ }
++ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
++ ret = SECFailure;
++ }
++
++ ssl_Release1stHandshakeLock(ss); /************************************/
++ return ret;
+ }
+
+ /* DO NOT USE. This function was exported in ssl.def with the wrong signature;
diff --git a/chromium/net/third_party/nss/patches/secitemarray.patch b/chromium/net/third_party/nss/patches/secitemarray.patch
new file mode 100644
index 00000000000..bb0d2ef4ff8
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/secitemarray.patch
@@ -0,0 +1,42 @@
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 14:10:35.113325316 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 14:13:25.995834521 -0700
+@@ -1293,6 +1293,15 @@ extern sslSessionIDUncacheFunc ssl_sid_u
+
+ SEC_BEGIN_PROTOS
+
++/* Functions for handling SECItemArrays, added in NSS 3.15 */
++extern SECItemArray *SECITEM_AllocArray(PLArenaPool *arena,
++ SECItemArray *array,
++ unsigned int len);
++extern SECItemArray *SECITEM_DupArray(PLArenaPool *arena,
++ const SECItemArray *from);
++extern void SECITEM_FreeArray(SECItemArray *array, PRBool freeit);
++extern void SECITEM_ZfreeArray(SECItemArray *array, PRBool freeit);
++
+ /* Internal initialization and installation of the SSL error tables */
+ extern SECStatus ssl_Init(void);
+ extern SECStatus ssl_InitializePRErrorTable(void);
+diff -pu a/nss/lib/ssl/sslt.h b/nss/lib/ssl/sslt.h
+--- a/nss/lib/ssl/sslt.h 2013-07-31 14:10:00.342814862 -0700
++++ b/nss/lib/ssl/sslt.h 2013-07-31 14:13:25.995834521 -0700
+@@ -10,6 +10,19 @@
+
+ #include "prtypes.h"
+
++/* SECItemArray is added in NSS 3.15. Define the type if compiling
++** against an older version of NSS.
++*/
++#include "nssutil.h"
++#if NSSUTIL_VMAJOR == 3 && NSSUTIL_VMINOR < 15
++typedef struct SECItemArrayStr SECItemArray;
++
++struct SECItemArrayStr {
++ SECItem *items;
++ unsigned int len;
++};
++#endif /* NSSUTIL_VMAJOR == 3 && NSSUTIL_VMINOR < 15 */
++
+ typedef struct SSL3StatisticsStr {
+ /* statistics from ssl3_SendClientHello (sch) */
+ long sch_sid_cache_hits;
diff --git a/chromium/net/third_party/nss/patches/secretexporterlocks.patch b/chromium/net/third_party/nss/patches/secretexporterlocks.patch
new file mode 100644
index 00000000000..f605cd5124f
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/secretexporterlocks.patch
@@ -0,0 +1,44 @@
+diff -pu a/nss/lib/ssl/sslinfo.c b/nss/lib/ssl/sslinfo.c
+--- a/nss/lib/ssl/sslinfo.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslinfo.c 2013-07-31 14:11:28.834113906 -0700
+@@ -349,8 +349,13 @@ SSL_ExportKeyingMaterial(PRFileDesc *fd,
+ return SECFailure;
+ }
+
++ ssl_GetRecvBufLock(ss);
++ ssl_GetSSL3HandshakeLock(ss);
++
+ if (ss->version < SSL_LIBRARY_VERSION_3_1_TLS) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION);
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+ }
+
+@@ -361,13 +366,17 @@ SSL_ExportKeyingMaterial(PRFileDesc *fd,
+ }
+ val = PORT_Alloc(valLen);
+ if (!val) {
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+ }
+ i = 0;
++
+ PORT_Memcpy(val + i, &ss->ssl3.hs.client_random.rand, SSL3_RANDOM_LENGTH);
+ i += SSL3_RANDOM_LENGTH;
+ PORT_Memcpy(val + i, &ss->ssl3.hs.server_random.rand, SSL3_RANDOM_LENGTH);
+ i += SSL3_RANDOM_LENGTH;
++
+ if (hasContext) {
+ val[i++] = contextLen >> 8;
+ val[i++] = contextLen;
+@@ -388,6 +397,8 @@ SSL_ExportKeyingMaterial(PRFileDesc *fd,
+ valLen, out, outLen);
+ }
+ ssl_ReleaseSpecReadLock(ss);
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ ssl_ReleaseRecvBufLock(ss);
+
+ PORT_ZFree(val, valLen);
+ return rv;
diff --git a/chromium/net/third_party/nss/patches/sslsock_903565.patch b/chromium/net/third_party/nss/patches/sslsock_903565.patch
new file mode 100644
index 00000000000..b744c584a3d
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/sslsock_903565.patch
@@ -0,0 +1,20 @@
+diff --git a/net/third_party/nss/ssl/sslsock.c b/net/third_party/nss/ssl/sslsock.c
+index fd71aee..db0da5f 100644
+--- a/net/third_party/nss/ssl/sslsock.c
++++ b/net/third_party/nss/ssl/sslsock.c
+@@ -3057,6 +3057,7 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
+ ss->opt.useSocks = PR_FALSE;
+ ss->opt.noLocks = !makeLocks;
+ ss->vrange = *VERSIONS_DEFAULTS(protocolVariant);
++ ss->protocolVariant = protocolVariant;
+
+ ss->peerID = NULL;
+ ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
+@@ -3117,7 +3118,6 @@ loser:
+ PORT_Free(ss);
+ ss = NULL;
+ }
+- ss->protocolVariant = protocolVariant;
+ }
+ return ss;
+ }
diff --git a/chromium/net/third_party/nss/patches/suitebonly.patch b/chromium/net/third_party/nss/patches/suitebonly.patch
new file mode 100644
index 00000000000..762606dca30
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/suitebonly.patch
@@ -0,0 +1,21 @@
+diff -pu a/nss/lib/ssl/ssl3ecc.c b/nss/lib/ssl/ssl3ecc.c
+--- a/nss/lib/ssl/ssl3ecc.c 2013-07-31 14:11:13.633890784 -0700
++++ b/nss/lib/ssl/ssl3ecc.c 2013-07-31 14:12:59.855450386 -0700
+@@ -1078,6 +1078,7 @@ static const PRUint8 ecPtFmt[6] = {
+ static PRBool
+ ssl3_SuiteBOnly(sslSocket *ss)
+ {
++#if 0
+ /* See if we can support small curves (like 163). If not, assume we can
+ * only support Suite-B curves (P-256, P-384, P-521). */
+ PK11SlotInfo *slot =
+@@ -1091,6 +1092,9 @@ ssl3_SuiteBOnly(sslSocket *ss)
+ /* we can, presume we can do all curves */
+ PK11_FreeSlot(slot);
+ return PR_FALSE;
++#else
++ return PR_TRUE;
++#endif
+ }
+
+ /* Send our "canned" (precompiled) Supported Elliptic Curves extension,
diff --git a/chromium/net/third_party/nss/patches/tls12chromium.patch b/chromium/net/third_party/nss/patches/tls12chromium.patch
new file mode 100644
index 00000000000..671b6ca3e13
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/tls12chromium.patch
@@ -0,0 +1,100 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 14:12:19.414856329 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 14:13:56.916288878 -0700
+@@ -31,6 +31,15 @@
+ #include "blapi.h"
+ #endif
+
++/* This is a bodge to allow this code to be compiled against older NSS headers
++ * that don't contain the TLS 1.2 changes. */
++#ifndef CKM_NSS_TLS_PRF_GENERAL_SHA256
++#define CKM_NSS_TLS_PRF_GENERAL_SHA256 (CKM_NSS + 21)
++#define CKM_NSS_TLS_MASTER_KEY_DERIVE_SHA256 (CKM_NSS + 22)
++#define CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256 (CKM_NSS + 23)
++#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
++#endif
++
+ #include <stdio.h>
+ #ifdef NSS_ENABLE_ZLIB
+ #include "zlib.h"
+diff -pu a/nss/lib/ssl/ssl3ecc.c b/nss/lib/ssl/ssl3ecc.c
+--- a/nss/lib/ssl/ssl3ecc.c 2013-07-31 14:13:15.115674638 -0700
++++ b/nss/lib/ssl/ssl3ecc.c 2013-07-31 14:13:56.916288878 -0700
+@@ -30,6 +30,12 @@
+
+ #include <stdio.h>
+
++/* This is a bodge to allow this code to be compiled against older NSS headers
++ * that don't contain the TLS 1.2 changes. */
++#ifndef CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256
++#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
++#endif
++
+ #ifdef NSS_ENABLE_ECC
+
+ /*
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 14:10:35.113325316 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 14:16:39.538677991 -0700
+@@ -17,8 +17,15 @@
+ #ifndef NO_PKCS11_BYPASS
+ #include "blapi.h"
+ #endif
++#include "pk11pub.h"
+ #include "nss.h"
+
++/* This is a bodge to allow this code to be compiled against older NSS headers
++ * that don't contain the TLS 1.2 changes. */
++#ifndef CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256
++#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
++#endif
++
+ #define SET_ERROR_CODE /* reminder */
+
+ struct cipherPolicyStr {
+@@ -1900,6 +1907,24 @@ SSL_VersionRangeGet(PRFileDesc *fd, SSLV
+ return SECSuccess;
+ }
+
++static PRCallOnceType checkTLS12TokenOnce;
++static PRBool tls12TokenExists;
++
++static PRStatus
++ssl_CheckTLS12Token(void)
++{
++ tls12TokenExists =
++ PK11_TokenExists(CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256);
++ return PR_SUCCESS;
++}
++
++static PRBool
++ssl_TLS12TokenExists(void)
++{
++ (void) PR_CallOnce(&checkTLS12TokenOnce, ssl_CheckTLS12Token);
++ return tls12TokenExists;
++}
++
+ SECStatus
+ SSL_VersionRangeSet(PRFileDesc *fd, const SSLVersionRange *vrange)
+ {
+@@ -1920,6 +1945,20 @@ SSL_VersionRangeSet(PRFileDesc *fd, cons
+ ssl_GetSSL3HandshakeLock(ss);
+
+ ss->vrange = *vrange;
++ /* If we don't have a sufficiently up-to-date softoken then we cannot do
++ * TLS 1.2. */
++ if (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_2 &&
++ !ssl_TLS12TokenExists()) {
++ /* If the user requested a minimum version of 1.2, then we don't
++ * silently downgrade. */
++ if (ss->vrange.min >= SSL_LIBRARY_VERSION_TLS_1_2) {
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ ssl_Release1stHandshakeLock(ss);
++ PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
++ return SECFailure;
++ }
++ ss->vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
++ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
diff --git a/chromium/net/third_party/nss/patches/tlsunique.patch b/chromium/net/third_party/nss/patches/tlsunique.patch
new file mode 100644
index 00000000000..69370a35e30
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/tlsunique.patch
@@ -0,0 +1,149 @@
+diff -pu a/nss/lib/ssl/ssl3con.c b/nss/lib/ssl/ssl3con.c
+--- a/nss/lib/ssl/ssl3con.c 2013-07-31 14:10:00.332814714 -0700
++++ b/nss/lib/ssl/ssl3con.c 2013-07-31 14:10:12.202988980 -0700
+@@ -11732,6 +11732,68 @@ ssl3_InitSocketPolicy(sslSocket *ss)
+ PORT_Memcpy(ss->cipherSuites, cipherSuites, sizeof cipherSuites);
+ }
+
++SECStatus
++ssl3_GetTLSUniqueChannelBinding(sslSocket *ss,
++ unsigned char *out,
++ unsigned int *outLen,
++ unsigned int outLenMax) {
++ PRBool isTLS;
++ int index = 0;
++ unsigned int len;
++ SECStatus rv = SECFailure;
++
++ *outLen = 0;
++
++ ssl_GetSSL3HandshakeLock(ss);
++
++ ssl_GetSpecReadLock(ss);
++ isTLS = (PRBool)(ss->ssl3.cwSpec->version > SSL_LIBRARY_VERSION_3_0);
++ ssl_ReleaseSpecReadLock(ss);
++
++ /* The tls-unique channel binding is the first Finished structure in the
++ * handshake. In the case of a resumption, that's the server's Finished.
++ * Otherwise, it's the client's Finished. */
++ len = ss->ssl3.hs.finishedBytes;
++
++ /* Sending or receiving a Finished message will set finishedBytes to a
++ * non-zero value. */
++ if (len == 0) {
++ PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
++ goto loser;
++ }
++
++ /* If we are in the middle of a renegotiation then the channel binding
++ * value is poorly defined and depends on the direction that it will be
++ * used on. Therefore we simply return an error in this case. */
++ if (ss->firstHsDone && ss->ssl3.hs.ws != idle_handshake) {
++ PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
++ goto loser;
++ }
++
++ /* If resuming, then we want the second Finished value in the array, which
++ * is the server's */
++ if (ss->ssl3.hs.isResuming)
++ index = 1;
++
++ *outLen = len;
++ if (outLenMax < len) {
++ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
++ goto loser;
++ }
++
++ if (isTLS) {
++ memcpy(out, &ss->ssl3.hs.finishedMsgs.tFinished[index], len);
++ } else {
++ memcpy(out, &ss->ssl3.hs.finishedMsgs.sFinished[index], len);
++ }
++
++ rv = SECSuccess;
++
++loser:
++ ssl_ReleaseSSL3HandshakeLock(ss);
++ return rv;
++}
++
+ /* ssl3_config_match_init must have already been called by
+ * the caller of this function.
+ */
+diff -pu a/nss/lib/ssl/ssl.h b/nss/lib/ssl/ssl.h
+--- a/nss/lib/ssl/ssl.h 2013-07-31 14:10:00.342814862 -0700
++++ b/nss/lib/ssl/ssl.h 2013-07-31 14:10:12.202988980 -0700
+@@ -249,6 +249,27 @@ SSL_IMPORT SECStatus SSL_CipherPrefGetDe
+ SSL_IMPORT SECStatus SSL_CipherPolicySet(PRInt32 cipher, PRInt32 policy);
+ SSL_IMPORT SECStatus SSL_CipherPolicyGet(PRInt32 cipher, PRInt32 *policy);
+
++/* SSLChannelBindingType enumerates the types of supported channel binding
++ * values. See RFC 5929. */
++typedef enum SSLChannelBindingType {
++ SSL_CHANNEL_BINDING_TLS_UNIQUE = 1,
++} SSLChannelBindingType;
++
++/* SSL_GetChannelBinding copies the requested channel binding value, as defined
++ * in RFC 5929, into |out|. The full length of the binding value is written
++ * into |*outLen|.
++ *
++ * At most |outLenMax| bytes of data are copied. If |outLenMax| is
++ * insufficient then the function returns SECFailure and sets the error to
++ * SEC_ERROR_OUTPUT_LEN, but |*outLen| is still set.
++ *
++ * This call will fail if made during a renegotiation. */
++SSL_IMPORT SECStatus SSL_GetChannelBinding(PRFileDesc *fd,
++ SSLChannelBindingType binding_type,
++ unsigned char *out,
++ unsigned int *outLen,
++ unsigned int outLenMax);
++
+ /* SSL Version Range API
+ **
+ ** This API should be used to control SSL 3.0 & TLS support instead of the
+diff -pu a/nss/lib/ssl/sslimpl.h b/nss/lib/ssl/sslimpl.h
+--- a/nss/lib/ssl/sslimpl.h 2013-07-31 14:10:00.342814862 -0700
++++ b/nss/lib/ssl/sslimpl.h 2013-07-31 14:10:12.202988980 -0700
+@@ -1770,6 +1770,11 @@ extern PRBool ssl_GetSessionTicketKeysPK
+ extern SECStatus ssl3_ValidateNextProtoNego(const unsigned char* data,
+ unsigned int length);
+
++extern SECStatus ssl3_GetTLSUniqueChannelBinding(sslSocket *ss,
++ unsigned char *out,
++ unsigned int *outLen,
++ unsigned int outLenMax);
++
+ /* Construct a new NSPR socket for the app to use */
+ extern PRFileDesc *ssl_NewPRSocket(sslSocket *ss, PRFileDesc *fd);
+ extern void ssl_FreePRSocket(PRFileDesc *fd);
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 14:10:00.342814862 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 14:10:12.202988980 -0700
+@@ -1366,6 +1366,27 @@ NSS_SetFrancePolicy(void)
+ return NSS_SetDomesticPolicy();
+ }
+
++SECStatus
++SSL_GetChannelBinding(PRFileDesc *fd,
++ SSLChannelBindingType binding_type,
++ unsigned char *out,
++ unsigned int *outLen,
++ unsigned int outLenMax) {
++ sslSocket *ss = ssl_FindSocket(fd);
++
++ if (!ss) {
++ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetChannelBinding",
++ SSL_GETPID(), fd));
++ return SECFailure;
++ }
++
++ if (binding_type != SSL_CHANNEL_BINDING_TLS_UNIQUE) {
++ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
++ return SECFailure;
++ }
++
++ return ssl3_GetTLSUniqueChannelBinding(ss, out, outLen, outLenMax);
++}
+
+
+ /* LOCKS ??? XXX */
diff --git a/chromium/net/third_party/nss/patches/versionskew.patch b/chromium/net/third_party/nss/patches/versionskew.patch
new file mode 100644
index 00000000000..1a9061e46ac
--- /dev/null
+++ b/chromium/net/third_party/nss/patches/versionskew.patch
@@ -0,0 +1,45 @@
+diff -pu a/nss/lib/ssl/sslsecur.c b/nss/lib/ssl/sslsecur.c
+--- a/nss/lib/ssl/sslsecur.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslsecur.c 2013-07-31 12:27:24.322323927 -0700
+@@ -1311,6 +1311,10 @@ SSL_SetURL(PRFileDesc *fd, const char *u
+ SECStatus
+ SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *certList)
+ {
++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
++ PR_NOT_REACHED("not implemented");
++ return SECFailure;
++#if 0
+ sslSocket * ss = ssl_FindSocket(fd);
+ CERTDistNames *names = NULL;
+
+@@ -1338,6 +1342,7 @@ SSL_SetTrustAnchors(PRFileDesc *fd, CERT
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
++#endif
+ }
+
+ /*
+diff -pu a/nss/lib/ssl/sslsock.c b/nss/lib/ssl/sslsock.c
+--- a/nss/lib/ssl/sslsock.c 2013-07-31 12:07:10.974699609 -0700
++++ b/nss/lib/ssl/sslsock.c 2013-07-31 12:27:24.322323927 -0700
+@@ -1625,6 +1625,11 @@ SSL_GetSRTPCipher(PRFileDesc *fd, PRUint
+ PRFileDesc *
+ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+ {
++ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
++ PR_NOT_REACHED("not implemented");
++ return NULL;
++
++#if 0
+ sslSocket * sm = NULL, *ss = NULL;
+ int i;
+ sslServerCerts * mc = NULL;
+@@ -1742,6 +1747,7 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
+ return fd;
+ loser:
+ return NULL;
++#endif
+ }
+
+ PRBool
diff --git a/chromium/net/third_party/nss/ssl.gyp b/chromium/net/third_party/nss/ssl.gyp
new file mode 100644
index 00000000000..fc526733c02
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl.gyp
@@ -0,0 +1,192 @@
+# Copyright (c) 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.
+
+{
+ 'conditions': [
+ [ 'os_posix == 1 and OS != "mac" and OS != "ios"', {
+ 'conditions': [
+ ['sysroot!=""', {
+ 'variables': {
+ 'pkg-config': '../../../build/linux/pkg-config-wrapper "<(sysroot)" "<(target_arch)"',
+ },
+ }, {
+ 'variables': {
+ 'pkg-config': 'pkg-config'
+ },
+ }],
+ ],
+ }],
+ ],
+
+ 'targets': [
+ {
+ 'target_name': 'libssl',
+ 'type': '<(component)',
+ 'product_name': 'crssl', # Don't conflict with OpenSSL's libssl
+ 'sources': [
+ 'ssl/authcert.c',
+ 'ssl/cmpcert.c',
+ 'ssl/derive.c',
+ 'ssl/dtlscon.c',
+ 'ssl/os2_err.c',
+ 'ssl/os2_err.h',
+ 'ssl/preenc.h',
+ 'ssl/prelib.c',
+ 'ssl/ssl.h',
+ 'ssl/ssl3con.c',
+ 'ssl/ssl3ecc.c',
+ 'ssl/ssl3ext.c',
+ 'ssl/ssl3gthr.c',
+ 'ssl/ssl3prot.h',
+ 'ssl/sslauth.c',
+ 'ssl/sslcon.c',
+ 'ssl/ssldef.c',
+ 'ssl/sslenum.c',
+ 'ssl/sslerr.c',
+ 'ssl/sslerr.h',
+ 'ssl/SSLerrs.h',
+ 'ssl/sslerrstrs.c',
+ 'ssl/sslgathr.c',
+ 'ssl/sslimpl.h',
+ 'ssl/sslinfo.c',
+ 'ssl/sslinit.c',
+ 'ssl/sslmutex.c',
+ 'ssl/sslmutex.h',
+ 'ssl/sslnonce.c',
+ 'ssl/sslplatf.c',
+ 'ssl/sslproto.h',
+ 'ssl/sslreveal.c',
+ 'ssl/sslsecur.c',
+ 'ssl/sslsnce.c',
+ 'ssl/sslsock.c',
+ 'ssl/sslt.h',
+ 'ssl/ssltrace.c',
+ 'ssl/sslver.c',
+ 'ssl/unix_err.c',
+ 'ssl/unix_err.h',
+ 'ssl/win32err.c',
+ 'ssl/win32err.h',
+ 'ssl/bodge/secitem_array.c',
+ ],
+ 'sources!': [
+ 'ssl/os2_err.c',
+ 'ssl/os2_err.h',
+ ],
+ 'defines': [
+ 'NO_PKCS11_BYPASS',
+ 'NSS_ENABLE_ECC',
+ 'USE_UTIL_DIRECTLY',
+ ],
+ 'msvs_disabled_warnings': [4018, 4244, 4267],
+ 'conditions': [
+ ['component == "shared_library"', {
+ 'conditions': [
+ ['OS == "mac" or OS == "ios"', {
+ 'xcode_settings': {
+ 'GCC_SYMBOLS_PRIVATE_EXTERN': 'NO',
+ },
+ }],
+ ['OS == "win"', {
+ 'sources': [
+ 'ssl/exports_win.def',
+ ],
+ }],
+ ['os_posix == 1 and OS != "mac" and OS != "ios"', {
+ 'cflags!': ['-fvisibility=hidden'],
+ }],
+ ],
+ }],
+ [ 'clang == 1', {
+ 'cflags': [
+ # See http://crbug.com/138571#c8. In short, sslsecur.c picks up the
+ # system's cert.h because cert.h isn't in chromium's repo.
+ '-Wno-incompatible-pointer-types',
+
+ # There is a broken header guard in /usr/include/nss/secmod.h:
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=884072
+ '-Wno-header-guard',
+ ],
+ }],
+ [ 'OS == "mac" or OS == "ios"', {
+ 'defines': [
+ 'XP_UNIX',
+ 'DARWIN',
+ 'XP_MACOSX',
+ ],
+ }],
+ [ 'OS == "mac"', {
+ 'link_settings': {
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/Security.framework',
+ ],
+ },
+ }],
+ [ 'OS == "win"', {
+ 'sources!': [
+ 'ssl/unix_err.c',
+ 'ssl/unix_err.h',
+ ],
+ },
+ { # else: OS != "win"
+ 'sources!': [
+ 'ssl/win32err.c',
+ 'ssl/win32err.h',
+ ],
+ },
+ ],
+ [ 'os_posix == 1 and OS != "mac" and OS != "ios"', {
+ 'include_dirs': [
+ 'ssl/bodge',
+ ],
+ 'cflags': [
+ '<!@(<(pkg-config) --cflags nss)',
+ ],
+ 'ldflags': [
+ '<!@(<(pkg-config) --libs-only-L --libs-only-other nss)',
+ ],
+ 'libraries': [
+ '<!@(<(pkg-config) --libs-only-l nss | sed -e "s/-lssl3//")',
+ ],
+ }],
+ [ 'OS == "mac" or OS == "ios" or OS == "win"', {
+ 'sources/': [
+ ['exclude', 'ssl/bodge/'],
+ ],
+ 'conditions': [
+ ['OS != "ios"', {
+ 'defines': [
+ 'NSS_PLATFORM_CLIENT_AUTH',
+ ],
+ 'direct_dependent_settings': {
+ 'defines': [
+ 'NSS_PLATFORM_CLIENT_AUTH',
+ ],
+ },
+ }],
+ ],
+ 'dependencies': [
+ '../../../third_party/nss/nss.gyp:nspr',
+ '../../../third_party/nss/nss.gyp:nss',
+ ],
+ 'export_dependent_settings': [
+ '../../../third_party/nss/nss.gyp:nspr',
+ '../../../third_party/nss/nss.gyp:nss',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ 'ssl',
+ ],
+ },
+ }],
+ ],
+ 'configurations': {
+ 'Debug_Base': {
+ 'defines': [
+ 'DEBUG',
+ ],
+ },
+ },
+ },
+ ],
+}
diff --git a/chromium/net/third_party/nss/ssl/Makefile b/chromium/net/third_party/nss/ssl/Makefile
new file mode 100644
index 00000000000..d56cbf29ead
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/Makefile
@@ -0,0 +1,63 @@
+#! gmake
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#######################################################################
+# (1) Include initial platform-independent assignments (MANDATORY). #
+#######################################################################
+
+include manifest.mn
+
+#######################################################################
+# (2) Include "global" configuration information. (OPTIONAL) #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/config.mk
+
+#######################################################################
+# (3) Include "component" configuration information. (OPTIONAL) #
+#######################################################################
+
+
+
+#######################################################################
+# (4) Include "local" platform-dependent assignments (OPTIONAL). #
+#######################################################################
+
+include config.mk
+
+ifeq (,$(filter-out WIN%,$(OS_TARGET)))
+CSRCS += win32err.c
+DEFINES += -DIN_LIBSSL
+else
+ifeq ($(OS_TARGET),OS2)
+CSRCS += os2_err.c
+else
+CSRCS += unix_err.c
+endif
+endif
+
+#######################################################################
+# (5) Execute "global" rules. (OPTIONAL) #
+#######################################################################
+
+include $(CORE_DEPTH)/coreconf/rules.mk
+
+#######################################################################
+# (6) Execute "component" rules. (OPTIONAL) #
+#######################################################################
+
+
+
+#######################################################################
+# (7) Execute "local" rules. (OPTIONAL). #
+#######################################################################
+
+export:: private_export
+
+ifndef NSS_NO_PKCS11_BYPASS
+# indicates dependency on freebl static lib
+$(SHARED_LIBRARY): $(CRYPTOLIB)
+endif
diff --git a/chromium/net/third_party/nss/ssl/SSLerrs.h b/chromium/net/third_party/nss/ssl/SSLerrs.h
new file mode 100644
index 00000000000..c0d26ccfadc
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/SSLerrs.h
@@ -0,0 +1,423 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* SSL-specific security error codes */
+/* caller must include "sslerr.h" */
+
+ER3(SSL_ERROR_EXPORT_ONLY_SERVER, SSL_ERROR_BASE + 0,
+"Unable to communicate securely. Peer does not support high-grade encryption.")
+
+ER3(SSL_ERROR_US_ONLY_SERVER, SSL_ERROR_BASE + 1,
+"Unable to communicate securely. Peer requires high-grade encryption which is not supported.")
+
+ER3(SSL_ERROR_NO_CYPHER_OVERLAP, SSL_ERROR_BASE + 2,
+"Cannot communicate securely with peer: no common encryption algorithm(s).")
+
+ER3(SSL_ERROR_NO_CERTIFICATE, SSL_ERROR_BASE + 3,
+"Unable to find the certificate or key necessary for authentication.")
+
+ER3(SSL_ERROR_BAD_CERTIFICATE, SSL_ERROR_BASE + 4,
+"Unable to communicate securely with peer: peers's certificate was rejected.")
+
+ER3(SSL_ERROR_UNUSED_5, SSL_ERROR_BASE + 5,
+"Unrecognized SSL error code.")
+
+ER3(SSL_ERROR_BAD_CLIENT, SSL_ERROR_BASE + 6,
+"The server has encountered bad data from the client.")
+
+ER3(SSL_ERROR_BAD_SERVER, SSL_ERROR_BASE + 7,
+"The client has encountered bad data from the server.")
+
+ER3(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, SSL_ERROR_BASE + 8,
+"Unsupported certificate type.")
+
+ER3(SSL_ERROR_UNSUPPORTED_VERSION, SSL_ERROR_BASE + 9,
+"Peer using unsupported version of security protocol.")
+
+ER3(SSL_ERROR_UNUSED_10, SSL_ERROR_BASE + 10,
+"Unrecognized SSL error code.")
+
+ER3(SSL_ERROR_WRONG_CERTIFICATE, SSL_ERROR_BASE + 11,
+"Client authentication failed: private key in key database does not match public key in certificate database.")
+
+ER3(SSL_ERROR_BAD_CERT_DOMAIN, SSL_ERROR_BASE + 12,
+"Unable to communicate securely with peer: requested domain name does not match the server's certificate.")
+
+ER3(SSL_ERROR_POST_WARNING, SSL_ERROR_BASE + 13,
+"Unrecognized SSL error code.")
+
+ER3(SSL_ERROR_SSL2_DISABLED, (SSL_ERROR_BASE + 14),
+"Peer only supports SSL version 2, which is locally disabled.")
+
+
+ER3(SSL_ERROR_BAD_MAC_READ, (SSL_ERROR_BASE + 15),
+"SSL received a record with an incorrect Message Authentication Code.")
+
+ER3(SSL_ERROR_BAD_MAC_ALERT, (SSL_ERROR_BASE + 16),
+"SSL peer reports incorrect Message Authentication Code.")
+
+ER3(SSL_ERROR_BAD_CERT_ALERT, (SSL_ERROR_BASE + 17),
+"SSL peer cannot verify your certificate.")
+
+ER3(SSL_ERROR_REVOKED_CERT_ALERT, (SSL_ERROR_BASE + 18),
+"SSL peer rejected your certificate as revoked.")
+
+ER3(SSL_ERROR_EXPIRED_CERT_ALERT, (SSL_ERROR_BASE + 19),
+"SSL peer rejected your certificate as expired.")
+
+ER3(SSL_ERROR_SSL_DISABLED, (SSL_ERROR_BASE + 20),
+"Cannot connect: SSL is disabled.")
+
+ER3(SSL_ERROR_FORTEZZA_PQG, (SSL_ERROR_BASE + 21),
+"Cannot connect: SSL peer is in another FORTEZZA domain.")
+
+ER3(SSL_ERROR_UNKNOWN_CIPHER_SUITE , (SSL_ERROR_BASE + 22),
+"An unknown SSL cipher suite has been requested.")
+
+ER3(SSL_ERROR_NO_CIPHERS_SUPPORTED , (SSL_ERROR_BASE + 23),
+"No cipher suites are present and enabled in this program.")
+
+ER3(SSL_ERROR_BAD_BLOCK_PADDING , (SSL_ERROR_BASE + 24),
+"SSL received a record with bad block padding.")
+
+ER3(SSL_ERROR_RX_RECORD_TOO_LONG , (SSL_ERROR_BASE + 25),
+"SSL received a record that exceeded the maximum permissible length.")
+
+ER3(SSL_ERROR_TX_RECORD_TOO_LONG , (SSL_ERROR_BASE + 26),
+"SSL attempted to send a record that exceeded the maximum permissible length.")
+
+/*
+ * Received a malformed (too long or short or invalid content) SSL handshake.
+ */
+ER3(SSL_ERROR_RX_MALFORMED_HELLO_REQUEST , (SSL_ERROR_BASE + 27),
+"SSL received a malformed Hello Request handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO , (SSL_ERROR_BASE + 28),
+"SSL received a malformed Client Hello handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_SERVER_HELLO , (SSL_ERROR_BASE + 29),
+"SSL received a malformed Server Hello handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_CERTIFICATE , (SSL_ERROR_BASE + 30),
+"SSL received a malformed Certificate handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH , (SSL_ERROR_BASE + 31),
+"SSL received a malformed Server Key Exchange handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_CERT_REQUEST , (SSL_ERROR_BASE + 32),
+"SSL received a malformed Certificate Request handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_HELLO_DONE , (SSL_ERROR_BASE + 33),
+"SSL received a malformed Server Hello Done handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_CERT_VERIFY , (SSL_ERROR_BASE + 34),
+"SSL received a malformed Certificate Verify handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH , (SSL_ERROR_BASE + 35),
+"SSL received a malformed Client Key Exchange handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_FINISHED , (SSL_ERROR_BASE + 36),
+"SSL received a malformed Finished handshake message.")
+
+/*
+ * Received a malformed (too long or short) SSL record.
+ */
+ER3(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER , (SSL_ERROR_BASE + 37),
+"SSL received a malformed Change Cipher Spec record.")
+
+ER3(SSL_ERROR_RX_MALFORMED_ALERT , (SSL_ERROR_BASE + 38),
+"SSL received a malformed Alert record.")
+
+ER3(SSL_ERROR_RX_MALFORMED_HANDSHAKE , (SSL_ERROR_BASE + 39),
+"SSL received a malformed Handshake record.")
+
+ER3(SSL_ERROR_RX_MALFORMED_APPLICATION_DATA , (SSL_ERROR_BASE + 40),
+"SSL received a malformed Application Data record.")
+
+/*
+ * Received an SSL handshake that was inappropriate for the state we're in.
+ * E.g. Server received message from server, or wrong state in state machine.
+ */
+ER3(SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST , (SSL_ERROR_BASE + 41),
+"SSL received an unexpected Hello Request handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO , (SSL_ERROR_BASE + 42),
+"SSL received an unexpected Client Hello handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO , (SSL_ERROR_BASE + 43),
+"SSL received an unexpected Server Hello handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CERTIFICATE , (SSL_ERROR_BASE + 44),
+"SSL received an unexpected Certificate handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH , (SSL_ERROR_BASE + 45),
+"SSL received an unexpected Server Key Exchange handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST , (SSL_ERROR_BASE + 46),
+"SSL received an unexpected Certificate Request handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_HELLO_DONE , (SSL_ERROR_BASE + 47),
+"SSL received an unexpected Server Hello Done handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY , (SSL_ERROR_BASE + 48),
+"SSL received an unexpected Certificate Verify handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH , (SSL_ERROR_BASE + 49),
+"SSL received an unexpected Client Key Exchange handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_FINISHED , (SSL_ERROR_BASE + 50),
+"SSL received an unexpected Finished handshake message.")
+
+/*
+ * Received an SSL record that was inappropriate for the state we're in.
+ */
+ER3(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER , (SSL_ERROR_BASE + 51),
+"SSL received an unexpected Change Cipher Spec record.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_ALERT , (SSL_ERROR_BASE + 52),
+"SSL received an unexpected Alert record.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_HANDSHAKE , (SSL_ERROR_BASE + 53),
+"SSL received an unexpected Handshake record.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA, (SSL_ERROR_BASE + 54),
+"SSL received an unexpected Application Data record.")
+
+/*
+ * Received record/message with unknown discriminant.
+ */
+ER3(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE , (SSL_ERROR_BASE + 55),
+"SSL received a record with an unknown content type.")
+
+ER3(SSL_ERROR_RX_UNKNOWN_HANDSHAKE , (SSL_ERROR_BASE + 56),
+"SSL received a handshake message with an unknown message type.")
+
+ER3(SSL_ERROR_RX_UNKNOWN_ALERT , (SSL_ERROR_BASE + 57),
+"SSL received an alert record with an unknown alert description.")
+
+/*
+ * Received an alert reporting what we did wrong. (more alerts above)
+ */
+ER3(SSL_ERROR_CLOSE_NOTIFY_ALERT , (SSL_ERROR_BASE + 58),
+"SSL peer has closed this connection.")
+
+ER3(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT , (SSL_ERROR_BASE + 59),
+"SSL peer was not expecting a handshake message it received.")
+
+ER3(SSL_ERROR_DECOMPRESSION_FAILURE_ALERT , (SSL_ERROR_BASE + 60),
+"SSL peer was unable to successfully decompress an SSL record it received.")
+
+ER3(SSL_ERROR_HANDSHAKE_FAILURE_ALERT , (SSL_ERROR_BASE + 61),
+"SSL peer was unable to negotiate an acceptable set of security parameters.")
+
+ER3(SSL_ERROR_ILLEGAL_PARAMETER_ALERT , (SSL_ERROR_BASE + 62),
+"SSL peer rejected a handshake message for unacceptable content.")
+
+ER3(SSL_ERROR_UNSUPPORTED_CERT_ALERT , (SSL_ERROR_BASE + 63),
+"SSL peer does not support certificates of the type it received.")
+
+ER3(SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT , (SSL_ERROR_BASE + 64),
+"SSL peer had some unspecified issue with the certificate it received.")
+
+
+ER3(SSL_ERROR_GENERATE_RANDOM_FAILURE , (SSL_ERROR_BASE + 65),
+"SSL experienced a failure of its random number generator.")
+
+ER3(SSL_ERROR_SIGN_HASHES_FAILURE , (SSL_ERROR_BASE + 66),
+"Unable to digitally sign data required to verify your certificate.")
+
+ER3(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE , (SSL_ERROR_BASE + 67),
+"SSL was unable to extract the public key from the peer's certificate.")
+
+ER3(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE , (SSL_ERROR_BASE + 68),
+"Unspecified failure while processing SSL Server Key Exchange handshake.")
+
+ER3(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE , (SSL_ERROR_BASE + 69),
+"Unspecified failure while processing SSL Client Key Exchange handshake.")
+
+ER3(SSL_ERROR_ENCRYPTION_FAILURE , (SSL_ERROR_BASE + 70),
+"Bulk data encryption algorithm failed in selected cipher suite.")
+
+ER3(SSL_ERROR_DECRYPTION_FAILURE , (SSL_ERROR_BASE + 71),
+"Bulk data decryption algorithm failed in selected cipher suite.")
+
+ER3(SSL_ERROR_SOCKET_WRITE_FAILURE , (SSL_ERROR_BASE + 72),
+"Attempt to write encrypted data to underlying socket failed.")
+
+ER3(SSL_ERROR_MD5_DIGEST_FAILURE , (SSL_ERROR_BASE + 73),
+"MD5 digest function failed.")
+
+ER3(SSL_ERROR_SHA_DIGEST_FAILURE , (SSL_ERROR_BASE + 74),
+"SHA-1 digest function failed.")
+
+ER3(SSL_ERROR_MAC_COMPUTATION_FAILURE , (SSL_ERROR_BASE + 75),
+"MAC computation failed.")
+
+ER3(SSL_ERROR_SYM_KEY_CONTEXT_FAILURE , (SSL_ERROR_BASE + 76),
+"Failure to create Symmetric Key context.")
+
+ER3(SSL_ERROR_SYM_KEY_UNWRAP_FAILURE , (SSL_ERROR_BASE + 77),
+"Failure to unwrap the Symmetric key in Client Key Exchange message.")
+
+ER3(SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED , (SSL_ERROR_BASE + 78),
+"SSL Server attempted to use domestic-grade public key with export cipher suite.")
+
+ER3(SSL_ERROR_IV_PARAM_FAILURE , (SSL_ERROR_BASE + 79),
+"PKCS11 code failed to translate an IV into a param.")
+
+ER3(SSL_ERROR_INIT_CIPHER_SUITE_FAILURE , (SSL_ERROR_BASE + 80),
+"Failed to initialize the selected cipher suite.")
+
+ER3(SSL_ERROR_SESSION_KEY_GEN_FAILURE , (SSL_ERROR_BASE + 81),
+"Client failed to generate session keys for SSL session.")
+
+ER3(SSL_ERROR_NO_SERVER_KEY_FOR_ALG , (SSL_ERROR_BASE + 82),
+"Server has no key for the attempted key exchange algorithm.")
+
+ER3(SSL_ERROR_TOKEN_INSERTION_REMOVAL , (SSL_ERROR_BASE + 83),
+"PKCS#11 token was inserted or removed while operation was in progress.")
+
+ER3(SSL_ERROR_TOKEN_SLOT_NOT_FOUND , (SSL_ERROR_BASE + 84),
+"No PKCS#11 token could be found to do a required operation.")
+
+ER3(SSL_ERROR_NO_COMPRESSION_OVERLAP , (SSL_ERROR_BASE + 85),
+"Cannot communicate securely with peer: no common compression algorithm(s).")
+
+ER3(SSL_ERROR_HANDSHAKE_NOT_COMPLETED , (SSL_ERROR_BASE + 86),
+"Cannot perform the operation until the handshake is complete.")
+
+ER3(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE , (SSL_ERROR_BASE + 87),
+"Received incorrect handshakes hash values from peer.")
+
+ER3(SSL_ERROR_CERT_KEA_MISMATCH , (SSL_ERROR_BASE + 88),
+"The certificate provided cannot be used with the selected key exchange algorithm.")
+
+ER3(SSL_ERROR_NO_TRUSTED_SSL_CLIENT_CA , (SSL_ERROR_BASE + 89),
+"No certificate authority is trusted for SSL client authentication.")
+
+ER3(SSL_ERROR_SESSION_NOT_FOUND , (SSL_ERROR_BASE + 90),
+"Client's SSL session ID not found in server's session cache.")
+
+ER3(SSL_ERROR_DECRYPTION_FAILED_ALERT , (SSL_ERROR_BASE + 91),
+"Peer was unable to decrypt an SSL record it received.")
+
+ER3(SSL_ERROR_RECORD_OVERFLOW_ALERT , (SSL_ERROR_BASE + 92),
+"Peer received an SSL record that was longer than is permitted.")
+
+ER3(SSL_ERROR_UNKNOWN_CA_ALERT , (SSL_ERROR_BASE + 93),
+"Peer does not recognize and trust the CA that issued your certificate.")
+
+ER3(SSL_ERROR_ACCESS_DENIED_ALERT , (SSL_ERROR_BASE + 94),
+"Peer received a valid certificate, but access was denied.")
+
+ER3(SSL_ERROR_DECODE_ERROR_ALERT , (SSL_ERROR_BASE + 95),
+"Peer could not decode an SSL handshake message.")
+
+ER3(SSL_ERROR_DECRYPT_ERROR_ALERT , (SSL_ERROR_BASE + 96),
+"Peer reports failure of signature verification or key exchange.")
+
+ER3(SSL_ERROR_EXPORT_RESTRICTION_ALERT , (SSL_ERROR_BASE + 97),
+"Peer reports negotiation not in compliance with export regulations.")
+
+ER3(SSL_ERROR_PROTOCOL_VERSION_ALERT , (SSL_ERROR_BASE + 98),
+"Peer reports incompatible or unsupported protocol version.")
+
+ER3(SSL_ERROR_INSUFFICIENT_SECURITY_ALERT , (SSL_ERROR_BASE + 99),
+"Server requires ciphers more secure than those supported by client.")
+
+ER3(SSL_ERROR_INTERNAL_ERROR_ALERT , (SSL_ERROR_BASE + 100),
+"Peer reports it experienced an internal error.")
+
+ER3(SSL_ERROR_USER_CANCELED_ALERT , (SSL_ERROR_BASE + 101),
+"Peer user canceled handshake.")
+
+ER3(SSL_ERROR_NO_RENEGOTIATION_ALERT , (SSL_ERROR_BASE + 102),
+"Peer does not permit renegotiation of SSL security parameters.")
+
+ER3(SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED , (SSL_ERROR_BASE + 103),
+"SSL server cache not configured and not disabled for this socket.")
+
+ER3(SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT , (SSL_ERROR_BASE + 104),
+"SSL peer does not support requested TLS hello extension.")
+
+ER3(SSL_ERROR_CERTIFICATE_UNOBTAINABLE_ALERT , (SSL_ERROR_BASE + 105),
+"SSL peer could not obtain your certificate from the supplied URL.")
+
+ER3(SSL_ERROR_UNRECOGNIZED_NAME_ALERT , (SSL_ERROR_BASE + 106),
+"SSL peer has no certificate for the requested DNS name.")
+
+ER3(SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT , (SSL_ERROR_BASE + 107),
+"SSL peer was unable to get an OCSP response for its certificate.")
+
+ER3(SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT , (SSL_ERROR_BASE + 108),
+"SSL peer reported bad certificate hash value.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET, (SSL_ERROR_BASE + 109),
+"SSL received an unexpected New Session Ticket handshake message.")
+
+ER3(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET, (SSL_ERROR_BASE + 110),
+"SSL received a malformed New Session Ticket handshake message.")
+
+ER3(SSL_ERROR_DECOMPRESSION_FAILURE, (SSL_ERROR_BASE + 111),
+"SSL received a compressed record that could not be decompressed.")
+
+ER3(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, (SSL_ERROR_BASE + 112),
+"Renegotiation is not allowed on this SSL socket.")
+
+ER3(SSL_ERROR_UNSAFE_NEGOTIATION, (SSL_ERROR_BASE + 113),
+"Peer attempted old style (potentially vulnerable) handshake.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD, (SSL_ERROR_BASE + 114),
+"SSL received an unexpected uncompressed record.")
+
+ER3(SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY, (SSL_ERROR_BASE + 115),
+"SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.")
+
+ER3(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID, (SSL_ERROR_BASE + 116),
+"SSL received invalid NPN extension data.")
+
+ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2, (SSL_ERROR_BASE + 117),
+"SSL feature not supported for SSL 2.0 connections.")
+
+ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS, (SSL_ERROR_BASE + 118),
+"SSL feature not supported for servers.")
+
+ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS, (SSL_ERROR_BASE + 119),
+"SSL feature not supported for clients.")
+
+ER3(SSL_ERROR_INVALID_VERSION_RANGE, (SSL_ERROR_BASE + 120),
+"SSL version range is not valid.")
+
+ER3(SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION, (SSL_ERROR_BASE + 121),
+"SSL peer selected a cipher suite disallowed for the selected protocol version.")
+
+ER3(SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST, (SSL_ERROR_BASE + 122),
+"SSL received a malformed Hello Verify Request handshake message.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST, (SSL_ERROR_BASE + 123),
+"SSL received an unexpected Hello Verify Request handshake message.")
+
+ER3(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION, (SSL_ERROR_BASE + 124),
+"SSL feature not supported for the protocol version.")
+
+ER3(SSL_ERROR_RX_UNEXPECTED_CERT_STATUS, (SSL_ERROR_BASE + 125),
+"SSL received an unexpected Certificate Status handshake message.")
+
+ER3(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM, (SSL_ERROR_BASE + 126),
+"Unsupported hash algorithm used by TLS peer.")
+
+ER3(SSL_ERROR_DIGEST_FAILURE, (SSL_ERROR_BASE + 127),
+"Digest function failed.")
+
+ER3(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM, (SSL_ERROR_BASE + 128),
+"Incorrect signature algorithm specified in a digitally-signed element.")
+
+ER3(SSL_ERROR_BAD_CHANNEL_ID_DATA, (SSL_ERROR_BASE + 129),
+"SSL received a malformed TLS Channel ID extension.")
+
+ER3(SSL_ERROR_INVALID_CHANNEL_ID_KEY, (SSL_ERROR_BASE + 130),
+"The application provided an invalid TLS Channel ID key.")
+
+ER3(SSL_ERROR_GET_CHANNEL_ID_FAILED, (SSL_ERROR_BASE + 131),
+"The application could not get a TLS Channel ID.")
diff --git a/chromium/net/third_party/nss/ssl/authcert.c b/chromium/net/third_party/nss/ssl/authcert.c
new file mode 100644
index 00000000000..bd0f6ed49a2
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/authcert.c
@@ -0,0 +1,89 @@
+/*
+ * NSS utility functions
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <string.h>
+#include "prerror.h"
+#include "secitem.h"
+#include "prnetdb.h"
+#include "cert.h"
+#include "nspr.h"
+#include "secder.h"
+#include "key.h"
+#include "nss.h"
+#include "ssl.h"
+#include "pk11func.h" /* for PK11_ function calls */
+
+/*
+ * This callback used by SSL to pull client sertificate upon
+ * server request
+ */
+SECStatus
+NSS_GetClientAuthData(void * arg,
+ PRFileDesc * socket,
+ struct CERTDistNamesStr * caNames,
+ struct CERTCertificateStr ** pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey)
+{
+ CERTCertificate * cert = NULL;
+ SECKEYPrivateKey * privkey = NULL;
+ char * chosenNickName = (char *)arg; /* CONST */
+ void * proto_win = NULL;
+ SECStatus rv = SECFailure;
+
+ proto_win = SSL_RevealPinArg(socket);
+
+ if (chosenNickName) {
+ cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(),
+ chosenNickName, certUsageSSLClient,
+ PR_FALSE, proto_win);
+ if ( cert ) {
+ privkey = PK11_FindKeyByAnyCert(cert, proto_win);
+ if ( privkey ) {
+ rv = SECSuccess;
+ } else {
+ CERT_DestroyCertificate(cert);
+ }
+ }
+ } else { /* no name given, automatically find the right cert. */
+ CERTCertNicknames * names;
+ int i;
+
+ names = CERT_GetCertNicknames(CERT_GetDefaultCertDB(),
+ SEC_CERT_NICKNAMES_USER, proto_win);
+ if (names != NULL) {
+ for (i = 0; i < names->numnicknames; i++) {
+ cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(),
+ names->nicknames[i], certUsageSSLClient,
+ PR_FALSE, proto_win);
+ if ( !cert )
+ continue;
+ /* Only check unexpired certs */
+ if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_TRUE) !=
+ secCertTimeValid ) {
+ CERT_DestroyCertificate(cert);
+ continue;
+ }
+ rv = NSS_CmpCertChainWCANames(cert, caNames);
+ if ( rv == SECSuccess ) {
+ privkey = PK11_FindKeyByAnyCert(cert, proto_win);
+ if ( privkey )
+ break;
+ }
+ rv = SECFailure;
+ CERT_DestroyCertificate(cert);
+ }
+ CERT_FreeNicknames(names);
+ }
+ }
+ if (rv == SECSuccess) {
+ *pRetCert = cert;
+ *pRetKey = privkey;
+ }
+ return rv;
+}
+
diff --git a/chromium/net/third_party/nss/ssl/bodge/nssrenam.h b/chromium/net/third_party/nss/ssl/bodge/nssrenam.h
new file mode 100644
index 00000000000..156646cdd2d
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/bodge/nssrenam.h
@@ -0,0 +1,47 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __nssrenam_h_
+#define __nssrenam_h_
+
+#define CERT_AddTempCertToPerm __CERT_AddTempCertToPerm
+#define PK11_CreateContextByRawKey __PK11_CreateContextByRawKey
+#define CERT_ClosePermCertDB __CERT_ClosePermCertDB
+#define CERT_DecodeDERCertificate __CERT_DecodeDERCertificate
+#define CERT_TraversePermCertsForNickname __CERT_TraversePermCertsForNickname
+#define CERT_TraversePermCertsForSubject __CERT_TraversePermCertsForSubject
+
+#endif /* __nssrenam_h_ */
diff --git a/chromium/net/third_party/nss/ssl/bodge/secitem_array.c b/chromium/net/third_party/nss/ssl/bodge/secitem_array.c
new file mode 100644
index 00000000000..2acd5f8e24a
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/bodge/secitem_array.c
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Support routines for SECItemArray data structure.
+ */
+
+#include "nssutil.h"
+#include "seccomon.h"
+#include "secitem.h"
+#include "secerr.h"
+#include "secport.h"
+
+#define NSSUTIL_VERSION_NUM \
+ (NSSUTIL_VMAJOR * 10000 + NSSUTIL_VMINOR * 100 + NSSUTIL_VPATCH)
+#if NSSUTIL_VERSION_NUM < 31500
+// Added in NSS 3.15.
+typedef struct SECItemArrayStr SECItemArray;
+
+struct SECItemArrayStr {
+ SECItem *items;
+ unsigned int len;
+};
+#endif
+
+SECItemArray *
+SECITEM_AllocArray(PLArenaPool *arena, SECItemArray *array, unsigned int len)
+{
+ SECItemArray *result = NULL;
+ void *mark = NULL;
+
+ if (arena != NULL) {
+ mark = PORT_ArenaMark(arena);
+ }
+
+ if (array == NULL) {
+ if (arena != NULL) {
+ result = PORT_ArenaZAlloc(arena, sizeof(SECItemArray));
+ } else {
+ result = PORT_ZAlloc(sizeof(SECItemArray));
+ }
+ if (result == NULL) {
+ goto loser;
+ }
+ } else {
+ PORT_Assert(array->items == NULL);
+ result = array;
+ }
+
+ result->len = len;
+ if (len) {
+ if (arena != NULL) {
+ result->items = PORT_ArenaZNewArray(arena, SECItem, len);
+ } else {
+ result->items = PORT_ZNewArray(SECItem, len);
+ }
+ if (result->items == NULL) {
+ goto loser;
+ }
+ } else {
+ result->items = NULL;
+ }
+
+ if (mark) {
+ PORT_ArenaUnmark(arena, mark);
+ }
+ return(result);
+
+loser:
+ if ( arena != NULL ) {
+ if (mark) {
+ PORT_ArenaRelease(arena, mark);
+ }
+ if (array != NULL) {
+ array->items = NULL;
+ array->len = 0;
+ }
+ } else {
+ if (result != NULL && array == NULL) {
+ PORT_Free(result);
+ }
+ /*
+ * If array is not NULL, the above has set array->data and
+ * array->len to 0.
+ */
+ }
+ return(NULL);
+}
+
+static void
+secitem_FreeArray(SECItemArray *array, PRBool zero_items, PRBool freeit)
+{
+ unsigned int i;
+
+ if (!array || !array->len || !array->items)
+ return;
+
+ for (i=0; i<array->len; ++i) {
+ SECItem *item = &array->items[i];
+
+ if (item->data) {
+ if (zero_items) {
+ SECITEM_ZfreeItem(item, PR_FALSE);
+ } else {
+ SECITEM_FreeItem(item, PR_FALSE);
+ }
+ }
+ }
+ PORT_Free(array->items);
+ array->items = NULL;
+ array->len = 0;
+
+ if (freeit)
+ PORT_Free(array);
+}
+
+void SECITEM_FreeArray(SECItemArray *array, PRBool freeit)
+{
+ secitem_FreeArray(array, PR_FALSE, freeit);
+}
+
+void SECITEM_ZfreeArray(SECItemArray *array, PRBool freeit)
+{
+ secitem_FreeArray(array, PR_TRUE, freeit);
+}
+
+SECItemArray *
+SECITEM_DupArray(PLArenaPool *arena, const SECItemArray *from)
+{
+ SECItemArray *result;
+ unsigned int i;
+
+ if (!from || !from->items || !from->len)
+ return NULL;
+
+ result = SECITEM_AllocArray(arena, NULL, from->len);
+ if (!result)
+ return NULL;
+
+ for (i=0; i<from->len; ++i) {
+ SECStatus rv = SECITEM_CopyItem(arena,
+ &result->items[i], &from->items[i]);
+ if (rv != SECSuccess) {
+ SECITEM_ZfreeArray(result, PR_TRUE);
+ return NULL;
+ }
+ }
+
+ return result;
+}
diff --git a/chromium/net/third_party/nss/ssl/cmpcert.c b/chromium/net/third_party/nss/ssl/cmpcert.c
new file mode 100644
index 00000000000..6d8423822d5
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/cmpcert.c
@@ -0,0 +1,90 @@
+/*
+ * NSS utility functions
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <string.h>
+#include "prerror.h"
+#include "secitem.h"
+#include "prnetdb.h"
+#include "cert.h"
+#include "nspr.h"
+#include "secder.h"
+#include "key.h"
+#include "nss.h"
+
+/*
+ * Look to see if any of the signers in the cert chain for "cert" are found
+ * in the list of caNames.
+ * Returns SECSuccess if so, SECFailure if not.
+ */
+SECStatus
+NSS_CmpCertChainWCANames(CERTCertificate *cert, CERTDistNames *caNames)
+{
+ SECItem * caname;
+ CERTCertificate * curcert;
+ CERTCertificate * oldcert;
+ PRInt32 contentlen;
+ int j;
+ int headerlen;
+ int depth;
+ SECStatus rv;
+ SECItem issuerName;
+ SECItem compatIssuerName;
+
+ if (!cert || !caNames || !caNames->nnames || !caNames->names ||
+ !caNames->names->data)
+ return SECFailure;
+ depth=0;
+ curcert = CERT_DupCertificate(cert);
+
+ while( curcert ) {
+ issuerName = curcert->derIssuer;
+
+ /* compute an alternate issuer name for compatibility with 2.0
+ * enterprise server, which send the CA names without
+ * the outer layer of DER header
+ */
+ rv = DER_Lengths(&issuerName, &headerlen, (PRUint32 *)&contentlen);
+ if ( rv == SECSuccess ) {
+ compatIssuerName.data = &issuerName.data[headerlen];
+ compatIssuerName.len = issuerName.len - headerlen;
+ } else {
+ compatIssuerName.data = NULL;
+ compatIssuerName.len = 0;
+ }
+
+ for (j = 0; j < caNames->nnames; j++) {
+ caname = &caNames->names[j];
+ if (SECITEM_CompareItem(&issuerName, caname) == SECEqual) {
+ rv = SECSuccess;
+ CERT_DestroyCertificate(curcert);
+ goto done;
+ } else if (SECITEM_CompareItem(&compatIssuerName, caname) == SECEqual) {
+ rv = SECSuccess;
+ CERT_DestroyCertificate(curcert);
+ goto done;
+ }
+ }
+ if ( ( depth <= 20 ) &&
+ ( SECITEM_CompareItem(&curcert->derIssuer, &curcert->derSubject)
+ != SECEqual ) ) {
+ oldcert = curcert;
+ curcert = CERT_FindCertByName(curcert->dbhandle,
+ &curcert->derIssuer);
+ CERT_DestroyCertificate(oldcert);
+ depth++;
+ } else {
+ CERT_DestroyCertificate(curcert);
+ curcert = NULL;
+ }
+ }
+ rv = SECFailure;
+
+done:
+ return rv;
+}
+
diff --git a/chromium/net/third_party/nss/ssl/derive.c b/chromium/net/third_party/nss/ssl/derive.c
new file mode 100644
index 00000000000..35cfe25122f
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/derive.c
@@ -0,0 +1,896 @@
+/*
+ * Key Derivation that doesn't use PKCS11
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ssl.h" /* prereq to sslimpl.h */
+#include "certt.h" /* prereq to sslimpl.h */
+#include "keythi.h" /* prereq to sslimpl.h */
+#include "sslimpl.h"
+#ifndef NO_PKCS11_BYPASS
+#include "blapi.h"
+#endif
+
+#include "keyhi.h"
+#include "pk11func.h"
+#include "secasn1.h"
+#include "cert.h"
+#include "secmodt.h"
+
+#include "sslproto.h"
+#include "sslerr.h"
+
+#ifndef NO_PKCS11_BYPASS
+/* make this a macro! */
+#ifdef NOT_A_MACRO
+static void
+buildSSLKey(unsigned char * keyBlock, unsigned int keyLen, SECItem * result,
+ const char * label)
+{
+ result->type = siBuffer;
+ result->data = keyBlock;
+ result->len = keyLen;
+ PRINT_BUF(100, (NULL, label, keyBlock, keyLen));
+}
+#else
+#define buildSSLKey(keyBlock, keyLen, result, label) \
+{ \
+ (result)->type = siBuffer; \
+ (result)->data = keyBlock; \
+ (result)->len = keyLen; \
+ PRINT_BUF(100, (NULL, label, keyBlock, keyLen)); \
+}
+#endif
+
+/*
+ * SSL Key generation given pre master secret
+ */
+#ifndef NUM_MIXERS
+#define NUM_MIXERS 9
+#endif
+static const char * const mixers[NUM_MIXERS] = {
+ "A",
+ "BB",
+ "CCC",
+ "DDDD",
+ "EEEEE",
+ "FFFFFF",
+ "GGGGGGG",
+ "HHHHHHHH",
+ "IIIIIIIII"
+};
+
+
+SECStatus
+ssl3_KeyAndMacDeriveBypass(
+ ssl3CipherSpec * pwSpec,
+ const unsigned char * cr,
+ const unsigned char * sr,
+ PRBool isTLS,
+ PRBool isExport)
+{
+ const ssl3BulkCipherDef *cipher_def = pwSpec->cipher_def;
+ unsigned char * key_block = pwSpec->key_block;
+ unsigned char * key_block2 = NULL;
+ unsigned int block_bytes = 0;
+ unsigned int block_needed = 0;
+ unsigned int i;
+ unsigned int keySize; /* actual size of cipher keys */
+ unsigned int effKeySize; /* effective size of cipher keys */
+ unsigned int macSize; /* size of MAC secret */
+ unsigned int IVSize; /* size of IV */
+ PRBool explicitIV = PR_FALSE;
+ SECStatus rv = SECFailure;
+ SECStatus status = SECSuccess;
+ PRBool isFIPS = PR_FALSE;
+ PRBool isTLS12 = pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2;
+
+ SECItem srcr;
+ SECItem crsr;
+
+ unsigned char srcrdata[SSL3_RANDOM_LENGTH * 2];
+ unsigned char crsrdata[SSL3_RANDOM_LENGTH * 2];
+ PRUint64 md5buf[22];
+ PRUint64 shabuf[40];
+
+#define md5Ctx ((MD5Context *)md5buf)
+#define shaCtx ((SHA1Context *)shabuf)
+
+ static const SECItem zed = { siBuffer, NULL, 0 };
+
+ if (pwSpec->msItem.data == NULL ||
+ pwSpec->msItem.len != SSL3_MASTER_SECRET_LENGTH) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return rv;
+ }
+
+ PRINT_BUF(100, (NULL, "Master Secret", pwSpec->msItem.data,
+ pwSpec->msItem.len));
+
+ /* figure out how much is needed */
+ macSize = pwSpec->mac_size;
+ keySize = cipher_def->key_size;
+ effKeySize = cipher_def->secret_key_size;
+ IVSize = cipher_def->iv_size;
+ if (keySize == 0) {
+ effKeySize = IVSize = 0; /* only MACing */
+ }
+ if (cipher_def->type == type_block &&
+ pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* Block ciphers in >= TLS 1.1 use a per-record, explicit IV. */
+ explicitIV = PR_TRUE;
+ }
+ block_needed =
+ 2 * (macSize + effKeySize + ((!isExport && !explicitIV) * IVSize));
+
+ /*
+ * clear out our returned keys so we can recover on failure
+ */
+ pwSpec->client.write_key_item = zed;
+ pwSpec->client.write_mac_key_item = zed;
+ pwSpec->server.write_key_item = zed;
+ pwSpec->server.write_mac_key_item = zed;
+
+ /* initialize the server random, client random block */
+ srcr.type = siBuffer;
+ srcr.data = srcrdata;
+ srcr.len = sizeof srcrdata;
+ PORT_Memcpy(srcrdata, sr, SSL3_RANDOM_LENGTH);
+ PORT_Memcpy(srcrdata + SSL3_RANDOM_LENGTH, cr, SSL3_RANDOM_LENGTH);
+
+ /* initialize the client random, server random block */
+ crsr.type = siBuffer;
+ crsr.data = crsrdata;
+ crsr.len = sizeof crsrdata;
+ PORT_Memcpy(crsrdata, cr, SSL3_RANDOM_LENGTH);
+ PORT_Memcpy(crsrdata + SSL3_RANDOM_LENGTH, sr, SSL3_RANDOM_LENGTH);
+ PRINT_BUF(100, (NULL, "Key & MAC CRSR", crsr.data, crsr.len));
+
+ /*
+ * generate the key material:
+ */
+ if (isTLS) {
+ SECItem keyblk;
+
+ keyblk.type = siBuffer;
+ keyblk.data = key_block;
+ keyblk.len = block_needed;
+
+ if (isTLS12) {
+ status = TLS_P_hash(HASH_AlgSHA256, &pwSpec->msItem,
+ "key expansion", &srcr, &keyblk, isFIPS);
+ } else {
+ status = TLS_PRF(&pwSpec->msItem, "key expansion", &srcr, &keyblk,
+ isFIPS);
+ }
+ if (status != SECSuccess) {
+ goto key_and_mac_derive_fail;
+ }
+ block_bytes = keyblk.len;
+ } else {
+ /* key_block =
+ * MD5(master_secret + SHA('A' + master_secret +
+ * ServerHello.random + ClientHello.random)) +
+ * MD5(master_secret + SHA('BB' + master_secret +
+ * ServerHello.random + ClientHello.random)) +
+ * MD5(master_secret + SHA('CCC' + master_secret +
+ * ServerHello.random + ClientHello.random)) +
+ * [...];
+ */
+ unsigned int made = 0;
+ for (i = 0; made < block_needed && i < NUM_MIXERS; ++i) {
+ unsigned int outLen;
+ unsigned char sha_out[SHA1_LENGTH];
+
+ SHA1_Begin(shaCtx);
+ SHA1_Update(shaCtx, (unsigned char*)(mixers[i]), i+1);
+ SHA1_Update(shaCtx, pwSpec->msItem.data, pwSpec->msItem.len);
+ SHA1_Update(shaCtx, srcr.data, srcr.len);
+ SHA1_End(shaCtx, sha_out, &outLen, SHA1_LENGTH);
+ PORT_Assert(outLen == SHA1_LENGTH);
+
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, pwSpec->msItem.data, pwSpec->msItem.len);
+ MD5_Update(md5Ctx, sha_out, outLen);
+ MD5_End(md5Ctx, key_block + made, &outLen, MD5_LENGTH);
+ PORT_Assert(outLen == MD5_LENGTH);
+ made += MD5_LENGTH;
+ }
+ block_bytes = made;
+ }
+ PORT_Assert(block_bytes >= block_needed);
+ PORT_Assert(block_bytes <= sizeof pwSpec->key_block);
+ PRINT_BUF(100, (NULL, "key block", key_block, block_bytes));
+
+ /*
+ * Put the key material where it goes.
+ */
+ key_block2 = key_block + block_bytes;
+ i = 0; /* now shows how much consumed */
+
+ /*
+ * The key_block is partitioned as follows:
+ * client_write_MAC_secret[CipherSpec.hash_size]
+ */
+ buildSSLKey(&key_block[i],macSize, &pwSpec->client.write_mac_key_item, \
+ "Client Write MAC Secret");
+ i += macSize;
+
+ /*
+ * server_write_MAC_secret[CipherSpec.hash_size]
+ */
+ buildSSLKey(&key_block[i],macSize, &pwSpec->server.write_mac_key_item, \
+ "Server Write MAC Secret");
+ i += macSize;
+
+ if (!keySize) {
+ /* only MACing */
+ buildSSLKey(NULL, 0, &pwSpec->client.write_key_item, \
+ "Client Write Key (MAC only)");
+ buildSSLKey(NULL, 0, &pwSpec->server.write_key_item, \
+ "Server Write Key (MAC only)");
+ buildSSLKey(NULL, 0, &pwSpec->client.write_iv_item, \
+ "Client Write IV (MAC only)");
+ buildSSLKey(NULL, 0, &pwSpec->server.write_iv_item, \
+ "Server Write IV (MAC only)");
+ } else if (!isExport) {
+ /*
+ ** Generate Domestic write keys and IVs.
+ ** client_write_key[CipherSpec.key_material]
+ */
+ buildSSLKey(&key_block[i], keySize, &pwSpec->client.write_key_item, \
+ "Domestic Client Write Key");
+ i += keySize;
+
+ /*
+ ** server_write_key[CipherSpec.key_material]
+ */
+ buildSSLKey(&key_block[i], keySize, &pwSpec->server.write_key_item, \
+ "Domestic Server Write Key");
+ i += keySize;
+
+ if (IVSize > 0) {
+ if (explicitIV) {
+ static unsigned char zero_block[32];
+ PORT_Assert(IVSize <= sizeof zero_block);
+ buildSSLKey(&zero_block[0], IVSize, \
+ &pwSpec->client.write_iv_item, \
+ "Domestic Client Write IV");
+ buildSSLKey(&zero_block[0], IVSize, \
+ &pwSpec->server.write_iv_item, \
+ "Domestic Server Write IV");
+ } else {
+ /*
+ ** client_write_IV[CipherSpec.IV_size]
+ */
+ buildSSLKey(&key_block[i], IVSize, \
+ &pwSpec->client.write_iv_item, \
+ "Domestic Client Write IV");
+ i += IVSize;
+
+ /*
+ ** server_write_IV[CipherSpec.IV_size]
+ */
+ buildSSLKey(&key_block[i], IVSize, \
+ &pwSpec->server.write_iv_item, \
+ "Domestic Server Write IV");
+ i += IVSize;
+ }
+ }
+ PORT_Assert(i <= block_bytes);
+ } else if (!isTLS) {
+ /*
+ ** Generate SSL3 Export write keys and IVs.
+ */
+ unsigned int outLen;
+
+ /*
+ ** client_write_key[CipherSpec.key_material]
+ ** final_client_write_key = MD5(client_write_key +
+ ** ClientHello.random + ServerHello.random);
+ */
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, &key_block[i], effKeySize);
+ MD5_Update(md5Ctx, crsr.data, crsr.len);
+ MD5_End(md5Ctx, key_block2, &outLen, MD5_LENGTH);
+ i += effKeySize;
+ buildSSLKey(key_block2, keySize, &pwSpec->client.write_key_item, \
+ "SSL3 Export Client Write Key");
+ key_block2 += keySize;
+
+ /*
+ ** server_write_key[CipherSpec.key_material]
+ ** final_server_write_key = MD5(server_write_key +
+ ** ServerHello.random + ClientHello.random);
+ */
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, &key_block[i], effKeySize);
+ MD5_Update(md5Ctx, srcr.data, srcr.len);
+ MD5_End(md5Ctx, key_block2, &outLen, MD5_LENGTH);
+ i += effKeySize;
+ buildSSLKey(key_block2, keySize, &pwSpec->server.write_key_item, \
+ "SSL3 Export Server Write Key");
+ key_block2 += keySize;
+ PORT_Assert(i <= block_bytes);
+
+ if (IVSize) {
+ /*
+ ** client_write_IV =
+ ** MD5(ClientHello.random + ServerHello.random);
+ */
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, crsr.data, crsr.len);
+ MD5_End(md5Ctx, key_block2, &outLen, MD5_LENGTH);
+ buildSSLKey(key_block2, IVSize, &pwSpec->client.write_iv_item, \
+ "SSL3 Export Client Write IV");
+ key_block2 += IVSize;
+
+ /*
+ ** server_write_IV =
+ ** MD5(ServerHello.random + ClientHello.random);
+ */
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, srcr.data, srcr.len);
+ MD5_End(md5Ctx, key_block2, &outLen, MD5_LENGTH);
+ buildSSLKey(key_block2, IVSize, &pwSpec->server.write_iv_item, \
+ "SSL3 Export Server Write IV");
+ key_block2 += IVSize;
+ }
+
+ PORT_Assert(key_block2 - key_block <= sizeof pwSpec->key_block);
+ } else {
+ /*
+ ** Generate TLS Export write keys and IVs.
+ */
+ SECItem secret ;
+ SECItem keyblk ;
+
+ secret.type = siBuffer;
+ keyblk.type = siBuffer;
+ /*
+ ** client_write_key[CipherSpec.key_material]
+ ** final_client_write_key = PRF(client_write_key,
+ ** "client write key",
+ ** client_random + server_random);
+ */
+ secret.data = &key_block[i];
+ secret.len = effKeySize;
+ i += effKeySize;
+ keyblk.data = key_block2;
+ keyblk.len = keySize;
+ status = TLS_PRF(&secret, "client write key", &crsr, &keyblk, isFIPS);
+ if (status != SECSuccess) {
+ goto key_and_mac_derive_fail;
+ }
+ buildSSLKey(key_block2, keySize, &pwSpec->client.write_key_item, \
+ "TLS Export Client Write Key");
+ key_block2 += keySize;
+
+ /*
+ ** server_write_key[CipherSpec.key_material]
+ ** final_server_write_key = PRF(server_write_key,
+ ** "server write key",
+ ** client_random + server_random);
+ */
+ secret.data = &key_block[i];
+ secret.len = effKeySize;
+ i += effKeySize;
+ keyblk.data = key_block2;
+ keyblk.len = keySize;
+ status = TLS_PRF(&secret, "server write key", &crsr, &keyblk, isFIPS);
+ if (status != SECSuccess) {
+ goto key_and_mac_derive_fail;
+ }
+ buildSSLKey(key_block2, keySize, &pwSpec->server.write_key_item, \
+ "TLS Export Server Write Key");
+ key_block2 += keySize;
+
+ /*
+ ** iv_block = PRF("", "IV block", client_random + server_random);
+ ** client_write_IV[SecurityParameters.IV_size]
+ ** server_write_IV[SecurityParameters.IV_size]
+ */
+ if (IVSize) {
+ secret.data = NULL;
+ secret.len = 0;
+ keyblk.data = key_block2;
+ keyblk.len = 2 * IVSize;
+ status = TLS_PRF(&secret, "IV block", &crsr, &keyblk, isFIPS);
+ if (status != SECSuccess) {
+ goto key_and_mac_derive_fail;
+ }
+ buildSSLKey(key_block2, IVSize, \
+ &pwSpec->client.write_iv_item, \
+ "TLS Export Client Write IV");
+ buildSSLKey(key_block2 + IVSize, IVSize, \
+ &pwSpec->server.write_iv_item, \
+ "TLS Export Server Write IV");
+ key_block2 += 2 * IVSize;
+ }
+ PORT_Assert(key_block2 - key_block <= sizeof pwSpec->key_block);
+ }
+ rv = SECSuccess;
+
+key_and_mac_derive_fail:
+
+ MD5_DestroyContext(md5Ctx, PR_FALSE);
+ SHA1_DestroyContext(shaCtx, PR_FALSE);
+
+ if (rv != SECSuccess) {
+ PORT_SetError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ }
+
+ return rv;
+}
+
+
+/* derive the Master Secret from the PMS */
+/* Presently, this is only done wtih RSA PMS, and only on the server side,
+ * so isRSA is always true.
+ */
+SECStatus
+ssl3_MasterKeyDeriveBypass(
+ ssl3CipherSpec * pwSpec,
+ const unsigned char * cr,
+ const unsigned char * sr,
+ const SECItem * pms,
+ PRBool isTLS,
+ PRBool isRSA)
+{
+ unsigned char * key_block = pwSpec->key_block;
+ SECStatus rv = SECSuccess;
+ PRBool isFIPS = PR_FALSE;
+ PRBool isTLS12 = pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2;
+
+ SECItem crsr;
+
+ unsigned char crsrdata[SSL3_RANDOM_LENGTH * 2];
+ PRUint64 md5buf[22];
+ PRUint64 shabuf[40];
+
+#define md5Ctx ((MD5Context *)md5buf)
+#define shaCtx ((SHA1Context *)shabuf)
+
+ /* first do the consistancy checks */
+ if (isRSA) {
+ PORT_Assert(pms->len == SSL3_RSA_PMS_LENGTH);
+ if (pms->len != SSL3_RSA_PMS_LENGTH) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ /* caller must test PMS version for rollback */
+ }
+
+ /* initialize the client random, server random block */
+ crsr.type = siBuffer;
+ crsr.data = crsrdata;
+ crsr.len = sizeof crsrdata;
+ PORT_Memcpy(crsrdata, cr, SSL3_RANDOM_LENGTH);
+ PORT_Memcpy(crsrdata + SSL3_RANDOM_LENGTH, sr, SSL3_RANDOM_LENGTH);
+ PRINT_BUF(100, (NULL, "Master Secret CRSR", crsr.data, crsr.len));
+
+ /* finally do the key gen */
+ if (isTLS) {
+ SECItem master = { siBuffer, NULL, 0 };
+
+ master.data = key_block;
+ master.len = SSL3_MASTER_SECRET_LENGTH;
+
+ if (isTLS12) {
+ rv = TLS_P_hash(HASH_AlgSHA256, pms, "master secret", &crsr,
+ &master, isFIPS);
+ } else {
+ rv = TLS_PRF(pms, "master secret", &crsr, &master, isFIPS);
+ }
+ if (rv != SECSuccess) {
+ PORT_SetError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ }
+ } else {
+ int i;
+ unsigned int made = 0;
+ for (i = 0; i < 3; i++) {
+ unsigned int outLen;
+ unsigned char sha_out[SHA1_LENGTH];
+
+ SHA1_Begin(shaCtx);
+ SHA1_Update(shaCtx, (unsigned char*) mixers[i], i+1);
+ SHA1_Update(shaCtx, pms->data, pms->len);
+ SHA1_Update(shaCtx, crsr.data, crsr.len);
+ SHA1_End(shaCtx, sha_out, &outLen, SHA1_LENGTH);
+ PORT_Assert(outLen == SHA1_LENGTH);
+
+ MD5_Begin(md5Ctx);
+ MD5_Update(md5Ctx, pms->data, pms->len);
+ MD5_Update(md5Ctx, sha_out, outLen);
+ MD5_End(md5Ctx, key_block + made, &outLen, MD5_LENGTH);
+ PORT_Assert(outLen == MD5_LENGTH);
+ made += outLen;
+ }
+ }
+
+ /* store the results */
+ PORT_Memcpy(pwSpec->raw_master_secret, key_block,
+ SSL3_MASTER_SECRET_LENGTH);
+ pwSpec->msItem.data = pwSpec->raw_master_secret;
+ pwSpec->msItem.len = SSL3_MASTER_SECRET_LENGTH;
+ PRINT_BUF(100, (NULL, "Master Secret", pwSpec->msItem.data,
+ pwSpec->msItem.len));
+
+ return rv;
+}
+
+static SECStatus
+ssl_canExtractMS(PK11SymKey *pms, PRBool isTLS, PRBool isDH, PRBool *pcbp)
+{ SECStatus rv;
+ PK11SymKey * ms = NULL;
+ SECItem params = {siBuffer, NULL, 0};
+ CK_SSL3_MASTER_KEY_DERIVE_PARAMS master_params;
+ unsigned char rand[SSL3_RANDOM_LENGTH];
+ CK_VERSION pms_version;
+ CK_MECHANISM_TYPE master_derive;
+ CK_MECHANISM_TYPE key_derive;
+ CK_FLAGS keyFlags;
+
+ if (pms == NULL)
+ return(SECFailure);
+
+ PORT_Memset(rand, 0, SSL3_RANDOM_LENGTH);
+
+ if (isTLS) {
+ if(isDH) master_derive = CKM_TLS_MASTER_KEY_DERIVE_DH;
+ else master_derive = CKM_TLS_MASTER_KEY_DERIVE;
+ key_derive = CKM_TLS_KEY_AND_MAC_DERIVE;
+ keyFlags = CKF_SIGN | CKF_VERIFY;
+ } else {
+ if (isDH) master_derive = CKM_SSL3_MASTER_KEY_DERIVE_DH;
+ else master_derive = CKM_SSL3_MASTER_KEY_DERIVE;
+ key_derive = CKM_SSL3_KEY_AND_MAC_DERIVE;
+ keyFlags = 0;
+ }
+
+ master_params.pVersion = &pms_version;
+ master_params.RandomInfo.pClientRandom = rand;
+ master_params.RandomInfo.ulClientRandomLen = SSL3_RANDOM_LENGTH;
+ master_params.RandomInfo.pServerRandom = rand;
+ master_params.RandomInfo.ulServerRandomLen = SSL3_RANDOM_LENGTH;
+
+ params.data = (unsigned char *) &master_params;
+ params.len = sizeof master_params;
+
+ ms = PK11_DeriveWithFlags(pms, master_derive, &params, key_derive,
+ CKA_DERIVE, 0, keyFlags);
+ if (ms == NULL)
+ return(SECFailure);
+
+ rv = PK11_ExtractKeyValue(ms);
+ *pcbp = (rv == SECSuccess);
+ PK11_FreeSymKey(ms);
+
+ return(rv);
+
+}
+#endif /* !NO_PKCS11_BYPASS */
+
+/* Check the key exchange algorithm for each cipher in the list to see if
+ * a master secret key can be extracted. If the KEA will use keys from the
+ * specified cert make sure the extract operation is attempted from the slot
+ * where the private key resides.
+ * If MS can be extracted for all ciphers, (*pcanbypass) is set to TRUE and
+ * SECSuccess is returned. In all other cases but one (*pcanbypass) is
+ * set to FALSE and SECFailure is returned.
+ * In that last case Derive() has been called successfully but the MS is null,
+ * CanBypass sets (*pcanbypass) to FALSE and returns SECSuccess indicating the
+ * arguments were all valid but the slot cannot be bypassed.
+ */
+
+/* XXX Add SSL_CBP_TLS1_1 and test it in protocolmask when setting isTLS. */
+
+SECStatus
+SSL_CanBypass(CERTCertificate *cert, SECKEYPrivateKey *srvPrivkey,
+ PRUint32 protocolmask, PRUint16 *ciphersuites, int nsuites,
+ PRBool *pcanbypass, void *pwArg)
+{
+#ifdef NO_PKCS11_BYPASS
+ if (!pcanbypass) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ *pcanbypass = PR_FALSE;
+ return SECSuccess;
+#else
+ SECStatus rv;
+ int i;
+ PRUint16 suite;
+ PK11SymKey * pms = NULL;
+ SECKEYPublicKey * srvPubkey = NULL;
+ KeyType privKeytype;
+ PK11SlotInfo * slot = NULL;
+ SECItem param;
+ CK_VERSION version;
+ CK_MECHANISM_TYPE mechanism_array[2];
+ SECItem enc_pms = {siBuffer, NULL, 0};
+ PRBool isTLS = PR_FALSE;
+ SSLCipherSuiteInfo csdef;
+ PRBool testrsa = PR_FALSE;
+ PRBool testrsa_export = PR_FALSE;
+ PRBool testecdh = PR_FALSE;
+ PRBool testecdhe = PR_FALSE;
+#ifdef NSS_ENABLE_ECC
+ SECKEYECParams ecParams = { siBuffer, NULL, 0 };
+#endif
+
+ if (!cert || !srvPrivkey || !ciphersuites || !pcanbypass) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ srvPubkey = CERT_ExtractPublicKey(cert);
+ if (!srvPubkey)
+ return SECFailure;
+
+ *pcanbypass = PR_TRUE;
+ rv = SECFailure;
+
+ /* determine which KEAs to test */
+ /* 0 (SSL_NULL_WITH_NULL_NULL) is used as a list terminator because
+ * SSL3 and TLS specs forbid negotiating that cipher suite number.
+ */
+ for (i=0; i < nsuites && (suite = *ciphersuites++) != 0; i++) {
+ /* skip SSL2 cipher suites and ones NSS doesn't support */
+ if (SSL_GetCipherSuiteInfo(suite, &csdef, sizeof(csdef)) != SECSuccess
+ || SSL_IS_SSL2_CIPHER(suite) )
+ continue;
+ switch (csdef.keaType) {
+ case ssl_kea_rsa:
+ switch (csdef.cipherSuite) {
+ case TLS_RSA_EXPORT1024_WITH_RC4_56_SHA:
+ case TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA:
+ case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+ case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+ testrsa_export = PR_TRUE;
+ }
+ if (!testrsa_export)
+ testrsa = PR_TRUE;
+ break;
+ case ssl_kea_ecdh:
+ if (strcmp(csdef.keaTypeName, "ECDHE") == 0) /* ephemeral? */
+ testecdhe = PR_TRUE;
+ else
+ testecdh = PR_TRUE;
+ break;
+ case ssl_kea_dh:
+ /* this is actually DHE */
+ default:
+ continue;
+ }
+ }
+
+ /* For each protocol try to derive and extract an MS.
+ * Failure of function any function except MS extract means
+ * continue with the next cipher test. Stop testing when the list is
+ * exhausted or when the first MS extract--not derive--fails.
+ */
+ privKeytype = SECKEY_GetPrivateKeyType(srvPrivkey);
+ protocolmask &= SSL_CBP_SSL3|SSL_CBP_TLS1_0;
+ while (protocolmask) {
+ if (protocolmask & SSL_CBP_SSL3) {
+ isTLS = PR_FALSE;
+ protocolmask ^= SSL_CBP_SSL3;
+ } else {
+ isTLS = PR_TRUE;
+ protocolmask ^= SSL_CBP_TLS1_0;
+ }
+
+ if (privKeytype == rsaKey && testrsa_export) {
+ if (PK11_GetPrivateModulusLen(srvPrivkey) > EXPORT_RSA_KEY_LENGTH) {
+ *pcanbypass = PR_FALSE;
+ rv = SECSuccess;
+ break;
+ } else
+ testrsa = PR_TRUE;
+ }
+ for (; privKeytype == rsaKey && testrsa; ) {
+ /* TLS_RSA */
+ unsigned char rsaPmsBuf[SSL3_RSA_PMS_LENGTH];
+ unsigned int outLen = 0;
+ CK_MECHANISM_TYPE target;
+ SECStatus irv;
+
+ mechanism_array[0] = CKM_SSL3_PRE_MASTER_KEY_GEN;
+ mechanism_array[1] = CKM_RSA_PKCS;
+
+ slot = PK11_GetBestSlotMultiple(mechanism_array, 2, pwArg);
+ if (slot == NULL) {
+ PORT_SetError(SSL_ERROR_TOKEN_SLOT_NOT_FOUND);
+ break;
+ }
+
+ /* Generate the pre-master secret ... (client side) */
+ version.major = 3 /*MSB(clientHelloVersion)*/;
+ version.minor = 0 /*LSB(clientHelloVersion)*/;
+ param.data = (unsigned char *)&version;
+ param.len = sizeof version;
+ pms = PK11_KeyGen(slot, CKM_SSL3_PRE_MASTER_KEY_GEN, &param, 0, pwArg);
+ PK11_FreeSlot(slot);
+ if (!pms)
+ break;
+ /* now wrap it */
+ enc_pms.len = SECKEY_PublicKeyStrength(srvPubkey);
+ enc_pms.data = (unsigned char*)PORT_Alloc(enc_pms.len);
+ if (enc_pms.data == NULL) {
+ PORT_SetError(PR_OUT_OF_MEMORY_ERROR);
+ break;
+ }
+ irv = PK11_PubWrapSymKey(CKM_RSA_PKCS, srvPubkey, pms, &enc_pms);
+ if (irv != SECSuccess)
+ break;
+ PK11_FreeSymKey(pms);
+ pms = NULL;
+ /* now do the server side--check the triple bypass first */
+ rv = PK11_PrivDecryptPKCS1(srvPrivkey, rsaPmsBuf, &outLen,
+ sizeof rsaPmsBuf,
+ (unsigned char *)enc_pms.data,
+ enc_pms.len);
+ /* if decrypt worked we're done with the RSA test */
+ if (rv == SECSuccess) {
+ *pcanbypass = PR_TRUE;
+ break;
+ }
+ /* check for fallback to double bypass */
+ target = isTLS ? CKM_TLS_MASTER_KEY_DERIVE
+ : CKM_SSL3_MASTER_KEY_DERIVE;
+ pms = PK11_PubUnwrapSymKey(srvPrivkey, &enc_pms,
+ target, CKA_DERIVE, 0);
+ rv = ssl_canExtractMS(pms, isTLS, PR_FALSE, pcanbypass);
+ if (rv == SECSuccess && *pcanbypass == PR_FALSE)
+ goto done;
+ break;
+ }
+
+ /* Check for NULL to avoid double free.
+ * SECItem_FreeItem sets data NULL in secitem.c#265
+ */
+ if (enc_pms.data != NULL) {
+ SECITEM_FreeItem(&enc_pms, PR_FALSE);
+ }
+#ifdef NSS_ENABLE_ECC
+ for (; (privKeytype == ecKey && ( testecdh || testecdhe)) ||
+ (privKeytype == rsaKey && testecdhe); ) {
+ CK_MECHANISM_TYPE target;
+ SECKEYPublicKey *keapub = NULL;
+ SECKEYPrivateKey *keapriv;
+ SECKEYPublicKey *cpub = NULL; /* client's ephemeral ECDH keys */
+ SECKEYPrivateKey *cpriv = NULL;
+ SECKEYECParams *pecParams = NULL;
+
+ if (privKeytype == ecKey && testecdhe) {
+ /* TLS_ECDHE_ECDSA */
+ pecParams = &srvPubkey->u.ec.DEREncodedParams;
+ } else if (privKeytype == rsaKey && testecdhe) {
+ /* TLS_ECDHE_RSA */
+ ECName ec_curve;
+ int serverKeyStrengthInBits;
+ int signatureKeyStrength;
+ int requiredECCbits;
+
+ /* find a curve of equivalent strength to the RSA key's */
+ requiredECCbits = PK11_GetPrivateModulusLen(srvPrivkey);
+ if (requiredECCbits < 0)
+ break;
+ requiredECCbits *= BPB;
+ serverKeyStrengthInBits = srvPubkey->u.rsa.modulus.len;
+ if (srvPubkey->u.rsa.modulus.data[0] == 0) {
+ serverKeyStrengthInBits--;
+ }
+ /* convert to strength in bits */
+ serverKeyStrengthInBits *= BPB;
+
+ signatureKeyStrength =
+ SSL_RSASTRENGTH_TO_ECSTRENGTH(serverKeyStrengthInBits);
+
+ if ( requiredECCbits > signatureKeyStrength )
+ requiredECCbits = signatureKeyStrength;
+
+ ec_curve =
+ ssl3_GetCurveWithECKeyStrength(
+ ssl3_GetSupportedECCurveMask(NULL),
+ requiredECCbits);
+ rv = ssl3_ECName2Params(NULL, ec_curve, &ecParams);
+ if (rv == SECFailure) {
+ break;
+ }
+ pecParams = &ecParams;
+ }
+
+ if (testecdhe) {
+ /* generate server's ephemeral keys */
+ keapriv = SECKEY_CreateECPrivateKey(pecParams, &keapub, NULL);
+ if (!keapriv || !keapub) {
+ if (keapriv)
+ SECKEY_DestroyPrivateKey(keapriv);
+ if (keapub)
+ SECKEY_DestroyPublicKey(keapub);
+ PORT_SetError(SEC_ERROR_KEYGEN_FAIL);
+ rv = SECFailure;
+ break;
+ }
+ } else {
+ /* TLS_ECDH_ECDSA */
+ keapub = srvPubkey;
+ keapriv = srvPrivkey;
+ pecParams = &srvPubkey->u.ec.DEREncodedParams;
+ }
+
+ /* perform client side ops */
+ /* generate a pair of ephemeral keys using server's parms */
+ cpriv = SECKEY_CreateECPrivateKey(pecParams, &cpub, NULL);
+ if (!cpriv || !cpub) {
+ if (testecdhe) {
+ SECKEY_DestroyPrivateKey(keapriv);
+ SECKEY_DestroyPublicKey(keapub);
+ }
+ PORT_SetError(SEC_ERROR_KEYGEN_FAIL);
+ rv = SECFailure;
+ break;
+ }
+ /* now do the server side */
+ /* determine the PMS using client's public value */
+ target = isTLS ? CKM_TLS_MASTER_KEY_DERIVE_DH
+ : CKM_SSL3_MASTER_KEY_DERIVE_DH;
+ pms = PK11_PubDeriveWithKDF(keapriv, cpub, PR_FALSE, NULL, NULL,
+ CKM_ECDH1_DERIVE,
+ target,
+ CKA_DERIVE, 0, CKD_NULL, NULL, NULL);
+ rv = ssl_canExtractMS(pms, isTLS, PR_TRUE, pcanbypass);
+ SECKEY_DestroyPrivateKey(cpriv);
+ SECKEY_DestroyPublicKey(cpub);
+ if (testecdhe) {
+ SECKEY_DestroyPrivateKey(keapriv);
+ SECKEY_DestroyPublicKey(keapub);
+ }
+ if (rv == SECSuccess && *pcanbypass == PR_FALSE)
+ goto done;
+ break;
+ }
+ /* Check for NULL to avoid double free. */
+ if (ecParams.data != NULL) {
+ PORT_Free(ecParams.data);
+ ecParams.data = NULL;
+ }
+#endif /* NSS_ENABLE_ECC */
+ if (pms)
+ PK11_FreeSymKey(pms);
+ }
+
+ /* *pcanbypass has been set */
+ rv = SECSuccess;
+
+ done:
+ if (pms)
+ PK11_FreeSymKey(pms);
+
+ /* Check for NULL to avoid double free.
+ * SECItem_FreeItem sets data NULL in secitem.c#265
+ */
+ if (enc_pms.data != NULL) {
+ SECITEM_FreeItem(&enc_pms, PR_FALSE);
+ }
+#ifdef NSS_ENABLE_ECC
+ if (ecParams.data != NULL) {
+ PORT_Free(ecParams.data);
+ ecParams.data = NULL;
+ }
+#endif /* NSS_ENABLE_ECC */
+
+ if (srvPubkey) {
+ SECKEY_DestroyPublicKey(srvPubkey);
+ srvPubkey = NULL;
+ }
+
+
+ return rv;
+#endif /* NO_PKCS11_BYPASS */
+}
+
diff --git a/chromium/net/third_party/nss/ssl/dtlscon.c b/chromium/net/third_party/nss/ssl/dtlscon.c
new file mode 100644
index 00000000000..e86ae01ddbe
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/dtlscon.c
@@ -0,0 +1,1139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * DTLS Protocol
+ */
+
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+
+#ifndef PR_ARRAY_SIZE
+#define PR_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+static SECStatus dtls_TransmitMessageFlight(sslSocket *ss);
+static void dtls_RetransmitTimerExpiredCb(sslSocket *ss);
+static SECStatus dtls_SendSavedWriteData(sslSocket *ss);
+
+/* -28 adjusts for the IP/UDP header */
+static const PRUint16 COMMON_MTU_VALUES[] = {
+ 1500 - 28, /* Ethernet MTU */
+ 1280 - 28, /* IPv6 minimum MTU */
+ 576 - 28, /* Common assumption */
+ 256 - 28 /* We're in serious trouble now */
+};
+
+#define DTLS_COOKIE_BYTES 32
+
+/* List copied from ssl3con.c:cipherSuites */
+static const ssl3CipherSuite nonDTLSSuites[] = {
+ /* XXX Make AES-GCM work with DTLS. */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+#endif /* NSS_ENABLE_ECC */
+ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+#endif /* NSS_ENABLE_ECC */
+ TLS_DHE_DSS_WITH_RC4_128_SHA,
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+#endif /* NSS_ENABLE_ECC */
+ SSL_RSA_WITH_RC4_128_MD5,
+ SSL_RSA_WITH_RC4_128_SHA,
+ TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5,
+ 0 /* End of list marker */
+};
+
+/* Map back and forth between TLS and DTLS versions in wire format.
+ * Mapping table is:
+ *
+ * TLS DTLS
+ * 1.1 (0302) 1.0 (feff)
+ */
+SSL3ProtocolVersion
+dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv)
+{
+ /* Anything other than TLS 1.1 is an error, so return
+ * the invalid version ffff. */
+ if (tlsv != SSL_LIBRARY_VERSION_TLS_1_1)
+ return 0xffff;
+
+ return SSL_LIBRARY_VERSION_DTLS_1_0_WIRE;
+}
+
+/* Map known DTLS versions to known TLS versions.
+ * - Invalid versions (< 1.0) return a version of 0
+ * - Versions > known return a version one higher than we know of
+ * to accomodate a theoretically newer version */
+SSL3ProtocolVersion
+dtls_DTLSVersionToTLSVersion(SSL3ProtocolVersion dtlsv)
+{
+ if (MSB(dtlsv) == 0xff) {
+ return 0;
+ }
+
+ if (dtlsv == SSL_LIBRARY_VERSION_DTLS_1_0_WIRE)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+
+ /* Return a fictional higher version than we know of */
+ return SSL_LIBRARY_VERSION_TLS_1_1 + 1;
+}
+
+/* On this socket, Disable non-DTLS cipher suites in the argument's list */
+SECStatus
+ssl3_DisableNonDTLSSuites(sslSocket * ss)
+{
+ const ssl3CipherSuite * suite;
+
+ for (suite = nonDTLSSuites; *suite; ++suite) {
+ SECStatus rv = ssl3_CipherPrefSet(ss, *suite, PR_FALSE);
+
+ PORT_Assert(rv == SECSuccess); /* else is coding error */
+ }
+ return SECSuccess;
+}
+
+/* Allocate a DTLSQueuedMessage.
+ *
+ * Called from dtls_QueueMessage()
+ */
+static DTLSQueuedMessage *
+dtls_AllocQueuedMessage(PRUint16 epoch, SSL3ContentType type,
+ const unsigned char *data, PRUint32 len)
+{
+ DTLSQueuedMessage *msg = NULL;
+
+ msg = PORT_ZAlloc(sizeof(DTLSQueuedMessage));
+ if (!msg)
+ return NULL;
+
+ msg->data = PORT_Alloc(len);
+ if (!msg->data) {
+ PORT_Free(msg);
+ return NULL;
+ }
+ PORT_Memcpy(msg->data, data, len);
+
+ msg->len = len;
+ msg->epoch = epoch;
+ msg->type = type;
+
+ return msg;
+}
+
+/*
+ * Free a handshake message
+ *
+ * Called from dtls_FreeHandshakeMessages()
+ */
+static void
+dtls_FreeHandshakeMessage(DTLSQueuedMessage *msg)
+{
+ if (!msg)
+ return;
+
+ PORT_ZFree(msg->data, msg->len);
+ PORT_Free(msg);
+}
+
+/*
+ * Free a list of handshake messages
+ *
+ * Called from:
+ * dtls_HandleHandshake()
+ * ssl3_DestroySSL3Info()
+ */
+void
+dtls_FreeHandshakeMessages(PRCList *list)
+{
+ PRCList *cur_p;
+
+ while (!PR_CLIST_IS_EMPTY(list)) {
+ cur_p = PR_LIST_TAIL(list);
+ PR_REMOVE_LINK(cur_p);
+ dtls_FreeHandshakeMessage((DTLSQueuedMessage *)cur_p);
+ }
+}
+
+/* Called only from ssl3_HandleRecord, for each (deciphered) DTLS record.
+ * origBuf is the decrypted ssl record content and is expected to contain
+ * complete handshake records
+ * Caller must hold the handshake and RecvBuf locks.
+ *
+ * Note that this code uses msg_len for two purposes:
+ *
+ * (1) To pass the length to ssl3_HandleHandshakeMessage()
+ * (2) To carry the length of a message currently being reassembled
+ *
+ * However, unlike ssl3_HandleHandshake(), it is not used to carry
+ * the state of reassembly (i.e., whether one is in progress). That
+ * is carried in recvdHighWater and recvdFragments.
+ */
+#define OFFSET_BYTE(o) (o/8)
+#define OFFSET_MASK(o) (1 << (o%8))
+
+SECStatus
+dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
+{
+ /* XXX OK for now.
+ * This doesn't work properly with asynchronous certificate validation.
+ * because that returns a WOULDBLOCK error. The current DTLS
+ * applications do not need asynchronous validation, but in the
+ * future we will need to add this.
+ */
+ sslBuffer buf = *origBuf;
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ while (buf.len > 0) {
+ PRUint8 type;
+ PRUint32 message_length;
+ PRUint16 message_seq;
+ PRUint32 fragment_offset;
+ PRUint32 fragment_length;
+ PRUint32 offset;
+
+ if (buf.len < 12) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ break;
+ }
+
+ /* Parse the header */
+ type = buf.buf[0];
+ message_length = (buf.buf[1] << 16) | (buf.buf[2] << 8) | buf.buf[3];
+ message_seq = (buf.buf[4] << 8) | buf.buf[5];
+ fragment_offset = (buf.buf[6] << 16) | (buf.buf[7] << 8) | buf.buf[8];
+ fragment_length = (buf.buf[9] << 16) | (buf.buf[10] << 8) | buf.buf[11];
+
+#define MAX_HANDSHAKE_MSG_LEN 0x1ffff /* 128k - 1 */
+ if (message_length > MAX_HANDSHAKE_MSG_LEN) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+#undef MAX_HANDSHAKE_MSG_LEN
+
+ buf.buf += 12;
+ buf.len -= 12;
+
+ /* This fragment must be complete */
+ if (buf.len < fragment_length) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ break;
+ }
+
+ /* Sanity check the packet contents */
+ if ((fragment_length + fragment_offset) > message_length) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ break;
+ }
+
+ /* There are three ways we could not be ready for this packet.
+ *
+ * 1. It's a partial next message.
+ * 2. It's a partial or complete message beyond the next
+ * 3. It's a message we've already seen
+ *
+ * If it's the complete next message we accept it right away.
+ * This is the common case for short messages
+ */
+ if ((message_seq == ss->ssl3.hs.recvMessageSeq)
+ && (fragment_offset == 0)
+ && (fragment_length == message_length)) {
+ /* Complete next message. Process immediately */
+ ss->ssl3.hs.msg_type = (SSL3HandshakeType)type;
+ ss->ssl3.hs.msg_len = message_length;
+
+ /* At this point we are advancing our state machine, so
+ * we can free our last flight of messages */
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+ ss->ssl3.hs.recvdHighWater = -1;
+ dtls_CancelTimer(ss);
+
+ /* Reset the timer to the initial value if the retry counter
+ * is 0, per Sec. 4.2.4.1 */
+ if (ss->ssl3.hs.rtRetries == 0) {
+ ss->ssl3.hs.rtTimeoutMs = INITIAL_DTLS_TIMEOUT_MS;
+ }
+
+ rv = ssl3_HandleHandshakeMessage(ss, buf.buf, ss->ssl3.hs.msg_len);
+ if (rv == SECFailure) {
+ /* Do not attempt to process rest of messages in this record */
+ break;
+ }
+ } else {
+ if (message_seq < ss->ssl3.hs.recvMessageSeq) {
+ /* Case 3: we do an immediate retransmit if we're
+ * in a waiting state*/
+ if (ss->ssl3.hs.rtTimerCb == NULL) {
+ /* Ignore */
+ } else if (ss->ssl3.hs.rtTimerCb ==
+ dtls_RetransmitTimerExpiredCb) {
+ SSL_TRC(30, ("%d: SSL3[%d]: Retransmit detected",
+ SSL_GETPID(), ss->fd));
+ /* Check to see if we retransmitted recently. If so,
+ * suppress the triggered retransmit. This avoids
+ * retransmit wars after packet loss.
+ * This is not in RFC 5346 but should be
+ */
+ if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) >
+ (ss->ssl3.hs.rtTimeoutMs / 4)) {
+ SSL_TRC(30,
+ ("%d: SSL3[%d]: Shortcutting retransmit timer",
+ SSL_GETPID(), ss->fd));
+
+ /* Cancel the timer and call the CB,
+ * which re-arms the timer */
+ dtls_CancelTimer(ss);
+ dtls_RetransmitTimerExpiredCb(ss);
+ rv = SECSuccess;
+ break;
+ } else {
+ SSL_TRC(30,
+ ("%d: SSL3[%d]: We just retransmitted. Ignoring.",
+ SSL_GETPID(), ss->fd));
+ rv = SECSuccess;
+ break;
+ }
+ } else if (ss->ssl3.hs.rtTimerCb == dtls_FinishedTimerCb) {
+ /* Retransmit the messages and re-arm the timer
+ * Note that we are not backing off the timer here.
+ * The spec isn't clear and my reasoning is that this
+ * may be a re-ordered packet rather than slowness,
+ * so let's be aggressive. */
+ dtls_CancelTimer(ss);
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv == SECSuccess) {
+ rv = dtls_StartTimer(ss, dtls_FinishedTimerCb);
+ }
+ if (rv != SECSuccess)
+ return rv;
+ break;
+ }
+ } else if (message_seq > ss->ssl3.hs.recvMessageSeq) {
+ /* Case 2
+ *
+ * Ignore this message. This means we don't handle out of
+ * order complete messages that well, but we're still
+ * compliant and this probably does not happen often
+ *
+ * XXX OK for now. Maybe do something smarter at some point?
+ */
+ } else {
+ /* Case 1
+ *
+ * Buffer the fragment for reassembly
+ */
+ /* Make room for the message */
+ if (ss->ssl3.hs.recvdHighWater == -1) {
+ PRUint32 map_length = OFFSET_BYTE(message_length) + 1;
+
+ rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, message_length);
+ if (rv != SECSuccess)
+ break;
+ /* Make room for the fragment map */
+ rv = sslBuffer_Grow(&ss->ssl3.hs.recvdFragments,
+ map_length);
+ if (rv != SECSuccess)
+ break;
+
+ /* Reset the reassembly map */
+ ss->ssl3.hs.recvdHighWater = 0;
+ PORT_Memset(ss->ssl3.hs.recvdFragments.buf, 0,
+ ss->ssl3.hs.recvdFragments.space);
+ ss->ssl3.hs.msg_type = (SSL3HandshakeType)type;
+ ss->ssl3.hs.msg_len = message_length;
+ }
+
+ /* If we have a message length mismatch, abandon the reassembly
+ * in progress and hope that the next retransmit will give us
+ * something sane
+ */
+ if (message_length != ss->ssl3.hs.msg_len) {
+ ss->ssl3.hs.recvdHighWater = -1;
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HANDSHAKE);
+ rv = SECFailure;
+ break;
+ }
+
+ /* Now copy this fragment into the buffer */
+ PORT_Assert((fragment_offset + fragment_length) <=
+ ss->ssl3.hs.msg_body.space);
+ PORT_Memcpy(ss->ssl3.hs.msg_body.buf + fragment_offset,
+ buf.buf, fragment_length);
+
+ /* This logic is a bit tricky. We have two values for
+ * reassembly state:
+ *
+ * - recvdHighWater contains the highest contiguous number of
+ * bytes received
+ * - recvdFragments contains a bitmask of packets received
+ * above recvdHighWater
+ *
+ * This avoids having to fill in the bitmask in the common
+ * case of adjacent fragments received in sequence
+ */
+ if (fragment_offset <= ss->ssl3.hs.recvdHighWater) {
+ /* Either this is the adjacent fragment or an overlapping
+ * fragment */
+ ss->ssl3.hs.recvdHighWater = fragment_offset +
+ fragment_length;
+ } else {
+ for (offset = fragment_offset;
+ offset < fragment_offset + fragment_length;
+ offset++) {
+ ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] |=
+ OFFSET_MASK(offset);
+ }
+ }
+
+ /* Now figure out the new high water mark if appropriate */
+ for (offset = ss->ssl3.hs.recvdHighWater;
+ offset < ss->ssl3.hs.msg_len; offset++) {
+ /* Note that this loop is not efficient, since it counts
+ * bit by bit. If we have a lot of out-of-order packets,
+ * we should optimize this */
+ if (ss->ssl3.hs.recvdFragments.buf[OFFSET_BYTE(offset)] &
+ OFFSET_MASK(offset)) {
+ ss->ssl3.hs.recvdHighWater++;
+ } else {
+ break;
+ }
+ }
+
+ /* If we have all the bytes, then we are good to go */
+ if (ss->ssl3.hs.recvdHighWater == ss->ssl3.hs.msg_len) {
+ ss->ssl3.hs.recvdHighWater = -1;
+
+ rv = ssl3_HandleHandshakeMessage(ss,
+ ss->ssl3.hs.msg_body.buf,
+ ss->ssl3.hs.msg_len);
+ if (rv == SECFailure)
+ break; /* Skip rest of record */
+
+ /* At this point we are advancing our state machine, so
+ * we can free our last flight of messages */
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+ dtls_CancelTimer(ss);
+
+ /* If there have been no retries this time, reset the
+ * timer value to the default per Section 4.2.4.1 */
+ if (ss->ssl3.hs.rtRetries == 0) {
+ ss->ssl3.hs.rtTimeoutMs = INITIAL_DTLS_TIMEOUT_MS;
+ }
+ }
+ }
+ }
+
+ buf.buf += fragment_length;
+ buf.len -= fragment_length;
+ }
+
+ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */
+
+ /* XXX OK for now. In future handle rv == SECWouldBlock safely in order
+ * to deal with asynchronous certificate verification */
+ return rv;
+}
+
+/* Enqueue a message (either handshake or CCS)
+ *
+ * Called from:
+ * dtls_StageHandshakeMessage()
+ * ssl3_SendChangeCipherSpecs()
+ */
+SECStatus dtls_QueueMessage(sslSocket *ss, SSL3ContentType type,
+ const SSL3Opaque *pIn, PRInt32 nIn)
+{
+ SECStatus rv = SECSuccess;
+ DTLSQueuedMessage *msg = NULL;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ msg = dtls_AllocQueuedMessage(ss->ssl3.cwSpec->epoch, type, pIn, nIn);
+
+ if (!msg) {
+ PORT_SetError(SEC_ERROR_NO_MEMORY);
+ rv = SECFailure;
+ } else {
+ PR_APPEND_LINK(&msg->link, &ss->ssl3.hs.lastMessageFlight);
+ }
+
+ return rv;
+}
+
+/* Add DTLS handshake message to the pending queue
+ * Empty the sendBuf buffer.
+ * This function returns SECSuccess or SECFailure, never SECWouldBlock.
+ * Always set sendBuf.len to 0, even when returning SECFailure.
+ *
+ * Called from:
+ * ssl3_AppendHandshakeHeader()
+ * dtls_FlushHandshake()
+ */
+SECStatus
+dtls_StageHandshakeMessage(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ /* This function is sometimes called when no data is actually to
+ * be staged, so just return SECSuccess. */
+ if (!ss->sec.ci.sendBuf.buf || !ss->sec.ci.sendBuf.len)
+ return rv;
+
+ rv = dtls_QueueMessage(ss, content_handshake,
+ ss->sec.ci.sendBuf.buf, ss->sec.ci.sendBuf.len);
+
+ /* Whether we succeeded or failed, toss the old handshake data. */
+ ss->sec.ci.sendBuf.len = 0;
+ return rv;
+}
+
+/* Enqueue the handshake message in sendBuf (if any) and then
+ * transmit the resulting flight of handshake messages.
+ *
+ * Called from:
+ * ssl3_FlushHandshake()
+ */
+SECStatus
+dtls_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ rv = dtls_StageHandshakeMessage(ss);
+ if (rv != SECSuccess)
+ return rv;
+
+ if (!(flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv != SECSuccess)
+ return rv;
+
+ if (!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)) {
+ ss->ssl3.hs.rtRetries = 0;
+ rv = dtls_StartTimer(ss, dtls_RetransmitTimerExpiredCb);
+ }
+ }
+
+ return rv;
+}
+
+/* The callback for when the retransmit timer expires
+ *
+ * Called from:
+ * dtls_CheckTimer()
+ * dtls_HandleHandshake()
+ */
+static void
+dtls_RetransmitTimerExpiredCb(sslSocket *ss)
+{
+ SECStatus rv = SECFailure;
+
+ ss->ssl3.hs.rtRetries++;
+
+ if (!(ss->ssl3.hs.rtRetries % 3)) {
+ /* If one of the messages was potentially greater than > MTU,
+ * then downgrade. Do this every time we have retransmitted a
+ * message twice, per RFC 6347 Sec. 4.1.1 */
+ dtls_SetMTU(ss, ss->ssl3.hs.maxMessageSent - 1);
+ }
+
+ rv = dtls_TransmitMessageFlight(ss);
+ if (rv == SECSuccess) {
+
+ /* Re-arm the timer */
+ rv = dtls_RestartTimer(ss, PR_TRUE, dtls_RetransmitTimerExpiredCb);
+ }
+
+ if (rv == SECFailure) {
+ /* XXX OK for now. In future maybe signal the stack that we couldn't
+ * transmit. For now, let the read handle any real network errors */
+ }
+}
+
+/* Transmit a flight of handshake messages, stuffing them
+ * into as few records as seems reasonable
+ *
+ * Called from:
+ * dtls_FlushHandshake()
+ * dtls_RetransmitTimerExpiredCb()
+ */
+static SECStatus
+dtls_TransmitMessageFlight(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+ PRCList *msg_p;
+ PRUint16 room_left = ss->ssl3.mtu;
+ PRInt32 sent;
+
+ ssl_GetXmitBufLock(ss);
+ ssl_GetSpecReadLock(ss);
+
+ /* DTLS does not buffer its handshake messages in
+ * ss->pendingBuf, but rather in the lastMessageFlight
+ * structure. This is just a sanity check that
+ * some programming error hasn't inadvertantly
+ * stuffed something in ss->pendingBuf
+ */
+ PORT_Assert(!ss->pendingBuf.len);
+ for (msg_p = PR_LIST_HEAD(&ss->ssl3.hs.lastMessageFlight);
+ msg_p != &ss->ssl3.hs.lastMessageFlight;
+ msg_p = PR_NEXT_LINK(msg_p)) {
+ DTLSQueuedMessage *msg = (DTLSQueuedMessage *)msg_p;
+
+ /* The logic here is:
+ *
+ * 1. If this is a message that will not fit into the remaining
+ * space, then flush.
+ * 2. If the message will now fit into the remaining space,
+ * encrypt, buffer, and loop.
+ * 3. If the message will not fit, then fragment.
+ *
+ * At the end of the function, flush.
+ */
+ if ((msg->len + SSL3_BUFFER_FUDGE) > room_left) {
+ /* The message will not fit into the remaining space, so flush */
+ rv = dtls_SendSavedWriteData(ss);
+ if (rv != SECSuccess)
+ break;
+
+ room_left = ss->ssl3.mtu;
+ }
+
+ if ((msg->len + SSL3_BUFFER_FUDGE) <= room_left) {
+ /* The message will fit, so encrypt and then continue with the
+ * next packet */
+ sent = ssl3_SendRecord(ss, msg->epoch, msg->type,
+ msg->data, msg->len,
+ ssl_SEND_FLAG_FORCE_INTO_BUFFER |
+ ssl_SEND_FLAG_USE_EPOCH);
+ if (sent != msg->len) {
+ rv = SECFailure;
+ if (sent != -1) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ break;
+ }
+
+ room_left = ss->ssl3.mtu - ss->pendingBuf.len;
+ } else {
+ /* The message will not fit, so fragment.
+ *
+ * XXX OK for now. Arrange to coalesce the last fragment
+ * of this message with the next message if possible.
+ * That would be more efficient.
+ */
+ PRUint32 fragment_offset = 0;
+ unsigned char fragment[DTLS_MAX_MTU]; /* >= than largest
+ * plausible MTU */
+
+ /* Assert that we have already flushed */
+ PORT_Assert(room_left == ss->ssl3.mtu);
+
+ /* Case 3: We now need to fragment this message
+ * DTLS only supports fragmenting handshaking messages */
+ PORT_Assert(msg->type == content_handshake);
+
+ /* The headers consume 12 bytes so the smalles possible
+ * message (i.e., an empty one) is 12 bytes
+ */
+ PORT_Assert(msg->len >= 12);
+
+ while ((fragment_offset + 12) < msg->len) {
+ PRUint32 fragment_len;
+ const unsigned char *content = msg->data + 12;
+ PRUint32 content_len = msg->len - 12;
+
+ /* The reason we use 8 here is that that's the length of
+ * the new DTLS data that we add to the header */
+ fragment_len = PR_MIN(room_left - (SSL3_BUFFER_FUDGE + 8),
+ content_len - fragment_offset);
+ PORT_Assert(fragment_len < DTLS_MAX_MTU - 12);
+ /* Make totally sure that we are within the buffer.
+ * Note that the only way that fragment len could get
+ * adjusted here is if
+ *
+ * (a) we are in release mode so the PORT_Assert is compiled out
+ * (b) either the MTU table is inconsistent with DTLS_MAX_MTU
+ * or ss->ssl3.mtu has become corrupt.
+ */
+ fragment_len = PR_MIN(fragment_len, DTLS_MAX_MTU - 12);
+
+ /* Construct an appropriate-sized fragment */
+ /* Type, length, sequence */
+ PORT_Memcpy(fragment, msg->data, 6);
+
+ /* Offset */
+ fragment[6] = (fragment_offset >> 16) & 0xff;
+ fragment[7] = (fragment_offset >> 8) & 0xff;
+ fragment[8] = (fragment_offset) & 0xff;
+
+ /* Fragment length */
+ fragment[9] = (fragment_len >> 16) & 0xff;
+ fragment[10] = (fragment_len >> 8) & 0xff;
+ fragment[11] = (fragment_len) & 0xff;
+
+ PORT_Memcpy(fragment + 12, content + fragment_offset,
+ fragment_len);
+
+ /*
+ * Send the record. We do this in two stages
+ * 1. Encrypt
+ */
+ sent = ssl3_SendRecord(ss, msg->epoch, msg->type,
+ fragment, fragment_len + 12,
+ ssl_SEND_FLAG_FORCE_INTO_BUFFER |
+ ssl_SEND_FLAG_USE_EPOCH);
+ if (sent != (fragment_len + 12)) {
+ rv = SECFailure;
+ if (sent != -1) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ break;
+ }
+
+ /* 2. Flush */
+ rv = dtls_SendSavedWriteData(ss);
+ if (rv != SECSuccess)
+ break;
+
+ fragment_offset += fragment_len;
+ }
+ }
+ }
+
+ /* Finally, we need to flush */
+ if (rv == SECSuccess)
+ rv = dtls_SendSavedWriteData(ss);
+
+ /* Give up the locks */
+ ssl_ReleaseSpecReadLock(ss);
+ ssl_ReleaseXmitBufLock(ss);
+
+ return rv;
+}
+
+/* Flush the data in the pendingBuf and update the max message sent
+ * so we can adjust the MTU estimate if we need to.
+ * Wrapper for ssl_SendSavedWriteData.
+ *
+ * Called from dtls_TransmitMessageFlight()
+ */
+static
+SECStatus dtls_SendSavedWriteData(sslSocket *ss)
+{
+ PRInt32 sent;
+
+ sent = ssl_SendSavedWriteData(ss);
+ if (sent < 0)
+ return SECFailure;
+
+ /* We should always have complete writes b/c datagram sockets
+ * don't really block */
+ if (ss->pendingBuf.len > 0) {
+ ssl_MapLowLevelError(SSL_ERROR_SOCKET_WRITE_FAILURE);
+ return SECFailure;
+ }
+
+ /* Update the largest message sent so we can adjust the MTU
+ * estimate if necessary */
+ if (sent > ss->ssl3.hs.maxMessageSent)
+ ss->ssl3.hs.maxMessageSent = sent;
+
+ return SECSuccess;
+}
+
+/* Compress, MAC, encrypt a DTLS record. Allows specification of
+ * the epoch using epoch value. If use_epoch is PR_TRUE then
+ * we use the provided epoch. If use_epoch is PR_FALSE then
+ * whatever the current value is in effect is used.
+ *
+ * Called from ssl3_SendRecord()
+ */
+SECStatus
+dtls_CompressMACEncryptRecord(sslSocket * ss,
+ DTLSEpoch epoch,
+ PRBool use_epoch,
+ SSL3ContentType type,
+ const SSL3Opaque * pIn,
+ PRUint32 contentLen,
+ sslBuffer * wrBuf)
+{
+ SECStatus rv = SECFailure;
+ ssl3CipherSpec * cwSpec;
+
+ ssl_GetSpecReadLock(ss); /********************************/
+
+ /* The reason for this switch-hitting code is that we might have
+ * a flight of records spanning an epoch boundary, e.g.,
+ *
+ * ClientKeyExchange (epoch = 0)
+ * ChangeCipherSpec (epoch = 0)
+ * Finished (epoch = 1)
+ *
+ * Thus, each record needs a different cipher spec. The information
+ * about which epoch to use is carried with the record.
+ */
+ if (use_epoch) {
+ if (ss->ssl3.cwSpec->epoch == epoch)
+ cwSpec = ss->ssl3.cwSpec;
+ else if (ss->ssl3.pwSpec->epoch == epoch)
+ cwSpec = ss->ssl3.pwSpec;
+ else
+ cwSpec = NULL;
+ } else {
+ cwSpec = ss->ssl3.cwSpec;
+ }
+
+ if (cwSpec) {
+ rv = ssl3_CompressMACEncryptRecord(cwSpec, ss->sec.isServer, PR_TRUE,
+ PR_FALSE, type, pIn, contentLen,
+ wrBuf);
+ } else {
+ PR_NOT_REACHED("Couldn't find a cipher spec matching epoch");
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ ssl_ReleaseSpecReadLock(ss); /************************************/
+
+ return rv;
+}
+
+/* Start a timer
+ *
+ * Called from:
+ * dtls_HandleHandshake()
+ * dtls_FlushHAndshake()
+ * dtls_RestartTimer()
+ */
+SECStatus
+dtls_StartTimer(sslSocket *ss, DTLSTimerCb cb)
+{
+ PORT_Assert(ss->ssl3.hs.rtTimerCb == NULL);
+
+ ss->ssl3.hs.rtTimerStarted = PR_IntervalNow();
+ ss->ssl3.hs.rtTimerCb = cb;
+
+ return SECSuccess;
+}
+
+/* Restart a timer with optional backoff
+ *
+ * Called from dtls_RetransmitTimerExpiredCb()
+ */
+SECStatus
+dtls_RestartTimer(sslSocket *ss, PRBool backoff, DTLSTimerCb cb)
+{
+ if (backoff) {
+ ss->ssl3.hs.rtTimeoutMs *= 2;
+ if (ss->ssl3.hs.rtTimeoutMs > MAX_DTLS_TIMEOUT_MS)
+ ss->ssl3.hs.rtTimeoutMs = MAX_DTLS_TIMEOUT_MS;
+ }
+
+ return dtls_StartTimer(ss, cb);
+}
+
+/* Cancel a pending timer
+ *
+ * Called from:
+ * dtls_HandleHandshake()
+ * dtls_CheckTimer()
+ */
+void
+dtls_CancelTimer(sslSocket *ss)
+{
+ PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
+
+ ss->ssl3.hs.rtTimerCb = NULL;
+}
+
+/* Check the pending timer and fire the callback if it expired
+ *
+ * Called from ssl3_GatherCompleteHandshake()
+ */
+void
+dtls_CheckTimer(sslSocket *ss)
+{
+ if (!ss->ssl3.hs.rtTimerCb)
+ return;
+
+ if ((PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted) >
+ PR_MillisecondsToInterval(ss->ssl3.hs.rtTimeoutMs)) {
+ /* Timer has expired */
+ DTLSTimerCb cb = ss->ssl3.hs.rtTimerCb;
+
+ /* Cancel the timer so that we can call the CB safely */
+ dtls_CancelTimer(ss);
+
+ /* Now call the CB */
+ cb(ss);
+ }
+}
+
+/* The callback to fire when the holddown timer for the Finished
+ * message expires and we can delete it
+ *
+ * Called from dtls_CheckTimer()
+ */
+void
+dtls_FinishedTimerCb(sslSocket *ss)
+{
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE);
+}
+
+/* Cancel the Finished hold-down timer and destroy the
+ * pending cipher spec. Note that this means that
+ * successive rehandshakes will fail if the Finished is
+ * lost.
+ *
+ * XXX OK for now. Figure out how to handle the combination
+ * of Finished lost and rehandshake
+ */
+void
+dtls_RehandshakeCleanup(sslSocket *ss)
+{
+ dtls_CancelTimer(ss);
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE);
+ ss->ssl3.hs.sendMessageSeq = 0;
+ ss->ssl3.hs.recvMessageSeq = 0;
+}
+
+/* Set the MTU to the next step less than or equal to the
+ * advertised value. Also used to downgrade the MTU by
+ * doing dtls_SetMTU(ss, biggest packet set).
+ *
+ * Passing 0 means set this to the largest MTU known
+ * (effectively resetting the PMTU backoff value).
+ *
+ * Called by:
+ * ssl3_InitState()
+ * dtls_RetransmitTimerExpiredCb()
+ */
+void
+dtls_SetMTU(sslSocket *ss, PRUint16 advertised)
+{
+ int i;
+
+ if (advertised == 0) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[0];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+
+ for (i = 0; i < PR_ARRAY_SIZE(COMMON_MTU_VALUES); i++) {
+ if (COMMON_MTU_VALUES[i] <= advertised) {
+ ss->ssl3.mtu = COMMON_MTU_VALUES[i];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+ return;
+ }
+ }
+
+ /* Fallback */
+ ss->ssl3.mtu = COMMON_MTU_VALUES[PR_ARRAY_SIZE(COMMON_MTU_VALUES)-1];
+ SSL_TRC(30, ("Resetting MTU to %d", ss->ssl3.mtu));
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a
+ * DTLS hello_verify_request
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+SECStatus
+dtls_HandleHelloVerifyRequest(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ int errCode = SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST;
+ SECStatus rv;
+ PRInt32 temp;
+ SECItem cookie = {siBuffer, NULL, 0};
+ SSL3AlertDescription desc = illegal_parameter;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle hello_verify_request handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->ssl3.hs.ws != wait_server_hello) {
+ errCode = SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST;
+ desc = unexpected_message;
+ goto alert_loser;
+ }
+
+ /* The version */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (temp < 0) {
+ goto loser; /* alert has been sent */
+ }
+
+ if (temp != SSL_LIBRARY_VERSION_DTLS_1_0_WIRE) {
+ /* Note: this will need adjustment for DTLS 1.2 per Section 4.2.1 */
+ goto alert_loser;
+ }
+
+ /* The cookie */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &cookie, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* alert has been sent */
+ }
+ if (cookie.len > DTLS_COOKIE_BYTES) {
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ PORT_Memcpy(ss->ssl3.hs.cookie, cookie.data, cookie.len);
+ ss->ssl3.hs.cookieLen = cookie.len;
+
+
+ ssl_GetXmitBufLock(ss); /*******************************/
+
+ /* Now re-send the client hello */
+ rv = ssl3_SendClientHello(ss, PR_TRUE);
+
+ ssl_ReleaseXmitBufLock(ss); /*******************************/
+
+ if (rv == SECSuccess)
+ return rv;
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+
+loser:
+ errCode = ssl_MapLowLevelError(errCode);
+ return SECFailure;
+}
+
+/* Initialize the DTLS anti-replay window
+ *
+ * Called from:
+ * ssl3_SetupPendingCipherSpec()
+ * ssl3_InitCipherSpec()
+ */
+void
+dtls_InitRecvdRecords(DTLSRecvdRecords *records)
+{
+ PORT_Memset(records->data, 0, sizeof(records->data));
+ records->left = 0;
+ records->right = DTLS_RECVD_RECORDS_WINDOW - 1;
+}
+
+/*
+ * Has this DTLS record been received? Return values are:
+ * -1 -- out of range to the left
+ * 0 -- not received yet
+ * 1 -- replay
+ *
+ * Called from: dtls_HandleRecord()
+ */
+int
+dtls_RecordGetRecvd(DTLSRecvdRecords *records, PRUint64 seq)
+{
+ PRUint64 offset;
+
+ /* Out of range to the left */
+ if (seq < records->left) {
+ return -1;
+ }
+
+ /* Out of range to the right; since we advance the window on
+ * receipt, that means that this packet has not been received
+ * yet */
+ if (seq > records->right)
+ return 0;
+
+ offset = seq % DTLS_RECVD_RECORDS_WINDOW;
+
+ return !!(records->data[offset / 8] & (1 << (offset % 8)));
+}
+
+/* Update the DTLS anti-replay window
+ *
+ * Called from ssl3_HandleRecord()
+ */
+void
+dtls_RecordSetRecvd(DTLSRecvdRecords *records, PRUint64 seq)
+{
+ PRUint64 offset;
+
+ if (seq < records->left)
+ return;
+
+ if (seq > records->right) {
+ PRUint64 new_left;
+ PRUint64 new_right;
+ PRUint64 right;
+
+ /* Slide to the right; this is the tricky part
+ *
+ * 1. new_top is set to have room for seq, on the
+ * next byte boundary by setting the right 8
+ * bits of seq
+ * 2. new_left is set to compensate.
+ * 3. Zero all bits between top and new_top. Since
+ * this is a ring, this zeroes everything as-yet
+ * unseen. Because we always operate on byte
+ * boundaries, we can zero one byte at a time
+ */
+ new_right = seq | 0x07;
+ new_left = (new_right - DTLS_RECVD_RECORDS_WINDOW) + 1;
+
+ for (right = records->right + 8; right <= new_right; right += 8) {
+ offset = right % DTLS_RECVD_RECORDS_WINDOW;
+ records->data[offset / 8] = 0;
+ }
+
+ records->right = new_right;
+ records->left = new_left;
+ }
+
+ offset = seq % DTLS_RECVD_RECORDS_WINDOW;
+
+ records->data[offset / 8] |= (1 << (offset % 8));
+}
+
+SECStatus
+DTLS_GetHandshakeTimeout(PRFileDesc *socket, PRIntervalTime *timeout)
+{
+ sslSocket * ss = NULL;
+ PRIntervalTime elapsed;
+ PRIntervalTime desired;
+
+ ss = ssl_FindSocket(socket);
+
+ if (!ss)
+ return SECFailure;
+
+ if (!IS_DTLS(ss))
+ return SECFailure;
+
+ if (!ss->ssl3.hs.rtTimerCb)
+ return SECFailure;
+
+ elapsed = PR_IntervalNow() - ss->ssl3.hs.rtTimerStarted;
+ desired = PR_MillisecondsToInterval(ss->ssl3.hs.rtTimeoutMs);
+ if (elapsed > desired) {
+ /* Timer expired */
+ *timeout = PR_INTERVAL_NO_WAIT;
+ } else {
+ *timeout = desired - elapsed;
+ }
+
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/exports_win.def b/chromium/net/third_party/nss/ssl/exports_win.def
new file mode 100644
index 00000000000..09c70b0b410
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/exports_win.def
@@ -0,0 +1,60 @@
+; Copyright (c) 2013 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.
+
+LIBRARY CRSSL
+EXPORTS
+
+NSS_CmpCertChainWCANames
+NSS_FindCertKEAType
+NSS_GetClientAuthData
+NSS_SetDomesticPolicy
+SSL_AuthCertificate
+SSL_AuthCertificateHook
+SSL_CipherPolicyGet
+SSL_CipherPolicySet
+SSL_CipherPrefGet
+SSL_CipherPrefGetDefault
+SSL_CipherPrefSet
+SSL_CipherPrefSetDefault
+SSL_ClearSessionCache
+SSL_ConfigSecureServer
+SSL_ConfigServerSessionIDCache
+SSL_ForceHandshake
+SSL_GetClientAuthDataHook
+SSL_GetSessionID
+SSL_HandshakeCallback
+SSL_ImportFD
+SSL_InvalidateSession
+SSL_OptionGet
+SSL_OptionGetDefault
+SSL_OptionSet
+SSL_OptionSetDefault
+SSL_PeerCertificate
+SSL_PeerStapledOCSPResponses
+SSL_ResetHandshake
+SSL_SetSockPeerID
+SSL_SetURL
+SSL_GetChannelInfo
+SSL_GetCipherSuiteInfo
+SSL_ShutdownServerSessionIDCache
+SSL_GetImplementedCiphers
+SSL_GetNumImplementedCiphers
+SSL_HandshakeNegotiatedExtension
+SSL_SetNextProtoCallback
+SSL_SetNextProtoNego
+SSL_GetNextProto
+DTLS_GetHandshakeTimeout
+DTLS_ImportFD
+SSL_ExportKeyingMaterial
+SSL_VersionRangeSet
+SSL_GetSRTPCipher
+SSL_SetSRTPCiphers
+
+; Chromium patches
+SSL_PeerCertificateChain
+SSL_SetClientChannelIDCallback
+SSL_GetPlatformClientAuthDataHook
+SSL_HandshakeResumedSession
+SSL_RestartHandshakeAfterChannelIDReq
+SSL_GetChannelBinding
diff --git a/chromium/net/third_party/nss/ssl/manifest.mn b/chromium/net/third_party/nss/ssl/manifest.mn
new file mode 100644
index 00000000000..4d46d46b86b
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/manifest.mn
@@ -0,0 +1,53 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+CORE_DEPTH = ../..
+
+# DEFINES = -DTRACE
+
+EXPORTS = \
+ ssl.h \
+ sslt.h \
+ sslerr.h \
+ sslproto.h \
+ preenc.h \
+ $(NULL)
+
+MODULE = nss
+MAPFILE = $(OBJDIR)/ssl.def
+
+CSRCS = \
+ derive.c \
+ dtlscon.c \
+ prelib.c \
+ ssl3con.c \
+ ssl3gthr.c \
+ sslauth.c \
+ sslcon.c \
+ ssldef.c \
+ sslenum.c \
+ sslerr.c \
+ sslerrstrs.c \
+ sslinit.c \
+ ssl3ext.c \
+ sslgathr.c \
+ sslmutex.c \
+ sslnonce.c \
+ sslreveal.c \
+ sslsecur.c \
+ sslsnce.c \
+ sslsock.c \
+ ssltrace.c \
+ sslver.c \
+ authcert.c \
+ cmpcert.c \
+ sslinfo.c \
+ ssl3ecc.c \
+ $(NULL)
+
+LIBRARY_NAME = ssl
+LIBRARY_VERSION = 3
+
+# This part of the code, including all sub-dirs, can be optimized for size
+export ALLOW_OPT_CODE_SIZE = 1
diff --git a/chromium/net/third_party/nss/ssl/notes.txt b/chromium/net/third_party/nss/ssl/notes.txt
new file mode 100644
index 00000000000..a71c08ef2a5
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/notes.txt
@@ -0,0 +1,134 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SSL's Buffers: enumerated and explained.
+
+---------------------------------------------------------------------------
+incoming:
+
+gs = ss->gather
+hs = ss->ssl3->hs
+
+gs->inbuf SSL3 only: incoming (encrypted) ssl records are placed here,
+ and then decrypted (or copied) to gs->buf.
+
+gs->buf SSL2: incoming SSL records are put here, and then decrypted
+ in place.
+ SSL3: ssl3_HandleHandshake puts decrypted ssl records here.
+
+hs.msg_body (SSL3 only) When an incoming handshake message spans more
+ than one ssl record, the first part(s) of it are accumulated
+ here until it all arrives.
+
+hs.msgState (SSL3 only) an alternative set of pointers/lengths for gs->buf.
+ Used only when a handleHandshake function returns SECWouldBlock.
+ ssl3_HandleHandshake remembers how far it previously got by
+ using these pointers instead of gs->buf when it is called
+ after a previous SECWouldBlock return.
+
+---------------------------------------------------------------------------
+outgoing:
+
+sec = ss->sec
+ci = ss->sec->ci /* connect info */
+
+ci->sendBuf Outgoing handshake messages are appended to this buffer.
+ This buffer will then be sent as a single SSL record.
+
+sec->writeBuf outgoing ssl records are constructed here and encrypted in
+ place before being written or copied to pendingBuf.
+
+ss->pendingBuf contains outgoing ciphertext that was saved after a write
+ attempt to the socket failed, e.g. EWouldBlock.
+ Generally empty with blocking sockets (should be no incomplete
+ writes).
+
+ss->saveBuf Used only by socks code. Intended to be used to buffer
+ outgoing data until a socks handshake completes. However,
+ this buffer is always empty. There is no code to put
+ anything into it.
+
+---------------------------------------------------------------------------
+
+SECWouldBlock means that the function cannot make progress because it is
+waiting for some event OTHER THAN socket I/O completion (e.g. waiting for
+user dialog to finish). It is not the same as EWOULDBLOCK.
+
+---------------------------------------------------------------------------
+
+Rank (order) of locks
+
+recvLock ->\ firstHandshake -> recvbuf -> ssl3Handshake -> xmitbuf -> "spec"
+sendLock ->/
+
+crypto and hash Data that must be protected while turning plaintext into
+ciphertext:
+
+SSL2: (in ssl2_Send*)
+ sec->hash*
+ sec->hashcx (ptr and data)
+ sec->enc
+ sec->writecx* (ptr and content)
+ sec->sendSecret*(ptr and content)
+ sec->sendSequence locked by xmitBufLock
+ sec->blockSize
+ sec->writeBuf* (ptr & content) locked by xmitBufLock
+ "in" locked by xmitBufLock
+
+SSl3: (in ssl3_SendPlainText)
+ ss->ssl3 (the pointer)
+ ss->ssl3->current_write* (the pointer and the data in the spec
+ and any data referenced by the spec.
+
+ ss->sec->isServer
+ ss->sec->writebuf* (ptr & content) locked by xmitBufLock
+ "buf" locked by xmitBufLock
+
+crypto and hash data that must be protected while turning ciphertext into
+plaintext:
+
+SSL2: (in ssl2_GatherData)
+ gs->* (locked by recvBufLock )
+ sec->dec
+ sec->readcx
+ sec->hash* (ptr and data)
+ sec->hashcx (ptr and data)
+
+SSL3: (in ssl3_HandleRecord )
+ ssl3->current_read* (the pointer and all data refernced)
+ ss->sec->isServer
+
+
+Data that must be protected while being used by a "writer":
+
+ss->pendingBuf.*
+ss->saveBuf.* (which is dead)
+
+in ssl3_sendPlainText
+
+ss->ssl3->current_write-> (spec)
+ss->sec->writeBuf.*
+ss->sec->isServer
+
+in SendBlock
+
+ss->sec->hash->length
+ss->sec->blockSize
+ss->sec->writeBuf.*
+ss->sec->sendSecret
+ss->sec->sendSequence
+ss->sec->writecx *
+ss->pendingBuf
+
+--------------------------------------------------------------------------
+
+Data variables (not const) protected by the "sslGlobalDataLock".
+Note, this really should be a reader/writer lock.
+
+allowedByPolicy sslcon.c
+maybeAllowedByPolicy sslcon.c
+chosenPreference sslcon.c
+policyWasSet sslcon.c
+
+cipherSuites[] ssl3con.c
diff --git a/chromium/net/third_party/nss/ssl/os2_err.c b/chromium/net/third_party/nss/ssl/os2_err.c
new file mode 100644
index 00000000000..a69ca91d9ce
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/os2_err.c
@@ -0,0 +1,280 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * this code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prerror.h"
+#include "prlog.h"
+#include <errno.h>
+
+
+/*
+ * Based on win32err.c
+ * OS2TODO Stub everything for now to build. HCT
+ */
+
+/* forward declaration. */
+void nss_MD_os2_map_default_error(PRInt32 err);
+
+void nss_MD_os2_map_opendir_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_closedir_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_readdir_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_delete_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+/* The error code for stat() is in errno. */
+void nss_MD_os2_map_stat_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_fstat_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_rename_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+/* The error code for access() is in errno. */
+void nss_MD_os2_map_access_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_mkdir_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_rmdir_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_read_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_transmitfile_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_write_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_lseek_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_fsync_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+/*
+ * For both CloseHandle() and closesocket().
+ */
+void nss_MD_os2_map_close_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_socket_error(PRInt32 err)
+{
+// PR_ASSERT(err != WSANOTINITIALISED);
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_recv_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_recvfrom_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_send_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEMSGSIZE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_sendto_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEMSGSIZE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_accept_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEOPNOTSUPP: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+// case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_acceptex_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_connect_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEWOULDBLOCK: prError = PR_IN_PROGRESS_ERROR; break;
+// case WSAEINVAL: prError = PR_ALREADY_INITIATED_ERROR; break;
+// case WSAETIMEDOUT: prError = PR_IO_TIMEOUT_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_bind_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEINVAL: prError = PR_SOCKET_ADDRESS_IS_BOUND_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_listen_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEOPNOTSUPP: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+// case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_shutdown_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_getsockname_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_getpeername_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_getsockopt_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_setsockopt_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_open_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+void nss_MD_os2_map_gethostname_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+/* Win32 select() only works on sockets. So in this
+** context, WSAENOTSOCK is equivalent to EBADF on Unix.
+*/
+void nss_MD_os2_map_select_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+// case WSAENOTSOCK: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ default: nss_MD_os2_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_os2_map_lockf_error(PRInt32 err)
+{
+ nss_MD_os2_map_default_error(err);
+}
+
+
+
+void nss_MD_os2_map_default_error(PRInt32 err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+// case ENOENT: prError = PR_FILE_NOT_FOUND_ERROR; break;
+// case ERROR_ACCESS_DENIED: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+// case ERROR_ALREADY_EXISTS: prError = PR_FILE_EXISTS_ERROR; break;
+// case ERROR_DISK_CORRUPT: prError = PR_IO_ERROR; break;
+// case ERROR_DISK_FULL: prError = PR_NO_DEVICE_SPACE_ERROR; break;
+// case ERROR_DISK_OPERATION_FAILED: prError = PR_IO_ERROR; break;
+// case ERROR_DRIVE_LOCKED: prError = PR_FILE_IS_LOCKED_ERROR; break;
+// case ERROR_FILENAME_EXCED_RANGE: prError = PR_NAME_TOO_LONG_ERROR; break;
+// case ERROR_FILE_CORRUPT: prError = PR_IO_ERROR; break;
+// case ERROR_FILE_EXISTS: prError = PR_FILE_EXISTS_ERROR; break;
+// case ERROR_FILE_INVALID: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+#if ERROR_FILE_NOT_FOUND != ENOENT
+// case ERROR_FILE_NOT_FOUND: prError = PR_FILE_NOT_FOUND_ERROR; break;
+#endif
+ default: prError = PR_UNKNOWN_ERROR; break;
+ }
+ PR_SetError(prError, err);
+}
+
diff --git a/chromium/net/third_party/nss/ssl/os2_err.h b/chromium/net/third_party/nss/ssl/os2_err.h
new file mode 100644
index 00000000000..3052d54a7af
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/os2_err.h
@@ -0,0 +1,53 @@
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * This code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* NSPR doesn't make these functions public, so we have to duplicate
+** them in NSS.
+*/
+
+//HCT Based on Win32err.h
+extern void nss_MD_os2_map_accept_error(PRInt32 err);
+extern void nss_MD_os2_map_acceptex_error(PRInt32 err);
+extern void nss_MD_os2_map_access_error(PRInt32 err);
+extern void nss_MD_os2_map_bind_error(PRInt32 err);
+extern void nss_MD_os2_map_close_error(PRInt32 err);
+extern void nss_MD_os2_map_closedir_error(PRInt32 err);
+extern void nss_MD_os2_map_connect_error(PRInt32 err);
+extern void nss_MD_os2_map_default_error(PRInt32 err);
+extern void nss_MD_os2_map_delete_error(PRInt32 err);
+extern void nss_MD_os2_map_fstat_error(PRInt32 err);
+extern void nss_MD_os2_map_fsync_error(PRInt32 err);
+extern void nss_MD_os2_map_gethostname_error(PRInt32 err);
+extern void nss_MD_os2_map_getpeername_error(PRInt32 err);
+extern void nss_MD_os2_map_getsockname_error(PRInt32 err);
+extern void nss_MD_os2_map_getsockopt_error(PRInt32 err);
+extern void nss_MD_os2_map_listen_error(PRInt32 err);
+extern void nss_MD_os2_map_lockf_error(PRInt32 err);
+extern void nss_MD_os2_map_lseek_error(PRInt32 err);
+extern void nss_MD_os2_map_mkdir_error(PRInt32 err);
+extern void nss_MD_os2_map_open_error(PRInt32 err);
+extern void nss_MD_os2_map_opendir_error(PRInt32 err);
+extern void nss_MD_os2_map_read_error(PRInt32 err);
+extern void nss_MD_os2_map_readdir_error(PRInt32 err);
+extern void nss_MD_os2_map_recv_error(PRInt32 err);
+extern void nss_MD_os2_map_recvfrom_error(PRInt32 err);
+extern void nss_MD_os2_map_rename_error(PRInt32 err);
+extern void nss_MD_os2_map_rmdir_error(PRInt32 err);
+extern void nss_MD_os2_map_select_error(PRInt32 err);
+extern void nss_MD_os2_map_send_error(PRInt32 err);
+extern void nss_MD_os2_map_sendto_error(PRInt32 err);
+extern void nss_MD_os2_map_setsockopt_error(PRInt32 err);
+extern void nss_MD_os2_map_shutdown_error(PRInt32 err);
+extern void nss_MD_os2_map_socket_error(PRInt32 err);
+extern void nss_MD_os2_map_stat_error(PRInt32 err);
+extern void nss_MD_os2_map_transmitfile_error(PRInt32 err);
+extern void nss_MD_os2_map_write_error(PRInt32 err);
diff --git a/chromium/net/third_party/nss/ssl/preenc.h b/chromium/net/third_party/nss/ssl/preenc.h
new file mode 100644
index 00000000000..af2475ddd2b
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/preenc.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */
+
+/*
+ * Fortezza support is removed.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Fortezza support is removed.
+ * This file remains so that old programs will continue to compile,
+ * But this functionality is no longer supported or implemented.
+ */
+
+#include "seccomon.h"
+#include "prio.h"
+
+typedef struct PEHeaderStr PEHeader;
+
+#define PE_MIME_TYPE "application/pre-encrypted"
+
+typedef struct PEFortezzaHeaderStr PEFortezzaHeader;
+typedef struct PEFortezzaGeneratedHeaderStr PEFortezzaGeneratedHeader;
+typedef struct PEFixedKeyHeaderStr PEFixedKeyHeader;
+typedef struct PERSAKeyHeaderStr PERSAKeyHeader;
+
+struct PEFortezzaHeaderStr {
+ unsigned char key[12];
+ unsigned char iv[24];
+ unsigned char hash[20];
+ unsigned char serial[8];
+};
+
+struct PEFortezzaGeneratedHeaderStr {
+ unsigned char key[12];
+ unsigned char iv[24];
+ unsigned char hash[20];
+ unsigned char Ra[128];
+ unsigned char Y[128];
+};
+
+struct PEFixedKeyHeaderStr {
+ unsigned char pkcs11Mech[4];
+ unsigned char labelLen[2];
+ unsigned char keyIDLen[2];
+ unsigned char ivLen[2];
+ unsigned char keyLen[2];
+ unsigned char data[1];
+};
+
+struct PERSAKeyHeaderStr {
+ unsigned char pkcs11Mech[4];
+ unsigned char issuerLen[2];
+ unsigned char serialLen[2];
+ unsigned char ivLen[2];
+ unsigned char keyLen[2];
+ unsigned char data[1];
+};
+
+#define PEFIXED_Label(header) (header->data)
+#define PEFIXED_KeyID(header) (&header->data[GetInt2(header->labelLen)])
+#define PEFIXED_IV(header) (&header->data[GetInt2(header->labelLen)\
+ +GetInt2(header->keyIDLen)])
+#define PEFIXED_Key(header) (&header->data[GetInt2(header->labelLen)\
+ +GetInt2(header->keyIDLen)+GetInt2(header->keyLen)])
+#define PERSA_Issuer(header) (header->data)
+#define PERSA_Serial(header) (&header->data[GetInt2(header->issuerLen)])
+#define PERSA_IV(header) (&header->data[GetInt2(header->issuerLen)\
+ +GetInt2(header->serialLen)])
+#define PERSA_Key(header) (&header->data[GetInt2(header->issuerLen)\
+ +GetInt2(header->serialLen)+GetInt2(header->keyLen)])
+struct PEHeaderStr {
+ unsigned char magic [2];
+ unsigned char len [2];
+ unsigned char type [2];
+ unsigned char version[2];
+ union {
+ PEFortezzaHeader fortezza;
+ PEFortezzaGeneratedHeader g_fortezza;
+ PEFixedKeyHeader fixed;
+ PERSAKeyHeader rsa;
+ } u;
+};
+
+#define PE_CRYPT_INTRO_LEN 8
+#define PE_INTRO_LEN 4
+#define PE_BASE_HEADER_LEN 8
+
+#define PRE_BLOCK_SIZE 8
+
+
+#define GetInt2(c) ((c[0] << 8) | c[1])
+#define GetInt4(c) (((unsigned long)c[0] << 24)|((unsigned long)c[1] << 16)\
+ |((unsigned long)c[2] << 8)| ((unsigned long)c[3]))
+#define PutInt2(c,i) ((c[1] = (i) & 0xff), (c[0] = ((i) >> 8) & 0xff))
+#define PutInt4(c,i) ((c[0]=((i) >> 24) & 0xff),(c[1]=((i) >> 16) & 0xff),\
+ (c[2] = ((i) >> 8) & 0xff), (c[3] = (i) & 0xff))
+
+#define PRE_MAGIC 0xc0de
+#define PRE_VERSION 0x1010
+#define PRE_FORTEZZA_FILE 0x00ff
+#define PRE_FORTEZZA_STREAM 0x00f5
+#define PRE_FORTEZZA_GEN_STREAM 0x00f6
+#define PRE_FIXED_FILE 0x000f
+#define PRE_RSA_FILE 0x001f
+#define PRE_FIXED_STREAM 0x0005
+
+PEHeader *SSL_PreencryptedStreamToFile(PRFileDesc *fd, PEHeader *,
+ int *headerSize);
+
+PEHeader *SSL_PreencryptedFileToStream(PRFileDesc *fd, PEHeader *,
+ int *headerSize);
+
diff --git a/chromium/net/third_party/nss/ssl/prelib.c b/chromium/net/third_party/nss/ssl/prelib.c
new file mode 100644
index 00000000000..a15174a31b1
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/prelib.c
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */
+
+/*
+ * Functions used by https servers to send (download) pre-encrypted files
+ * over SSL connections that use Fortezza ciphersuites.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cert.h"
+#include "ssl.h"
+#include "keyhi.h"
+#include "secitem.h"
+#include "sslimpl.h"
+#include "pkcs11t.h"
+#include "preenc.h"
+#include "pk11func.h"
+
+PEHeader *SSL_PreencryptedStreamToFile(PRFileDesc *fd, PEHeader *inHeader,
+ int *headerSize)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return NULL;
+}
+
+PEHeader *SSL_PreencryptedFileToStream(PRFileDesc *fd, PEHeader *header,
+ int *headerSize)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return NULL;
+}
+
+
diff --git a/chromium/net/third_party/nss/ssl/ssl.h b/chromium/net/third_party/nss/ssl/ssl.h
new file mode 100644
index 00000000000..c083a6b2a60
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl.h
@@ -0,0 +1,1110 @@
+/*
+ * This file contains prototypes for the public SSL functions.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ssl_h_
+#define __ssl_h_
+
+#include "prtypes.h"
+#include "prerror.h"
+#include "prio.h"
+#include "seccomon.h"
+#include "cert.h"
+#include "keyt.h"
+
+#include "sslt.h" /* public ssl data types */
+
+#if defined(_WIN32) && !defined(IN_LIBSSL) && !defined(NSS_USE_STATIC_LIBS)
+#define SSL_IMPORT extern __declspec(dllimport)
+#else
+#define SSL_IMPORT extern
+#endif
+
+SEC_BEGIN_PROTOS
+
+/* constant table enumerating all implemented SSL 2 and 3 cipher suites. */
+SSL_IMPORT const PRUint16 SSL_ImplementedCiphers[];
+
+/* the same as the above, but is a function */
+SSL_IMPORT const PRUint16 *SSL_GetImplementedCiphers(void);
+
+/* number of entries in the above table. */
+SSL_IMPORT const PRUint16 SSL_NumImplementedCiphers;
+
+/* the same as the above, but is a function */
+SSL_IMPORT PRUint16 SSL_GetNumImplementedCiphers(void);
+
+/* Macro to tell which ciphers in table are SSL2 vs SSL3/TLS. */
+#define SSL_IS_SSL2_CIPHER(which) (((which) & 0xfff0) == 0xff00)
+
+/*
+** Imports fd into SSL, returning a new socket. Copies SSL configuration
+** from model.
+*/
+SSL_IMPORT PRFileDesc *SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd);
+
+/*
+** Imports fd into DTLS, returning a new socket. Copies DTLS configuration
+** from model.
+*/
+SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd);
+
+/*
+** Enable/disable an ssl mode
+**
+** SSL_SECURITY:
+** enable/disable use of SSL security protocol before connect
+**
+** SSL_SOCKS:
+** enable/disable use of socks before connect
+** (No longer supported).
+**
+** SSL_REQUEST_CERTIFICATE:
+** require a certificate during secure connect
+*/
+/* options */
+#define SSL_SECURITY 1 /* (on by default) */
+#define SSL_SOCKS 2 /* (off by default) */
+#define SSL_REQUEST_CERTIFICATE 3 /* (off by default) */
+#define SSL_HANDSHAKE_AS_CLIENT 5 /* force accept to hs as client */
+ /* (off by default) */
+#define SSL_HANDSHAKE_AS_SERVER 6 /* force connect to hs as server */
+ /* (off by default) */
+
+/* OBSOLETE: SSL v2 is obsolete and may be removed soon. */
+#define SSL_ENABLE_SSL2 7 /* enable ssl v2 (off by default) */
+
+/* OBSOLETE: See "SSL Version Range API" below for the replacement and a
+** description of the non-obvious semantics of using SSL_ENABLE_SSL3.
+*/
+#define SSL_ENABLE_SSL3 8 /* enable ssl v3 (on by default) */
+
+#define SSL_NO_CACHE 9 /* don't use the session cache */
+ /* (off by default) */
+#define SSL_REQUIRE_CERTIFICATE 10 /* (SSL_REQUIRE_FIRST_HANDSHAKE */
+ /* by default) */
+#define SSL_ENABLE_FDX 11 /* permit simultaneous read/write */
+ /* (off by default) */
+
+/* OBSOLETE: SSL v2 compatible hellos are not accepted by some TLS servers
+** and cannot negotiate extensions. SSL v2 is obsolete. This option may be
+** removed soon.
+*/
+#define SSL_V2_COMPATIBLE_HELLO 12 /* send v3 client hello in v2 fmt */
+ /* (off by default) */
+
+/* OBSOLETE: See "SSL Version Range API" below for the replacement and a
+** description of the non-obvious semantics of using SSL_ENABLE_TLS.
+*/
+#define SSL_ENABLE_TLS 13 /* enable TLS (on by default) */
+
+#define SSL_ROLLBACK_DETECTION 14 /* for compatibility, default: on */
+#define SSL_NO_STEP_DOWN 15 /* Disable export cipher suites */
+ /* if step-down keys are needed. */
+ /* default: off, generate */
+ /* step-down keys if needed. */
+#define SSL_BYPASS_PKCS11 16 /* use PKCS#11 for pub key only */
+#define SSL_NO_LOCKS 17 /* Don't use locks for protection */
+#define SSL_ENABLE_SESSION_TICKETS 18 /* Enable TLS SessionTicket */
+ /* extension (off by default) */
+#define SSL_ENABLE_DEFLATE 19 /* Enable TLS compression with */
+ /* DEFLATE (off by default) */
+#define SSL_ENABLE_RENEGOTIATION 20 /* Values below (default: never) */
+#define SSL_REQUIRE_SAFE_NEGOTIATION 21 /* Peer must send Signaling */
+ /* Cipher Suite Value (SCSV) or */
+ /* Renegotiation Info (RI) */
+ /* extension in ALL handshakes. */
+ /* default: off */
+#define SSL_ENABLE_FALSE_START 22 /* Enable SSL false start (off by */
+ /* default, applies only to */
+ /* clients). False start is a */
+/* mode where an SSL client will start sending application data before */
+/* verifying the server's Finished message. This means that we could end up */
+/* sending data to an imposter. However, the data will be encrypted and */
+/* only the true server can derive the session key. Thus, so long as the */
+/* cipher isn't broken this is safe. Because of this, False Start will only */
+/* occur on RSA or DH ciphersuites where the cipher's key length is >= 80 */
+/* bits. The advantage of False Start is that it saves a round trip for */
+/* client-speaks-first protocols when performing a full handshake. */
+
+/* For SSL 3.0 and TLS 1.0, by default we prevent chosen plaintext attacks
+ * on SSL CBC mode cipher suites (see RFC 4346 Section F.3) by splitting
+ * non-empty application_data records into two records; the first record has
+ * only the first byte of plaintext, and the second has the rest.
+ *
+ * This only prevents the attack in the sending direction; the connection may
+ * still be vulnerable to such attacks if the peer does not implement a similar
+ * countermeasure.
+ *
+ * This protection mechanism is on by default; the default can be overridden by
+ * setting NSS_SSL_CBC_RANDOM_IV=0 in the environment prior to execution,
+ * and/or by the application setting the option SSL_CBC_RANDOM_IV to PR_FALSE.
+ *
+ * The per-record IV in TLS 1.1 and later adds one block of overhead per
+ * record, whereas this hack will add at least two blocks of overhead per
+ * record, so TLS 1.1+ will always be more efficient.
+ *
+ * Other implementations (e.g. some versions of OpenSSL, in some
+ * configurations) prevent the same attack by prepending an empty
+ * application_data record to every application_data record they send; we do
+ * not do that because some implementations cannot handle empty
+ * application_data records. Also, we only split application_data records and
+ * not other types of records, because some implementations will not accept
+ * fragmented records of some other types (e.g. some versions of NSS do not
+ * accept fragmented alerts).
+ */
+#define SSL_CBC_RANDOM_IV 23
+#define SSL_ENABLE_OCSP_STAPLING 24 /* Request OCSP stapling (client) */
+
+#ifdef SSL_DEPRECATED_FUNCTION
+/* Old deprecated function names */
+SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRBool on);
+SSL_IMPORT SECStatus SSL_EnableDefault(int option, PRBool on);
+#endif
+
+/* New function names */
+SSL_IMPORT SECStatus SSL_OptionSet(PRFileDesc *fd, PRInt32 option, PRBool on);
+SSL_IMPORT SECStatus SSL_OptionGet(PRFileDesc *fd, PRInt32 option, PRBool *on);
+SSL_IMPORT SECStatus SSL_OptionSetDefault(PRInt32 option, PRBool on);
+SSL_IMPORT SECStatus SSL_OptionGetDefault(PRInt32 option, PRBool *on);
+SSL_IMPORT SECStatus SSL_CertDBHandleSet(PRFileDesc *fd, CERTCertDBHandle *dbHandle);
+
+/* SSLNextProtoCallback is called during the handshake for the client, when a
+ * Next Protocol Negotiation (NPN) extension has been received from the server.
+ * |protos| and |protosLen| define a buffer which contains the server's
+ * advertisement. This data is guaranteed to be well formed per the NPN spec.
+ * |protoOut| is a buffer provided by the caller, of length 255 (the maximum
+ * allowed by the protocol). On successful return, the protocol to be announced
+ * to the server will be in |protoOut| and its length in |*protoOutLen|.
+ *
+ * The callback must return SECFailure or SECSuccess (not SECWouldBlock).
+ */
+typedef SECStatus (PR_CALLBACK *SSLNextProtoCallback)(
+ void *arg,
+ PRFileDesc *fd,
+ const unsigned char* protos,
+ unsigned int protosLen,
+ unsigned char* protoOut,
+ unsigned int* protoOutLen,
+ unsigned int protoMaxOut);
+
+/* SSL_SetNextProtoCallback sets a callback function to handle Next Protocol
+ * Negotiation. It causes a client to advertise NPN. */
+SSL_IMPORT SECStatus SSL_SetNextProtoCallback(PRFileDesc *fd,
+ SSLNextProtoCallback callback,
+ void *arg);
+
+/* SSL_SetNextProtoNego can be used as an alternative to
+ * SSL_SetNextProtoCallback. It also causes a client to advertise NPN and
+ * installs a default callback function which selects the first supported
+ * protocol in server-preference order. If no matching protocol is found it
+ * selects the first supported protocol.
+ *
+ * Using this function also allows the client to transparently support ALPN.
+ * The same set of protocols will be advertised via ALPN and, if the server
+ * uses ALPN to select a protocol, SSL_GetNextProto will return
+ * SSL_NEXT_PROTO_SELECTED as the state.
+ *
+ * Since NPN uses the first protocol as the fallback protocol, when sending an
+ * ALPN extension, the first protocol is moved to the end of the list. This
+ * indicates that the fallback protocol is the least preferred. The other
+ * protocols should be in preference order.
+ *
+ * The supported protocols are specified in |data| in wire-format (8-bit
+ * length-prefixed). For example: "\010http/1.1\006spdy/2". */
+SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd,
+ const unsigned char *data,
+ unsigned int length);
+
+typedef enum SSLNextProtoState {
+ SSL_NEXT_PROTO_NO_SUPPORT = 0, /* No peer support */
+ SSL_NEXT_PROTO_NEGOTIATED = 1, /* Mutual agreement */
+ SSL_NEXT_PROTO_NO_OVERLAP = 2, /* No protocol overlap found */
+ SSL_NEXT_PROTO_SELECTED = 3 /* Server selected proto (ALPN) */
+} SSLNextProtoState;
+
+/* SSL_GetNextProto can be used in the HandshakeCallback or any time after
+ * a handshake to retrieve the result of the Next Protocol negotiation.
+ *
+ * The length of the negotiated protocol, if any, is written into *bufLen.
+ * If the negotiated protocol is longer than bufLenMax, then SECFailure is
+ * returned. Otherwise, the negotiated protocol, if any, is written into buf,
+ * and SECSuccess is returned. */
+SSL_IMPORT SECStatus SSL_GetNextProto(PRFileDesc *fd,
+ SSLNextProtoState *state,
+ unsigned char *buf,
+ unsigned int *bufLen,
+ unsigned int bufLenMax);
+
+/*
+** Control ciphers that SSL uses. If on is non-zero then the named cipher
+** is enabled, otherwise it is disabled.
+** The "cipher" values are defined in sslproto.h (the SSL_EN_* values).
+** EnableCipher records user preferences.
+** SetPolicy sets the policy according to the policy module.
+*/
+#ifdef SSL_DEPRECATED_FUNCTION
+/* Old deprecated function names */
+SSL_IMPORT SECStatus SSL_EnableCipher(long which, PRBool enabled);
+SSL_IMPORT SECStatus SSL_SetPolicy(long which, int policy);
+#endif
+
+/* New function names */
+SSL_IMPORT SECStatus SSL_CipherPrefSet(PRFileDesc *fd, PRInt32 cipher, PRBool enabled);
+SSL_IMPORT SECStatus SSL_CipherPrefGet(PRFileDesc *fd, PRInt32 cipher, PRBool *enabled);
+SSL_IMPORT SECStatus SSL_CipherPrefSetDefault(PRInt32 cipher, PRBool enabled);
+SSL_IMPORT SECStatus SSL_CipherPrefGetDefault(PRInt32 cipher, PRBool *enabled);
+SSL_IMPORT SECStatus SSL_CipherPolicySet(PRInt32 cipher, PRInt32 policy);
+SSL_IMPORT SECStatus SSL_CipherPolicyGet(PRInt32 cipher, PRInt32 *policy);
+
+/* SSLChannelBindingType enumerates the types of supported channel binding
+ * values. See RFC 5929. */
+typedef enum SSLChannelBindingType {
+ SSL_CHANNEL_BINDING_TLS_UNIQUE = 1,
+} SSLChannelBindingType;
+
+/* SSL_GetChannelBinding copies the requested channel binding value, as defined
+ * in RFC 5929, into |out|. The full length of the binding value is written
+ * into |*outLen|.
+ *
+ * At most |outLenMax| bytes of data are copied. If |outLenMax| is
+ * insufficient then the function returns SECFailure and sets the error to
+ * SEC_ERROR_OUTPUT_LEN, but |*outLen| is still set.
+ *
+ * This call will fail if made during a renegotiation. */
+SSL_IMPORT SECStatus SSL_GetChannelBinding(PRFileDesc *fd,
+ SSLChannelBindingType binding_type,
+ unsigned char *out,
+ unsigned int *outLen,
+ unsigned int outLenMax);
+
+/* SSL Version Range API
+**
+** This API should be used to control SSL 3.0 & TLS support instead of the
+** older SSL_Option* API; however, the SSL_Option* API MUST still be used to
+** control SSL 2.0 support. In this version of libssl, SSL 3.0 and TLS 1.0 are
+** enabled by default. Future versions of libssl may change which versions of
+** the protocol are enabled by default.
+**
+** The SSLProtocolVariant enum indicates whether the protocol is of type
+** stream or datagram. This must be provided to the functions that do not
+** take an fd. Functions which take an fd will get the variant from the fd,
+** which is typed.
+**
+** Using the new version range API in conjunction with the older
+** SSL_OptionSet-based API for controlling the enabled protocol versions may
+** cause unexpected results. Going forward, we guarantee only the following:
+**
+** SSL_OptionGet(SSL_ENABLE_TLS) will return PR_TRUE if *ANY* versions of TLS
+** are enabled.
+**
+** SSL_OptionSet(SSL_ENABLE_TLS, PR_FALSE) will disable *ALL* versions of TLS,
+** including TLS 1.0 and later.
+**
+** The above two properties provide compatibility for applications that use
+** SSL_OptionSet to implement the insecure fallback from TLS 1.x to SSL 3.0.
+**
+** SSL_OptionSet(SSL_ENABLE_TLS, PR_TRUE) will enable TLS 1.0, and may also
+** enable some later versions of TLS, if it is necessary to do so in order to
+** keep the set of enabled versions contiguous. For example, if TLS 1.2 is
+** enabled, then after SSL_OptionSet(SSL_ENABLE_TLS, PR_TRUE), TLS 1.0,
+** TLS 1.1, and TLS 1.2 will be enabled, and the call will have no effect on
+** whether SSL 3.0 is enabled. If no later versions of TLS are enabled at the
+** time SSL_OptionSet(SSL_ENABLE_TLS, PR_TRUE) is called, then no later
+** versions of TLS will be enabled by the call.
+**
+** SSL_OptionSet(SSL_ENABLE_SSL3, PR_FALSE) will disable SSL 3.0, and will not
+** change the set of TLS versions that are enabled.
+**
+** SSL_OptionSet(SSL_ENABLE_SSL3, PR_TRUE) will enable SSL 3.0, and may also
+** enable some versions of TLS if TLS 1.1 or later is enabled at the time of
+** the call, the same way SSL_OptionSet(SSL_ENABLE_TLS, PR_TRUE) works, in
+** order to keep the set of enabled versions contiguous.
+*/
+
+/* Returns, in |*vrange|, the range of SSL3/TLS versions supported for the
+** given protocol variant by the version of libssl linked-to at runtime.
+*/
+SSL_IMPORT SECStatus SSL_VersionRangeGetSupported(
+ SSLProtocolVariant protocolVariant, SSLVersionRange *vrange);
+
+/* Returns, in |*vrange|, the range of SSL3/TLS versions enabled by default
+** for the given protocol variant.
+*/
+SSL_IMPORT SECStatus SSL_VersionRangeGetDefault(
+ SSLProtocolVariant protocolVariant, SSLVersionRange *vrange);
+
+/* Sets the range of enabled-by-default SSL3/TLS versions for the given
+** protocol variant to |*vrange|.
+*/
+SSL_IMPORT SECStatus SSL_VersionRangeSetDefault(
+ SSLProtocolVariant protocolVariant, const SSLVersionRange *vrange);
+
+/* Returns, in |*vrange|, the range of enabled SSL3/TLS versions for |fd|. */
+SSL_IMPORT SECStatus SSL_VersionRangeGet(PRFileDesc *fd,
+ SSLVersionRange *vrange);
+
+/* Sets the range of enabled SSL3/TLS versions for |fd| to |*vrange|. */
+SSL_IMPORT SECStatus SSL_VersionRangeSet(PRFileDesc *fd,
+ const SSLVersionRange *vrange);
+
+
+/* Values for "policy" argument to SSL_PolicySet */
+/* Values returned by SSL_CipherPolicyGet. */
+#define SSL_NOT_ALLOWED 0 /* or invalid or unimplemented */
+#define SSL_ALLOWED 1
+#define SSL_RESTRICTED 2 /* only with "Step-Up" certs. */
+
+/* Values for "on" with SSL_REQUIRE_CERTIFICATE. */
+#define SSL_REQUIRE_NEVER ((PRBool)0)
+#define SSL_REQUIRE_ALWAYS ((PRBool)1)
+#define SSL_REQUIRE_FIRST_HANDSHAKE ((PRBool)2)
+#define SSL_REQUIRE_NO_ERROR ((PRBool)3)
+
+/* Values for "on" with SSL_ENABLE_RENEGOTIATION */
+/* Never renegotiate at all. */
+#define SSL_RENEGOTIATE_NEVER ((PRBool)0)
+/* Renegotiate without restriction, whether or not the peer's client hello */
+/* bears the renegotiation info extension. Vulnerable, as in the past. */
+#define SSL_RENEGOTIATE_UNRESTRICTED ((PRBool)1)
+/* Only renegotiate if the peer's hello bears the TLS renegotiation_info */
+/* extension. This is safe renegotiation. */
+#define SSL_RENEGOTIATE_REQUIRES_XTN ((PRBool)2)
+/* Disallow unsafe renegotiation in server sockets only, but allow clients */
+/* to continue to renegotiate with vulnerable servers. */
+/* This value should only be used during the transition period when few */
+/* servers have been upgraded. */
+#define SSL_RENEGOTIATE_TRANSITIONAL ((PRBool)3)
+
+/*
+** Reset the handshake state for fd. This will make the complete SSL
+** handshake protocol execute from the ground up on the next i/o
+** operation.
+*/
+SSL_IMPORT SECStatus SSL_ResetHandshake(PRFileDesc *fd, PRBool asServer);
+
+/*
+** Force the handshake for fd to complete immediately. This blocks until
+** the complete SSL handshake protocol is finished.
+*/
+SSL_IMPORT SECStatus SSL_ForceHandshake(PRFileDesc *fd);
+
+/*
+** Same as above, but with an I/O timeout.
+ */
+SSL_IMPORT SECStatus SSL_ForceHandshakeWithTimeout(PRFileDesc *fd,
+ PRIntervalTime timeout);
+
+SSL_IMPORT SECStatus SSL_RestartHandshakeAfterCertReq(PRFileDesc *fd,
+ CERTCertificate *cert,
+ SECKEYPrivateKey *key,
+ CERTCertificateList *certChain);
+
+/*
+** Query security status of socket. *on is set to one if security is
+** enabled. *keySize will contain the stream key size used. *issuer will
+** contain the RFC1485 verison of the name of the issuer of the
+** certificate at the other end of the connection. For a client, this is
+** the issuer of the server's certificate; for a server, this is the
+** issuer of the client's certificate (if any). Subject is the subject of
+** the other end's certificate. The pointers can be zero if the desired
+** data is not needed. All strings returned by this function are owned
+** by the caller, and need to be freed with PORT_Free.
+*/
+SSL_IMPORT SECStatus SSL_SecurityStatus(PRFileDesc *fd, int *on, char **cipher,
+ int *keySize, int *secretKeySize,
+ char **issuer, char **subject);
+
+/* Values for "on" */
+#define SSL_SECURITY_STATUS_NOOPT -1
+#define SSL_SECURITY_STATUS_OFF 0
+#define SSL_SECURITY_STATUS_ON_HIGH 1
+#define SSL_SECURITY_STATUS_ON_LOW 2
+#define SSL_SECURITY_STATUS_FORTEZZA 3 /* NO LONGER SUPPORTED */
+
+/*
+** Return the certificate for our SSL peer. If the client calls this
+** it will always return the server's certificate. If the server calls
+** this, it may return NULL if client authentication is not enabled or
+** if the client had no certificate when asked.
+** "fd" the socket "file" descriptor
+*/
+SSL_IMPORT CERTCertificate *SSL_PeerCertificate(PRFileDesc *fd);
+
+/* SSL_PeerStapledOCSPResponses returns the OCSP responses that were provided
+ * by the TLS server. The return value is a pointer to an internal SECItemArray
+ * that contains the returned OCSP responses; it is only valid until the
+ * callback function that calls SSL_PeerStapledOCSPResponses returns.
+ *
+ * If no OCSP responses were given by the server then the result will be empty.
+ * If there was an error, then the result will be NULL.
+ *
+ * You must set the SSL_ENABLE_OCSP_STAPLING option to enable OCSP stapling.
+ * to be provided by a server.
+ *
+ * libssl does not do any validation of the OCSP response itself; the
+ * authenticate certificate hook is responsible for doing so. The default
+ * authenticate certificate hook, SSL_AuthCertificate, does not implement
+ * any OCSP stapling funtionality, but this may change in future versions.
+ */
+SSL_IMPORT const SECItemArray * SSL_PeerStapledOCSPResponses(PRFileDesc *fd);
+
+/* SSL_SetStapledOCSPResponses stores an array of one or multiple OCSP responses
+ * in the fd's data, which may be sent as part of a server side cert_status
+ * handshake message. Parameter |responses| is for the server certificate of
+ * the key exchange type |kea|.
+ * The function will duplicate the responses array.
+ */
+SSL_IMPORT SECStatus
+SSL_SetStapledOCSPResponses(PRFileDesc *fd, const SECItemArray *responses,
+ SSLKEAType kea);
+
+/*
+** Return references to the certificates presented by the SSL peer.
+** |maxNumCerts| must contain the size of the |certs| array. On successful
+** return, |*numCerts| contains the number of certificates available and
+** |certs| will contain references to as many certificates as would fit.
+** Therefore if |*numCerts| contains a value less than or equal to
+** |maxNumCerts|, then all certificates were returned.
+*/
+SSL_IMPORT SECStatus SSL_PeerCertificateChain(
+ PRFileDesc *fd, CERTCertificate **certs,
+ unsigned int *numCerts, unsigned int maxNumCerts);
+
+/*
+** Authenticate certificate hook. Called when a certificate comes in
+** (because of SSL_REQUIRE_CERTIFICATE in SSL_Enable) to authenticate the
+** certificate.
+**
+** The authenticate certificate hook must return SECSuccess to indicate the
+** certificate is valid, SECFailure to indicate the certificate is invalid,
+** or SECWouldBlock if the application will authenticate the certificate
+** asynchronously. SECWouldBlock is only supported for non-blocking sockets.
+**
+** If the authenticate certificate hook returns SECFailure, then the bad cert
+** hook will be called. The bad cert handler is NEVER called if the
+** authenticate certificate hook returns SECWouldBlock. If the application
+** needs to handle and/or override a bad cert, it should do so before it
+** calls SSL_AuthCertificateComplete (modifying the error it passes to
+** SSL_AuthCertificateComplete as needed).
+**
+** See the documentation for SSL_AuthCertificateComplete for more information
+** about the asynchronous behavior that occurs when the authenticate
+** certificate hook returns SECWouldBlock.
+**
+** RFC 6066 says that clients should send the bad_certificate_status_response
+** alert when they encounter an error processing the stapled OCSP response.
+** libssl does not provide a way for the authenticate certificate hook to
+** indicate that an OCSP error (SEC_ERROR_OCSP_*) that it returns is an error
+** in the stapled OCSP response or an error in some other OCSP response.
+** Further, NSS does not provide a convenient way to control or determine
+** which OCSP response(s) were used to validate a certificate chain.
+** Consequently, the current version of libssl does not ever send the
+** bad_certificate_status_response alert. This may change in future releases.
+*/
+typedef SECStatus (PR_CALLBACK *SSLAuthCertificate)(void *arg, PRFileDesc *fd,
+ PRBool checkSig,
+ PRBool isServer);
+
+SSL_IMPORT SECStatus SSL_AuthCertificateHook(PRFileDesc *fd,
+ SSLAuthCertificate f,
+ void *arg);
+
+/* An implementation of the certificate authentication hook */
+SSL_IMPORT SECStatus SSL_AuthCertificate(void *arg, PRFileDesc *fd,
+ PRBool checkSig, PRBool isServer);
+
+/*
+ * Prototype for SSL callback to get client auth data from the application.
+ * arg - application passed argument
+ * caNames - pointer to distinguished names of CAs that the server likes
+ * pRetCert - pointer to pointer to cert, for return of cert
+ * pRetKey - pointer to key pointer, for return of key
+ */
+typedef SECStatus (PR_CALLBACK *SSLGetClientAuthData)(void *arg,
+ PRFileDesc *fd,
+ CERTDistNames *caNames,
+ CERTCertificate **pRetCert,/*return */
+ SECKEYPrivateKey **pRetKey);/* return */
+
+/*
+ * Set the client side callback for SSL to retrieve user's private key
+ * and certificate.
+ * fd - the file descriptor for the connection in question
+ * f - the application's callback that delivers the key and cert
+ * a - application specific data
+ */
+SSL_IMPORT SECStatus SSL_GetClientAuthDataHook(PRFileDesc *fd,
+ SSLGetClientAuthData f, void *a);
+
+/*
+ * Prototype for SSL callback to get client auth data from the application,
+ * optionally using the underlying platform's cryptographic primitives.
+ * To use the platform cryptographic primitives, caNames and pRetCerts
+ * should be set. To use NSS, pRetNSSCert and pRetNSSKey should be set.
+ * Returning SECFailure will cause the socket to send no client certificate.
+ * arg - application passed argument
+ * caNames - pointer to distinguished names of CAs that the server likes
+ * pRetCerts - pointer to pointer to list of certs, with the first being
+ * the client cert, and any following being used for chain
+ * building
+ * pRetKey - pointer to native key pointer, for return of key
+ * - Windows: A pointer to a PCERT_KEY_CONTEXT that was allocated
+ * via PORT_Alloc(). Ownership of the PCERT_KEY_CONTEXT
+ * is transferred to NSS, which will free via
+ * PORT_Free().
+ * - Mac OS X: A pointer to a SecKeyRef. Ownership is
+ * transferred to NSS, which will free via CFRelease().
+ * pRetNSSCert - pointer to pointer to NSS cert, for return of cert.
+ * pRetNSSKey - pointer to NSS key pointer, for return of key.
+ */
+typedef SECStatus (PR_CALLBACK *SSLGetPlatformClientAuthData)(void *arg,
+ PRFileDesc *fd,
+ CERTDistNames *caNames,
+ CERTCertList **pRetCerts,/*return */
+ void **pRetKey,/* return */
+ CERTCertificate **pRetNSSCert,/*return */
+ SECKEYPrivateKey **pRetNSSKey);/* return */
+
+/*
+ * Set the client side callback for SSL to retrieve user's private key
+ * and certificate.
+ * Note: If a platform client auth callback is set, the callback configured by
+ * SSL_GetClientAuthDataHook, if any, will not be called.
+ *
+ * fd - the file descriptor for the connection in question
+ * f - the application's callback that delivers the key and cert
+ * a - application specific data
+ */
+SSL_IMPORT SECStatus
+SSL_GetPlatformClientAuthDataHook(PRFileDesc *fd,
+ SSLGetPlatformClientAuthData f, void *a);
+
+/*
+** SNI extension processing callback function.
+** It is called when SSL socket receives SNI extension in ClientHello message.
+** Upon this callback invocation, application is responsible to reconfigure the
+** socket with the data for a particular server name.
+** There are three potential outcomes of this function invocation:
+** * application does not recognize the name or the type and wants the
+** "unrecognized_name" alert be sent to the client. In this case the callback
+** function must return SSL_SNI_SEND_ALERT status.
+** * application does not recognize the name, but wants to continue with
+** the handshake using the current socket configuration. In this case,
+** no socket reconfiguration is needed and the function should return
+** SSL_SNI_CURRENT_CONFIG_IS_USED.
+** * application recognizes the name and reconfigures the socket with
+** appropriate certs, key, etc. There are many ways to reconfigure. NSS
+** provides SSL_ReconfigFD function that can be used to update the socket
+** data from model socket. To continue with the rest of the handshake, the
+** implementation function should return an index of a name it has chosen.
+** LibSSL will ignore any SNI extension received in a ClientHello message
+** if application does not register a SSLSNISocketConfig callback.
+** Each type field of SECItem indicates the name type.
+** NOTE: currently RFC3546 defines only one name type: sni_host_name.
+** Client is allowed to send only one name per known type. LibSSL will
+** send an "unrecognized_name" alert if SNI extension name list contains more
+** then one name of a type.
+*/
+typedef PRInt32 (PR_CALLBACK *SSLSNISocketConfig)(PRFileDesc *fd,
+ const SECItem *srvNameArr,
+ PRUint32 srvNameArrSize,
+ void *arg);
+
+/*
+** SSLSNISocketConfig should return an index within 0 and srvNameArrSize-1
+** when it has reconfigured the socket fd to use certs and keys, etc
+** for a specific name. There are two other allowed return values. One
+** tells libSSL to use the default cert and key. The other tells libSSL
+** to send the "unrecognized_name" alert. These values are:
+**/
+#define SSL_SNI_CURRENT_CONFIG_IS_USED -1
+#define SSL_SNI_SEND_ALERT -2
+
+/*
+** Set application implemented SNISocketConfig callback.
+*/
+SSL_IMPORT SECStatus SSL_SNISocketConfigHook(PRFileDesc *fd,
+ SSLSNISocketConfig f,
+ void *arg);
+
+/*
+** Reconfigure fd SSL socket with model socket parameters. Sets
+** server certs and keys, list of trust anchor, socket options
+** and all SSL socket call backs and parameters.
+*/
+SSL_IMPORT PRFileDesc *SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd);
+
+/*
+ * Set the client side argument for SSL to retrieve PKCS #11 pin.
+ * fd - the file descriptor for the connection in question
+ * a - pkcs11 application specific data
+ */
+SSL_IMPORT SECStatus SSL_SetPKCS11PinArg(PRFileDesc *fd, void *a);
+
+/*
+** This is a callback for dealing with server certs that are not authenticated
+** by the client. The client app can decide that it actually likes the
+** cert by some external means and restart the connection.
+**
+** The bad cert hook must return SECSuccess to override the result of the
+** authenticate certificate hook, SECFailure if the certificate should still be
+** considered invalid, or SECWouldBlock if the application will authenticate
+** the certificate asynchronously. SECWouldBlock is only supported for
+** non-blocking sockets.
+**
+** See the documentation for SSL_AuthCertificateComplete for more information
+** about the asynchronous behavior that occurs when the bad cert hook returns
+** SECWouldBlock.
+*/
+typedef SECStatus (PR_CALLBACK *SSLBadCertHandler)(void *arg, PRFileDesc *fd);
+SSL_IMPORT SECStatus SSL_BadCertHook(PRFileDesc *fd, SSLBadCertHandler f,
+ void *arg);
+
+/*
+** Configure SSL socket for running a secure server. Needs the
+** certificate for the server and the servers private key. The arguments
+** are copied.
+*/
+SSL_IMPORT SECStatus SSL_ConfigSecureServer(
+ PRFileDesc *fd, CERTCertificate *cert,
+ SECKEYPrivateKey *key, SSLKEAType kea);
+
+/*
+** Allows SSL socket configuration with caller-supplied certificate chain.
+** If certChainOpt is NULL, tries to find one.
+*/
+SSL_IMPORT SECStatus
+SSL_ConfigSecureServerWithCertChain(PRFileDesc *fd, CERTCertificate *cert,
+ const CERTCertificateList *certChainOpt,
+ SECKEYPrivateKey *key, SSLKEAType kea);
+
+/*
+** Configure a secure server's session-id cache. Define the maximum number
+** of entries in the cache, the longevity of the entires, and the directory
+** where the cache files will be placed. These values can be zero, and
+** if so, the implementation will choose defaults.
+** This version of the function is for use in applications that have only one
+** process that uses the cache (even if that process has multiple threads).
+*/
+SSL_IMPORT SECStatus SSL_ConfigServerSessionIDCache(int maxCacheEntries,
+ PRUint32 timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory);
+
+/* Configure a secure server's session-id cache. Depends on value of
+ * enableMPCache, configures malti-proc or single proc cache. */
+SSL_IMPORT SECStatus SSL_ConfigServerSessionIDCacheWithOpt(
+ PRUint32 timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries,
+ PRBool enableMPCache);
+
+/*
+** Like SSL_ConfigServerSessionIDCache, with one important difference.
+** If the application will run multiple processes (as opposed to, or in
+** addition to multiple threads), then it must call this function, instead
+** of calling SSL_ConfigServerSessionIDCache().
+** This has nothing to do with the number of processORs, only processEs.
+** This function sets up a Server Session ID (SID) cache that is safe for
+** access by multiple processes on the same system.
+*/
+SSL_IMPORT SECStatus SSL_ConfigMPServerSIDCache(int maxCacheEntries,
+ PRUint32 timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory);
+
+/* Get and set the configured maximum number of mutexes used for the
+** server's store of SSL sessions. This value is used by the server
+** session ID cache initialization functions shown above. Note that on
+** some platforms, these mutexes are actually implemented with POSIX
+** semaphores, or with unnamed pipes. The default value varies by platform.
+** An attempt to set a too-low maximum will return an error and the
+** configured value will not be changed.
+*/
+SSL_IMPORT PRUint32 SSL_GetMaxServerCacheLocks(void);
+SSL_IMPORT SECStatus SSL_SetMaxServerCacheLocks(PRUint32 maxLocks);
+
+/* environment variable set by SSL_ConfigMPServerSIDCache, and queried by
+ * SSL_InheritMPServerSIDCache when envString is NULL.
+ */
+#define SSL_ENV_VAR_NAME "SSL_INHERITANCE"
+
+/* called in child to inherit SID Cache variables.
+ * If envString is NULL, this function will use the value of the environment
+ * variable "SSL_INHERITANCE", otherwise the string value passed in will be
+ * used.
+ */
+SSL_IMPORT SECStatus SSL_InheritMPServerSIDCache(const char * envString);
+
+/*
+** Set the callback on a particular socket that gets called when we finish
+** performing a handshake.
+*/
+typedef void (PR_CALLBACK *SSLHandshakeCallback)(PRFileDesc *fd,
+ void *client_data);
+SSL_IMPORT SECStatus SSL_HandshakeCallback(PRFileDesc *fd,
+ SSLHandshakeCallback cb, void *client_data);
+
+/*
+** For the server, request a new handshake. For the client, begin a new
+** handshake. If flushCache is non-zero, the SSL3 cache entry will be
+** flushed first, ensuring that a full SSL handshake will be done.
+** If flushCache is zero, and an SSL connection is established, it will
+** do the much faster session restart handshake. This will change the
+** session keys without doing another private key operation.
+*/
+SSL_IMPORT SECStatus SSL_ReHandshake(PRFileDesc *fd, PRBool flushCache);
+
+/*
+** Same as above, but with an I/O timeout.
+ */
+SSL_IMPORT SECStatus SSL_ReHandshakeWithTimeout(PRFileDesc *fd,
+ PRBool flushCache,
+ PRIntervalTime timeout);
+
+/* Returns a SECItem containing the certificate_types field of the
+** CertificateRequest message. Each byte of the data is a TLS
+** ClientCertificateType value, and they are ordered from most preferred to
+** least. This function should only be called from the
+** SSL_GetClientAuthDataHook callback, and will return NULL if called at any
+** other time. The returned value is valid only until the callback returns, and
+** should not be freed.
+*/
+SSL_IMPORT const SECItem *
+SSL_GetRequestedClientCertificateTypes(PRFileDesc *fd);
+
+#ifdef SSL_DEPRECATED_FUNCTION
+/* deprecated!
+** For the server, request a new handshake. For the client, begin a new
+** handshake. Flushes SSL3 session cache entry first, ensuring that a
+** full handshake will be done.
+** This call is equivalent to SSL_ReHandshake(fd, PR_TRUE)
+*/
+SSL_IMPORT SECStatus SSL_RedoHandshake(PRFileDesc *fd);
+#endif
+
+/*
+ * Allow the application to pass a URL or hostname into the SSL library.
+ */
+SSL_IMPORT SECStatus SSL_SetURL(PRFileDesc *fd, const char *url);
+
+/*
+ * Allow an application to define a set of trust anchors for peer
+ * cert validation.
+ */
+SSL_IMPORT SECStatus SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *list);
+
+/*
+** Return the number of bytes that SSL has waiting in internal buffers.
+** Return 0 if security is not enabled.
+*/
+SSL_IMPORT int SSL_DataPending(PRFileDesc *fd);
+
+/*
+** Invalidate the SSL session associated with fd.
+*/
+SSL_IMPORT SECStatus SSL_InvalidateSession(PRFileDesc *fd);
+
+/*
+** Return a SECItem containing the SSL session ID associated with the fd.
+*/
+SSL_IMPORT SECItem *SSL_GetSessionID(PRFileDesc *fd);
+
+/*
+** Clear out the client's SSL session cache, not the server's session cache.
+*/
+SSL_IMPORT void SSL_ClearSessionCache(void);
+
+/*
+** Close the server's SSL session cache.
+*/
+SSL_IMPORT SECStatus SSL_ShutdownServerSessionIDCache(void);
+
+/*
+** Set peer information so we can correctly look up SSL session later.
+** You only have to do this if you're tunneling through a proxy.
+*/
+SSL_IMPORT SECStatus SSL_SetSockPeerID(PRFileDesc *fd, const char *peerID);
+
+/*
+** Reveal the security information for the peer.
+*/
+SSL_IMPORT CERTCertificate * SSL_RevealCert(PRFileDesc * socket);
+SSL_IMPORT void * SSL_RevealPinArg(PRFileDesc * socket);
+SSL_IMPORT char * SSL_RevealURL(PRFileDesc * socket);
+
+/* This callback may be passed to the SSL library via a call to
+ * SSL_GetClientAuthDataHook() for each SSL client socket.
+ * It will be invoked when SSL needs to know what certificate and private key
+ * (if any) to use to respond to a request for client authentication.
+ * If arg is non-NULL, it is a pointer to a NULL-terminated string containing
+ * the nickname of the cert/key pair to use.
+ * If arg is NULL, this function will search the cert and key databases for
+ * a suitable match and send it if one is found.
+ */
+SSL_IMPORT SECStatus
+NSS_GetClientAuthData(void * arg,
+ PRFileDesc * socket,
+ struct CERTDistNamesStr * caNames,
+ struct CERTCertificateStr ** pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey);
+
+/*
+** Configure DTLS-SRTP (RFC 5764) cipher suite preferences.
+** Input is a list of ciphers in descending preference order and a length
+** of the list. As a side effect, this causes the use_srtp extension to be
+** negotiated.
+**
+** Invalid or unimplemented cipher suites in |ciphers| are ignored. If at
+** least one cipher suite in |ciphers| is implemented, returns SECSuccess.
+** Otherwise returns SECFailure.
+*/
+SSL_IMPORT SECStatus SSL_SetSRTPCiphers(PRFileDesc *fd,
+ const PRUint16 *ciphers,
+ unsigned int numCiphers);
+
+/*
+** Get the selected DTLS-SRTP cipher suite (if any).
+** To be called after the handshake completes.
+** Returns SECFailure if not negotiated.
+*/
+SSL_IMPORT SECStatus SSL_GetSRTPCipher(PRFileDesc *fd,
+ PRUint16 *cipher);
+
+/*
+ * Look to see if any of the signers in the cert chain for "cert" are found
+ * in the list of caNames.
+ * Returns SECSuccess if so, SECFailure if not.
+ * Used by NSS_GetClientAuthData. May be used by other callback functions.
+ */
+SSL_IMPORT SECStatus NSS_CmpCertChainWCANames(CERTCertificate *cert,
+ CERTDistNames *caNames);
+
+/*
+ * Returns key exchange type of the keys in an SSL server certificate.
+ */
+SSL_IMPORT SSLKEAType NSS_FindCertKEAType(CERTCertificate * cert);
+
+/* Set cipher policies to a predefined Domestic (U.S.A.) policy.
+ * This essentially enables all supported ciphers.
+ */
+SSL_IMPORT SECStatus NSS_SetDomesticPolicy(void);
+
+/* Set cipher policies to a predefined Policy that is exportable from the USA
+ * according to present U.S. policies as we understand them.
+ * See documentation for the list.
+ * Note that your particular application program may be able to obtain
+ * an export license with more or fewer capabilities than those allowed
+ * by this function. In that case, you should use SSL_SetPolicy()
+ * to explicitly allow those ciphers you may legally export.
+ */
+SSL_IMPORT SECStatus NSS_SetExportPolicy(void);
+
+/* Set cipher policies to a predefined Policy that is exportable from the USA
+ * according to present U.S. policies as we understand them, and that the
+ * nation of France will permit to be imported into their country.
+ * See documentation for the list.
+ */
+SSL_IMPORT SECStatus NSS_SetFrancePolicy(void);
+
+SSL_IMPORT SSL3Statistics * SSL_GetStatistics(void);
+
+/* Report more information than SSL_SecurityStatus.
+** Caller supplies the info struct. Function fills it in.
+*/
+SSL_IMPORT SECStatus SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info,
+ PRUintn len);
+SSL_IMPORT SECStatus SSL_GetCipherSuiteInfo(PRUint16 cipherSuite,
+ SSLCipherSuiteInfo *info, PRUintn len);
+
+/* Returnes negotiated through SNI host info. */
+SSL_IMPORT SECItem *SSL_GetNegotiatedHostInfo(PRFileDesc *fd);
+
+/* Export keying material according to RFC 5705.
+** fd must correspond to a TLS 1.0 or higher socket and out must
+** already be allocated. If hasContext is false, it uses the no-context
+** construction from the RFC and ignores the context and contextLen
+** arguments.
+*/
+SSL_IMPORT SECStatus SSL_ExportKeyingMaterial(PRFileDesc *fd,
+ const char *label,
+ unsigned int labelLen,
+ PRBool hasContext,
+ const unsigned char *context,
+ unsigned int contextLen,
+ unsigned char *out,
+ unsigned int outLen);
+
+/*
+** Return a new reference to the certificate that was most recently sent
+** to the peer on this SSL/TLS connection, or NULL if none has been sent.
+*/
+SSL_IMPORT CERTCertificate * SSL_LocalCertificate(PRFileDesc *fd);
+
+/* Test an SSL configuration to see if SSL_BYPASS_PKCS11 can be turned on.
+** Check the key exchange algorithm for each cipher in the list to see if
+** a master secret key can be extracted after being derived with the mechanism
+** required by the protocolmask argument. If the KEA will use keys from the
+** specified cert make sure the extract operation is attempted from the slot
+** where the private key resides.
+** If MS can be extracted for all ciphers, (*pcanbypass) is set to TRUE and
+** SECSuccess is returned. In all other cases but one (*pcanbypass) is
+** set to FALSE and SECFailure is returned.
+** In that last case Derive() has been called successfully but the MS is null,
+** CanBypass sets (*pcanbypass) to FALSE and returns SECSuccess indicating the
+** arguments were all valid but the slot cannot be bypassed.
+**
+** Note: A TRUE return code from CanBypass means "Your configuration will perform
+** NO WORSE with the bypass enabled than without"; it does NOT mean that every
+** cipher suite listed will work properly with the selected protocols.
+**
+** Caveat: If export cipher suites are included in the argument list Canbypass
+** will return FALSE.
+**/
+
+/* protocol mask bits */
+#define SSL_CBP_SSL3 0x0001 /* test SSL v3 mechanisms */
+#define SSL_CBP_TLS1_0 0x0002 /* test TLS v1.0 mechanisms */
+
+SSL_IMPORT SECStatus SSL_CanBypass(CERTCertificate *cert,
+ SECKEYPrivateKey *privKey,
+ PRUint32 protocolmask,
+ PRUint16 *ciphers, int nciphers,
+ PRBool *pcanbypass, void *pwArg);
+
+/*
+** Did the handshake with the peer negotiate the given extension?
+** Output parameter valid only if function returns SECSuccess
+*/
+SSL_IMPORT SECStatus SSL_HandshakeNegotiatedExtension(PRFileDesc * socket,
+ SSLExtensionType extId,
+ PRBool *yes);
+
+SSL_IMPORT SECStatus SSL_HandshakeResumedSession(PRFileDesc *fd,
+ PRBool *last_handshake_resumed);
+
+/* See SSL_SetClientChannelIDCallback for usage. If the callback returns
+ * SECWouldBlock then SSL_RestartHandshakeAfterChannelIDReq should be called in
+ * the future to restart the handshake. On SECSuccess, the callback must have
+ * written a P-256, EC key pair to |*out_public_key| and |*out_private_key|. */
+typedef SECStatus (PR_CALLBACK *SSLClientChannelIDCallback)(
+ void *arg,
+ PRFileDesc *fd,
+ SECKEYPublicKey **out_public_key,
+ SECKEYPrivateKey **out_private_key);
+
+/* SSL_RestartHandshakeAfterChannelIDReq attempts to restart the handshake
+ * after a ChannelID callback returned SECWouldBlock.
+ *
+ * This function takes ownership of |channelIDPub| and |channelID|. */
+SSL_IMPORT SECStatus SSL_RestartHandshakeAfterChannelIDReq(
+ PRFileDesc *fd,
+ SECKEYPublicKey *channelIDPub,
+ SECKEYPrivateKey *channelID);
+
+/* SSL_SetClientChannelIDCallback sets a callback function that will be called
+ * once the server's ServerHello has been processed. This is only applicable to
+ * a client socket and setting this callback causes the TLS Channel ID
+ * extension to be advertised. */
+SSL_IMPORT SECStatus SSL_SetClientChannelIDCallback(
+ PRFileDesc *fd,
+ SSLClientChannelIDCallback callback,
+ void *arg);
+
+/*
+** How long should we wait before retransmitting the next flight of
+** the DTLS handshake? Returns SECFailure if not DTLS or not in a
+** handshake.
+*/
+SSL_IMPORT SECStatus DTLS_GetHandshakeTimeout(PRFileDesc *socket,
+ PRIntervalTime *timeout);
+
+/*
+ * Return a boolean that indicates whether the underlying library
+ * will perform as the caller expects.
+ *
+ * The only argument is a string, which should be the version
+ * identifier of the NSS library. That string will be compared
+ * against a string that represents the actual build version of
+ * the SSL library.
+ */
+extern PRBool NSSSSL_VersionCheck(const char *importedVersion);
+
+/*
+ * Returns a const string of the SSL library version.
+ */
+extern const char *NSSSSL_GetVersion(void);
+
+/* Restart an SSL connection that was paused to do asynchronous certificate
+ * chain validation (when the auth certificate hook or bad cert handler
+ * returned SECWouldBlock).
+ *
+ * This function only works for non-blocking sockets; Do not use it for
+ * blocking sockets. Currently, this function works only for the client role of
+ * a connection; it does not work for the server role.
+ *
+ * The application must call SSL_AuthCertificateComplete with 0 as the value of
+ * the error parameter after it has successfully validated the peer's
+ * certificate, in order to continue the SSL handshake.
+ *
+ * The application may call SSL_AuthCertificateComplete with a non-zero value
+ * for error (e.g. SEC_ERROR_REVOKED_CERTIFICATE) when certificate validation
+ * fails, before it closes the connection. If the application does so, an
+ * alert corresponding to the error (e.g. certificate_revoked) will be sent to
+ * the peer. See the source code of the internal function
+ * ssl3_SendAlertForCertError for the current mapping of error to alert. This
+ * mapping may change in future versions of libssl.
+ *
+ * This function will not complete the entire handshake. The application must
+ * call SSL_ForceHandshake, PR_Recv, PR_Send, etc. after calling this function
+ * to force the handshake to complete.
+ *
+ * On the first handshake of a connection, libssl will wait for the peer's
+ * certificate to be authenticated before calling the handshake callback,
+ * sending a client certificate, sending any application data, or returning
+ * any application data to the application. On subsequent (renegotiation)
+ * handshakes, libssl will block the handshake unconditionally while the
+ * certificate is being validated.
+ *
+ * libssl may send and receive handshake messages while waiting for the
+ * application to call SSL_AuthCertificateComplete, and it may call other
+ * callbacks (e.g, the client auth data hook) before
+ * SSL_AuthCertificateComplete has been called.
+ *
+ * An application that uses this asynchronous mechanism will usually have lower
+ * handshake latency if it has to do public key operations on the certificate
+ * chain and/or CRL/OCSP/cert fetching during the authentication, especially if
+ * it does so in parallel on another thread. However, if the application can
+ * authenticate the peer's certificate quickly then it may be more efficient
+ * to use the synchronous mechanism (i.e. returning SECFailure/SECSuccess
+ * instead of SECWouldBlock from the authenticate certificate hook).
+ *
+ * Be careful about converting an application from synchronous cert validation
+ * to asynchronous certificate validation. A naive conversion is likely to
+ * result in deadlocks; e.g. the application will wait in PR_Poll for network
+ * I/O on the connection while all network I/O on the connection is blocked
+ * waiting for this function to be called.
+ *
+ * Returns SECFailure on failure, SECSuccess on success. Never returns
+ * SECWouldBlock. Note that SSL_AuthCertificateComplete will (usually) return
+ * SECSuccess; do not interpret the return value of SSL_AuthCertificateComplete
+ * as an indicator of whether it is OK to continue using the connection. For
+ * example, SSL_AuthCertificateComplete(fd, SEC_ERROR_REVOKED_CERTIFICATE) will
+ * return SECSuccess (normally), but that does not mean that the application
+ * should continue using the connection. If the application passes a non-zero
+ * value for second argument (error), or if SSL_AuthCertificateComplete returns
+ * anything other than SECSuccess, then the application should close the
+ * connection.
+ */
+SSL_IMPORT SECStatus SSL_AuthCertificateComplete(PRFileDesc *fd,
+ PRErrorCode error);
+SEC_END_PROTOS
+
+#endif /* __ssl_h_ */
diff --git a/chromium/net/third_party/nss/ssl/ssl.rc b/chromium/net/third_party/nss/ssl/ssl.rc
new file mode 100644
index 00000000000..809a07e5ffd
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl.rc
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nss.h"
+#include <winver.h>
+
+#define MY_LIBNAME "ssl"
+#define MY_FILEDESCRIPTION "NSS SSL Library"
+
+#define STRINGIZE(x) #x
+#define STRINGIZE2(x) STRINGIZE(x)
+#define NSS_VMAJOR_STR STRINGIZE2(NSS_VMAJOR)
+
+#ifdef _DEBUG
+#define MY_DEBUG_STR " (debug)"
+#define MY_FILEFLAGS_1 VS_FF_DEBUG
+#else
+#define MY_DEBUG_STR ""
+#define MY_FILEFLAGS_1 0x0L
+#endif
+#if NSS_BETA
+#define MY_FILEFLAGS_2 MY_FILEFLAGS_1|VS_FF_PRERELEASE
+#else
+#define MY_FILEFLAGS_2 MY_FILEFLAGS_1
+#endif
+
+#ifdef WINNT
+#define MY_FILEOS VOS_NT_WINDOWS32
+#else
+#define MY_FILEOS VOS__WINDOWS32
+#endif
+
+#define MY_INTERNAL_NAME MY_LIBNAME NSS_VMAJOR_STR
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version-information resource
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION NSS_VMAJOR,NSS_VMINOR,NSS_VPATCH,NSS_VBUILD
+ PRODUCTVERSION NSS_VMAJOR,NSS_VMINOR,NSS_VPATCH,NSS_VBUILD
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS MY_FILEFLAGS_2
+ FILEOS MY_FILEOS
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L // not used
+
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904B0" // Lang=US English, CharSet=Unicode
+ BEGIN
+ VALUE "CompanyName", "Mozilla Foundation\0"
+ VALUE "FileDescription", MY_FILEDESCRIPTION MY_DEBUG_STR "\0"
+ VALUE "FileVersion", NSS_VERSION "\0"
+ VALUE "InternalName", MY_INTERNAL_NAME "\0"
+ VALUE "OriginalFilename", MY_INTERNAL_NAME ".dll\0"
+ VALUE "ProductName", "Network Security Services\0"
+ VALUE "ProductVersion", NSS_VERSION "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/chromium/net/third_party/nss/ssl/ssl3con.c b/chromium/net/third_party/nss/ssl/ssl3con.c
new file mode 100644
index 00000000000..e614eab2bde
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl3con.c
@@ -0,0 +1,12310 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * SSL3 Protocol
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* TODO(ekr): Implement HelloVerifyRequest on server side. OK for now. */
+
+#include "cert.h"
+#include "ssl.h"
+#include "cryptohi.h" /* for DSAU_ stuff */
+#include "keyhi.h"
+#include "secder.h"
+#include "secitem.h"
+#include "sechash.h"
+
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "sslerr.h"
+#include "prtime.h"
+#include "prinrval.h"
+#include "prerror.h"
+#include "pratom.h"
+#include "prthread.h"
+
+#include "pk11func.h"
+#include "secmod.h"
+#ifndef NO_PKCS11_BYPASS
+#include "blapi.h"
+#endif
+
+/* This is a bodge to allow this code to be compiled against older NSS headers
+ * that don't contain the TLS 1.2 changes. */
+#ifndef CKM_NSS_TLS_PRF_GENERAL_SHA256
+#define CKM_NSS_TLS_PRF_GENERAL_SHA256 (CKM_NSS + 21)
+#define CKM_NSS_TLS_MASTER_KEY_DERIVE_SHA256 (CKM_NSS + 22)
+#define CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256 (CKM_NSS + 23)
+#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
+#endif
+
+#include <stdio.h>
+#ifdef NSS_ENABLE_ZLIB
+#include "zlib.h"
+#endif
+#ifdef LINUX
+#include <dlfcn.h>
+#endif
+
+#ifndef PK11_SETATTRS
+#define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \
+ (x)->pValue=(v); (x)->ulValueLen = (l);
+#endif
+
+static SECStatus ssl3_AuthCertificate(sslSocket *ss);
+static void ssl3_CleanupPeerCerts(sslSocket *ss);
+static void ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid);
+static PK11SymKey *ssl3_GenerateRSAPMS(sslSocket *ss, ssl3CipherSpec *spec,
+ PK11SlotInfo * serverKeySlot);
+static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms);
+static SECStatus ssl3_DeriveConnectionKeysPKCS11(sslSocket *ss);
+static SECStatus ssl3_HandshakeFailure( sslSocket *ss);
+static SECStatus ssl3_InitState( sslSocket *ss);
+static SECStatus ssl3_SendCertificate( sslSocket *ss);
+static SECStatus ssl3_SendCertificateStatus( sslSocket *ss);
+static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss);
+static SECStatus ssl3_SendCertificateRequest(sslSocket *ss);
+static SECStatus ssl3_SendNextProto( sslSocket *ss);
+static SECStatus ssl3_SendEncryptedExtensions(sslSocket *ss);
+static SECStatus ssl3_SendFinished( sslSocket *ss, PRInt32 flags);
+static SECStatus ssl3_SendServerHello( sslSocket *ss);
+static SECStatus ssl3_SendServerHelloDone( sslSocket *ss);
+static SECStatus ssl3_SendServerKeyExchange( sslSocket *ss);
+static SECStatus ssl3_UpdateHandshakeHashes( sslSocket *ss,
+ const unsigned char *b,
+ unsigned int l);
+static SECStatus ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags);
+static int ssl3_OIDToTLSHashAlgorithm(SECOidTag oid);
+
+static SECStatus Null_Cipher(void *ctx, unsigned char *output, int *outputLen,
+ int maxOutputLen, const unsigned char *input,
+ int inputLen);
+#ifndef NO_PKCS11_BYPASS
+static SECStatus ssl3_AESGCMBypass(ssl3KeyMaterial *keys, PRBool doDecrypt,
+ unsigned char *out, int *outlen, int maxout,
+ const unsigned char *in, int inlen,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seq_num);
+#endif
+
+#define MAX_SEND_BUF_LENGTH 32000 /* watch for 16-bit integer overflow */
+#define MIN_SEND_BUF_LENGTH 4000
+
+/* This list of SSL3 cipher suites is sorted in descending order of
+ * precedence (desirability). It only includes cipher suites we implement.
+ * This table is modified by SSL3_SetPolicy(). The ordering of cipher suites
+ * in this table must match the ordering in SSL_ImplementedCiphers (sslenum.c)
+ */
+static ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED] = {
+ /* cipher_suite policy enabled is_present*/
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_RSA_WITH_AES_256_CBC_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_DHE_DSS_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDH_ECDSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { TLS_RSA_WITH_SEED_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { SSL_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { SSL_RSA_WITH_RC4_128_MD5, SSL_NOT_ALLOWED, PR_TRUE, PR_FALSE},
+ { TLS_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { TLS_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+ { SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE,PR_FALSE},
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, PR_TRUE, PR_FALSE},
+
+
+ { SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { SSL_RSA_FIPS_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { SSL_RSA_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+
+ { SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDHE_ECDSA_WITH_NULL_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { TLS_ECDHE_RSA_WITH_NULL_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { TLS_ECDH_RSA_WITH_NULL_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+ { TLS_ECDH_ECDSA_WITH_NULL_SHA, SSL_NOT_ALLOWED, PR_FALSE, PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+ { SSL_RSA_WITH_NULL_SHA, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { TLS_RSA_WITH_NULL_SHA256, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+ { SSL_RSA_WITH_NULL_MD5, SSL_NOT_ALLOWED, PR_FALSE,PR_FALSE},
+
+};
+
+/* This list of SSL3 compression methods is sorted in descending order of
+ * precedence (desirability). It only includes compression methods we
+ * implement.
+ */
+static const /*SSLCompressionMethod*/ PRUint8 compressions [] = {
+#ifdef NSS_ENABLE_ZLIB
+ ssl_compression_deflate,
+#endif
+ ssl_compression_null
+};
+
+static const int compressionMethodsCount =
+ sizeof(compressions) / sizeof(compressions[0]);
+
+/* compressionEnabled returns true iff the compression algorithm is enabled
+ * for the given SSL socket. */
+static PRBool
+compressionEnabled(sslSocket *ss, SSLCompressionMethod compression)
+{
+ switch (compression) {
+ case ssl_compression_null:
+ return PR_TRUE; /* Always enabled */
+#ifdef NSS_ENABLE_ZLIB
+ case ssl_compression_deflate:
+ return ss->opt.enableDeflate;
+#endif
+ default:
+ return PR_FALSE;
+ }
+}
+
+static const /*SSL3ClientCertificateType */ PRUint8 certificate_types [] = {
+ ct_RSA_sign,
+#ifdef NSS_ENABLE_ECC
+ ct_ECDSA_sign,
+#endif /* NSS_ENABLE_ECC */
+ ct_DSS_sign,
+};
+
+/* This block is the contents of the supported_signature_algorithms field of
+ * our TLS 1.2 CertificateRequest message, in wire format. See
+ * https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+ *
+ * This block contains only sha256 entries because we only support TLS 1.2
+ * CertificateVerify messages that use the handshake hash. */
+static const PRUint8 supported_signature_algorithms[] = {
+ tls_hash_sha256, tls_sig_rsa,
+#ifdef NSS_ENABLE_ECC
+ tls_hash_sha256, tls_sig_ecdsa,
+#endif
+ tls_hash_sha256, tls_sig_dsa,
+};
+
+#define EXPORT_RSA_KEY_LENGTH 64 /* bytes */
+
+
+/* This global item is used only in servers. It is is initialized by
+** SSL_ConfigSecureServer(), and is used in ssl3_SendCertificateRequest().
+*/
+CERTDistNames *ssl3_server_ca_list = NULL;
+static SSL3Statistics ssl3stats;
+
+/* indexed by SSL3BulkCipher */
+static const ssl3BulkCipherDef bulk_cipher_defs[] = {
+ /* |--------- Lengths --------| */
+ /* cipher calg k s type i b t n */
+ /* e e v l a o */
+ /* y c | o g n */
+ /* | r | c | c */
+ /* | e | k | e */
+ /* | t | | | | */
+ {cipher_null, calg_null, 0, 0, type_stream, 0, 0, 0, 0},
+ {cipher_rc4, calg_rc4, 16,16, type_stream, 0, 0, 0, 0},
+ {cipher_rc4_40, calg_rc4, 16, 5, type_stream, 0, 0, 0, 0},
+ {cipher_rc4_56, calg_rc4, 16, 7, type_stream, 0, 0, 0, 0},
+ {cipher_rc2, calg_rc2, 16,16, type_block, 8, 8, 0, 0},
+ {cipher_rc2_40, calg_rc2, 16, 5, type_block, 8, 8, 0, 0},
+ {cipher_des, calg_des, 8, 8, type_block, 8, 8, 0, 0},
+ {cipher_3des, calg_3des, 24,24, type_block, 8, 8, 0, 0},
+ {cipher_des40, calg_des, 8, 5, type_block, 8, 8, 0, 0},
+ {cipher_idea, calg_idea, 16,16, type_block, 8, 8, 0, 0},
+ {cipher_aes_128, calg_aes, 16,16, type_block, 16,16, 0, 0},
+ {cipher_aes_256, calg_aes, 32,32, type_block, 16,16, 0, 0},
+ {cipher_camellia_128, calg_camellia, 16,16, type_block, 16,16, 0, 0},
+ {cipher_camellia_256, calg_camellia, 32,32, type_block, 16,16, 0, 0},
+ {cipher_seed, calg_seed, 16,16, type_block, 16,16, 0, 0},
+ {cipher_aes_128_gcm, calg_aes_gcm, 16,16, type_aead, 4, 0,16, 8},
+ {cipher_missing, calg_null, 0, 0, type_stream, 0, 0, 0, 0},
+};
+
+static const ssl3KEADef kea_defs[] =
+{ /* indexed by SSL3KeyExchangeAlgorithm */
+ /* kea exchKeyType signKeyType is_limited limit tls_keygen */
+ {kea_null, kt_null, sign_null, PR_FALSE, 0, PR_FALSE},
+ {kea_rsa, kt_rsa, sign_rsa, PR_FALSE, 0, PR_FALSE},
+ {kea_rsa_export, kt_rsa, sign_rsa, PR_TRUE, 512, PR_FALSE},
+ {kea_rsa_export_1024,kt_rsa, sign_rsa, PR_TRUE, 1024, PR_FALSE},
+ {kea_dh_dss, kt_dh, sign_dsa, PR_FALSE, 0, PR_FALSE},
+ {kea_dh_dss_export, kt_dh, sign_dsa, PR_TRUE, 512, PR_FALSE},
+ {kea_dh_rsa, kt_dh, sign_rsa, PR_FALSE, 0, PR_FALSE},
+ {kea_dh_rsa_export, kt_dh, sign_rsa, PR_TRUE, 512, PR_FALSE},
+ {kea_dhe_dss, kt_dh, sign_dsa, PR_FALSE, 0, PR_FALSE},
+ {kea_dhe_dss_export, kt_dh, sign_dsa, PR_TRUE, 512, PR_FALSE},
+ {kea_dhe_rsa, kt_dh, sign_rsa, PR_FALSE, 0, PR_FALSE},
+ {kea_dhe_rsa_export, kt_dh, sign_rsa, PR_TRUE, 512, PR_FALSE},
+ {kea_dh_anon, kt_dh, sign_null, PR_FALSE, 0, PR_FALSE},
+ {kea_dh_anon_export, kt_dh, sign_null, PR_TRUE, 512, PR_FALSE},
+ {kea_rsa_fips, kt_rsa, sign_rsa, PR_FALSE, 0, PR_TRUE },
+#ifdef NSS_ENABLE_ECC
+ {kea_ecdh_ecdsa, kt_ecdh, sign_ecdsa, PR_FALSE, 0, PR_FALSE},
+ {kea_ecdhe_ecdsa, kt_ecdh, sign_ecdsa, PR_FALSE, 0, PR_FALSE},
+ {kea_ecdh_rsa, kt_ecdh, sign_rsa, PR_FALSE, 0, PR_FALSE},
+ {kea_ecdhe_rsa, kt_ecdh, sign_rsa, PR_FALSE, 0, PR_FALSE},
+ {kea_ecdh_anon, kt_ecdh, sign_null, PR_FALSE, 0, PR_FALSE},
+#endif /* NSS_ENABLE_ECC */
+};
+
+/* must use ssl_LookupCipherSuiteDef to access */
+static const ssl3CipherSuiteDef cipher_suite_defs[] =
+{
+/* cipher_suite bulk_cipher_alg mac_alg key_exchange_alg */
+
+ {SSL_NULL_WITH_NULL_NULL, cipher_null, mac_null, kea_null},
+ {SSL_RSA_WITH_NULL_MD5, cipher_null, mac_md5, kea_rsa},
+ {SSL_RSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_rsa},
+ {TLS_RSA_WITH_NULL_SHA256, cipher_null, hmac_sha256, kea_rsa},
+ {SSL_RSA_EXPORT_WITH_RC4_40_MD5,cipher_rc4_40, mac_md5, kea_rsa_export},
+ {SSL_RSA_WITH_RC4_128_MD5, cipher_rc4, mac_md5, kea_rsa},
+ {SSL_RSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_rsa},
+ {SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
+ cipher_rc2_40, mac_md5, kea_rsa_export},
+#if 0 /* not implemented */
+ {SSL_RSA_WITH_IDEA_CBC_SHA, cipher_idea, mac_sha, kea_rsa},
+ {SSL_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_rsa_export},
+#endif
+ {SSL_RSA_WITH_DES_CBC_SHA, cipher_des, mac_sha, kea_rsa},
+ {SSL_RSA_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_rsa},
+ {SSL_DHE_DSS_WITH_DES_CBC_SHA, cipher_des, mac_sha, kea_dhe_dss},
+ {SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ cipher_3des, mac_sha, kea_dhe_dss},
+ {TLS_DHE_DSS_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_dhe_dss},
+#if 0 /* not implemented */
+ {SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_dh_dss_export},
+ {SSL_DH_DSS_DES_CBC_SHA, cipher_des, mac_sha, kea_dh_dss},
+ {SSL_DH_DSS_3DES_CBC_SHA, cipher_3des, mac_sha, kea_dh_dss},
+ {SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_dh_rsa_export},
+ {SSL_DH_RSA_DES_CBC_SHA, cipher_des, mac_sha, kea_dh_rsa},
+ {SSL_DH_RSA_3DES_CBC_SHA, cipher_3des, mac_sha, kea_dh_rsa},
+ {SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_dh_dss_export},
+ {SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_dh_rsa_export},
+#endif
+ {SSL_DHE_RSA_WITH_DES_CBC_SHA, cipher_des, mac_sha, kea_dhe_rsa},
+ {SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ cipher_3des, mac_sha, kea_dhe_rsa},
+#if 0
+ {SSL_DH_ANON_EXPORT_RC4_40_MD5, cipher_rc4_40, mac_md5, kea_dh_anon_export},
+ {SSL_DH_ANON_EXPORT_WITH_DES40_CBC_SHA,
+ cipher_des40, mac_sha, kea_dh_anon_export},
+ {SSL_DH_ANON_DES_CBC_SHA, cipher_des, mac_sha, kea_dh_anon},
+ {SSL_DH_ANON_3DES_CBC_SHA, cipher_3des, mac_sha, kea_dh_anon},
+#endif
+
+
+/* New TLS cipher suites */
+ {TLS_RSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_rsa},
+ {TLS_RSA_WITH_AES_128_CBC_SHA256, cipher_aes_128, hmac_sha256, kea_rsa},
+ {TLS_DHE_DSS_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_dhe_dss},
+ {TLS_DHE_RSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_dhe_rsa},
+ {TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, cipher_aes_128, hmac_sha256, kea_dhe_rsa},
+ {TLS_RSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_rsa},
+ {TLS_RSA_WITH_AES_256_CBC_SHA256, cipher_aes_256, hmac_sha256, kea_rsa},
+ {TLS_DHE_DSS_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_dhe_dss},
+ {TLS_DHE_RSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_dhe_rsa},
+ {TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, cipher_aes_256, hmac_sha256, kea_dhe_rsa},
+#if 0
+ {TLS_DH_DSS_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_dh_dss},
+ {TLS_DH_RSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_dh_rsa},
+ {TLS_DH_ANON_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_dh_anon},
+ {TLS_DH_DSS_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_dh_dss},
+ {TLS_DH_RSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_dh_rsa},
+ {TLS_DH_ANON_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_dh_anon},
+#endif
+
+ {TLS_RSA_WITH_SEED_CBC_SHA, cipher_seed, mac_sha, kea_rsa},
+
+ {TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, cipher_camellia_128, mac_sha, kea_rsa},
+ {TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
+ cipher_camellia_128, mac_sha, kea_dhe_dss},
+ {TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
+ cipher_camellia_128, mac_sha, kea_dhe_rsa},
+ {TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, cipher_camellia_256, mac_sha, kea_rsa},
+ {TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
+ cipher_camellia_256, mac_sha, kea_dhe_dss},
+ {TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
+ cipher_camellia_256, mac_sha, kea_dhe_rsa},
+
+ {TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
+ cipher_des, mac_sha,kea_rsa_export_1024},
+ {TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
+ cipher_rc4_56, mac_sha,kea_rsa_export_1024},
+
+ {SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_rsa_fips},
+ {SSL_RSA_FIPS_WITH_DES_CBC_SHA, cipher_des, mac_sha, kea_rsa_fips},
+
+ {TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_dhe_rsa},
+ {TLS_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_rsa},
+ {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_ecdhe_rsa},
+ {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, cipher_aes_128_gcm, mac_null, kea_ecdhe_ecdsa},
+
+#ifdef NSS_ENABLE_ECC
+ {TLS_ECDH_ECDSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_ecdsa},
+ {TLS_ECDH_ECDSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdh_ecdsa},
+ {TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_ecdh_ecdsa},
+ {TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_ecdh_ecdsa},
+ {TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_ecdh_ecdsa},
+
+ {TLS_ECDHE_ECDSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdhe_ecdsa},
+ {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdhe_ecdsa},
+ {TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_ecdhe_ecdsa},
+ {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_ecdhe_ecdsa},
+ {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, cipher_aes_128, hmac_sha256, kea_ecdhe_ecdsa},
+ {TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_ecdhe_ecdsa},
+
+ {TLS_ECDH_RSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_rsa},
+ {TLS_ECDH_RSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdh_rsa},
+ {TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_ecdh_rsa},
+ {TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_ecdh_rsa},
+ {TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_ecdh_rsa},
+
+ {TLS_ECDHE_RSA_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdhe_rsa},
+ {TLS_ECDHE_RSA_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdhe_rsa},
+ {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_ecdhe_rsa},
+ {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_ecdhe_rsa},
+ {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, cipher_aes_128, hmac_sha256, kea_ecdhe_rsa},
+ {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_ecdhe_rsa},
+
+#if 0
+ {TLS_ECDH_anon_WITH_NULL_SHA, cipher_null, mac_sha, kea_ecdh_anon},
+ {TLS_ECDH_anon_WITH_RC4_128_SHA, cipher_rc4, mac_sha, kea_ecdh_anon},
+ {TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, cipher_3des, mac_sha, kea_ecdh_anon},
+ {TLS_ECDH_anon_WITH_AES_128_CBC_SHA, cipher_aes_128, mac_sha, kea_ecdh_anon},
+ {TLS_ECDH_anon_WITH_AES_256_CBC_SHA, cipher_aes_256, mac_sha, kea_ecdh_anon},
+#endif
+#endif /* NSS_ENABLE_ECC */
+};
+
+static const CK_MECHANISM_TYPE kea_alg_defs[] = {
+ 0x80000000L,
+ CKM_RSA_PKCS,
+ CKM_DH_PKCS_DERIVE,
+ CKM_KEA_KEY_DERIVE,
+ CKM_ECDH1_DERIVE
+};
+
+typedef struct SSLCipher2MechStr {
+ SSLCipherAlgorithm calg;
+ CK_MECHANISM_TYPE cmech;
+} SSLCipher2Mech;
+
+/* indexed by type SSLCipherAlgorithm */
+static const SSLCipher2Mech alg2Mech[] = {
+ /* calg, cmech */
+ { calg_null , (CK_MECHANISM_TYPE)0x80000000L },
+ { calg_rc4 , CKM_RC4 },
+ { calg_rc2 , CKM_RC2_CBC },
+ { calg_des , CKM_DES_CBC },
+ { calg_3des , CKM_DES3_CBC },
+ { calg_idea , CKM_IDEA_CBC },
+ { calg_fortezza , CKM_SKIPJACK_CBC64 },
+ { calg_aes , CKM_AES_CBC },
+ { calg_camellia , CKM_CAMELLIA_CBC },
+ { calg_seed , CKM_SEED_CBC },
+ { calg_aes_gcm , CKM_AES_GCM },
+/* { calg_init , (CK_MECHANISM_TYPE)0x7fffffffL } */
+};
+
+#define mmech_null (CK_MECHANISM_TYPE)0x80000000L
+#define mmech_md5 CKM_SSL3_MD5_MAC
+#define mmech_sha CKM_SSL3_SHA1_MAC
+#define mmech_md5_hmac CKM_MD5_HMAC
+#define mmech_sha_hmac CKM_SHA_1_HMAC
+#define mmech_sha256_hmac CKM_SHA256_HMAC
+
+static const ssl3MACDef mac_defs[] = { /* indexed by SSL3MACAlgorithm */
+ /* pad_size is only used for SSL 3.0 MAC. See RFC 6101 Sec. 5.2.3.1. */
+ /* mac mmech pad_size mac_size */
+ { mac_null, mmech_null, 0, 0 },
+ { mac_md5, mmech_md5, 48, MD5_LENGTH },
+ { mac_sha, mmech_sha, 40, SHA1_LENGTH},
+ {hmac_md5, mmech_md5_hmac, 0, MD5_LENGTH },
+ {hmac_sha, mmech_sha_hmac, 0, SHA1_LENGTH},
+ {hmac_sha256, mmech_sha256_hmac, 0, SHA256_LENGTH},
+};
+
+/* indexed by SSL3BulkCipher */
+const char * const ssl3_cipherName[] = {
+ "NULL",
+ "RC4",
+ "RC4-40",
+ "RC4-56",
+ "RC2-CBC",
+ "RC2-CBC-40",
+ "DES-CBC",
+ "3DES-EDE-CBC",
+ "DES-CBC-40",
+ "IDEA-CBC",
+ "AES-128",
+ "AES-256",
+ "Camellia-128",
+ "Camellia-256",
+ "SEED-CBC",
+ "AES-128-GCM",
+ "missing"
+};
+
+#ifdef NSS_ENABLE_ECC
+/* The ECCWrappedKeyInfo structure defines how various pieces of
+ * information are laid out within wrappedSymmetricWrappingkey
+ * for ECDH key exchange. Since wrappedSymmetricWrappingkey is
+ * a 512-byte buffer (see sslimpl.h), the variable length field
+ * in ECCWrappedKeyInfo can be at most (512 - 8) = 504 bytes.
+ *
+ * XXX For now, NSS only supports named elliptic curves of size 571 bits
+ * or smaller. The public value will fit within 145 bytes and EC params
+ * will fit within 12 bytes. We'll need to revisit this when NSS
+ * supports arbitrary curves.
+ */
+#define MAX_EC_WRAPPED_KEY_BUFLEN 504
+
+typedef struct ECCWrappedKeyInfoStr {
+ PRUint16 size; /* EC public key size in bits */
+ PRUint16 encodedParamLen; /* length (in bytes) of DER encoded EC params */
+ PRUint16 pubValueLen; /* length (in bytes) of EC public value */
+ PRUint16 wrappedKeyLen; /* length (in bytes) of the wrapped key */
+ PRUint8 var[MAX_EC_WRAPPED_KEY_BUFLEN]; /* this buffer contains the */
+ /* EC public-key params, the EC public value and the wrapped key */
+} ECCWrappedKeyInfo;
+#endif /* NSS_ENABLE_ECC */
+
+#if defined(TRACE)
+
+static char *
+ssl3_DecodeHandshakeType(int msgType)
+{
+ char * rv;
+ static char line[40];
+
+ switch(msgType) {
+ case hello_request: rv = "hello_request (0)"; break;
+ case client_hello: rv = "client_hello (1)"; break;
+ case server_hello: rv = "server_hello (2)"; break;
+ case hello_verify_request: rv = "hello_verify_request (3)"; break;
+ case certificate: rv = "certificate (11)"; break;
+ case server_key_exchange: rv = "server_key_exchange (12)"; break;
+ case certificate_request: rv = "certificate_request (13)"; break;
+ case server_hello_done: rv = "server_hello_done (14)"; break;
+ case certificate_verify: rv = "certificate_verify (15)"; break;
+ case client_key_exchange: rv = "client_key_exchange (16)"; break;
+ case finished: rv = "finished (20)"; break;
+ default:
+ sprintf(line, "*UNKNOWN* handshake type! (%d)", msgType);
+ rv = line;
+ }
+ return rv;
+}
+
+static char *
+ssl3_DecodeContentType(int msgType)
+{
+ char * rv;
+ static char line[40];
+
+ switch(msgType) {
+ case content_change_cipher_spec:
+ rv = "change_cipher_spec (20)"; break;
+ case content_alert: rv = "alert (21)"; break;
+ case content_handshake: rv = "handshake (22)"; break;
+ case content_application_data:
+ rv = "application_data (23)"; break;
+ default:
+ sprintf(line, "*UNKNOWN* record type! (%d)", msgType);
+ rv = line;
+ }
+ return rv;
+}
+
+#endif
+
+SSL3Statistics *
+SSL_GetStatistics(void)
+{
+ return &ssl3stats;
+}
+
+typedef struct tooLongStr {
+#if defined(IS_LITTLE_ENDIAN)
+ PRInt32 low;
+ PRInt32 high;
+#else
+ PRInt32 high;
+ PRInt32 low;
+#endif
+} tooLong;
+
+void SSL_AtomicIncrementLong(long * x)
+{
+ if ((sizeof *x) == sizeof(PRInt32)) {
+ PR_ATOMIC_INCREMENT((PRInt32 *)x);
+ } else {
+ tooLong * tl = (tooLong *)x;
+ if (PR_ATOMIC_INCREMENT(&tl->low) == 0)
+ PR_ATOMIC_INCREMENT(&tl->high);
+ }
+}
+
+static PRBool
+ssl3_CipherSuiteAllowedForVersion(ssl3CipherSuite cipherSuite,
+ SSL3ProtocolVersion version)
+{
+ switch (cipherSuite) {
+ /* See RFC 4346 A.5. Export cipher suites must not be used in TLS 1.1 or
+ * later. This set of cipher suites is similar to, but different from, the
+ * set of cipher suites considered exportable by SSL_IsExportCipherSuite.
+ */
+ case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+ case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+ /* SSL_RSA_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ * SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ * SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ * SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ * SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ * SSL_DH_ANON_EXPORT_WITH_RC4_40_MD5: never implemented
+ * SSL_DH_ANON_EXPORT_WITH_DES40_CBC_SHA: never implemented
+ */
+ return version <= SSL_LIBRARY_VERSION_TLS_1_0;
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_WITH_NULL_SHA256:
+ return version >= SSL_LIBRARY_VERSION_TLS_1_2;
+ default:
+ return PR_TRUE;
+ }
+}
+
+/* return pointer to ssl3CipherSuiteDef for suite, or NULL */
+/* XXX This does a linear search. A binary search would be better. */
+static const ssl3CipherSuiteDef *
+ssl_LookupCipherSuiteDef(ssl3CipherSuite suite)
+{
+ int cipher_suite_def_len =
+ sizeof(cipher_suite_defs) / sizeof(cipher_suite_defs[0]);
+ int i;
+
+ for (i = 0; i < cipher_suite_def_len; i++) {
+ if (cipher_suite_defs[i].cipher_suite == suite)
+ return &cipher_suite_defs[i];
+ }
+ PORT_Assert(PR_FALSE); /* We should never get here. */
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ return NULL;
+}
+
+/* Find the cipher configuration struct associate with suite */
+/* XXX This does a linear search. A binary search would be better. */
+static ssl3CipherSuiteCfg *
+ssl_LookupCipherSuiteCfg(ssl3CipherSuite suite, ssl3CipherSuiteCfg *suites)
+{
+ int i;
+
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ if (suites[i].cipher_suite == suite)
+ return &suites[i];
+ }
+ /* return NULL and let the caller handle it. */
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ return NULL;
+}
+
+
+/* Initialize the suite->isPresent value for config_match
+ * Returns count of enabled ciphers supported by extant tokens,
+ * regardless of policy or user preference.
+ * If this returns zero, the user cannot do SSL v3.
+ */
+int
+ssl3_config_match_init(sslSocket *ss)
+{
+ ssl3CipherSuiteCfg * suite;
+ const ssl3CipherSuiteDef *cipher_def;
+ SSLCipherAlgorithm cipher_alg;
+ CK_MECHANISM_TYPE cipher_mech;
+ SSL3KEAType exchKeyType;
+ int i;
+ int numPresent = 0;
+ int numEnabled = 0;
+ PRBool isServer;
+ sslServerCerts *svrAuth;
+
+ PORT_Assert(ss);
+ if (!ss) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return 0;
+ }
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ return 0;
+ }
+ isServer = (PRBool)(ss->sec.isServer != 0);
+
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ suite = &ss->cipherSuites[i];
+ if (suite->enabled) {
+ ++numEnabled;
+ /* We need the cipher defs to see if we have a token that can handle
+ * this cipher. It isn't part of the static definition.
+ */
+ cipher_def = ssl_LookupCipherSuiteDef(suite->cipher_suite);
+ if (!cipher_def) {
+ suite->isPresent = PR_FALSE;
+ continue;
+ }
+ cipher_alg = bulk_cipher_defs[cipher_def->bulk_cipher_alg].calg;
+ PORT_Assert( alg2Mech[cipher_alg].calg == cipher_alg);
+ cipher_mech = alg2Mech[cipher_alg].cmech;
+ exchKeyType =
+ kea_defs[cipher_def->key_exchange_alg].exchKeyType;
+#ifndef NSS_ENABLE_ECC
+ svrAuth = ss->serverCerts + exchKeyType;
+#else
+ /* XXX SSLKEAType isn't really a good choice for
+ * indexing certificates. It doesn't work for
+ * (EC)DHE-* ciphers. Here we use a hack to ensure
+ * that the server uses an RSA cert for (EC)DHE-RSA.
+ */
+ switch (cipher_def->key_exchange_alg) {
+ case kea_ecdhe_rsa:
+#if NSS_SERVER_DHE_IMPLEMENTED
+ /* XXX NSS does not yet implement the server side of _DHE_
+ * cipher suites. Correcting the computation for svrAuth,
+ * as the case below does, causes NSS SSL servers to begin to
+ * negotiate cipher suites they do not implement. So, until
+ * server side _DHE_ is implemented, keep this disabled.
+ */
+ case kea_dhe_rsa:
+#endif
+ svrAuth = ss->serverCerts + kt_rsa;
+ break;
+ case kea_ecdh_ecdsa:
+ case kea_ecdh_rsa:
+ /*
+ * XXX We ought to have different indices for
+ * ECDSA- and RSA-signed EC certificates so
+ * we could support both key exchange mechanisms
+ * simultaneously. For now, both of them use
+ * whatever is in the certificate slot for kt_ecdh
+ */
+ default:
+ svrAuth = ss->serverCerts + exchKeyType;
+ break;
+ }
+#endif /* NSS_ENABLE_ECC */
+
+ /* Mark the suites that are backed by real tokens, certs and keys */
+ suite->isPresent = (PRBool)
+ (((exchKeyType == kt_null) ||
+ ((!isServer || (svrAuth->serverKeyPair &&
+ svrAuth->SERVERKEY &&
+ svrAuth->serverCertChain)) &&
+ PK11_TokenExists(kea_alg_defs[exchKeyType]))) &&
+ ((cipher_alg == calg_null) || PK11_TokenExists(cipher_mech)));
+ if (suite->isPresent)
+ ++numPresent;
+ }
+ }
+ PORT_Assert(numPresent > 0 || numEnabled == 0);
+ if (numPresent <= 0) {
+ PORT_SetError(SSL_ERROR_NO_CIPHERS_SUPPORTED);
+ }
+ return numPresent;
+}
+
+
+/* return PR_TRUE if suite matches policy and enabled state */
+/* It would be a REALLY BAD THING (tm) if we ever permitted the use
+** of a cipher that was NOT_ALLOWED. So, if this is ever called with
+** policy == SSL_NOT_ALLOWED, report no match.
+*/
+/* adjust suite enabled to the availability of a token that can do the
+ * cipher suite. */
+static PRBool
+config_match(ssl3CipherSuiteCfg *suite, int policy, PRBool enabled)
+{
+ PORT_Assert(policy != SSL_NOT_ALLOWED && enabled != PR_FALSE);
+ if (policy == SSL_NOT_ALLOWED || !enabled)
+ return PR_FALSE;
+ return (PRBool)(suite->enabled &&
+ suite->isPresent &&
+ suite->policy != SSL_NOT_ALLOWED &&
+ suite->policy <= policy);
+}
+
+/* return number of cipher suites that match policy and enabled state */
+/* called from ssl3_SendClientHello and ssl3_ConstructV2CipherSpecsHack */
+static int
+count_cipher_suites(sslSocket *ss, int policy, PRBool enabled)
+{
+ int i, count = 0;
+
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ return 0;
+ }
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ if (config_match(&ss->cipherSuites[i], policy, enabled))
+ count++;
+ }
+ if (count <= 0) {
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ }
+ return count;
+}
+
+/*
+ * Null compression, mac and encryption functions
+ */
+
+static SECStatus
+Null_Cipher(void *ctx, unsigned char *output, int *outputLen, int maxOutputLen,
+ const unsigned char *input, int inputLen)
+{
+ *outputLen = inputLen;
+ if (input != output)
+ PORT_Memcpy(output, input, inputLen);
+ return SECSuccess;
+}
+
+/*
+ * SSL3 Utility functions
+ */
+
+/* allowLargerPeerVersion controls whether the function will select the
+ * highest enabled SSL version or fail when peerVersion is greater than the
+ * highest enabled version.
+ *
+ * If allowLargerPeerVersion is true, peerVersion is the peer's highest
+ * enabled version rather than the peer's selected version.
+ */
+SECStatus
+ssl3_NegotiateVersion(sslSocket *ss, SSL3ProtocolVersion peerVersion,
+ PRBool allowLargerPeerVersion)
+{
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ return SECFailure;
+ }
+
+ if (peerVersion < ss->vrange.min ||
+ (peerVersion > ss->vrange.max && !allowLargerPeerVersion)) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+
+ ss->version = PR_MIN(peerVersion, ss->vrange.max);
+ PORT_Assert(ssl3_VersionIsSupported(ss->protocolVariant, ss->version));
+
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_GetNewRandom(SSL3Random *random)
+{
+ PRUint32 gmt = ssl_Time();
+ SECStatus rv;
+
+ random->rand[0] = (unsigned char)(gmt >> 24);
+ random->rand[1] = (unsigned char)(gmt >> 16);
+ random->rand[2] = (unsigned char)(gmt >> 8);
+ random->rand[3] = (unsigned char)(gmt);
+
+ /* first 4 bytes are reserverd for time */
+ rv = PK11_GenerateRandom(&random->rand[4], SSL3_RANDOM_LENGTH - 4);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
+ }
+ return rv;
+}
+
+/* Called by ssl3_SendServerKeyExchange and ssl3_SendCertificateVerify */
+SECStatus
+ssl3_SignHashes(SSL3Hashes *hash, SECKEYPrivateKey *key, SECItem *buf,
+ PRBool isTLS)
+{
+ SECStatus rv = SECFailure;
+ PRBool doDerEncode = PR_FALSE;
+ int signatureLen;
+ SECItem hashItem;
+
+ buf->data = NULL;
+
+ switch (key->keyType) {
+ case rsaKey:
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ break;
+ case dsaKey:
+ doDerEncode = isTLS;
+ /* SEC_OID_UNKNOWN is used to specify the MD5/SHA1 concatenated hash.
+ * In that case, we use just the SHA1 part. */
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ break;
+#ifdef NSS_ENABLE_ECC
+ case ecKey:
+ doDerEncode = PR_TRUE;
+ /* SEC_OID_UNKNOWN is used to specify the MD5/SHA1 concatenated hash.
+ * In that case, we use just the SHA1 part. */
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ break;
+#endif /* NSS_ENABLE_ECC */
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+ PRINT_BUF(60, (NULL, "hash(es) to be signed", hashItem.data, hashItem.len));
+
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ signatureLen = PK11_SignatureLen(key);
+ if (signatureLen <= 0) {
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+
+ buf->len = (unsigned)signatureLen;
+ buf->data = (unsigned char *)PORT_Alloc(signatureLen);
+ if (!buf->data)
+ goto done; /* error code was set. */
+
+ rv = PK11_Sign(key, buf, &hashItem);
+ } else {
+ rv = SGN_Digest(key, hash->hashAlg, buf, &hashItem);
+ }
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SIGN_HASHES_FAILURE);
+ } else if (doDerEncode) {
+ SECItem derSig = {siBuffer, NULL, 0};
+
+ /* This also works for an ECDSA signature */
+ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len);
+ if (rv == SECSuccess) {
+ PORT_Free(buf->data); /* discard unencoded signature. */
+ *buf = derSig; /* give caller encoded signature. */
+ } else if (derSig.data) {
+ PORT_Free(derSig.data);
+ }
+ }
+
+ PRINT_BUF(60, (NULL, "signed hashes", (unsigned char*)buf->data, buf->len));
+done:
+ if (rv != SECSuccess && buf->data) {
+ PORT_Free(buf->data);
+ buf->data = NULL;
+ }
+ return rv;
+}
+
+/* Called from ssl3_HandleServerKeyExchange, ssl3_HandleCertificateVerify */
+SECStatus
+ssl3_VerifySignedHashes(SSL3Hashes *hash, CERTCertificate *cert,
+ SECItem *buf, PRBool isTLS, void *pwArg)
+{
+ SECKEYPublicKey * key;
+ SECItem * signature = NULL;
+ SECStatus rv;
+ SECItem hashItem;
+ SECOidTag encAlg;
+ SECOidTag hashAlg;
+
+
+ PRINT_BUF(60, (NULL, "check signed hashes",
+ buf->data, buf->len));
+
+ key = CERT_ExtractPublicKey(cert);
+ if (key == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE);
+ return SECFailure;
+ }
+
+ hashAlg = hash->hashAlg;
+ switch (key->keyType) {
+ case rsaKey:
+ encAlg = SEC_OID_PKCS1_RSA_ENCRYPTION;
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ break;
+ case dsaKey:
+ encAlg = SEC_OID_ANSIX9_DSA_SIGNATURE;
+ /* SEC_OID_UNKNOWN is used to specify the MD5/SHA1 concatenated hash.
+ * In that case, we use just the SHA1 part. */
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ /* Allow DER encoded DSA signatures in SSL 3.0 */
+ if (isTLS || buf->len != SECKEY_SignatureLen(key)) {
+ signature = DSAU_DecodeDerSig(buf);
+ if (!signature) {
+ PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ return SECFailure;
+ }
+ buf = signature;
+ }
+ break;
+
+#ifdef NSS_ENABLE_ECC
+ case ecKey:
+ encAlg = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
+ /* SEC_OID_UNKNOWN is used to specify the MD5/SHA1 concatenated hash.
+ * In that case, we use just the SHA1 part.
+ * ECDSA signatures always encode the integers r and s using ASN.1
+ * (unlike DSA where ASN.1 encoding is used with TLS but not with
+ * SSL3). So we can use VFY_VerifyDigestDirect for ECDSA.
+ */
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashAlg = SEC_OID_SHA1;
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ break;
+#endif /* NSS_ENABLE_ECC */
+
+ default:
+ SECKEY_DestroyPublicKey(key);
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ return SECFailure;
+ }
+
+ PRINT_BUF(60, (NULL, "hash(es) to be verified",
+ hashItem.data, hashItem.len));
+
+ if (hashAlg == SEC_OID_UNKNOWN || key->keyType == dsaKey) {
+ /* VFY_VerifyDigestDirect requires DSA signatures to be DER-encoded.
+ * DSA signatures are DER-encoded in TLS but not in SSL3 and the code
+ * above always removes the DER encoding of DSA signatures when
+ * present. Thus DSA signatures are always verified with PK11_Verify.
+ */
+ rv = PK11_Verify(key, buf, &hashItem, pwArg);
+ } else {
+ rv = VFY_VerifyDigestDirect(&hashItem, key, buf, encAlg, hashAlg,
+ pwArg);
+ }
+ SECKEY_DestroyPublicKey(key);
+ if (signature) {
+ SECITEM_FreeItem(signature, PR_TRUE);
+ }
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ }
+ return rv;
+}
+
+
+/* Caller must set hiLevel error code. */
+/* Called from ssl3_ComputeExportRSAKeyHash
+ * ssl3_ComputeDHKeyHash
+ * which are called from ssl3_HandleServerKeyExchange.
+ *
+ * hashAlg: either the OID for a hash algorithm or SEC_OID_UNKNOWN to specify
+ * the pre-1.2, MD5/SHA1 combination hash.
+ */
+SECStatus
+ssl3_ComputeCommonKeyHash(SECOidTag hashAlg,
+ PRUint8 * hashBuf, unsigned int bufLen,
+ SSL3Hashes *hashes, PRBool bypassPKCS11)
+{
+ SECStatus rv = SECSuccess;
+
+#ifndef NO_PKCS11_BYPASS
+ if (bypassPKCS11) {
+ if (hashAlg == SEC_OID_UNKNOWN) {
+ MD5_HashBuf (hashes->u.s.md5, hashBuf, bufLen);
+ SHA1_HashBuf(hashes->u.s.sha, hashBuf, bufLen);
+ hashes->len = MD5_LENGTH + SHA1_LENGTH;
+ } else if (hashAlg == SEC_OID_SHA1) {
+ SHA1_HashBuf(hashes->u.raw, hashBuf, bufLen);
+ hashes->len = SHA1_LENGTH;
+ } else if (hashAlg == SEC_OID_SHA256) {
+ SHA256_HashBuf(hashes->u.raw, hashBuf, bufLen);
+ hashes->len = SHA256_LENGTH;
+ } else if (hashAlg == SEC_OID_SHA384) {
+ SHA384_HashBuf(hashes->u.raw, hashBuf, bufLen);
+ hashes->len = SHA384_LENGTH;
+ } else if (hashAlg == SEC_OID_SHA512) {
+ SHA512_HashBuf(hashes->u.raw, hashBuf, bufLen);
+ hashes->len = SHA512_LENGTH;
+ } else {
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+ } else
+#endif
+ {
+ if (hashAlg == SEC_OID_UNKNOWN) {
+ rv = PK11_HashBuf(SEC_OID_MD5, hashes->u.s.md5, hashBuf, bufLen);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto done;
+ }
+
+ rv = PK11_HashBuf(SEC_OID_SHA1, hashes->u.s.sha, hashBuf, bufLen);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ rv = SECFailure;
+ }
+ hashes->len = MD5_LENGTH + SHA1_LENGTH;
+ } else {
+ hashes->len = HASH_ResultLenByOidTag(hashAlg);
+ if (hashes->len > sizeof(hashes->u.raw)) {
+ ssl_MapLowLevelError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ rv = SECFailure;
+ goto done;
+ }
+ rv = PK11_HashBuf(hashAlg, hashes->u.raw, hashBuf, bufLen);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ rv = SECFailure;
+ }
+ }
+ }
+ hashes->hashAlg = hashAlg;
+
+done:
+ return rv;
+}
+
+/* Caller must set hiLevel error code.
+** Called from ssl3_SendServerKeyExchange and
+** ssl3_HandleServerKeyExchange.
+*/
+static SECStatus
+ssl3_ComputeExportRSAKeyHash(SECOidTag hashAlg,
+ SECItem modulus, SECItem publicExponent,
+ SSL3Random *client_rand, SSL3Random *server_rand,
+ SSL3Hashes *hashes, PRBool bypassPKCS11)
+{
+ PRUint8 * hashBuf;
+ PRUint8 * pBuf;
+ SECStatus rv = SECSuccess;
+ unsigned int bufLen;
+ PRUint8 buf[2*SSL3_RANDOM_LENGTH + 2 + 4096/8 + 2 + 4096/8];
+
+ bufLen = 2*SSL3_RANDOM_LENGTH + 2 + modulus.len + 2 + publicExponent.len;
+ if (bufLen <= sizeof buf) {
+ hashBuf = buf;
+ } else {
+ hashBuf = PORT_Alloc(bufLen);
+ if (!hashBuf) {
+ return SECFailure;
+ }
+ }
+
+ memcpy(hashBuf, client_rand, SSL3_RANDOM_LENGTH);
+ pBuf = hashBuf + SSL3_RANDOM_LENGTH;
+ memcpy(pBuf, server_rand, SSL3_RANDOM_LENGTH);
+ pBuf += SSL3_RANDOM_LENGTH;
+ pBuf[0] = (PRUint8)(modulus.len >> 8);
+ pBuf[1] = (PRUint8)(modulus.len);
+ pBuf += 2;
+ memcpy(pBuf, modulus.data, modulus.len);
+ pBuf += modulus.len;
+ pBuf[0] = (PRUint8)(publicExponent.len >> 8);
+ pBuf[1] = (PRUint8)(publicExponent.len);
+ pBuf += 2;
+ memcpy(pBuf, publicExponent.data, publicExponent.len);
+ pBuf += publicExponent.len;
+ PORT_Assert((unsigned int)(pBuf - hashBuf) == bufLen);
+
+ rv = ssl3_ComputeCommonKeyHash(hashAlg, hashBuf, bufLen, hashes,
+ bypassPKCS11);
+
+ PRINT_BUF(95, (NULL, "RSAkey hash: ", hashBuf, bufLen));
+ if (hashAlg == SEC_OID_UNKNOWN) {
+ PRINT_BUF(95, (NULL, "RSAkey hash: MD5 result",
+ hashes->u.s.md5, MD5_LENGTH));
+ PRINT_BUF(95, (NULL, "RSAkey hash: SHA1 result",
+ hashes->u.s.sha, SHA1_LENGTH));
+ } else {
+ PRINT_BUF(95, (NULL, "RSAkey hash: result",
+ hashes->u.raw, hashes->len));
+ }
+
+ if (hashBuf != buf && hashBuf != NULL)
+ PORT_Free(hashBuf);
+ return rv;
+}
+
+/* Caller must set hiLevel error code. */
+/* Called from ssl3_HandleServerKeyExchange. */
+static SECStatus
+ssl3_ComputeDHKeyHash(SECOidTag hashAlg,
+ SECItem dh_p, SECItem dh_g, SECItem dh_Ys,
+ SSL3Random *client_rand, SSL3Random *server_rand,
+ SSL3Hashes *hashes, PRBool bypassPKCS11)
+{
+ PRUint8 * hashBuf;
+ PRUint8 * pBuf;
+ SECStatus rv = SECSuccess;
+ unsigned int bufLen;
+ PRUint8 buf[2*SSL3_RANDOM_LENGTH + 2 + 4096/8 + 2 + 4096/8];
+
+ bufLen = 2*SSL3_RANDOM_LENGTH + 2 + dh_p.len + 2 + dh_g.len + 2 + dh_Ys.len;
+ if (bufLen <= sizeof buf) {
+ hashBuf = buf;
+ } else {
+ hashBuf = PORT_Alloc(bufLen);
+ if (!hashBuf) {
+ return SECFailure;
+ }
+ }
+
+ memcpy(hashBuf, client_rand, SSL3_RANDOM_LENGTH);
+ pBuf = hashBuf + SSL3_RANDOM_LENGTH;
+ memcpy(pBuf, server_rand, SSL3_RANDOM_LENGTH);
+ pBuf += SSL3_RANDOM_LENGTH;
+ pBuf[0] = (PRUint8)(dh_p.len >> 8);
+ pBuf[1] = (PRUint8)(dh_p.len);
+ pBuf += 2;
+ memcpy(pBuf, dh_p.data, dh_p.len);
+ pBuf += dh_p.len;
+ pBuf[0] = (PRUint8)(dh_g.len >> 8);
+ pBuf[1] = (PRUint8)(dh_g.len);
+ pBuf += 2;
+ memcpy(pBuf, dh_g.data, dh_g.len);
+ pBuf += dh_g.len;
+ pBuf[0] = (PRUint8)(dh_Ys.len >> 8);
+ pBuf[1] = (PRUint8)(dh_Ys.len);
+ pBuf += 2;
+ memcpy(pBuf, dh_Ys.data, dh_Ys.len);
+ pBuf += dh_Ys.len;
+ PORT_Assert((unsigned int)(pBuf - hashBuf) == bufLen);
+
+ rv = ssl3_ComputeCommonKeyHash(hashAlg, hashBuf, bufLen, hashes,
+ bypassPKCS11);
+
+ PRINT_BUF(95, (NULL, "DHkey hash: ", hashBuf, bufLen));
+ if (hashAlg == SEC_OID_UNKNOWN) {
+ PRINT_BUF(95, (NULL, "DHkey hash: MD5 result",
+ hashes->u.s.md5, MD5_LENGTH));
+ PRINT_BUF(95, (NULL, "DHkey hash: SHA1 result",
+ hashes->u.s.sha, SHA1_LENGTH));
+ } else {
+ PRINT_BUF(95, (NULL, "DHkey hash: result",
+ hashes->u.raw, hashes->len));
+ }
+
+ if (hashBuf != buf && hashBuf != NULL)
+ PORT_Free(hashBuf);
+ return rv;
+}
+
+static void
+ssl3_BumpSequenceNumber(SSL3SequenceNumber *num)
+{
+ num->low++;
+ if (num->low == 0)
+ num->high++;
+}
+
+/* Called twice, only from ssl3_DestroyCipherSpec (immediately below). */
+static void
+ssl3_CleanupKeyMaterial(ssl3KeyMaterial *mat)
+{
+ if (mat->write_key != NULL) {
+ PK11_FreeSymKey(mat->write_key);
+ mat->write_key = NULL;
+ }
+ if (mat->write_mac_key != NULL) {
+ PK11_FreeSymKey(mat->write_mac_key);
+ mat->write_mac_key = NULL;
+ }
+ if (mat->write_mac_context != NULL) {
+ PK11_DestroyContext(mat->write_mac_context, PR_TRUE);
+ mat->write_mac_context = NULL;
+ }
+}
+
+/* Called from ssl3_SendChangeCipherSpecs() and
+** ssl3_HandleChangeCipherSpecs()
+** ssl3_DestroySSL3Info
+** Caller must hold SpecWriteLock.
+*/
+void
+ssl3_DestroyCipherSpec(ssl3CipherSpec *spec, PRBool freeSrvName)
+{
+ PRBool freeit = (PRBool)(!spec->bypassCiphers);
+/* PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss)); Don't have ss! */
+ if (spec->destroy) {
+ spec->destroy(spec->encodeContext, freeit);
+ spec->destroy(spec->decodeContext, freeit);
+ spec->encodeContext = NULL; /* paranoia */
+ spec->decodeContext = NULL;
+ }
+ if (spec->destroyCompressContext && spec->compressContext) {
+ spec->destroyCompressContext(spec->compressContext, 1);
+ spec->compressContext = NULL;
+ }
+ if (spec->destroyDecompressContext && spec->decompressContext) {
+ spec->destroyDecompressContext(spec->decompressContext, 1);
+ spec->decompressContext = NULL;
+ }
+ if (freeSrvName && spec->srvVirtName.data) {
+ SECITEM_FreeItem(&spec->srvVirtName, PR_FALSE);
+ }
+ if (spec->master_secret != NULL) {
+ PK11_FreeSymKey(spec->master_secret);
+ spec->master_secret = NULL;
+ }
+ spec->msItem.data = NULL;
+ spec->msItem.len = 0;
+ ssl3_CleanupKeyMaterial(&spec->client);
+ ssl3_CleanupKeyMaterial(&spec->server);
+ spec->bypassCiphers = PR_FALSE;
+ spec->destroy=NULL;
+ spec->destroyCompressContext = NULL;
+ spec->destroyDecompressContext = NULL;
+}
+
+/* Fill in the pending cipher spec with info from the selected ciphersuite.
+** This is as much initialization as we can do without having key material.
+** Called from ssl3_HandleServerHello(), ssl3_SendServerHello()
+** Caller must hold the ssl3 handshake lock.
+** Acquires & releases SpecWriteLock.
+*/
+static SECStatus
+ssl3_SetupPendingCipherSpec(sslSocket *ss)
+{
+ ssl3CipherSpec * pwSpec;
+ ssl3CipherSpec * cwSpec;
+ ssl3CipherSuite suite = ss->ssl3.hs.cipher_suite;
+ SSL3MACAlgorithm mac;
+ SSL3BulkCipher cipher;
+ SSL3KeyExchangeAlgorithm kea;
+ const ssl3CipherSuiteDef *suite_def;
+ PRBool isTLS;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ ssl_GetSpecWriteLock(ss); /*******************************/
+
+ pwSpec = ss->ssl3.pwSpec;
+ PORT_Assert(pwSpec == ss->ssl3.prSpec);
+
+ /* This hack provides maximal interoperability with SSL 3 servers. */
+ cwSpec = ss->ssl3.cwSpec;
+ if (cwSpec->mac_def->mac == mac_null) {
+ /* SSL records are not being MACed. */
+ cwSpec->version = ss->version;
+ }
+
+ pwSpec->version = ss->version;
+ isTLS = (PRBool)(pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+
+ SSL_TRC(3, ("%d: SSL3[%d]: Set XXX Pending Cipher Suite to 0x%04x",
+ SSL_GETPID(), ss->fd, suite));
+
+ suite_def = ssl_LookupCipherSuiteDef(suite);
+ if (suite_def == NULL) {
+ ssl_ReleaseSpecWriteLock(ss);
+ return SECFailure; /* error code set by ssl_LookupCipherSuiteDef */
+ }
+
+ if (IS_DTLS(ss)) {
+ /* Double-check that we did not pick an RC4 suite */
+ PORT_Assert((suite_def->bulk_cipher_alg != cipher_rc4) &&
+ (suite_def->bulk_cipher_alg != cipher_rc4_40) &&
+ (suite_def->bulk_cipher_alg != cipher_rc4_56));
+ }
+
+ cipher = suite_def->bulk_cipher_alg;
+ kea = suite_def->key_exchange_alg;
+ mac = suite_def->mac_alg;
+ if (mac <= ssl_mac_sha && mac != ssl_mac_null && isTLS)
+ mac += 2;
+
+ ss->ssl3.hs.suite_def = suite_def;
+ ss->ssl3.hs.kea_def = &kea_defs[kea];
+ PORT_Assert(ss->ssl3.hs.kea_def->kea == kea);
+
+ pwSpec->cipher_def = &bulk_cipher_defs[cipher];
+ PORT_Assert(pwSpec->cipher_def->cipher == cipher);
+
+ pwSpec->mac_def = &mac_defs[mac];
+ PORT_Assert(pwSpec->mac_def->mac == mac);
+
+ ss->sec.keyBits = pwSpec->cipher_def->key_size * BPB;
+ ss->sec.secretKeyBits = pwSpec->cipher_def->secret_key_size * BPB;
+ ss->sec.cipherType = cipher;
+
+ pwSpec->encodeContext = NULL;
+ pwSpec->decodeContext = NULL;
+
+ pwSpec->mac_size = pwSpec->mac_def->mac_size;
+
+ pwSpec->compression_method = ss->ssl3.hs.compression;
+ pwSpec->compressContext = NULL;
+ pwSpec->decompressContext = NULL;
+
+ ssl_ReleaseSpecWriteLock(ss); /*******************************/
+ return SECSuccess;
+}
+
+#ifdef NSS_ENABLE_ZLIB
+#define SSL3_DEFLATE_CONTEXT_SIZE sizeof(z_stream)
+
+static SECStatus
+ssl3_MapZlibError(int zlib_error)
+{
+ switch (zlib_error) {
+ case Z_OK:
+ return SECSuccess;
+ default:
+ return SECFailure;
+ }
+}
+
+static SECStatus
+ssl3_DeflateInit(void *void_context)
+{
+ z_stream *context = void_context;
+ context->zalloc = NULL;
+ context->zfree = NULL;
+ context->opaque = NULL;
+
+ return ssl3_MapZlibError(deflateInit(context, Z_DEFAULT_COMPRESSION));
+}
+
+static SECStatus
+ssl3_InflateInit(void *void_context)
+{
+ z_stream *context = void_context;
+ context->zalloc = NULL;
+ context->zfree = NULL;
+ context->opaque = NULL;
+ context->next_in = NULL;
+ context->avail_in = 0;
+
+ return ssl3_MapZlibError(inflateInit(context));
+}
+
+static SECStatus
+ssl3_DeflateCompress(void *void_context, unsigned char *out, int *out_len,
+ int maxout, const unsigned char *in, int inlen)
+{
+ z_stream *context = void_context;
+
+ if (!inlen) {
+ *out_len = 0;
+ return SECSuccess;
+ }
+
+ context->next_in = (unsigned char*) in;
+ context->avail_in = inlen;
+ context->next_out = out;
+ context->avail_out = maxout;
+ if (deflate(context, Z_SYNC_FLUSH) != Z_OK) {
+ return SECFailure;
+ }
+ if (context->avail_out == 0) {
+ /* We ran out of space! */
+ SSL_TRC(3, ("%d: SSL3[%d] Ran out of buffer while compressing",
+ SSL_GETPID()));
+ return SECFailure;
+ }
+
+ *out_len = maxout - context->avail_out;
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_DeflateDecompress(void *void_context, unsigned char *out, int *out_len,
+ int maxout, const unsigned char *in, int inlen)
+{
+ z_stream *context = void_context;
+
+ if (!inlen) {
+ *out_len = 0;
+ return SECSuccess;
+ }
+
+ context->next_in = (unsigned char*) in;
+ context->avail_in = inlen;
+ context->next_out = out;
+ context->avail_out = maxout;
+ if (inflate(context, Z_SYNC_FLUSH) != Z_OK) {
+ PORT_SetError(SSL_ERROR_DECOMPRESSION_FAILURE);
+ return SECFailure;
+ }
+
+ *out_len = maxout - context->avail_out;
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_DestroyCompressContext(void *void_context, PRBool unused)
+{
+ deflateEnd(void_context);
+ PORT_Free(void_context);
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_DestroyDecompressContext(void *void_context, PRBool unused)
+{
+ inflateEnd(void_context);
+ PORT_Free(void_context);
+ return SECSuccess;
+}
+
+#endif /* NSS_ENABLE_ZLIB */
+
+/* Initialize the compression functions and contexts for the given
+ * CipherSpec. */
+static SECStatus
+ssl3_InitCompressionContext(ssl3CipherSpec *pwSpec)
+{
+ /* Setup the compression functions */
+ switch (pwSpec->compression_method) {
+ case ssl_compression_null:
+ pwSpec->compressor = NULL;
+ pwSpec->decompressor = NULL;
+ pwSpec->compressContext = NULL;
+ pwSpec->decompressContext = NULL;
+ pwSpec->destroyCompressContext = NULL;
+ pwSpec->destroyDecompressContext = NULL;
+ break;
+#ifdef NSS_ENABLE_ZLIB
+ case ssl_compression_deflate:
+ pwSpec->compressor = ssl3_DeflateCompress;
+ pwSpec->decompressor = ssl3_DeflateDecompress;
+ pwSpec->compressContext = PORT_Alloc(SSL3_DEFLATE_CONTEXT_SIZE);
+ pwSpec->decompressContext = PORT_Alloc(SSL3_DEFLATE_CONTEXT_SIZE);
+ pwSpec->destroyCompressContext = ssl3_DestroyCompressContext;
+ pwSpec->destroyDecompressContext = ssl3_DestroyDecompressContext;
+ ssl3_DeflateInit(pwSpec->compressContext);
+ ssl3_InflateInit(pwSpec->decompressContext);
+ break;
+#endif /* NSS_ENABLE_ZLIB */
+ default:
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+#ifndef NO_PKCS11_BYPASS
+/* Initialize encryption contexts for pending spec.
+ * MAC contexts are set up when computing the mac, not here.
+ * Master Secret already is derived in spec->msItem
+ * Caller holds Spec write lock.
+ */
+static SECStatus
+ssl3_InitPendingContextsBypass(sslSocket *ss)
+{
+ ssl3CipherSpec * pwSpec;
+ const ssl3BulkCipherDef *cipher_def;
+ void * serverContext = NULL;
+ void * clientContext = NULL;
+ BLapiInitContextFunc initFn = (BLapiInitContextFunc)NULL;
+ int mode = 0;
+ unsigned int optArg1 = 0;
+ unsigned int optArg2 = 0;
+ PRBool server_encrypts = ss->sec.isServer;
+ SSLCipherAlgorithm calg;
+ SECStatus rv;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+
+ pwSpec = ss->ssl3.pwSpec;
+ cipher_def = pwSpec->cipher_def;
+
+ calg = cipher_def->calg;
+
+ if (calg == calg_aes_gcm) {
+ pwSpec->encode = NULL;
+ pwSpec->decode = NULL;
+ pwSpec->destroy = NULL;
+ pwSpec->encodeContext = NULL;
+ pwSpec->decodeContext = NULL;
+ pwSpec->aead = ssl3_AESGCMBypass;
+ ssl3_InitCompressionContext(pwSpec);
+ return SECSuccess;
+ }
+
+ serverContext = pwSpec->server.cipher_context;
+ clientContext = pwSpec->client.cipher_context;
+
+ switch (calg) {
+ case ssl_calg_null:
+ pwSpec->encode = Null_Cipher;
+ pwSpec->decode = Null_Cipher;
+ pwSpec->destroy = NULL;
+ goto success;
+
+ case ssl_calg_rc4:
+ initFn = (BLapiInitContextFunc)RC4_InitContext;
+ pwSpec->encode = (SSLCipher) RC4_Encrypt;
+ pwSpec->decode = (SSLCipher) RC4_Decrypt;
+ pwSpec->destroy = (SSLDestroy) RC4_DestroyContext;
+ break;
+ case ssl_calg_rc2:
+ initFn = (BLapiInitContextFunc)RC2_InitContext;
+ mode = NSS_RC2_CBC;
+ optArg1 = cipher_def->key_size;
+ pwSpec->encode = (SSLCipher) RC2_Encrypt;
+ pwSpec->decode = (SSLCipher) RC2_Decrypt;
+ pwSpec->destroy = (SSLDestroy) RC2_DestroyContext;
+ break;
+ case ssl_calg_des:
+ initFn = (BLapiInitContextFunc)DES_InitContext;
+ mode = NSS_DES_CBC;
+ optArg1 = server_encrypts;
+ pwSpec->encode = (SSLCipher) DES_Encrypt;
+ pwSpec->decode = (SSLCipher) DES_Decrypt;
+ pwSpec->destroy = (SSLDestroy) DES_DestroyContext;
+ break;
+ case ssl_calg_3des:
+ initFn = (BLapiInitContextFunc)DES_InitContext;
+ mode = NSS_DES_EDE3_CBC;
+ optArg1 = server_encrypts;
+ pwSpec->encode = (SSLCipher) DES_Encrypt;
+ pwSpec->decode = (SSLCipher) DES_Decrypt;
+ pwSpec->destroy = (SSLDestroy) DES_DestroyContext;
+ break;
+ case ssl_calg_aes:
+ initFn = (BLapiInitContextFunc)AES_InitContext;
+ mode = NSS_AES_CBC;
+ optArg1 = server_encrypts;
+ optArg2 = AES_BLOCK_SIZE;
+ pwSpec->encode = (SSLCipher) AES_Encrypt;
+ pwSpec->decode = (SSLCipher) AES_Decrypt;
+ pwSpec->destroy = (SSLDestroy) AES_DestroyContext;
+ break;
+
+ case ssl_calg_camellia:
+ initFn = (BLapiInitContextFunc)Camellia_InitContext;
+ mode = NSS_CAMELLIA_CBC;
+ optArg1 = server_encrypts;
+ optArg2 = CAMELLIA_BLOCK_SIZE;
+ pwSpec->encode = (SSLCipher) Camellia_Encrypt;
+ pwSpec->decode = (SSLCipher) Camellia_Decrypt;
+ pwSpec->destroy = (SSLDestroy) Camellia_DestroyContext;
+ break;
+
+ case ssl_calg_seed:
+ initFn = (BLapiInitContextFunc)SEED_InitContext;
+ mode = NSS_SEED_CBC;
+ optArg1 = server_encrypts;
+ optArg2 = SEED_BLOCK_SIZE;
+ pwSpec->encode = (SSLCipher) SEED_Encrypt;
+ pwSpec->decode = (SSLCipher) SEED_Decrypt;
+ pwSpec->destroy = (SSLDestroy) SEED_DestroyContext;
+ break;
+
+ case ssl_calg_idea:
+ case ssl_calg_fortezza :
+ default:
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto bail_out;
+ }
+ rv = (*initFn)(serverContext,
+ pwSpec->server.write_key_item.data,
+ pwSpec->server.write_key_item.len,
+ pwSpec->server.write_iv_item.data,
+ mode, optArg1, optArg2);
+ if (rv != SECSuccess) {
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto bail_out;
+ }
+
+ switch (calg) {
+ case ssl_calg_des:
+ case ssl_calg_3des:
+ case ssl_calg_aes:
+ case ssl_calg_camellia:
+ case ssl_calg_seed:
+ /* For block ciphers, if the server is encrypting, then the client
+ * is decrypting, and vice versa.
+ */
+ optArg1 = !optArg1;
+ break;
+ /* kill warnings. */
+ case ssl_calg_null:
+ case ssl_calg_rc4:
+ case ssl_calg_rc2:
+ case ssl_calg_idea:
+ case ssl_calg_fortezza:
+ break;
+ }
+
+ rv = (*initFn)(clientContext,
+ pwSpec->client.write_key_item.data,
+ pwSpec->client.write_key_item.len,
+ pwSpec->client.write_iv_item.data,
+ mode, optArg1, optArg2);
+ if (rv != SECSuccess) {
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto bail_out;
+ }
+
+ pwSpec->encodeContext = (ss->sec.isServer) ? serverContext : clientContext;
+ pwSpec->decodeContext = (ss->sec.isServer) ? clientContext : serverContext;
+
+ ssl3_InitCompressionContext(pwSpec);
+
+success:
+ return SECSuccess;
+
+bail_out:
+ return SECFailure;
+}
+#endif
+
+/* This function should probably be moved to pk11wrap and be named
+ * PK11_ParamFromIVAndEffectiveKeyBits
+ */
+static SECItem *
+ssl3_ParamFromIV(CK_MECHANISM_TYPE mtype, SECItem *iv, CK_ULONG ulEffectiveBits)
+{
+ SECItem * param = PK11_ParamFromIV(mtype, iv);
+ if (param && param->data && param->len >= sizeof(CK_RC2_PARAMS)) {
+ switch (mtype) {
+ case CKM_RC2_KEY_GEN:
+ case CKM_RC2_ECB:
+ case CKM_RC2_CBC:
+ case CKM_RC2_MAC:
+ case CKM_RC2_MAC_GENERAL:
+ case CKM_RC2_CBC_PAD:
+ *(CK_RC2_PARAMS *)param->data = ulEffectiveBits;
+ default: break;
+ }
+ }
+ return param;
+}
+
+/* ssl3_BuildRecordPseudoHeader writes the TLS pseudo-header (the data which
+ * is included in the MAC) to |out| and returns its length. */
+static unsigned int
+ssl3_BuildRecordPseudoHeader(unsigned char *out,
+ SSL3SequenceNumber seq_num,
+ SSL3ContentType type,
+ PRBool includesVersion,
+ SSL3ProtocolVersion version,
+ PRBool isDTLS,
+ int length)
+{
+ out[0] = (unsigned char)(seq_num.high >> 24);
+ out[1] = (unsigned char)(seq_num.high >> 16);
+ out[2] = (unsigned char)(seq_num.high >> 8);
+ out[3] = (unsigned char)(seq_num.high >> 0);
+ out[4] = (unsigned char)(seq_num.low >> 24);
+ out[5] = (unsigned char)(seq_num.low >> 16);
+ out[6] = (unsigned char)(seq_num.low >> 8);
+ out[7] = (unsigned char)(seq_num.low >> 0);
+ out[8] = type;
+
+ /* SSL3 MAC doesn't include the record's version field. */
+ if (!includesVersion) {
+ out[9] = MSB(length);
+ out[10] = LSB(length);
+ return 11;
+ }
+
+ /* TLS MAC and AEAD additional data include version. */
+ if (isDTLS) {
+ SSL3ProtocolVersion dtls_version;
+
+ dtls_version = dtls_TLSVersionToDTLSVersion(version);
+ out[9] = MSB(dtls_version);
+ out[10] = LSB(dtls_version);
+ } else {
+ out[9] = MSB(version);
+ out[10] = LSB(version);
+ }
+ out[11] = MSB(length);
+ out[12] = LSB(length);
+ return 13;
+}
+
+typedef SECStatus (*PK11CryptFcn)(
+ PK11SymKey *symKey, CK_MECHANISM_TYPE mechanism, SECItem *param,
+ unsigned char *out, unsigned int *outLen, unsigned int maxLen,
+ const unsigned char *in, unsigned int inLen);
+
+static PK11CryptFcn pk11_encrypt = NULL;
+static PK11CryptFcn pk11_decrypt = NULL;
+
+static PRCallOnceType resolvePK11CryptOnce;
+
+static PRStatus
+ssl3_ResolvePK11CryptFunctions(void)
+{
+#ifdef LINUX
+ /* On Linux we use the system NSS libraries. Look up the PK11_Encrypt and
+ * PK11_Decrypt functions at run time. */
+ void *handle = dlopen(NULL, RTLD_LAZY);
+ if (!handle) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return PR_FAILURE;
+ }
+ pk11_encrypt = (PK11CryptFcn)dlsym(handle, "PK11_Encrypt");
+ pk11_decrypt = (PK11CryptFcn)dlsym(handle, "PK11_Decrypt");
+ dlclose(handle);
+ return PR_SUCCESS;
+#else
+ /* On other platforms we use our own copy of NSS. PK11_Encrypt and
+ * PK11_Decrypt are known to be available. */
+ pk11_encrypt = PK11_Encrypt;
+ pk11_decrypt = PK11_Decrypt;
+ return PR_SUCCESS;
+#endif
+}
+
+/*
+ * In NSS 3.15, PK11_Encrypt and PK11_Decrypt were added to provide access
+ * to the AES GCM implementation in the NSS softoken. So the presence of
+ * these two functions implies the NSS version supports AES GCM.
+ */
+static PRBool
+ssl3_HasGCMSupport(void)
+{
+ (void)PR_CallOnce(&resolvePK11CryptOnce, ssl3_ResolvePK11CryptFunctions);
+ return pk11_encrypt != NULL;
+}
+
+/* On this socket, disable the GCM cipher suites */
+SECStatus
+ssl3_DisableGCMSuites(sslSocket * ss)
+{
+ unsigned int i;
+
+ for (i = 0; i < PR_ARRAY_SIZE(cipher_suite_defs); i++) {
+ const ssl3CipherSuiteDef *cipher_def = &cipher_suite_defs[i];
+ if (cipher_def->bulk_cipher_alg == cipher_aes_128_gcm) {
+ SECStatus rv = ssl3_CipherPrefSet(ss, cipher_def->cipher_suite,
+ PR_FALSE);
+ PORT_Assert(rv == SECSuccess); /* else is coding error */
+ }
+ }
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_AESGCM(ssl3KeyMaterial *keys,
+ PRBool doDecrypt,
+ unsigned char *out,
+ int *outlen,
+ int maxout,
+ const unsigned char *in,
+ int inlen,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seq_num)
+{
+ SECItem param;
+ SECStatus rv = SECFailure;
+ unsigned char nonce[12];
+ unsigned char additionalData[13];
+ unsigned int additionalDataLen;
+ unsigned int uOutLen;
+ CK_GCM_PARAMS gcmParams;
+
+ static const int tagSize = 16;
+ static const int explicitNonceLen = 8;
+
+ /* See https://tools.ietf.org/html/rfc5246#section-6.2.3.3 for the
+ * definition of the AEAD additional data. */
+ additionalDataLen = ssl3_BuildRecordPseudoHeader(
+ additionalData, seq_num, type, PR_TRUE /* includes version */,
+ version, PR_FALSE /* not DTLS */,
+ inlen - (doDecrypt ? explicitNonceLen + tagSize : 0));
+ PORT_Assert(additionalDataLen <= sizeof(additionalData));
+
+ /* See https://tools.ietf.org/html/rfc5288#section-3 for details of how the
+ * nonce is formed. */
+ memcpy(nonce, keys->write_iv, 4);
+ if (doDecrypt) {
+ memcpy(nonce + 4, in, explicitNonceLen);
+ in += explicitNonceLen;
+ inlen -= explicitNonceLen;
+ *outlen = 0;
+ } else {
+ if (maxout < explicitNonceLen) {
+ PORT_SetError(SEC_ERROR_INPUT_LEN);
+ return SECFailure;
+ }
+ /* Use the 64-bit sequence number as the explicit nonce. */
+ memcpy(nonce + 4, additionalData, explicitNonceLen);
+ memcpy(out, additionalData, explicitNonceLen);
+ out += explicitNonceLen;
+ maxout -= explicitNonceLen;
+ *outlen = explicitNonceLen;
+ }
+
+ param.type = siBuffer;
+ param.data = (unsigned char *) &gcmParams;
+ param.len = sizeof(gcmParams);
+ gcmParams.pIv = nonce;
+ gcmParams.ulIvLen = sizeof(nonce);
+ gcmParams.pAAD = additionalData;
+ gcmParams.ulAADLen = additionalDataLen;
+ gcmParams.ulTagBits = tagSize * 8;
+
+ if (doDecrypt) {
+ rv = pk11_decrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
+ maxout, in, inlen);
+ } else {
+ rv = pk11_encrypt(keys->write_key, CKM_AES_GCM, &param, out, &uOutLen,
+ maxout, in, inlen);
+ }
+ *outlen += (int) uOutLen;
+
+ return rv;
+}
+
+#ifndef NO_PKCS11_BYPASS
+static SECStatus
+ssl3_AESGCMBypass(ssl3KeyMaterial *keys,
+ PRBool doDecrypt,
+ unsigned char *out,
+ int *outlen,
+ int maxout,
+ const unsigned char *in,
+ int inlen,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seq_num)
+{
+ SECStatus rv = SECFailure;
+ unsigned char nonce[12];
+ unsigned char additionalData[13];
+ unsigned int additionalDataLen;
+ unsigned int uOutLen;
+ AESContext *cx;
+ CK_GCM_PARAMS gcmParams;
+
+ static const int tagSize = 16;
+ static const int explicitNonceLen = 8;
+
+ /* See https://tools.ietf.org/html/rfc5246#section-6.2.3.3 for the
+ * definition of the AEAD additional data. */
+ additionalDataLen = ssl3_BuildRecordPseudoHeader(
+ additionalData, seq_num, type, PR_TRUE /* includes version */,
+ version, PR_FALSE /* not DTLS */,
+ inlen - (doDecrypt ? explicitNonceLen + tagSize : 0));
+ PORT_Assert(additionalDataLen <= sizeof(additionalData));
+
+ /* See https://tools.ietf.org/html/rfc5288#section-3 for details of how the
+ * nonce is formed. */
+ PORT_Assert(keys->write_iv_item.len == 4);
+ if (keys->write_iv_item.len != 4) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ memcpy(nonce, keys->write_iv_item.data, 4);
+ if (doDecrypt) {
+ memcpy(nonce + 4, in, explicitNonceLen);
+ in += explicitNonceLen;
+ inlen -= explicitNonceLen;
+ *outlen = 0;
+ } else {
+ if (maxout < explicitNonceLen) {
+ PORT_SetError(SEC_ERROR_INPUT_LEN);
+ return SECFailure;
+ }
+ /* Use the 64-bit sequence number as the explicit nonce. */
+ memcpy(nonce + 4, additionalData, explicitNonceLen);
+ memcpy(out, additionalData, explicitNonceLen);
+ out += explicitNonceLen;
+ maxout -= explicitNonceLen;
+ *outlen = explicitNonceLen;
+ }
+
+ gcmParams.pIv = nonce;
+ gcmParams.ulIvLen = sizeof(nonce);
+ gcmParams.pAAD = additionalData;
+ gcmParams.ulAADLen = additionalDataLen;
+ gcmParams.ulTagBits = tagSize * 8;
+
+ cx = (AESContext *)keys->cipher_context;
+ rv = AES_InitContext(cx, keys->write_key_item.data,
+ keys->write_key_item.len,
+ (unsigned char *)&gcmParams, NSS_AES_GCM, !doDecrypt,
+ AES_BLOCK_SIZE);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ if (doDecrypt) {
+ rv = AES_Decrypt(cx, out, &uOutLen, maxout, in, inlen);
+ } else {
+ rv = AES_Encrypt(cx, out, &uOutLen, maxout, in, inlen);
+ }
+ AES_DestroyContext(cx, PR_FALSE);
+ *outlen += (int) uOutLen;
+
+ return rv;
+}
+#endif
+
+/* Initialize encryption and MAC contexts for pending spec.
+ * Master Secret already is derived.
+ * Caller holds Spec write lock.
+ */
+static SECStatus
+ssl3_InitPendingContextsPKCS11(sslSocket *ss)
+{
+ ssl3CipherSpec * pwSpec;
+ const ssl3BulkCipherDef *cipher_def;
+ PK11Context * serverContext = NULL;
+ PK11Context * clientContext = NULL;
+ SECItem * param;
+ CK_MECHANISM_TYPE mechanism;
+ CK_MECHANISM_TYPE mac_mech;
+ CK_ULONG macLength;
+ CK_ULONG effKeyBits;
+ SECItem iv;
+ SECItem mac_param;
+ SSLCipherAlgorithm calg;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+
+ pwSpec = ss->ssl3.pwSpec;
+ cipher_def = pwSpec->cipher_def;
+ macLength = pwSpec->mac_size;
+ calg = cipher_def->calg;
+ PORT_Assert(alg2Mech[calg].calg == calg);
+
+ pwSpec->client.write_mac_context = NULL;
+ pwSpec->server.write_mac_context = NULL;
+
+ if (calg == calg_aes_gcm) {
+ pwSpec->encode = NULL;
+ pwSpec->decode = NULL;
+ pwSpec->destroy = NULL;
+ pwSpec->encodeContext = NULL;
+ pwSpec->decodeContext = NULL;
+ pwSpec->aead = ssl3_AESGCM;
+ return SECSuccess;
+ }
+
+ /*
+ ** Now setup the MAC contexts,
+ ** crypto contexts are setup below.
+ */
+
+ mac_mech = pwSpec->mac_def->mmech;
+ mac_param.data = (unsigned char *)&macLength;
+ mac_param.len = sizeof(macLength);
+ mac_param.type = 0;
+
+ pwSpec->client.write_mac_context = PK11_CreateContextBySymKey(
+ mac_mech, CKA_SIGN, pwSpec->client.write_mac_key, &mac_param);
+ if (pwSpec->client.write_mac_context == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SYM_KEY_CONTEXT_FAILURE);
+ goto fail;
+ }
+ pwSpec->server.write_mac_context = PK11_CreateContextBySymKey(
+ mac_mech, CKA_SIGN, pwSpec->server.write_mac_key, &mac_param);
+ if (pwSpec->server.write_mac_context == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SYM_KEY_CONTEXT_FAILURE);
+ goto fail;
+ }
+
+ /*
+ ** Now setup the crypto contexts.
+ */
+
+ if (calg == calg_null) {
+ pwSpec->encode = Null_Cipher;
+ pwSpec->decode = Null_Cipher;
+ pwSpec->destroy = NULL;
+ return SECSuccess;
+ }
+ mechanism = alg2Mech[calg].cmech;
+ effKeyBits = cipher_def->key_size * BPB;
+
+ /*
+ * build the server context
+ */
+ iv.data = pwSpec->server.write_iv;
+ iv.len = cipher_def->iv_size;
+ param = ssl3_ParamFromIV(mechanism, &iv, effKeyBits);
+ if (param == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_IV_PARAM_FAILURE);
+ goto fail;
+ }
+ serverContext = PK11_CreateContextBySymKey(mechanism,
+ (ss->sec.isServer ? CKA_ENCRYPT : CKA_DECRYPT),
+ pwSpec->server.write_key, param);
+ iv.data = PK11_IVFromParam(mechanism, param, (int *)&iv.len);
+ if (iv.data)
+ PORT_Memcpy(pwSpec->server.write_iv, iv.data, iv.len);
+ SECITEM_FreeItem(param, PR_TRUE);
+ if (serverContext == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SYM_KEY_CONTEXT_FAILURE);
+ goto fail;
+ }
+
+ /*
+ * build the client context
+ */
+ iv.data = pwSpec->client.write_iv;
+ iv.len = cipher_def->iv_size;
+
+ param = ssl3_ParamFromIV(mechanism, &iv, effKeyBits);
+ if (param == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_IV_PARAM_FAILURE);
+ goto fail;
+ }
+ clientContext = PK11_CreateContextBySymKey(mechanism,
+ (ss->sec.isServer ? CKA_DECRYPT : CKA_ENCRYPT),
+ pwSpec->client.write_key, param);
+ iv.data = PK11_IVFromParam(mechanism, param, (int *)&iv.len);
+ if (iv.data)
+ PORT_Memcpy(pwSpec->client.write_iv, iv.data, iv.len);
+ SECITEM_FreeItem(param,PR_TRUE);
+ if (clientContext == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SYM_KEY_CONTEXT_FAILURE);
+ goto fail;
+ }
+ pwSpec->encode = (SSLCipher) PK11_CipherOp;
+ pwSpec->decode = (SSLCipher) PK11_CipherOp;
+ pwSpec->destroy = (SSLDestroy) PK11_DestroyContext;
+
+ pwSpec->encodeContext = (ss->sec.isServer) ? serverContext : clientContext;
+ pwSpec->decodeContext = (ss->sec.isServer) ? clientContext : serverContext;
+
+ serverContext = NULL;
+ clientContext = NULL;
+
+ ssl3_InitCompressionContext(pwSpec);
+
+ return SECSuccess;
+
+fail:
+ if (serverContext != NULL) PK11_DestroyContext(serverContext, PR_TRUE);
+ if (clientContext != NULL) PK11_DestroyContext(clientContext, PR_TRUE);
+ if (pwSpec->client.write_mac_context != NULL) {
+ PK11_DestroyContext(pwSpec->client.write_mac_context,PR_TRUE);
+ pwSpec->client.write_mac_context = NULL;
+ }
+ if (pwSpec->server.write_mac_context != NULL) {
+ PK11_DestroyContext(pwSpec->server.write_mac_context,PR_TRUE);
+ pwSpec->server.write_mac_context = NULL;
+ }
+
+ return SECFailure;
+}
+
+/* Complete the initialization of all keys, ciphers, MACs and their contexts
+ * for the pending Cipher Spec.
+ * Called from: ssl3_SendClientKeyExchange (for Full handshake)
+ * ssl3_HandleRSAClientKeyExchange (for Full handshake)
+ * ssl3_HandleServerHello (for session restart)
+ * ssl3_HandleClientHello (for session restart)
+ * Sets error code, but caller probably should override to disambiguate.
+ * NULL pms means re-use old master_secret.
+ *
+ * This code is common to the bypass and PKCS11 execution paths.
+ * For the bypass case, pms is NULL.
+ */
+SECStatus
+ssl3_InitPendingCipherSpec(sslSocket *ss, PK11SymKey *pms)
+{
+ ssl3CipherSpec * pwSpec;
+ ssl3CipherSpec * cwSpec;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ ssl_GetSpecWriteLock(ss); /**************************************/
+
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+
+ pwSpec = ss->ssl3.pwSpec;
+ cwSpec = ss->ssl3.cwSpec;
+
+ if (pms || (!pwSpec->msItem.len && !pwSpec->master_secret)) {
+ rv = ssl3_DeriveMasterSecret(ss, pms);
+ if (rv != SECSuccess) {
+ goto done; /* err code set by ssl3_DeriveMasterSecret */
+ }
+ }
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11 && pwSpec->msItem.len && pwSpec->msItem.data) {
+ /* Double Bypass succeeded in extracting the master_secret */
+ const ssl3KEADef * kea_def = ss->ssl3.hs.kea_def;
+ PRBool isTLS = (PRBool)(kea_def->tls_keygen ||
+ (pwSpec->version > SSL_LIBRARY_VERSION_3_0));
+ pwSpec->bypassCiphers = PR_TRUE;
+ rv = ssl3_KeyAndMacDeriveBypass( pwSpec,
+ (const unsigned char *)&ss->ssl3.hs.client_random,
+ (const unsigned char *)&ss->ssl3.hs.server_random,
+ isTLS,
+ (PRBool)(kea_def->is_limited));
+ if (rv == SECSuccess) {
+ rv = ssl3_InitPendingContextsBypass(ss);
+ }
+ } else
+#endif
+ if (pwSpec->master_secret) {
+ rv = ssl3_DeriveConnectionKeysPKCS11(ss);
+ if (rv == SECSuccess) {
+ rv = ssl3_InitPendingContextsPKCS11(ss);
+ }
+ } else {
+ PORT_Assert(pwSpec->master_secret);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+ }
+ if (rv != SECSuccess) {
+ goto done;
+ }
+
+ /* Generic behaviors -- common to all crypto methods */
+ if (!IS_DTLS(ss)) {
+ pwSpec->read_seq_num.high = pwSpec->write_seq_num.high = 0;
+ } else {
+ if (cwSpec->epoch == PR_UINT16_MAX) {
+ /* The problem here is that we have rehandshaked too many
+ * times (you are not allowed to wrap the epoch). The
+ * spec says you should be discarding the connection
+ * and start over, so not much we can do here. */
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+ goto done;
+ }
+ /* The sequence number has the high 16 bits as the epoch. */
+ pwSpec->epoch = cwSpec->epoch + 1;
+ pwSpec->read_seq_num.high = pwSpec->write_seq_num.high =
+ pwSpec->epoch << 16;
+
+ dtls_InitRecvdRecords(&pwSpec->recvdRecords);
+ }
+ pwSpec->read_seq_num.low = pwSpec->write_seq_num.low = 0;
+
+done:
+ ssl_ReleaseSpecWriteLock(ss); /******************************/
+ if (rv != SECSuccess)
+ ssl_MapLowLevelError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ return rv;
+}
+
+/*
+ * 60 bytes is 3 times the maximum length MAC size that is supported.
+ */
+static const unsigned char mac_pad_1 [60] = {
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
+ 0x36, 0x36, 0x36, 0x36
+};
+static const unsigned char mac_pad_2 [60] = {
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
+ 0x5c, 0x5c, 0x5c, 0x5c
+};
+
+/* Called from: ssl3_SendRecord()
+** Caller must already hold the SpecReadLock. (wish we could assert that!)
+*/
+static SECStatus
+ssl3_ComputeRecordMAC(
+ ssl3CipherSpec * spec,
+ PRBool useServerMacKey,
+ PRBool isDTLS,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seq_num,
+ const SSL3Opaque * input,
+ int inputLength,
+ unsigned char * outbuf,
+ unsigned int * outLength)
+{
+ const ssl3MACDef * mac_def;
+ SECStatus rv;
+ PRBool isTLS;
+ unsigned int tempLen;
+ unsigned char temp[MAX_MAC_LENGTH];
+
+ /* TLS MAC includes the record's version field, SSL's doesn't.
+ ** We decide which MAC defintiion to use based on the version of
+ ** the protocol that was negotiated when the spec became current,
+ ** NOT based on the version value in the record itself.
+ ** But, we use the record's version value in the computation.
+ */
+ isTLS = spec->version > SSL_LIBRARY_VERSION_3_0;
+ tempLen = ssl3_BuildRecordPseudoHeader(temp, seq_num, type, isTLS,
+ version, isDTLS, inputLength);
+ PORT_Assert(tempLen <= sizeof(temp));
+
+ PRINT_BUF(95, (NULL, "frag hash1: temp", temp, tempLen));
+ PRINT_BUF(95, (NULL, "frag hash1: input", input, inputLength));
+
+ mac_def = spec->mac_def;
+ if (mac_def->mac == mac_null) {
+ *outLength = 0;
+ return SECSuccess;
+ }
+#ifndef NO_PKCS11_BYPASS
+ if (spec->bypassCiphers) {
+ /* bypass version */
+ const SECHashObject *hashObj = NULL;
+ unsigned int pad_bytes = 0;
+ PRUint64 write_mac_context[MAX_MAC_CONTEXT_LLONGS];
+
+ switch (mac_def->mac) {
+ case ssl_mac_null:
+ *outLength = 0;
+ return SECSuccess;
+ case ssl_mac_md5:
+ pad_bytes = 48;
+ hashObj = HASH_GetRawHashObject(HASH_AlgMD5);
+ break;
+ case ssl_mac_sha:
+ pad_bytes = 40;
+ hashObj = HASH_GetRawHashObject(HASH_AlgSHA1);
+ break;
+ case ssl_hmac_md5: /* used with TLS */
+ hashObj = HASH_GetRawHashObject(HASH_AlgMD5);
+ break;
+ case ssl_hmac_sha: /* used with TLS */
+ hashObj = HASH_GetRawHashObject(HASH_AlgSHA1);
+ break;
+ case ssl_hmac_sha256: /* used with TLS */
+ hashObj = HASH_GetRawHashObject(HASH_AlgSHA256);
+ break;
+ default:
+ break;
+ }
+ if (!hashObj) {
+ PORT_Assert(0);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ if (!isTLS) {
+ /* compute "inner" part of SSL3 MAC */
+ hashObj->begin(write_mac_context);
+ if (useServerMacKey)
+ hashObj->update(write_mac_context,
+ spec->server.write_mac_key_item.data,
+ spec->server.write_mac_key_item.len);
+ else
+ hashObj->update(write_mac_context,
+ spec->client.write_mac_key_item.data,
+ spec->client.write_mac_key_item.len);
+ hashObj->update(write_mac_context, mac_pad_1, pad_bytes);
+ hashObj->update(write_mac_context, temp, tempLen);
+ hashObj->update(write_mac_context, input, inputLength);
+ hashObj->end(write_mac_context, temp, &tempLen, sizeof temp);
+
+ /* compute "outer" part of SSL3 MAC */
+ hashObj->begin(write_mac_context);
+ if (useServerMacKey)
+ hashObj->update(write_mac_context,
+ spec->server.write_mac_key_item.data,
+ spec->server.write_mac_key_item.len);
+ else
+ hashObj->update(write_mac_context,
+ spec->client.write_mac_key_item.data,
+ spec->client.write_mac_key_item.len);
+ hashObj->update(write_mac_context, mac_pad_2, pad_bytes);
+ hashObj->update(write_mac_context, temp, tempLen);
+ hashObj->end(write_mac_context, outbuf, outLength, spec->mac_size);
+ rv = SECSuccess;
+ } else { /* is TLS */
+#define cx ((HMACContext *)write_mac_context)
+ if (useServerMacKey) {
+ rv = HMAC_Init(cx, hashObj,
+ spec->server.write_mac_key_item.data,
+ spec->server.write_mac_key_item.len, PR_FALSE);
+ } else {
+ rv = HMAC_Init(cx, hashObj,
+ spec->client.write_mac_key_item.data,
+ spec->client.write_mac_key_item.len, PR_FALSE);
+ }
+ if (rv == SECSuccess) {
+ HMAC_Begin(cx);
+ HMAC_Update(cx, temp, tempLen);
+ HMAC_Update(cx, input, inputLength);
+ rv = HMAC_Finish(cx, outbuf, outLength, spec->mac_size);
+ HMAC_Destroy(cx, PR_FALSE);
+ }
+#undef cx
+ }
+ } else
+#endif
+ {
+ PK11Context *mac_context =
+ (useServerMacKey ? spec->server.write_mac_context
+ : spec->client.write_mac_context);
+ rv = PK11_DigestBegin(mac_context);
+ rv |= PK11_DigestOp(mac_context, temp, tempLen);
+ rv |= PK11_DigestOp(mac_context, input, inputLength);
+ rv |= PK11_DigestFinal(mac_context, outbuf, outLength, spec->mac_size);
+ }
+
+ PORT_Assert(rv != SECSuccess || *outLength == (unsigned)spec->mac_size);
+
+ PRINT_BUF(95, (NULL, "frag hash2: result", outbuf, *outLength));
+
+ if (rv != SECSuccess) {
+ rv = SECFailure;
+ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+ }
+ return rv;
+}
+
+/* This is a bodge to allow this code to be compiled against older NSS headers
+ * that don't contain the CBC constant-time changes. */
+#ifndef CKM_NSS_HMAC_CONSTANT_TIME
+#define CKM_NSS_HMAC_CONSTANT_TIME (CKM_NSS + 19)
+#define CKM_NSS_SSL3_MAC_CONSTANT_TIME (CKM_NSS + 20)
+
+typedef struct CK_NSS_MAC_CONSTANT_TIME_PARAMS {
+ CK_MECHANISM_TYPE macAlg; /* in */
+ CK_ULONG ulBodyTotalLen; /* in */
+ CK_BYTE * pHeader; /* in */
+ CK_ULONG ulHeaderLen; /* in */
+} CK_NSS_MAC_CONSTANT_TIME_PARAMS;
+#endif
+
+/* Called from: ssl3_HandleRecord()
+ * Caller must already hold the SpecReadLock. (wish we could assert that!)
+ *
+ * On entry:
+ * originalLen >= inputLen >= MAC size
+*/
+static SECStatus
+ssl3_ComputeRecordMACConstantTime(
+ ssl3CipherSpec * spec,
+ PRBool useServerMacKey,
+ PRBool isDTLS,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seq_num,
+ const SSL3Opaque * input,
+ int inputLen,
+ int originalLen,
+ unsigned char * outbuf,
+ unsigned int * outLen)
+{
+ CK_MECHANISM_TYPE macType;
+ CK_NSS_MAC_CONSTANT_TIME_PARAMS params;
+ PK11Context * mac_context;
+ SECItem param;
+ SECStatus rv;
+ unsigned char header[13];
+ PK11SymKey * key;
+ int recordLength;
+
+ PORT_Assert(inputLen >= spec->mac_size);
+ PORT_Assert(originalLen >= inputLen);
+
+ if (spec->bypassCiphers) {
+ /* This function doesn't support PKCS#11 bypass. We fallback on the
+ * non-constant time version. */
+ goto fallback;
+ }
+
+ if (spec->mac_def->mac == mac_null) {
+ *outLen = 0;
+ return SECSuccess;
+ }
+
+ header[0] = (unsigned char)(seq_num.high >> 24);
+ header[1] = (unsigned char)(seq_num.high >> 16);
+ header[2] = (unsigned char)(seq_num.high >> 8);
+ header[3] = (unsigned char)(seq_num.high >> 0);
+ header[4] = (unsigned char)(seq_num.low >> 24);
+ header[5] = (unsigned char)(seq_num.low >> 16);
+ header[6] = (unsigned char)(seq_num.low >> 8);
+ header[7] = (unsigned char)(seq_num.low >> 0);
+ header[8] = type;
+
+ macType = CKM_NSS_HMAC_CONSTANT_TIME;
+ recordLength = inputLen - spec->mac_size;
+ if (spec->version <= SSL_LIBRARY_VERSION_3_0) {
+ macType = CKM_NSS_SSL3_MAC_CONSTANT_TIME;
+ header[9] = recordLength >> 8;
+ header[10] = recordLength;
+ params.ulHeaderLen = 11;
+ } else {
+ if (isDTLS) {
+ SSL3ProtocolVersion dtls_version;
+
+ dtls_version = dtls_TLSVersionToDTLSVersion(version);
+ header[9] = dtls_version >> 8;
+ header[10] = dtls_version;
+ } else {
+ header[9] = version >> 8;
+ header[10] = version;
+ }
+ header[11] = recordLength >> 8;
+ header[12] = recordLength;
+ params.ulHeaderLen = 13;
+ }
+
+ params.macAlg = spec->mac_def->mmech;
+ params.ulBodyTotalLen = originalLen;
+ params.pHeader = header;
+
+ param.data = (unsigned char*) &params;
+ param.len = sizeof(params);
+ param.type = 0;
+
+ key = spec->server.write_mac_key;
+ if (!useServerMacKey) {
+ key = spec->client.write_mac_key;
+ }
+ mac_context = PK11_CreateContextBySymKey(macType, CKA_SIGN, key, &param);
+ if (mac_context == NULL) {
+ /* Older versions of NSS may not support constant-time MAC. */
+ goto fallback;
+ }
+
+ rv = PK11_DigestBegin(mac_context);
+ rv |= PK11_DigestOp(mac_context, input, inputLen);
+ rv |= PK11_DigestFinal(mac_context, outbuf, outLen, spec->mac_size);
+ PK11_DestroyContext(mac_context, PR_TRUE);
+
+ PORT_Assert(rv != SECSuccess || *outLen == (unsigned)spec->mac_size);
+
+ if (rv != SECSuccess) {
+ rv = SECFailure;
+ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+ }
+ return rv;
+
+fallback:
+ /* ssl3_ComputeRecordMAC expects the MAC to have been removed from the
+ * length already. */
+ inputLen -= spec->mac_size;
+ return ssl3_ComputeRecordMAC(spec, useServerMacKey, isDTLS, type,
+ version, seq_num, input, inputLen,
+ outbuf, outLen);
+}
+
+static PRBool
+ssl3_ClientAuthTokenPresent(sslSessionID *sid) {
+ PK11SlotInfo *slot = NULL;
+ PRBool isPresent = PR_TRUE;
+
+ /* we only care if we are doing client auth */
+ /* If NSS_PLATFORM_CLIENT_AUTH is defined and a platformClientKey is being
+ * used, u.ssl3.clAuthValid will be false and this function will always
+ * return PR_TRUE. */
+ if (!sid || !sid->u.ssl3.clAuthValid) {
+ return PR_TRUE;
+ }
+
+ /* get the slot */
+ slot = SECMOD_LookupSlot(sid->u.ssl3.clAuthModuleID,
+ sid->u.ssl3.clAuthSlotID);
+ if (slot == NULL ||
+ !PK11_IsPresent(slot) ||
+ sid->u.ssl3.clAuthSeries != PK11_GetSlotSeries(slot) ||
+ sid->u.ssl3.clAuthSlotID != PK11_GetSlotID(slot) ||
+ sid->u.ssl3.clAuthModuleID != PK11_GetModuleID(slot) ||
+ (PK11_NeedLogin(slot) && !PK11_IsLoggedIn(slot, NULL))) {
+ isPresent = PR_FALSE;
+ }
+ if (slot) {
+ PK11_FreeSlot(slot);
+ }
+ return isPresent;
+}
+
+/* Caller must hold the spec read lock. */
+SECStatus
+ssl3_CompressMACEncryptRecord(ssl3CipherSpec * cwSpec,
+ PRBool isServer,
+ PRBool isDTLS,
+ PRBool capRecordVersion,
+ SSL3ContentType type,
+ const SSL3Opaque * pIn,
+ PRUint32 contentLen,
+ sslBuffer * wrBuf)
+{
+ const ssl3BulkCipherDef * cipher_def;
+ SECStatus rv;
+ PRUint32 macLen = 0;
+ PRUint32 fragLen;
+ PRUint32 p1Len, p2Len, oddLen = 0;
+ PRUint16 headerLen;
+ int ivLen = 0;
+ int cipherBytes = 0;
+
+ cipher_def = cwSpec->cipher_def;
+ headerLen = isDTLS ? DTLS_RECORD_HEADER_LENGTH : SSL3_RECORD_HEADER_LENGTH;
+
+ if (cipher_def->type == type_block &&
+ cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* Prepend the per-record explicit IV using technique 2b from
+ * RFC 4346 section 6.2.3.2: The IV is a cryptographically
+ * strong random number XORed with the CBC residue from the previous
+ * record.
+ */
+ ivLen = cipher_def->iv_size;
+ if (ivLen > wrBuf->space - headerLen) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ rv = PK11_GenerateRandom(wrBuf->buf + headerLen, ivLen);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
+ return rv;
+ }
+ rv = cwSpec->encode( cwSpec->encodeContext,
+ wrBuf->buf + headerLen,
+ &cipherBytes, /* output and actual outLen */
+ ivLen, /* max outlen */
+ wrBuf->buf + headerLen,
+ ivLen); /* input and inputLen*/
+ if (rv != SECSuccess || cipherBytes != ivLen) {
+ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
+ return SECFailure;
+ }
+ }
+
+ if (cwSpec->compressor) {
+ int outlen;
+ rv = cwSpec->compressor(
+ cwSpec->compressContext,
+ wrBuf->buf + headerLen + ivLen, &outlen,
+ wrBuf->space - headerLen - ivLen, pIn, contentLen);
+ if (rv != SECSuccess)
+ return rv;
+ pIn = wrBuf->buf + headerLen + ivLen;
+ contentLen = outlen;
+ }
+
+ if (cipher_def->type == type_aead) {
+ const int nonceLen = cipher_def->explicit_nonce_size;
+ const int tagLen = cipher_def->tag_size;
+
+ if (headerLen + nonceLen + contentLen + tagLen > wrBuf->space) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ cipherBytes = contentLen;
+ rv = cwSpec->aead(
+ isServer ? &cwSpec->server : &cwSpec->client,
+ PR_FALSE, /* do encrypt */
+ wrBuf->buf + headerLen, /* output */
+ &cipherBytes, /* out len */
+ wrBuf->space - headerLen, /* max out */
+ pIn, contentLen, /* input */
+ type, cwSpec->version, cwSpec->write_seq_num);
+ if (rv != SECSuccess) {
+ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
+ return SECFailure;
+ }
+ } else {
+ /*
+ * Add the MAC
+ */
+ rv = ssl3_ComputeRecordMAC( cwSpec, isServer, isDTLS,
+ type, cwSpec->version, cwSpec->write_seq_num, pIn, contentLen,
+ wrBuf->buf + headerLen + ivLen + contentLen, &macLen);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MAC_COMPUTATION_FAILURE);
+ return SECFailure;
+ }
+ p1Len = contentLen;
+ p2Len = macLen;
+ fragLen = contentLen + macLen; /* needs to be encrypted */
+ PORT_Assert(fragLen <= MAX_FRAGMENT_LENGTH + 1024);
+
+ /*
+ * Pad the text (if we're doing a block cipher)
+ * then Encrypt it
+ */
+ if (cipher_def->type == type_block) {
+ unsigned char * pBuf;
+ int padding_length;
+ int i;
+
+ oddLen = contentLen % cipher_def->block_size;
+ /* Assume blockSize is a power of two */
+ padding_length = cipher_def->block_size - 1 -
+ ((fragLen) & (cipher_def->block_size - 1));
+ fragLen += padding_length + 1;
+ PORT_Assert((fragLen % cipher_def->block_size) == 0);
+
+ /* Pad according to TLS rules (also acceptable to SSL3). */
+ pBuf = &wrBuf->buf[headerLen + ivLen + fragLen - 1];
+ for (i = padding_length + 1; i > 0; --i) {
+ *pBuf-- = padding_length;
+ }
+ /* now, if contentLen is not a multiple of block size, fix it */
+ p2Len = fragLen - p1Len;
+ }
+ if (p1Len < 256) {
+ oddLen = p1Len;
+ p1Len = 0;
+ } else {
+ p1Len -= oddLen;
+ }
+ if (oddLen) {
+ p2Len += oddLen;
+ PORT_Assert( (cipher_def->block_size < 2) || \
+ (p2Len % cipher_def->block_size) == 0);
+ memmove(wrBuf->buf + headerLen + ivLen + p1Len, pIn + p1Len,
+ oddLen);
+ }
+ if (p1Len > 0) {
+ int cipherBytesPart1 = -1;
+ rv = cwSpec->encode( cwSpec->encodeContext,
+ wrBuf->buf + headerLen + ivLen, /* output */
+ &cipherBytesPart1, /* actual outlen */
+ p1Len, /* max outlen */
+ pIn, p1Len); /* input, and inputlen */
+ PORT_Assert(rv == SECSuccess && cipherBytesPart1 == (int) p1Len);
+ if (rv != SECSuccess || cipherBytesPart1 != (int) p1Len) {
+ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
+ return SECFailure;
+ }
+ cipherBytes += cipherBytesPart1;
+ }
+ if (p2Len > 0) {
+ int cipherBytesPart2 = -1;
+ rv = cwSpec->encode( cwSpec->encodeContext,
+ wrBuf->buf + headerLen + ivLen + p1Len,
+ &cipherBytesPart2, /* output and actual outLen */
+ p2Len, /* max outlen */
+ wrBuf->buf + headerLen + ivLen + p1Len,
+ p2Len); /* input and inputLen*/
+ PORT_Assert(rv == SECSuccess && cipherBytesPart2 == (int) p2Len);
+ if (rv != SECSuccess || cipherBytesPart2 != (int) p2Len) {
+ PORT_SetError(SSL_ERROR_ENCRYPTION_FAILURE);
+ return SECFailure;
+ }
+ cipherBytes += cipherBytesPart2;
+ }
+ }
+
+ PORT_Assert(cipherBytes <= MAX_FRAGMENT_LENGTH + 1024);
+
+ wrBuf->len = cipherBytes + headerLen;
+ wrBuf->buf[0] = type;
+ if (isDTLS) {
+ SSL3ProtocolVersion version;
+
+ version = dtls_TLSVersionToDTLSVersion(cwSpec->version);
+ wrBuf->buf[1] = MSB(version);
+ wrBuf->buf[2] = LSB(version);
+ wrBuf->buf[3] = (unsigned char)(cwSpec->write_seq_num.high >> 24);
+ wrBuf->buf[4] = (unsigned char)(cwSpec->write_seq_num.high >> 16);
+ wrBuf->buf[5] = (unsigned char)(cwSpec->write_seq_num.high >> 8);
+ wrBuf->buf[6] = (unsigned char)(cwSpec->write_seq_num.high >> 0);
+ wrBuf->buf[7] = (unsigned char)(cwSpec->write_seq_num.low >> 24);
+ wrBuf->buf[8] = (unsigned char)(cwSpec->write_seq_num.low >> 16);
+ wrBuf->buf[9] = (unsigned char)(cwSpec->write_seq_num.low >> 8);
+ wrBuf->buf[10] = (unsigned char)(cwSpec->write_seq_num.low >> 0);
+ wrBuf->buf[11] = MSB(cipherBytes);
+ wrBuf->buf[12] = LSB(cipherBytes);
+ } else {
+ SSL3ProtocolVersion version = cwSpec->version;
+
+ if (capRecordVersion) {
+ version = PR_MIN(SSL_LIBRARY_VERSION_TLS_1_0, version);
+ }
+ wrBuf->buf[1] = MSB(version);
+ wrBuf->buf[2] = LSB(version);
+ wrBuf->buf[3] = MSB(cipherBytes);
+ wrBuf->buf[4] = LSB(cipherBytes);
+ }
+
+ ssl3_BumpSequenceNumber(&cwSpec->write_seq_num);
+
+ return SECSuccess;
+}
+
+/* Process the plain text before sending it.
+ * Returns the number of bytes of plaintext that were successfully sent
+ * plus the number of bytes of plaintext that were copied into the
+ * output (write) buffer.
+ * Returns SECFailure on a hard IO error, memory error, or crypto error.
+ * Does NOT return SECWouldBlock.
+ *
+ * Notes on the use of the private ssl flags:
+ * (no private SSL flags)
+ * Attempt to make and send SSL records for all plaintext
+ * If non-blocking and a send gets WOULD_BLOCK,
+ * or if the pending (ciphertext) buffer is not empty,
+ * then buffer remaining bytes of ciphertext into pending buf,
+ * and continue to do that for all succssive records until all
+ * bytes are used.
+ * ssl_SEND_FLAG_FORCE_INTO_BUFFER
+ * As above, except this suppresses all write attempts, and forces
+ * all ciphertext into the pending ciphertext buffer.
+ * ssl_SEND_FLAG_USE_EPOCH (for DTLS)
+ * Forces the use of the provided epoch
+ * ssl_SEND_FLAG_CAP_RECORD_VERSION
+ * Caps the record layer version number of TLS ClientHello to { 3, 1 }
+ * (TLS 1.0). Some TLS 1.0 servers (which seem to use F5 BIG-IP) ignore
+ * ClientHello.client_version and use the record layer version number
+ * (TLSPlaintext.version) instead when negotiating protocol versions. In
+ * addition, if the record layer version number of ClientHello is { 3, 2 }
+ * (TLS 1.1) or higher, these servers reset the TCP connections. Set this
+ * flag to work around such servers.
+ */
+PRInt32
+ssl3_SendRecord( sslSocket * ss,
+ DTLSEpoch epoch, /* DTLS only */
+ SSL3ContentType type,
+ const SSL3Opaque * pIn, /* input buffer */
+ PRInt32 nIn, /* bytes of input */
+ PRInt32 flags)
+{
+ sslBuffer * wrBuf = &ss->sec.writeBuf;
+ SECStatus rv;
+ PRInt32 totalSent = 0;
+ PRBool capRecordVersion;
+
+ SSL_TRC(3, ("%d: SSL3[%d] SendRecord type: %s nIn=%d",
+ SSL_GETPID(), ss->fd, ssl3_DecodeContentType(type),
+ nIn));
+ PRINT_BUF(3, (ss, "Send record (plain text)", pIn, nIn));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ capRecordVersion = ((flags & ssl_SEND_FLAG_CAP_RECORD_VERSION) != 0);
+
+ if (capRecordVersion) {
+ /* ssl_SEND_FLAG_CAP_RECORD_VERSION can only be used with the
+ * TLS initial ClientHello. */
+ PORT_Assert(!IS_DTLS(ss));
+ PORT_Assert(!ss->firstHsDone);
+ PORT_Assert(type == content_handshake);
+ PORT_Assert(ss->ssl3.hs.ws == wait_server_hello);
+ }
+
+ if (ss->ssl3.initialized == PR_FALSE) {
+ /* This can happen on a server if the very first incoming record
+ ** looks like a defective ssl3 record (e.g. too long), and we're
+ ** trying to send an alert.
+ */
+ PR_ASSERT(type == content_alert);
+ rv = ssl3_InitState(ss);
+ if (rv != SECSuccess) {
+ return SECFailure; /* ssl3_InitState has set the error code. */
+ }
+ }
+
+ /* check for Token Presence */
+ if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
+ PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+ return SECFailure;
+ }
+
+ while (nIn > 0) {
+ PRUint32 contentLen = PR_MIN(nIn, MAX_FRAGMENT_LENGTH);
+ unsigned int spaceNeeded;
+ unsigned int numRecords;
+
+ ssl_GetSpecReadLock(ss); /********************************/
+
+ if (nIn > 1 && ss->opt.cbcRandomIV &&
+ ss->ssl3.cwSpec->version < SSL_LIBRARY_VERSION_TLS_1_1 &&
+ type == content_application_data &&
+ ss->ssl3.cwSpec->cipher_def->type == type_block /* CBC mode */) {
+ /* We will split the first byte of the record into its own record,
+ * as explained in the documentation for SSL_CBC_RANDOM_IV in ssl.h
+ */
+ numRecords = 2;
+ } else {
+ numRecords = 1;
+ }
+
+ spaceNeeded = contentLen + (numRecords * SSL3_BUFFER_FUDGE);
+ if (ss->ssl3.cwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1 &&
+ ss->ssl3.cwSpec->cipher_def->type == type_block) {
+ spaceNeeded += ss->ssl3.cwSpec->cipher_def->iv_size;
+ }
+ if (spaceNeeded > wrBuf->space) {
+ rv = sslBuffer_Grow(wrBuf, spaceNeeded);
+ if (rv != SECSuccess) {
+ SSL_DBG(("%d: SSL3[%d]: SendRecord, tried to get %d bytes",
+ SSL_GETPID(), ss->fd, spaceNeeded));
+ goto spec_locked_loser; /* sslBuffer_Grow set error code. */
+ }
+ }
+
+ if (numRecords == 2) {
+ sslBuffer secondRecord;
+
+ rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+ ss->sec.isServer, IS_DTLS(ss),
+ capRecordVersion, type, pIn,
+ 1, wrBuf);
+ if (rv != SECSuccess)
+ goto spec_locked_loser;
+
+ PRINT_BUF(50, (ss, "send (encrypted) record data [1/2]:",
+ wrBuf->buf, wrBuf->len));
+
+ secondRecord.buf = wrBuf->buf + wrBuf->len;
+ secondRecord.len = 0;
+ secondRecord.space = wrBuf->space - wrBuf->len;
+
+ rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+ ss->sec.isServer, IS_DTLS(ss),
+ capRecordVersion, type,
+ pIn + 1, contentLen - 1,
+ &secondRecord);
+ if (rv == SECSuccess) {
+ PRINT_BUF(50, (ss, "send (encrypted) record data [2/2]:",
+ secondRecord.buf, secondRecord.len));
+ wrBuf->len += secondRecord.len;
+ }
+ } else {
+ if (!IS_DTLS(ss)) {
+ rv = ssl3_CompressMACEncryptRecord(ss->ssl3.cwSpec,
+ ss->sec.isServer,
+ IS_DTLS(ss),
+ capRecordVersion,
+ type, pIn,
+ contentLen, wrBuf);
+ } else {
+ rv = dtls_CompressMACEncryptRecord(ss, epoch,
+ !!(flags & ssl_SEND_FLAG_USE_EPOCH),
+ type, pIn,
+ contentLen, wrBuf);
+ }
+
+ if (rv == SECSuccess) {
+ PRINT_BUF(50, (ss, "send (encrypted) record data:",
+ wrBuf->buf, wrBuf->len));
+ }
+ }
+
+spec_locked_loser:
+ ssl_ReleaseSpecReadLock(ss); /************************************/
+
+ if (rv != SECSuccess)
+ return SECFailure;
+
+ pIn += contentLen;
+ nIn -= contentLen;
+ PORT_Assert( nIn >= 0 );
+
+ /* If there's still some previously saved ciphertext,
+ * or the caller doesn't want us to send the data yet,
+ * then add all our new ciphertext to the amount previously saved.
+ */
+ if ((ss->pendingBuf.len > 0) ||
+ (flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
+
+ rv = ssl_SaveWriteData(ss, wrBuf->buf, wrBuf->len);
+ if (rv != SECSuccess) {
+ /* presumably a memory error, SEC_ERROR_NO_MEMORY */
+ return SECFailure;
+ }
+ wrBuf->len = 0; /* All cipher text is saved away. */
+
+ if (!(flags & ssl_SEND_FLAG_FORCE_INTO_BUFFER)) {
+ PRInt32 sent;
+ ss->handshakeBegun = 1;
+ sent = ssl_SendSavedWriteData(ss);
+ if (sent < 0 && PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ ssl_MapLowLevelError(SSL_ERROR_SOCKET_WRITE_FAILURE);
+ return SECFailure;
+ }
+ if (ss->pendingBuf.len) {
+ flags |= ssl_SEND_FLAG_FORCE_INTO_BUFFER;
+ }
+ }
+ } else if (wrBuf->len > 0) {
+ PRInt32 sent;
+ ss->handshakeBegun = 1;
+ sent = ssl_DefSend(ss, wrBuf->buf, wrBuf->len,
+ flags & ~ssl_SEND_FLAG_MASK);
+ if (sent < 0) {
+ if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
+ ssl_MapLowLevelError(SSL_ERROR_SOCKET_WRITE_FAILURE);
+ return SECFailure;
+ }
+ /* we got PR_WOULD_BLOCK_ERROR, which means none was sent. */
+ sent = 0;
+ }
+ wrBuf->len -= sent;
+ if (wrBuf->len) {
+ if (IS_DTLS(ss)) {
+ /* DTLS just says no in this case. No buffering */
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return SECFailure;
+ }
+ /* now take all the remaining unsent new ciphertext and
+ * append it to the buffer of previously unsent ciphertext.
+ */
+ rv = ssl_SaveWriteData(ss, wrBuf->buf + sent, wrBuf->len);
+ if (rv != SECSuccess) {
+ /* presumably a memory error, SEC_ERROR_NO_MEMORY */
+ return SECFailure;
+ }
+ }
+ }
+ totalSent += contentLen;
+ }
+ return totalSent;
+}
+
+#define SSL3_PENDING_HIGH_WATER 1024
+
+/* Attempt to send the content of "in" in an SSL application_data record.
+ * Returns "len" or SECFailure, never SECWouldBlock, nor SECSuccess.
+ */
+int
+ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in,
+ PRInt32 len, PRInt32 flags)
+{
+ PRInt32 totalSent = 0;
+ PRInt32 discarded = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+ /* These flags for internal use only */
+ PORT_Assert(!(flags & (ssl_SEND_FLAG_USE_EPOCH |
+ ssl_SEND_FLAG_NO_RETRANSMIT)));
+ if (len < 0 || !in) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+
+ if (ss->pendingBuf.len > SSL3_PENDING_HIGH_WATER &&
+ !ssl_SocketIsBlocking(ss)) {
+ PORT_Assert(!ssl_SocketIsBlocking(ss));
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ return SECFailure;
+ }
+
+ if (ss->appDataBuffered && len) {
+ PORT_Assert (in[0] == (unsigned char)(ss->appDataBuffered));
+ if (in[0] != (unsigned char)(ss->appDataBuffered)) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ in++;
+ len--;
+ discarded = 1;
+ }
+ while (len > totalSent) {
+ PRInt32 sent, toSend;
+
+ if (totalSent > 0) {
+ /*
+ * The thread yield is intended to give the reader thread a
+ * chance to get some cycles while the writer thread is in
+ * the middle of a large application data write. (See
+ * Bugzilla bug 127740, comment #1.)
+ */
+ ssl_ReleaseXmitBufLock(ss);
+ PR_Sleep(PR_INTERVAL_NO_WAIT); /* PR_Yield(); */
+ ssl_GetXmitBufLock(ss);
+ }
+ toSend = PR_MIN(len - totalSent, MAX_FRAGMENT_LENGTH);
+ /*
+ * Note that the 0 epoch is OK because flags will never require
+ * its use, as guaranteed by the PORT_Assert above.
+ */
+ sent = ssl3_SendRecord(ss, 0, content_application_data,
+ in + totalSent, toSend, flags);
+ if (sent < 0) {
+ if (totalSent > 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR) {
+ PORT_Assert(ss->lastWriteBlocked);
+ break;
+ }
+ return SECFailure; /* error code set by ssl3_SendRecord */
+ }
+ totalSent += sent;
+ if (ss->pendingBuf.len) {
+ /* must be a non-blocking socket */
+ PORT_Assert(!ssl_SocketIsBlocking(ss));
+ PORT_Assert(ss->lastWriteBlocked);
+ break;
+ }
+ }
+ if (ss->pendingBuf.len) {
+ /* Must be non-blocking. */
+ PORT_Assert(!ssl_SocketIsBlocking(ss));
+ if (totalSent > 0) {
+ ss->appDataBuffered = 0x100 | in[totalSent - 1];
+ }
+
+ totalSent = totalSent + discarded - 1;
+ if (totalSent <= 0) {
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ totalSent = SECFailure;
+ }
+ return totalSent;
+ }
+ ss->appDataBuffered = 0;
+ return totalSent + discarded;
+}
+
+/* Attempt to send buffered handshake messages.
+ * This function returns SECSuccess or SECFailure, never SECWouldBlock.
+ * Always set sendBuf.len to 0, even when returning SECFailure.
+ *
+ * Depending on whether we are doing DTLS or not, this either calls
+ *
+ * - ssl3_FlushHandshakeMessages if non-DTLS
+ * - dtls_FlushHandshakeMessages if DTLS
+ *
+ * Called from SSL3_SendAlert(), ssl3_SendChangeCipherSpecs(),
+ * ssl3_AppendHandshake(), ssl3_SendClientHello(),
+ * ssl3_SendHelloRequest(), ssl3_SendServerHelloDone(),
+ * ssl3_SendFinished(),
+ */
+static SECStatus
+ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags)
+{
+ if (IS_DTLS(ss)) {
+ return dtls_FlushHandshakeMessages(ss, flags);
+ } else {
+ return ssl3_FlushHandshakeMessages(ss, flags);
+ }
+}
+
+/* Attempt to send the content of sendBuf buffer in an SSL handshake record.
+ * This function returns SECSuccess or SECFailure, never SECWouldBlock.
+ * Always set sendBuf.len to 0, even when returning SECFailure.
+ *
+ * Called from ssl3_FlushHandshake
+ */
+static SECStatus
+ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags)
+{
+ static const PRInt32 allowedFlags = ssl_SEND_FLAG_FORCE_INTO_BUFFER |
+ ssl_SEND_FLAG_CAP_RECORD_VERSION;
+ PRInt32 rv = SECSuccess;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ if (!ss->sec.ci.sendBuf.buf || !ss->sec.ci.sendBuf.len)
+ return rv;
+
+ /* only these flags are allowed */
+ PORT_Assert(!(flags & ~allowedFlags));
+ if ((flags & ~allowedFlags) != 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ } else {
+ rv = ssl3_SendRecord(ss, 0, content_handshake, ss->sec.ci.sendBuf.buf,
+ ss->sec.ci.sendBuf.len, flags);
+ }
+ if (rv < 0) {
+ int err = PORT_GetError();
+ PORT_Assert(err != PR_WOULD_BLOCK_ERROR);
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ }
+ } else if (rv < ss->sec.ci.sendBuf.len) {
+ /* short write should never happen */
+ PORT_Assert(rv >= ss->sec.ci.sendBuf.len);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+ } else {
+ rv = SECSuccess;
+ }
+
+ /* Whether we succeeded or failed, toss the old handshake data. */
+ ss->sec.ci.sendBuf.len = 0;
+ return rv;
+}
+
+/*
+ * Called from ssl3_HandleAlert and from ssl3_HandleCertificate when
+ * the remote client sends a negative response to our certificate request.
+ * Returns SECFailure if the application has required client auth.
+ * SECSuccess otherwise.
+ */
+static SECStatus
+ssl3_HandleNoCertificate(sslSocket *ss)
+{
+ if (ss->sec.peerCert != NULL) {
+ if (ss->sec.peerKey != NULL) {
+ SECKEY_DestroyPublicKey(ss->sec.peerKey);
+ ss->sec.peerKey = NULL;
+ }
+ CERT_DestroyCertificate(ss->sec.peerCert);
+ ss->sec.peerCert = NULL;
+ }
+ ssl3_CleanupPeerCerts(ss);
+
+ /* If the server has required client-auth blindly but doesn't
+ * actually look at the certificate it won't know that no
+ * certificate was presented so we shutdown the socket to ensure
+ * an error. We only do this if we haven't already completed the
+ * first handshake because if we're redoing the handshake we
+ * know the server is paying attention to the certificate.
+ */
+ if ((ss->opt.requireCertificate == SSL_REQUIRE_ALWAYS) ||
+ (!ss->firstHsDone &&
+ (ss->opt.requireCertificate == SSL_REQUIRE_FIRST_HANDSHAKE))) {
+ PRFileDesc * lower;
+
+ if (ss->sec.uncache)
+ ss->sec.uncache(ss->sec.ci.sid);
+ SSL3_SendAlert(ss, alert_fatal, bad_certificate);
+
+ lower = ss->fd->lower;
+#ifdef _WIN32
+ lower->methods->shutdown(lower, PR_SHUTDOWN_SEND);
+#else
+ lower->methods->shutdown(lower, PR_SHUTDOWN_BOTH);
+#endif
+ PORT_SetError(SSL_ERROR_NO_CERTIFICATE);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+/************************************************************************
+ * Alerts
+ */
+
+/*
+** Acquires both handshake and XmitBuf locks.
+** Called from: ssl3_IllegalParameter <-
+** ssl3_HandshakeFailure <-
+** ssl3_HandleAlert <- ssl3_HandleRecord.
+** ssl3_HandleChangeCipherSpecs <- ssl3_HandleRecord
+** ssl3_ConsumeHandshakeVariable <-
+** ssl3_HandleHelloRequest <-
+** ssl3_HandleServerHello <-
+** ssl3_HandleServerKeyExchange <-
+** ssl3_HandleCertificateRequest <-
+** ssl3_HandleServerHelloDone <-
+** ssl3_HandleClientHello <-
+** ssl3_HandleV2ClientHello <-
+** ssl3_HandleCertificateVerify <-
+** ssl3_HandleClientKeyExchange <-
+** ssl3_HandleCertificate <-
+** ssl3_HandleFinished <-
+** ssl3_HandleHandshakeMessage <-
+** ssl3_HandleRecord <-
+**
+*/
+SECStatus
+SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level, SSL3AlertDescription desc)
+{
+ PRUint8 bytes[2];
+ SECStatus rv;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send alert record, level=%d desc=%d",
+ SSL_GETPID(), ss->fd, level, desc));
+
+ bytes[0] = level;
+ bytes[1] = desc;
+
+ ssl_GetSSL3HandshakeLock(ss);
+ if (level == alert_fatal) {
+ if (!ss->opt.noCache && ss->sec.ci.sid && ss->sec.uncache) {
+ ss->sec.uncache(ss->sec.ci.sid);
+ }
+ }
+ ssl_GetXmitBufLock(ss);
+ rv = ssl3_FlushHandshake(ss, ssl_SEND_FLAG_FORCE_INTO_BUFFER);
+ if (rv == SECSuccess) {
+ PRInt32 sent;
+ sent = ssl3_SendRecord(ss, 0, content_alert, bytes, 2,
+ desc == no_certificate
+ ? ssl_SEND_FLAG_FORCE_INTO_BUFFER : 0);
+ rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
+ }
+ ssl_ReleaseXmitBufLock(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return rv; /* error set by ssl3_FlushHandshake or ssl3_SendRecord */
+}
+
+/*
+ * Send illegal_parameter alert. Set generic error number.
+ */
+static SECStatus
+ssl3_IllegalParameter(sslSocket *ss)
+{
+ (void)SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
+ PORT_SetError(ss->sec.isServer ? SSL_ERROR_BAD_CLIENT
+ : SSL_ERROR_BAD_SERVER );
+ return SECFailure;
+}
+
+/*
+ * Send handshake_Failure alert. Set generic error number.
+ */
+static SECStatus
+ssl3_HandshakeFailure(sslSocket *ss)
+{
+ (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure);
+ PORT_SetError( ss->sec.isServer ? SSL_ERROR_BAD_CLIENT
+ : SSL_ERROR_BAD_SERVER );
+ return SECFailure;
+}
+
+static void
+ssl3_SendAlertForCertError(sslSocket * ss, PRErrorCode errCode)
+{
+ SSL3AlertDescription desc = bad_certificate;
+ PRBool isTLS = ss->version >= SSL_LIBRARY_VERSION_3_1_TLS;
+
+ switch (errCode) {
+ case SEC_ERROR_LIBRARY_FAILURE: desc = unsupported_certificate; break;
+ case SEC_ERROR_EXPIRED_CERTIFICATE: desc = certificate_expired; break;
+ case SEC_ERROR_REVOKED_CERTIFICATE: desc = certificate_revoked; break;
+ case SEC_ERROR_INADEQUATE_KEY_USAGE:
+ case SEC_ERROR_INADEQUATE_CERT_TYPE:
+ desc = certificate_unknown; break;
+ case SEC_ERROR_UNTRUSTED_CERT:
+ desc = isTLS ? access_denied : certificate_unknown; break;
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ desc = isTLS ? unknown_ca : certificate_unknown; break;
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ desc = isTLS ? unknown_ca : certificate_expired; break;
+
+ case SEC_ERROR_CERT_NOT_IN_NAME_SPACE:
+ case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_BAD_SIGNATURE:
+ default: desc = bad_certificate; break;
+ }
+ SSL_DBG(("%d: SSL3[%d]: peer certificate is no good: error=%d",
+ SSL_GETPID(), ss->fd, errCode));
+
+ (void) SSL3_SendAlert(ss, alert_fatal, desc);
+}
+
+
+/*
+ * Send decode_error alert. Set generic error number.
+ */
+SECStatus
+ssl3_DecodeError(sslSocket *ss)
+{
+ (void)SSL3_SendAlert(ss, alert_fatal,
+ ss->version > SSL_LIBRARY_VERSION_3_0 ? decode_error
+ : illegal_parameter);
+ PORT_SetError( ss->sec.isServer ? SSL_ERROR_BAD_CLIENT
+ : SSL_ERROR_BAD_SERVER );
+ return SECFailure;
+}
+
+/* Called from ssl3_HandleRecord.
+** Caller must hold both RecvBuf and Handshake locks.
+*/
+static SECStatus
+ssl3_HandleAlert(sslSocket *ss, sslBuffer *buf)
+{
+ SSL3AlertLevel level;
+ SSL3AlertDescription desc;
+ int error;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle alert record", SSL_GETPID(), ss->fd));
+
+ if (buf->len != 2) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ALERT);
+ return SECFailure;
+ }
+ level = (SSL3AlertLevel)buf->buf[0];
+ desc = (SSL3AlertDescription)buf->buf[1];
+ buf->len = 0;
+ SSL_TRC(5, ("%d: SSL3[%d] received alert, level = %d, description = %d",
+ SSL_GETPID(), ss->fd, level, desc));
+
+ switch (desc) {
+ case close_notify: ss->recvdCloseNotify = 1;
+ error = SSL_ERROR_CLOSE_NOTIFY_ALERT; break;
+ case unexpected_message: error = SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT;
+ break;
+ case bad_record_mac: error = SSL_ERROR_BAD_MAC_ALERT; break;
+ case decryption_failed_RESERVED:
+ error = SSL_ERROR_DECRYPTION_FAILED_ALERT;
+ break;
+ case record_overflow: error = SSL_ERROR_RECORD_OVERFLOW_ALERT; break;
+ case decompression_failure: error = SSL_ERROR_DECOMPRESSION_FAILURE_ALERT;
+ break;
+ case handshake_failure: error = SSL_ERROR_HANDSHAKE_FAILURE_ALERT;
+ break;
+ case no_certificate: error = SSL_ERROR_NO_CERTIFICATE; break;
+ case bad_certificate: error = SSL_ERROR_BAD_CERT_ALERT; break;
+ case unsupported_certificate:error = SSL_ERROR_UNSUPPORTED_CERT_ALERT;break;
+ case certificate_revoked: error = SSL_ERROR_REVOKED_CERT_ALERT; break;
+ case certificate_expired: error = SSL_ERROR_EXPIRED_CERT_ALERT; break;
+ case certificate_unknown: error = SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT;
+ break;
+ case illegal_parameter: error = SSL_ERROR_ILLEGAL_PARAMETER_ALERT;break;
+
+ /* All alerts below are TLS only. */
+ case unknown_ca: error = SSL_ERROR_UNKNOWN_CA_ALERT; break;
+ case access_denied: error = SSL_ERROR_ACCESS_DENIED_ALERT; break;
+ case decode_error: error = SSL_ERROR_DECODE_ERROR_ALERT; break;
+ case decrypt_error: error = SSL_ERROR_DECRYPT_ERROR_ALERT; break;
+ case export_restriction: error = SSL_ERROR_EXPORT_RESTRICTION_ALERT;
+ break;
+ case protocol_version: error = SSL_ERROR_PROTOCOL_VERSION_ALERT; break;
+ case insufficient_security: error = SSL_ERROR_INSUFFICIENT_SECURITY_ALERT;
+ break;
+ case internal_error: error = SSL_ERROR_INTERNAL_ERROR_ALERT; break;
+ case user_canceled: error = SSL_ERROR_USER_CANCELED_ALERT; break;
+ case no_renegotiation: error = SSL_ERROR_NO_RENEGOTIATION_ALERT; break;
+
+ /* Alerts for TLS client hello extensions */
+ case unsupported_extension:
+ error = SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT; break;
+ case certificate_unobtainable:
+ error = SSL_ERROR_CERTIFICATE_UNOBTAINABLE_ALERT; break;
+ case unrecognized_name:
+ error = SSL_ERROR_UNRECOGNIZED_NAME_ALERT; break;
+ case bad_certificate_status_response:
+ error = SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT; break;
+ case bad_certificate_hash_value:
+ error = SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT; break;
+ default: error = SSL_ERROR_RX_UNKNOWN_ALERT; break;
+ }
+ if (level == alert_fatal) {
+ if (!ss->opt.noCache) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(ss->sec.ci.sid);
+ }
+ if ((ss->ssl3.hs.ws == wait_server_hello) &&
+ (desc == handshake_failure)) {
+ /* XXX This is a hack. We're assuming that any handshake failure
+ * XXX on the client hello is a failure to match ciphers.
+ */
+ error = SSL_ERROR_NO_CYPHER_OVERLAP;
+ }
+ PORT_SetError(error);
+ return SECFailure;
+ }
+ if ((desc == no_certificate) && (ss->ssl3.hs.ws == wait_client_cert)) {
+ /* I'm a server. I've requested a client cert. He hasn't got one. */
+ SECStatus rv;
+
+ PORT_Assert(ss->sec.isServer);
+ ss->ssl3.hs.ws = wait_client_key;
+ rv = ssl3_HandleNoCertificate(ss);
+ return rv;
+ }
+ return SECSuccess;
+}
+
+/*
+ * Change Cipher Specs
+ * Called from ssl3_HandleServerHelloDone,
+ * ssl3_HandleClientHello,
+ * and ssl3_HandleFinished
+ *
+ * Acquires and releases spec write lock, to protect switching the current
+ * and pending write spec pointers.
+ */
+
+static SECStatus
+ssl3_SendChangeCipherSpecs(sslSocket *ss)
+{
+ PRUint8 change = change_cipher_spec_choice;
+ ssl3CipherSpec * pwSpec;
+ SECStatus rv;
+ PRInt32 sent;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send change_cipher_spec record",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ rv = ssl3_FlushHandshake(ss, ssl_SEND_FLAG_FORCE_INTO_BUFFER);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by ssl3_FlushHandshake */
+ }
+ if (!IS_DTLS(ss)) {
+ sent = ssl3_SendRecord(ss, 0, content_change_cipher_spec, &change, 1,
+ ssl_SEND_FLAG_FORCE_INTO_BUFFER);
+ if (sent < 0) {
+ return (SECStatus)sent; /* error code set by ssl3_SendRecord */
+ }
+ } else {
+ rv = dtls_QueueMessage(ss, content_change_cipher_spec, &change, 1);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+
+ /* swap the pending and current write specs. */
+ ssl_GetSpecWriteLock(ss); /**************************************/
+ pwSpec = ss->ssl3.pwSpec;
+
+ ss->ssl3.pwSpec = ss->ssl3.cwSpec;
+ ss->ssl3.cwSpec = pwSpec;
+
+ SSL_TRC(3, ("%d: SSL3[%d] Set Current Write Cipher Suite to Pending",
+ SSL_GETPID(), ss->fd ));
+
+ /* We need to free up the contexts, keys and certs ! */
+ /* If we are really through with the old cipher spec
+ * (Both the read and write sides have changed) destroy it.
+ */
+ if (ss->ssl3.prSpec == ss->ssl3.pwSpec) {
+ if (!IS_DTLS(ss)) {
+ ssl3_DestroyCipherSpec(ss->ssl3.pwSpec, PR_FALSE/*freeSrvName*/);
+ } else {
+ /* With DTLS, we need to set a holddown timer in case the final
+ * message got lost */
+ ss->ssl3.hs.rtTimeoutMs = DTLS_FINISHED_TIMER_MS;
+ dtls_StartTimer(ss, dtls_FinishedTimerCb);
+ }
+ }
+ ssl_ReleaseSpecWriteLock(ss); /**************************************/
+
+ return SECSuccess;
+}
+
+/* Called from ssl3_HandleRecord.
+** Caller must hold both RecvBuf and Handshake locks.
+ *
+ * Acquires and releases spec write lock, to protect switching the current
+ * and pending write spec pointers.
+*/
+static SECStatus
+ssl3_HandleChangeCipherSpecs(sslSocket *ss, sslBuffer *buf)
+{
+ ssl3CipherSpec * prSpec;
+ SSL3WaitState ws = ss->ssl3.hs.ws;
+ SSL3ChangeCipherSpecChoice change;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle change_cipher_spec record",
+ SSL_GETPID(), ss->fd));
+
+ if (ws != wait_change_cipher) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER);
+ return SECFailure;
+ }
+
+ if(buf->len != 1) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+ return SECFailure;
+ }
+ change = (SSL3ChangeCipherSpecChoice)buf->buf[0];
+ if (change != change_cipher_spec_choice) {
+ /* illegal_parameter is correct here for both SSL3 and TLS. */
+ (void)ssl3_IllegalParameter(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER);
+ return SECFailure;
+ }
+ buf->len = 0;
+
+ /* Swap the pending and current read specs. */
+ ssl_GetSpecWriteLock(ss); /*************************************/
+ prSpec = ss->ssl3.prSpec;
+
+ ss->ssl3.prSpec = ss->ssl3.crSpec;
+ ss->ssl3.crSpec = prSpec;
+ ss->ssl3.hs.ws = wait_finished;
+
+ SSL_TRC(3, ("%d: SSL3[%d] Set Current Read Cipher Suite to Pending",
+ SSL_GETPID(), ss->fd ));
+
+ /* If we are really through with the old cipher prSpec
+ * (Both the read and write sides have changed) destroy it.
+ */
+ if (ss->ssl3.prSpec == ss->ssl3.pwSpec) {
+ ssl3_DestroyCipherSpec(ss->ssl3.prSpec, PR_FALSE/*freeSrvName*/);
+ }
+ ssl_ReleaseSpecWriteLock(ss); /*************************************/
+ return SECSuccess;
+}
+
+/* This method uses PKCS11 to derive the MS from the PMS, where PMS
+** is a PKCS11 symkey. This is used in all cases except the
+** "triple bypass" with RSA key exchange.
+** Called from ssl3_InitPendingCipherSpec. prSpec is pwSpec.
+*/
+static SECStatus
+ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms)
+{
+ ssl3CipherSpec * pwSpec = ss->ssl3.pwSpec;
+ const ssl3KEADef *kea_def= ss->ssl3.hs.kea_def;
+ unsigned char * cr = (unsigned char *)&ss->ssl3.hs.client_random;
+ unsigned char * sr = (unsigned char *)&ss->ssl3.hs.server_random;
+ PRBool isTLS = (PRBool)(kea_def->tls_keygen ||
+ (pwSpec->version > SSL_LIBRARY_VERSION_3_0));
+ PRBool isTLS12=
+ (PRBool)(isTLS && pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+ /*
+ * Whenever isDH is true, we need to use CKM_TLS_MASTER_KEY_DERIVE_DH
+ * which, unlike CKM_TLS_MASTER_KEY_DERIVE, converts arbitrary size
+ * data into a 48-byte value.
+ */
+ PRBool isDH = (PRBool) ((ss->ssl3.hs.kea_def->exchKeyType == kt_dh) ||
+ (ss->ssl3.hs.kea_def->exchKeyType == kt_ecdh));
+ SECStatus rv = SECFailure;
+ CK_MECHANISM_TYPE master_derive;
+ CK_MECHANISM_TYPE key_derive;
+ SECItem params;
+ CK_FLAGS keyFlags;
+ CK_VERSION pms_version;
+ CK_SSL3_MASTER_KEY_DERIVE_PARAMS master_params;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+ if (isTLS12) {
+ if(isDH) master_derive = CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256;
+ else master_derive = CKM_NSS_TLS_MASTER_KEY_DERIVE_SHA256;
+ key_derive = CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256;
+ keyFlags = CKF_SIGN | CKF_VERIFY;
+ } else if (isTLS) {
+ if(isDH) master_derive = CKM_TLS_MASTER_KEY_DERIVE_DH;
+ else master_derive = CKM_TLS_MASTER_KEY_DERIVE;
+ key_derive = CKM_TLS_KEY_AND_MAC_DERIVE;
+ keyFlags = CKF_SIGN | CKF_VERIFY;
+ } else {
+ if (isDH) master_derive = CKM_SSL3_MASTER_KEY_DERIVE_DH;
+ else master_derive = CKM_SSL3_MASTER_KEY_DERIVE;
+ key_derive = CKM_SSL3_KEY_AND_MAC_DERIVE;
+ keyFlags = 0;
+ }
+
+ if (pms || !pwSpec->master_secret) {
+ if (isDH) {
+ master_params.pVersion = NULL;
+ } else {
+ master_params.pVersion = &pms_version;
+ }
+ master_params.RandomInfo.pClientRandom = cr;
+ master_params.RandomInfo.ulClientRandomLen = SSL3_RANDOM_LENGTH;
+ master_params.RandomInfo.pServerRandom = sr;
+ master_params.RandomInfo.ulServerRandomLen = SSL3_RANDOM_LENGTH;
+
+ params.data = (unsigned char *) &master_params;
+ params.len = sizeof master_params;
+ }
+
+ if (pms != NULL) {
+#if defined(TRACE)
+ if (ssl_trace >= 100) {
+ SECStatus extractRV = PK11_ExtractKeyValue(pms);
+ if (extractRV == SECSuccess) {
+ SECItem * keyData = PK11_GetKeyData(pms);
+ if (keyData && keyData->data && keyData->len) {
+ ssl_PrintBuf(ss, "Pre-Master Secret",
+ keyData->data, keyData->len);
+ }
+ }
+ }
+#endif
+ pwSpec->master_secret = PK11_DeriveWithFlags(pms, master_derive,
+ &params, key_derive, CKA_DERIVE, 0, keyFlags);
+ if (!isDH && pwSpec->master_secret && ss->opt.detectRollBack) {
+ SSL3ProtocolVersion client_version;
+ client_version = pms_version.major << 8 | pms_version.minor;
+
+ if (IS_DTLS(ss)) {
+ client_version = dtls_DTLSVersionToTLSVersion(client_version);
+ }
+
+ if (client_version != ss->clientHelloVersion) {
+ /* Destroy it. Version roll-back detected. */
+ PK11_FreeSymKey(pwSpec->master_secret);
+ pwSpec->master_secret = NULL;
+ }
+ }
+ if (pwSpec->master_secret == NULL) {
+ /* Generate a faux master secret in the same slot as the old one. */
+ PK11SlotInfo * slot = PK11_GetSlotFromKey((PK11SymKey *)pms);
+ PK11SymKey * fpms = ssl3_GenerateRSAPMS(ss, pwSpec, slot);
+
+ PK11_FreeSlot(slot);
+ if (fpms != NULL) {
+ pwSpec->master_secret = PK11_DeriveWithFlags(fpms,
+ master_derive, &params, key_derive,
+ CKA_DERIVE, 0, keyFlags);
+ PK11_FreeSymKey(fpms);
+ }
+ }
+ }
+ if (pwSpec->master_secret == NULL) {
+ /* Generate a faux master secret from the internal slot. */
+ PK11SlotInfo * slot = PK11_GetInternalSlot();
+ PK11SymKey * fpms = ssl3_GenerateRSAPMS(ss, pwSpec, slot);
+
+ PK11_FreeSlot(slot);
+ if (fpms != NULL) {
+ pwSpec->master_secret = PK11_DeriveWithFlags(fpms,
+ master_derive, &params, key_derive,
+ CKA_DERIVE, 0, keyFlags);
+ if (pwSpec->master_secret == NULL) {
+ pwSpec->master_secret = fpms; /* use the fpms as the master. */
+ fpms = NULL;
+ }
+ }
+ if (fpms) {
+ PK11_FreeSymKey(fpms);
+ }
+ }
+ if (pwSpec->master_secret == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ return rv;
+ }
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ SECItem * keydata;
+ /* In hope of doing a "double bypass",
+ * need to extract the master secret's value from the key object
+ * and store it raw in the sslSocket struct.
+ */
+ rv = PK11_ExtractKeyValue(pwSpec->master_secret);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ /* This returns the address of the secItem inside the key struct,
+ * not a copy or a reference. So, there's no need to free it.
+ */
+ keydata = PK11_GetKeyData(pwSpec->master_secret);
+ if (keydata && keydata->len <= sizeof pwSpec->raw_master_secret) {
+ memcpy(pwSpec->raw_master_secret, keydata->data, keydata->len);
+ pwSpec->msItem.data = pwSpec->raw_master_secret;
+ pwSpec->msItem.len = keydata->len;
+ } else {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ }
+#endif
+ return SECSuccess;
+}
+
+
+/*
+ * Derive encryption and MAC Keys (and IVs) from master secret
+ * Sets a useful error code when returning SECFailure.
+ *
+ * Called only from ssl3_InitPendingCipherSpec(),
+ * which in turn is called from
+ * sendRSAClientKeyExchange (for Full handshake)
+ * sendDHClientKeyExchange (for Full handshake)
+ * ssl3_HandleClientKeyExchange (for Full handshake)
+ * ssl3_HandleServerHello (for session restart)
+ * ssl3_HandleClientHello (for session restart)
+ * Caller MUST hold the specWriteLock, and SSL3HandshakeLock.
+ * ssl3_InitPendingCipherSpec does that.
+ *
+ */
+static SECStatus
+ssl3_DeriveConnectionKeysPKCS11(sslSocket *ss)
+{
+ ssl3CipherSpec * pwSpec = ss->ssl3.pwSpec;
+ const ssl3KEADef * kea_def = ss->ssl3.hs.kea_def;
+ unsigned char * cr = (unsigned char *)&ss->ssl3.hs.client_random;
+ unsigned char * sr = (unsigned char *)&ss->ssl3.hs.server_random;
+ PRBool isTLS = (PRBool)(kea_def->tls_keygen ||
+ (pwSpec->version > SSL_LIBRARY_VERSION_3_0));
+ PRBool isTLS12=
+ (PRBool)(isTLS && pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+ /* following variables used in PKCS11 path */
+ const ssl3BulkCipherDef *cipher_def = pwSpec->cipher_def;
+ PK11SlotInfo * slot = NULL;
+ PK11SymKey * symKey = NULL;
+ void * pwArg = ss->pkcs11PinArg;
+ int keySize;
+ CK_SSL3_KEY_MAT_PARAMS key_material_params;
+ CK_SSL3_KEY_MAT_OUT returnedKeys;
+ CK_MECHANISM_TYPE key_derive;
+ CK_MECHANISM_TYPE bulk_mechanism;
+ SSLCipherAlgorithm calg;
+ SECItem params;
+ PRBool skipKeysAndIVs = (PRBool)(cipher_def->calg == calg_null);
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+
+ if (!pwSpec->master_secret) {
+ PORT_SetError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ return SECFailure;
+ }
+ /*
+ * generate the key material
+ */
+ key_material_params.ulMacSizeInBits = pwSpec->mac_size * BPB;
+ key_material_params.ulKeySizeInBits = cipher_def->secret_key_size* BPB;
+ key_material_params.ulIVSizeInBits = cipher_def->iv_size * BPB;
+ if (cipher_def->type == type_block &&
+ pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* Block ciphers in >= TLS 1.1 use a per-record, explicit IV. */
+ key_material_params.ulIVSizeInBits = 0;
+ memset(pwSpec->client.write_iv, 0, cipher_def->iv_size);
+ memset(pwSpec->server.write_iv, 0, cipher_def->iv_size);
+ }
+
+ key_material_params.bIsExport = (CK_BBOOL)(kea_def->is_limited);
+
+ key_material_params.RandomInfo.pClientRandom = cr;
+ key_material_params.RandomInfo.ulClientRandomLen = SSL3_RANDOM_LENGTH;
+ key_material_params.RandomInfo.pServerRandom = sr;
+ key_material_params.RandomInfo.ulServerRandomLen = SSL3_RANDOM_LENGTH;
+ key_material_params.pReturnedKeyMaterial = &returnedKeys;
+
+ returnedKeys.pIVClient = pwSpec->client.write_iv;
+ returnedKeys.pIVServer = pwSpec->server.write_iv;
+ keySize = cipher_def->key_size;
+
+ if (skipKeysAndIVs) {
+ keySize = 0;
+ key_material_params.ulKeySizeInBits = 0;
+ key_material_params.ulIVSizeInBits = 0;
+ returnedKeys.pIVClient = NULL;
+ returnedKeys.pIVServer = NULL;
+ }
+
+ calg = cipher_def->calg;
+ PORT_Assert( alg2Mech[calg].calg == calg);
+ bulk_mechanism = alg2Mech[calg].cmech;
+
+ params.data = (unsigned char *)&key_material_params;
+ params.len = sizeof(key_material_params);
+
+ if (isTLS12) {
+ key_derive = CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256;
+ } else if (isTLS) {
+ key_derive = CKM_TLS_KEY_AND_MAC_DERIVE;
+ } else {
+ key_derive = CKM_SSL3_KEY_AND_MAC_DERIVE;
+ }
+
+ /* CKM_SSL3_KEY_AND_MAC_DERIVE is defined to set ENCRYPT, DECRYPT, and
+ * DERIVE by DEFAULT */
+ symKey = PK11_Derive(pwSpec->master_secret, key_derive, &params,
+ bulk_mechanism, CKA_ENCRYPT, keySize);
+ if (!symKey) {
+ ssl_MapLowLevelError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ return SECFailure;
+ }
+ /* we really should use the actual mac'ing mechanism here, but we
+ * don't because these types are used to map keytype anyway and both
+ * mac's map to the same keytype.
+ */
+ slot = PK11_GetSlotFromKey(symKey);
+
+ PK11_FreeSlot(slot); /* slot is held until the key is freed */
+ pwSpec->client.write_mac_key =
+ PK11_SymKeyFromHandle(slot, symKey, PK11_OriginDerive,
+ CKM_SSL3_SHA1_MAC, returnedKeys.hClientMacSecret, PR_TRUE, pwArg);
+ if (pwSpec->client.write_mac_key == NULL ) {
+ goto loser; /* loser sets err */
+ }
+ pwSpec->server.write_mac_key =
+ PK11_SymKeyFromHandle(slot, symKey, PK11_OriginDerive,
+ CKM_SSL3_SHA1_MAC, returnedKeys.hServerMacSecret, PR_TRUE, pwArg);
+ if (pwSpec->server.write_mac_key == NULL ) {
+ goto loser; /* loser sets err */
+ }
+ if (!skipKeysAndIVs) {
+ pwSpec->client.write_key =
+ PK11_SymKeyFromHandle(slot, symKey, PK11_OriginDerive,
+ bulk_mechanism, returnedKeys.hClientKey, PR_TRUE, pwArg);
+ if (pwSpec->client.write_key == NULL ) {
+ goto loser; /* loser sets err */
+ }
+ pwSpec->server.write_key =
+ PK11_SymKeyFromHandle(slot, symKey, PK11_OriginDerive,
+ bulk_mechanism, returnedKeys.hServerKey, PR_TRUE, pwArg);
+ if (pwSpec->server.write_key == NULL ) {
+ goto loser; /* loser sets err */
+ }
+ }
+ PK11_FreeSymKey(symKey);
+ return SECSuccess;
+
+
+loser:
+ if (symKey) PK11_FreeSymKey(symKey);
+ ssl_MapLowLevelError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
+ return SECFailure;
+}
+
+/* ssl3_InitHandshakeHashes creates handshake hash contexts and hashes in
+ * buffered messages in ss->ssl3.hs.messages. */
+static SECStatus
+ssl3_InitHandshakeHashes(sslSocket *ss)
+{
+ SSL_TRC(30,("%d: SSL3[%d]: start handshake hashes", SSL_GETPID(), ss->fd));
+
+ PORT_Assert(ss->ssl3.hs.hashType == handshake_hash_unknown);
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ PORT_Assert(!ss->ssl3.hs.sha_obj && !ss->ssl3.hs.sha_clone);
+ if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ /* If we ever support ciphersuites where the PRF hash isn't SHA-256
+ * then this will need to be updated. */
+ ss->ssl3.hs.sha_obj = HASH_GetRawHashObject(HASH_AlgSHA256);
+ if (!ss->ssl3.hs.sha_obj) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ ss->ssl3.hs.sha_clone = (void (*)(void *, void *))SHA256_Clone;
+ ss->ssl3.hs.hashType = handshake_hash_single;
+ ss->ssl3.hs.sha_obj->begin(ss->ssl3.hs.sha_cx);
+ } else {
+ ss->ssl3.hs.hashType = handshake_hash_combo;
+ MD5_Begin((MD5Context *)ss->ssl3.hs.md5_cx);
+ SHA1_Begin((SHA1Context *)ss->ssl3.hs.sha_cx);
+ }
+ } else
+#endif
+ {
+ PORT_Assert(!ss->ssl3.hs.md5 && !ss->ssl3.hs.sha);
+ /*
+ * note: We should probably lookup an SSL3 slot for these
+ * handshake hashes in hopes that we wind up with the same slots
+ * that the master secret will wind up in ...
+ */
+ if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ /* If we ever support ciphersuites where the PRF hash isn't SHA-256
+ * then this will need to be updated. */
+ ss->ssl3.hs.sha = PK11_CreateDigestContext(SEC_OID_SHA256);
+ if (ss->ssl3.hs.sha == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ ss->ssl3.hs.hashType = handshake_hash_single;
+
+ if (PK11_DigestBegin(ss->ssl3.hs.sha) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ } else {
+ /* Both ss->ssl3.hs.md5 and ss->ssl3.hs.sha should be NULL or
+ * created successfully. */
+ ss->ssl3.hs.md5 = PK11_CreateDigestContext(SEC_OID_MD5);
+ if (ss->ssl3.hs.md5 == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ ss->ssl3.hs.sha = PK11_CreateDigestContext(SEC_OID_SHA1);
+ if (ss->ssl3.hs.sha == NULL) {
+ PK11_DestroyContext(ss->ssl3.hs.md5, PR_TRUE);
+ ss->ssl3.hs.md5 = NULL;
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ ss->ssl3.hs.hashType = handshake_hash_combo;
+
+ if (PK11_DigestBegin(ss->ssl3.hs.md5) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ if (PK11_DigestBegin(ss->ssl3.hs.sha) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ }
+ }
+
+ if (ss->ssl3.hs.messages.len > 0) {
+ if (ssl3_UpdateHandshakeHashes(ss, ss->ssl3.hs.messages.buf,
+ ss->ssl3.hs.messages.len) !=
+ SECSuccess) {
+ return SECFailure;
+ }
+ PORT_Free(ss->ssl3.hs.messages.buf);
+ ss->ssl3.hs.messages.buf = NULL;
+ ss->ssl3.hs.messages.len = 0;
+ ss->ssl3.hs.messages.space = 0;
+ }
+
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_RestartHandshakeHashes(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+
+ SSL_TRC(30,("%d: SSL3[%d]: reset handshake hashes",
+ SSL_GETPID(), ss->fd ));
+ ss->ssl3.hs.hashType = handshake_hash_unknown;
+ ss->ssl3.hs.messages.len = 0;
+#ifndef NO_PKCS11_BYPASS
+ ss->ssl3.hs.sha_obj = NULL;
+ ss->ssl3.hs.sha_clone = NULL;
+#endif
+ if (ss->ssl3.hs.md5) {
+ PK11_DestroyContext(ss->ssl3.hs.md5,PR_TRUE);
+ ss->ssl3.hs.md5 = NULL;
+ }
+ if (ss->ssl3.hs.sha) {
+ PK11_DestroyContext(ss->ssl3.hs.sha,PR_TRUE);
+ ss->ssl3.hs.sha = NULL;
+ }
+ return rv;
+}
+
+/*
+ * Handshake messages
+ */
+/* Called from ssl3_InitHandshakeHashes()
+** ssl3_AppendHandshake()
+** ssl3_StartHandshakeHash()
+** ssl3_HandleV2ClientHello()
+** ssl3_HandleHandshakeMessage()
+** Caller must hold the ssl3Handshake lock.
+*/
+static SECStatus
+ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b,
+ unsigned int l)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ /* We need to buffer the handshake messages until we have established
+ * which handshake hash function to use. */
+ if (ss->ssl3.hs.hashType == handshake_hash_unknown) {
+ return sslBuffer_Append(&ss->ssl3.hs.messages, b, l);
+ }
+
+ PRINT_BUF(90, (NULL, "handshake hash input:", b, l));
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ if (ss->ssl3.hs.hashType == handshake_hash_single) {
+ ss->ssl3.hs.sha_obj->update(ss->ssl3.hs.sha_cx, b, l);
+ } else {
+ MD5_Update((MD5Context *)ss->ssl3.hs.md5_cx, b, l);
+ SHA1_Update((SHA1Context *)ss->ssl3.hs.sha_cx, b, l);
+ }
+ return rv;
+ }
+#endif
+ if (ss->ssl3.hs.hashType == handshake_hash_single) {
+ rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ return rv;
+ }
+ } else {
+ rv = PK11_DigestOp(ss->ssl3.hs.md5, b, l);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ return rv;
+ }
+ rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ return rv;
+ }
+ }
+ return rv;
+}
+
+/**************************************************************************
+ * Append Handshake functions.
+ * All these functions set appropriate error codes.
+ * Most rely on ssl3_AppendHandshake to set the error code.
+ **************************************************************************/
+SECStatus
+ssl3_AppendHandshake(sslSocket *ss, const void *void_src, PRInt32 bytes)
+{
+ unsigned char * src = (unsigned char *)void_src;
+ int room = ss->sec.ci.sendBuf.space - ss->sec.ci.sendBuf.len;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) ); /* protects sendBuf. */
+
+ if (!bytes)
+ return SECSuccess;
+ if (ss->sec.ci.sendBuf.space < MAX_SEND_BUF_LENGTH && room < bytes) {
+ rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, PR_MAX(MIN_SEND_BUF_LENGTH,
+ PR_MIN(MAX_SEND_BUF_LENGTH, ss->sec.ci.sendBuf.len + bytes)));
+ if (rv != SECSuccess)
+ return rv; /* sslBuffer_Grow has set a memory error code. */
+ room = ss->sec.ci.sendBuf.space - ss->sec.ci.sendBuf.len;
+ }
+
+ PRINT_BUF(60, (ss, "Append to Handshake", (unsigned char*)void_src, bytes));
+ rv = ssl3_UpdateHandshakeHashes(ss, src, bytes);
+ if (rv != SECSuccess)
+ return rv; /* error code set by ssl3_UpdateHandshakeHashes */
+
+ while (bytes > room) {
+ if (room > 0)
+ PORT_Memcpy(ss->sec.ci.sendBuf.buf + ss->sec.ci.sendBuf.len, src,
+ room);
+ ss->sec.ci.sendBuf.len += room;
+ rv = ssl3_FlushHandshake(ss, ssl_SEND_FLAG_FORCE_INTO_BUFFER);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by ssl3_FlushHandshake */
+ }
+ bytes -= room;
+ src += room;
+ room = ss->sec.ci.sendBuf.space;
+ PORT_Assert(ss->sec.ci.sendBuf.len == 0);
+ }
+ PORT_Memcpy(ss->sec.ci.sendBuf.buf + ss->sec.ci.sendBuf.len, src, bytes);
+ ss->sec.ci.sendBuf.len += bytes;
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_AppendHandshakeNumber(sslSocket *ss, PRInt32 num, PRInt32 lenSize)
+{
+ SECStatus rv;
+ PRUint8 b[4];
+ PRUint8 * p = b;
+
+ switch (lenSize) {
+ case 4:
+ *p++ = (num >> 24) & 0xff;
+ case 3:
+ *p++ = (num >> 16) & 0xff;
+ case 2:
+ *p++ = (num >> 8) & 0xff;
+ case 1:
+ *p = num & 0xff;
+ }
+ SSL_TRC(60, ("%d: number:", SSL_GETPID()));
+ rv = ssl3_AppendHandshake(ss, &b[0], lenSize);
+ return rv; /* error code set by AppendHandshake, if applicable. */
+}
+
+SECStatus
+ssl3_AppendHandshakeVariable(
+ sslSocket *ss, const SSL3Opaque *src, PRInt32 bytes, PRInt32 lenSize)
+{
+ SECStatus rv;
+
+ PORT_Assert((bytes < (1<<8) && lenSize == 1) ||
+ (bytes < (1L<<16) && lenSize == 2) ||
+ (bytes < (1L<<24) && lenSize == 3));
+
+ SSL_TRC(60,("%d: append variable:", SSL_GETPID()));
+ rv = ssl3_AppendHandshakeNumber(ss, bytes, lenSize);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+ SSL_TRC(60, ("data:"));
+ rv = ssl3_AppendHandshake(ss, src, bytes);
+ return rv; /* error code set by AppendHandshake, if applicable. */
+}
+
+SECStatus
+ssl3_AppendHandshakeHeader(sslSocket *ss, SSL3HandshakeType t, PRUint32 length)
+{
+ SECStatus rv;
+
+ /* If we already have a message in place, we need to enqueue it.
+ * This empties the buffer. This is a convenient place to call
+ * dtls_StageHandshakeMessage to mark the message boundary.
+ */
+ if (IS_DTLS(ss)) {
+ rv = dtls_StageHandshakeMessage(ss);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+
+ SSL_TRC(30,("%d: SSL3[%d]: append handshake header: type %s",
+ SSL_GETPID(), ss->fd, ssl3_DecodeHandshakeType(t)));
+
+ rv = ssl3_AppendHandshakeNumber(ss, t, 1);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, length, 3);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+
+ if (IS_DTLS(ss)) {
+ /* Note that we make an unfragmented message here. We fragment in the
+ * transmission code, if necessary */
+ rv = ssl3_AppendHandshakeNumber(ss, ss->ssl3.hs.sendMessageSeq, 2);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+ ss->ssl3.hs.sendMessageSeq++;
+
+ /* 0 is the fragment offset, because it's not fragmented yet */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 3);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+
+ /* Fragment length -- set to the packet length because not fragmented */
+ rv = ssl3_AppendHandshakeNumber(ss, length, 3);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake, if applicable. */
+ }
+ }
+
+ return rv; /* error code set by AppendHandshake, if applicable. */
+}
+
+/* ssl3_AppendSignatureAndHashAlgorithm appends the serialisation of
+ * |sigAndHash| to the current handshake message. */
+SECStatus
+ssl3_AppendSignatureAndHashAlgorithm(
+ sslSocket *ss, const SSL3SignatureAndHashAlgorithm* sigAndHash)
+{
+ unsigned char serialized[2];
+
+ serialized[0] = ssl3_OIDToTLSHashAlgorithm(sigAndHash->hashAlg);
+ if (serialized[0] == 0) {
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+
+ serialized[1] = sigAndHash->sigAlg;
+
+ return ssl3_AppendHandshake(ss, serialized, sizeof(serialized));
+}
+
+/**************************************************************************
+ * Consume Handshake functions.
+ *
+ * All data used in these functions is protected by two locks,
+ * the RecvBufLock and the SSL3HandshakeLock
+ **************************************************************************/
+
+/* Read up the next "bytes" number of bytes from the (decrypted) input
+ * stream "b" (which is *length bytes long). Copy them into buffer "v".
+ * Reduces *length by bytes. Advances *b by bytes.
+ *
+ * If this function returns SECFailure, it has already sent an alert,
+ * and has set a generic error code. The caller should probably
+ * override the generic error code by setting another.
+ */
+SECStatus
+ssl3_ConsumeHandshake(sslSocket *ss, void *v, PRInt32 bytes, SSL3Opaque **b,
+ PRUint32 *length)
+{
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if ((PRUint32)bytes > *length) {
+ return ssl3_DecodeError(ss);
+ }
+ PORT_Memcpy(v, *b, bytes);
+ PRINT_BUF(60, (ss, "consume bytes:", *b, bytes));
+ *b += bytes;
+ *length -= bytes;
+ return SECSuccess;
+}
+
+/* Read up the next "bytes" number of bytes from the (decrypted) input
+ * stream "b" (which is *length bytes long), and interpret them as an
+ * integer in network byte order. Returns the received value.
+ * Reduces *length by bytes. Advances *b by bytes.
+ *
+ * Returns SECFailure (-1) on failure.
+ * This value is indistinguishable from the equivalent received value.
+ * Only positive numbers are to be received this way.
+ * Thus, the largest value that may be sent this way is 0x7fffffff.
+ * On error, an alert has been sent, and a generic error code has been set.
+ */
+PRInt32
+ssl3_ConsumeHandshakeNumber(sslSocket *ss, PRInt32 bytes, SSL3Opaque **b,
+ PRUint32 *length)
+{
+ PRUint8 *buf = *b;
+ int i;
+ PRInt32 num = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( bytes <= sizeof num);
+
+ if ((PRUint32)bytes > *length) {
+ return ssl3_DecodeError(ss);
+ }
+ PRINT_BUF(60, (ss, "consume bytes:", *b, bytes));
+
+ for (i = 0; i < bytes; i++)
+ num = (num << 8) + buf[i];
+ *b += bytes;
+ *length -= bytes;
+ return num;
+}
+
+/* Read in two values from the incoming decrypted byte stream "b", which is
+ * *length bytes long. The first value is a number whose size is "bytes"
+ * bytes long. The second value is a byte-string whose size is the value
+ * of the first number received. The latter byte-string, and its length,
+ * is returned in the SECItem i.
+ *
+ * Returns SECFailure (-1) on failure.
+ * On error, an alert has been sent, and a generic error code has been set.
+ *
+ * RADICAL CHANGE for NSS 3.11. All callers of this function make copies
+ * of the data returned in the SECItem *i, so making a copy of it here
+ * is simply wasteful. So, This function now just sets SECItem *i to
+ * point to the values in the buffer **b.
+ */
+SECStatus
+ssl3_ConsumeHandshakeVariable(sslSocket *ss, SECItem *i, PRInt32 bytes,
+ SSL3Opaque **b, PRUint32 *length)
+{
+ PRInt32 count;
+
+ PORT_Assert(bytes <= 3);
+ i->len = 0;
+ i->data = NULL;
+ count = ssl3_ConsumeHandshakeNumber(ss, bytes, b, length);
+ if (count < 0) { /* Can't test for SECSuccess here. */
+ return SECFailure;
+ }
+ if (count > 0) {
+ if ((PRUint32)count > *length) {
+ return ssl3_DecodeError(ss);
+ }
+ i->data = *b;
+ i->len = count;
+ *b += count;
+ *length -= count;
+ }
+ return SECSuccess;
+}
+
+/* tlsHashOIDMap contains the mapping between TLS hash identifiers and the
+ * SECOidTag used internally by NSS. */
+static const struct {
+ int tlsHash;
+ SECOidTag oid;
+} tlsHashOIDMap[] = {
+ { tls_hash_md5, SEC_OID_MD5 },
+ { tls_hash_sha1, SEC_OID_SHA1 },
+ { tls_hash_sha224, SEC_OID_SHA224 },
+ { tls_hash_sha256, SEC_OID_SHA256 },
+ { tls_hash_sha384, SEC_OID_SHA384 },
+ { tls_hash_sha512, SEC_OID_SHA512 }
+};
+
+/* ssl3_TLSHashAlgorithmToOID converts a TLS hash identifier into an OID value.
+ * If the hash is not recognised, SEC_OID_UNKNOWN is returned.
+ *
+ * See https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+SECOidTag
+ssl3_TLSHashAlgorithmToOID(int hashFunc)
+{
+ unsigned int i;
+
+ for (i = 0; i < PR_ARRAY_SIZE(tlsHashOIDMap); i++) {
+ if (hashFunc == tlsHashOIDMap[i].tlsHash) {
+ return tlsHashOIDMap[i].oid;
+ }
+ }
+ return SEC_OID_UNKNOWN;
+}
+
+/* ssl3_OIDToTLSHashAlgorithm converts an OID to a TLS hash algorithm
+ * identifier. If the hash is not recognised, zero is returned.
+ *
+ * See https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+static int
+ssl3_OIDToTLSHashAlgorithm(SECOidTag oid)
+{
+ unsigned int i;
+
+ for (i = 0; i < PR_ARRAY_SIZE(tlsHashOIDMap); i++) {
+ if (oid == tlsHashOIDMap[i].oid) {
+ return tlsHashOIDMap[i].tlsHash;
+ }
+ }
+ return 0;
+}
+
+/* ssl3_TLSSignatureAlgorithmForKeyType returns the TLS 1.2 signature algorithm
+ * identifier for a given KeyType. */
+static SECStatus
+ssl3_TLSSignatureAlgorithmForKeyType(KeyType keyType,
+ TLSSignatureAlgorithm *out)
+{
+ switch (keyType) {
+ case rsaKey:
+ *out = tls_sig_rsa;
+ return SECSuccess;
+ case dsaKey:
+ *out = tls_sig_dsa;
+ return SECSuccess;
+ case ecKey:
+ *out = tls_sig_ecdsa;
+ return SECSuccess;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ return SECFailure;
+ }
+}
+
+/* ssl3_TLSSignatureAlgorithmForCertificate returns the TLS 1.2 signature
+ * algorithm identifier for the given certificate. */
+static SECStatus
+ssl3_TLSSignatureAlgorithmForCertificate(CERTCertificate *cert,
+ TLSSignatureAlgorithm *out)
+{
+ SECKEYPublicKey *key;
+ KeyType keyType;
+
+ key = CERT_ExtractPublicKey(cert);
+ if (key == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE);
+ return SECFailure;
+ }
+
+ keyType = key->keyType;
+ SECKEY_DestroyPublicKey(key);
+ return ssl3_TLSSignatureAlgorithmForKeyType(keyType, out);
+}
+
+/* ssl3_CheckSignatureAndHashAlgorithmConsistency checks that the signature
+ * algorithm identifier in |sigAndHash| is consistent with the public key in
+ * |cert|. If so, SECSuccess is returned. Otherwise, PORT_SetError is called
+ * and SECFailure is returned. */
+SECStatus
+ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ const SSL3SignatureAndHashAlgorithm *sigAndHash, CERTCertificate* cert)
+{
+ SECStatus rv;
+ TLSSignatureAlgorithm sigAlg;
+
+ rv = ssl3_TLSSignatureAlgorithmForCertificate(cert, &sigAlg);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ if (sigAlg != sigAndHash->sigAlg) {
+ PORT_SetError(SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+/* ssl3_ConsumeSignatureAndHashAlgorithm reads a SignatureAndHashAlgorithm
+ * structure from |b| and puts the resulting value into |out|. |b| and |length|
+ * are updated accordingly.
+ *
+ * See https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+SECStatus
+ssl3_ConsumeSignatureAndHashAlgorithm(sslSocket *ss,
+ SSL3Opaque **b,
+ PRUint32 *length,
+ SSL3SignatureAndHashAlgorithm *out)
+{
+ unsigned char bytes[2];
+ SECStatus rv;
+
+ rv = ssl3_ConsumeHandshake(ss, bytes, sizeof(bytes), b, length);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ out->hashAlg = ssl3_TLSHashAlgorithmToOID(bytes[0]);
+ if (out->hashAlg == SEC_OID_UNKNOWN) {
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+
+ out->sigAlg = bytes[1];
+ return SECSuccess;
+}
+
+/**************************************************************************
+ * end of Consume Handshake functions.
+ **************************************************************************/
+
+/* Extract the hashes of handshake messages to this point.
+ * Called from ssl3_SendCertificateVerify
+ * ssl3_SendFinished
+ * ssl3_HandleHandshakeMessage
+ *
+ * Caller must hold the SSL3HandshakeLock.
+ * Caller must hold a read or write lock on the Spec R/W lock.
+ * (There is presently no way to assert on a Read lock.)
+ */
+static SECStatus
+ssl3_ComputeHandshakeHashes(sslSocket * ss,
+ ssl3CipherSpec *spec, /* uses ->master_secret */
+ SSL3Hashes * hashes, /* output goes here. */
+ PRUint32 sender)
+{
+ SECStatus rv = SECSuccess;
+ PRBool isTLS = (PRBool)(spec->version > SSL_LIBRARY_VERSION_3_0);
+ unsigned int outLength;
+ SSL3Opaque md5_inner[MAX_MAC_LENGTH];
+ SSL3Opaque sha_inner[MAX_MAC_LENGTH];
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ hashes->hashAlg = SEC_OID_UNKNOWN;
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11 &&
+ ss->ssl3.hs.hashType == handshake_hash_single) {
+ /* compute them without PKCS11 */
+ PRUint64 sha_cx[MAX_MAC_CONTEXT_LLONGS];
+
+ if (!spec->msItem.data) {
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ ss->ssl3.hs.sha_clone(sha_cx, ss->ssl3.hs.sha_cx);
+ ss->ssl3.hs.sha_obj->end(sha_cx, hashes->u.raw, &hashes->len,
+ sizeof(hashes->u.raw));
+
+ PRINT_BUF(60, (NULL, "SHA-256: result", hashes->u.raw, hashes->len));
+
+ /* If we ever support ciphersuites where the PRF hash isn't SHA-256
+ * then this will need to be updated. */
+ hashes->hashAlg = SEC_OID_SHA256;
+ rv = SECSuccess;
+ } else if (ss->opt.bypassPKCS11) {
+ /* compute them without PKCS11 */
+ PRUint64 md5_cx[MAX_MAC_CONTEXT_LLONGS];
+ PRUint64 sha_cx[MAX_MAC_CONTEXT_LLONGS];
+
+#define md5cx ((MD5Context *)md5_cx)
+#define shacx ((SHA1Context *)sha_cx)
+
+ if (!spec->msItem.data) {
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ MD5_Clone (md5cx, (MD5Context *)ss->ssl3.hs.md5_cx);
+ SHA1_Clone(shacx, (SHA1Context *)ss->ssl3.hs.sha_cx);
+
+ if (!isTLS) {
+ /* compute hashes for SSL3. */
+ unsigned char s[4];
+
+ s[0] = (unsigned char)(sender >> 24);
+ s[1] = (unsigned char)(sender >> 16);
+ s[2] = (unsigned char)(sender >> 8);
+ s[3] = (unsigned char)sender;
+
+ if (sender != 0) {
+ MD5_Update(md5cx, s, 4);
+ PRINT_BUF(95, (NULL, "MD5 inner: sender", s, 4));
+ }
+
+ PRINT_BUF(95, (NULL, "MD5 inner: MAC Pad 1", mac_pad_1,
+ mac_defs[mac_md5].pad_size));
+
+ MD5_Update(md5cx, spec->msItem.data, spec->msItem.len);
+ MD5_Update(md5cx, mac_pad_1, mac_defs[mac_md5].pad_size);
+ MD5_End(md5cx, md5_inner, &outLength, MD5_LENGTH);
+
+ PRINT_BUF(95, (NULL, "MD5 inner: result", md5_inner, outLength));
+
+ if (sender != 0) {
+ SHA1_Update(shacx, s, 4);
+ PRINT_BUF(95, (NULL, "SHA inner: sender", s, 4));
+ }
+
+ PRINT_BUF(95, (NULL, "SHA inner: MAC Pad 1", mac_pad_1,
+ mac_defs[mac_sha].pad_size));
+
+ SHA1_Update(shacx, spec->msItem.data, spec->msItem.len);
+ SHA1_Update(shacx, mac_pad_1, mac_defs[mac_sha].pad_size);
+ SHA1_End(shacx, sha_inner, &outLength, SHA1_LENGTH);
+
+ PRINT_BUF(95, (NULL, "SHA inner: result", sha_inner, outLength));
+ PRINT_BUF(95, (NULL, "MD5 outer: MAC Pad 2", mac_pad_2,
+ mac_defs[mac_md5].pad_size));
+ PRINT_BUF(95, (NULL, "MD5 outer: MD5 inner", md5_inner, MD5_LENGTH));
+
+ MD5_Begin(md5cx);
+ MD5_Update(md5cx, spec->msItem.data, spec->msItem.len);
+ MD5_Update(md5cx, mac_pad_2, mac_defs[mac_md5].pad_size);
+ MD5_Update(md5cx, md5_inner, MD5_LENGTH);
+ }
+ MD5_End(md5cx, hashes->u.s.md5, &outLength, MD5_LENGTH);
+
+ PRINT_BUF(60, (NULL, "MD5 outer: result", hashes->u.s.md5, MD5_LENGTH));
+
+ if (!isTLS) {
+ PRINT_BUF(95, (NULL, "SHA outer: MAC Pad 2", mac_pad_2,
+ mac_defs[mac_sha].pad_size));
+ PRINT_BUF(95, (NULL, "SHA outer: SHA inner", sha_inner, SHA1_LENGTH));
+
+ SHA1_Begin(shacx);
+ SHA1_Update(shacx, spec->msItem.data, spec->msItem.len);
+ SHA1_Update(shacx, mac_pad_2, mac_defs[mac_sha].pad_size);
+ SHA1_Update(shacx, sha_inner, SHA1_LENGTH);
+ }
+ SHA1_End(shacx, hashes->u.s.sha, &outLength, SHA1_LENGTH);
+
+ PRINT_BUF(60, (NULL, "SHA outer: result", hashes->u.s.sha, SHA1_LENGTH));
+
+ hashes->len = MD5_LENGTH + SHA1_LENGTH;
+ rv = SECSuccess;
+#undef md5cx
+#undef shacx
+ } else
+#endif
+ if (ss->ssl3.hs.hashType == handshake_hash_single) {
+ /* compute hashes with PKCS11 */
+ PK11Context *h;
+ unsigned int stateLen;
+ unsigned char stackBuf[1024];
+ unsigned char *stateBuf = NULL;
+
+ if (!spec->master_secret) {
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ h = ss->ssl3.hs.sha;
+ stateBuf = PK11_SaveContextAlloc(h, stackBuf,
+ sizeof(stackBuf), &stateLen);
+ if (stateBuf == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ goto tls12_loser;
+ }
+ rv |= PK11_DigestFinal(h, hashes->u.raw, &hashes->len,
+ sizeof(hashes->u.raw));
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto tls12_loser;
+ }
+ /* If we ever support ciphersuites where the PRF hash isn't SHA-256
+ * then this will need to be updated. */
+ hashes->hashAlg = SEC_OID_SHA256;
+ rv = SECSuccess;
+
+tls12_loser:
+ if (stateBuf) {
+ if (PK11_RestoreContext(h, stateBuf, stateLen) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ rv = SECFailure;
+ }
+ if (stateBuf != stackBuf) {
+ PORT_ZFree(stateBuf, stateLen);
+ }
+ }
+ } else {
+ /* compute hashes with PKCS11 */
+ PK11Context * md5;
+ PK11Context * sha = NULL;
+ unsigned char *md5StateBuf = NULL;
+ unsigned char *shaStateBuf = NULL;
+ unsigned int md5StateLen, shaStateLen;
+ unsigned char md5StackBuf[256];
+ unsigned char shaStackBuf[512];
+
+ if (!spec->master_secret) {
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HANDSHAKE);
+ return SECFailure;
+ }
+
+ md5StateBuf = PK11_SaveContextAlloc(ss->ssl3.hs.md5, md5StackBuf,
+ sizeof md5StackBuf, &md5StateLen);
+ if (md5StateBuf == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ goto loser;
+ }
+ md5 = ss->ssl3.hs.md5;
+
+ shaStateBuf = PK11_SaveContextAlloc(ss->ssl3.hs.sha, shaStackBuf,
+ sizeof shaStackBuf, &shaStateLen);
+ if (shaStateBuf == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ goto loser;
+ }
+ sha = ss->ssl3.hs.sha;
+
+ if (!isTLS) {
+ /* compute hashes for SSL3. */
+ unsigned char s[4];
+
+ s[0] = (unsigned char)(sender >> 24);
+ s[1] = (unsigned char)(sender >> 16);
+ s[2] = (unsigned char)(sender >> 8);
+ s[3] = (unsigned char)sender;
+
+ if (sender != 0) {
+ rv |= PK11_DigestOp(md5, s, 4);
+ PRINT_BUF(95, (NULL, "MD5 inner: sender", s, 4));
+ }
+
+ PRINT_BUF(95, (NULL, "MD5 inner: MAC Pad 1", mac_pad_1,
+ mac_defs[mac_md5].pad_size));
+
+ rv |= PK11_DigestKey(md5,spec->master_secret);
+ rv |= PK11_DigestOp(md5, mac_pad_1, mac_defs[mac_md5].pad_size);
+ rv |= PK11_DigestFinal(md5, md5_inner, &outLength, MD5_LENGTH);
+ PORT_Assert(rv != SECSuccess || outLength == MD5_LENGTH);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ PRINT_BUF(95, (NULL, "MD5 inner: result", md5_inner, outLength));
+
+ if (sender != 0) {
+ rv |= PK11_DigestOp(sha, s, 4);
+ PRINT_BUF(95, (NULL, "SHA inner: sender", s, 4));
+ }
+
+ PRINT_BUF(95, (NULL, "SHA inner: MAC Pad 1", mac_pad_1,
+ mac_defs[mac_sha].pad_size));
+
+ rv |= PK11_DigestKey(sha, spec->master_secret);
+ rv |= PK11_DigestOp(sha, mac_pad_1, mac_defs[mac_sha].pad_size);
+ rv |= PK11_DigestFinal(sha, sha_inner, &outLength, SHA1_LENGTH);
+ PORT_Assert(rv != SECSuccess || outLength == SHA1_LENGTH);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ PRINT_BUF(95, (NULL, "SHA inner: result", sha_inner, outLength));
+
+ PRINT_BUF(95, (NULL, "MD5 outer: MAC Pad 2", mac_pad_2,
+ mac_defs[mac_md5].pad_size));
+ PRINT_BUF(95, (NULL, "MD5 outer: MD5 inner", md5_inner, MD5_LENGTH));
+
+ rv |= PK11_DigestBegin(md5);
+ rv |= PK11_DigestKey(md5, spec->master_secret);
+ rv |= PK11_DigestOp(md5, mac_pad_2, mac_defs[mac_md5].pad_size);
+ rv |= PK11_DigestOp(md5, md5_inner, MD5_LENGTH);
+ }
+ rv |= PK11_DigestFinal(md5, hashes->u.s.md5, &outLength, MD5_LENGTH);
+ PORT_Assert(rv != SECSuccess || outLength == MD5_LENGTH);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ PRINT_BUF(60, (NULL, "MD5 outer: result", hashes->u.s.md5, MD5_LENGTH));
+
+ if (!isTLS) {
+ PRINT_BUF(95, (NULL, "SHA outer: MAC Pad 2", mac_pad_2,
+ mac_defs[mac_sha].pad_size));
+ PRINT_BUF(95, (NULL, "SHA outer: SHA inner", sha_inner, SHA1_LENGTH));
+
+ rv |= PK11_DigestBegin(sha);
+ rv |= PK11_DigestKey(sha,spec->master_secret);
+ rv |= PK11_DigestOp(sha, mac_pad_2, mac_defs[mac_sha].pad_size);
+ rv |= PK11_DigestOp(sha, sha_inner, SHA1_LENGTH);
+ }
+ rv |= PK11_DigestFinal(sha, hashes->u.s.sha, &outLength, SHA1_LENGTH);
+ PORT_Assert(rv != SECSuccess || outLength == SHA1_LENGTH);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ PRINT_BUF(60, (NULL, "SHA outer: result", hashes->u.s.sha, SHA1_LENGTH));
+
+ hashes->len = MD5_LENGTH + SHA1_LENGTH;
+ rv = SECSuccess;
+
+ loser:
+ if (md5StateBuf) {
+ if (PK11_RestoreContext(ss->ssl3.hs.md5, md5StateBuf, md5StateLen)
+ != SECSuccess)
+ {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ rv = SECFailure;
+ }
+ if (md5StateBuf != md5StackBuf) {
+ PORT_ZFree(md5StateBuf, md5StateLen);
+ }
+ }
+ if (shaStateBuf) {
+ if (PK11_RestoreContext(ss->ssl3.hs.sha, shaStateBuf, shaStateLen)
+ != SECSuccess)
+ {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ rv = SECFailure;
+ }
+ if (shaStateBuf != shaStackBuf) {
+ PORT_ZFree(shaStateBuf, shaStateLen);
+ }
+ }
+ }
+ return rv;
+}
+
+/*
+ * SSL 2 based implementations pass in the initial outbound buffer
+ * so that the handshake hash can contain the included information.
+ *
+ * Called from ssl2_BeginClientHandshake() in sslcon.c
+ */
+SECStatus
+ssl3_StartHandshakeHash(sslSocket *ss, unsigned char * buf, int length)
+{
+ SECStatus rv;
+
+ ssl_GetSSL3HandshakeLock(ss); /**************************************/
+
+ rv = ssl3_InitState(ss);
+ if (rv != SECSuccess) {
+ goto done; /* ssl3_InitState has set the error code. */
+ }
+ rv = ssl3_RestartHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ goto done;
+ }
+
+ PORT_Memset(&ss->ssl3.hs.client_random, 0, SSL3_RANDOM_LENGTH);
+ PORT_Memcpy(
+ &ss->ssl3.hs.client_random.rand[SSL3_RANDOM_LENGTH - SSL_CHALLENGE_BYTES],
+ &ss->sec.ci.clientChallenge,
+ SSL_CHALLENGE_BYTES);
+
+ rv = ssl3_UpdateHandshakeHashes(ss, buf, length);
+ /* if it failed, ssl3_UpdateHandshakeHashes has set the error code. */
+
+done:
+ ssl_ReleaseSSL3HandshakeLock(ss); /**************************************/
+ return rv;
+}
+
+/**************************************************************************
+ * end of Handshake Hash functions.
+ * Begin Send and Handle functions for handshakes.
+ **************************************************************************/
+
+/* Called from ssl3_HandleHelloRequest(),
+ * ssl3_RedoHandshake()
+ * ssl2_BeginClientHandshake (when resuming ssl3 session)
+ * dtls_HandleHelloVerifyRequest(with resending=PR_TRUE)
+ */
+SECStatus
+ssl3_SendClientHello(sslSocket *ss, PRBool resending)
+{
+ sslSessionID * sid;
+ ssl3CipherSpec * cwSpec;
+ SECStatus rv;
+ int i;
+ int length;
+ int num_suites;
+ int actual_count = 0;
+ PRBool isTLS = PR_FALSE;
+ PRBool requestingResume = PR_FALSE;
+ PRInt32 total_exten_len = 0;
+ unsigned numCompressionMethods;
+ PRInt32 flags;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send client_hello handshake", SSL_GETPID(),
+ ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ rv = ssl3_InitState(ss);
+ if (rv != SECSuccess) {
+ return rv; /* ssl3_InitState has set the error code. */
+ }
+ ss->ssl3.hs.sendingSCSV = PR_FALSE; /* Must be reset every handshake */
+ PORT_Assert(IS_DTLS(ss) || !resending);
+
+ /* We might be starting a session renegotiation in which case we should
+ * clear previous state.
+ */
+ PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData));
+
+ rv = ssl3_RestartHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ /*
+ * During a renegotiation, ss->clientHelloVersion will be used again to
+ * work around a Windows SChannel bug. Ensure that it is still enabled.
+ */
+ if (ss->firstHsDone) {
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ return SECFailure;
+ }
+
+ if (ss->clientHelloVersion < ss->vrange.min ||
+ ss->clientHelloVersion > ss->vrange.max) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+ }
+
+ /* We ignore ss->sec.ci.sid here, and use ssl_Lookup because Lookup
+ * handles expired entries and other details.
+ * XXX If we've been called from ssl2_BeginClientHandshake, then
+ * this lookup is duplicative and wasteful.
+ */
+ sid = (ss->opt.noCache) ? NULL
+ : ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID, ss->url);
+
+ /* We can't resume based on a different token. If the sid exists,
+ * make sure the token that holds the master secret still exists ...
+ * If we previously did client-auth, make sure that the token that holds
+ * the private key still exists, is logged in, hasn't been removed, etc.
+ */
+ if (sid) {
+ PRBool sidOK = PR_TRUE;
+ if (sid->u.ssl3.keys.msIsWrapped) {
+ /* Session key was wrapped, which means it was using PKCS11, */
+ PK11SlotInfo *slot = NULL;
+ if (sid->u.ssl3.masterValid && !ss->opt.bypassPKCS11) {
+ slot = SECMOD_LookupSlot(sid->u.ssl3.masterModuleID,
+ sid->u.ssl3.masterSlotID);
+ }
+ if (slot == NULL) {
+ sidOK = PR_FALSE;
+ } else {
+ PK11SymKey *wrapKey = NULL;
+ if (!PK11_IsPresent(slot) ||
+ ((wrapKey = PK11_GetWrapKey(slot,
+ sid->u.ssl3.masterWrapIndex,
+ sid->u.ssl3.masterWrapMech,
+ sid->u.ssl3.masterWrapSeries,
+ ss->pkcs11PinArg)) == NULL) ) {
+ sidOK = PR_FALSE;
+ }
+ if (wrapKey) PK11_FreeSymKey(wrapKey);
+ PK11_FreeSlot(slot);
+ slot = NULL;
+ }
+ }
+ /* If we previously did client-auth, make sure that the token that
+ ** holds the private key still exists, is logged in, hasn't been
+ ** removed, etc.
+ */
+ if (sidOK && !ssl3_ClientAuthTokenPresent(sid)) {
+ sidOK = PR_FALSE;
+ }
+
+ /* TLS 1.0 (RFC 2246) Appendix E says:
+ * Whenever a client already knows the highest protocol known to
+ * a server (for example, when resuming a session), it should
+ * initiate the connection in that native protocol.
+ * So we pass sid->version to ssl3_NegotiateVersion() here, except
+ * when renegotiating.
+ *
+ * Windows SChannel compares the client_version inside the RSA
+ * EncryptedPreMasterSecret of a renegotiation with the
+ * client_version of the initial ClientHello rather than the
+ * ClientHello in the renegotiation. To work around this bug, we
+ * continue to use the client_version used in the initial
+ * ClientHello when renegotiating.
+ */
+ if (sidOK) {
+ if (ss->firstHsDone) {
+ /*
+ * The client_version of the initial ClientHello is still
+ * available in ss->clientHelloVersion. Ensure that
+ * sid->version is bounded within
+ * [ss->vrange.min, ss->clientHelloVersion], otherwise we
+ * can't use sid.
+ */
+ if (sid->version >= ss->vrange.min &&
+ sid->version <= ss->clientHelloVersion) {
+ ss->version = ss->clientHelloVersion;
+ } else {
+ sidOK = PR_FALSE;
+ }
+ } else {
+ if (ssl3_NegotiateVersion(ss, sid->version,
+ PR_FALSE) != SECSuccess) {
+ sidOK = PR_FALSE;
+ }
+ }
+ }
+
+ if (!sidOK) {
+ SSL_AtomicIncrementLong(& ssl3stats.sch_sid_cache_not_ok );
+ if (ss->sec.uncache)
+ (*ss->sec.uncache)(sid);
+ ssl_FreeSID(sid);
+ sid = NULL;
+ }
+ }
+
+ if (sid) {
+ requestingResume = PR_TRUE;
+ SSL_AtomicIncrementLong(& ssl3stats.sch_sid_cache_hits );
+
+ /* Are we attempting a stateless session resume? */
+ if (sid->version > SSL_LIBRARY_VERSION_3_0 &&
+ sid->u.ssl3.sessionTicket.ticket.data)
+ SSL_AtomicIncrementLong(& ssl3stats.sch_sid_stateless_resumes );
+
+ PRINT_BUF(4, (ss, "client, found session-id:", sid->u.ssl3.sessionID,
+ sid->u.ssl3.sessionIDLength));
+
+ ss->ssl3.policy = sid->u.ssl3.policy;
+ } else {
+ SSL_AtomicIncrementLong(& ssl3stats.sch_sid_cache_misses );
+
+ /*
+ * Windows SChannel compares the client_version inside the RSA
+ * EncryptedPreMasterSecret of a renegotiation with the
+ * client_version of the initial ClientHello rather than the
+ * ClientHello in the renegotiation. To work around this bug, we
+ * continue to use the client_version used in the initial
+ * ClientHello when renegotiating.
+ */
+ if (ss->firstHsDone) {
+ ss->version = ss->clientHelloVersion;
+ } else {
+ rv = ssl3_NegotiateVersion(ss, SSL_LIBRARY_VERSION_MAX_SUPPORTED,
+ PR_TRUE);
+ if (rv != SECSuccess)
+ return rv; /* error code was set */
+ }
+
+ sid = ssl3_NewSessionID(ss, PR_FALSE);
+ if (!sid) {
+ return SECFailure; /* memory error is set */
+ }
+ }
+
+ isTLS = (ss->version > SSL_LIBRARY_VERSION_3_0);
+ ssl_GetSpecWriteLock(ss);
+ cwSpec = ss->ssl3.cwSpec;
+ if (cwSpec->mac_def->mac == mac_null) {
+ /* SSL records are not being MACed. */
+ cwSpec->version = ss->version;
+ }
+ ssl_ReleaseSpecWriteLock(ss);
+
+ if (ss->sec.ci.sid != NULL) {
+ ssl_FreeSID(ss->sec.ci.sid); /* decrement ref count, free if zero */
+ }
+ ss->sec.ci.sid = sid;
+
+ ss->sec.send = ssl3_SendApplicationData;
+
+ /* shouldn't get here if SSL3 is disabled, but ... */
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ PR_NOT_REACHED("No versions of SSL 3.0 or later are enabled");
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ return SECFailure;
+ }
+
+ /* how many suites does our PKCS11 support (regardless of policy)? */
+ num_suites = ssl3_config_match_init(ss);
+ if (!num_suites)
+ return SECFailure; /* ssl3_config_match_init has set error code. */
+
+ /* HACK for SCSV in SSL 3.0. On initial handshake, prepend SCSV,
+ * only if TLS is disabled.
+ */
+ if (!ss->firstHsDone && !isTLS) {
+ /* Must set this before calling Hello Extension Senders,
+ * to suppress sending of empty RI extension.
+ */
+ ss->ssl3.hs.sendingSCSV = PR_TRUE;
+ }
+
+ if (isTLS || (ss->firstHsDone && ss->peerRequestedProtection)) {
+ PRUint32 maxBytes = 65535; /* 2^16 - 1 */
+ PRInt32 extLen;
+
+ extLen = ssl3_CallHelloExtensionSenders(ss, PR_FALSE, maxBytes, NULL);
+ if (extLen < 0) {
+ return SECFailure;
+ }
+ maxBytes -= extLen;
+ total_exten_len += extLen;
+
+ if (total_exten_len > 0)
+ total_exten_len += 2;
+ }
+
+#if defined(NSS_ENABLE_ECC)
+ if (!total_exten_len || !isTLS) {
+ /* not sending the elliptic_curves and ec_point_formats extensions */
+ ssl3_DisableECCSuites(ss, NULL); /* disable all ECC suites */
+ }
+#endif
+
+ if (IS_DTLS(ss)) {
+ ssl3_DisableNonDTLSSuites(ss);
+ }
+
+ if (!ssl3_HasGCMSupport()) {
+ ssl3_DisableGCMSuites(ss);
+ }
+
+ /* how many suites are permitted by policy and user preference? */
+ num_suites = count_cipher_suites(ss, ss->ssl3.policy, PR_TRUE);
+ if (!num_suites)
+ return SECFailure; /* count_cipher_suites has set error code. */
+ if (ss->ssl3.hs.sendingSCSV) {
+ ++num_suites; /* make room for SCSV */
+ }
+
+ /* count compression methods */
+ numCompressionMethods = 0;
+ for (i = 0; i < compressionMethodsCount; i++) {
+ if (compressionEnabled(ss, compressions[i]))
+ numCompressionMethods++;
+ }
+
+ length = sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH +
+ 1 + ((sid == NULL) ? 0 : sid->u.ssl3.sessionIDLength) +
+ 2 + num_suites*sizeof(ssl3CipherSuite) +
+ 1 + numCompressionMethods + total_exten_len;
+ if (IS_DTLS(ss)) {
+ length += 1 + ss->ssl3.hs.cookieLen;
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, client_hello, length);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+
+ if (ss->firstHsDone) {
+ /* The client hello version must stay unchanged to work around
+ * the Windows SChannel bug described above. */
+ PORT_Assert(ss->version == ss->clientHelloVersion);
+ }
+ ss->clientHelloVersion = ss->version;
+ if (IS_DTLS(ss)) {
+ PRUint16 version;
+
+ version = dtls_TLSVersionToDTLSVersion(ss->clientHelloVersion);
+ rv = ssl3_AppendHandshakeNumber(ss, version, 2);
+ } else {
+ rv = ssl3_AppendHandshakeNumber(ss, ss->clientHelloVersion, 2);
+ }
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+
+ if (!resending) { /* Don't re-generate if we are in DTLS re-sending mode */
+ rv = ssl3_GetNewRandom(&ss->ssl3.hs.client_random);
+ if (rv != SECSuccess) {
+ return rv; /* err set by GetNewRandom. */
+ }
+ }
+ rv = ssl3_AppendHandshake(ss, &ss->ssl3.hs.client_random,
+ SSL3_RANDOM_LENGTH);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+
+ if (sid)
+ rv = ssl3_AppendHandshakeVariable(
+ ss, sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength, 1);
+ else
+ rv = ssl3_AppendHandshakeVariable(ss, NULL, 0, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+
+ if (IS_DTLS(ss)) {
+ rv = ssl3_AppendHandshakeVariable(
+ ss, ss->ssl3.hs.cookie, ss->ssl3.hs.cookieLen, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, num_suites*sizeof(ssl3CipherSuite), 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+
+ if (ss->ssl3.hs.sendingSCSV) {
+ /* Add the actual SCSV */
+ rv = ssl3_AppendHandshakeNumber(ss, TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
+ sizeof(ssl3CipherSuite));
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ actual_count++;
+ }
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
+ if (config_match(suite, ss->ssl3.policy, PR_TRUE)) {
+ actual_count++;
+ if (actual_count > num_suites) {
+ /* set error card removal/insertion error */
+ PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+ return SECFailure;
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, suite->cipher_suite,
+ sizeof(ssl3CipherSuite));
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ }
+ }
+
+ /* if cards were removed or inserted between count_cipher_suites and
+ * generating our list, detect the error here rather than send it off to
+ * the server.. */
+ if (actual_count != num_suites) {
+ /* Card removal/insertion error */
+ PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+ return SECFailure;
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, numCompressionMethods, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ for (i = 0; i < compressionMethodsCount; i++) {
+ if (!compressionEnabled(ss, compressions[i]))
+ continue;
+ rv = ssl3_AppendHandshakeNumber(ss, compressions[i], 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_AppendHandshake* */
+ }
+ }
+
+ if (total_exten_len) {
+ PRUint32 maxBytes = total_exten_len - 2;
+ PRInt32 extLen;
+
+ rv = ssl3_AppendHandshakeNumber(ss, maxBytes, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+
+ extLen = ssl3_CallHelloExtensionSenders(ss, PR_TRUE, maxBytes, NULL);
+ if (extLen < 0) {
+ return SECFailure;
+ }
+ maxBytes -= extLen;
+ PORT_Assert(!maxBytes);
+ }
+ if (ss->ssl3.hs.sendingSCSV) {
+ /* Since we sent the SCSV, pretend we sent empty RI extension. */
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_renegotiation_info_xtn;
+ }
+
+ flags = 0;
+ if (!ss->firstHsDone && !requestingResume && !IS_DTLS(ss)) {
+ flags |= ssl_SEND_FLAG_CAP_RECORD_VERSION;
+ }
+ rv = ssl3_FlushHandshake(ss, flags);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by ssl3_FlushHandshake */
+ }
+
+ ss->ssl3.hs.ws = wait_server_hello;
+ return rv;
+}
+
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Hello Request.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleHelloRequest(sslSocket *ss)
+{
+ sslSessionID *sid = ss->sec.ci.sid;
+ SECStatus rv;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle hello_request handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ss->ssl3.hs.ws == wait_server_hello)
+ return SECSuccess;
+ if (ss->ssl3.hs.ws != idle_handshake || ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST);
+ return SECFailure;
+ }
+ if (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER) {
+ ssl_GetXmitBufLock(ss);
+ rv = SSL3_SendAlert(ss, alert_warning, no_renegotiation);
+ ssl_ReleaseXmitBufLock(ss);
+ PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
+ return SECFailure;
+ }
+
+ if (sid) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid);
+ ssl_FreeSID(sid);
+ ss->sec.ci.sid = NULL;
+ }
+
+ if (IS_DTLS(ss)) {
+ dtls_RehandshakeCleanup(ss);
+ }
+
+ ssl_GetXmitBufLock(ss);
+ rv = ssl3_SendClientHello(ss, PR_FALSE);
+ ssl_ReleaseXmitBufLock(ss);
+
+ return rv;
+}
+
+#define UNKNOWN_WRAP_MECHANISM 0x7fffffff
+
+static const CK_MECHANISM_TYPE wrapMechanismList[SSL_NUM_WRAP_MECHS] = {
+ CKM_DES3_ECB,
+ CKM_CAST5_ECB,
+ CKM_DES_ECB,
+ CKM_KEY_WRAP_LYNKS,
+ CKM_IDEA_ECB,
+ CKM_CAST3_ECB,
+ CKM_CAST_ECB,
+ CKM_RC5_ECB,
+ CKM_RC2_ECB,
+ CKM_CDMF_ECB,
+ CKM_SKIPJACK_WRAP,
+ CKM_SKIPJACK_CBC64,
+ CKM_AES_ECB,
+ CKM_CAMELLIA_ECB,
+ CKM_SEED_ECB,
+ UNKNOWN_WRAP_MECHANISM
+};
+
+static int
+ssl_FindIndexByWrapMechanism(CK_MECHANISM_TYPE mech)
+{
+ const CK_MECHANISM_TYPE *pMech = wrapMechanismList;
+
+ while (mech != *pMech && *pMech != UNKNOWN_WRAP_MECHANISM) {
+ ++pMech;
+ }
+ return (*pMech == UNKNOWN_WRAP_MECHANISM) ? -1
+ : (pMech - wrapMechanismList);
+}
+
+static PK11SymKey *
+ssl_UnwrapSymWrappingKey(
+ SSLWrappedSymWrappingKey *pWswk,
+ SECKEYPrivateKey * svrPrivKey,
+ SSL3KEAType exchKeyType,
+ CK_MECHANISM_TYPE masterWrapMech,
+ void * pwArg)
+{
+ PK11SymKey * unwrappedWrappingKey = NULL;
+ SECItem wrappedKey;
+#ifdef NSS_ENABLE_ECC
+ PK11SymKey * Ks;
+ SECKEYPublicKey pubWrapKey;
+ ECCWrappedKeyInfo *ecWrapped;
+#endif /* NSS_ENABLE_ECC */
+
+ /* found the wrapping key on disk. */
+ PORT_Assert(pWswk->symWrapMechanism == masterWrapMech);
+ PORT_Assert(pWswk->exchKeyType == exchKeyType);
+ if (pWswk->symWrapMechanism != masterWrapMech ||
+ pWswk->exchKeyType != exchKeyType) {
+ goto loser;
+ }
+ wrappedKey.type = siBuffer;
+ wrappedKey.data = pWswk->wrappedSymmetricWrappingkey;
+ wrappedKey.len = pWswk->wrappedSymKeyLen;
+ PORT_Assert(wrappedKey.len <= sizeof pWswk->wrappedSymmetricWrappingkey);
+
+ switch (exchKeyType) {
+
+ case kt_rsa:
+ unwrappedWrappingKey =
+ PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey,
+ masterWrapMech, CKA_UNWRAP, 0);
+ break;
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh:
+ /*
+ * For kt_ecdh, we first create an EC public key based on
+ * data stored with the wrappedSymmetricWrappingkey. Next,
+ * we do an ECDH computation involving this public key and
+ * the SSL server's (long-term) EC private key. The resulting
+ * shared secret is treated the same way as Fortezza's Ks, i.e.,
+ * it is used to recover the symmetric wrapping key.
+ *
+ * The data in wrappedSymmetricWrappingkey is laid out as defined
+ * in the ECCWrappedKeyInfo structure.
+ */
+ ecWrapped = (ECCWrappedKeyInfo *) pWswk->wrappedSymmetricWrappingkey;
+
+ PORT_Assert(ecWrapped->encodedParamLen + ecWrapped->pubValueLen +
+ ecWrapped->wrappedKeyLen <= MAX_EC_WRAPPED_KEY_BUFLEN);
+
+ if (ecWrapped->encodedParamLen + ecWrapped->pubValueLen +
+ ecWrapped->wrappedKeyLen > MAX_EC_WRAPPED_KEY_BUFLEN) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto loser;
+ }
+
+ pubWrapKey.keyType = ecKey;
+ pubWrapKey.u.ec.size = ecWrapped->size;
+ pubWrapKey.u.ec.DEREncodedParams.len = ecWrapped->encodedParamLen;
+ pubWrapKey.u.ec.DEREncodedParams.data = ecWrapped->var;
+ pubWrapKey.u.ec.publicValue.len = ecWrapped->pubValueLen;
+ pubWrapKey.u.ec.publicValue.data = ecWrapped->var +
+ ecWrapped->encodedParamLen;
+
+ wrappedKey.len = ecWrapped->wrappedKeyLen;
+ wrappedKey.data = ecWrapped->var + ecWrapped->encodedParamLen +
+ ecWrapped->pubValueLen;
+
+ /* Derive Ks using ECDH */
+ Ks = PK11_PubDeriveWithKDF(svrPrivKey, &pubWrapKey, PR_FALSE, NULL,
+ NULL, CKM_ECDH1_DERIVE, masterWrapMech,
+ CKA_DERIVE, 0, CKD_NULL, NULL, NULL);
+ if (Ks == NULL) {
+ goto loser;
+ }
+
+ /* Use Ks to unwrap the wrapping key */
+ unwrappedWrappingKey = PK11_UnwrapSymKey(Ks, masterWrapMech, NULL,
+ &wrappedKey, masterWrapMech,
+ CKA_UNWRAP, 0);
+ PK11_FreeSymKey(Ks);
+
+ break;
+#endif
+
+ default:
+ /* Assert? */
+ SET_ERROR_CODE
+ goto loser;
+ }
+loser:
+ return unwrappedWrappingKey;
+}
+
+/* Each process sharing the server session ID cache has its own array of
+ * SymKey pointers for the symmetric wrapping keys that are used to wrap
+ * the master secrets. There is one key for each KEA type. These Symkeys
+ * correspond to the wrapped SymKeys kept in the server session cache.
+ */
+
+typedef struct {
+ PK11SymKey * symWrapKey[kt_kea_size];
+} ssl3SymWrapKey;
+
+static PZLock * symWrapKeysLock = NULL;
+static ssl3SymWrapKey symWrapKeys[SSL_NUM_WRAP_MECHS];
+
+SECStatus ssl_FreeSymWrapKeysLock(void)
+{
+ if (symWrapKeysLock) {
+ PZ_DestroyLock(symWrapKeysLock);
+ symWrapKeysLock = NULL;
+ return SECSuccess;
+ }
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+}
+
+SECStatus
+SSL3_ShutdownServerCache(void)
+{
+ int i, j;
+
+ if (!symWrapKeysLock)
+ return SECSuccess; /* lock was never initialized */
+ PZ_Lock(symWrapKeysLock);
+ /* get rid of all symWrapKeys */
+ for (i = 0; i < SSL_NUM_WRAP_MECHS; ++i) {
+ for (j = 0; j < kt_kea_size; ++j) {
+ PK11SymKey ** pSymWrapKey;
+ pSymWrapKey = &symWrapKeys[i].symWrapKey[j];
+ if (*pSymWrapKey) {
+ PK11_FreeSymKey(*pSymWrapKey);
+ *pSymWrapKey = NULL;
+ }
+ }
+ }
+
+ PZ_Unlock(symWrapKeysLock);
+ ssl_FreeSessionCacheLocks();
+ return SECSuccess;
+}
+
+SECStatus ssl_InitSymWrapKeysLock(void)
+{
+ symWrapKeysLock = PZ_NewLock(nssILockOther);
+ return symWrapKeysLock ? SECSuccess : SECFailure;
+}
+
+/* Try to get wrapping key for mechanism from in-memory array.
+ * If that fails, look for one on disk.
+ * If that fails, generate a new one, put the new one on disk,
+ * Put the new key in the in-memory array.
+ */
+static PK11SymKey *
+getWrappingKey( sslSocket * ss,
+ PK11SlotInfo * masterSecretSlot,
+ SSL3KEAType exchKeyType,
+ CK_MECHANISM_TYPE masterWrapMech,
+ void * pwArg)
+{
+ SECKEYPrivateKey * svrPrivKey;
+ SECKEYPublicKey * svrPubKey = NULL;
+ PK11SymKey * unwrappedWrappingKey = NULL;
+ PK11SymKey ** pSymWrapKey;
+ CK_MECHANISM_TYPE asymWrapMechanism = CKM_INVALID_MECHANISM;
+ int length;
+ int symWrapMechIndex;
+ SECStatus rv;
+ SECItem wrappedKey;
+ SSLWrappedSymWrappingKey wswk;
+#ifdef NSS_ENABLE_ECC
+ PK11SymKey * Ks = NULL;
+ SECKEYPublicKey *pubWrapKey = NULL;
+ SECKEYPrivateKey *privWrapKey = NULL;
+ ECCWrappedKeyInfo *ecWrapped;
+#endif /* NSS_ENABLE_ECC */
+
+ svrPrivKey = ss->serverCerts[exchKeyType].SERVERKEY;
+ PORT_Assert(svrPrivKey != NULL);
+ if (!svrPrivKey) {
+ return NULL; /* why are we here?!? */
+ }
+
+ symWrapMechIndex = ssl_FindIndexByWrapMechanism(masterWrapMech);
+ PORT_Assert(symWrapMechIndex >= 0);
+ if (symWrapMechIndex < 0)
+ return NULL; /* invalid masterWrapMech. */
+
+ pSymWrapKey = &symWrapKeys[symWrapMechIndex].symWrapKey[exchKeyType];
+
+ ssl_InitSessionCacheLocks(PR_TRUE);
+
+ PZ_Lock(symWrapKeysLock);
+
+ unwrappedWrappingKey = *pSymWrapKey;
+ if (unwrappedWrappingKey != NULL) {
+ if (PK11_VerifyKeyOK(unwrappedWrappingKey)) {
+ unwrappedWrappingKey = PK11_ReferenceSymKey(unwrappedWrappingKey);
+ goto done;
+ }
+ /* slot series has changed, so this key is no good any more. */
+ PK11_FreeSymKey(unwrappedWrappingKey);
+ *pSymWrapKey = unwrappedWrappingKey = NULL;
+ }
+
+ /* Try to get wrapped SymWrapping key out of the (disk) cache. */
+ /* Following call fills in wswk on success. */
+ if (ssl_GetWrappingKey(symWrapMechIndex, exchKeyType, &wswk)) {
+ /* found the wrapped sym wrapping key on disk. */
+ unwrappedWrappingKey =
+ ssl_UnwrapSymWrappingKey(&wswk, svrPrivKey, exchKeyType,
+ masterWrapMech, pwArg);
+ if (unwrappedWrappingKey) {
+ goto install;
+ }
+ }
+
+ if (!masterSecretSlot) /* caller doesn't want to create a new one. */
+ goto loser;
+
+ length = PK11_GetBestKeyLength(masterSecretSlot, masterWrapMech);
+ /* Zero length means fixed key length algorithm, or error.
+ * It's ambiguous.
+ */
+ unwrappedWrappingKey = PK11_KeyGen(masterSecretSlot, masterWrapMech, NULL,
+ length, pwArg);
+ if (!unwrappedWrappingKey) {
+ goto loser;
+ }
+
+ /* Prepare the buffer to receive the wrappedWrappingKey,
+ * the symmetric wrapping key wrapped using the server's pub key.
+ */
+ PORT_Memset(&wswk, 0, sizeof wswk); /* eliminate UMRs. */
+
+ if (ss->serverCerts[exchKeyType].serverKeyPair) {
+ svrPubKey = ss->serverCerts[exchKeyType].serverKeyPair->pubKey;
+ }
+ if (svrPubKey == NULL) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto loser;
+ }
+ wrappedKey.type = siBuffer;
+ wrappedKey.len = SECKEY_PublicKeyStrength(svrPubKey);
+ wrappedKey.data = wswk.wrappedSymmetricWrappingkey;
+
+ PORT_Assert(wrappedKey.len <= sizeof wswk.wrappedSymmetricWrappingkey);
+ if (wrappedKey.len > sizeof wswk.wrappedSymmetricWrappingkey)
+ goto loser;
+
+ /* wrap symmetric wrapping key in server's public key. */
+ switch (exchKeyType) {
+ case kt_rsa:
+ asymWrapMechanism = CKM_RSA_PKCS;
+ rv = PK11_PubWrapSymKey(asymWrapMechanism, svrPubKey,
+ unwrappedWrappingKey, &wrappedKey);
+ break;
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh:
+ /*
+ * We generate an ephemeral EC key pair. Perform an ECDH
+ * computation involving this ephemeral EC public key and
+ * the SSL server's (long-term) EC private key. The resulting
+ * shared secret is treated in the same way as Fortezza's Ks,
+ * i.e., it is used to wrap the wrapping key. To facilitate
+ * unwrapping in ssl_UnwrapWrappingKey, we also store all
+ * relevant info about the ephemeral EC public key in
+ * wswk.wrappedSymmetricWrappingkey and lay it out as
+ * described in the ECCWrappedKeyInfo structure.
+ */
+ PORT_Assert(svrPubKey->keyType == ecKey);
+ if (svrPubKey->keyType != ecKey) {
+ /* something is wrong in sslsecur.c if this isn't an ecKey */
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+ goto ec_cleanup;
+ }
+
+ privWrapKey = SECKEY_CreateECPrivateKey(
+ &svrPubKey->u.ec.DEREncodedParams, &pubWrapKey, NULL);
+ if ((privWrapKey == NULL) || (pubWrapKey == NULL)) {
+ rv = SECFailure;
+ goto ec_cleanup;
+ }
+
+ /* Set the key size in bits */
+ if (pubWrapKey->u.ec.size == 0) {
+ pubWrapKey->u.ec.size = SECKEY_PublicKeyStrengthInBits(svrPubKey);
+ }
+
+ PORT_Assert(pubWrapKey->u.ec.DEREncodedParams.len +
+ pubWrapKey->u.ec.publicValue.len < MAX_EC_WRAPPED_KEY_BUFLEN);
+ if (pubWrapKey->u.ec.DEREncodedParams.len +
+ pubWrapKey->u.ec.publicValue.len >= MAX_EC_WRAPPED_KEY_BUFLEN) {
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ rv = SECFailure;
+ goto ec_cleanup;
+ }
+
+ /* Derive Ks using ECDH */
+ Ks = PK11_PubDeriveWithKDF(svrPrivKey, pubWrapKey, PR_FALSE, NULL,
+ NULL, CKM_ECDH1_DERIVE, masterWrapMech,
+ CKA_DERIVE, 0, CKD_NULL, NULL, NULL);
+ if (Ks == NULL) {
+ rv = SECFailure;
+ goto ec_cleanup;
+ }
+
+ ecWrapped = (ECCWrappedKeyInfo *) (wswk.wrappedSymmetricWrappingkey);
+ ecWrapped->size = pubWrapKey->u.ec.size;
+ ecWrapped->encodedParamLen = pubWrapKey->u.ec.DEREncodedParams.len;
+ PORT_Memcpy(ecWrapped->var, pubWrapKey->u.ec.DEREncodedParams.data,
+ pubWrapKey->u.ec.DEREncodedParams.len);
+
+ ecWrapped->pubValueLen = pubWrapKey->u.ec.publicValue.len;
+ PORT_Memcpy(ecWrapped->var + ecWrapped->encodedParamLen,
+ pubWrapKey->u.ec.publicValue.data,
+ pubWrapKey->u.ec.publicValue.len);
+
+ wrappedKey.len = MAX_EC_WRAPPED_KEY_BUFLEN -
+ (ecWrapped->encodedParamLen + ecWrapped->pubValueLen);
+ wrappedKey.data = ecWrapped->var + ecWrapped->encodedParamLen +
+ ecWrapped->pubValueLen;
+
+ /* wrap symmetricWrapping key with the local Ks */
+ rv = PK11_WrapSymKey(masterWrapMech, NULL, Ks,
+ unwrappedWrappingKey, &wrappedKey);
+
+ if (rv != SECSuccess) {
+ goto ec_cleanup;
+ }
+
+ /* Write down the length of wrapped key in the buffer
+ * wswk.wrappedSymmetricWrappingkey at the appropriate offset
+ */
+ ecWrapped->wrappedKeyLen = wrappedKey.len;
+
+ec_cleanup:
+ if (privWrapKey) SECKEY_DestroyPrivateKey(privWrapKey);
+ if (pubWrapKey) SECKEY_DestroyPublicKey(pubWrapKey);
+ if (Ks) PK11_FreeSymKey(Ks);
+ asymWrapMechanism = masterWrapMech;
+ break;
+#endif /* NSS_ENABLE_ECC */
+
+ default:
+ rv = SECFailure;
+ break;
+ }
+
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ PORT_Assert(asymWrapMechanism != CKM_INVALID_MECHANISM);
+
+ wswk.symWrapMechanism = masterWrapMech;
+ wswk.symWrapMechIndex = symWrapMechIndex;
+ wswk.asymWrapMechanism = asymWrapMechanism;
+ wswk.exchKeyType = exchKeyType;
+ wswk.wrappedSymKeyLen = wrappedKey.len;
+
+ /* put it on disk. */
+ /* If the wrapping key for this KEA type has already been set,
+ * then abandon the value we just computed and
+ * use the one we got from the disk.
+ */
+ if (ssl_SetWrappingKey(&wswk)) {
+ /* somebody beat us to it. The original contents of our wswk
+ * has been replaced with the content on disk. Now, discard
+ * the key we just created and unwrap this new one.
+ */
+ PK11_FreeSymKey(unwrappedWrappingKey);
+
+ unwrappedWrappingKey =
+ ssl_UnwrapSymWrappingKey(&wswk, svrPrivKey, exchKeyType,
+ masterWrapMech, pwArg);
+ }
+
+install:
+ if (unwrappedWrappingKey) {
+ *pSymWrapKey = PK11_ReferenceSymKey(unwrappedWrappingKey);
+ }
+
+loser:
+done:
+ PZ_Unlock(symWrapKeysLock);
+ return unwrappedWrappingKey;
+}
+
+/* hexEncode hex encodes |length| bytes from |in| and writes it as |length*2|
+ * bytes to |out|. */
+static void
+hexEncode(char *out, const unsigned char *in, unsigned int length)
+{
+ static const char hextable[] = "0123456789abcdef";
+ unsigned int i;
+
+ for (i = 0; i < length; i++) {
+ *(out++) = hextable[in[i] >> 4];
+ *(out++) = hextable[in[i] & 15];
+ }
+}
+
+/* Called from ssl3_SendClientKeyExchange(). */
+/* Presently, this always uses PKCS11. There is no bypass for this. */
+static SECStatus
+sendRSAClientKeyExchange(sslSocket * ss, SECKEYPublicKey * svrPubKey)
+{
+ PK11SymKey * pms = NULL;
+ SECStatus rv = SECFailure;
+ SECItem enc_pms = {siBuffer, NULL, 0};
+ PRBool isTLS;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ /* Generate the pre-master secret ... */
+ ssl_GetSpecWriteLock(ss);
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+
+ pms = ssl3_GenerateRSAPMS(ss, ss->ssl3.pwSpec, NULL);
+ ssl_ReleaseSpecWriteLock(ss);
+ if (pms == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ /* Get the wrapped (encrypted) pre-master secret, enc_pms */
+ enc_pms.len = SECKEY_PublicKeyStrength(svrPubKey);
+ enc_pms.data = (unsigned char*)PORT_Alloc(enc_pms.len);
+ if (enc_pms.data == NULL) {
+ goto loser; /* err set by PORT_Alloc */
+ }
+
+ /* wrap pre-master secret in server's public key. */
+ rv = PK11_PubWrapSymKey(CKM_RSA_PKCS, svrPubKey, pms, &enc_pms);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ if (ssl_keylog_iob) {
+ SECStatus extractRV = PK11_ExtractKeyValue(pms);
+ if (extractRV == SECSuccess) {
+ SECItem * keyData = PK11_GetKeyData(pms);
+ if (keyData && keyData->data && keyData->len) {
+#ifdef TRACE
+ if (ssl_trace >= 100) {
+ ssl_PrintBuf(ss, "Pre-Master Secret",
+ keyData->data, keyData->len);
+ }
+#endif
+ if (ssl_keylog_iob && enc_pms.len >= 8 && keyData->len == 48) {
+ /* https://developer.mozilla.org/en/NSS_Key_Log_Format */
+
+ /* There could be multiple, concurrent writers to the
+ * keylog, so we have to do everything in a single call to
+ * fwrite. */
+ char buf[4 + 8*2 + 1 + 48*2 + 1];
+
+ strcpy(buf, "RSA ");
+ hexEncode(buf + 4, enc_pms.data, 8);
+ buf[20] = ' ';
+ hexEncode(buf + 21, keyData->data, 48);
+ buf[sizeof(buf) - 1] = '\n';
+
+ fwrite(buf, sizeof(buf), 1, ssl_keylog_iob);
+ fflush(ssl_keylog_iob);
+ }
+ }
+ }
+ }
+
+ rv = ssl3_InitPendingCipherSpec(ss, pms);
+ PK11_FreeSymKey(pms); pms = NULL;
+
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, client_key_exchange,
+ isTLS ? enc_pms.len + 2 : enc_pms.len);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+ if (isTLS) {
+ rv = ssl3_AppendHandshakeVariable(ss, enc_pms.data, enc_pms.len, 2);
+ } else {
+ rv = ssl3_AppendHandshake(ss, enc_pms.data, enc_pms.len);
+ }
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+
+ rv = SECSuccess;
+
+loser:
+ if (enc_pms.data != NULL) {
+ PORT_Free(enc_pms.data);
+ }
+ if (pms != NULL) {
+ PK11_FreeSymKey(pms);
+ }
+ return rv;
+}
+
+/* Called from ssl3_SendClientKeyExchange(). */
+/* Presently, this always uses PKCS11. There is no bypass for this. */
+static SECStatus
+sendDHClientKeyExchange(sslSocket * ss, SECKEYPublicKey * svrPubKey)
+{
+ PK11SymKey * pms = NULL;
+ SECStatus rv = SECFailure;
+ PRBool isTLS;
+ CK_MECHANISM_TYPE target;
+
+ SECKEYDHParams dhParam; /* DH parameters */
+ SECKEYPublicKey *pubKey = NULL; /* Ephemeral DH key */
+ SECKEYPrivateKey *privKey = NULL; /* Ephemeral DH key */
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+
+ /* Copy DH parameters from server key */
+
+ if (svrPubKey->keyType != dhKey) {
+ PORT_SetError(SEC_ERROR_BAD_KEY);
+ goto loser;
+ }
+ dhParam.prime.data = svrPubKey->u.dh.prime.data;
+ dhParam.prime.len = svrPubKey->u.dh.prime.len;
+ dhParam.base.data = svrPubKey->u.dh.base.data;
+ dhParam.base.len = svrPubKey->u.dh.base.len;
+
+ /* Generate ephemeral DH keypair */
+ privKey = SECKEY_CreateDHPrivateKey(&dhParam, &pubKey, NULL);
+ if (!privKey || !pubKey) {
+ ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
+ rv = SECFailure;
+ goto loser;
+ }
+ PRINT_BUF(50, (ss, "DH public value:",
+ pubKey->u.dh.publicValue.data,
+ pubKey->u.dh.publicValue.len));
+
+ if (isTLS) target = CKM_TLS_MASTER_KEY_DERIVE_DH;
+ else target = CKM_SSL3_MASTER_KEY_DERIVE_DH;
+
+ /* Determine the PMS */
+
+ pms = PK11_PubDerive(privKey, svrPubKey, PR_FALSE, NULL, NULL,
+ CKM_DH_PKCS_DERIVE, target, CKA_DERIVE, 0, NULL);
+
+ if (pms == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ SECKEY_DestroyPrivateKey(privKey);
+ privKey = NULL;
+
+ rv = ssl3_InitPendingCipherSpec(ss, pms);
+ PK11_FreeSymKey(pms); pms = NULL;
+
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, client_key_exchange,
+ pubKey->u.dh.publicValue.len + 2);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+ rv = ssl3_AppendHandshakeVariable(ss,
+ pubKey->u.dh.publicValue.data,
+ pubKey->u.dh.publicValue.len, 2);
+ SECKEY_DestroyPublicKey(pubKey);
+ pubKey = NULL;
+
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+
+ rv = SECSuccess;
+
+
+loser:
+
+ if(pms) PK11_FreeSymKey(pms);
+ if(privKey) SECKEY_DestroyPrivateKey(privKey);
+ if(pubKey) SECKEY_DestroyPublicKey(pubKey);
+ return rv;
+}
+
+
+
+
+
+/* Called from ssl3_HandleServerHelloDone(). */
+static SECStatus
+ssl3_SendClientKeyExchange(sslSocket *ss)
+{
+ SECKEYPublicKey * serverKey = NULL;
+ SECStatus rv = SECFailure;
+ PRBool isTLS;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send client_key_exchange handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->sec.peerKey == NULL) {
+ serverKey = CERT_ExtractPublicKey(ss->sec.peerCert);
+ if (serverKey == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE);
+ return SECFailure;
+ }
+ } else {
+ serverKey = ss->sec.peerKey;
+ ss->sec.peerKey = NULL; /* we're done with it now */
+ }
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ /* enforce limits on kea key sizes. */
+ if (ss->ssl3.hs.kea_def->is_limited) {
+ int keyLen = SECKEY_PublicKeyStrength(serverKey); /* bytes */
+
+ if (keyLen * BPB > ss->ssl3.hs.kea_def->key_size_limit) {
+ if (isTLS)
+ (void)SSL3_SendAlert(ss, alert_fatal, export_restriction);
+ else
+ (void)ssl3_HandshakeFailure(ss);
+ PORT_SetError(SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED);
+ goto loser;
+ }
+ }
+
+ ss->sec.keaType = ss->ssl3.hs.kea_def->exchKeyType;
+ ss->sec.keaKeyBits = SECKEY_PublicKeyStrengthInBits(serverKey);
+
+ switch (ss->ssl3.hs.kea_def->exchKeyType) {
+ case kt_rsa:
+ rv = sendRSAClientKeyExchange(ss, serverKey);
+ break;
+
+ case kt_dh:
+ rv = sendDHClientKeyExchange(ss, serverKey);
+ break;
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh:
+ rv = ssl3_SendECDHClientKeyExchange(ss, serverKey);
+ break;
+#endif /* NSS_ENABLE_ECC */
+
+ default:
+ /* got an unknown or unsupported Key Exchange Algorithm. */
+ SEND_ALERT
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ break;
+ }
+
+ SSL_TRC(3, ("%d: SSL3[%d]: DONE sending client_key_exchange",
+ SSL_GETPID(), ss->fd));
+
+loser:
+ if (serverKey)
+ SECKEY_DestroyPublicKey(serverKey);
+ return rv; /* err code already set. */
+}
+
+/* Called from ssl3_HandleServerHelloDone(). */
+static SECStatus
+ssl3_SendCertificateVerify(sslSocket *ss)
+{
+ SECStatus rv = SECFailure;
+ PRBool isTLS;
+ PRBool isTLS12;
+ SECItem buf = {siBuffer, NULL, 0};
+ SSL3Hashes hashes;
+ KeyType keyType;
+ unsigned int len;
+ SSL3SignatureAndHashAlgorithm sigAndHash;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send certificate_verify handshake",
+ SSL_GETPID(), ss->fd));
+
+ ssl_GetSpecReadLock(ss);
+ rv = ssl3_ComputeHandshakeHashes(ss, ss->ssl3.pwSpec, &hashes, 0);
+ ssl_ReleaseSpecReadLock(ss);
+ if (rv != SECSuccess) {
+ goto done; /* err code was set by ssl3_ComputeHandshakeHashes */
+ }
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+ if (ss->ssl3.platformClientKey) {
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ keyType = CERT_GetCertKeyType(
+ &ss->ssl3.clientCertificate->subjectPublicKeyInfo);
+ rv = ssl3_PlatformSignHashes(
+ &hashes, ss->ssl3.platformClientKey, &buf, isTLS, keyType);
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+ ss->ssl3.platformClientKey = (PlatformKey)NULL;
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ } else {
+ keyType = ss->ssl3.clientPrivateKey->keyType;
+ rv = ssl3_SignHashes(&hashes, ss->ssl3.clientPrivateKey, &buf, isTLS);
+ if (rv == SECSuccess) {
+ PK11SlotInfo * slot;
+ sslSessionID * sid = ss->sec.ci.sid;
+
+ /* Remember the info about the slot that did the signing.
+ ** Later, when doing an SSL restart handshake, verify this.
+ ** These calls are mere accessors, and can't fail.
+ */
+ slot = PK11_GetSlotFromPrivateKey(ss->ssl3.clientPrivateKey);
+ sid->u.ssl3.clAuthSeries = PK11_GetSlotSeries(slot);
+ sid->u.ssl3.clAuthSlotID = PK11_GetSlotID(slot);
+ sid->u.ssl3.clAuthModuleID = PK11_GetModuleID(slot);
+ sid->u.ssl3.clAuthValid = PR_TRUE;
+ PK11_FreeSlot(slot);
+ }
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
+ if (rv != SECSuccess) {
+ goto done; /* err code was set by ssl3_SignHashes */
+ }
+
+ len = buf.len + 2 + (isTLS12 ? 2 : 0);
+
+ rv = ssl3_AppendHandshakeHeader(ss, certificate_verify, len);
+ if (rv != SECSuccess) {
+ goto done; /* error code set by AppendHandshake */
+ }
+ if (isTLS12) {
+ rv = ssl3_TLSSignatureAlgorithmForKeyType(keyType,
+ &sigAndHash.sigAlg);
+ if (rv != SECSuccess) {
+ goto done;
+ }
+ /* We always sign using the handshake hash function. It's possible that
+ * a server could support SHA-256 as the handshake hash but not as a
+ * signature hash. In that case we wouldn't be able to do client
+ * certificates with it. The alternative is to buffer all handshake
+ * messages. */
+ sigAndHash.hashAlg = hashes.hashAlg;
+
+ rv = ssl3_AppendSignatureAndHashAlgorithm(ss, &sigAndHash);
+ if (rv != SECSuccess) {
+ goto done; /* err set by AppendHandshake. */
+ }
+ }
+ rv = ssl3_AppendHandshakeVariable(ss, buf.data, buf.len, 2);
+ if (rv != SECSuccess) {
+ goto done; /* error code set by AppendHandshake */
+ }
+
+done:
+ if (buf.data)
+ PORT_Free(buf.data);
+ return rv;
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 ServerHello message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ sslSessionID *sid = ss->sec.ci.sid;
+ PRInt32 temp; /* allow for consume number failure */
+ PRBool suite_found = PR_FALSE;
+ int i;
+ int errCode = SSL_ERROR_RX_MALFORMED_SERVER_HELLO;
+ SECStatus rv;
+ SECItem sidBytes = {siBuffer, NULL, 0};
+ PRBool sid_match;
+ PRBool isTLS = PR_FALSE;
+ SSL3AlertDescription desc = illegal_parameter;
+ SSL3ProtocolVersion version;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle server_hello handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->ssl3.initialized );
+
+ if (ss->ssl3.hs.ws != wait_server_hello) {
+ errCode = SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO;
+ desc = unexpected_message;
+ goto alert_loser;
+ }
+
+ /* clean up anything left from previous handshake. */
+ if (ss->ssl3.clientCertChain != NULL) {
+ CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
+ ss->ssl3.clientCertChain = NULL;
+ }
+ if (ss->ssl3.clientCertificate != NULL) {
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+ ss->ssl3.clientCertificate = NULL;
+ }
+ if (ss->ssl3.clientPrivateKey != NULL) {
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (ss->ssl3.platformClientKey) {
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+ ss->ssl3.platformClientKey = (PlatformKey)NULL;
+ }
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ if (ss->ssl3.channelID != NULL) {
+ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
+ ss->ssl3.channelID = NULL;
+ }
+ if (ss->ssl3.channelIDPub != NULL) {
+ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
+ ss->ssl3.channelIDPub = NULL;
+ }
+
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (temp < 0) {
+ goto loser; /* alert has been sent */
+ }
+ version = (SSL3ProtocolVersion)temp;
+
+ if (IS_DTLS(ss)) {
+ /* RFC 4347 required that you verify that the server versions
+ * match (Section 4.2.1) in the HelloVerifyRequest and the
+ * ServerHello.
+ *
+ * RFC 6347 suggests (SHOULD) that servers always use 1.0
+ * in HelloVerifyRequest and allows the versions not to match,
+ * especially when 1.2 is being negotiated.
+ *
+ * Therefore we do not check for matching here.
+ */
+ version = dtls_DTLSVersionToTLSVersion(version);
+ if (version == 0) { /* Insane version number */
+ goto alert_loser;
+ }
+ }
+
+ rv = ssl3_NegotiateVersion(ss, version, PR_FALSE);
+ if (rv != SECSuccess) {
+ desc = (version > SSL_LIBRARY_VERSION_3_0) ? protocol_version
+ : handshake_failure;
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+ }
+ isTLS = (ss->version > SSL_LIBRARY_VERSION_3_0);
+
+ rv = ssl3_InitHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ desc = internal_error;
+ errCode = PORT_GetError();
+ goto alert_loser;
+ }
+
+ rv = ssl3_ConsumeHandshake(
+ ss, &ss->ssl3.hs.server_random, SSL3_RANDOM_LENGTH, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* alert has been sent */
+ }
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &sidBytes, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* alert has been sent */
+ }
+ if (sidBytes.len > SSL3_SESSIONID_BYTES) {
+ if (isTLS)
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ /* find selected cipher suite in our list. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (temp < 0) {
+ goto loser; /* alert has been sent */
+ }
+ ssl3_config_match_init(ss);
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
+ if (temp == suite->cipher_suite) {
+ if (!config_match(suite, ss->ssl3.policy, PR_TRUE)) {
+ break; /* failure */
+ }
+ if (!ssl3_CipherSuiteAllowedForVersion(suite->cipher_suite,
+ ss->version)) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION;
+ goto alert_loser;
+ }
+
+ suite_found = PR_TRUE;
+ break; /* success */
+ }
+ }
+ if (!suite_found) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+ }
+ ss->ssl3.hs.cipher_suite = (ssl3CipherSuite)temp;
+ ss->ssl3.hs.suite_def = ssl_LookupCipherSuiteDef((ssl3CipherSuite)temp);
+ PORT_Assert(ss->ssl3.hs.suite_def);
+ if (!ss->ssl3.hs.suite_def) {
+ PORT_SetError(errCode = SEC_ERROR_LIBRARY_FAILURE);
+ goto loser; /* we don't send alerts for our screw-ups. */
+ }
+
+ /* find selected compression method in our list. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &b, &length);
+ if (temp < 0) {
+ goto loser; /* alert has been sent */
+ }
+ suite_found = PR_FALSE;
+ for (i = 0; i < compressionMethodsCount; i++) {
+ if (temp == compressions[i]) {
+ if (!compressionEnabled(ss, compressions[i])) {
+ break; /* failure */
+ }
+ suite_found = PR_TRUE;
+ break; /* success */
+ }
+ }
+ if (!suite_found) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_NO_COMPRESSION_OVERLAP;
+ goto alert_loser;
+ }
+ ss->ssl3.hs.compression = (SSLCompressionMethod)temp;
+
+ /* Note that if !isTLS and the extra stuff is not extensions, we
+ * do NOT goto alert_loser.
+ * There are some old SSL 3.0 implementations that do send stuff
+ * after the end of the server hello, and we deliberately ignore
+ * such stuff in the interest of maximal interoperability (being
+ * "generous in what you accept").
+ * Update: Starting in NSS 3.12.6, we handle the renegotiation_info
+ * extension in SSL 3.0.
+ */
+ if (length != 0) {
+ SECItem extensions;
+ rv = ssl3_ConsumeHandshakeVariable(ss, &extensions, 2, &b, &length);
+ if (rv != SECSuccess || length != 0) {
+ if (isTLS)
+ goto alert_loser;
+ } else {
+ rv = ssl3_HandleHelloExtensions(ss, &extensions.data,
+ &extensions.len);
+ if (rv != SECSuccess)
+ goto alert_loser;
+ }
+ }
+ if ((ss->opt.requireSafeNegotiation ||
+ (ss->firstHsDone && (ss->peerRequestedProtection ||
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_REQUIRES_XTN))) &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = ss->firstHsDone ? SSL_ERROR_RENEGOTIATION_NOT_ALLOWED
+ : SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
+ }
+
+ /* Any errors after this point are not "malformed" errors. */
+ desc = handshake_failure;
+
+ /* we need to call ssl3_SetupPendingCipherSpec here so we can check the
+ * key exchange algorithm. */
+ rv = ssl3_SetupPendingCipherSpec(ss);
+ if (rv != SECSuccess) {
+ goto alert_loser; /* error code is set. */
+ }
+
+ /* We may or may not have sent a session id, we may get one back or
+ * not and if so it may match the one we sent.
+ * Attempt to restore the master secret to see if this is so...
+ * Don't consider failure to find a matching SID an error.
+ */
+ sid_match = (PRBool)(sidBytes.len > 0 &&
+ sidBytes.len == sid->u.ssl3.sessionIDLength &&
+ !PORT_Memcmp(sid->u.ssl3.sessionID, sidBytes.data, sidBytes.len));
+
+ if (sid_match &&
+ sid->version == ss->version &&
+ sid->u.ssl3.cipherSuite == ss->ssl3.hs.cipher_suite) do {
+ ssl3CipherSpec *pwSpec = ss->ssl3.pwSpec;
+
+ SECItem wrappedMS; /* wrapped master secret. */
+
+ ss->sec.authAlgorithm = sid->authAlgorithm;
+ ss->sec.authKeyBits = sid->authKeyBits;
+ ss->sec.keaType = sid->keaType;
+ ss->sec.keaKeyBits = sid->keaKeyBits;
+
+ /* 3 cases here:
+ * a) key is wrapped (implies using PKCS11)
+ * b) key is unwrapped, but we're still using PKCS11
+ * c) key is unwrapped, and we're bypassing PKCS11.
+ */
+ if (sid->u.ssl3.keys.msIsWrapped) {
+ PK11SlotInfo *slot;
+ PK11SymKey * wrapKey; /* wrapping key */
+ CK_FLAGS keyFlags = 0;
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ /* we cannot restart a non-bypass session in a
+ ** bypass socket.
+ */
+ break;
+ }
+#endif
+ /* unwrap master secret with PKCS11 */
+ slot = SECMOD_LookupSlot(sid->u.ssl3.masterModuleID,
+ sid->u.ssl3.masterSlotID);
+ if (slot == NULL) {
+ break; /* not considered an error. */
+ }
+ if (!PK11_IsPresent(slot)) {
+ PK11_FreeSlot(slot);
+ break; /* not considered an error. */
+ }
+ wrapKey = PK11_GetWrapKey(slot, sid->u.ssl3.masterWrapIndex,
+ sid->u.ssl3.masterWrapMech,
+ sid->u.ssl3.masterWrapSeries,
+ ss->pkcs11PinArg);
+ PK11_FreeSlot(slot);
+ if (wrapKey == NULL) {
+ break; /* not considered an error. */
+ }
+
+ if (ss->version > SSL_LIBRARY_VERSION_3_0) { /* isTLS */
+ keyFlags = CKF_SIGN | CKF_VERIFY;
+ }
+
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+ pwSpec->master_secret =
+ PK11_UnwrapSymKeyWithFlags(wrapKey, sid->u.ssl3.masterWrapMech,
+ NULL, &wrappedMS, CKM_SSL3_MASTER_KEY_DERIVE,
+ CKA_DERIVE, sizeof(SSL3MasterSecret), keyFlags);
+ errCode = PORT_GetError();
+ PK11_FreeSymKey(wrapKey);
+ if (pwSpec->master_secret == NULL) {
+ break; /* errorCode set just after call to UnwrapSymKey. */
+ }
+#ifndef NO_PKCS11_BYPASS
+ } else if (ss->opt.bypassPKCS11) {
+ /* MS is not wrapped */
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+ memcpy(pwSpec->raw_master_secret, wrappedMS.data, wrappedMS.len);
+ pwSpec->msItem.data = pwSpec->raw_master_secret;
+ pwSpec->msItem.len = wrappedMS.len;
+#endif
+ } else {
+ /* We CAN restart a bypass session in a non-bypass socket. */
+ /* need to import the raw master secret to session object */
+ PK11SlotInfo *slot = PK11_GetInternalSlot();
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+ pwSpec->master_secret =
+ PK11_ImportSymKey(slot, CKM_SSL3_MASTER_KEY_DERIVE,
+ PK11_OriginUnwrap, CKA_ENCRYPT,
+ &wrappedMS, NULL);
+ PK11_FreeSlot(slot);
+ if (pwSpec->master_secret == NULL) {
+ break;
+ }
+ }
+
+ /* Got a Match */
+ SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_cache_hits );
+
+ /* If we sent a session ticket, then this is a stateless resume. */
+ if (sid->version > SSL_LIBRARY_VERSION_3_0 &&
+ sid->u.ssl3.sessionTicket.ticket.data != NULL)
+ SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_stateless_resumes );
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn))
+ ss->ssl3.hs.ws = wait_new_session_ticket;
+ else
+ ss->ssl3.hs.ws = wait_change_cipher;
+
+ ss->ssl3.hs.isResuming = PR_TRUE;
+
+ /* copy the peer cert from the SID */
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
+ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+ /* NULL value for PMS signifies re-use of the old MS */
+ rv = ssl3_InitPendingCipherSpec(ss, NULL);
+ if (rv != SECSuccess) {
+ goto alert_loser; /* err code was set */
+ }
+ goto winner;
+ } while (0);
+
+ if (sid_match)
+ SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_cache_not_ok );
+ else
+ SSL_AtomicIncrementLong(& ssl3stats.hsh_sid_cache_misses );
+
+ /* throw the old one away */
+ sid->u.ssl3.keys.resumable = PR_FALSE;
+ if (ss->sec.uncache)
+ (*ss->sec.uncache)(sid);
+ ssl_FreeSID(sid);
+
+ /* get a new sid */
+ ss->sec.ci.sid = sid = ssl3_NewSessionID(ss, PR_FALSE);
+ if (sid == NULL) {
+ goto alert_loser; /* memory error is set. */
+ }
+
+ sid->version = ss->version;
+ sid->u.ssl3.sessionIDLength = sidBytes.len;
+ PORT_Memcpy(sid->u.ssl3.sessionID, sidBytes.data, sidBytes.len);
+
+ ss->ssl3.hs.isResuming = PR_FALSE;
+ ss->ssl3.hs.ws = wait_server_cert;
+
+winner:
+ /* If we will need a ChannelID key then we make the callback now. This
+ * allows the handshake to be restarted cleanly if the callback returns
+ * SECWouldBlock. */
+ if (ssl3_ExtensionNegotiated(ss, ssl_channel_id_xtn)) {
+ rv = ss->getChannelID(ss->getChannelIDArg, ss->fd,
+ &ss->ssl3.channelIDPub, &ss->ssl3.channelID);
+ if (rv == SECWouldBlock) {
+ ssl3_SetAlwaysBlock(ss);
+ return rv;
+ }
+ if (rv != SECSuccess ||
+ ss->ssl3.channelIDPub == NULL ||
+ ss->ssl3.channelID == NULL) {
+ PORT_SetError(SSL_ERROR_GET_CHANNEL_ID_FAILED);
+ desc = internal_error;
+ goto alert_loser;
+ }
+ }
+
+ return SECSuccess;
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+
+loser:
+ errCode = ssl_MapLowLevelError(errCode);
+ return SECFailure;
+}
+
+/* ssl3_BigIntGreaterThanOne returns true iff |mpint|, taken as an unsigned,
+ * big-endian integer is > 1 */
+static PRBool
+ssl3_BigIntGreaterThanOne(const SECItem* mpint) {
+ unsigned char firstNonZeroByte = 0;
+ unsigned int i;
+
+ for (i = 0; i < mpint->len; i++) {
+ if (mpint->data[i]) {
+ firstNonZeroByte = mpint->data[i];
+ break;
+ }
+ }
+
+ if (firstNonZeroByte == 0)
+ return PR_FALSE;
+ if (firstNonZeroByte > 1)
+ return PR_TRUE;
+
+ /* firstNonZeroByte == 1, therefore mpint > 1 iff the first non-zero byte
+ * is followed by another byte. */
+ return (i < mpint->len - 1);
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 ServerKeyExchange message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleServerKeyExchange(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ PLArenaPool * arena = NULL;
+ SECKEYPublicKey *peerKey = NULL;
+ PRBool isTLS, isTLS12;
+ SECStatus rv;
+ int errCode = SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH;
+ SSL3AlertDescription desc = illegal_parameter;
+ SSL3Hashes hashes;
+ SECItem signature = {siBuffer, NULL, 0};
+ SSL3SignatureAndHashAlgorithm sigAndHash;
+
+ sigAndHash.hashAlg = SEC_OID_UNKNOWN;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle server_key_exchange handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ss->ssl3.hs.ws != wait_server_key &&
+ ss->ssl3.hs.ws != wait_server_cert) {
+ errCode = SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH;
+ desc = unexpected_message;
+ goto alert_loser;
+ }
+ if (ss->sec.peerCert == NULL) {
+ errCode = SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH;
+ desc = unexpected_message;
+ goto alert_loser;
+ }
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ switch (ss->ssl3.hs.kea_def->exchKeyType) {
+
+ case kt_rsa: {
+ SECItem modulus = {siBuffer, NULL, 0};
+ SECItem exponent = {siBuffer, NULL, 0};
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &modulus, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &exponent, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (isTLS12) {
+ rv = ssl3_ConsumeSignatureAndHashAlgorithm(ss, &b, &length,
+ &sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed or unsupported. */
+ }
+ rv = ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ &sigAndHash, ss->sec.peerCert);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &signature, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (length != 0) {
+ if (isTLS)
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ /* failures after this point are not malformed handshakes. */
+ /* TLS: send decrypt_error if signature failed. */
+ desc = isTLS ? decrypt_error : handshake_failure;
+
+ /*
+ * check to make sure the hash is signed by right guy
+ */
+ rv = ssl3_ComputeExportRSAKeyHash(sigAndHash.hashAlg, modulus, exponent,
+ &ss->ssl3.hs.client_random,
+ &ss->ssl3.hs.server_random,
+ &hashes, ss->opt.bypassPKCS11);
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+ rv = ssl3_VerifySignedHashes(&hashes, ss->sec.peerCert, &signature,
+ isTLS, ss->pkcs11PinArg);
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+
+ /*
+ * we really need to build a new key here because we can no longer
+ * ignore calling SECKEY_DestroyPublicKey. Using the key may allocate
+ * pkcs11 slots and ID's.
+ */
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ goto no_memory;
+ }
+
+ peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
+ if (peerKey == NULL) {
+ PORT_FreeArena(arena, PR_FALSE);
+ goto no_memory;
+ }
+
+ peerKey->arena = arena;
+ peerKey->keyType = rsaKey;
+ peerKey->pkcs11Slot = NULL;
+ peerKey->pkcs11ID = CK_INVALID_HANDLE;
+ if (SECITEM_CopyItem(arena, &peerKey->u.rsa.modulus, &modulus) ||
+ SECITEM_CopyItem(arena, &peerKey->u.rsa.publicExponent, &exponent))
+ {
+ PORT_FreeArena(arena, PR_FALSE);
+ goto no_memory;
+ }
+ ss->sec.peerKey = peerKey;
+ ss->ssl3.hs.ws = wait_cert_request;
+ return SECSuccess;
+ }
+
+ case kt_dh: {
+ SECItem dh_p = {siBuffer, NULL, 0};
+ SECItem dh_g = {siBuffer, NULL, 0};
+ SECItem dh_Ys = {siBuffer, NULL, 0};
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &dh_p, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (dh_p.len < 512/8) {
+ errCode = SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY;
+ goto alert_loser;
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &dh_g, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (dh_g.len > dh_p.len || !ssl3_BigIntGreaterThanOne(&dh_g))
+ goto alert_loser;
+ rv = ssl3_ConsumeHandshakeVariable(ss, &dh_Ys, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (dh_Ys.len > dh_p.len || !ssl3_BigIntGreaterThanOne(&dh_Ys))
+ goto alert_loser;
+ if (isTLS12) {
+ rv = ssl3_ConsumeSignatureAndHashAlgorithm(ss, &b, &length,
+ &sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed or unsupported. */
+ }
+ rv = ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ &sigAndHash, ss->sec.peerCert);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &signature, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ if (length != 0) {
+ if (isTLS)
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ PRINT_BUF(60, (NULL, "Server DH p", dh_p.data, dh_p.len));
+ PRINT_BUF(60, (NULL, "Server DH g", dh_g.data, dh_g.len));
+ PRINT_BUF(60, (NULL, "Server DH Ys", dh_Ys.data, dh_Ys.len));
+
+ /* failures after this point are not malformed handshakes. */
+ /* TLS: send decrypt_error if signature failed. */
+ desc = isTLS ? decrypt_error : handshake_failure;
+
+ /*
+ * check to make sure the hash is signed by right guy
+ */
+ rv = ssl3_ComputeDHKeyHash(sigAndHash.hashAlg, dh_p, dh_g, dh_Ys,
+ &ss->ssl3.hs.client_random,
+ &ss->ssl3.hs.server_random,
+ &hashes, ss->opt.bypassPKCS11);
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+ rv = ssl3_VerifySignedHashes(&hashes, ss->sec.peerCert, &signature,
+ isTLS, ss->pkcs11PinArg);
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+
+ /*
+ * we really need to build a new key here because we can no longer
+ * ignore calling SECKEY_DestroyPublicKey. Using the key may allocate
+ * pkcs11 slots and ID's.
+ */
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ goto no_memory;
+ }
+
+ ss->sec.peerKey = peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
+ if (peerKey == NULL) {
+ goto no_memory;
+ }
+
+ peerKey->arena = arena;
+ peerKey->keyType = dhKey;
+ peerKey->pkcs11Slot = NULL;
+ peerKey->pkcs11ID = CK_INVALID_HANDLE;
+
+ if (SECITEM_CopyItem(arena, &peerKey->u.dh.prime, &dh_p) ||
+ SECITEM_CopyItem(arena, &peerKey->u.dh.base, &dh_g) ||
+ SECITEM_CopyItem(arena, &peerKey->u.dh.publicValue, &dh_Ys))
+ {
+ PORT_FreeArena(arena, PR_FALSE);
+ goto no_memory;
+ }
+ ss->sec.peerKey = peerKey;
+ ss->ssl3.hs.ws = wait_cert_request;
+ return SECSuccess;
+ }
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh:
+ rv = ssl3_HandleECDHServerKeyExchange(ss, b, length);
+ return rv;
+#endif /* NSS_ENABLE_ECC */
+
+ default:
+ desc = handshake_failure;
+ errCode = SEC_ERROR_UNSUPPORTED_KEYALG;
+ break; /* goto alert_loser; */
+ }
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+loser:
+ PORT_SetError( errCode );
+ return SECFailure;
+
+no_memory: /* no-memory error has already been set. */
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+}
+
+
+typedef struct dnameNode {
+ struct dnameNode *next;
+ SECItem name;
+} dnameNode;
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Certificate Request message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleCertificateRequest(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ PLArenaPool * arena = NULL;
+ dnameNode * node;
+ PRInt32 remaining;
+ PRBool isTLS = PR_FALSE;
+ PRBool isTLS12 = PR_FALSE;
+ int i;
+ int errCode = SSL_ERROR_RX_MALFORMED_CERT_REQUEST;
+ int nnames = 0;
+ SECStatus rv;
+ SSL3AlertDescription desc = illegal_parameter;
+ SECItem cert_types = {siBuffer, NULL, 0};
+ SECItem algorithms = {siBuffer, NULL, 0};
+ CERTDistNames ca_list;
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ CERTCertList * platform_cert_list = NULL;
+ CERTCertListNode * certNode = NULL;
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle certificate_request handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ss->ssl3.hs.ws != wait_cert_request &&
+ ss->ssl3.hs.ws != wait_server_key) {
+ desc = unexpected_message;
+ errCode = SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST;
+ goto alert_loser;
+ }
+
+ PORT_Assert(ss->ssl3.clientCertChain == NULL);
+ PORT_Assert(ss->ssl3.clientCertificate == NULL);
+ PORT_Assert(ss->ssl3.clientPrivateKey == NULL);
+ PORT_Assert(ss->ssl3.platformClientKey == (PlatformKey)NULL);
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+ rv = ssl3_ConsumeHandshakeVariable(ss, &cert_types, 1, &b, &length);
+ if (rv != SECSuccess)
+ goto loser; /* malformed, alert has been sent */
+
+ PORT_Assert(!ss->requestedCertTypes);
+ ss->requestedCertTypes = &cert_types;
+
+ if (isTLS12) {
+ rv = ssl3_ConsumeHandshakeVariable(ss, &algorithms, 2, &b, &length);
+ if (rv != SECSuccess)
+ goto loser; /* malformed, alert has been sent */
+ /* An empty or odd-length value is invalid.
+ * SignatureAndHashAlgorithm
+ * supported_signature_algorithms<2..2^16-2>;
+ */
+ if (algorithms.len == 0 || (algorithms.len & 1) != 0)
+ goto alert_loser;
+ }
+
+ arena = ca_list.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL)
+ goto no_mem;
+
+ remaining = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (remaining < 0)
+ goto loser; /* malformed, alert has been sent */
+
+ if ((PRUint32)remaining > length)
+ goto alert_loser;
+
+ ca_list.head = node = PORT_ArenaZNew(arena, dnameNode);
+ if (node == NULL)
+ goto no_mem;
+
+ while (remaining > 0) {
+ PRInt32 len;
+
+ if (remaining < 2)
+ goto alert_loser; /* malformed */
+
+ node->name.len = len = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (len <= 0)
+ goto loser; /* malformed, alert has been sent */
+
+ remaining -= 2;
+ if (remaining < len)
+ goto alert_loser; /* malformed */
+
+ node->name.data = b;
+ b += len;
+ length -= len;
+ remaining -= len;
+ nnames++;
+ if (remaining <= 0)
+ break; /* success */
+
+ node->next = PORT_ArenaZNew(arena, dnameNode);
+ node = node->next;
+ if (node == NULL)
+ goto no_mem;
+ }
+
+ ca_list.nnames = nnames;
+ ca_list.names = PORT_ArenaNewArray(arena, SECItem, nnames);
+ if (nnames > 0 && ca_list.names == NULL)
+ goto no_mem;
+
+ for(i = 0, node = (dnameNode*)ca_list.head;
+ i < nnames;
+ i++, node = node->next) {
+ ca_list.names[i] = node->name;
+ }
+
+ if (length != 0)
+ goto alert_loser; /* malformed */
+
+ desc = no_certificate;
+ ss->ssl3.hs.ws = wait_hello_done;
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (ss->getPlatformClientAuthData != NULL) {
+ /* XXX Should pass cert_types and algorithms in this call!! */
+ rv = (SECStatus)(*ss->getPlatformClientAuthData)(
+ ss->getPlatformClientAuthDataArg,
+ ss->fd, &ca_list,
+ &platform_cert_list,
+ (void**)&ss->ssl3.platformClientKey,
+ &ss->ssl3.clientCertificate,
+ &ss->ssl3.clientPrivateKey);
+ } else
+#endif
+ if (ss->getClientAuthData != NULL) {
+ /* XXX Should pass cert_types and algorithms in this call!! */
+ rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg,
+ ss->fd, &ca_list,
+ &ss->ssl3.clientCertificate,
+ &ss->ssl3.clientPrivateKey);
+ } else {
+ rv = SECFailure; /* force it to send a no_certificate alert */
+ }
+
+ switch (rv) {
+ case SECWouldBlock: /* getClientAuthData has put up a dialog box. */
+ ssl3_SetAlwaysBlock(ss);
+ break; /* not an error */
+
+ case SECSuccess:
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (!platform_cert_list || CERT_LIST_EMPTY(platform_cert_list) ||
+ !ss->ssl3.platformClientKey) {
+ if (platform_cert_list) {
+ CERT_DestroyCertList(platform_cert_list);
+ platform_cert_list = NULL;
+ }
+ if (ss->ssl3.platformClientKey) {
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+ ss->ssl3.platformClientKey = (PlatformKey)NULL;
+ }
+ /* Fall through to NSS client auth check */
+ } else {
+ certNode = CERT_LIST_HEAD(platform_cert_list);
+ ss->ssl3.clientCertificate = CERT_DupCertificate(certNode->cert);
+
+ /* Setting ssl3.clientCertChain non-NULL will cause
+ * ssl3_HandleServerHelloDone to call SendCertificate.
+ * Note: clientCertChain should include the EE cert as
+ * clientCertificate is ignored during the actual sending
+ */
+ ss->ssl3.clientCertChain =
+ hack_NewCertificateListFromCertList(platform_cert_list);
+ CERT_DestroyCertList(platform_cert_list);
+ platform_cert_list = NULL;
+ if (ss->ssl3.clientCertChain == NULL) {
+ if (ss->ssl3.clientCertificate != NULL) {
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+ ss->ssl3.clientCertificate = NULL;
+ }
+ if (ss->ssl3.platformClientKey) {
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+ ss->ssl3.platformClientKey = (PlatformKey)NULL;
+ }
+ goto send_no_certificate;
+ }
+ break; /* not an error */
+ }
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ /* check what the callback function returned */
+ if ((!ss->ssl3.clientCertificate) || (!ss->ssl3.clientPrivateKey)) {
+ /* we are missing either the key or cert */
+ if (ss->ssl3.clientCertificate) {
+ /* got a cert, but no key - free it */
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+ ss->ssl3.clientCertificate = NULL;
+ }
+ if (ss->ssl3.clientPrivateKey) {
+ /* got a key, but no cert - free it */
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
+ goto send_no_certificate;
+ }
+ /* Setting ssl3.clientCertChain non-NULL will cause
+ * ssl3_HandleServerHelloDone to call SendCertificate.
+ */
+ ss->ssl3.clientCertChain = CERT_CertChainFromCert(
+ ss->ssl3.clientCertificate,
+ certUsageSSLClient, PR_FALSE);
+ if (ss->ssl3.clientCertChain == NULL) {
+ if (ss->ssl3.clientCertificate != NULL) {
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+ ss->ssl3.clientCertificate = NULL;
+ }
+ if (ss->ssl3.clientPrivateKey != NULL) {
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
+ goto send_no_certificate;
+ }
+ break; /* not an error */
+
+ case SECFailure:
+ default:
+send_no_certificate:
+ if (isTLS) {
+ ss->ssl3.sendEmptyCert = PR_TRUE;
+ } else {
+ (void)SSL3_SendAlert(ss, alert_warning, no_certificate);
+ }
+ rv = SECSuccess;
+ break;
+ }
+ goto done;
+
+no_mem:
+ rv = SECFailure;
+ PORT_SetError(SEC_ERROR_NO_MEMORY);
+ goto done;
+
+alert_loser:
+ if (isTLS && desc == illegal_parameter)
+ desc = decode_error;
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+loser:
+ PORT_SetError(errCode);
+ rv = SECFailure;
+done:
+ ss->requestedCertTypes = NULL;
+ if (arena != NULL)
+ PORT_FreeArena(arena, PR_FALSE);
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (platform_cert_list)
+ CERT_DestroyCertList(platform_cert_list);
+#endif
+ return rv;
+}
+
+/*
+ * attempt to restart the handshake after asynchronously handling
+ * a request for the client's certificate.
+ *
+ * inputs:
+ * cert Client cert chosen by application.
+ * Note: ssl takes this reference, and does not bump the
+ * reference count. The caller should drop its reference
+ * without calling CERT_DestroyCert after calling this function.
+ *
+ * key Private key associated with cert. This function takes
+ * ownership of the private key, so the caller should drop its
+ * reference without destroying the private key after this
+ * function returns.
+ *
+ * certChain DER-encoded certs, client cert and its signers.
+ * Note: ssl takes this reference, and does not copy the chain.
+ * The caller should drop its reference without destroying the
+ * chain. SSL will free the chain when it is done with it.
+ *
+ * Return value: XXX
+ *
+ * XXX This code only works on the initial handshake on a connection, XXX
+ * It does not work on a subsequent handshake (redo).
+ *
+ * Caller holds 1stHandshakeLock.
+ */
+SECStatus
+ssl3_RestartHandshakeAfterCertReq(sslSocket * ss,
+ CERTCertificate * cert,
+ SECKEYPrivateKey * key,
+ CERTCertificateList *certChain)
+{
+ SECStatus rv = SECSuccess;
+
+ /* XXX This code only works on the initial handshake on a connection,
+ ** XXX It does not work on a subsequent handshake (redo).
+ */
+ if (ss->handshake != 0) {
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->ssl3.clientCertificate = cert;
+ ss->ssl3.clientPrivateKey = key;
+ ss->ssl3.clientCertChain = certChain;
+ if (!cert || !key || !certChain) {
+ /* we are missing the key, cert, or cert chain */
+ if (ss->ssl3.clientCertificate) {
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+ ss->ssl3.clientCertificate = NULL;
+ }
+ if (ss->ssl3.clientPrivateKey) {
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+ ss->ssl3.clientPrivateKey = NULL;
+ }
+ if (ss->ssl3.clientCertChain != NULL) {
+ CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
+ ss->ssl3.clientCertChain = NULL;
+ }
+ if (ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0) {
+ ss->ssl3.sendEmptyCert = PR_TRUE;
+ } else {
+ (void)SSL3_SendAlert(ss, alert_warning, no_certificate);
+ }
+ }
+ } else {
+ if (cert) {
+ CERT_DestroyCertificate(cert);
+ }
+ if (key) {
+ SECKEY_DestroyPrivateKey(key);
+ }
+ if (certChain) {
+ CERT_DestroyCertificateList(certChain);
+ }
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+ }
+ return rv;
+}
+
+PRBool
+ssl3_CanFalseStart(sslSocket *ss) {
+ PRBool rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ /* XXX: does not take into account whether we are waiting for
+ * SSL_AuthCertificateComplete or SSL_RestartHandshakeAfterCertReq. If/when
+ * that is done, this function could return different results each time it
+ * would be called.
+ */
+
+ ssl_GetSpecReadLock(ss);
+ rv = ss->opt.enableFalseStart &&
+ !ss->sec.isServer &&
+ !ss->ssl3.hs.isResuming &&
+ ss->ssl3.cwSpec &&
+
+ /* An attacker can control the selected ciphersuite so we only wish to
+ * do False Start in the case that the selected ciphersuite is
+ * sufficiently strong that the attack can gain no advantage.
+ * Therefore we require an 80-bit cipher and a forward-secret key
+ * exchange. */
+ ss->ssl3.cwSpec->cipher_def->secret_key_size >= 10 &&
+ (ss->ssl3.hs.kea_def->kea == kea_dhe_dss ||
+ ss->ssl3.hs.kea_def->kea == kea_dhe_rsa ||
+ ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa ||
+ ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa);
+ ssl_ReleaseSpecReadLock(ss);
+ return rv;
+}
+
+static SECStatus ssl3_SendClientSecondRound(sslSocket *ss);
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Server Hello Done message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleServerHelloDone(sslSocket *ss)
+{
+ SECStatus rv;
+ SSL3WaitState ws = ss->ssl3.hs.ws;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle server_hello_done handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ws != wait_hello_done &&
+ ws != wait_server_cert &&
+ ws != wait_server_key &&
+ ws != wait_cert_request) {
+ SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_DONE);
+ return SECFailure;
+ }
+
+ rv = ssl3_SendClientSecondRound(ss);
+
+ return rv;
+}
+
+/* Called from ssl3_HandleServerHelloDone and ssl3_AuthCertificateComplete.
+ *
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_SendClientSecondRound(sslSocket *ss)
+{
+ SECStatus rv;
+ PRBool sendClientCert;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ sendClientCert = !ss->ssl3.sendEmptyCert &&
+ ss->ssl3.clientCertChain != NULL &&
+ (ss->ssl3.platformClientKey ||
+ ss->ssl3.clientPrivateKey != NULL);
+
+ /* We must wait for the server's certificate to be authenticated before
+ * sending the client certificate in order to disclosing the client
+ * certificate to an attacker that does not have a valid cert for the
+ * domain we are connecting to.
+ *
+ * XXX: We should do the same for the NPN extension, but for that we
+ * need an option to give the application the ability to leak the NPN
+ * information to get better performance.
+ *
+ * During the initial handshake on a connection, we never send/receive
+ * application data until we have authenticated the server's certificate;
+ * i.e. we have fully authenticated the handshake before using the cipher
+ * specs agreed upon for that handshake. During a renegotiation, we may
+ * continue sending and receiving application data during the handshake
+ * interleaved with the handshake records. If we were to send the client's
+ * second round for a renegotiation before the server's certificate was
+ * authenticated, then the application data sent/received after this point
+ * would be using cipher spec that hadn't been authenticated. By waiting
+ * until the server's certificate has been authenticated during
+ * renegotiations, we ensure that renegotiations have the same property
+ * as initial handshakes; i.e. we have fully authenticated the handshake
+ * before using the cipher specs agreed upon for that handshake for
+ * application data.
+ */
+ if (ss->ssl3.hs.restartTarget) {
+ PR_NOT_REACHED("unexpected ss->ssl3.hs.restartTarget");
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ if (ss->ssl3.hs.authCertificatePending &&
+ (sendClientCert || ss->ssl3.sendEmptyCert || ss->firstHsDone)) {
+ ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound;
+ return SECWouldBlock;
+ }
+
+ ssl_GetXmitBufLock(ss); /*******************************/
+
+ if (ss->ssl3.sendEmptyCert) {
+ ss->ssl3.sendEmptyCert = PR_FALSE;
+ rv = ssl3_SendEmptyCertificate(ss);
+ /* Don't send verify */
+ if (rv != SECSuccess) {
+ goto loser; /* error code is set. */
+ }
+ } else if (sendClientCert) {
+ rv = ssl3_SendCertificate(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* error code is set. */
+ }
+ }
+
+ rv = ssl3_SendClientKeyExchange(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* err is set. */
+ }
+
+ if (sendClientCert) {
+ rv = ssl3_SendCertificateVerify(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* err is set. */
+ }
+ }
+
+ rv = ssl3_SendChangeCipherSpecs(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+ }
+
+ /* XXX: If the server's certificate hasn't been authenticated by this
+ * point, then we may be leaking this NPN message to an attacker.
+ */
+ if (!ss->firstHsDone) {
+ rv = ssl3_SendNextProto(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+ }
+ }
+ rv = ssl3_SendEncryptedExtensions(ss);
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+ }
+
+ rv = ssl3_SendFinished(ss, 0);
+ if (rv != SECSuccess) {
+ goto loser; /* err code was set. */
+ }
+
+ ssl_ReleaseXmitBufLock(ss); /*******************************/
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn))
+ ss->ssl3.hs.ws = wait_new_session_ticket;
+ else
+ ss->ssl3.hs.ws = wait_change_cipher;
+
+ /* Do the handshake callback for sslv3 here, if we can false start. */
+ if (ss->handshakeCallback != NULL && ssl3_CanFalseStart(ss)) {
+ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
+ }
+
+ return SECSuccess;
+
+loser:
+ ssl_ReleaseXmitBufLock(ss);
+ return rv;
+}
+
+/*
+ * Routines used by servers
+ */
+static SECStatus
+ssl3_SendHelloRequest(sslSocket *ss)
+{
+ SECStatus rv;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send hello_request handshake", SSL_GETPID(),
+ ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ rv = ssl3_AppendHandshakeHeader(ss, hello_request, 0);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake */
+ }
+ rv = ssl3_FlushHandshake(ss, 0);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by ssl3_FlushHandshake */
+ }
+ ss->ssl3.hs.ws = wait_client_hello;
+ return SECSuccess;
+}
+
+/*
+ * Called from:
+ * ssl3_HandleClientHello()
+ */
+static SECComparison
+ssl3_ServerNameCompare(const SECItem *name1, const SECItem *name2)
+{
+ if (!name1 != !name2) {
+ return SECLessThan;
+ }
+ if (!name1) {
+ return SECEqual;
+ }
+ if (name1->type != name2->type) {
+ return SECLessThan;
+ }
+ return SECITEM_CompareItem(name1, name2);
+}
+
+/* Sets memory error when returning NULL.
+ * Called from:
+ * ssl3_SendClientHello()
+ * ssl3_HandleServerHello()
+ * ssl3_HandleClientHello()
+ * ssl3_HandleV2ClientHello()
+ */
+sslSessionID *
+ssl3_NewSessionID(sslSocket *ss, PRBool is_server)
+{
+ sslSessionID *sid;
+
+ sid = PORT_ZNew(sslSessionID);
+ if (sid == NULL)
+ return sid;
+
+ if (is_server) {
+ const SECItem * srvName;
+ SECStatus rv = SECSuccess;
+
+ ssl_GetSpecReadLock(ss); /********************************/
+ srvName = &ss->ssl3.prSpec->srvVirtName;
+ if (srvName->len && srvName->data) {
+ rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.srvName, srvName);
+ }
+ ssl_ReleaseSpecReadLock(ss); /************************************/
+ if (rv != SECSuccess) {
+ PORT_Free(sid);
+ return NULL;
+ }
+ }
+ sid->peerID = (ss->peerID == NULL) ? NULL : PORT_Strdup(ss->peerID);
+ sid->urlSvrName = (ss->url == NULL) ? NULL : PORT_Strdup(ss->url);
+ sid->addr = ss->sec.ci.peer;
+ sid->port = ss->sec.ci.port;
+ sid->references = 1;
+ sid->cached = never_cached;
+ sid->version = ss->version;
+
+ sid->u.ssl3.keys.resumable = PR_TRUE;
+ sid->u.ssl3.policy = SSL_ALLOWED;
+ sid->u.ssl3.clientWriteKey = NULL;
+ sid->u.ssl3.serverWriteKey = NULL;
+
+ if (is_server) {
+ SECStatus rv;
+ int pid = SSL_GETPID();
+
+ sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
+ sid->u.ssl3.sessionID[0] = (pid >> 8) & 0xff;
+ sid->u.ssl3.sessionID[1] = pid & 0xff;
+ rv = PK11_GenerateRandom(sid->u.ssl3.sessionID + 2,
+ SSL3_SESSIONID_BYTES -2);
+ if (rv != SECSuccess) {
+ ssl_FreeSID(sid);
+ ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
+ return NULL;
+ }
+ }
+ return sid;
+}
+
+/* Called from: ssl3_HandleClientHello, ssl3_HandleV2ClientHello */
+static SECStatus
+ssl3_SendServerHelloSequence(sslSocket *ss)
+{
+ const ssl3KEADef *kea_def;
+ SECStatus rv;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: begin send server_hello sequence",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ rv = ssl3_SendServerHello(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err code is set. */
+ }
+ rv = ssl3_SendCertificate(ss);
+ if (rv != SECSuccess) {
+ return rv; /* error code is set. */
+ }
+ rv = ssl3_SendCertificateStatus(ss);
+ if (rv != SECSuccess) {
+ return rv; /* error code is set. */
+ }
+ /* We have to do this after the call to ssl3_SendServerHello,
+ * because kea_def is set up by ssl3_SendServerHello().
+ */
+ kea_def = ss->ssl3.hs.kea_def;
+ ss->ssl3.hs.usedStepDownKey = PR_FALSE;
+
+ if (kea_def->is_limited && kea_def->exchKeyType == kt_rsa) {
+ /* see if we can legally use the key in the cert. */
+ int keyLen; /* bytes */
+
+ keyLen = PK11_GetPrivateModulusLen(
+ ss->serverCerts[kea_def->exchKeyType].SERVERKEY);
+
+ if (keyLen > 0 &&
+ keyLen * BPB <= kea_def->key_size_limit ) {
+ /* XXX AND cert is not signing only!! */
+ /* just fall through and use it. */
+ } else if (ss->stepDownKeyPair != NULL) {
+ ss->ssl3.hs.usedStepDownKey = PR_TRUE;
+ rv = ssl3_SendServerKeyExchange(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err code was set. */
+ }
+ } else {
+#ifndef HACKED_EXPORT_SERVER
+ PORT_SetError(SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED);
+ return rv;
+#endif
+ }
+#ifdef NSS_ENABLE_ECC
+ } else if ((kea_def->kea == kea_ecdhe_rsa) ||
+ (kea_def->kea == kea_ecdhe_ecdsa)) {
+ rv = ssl3_SendServerKeyExchange(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err code was set. */
+ }
+#endif /* NSS_ENABLE_ECC */
+ }
+
+ if (ss->opt.requestCertificate) {
+ rv = ssl3_SendCertificateRequest(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err code is set. */
+ }
+ }
+ rv = ssl3_SendServerHelloDone(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err code is set. */
+ }
+
+ ss->ssl3.hs.ws = (ss->opt.requestCertificate) ? wait_client_cert
+ : wait_client_key;
+ return SECSuccess;
+}
+
+/* An empty TLS Renegotiation Info (RI) extension */
+static const PRUint8 emptyRIext[5] = {0xff, 0x01, 0x00, 0x01, 0x00};
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Client Hello message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleClientHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ sslSessionID * sid = NULL;
+ PRInt32 tmp;
+ unsigned int i;
+ int j;
+ SECStatus rv;
+ int errCode = SSL_ERROR_RX_MALFORMED_CLIENT_HELLO;
+ SSL3AlertDescription desc = illegal_parameter;
+ SSL3AlertLevel level = alert_fatal;
+ SSL3ProtocolVersion version;
+ SECItem sidBytes = {siBuffer, NULL, 0};
+ SECItem cookieBytes = {siBuffer, NULL, 0};
+ SECItem suites = {siBuffer, NULL, 0};
+ SECItem comps = {siBuffer, NULL, 0};
+ PRBool haveSpecWriteLock = PR_FALSE;
+ PRBool haveXmitBufLock = PR_FALSE;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle client_hello handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->ssl3.initialized );
+
+ /* Get peer name of client */
+ rv = ssl_GetPeerInfo(ss);
+ if (rv != SECSuccess) {
+ return rv; /* error code is set. */
+ }
+
+ /* Clearing the handshake pointers so that ssl_Do1stHandshake won't
+ * call ssl2_HandleMessage.
+ *
+ * The issue here is that TLS ordinarily starts out in
+ * ssl2_HandleV3HandshakeRecord() because of the backward-compatibility
+ * code paths. That function zeroes these next pointers. But with DTLS,
+ * we don't even try to do the v2 ClientHello so we skip that function
+ * and need to reset these values here.
+ */
+ if (IS_DTLS(ss)) {
+ ss->nextHandshake = 0;
+ ss->securityHandshake = 0;
+ }
+
+ /* We might be starting session renegotiation in which case we should
+ * clear previous state.
+ */
+ PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData));
+ ss->statelessResume = PR_FALSE;
+
+ if ((ss->ssl3.hs.ws != wait_client_hello) &&
+ (ss->ssl3.hs.ws != idle_handshake)) {
+ desc = unexpected_message;
+ errCode = SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO;
+ goto alert_loser;
+ }
+ if (ss->ssl3.hs.ws == idle_handshake &&
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER) {
+ desc = no_renegotiation;
+ level = alert_warning;
+ errCode = SSL_ERROR_RENEGOTIATION_NOT_ALLOWED;
+ goto alert_loser;
+ }
+
+ if (IS_DTLS(ss)) {
+ dtls_RehandshakeCleanup(ss);
+ }
+
+ tmp = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (tmp < 0)
+ goto loser; /* malformed, alert already sent */
+
+ /* Translate the version */
+ if (IS_DTLS(ss)) {
+ ss->clientHelloVersion = version =
+ dtls_DTLSVersionToTLSVersion((SSL3ProtocolVersion)tmp);
+ } else {
+ ss->clientHelloVersion = version = (SSL3ProtocolVersion)tmp;
+ }
+
+ rv = ssl3_NegotiateVersion(ss, version, PR_TRUE);
+ if (rv != SECSuccess) {
+ desc = (version > SSL_LIBRARY_VERSION_3_0) ? protocol_version
+ : handshake_failure;
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+ }
+
+ rv = ssl3_InitHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ desc = internal_error;
+ errCode = PORT_GetError();
+ goto alert_loser;
+ }
+
+ /* grab the client random data. */
+ rv = ssl3_ConsumeHandshake(
+ ss, &ss->ssl3.hs.client_random, SSL3_RANDOM_LENGTH, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+
+ /* grab the client's SID, if present. */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &sidBytes, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+
+ /* grab the client's cookie, if present. */
+ if (IS_DTLS(ss)) {
+ rv = ssl3_ConsumeHandshakeVariable(ss, &cookieBytes, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+ }
+
+ /* grab the list of cipher suites. */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &suites, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+
+ /* grab the list of compression methods. */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &comps, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+
+ desc = handshake_failure;
+
+ /* Handle TLS hello extensions for SSL3 & TLS. We do not know if
+ * we are restarting a previous session until extensions have been
+ * parsed, since we might have received a SessionTicket extension.
+ * Note: we allow extensions even when negotiating SSL3 for the sake
+ * of interoperability (and backwards compatibility).
+ */
+
+ if (length) {
+ /* Get length of hello extensions */
+ PRInt32 extension_length;
+ extension_length = ssl3_ConsumeHandshakeNumber(ss, 2, &b, &length);
+ if (extension_length < 0) {
+ goto loser; /* alert already sent */
+ }
+ if (extension_length != length) {
+ ssl3_DecodeError(ss); /* send alert */
+ goto loser;
+ }
+ rv = ssl3_HandleHelloExtensions(ss, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed */
+ }
+ }
+ if (!ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ /* If we didn't receive an RI extension, look for the SCSV,
+ * and if found, treat it just like an empty RI extension
+ * by processing a local copy of an empty RI extension.
+ */
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == TLS_EMPTY_RENEGOTIATION_INFO_SCSV) {
+ SSL3Opaque * b2 = (SSL3Opaque *)emptyRIext;
+ PRUint32 L2 = sizeof emptyRIext;
+ (void)ssl3_HandleHelloExtensions(ss, &b2, &L2);
+ break;
+ }
+ }
+ }
+ if (ss->firstHsDone &&
+ (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_REQUIRES_XTN ||
+ ss->opt.enableRenegotiation == SSL_RENEGOTIATE_TRANSITIONAL) &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = no_renegotiation;
+ level = alert_warning;
+ errCode = SSL_ERROR_RENEGOTIATION_NOT_ALLOWED;
+ goto alert_loser;
+ }
+ if ((ss->opt.requireSafeNegotiation ||
+ (ss->firstHsDone && ss->peerRequestedProtection)) &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
+ }
+
+ /* We do stateful resumes only if either of the following
+ * conditions are satisfied: (1) the client does not support the
+ * session ticket extension, or (2) the client support the session
+ * ticket extension, but sent an empty ticket.
+ */
+ if (!ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn) ||
+ ss->xtnData.emptySessionTicket) {
+ if (sidBytes.len > 0 && !ss->opt.noCache) {
+ SSL_TRC(7, ("%d: SSL3[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x",
+ SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0],
+ ss->sec.ci.peer.pr_s6_addr32[1],
+ ss->sec.ci.peer.pr_s6_addr32[2],
+ ss->sec.ci.peer.pr_s6_addr32[3]));
+ if (ssl_sid_lookup) {
+ sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sidBytes.data,
+ sidBytes.len, ss->dbHandle);
+ } else {
+ errCode = SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED;
+ goto loser;
+ }
+ }
+ } else if (ss->statelessResume) {
+ /* Fill in the client's session ID if doing a stateless resume.
+ * (When doing stateless resumes, server echos client's SessionID.)
+ */
+ sid = ss->sec.ci.sid;
+ PORT_Assert(sid != NULL); /* Should have already been filled in.*/
+
+ if (sidBytes.len > 0 && sidBytes.len <= SSL3_SESSIONID_BYTES) {
+ sid->u.ssl3.sessionIDLength = sidBytes.len;
+ PORT_Memcpy(sid->u.ssl3.sessionID, sidBytes.data,
+ sidBytes.len);
+ sid->u.ssl3.sessionIDLength = sidBytes.len;
+ } else {
+ sid->u.ssl3.sessionIDLength = 0;
+ }
+ ss->sec.ci.sid = NULL;
+ }
+
+ /* We only send a session ticket extension if the client supports
+ * the extension and we are unable to do either a stateful or
+ * stateless resume.
+ *
+ * TODO: send a session ticket if performing a stateful
+ * resumption. (As per RFC4507, a server may issue a session
+ * ticket while doing a (stateless or stateful) session resume,
+ * but OpenSSL-0.9.8g does not accept session tickets while
+ * resuming.)
+ */
+ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn) && sid == NULL) {
+ ssl3_RegisterServerHelloExtensionSender(ss,
+ ssl_session_ticket_xtn, ssl3_SendSessionTicketXtn);
+ }
+
+ if (sid != NULL) {
+ /* We've found a session cache entry for this client.
+ * Now, if we're going to require a client-auth cert,
+ * and we don't already have this client's cert in the session cache,
+ * and this is the first handshake on this connection (not a redo),
+ * then drop this old cache entry and start a new session.
+ */
+ if ((sid->peerCert == NULL) && ss->opt.requestCertificate &&
+ ((ss->opt.requireCertificate == SSL_REQUIRE_ALWAYS) ||
+ (ss->opt.requireCertificate == SSL_REQUIRE_NO_ERROR) ||
+ ((ss->opt.requireCertificate == SSL_REQUIRE_FIRST_HANDSHAKE)
+ && !ss->firstHsDone))) {
+
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_not_ok );
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid);
+ ssl_FreeSID(sid);
+ sid = NULL;
+ }
+ }
+
+#ifdef NSS_ENABLE_ECC
+ /* Disable any ECC cipher suites for which we have no cert. */
+ ssl3_FilterECCipherSuitesByServerCerts(ss);
+#endif
+
+ if (IS_DTLS(ss)) {
+ ssl3_DisableNonDTLSSuites(ss);
+ }
+
+ if (!ssl3_HasGCMSupport()) {
+ ssl3_DisableGCMSuites(ss);
+ }
+
+#ifdef PARANOID
+ /* Look for a matching cipher suite. */
+ j = ssl3_config_match_init(ss);
+ if (j <= 0) { /* no ciphers are working/supported by PK11 */
+ errCode = PORT_GetError(); /* error code is already set. */
+ goto alert_loser;
+ }
+#endif
+
+ /* If we already have a session for this client, be sure to pick the
+ ** same cipher suite and compression method we picked before.
+ ** This is not a loop, despite appearances.
+ */
+ if (sid) do {
+ ssl3CipherSuiteCfg *suite;
+
+ /* Check that the cached compression method is still enabled. */
+ if (!compressionEnabled(ss, sid->u.ssl3.compression))
+ break;
+
+ /* Check that the cached compression method is in the client's list */
+ for (i = 0; i < comps.len; i++) {
+ if (comps.data[i] == sid->u.ssl3.compression)
+ break;
+ }
+ if (i == comps.len)
+ break;
+
+ suite = ss->cipherSuites;
+ /* Find the entry for the cipher suite used in the cached session. */
+ for (j = ssl_V3_SUITES_IMPLEMENTED; j > 0; --j, ++suite) {
+ if (suite->cipher_suite == sid->u.ssl3.cipherSuite)
+ break;
+ }
+ PORT_Assert(j > 0);
+ if (j <= 0)
+ break;
+#ifdef PARANOID
+ /* Double check that the cached cipher suite is still enabled,
+ * implemented, and allowed by policy. Might have been disabled.
+ * The product policy won't change during the process lifetime.
+ * Implemented ("isPresent") shouldn't change for servers.
+ */
+ if (!config_match(suite, ss->ssl3.policy, PR_TRUE))
+ break;
+#else
+ if (!suite->enabled)
+ break;
+#endif
+ /* Double check that the cached cipher suite is in the client's list */
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == suite->cipher_suite) {
+ ss->ssl3.hs.cipher_suite = suite->cipher_suite;
+ ss->ssl3.hs.suite_def =
+ ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
+
+ /* Use the cached compression method. */
+ ss->ssl3.hs.compression = sid->u.ssl3.compression;
+ goto compression_found;
+ }
+ }
+ } while (0);
+
+ /* START A NEW SESSION */
+
+#ifndef PARANOID
+ /* Look for a matching cipher suite. */
+ j = ssl3_config_match_init(ss);
+ if (j <= 0) { /* no ciphers are working/supported by PK11 */
+ errCode = PORT_GetError(); /* error code is already set. */
+ goto alert_loser;
+ }
+#endif
+
+ /* Select a cipher suite.
+ **
+ ** NOTE: This suite selection algorithm should be the same as the one in
+ ** ssl3_HandleV2ClientHello().
+ **
+ ** If TLS 1.0 is enabled, we could handle the case where the client
+ ** offered TLS 1.1 but offered only export cipher suites by choosing TLS
+ ** 1.0 and selecting one of those export cipher suites. However, a secure
+ ** TLS 1.1 client should not have export cipher suites enabled at all,
+ ** and a TLS 1.1 client should definitely not be offering *only* export
+ ** cipher suites. Therefore, we refuse to negotiate export cipher suites
+ ** with any client that indicates support for TLS 1.1 or higher when we
+ ** (the server) have TLS 1.1 support enabled.
+ */
+ for (j = 0; j < ssl_V3_SUITES_IMPLEMENTED; j++) {
+ ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
+ if (!config_match(suite, ss->ssl3.policy, PR_TRUE) ||
+ !ssl3_CipherSuiteAllowedForVersion(suite->cipher_suite,
+ ss->version)) {
+ continue;
+ }
+ for (i = 0; i + 1 < suites.len; i += 2) {
+ PRUint16 suite_i = (suites.data[i] << 8) | suites.data[i + 1];
+ if (suite_i == suite->cipher_suite) {
+ ss->ssl3.hs.cipher_suite = suite->cipher_suite;
+ ss->ssl3.hs.suite_def =
+ ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
+ goto suite_found;
+ }
+ }
+ }
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+
+suite_found:
+ /* Look for a matching compression algorithm. */
+ for (i = 0; i < comps.len; i++) {
+ if (!compressionEnabled(ss, comps.data[i]))
+ continue;
+ for (j = 0; j < compressionMethodsCount; j++) {
+ if (comps.data[i] == compressions[j]) {
+ ss->ssl3.hs.compression =
+ (SSLCompressionMethod)compressions[j];
+ goto compression_found;
+ }
+ }
+ }
+ errCode = SSL_ERROR_NO_COMPRESSION_OVERLAP;
+ /* null compression must be supported */
+ goto alert_loser;
+
+compression_found:
+ suites.data = NULL;
+ comps.data = NULL;
+
+ ss->sec.send = ssl3_SendApplicationData;
+
+ /* If there are any failures while processing the old sid,
+ * we don't consider them to be errors. Instead, We just behave
+ * as if the client had sent us no sid to begin with, and make a new one.
+ */
+ if (sid != NULL) do {
+ ssl3CipherSpec *pwSpec;
+ SECItem wrappedMS; /* wrapped key */
+
+ if (sid->version != ss->version ||
+ sid->u.ssl3.cipherSuite != ss->ssl3.hs.cipher_suite ||
+ sid->u.ssl3.compression != ss->ssl3.hs.compression) {
+ break; /* not an error */
+ }
+
+ if (ss->sec.ci.sid) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(ss->sec.ci.sid);
+ PORT_Assert(ss->sec.ci.sid != sid); /* should be impossible, but ... */
+ if (ss->sec.ci.sid != sid) {
+ ssl_FreeSID(ss->sec.ci.sid);
+ }
+ ss->sec.ci.sid = NULL;
+ }
+ /* we need to resurrect the master secret.... */
+
+ ssl_GetSpecWriteLock(ss); haveSpecWriteLock = PR_TRUE;
+ pwSpec = ss->ssl3.pwSpec;
+ if (sid->u.ssl3.keys.msIsWrapped) {
+ PK11SymKey * wrapKey; /* wrapping key */
+ CK_FLAGS keyFlags = 0;
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ /* we cannot restart a non-bypass session in a
+ ** bypass socket.
+ */
+ break;
+ }
+#endif
+
+ wrapKey = getWrappingKey(ss, NULL, sid->u.ssl3.exchKeyType,
+ sid->u.ssl3.masterWrapMech,
+ ss->pkcs11PinArg);
+ if (!wrapKey) {
+ /* we have a SID cache entry, but no wrapping key for it??? */
+ break;
+ }
+
+ if (ss->version > SSL_LIBRARY_VERSION_3_0) { /* isTLS */
+ keyFlags = CKF_SIGN | CKF_VERIFY;
+ }
+
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+
+ /* unwrap the master secret. */
+ pwSpec->master_secret =
+ PK11_UnwrapSymKeyWithFlags(wrapKey, sid->u.ssl3.masterWrapMech,
+ NULL, &wrappedMS, CKM_SSL3_MASTER_KEY_DERIVE,
+ CKA_DERIVE, sizeof(SSL3MasterSecret), keyFlags);
+ PK11_FreeSymKey(wrapKey);
+ if (pwSpec->master_secret == NULL) {
+ break; /* not an error */
+ }
+#ifndef NO_PKCS11_BYPASS
+ } else if (ss->opt.bypassPKCS11) {
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+ memcpy(pwSpec->raw_master_secret, wrappedMS.data, wrappedMS.len);
+ pwSpec->msItem.data = pwSpec->raw_master_secret;
+ pwSpec->msItem.len = wrappedMS.len;
+#endif
+ } else {
+ /* We CAN restart a bypass session in a non-bypass socket. */
+ /* need to import the raw master secret to session object */
+ PK11SlotInfo * slot;
+ wrappedMS.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wrappedMS.len = sid->u.ssl3.keys.wrapped_master_secret_len;
+ slot = PK11_GetInternalSlot();
+ pwSpec->master_secret =
+ PK11_ImportSymKey(slot, CKM_SSL3_MASTER_KEY_DERIVE,
+ PK11_OriginUnwrap, CKA_ENCRYPT, &wrappedMS,
+ NULL);
+ PK11_FreeSlot(slot);
+ if (pwSpec->master_secret == NULL) {
+ break; /* not an error */
+ }
+ }
+ ss->sec.ci.sid = sid;
+ if (sid->peerCert != NULL) {
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
+ ssl3_CopyPeerCertsFromSID(ss, sid);
+ }
+
+ /*
+ * Old SID passed all tests, so resume this old session.
+ *
+ * XXX make sure compression still matches
+ */
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_hits );
+ if (ss->statelessResume)
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_stateless_resumes );
+ ss->ssl3.hs.isResuming = PR_TRUE;
+
+ ss->sec.authAlgorithm = sid->authAlgorithm;
+ ss->sec.authKeyBits = sid->authKeyBits;
+ ss->sec.keaType = sid->keaType;
+ ss->sec.keaKeyBits = sid->keaKeyBits;
+
+ /* server sids don't remember the server cert we previously sent,
+ ** but they do remember the kea type we originally used, so we
+ ** can locate it again, provided that the current ssl socket
+ ** has had its server certs configured the same as the previous one.
+ */
+ ss->sec.localCert =
+ CERT_DupCertificate(ss->serverCerts[sid->keaType].serverCert);
+
+ /* Copy cached name in to pending spec */
+ if (sid != NULL &&
+ sid->version > SSL_LIBRARY_VERSION_3_0 &&
+ sid->u.ssl3.srvName.len && sid->u.ssl3.srvName.data) {
+ /* Set server name from sid */
+ SECItem *sidName = &sid->u.ssl3.srvName;
+ SECItem *pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ rv = SECITEM_CopyItem(NULL, pwsName, sidName);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ desc = internal_error;
+ goto alert_loser;
+ }
+ }
+
+ /* Clean up sni name array */
+ if (ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn) &&
+ ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ ss->xtnData.sniNameArrSize = 0;
+ }
+
+ ssl_GetXmitBufLock(ss); haveXmitBufLock = PR_TRUE;
+
+ rv = ssl3_SendServerHello(ss);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ if (haveSpecWriteLock) {
+ ssl_ReleaseSpecWriteLock(ss);
+ haveSpecWriteLock = PR_FALSE;
+ }
+
+ /* NULL value for PMS signifies re-use of the old MS */
+ rv = ssl3_InitPendingCipherSpec(ss, NULL);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ rv = ssl3_SendChangeCipherSpecs(ss);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+ rv = ssl3_SendFinished(ss, 0);
+ ss->ssl3.hs.ws = wait_change_cipher;
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ if (haveXmitBufLock) {
+ ssl_ReleaseXmitBufLock(ss);
+ haveXmitBufLock = PR_FALSE;
+ }
+
+ return SECSuccess;
+ } while (0);
+
+ if (haveSpecWriteLock) {
+ ssl_ReleaseSpecWriteLock(ss);
+ haveSpecWriteLock = PR_FALSE;
+ }
+
+ if (sid) { /* we had a sid, but it's no longer valid, free it */
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_not_ok );
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid);
+ ssl_FreeSID(sid);
+ sid = NULL;
+ }
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_misses );
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn)) {
+ int ret = 0;
+ if (ss->sniSocketConfig) do { /* not a loop */
+ ret = SSL_SNI_SEND_ALERT;
+ /* If extension is negotiated, the len of names should > 0. */
+ if (ss->xtnData.sniNameArrSize) {
+ /* Calling client callback to reconfigure the socket. */
+ ret = (SECStatus)(*ss->sniSocketConfig)(ss->fd,
+ ss->xtnData.sniNameArr,
+ ss->xtnData.sniNameArrSize,
+ ss->sniSocketConfigArg);
+ }
+ if (ret <= SSL_SNI_SEND_ALERT) {
+ /* Application does not know the name or was not able to
+ * properly reconfigure the socket. */
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = unrecognized_name;
+ break;
+ } else if (ret == SSL_SNI_CURRENT_CONFIG_IS_USED) {
+ SECStatus rv = SECSuccess;
+ SECItem * cwsName, *pwsName;
+
+ ssl_GetSpecWriteLock(ss); /*******************************/
+ pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ cwsName = &ss->ssl3.cwSpec->srvVirtName;
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ /* not allow name change on the 2d HS */
+ if (ss->firstHsDone) {
+ if (ssl3_ServerNameCompare(pwsName, cwsName)) {
+ ssl_ReleaseSpecWriteLock(ss); /******************/
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ }
+#endif
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ if (cwsName->data) {
+ rv = SECITEM_CopyItem(NULL, pwsName, cwsName);
+ }
+ ssl_ReleaseSpecWriteLock(ss); /**************************/
+ if (rv != SECSuccess) {
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ } else if (ret < ss->xtnData.sniNameArrSize) {
+ /* Application has configured new socket info. Lets check it
+ * and save the name. */
+ SECStatus rv;
+ SECItem * name = &ss->xtnData.sniNameArr[ret];
+ int configedCiphers;
+ SECItem * pwsName;
+
+ /* get rid of the old name and save the newly picked. */
+ /* This code is protected by ssl3HandshakeLock. */
+ ssl_GetSpecWriteLock(ss); /*******************************/
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ /* not allow name change on the 2d HS */
+ if (ss->firstHsDone) {
+ SECItem *cwsName = &ss->ssl3.cwSpec->srvVirtName;
+ if (ssl3_ServerNameCompare(name, cwsName)) {
+ ssl_ReleaseSpecWriteLock(ss); /******************/
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ }
+#endif
+ pwsName = &ss->ssl3.pwSpec->srvVirtName;
+ if (pwsName->data) {
+ SECITEM_FreeItem(pwsName, PR_FALSE);
+ }
+ rv = SECITEM_CopyItem(NULL, pwsName, name);
+ ssl_ReleaseSpecWriteLock(ss); /***************************/
+ if (rv != SECSuccess) {
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ configedCiphers = ssl3_config_match_init(ss);
+ if (configedCiphers <= 0) {
+ /* no ciphers are working/supported */
+ errCode = PORT_GetError();
+ desc = handshake_failure;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ /* Need to tell the client that application has picked
+ * the name from the offered list and reconfigured the socket.
+ */
+ ssl3_RegisterServerHelloExtensionSender(ss, ssl_server_name_xtn,
+ ssl3_SendServerNameXtn);
+ } else {
+ /* Callback returned index outside of the boundary. */
+ PORT_Assert(ret < ss->xtnData.sniNameArrSize);
+ errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
+ desc = internal_error;
+ ret = SSL_SNI_SEND_ALERT;
+ break;
+ }
+ } while (0);
+ /* Free sniNameArr. The data that each SECItem in the array
+ * points into is the data from the input buffer "b". It will
+ * not be available outside the scope of this or it's child
+ * functions.*/
+ if (ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ ss->xtnData.sniNameArrSize = 0;
+ }
+ if (ret <= SSL_SNI_SEND_ALERT) {
+ /* desc and errCode should be set. */
+ goto alert_loser;
+ }
+ }
+#ifndef SSL_SNI_ALLOW_NAME_CHANGE_2HS
+ else if (ss->firstHsDone) {
+ /* Check that we don't have the name is current spec
+ * if this extension was not negotiated on the 2d hs. */
+ PRBool passed = PR_TRUE;
+ ssl_GetSpecReadLock(ss); /*******************************/
+ if (ss->ssl3.cwSpec->srvVirtName.data) {
+ passed = PR_FALSE;
+ }
+ ssl_ReleaseSpecReadLock(ss); /***************************/
+ if (!passed) {
+ errCode = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
+ desc = handshake_failure;
+ goto alert_loser;
+ }
+ }
+#endif
+
+ sid = ssl3_NewSessionID(ss, PR_TRUE);
+ if (sid == NULL) {
+ errCode = PORT_GetError();
+ goto loser; /* memory error is set. */
+ }
+ ss->sec.ci.sid = sid;
+
+ ss->ssl3.hs.isResuming = PR_FALSE;
+ ssl_GetXmitBufLock(ss);
+ rv = ssl3_SendServerHelloSequence(ss);
+ ssl_ReleaseXmitBufLock(ss);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ if (haveXmitBufLock) {
+ ssl_ReleaseXmitBufLock(ss);
+ haveXmitBufLock = PR_FALSE;
+ }
+
+ return SECSuccess;
+
+alert_loser:
+ if (haveSpecWriteLock) {
+ ssl_ReleaseSpecWriteLock(ss);
+ haveSpecWriteLock = PR_FALSE;
+ }
+ (void)SSL3_SendAlert(ss, level, desc);
+ /* FALLTHRU */
+loser:
+ if (haveSpecWriteLock) {
+ ssl_ReleaseSpecWriteLock(ss);
+ haveSpecWriteLock = PR_FALSE;
+ }
+
+ if (haveXmitBufLock) {
+ ssl_ReleaseXmitBufLock(ss);
+ haveXmitBufLock = PR_FALSE;
+ }
+
+ PORT_SetError(errCode);
+ return SECFailure;
+}
+
+/*
+ * ssl3_HandleV2ClientHello is used when a V2 formatted hello comes
+ * in asking to use the V3 handshake.
+ * Called from ssl2_HandleClientHelloMessage() in sslcon.c
+ */
+SECStatus
+ssl3_HandleV2ClientHello(sslSocket *ss, unsigned char *buffer, int length)
+{
+ sslSessionID * sid = NULL;
+ unsigned char * suites;
+ unsigned char * random;
+ SSL3ProtocolVersion version;
+ SECStatus rv;
+ int i;
+ int j;
+ int sid_length;
+ int suite_length;
+ int rand_length;
+ int errCode = SSL_ERROR_RX_MALFORMED_CLIENT_HELLO;
+ SSL3AlertDescription desc = handshake_failure;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle v2 client_hello", SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ ssl_GetSSL3HandshakeLock(ss);
+
+ PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData));
+
+ rv = ssl3_InitState(ss);
+ if (rv != SECSuccess) {
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return rv; /* ssl3_InitState has set the error code. */
+ }
+ rv = ssl3_RestartHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return rv;
+ }
+
+ if (ss->ssl3.hs.ws != wait_client_hello) {
+ desc = unexpected_message;
+ errCode = SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO;
+ goto loser; /* alert_loser */
+ }
+
+ version = (buffer[1] << 8) | buffer[2];
+ suite_length = (buffer[3] << 8) | buffer[4];
+ sid_length = (buffer[5] << 8) | buffer[6];
+ rand_length = (buffer[7] << 8) | buffer[8];
+ ss->clientHelloVersion = version;
+
+ rv = ssl3_NegotiateVersion(ss, version, PR_TRUE);
+ if (rv != SECSuccess) {
+ /* send back which ever alert client will understand. */
+ desc = (version > SSL_LIBRARY_VERSION_3_0) ? protocol_version : handshake_failure;
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+ }
+
+ rv = ssl3_InitHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ desc = internal_error;
+ errCode = PORT_GetError();
+ goto alert_loser;
+ }
+
+ /* if we get a non-zero SID, just ignore it. */
+ if (length !=
+ SSL_HL_CLIENT_HELLO_HBYTES + suite_length + sid_length + rand_length) {
+ SSL_DBG(("%d: SSL3[%d]: bad v2 client hello message, len=%d should=%d",
+ SSL_GETPID(), ss->fd, length,
+ SSL_HL_CLIENT_HELLO_HBYTES + suite_length + sid_length +
+ rand_length));
+ goto loser; /* malformed */ /* alert_loser */
+ }
+
+ suites = buffer + SSL_HL_CLIENT_HELLO_HBYTES;
+ random = suites + suite_length + sid_length;
+
+ if (rand_length < SSL_MIN_CHALLENGE_BYTES ||
+ rand_length > SSL_MAX_CHALLENGE_BYTES) {
+ goto loser; /* malformed */ /* alert_loser */
+ }
+
+ PORT_Assert(SSL_MAX_CHALLENGE_BYTES == SSL3_RANDOM_LENGTH);
+
+ PORT_Memset(&ss->ssl3.hs.client_random, 0, SSL3_RANDOM_LENGTH);
+ PORT_Memcpy(
+ &ss->ssl3.hs.client_random.rand[SSL3_RANDOM_LENGTH - rand_length],
+ random, rand_length);
+
+ PRINT_BUF(60, (ss, "client random:", &ss->ssl3.hs.client_random.rand[0],
+ SSL3_RANDOM_LENGTH));
+#ifdef NSS_ENABLE_ECC
+ /* Disable any ECC cipher suites for which we have no cert. */
+ ssl3_FilterECCipherSuitesByServerCerts(ss);
+#endif
+ i = ssl3_config_match_init(ss);
+ if (i <= 0) {
+ errCode = PORT_GetError(); /* error code is already set. */
+ goto alert_loser;
+ }
+
+ /* Select a cipher suite.
+ **
+ ** NOTE: This suite selection algorithm should be the same as the one in
+ ** ssl3_HandleClientHello().
+ **
+ ** See the comments about export cipher suites in ssl3_HandleClientHello().
+ */
+ for (j = 0; j < ssl_V3_SUITES_IMPLEMENTED; j++) {
+ ssl3CipherSuiteCfg *suite = &ss->cipherSuites[j];
+ if (!config_match(suite, ss->ssl3.policy, PR_TRUE) ||
+ !ssl3_CipherSuiteAllowedForVersion(suite->cipher_suite,
+ ss->version)) {
+ continue;
+ }
+ for (i = 0; i+2 < suite_length; i += 3) {
+ PRUint32 suite_i = (suites[i] << 16)|(suites[i+1] << 8)|suites[i+2];
+ if (suite_i == suite->cipher_suite) {
+ ss->ssl3.hs.cipher_suite = suite->cipher_suite;
+ ss->ssl3.hs.suite_def =
+ ssl_LookupCipherSuiteDef(ss->ssl3.hs.cipher_suite);
+ goto suite_found;
+ }
+ }
+ }
+ errCode = SSL_ERROR_NO_CYPHER_OVERLAP;
+ goto alert_loser;
+
+suite_found:
+
+ /* Look for the SCSV, and if found, treat it just like an empty RI
+ * extension by processing a local copy of an empty RI extension.
+ */
+ for (i = 0; i+2 < suite_length; i += 3) {
+ PRUint32 suite_i = (suites[i] << 16) | (suites[i+1] << 8) | suites[i+2];
+ if (suite_i == TLS_EMPTY_RENEGOTIATION_INFO_SCSV) {
+ SSL3Opaque * b2 = (SSL3Opaque *)emptyRIext;
+ PRUint32 L2 = sizeof emptyRIext;
+ (void)ssl3_HandleHelloExtensions(ss, &b2, &L2);
+ break;
+ }
+ }
+
+ if (ss->opt.requireSafeNegotiation &&
+ !ssl3_ExtensionNegotiated(ss, ssl_renegotiation_info_xtn)) {
+ desc = handshake_failure;
+ errCode = SSL_ERROR_UNSAFE_NEGOTIATION;
+ goto alert_loser;
+ }
+
+ ss->ssl3.hs.compression = ssl_compression_null;
+ ss->sec.send = ssl3_SendApplicationData;
+
+ /* we don't even search for a cache hit here. It's just a miss. */
+ SSL_AtomicIncrementLong(& ssl3stats.hch_sid_cache_misses );
+ sid = ssl3_NewSessionID(ss, PR_TRUE);
+ if (sid == NULL) {
+ errCode = PORT_GetError();
+ goto loser; /* memory error is set. */
+ }
+ ss->sec.ci.sid = sid;
+ /* do not worry about memory leak of sid since it now belongs to ci */
+
+ /* We have to update the handshake hashes before we can send stuff */
+ rv = ssl3_UpdateHandshakeHashes(ss, buffer, length);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ ssl_GetXmitBufLock(ss);
+ rv = ssl3_SendServerHelloSequence(ss);
+ ssl_ReleaseXmitBufLock(ss);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+
+ /* XXX_1 The call stack to here is:
+ * ssl_Do1stHandshake -> ssl2_HandleClientHelloMessage -> here.
+ * ssl2_HandleClientHelloMessage returns whatever we return here.
+ * ssl_Do1stHandshake will continue looping if it gets back either
+ * SECSuccess or SECWouldBlock.
+ * SECSuccess is preferable here. See XXX_1 in sslgathr.c.
+ */
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return SECSuccess;
+
+alert_loser:
+ SSL3_SendAlert(ss, alert_fatal, desc);
+loser:
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ PORT_SetError(errCode);
+ return SECFailure;
+}
+
+/* The negotiated version number has been already placed in ss->version.
+**
+** Called from: ssl3_HandleClientHello (resuming session),
+** ssl3_SendServerHelloSequence <- ssl3_HandleClientHello (new session),
+** ssl3_SendServerHelloSequence <- ssl3_HandleV2ClientHello (new session)
+*/
+static SECStatus
+ssl3_SendServerHello(sslSocket *ss)
+{
+ sslSessionID *sid;
+ SECStatus rv;
+ PRUint32 maxBytes = 65535;
+ PRUint32 length;
+ PRInt32 extensions_len = 0;
+ SSL3ProtocolVersion version;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send server_hello handshake", SSL_GETPID(),
+ ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (!IS_DTLS(ss)) {
+ PORT_Assert(MSB(ss->version) == MSB(SSL_LIBRARY_VERSION_3_0));
+
+ if (MSB(ss->version) != MSB(SSL_LIBRARY_VERSION_3_0)) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+ } else {
+ PORT_Assert(MSB(ss->version) == MSB(SSL_LIBRARY_VERSION_DTLS_1_0));
+
+ if (MSB(ss->version) != MSB(SSL_LIBRARY_VERSION_DTLS_1_0)) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+ }
+
+ sid = ss->sec.ci.sid;
+
+ extensions_len = ssl3_CallHelloExtensionSenders(ss, PR_FALSE, maxBytes,
+ &ss->xtnData.serverSenders[0]);
+ if (extensions_len > 0)
+ extensions_len += 2; /* Add sizeof total extension length */
+
+ length = sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH + 1 +
+ ((sid == NULL) ? 0: sid->u.ssl3.sessionIDLength) +
+ sizeof(ssl3CipherSuite) + 1 + extensions_len;
+ rv = ssl3_AppendHandshakeHeader(ss, server_hello, length);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+
+ if (IS_DTLS(ss)) {
+ version = dtls_TLSVersionToDTLSVersion(ss->version);
+ } else {
+ version = ss->version;
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, version, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_GetNewRandom(&ss->ssl3.hs.server_random);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
+ return rv;
+ }
+ rv = ssl3_AppendHandshake(
+ ss, &ss->ssl3.hs.server_random, SSL3_RANDOM_LENGTH);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+
+ if (sid)
+ rv = ssl3_AppendHandshakeVariable(
+ ss, sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength, 1);
+ else
+ rv = ssl3_AppendHandshakeVariable(ss, NULL, 0, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, ss->ssl3.hs.cipher_suite, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, ss->ssl3.hs.compression, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ if (extensions_len) {
+ PRInt32 sent_len;
+
+ extensions_len -= 2;
+ rv = ssl3_AppendHandshakeNumber(ss, extensions_len, 2);
+ if (rv != SECSuccess)
+ return rv; /* err set by ssl3_SetupPendingCipherSpec */
+ sent_len = ssl3_CallHelloExtensionSenders(ss, PR_TRUE, extensions_len,
+ &ss->xtnData.serverSenders[0]);
+ PORT_Assert(sent_len == extensions_len);
+ if (sent_len != extensions_len) {
+ if (sent_len >= 0)
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ }
+ rv = ssl3_SetupPendingCipherSpec(ss);
+ if (rv != SECSuccess) {
+ return rv; /* err set by ssl3_SetupPendingCipherSpec */
+ }
+
+ return SECSuccess;
+}
+
+/* ssl3_PickSignatureHashAlgorithm selects a hash algorithm to use when signing
+ * elements of the handshake. (The negotiated cipher suite determines the
+ * signature algorithm.) Prior to TLS 1.2, the MD5/SHA1 combination is always
+ * used. With TLS 1.2, a client may advertise its support for signature and
+ * hash combinations. */
+static SECStatus
+ssl3_PickSignatureHashAlgorithm(sslSocket *ss,
+ SSL3SignatureAndHashAlgorithm* out)
+{
+ TLSSignatureAlgorithm sigAlg;
+ unsigned int i, j;
+ /* hashPreference expresses our preferences for hash algorithms, most
+ * preferable first. */
+ static const PRUint8 hashPreference[] = {
+ tls_hash_sha256,
+ tls_hash_sha384,
+ tls_hash_sha512,
+ tls_hash_sha1,
+ };
+
+ switch (ss->ssl3.hs.kea_def->kea) {
+ case kea_rsa:
+ case kea_rsa_export:
+ case kea_rsa_export_1024:
+ case kea_dh_rsa:
+ case kea_dh_rsa_export:
+ case kea_dhe_rsa:
+ case kea_dhe_rsa_export:
+ case kea_rsa_fips:
+ case kea_ecdh_rsa:
+ case kea_ecdhe_rsa:
+ sigAlg = tls_sig_rsa;
+ break;
+ case kea_dh_dss:
+ case kea_dh_dss_export:
+ case kea_dhe_dss:
+ case kea_dhe_dss_export:
+ sigAlg = tls_sig_dsa;
+ break;
+ case kea_ecdh_ecdsa:
+ case kea_ecdhe_ecdsa:
+ sigAlg = tls_sig_ecdsa;
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ return SECFailure;
+ }
+ out->sigAlg = sigAlg;
+
+ if (ss->version <= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* SEC_OID_UNKNOWN means the MD5/SHA1 combo hash used in TLS 1.1 and
+ * prior. */
+ out->hashAlg = SEC_OID_UNKNOWN;
+ return SECSuccess;
+ }
+
+ if (ss->ssl3.hs.numClientSigAndHash == 0) {
+ /* If the client didn't provide any signature_algorithms extension then
+ * we can assume that they support SHA-1:
+ * https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+ out->hashAlg = SEC_OID_SHA1;
+ return SECSuccess;
+ }
+
+ for (i = 0; i < PR_ARRAY_SIZE(hashPreference); i++) {
+ for (j = 0; j < ss->ssl3.hs.numClientSigAndHash; j++) {
+ const SSL3SignatureAndHashAlgorithm* sh =
+ &ss->ssl3.hs.clientSigAndHash[j];
+ if (sh->sigAlg == sigAlg && sh->hashAlg == hashPreference[i]) {
+ out->hashAlg = sh->hashAlg;
+ return SECSuccess;
+ }
+ }
+ }
+
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+}
+
+
+static SECStatus
+ssl3_SendServerKeyExchange(sslSocket *ss)
+{
+ const ssl3KEADef * kea_def = ss->ssl3.hs.kea_def;
+ SECStatus rv = SECFailure;
+ int length;
+ PRBool isTLS;
+ SECItem signed_hash = {siBuffer, NULL, 0};
+ SSL3Hashes hashes;
+ SECKEYPublicKey * sdPub; /* public key for step-down */
+ SSL3SignatureAndHashAlgorithm sigAndHash;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send server_key_exchange handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ssl3_PickSignatureHashAlgorithm(ss, &sigAndHash) != SECSuccess) {
+ return SECFailure;
+ }
+
+ switch (kea_def->exchKeyType) {
+ case kt_rsa:
+ /* Perform SSL Step-Down here. */
+ sdPub = ss->stepDownKeyPair->pubKey;
+ PORT_Assert(sdPub != NULL);
+ if (!sdPub) {
+ PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+ }
+ rv = ssl3_ComputeExportRSAKeyHash(sigAndHash.hashAlg,
+ sdPub->u.rsa.modulus,
+ sdPub->u.rsa.publicExponent,
+ &ss->ssl3.hs.client_random,
+ &ss->ssl3.hs.server_random,
+ &hashes, ss->opt.bypassPKCS11);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ return rv;
+ }
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ rv = ssl3_SignHashes(&hashes, ss->serverCerts[kt_rsa].SERVERKEY,
+ &signed_hash, isTLS);
+ if (rv != SECSuccess) {
+ goto loser; /* ssl3_SignHashes has set err. */
+ }
+ if (signed_hash.data == NULL) {
+ /* how can this happen and rv == SECSuccess ?? */
+ PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+ length = 2 + sdPub->u.rsa.modulus.len +
+ 2 + sdPub->u.rsa.publicExponent.len +
+ 2 + signed_hash.len;
+
+ rv = ssl3_AppendHandshakeHeader(ss, server_key_exchange, length);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ rv = ssl3_AppendHandshakeVariable(ss, sdPub->u.rsa.modulus.data,
+ sdPub->u.rsa.modulus.len, 2);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ rv = ssl3_AppendHandshakeVariable(
+ ss, sdPub->u.rsa.publicExponent.data,
+ sdPub->u.rsa.publicExponent.len, 2);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ if (ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ rv = ssl3_AppendSignatureAndHashAlgorithm(ss, &sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+ }
+
+ rv = ssl3_AppendHandshakeVariable(ss, signed_hash.data,
+ signed_hash.len, 2);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+ PORT_Free(signed_hash.data);
+ return SECSuccess;
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh: {
+ rv = ssl3_SendECDHServerKeyExchange(ss, &sigAndHash);
+ return rv;
+ }
+#endif /* NSS_ENABLE_ECC */
+
+ case kt_dh:
+ case kt_null:
+ default:
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ break;
+ }
+loser:
+ if (signed_hash.data != NULL)
+ PORT_Free(signed_hash.data);
+ return SECFailure;
+}
+
+
+static SECStatus
+ssl3_SendCertificateRequest(sslSocket *ss)
+{
+ PRBool isTLS12;
+ SECItem * name;
+ CERTDistNames *ca_list;
+ const PRUint8 *certTypes;
+ const PRUint8 *sigAlgs;
+ SECItem * names = NULL;
+ SECStatus rv;
+ int length;
+ int i;
+ int calen = 0;
+ int nnames = 0;
+ int certTypesLength;
+ int sigAlgsLength;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send certificate_request handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ /* ssl3.ca_list is initialized to NULL, and never changed. */
+ ca_list = ss->ssl3.ca_list;
+ if (!ca_list) {
+ ca_list = ssl3_server_ca_list;
+ }
+
+ if (ca_list != NULL) {
+ names = ca_list->names;
+ nnames = ca_list->nnames;
+ }
+
+ for (i = 0, name = names; i < nnames; i++, name++) {
+ calen += 2 + name->len;
+ }
+
+ certTypes = certificate_types;
+ certTypesLength = sizeof certificate_types;
+ sigAlgs = supported_signature_algorithms;
+ sigAlgsLength = sizeof supported_signature_algorithms;
+
+ length = 1 + certTypesLength + 2 + calen;
+ if (isTLS12) {
+ length += 2 + sigAlgsLength;
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, certificate_request, length);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_AppendHandshakeVariable(ss, certTypes, certTypesLength, 1);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ if (isTLS12) {
+ rv = ssl3_AppendHandshakeVariable(ss, sigAlgs, sigAlgsLength, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, calen, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ for (i = 0, name = names; i < nnames; i++, name++) {
+ rv = ssl3_AppendHandshakeVariable(ss, name->data, name->len, 2);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ }
+
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_SendServerHelloDone(sslSocket *ss)
+{
+ SECStatus rv;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send server_hello_done handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ rv = ssl3_AppendHandshakeHeader(ss, server_hello_done, 0);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_FlushHandshake(ss, 0);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by ssl3_FlushHandshake */
+ }
+ return SECSuccess;
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Certificate Verify message
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleCertificateVerify(sslSocket *ss, SSL3Opaque *b, PRUint32 length,
+ SSL3Hashes *hashes)
+{
+ SECItem signed_hash = {siBuffer, NULL, 0};
+ SECStatus rv;
+ int errCode = SSL_ERROR_RX_MALFORMED_CERT_VERIFY;
+ SSL3AlertDescription desc = handshake_failure;
+ PRBool isTLS, isTLS12;
+ SSL3SignatureAndHashAlgorithm sigAndHash;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle certificate_verify handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ if (ss->ssl3.hs.ws != wait_cert_verify || ss->sec.peerCert == NULL) {
+ desc = unexpected_message;
+ errCode = SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY;
+ goto alert_loser;
+ }
+
+ if (isTLS12) {
+ rv = ssl3_ConsumeSignatureAndHashAlgorithm(ss, &b, &length,
+ &sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed or unsupported. */
+ }
+ rv = ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ &sigAndHash, ss->sec.peerCert);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ desc = decrypt_error;
+ goto alert_loser;
+ }
+
+ /* We only support CertificateVerify messages that use the handshake
+ * hash. */
+ if (sigAndHash.hashAlg != hashes->hashAlg) {
+ errCode = SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM;
+ desc = decrypt_error;
+ goto alert_loser;
+ }
+ }
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &signed_hash, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+
+ /* XXX verify that the key & kea match */
+ rv = ssl3_VerifySignedHashes(hashes, ss->sec.peerCert, &signed_hash,
+ isTLS, ss->pkcs11PinArg);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ desc = isTLS ? decrypt_error : handshake_failure;
+ goto alert_loser;
+ }
+
+ signed_hash.data = NULL;
+
+ if (length != 0) {
+ desc = isTLS ? decode_error : illegal_parameter;
+ goto alert_loser; /* malformed */
+ }
+ ss->ssl3.hs.ws = wait_change_cipher;
+ return SECSuccess;
+
+alert_loser:
+ SSL3_SendAlert(ss, alert_fatal, desc);
+loser:
+ PORT_SetError(errCode);
+ return SECFailure;
+}
+
+
+/* find a slot that is able to generate a PMS and wrap it with RSA.
+ * Then generate and return the PMS.
+ * If the serverKeySlot parameter is non-null, this function will use
+ * that slot to do the job, otherwise it will find a slot.
+ *
+ * Called from ssl3_DeriveConnectionKeysPKCS11() (above)
+ * sendRSAClientKeyExchange() (above)
+ * ssl3_HandleRSAClientKeyExchange() (below)
+ * Caller must hold the SpecWriteLock, the SSL3HandshakeLock
+ */
+static PK11SymKey *
+ssl3_GenerateRSAPMS(sslSocket *ss, ssl3CipherSpec *spec,
+ PK11SlotInfo * serverKeySlot)
+{
+ PK11SymKey * pms = NULL;
+ PK11SlotInfo * slot = serverKeySlot;
+ void * pwArg = ss->pkcs11PinArg;
+ SECItem param;
+ CK_VERSION version;
+ CK_MECHANISM_TYPE mechanism_array[3];
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (slot == NULL) {
+ SSLCipherAlgorithm calg;
+ /* The specReadLock would suffice here, but we cannot assert on
+ ** read locks. Also, all the callers who call with a non-null
+ ** slot already hold the SpecWriteLock.
+ */
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSpecWriteLock(ss));
+ PORT_Assert(ss->ssl3.prSpec == ss->ssl3.pwSpec);
+
+ calg = spec->cipher_def->calg;
+ PORT_Assert(alg2Mech[calg].calg == calg);
+
+ /* First get an appropriate slot. */
+ mechanism_array[0] = CKM_SSL3_PRE_MASTER_KEY_GEN;
+ mechanism_array[1] = CKM_RSA_PKCS;
+ mechanism_array[2] = alg2Mech[calg].cmech;
+
+ slot = PK11_GetBestSlotMultiple(mechanism_array, 3, pwArg);
+ if (slot == NULL) {
+ /* can't find a slot with all three, find a slot with the minimum */
+ slot = PK11_GetBestSlotMultiple(mechanism_array, 2, pwArg);
+ if (slot == NULL) {
+ PORT_SetError(SSL_ERROR_TOKEN_SLOT_NOT_FOUND);
+ return pms; /* which is NULL */
+ }
+ }
+ }
+
+ /* Generate the pre-master secret ... */
+ if (IS_DTLS(ss)) {
+ SSL3ProtocolVersion temp;
+
+ temp = dtls_TLSVersionToDTLSVersion(ss->clientHelloVersion);
+ version.major = MSB(temp);
+ version.minor = LSB(temp);
+ } else {
+ version.major = MSB(ss->clientHelloVersion);
+ version.minor = LSB(ss->clientHelloVersion);
+ }
+
+ param.data = (unsigned char *)&version;
+ param.len = sizeof version;
+
+ pms = PK11_KeyGen(slot, CKM_SSL3_PRE_MASTER_KEY_GEN, &param, 0, pwArg);
+ if (!serverKeySlot)
+ PK11_FreeSlot(slot);
+ if (pms == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ }
+ return pms;
+}
+
+/* Note: The Bleichenbacher attack on PKCS#1 necessitates that we NEVER
+ * return any indication of failure of the Client Key Exchange message,
+ * where that failure is caused by the content of the client's message.
+ * This function must not return SECFailure for any reason that is directly
+ * or indirectly caused by the content of the client's encrypted PMS.
+ * We must not send an alert and also not drop the connection.
+ * Instead, we generate a random PMS. This will cause a failure
+ * in the processing the finished message, which is exactly where
+ * the failure must occur.
+ *
+ * Called from ssl3_HandleClientKeyExchange
+ */
+static SECStatus
+ssl3_HandleRSAClientKeyExchange(sslSocket *ss,
+ SSL3Opaque *b,
+ PRUint32 length,
+ SECKEYPrivateKey *serverKey)
+{
+ PK11SymKey * pms;
+#ifndef NO_PKCS11_BYPASS
+ unsigned char * cr = (unsigned char *)&ss->ssl3.hs.client_random;
+ unsigned char * sr = (unsigned char *)&ss->ssl3.hs.server_random;
+ ssl3CipherSpec * pwSpec = ss->ssl3.pwSpec;
+ unsigned int outLen = 0;
+#endif
+ PRBool isTLS = PR_FALSE;
+ SECStatus rv;
+ SECItem enc_pms;
+ unsigned char rsaPmsBuf[SSL3_RSA_PMS_LENGTH];
+ SECItem pmsItem = {siBuffer, NULL, 0};
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->ssl3.prSpec == ss->ssl3.pwSpec );
+
+ enc_pms.data = b;
+ enc_pms.len = length;
+ pmsItem.data = rsaPmsBuf;
+ pmsItem.len = sizeof rsaPmsBuf;
+
+ if (ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0) { /* isTLS */
+ PRInt32 kLen;
+ kLen = ssl3_ConsumeHandshakeNumber(ss, 2, &enc_pms.data, &enc_pms.len);
+ if (kLen < 0) {
+ PORT_SetError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+ }
+ if ((unsigned)kLen < enc_pms.len) {
+ enc_pms.len = kLen;
+ }
+ isTLS = PR_TRUE;
+ } else {
+ isTLS = (PRBool)(ss->ssl3.hs.kea_def->tls_keygen != 0);
+ }
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ /* TRIPLE BYPASS, get PMS directly from RSA decryption.
+ * Use PK11_PrivDecryptPKCS1 to decrypt the PMS to a buffer,
+ * then, check for version rollback attack, then
+ * do the equivalent of ssl3_DeriveMasterSecret, placing the MS in
+ * pwSpec->msItem. Finally call ssl3_InitPendingCipherSpec with
+ * ss and NULL, so that it will use the MS we've already derived here.
+ */
+
+ rv = PK11_PrivDecryptPKCS1(serverKey, rsaPmsBuf, &outLen,
+ sizeof rsaPmsBuf, enc_pms.data, enc_pms.len);
+ if (rv != SECSuccess) {
+ /* triple bypass failed. Let's try for a double bypass. */
+ goto double_bypass;
+ } else if (ss->opt.detectRollBack) {
+ SSL3ProtocolVersion client_version =
+ (rsaPmsBuf[0] << 8) | rsaPmsBuf[1];
+
+ if (IS_DTLS(ss)) {
+ client_version = dtls_DTLSVersionToTLSVersion(client_version);
+ }
+
+ if (client_version != ss->clientHelloVersion) {
+ /* Version roll-back detected. ensure failure. */
+ rv = PK11_GenerateRandom(rsaPmsBuf, sizeof rsaPmsBuf);
+ }
+ }
+ /* have PMS, build MS without PKCS11 */
+ rv = ssl3_MasterKeyDeriveBypass(pwSpec, cr, sr, &pmsItem, isTLS,
+ PR_TRUE);
+ if (rv != SECSuccess) {
+ pwSpec->msItem.data = pwSpec->raw_master_secret;
+ pwSpec->msItem.len = SSL3_MASTER_SECRET_LENGTH;
+ PK11_GenerateRandom(pwSpec->msItem.data, pwSpec->msItem.len);
+ }
+ rv = ssl3_InitPendingCipherSpec(ss, NULL);
+ } else
+#endif
+ {
+#ifndef NO_PKCS11_BYPASS
+double_bypass:
+#endif
+ /*
+ * unwrap pms out of the incoming buffer
+ * Note: CKM_SSL3_MASTER_KEY_DERIVE is NOT the mechanism used to do
+ * the unwrap. Rather, it is the mechanism with which the
+ * unwrapped pms will be used.
+ */
+ pms = PK11_PubUnwrapSymKey(serverKey, &enc_pms,
+ CKM_SSL3_MASTER_KEY_DERIVE, CKA_DERIVE, 0);
+ if (pms != NULL) {
+ PRINT_BUF(60, (ss, "decrypted premaster secret:",
+ PK11_GetKeyData(pms)->data,
+ PK11_GetKeyData(pms)->len));
+ } else {
+ /* unwrap failed. Generate a bogus PMS and carry on. */
+ PK11SlotInfo * slot = PK11_GetSlotFromPrivateKey(serverKey);
+
+ ssl_GetSpecWriteLock(ss);
+ pms = ssl3_GenerateRSAPMS(ss, ss->ssl3.prSpec, slot);
+ ssl_ReleaseSpecWriteLock(ss);
+ PK11_FreeSlot(slot);
+ }
+
+ if (pms == NULL) {
+ /* last gasp. */
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+ }
+
+ /* This step will derive the MS from the PMS, among other things. */
+ rv = ssl3_InitPendingCipherSpec(ss, pms);
+ PK11_FreeSymKey(pms);
+ }
+
+ if (rv != SECSuccess) {
+ SEND_ALERT
+ return SECFailure; /* error code set by ssl3_InitPendingCipherSpec */
+ }
+ return SECSuccess;
+}
+
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 ClientKeyExchange message from the remote client
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleClientKeyExchange(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ SECKEYPrivateKey *serverKey = NULL;
+ SECStatus rv;
+ const ssl3KEADef *kea_def;
+ ssl3KeyPair *serverKeyPair = NULL;
+#ifdef NSS_ENABLE_ECC
+ SECKEYPublicKey *serverPubKey = NULL;
+#endif /* NSS_ENABLE_ECC */
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle client_key_exchange handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ss->ssl3.hs.ws != wait_client_key) {
+ SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH);
+ return SECFailure;
+ }
+
+ kea_def = ss->ssl3.hs.kea_def;
+
+ if (ss->ssl3.hs.usedStepDownKey) {
+ PORT_Assert(kea_def->is_limited /* XXX OR cert is signing only */
+ && kea_def->exchKeyType == kt_rsa
+ && ss->stepDownKeyPair != NULL);
+ if (!kea_def->is_limited ||
+ kea_def->exchKeyType != kt_rsa ||
+ ss->stepDownKeyPair == NULL) {
+ /* shouldn't happen, don't use step down if it does */
+ goto skip;
+ }
+ serverKeyPair = ss->stepDownKeyPair;
+ ss->sec.keaKeyBits = EXPORT_RSA_KEY_LENGTH * BPB;
+ } else
+skip:
+#ifdef NSS_ENABLE_ECC
+ /* XXX Using SSLKEAType to index server certifiates
+ * does not work for (EC)DHE ciphers. Until we have
+ * an indexing mechanism general enough for all key
+ * exchange algorithms, we'll need to deal with each
+ * one seprately.
+ */
+ if ((kea_def->kea == kea_ecdhe_rsa) ||
+ (kea_def->kea == kea_ecdhe_ecdsa)) {
+ if (ss->ephemeralECDHKeyPair != NULL) {
+ serverKeyPair = ss->ephemeralECDHKeyPair;
+ if (serverKeyPair->pubKey) {
+ ss->sec.keaKeyBits =
+ SECKEY_PublicKeyStrengthInBits(serverKeyPair->pubKey);
+ }
+ }
+ } else
+#endif
+ {
+ sslServerCerts * sc = ss->serverCerts + kea_def->exchKeyType;
+ serverKeyPair = sc->serverKeyPair;
+ ss->sec.keaKeyBits = sc->serverKeyBits;
+ }
+
+ if (serverKeyPair) {
+ serverKey = serverKeyPair->privKey;
+ }
+
+ if (serverKey == NULL) {
+ SEND_ALERT
+ PORT_SetError(SSL_ERROR_NO_SERVER_KEY_FOR_ALG);
+ return SECFailure;
+ }
+
+ ss->sec.keaType = kea_def->exchKeyType;
+
+ switch (kea_def->exchKeyType) {
+ case kt_rsa:
+ rv = ssl3_HandleRSAClientKeyExchange(ss, b, length, serverKey);
+ if (rv != SECSuccess) {
+ SEND_ALERT
+ return SECFailure; /* error code set */
+ }
+ break;
+
+
+#ifdef NSS_ENABLE_ECC
+ case kt_ecdh:
+ /* XXX We really ought to be able to store multiple
+ * EC certs (a requirement if we wish to support both
+ * ECDH-RSA and ECDH-ECDSA key exchanges concurrently).
+ * When we make that change, we'll need an index other
+ * than kt_ecdh to pick the right EC certificate.
+ */
+ if (serverKeyPair) {
+ serverPubKey = serverKeyPair->pubKey;
+ }
+ if (serverPubKey == NULL) {
+ /* XXX Is this the right error code? */
+ PORT_SetError(SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE);
+ return SECFailure;
+ }
+ rv = ssl3_HandleECDHClientKeyExchange(ss, b, length,
+ serverPubKey, serverKey);
+ if (rv != SECSuccess) {
+ return SECFailure; /* error code set */
+ }
+ break;
+#endif /* NSS_ENABLE_ECC */
+
+ default:
+ (void) ssl3_HandshakeFailure(ss);
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ return SECFailure;
+ }
+ ss->ssl3.hs.ws = ss->sec.peerCert ? wait_cert_verify : wait_change_cipher;
+ return SECSuccess;
+
+}
+
+/* This is TLS's equivalent of sending a no_certificate alert. */
+static SECStatus
+ssl3_SendEmptyCertificate(sslSocket *ss)
+{
+ SECStatus rv;
+
+ rv = ssl3_AppendHandshakeHeader(ss, certificate, 3);
+ if (rv == SECSuccess) {
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 3);
+ }
+ return rv; /* error, if any, set by functions called above. */
+}
+
+SECStatus
+ssl3_HandleNewSessionTicket(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ SECStatus rv;
+ NewSessionTicket session_ticket;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle session_ticket handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (ss->ssl3.hs.ws != wait_new_session_ticket) {
+ SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET);
+ return SECFailure;
+ }
+
+ session_ticket.received_timestamp = ssl_Time();
+ if (length < 4) {
+ (void)SSL3_SendAlert(ss, alert_fatal, decode_error);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET);
+ return SECFailure;
+ }
+ session_ticket.ticket_lifetime_hint =
+ (PRUint32)ssl3_ConsumeHandshakeNumber(ss, 4, &b, &length);
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &session_ticket.ticket, 2,
+ &b, &length);
+ if (length != 0 || rv != SECSuccess) {
+ (void)SSL3_SendAlert(ss, alert_fatal, decode_error);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET);
+ return SECFailure; /* malformed */
+ }
+
+ rv = ssl3_SetSIDSessionTicket(ss->sec.ci.sid, &session_ticket);
+ if (rv != SECSuccess) {
+ (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure);
+ PORT_SetError(SSL_ERROR_INTERNAL_ERROR_ALERT);
+ return SECFailure;
+ }
+ ss->ssl3.hs.ws = wait_change_cipher;
+ return SECSuccess;
+}
+
+#ifdef NISCC_TEST
+static PRInt32 connNum = 0;
+
+static SECStatus
+get_fake_cert(SECItem *pCertItem, int *pIndex)
+{
+ PRFileDesc *cf;
+ char * testdir;
+ char * startat;
+ char * stopat;
+ const char *extension;
+ int fileNum;
+ PRInt32 numBytes = 0;
+ PRStatus prStatus;
+ PRFileInfo info;
+ char cfn[100];
+
+ pCertItem->data = 0;
+ if ((testdir = PR_GetEnv("NISCC_TEST")) == NULL) {
+ return SECSuccess;
+ }
+ *pIndex = (NULL != strstr(testdir, "root"));
+ extension = (strstr(testdir, "simple") ? "" : ".der");
+ fileNum = PR_ATOMIC_INCREMENT(&connNum) - 1;
+ if ((startat = PR_GetEnv("START_AT")) != NULL) {
+ fileNum += atoi(startat);
+ }
+ if ((stopat = PR_GetEnv("STOP_AT")) != NULL &&
+ fileNum >= atoi(stopat)) {
+ *pIndex = -1;
+ return SECSuccess;
+ }
+ sprintf(cfn, "%s/%08d%s", testdir, fileNum, extension);
+ cf = PR_Open(cfn, PR_RDONLY, 0);
+ if (!cf) {
+ goto loser;
+ }
+ prStatus = PR_GetOpenFileInfo(cf, &info);
+ if (prStatus != PR_SUCCESS) {
+ PR_Close(cf);
+ goto loser;
+ }
+ pCertItem = SECITEM_AllocItem(NULL, pCertItem, info.size);
+ if (pCertItem) {
+ numBytes = PR_Read(cf, pCertItem->data, info.size);
+ }
+ PR_Close(cf);
+ if (numBytes != info.size) {
+ SECITEM_FreeItem(pCertItem, PR_FALSE);
+ PORT_SetError(SEC_ERROR_IO);
+ goto loser;
+ }
+ fprintf(stderr, "using %s\n", cfn);
+ return SECSuccess;
+
+loser:
+ fprintf(stderr, "failed to use %s\n", cfn);
+ *pIndex = -1;
+ return SECFailure;
+}
+#endif
+
+/*
+ * Used by both client and server.
+ * Called from HandleServerHelloDone and from SendServerHelloSequence.
+ */
+static SECStatus
+ssl3_SendCertificate(sslSocket *ss)
+{
+ SECStatus rv;
+ CERTCertificateList *certChain;
+ int len = 0;
+ int i;
+ SSL3KEAType certIndex;
+#ifdef NISCC_TEST
+ SECItem fakeCert;
+ int ndex = -1;
+#endif
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send certificate handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->sec.localCert)
+ CERT_DestroyCertificate(ss->sec.localCert);
+ if (ss->sec.isServer) {
+ sslServerCerts * sc = NULL;
+
+ /* XXX SSLKEAType isn't really a good choice for
+ * indexing certificates (it breaks when we deal
+ * with (EC)DHE-* cipher suites. This hack ensures
+ * the RSA cert is picked for (EC)DHE-RSA.
+ * Revisit this when we add server side support
+ * for ECDHE-ECDSA or client-side authentication
+ * using EC certificates.
+ */
+ if ((ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) ||
+ (ss->ssl3.hs.kea_def->kea == kea_dhe_rsa)) {
+ certIndex = kt_rsa;
+ } else {
+ certIndex = ss->ssl3.hs.kea_def->exchKeyType;
+ }
+ sc = ss->serverCerts + certIndex;
+ certChain = sc->serverCertChain;
+ ss->sec.authKeyBits = sc->serverKeyBits;
+ ss->sec.authAlgorithm = ss->ssl3.hs.kea_def->signKeyType;
+ ss->sec.localCert = CERT_DupCertificate(sc->serverCert);
+ } else {
+ certChain = ss->ssl3.clientCertChain;
+ ss->sec.localCert = CERT_DupCertificate(ss->ssl3.clientCertificate);
+ }
+
+#ifdef NISCC_TEST
+ rv = get_fake_cert(&fakeCert, &ndex);
+#endif
+
+ if (certChain) {
+ for (i = 0; i < certChain->len; i++) {
+#ifdef NISCC_TEST
+ if (fakeCert.len > 0 && i == ndex) {
+ len += fakeCert.len + 3;
+ } else {
+ len += certChain->certs[i].len + 3;
+ }
+#else
+ len += certChain->certs[i].len + 3;
+#endif
+ }
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, certificate, len + 3);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, len, 3);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ if (certChain) {
+ for (i = 0; i < certChain->len; i++) {
+#ifdef NISCC_TEST
+ if (fakeCert.len > 0 && i == ndex) {
+ rv = ssl3_AppendHandshakeVariable(ss, fakeCert.data,
+ fakeCert.len, 3);
+ SECITEM_FreeItem(&fakeCert, PR_FALSE);
+ } else {
+ rv = ssl3_AppendHandshakeVariable(ss, certChain->certs[i].data,
+ certChain->certs[i].len, 3);
+ }
+#else
+ rv = ssl3_AppendHandshakeVariable(ss, certChain->certs[i].data,
+ certChain->certs[i].len, 3);
+#endif
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ }
+ }
+
+ return SECSuccess;
+}
+
+/*
+ * Used by server only.
+ * single-stapling, send only a single cert status
+ */
+static SECStatus
+ssl3_SendCertificateStatus(sslSocket *ss)
+{
+ SECStatus rv;
+ int len = 0;
+ SECItemArray *statusToSend = NULL;
+ SSL3KEAType certIndex;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send certificate status handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert( ss->sec.isServer);
+
+ if (!ssl3_ExtensionNegotiated(ss, ssl_cert_status_xtn))
+ return SECSuccess;
+
+ /* Use certStatus based on the cert being used. */
+ if ((ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) ||
+ (ss->ssl3.hs.kea_def->kea == kea_dhe_rsa)) {
+ certIndex = kt_rsa;
+ } else {
+ certIndex = ss->ssl3.hs.kea_def->exchKeyType;
+ }
+ if (ss->certStatusArray[certIndex] && ss->certStatusArray[certIndex]->len) {
+ statusToSend = ss->certStatusArray[certIndex];
+ }
+ if (!statusToSend)
+ return SECSuccess;
+
+ /* Use the array's first item only (single stapling) */
+ len = 1 + statusToSend->items[0].len + 3;
+
+ rv = ssl3_AppendHandshakeHeader(ss, certificate_status, len);
+ if (rv != SECSuccess) {
+ return rv; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_AppendHandshakeNumber(ss, 1 /*ocsp*/, 1);
+ if (rv != SECSuccess)
+ return rv; /* err set by AppendHandshake. */
+
+ rv = ssl3_AppendHandshakeVariable(ss,
+ statusToSend->items[0].data,
+ statusToSend->items[0].len,
+ 3);
+ if (rv != SECSuccess)
+ return rv; /* err set by AppendHandshake. */
+
+ return SECSuccess;
+}
+
+/* This is used to delete the CA certificates in the peer certificate chain
+ * from the cert database after they've been validated.
+ */
+static void
+ssl3_CleanupPeerCerts(sslSocket *ss)
+{
+ PLArenaPool * arena = ss->ssl3.peerCertArena;
+ ssl3CertNode *certs = (ssl3CertNode *)ss->ssl3.peerCertChain;
+
+ for (; certs; certs = certs->next) {
+ CERT_DestroyCertificate(certs->cert);
+ }
+ if (arena) PORT_FreeArena(arena, PR_FALSE);
+ ss->ssl3.peerCertArena = NULL;
+ ss->ssl3.peerCertChain = NULL;
+}
+
+static void
+ssl3_CopyPeerCertsFromSID(sslSocket *ss, sslSessionID *sid)
+{
+ PLArenaPool *arena;
+ ssl3CertNode *lastCert = NULL;
+ ssl3CertNode *certs = NULL;
+ int i;
+
+ if (!sid->peerCertChain[0])
+ return;
+ PORT_Assert(!ss->ssl3.peerCertArena);
+ PORT_Assert(!ss->ssl3.peerCertChain);
+ ss->ssl3.peerCertArena = arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
+ ssl3CertNode *c = PORT_ArenaNew(arena, ssl3CertNode);
+ c->cert = CERT_DupCertificate(sid->peerCertChain[i]);
+ c->next = NULL;
+ if (lastCert) {
+ lastCert->next = c;
+ } else {
+ certs = c;
+ }
+ lastCert = c;
+ }
+ ss->ssl3.peerCertChain = certs;
+}
+
+static void
+ssl3_CopyPeerCertsToSID(ssl3CertNode *certs, sslSessionID *sid)
+{
+ int i = 0;
+ ssl3CertNode *c = certs;
+ for (; i < MAX_PEER_CERT_CHAIN_SIZE && c; i++, c = c->next) {
+ PORT_Assert(!sid->peerCertChain[i]);
+ sid->peerCertChain[i] = CERT_DupCertificate(c->cert);
+ }
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 CertificateStatus message.
+ * Caller must hold Handshake and RecvBuf locks.
+ * This is always called before ssl3_HandleCertificate, even if the Certificate
+ * message is sent first.
+ */
+static SECStatus
+ssl3_HandleCertificateStatus(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ PRInt32 status, len;
+
+ if (ss->ssl3.hs.ws != wait_certificate_status) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CERT_STATUS);
+ return SECFailure;
+ }
+
+ PORT_Assert(!ss->sec.isServer);
+
+ /* Consume the CertificateStatusType enum */
+ status = ssl3_ConsumeHandshakeNumber(ss, 1, &b, &length);
+ if (status != 1 /* ocsp */) {
+ goto format_loser;
+ }
+
+ len = ssl3_ConsumeHandshakeNumber(ss, 3, &b, &length);
+ if (len != length) {
+ goto format_loser;
+ }
+
+#define MAX_CERTSTATUS_LEN 0x1ffff /* 128k - 1 */
+ if (length > MAX_CERTSTATUS_LEN)
+ goto format_loser;
+#undef MAX_CERTSTATUS_LEN
+
+ /* Array size 1, because we currently implement single-stapling only */
+ SECITEM_AllocArray(NULL, &ss->sec.ci.sid->peerCertStatus, 1);
+ if (!ss->sec.ci.sid->peerCertStatus.items)
+ return SECFailure;
+
+ ss->sec.ci.sid->peerCertStatus.items[0].data = PORT_Alloc(length);
+
+ if (!ss->sec.ci.sid->peerCertStatus.items[0].data) {
+ SECITEM_FreeArray(&ss->sec.ci.sid->peerCertStatus, PR_FALSE);
+ return SECFailure;
+ }
+
+ PORT_Memcpy(ss->sec.ci.sid->peerCertStatus.items[0].data, b, length);
+ ss->sec.ci.sid->peerCertStatus.items[0].len = length;
+ ss->sec.ci.sid->peerCertStatus.items[0].type = siBuffer;
+
+ return ssl3_AuthCertificate(ss);
+
+format_loser:
+ return ssl3_DecodeError(ss);
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Certificate message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleCertificate(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ ssl3CertNode * c;
+ ssl3CertNode * lastCert = NULL;
+ PRInt32 remaining = 0;
+ PRInt32 size;
+ SECStatus rv;
+ PRBool isServer = (PRBool)(!!ss->sec.isServer);
+ PRBool isTLS;
+ SSL3AlertDescription desc;
+ int errCode = SSL_ERROR_RX_MALFORMED_CERTIFICATE;
+ SECItem certItem;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle certificate handshake",
+ SSL_GETPID(), ss->fd));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if ((ss->ssl3.hs.ws != wait_server_cert) &&
+ (ss->ssl3.hs.ws != wait_client_cert)) {
+ desc = unexpected_message;
+ errCode = SSL_ERROR_RX_UNEXPECTED_CERTIFICATE;
+ goto alert_loser;
+ }
+
+ if (ss->sec.peerCert != NULL) {
+ if (ss->sec.peerKey) {
+ SECKEY_DestroyPublicKey(ss->sec.peerKey);
+ ss->sec.peerKey = NULL;
+ }
+ CERT_DestroyCertificate(ss->sec.peerCert);
+ ss->sec.peerCert = NULL;
+ }
+
+ ssl3_CleanupPeerCerts(ss);
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+
+ /* It is reported that some TLS client sends a Certificate message
+ ** with a zero-length message body. We'll treat that case like a
+ ** normal no_certificates message to maximize interoperability.
+ */
+ if (length) {
+ remaining = ssl3_ConsumeHandshakeNumber(ss, 3, &b, &length);
+ if (remaining < 0)
+ goto loser; /* fatal alert already sent by ConsumeHandshake. */
+ if ((PRUint32)remaining > length)
+ goto decode_loser;
+ }
+
+ if (!remaining) {
+ if (!(isTLS && isServer)) {
+ desc = bad_certificate;
+ goto alert_loser;
+ }
+ /* This is TLS's version of a no_certificate alert. */
+ /* I'm a server. I've requested a client cert. He hasn't got one. */
+ rv = ssl3_HandleNoCertificate(ss);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser;
+ }
+ ss->ssl3.hs.ws = wait_client_key;
+ return SECSuccess;
+ }
+
+ ss->ssl3.peerCertArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (ss->ssl3.peerCertArena == NULL) {
+ goto loser; /* don't send alerts on memory errors */
+ }
+
+ /* First get the peer cert. */
+ remaining -= 3;
+ if (remaining < 0)
+ goto decode_loser;
+
+ size = ssl3_ConsumeHandshakeNumber(ss, 3, &b, &length);
+ if (size <= 0)
+ goto loser; /* fatal alert already sent by ConsumeHandshake. */
+
+ if (remaining < size)
+ goto decode_loser;
+
+ certItem.data = b;
+ certItem.len = size;
+ b += size;
+ length -= size;
+ remaining -= size;
+
+ ss->sec.peerCert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
+ PR_FALSE, PR_TRUE);
+ if (ss->sec.peerCert == NULL) {
+ /* We should report an alert if the cert was bad, but not if the
+ * problem was just some local problem, like memory error.
+ */
+ goto ambiguous_err;
+ }
+
+ /* Now get all of the CA certs. */
+ while (remaining > 0) {
+ remaining -= 3;
+ if (remaining < 0)
+ goto decode_loser;
+
+ size = ssl3_ConsumeHandshakeNumber(ss, 3, &b, &length);
+ if (size <= 0)
+ goto loser; /* fatal alert already sent by ConsumeHandshake. */
+
+ if (remaining < size)
+ goto decode_loser;
+
+ certItem.data = b;
+ certItem.len = size;
+ b += size;
+ length -= size;
+ remaining -= size;
+
+ c = PORT_ArenaNew(ss->ssl3.peerCertArena, ssl3CertNode);
+ if (c == NULL) {
+ goto loser; /* don't send alerts on memory errors */
+ }
+
+ c->cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
+ PR_FALSE, PR_TRUE);
+ if (c->cert == NULL) {
+ goto ambiguous_err;
+ }
+
+ c->next = NULL;
+ if (lastCert) {
+ lastCert->next = c;
+ } else {
+ ss->ssl3.peerCertChain = c;
+ }
+ lastCert = c;
+ }
+
+ if (remaining != 0)
+ goto decode_loser;
+
+ SECKEY_UpdateCertPQG(ss->sec.peerCert);
+
+ if (!isServer && ssl3_ExtensionNegotiated(ss, ssl_cert_status_xtn)) {
+ ss->ssl3.hs.ws = wait_certificate_status;
+ rv = SECSuccess;
+ } else {
+ rv = ssl3_AuthCertificate(ss); /* sets ss->ssl3.hs.ws */
+ }
+
+ return rv;
+
+ambiguous_err:
+ errCode = PORT_GetError();
+ switch (errCode) {
+ case PR_OUT_OF_MEMORY_ERROR:
+ case SEC_ERROR_BAD_DATABASE:
+ case SEC_ERROR_NO_MEMORY:
+ if (isTLS) {
+ desc = internal_error;
+ goto alert_loser;
+ }
+ goto loser;
+ }
+ ssl3_SendAlertForCertError(ss, errCode);
+ goto loser;
+
+decode_loser:
+ desc = isTLS ? decode_error : bad_certificate;
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+
+loser:
+ (void)ssl_MapLowLevelError(errCode);
+ return SECFailure;
+}
+
+static SECStatus
+ssl3_AuthCertificate(sslSocket *ss)
+{
+ SECStatus rv;
+ PRBool isServer = (PRBool)(!!ss->sec.isServer);
+ int errCode;
+
+ ss->ssl3.hs.authCertificatePending = PR_FALSE;
+
+ /*
+ * Ask caller-supplied callback function to validate cert chain.
+ */
+ rv = (SECStatus)(*ss->authCertificate)(ss->authCertificateArg, ss->fd,
+ PR_TRUE, isServer);
+ if (rv) {
+ errCode = PORT_GetError();
+ if (rv != SECWouldBlock) {
+ if (ss->handleBadCert) {
+ rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd);
+ }
+ }
+
+ if (rv == SECWouldBlock) {
+ if (ss->sec.isServer) {
+ errCode = SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS;
+ rv = SECFailure;
+ goto loser;
+ }
+
+ ss->ssl3.hs.authCertificatePending = PR_TRUE;
+ rv = SECSuccess;
+
+ /* XXX: Async cert validation and False Start don't work together
+ * safely yet; if we leave False Start enabled, we may end up false
+ * starting (sending application data) before we
+ * SSL_AuthCertificateComplete has been called.
+ */
+ ss->opt.enableFalseStart = PR_FALSE;
+ }
+
+ if (rv != SECSuccess) {
+ ssl3_SendAlertForCertError(ss, errCode);
+ goto loser;
+ }
+ }
+
+ ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
+ ssl3_CopyPeerCertsToSID(ss->ssl3.peerCertChain, ss->sec.ci.sid);
+
+ if (!ss->sec.isServer) {
+ CERTCertificate *cert = ss->sec.peerCert;
+
+ /* set the server authentication and key exchange types and sizes
+ ** from the value in the cert. If the key exchange key is different,
+ ** it will get fixed when we handle the server key exchange message.
+ */
+ SECKEYPublicKey * pubKey = CERT_ExtractPublicKey(cert);
+ ss->sec.authAlgorithm = ss->ssl3.hs.kea_def->signKeyType;
+ ss->sec.keaType = ss->ssl3.hs.kea_def->exchKeyType;
+ if (pubKey) {
+ ss->sec.keaKeyBits = ss->sec.authKeyBits =
+ SECKEY_PublicKeyStrengthInBits(pubKey);
+#ifdef NSS_ENABLE_ECC
+ if (ss->sec.keaType == kt_ecdh) {
+ /* Get authKeyBits from signing key.
+ * XXX The code below uses a quick approximation of
+ * key size based on cert->signatureWrap.signature.data
+ * (which contains the DER encoded signature). The field
+ * cert->signatureWrap.signature.len contains the
+ * length of the encoded signature in bits.
+ */
+ if (ss->ssl3.hs.kea_def->kea == kea_ecdh_ecdsa) {
+ ss->sec.authKeyBits =
+ cert->signatureWrap.signature.data[3]*8;
+ if (cert->signatureWrap.signature.data[4] == 0x00)
+ ss->sec.authKeyBits -= 8;
+ /*
+ * XXX: if cert is not signed by ecdsa we should
+ * destroy pubKey and goto bad_cert
+ */
+ } else if (ss->ssl3.hs.kea_def->kea == kea_ecdh_rsa) {
+ ss->sec.authKeyBits = cert->signatureWrap.signature.len;
+ /*
+ * XXX: if cert is not signed by rsa we should
+ * destroy pubKey and goto bad_cert
+ */
+ }
+ }
+#endif /* NSS_ENABLE_ECC */
+ SECKEY_DestroyPublicKey(pubKey);
+ pubKey = NULL;
+ }
+
+ ss->ssl3.hs.ws = wait_cert_request; /* disallow server_key_exchange */
+ if (ss->ssl3.hs.kea_def->is_limited ||
+ /* XXX OR server cert is signing only. */
+#ifdef NSS_ENABLE_ECC
+ ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa ||
+ ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa ||
+#endif /* NSS_ENABLE_ECC */
+ ss->ssl3.hs.kea_def->exchKeyType == kt_dh) {
+ ss->ssl3.hs.ws = wait_server_key; /* allow server_key_exchange */
+ }
+ } else {
+ ss->ssl3.hs.ws = wait_client_key;
+ }
+
+ PORT_Assert(rv == SECSuccess);
+ if (rv != SECSuccess) {
+ errCode = SEC_ERROR_LIBRARY_FAILURE;
+ rv = SECFailure;
+ goto loser;
+ }
+
+ return rv;
+
+loser:
+ (void)ssl_MapLowLevelError(errCode);
+ return SECFailure;
+}
+
+static SECStatus ssl3_FinishHandshake(sslSocket *ss);
+
+static SECStatus
+ssl3_AlwaysFail(sslSocket * ss)
+{
+ PORT_SetError(PR_INVALID_STATE_ERROR);
+ return SECFailure;
+}
+
+/* Caller must hold 1stHandshakeLock.
+*/
+SECStatus
+ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error)
+{
+ SECStatus rv;
+
+ PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss));
+
+ if (ss->sec.isServer) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS);
+ return SECFailure;
+ }
+
+ ssl_GetRecvBufLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ if (!ss->ssl3.hs.authCertificatePending) {
+ PORT_SetError(PR_INVALID_STATE_ERROR);
+ rv = SECFailure;
+ goto done;
+ }
+
+ ss->ssl3.hs.authCertificatePending = PR_FALSE;
+
+ if (error != 0) {
+ ss->ssl3.hs.restartTarget = ssl3_AlwaysFail;
+ ssl3_SendAlertForCertError(ss, error);
+ rv = SECSuccess;
+ } else if (ss->ssl3.hs.restartTarget != NULL) {
+ sslRestartTarget target = ss->ssl3.hs.restartTarget;
+ ss->ssl3.hs.restartTarget = NULL;
+ rv = target(ss);
+ /* Even if we blocked here, we have accomplished enough to claim
+ * success. Any remaining work will be taken care of by subsequent
+ * calls to SSL_ForceHandshake/PR_Send/PR_Read/etc.
+ */
+ if (rv == SECWouldBlock) {
+ rv = SECSuccess;
+ }
+ } else {
+ rv = SECSuccess;
+ }
+
+done:
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_ReleaseRecvBufLock(ss);
+
+ return rv;
+}
+
+static SECStatus
+ssl3_ComputeTLSFinished(ssl3CipherSpec *spec,
+ PRBool isServer,
+ const SSL3Hashes * hashes,
+ TLSFinished * tlsFinished)
+{
+ const char * label;
+ unsigned int len;
+ SECStatus rv;
+
+ label = isServer ? "server finished" : "client finished";
+ len = 15;
+
+ rv = ssl3_TLSPRFWithMasterSecret(spec, label, len, hashes->u.raw,
+ hashes->len, tlsFinished->verify_data,
+ sizeof tlsFinished->verify_data);
+
+ return rv;
+}
+
+/* The calling function must acquire and release the appropriate
+ * lock (e.g., ssl_GetSpecReadLock / ssl_ReleaseSpecReadLock for
+ * ss->ssl3.crSpec).
+ */
+SECStatus
+ssl3_TLSPRFWithMasterSecret(ssl3CipherSpec *spec, const char *label,
+ unsigned int labelLen, const unsigned char *val, unsigned int valLen,
+ unsigned char *out, unsigned int outLen)
+{
+ SECStatus rv = SECSuccess;
+
+ if (spec->master_secret && !spec->bypassCiphers) {
+ SECItem param = {siBuffer, NULL, 0};
+ CK_MECHANISM_TYPE mech = CKM_TLS_PRF_GENERAL;
+ PK11Context *prf_context;
+ unsigned int retLen;
+
+ if (spec->version >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ mech = CKM_NSS_TLS_PRF_GENERAL_SHA256;
+ }
+ prf_context = PK11_CreateContextBySymKey(mech, CKA_SIGN,
+ spec->master_secret, &param);
+ if (!prf_context)
+ return SECFailure;
+
+ rv = PK11_DigestBegin(prf_context);
+ rv |= PK11_DigestOp(prf_context, (unsigned char *) label, labelLen);
+ rv |= PK11_DigestOp(prf_context, val, valLen);
+ rv |= PK11_DigestFinal(prf_context, out, &retLen, outLen);
+ PORT_Assert(rv != SECSuccess || retLen == outLen);
+
+ PK11_DestroyContext(prf_context, PR_TRUE);
+ } else {
+ /* bypass PKCS11 */
+#ifdef NO_PKCS11_BYPASS
+ PORT_Assert(spec->master_secret);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ rv = SECFailure;
+#else
+ SECItem inData = { siBuffer, };
+ SECItem outData = { siBuffer, };
+ PRBool isFIPS = PR_FALSE;
+
+ inData.data = (unsigned char *) val;
+ inData.len = valLen;
+ outData.data = out;
+ outData.len = outLen;
+ if (spec->version >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ rv = TLS_P_hash(HASH_AlgSHA256, &spec->msItem, label, &inData,
+ &outData, isFIPS);
+ } else {
+ rv = TLS_PRF(&spec->msItem, label, &inData, &outData, isFIPS);
+ }
+ PORT_Assert(rv != SECSuccess || outData.len == outLen);
+#endif
+ }
+ return rv;
+}
+
+/* called from ssl3_HandleServerHelloDone
+ */
+static SECStatus
+ssl3_SendNextProto(sslSocket *ss)
+{
+ SECStatus rv;
+ int padding_len;
+ static const unsigned char padding[32] = {0};
+
+ if (ss->ssl3.nextProto.len == 0 ||
+ ss->ssl3.nextProtoState == SSL_NEXT_PROTO_SELECTED) {
+ return SECSuccess;
+ }
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ padding_len = 32 - ((ss->ssl3.nextProto.len + 2) % 32);
+
+ rv = ssl3_AppendHandshakeHeader(ss, next_proto, ss->ssl3.nextProto.len +
+ 2 + padding_len);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshakeHeader */
+ }
+ rv = ssl3_AppendHandshakeVariable(ss, ss->ssl3.nextProto.data,
+ ss->ssl3.nextProto.len, 1);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake */
+ }
+ rv = ssl3_AppendHandshakeVariable(ss, padding, padding_len, 1);
+ if (rv != SECSuccess) {
+ return rv; /* error code set by AppendHandshake */
+ }
+ return rv;
+}
+
+/* called from ssl3_SendFinished
+ *
+ * This function is simply a debugging aid and therefore does not return a
+ * SECStatus. */
+static void
+ssl3_RecordKeyLog(sslSocket *ss)
+{
+ SECStatus rv;
+ SECItem *keyData;
+ char buf[14 /* "CLIENT_RANDOM " */ +
+ SSL3_RANDOM_LENGTH*2 /* client_random */ +
+ 1 /* " " */ +
+ 48*2 /* master secret */ +
+ 1 /* new line */];
+ unsigned int j;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (!ssl_keylog_iob)
+ return;
+
+ rv = PK11_ExtractKeyValue(ss->ssl3.cwSpec->master_secret);
+ if (rv != SECSuccess)
+ return;
+
+ ssl_GetSpecReadLock(ss);
+
+ /* keyData does not need to be freed. */
+ keyData = PK11_GetKeyData(ss->ssl3.cwSpec->master_secret);
+ if (!keyData || !keyData->data || keyData->len != 48) {
+ ssl_ReleaseSpecReadLock(ss);
+ return;
+ }
+
+ /* https://developer.mozilla.org/en/NSS_Key_Log_Format */
+
+ /* There could be multiple, concurrent writers to the
+ * keylog, so we have to do everything in a single call to
+ * fwrite. */
+
+ memcpy(buf, "CLIENT_RANDOM ", 14);
+ j = 14;
+ hexEncode(buf + j, ss->ssl3.hs.client_random.rand, SSL3_RANDOM_LENGTH);
+ j += SSL3_RANDOM_LENGTH*2;
+ buf[j++] = ' ';
+ hexEncode(buf + j, keyData->data, 48);
+ j += 48*2;
+ buf[j++] = '\n';
+
+ PORT_Assert(j == sizeof(buf));
+
+ ssl_ReleaseSpecReadLock(ss);
+
+ if (fwrite(buf, sizeof(buf), 1, ssl_keylog_iob) != 1)
+ return;
+ fflush(ssl_keylog_iob);
+ return;
+}
+
+/* called from ssl3_SendClientSecondRound
+ * ssl3_HandleFinished
+ */
+static SECStatus
+ssl3_SendEncryptedExtensions(sslSocket *ss)
+{
+ static const char CHANNEL_ID_MAGIC[] = "TLS Channel ID signature";
+ /* This is the ASN.1 prefix for a P-256 public key. Specifically it's:
+ * SEQUENCE
+ * SEQUENCE
+ * OID id-ecPublicKey
+ * OID prime256v1
+ * BIT STRING, length 66, 0 trailing bits: 0x04
+ *
+ * The 0x04 in the BIT STRING is the prefix for an uncompressed, X9.62
+ * public key. Following that are the two field elements as 32-byte,
+ * big-endian numbers, as required by the Channel ID. */
+ static const unsigned char P256_SPKI_PREFIX[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+ 0x42, 0x00, 0x04
+ };
+ /* ChannelIDs are always 128 bytes long: 64 bytes of P-256 public key and 64
+ * bytes of ECDSA signature. */
+ static const int CHANNEL_ID_PUBLIC_KEY_LENGTH = 64;
+ static const int CHANNEL_ID_LENGTH = 128;
+
+ SECStatus rv = SECFailure;
+ SECItem *spki = NULL;
+ SSL3Hashes hashes;
+ const unsigned char *pub_bytes;
+ unsigned char signed_data[sizeof(CHANNEL_ID_MAGIC) + sizeof(SSL3Hashes)];
+ unsigned char digest[SHA256_LENGTH];
+ SECItem digest_item;
+ unsigned char signature[64];
+ SECItem signature_item;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->ssl3.channelID == NULL)
+ return SECSuccess;
+
+ PORT_Assert(ssl3_ExtensionNegotiated(ss, ssl_channel_id_xtn));
+
+ if (SECKEY_GetPrivateKeyType(ss->ssl3.channelID) != ecKey ||
+ PK11_SignatureLen(ss->ssl3.channelID) != sizeof(signature)) {
+ PORT_SetError(SSL_ERROR_INVALID_CHANNEL_ID_KEY);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ ssl_GetSpecReadLock(ss);
+ rv = ssl3_ComputeHandshakeHashes(ss, ss->ssl3.cwSpec, &hashes, 0);
+ ssl_ReleaseSpecReadLock(ss);
+
+ if (rv != SECSuccess)
+ goto loser;
+
+ rv = ssl3_AppendHandshakeHeader(ss, encrypted_extensions,
+ 2 + 2 + CHANNEL_ID_LENGTH);
+ if (rv != SECSuccess)
+ goto loser; /* error code set by AppendHandshakeHeader */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_channel_id_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser; /* error code set by AppendHandshake */
+ rv = ssl3_AppendHandshakeNumber(ss, CHANNEL_ID_LENGTH, 2);
+ if (rv != SECSuccess)
+ goto loser; /* error code set by AppendHandshake */
+
+ spki = SECKEY_EncodeDERSubjectPublicKeyInfo(ss->ssl3.channelIDPub);
+
+ if (spki->len != sizeof(P256_SPKI_PREFIX) + CHANNEL_ID_PUBLIC_KEY_LENGTH ||
+ memcmp(spki->data, P256_SPKI_PREFIX, sizeof(P256_SPKI_PREFIX) != 0)) {
+ PORT_SetError(SSL_ERROR_INVALID_CHANNEL_ID_KEY);
+ rv = SECFailure;
+ goto loser;
+ }
+
+ pub_bytes = spki->data + sizeof(P256_SPKI_PREFIX);
+
+ memcpy(signed_data, CHANNEL_ID_MAGIC, sizeof(CHANNEL_ID_MAGIC));
+ memcpy(signed_data + sizeof(CHANNEL_ID_MAGIC), hashes.u.raw, hashes.len);
+
+ rv = PK11_HashBuf(SEC_OID_SHA256, digest, signed_data,
+ sizeof(CHANNEL_ID_MAGIC) + hashes.len);
+ if (rv != SECSuccess)
+ goto loser;
+
+ digest_item.data = digest;
+ digest_item.len = sizeof(digest);
+
+ signature_item.data = signature;
+ signature_item.len = sizeof(signature);
+
+ rv = PK11_Sign(ss->ssl3.channelID, &signature_item, &digest_item);
+ if (rv != SECSuccess)
+ goto loser;
+
+ rv = ssl3_AppendHandshake(ss, pub_bytes, CHANNEL_ID_PUBLIC_KEY_LENGTH);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshake(ss, signature, sizeof(signature));
+
+loser:
+ if (spki)
+ SECITEM_FreeItem(spki, PR_TRUE);
+ if (ss->ssl3.channelID) {
+ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
+ ss->ssl3.channelID = NULL;
+ }
+ if (ss->ssl3.channelIDPub) {
+ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
+ ss->ssl3.channelIDPub = NULL;
+ }
+
+ return rv;
+}
+
+/* ssl3_RestartHandshakeAfterChannelIDReq is called to restart a handshake
+ * after a ChannelID callback returned SECWouldBlock. At this point we have
+ * processed the server's ServerHello but not yet any further messages. We will
+ * always get a message from the server after a ServerHello so either they are
+ * waiting in the buffer or we'll get network I/O. */
+SECStatus
+ssl3_RestartHandshakeAfterChannelIDReq(sslSocket *ss,
+ SECKEYPublicKey *channelIDPub,
+ SECKEYPrivateKey *channelID)
+{
+ if (ss->handshake == 0) {
+ SECKEY_DestroyPublicKey(channelIDPub);
+ SECKEY_DestroyPrivateKey(channelID);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ if (channelIDPub == NULL ||
+ channelID == NULL) {
+ if (channelIDPub)
+ SECKEY_DestroyPublicKey(channelIDPub);
+ if (channelID)
+ SECKEY_DestroyPrivateKey(channelID);
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+
+ if (ss->ssl3.channelID)
+ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
+ if (ss->ssl3.channelIDPub)
+ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
+
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->ssl3.channelID = channelID;
+ ss->ssl3.channelIDPub = channelIDPub;
+
+ return SECSuccess;
+}
+
+/* called from ssl3_HandleServerHelloDone
+ * ssl3_HandleClientHello
+ * ssl3_HandleFinished
+ */
+static SECStatus
+ssl3_SendFinished(sslSocket *ss, PRInt32 flags)
+{
+ ssl3CipherSpec *cwSpec;
+ PRBool isTLS;
+ PRBool isServer = ss->sec.isServer;
+ SECStatus rv;
+ SSL3Sender sender = isServer ? sender_server : sender_client;
+ SSL3Hashes hashes;
+ TLSFinished tlsFinished;
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send finished handshake", SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ ssl_GetSpecReadLock(ss);
+ cwSpec = ss->ssl3.cwSpec;
+ isTLS = (PRBool)(cwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ rv = ssl3_ComputeHandshakeHashes(ss, cwSpec, &hashes, sender);
+ if (isTLS && rv == SECSuccess) {
+ rv = ssl3_ComputeTLSFinished(cwSpec, isServer, &hashes, &tlsFinished);
+ }
+ ssl_ReleaseSpecReadLock(ss);
+ if (rv != SECSuccess) {
+ goto fail; /* err code was set by ssl3_ComputeHandshakeHashes */
+ }
+
+ if (isTLS) {
+ if (isServer)
+ ss->ssl3.hs.finishedMsgs.tFinished[1] = tlsFinished;
+ else
+ ss->ssl3.hs.finishedMsgs.tFinished[0] = tlsFinished;
+ ss->ssl3.hs.finishedBytes = sizeof tlsFinished;
+ rv = ssl3_AppendHandshakeHeader(ss, finished, sizeof tlsFinished);
+ if (rv != SECSuccess)
+ goto fail; /* err set by AppendHandshake. */
+ rv = ssl3_AppendHandshake(ss, &tlsFinished, sizeof tlsFinished);
+ if (rv != SECSuccess)
+ goto fail; /* err set by AppendHandshake. */
+ } else {
+ if (isServer)
+ ss->ssl3.hs.finishedMsgs.sFinished[1] = hashes.u.s;
+ else
+ ss->ssl3.hs.finishedMsgs.sFinished[0] = hashes.u.s;
+ PORT_Assert(hashes.len == sizeof hashes.u.s);
+ ss->ssl3.hs.finishedBytes = sizeof hashes.u.s;
+ rv = ssl3_AppendHandshakeHeader(ss, finished, sizeof hashes.u.s);
+ if (rv != SECSuccess)
+ goto fail; /* err set by AppendHandshake. */
+ rv = ssl3_AppendHandshake(ss, &hashes.u.s, sizeof hashes.u.s);
+ if (rv != SECSuccess)
+ goto fail; /* err set by AppendHandshake. */
+ }
+ rv = ssl3_FlushHandshake(ss, flags);
+ if (rv != SECSuccess) {
+ goto fail; /* error code set by ssl3_FlushHandshake */
+ }
+
+ ssl3_RecordKeyLog(ss);
+
+ return SECSuccess;
+
+fail:
+ return rv;
+}
+
+/* wrap the master secret, and put it into the SID.
+ * Caller holds the Spec read lock.
+ */
+SECStatus
+ssl3_CacheWrappedMasterSecret(sslSocket *ss, sslSessionID *sid,
+ ssl3CipherSpec *spec, SSL3KEAType effectiveExchKeyType)
+{
+ PK11SymKey * wrappingKey = NULL;
+ PK11SlotInfo * symKeySlot;
+ void * pwArg = ss->pkcs11PinArg;
+ SECStatus rv = SECFailure;
+ PRBool isServer = ss->sec.isServer;
+ CK_MECHANISM_TYPE mechanism = CKM_INVALID_MECHANISM;
+ symKeySlot = PK11_GetSlotFromKey(spec->master_secret);
+ if (!isServer) {
+ int wrapKeyIndex;
+ int incarnation;
+
+ /* these next few functions are mere accessors and don't fail. */
+ sid->u.ssl3.masterWrapIndex = wrapKeyIndex =
+ PK11_GetCurrentWrapIndex(symKeySlot);
+ PORT_Assert(wrapKeyIndex == 0); /* array has only one entry! */
+
+ sid->u.ssl3.masterWrapSeries = incarnation =
+ PK11_GetSlotSeries(symKeySlot);
+ sid->u.ssl3.masterSlotID = PK11_GetSlotID(symKeySlot);
+ sid->u.ssl3.masterModuleID = PK11_GetModuleID(symKeySlot);
+ sid->u.ssl3.masterValid = PR_TRUE;
+ /* Get the default wrapping key, for wrapping the master secret before
+ * placing it in the SID cache entry. */
+ wrappingKey = PK11_GetWrapKey(symKeySlot, wrapKeyIndex,
+ CKM_INVALID_MECHANISM, incarnation,
+ pwArg);
+ if (wrappingKey) {
+ mechanism = PK11_GetMechanism(wrappingKey); /* can't fail. */
+ } else {
+ int keyLength;
+ /* if the wrappingKey doesn't exist, attempt to create it.
+ * Note: we intentionally ignore errors here. If we cannot
+ * generate a wrapping key, it is not fatal to this SSL connection,
+ * but we will not be able to restart this session.
+ */
+ mechanism = PK11_GetBestWrapMechanism(symKeySlot);
+ keyLength = PK11_GetBestKeyLength(symKeySlot, mechanism);
+ /* Zero length means fixed key length algorithm, or error.
+ * It's ambiguous.
+ */
+ wrappingKey = PK11_KeyGen(symKeySlot, mechanism, NULL,
+ keyLength, pwArg);
+ if (wrappingKey) {
+ PK11_SetWrapKey(symKeySlot, wrapKeyIndex, wrappingKey);
+ }
+ }
+ } else {
+ /* server socket using session cache. */
+ mechanism = PK11_GetBestWrapMechanism(symKeySlot);
+ if (mechanism != CKM_INVALID_MECHANISM) {
+ wrappingKey =
+ getWrappingKey(ss, symKeySlot, effectiveExchKeyType,
+ mechanism, pwArg);
+ if (wrappingKey) {
+ mechanism = PK11_GetMechanism(wrappingKey); /* can't fail. */
+ }
+ }
+ }
+
+ sid->u.ssl3.masterWrapMech = mechanism;
+ PK11_FreeSlot(symKeySlot);
+
+ if (wrappingKey) {
+ SECItem wmsItem;
+
+ wmsItem.data = sid->u.ssl3.keys.wrapped_master_secret;
+ wmsItem.len = sizeof sid->u.ssl3.keys.wrapped_master_secret;
+ rv = PK11_WrapSymKey(mechanism, NULL, wrappingKey,
+ spec->master_secret, &wmsItem);
+ /* rv is examined below. */
+ sid->u.ssl3.keys.wrapped_master_secret_len = wmsItem.len;
+ PK11_FreeSymKey(wrappingKey);
+ }
+ return rv;
+}
+
+/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
+ * ssl3 Finished message from the peer.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleFinished(sslSocket *ss, SSL3Opaque *b, PRUint32 length,
+ const SSL3Hashes *hashes)
+{
+ sslSessionID * sid = ss->sec.ci.sid;
+ SECStatus rv = SECSuccess;
+ PRBool isServer = ss->sec.isServer;
+ PRBool isTLS;
+ SSL3KEAType effectiveExchKeyType;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ SSL_TRC(3, ("%d: SSL3[%d]: handle finished handshake",
+ SSL_GETPID(), ss->fd));
+
+ if (ss->ssl3.hs.ws != wait_finished) {
+ SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_FINISHED);
+ return SECFailure;
+ }
+
+ isTLS = (PRBool)(ss->ssl3.crSpec->version > SSL_LIBRARY_VERSION_3_0);
+ if (isTLS) {
+ TLSFinished tlsFinished;
+
+ if (length != sizeof tlsFinished) {
+ (void)SSL3_SendAlert(ss, alert_fatal, decode_error);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_FINISHED);
+ return SECFailure;
+ }
+ rv = ssl3_ComputeTLSFinished(ss->ssl3.crSpec, !isServer,
+ hashes, &tlsFinished);
+ if (!isServer)
+ ss->ssl3.hs.finishedMsgs.tFinished[1] = tlsFinished;
+ else
+ ss->ssl3.hs.finishedMsgs.tFinished[0] = tlsFinished;
+ ss->ssl3.hs.finishedBytes = sizeof tlsFinished;
+ if (rv != SECSuccess ||
+ 0 != NSS_SecureMemcmp(&tlsFinished, b, length)) {
+ (void)SSL3_SendAlert(ss, alert_fatal, decrypt_error);
+ PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ return SECFailure;
+ }
+ } else {
+ if (length != sizeof(SSL3Finished)) {
+ (void)ssl3_IllegalParameter(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_FINISHED);
+ return SECFailure;
+ }
+
+ if (!isServer)
+ ss->ssl3.hs.finishedMsgs.sFinished[1] = hashes->u.s;
+ else
+ ss->ssl3.hs.finishedMsgs.sFinished[0] = hashes->u.s;
+ PORT_Assert(hashes->len == sizeof hashes->u.s);
+ ss->ssl3.hs.finishedBytes = sizeof hashes->u.s;
+ if (0 != NSS_SecureMemcmp(&hashes->u.s, b, length)) {
+ (void)ssl3_HandshakeFailure(ss);
+ PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ return SECFailure;
+ }
+ }
+
+ ssl_GetXmitBufLock(ss); /*************************************/
+
+ if ((isServer && !ss->ssl3.hs.isResuming) ||
+ (!isServer && ss->ssl3.hs.isResuming)) {
+ PRInt32 flags = 0;
+
+ /* Send a NewSessionTicket message if the client sent us
+ * either an empty session ticket, or one that did not verify.
+ * (Note that if either of these conditions was met, then the
+ * server has sent a SessionTicket extension in the
+ * ServerHello message.)
+ */
+ if (isServer && !ss->ssl3.hs.isResuming &&
+ ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn)) {
+ rv = ssl3_SendNewSessionTicket(ss);
+ if (rv != SECSuccess) {
+ goto xmit_loser;
+ }
+ }
+
+ rv = ssl3_SendChangeCipherSpecs(ss);
+ if (rv != SECSuccess) {
+ goto xmit_loser; /* err is set. */
+ }
+ /* If this thread is in SSL_SecureSend (trying to write some data)
+ ** then set the ssl_SEND_FLAG_FORCE_INTO_BUFFER flag, so that the
+ ** last two handshake messages (change cipher spec and finished)
+ ** will be sent in the same send/write call as the application data.
+ */
+ if (ss->writerThread == PR_GetCurrentThread()) {
+ flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER;
+ }
+
+ if (!isServer) {
+ if (!ss->firstHsDone) {
+ rv = ssl3_SendNextProto(ss);
+ if (rv != SECSuccess) {
+ goto xmit_loser; /* err code was set. */
+ }
+ }
+ rv = ssl3_SendEncryptedExtensions(ss);
+ if (rv != SECSuccess)
+ goto xmit_loser; /* err code was set. */
+ }
+
+ if (IS_DTLS(ss)) {
+ flags |= ssl_SEND_FLAG_NO_RETRANSMIT;
+ }
+
+ rv = ssl3_SendFinished(ss, flags);
+ if (rv != SECSuccess) {
+ goto xmit_loser; /* err is set. */
+ }
+ }
+
+xmit_loser:
+ ssl_ReleaseXmitBufLock(ss); /*************************************/
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ ss->gs.writeOffset = 0;
+ ss->gs.readOffset = 0;
+
+ if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) {
+ effectiveExchKeyType = kt_rsa;
+ } else {
+ effectiveExchKeyType = ss->ssl3.hs.kea_def->exchKeyType;
+ }
+
+ if (sid->cached == never_cached && !ss->opt.noCache && ss->sec.cache) {
+ /* fill in the sid */
+ sid->u.ssl3.cipherSuite = ss->ssl3.hs.cipher_suite;
+ sid->u.ssl3.compression = ss->ssl3.hs.compression;
+ sid->u.ssl3.policy = ss->ssl3.policy;
+#ifdef NSS_ENABLE_ECC
+ sid->u.ssl3.negotiatedECCurves = ss->ssl3.hs.negotiatedECCurves;
+#endif
+ sid->u.ssl3.exchKeyType = effectiveExchKeyType;
+ sid->version = ss->version;
+ sid->authAlgorithm = ss->sec.authAlgorithm;
+ sid->authKeyBits = ss->sec.authKeyBits;
+ sid->keaType = ss->sec.keaType;
+ sid->keaKeyBits = ss->sec.keaKeyBits;
+ sid->lastAccessTime = sid->creationTime = ssl_Time();
+ sid->expirationTime = sid->creationTime + ssl3_sid_timeout;
+ sid->localCert = CERT_DupCertificate(ss->sec.localCert);
+
+ ssl_GetSpecReadLock(ss); /*************************************/
+
+ /* Copy the master secret (wrapped or unwrapped) into the sid */
+ if (ss->ssl3.crSpec->msItem.len && ss->ssl3.crSpec->msItem.data) {
+ sid->u.ssl3.keys.wrapped_master_secret_len =
+ ss->ssl3.crSpec->msItem.len;
+ memcpy(sid->u.ssl3.keys.wrapped_master_secret,
+ ss->ssl3.crSpec->msItem.data, ss->ssl3.crSpec->msItem.len);
+ sid->u.ssl3.masterValid = PR_TRUE;
+ sid->u.ssl3.keys.msIsWrapped = PR_FALSE;
+ rv = SECSuccess;
+ } else {
+ rv = ssl3_CacheWrappedMasterSecret(ss, ss->sec.ci.sid,
+ ss->ssl3.crSpec,
+ effectiveExchKeyType);
+ sid->u.ssl3.keys.msIsWrapped = PR_TRUE;
+ }
+ ssl_ReleaseSpecReadLock(ss); /*************************************/
+
+ /* If the wrap failed, we don't cache the sid.
+ * The connection continues normally however.
+ */
+ ss->ssl3.hs.cacheSID = rv == SECSuccess;
+ }
+
+ if (ss->ssl3.hs.authCertificatePending) {
+ if (ss->ssl3.hs.restartTarget) {
+ PR_NOT_REACHED("ssl3_HandleFinished: unexpected restartTarget");
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ ss->ssl3.hs.restartTarget = ssl3_FinishHandshake;
+ return SECWouldBlock;
+ }
+
+ rv = ssl3_FinishHandshake(ss);
+ return rv;
+}
+
+SECStatus
+ssl3_FinishHandshake(sslSocket * ss)
+{
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->ssl3.hs.restartTarget == NULL );
+
+ /* The first handshake is now completed. */
+ ss->handshake = NULL;
+ ss->firstHsDone = PR_TRUE;
+
+ if (ss->ssl3.hs.cacheSID) {
+ (*ss->sec.cache)(ss->sec.ci.sid);
+ ss->ssl3.hs.cacheSID = PR_FALSE;
+ }
+
+ ss->ssl3.hs.ws = idle_handshake;
+
+ /* Do the handshake callback for sslv3 here, if we cannot false start. */
+ if (ss->handshakeCallback != NULL && !ssl3_CanFalseStart(ss)) {
+ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
+ }
+
+ return SECSuccess;
+}
+
+/* Called from ssl3_HandleHandshake() when it has gathered a complete ssl3
+ * hanshake message.
+ * Caller must hold Handshake and RecvBuf locks.
+ */
+SECStatus
+ssl3_HandleHandshakeMessage(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ SECStatus rv = SECSuccess;
+ SSL3HandshakeType type = ss->ssl3.hs.msg_type;
+ SSL3Hashes hashes; /* computed hashes are put here. */
+ PRUint8 hdr[4];
+ PRUint8 dtlsData[8];
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ /*
+ * We have to compute the hashes before we update them with the
+ * current message.
+ */
+ ssl_GetSpecReadLock(ss); /************************************/
+ if((type == finished) || (type == certificate_verify)) {
+ SSL3Sender sender = (SSL3Sender)0;
+ ssl3CipherSpec *rSpec = ss->ssl3.prSpec;
+
+ if (type == finished) {
+ sender = ss->sec.isServer ? sender_client : sender_server;
+ rSpec = ss->ssl3.crSpec;
+ }
+ rv = ssl3_ComputeHandshakeHashes(ss, rSpec, &hashes, sender);
+ }
+ ssl_ReleaseSpecReadLock(ss); /************************************/
+ if (rv != SECSuccess) {
+ return rv; /* error code was set by ssl3_ComputeHandshakeHashes*/
+ }
+ SSL_TRC(30,("%d: SSL3[%d]: handle handshake message: %s", SSL_GETPID(),
+ ss->fd, ssl3_DecodeHandshakeType(ss->ssl3.hs.msg_type)));
+
+ hdr[0] = (PRUint8)ss->ssl3.hs.msg_type;
+ hdr[1] = (PRUint8)(length >> 16);
+ hdr[2] = (PRUint8)(length >> 8);
+ hdr[3] = (PRUint8)(length );
+
+ /* Start new handshake hashes when we start a new handshake */
+ if (ss->ssl3.hs.msg_type == client_hello) {
+ rv = ssl3_RestartHandshakeHashes(ss);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+ /* We should not include hello_request and hello_verify_request messages
+ * in the handshake hashes */
+ if ((ss->ssl3.hs.msg_type != hello_request) &&
+ (ss->ssl3.hs.msg_type != hello_verify_request)) {
+ rv = ssl3_UpdateHandshakeHashes(ss, (unsigned char*) hdr, 4);
+ if (rv != SECSuccess) return rv; /* err code already set. */
+
+ /* Extra data to simulate a complete DTLS handshake fragment */
+ if (IS_DTLS(ss)) {
+ /* Sequence number */
+ dtlsData[0] = MSB(ss->ssl3.hs.recvMessageSeq);
+ dtlsData[1] = LSB(ss->ssl3.hs.recvMessageSeq);
+
+ /* Fragment offset */
+ dtlsData[2] = 0;
+ dtlsData[3] = 0;
+ dtlsData[4] = 0;
+
+ /* Fragment length */
+ dtlsData[5] = (PRUint8)(length >> 16);
+ dtlsData[6] = (PRUint8)(length >> 8);
+ dtlsData[7] = (PRUint8)(length );
+
+ rv = ssl3_UpdateHandshakeHashes(ss, (unsigned char*) dtlsData,
+ sizeof(dtlsData));
+ if (rv != SECSuccess) return rv; /* err code already set. */
+ }
+
+ /* The message body */
+ rv = ssl3_UpdateHandshakeHashes(ss, b, length);
+ if (rv != SECSuccess) return rv; /* err code already set. */
+ }
+
+ PORT_SetError(0); /* each message starts with no error. */
+
+ if (ss->ssl3.hs.ws == wait_certificate_status &&
+ ss->ssl3.hs.msg_type != certificate_status) {
+ /* If we negotiated the certificate_status extension then we deferred
+ * certificate validation until we get the CertificateStatus messsage.
+ * But the CertificateStatus message is optional. If the server did
+ * not send it then we need to validate the certificate now. If the
+ * server does send the CertificateStatus message then we will
+ * authenticate the certificate in ssl3_HandleCertificateStatus.
+ */
+ rv = ssl3_AuthCertificate(ss); /* sets ss->ssl3.hs.ws */
+ PORT_Assert(rv != SECWouldBlock);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ }
+
+ switch (ss->ssl3.hs.msg_type) {
+ case hello_request:
+ if (length != 0) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HELLO_REQUEST);
+ return SECFailure;
+ }
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST);
+ return SECFailure;
+ }
+ rv = ssl3_HandleHelloRequest(ss);
+ break;
+ case client_hello:
+ if (!ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO);
+ return SECFailure;
+ }
+ rv = ssl3_HandleClientHello(ss, b, length);
+ break;
+ case server_hello:
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO);
+ return SECFailure;
+ }
+ rv = ssl3_HandleServerHello(ss, b, length);
+ break;
+ case hello_verify_request:
+ if (!IS_DTLS(ss) || ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST);
+ return SECFailure;
+ }
+ rv = dtls_HandleHelloVerifyRequest(ss, b, length);
+ break;
+ case certificate:
+ rv = ssl3_HandleCertificate(ss, b, length);
+ break;
+ case certificate_status:
+ rv = ssl3_HandleCertificateStatus(ss, b, length);
+ break;
+ case server_key_exchange:
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH);
+ return SECFailure;
+ }
+ rv = ssl3_HandleServerKeyExchange(ss, b, length);
+ break;
+ case certificate_request:
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST);
+ return SECFailure;
+ }
+ rv = ssl3_HandleCertificateRequest(ss, b, length);
+ break;
+ case server_hello_done:
+ if (length != 0) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_HELLO_DONE);
+ return SECFailure;
+ }
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_HELLO_DONE);
+ return SECFailure;
+ }
+ rv = ssl3_HandleServerHelloDone(ss);
+ break;
+ case certificate_verify:
+ if (!ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY);
+ return SECFailure;
+ }
+ rv = ssl3_HandleCertificateVerify(ss, b, length, &hashes);
+ break;
+ case client_key_exchange:
+ if (!ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH);
+ return SECFailure;
+ }
+ rv = ssl3_HandleClientKeyExchange(ss, b, length);
+ break;
+ case new_session_ticket:
+ if (ss->sec.isServer) {
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET);
+ return SECFailure;
+ }
+ rv = ssl3_HandleNewSessionTicket(ss, b, length);
+ break;
+ case finished:
+ rv = ssl3_HandleFinished(ss, b, length, &hashes);
+ break;
+ default:
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNKNOWN_HANDSHAKE);
+ rv = SECFailure;
+ }
+
+ if (IS_DTLS(ss) && (rv != SECFailure)) {
+ /* Increment the expected sequence number */
+ ss->ssl3.hs.recvMessageSeq++;
+ }
+
+ return rv;
+}
+
+/* Called only from ssl3_HandleRecord, for each (deciphered) ssl3 record.
+ * origBuf is the decrypted ssl record content.
+ * Caller must hold the handshake and RecvBuf locks.
+ */
+static SECStatus
+ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
+{
+ /*
+ * There may be a partial handshake message already in the handshake
+ * state. The incoming buffer may contain another portion, or a
+ * complete message or several messages followed by another portion.
+ *
+ * Each message is made contiguous before being passed to the actual
+ * message parser.
+ */
+ sslBuffer *buf = &ss->ssl3.hs.msgState; /* do not lose the original buffer pointer */
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (buf->buf == NULL) {
+ *buf = *origBuf;
+ }
+ while (buf->len > 0) {
+ if (ss->ssl3.hs.header_bytes < 4) {
+ PRUint8 t;
+ t = *(buf->buf++);
+ buf->len--;
+ if (ss->ssl3.hs.header_bytes++ == 0)
+ ss->ssl3.hs.msg_type = (SSL3HandshakeType)t;
+ else
+ ss->ssl3.hs.msg_len = (ss->ssl3.hs.msg_len << 8) + t;
+ if (ss->ssl3.hs.header_bytes < 4)
+ continue;
+
+#define MAX_HANDSHAKE_MSG_LEN 0x1ffff /* 128k - 1 */
+ if (ss->ssl3.hs.msg_len > MAX_HANDSHAKE_MSG_LEN) {
+ (void)ssl3_DecodeError(ss);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+#undef MAX_HANDSHAKE_MSG_LEN
+
+ /* If msg_len is zero, be sure we fall through,
+ ** even if buf->len is zero.
+ */
+ if (ss->ssl3.hs.msg_len > 0)
+ continue;
+ }
+
+ /*
+ * Header has been gathered and there is at least one byte of new
+ * data available for this message. If it can be done right out
+ * of the original buffer, then use it from there.
+ */
+ if (ss->ssl3.hs.msg_body.len == 0 && buf->len >= ss->ssl3.hs.msg_len) {
+ /* handle it from input buffer */
+ rv = ssl3_HandleHandshakeMessage(ss, buf->buf, ss->ssl3.hs.msg_len);
+ if (rv == SECFailure) {
+ /* This test wants to fall through on either
+ * SECSuccess or SECWouldBlock.
+ * ssl3_HandleHandshakeMessage MUST set the error code.
+ */
+ return rv;
+ }
+ buf->buf += ss->ssl3.hs.msg_len;
+ buf->len -= ss->ssl3.hs.msg_len;
+ ss->ssl3.hs.msg_len = 0;
+ ss->ssl3.hs.header_bytes = 0;
+ if (rv != SECSuccess) { /* return if SECWouldBlock. */
+ return rv;
+ }
+ } else {
+ /* must be copied to msg_body and dealt with from there */
+ unsigned int bytes;
+
+ PORT_Assert(ss->ssl3.hs.msg_body.len < ss->ssl3.hs.msg_len);
+ bytes = PR_MIN(buf->len, ss->ssl3.hs.msg_len - ss->ssl3.hs.msg_body.len);
+
+ /* Grow the buffer if needed */
+ rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, ss->ssl3.hs.msg_len);
+ if (rv != SECSuccess) {
+ /* sslBuffer_Grow has set a memory error code. */
+ return SECFailure;
+ }
+
+ PORT_Memcpy(ss->ssl3.hs.msg_body.buf + ss->ssl3.hs.msg_body.len,
+ buf->buf, bytes);
+ ss->ssl3.hs.msg_body.len += bytes;
+ buf->buf += bytes;
+ buf->len -= bytes;
+
+ PORT_Assert(ss->ssl3.hs.msg_body.len <= ss->ssl3.hs.msg_len);
+
+ /* if we have a whole message, do it */
+ if (ss->ssl3.hs.msg_body.len == ss->ssl3.hs.msg_len) {
+ rv = ssl3_HandleHandshakeMessage(
+ ss, ss->ssl3.hs.msg_body.buf, ss->ssl3.hs.msg_len);
+ if (rv == SECFailure) {
+ /* This test wants to fall through on either
+ * SECSuccess or SECWouldBlock.
+ * ssl3_HandleHandshakeMessage MUST set error code.
+ */
+ return rv;
+ }
+ ss->ssl3.hs.msg_body.len = 0;
+ ss->ssl3.hs.msg_len = 0;
+ ss->ssl3.hs.header_bytes = 0;
+ if (rv != SECSuccess) { /* return if SECWouldBlock. */
+ return rv;
+ }
+ } else {
+ PORT_Assert(buf->len == 0);
+ break;
+ }
+ }
+ } /* end loop */
+
+ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */
+ buf->buf = NULL; /* not a leak. */
+ return SECSuccess;
+}
+
+/* These macros return the given value with the MSB copied to all the other
+ * bits. They use the fact that arithmetic shift shifts-in the sign bit.
+ * However, this is not ensured by the C standard so you may need to replace
+ * them with something else for odd compilers. */
+#define DUPLICATE_MSB_TO_ALL(x) ( (unsigned)( (int)(x) >> (sizeof(int)*8-1) ) )
+#define DUPLICATE_MSB_TO_ALL_8(x) ((unsigned char)(DUPLICATE_MSB_TO_ALL(x)))
+
+/* SECStatusToMask returns, in constant time, a mask value of all ones if
+ * rv == SECSuccess. Otherwise it returns zero. */
+static unsigned int
+SECStatusToMask(SECStatus rv)
+{
+ unsigned int good;
+ /* rv ^ SECSuccess is zero iff rv == SECSuccess. Subtracting one results
+ * in the MSB being set to one iff it was zero before. */
+ good = rv ^ SECSuccess;
+ good--;
+ return DUPLICATE_MSB_TO_ALL(good);
+}
+
+/* ssl_ConstantTimeGE returns 0xff if a>=b and 0x00 otherwise. */
+static unsigned char
+ssl_ConstantTimeGE(unsigned int a, unsigned int b)
+{
+ a -= b;
+ return DUPLICATE_MSB_TO_ALL(~a);
+}
+
+/* ssl_ConstantTimeEQ8 returns 0xff if a==b and 0x00 otherwise. */
+static unsigned char
+ssl_ConstantTimeEQ8(unsigned char a, unsigned char b)
+{
+ unsigned int c = a ^ b;
+ c--;
+ return DUPLICATE_MSB_TO_ALL_8(c);
+}
+
+static SECStatus
+ssl_RemoveSSLv3CBCPadding(sslBuffer *plaintext,
+ unsigned int blockSize,
+ unsigned int macSize)
+{
+ unsigned int paddingLength, good, t;
+ const unsigned int overhead = 1 /* padding length byte */ + macSize;
+
+ /* These lengths are all public so we can test them in non-constant
+ * time. */
+ if (overhead > plaintext->len) {
+ return SECFailure;
+ }
+
+ paddingLength = plaintext->buf[plaintext->len-1];
+ /* SSLv3 padding bytes are random and cannot be checked. */
+ t = plaintext->len;
+ t -= paddingLength+overhead;
+ /* If len >= paddingLength+overhead then the MSB of t is zero. */
+ good = DUPLICATE_MSB_TO_ALL(~t);
+ /* SSLv3 requires that the padding is minimal. */
+ t = blockSize - (paddingLength+1);
+ good &= DUPLICATE_MSB_TO_ALL(~t);
+ plaintext->len -= good & (paddingLength+1);
+ return (good & SECSuccess) | (~good & SECFailure);
+}
+
+static SECStatus
+ssl_RemoveTLSCBCPadding(sslBuffer *plaintext, unsigned int macSize)
+{
+ unsigned int paddingLength, good, t, toCheck, i;
+ const unsigned int overhead = 1 /* padding length byte */ + macSize;
+
+ /* These lengths are all public so we can test them in non-constant
+ * time. */
+ if (overhead > plaintext->len) {
+ return SECFailure;
+ }
+
+ paddingLength = plaintext->buf[plaintext->len-1];
+ t = plaintext->len;
+ t -= paddingLength+overhead;
+ /* If len >= paddingLength+overhead then the MSB of t is zero. */
+ good = DUPLICATE_MSB_TO_ALL(~t);
+
+ /* The padding consists of a length byte at the end of the record and then
+ * that many bytes of padding, all with the same value as the length byte.
+ * Thus, with the length byte included, there are paddingLength+1 bytes of
+ * padding.
+ *
+ * We can't check just |paddingLength+1| bytes because that leaks
+ * decrypted information. Therefore we always have to check the maximum
+ * amount of padding possible. (Again, the length of the record is
+ * public information so we can use it.) */
+ toCheck = 255; /* maximum amount of padding. */
+ if (toCheck > plaintext->len-1) {
+ toCheck = plaintext->len-1;
+ }
+
+ for (i = 0; i < toCheck; i++) {
+ unsigned int t = paddingLength - i;
+ /* If i <= paddingLength then the MSB of t is zero and mask is
+ * 0xff. Otherwise, mask is 0. */
+ unsigned char mask = DUPLICATE_MSB_TO_ALL(~t);
+ unsigned char b = plaintext->buf[plaintext->len-1-i];
+ /* The final |paddingLength+1| bytes should all have the value
+ * |paddingLength|. Therefore the XOR should be zero. */
+ good &= ~(mask&(paddingLength ^ b));
+ }
+
+ /* If any of the final |paddingLength+1| bytes had the wrong value,
+ * one or more of the lower eight bits of |good| will be cleared. We
+ * AND the bottom 8 bits together and duplicate the result to all the
+ * bits. */
+ good &= good >> 4;
+ good &= good >> 2;
+ good &= good >> 1;
+ good <<= sizeof(good)*8-1;
+ good = DUPLICATE_MSB_TO_ALL(good);
+
+ plaintext->len -= good & (paddingLength+1);
+ return (good & SECSuccess) | (~good & SECFailure);
+}
+
+/* On entry:
+ * originalLength >= macSize
+ * macSize <= MAX_MAC_LENGTH
+ * plaintext->len >= macSize
+ */
+static void
+ssl_CBCExtractMAC(sslBuffer *plaintext,
+ unsigned int originalLength,
+ SSL3Opaque* out,
+ unsigned int macSize)
+{
+ unsigned char rotatedMac[MAX_MAC_LENGTH];
+ /* macEnd is the index of |plaintext->buf| just after the end of the
+ * MAC. */
+ unsigned macEnd = plaintext->len;
+ unsigned macStart = macEnd - macSize;
+ /* scanStart contains the number of bytes that we can ignore because
+ * the MAC's position can only vary by 255 bytes. */
+ unsigned scanStart = 0;
+ unsigned i, j, divSpoiler;
+ unsigned char rotateOffset;
+
+ if (originalLength > macSize + 255 + 1)
+ scanStart = originalLength - (macSize + 255 + 1);
+
+ /* divSpoiler contains a multiple of macSize that is used to cause the
+ * modulo operation to be constant time. Without this, the time varies
+ * based on the amount of padding when running on Intel chips at least.
+ *
+ * The aim of right-shifting macSize is so that the compiler doesn't
+ * figure out that it can remove divSpoiler as that would require it
+ * to prove that macSize is always even, which I hope is beyond it. */
+ divSpoiler = macSize >> 1;
+ divSpoiler <<= (sizeof(divSpoiler)-1)*8;
+ rotateOffset = (divSpoiler + macStart - scanStart) % macSize;
+
+ memset(rotatedMac, 0, macSize);
+ for (i = scanStart; i < originalLength;) {
+ for (j = 0; j < macSize && i < originalLength; i++, j++) {
+ unsigned char macStarted = ssl_ConstantTimeGE(i, macStart);
+ unsigned char macEnded = ssl_ConstantTimeGE(i, macEnd);
+ unsigned char b = 0;
+ b = plaintext->buf[i];
+ rotatedMac[j] |= b & macStarted & ~macEnded;
+ }
+ }
+
+ /* Now rotate the MAC. If we knew that the MAC fit into a CPU cache line
+ * we could line-align |rotatedMac| and rotate in place. */
+ memset(out, 0, macSize);
+ for (i = 0; i < macSize; i++) {
+ unsigned char offset =
+ (divSpoiler + macSize - rotateOffset + i) % macSize;
+ for (j = 0; j < macSize; j++) {
+ out[j] |= rotatedMac[i] & ssl_ConstantTimeEQ8(j, offset);
+ }
+ }
+}
+
+/* if cText is non-null, then decipher, check MAC, and decompress the
+ * SSL record from cText->buf (typically gs->inbuf)
+ * into databuf (typically gs->buf), and any previous contents of databuf
+ * is lost. Then handle databuf according to its SSL record type,
+ * unless it's an application record.
+ *
+ * If cText is NULL, then the ciphertext has previously been deciphered and
+ * checked, and is already sitting in databuf. It is processed as an SSL
+ * Handshake message.
+ *
+ * DOES NOT process the decrypted/decompressed application data.
+ * On return, databuf contains the decrypted/decompressed record.
+ *
+ * Called from ssl3_GatherCompleteHandshake
+ * ssl3_RestartHandshakeAfterCertReq
+ *
+ * Caller must hold the RecvBufLock.
+ *
+ * This function aquires and releases the SSL3Handshake Lock, holding the
+ * lock around any calls to functions that handle records other than
+ * Application Data records.
+ */
+SECStatus
+ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cText, sslBuffer *databuf)
+{
+ const ssl3BulkCipherDef *cipher_def;
+ ssl3CipherSpec * crSpec;
+ SECStatus rv;
+ unsigned int hashBytes = MAX_MAC_LENGTH + 1;
+ PRBool isTLS;
+ SSL3ContentType rType;
+ SSL3Opaque hash[MAX_MAC_LENGTH];
+ SSL3Opaque givenHashBuf[MAX_MAC_LENGTH];
+ SSL3Opaque *givenHash;
+ sslBuffer *plaintext;
+ sslBuffer temp_buf;
+ PRUint64 dtls_seq_num;
+ unsigned int ivLen = 0;
+ unsigned int originalLen = 0;
+ unsigned int good;
+ unsigned int minLength;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ if (!ss->ssl3.initialized) {
+ ssl_GetSSL3HandshakeLock(ss);
+ rv = ssl3_InitState(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ if (rv != SECSuccess) {
+ return rv; /* ssl3_InitState has set the error code. */
+ }
+ }
+
+ /* check for Token Presence */
+ if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
+ PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+ return SECFailure;
+ }
+
+ /* cText is NULL when we're called from ssl3_RestartHandshakeAfterXXX().
+ * This implies that databuf holds a previously deciphered SSL Handshake
+ * message.
+ */
+ if (cText == NULL) {
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, resuming handshake",
+ SSL_GETPID(), ss->fd));
+ rType = content_handshake;
+ goto process_it;
+ }
+
+ ssl_GetSpecReadLock(ss); /******************************************/
+
+ crSpec = ss->ssl3.crSpec;
+ cipher_def = crSpec->cipher_def;
+
+ /*
+ * DTLS relevance checks:
+ * Note that this code currently ignores all out-of-epoch packets,
+ * which means we lose some in the case of rehandshake +
+ * loss/reordering. Since DTLS is explicitly unreliable, this
+ * seems like a good tradeoff for implementation effort and is
+ * consistent with the guidance of RFC 6347 Sections 4.1 and 4.2.4.1
+ */
+ if (IS_DTLS(ss)) {
+ DTLSEpoch epoch = (cText->seq_num.high >> 16) & 0xffff;
+
+ if (crSpec->epoch != epoch) {
+ ssl_ReleaseSpecReadLock(ss);
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, received packet "
+ "from irrelevant epoch %d", SSL_GETPID(), ss->fd, epoch));
+ /* Silently drop the packet */
+ databuf->len = 0; /* Needed to ensure data not left around */
+ return SECSuccess;
+ }
+
+ dtls_seq_num = (((PRUint64)(cText->seq_num.high & 0xffff)) << 32) |
+ ((PRUint64)cText->seq_num.low);
+
+ if (dtls_RecordGetRecvd(&crSpec->recvdRecords, dtls_seq_num) != 0) {
+ ssl_ReleaseSpecReadLock(ss);
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, rejecting "
+ "potentially replayed packet", SSL_GETPID(), ss->fd));
+ /* Silently drop the packet */
+ databuf->len = 0; /* Needed to ensure data not left around */
+ return SECSuccess;
+ }
+ }
+
+ good = ~0U;
+ minLength = crSpec->mac_size;
+ if (cipher_def->type == type_block) {
+ /* CBC records have a padding length byte at the end. */
+ minLength++;
+ if (crSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* With >= TLS 1.1, CBC records have an explicit IV. */
+ minLength += cipher_def->iv_size;
+ }
+ } else if (cipher_def->type == type_aead) {
+ minLength = cipher_def->explicit_nonce_size + cipher_def->tag_size;
+ }
+
+ /* We can perform this test in variable time because the record's total
+ * length and the ciphersuite are both public knowledge. */
+ if (cText->buf->len < minLength) {
+ goto decrypt_loser;
+ }
+
+ if (cipher_def->type == type_block &&
+ crSpec->version >= SSL_LIBRARY_VERSION_TLS_1_1) {
+ /* Consume the per-record explicit IV. RFC 4346 Section 6.2.3.2 states
+ * "The receiver decrypts the entire GenericBlockCipher structure and
+ * then discards the first cipher block corresponding to the IV
+ * component." Instead, we decrypt the first cipher block and then
+ * discard it before decrypting the rest.
+ */
+ SSL3Opaque iv[MAX_IV_LENGTH];
+ int decoded;
+
+ ivLen = cipher_def->iv_size;
+ if (ivLen < 8 || ivLen > sizeof(iv)) {
+ ssl_ReleaseSpecReadLock(ss);
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ PRINT_BUF(80, (ss, "IV (ciphertext):", cText->buf->buf, ivLen));
+
+ /* The decryption result is garbage, but since we just throw away
+ * the block it doesn't matter. The decryption of the next block
+ * depends only on the ciphertext of the IV block.
+ */
+ rv = crSpec->decode(crSpec->decodeContext, iv, &decoded,
+ sizeof(iv), cText->buf->buf, ivLen);
+
+ good &= SECStatusToMask(rv);
+ }
+
+ /* If we will be decompressing the buffer we need to decrypt somewhere
+ * other than into databuf */
+ if (crSpec->decompressor) {
+ temp_buf.buf = NULL;
+ temp_buf.space = 0;
+ plaintext = &temp_buf;
+ } else {
+ plaintext = databuf;
+ }
+
+ plaintext->len = 0; /* filled in by decode call below. */
+ if (plaintext->space < MAX_FRAGMENT_LENGTH) {
+ rv = sslBuffer_Grow(plaintext, MAX_FRAGMENT_LENGTH + 2048);
+ if (rv != SECSuccess) {
+ ssl_ReleaseSpecReadLock(ss);
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, tried to get %d bytes",
+ SSL_GETPID(), ss->fd, MAX_FRAGMENT_LENGTH + 2048));
+ /* sslBuffer_Grow has set a memory error code. */
+ /* Perhaps we should send an alert. (but we have no memory!) */
+ return SECFailure;
+ }
+ }
+
+ PRINT_BUF(80, (ss, "ciphertext:", cText->buf->buf + ivLen,
+ cText->buf->len - ivLen));
+
+ isTLS = (PRBool)(crSpec->version > SSL_LIBRARY_VERSION_3_0);
+
+ if (isTLS && cText->buf->len - ivLen > (MAX_FRAGMENT_LENGTH + 2048)) {
+ ssl_ReleaseSpecReadLock(ss);
+ SSL3_SendAlert(ss, alert_fatal, record_overflow);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+
+ rType = cText->type;
+ if (cipher_def->type == type_aead) {
+ rv = crSpec->aead(
+ ss->sec.isServer ? &crSpec->client : &crSpec->server,
+ PR_TRUE, /* do decrypt */
+ plaintext->buf, /* out */
+ (int*) &plaintext->len, /* outlen */
+ plaintext->space, /* maxout */
+ cText->buf->buf, /* in */
+ cText->buf->len, /* inlen */
+ rType, /* record type */
+ cText->version,
+ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num);
+ if (rv != SECSuccess) {
+ good = 0;
+ }
+ } else {
+ if (cipher_def->type == type_block &&
+ ((cText->buf->len - ivLen) % cipher_def->block_size) != 0) {
+ goto decrypt_loser;
+ }
+
+ /* decrypt from cText buf to plaintext. */
+ rv = crSpec->decode(
+ crSpec->decodeContext, plaintext->buf, (int *)&plaintext->len,
+ plaintext->space, cText->buf->buf + ivLen, cText->buf->len - ivLen);
+ if (rv != SECSuccess) {
+ goto decrypt_loser;
+ }
+
+ PRINT_BUF(80, (ss, "cleartext:", plaintext->buf, plaintext->len));
+
+ originalLen = plaintext->len;
+
+ /* If it's a block cipher, check and strip the padding. */
+ if (cipher_def->type == type_block) {
+ const unsigned int blockSize = cipher_def->block_size;
+ const unsigned int macSize = crSpec->mac_size;
+
+ if (crSpec->version <= SSL_LIBRARY_VERSION_3_0) {
+ good &= SECStatusToMask(ssl_RemoveSSLv3CBCPadding(
+ plaintext, blockSize, macSize));
+ } else {
+ good &= SECStatusToMask(ssl_RemoveTLSCBCPadding(
+ plaintext, macSize));
+ }
+ }
+
+ /* compute the MAC */
+ if (cipher_def->type == type_block) {
+ rv = ssl3_ComputeRecordMACConstantTime(
+ crSpec, (PRBool)(!ss->sec.isServer),
+ IS_DTLS(ss), rType, cText->version,
+ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
+ plaintext->buf, plaintext->len, originalLen,
+ hash, &hashBytes);
+
+ ssl_CBCExtractMAC(plaintext, originalLen, givenHashBuf,
+ crSpec->mac_size);
+ givenHash = givenHashBuf;
+
+ /* plaintext->len will always have enough space to remove the MAC
+ * because in ssl_Remove{SSLv3|TLS}CBCPadding we only adjust
+ * plaintext->len if the result has enough space for the MAC and we
+ * tested the unadjusted size against minLength, above. */
+ plaintext->len -= crSpec->mac_size;
+ } else {
+ /* This is safe because we checked the minLength above. */
+ plaintext->len -= crSpec->mac_size;
+
+ rv = ssl3_ComputeRecordMAC(
+ crSpec, (PRBool)(!ss->sec.isServer),
+ IS_DTLS(ss), rType, cText->version,
+ IS_DTLS(ss) ? cText->seq_num : crSpec->read_seq_num,
+ plaintext->buf, plaintext->len,
+ hash, &hashBytes);
+
+ /* We can read the MAC directly from the record because its location
+ * is public when a stream cipher is used. */
+ givenHash = plaintext->buf + plaintext->len;
+ }
+
+ good &= SECStatusToMask(rv);
+
+ if (hashBytes != (unsigned)crSpec->mac_size ||
+ NSS_SecureMemcmp(givenHash, hash, crSpec->mac_size) != 0) {
+ /* We're allowed to leak whether or not the MAC check was correct */
+ good = 0;
+ }
+ }
+
+ if (good == 0) {
+decrypt_loser:
+ /* must not hold spec lock when calling SSL3_SendAlert. */
+ ssl_ReleaseSpecReadLock(ss);
+
+ SSL_DBG(("%d: SSL3[%d]: decryption failed", SSL_GETPID(), ss->fd));
+
+ if (!IS_DTLS(ss)) {
+ SSL3_SendAlert(ss, alert_fatal, bad_record_mac);
+ /* always log mac error, in case attacker can read server logs. */
+ PORT_SetError(SSL_ERROR_BAD_MAC_READ);
+ return SECFailure;
+ } else {
+ /* Silently drop the packet */
+ databuf->len = 0; /* Needed to ensure data not left around */
+ return SECSuccess;
+ }
+ }
+
+ if (!IS_DTLS(ss)) {
+ ssl3_BumpSequenceNumber(&crSpec->read_seq_num);
+ } else {
+ dtls_RecordSetRecvd(&crSpec->recvdRecords, dtls_seq_num);
+ }
+
+ ssl_ReleaseSpecReadLock(ss); /*****************************************/
+
+ /*
+ * The decrypted data is now in plaintext.
+ */
+
+ /* possibly decompress the record. If we aren't using compression then
+ * plaintext == databuf and so the uncompressed data is already in
+ * databuf. */
+ if (crSpec->decompressor) {
+ if (databuf->space < plaintext->len + SSL3_COMPRESSION_MAX_EXPANSION) {
+ rv = sslBuffer_Grow(
+ databuf, plaintext->len + SSL3_COMPRESSION_MAX_EXPANSION);
+ if (rv != SECSuccess) {
+ SSL_DBG(("%d: SSL3[%d]: HandleRecord, tried to get %d bytes",
+ SSL_GETPID(), ss->fd,
+ plaintext->len + SSL3_COMPRESSION_MAX_EXPANSION));
+ /* sslBuffer_Grow has set a memory error code. */
+ /* Perhaps we should send an alert. (but we have no memory!) */
+ PORT_Free(plaintext->buf);
+ return SECFailure;
+ }
+ }
+
+ rv = crSpec->decompressor(crSpec->decompressContext,
+ databuf->buf,
+ (int*) &databuf->len,
+ databuf->space,
+ plaintext->buf,
+ plaintext->len);
+
+ if (rv != SECSuccess) {
+ int err = ssl_MapLowLevelError(SSL_ERROR_DECOMPRESSION_FAILURE);
+ SSL3_SendAlert(ss, alert_fatal,
+ isTLS ? decompression_failure : bad_record_mac);
+
+ /* There appears to be a bug with (at least) Apache + OpenSSL where
+ * resumed SSLv3 connections don't actually use compression. See
+ * comments 93-95 of
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=275744
+ *
+ * So, if we get a decompression error, and the record appears to
+ * be already uncompressed, then we return a more specific error
+ * code to hopefully save somebody some debugging time in the
+ * future.
+ */
+ if (plaintext->len >= 4) {
+ unsigned int len = ((unsigned int) plaintext->buf[1] << 16) |
+ ((unsigned int) plaintext->buf[2] << 8) |
+ (unsigned int) plaintext->buf[3];
+ if (len == plaintext->len - 4) {
+ /* This appears to be uncompressed already */
+ err = SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD;
+ }
+ }
+
+ PORT_Free(plaintext->buf);
+ PORT_SetError(err);
+ return SECFailure;
+ }
+
+ PORT_Free(plaintext->buf);
+ }
+
+ /*
+ ** Having completed the decompression, check the length again.
+ */
+ if (isTLS && databuf->len > (MAX_FRAGMENT_LENGTH + 1024)) {
+ SSL3_SendAlert(ss, alert_fatal, record_overflow);
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+
+ /* Application data records are processed by the caller of this
+ ** function, not by this function.
+ */
+ if (rType == content_application_data) {
+ if (ss->firstHsDone)
+ return SECSuccess;
+ (void)SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ PORT_SetError(SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA);
+ return SECFailure;
+ }
+
+ /* It's a record that must be handled by ssl itself, not the application.
+ */
+process_it:
+ /* XXX Get the xmit lock here. Odds are very high that we'll be xmiting
+ * data ang getting the xmit lock here prevents deadlocks.
+ */
+ ssl_GetSSL3HandshakeLock(ss);
+
+ /* All the functions called in this switch MUST set error code if
+ ** they return SECFailure or SECWouldBlock.
+ */
+ switch (rType) {
+ case content_change_cipher_spec:
+ rv = ssl3_HandleChangeCipherSpecs(ss, databuf);
+ break;
+ case content_alert:
+ rv = ssl3_HandleAlert(ss, databuf);
+ break;
+ case content_handshake:
+ if (!IS_DTLS(ss)) {
+ rv = ssl3_HandleHandshake(ss, databuf);
+ } else {
+ rv = dtls_HandleHandshake(ss, databuf);
+ }
+ break;
+ /*
+ case content_application_data is handled before this switch
+ */
+ default:
+ SSL_DBG(("%d: SSL3[%d]: bogus content type=%d",
+ SSL_GETPID(), ss->fd, cText->type));
+ /* XXX Send an alert ??? */
+ PORT_SetError(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE);
+ rv = SECFailure;
+ break;
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return rv;
+
+}
+
+/*
+ * Initialization functions
+ */
+
+/* Called from ssl3_InitState, immediately below. */
+/* Caller must hold the SpecWriteLock. */
+static void
+ssl3_InitCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
+{
+ spec->cipher_def = &bulk_cipher_defs[cipher_null];
+ PORT_Assert(spec->cipher_def->cipher == cipher_null);
+ spec->mac_def = &mac_defs[mac_null];
+ PORT_Assert(spec->mac_def->mac == mac_null);
+ spec->encode = Null_Cipher;
+ spec->decode = Null_Cipher;
+ spec->destroy = NULL;
+ spec->compressor = NULL;
+ spec->decompressor = NULL;
+ spec->destroyCompressContext = NULL;
+ spec->destroyDecompressContext = NULL;
+ spec->mac_size = 0;
+ spec->master_secret = NULL;
+ spec->bypassCiphers = PR_FALSE;
+
+ spec->msItem.data = NULL;
+ spec->msItem.len = 0;
+
+ spec->client.write_key = NULL;
+ spec->client.write_mac_key = NULL;
+ spec->client.write_mac_context = NULL;
+
+ spec->server.write_key = NULL;
+ spec->server.write_mac_key = NULL;
+ spec->server.write_mac_context = NULL;
+
+ spec->write_seq_num.high = 0;
+ spec->write_seq_num.low = 0;
+
+ spec->read_seq_num.high = 0;
+ spec->read_seq_num.low = 0;
+
+ spec->epoch = 0;
+ dtls_InitRecvdRecords(&spec->recvdRecords);
+
+ spec->version = ss->vrange.max;
+}
+
+/* Called from: ssl3_SendRecord
+** ssl3_StartHandshakeHash() <- ssl2_BeginClientHandshake()
+** ssl3_SendClientHello()
+** ssl3_HandleV2ClientHello()
+** ssl3_HandleRecord()
+**
+** This function should perhaps acquire and release the SpecWriteLock.
+**
+**
+*/
+static SECStatus
+ssl3_InitState(sslSocket *ss)
+{
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->ssl3.initialized)
+ return SECSuccess; /* Function should be idempotent */
+
+ ss->ssl3.policy = SSL_ALLOWED;
+
+ ssl_GetSpecWriteLock(ss);
+ ss->ssl3.crSpec = ss->ssl3.cwSpec = &ss->ssl3.specs[0];
+ ss->ssl3.prSpec = ss->ssl3.pwSpec = &ss->ssl3.specs[1];
+ ss->ssl3.hs.sendingSCSV = PR_FALSE;
+ ssl3_InitCipherSpec(ss, ss->ssl3.crSpec);
+ ssl3_InitCipherSpec(ss, ss->ssl3.prSpec);
+
+ ss->ssl3.hs.ws = (ss->sec.isServer) ? wait_client_hello : wait_server_hello;
+#ifdef NSS_ENABLE_ECC
+ ss->ssl3.hs.negotiatedECCurves = ssl3_GetSupportedECCurveMask(ss);
+#endif
+ ssl_ReleaseSpecWriteLock(ss);
+
+ PORT_Memset(&ss->xtnData, 0, sizeof(TLSExtensionData));
+
+ if (IS_DTLS(ss)) {
+ ss->ssl3.hs.sendMessageSeq = 0;
+ ss->ssl3.hs.recvMessageSeq = 0;
+ ss->ssl3.hs.rtTimeoutMs = INITIAL_DTLS_TIMEOUT_MS;
+ ss->ssl3.hs.rtRetries = 0;
+ ss->ssl3.hs.recvdHighWater = -1;
+ PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
+ dtls_SetMTU(ss, 0); /* Set the MTU to the highest plateau */
+ }
+
+ PORT_Assert(!ss->ssl3.hs.messages.buf && !ss->ssl3.hs.messages.space);
+ ss->ssl3.hs.messages.buf = NULL;
+ ss->ssl3.hs.messages.space = 0;
+
+ ss->ssl3.initialized = PR_TRUE;
+ return SECSuccess;
+}
+
+/* Returns a reference counted object that contains a key pair.
+ * Or NULL on failure. Initial ref count is 1.
+ * Uses the keys in the pair as input.
+ */
+ssl3KeyPair *
+ssl3_NewKeyPair( SECKEYPrivateKey * privKey, SECKEYPublicKey * pubKey)
+{
+ ssl3KeyPair * pair;
+
+ if (!privKey || !pubKey) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return NULL;
+ }
+ pair = PORT_ZNew(ssl3KeyPair);
+ if (!pair)
+ return NULL; /* error code is set. */
+ pair->refCount = 1;
+ pair->privKey = privKey;
+ pair->pubKey = pubKey;
+ return pair; /* success */
+}
+
+ssl3KeyPair *
+ssl3_GetKeyPairRef(ssl3KeyPair * keyPair)
+{
+ PR_ATOMIC_INCREMENT(&keyPair->refCount);
+ return keyPair;
+}
+
+void
+ssl3_FreeKeyPair(ssl3KeyPair * keyPair)
+{
+ PRInt32 newCount = PR_ATOMIC_DECREMENT(&keyPair->refCount);
+ if (!newCount) {
+ if (keyPair->privKey)
+ SECKEY_DestroyPrivateKey(keyPair->privKey);
+ if (keyPair->pubKey)
+ SECKEY_DestroyPublicKey( keyPair->pubKey);
+ PORT_Free(keyPair);
+ }
+}
+
+
+
+/*
+ * Creates the public and private RSA keys for SSL Step down.
+ * Called from SSL_ConfigSecureServer in sslsecur.c
+ */
+SECStatus
+ssl3_CreateRSAStepDownKeys(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+ SECKEYPrivateKey * privKey; /* RSA step down key */
+ SECKEYPublicKey * pubKey; /* RSA step down key */
+
+ if (ss->stepDownKeyPair)
+ ssl3_FreeKeyPair(ss->stepDownKeyPair);
+ ss->stepDownKeyPair = NULL;
+#ifndef HACKED_EXPORT_SERVER
+ /* Sigh, should have a get key strength call for private keys */
+ if (PK11_GetPrivateModulusLen(ss->serverCerts[kt_rsa].SERVERKEY) >
+ EXPORT_RSA_KEY_LENGTH) {
+ /* need to ask for the key size in bits */
+ privKey = SECKEY_CreateRSAPrivateKey(EXPORT_RSA_KEY_LENGTH * BPB,
+ &pubKey, NULL);
+ if (!privKey || !pubKey ||
+ !(ss->stepDownKeyPair = ssl3_NewKeyPair(privKey, pubKey))) {
+ ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
+ rv = SECFailure;
+ }
+ }
+#endif
+ return rv;
+}
+
+
+/* record the export policy for this cipher suite */
+SECStatus
+ssl3_SetPolicy(ssl3CipherSuite which, int policy)
+{
+ ssl3CipherSuiteCfg *suite;
+
+ suite = ssl_LookupCipherSuiteCfg(which, cipherSuites);
+ if (suite == NULL) {
+ return SECFailure; /* err code was set by ssl_LookupCipherSuiteCfg */
+ }
+ suite->policy = policy;
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_GetPolicy(ssl3CipherSuite which, PRInt32 *oPolicy)
+{
+ ssl3CipherSuiteCfg *suite;
+ PRInt32 policy;
+ SECStatus rv;
+
+ suite = ssl_LookupCipherSuiteCfg(which, cipherSuites);
+ if (suite) {
+ policy = suite->policy;
+ rv = SECSuccess;
+ } else {
+ policy = SSL_NOT_ALLOWED;
+ rv = SECFailure; /* err code was set by Lookup. */
+ }
+ *oPolicy = policy;
+ return rv;
+}
+
+/* record the user preference for this suite */
+SECStatus
+ssl3_CipherPrefSetDefault(ssl3CipherSuite which, PRBool enabled)
+{
+ ssl3CipherSuiteCfg *suite;
+
+ suite = ssl_LookupCipherSuiteCfg(which, cipherSuites);
+ if (suite == NULL) {
+ return SECFailure; /* err code was set by ssl_LookupCipherSuiteCfg */
+ }
+ suite->enabled = enabled;
+ return SECSuccess;
+}
+
+/* return the user preference for this suite */
+SECStatus
+ssl3_CipherPrefGetDefault(ssl3CipherSuite which, PRBool *enabled)
+{
+ ssl3CipherSuiteCfg *suite;
+ PRBool pref;
+ SECStatus rv;
+
+ suite = ssl_LookupCipherSuiteCfg(which, cipherSuites);
+ if (suite) {
+ pref = suite->enabled;
+ rv = SECSuccess;
+ } else {
+ pref = SSL_NOT_ALLOWED;
+ rv = SECFailure; /* err code was set by Lookup. */
+ }
+ *enabled = pref;
+ return rv;
+}
+
+SECStatus
+ssl3_CipherPrefSet(sslSocket *ss, ssl3CipherSuite which, PRBool enabled)
+{
+ ssl3CipherSuiteCfg *suite;
+
+ suite = ssl_LookupCipherSuiteCfg(which, ss->cipherSuites);
+ if (suite == NULL) {
+ return SECFailure; /* err code was set by ssl_LookupCipherSuiteCfg */
+ }
+ suite->enabled = enabled;
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_CipherPrefGet(sslSocket *ss, ssl3CipherSuite which, PRBool *enabled)
+{
+ ssl3CipherSuiteCfg *suite;
+ PRBool pref;
+ SECStatus rv;
+
+ suite = ssl_LookupCipherSuiteCfg(which, ss->cipherSuites);
+ if (suite) {
+ pref = suite->enabled;
+ rv = SECSuccess;
+ } else {
+ pref = SSL_NOT_ALLOWED;
+ rv = SECFailure; /* err code was set by Lookup. */
+ }
+ *enabled = pref;
+ return rv;
+}
+
+/* copy global default policy into socket. */
+void
+ssl3_InitSocketPolicy(sslSocket *ss)
+{
+ PORT_Memcpy(ss->cipherSuites, cipherSuites, sizeof cipherSuites);
+}
+
+SECStatus
+ssl3_GetTLSUniqueChannelBinding(sslSocket *ss,
+ unsigned char *out,
+ unsigned int *outLen,
+ unsigned int outLenMax) {
+ PRBool isTLS;
+ int index = 0;
+ unsigned int len;
+ SECStatus rv = SECFailure;
+
+ *outLen = 0;
+
+ ssl_GetSSL3HandshakeLock(ss);
+
+ ssl_GetSpecReadLock(ss);
+ isTLS = (PRBool)(ss->ssl3.cwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ ssl_ReleaseSpecReadLock(ss);
+
+ /* The tls-unique channel binding is the first Finished structure in the
+ * handshake. In the case of a resumption, that's the server's Finished.
+ * Otherwise, it's the client's Finished. */
+ len = ss->ssl3.hs.finishedBytes;
+
+ /* Sending or receiving a Finished message will set finishedBytes to a
+ * non-zero value. */
+ if (len == 0) {
+ PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
+ goto loser;
+ }
+
+ /* If we are in the middle of a renegotiation then the channel binding
+ * value is poorly defined and depends on the direction that it will be
+ * used on. Therefore we simply return an error in this case. */
+ if (ss->firstHsDone && ss->ssl3.hs.ws != idle_handshake) {
+ PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
+ goto loser;
+ }
+
+ /* If resuming, then we want the second Finished value in the array, which
+ * is the server's */
+ if (ss->ssl3.hs.isResuming)
+ index = 1;
+
+ *outLen = len;
+ if (outLenMax < len) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ goto loser;
+ }
+
+ if (isTLS) {
+ memcpy(out, &ss->ssl3.hs.finishedMsgs.tFinished[index], len);
+ } else {
+ memcpy(out, &ss->ssl3.hs.finishedMsgs.sFinished[index], len);
+ }
+
+ rv = SECSuccess;
+
+loser:
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ return rv;
+}
+
+/* ssl3_config_match_init must have already been called by
+ * the caller of this function.
+ */
+SECStatus
+ssl3_ConstructV2CipherSpecsHack(sslSocket *ss, unsigned char *cs, int *size)
+{
+ int i, count = 0;
+
+ PORT_Assert(ss != 0);
+ if (!ss) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ if (SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ *size = 0;
+ return SECSuccess;
+ }
+ if (cs == NULL) {
+ *size = count_cipher_suites(ss, SSL_ALLOWED, PR_TRUE);
+ return SECSuccess;
+ }
+
+ /* ssl3_config_match_init was called by the caller of this function. */
+ for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+ ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
+ if (config_match(suite, SSL_ALLOWED, PR_TRUE)) {
+ if (cs != NULL) {
+ *cs++ = 0x00;
+ *cs++ = (suite->cipher_suite >> 8) & 0xFF;
+ *cs++ = suite->cipher_suite & 0xFF;
+ }
+ count++;
+ }
+ }
+ *size = count;
+ return SECSuccess;
+}
+
+/*
+** If ssl3 socket has completed the first handshake, and is in idle state,
+** then start a new handshake.
+** If flushCache is true, the SID cache will be flushed first, forcing a
+** "Full" handshake (not a session restart handshake), to be done.
+**
+** called from SSL_RedoHandshake(), which already holds the handshake locks.
+*/
+SECStatus
+ssl3_RedoHandshake(sslSocket *ss, PRBool flushCache)
+{
+ sslSessionID * sid = ss->sec.ci.sid;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ if (!ss->firstHsDone ||
+ ((ss->version >= SSL_LIBRARY_VERSION_3_0) &&
+ ss->ssl3.initialized &&
+ (ss->ssl3.hs.ws != idle_handshake))) {
+ PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
+ return SECFailure;
+ }
+
+ if (IS_DTLS(ss)) {
+ dtls_RehandshakeCleanup(ss);
+ }
+
+ if (ss->opt.enableRenegotiation == SSL_RENEGOTIATE_NEVER) {
+ PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
+ return SECFailure;
+ }
+ if (sid && flushCache) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid); /* remove it from whichever cache it's in. */
+ ssl_FreeSID(sid); /* dec ref count and free if zero. */
+ ss->sec.ci.sid = NULL;
+ }
+
+ ssl_GetXmitBufLock(ss); /**************************************/
+
+ /* start off a new handshake. */
+ rv = (ss->sec.isServer) ? ssl3_SendHelloRequest(ss)
+ : ssl3_SendClientHello(ss, PR_FALSE);
+
+ ssl_ReleaseXmitBufLock(ss); /**************************************/
+ return rv;
+}
+
+/* Called from ssl_DestroySocketContents() in sslsock.c */
+void
+ssl3_DestroySSL3Info(sslSocket *ss)
+{
+
+ if (ss->ssl3.clientCertificate != NULL)
+ CERT_DestroyCertificate(ss->ssl3.clientCertificate);
+
+ if (ss->ssl3.clientPrivateKey != NULL)
+ SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (ss->ssl3.platformClientKey)
+ ssl_FreePlatformKey(ss->ssl3.platformClientKey);
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+ if (ss->ssl3.channelID)
+ SECKEY_DestroyPrivateKey(ss->ssl3.channelID);
+ if (ss->ssl3.channelIDPub)
+ SECKEY_DestroyPublicKey(ss->ssl3.channelIDPub);
+
+ if (ss->ssl3.peerCertArena != NULL)
+ ssl3_CleanupPeerCerts(ss);
+
+ if (ss->ssl3.clientCertChain != NULL) {
+ CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
+ ss->ssl3.clientCertChain = NULL;
+ }
+
+ /* clean up handshake */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ if (ss->ssl3.hs.hashType == handshake_hash_combo) {
+ SHA1_DestroyContext((SHA1Context *)ss->ssl3.hs.sha_cx, PR_FALSE);
+ MD5_DestroyContext((MD5Context *)ss->ssl3.hs.md5_cx, PR_FALSE);
+ } else if (ss->ssl3.hs.hashType == handshake_hash_single) {
+ ss->ssl3.hs.sha_obj->destroy(ss->ssl3.hs.sha_cx, PR_FALSE);
+ }
+ }
+#endif
+ if (ss->ssl3.hs.md5) {
+ PK11_DestroyContext(ss->ssl3.hs.md5,PR_TRUE);
+ }
+ if (ss->ssl3.hs.sha) {
+ PK11_DestroyContext(ss->ssl3.hs.sha,PR_TRUE);
+ }
+ if (ss->ssl3.hs.clientSigAndHash) {
+ PORT_Free(ss->ssl3.hs.clientSigAndHash);
+ }
+ if (ss->ssl3.hs.messages.buf) {
+ PORT_Free(ss->ssl3.hs.messages.buf);
+ ss->ssl3.hs.messages.buf = NULL;
+ ss->ssl3.hs.messages.len = 0;
+ ss->ssl3.hs.messages.space = 0;
+ }
+
+ /* free the SSL3Buffer (msg_body) */
+ PORT_Free(ss->ssl3.hs.msg_body.buf);
+
+ /* free up the CipherSpecs */
+ ssl3_DestroyCipherSpec(&ss->ssl3.specs[0], PR_TRUE/*freeSrvName*/);
+ ssl3_DestroyCipherSpec(&ss->ssl3.specs[1], PR_TRUE/*freeSrvName*/);
+
+ /* Destroy the DTLS data */
+ if (IS_DTLS(ss)) {
+ dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
+ if (ss->ssl3.hs.recvdFragments.buf) {
+ PORT_Free(ss->ssl3.hs.recvdFragments.buf);
+ }
+ }
+
+ ss->ssl3.initialized = PR_FALSE;
+
+ SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE);
+}
+
+/* End of ssl3con.c */
diff --git a/chromium/net/third_party/nss/ssl/ssl3ecc.c b/chromium/net/third_party/nss/ssl/ssl3ecc.c
new file mode 100644
index 00000000000..a3638e75f1c
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl3ecc.c
@@ -0,0 +1,1280 @@
+/*
+ * SSL3 Protocol
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* ECC code moved here from ssl3con.c */
+
+#include "nss.h"
+#include "cert.h"
+#include "ssl.h"
+#include "cryptohi.h" /* for DSAU_ stuff */
+#include "keyhi.h"
+#include "secder.h"
+#include "secitem.h"
+
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "sslerr.h"
+#include "prtime.h"
+#include "prinrval.h"
+#include "prerror.h"
+#include "pratom.h"
+#include "prthread.h"
+#include "prinit.h"
+
+#include "pk11func.h"
+#include "secmod.h"
+
+#include <stdio.h>
+
+/* This is a bodge to allow this code to be compiled against older NSS headers
+ * that don't contain the TLS 1.2 changes. */
+#ifndef CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256
+#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
+#endif
+
+#ifdef NSS_ENABLE_ECC
+
+/*
+ * In NSS 3.13.2 the definition of the EC_POINT_FORM_UNCOMPRESSED macro
+ * was moved from the internal header ec.h to the public header blapit.h.
+ * Define the macro here when compiling against older system NSS headers.
+ */
+#ifndef EC_POINT_FORM_UNCOMPRESSED
+#define EC_POINT_FORM_UNCOMPRESSED 0x04
+#endif
+
+#ifndef PK11_SETATTRS
+#define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \
+ (x)->pValue=(v); (x)->ulValueLen = (l);
+#endif
+
+#define SSL_GET_SERVER_PUBLIC_KEY(sock, type) \
+ (ss->serverCerts[type].serverKeyPair ? \
+ ss->serverCerts[type].serverKeyPair->pubKey : NULL)
+
+#define SSL_IS_CURVE_NEGOTIATED(curvemsk, curveName) \
+ ((curveName > ec_noName) && \
+ (curveName < ec_pastLastName) && \
+ ((1UL << curveName) & curvemsk) != 0)
+
+
+
+static SECStatus ssl3_CreateECDHEphemeralKeys(sslSocket *ss, ECName ec_curve);
+
+#define supportedCurve(x) (((x) > ec_noName) && ((x) < ec_pastLastName))
+
+/* Table containing OID tags for elliptic curves named in the
+ * ECC-TLS IETF draft.
+ */
+static const SECOidTag ecName2OIDTag[] = {
+ 0,
+ SEC_OID_SECG_EC_SECT163K1, /* 1 */
+ SEC_OID_SECG_EC_SECT163R1, /* 2 */
+ SEC_OID_SECG_EC_SECT163R2, /* 3 */
+ SEC_OID_SECG_EC_SECT193R1, /* 4 */
+ SEC_OID_SECG_EC_SECT193R2, /* 5 */
+ SEC_OID_SECG_EC_SECT233K1, /* 6 */
+ SEC_OID_SECG_EC_SECT233R1, /* 7 */
+ SEC_OID_SECG_EC_SECT239K1, /* 8 */
+ SEC_OID_SECG_EC_SECT283K1, /* 9 */
+ SEC_OID_SECG_EC_SECT283R1, /* 10 */
+ SEC_OID_SECG_EC_SECT409K1, /* 11 */
+ SEC_OID_SECG_EC_SECT409R1, /* 12 */
+ SEC_OID_SECG_EC_SECT571K1, /* 13 */
+ SEC_OID_SECG_EC_SECT571R1, /* 14 */
+ SEC_OID_SECG_EC_SECP160K1, /* 15 */
+ SEC_OID_SECG_EC_SECP160R1, /* 16 */
+ SEC_OID_SECG_EC_SECP160R2, /* 17 */
+ SEC_OID_SECG_EC_SECP192K1, /* 18 */
+ SEC_OID_SECG_EC_SECP192R1, /* 19 */
+ SEC_OID_SECG_EC_SECP224K1, /* 20 */
+ SEC_OID_SECG_EC_SECP224R1, /* 21 */
+ SEC_OID_SECG_EC_SECP256K1, /* 22 */
+ SEC_OID_SECG_EC_SECP256R1, /* 23 */
+ SEC_OID_SECG_EC_SECP384R1, /* 24 */
+ SEC_OID_SECG_EC_SECP521R1, /* 25 */
+};
+
+static const PRUint16 curve2bits[] = {
+ 0, /* ec_noName = 0, */
+ 163, /* ec_sect163k1 = 1, */
+ 163, /* ec_sect163r1 = 2, */
+ 163, /* ec_sect163r2 = 3, */
+ 193, /* ec_sect193r1 = 4, */
+ 193, /* ec_sect193r2 = 5, */
+ 233, /* ec_sect233k1 = 6, */
+ 233, /* ec_sect233r1 = 7, */
+ 239, /* ec_sect239k1 = 8, */
+ 283, /* ec_sect283k1 = 9, */
+ 283, /* ec_sect283r1 = 10, */
+ 409, /* ec_sect409k1 = 11, */
+ 409, /* ec_sect409r1 = 12, */
+ 571, /* ec_sect571k1 = 13, */
+ 571, /* ec_sect571r1 = 14, */
+ 160, /* ec_secp160k1 = 15, */
+ 160, /* ec_secp160r1 = 16, */
+ 160, /* ec_secp160r2 = 17, */
+ 192, /* ec_secp192k1 = 18, */
+ 192, /* ec_secp192r1 = 19, */
+ 224, /* ec_secp224k1 = 20, */
+ 224, /* ec_secp224r1 = 21, */
+ 256, /* ec_secp256k1 = 22, */
+ 256, /* ec_secp256r1 = 23, */
+ 384, /* ec_secp384r1 = 24, */
+ 521, /* ec_secp521r1 = 25, */
+ 65535 /* ec_pastLastName */
+};
+
+typedef struct Bits2CurveStr {
+ PRUint16 bits;
+ ECName curve;
+} Bits2Curve;
+
+static const Bits2Curve bits2curve [] = {
+ { 192, ec_secp192r1 /* = 19, fast */ },
+ { 160, ec_secp160r2 /* = 17, fast */ },
+ { 160, ec_secp160k1 /* = 15, */ },
+ { 160, ec_secp160r1 /* = 16, */ },
+ { 163, ec_sect163k1 /* = 1, */ },
+ { 163, ec_sect163r1 /* = 2, */ },
+ { 163, ec_sect163r2 /* = 3, */ },
+ { 192, ec_secp192k1 /* = 18, */ },
+ { 193, ec_sect193r1 /* = 4, */ },
+ { 193, ec_sect193r2 /* = 5, */ },
+ { 224, ec_secp224r1 /* = 21, fast */ },
+ { 224, ec_secp224k1 /* = 20, */ },
+ { 233, ec_sect233k1 /* = 6, */ },
+ { 233, ec_sect233r1 /* = 7, */ },
+ { 239, ec_sect239k1 /* = 8, */ },
+ { 256, ec_secp256r1 /* = 23, fast */ },
+ { 256, ec_secp256k1 /* = 22, */ },
+ { 283, ec_sect283k1 /* = 9, */ },
+ { 283, ec_sect283r1 /* = 10, */ },
+ { 384, ec_secp384r1 /* = 24, fast */ },
+ { 409, ec_sect409k1 /* = 11, */ },
+ { 409, ec_sect409r1 /* = 12, */ },
+ { 521, ec_secp521r1 /* = 25, fast */ },
+ { 571, ec_sect571k1 /* = 13, */ },
+ { 571, ec_sect571r1 /* = 14, */ },
+ { 65535, ec_noName }
+};
+
+typedef struct ECDHEKeyPairStr {
+ ssl3KeyPair * pair;
+ int error; /* error code of the call-once function */
+ PRCallOnceType once;
+} ECDHEKeyPair;
+
+/* arrays of ECDHE KeyPairs */
+static ECDHEKeyPair gECDHEKeyPairs[ec_pastLastName];
+
+SECStatus
+ssl3_ECName2Params(PLArenaPool * arena, ECName curve, SECKEYECParams * params)
+{
+ SECOidData *oidData = NULL;
+
+ if ((curve <= ec_noName) || (curve >= ec_pastLastName) ||
+ ((oidData = SECOID_FindOIDByTag(ecName2OIDTag[curve])) == NULL)) {
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
+ return SECFailure;
+ }
+
+ SECITEM_AllocItem(arena, params, (2 + oidData->oid.len));
+ /*
+ * params->data needs to contain the ASN encoding of an object ID (OID)
+ * representing the named curve. The actual OID is in
+ * oidData->oid.data so we simply prepend 0x06 and OID length
+ */
+ params->data[0] = SEC_ASN1_OBJECT_ID;
+ params->data[1] = oidData->oid.len;
+ memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);
+
+ return SECSuccess;
+}
+
+static ECName
+params2ecName(SECKEYECParams * params)
+{
+ SECItem oid = { siBuffer, NULL, 0};
+ SECOidData *oidData = NULL;
+ ECName i;
+
+ /*
+ * params->data needs to contain the ASN encoding of an object ID (OID)
+ * representing a named curve. Here, we strip away everything
+ * before the actual OID and use the OID to look up a named curve.
+ */
+ if (params->data[0] != SEC_ASN1_OBJECT_ID) return ec_noName;
+ oid.len = params->len - 2;
+ oid.data = params->data + 2;
+ if ((oidData = SECOID_FindOID(&oid)) == NULL) return ec_noName;
+ for (i = ec_noName + 1; i < ec_pastLastName; i++) {
+ if (ecName2OIDTag[i] == oidData->offset)
+ return i;
+ }
+
+ return ec_noName;
+}
+
+/* Caller must set hiLevel error code. */
+static SECStatus
+ssl3_ComputeECDHKeyHash(SECOidTag hashAlg,
+ SECItem ec_params, SECItem server_ecpoint,
+ SSL3Random *client_rand, SSL3Random *server_rand,
+ SSL3Hashes *hashes, PRBool bypassPKCS11)
+{
+ PRUint8 * hashBuf;
+ PRUint8 * pBuf;
+ SECStatus rv = SECSuccess;
+ unsigned int bufLen;
+ /*
+ * XXX For now, we only support named curves (the appropriate
+ * checks are made before this method is called) so ec_params
+ * takes up only two bytes. ECPoint needs to fit in 256 bytes
+ * (because the spec says the length must fit in one byte)
+ */
+ PRUint8 buf[2*SSL3_RANDOM_LENGTH + 2 + 1 + 256];
+
+ bufLen = 2*SSL3_RANDOM_LENGTH + ec_params.len + 1 + server_ecpoint.len;
+ if (bufLen <= sizeof buf) {
+ hashBuf = buf;
+ } else {
+ hashBuf = PORT_Alloc(bufLen);
+ if (!hashBuf) {
+ return SECFailure;
+ }
+ }
+
+ memcpy(hashBuf, client_rand, SSL3_RANDOM_LENGTH);
+ pBuf = hashBuf + SSL3_RANDOM_LENGTH;
+ memcpy(pBuf, server_rand, SSL3_RANDOM_LENGTH);
+ pBuf += SSL3_RANDOM_LENGTH;
+ memcpy(pBuf, ec_params.data, ec_params.len);
+ pBuf += ec_params.len;
+ pBuf[0] = (PRUint8)(server_ecpoint.len);
+ pBuf += 1;
+ memcpy(pBuf, server_ecpoint.data, server_ecpoint.len);
+ pBuf += server_ecpoint.len;
+ PORT_Assert((unsigned int)(pBuf - hashBuf) == bufLen);
+
+ rv = ssl3_ComputeCommonKeyHash(hashAlg, hashBuf, bufLen, hashes,
+ bypassPKCS11);
+
+ PRINT_BUF(95, (NULL, "ECDHkey hash: ", hashBuf, bufLen));
+ PRINT_BUF(95, (NULL, "ECDHkey hash: MD5 result",
+ hashes->u.s.md5, MD5_LENGTH));
+ PRINT_BUF(95, (NULL, "ECDHkey hash: SHA1 result",
+ hashes->u.s.sha, SHA1_LENGTH));
+
+ if (hashBuf != buf)
+ PORT_Free(hashBuf);
+ return rv;
+}
+
+
+/* Called from ssl3_SendClientKeyExchange(). */
+SECStatus
+ssl3_SendECDHClientKeyExchange(sslSocket * ss, SECKEYPublicKey * svrPubKey)
+{
+ PK11SymKey * pms = NULL;
+ SECStatus rv = SECFailure;
+ PRBool isTLS, isTLS12;
+ CK_MECHANISM_TYPE target;
+ SECKEYPublicKey *pubKey = NULL; /* Ephemeral ECDH key */
+ SECKEYPrivateKey *privKey = NULL; /* Ephemeral ECDH key */
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ /* Generate ephemeral EC keypair */
+ if (svrPubKey->keyType != ecKey) {
+ PORT_SetError(SEC_ERROR_BAD_KEY);
+ goto loser;
+ }
+ /* XXX SHOULD CALL ssl3_CreateECDHEphemeralKeys here, instead! */
+ privKey = SECKEY_CreateECPrivateKey(&svrPubKey->u.ec.DEREncodedParams,
+ &pubKey, ss->pkcs11PinArg);
+ if (!privKey || !pubKey) {
+ ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
+ rv = SECFailure;
+ goto loser;
+ }
+ PRINT_BUF(50, (ss, "ECDH public value:",
+ pubKey->u.ec.publicValue.data,
+ pubKey->u.ec.publicValue.len));
+
+ if (isTLS12) {
+ target = CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256;
+ } else if (isTLS) {
+ target = CKM_TLS_MASTER_KEY_DERIVE_DH;
+ } else {
+ target = CKM_SSL3_MASTER_KEY_DERIVE_DH;
+ }
+
+ /* Determine the PMS */
+ pms = PK11_PubDeriveWithKDF(privKey, svrPubKey, PR_FALSE, NULL, NULL,
+ CKM_ECDH1_DERIVE, target, CKA_DERIVE, 0,
+ CKD_NULL, NULL, NULL);
+
+ if (pms == NULL) {
+ SSL3AlertDescription desc = illegal_parameter;
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ SECKEY_DestroyPrivateKey(privKey);
+ privKey = NULL;
+
+ rv = ssl3_InitPendingCipherSpec(ss, pms);
+ PK11_FreeSymKey(pms); pms = NULL;
+
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ rv = ssl3_AppendHandshakeHeader(ss, client_key_exchange,
+ pubKey->u.ec.publicValue.len + 1);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+
+ rv = ssl3_AppendHandshakeVariable(ss,
+ pubKey->u.ec.publicValue.data,
+ pubKey->u.ec.publicValue.len, 1);
+ SECKEY_DestroyPublicKey(pubKey);
+ pubKey = NULL;
+
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_AppendHandshake* */
+ }
+
+ rv = SECSuccess;
+
+loser:
+ if(pms) PK11_FreeSymKey(pms);
+ if(privKey) SECKEY_DestroyPrivateKey(privKey);
+ if(pubKey) SECKEY_DestroyPublicKey(pubKey);
+ return rv;
+}
+
+
+/*
+** Called from ssl3_HandleClientKeyExchange()
+*/
+SECStatus
+ssl3_HandleECDHClientKeyExchange(sslSocket *ss, SSL3Opaque *b,
+ PRUint32 length,
+ SECKEYPublicKey *srvrPubKey,
+ SECKEYPrivateKey *srvrPrivKey)
+{
+ PK11SymKey * pms;
+ SECStatus rv;
+ SECKEYPublicKey clntPubKey;
+ CK_MECHANISM_TYPE target;
+ PRBool isTLS, isTLS12;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss) );
+
+ clntPubKey.keyType = ecKey;
+ clntPubKey.u.ec.DEREncodedParams.len =
+ srvrPubKey->u.ec.DEREncodedParams.len;
+ clntPubKey.u.ec.DEREncodedParams.data =
+ srvrPubKey->u.ec.DEREncodedParams.data;
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &clntPubKey.u.ec.publicValue,
+ 1, &b, &length);
+ if (rv != SECSuccess) {
+ SEND_ALERT
+ return SECFailure; /* XXX Who sets the error code?? */
+ }
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ if (isTLS12) {
+ target = CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256;
+ } else if (isTLS) {
+ target = CKM_TLS_MASTER_KEY_DERIVE_DH;
+ } else {
+ target = CKM_SSL3_MASTER_KEY_DERIVE_DH;
+ }
+
+ /* Determine the PMS */
+ pms = PK11_PubDeriveWithKDF(srvrPrivKey, &clntPubKey, PR_FALSE, NULL, NULL,
+ CKM_ECDH1_DERIVE, target, CKA_DERIVE, 0,
+ CKD_NULL, NULL, NULL);
+
+ if (pms == NULL) {
+ /* last gasp. */
+ ssl_MapLowLevelError(SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+ }
+
+ rv = ssl3_InitPendingCipherSpec(ss, pms);
+ PK11_FreeSymKey(pms);
+ if (rv != SECSuccess) {
+ SEND_ALERT
+ return SECFailure; /* error code set by ssl3_InitPendingCipherSpec */
+ }
+ return SECSuccess;
+}
+
+ECName
+ssl3_GetCurveWithECKeyStrength(PRUint32 curvemsk, int requiredECCbits)
+{
+ int i;
+
+ for ( i = 0; bits2curve[i].curve != ec_noName; i++) {
+ if (bits2curve[i].bits < requiredECCbits)
+ continue;
+ if (SSL_IS_CURVE_NEGOTIATED(curvemsk, bits2curve[i].curve)) {
+ return bits2curve[i].curve;
+ }
+ }
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return ec_noName;
+}
+
+/* find the "weakest link". Get strength of signature key and of sym key.
+ * choose curve for the weakest of those two.
+ */
+ECName
+ssl3_GetCurveNameForServerSocket(sslSocket *ss)
+{
+ SECKEYPublicKey * svrPublicKey = NULL;
+ ECName ec_curve = ec_noName;
+ int signatureKeyStrength = 521;
+ int requiredECCbits = ss->sec.secretKeyBits * 2;
+
+ if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_ecdsa) {
+ svrPublicKey = SSL_GET_SERVER_PUBLIC_KEY(ss, kt_ecdh);
+ if (svrPublicKey)
+ ec_curve = params2ecName(&svrPublicKey->u.ec.DEREncodedParams);
+ if (!SSL_IS_CURVE_NEGOTIATED(ss->ssl3.hs.negotiatedECCurves, ec_curve)) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return ec_noName;
+ }
+ signatureKeyStrength = curve2bits[ ec_curve ];
+ } else {
+ /* RSA is our signing cert */
+ int serverKeyStrengthInBits;
+
+ svrPublicKey = SSL_GET_SERVER_PUBLIC_KEY(ss, kt_rsa);
+ if (!svrPublicKey) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return ec_noName;
+ }
+
+ /* currently strength in bytes */
+ serverKeyStrengthInBits = svrPublicKey->u.rsa.modulus.len;
+ if (svrPublicKey->u.rsa.modulus.data[0] == 0) {
+ serverKeyStrengthInBits--;
+ }
+ /* convert to strength in bits */
+ serverKeyStrengthInBits *= BPB;
+
+ signatureKeyStrength =
+ SSL_RSASTRENGTH_TO_ECSTRENGTH(serverKeyStrengthInBits);
+ }
+ if ( requiredECCbits > signatureKeyStrength )
+ requiredECCbits = signatureKeyStrength;
+
+ return ssl3_GetCurveWithECKeyStrength(ss->ssl3.hs.negotiatedECCurves,
+ requiredECCbits);
+}
+
+/* function to clear out the lists */
+static SECStatus
+ssl3_ShutdownECDHECurves(void *appData, void *nssData)
+{
+ int i;
+ ECDHEKeyPair *keyPair = &gECDHEKeyPairs[0];
+
+ for (i=0; i < ec_pastLastName; i++, keyPair++) {
+ if (keyPair->pair) {
+ ssl3_FreeKeyPair(keyPair->pair);
+ }
+ }
+ memset(gECDHEKeyPairs, 0, sizeof gECDHEKeyPairs);
+ return SECSuccess;
+}
+
+static PRStatus
+ssl3_ECRegister(void)
+{
+ SECStatus rv;
+ rv = NSS_RegisterShutdown(ssl3_ShutdownECDHECurves, gECDHEKeyPairs);
+ if (rv != SECSuccess) {
+ gECDHEKeyPairs[ec_noName].error = PORT_GetError();
+ }
+ return (PRStatus)rv;
+}
+
+/* CallOnce function, called once for each named curve. */
+static PRStatus
+ssl3_CreateECDHEphemeralKeyPair(void * arg)
+{
+ SECKEYPrivateKey * privKey = NULL;
+ SECKEYPublicKey * pubKey = NULL;
+ ssl3KeyPair * keyPair = NULL;
+ ECName ec_curve = (ECName)arg;
+ SECKEYECParams ecParams = { siBuffer, NULL, 0 };
+
+ PORT_Assert(gECDHEKeyPairs[ec_curve].pair == NULL);
+
+ /* ok, no one has generated a global key for this curve yet, do so */
+ if (ssl3_ECName2Params(NULL, ec_curve, &ecParams) != SECSuccess) {
+ gECDHEKeyPairs[ec_curve].error = PORT_GetError();
+ return PR_FAILURE;
+ }
+
+ privKey = SECKEY_CreateECPrivateKey(&ecParams, &pubKey, NULL);
+ SECITEM_FreeItem(&ecParams, PR_FALSE);
+
+ if (!privKey || !pubKey || !(keyPair = ssl3_NewKeyPair(privKey, pubKey))) {
+ if (privKey) {
+ SECKEY_DestroyPrivateKey(privKey);
+ }
+ if (pubKey) {
+ SECKEY_DestroyPublicKey(pubKey);
+ }
+ ssl_MapLowLevelError(SEC_ERROR_KEYGEN_FAIL);
+ gECDHEKeyPairs[ec_curve].error = PORT_GetError();
+ return PR_FAILURE;
+ }
+
+ gECDHEKeyPairs[ec_curve].pair = keyPair;
+ return PR_SUCCESS;
+}
+
+/*
+ * Creates the ephemeral public and private ECDH keys used by
+ * server in ECDHE_RSA and ECDHE_ECDSA handshakes.
+ * For now, the elliptic curve is chosen to be the same
+ * strength as the signing certificate (ECC or RSA).
+ * We need an API to specify the curve. This won't be a real
+ * issue until we further develop server-side support for ECC
+ * cipher suites.
+ */
+static SECStatus
+ssl3_CreateECDHEphemeralKeys(sslSocket *ss, ECName ec_curve)
+{
+ ssl3KeyPair * keyPair = NULL;
+
+ /* if there's no global key for this curve, make one. */
+ if (gECDHEKeyPairs[ec_curve].pair == NULL) {
+ PRStatus status;
+
+ status = PR_CallOnce(&gECDHEKeyPairs[ec_noName].once, ssl3_ECRegister);
+ if (status != PR_SUCCESS) {
+ PORT_SetError(gECDHEKeyPairs[ec_noName].error);
+ return SECFailure;
+ }
+ status = PR_CallOnceWithArg(&gECDHEKeyPairs[ec_curve].once,
+ ssl3_CreateECDHEphemeralKeyPair,
+ (void *)ec_curve);
+ if (status != PR_SUCCESS) {
+ PORT_SetError(gECDHEKeyPairs[ec_curve].error);
+ return SECFailure;
+ }
+ }
+
+ keyPair = gECDHEKeyPairs[ec_curve].pair;
+ PORT_Assert(keyPair != NULL);
+ if (!keyPair)
+ return SECFailure;
+ ss->ephemeralECDHKeyPair = ssl3_GetKeyPairRef(keyPair);
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_HandleECDHServerKeyExchange(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
+{
+ PLArenaPool * arena = NULL;
+ SECKEYPublicKey *peerKey = NULL;
+ PRBool isTLS, isTLS12;
+ SECStatus rv;
+ int errCode = SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH;
+ SSL3AlertDescription desc = illegal_parameter;
+ SSL3Hashes hashes;
+ SECItem signature = {siBuffer, NULL, 0};
+
+ SECItem ec_params = {siBuffer, NULL, 0};
+ SECItem ec_point = {siBuffer, NULL, 0};
+ unsigned char paramBuf[3]; /* only for curve_type == named_curve */
+ SSL3SignatureAndHashAlgorithm sigAndHash;
+
+ sigAndHash.hashAlg = SEC_OID_UNKNOWN;
+
+ isTLS = (PRBool)(ss->ssl3.prSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.prSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ /* XXX This works only for named curves, revisit this when
+ * we support generic curves.
+ */
+ ec_params.len = sizeof paramBuf;
+ ec_params.data = paramBuf;
+ rv = ssl3_ConsumeHandshake(ss, ec_params.data, ec_params.len, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+
+ /* Fail if the curve is not a named curve */
+ if ((ec_params.data[0] != ec_type_named) ||
+ (ec_params.data[1] != 0) ||
+ !supportedCurve(ec_params.data[2])) {
+ errCode = SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
+ desc = handshake_failure;
+ goto alert_loser;
+ }
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &ec_point, 1, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+ /* Fail if the ec point uses compressed representation */
+ if (ec_point.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
+ errCode = SEC_ERROR_UNSUPPORTED_EC_POINT_FORM;
+ desc = handshake_failure;
+ goto alert_loser;
+ }
+
+ if (isTLS12) {
+ rv = ssl3_ConsumeSignatureAndHashAlgorithm(ss, &b, &length,
+ &sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed or unsupported. */
+ }
+ rv = ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ &sigAndHash, ss->sec.peerCert);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &signature, 2, &b, &length);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed. */
+ }
+
+ if (length != 0) {
+ if (isTLS)
+ desc = decode_error;
+ goto alert_loser; /* malformed. */
+ }
+
+ PRINT_BUF(60, (NULL, "Server EC params", ec_params.data,
+ ec_params.len));
+ PRINT_BUF(60, (NULL, "Server EC point", ec_point.data, ec_point.len));
+
+ /* failures after this point are not malformed handshakes. */
+ /* TLS: send decrypt_error if signature failed. */
+ desc = isTLS ? decrypt_error : handshake_failure;
+
+ /*
+ * check to make sure the hash is signed by right guy
+ */
+ rv = ssl3_ComputeECDHKeyHash(sigAndHash.hashAlg, ec_params, ec_point,
+ &ss->ssl3.hs.client_random,
+ &ss->ssl3.hs.server_random,
+ &hashes, ss->opt.bypassPKCS11);
+
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+ rv = ssl3_VerifySignedHashes(&hashes, ss->sec.peerCert, &signature,
+ isTLS, ss->pkcs11PinArg);
+ if (rv != SECSuccess) {
+ errCode =
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto alert_loser;
+ }
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (arena == NULL) {
+ goto no_memory;
+ }
+
+ ss->sec.peerKey = peerKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
+ if (peerKey == NULL) {
+ goto no_memory;
+ }
+
+ peerKey->arena = arena;
+ peerKey->keyType = ecKey;
+
+ /* set up EC parameters in peerKey */
+ if (ssl3_ECName2Params(arena, ec_params.data[2],
+ &peerKey->u.ec.DEREncodedParams) != SECSuccess) {
+ /* we should never get here since we already
+ * checked that we are dealing with a supported curve
+ */
+ errCode = SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
+ goto alert_loser;
+ }
+
+ /* copy publicValue in peerKey */
+ if (SECITEM_CopyItem(arena, &peerKey->u.ec.publicValue, &ec_point))
+ {
+ PORT_FreeArena(arena, PR_FALSE);
+ goto no_memory;
+ }
+ peerKey->pkcs11Slot = NULL;
+ peerKey->pkcs11ID = CK_INVALID_HANDLE;
+
+ ss->sec.peerKey = peerKey;
+ ss->ssl3.hs.ws = wait_cert_request;
+
+ return SECSuccess;
+
+alert_loser:
+ (void)SSL3_SendAlert(ss, alert_fatal, desc);
+loser:
+ PORT_SetError( errCode );
+ return SECFailure;
+
+no_memory: /* no-memory error has already been set. */
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+}
+
+SECStatus
+ssl3_SendECDHServerKeyExchange(
+ sslSocket *ss,
+ const SSL3SignatureAndHashAlgorithm *sigAndHash)
+{
+ const ssl3KEADef * kea_def = ss->ssl3.hs.kea_def;
+ SECStatus rv = SECFailure;
+ int length;
+ PRBool isTLS, isTLS12;
+ SECItem signed_hash = {siBuffer, NULL, 0};
+ SSL3Hashes hashes;
+
+ SECKEYPublicKey * ecdhePub;
+ SECItem ec_params = {siBuffer, NULL, 0};
+ unsigned char paramBuf[3];
+ ECName curve;
+ SSL3KEAType certIndex;
+
+ /* Generate ephemeral ECDH key pair and send the public key */
+ curve = ssl3_GetCurveNameForServerSocket(ss);
+ if (curve == ec_noName) {
+ goto loser;
+ }
+ rv = ssl3_CreateECDHEphemeralKeys(ss, curve);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+ ecdhePub = ss->ephemeralECDHKeyPair->pubKey;
+ PORT_Assert(ecdhePub != NULL);
+ if (!ecdhePub) {
+ PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ return SECFailure;
+ }
+
+ ec_params.len = sizeof paramBuf;
+ ec_params.data = paramBuf;
+ curve = params2ecName(&ecdhePub->u.ec.DEREncodedParams);
+ if (curve != ec_noName) {
+ ec_params.data[0] = ec_type_named;
+ ec_params.data[1] = 0x00;
+ ec_params.data[2] = curve;
+ } else {
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);
+ goto loser;
+ }
+
+ rv = ssl3_ComputeECDHKeyHash(sigAndHash->hashAlg,
+ ec_params,
+ ecdhePub->u.ec.publicValue,
+ &ss->ssl3.hs.client_random,
+ &ss->ssl3.hs.server_random,
+ &hashes, ss->opt.bypassPKCS11);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ isTLS = (PRBool)(ss->ssl3.pwSpec->version > SSL_LIBRARY_VERSION_3_0);
+ isTLS12 = (PRBool)(ss->ssl3.pwSpec->version >= SSL_LIBRARY_VERSION_TLS_1_2);
+
+ /* XXX SSLKEAType isn't really a good choice for
+ * indexing certificates but that's all we have
+ * for now.
+ */
+ if (kea_def->kea == kea_ecdhe_rsa)
+ certIndex = kt_rsa;
+ else /* kea_def->kea == kea_ecdhe_ecdsa */
+ certIndex = kt_ecdh;
+
+ rv = ssl3_SignHashes(&hashes, ss->serverCerts[certIndex].SERVERKEY,
+ &signed_hash, isTLS);
+ if (rv != SECSuccess) {
+ goto loser; /* ssl3_SignHashes has set err. */
+ }
+ if (signed_hash.data == NULL) {
+ /* how can this happen and rv == SECSuccess ?? */
+ PORT_SetError(SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE);
+ goto loser;
+ }
+
+ length = ec_params.len +
+ 1 + ecdhePub->u.ec.publicValue.len +
+ (isTLS12 ? 2 : 0) + 2 + signed_hash.len;
+
+ rv = ssl3_AppendHandshakeHeader(ss, server_key_exchange, length);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ rv = ssl3_AppendHandshake(ss, ec_params.data, ec_params.len);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ rv = ssl3_AppendHandshakeVariable(ss, ecdhePub->u.ec.publicValue.data,
+ ecdhePub->u.ec.publicValue.len, 1);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ if (isTLS12) {
+ rv = ssl3_AppendSignatureAndHashAlgorithm(ss, sigAndHash);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+ }
+
+ rv = ssl3_AppendHandshakeVariable(ss, signed_hash.data,
+ signed_hash.len, 2);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by AppendHandshake. */
+ }
+
+ PORT_Free(signed_hash.data);
+ return SECSuccess;
+
+loser:
+ if (signed_hash.data != NULL)
+ PORT_Free(signed_hash.data);
+ return SECFailure;
+}
+
+/* Lists of ECC cipher suites for searching and disabling. */
+
+static const ssl3CipherSuite ecdh_suites[] = {
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_NULL_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_RSA_WITH_NULL_SHA,
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+static const ssl3CipherSuite ecdh_ecdsa_suites[] = {
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_NULL_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+static const ssl3CipherSuite ecdh_rsa_suites[] = {
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_RSA_WITH_NULL_SHA,
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+static const ssl3CipherSuite ecdhe_ecdsa_suites[] = {
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+static const ssl3CipherSuite ecdhe_rsa_suites[] = {
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+/* List of all ECC cipher suites */
+static const ssl3CipherSuite ecSuites[] = {
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_NULL_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_RSA_WITH_NULL_SHA,
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ 0 /* end of list marker */
+};
+
+/* On this socket, Disable the ECC cipher suites in the argument's list */
+SECStatus
+ssl3_DisableECCSuites(sslSocket * ss, const ssl3CipherSuite * suite)
+{
+ if (!suite)
+ suite = ecSuites;
+ for (; *suite; ++suite) {
+ SECStatus rv = ssl3_CipherPrefSet(ss, *suite, PR_FALSE);
+
+ PORT_Assert(rv == SECSuccess); /* else is coding error */
+ }
+ return SECSuccess;
+}
+
+/* Look at the server certs configured on this socket, and disable any
+ * ECC cipher suites that are not supported by those certs.
+ */
+void
+ssl3_FilterECCipherSuitesByServerCerts(sslSocket * ss)
+{
+ CERTCertificate * svrCert;
+
+ svrCert = ss->serverCerts[kt_rsa].serverCert;
+ if (!svrCert) {
+ ssl3_DisableECCSuites(ss, ecdhe_rsa_suites);
+ }
+
+ svrCert = ss->serverCerts[kt_ecdh].serverCert;
+ if (!svrCert) {
+ ssl3_DisableECCSuites(ss, ecdh_suites);
+ ssl3_DisableECCSuites(ss, ecdhe_ecdsa_suites);
+ } else {
+ SECOidTag sigTag = SECOID_GetAlgorithmTag(&svrCert->signature);
+
+ switch (sigTag) {
+ case SEC_OID_PKCS1_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
+ ssl3_DisableECCSuites(ss, ecdh_ecdsa_suites);
+ break;
+ case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE:
+ case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE:
+ case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE:
+ case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE:
+ case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE:
+ case SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST:
+ case SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST:
+ ssl3_DisableECCSuites(ss, ecdh_rsa_suites);
+ break;
+ default:
+ ssl3_DisableECCSuites(ss, ecdh_suites);
+ break;
+ }
+ }
+}
+
+/* Ask: is ANY ECC cipher suite enabled on this socket? */
+/* Order(N^2). Yuk. Also, this ignores export policy. */
+PRBool
+ssl3_IsECCEnabled(sslSocket * ss)
+{
+ const ssl3CipherSuite * suite;
+ PK11SlotInfo *slot;
+
+ /* make sure we can do ECC */
+ slot = PK11_GetBestSlot(CKM_ECDH1_DERIVE, ss->pkcs11PinArg);
+ if (!slot) {
+ return PR_FALSE;
+ }
+ PK11_FreeSlot(slot);
+
+ /* make sure an ECC cipher is enabled */
+ for (suite = ecSuites; *suite; ++suite) {
+ PRBool enabled = PR_FALSE;
+ SECStatus rv = ssl3_CipherPrefGet(ss, *suite, &enabled);
+
+ PORT_Assert(rv == SECSuccess); /* else is coding error */
+ if (rv == SECSuccess && enabled)
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+
+#define BE(n) 0, n
+
+/* Prefabricated TLS client hello extension, Elliptic Curves List,
+ * offers only 3 curves, the Suite B curves, 23-25
+ */
+static const PRUint8 suiteBECList[12] = {
+ BE(10), /* Extension type */
+ BE( 8), /* octets that follow ( 3 pairs + 1 length pair) */
+ BE( 6), /* octets that follow ( 3 pairs) */
+ BE(23), BE(24), BE(25)
+};
+
+/* Prefabricated TLS client hello extension, Elliptic Curves List,
+ * offers curves 1-25.
+ */
+static const PRUint8 tlsECList[56] = {
+ BE(10), /* Extension type */
+ BE(52), /* octets that follow (25 pairs + 1 length pair) */
+ BE(50), /* octets that follow (25 pairs) */
+ BE( 1), BE( 2), BE( 3), BE( 4), BE( 5), BE( 6), BE( 7),
+ BE( 8), BE( 9), BE(10), BE(11), BE(12), BE(13), BE(14), BE(15),
+ BE(16), BE(17), BE(18), BE(19), BE(20), BE(21), BE(22), BE(23),
+ BE(24), BE(25)
+};
+
+static const PRUint8 ecPtFmt[6] = {
+ BE(11), /* Extension type */
+ BE( 2), /* octets that follow */
+ 1, /* octets that follow */
+ 0 /* uncompressed type only */
+};
+
+/* This function already presumes we can do ECC, ssl3_IsECCEnabled must be
+ * called before this function. It looks to see if we have a token which
+ * is capable of doing smaller than SuiteB curves. If the token can, we
+ * presume the token can do the whole SSL suite of curves. If it can't we
+ * presume the token that allowed ECC to be enabled can only do suite B
+ * curves. */
+static PRBool
+ssl3_SuiteBOnly(sslSocket *ss)
+{
+#if 0
+ /* See if we can support small curves (like 163). If not, assume we can
+ * only support Suite-B curves (P-256, P-384, P-521). */
+ PK11SlotInfo *slot =
+ PK11_GetBestSlotWithAttributes(CKM_ECDH1_DERIVE, 0, 163,
+ ss ? ss->pkcs11PinArg : NULL);
+
+ if (!slot) {
+ /* nope, presume we can only do suite B */
+ return PR_TRUE;
+ }
+ /* we can, presume we can do all curves */
+ PK11_FreeSlot(slot);
+ return PR_FALSE;
+#else
+ return PR_TRUE;
+#endif
+}
+
+/* Send our "canned" (precompiled) Supported Elliptic Curves extension,
+ * which says that we support all TLS-defined named curves.
+ */
+PRInt32
+ssl3_SendSupportedCurvesXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 ecListSize = 0;
+ const PRUint8 *ecList = NULL;
+
+ if (!ss || !ssl3_IsECCEnabled(ss))
+ return 0;
+
+ if (ssl3_SuiteBOnly(ss)) {
+ ecListSize = sizeof suiteBECList;
+ ecList = suiteBECList;
+ } else {
+ ecListSize = sizeof tlsECList;
+ ecList = tlsECList;
+ }
+
+ if (append && maxBytes >= ecListSize) {
+ SECStatus rv = ssl3_AppendHandshake(ss, ecList, ecListSize);
+ if (rv != SECSuccess)
+ return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_elliptic_curves_xtn;
+ }
+ }
+ return ecListSize;
+}
+
+PRUint32
+ssl3_GetSupportedECCurveMask(sslSocket *ss)
+{
+ if (ssl3_SuiteBOnly(ss)) {
+ return SSL3_SUITE_B_SUPPORTED_CURVES_MASK;
+ }
+ return SSL3_ALL_SUPPORTED_CURVES_MASK;
+}
+
+/* Send our "canned" (precompiled) Supported Point Formats extension,
+ * which says that we only support uncompressed points.
+ */
+PRInt32
+ssl3_SendSupportedPointFormatsXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ if (!ss || !ssl3_IsECCEnabled(ss))
+ return 0;
+ if (append && maxBytes >= (sizeof ecPtFmt)) {
+ SECStatus rv = ssl3_AppendHandshake(ss, ecPtFmt, (sizeof ecPtFmt));
+ if (rv != SECSuccess)
+ return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_ec_point_formats_xtn;
+ }
+ }
+ return (sizeof ecPtFmt);
+}
+
+/* Just make sure that the remote client supports uncompressed points,
+ * Since that is all we support. Disable ECC cipher suites if it doesn't.
+ */
+SECStatus
+ssl3_HandleSupportedPointFormatsXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ int i;
+
+ if (data->len < 2 || data->len > 255 || !data->data ||
+ data->len != (unsigned int)data->data[0] + 1) {
+ /* malformed */
+ goto loser;
+ }
+ for (i = data->len; --i > 0; ) {
+ if (data->data[i] == 0) {
+ /* indicate that we should send a reply */
+ SECStatus rv;
+ rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type,
+ &ssl3_SendSupportedPointFormatsXtn);
+ return rv;
+ }
+ }
+loser:
+ /* evil client doesn't support uncompressed */
+ ssl3_DisableECCSuites(ss, ecSuites);
+ return SECFailure;
+}
+
+
+#define SSL3_GET_SERVER_PUBLICKEY(sock, type) \
+ (ss->serverCerts[type].serverKeyPair ? \
+ ss->serverCerts[type].serverKeyPair->pubKey : NULL)
+
+/* Extract the TLS curve name for the public key in our EC server cert. */
+ECName ssl3_GetSvrCertCurveName(sslSocket *ss)
+{
+ SECKEYPublicKey *srvPublicKey;
+ ECName ec_curve = ec_noName;
+
+ srvPublicKey = SSL3_GET_SERVER_PUBLICKEY(ss, kt_ecdh);
+ if (srvPublicKey) {
+ ec_curve = params2ecName(&srvPublicKey->u.ec.DEREncodedParams);
+ }
+ return ec_curve;
+}
+
+/* Ensure that the curve in our server cert is one of the ones suppored
+ * by the remote client, and disable all ECC cipher suites if not.
+ */
+SECStatus
+ssl3_HandleSupportedCurvesXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
+{
+ PRInt32 list_len;
+ PRUint32 peerCurves = 0;
+ PRUint32 mutualCurves = 0;
+ PRUint16 svrCertCurveName;
+
+ if (!data->data || data->len < 4 || data->len > 65535)
+ goto loser;
+ /* get the length of elliptic_curve_list */
+ list_len = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
+ if (list_len < 0 || data->len != list_len || (data->len % 2) != 0) {
+ /* malformed */
+ goto loser;
+ }
+ /* build bit vector of peer's supported curve names */
+ while (data->len) {
+ PRInt32 curve_name =
+ ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
+ if (curve_name > ec_noName && curve_name < ec_pastLastName) {
+ peerCurves |= (1U << curve_name);
+ }
+ }
+ /* What curves do we support in common? */
+ mutualCurves = ss->ssl3.hs.negotiatedECCurves &= peerCurves;
+ if (!mutualCurves) { /* no mutually supported EC Curves */
+ goto loser;
+ }
+
+ /* if our ECC cert doesn't use one of these supported curves,
+ * disable ECC cipher suites that require an ECC cert.
+ */
+ svrCertCurveName = ssl3_GetSvrCertCurveName(ss);
+ if (svrCertCurveName != ec_noName &&
+ (mutualCurves & (1U << svrCertCurveName)) != 0) {
+ return SECSuccess;
+ }
+ /* Our EC cert doesn't contain a mutually supported curve.
+ * Disable all ECC cipher suites that require an EC cert
+ */
+ ssl3_DisableECCSuites(ss, ecdh_ecdsa_suites);
+ ssl3_DisableECCSuites(ss, ecdhe_ecdsa_suites);
+ return SECFailure;
+
+loser:
+ /* no common curve supported */
+ ssl3_DisableECCSuites(ss, ecSuites);
+ return SECFailure;
+}
+
+#endif /* NSS_ENABLE_ECC */
diff --git a/chromium/net/third_party/nss/ssl/ssl3ext.c b/chromium/net/third_party/nss/ssl/ssl3ext.c
new file mode 100644
index 00000000000..04157701e90
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl3ext.c
@@ -0,0 +1,2299 @@
+/*
+ * SSL3 Protocol
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* TLS extension code moved here from ssl3ecc.c */
+
+#include "nssrenam.h"
+#include "nss.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "pk11pub.h"
+#ifdef NO_PKCS11_BYPASS
+#include "blapit.h"
+#else
+#include "blapi.h"
+#endif
+#include "prinit.h"
+
+static unsigned char key_name[SESS_TICKET_KEY_NAME_LEN];
+static PK11SymKey *session_ticket_enc_key_pkcs11 = NULL;
+static PK11SymKey *session_ticket_mac_key_pkcs11 = NULL;
+
+#ifndef NO_PKCS11_BYPASS
+static unsigned char session_ticket_enc_key[AES_256_KEY_LENGTH];
+static unsigned char session_ticket_mac_key[SHA256_LENGTH];
+
+static PRBool session_ticket_keys_initialized = PR_FALSE;
+#endif
+static PRCallOnceType generate_session_keys_once;
+
+/* forward static function declarations */
+static SECStatus ssl3_ParseEncryptedSessionTicket(sslSocket *ss,
+ SECItem *data, EncryptedSessionTicket *enc_session_ticket);
+static SECStatus ssl3_AppendToItem(SECItem *item, const unsigned char *buf,
+ PRUint32 bytes);
+static SECStatus ssl3_AppendNumberToItem(SECItem *item, PRUint32 num,
+ PRInt32 lenSize);
+static SECStatus ssl3_GetSessionTicketKeysPKCS11(sslSocket *ss,
+ PK11SymKey **aes_key, PK11SymKey **mac_key);
+#ifndef NO_PKCS11_BYPASS
+static SECStatus ssl3_GetSessionTicketKeys(const unsigned char **aes_key,
+ PRUint32 *aes_key_length, const unsigned char **mac_key,
+ PRUint32 *mac_key_length);
+#endif
+static PRInt32 ssl3_SendRenegotiationInfoXtn(sslSocket * ss,
+ PRBool append, PRUint32 maxBytes);
+static SECStatus ssl3_HandleRenegotiationInfoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static SECStatus ssl3_ClientHandleAppProtoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static PRInt32 ssl3_ClientSendAppProtoXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+static PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+static PRInt32 ssl3_SendUseSRTPXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+static SECStatus ssl3_HandleUseSRTPXtn(sslSocket * ss, PRUint16 ex_type,
+ SECItem *data);
+static SECStatus ssl3_ClientHandleChannelIDXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static PRInt32 ssl3_ClientSendChannelIDXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+static SECStatus ssl3_ServerSendStatusRequestXtn(sslSocket * ss,
+ PRBool append, PRUint32 maxBytes);
+static SECStatus ssl3_ServerHandleStatusRequestXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+static SECStatus ssl3_ClientHandleStatusRequestXtn(sslSocket *ss,
+ PRUint16 ex_type,
+ SECItem *data);
+static PRInt32 ssl3_ClientSendStatusRequestXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes);
+static PRInt32 ssl3_ClientSendSigAlgsXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+static SECStatus ssl3_ServerHandleSigAlgsXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data);
+
+/*
+ * Write bytes. Using this function means the SECItem structure
+ * cannot be freed. The caller is expected to call this function
+ * on a shallow copy of the structure.
+ */
+static SECStatus
+ssl3_AppendToItem(SECItem *item, const unsigned char *buf, PRUint32 bytes)
+{
+ if (bytes > item->len)
+ return SECFailure;
+
+ PORT_Memcpy(item->data, buf, bytes);
+ item->data += bytes;
+ item->len -= bytes;
+ return SECSuccess;
+}
+
+/*
+ * Write a number in network byte order. Using this function means the
+ * SECItem structure cannot be freed. The caller is expected to call
+ * this function on a shallow copy of the structure.
+ */
+static SECStatus
+ssl3_AppendNumberToItem(SECItem *item, PRUint32 num, PRInt32 lenSize)
+{
+ SECStatus rv;
+ PRUint8 b[4];
+ PRUint8 * p = b;
+
+ switch (lenSize) {
+ case 4:
+ *p++ = (PRUint8) (num >> 24);
+ case 3:
+ *p++ = (PRUint8) (num >> 16);
+ case 2:
+ *p++ = (PRUint8) (num >> 8);
+ case 1:
+ *p = (PRUint8) num;
+ }
+ rv = ssl3_AppendToItem(item, &b[0], lenSize);
+ return rv;
+}
+
+static SECStatus ssl3_SessionTicketShutdown(void* appData, void* nssData)
+{
+ if (session_ticket_enc_key_pkcs11) {
+ PK11_FreeSymKey(session_ticket_enc_key_pkcs11);
+ session_ticket_enc_key_pkcs11 = NULL;
+ }
+ if (session_ticket_mac_key_pkcs11) {
+ PK11_FreeSymKey(session_ticket_mac_key_pkcs11);
+ session_ticket_mac_key_pkcs11 = NULL;
+ }
+ PORT_Memset(&generate_session_keys_once, 0,
+ sizeof(generate_session_keys_once));
+ return SECSuccess;
+}
+
+
+static PRStatus
+ssl3_GenerateSessionTicketKeysPKCS11(void *data)
+{
+ SECStatus rv;
+ sslSocket *ss = (sslSocket *)data;
+ SECKEYPrivateKey *svrPrivKey = ss->serverCerts[kt_rsa].SERVERKEY;
+ SECKEYPublicKey *svrPubKey = ss->serverCerts[kt_rsa].serverKeyPair->pubKey;
+
+ if (svrPrivKey == NULL || svrPubKey == NULL) {
+ SSL_DBG(("%d: SSL[%d]: Pub or priv key(s) is NULL.",
+ SSL_GETPID(), ss->fd));
+ goto loser;
+ }
+
+ /* Get a copy of the session keys from shared memory. */
+ PORT_Memcpy(key_name, SESS_TICKET_KEY_NAME_PREFIX,
+ sizeof(SESS_TICKET_KEY_NAME_PREFIX));
+ if (!ssl_GetSessionTicketKeysPKCS11(svrPrivKey, svrPubKey,
+ ss->pkcs11PinArg, &key_name[SESS_TICKET_KEY_NAME_PREFIX_LEN],
+ &session_ticket_enc_key_pkcs11, &session_ticket_mac_key_pkcs11))
+ return PR_FAILURE;
+
+ rv = NSS_RegisterShutdown(ssl3_SessionTicketShutdown, NULL);
+ if (rv != SECSuccess)
+ goto loser;
+
+ return PR_SUCCESS;
+
+loser:
+ ssl3_SessionTicketShutdown(NULL, NULL);
+ return PR_FAILURE;
+}
+
+static SECStatus
+ssl3_GetSessionTicketKeysPKCS11(sslSocket *ss, PK11SymKey **aes_key,
+ PK11SymKey **mac_key)
+{
+ if (PR_CallOnceWithArg(&generate_session_keys_once,
+ ssl3_GenerateSessionTicketKeysPKCS11, ss) != PR_SUCCESS)
+ return SECFailure;
+
+ if (session_ticket_enc_key_pkcs11 == NULL ||
+ session_ticket_mac_key_pkcs11 == NULL)
+ return SECFailure;
+
+ *aes_key = session_ticket_enc_key_pkcs11;
+ *mac_key = session_ticket_mac_key_pkcs11;
+ return SECSuccess;
+}
+
+#ifndef NO_PKCS11_BYPASS
+static PRStatus
+ssl3_GenerateSessionTicketKeys(void)
+{
+ PORT_Memcpy(key_name, SESS_TICKET_KEY_NAME_PREFIX,
+ sizeof(SESS_TICKET_KEY_NAME_PREFIX));
+
+ if (!ssl_GetSessionTicketKeys(&key_name[SESS_TICKET_KEY_NAME_PREFIX_LEN],
+ session_ticket_enc_key, session_ticket_mac_key))
+ return PR_FAILURE;
+
+ session_ticket_keys_initialized = PR_TRUE;
+ return PR_SUCCESS;
+}
+
+static SECStatus
+ssl3_GetSessionTicketKeys(const unsigned char **aes_key,
+ PRUint32 *aes_key_length, const unsigned char **mac_key,
+ PRUint32 *mac_key_length)
+{
+ if (PR_CallOnce(&generate_session_keys_once,
+ ssl3_GenerateSessionTicketKeys) != PR_SUCCESS)
+ return SECFailure;
+
+ if (!session_ticket_keys_initialized)
+ return SECFailure;
+
+ *aes_key = session_ticket_enc_key;
+ *aes_key_length = sizeof(session_ticket_enc_key);
+ *mac_key = session_ticket_mac_key;
+ *mac_key_length = sizeof(session_ticket_mac_key);
+
+ return SECSuccess;
+}
+#endif
+
+/* Table of handlers for received TLS hello extensions, one per extension.
+ * In the second generation, this table will be dynamic, and functions
+ * will be registered here.
+ */
+/* This table is used by the server, to handle client hello extensions. */
+static const ssl3HelloExtensionHandler clientHelloHandlers[] = {
+ { ssl_server_name_xtn, &ssl3_HandleServerNameXtn },
+#ifdef NSS_ENABLE_ECC
+ { ssl_elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn },
+ { ssl_ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn },
+#endif
+ { ssl_session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ServerHandleNextProtoNegoXtn },
+ { ssl_use_srtp_xtn, &ssl3_HandleUseSRTPXtn },
+ { ssl_cert_status_xtn, &ssl3_ServerHandleStatusRequestXtn },
+ { ssl_signature_algorithms_xtn, &ssl3_ServerHandleSigAlgsXtn },
+ { -1, NULL }
+};
+
+/* These two tables are used by the client, to handle server hello
+ * extensions. */
+static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
+ { ssl_server_name_xtn, &ssl3_HandleServerNameXtn },
+ /* TODO: add a handler for ssl_ec_point_formats_xtn */
+ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ClientHandleNextProtoNegoXtn },
+ { ssl_app_layer_protocol_xtn, &ssl3_ClientHandleAppProtoXtn },
+ { ssl_use_srtp_xtn, &ssl3_HandleUseSRTPXtn },
+ { ssl_channel_id_xtn, &ssl3_ClientHandleChannelIDXtn },
+ { ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
+ { -1, NULL }
+};
+
+static const ssl3HelloExtensionHandler serverHelloHandlersSSL3[] = {
+ { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn },
+ { -1, NULL }
+};
+
+/* Tables of functions to format TLS hello extensions, one function per
+ * extension.
+ * These static tables are for the formatting of client hello extensions.
+ * The server's table of hello senders is dynamic, in the socket struct,
+ * and sender functions are registered there.
+ */
+static const
+ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
+ { ssl_server_name_xtn, &ssl3_SendServerNameXtn },
+ { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn },
+#ifdef NSS_ENABLE_ECC
+ { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn },
+ { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn },
+#endif
+ { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn },
+ { ssl_next_proto_nego_xtn, &ssl3_ClientSendNextProtoNegoXtn },
+ { ssl_app_layer_protocol_xtn, &ssl3_ClientSendAppProtoXtn },
+ { ssl_use_srtp_xtn, &ssl3_SendUseSRTPXtn },
+ { ssl_channel_id_xtn, &ssl3_ClientSendChannelIDXtn },
+ { ssl_cert_status_xtn, &ssl3_ClientSendStatusRequestXtn },
+ { ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn }
+ /* any extra entries will appear as { 0, NULL } */
+};
+
+static const
+ssl3HelloExtensionSender clientHelloSendersSSL3[SSL_MAX_EXTENSIONS] = {
+ { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn }
+ /* any extra entries will appear as { 0, NULL } */
+};
+
+static PRBool
+arrayContainsExtension(const PRUint16 *array, PRUint32 len, PRUint16 ex_type)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ if (ex_type == array[i])
+ return PR_TRUE;
+ }
+ return PR_FALSE;
+}
+
+PRBool
+ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ return arrayContainsExtension(xtnData->negotiated,
+ xtnData->numNegotiated, ex_type);
+}
+
+static PRBool
+ssl3_ClientExtensionAdvertised(sslSocket *ss, PRUint16 ex_type) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ return arrayContainsExtension(xtnData->advertised,
+ xtnData->numAdvertised, ex_type);
+}
+
+/* Format an SNI extension, using the name from the socket's URL,
+ * unless that name is a dotted decimal string.
+ * Used by client and server.
+ */
+PRInt32
+ssl3_SendServerNameXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
+{
+ SECStatus rv;
+ if (!ss)
+ return 0;
+ if (!ss->sec.isServer) {
+ PRUint32 len;
+ PRNetAddr netAddr;
+
+ /* must have a hostname */
+ if (!ss->url || !ss->url[0])
+ return 0;
+ /* must not be an IPv4 or IPv6 address */
+ if (PR_SUCCESS == PR_StringToNetAddr(ss->url, &netAddr)) {
+ /* is an IP address (v4 or v6) */
+ return 0;
+ }
+ len = PORT_Strlen(ss->url);
+ if (append && maxBytes >= len + 9) {
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 5, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of server_name_list */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 3, 2);
+ if (rv != SECSuccess) return -1;
+ /* Name Type (sni_host_name) */
+ rv = ssl3_AppendHandshake(ss, "\0", 1);
+ if (rv != SECSuccess) return -1;
+ /* HostName (length and value) */
+ rv = ssl3_AppendHandshakeVariable(ss, (PRUint8 *)ss->url, len, 2);
+ if (rv != SECSuccess) return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_server_name_xtn;
+ }
+ }
+ return len + 9;
+ }
+ /* Server side */
+ if (append && maxBytes >= 4) {
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_server_name_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess) return -1;
+ }
+ return 4;
+}
+
+/* handle an incoming SNI extension, by ignoring it. */
+SECStatus
+ssl3_HandleServerNameXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
+{
+ SECItem *names = NULL;
+ PRUint32 listCount = 0, namesPos = 0, i;
+ TLSExtensionData *xtnData = &ss->xtnData;
+ SECItem ldata;
+ PRInt32 listLenBytes = 0;
+
+ if (!ss->sec.isServer) {
+ /* Verify extension_data is empty. */
+ if (data->data || data->len ||
+ !ssl3_ExtensionNegotiated(ss, ssl_server_name_xtn)) {
+ /* malformed or was not initiated by the client.*/
+ return SECFailure;
+ }
+ return SECSuccess;
+ }
+
+ /* Server side - consume client data and register server sender. */
+ /* do not parse the data if don't have user extension handling function. */
+ if (!ss->sniSocketConfig) {
+ return SECSuccess;
+ }
+ /* length of server_name_list */
+ listLenBytes = ssl3_ConsumeHandshakeNumber(ss, 2, &data->data, &data->len);
+ if (listLenBytes == 0 || listLenBytes != data->len) {
+ return SECFailure;
+ }
+ ldata = *data;
+ /* Calculate the size of the array.*/
+ while (listLenBytes > 0) {
+ SECItem litem;
+ SECStatus rv;
+ PRInt32 type;
+ /* Name Type (sni_host_name) */
+ type = ssl3_ConsumeHandshakeNumber(ss, 1, &ldata.data, &ldata.len);
+ if (!ldata.len) {
+ return SECFailure;
+ }
+ rv = ssl3_ConsumeHandshakeVariable(ss, &litem, 2, &ldata.data, &ldata.len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Adjust total length for cunsumed item, item len and type.*/
+ listLenBytes -= litem.len + 3;
+ if (listLenBytes > 0 && !ldata.len) {
+ return SECFailure;
+ }
+ listCount += 1;
+ }
+ if (!listCount) {
+ return SECFailure;
+ }
+ names = PORT_ZNewArray(SECItem, listCount);
+ if (!names) {
+ return SECFailure;
+ }
+ for (i = 0;i < listCount;i++) {
+ int j;
+ PRInt32 type;
+ SECStatus rv;
+ PRBool nametypePresent = PR_FALSE;
+ /* Name Type (sni_host_name) */
+ type = ssl3_ConsumeHandshakeNumber(ss, 1, &data->data, &data->len);
+ /* Check if we have such type in the list */
+ for (j = 0;j < listCount && names[j].data;j++) {
+ if (names[j].type == type) {
+ nametypePresent = PR_TRUE;
+ break;
+ }
+ }
+ /* HostName (length and value) */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &names[namesPos], 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (nametypePresent == PR_FALSE) {
+ namesPos += 1;
+ }
+ }
+ /* Free old and set the new data. */
+ if (xtnData->sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ }
+ xtnData->sniNameArr = names;
+ xtnData->sniNameArrSize = namesPos;
+ xtnData->negotiated[xtnData->numNegotiated++] = ssl_server_name_xtn;
+
+ return SECSuccess;
+
+loser:
+ PORT_Free(names);
+ return SECFailure;
+}
+
+/* Called by both clients and servers.
+ * Clients sends a filled in session ticket if one is available, and otherwise
+ * sends an empty ticket. Servers always send empty tickets.
+ */
+PRInt32
+ssl3_SendSessionTicketXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+ NewSessionTicket *session_ticket = NULL;
+
+ /* Ignore the SessionTicket extension if processing is disabled. */
+ if (!ss->opt.enableSessionTickets)
+ return 0;
+
+ /* Empty extension length = extension_type (2-bytes) +
+ * length(extension_data) (2-bytes)
+ */
+ extension_length = 4;
+
+ /* If we are a client then send a session ticket if one is availble.
+ * Servers that support the extension and are willing to negotiate the
+ * the extension always respond with an empty extension.
+ */
+ if (!ss->sec.isServer) {
+ sslSessionID *sid = ss->sec.ci.sid;
+ session_ticket = &sid->u.ssl3.sessionTicket;
+ if (session_ticket->ticket.data) {
+ if (ss->xtnData.ticketTimestampVerified) {
+ extension_length += session_ticket->ticket.len;
+ } else if (!append &&
+ (session_ticket->ticket_lifetime_hint == 0 ||
+ (session_ticket->ticket_lifetime_hint +
+ session_ticket->received_timestamp > ssl_Time()))) {
+ extension_length += session_ticket->ticket.len;
+ ss->xtnData.ticketTimestampVerified = PR_TRUE;
+ }
+ }
+ }
+
+ if (append && maxBytes >= extension_length) {
+ SECStatus rv;
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_session_ticket_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ if (session_ticket && session_ticket->ticket.data &&
+ ss->xtnData.ticketTimestampVerified) {
+ rv = ssl3_AppendHandshakeVariable(ss, session_ticket->ticket.data,
+ session_ticket->ticket.len, 2);
+ ss->xtnData.ticketTimestampVerified = PR_FALSE;
+ } else {
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ }
+ if (rv != SECSuccess)
+ goto loser;
+
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_session_ticket_xtn;
+ }
+ } else if (maxBytes < extension_length) {
+ PORT_Assert(0);
+ return 0;
+ }
+ return extension_length;
+
+ loser:
+ ss->xtnData.ticketTimestampVerified = PR_FALSE;
+ return -1;
+}
+
+/* handle an incoming Next Protocol Negotiation extension. */
+static SECStatus
+ssl3_ServerHandleNextProtoNegoXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
+{
+ if (ss->firstHsDone || data->len != 0) {
+ /* Clients MUST send an empty NPN extension, if any. */
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+
+ /* TODO: server side NPN support would require calling
+ * ssl3_RegisterServerHelloExtensionSender here in order to echo the
+ * extension back to the client. */
+
+ return SECSuccess;
+}
+
+/* ssl3_ValidateNextProtoNego checks that the given block of data is valid: none
+ * of the lengths may be 0 and the sum of the lengths must equal the length of
+ * the block. */
+SECStatus
+ssl3_ValidateNextProtoNego(const unsigned char* data, unsigned int length)
+{
+ unsigned int offset = 0;
+
+ while (offset < length) {
+ unsigned int newOffset = offset + 1 + (unsigned int) data[offset];
+ /* Reject embedded nulls to protect against buggy applications that
+ * store protocol identifiers in null-terminated strings.
+ */
+ if (newOffset > length || data[offset] == 0) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+ offset = newOffset;
+ }
+
+ if (offset > length) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ SECStatus rv;
+ unsigned char resultBuffer[255];
+ SECItem result = { siBuffer, resultBuffer, 0 };
+
+ PORT_Assert(!ss->firstHsDone);
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_app_layer_protocol_xtn)) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ rv = ssl3_ValidateNextProtoNego(data->data, data->len);
+ if (rv != SECSuccess)
+ return rv;
+
+ /* ss->nextProtoCallback cannot normally be NULL if we negotiated the
+ * extension. However, It is possible that an application erroneously
+ * cleared the callback between the time we sent the ClientHello and now.
+ */
+ PORT_Assert(ss->nextProtoCallback != NULL);
+ if (!ss->nextProtoCallback) {
+ /* XXX Use a better error code. This is an application error, not an
+ * NSS bug. */
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ rv = ss->nextProtoCallback(ss->nextProtoArg, ss->fd, data->data, data->len,
+ result.data, &result.len, sizeof resultBuffer);
+ if (rv != SECSuccess)
+ return rv;
+ /* If the callback wrote more than allowed to |result| it has corrupted our
+ * stack. */
+ if (result.len > sizeof resultBuffer) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+
+ SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE);
+ return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &result);
+}
+
+static SECStatus
+ssl3_ClientHandleAppProtoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
+{
+ const unsigned char* d = data->data;
+ PRUint16 name_list_len;
+ SECItem protocol_name;
+
+ if (ssl3_ExtensionNegotiated(ss, ssl_next_proto_nego_xtn)) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+
+ /* The extension data from the server has the following format:
+ * uint16 name_list_len;
+ * uint8 len;
+ * uint8 protocol_name[len]; */
+ if (data->len < 4 || data->len > 2 + 1 + 255) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ name_list_len = ((PRUint16) d[0]) << 8 |
+ ((PRUint16) d[1]);
+ if (name_list_len != data->len - 2 ||
+ d[2] != data->len - 3) {
+ PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
+ return SECFailure;
+ }
+
+ protocol_name.data = data->data + 3;
+ protocol_name.len = data->len - 3;
+
+ SECITEM_FreeItem(&ss->ssl3.nextProto, PR_FALSE);
+ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_SELECTED;
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ return SECITEM_CopyItem(NULL, &ss->ssl3.nextProto, &protocol_name);
+}
+
+static PRInt32
+ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+
+ /* Renegotiations do not send this extension. */
+ if (!ss->nextProtoCallback || ss->firstHsDone) {
+ return 0;
+ }
+
+ extension_length = 4;
+
+ if (append && maxBytes >= extension_length) {
+ SECStatus rv;
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_next_proto_nego_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_next_proto_nego_xtn;
+ } else if (maxBytes < extension_length) {
+ return 0;
+ }
+
+ return extension_length;
+
+loser:
+ return -1;
+}
+
+static PRInt32
+ssl3_ClientSendAppProtoXtn(sslSocket * ss, PRBool append, PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+ unsigned char *alpn_protos = NULL;
+
+ /* Renegotiations do not send this extension. */
+ if (!ss->opt.nextProtoNego.data || ss->firstHsDone) {
+ return 0;
+ }
+
+ extension_length = 2 /* extension type */ + 2 /* extension length */ +
+ 2 /* protocol name list length */ +
+ ss->opt.nextProtoNego.len;
+
+ if (append && maxBytes >= extension_length) {
+ /* NPN requires that the client's fallback protocol is first in the
+ * list. However, ALPN sends protocols in preference order. So we
+ * allocate a buffer and move the first protocol to the end of the
+ * list. */
+ SECStatus rv;
+ const unsigned int len = ss->opt.nextProtoNego.len;
+
+ alpn_protos = PORT_Alloc(len);
+ if (alpn_protos == NULL) {
+ return SECFailure;
+ }
+ if (len > 0) {
+ /* Each protocol string is prefixed with a single byte length. */
+ unsigned int i = ss->opt.nextProtoNego.data[0] + 1;
+ if (i <= len) {
+ memcpy(alpn_protos, &ss->opt.nextProtoNego.data[i], len - i);
+ memcpy(alpn_protos + len - i, ss->opt.nextProtoNego.data, i);
+ } else {
+ /* This seems to be invalid data so we'll send as-is. */
+ memcpy(alpn_protos, ss->opt.nextProtoNego.data, len);
+ }
+ }
+
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_app_layer_protocol_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeVariable(ss, alpn_protos, len, 2);
+ PORT_Free(alpn_protos);
+ alpn_protos = NULL;
+ if (rv != SECSuccess)
+ goto loser;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_app_layer_protocol_xtn;
+ } else if (maxBytes < extension_length) {
+ return 0;
+ }
+
+ return extension_length;
+
+loser:
+ if (alpn_protos)
+ PORT_Free(alpn_protos);
+ return -1;
+}
+
+static SECStatus
+ssl3_ClientHandleChannelIDXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ PORT_Assert(ss->getChannelID != NULL);
+
+ if (data->len) {
+ PORT_SetError(SSL_ERROR_BAD_CHANNEL_ID_DATA);
+ return SECFailure;
+ }
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ return SECSuccess;
+}
+
+static PRInt32
+ssl3_ClientSendChannelIDXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length = 4;
+
+ if (!ss->getChannelID)
+ return 0;
+
+ if (maxBytes < extension_length) {
+ PORT_Assert(0);
+ return 0;
+ }
+
+ if (append) {
+ SECStatus rv;
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_channel_id_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_channel_id_xtn;
+ }
+
+ return extension_length;
+
+loser:
+ return -1;
+}
+
+static SECStatus
+ssl3_ClientHandleStatusRequestXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ /* The echoed extension must be empty. */
+ if (data->len != 0)
+ return SECFailure;
+
+ /* Keep track of negotiated extensions. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+
+ return SECSuccess;
+}
+
+static PRInt32
+ssl3_ServerSendStatusRequestXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+ SECStatus rv;
+ int i;
+ PRBool haveStatus = PR_FALSE;
+
+ for (i = kt_null; i < kt_kea_size; i++) {
+ /* TODO: This is a temporary workaround.
+ * The correct code needs to see if we have an OCSP response for
+ * the server certificate being used, rather than if we have any
+ * OCSP response. See also ssl3_SendCertificateStatus.
+ */
+ if (ss->certStatusArray[i] && ss->certStatusArray[i]->len) {
+ haveStatus = PR_TRUE;
+ break;
+ }
+ }
+ if (!haveStatus)
+ return 0;
+
+ extension_length = 2 + 2;
+ if (append && maxBytes >= extension_length) {
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_cert_status_xtn, 2);
+ if (rv != SECSuccess)
+ return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ return -1;
+ }
+
+ return extension_length;
+}
+
+/* ssl3_ClientSendStatusRequestXtn builds the status_request extension on the
+ * client side. See RFC 4366 section 3.6. */
+static PRInt32
+ssl3_ClientSendStatusRequestXtn(sslSocket * ss, PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length;
+
+ if (!ss->opt.enableOCSPStapling)
+ return 0;
+
+ /* extension_type (2-bytes) +
+ * length(extension_data) (2-bytes) +
+ * status_type (1) +
+ * responder_id_list length (2) +
+ * request_extensions length (2)
+ */
+ extension_length = 9;
+
+ if (append && maxBytes >= extension_length) {
+ SECStatus rv;
+ TLSExtensionData *xtnData;
+
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_cert_status_xtn, 2);
+ if (rv != SECSuccess)
+ return -1;
+ rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
+ if (rv != SECSuccess)
+ return -1;
+ rv = ssl3_AppendHandshakeNumber(ss, 1 /* status_type ocsp */, 1);
+ if (rv != SECSuccess)
+ return -1;
+ /* A zero length responder_id_list means that the responders are
+ * implicitly known to the server. */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ return -1;
+ /* A zero length request_extensions means that there are no extensions.
+ * Specifically, we don't set the id-pkix-ocsp-nonce extension. This
+ * means that the server can replay a cached OCSP response to us. */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ return -1;
+
+ xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] = ssl_cert_status_xtn;
+ } else if (maxBytes < extension_length) {
+ PORT_Assert(0);
+ return 0;
+ }
+ return extension_length;
+}
+
+/*
+ * NewSessionTicket
+ * Called from ssl3_HandleFinished
+ */
+SECStatus
+ssl3_SendNewSessionTicket(sslSocket *ss)
+{
+ int i;
+ SECStatus rv;
+ NewSessionTicket ticket;
+ SECItem plaintext;
+ SECItem plaintext_item = {0, NULL, 0};
+ SECItem ciphertext = {0, NULL, 0};
+ PRUint32 ciphertext_length;
+ PRBool ms_is_wrapped;
+ unsigned char wrapped_ms[SSL3_MASTER_SECRET_LENGTH];
+ SECItem ms_item = {0, NULL, 0};
+ SSL3KEAType effectiveExchKeyType = ssl_kea_null;
+ PRUint32 padding_length;
+ PRUint32 message_length;
+ PRUint32 cert_length;
+ PRUint8 length_buf[4];
+ PRUint32 now;
+ PK11SymKey *aes_key_pkcs11;
+ PK11SymKey *mac_key_pkcs11;
+#ifndef NO_PKCS11_BYPASS
+ const unsigned char *aes_key;
+ const unsigned char *mac_key;
+ PRUint32 aes_key_length;
+ PRUint32 mac_key_length;
+ PRUint64 aes_ctx_buf[MAX_CIPHER_CONTEXT_LLONGS];
+ AESContext *aes_ctx;
+ const SECHashObject *hashObj = NULL;
+ PRUint64 hmac_ctx_buf[MAX_MAC_CONTEXT_LLONGS];
+ HMACContext *hmac_ctx;
+#endif
+ CK_MECHANISM_TYPE cipherMech = CKM_AES_CBC;
+ PK11Context *aes_ctx_pkcs11;
+ CK_MECHANISM_TYPE macMech = CKM_SHA256_HMAC;
+ PK11Context *hmac_ctx_pkcs11;
+ unsigned char computed_mac[TLS_EX_SESS_TICKET_MAC_LENGTH];
+ unsigned int computed_mac_length;
+ unsigned char iv[AES_BLOCK_SIZE];
+ SECItem ivItem;
+ SECItem *srvName = NULL;
+ PRUint32 srvNameLen = 0;
+ CK_MECHANISM_TYPE msWrapMech = 0; /* dummy default value,
+ * must be >= 0 */
+
+ SSL_TRC(3, ("%d: SSL3[%d]: send session_ticket handshake",
+ SSL_GETPID(), ss->fd));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+ PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+
+ ticket.ticket_lifetime_hint = TLS_EX_SESS_TICKET_LIFETIME_HINT;
+ cert_length = (ss->opt.requestCertificate && ss->sec.ci.sid->peerCert) ?
+ 3 + ss->sec.ci.sid->peerCert->derCert.len : 0;
+
+ /* Get IV and encryption keys */
+ ivItem.data = iv;
+ ivItem.len = sizeof(iv);
+ rv = PK11_GenerateRandom(iv, sizeof(iv));
+ if (rv != SECSuccess) goto loser;
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ rv = ssl3_GetSessionTicketKeys(&aes_key, &aes_key_length,
+ &mac_key, &mac_key_length);
+ } else
+#endif
+ {
+ rv = ssl3_GetSessionTicketKeysPKCS11(ss, &aes_key_pkcs11,
+ &mac_key_pkcs11);
+ }
+ if (rv != SECSuccess) goto loser;
+
+ if (ss->ssl3.pwSpec->msItem.len && ss->ssl3.pwSpec->msItem.data) {
+ /* The master secret is available unwrapped. */
+ ms_item.data = ss->ssl3.pwSpec->msItem.data;
+ ms_item.len = ss->ssl3.pwSpec->msItem.len;
+ ms_is_wrapped = PR_FALSE;
+ } else {
+ /* Extract the master secret wrapped. */
+ sslSessionID sid;
+ PORT_Memset(&sid, 0, sizeof(sslSessionID));
+
+ if (ss->ssl3.hs.kea_def->kea == kea_ecdhe_rsa) {
+ effectiveExchKeyType = kt_rsa;
+ } else {
+ effectiveExchKeyType = ss->ssl3.hs.kea_def->exchKeyType;
+ }
+
+ rv = ssl3_CacheWrappedMasterSecret(ss, &sid, ss->ssl3.pwSpec,
+ effectiveExchKeyType);
+ if (rv == SECSuccess) {
+ if (sid.u.ssl3.keys.wrapped_master_secret_len > sizeof(wrapped_ms))
+ goto loser;
+ memcpy(wrapped_ms, sid.u.ssl3.keys.wrapped_master_secret,
+ sid.u.ssl3.keys.wrapped_master_secret_len);
+ ms_item.data = wrapped_ms;
+ ms_item.len = sid.u.ssl3.keys.wrapped_master_secret_len;
+ msWrapMech = sid.u.ssl3.masterWrapMech;
+ } else {
+ /* TODO: else send an empty ticket. */
+ goto loser;
+ }
+ ms_is_wrapped = PR_TRUE;
+ }
+ /* Prep to send negotiated name */
+ srvName = &ss->ssl3.pwSpec->srvVirtName;
+ if (srvName->data && srvName->len) {
+ srvNameLen = 2 + srvName->len; /* len bytes + name len */
+ }
+
+ ciphertext_length =
+ sizeof(PRUint16) /* ticket_version */
+ + sizeof(SSL3ProtocolVersion) /* ssl_version */
+ + sizeof(ssl3CipherSuite) /* ciphersuite */
+ + 1 /* compression */
+ + 10 /* cipher spec parameters */
+ + 1 /* SessionTicket.ms_is_wrapped */
+ + 1 /* effectiveExchKeyType */
+ + 4 /* msWrapMech */
+ + 2 /* master_secret.length */
+ + ms_item.len /* master_secret */
+ + 1 /* client_auth_type */
+ + cert_length /* cert */
+ + 1 /* server name type */
+ + srvNameLen /* name len + length field */
+ + sizeof(ticket.ticket_lifetime_hint);
+ padding_length = AES_BLOCK_SIZE -
+ (ciphertext_length % AES_BLOCK_SIZE);
+ ciphertext_length += padding_length;
+
+ message_length =
+ sizeof(ticket.ticket_lifetime_hint) /* ticket_lifetime_hint */
+ + 2 /* length field for NewSessionTicket.ticket */
+ + SESS_TICKET_KEY_NAME_LEN /* key_name */
+ + AES_BLOCK_SIZE /* iv */
+ + 2 /* length field for NewSessionTicket.ticket.encrypted_state */
+ + ciphertext_length /* encrypted_state */
+ + TLS_EX_SESS_TICKET_MAC_LENGTH; /* mac */
+
+ if (SECITEM_AllocItem(NULL, &plaintext_item, ciphertext_length) == NULL)
+ goto loser;
+
+ plaintext = plaintext_item;
+
+ /* ticket_version */
+ rv = ssl3_AppendNumberToItem(&plaintext, TLS_EX_SESS_TICKET_VERSION,
+ sizeof(PRUint16));
+ if (rv != SECSuccess) goto loser;
+
+ /* ssl_version */
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->version,
+ sizeof(SSL3ProtocolVersion));
+ if (rv != SECSuccess) goto loser;
+
+ /* ciphersuite */
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->ssl3.hs.cipher_suite,
+ sizeof(ssl3CipherSuite));
+ if (rv != SECSuccess) goto loser;
+
+ /* compression */
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->ssl3.hs.compression, 1);
+ if (rv != SECSuccess) goto loser;
+
+ /* cipher spec parameters */
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.authAlgorithm, 1);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.authKeyBits, 4);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.keaType, 1);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, ss->sec.keaKeyBits, 4);
+ if (rv != SECSuccess) goto loser;
+
+ /* master_secret */
+ rv = ssl3_AppendNumberToItem(&plaintext, ms_is_wrapped, 1);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, effectiveExchKeyType, 1);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, msWrapMech, 4);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext, ms_item.len, 2);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendToItem(&plaintext, ms_item.data, ms_item.len);
+ if (rv != SECSuccess) goto loser;
+
+ /* client_identity */
+ if (ss->opt.requestCertificate && ss->sec.ci.sid->peerCert) {
+ rv = ssl3_AppendNumberToItem(&plaintext, CLIENT_AUTH_CERTIFICATE, 1);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendNumberToItem(&plaintext,
+ ss->sec.ci.sid->peerCert->derCert.len, 3);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendToItem(&plaintext,
+ ss->sec.ci.sid->peerCert->derCert.data,
+ ss->sec.ci.sid->peerCert->derCert.len);
+ if (rv != SECSuccess) goto loser;
+ } else {
+ rv = ssl3_AppendNumberToItem(&plaintext, 0, 1);
+ if (rv != SECSuccess) goto loser;
+ }
+
+ /* timestamp */
+ now = ssl_Time();
+ rv = ssl3_AppendNumberToItem(&plaintext, now,
+ sizeof(ticket.ticket_lifetime_hint));
+ if (rv != SECSuccess) goto loser;
+
+ if (srvNameLen) {
+ /* Name Type (sni_host_name) */
+ rv = ssl3_AppendNumberToItem(&plaintext, srvName->type, 1);
+ if (rv != SECSuccess) goto loser;
+ /* HostName (length and value) */
+ rv = ssl3_AppendNumberToItem(&plaintext, srvName->len, 2);
+ if (rv != SECSuccess) goto loser;
+ rv = ssl3_AppendToItem(&plaintext, srvName->data, srvName->len);
+ if (rv != SECSuccess) goto loser;
+ } else {
+ /* No Name */
+ rv = ssl3_AppendNumberToItem(&plaintext, (char)TLS_STE_NO_SERVER_NAME,
+ 1);
+ if (rv != SECSuccess) goto loser;
+ }
+
+ PORT_Assert(plaintext.len == padding_length);
+ for (i = 0; i < padding_length; i++)
+ plaintext.data[i] = (unsigned char)padding_length;
+
+ if (SECITEM_AllocItem(NULL, &ciphertext, ciphertext_length) == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* Generate encrypted portion of ticket. */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ aes_ctx = (AESContext *)aes_ctx_buf;
+ rv = AES_InitContext(aes_ctx, aes_key, aes_key_length, iv,
+ NSS_AES_CBC, 1, AES_BLOCK_SIZE);
+ if (rv != SECSuccess) goto loser;
+
+ rv = AES_Encrypt(aes_ctx, ciphertext.data, &ciphertext.len,
+ ciphertext.len, plaintext_item.data,
+ plaintext_item.len);
+ if (rv != SECSuccess) goto loser;
+ } else
+#endif
+ {
+ aes_ctx_pkcs11 = PK11_CreateContextBySymKey(cipherMech,
+ CKA_ENCRYPT, aes_key_pkcs11, &ivItem);
+ if (!aes_ctx_pkcs11)
+ goto loser;
+
+ rv = PK11_CipherOp(aes_ctx_pkcs11, ciphertext.data,
+ (int *)&ciphertext.len, ciphertext.len,
+ plaintext_item.data, plaintext_item.len);
+ PK11_Finalize(aes_ctx_pkcs11);
+ PK11_DestroyContext(aes_ctx_pkcs11, PR_TRUE);
+ if (rv != SECSuccess) goto loser;
+ }
+
+ /* Convert ciphertext length to network order. */
+ length_buf[0] = (ciphertext.len >> 8) & 0xff;
+ length_buf[1] = (ciphertext.len ) & 0xff;
+
+ /* Compute MAC. */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ hmac_ctx = (HMACContext *)hmac_ctx_buf;
+ hashObj = HASH_GetRawHashObject(HASH_AlgSHA256);
+ if (HMAC_Init(hmac_ctx, hashObj, mac_key,
+ mac_key_length, PR_FALSE) != SECSuccess)
+ goto loser;
+
+ HMAC_Begin(hmac_ctx);
+ HMAC_Update(hmac_ctx, key_name, SESS_TICKET_KEY_NAME_LEN);
+ HMAC_Update(hmac_ctx, iv, sizeof(iv));
+ HMAC_Update(hmac_ctx, (unsigned char *)length_buf, 2);
+ HMAC_Update(hmac_ctx, ciphertext.data, ciphertext.len);
+ HMAC_Finish(hmac_ctx, computed_mac, &computed_mac_length,
+ sizeof(computed_mac));
+ } else
+#endif
+ {
+ SECItem macParam;
+ macParam.data = NULL;
+ macParam.len = 0;
+ hmac_ctx_pkcs11 = PK11_CreateContextBySymKey(macMech,
+ CKA_SIGN, mac_key_pkcs11, &macParam);
+ if (!hmac_ctx_pkcs11)
+ goto loser;
+
+ rv = PK11_DigestBegin(hmac_ctx_pkcs11);
+ rv = PK11_DigestOp(hmac_ctx_pkcs11, key_name,
+ SESS_TICKET_KEY_NAME_LEN);
+ rv = PK11_DigestOp(hmac_ctx_pkcs11, iv, sizeof(iv));
+ rv = PK11_DigestOp(hmac_ctx_pkcs11, (unsigned char *)length_buf, 2);
+ rv = PK11_DigestOp(hmac_ctx_pkcs11, ciphertext.data, ciphertext.len);
+ rv = PK11_DigestFinal(hmac_ctx_pkcs11, computed_mac,
+ &computed_mac_length, sizeof(computed_mac));
+ PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE);
+ if (rv != SECSuccess) goto loser;
+ }
+
+ /* Serialize the handshake message. */
+ rv = ssl3_AppendHandshakeHeader(ss, new_session_ticket, message_length);
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshakeNumber(ss, ticket.ticket_lifetime_hint,
+ sizeof(ticket.ticket_lifetime_hint));
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshakeNumber(ss,
+ message_length - sizeof(ticket.ticket_lifetime_hint) - 2, 2);
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshake(ss, key_name, SESS_TICKET_KEY_NAME_LEN);
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshake(ss, iv, sizeof(iv));
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshakeVariable(ss, ciphertext.data, ciphertext.len, 2);
+ if (rv != SECSuccess) goto loser;
+
+ rv = ssl3_AppendHandshake(ss, computed_mac, computed_mac_length);
+ if (rv != SECSuccess) goto loser;
+
+loser:
+ if (plaintext_item.data)
+ SECITEM_FreeItem(&plaintext_item, PR_FALSE);
+ if (ciphertext.data)
+ SECITEM_FreeItem(&ciphertext, PR_FALSE);
+
+ return rv;
+}
+
+/* When a client receives a SessionTicket extension a NewSessionTicket
+ * message is expected during the handshake.
+ */
+SECStatus
+ssl3_ClientHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ if (data->len != 0)
+ return SECFailure;
+
+ /* Keep track of negotiated extensions. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_ServerHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ SECStatus rv;
+ SECItem *decrypted_state = NULL;
+ SessionTicket *parsed_session_ticket = NULL;
+ sslSessionID *sid = NULL;
+ SSL3Statistics *ssl3stats;
+
+ /* Ignore the SessionTicket extension if processing is disabled. */
+ if (!ss->opt.enableSessionTickets)
+ return SECSuccess;
+
+ /* Keep track of negotiated extensions. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+
+ /* Parse the received ticket sent in by the client. We are
+ * lenient about some parse errors, falling back to a fullshake
+ * instead of terminating the current connection.
+ */
+ if (data->len == 0) {
+ ss->xtnData.emptySessionTicket = PR_TRUE;
+ } else {
+ int i;
+ SECItem extension_data;
+ EncryptedSessionTicket enc_session_ticket;
+ unsigned char computed_mac[TLS_EX_SESS_TICKET_MAC_LENGTH];
+ unsigned int computed_mac_length;
+#ifndef NO_PKCS11_BYPASS
+ const SECHashObject *hashObj;
+ const unsigned char *aes_key;
+ const unsigned char *mac_key;
+ PRUint32 aes_key_length;
+ PRUint32 mac_key_length;
+ PRUint64 hmac_ctx_buf[MAX_MAC_CONTEXT_LLONGS];
+ HMACContext *hmac_ctx;
+ PRUint64 aes_ctx_buf[MAX_CIPHER_CONTEXT_LLONGS];
+ AESContext *aes_ctx;
+#endif
+ PK11SymKey *aes_key_pkcs11;
+ PK11SymKey *mac_key_pkcs11;
+ PK11Context *hmac_ctx_pkcs11;
+ CK_MECHANISM_TYPE macMech = CKM_SHA256_HMAC;
+ PK11Context *aes_ctx_pkcs11;
+ CK_MECHANISM_TYPE cipherMech = CKM_AES_CBC;
+ unsigned char * padding;
+ PRUint32 padding_length;
+ unsigned char *buffer;
+ unsigned int buffer_len;
+ PRInt32 temp;
+ SECItem cert_item;
+ PRInt8 nameType = TLS_STE_NO_SERVER_NAME;
+
+ /* Turn off stateless session resumption if the client sends a
+ * SessionTicket extension, even if the extension turns out to be
+ * malformed (ss->sec.ci.sid is non-NULL when doing session
+ * renegotiation.)
+ */
+ if (ss->sec.ci.sid != NULL) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(ss->sec.ci.sid);
+ ssl_FreeSID(ss->sec.ci.sid);
+ ss->sec.ci.sid = NULL;
+ }
+
+ extension_data.data = data->data; /* Keep a copy for future use. */
+ extension_data.len = data->len;
+
+ if (ssl3_ParseEncryptedSessionTicket(ss, data, &enc_session_ticket)
+ != SECSuccess)
+ return SECFailure;
+
+ /* Get session ticket keys. */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ rv = ssl3_GetSessionTicketKeys(&aes_key, &aes_key_length,
+ &mac_key, &mac_key_length);
+ } else
+#endif
+ {
+ rv = ssl3_GetSessionTicketKeysPKCS11(ss, &aes_key_pkcs11,
+ &mac_key_pkcs11);
+ }
+ if (rv != SECSuccess) {
+ SSL_DBG(("%d: SSL[%d]: Unable to get/generate session ticket keys.",
+ SSL_GETPID(), ss->fd));
+ goto loser;
+ }
+
+ /* If the ticket sent by the client was generated under a key different
+ * from the one we have, bypass ticket processing.
+ */
+ if (PORT_Memcmp(enc_session_ticket.key_name, key_name,
+ SESS_TICKET_KEY_NAME_LEN) != 0) {
+ SSL_DBG(("%d: SSL[%d]: Session ticket key_name sent mismatch.",
+ SSL_GETPID(), ss->fd));
+ goto no_ticket;
+ }
+
+ /* Verify the MAC on the ticket. MAC verification may also
+ * fail if the MAC key has been recently refreshed.
+ */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ hmac_ctx = (HMACContext *)hmac_ctx_buf;
+ hashObj = HASH_GetRawHashObject(HASH_AlgSHA256);
+ if (HMAC_Init(hmac_ctx, hashObj, mac_key,
+ sizeof(session_ticket_mac_key), PR_FALSE) != SECSuccess)
+ goto no_ticket;
+ HMAC_Begin(hmac_ctx);
+ HMAC_Update(hmac_ctx, extension_data.data,
+ extension_data.len - TLS_EX_SESS_TICKET_MAC_LENGTH);
+ if (HMAC_Finish(hmac_ctx, computed_mac, &computed_mac_length,
+ sizeof(computed_mac)) != SECSuccess)
+ goto no_ticket;
+ } else
+#endif
+ {
+ SECItem macParam;
+ macParam.data = NULL;
+ macParam.len = 0;
+ hmac_ctx_pkcs11 = PK11_CreateContextBySymKey(macMech,
+ CKA_SIGN, mac_key_pkcs11, &macParam);
+ if (!hmac_ctx_pkcs11) {
+ SSL_DBG(("%d: SSL[%d]: Unable to create HMAC context: %d.",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ goto no_ticket;
+ } else {
+ SSL_DBG(("%d: SSL[%d]: Successfully created HMAC context.",
+ SSL_GETPID(), ss->fd));
+ }
+ rv = PK11_DigestBegin(hmac_ctx_pkcs11);
+ rv = PK11_DigestOp(hmac_ctx_pkcs11, extension_data.data,
+ extension_data.len - TLS_EX_SESS_TICKET_MAC_LENGTH);
+ if (rv != SECSuccess) {
+ PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE);
+ goto no_ticket;
+ }
+ rv = PK11_DigestFinal(hmac_ctx_pkcs11, computed_mac,
+ &computed_mac_length, sizeof(computed_mac));
+ PK11_DestroyContext(hmac_ctx_pkcs11, PR_TRUE);
+ if (rv != SECSuccess)
+ goto no_ticket;
+ }
+ if (NSS_SecureMemcmp(computed_mac, enc_session_ticket.mac,
+ computed_mac_length) != 0) {
+ SSL_DBG(("%d: SSL[%d]: Session ticket MAC mismatch.",
+ SSL_GETPID(), ss->fd));
+ goto no_ticket;
+ }
+
+ /* We ignore key_name for now.
+ * This is ok as MAC verification succeeded.
+ */
+
+ /* Decrypt the ticket. */
+
+ /* Plaintext is shorter than the ciphertext due to padding. */
+ decrypted_state = SECITEM_AllocItem(NULL, NULL,
+ enc_session_ticket.encrypted_state.len);
+
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11) {
+ aes_ctx = (AESContext *)aes_ctx_buf;
+ rv = AES_InitContext(aes_ctx, aes_key,
+ sizeof(session_ticket_enc_key), enc_session_ticket.iv,
+ NSS_AES_CBC, 0,AES_BLOCK_SIZE);
+ if (rv != SECSuccess) {
+ SSL_DBG(("%d: SSL[%d]: Unable to create AES context.",
+ SSL_GETPID(), ss->fd));
+ goto no_ticket;
+ }
+
+ rv = AES_Decrypt(aes_ctx, decrypted_state->data,
+ &decrypted_state->len, decrypted_state->len,
+ enc_session_ticket.encrypted_state.data,
+ enc_session_ticket.encrypted_state.len);
+ if (rv != SECSuccess)
+ goto no_ticket;
+ } else
+#endif
+ {
+ SECItem ivItem;
+ ivItem.data = enc_session_ticket.iv;
+ ivItem.len = AES_BLOCK_SIZE;
+ aes_ctx_pkcs11 = PK11_CreateContextBySymKey(cipherMech,
+ CKA_DECRYPT, aes_key_pkcs11, &ivItem);
+ if (!aes_ctx_pkcs11) {
+ SSL_DBG(("%d: SSL[%d]: Unable to create AES context.",
+ SSL_GETPID(), ss->fd));
+ goto no_ticket;
+ }
+
+ rv = PK11_CipherOp(aes_ctx_pkcs11, decrypted_state->data,
+ (int *)&decrypted_state->len, decrypted_state->len,
+ enc_session_ticket.encrypted_state.data,
+ enc_session_ticket.encrypted_state.len);
+ PK11_Finalize(aes_ctx_pkcs11);
+ PK11_DestroyContext(aes_ctx_pkcs11, PR_TRUE);
+ if (rv != SECSuccess)
+ goto no_ticket;
+ }
+
+ /* Check padding. */
+ padding_length =
+ (PRUint32)decrypted_state->data[decrypted_state->len - 1];
+ if (padding_length == 0 || padding_length > AES_BLOCK_SIZE)
+ goto no_ticket;
+
+ padding = &decrypted_state->data[decrypted_state->len - padding_length];
+ for (i = 0; i < padding_length; i++, padding++) {
+ if (padding_length != (PRUint32)*padding)
+ goto no_ticket;
+ }
+
+ /* Deserialize session state. */
+ buffer = decrypted_state->data;
+ buffer_len = decrypted_state->len;
+
+ parsed_session_ticket = PORT_ZAlloc(sizeof(SessionTicket));
+ if (parsed_session_ticket == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* Read ticket_version (which is ignored for now.) */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->ticket_version = (SSL3ProtocolVersion)temp;
+
+ /* Read SSLVersion. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->ssl_version = (SSL3ProtocolVersion)temp;
+
+ /* Read cipher_suite. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->cipher_suite = (ssl3CipherSuite)temp;
+
+ /* Read compression_method. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->compression_method = (SSLCompressionMethod)temp;
+
+ /* Read cipher spec parameters. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->authAlgorithm = (SSLSignType)temp;
+ temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->authKeyBits = (PRUint32)temp;
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->keaType = (SSLKEAType)temp;
+ temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->keaKeyBits = (PRUint32)temp;
+
+ /* Read wrapped master_secret. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->ms_is_wrapped = (PRBool)temp;
+
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->exchKeyType = (SSL3KEAType)temp;
+
+ temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->msWrapMech = (CK_MECHANISM_TYPE)temp;
+
+ temp = ssl3_ConsumeHandshakeNumber(ss, 2, &buffer, &buffer_len);
+ if (temp < 0) goto no_ticket;
+ parsed_session_ticket->ms_length = (PRUint16)temp;
+ if (parsed_session_ticket->ms_length == 0 || /* sanity check MS. */
+ parsed_session_ticket->ms_length >
+ sizeof(parsed_session_ticket->master_secret))
+ goto no_ticket;
+
+ /* Allow for the wrapped master secret to be longer. */
+ if (buffer_len < sizeof(SSL3_MASTER_SECRET_LENGTH))
+ goto no_ticket;
+ PORT_Memcpy(parsed_session_ticket->master_secret, buffer,
+ parsed_session_ticket->ms_length);
+ buffer += parsed_session_ticket->ms_length;
+ buffer_len -= parsed_session_ticket->ms_length;
+
+ /* Read client_identity */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (temp < 0)
+ goto no_ticket;
+ parsed_session_ticket->client_identity.client_auth_type =
+ (ClientAuthenticationType)temp;
+ switch(parsed_session_ticket->client_identity.client_auth_type) {
+ case CLIENT_AUTH_ANONYMOUS:
+ break;
+ case CLIENT_AUTH_CERTIFICATE:
+ rv = ssl3_ConsumeHandshakeVariable(ss, &cert_item, 3,
+ &buffer, &buffer_len);
+ if (rv != SECSuccess) goto no_ticket;
+ rv = SECITEM_CopyItem(NULL, &parsed_session_ticket->peer_cert,
+ &cert_item);
+ if (rv != SECSuccess) goto no_ticket;
+ break;
+ default:
+ goto no_ticket;
+ }
+ /* Read timestamp. */
+ temp = ssl3_ConsumeHandshakeNumber(ss, 4, &buffer, &buffer_len);
+ if (temp < 0)
+ goto no_ticket;
+ parsed_session_ticket->timestamp = (PRUint32)temp;
+
+ /* Read server name */
+ nameType =
+ ssl3_ConsumeHandshakeNumber(ss, 1, &buffer, &buffer_len);
+ if (nameType != TLS_STE_NO_SERVER_NAME) {
+ SECItem name_item;
+ rv = ssl3_ConsumeHandshakeVariable(ss, &name_item, 2, &buffer,
+ &buffer_len);
+ if (rv != SECSuccess) goto no_ticket;
+ rv = SECITEM_CopyItem(NULL, &parsed_session_ticket->srvName,
+ &name_item);
+ if (rv != SECSuccess) goto no_ticket;
+ parsed_session_ticket->srvName.type = nameType;
+ }
+
+ /* Done parsing. Check that all bytes have been consumed. */
+ if (buffer_len != padding_length)
+ goto no_ticket;
+
+ /* Use the ticket if it has not expired, otherwise free the allocated
+ * memory since the ticket is of no use.
+ */
+ if (parsed_session_ticket->timestamp != 0 &&
+ parsed_session_ticket->timestamp +
+ TLS_EX_SESS_TICKET_LIFETIME_HINT > ssl_Time()) {
+
+ sid = ssl3_NewSessionID(ss, PR_TRUE);
+ if (sid == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+
+ /* Copy over parameters. */
+ sid->version = parsed_session_ticket->ssl_version;
+ sid->u.ssl3.cipherSuite = parsed_session_ticket->cipher_suite;
+ sid->u.ssl3.compression = parsed_session_ticket->compression_method;
+ sid->authAlgorithm = parsed_session_ticket->authAlgorithm;
+ sid->authKeyBits = parsed_session_ticket->authKeyBits;
+ sid->keaType = parsed_session_ticket->keaType;
+ sid->keaKeyBits = parsed_session_ticket->keaKeyBits;
+
+ /* Copy master secret. */
+#ifndef NO_PKCS11_BYPASS
+ if (ss->opt.bypassPKCS11 &&
+ parsed_session_ticket->ms_is_wrapped)
+ goto no_ticket;
+#endif
+ if (parsed_session_ticket->ms_length >
+ sizeof(sid->u.ssl3.keys.wrapped_master_secret))
+ goto no_ticket;
+ PORT_Memcpy(sid->u.ssl3.keys.wrapped_master_secret,
+ parsed_session_ticket->master_secret,
+ parsed_session_ticket->ms_length);
+ sid->u.ssl3.keys.wrapped_master_secret_len =
+ parsed_session_ticket->ms_length;
+ sid->u.ssl3.exchKeyType = parsed_session_ticket->exchKeyType;
+ sid->u.ssl3.masterWrapMech = parsed_session_ticket->msWrapMech;
+ sid->u.ssl3.keys.msIsWrapped =
+ parsed_session_ticket->ms_is_wrapped;
+ sid->u.ssl3.masterValid = PR_TRUE;
+ sid->u.ssl3.keys.resumable = PR_TRUE;
+
+ /* Copy over client cert from session ticket if there is one. */
+ if (parsed_session_ticket->peer_cert.data != NULL) {
+ if (sid->peerCert != NULL)
+ CERT_DestroyCertificate(sid->peerCert);
+ sid->peerCert = CERT_NewTempCertificate(ss->dbHandle,
+ &parsed_session_ticket->peer_cert, NULL, PR_FALSE, PR_TRUE);
+ if (sid->peerCert == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+ }
+ if (parsed_session_ticket->srvName.data != NULL) {
+ sid->u.ssl3.srvName = parsed_session_ticket->srvName;
+ }
+ ss->statelessResume = PR_TRUE;
+ ss->sec.ci.sid = sid;
+ }
+ }
+
+ if (0) {
+no_ticket:
+ SSL_DBG(("%d: SSL[%d]: Session ticket parsing failed.",
+ SSL_GETPID(), ss->fd));
+ ssl3stats = SSL_GetStatistics();
+ SSL_AtomicIncrementLong(& ssl3stats->hch_sid_ticket_parse_failures );
+ }
+ rv = SECSuccess;
+
+loser:
+ /* ss->sec.ci.sid == sid if it did NOT come here via goto statement
+ * in that case do not free sid
+ */
+ if (sid && (ss->sec.ci.sid != sid)) {
+ ssl_FreeSID(sid);
+ sid = NULL;
+ }
+ if (decrypted_state != NULL) {
+ SECITEM_FreeItem(decrypted_state, PR_TRUE);
+ decrypted_state = NULL;
+ }
+
+ if (parsed_session_ticket != NULL) {
+ if (parsed_session_ticket->peer_cert.data) {
+ SECITEM_FreeItem(&parsed_session_ticket->peer_cert, PR_FALSE);
+ }
+ PORT_ZFree(parsed_session_ticket, sizeof(SessionTicket));
+ }
+
+ return rv;
+}
+
+/*
+ * Read bytes. Using this function means the SECItem structure
+ * cannot be freed. The caller is expected to call this function
+ * on a shallow copy of the structure.
+ */
+static SECStatus
+ssl3_ConsumeFromItem(SECItem *item, unsigned char **buf, PRUint32 bytes)
+{
+ if (bytes > item->len)
+ return SECFailure;
+
+ *buf = item->data;
+ item->data += bytes;
+ item->len -= bytes;
+ return SECSuccess;
+}
+
+static SECStatus
+ssl3_ParseEncryptedSessionTicket(sslSocket *ss, SECItem *data,
+ EncryptedSessionTicket *enc_session_ticket)
+{
+ if (ssl3_ConsumeFromItem(data, &enc_session_ticket->key_name,
+ SESS_TICKET_KEY_NAME_LEN) != SECSuccess)
+ return SECFailure;
+ if (ssl3_ConsumeFromItem(data, &enc_session_ticket->iv,
+ AES_BLOCK_SIZE) != SECSuccess)
+ return SECFailure;
+ if (ssl3_ConsumeHandshakeVariable(ss, &enc_session_ticket->encrypted_state,
+ 2, &data->data, &data->len) != SECSuccess)
+ return SECFailure;
+ if (ssl3_ConsumeFromItem(data, &enc_session_ticket->mac,
+ TLS_EX_SESS_TICKET_MAC_LENGTH) != SECSuccess)
+ return SECFailure;
+ if (data->len != 0) /* Make sure that we have consumed all bytes. */
+ return SECFailure;
+
+ return SECSuccess;
+}
+
+/* go through hello extensions in buffer "b".
+ * For each one, find the extension handler in the table, and
+ * if present, invoke that handler.
+ * Servers ignore any extensions with unknown extension types.
+ * Clients reject any extensions with unadvertised extension types.
+ */
+SECStatus
+ssl3_HandleHelloExtensions(sslSocket *ss, SSL3Opaque **b, PRUint32 *length)
+{
+ const ssl3HelloExtensionHandler * handlers;
+
+ if (ss->sec.isServer) {
+ handlers = clientHelloHandlers;
+ } else if (ss->version > SSL_LIBRARY_VERSION_3_0) {
+ handlers = serverHelloHandlersTLS;
+ } else {
+ handlers = serverHelloHandlersSSL3;
+ }
+
+ while (*length) {
+ const ssl3HelloExtensionHandler * handler;
+ SECStatus rv;
+ PRInt32 extension_type;
+ SECItem extension_data;
+
+ /* Get the extension's type field */
+ extension_type = ssl3_ConsumeHandshakeNumber(ss, 2, b, length);
+ if (extension_type < 0) /* failure to decode extension_type */
+ return SECFailure; /* alert already sent */
+
+ /* get the data for this extension, so we can pass it or skip it. */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &extension_data, 2, b, length);
+ if (rv != SECSuccess)
+ return rv;
+
+ /* Check whether the server sent an extension which was not advertised
+ * in the ClientHello.
+ */
+ if (!ss->sec.isServer &&
+ !ssl3_ClientExtensionAdvertised(ss, extension_type))
+ return SECFailure; /* TODO: send unsupported_extension alert */
+
+ /* Check whether an extension has been sent multiple times. */
+ if (ssl3_ExtensionNegotiated(ss, extension_type))
+ return SECFailure;
+
+ /* find extension_type in table of Hello Extension Handlers */
+ for (handler = handlers; handler->ex_type >= 0; handler++) {
+ /* if found, call this handler */
+ if (handler->ex_type == extension_type) {
+ rv = (*handler->ex_handler)(ss, (PRUint16)extension_type,
+ &extension_data);
+ /* Ignore this result */
+ /* Treat all bad extensions as unrecognized types. */
+ break;
+ }
+ }
+ }
+ return SECSuccess;
+}
+
+/* Add a callback function to the table of senders of server hello extensions.
+ */
+SECStatus
+ssl3_RegisterServerHelloExtensionSender(sslSocket *ss, PRUint16 ex_type,
+ ssl3HelloExtensionSenderFunc cb)
+{
+ int i;
+ ssl3HelloExtensionSender *sender = &ss->xtnData.serverSenders[0];
+
+ for (i = 0; i < SSL_MAX_EXTENSIONS; ++i, ++sender) {
+ if (!sender->ex_sender) {
+ sender->ex_type = ex_type;
+ sender->ex_sender = cb;
+ return SECSuccess;
+ }
+ /* detect duplicate senders */
+ PORT_Assert(sender->ex_type != ex_type);
+ if (sender->ex_type == ex_type) {
+ /* duplicate */
+ break;
+ }
+ }
+ PORT_Assert(i < SSL_MAX_EXTENSIONS); /* table needs to grow */
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+}
+
+/* call each of the extension senders and return the accumulated length */
+PRInt32
+ssl3_CallHelloExtensionSenders(sslSocket *ss, PRBool append, PRUint32 maxBytes,
+ const ssl3HelloExtensionSender *sender)
+{
+ PRInt32 total_exten_len = 0;
+ int i;
+
+ if (!sender) {
+ sender = ss->version > SSL_LIBRARY_VERSION_3_0 ?
+ &clientHelloSendersTLS[0] : &clientHelloSendersSSL3[0];
+ }
+
+ for (i = 0; i < SSL_MAX_EXTENSIONS; ++i, ++sender) {
+ if (sender->ex_sender) {
+ PRInt32 extLen = (*sender->ex_sender)(ss, append, maxBytes);
+ if (extLen < 0)
+ return -1;
+ maxBytes -= extLen;
+ total_exten_len += extLen;
+ }
+ }
+ return total_exten_len;
+}
+
+
+/* Extension format:
+ * Extension number: 2 bytes
+ * Extension length: 2 bytes
+ * Verify Data Length: 1 byte
+ * Verify Data (TLS): 12 bytes (client) or 24 bytes (server)
+ * Verify Data (SSL): 36 bytes (client) or 72 bytes (server)
+ */
+static PRInt32
+ssl3_SendRenegotiationInfoXtn(
+ sslSocket * ss,
+ PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 len, needed;
+
+ /* In draft-ietf-tls-renegotiation-03, it is NOT RECOMMENDED to send
+ * both the SCSV and the empty RI, so when we send SCSV in
+ * the initial handshake, we don't also send RI.
+ */
+ if (!ss || ss->ssl3.hs.sendingSCSV)
+ return 0;
+ len = !ss->firstHsDone ? 0 :
+ (ss->sec.isServer ? ss->ssl3.hs.finishedBytes * 2
+ : ss->ssl3.hs.finishedBytes);
+ needed = 5 + len;
+ if (append && maxBytes >= needed) {
+ SECStatus rv;
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_renegotiation_info_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* length of extension_data */
+ rv = ssl3_AppendHandshakeNumber(ss, len + 1, 2);
+ if (rv != SECSuccess) return -1;
+ /* verify_Data from previous Finished message(s) */
+ rv = ssl3_AppendHandshakeVariable(ss,
+ ss->ssl3.hs.finishedMsgs.data, len, 1);
+ if (rv != SECSuccess) return -1;
+ if (!ss->sec.isServer) {
+ TLSExtensionData *xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] =
+ ssl_renegotiation_info_xtn;
+ }
+ }
+ return needed;
+}
+
+static SECStatus
+ssl3_ServerHandleStatusRequestXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ SECStatus rv = SECSuccess;
+
+ /* remember that we got this extension. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ PORT_Assert(ss->sec.isServer);
+ /* prepare to send back the appropriate response */
+ rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type,
+ ssl3_ServerSendStatusRequestXtn);
+ return rv;
+}
+
+/* This function runs in both the client and server. */
+static SECStatus
+ssl3_HandleRenegotiationInfoXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data)
+{
+ SECStatus rv = SECSuccess;
+ PRUint32 len = 0;
+
+ if (ss->firstHsDone) {
+ len = ss->sec.isServer ? ss->ssl3.hs.finishedBytes
+ : ss->ssl3.hs.finishedBytes * 2;
+ }
+ if (data->len != 1 + len ||
+ data->data[0] != len || (len &&
+ NSS_SecureMemcmp(ss->ssl3.hs.finishedMsgs.data,
+ data->data + 1, len))) {
+ /* Can we do this here? Or, must we arrange for the caller to do it? */
+ (void)SSL3_SendAlert(ss, alert_fatal, handshake_failure);
+ PORT_SetError(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ return SECFailure;
+ }
+ /* remember that we got this extension and it was correct. */
+ ss->peerRequestedProtection = 1;
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ if (ss->sec.isServer) {
+ /* prepare to send back the appropriate response */
+ rv = ssl3_RegisterServerHelloExtensionSender(ss, ex_type,
+ ssl3_SendRenegotiationInfoXtn);
+ }
+ return rv;
+}
+
+static PRInt32
+ssl3_SendUseSRTPXtn(sslSocket *ss, PRBool append, PRUint32 maxBytes)
+{
+ PRUint32 ext_data_len;
+ PRInt16 i;
+ SECStatus rv;
+
+ if (!ss)
+ return 0;
+
+ if (!ss->sec.isServer) {
+ /* Client side */
+
+ if (!IS_DTLS(ss) || !ss->ssl3.dtlsSRTPCipherCount)
+ return 0; /* Not relevant */
+
+ ext_data_len = 2 + 2 * ss->ssl3.dtlsSRTPCipherCount + 1;
+
+ if (append && maxBytes >= 4 + ext_data_len) {
+ /* Extension type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_use_srtp_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* Length of extension data */
+ rv = ssl3_AppendHandshakeNumber(ss, ext_data_len, 2);
+ if (rv != SECSuccess) return -1;
+ /* Length of the SRTP cipher list */
+ rv = ssl3_AppendHandshakeNumber(ss,
+ 2 * ss->ssl3.dtlsSRTPCipherCount,
+ 2);
+ if (rv != SECSuccess) return -1;
+ /* The SRTP ciphers */
+ for (i = 0; i < ss->ssl3.dtlsSRTPCipherCount; i++) {
+ rv = ssl3_AppendHandshakeNumber(ss,
+ ss->ssl3.dtlsSRTPCiphers[i],
+ 2);
+ }
+ /* Empty MKI value */
+ ssl3_AppendHandshakeVariable(ss, NULL, 0, 1);
+
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_use_srtp_xtn;
+ }
+
+ return 4 + ext_data_len;
+ }
+
+ /* Server side */
+ if (append && maxBytes >= 9) {
+ /* Extension type */
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_use_srtp_xtn, 2);
+ if (rv != SECSuccess) return -1;
+ /* Length of extension data */
+ rv = ssl3_AppendHandshakeNumber(ss, 5, 2);
+ if (rv != SECSuccess) return -1;
+ /* Length of the SRTP cipher list */
+ rv = ssl3_AppendHandshakeNumber(ss, 2, 2);
+ if (rv != SECSuccess) return -1;
+ /* The selected cipher */
+ rv = ssl3_AppendHandshakeNumber(ss, ss->ssl3.dtlsSRTPCipherSuite, 2);
+ if (rv != SECSuccess) return -1;
+ /* Empty MKI value */
+ ssl3_AppendHandshakeVariable(ss, NULL, 0, 1);
+ }
+
+ return 9;
+}
+
+static SECStatus
+ssl3_HandleUseSRTPXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
+{
+ SECStatus rv;
+ SECItem ciphers = {siBuffer, NULL, 0};
+ PRUint16 i;
+ unsigned int j;
+ PRUint16 cipher = 0;
+ PRBool found = PR_FALSE;
+ SECItem litem;
+
+ if (!ss->sec.isServer) {
+ /* Client side */
+ if (!data->data || !data->len) {
+ /* malformed */
+ return SECFailure;
+ }
+
+ /* Get the cipher list */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &ciphers, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Now check that the number of ciphers listed is 1 (len = 2) */
+ if (ciphers.len != 2) {
+ return SECFailure;
+ }
+
+ /* Get the selected cipher */
+ cipher = (ciphers.data[0] << 8) | ciphers.data[1];
+
+ /* Now check that this is one of the ciphers we offered */
+ for (i = 0; i < ss->ssl3.dtlsSRTPCipherCount; i++) {
+ if (cipher == ss->ssl3.dtlsSRTPCiphers[i]) {
+ found = PR_TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ return SECFailure;
+ }
+
+ /* Get the srtp_mki value */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &litem, 1,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* We didn't offer an MKI, so this must be 0 length */
+ /* XXX RFC 5764 Section 4.1.3 says:
+ * If the client detects a nonzero-length MKI in the server's
+ * response that is different than the one the client offered,
+ * then the client MUST abort the handshake and SHOULD send an
+ * invalid_parameter alert.
+ *
+ * Due to a limitation of the ssl3_HandleHelloExtensions function,
+ * returning SECFailure here won't abort the handshake. It will
+ * merely cause the use_srtp extension to be not negotiated. We
+ * should fix this. See NSS bug 753136.
+ */
+ if (litem.len != 0) {
+ return SECFailure;
+ }
+
+ if (data->len != 0) {
+ /* malformed */
+ return SECFailure;
+ }
+
+ /* OK, this looks fine. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_use_srtp_xtn;
+ ss->ssl3.dtlsSRTPCipherSuite = cipher;
+ return SECSuccess;
+ }
+
+ /* Server side */
+ if (!IS_DTLS(ss) || !ss->ssl3.dtlsSRTPCipherCount) {
+ /* Ignore the extension if we aren't doing DTLS or no DTLS-SRTP
+ * preferences have been set. */
+ return SECSuccess;
+ }
+
+ if (!data->data || data->len < 5) {
+ /* malformed */
+ return SECFailure;
+ }
+
+ /* Get the cipher list */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &ciphers, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Check that the list is even length */
+ if (ciphers.len % 2) {
+ return SECFailure;
+ }
+
+ /* Walk through the offered list and pick the most preferred of our
+ * ciphers, if any */
+ for (i = 0; !found && i < ss->ssl3.dtlsSRTPCipherCount; i++) {
+ for (j = 0; j + 1 < ciphers.len; j += 2) {
+ cipher = (ciphers.data[j] << 8) | ciphers.data[j + 1];
+ if (cipher == ss->ssl3.dtlsSRTPCiphers[i]) {
+ found = PR_TRUE;
+ break;
+ }
+ }
+ }
+
+ /* Get the srtp_mki value */
+ rv = ssl3_ConsumeHandshakeVariable(ss, &litem, 1, &data->data, &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ if (data->len != 0) {
+ return SECFailure; /* Malformed */
+ }
+
+ /* Now figure out what to do */
+ if (!found) {
+ /* No matching ciphers */
+ return SECSuccess;
+ }
+
+ /* OK, we have a valid cipher and we've selected it */
+ ss->ssl3.dtlsSRTPCipherSuite = cipher;
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_use_srtp_xtn;
+
+ return ssl3_RegisterServerHelloExtensionSender(ss, ssl_use_srtp_xtn,
+ ssl3_SendUseSRTPXtn);
+}
+
+/* ssl3_ServerHandleSigAlgsXtn handles the signature_algorithms extension
+ * from a client.
+ * See https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+static SECStatus
+ssl3_ServerHandleSigAlgsXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data)
+{
+ SECStatus rv;
+ SECItem algorithms;
+ const unsigned char *b;
+ unsigned int numAlgorithms, i;
+
+ /* Ignore this extension if we aren't doing TLS 1.2 or greater. */
+ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_2) {
+ return SECSuccess;
+ }
+
+ /* Keep track of negotiated extensions. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+
+ rv = ssl3_ConsumeHandshakeVariable(ss, &algorithms, 2, &data->data,
+ &data->len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ /* Trailing data, empty value, or odd-length value is invalid. */
+ if (data->len != 0 || algorithms.len == 0 || (algorithms.len & 1) != 0) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+ return SECFailure;
+ }
+
+ numAlgorithms = algorithms.len/2;
+
+ /* We don't care to process excessive numbers of algorithms. */
+ if (numAlgorithms > 512) {
+ numAlgorithms = 512;
+ }
+
+ ss->ssl3.hs.clientSigAndHash =
+ PORT_NewArray(SSL3SignatureAndHashAlgorithm, numAlgorithms);
+ if (!ss->ssl3.hs.clientSigAndHash) {
+ return SECFailure;
+ }
+ ss->ssl3.hs.numClientSigAndHash = 0;
+
+ b = algorithms.data;
+ for (i = 0; i < numAlgorithms; i++) {
+ unsigned char tls_hash = *(b++);
+ unsigned char tls_sig = *(b++);
+ SECOidTag hash = ssl3_TLSHashAlgorithmToOID(tls_hash);
+
+ if (hash == SEC_OID_UNKNOWN) {
+ /* We ignore formats that we don't understand. */
+ continue;
+ }
+ /* tls_sig support will be checked later in
+ * ssl3_PickSignatureHashAlgorithm. */
+ ss->ssl3.hs.clientSigAndHash[i].hashAlg = hash;
+ ss->ssl3.hs.clientSigAndHash[i].sigAlg = tls_sig;
+ ss->ssl3.hs.numClientSigAndHash++;
+ }
+
+ if (!ss->ssl3.hs.numClientSigAndHash) {
+ /* We didn't understand any of the client's requested signature
+ * formats. We'll use the defaults. */
+ PORT_Free(ss->ssl3.hs.clientSigAndHash);
+ ss->ssl3.hs.clientSigAndHash = NULL;
+ }
+
+ return SECSuccess;
+}
+
+/* ssl3_ClientSendSigAlgsXtn sends the signature_algorithm extension for TLS
+ * 1.2 ClientHellos. */
+static PRInt32
+ssl3_ClientSendSigAlgsXtn(sslSocket * ss, PRBool append, PRUint32 maxBytes)
+{
+ static const unsigned char signatureAlgorithms[] = {
+ /* This block is the contents of our signature_algorithms extension, in
+ * wire format. See
+ * https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+ tls_hash_sha256, tls_sig_rsa,
+ tls_hash_sha384, tls_sig_rsa,
+ tls_hash_sha1, tls_sig_rsa,
+#ifdef NSS_ENABLE_ECC
+ tls_hash_sha256, tls_sig_ecdsa,
+ tls_hash_sha384, tls_sig_ecdsa,
+ tls_hash_sha1, tls_sig_ecdsa,
+#endif
+ tls_hash_sha256, tls_sig_dsa,
+ tls_hash_sha1, tls_sig_dsa,
+ };
+ PRInt32 extension_length;
+
+ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_2) {
+ return 0;
+ }
+
+ extension_length =
+ 2 /* extension type */ +
+ 2 /* extension length */ +
+ 2 /* supported_signature_algorithms length */ +
+ sizeof(signatureAlgorithms);
+
+ if (append && maxBytes >= extension_length) {
+ SECStatus rv;
+ rv = ssl3_AppendHandshakeNumber(ss, ssl_signature_algorithms_xtn, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeNumber(ss, extension_length - 4, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ rv = ssl3_AppendHandshakeVariable(ss, signatureAlgorithms,
+ sizeof(signatureAlgorithms), 2);
+ if (rv != SECSuccess)
+ goto loser;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_signature_algorithms_xtn;
+ } else if (maxBytes < extension_length) {
+ PORT_Assert(0);
+ return 0;
+ }
+
+ return extension_length;
+
+loser:
+ return -1;
+}
diff --git a/chromium/net/third_party/nss/ssl/ssl3gthr.c b/chromium/net/third_party/nss/ssl/ssl3gthr.c
new file mode 100644
index 00000000000..6d625152662
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl3gthr.c
@@ -0,0 +1,413 @@
+/*
+ * Gather (Read) entire SSL3 records from socket into buffer.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "ssl3prot.h"
+
+/*
+ * Attempt to read in an entire SSL3 record.
+ * Blocks here for blocking sockets, otherwise returns -1 with
+ * PR_WOULD_BLOCK_ERROR when socket would block.
+ *
+ * returns 1 if received a complete SSL3 record.
+ * returns 0 if recv returns EOF
+ * returns -1 if recv returns < 0
+ * (The error value may have already been set to PR_WOULD_BLOCK_ERROR)
+ *
+ * Caller must hold the recv buf lock.
+ *
+ * The Gather state machine has 3 states: GS_INIT, GS_HEADER, GS_DATA.
+ * GS_HEADER: waiting for the 5-byte SSL3 record header to come in.
+ * GS_DATA: waiting for the body of the SSL3 record to come in.
+ *
+ * This loop returns when either
+ * (a) an error or EOF occurs,
+ * (b) PR_WOULD_BLOCK_ERROR,
+ * (c) data (entire SSL3 record) has been received.
+ */
+static int
+ssl3_GatherData(sslSocket *ss, sslGather *gs, int flags)
+{
+ unsigned char *bp;
+ unsigned char *lbp;
+ int nb;
+ int err;
+ int rv = 1;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ if (gs->state == GS_INIT) {
+ gs->state = GS_HEADER;
+ gs->remainder = 5;
+ gs->offset = 0;
+ gs->writeOffset = 0;
+ gs->readOffset = 0;
+ gs->inbuf.len = 0;
+ }
+
+ lbp = gs->inbuf.buf;
+ for(;;) {
+ SSL_TRC(30, ("%d: SSL3[%d]: gather state %d (need %d more)",
+ SSL_GETPID(), ss->fd, gs->state, gs->remainder));
+ bp = ((gs->state != GS_HEADER) ? lbp : gs->hdr) + gs->offset;
+ nb = ssl_DefRecv(ss, bp, gs->remainder, flags);
+
+ if (nb > 0) {
+ PRINT_BUF(60, (ss, "raw gather data:", bp, nb));
+ } else if (nb == 0) {
+ /* EOF */
+ SSL_TRC(30, ("%d: SSL3[%d]: EOF", SSL_GETPID(), ss->fd));
+ rv = 0;
+ break;
+ } else /* if (nb < 0) */ {
+ SSL_DBG(("%d: SSL3[%d]: recv error %d", SSL_GETPID(), ss->fd,
+ PR_GetError()));
+ rv = SECFailure;
+ break;
+ }
+
+ PORT_Assert( nb <= gs->remainder );
+ if (nb > gs->remainder) {
+ /* ssl_DefRecv is misbehaving! this error is fatal to SSL. */
+ gs->state = GS_INIT; /* so we don't crash next time */
+ rv = SECFailure;
+ break;
+ }
+
+ gs->offset += nb;
+ gs->remainder -= nb;
+ if (gs->state == GS_DATA)
+ gs->inbuf.len += nb;
+
+ /* if there's more to go, read some more. */
+ if (gs->remainder > 0) {
+ continue;
+ }
+
+ /* have received entire record header, or entire record. */
+ switch (gs->state) {
+ case GS_HEADER:
+ /*
+ ** Have received SSL3 record header in gs->hdr.
+ ** Now extract the length of the following encrypted data,
+ ** and then read in the rest of the SSL3 record into gs->inbuf.
+ */
+ gs->remainder = (gs->hdr[3] << 8) | gs->hdr[4];
+
+ /* This is the max fragment length for an encrypted fragment
+ ** plus the size of the record header.
+ */
+ if(gs->remainder > (MAX_FRAGMENT_LENGTH + 2048 + 5)) {
+ SSL3_SendAlert(ss, alert_fatal, unexpected_message);
+ gs->state = GS_INIT;
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ return SECFailure;
+ }
+
+ gs->state = GS_DATA;
+ gs->offset = 0;
+ gs->inbuf.len = 0;
+
+ if (gs->remainder > gs->inbuf.space) {
+ err = sslBuffer_Grow(&gs->inbuf, gs->remainder);
+ if (err) { /* realloc has set error code to no mem. */
+ return err;
+ }
+ lbp = gs->inbuf.buf;
+ }
+ break; /* End this case. Continue around the loop. */
+
+
+ case GS_DATA:
+ /*
+ ** SSL3 record has been completely received.
+ */
+ gs->state = GS_INIT;
+ return 1;
+ }
+ }
+
+ return rv;
+}
+
+/*
+ * Read in an entire DTLS record.
+ *
+ * Blocks here for blocking sockets, otherwise returns -1 with
+ * PR_WOULD_BLOCK_ERROR when socket would block.
+ *
+ * This is simpler than SSL because we are reading on a datagram socket
+ * and datagrams must contain >=1 complete records.
+ *
+ * returns 1 if received a complete DTLS record.
+ * returns 0 if recv returns EOF
+ * returns -1 if recv returns < 0
+ * (The error value may have already been set to PR_WOULD_BLOCK_ERROR)
+ *
+ * Caller must hold the recv buf lock.
+ *
+ * This loop returns when either
+ * (a) an error or EOF occurs,
+ * (b) PR_WOULD_BLOCK_ERROR,
+ * (c) data (entire DTLS record) has been received.
+ */
+static int
+dtls_GatherData(sslSocket *ss, sslGather *gs, int flags)
+{
+ int nb;
+ int err;
+ int rv = 1;
+
+ SSL_TRC(30, ("dtls_GatherData"));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ gs->state = GS_HEADER;
+ gs->offset = 0;
+
+ if (gs->dtlsPacketOffset == gs->dtlsPacket.len) { /* No data left */
+ gs->dtlsPacketOffset = 0;
+ gs->dtlsPacket.len = 0;
+
+ /* Resize to the maximum possible size so we can fit a full datagram */
+ /* This is the max fragment length for an encrypted fragment
+ ** plus the size of the record header.
+ ** This magic constant is copied from ssl3_GatherData, with 5 changed
+ ** to 13 (the size of the record header).
+ */
+ if (gs->dtlsPacket.space < MAX_FRAGMENT_LENGTH + 2048 + 13) {
+ err = sslBuffer_Grow(&gs->dtlsPacket,
+ MAX_FRAGMENT_LENGTH + 2048 + 13);
+ if (err) { /* realloc has set error code to no mem. */
+ return err;
+ }
+ }
+
+ /* recv() needs to read a full datagram at a time */
+ nb = ssl_DefRecv(ss, gs->dtlsPacket.buf, gs->dtlsPacket.space, flags);
+
+ if (nb > 0) {
+ PRINT_BUF(60, (ss, "raw gather data:", gs->dtlsPacket.buf, nb));
+ } else if (nb == 0) {
+ /* EOF */
+ SSL_TRC(30, ("%d: SSL3[%d]: EOF", SSL_GETPID(), ss->fd));
+ rv = 0;
+ return rv;
+ } else /* if (nb < 0) */ {
+ SSL_DBG(("%d: SSL3[%d]: recv error %d", SSL_GETPID(), ss->fd,
+ PR_GetError()));
+ rv = SECFailure;
+ return rv;
+ }
+
+ gs->dtlsPacket.len = nb;
+ }
+
+ /* At this point we should have >=1 complete records lined up in
+ * dtlsPacket. Read off the header.
+ */
+ if ((gs->dtlsPacket.len - gs->dtlsPacketOffset) < 13) {
+ SSL_DBG(("%d: SSL3[%d]: rest of DTLS packet "
+ "too short to contain header", SSL_GETPID(), ss->fd));
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ gs->dtlsPacketOffset = 0;
+ gs->dtlsPacket.len = 0;
+ rv = SECFailure;
+ return rv;
+ }
+ memcpy(gs->hdr, gs->dtlsPacket.buf + gs->dtlsPacketOffset, 13);
+ gs->dtlsPacketOffset += 13;
+
+ /* Have received SSL3 record header in gs->hdr. */
+ gs->remainder = (gs->hdr[11] << 8) | gs->hdr[12];
+
+ if ((gs->dtlsPacket.len - gs->dtlsPacketOffset) < gs->remainder) {
+ SSL_DBG(("%d: SSL3[%d]: rest of DTLS packet too short "
+ "to contain rest of body", SSL_GETPID(), ss->fd));
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ gs->dtlsPacketOffset = 0;
+ gs->dtlsPacket.len = 0;
+ rv = SECFailure;
+ return rv;
+ }
+
+ /* OK, we have at least one complete packet, copy into inbuf */
+ if (gs->remainder > gs->inbuf.space) {
+ err = sslBuffer_Grow(&gs->inbuf, gs->remainder);
+ if (err) { /* realloc has set error code to no mem. */
+ return err;
+ }
+ }
+
+ memcpy(gs->inbuf.buf, gs->dtlsPacket.buf + gs->dtlsPacketOffset,
+ gs->remainder);
+ gs->inbuf.len = gs->remainder;
+ gs->offset = gs->remainder;
+ gs->dtlsPacketOffset += gs->remainder;
+ gs->state = GS_INIT;
+
+ return 1;
+}
+
+/* Gather in a record and when complete, Handle that record.
+ * Repeat this until the handshake is complete,
+ * or until application data is available.
+ *
+ * Returns 1 when the handshake is completed without error, or
+ * application data is available.
+ * Returns 0 if ssl3_GatherData hits EOF.
+ * Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
+ * Returns -2 on SECWouldBlock return from ssl3_HandleRecord.
+ *
+ * Called from ssl_GatherRecord1stHandshake in sslcon.c,
+ * and from SSL_ForceHandshake in sslsecur.c
+ * and from ssl3_GatherAppDataRecord below (<- DoRecv in sslsecur.c).
+ *
+ * Caller must hold the recv buf lock.
+ */
+int
+ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
+{
+ SSL3Ciphertext cText;
+ int rv;
+ PRBool canFalseStart = PR_FALSE;
+
+ SSL_TRC(30, ("ssl3_GatherCompleteHandshake"));
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ do {
+ PRBool handleRecordNow = PR_FALSE;
+
+ ssl_GetSSL3HandshakeLock(ss);
+
+ /* Without this, we may end up wrongly reporting
+ * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the
+ * peer while we are waiting to be restarted.
+ */
+ if (ss->ssl3.hs.restartTarget) {
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ return (int) SECFailure;
+ }
+
+ /* Treat an empty msgState like a NULL msgState. (Most of the time
+ * when ssl3_HandleHandshake returns SECWouldBlock, it leaves
+ * behind a non-NULL but zero-length msgState).
+ * Test: async_cert_restart_server_sends_hello_request_first_in_separate_record
+ */
+ if (ss->ssl3.hs.msgState.buf) {
+ if (ss->ssl3.hs.msgState.len == 0) {
+ ss->ssl3.hs.msgState.buf = NULL;
+ } else {
+ handleRecordNow = PR_TRUE;
+ }
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+
+ if (handleRecordNow) {
+ /* ssl3_HandleHandshake previously returned SECWouldBlock and the
+ * as-yet-unprocessed plaintext of that previous handshake record.
+ * We need to process it now before we overwrite it with the next
+ * handshake record.
+ */
+ rv = ssl3_HandleRecord(ss, NULL, &ss->gs.buf);
+ } else {
+ /* bring in the next sslv3 record. */
+ if (!IS_DTLS(ss)) {
+ rv = ssl3_GatherData(ss, &ss->gs, flags);
+ } else {
+ rv = dtls_GatherData(ss, &ss->gs, flags);
+
+ /* If we got a would block error, that means that no data was
+ * available, so we check the timer to see if it's time to
+ * retransmit */
+ if (rv == SECFailure &&
+ (PORT_GetError() == PR_WOULD_BLOCK_ERROR)) {
+ ssl_GetSSL3HandshakeLock(ss);
+ dtls_CheckTimer(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ /* Restore the error in case something succeeded */
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ }
+ }
+
+ if (rv <= 0) {
+ return rv;
+ }
+
+ /* decipher it, and handle it if it's a handshake.
+ * If it's application data, ss->gs.buf will not be empty upon return.
+ * If it's a change cipher spec, alert, or handshake message,
+ * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess.
+ */
+ cText.type = (SSL3ContentType)ss->gs.hdr[0];
+ cText.version = (ss->gs.hdr[1] << 8) | ss->gs.hdr[2];
+
+ if (IS_DTLS(ss)) {
+ int i;
+
+ cText.version = dtls_DTLSVersionToTLSVersion(cText.version);
+ /* DTLS sequence number */
+ cText.seq_num.high = 0; cText.seq_num.low = 0;
+ for (i = 0; i < 4; i++) {
+ cText.seq_num.high <<= 8; cText.seq_num.low <<= 8;
+ cText.seq_num.high |= ss->gs.hdr[3 + i];
+ cText.seq_num.low |= ss->gs.hdr[7 + i];
+ }
+ }
+
+ cText.buf = &ss->gs.inbuf;
+ rv = ssl3_HandleRecord(ss, &cText, &ss->gs.buf);
+ }
+ if (rv < 0) {
+ return ss->recvdCloseNotify ? 0 : rv;
+ }
+
+ /* If we kicked off a false start in ssl3_HandleServerHelloDone, break
+ * out of this loop early without finishing the handshake.
+ */
+ if (ss->opt.enableFalseStart) {
+ ssl_GetSSL3HandshakeLock(ss);
+ canFalseStart = (ss->ssl3.hs.ws == wait_change_cipher ||
+ ss->ssl3.hs.ws == wait_new_session_ticket) &&
+ ssl3_CanFalseStart(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ }
+ } while (ss->ssl3.hs.ws != idle_handshake &&
+ !canFalseStart &&
+ ss->gs.buf.len == 0);
+
+ ss->gs.readOffset = 0;
+ ss->gs.writeOffset = ss->gs.buf.len;
+ return 1;
+}
+
+/* Repeatedly gather in a record and when complete, Handle that record.
+ * Repeat this until some application data is received.
+ *
+ * Returns 1 when application data is available.
+ * Returns 0 if ssl3_GatherData hits EOF.
+ * Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
+ * Returns -2 on SECWouldBlock return from ssl3_HandleRecord.
+ *
+ * Called from DoRecv in sslsecur.c
+ * Caller must hold the recv buf lock.
+ */
+int
+ssl3_GatherAppDataRecord(sslSocket *ss, int flags)
+{
+ int rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ do {
+ rv = ssl3_GatherCompleteHandshake(ss, flags);
+ } while (rv > 0 && ss->gs.buf.len == 0);
+
+ return rv;
+}
diff --git a/chromium/net/third_party/nss/ssl/ssl3prot.h b/chromium/net/third_party/nss/ssl/ssl3prot.h
new file mode 100644
index 00000000000..0eab970bc6a
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssl3prot.h
@@ -0,0 +1,363 @@
+/* Private header file of libSSL.
+ * Various and sundry protocol constants. DON'T CHANGE THESE. These
+ * values are defined by the SSL 3.0 protocol specification.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __ssl3proto_h_
+#define __ssl3proto_h_
+
+typedef PRUint8 SSL3Opaque;
+
+typedef PRUint16 SSL3ProtocolVersion;
+/* version numbers are defined in sslproto.h */
+
+typedef PRUint16 ssl3CipherSuite;
+/* The cipher suites are defined in sslproto.h */
+
+#define MAX_CERT_TYPES 10
+#define MAX_COMPRESSION_METHODS 10
+#define MAX_MAC_LENGTH 64
+#define MAX_PADDING_LENGTH 64
+#define MAX_KEY_LENGTH 64
+#define EXPORT_KEY_LENGTH 5
+#define SSL3_RANDOM_LENGTH 32
+
+#define SSL3_RECORD_HEADER_LENGTH 5
+
+/* SSL3_RECORD_HEADER_LENGTH + epoch/sequence_number */
+#define DTLS_RECORD_HEADER_LENGTH 13
+
+#define MAX_FRAGMENT_LENGTH 16384
+
+typedef enum {
+ content_change_cipher_spec = 20,
+ content_alert = 21,
+ content_handshake = 22,
+ content_application_data = 23
+} SSL3ContentType;
+
+typedef struct {
+ SSL3ContentType type;
+ SSL3ProtocolVersion version;
+ PRUint16 length;
+ SECItem fragment;
+} SSL3Plaintext;
+
+typedef struct {
+ SSL3ContentType type;
+ SSL3ProtocolVersion version;
+ PRUint16 length;
+ SECItem fragment;
+} SSL3Compressed;
+
+typedef struct {
+ SECItem content;
+ SSL3Opaque MAC[MAX_MAC_LENGTH];
+} SSL3GenericStreamCipher;
+
+typedef struct {
+ SECItem content;
+ SSL3Opaque MAC[MAX_MAC_LENGTH];
+ PRUint8 padding[MAX_PADDING_LENGTH];
+ PRUint8 padding_length;
+} SSL3GenericBlockCipher;
+
+typedef enum { change_cipher_spec_choice = 1 } SSL3ChangeCipherSpecChoice;
+
+typedef struct {
+ SSL3ChangeCipherSpecChoice choice;
+} SSL3ChangeCipherSpec;
+
+typedef enum { alert_warning = 1, alert_fatal = 2 } SSL3AlertLevel;
+
+typedef enum {
+ close_notify = 0,
+ unexpected_message = 10,
+ bad_record_mac = 20,
+ decryption_failed_RESERVED = 21, /* do not send; see RFC 5246 */
+ record_overflow = 22, /* TLS only */
+ decompression_failure = 30,
+ handshake_failure = 40,
+ no_certificate = 41, /* SSL3 only, NOT TLS */
+ bad_certificate = 42,
+ unsupported_certificate = 43,
+ certificate_revoked = 44,
+ certificate_expired = 45,
+ certificate_unknown = 46,
+ illegal_parameter = 47,
+
+/* All alerts below are TLS only. */
+ unknown_ca = 48,
+ access_denied = 49,
+ decode_error = 50,
+ decrypt_error = 51,
+ export_restriction = 60,
+ protocol_version = 70,
+ insufficient_security = 71,
+ internal_error = 80,
+ user_canceled = 90,
+ no_renegotiation = 100,
+
+/* Alerts for client hello extensions */
+ unsupported_extension = 110,
+ certificate_unobtainable = 111,
+ unrecognized_name = 112,
+ bad_certificate_status_response = 113,
+ bad_certificate_hash_value = 114
+
+} SSL3AlertDescription;
+
+typedef struct {
+ SSL3AlertLevel level;
+ SSL3AlertDescription description;
+} SSL3Alert;
+
+typedef enum {
+ hello_request = 0,
+ client_hello = 1,
+ server_hello = 2,
+ hello_verify_request = 3,
+ new_session_ticket = 4,
+ certificate = 11,
+ server_key_exchange = 12,
+ certificate_request = 13,
+ server_hello_done = 14,
+ certificate_verify = 15,
+ client_key_exchange = 16,
+ finished = 20,
+ certificate_status = 22,
+ next_proto = 67,
+ encrypted_extensions= 203
+} SSL3HandshakeType;
+
+typedef struct {
+ PRUint8 empty;
+} SSL3HelloRequest;
+
+typedef struct {
+ SSL3Opaque rand[SSL3_RANDOM_LENGTH];
+} SSL3Random;
+
+typedef struct {
+ SSL3Opaque id[32];
+ PRUint8 length;
+} SSL3SessionID;
+
+typedef struct {
+ SSL3ProtocolVersion client_version;
+ SSL3Random random;
+ SSL3SessionID session_id;
+ SECItem cipher_suites;
+ PRUint8 cm_count;
+ SSLCompressionMethod compression_methods[MAX_COMPRESSION_METHODS];
+} SSL3ClientHello;
+
+typedef struct {
+ SSL3ProtocolVersion server_version;
+ SSL3Random random;
+ SSL3SessionID session_id;
+ ssl3CipherSuite cipher_suite;
+ SSLCompressionMethod compression_method;
+} SSL3ServerHello;
+
+typedef struct {
+ SECItem list;
+} SSL3Certificate;
+
+/* SSL3SignType moved to ssl.h */
+
+/* The SSL key exchange method used */
+typedef enum {
+ kea_null,
+ kea_rsa,
+ kea_rsa_export,
+ kea_rsa_export_1024,
+ kea_dh_dss,
+ kea_dh_dss_export,
+ kea_dh_rsa,
+ kea_dh_rsa_export,
+ kea_dhe_dss,
+ kea_dhe_dss_export,
+ kea_dhe_rsa,
+ kea_dhe_rsa_export,
+ kea_dh_anon,
+ kea_dh_anon_export,
+ kea_rsa_fips,
+ kea_ecdh_ecdsa,
+ kea_ecdhe_ecdsa,
+ kea_ecdh_rsa,
+ kea_ecdhe_rsa,
+ kea_ecdh_anon
+} SSL3KeyExchangeAlgorithm;
+
+typedef struct {
+ SECItem modulus;
+ SECItem exponent;
+} SSL3ServerRSAParams;
+
+typedef struct {
+ SECItem p;
+ SECItem g;
+ SECItem Ys;
+} SSL3ServerDHParams;
+
+typedef struct {
+ union {
+ SSL3ServerDHParams dh;
+ SSL3ServerRSAParams rsa;
+ } u;
+} SSL3ServerParams;
+
+/* This enum reflects HashAlgorithm enum from
+ * https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+ *
+ * When updating, be sure to also update ssl3_TLSHashAlgorithmToOID. */
+enum {
+ tls_hash_md5 = 1,
+ tls_hash_sha1 = 2,
+ tls_hash_sha224 = 3,
+ tls_hash_sha256 = 4,
+ tls_hash_sha384 = 5,
+ tls_hash_sha512 = 6
+};
+
+/* This enum reflects SignatureAlgorithm enum from
+ * https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 */
+typedef enum {
+ tls_sig_rsa = 1,
+ tls_sig_dsa = 2,
+ tls_sig_ecdsa = 3
+} TLSSignatureAlgorithm;
+
+typedef struct {
+ SECOidTag hashAlg;
+ TLSSignatureAlgorithm sigAlg;
+} SSL3SignatureAndHashAlgorithm;
+
+/* SSL3HashesIndividually contains a combination MD5/SHA1 hash, as used in TLS
+ * prior to 1.2. */
+typedef struct {
+ PRUint8 md5[16];
+ PRUint8 sha[20];
+} SSL3HashesIndividually;
+
+/* SSL3Hashes contains an SSL hash value. The digest is contained in |u.raw|
+ * which, if |hashAlg==SEC_OID_UNKNOWN| is also a SSL3HashesIndividually
+ * struct. */
+typedef struct {
+ unsigned int len;
+ SECOidTag hashAlg;
+ union {
+ PRUint8 raw[64];
+ SSL3HashesIndividually s;
+ } u;
+} SSL3Hashes;
+
+typedef struct {
+ union {
+ SSL3Opaque anonymous;
+ SSL3Hashes certified;
+ } u;
+} SSL3ServerKeyExchange;
+
+typedef enum {
+ ct_RSA_sign = 1,
+ ct_DSS_sign = 2,
+ ct_RSA_fixed_DH = 3,
+ ct_DSS_fixed_DH = 4,
+ ct_RSA_ephemeral_DH = 5,
+ ct_DSS_ephemeral_DH = 6,
+ ct_ECDSA_sign = 64,
+ ct_RSA_fixed_ECDH = 65,
+ ct_ECDSA_fixed_ECDH = 66
+
+} SSL3ClientCertificateType;
+
+typedef SECItem *SSL3DistinquishedName;
+
+typedef struct {
+ SSL3Opaque client_version[2];
+ SSL3Opaque random[46];
+} SSL3RSAPreMasterSecret;
+
+typedef SECItem SSL3EncryptedPreMasterSecret;
+
+
+typedef SSL3Opaque SSL3MasterSecret[48];
+
+typedef enum { implicit, explicit } SSL3PublicValueEncoding;
+
+typedef struct {
+ union {
+ SSL3Opaque implicit;
+ SECItem explicit;
+ } dh_public;
+} SSL3ClientDiffieHellmanPublic;
+
+typedef struct {
+ union {
+ SSL3EncryptedPreMasterSecret rsa;
+ SSL3ClientDiffieHellmanPublic diffie_helman;
+ } exchange_keys;
+} SSL3ClientKeyExchange;
+
+typedef SSL3Hashes SSL3PreSignedCertificateVerify;
+
+typedef SECItem SSL3CertificateVerify;
+
+typedef enum {
+ sender_client = 0x434c4e54,
+ sender_server = 0x53525652
+} SSL3Sender;
+
+typedef SSL3HashesIndividually SSL3Finished;
+
+typedef struct {
+ SSL3Opaque verify_data[12];
+} TLSFinished;
+
+/*
+ * TLS extension related data structures and constants.
+ */
+
+/* SessionTicket extension related data structures. */
+
+/* NewSessionTicket handshake message. */
+typedef struct {
+ PRUint32 received_timestamp;
+ PRUint32 ticket_lifetime_hint;
+ SECItem ticket;
+} NewSessionTicket;
+
+typedef enum {
+ CLIENT_AUTH_ANONYMOUS = 0,
+ CLIENT_AUTH_CERTIFICATE = 1
+} ClientAuthenticationType;
+
+typedef struct {
+ ClientAuthenticationType client_auth_type;
+ union {
+ SSL3Opaque *certificate_list;
+ } identity;
+} ClientIdentity;
+
+#define SESS_TICKET_KEY_NAME_LEN 16
+#define SESS_TICKET_KEY_NAME_PREFIX "NSS!"
+#define SESS_TICKET_KEY_NAME_PREFIX_LEN 4
+#define SESS_TICKET_KEY_VAR_NAME_LEN 12
+
+typedef struct {
+ unsigned char *key_name;
+ unsigned char *iv;
+ SECItem encrypted_state;
+ unsigned char *mac;
+} EncryptedSessionTicket;
+
+#define TLS_EX_SESS_TICKET_MAC_LENGTH 32
+
+#define TLS_STE_NO_SERVER_NAME -1
+
+#endif /* __ssl3proto_h_ */
diff --git a/chromium/net/third_party/nss/ssl/sslauth.c b/chromium/net/third_party/nss/ssl/sslauth.c
new file mode 100644
index 00000000000..8e818decd85
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslauth.c
@@ -0,0 +1,332 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "cert.h"
+#include "secitem.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "pk11func.h"
+#include "ocsp.h"
+
+/* NEED LOCKS IN HERE. */
+CERTCertificate *
+SSL_PeerCertificate(PRFileDesc *fd)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in PeerCertificate",
+ SSL_GETPID(), fd));
+ return 0;
+ }
+ if (ss->opt.useSecurity && ss->sec.peerCert) {
+ return CERT_DupCertificate(ss->sec.peerCert);
+ }
+ return 0;
+}
+
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_PeerCertificateChain(PRFileDesc *fd, CERTCertificate **certs,
+ unsigned int *numCerts, unsigned int maxNumCerts)
+{
+ sslSocket *ss;
+ ssl3CertNode* cur;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in PeerCertificateChain",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ if (!ss->opt.useSecurity)
+ return SECFailure;
+
+ if (ss->sec.peerCert == NULL) {
+ *numCerts = 0;
+ return SECSuccess;
+ }
+
+ *numCerts = 1; /* for the leaf certificate */
+ if (maxNumCerts > 0)
+ certs[0] = CERT_DupCertificate(ss->sec.peerCert);
+
+ for (cur = ss->ssl3.peerCertChain; cur; cur = cur->next) {
+ if (*numCerts < maxNumCerts)
+ certs[*numCerts] = CERT_DupCertificate(cur->cert);
+ (*numCerts)++;
+ }
+
+ return SECSuccess;
+}
+
+/* NEED LOCKS IN HERE. */
+CERTCertificate *
+SSL_LocalCertificate(PRFileDesc *fd)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in PeerCertificate",
+ SSL_GETPID(), fd));
+ return NULL;
+ }
+ if (ss->opt.useSecurity) {
+ if (ss->sec.localCert) {
+ return CERT_DupCertificate(ss->sec.localCert);
+ }
+ if (ss->sec.ci.sid && ss->sec.ci.sid->localCert) {
+ return CERT_DupCertificate(ss->sec.ci.sid->localCert);
+ }
+ }
+ return NULL;
+}
+
+
+
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_SecurityStatus(PRFileDesc *fd, int *op, char **cp, int *kp0, int *kp1,
+ char **ip, char **sp)
+{
+ sslSocket *ss;
+ const char *cipherName;
+ PRBool isDes = PR_FALSE;
+ PRBool enoughFirstHsDone = PR_FALSE;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SecurityStatus",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (cp) *cp = 0;
+ if (kp0) *kp0 = 0;
+ if (kp1) *kp1 = 0;
+ if (ip) *ip = 0;
+ if (sp) *sp = 0;
+ if (op) {
+ *op = SSL_SECURITY_STATUS_OFF;
+ }
+
+ if (ss->firstHsDone) {
+ enoughFirstHsDone = PR_TRUE;
+ } else if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ ssl3_CanFalseStart(ss)) {
+ enoughFirstHsDone = PR_TRUE;
+ }
+
+ if (ss->opt.useSecurity && enoughFirstHsDone) {
+ if (ss->version < SSL_LIBRARY_VERSION_3_0) {
+ cipherName = ssl_cipherName[ss->sec.cipherType];
+ } else {
+ cipherName = ssl3_cipherName[ss->sec.cipherType];
+ }
+ PORT_Assert(cipherName);
+ if (cipherName) {
+ if (PORT_Strstr(cipherName, "DES")) isDes = PR_TRUE;
+
+ if (cp) {
+ *cp = PORT_Strdup(cipherName);
+ }
+ }
+
+ if (kp0) {
+ *kp0 = ss->sec.keyBits;
+ if (isDes) *kp0 = (*kp0 * 7) / 8;
+ }
+ if (kp1) {
+ *kp1 = ss->sec.secretKeyBits;
+ if (isDes) *kp1 = (*kp1 * 7) / 8;
+ }
+ if (op) {
+ if (ss->sec.keyBits == 0) {
+ *op = SSL_SECURITY_STATUS_OFF;
+ } else if (ss->sec.secretKeyBits < 90) {
+ *op = SSL_SECURITY_STATUS_ON_LOW;
+
+ } else {
+ *op = SSL_SECURITY_STATUS_ON_HIGH;
+ }
+ }
+
+ if (ip || sp) {
+ CERTCertificate *cert;
+
+ cert = ss->sec.peerCert;
+ if (cert) {
+ if (ip) {
+ *ip = CERT_NameToAscii(&cert->issuer);
+ }
+ if (sp) {
+ *sp = CERT_NameToAscii(&cert->subject);
+ }
+ } else {
+ if (ip) {
+ *ip = PORT_Strdup("no certificate");
+ }
+ if (sp) {
+ *sp = PORT_Strdup("no certificate");
+ }
+ }
+ }
+ }
+
+ return SECSuccess;
+}
+
+/************************************************************************/
+
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_AuthCertificateHook(PRFileDesc *s, SSLAuthCertificate func, void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(s);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in AuthCertificateHook",
+ SSL_GETPID(), s));
+ return SECFailure;
+ }
+
+ ss->authCertificate = func;
+ ss->authCertificateArg = arg;
+
+ return SECSuccess;
+}
+
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_GetClientAuthDataHook(PRFileDesc *s, SSLGetClientAuthData func,
+ void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(s);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in GetClientAuthDataHook",
+ SSL_GETPID(), s));
+ return SECFailure;
+ }
+
+ ss->getClientAuthData = func;
+ ss->getClientAuthDataArg = arg;
+ return SECSuccess;
+}
+
+SECStatus
+SSL_SetClientChannelIDCallback(PRFileDesc *fd,
+ SSLClientChannelIDCallback callback,
+ void *arg) {
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetClientChannelIDCallback",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ss->getChannelID = callback;
+ ss->getChannelIDArg = arg;
+
+ return SECSuccess;
+}
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_GetPlatformClientAuthDataHook(PRFileDesc *s,
+ SSLGetPlatformClientAuthData func,
+ void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(s);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in GetPlatformClientAuthDataHook",
+ SSL_GETPID(), s));
+ return SECFailure;
+ }
+
+ ss->getPlatformClientAuthData = func;
+ ss->getPlatformClientAuthDataArg = arg;
+ return SECSuccess;
+}
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+/* NEED LOCKS IN HERE. */
+SECStatus
+SSL_SetPKCS11PinArg(PRFileDesc *s, void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(s);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in GetClientAuthDataHook",
+ SSL_GETPID(), s));
+ return SECFailure;
+ }
+
+ ss->pkcs11PinArg = arg;
+ return SECSuccess;
+}
+
+
+/* This is the "default" authCert callback function. It is called when a
+ * certificate message is received from the peer and the local application
+ * has not registered an authCert callback function.
+ */
+SECStatus
+SSL_AuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer)
+{
+ SECStatus rv;
+ CERTCertDBHandle * handle;
+ sslSocket * ss;
+ SECCertUsage certUsage;
+ const char * hostname = NULL;
+ PRTime now = PR_Now();
+ SECItemArray * certStatusArray;
+
+ ss = ssl_FindSocket(fd);
+ PORT_Assert(ss != NULL);
+ if (!ss) {
+ return SECFailure;
+ }
+
+ handle = (CERTCertDBHandle *)arg;
+ certStatusArray = &ss->sec.ci.sid->peerCertStatus;
+
+ if (certStatusArray->len) {
+ CERT_CacheOCSPResponseFromSideChannel(handle, ss->sec.peerCert,
+ now, &certStatusArray->items[0],
+ ss->pkcs11PinArg);
+ }
+
+ /* this may seem backwards, but isn't. */
+ certUsage = isServer ? certUsageSSLClient : certUsageSSLServer;
+
+ rv = CERT_VerifyCert(handle, ss->sec.peerCert, checkSig, certUsage,
+ now, ss->pkcs11PinArg, NULL);
+
+ if ( rv != SECSuccess || isServer )
+ return rv;
+
+ /* cert is OK. This is the client side of an SSL connection.
+ * Now check the name field in the cert against the desired hostname.
+ * NB: This is our only defense against Man-In-The-Middle (MITM) attacks!
+ */
+ hostname = ss->url;
+ if (hostname && hostname[0])
+ rv = CERT_VerifyCertName(ss->sec.peerCert, hostname);
+ else
+ rv = SECFailure;
+ if (rv != SECSuccess)
+ PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
+
+ return rv;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslcon.c b/chromium/net/third_party/nss/ssl/sslcon.c
new file mode 100644
index 00000000000..2fc6602a2b6
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslcon.c
@@ -0,0 +1,3696 @@
+/*
+ * SSL v2 handshake functions, and functions common to SSL2 and SSL3.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nssrenam.h"
+#include "cert.h"
+#include "secitem.h"
+#include "sechash.h"
+#include "cryptohi.h" /* for SGN_ funcs */
+#include "keyhi.h" /* for SECKEY_ high level functions. */
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "ssl3prot.h"
+#include "sslerr.h"
+#include "pk11func.h"
+#include "prinit.h"
+#include "prtime.h" /* for PR_Now() */
+
+#define XXX
+static PRBool policyWasSet;
+
+/* This ordered list is indexed by (SSL_CK_xx * 3) */
+/* Second and third bytes are MSB and LSB of master key length. */
+static const PRUint8 allCipherSuites[] = {
+ 0, 0, 0,
+ SSL_CK_RC4_128_WITH_MD5, 0x00, 0x80,
+ SSL_CK_RC4_128_EXPORT40_WITH_MD5, 0x00, 0x80,
+ SSL_CK_RC2_128_CBC_WITH_MD5, 0x00, 0x80,
+ SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80,
+ SSL_CK_IDEA_128_CBC_WITH_MD5, 0x00, 0x80,
+ SSL_CK_DES_64_CBC_WITH_MD5, 0x00, 0x40,
+ SSL_CK_DES_192_EDE3_CBC_WITH_MD5, 0x00, 0xC0,
+ 0, 0, 0
+};
+
+#define ssl2_NUM_SUITES_IMPLEMENTED 6
+
+/* This list is sent back to the client when the client-hello message
+ * contains no overlapping ciphers, so the client can report what ciphers
+ * are supported by the server. Unlike allCipherSuites (above), this list
+ * is sorted by descending preference, not by cipherSuite number.
+ */
+static const PRUint8 implementedCipherSuites[ssl2_NUM_SUITES_IMPLEMENTED * 3] = {
+ SSL_CK_RC4_128_WITH_MD5, 0x00, 0x80,
+ SSL_CK_RC2_128_CBC_WITH_MD5, 0x00, 0x80,
+ SSL_CK_DES_192_EDE3_CBC_WITH_MD5, 0x00, 0xC0,
+ SSL_CK_DES_64_CBC_WITH_MD5, 0x00, 0x40,
+ SSL_CK_RC4_128_EXPORT40_WITH_MD5, 0x00, 0x80,
+ SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, 0x00, 0x80
+};
+
+typedef struct ssl2SpecsStr {
+ PRUint8 nkm; /* do this many hashes to generate key material. */
+ PRUint8 nkd; /* size of readKey and writeKey in bytes. */
+ PRUint8 blockSize;
+ PRUint8 blockShift;
+ CK_MECHANISM_TYPE mechanism;
+ PRUint8 keyLen; /* cipher symkey size in bytes. */
+ PRUint8 pubLen; /* publicly reveal this many bytes of key. */
+ PRUint8 ivLen; /* length of IV data at *ca. */
+} ssl2Specs;
+
+static const ssl2Specs ssl_Specs[] = {
+/* NONE */
+ { 0, 0, 0, 0, },
+/* SSL_CK_RC4_128_WITH_MD5 */
+ { 2, 16, 1, 0, CKM_RC4, 16, 0, 0, },
+/* SSL_CK_RC4_128_EXPORT40_WITH_MD5 */
+ { 2, 16, 1, 0, CKM_RC4, 16, 11, 0, },
+/* SSL_CK_RC2_128_CBC_WITH_MD5 */
+ { 2, 16, 8, 3, CKM_RC2_CBC, 16, 0, 8, },
+/* SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 */
+ { 2, 16, 8, 3, CKM_RC2_CBC, 16, 11, 8, },
+/* SSL_CK_IDEA_128_CBC_WITH_MD5 */
+ { 0, 0, 0, 0, },
+/* SSL_CK_DES_64_CBC_WITH_MD5 */
+ { 1, 8, 8, 3, CKM_DES_CBC, 8, 0, 8, },
+/* SSL_CK_DES_192_EDE3_CBC_WITH_MD5 */
+ { 3, 24, 8, 3, CKM_DES3_CBC, 24, 0, 8, },
+};
+
+#define SET_ERROR_CODE /* reminder */
+#define TEST_FOR_FAILURE /* reminder */
+
+/*
+** Put a string tag in the library so that we can examine an executable
+** and see what kind of security it supports.
+*/
+const char *ssl_version = "SECURITY_VERSION:"
+ " +us"
+ " +export"
+#ifdef TRACE
+ " +trace"
+#endif
+#ifdef DEBUG
+ " +debug"
+#endif
+ ;
+
+const char * const ssl_cipherName[] = {
+ "unknown",
+ "RC4",
+ "RC4-Export",
+ "RC2-CBC",
+ "RC2-CBC-Export",
+ "IDEA-CBC",
+ "DES-CBC",
+ "DES-EDE3-CBC",
+ "unknown",
+ "unknown", /* was fortezza, NO LONGER USED */
+};
+
+
+/* bit-masks, showing which SSLv2 suites are allowed.
+ * lsb corresponds to first cipher suite in allCipherSuites[].
+ */
+static PRUint16 allowedByPolicy; /* all off by default */
+static PRUint16 maybeAllowedByPolicy; /* all off by default */
+static PRUint16 chosenPreference = 0xff; /* all on by default */
+
+/* bit values for the above two bit masks */
+#define SSL_CB_RC4_128_WITH_MD5 (1 << SSL_CK_RC4_128_WITH_MD5)
+#define SSL_CB_RC4_128_EXPORT40_WITH_MD5 (1 << SSL_CK_RC4_128_EXPORT40_WITH_MD5)
+#define SSL_CB_RC2_128_CBC_WITH_MD5 (1 << SSL_CK_RC2_128_CBC_WITH_MD5)
+#define SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 (1 << SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5)
+#define SSL_CB_IDEA_128_CBC_WITH_MD5 (1 << SSL_CK_IDEA_128_CBC_WITH_MD5)
+#define SSL_CB_DES_64_CBC_WITH_MD5 (1 << SSL_CK_DES_64_CBC_WITH_MD5)
+#define SSL_CB_DES_192_EDE3_CBC_WITH_MD5 (1 << SSL_CK_DES_192_EDE3_CBC_WITH_MD5)
+#define SSL_CB_IMPLEMENTED \
+ (SSL_CB_RC4_128_WITH_MD5 | \
+ SSL_CB_RC4_128_EXPORT40_WITH_MD5 | \
+ SSL_CB_RC2_128_CBC_WITH_MD5 | \
+ SSL_CB_RC2_128_CBC_EXPORT40_WITH_MD5 | \
+ SSL_CB_DES_64_CBC_WITH_MD5 | \
+ SSL_CB_DES_192_EDE3_CBC_WITH_MD5)
+
+
+/* Construct a socket's list of cipher specs from the global default values.
+ */
+static SECStatus
+ssl2_ConstructCipherSpecs(sslSocket *ss)
+{
+ PRUint8 * cs = NULL;
+ unsigned int allowed;
+ unsigned int count;
+ int ssl3_count = 0;
+ int final_count;
+ int i;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ count = 0;
+ PORT_Assert(ss != 0);
+ allowed = !ss->opt.enableSSL2 ? 0 :
+ (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED);
+ while (allowed) {
+ if (allowed & 1)
+ ++count;
+ allowed >>= 1;
+ }
+
+ /* Call ssl3_config_match_init() once here,
+ * instead of inside ssl3_ConstructV2CipherSpecsHack(),
+ * because the latter gets called twice below,
+ * and then again in ssl2_BeginClientHandshake().
+ */
+ ssl3_config_match_init(ss);
+
+ /* ask SSL3 how many cipher suites it has. */
+ rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3_count);
+ if (rv < 0)
+ return rv;
+ count += ssl3_count;
+
+ /* Allocate memory to hold cipher specs */
+ if (count > 0)
+ cs = (PRUint8*) PORT_Alloc(count * 3);
+ else
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ if (cs == NULL)
+ return SECFailure;
+
+ if (ss->cipherSpecs != NULL) {
+ PORT_Free(ss->cipherSpecs);
+ }
+ ss->cipherSpecs = cs;
+ ss->sizeCipherSpecs = count * 3;
+
+ /* fill in cipher specs for SSL2 cipher suites */
+ allowed = !ss->opt.enableSSL2 ? 0 :
+ (ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED);
+ for (i = 0; i < ssl2_NUM_SUITES_IMPLEMENTED * 3; i += 3) {
+ const PRUint8 * hs = implementedCipherSuites + i;
+ int ok = allowed & (1U << hs[0]);
+ if (ok) {
+ cs[0] = hs[0];
+ cs[1] = hs[1];
+ cs[2] = hs[2];
+ cs += 3;
+ }
+ }
+
+ /* now have SSL3 add its suites onto the end */
+ rv = ssl3_ConstructV2CipherSpecsHack(ss, cs, &final_count);
+
+ /* adjust for any difference between first pass and second pass */
+ ss->sizeCipherSpecs -= (ssl3_count - final_count) * 3;
+
+ return rv;
+}
+
+/* This function is called immediately after ssl2_ConstructCipherSpecs()
+** at the beginning of a handshake. It detects cases where a protocol
+** (e.g. SSL2 or SSL3) is logically enabled, but all its cipher suites
+** for that protocol have been disabled. If such cases, it clears the
+** enable bit for the protocol. If no protocols remain enabled, or
+** if no cipher suites are found, it sets the error code and returns
+** SECFailure, otherwise it returns SECSuccess.
+*/
+static SECStatus
+ssl2_CheckConfigSanity(sslSocket *ss)
+{
+ unsigned int allowed;
+ int ssl3CipherCount = 0;
+ SECStatus rv;
+
+ /* count the SSL2 and SSL3 enabled ciphers.
+ * if either is zero, clear the socket's enable for that protocol.
+ */
+ if (!ss->cipherSpecs)
+ goto disabled;
+
+ allowed = ss->allowedByPolicy & ss->chosenPreference;
+ if (! allowed)
+ ss->opt.enableSSL2 = PR_FALSE; /* not really enabled if no ciphers */
+
+ /* ssl3_config_match_init was called in ssl2_ConstructCipherSpecs(). */
+ /* Ask how many ssl3 CipherSuites were enabled. */
+ rv = ssl3_ConstructV2CipherSpecsHack(ss, NULL, &ssl3CipherCount);
+ if (rv != SECSuccess || ssl3CipherCount <= 0) {
+ /* SSL3/TLS not really enabled if no ciphers */
+ ss->vrange.min = SSL_LIBRARY_VERSION_NONE;
+ ss->vrange.max = SSL_LIBRARY_VERSION_NONE;
+ }
+
+ if (!ss->opt.enableSSL2 && SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ SSL_DBG(("%d: SSL[%d]: Can't handshake! all versions disabled.",
+ SSL_GETPID(), ss->fd));
+disabled:
+ PORT_SetError(SSL_ERROR_SSL_DISABLED);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+/*
+ * Since this is a global (not per-socket) setting, we cannot use the
+ * HandshakeLock to protect this. Probably want a global lock.
+ */
+SECStatus
+ssl2_SetPolicy(PRInt32 which, PRInt32 policy)
+{
+ PRUint32 bitMask;
+ SECStatus rv = SECSuccess;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ return SECFailure;
+ }
+
+ if (policy == SSL_ALLOWED) {
+ allowedByPolicy |= bitMask;
+ maybeAllowedByPolicy |= bitMask;
+ } else if (policy == SSL_RESTRICTED) {
+ allowedByPolicy &= ~bitMask;
+ maybeAllowedByPolicy |= bitMask;
+ } else {
+ allowedByPolicy &= ~bitMask;
+ maybeAllowedByPolicy &= ~bitMask;
+ }
+ allowedByPolicy &= SSL_CB_IMPLEMENTED;
+ maybeAllowedByPolicy &= SSL_CB_IMPLEMENTED;
+
+ policyWasSet = PR_TRUE;
+ return rv;
+}
+
+SECStatus
+ssl2_GetPolicy(PRInt32 which, PRInt32 *oPolicy)
+{
+ PRUint32 bitMask;
+ PRInt32 policy;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ /* Caller assures oPolicy is not null. */
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ *oPolicy = SSL_NOT_ALLOWED;
+ return SECFailure;
+ }
+
+ if (maybeAllowedByPolicy & bitMask) {
+ policy = (allowedByPolicy & bitMask) ? SSL_ALLOWED : SSL_RESTRICTED;
+ } else {
+ policy = SSL_NOT_ALLOWED;
+ }
+
+ *oPolicy = policy;
+ return SECSuccess;
+}
+
+/*
+ * Since this is a global (not per-socket) setting, we cannot use the
+ * HandshakeLock to protect this. Probably want a global lock.
+ * Called from SSL_CipherPrefSetDefault in sslsock.c
+ * These changes have no effect on any sslSockets already created.
+ */
+SECStatus
+ssl2_CipherPrefSetDefault(PRInt32 which, PRBool enabled)
+{
+ PRUint32 bitMask;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ return SECFailure;
+ }
+
+ if (enabled)
+ chosenPreference |= bitMask;
+ else
+ chosenPreference &= ~bitMask;
+ chosenPreference &= SSL_CB_IMPLEMENTED;
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl2_CipherPrefGetDefault(PRInt32 which, PRBool *enabled)
+{
+ PRBool rv = PR_FALSE;
+ PRUint32 bitMask;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ *enabled = PR_FALSE;
+ return SECFailure;
+ }
+
+ rv = (PRBool)((chosenPreference & bitMask) != 0);
+ *enabled = rv;
+ return SECSuccess;
+}
+
+SECStatus
+ssl2_CipherPrefSet(sslSocket *ss, PRInt32 which, PRBool enabled)
+{
+ PRUint32 bitMask;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ return SECFailure;
+ }
+
+ if (enabled)
+ ss->chosenPreference |= bitMask;
+ else
+ ss->chosenPreference &= ~bitMask;
+ ss->chosenPreference &= SSL_CB_IMPLEMENTED;
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl2_CipherPrefGet(sslSocket *ss, PRInt32 which, PRBool *enabled)
+{
+ PRBool rv = PR_FALSE;
+ PRUint32 bitMask;
+
+ which &= 0x000f;
+ bitMask = 1 << which;
+
+ if (!(bitMask & SSL_CB_IMPLEMENTED)) {
+ PORT_SetError(SSL_ERROR_UNKNOWN_CIPHER_SUITE);
+ *enabled = PR_FALSE;
+ return SECFailure;
+ }
+
+ rv = (PRBool)((ss->chosenPreference & bitMask) != 0);
+ *enabled = rv;
+ return SECSuccess;
+}
+
+
+/* copy global default policy into socket. */
+void
+ssl2_InitSocketPolicy(sslSocket *ss)
+{
+ ss->allowedByPolicy = allowedByPolicy;
+ ss->maybeAllowedByPolicy = maybeAllowedByPolicy;
+ ss->chosenPreference = chosenPreference;
+}
+
+
+/************************************************************************/
+
+/* Called from ssl2_CreateSessionCypher(), which already holds handshake lock.
+ */
+static SECStatus
+ssl2_CreateMAC(sslSecurityInfo *sec, SECItem *readKey, SECItem *writeKey,
+ int cipherChoice)
+{
+ switch (cipherChoice) {
+
+ case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_WITH_MD5:
+ case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
+ case SSL_CK_RC4_128_WITH_MD5:
+ case SSL_CK_DES_64_CBC_WITH_MD5:
+ case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
+ sec->hash = HASH_GetHashObject(HASH_AlgMD5);
+ SECITEM_CopyItem(0, &sec->sendSecret, writeKey);
+ SECITEM_CopyItem(0, &sec->rcvSecret, readKey);
+ break;
+
+ default:
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+ sec->hashcx = (*sec->hash->create)();
+ if (sec->hashcx == NULL)
+ return SECFailure;
+ return SECSuccess;
+}
+
+/************************************************************************
+ * All the Send functions below must acquire and release the socket's
+ * xmitBufLock.
+ */
+
+/* Called from all the Send* functions below. */
+static SECStatus
+ssl2_GetSendBuffer(sslSocket *ss, unsigned int len)
+{
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
+
+ if (len < 128) {
+ len = 128;
+ }
+ if (len > ss->sec.ci.sendBuf.space) {
+ rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, len);
+ if (rv != SECSuccess) {
+ SSL_DBG(("%d: SSL[%d]: ssl2_GetSendBuffer failed, tried to get %d bytes",
+ SSL_GETPID(), ss->fd, len));
+ rv = SECFailure;
+ }
+ }
+ return rv;
+}
+
+/* Called from:
+ * ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage()
+ * ssl2_HandleRequestCertificate() <- ssl2_HandleMessage() <-
+ ssl_Do1stHandshake()
+ * ssl2_HandleMessage() <- ssl_Do1stHandshake()
+ * ssl2_HandleServerHelloMessage() <- ssl_Do1stHandshake()
+ after ssl2_BeginClientHandshake()
+ * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake()
+ after ssl2_BeginServerHandshake()
+ *
+ * Acquires and releases the socket's xmitBufLock.
+ */
+int
+ssl2_SendErrorMessage(sslSocket *ss, int error)
+{
+ int rv;
+ PRUint8 msg[SSL_HL_ERROR_HBYTES];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ msg[0] = SSL_MT_ERROR;
+ msg[1] = MSB(error);
+ msg[2] = LSB(error);
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending error %d", SSL_GETPID(), ss->fd, error));
+
+ ss->handshakeBegun = 1;
+ rv = (*ss->sec.send)(ss, msg, sizeof(msg), 0);
+ if (rv >= 0) {
+ rv = SECSuccess;
+ }
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from ssl2_TryToFinish().
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static SECStatus
+ssl2_SendClientFinishedMessage(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+ int sent;
+ PRUint8 msg[1 + SSL_CONNECTIONID_BYTES];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ if (ss->sec.ci.sentFinished == 0) {
+ ss->sec.ci.sentFinished = 1;
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending client-finished",
+ SSL_GETPID(), ss->fd));
+
+ msg[0] = SSL_MT_CLIENT_FINISHED;
+ PORT_Memcpy(msg+1, ss->sec.ci.connectionID,
+ sizeof(ss->sec.ci.connectionID));
+
+ DUMP_MSG(29, (ss, msg, 1 + sizeof(ss->sec.ci.connectionID)));
+ sent = (*ss->sec.send)(ss, msg, 1 + sizeof(ss->sec.ci.connectionID), 0);
+ rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
+ }
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from
+ * ssl2_HandleClientSessionKeyMessage() <- ssl2_HandleClientHelloMessage()
+ * ssl2_HandleClientHelloMessage() <- ssl_Do1stHandshake()
+ after ssl2_BeginServerHandshake()
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static SECStatus
+ssl2_SendServerVerifyMessage(sslSocket *ss)
+{
+ PRUint8 * msg;
+ int sendLen;
+ int sent;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ sendLen = 1 + SSL_CHALLENGE_BYTES;
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv != SECSuccess) {
+ goto done;
+ }
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_SERVER_VERIFY;
+ PORT_Memcpy(msg+1, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+ sent = (*ss->sec.send)(ss, msg, sendLen, 0);
+
+ rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
+
+done:
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from ssl2_TryToFinish().
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static SECStatus
+ssl2_SendServerFinishedMessage(sslSocket *ss)
+{
+ sslSessionID * sid;
+ PRUint8 * msg;
+ int sendLen, sent;
+ SECStatus rv = SECSuccess;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ if (ss->sec.ci.sentFinished == 0) {
+ ss->sec.ci.sentFinished = 1;
+ PORT_Assert(ss->sec.ci.sid != 0);
+ sid = ss->sec.ci.sid;
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending server-finished",
+ SSL_GETPID(), ss->fd));
+
+ sendLen = 1 + sizeof(sid->u.ssl2.sessionID);
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv != SECSuccess) {
+ goto done;
+ }
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_SERVER_FINISHED;
+ PORT_Memcpy(msg+1, sid->u.ssl2.sessionID,
+ sizeof(sid->u.ssl2.sessionID));
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+ sent = (*ss->sec.send)(ss, msg, sendLen, 0);
+
+ if (sent < 0) {
+ /* If send failed, it is now a bogus session-id */
+ if (ss->sec.uncache)
+ (*ss->sec.uncache)(sid);
+ rv = (SECStatus)sent;
+ } else if (!ss->opt.noCache) {
+ /* Put the sid in session-id cache, (may already be there) */
+ (*ss->sec.cache)(sid);
+ rv = SECSuccess;
+ }
+ ssl_FreeSID(sid);
+ ss->sec.ci.sid = 0;
+ }
+done:
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from ssl2_ClientSetupSessionCypher() <-
+ * ssl2_HandleServerHelloMessage()
+ * after ssl2_BeginClientHandshake()
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static SECStatus
+ssl2_SendSessionKeyMessage(sslSocket *ss, int cipher, int keySize,
+ PRUint8 *ca, int caLen,
+ PRUint8 *ck, int ckLen,
+ PRUint8 *ek, int ekLen)
+{
+ PRUint8 * msg;
+ int sendLen;
+ int sent;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ sendLen = SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen;
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv != SECSuccess)
+ goto done;
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending client-session-key",
+ SSL_GETPID(), ss->fd));
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_CLIENT_MASTER_KEY;
+ msg[1] = cipher;
+ msg[2] = MSB(keySize);
+ msg[3] = LSB(keySize);
+ msg[4] = MSB(ckLen);
+ msg[5] = LSB(ckLen);
+ msg[6] = MSB(ekLen);
+ msg[7] = LSB(ekLen);
+ msg[8] = MSB(caLen);
+ msg[9] = LSB(caLen);
+ PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES, ck, ckLen);
+ PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen, ek, ekLen);
+ PORT_Memcpy(msg+SSL_HL_CLIENT_MASTER_KEY_HBYTES+ckLen+ekLen, ca, caLen);
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+ sent = (*ss->sec.send)(ss, msg, sendLen, 0);
+ rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
+done:
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from ssl2_TriggerNextMessage() <- ssl2_HandleMessage()
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static SECStatus
+ssl2_SendCertificateRequestMessage(sslSocket *ss)
+{
+ PRUint8 * msg;
+ int sent;
+ int sendLen;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ sendLen = SSL_HL_REQUEST_CERTIFICATE_HBYTES + SSL_CHALLENGE_BYTES;
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv != SECSuccess)
+ goto done;
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending certificate request",
+ SSL_GETPID(), ss->fd));
+
+ /* Generate random challenge for client to encrypt */
+ PK11_GenerateRandom(ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_REQUEST_CERTIFICATE;
+ msg[1] = SSL_AT_MD5_WITH_RSA_ENCRYPTION;
+ PORT_Memcpy(msg + SSL_HL_REQUEST_CERTIFICATE_HBYTES,
+ ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+ sent = (*ss->sec.send)(ss, msg, sendLen, 0);
+ rv = (sent >= 0) ? SECSuccess : (SECStatus)sent;
+done:
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/* Called from ssl2_HandleRequestCertificate() <- ssl2_HandleMessage()
+ * Acquires and releases the socket's xmitBufLock.
+ */
+static int
+ssl2_SendCertificateResponseMessage(sslSocket *ss, SECItem *cert,
+ SECItem *encCode)
+{
+ PRUint8 *msg;
+ int rv, sendLen;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ sendLen = SSL_HL_CLIENT_CERTIFICATE_HBYTES + encCode->len + cert->len;
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv)
+ goto done;
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending certificate response",
+ SSL_GETPID(), ss->fd));
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_CLIENT_CERTIFICATE;
+ msg[1] = SSL_CT_X509_CERTIFICATE;
+ msg[2] = MSB(cert->len);
+ msg[3] = LSB(cert->len);
+ msg[4] = MSB(encCode->len);
+ msg[5] = LSB(encCode->len);
+ PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES, cert->data, cert->len);
+ PORT_Memcpy(msg + SSL_HL_CLIENT_CERTIFICATE_HBYTES + cert->len,
+ encCode->data, encCode->len);
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+ rv = (*ss->sec.send)(ss, msg, sendLen, 0);
+ if (rv >= 0) {
+ rv = SECSuccess;
+ }
+done:
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+ return rv;
+}
+
+/********************************************************************
+** Send functions above this line must aquire & release the socket's
+** xmitBufLock.
+** All the ssl2_Send functions below this line are called vis ss->sec.send
+** and require that the caller hold the xmitBufLock.
+*/
+
+/*
+** Called from ssl2_SendStream, ssl2_SendBlock, but not from ssl2_SendClear.
+*/
+static SECStatus
+ssl2_CalcMAC(PRUint8 * result,
+ sslSecurityInfo * sec,
+ const PRUint8 * data,
+ unsigned int dataLen,
+ unsigned int paddingLen)
+{
+ const PRUint8 * secret = sec->sendSecret.data;
+ unsigned int secretLen = sec->sendSecret.len;
+ unsigned long sequenceNumber = sec->sendSequence;
+ unsigned int nout;
+ PRUint8 seq[4];
+ PRUint8 padding[32];/* XXX max blocksize? */
+
+ if (!sec->hash || !sec->hash->length)
+ return SECSuccess;
+ if (!sec->hashcx)
+ return SECFailure;
+
+ /* Reset hash function */
+ (*sec->hash->begin)(sec->hashcx);
+
+ /* Feed hash the data */
+ (*sec->hash->update)(sec->hashcx, secret, secretLen);
+ (*sec->hash->update)(sec->hashcx, data, dataLen);
+ PORT_Memset(padding, paddingLen, paddingLen);
+ (*sec->hash->update)(sec->hashcx, padding, paddingLen);
+
+ seq[0] = (PRUint8) (sequenceNumber >> 24);
+ seq[1] = (PRUint8) (sequenceNumber >> 16);
+ seq[2] = (PRUint8) (sequenceNumber >> 8);
+ seq[3] = (PRUint8) (sequenceNumber);
+
+ PRINT_BUF(60, (0, "calc-mac secret:", secret, secretLen));
+ PRINT_BUF(60, (0, "calc-mac data:", data, dataLen));
+ PRINT_BUF(60, (0, "calc-mac padding:", padding, paddingLen));
+ PRINT_BUF(60, (0, "calc-mac seq:", seq, 4));
+
+ (*sec->hash->update)(sec->hashcx, seq, 4);
+
+ /* Get result */
+ (*sec->hash->end)(sec->hashcx, result, &nout, sec->hash->length);
+
+ return SECSuccess;
+}
+
+/*
+** Maximum transmission amounts. These are tiny bit smaller than they
+** need to be (they account for the MAC length plus some padding),
+** assuming the MAC is 16 bytes long and the padding is a max of 7 bytes
+** long. This gives an additional 9 bytes of slop to work within.
+*/
+#define MAX_STREAM_CYPHER_LEN 0x7fe0
+#define MAX_BLOCK_CYPHER_LEN 0x3fe0
+
+/*
+** Send some data in the clear.
+** Package up data with the length header and send it.
+**
+** Return count of bytes successfully written, or negative number (failure).
+*/
+static PRInt32
+ssl2_SendClear(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
+{
+ PRUint8 * out;
+ int rv;
+ int amount;
+ int count = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes in the clear",
+ SSL_GETPID(), ss->fd, len));
+ PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len));
+
+ while (len) {
+ amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN );
+ if (amount + 2 > ss->sec.writeBuf.space) {
+ rv = sslBuffer_Grow(&ss->sec.writeBuf, amount + 2);
+ if (rv != SECSuccess) {
+ count = rv;
+ break;
+ }
+ }
+ out = ss->sec.writeBuf.buf;
+
+ /*
+ ** Construct message.
+ */
+ out[0] = 0x80 | MSB(amount);
+ out[1] = LSB(amount);
+ PORT_Memcpy(&out[2], in, amount);
+
+ /* Now send the data */
+ rv = ssl_DefSend(ss, out, amount + 2, flags & ~ssl_SEND_FLAG_MASK);
+ if (rv < 0) {
+ if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
+ rv = 0;
+ } else {
+ /* Return short write if some data already went out... */
+ if (count == 0)
+ count = rv;
+ break;
+ }
+ }
+
+ if ((unsigned)rv < (amount + 2)) {
+ /* Short write. Save the data and return. */
+ if (ssl_SaveWriteData(ss, out + rv, amount + 2 - rv)
+ == SECFailure) {
+ count = SECFailure;
+ } else {
+ count += amount;
+ ss->sec.sendSequence++;
+ }
+ break;
+ }
+
+ ss->sec.sendSequence++;
+ in += amount;
+ count += amount;
+ len -= amount;
+ }
+
+ return count;
+}
+
+/*
+** Send some data, when using a stream cipher. Stream ciphers have a
+** block size of 1. Package up the data with the length header
+** and send it.
+*/
+static PRInt32
+ssl2_SendStream(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
+{
+ PRUint8 * out;
+ int rv;
+ int count = 0;
+
+ int amount;
+ PRUint8 macLen;
+ int nout;
+ int buflen;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using stream cipher",
+ SSL_GETPID(), ss->fd, len));
+ PRINT_BUF(50, (ss, "clear data:", (PRUint8*) in, len));
+
+ while (len) {
+ ssl_GetSpecReadLock(ss); /*************************************/
+
+ macLen = ss->sec.hash->length;
+ amount = PR_MIN( len, MAX_STREAM_CYPHER_LEN );
+ buflen = amount + 2 + macLen;
+ if (buflen > ss->sec.writeBuf.space) {
+ rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ out = ss->sec.writeBuf.buf;
+ nout = amount + macLen;
+ out[0] = 0x80 | MSB(nout);
+ out[1] = LSB(nout);
+
+ /* Calculate MAC */
+ rv = ssl2_CalcMAC(out+2, /* put MAC here */
+ &ss->sec,
+ in, amount, /* input addr & length */
+ 0); /* no padding */
+ if (rv != SECSuccess)
+ goto loser;
+
+ /* Encrypt MAC */
+ rv = (*ss->sec.enc)(ss->sec.writecx, out+2, &nout, macLen, out+2, macLen);
+ if (rv) goto loser;
+
+ /* Encrypt data from caller */
+ rv = (*ss->sec.enc)(ss->sec.writecx, out+2+macLen, &nout, amount, in, amount);
+ if (rv) goto loser;
+
+ ssl_ReleaseSpecReadLock(ss); /*************************************/
+
+ PRINT_BUF(50, (ss, "encrypted data:", out, buflen));
+
+ rv = ssl_DefSend(ss, out, buflen, flags & ~ssl_SEND_FLAG_MASK);
+ if (rv < 0) {
+ if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
+ SSL_TRC(50, ("%d: SSL[%d]: send stream would block, "
+ "saving data", SSL_GETPID(), ss->fd));
+ rv = 0;
+ } else {
+ SSL_TRC(10, ("%d: SSL[%d]: send stream error %d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ /* Return short write if some data already went out... */
+ if (count == 0)
+ count = rv;
+ goto done;
+ }
+ }
+
+ if ((unsigned)rv < buflen) {
+ /* Short write. Save the data and return. */
+ if (ssl_SaveWriteData(ss, out + rv, buflen - rv) == SECFailure) {
+ count = SECFailure;
+ } else {
+ count += amount;
+ ss->sec.sendSequence++;
+ }
+ goto done;
+ }
+
+ ss->sec.sendSequence++;
+ in += amount;
+ count += amount;
+ len -= amount;
+ }
+
+done:
+ return count;
+
+loser:
+ ssl_ReleaseSpecReadLock(ss);
+ return SECFailure;
+}
+
+/*
+** Send some data, when using a block cipher. Package up the data with
+** the length header and send it.
+*/
+/* XXX assumes blocksize is > 7 */
+static PRInt32
+ssl2_SendBlock(sslSocket *ss, const PRUint8 *in, PRInt32 len, PRInt32 flags)
+{
+ PRUint8 * out; /* begining of output buffer. */
+ PRUint8 * op; /* next output byte goes here. */
+ int rv; /* value from funcs we called. */
+ int count = 0; /* this function's return value. */
+
+ unsigned int hlen; /* output record hdr len, 2 or 3 */
+ unsigned int macLen; /* MAC is this many bytes long. */
+ int amount; /* of plaintext to go in record. */
+ unsigned int padding; /* add this many padding byte. */
+ int nout; /* ciphertext size after header. */
+ int buflen; /* size of generated record. */
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+
+ SSL_TRC(10, ("%d: SSL[%d]: sending %d bytes using block cipher",
+ SSL_GETPID(), ss->fd, len));
+ PRINT_BUF(50, (ss, "clear data:", in, len));
+
+ while (len) {
+ ssl_GetSpecReadLock(ss); /*************************************/
+
+ macLen = ss->sec.hash->length;
+ /* Figure out how much to send, including mac and padding */
+ amount = PR_MIN( len, MAX_BLOCK_CYPHER_LEN );
+ nout = amount + macLen;
+ padding = nout & (ss->sec.blockSize - 1);
+ if (padding) {
+ hlen = 3;
+ padding = ss->sec.blockSize - padding;
+ nout += padding;
+ } else {
+ hlen = 2;
+ }
+ buflen = hlen + nout;
+ if (buflen > ss->sec.writeBuf.space) {
+ rv = sslBuffer_Grow(&ss->sec.writeBuf, buflen);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ out = ss->sec.writeBuf.buf;
+
+ /* Construct header */
+ op = out;
+ if (padding) {
+ *op++ = MSB(nout);
+ *op++ = LSB(nout);
+ *op++ = padding;
+ } else {
+ *op++ = 0x80 | MSB(nout);
+ *op++ = LSB(nout);
+ }
+
+ /* Calculate MAC */
+ rv = ssl2_CalcMAC(op, /* MAC goes here. */
+ &ss->sec,
+ in, amount, /* intput addr, len */
+ padding);
+ if (rv != SECSuccess)
+ goto loser;
+ op += macLen;
+
+ /* Copy in the input data */
+ /* XXX could eliminate the copy by folding it into the encryption */
+ PORT_Memcpy(op, in, amount);
+ op += amount;
+ if (padding) {
+ PORT_Memset(op, padding, padding);
+ op += padding;
+ }
+
+ /* Encrypt result */
+ rv = (*ss->sec.enc)(ss->sec.writecx, out+hlen, &nout, buflen-hlen,
+ out+hlen, op - (out + hlen));
+ if (rv)
+ goto loser;
+
+ ssl_ReleaseSpecReadLock(ss); /*************************************/
+
+ PRINT_BUF(50, (ss, "final xmit data:", out, op - out));
+
+ rv = ssl_DefSend(ss, out, op - out, flags & ~ssl_SEND_FLAG_MASK);
+ if (rv < 0) {
+ if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
+ rv = 0;
+ } else {
+ SSL_TRC(10, ("%d: SSL[%d]: send block error %d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ /* Return short write if some data already went out... */
+ if (count == 0)
+ count = rv;
+ goto done;
+ }
+ }
+
+ if (rv < (op - out)) {
+ /* Short write. Save the data and return. */
+ if (ssl_SaveWriteData(ss, out + rv, op - out - rv) == SECFailure) {
+ count = SECFailure;
+ } else {
+ count += amount;
+ ss->sec.sendSequence++;
+ }
+ goto done;
+ }
+
+ ss->sec.sendSequence++;
+ in += amount;
+ count += amount;
+ len -= amount;
+ }
+
+done:
+ return count;
+
+loser:
+ ssl_ReleaseSpecReadLock(ss);
+ return SECFailure;
+}
+
+/*
+** Called from: ssl2_HandleServerHelloMessage,
+** ssl2_HandleClientSessionKeyMessage,
+** ssl2_HandleClientHelloMessage,
+**
+*/
+static void
+ssl2_UseEncryptedSendFunc(sslSocket *ss)
+{
+ ssl_GetXmitBufLock(ss);
+ PORT_Assert(ss->sec.hashcx != 0);
+
+ ss->gs.encrypted = 1;
+ ss->sec.send = (ss->sec.blockSize > 1) ? ssl2_SendBlock : ssl2_SendStream;
+ ssl_ReleaseXmitBufLock(ss);
+}
+
+/* Called while initializing socket in ssl_CreateSecurityInfo().
+** This function allows us to keep the name of ssl2_SendClear static.
+*/
+void
+ssl2_UseClearSendFunc(sslSocket *ss)
+{
+ ss->sec.send = ssl2_SendClear;
+}
+
+/************************************************************************
+** END of Send functions. *
+*************************************************************************/
+
+/***********************************************************************
+ * For SSL3, this gathers in and handles records/messages until either
+ * the handshake is complete or application data is available.
+ *
+ * For SSL2, this gathers in only the next SSLV2 record.
+ *
+ * Called from ssl_Do1stHandshake() via function pointer ss->handshake.
+ * Caller must hold handshake lock.
+ * This function acquires and releases the RecvBufLock.
+ *
+ * returns SECSuccess for success.
+ * returns SECWouldBlock when that value is returned by ssl2_GatherRecord() or
+ * ssl3_GatherCompleteHandshake().
+ * returns SECFailure on all other errors.
+ *
+ * The gather functions called by ssl_GatherRecord1stHandshake are expected
+ * to return values interpreted as follows:
+ * 1 : the function completed without error.
+ * 0 : the function read EOF.
+ * -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
+ * -2 : the function wants ssl_GatherRecord1stHandshake to be called again
+ * immediately, by ssl_Do1stHandshake.
+ *
+ * This code is similar to, and easily confused with, DoRecv() in sslsecur.c
+ *
+ * This function is called from ssl_Do1stHandshake().
+ * The following functions put ssl_GatherRecord1stHandshake into ss->handshake:
+ * ssl2_HandleMessage
+ * ssl2_HandleVerifyMessage
+ * ssl2_HandleServerHelloMessage
+ * ssl2_BeginClientHandshake
+ * ssl2_HandleClientSessionKeyMessage
+ * ssl3_RestartHandshakeAfterCertReq
+ * ssl3_RestartHandshakeAfterServerCert
+ * ssl2_HandleClientHelloMessage
+ * ssl2_BeginServerHandshake
+ */
+SECStatus
+ssl_GatherRecord1stHandshake(sslSocket *ss)
+{
+ int rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetRecvBufLock(ss);
+
+ /* The special case DTLS logic is needed here because the SSL/TLS
+ * version wants to auto-detect SSL2 vs. SSL3 on the initial handshake
+ * (ss->version == 0) but with DTLS it gets confused, so we force the
+ * SSL3 version.
+ */
+ if ((ss->version >= SSL_LIBRARY_VERSION_3_0) || IS_DTLS(ss)) {
+ /* Wait for handshake to complete, or application data to arrive. */
+ rv = ssl3_GatherCompleteHandshake(ss, 0);
+ } else {
+ /* See if we have a complete record */
+ rv = ssl2_GatherRecord(ss, 0);
+ }
+ SSL_TRC(10, ("%d: SSL[%d]: handshake gathering, rv=%d",
+ SSL_GETPID(), ss->fd, rv));
+
+ ssl_ReleaseRecvBufLock(ss);
+
+ if (rv <= 0) {
+ if (rv == SECWouldBlock) {
+ /* Progress is blocked waiting for callback completion. */
+ SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)",
+ SSL_GETPID(), ss->fd, ss->gs.remainder));
+ return SECWouldBlock;
+ }
+ if (rv == 0) {
+ /* EOF. Loser */
+ PORT_SetError(PR_END_OF_FILE_ERROR);
+ }
+ return SECFailure; /* rv is < 0 here. */
+ }
+
+ SSL_TRC(10, ("%d: SSL[%d]: got handshake record of %d bytes",
+ SSL_GETPID(), ss->fd, ss->gs.recordLen));
+
+ ss->handshake = 0; /* makes ssl_Do1stHandshake call ss->nextHandshake.*/
+ return SECSuccess;
+}
+
+/************************************************************************/
+
+/* Called from ssl2_ServerSetupSessionCypher()
+ * ssl2_ClientSetupSessionCypher()
+ */
+static SECStatus
+ssl2_FillInSID(sslSessionID * sid,
+ int cipher,
+ PRUint8 *keyData,
+ int keyLen,
+ PRUint8 *ca,
+ int caLen,
+ int keyBits,
+ int secretKeyBits,
+ SSLSignType authAlgorithm,
+ PRUint32 authKeyBits,
+ SSLKEAType keaType,
+ PRUint32 keaKeyBits)
+{
+ PORT_Assert(sid->references == 1);
+ PORT_Assert(sid->cached == never_cached);
+ PORT_Assert(sid->u.ssl2.masterKey.data == 0);
+ PORT_Assert(sid->u.ssl2.cipherArg.data == 0);
+
+ sid->version = SSL_LIBRARY_VERSION_2;
+
+ sid->u.ssl2.cipherType = cipher;
+ sid->u.ssl2.masterKey.data = (PRUint8*) PORT_Alloc(keyLen);
+ if (!sid->u.ssl2.masterKey.data) {
+ return SECFailure;
+ }
+ PORT_Memcpy(sid->u.ssl2.masterKey.data, keyData, keyLen);
+ sid->u.ssl2.masterKey.len = keyLen;
+ sid->u.ssl2.keyBits = keyBits;
+ sid->u.ssl2.secretKeyBits = secretKeyBits;
+ sid->authAlgorithm = authAlgorithm;
+ sid->authKeyBits = authKeyBits;
+ sid->keaType = keaType;
+ sid->keaKeyBits = keaKeyBits;
+ sid->lastAccessTime = sid->creationTime = ssl_Time();
+ sid->expirationTime = sid->creationTime + ssl_sid_timeout;
+
+ if (caLen) {
+ sid->u.ssl2.cipherArg.data = (PRUint8*) PORT_Alloc(caLen);
+ if (!sid->u.ssl2.cipherArg.data) {
+ return SECFailure;
+ }
+ sid->u.ssl2.cipherArg.len = caLen;
+ PORT_Memcpy(sid->u.ssl2.cipherArg.data, ca, caLen);
+ }
+ return SECSuccess;
+}
+
+/*
+** Construct session keys given the masterKey (tied to the session-id),
+** the client's challenge and the server's nonce.
+**
+** Called from ssl2_CreateSessionCypher() <-
+*/
+static SECStatus
+ssl2_ProduceKeys(sslSocket * ss,
+ SECItem * readKey,
+ SECItem * writeKey,
+ SECItem * masterKey,
+ PRUint8 * challenge,
+ PRUint8 * nonce,
+ int cipherType)
+{
+ PK11Context * cx = 0;
+ unsigned nkm = 0; /* number of hashes to generate key mat. */
+ unsigned nkd = 0; /* size of readKey and writeKey. */
+ unsigned part;
+ unsigned i;
+ unsigned off;
+ SECStatus rv;
+ PRUint8 countChar;
+ PRUint8 km[3*16]; /* buffer for key material. */
+
+ readKey->data = 0;
+ writeKey->data = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ rv = SECSuccess;
+ cx = PK11_CreateDigestContext(SEC_OID_MD5);
+ if (cx == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ return SECFailure;
+ }
+
+ nkm = ssl_Specs[cipherType].nkm;
+ nkd = ssl_Specs[cipherType].nkd;
+
+ readKey->data = (PRUint8*) PORT_Alloc(nkd);
+ if (!readKey->data)
+ goto loser;
+ readKey->len = nkd;
+
+ writeKey->data = (PRUint8*) PORT_Alloc(nkd);
+ if (!writeKey->data)
+ goto loser;
+ writeKey->len = nkd;
+
+ /* Produce key material */
+ countChar = '0';
+ for (i = 0, off = 0; i < nkm; i++, off += 16) {
+ rv = PK11_DigestBegin(cx);
+ rv |= PK11_DigestOp(cx, masterKey->data, masterKey->len);
+ rv |= PK11_DigestOp(cx, &countChar, 1);
+ rv |= PK11_DigestOp(cx, challenge, SSL_CHALLENGE_BYTES);
+ rv |= PK11_DigestOp(cx, nonce, SSL_CONNECTIONID_BYTES);
+ rv |= PK11_DigestFinal(cx, km+off, &part, MD5_LENGTH);
+ if (rv != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
+ rv = SECFailure;
+ goto loser;
+ }
+ countChar++;
+ }
+
+ /* Produce keys */
+ PORT_Memcpy(readKey->data, km, nkd);
+ PORT_Memcpy(writeKey->data, km + nkd, nkd);
+
+loser:
+ PK11_DestroyContext(cx, PR_TRUE);
+ return rv;
+}
+
+/* Called from ssl2_ServerSetupSessionCypher()
+** <- ssl2_HandleClientSessionKeyMessage()
+** <- ssl2_HandleClientHelloMessage()
+** and from ssl2_ClientSetupSessionCypher()
+** <- ssl2_HandleServerHelloMessage()
+*/
+static SECStatus
+ssl2_CreateSessionCypher(sslSocket *ss, sslSessionID *sid, PRBool isClient)
+{
+ SECItem * rk = NULL;
+ SECItem * wk = NULL;
+ SECItem * param;
+ SECStatus rv;
+ int cipherType = sid->u.ssl2.cipherType;
+ PK11SlotInfo * slot = NULL;
+ CK_MECHANISM_TYPE mechanism;
+ SECItem readKey;
+ SECItem writeKey;
+
+ void *readcx = 0;
+ void *writecx = 0;
+ readKey.data = 0;
+ writeKey.data = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ if (ss->sec.ci.sid == 0)
+ goto sec_loser; /* don't crash if asserts are off */
+
+ /* Trying to cut down on all these switch statements that should be tables.
+ * So, test cipherType once, here, and then use tables below.
+ */
+ switch (cipherType) {
+ case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
+ case SSL_CK_RC4_128_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_WITH_MD5:
+ case SSL_CK_DES_64_CBC_WITH_MD5:
+ case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
+ break;
+
+ default:
+ SSL_DBG(("%d: SSL[%d]: ssl2_CreateSessionCypher: unknown cipher=%d",
+ SSL_GETPID(), ss->fd, cipherType));
+ PORT_SetError(isClient ? SSL_ERROR_BAD_SERVER : SSL_ERROR_BAD_CLIENT);
+ goto sec_loser;
+ }
+
+ rk = isClient ? &readKey : &writeKey;
+ wk = isClient ? &writeKey : &readKey;
+
+ /* Produce the keys for this session */
+ rv = ssl2_ProduceKeys(ss, &readKey, &writeKey, &sid->u.ssl2.masterKey,
+ ss->sec.ci.clientChallenge, ss->sec.ci.connectionID,
+ cipherType);
+ if (rv != SECSuccess)
+ goto loser;
+ PRINT_BUF(7, (ss, "Session read-key: ", rk->data, rk->len));
+ PRINT_BUF(7, (ss, "Session write-key: ", wk->data, wk->len));
+
+ PORT_Memcpy(ss->sec.ci.readKey, readKey.data, readKey.len);
+ PORT_Memcpy(ss->sec.ci.writeKey, writeKey.data, writeKey.len);
+ ss->sec.ci.keySize = readKey.len;
+
+ /* Setup the MAC */
+ rv = ssl2_CreateMAC(&ss->sec, rk, wk, cipherType);
+ if (rv != SECSuccess)
+ goto loser;
+
+ /* First create the session key object */
+ SSL_TRC(3, ("%d: SSL[%d]: using %s", SSL_GETPID(), ss->fd,
+ ssl_cipherName[cipherType]));
+
+
+ mechanism = ssl_Specs[cipherType].mechanism;
+
+ /* set destructer before we call loser... */
+ ss->sec.destroy = (void (*)(void*, PRBool)) PK11_DestroyContext;
+ slot = PK11_GetBestSlot(mechanism, ss->pkcs11PinArg);
+ if (slot == NULL)
+ goto loser;
+
+ param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg);
+ if (param == NULL)
+ goto loser;
+ readcx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap,
+ CKA_DECRYPT, rk, param,
+ ss->pkcs11PinArg);
+ SECITEM_FreeItem(param, PR_TRUE);
+ if (readcx == NULL)
+ goto loser;
+
+ /* build the client context */
+ param = PK11_ParamFromIV(mechanism, &sid->u.ssl2.cipherArg);
+ if (param == NULL)
+ goto loser;
+ writecx = PK11_CreateContextByRawKey(slot, mechanism, PK11_OriginUnwrap,
+ CKA_ENCRYPT, wk, param,
+ ss->pkcs11PinArg);
+ SECITEM_FreeItem(param,PR_TRUE);
+ if (writecx == NULL)
+ goto loser;
+ PK11_FreeSlot(slot);
+
+ rv = SECSuccess;
+ ss->sec.enc = (SSLCipher) PK11_CipherOp;
+ ss->sec.dec = (SSLCipher) PK11_CipherOp;
+ ss->sec.readcx = (void *) readcx;
+ ss->sec.writecx = (void *) writecx;
+ ss->sec.blockSize = ssl_Specs[cipherType].blockSize;
+ ss->sec.blockShift = ssl_Specs[cipherType].blockShift;
+ ss->sec.cipherType = sid->u.ssl2.cipherType;
+ ss->sec.keyBits = sid->u.ssl2.keyBits;
+ ss->sec.secretKeyBits = sid->u.ssl2.secretKeyBits;
+ goto done;
+
+ loser:
+ if (ss->sec.destroy) {
+ if (readcx) (*ss->sec.destroy)(readcx, PR_TRUE);
+ if (writecx) (*ss->sec.destroy)(writecx, PR_TRUE);
+ }
+ ss->sec.destroy = NULL;
+ if (slot) PK11_FreeSlot(slot);
+
+ sec_loser:
+ rv = SECFailure;
+
+ done:
+ if (rk) {
+ SECITEM_ZfreeItem(rk, PR_FALSE);
+ }
+ if (wk) {
+ SECITEM_ZfreeItem(wk, PR_FALSE);
+ }
+ return rv;
+}
+
+/*
+** Setup the server ciphers given information from a CLIENT-MASTER-KEY
+** message.
+** "ss" pointer to the ssl-socket object
+** "cipher" the cipher type to use
+** "keyBits" the size of the final cipher key
+** "ck" the clear-key data
+** "ckLen" the number of bytes of clear-key data
+** "ek" the encrypted-key data
+** "ekLen" the number of bytes of encrypted-key data
+** "ca" the cipher-arg data
+** "caLen" the number of bytes of cipher-arg data
+**
+** The MASTER-KEY is constructed by first decrypting the encrypted-key
+** data. This produces the SECRET-KEY-DATA. The MASTER-KEY is composed by
+** concatenating the clear-key data with the SECRET-KEY-DATA. This code
+** checks to make sure that the client didn't send us an improper amount
+** of SECRET-KEY-DATA (it restricts the length of that data to match the
+** spec).
+**
+** Called from ssl2_HandleClientSessionKeyMessage().
+*/
+static SECStatus
+ssl2_ServerSetupSessionCypher(sslSocket *ss, int cipher, unsigned int keyBits,
+ PRUint8 *ck, unsigned int ckLen,
+ PRUint8 *ek, unsigned int ekLen,
+ PRUint8 *ca, unsigned int caLen)
+{
+ PRUint8 * dk = NULL; /* decrypted master key */
+ sslSessionID * sid;
+ sslServerCerts * sc = ss->serverCerts + kt_rsa;
+ PRUint8 * kbuf = 0; /* buffer for RSA decrypted data. */
+ unsigned int ddLen; /* length of RSA decrypted data in kbuf */
+ unsigned int keySize;
+ unsigned int dkLen; /* decrypted key length in bytes */
+ int modulusLen;
+ SECStatus rv;
+ PRUint16 allowed; /* cipher kinds enabled and allowed by policy */
+ PRUint8 mkbuf[SSL_MAX_MASTER_KEY_BYTES];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert((sc->SERVERKEY != 0));
+ PORT_Assert((ss->sec.ci.sid != 0));
+ sid = ss->sec.ci.sid;
+
+ /* Trying to cut down on all these switch statements that should be tables.
+ * So, test cipherType once, here, and then use tables below.
+ */
+ switch (cipher) {
+ case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
+ case SSL_CK_RC4_128_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_WITH_MD5:
+ case SSL_CK_DES_64_CBC_WITH_MD5:
+ case SSL_CK_DES_192_EDE3_CBC_WITH_MD5:
+ break;
+
+ default:
+ SSL_DBG(("%d: SSL[%d]: ssl2_ServerSetupSessionCypher: unknown cipher=%d",
+ SSL_GETPID(), ss->fd, cipher));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ allowed = ss->allowedByPolicy & ss->chosenPreference & SSL_CB_IMPLEMENTED;
+ if (!(allowed & (1 << cipher))) {
+ /* client chose a kind we don't allow! */
+ SSL_DBG(("%d: SSL[%d]: disallowed cipher=%d",
+ SSL_GETPID(), ss->fd, cipher));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ keySize = ssl_Specs[cipher].keyLen;
+ if (keyBits != keySize * BPB) {
+ SSL_DBG(("%d: SSL[%d]: invalid master secret key length=%d (bits)!",
+ SSL_GETPID(), ss->fd, keyBits));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ if (ckLen != ssl_Specs[cipher].pubLen) {
+ SSL_DBG(("%d: SSL[%d]: invalid clear key length, ckLen=%d (bytes)!",
+ SSL_GETPID(), ss->fd, ckLen));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ if (caLen != ssl_Specs[cipher].ivLen) {
+ SSL_DBG(("%d: SSL[%d]: invalid key args length, caLen=%d (bytes)!",
+ SSL_GETPID(), ss->fd, caLen));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ modulusLen = PK11_GetPrivateModulusLen(sc->SERVERKEY);
+ if (modulusLen == -1) {
+ /* XXX If the key is bad, then PK11_PubDecryptRaw will fail below. */
+ modulusLen = ekLen;
+ }
+ if (ekLen > modulusLen || ekLen + ckLen < keySize) {
+ SSL_DBG(("%d: SSL[%d]: invalid encrypted key length, ekLen=%d (bytes)!",
+ SSL_GETPID(), ss->fd, ekLen));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto loser;
+ }
+
+ /* allocate the buffer to hold the decrypted portion of the key. */
+ kbuf = (PRUint8*)PORT_Alloc(modulusLen);
+ if (!kbuf) {
+ goto loser;
+ }
+ dkLen = keySize - ckLen;
+ dk = kbuf + modulusLen - dkLen;
+
+ /* Decrypt encrypted half of the key.
+ ** NOTE: PK11_PubDecryptRaw will barf on a non-RSA key. This is
+ ** desired behavior here.
+ */
+ rv = PK11_PubDecryptRaw(sc->SERVERKEY, kbuf, &ddLen, modulusLen, ek, ekLen);
+ if (rv != SECSuccess)
+ goto hide_loser;
+
+ /* Is the length of the decrypted data (ddLen) the expected value? */
+ if (modulusLen != ddLen)
+ goto hide_loser;
+
+ /* Cheaply verify that PKCS#1 was used to format the encryption block */
+ if ((kbuf[0] != 0x00) || (kbuf[1] != 0x02) || (dk[-1] != 0x00)) {
+ SSL_DBG(("%d: SSL[%d]: strange encryption block",
+ SSL_GETPID(), ss->fd));
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto hide_loser;
+ }
+
+ /* Make sure we're not subject to a version rollback attack. */
+ if (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ static const PRUint8 threes[8] = { 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03 };
+
+ if (PORT_Memcmp(dk - 8 - 1, threes, 8) == 0) {
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ goto hide_loser;
+ }
+ }
+ if (0) {
+hide_loser:
+ /* Defense against the Bleichenbacher attack.
+ * Provide the client with NO CLUES that the decrypted master key
+ * was erroneous. Don't send any error messages.
+ * Instead, Generate a completely bogus master key .
+ */
+ PK11_GenerateRandom(dk, dkLen);
+ }
+
+ /*
+ ** Construct master key out of the pieces.
+ */
+ if (ckLen) {
+ PORT_Memcpy(mkbuf, ck, ckLen);
+ }
+ PORT_Memcpy(mkbuf + ckLen, dk, dkLen);
+
+ /* Fill in session-id */
+ rv = ssl2_FillInSID(sid, cipher, mkbuf, keySize, ca, caLen,
+ keyBits, keyBits - (ckLen<<3),
+ ss->sec.authAlgorithm, ss->sec.authKeyBits,
+ ss->sec.keaType, ss->sec.keaKeyBits);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Create session ciphers */
+ rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ SSL_TRC(1, ("%d: SSL[%d]: server, using %s cipher, clear=%d total=%d",
+ SSL_GETPID(), ss->fd, ssl_cipherName[cipher],
+ ckLen<<3, keySize<<3));
+ rv = SECSuccess;
+ goto done;
+
+ loser:
+ rv = SECFailure;
+
+ done:
+ PORT_Free(kbuf);
+ return rv;
+}
+
+/************************************************************************/
+
+/*
+** Rewrite the incoming cipher specs, comparing to list of specs we support,
+** (ss->cipherSpecs) and eliminating anything we don't support
+**
+* Note: Our list may contain SSL v3 ciphers.
+* We MUST NOT match on any of those.
+* Fortunately, this is easy to detect because SSLv3 ciphers have zero
+* in the first byte, and none of the SSLv2 ciphers do.
+*
+* Called from ssl2_HandleClientHelloMessage().
+* Returns the number of bytes of "qualified cipher specs",
+* which is typically a multiple of 3, but will be zero if there are none.
+*/
+static int
+ssl2_QualifyCypherSpecs(sslSocket *ss,
+ PRUint8 * cs, /* cipher specs in client hello msg. */
+ int csLen)
+{
+ PRUint8 * ms;
+ PRUint8 * hs;
+ PRUint8 * qs;
+ int mc;
+ int hc;
+ PRUint8 qualifiedSpecs[ssl2_NUM_SUITES_IMPLEMENTED * 3];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ if (!ss->cipherSpecs) {
+ SECStatus rv = ssl2_ConstructCipherSpecs(ss);
+ if (rv != SECSuccess || !ss->cipherSpecs)
+ return 0;
+ }
+
+ PRINT_BUF(10, (ss, "specs from client:", cs, csLen));
+ qs = qualifiedSpecs;
+ ms = ss->cipherSpecs;
+ for (mc = ss->sizeCipherSpecs; mc > 0; mc -= 3, ms += 3) {
+ if (ms[0] == 0)
+ continue;
+ for (hs = cs, hc = csLen; hc > 0; hs += 3, hc -= 3) {
+ if ((hs[0] == ms[0]) &&
+ (hs[1] == ms[1]) &&
+ (hs[2] == ms[2])) {
+ /* Copy this cipher spec into the "keep" section */
+ qs[0] = hs[0];
+ qs[1] = hs[1];
+ qs[2] = hs[2];
+ qs += 3;
+ break;
+ }
+ }
+ }
+ hc = qs - qualifiedSpecs;
+ PRINT_BUF(10, (ss, "qualified specs from client:", qualifiedSpecs, hc));
+ PORT_Memcpy(cs, qualifiedSpecs, hc);
+ return hc;
+}
+
+/*
+** Pick the best cipher we can find, given the array of server cipher
+** specs. Returns cipher number (e.g. SSL_CK_*), or -1 for no overlap.
+** If successful, stores the master key size (bytes) in *pKeyLen.
+**
+** This is correct only for the client side, but presently
+** this function is only called from
+** ssl2_ClientSetupSessionCypher() <- ssl2_HandleServerHelloMessage()
+**
+** Note that most servers only return a single cipher suite in their
+** ServerHello messages. So, the code below for finding the "best" cipher
+** suite usually has only one choice. The client and server should send
+** their cipher suite lists sorted in descending order by preference.
+*/
+static int
+ssl2_ChooseSessionCypher(sslSocket *ss,
+ int hc, /* number of cs's in hs. */
+ PRUint8 * hs, /* server hello's cipher suites. */
+ int * pKeyLen) /* out: sym key size in bytes. */
+{
+ PRUint8 * ms;
+ unsigned int i;
+ int bestKeySize;
+ int bestRealKeySize;
+ int bestCypher;
+ int keySize;
+ int realKeySize;
+ PRUint8 * ohs = hs;
+ const PRUint8 * preferred;
+ static const PRUint8 noneSuch[3] = { 0, 0, 0 };
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ if (!ss->cipherSpecs) {
+ SECStatus rv = ssl2_ConstructCipherSpecs(ss);
+ if (rv != SECSuccess || !ss->cipherSpecs)
+ goto loser;
+ }
+
+ if (!ss->preferredCipher) {
+ unsigned int allowed = ss->allowedByPolicy & ss->chosenPreference &
+ SSL_CB_IMPLEMENTED;
+ if (allowed) {
+ preferred = implementedCipherSuites;
+ for (i = ssl2_NUM_SUITES_IMPLEMENTED; i > 0; --i) {
+ if (0 != (allowed & (1U << preferred[0]))) {
+ ss->preferredCipher = preferred;
+ break;
+ }
+ preferred += 3;
+ }
+ }
+ }
+ preferred = ss->preferredCipher ? ss->preferredCipher : noneSuch;
+ /*
+ ** Scan list of ciphers received from peer and look for a match in
+ ** our list.
+ * Note: Our list may contain SSL v3 ciphers.
+ * We MUST NOT match on any of those.
+ * Fortunately, this is easy to detect because SSLv3 ciphers have zero
+ * in the first byte, and none of the SSLv2 ciphers do.
+ */
+ bestKeySize = bestRealKeySize = 0;
+ bestCypher = -1;
+ while (--hc >= 0) {
+ for (i = 0, ms = ss->cipherSpecs; i < ss->sizeCipherSpecs; i += 3, ms += 3) {
+ if ((hs[0] == preferred[0]) &&
+ (hs[1] == preferred[1]) &&
+ (hs[2] == preferred[2]) &&
+ hs[0] != 0) {
+ /* Pick this cipher immediately! */
+ *pKeyLen = (((hs[1] << 8) | hs[2]) + 7) >> 3;
+ return hs[0];
+ }
+ if ((hs[0] == ms[0]) && (hs[1] == ms[1]) && (hs[2] == ms[2]) &&
+ hs[0] != 0) {
+ /* Found a match */
+
+ /* Use secret keySize to determine which cipher is best */
+ realKeySize = (hs[1] << 8) | hs[2];
+ switch (hs[0]) {
+ case SSL_CK_RC4_128_EXPORT40_WITH_MD5:
+ case SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5:
+ keySize = 40;
+ break;
+ default:
+ keySize = realKeySize;
+ break;
+ }
+ if (keySize > bestKeySize) {
+ bestCypher = hs[0];
+ bestKeySize = keySize;
+ bestRealKeySize = realKeySize;
+ }
+ }
+ }
+ hs += 3;
+ }
+ if (bestCypher < 0) {
+ /*
+ ** No overlap between server and client. Re-examine server list
+ ** to see what kind of ciphers it does support so that we can set
+ ** the error code appropriately.
+ */
+ if ((ohs[0] == SSL_CK_RC4_128_WITH_MD5) ||
+ (ohs[0] == SSL_CK_RC2_128_CBC_WITH_MD5)) {
+ PORT_SetError(SSL_ERROR_US_ONLY_SERVER);
+ } else if ((ohs[0] == SSL_CK_RC4_128_EXPORT40_WITH_MD5) ||
+ (ohs[0] == SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5)) {
+ PORT_SetError(SSL_ERROR_EXPORT_ONLY_SERVER);
+ } else {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ }
+ SSL_DBG(("%d: SSL[%d]: no cipher overlap", SSL_GETPID(), ss->fd));
+ goto loser;
+ }
+ *pKeyLen = (bestRealKeySize + 7) >> 3;
+ return bestCypher;
+
+ loser:
+ return -1;
+}
+
+static SECStatus
+ssl2_ClientHandleServerCert(sslSocket *ss, PRUint8 *certData, int certLen)
+{
+ CERTCertificate *cert = NULL;
+ SECItem certItem;
+
+ certItem.data = certData;
+ certItem.len = certLen;
+
+ /* decode the certificate */
+ cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
+ PR_FALSE, PR_TRUE);
+
+ if (cert == NULL) {
+ SSL_DBG(("%d: SSL[%d]: decode of server certificate fails",
+ SSL_GETPID(), ss->fd));
+ PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+ return SECFailure;
+ }
+
+#ifdef TRACE
+ {
+ if (ssl_trace >= 1) {
+ char *issuer;
+ char *subject;
+ issuer = CERT_NameToAscii(&cert->issuer);
+ subject = CERT_NameToAscii(&cert->subject);
+ SSL_TRC(1,("%d: server certificate issuer: '%s'",
+ SSL_GETPID(), issuer ? issuer : "OOPS"));
+ SSL_TRC(1,("%d: server name: '%s'",
+ SSL_GETPID(), subject ? subject : "OOPS"));
+ PORT_Free(issuer);
+ PORT_Free(subject);
+ }
+ }
+#endif
+
+ ss->sec.peerCert = cert;
+ return SECSuccess;
+}
+
+
+/*
+ * Format one block of data for public/private key encryption using
+ * the rules defined in PKCS #1. SSL2 does this itself to handle the
+ * rollback detection.
+ */
+#define RSA_BLOCK_MIN_PAD_LEN 8
+#define RSA_BLOCK_FIRST_OCTET 0x00
+#define RSA_BLOCK_AFTER_PAD_OCTET 0x00
+#define RSA_BLOCK_PUBLIC_OCTET 0x02
+unsigned char *
+ssl_FormatSSL2Block(unsigned modulusLen, SECItem *data)
+{
+ unsigned char *block;
+ unsigned char *bp;
+ int padLen;
+ SECStatus rv;
+ int i;
+
+ if (modulusLen < data->len + (3 + RSA_BLOCK_MIN_PAD_LEN)) {
+ PORT_SetError(SEC_ERROR_BAD_KEY);
+ return NULL;
+ }
+ block = (unsigned char *) PORT_Alloc(modulusLen);
+ if (block == NULL)
+ return NULL;
+
+ bp = block;
+
+ /*
+ * All RSA blocks start with two octets:
+ * 0x00 || BlockType
+ */
+ *bp++ = RSA_BLOCK_FIRST_OCTET;
+ *bp++ = RSA_BLOCK_PUBLIC_OCTET;
+
+ /*
+ * 0x00 || BT || Pad || 0x00 || ActualData
+ * 1 1 padLen 1 data->len
+ * Pad is all non-zero random bytes.
+ */
+ padLen = modulusLen - data->len - 3;
+ PORT_Assert (padLen >= RSA_BLOCK_MIN_PAD_LEN);
+ rv = PK11_GenerateRandom(bp, padLen);
+ if (rv == SECFailure) goto loser;
+ /* replace all the 'zero' bytes */
+ for (i = 0; i < padLen; i++) {
+ while (bp[i] == RSA_BLOCK_AFTER_PAD_OCTET) {
+ rv = PK11_GenerateRandom(bp+i, 1);
+ if (rv == SECFailure) goto loser;
+ }
+ }
+ bp += padLen;
+ *bp++ = RSA_BLOCK_AFTER_PAD_OCTET;
+ PORT_Memcpy (bp, data->data, data->len);
+
+ return block;
+loser:
+ if (block) PORT_Free(block);
+ return NULL;
+}
+
+/*
+** Given the server's public key and cipher specs, generate a session key
+** that is ready to use for encrypting/decrypting the byte stream. At
+** the same time, generate the SSL_MT_CLIENT_MASTER_KEY message and
+** send it to the server.
+**
+** Called from ssl2_HandleServerHelloMessage()
+*/
+static SECStatus
+ssl2_ClientSetupSessionCypher(sslSocket *ss, PRUint8 *cs, int csLen)
+{
+ sslSessionID * sid;
+ PRUint8 * ca; /* points to iv data, or NULL if none. */
+ PRUint8 * ekbuf = 0;
+ CERTCertificate * cert = 0;
+ SECKEYPublicKey * serverKey = 0;
+ unsigned modulusLen = 0;
+ SECStatus rv;
+ int cipher;
+ int keyLen; /* cipher symkey size in bytes. */
+ int ckLen; /* publicly reveal this many bytes of key. */
+ int caLen; /* length of IV data at *ca. */
+ int nc;
+
+ unsigned char *eblock; /* holds unencrypted PKCS#1 formatted key. */
+ SECItem rek; /* holds portion of symkey to be encrypted. */
+
+ PRUint8 keyData[SSL_MAX_MASTER_KEY_BYTES];
+ PRUint8 iv [8];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ eblock = NULL;
+
+ sid = ss->sec.ci.sid;
+ PORT_Assert(sid != 0);
+
+ cert = ss->sec.peerCert;
+
+ serverKey = CERT_ExtractPublicKey(cert);
+ if (!serverKey) {
+ SSL_DBG(("%d: SSL[%d]: extract public key failed: error=%d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ PORT_SetError(SSL_ERROR_BAD_CERTIFICATE);
+ rv = SECFailure;
+ goto loser2;
+ }
+
+ ss->sec.authAlgorithm = ssl_sign_rsa;
+ ss->sec.keaType = ssl_kea_rsa;
+ ss->sec.keaKeyBits = \
+ ss->sec.authKeyBits = SECKEY_PublicKeyStrengthInBits(serverKey);
+
+ /* Choose a compatible cipher with the server */
+ nc = csLen / 3;
+ cipher = ssl2_ChooseSessionCypher(ss, nc, cs, &keyLen);
+ if (cipher < 0) {
+ /* ssl2_ChooseSessionCypher has set error code. */
+ ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS);
+ goto loser;
+ }
+
+ /* Generate the random keys */
+ PK11_GenerateRandom(keyData, sizeof(keyData));
+
+ /*
+ ** Next, carve up the keys into clear and encrypted portions. The
+ ** clear data is taken from the start of keyData and the encrypted
+ ** portion from the remainder. Note that each of these portions is
+ ** carved in half, one half for the read-key and one for the
+ ** write-key.
+ */
+ ca = 0;
+
+ /* We know that cipher is a legit value here, because
+ * ssl2_ChooseSessionCypher doesn't return bogus values.
+ */
+ ckLen = ssl_Specs[cipher].pubLen; /* cleartext key length. */
+ caLen = ssl_Specs[cipher].ivLen; /* IV length. */
+ if (caLen) {
+ PORT_Assert(sizeof iv >= caLen);
+ PK11_GenerateRandom(iv, caLen);
+ ca = iv;
+ }
+
+ /* Fill in session-id */
+ rv = ssl2_FillInSID(sid, cipher, keyData, keyLen,
+ ca, caLen, keyLen << 3, (keyLen - ckLen) << 3,
+ ss->sec.authAlgorithm, ss->sec.authKeyBits,
+ ss->sec.keaType, ss->sec.keaKeyBits);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ SSL_TRC(1, ("%d: SSL[%d]: client, using %s cipher, clear=%d total=%d",
+ SSL_GETPID(), ss->fd, ssl_cipherName[cipher],
+ ckLen<<3, keyLen<<3));
+
+ /* Now setup read and write ciphers */
+ rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /*
+ ** Fill in the encryption buffer with some random bytes. Then
+ ** copy in the portion of the session key we are encrypting.
+ */
+ modulusLen = SECKEY_PublicKeyStrength(serverKey);
+ rek.data = keyData + ckLen;
+ rek.len = keyLen - ckLen;
+ eblock = ssl_FormatSSL2Block(modulusLen, &rek);
+ if (eblock == NULL)
+ goto loser;
+
+ /* Set up the padding for version 2 rollback detection. */
+ /* XXX We should really use defines here */
+ if (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ PORT_Assert((modulusLen - rek.len) > 12);
+ PORT_Memset(eblock + modulusLen - rek.len - 8 - 1, 0x03, 8);
+ }
+ ekbuf = (PRUint8*) PORT_Alloc(modulusLen);
+ if (!ekbuf)
+ goto loser;
+ PRINT_BUF(10, (ss, "master key encryption block:",
+ eblock, modulusLen));
+
+ /* Encrypt ekitem */
+ rv = PK11_PubEncryptRaw(serverKey, ekbuf, eblock, modulusLen,
+ ss->pkcs11PinArg);
+ if (rv)
+ goto loser;
+
+ /* Now we have everything ready to send */
+ rv = ssl2_SendSessionKeyMessage(ss, cipher, keyLen << 3, ca, caLen,
+ keyData, ckLen, ekbuf, modulusLen);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = SECSuccess;
+ goto done;
+
+ loser:
+ rv = SECFailure;
+
+ loser2:
+ done:
+ PORT_Memset(keyData, 0, sizeof(keyData));
+ PORT_ZFree(ekbuf, modulusLen);
+ PORT_ZFree(eblock, modulusLen);
+ SECKEY_DestroyPublicKey(serverKey);
+ return rv;
+}
+
+/************************************************************************/
+
+/*
+ * Called from ssl2_HandleMessage in response to SSL_MT_SERVER_FINISHED message.
+ * Caller holds recvBufLock and handshakeLock
+ */
+static void
+ssl2_ClientRegSessionID(sslSocket *ss, PRUint8 *s)
+{
+ sslSessionID *sid = ss->sec.ci.sid;
+
+ /* Record entry in nonce cache */
+ if (sid->peerCert == NULL) {
+ PORT_Memcpy(sid->u.ssl2.sessionID, s, sizeof(sid->u.ssl2.sessionID));
+ sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
+
+ }
+ if (!ss->opt.noCache)
+ (*ss->sec.cache)(sid);
+}
+
+/* Called from ssl2_HandleMessage() */
+static SECStatus
+ssl2_TriggerNextMessage(sslSocket *ss)
+{
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ if ((ss->sec.ci.requiredElements & CIS_HAVE_CERTIFICATE) &&
+ !(ss->sec.ci.sentElements & CIS_HAVE_CERTIFICATE)) {
+ ss->sec.ci.sentElements |= CIS_HAVE_CERTIFICATE;
+ rv = ssl2_SendCertificateRequestMessage(ss);
+ return rv;
+ }
+ return SECSuccess;
+}
+
+/* See if it's time to send our finished message, or if the handshakes are
+** complete. Send finished message if appropriate.
+** Returns SECSuccess unless anything goes wrong.
+**
+** Called from ssl2_HandleMessage,
+** ssl2_HandleVerifyMessage
+** ssl2_HandleServerHelloMessage
+** ssl2_HandleClientSessionKeyMessage
+*/
+static SECStatus
+ssl2_TryToFinish(sslSocket *ss)
+{
+ SECStatus rv;
+ char e, ef;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ e = ss->sec.ci.elements;
+ ef = e | CIS_HAVE_FINISHED;
+ if ((ef & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) {
+ if (ss->sec.isServer) {
+ /* Send server finished message if we already didn't */
+ rv = ssl2_SendServerFinishedMessage(ss);
+ } else {
+ /* Send client finished message if we already didn't */
+ rv = ssl2_SendClientFinishedMessage(ss);
+ }
+ if (rv != SECSuccess) {
+ return rv;
+ }
+ if ((e & ss->sec.ci.requiredElements) == ss->sec.ci.requiredElements) {
+ /* Totally finished */
+ ss->handshake = 0;
+ return SECSuccess;
+ }
+ }
+ return SECSuccess;
+}
+
+/*
+** Called from ssl2_HandleRequestCertificate
+*/
+static SECStatus
+ssl2_SignResponse(sslSocket *ss,
+ SECKEYPrivateKey *key,
+ SECItem *response)
+{
+ SGNContext * sgn = NULL;
+ PRUint8 * challenge;
+ unsigned int len;
+ SECStatus rv = SECFailure;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ challenge = ss->sec.ci.serverChallenge;
+ len = ss->sec.ci.serverChallengeLen;
+
+ /* Sign the expected data... */
+ sgn = SGN_NewContext(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,key);
+ if (!sgn)
+ goto done;
+ rv = SGN_Begin(sgn);
+ if (rv != SECSuccess)
+ goto done;
+ rv = SGN_Update(sgn, ss->sec.ci.readKey, ss->sec.ci.keySize);
+ if (rv != SECSuccess)
+ goto done;
+ rv = SGN_Update(sgn, ss->sec.ci.writeKey, ss->sec.ci.keySize);
+ if (rv != SECSuccess)
+ goto done;
+ rv = SGN_Update(sgn, challenge, len);
+ if (rv != SECSuccess)
+ goto done;
+ rv = SGN_Update(sgn, ss->sec.peerCert->derCert.data,
+ ss->sec.peerCert->derCert.len);
+ if (rv != SECSuccess)
+ goto done;
+ rv = SGN_End(sgn, response);
+ if (rv != SECSuccess)
+ goto done;
+
+done:
+ SGN_DestroyContext(sgn, PR_TRUE);
+ return rv == SECSuccess ? SECSuccess : SECFailure;
+}
+
+/*
+** Try to handle a request-certificate message. Get client's certificate
+** and private key and sign a message for the server to see.
+** Caller must hold handshakeLock
+**
+** Called from ssl2_HandleMessage().
+*/
+static int
+ssl2_HandleRequestCertificate(sslSocket *ss)
+{
+ CERTCertificate * cert = NULL; /* app-selected client cert. */
+ SECKEYPrivateKey *key = NULL; /* priv key for cert. */
+ SECStatus rv;
+ SECItem response;
+ int ret = 0;
+ PRUint8 authType;
+
+
+ /*
+ * These things all need to be initialized before we can "goto loser".
+ */
+ response.data = NULL;
+
+ /* get challenge info from connectionInfo */
+ authType = ss->sec.ci.authType;
+
+ if (authType != SSL_AT_MD5_WITH_RSA_ENCRYPTION) {
+ SSL_TRC(7, ("%d: SSL[%d]: unsupported auth type 0x%x", SSL_GETPID(),
+ ss->fd, authType));
+ goto no_cert_error;
+ }
+
+ /* Get certificate and private-key from client */
+ if (!ss->getClientAuthData) {
+ SSL_TRC(7, ("%d: SSL[%d]: client doesn't support client-auth",
+ SSL_GETPID(), ss->fd));
+ goto no_cert_error;
+ }
+ ret = (*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd,
+ NULL, &cert, &key);
+ if ( ret == SECWouldBlock ) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ ret = -1;
+ goto loser;
+ }
+
+ if (ret) {
+ goto no_cert_error;
+ }
+
+ /* check what the callback function returned */
+ if ((!cert) || (!key)) {
+ /* we are missing either the key or cert */
+ if (cert) {
+ /* got a cert, but no key - free it */
+ CERT_DestroyCertificate(cert);
+ cert = NULL;
+ }
+ if (key) {
+ /* got a key, but no cert - free it */
+ SECKEY_DestroyPrivateKey(key);
+ key = NULL;
+ }
+ goto no_cert_error;
+ }
+
+ rv = ssl2_SignResponse(ss, key, &response);
+ if ( rv != SECSuccess ) {
+ ret = -1;
+ goto loser;
+ }
+
+ /* Send response message */
+ ret = ssl2_SendCertificateResponseMessage(ss, &cert->derCert, &response);
+
+ /* Now, remember the cert we sent. But first, forget any previous one. */
+ if (ss->sec.localCert) {
+ CERT_DestroyCertificate(ss->sec.localCert);
+ }
+ ss->sec.localCert = CERT_DupCertificate(cert);
+ PORT_Assert(!ss->sec.ci.sid->localCert);
+ if (ss->sec.ci.sid->localCert) {
+ CERT_DestroyCertificate(ss->sec.ci.sid->localCert);
+ }
+ ss->sec.ci.sid->localCert = cert;
+ cert = NULL;
+
+ goto done;
+
+ no_cert_error:
+ SSL_TRC(7, ("%d: SSL[%d]: no certificate (ret=%d)", SSL_GETPID(),
+ ss->fd, ret));
+ ret = ssl2_SendErrorMessage(ss, SSL_PE_NO_CERTIFICATE);
+
+ loser:
+ done:
+ if ( cert ) {
+ CERT_DestroyCertificate(cert);
+ }
+ if ( key ) {
+ SECKEY_DestroyPrivateKey(key);
+ }
+ if ( response.data ) {
+ PORT_Free(response.data);
+ }
+
+ return ret;
+}
+
+/*
+** Called from ssl2_HandleMessage for SSL_MT_CLIENT_CERTIFICATE message.
+** Caller must hold HandshakeLock and RecvBufLock, since cd and response
+** are contained in the gathered input data.
+*/
+static SECStatus
+ssl2_HandleClientCertificate(sslSocket * ss,
+ PRUint8 certType, /* XXX unused */
+ PRUint8 * cd,
+ unsigned int cdLen,
+ PRUint8 * response,
+ unsigned int responseLen)
+{
+ CERTCertificate *cert = NULL;
+ SECKEYPublicKey *pubKey = NULL;
+ VFYContext * vfy = NULL;
+ SECItem * derCert;
+ SECStatus rv = SECFailure;
+ SECItem certItem;
+ SECItem rep;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ /* Extract the certificate */
+ certItem.data = cd;
+ certItem.len = cdLen;
+
+ cert = CERT_NewTempCertificate(ss->dbHandle, &certItem, NULL,
+ PR_FALSE, PR_TRUE);
+ if (cert == NULL) {
+ goto loser;
+ }
+
+ /* save the certificate, since the auth routine will need it */
+ ss->sec.peerCert = cert;
+
+ /* Extract the public key */
+ pubKey = CERT_ExtractPublicKey(cert);
+ if (!pubKey)
+ goto loser;
+
+ /* Verify the response data... */
+ rep.data = response;
+ rep.len = responseLen;
+ /* SSL 2.0 only supports RSA certs, so we don't have to worry about
+ * DSA here. */
+ vfy = VFY_CreateContext(pubKey, &rep, SEC_OID_PKCS1_RSA_ENCRYPTION,
+ ss->pkcs11PinArg);
+ if (!vfy)
+ goto loser;
+ rv = VFY_Begin(vfy);
+ if (rv)
+ goto loser;
+
+ rv = VFY_Update(vfy, ss->sec.ci.readKey, ss->sec.ci.keySize);
+ if (rv)
+ goto loser;
+ rv = VFY_Update(vfy, ss->sec.ci.writeKey, ss->sec.ci.keySize);
+ if (rv)
+ goto loser;
+ rv = VFY_Update(vfy, ss->sec.ci.serverChallenge, SSL_CHALLENGE_BYTES);
+ if (rv)
+ goto loser;
+
+ derCert = &ss->serverCerts[kt_rsa].serverCert->derCert;
+ rv = VFY_Update(vfy, derCert->data, derCert->len);
+ if (rv)
+ goto loser;
+ rv = VFY_End(vfy);
+ if (rv)
+ goto loser;
+
+ /* Now ask the server application if it likes the certificate... */
+ rv = (SECStatus) (*ss->authCertificate)(ss->authCertificateArg,
+ ss->fd, PR_TRUE, PR_TRUE);
+ /* Hey, it liked it. */
+ if (SECSuccess == rv)
+ goto done;
+
+loser:
+ ss->sec.peerCert = NULL;
+ CERT_DestroyCertificate(cert);
+
+done:
+ VFY_DestroyContext(vfy, PR_TRUE);
+ SECKEY_DestroyPublicKey(pubKey);
+ return rv;
+}
+
+/*
+** Handle remaining messages between client/server. Process finished
+** messages from either side and any authentication requests.
+** This should only be called for SSLv2 handshake messages,
+** not for application data records.
+** Caller must hold handshake lock.
+**
+** Called from ssl_Do1stHandshake().
+**
+*/
+static SECStatus
+ssl2_HandleMessage(sslSocket *ss)
+{
+ PRUint8 * data;
+ PRUint8 * cid;
+ unsigned len, certType, certLen, responseLen;
+ int rv;
+ int rv2;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ssl_GetRecvBufLock(ss);
+
+ data = ss->gs.buf.buf + ss->gs.recordOffset;
+
+ if (ss->gs.recordLen < 1) {
+ goto bad_peer;
+ }
+ SSL_TRC(3, ("%d: SSL[%d]: received %d message",
+ SSL_GETPID(), ss->fd, data[0]));
+ DUMP_MSG(29, (ss, data, ss->gs.recordLen));
+
+ switch (data[0]) {
+ case SSL_MT_CLIENT_FINISHED:
+ if (ss->sec.ci.elements & CIS_HAVE_FINISHED) {
+ SSL_DBG(("%d: SSL[%d]: dup client-finished message",
+ SSL_GETPID(), ss->fd));
+ goto bad_peer;
+ }
+
+ /* See if nonce matches */
+ len = ss->gs.recordLen - 1;
+ cid = data + 1;
+ if ((len != sizeof(ss->sec.ci.connectionID)) ||
+ (PORT_Memcmp(ss->sec.ci.connectionID, cid, len) != 0)) {
+ SSL_DBG(("%d: SSL[%d]: bad connection-id", SSL_GETPID(), ss->fd));
+ PRINT_BUF(5, (ss, "sent connection-id",
+ ss->sec.ci.connectionID,
+ sizeof(ss->sec.ci.connectionID)));
+ PRINT_BUF(5, (ss, "rcvd connection-id", cid, len));
+ goto bad_peer;
+ }
+
+ SSL_TRC(5, ("%d: SSL[%d]: got client finished, waiting for 0x%d",
+ SSL_GETPID(), ss->fd,
+ ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
+ ss->sec.ci.elements |= CIS_HAVE_FINISHED;
+ break;
+
+ case SSL_MT_SERVER_FINISHED:
+ if (ss->sec.ci.elements & CIS_HAVE_FINISHED) {
+ SSL_DBG(("%d: SSL[%d]: dup server-finished message",
+ SSL_GETPID(), ss->fd));
+ goto bad_peer;
+ }
+
+ if (ss->gs.recordLen - 1 != SSL2_SESSIONID_BYTES) {
+ SSL_DBG(("%d: SSL[%d]: bad server-finished message, len=%d",
+ SSL_GETPID(), ss->fd, ss->gs.recordLen));
+ goto bad_peer;
+ }
+ ssl2_ClientRegSessionID(ss, data+1);
+ SSL_TRC(5, ("%d: SSL[%d]: got server finished, waiting for 0x%d",
+ SSL_GETPID(), ss->fd,
+ ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
+ ss->sec.ci.elements |= CIS_HAVE_FINISHED;
+ break;
+
+ case SSL_MT_REQUEST_CERTIFICATE:
+ len = ss->gs.recordLen - 2;
+ if ((len < SSL_MIN_CHALLENGE_BYTES) ||
+ (len > SSL_MAX_CHALLENGE_BYTES)) {
+ /* Bad challenge */
+ SSL_DBG(("%d: SSL[%d]: bad cert request message: code len=%d",
+ SSL_GETPID(), ss->fd, len));
+ goto bad_peer;
+ }
+
+ /* save auth request info */
+ ss->sec.ci.authType = data[1];
+ ss->sec.ci.serverChallengeLen = len;
+ PORT_Memcpy(ss->sec.ci.serverChallenge, data + 2, len);
+
+ rv = ssl2_HandleRequestCertificate(ss);
+ if (rv == SECWouldBlock) {
+ SSL_TRC(3, ("%d: SSL[%d]: async cert request",
+ SSL_GETPID(), ss->fd));
+ /* someone is handling this asynchronously */
+ ssl_ReleaseRecvBufLock(ss);
+ return SECWouldBlock;
+ }
+ if (rv) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+ break;
+
+ case SSL_MT_CLIENT_CERTIFICATE:
+ if (!ss->authCertificate) {
+ /* Server asked for authentication and can't handle it */
+ PORT_SetError(SSL_ERROR_BAD_SERVER);
+ goto loser;
+ }
+ if (ss->gs.recordLen < SSL_HL_CLIENT_CERTIFICATE_HBYTES) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+ certType = data[1];
+ certLen = (data[2] << 8) | data[3];
+ responseLen = (data[4] << 8) | data[5];
+ if (certType != SSL_CT_X509_CERTIFICATE) {
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
+ goto loser;
+ }
+ if (certLen + responseLen + SSL_HL_CLIENT_CERTIFICATE_HBYTES
+ > ss->gs.recordLen) {
+ /* prevent overflow crash. */
+ rv = SECFailure;
+ } else
+ rv = ssl2_HandleClientCertificate(ss, data[1],
+ data + SSL_HL_CLIENT_CERTIFICATE_HBYTES,
+ certLen,
+ data + SSL_HL_CLIENT_CERTIFICATE_HBYTES + certLen,
+ responseLen);
+ if (rv) {
+ rv2 = ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
+ SET_ERROR_CODE
+ goto loser;
+ }
+ ss->sec.ci.elements |= CIS_HAVE_CERTIFICATE;
+ break;
+
+ case SSL_MT_ERROR:
+ rv = (data[1] << 8) | data[2];
+ SSL_TRC(2, ("%d: SSL[%d]: got error message, error=0x%x",
+ SSL_GETPID(), ss->fd, rv));
+
+ /* Convert protocol error number into API error number */
+ switch (rv) {
+ case SSL_PE_NO_CYPHERS:
+ rv = SSL_ERROR_NO_CYPHER_OVERLAP;
+ break;
+ case SSL_PE_NO_CERTIFICATE:
+ rv = SSL_ERROR_NO_CERTIFICATE;
+ break;
+ case SSL_PE_BAD_CERTIFICATE:
+ rv = SSL_ERROR_BAD_CERTIFICATE;
+ break;
+ case SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE:
+ rv = SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE;
+ break;
+ default:
+ goto bad_peer;
+ }
+ /* XXX make certificate-request optionally fail... */
+ PORT_SetError(rv);
+ goto loser;
+
+ default:
+ SSL_DBG(("%d: SSL[%d]: unknown message %d",
+ SSL_GETPID(), ss->fd, data[0]));
+ goto loser;
+ }
+
+ SSL_TRC(3, ("%d: SSL[%d]: handled %d message, required=0x%x got=0x%x",
+ SSL_GETPID(), ss->fd, data[0],
+ ss->sec.ci.requiredElements, ss->sec.ci.elements));
+
+ rv = ssl2_TryToFinish(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ ss->gs.recordLen = 0;
+ ssl_ReleaseRecvBufLock(ss);
+
+ if (ss->handshake == 0) {
+ return SECSuccess;
+ }
+
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleMessage;
+ return ssl2_TriggerNextMessage(ss);
+
+ bad_peer:
+ PORT_SetError(ss->sec.isServer ? SSL_ERROR_BAD_CLIENT : SSL_ERROR_BAD_SERVER);
+ /* FALL THROUGH */
+
+ loser:
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+}
+
+/************************************************************************/
+
+/* Called from ssl_Do1stHandshake, after ssl2_HandleServerHelloMessage.
+*/
+static SECStatus
+ssl2_HandleVerifyMessage(sslSocket *ss)
+{
+ PRUint8 * data;
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ ssl_GetRecvBufLock(ss);
+
+ data = ss->gs.buf.buf + ss->gs.recordOffset;
+ DUMP_MSG(29, (ss, data, ss->gs.recordLen));
+ if ((ss->gs.recordLen != 1 + SSL_CHALLENGE_BYTES) ||
+ (data[0] != SSL_MT_SERVER_VERIFY) ||
+ NSS_SecureMemcmp(data+1, ss->sec.ci.clientChallenge,
+ SSL_CHALLENGE_BYTES)) {
+ /* Bad server */
+ PORT_SetError(SSL_ERROR_BAD_SERVER);
+ goto loser;
+ }
+ ss->sec.ci.elements |= CIS_HAVE_VERIFY;
+
+ SSL_TRC(5, ("%d: SSL[%d]: got server-verify, required=0x%d got=0x%x",
+ SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements,
+ ss->sec.ci.elements));
+
+ rv = ssl2_TryToFinish(ss);
+ if (rv)
+ goto loser;
+
+ ss->gs.recordLen = 0;
+ ssl_ReleaseRecvBufLock(ss);
+
+ if (ss->handshake == 0) {
+ return SECSuccess;
+ }
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleMessage;
+ return SECSuccess;
+
+
+ loser:
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+}
+
+/* Not static because ssl2_GatherData() tests ss->nextHandshake for this value.
+ * ICK!
+ * Called from ssl_Do1stHandshake after ssl2_BeginClientHandshake()
+ */
+SECStatus
+ssl2_HandleServerHelloMessage(sslSocket *ss)
+{
+ sslSessionID * sid;
+ PRUint8 * cert;
+ PRUint8 * cs;
+ PRUint8 * data;
+ SECStatus rv;
+ int needed, sidHit, certLen, csLen, cidLen, certType, err;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ if (!ss->opt.enableSSL2) {
+ PORT_SetError(SSL_ERROR_SSL2_DISABLED);
+ return SECFailure;
+ }
+
+ ssl_GetRecvBufLock(ss);
+
+ PORT_Assert(ss->sec.ci.sid != 0);
+ sid = ss->sec.ci.sid;
+
+ data = ss->gs.buf.buf + ss->gs.recordOffset;
+ DUMP_MSG(29, (ss, data, ss->gs.recordLen));
+
+ /* Make sure first message has some data and is the server hello message */
+ if ((ss->gs.recordLen < SSL_HL_SERVER_HELLO_HBYTES)
+ || (data[0] != SSL_MT_SERVER_HELLO)) {
+ if ((data[0] == SSL_MT_ERROR) && (ss->gs.recordLen == 3)) {
+ err = (data[1] << 8) | data[2];
+ if (err == SSL_PE_NO_CYPHERS) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ goto loser;
+ }
+ }
+ goto bad_server;
+ }
+
+ sidHit = data[1];
+ certType = data[2];
+ ss->version = (data[3] << 8) | data[4];
+ certLen = (data[5] << 8) | data[6];
+ csLen = (data[7] << 8) | data[8];
+ cidLen = (data[9] << 8) | data[10];
+ cert = data + SSL_HL_SERVER_HELLO_HBYTES;
+ cs = cert + certLen;
+
+ SSL_TRC(5,
+ ("%d: SSL[%d]: server-hello, hit=%d vers=%x certLen=%d csLen=%d cidLen=%d",
+ SSL_GETPID(), ss->fd, sidHit, ss->version, certLen,
+ csLen, cidLen));
+ if (ss->version != SSL_LIBRARY_VERSION_2) {
+ if (ss->version < SSL_LIBRARY_VERSION_2) {
+ SSL_TRC(3, ("%d: SSL[%d]: demoting self (%x) to server version (%x)",
+ SSL_GETPID(), ss->fd, SSL_LIBRARY_VERSION_2,
+ ss->version));
+ } else {
+ SSL_TRC(1, ("%d: SSL[%d]: server version is %x (we are %x)",
+ SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2));
+ /* server claims to be newer but does not follow protocol */
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
+ goto loser;
+ }
+ }
+
+ if ((SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen + cidLen
+ > ss->gs.recordLen)
+ || (csLen % 3) != 0
+ /* || cidLen < SSL_CONNECTIONID_BYTES || cidLen > 32 */
+ ) {
+ goto bad_server;
+ }
+
+ /* Save connection-id.
+ ** This code only saves the first 16 byte of the connectionID.
+ ** If the connectionID is shorter than 16 bytes, it is zero-padded.
+ */
+ if (cidLen < sizeof ss->sec.ci.connectionID)
+ memset(ss->sec.ci.connectionID, 0, sizeof ss->sec.ci.connectionID);
+ cidLen = PR_MIN(cidLen, sizeof ss->sec.ci.connectionID);
+ PORT_Memcpy(ss->sec.ci.connectionID, cs + csLen, cidLen);
+
+ /* See if session-id hit */
+ needed = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED | CIS_HAVE_VERIFY;
+ if (sidHit) {
+ if (certLen || csLen) {
+ /* Uh oh - bogus server */
+ SSL_DBG(("%d: SSL[%d]: client, huh? hit=%d certLen=%d csLen=%d",
+ SSL_GETPID(), ss->fd, sidHit, certLen, csLen));
+ goto bad_server;
+ }
+
+ /* Total winner. */
+ SSL_TRC(1, ("%d: SSL[%d]: client, using nonce for peer=0x%08x "
+ "port=0x%04x",
+ SSL_GETPID(), ss->fd, ss->sec.ci.peer, ss->sec.ci.port));
+ ss->sec.peerCert = CERT_DupCertificate(sid->peerCert);
+ ss->sec.authAlgorithm = sid->authAlgorithm;
+ ss->sec.authKeyBits = sid->authKeyBits;
+ ss->sec.keaType = sid->keaType;
+ ss->sec.keaKeyBits = sid->keaKeyBits;
+ rv = ssl2_CreateSessionCypher(ss, sid, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ } else {
+ if (certType != SSL_CT_X509_CERTIFICATE) {
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE);
+ goto loser;
+ }
+ if (csLen == 0) {
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ SSL_DBG(("%d: SSL[%d]: no cipher overlap",
+ SSL_GETPID(), ss->fd));
+ goto loser;
+ }
+ if (certLen == 0) {
+ SSL_DBG(("%d: SSL[%d]: client, huh? certLen=%d csLen=%d",
+ SSL_GETPID(), ss->fd, certLen, csLen));
+ goto bad_server;
+ }
+
+ if (sid->cached != never_cached) {
+ /* Forget our session-id - server didn't like it */
+ SSL_TRC(7, ("%d: SSL[%d]: server forgot me, uncaching session-id",
+ SSL_GETPID(), ss->fd));
+ if (ss->sec.uncache)
+ (*ss->sec.uncache)(sid);
+ ssl_FreeSID(sid);
+ ss->sec.ci.sid = sid = PORT_ZNew(sslSessionID);
+ if (!sid) {
+ goto loser;
+ }
+ sid->references = 1;
+ sid->addr = ss->sec.ci.peer;
+ sid->port = ss->sec.ci.port;
+ }
+
+ /* decode the server's certificate */
+ rv = ssl2_ClientHandleServerCert(ss, cert, certLen);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) {
+ (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
+ }
+ goto loser;
+ }
+
+ /* Setup new session cipher */
+ rv = ssl2_ClientSetupSessionCypher(ss, cs, csLen);
+ if (rv != SECSuccess) {
+ if (PORT_GetError() == SSL_ERROR_BAD_CERTIFICATE) {
+ (void) ssl2_SendErrorMessage(ss, SSL_PE_BAD_CERTIFICATE);
+ }
+ goto loser;
+ }
+ }
+
+ /* Build up final list of required elements */
+ ss->sec.ci.elements = CIS_HAVE_MASTER_KEY;
+ ss->sec.ci.requiredElements = needed;
+
+ if (!sidHit) {
+ /* verify the server's certificate. if sidHit, don't check signatures */
+ rv = (* ss->authCertificate)(ss->authCertificateArg, ss->fd,
+ (PRBool)(!sidHit), PR_FALSE);
+ if (rv) {
+ if (ss->handleBadCert) {
+ rv = (*ss->handleBadCert)(ss->badCertArg, ss->fd);
+ if ( rv ) {
+ if ( rv == SECWouldBlock ) {
+ SSL_DBG(("%d: SSL[%d]: SSL2 bad cert handler returned "
+ "SECWouldBlock", SSL_GETPID(), ss->fd));
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ rv = SECFailure;
+ } else {
+ /* cert is bad */
+ SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ }
+ goto loser;
+ }
+ /* cert is good */
+ } else {
+ SSL_DBG(("%d: SSL[%d]: server certificate is no good: error=%d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ goto loser;
+ }
+ }
+ }
+ /*
+ ** At this point we have a completed session key and our session
+ ** cipher is setup and ready to go. Switch to encrypted write routine
+ ** as all future message data is to be encrypted.
+ */
+ ssl2_UseEncryptedSendFunc(ss);
+
+ rv = ssl2_TryToFinish(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ ss->gs.recordLen = 0;
+
+ ssl_ReleaseRecvBufLock(ss);
+
+ if (ss->handshake == 0) {
+ return SECSuccess;
+ }
+
+ SSL_TRC(5, ("%d: SSL[%d]: got server-hello, required=0x%d got=0x%x",
+ SSL_GETPID(), ss->fd, ss->sec.ci.requiredElements,
+ ss->sec.ci.elements));
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleVerifyMessage;
+ return SECSuccess;
+
+ bad_server:
+ PORT_SetError(SSL_ERROR_BAD_SERVER);
+ /* FALL THROUGH */
+
+ loser:
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+}
+
+/* Sends out the initial client Hello message on the connection.
+ * Acquires and releases the socket's xmitBufLock.
+ */
+SECStatus
+ssl2_BeginClientHandshake(sslSocket *ss)
+{
+ sslSessionID *sid;
+ PRUint8 *msg;
+ PRUint8 *cp;
+ PRUint8 *localCipherSpecs = NULL;
+ unsigned int localCipherSize;
+ unsigned int i;
+ int sendLen, sidLen = 0;
+ SECStatus rv;
+ TLSExtensionData *xtnData;
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ ss->sec.isServer = 0;
+ ss->sec.sendSequence = 0;
+ ss->sec.rcvSequence = 0;
+ ssl_ChooseSessionIDProcs(&ss->sec);
+
+ if (!ss->cipherSpecs) {
+ rv = ssl2_ConstructCipherSpecs(ss);
+ if (rv != SECSuccess)
+ goto loser;
+ }
+
+ /* count the SSL2 and SSL3 enabled ciphers.
+ * if either is zero, clear the socket's enable for that protocol.
+ */
+ rv = ssl2_CheckConfigSanity(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ /* Get peer name of server */
+ rv = ssl_GetPeerInfo(ss);
+ if (rv < 0) {
+#ifdef HPUX11
+ /*
+ * On some HP-UX B.11.00 systems, getpeername() occasionally
+ * fails with ENOTCONN after a successful completion of
+ * non-blocking connect. I found that if we do a write()
+ * and then retry getpeername(), it will work.
+ */
+ if (PR_GetError() == PR_NOT_CONNECTED_ERROR) {
+ char dummy;
+ (void) PR_Write(ss->fd->lower, &dummy, 0);
+ rv = ssl_GetPeerInfo(ss);
+ if (rv < 0) {
+ goto loser;
+ }
+ }
+#else
+ goto loser;
+#endif
+ }
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending client-hello", SSL_GETPID(), ss->fd));
+
+ /* Try to find server in our session-id cache */
+ if (ss->opt.noCache) {
+ sid = NULL;
+ } else {
+ sid = ssl_LookupSID(&ss->sec.ci.peer, ss->sec.ci.port, ss->peerID,
+ ss->url);
+ }
+ while (sid) { /* this isn't really a loop */
+ PRBool sidVersionEnabled =
+ (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange) &&
+ sid->version >= ss->vrange.min &&
+ sid->version <= ss->vrange.max) ||
+ (sid->version < SSL_LIBRARY_VERSION_3_0 && ss->opt.enableSSL2);
+
+ /* if we're not doing this SID's protocol any more, drop it. */
+ if (!sidVersionEnabled) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid);
+ ssl_FreeSID(sid);
+ sid = NULL;
+ break;
+ }
+ if (sid->version < SSL_LIBRARY_VERSION_3_0) {
+ /* If the cipher in this sid is not enabled, drop it. */
+ for (i = 0; i < ss->sizeCipherSpecs; i += 3) {
+ if (ss->cipherSpecs[i] == sid->u.ssl2.cipherType)
+ break;
+ }
+ if (i >= ss->sizeCipherSpecs) {
+ if (ss->sec.uncache)
+ ss->sec.uncache(sid);
+ ssl_FreeSID(sid);
+ sid = NULL;
+ break;
+ }
+ }
+ sidLen = sizeof(sid->u.ssl2.sessionID);
+ PRINT_BUF(4, (ss, "client, found session-id:", sid->u.ssl2.sessionID,
+ sidLen));
+ ss->version = sid->version;
+ PORT_Assert(!ss->sec.localCert);
+ if (ss->sec.localCert) {
+ CERT_DestroyCertificate(ss->sec.localCert);
+ }
+ ss->sec.localCert = CERT_DupCertificate(sid->localCert);
+ break; /* this isn't really a loop */
+ }
+ if (!sid) {
+ sidLen = 0;
+ sid = PORT_ZNew(sslSessionID);
+ if (!sid) {
+ goto loser;
+ }
+ sid->references = 1;
+ sid->cached = never_cached;
+ sid->addr = ss->sec.ci.peer;
+ sid->port = ss->sec.ci.port;
+ if (ss->peerID != NULL) {
+ sid->peerID = PORT_Strdup(ss->peerID);
+ }
+ if (ss->url != NULL) {
+ sid->urlSvrName = PORT_Strdup(ss->url);
+ }
+ }
+ ss->sec.ci.sid = sid;
+
+ PORT_Assert(sid != NULL);
+
+ if ((sid->version >= SSL_LIBRARY_VERSION_3_0 || !ss->opt.v2CompatibleHello) &&
+ !SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ ss->gs.state = GS_INIT;
+ ss->handshake = ssl_GatherRecord1stHandshake;
+
+ /* ssl3_SendClientHello will override this if it succeeds. */
+ ss->version = SSL_LIBRARY_VERSION_3_0;
+
+ ssl_GetSSL3HandshakeLock(ss);
+ ssl_GetXmitBufLock(ss);
+ rv = ssl3_SendClientHello(ss, PR_FALSE);
+ ssl_ReleaseXmitBufLock(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+
+ return rv;
+ }
+#if defined(NSS_ENABLE_ECC)
+ /* ensure we don't neogtiate ECC cipher suites with SSL2 hello */
+ ssl3_DisableECCSuites(ss, NULL); /* disable all ECC suites */
+ if (ss->cipherSpecs != NULL) {
+ PORT_Free(ss->cipherSpecs);
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0;
+ }
+#endif
+
+ if (!ss->cipherSpecs) {
+ rv = ssl2_ConstructCipherSpecs(ss);
+ if (rv < 0) {
+ return rv;
+ }
+ }
+ localCipherSpecs = ss->cipherSpecs;
+ localCipherSize = ss->sizeCipherSpecs;
+
+ /* Add 3 for SCSV */
+ sendLen = SSL_HL_CLIENT_HELLO_HBYTES + localCipherSize + 3 + sidLen +
+ SSL_CHALLENGE_BYTES;
+
+ /* Generate challenge bytes for server */
+ PK11_GenerateRandom(ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);
+
+ ssl_GetXmitBufLock(ss); /***************************************/
+
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv)
+ goto unlock_loser;
+
+ /* Construct client-hello message */
+ cp = msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_CLIENT_HELLO;
+ ss->clientHelloVersion = SSL3_ALL_VERSIONS_DISABLED(&ss->vrange) ?
+ SSL_LIBRARY_VERSION_2 : ss->vrange.max;
+
+ msg[1] = MSB(ss->clientHelloVersion);
+ msg[2] = LSB(ss->clientHelloVersion);
+ /* Add 3 for SCSV */
+ msg[3] = MSB(localCipherSize + 3);
+ msg[4] = LSB(localCipherSize + 3);
+ msg[5] = MSB(sidLen);
+ msg[6] = LSB(sidLen);
+ msg[7] = MSB(SSL_CHALLENGE_BYTES);
+ msg[8] = LSB(SSL_CHALLENGE_BYTES);
+ cp += SSL_HL_CLIENT_HELLO_HBYTES;
+ PORT_Memcpy(cp, localCipherSpecs, localCipherSize);
+ cp += localCipherSize;
+ /*
+ * Add SCSV. SSL 2.0 cipher suites are listed before SSL 3.0 cipher
+ * suites in localCipherSpecs for compatibility with SSL 2.0 servers.
+ * Since SCSV looks like an SSL 3.0 cipher suite, we can't add it at
+ * the beginning.
+ */
+ cp[0] = 0x00;
+ cp[1] = 0x00;
+ cp[2] = 0xff;
+ cp += 3;
+ if (sidLen) {
+ PORT_Memcpy(cp, sid->u.ssl2.sessionID, sidLen);
+ cp += sidLen;
+ }
+ PORT_Memcpy(cp, ss->sec.ci.clientChallenge, SSL_CHALLENGE_BYTES);
+
+ /* Send it to the server */
+ DUMP_MSG(29, (ss, msg, sendLen));
+ ss->handshakeBegun = 1;
+ rv = (*ss->sec.send)(ss, msg, sendLen, 0);
+
+ ssl_ReleaseXmitBufLock(ss); /***************************************/
+
+ if (rv < 0) {
+ goto loser;
+ }
+
+ rv = ssl3_StartHandshakeHash(ss, msg, sendLen);
+ if (rv < 0) {
+ goto loser;
+ }
+
+ /*
+ * Since we sent the SCSV, pretend we sent empty RI extension. We need
+ * to record the extension has been advertised after ssl3_InitState has
+ * been called, which ssl3_StartHandshakeHash took care for us above.
+ */
+ xtnData = &ss->xtnData;
+ xtnData->advertised[xtnData->numAdvertised++] = ssl_renegotiation_info_xtn;
+
+ /* Setup to receive servers hello message */
+ ssl_GetRecvBufLock(ss);
+ ss->gs.recordLen = 0;
+ ssl_ReleaseRecvBufLock(ss);
+
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleServerHelloMessage;
+ return SECSuccess;
+
+unlock_loser:
+ ssl_ReleaseXmitBufLock(ss);
+loser:
+ return SECFailure;
+}
+
+/************************************************************************/
+
+/* Handle the CLIENT-MASTER-KEY message.
+** Acquires and releases RecvBufLock.
+** Called from ssl2_HandleClientHelloMessage().
+*/
+static SECStatus
+ssl2_HandleClientSessionKeyMessage(sslSocket *ss)
+{
+ PRUint8 * data;
+ unsigned int caLen;
+ unsigned int ckLen;
+ unsigned int ekLen;
+ unsigned int keyBits;
+ int cipher;
+ SECStatus rv;
+
+
+ ssl_GetRecvBufLock(ss);
+
+ data = ss->gs.buf.buf + ss->gs.recordOffset;
+ DUMP_MSG(29, (ss, data, ss->gs.recordLen));
+
+ if ((ss->gs.recordLen < SSL_HL_CLIENT_MASTER_KEY_HBYTES)
+ || (data[0] != SSL_MT_CLIENT_MASTER_KEY)) {
+ goto bad_client;
+ }
+ cipher = data[1];
+ keyBits = (data[2] << 8) | data[3];
+ ckLen = (data[4] << 8) | data[5];
+ ekLen = (data[6] << 8) | data[7];
+ caLen = (data[8] << 8) | data[9];
+
+ SSL_TRC(5, ("%d: SSL[%d]: session-key, cipher=%d keyBits=%d ckLen=%d ekLen=%d caLen=%d",
+ SSL_GETPID(), ss->fd, cipher, keyBits, ckLen, ekLen, caLen));
+
+ if (ss->gs.recordLen <
+ SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen + caLen) {
+ SSL_DBG(("%d: SSL[%d]: protocol size mismatch dataLen=%d",
+ SSL_GETPID(), ss->fd, ss->gs.recordLen));
+ goto bad_client;
+ }
+
+ /* Use info from client to setup session key */
+ rv = ssl2_ServerSetupSessionCypher(ss, cipher, keyBits,
+ data + SSL_HL_CLIENT_MASTER_KEY_HBYTES, ckLen,
+ data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen, ekLen,
+ data + SSL_HL_CLIENT_MASTER_KEY_HBYTES + ckLen + ekLen, caLen);
+ ss->gs.recordLen = 0; /* we're done with this record. */
+
+ ssl_ReleaseRecvBufLock(ss);
+
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ ss->sec.ci.elements |= CIS_HAVE_MASTER_KEY;
+ ssl2_UseEncryptedSendFunc(ss);
+
+ /* Send server verify message now that keys are established */
+ rv = ssl2_SendServerVerifyMessage(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ rv = ssl2_TryToFinish(ss);
+ if (rv != SECSuccess)
+ goto loser;
+ if (ss->handshake == 0) {
+ return SECSuccess;
+ }
+
+ SSL_TRC(5, ("%d: SSL[%d]: server: waiting for elements=0x%d",
+ SSL_GETPID(), ss->fd,
+ ss->sec.ci.requiredElements ^ ss->sec.ci.elements));
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleMessage;
+
+ return ssl2_TriggerNextMessage(ss);
+
+bad_client:
+ ssl_ReleaseRecvBufLock(ss);
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ /* FALLTHROUGH */
+
+loser:
+ return SECFailure;
+}
+
+/*
+** Handle the initial hello message from the client
+**
+** not static because ssl2_GatherData() tests ss->nextHandshake for this value.
+*/
+SECStatus
+ssl2_HandleClientHelloMessage(sslSocket *ss)
+{
+ sslSessionID *sid;
+ sslServerCerts * sc;
+ CERTCertificate *serverCert;
+ PRUint8 *msg;
+ PRUint8 *data;
+ PRUint8 *cs;
+ PRUint8 *sd;
+ PRUint8 *cert = NULL;
+ PRUint8 *challenge;
+ unsigned int challengeLen;
+ SECStatus rv;
+ int csLen;
+ int sendLen;
+ int sdLen;
+ int certLen;
+ int pid;
+ int sent;
+ int gotXmitBufLock = 0;
+#if defined(SOLARIS) && defined(i386)
+ volatile PRUint8 hit;
+#else
+ int hit;
+#endif
+ PRUint8 csImpl[sizeof implementedCipherSuites];
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ sc = ss->serverCerts + kt_rsa;
+ serverCert = sc->serverCert;
+
+ ssl_GetRecvBufLock(ss);
+
+
+ data = ss->gs.buf.buf + ss->gs.recordOffset;
+ DUMP_MSG(29, (ss, data, ss->gs.recordLen));
+
+ /* Make sure first message has some data and is the client hello message */
+ if ((ss->gs.recordLen < SSL_HL_CLIENT_HELLO_HBYTES)
+ || (data[0] != SSL_MT_CLIENT_HELLO)) {
+ goto bad_client;
+ }
+
+ /* Get peer name of client */
+ rv = ssl_GetPeerInfo(ss);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Examine version information */
+ /*
+ * See if this might be a V2 client hello asking to use the V3 protocol
+ */
+ if ((data[0] == SSL_MT_CLIENT_HELLO) &&
+ (data[1] >= MSB(SSL_LIBRARY_VERSION_3_0)) &&
+ !SSL3_ALL_VERSIONS_DISABLED(&ss->vrange)) {
+ rv = ssl3_HandleV2ClientHello(ss, data, ss->gs.recordLen);
+ if (rv != SECFailure) { /* Success */
+ ss->handshake = NULL;
+ ss->nextHandshake = ssl_GatherRecord1stHandshake;
+ ss->securityHandshake = NULL;
+ ss->gs.state = GS_INIT;
+
+ /* ssl3_HandleV3ClientHello has set ss->version,
+ ** and has gotten us a brand new sid.
+ */
+ ss->sec.ci.sid->version = ss->version;
+ }
+ ssl_ReleaseRecvBufLock(ss);
+ return rv;
+ }
+ /* Previously, there was a test here to see if SSL2 was enabled.
+ ** If not, an error code was set, and SECFailure was returned,
+ ** without sending any error code to the other end of the connection.
+ ** That test has been removed. If SSL2 has been disabled, there
+ ** should be no SSL2 ciphers enabled, and consequently, the code
+ ** below should send the ssl2 error message SSL_PE_NO_CYPHERS.
+ ** We now believe this is the correct thing to do, even when SSL2
+ ** has been explicitly disabled by the application.
+ */
+
+ /* Extract info from message */
+ ss->version = (data[1] << 8) | data[2];
+
+ /* If some client thinks ssl v2 is 2.0 instead of 0.2, we'll allow it. */
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
+ ss->version = SSL_LIBRARY_VERSION_2;
+ }
+
+ csLen = (data[3] << 8) | data[4];
+ sdLen = (data[5] << 8) | data[6];
+ challengeLen = (data[7] << 8) | data[8];
+ cs = data + SSL_HL_CLIENT_HELLO_HBYTES;
+ sd = cs + csLen;
+ challenge = sd + sdLen;
+ PRINT_BUF(7, (ss, "server, client session-id value:", sd, sdLen));
+
+ if (!csLen || (csLen % 3) != 0 ||
+ (sdLen != 0 && sdLen != SSL2_SESSIONID_BYTES) ||
+ challengeLen < SSL_MIN_CHALLENGE_BYTES ||
+ challengeLen > SSL_MAX_CHALLENGE_BYTES ||
+ (unsigned)ss->gs.recordLen !=
+ SSL_HL_CLIENT_HELLO_HBYTES + csLen + sdLen + challengeLen) {
+ SSL_DBG(("%d: SSL[%d]: bad client hello message, len=%d should=%d",
+ SSL_GETPID(), ss->fd, ss->gs.recordLen,
+ SSL_HL_CLIENT_HELLO_HBYTES+csLen+sdLen+challengeLen));
+ goto bad_client;
+ }
+
+ SSL_TRC(3, ("%d: SSL[%d]: client version is %x",
+ SSL_GETPID(), ss->fd, ss->version));
+ if (ss->version != SSL_LIBRARY_VERSION_2) {
+ if (ss->version > SSL_LIBRARY_VERSION_2) {
+ /*
+ ** Newer client than us. Things are ok because new clients
+ ** are required to be backwards compatible with old servers.
+ ** Change version number to our version number so that client
+ ** knows whats up.
+ */
+ ss->version = SSL_LIBRARY_VERSION_2;
+ } else {
+ SSL_TRC(1, ("%d: SSL[%d]: client version is %x (we are %x)",
+ SSL_GETPID(), ss->fd, ss->version, SSL_LIBRARY_VERSION_2));
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
+ goto loser;
+ }
+ }
+
+ /* Qualify cipher specs before returning them to client */
+ csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen);
+ if (csLen == 0) {
+ /* no overlap, send client our list of supported SSL v2 ciphers. */
+ cs = csImpl;
+ csLen = sizeof implementedCipherSuites;
+ PORT_Memcpy(cs, implementedCipherSuites, csLen);
+ csLen = ssl2_QualifyCypherSpecs(ss, cs, csLen);
+ if (csLen == 0) {
+ /* We don't support any SSL v2 ciphers! */
+ ssl2_SendErrorMessage(ss, SSL_PE_NO_CYPHERS);
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ goto loser;
+ }
+ /* Since this handhsake is going to fail, don't cache it. */
+ ss->opt.noCache = 1;
+ }
+
+ /* Squirrel away the challenge for later */
+ PORT_Memcpy(ss->sec.ci.clientChallenge, challenge, challengeLen);
+
+ /* Examine message and see if session-id is good */
+ ss->sec.ci.elements = 0;
+ if (sdLen > 0 && !ss->opt.noCache) {
+ SSL_TRC(7, ("%d: SSL[%d]: server, lookup client session-id for 0x%08x%08x%08x%08x",
+ SSL_GETPID(), ss->fd, ss->sec.ci.peer.pr_s6_addr32[0],
+ ss->sec.ci.peer.pr_s6_addr32[1],
+ ss->sec.ci.peer.pr_s6_addr32[2],
+ ss->sec.ci.peer.pr_s6_addr32[3]));
+ sid = (*ssl_sid_lookup)(&ss->sec.ci.peer, sd, sdLen, ss->dbHandle);
+ } else {
+ sid = NULL;
+ }
+ if (sid) {
+ /* Got a good session-id. Short cut! */
+ SSL_TRC(1, ("%d: SSL[%d]: server, using session-id for 0x%08x (age=%d)",
+ SSL_GETPID(), ss->fd, ss->sec.ci.peer,
+ ssl_Time() - sid->creationTime));
+ PRINT_BUF(1, (ss, "session-id value:", sd, sdLen));
+ ss->sec.ci.sid = sid;
+ ss->sec.ci.elements = CIS_HAVE_MASTER_KEY;
+ hit = 1;
+ certLen = 0;
+ csLen = 0;
+
+ ss->sec.authAlgorithm = sid->authAlgorithm;
+ ss->sec.authKeyBits = sid->authKeyBits;
+ ss->sec.keaType = sid->keaType;
+ ss->sec.keaKeyBits = sid->keaKeyBits;
+
+ rv = ssl2_CreateSessionCypher(ss, sid, PR_FALSE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ } else {
+ SECItem * derCert = &serverCert->derCert;
+
+ SSL_TRC(7, ("%d: SSL[%d]: server, lookup nonce missed",
+ SSL_GETPID(), ss->fd));
+ if (!serverCert) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+ hit = 0;
+ sid = PORT_ZNew(sslSessionID);
+ if (!sid) {
+ goto loser;
+ }
+ sid->references = 1;
+ sid->addr = ss->sec.ci.peer;
+ sid->port = ss->sec.ci.port;
+
+ /* Invent a session-id */
+ ss->sec.ci.sid = sid;
+ PK11_GenerateRandom(sid->u.ssl2.sessionID+2, SSL2_SESSIONID_BYTES-2);
+
+ pid = SSL_GETPID();
+ sid->u.ssl2.sessionID[0] = MSB(pid);
+ sid->u.ssl2.sessionID[1] = LSB(pid);
+ cert = derCert->data;
+ certLen = derCert->len;
+
+ /* pretend that server sids remember the local cert. */
+ PORT_Assert(!sid->localCert);
+ if (sid->localCert) {
+ CERT_DestroyCertificate(sid->localCert);
+ }
+ sid->localCert = CERT_DupCertificate(serverCert);
+
+ ss->sec.authAlgorithm = ssl_sign_rsa;
+ ss->sec.keaType = ssl_kea_rsa;
+ ss->sec.keaKeyBits = \
+ ss->sec.authKeyBits = ss->serverCerts[kt_rsa].serverKeyBits;
+ }
+
+ /* server sids don't remember the local cert, so whether we found
+ ** a sid or not, just "remember" we used the rsa server cert.
+ */
+ if (ss->sec.localCert) {
+ CERT_DestroyCertificate(ss->sec.localCert);
+ }
+ ss->sec.localCert = CERT_DupCertificate(serverCert);
+
+ /* Build up final list of required elements */
+ ss->sec.ci.requiredElements = CIS_HAVE_MASTER_KEY | CIS_HAVE_FINISHED;
+ if (ss->opt.requestCertificate) {
+ ss->sec.ci.requiredElements |= CIS_HAVE_CERTIFICATE;
+ }
+ ss->sec.ci.sentElements = 0;
+
+ /* Send hello message back to client */
+ sendLen = SSL_HL_SERVER_HELLO_HBYTES + certLen + csLen
+ + SSL_CONNECTIONID_BYTES;
+
+ ssl_GetXmitBufLock(ss); gotXmitBufLock = 1;
+ rv = ssl2_GetSendBuffer(ss, sendLen);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ SSL_TRC(3, ("%d: SSL[%d]: sending server-hello (%d)",
+ SSL_GETPID(), ss->fd, sendLen));
+
+ msg = ss->sec.ci.sendBuf.buf;
+ msg[0] = SSL_MT_SERVER_HELLO;
+ msg[1] = hit;
+ msg[2] = SSL_CT_X509_CERTIFICATE;
+ msg[3] = MSB(ss->version);
+ msg[4] = LSB(ss->version);
+ msg[5] = MSB(certLen);
+ msg[6] = LSB(certLen);
+ msg[7] = MSB(csLen);
+ msg[8] = LSB(csLen);
+ msg[9] = MSB(SSL_CONNECTIONID_BYTES);
+ msg[10] = LSB(SSL_CONNECTIONID_BYTES);
+ if (certLen) {
+ PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES, cert, certLen);
+ }
+ if (csLen) {
+ PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen, cs, csLen);
+ }
+ PORT_Memcpy(msg+SSL_HL_SERVER_HELLO_HBYTES+certLen+csLen,
+ ss->sec.ci.connectionID, SSL_CONNECTIONID_BYTES);
+
+ DUMP_MSG(29, (ss, msg, sendLen));
+
+ ss->handshakeBegun = 1;
+ sent = (*ss->sec.send)(ss, msg, sendLen, 0);
+ if (sent < 0) {
+ goto loser;
+ }
+ ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0;
+
+ ss->gs.recordLen = 0;
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ if (hit) {
+ /* Old SID Session key is good. Go encrypted */
+ ssl2_UseEncryptedSendFunc(ss);
+
+ /* Send server verify message now that keys are established */
+ rv = ssl2_SendServerVerifyMessage(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ ss->nextHandshake = ssl2_HandleMessage;
+ ssl_ReleaseRecvBufLock(ss);
+ rv = ssl2_TriggerNextMessage(ss);
+ return rv;
+ }
+ ss->nextHandshake = ssl2_HandleClientSessionKeyMessage;
+ ssl_ReleaseRecvBufLock(ss);
+ return SECSuccess;
+
+ bad_client:
+ PORT_SetError(SSL_ERROR_BAD_CLIENT);
+ /* FALLTHROUGH */
+
+ loser:
+ if (gotXmitBufLock) {
+ ssl_ReleaseXmitBufLock(ss); gotXmitBufLock = 0;
+ }
+ SSL_TRC(10, ("%d: SSL[%d]: server, wait for client-hello lossage",
+ SSL_GETPID(), ss->fd));
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+}
+
+SECStatus
+ssl2_BeginServerHandshake(sslSocket *ss)
+{
+ SECStatus rv;
+ sslServerCerts * rsaAuth = ss->serverCerts + kt_rsa;
+
+ ss->sec.isServer = 1;
+ ssl_ChooseSessionIDProcs(&ss->sec);
+ ss->sec.sendSequence = 0;
+ ss->sec.rcvSequence = 0;
+
+ /* don't turn on SSL2 if we don't have an RSA key and cert */
+ if (!rsaAuth->serverKeyPair || !rsaAuth->SERVERKEY ||
+ !rsaAuth->serverCert) {
+ ss->opt.enableSSL2 = PR_FALSE;
+ }
+
+ if (!ss->cipherSpecs) {
+ rv = ssl2_ConstructCipherSpecs(ss);
+ if (rv != SECSuccess)
+ goto loser;
+ }
+
+ /* count the SSL2 and SSL3 enabled ciphers.
+ * if either is zero, clear the socket's enable for that protocol.
+ */
+ rv = ssl2_CheckConfigSanity(ss);
+ if (rv != SECSuccess)
+ goto loser;
+
+ /*
+ ** Generate connection-id. Always do this, even if things fail
+ ** immediately. This way the random number generator is always
+ ** rolling around, every time we get a connection.
+ */
+ PK11_GenerateRandom(ss->sec.ci.connectionID,
+ sizeof(ss->sec.ci.connectionID));
+
+ ss->gs.recordLen = 0;
+ ss->handshake = ssl_GatherRecord1stHandshake;
+ ss->nextHandshake = ssl2_HandleClientHelloMessage;
+ return SECSuccess;
+
+loser:
+ return SECFailure;
+}
+
+/* This function doesn't really belong in this file.
+** It's here to keep AIX compilers from optimizing it away,
+** and not including it in the DSO.
+*/
+
+#include "nss.h"
+extern const char __nss_ssl_rcsid[];
+extern const char __nss_ssl_sccsid[];
+
+PRBool
+NSSSSL_VersionCheck(const char *importedVersion)
+{
+ /*
+ * This is the secret handshake algorithm.
+ *
+ * This release has a simple version compatibility
+ * check algorithm. This release is not backward
+ * compatible with previous major releases. It is
+ * not compatible with future major, minor, or
+ * patch releases.
+ */
+ volatile char c; /* force a reference that won't get optimized away */
+
+ c = __nss_ssl_rcsid[0] + __nss_ssl_sccsid[0];
+ return NSS_VersionCheck(importedVersion);
+}
+
+const char *
+NSSSSL_GetVersion(void)
+{
+ return NSS_VERSION;
+}
diff --git a/chromium/net/third_party/nss/ssl/ssldef.c b/chromium/net/third_party/nss/ssl/ssldef.c
new file mode 100644
index 00000000000..cc3ecc8bf14
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssldef.c
@@ -0,0 +1,210 @@
+/*
+ * "Default" SSLSocket methods, used by sockets that do neither SSL nor socks.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+
+#if defined(WIN32)
+#define MAP_ERROR(from,to) if (err == from) { PORT_SetError(to); }
+#define DEFINE_ERROR PRErrorCode err = PR_GetError();
+#else
+#define MAP_ERROR(from,to)
+#define DEFINE_ERROR
+#endif
+
+int ssl_DefConnect(sslSocket *ss, const PRNetAddr *sa)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->connect(lower, sa, ss->cTimeout);
+ return rv;
+}
+
+int ssl_DefBind(sslSocket *ss, const PRNetAddr *addr)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->bind(lower, addr);
+ return rv;
+}
+
+int ssl_DefListen(sslSocket *ss, int backlog)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->listen(lower, backlog);
+ return rv;
+}
+
+int ssl_DefShutdown(sslSocket *ss, int how)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->shutdown(lower, how);
+ return rv;
+}
+
+int ssl_DefRecv(sslSocket *ss, unsigned char *buf, int len, int flags)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->recv(lower, (void *)buf, len, flags, ss->rTimeout);
+ if (rv < 0) {
+ DEFINE_ERROR
+ MAP_ERROR(PR_SOCKET_SHUTDOWN_ERROR, PR_CONNECT_RESET_ERROR)
+ } else if (rv > len) {
+ PORT_Assert(rv <= len);
+ PORT_SetError(PR_BUFFER_OVERFLOW_ERROR);
+ rv = SECFailure;
+ }
+ return rv;
+}
+
+/* Default (unencrypted) send.
+ * For blocking sockets, always returns len or SECFailure, no short writes.
+ * For non-blocking sockets:
+ * Returns positive count if any data was written, else returns SECFailure.
+ * Short writes may occur. Does not return SECWouldBlock.
+ */
+int ssl_DefSend(sslSocket *ss, const unsigned char *buf, int len, int flags)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int sent = 0;
+
+#if NSS_DISABLE_NAGLE_DELAYS
+ /* Although this is overkill, we disable Nagle delays completely for
+ ** SSL sockets.
+ */
+ if (ss->opt.useSecurity && !ss->delayDisabled) {
+ ssl_EnableNagleDelay(ss, PR_FALSE); /* ignore error */
+ ss->delayDisabled = 1;
+ }
+#endif
+ do {
+ int rv = lower->methods->send(lower, (const void *)(buf + sent),
+ len - sent, flags, ss->wTimeout);
+ if (rv < 0) {
+ PRErrorCode err = PR_GetError();
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ ss->lastWriteBlocked = 1;
+ return sent ? sent : SECFailure;
+ }
+ ss->lastWriteBlocked = 0;
+ MAP_ERROR(PR_CONNECT_ABORTED_ERROR, PR_CONNECT_RESET_ERROR)
+ /* Loser */
+ return rv;
+ }
+ sent += rv;
+
+ if (IS_DTLS(ss) && (len > sent)) {
+ /* We got a partial write so just return it */
+ return sent;
+ }
+ } while (len > sent);
+ ss->lastWriteBlocked = 0;
+ return sent;
+}
+
+int ssl_DefRead(sslSocket *ss, unsigned char *buf, int len)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->read(lower, (void *)buf, len);
+ if (rv < 0) {
+ DEFINE_ERROR
+ MAP_ERROR(PR_SOCKET_SHUTDOWN_ERROR, PR_CONNECT_RESET_ERROR)
+ }
+ return rv;
+}
+
+int ssl_DefWrite(sslSocket *ss, const unsigned char *buf, int len)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int sent = 0;
+
+ do {
+ int rv = lower->methods->write(lower, (const void *)(buf + sent),
+ len - sent);
+ if (rv < 0) {
+ PRErrorCode err = PR_GetError();
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ ss->lastWriteBlocked = 1;
+ return sent ? sent : SECFailure;
+ }
+ ss->lastWriteBlocked = 0;
+ MAP_ERROR(PR_CONNECT_ABORTED_ERROR, PR_CONNECT_RESET_ERROR)
+ /* Loser */
+ return rv;
+ }
+ sent += rv;
+ } while (len > sent);
+ ss->lastWriteBlocked = 0;
+ return sent;
+}
+
+int ssl_DefGetpeername(sslSocket *ss, PRNetAddr *name)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->getpeername(lower, name);
+ return rv;
+}
+
+int ssl_DefGetsockname(sslSocket *ss, PRNetAddr *name)
+{
+ PRFileDesc *lower = ss->fd->lower;
+ int rv;
+
+ rv = lower->methods->getsockname(lower, name);
+ return rv;
+}
+
+int ssl_DefClose(sslSocket *ss)
+{
+ PRFileDesc *fd;
+ PRFileDesc *popped;
+ int rv;
+
+ fd = ss->fd;
+
+ /* First, remove the SSL layer PRFileDesc from the socket's stack,
+ ** then invoke the SSL layer's PRFileDesc destructor.
+ ** This must happen before the next layer down is closed.
+ */
+ PORT_Assert(fd->higher == NULL);
+ if (fd->higher) {
+ PORT_SetError(PR_BAD_DESCRIPTOR_ERROR);
+ return SECFailure;
+ }
+ ss->fd = NULL;
+
+ /* PR_PopIOLayer will swap the contents of the top two PRFileDescs on
+ ** the stack, and then remove the second one. This way, the address
+ ** of the PRFileDesc on the top of the stack doesn't change.
+ */
+ popped = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+ popped->dtor(popped);
+
+ /* fd is now the PRFileDesc for the next layer down.
+ ** Now close the underlying socket.
+ */
+ rv = fd->methods->close(fd);
+
+ ssl_FreeSocket(ss);
+
+ SSL_TRC(5, ("%d: SSL[%d]: closing, rv=%d errno=%d",
+ SSL_GETPID(), fd, rv, PORT_GetError()));
+ return rv;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslenum.c b/chromium/net/third_party/nss/ssl/sslenum.c
new file mode 100644
index 00000000000..597ec072399
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslenum.c
@@ -0,0 +1,150 @@
+/*
+ * Table enumerating all implemented cipher suites
+ * Part of public API.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ssl.h"
+#include "sslproto.h"
+
+/*
+ * The ciphers are listed in the following order:
+ * - stronger ciphers before weaker ciphers
+ * - national ciphers before international ciphers
+ * - faster ciphers before slower ciphers
+ *
+ * National ciphers such as Camellia are listed before international ciphers
+ * such as AES and RC4 to allow servers that prefer Camellia to negotiate
+ * Camellia without having to disable AES and RC4, which are needed for
+ * interoperability with clients that don't yet implement Camellia.
+ *
+ * The ordering of cipher suites in this table must match the ordering in
+ * the cipherSuites table in ssl3con.c.
+ *
+ * If new ECC cipher suites are added, also update the ssl3CipherSuite arrays
+ * in ssl3ecc.c.
+ *
+ * Finally, update the ssl_V3_SUITES_IMPLEMENTED macro in sslimpl.h.
+ */
+const PRUint16 SSL_ImplementedCiphers[] = {
+ /* AES-GCM */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+#endif /* NSS_ENABLE_ECC */
+ TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+
+ /* 256-bit */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+#endif /* NSS_ENABLE_ECC */
+ TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
+ TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+ TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+#endif /* NSS_ENABLE_ECC */
+ TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ TLS_RSA_WITH_AES_256_CBC_SHA256,
+
+ /* 128-bit */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+#endif /* NSS_ENABLE_ECC */
+ TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
+ TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
+ TLS_DHE_DSS_WITH_RC4_128_SHA,
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+ TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+#endif /* NSS_ENABLE_ECC */
+ TLS_RSA_WITH_SEED_CBC_SHA,
+ TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
+ SSL_RSA_WITH_RC4_128_SHA,
+ SSL_RSA_WITH_RC4_128_MD5,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_CBC_SHA256,
+
+ /* 112-bit 3DES */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+#endif /* NSS_ENABLE_ECC */
+ SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+#endif /* NSS_ENABLE_ECC */
+ SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA,
+ SSL_RSA_WITH_3DES_EDE_CBC_SHA,
+
+ /* 56-bit DES "domestic" cipher suites */
+ SSL_DHE_RSA_WITH_DES_CBC_SHA,
+ SSL_DHE_DSS_WITH_DES_CBC_SHA,
+ SSL_RSA_FIPS_WITH_DES_CBC_SHA,
+ SSL_RSA_WITH_DES_CBC_SHA,
+
+ /* export ciphersuites with 1024-bit public key exchange keys */
+ TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
+ TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
+
+ /* export ciphersuites with 512-bit public key exchange keys */
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5,
+ SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
+
+ /* ciphersuites with no encryption */
+#ifdef NSS_ENABLE_ECC
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDH_RSA_WITH_NULL_SHA,
+ TLS_ECDH_ECDSA_WITH_NULL_SHA,
+#endif /* NSS_ENABLE_ECC */
+ SSL_RSA_WITH_NULL_SHA,
+ TLS_RSA_WITH_NULL_SHA256,
+ SSL_RSA_WITH_NULL_MD5,
+
+ /* SSL2 cipher suites. */
+ SSL_EN_RC4_128_WITH_MD5,
+ SSL_EN_RC2_128_CBC_WITH_MD5,
+ SSL_EN_DES_192_EDE3_CBC_WITH_MD5, /* actually 112, not 192 */
+ SSL_EN_DES_64_CBC_WITH_MD5,
+ SSL_EN_RC4_128_EXPORT40_WITH_MD5,
+ SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5,
+
+ 0
+
+};
+
+const PRUint16 SSL_NumImplementedCiphers =
+ (sizeof SSL_ImplementedCiphers) / (sizeof SSL_ImplementedCiphers[0]) - 1;
+
+const PRUint16 *
+SSL_GetImplementedCiphers(void)
+{
+ return SSL_ImplementedCiphers;
+}
+
+PRUint16
+SSL_GetNumImplementedCiphers(void)
+{
+ return SSL_NumImplementedCiphers;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslerr.c b/chromium/net/third_party/nss/ssl/sslerr.c
new file mode 100644
index 00000000000..f827221aee2
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslerr.c
@@ -0,0 +1,41 @@
+/*
+ * Function to set error code only when meaningful error has not already
+ * been set.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prerror.h"
+#include "secerr.h"
+#include "sslerr.h"
+#include "seccomon.h"
+
+/* look at the current value of PR_GetError, and evaluate it to see
+ * if it is meaningful or meaningless (out of context).
+ * If it is meaningless, replace it with the hiLevelError.
+ * Returns the chosen error value.
+ */
+int
+ssl_MapLowLevelError(int hiLevelError)
+{
+ int oldErr = PORT_GetError();
+
+ switch (oldErr) {
+
+ case 0:
+ case PR_IO_ERROR:
+ case SEC_ERROR_IO:
+ case SEC_ERROR_BAD_DATA:
+ case SEC_ERROR_LIBRARY_FAILURE:
+ case SEC_ERROR_EXTENSION_NOT_FOUND:
+ case SSL_ERROR_BAD_CLIENT:
+ case SSL_ERROR_BAD_SERVER:
+ case SSL_ERROR_SESSION_NOT_FOUND:
+ PORT_SetError(hiLevelError);
+ return hiLevelError;
+
+ default: /* leave the majority of error codes alone. */
+ return oldErr;
+ }
+}
diff --git a/chromium/net/third_party/nss/ssl/sslerr.h b/chromium/net/third_party/nss/ssl/sslerr.h
new file mode 100644
index 00000000000..5a949c9a248
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslerr.h
@@ -0,0 +1,204 @@
+/*
+ * Enumeration of all SSL-specific error codes.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __SSL_ERR_H_
+#define __SSL_ERR_H_
+
+
+#define SSL_ERROR_BASE (-0x3000)
+#define SSL_ERROR_LIMIT (SSL_ERROR_BASE + 1000)
+
+#define IS_SSL_ERROR(code) \
+ (((code) >= SSL_ERROR_BASE) && ((code) < SSL_ERROR_LIMIT))
+
+#ifndef NO_SECURITY_ERROR_ENUM
+typedef enum {
+SSL_ERROR_EXPORT_ONLY_SERVER = (SSL_ERROR_BASE + 0),
+SSL_ERROR_US_ONLY_SERVER = (SSL_ERROR_BASE + 1),
+SSL_ERROR_NO_CYPHER_OVERLAP = (SSL_ERROR_BASE + 2),
+/*
+ * Received an alert reporting what we did wrong. (more alerts below)
+ */
+SSL_ERROR_NO_CERTIFICATE /*_ALERT */ = (SSL_ERROR_BASE + 3),
+SSL_ERROR_BAD_CERTIFICATE = (SSL_ERROR_BASE + 4),
+SSL_ERROR_UNUSED_5 = (SSL_ERROR_BASE + 5),
+ /* error 5 is obsolete */
+SSL_ERROR_BAD_CLIENT = (SSL_ERROR_BASE + 6),
+SSL_ERROR_BAD_SERVER = (SSL_ERROR_BASE + 7),
+SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE = (SSL_ERROR_BASE + 8),
+SSL_ERROR_UNSUPPORTED_VERSION = (SSL_ERROR_BASE + 9),
+SSL_ERROR_UNUSED_10 = (SSL_ERROR_BASE + 10),
+ /* error 10 is obsolete */
+SSL_ERROR_WRONG_CERTIFICATE = (SSL_ERROR_BASE + 11),
+SSL_ERROR_BAD_CERT_DOMAIN = (SSL_ERROR_BASE + 12),
+SSL_ERROR_POST_WARNING = (SSL_ERROR_BASE + 13),
+SSL_ERROR_SSL2_DISABLED = (SSL_ERROR_BASE + 14),
+SSL_ERROR_BAD_MAC_READ = (SSL_ERROR_BASE + 15),
+/*
+ * Received an alert reporting what we did wrong.
+ * (two more alerts above, and many more below)
+ */
+SSL_ERROR_BAD_MAC_ALERT = (SSL_ERROR_BASE + 16),
+SSL_ERROR_BAD_CERT_ALERT = (SSL_ERROR_BASE + 17),
+SSL_ERROR_REVOKED_CERT_ALERT = (SSL_ERROR_BASE + 18),
+SSL_ERROR_EXPIRED_CERT_ALERT = (SSL_ERROR_BASE + 19),
+
+SSL_ERROR_SSL_DISABLED = (SSL_ERROR_BASE + 20),
+SSL_ERROR_FORTEZZA_PQG = (SSL_ERROR_BASE + 21),
+SSL_ERROR_UNKNOWN_CIPHER_SUITE = (SSL_ERROR_BASE + 22),
+SSL_ERROR_NO_CIPHERS_SUPPORTED = (SSL_ERROR_BASE + 23),
+SSL_ERROR_BAD_BLOCK_PADDING = (SSL_ERROR_BASE + 24),
+SSL_ERROR_RX_RECORD_TOO_LONG = (SSL_ERROR_BASE + 25),
+SSL_ERROR_TX_RECORD_TOO_LONG = (SSL_ERROR_BASE + 26),
+/*
+ * Received a malformed (too long or short) SSL handshake.
+ */
+SSL_ERROR_RX_MALFORMED_HELLO_REQUEST = (SSL_ERROR_BASE + 27),
+SSL_ERROR_RX_MALFORMED_CLIENT_HELLO = (SSL_ERROR_BASE + 28),
+SSL_ERROR_RX_MALFORMED_SERVER_HELLO = (SSL_ERROR_BASE + 29),
+SSL_ERROR_RX_MALFORMED_CERTIFICATE = (SSL_ERROR_BASE + 30),
+SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH = (SSL_ERROR_BASE + 31),
+SSL_ERROR_RX_MALFORMED_CERT_REQUEST = (SSL_ERROR_BASE + 32),
+SSL_ERROR_RX_MALFORMED_HELLO_DONE = (SSL_ERROR_BASE + 33),
+SSL_ERROR_RX_MALFORMED_CERT_VERIFY = (SSL_ERROR_BASE + 34),
+SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH = (SSL_ERROR_BASE + 35),
+SSL_ERROR_RX_MALFORMED_FINISHED = (SSL_ERROR_BASE + 36),
+/*
+ * Received a malformed (too long or short) SSL record.
+ */
+SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER = (SSL_ERROR_BASE + 37),
+SSL_ERROR_RX_MALFORMED_ALERT = (SSL_ERROR_BASE + 38),
+SSL_ERROR_RX_MALFORMED_HANDSHAKE = (SSL_ERROR_BASE + 39),
+SSL_ERROR_RX_MALFORMED_APPLICATION_DATA = (SSL_ERROR_BASE + 40),
+/*
+ * Received an SSL handshake that was inappropriate for the state we're in.
+ * E.g. Server received message from server, or wrong state in state machine.
+ */
+SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST = (SSL_ERROR_BASE + 41),
+SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO = (SSL_ERROR_BASE + 42),
+SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO = (SSL_ERROR_BASE + 43),
+SSL_ERROR_RX_UNEXPECTED_CERTIFICATE = (SSL_ERROR_BASE + 44),
+SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH = (SSL_ERROR_BASE + 45),
+SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST = (SSL_ERROR_BASE + 46),
+SSL_ERROR_RX_UNEXPECTED_HELLO_DONE = (SSL_ERROR_BASE + 47),
+SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY = (SSL_ERROR_BASE + 48),
+SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH = (SSL_ERROR_BASE + 49),
+SSL_ERROR_RX_UNEXPECTED_FINISHED = (SSL_ERROR_BASE + 50),
+/*
+ * Received an SSL record that was inappropriate for the state we're in.
+ */
+SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER = (SSL_ERROR_BASE + 51),
+SSL_ERROR_RX_UNEXPECTED_ALERT = (SSL_ERROR_BASE + 52),
+SSL_ERROR_RX_UNEXPECTED_HANDSHAKE = (SSL_ERROR_BASE + 53),
+SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA= (SSL_ERROR_BASE + 54),
+/*
+ * Received record/message with unknown discriminant.
+ */
+SSL_ERROR_RX_UNKNOWN_RECORD_TYPE = (SSL_ERROR_BASE + 55),
+SSL_ERROR_RX_UNKNOWN_HANDSHAKE = (SSL_ERROR_BASE + 56),
+SSL_ERROR_RX_UNKNOWN_ALERT = (SSL_ERROR_BASE + 57),
+/*
+ * Received an alert reporting what we did wrong. (more alerts above)
+ */
+SSL_ERROR_CLOSE_NOTIFY_ALERT = (SSL_ERROR_BASE + 58),
+SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT = (SSL_ERROR_BASE + 59),
+SSL_ERROR_DECOMPRESSION_FAILURE_ALERT = (SSL_ERROR_BASE + 60),
+SSL_ERROR_HANDSHAKE_FAILURE_ALERT = (SSL_ERROR_BASE + 61),
+SSL_ERROR_ILLEGAL_PARAMETER_ALERT = (SSL_ERROR_BASE + 62),
+SSL_ERROR_UNSUPPORTED_CERT_ALERT = (SSL_ERROR_BASE + 63),
+SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT = (SSL_ERROR_BASE + 64),
+
+SSL_ERROR_GENERATE_RANDOM_FAILURE = (SSL_ERROR_BASE + 65),
+SSL_ERROR_SIGN_HASHES_FAILURE = (SSL_ERROR_BASE + 66),
+SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE = (SSL_ERROR_BASE + 67),
+SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE = (SSL_ERROR_BASE + 68),
+SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE = (SSL_ERROR_BASE + 69),
+
+SSL_ERROR_ENCRYPTION_FAILURE = (SSL_ERROR_BASE + 70),
+SSL_ERROR_DECRYPTION_FAILURE = (SSL_ERROR_BASE + 71), /* don't use */
+SSL_ERROR_SOCKET_WRITE_FAILURE = (SSL_ERROR_BASE + 72),
+
+SSL_ERROR_MD5_DIGEST_FAILURE = (SSL_ERROR_BASE + 73),
+SSL_ERROR_SHA_DIGEST_FAILURE = (SSL_ERROR_BASE + 74),
+SSL_ERROR_MAC_COMPUTATION_FAILURE = (SSL_ERROR_BASE + 75),
+SSL_ERROR_SYM_KEY_CONTEXT_FAILURE = (SSL_ERROR_BASE + 76),
+SSL_ERROR_SYM_KEY_UNWRAP_FAILURE = (SSL_ERROR_BASE + 77),
+SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED = (SSL_ERROR_BASE + 78),
+SSL_ERROR_IV_PARAM_FAILURE = (SSL_ERROR_BASE + 79),
+SSL_ERROR_INIT_CIPHER_SUITE_FAILURE = (SSL_ERROR_BASE + 80),
+SSL_ERROR_SESSION_KEY_GEN_FAILURE = (SSL_ERROR_BASE + 81),
+SSL_ERROR_NO_SERVER_KEY_FOR_ALG = (SSL_ERROR_BASE + 82),
+SSL_ERROR_TOKEN_INSERTION_REMOVAL = (SSL_ERROR_BASE + 83),
+SSL_ERROR_TOKEN_SLOT_NOT_FOUND = (SSL_ERROR_BASE + 84),
+SSL_ERROR_NO_COMPRESSION_OVERLAP = (SSL_ERROR_BASE + 85),
+SSL_ERROR_HANDSHAKE_NOT_COMPLETED = (SSL_ERROR_BASE + 86),
+SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE = (SSL_ERROR_BASE + 87),
+SSL_ERROR_CERT_KEA_MISMATCH = (SSL_ERROR_BASE + 88),
+/* SSL_ERROR_NO_TRUSTED_SSL_CLIENT_CA became obsolete in NSS 3.14. */
+SSL_ERROR_NO_TRUSTED_SSL_CLIENT_CA = (SSL_ERROR_BASE + 89),
+SSL_ERROR_SESSION_NOT_FOUND = (SSL_ERROR_BASE + 90),
+
+SSL_ERROR_DECRYPTION_FAILED_ALERT = (SSL_ERROR_BASE + 91),
+SSL_ERROR_RECORD_OVERFLOW_ALERT = (SSL_ERROR_BASE + 92),
+SSL_ERROR_UNKNOWN_CA_ALERT = (SSL_ERROR_BASE + 93),
+SSL_ERROR_ACCESS_DENIED_ALERT = (SSL_ERROR_BASE + 94),
+SSL_ERROR_DECODE_ERROR_ALERT = (SSL_ERROR_BASE + 95),
+SSL_ERROR_DECRYPT_ERROR_ALERT = (SSL_ERROR_BASE + 96),
+SSL_ERROR_EXPORT_RESTRICTION_ALERT = (SSL_ERROR_BASE + 97),
+SSL_ERROR_PROTOCOL_VERSION_ALERT = (SSL_ERROR_BASE + 98),
+SSL_ERROR_INSUFFICIENT_SECURITY_ALERT = (SSL_ERROR_BASE + 99),
+SSL_ERROR_INTERNAL_ERROR_ALERT = (SSL_ERROR_BASE + 100),
+SSL_ERROR_USER_CANCELED_ALERT = (SSL_ERROR_BASE + 101),
+SSL_ERROR_NO_RENEGOTIATION_ALERT = (SSL_ERROR_BASE + 102),
+
+SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED = (SSL_ERROR_BASE + 103),
+
+SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT = (SSL_ERROR_BASE + 104),
+SSL_ERROR_CERTIFICATE_UNOBTAINABLE_ALERT = (SSL_ERROR_BASE + 105),
+SSL_ERROR_UNRECOGNIZED_NAME_ALERT = (SSL_ERROR_BASE + 106),
+SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT = (SSL_ERROR_BASE + 107),
+SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT = (SSL_ERROR_BASE + 108),
+
+SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET = (SSL_ERROR_BASE + 109),
+SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET = (SSL_ERROR_BASE + 110),
+
+SSL_ERROR_DECOMPRESSION_FAILURE = (SSL_ERROR_BASE + 111),
+SSL_ERROR_RENEGOTIATION_NOT_ALLOWED = (SSL_ERROR_BASE + 112),
+SSL_ERROR_UNSAFE_NEGOTIATION = (SSL_ERROR_BASE + 113),
+
+SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD = (SSL_ERROR_BASE + 114),
+
+SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY = (SSL_ERROR_BASE + 115),
+
+SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID = (SSL_ERROR_BASE + 116),
+
+SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2 = (SSL_ERROR_BASE + 117),
+SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS = (SSL_ERROR_BASE + 118),
+SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS = (SSL_ERROR_BASE + 119),
+
+SSL_ERROR_INVALID_VERSION_RANGE = (SSL_ERROR_BASE + 120),
+SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION = (SSL_ERROR_BASE + 121),
+
+SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST = (SSL_ERROR_BASE + 122),
+SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST = (SSL_ERROR_BASE + 123),
+
+SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION = (SSL_ERROR_BASE + 124),
+
+SSL_ERROR_RX_UNEXPECTED_CERT_STATUS = (SSL_ERROR_BASE + 125),
+
+SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM = (SSL_ERROR_BASE + 126),
+SSL_ERROR_DIGEST_FAILURE = (SSL_ERROR_BASE + 127),
+SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM = (SSL_ERROR_BASE + 128),
+
+SSL_ERROR_BAD_CHANNEL_ID_DATA = (SSL_ERROR_BASE + 129),
+SSL_ERROR_INVALID_CHANNEL_ID_KEY = (SSL_ERROR_BASE + 130),
+SSL_ERROR_GET_CHANNEL_ID_FAILED = (SSL_ERROR_BASE + 131),
+
+SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */
+} SSLErrorCodes;
+#endif /* NO_SECURITY_ERROR_ENUM */
+
+#endif /* __SSL_ERR_H_ */
diff --git a/chromium/net/third_party/nss/ssl/sslerrstrs.c b/chromium/net/third_party/nss/ssl/sslerrstrs.c
new file mode 100644
index 00000000000..34f4ea9b9d8
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslerrstrs.c
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prerror.h"
+#include "sslerr.h"
+#include "prinit.h"
+#include "nssutil.h"
+#include "ssl.h"
+
+#define ER3(name, value, str) {#name, str},
+
+static const struct PRErrorMessage ssltext[] = {
+#include "SSLerrs.h"
+ {0,0}
+};
+
+static const struct PRErrorTable ssl_et = {
+ ssltext, "sslerr", SSL_ERROR_BASE,
+ (sizeof ssltext)/(sizeof ssltext[0])
+};
+
+static PRStatus
+ssl_InitializePRErrorTableOnce(void) {
+ return PR_ErrorInstallTable(&ssl_et);
+}
+
+static PRCallOnceType once;
+
+SECStatus
+ssl_InitializePRErrorTable(void)
+{
+ return (PR_SUCCESS == PR_CallOnce(&once, ssl_InitializePRErrorTableOnce))
+ ? SECSuccess : SECFailure;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslgathr.c b/chromium/net/third_party/nss/ssl/sslgathr.c
new file mode 100644
index 00000000000..6c17eb00fae
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslgathr.c
@@ -0,0 +1,453 @@
+/*
+ * Gather (Read) entire SSL2 records from socket into buffer.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+
+/* Forward static declarations */
+static SECStatus ssl2_HandleV3HandshakeRecord(sslSocket *ss);
+
+/*
+** Gather a single record of data from the receiving stream. This code
+** first gathers the header (2 or 3 bytes long depending on the value of
+** the most significant bit in the first byte) then gathers up the data
+** for the record into gs->buf. This code handles non-blocking I/O
+** and is to be called multiple times until ss->sec.recordLen != 0.
+** This function decrypts the gathered record in place, in gs_buf.
+ *
+ * Caller must hold RecvBufLock.
+ *
+ * Returns +1 when it has gathered a complete SSLV2 record.
+ * Returns 0 if it hits EOF.
+ * Returns -1 (SECFailure) on any error
+ * Returns -2 (SECWouldBlock) when it gathers an SSL v3 client hello header.
+**
+** The SSL2 Gather State machine has 4 states:
+** GS_INIT - Done reading in previous record. Haven't begun to read in
+** next record. When ssl2_GatherData is called with the machine
+** in this state, the machine will attempt to read the first 3
+** bytes of the SSL2 record header, and will advance the state
+** to GS_HEADER.
+**
+** GS_HEADER - The machine is in this state while waiting for the completion
+** of the first 3 bytes of the SSL2 record. When complete, the
+** machine will compute the remaining unread length of this record
+** and will initiate a read of that many bytes. The machine will
+** advance to one of two states, depending on whether the record
+** is encrypted (GS_MAC), or unencrypted (GS_DATA).
+**
+** GS_MAC - The machine is in this state while waiting for the remainder
+** of the SSL2 record to be read in. When the read is completed,
+** the machine checks the record for valid length, decrypts it,
+** and checks and discards the MAC, then advances to GS_INIT.
+**
+** GS_DATA - The machine is in this state while waiting for the remainder
+** of the unencrypted SSL2 record to be read in. Upon completion,
+** the machine advances to the GS_INIT state and returns the data.
+*/
+int
+ssl2_GatherData(sslSocket *ss, sslGather *gs, int flags)
+{
+ unsigned char * bp;
+ unsigned char * pBuf;
+ int nb, err, rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+
+ if (gs->state == GS_INIT) {
+ /* Initialize gathering engine */
+ gs->state = GS_HEADER;
+ gs->remainder = 3;
+ gs->count = 3;
+ gs->offset = 0;
+ gs->recordLen = 0;
+ gs->recordPadding = 0;
+ gs->hdr[2] = 0;
+
+ gs->writeOffset = 0;
+ gs->readOffset = 0;
+ }
+ if (gs->encrypted) {
+ PORT_Assert(ss->sec.hash != 0);
+ }
+
+ pBuf = gs->buf.buf;
+ for (;;) {
+ SSL_TRC(30, ("%d: SSL[%d]: gather state %d (need %d more)",
+ SSL_GETPID(), ss->fd, gs->state, gs->remainder));
+ bp = ((gs->state != GS_HEADER) ? pBuf : gs->hdr) + gs->offset;
+ nb = ssl_DefRecv(ss, bp, gs->remainder, flags);
+ if (nb > 0) {
+ PRINT_BUF(60, (ss, "raw gather data:", bp, nb));
+ }
+ if (nb == 0) {
+ /* EOF */
+ SSL_TRC(30, ("%d: SSL[%d]: EOF", SSL_GETPID(), ss->fd));
+ rv = 0;
+ break;
+ }
+ if (nb < 0) {
+ SSL_DBG(("%d: SSL[%d]: recv error %d", SSL_GETPID(), ss->fd,
+ PR_GetError()));
+ rv = SECFailure;
+ break;
+ }
+
+ gs->offset += nb;
+ gs->remainder -= nb;
+
+ if (gs->remainder > 0) {
+ continue;
+ }
+
+ /* Probably finished this piece */
+ switch (gs->state) {
+ case GS_HEADER:
+ if (!SSL3_ALL_VERSIONS_DISABLED(&ss->vrange) && !ss->firstHsDone) {
+
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ /* If this looks like an SSL3 handshake record,
+ ** and we're expecting an SSL2 Hello message from our peer,
+ ** handle it here.
+ */
+ if (gs->hdr[0] == content_handshake) {
+ if ((ss->nextHandshake == ssl2_HandleClientHelloMessage) ||
+ (ss->nextHandshake == ssl2_HandleServerHelloMessage)) {
+ rv = ssl2_HandleV3HandshakeRecord(ss);
+ if (rv == SECFailure) {
+ return SECFailure;
+ }
+ }
+ /* XXX_1 The call stack to here is:
+ * ssl_Do1stHandshake -> ssl_GatherRecord1stHandshake ->
+ * ssl2_GatherRecord -> here.
+ * We want to return all the way out to ssl_Do1stHandshake,
+ * and have it call ssl_GatherRecord1stHandshake again.
+ * ssl_GatherRecord1stHandshake will call
+ * ssl3_GatherCompleteHandshake when it is called again.
+ *
+ * Returning SECWouldBlock here causes
+ * ssl_GatherRecord1stHandshake to return without clearing
+ * ss->handshake, ensuring that ssl_Do1stHandshake will
+ * call it again immediately.
+ *
+ * If we return 1 here, ssl_GatherRecord1stHandshake will
+ * clear ss->handshake before returning, and thus will not
+ * be called again by ssl_Do1stHandshake.
+ */
+ return SECWouldBlock;
+ } else if (gs->hdr[0] == content_alert) {
+ if (ss->nextHandshake == ssl2_HandleServerHelloMessage) {
+ /* XXX This is a hack. We're assuming that any failure
+ * XXX on the client hello is a failure to match
+ * XXX ciphers.
+ */
+ PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
+ return SECFailure;
+ }
+ }
+ }
+
+ /* we've got the first 3 bytes. The header may be two or three. */
+ if (gs->hdr[0] & 0x80) {
+ /* This record has a 2-byte header, and no padding */
+ gs->count = ((gs->hdr[0] & 0x7f) << 8) | gs->hdr[1];
+ gs->recordPadding = 0;
+ } else {
+ /* This record has a 3-byte header that is all read in now. */
+ gs->count = ((gs->hdr[0] & 0x3f) << 8) | gs->hdr[1];
+ /* is_escape = (gs->hdr[0] & 0x40) != 0; */
+ gs->recordPadding = gs->hdr[2];
+ }
+ if (!gs->count) {
+ PORT_SetError(SSL_ERROR_RX_RECORD_TOO_LONG);
+ goto cleanup;
+ }
+
+ if (gs->count > gs->buf.space) {
+ err = sslBuffer_Grow(&gs->buf, gs->count);
+ if (err) {
+ return err;
+ }
+ pBuf = gs->buf.buf;
+ }
+
+
+ if (gs->hdr[0] & 0x80) {
+ /* we've already read in the first byte of the body.
+ ** Put it into the buffer.
+ */
+ pBuf[0] = gs->hdr[2];
+ gs->offset = 1;
+ gs->remainder = gs->count - 1;
+ } else {
+ gs->offset = 0;
+ gs->remainder = gs->count;
+ }
+
+ if (gs->encrypted) {
+ gs->state = GS_MAC;
+ gs->recordLen = gs->count - gs->recordPadding
+ - ss->sec.hash->length;
+ } else {
+ gs->state = GS_DATA;
+ gs->recordLen = gs->count;
+ }
+
+ break;
+
+
+ case GS_MAC:
+ /* Have read in entire rest of the ciphertext.
+ ** Check for valid length.
+ ** Decrypt it.
+ ** Check the MAC.
+ */
+ PORT_Assert(gs->encrypted);
+
+ {
+ unsigned int macLen;
+ int nout;
+ unsigned char mac[SSL_MAX_MAC_BYTES];
+
+ ssl_GetSpecReadLock(ss); /**********************************/
+
+ /* If this is a stream cipher, blockSize will be 1,
+ * and this test will always be false.
+ * If this is a block cipher, this will detect records
+ * that are not a multiple of the blocksize in length.
+ */
+ if (gs->count & (ss->sec.blockSize - 1)) {
+ /* This is an error. Sender is misbehaving */
+ SSL_DBG(("%d: SSL[%d]: sender, count=%d blockSize=%d",
+ SSL_GETPID(), ss->fd, gs->count,
+ ss->sec.blockSize));
+ PORT_SetError(SSL_ERROR_BAD_BLOCK_PADDING);
+ rv = SECFailure;
+ goto spec_locked_done;
+ }
+ PORT_Assert(gs->count == gs->offset);
+
+ if (gs->offset == 0) {
+ rv = 0; /* means EOF. */
+ goto spec_locked_done;
+ }
+
+ /* Decrypt the portion of data that we just received.
+ ** Decrypt it in place.
+ */
+ rv = (*ss->sec.dec)(ss->sec.readcx, pBuf, &nout, gs->offset,
+ pBuf, gs->offset);
+ if (rv != SECSuccess) {
+ goto spec_locked_done;
+ }
+
+
+ /* Have read in all the MAC portion of record
+ **
+ ** Prepare MAC by resetting it and feeding it the shared secret
+ */
+ macLen = ss->sec.hash->length;
+ if (gs->offset >= macLen) {
+ PRUint32 sequenceNumber = ss->sec.rcvSequence++;
+ unsigned char seq[4];
+
+ seq[0] = (unsigned char) (sequenceNumber >> 24);
+ seq[1] = (unsigned char) (sequenceNumber >> 16);
+ seq[2] = (unsigned char) (sequenceNumber >> 8);
+ seq[3] = (unsigned char) (sequenceNumber);
+
+ (*ss->sec.hash->begin)(ss->sec.hashcx);
+ (*ss->sec.hash->update)(ss->sec.hashcx, ss->sec.rcvSecret.data,
+ ss->sec.rcvSecret.len);
+ (*ss->sec.hash->update)(ss->sec.hashcx, pBuf + macLen,
+ gs->offset - macLen);
+ (*ss->sec.hash->update)(ss->sec.hashcx, seq, 4);
+ (*ss->sec.hash->end)(ss->sec.hashcx, mac, &macLen, macLen);
+
+ PORT_Assert(macLen == ss->sec.hash->length);
+
+ ssl_ReleaseSpecReadLock(ss); /******************************/
+
+ if (NSS_SecureMemcmp(mac, pBuf, macLen) != 0) {
+ /* MAC's didn't match... */
+ SSL_DBG(("%d: SSL[%d]: mac check failed, seq=%d",
+ SSL_GETPID(), ss->fd, ss->sec.rcvSequence));
+ PRINT_BUF(1, (ss, "computed mac:", mac, macLen));
+ PRINT_BUF(1, (ss, "received mac:", pBuf, macLen));
+ PORT_SetError(SSL_ERROR_BAD_MAC_READ);
+ rv = SECFailure;
+ goto cleanup;
+ }
+ } else {
+ ssl_ReleaseSpecReadLock(ss); /******************************/
+ }
+
+ if (gs->recordPadding + macLen <= gs->offset) {
+ gs->recordOffset = macLen;
+ gs->readOffset = macLen;
+ gs->writeOffset = gs->offset - gs->recordPadding;
+ rv = 1;
+ } else {
+ PORT_SetError(SSL_ERROR_BAD_BLOCK_PADDING);
+cleanup:
+ /* nothing in the buffer any more. */
+ gs->recordOffset = 0;
+ gs->readOffset = 0;
+ gs->writeOffset = 0;
+ rv = SECFailure;
+ }
+
+ gs->recordLen = gs->writeOffset - gs->readOffset;
+ gs->recordPadding = 0; /* forget we did any padding. */
+ gs->state = GS_INIT;
+
+
+ if (rv > 0) {
+ PRINT_BUF(50, (ss, "recv clear record:",
+ pBuf + gs->recordOffset, gs->recordLen));
+ }
+ return rv;
+
+spec_locked_done:
+ ssl_ReleaseSpecReadLock(ss);
+ return rv;
+ }
+
+ case GS_DATA:
+ /* Have read in all the DATA portion of record */
+
+ gs->recordOffset = 0;
+ gs->readOffset = 0;
+ gs->writeOffset = gs->offset;
+ PORT_Assert(gs->recordLen == gs->writeOffset - gs->readOffset);
+ gs->recordLen = gs->offset;
+ gs->recordPadding = 0;
+ gs->state = GS_INIT;
+
+ ++ss->sec.rcvSequence;
+
+ PRINT_BUF(50, (ss, "recv clear record:",
+ pBuf + gs->recordOffset, gs->recordLen));
+ return 1;
+
+ } /* end switch gs->state */
+ } /* end gather loop. */
+ return rv;
+}
+
+/*
+** Gather a single record of data from the receiving stream. This code
+** first gathers the header (2 or 3 bytes long depending on the value of
+** the most significant bit in the first byte) then gathers up the data
+** for the record into the readBuf. This code handles non-blocking I/O
+** and is to be called multiple times until ss->sec.recordLen != 0.
+ *
+ * Returns +1 when it has gathered a complete SSLV2 record.
+ * Returns 0 if it hits EOF.
+ * Returns -1 (SECFailure) on any error
+ * Returns -2 (SECWouldBlock)
+ *
+ * Called by ssl_GatherRecord1stHandshake in sslcon.c,
+ * and by DoRecv in sslsecur.c
+ * Caller must hold RecvBufLock.
+ */
+int
+ssl2_GatherRecord(sslSocket *ss, int flags)
+{
+ return ssl2_GatherData(ss, &ss->gs, flags);
+}
+
+/*
+ * Returns +1 when it has gathered a complete SSLV2 record.
+ * Returns 0 if it hits EOF.
+ * Returns -1 (SECFailure) on any error
+ * Returns -2 (SECWouldBlock)
+ *
+ * Called from SocksStartGather in sslsocks.c
+ * Caller must hold RecvBufLock.
+ */
+int
+ssl2_StartGatherBytes(sslSocket *ss, sslGather *gs, unsigned int count)
+{
+ int rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ gs->state = GS_DATA;
+ gs->remainder = count;
+ gs->count = count;
+ gs->offset = 0;
+ if (count > gs->buf.space) {
+ rv = sslBuffer_Grow(&gs->buf, count);
+ if (rv) {
+ return rv;
+ }
+ }
+ return ssl2_GatherData(ss, gs, 0);
+}
+
+/* Caller should hold RecvBufLock. */
+SECStatus
+ssl_InitGather(sslGather *gs)
+{
+ SECStatus status;
+
+ gs->state = GS_INIT;
+ gs->writeOffset = 0;
+ gs->readOffset = 0;
+ gs->dtlsPacketOffset = 0;
+ gs->dtlsPacket.len = 0;
+ status = sslBuffer_Grow(&gs->buf, 4096);
+ return status;
+}
+
+/* Caller must hold RecvBufLock. */
+void
+ssl_DestroyGather(sslGather *gs)
+{
+ if (gs) { /* the PORT_*Free functions check for NULL pointers. */
+ PORT_ZFree(gs->buf.buf, gs->buf.space);
+ PORT_Free(gs->inbuf.buf);
+ PORT_Free(gs->dtlsPacket.buf);
+ }
+}
+
+/* Caller must hold RecvBufLock. */
+static SECStatus
+ssl2_HandleV3HandshakeRecord(sslSocket *ss)
+{
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveRecvBufLock(ss) );
+ PORT_Assert( ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+
+ /* We've read in 3 bytes, there are 2 more to go in an ssl3 header. */
+ ss->gs.remainder = 2;
+ ss->gs.count = 0;
+
+ /* Clearing these handshake pointers ensures that
+ * ssl_Do1stHandshake won't call ssl2_HandleMessage when we return.
+ */
+ ss->nextHandshake = 0;
+ ss->securityHandshake = 0;
+
+ /* Setting ss->version to an SSL 3.x value will cause
+ ** ssl_GatherRecord1stHandshake to invoke ssl3_GatherCompleteHandshake()
+ ** the next time it is called.
+ **/
+ rv = ssl3_NegotiateVersion(ss, SSL_LIBRARY_VERSION_MAX_SUPPORTED,
+ PR_TRUE);
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ ss->sec.send = ssl3_SendApplicationData;
+
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslimpl.h b/chromium/net/third_party/nss/ssl/sslimpl.h
new file mode 100644
index 00000000000..da302abe57d
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslimpl.h
@@ -0,0 +1,1941 @@
+/*
+ * This file is PRIVATE to SSL and should be the first thing included by
+ * any SSL implementation file.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __sslimpl_h_
+#define __sslimpl_h_
+
+#ifdef DEBUG
+#undef NDEBUG
+#else
+#undef NDEBUG
+#define NDEBUG
+#endif
+#include "secport.h"
+#include "secerr.h"
+#include "sslerr.h"
+#include "ssl3prot.h"
+#include "hasht.h"
+#include "keythi.h"
+#include "nssilock.h"
+#include "pkcs11t.h"
+#if defined(XP_UNIX) || defined(XP_BEOS)
+#include "unistd.h"
+#endif
+#include "nssrwlk.h"
+#include "prthread.h"
+#include "prclist.h"
+
+#include "sslt.h" /* for some formerly private types, now public */
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+#if defined(XP_WIN32)
+#include <windows.h>
+#include <wincrypt.h>
+#elif defined(XP_MACOSX)
+#include <Security/Security.h>
+#endif
+#endif
+
+/* to make some of these old enums public without namespace pollution,
+** it was necessary to prepend ssl_ to the names.
+** These #defines preserve compatibility with the old code here in libssl.
+*/
+typedef SSLKEAType SSL3KEAType;
+typedef SSLMACAlgorithm SSL3MACAlgorithm;
+typedef SSLSignType SSL3SignType;
+
+#define sign_null ssl_sign_null
+#define sign_rsa ssl_sign_rsa
+#define sign_dsa ssl_sign_dsa
+#define sign_ecdsa ssl_sign_ecdsa
+
+#define calg_null ssl_calg_null
+#define calg_rc4 ssl_calg_rc4
+#define calg_rc2 ssl_calg_rc2
+#define calg_des ssl_calg_des
+#define calg_3des ssl_calg_3des
+#define calg_idea ssl_calg_idea
+#define calg_fortezza ssl_calg_fortezza /* deprecated, must preserve */
+#define calg_aes ssl_calg_aes
+#define calg_camellia ssl_calg_camellia
+#define calg_seed ssl_calg_seed
+#define calg_aes_gcm ssl_calg_aes_gcm
+
+#define mac_null ssl_mac_null
+#define mac_md5 ssl_mac_md5
+#define mac_sha ssl_mac_sha
+#define hmac_md5 ssl_hmac_md5
+#define hmac_sha ssl_hmac_sha
+#define hmac_sha256 ssl_hmac_sha256
+
+#define SET_ERROR_CODE /* reminder */
+#define SEND_ALERT /* reminder */
+#define TEST_FOR_FAILURE /* reminder */
+#define DEAL_WITH_FAILURE /* reminder */
+
+#if defined(DEBUG) || defined(TRACE)
+#ifdef __cplusplus
+#define Debug 1
+#else
+extern int Debug;
+#endif
+#else
+#undef Debug
+#endif
+
+#if defined(DEBUG) && !defined(TRACE) && !defined(NISCC_TEST)
+#define TRACE
+#endif
+
+#ifdef TRACE
+#define SSL_TRC(a,b) if (ssl_trace >= (a)) ssl_Trace b
+#define PRINT_BUF(a,b) if (ssl_trace >= (a)) ssl_PrintBuf b
+#define DUMP_MSG(a,b) if (ssl_trace >= (a)) ssl_DumpMsg b
+#else
+#define SSL_TRC(a,b)
+#define PRINT_BUF(a,b)
+#define DUMP_MSG(a,b)
+#endif
+
+#ifdef DEBUG
+#define SSL_DBG(b) if (ssl_debug) ssl_Trace b
+#else
+#define SSL_DBG(b)
+#endif
+
+#include "private/pprthred.h" /* for PR_InMonitor() */
+#define ssl_InMonitor(m) PZ_InMonitor(m)
+
+#define LSB(x) ((unsigned char) ((x) & 0xff))
+#define MSB(x) ((unsigned char) (((unsigned)(x)) >> 8))
+
+/************************************************************************/
+
+typedef enum { SSLAppOpRead = 0,
+ SSLAppOpWrite,
+ SSLAppOpRDWR,
+ SSLAppOpPost,
+ SSLAppOpHeader
+} SSLAppOperation;
+
+#define SSL_MIN_MASTER_KEY_BYTES 5
+#define SSL_MAX_MASTER_KEY_BYTES 64
+
+#define SSL2_SESSIONID_BYTES 16
+#define SSL3_SESSIONID_BYTES 32
+
+#define SSL_MIN_CHALLENGE_BYTES 16
+#define SSL_MAX_CHALLENGE_BYTES 32
+#define SSL_CHALLENGE_BYTES 16
+
+#define SSL_CONNECTIONID_BYTES 16
+
+#define SSL_MIN_CYPHER_ARG_BYTES 0
+#define SSL_MAX_CYPHER_ARG_BYTES 32
+
+#define SSL_MAX_MAC_BYTES 16
+
+#define SSL3_RSA_PMS_LENGTH 48
+#define SSL3_MASTER_SECRET_LENGTH 48
+
+/* number of wrap mechanisms potentially used to wrap master secrets. */
+#define SSL_NUM_WRAP_MECHS 16
+
+/* This makes the cert cache entry exactly 4k. */
+#define SSL_MAX_CACHED_CERT_LEN 4060
+
+#define NUM_MIXERS 9
+
+/* Mask of the 25 named curves we support. */
+#define SSL3_ALL_SUPPORTED_CURVES_MASK 0x3fffffe
+/* Mask of only 3 curves, suite B */
+#define SSL3_SUITE_B_SUPPORTED_CURVES_MASK 0x3800000
+
+#ifndef BPB
+#define BPB 8 /* Bits Per Byte */
+#endif
+
+#define EXPORT_RSA_KEY_LENGTH 64 /* bytes */
+
+#define INITIAL_DTLS_TIMEOUT_MS 1000 /* Default value from RFC 4347 = 1s*/
+#define MAX_DTLS_TIMEOUT_MS 60000 /* 1 minute */
+#define DTLS_FINISHED_TIMER_MS 120000 /* Time to wait in FINISHED state */
+
+typedef struct sslBufferStr sslBuffer;
+typedef struct sslConnectInfoStr sslConnectInfo;
+typedef struct sslGatherStr sslGather;
+typedef struct sslSecurityInfoStr sslSecurityInfo;
+typedef struct sslSessionIDStr sslSessionID;
+typedef struct sslSocketStr sslSocket;
+typedef struct sslSocketOpsStr sslSocketOps;
+
+typedef struct ssl3StateStr ssl3State;
+typedef struct ssl3CertNodeStr ssl3CertNode;
+typedef struct ssl3BulkCipherDefStr ssl3BulkCipherDef;
+typedef struct ssl3MACDefStr ssl3MACDef;
+typedef struct ssl3KeyPairStr ssl3KeyPair;
+
+struct ssl3CertNodeStr {
+ struct ssl3CertNodeStr *next;
+ CERTCertificate * cert;
+};
+
+typedef SECStatus (*sslHandshakeFunc)(sslSocket *ss);
+
+/* This type points to the low layer send func,
+** e.g. ssl2_SendStream or ssl3_SendPlainText.
+** These functions return the same values as PR_Send,
+** i.e. >= 0 means number of bytes sent, < 0 means error.
+*/
+typedef PRInt32 (*sslSendFunc)(sslSocket *ss, const unsigned char *buf,
+ PRInt32 n, PRInt32 flags);
+
+typedef void (*sslSessionIDCacheFunc) (sslSessionID *sid);
+typedef void (*sslSessionIDUncacheFunc)(sslSessionID *sid);
+typedef sslSessionID *(*sslSessionIDLookupFunc)(const PRIPv6Addr *addr,
+ unsigned char* sid,
+ unsigned int sidLen,
+ CERTCertDBHandle * dbHandle);
+
+/* registerable callback function that either appends extension to buffer
+ * or returns length of data that it would have appended.
+ */
+typedef PRInt32 (*ssl3HelloExtensionSenderFunc)(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+
+/* registerable callback function that handles a received extension,
+ * of the given type.
+ */
+typedef SECStatus (* ssl3HelloExtensionHandlerFunc)(sslSocket *ss,
+ PRUint16 ex_type,
+ SECItem * data);
+
+/* row in a table of hello extension senders */
+typedef struct {
+ PRInt32 ex_type;
+ ssl3HelloExtensionSenderFunc ex_sender;
+} ssl3HelloExtensionSender;
+
+/* row in a table of hello extension handlers */
+typedef struct {
+ PRInt32 ex_type;
+ ssl3HelloExtensionHandlerFunc ex_handler;
+} ssl3HelloExtensionHandler;
+
+extern SECStatus
+ssl3_RegisterServerHelloExtensionSender(sslSocket *ss, PRUint16 ex_type,
+ ssl3HelloExtensionSenderFunc cb);
+
+extern PRInt32
+ssl3_CallHelloExtensionSenders(sslSocket *ss, PRBool append, PRUint32 maxBytes,
+ const ssl3HelloExtensionSender *sender);
+
+/* Socket ops */
+struct sslSocketOpsStr {
+ int (*connect) (sslSocket *, const PRNetAddr *);
+ PRFileDesc *(*accept) (sslSocket *, PRNetAddr *);
+ int (*bind) (sslSocket *, const PRNetAddr *);
+ int (*listen) (sslSocket *, int);
+ int (*shutdown)(sslSocket *, int);
+ int (*close) (sslSocket *);
+
+ int (*recv) (sslSocket *, unsigned char *, int, int);
+
+ /* points to the higher-layer send func, e.g. ssl_SecureSend. */
+ int (*send) (sslSocket *, const unsigned char *, int, int);
+ int (*read) (sslSocket *, unsigned char *, int);
+ int (*write) (sslSocket *, const unsigned char *, int);
+
+ int (*getpeername)(sslSocket *, PRNetAddr *);
+ int (*getsockname)(sslSocket *, PRNetAddr *);
+};
+
+/* Flags interpreted by ssl send functions. */
+#define ssl_SEND_FLAG_FORCE_INTO_BUFFER 0x40000000
+#define ssl_SEND_FLAG_NO_BUFFER 0x20000000
+#define ssl_SEND_FLAG_USE_EPOCH 0x10000000 /* DTLS only */
+#define ssl_SEND_FLAG_NO_RETRANSMIT 0x08000000 /* DTLS only */
+#define ssl_SEND_FLAG_CAP_RECORD_VERSION \
+ 0x04000000 /* TLS only */
+#define ssl_SEND_FLAG_MASK 0x7f000000
+
+/*
+** A buffer object.
+*/
+struct sslBufferStr {
+ unsigned char * buf;
+ unsigned int len;
+ unsigned int space;
+};
+
+/*
+** SSL3 cipher suite policy and preference struct.
+*/
+typedef struct {
+#if !defined(_WIN32)
+ unsigned int cipher_suite : 16;
+ unsigned int policy : 8;
+ unsigned int enabled : 1;
+ unsigned int isPresent : 1;
+#else
+ ssl3CipherSuite cipher_suite;
+ PRUint8 policy;
+ unsigned char enabled : 1;
+ unsigned char isPresent : 1;
+#endif
+} ssl3CipherSuiteCfg;
+
+#ifdef NSS_ENABLE_ECC
+#define ssl_V3_SUITES_IMPLEMENTED 61
+#else
+#define ssl_V3_SUITES_IMPLEMENTED 37
+#endif /* NSS_ENABLE_ECC */
+
+#define MAX_DTLS_SRTP_CIPHER_SUITES 4
+
+typedef struct sslOptionsStr {
+ /* If SSL_SetNextProtoNego has been called, then this contains the
+ * list of supported protocols. */
+ SECItem nextProtoNego;
+
+ unsigned int useSecurity : 1; /* 1 */
+ unsigned int useSocks : 1; /* 2 */
+ unsigned int requestCertificate : 1; /* 3 */
+ unsigned int requireCertificate : 2; /* 4-5 */
+ unsigned int handshakeAsClient : 1; /* 6 */
+ unsigned int handshakeAsServer : 1; /* 7 */
+ unsigned int enableSSL2 : 1; /* 8 */
+ unsigned int unusedBit9 : 1; /* 9 */
+ unsigned int unusedBit10 : 1; /* 10 */
+ unsigned int noCache : 1; /* 11 */
+ unsigned int fdx : 1; /* 12 */
+ unsigned int v2CompatibleHello : 1; /* 13 */
+ unsigned int detectRollBack : 1; /* 14 */
+ unsigned int noStepDown : 1; /* 15 */
+ unsigned int bypassPKCS11 : 1; /* 16 */
+ unsigned int noLocks : 1; /* 17 */
+ unsigned int enableSessionTickets : 1; /* 18 */
+ unsigned int enableDeflate : 1; /* 19 */
+ unsigned int enableRenegotiation : 2; /* 20-21 */
+ unsigned int requireSafeNegotiation : 1; /* 22 */
+ unsigned int enableFalseStart : 1; /* 23 */
+ unsigned int cbcRandomIV : 1; /* 24 */
+ unsigned int enableOCSPStapling : 1; /* 25 */
+} sslOptions;
+
+typedef enum { sslHandshakingUndetermined = 0,
+ sslHandshakingAsClient,
+ sslHandshakingAsServer
+} sslHandshakingType;
+
+typedef struct sslServerCertsStr {
+ /* Configuration state for server sockets */
+ CERTCertificate * serverCert;
+ CERTCertificateList * serverCertChain;
+ ssl3KeyPair * serverKeyPair;
+ unsigned int serverKeyBits;
+} sslServerCerts;
+
+#define SERVERKEY serverKeyPair->privKey
+
+#define SSL_LOCK_RANK_SPEC 255
+#define SSL_LOCK_RANK_GLOBAL NSS_RWLOCK_RANK_NONE
+
+/* These are the valid values for shutdownHow.
+** These values are each 1 greater than the NSPR values, and the code
+** depends on that relation to efficiently convert PR_SHUTDOWN values
+** into ssl_SHUTDOWN values. These values use one bit for read, and
+** another bit for write, and can be used as bitmasks.
+*/
+#define ssl_SHUTDOWN_NONE 0 /* NOT shutdown at all */
+#define ssl_SHUTDOWN_RCV 1 /* PR_SHUTDOWN_RCV +1 */
+#define ssl_SHUTDOWN_SEND 2 /* PR_SHUTDOWN_SEND +1 */
+#define ssl_SHUTDOWN_BOTH 3 /* PR_SHUTDOWN_BOTH +1 */
+
+/*
+** A gather object. Used to read some data until a count has been
+** satisfied. Primarily for support of async sockets.
+** Everything in here is protected by the recvBufLock.
+*/
+struct sslGatherStr {
+ int state; /* see GS_ values below. */ /* ssl 2 & 3 */
+
+ /* "buf" holds received plaintext SSL records, after decrypt and MAC check.
+ * SSL2: recv'd ciphertext records are put here, then decrypted in place.
+ * SSL3: recv'd ciphertext records are put in inbuf (see below), then
+ * decrypted into buf.
+ */
+ sslBuffer buf; /*recvBufLock*/ /* ssl 2 & 3 */
+
+ /* number of bytes previously read into hdr or buf(ssl2) or inbuf (ssl3).
+ ** (offset - writeOffset) is the number of ciphertext bytes read in but
+ ** not yet deciphered.
+ */
+ unsigned int offset; /* ssl 2 & 3 */
+
+ /* number of bytes to read in next call to ssl_DefRecv (recv) */
+ unsigned int remainder; /* ssl 2 & 3 */
+
+ /* Number of ciphertext bytes to read in after 2-byte SSL record header. */
+ unsigned int count; /* ssl2 only */
+
+ /* size of the final plaintext record.
+ ** == count - (recordPadding + MAC size)
+ */
+ unsigned int recordLen; /* ssl2 only */
+
+ /* number of bytes of padding to be removed after decrypting. */
+ /* This value is taken from the record's hdr[2], which means a too large
+ * value could crash us.
+ */
+ unsigned int recordPadding; /* ssl2 only */
+
+ /* plaintext DATA begins this many bytes into "buf". */
+ unsigned int recordOffset; /* ssl2 only */
+
+ int encrypted; /* SSL2 session is now encrypted. ssl2 only */
+
+ /* These next two values are used by SSL2 and SSL3.
+ ** DoRecv uses them to extract application data.
+ ** The difference between writeOffset and readOffset is the amount of
+ ** data available to the application. Note that the actual offset of
+ ** the data in "buf" is recordOffset (above), not readOffset.
+ ** In the current implementation, this is made available before the
+ ** MAC is checked!!
+ */
+ unsigned int readOffset; /* Spot where DATA reader (e.g. application
+ ** or handshake code) will read next.
+ ** Always zero for SSl3 application data.
+ */
+ /* offset in buf/inbuf/hdr into which new data will be read from socket. */
+ unsigned int writeOffset;
+
+ /* Buffer for ssl3 to read (encrypted) data from the socket */
+ sslBuffer inbuf; /*recvBufLock*/ /* ssl3 only */
+
+ /* The ssl[23]_GatherData functions read data into this buffer, rather
+ ** than into buf or inbuf, while in the GS_HEADER state.
+ ** The portion of the SSL record header put here always comes off the wire
+ ** as plaintext, never ciphertext.
+ ** For SSL2, the plaintext portion is two bytes long. For SSl3 it is 5.
+ ** For DTLS it is 13.
+ */
+ unsigned char hdr[13]; /* ssl 2 & 3 or dtls */
+
+ /* Buffer for DTLS data read off the wire as a single datagram */
+ sslBuffer dtlsPacket;
+
+ /* the start of the buffered DTLS record in dtlsPacket */
+ unsigned int dtlsPacketOffset;
+};
+
+/* sslGather.state */
+#define GS_INIT 0
+#define GS_HEADER 1
+#define GS_MAC 2
+#define GS_DATA 3
+#define GS_PAD 4
+
+#if defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_WIN32)
+typedef PCERT_KEY_CONTEXT PlatformKey;
+#elif defined(NSS_PLATFORM_CLIENT_AUTH) && defined(XP_MACOSX)
+typedef SecKeyRef PlatformKey;
+#else
+typedef void *PlatformKey;
+#endif
+
+
+
+/*
+** ssl3State and CipherSpec structs
+*/
+
+/* The SSL bulk cipher definition */
+typedef enum {
+ cipher_null,
+ cipher_rc4,
+ cipher_rc4_40,
+ cipher_rc4_56,
+ cipher_rc2,
+ cipher_rc2_40,
+ cipher_des,
+ cipher_3des,
+ cipher_des40,
+ cipher_idea,
+ cipher_aes_128,
+ cipher_aes_256,
+ cipher_camellia_128,
+ cipher_camellia_256,
+ cipher_seed,
+ cipher_aes_128_gcm,
+ cipher_missing /* reserved for no such supported cipher */
+ /* This enum must match ssl3_cipherName[] in ssl3con.c. */
+} SSL3BulkCipher;
+
+typedef enum { type_stream, type_block, type_aead } CipherType;
+
+#define MAX_IV_LENGTH 24
+
+/*
+ * Do not depend upon 64 bit arithmetic in the underlying machine.
+ */
+typedef struct {
+ PRUint32 high;
+ PRUint32 low;
+} SSL3SequenceNumber;
+
+typedef PRUint16 DTLSEpoch;
+
+typedef void (*DTLSTimerCb)(sslSocket *);
+
+#define MAX_MAC_CONTEXT_BYTES 400 /* 400 is large enough for MD5, SHA-1, and
+ * SHA-256. For SHA-384 support, increase
+ * it to 712. */
+#define MAX_MAC_CONTEXT_LLONGS (MAX_MAC_CONTEXT_BYTES / 8)
+
+#define MAX_CIPHER_CONTEXT_BYTES 2080
+#define MAX_CIPHER_CONTEXT_LLONGS (MAX_CIPHER_CONTEXT_BYTES / 8)
+
+typedef struct {
+ SSL3Opaque wrapped_master_secret[48];
+ PRUint16 wrapped_master_secret_len;
+ PRUint8 msIsWrapped;
+ PRUint8 resumable;
+} ssl3SidKeys; /* 52 bytes */
+
+typedef struct {
+ PK11SymKey *write_key;
+ PK11SymKey *write_mac_key;
+ PK11Context *write_mac_context;
+ SECItem write_key_item;
+ SECItem write_iv_item;
+ SECItem write_mac_key_item;
+ SSL3Opaque write_iv[MAX_IV_LENGTH];
+ PRUint64 cipher_context[MAX_CIPHER_CONTEXT_LLONGS];
+} ssl3KeyMaterial;
+
+typedef SECStatus (*SSLCipher)(void * context,
+ unsigned char * out,
+ int * outlen,
+ int maxout,
+ const unsigned char *in,
+ int inlen);
+typedef SECStatus (*SSLAEADCipher)(
+ ssl3KeyMaterial * keys,
+ PRBool doDecrypt,
+ unsigned char * out,
+ int * outlen,
+ int maxout,
+ const unsigned char *in,
+ int inlen,
+ SSL3ContentType type,
+ SSL3ProtocolVersion version,
+ SSL3SequenceNumber seqnum);
+typedef SECStatus (*SSLCompressor)(void * context,
+ unsigned char * out,
+ int * outlen,
+ int maxout,
+ const unsigned char *in,
+ int inlen);
+typedef SECStatus (*SSLDestroy)(void *context, PRBool freeit);
+
+/* The DTLS anti-replay window. Defined here because we need it in
+ * the cipher spec. Note that this is a ring buffer but left and
+ * right represent the true window, with modular arithmetic used to
+ * map them onto the buffer.
+ */
+#define DTLS_RECVD_RECORDS_WINDOW 1024 /* Packets; approximate
+ * Must be divisible by 8
+ */
+typedef struct DTLSRecvdRecordsStr {
+ unsigned char data[DTLS_RECVD_RECORDS_WINDOW/8];
+ PRUint64 left;
+ PRUint64 right;
+} DTLSRecvdRecords;
+
+/*
+** These are the "specs" in the "ssl3" struct.
+** Access to the pointers to these specs, and all the specs' contents
+** (direct and indirect) is protected by the reader/writer lock ss->specLock.
+*/
+typedef struct {
+ const ssl3BulkCipherDef *cipher_def;
+ const ssl3MACDef * mac_def;
+ SSLCompressionMethod compression_method;
+ int mac_size;
+ SSLCipher encode;
+ SSLCipher decode;
+ SSLAEADCipher aead;
+ SSLDestroy destroy;
+ void * encodeContext;
+ void * decodeContext;
+ SSLCompressor compressor; /* Don't name these fields compress */
+ SSLCompressor decompressor; /* and uncompress because zconf.h */
+ /* may define them as macros. */
+ SSLDestroy destroyCompressContext;
+ void * compressContext;
+ SSLDestroy destroyDecompressContext;
+ void * decompressContext;
+ PRBool bypassCiphers; /* did double bypass (at least) */
+ PK11SymKey * master_secret;
+ SSL3SequenceNumber write_seq_num;
+ SSL3SequenceNumber read_seq_num;
+ SSL3ProtocolVersion version;
+ ssl3KeyMaterial client;
+ ssl3KeyMaterial server;
+ SECItem msItem;
+ unsigned char key_block[NUM_MIXERS * MD5_LENGTH];
+ unsigned char raw_master_secret[56];
+ SECItem srvVirtName; /* for server: name that was negotiated
+ * with a client. For client - is
+ * always set to NULL.*/
+ DTLSEpoch epoch;
+ DTLSRecvdRecords recvdRecords;
+} ssl3CipherSpec;
+
+typedef enum { never_cached,
+ in_client_cache,
+ in_server_cache,
+ invalid_cache /* no longer in any cache. */
+} Cached;
+
+#define MAX_PEER_CERT_CHAIN_SIZE 8
+
+struct sslSessionIDStr {
+ sslSessionID * next; /* chain used for client sockets, only */
+
+ CERTCertificate * peerCert;
+ CERTCertificate * peerCertChain[MAX_PEER_CERT_CHAIN_SIZE];
+ SECItemArray peerCertStatus; /* client only */
+ const char * peerID; /* client only */
+ const char * urlSvrName; /* client only */
+ CERTCertificate * localCert;
+
+ PRIPv6Addr addr;
+ PRUint16 port;
+
+ SSL3ProtocolVersion version;
+
+ PRUint32 creationTime; /* seconds since Jan 1, 1970 */
+ PRUint32 lastAccessTime; /* seconds since Jan 1, 1970 */
+ PRUint32 expirationTime; /* seconds since Jan 1, 1970 */
+ Cached cached;
+ int references;
+
+ SSLSignType authAlgorithm;
+ PRUint32 authKeyBits;
+ SSLKEAType keaType;
+ PRUint32 keaKeyBits;
+
+ union {
+ struct {
+ /* the V2 code depends upon the size of sessionID. */
+ unsigned char sessionID[SSL2_SESSIONID_BYTES];
+
+ /* Stuff used to recreate key and read/write cipher objects */
+ SECItem masterKey; /* never wrapped */
+ int cipherType;
+ SECItem cipherArg;
+ int keyBits;
+ int secretKeyBits;
+ } ssl2;
+ struct {
+ /* values that are copied into the server's on-disk SID cache. */
+ PRUint8 sessionIDLength;
+ SSL3Opaque sessionID[SSL3_SESSIONID_BYTES];
+
+ ssl3CipherSuite cipherSuite;
+ SSLCompressionMethod compression;
+ int policy;
+ ssl3SidKeys keys;
+ CK_MECHANISM_TYPE masterWrapMech;
+ /* mechanism used to wrap master secret */
+ SSL3KEAType exchKeyType;
+ /* key type used in exchange algorithm,
+ * and to wrap the sym wrapping key. */
+#ifdef NSS_ENABLE_ECC
+ PRUint32 negotiatedECCurves;
+#endif /* NSS_ENABLE_ECC */
+
+ /* The following values are NOT restored from the server's on-disk
+ * session cache, but are restored from the client's cache.
+ */
+ PK11SymKey * clientWriteKey;
+ PK11SymKey * serverWriteKey;
+
+ /* The following values pertain to the slot that wrapped the
+ ** master secret. (used only in client)
+ */
+ SECMODModuleID masterModuleID;
+ /* what module wrapped the master secret */
+ CK_SLOT_ID masterSlotID;
+ PRUint16 masterWrapIndex;
+ /* what's the key index for the wrapping key */
+ PRUint16 masterWrapSeries;
+ /* keep track of the slot series, so we don't
+ * accidently try to use new keys after the
+ * card gets removed and replaced.*/
+
+ /* The following values pertain to the slot that did the signature
+ ** for client auth. (used only in client)
+ */
+ SECMODModuleID clAuthModuleID;
+ CK_SLOT_ID clAuthSlotID;
+ PRUint16 clAuthSeries;
+
+ char masterValid;
+ char clAuthValid;
+
+ /* Session ticket if we have one, is sent as an extension in the
+ * ClientHello message. This field is used by clients.
+ */
+ NewSessionTicket sessionTicket;
+ SECItem srvName;
+ } ssl3;
+ } u;
+};
+
+
+typedef struct ssl3CipherSuiteDefStr {
+ ssl3CipherSuite cipher_suite;
+ SSL3BulkCipher bulk_cipher_alg;
+ SSL3MACAlgorithm mac_alg;
+ SSL3KeyExchangeAlgorithm key_exchange_alg;
+} ssl3CipherSuiteDef;
+
+/*
+** There are tables of these, all const.
+*/
+typedef struct {
+ SSL3KeyExchangeAlgorithm kea;
+ SSL3KEAType exchKeyType;
+ SSL3SignType signKeyType;
+ PRBool is_limited;
+ int key_size_limit;
+ PRBool tls_keygen;
+} ssl3KEADef;
+
+/*
+** There are tables of these, all const.
+*/
+struct ssl3BulkCipherDefStr {
+ SSL3BulkCipher cipher;
+ SSLCipherAlgorithm calg;
+ int key_size;
+ int secret_key_size;
+ CipherType type;
+ int iv_size;
+ int block_size;
+ int tag_size; /* authentication tag size for AEAD ciphers. */
+ int explicit_nonce_size; /* for AEAD ciphers. */
+};
+
+/*
+** There are tables of these, all const.
+*/
+struct ssl3MACDefStr {
+ SSL3MACAlgorithm mac;
+ CK_MECHANISM_TYPE mmech;
+ int pad_size;
+ int mac_size;
+};
+
+typedef enum {
+ wait_client_hello,
+ wait_client_cert,
+ wait_client_key,
+ wait_cert_verify,
+ wait_change_cipher,
+ wait_finished,
+ wait_server_hello,
+ wait_certificate_status,
+ wait_server_cert,
+ wait_server_key,
+ wait_cert_request,
+ wait_hello_done,
+ wait_new_session_ticket,
+ idle_handshake
+} SSL3WaitState;
+
+/*
+ * TLS extension related constants and data structures.
+ */
+typedef struct TLSExtensionDataStr TLSExtensionData;
+typedef struct SessionTicketDataStr SessionTicketData;
+
+struct TLSExtensionDataStr {
+ /* registered callbacks that send server hello extensions */
+ ssl3HelloExtensionSender serverSenders[SSL_MAX_EXTENSIONS];
+ /* Keep track of the extensions that are negotiated. */
+ PRUint16 numAdvertised;
+ PRUint16 numNegotiated;
+ PRUint16 advertised[SSL_MAX_EXTENSIONS];
+ PRUint16 negotiated[SSL_MAX_EXTENSIONS];
+
+ /* SessionTicket Extension related data. */
+ PRBool ticketTimestampVerified;
+ PRBool emptySessionTicket;
+
+ /* SNI Extension related data
+ * Names data is not coppied from the input buffer. It can not be
+ * used outside the scope where input buffer is defined and that
+ * is beyond ssl3_HandleClientHello function. */
+ SECItem *sniNameArr;
+ PRUint32 sniNameArrSize;
+};
+
+typedef SECStatus (*sslRestartTarget)(sslSocket *);
+
+/*
+** A DTLS queued message (potentially to be retransmitted)
+*/
+typedef struct DTLSQueuedMessageStr {
+ PRCList link; /* The linked list link */
+ DTLSEpoch epoch; /* The epoch to use */
+ SSL3ContentType type; /* The message type */
+ unsigned char *data; /* The data */
+ PRUint16 len; /* The data length */
+} DTLSQueuedMessage;
+
+typedef enum {
+ handshake_hash_unknown = 0,
+ handshake_hash_combo = 1, /* The MD5/SHA-1 combination */
+ handshake_hash_single = 2 /* A single hash */
+} SSL3HandshakeHashType;
+
+/*
+** This is the "hs" member of the "ssl3" struct.
+** This entire struct is protected by ssl3HandshakeLock
+*/
+typedef struct SSL3HandshakeStateStr {
+ SSL3Random server_random;
+ SSL3Random client_random;
+ SSL3WaitState ws;
+
+ /* This group of members is used for handshake running hashes. */
+ SSL3HandshakeHashType hashType;
+ sslBuffer messages; /* Accumulated handshake messages */
+#ifndef NO_PKCS11_BYPASS
+ /* Bypass mode:
+ * SSL 3.0 - TLS 1.1 use both |md5_cx| and |sha_cx|. |md5_cx| is used for
+ * MD5 and |sha_cx| for SHA-1.
+ * TLS 1.2 and later use only |sha_cx|, for SHA-256. NOTE: When we support
+ * SHA-384, increase MAX_MAC_CONTEXT_BYTES to 712. */
+ PRUint64 md5_cx[MAX_MAC_CONTEXT_LLONGS];
+ PRUint64 sha_cx[MAX_MAC_CONTEXT_LLONGS];
+ const SECHashObject * sha_obj;
+ /* The function prototype of sha_obj->clone() does not match the prototype
+ * of the freebl <HASH>_Clone functions, so we need a dedicated function
+ * pointer for the <HASH>_Clone function. */
+ void (*sha_clone)(void *dest, void *src);
+#endif
+ /* PKCS #11 mode:
+ * SSL 3.0 - TLS 1.1 use both |md5| and |sha|. |md5| is used for MD5 and
+ * |sha| for SHA-1.
+ * TLS 1.2 and later use only |sha|, for SHA-256. */
+ PK11Context * md5;
+ PK11Context * sha;
+
+const ssl3KEADef * kea_def;
+ ssl3CipherSuite cipher_suite;
+const ssl3CipherSuiteDef *suite_def;
+ SSLCompressionMethod compression;
+ sslBuffer msg_body; /* protected by recvBufLock */
+ /* partial handshake message from record layer */
+ unsigned int header_bytes;
+ /* number of bytes consumed from handshake */
+ /* message for message type and header length */
+ SSL3HandshakeType msg_type;
+ unsigned long msg_len;
+ SECItem ca_list; /* used only by client */
+ PRBool isResuming; /* are we resuming a session */
+ PRBool usedStepDownKey; /* we did a server key exchange. */
+ PRBool sendingSCSV; /* instead of empty RI */
+ sslBuffer msgState; /* current state for handshake messages*/
+ /* protected by recvBufLock */
+ PRUint16 finishedBytes; /* size of single finished below */
+ union {
+ TLSFinished tFinished[2]; /* client, then server */
+ SSL3Finished sFinished[2];
+ SSL3Opaque data[72];
+ } finishedMsgs;
+#ifdef NSS_ENABLE_ECC
+ PRUint32 negotiatedECCurves; /* bit mask */
+#endif /* NSS_ENABLE_ECC */
+
+ PRBool authCertificatePending;
+ /* Which function should SSL_RestartHandshake* call if we're blocked?
+ * One of NULL, ssl3_SendClientSecondRound, ssl3_FinishHandshake,
+ * or ssl3_AlwaysFail */
+ sslRestartTarget restartTarget;
+ /* Shared state between ssl3_HandleFinished and ssl3_FinishHandshake */
+ PRBool cacheSID;
+
+ /* clientSigAndHash contains the contents of the signature_algorithms
+ * extension (if any) from the client. This is only valid for TLS 1.2
+ * or later. */
+ SSL3SignatureAndHashAlgorithm *clientSigAndHash;
+ unsigned int numClientSigAndHash;
+
+ /* This group of values is used for DTLS */
+ PRUint16 sendMessageSeq; /* The sending message sequence
+ * number */
+ PRCList lastMessageFlight; /* The last message flight we
+ * sent */
+ PRUint16 maxMessageSent; /* The largest message we sent */
+ PRUint16 recvMessageSeq; /* The receiving message sequence
+ * number */
+ sslBuffer recvdFragments; /* The fragments we have received in
+ * a bitmask */
+ PRInt32 recvdHighWater; /* The high water mark for fragments
+ * received. -1 means no reassembly
+ * in progress. */
+ unsigned char cookie[32]; /* The cookie */
+ unsigned char cookieLen; /* The length of the cookie */
+ PRIntervalTime rtTimerStarted; /* When the timer was started */
+ DTLSTimerCb rtTimerCb; /* The function to call on expiry */
+ PRUint32 rtTimeoutMs; /* The length of the current timeout
+ * used for backoff (in ms) */
+ PRUint32 rtRetries; /* The retry counter */
+} SSL3HandshakeState;
+
+
+
+/*
+** This is the "ssl3" struct, as in "ss->ssl3".
+** note:
+** usually, crSpec == cwSpec and prSpec == pwSpec.
+** Sometimes, crSpec == pwSpec and prSpec == cwSpec.
+** But there are never more than 2 actual specs.
+** No spec must ever be modified if either "current" pointer points to it.
+*/
+struct ssl3StateStr {
+
+ /*
+ ** The following Specs and Spec pointers must be protected using the
+ ** Spec Lock.
+ */
+ ssl3CipherSpec * crSpec; /* current read spec. */
+ ssl3CipherSpec * prSpec; /* pending read spec. */
+ ssl3CipherSpec * cwSpec; /* current write spec. */
+ ssl3CipherSpec * pwSpec; /* pending write spec. */
+
+ CERTCertificate * clientCertificate; /* used by client */
+ SECKEYPrivateKey * clientPrivateKey; /* used by client */
+ /* platformClientKey is present even when NSS_PLATFORM_CLIENT_AUTH is not
+ * defined in order to allow cleaner conditional code.
+ * At most one of clientPrivateKey and platformClientKey may be set. */
+ PlatformKey platformClientKey; /* used by client */
+ CERTCertificateList *clientCertChain; /* used by client */
+ PRBool sendEmptyCert; /* used by client */
+
+ SECKEYPrivateKey *channelID; /* used by client */
+ SECKEYPublicKey *channelIDPub; /* used by client */
+
+ int policy;
+ /* This says what cipher suites we can do, and should
+ * be either SSL_ALLOWED or SSL_RESTRICTED
+ */
+ PLArenaPool * peerCertArena;
+ /* These are used to keep track of the peer CA */
+ void * peerCertChain;
+ /* chain while we are trying to validate it. */
+ CERTDistNames * ca_list;
+ /* used by server. trusted CAs for this socket. */
+ PRBool initialized;
+ SSL3HandshakeState hs;
+ ssl3CipherSpec specs[2]; /* one is current, one is pending. */
+
+ /* In a client: if the server supports Next Protocol Negotiation, then
+ * this is the protocol that was negotiated.
+ */
+ SECItem nextProto;
+ SSLNextProtoState nextProtoState;
+
+ PRUint16 mtu; /* Our estimate of the MTU */
+
+ /* DTLS-SRTP cipher suite preferences (if any) */
+ PRUint16 dtlsSRTPCiphers[MAX_DTLS_SRTP_CIPHER_SUITES];
+ PRUint16 dtlsSRTPCipherCount;
+ PRUint16 dtlsSRTPCipherSuite; /* 0 if not selected */
+};
+
+#define DTLS_MAX_MTU 1500 /* Ethernet MTU but without subtracting the
+ * headers, so slightly larger than expected */
+#define IS_DTLS(ss) (ss->protocolVariant == ssl_variant_datagram)
+
+typedef struct {
+ SSL3ContentType type;
+ SSL3ProtocolVersion version;
+ SSL3SequenceNumber seq_num; /* DTLS only */
+ sslBuffer * buf;
+} SSL3Ciphertext;
+
+struct ssl3KeyPairStr {
+ SECKEYPrivateKey * privKey;
+ SECKEYPublicKey * pubKey;
+ PRInt32 refCount; /* use PR_Atomic calls for this. */
+};
+
+typedef struct SSLWrappedSymWrappingKeyStr {
+ SSL3Opaque wrappedSymmetricWrappingkey[512];
+ CK_MECHANISM_TYPE symWrapMechanism;
+ /* unwrapped symmetric wrapping key uses this mechanism */
+ CK_MECHANISM_TYPE asymWrapMechanism;
+ /* mechanism used to wrap the SymmetricWrappingKey using
+ * server's public and/or private keys. */
+ SSL3KEAType exchKeyType; /* type of keys used to wrap SymWrapKey*/
+ PRInt32 symWrapMechIndex;
+ PRUint16 wrappedSymKeyLen;
+} SSLWrappedSymWrappingKey;
+
+typedef struct SessionTicketStr {
+ PRUint16 ticket_version;
+ SSL3ProtocolVersion ssl_version;
+ ssl3CipherSuite cipher_suite;
+ SSLCompressionMethod compression_method;
+ SSLSignType authAlgorithm;
+ PRUint32 authKeyBits;
+ SSLKEAType keaType;
+ PRUint32 keaKeyBits;
+ /*
+ * exchKeyType and msWrapMech contain meaningful values only if
+ * ms_is_wrapped is true.
+ */
+ PRUint8 ms_is_wrapped;
+ SSLKEAType exchKeyType; /* XXX(wtc): same as keaType above? */
+ CK_MECHANISM_TYPE msWrapMech;
+ PRUint16 ms_length;
+ SSL3Opaque master_secret[48];
+ ClientIdentity client_identity;
+ SECItem peer_cert;
+ PRUint32 timestamp;
+ SECItem srvName; /* negotiated server name */
+} SessionTicket;
+
+/*
+ * SSL2 buffers used in SSL3.
+ * writeBuf in the SecurityInfo maintained by sslsecur.c is used
+ * to hold the data just about to be passed to the kernel
+ * sendBuf in the ConnectInfo maintained by sslcon.c is used
+ * to hold handshake messages as they are accumulated
+ */
+
+/*
+** This is "ci", as in "ss->sec.ci".
+**
+** Protection: All the variables in here are protected by
+** firstHandshakeLock AND (in ssl3) ssl3HandshakeLock
+*/
+struct sslConnectInfoStr {
+ /* outgoing handshakes appended to this. */
+ sslBuffer sendBuf; /*xmitBufLock*/ /* ssl 2 & 3 */
+
+ PRIPv6Addr peer; /* ssl 2 & 3 */
+ unsigned short port; /* ssl 2 & 3 */
+
+ sslSessionID *sid; /* ssl 2 & 3 */
+
+ /* see CIS_HAVE defines below for the bit values in *elements. */
+ char elements; /* ssl2 only */
+ char requiredElements; /* ssl2 only */
+ char sentElements; /* ssl2 only */
+
+ char sentFinished; /* ssl2 only */
+
+ /* Length of server challenge. Used by client when saving challenge */
+ int serverChallengeLen; /* ssl2 only */
+ /* type of authentication requested by server */
+ unsigned char authType; /* ssl2 only */
+
+ /* Challenge sent by client to server in client-hello message */
+ /* SSL3 gets a copy of this. See ssl3_StartHandshakeHash(). */
+ unsigned char clientChallenge[SSL_MAX_CHALLENGE_BYTES]; /* ssl 2 & 3 */
+
+ /* Connection-id sent by server to client in server-hello message */
+ unsigned char connectionID[SSL_CONNECTIONID_BYTES]; /* ssl2 only */
+
+ /* Challenge sent by server to client in request-certificate message */
+ unsigned char serverChallenge[SSL_MAX_CHALLENGE_BYTES]; /* ssl2 only */
+
+ /* Information kept to handle a request-certificate message */
+ unsigned char readKey[SSL_MAX_MASTER_KEY_BYTES]; /* ssl2 only */
+ unsigned char writeKey[SSL_MAX_MASTER_KEY_BYTES]; /* ssl2 only */
+ unsigned keySize; /* ssl2 only */
+};
+
+/* bit values for ci->elements, ci->requiredElements, sentElements. */
+#define CIS_HAVE_MASTER_KEY 0x01
+#define CIS_HAVE_CERTIFICATE 0x02
+#define CIS_HAVE_FINISHED 0x04
+#define CIS_HAVE_VERIFY 0x08
+
+/* Note: The entire content of this struct and whatever it points to gets
+ * blown away by SSL_ResetHandshake(). This is "sec" as in "ss->sec".
+ *
+ * Unless otherwise specified below, the contents of this struct are
+ * protected by firstHandshakeLock AND (in ssl3) ssl3HandshakeLock.
+ */
+struct sslSecurityInfoStr {
+ sslSendFunc send; /*xmitBufLock*/ /* ssl 2 & 3 */
+ int isServer; /* Spec Lock?*/ /* ssl 2 & 3 */
+ sslBuffer writeBuf; /*xmitBufLock*/ /* ssl 2 & 3 */
+
+ int cipherType; /* ssl 2 & 3 */
+ int keyBits; /* ssl 2 & 3 */
+ int secretKeyBits; /* ssl 2 & 3 */
+ CERTCertificate *localCert; /* ssl 2 & 3 */
+ CERTCertificate *peerCert; /* ssl 2 & 3 */
+ SECKEYPublicKey *peerKey; /* ssl3 only */
+
+ SSLSignType authAlgorithm;
+ PRUint32 authKeyBits;
+ SSLKEAType keaType;
+ PRUint32 keaKeyBits;
+
+ /*
+ ** Procs used for SID cache (nonce) management.
+ ** Different implementations exist for clients/servers
+ ** The lookup proc is only used for servers. Baloney!
+ */
+ sslSessionIDCacheFunc cache; /* ssl 2 & 3 */
+ sslSessionIDUncacheFunc uncache; /* ssl 2 & 3 */
+
+ /*
+ ** everything below here is for ssl2 only.
+ ** This stuff is equivalent to SSL3's "spec", and is protected by the
+ ** same "Spec Lock" as used for SSL3's specs.
+ */
+ PRUint32 sendSequence; /*xmitBufLock*/ /* ssl2 only */
+ PRUint32 rcvSequence; /*recvBufLock*/ /* ssl2 only */
+
+ /* Hash information; used for one-way-hash functions (MD2, MD5, etc.) */
+ const SECHashObject *hash; /* Spec Lock */ /* ssl2 only */
+ void *hashcx; /* Spec Lock */ /* ssl2 only */
+
+ SECItem sendSecret; /* Spec Lock */ /* ssl2 only */
+ SECItem rcvSecret; /* Spec Lock */ /* ssl2 only */
+
+ /* Session cypher contexts; one for each direction */
+ void *readcx; /* Spec Lock */ /* ssl2 only */
+ void *writecx; /* Spec Lock */ /* ssl2 only */
+ SSLCipher enc; /* Spec Lock */ /* ssl2 only */
+ SSLCipher dec; /* Spec Lock */ /* ssl2 only */
+ void (*destroy)(void *, PRBool); /* Spec Lock */ /* ssl2 only */
+
+ /* Blocking information for the session cypher */
+ int blockShift; /* Spec Lock */ /* ssl2 only */
+ int blockSize; /* Spec Lock */ /* ssl2 only */
+
+ /* These are used during a connection handshake */
+ sslConnectInfo ci; /* ssl 2 & 3 */
+
+};
+
+/*
+** SSL Socket struct
+**
+** Protection: XXX
+*/
+struct sslSocketStr {
+ PRFileDesc * fd;
+
+ /* Pointer to operations vector for this socket */
+ const sslSocketOps * ops;
+
+ /* SSL socket options */
+ sslOptions opt;
+ /* Enabled version range */
+ SSLVersionRange vrange;
+
+ /* State flags */
+ unsigned long clientAuthRequested;
+ unsigned long delayDisabled; /* Nagle delay disabled */
+ unsigned long firstHsDone; /* first handshake is complete. */
+ unsigned long handshakeBegun;
+ unsigned long lastWriteBlocked;
+ unsigned long recvdCloseNotify; /* received SSL EOF. */
+ unsigned long TCPconnected;
+ unsigned long appDataBuffered;
+ unsigned long peerRequestedProtection; /* from old renegotiation */
+
+ /* version of the protocol to use */
+ SSL3ProtocolVersion version;
+ SSL3ProtocolVersion clientHelloVersion; /* version sent in client hello. */
+
+ sslSecurityInfo sec; /* not a pointer any more */
+
+ /* protected by firstHandshakeLock AND (in ssl3) ssl3HandshakeLock. */
+ const char *url; /* ssl 2 & 3 */
+
+ sslHandshakeFunc handshake; /*firstHandshakeLock*/
+ sslHandshakeFunc nextHandshake; /*firstHandshakeLock*/
+ sslHandshakeFunc securityHandshake; /*firstHandshakeLock*/
+
+ /* the following variable is only used with socks or other proxies. */
+ char * peerID; /* String uniquely identifies target server. */
+
+ unsigned char * cipherSpecs;
+ unsigned int sizeCipherSpecs;
+const unsigned char * preferredCipher;
+
+ /* TLS ClientCertificateTypes requested during HandleCertificateRequest. */
+ /* Will be NULL at all other times. */
+ const SECItem *requestedCertTypes;
+
+ ssl3KeyPair * stepDownKeyPair; /* RSA step down keys */
+
+ /* Callbacks */
+ SSLAuthCertificate authCertificate;
+ void *authCertificateArg;
+ SSLGetClientAuthData getClientAuthData;
+ void *getClientAuthDataArg;
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ SSLGetPlatformClientAuthData getPlatformClientAuthData;
+ void *getPlatformClientAuthDataArg;
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ SSLSNISocketConfig sniSocketConfig;
+ void *sniSocketConfigArg;
+ SSLBadCertHandler handleBadCert;
+ void *badCertArg;
+ SSLHandshakeCallback handshakeCallback;
+ void *handshakeCallbackData;
+ void *pkcs11PinArg;
+ SSLNextProtoCallback nextProtoCallback;
+ void *nextProtoArg;
+ SSLClientChannelIDCallback getChannelID;
+ void *getChannelIDArg;
+
+ PRIntervalTime rTimeout; /* timeout for NSPR I/O */
+ PRIntervalTime wTimeout; /* timeout for NSPR I/O */
+ PRIntervalTime cTimeout; /* timeout for NSPR I/O */
+
+ PZLock * recvLock; /* lock against multiple reader threads. */
+ PZLock * sendLock; /* lock against multiple sender threads. */
+
+ PZMonitor * recvBufLock; /* locks low level recv buffers. */
+ PZMonitor * xmitBufLock; /* locks low level xmit buffers. */
+
+ /* Only one thread may operate on the socket until the initial handshake
+ ** is complete. This Monitor ensures that. Since SSL2 handshake is
+ ** only done once, this is also effectively the SSL2 handshake lock.
+ */
+ PZMonitor * firstHandshakeLock;
+
+ /* This monitor protects the ssl3 handshake state machine data.
+ ** Only one thread (reader or writer) may be in the ssl3 handshake state
+ ** machine at any time. */
+ PZMonitor * ssl3HandshakeLock;
+
+ /* reader/writer lock, protects the secret data needed to encrypt and MAC
+ ** outgoing records, and to decrypt and MAC check incoming ciphertext
+ ** records. */
+ NSSRWLock * specLock;
+
+ /* handle to perm cert db (and implicitly to the temp cert db) used
+ ** with this socket.
+ */
+ CERTCertDBHandle * dbHandle;
+
+ PRThread * writerThread; /* thread holds SSL_LOCK_WRITER lock */
+
+ PRUint16 shutdownHow; /* See ssl_SHUTDOWN defines below. */
+
+ PRUint16 allowedByPolicy; /* copy of global policy bits. */
+ PRUint16 maybeAllowedByPolicy; /* copy of global policy bits. */
+ PRUint16 chosenPreference; /* SSL2 cipher preferences. */
+
+ sslHandshakingType handshaking;
+
+ /* Gather object used for gathering data */
+ sslGather gs; /*recvBufLock*/
+
+ sslBuffer saveBuf; /*xmitBufLock*/
+ sslBuffer pendingBuf; /*xmitBufLock*/
+
+ /* Configuration state for server sockets */
+ /* server cert and key for each KEA type */
+ sslServerCerts serverCerts[kt_kea_size];
+ /* each cert needs its own status */
+ SECItemArray * certStatusArray[kt_kea_size];
+
+ ssl3CipherSuiteCfg cipherSuites[ssl_V3_SUITES_IMPLEMENTED];
+ ssl3KeyPair * ephemeralECDHKeyPair; /* for ECDHE-* handshake */
+
+ /* SSL3 state info. Formerly was a pointer */
+ ssl3State ssl3;
+
+ /*
+ * TLS extension related data.
+ */
+ /* True when the current session is a stateless resume. */
+ PRBool statelessResume;
+ TLSExtensionData xtnData;
+
+ /* Whether we are doing stream or datagram mode */
+ SSLProtocolVariant protocolVariant;
+};
+
+
+
+/* All the global data items declared here should be protected using the
+** ssl_global_data_lock, which is a reader/writer lock.
+*/
+extern NSSRWLock * ssl_global_data_lock;
+extern char ssl_debug;
+extern char ssl_trace;
+extern FILE * ssl_trace_iob;
+extern FILE * ssl_keylog_iob;
+extern CERTDistNames * ssl3_server_ca_list;
+extern PRUint32 ssl_sid_timeout;
+extern PRUint32 ssl3_sid_timeout;
+
+extern const char * const ssl_cipherName[];
+extern const char * const ssl3_cipherName[];
+
+extern sslSessionIDLookupFunc ssl_sid_lookup;
+extern sslSessionIDCacheFunc ssl_sid_cache;
+extern sslSessionIDUncacheFunc ssl_sid_uncache;
+
+/************************************************************************/
+
+SEC_BEGIN_PROTOS
+
+/* Functions for handling SECItemArrays, added in NSS 3.15 */
+extern SECItemArray *SECITEM_AllocArray(PLArenaPool *arena,
+ SECItemArray *array,
+ unsigned int len);
+extern SECItemArray *SECITEM_DupArray(PLArenaPool *arena,
+ const SECItemArray *from);
+extern void SECITEM_FreeArray(SECItemArray *array, PRBool freeit);
+extern void SECITEM_ZfreeArray(SECItemArray *array, PRBool freeit);
+
+/* Internal initialization and installation of the SSL error tables */
+extern SECStatus ssl_Init(void);
+extern SECStatus ssl_InitializePRErrorTable(void);
+
+/* Implementation of ops for default (non socks, non secure) case */
+extern int ssl_DefConnect(sslSocket *ss, const PRNetAddr *addr);
+extern PRFileDesc *ssl_DefAccept(sslSocket *ss, PRNetAddr *addr);
+extern int ssl_DefBind(sslSocket *ss, const PRNetAddr *addr);
+extern int ssl_DefListen(sslSocket *ss, int backlog);
+extern int ssl_DefShutdown(sslSocket *ss, int how);
+extern int ssl_DefClose(sslSocket *ss);
+extern int ssl_DefRecv(sslSocket *ss, unsigned char *buf, int len, int flags);
+extern int ssl_DefSend(sslSocket *ss, const unsigned char *buf,
+ int len, int flags);
+extern int ssl_DefRead(sslSocket *ss, unsigned char *buf, int len);
+extern int ssl_DefWrite(sslSocket *ss, const unsigned char *buf, int len);
+extern int ssl_DefGetpeername(sslSocket *ss, PRNetAddr *name);
+extern int ssl_DefGetsockname(sslSocket *ss, PRNetAddr *name);
+extern int ssl_DefGetsockopt(sslSocket *ss, PRSockOption optname,
+ void *optval, PRInt32 *optlen);
+extern int ssl_DefSetsockopt(sslSocket *ss, PRSockOption optname,
+ const void *optval, PRInt32 optlen);
+
+/* Implementation of ops for socks only case */
+extern int ssl_SocksConnect(sslSocket *ss, const PRNetAddr *addr);
+extern PRFileDesc *ssl_SocksAccept(sslSocket *ss, PRNetAddr *addr);
+extern int ssl_SocksBind(sslSocket *ss, const PRNetAddr *addr);
+extern int ssl_SocksListen(sslSocket *ss, int backlog);
+extern int ssl_SocksGetsockname(sslSocket *ss, PRNetAddr *name);
+extern int ssl_SocksRecv(sslSocket *ss, unsigned char *buf, int len, int flags);
+extern int ssl_SocksSend(sslSocket *ss, const unsigned char *buf,
+ int len, int flags);
+extern int ssl_SocksRead(sslSocket *ss, unsigned char *buf, int len);
+extern int ssl_SocksWrite(sslSocket *ss, const unsigned char *buf, int len);
+
+/* Implementation of ops for secure only case */
+extern int ssl_SecureConnect(sslSocket *ss, const PRNetAddr *addr);
+extern PRFileDesc *ssl_SecureAccept(sslSocket *ss, PRNetAddr *addr);
+extern int ssl_SecureRecv(sslSocket *ss, unsigned char *buf,
+ int len, int flags);
+extern int ssl_SecureSend(sslSocket *ss, const unsigned char *buf,
+ int len, int flags);
+extern int ssl_SecureRead(sslSocket *ss, unsigned char *buf, int len);
+extern int ssl_SecureWrite(sslSocket *ss, const unsigned char *buf, int len);
+extern int ssl_SecureShutdown(sslSocket *ss, int how);
+extern int ssl_SecureClose(sslSocket *ss);
+
+/* Implementation of ops for secure socks case */
+extern int ssl_SecureSocksConnect(sslSocket *ss, const PRNetAddr *addr);
+extern PRFileDesc *ssl_SecureSocksAccept(sslSocket *ss, PRNetAddr *addr);
+extern PRFileDesc *ssl_FindTop(sslSocket *ss);
+
+/* Gather funcs. */
+extern sslGather * ssl_NewGather(void);
+extern SECStatus ssl_InitGather(sslGather *gs);
+extern void ssl_DestroyGather(sslGather *gs);
+extern int ssl2_GatherData(sslSocket *ss, sslGather *gs, int flags);
+extern int ssl2_GatherRecord(sslSocket *ss, int flags);
+extern SECStatus ssl_GatherRecord1stHandshake(sslSocket *ss);
+
+extern SECStatus ssl2_HandleClientHelloMessage(sslSocket *ss);
+extern SECStatus ssl2_HandleServerHelloMessage(sslSocket *ss);
+extern int ssl2_StartGatherBytes(sslSocket *ss, sslGather *gs,
+ unsigned int count);
+
+extern SECStatus ssl_CreateSecurityInfo(sslSocket *ss);
+extern SECStatus ssl_CopySecurityInfo(sslSocket *ss, sslSocket *os);
+extern void ssl_ResetSecurityInfo(sslSecurityInfo *sec, PRBool doMemset);
+extern void ssl_DestroySecurityInfo(sslSecurityInfo *sec);
+
+extern void ssl_PrintBuf(sslSocket *ss, const char *msg, const void *cp, int len);
+extern void ssl_DumpMsg(sslSocket *ss, unsigned char *bp, unsigned len);
+
+extern int ssl_SendSavedWriteData(sslSocket *ss);
+extern SECStatus ssl_SaveWriteData(sslSocket *ss,
+ const void* p, unsigned int l);
+extern SECStatus ssl2_BeginClientHandshake(sslSocket *ss);
+extern SECStatus ssl2_BeginServerHandshake(sslSocket *ss);
+extern int ssl_Do1stHandshake(sslSocket *ss);
+
+extern SECStatus sslBuffer_Grow(sslBuffer *b, unsigned int newLen);
+extern SECStatus sslBuffer_Append(sslBuffer *b, const void * data,
+ unsigned int len);
+
+extern void ssl2_UseClearSendFunc(sslSocket *ss);
+extern void ssl_ChooseSessionIDProcs(sslSecurityInfo *sec);
+
+extern sslSessionID *ssl3_NewSessionID(sslSocket *ss, PRBool is_server);
+extern sslSessionID *ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port,
+ const char *peerID, const char *urlSvrName);
+extern void ssl_FreeSID(sslSessionID *sid);
+
+extern int ssl3_SendApplicationData(sslSocket *ss, const PRUint8 *in,
+ int len, int flags);
+
+extern PRBool ssl_FdIsBlocking(PRFileDesc *fd);
+
+extern PRBool ssl_SocketIsBlocking(sslSocket *ss);
+
+extern void ssl3_SetAlwaysBlock(sslSocket *ss);
+
+extern SECStatus ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled);
+
+extern PRBool ssl3_CanFalseStart(sslSocket *ss);
+extern SECStatus
+ssl3_CompressMACEncryptRecord(ssl3CipherSpec * cwSpec,
+ PRBool isServer,
+ PRBool isDTLS,
+ PRBool capRecordVersion,
+ SSL3ContentType type,
+ const SSL3Opaque * pIn,
+ PRUint32 contentLen,
+ sslBuffer * wrBuf);
+extern PRInt32 ssl3_SendRecord(sslSocket *ss, DTLSEpoch epoch,
+ SSL3ContentType type,
+ const SSL3Opaque* pIn, PRInt32 nIn,
+ PRInt32 flags);
+
+#ifdef NSS_ENABLE_ZLIB
+/*
+ * The DEFLATE algorithm can result in an expansion of 0.1% + 12 bytes. For a
+ * maximum TLS record payload of 2**14 bytes, that's 29 bytes.
+ */
+#define SSL3_COMPRESSION_MAX_EXPANSION 29
+#else /* !NSS_ENABLE_ZLIB */
+#define SSL3_COMPRESSION_MAX_EXPANSION 0
+#endif
+
+/*
+ * make sure there is room in the write buffer for padding and
+ * other compression and cryptographic expansions.
+ */
+#define SSL3_BUFFER_FUDGE 100 + SSL3_COMPRESSION_MAX_EXPANSION
+
+#define SSL_LOCK_READER(ss) if (ss->recvLock) PZ_Lock(ss->recvLock)
+#define SSL_UNLOCK_READER(ss) if (ss->recvLock) PZ_Unlock(ss->recvLock)
+#define SSL_LOCK_WRITER(ss) if (ss->sendLock) PZ_Lock(ss->sendLock)
+#define SSL_UNLOCK_WRITER(ss) if (ss->sendLock) PZ_Unlock(ss->sendLock)
+
+/* firstHandshakeLock -> recvBufLock */
+#define ssl_Get1stHandshakeLock(ss) \
+ { if (!ss->opt.noLocks) { \
+ PORT_Assert(PZ_InMonitor((ss)->firstHandshakeLock) || \
+ !ssl_HaveRecvBufLock(ss)); \
+ PZ_EnterMonitor((ss)->firstHandshakeLock); \
+ } }
+#define ssl_Release1stHandshakeLock(ss) \
+ { if (!ss->opt.noLocks) PZ_ExitMonitor((ss)->firstHandshakeLock); }
+#define ssl_Have1stHandshakeLock(ss) \
+ (PZ_InMonitor((ss)->firstHandshakeLock))
+
+/* ssl3HandshakeLock -> xmitBufLock */
+#define ssl_GetSSL3HandshakeLock(ss) \
+ { if (!ss->opt.noLocks) { \
+ PORT_Assert(!ssl_HaveXmitBufLock(ss)); \
+ PZ_EnterMonitor((ss)->ssl3HandshakeLock); \
+ } }
+#define ssl_ReleaseSSL3HandshakeLock(ss) \
+ { if (!ss->opt.noLocks) PZ_ExitMonitor((ss)->ssl3HandshakeLock); }
+#define ssl_HaveSSL3HandshakeLock(ss) \
+ (PZ_InMonitor((ss)->ssl3HandshakeLock))
+
+#define ssl_GetSpecReadLock(ss) \
+ { if (!ss->opt.noLocks) NSSRWLock_LockRead((ss)->specLock); }
+#define ssl_ReleaseSpecReadLock(ss) \
+ { if (!ss->opt.noLocks) NSSRWLock_UnlockRead((ss)->specLock); }
+/* NSSRWLock_HaveReadLock is not exported so there's no
+ * ssl_HaveSpecReadLock macro. */
+
+#define ssl_GetSpecWriteLock(ss) \
+ { if (!ss->opt.noLocks) NSSRWLock_LockWrite((ss)->specLock); }
+#define ssl_ReleaseSpecWriteLock(ss) \
+ { if (!ss->opt.noLocks) NSSRWLock_UnlockWrite((ss)->specLock); }
+#define ssl_HaveSpecWriteLock(ss) \
+ (NSSRWLock_HaveWriteLock((ss)->specLock))
+
+/* recvBufLock -> ssl3HandshakeLock -> xmitBufLock */
+#define ssl_GetRecvBufLock(ss) \
+ { if (!ss->opt.noLocks) { \
+ PORT_Assert(!ssl_HaveSSL3HandshakeLock(ss)); \
+ PORT_Assert(!ssl_HaveXmitBufLock(ss)); \
+ PZ_EnterMonitor((ss)->recvBufLock); \
+ } }
+#define ssl_ReleaseRecvBufLock(ss) \
+ { if (!ss->opt.noLocks) PZ_ExitMonitor( (ss)->recvBufLock); }
+#define ssl_HaveRecvBufLock(ss) \
+ (PZ_InMonitor((ss)->recvBufLock))
+
+/* xmitBufLock -> specLock */
+#define ssl_GetXmitBufLock(ss) \
+ { if (!ss->opt.noLocks) PZ_EnterMonitor((ss)->xmitBufLock); }
+#define ssl_ReleaseXmitBufLock(ss) \
+ { if (!ss->opt.noLocks) PZ_ExitMonitor( (ss)->xmitBufLock); }
+#define ssl_HaveXmitBufLock(ss) \
+ (PZ_InMonitor((ss)->xmitBufLock))
+
+/* Placeholder value used in version ranges when SSL 3.0 and all
+ * versions of TLS are disabled.
+ */
+#define SSL_LIBRARY_VERSION_NONE 0
+
+/* SSL_LIBRARY_VERSION_MAX_SUPPORTED is the maximum version that this version
+ * of libssl supports. Applications should use SSL_VersionRangeGetSupported at
+ * runtime to determine which versions are supported by the version of libssl
+ * in use.
+ */
+#define SSL_LIBRARY_VERSION_MAX_SUPPORTED SSL_LIBRARY_VERSION_TLS_1_2
+
+/* Rename this macro SSL_ALL_VERSIONS_DISABLED when SSL 2.0 is removed. */
+#define SSL3_ALL_VERSIONS_DISABLED(vrange) \
+ ((vrange)->min == SSL_LIBRARY_VERSION_NONE)
+
+extern PRBool ssl3_VersionIsSupported(SSLProtocolVariant protocolVariant,
+ SSL3ProtocolVersion version);
+
+extern SECStatus ssl3_KeyAndMacDeriveBypass(ssl3CipherSpec * pwSpec,
+ const unsigned char * cr, const unsigned char * sr,
+ PRBool isTLS, PRBool isExport);
+extern SECStatus ssl3_MasterKeyDeriveBypass( ssl3CipherSpec * pwSpec,
+ const unsigned char * cr, const unsigned char * sr,
+ const SECItem * pms, PRBool isTLS, PRBool isRSA);
+
+/* These functions are called from secnav, even though they're "private". */
+
+extern int ssl2_SendErrorMessage(struct sslSocketStr *ss, int error);
+extern sslSocket *ssl_FindSocket(PRFileDesc *fd);
+extern void ssl_FreeSocket(struct sslSocketStr *ssl);
+extern SECStatus SSL3_SendAlert(sslSocket *ss, SSL3AlertLevel level,
+ SSL3AlertDescription desc);
+extern SECStatus ssl3_DecodeError(sslSocket *ss);
+
+extern SECStatus ssl3_RestartHandshakeAfterCertReq(sslSocket * ss,
+ CERTCertificate * cert,
+ SECKEYPrivateKey * key,
+ CERTCertificateList *certChain);
+
+extern SECStatus ssl3_RestartHandshakeAfterChannelIDReq(
+ sslSocket *ss,
+ SECKEYPublicKey *channelIDPub,
+ SECKEYPrivateKey *channelID);
+
+extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error);
+
+/*
+ * for dealing with SSL 3.0 clients sending SSL 2.0 format hellos
+ */
+extern SECStatus ssl3_HandleV2ClientHello(
+ sslSocket *ss, unsigned char *buffer, int length);
+extern SECStatus ssl3_StartHandshakeHash(
+ sslSocket *ss, unsigned char *buf, int length);
+
+/*
+ * SSL3 specific routines
+ */
+SECStatus ssl3_SendClientHello(sslSocket *ss, PRBool resending);
+
+/*
+ * input into the SSL3 machinery from the actualy network reading code
+ */
+SECStatus ssl3_HandleRecord(
+ sslSocket *ss, SSL3Ciphertext *cipher, sslBuffer *out);
+
+int ssl3_GatherAppDataRecord(sslSocket *ss, int flags);
+int ssl3_GatherCompleteHandshake(sslSocket *ss, int flags);
+/*
+ * When talking to export clients or using export cipher suites, servers
+ * with public RSA keys larger than 512 bits need to use a 512-bit public
+ * key, signed by the larger key. The smaller key is a "step down" key.
+ * Generate that key pair and keep it around.
+ */
+extern SECStatus ssl3_CreateRSAStepDownKeys(sslSocket *ss);
+
+#ifdef NSS_ENABLE_ECC
+extern void ssl3_FilterECCipherSuitesByServerCerts(sslSocket *ss);
+extern PRBool ssl3_IsECCEnabled(sslSocket *ss);
+extern SECStatus ssl3_DisableECCSuites(sslSocket * ss,
+ const ssl3CipherSuite * suite);
+extern PRUint32 ssl3_GetSupportedECCurveMask(sslSocket *ss);
+
+
+/* Macro for finding a curve equivalent in strength to RSA key's */
+#define SSL_RSASTRENGTH_TO_ECSTRENGTH(s) \
+ ((s <= 1024) ? 160 \
+ : ((s <= 2048) ? 224 \
+ : ((s <= 3072) ? 256 \
+ : ((s <= 7168) ? 384 : 521 ) ) ) )
+
+/* Types and names of elliptic curves used in TLS */
+typedef enum { ec_type_explicitPrime = 1,
+ ec_type_explicitChar2Curve = 2,
+ ec_type_named
+} ECType;
+
+typedef enum { ec_noName = 0,
+ ec_sect163k1 = 1,
+ ec_sect163r1 = 2,
+ ec_sect163r2 = 3,
+ ec_sect193r1 = 4,
+ ec_sect193r2 = 5,
+ ec_sect233k1 = 6,
+ ec_sect233r1 = 7,
+ ec_sect239k1 = 8,
+ ec_sect283k1 = 9,
+ ec_sect283r1 = 10,
+ ec_sect409k1 = 11,
+ ec_sect409r1 = 12,
+ ec_sect571k1 = 13,
+ ec_sect571r1 = 14,
+ ec_secp160k1 = 15,
+ ec_secp160r1 = 16,
+ ec_secp160r2 = 17,
+ ec_secp192k1 = 18,
+ ec_secp192r1 = 19,
+ ec_secp224k1 = 20,
+ ec_secp224r1 = 21,
+ ec_secp256k1 = 22,
+ ec_secp256r1 = 23,
+ ec_secp384r1 = 24,
+ ec_secp521r1 = 25,
+ ec_pastLastName
+} ECName;
+
+extern SECStatus ssl3_ECName2Params(PLArenaPool *arena, ECName curve,
+ SECKEYECParams *params);
+ECName ssl3_GetCurveWithECKeyStrength(PRUint32 curvemsk, int requiredECCbits);
+
+
+#endif /* NSS_ENABLE_ECC */
+
+extern SECStatus ssl3_CipherPrefSetDefault(ssl3CipherSuite which, PRBool on);
+extern SECStatus ssl3_CipherPrefGetDefault(ssl3CipherSuite which, PRBool *on);
+extern SECStatus ssl2_CipherPrefSetDefault(PRInt32 which, PRBool enabled);
+extern SECStatus ssl2_CipherPrefGetDefault(PRInt32 which, PRBool *enabled);
+
+extern SECStatus ssl3_CipherPrefSet(sslSocket *ss, ssl3CipherSuite which, PRBool on);
+extern SECStatus ssl3_CipherPrefGet(sslSocket *ss, ssl3CipherSuite which, PRBool *on);
+extern SECStatus ssl2_CipherPrefSet(sslSocket *ss, PRInt32 which, PRBool enabled);
+extern SECStatus ssl2_CipherPrefGet(sslSocket *ss, PRInt32 which, PRBool *enabled);
+
+extern SECStatus ssl3_SetPolicy(ssl3CipherSuite which, PRInt32 policy);
+extern SECStatus ssl3_GetPolicy(ssl3CipherSuite which, PRInt32 *policy);
+extern SECStatus ssl2_SetPolicy(PRInt32 which, PRInt32 policy);
+extern SECStatus ssl2_GetPolicy(PRInt32 which, PRInt32 *policy);
+
+extern void ssl2_InitSocketPolicy(sslSocket *ss);
+extern void ssl3_InitSocketPolicy(sslSocket *ss);
+
+extern SECStatus ssl3_ConstructV2CipherSpecsHack(sslSocket *ss,
+ unsigned char *cs, int *size);
+
+extern SECStatus ssl3_RedoHandshake(sslSocket *ss, PRBool flushCache);
+extern SECStatus ssl3_HandleHandshakeMessage(sslSocket *ss, SSL3Opaque *b,
+ PRUint32 length);
+
+extern void ssl3_DestroySSL3Info(sslSocket *ss);
+
+extern SECStatus ssl3_NegotiateVersion(sslSocket *ss,
+ SSL3ProtocolVersion peerVersion,
+ PRBool allowLargerPeerVersion);
+
+extern SECStatus ssl_GetPeerInfo(sslSocket *ss);
+
+#ifdef NSS_ENABLE_ECC
+/* ECDH functions */
+extern SECStatus ssl3_SendECDHClientKeyExchange(sslSocket * ss,
+ SECKEYPublicKey * svrPubKey);
+extern SECStatus ssl3_HandleECDHServerKeyExchange(sslSocket *ss,
+ SSL3Opaque *b, PRUint32 length);
+extern SECStatus ssl3_HandleECDHClientKeyExchange(sslSocket *ss,
+ SSL3Opaque *b, PRUint32 length,
+ SECKEYPublicKey *srvrPubKey,
+ SECKEYPrivateKey *srvrPrivKey);
+extern SECStatus ssl3_SendECDHServerKeyExchange(sslSocket *ss,
+ const SSL3SignatureAndHashAlgorithm *sigAndHash);
+#endif
+
+extern SECStatus ssl3_ComputeCommonKeyHash(SECOidTag hashAlg,
+ PRUint8 * hashBuf,
+ unsigned int bufLen, SSL3Hashes *hashes,
+ PRBool bypassPKCS11);
+extern void ssl3_DestroyCipherSpec(ssl3CipherSpec *spec, PRBool freeSrvName);
+extern SECStatus ssl3_InitPendingCipherSpec(sslSocket *ss, PK11SymKey *pms);
+extern SECStatus ssl3_AppendHandshake(sslSocket *ss, const void *void_src,
+ PRInt32 bytes);
+extern SECStatus ssl3_AppendHandshakeHeader(sslSocket *ss,
+ SSL3HandshakeType t, PRUint32 length);
+extern SECStatus ssl3_AppendHandshakeNumber(sslSocket *ss, PRInt32 num,
+ PRInt32 lenSize);
+extern SECStatus ssl3_AppendHandshakeVariable( sslSocket *ss,
+ const SSL3Opaque *src, PRInt32 bytes, PRInt32 lenSize);
+extern SECStatus ssl3_AppendSignatureAndHashAlgorithm(sslSocket *ss,
+ const SSL3SignatureAndHashAlgorithm* sigAndHash);
+extern SECStatus ssl3_ConsumeHandshake(sslSocket *ss, void *v, PRInt32 bytes,
+ SSL3Opaque **b, PRUint32 *length);
+extern PRInt32 ssl3_ConsumeHandshakeNumber(sslSocket *ss, PRInt32 bytes,
+ SSL3Opaque **b, PRUint32 *length);
+extern SECStatus ssl3_ConsumeHandshakeVariable(sslSocket *ss, SECItem *i,
+ PRInt32 bytes, SSL3Opaque **b, PRUint32 *length);
+extern SECOidTag ssl3_TLSHashAlgorithmToOID(int hashFunc);
+extern SECStatus ssl3_CheckSignatureAndHashAlgorithmConsistency(
+ const SSL3SignatureAndHashAlgorithm *sigAndHash,
+ CERTCertificate* cert);
+extern SECStatus ssl3_ConsumeSignatureAndHashAlgorithm(sslSocket *ss,
+ SSL3Opaque **b, PRUint32 *length,
+ SSL3SignatureAndHashAlgorithm *out);
+extern SECStatus ssl3_SignHashes(SSL3Hashes *hash, SECKEYPrivateKey *key,
+ SECItem *buf, PRBool isTLS);
+extern SECStatus ssl3_VerifySignedHashes(SSL3Hashes *hash,
+ CERTCertificate *cert, SECItem *buf, PRBool isTLS,
+ void *pwArg);
+extern SECStatus ssl3_CacheWrappedMasterSecret(sslSocket *ss,
+ sslSessionID *sid, ssl3CipherSpec *spec,
+ SSL3KEAType effectiveExchKeyType);
+
+/* Functions that handle ClientHello and ServerHello extensions. */
+extern SECStatus ssl3_HandleServerNameXtn(sslSocket * ss,
+ PRUint16 ex_type, SECItem *data);
+extern SECStatus ssl3_HandleSupportedCurvesXtn(sslSocket * ss,
+ PRUint16 ex_type, SECItem *data);
+extern SECStatus ssl3_HandleSupportedPointFormatsXtn(sslSocket * ss,
+ PRUint16 ex_type, SECItem *data);
+extern SECStatus ssl3_ClientHandleSessionTicketXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+extern SECStatus ssl3_ServerHandleSessionTicketXtn(sslSocket *ss,
+ PRUint16 ex_type, SECItem *data);
+
+/* ClientHello and ServerHello extension senders.
+ * Note that not all extension senders are exposed here; only those that
+ * that need exposure.
+ */
+extern PRInt32 ssl3_SendSessionTicketXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+
+/* ClientHello and ServerHello extension senders.
+ * The code is in ssl3ext.c.
+ */
+extern PRInt32 ssl3_SendServerNameXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes);
+
+/* Assigns new cert, cert chain and keys to ss->serverCerts
+ * struct. If certChain is NULL, tries to find one. Aborts if
+ * fails to do so. If cert and keyPair are NULL - unconfigures
+ * sslSocket of kea type.*/
+extern SECStatus ssl_ConfigSecureServer(sslSocket *ss, CERTCertificate *cert,
+ const CERTCertificateList *certChain,
+ ssl3KeyPair *keyPair, SSLKEAType kea);
+
+#ifdef NSS_ENABLE_ECC
+extern PRInt32 ssl3_SendSupportedCurvesXtn(sslSocket *ss,
+ PRBool append, PRUint32 maxBytes);
+extern PRInt32 ssl3_SendSupportedPointFormatsXtn(sslSocket *ss,
+ PRBool append, PRUint32 maxBytes);
+#endif
+
+/* call the registered extension handlers. */
+extern SECStatus ssl3_HandleHelloExtensions(sslSocket *ss,
+ SSL3Opaque **b, PRUint32 *length);
+
+/* Hello Extension related routines. */
+extern PRBool ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type);
+extern SECStatus ssl3_SetSIDSessionTicket(sslSessionID *sid,
+ NewSessionTicket *session_ticket);
+extern SECStatus ssl3_SendNewSessionTicket(sslSocket *ss);
+extern PRBool ssl_GetSessionTicketKeys(unsigned char *keyName,
+ unsigned char *encKey, unsigned char *macKey);
+extern PRBool ssl_GetSessionTicketKeysPKCS11(SECKEYPrivateKey *svrPrivKey,
+ SECKEYPublicKey *svrPubKey, void *pwArg,
+ unsigned char *keyName, PK11SymKey **aesKey,
+ PK11SymKey **macKey);
+
+/* Tell clients to consider tickets valid for this long. */
+#define TLS_EX_SESS_TICKET_LIFETIME_HINT (2 * 24 * 60 * 60) /* 2 days */
+#define TLS_EX_SESS_TICKET_VERSION (0x0100)
+
+extern SECStatus ssl3_ValidateNextProtoNego(const unsigned char* data,
+ unsigned int length);
+
+extern SECStatus ssl3_GetTLSUniqueChannelBinding(sslSocket *ss,
+ unsigned char *out,
+ unsigned int *outLen,
+ unsigned int outLenMax);
+
+/* Construct a new NSPR socket for the app to use */
+extern PRFileDesc *ssl_NewPRSocket(sslSocket *ss, PRFileDesc *fd);
+extern void ssl_FreePRSocket(PRFileDesc *fd);
+
+/* Internal config function so SSL2 can initialize the present state of
+ * various ciphers */
+extern int ssl3_config_match_init(sslSocket *);
+
+/* Create a new ref counted key pair object from two keys. */
+extern ssl3KeyPair * ssl3_NewKeyPair( SECKEYPrivateKey * privKey,
+ SECKEYPublicKey * pubKey);
+
+/* get a new reference (bump ref count) to an ssl3KeyPair. */
+extern ssl3KeyPair * ssl3_GetKeyPairRef(ssl3KeyPair * keyPair);
+
+/* Decrement keypair's ref count and free if zero. */
+extern void ssl3_FreeKeyPair(ssl3KeyPair * keyPair);
+
+/* calls for accessing wrapping keys across processes. */
+extern PRBool
+ssl_GetWrappingKey( PRInt32 symWrapMechIndex,
+ SSL3KEAType exchKeyType,
+ SSLWrappedSymWrappingKey *wswk);
+
+/* The caller passes in the new value it wants
+ * to set. This code tests the wrapped sym key entry in the file on disk.
+ * If it is uninitialized, this function writes the caller's value into
+ * the disk entry, and returns false.
+ * Otherwise, it overwrites the caller's wswk with the value obtained from
+ * the disk, and returns PR_TRUE.
+ * This is all done while holding the locks/semaphores necessary to make
+ * the operation atomic.
+ */
+extern PRBool
+ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk);
+
+/* get rid of the symmetric wrapping key references. */
+extern SECStatus SSL3_ShutdownServerCache(void);
+
+extern SECStatus ssl_InitSymWrapKeysLock(void);
+
+extern SECStatus ssl_FreeSymWrapKeysLock(void);
+
+extern SECStatus ssl_InitSessionCacheLocks(PRBool lazyInit);
+
+extern SECStatus ssl_FreeSessionCacheLocks(void);
+
+/***************** platform client auth ****************/
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+// Releases the platform key.
+extern void ssl_FreePlatformKey(PlatformKey key);
+
+// Implement the client CertificateVerify message for SSL3/TLS1.0
+extern SECStatus ssl3_PlatformSignHashes(SSL3Hashes *hash,
+ PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType);
+
+// Converts a CERTCertList* (A collection of CERTCertificates) into a
+// CERTCertificateList* (A collection of SECItems), or returns NULL if
+// it cannot be converted.
+// This is to allow the platform-supplied chain to be created with purely
+// public API functions, using the preferred CERTCertList mutators, rather
+// pushing this hack to clients.
+extern CERTCertificateList* hack_NewCertificateListFromCertList(
+ CERTCertList* list);
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+
+/**************** DTLS-specific functions **************/
+extern void dtls_FreeQueuedMessage(DTLSQueuedMessage *msg);
+extern void dtls_FreeQueuedMessages(PRCList *lst);
+extern void dtls_FreeHandshakeMessages(PRCList *lst);
+
+extern SECStatus dtls_HandleHandshake(sslSocket *ss, sslBuffer *origBuf);
+extern SECStatus dtls_HandleHelloVerifyRequest(sslSocket *ss,
+ SSL3Opaque *b, PRUint32 length);
+extern SECStatus dtls_StageHandshakeMessage(sslSocket *ss);
+extern SECStatus dtls_QueueMessage(sslSocket *ss, SSL3ContentType type,
+ const SSL3Opaque *pIn, PRInt32 nIn);
+extern SECStatus dtls_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags);
+extern SECStatus dtls_CompressMACEncryptRecord(sslSocket *ss,
+ DTLSEpoch epoch,
+ PRBool use_epoch,
+ SSL3ContentType type,
+ const SSL3Opaque *pIn,
+ PRUint32 contentLen,
+ sslBuffer *wrBuf);
+SECStatus ssl3_DisableNonDTLSSuites(sslSocket * ss);
+extern SECStatus dtls_StartTimer(sslSocket *ss, DTLSTimerCb cb);
+extern SECStatus dtls_RestartTimer(sslSocket *ss, PRBool backoff,
+ DTLSTimerCb cb);
+extern void dtls_CheckTimer(sslSocket *ss);
+extern void dtls_CancelTimer(sslSocket *ss);
+extern void dtls_FinishedTimerCb(sslSocket *ss);
+extern void dtls_SetMTU(sslSocket *ss, PRUint16 advertised);
+extern void dtls_InitRecvdRecords(DTLSRecvdRecords *records);
+extern int dtls_RecordGetRecvd(DTLSRecvdRecords *records, PRUint64 seq);
+extern void dtls_RecordSetRecvd(DTLSRecvdRecords *records, PRUint64 seq);
+extern void dtls_RehandshakeCleanup(sslSocket *ss);
+extern SSL3ProtocolVersion
+dtls_TLSVersionToDTLSVersion(SSL3ProtocolVersion tlsv);
+extern SSL3ProtocolVersion
+dtls_DTLSVersionToTLSVersion(SSL3ProtocolVersion dtlsv);
+
+/********************** misc calls *********************/
+
+extern int ssl_MapLowLevelError(int hiLevelError);
+
+extern PRUint32 ssl_Time(void);
+
+extern void SSL_AtomicIncrementLong(long * x);
+
+SECStatus SSL_DisableDefaultExportCipherSuites(void);
+SECStatus SSL_DisableExportCipherSuites(PRFileDesc * fd);
+PRBool SSL_IsExportCipherSuite(PRUint16 cipherSuite);
+
+extern SECStatus
+ssl3_TLSPRFWithMasterSecret(ssl3CipherSpec *spec,
+ const char *label, unsigned int labelLen,
+ const unsigned char *val, unsigned int valLen,
+ unsigned char *out, unsigned int outLen);
+
+#ifdef TRACE
+#define SSL_TRACE(msg) ssl_Trace msg
+#else
+#define SSL_TRACE(msg)
+#endif
+
+void ssl_Trace(const char *format, ...);
+
+SEC_END_PROTOS
+
+#if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS)
+#define SSL_GETPID getpid
+#elif defined(WIN32)
+extern int __cdecl _getpid(void);
+#define SSL_GETPID _getpid
+#else
+#define SSL_GETPID() 0
+#endif
+
+#endif /* __sslimpl_h_ */
diff --git a/chromium/net/third_party/nss/ssl/sslinfo.c b/chromium/net/third_party/nss/ssl/sslinfo.c
new file mode 100644
index 00000000000..215731e4b02
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslinfo.c
@@ -0,0 +1,413 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+
+static const char *
+ssl_GetCompressionMethodName(SSLCompressionMethod compression)
+{
+ switch (compression) {
+ case ssl_compression_null:
+ return "NULL";
+#ifdef NSS_ENABLE_ZLIB
+ case ssl_compression_deflate:
+ return "DEFLATE";
+#endif
+ default:
+ return "???";
+ }
+}
+
+SECStatus
+SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info, PRUintn len)
+{
+ sslSocket * ss;
+ SSLChannelInfo inf;
+ sslSessionID * sid;
+ PRBool enoughFirstHsDone = PR_FALSE;
+
+ if (!info || len < sizeof inf.length) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetChannelInfo",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ memset(&inf, 0, sizeof inf);
+ inf.length = PR_MIN(sizeof inf, len);
+
+ if (ss->firstHsDone) {
+ enoughFirstHsDone = PR_TRUE;
+ } else if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ ssl3_CanFalseStart(ss)) {
+ enoughFirstHsDone = PR_TRUE;
+ }
+
+ if (ss->opt.useSecurity && enoughFirstHsDone) {
+ sid = ss->sec.ci.sid;
+ inf.protocolVersion = ss->version;
+ inf.authKeyBits = ss->sec.authKeyBits;
+ inf.keaKeyBits = ss->sec.keaKeyBits;
+ if (ss->version < SSL_LIBRARY_VERSION_3_0) { /* SSL2 */
+ inf.cipherSuite = ss->sec.cipherType | 0xff00;
+ inf.compressionMethod = ssl_compression_null;
+ inf.compressionMethodName = "N/A";
+ } else if (ss->ssl3.initialized) { /* SSL3 and TLS */
+ ssl_GetSpecReadLock(ss);
+ /* XXX The cipher suite should be in the specs and this
+ * function should get it from cwSpec rather than from the "hs".
+ * See bug 275744 comment 69 and bug 766137.
+ */
+ inf.cipherSuite = ss->ssl3.hs.cipher_suite;
+ inf.compressionMethod = ss->ssl3.cwSpec->compression_method;
+ ssl_ReleaseSpecReadLock(ss);
+ inf.compressionMethodName =
+ ssl_GetCompressionMethodName(inf.compressionMethod);
+ }
+ if (sid) {
+ inf.creationTime = sid->creationTime;
+ inf.lastAccessTime = sid->lastAccessTime;
+ inf.expirationTime = sid->expirationTime;
+ if (ss->version < SSL_LIBRARY_VERSION_3_0) { /* SSL2 */
+ inf.sessionIDLength = SSL2_SESSIONID_BYTES;
+ memcpy(inf.sessionID, sid->u.ssl2.sessionID,
+ SSL2_SESSIONID_BYTES);
+ } else {
+ unsigned int sidLen = sid->u.ssl3.sessionIDLength;
+ sidLen = PR_MIN(sidLen, sizeof inf.sessionID);
+ inf.sessionIDLength = sidLen;
+ memcpy(inf.sessionID, sid->u.ssl3.sessionID, sidLen);
+ }
+ }
+ }
+
+ memcpy(info, &inf, inf.length);
+
+ return SECSuccess;
+}
+
+
+#define CS(x) x, #x
+#define CK(x) x | 0xff00, #x
+
+#define S_DSA "DSA", ssl_auth_dsa
+#define S_RSA "RSA", ssl_auth_rsa
+#define S_KEA "KEA", ssl_auth_kea
+#define S_ECDSA "ECDSA", ssl_auth_ecdsa
+
+#define K_DHE "DHE", kt_dh
+#define K_RSA "RSA", kt_rsa
+#define K_KEA "KEA", kt_kea
+#define K_ECDH "ECDH", kt_ecdh
+#define K_ECDHE "ECDHE", kt_ecdh
+
+#define C_SEED "SEED", calg_seed
+#define C_CAMELLIA "CAMELLIA", calg_camellia
+#define C_AES "AES", calg_aes
+#define C_RC4 "RC4", calg_rc4
+#define C_RC2 "RC2", calg_rc2
+#define C_DES "DES", calg_des
+#define C_3DES "3DES", calg_3des
+#define C_NULL "NULL", calg_null
+#define C_SJ "SKIPJACK", calg_sj
+#define C_AESGCM "AES-GCM", calg_aes_gcm
+
+#define B_256 256, 256, 256
+#define B_128 128, 128, 128
+#define B_3DES 192, 156, 112
+#define B_SJ 96, 80, 80
+#define B_DES 64, 56, 56
+#define B_56 128, 56, 56
+#define B_40 128, 40, 40
+#define B_0 0, 0, 0
+
+#define M_SHA256 "SHA256", ssl_hmac_sha256, 256
+#define M_SHA "SHA1", ssl_mac_sha, 160
+#define M_MD5 "MD5", ssl_mac_md5, 128
+#define M_NULL "NULL", ssl_mac_null, 0
+
+static const SSLCipherSuiteInfo suiteInfo[] = {
+/* <------ Cipher suite --------------------> <auth> <KEA> <bulk cipher> <MAC> <FIPS> */
+{0,CS(TLS_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
+
+{0,CS(TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, 0, 0, 0, },
+{0,CS(TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, 0, 0, 0, },
+{0,CS(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_DHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_DHE, C_AES, B_256, M_SHA, 1, 0, 0, },
+{0,CS(TLS_DHE_DSS_WITH_AES_256_CBC_SHA), S_DSA, K_DHE, C_AES, B_256, M_SHA, 1, 0, 0, },
+{0,CS(TLS_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_256, M_SHA, 0, 0, 0, },
+{0,CS(TLS_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_RSA, C_AES, B_256, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_RSA, C_AES, B_256, M_SHA, 1, 0, 0, },
+
+{0,CS(TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
+{0,CS(TLS_DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED,B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_128, M_SHA, 0, 0, 0, },
+{0,CS(SSL_RSA_WITH_RC4_128_SHA), S_RSA, K_RSA, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(SSL_RSA_WITH_RC4_128_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, 0, 0, 0, },
+{0,CS(TLS_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_RSA, C_AES, B_128, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_RSA, C_AES, B_128, M_SHA, 1, 0, 0, },
+
+{0,CS(SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_DHE, C_3DES,B_3DES,M_SHA, 1, 0, 0, },
+{0,CS(SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA), S_DSA, K_DHE, C_3DES,B_3DES,M_SHA, 1, 0, 0, },
+{0,CS(SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES,B_3DES,M_SHA, 1, 0, 1, },
+{0,CS(SSL_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES,B_3DES,M_SHA, 1, 0, 0, },
+
+{0,CS(SSL_DHE_RSA_WITH_DES_CBC_SHA), S_RSA, K_DHE, C_DES, B_DES, M_SHA, 0, 0, 0, },
+{0,CS(SSL_DHE_DSS_WITH_DES_CBC_SHA), S_DSA, K_DHE, C_DES, B_DES, M_SHA, 0, 0, 0, },
+{0,CS(SSL_RSA_FIPS_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, 0, 0, 1, },
+{0,CS(SSL_RSA_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, 0, 0, 0, },
+
+{0,CS(TLS_RSA_EXPORT1024_WITH_RC4_56_SHA), S_RSA, K_RSA, C_RC4, B_56, M_SHA, 0, 1, 0, },
+{0,CS(TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, 0, 1, 0, },
+{0,CS(SSL_RSA_EXPORT_WITH_RC4_40_MD5), S_RSA, K_RSA, C_RC4, B_40, M_MD5, 0, 1, 0, },
+{0,CS(SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5), S_RSA, K_RSA, C_RC2, B_40, M_MD5, 0, 1, 0, },
+{0,CS(TLS_RSA_WITH_NULL_SHA256), S_RSA, K_RSA, C_NULL,B_0, M_SHA256, 0, 1, 0, },
+{0,CS(SSL_RSA_WITH_NULL_SHA), S_RSA, K_RSA, C_NULL,B_0, M_SHA, 0, 1, 0, },
+{0,CS(SSL_RSA_WITH_NULL_MD5), S_RSA, K_RSA, C_NULL,B_0, M_MD5, 0, 1, 0, },
+
+#ifdef NSS_ENABLE_ECC
+/* ECC cipher suites */
+{0,CS(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_NULL, 1, 0, 0, },
+
+{0,CS(TLS_ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_256, M_SHA, 1, 0, 0, },
+
+{0,CS(TLS_ECDHE_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDHE, C_NULL, B_0, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDHE, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDHE, C_3DES, B_3DES, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, 1, 0, 0, },
+
+{0,CS(TLS_ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, 1, 0, 0, },
+
+{0,CS(TLS_ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, 0, 0, 0, },
+{0,CS(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDHE, C_3DES, B_3DES, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_128, M_SHA, 1, 0, 0, },
+{0,CS(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_ECDHE, C_AES, B_128, M_SHA256, 1, 0, 0, },
+{0,CS(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_256, M_SHA, 1, 0, 0, },
+#endif /* NSS_ENABLE_ECC */
+
+/* SSL 2 table */
+{0,CK(SSL_CK_RC4_128_WITH_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, 0, 0, 0, },
+{0,CK(SSL_CK_RC2_128_CBC_WITH_MD5), S_RSA, K_RSA, C_RC2, B_128, M_MD5, 0, 0, 0, },
+{0,CK(SSL_CK_DES_192_EDE3_CBC_WITH_MD5), S_RSA, K_RSA, C_3DES,B_3DES,M_MD5, 0, 0, 0, },
+{0,CK(SSL_CK_DES_64_CBC_WITH_MD5), S_RSA, K_RSA, C_DES, B_DES, M_MD5, 0, 0, 0, },
+{0,CK(SSL_CK_RC4_128_EXPORT40_WITH_MD5), S_RSA, K_RSA, C_RC4, B_40, M_MD5, 0, 1, 0, },
+{0,CK(SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5), S_RSA, K_RSA, C_RC2, B_40, M_MD5, 0, 1, 0, }
+};
+
+#define NUM_SUITEINFOS ((sizeof suiteInfo) / (sizeof suiteInfo[0]))
+
+
+SECStatus SSL_GetCipherSuiteInfo(PRUint16 cipherSuite,
+ SSLCipherSuiteInfo *info, PRUintn len)
+{
+ unsigned int i;
+
+ len = PR_MIN(len, sizeof suiteInfo[0]);
+ if (!info || len < sizeof suiteInfo[0].length) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ for (i = 0; i < NUM_SUITEINFOS; i++) {
+ if (suiteInfo[i].cipherSuite == cipherSuite) {
+ memcpy(info, &suiteInfo[i], len);
+ info->length = len;
+ return SECSuccess;
+ }
+ }
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+}
+
+/* This function might be a candidate to be public.
+ * Disables all export ciphers in the default set of enabled ciphers.
+ */
+SECStatus
+SSL_DisableDefaultExportCipherSuites(void)
+{
+ const SSLCipherSuiteInfo * pInfo = suiteInfo;
+ unsigned int i;
+ SECStatus rv;
+
+ for (i = 0; i < NUM_SUITEINFOS; ++i, ++pInfo) {
+ if (pInfo->isExportable) {
+ rv = SSL_CipherPrefSetDefault(pInfo->cipherSuite, PR_FALSE);
+ PORT_Assert(rv == SECSuccess);
+ }
+ }
+ return SECSuccess;
+}
+
+/* This function might be a candidate to be public,
+ * except that it takes an sslSocket pointer as an argument.
+ * A Public version would take a PRFileDesc pointer.
+ * Disables all export ciphers in the default set of enabled ciphers.
+ */
+SECStatus
+SSL_DisableExportCipherSuites(PRFileDesc * fd)
+{
+ const SSLCipherSuiteInfo * pInfo = suiteInfo;
+ unsigned int i;
+ SECStatus rv;
+
+ for (i = 0; i < NUM_SUITEINFOS; ++i, ++pInfo) {
+ if (pInfo->isExportable) {
+ rv = SSL_CipherPrefSet(fd, pInfo->cipherSuite, PR_FALSE);
+ PORT_Assert(rv == SECSuccess);
+ }
+ }
+ return SECSuccess;
+}
+
+/* Tells us if the named suite is exportable
+ * returns false for unknown suites.
+ */
+PRBool
+SSL_IsExportCipherSuite(PRUint16 cipherSuite)
+{
+ unsigned int i;
+ for (i = 0; i < NUM_SUITEINFOS; i++) {
+ if (suiteInfo[i].cipherSuite == cipherSuite) {
+ return (PRBool)(suiteInfo[i].isExportable);
+ }
+ }
+ return PR_FALSE;
+}
+
+SECItem*
+SSL_GetNegotiatedHostInfo(PRFileDesc *fd)
+{
+ SECItem *sniName = NULL;
+ sslSocket *ss;
+ char *name = NULL;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNegotiatedHostInfo",
+ SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ if (ss->sec.isServer) {
+ if (ss->version > SSL_LIBRARY_VERSION_3_0 &&
+ ss->ssl3.initialized) { /* TLS */
+ SECItem *crsName;
+ ssl_GetSpecReadLock(ss); /*********************************/
+ crsName = &ss->ssl3.cwSpec->srvVirtName;
+ if (crsName->data) {
+ sniName = SECITEM_DupItem(crsName);
+ }
+ ssl_ReleaseSpecReadLock(ss); /*----------------------------*/
+ }
+ return sniName;
+ }
+ name = SSL_RevealURL(fd);
+ if (name) {
+ sniName = PORT_ZNew(SECItem);
+ if (!sniName) {
+ PORT_Free(name);
+ return NULL;
+ }
+ sniName->data = (void*)name;
+ sniName->len = PORT_Strlen(name);
+ }
+ return sniName;
+}
+
+SECStatus
+SSL_ExportKeyingMaterial(PRFileDesc *fd,
+ const char *label, unsigned int labelLen,
+ PRBool hasContext,
+ const unsigned char *context, unsigned int contextLen,
+ unsigned char *out, unsigned int outLen)
+{
+ sslSocket *ss;
+ unsigned char *val = NULL;
+ unsigned int valLen, i;
+ SECStatus rv = SECFailure;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in ExportKeyingMaterial",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ssl_GetRecvBufLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ if (ss->version < SSL_LIBRARY_VERSION_3_1_TLS) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+ }
+
+ /* construct PRF arguments */
+ valLen = SSL3_RANDOM_LENGTH * 2;
+ if (hasContext) {
+ valLen += 2 /* PRUint16 length */ + contextLen;
+ }
+ val = PORT_Alloc(valLen);
+ if (!val) {
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_ReleaseRecvBufLock(ss);
+ return SECFailure;
+ }
+ i = 0;
+
+ PORT_Memcpy(val + i, &ss->ssl3.hs.client_random.rand, SSL3_RANDOM_LENGTH);
+ i += SSL3_RANDOM_LENGTH;
+ PORT_Memcpy(val + i, &ss->ssl3.hs.server_random.rand, SSL3_RANDOM_LENGTH);
+ i += SSL3_RANDOM_LENGTH;
+
+ if (hasContext) {
+ val[i++] = contextLen >> 8;
+ val[i++] = contextLen;
+ PORT_Memcpy(val + i, context, contextLen);
+ i += contextLen;
+ }
+ PORT_Assert(i == valLen);
+
+ /* Allow TLS keying material to be exported sooner, when the master
+ * secret is available and we have sent ChangeCipherSpec.
+ */
+ ssl_GetSpecReadLock(ss);
+ if (!ss->ssl3.cwSpec->master_secret && !ss->ssl3.cwSpec->msItem.len) {
+ PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
+ rv = SECFailure;
+ } else {
+ rv = ssl3_TLSPRFWithMasterSecret(ss->ssl3.cwSpec, label, labelLen, val,
+ valLen, out, outLen);
+ }
+ ssl_ReleaseSpecReadLock(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_ReleaseRecvBufLock(ss);
+
+ PORT_ZFree(val, valLen);
+ return rv;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslinit.c b/chromium/net/third_party/nss/ssl/sslinit.c
new file mode 100644
index 00000000000..047cc8d1d94
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslinit.c
@@ -0,0 +1,28 @@
+/*
+ * NSS utility functions
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prtypes.h"
+#include "prinit.h"
+#include "seccomon.h"
+#include "secerr.h"
+#include "ssl.h"
+#include "sslimpl.h"
+
+static int ssl_inited = 0;
+
+SECStatus
+ssl_Init(void)
+{
+ if (!ssl_inited) {
+ if (ssl_InitializePRErrorTable() != SECSuccess) {
+ PORT_SetError(SEC_ERROR_NO_MEMORY);
+ return (SECFailure);
+ }
+ ssl_inited = 1;
+ }
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslmutex.c b/chromium/net/third_party/nss/ssl/sslmutex.c
new file mode 100644
index 00000000000..ff6368069df
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslmutex.c
@@ -0,0 +1,640 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "seccomon.h"
+/* This ifdef should match the one in sslsnce.c */
+#if defined(XP_UNIX) || defined(XP_WIN32) || defined (XP_OS2) || defined(XP_BEOS)
+
+#include "sslmutex.h"
+#include "prerr.h"
+
+static SECStatus single_process_sslMutex_Init(sslMutex* pMutex)
+{
+ PR_ASSERT(pMutex != 0 && pMutex->u.sslLock == 0 );
+
+ pMutex->u.sslLock = PR_NewLock();
+ if (!pMutex->u.sslLock) {
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+static SECStatus single_process_sslMutex_Destroy(sslMutex* pMutex)
+{
+ PR_ASSERT(pMutex != 0);
+ PR_ASSERT(pMutex->u.sslLock!= 0);
+ if (!pMutex->u.sslLock) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ PR_DestroyLock(pMutex->u.sslLock);
+ return SECSuccess;
+}
+
+static SECStatus single_process_sslMutex_Unlock(sslMutex* pMutex)
+{
+ PR_ASSERT(pMutex != 0 );
+ PR_ASSERT(pMutex->u.sslLock !=0);
+ if (!pMutex->u.sslLock) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ PR_Unlock(pMutex->u.sslLock);
+ return SECSuccess;
+}
+
+static SECStatus single_process_sslMutex_Lock(sslMutex* pMutex)
+{
+ PR_ASSERT(pMutex != 0);
+ PR_ASSERT(pMutex->u.sslLock != 0 );
+ if (!pMutex->u.sslLock) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ PR_Lock(pMutex->u.sslLock);
+ return SECSuccess;
+}
+
+#if defined(LINUX) || defined(AIX) || defined(BEOS) || defined(BSDI) || (defined(NETBSD) && __NetBSD_Version__ < 500000000) || defined(OPENBSD)
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include "unix_err.h"
+#include "pratom.h"
+
+#define SSL_MUTEX_MAGIC 0xfeedfd
+#define NONBLOCKING_POSTS 1 /* maybe this is faster */
+
+#if NONBLOCKING_POSTS
+
+#ifndef FNONBLOCK
+#define FNONBLOCK O_NONBLOCK
+#endif
+
+static int
+setNonBlocking(int fd, int nonBlocking)
+{
+ int flags;
+ int err;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (0 > flags)
+ return flags;
+ if (nonBlocking)
+ flags |= FNONBLOCK;
+ else
+ flags &= ~FNONBLOCK;
+ err = fcntl(fd, F_SETFL, flags);
+ return err;
+}
+#endif
+
+SECStatus
+sslMutex_Init(sslMutex *pMutex, int shared)
+{
+ int err;
+ PR_ASSERT(pMutex);
+ pMutex->isMultiProcess = (PRBool)(shared != 0);
+ if (!shared) {
+ return single_process_sslMutex_Init(pMutex);
+ }
+ pMutex->u.pipeStr.mPipes[0] = -1;
+ pMutex->u.pipeStr.mPipes[1] = -1;
+ pMutex->u.pipeStr.mPipes[2] = -1;
+ pMutex->u.pipeStr.nWaiters = 0;
+
+ err = pipe(pMutex->u.pipeStr.mPipes);
+ if (err) {
+ nss_MD_unix_map_default_error(errno);
+ return err;
+ }
+#if NONBLOCKING_POSTS
+ err = setNonBlocking(pMutex->u.pipeStr.mPipes[1], 1);
+ if (err)
+ goto loser;
+#endif
+
+ pMutex->u.pipeStr.mPipes[2] = SSL_MUTEX_MAGIC;
+
+#if defined(LINUX) && defined(i386)
+ /* Pipe starts out empty */
+ return SECSuccess;
+#else
+ /* Pipe starts with one byte. */
+ return sslMutex_Unlock(pMutex);
+#endif
+
+loser:
+ nss_MD_unix_map_default_error(errno);
+ close(pMutex->u.pipeStr.mPipes[0]);
+ close(pMutex->u.pipeStr.mPipes[1]);
+ return SECFailure;
+}
+
+SECStatus
+sslMutex_Destroy(sslMutex *pMutex, PRBool processLocal)
+{
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Destroy(pMutex);
+ }
+ if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ close(pMutex->u.pipeStr.mPipes[0]);
+ close(pMutex->u.pipeStr.mPipes[1]);
+
+ if (processLocal) {
+ return SECSuccess;
+ }
+
+ pMutex->u.pipeStr.mPipes[0] = -1;
+ pMutex->u.pipeStr.mPipes[1] = -1;
+ pMutex->u.pipeStr.mPipes[2] = -1;
+ pMutex->u.pipeStr.nWaiters = 0;
+
+ return SECSuccess;
+}
+
+#if defined(LINUX) && defined(i386)
+/* No memory barrier needed for this platform */
+
+/* nWaiters includes the holder of the lock (if any) and the number
+** threads waiting for it. After incrementing nWaiters, if the count
+** is exactly 1, then you have the lock and may proceed. If the
+** count is greater than 1, then you must wait on the pipe.
+*/
+
+
+SECStatus
+sslMutex_Unlock(sslMutex *pMutex)
+{
+ PRInt32 newValue;
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Unlock(pMutex);
+ }
+
+ if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ /* Do Memory Barrier here. */
+ newValue = PR_ATOMIC_DECREMENT(&pMutex->u.pipeStr.nWaiters);
+ if (newValue > 0) {
+ int cc;
+ char c = 1;
+ do {
+ cc = write(pMutex->u.pipeStr.mPipes[1], &c, 1);
+ } while (cc < 0 && (errno == EINTR || errno == EAGAIN));
+ if (cc != 1) {
+ if (cc < 0)
+ nss_MD_unix_map_default_error(errno);
+ else
+ PORT_SetError(PR_UNKNOWN_ERROR);
+ return SECFailure;
+ }
+ }
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Lock(sslMutex *pMutex)
+{
+ PRInt32 newValue;
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Lock(pMutex);
+ }
+
+ if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ newValue = PR_ATOMIC_INCREMENT(&pMutex->u.pipeStr.nWaiters);
+ /* Do Memory Barrier here. */
+ if (newValue > 1) {
+ int cc;
+ char c;
+ do {
+ cc = read(pMutex->u.pipeStr.mPipes[0], &c, 1);
+ } while (cc < 0 && errno == EINTR);
+ if (cc != 1) {
+ if (cc < 0)
+ nss_MD_unix_map_default_error(errno);
+ else
+ PORT_SetError(PR_UNKNOWN_ERROR);
+ return SECFailure;
+ }
+ }
+ return SECSuccess;
+}
+
+#else
+
+/* Using Atomic operations requires the use of a memory barrier instruction
+** on PowerPC, Sparc, and Alpha. NSPR's PR_Atomic functions do not perform
+** them, and NSPR does not provide a function that does them (e.g. PR_Barrier).
+** So, we don't use them on those platforms.
+*/
+
+SECStatus
+sslMutex_Unlock(sslMutex *pMutex)
+{
+ int cc;
+ char c = 1;
+
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Unlock(pMutex);
+ }
+
+ if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ do {
+ cc = write(pMutex->u.pipeStr.mPipes[1], &c, 1);
+ } while (cc < 0 && (errno == EINTR || errno == EAGAIN));
+ if (cc != 1) {
+ if (cc < 0)
+ nss_MD_unix_map_default_error(errno);
+ else
+ PORT_SetError(PR_UNKNOWN_ERROR);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Lock(sslMutex *pMutex)
+{
+ int cc;
+ char c;
+
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Lock(pMutex);
+ }
+
+ if (pMutex->u.pipeStr.mPipes[2] != SSL_MUTEX_MAGIC) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+
+ do {
+ cc = read(pMutex->u.pipeStr.mPipes[0], &c, 1);
+ } while (cc < 0 && errno == EINTR);
+ if (cc != 1) {
+ if (cc < 0)
+ nss_MD_unix_map_default_error(errno);
+ else
+ PORT_SetError(PR_UNKNOWN_ERROR);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+#endif
+
+#elif defined(WIN32)
+
+#include "win32err.h"
+
+/* on Windows, we need to find the optimal type of locking mechanism to use
+ for the sslMutex.
+
+ There are 3 cases :
+ 1) single-process, use a PRLock, as for all other platforms
+ 2) Win95 multi-process, use a Win32 mutex
+ 3) on WINNT multi-process, use a PRLock + a Win32 mutex
+
+*/
+
+#ifdef WINNT
+
+SECStatus sslMutex_2LevelInit(sslMutex *sem)
+{
+ /* the following adds a PRLock to sslMutex . This is done in each
+ process of a multi-process server and is only needed on WINNT, if
+ using fibers. We can't tell if native threads or fibers are used, so
+ we always do it on WINNT
+ */
+ PR_ASSERT(sem);
+ if (sem) {
+ /* we need to reset the sslLock in the children or the single_process init
+ function below will assert */
+ sem->u.sslLock = NULL;
+ }
+ return single_process_sslMutex_Init(sem);
+}
+
+static SECStatus sslMutex_2LevelDestroy(sslMutex *sem)
+{
+ return single_process_sslMutex_Destroy(sem);
+}
+
+#endif
+
+SECStatus
+sslMutex_Init(sslMutex *pMutex, int shared)
+{
+#ifdef WINNT
+ SECStatus retvalue;
+#endif
+ HANDLE hMutex;
+ SECURITY_ATTRIBUTES attributes =
+ { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
+
+ PR_ASSERT(pMutex != 0 && (pMutex->u.sslMutx == 0 ||
+ pMutex->u.sslMutx == INVALID_HANDLE_VALUE) );
+
+ pMutex->isMultiProcess = (PRBool)(shared != 0);
+
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Init(pMutex);
+ }
+
+#ifdef WINNT
+ /* we need a lock on WINNT for fibers in the parent process */
+ retvalue = sslMutex_2LevelInit(pMutex);
+ if (SECSuccess != retvalue)
+ return SECFailure;
+#endif
+
+ if (!pMutex || ((hMutex = pMutex->u.sslMutx) != 0 &&
+ hMutex != INVALID_HANDLE_VALUE)) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ attributes.bInheritHandle = (shared ? TRUE : FALSE);
+ hMutex = CreateMutex(&attributes, FALSE, NULL);
+ if (hMutex == NULL) {
+ hMutex = INVALID_HANDLE_VALUE;
+ nss_MD_win32_map_default_error(GetLastError());
+ return SECFailure;
+ }
+ pMutex->u.sslMutx = hMutex;
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Destroy(sslMutex *pMutex, PRBool processLocal)
+{
+ HANDLE hMutex;
+ int rv;
+ int retvalue = SECSuccess;
+
+ PR_ASSERT(pMutex != 0);
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Destroy(pMutex);
+ }
+
+ /* multi-process mode */
+#ifdef WINNT
+ /* on NT, get rid of the PRLock used for fibers within a process */
+ retvalue = sslMutex_2LevelDestroy(pMutex);
+#endif
+
+ PR_ASSERT( pMutex->u.sslMutx != 0 &&
+ pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
+ if (!pMutex || (hMutex = pMutex->u.sslMutx) == 0
+ || hMutex == INVALID_HANDLE_VALUE) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+
+ rv = CloseHandle(hMutex); /* ignore error */
+ if (!processLocal && rv) {
+ pMutex->u.sslMutx = hMutex = INVALID_HANDLE_VALUE;
+ }
+ if (!rv) {
+ nss_MD_win32_map_default_error(GetLastError());
+ retvalue = SECFailure;
+ }
+ return retvalue;
+}
+
+int
+sslMutex_Unlock(sslMutex *pMutex)
+{
+ BOOL success = FALSE;
+ HANDLE hMutex;
+
+ PR_ASSERT(pMutex != 0 );
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Unlock(pMutex);
+ }
+
+ PR_ASSERT(pMutex->u.sslMutx != 0 &&
+ pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
+ if (!pMutex || (hMutex = pMutex->u.sslMutx) == 0 ||
+ hMutex == INVALID_HANDLE_VALUE) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+ success = ReleaseMutex(hMutex);
+ if (!success) {
+ nss_MD_win32_map_default_error(GetLastError());
+ return SECFailure;
+ }
+#ifdef WINNT
+ return single_process_sslMutex_Unlock(pMutex);
+ /* release PRLock for other fibers in the process */
+#else
+ return SECSuccess;
+#endif
+}
+
+int
+sslMutex_Lock(sslMutex *pMutex)
+{
+ HANDLE hMutex;
+ DWORD event;
+ DWORD lastError;
+ SECStatus rv;
+ SECStatus retvalue = SECSuccess;
+ PR_ASSERT(pMutex != 0);
+
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Lock(pMutex);
+ }
+#ifdef WINNT
+ /* lock first to preserve from other threads/fibers
+ in the same process */
+ retvalue = single_process_sslMutex_Lock(pMutex);
+#endif
+ PR_ASSERT(pMutex->u.sslMutx != 0 &&
+ pMutex->u.sslMutx != INVALID_HANDLE_VALUE);
+ if (!pMutex || (hMutex = pMutex->u.sslMutx) == 0 ||
+ hMutex == INVALID_HANDLE_VALUE) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure; /* what else ? */
+ }
+ /* acquire the mutex to be the only owner accross all other processes */
+ event = WaitForSingleObject(hMutex, INFINITE);
+ switch (event) {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED:
+ rv = SECSuccess;
+ break;
+
+ case WAIT_TIMEOUT:
+#if defined(WAIT_IO_COMPLETION)
+ case WAIT_IO_COMPLETION:
+#endif
+ default: /* should never happen. nothing we can do. */
+ PR_ASSERT(!("WaitForSingleObject returned invalid value."));
+ PORT_SetError(PR_UNKNOWN_ERROR);
+ rv = SECFailure;
+ break;
+
+ case WAIT_FAILED: /* failure returns this */
+ rv = SECFailure;
+ lastError = GetLastError(); /* for debugging */
+ nss_MD_win32_map_default_error(lastError);
+ break;
+ }
+
+ if (! (SECSuccess == retvalue && SECSuccess == rv)) {
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+#elif defined(XP_UNIX)
+
+#include <errno.h>
+#include "unix_err.h"
+
+SECStatus
+sslMutex_Init(sslMutex *pMutex, int shared)
+{
+ int rv;
+ PR_ASSERT(pMutex);
+ pMutex->isMultiProcess = (PRBool)(shared != 0);
+ if (!shared) {
+ return single_process_sslMutex_Init(pMutex);
+ }
+ do {
+ rv = sem_init(&pMutex->u.sem, shared, 1);
+ } while (rv < 0 && errno == EINTR);
+ if (rv < 0) {
+ nss_MD_unix_map_default_error(errno);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Destroy(sslMutex *pMutex, PRBool processLocal)
+{
+ int rv;
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Destroy(pMutex);
+ }
+
+ /* semaphores are global resources. See SEM_DESTROY(3) man page */
+ if (processLocal) {
+ return SECSuccess;
+ }
+ do {
+ rv = sem_destroy(&pMutex->u.sem);
+ } while (rv < 0 && errno == EINTR);
+ if (rv < 0) {
+ nss_MD_unix_map_default_error(errno);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Unlock(sslMutex *pMutex)
+{
+ int rv;
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Unlock(pMutex);
+ }
+ do {
+ rv = sem_post(&pMutex->u.sem);
+ } while (rv < 0 && errno == EINTR);
+ if (rv < 0) {
+ nss_MD_unix_map_default_error(errno);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+SECStatus
+sslMutex_Lock(sslMutex *pMutex)
+{
+ int rv;
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Lock(pMutex);
+ }
+ do {
+ rv = sem_wait(&pMutex->u.sem);
+ } while (rv < 0 && errno == EINTR);
+ if (rv < 0) {
+ nss_MD_unix_map_default_error(errno);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+#else
+
+SECStatus
+sslMutex_Init(sslMutex *pMutex, int shared)
+{
+ PR_ASSERT(pMutex);
+ pMutex->isMultiProcess = (PRBool)(shared != 0);
+ if (!shared) {
+ return single_process_sslMutex_Init(pMutex);
+ }
+ PORT_Assert(!("sslMutex_Init not implemented for multi-process applications !"));
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+}
+
+SECStatus
+sslMutex_Destroy(sslMutex *pMutex, PRBool processLocal)
+{
+ PR_ASSERT(pMutex);
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Destroy(pMutex);
+ }
+ PORT_Assert(!("sslMutex_Destroy not implemented for multi-process applications !"));
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+}
+
+SECStatus
+sslMutex_Unlock(sslMutex *pMutex)
+{
+ PR_ASSERT(pMutex);
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Unlock(pMutex);
+ }
+ PORT_Assert(!("sslMutex_Unlock not implemented for multi-process applications !"));
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+}
+
+SECStatus
+sslMutex_Lock(sslMutex *pMutex)
+{
+ PR_ASSERT(pMutex);
+ if (PR_FALSE == pMutex->isMultiProcess) {
+ return single_process_sslMutex_Lock(pMutex);
+ }
+ PORT_Assert(!("sslMutex_Lock not implemented for multi-process applications !"));
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+}
+
+#endif
+
+#endif
diff --git a/chromium/net/third_party/nss/ssl/sslmutex.h b/chromium/net/third_party/nss/ssl/sslmutex.h
new file mode 100644
index 00000000000..b784baf665b
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslmutex.h
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef __SSLMUTEX_H_
+#define __SSLMUTEX_H_ 1
+
+/* What SSL really wants is portable process-shared unnamed mutexes in
+ * shared memory, that have the property that if the process that holds
+ * them dies, they are released automatically, and that (unlike fcntl
+ * record locking) lock to the thread, not to the process.
+ * NSPR doesn't provide that.
+ * Windows has mutexes that meet that description, but they're not portable.
+ * POSIX mutexes are not automatically released when the holder dies,
+ * and other processes/threads cannot release the mutex on behalf of the
+ * dead holder.
+ * POSIX semaphores can be used to accomplish this on systems that implement
+ * process-shared unnamed POSIX semaphores, because a watchdog thread can
+ * discover and release semaphores that were held by a dead process.
+ * On systems that do not support process-shared POSIX unnamed semaphores,
+ * they can be emulated using pipes.
+ * The performance cost of doing that is not yet measured.
+ *
+ * So, this API looks a lot like POSIX pthread mutexes.
+ */
+
+#include "prtypes.h"
+#include "prlock.h"
+
+#if defined(NETBSD)
+#include <sys/param.h> /* for __NetBSD_Version__ */
+#endif
+
+#if defined(WIN32)
+
+#include <wtypes.h>
+
+typedef struct
+{
+ PRBool isMultiProcess;
+#ifdef WINNT
+ /* on WINNT we need both the PRLock and the Win32 mutex for fibers */
+ struct {
+#else
+ union {
+#endif
+ PRLock* sslLock;
+ HANDLE sslMutx;
+ } u;
+} sslMutex;
+
+typedef int sslPID;
+
+#elif defined(LINUX) || defined(AIX) || defined(BEOS) || defined(BSDI) || (defined(NETBSD) && __NetBSD_Version__ < 500000000) || defined(OPENBSD)
+
+#include <sys/types.h>
+#include "prtypes.h"
+
+typedef struct {
+ PRBool isMultiProcess;
+ union {
+ PRLock* sslLock;
+ struct {
+ int mPipes[3];
+ PRInt32 nWaiters;
+ } pipeStr;
+ } u;
+} sslMutex;
+typedef pid_t sslPID;
+
+#elif defined(XP_UNIX) /* other types of Unix */
+
+#include <sys/types.h> /* for pid_t */
+#include <semaphore.h> /* for sem_t, and sem_* functions */
+
+typedef struct
+{
+ PRBool isMultiProcess;
+ union {
+ PRLock* sslLock;
+ sem_t sem;
+ } u;
+} sslMutex;
+
+typedef pid_t sslPID;
+
+#else
+
+/* what platform is this ?? */
+
+typedef struct {
+ PRBool isMultiProcess;
+ union {
+ PRLock* sslLock;
+ /* include cross-process locking mechanism here */
+ } u;
+} sslMutex;
+
+typedef int sslPID;
+
+#endif
+
+#include "seccomon.h"
+
+SEC_BEGIN_PROTOS
+
+extern SECStatus sslMutex_Init(sslMutex *sem, int shared);
+
+/* If processLocal is set to true, then just free resources which are *only* associated
+ * with the current process. Leave any shared resources (including the state of
+ * shared memory) intact. */
+extern SECStatus sslMutex_Destroy(sslMutex *sem, PRBool processLocal);
+
+extern SECStatus sslMutex_Unlock(sslMutex *sem);
+
+extern SECStatus sslMutex_Lock(sslMutex *sem);
+
+#ifdef WINNT
+
+extern SECStatus sslMutex_2LevelInit(sslMutex *sem);
+
+#endif
+
+SEC_END_PROTOS
+
+#endif
diff --git a/chromium/net/third_party/nss/ssl/sslnonce.c b/chromium/net/third_party/nss/ssl/sslnonce.c
new file mode 100644
index 00000000000..5d8a95407aa
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslnonce.c
@@ -0,0 +1,508 @@
+/*
+ * This file implements the CLIENT Session ID cache.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cert.h"
+#include "pk11pub.h"
+#include "secitem.h"
+#include "ssl.h"
+#include "nss.h"
+
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "nssilock.h"
+#if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
+#include <time.h>
+#endif
+
+PRUint32 ssl_sid_timeout = 100;
+PRUint32 ssl3_sid_timeout = 86400L; /* 24 hours */
+
+static sslSessionID *cache = NULL;
+static PZLock * cacheLock = NULL;
+
+/* sids can be in one of 4 states:
+ *
+ * never_cached, created, but not yet put into cache.
+ * in_client_cache, in the client cache's linked list.
+ * in_server_cache, entry came from the server's cache file.
+ * invalid_cache has been removed from the cache.
+ */
+
+#define LOCK_CACHE lock_cache()
+#define UNLOCK_CACHE PZ_Unlock(cacheLock)
+
+static SECStatus
+ssl_InitClientSessionCacheLock(void)
+{
+ cacheLock = PZ_NewLock(nssILockCache);
+ return cacheLock ? SECSuccess : SECFailure;
+}
+
+static SECStatus
+ssl_FreeClientSessionCacheLock(void)
+{
+ if (cacheLock) {
+ PZ_DestroyLock(cacheLock);
+ cacheLock = NULL;
+ return SECSuccess;
+ }
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+}
+
+static PRBool LocksInitializedEarly = PR_FALSE;
+
+static SECStatus
+FreeSessionCacheLocks()
+{
+ SECStatus rv1, rv2;
+ rv1 = ssl_FreeSymWrapKeysLock();
+ rv2 = ssl_FreeClientSessionCacheLock();
+ if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) {
+ return SECSuccess;
+ }
+ return SECFailure;
+}
+
+static SECStatus
+InitSessionCacheLocks(void)
+{
+ SECStatus rv1, rv2;
+ PRErrorCode rc;
+ rv1 = ssl_InitSymWrapKeysLock();
+ rv2 = ssl_InitClientSessionCacheLock();
+ if ( (SECSuccess == rv1) && (SECSuccess == rv2) ) {
+ return SECSuccess;
+ }
+ rc = PORT_GetError();
+ FreeSessionCacheLocks();
+ PORT_SetError(rc);
+ return SECFailure;
+}
+
+/* free the session cache locks if they were initialized early */
+SECStatus
+ssl_FreeSessionCacheLocks()
+{
+ PORT_Assert(PR_TRUE == LocksInitializedEarly);
+ if (!LocksInitializedEarly) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return SECFailure;
+ }
+ FreeSessionCacheLocks();
+ LocksInitializedEarly = PR_FALSE;
+ return SECSuccess;
+}
+
+static PRCallOnceType lockOnce;
+
+/* free the session cache locks if they were initialized lazily */
+static SECStatus ssl_ShutdownLocks(void* appData, void* nssData)
+{
+ PORT_Assert(PR_FALSE == LocksInitializedEarly);
+ if (LocksInitializedEarly) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ return SECFailure;
+ }
+ FreeSessionCacheLocks();
+ memset(&lockOnce, 0, sizeof(lockOnce));
+ return SECSuccess;
+}
+
+static PRStatus initSessionCacheLocksLazily(void)
+{
+ SECStatus rv = InitSessionCacheLocks();
+ if (SECSuccess != rv) {
+ return PR_FAILURE;
+ }
+ rv = NSS_RegisterShutdown(ssl_ShutdownLocks, NULL);
+ PORT_Assert(SECSuccess == rv);
+ if (SECSuccess != rv) {
+ return PR_FAILURE;
+ }
+ return PR_SUCCESS;
+}
+
+/* lazyInit means that the call is not happening during a 1-time
+ * initialization function, but rather during dynamic, lazy initialization
+ */
+SECStatus
+ssl_InitSessionCacheLocks(PRBool lazyInit)
+{
+ if (LocksInitializedEarly) {
+ return SECSuccess;
+ }
+
+ if (lazyInit) {
+ return (PR_SUCCESS ==
+ PR_CallOnce(&lockOnce, initSessionCacheLocksLazily)) ?
+ SECSuccess : SECFailure;
+ }
+
+ if (SECSuccess == InitSessionCacheLocks()) {
+ LocksInitializedEarly = PR_TRUE;
+ return SECSuccess;
+ }
+
+ return SECFailure;
+}
+
+static void
+lock_cache(void)
+{
+ ssl_InitSessionCacheLocks(PR_TRUE);
+ PZ_Lock(cacheLock);
+}
+
+/* BEWARE: This function gets called for both client and server SIDs !!
+ * If the unreferenced sid is not in the cache, Free sid and its contents.
+ */
+static void
+ssl_DestroySID(sslSessionID *sid)
+{
+ int i;
+ SSL_TRC(8, ("SSL: destroy sid: sid=0x%x cached=%d", sid, sid->cached));
+ PORT_Assert((sid->references == 0));
+
+ if (sid->cached == in_client_cache)
+ return; /* it will get taken care of next time cache is traversed. */
+
+ if (sid->version < SSL_LIBRARY_VERSION_3_0) {
+ SECITEM_ZfreeItem(&sid->u.ssl2.masterKey, PR_FALSE);
+ SECITEM_ZfreeItem(&sid->u.ssl2.cipherArg, PR_FALSE);
+ }
+ if (sid->peerID != NULL)
+ PORT_Free((void *)sid->peerID); /* CONST */
+
+ if (sid->urlSvrName != NULL)
+ PORT_Free((void *)sid->urlSvrName); /* CONST */
+
+ if ( sid->peerCert ) {
+ CERT_DestroyCertificate(sid->peerCert);
+ }
+ for (i = 0; i < MAX_PEER_CERT_CHAIN_SIZE && sid->peerCertChain[i]; i++) {
+ CERT_DestroyCertificate(sid->peerCertChain[i]);
+ }
+ if (sid->peerCertStatus.items) {
+ SECITEM_FreeArray(&sid->peerCertStatus, PR_FALSE);
+ }
+
+ if ( sid->localCert ) {
+ CERT_DestroyCertificate(sid->localCert);
+ }
+ if (sid->u.ssl3.sessionTicket.ticket.data) {
+ SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE);
+ }
+ if (sid->u.ssl3.srvName.data) {
+ SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
+ }
+
+ PORT_ZFree(sid, sizeof(sslSessionID));
+}
+
+/* BEWARE: This function gets called for both client and server SIDs !!
+ * Decrement reference count, and
+ * free sid if ref count is zero, and sid is not in the cache.
+ * Does NOT remove from the cache first.
+ * If the sid is still in the cache, it is left there until next time
+ * the cache list is traversed.
+ */
+static void
+ssl_FreeLockedSID(sslSessionID *sid)
+{
+ PORT_Assert(sid->references >= 1);
+ if (--sid->references == 0) {
+ ssl_DestroySID(sid);
+ }
+}
+
+/* BEWARE: This function gets called for both client and server SIDs !!
+ * Decrement reference count, and
+ * free sid if ref count is zero, and sid is not in the cache.
+ * Does NOT remove from the cache first.
+ * These locks are necessary because the sid _might_ be in the cache list.
+ */
+void
+ssl_FreeSID(sslSessionID *sid)
+{
+ LOCK_CACHE;
+ ssl_FreeLockedSID(sid);
+ UNLOCK_CACHE;
+}
+
+/************************************************************************/
+
+/*
+** Lookup sid entry in cache by Address, port, and peerID string.
+** If found, Increment reference count, and return pointer to caller.
+** If it has timed out or ref count is zero, remove from list and free it.
+*/
+
+sslSessionID *
+ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
+ const char * urlSvrName)
+{
+ sslSessionID **sidp;
+ sslSessionID * sid;
+ PRUint32 now;
+
+ if (!urlSvrName)
+ return NULL;
+ now = ssl_Time();
+ LOCK_CACHE;
+ sidp = &cache;
+ while ((sid = *sidp) != 0) {
+ PORT_Assert(sid->cached == in_client_cache);
+ PORT_Assert(sid->references >= 1);
+
+ SSL_TRC(8, ("SSL: Lookup1: sid=0x%x", sid));
+
+ if (sid->expirationTime < now || !sid->references) {
+ /*
+ ** This session-id timed out, or was orphaned.
+ ** Don't even care who it belongs to, blow it out of our cache.
+ */
+ SSL_TRC(7, ("SSL: lookup1, throwing sid out, age=%d refs=%d",
+ now - sid->creationTime, sid->references));
+
+ *sidp = sid->next; /* delink it from the list. */
+ sid->cached = invalid_cache; /* mark not on list. */
+ if (!sid->references)
+ ssl_DestroySID(sid);
+ else
+ ssl_FreeLockedSID(sid); /* drop ref count, free. */
+
+ } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
+ (sid->port == port) && /* server port matches */
+ /* proxy (peerID) matches */
+ (((peerID == NULL) && (sid->peerID == NULL)) ||
+ ((peerID != NULL) && (sid->peerID != NULL) &&
+ PORT_Strcmp(sid->peerID, peerID) == 0)) &&
+ /* is cacheable */
+ (sid->version < SSL_LIBRARY_VERSION_3_0 ||
+ sid->u.ssl3.keys.resumable) &&
+ /* server hostname matches. */
+ (sid->urlSvrName != NULL) &&
+ ((0 == PORT_Strcmp(urlSvrName, sid->urlSvrName)) ||
+ ((sid->peerCert != NULL) && (SECSuccess ==
+ CERT_VerifyCertName(sid->peerCert, urlSvrName))) )
+ ) {
+ /* Hit */
+ sid->lastAccessTime = now;
+ sid->references++;
+ break;
+ } else {
+ sidp = &sid->next;
+ }
+ }
+ UNLOCK_CACHE;
+ return sid;
+}
+
+/*
+** Add an sid to the cache or return a previously cached entry to the cache.
+** Although this is static, it is called via ss->sec.cache().
+*/
+static void
+CacheSID(sslSessionID *sid)
+{
+ PRUint32 expirationPeriod;
+ SSL_TRC(8, ("SSL: Cache: sid=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
+ "time=%x cached=%d",
+ sid, sid->cached, sid->addr.pr_s6_addr32[0],
+ sid->addr.pr_s6_addr32[1], sid->addr.pr_s6_addr32[2],
+ sid->addr.pr_s6_addr32[3], sid->port, sid->creationTime,
+ sid->cached));
+
+ if (sid->cached == in_client_cache)
+ return;
+
+ if (!sid->urlSvrName) {
+ /* don't cache this SID because it can never be matched */
+ return;
+ }
+
+ /* XXX should be different trace for version 2 vs. version 3 */
+ if (sid->version < SSL_LIBRARY_VERSION_3_0) {
+ expirationPeriod = ssl_sid_timeout;
+ PRINT_BUF(8, (0, "sessionID:",
+ sid->u.ssl2.sessionID, sizeof(sid->u.ssl2.sessionID)));
+ PRINT_BUF(8, (0, "masterKey:",
+ sid->u.ssl2.masterKey.data, sid->u.ssl2.masterKey.len));
+ PRINT_BUF(8, (0, "cipherArg:",
+ sid->u.ssl2.cipherArg.data, sid->u.ssl2.cipherArg.len));
+ } else {
+ if (sid->u.ssl3.sessionIDLength == 0 &&
+ sid->u.ssl3.sessionTicket.ticket.data == NULL)
+ return;
+ /* Client generates the SessionID if this was a stateless resume. */
+ if (sid->u.ssl3.sessionIDLength == 0) {
+ SECStatus rv;
+ rv = PK11_GenerateRandom(sid->u.ssl3.sessionID,
+ SSL3_SESSIONID_BYTES);
+ if (rv != SECSuccess)
+ return;
+ sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
+ }
+ expirationPeriod = ssl3_sid_timeout;
+ PRINT_BUF(8, (0, "sessionID:",
+ sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength));
+ }
+ PORT_Assert(sid->creationTime != 0 && sid->expirationTime != 0);
+ if (!sid->creationTime)
+ sid->lastAccessTime = sid->creationTime = ssl_Time();
+ if (!sid->expirationTime)
+ sid->expirationTime = sid->creationTime + expirationPeriod;
+
+ /*
+ * Put sid into the cache. Bump reference count to indicate that
+ * cache is holding a reference. Uncache will reduce the cache
+ * reference.
+ */
+ LOCK_CACHE;
+ sid->references++;
+ sid->cached = in_client_cache;
+ sid->next = cache;
+ cache = sid;
+ UNLOCK_CACHE;
+}
+
+/*
+ * If sid "zap" is in the cache,
+ * removes sid from cache, and decrements reference count.
+ * Caller must hold cache lock.
+ */
+static void
+UncacheSID(sslSessionID *zap)
+{
+ sslSessionID **sidp = &cache;
+ sslSessionID *sid;
+
+ if (zap->cached != in_client_cache) {
+ return;
+ }
+
+ SSL_TRC(8,("SSL: Uncache: zap=0x%x cached=%d addr=0x%08x%08x%08x%08x port=0x%04x "
+ "time=%x cipher=%d",
+ zap, zap->cached, zap->addr.pr_s6_addr32[0],
+ zap->addr.pr_s6_addr32[1], zap->addr.pr_s6_addr32[2],
+ zap->addr.pr_s6_addr32[3], zap->port, zap->creationTime,
+ zap->u.ssl2.cipherType));
+ if (zap->version < SSL_LIBRARY_VERSION_3_0) {
+ PRINT_BUF(8, (0, "sessionID:",
+ zap->u.ssl2.sessionID, sizeof(zap->u.ssl2.sessionID)));
+ PRINT_BUF(8, (0, "masterKey:",
+ zap->u.ssl2.masterKey.data, zap->u.ssl2.masterKey.len));
+ PRINT_BUF(8, (0, "cipherArg:",
+ zap->u.ssl2.cipherArg.data, zap->u.ssl2.cipherArg.len));
+ }
+
+ /* See if it's in the cache, if so nuke it */
+ while ((sid = *sidp) != 0) {
+ if (sid == zap) {
+ /*
+ ** Bingo. Reduce reference count by one so that when
+ ** everyone is done with the sid we can free it up.
+ */
+ *sidp = zap->next;
+ zap->cached = invalid_cache;
+ ssl_FreeLockedSID(zap);
+ return;
+ }
+ sidp = &sid->next;
+ }
+}
+
+/* If sid "zap" is in the cache,
+ * removes sid from cache, and decrements reference count.
+ * Although this function is static, it is called externally via
+ * ss->sec.uncache().
+ */
+static void
+LockAndUncacheSID(sslSessionID *zap)
+{
+ LOCK_CACHE;
+ UncacheSID(zap);
+ UNLOCK_CACHE;
+
+}
+
+/* choose client or server cache functions for this sslsocket. */
+void
+ssl_ChooseSessionIDProcs(sslSecurityInfo *sec)
+{
+ if (sec->isServer) {
+ sec->cache = ssl_sid_cache;
+ sec->uncache = ssl_sid_uncache;
+ } else {
+ sec->cache = CacheSID;
+ sec->uncache = LockAndUncacheSID;
+ }
+}
+
+/* wipe out the entire client session cache. */
+void
+SSL_ClearSessionCache(void)
+{
+ LOCK_CACHE;
+ while(cache != NULL)
+ UncacheSID(cache);
+ UNLOCK_CACHE;
+}
+
+/* returns an unsigned int containing the number of seconds in PR_Now() */
+PRUint32
+ssl_Time(void)
+{
+ PRUint32 myTime;
+#if defined(XP_UNIX) || defined(XP_WIN) || defined(_WINDOWS) || defined(XP_BEOS)
+ myTime = time(NULL); /* accurate until the year 2038. */
+#else
+ /* portable, but possibly slower */
+ PRTime now;
+ PRInt64 ll;
+
+ now = PR_Now();
+ LL_I2L(ll, 1000000L);
+ LL_DIV(now, now, ll);
+ LL_L2UI(myTime, now);
+#endif
+ return myTime;
+}
+
+SECStatus
+ssl3_SetSIDSessionTicket(sslSessionID *sid, NewSessionTicket *session_ticket)
+{
+ SECStatus rv;
+
+ /* We need to lock the cache, as this sid might already be in the cache. */
+ LOCK_CACHE;
+
+ /* A server might have sent us an empty ticket, which has the
+ * effect of clearing the previously known ticket.
+ */
+ if (sid->u.ssl3.sessionTicket.ticket.data)
+ SECITEM_FreeItem(&sid->u.ssl3.sessionTicket.ticket, PR_FALSE);
+ if (session_ticket->ticket.len > 0) {
+ rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.sessionTicket.ticket,
+ &session_ticket->ticket);
+ if (rv != SECSuccess) {
+ UNLOCK_CACHE;
+ return rv;
+ }
+ } else {
+ sid->u.ssl3.sessionTicket.ticket.data = NULL;
+ sid->u.ssl3.sessionTicket.ticket.len = 0;
+ }
+ sid->u.ssl3.sessionTicket.received_timestamp =
+ session_ticket->received_timestamp;
+ sid->u.ssl3.sessionTicket.ticket_lifetime_hint =
+ session_ticket->ticket_lifetime_hint;
+
+ UNLOCK_CACHE;
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslplatf.c b/chromium/net/third_party/nss/ssl/sslplatf.c
new file mode 100644
index 00000000000..fee702191a7
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslplatf.c
@@ -0,0 +1,732 @@
+/*
+ * Platform specific crypto wrappers
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1994-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Ryan Sleevi <ryan.sleevi@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+/* $Id$ */
+#include "certt.h"
+#include "cryptohi.h"
+#include "keythi.h"
+#include "nss.h"
+#include "secitem.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "prerror.h"
+#include "prinit.h"
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+#ifdef XP_WIN32
+#include <NCrypt.h>
+#endif
+#endif
+
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+CERTCertificateList*
+hack_NewCertificateListFromCertList(CERTCertList* list)
+{
+ CERTCertificateList * chain = NULL;
+ PLArenaPool * arena = NULL;
+ CERTCertListNode * node;
+ int len;
+
+ if (CERT_LIST_EMPTY(list))
+ goto loser;
+
+ arena = PORT_NewArena(4096);
+ if (arena == NULL)
+ goto loser;
+
+ for (len = 0, node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
+ len++, node = CERT_LIST_NEXT(node)) {
+ }
+
+ chain = PORT_ArenaNew(arena, CERTCertificateList);
+ if (chain == NULL)
+ goto loser;
+
+ chain->certs = PORT_ArenaNewArray(arena, SECItem, len);
+ if (!chain->certs)
+ goto loser;
+ chain->len = len;
+
+ for (len = 0, node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
+ len++, node = CERT_LIST_NEXT(node)) {
+ // Check to see if the last cert to be sent is a self-signed cert,
+ // and if so, omit it from the list of certificates. However, if
+ // there is only one cert (len == 0), include the cert, as it means
+ // the EE cert is self-signed.
+ if (len > 0 && (len == chain->len - 1) && node->cert->isRoot) {
+ chain->len = len;
+ break;
+ }
+ SECITEM_CopyItem(arena, &chain->certs[len], &node->cert->derCert);
+ }
+
+ chain->arena = arena;
+ return chain;
+
+loser:
+ if (arena) {
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ return NULL;
+}
+
+#if defined(XP_WIN32)
+typedef SECURITY_STATUS (WINAPI *NCryptFreeObjectFunc)(NCRYPT_HANDLE);
+typedef SECURITY_STATUS (WINAPI *NCryptSignHashFunc)(
+ NCRYPT_KEY_HANDLE /* hKey */,
+ VOID* /* pPaddingInfo */,
+ PBYTE /* pbHashValue */,
+ DWORD /* cbHashValue */,
+ PBYTE /* pbSignature */,
+ DWORD /* cbSignature */,
+ DWORD* /* pcbResult */,
+ DWORD /* dwFlags */);
+
+static PRCallOnceType cngFunctionsInitOnce;
+static const PRCallOnceType pristineCallOnce;
+
+static PRLibrary *ncrypt_library = NULL;
+static NCryptFreeObjectFunc pNCryptFreeObject = NULL;
+static NCryptSignHashFunc pNCryptSignHash = NULL;
+
+static SECStatus
+ssl_ShutdownCngFunctions(void *appData, void *nssData)
+{
+ pNCryptSignHash = NULL;
+ pNCryptFreeObject = NULL;
+ if (ncrypt_library) {
+ PR_UnloadLibrary(ncrypt_library);
+ ncrypt_library = NULL;
+ }
+
+ cngFunctionsInitOnce = pristineCallOnce;
+
+ return SECSuccess;
+}
+
+static PRStatus
+ssl_InitCngFunctions(void)
+{
+ SECStatus rv;
+
+ ncrypt_library = PR_LoadLibrary("ncrypt.dll");
+ if (ncrypt_library == NULL)
+ goto loser;
+
+ pNCryptFreeObject = (NCryptFreeObjectFunc)PR_FindFunctionSymbol(
+ ncrypt_library, "NCryptFreeObject");
+ if (pNCryptFreeObject == NULL)
+ goto loser;
+
+ pNCryptSignHash = (NCryptSignHashFunc)PR_FindFunctionSymbol(
+ ncrypt_library, "NCryptSignHash");
+ if (pNCryptSignHash == NULL)
+ goto loser;
+
+ rv = NSS_RegisterShutdown(ssl_ShutdownCngFunctions, NULL);
+ if (rv != SECSuccess)
+ goto loser;
+
+ return PR_SUCCESS;
+
+loser:
+ pNCryptSignHash = NULL;
+ pNCryptFreeObject = NULL;
+ if (ncrypt_library) {
+ PR_UnloadLibrary(ncrypt_library);
+ ncrypt_library = NULL;
+ }
+
+ return PR_FAILURE;
+}
+
+static SECStatus
+ssl_InitCng(void)
+{
+ if (PR_CallOnce(&cngFunctionsInitOnce, ssl_InitCngFunctions) != PR_SUCCESS)
+ return SECFailure;
+ return SECSuccess;
+}
+
+void
+ssl_FreePlatformKey(PlatformKey key)
+{
+ if (!key)
+ return;
+
+ if (key->dwKeySpec == CERT_NCRYPT_KEY_SPEC) {
+ if (ssl_InitCng() == SECSuccess) {
+ (*pNCryptFreeObject)(key->hNCryptKey);
+ }
+ } else {
+ CryptReleaseContext(key->hCryptProv, 0);
+ }
+ PORT_Free(key);
+}
+
+static SECStatus
+ssl3_CngPlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType)
+{
+ SECStatus rv = SECFailure;
+ SECURITY_STATUS ncrypt_status;
+ PRBool doDerEncode = PR_FALSE;
+ SECItem hashItem;
+ DWORD signatureLen = 0;
+ DWORD dwFlags = 0;
+ VOID *pPaddingInfo = NULL;
+
+ /* Always encode using PKCS#1 block type. */
+ BCRYPT_PKCS1_PADDING_INFO rsaPaddingInfo;
+
+ if (key->dwKeySpec != CERT_NCRYPT_KEY_SPEC) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+ if (ssl_InitCng() != SECSuccess) {
+ PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+ return SECFailure;
+ }
+
+ switch (keyType) {
+ case rsaKey:
+ switch (hash->hashAlg) {
+ case SEC_OID_UNKNOWN:
+ /* No OID/encoded DigestInfo. */
+ rsaPaddingInfo.pszAlgId = NULL;
+ break;
+ case SEC_OID_SHA1:
+ rsaPaddingInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM;
+ break;
+ case SEC_OID_SHA256:
+ rsaPaddingInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM;
+ break;
+ case SEC_OID_SHA384:
+ rsaPaddingInfo.pszAlgId = BCRYPT_SHA384_ALGORITHM;
+ break;
+ case SEC_OID_SHA512:
+ rsaPaddingInfo.pszAlgId = BCRYPT_SHA512_ALGORITHM;
+ break;
+ default:
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ dwFlags = BCRYPT_PAD_PKCS1;
+ pPaddingInfo = &rsaPaddingInfo;
+ break;
+ case dsaKey:
+ case ecKey:
+ if (keyType == ecKey) {
+ doDerEncode = PR_TRUE;
+ } else {
+ doDerEncode = isTLS;
+ }
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+ PRINT_BUF(60, (NULL, "hash(es) to be signed", hashItem.data, hashItem.len));
+
+ ncrypt_status = (*pNCryptSignHash)(key->hNCryptKey, pPaddingInfo,
+ (PBYTE)hashItem.data, hashItem.len,
+ NULL, 0, &signatureLen, dwFlags);
+ if (FAILED(ncrypt_status) || signatureLen == 0) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, ncrypt_status);
+ goto done;
+ }
+
+ buf->data = (unsigned char *)PORT_Alloc(signatureLen);
+ if (!buf->data) {
+ goto done; /* error code was set. */
+ }
+
+ ncrypt_status = (*pNCryptSignHash)(key->hNCryptKey, pPaddingInfo,
+ (PBYTE)hashItem.data, hashItem.len,
+ (PBYTE)buf->data, signatureLen,
+ &signatureLen, dwFlags);
+ if (FAILED(ncrypt_status) || signatureLen == 0) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, ncrypt_status);
+ goto done;
+ }
+
+ buf->len = signatureLen;
+
+ if (doDerEncode) {
+ SECItem derSig = {siBuffer, NULL, 0};
+
+ /* This also works for an ECDSA signature */
+ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len);
+ if (rv == SECSuccess) {
+ PORT_Free(buf->data); /* discard unencoded signature. */
+ *buf = derSig; /* give caller encoded signature. */
+ } else if (derSig.data) {
+ PORT_Free(derSig.data);
+ }
+ } else {
+ rv = SECSuccess;
+ }
+
+ PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len));
+
+done:
+ if (rv != SECSuccess && buf->data) {
+ PORT_Free(buf->data);
+ buf->data = NULL;
+ buf->len = 0;
+ }
+
+ return rv;
+}
+
+static SECStatus
+ssl3_CAPIPlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType)
+{
+ SECStatus rv = SECFailure;
+ PRBool doDerEncode = PR_FALSE;
+ SECItem hashItem;
+ DWORD argLen = 0;
+ DWORD signatureLen = 0;
+ ALG_ID hashAlg = 0;
+ HCRYPTHASH hHash = 0;
+ DWORD hashLen = 0;
+ unsigned int i = 0;
+
+ buf->data = NULL;
+
+ switch (hash->hashAlg) {
+ case SEC_OID_UNKNOWN:
+ hashAlg = 0;
+ break;
+ case SEC_OID_SHA1:
+ hashAlg = CALG_SHA1;
+ break;
+ case SEC_OID_SHA256:
+ hashAlg = CALG_SHA_256;
+ break;
+ case SEC_OID_SHA384:
+ hashAlg = CALG_SHA_384;
+ break;
+ case SEC_OID_SHA512:
+ hashAlg = CALG_SHA_512;
+ break;
+ default:
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+
+ switch (keyType) {
+ case rsaKey:
+ if (hashAlg == 0) {
+ hashAlg = CALG_SSL3_SHAMD5;
+ }
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ break;
+ case dsaKey:
+ case ecKey:
+ if (keyType == ecKey) {
+ doDerEncode = PR_TRUE;
+ } else {
+ doDerEncode = isTLS;
+ }
+ if (hashAlg == 0) {
+ hashAlg = CALG_SHA1;
+ hashItem.data = hash->u.s.sha;
+ hashItem.len = sizeof(hash->u.s.sha);
+ } else {
+ hashItem.data = hash->u.raw;
+ hashItem.len = hash->len;
+ }
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+ PRINT_BUF(60, (NULL, "hash(es) to be signed", hashItem.data, hashItem.len));
+
+ if (!CryptCreateHash(key->hCryptProv, hashAlg, 0, 0, &hHash)) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError());
+ goto done;
+ }
+ argLen = sizeof(hashLen);
+ if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&hashLen, &argLen, 0)) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError());
+ goto done;
+ }
+ if (hashLen != hashItem.len) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, 0);
+ goto done;
+ }
+ if (!CryptSetHashParam(hHash, HP_HASHVAL, (BYTE*)hashItem.data, 0)) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError());
+ goto done;
+ }
+ if (!CryptSignHash(hHash, key->dwKeySpec, NULL, 0,
+ NULL, &signatureLen) || signatureLen == 0) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError());
+ goto done;
+ }
+ buf->data = (unsigned char *)PORT_Alloc(signatureLen);
+ if (!buf->data)
+ goto done; /* error code was set. */
+
+ if (!CryptSignHash(hHash, key->dwKeySpec, NULL, 0,
+ (BYTE*)buf->data, &signatureLen)) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, GetLastError());
+ goto done;
+ }
+ buf->len = signatureLen;
+
+ /* CryptoAPI signs in little-endian, so reverse */
+ for (i = 0; i < buf->len / 2; ++i) {
+ unsigned char tmp = buf->data[i];
+ buf->data[i] = buf->data[buf->len - 1 - i];
+ buf->data[buf->len - 1 - i] = tmp;
+ }
+ if (doDerEncode) {
+ SECItem derSig = {siBuffer, NULL, 0};
+
+ /* This also works for an ECDSA signature */
+ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len);
+ if (rv == SECSuccess) {
+ PORT_Free(buf->data); /* discard unencoded signature. */
+ *buf = derSig; /* give caller encoded signature. */
+ } else if (derSig.data) {
+ PORT_Free(derSig.data);
+ }
+ } else {
+ rv = SECSuccess;
+ }
+
+ PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len));
+done:
+ if (hHash)
+ CryptDestroyHash(hHash);
+ if (rv != SECSuccess && buf->data) {
+ PORT_Free(buf->data);
+ buf->data = NULL;
+ }
+ return rv;
+}
+
+SECStatus
+ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType)
+{
+ if (key->dwKeySpec == CERT_NCRYPT_KEY_SPEC) {
+ return ssl3_CngPlatformSignHashes(hash, key, buf, isTLS, keyType);
+ }
+ return ssl3_CAPIPlatformSignHashes(hash, key, buf, isTLS, keyType);
+}
+
+#elif defined(XP_MACOSX)
+#include <Security/cssm.h>
+
+void
+ssl_FreePlatformKey(PlatformKey key)
+{
+ CFRelease(key);
+}
+
+#define SSL_MAX_DIGEST_INFO_PREFIX 20
+
+/* ssl3_GetDigestInfoPrefix sets |out| and |out_len| to point to a buffer that
+ * contains ASN.1 data that should be prepended to a hash of the given type in
+ * order to create a DigestInfo structure that is valid for use in a PKCS #1
+ * v1.5 RSA signature. |out_len| will not be set to a value greater than
+ * SSL_MAX_DIGEST_INFO_PREFIX. */
+static SECStatus
+ssl3_GetDigestInfoPrefix(SECOidTag hashAlg,
+ const SSL3Opaque** out, unsigned int *out_len)
+{
+ /* These are the DER encoding of ASN.1 DigestInfo structures:
+ * DigestInfo ::= SEQUENCE {
+ * digestAlgorithm AlgorithmIdentifier,
+ * digest OCTET STRING
+ * }
+ * See PKCS #1 v2.2 Section 9.2, Note 1.
+ */
+ static const unsigned char kSHA1[] = {
+ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
+ 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14
+ };
+ static const unsigned char kSHA224[] = {
+ 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
+ 0x00, 0x04, 0x1c
+ };
+ static const unsigned char kSHA256[] = {
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20
+ };
+ static const unsigned char kSHA384[] = {
+ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
+ 0x00, 0x04, 0x30
+ };
+ static const unsigned char kSHA512[] = {
+ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+ 0x00, 0x04, 0x40
+ };
+
+ switch (hashAlg) {
+ case SEC_OID_UNKNOWN:
+ *out_len = 0;
+ break;
+ case SEC_OID_SHA1:
+ *out = kSHA1;
+ *out_len = sizeof(kSHA1);
+ break;
+ case SEC_OID_SHA224:
+ *out = kSHA224;
+ *out_len = sizeof(kSHA224);
+ break;
+ case SEC_OID_SHA256:
+ *out = kSHA256;
+ *out_len = sizeof(kSHA256);
+ break;
+ case SEC_OID_SHA384:
+ *out = kSHA384;
+ *out_len = sizeof(kSHA384);
+ break;
+ case SEC_OID_SHA512:
+ *out = kSHA512;
+ *out_len = sizeof(kSHA512);
+ break;
+ default:
+ PORT_SetError(SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus
+ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType)
+{
+ SECStatus rv = SECFailure;
+ PRBool doDerEncode = PR_FALSE;
+ unsigned int signatureLen;
+ OSStatus status = noErr;
+ CSSM_CSP_HANDLE cspHandle = 0;
+ const CSSM_KEY *cssmKey = NULL;
+ CSSM_ALGORITHMS sigAlg;
+ CSSM_ALGORITHMS digestAlg;
+ const CSSM_ACCESS_CREDENTIALS * cssmCreds = NULL;
+ CSSM_RETURN cssmRv;
+ CSSM_DATA hashData;
+ CSSM_DATA signatureData;
+ CSSM_CC_HANDLE cssmSignature = 0;
+ const SSL3Opaque* prefix;
+ unsigned int prefixLen;
+ SSL3Opaque prefixAndHash[SSL_MAX_DIGEST_INFO_PREFIX + HASH_LENGTH_MAX];
+
+ buf->data = NULL;
+
+ status = SecKeyGetCSPHandle(key, &cspHandle);
+ if (status != noErr) {
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+
+ status = SecKeyGetCSSMKey(key, &cssmKey);
+ if (status != noErr || !cssmKey) {
+ PORT_SetError(SEC_ERROR_NO_KEY);
+ goto done;
+ }
+
+ /* SecKeyGetBlockSize wasn't addeded until OS X 10.6 - but the
+ * needed information is readily available on the key itself.
+ */
+ signatureLen = (cssmKey->KeyHeader.LogicalKeySizeInBits + 7) / 8;
+
+ if (signatureLen == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+
+ buf->data = (unsigned char *)PORT_Alloc(signatureLen);
+ if (!buf->data)
+ goto done; /* error code was set. */
+
+ sigAlg = cssmKey->KeyHeader.AlgorithmId;
+ digestAlg = CSSM_ALGID_NONE;
+
+ switch (keyType) {
+ case rsaKey:
+ PORT_Assert(sigAlg == CSSM_ALGID_RSA);
+ if (ssl3_GetDigestInfoPrefix(hash->hashAlg, &prefix, &prefixLen) !=
+ SECSuccess) {
+ goto done;
+ }
+ if (prefixLen + hash->len > sizeof(prefixAndHash)) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto done;
+ }
+ memcpy(prefixAndHash, prefix, prefixLen);
+ memcpy(prefixAndHash + prefixLen, hash->u.raw, hash->len);
+ hashData.Data = prefixAndHash;
+ hashData.Length = prefixLen + hash->len;
+ break;
+ case dsaKey:
+ case ecKey:
+ if (keyType == ecKey) {
+ PORT_Assert(sigAlg == CSSM_ALGID_ECDSA);
+ doDerEncode = PR_TRUE;
+ } else {
+ PORT_Assert(sigAlg == CSSM_ALGID_DSA);
+ doDerEncode = isTLS;
+ }
+ if (hash->hashAlg == SEC_OID_UNKNOWN) {
+ hashData.Data = hash->u.s.sha;
+ hashData.Length = sizeof(hash->u.s.sha);
+ } else {
+ hashData.Data = hash->u.raw;
+ hashData.Length = hash->len;
+ }
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_KEY);
+ goto done;
+ }
+ PRINT_BUF(60, (NULL, "hash(es) to be signed", hashData.Data, hashData.Length));
+
+ /* TODO(rsleevi): Should it be kSecCredentialTypeNoUI? In Win32, at least,
+ * you can prevent the UI by setting the provider handle on the
+ * certificate to be opened with CRYPT_SILENT, but is there an equivalent?
+ */
+ status = SecKeyGetCredentials(key, CSSM_ACL_AUTHORIZATION_SIGN,
+ kSecCredentialTypeDefault, &cssmCreds);
+ if (status != noErr) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, status);
+ goto done;
+ }
+
+ signatureData.Length = signatureLen;
+ signatureData.Data = (uint8*)buf->data;
+
+ cssmRv = CSSM_CSP_CreateSignatureContext(cspHandle, sigAlg, cssmCreds,
+ cssmKey, &cssmSignature);
+ if (cssmRv) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv);
+ goto done;
+ }
+
+ /* See "Apple Cryptographic Service Provider Functional Specification" */
+ if (cssmKey->KeyHeader.AlgorithmId == CSSM_ALGID_RSA) {
+ /* To set RSA blinding for RSA keys */
+ CSSM_CONTEXT_ATTRIBUTE blindingAttr;
+ blindingAttr.AttributeType = CSSM_ATTRIBUTE_RSA_BLINDING;
+ blindingAttr.AttributeLength = sizeof(uint32);
+ blindingAttr.Attribute.Uint32 = 1;
+ cssmRv = CSSM_UpdateContextAttributes(cssmSignature, 1, &blindingAttr);
+ if (cssmRv) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv);
+ goto done;
+ }
+ }
+
+ cssmRv = CSSM_SignData(cssmSignature, &hashData, 1, digestAlg,
+ &signatureData);
+ if (cssmRv) {
+ PR_SetError(SSL_ERROR_SIGN_HASHES_FAILURE, cssmRv);
+ goto done;
+ }
+ buf->len = signatureData.Length;
+
+ if (doDerEncode) {
+ SECItem derSig = {siBuffer, NULL, 0};
+
+ /* This also works for an ECDSA signature */
+ rv = DSAU_EncodeDerSigWithLen(&derSig, buf, buf->len);
+ if (rv == SECSuccess) {
+ PORT_Free(buf->data); /* discard unencoded signature. */
+ *buf = derSig; /* give caller encoded signature. */
+ } else if (derSig.data) {
+ PORT_Free(derSig.data);
+ }
+ } else {
+ rv = SECSuccess;
+ }
+
+ PRINT_BUF(60, (NULL, "signed hashes", buf->data, buf->len));
+done:
+ /* cspHandle, cssmKey, and cssmCreds are owned by the SecKeyRef and
+ * should not be freed. When the PlatformKey is freed, they will be
+ * released.
+ */
+ if (cssmSignature)
+ CSSM_DeleteContext(cssmSignature);
+
+ if (rv != SECSuccess && buf->data) {
+ PORT_Free(buf->data);
+ buf->data = NULL;
+ }
+ return rv;
+}
+#else
+void
+ssl_FreePlatformKey(PlatformKey key)
+{
+}
+
+SECStatus
+ssl3_PlatformSignHashes(SSL3Hashes *hash, PlatformKey key, SECItem *buf,
+ PRBool isTLS, KeyType keyType)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return SECFailure;
+}
+#endif
+
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
diff --git a/chromium/net/third_party/nss/ssl/sslproto.h b/chromium/net/third_party/nss/ssl/sslproto.h
new file mode 100644
index 00000000000..53bba011bb9
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslproto.h
@@ -0,0 +1,231 @@
+/*
+ * Various and sundry protocol constants. DON'T CHANGE THESE. These values
+ * are mostly defined by the SSL2, SSL3, or TLS protocol specifications.
+ * Cipher kinds and ciphersuites are part of the public API.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __sslproto_h_
+#define __sslproto_h_
+
+/* All versions less than 3_0 are treated as SSL version 2 */
+#define SSL_LIBRARY_VERSION_2 0x0002
+#define SSL_LIBRARY_VERSION_3_0 0x0300
+#define SSL_LIBRARY_VERSION_TLS_1_0 0x0301
+#define SSL_LIBRARY_VERSION_TLS_1_1 0x0302
+#define SSL_LIBRARY_VERSION_TLS_1_2 0x0303
+/* Note: this is the internal format, not the wire format */
+#define SSL_LIBRARY_VERSION_DTLS_1_0 0x0302
+
+/* deprecated old name */
+#define SSL_LIBRARY_VERSION_3_1_TLS SSL_LIBRARY_VERSION_TLS_1_0
+
+/* The DTLS version used in the spec */
+#define SSL_LIBRARY_VERSION_DTLS_1_0_WIRE ((~0x0100) & 0xffff)
+
+/* Header lengths of some of the messages */
+#define SSL_HL_ERROR_HBYTES 3
+#define SSL_HL_CLIENT_HELLO_HBYTES 9
+#define SSL_HL_CLIENT_MASTER_KEY_HBYTES 10
+#define SSL_HL_CLIENT_FINISHED_HBYTES 1
+#define SSL_HL_SERVER_HELLO_HBYTES 11
+#define SSL_HL_SERVER_VERIFY_HBYTES 1
+#define SSL_HL_SERVER_FINISHED_HBYTES 1
+#define SSL_HL_REQUEST_CERTIFICATE_HBYTES 2
+#define SSL_HL_CLIENT_CERTIFICATE_HBYTES 6
+
+/* Security handshake protocol codes */
+#define SSL_MT_ERROR 0
+#define SSL_MT_CLIENT_HELLO 1
+#define SSL_MT_CLIENT_MASTER_KEY 2
+#define SSL_MT_CLIENT_FINISHED 3
+#define SSL_MT_SERVER_HELLO 4
+#define SSL_MT_SERVER_VERIFY 5
+#define SSL_MT_SERVER_FINISHED 6
+#define SSL_MT_REQUEST_CERTIFICATE 7
+#define SSL_MT_CLIENT_CERTIFICATE 8
+
+/* Certificate types */
+#define SSL_CT_X509_CERTIFICATE 0x01
+#if 0 /* XXX Not implemented yet */
+#define SSL_PKCS6_CERTIFICATE 0x02
+#endif
+#define SSL_AT_MD5_WITH_RSA_ENCRYPTION 0x01
+
+/* Error codes */
+#define SSL_PE_NO_CYPHERS 0x0001
+#define SSL_PE_NO_CERTIFICATE 0x0002
+#define SSL_PE_BAD_CERTIFICATE 0x0004
+#define SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE 0x0006
+
+/* Cypher kinds (not the spec version!) */
+#define SSL_CK_RC4_128_WITH_MD5 0x01
+#define SSL_CK_RC4_128_EXPORT40_WITH_MD5 0x02
+#define SSL_CK_RC2_128_CBC_WITH_MD5 0x03
+#define SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 0x04
+#define SSL_CK_IDEA_128_CBC_WITH_MD5 0x05
+#define SSL_CK_DES_64_CBC_WITH_MD5 0x06
+#define SSL_CK_DES_192_EDE3_CBC_WITH_MD5 0x07
+
+/* Cipher enables. These are used only for SSL_EnableCipher
+ * These values define the SSL2 suites, and do not colide with the
+ * SSL3 Cipher suites defined below.
+ */
+#define SSL_EN_RC4_128_WITH_MD5 0xFF01
+#define SSL_EN_RC4_128_EXPORT40_WITH_MD5 0xFF02
+#define SSL_EN_RC2_128_CBC_WITH_MD5 0xFF03
+#define SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5 0xFF04
+#define SSL_EN_IDEA_128_CBC_WITH_MD5 0xFF05
+#define SSL_EN_DES_64_CBC_WITH_MD5 0xFF06
+#define SSL_EN_DES_192_EDE3_CBC_WITH_MD5 0xFF07
+
+/* SSL v3 Cipher Suites */
+#define SSL_NULL_WITH_NULL_NULL 0x0000
+
+#define SSL_RSA_WITH_NULL_MD5 0x0001
+#define SSL_RSA_WITH_NULL_SHA 0x0002
+#define SSL_RSA_EXPORT_WITH_RC4_40_MD5 0x0003
+#define SSL_RSA_WITH_RC4_128_MD5 0x0004
+#define SSL_RSA_WITH_RC4_128_SHA 0x0005
+#define SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 0x0006
+#define SSL_RSA_WITH_IDEA_CBC_SHA 0x0007
+#define SSL_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0008
+#define SSL_RSA_WITH_DES_CBC_SHA 0x0009
+#define SSL_RSA_WITH_3DES_EDE_CBC_SHA 0x000a
+
+#define SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA 0x000b
+#define SSL_DH_DSS_WITH_DES_CBC_SHA 0x000c
+#define SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA 0x000d
+#define SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA 0x000e
+#define SSL_DH_RSA_WITH_DES_CBC_SHA 0x000f
+#define SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA 0x0010
+
+#define SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 0x0011
+#define SSL_DHE_DSS_WITH_DES_CBC_SHA 0x0012
+#define SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x0013
+#define SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0014
+#define SSL_DHE_RSA_WITH_DES_CBC_SHA 0x0015
+#define SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA 0x0016
+
+#define SSL_DH_ANON_EXPORT_WITH_RC4_40_MD5 0x0017
+#define SSL_DH_ANON_WITH_RC4_128_MD5 0x0018
+#define SSL_DH_ANON_EXPORT_WITH_DES40_CBC_SHA 0x0019
+#define SSL_DH_ANON_WITH_DES_CBC_SHA 0x001a
+#define SSL_DH_ANON_WITH_3DES_EDE_CBC_SHA 0x001b
+
+#define SSL_FORTEZZA_DMS_WITH_NULL_SHA 0x001c /* deprecated */
+#define SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA 0x001d /* deprecated */
+#define SSL_FORTEZZA_DMS_WITH_RC4_128_SHA 0x001e /* deprecated */
+
+/* New TLS cipher suites */
+#define TLS_RSA_WITH_AES_128_CBC_SHA 0x002F
+#define TLS_DH_DSS_WITH_AES_128_CBC_SHA 0x0030
+#define TLS_DH_RSA_WITH_AES_128_CBC_SHA 0x0031
+#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA 0x0032
+#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA 0x0033
+#define TLS_DH_ANON_WITH_AES_128_CBC_SHA 0x0034
+
+#define TLS_RSA_WITH_AES_256_CBC_SHA 0x0035
+#define TLS_DH_DSS_WITH_AES_256_CBC_SHA 0x0036
+#define TLS_DH_RSA_WITH_AES_256_CBC_SHA 0x0037
+#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA 0x0038
+#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA 0x0039
+#define TLS_DH_ANON_WITH_AES_256_CBC_SHA 0x003A
+#define TLS_RSA_WITH_NULL_SHA256 0x003B
+#define TLS_RSA_WITH_AES_128_CBC_SHA256 0x003C
+#define TLS_RSA_WITH_AES_256_CBC_SHA256 0x003D
+
+#define TLS_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0041
+#define TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0042
+#define TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0043
+#define TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0044
+#define TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0045
+#define TLS_DH_ANON_WITH_CAMELLIA_128_CBC_SHA 0x0046
+
+#define TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA 0x0062
+#define TLS_RSA_EXPORT1024_WITH_RC4_56_SHA 0x0064
+
+#define TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA 0x0063
+#define TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA 0x0065
+#define TLS_DHE_DSS_WITH_RC4_128_SHA 0x0066
+#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 0x0067
+#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 0x006B
+
+#define TLS_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0084
+#define TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0085
+#define TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0086
+#define TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0087
+#define TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0088
+#define TLS_DH_ANON_WITH_CAMELLIA_256_CBC_SHA 0x0089
+
+#define TLS_RSA_WITH_SEED_CBC_SHA 0x0096
+
+#define TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C
+#define TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E
+#define TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 0x00A2
+
+/* TLS "Signaling Cipher Suite Value" (SCSV). May be requested by client.
+ * Must NEVER be chosen by server. SSL 3.0 server acknowledges by sending
+ * back an empty Renegotiation Info (RI) server hello extension.
+ */
+#define TLS_EMPTY_RENEGOTIATION_INFO_SCSV 0x00FF
+
+/* Cipher Suite Values starting with 0xC000 are defined in informational
+ * RFCs.
+ */
+#define TLS_ECDH_ECDSA_WITH_NULL_SHA 0xC001
+#define TLS_ECDH_ECDSA_WITH_RC4_128_SHA 0xC002
+#define TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC003
+#define TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA 0xC004
+#define TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA 0xC005
+
+#define TLS_ECDHE_ECDSA_WITH_NULL_SHA 0xC006
+#define TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 0xC007
+#define TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC008
+#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC009
+#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0xC00A
+
+#define TLS_ECDH_RSA_WITH_NULL_SHA 0xC00B
+#define TLS_ECDH_RSA_WITH_RC4_128_SHA 0xC00C
+#define TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA 0xC00D
+#define TLS_ECDH_RSA_WITH_AES_128_CBC_SHA 0xC00E
+#define TLS_ECDH_RSA_WITH_AES_256_CBC_SHA 0xC00F
+
+#define TLS_ECDHE_RSA_WITH_NULL_SHA 0xC010
+#define TLS_ECDHE_RSA_WITH_RC4_128_SHA 0xC011
+#define TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 0xC012
+#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0xC013
+#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 0xC014
+
+#define TLS_ECDH_anon_WITH_NULL_SHA 0xC015
+#define TLS_ECDH_anon_WITH_RC4_128_SHA 0xC016
+#define TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA 0xC017
+#define TLS_ECDH_anon_WITH_AES_128_CBC_SHA 0xC018
+#define TLS_ECDH_anon_WITH_AES_256_CBC_SHA 0xC019
+
+#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023
+#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027
+
+#define TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B
+#define TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0xC02D
+#define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F
+#define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031
+
+/* Netscape "experimental" cipher suites. */
+#define SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA 0xffe0
+#define SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA 0xffe1
+
+/* New non-experimental openly spec'ed versions of those cipher suites. */
+#define SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA 0xfeff
+#define SSL_RSA_FIPS_WITH_DES_CBC_SHA 0xfefe
+
+/* DTLS-SRTP cipher suites from RFC 5764 */
+/* If you modify this, also modify MAX_DTLS_SRTP_CIPHER_SUITES in sslimpl.h */
+#define SRTP_AES128_CM_HMAC_SHA1_80 0x0001
+#define SRTP_AES128_CM_HMAC_SHA1_32 0x0002
+#define SRTP_NULL_HMAC_SHA1_80 0x0005
+#define SRTP_NULL_HMAC_SHA1_32 0x0006
+
+#endif /* __sslproto_h_ */
diff --git a/chromium/net/third_party/nss/ssl/sslreveal.c b/chromium/net/third_party/nss/ssl/sslreveal.c
new file mode 100644
index 00000000000..d97299885d9
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslreveal.c
@@ -0,0 +1,112 @@
+/*
+ * Accessor functions for SSLSocket private members.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "cert.h"
+#include "ssl.h"
+#include "certt.h"
+#include "sslimpl.h"
+
+/* given PRFileDesc, returns a copy of certificate associated with the socket
+ * the caller should delete the cert when done with SSL_DestroyCertificate
+ */
+CERTCertificate *
+SSL_RevealCert(PRFileDesc * fd)
+{
+ CERTCertificate * cert = NULL;
+ sslSocket * sslsocket = NULL;
+
+ sslsocket = ssl_FindSocket(fd);
+
+ /* CERT_DupCertificate increases reference count and returns pointer to
+ * the same cert
+ */
+ if (sslsocket && sslsocket->sec.peerCert)
+ cert = CERT_DupCertificate(sslsocket->sec.peerCert);
+
+ return cert;
+}
+
+/* given PRFileDesc, returns a pointer to PinArg associated with the socket
+ */
+void *
+SSL_RevealPinArg(PRFileDesc * fd)
+{
+ sslSocket * sslsocket = NULL;
+ void * PinArg = NULL;
+
+ sslsocket = ssl_FindSocket(fd);
+
+ /* is pkcs11PinArg part of the sslSocket or sslSecurityInfo ? */
+ if (sslsocket)
+ PinArg = sslsocket->pkcs11PinArg;
+
+ return PinArg;
+}
+
+
+/* given PRFileDesc, returns a pointer to the URL associated with the socket
+ * the caller should free url when done
+ */
+char *
+SSL_RevealURL(PRFileDesc * fd)
+{
+ sslSocket * sslsocket = NULL;
+ char * url = NULL;
+
+ sslsocket = ssl_FindSocket(fd);
+
+ if (sslsocket && sslsocket->url)
+ url = PL_strdup(sslsocket->url);
+
+ return url;
+}
+
+
+/* given PRFileDesc, returns status information related to extensions
+ * negotiated with peer during the handshake.
+ */
+
+SECStatus
+SSL_HandshakeNegotiatedExtension(PRFileDesc * socket,
+ SSLExtensionType extId,
+ PRBool *pYes)
+{
+ /* some decisions derived from SSL_GetChannelInfo */
+ sslSocket * sslsocket = NULL;
+
+ if (!pYes) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ sslsocket = ssl_FindSocket(socket);
+ if (!sslsocket) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in HandshakeNegotiatedExtension",
+ SSL_GETPID(), socket));
+ return SECFailure;
+ }
+
+ *pYes = PR_FALSE;
+
+ /* according to public API SSL_GetChannelInfo, this doesn't need a lock */
+ if (sslsocket->opt.useSecurity) {
+ if (sslsocket->ssl3.initialized) { /* SSL3 and TLS */
+ /* now we know this socket went through ssl3_InitState() and
+ * ss->xtnData got initialized, which is the only member accessed by
+ * ssl3_ExtensionNegotiated();
+ * Member xtnData appears to get accessed in functions that handle
+ * the handshake (hello messages and extension sending),
+ * therefore the handshake lock should be sufficient.
+ */
+ ssl_GetSSL3HandshakeLock(sslsocket);
+ *pYes = ssl3_ExtensionNegotiated(sslsocket, extId);
+ ssl_ReleaseSSL3HandshakeLock(sslsocket);
+ }
+ }
+
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslsecur.c b/chromium/net/third_party/nss/ssl/sslsecur.c
new file mode 100644
index 00000000000..0714a0b75bb
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslsecur.c
@@ -0,0 +1,1598 @@
+/*
+ * Various SSL functions.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "cert.h"
+#include "secitem.h"
+#include "keyhi.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "secoid.h" /* for SECOID_GetALgorithmTag */
+#include "pk11func.h" /* for PK11_GenerateRandom */
+#include "nss.h" /* for NSS_RegisterShutdown */
+#include "prinit.h" /* for PR_CallOnceWithArg */
+
+#define MAX_BLOCK_CYPHER_SIZE 32
+
+#define TEST_FOR_FAILURE /* reminder */
+#define SET_ERROR_CODE /* reminder */
+
+/* Returns a SECStatus: SECSuccess or SECFailure, NOT SECWouldBlock.
+ *
+ * Currently, the list of functions called through ss->handshake is:
+ *
+ * In sslsocks.c:
+ * SocksGatherRecord
+ * SocksHandleReply
+ * SocksStartGather
+ *
+ * In sslcon.c:
+ * ssl_GatherRecord1stHandshake
+ * ssl2_HandleClientSessionKeyMessage
+ * ssl2_HandleMessage
+ * ssl2_HandleVerifyMessage
+ * ssl2_BeginClientHandshake
+ * ssl2_BeginServerHandshake
+ * ssl2_HandleClientHelloMessage
+ * ssl2_HandleServerHelloMessage
+ *
+ * The ss->handshake function returns SECWouldBlock under these conditions:
+ * 1. ssl_GatherRecord1stHandshake called ssl2_GatherData which read in
+ * the beginning of an SSL v3 hello message and returned SECWouldBlock
+ * to switch to SSL v3 handshake processing.
+ *
+ * 2. ssl2_HandleClientHelloMessage discovered version 3.0 in the incoming
+ * v2 client hello msg, and called ssl3_HandleV2ClientHello which
+ * returned SECWouldBlock.
+ *
+ * 3. SECWouldBlock was returned by one of the callback functions, via
+ * one of these paths:
+ * - ssl2_HandleMessage() -> ssl2_HandleRequestCertificate() ->
+ * ss->getClientAuthData()
+ *
+ * - ssl2_HandleServerHelloMessage() -> ss->handleBadCert()
+ *
+ * - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() ->
+ * ssl3_HandleRecord() -> ssl3_HandleHandshake() ->
+ * ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificate() ->
+ * ss->handleBadCert()
+ *
+ * - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() ->
+ * ssl3_HandleRecord() -> ssl3_HandleHandshake() ->
+ * ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificateRequest() ->
+ * ss->getClientAuthData()
+ *
+ * Called from: SSL_ForceHandshake (below),
+ * ssl_SecureRecv (below) and
+ * ssl_SecureSend (below)
+ * from: WaitForResponse in sslsocks.c
+ * ssl_SocksRecv in sslsocks.c
+ * ssl_SocksSend in sslsocks.c
+ *
+ * Caller must hold the (write) handshakeLock.
+ */
+int
+ssl_Do1stHandshake(sslSocket *ss)
+{
+ int rv = SECSuccess;
+ int loopCount = 0;
+
+ do {
+ PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss) );
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveRecvBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveXmitBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss));
+
+ if (ss->handshake == 0) {
+ /* Previous handshake finished. Switch to next one */
+ ss->handshake = ss->nextHandshake;
+ ss->nextHandshake = 0;
+ }
+ if (ss->handshake == 0) {
+ /* Previous handshake finished. Switch to security handshake */
+ ss->handshake = ss->securityHandshake;
+ ss->securityHandshake = 0;
+ }
+ if (ss->handshake == 0) {
+ ssl_GetRecvBufLock(ss);
+ ss->gs.recordLen = 0;
+ ssl_ReleaseRecvBufLock(ss);
+
+ SSL_TRC(3, ("%d: SSL[%d]: handshake is completed",
+ SSL_GETPID(), ss->fd));
+ /* call handshake callback for ssl v2 */
+ /* for v3 this is done in ssl3_HandleFinished() */
+ if ((ss->handshakeCallback != NULL) && /* has callback */
+ (!ss->firstHsDone) && /* only first time */
+ (ss->version < SSL_LIBRARY_VERSION_3_0)) { /* not ssl3 */
+ ss->firstHsDone = PR_TRUE;
+ (ss->handshakeCallback)(ss->fd, ss->handshakeCallbackData);
+ }
+ ss->firstHsDone = PR_TRUE;
+ ss->gs.writeOffset = 0;
+ ss->gs.readOffset = 0;
+ break;
+ }
+ rv = (*ss->handshake)(ss);
+ ++loopCount;
+ /* This code must continue to loop on SECWouldBlock,
+ * or any positive value. See XXX_1 comments.
+ */
+ } while (rv != SECFailure); /* was (rv >= 0); XXX_1 */
+
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveRecvBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveXmitBufLock(ss));
+ PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss));
+
+ if (rv == SECWouldBlock) {
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ rv = SECFailure;
+ }
+ return rv;
+}
+
+/*
+ * Handshake function that blocks. Used to force a
+ * retry on a connection on the next read/write.
+ */
+static SECStatus
+ssl3_AlwaysBlock(sslSocket *ss)
+{
+ PORT_SetError(PR_WOULD_BLOCK_ERROR); /* perhaps redundant. */
+ return SECWouldBlock;
+}
+
+/*
+ * set the initial handshake state machine to block
+ */
+void
+ssl3_SetAlwaysBlock(sslSocket *ss)
+{
+ if (!ss->firstHsDone) {
+ ss->handshake = ssl3_AlwaysBlock;
+ ss->nextHandshake = 0;
+ }
+}
+
+static SECStatus
+ssl_SetTimeout(PRFileDesc *fd, PRIntervalTime timeout)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SetTimeout", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ SSL_LOCK_READER(ss);
+ ss->rTimeout = timeout;
+ if (ss->opt.fdx) {
+ SSL_LOCK_WRITER(ss);
+ }
+ ss->wTimeout = timeout;
+ if (ss->opt.fdx) {
+ SSL_UNLOCK_WRITER(ss);
+ }
+ SSL_UNLOCK_READER(ss);
+ return SECSuccess;
+}
+
+/* Acquires and releases HandshakeLock.
+*/
+SECStatus
+SSL_ResetHandshake(PRFileDesc *s, PRBool asServer)
+{
+ sslSocket *ss;
+ SECStatus status;
+ PRNetAddr addr;
+
+ ss = ssl_FindSocket(s);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in ResetHandshake", SSL_GETPID(), s));
+ return SECFailure;
+ }
+
+ /* Don't waste my time */
+ if (!ss->opt.useSecurity)
+ return SECSuccess;
+
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+
+ /* Reset handshake state */
+ ssl_Get1stHandshakeLock(ss);
+
+ ss->firstHsDone = PR_FALSE;
+ if ( asServer ) {
+ ss->handshake = ssl2_BeginServerHandshake;
+ ss->handshaking = sslHandshakingAsServer;
+ } else {
+ ss->handshake = ssl2_BeginClientHandshake;
+ ss->handshaking = sslHandshakingAsClient;
+ }
+ ss->nextHandshake = 0;
+ ss->securityHandshake = 0;
+
+ ssl_GetRecvBufLock(ss);
+ status = ssl_InitGather(&ss->gs);
+ ssl_ReleaseRecvBufLock(ss);
+
+ ssl_GetSSL3HandshakeLock(ss);
+
+ /*
+ ** Blow away old security state and get a fresh setup.
+ */
+ ssl_GetXmitBufLock(ss);
+ ssl_ResetSecurityInfo(&ss->sec, PR_TRUE);
+ status = ssl_CreateSecurityInfo(ss);
+ ssl_ReleaseXmitBufLock(ss);
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ if (!ss->TCPconnected)
+ ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr));
+
+ SSL_UNLOCK_WRITER(ss);
+ SSL_UNLOCK_READER(ss);
+
+ return status;
+}
+
+/* For SSLv2, does nothing but return an error.
+** For SSLv3, flushes SID cache entry (if requested),
+** and then starts new client hello or hello request.
+** Acquires and releases HandshakeLock.
+*/
+SECStatus
+SSL_ReHandshake(PRFileDesc *fd, PRBool flushCache)
+{
+ sslSocket *ss;
+ SECStatus rv;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in RedoHandshake", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (!ss->opt.useSecurity)
+ return SECSuccess;
+
+ ssl_Get1stHandshakeLock(ss);
+
+ /* SSL v2 protocol does not support subsequent handshakes. */
+ if (ss->version < SSL_LIBRARY_VERSION_3_0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ } else {
+ ssl_GetSSL3HandshakeLock(ss);
+ rv = ssl3_RedoHandshake(ss, flushCache); /* force full handshake. */
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ }
+
+ ssl_Release1stHandshakeLock(ss);
+
+ return rv;
+}
+
+/*
+** Same as above, but with an I/O timeout.
+ */
+SSL_IMPORT SECStatus SSL_ReHandshakeWithTimeout(PRFileDesc *fd,
+ PRBool flushCache,
+ PRIntervalTime timeout)
+{
+ if (SECSuccess != ssl_SetTimeout(fd, timeout)) {
+ return SECFailure;
+ }
+ return SSL_ReHandshake(fd, flushCache);
+}
+
+SECStatus
+SSL_RedoHandshake(PRFileDesc *fd)
+{
+ return SSL_ReHandshake(fd, PR_TRUE);
+}
+
+/* Register an application callback to be called when SSL handshake completes.
+** Acquires and releases HandshakeLock.
+*/
+SECStatus
+SSL_HandshakeCallback(PRFileDesc *fd, SSLHandshakeCallback cb,
+ void *client_data)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in HandshakeCallback",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (!ss->opt.useSecurity) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ ss->handshakeCallback = cb;
+ ss->handshakeCallbackData = client_data;
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
+}
+
+/* Try to make progress on an SSL handshake by attempting to read the
+** next handshake from the peer, and sending any responses.
+** For non-blocking sockets, returns PR_ERROR_WOULD_BLOCK if it cannot
+** read the next handshake from the underlying socket.
+** For SSLv2, returns when handshake is complete or fatal error occurs.
+** For SSLv3, returns when handshake is complete, or application data has
+** arrived that must be taken by application before handshake can continue,
+** or a fatal error occurs.
+** Application should use handshake completion callback to tell which.
+*/
+SECStatus
+SSL_ForceHandshake(PRFileDesc *fd)
+{
+ sslSocket *ss;
+ SECStatus rv = SECFailure;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in ForceHandshake",
+ SSL_GETPID(), fd));
+ return rv;
+ }
+
+ /* Don't waste my time */
+ if (!ss->opt.useSecurity)
+ return SECSuccess;
+
+ if (!ssl_SocketIsBlocking(ss)) {
+ ssl_GetXmitBufLock(ss);
+ if (ss->pendingBuf.len != 0) {
+ int sent = ssl_SendSavedWriteData(ss);
+ if ((sent < 0) && (PORT_GetError() != PR_WOULD_BLOCK_ERROR)) {
+ ssl_ReleaseXmitBufLock(ss);
+ return SECFailure;
+ }
+ }
+ ssl_ReleaseXmitBufLock(ss);
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
+ int gatherResult;
+
+ ssl_GetRecvBufLock(ss);
+ gatherResult = ssl3_GatherCompleteHandshake(ss, 0);
+ ssl_ReleaseRecvBufLock(ss);
+ if (gatherResult > 0) {
+ rv = SECSuccess;
+ } else if (gatherResult == 0) {
+ PORT_SetError(PR_END_OF_FILE_ERROR);
+ } else if (gatherResult == SECWouldBlock) {
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ }
+ } else if (!ss->firstHsDone) {
+ rv = ssl_Do1stHandshake(ss);
+ } else {
+ /* tried to force handshake on an SSL 2 socket that has
+ ** already completed the handshake. */
+ rv = SECSuccess; /* just pretend we did it. */
+ }
+
+ ssl_Release1stHandshakeLock(ss);
+
+ return rv;
+}
+
+/*
+ ** Same as above, but with an I/O timeout.
+ */
+SSL_IMPORT SECStatus SSL_ForceHandshakeWithTimeout(PRFileDesc *fd,
+ PRIntervalTime timeout)
+{
+ if (SECSuccess != ssl_SetTimeout(fd, timeout)) {
+ return SECFailure;
+ }
+ return SSL_ForceHandshake(fd);
+}
+
+
+/************************************************************************/
+
+/*
+** Grow a buffer to hold newLen bytes of data.
+** Called for both recv buffers and xmit buffers.
+** Caller must hold xmitBufLock or recvBufLock, as appropriate.
+*/
+SECStatus
+sslBuffer_Grow(sslBuffer *b, unsigned int newLen)
+{
+ newLen = PR_MAX(newLen, MAX_FRAGMENT_LENGTH + 2048);
+ if (newLen > b->space) {
+ unsigned char *newBuf;
+ if (b->buf) {
+ newBuf = (unsigned char *) PORT_Realloc(b->buf, newLen);
+ } else {
+ newBuf = (unsigned char *) PORT_Alloc(newLen);
+ }
+ if (!newBuf) {
+ return SECFailure;
+ }
+ SSL_TRC(10, ("%d: SSL: grow buffer from %d to %d",
+ SSL_GETPID(), b->space, newLen));
+ b->buf = newBuf;
+ b->space = newLen;
+ }
+ return SECSuccess;
+}
+
+SECStatus
+sslBuffer_Append(sslBuffer *b, const void * data, unsigned int len)
+{
+ unsigned int newLen = b->len + len;
+ SECStatus rv;
+
+ rv = sslBuffer_Grow(b, newLen);
+ if (rv != SECSuccess)
+ return rv;
+ PORT_Memcpy(b->buf + b->len, data, len);
+ b->len += len;
+ return SECSuccess;
+}
+
+/*
+** Save away write data that is trying to be written before the security
+** handshake has been completed. When the handshake is completed, we will
+** flush this data out.
+** Caller must hold xmitBufLock
+*/
+SECStatus
+ssl_SaveWriteData(sslSocket *ss, const void *data, unsigned int len)
+{
+ SECStatus rv;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+ rv = sslBuffer_Append(&ss->pendingBuf, data, len);
+ SSL_TRC(5, ("%d: SSL[%d]: saving %u bytes of data (%u total saved so far)",
+ SSL_GETPID(), ss->fd, len, ss->pendingBuf.len));
+ return rv;
+}
+
+/*
+** Send saved write data. This will flush out data sent prior to a
+** complete security handshake. Hopefully there won't be too much of it.
+** Returns count of the bytes sent, NOT a SECStatus.
+** Caller must hold xmitBufLock
+*/
+int
+ssl_SendSavedWriteData(sslSocket *ss)
+{
+ int rv = 0;
+
+ PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss) );
+ if (ss->pendingBuf.len != 0) {
+ SSL_TRC(5, ("%d: SSL[%d]: sending %d bytes of saved data",
+ SSL_GETPID(), ss->fd, ss->pendingBuf.len));
+ rv = ssl_DefSend(ss, ss->pendingBuf.buf, ss->pendingBuf.len, 0);
+ if (rv < 0) {
+ return rv;
+ }
+ ss->pendingBuf.len -= rv;
+ if (ss->pendingBuf.len > 0 && rv > 0) {
+ /* UGH !! This shifts the whole buffer down by copying it */
+ PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
+ ss->pendingBuf.len);
+ }
+ }
+ return rv;
+}
+
+/************************************************************************/
+
+/*
+** Receive some application data on a socket. Reads SSL records from the input
+** stream, decrypts them and then copies them to the output buffer.
+** Called from ssl_SecureRecv() below.
+**
+** Caller does NOT hold 1stHandshakeLock because that handshake is over.
+** Caller doesn't call this until initial handshake is complete.
+** For SSLv2, there is no subsequent handshake.
+** For SSLv3, the call to ssl3_GatherAppDataRecord may encounter handshake
+** messages from a subsequent handshake.
+**
+** This code is similar to, and easily confused with,
+** ssl_GatherRecord1stHandshake() in sslcon.c
+*/
+static int
+DoRecv(sslSocket *ss, unsigned char *out, int len, int flags)
+{
+ int rv;
+ int amount;
+ int available;
+
+ ssl_GetRecvBufLock(ss);
+
+ available = ss->gs.writeOffset - ss->gs.readOffset;
+ if (available == 0) {
+ /* Get some more data */
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
+ /* Wait for application data to arrive. */
+ rv = ssl3_GatherAppDataRecord(ss, 0);
+ } else {
+ /* See if we have a complete record */
+ rv = ssl2_GatherRecord(ss, 0);
+ }
+ if (rv <= 0) {
+ if (rv == 0) {
+ /* EOF */
+ SSL_TRC(10, ("%d: SSL[%d]: ssl_recv EOF",
+ SSL_GETPID(), ss->fd));
+ goto done;
+ }
+ if ((rv != SECWouldBlock) &&
+ (PR_GetError() != PR_WOULD_BLOCK_ERROR)) {
+ /* Some random error */
+ goto done;
+ }
+
+ /*
+ ** Gather record is blocked waiting for more record data to
+ ** arrive. Try to process what we have already received
+ */
+ } else {
+ /* Gather record has finished getting a complete record */
+ }
+
+ /* See if any clear data is now available */
+ available = ss->gs.writeOffset - ss->gs.readOffset;
+ if (available == 0) {
+ /*
+ ** No partial data is available. Force error code to
+ ** EWOULDBLOCK so that caller will try again later. Note
+ ** that the error code is probably EWOULDBLOCK already,
+ ** but if it isn't (for example, if we received a zero
+ ** length record) then this will force it to be correct.
+ */
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ rv = SECFailure;
+ goto done;
+ }
+ SSL_TRC(30, ("%d: SSL[%d]: partial data ready, available=%d",
+ SSL_GETPID(), ss->fd, available));
+ }
+
+ /* Dole out clear data to reader */
+ amount = PR_MIN(len, available);
+ PORT_Memcpy(out, ss->gs.buf.buf + ss->gs.readOffset, amount);
+ if (!(flags & PR_MSG_PEEK)) {
+ ss->gs.readOffset += amount;
+ }
+ PORT_Assert(ss->gs.readOffset <= ss->gs.writeOffset);
+ rv = amount;
+
+ SSL_TRC(30, ("%d: SSL[%d]: amount=%d available=%d",
+ SSL_GETPID(), ss->fd, amount, available));
+ PRINT_BUF(4, (ss, "DoRecv receiving plaintext:", out, amount));
+
+done:
+ ssl_ReleaseRecvBufLock(ss);
+ return rv;
+}
+
+/************************************************************************/
+
+/*
+** Return SSLKEAType derived from cert's Public Key algorithm info.
+*/
+SSLKEAType
+NSS_FindCertKEAType(CERTCertificate * cert)
+{
+ SSLKEAType keaType = kt_null;
+ int tag;
+
+ if (!cert) goto loser;
+
+ tag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
+
+ switch (tag) {
+ case SEC_OID_X500_RSA_ENCRYPTION:
+ case SEC_OID_PKCS1_RSA_ENCRYPTION:
+ keaType = kt_rsa;
+ break;
+ case SEC_OID_X942_DIFFIE_HELMAN_KEY:
+ keaType = kt_dh;
+ break;
+#ifdef NSS_ENABLE_ECC
+ case SEC_OID_ANSIX962_EC_PUBLIC_KEY:
+ keaType = kt_ecdh;
+ break;
+#endif /* NSS_ENABLE_ECC */
+ default:
+ keaType = kt_null;
+ }
+
+ loser:
+
+ return keaType;
+}
+
+static const PRCallOnceType pristineCallOnce;
+static PRCallOnceType setupServerCAListOnce;
+
+static SECStatus serverCAListShutdown(void* appData, void* nssData)
+{
+ PORT_Assert(ssl3_server_ca_list);
+ if (ssl3_server_ca_list) {
+ CERT_FreeDistNames(ssl3_server_ca_list);
+ ssl3_server_ca_list = NULL;
+ }
+ setupServerCAListOnce = pristineCallOnce;
+ return SECSuccess;
+}
+
+static PRStatus serverCAListSetup(void *arg)
+{
+ CERTCertDBHandle *dbHandle = (CERTCertDBHandle *)arg;
+ SECStatus rv = NSS_RegisterShutdown(serverCAListShutdown, NULL);
+ PORT_Assert(SECSuccess == rv);
+ if (SECSuccess == rv) {
+ ssl3_server_ca_list = CERT_GetSSLCACerts(dbHandle);
+ return PR_SUCCESS;
+ }
+ return PR_FAILURE;
+}
+
+SECStatus
+ssl_ConfigSecureServer(sslSocket *ss, CERTCertificate *cert,
+ const CERTCertificateList *certChain,
+ ssl3KeyPair *keyPair, SSLKEAType kea)
+{
+ CERTCertificateList *localCertChain = NULL;
+ sslServerCerts *sc = ss->serverCerts + kea;
+
+ /* load the server certificate */
+ if (sc->serverCert != NULL) {
+ CERT_DestroyCertificate(sc->serverCert);
+ sc->serverCert = NULL;
+ sc->serverKeyBits = 0;
+ }
+ /* load the server cert chain */
+ if (sc->serverCertChain != NULL) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ sc->serverCertChain = NULL;
+ }
+ if (cert) {
+ sc->serverCert = CERT_DupCertificate(cert);
+ /* get the size of the cert's public key, and remember it */
+ sc->serverKeyBits = SECKEY_PublicKeyStrengthInBits(keyPair->pubKey);
+ if (!certChain) {
+ localCertChain =
+ CERT_CertChainFromCert(sc->serverCert, certUsageSSLServer,
+ PR_TRUE);
+ if (!localCertChain)
+ goto loser;
+ }
+ sc->serverCertChain = (certChain) ? CERT_DupCertList(certChain) :
+ localCertChain;
+ if (!sc->serverCertChain) {
+ goto loser;
+ }
+ localCertChain = NULL; /* consumed */
+ }
+
+ /* get keyPair */
+ if (sc->serverKeyPair != NULL) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ sc->serverKeyPair = NULL;
+ }
+ if (keyPair) {
+ SECKEY_CacheStaticFlags(keyPair->privKey);
+ sc->serverKeyPair = ssl3_GetKeyPairRef(keyPair);
+ }
+ if (kea == kt_rsa && cert && sc->serverKeyBits > 512 &&
+ !ss->opt.noStepDown && !ss->stepDownKeyPair) {
+ if (ssl3_CreateRSAStepDownKeys(ss) != SECSuccess) {
+ goto loser;
+ }
+ }
+ return SECSuccess;
+
+loser:
+ if (localCertChain) {
+ CERT_DestroyCertificateList(localCertChain);
+ }
+ if (sc->serverCert != NULL) {
+ CERT_DestroyCertificate(sc->serverCert);
+ sc->serverCert = NULL;
+ }
+ if (sc->serverCertChain != NULL) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ sc->serverCertChain = NULL;
+ }
+ if (sc->serverKeyPair != NULL) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ sc->serverKeyPair = NULL;
+ }
+ return SECFailure;
+}
+
+/* XXX need to protect the data that gets changed here.!! */
+
+SECStatus
+SSL_ConfigSecureServer(PRFileDesc *fd, CERTCertificate *cert,
+ SECKEYPrivateKey *key, SSL3KEAType kea)
+{
+
+ return SSL_ConfigSecureServerWithCertChain(fd, cert, NULL, key, kea);
+}
+
+SECStatus
+SSL_ConfigSecureServerWithCertChain(PRFileDesc *fd, CERTCertificate *cert,
+ const CERTCertificateList *certChainOpt,
+ SECKEYPrivateKey *key, SSL3KEAType kea)
+{
+ sslSocket *ss;
+ SECKEYPublicKey *pubKey = NULL;
+ ssl3KeyPair *keyPair = NULL;
+ SECStatus rv = SECFailure;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ return SECFailure;
+ }
+
+ /* Both key and cert must have a value or be NULL */
+ /* Passing a value of NULL will turn off key exchange algorithms that were
+ * previously turned on */
+ if (!cert != !key) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ /* make sure the key exchange is recognized */
+ if ((kea >= kt_kea_size) || (kea < kt_null)) {
+ PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
+ return SECFailure;
+ }
+
+ if (kea != NSS_FindCertKEAType(cert)) {
+ PORT_SetError(SSL_ERROR_CERT_KEA_MISMATCH);
+ return SECFailure;
+ }
+
+ if (cert) {
+ /* get the size of the cert's public key, and remember it */
+ pubKey = CERT_ExtractPublicKey(cert);
+ if (!pubKey)
+ return SECFailure;
+ }
+
+ if (key) {
+ SECKEYPrivateKey * keyCopy = NULL;
+ CK_MECHANISM_TYPE keyMech = CKM_INVALID_MECHANISM;
+
+ if (key->pkcs11Slot) {
+ PK11SlotInfo * bestSlot;
+ bestSlot = PK11_ReferenceSlot(key->pkcs11Slot);
+ if (bestSlot) {
+ keyCopy = PK11_CopyTokenPrivKeyToSessionPrivKey(bestSlot, key);
+ PK11_FreeSlot(bestSlot);
+ }
+ }
+ if (keyCopy == NULL)
+ keyMech = PK11_MapSignKeyType(key->keyType);
+ if (keyMech != CKM_INVALID_MECHANISM) {
+ PK11SlotInfo * bestSlot;
+ /* XXX Maybe should be bestSlotMultiple? */
+ bestSlot = PK11_GetBestSlot(keyMech, NULL /* wincx */);
+ if (bestSlot) {
+ keyCopy = PK11_CopyTokenPrivKeyToSessionPrivKey(bestSlot, key);
+ PK11_FreeSlot(bestSlot);
+ }
+ }
+ if (keyCopy == NULL)
+ keyCopy = SECKEY_CopyPrivateKey(key);
+ if (keyCopy == NULL)
+ goto loser;
+ keyPair = ssl3_NewKeyPair(keyCopy, pubKey);
+ if (keyPair == NULL) {
+ SECKEY_DestroyPrivateKey(keyCopy);
+ goto loser;
+ }
+ pubKey = NULL; /* adopted by serverKeyPair */
+ }
+ if (ssl_ConfigSecureServer(ss, cert, certChainOpt,
+ keyPair, kea) == SECFailure) {
+ goto loser;
+ }
+
+ /* Only do this once because it's global. */
+ if (PR_SUCCESS == PR_CallOnceWithArg(&setupServerCAListOnce,
+ &serverCAListSetup,
+ (void *)(ss->dbHandle))) {
+ rv = SECSuccess;
+ }
+
+loser:
+ if (keyPair) {
+ ssl3_FreeKeyPair(keyPair);
+ }
+ if (pubKey) {
+ SECKEY_DestroyPublicKey(pubKey);
+ pubKey = NULL;
+ }
+ return rv;
+}
+
+/************************************************************************/
+
+SECStatus
+ssl_CreateSecurityInfo(sslSocket *ss)
+{
+ SECStatus status;
+
+ /* initialize sslv2 socket to send data in the clear. */
+ ssl2_UseClearSendFunc(ss);
+
+ ss->sec.blockSize = 1;
+ ss->sec.blockShift = 0;
+
+ ssl_GetXmitBufLock(ss);
+ status = sslBuffer_Grow(&ss->sec.writeBuf, 4096);
+ ssl_ReleaseXmitBufLock(ss);
+
+ return status;
+}
+
+SECStatus
+ssl_CopySecurityInfo(sslSocket *ss, sslSocket *os)
+{
+ ss->sec.send = os->sec.send;
+ ss->sec.isServer = os->sec.isServer;
+ ss->sec.keyBits = os->sec.keyBits;
+ ss->sec.secretKeyBits = os->sec.secretKeyBits;
+
+ ss->sec.peerCert = CERT_DupCertificate(os->sec.peerCert);
+ if (os->sec.peerCert && !ss->sec.peerCert)
+ goto loser;
+
+ ss->sec.cache = os->sec.cache;
+ ss->sec.uncache = os->sec.uncache;
+
+ /* we don't dup the connection info. */
+
+ ss->sec.sendSequence = os->sec.sendSequence;
+ ss->sec.rcvSequence = os->sec.rcvSequence;
+
+ if (os->sec.hash && os->sec.hashcx) {
+ ss->sec.hash = os->sec.hash;
+ ss->sec.hashcx = os->sec.hash->clone(os->sec.hashcx);
+ if (os->sec.hashcx && !ss->sec.hashcx)
+ goto loser;
+ } else {
+ ss->sec.hash = NULL;
+ ss->sec.hashcx = NULL;
+ }
+
+ SECITEM_CopyItem(0, &ss->sec.sendSecret, &os->sec.sendSecret);
+ if (os->sec.sendSecret.data && !ss->sec.sendSecret.data)
+ goto loser;
+ SECITEM_CopyItem(0, &ss->sec.rcvSecret, &os->sec.rcvSecret);
+ if (os->sec.rcvSecret.data && !ss->sec.rcvSecret.data)
+ goto loser;
+
+ /* XXX following code is wrong if either cx != 0 */
+ PORT_Assert(os->sec.readcx == 0);
+ PORT_Assert(os->sec.writecx == 0);
+ ss->sec.readcx = os->sec.readcx;
+ ss->sec.writecx = os->sec.writecx;
+ ss->sec.destroy = 0;
+
+ ss->sec.enc = os->sec.enc;
+ ss->sec.dec = os->sec.dec;
+
+ ss->sec.blockShift = os->sec.blockShift;
+ ss->sec.blockSize = os->sec.blockSize;
+
+ return SECSuccess;
+
+loser:
+ return SECFailure;
+}
+
+/* Reset sec back to its initial state.
+** Caller holds any relevant locks.
+*/
+void
+ssl_ResetSecurityInfo(sslSecurityInfo *sec, PRBool doMemset)
+{
+ /* Destroy MAC */
+ if (sec->hash && sec->hashcx) {
+ (*sec->hash->destroy)(sec->hashcx, PR_TRUE);
+ sec->hashcx = NULL;
+ sec->hash = NULL;
+ }
+ SECITEM_ZfreeItem(&sec->sendSecret, PR_FALSE);
+ SECITEM_ZfreeItem(&sec->rcvSecret, PR_FALSE);
+
+ /* Destroy ciphers */
+ if (sec->destroy) {
+ (*sec->destroy)(sec->readcx, PR_TRUE);
+ (*sec->destroy)(sec->writecx, PR_TRUE);
+ sec->readcx = NULL;
+ sec->writecx = NULL;
+ } else {
+ PORT_Assert(sec->readcx == 0);
+ PORT_Assert(sec->writecx == 0);
+ }
+ sec->readcx = 0;
+ sec->writecx = 0;
+
+ if (sec->localCert) {
+ CERT_DestroyCertificate(sec->localCert);
+ sec->localCert = NULL;
+ }
+ if (sec->peerCert) {
+ CERT_DestroyCertificate(sec->peerCert);
+ sec->peerCert = NULL;
+ }
+ if (sec->peerKey) {
+ SECKEY_DestroyPublicKey(sec->peerKey);
+ sec->peerKey = NULL;
+ }
+
+ /* cleanup the ci */
+ if (sec->ci.sid != NULL) {
+ ssl_FreeSID(sec->ci.sid);
+ }
+ PORT_ZFree(sec->ci.sendBuf.buf, sec->ci.sendBuf.space);
+ if (doMemset) {
+ memset(&sec->ci, 0, sizeof sec->ci);
+ }
+
+}
+
+/*
+** Called from SSL_ResetHandshake (above), and
+** from ssl_FreeSocket in sslsock.c
+** Caller should hold relevant locks (e.g. XmitBufLock)
+*/
+void
+ssl_DestroySecurityInfo(sslSecurityInfo *sec)
+{
+ ssl_ResetSecurityInfo(sec, PR_FALSE);
+
+ PORT_ZFree(sec->writeBuf.buf, sec->writeBuf.space);
+ sec->writeBuf.buf = 0;
+
+ memset(sec, 0, sizeof *sec);
+}
+
+/************************************************************************/
+
+int
+ssl_SecureConnect(sslSocket *ss, const PRNetAddr *sa)
+{
+ PRFileDesc *osfd = ss->fd->lower;
+ int rv;
+
+ if ( ss->opt.handshakeAsServer ) {
+ ss->securityHandshake = ssl2_BeginServerHandshake;
+ ss->handshaking = sslHandshakingAsServer;
+ } else {
+ ss->securityHandshake = ssl2_BeginClientHandshake;
+ ss->handshaking = sslHandshakingAsClient;
+ }
+
+ /* connect to server */
+ rv = osfd->methods->connect(osfd, sa, ss->cTimeout);
+ if (rv == PR_SUCCESS) {
+ ss->TCPconnected = 1;
+ } else {
+ int err = PR_GetError();
+ SSL_DBG(("%d: SSL[%d]: connect failed, errno=%d",
+ SSL_GETPID(), ss->fd, err));
+ if (err == PR_IS_CONNECTED_ERROR) {
+ ss->TCPconnected = 1;
+ }
+ }
+
+ SSL_TRC(5, ("%d: SSL[%d]: secure connect completed, rv == %d",
+ SSL_GETPID(), ss->fd, rv));
+ return rv;
+}
+
+/*
+ * The TLS 1.2 RFC 5246, Section 7.2.1 says:
+ *
+ * Unless some other fatal alert has been transmitted, each party is
+ * required to send a close_notify alert before closing the write side
+ * of the connection. The other party MUST respond with a close_notify
+ * alert of its own and close down the connection immediately,
+ * discarding any pending writes. It is not required for the initiator
+ * of the close to wait for the responding close_notify alert before
+ * closing the read side of the connection.
+ *
+ * The second sentence requires that we send a close_notify alert when we
+ * have received a close_notify alert. In practice, all SSL implementations
+ * close the socket immediately after sending a close_notify alert (which is
+ * allowed by the third sentence), so responding with a close_notify alert
+ * would result in a write failure with the ECONNRESET error. This is why
+ * we don't respond with a close_notify alert.
+ *
+ * Also, in the unlikely event that the TCP pipe is full and the peer stops
+ * reading, the SSL3_SendAlert call in ssl_SecureClose and ssl_SecureShutdown
+ * may block indefinitely in blocking mode, and may fail (without retrying)
+ * in non-blocking mode.
+ */
+
+int
+ssl_SecureClose(sslSocket *ss)
+{
+ int rv;
+
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ !(ss->shutdownHow & ssl_SHUTDOWN_SEND) &&
+ ss->firstHsDone &&
+ !ss->recvdCloseNotify &&
+ ss->ssl3.initialized) {
+
+ /* We don't want the final alert to be Nagle delayed. */
+ if (!ss->delayDisabled) {
+ ssl_EnableNagleDelay(ss, PR_FALSE);
+ ss->delayDisabled = 1;
+ }
+
+ (void) SSL3_SendAlert(ss, alert_warning, close_notify);
+ }
+ rv = ssl_DefClose(ss);
+ return rv;
+}
+
+/* Caller handles all locking */
+int
+ssl_SecureShutdown(sslSocket *ss, int nsprHow)
+{
+ PRFileDesc *osfd = ss->fd->lower;
+ int rv;
+ PRIntn sslHow = nsprHow + 1;
+
+ if ((unsigned)nsprHow > PR_SHUTDOWN_BOTH) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return PR_FAILURE;
+ }
+
+ if ((sslHow & ssl_SHUTDOWN_SEND) != 0 &&
+ ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ !(ss->shutdownHow & ssl_SHUTDOWN_SEND) &&
+ ss->firstHsDone &&
+ !ss->recvdCloseNotify &&
+ ss->ssl3.initialized) {
+
+ (void) SSL3_SendAlert(ss, alert_warning, close_notify);
+ }
+
+ rv = osfd->methods->shutdown(osfd, nsprHow);
+
+ ss->shutdownHow |= sslHow;
+
+ return rv;
+}
+
+/************************************************************************/
+
+
+int
+ssl_SecureRecv(sslSocket *ss, unsigned char *buf, int len, int flags)
+{
+ sslSecurityInfo *sec;
+ int rv = 0;
+
+ sec = &ss->sec;
+
+ if (ss->shutdownHow & ssl_SHUTDOWN_RCV) {
+ PORT_SetError(PR_SOCKET_SHUTDOWN_ERROR);
+ return PR_FAILURE;
+ }
+ if (flags & ~PR_MSG_PEEK) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return PR_FAILURE;
+ }
+
+ if (!ssl_SocketIsBlocking(ss) && !ss->opt.fdx) {
+ ssl_GetXmitBufLock(ss);
+ if (ss->pendingBuf.len != 0) {
+ rv = ssl_SendSavedWriteData(ss);
+ if ((rv < 0) && (PORT_GetError() != PR_WOULD_BLOCK_ERROR)) {
+ ssl_ReleaseXmitBufLock(ss);
+ return SECFailure;
+ }
+ }
+ ssl_ReleaseXmitBufLock(ss);
+ }
+
+ rv = 0;
+ /* If any of these is non-zero, the initial handshake is not done. */
+ if (!ss->firstHsDone) {
+ ssl_Get1stHandshakeLock(ss);
+ if (ss->handshake || ss->nextHandshake || ss->securityHandshake) {
+ rv = ssl_Do1stHandshake(ss);
+ }
+ ssl_Release1stHandshakeLock(ss);
+ }
+ if (rv < 0) {
+ return rv;
+ }
+
+ if (len == 0) return 0;
+
+ rv = DoRecv(ss, (unsigned char*) buf, len, flags);
+ SSL_TRC(2, ("%d: SSL[%d]: recving %d bytes securely (errno=%d)",
+ SSL_GETPID(), ss->fd, rv, PORT_GetError()));
+ return rv;
+}
+
+int
+ssl_SecureRead(sslSocket *ss, unsigned char *buf, int len)
+{
+ return ssl_SecureRecv(ss, buf, len, 0);
+}
+
+/* Caller holds the SSL Socket's write lock. SSL_LOCK_WRITER(ss) */
+int
+ssl_SecureSend(sslSocket *ss, const unsigned char *buf, int len, int flags)
+{
+ int rv = 0;
+
+ SSL_TRC(2, ("%d: SSL[%d]: SecureSend: sending %d bytes",
+ SSL_GETPID(), ss->fd, len));
+
+ if (ss->shutdownHow & ssl_SHUTDOWN_SEND) {
+ PORT_SetError(PR_SOCKET_SHUTDOWN_ERROR);
+ rv = PR_FAILURE;
+ goto done;
+ }
+ if (flags) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ rv = PR_FAILURE;
+ goto done;
+ }
+
+ ssl_GetXmitBufLock(ss);
+ if (ss->pendingBuf.len != 0) {
+ PORT_Assert(ss->pendingBuf.len > 0);
+ rv = ssl_SendSavedWriteData(ss);
+ if (rv >= 0 && ss->pendingBuf.len != 0) {
+ PORT_Assert(ss->pendingBuf.len > 0);
+ PORT_SetError(PR_WOULD_BLOCK_ERROR);
+ rv = SECFailure;
+ }
+ }
+ ssl_ReleaseXmitBufLock(ss);
+ if (rv < 0) {
+ goto done;
+ }
+
+ if (len > 0)
+ ss->writerThread = PR_GetCurrentThread();
+ /* If any of these is non-zero, the initial handshake is not done. */
+ if (!ss->firstHsDone) {
+ PRBool canFalseStart = PR_FALSE;
+ ssl_Get1stHandshakeLock(ss);
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
+ ssl_GetSSL3HandshakeLock(ss);
+ if ((ss->ssl3.hs.ws == wait_change_cipher ||
+ ss->ssl3.hs.ws == wait_finished ||
+ ss->ssl3.hs.ws == wait_new_session_ticket) &&
+ ssl3_CanFalseStart(ss)) {
+ canFalseStart = PR_TRUE;
+ }
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ }
+ if (!canFalseStart &&
+ (ss->handshake || ss->nextHandshake || ss->securityHandshake)) {
+ rv = ssl_Do1stHandshake(ss);
+ }
+ ssl_Release1stHandshakeLock(ss);
+ }
+ if (rv < 0) {
+ ss->writerThread = NULL;
+ goto done;
+ }
+
+ /* Check for zero length writes after we do housekeeping so we make forward
+ * progress.
+ */
+ if (len == 0) {
+ rv = 0;
+ goto done;
+ }
+ PORT_Assert(buf != NULL);
+ if (!buf) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ rv = PR_FAILURE;
+ goto done;
+ }
+
+ /* Send out the data using one of these functions:
+ * ssl2_SendClear, ssl2_SendStream, ssl2_SendBlock,
+ * ssl3_SendApplicationData
+ */
+ ssl_GetXmitBufLock(ss);
+ rv = (*ss->sec.send)(ss, buf, len, flags);
+ ssl_ReleaseXmitBufLock(ss);
+ ss->writerThread = NULL;
+done:
+ if (rv < 0) {
+ SSL_TRC(2, ("%d: SSL[%d]: SecureSend: returning %d count, error %d",
+ SSL_GETPID(), ss->fd, rv, PORT_GetError()));
+ } else {
+ SSL_TRC(2, ("%d: SSL[%d]: SecureSend: returning %d count",
+ SSL_GETPID(), ss->fd, rv));
+ }
+ return rv;
+}
+
+int
+ssl_SecureWrite(sslSocket *ss, const unsigned char *buf, int len)
+{
+ return ssl_SecureSend(ss, buf, len, 0);
+}
+
+SECStatus
+SSL_BadCertHook(PRFileDesc *fd, SSLBadCertHandler f, void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSLBadCertHook",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ss->handleBadCert = f;
+ ss->badCertArg = arg;
+
+ return SECSuccess;
+}
+
+/*
+ * Allow the application to pass the url or hostname into the SSL library
+ * so that we can do some checking on it. It will be used for the value in
+ * SNI extension of client hello message.
+ */
+SECStatus
+SSL_SetURL(PRFileDesc *fd, const char *url)
+{
+ sslSocket * ss = ssl_FindSocket(fd);
+ SECStatus rv = SECSuccess;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSLSetURL",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ if ( ss->url ) {
+ PORT_Free((void *)ss->url); /* CONST */
+ }
+
+ ss->url = (const char *)PORT_Strdup(url);
+ if ( ss->url == NULL ) {
+ rv = SECFailure;
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return rv;
+}
+
+/*
+ * Allow the application to pass the set of trust anchors
+ */
+SECStatus
+SSL_SetTrustAnchors(PRFileDesc *fd, CERTCertList *certList)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ PR_NOT_REACHED("not implemented");
+ return SECFailure;
+#if 0
+ sslSocket * ss = ssl_FindSocket(fd);
+ CERTDistNames *names = NULL;
+
+ if (!certList) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetTrustAnchors",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ names = CERT_DistNamesFromCertList(certList);
+ if (names == NULL) {
+ return SECFailure;
+ }
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+ if (ss->ssl3.ca_list) {
+ CERT_FreeDistNames(ss->ssl3.ca_list);
+ }
+ ss->ssl3.ca_list = names;
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
+#endif
+}
+
+/*
+** Returns Negative number on error, zero or greater on success.
+** Returns the amount of data immediately available to be read.
+*/
+int
+SSL_DataPending(PRFileDesc *fd)
+{
+ sslSocket *ss;
+ int rv = 0;
+
+ ss = ssl_FindSocket(fd);
+
+ if (ss && ss->opt.useSecurity) {
+ ssl_GetRecvBufLock(ss);
+ rv = ss->gs.writeOffset - ss->gs.readOffset;
+ ssl_ReleaseRecvBufLock(ss);
+ }
+
+ return rv;
+}
+
+SECStatus
+SSL_InvalidateSession(PRFileDesc *fd)
+{
+ sslSocket * ss = ssl_FindSocket(fd);
+ SECStatus rv = SECFailure;
+
+ if (ss) {
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ if (ss->sec.ci.sid && ss->sec.uncache) {
+ ss->sec.uncache(ss->sec.ci.sid);
+ rv = SECSuccess;
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+ }
+ return rv;
+}
+
+SECItem *
+SSL_GetSessionID(PRFileDesc *fd)
+{
+ sslSocket * ss;
+ SECItem * item = NULL;
+
+ ss = ssl_FindSocket(fd);
+ if (ss) {
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ if (ss->opt.useSecurity && ss->firstHsDone && ss->sec.ci.sid) {
+ item = (SECItem *)PORT_Alloc(sizeof(SECItem));
+ if (item) {
+ sslSessionID * sid = ss->sec.ci.sid;
+ if (sid->version < SSL_LIBRARY_VERSION_3_0) {
+ item->len = SSL2_SESSIONID_BYTES;
+ item->data = (unsigned char*)PORT_Alloc(item->len);
+ PORT_Memcpy(item->data, sid->u.ssl2.sessionID, item->len);
+ } else {
+ item->len = sid->u.ssl3.sessionIDLength;
+ item->data = (unsigned char*)PORT_Alloc(item->len);
+ PORT_Memcpy(item->data, sid->u.ssl3.sessionID, item->len);
+ }
+ }
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+ }
+ return item;
+}
+
+SECStatus
+SSL_CertDBHandleSet(PRFileDesc *fd, CERTCertDBHandle *dbHandle)
+{
+ sslSocket * ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss)
+ return SECFailure;
+ if (!dbHandle) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ss->dbHandle = dbHandle;
+ return SECSuccess;
+}
+
+/*
+ * attempt to restart the handshake after asynchronously handling
+ * a request for the client's certificate.
+ *
+ * inputs:
+ * cert Client cert chosen by application.
+ * Note: ssl takes this reference, and does not bump the
+ * reference count. The caller should drop its reference
+ * without calling CERT_DestroyCertificate after calling this
+ * function.
+ *
+ * key Private key associated with cert. This function takes
+ * ownership of the private key, so the caller should drop its
+ * reference without destroying the private key after this
+ * function returns.
+ *
+ * certChain Chain of signers for cert.
+ * Note: ssl takes this reference, and does not copy the chain.
+ * The caller should drop its reference without destroying the
+ * chain. SSL will free the chain when it is done with it.
+ *
+ * Return value: XXX
+ *
+ * XXX This code only works on the initial handshake on a connection, XXX
+ * It does not work on a subsequent handshake (redo).
+ */
+SECStatus
+SSL_RestartHandshakeAfterCertReq(PRFileDesc * fd,
+ CERTCertificate * cert,
+ SECKEYPrivateKey * key,
+ CERTCertificateList *certChain)
+{
+ sslSocket * ss = ssl_FindSocket(fd);
+ SECStatus ret;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_RestartHandshakeAfterCertReq",
+ SSL_GETPID(), fd));
+ if (cert) {
+ CERT_DestroyCertificate(cert);
+ }
+ if (key) {
+ SECKEY_DestroyPrivateKey(key);
+ }
+ if (certChain) {
+ CERT_DestroyCertificateList(certChain);
+ }
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss); /************************************/
+
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0) {
+ ret = ssl3_RestartHandshakeAfterCertReq(ss, cert, key, certChain);
+ } else {
+ if (certChain != NULL) {
+ CERT_DestroyCertificateList(certChain);
+ }
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ ret = SECFailure;
+ }
+
+ ssl_Release1stHandshakeLock(ss); /************************************/
+ return ret;
+}
+
+SECStatus
+SSL_RestartHandshakeAfterChannelIDReq(PRFileDesc * fd,
+ SECKEYPublicKey * channelIDPub,
+ SECKEYPrivateKey *channelID)
+{
+ sslSocket * ss = ssl_FindSocket(fd);
+ SECStatus ret;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in"
+ " SSL_RestartHandshakeAfterChannelIDReq",
+ SSL_GETPID(), fd));
+ goto loser;
+ }
+
+
+ ssl_Get1stHandshakeLock(ss);
+
+ if (ss->version < SSL_LIBRARY_VERSION_3_0) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ ssl_Release1stHandshakeLock(ss);
+ goto loser;
+ }
+
+ ret = ssl3_RestartHandshakeAfterChannelIDReq(ss, channelIDPub,
+ channelID);
+ ssl_Release1stHandshakeLock(ss);
+
+ return ret;
+
+loser:
+ SECKEY_DestroyPublicKey(channelIDPub);
+ SECKEY_DestroyPrivateKey(channelID);
+ return SECFailure;
+}
+
+/* DO NOT USE. This function was exported in ssl.def with the wrong signature;
+ * this implementation exists to maintain link-time compatibility.
+ */
+int
+SSL_RestartHandshakeAfterServerCert(sslSocket * ss)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ return -1;
+}
+
+/* See documentation in ssl.h */
+SECStatus
+SSL_AuthCertificateComplete(PRFileDesc *fd, PRErrorCode error)
+{
+ SECStatus rv;
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_AuthCertificateComplete",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+
+ if (!ss->ssl3.initialized) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ } else if (ss->version < SSL_LIBRARY_VERSION_3_0) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ rv = SECFailure;
+ } else {
+ rv = ssl3_AuthCertificateComplete(ss, error);
+ }
+
+ ssl_Release1stHandshakeLock(ss);
+
+ return rv;
+}
+
+/* For more info see ssl.h */
+SECStatus
+SSL_SNISocketConfigHook(PRFileDesc *fd, SSLSNISocketConfig func,
+ void *arg)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SNISocketConfigHook",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ ss->sniSocketConfig = func;
+ ss->sniSocketConfigArg = arg;
+ return SECSuccess;
+}
diff --git a/chromium/net/third_party/nss/ssl/sslsnce.c b/chromium/net/third_party/nss/ssl/sslsnce.c
new file mode 100644
index 00000000000..b0446adc17f
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslsnce.c
@@ -0,0 +1,2213 @@
+/* This file implements the SERVER Session ID cache.
+ * NOTE: The contents of this file are NOT used by the client.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Note: ssl_FreeSID() in sslnonce.c gets used for both client and server
+ * cache sids!
+ *
+ * About record locking among different server processes:
+ *
+ * All processes that are part of the same conceptual server (serving on
+ * the same address and port) MUST share a common SSL session cache.
+ * This code makes the content of the shared cache accessible to all
+ * processes on the same "server". This code works on Unix and Win32 only.
+ *
+ * We use NSPR anonymous shared memory and move data to & from shared memory.
+ * We must do explicit locking of the records for all reads and writes.
+ * The set of Cache entries are divided up into "sets" of 128 entries.
+ * Each set is protected by a lock. There may be one or more sets protected
+ * by each lock. That is, locks to sets are 1:N.
+ * There is one lock for the entire cert cache.
+ * There is one lock for the set of wrapped sym wrap keys.
+ *
+ * The anonymous shared memory is laid out as if it were declared like this:
+ *
+ * struct {
+ * cacheDescriptor desc;
+ * sidCacheLock sidCacheLocks[ numSIDCacheLocks];
+ * sidCacheLock keyCacheLock;
+ * sidCacheLock certCacheLock;
+ * sidCacheSet sidCacheSets[ numSIDCacheSets ];
+ * sidCacheEntry sidCacheData[ numSIDCacheEntries];
+ * certCacheEntry certCacheData[numCertCacheEntries];
+ * SSLWrappedSymWrappingKey keyCacheData[kt_kea_size][SSL_NUM_WRAP_MECHS];
+ * PRUint8 keyNameSuffix[SESS_TICKET_KEY_VAR_NAME_LEN]
+ * encKeyCacheEntry ticketEncKey; // Wrapped in non-bypass mode
+ * encKeyCacheEntry ticketMacKey; // Wrapped in non-bypass mode
+ * PRBool ticketKeysValid;
+ * sidCacheLock srvNameCacheLock;
+ * srvNameCacheEntry srvNameData[ numSrvNameCacheEntries ];
+ * } cacheMemCacheData;
+ */
+#include "seccomon.h"
+
+#if defined(XP_UNIX) || defined(XP_WIN32) || defined (XP_OS2) || defined(XP_BEOS)
+
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "pk11func.h"
+#include "base64.h"
+#include "keyhi.h"
+#ifdef NO_PKCS11_BYPASS
+#include "blapit.h"
+#include "sechash.h"
+#else
+#include "blapi.h"
+#endif
+
+#include <stdio.h>
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+
+#include <syslog.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include "unix_err.h"
+
+#else
+
+#ifdef XP_WIN32
+#include <wtypes.h>
+#include "win32err.h"
+#endif
+
+#endif
+#include <sys/types.h>
+
+#define SET_ERROR_CODE /* reminder */
+
+#include "nspr.h"
+#include "sslmutex.h"
+
+/*
+** Format of a cache entry in the shared memory.
+*/
+struct sidCacheEntryStr {
+/* 16 */ PRIPv6Addr addr; /* client's IP address */
+/* 4 */ PRUint32 creationTime;
+/* 4 */ PRUint32 lastAccessTime;
+/* 4 */ PRUint32 expirationTime;
+/* 2 */ PRUint16 version;
+/* 1 */ PRUint8 valid;
+/* 1 */ PRUint8 sessionIDLength;
+/* 32 */ PRUint8 sessionID[SSL3_SESSIONID_BYTES];
+/* 2 */ PRUint16 authAlgorithm;
+/* 2 */ PRUint16 authKeyBits;
+/* 2 */ PRUint16 keaType;
+/* 2 */ PRUint16 keaKeyBits;
+/* 72 - common header total */
+
+ union {
+ struct {
+/* 64 */ PRUint8 masterKey[SSL_MAX_MASTER_KEY_BYTES];
+/* 32 */ PRUint8 cipherArg[SSL_MAX_CYPHER_ARG_BYTES];
+
+/* 1 */ PRUint8 cipherType;
+/* 1 */ PRUint8 masterKeyLen;
+/* 1 */ PRUint8 keyBits;
+/* 1 */ PRUint8 secretKeyBits;
+/* 1 */ PRUint8 cipherArgLen;
+/*101 */} ssl2;
+
+ struct {
+/* 2 */ ssl3CipherSuite cipherSuite;
+/* 2 */ PRUint16 compression; /* SSLCompressionMethod */
+
+/* 52 */ ssl3SidKeys keys; /* keys, wrapped as needed. */
+
+/* 4 */ PRUint32 masterWrapMech;
+/* 4 */ SSL3KEAType exchKeyType;
+/* 4 */ PRInt32 certIndex;
+/* 4 */ PRInt32 srvNameIndex;
+/* 32 */ PRUint8 srvNameHash[SHA256_LENGTH]; /* SHA256 name hash */
+/*104 */} ssl3;
+/* force sizeof(sidCacheEntry) to be a multiple of cache line size */
+ struct {
+/*120 */ PRUint8 filler[120]; /* 72+120==192, a multiple of 16 */
+ } forceSize;
+ } u;
+};
+typedef struct sidCacheEntryStr sidCacheEntry;
+
+/* The length of this struct is supposed to be a power of 2, e.g. 4KB */
+struct certCacheEntryStr {
+ PRUint16 certLength; /* 2 */
+ PRUint16 sessionIDLength; /* 2 */
+ PRUint8 sessionID[SSL3_SESSIONID_BYTES]; /* 32 */
+ PRUint8 cert[SSL_MAX_CACHED_CERT_LEN]; /* 4060 */
+}; /* total 4096 */
+typedef struct certCacheEntryStr certCacheEntry;
+
+struct sidCacheLockStr {
+ PRUint32 timeStamp;
+ sslMutex mutex;
+ sslPID pid;
+};
+typedef struct sidCacheLockStr sidCacheLock;
+
+struct sidCacheSetStr {
+ PRIntn next;
+};
+typedef struct sidCacheSetStr sidCacheSet;
+
+struct encKeyCacheEntryStr {
+ PRUint8 bytes[512];
+ PRInt32 length;
+};
+typedef struct encKeyCacheEntryStr encKeyCacheEntry;
+
+#define SSL_MAX_DNS_HOST_NAME 1024
+
+struct srvNameCacheEntryStr {
+ PRUint16 type; /* 2 */
+ PRUint16 nameLen; /* 2 */
+ PRUint8 name[SSL_MAX_DNS_HOST_NAME + 12]; /* 1034 */
+ PRUint8 nameHash[SHA256_LENGTH]; /* 32 */
+ /* 1072 */
+};
+typedef struct srvNameCacheEntryStr srvNameCacheEntry;
+
+
+struct cacheDescStr {
+
+ PRUint32 cacheMemSize;
+
+ PRUint32 numSIDCacheLocks;
+ PRUint32 numSIDCacheSets;
+ PRUint32 numSIDCacheSetsPerLock;
+
+ PRUint32 numSIDCacheEntries;
+ PRUint32 sidCacheSize;
+
+ PRUint32 numCertCacheEntries;
+ PRUint32 certCacheSize;
+
+ PRUint32 numKeyCacheEntries;
+ PRUint32 keyCacheSize;
+
+ PRUint32 numSrvNameCacheEntries;
+ PRUint32 srvNameCacheSize;
+
+ PRUint32 ssl2Timeout;
+ PRUint32 ssl3Timeout;
+
+ PRUint32 numSIDCacheLocksInitialized;
+
+ /* These values are volatile, and are accessed through sharedCache-> */
+ PRUint32 nextCertCacheEntry; /* certCacheLock protects */
+ PRBool stopPolling;
+ PRBool everInherited;
+
+ /* The private copies of these values are pointers into shared mem */
+ /* The copies of these values in shared memory are merely offsets */
+ sidCacheLock * sidCacheLocks;
+ sidCacheLock * keyCacheLock;
+ sidCacheLock * certCacheLock;
+ sidCacheLock * srvNameCacheLock;
+ sidCacheSet * sidCacheSets;
+ sidCacheEntry * sidCacheData;
+ certCacheEntry * certCacheData;
+ SSLWrappedSymWrappingKey * keyCacheData;
+ PRUint8 * ticketKeyNameSuffix;
+ encKeyCacheEntry * ticketEncKey;
+ encKeyCacheEntry * ticketMacKey;
+ PRUint32 * ticketKeysValid;
+ srvNameCacheEntry * srvNameCacheData;
+
+ /* Only the private copies of these pointers are valid */
+ char * cacheMem;
+ struct cacheDescStr * sharedCache; /* shared copy of this struct */
+ PRFileMap * cacheMemMap;
+ PRThread * poller;
+ PRUint32 mutexTimeout;
+ PRBool shared;
+};
+typedef struct cacheDescStr cacheDesc;
+
+static cacheDesc globalCache;
+
+static const char envVarName[] = { SSL_ENV_VAR_NAME };
+
+static PRBool isMultiProcess = PR_FALSE;
+
+
+#define DEF_SID_CACHE_ENTRIES 10000
+#define DEF_CERT_CACHE_ENTRIES 250
+#define MIN_CERT_CACHE_ENTRIES 125 /* the effective size in old releases. */
+#define DEF_KEY_CACHE_ENTRIES 250
+#define DEF_NAME_CACHE_ENTRIES 1000
+
+#define SID_CACHE_ENTRIES_PER_SET 128
+#define SID_ALIGNMENT 16
+
+#define DEF_SSL2_TIMEOUT 100 /* seconds */
+#define MAX_SSL2_TIMEOUT 100 /* seconds */
+#define MIN_SSL2_TIMEOUT 5 /* seconds */
+
+#define DEF_SSL3_TIMEOUT 86400L /* 24 hours */
+#define MAX_SSL3_TIMEOUT 86400L /* 24 hours */
+#define MIN_SSL3_TIMEOUT 5 /* seconds */
+
+#if defined(AIX) || defined(LINUX) || defined(NETBSD) || defined(OPENBSD)
+#define MAX_SID_CACHE_LOCKS 8 /* two FDs per lock */
+#elif defined(OSF1)
+#define MAX_SID_CACHE_LOCKS 16 /* one FD per lock */
+#else
+#define MAX_SID_CACHE_LOCKS 256
+#endif
+
+#define SID_HOWMANY(val, size) (((val) + ((size) - 1)) / (size))
+#define SID_ROUNDUP(val, size) ((size) * SID_HOWMANY((val), (size)))
+
+
+static sslPID myPid;
+static PRUint32 ssl_max_sid_cache_locks = MAX_SID_CACHE_LOCKS;
+
+/* forward static function declarations */
+static PRUint32 SIDindex(cacheDesc *cache, const PRIPv6Addr *addr, PRUint8 *s,
+ unsigned nl);
+static SECStatus LaunchLockPoller(cacheDesc *cache);
+static SECStatus StopLockPoller(cacheDesc *cache);
+
+
+struct inheritanceStr {
+ PRUint32 cacheMemSize;
+ PRUint32 fmStrLen;
+};
+
+typedef struct inheritanceStr inheritance;
+
+#if defined(_WIN32) || defined(XP_OS2)
+
+#define DEFAULT_CACHE_DIRECTORY "\\temp"
+
+#endif /* _win32 */
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+
+#define DEFAULT_CACHE_DIRECTORY "/tmp"
+
+#endif /* XP_UNIX || XP_BEOS */
+
+
+/************************************************************************/
+
+static PRUint32
+LockSidCacheLock(sidCacheLock *lock, PRUint32 now)
+{
+ SECStatus rv = sslMutex_Lock(&lock->mutex);
+ if (rv != SECSuccess)
+ return 0;
+ if (!now)
+ now = ssl_Time();
+ lock->timeStamp = now;
+ lock->pid = myPid;
+ return now;
+}
+
+static SECStatus
+UnlockSidCacheLock(sidCacheLock *lock)
+{
+ SECStatus rv;
+
+ lock->pid = 0;
+ rv = sslMutex_Unlock(&lock->mutex);
+ return rv;
+}
+
+/* returns the value of ssl_Time on success, zero on failure. */
+static PRUint32
+LockSet(cacheDesc *cache, PRUint32 set, PRUint32 now)
+{
+ PRUint32 lockNum = set % cache->numSIDCacheLocks;
+ sidCacheLock * lock = cache->sidCacheLocks + lockNum;
+
+ return LockSidCacheLock(lock, now);
+}
+
+static SECStatus
+UnlockSet(cacheDesc *cache, PRUint32 set)
+{
+ PRUint32 lockNum = set % cache->numSIDCacheLocks;
+ sidCacheLock * lock = cache->sidCacheLocks + lockNum;
+
+ return UnlockSidCacheLock(lock);
+}
+
+/************************************************************************/
+
+
+/* Put a certificate in the cache. Update the cert index in the sce.
+*/
+static PRUint32
+CacheCert(cacheDesc * cache, CERTCertificate *cert, sidCacheEntry *sce)
+{
+ PRUint32 now;
+ certCacheEntry cce;
+
+ if ((cert->derCert.len > SSL_MAX_CACHED_CERT_LEN) ||
+ (cert->derCert.len <= 0) ||
+ (cert->derCert.data == NULL)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return 0;
+ }
+
+ cce.sessionIDLength = sce->sessionIDLength;
+ PORT_Memcpy(cce.sessionID, sce->sessionID, cce.sessionIDLength);
+
+ cce.certLength = cert->derCert.len;
+ PORT_Memcpy(cce.cert, cert->derCert.data, cce.certLength);
+
+ /* get lock on cert cache */
+ now = LockSidCacheLock(cache->certCacheLock, 0);
+ if (now) {
+
+ /* Find where to place the next cert cache entry. */
+ cacheDesc * sharedCache = cache->sharedCache;
+ PRUint32 ndx = sharedCache->nextCertCacheEntry;
+
+ /* write the entry */
+ cache->certCacheData[ndx] = cce;
+
+ /* remember where we put it. */
+ sce->u.ssl3.certIndex = ndx;
+
+ /* update the "next" cache entry index */
+ sharedCache->nextCertCacheEntry =
+ (ndx + 1) % cache->numCertCacheEntries;
+
+ UnlockSidCacheLock(cache->certCacheLock);
+ }
+ return now;
+
+}
+
+/* Server configuration hash tables need to account the SECITEM.type
+ * field as well. These functions accomplish that. */
+static PLHashNumber
+Get32BitNameHash(const SECItem *name)
+{
+ PLHashNumber rv = SECITEM_Hash(name);
+
+ PRUint8 *rvc = (PRUint8 *)&rv;
+ rvc[ name->len % sizeof(rv) ] ^= name->type;
+
+ return rv;
+}
+
+/* Put a name in the cache. Update the cert index in the sce.
+*/
+static PRUint32
+CacheSrvName(cacheDesc * cache, SECItem *name, sidCacheEntry *sce)
+{
+ PRUint32 now;
+ PRUint32 ndx;
+ srvNameCacheEntry snce;
+
+ if (!name || name->len <= 0 ||
+ name->len > SSL_MAX_DNS_HOST_NAME) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return 0;
+ }
+
+ snce.type = name->type;
+ snce.nameLen = name->len;
+ PORT_Memcpy(snce.name, name->data, snce.nameLen);
+#ifdef NO_PKCS11_BYPASS
+ HASH_HashBuf(HASH_AlgSHA256, snce.nameHash, name->data, name->len);
+#else
+ SHA256_HashBuf(snce.nameHash, (unsigned char*)name->data,
+ name->len);
+#endif
+ /* get index of the next name */
+ ndx = Get32BitNameHash(name);
+ /* get lock on cert cache */
+ now = LockSidCacheLock(cache->srvNameCacheLock, 0);
+ if (now) {
+ if (cache->numSrvNameCacheEntries > 0) {
+ /* Fit the index into array */
+ ndx %= cache->numSrvNameCacheEntries;
+ /* write the entry */
+ cache->srvNameCacheData[ndx] = snce;
+ /* remember where we put it. */
+ sce->u.ssl3.srvNameIndex = ndx;
+ /* Copy hash into sid hash */
+ PORT_Memcpy(sce->u.ssl3.srvNameHash, snce.nameHash, SHA256_LENGTH);
+ }
+ UnlockSidCacheLock(cache->srvNameCacheLock);
+ }
+ return now;
+}
+
+/*
+** Convert local SID to shared memory one
+*/
+static void
+ConvertFromSID(sidCacheEntry *to, sslSessionID *from)
+{
+ to->valid = 1;
+ to->version = from->version;
+ to->addr = from->addr;
+ to->creationTime = from->creationTime;
+ to->lastAccessTime = from->lastAccessTime;
+ to->expirationTime = from->expirationTime;
+ to->authAlgorithm = from->authAlgorithm;
+ to->authKeyBits = from->authKeyBits;
+ to->keaType = from->keaType;
+ to->keaKeyBits = from->keaKeyBits;
+
+ if (from->version < SSL_LIBRARY_VERSION_3_0) {
+ if ((from->u.ssl2.masterKey.len > SSL_MAX_MASTER_KEY_BYTES) ||
+ (from->u.ssl2.cipherArg.len > SSL_MAX_CYPHER_ARG_BYTES)) {
+ SSL_DBG(("%d: SSL: masterKeyLen=%d cipherArgLen=%d",
+ myPid, from->u.ssl2.masterKey.len,
+ from->u.ssl2.cipherArg.len));
+ to->valid = 0;
+ return;
+ }
+
+ to->u.ssl2.cipherType = from->u.ssl2.cipherType;
+ to->u.ssl2.masterKeyLen = from->u.ssl2.masterKey.len;
+ to->u.ssl2.cipherArgLen = from->u.ssl2.cipherArg.len;
+ to->u.ssl2.keyBits = from->u.ssl2.keyBits;
+ to->u.ssl2.secretKeyBits = from->u.ssl2.secretKeyBits;
+ to->sessionIDLength = SSL2_SESSIONID_BYTES;
+ PORT_Memcpy(to->sessionID, from->u.ssl2.sessionID, SSL2_SESSIONID_BYTES);
+ PORT_Memcpy(to->u.ssl2.masterKey, from->u.ssl2.masterKey.data,
+ from->u.ssl2.masterKey.len);
+ PORT_Memcpy(to->u.ssl2.cipherArg, from->u.ssl2.cipherArg.data,
+ from->u.ssl2.cipherArg.len);
+#ifdef DEBUG
+ PORT_Memset(to->u.ssl2.masterKey+from->u.ssl2.masterKey.len, 0,
+ sizeof(to->u.ssl2.masterKey) - from->u.ssl2.masterKey.len);
+ PORT_Memset(to->u.ssl2.cipherArg+from->u.ssl2.cipherArg.len, 0,
+ sizeof(to->u.ssl2.cipherArg) - from->u.ssl2.cipherArg.len);
+#endif
+ SSL_TRC(8, ("%d: SSL: ConvertSID: masterKeyLen=%d cipherArgLen=%d "
+ "time=%d addr=0x%08x%08x%08x%08x cipherType=%d", myPid,
+ to->u.ssl2.masterKeyLen, to->u.ssl2.cipherArgLen,
+ to->creationTime, to->addr.pr_s6_addr32[0],
+ to->addr.pr_s6_addr32[1], to->addr.pr_s6_addr32[2],
+ to->addr.pr_s6_addr32[3], to->u.ssl2.cipherType));
+ } else {
+ /* This is an SSL v3 session */
+
+ to->u.ssl3.cipherSuite = from->u.ssl3.cipherSuite;
+ to->u.ssl3.compression = (PRUint16)from->u.ssl3.compression;
+ to->u.ssl3.keys = from->u.ssl3.keys;
+ to->u.ssl3.masterWrapMech = from->u.ssl3.masterWrapMech;
+ to->u.ssl3.exchKeyType = from->u.ssl3.exchKeyType;
+ to->sessionIDLength = from->u.ssl3.sessionIDLength;
+ to->u.ssl3.certIndex = -1;
+ to->u.ssl3.srvNameIndex = -1;
+
+ PORT_Memcpy(to->sessionID, from->u.ssl3.sessionID,
+ to->sessionIDLength);
+
+ SSL_TRC(8, ("%d: SSL3: ConvertSID: time=%d addr=0x%08x%08x%08x%08x "
+ "cipherSuite=%d",
+ myPid, to->creationTime, to->addr.pr_s6_addr32[0],
+ to->addr.pr_s6_addr32[1], to->addr.pr_s6_addr32[2],
+ to->addr.pr_s6_addr32[3], to->u.ssl3.cipherSuite));
+ }
+}
+
+/*
+** Convert shared memory cache-entry to local memory based one
+** This is only called from ServerSessionIDLookup().
+** Caller must hold cache lock when calling this.
+*/
+static sslSessionID *
+ConvertToSID(sidCacheEntry * from,
+ certCacheEntry * pcce,
+ srvNameCacheEntry *psnce,
+ CERTCertDBHandle * dbHandle)
+{
+ sslSessionID *to;
+ PRUint16 version = from->version;
+
+ to = PORT_ZNew(sslSessionID);
+ if (!to) {
+ return 0;
+ }
+
+ if (version < SSL_LIBRARY_VERSION_3_0) {
+ /* This is an SSL v2 session */
+ to->u.ssl2.masterKey.data =
+ (unsigned char*) PORT_Alloc(from->u.ssl2.masterKeyLen);
+ if (!to->u.ssl2.masterKey.data) {
+ goto loser;
+ }
+ if (from->u.ssl2.cipherArgLen) {
+ to->u.ssl2.cipherArg.data =
+ (unsigned char*)PORT_Alloc(from->u.ssl2.cipherArgLen);
+ if (!to->u.ssl2.cipherArg.data) {
+ goto loser;
+ }
+ PORT_Memcpy(to->u.ssl2.cipherArg.data, from->u.ssl2.cipherArg,
+ from->u.ssl2.cipherArgLen);
+ }
+
+ to->u.ssl2.cipherType = from->u.ssl2.cipherType;
+ to->u.ssl2.masterKey.len = from->u.ssl2.masterKeyLen;
+ to->u.ssl2.cipherArg.len = from->u.ssl2.cipherArgLen;
+ to->u.ssl2.keyBits = from->u.ssl2.keyBits;
+ to->u.ssl2.secretKeyBits = from->u.ssl2.secretKeyBits;
+/* to->sessionIDLength = SSL2_SESSIONID_BYTES; */
+ PORT_Memcpy(to->u.ssl2.sessionID, from->sessionID, SSL2_SESSIONID_BYTES);
+ PORT_Memcpy(to->u.ssl2.masterKey.data, from->u.ssl2.masterKey,
+ from->u.ssl2.masterKeyLen);
+
+ SSL_TRC(8, ("%d: SSL: ConvertToSID: masterKeyLen=%d cipherArgLen=%d "
+ "time=%d addr=0x%08x%08x%08x%08x cipherType=%d",
+ myPid, to->u.ssl2.masterKey.len,
+ to->u.ssl2.cipherArg.len, to->creationTime,
+ to->addr.pr_s6_addr32[0], to->addr.pr_s6_addr32[1],
+ to->addr.pr_s6_addr32[2], to->addr.pr_s6_addr32[3],
+ to->u.ssl2.cipherType));
+ } else {
+ /* This is an SSL v3 session */
+
+ to->u.ssl3.sessionIDLength = from->sessionIDLength;
+ to->u.ssl3.cipherSuite = from->u.ssl3.cipherSuite;
+ to->u.ssl3.compression = (SSLCompressionMethod)from->u.ssl3.compression;
+ to->u.ssl3.keys = from->u.ssl3.keys;
+ to->u.ssl3.masterWrapMech = from->u.ssl3.masterWrapMech;
+ to->u.ssl3.exchKeyType = from->u.ssl3.exchKeyType;
+ if (from->u.ssl3.srvNameIndex != -1 && psnce) {
+ SECItem name;
+ SECStatus rv;
+ name.type = psnce->type;
+ name.len = psnce->nameLen;
+ name.data = psnce->name;
+ rv = SECITEM_CopyItem(NULL, &to->u.ssl3.srvName, &name);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+
+ PORT_Memcpy(to->u.ssl3.sessionID, from->sessionID, from->sessionIDLength);
+
+ /* the portions of the SID that are only restored on the client
+ * are set to invalid values on the server.
+ */
+ to->u.ssl3.clientWriteKey = NULL;
+ to->u.ssl3.serverWriteKey = NULL;
+
+ to->urlSvrName = NULL;
+
+ to->u.ssl3.masterModuleID = (SECMODModuleID)-1; /* invalid value */
+ to->u.ssl3.masterSlotID = (CK_SLOT_ID)-1; /* invalid value */
+ to->u.ssl3.masterWrapIndex = 0;
+ to->u.ssl3.masterWrapSeries = 0;
+ to->u.ssl3.masterValid = PR_FALSE;
+
+ to->u.ssl3.clAuthModuleID = (SECMODModuleID)-1; /* invalid value */
+ to->u.ssl3.clAuthSlotID = (CK_SLOT_ID)-1; /* invalid value */
+ to->u.ssl3.clAuthSeries = 0;
+ to->u.ssl3.clAuthValid = PR_FALSE;
+
+ if (from->u.ssl3.certIndex != -1 && pcce) {
+ SECItem derCert;
+
+ derCert.len = pcce->certLength;
+ derCert.data = pcce->cert;
+
+ to->peerCert = CERT_NewTempCertificate(dbHandle, &derCert, NULL,
+ PR_FALSE, PR_TRUE);
+ if (to->peerCert == NULL)
+ goto loser;
+ }
+ }
+
+ to->version = from->version;
+ to->creationTime = from->creationTime;
+ to->lastAccessTime = from->lastAccessTime;
+ to->expirationTime = from->expirationTime;
+ to->cached = in_server_cache;
+ to->addr = from->addr;
+ to->references = 1;
+ to->authAlgorithm = from->authAlgorithm;
+ to->authKeyBits = from->authKeyBits;
+ to->keaType = from->keaType;
+ to->keaKeyBits = from->keaKeyBits;
+
+ return to;
+
+ loser:
+ if (to) {
+ if (version < SSL_LIBRARY_VERSION_3_0) {
+ if (to->u.ssl2.masterKey.data)
+ PORT_Free(to->u.ssl2.masterKey.data);
+ if (to->u.ssl2.cipherArg.data)
+ PORT_Free(to->u.ssl2.cipherArg.data);
+ } else {
+ SECITEM_FreeItem(&to->u.ssl3.srvName, PR_FALSE);
+ }
+ PORT_Free(to);
+ }
+ return NULL;
+}
+
+
+
+/*
+** Perform some mumbo jumbo on the ip-address and the session-id value to
+** compute a hash value.
+*/
+static PRUint32
+SIDindex(cacheDesc *cache, const PRIPv6Addr *addr, PRUint8 *s, unsigned nl)
+{
+ PRUint32 rv;
+ PRUint32 x[8];
+
+ memset(x, 0, sizeof x);
+ if (nl > sizeof x)
+ nl = sizeof x;
+ memcpy(x, s, nl);
+
+ rv = (addr->pr_s6_addr32[0] ^ addr->pr_s6_addr32[1] ^
+ addr->pr_s6_addr32[2] ^ addr->pr_s6_addr32[3] ^
+ x[0] ^ x[1] ^ x[2] ^ x[3] ^ x[4] ^ x[5] ^ x[6] ^ x[7])
+ % cache->numSIDCacheSets;
+ return rv;
+}
+
+
+
+/*
+** Look something up in the cache. This will invalidate old entries
+** in the process. Caller has locked the cache set!
+** Returns PR_TRUE if found a valid match. PR_FALSE otherwise.
+*/
+static sidCacheEntry *
+FindSID(cacheDesc *cache, PRUint32 setNum, PRUint32 now,
+ const PRIPv6Addr *addr, unsigned char *sessionID,
+ unsigned sessionIDLength)
+{
+ PRUint32 ndx = cache->sidCacheSets[setNum].next;
+ int i;
+
+ sidCacheEntry * set = cache->sidCacheData +
+ (setNum * SID_CACHE_ENTRIES_PER_SET);
+
+ for (i = SID_CACHE_ENTRIES_PER_SET; i > 0; --i) {
+ sidCacheEntry * sce;
+
+ ndx = (ndx - 1) % SID_CACHE_ENTRIES_PER_SET;
+ sce = set + ndx;
+
+ if (!sce->valid)
+ continue;
+
+ if (now > sce->expirationTime) {
+ /* SessionID has timed out. Invalidate the entry. */
+ SSL_TRC(7, ("%d: timed out sid entry addr=%08x%08x%08x%08x now=%x "
+ "time+=%x",
+ myPid, sce->addr.pr_s6_addr32[0],
+ sce->addr.pr_s6_addr32[1], sce->addr.pr_s6_addr32[2],
+ sce->addr.pr_s6_addr32[3], now,
+ sce->expirationTime ));
+ sce->valid = 0;
+ continue;
+ }
+
+ /*
+ ** Next, examine specific session-id/addr data to see if the cache
+ ** entry matches our addr+session-id value
+ */
+ if (sessionIDLength == sce->sessionIDLength &&
+ !memcmp(&sce->addr, addr, sizeof(PRIPv6Addr)) &&
+ !memcmp(sce->sessionID, sessionID, sessionIDLength)) {
+ /* Found it */
+ return sce;
+ }
+ }
+
+ PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND);
+ return NULL;
+}
+
+/************************************************************************/
+
+/* This is the primary function for finding entries in the server's sid cache.
+ * Although it is static, this function is called via the global function
+ * pointer ssl_sid_lookup.
+ */
+static sslSessionID *
+ServerSessionIDLookup(const PRIPv6Addr *addr,
+ unsigned char *sessionID,
+ unsigned int sessionIDLength,
+ CERTCertDBHandle * dbHandle)
+{
+ sslSessionID * sid = 0;
+ sidCacheEntry * psce;
+ certCacheEntry *pcce = 0;
+ srvNameCacheEntry *psnce = 0;
+ cacheDesc * cache = &globalCache;
+ PRUint32 now;
+ PRUint32 set;
+ PRInt32 cndx;
+ sidCacheEntry sce;
+ certCacheEntry cce;
+ srvNameCacheEntry snce;
+
+ set = SIDindex(cache, addr, sessionID, sessionIDLength);
+ now = LockSet(cache, set, 0);
+ if (!now)
+ return NULL;
+
+ psce = FindSID(cache, set, now, addr, sessionID, sessionIDLength);
+ if (psce) {
+ if (psce->version >= SSL_LIBRARY_VERSION_3_0) {
+ if ((cndx = psce->u.ssl3.certIndex) != -1) {
+
+ PRUint32 gotLock = LockSidCacheLock(cache->certCacheLock, now);
+ if (gotLock) {
+ pcce = &cache->certCacheData[cndx];
+
+ /* See if the cert's session ID matches the sce cache. */
+ if ((pcce->sessionIDLength == psce->sessionIDLength) &&
+ !PORT_Memcmp(pcce->sessionID, psce->sessionID,
+ pcce->sessionIDLength)) {
+ cce = *pcce;
+ } else {
+ /* The cert doesen't match the SID cache entry,
+ ** so invalidate the SID cache entry.
+ */
+ psce->valid = 0;
+ psce = 0;
+ pcce = 0;
+ }
+ UnlockSidCacheLock(cache->certCacheLock);
+ } else {
+ /* what the ??. Didn't get the cert cache lock.
+ ** Don't invalidate the SID cache entry, but don't find it.
+ */
+ PORT_Assert(!("Didn't get cert Cache Lock!"));
+ psce = 0;
+ pcce = 0;
+ }
+ }
+ if (psce && ((cndx = psce->u.ssl3.srvNameIndex) != -1)) {
+ PRUint32 gotLock = LockSidCacheLock(cache->srvNameCacheLock,
+ now);
+ if (gotLock) {
+ psnce = &cache->srvNameCacheData[cndx];
+
+ if (!PORT_Memcmp(psnce->nameHash, psce->u.ssl3.srvNameHash,
+ SHA256_LENGTH)) {
+ snce = *psnce;
+ } else {
+ /* The name doesen't match the SID cache entry,
+ ** so invalidate the SID cache entry.
+ */
+ psce->valid = 0;
+ psce = 0;
+ psnce = 0;
+ }
+ UnlockSidCacheLock(cache->srvNameCacheLock);
+ } else {
+ /* what the ??. Didn't get the cert cache lock.
+ ** Don't invalidate the SID cache entry, but don't find it.
+ */
+ PORT_Assert(!("Didn't get name Cache Lock!"));
+ psce = 0;
+ psnce = 0;
+ }
+
+ }
+ }
+ if (psce) {
+ psce->lastAccessTime = now;
+ sce = *psce; /* grab a copy while holding the lock */
+ }
+ }
+ UnlockSet(cache, set);
+ if (psce) {
+ /* sce conains a copy of the cache entry.
+ ** Convert shared memory format to local format
+ */
+ sid = ConvertToSID(&sce, pcce ? &cce : 0, psnce ? &snce : 0, dbHandle);
+ }
+ return sid;
+}
+
+/*
+** Place a sid into the cache, if it isn't already there.
+*/
+static void
+ServerSessionIDCache(sslSessionID *sid)
+{
+ sidCacheEntry sce;
+ PRUint32 now = 0;
+ PRUint16 version = sid->version;
+ cacheDesc * cache = &globalCache;
+
+ if ((version >= SSL_LIBRARY_VERSION_3_0) &&
+ (sid->u.ssl3.sessionIDLength == 0)) {
+ return;
+ }
+
+ if (sid->cached == never_cached || sid->cached == invalid_cache) {
+ PRUint32 set;
+
+ PORT_Assert(sid->creationTime != 0);
+ if (!sid->creationTime)
+ sid->lastAccessTime = sid->creationTime = ssl_Time();
+ if (version < SSL_LIBRARY_VERSION_3_0) {
+ /* override caller's expiration time, which uses client timeout
+ * duration, not server timeout duration.
+ */
+ sid->expirationTime = sid->creationTime + cache->ssl2Timeout;
+ SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x "
+ "cipher=%d", myPid, sid->cached,
+ sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
+ sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3],
+ sid->creationTime, sid->u.ssl2.cipherType));
+ PRINT_BUF(8, (0, "sessionID:", sid->u.ssl2.sessionID,
+ SSL2_SESSIONID_BYTES));
+ PRINT_BUF(8, (0, "masterKey:", sid->u.ssl2.masterKey.data,
+ sid->u.ssl2.masterKey.len));
+ PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data,
+ sid->u.ssl2.cipherArg.len));
+
+ } else {
+ /* override caller's expiration time, which uses client timeout
+ * duration, not server timeout duration.
+ */
+ sid->expirationTime = sid->creationTime + cache->ssl3Timeout;
+ SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x "
+ "cipherSuite=%d", myPid, sid->cached,
+ sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
+ sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3],
+ sid->creationTime, sid->u.ssl3.cipherSuite));
+ PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID,
+ sid->u.ssl3.sessionIDLength));
+ }
+
+ ConvertFromSID(&sce, sid);
+
+ if (version >= SSL_LIBRARY_VERSION_3_0) {
+ SECItem *name = &sid->u.ssl3.srvName;
+ if (name->len && name->data) {
+ now = CacheSrvName(cache, name, &sce);
+ }
+ if (sid->peerCert != NULL) {
+ now = CacheCert(cache, sid->peerCert, &sce);
+ }
+ }
+
+ set = SIDindex(cache, &sce.addr, sce.sessionID, sce.sessionIDLength);
+ now = LockSet(cache, set, now);
+ if (now) {
+ PRUint32 next = cache->sidCacheSets[set].next;
+ PRUint32 ndx = set * SID_CACHE_ENTRIES_PER_SET + next;
+
+ /* Write out new cache entry */
+ cache->sidCacheData[ndx] = sce;
+
+ cache->sidCacheSets[set].next =
+ (next + 1) % SID_CACHE_ENTRIES_PER_SET;
+
+ UnlockSet(cache, set);
+ sid->cached = in_server_cache;
+ }
+ }
+}
+
+/*
+** Although this is static, it is called from ssl via global function pointer
+** ssl_sid_uncache. This invalidates the referenced cache entry.
+*/
+static void
+ServerSessionIDUncache(sslSessionID *sid)
+{
+ cacheDesc * cache = &globalCache;
+ PRUint8 * sessionID;
+ unsigned int sessionIDLength;
+ PRErrorCode err;
+ PRUint32 set;
+ PRUint32 now;
+ sidCacheEntry *psce;
+
+ if (sid == NULL)
+ return;
+
+ /* Uncaching a SID should never change the error code.
+ ** So save it here and restore it before exiting.
+ */
+ err = PR_GetError();
+
+ if (sid->version < SSL_LIBRARY_VERSION_3_0) {
+ sessionID = sid->u.ssl2.sessionID;
+ sessionIDLength = SSL2_SESSIONID_BYTES;
+ SSL_TRC(8, ("%d: SSL: UncacheMT: valid=%d addr=0x%08x%08x%08x%08x time=%x "
+ "cipher=%d", myPid, sid->cached,
+ sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
+ sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3],
+ sid->creationTime, sid->u.ssl2.cipherType));
+ PRINT_BUF(8, (0, "sessionID:", sessionID, sessionIDLength));
+ PRINT_BUF(8, (0, "masterKey:", sid->u.ssl2.masterKey.data,
+ sid->u.ssl2.masterKey.len));
+ PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data,
+ sid->u.ssl2.cipherArg.len));
+ } else {
+ sessionID = sid->u.ssl3.sessionID;
+ sessionIDLength = sid->u.ssl3.sessionIDLength;
+ SSL_TRC(8, ("%d: SSL3: UncacheMT: valid=%d addr=0x%08x%08x%08x%08x time=%x "
+ "cipherSuite=%d", myPid, sid->cached,
+ sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
+ sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3],
+ sid->creationTime, sid->u.ssl3.cipherSuite));
+ PRINT_BUF(8, (0, "sessionID:", sessionID, sessionIDLength));
+ }
+ set = SIDindex(cache, &sid->addr, sessionID, sessionIDLength);
+ now = LockSet(cache, set, 0);
+ if (now) {
+ psce = FindSID(cache, set, now, &sid->addr, sessionID, sessionIDLength);
+ if (psce) {
+ psce->valid = 0;
+ }
+ UnlockSet(cache, set);
+ }
+ sid->cached = invalid_cache;
+ PORT_SetError(err);
+}
+
+#ifdef XP_OS2
+
+#define INCL_DOSPROCESS
+#include <os2.h>
+
+long gettid(void)
+{
+ PTIB ptib;
+ PPIB ppib;
+ DosGetInfoBlocks(&ptib, &ppib);
+ return ((long)ptib->tib_ordinal); /* thread id */
+}
+#endif
+
+static void
+CloseCache(cacheDesc *cache)
+{
+ int locks_initialized = cache->numSIDCacheLocksInitialized;
+
+ if (cache->cacheMem) {
+ if (cache->sharedCache) {
+ sidCacheLock *pLock = cache->sidCacheLocks;
+ for (; locks_initialized > 0; --locks_initialized, ++pLock ) {
+ /* If everInherited is true, this shared cache was (and may
+ ** still be) in use by multiple processes. We do not wish to
+ ** destroy the mutexes while they are still in use, but we do
+ ** want to free mutex resources associated with this process.
+ */
+ sslMutex_Destroy(&pLock->mutex,
+ cache->sharedCache->everInherited);
+ }
+ }
+ if (cache->shared) {
+ PR_MemUnmap(cache->cacheMem, cache->cacheMemSize);
+ } else {
+ PORT_Free(cache->cacheMem);
+ }
+ cache->cacheMem = NULL;
+ }
+ if (cache->cacheMemMap) {
+ PR_CloseFileMap(cache->cacheMemMap);
+ cache->cacheMemMap = NULL;
+ }
+ memset(cache, 0, sizeof *cache);
+}
+
+static SECStatus
+InitCache(cacheDesc *cache, int maxCacheEntries, int maxCertCacheEntries,
+ int maxSrvNameCacheEntries, PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout, const char *directory, PRBool shared)
+{
+ ptrdiff_t ptr;
+ sidCacheLock *pLock;
+ char * cacheMem;
+ PRFileMap * cacheMemMap;
+ char * cfn = NULL; /* cache file name */
+ int locks_initialized = 0;
+ int locks_to_initialize = 0;
+ PRUint32 init_time;
+
+ if ( (!cache) || (maxCacheEntries < 0) || (!directory) ) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (cache->cacheMem) {
+ /* Already done */
+ return SECSuccess;
+ }
+
+ /* make sure loser can clean up properly */
+ cache->shared = shared;
+ cache->cacheMem = cacheMem = NULL;
+ cache->cacheMemMap = cacheMemMap = NULL;
+ cache->sharedCache = (cacheDesc *)0;
+
+ cache->numSIDCacheLocksInitialized = 0;
+ cache->nextCertCacheEntry = 0;
+ cache->stopPolling = PR_FALSE;
+ cache->everInherited = PR_FALSE;
+ cache->poller = NULL;
+ cache->mutexTimeout = 0;
+
+ cache->numSIDCacheEntries = maxCacheEntries ? maxCacheEntries
+ : DEF_SID_CACHE_ENTRIES;
+ cache->numSIDCacheSets =
+ SID_HOWMANY(cache->numSIDCacheEntries, SID_CACHE_ENTRIES_PER_SET);
+
+ cache->numSIDCacheEntries =
+ cache->numSIDCacheSets * SID_CACHE_ENTRIES_PER_SET;
+
+ cache->numSIDCacheLocks =
+ PR_MIN(cache->numSIDCacheSets, ssl_max_sid_cache_locks);
+
+ cache->numSIDCacheSetsPerLock =
+ SID_HOWMANY(cache->numSIDCacheSets, cache->numSIDCacheLocks);
+
+ cache->numCertCacheEntries = (maxCertCacheEntries > 0) ?
+ maxCertCacheEntries : 0;
+ cache->numSrvNameCacheEntries = (maxSrvNameCacheEntries >= 0) ?
+ maxSrvNameCacheEntries : DEF_NAME_CACHE_ENTRIES;
+
+ /* compute size of shared memory, and offsets of all pointers */
+ ptr = 0;
+ cache->cacheMem = (char *)ptr;
+ ptr += SID_ROUNDUP(sizeof(cacheDesc), SID_ALIGNMENT);
+
+ cache->sidCacheLocks = (sidCacheLock *)ptr;
+ cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks;
+ cache->certCacheLock = cache->keyCacheLock + 1;
+ cache->srvNameCacheLock = cache->certCacheLock + 1;
+ ptr = (ptrdiff_t)(cache->srvNameCacheLock + 1);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->sidCacheSets = (sidCacheSet *)ptr;
+ ptr = (ptrdiff_t)(cache->sidCacheSets + cache->numSIDCacheSets);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->sidCacheData = (sidCacheEntry *)ptr;
+ ptr = (ptrdiff_t)(cache->sidCacheData + cache->numSIDCacheEntries);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->certCacheData = (certCacheEntry *)ptr;
+ cache->sidCacheSize =
+ (char *)cache->certCacheData - (char *)cache->sidCacheData;
+
+ if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES) {
+ /* This is really a poor way to computer this! */
+ cache->numCertCacheEntries = cache->sidCacheSize / sizeof(certCacheEntry);
+ if (cache->numCertCacheEntries < MIN_CERT_CACHE_ENTRIES)
+ cache->numCertCacheEntries = MIN_CERT_CACHE_ENTRIES;
+ }
+ ptr = (ptrdiff_t)(cache->certCacheData + cache->numCertCacheEntries);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->keyCacheData = (SSLWrappedSymWrappingKey *)ptr;
+ cache->certCacheSize =
+ (char *)cache->keyCacheData - (char *)cache->certCacheData;
+
+ cache->numKeyCacheEntries = kt_kea_size * SSL_NUM_WRAP_MECHS;
+ ptr = (ptrdiff_t)(cache->keyCacheData + cache->numKeyCacheEntries);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->keyCacheSize = (char *)ptr - (char *)cache->keyCacheData;
+
+ cache->ticketKeyNameSuffix = (PRUint8 *)ptr;
+ ptr = (ptrdiff_t)(cache->ticketKeyNameSuffix +
+ SESS_TICKET_KEY_VAR_NAME_LEN);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->ticketEncKey = (encKeyCacheEntry *)ptr;
+ ptr = (ptrdiff_t)(cache->ticketEncKey + 1);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->ticketMacKey = (encKeyCacheEntry *)ptr;
+ ptr = (ptrdiff_t)(cache->ticketMacKey + 1);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->ticketKeysValid = (PRUint32 *)ptr;
+ ptr = (ptrdiff_t)(cache->ticketKeysValid + 1);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->srvNameCacheData = (srvNameCacheEntry *)ptr;
+ cache->srvNameCacheSize =
+ cache->numSrvNameCacheEntries * sizeof(srvNameCacheEntry);
+ ptr = (ptrdiff_t)(cache->srvNameCacheData + cache->numSrvNameCacheEntries);
+ ptr = SID_ROUNDUP(ptr, SID_ALIGNMENT);
+
+ cache->cacheMemSize = ptr;
+
+ if (ssl2_timeout) {
+ if (ssl2_timeout > MAX_SSL2_TIMEOUT) {
+ ssl2_timeout = MAX_SSL2_TIMEOUT;
+ }
+ if (ssl2_timeout < MIN_SSL2_TIMEOUT) {
+ ssl2_timeout = MIN_SSL2_TIMEOUT;
+ }
+ cache->ssl2Timeout = ssl2_timeout;
+ } else {
+ cache->ssl2Timeout = DEF_SSL2_TIMEOUT;
+ }
+
+ if (ssl3_timeout) {
+ if (ssl3_timeout > MAX_SSL3_TIMEOUT) {
+ ssl3_timeout = MAX_SSL3_TIMEOUT;
+ }
+ if (ssl3_timeout < MIN_SSL3_TIMEOUT) {
+ ssl3_timeout = MIN_SSL3_TIMEOUT;
+ }
+ cache->ssl3Timeout = ssl3_timeout;
+ } else {
+ cache->ssl3Timeout = DEF_SSL3_TIMEOUT;
+ }
+
+ if (shared) {
+ /* Create file names */
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ /* there's some confusion here about whether PR_OpenAnonFileMap wants
+ ** a directory name or a file name for its first argument.
+ cfn = PR_smprintf("%s/.sslsvrcache.%d", directory, myPid);
+ */
+ cfn = PR_smprintf("%s", directory);
+#elif defined(XP_WIN32)
+ cfn = PR_smprintf("%s/svrcache_%d_%x.ssl", directory, myPid,
+ GetCurrentThreadId());
+#elif defined(XP_OS2)
+ cfn = PR_smprintf("%s/svrcache_%d_%x.ssl", directory, myPid,
+ gettid());
+#else
+#error "Don't know how to create file name for this platform!"
+#endif
+ if (!cfn) {
+ goto loser;
+ }
+
+ /* Create cache */
+ cacheMemMap = PR_OpenAnonFileMap(cfn, cache->cacheMemSize,
+ PR_PROT_READWRITE);
+
+ PR_smprintf_free(cfn);
+ if(!cacheMemMap) {
+ goto loser;
+ }
+
+ cacheMem = PR_MemMap(cacheMemMap, 0, cache->cacheMemSize);
+ } else {
+ cacheMem = PORT_Alloc(cache->cacheMemSize);
+ }
+
+ if (! cacheMem) {
+ goto loser;
+ }
+
+ /* Initialize shared memory. This may not be necessary on all platforms */
+ memset(cacheMem, 0, cache->cacheMemSize);
+
+ /* Copy cache descriptor header into shared memory */
+ memcpy(cacheMem, cache, sizeof *cache);
+
+ /* save private copies of these values */
+ cache->cacheMemMap = cacheMemMap;
+ cache->cacheMem = cacheMem;
+ cache->sharedCache = (cacheDesc *)cacheMem;
+
+ /* Fix pointers in our private copy of cache descriptor to point to
+ ** spaces in shared memory
+ */
+ ptr = (ptrdiff_t)cache->cacheMem;
+ *(ptrdiff_t *)(&cache->sidCacheLocks) += ptr;
+ *(ptrdiff_t *)(&cache->keyCacheLock ) += ptr;
+ *(ptrdiff_t *)(&cache->certCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->sidCacheSets ) += ptr;
+ *(ptrdiff_t *)(&cache->sidCacheData ) += ptr;
+ *(ptrdiff_t *)(&cache->certCacheData) += ptr;
+ *(ptrdiff_t *)(&cache->keyCacheData ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketKeyNameSuffix) += ptr;
+ *(ptrdiff_t *)(&cache->ticketEncKey ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketMacKey ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketKeysValid) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheData) += ptr;
+
+ /* initialize the locks */
+ init_time = ssl_Time();
+ pLock = cache->sidCacheLocks;
+ for (locks_to_initialize = cache->numSIDCacheLocks + 3;
+ locks_initialized < locks_to_initialize;
+ ++locks_initialized, ++pLock ) {
+
+ SECStatus err = sslMutex_Init(&pLock->mutex, shared);
+ if (err) {
+ cache->numSIDCacheLocksInitialized = locks_initialized;
+ goto loser;
+ }
+ pLock->timeStamp = init_time;
+ pLock->pid = 0;
+ }
+ cache->numSIDCacheLocksInitialized = locks_initialized;
+
+ return SECSuccess;
+
+loser:
+ CloseCache(cache);
+ return SECFailure;
+}
+
+PRUint32
+SSL_GetMaxServerCacheLocks(void)
+{
+ return ssl_max_sid_cache_locks + 2;
+ /* The extra two are the cert cache lock and the key cache lock. */
+}
+
+SECStatus
+SSL_SetMaxServerCacheLocks(PRUint32 maxLocks)
+{
+ /* Minimum is 1 sid cache lock, 1 cert cache lock and 1 key cache lock.
+ ** We'd like to test for a maximum value, but not all platforms' header
+ ** files provide a symbol or function or other means of determining
+ ** the maximum, other than trial and error.
+ */
+ if (maxLocks < 3) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ssl_max_sid_cache_locks = maxLocks - 2;
+ /* The extra two are the cert cache lock and the key cache lock. */
+ return SECSuccess;
+}
+
+static SECStatus
+ssl_ConfigServerSessionIDCacheInstanceWithOpt(cacheDesc *cache,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ PRBool shared,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries)
+{
+ SECStatus rv;
+
+ PORT_Assert(sizeof(sidCacheEntry) == 192);
+ PORT_Assert(sizeof(certCacheEntry) == 4096);
+ PORT_Assert(sizeof(srvNameCacheEntry) == 1072);
+
+ rv = ssl_Init();
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ myPid = SSL_GETPID();
+ if (!directory) {
+ directory = DEFAULT_CACHE_DIRECTORY;
+ }
+ rv = InitCache(cache, maxCacheEntries, maxCertCacheEntries,
+ maxSrvNameCacheEntries, ssl2_timeout, ssl3_timeout,
+ directory, shared);
+ if (rv) {
+ SET_ERROR_CODE
+ return SECFailure;
+ }
+
+ ssl_sid_lookup = ServerSessionIDLookup;
+ ssl_sid_cache = ServerSessionIDCache;
+ ssl_sid_uncache = ServerSessionIDUncache;
+ return SECSuccess;
+}
+
+SECStatus
+SSL_ConfigServerSessionIDCacheInstance( cacheDesc *cache,
+ int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory, PRBool shared)
+{
+ return ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache,
+ ssl2_timeout,
+ ssl3_timeout,
+ directory,
+ shared,
+ maxCacheEntries,
+ -1, -1);
+}
+
+SECStatus
+SSL_ConfigServerSessionIDCache( int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory)
+{
+ ssl_InitSessionCacheLocks(PR_FALSE);
+ return SSL_ConfigServerSessionIDCacheInstance(&globalCache,
+ maxCacheEntries, ssl2_timeout, ssl3_timeout, directory, PR_FALSE);
+}
+
+SECStatus
+SSL_ShutdownServerSessionIDCacheInstance(cacheDesc *cache)
+{
+ CloseCache(cache);
+ return SECSuccess;
+}
+
+SECStatus
+SSL_ShutdownServerSessionIDCache(void)
+{
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ /* Stop the thread that polls cache for expired locks on Unix */
+ StopLockPoller(&globalCache);
+#endif
+ SSL3_ShutdownServerCache();
+ return SSL_ShutdownServerSessionIDCacheInstance(&globalCache);
+}
+
+/* Use this function, instead of SSL_ConfigServerSessionIDCache,
+ * if the cache will be shared by multiple processes.
+ */
+static SECStatus
+ssl_ConfigMPServerSIDCacheWithOpt( PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries)
+{
+ char * envValue;
+ char * inhValue;
+ cacheDesc * cache = &globalCache;
+ PRUint32 fmStrLen;
+ SECStatus result;
+ PRStatus prStatus;
+ SECStatus putEnvFailed;
+ inheritance inherit;
+ char fmString[PR_FILEMAP_STRING_BUFSIZE];
+
+ isMultiProcess = PR_TRUE;
+ result = ssl_ConfigServerSessionIDCacheInstanceWithOpt(cache,
+ ssl2_timeout, ssl3_timeout, directory, PR_TRUE,
+ maxCacheEntries, maxCacheEntries, maxSrvNameCacheEntries);
+ if (result != SECSuccess)
+ return result;
+
+ prStatus = PR_ExportFileMapAsString(cache->cacheMemMap,
+ sizeof fmString, fmString);
+ if ((prStatus != PR_SUCCESS) || !(fmStrLen = strlen(fmString))) {
+ SET_ERROR_CODE
+ return SECFailure;
+ }
+
+ inherit.cacheMemSize = cache->cacheMemSize;
+ inherit.fmStrLen = fmStrLen;
+
+ inhValue = BTOA_DataToAscii((unsigned char *)&inherit, sizeof inherit);
+ if (!inhValue || !strlen(inhValue)) {
+ SET_ERROR_CODE
+ return SECFailure;
+ }
+ envValue = PR_smprintf("%s,%s", inhValue, fmString);
+ if (!envValue || !strlen(envValue)) {
+ SET_ERROR_CODE
+ return SECFailure;
+ }
+ PORT_Free(inhValue);
+
+ putEnvFailed = (SECStatus)NSS_PutEnv(envVarName, envValue);
+ PR_smprintf_free(envValue);
+ if (putEnvFailed) {
+ SET_ERROR_CODE
+ result = SECFailure;
+ }
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ /* Launch thread to poll cache for expired locks on Unix */
+ LaunchLockPoller(cache);
+#endif
+ return result;
+}
+
+/* Use this function, instead of SSL_ConfigServerSessionIDCache,
+ * if the cache will be shared by multiple processes.
+ */
+SECStatus
+SSL_ConfigMPServerSIDCache( int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory)
+{
+ return ssl_ConfigMPServerSIDCacheWithOpt(ssl2_timeout,
+ ssl3_timeout,
+ directory,
+ maxCacheEntries,
+ -1, -1);
+}
+
+SECStatus
+SSL_ConfigServerSessionIDCacheWithOpt(
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory,
+ int maxCacheEntries,
+ int maxCertCacheEntries,
+ int maxSrvNameCacheEntries,
+ PRBool enableMPCache)
+{
+ if (!enableMPCache) {
+ ssl_InitSessionCacheLocks(PR_FALSE);
+ return ssl_ConfigServerSessionIDCacheInstanceWithOpt(&globalCache,
+ ssl2_timeout, ssl3_timeout, directory, PR_FALSE,
+ maxCacheEntries, maxCertCacheEntries, maxSrvNameCacheEntries);
+ } else {
+ return ssl_ConfigMPServerSIDCacheWithOpt(ssl2_timeout, ssl3_timeout,
+ directory, maxCacheEntries, maxCertCacheEntries,
+ maxSrvNameCacheEntries);
+ }
+}
+
+SECStatus
+SSL_InheritMPServerSIDCacheInstance(cacheDesc *cache, const char * envString)
+{
+ unsigned char * decoString = NULL;
+ char * fmString = NULL;
+ char * myEnvString = NULL;
+ unsigned int decoLen;
+ ptrdiff_t ptr;
+ inheritance inherit;
+ cacheDesc my;
+#ifdef WINNT
+ sidCacheLock* newLocks;
+ int locks_initialized = 0;
+ int locks_to_initialize = 0;
+#endif
+ SECStatus status = ssl_Init();
+
+ if (status != SECSuccess) {
+ return status;
+ }
+
+ myPid = SSL_GETPID();
+
+ /* If this child was created by fork(), and not by exec() on unix,
+ ** then isMultiProcess will already be set.
+ ** If not, we'll set it below.
+ */
+ if (isMultiProcess) {
+ if (cache && cache->sharedCache) {
+ cache->sharedCache->everInherited = PR_TRUE;
+ }
+ return SECSuccess; /* already done. */
+ }
+
+ ssl_InitSessionCacheLocks(PR_FALSE);
+
+ ssl_sid_lookup = ServerSessionIDLookup;
+ ssl_sid_cache = ServerSessionIDCache;
+ ssl_sid_uncache = ServerSessionIDUncache;
+
+ if (!envString) {
+ envString = getenv(envVarName);
+ if (!envString) {
+ SET_ERROR_CODE
+ return SECFailure;
+ }
+ }
+ myEnvString = PORT_Strdup(envString);
+ if (!myEnvString)
+ return SECFailure;
+ fmString = strchr(myEnvString, ',');
+ if (!fmString)
+ goto loser;
+ *fmString++ = 0;
+
+ decoString = ATOB_AsciiToData(myEnvString, &decoLen);
+ if (!decoString) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+ if (decoLen != sizeof inherit) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+
+ PORT_Memcpy(&inherit, decoString, sizeof inherit);
+
+ if (strlen(fmString) != inherit.fmStrLen ) {
+ goto loser;
+ }
+
+ memset(cache, 0, sizeof *cache);
+ cache->cacheMemSize = inherit.cacheMemSize;
+
+ /* Create cache */
+ cache->cacheMemMap = PR_ImportFileMapFromString(fmString);
+ if(! cache->cacheMemMap) {
+ goto loser;
+ }
+ cache->cacheMem = PR_MemMap(cache->cacheMemMap, 0, cache->cacheMemSize);
+ if (! cache->cacheMem) {
+ goto loser;
+ }
+ cache->sharedCache = (cacheDesc *)cache->cacheMem;
+
+ if (cache->sharedCache->cacheMemSize != cache->cacheMemSize) {
+ SET_ERROR_CODE
+ goto loser;
+ }
+
+ /* We're now going to overwrite the local cache instance with the
+ ** shared copy of the cache struct, then update several values in
+ ** the local cache using the values for cache->cacheMemMap and
+ ** cache->cacheMem computed just above. So, we copy cache into
+ ** the automatic variable "my", to preserve the variables while
+ ** cache is overwritten.
+ */
+ my = *cache; /* save values computed above. */
+ memcpy(cache, cache->sharedCache, sizeof *cache); /* overwrite */
+
+ /* Fix pointers in our private copy of cache descriptor to point to
+ ** spaces in shared memory, whose address is now in "my".
+ */
+ ptr = (ptrdiff_t)my.cacheMem;
+ *(ptrdiff_t *)(&cache->sidCacheLocks) += ptr;
+ *(ptrdiff_t *)(&cache->keyCacheLock ) += ptr;
+ *(ptrdiff_t *)(&cache->certCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheLock) += ptr;
+ *(ptrdiff_t *)(&cache->sidCacheSets ) += ptr;
+ *(ptrdiff_t *)(&cache->sidCacheData ) += ptr;
+ *(ptrdiff_t *)(&cache->certCacheData) += ptr;
+ *(ptrdiff_t *)(&cache->keyCacheData ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketKeyNameSuffix) += ptr;
+ *(ptrdiff_t *)(&cache->ticketEncKey ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketMacKey ) += ptr;
+ *(ptrdiff_t *)(&cache->ticketKeysValid) += ptr;
+ *(ptrdiff_t *)(&cache->srvNameCacheData) += ptr;
+
+ cache->cacheMemMap = my.cacheMemMap;
+ cache->cacheMem = my.cacheMem;
+ cache->sharedCache = (cacheDesc *)cache->cacheMem;
+
+#ifdef WINNT
+ /* On Windows NT we need to "fix" the sidCacheLocks here to support fibers
+ ** When NT fibers are used in a multi-process server, a second level of
+ ** locking is needed to prevent a deadlock, in case a fiber acquires the
+ ** cross-process mutex, yields, and another fiber is later scheduled on
+ ** the same native thread and tries to acquire the cross-process mutex.
+ ** We do this by using a PRLock in the sslMutex. However, it is stored in
+ ** shared memory as part of sidCacheLocks, and we don't want to overwrite
+ ** the PRLock of the parent process. So we need to make new, private
+ ** copies of sidCacheLocks before modifying the sslMutex with our own
+ ** PRLock
+ */
+
+ /* note from jpierre : this should be free'd in child processes when
+ ** a function is added to delete the SSL session cache in the future.
+ */
+ locks_to_initialize = cache->numSIDCacheLocks + 3;
+ newLocks = PORT_NewArray(sidCacheLock, locks_to_initialize);
+ if (!newLocks)
+ goto loser;
+ /* copy the old locks */
+ memcpy(newLocks, cache->sidCacheLocks,
+ locks_to_initialize * sizeof(sidCacheLock));
+ cache->sidCacheLocks = newLocks;
+ /* fix the locks */
+ for (; locks_initialized < locks_to_initialize; ++locks_initialized) {
+ /* now, make a local PRLock in this sslMutex for this child process */
+ SECStatus err;
+ err = sslMutex_2LevelInit(&newLocks[locks_initialized].mutex);
+ if (err != SECSuccess) {
+ cache->numSIDCacheLocksInitialized = locks_initialized;
+ goto loser;
+ }
+ }
+ cache->numSIDCacheLocksInitialized = locks_initialized;
+
+ /* also fix the key and cert cache which use the last 2 lock entries */
+ cache->keyCacheLock = cache->sidCacheLocks + cache->numSIDCacheLocks;
+ cache->certCacheLock = cache->keyCacheLock + 1;
+ cache->srvNameCacheLock = cache->certCacheLock + 1;
+#endif
+
+ PORT_Free(myEnvString);
+ PORT_Free(decoString);
+
+ /* mark that we have inherited this. */
+ cache->sharedCache->everInherited = PR_TRUE;
+ isMultiProcess = PR_TRUE;
+
+ return SECSuccess;
+
+loser:
+ PORT_Free(myEnvString);
+ if (decoString)
+ PORT_Free(decoString);
+ CloseCache(cache);
+ return SECFailure;
+}
+
+SECStatus
+SSL_InheritMPServerSIDCache(const char * envString)
+{
+ return SSL_InheritMPServerSIDCacheInstance(&globalCache, envString);
+}
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+
+#define SID_LOCK_EXPIRATION_TIMEOUT 30 /* seconds */
+
+static void
+LockPoller(void * arg)
+{
+ cacheDesc * cache = (cacheDesc *)arg;
+ cacheDesc * sharedCache = cache->sharedCache;
+ sidCacheLock * pLock;
+ PRIntervalTime timeout;
+ PRUint32 now;
+ PRUint32 then;
+ int locks_polled = 0;
+ int locks_to_poll = cache->numSIDCacheLocks + 2;
+ PRUint32 expiration = cache->mutexTimeout;
+
+ timeout = PR_SecondsToInterval(expiration);
+ while(!sharedCache->stopPolling) {
+ PR_Sleep(timeout);
+ if (sharedCache->stopPolling)
+ break;
+
+ now = ssl_Time();
+ then = now - expiration;
+ for (pLock = cache->sidCacheLocks, locks_polled = 0;
+ locks_to_poll > locks_polled && !sharedCache->stopPolling;
+ ++locks_polled, ++pLock ) {
+ pid_t pid;
+
+ if (pLock->timeStamp < then &&
+ pLock->timeStamp != 0 &&
+ (pid = pLock->pid) != 0) {
+
+ /* maybe we should try the lock? */
+ int result = kill(pid, 0);
+ if (result < 0 && errno == ESRCH) {
+ SECStatus rv;
+ /* No process exists by that pid any more.
+ ** Treat this mutex as abandoned.
+ */
+ pLock->timeStamp = now;
+ pLock->pid = 0;
+ rv = sslMutex_Unlock(&pLock->mutex);
+ if (rv != SECSuccess) {
+ /* Now what? */
+ }
+ }
+ }
+ } /* end of loop over locks */
+ } /* end of entire polling loop */
+}
+
+/* Launch thread to poll cache for expired locks */
+static SECStatus
+LaunchLockPoller(cacheDesc *cache)
+{
+ const char * timeoutString;
+ PRThread * pollerThread;
+
+ cache->mutexTimeout = SID_LOCK_EXPIRATION_TIMEOUT;
+ timeoutString = getenv("NSS_SSL_SERVER_CACHE_MUTEX_TIMEOUT");
+ if (timeoutString) {
+ long newTime = strtol(timeoutString, 0, 0);
+ if (newTime == 0)
+ return SECSuccess; /* application doesn't want poller thread */
+ if (newTime > 0)
+ cache->mutexTimeout = (PRUint32)newTime;
+ /* if error (newTime < 0) ignore it and use default */
+ }
+
+ pollerThread =
+ PR_CreateThread(PR_USER_THREAD, LockPoller, cache, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
+ if (!pollerThread) {
+ return SECFailure;
+ }
+ cache->poller = pollerThread;
+ return SECSuccess;
+}
+
+/* Stop the thread that polls cache for expired locks */
+static SECStatus
+StopLockPoller(cacheDesc *cache)
+{
+ if (!cache->poller) {
+ return SECSuccess;
+ }
+ cache->sharedCache->stopPolling = PR_TRUE;
+ if (PR_Interrupt(cache->poller) != PR_SUCCESS) {
+ return SECFailure;
+ }
+ if (PR_JoinThread(cache->poller) != PR_SUCCESS) {
+ return SECFailure;
+ }
+ cache->poller = NULL;
+ return SECSuccess;
+}
+#endif
+
+/************************************************************************
+ * Code dealing with shared wrapped symmetric wrapping keys below *
+ ************************************************************************/
+
+/* If now is zero, it implies that the lock is not held, and must be
+** aquired here.
+*/
+static PRBool
+getSvrWrappingKey(PRInt32 symWrapMechIndex,
+ SSL3KEAType exchKeyType,
+ SSLWrappedSymWrappingKey *wswk,
+ cacheDesc * cache,
+ PRUint32 lockTime)
+{
+ PRUint32 ndx = (exchKeyType * SSL_NUM_WRAP_MECHS) + symWrapMechIndex;
+ SSLWrappedSymWrappingKey * pwswk = cache->keyCacheData + ndx;
+ PRUint32 now = 0;
+ PRBool rv = PR_FALSE;
+
+ if (!cache->cacheMem) { /* cache is uninitialized */
+ PORT_SetError(SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED);
+ return rv;
+ }
+ if (!lockTime) {
+ lockTime = now = LockSidCacheLock(cache->keyCacheLock, now);
+ if (!lockTime) {
+ return rv;
+ }
+ }
+ if (pwswk->exchKeyType == exchKeyType &&
+ pwswk->symWrapMechIndex == symWrapMechIndex &&
+ pwswk->wrappedSymKeyLen != 0) {
+ *wswk = *pwswk;
+ rv = PR_TRUE;
+ }
+ if (now) {
+ UnlockSidCacheLock(cache->keyCacheLock);
+ }
+ return rv;
+}
+
+PRBool
+ssl_GetWrappingKey( PRInt32 symWrapMechIndex,
+ SSL3KEAType exchKeyType,
+ SSLWrappedSymWrappingKey *wswk)
+{
+ PRBool rv;
+
+ PORT_Assert( (unsigned)exchKeyType < kt_kea_size);
+ PORT_Assert( (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS);
+ if ((unsigned)exchKeyType < kt_kea_size &&
+ (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS) {
+ rv = getSvrWrappingKey(symWrapMechIndex, exchKeyType, wswk,
+ &globalCache, 0);
+ } else {
+ rv = PR_FALSE;
+ }
+
+ return rv;
+}
+
+/* Wrap and cache a session ticket key. */
+static PRBool
+WrapTicketKey(SECKEYPublicKey *svrPubKey, PK11SymKey *symKey,
+ const char *keyName, encKeyCacheEntry* cacheEntry)
+{
+ SECItem wrappedKey = {siBuffer, NULL, 0};
+
+ wrappedKey.len = SECKEY_PublicKeyStrength(svrPubKey);
+ PORT_Assert(wrappedKey.len <= sizeof(cacheEntry->bytes));
+ if (wrappedKey.len > sizeof(cacheEntry->bytes))
+ return PR_FALSE;
+ wrappedKey.data = cacheEntry->bytes;
+
+ if (PK11_PubWrapSymKey(CKM_RSA_PKCS, svrPubKey, symKey, &wrappedKey)
+ != SECSuccess) {
+ SSL_DBG(("%d: SSL[%s]: Unable to wrap session ticket %s.",
+ SSL_GETPID(), "unknown", keyName));
+ return PR_FALSE;
+ }
+ cacheEntry->length = wrappedKey.len;
+ return PR_TRUE;
+}
+
+static PRBool
+GenerateTicketKeys(void *pwArg, unsigned char *keyName, PK11SymKey **aesKey,
+ PK11SymKey **macKey)
+{
+ PK11SlotInfo *slot;
+ CK_MECHANISM_TYPE mechanismArray[2];
+ PK11SymKey *aesKeyTmp = NULL;
+ PK11SymKey *macKeyTmp = NULL;
+ cacheDesc *cache = &globalCache;
+ PRUint8 ticketKeyNameSuffixLocal[SESS_TICKET_KEY_VAR_NAME_LEN];
+ PRUint8 *ticketKeyNameSuffix;
+
+ if (!cache->cacheMem) {
+ /* cache is not initalized. Use stack buffer */
+ ticketKeyNameSuffix = ticketKeyNameSuffixLocal;
+ } else {
+ ticketKeyNameSuffix = cache->ticketKeyNameSuffix;
+ }
+
+ if (PK11_GenerateRandom(ticketKeyNameSuffix,
+ SESS_TICKET_KEY_VAR_NAME_LEN) != SECSuccess) {
+ SSL_DBG(("%d: SSL[%s]: Unable to generate random key name bytes.",
+ SSL_GETPID(), "unknown"));
+ goto loser;
+ }
+
+ mechanismArray[0] = CKM_AES_CBC;
+ mechanismArray[1] = CKM_SHA256_HMAC;
+
+ slot = PK11_GetBestSlotMultiple(mechanismArray, 2, pwArg);
+ if (slot) {
+ aesKeyTmp = PK11_KeyGen(slot, mechanismArray[0], NULL,
+ AES_256_KEY_LENGTH, pwArg);
+ macKeyTmp = PK11_KeyGen(slot, mechanismArray[1], NULL,
+ SHA256_LENGTH, pwArg);
+ PK11_FreeSlot(slot);
+ }
+
+ if (aesKeyTmp == NULL || macKeyTmp == NULL) {
+ SSL_DBG(("%d: SSL[%s]: Unable to generate session ticket keys.",
+ SSL_GETPID(), "unknown"));
+ goto loser;
+ }
+ PORT_Memcpy(keyName, ticketKeyNameSuffix, SESS_TICKET_KEY_VAR_NAME_LEN);
+ *aesKey = aesKeyTmp;
+ *macKey = macKeyTmp;
+ return PR_TRUE;
+
+loser:
+ if (aesKeyTmp)
+ PK11_FreeSymKey(aesKeyTmp);
+ if (macKeyTmp)
+ PK11_FreeSymKey(macKeyTmp);
+ return PR_FALSE;
+}
+
+static PRBool
+GenerateAndWrapTicketKeys(SECKEYPublicKey *svrPubKey, void *pwArg,
+ unsigned char *keyName, PK11SymKey **aesKey,
+ PK11SymKey **macKey)
+{
+ PK11SymKey *aesKeyTmp = NULL;
+ PK11SymKey *macKeyTmp = NULL;
+ cacheDesc *cache = &globalCache;
+
+ if (!GenerateTicketKeys(pwArg, keyName, &aesKeyTmp, &macKeyTmp)) {
+ goto loser;
+ }
+
+ if (cache->cacheMem) {
+ /* Export the keys to the shared cache in wrapped form. */
+ if (!WrapTicketKey(svrPubKey, aesKeyTmp, "enc key", cache->ticketEncKey))
+ goto loser;
+ if (!WrapTicketKey(svrPubKey, macKeyTmp, "mac key", cache->ticketMacKey))
+ goto loser;
+ }
+ *aesKey = aesKeyTmp;
+ *macKey = macKeyTmp;
+ return PR_TRUE;
+
+loser:
+ if (aesKeyTmp)
+ PK11_FreeSymKey(aesKeyTmp);
+ if (macKeyTmp)
+ PK11_FreeSymKey(macKeyTmp);
+ return PR_FALSE;
+}
+
+static PRBool
+UnwrapCachedTicketKeys(SECKEYPrivateKey *svrPrivKey, unsigned char *keyName,
+ PK11SymKey **aesKey, PK11SymKey **macKey)
+{
+ SECItem wrappedKey = {siBuffer, NULL, 0};
+ PK11SymKey *aesKeyTmp = NULL;
+ PK11SymKey *macKeyTmp = NULL;
+ cacheDesc *cache = &globalCache;
+
+ wrappedKey.data = cache->ticketEncKey->bytes;
+ wrappedKey.len = cache->ticketEncKey->length;
+ PORT_Assert(wrappedKey.len <= sizeof(cache->ticketEncKey->bytes));
+ aesKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey,
+ CKM_AES_CBC, CKA_DECRYPT, 0);
+
+ wrappedKey.data = cache->ticketMacKey->bytes;
+ wrappedKey.len = cache->ticketMacKey->length;
+ PORT_Assert(wrappedKey.len <= sizeof(cache->ticketMacKey->bytes));
+ macKeyTmp = PK11_PubUnwrapSymKey(svrPrivKey, &wrappedKey,
+ CKM_SHA256_HMAC, CKA_SIGN, 0);
+
+ if (aesKeyTmp == NULL || macKeyTmp == NULL) {
+ SSL_DBG(("%d: SSL[%s]: Unable to unwrap session ticket keys.",
+ SSL_GETPID(), "unknown"));
+ goto loser;
+ }
+ SSL_DBG(("%d: SSL[%s]: Successfully unwrapped session ticket keys.",
+ SSL_GETPID(), "unknown"));
+
+ PORT_Memcpy(keyName, cache->ticketKeyNameSuffix,
+ SESS_TICKET_KEY_VAR_NAME_LEN);
+ *aesKey = aesKeyTmp;
+ *macKey = macKeyTmp;
+ return PR_TRUE;
+
+loser:
+ if (aesKeyTmp)
+ PK11_FreeSymKey(aesKeyTmp);
+ if (macKeyTmp)
+ PK11_FreeSymKey(macKeyTmp);
+ return PR_FALSE;
+}
+
+PRBool
+ssl_GetSessionTicketKeysPKCS11(SECKEYPrivateKey *svrPrivKey,
+ SECKEYPublicKey *svrPubKey, void *pwArg,
+ unsigned char *keyName, PK11SymKey **aesKey,
+ PK11SymKey **macKey)
+{
+ PRUint32 now = 0;
+ PRBool rv = PR_FALSE;
+ PRBool keysGenerated = PR_FALSE;
+ cacheDesc *cache = &globalCache;
+
+ if (!cache->cacheMem) {
+ /* cache is uninitialized. Generate keys and return them
+ * without caching. */
+ return GenerateTicketKeys(pwArg, keyName, aesKey, macKey);
+ }
+
+ now = LockSidCacheLock(cache->keyCacheLock, now);
+ if (!now)
+ return rv;
+
+ if (!*(cache->ticketKeysValid)) {
+ /* Keys do not exist, create them. */
+ if (!GenerateAndWrapTicketKeys(svrPubKey, pwArg, keyName,
+ aesKey, macKey))
+ goto loser;
+ keysGenerated = PR_TRUE;
+ *(cache->ticketKeysValid) = 1;
+ }
+
+ rv = PR_TRUE;
+
+ loser:
+ UnlockSidCacheLock(cache->keyCacheLock);
+ if (rv && !keysGenerated)
+ rv = UnwrapCachedTicketKeys(svrPrivKey, keyName, aesKey, macKey);
+ return rv;
+}
+
+PRBool
+ssl_GetSessionTicketKeys(unsigned char *keyName, unsigned char *encKey,
+ unsigned char *macKey)
+{
+ PRBool rv = PR_FALSE;
+ PRUint32 now = 0;
+ cacheDesc *cache = &globalCache;
+ PRUint8 ticketMacKey[SHA256_LENGTH], ticketEncKey[AES_256_KEY_LENGTH];
+ PRUint8 ticketKeyNameSuffixLocal[SESS_TICKET_KEY_VAR_NAME_LEN];
+ PRUint8 *ticketMacKeyPtr, *ticketEncKeyPtr, *ticketKeyNameSuffix;
+ PRBool cacheIsEnabled = PR_TRUE;
+
+ if (!cache->cacheMem) { /* cache is uninitialized */
+ cacheIsEnabled = PR_FALSE;
+ ticketKeyNameSuffix = ticketKeyNameSuffixLocal;
+ ticketEncKeyPtr = ticketEncKey;
+ ticketMacKeyPtr = ticketMacKey;
+ } else {
+ /* these values have constant memory locations in the cache.
+ * Ok to reference them without holding the lock. */
+ ticketKeyNameSuffix = cache->ticketKeyNameSuffix;
+ ticketEncKeyPtr = cache->ticketEncKey->bytes;
+ ticketMacKeyPtr = cache->ticketMacKey->bytes;
+ }
+
+ if (cacheIsEnabled) {
+ /* Grab lock if initialized. */
+ now = LockSidCacheLock(cache->keyCacheLock, now);
+ if (!now)
+ return rv;
+ }
+ /* Going to regenerate keys on every call if cache was not
+ * initialized. */
+ if (!cacheIsEnabled || !*(cache->ticketKeysValid)) {
+ if (PK11_GenerateRandom(ticketKeyNameSuffix,
+ SESS_TICKET_KEY_VAR_NAME_LEN) != SECSuccess)
+ goto loser;
+ if (PK11_GenerateRandom(ticketEncKeyPtr,
+ AES_256_KEY_LENGTH) != SECSuccess)
+ goto loser;
+ if (PK11_GenerateRandom(ticketMacKeyPtr,
+ SHA256_LENGTH) != SECSuccess)
+ goto loser;
+ if (cacheIsEnabled) {
+ *(cache->ticketKeysValid) = 1;
+ }
+ }
+
+ rv = PR_TRUE;
+
+ loser:
+ if (cacheIsEnabled) {
+ UnlockSidCacheLock(cache->keyCacheLock);
+ }
+ if (rv) {
+ PORT_Memcpy(keyName, ticketKeyNameSuffix,
+ SESS_TICKET_KEY_VAR_NAME_LEN);
+ PORT_Memcpy(encKey, ticketEncKeyPtr, AES_256_KEY_LENGTH);
+ PORT_Memcpy(macKey, ticketMacKeyPtr, SHA256_LENGTH);
+ }
+ return rv;
+}
+
+/* The caller passes in the new value it wants
+ * to set. This code tests the wrapped sym key entry in the shared memory.
+ * If it is uninitialized, this function writes the caller's value into
+ * the disk entry, and returns false.
+ * Otherwise, it overwrites the caller's wswk with the value obtained from
+ * the disk, and returns PR_TRUE.
+ * This is all done while holding the locks/mutexes necessary to make
+ * the operation atomic.
+ */
+PRBool
+ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk)
+{
+ cacheDesc * cache = &globalCache;
+ PRBool rv = PR_FALSE;
+ SSL3KEAType exchKeyType = wswk->exchKeyType;
+ /* type of keys used to wrap SymWrapKey*/
+ PRInt32 symWrapMechIndex = wswk->symWrapMechIndex;
+ PRUint32 ndx;
+ PRUint32 now = 0;
+ SSLWrappedSymWrappingKey myWswk;
+
+ if (!cache->cacheMem) { /* cache is uninitialized */
+ PORT_SetError(SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED);
+ return 0;
+ }
+
+ PORT_Assert( (unsigned)exchKeyType < kt_kea_size);
+ if ((unsigned)exchKeyType >= kt_kea_size)
+ return 0;
+
+ PORT_Assert( (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS);
+ if ((unsigned)symWrapMechIndex >= SSL_NUM_WRAP_MECHS)
+ return 0;
+
+ ndx = (exchKeyType * SSL_NUM_WRAP_MECHS) + symWrapMechIndex;
+ PORT_Memset(&myWswk, 0, sizeof myWswk); /* eliminate UMRs. */
+
+ now = LockSidCacheLock(cache->keyCacheLock, now);
+ if (now) {
+ rv = getSvrWrappingKey(wswk->symWrapMechIndex, wswk->exchKeyType,
+ &myWswk, cache, now);
+ if (rv) {
+ /* we found it on disk, copy it out to the caller. */
+ PORT_Memcpy(wswk, &myWswk, sizeof *wswk);
+ } else {
+ /* Wasn't on disk, and we're still holding the lock, so write it. */
+ cache->keyCacheData[ndx] = *wswk;
+ }
+ UnlockSidCacheLock(cache->keyCacheLock);
+ }
+ return rv;
+}
+
+#else /* MAC version or other platform */
+
+#include "seccomon.h"
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+
+SECStatus
+SSL_ConfigServerSessionIDCache( int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory)
+{
+ PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_ConfigServerSessionIDCache)");
+ return SECFailure;
+}
+
+SECStatus
+SSL_ConfigMPServerSIDCache( int maxCacheEntries,
+ PRUint32 ssl2_timeout,
+ PRUint32 ssl3_timeout,
+ const char * directory)
+{
+ PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_ConfigMPServerSIDCache)");
+ return SECFailure;
+}
+
+SECStatus
+SSL_InheritMPServerSIDCache(const char * envString)
+{
+ PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_InheritMPServerSIDCache)");
+ return SECFailure;
+}
+
+PRBool
+ssl_GetWrappingKey( PRInt32 symWrapMechIndex,
+ SSL3KEAType exchKeyType,
+ SSLWrappedSymWrappingKey *wswk)
+{
+ PRBool rv = PR_FALSE;
+ PR_ASSERT(!"SSL servers are not supported on this platform. (ssl_GetWrappingKey)");
+ return rv;
+}
+
+/* This is a kind of test-and-set. The caller passes in the new value it wants
+ * to set. This code tests the wrapped sym key entry in the shared memory.
+ * If it is uninitialized, this function writes the caller's value into
+ * the disk entry, and returns false.
+ * Otherwise, it overwrites the caller's wswk with the value obtained from
+ * the disk, and returns PR_TRUE.
+ * This is all done while holding the locks/mutexes necessary to make
+ * the operation atomic.
+ */
+PRBool
+ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk)
+{
+ PRBool rv = PR_FALSE;
+ PR_ASSERT(!"SSL servers are not supported on this platform. (ssl_SetWrappingKey)");
+ return rv;
+}
+
+PRUint32
+SSL_GetMaxServerCacheLocks(void)
+{
+ PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_GetMaxServerCacheLocks)");
+ return -1;
+}
+
+SECStatus
+SSL_SetMaxServerCacheLocks(PRUint32 maxLocks)
+{
+ PR_ASSERT(!"SSL servers are not supported on this platform. (SSL_SetMaxServerCacheLocks)");
+ return SECFailure;
+}
+
+#endif /* XP_UNIX || XP_WIN32 */
diff --git a/chromium/net/third_party/nss/ssl/sslsock.c b/chromium/net/third_party/nss/ssl/sslsock.c
new file mode 100644
index 00000000000..c17c7a3ad03
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslsock.c
@@ -0,0 +1,3128 @@
+/*
+ * vtables (and methods that call through them) for the 4 types of
+ * SSLSockets supported. Only one type is still supported.
+ * Various other functions.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "seccomon.h"
+#include "cert.h"
+#include "keyhi.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "nspr.h"
+#include "private/pprio.h"
+#ifndef NO_PKCS11_BYPASS
+#include "blapi.h"
+#endif
+#include "pk11pub.h"
+#include "nss.h"
+
+/* This is a bodge to allow this code to be compiled against older NSS headers
+ * that don't contain the TLS 1.2 changes. */
+#ifndef CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256
+#define CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256 (CKM_NSS + 24)
+#endif
+
+#define SET_ERROR_CODE /* reminder */
+
+struct cipherPolicyStr {
+ int cipher;
+ unsigned char export; /* policy value for export policy */
+ unsigned char france; /* policy value for france policy */
+};
+
+typedef struct cipherPolicyStr cipherPolicy;
+
+/* This table contains two preconfigured policies: Export and France.
+** It is used only by the functions NSS_SetDomesticPolicy,
+** NSS_SetExportPolicy, and NSS_SetFrancePolicy.
+** Order of entries is not important.
+*/
+static cipherPolicy ssl_ciphers[] = { /* Export France */
+ { SSL_EN_RC4_128_WITH_MD5, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_EN_RC4_128_EXPORT40_WITH_MD5, SSL_ALLOWED, SSL_ALLOWED },
+ { SSL_EN_RC2_128_CBC_WITH_MD5, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5, SSL_ALLOWED, SSL_ALLOWED },
+ { SSL_EN_DES_64_CBC_WITH_MD5, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_EN_DES_192_EDE3_CBC_WITH_MD5, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_RSA_WITH_RC4_128_MD5, SSL_RESTRICTED, SSL_NOT_ALLOWED },
+ { SSL_RSA_WITH_RC4_128_SHA, SSL_RESTRICTED, SSL_NOT_ALLOWED },
+ { SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RESTRICTED, SSL_NOT_ALLOWED },
+ { SSL_RSA_FIPS_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_RSA_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_ALLOWED, SSL_ALLOWED },
+ { SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5, SSL_ALLOWED, SSL_ALLOWED },
+ { SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_DSS_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { SSL_RSA_WITH_NULL_MD5, SSL_ALLOWED, SSL_ALLOWED },
+ { SSL_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_RSA_WITH_NULL_SHA256, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_AES_256_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_WITH_SEED_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, SSL_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, SSL_ALLOWED, SSL_NOT_ALLOWED },
+#ifdef NSS_ENABLE_ECC
+ { TLS_ECDH_ECDSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_ECDH_ECDSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_NULL_SHA, SSL_ALLOWED, SSL_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_RC4_128_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+ { TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED },
+#endif /* NSS_ENABLE_ECC */
+ { 0, SSL_NOT_ALLOWED, SSL_NOT_ALLOWED }
+};
+
+static const sslSocketOps ssl_default_ops = { /* No SSL. */
+ ssl_DefConnect,
+ NULL,
+ ssl_DefBind,
+ ssl_DefListen,
+ ssl_DefShutdown,
+ ssl_DefClose,
+ ssl_DefRecv,
+ ssl_DefSend,
+ ssl_DefRead,
+ ssl_DefWrite,
+ ssl_DefGetpeername,
+ ssl_DefGetsockname
+};
+
+static const sslSocketOps ssl_secure_ops = { /* SSL. */
+ ssl_SecureConnect,
+ NULL,
+ ssl_DefBind,
+ ssl_DefListen,
+ ssl_SecureShutdown,
+ ssl_SecureClose,
+ ssl_SecureRecv,
+ ssl_SecureSend,
+ ssl_SecureRead,
+ ssl_SecureWrite,
+ ssl_DefGetpeername,
+ ssl_DefGetsockname
+};
+
+/*
+** default settings for socket enables
+*/
+static sslOptions ssl_defaults = {
+ { siBuffer, NULL, 0 }, /* nextProtoNego */
+ PR_TRUE, /* useSecurity */
+ PR_FALSE, /* useSocks */
+ PR_FALSE, /* requestCertificate */
+ 2, /* requireCertificate */
+ PR_FALSE, /* handshakeAsClient */
+ PR_FALSE, /* handshakeAsServer */
+ PR_FALSE, /* enableSSL2 */ /* now defaults to off in NSS 3.13 */
+ PR_FALSE, /* unusedBit9 */
+ PR_FALSE, /* unusedBit10 */
+ PR_FALSE, /* noCache */
+ PR_FALSE, /* fdx */
+ PR_FALSE, /* v2CompatibleHello */ /* now defaults to off in NSS 3.13 */
+ PR_TRUE, /* detectRollBack */
+ PR_FALSE, /* noStepDown */
+ PR_FALSE, /* bypassPKCS11 */
+ PR_FALSE, /* noLocks */
+ PR_FALSE, /* enableSessionTickets */
+ PR_FALSE, /* enableDeflate */
+ 2, /* enableRenegotiation (default: requires extension) */
+ PR_FALSE, /* requireSafeNegotiation */
+ PR_FALSE, /* enableFalseStart */
+ PR_TRUE, /* cbcRandomIV */
+ PR_FALSE /* enableOCSPStapling */
+};
+
+/*
+ * default range of enabled SSL/TLS protocols
+ */
+static SSLVersionRange versions_defaults_stream = {
+ SSL_LIBRARY_VERSION_3_0,
+ SSL_LIBRARY_VERSION_TLS_1_0
+};
+
+static SSLVersionRange versions_defaults_datagram = {
+ SSL_LIBRARY_VERSION_TLS_1_1,
+ SSL_LIBRARY_VERSION_TLS_1_1
+};
+
+#define VERSIONS_DEFAULTS(variant) \
+ (variant == ssl_variant_stream ? &versions_defaults_stream : \
+ &versions_defaults_datagram)
+
+sslSessionIDLookupFunc ssl_sid_lookup;
+sslSessionIDCacheFunc ssl_sid_cache;
+sslSessionIDUncacheFunc ssl_sid_uncache;
+
+static PRBool ssl_inited = PR_FALSE;
+static PRDescIdentity ssl_layer_id;
+
+PRBool locksEverDisabled; /* implicitly PR_FALSE */
+PRBool ssl_force_locks; /* implicitly PR_FALSE */
+int ssl_lock_readers = 1; /* default true. */
+char ssl_debug;
+char ssl_trace;
+FILE * ssl_trace_iob;
+FILE * ssl_keylog_iob;
+char lockStatus[] = "Locks are ENABLED. ";
+#define LOCKSTATUS_OFFSET 10 /* offset of ENABLED */
+
+/* SRTP_NULL_HMAC_SHA1_80 and SRTP_NULL_HMAC_SHA1_32 are not implemented. */
+static const PRUint16 srtpCiphers[] = {
+ SRTP_AES128_CM_HMAC_SHA1_80,
+ SRTP_AES128_CM_HMAC_SHA1_32,
+ 0
+};
+
+/* forward declarations. */
+static sslSocket *ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant variant);
+static SECStatus ssl_MakeLocks(sslSocket *ss);
+static void ssl_SetDefaultsFromEnvironment(void);
+static PRStatus ssl_PushIOLayer(sslSocket *ns, PRFileDesc *stack,
+ PRDescIdentity id);
+
+/************************************************************************/
+
+/*
+** Lookup a socket structure from a file descriptor.
+** Only functions called through the PRIOMethods table should use this.
+** Other app-callable functions should use ssl_FindSocket.
+*/
+static sslSocket *
+ssl_GetPrivate(PRFileDesc *fd)
+{
+ sslSocket *ss;
+
+ PORT_Assert(fd != NULL);
+ PORT_Assert(fd->methods->file_type == PR_DESC_LAYERED);
+ PORT_Assert(fd->identity == ssl_layer_id);
+
+ if (fd->methods->file_type != PR_DESC_LAYERED ||
+ fd->identity != ssl_layer_id) {
+ PORT_SetError(PR_BAD_DESCRIPTOR_ERROR);
+ return NULL;
+ }
+
+ ss = (sslSocket *)fd->secret;
+ ss->fd = fd;
+ return ss;
+}
+
+/* This function tries to find the SSL layer in the stack.
+ * It searches for the first SSL layer at or below the argument fd,
+ * and failing that, it searches for the nearest SSL layer above the
+ * argument fd. It returns the private sslSocket from the found layer.
+ */
+sslSocket *
+ssl_FindSocket(PRFileDesc *fd)
+{
+ PRFileDesc *layer;
+ sslSocket *ss;
+
+ PORT_Assert(fd != NULL);
+ PORT_Assert(ssl_layer_id != 0);
+
+ layer = PR_GetIdentitiesLayer(fd, ssl_layer_id);
+ if (layer == NULL) {
+ PORT_SetError(PR_BAD_DESCRIPTOR_ERROR);
+ return NULL;
+ }
+
+ ss = (sslSocket *)layer->secret;
+ ss->fd = layer;
+ return ss;
+}
+
+static sslSocket *
+ssl_DupSocket(sslSocket *os)
+{
+ sslSocket *ss;
+ SECStatus rv;
+
+ ss = ssl_NewSocket((PRBool)(!os->opt.noLocks), os->protocolVariant);
+ if (ss) {
+ ss->opt = os->opt;
+ ss->opt.useSocks = PR_FALSE;
+ ss->vrange = os->vrange;
+
+ ss->peerID = !os->peerID ? NULL : PORT_Strdup(os->peerID);
+ ss->url = !os->url ? NULL : PORT_Strdup(os->url);
+
+ ss->ops = os->ops;
+ ss->rTimeout = os->rTimeout;
+ ss->wTimeout = os->wTimeout;
+ ss->cTimeout = os->cTimeout;
+ ss->dbHandle = os->dbHandle;
+
+ /* copy ssl2&3 policy & prefs, even if it's not selected (yet) */
+ ss->allowedByPolicy = os->allowedByPolicy;
+ ss->maybeAllowedByPolicy= os->maybeAllowedByPolicy;
+ ss->chosenPreference = os->chosenPreference;
+ PORT_Memcpy(ss->cipherSuites, os->cipherSuites, sizeof os->cipherSuites);
+ PORT_Memcpy(ss->ssl3.dtlsSRTPCiphers, os->ssl3.dtlsSRTPCiphers,
+ sizeof(PRUint16) * os->ssl3.dtlsSRTPCipherCount);
+ ss->ssl3.dtlsSRTPCipherCount = os->ssl3.dtlsSRTPCipherCount;
+
+ if (os->cipherSpecs) {
+ ss->cipherSpecs = (unsigned char*)PORT_Alloc(os->sizeCipherSpecs);
+ if (ss->cipherSpecs)
+ PORT_Memcpy(ss->cipherSpecs, os->cipherSpecs,
+ os->sizeCipherSpecs);
+ ss->sizeCipherSpecs = os->sizeCipherSpecs;
+ ss->preferredCipher = os->preferredCipher;
+ } else {
+ ss->cipherSpecs = NULL; /* produced lazily */
+ ss->sizeCipherSpecs = 0;
+ ss->preferredCipher = NULL;
+ }
+ if (ss->opt.useSecurity) {
+ /* This int should be SSLKEAType, but CC on Irix complains,
+ * during the for loop.
+ */
+ int i;
+ sslServerCerts * oc = os->serverCerts;
+ sslServerCerts * sc = ss->serverCerts;
+
+ for (i=kt_null; i < kt_kea_size; i++, oc++, sc++) {
+ if (oc->serverCert && oc->serverCertChain) {
+ sc->serverCert = CERT_DupCertificate(oc->serverCert);
+ sc->serverCertChain = CERT_DupCertList(oc->serverCertChain);
+ if (!sc->serverCertChain)
+ goto loser;
+ } else {
+ sc->serverCert = NULL;
+ sc->serverCertChain = NULL;
+ }
+ sc->serverKeyPair = oc->serverKeyPair ?
+ ssl3_GetKeyPairRef(oc->serverKeyPair) : NULL;
+ if (oc->serverKeyPair && !sc->serverKeyPair)
+ goto loser;
+ sc->serverKeyBits = oc->serverKeyBits;
+ ss->certStatusArray[i] = !os->certStatusArray[i] ? NULL :
+ SECITEM_DupArray(NULL, os->certStatusArray[i]);
+ }
+ ss->stepDownKeyPair = !os->stepDownKeyPair ? NULL :
+ ssl3_GetKeyPairRef(os->stepDownKeyPair);
+ ss->ephemeralECDHKeyPair = !os->ephemeralECDHKeyPair ? NULL :
+ ssl3_GetKeyPairRef(os->ephemeralECDHKeyPair);
+/*
+ * XXX the preceding CERT_ and SECKEY_ functions can fail and return NULL.
+ * XXX We should detect this, and not just march on with NULL pointers.
+ */
+ ss->authCertificate = os->authCertificate;
+ ss->authCertificateArg = os->authCertificateArg;
+ ss->getClientAuthData = os->getClientAuthData;
+ ss->getClientAuthDataArg = os->getClientAuthDataArg;
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ ss->getPlatformClientAuthData = os->getPlatformClientAuthData;
+ ss->getPlatformClientAuthDataArg = os->getPlatformClientAuthDataArg;
+#endif
+ ss->sniSocketConfig = os->sniSocketConfig;
+ ss->sniSocketConfigArg = os->sniSocketConfigArg;
+ ss->handleBadCert = os->handleBadCert;
+ ss->badCertArg = os->badCertArg;
+ ss->handshakeCallback = os->handshakeCallback;
+ ss->handshakeCallbackData = os->handshakeCallbackData;
+ ss->pkcs11PinArg = os->pkcs11PinArg;
+ ss->getChannelID = os->getChannelID;
+ ss->getChannelIDArg = os->getChannelIDArg;
+
+ /* Create security data */
+ rv = ssl_CopySecurityInfo(ss, os);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ }
+ return ss;
+
+loser:
+ ssl_FreeSocket(ss);
+ return NULL;
+}
+
+static void
+ssl_DestroyLocks(sslSocket *ss)
+{
+ /* Destroy locks. */
+ if (ss->firstHandshakeLock) {
+ PZ_DestroyMonitor(ss->firstHandshakeLock);
+ ss->firstHandshakeLock = NULL;
+ }
+ if (ss->ssl3HandshakeLock) {
+ PZ_DestroyMonitor(ss->ssl3HandshakeLock);
+ ss->ssl3HandshakeLock = NULL;
+ }
+ if (ss->specLock) {
+ NSSRWLock_Destroy(ss->specLock);
+ ss->specLock = NULL;
+ }
+
+ if (ss->recvLock) {
+ PZ_DestroyLock(ss->recvLock);
+ ss->recvLock = NULL;
+ }
+ if (ss->sendLock) {
+ PZ_DestroyLock(ss->sendLock);
+ ss->sendLock = NULL;
+ }
+ if (ss->xmitBufLock) {
+ PZ_DestroyMonitor(ss->xmitBufLock);
+ ss->xmitBufLock = NULL;
+ }
+ if (ss->recvBufLock) {
+ PZ_DestroyMonitor(ss->recvBufLock);
+ ss->recvBufLock = NULL;
+ }
+}
+
+/* Caller holds any relevant locks */
+static void
+ssl_DestroySocketContents(sslSocket *ss)
+{
+ /* "i" should be of type SSLKEAType, but CC on IRIX complains during
+ * the for loop.
+ */
+ int i;
+
+ /* Free up socket */
+ ssl_DestroySecurityInfo(&ss->sec);
+
+ ssl3_DestroySSL3Info(ss);
+
+ PORT_Free(ss->saveBuf.buf);
+ PORT_Free(ss->pendingBuf.buf);
+ ssl_DestroyGather(&ss->gs);
+
+ if (ss->peerID != NULL)
+ PORT_Free(ss->peerID);
+ if (ss->url != NULL)
+ PORT_Free((void *)ss->url); /* CONST */
+ if (ss->cipherSpecs) {
+ PORT_Free(ss->cipherSpecs);
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0;
+ }
+
+ /* Clean up server configuration */
+ for (i=kt_null; i < kt_kea_size; i++) {
+ sslServerCerts * sc = ss->serverCerts + i;
+ if (sc->serverCert != NULL)
+ CERT_DestroyCertificate(sc->serverCert);
+ if (sc->serverCertChain != NULL)
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ if (sc->serverKeyPair != NULL)
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ if (ss->certStatusArray[i] != NULL) {
+ SECITEM_FreeArray(ss->certStatusArray[i], PR_TRUE);
+ ss->certStatusArray[i] = NULL;
+ }
+ }
+ if (ss->stepDownKeyPair) {
+ ssl3_FreeKeyPair(ss->stepDownKeyPair);
+ ss->stepDownKeyPair = NULL;
+ }
+ if (ss->ephemeralECDHKeyPair) {
+ ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair);
+ ss->ephemeralECDHKeyPair = NULL;
+ }
+ SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
+ PORT_Assert(!ss->xtnData.sniNameArr);
+ if (ss->xtnData.sniNameArr) {
+ PORT_Free(ss->xtnData.sniNameArr);
+ ss->xtnData.sniNameArr = NULL;
+ }
+}
+
+/*
+ * free an sslSocket struct, and all the stuff that hangs off of it
+ */
+void
+ssl_FreeSocket(sslSocket *ss)
+{
+/* Get every lock you can imagine!
+** Caller already holds these:
+** SSL_LOCK_READER(ss);
+** SSL_LOCK_WRITER(ss);
+*/
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetRecvBufLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+ ssl_GetXmitBufLock(ss);
+ ssl_GetSpecWriteLock(ss);
+
+ ssl_DestroySocketContents(ss);
+
+ /* Release all the locks acquired above. */
+ SSL_UNLOCK_READER(ss);
+ SSL_UNLOCK_WRITER(ss);
+ ssl_Release1stHandshakeLock(ss);
+ ssl_ReleaseRecvBufLock(ss);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_ReleaseXmitBufLock(ss);
+ ssl_ReleaseSpecWriteLock(ss);
+
+ ssl_DestroyLocks(ss);
+
+#ifdef DEBUG
+ PORT_Memset(ss, 0x1f, sizeof *ss);
+#endif
+ PORT_Free(ss);
+ return;
+}
+
+/************************************************************************/
+SECStatus
+ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled)
+{
+ PRFileDesc * osfd = ss->fd->lower;
+ SECStatus rv = SECFailure;
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_NoDelay;
+ opt.value.no_delay = (PRBool)!enabled;
+
+ if (osfd->methods->setsocketoption) {
+ rv = (SECStatus) osfd->methods->setsocketoption(osfd, &opt);
+ } else {
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ }
+
+ return rv;
+}
+
+static void
+ssl_ChooseOps(sslSocket *ss)
+{
+ ss->ops = ss->opt.useSecurity ? &ssl_secure_ops : &ssl_default_ops;
+}
+
+/* Called from SSL_Enable (immediately below) */
+static SECStatus
+PrepareSocket(sslSocket *ss)
+{
+ SECStatus rv = SECSuccess;
+
+ ssl_ChooseOps(ss);
+ return rv;
+}
+
+SECStatus
+SSL_Enable(PRFileDesc *fd, int which, PRBool on)
+{
+ return SSL_OptionSet(fd, which, on);
+}
+
+#ifndef NO_PKCS11_BYPASS
+static const PRCallOnceType pristineCallOnce;
+static PRCallOnceType setupBypassOnce;
+
+static SECStatus SSL_BypassShutdown(void* appData, void* nssData)
+{
+ /* unload freeBL shared library from memory */
+ BL_Unload();
+ setupBypassOnce = pristineCallOnce;
+ return SECSuccess;
+}
+
+static PRStatus SSL_BypassRegisterShutdown(void)
+{
+ SECStatus rv = NSS_RegisterShutdown(SSL_BypassShutdown, NULL);
+ PORT_Assert(SECSuccess == rv);
+ return SECSuccess == rv ? PR_SUCCESS : PR_FAILURE;
+}
+#endif
+
+static PRStatus SSL_BypassSetup(void)
+{
+#ifdef NO_PKCS11_BYPASS
+ /* Guarantee binary compatibility */
+ return PR_SUCCESS;
+#else
+ return PR_CallOnce(&setupBypassOnce, &SSL_BypassRegisterShutdown);
+#endif
+}
+
+/* Implements the semantics for SSL_OptionSet(SSL_ENABLE_TLS, on) described in
+ * ssl.h in the section "SSL version range setting API".
+ */
+static void
+ssl_EnableTLS(SSLVersionRange *vrange, PRBool on)
+{
+ if (SSL3_ALL_VERSIONS_DISABLED(vrange)) {
+ if (on) {
+ vrange->min = SSL_LIBRARY_VERSION_TLS_1_0;
+ vrange->max = SSL_LIBRARY_VERSION_TLS_1_0;
+ } /* else don't change anything */
+ return;
+ }
+
+ if (on) {
+ /* Expand the range of enabled version to include TLS 1.0 */
+ vrange->min = PR_MIN(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
+ vrange->max = PR_MAX(vrange->max, SSL_LIBRARY_VERSION_TLS_1_0);
+ } else {
+ /* Disable all TLS versions, leaving only SSL 3.0 if it was enabled */
+ if (vrange->min == SSL_LIBRARY_VERSION_3_0) {
+ vrange->max = SSL_LIBRARY_VERSION_3_0;
+ } else {
+ /* Only TLS was enabled, so now no versions are. */
+ vrange->min = SSL_LIBRARY_VERSION_NONE;
+ vrange->max = SSL_LIBRARY_VERSION_NONE;
+ }
+ }
+}
+
+/* Implements the semantics for SSL_OptionSet(SSL_ENABLE_SSL3, on) described in
+ * ssl.h in the section "SSL version range setting API".
+ */
+static void
+ssl_EnableSSL3(SSLVersionRange *vrange, PRBool on)
+{
+ if (SSL3_ALL_VERSIONS_DISABLED(vrange)) {
+ if (on) {
+ vrange->min = SSL_LIBRARY_VERSION_3_0;
+ vrange->max = SSL_LIBRARY_VERSION_3_0;
+ } /* else don't change anything */
+ return;
+ }
+
+ if (on) {
+ /* Expand the range of enabled versions to include SSL 3.0. We know
+ * SSL 3.0 or some version of TLS is already enabled at this point, so
+ * we don't need to change vrange->max.
+ */
+ vrange->min = SSL_LIBRARY_VERSION_3_0;
+ } else {
+ /* Disable SSL 3.0, leaving TLS unaffected. */
+ if (vrange->max > SSL_LIBRARY_VERSION_3_0) {
+ vrange->min = PR_MAX(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
+ } else {
+ /* Only SSL 3.0 was enabled, so now no versions are. */
+ vrange->min = SSL_LIBRARY_VERSION_NONE;
+ vrange->max = SSL_LIBRARY_VERSION_NONE;
+ }
+ }
+}
+
+SECStatus
+SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRBool on)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+ SECStatus rv = SECSuccess;
+ PRBool holdingLocks;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in Enable", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ holdingLocks = (!ss->opt.noLocks);
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ switch (which) {
+ case SSL_SOCKS:
+ ss->opt.useSocks = PR_FALSE;
+ rv = PrepareSocket(ss);
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+ break;
+
+ case SSL_SECURITY:
+ ss->opt.useSecurity = on;
+ rv = PrepareSocket(ss);
+ break;
+
+ case SSL_REQUEST_CERTIFICATE:
+ ss->opt.requestCertificate = on;
+ break;
+
+ case SSL_REQUIRE_CERTIFICATE:
+ ss->opt.requireCertificate = on;
+ break;
+
+ case SSL_HANDSHAKE_AS_CLIENT:
+ if ( ss->opt.handshakeAsServer && on ) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ break;
+ }
+ ss->opt.handshakeAsClient = on;
+ break;
+
+ case SSL_HANDSHAKE_AS_SERVER:
+ if ( ss->opt.handshakeAsClient && on ) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ break;
+ }
+ ss->opt.handshakeAsServer = on;
+ break;
+
+ case SSL_ENABLE_TLS:
+ if (IS_DTLS(ss)) {
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure; /* not allowed */
+ }
+ break;
+ }
+ ssl_EnableTLS(&ss->vrange, on);
+ ss->preferredCipher = NULL;
+ if (ss->cipherSpecs) {
+ PORT_Free(ss->cipherSpecs);
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0;
+ }
+ break;
+
+ case SSL_ENABLE_SSL3:
+ if (IS_DTLS(ss)) {
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure; /* not allowed */
+ }
+ break;
+ }
+ ssl_EnableSSL3(&ss->vrange, on);
+ ss->preferredCipher = NULL;
+ if (ss->cipherSpecs) {
+ PORT_Free(ss->cipherSpecs);
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0;
+ }
+ break;
+
+ case SSL_ENABLE_SSL2:
+ if (IS_DTLS(ss)) {
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure; /* not allowed */
+ }
+ break;
+ }
+ ss->opt.enableSSL2 = on;
+ if (on) {
+ ss->opt.v2CompatibleHello = on;
+ }
+ ss->preferredCipher = NULL;
+ if (ss->cipherSpecs) {
+ PORT_Free(ss->cipherSpecs);
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0;
+ }
+ break;
+
+ case SSL_NO_CACHE:
+ ss->opt.noCache = on;
+ break;
+
+ case SSL_ENABLE_FDX:
+ if (on && ss->opt.noLocks) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+ ss->opt.fdx = on;
+ break;
+
+ case SSL_V2_COMPATIBLE_HELLO:
+ if (IS_DTLS(ss)) {
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure; /* not allowed */
+ }
+ break;
+ }
+ ss->opt.v2CompatibleHello = on;
+ if (!on) {
+ ss->opt.enableSSL2 = on;
+ }
+ break;
+
+ case SSL_ROLLBACK_DETECTION:
+ ss->opt.detectRollBack = on;
+ break;
+
+ case SSL_NO_STEP_DOWN:
+ ss->opt.noStepDown = on;
+ if (on)
+ SSL_DisableExportCipherSuites(fd);
+ break;
+
+ case SSL_BYPASS_PKCS11:
+ if (ss->handshakeBegun) {
+ PORT_SetError(PR_INVALID_STATE_ERROR);
+ rv = SECFailure;
+ } else {
+ if (PR_FALSE != on) {
+ if (PR_SUCCESS == SSL_BypassSetup() ) {
+#ifdef NO_PKCS11_BYPASS
+ ss->opt.bypassPKCS11 = PR_FALSE;
+#else
+ ss->opt.bypassPKCS11 = on;
+#endif
+ } else {
+ rv = SECFailure;
+ }
+ } else {
+ ss->opt.bypassPKCS11 = PR_FALSE;
+ }
+ }
+ break;
+
+ case SSL_NO_LOCKS:
+ if (on && ss->opt.fdx) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+ if (on && ssl_force_locks)
+ on = PR_FALSE; /* silent override */
+ ss->opt.noLocks = on;
+ if (on) {
+ locksEverDisabled = PR_TRUE;
+ strcpy(lockStatus + LOCKSTATUS_OFFSET, "DISABLED.");
+ } else if (!holdingLocks) {
+ rv = ssl_MakeLocks(ss);
+ if (rv != SECSuccess) {
+ ss->opt.noLocks = PR_TRUE;
+ }
+ }
+ break;
+
+ case SSL_ENABLE_SESSION_TICKETS:
+ ss->opt.enableSessionTickets = on;
+ break;
+
+ case SSL_ENABLE_DEFLATE:
+ ss->opt.enableDeflate = on;
+ break;
+
+ case SSL_ENABLE_RENEGOTIATION:
+ ss->opt.enableRenegotiation = on;
+ break;
+
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ ss->opt.requireSafeNegotiation = on;
+ break;
+
+ case SSL_ENABLE_FALSE_START:
+ ss->opt.enableFalseStart = on;
+ break;
+
+ case SSL_CBC_RANDOM_IV:
+ ss->opt.cbcRandomIV = on;
+ break;
+
+ case SSL_ENABLE_OCSP_STAPLING:
+ ss->opt.enableOCSPStapling = on;
+ break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+
+ /* We can't use the macros for releasing the locks here,
+ * because ss->opt.noLocks might have changed just above.
+ * We must release these locks (monitors) here, if we aquired them above,
+ * regardless of the current value of ss->opt.noLocks.
+ */
+ if (holdingLocks) {
+ PZ_ExitMonitor((ss)->ssl3HandshakeLock);
+ PZ_ExitMonitor((ss)->firstHandshakeLock);
+ }
+
+ return rv;
+}
+
+SECStatus
+SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRBool *pOn)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+ SECStatus rv = SECSuccess;
+ PRBool on = PR_FALSE;
+
+ if (!pOn) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in Enable", SSL_GETPID(), fd));
+ *pOn = PR_FALSE;
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ switch (which) {
+ case SSL_SOCKS: on = PR_FALSE; break;
+ case SSL_SECURITY: on = ss->opt.useSecurity; break;
+ case SSL_REQUEST_CERTIFICATE: on = ss->opt.requestCertificate; break;
+ case SSL_REQUIRE_CERTIFICATE: on = ss->opt.requireCertificate; break;
+ case SSL_HANDSHAKE_AS_CLIENT: on = ss->opt.handshakeAsClient; break;
+ case SSL_HANDSHAKE_AS_SERVER: on = ss->opt.handshakeAsServer; break;
+ case SSL_ENABLE_TLS:
+ on = ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_0;
+ break;
+ case SSL_ENABLE_SSL3:
+ on = ss->vrange.min == SSL_LIBRARY_VERSION_3_0;
+ break;
+ case SSL_ENABLE_SSL2: on = ss->opt.enableSSL2; break;
+ case SSL_NO_CACHE: on = ss->opt.noCache; break;
+ case SSL_ENABLE_FDX: on = ss->opt.fdx; break;
+ case SSL_V2_COMPATIBLE_HELLO: on = ss->opt.v2CompatibleHello; break;
+ case SSL_ROLLBACK_DETECTION: on = ss->opt.detectRollBack; break;
+ case SSL_NO_STEP_DOWN: on = ss->opt.noStepDown; break;
+ case SSL_BYPASS_PKCS11: on = ss->opt.bypassPKCS11; break;
+ case SSL_NO_LOCKS: on = ss->opt.noLocks; break;
+ case SSL_ENABLE_SESSION_TICKETS:
+ on = ss->opt.enableSessionTickets;
+ break;
+ case SSL_ENABLE_DEFLATE: on = ss->opt.enableDeflate; break;
+ case SSL_ENABLE_RENEGOTIATION:
+ on = ss->opt.enableRenegotiation; break;
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ on = ss->opt.requireSafeNegotiation; break;
+ case SSL_ENABLE_FALSE_START: on = ss->opt.enableFalseStart; break;
+ case SSL_CBC_RANDOM_IV: on = ss->opt.cbcRandomIV; break;
+ case SSL_ENABLE_OCSP_STAPLING: on = ss->opt.enableOCSPStapling; break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ *pOn = on;
+ return rv;
+}
+
+SECStatus
+SSL_OptionGetDefault(PRInt32 which, PRBool *pOn)
+{
+ SECStatus rv = SECSuccess;
+ PRBool on = PR_FALSE;
+
+ if (!pOn) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ssl_SetDefaultsFromEnvironment();
+
+ switch (which) {
+ case SSL_SOCKS: on = PR_FALSE; break;
+ case SSL_SECURITY: on = ssl_defaults.useSecurity; break;
+ case SSL_REQUEST_CERTIFICATE: on = ssl_defaults.requestCertificate; break;
+ case SSL_REQUIRE_CERTIFICATE: on = ssl_defaults.requireCertificate; break;
+ case SSL_HANDSHAKE_AS_CLIENT: on = ssl_defaults.handshakeAsClient; break;
+ case SSL_HANDSHAKE_AS_SERVER: on = ssl_defaults.handshakeAsServer; break;
+ case SSL_ENABLE_TLS:
+ on = versions_defaults_stream.max >= SSL_LIBRARY_VERSION_TLS_1_0;
+ break;
+ case SSL_ENABLE_SSL3:
+ on = versions_defaults_stream.min == SSL_LIBRARY_VERSION_3_0;
+ break;
+ case SSL_ENABLE_SSL2: on = ssl_defaults.enableSSL2; break;
+ case SSL_NO_CACHE: on = ssl_defaults.noCache; break;
+ case SSL_ENABLE_FDX: on = ssl_defaults.fdx; break;
+ case SSL_V2_COMPATIBLE_HELLO: on = ssl_defaults.v2CompatibleHello; break;
+ case SSL_ROLLBACK_DETECTION: on = ssl_defaults.detectRollBack; break;
+ case SSL_NO_STEP_DOWN: on = ssl_defaults.noStepDown; break;
+ case SSL_BYPASS_PKCS11: on = ssl_defaults.bypassPKCS11; break;
+ case SSL_NO_LOCKS: on = ssl_defaults.noLocks; break;
+ case SSL_ENABLE_SESSION_TICKETS:
+ on = ssl_defaults.enableSessionTickets;
+ break;
+ case SSL_ENABLE_DEFLATE: on = ssl_defaults.enableDeflate; break;
+ case SSL_ENABLE_RENEGOTIATION:
+ on = ssl_defaults.enableRenegotiation; break;
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ on = ssl_defaults.requireSafeNegotiation;
+ break;
+ case SSL_ENABLE_FALSE_START: on = ssl_defaults.enableFalseStart; break;
+ case SSL_CBC_RANDOM_IV: on = ssl_defaults.cbcRandomIV; break;
+ case SSL_ENABLE_OCSP_STAPLING:
+ on = ssl_defaults.enableOCSPStapling;
+ break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ rv = SECFailure;
+ }
+
+ *pOn = on;
+ return rv;
+}
+
+/* XXX Use Global Lock to protect this stuff. */
+SECStatus
+SSL_EnableDefault(int which, PRBool on)
+{
+ return SSL_OptionSetDefault(which, on);
+}
+
+SECStatus
+SSL_OptionSetDefault(PRInt32 which, PRBool on)
+{
+ SECStatus status = ssl_Init();
+
+ if (status != SECSuccess) {
+ return status;
+ }
+
+ ssl_SetDefaultsFromEnvironment();
+
+ switch (which) {
+ case SSL_SOCKS:
+ ssl_defaults.useSocks = PR_FALSE;
+ if (on) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ break;
+
+ case SSL_SECURITY:
+ ssl_defaults.useSecurity = on;
+ break;
+
+ case SSL_REQUEST_CERTIFICATE:
+ ssl_defaults.requestCertificate = on;
+ break;
+
+ case SSL_REQUIRE_CERTIFICATE:
+ ssl_defaults.requireCertificate = on;
+ break;
+
+ case SSL_HANDSHAKE_AS_CLIENT:
+ if ( ssl_defaults.handshakeAsServer && on ) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ssl_defaults.handshakeAsClient = on;
+ break;
+
+ case SSL_HANDSHAKE_AS_SERVER:
+ if ( ssl_defaults.handshakeAsClient && on ) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ssl_defaults.handshakeAsServer = on;
+ break;
+
+ case SSL_ENABLE_TLS:
+ ssl_EnableTLS(&versions_defaults_stream, on);
+ break;
+
+ case SSL_ENABLE_SSL3:
+ ssl_EnableSSL3(&versions_defaults_stream, on);
+ break;
+
+ case SSL_ENABLE_SSL2:
+ ssl_defaults.enableSSL2 = on;
+ if (on) {
+ ssl_defaults.v2CompatibleHello = on;
+ }
+ break;
+
+ case SSL_NO_CACHE:
+ ssl_defaults.noCache = on;
+ break;
+
+ case SSL_ENABLE_FDX:
+ if (on && ssl_defaults.noLocks) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ssl_defaults.fdx = on;
+ break;
+
+ case SSL_V2_COMPATIBLE_HELLO:
+ ssl_defaults.v2CompatibleHello = on;
+ if (!on) {
+ ssl_defaults.enableSSL2 = on;
+ }
+ break;
+
+ case SSL_ROLLBACK_DETECTION:
+ ssl_defaults.detectRollBack = on;
+ break;
+
+ case SSL_NO_STEP_DOWN:
+ ssl_defaults.noStepDown = on;
+ if (on)
+ SSL_DisableDefaultExportCipherSuites();
+ break;
+
+ case SSL_BYPASS_PKCS11:
+ if (PR_FALSE != on) {
+ if (PR_SUCCESS == SSL_BypassSetup()) {
+#ifdef NO_PKCS11_BYPASS
+ ssl_defaults.bypassPKCS11 = PR_FALSE;
+#else
+ ssl_defaults.bypassPKCS11 = on;
+#endif
+ } else {
+ return SECFailure;
+ }
+ } else {
+ ssl_defaults.bypassPKCS11 = PR_FALSE;
+ }
+ break;
+
+ case SSL_NO_LOCKS:
+ if (on && ssl_defaults.fdx) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (on && ssl_force_locks)
+ on = PR_FALSE; /* silent override */
+ ssl_defaults.noLocks = on;
+ if (on) {
+ locksEverDisabled = PR_TRUE;
+ strcpy(lockStatus + LOCKSTATUS_OFFSET, "DISABLED.");
+ }
+ break;
+
+ case SSL_ENABLE_SESSION_TICKETS:
+ ssl_defaults.enableSessionTickets = on;
+ break;
+
+ case SSL_ENABLE_DEFLATE:
+ ssl_defaults.enableDeflate = on;
+ break;
+
+ case SSL_ENABLE_RENEGOTIATION:
+ ssl_defaults.enableRenegotiation = on;
+ break;
+
+ case SSL_REQUIRE_SAFE_NEGOTIATION:
+ ssl_defaults.requireSafeNegotiation = on;
+ break;
+
+ case SSL_ENABLE_FALSE_START:
+ ssl_defaults.enableFalseStart = on;
+ break;
+
+ case SSL_CBC_RANDOM_IV:
+ ssl_defaults.cbcRandomIV = on;
+ break;
+
+ case SSL_ENABLE_OCSP_STAPLING:
+ ssl_defaults.enableOCSPStapling = on;
+ break;
+
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+/* function tells us if the cipher suite is one that we no longer support. */
+static PRBool
+ssl_IsRemovedCipherSuite(PRInt32 suite)
+{
+ switch (suite) {
+ case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+ case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA:
+ case SSL_FORTEZZA_DMS_WITH_RC4_128_SHA:
+ return PR_TRUE;
+ default:
+ return PR_FALSE;
+ }
+}
+
+/* Part of the public NSS API.
+ * Since this is a global (not per-socket) setting, we cannot use the
+ * HandshakeLock to protect this. Probably want a global lock.
+ */
+SECStatus
+SSL_SetPolicy(long which, int policy)
+{
+ if ((which & 0xfffe) == SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA) {
+ /* one of the two old FIPS ciphers */
+ if (which == SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA)
+ which = SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA;
+ else if (which == SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA)
+ which = SSL_RSA_FIPS_WITH_DES_CBC_SHA;
+ }
+ if (ssl_IsRemovedCipherSuite(which))
+ return SECSuccess;
+ return SSL_CipherPolicySet(which, policy);
+}
+
+SECStatus
+SSL_CipherPolicySet(PRInt32 which, PRInt32 policy)
+{
+ SECStatus rv = ssl_Init();
+
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ if (ssl_IsRemovedCipherSuite(which)) {
+ rv = SECSuccess;
+ } else if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_SetPolicy(which, policy);
+ } else {
+ rv = ssl3_SetPolicy((ssl3CipherSuite)which, policy);
+ }
+ return rv;
+}
+
+SECStatus
+SSL_CipherPolicyGet(PRInt32 which, PRInt32 *oPolicy)
+{
+ SECStatus rv;
+
+ if (!oPolicy) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (ssl_IsRemovedCipherSuite(which)) {
+ *oPolicy = SSL_NOT_ALLOWED;
+ rv = SECSuccess;
+ } else if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_GetPolicy(which, oPolicy);
+ } else {
+ rv = ssl3_GetPolicy((ssl3CipherSuite)which, oPolicy);
+ }
+ return rv;
+}
+
+/* Part of the public NSS API.
+ * Since this is a global (not per-socket) setting, we cannot use the
+ * HandshakeLock to protect this. Probably want a global lock.
+ * These changes have no effect on any sslSockets already created.
+ */
+SECStatus
+SSL_EnableCipher(long which, PRBool enabled)
+{
+ if ((which & 0xfffe) == SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA) {
+ /* one of the two old FIPS ciphers */
+ if (which == SSL_RSA_OLDFIPS_WITH_3DES_EDE_CBC_SHA)
+ which = SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA;
+ else if (which == SSL_RSA_OLDFIPS_WITH_DES_CBC_SHA)
+ which = SSL_RSA_FIPS_WITH_DES_CBC_SHA;
+ }
+ if (ssl_IsRemovedCipherSuite(which))
+ return SECSuccess;
+ return SSL_CipherPrefSetDefault(which, enabled);
+}
+
+SECStatus
+SSL_CipherPrefSetDefault(PRInt32 which, PRBool enabled)
+{
+ SECStatus rv = ssl_Init();
+
+ if (rv != SECSuccess) {
+ return rv;
+ }
+
+ if (ssl_IsRemovedCipherSuite(which))
+ return SECSuccess;
+ if (enabled && ssl_defaults.noStepDown && SSL_IsExportCipherSuite(which)) {
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ return SECFailure;
+ }
+ if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_CipherPrefSetDefault(which, enabled);
+ } else {
+ rv = ssl3_CipherPrefSetDefault((ssl3CipherSuite)which, enabled);
+ }
+ return rv;
+}
+
+SECStatus
+SSL_CipherPrefGetDefault(PRInt32 which, PRBool *enabled)
+{
+ SECStatus rv;
+
+ if (!enabled) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (ssl_IsRemovedCipherSuite(which)) {
+ *enabled = PR_FALSE;
+ rv = SECSuccess;
+ } else if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_CipherPrefGetDefault(which, enabled);
+ } else {
+ rv = ssl3_CipherPrefGetDefault((ssl3CipherSuite)which, enabled);
+ }
+ return rv;
+}
+
+SECStatus
+SSL_CipherPrefSet(PRFileDesc *fd, PRInt32 which, PRBool enabled)
+{
+ SECStatus rv;
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in CipherPrefSet", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ if (ssl_IsRemovedCipherSuite(which))
+ return SECSuccess;
+ if (enabled && ss->opt.noStepDown && SSL_IsExportCipherSuite(which)) {
+ PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
+ return SECFailure;
+ }
+ if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_CipherPrefSet(ss, which, enabled);
+ } else {
+ rv = ssl3_CipherPrefSet(ss, (ssl3CipherSuite)which, enabled);
+ }
+ return rv;
+}
+
+SECStatus
+SSL_CipherPrefGet(PRFileDesc *fd, PRInt32 which, PRBool *enabled)
+{
+ SECStatus rv;
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!enabled) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in CipherPrefGet", SSL_GETPID(), fd));
+ *enabled = PR_FALSE;
+ return SECFailure;
+ }
+ if (ssl_IsRemovedCipherSuite(which)) {
+ *enabled = PR_FALSE;
+ rv = SECSuccess;
+ } else if (SSL_IS_SSL2_CIPHER(which)) {
+ rv = ssl2_CipherPrefGet(ss, which, enabled);
+ } else {
+ rv = ssl3_CipherPrefGet(ss, (ssl3CipherSuite)which, enabled);
+ }
+ return rv;
+}
+
+SECStatus
+NSS_SetDomesticPolicy(void)
+{
+ SECStatus status = SECSuccess;
+ cipherPolicy * policy;
+
+ for (policy = ssl_ciphers; policy->cipher != 0; ++policy) {
+ status = SSL_SetPolicy(policy->cipher, SSL_ALLOWED);
+ if (status != SECSuccess)
+ break;
+ }
+ return status;
+}
+
+SECStatus
+NSS_SetExportPolicy(void)
+{
+ return NSS_SetDomesticPolicy();
+}
+
+SECStatus
+NSS_SetFrancePolicy(void)
+{
+ return NSS_SetDomesticPolicy();
+}
+
+SECStatus
+SSL_GetChannelBinding(PRFileDesc *fd,
+ SSLChannelBindingType binding_type,
+ unsigned char *out,
+ unsigned int *outLen,
+ unsigned int outLenMax) {
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetChannelBinding",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (binding_type != SSL_CHANNEL_BINDING_TLS_UNIQUE) {
+ PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
+ return SECFailure;
+ }
+
+ return ssl3_GetTLSUniqueChannelBinding(ss, out, outLen, outLenMax);
+}
+
+
+/* LOCKS ??? XXX */
+static PRFileDesc *
+ssl_ImportFD(PRFileDesc *model, PRFileDesc *fd, SSLProtocolVariant variant)
+{
+ sslSocket * ns = NULL;
+ PRStatus rv;
+ PRNetAddr addr;
+ SECStatus status = ssl_Init();
+
+ if (status != SECSuccess) {
+ return NULL;
+ }
+
+ if (model == NULL) {
+ /* Just create a default socket if we're given NULL for the model */
+ ns = ssl_NewSocket((PRBool)(!ssl_defaults.noLocks), variant);
+ } else {
+ sslSocket * ss = ssl_FindSocket(model);
+ if (ss == NULL || ss->protocolVariant != variant) {
+ SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ImportFD",
+ SSL_GETPID(), model));
+ return NULL;
+ }
+ ns = ssl_DupSocket(ss);
+ }
+ if (ns == NULL)
+ return NULL;
+
+ rv = ssl_PushIOLayer(ns, fd, PR_TOP_IO_LAYER);
+ if (rv != PR_SUCCESS) {
+ ssl_FreeSocket(ns);
+ SET_ERROR_CODE
+ return NULL;
+ }
+ ns = ssl_FindSocket(fd);
+ PORT_Assert(ns);
+ if (ns)
+ ns->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ns, &addr));
+ return fd;
+}
+
+PRFileDesc *
+SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd)
+{
+ return ssl_ImportFD(model, fd, ssl_variant_stream);
+}
+
+PRFileDesc *
+DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd)
+{
+ return ssl_ImportFD(model, fd, ssl_variant_datagram);
+}
+
+SECStatus
+SSL_SetNextProtoCallback(PRFileDesc *fd, SSLNextProtoCallback callback,
+ void *arg)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoCallback", SSL_GETPID(),
+ fd));
+ return SECFailure;
+ }
+
+ ssl_GetSSL3HandshakeLock(ss);
+ ss->nextProtoCallback = callback;
+ ss->nextProtoArg = arg;
+ ssl_ReleaseSSL3HandshakeLock(ss);
+
+ return SECSuccess;
+}
+
+/* ssl_NextProtoNegoCallback is set as an NPN callback for the case when
+ * SSL_SetNextProtoNego is used.
+ */
+static SECStatus
+ssl_NextProtoNegoCallback(void *arg, PRFileDesc *fd,
+ const unsigned char *protos, unsigned int protos_len,
+ unsigned char *protoOut, unsigned int *protoOutLen,
+ unsigned int protoMaxLen)
+{
+ unsigned int i, j;
+ const unsigned char *result;
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in ssl_NextProtoNegoCallback",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (protos_len == 0) {
+ /* The server supports the extension, but doesn't have any protocols
+ * configured. In this case we request our favoured protocol. */
+ goto pick_first;
+ }
+
+ /* For each protocol in server preference, see if we support it. */
+ for (i = 0; i < protos_len; ) {
+ for (j = 0; j < ss->opt.nextProtoNego.len; ) {
+ if (protos[i] == ss->opt.nextProtoNego.data[j] &&
+ PORT_Memcmp(&protos[i+1], &ss->opt.nextProtoNego.data[j+1],
+ protos[i]) == 0) {
+ /* We found a match. */
+ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NEGOTIATED;
+ result = &protos[i];
+ goto found;
+ }
+ j += 1 + (unsigned int)ss->opt.nextProtoNego.data[j];
+ }
+ i += 1 + (unsigned int)protos[i];
+ }
+
+pick_first:
+ ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NO_OVERLAP;
+ result = ss->opt.nextProtoNego.data;
+
+found:
+ if (protoMaxLen < result[0]) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+ memcpy(protoOut, result + 1, result[0]);
+ *protoOutLen = result[0];
+ return SECSuccess;
+}
+
+SECStatus
+SSL_SetNextProtoNego(PRFileDesc *fd, const unsigned char *data,
+ unsigned int length)
+{
+ sslSocket *ss;
+ SECStatus rv;
+ SECItem dataItem = { siBuffer, (unsigned char *) data, length };
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoNego",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (ssl3_ValidateNextProtoNego(data, length) != SECSuccess)
+ return SECFailure;
+
+ ssl_GetSSL3HandshakeLock(ss);
+ SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
+ rv = SECITEM_CopyItem(NULL, &ss->opt.nextProtoNego, &dataItem);
+ ssl_ReleaseSSL3HandshakeLock(ss);
+
+ if (rv != SECSuccess)
+ return rv;
+
+ return SSL_SetNextProtoCallback(fd, ssl_NextProtoNegoCallback, NULL);
+}
+
+SECStatus
+SSL_GetNextProto(PRFileDesc *fd, SSLNextProtoState *state, unsigned char *buf,
+ unsigned int *bufLen, unsigned int bufLenMax)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNextProto", SSL_GETPID(),
+ fd));
+ return SECFailure;
+ }
+
+ if (!state || !buf || !bufLen) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ *state = ss->ssl3.nextProtoState;
+
+ if (ss->ssl3.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT &&
+ ss->ssl3.nextProto.data) {
+ if (ss->ssl3.nextProto.len > bufLenMax) {
+ PORT_SetError(SEC_ERROR_OUTPUT_LEN);
+ return SECFailure;
+ }
+ PORT_Memcpy(buf, ss->ssl3.nextProto.data, ss->ssl3.nextProto.len);
+ *bufLen = ss->ssl3.nextProto.len;
+ } else {
+ *bufLen = 0;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus SSL_SetSRTPCiphers(PRFileDesc *fd,
+ const PRUint16 *ciphers,
+ unsigned int numCiphers)
+{
+ sslSocket *ss;
+ unsigned int i;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss || !IS_DTLS(ss)) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetSRTPCiphers",
+ SSL_GETPID(), fd));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (numCiphers > MAX_DTLS_SRTP_CIPHER_SUITES) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ss->ssl3.dtlsSRTPCipherCount = 0;
+ for (i = 0; i < numCiphers; i++) {
+ const PRUint16 *srtpCipher = srtpCiphers;
+
+ while (*srtpCipher) {
+ if (ciphers[i] == *srtpCipher)
+ break;
+ srtpCipher++;
+ }
+ if (*srtpCipher) {
+ ss->ssl3.dtlsSRTPCiphers[ss->ssl3.dtlsSRTPCipherCount++] =
+ ciphers[i];
+ } else {
+ SSL_DBG(("%d: SSL[%d]: invalid or unimplemented SRTP cipher "
+ "suite specified: 0x%04hx", SSL_GETPID(), fd,
+ ciphers[i]));
+ }
+ }
+
+ if (ss->ssl3.dtlsSRTPCipherCount == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus
+SSL_GetSRTPCipher(PRFileDesc *fd, PRUint16 *cipher)
+{
+ sslSocket * ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetSRTPCipher",
+ SSL_GETPID(), fd));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (!ss->ssl3.dtlsSRTPCipherSuite) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ *cipher = ss->ssl3.dtlsSRTPCipherSuite;
+ return SECSuccess;
+}
+
+PRFileDesc *
+SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
+{
+ PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
+ PR_NOT_REACHED("not implemented");
+ return NULL;
+
+#if 0
+ sslSocket * sm = NULL, *ss = NULL;
+ int i;
+ sslServerCerts * mc = NULL;
+ sslServerCerts * sc = NULL;
+
+ if (model == NULL) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return NULL;
+ }
+ sm = ssl_FindSocket(model);
+ if (sm == NULL) {
+ SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ReconfigFD",
+ SSL_GETPID(), model));
+ return NULL;
+ }
+ ss = ssl_FindSocket(fd);
+ PORT_Assert(ss);
+ if (ss == NULL) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+
+ ss->opt = sm->opt;
+ ss->vrange = sm->vrange;
+ PORT_Memcpy(ss->cipherSuites, sm->cipherSuites, sizeof sm->cipherSuites);
+ PORT_Memcpy(ss->ssl3.dtlsSRTPCiphers, sm->ssl3.dtlsSRTPCiphers,
+ sizeof(PRUint16) * sm->ssl3.dtlsSRTPCipherCount);
+ ss->ssl3.dtlsSRTPCipherCount = sm->ssl3.dtlsSRTPCipherCount;
+
+ if (!ss->opt.useSecurity) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return NULL;
+ }
+ /* This int should be SSLKEAType, but CC on Irix complains,
+ * during the for loop.
+ */
+ for (i=kt_null; i < kt_kea_size; i++) {
+ mc = &(sm->serverCerts[i]);
+ sc = &(ss->serverCerts[i]);
+ if (mc->serverCert && mc->serverCertChain) {
+ if (sc->serverCert) {
+ CERT_DestroyCertificate(sc->serverCert);
+ }
+ sc->serverCert = CERT_DupCertificate(mc->serverCert);
+ if (sc->serverCertChain) {
+ CERT_DestroyCertificateList(sc->serverCertChain);
+ }
+ sc->serverCertChain = CERT_DupCertList(mc->serverCertChain);
+ if (!sc->serverCertChain)
+ goto loser;
+ if (sm->certStatusArray[i]) {
+ if (ss->certStatusArray[i]) {
+ SECITEM_FreeArray(ss->certStatusArray[i], PR_TRUE);
+ ss->certStatusArray[i] = NULL;
+ }
+ ss->certStatusArray[i] = SECITEM_DupArray(NULL, sm->certStatusArray[i]);
+ if (!ss->certStatusArray[i])
+ goto loser;
+ }
+ }
+ if (mc->serverKeyPair) {
+ if (sc->serverKeyPair) {
+ ssl3_FreeKeyPair(sc->serverKeyPair);
+ }
+ sc->serverKeyPair = ssl3_GetKeyPairRef(mc->serverKeyPair);
+ sc->serverKeyBits = mc->serverKeyBits;
+ }
+ }
+ if (sm->stepDownKeyPair) {
+ if (ss->stepDownKeyPair) {
+ ssl3_FreeKeyPair(ss->stepDownKeyPair);
+ }
+ ss->stepDownKeyPair = ssl3_GetKeyPairRef(sm->stepDownKeyPair);
+ }
+ if (sm->ephemeralECDHKeyPair) {
+ if (ss->ephemeralECDHKeyPair) {
+ ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair);
+ }
+ ss->ephemeralECDHKeyPair =
+ ssl3_GetKeyPairRef(sm->ephemeralECDHKeyPair);
+ }
+ /* copy trust anchor names */
+ if (sm->ssl3.ca_list) {
+ if (ss->ssl3.ca_list) {
+ CERT_FreeDistNames(ss->ssl3.ca_list);
+ }
+ ss->ssl3.ca_list = CERT_DupDistNames(sm->ssl3.ca_list);
+ if (!ss->ssl3.ca_list) {
+ goto loser;
+ }
+ }
+
+ if (sm->authCertificate)
+ ss->authCertificate = sm->authCertificate;
+ if (sm->authCertificateArg)
+ ss->authCertificateArg = sm->authCertificateArg;
+ if (sm->getClientAuthData)
+ ss->getClientAuthData = sm->getClientAuthData;
+ if (sm->getClientAuthDataArg)
+ ss->getClientAuthDataArg = sm->getClientAuthDataArg;
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ if (sm->getPlatformClientAuthData)
+ ss->getPlatformClientAuthData = sm->getPlatformClientAuthData;
+ if (sm->getPlatformClientAuthDataArg)
+ ss->getPlatformClientAuthDataArg = sm->getPlatformClientAuthDataArg;
+#endif
+ if (sm->sniSocketConfig)
+ ss->sniSocketConfig = sm->sniSocketConfig;
+ if (sm->sniSocketConfigArg)
+ ss->sniSocketConfigArg = sm->sniSocketConfigArg;
+ if (sm->handleBadCert)
+ ss->handleBadCert = sm->handleBadCert;
+ if (sm->badCertArg)
+ ss->badCertArg = sm->badCertArg;
+ if (sm->handshakeCallback)
+ ss->handshakeCallback = sm->handshakeCallback;
+ if (sm->handshakeCallbackData)
+ ss->handshakeCallbackData = sm->handshakeCallbackData;
+ if (sm->pkcs11PinArg)
+ ss->pkcs11PinArg = sm->pkcs11PinArg;
+ if (sm->getChannelID)
+ ss->getChannelID = sm->getChannelID;
+ if (sm->getChannelIDArg)
+ ss->getChannelIDArg = sm->getChannelIDArg;
+ return fd;
+loser:
+ return NULL;
+#endif
+}
+
+PRBool
+ssl3_VersionIsSupported(SSLProtocolVariant protocolVariant,
+ SSL3ProtocolVersion version)
+{
+ switch (protocolVariant) {
+ case ssl_variant_stream:
+ return (version >= SSL_LIBRARY_VERSION_3_0 &&
+ version <= SSL_LIBRARY_VERSION_MAX_SUPPORTED);
+ case ssl_variant_datagram:
+ return (version >= SSL_LIBRARY_VERSION_TLS_1_1 &&
+ version <= SSL_LIBRARY_VERSION_MAX_SUPPORTED);
+ default:
+ /* Can't get here */
+ PORT_Assert(PR_FALSE);
+ return PR_FALSE;
+ }
+}
+
+/* Returns PR_TRUE if the given version range is valid and
+** fully supported; otherwise, returns PR_FALSE.
+*/
+static PRBool
+ssl3_VersionRangeIsValid(SSLProtocolVariant protocolVariant,
+ const SSLVersionRange *vrange)
+{
+ return vrange &&
+ vrange->min <= vrange->max &&
+ ssl3_VersionIsSupported(protocolVariant, vrange->min) &&
+ ssl3_VersionIsSupported(protocolVariant, vrange->max);
+}
+
+SECStatus
+SSL_VersionRangeGetSupported(SSLProtocolVariant protocolVariant,
+ SSLVersionRange *vrange)
+{
+ if (!vrange) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ switch (protocolVariant) {
+ case ssl_variant_stream:
+ vrange->min = SSL_LIBRARY_VERSION_3_0;
+ vrange->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
+ break;
+ case ssl_variant_datagram:
+ vrange->min = SSL_LIBRARY_VERSION_TLS_1_1;
+ vrange->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
+ break;
+ default:
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus
+SSL_VersionRangeGetDefault(SSLProtocolVariant protocolVariant,
+ SSLVersionRange *vrange)
+{
+ if ((protocolVariant != ssl_variant_stream &&
+ protocolVariant != ssl_variant_datagram) || !vrange) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ *vrange = *VERSIONS_DEFAULTS(protocolVariant);
+
+ return SECSuccess;
+}
+
+SECStatus
+SSL_VersionRangeSetDefault(SSLProtocolVariant protocolVariant,
+ const SSLVersionRange *vrange)
+{
+ if (!ssl3_VersionRangeIsValid(protocolVariant, vrange)) {
+ PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
+ return SECFailure;
+ }
+
+ *VERSIONS_DEFAULTS(protocolVariant) = *vrange;
+
+ return SECSuccess;
+}
+
+SECStatus
+SSL_VersionRangeGet(PRFileDesc *fd, SSLVersionRange *vrange)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL3_VersionRangeGet",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (!vrange) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ *vrange = ss->vrange;
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
+}
+
+static PRCallOnceType checkTLS12TokenOnce;
+static PRBool tls12TokenExists;
+
+static PRStatus
+ssl_CheckTLS12Token(void)
+{
+ tls12TokenExists =
+ PK11_TokenExists(CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256);
+ return PR_SUCCESS;
+}
+
+static PRBool
+ssl_TLS12TokenExists(void)
+{
+ (void) PR_CallOnce(&checkTLS12TokenOnce, ssl_CheckTLS12Token);
+ return tls12TokenExists;
+}
+
+SECStatus
+SSL_VersionRangeSet(PRFileDesc *fd, const SSLVersionRange *vrange)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL3_VersionRangeSet",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (!ssl3_VersionRangeIsValid(ss->protocolVariant, vrange)) {
+ PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
+ return SECFailure;
+ }
+
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ ss->vrange = *vrange;
+ /* If we don't have a sufficiently up-to-date softoken then we cannot do
+ * TLS 1.2. */
+ if (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_2 &&
+ !ssl_TLS12TokenExists()) {
+ /* If the user requested a minimum version of 1.2, then we don't
+ * silently downgrade. */
+ if (ss->vrange.min >= SSL_LIBRARY_VERSION_TLS_1_2) {
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+ PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
+ return SECFailure;
+ }
+ ss->vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+
+ return SECSuccess;
+}
+
+const SECItemArray *
+SSL_PeerStapledOCSPResponses(PRFileDesc *fd)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_PeerStapledOCSPResponses",
+ SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ if (!ss->sec.ci.sid) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return NULL;
+ }
+
+ return &ss->sec.ci.sid->peerCertStatus;
+}
+
+SECStatus
+SSL_HandshakeResumedSession(PRFileDesc *fd, PRBool *handshake_resumed) {
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_HandshakeResumedSession",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ *handshake_resumed = ss->ssl3.hs.isResuming;
+ return SECSuccess;
+}
+
+const SECItem *
+SSL_GetRequestedClientCertificateTypes(PRFileDesc *fd)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in "
+ "SSL_GetRequestedClientCertificateTypes", SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ return ss->requestedCertTypes;
+}
+
+/************************************************************************/
+/* The following functions are the TOP LEVEL SSL functions.
+** They all get called through the NSPRIOMethods table below.
+*/
+
+static PRFileDesc * PR_CALLBACK
+ssl_Accept(PRFileDesc *fd, PRNetAddr *sockaddr, PRIntervalTime timeout)
+{
+ sslSocket *ss;
+ sslSocket *ns = NULL;
+ PRFileDesc *newfd = NULL;
+ PRFileDesc *osfd;
+ PRStatus status;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in accept", SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ /* IF this is a listen socket, there shouldn't be any I/O going on */
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+ ssl_Get1stHandshakeLock(ss);
+ ssl_GetSSL3HandshakeLock(ss);
+
+ ss->cTimeout = timeout;
+
+ osfd = ss->fd->lower;
+
+ /* First accept connection */
+ newfd = osfd->methods->accept(osfd, sockaddr, timeout);
+ if (newfd == NULL) {
+ SSL_DBG(("%d: SSL[%d]: accept failed, errno=%d",
+ SSL_GETPID(), ss->fd, PORT_GetError()));
+ } else {
+ /* Create ssl module */
+ ns = ssl_DupSocket(ss);
+ }
+
+ ssl_ReleaseSSL3HandshakeLock(ss);
+ ssl_Release1stHandshakeLock(ss);
+ SSL_UNLOCK_WRITER(ss);
+ SSL_UNLOCK_READER(ss); /* ss isn't used below here. */
+
+ if (ns == NULL)
+ goto loser;
+
+ /* push ssl module onto the new socket */
+ status = ssl_PushIOLayer(ns, newfd, PR_TOP_IO_LAYER);
+ if (status != PR_SUCCESS)
+ goto loser;
+
+ /* Now start server connection handshake with client.
+ ** Don't need locks here because nobody else has a reference to ns yet.
+ */
+ if ( ns->opt.useSecurity ) {
+ if ( ns->opt.handshakeAsClient ) {
+ ns->handshake = ssl2_BeginClientHandshake;
+ ss->handshaking = sslHandshakingAsClient;
+ } else {
+ ns->handshake = ssl2_BeginServerHandshake;
+ ss->handshaking = sslHandshakingAsServer;
+ }
+ }
+ ns->TCPconnected = 1;
+ return newfd;
+
+loser:
+ if (ns != NULL)
+ ssl_FreeSocket(ns);
+ if (newfd != NULL)
+ PR_Close(newfd);
+ return NULL;
+}
+
+static PRStatus PR_CALLBACK
+ssl_Connect(PRFileDesc *fd, const PRNetAddr *sockaddr, PRIntervalTime timeout)
+{
+ sslSocket *ss;
+ PRStatus rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in connect", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+
+ /* IF this is a listen socket, there shouldn't be any I/O going on */
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+
+ ss->cTimeout = timeout;
+ rv = (PRStatus)(*ss->ops->connect)(ss, sockaddr);
+
+ SSL_UNLOCK_WRITER(ss);
+ SSL_UNLOCK_READER(ss);
+
+ return rv;
+}
+
+static PRStatus PR_CALLBACK
+ssl_Bind(PRFileDesc *fd, const PRNetAddr *addr)
+{
+ sslSocket * ss = ssl_GetPrivate(fd);
+ PRStatus rv;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in bind", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+
+ rv = (PRStatus)(*ss->ops->bind)(ss, addr);
+
+ SSL_UNLOCK_WRITER(ss);
+ SSL_UNLOCK_READER(ss);
+ return rv;
+}
+
+static PRStatus PR_CALLBACK
+ssl_Listen(PRFileDesc *fd, PRIntn backlog)
+{
+ sslSocket * ss = ssl_GetPrivate(fd);
+ PRStatus rv;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in listen", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+
+ rv = (PRStatus)(*ss->ops->listen)(ss, backlog);
+
+ SSL_UNLOCK_WRITER(ss);
+ SSL_UNLOCK_READER(ss);
+ return rv;
+}
+
+static PRStatus PR_CALLBACK
+ssl_Shutdown(PRFileDesc *fd, PRIntn how)
+{
+ sslSocket * ss = ssl_GetPrivate(fd);
+ PRStatus rv;
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in shutdown", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+ if (how == PR_SHUTDOWN_RCV || how == PR_SHUTDOWN_BOTH) {
+ SSL_LOCK_READER(ss);
+ }
+ if (how == PR_SHUTDOWN_SEND || how == PR_SHUTDOWN_BOTH) {
+ SSL_LOCK_WRITER(ss);
+ }
+
+ rv = (PRStatus)(*ss->ops->shutdown)(ss, how);
+
+ if (how == PR_SHUTDOWN_SEND || how == PR_SHUTDOWN_BOTH) {
+ SSL_UNLOCK_WRITER(ss);
+ }
+ if (how == PR_SHUTDOWN_RCV || how == PR_SHUTDOWN_BOTH) {
+ SSL_UNLOCK_READER(ss);
+ }
+ return rv;
+}
+
+static PRStatus PR_CALLBACK
+ssl_Close(PRFileDesc *fd)
+{
+ sslSocket *ss;
+ PRStatus rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in close", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+
+ /* There must not be any I/O going on */
+ SSL_LOCK_READER(ss);
+ SSL_LOCK_WRITER(ss);
+
+ /* By the time this function returns,
+ ** ss is an invalid pointer, and the locks to which it points have
+ ** been unlocked and freed. So, this is the ONE PLACE in all of SSL
+ ** where the LOCK calls and the corresponding UNLOCK calls are not in
+ ** the same function scope. The unlock calls are in ssl_FreeSocket().
+ */
+ rv = (PRStatus)(*ss->ops->close)(ss);
+
+ return rv;
+}
+
+static int PR_CALLBACK
+ssl_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ sslSocket *ss;
+ int rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in recv", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ SSL_LOCK_READER(ss);
+ ss->rTimeout = timeout;
+ if (!ss->opt.fdx)
+ ss->wTimeout = timeout;
+ rv = (*ss->ops->recv)(ss, (unsigned char*)buf, len, flags);
+ SSL_UNLOCK_READER(ss);
+ return rv;
+}
+
+static int PR_CALLBACK
+ssl_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ sslSocket *ss;
+ int rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in send", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ SSL_LOCK_WRITER(ss);
+ ss->wTimeout = timeout;
+ if (!ss->opt.fdx)
+ ss->rTimeout = timeout;
+ rv = (*ss->ops->send)(ss, (const unsigned char*)buf, len, flags);
+ SSL_UNLOCK_WRITER(ss);
+ return rv;
+}
+
+static int PR_CALLBACK
+ssl_Read(PRFileDesc *fd, void *buf, PRInt32 len)
+{
+ sslSocket *ss;
+ int rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in read", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ SSL_LOCK_READER(ss);
+ ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
+ if (!ss->opt.fdx)
+ ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
+ rv = (*ss->ops->read)(ss, (unsigned char*)buf, len);
+ SSL_UNLOCK_READER(ss);
+ return rv;
+}
+
+static int PR_CALLBACK
+ssl_Write(PRFileDesc *fd, const void *buf, PRInt32 len)
+{
+ sslSocket *ss;
+ int rv;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in write", SSL_GETPID(), fd));
+ return SECFailure;
+ }
+ SSL_LOCK_WRITER(ss);
+ ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
+ if (!ss->opt.fdx)
+ ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
+ rv = (*ss->ops->write)(ss, (const unsigned char*)buf, len);
+ SSL_UNLOCK_WRITER(ss);
+ return rv;
+}
+
+static PRStatus PR_CALLBACK
+ssl_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
+{
+ sslSocket *ss;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in getpeername", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+ return (PRStatus)(*ss->ops->getpeername)(ss, addr);
+}
+
+/*
+*/
+SECStatus
+ssl_GetPeerInfo(sslSocket *ss)
+{
+ PRFileDesc * osfd;
+ int rv;
+ PRNetAddr sin;
+
+ osfd = ss->fd->lower;
+
+ PORT_Memset(&sin, 0, sizeof(sin));
+ rv = osfd->methods->getpeername(osfd, &sin);
+ if (rv < 0) {
+ return SECFailure;
+ }
+ ss->TCPconnected = 1;
+ if (sin.inet.family == PR_AF_INET) {
+ PR_ConvertIPv4AddrToIPv6(sin.inet.ip, &ss->sec.ci.peer);
+ ss->sec.ci.port = sin.inet.port;
+ } else if (sin.ipv6.family == PR_AF_INET6) {
+ ss->sec.ci.peer = sin.ipv6.ip;
+ ss->sec.ci.port = sin.ipv6.port;
+ } else {
+ PORT_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR);
+ return SECFailure;
+ }
+ return SECSuccess;
+}
+
+static PRStatus PR_CALLBACK
+ssl_GetSockName(PRFileDesc *fd, PRNetAddr *name)
+{
+ sslSocket *ss;
+
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in getsockname", SSL_GETPID(), fd));
+ return PR_FAILURE;
+ }
+ return (PRStatus)(*ss->ops->getsockname)(ss, name);
+}
+
+SECStatus
+SSL_SetStapledOCSPResponses(PRFileDesc *fd, const SECItemArray *responses,
+ SSLKEAType kea)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetStapledOCSPResponses",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if ( kea <= 0 || kea >= kt_kea_size) {
+ SSL_DBG(("%d: SSL[%d]: invalid key in SSL_SetStapledOCSPResponses",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (ss->certStatusArray[kea]) {
+ SECITEM_FreeArray(ss->certStatusArray[kea], PR_TRUE);
+ ss->certStatusArray[kea] = NULL;
+ }
+ if (responses) {
+ ss->certStatusArray[kea] = SECITEM_DupArray(NULL, responses);
+ }
+ return (ss->certStatusArray[kea] || !responses) ? SECSuccess : SECFailure;
+}
+
+SECStatus
+SSL_SetSockPeerID(PRFileDesc *fd, const char *peerID)
+{
+ sslSocket *ss;
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetSockPeerID",
+ SSL_GETPID(), fd));
+ return SECFailure;
+ }
+
+ if (ss->peerID) {
+ PORT_Free(ss->peerID);
+ ss->peerID = NULL;
+ }
+ if (peerID)
+ ss->peerID = PORT_Strdup(peerID);
+ return (ss->peerID || !peerID) ? SECSuccess : SECFailure;
+}
+
+#define PR_POLL_RW (PR_POLL_WRITE | PR_POLL_READ)
+
+static PRInt16 PR_CALLBACK
+ssl_Poll(PRFileDesc *fd, PRInt16 how_flags, PRInt16 *p_out_flags)
+{
+ sslSocket *ss;
+ PRInt16 new_flags = how_flags; /* should select on these flags. */
+ PRNetAddr addr;
+
+ *p_out_flags = 0;
+ ss = ssl_GetPrivate(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_Poll",
+ SSL_GETPID(), fd));
+ return 0; /* don't poll on this socket */
+ }
+
+ if (ss->opt.useSecurity &&
+ ss->handshaking != sslHandshakingUndetermined &&
+ !ss->firstHsDone &&
+ (how_flags & PR_POLL_RW)) {
+ if (!ss->TCPconnected) {
+ ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr));
+ }
+ /* If it's not connected, then presumably the application is polling
+ ** on read or write appropriately, so don't change it.
+ */
+ if (ss->TCPconnected) {
+ if (!ss->handshakeBegun) {
+ /* If the handshake has not begun, poll on read or write
+ ** based on the local application's role in the handshake,
+ ** not based on what the application requested.
+ */
+ new_flags &= ~PR_POLL_RW;
+ if (ss->handshaking == sslHandshakingAsClient) {
+ new_flags |= PR_POLL_WRITE;
+ } else { /* handshaking as server */
+ new_flags |= PR_POLL_READ;
+ }
+ } else
+ /* First handshake is in progress */
+ if (ss->lastWriteBlocked) {
+ if (new_flags & PR_POLL_READ) {
+ /* The caller is waiting for data to be received,
+ ** but the initial handshake is blocked on write, or the
+ ** client's first handshake record has not been written.
+ ** The code should select on write, not read.
+ */
+ new_flags ^= PR_POLL_READ; /* don't select on read. */
+ new_flags |= PR_POLL_WRITE; /* do select on write. */
+ }
+ } else if (new_flags & PR_POLL_WRITE) {
+ /* The caller is trying to write, but the handshake is
+ ** blocked waiting for data to read, and the first
+ ** handshake has been sent. so do NOT to poll on write.
+ */
+ new_flags ^= PR_POLL_WRITE; /* don't select on write. */
+ new_flags |= PR_POLL_READ; /* do select on read. */
+ }
+ }
+ } else if ((new_flags & PR_POLL_READ) && (SSL_DataPending(fd) > 0)) {
+ *p_out_flags = PR_POLL_READ; /* it's ready already. */
+ return new_flags;
+ } else if ((ss->lastWriteBlocked) && (how_flags & PR_POLL_READ) &&
+ (ss->pendingBuf.len != 0)) { /* write data waiting to be sent */
+ new_flags |= PR_POLL_WRITE; /* also select on write. */
+ }
+
+ if (ss->version >= SSL_LIBRARY_VERSION_3_0 &&
+ ss->ssl3.hs.restartTarget != NULL) {
+ /* Read and write will block until the asynchronous callback completes
+ * (e.g. until SSL_AuthCertificateComplete is called), so don't tell
+ * the caller to poll the socket unless there is pending write data.
+ */
+ if (ss->lastWriteBlocked && ss->pendingBuf.len != 0) {
+ /* Ignore any newly-received data on the socket, but do wait for
+ * the socket to become writable again. Here, it is OK for an error
+ * to be detected, because our logic for sending pending write data
+ * will allow us to report the error to the caller without the risk
+ * of the application spinning.
+ */
+ new_flags &= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ } else {
+ /* Unfortunately, clearing new_flags will make it impossible for
+ * the application to detect errors that it would otherwise be
+ * able to detect with PR_POLL_EXCEPT, until the asynchronous
+ * callback completes. However, we must clear all the flags to
+ * prevent the application from spinning (alternating between
+ * calling PR_Poll that would return PR_POLL_EXCEPT, and send/recv
+ * which won't actually report the I/O error while we are waiting
+ * for the asynchronous callback to complete).
+ */
+ new_flags = 0;
+ }
+ }
+
+ if (new_flags && (fd->lower->methods->poll != NULL)) {
+ PRInt16 lower_out_flags = 0;
+ PRInt16 lower_new_flags;
+ lower_new_flags = fd->lower->methods->poll(fd->lower, new_flags,
+ &lower_out_flags);
+ if ((lower_new_flags & lower_out_flags) && (how_flags != new_flags)) {
+ PRInt16 out_flags = lower_out_flags & ~PR_POLL_RW;
+ if (lower_out_flags & PR_POLL_READ)
+ out_flags |= PR_POLL_WRITE;
+ if (lower_out_flags & PR_POLL_WRITE)
+ out_flags |= PR_POLL_READ;
+ *p_out_flags = out_flags;
+ new_flags = how_flags;
+ } else {
+ *p_out_flags = lower_out_flags;
+ new_flags = lower_new_flags;
+ }
+ }
+
+ return new_flags;
+}
+
+static PRInt32 PR_CALLBACK
+ssl_TransmitFile(PRFileDesc *sd, PRFileDesc *fd,
+ const void *headers, PRInt32 hlen,
+ PRTransmitFileFlags flags, PRIntervalTime timeout)
+{
+ PRSendFileData sfd;
+
+ sfd.fd = fd;
+ sfd.file_offset = 0;
+ sfd.file_nbytes = 0;
+ sfd.header = headers;
+ sfd.hlen = hlen;
+ sfd.trailer = NULL;
+ sfd.tlen = 0;
+
+ return sd->methods->sendfile(sd, &sfd, flags, timeout);
+}
+
+
+PRBool
+ssl_FdIsBlocking(PRFileDesc *fd)
+{
+ PRSocketOptionData opt;
+ PRStatus status;
+
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = PR_FALSE;
+ status = PR_GetSocketOption(fd, &opt);
+ if (status != PR_SUCCESS)
+ return PR_FALSE;
+ return (PRBool)!opt.value.non_blocking;
+}
+
+PRBool
+ssl_SocketIsBlocking(sslSocket *ss)
+{
+ return ssl_FdIsBlocking(ss->fd);
+}
+
+PRInt32 sslFirstBufSize = 8 * 1024;
+PRInt32 sslCopyLimit = 1024;
+
+static PRInt32 PR_CALLBACK
+ssl_WriteV(PRFileDesc *fd, const PRIOVec *iov, PRInt32 vectors,
+ PRIntervalTime timeout)
+{
+ PRInt32 bufLen;
+ PRInt32 left;
+ PRInt32 rv;
+ PRInt32 sent = 0;
+ const PRInt32 first_len = sslFirstBufSize;
+ const PRInt32 limit = sslCopyLimit;
+ PRBool blocking;
+ PRIOVec myIov = { 0, 0 };
+ char buf[MAX_FRAGMENT_LENGTH];
+
+ if (vectors > PR_MAX_IOVECTOR_SIZE) {
+ PORT_SetError(PR_BUFFER_OVERFLOW_ERROR);
+ return -1;
+ }
+ blocking = ssl_FdIsBlocking(fd);
+
+#define K16 sizeof(buf)
+#define KILL_VECTORS while (vectors && !iov->iov_len) { ++iov; --vectors; }
+#define GET_VECTOR do { myIov = *iov++; --vectors; KILL_VECTORS } while (0)
+#define HANDLE_ERR(rv, len) \
+ if (rv != len) { \
+ if (rv < 0) { \
+ if (!blocking \
+ && (PR_GetError() == PR_WOULD_BLOCK_ERROR) \
+ && (sent > 0)) { \
+ return sent; \
+ } else { \
+ return -1; \
+ } \
+ } \
+ /* Only a nonblocking socket can have partial sends */ \
+ PR_ASSERT(!blocking); \
+ return sent + rv; \
+ }
+#define SEND(bfr, len) \
+ do { \
+ rv = ssl_Send(fd, bfr, len, 0, timeout); \
+ HANDLE_ERR(rv, len) \
+ sent += len; \
+ } while (0)
+
+ /* Make sure the first write is at least 8 KB, if possible. */
+ KILL_VECTORS
+ if (!vectors)
+ return ssl_Send(fd, 0, 0, 0, timeout);
+ GET_VECTOR;
+ if (!vectors) {
+ return ssl_Send(fd, myIov.iov_base, myIov.iov_len, 0, timeout);
+ }
+ if (myIov.iov_len < first_len) {
+ PORT_Memcpy(buf, myIov.iov_base, myIov.iov_len);
+ bufLen = myIov.iov_len;
+ left = first_len - bufLen;
+ while (vectors && left) {
+ int toCopy;
+ GET_VECTOR;
+ toCopy = PR_MIN(left, myIov.iov_len);
+ PORT_Memcpy(buf + bufLen, myIov.iov_base, toCopy);
+ bufLen += toCopy;
+ left -= toCopy;
+ myIov.iov_base += toCopy;
+ myIov.iov_len -= toCopy;
+ }
+ SEND( buf, bufLen );
+ }
+
+ while (vectors || myIov.iov_len) {
+ PRInt32 addLen;
+ if (!myIov.iov_len) {
+ GET_VECTOR;
+ }
+ while (myIov.iov_len >= K16) {
+ SEND(myIov.iov_base, K16);
+ myIov.iov_base += K16;
+ myIov.iov_len -= K16;
+ }
+ if (!myIov.iov_len)
+ continue;
+
+ if (!vectors || myIov.iov_len > limit) {
+ addLen = 0;
+ } else if ((addLen = iov->iov_len % K16) + myIov.iov_len <= limit) {
+ /* Addlen is already computed. */;
+ } else if (vectors > 1 &&
+ iov[1].iov_len % K16 + addLen + myIov.iov_len <= 2 * limit) {
+ addLen = limit - myIov.iov_len;
+ } else
+ addLen = 0;
+
+ if (!addLen) {
+ SEND( myIov.iov_base, myIov.iov_len );
+ myIov.iov_len = 0;
+ continue;
+ }
+ PORT_Memcpy(buf, myIov.iov_base, myIov.iov_len);
+ bufLen = myIov.iov_len;
+ do {
+ GET_VECTOR;
+ PORT_Memcpy(buf + bufLen, myIov.iov_base, addLen);
+ myIov.iov_base += addLen;
+ myIov.iov_len -= addLen;
+ bufLen += addLen;
+
+ left = PR_MIN( limit, K16 - bufLen);
+ if (!vectors /* no more left */
+ || myIov.iov_len > 0 /* we didn't use that one all up */
+ || bufLen >= K16 /* it's full. */
+ ) {
+ addLen = 0;
+ } else if ((addLen = iov->iov_len % K16) <= left) {
+ /* Addlen is already computed. */;
+ } else if (vectors > 1 &&
+ iov[1].iov_len % K16 + addLen <= left + limit) {
+ addLen = left;
+ } else
+ addLen = 0;
+
+ } while (addLen);
+ SEND( buf, bufLen );
+ }
+ return sent;
+}
+
+/*
+ * These functions aren't implemented.
+ */
+
+static PRInt32 PR_CALLBACK
+ssl_Available(PRFileDesc *fd)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return SECFailure;
+}
+
+static PRInt64 PR_CALLBACK
+ssl_Available64(PRFileDesc *fd)
+{
+ PRInt64 res;
+
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ LL_I2L(res, -1L);
+ return res;
+}
+
+static PRStatus PR_CALLBACK
+ssl_FSync(PRFileDesc *fd)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return PR_FAILURE;
+}
+
+static PRInt32 PR_CALLBACK
+ssl_Seek(PRFileDesc *fd, PRInt32 offset, PRSeekWhence how) {
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return SECFailure;
+}
+
+static PRInt64 PR_CALLBACK
+ssl_Seek64(PRFileDesc *fd, PRInt64 offset, PRSeekWhence how) {
+ PRInt64 res;
+
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ LL_I2L(res, -1L);
+ return res;
+}
+
+static PRStatus PR_CALLBACK
+ssl_FileInfo(PRFileDesc *fd, PRFileInfo *info)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return PR_FAILURE;
+}
+
+static PRStatus PR_CALLBACK
+ssl_FileInfo64(PRFileDesc *fd, PRFileInfo64 *info)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return PR_FAILURE;
+}
+
+static PRInt32 PR_CALLBACK
+ssl_RecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRNetAddr *addr, PRIntervalTime timeout)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return SECFailure;
+}
+
+static PRInt32 PR_CALLBACK
+ssl_SendTo(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
+ const PRNetAddr *addr, PRIntervalTime timeout)
+{
+ PORT_Assert(0);
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return SECFailure;
+}
+
+static const PRIOMethods ssl_methods = {
+ PR_DESC_LAYERED,
+ ssl_Close, /* close */
+ ssl_Read, /* read */
+ ssl_Write, /* write */
+ ssl_Available, /* available */
+ ssl_Available64, /* available64 */
+ ssl_FSync, /* fsync */
+ ssl_Seek, /* seek */
+ ssl_Seek64, /* seek64 */
+ ssl_FileInfo, /* fileInfo */
+ ssl_FileInfo64, /* fileInfo64 */
+ ssl_WriteV, /* writev */
+ ssl_Connect, /* connect */
+ ssl_Accept, /* accept */
+ ssl_Bind, /* bind */
+ ssl_Listen, /* listen */
+ ssl_Shutdown, /* shutdown */
+ ssl_Recv, /* recv */
+ ssl_Send, /* send */
+ ssl_RecvFrom, /* recvfrom */
+ ssl_SendTo, /* sendto */
+ ssl_Poll, /* poll */
+ PR_EmulateAcceptRead, /* acceptread */
+ ssl_TransmitFile, /* transmitfile */
+ ssl_GetSockName, /* getsockname */
+ ssl_GetPeerName, /* getpeername */
+ NULL, /* getsockopt OBSOLETE */
+ NULL, /* setsockopt OBSOLETE */
+ NULL, /* getsocketoption */
+ NULL, /* setsocketoption */
+ PR_EmulateSendFile, /* Send a (partial) file with header/trailer*/
+ NULL, /* reserved for future use */
+ NULL, /* reserved for future use */
+ NULL, /* reserved for future use */
+ NULL, /* reserved for future use */
+ NULL /* reserved for future use */
+};
+
+
+static PRIOMethods combined_methods;
+
+static void
+ssl_SetupIOMethods(void)
+{
+ PRIOMethods *new_methods = &combined_methods;
+ const PRIOMethods *nspr_methods = PR_GetDefaultIOMethods();
+ const PRIOMethods *my_methods = &ssl_methods;
+
+ *new_methods = *nspr_methods;
+
+ new_methods->file_type = my_methods->file_type;
+ new_methods->close = my_methods->close;
+ new_methods->read = my_methods->read;
+ new_methods->write = my_methods->write;
+ new_methods->available = my_methods->available;
+ new_methods->available64 = my_methods->available64;
+ new_methods->fsync = my_methods->fsync;
+ new_methods->seek = my_methods->seek;
+ new_methods->seek64 = my_methods->seek64;
+ new_methods->fileInfo = my_methods->fileInfo;
+ new_methods->fileInfo64 = my_methods->fileInfo64;
+ new_methods->writev = my_methods->writev;
+ new_methods->connect = my_methods->connect;
+ new_methods->accept = my_methods->accept;
+ new_methods->bind = my_methods->bind;
+ new_methods->listen = my_methods->listen;
+ new_methods->shutdown = my_methods->shutdown;
+ new_methods->recv = my_methods->recv;
+ new_methods->send = my_methods->send;
+ new_methods->recvfrom = my_methods->recvfrom;
+ new_methods->sendto = my_methods->sendto;
+ new_methods->poll = my_methods->poll;
+ new_methods->acceptread = my_methods->acceptread;
+ new_methods->transmitfile = my_methods->transmitfile;
+ new_methods->getsockname = my_methods->getsockname;
+ new_methods->getpeername = my_methods->getpeername;
+/* new_methods->getsocketoption = my_methods->getsocketoption; */
+/* new_methods->setsocketoption = my_methods->setsocketoption; */
+ new_methods->sendfile = my_methods->sendfile;
+
+}
+
+static PRCallOnceType initIoLayerOnce;
+
+static PRStatus
+ssl_InitIOLayer(void)
+{
+ ssl_layer_id = PR_GetUniqueIdentity("SSL");
+ ssl_SetupIOMethods();
+ ssl_inited = PR_TRUE;
+ return PR_SUCCESS;
+}
+
+static PRStatus
+ssl_PushIOLayer(sslSocket *ns, PRFileDesc *stack, PRDescIdentity id)
+{
+ PRFileDesc *layer = NULL;
+ PRStatus status;
+
+ if (!ssl_inited) {
+ status = PR_CallOnce(&initIoLayerOnce, &ssl_InitIOLayer);
+ if (status != PR_SUCCESS)
+ goto loser;
+ }
+
+ if (ns == NULL)
+ goto loser;
+
+ layer = PR_CreateIOLayerStub(ssl_layer_id, &combined_methods);
+ if (layer == NULL)
+ goto loser;
+ layer->secret = (PRFilePrivate *)ns;
+
+ /* Here, "stack" points to the PRFileDesc on the top of the stack.
+ ** "layer" points to a new FD that is to be inserted into the stack.
+ ** If layer is being pushed onto the top of the stack, then
+ ** PR_PushIOLayer switches the contents of stack and layer, and then
+ ** puts stack on top of layer, so that after it is done, the top of
+ ** stack is the same "stack" as it was before, and layer is now the
+ ** FD for the former top of stack.
+ ** After this call, stack always points to the top PRFD on the stack.
+ ** If this function fails, the contents of stack and layer are as
+ ** they were before the call.
+ */
+ status = PR_PushIOLayer(stack, id, layer);
+ if (status != PR_SUCCESS)
+ goto loser;
+
+ ns->fd = (id == PR_TOP_IO_LAYER) ? stack : layer;
+ return PR_SUCCESS;
+
+loser:
+ if (layer) {
+ layer->dtor(layer); /* free layer */
+ }
+ return PR_FAILURE;
+}
+
+/* if this fails, caller must destroy socket. */
+static SECStatus
+ssl_MakeLocks(sslSocket *ss)
+{
+ ss->firstHandshakeLock = PZ_NewMonitor(nssILockSSL);
+ if (!ss->firstHandshakeLock)
+ goto loser;
+ ss->ssl3HandshakeLock = PZ_NewMonitor(nssILockSSL);
+ if (!ss->ssl3HandshakeLock)
+ goto loser;
+ ss->specLock = NSSRWLock_New(SSL_LOCK_RANK_SPEC, NULL);
+ if (!ss->specLock)
+ goto loser;
+ ss->recvBufLock = PZ_NewMonitor(nssILockSSL);
+ if (!ss->recvBufLock)
+ goto loser;
+ ss->xmitBufLock = PZ_NewMonitor(nssILockSSL);
+ if (!ss->xmitBufLock)
+ goto loser;
+ ss->writerThread = NULL;
+ if (ssl_lock_readers) {
+ ss->recvLock = PZ_NewLock(nssILockSSL);
+ if (!ss->recvLock)
+ goto loser;
+ ss->sendLock = PZ_NewLock(nssILockSSL);
+ if (!ss->sendLock)
+ goto loser;
+ }
+ return SECSuccess;
+loser:
+ ssl_DestroyLocks(ss);
+ return SECFailure;
+}
+
+#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_BEOS)
+#define NSS_HAVE_GETENV 1
+#endif
+
+#define LOWER(x) (x | 0x20) /* cheap ToLower function ignores LOCALE */
+
+static void
+ssl_SetDefaultsFromEnvironment(void)
+{
+#if defined( NSS_HAVE_GETENV )
+ static int firsttime = 1;
+
+ if (firsttime) {
+ char * ev;
+ firsttime = 0;
+#ifdef DEBUG
+ ev = getenv("SSLDEBUGFILE");
+ if (ev && ev[0]) {
+ ssl_trace_iob = fopen(ev, "w");
+ }
+ if (!ssl_trace_iob) {
+ ssl_trace_iob = stderr;
+ }
+#ifdef TRACE
+ ev = getenv("SSLTRACE");
+ if (ev && ev[0]) {
+ ssl_trace = atoi(ev);
+ SSL_TRACE(("SSL: tracing set to %d", ssl_trace));
+ }
+#endif /* TRACE */
+ ev = getenv("SSLDEBUG");
+ if (ev && ev[0]) {
+ ssl_debug = atoi(ev);
+ SSL_TRACE(("SSL: debugging set to %d", ssl_debug));
+ }
+#endif /* DEBUG */
+ ev = getenv("SSLKEYLOGFILE");
+ if (ev && ev[0]) {
+ ssl_keylog_iob = fopen(ev, "a");
+ if (!ssl_keylog_iob) {
+ SSL_TRACE(("SSL: failed to open key log file"));
+ } else {
+ if (ftell(ssl_keylog_iob) == 0) {
+ fputs("# SSL/TLS secrets log file, generated by NSS\n",
+ ssl_keylog_iob);
+ }
+ SSL_TRACE(("SSL: logging SSL/TLS secrets to %s", ev));
+ }
+ }
+#ifndef NO_PKCS11_BYPASS
+ ev = getenv("SSLBYPASS");
+ if (ev && ev[0]) {
+ ssl_defaults.bypassPKCS11 = (ev[0] == '1');
+ SSL_TRACE(("SSL: bypass default set to %d", \
+ ssl_defaults.bypassPKCS11));
+ }
+#endif /* NO_PKCS11_BYPASS */
+ ev = getenv("SSLFORCELOCKS");
+ if (ev && ev[0] == '1') {
+ ssl_force_locks = PR_TRUE;
+ ssl_defaults.noLocks = 0;
+ strcpy(lockStatus + LOCKSTATUS_OFFSET, "FORCED. ");
+ SSL_TRACE(("SSL: force_locks set to %d", ssl_force_locks));
+ }
+ ev = getenv("NSS_SSL_ENABLE_RENEGOTIATION");
+ if (ev) {
+ if (ev[0] == '1' || LOWER(ev[0]) == 'u')
+ ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_UNRESTRICTED;
+ else if (ev[0] == '0' || LOWER(ev[0]) == 'n')
+ ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
+ else if (ev[0] == '2' || LOWER(ev[0]) == 'r')
+ ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_REQUIRES_XTN;
+ else if (ev[0] == '3' || LOWER(ev[0]) == 't')
+ ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_TRANSITIONAL;
+ SSL_TRACE(("SSL: enableRenegotiation set to %d",
+ ssl_defaults.enableRenegotiation));
+ }
+ ev = getenv("NSS_SSL_REQUIRE_SAFE_NEGOTIATION");
+ if (ev && ev[0] == '1') {
+ ssl_defaults.requireSafeNegotiation = PR_TRUE;
+ SSL_TRACE(("SSL: requireSafeNegotiation set to %d",
+ PR_TRUE));
+ }
+ ev = getenv("NSS_SSL_CBC_RANDOM_IV");
+ if (ev && ev[0] == '0') {
+ ssl_defaults.cbcRandomIV = PR_FALSE;
+ SSL_TRACE(("SSL: cbcRandomIV set to 0"));
+ }
+ }
+#endif /* NSS_HAVE_GETENV */
+}
+
+/*
+** Create a newsocket structure for a file descriptor.
+*/
+static sslSocket *
+ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
+{
+ sslSocket *ss;
+
+ ssl_SetDefaultsFromEnvironment();
+
+ if (ssl_force_locks)
+ makeLocks = PR_TRUE;
+
+ /* Make a new socket and get it ready */
+ ss = (sslSocket*) PORT_ZAlloc(sizeof(sslSocket));
+ if (ss) {
+ /* This should be of type SSLKEAType, but CC on IRIX
+ * complains during the for loop.
+ */
+ int i;
+ SECStatus status;
+
+ ss->opt = ssl_defaults;
+ ss->opt.useSocks = PR_FALSE;
+ ss->opt.noLocks = !makeLocks;
+ ss->vrange = *VERSIONS_DEFAULTS(protocolVariant);
+ ss->protocolVariant = protocolVariant;
+
+ ss->peerID = NULL;
+ ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
+ ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
+ ss->cTimeout = PR_INTERVAL_NO_TIMEOUT;
+ ss->cipherSpecs = NULL;
+ ss->sizeCipherSpecs = 0; /* produced lazily */
+ ss->preferredCipher = NULL;
+ ss->url = NULL;
+
+ for (i=kt_null; i < kt_kea_size; i++) {
+ sslServerCerts * sc = ss->serverCerts + i;
+ sc->serverCert = NULL;
+ sc->serverCertChain = NULL;
+ sc->serverKeyPair = NULL;
+ sc->serverKeyBits = 0;
+ ss->certStatusArray[i] = NULL;
+ }
+ ss->requestedCertTypes = NULL;
+ ss->stepDownKeyPair = NULL;
+ ss->dbHandle = CERT_GetDefaultCertDB();
+
+ /* Provide default implementation of hooks */
+ ss->authCertificate = SSL_AuthCertificate;
+ ss->authCertificateArg = (void *)ss->dbHandle;
+ ss->sniSocketConfig = NULL;
+ ss->sniSocketConfigArg = NULL;
+ ss->getClientAuthData = NULL;
+#ifdef NSS_PLATFORM_CLIENT_AUTH
+ ss->getPlatformClientAuthData = NULL;
+ ss->getPlatformClientAuthDataArg = NULL;
+#endif /* NSS_PLATFORM_CLIENT_AUTH */
+ ss->handleBadCert = NULL;
+ ss->badCertArg = NULL;
+ ss->pkcs11PinArg = NULL;
+ ss->ephemeralECDHKeyPair = NULL;
+ ss->getChannelID = NULL;
+ ss->getChannelIDArg = NULL;
+
+ ssl_ChooseOps(ss);
+ ssl2_InitSocketPolicy(ss);
+ ssl3_InitSocketPolicy(ss);
+ PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
+
+ if (makeLocks) {
+ status = ssl_MakeLocks(ss);
+ if (status != SECSuccess)
+ goto loser;
+ }
+ status = ssl_CreateSecurityInfo(ss);
+ if (status != SECSuccess)
+ goto loser;
+ status = ssl_InitGather(&ss->gs);
+ if (status != SECSuccess) {
+loser:
+ ssl_DestroySocketContents(ss);
+ ssl_DestroyLocks(ss);
+ PORT_Free(ss);
+ ss = NULL;
+ }
+ }
+ return ss;
+}
+
diff --git a/chromium/net/third_party/nss/ssl/sslt.h b/chromium/net/third_party/nss/ssl/sslt.h
new file mode 100644
index 00000000000..f4be1743303
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslt.h
@@ -0,0 +1,208 @@
+/*
+ * This file contains prototypes for the public SSL functions.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __sslt_h_
+#define __sslt_h_
+
+#include "prtypes.h"
+
+/* SECItemArray is added in NSS 3.15. Define the type if compiling
+** against an older version of NSS.
+*/
+#include "nssutil.h"
+#if NSSUTIL_VMAJOR == 3 && NSSUTIL_VMINOR < 15
+typedef struct SECItemArrayStr SECItemArray;
+
+struct SECItemArrayStr {
+ SECItem *items;
+ unsigned int len;
+};
+#endif /* NSSUTIL_VMAJOR == 3 && NSSUTIL_VMINOR < 15 */
+
+typedef struct SSL3StatisticsStr {
+ /* statistics from ssl3_SendClientHello (sch) */
+ long sch_sid_cache_hits;
+ long sch_sid_cache_misses;
+ long sch_sid_cache_not_ok;
+
+ /* statistics from ssl3_HandleServerHello (hsh) */
+ long hsh_sid_cache_hits;
+ long hsh_sid_cache_misses;
+ long hsh_sid_cache_not_ok;
+
+ /* statistics from ssl3_HandleClientHello (hch) */
+ long hch_sid_cache_hits;
+ long hch_sid_cache_misses;
+ long hch_sid_cache_not_ok;
+
+ /* statistics related to stateless resume */
+ long sch_sid_stateless_resumes;
+ long hsh_sid_stateless_resumes;
+ long hch_sid_stateless_resumes;
+ long hch_sid_ticket_parse_failures;
+} SSL3Statistics;
+
+/* Key Exchange algorithm values */
+typedef enum {
+ ssl_kea_null = 0,
+ ssl_kea_rsa = 1,
+ ssl_kea_dh = 2,
+ ssl_kea_fortezza = 3, /* deprecated, now unused */
+ ssl_kea_ecdh = 4,
+ ssl_kea_size /* number of ssl_kea_ algorithms */
+} SSLKEAType;
+
+/* The following defines are for backwards compatibility.
+** They will be removed in a forthcoming release to reduce namespace pollution.
+** programs that use the kt_ symbols should convert to the ssl_kt_ symbols
+** soon.
+*/
+#define kt_null ssl_kea_null
+#define kt_rsa ssl_kea_rsa
+#define kt_dh ssl_kea_dh
+#define kt_fortezza ssl_kea_fortezza /* deprecated, now unused */
+#define kt_ecdh ssl_kea_ecdh
+#define kt_kea_size ssl_kea_size
+
+typedef enum {
+ ssl_sign_null = 0,
+ ssl_sign_rsa = 1,
+ ssl_sign_dsa = 2,
+ ssl_sign_ecdsa = 3
+} SSLSignType;
+
+typedef enum {
+ ssl_auth_null = 0,
+ ssl_auth_rsa = 1,
+ ssl_auth_dsa = 2,
+ ssl_auth_kea = 3,
+ ssl_auth_ecdsa = 4
+} SSLAuthType;
+
+typedef enum {
+ ssl_calg_null = 0,
+ ssl_calg_rc4 = 1,
+ ssl_calg_rc2 = 2,
+ ssl_calg_des = 3,
+ ssl_calg_3des = 4,
+ ssl_calg_idea = 5,
+ ssl_calg_fortezza = 6, /* deprecated, now unused */
+ ssl_calg_aes = 7,
+ ssl_calg_camellia = 8,
+ ssl_calg_seed = 9,
+ ssl_calg_aes_gcm = 10
+} SSLCipherAlgorithm;
+
+typedef enum {
+ ssl_mac_null = 0,
+ ssl_mac_md5 = 1,
+ ssl_mac_sha = 2,
+ ssl_hmac_md5 = 3, /* TLS HMAC version of mac_md5 */
+ ssl_hmac_sha = 4, /* TLS HMAC version of mac_sha */
+ ssl_hmac_sha256 = 5
+} SSLMACAlgorithm;
+
+typedef enum {
+ ssl_compression_null = 0,
+ ssl_compression_deflate = 1 /* RFC 3749 */
+} SSLCompressionMethod;
+
+typedef struct SSLChannelInfoStr {
+ PRUint32 length;
+ PRUint16 protocolVersion;
+ PRUint16 cipherSuite;
+
+ /* server authentication info */
+ PRUint32 authKeyBits;
+
+ /* key exchange algorithm info */
+ PRUint32 keaKeyBits;
+
+ /* session info */
+ PRUint32 creationTime; /* seconds since Jan 1, 1970 */
+ PRUint32 lastAccessTime; /* seconds since Jan 1, 1970 */
+ PRUint32 expirationTime; /* seconds since Jan 1, 1970 */
+ PRUint32 sessionIDLength; /* up to 32 */
+ PRUint8 sessionID [32];
+
+ /* The following fields are added in NSS 3.12.5. */
+
+ /* compression method info */
+ const char * compressionMethodName;
+ SSLCompressionMethod compressionMethod;
+} SSLChannelInfo;
+
+typedef struct SSLCipherSuiteInfoStr {
+ PRUint16 length;
+ PRUint16 cipherSuite;
+
+ /* Cipher Suite Name */
+ const char * cipherSuiteName;
+
+ /* server authentication info */
+ const char * authAlgorithmName;
+ SSLAuthType authAlgorithm;
+
+ /* key exchange algorithm info */
+ const char * keaTypeName;
+ SSLKEAType keaType;
+
+ /* symmetric encryption info */
+ const char * symCipherName;
+ SSLCipherAlgorithm symCipher;
+ PRUint16 symKeyBits;
+ PRUint16 symKeySpace;
+ PRUint16 effectiveKeyBits;
+
+ /* MAC info */
+ const char * macAlgorithmName;
+ SSLMACAlgorithm macAlgorithm;
+ PRUint16 macBits;
+
+ PRUintn isFIPS : 1;
+ PRUintn isExportable : 1;
+ PRUintn nonStandard : 1;
+ PRUintn reservedBits :29;
+
+} SSLCipherSuiteInfo;
+
+typedef enum {
+ ssl_variant_stream = 0,
+ ssl_variant_datagram = 1
+} SSLProtocolVariant;
+
+typedef struct SSLVersionRangeStr {
+ PRUint16 min;
+ PRUint16 max;
+} SSLVersionRange;
+
+typedef enum {
+ SSL_sni_host_name = 0,
+ SSL_sni_type_total
+} SSLSniNameType;
+
+/* Supported extensions. */
+/* Update SSL_MAX_EXTENSIONS whenever a new extension type is added. */
+typedef enum {
+ ssl_server_name_xtn = 0,
+ ssl_cert_status_xtn = 5,
+#ifdef NSS_ENABLE_ECC
+ ssl_elliptic_curves_xtn = 10,
+ ssl_ec_point_formats_xtn = 11,
+#endif
+ ssl_signature_algorithms_xtn = 13,
+ ssl_use_srtp_xtn = 14,
+ ssl_app_layer_protocol_xtn = 16,
+ ssl_session_ticket_xtn = 35,
+ ssl_next_proto_nego_xtn = 13172,
+ ssl_channel_id_xtn = 30031,
+ ssl_renegotiation_info_xtn = 0xff01 /* experimental number */
+} SSLExtensionType;
+
+#define SSL_MAX_EXTENSIONS 11
+
+#endif /* __sslt_h_ */
diff --git a/chromium/net/third_party/nss/ssl/ssltrace.c b/chromium/net/third_party/nss/ssl/ssltrace.c
new file mode 100644
index 00000000000..ee540d58751
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/ssltrace.c
@@ -0,0 +1,243 @@
+/*
+ * Functions to trace SSL protocol behavior in DEBUG builds.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <stdarg.h>
+#include "cert.h"
+#include "ssl.h"
+#include "sslimpl.h"
+#include "sslproto.h"
+#include "prprf.h"
+
+#if defined(DEBUG) || defined(TRACE)
+static const char *hex = "0123456789abcdef";
+
+static const char printable[257] = {
+ "................" /* 0x */
+ "................" /* 1x */
+ " !\"#$%&'()*+,-./" /* 2x */
+ "0123456789:;<=>?" /* 3x */
+ "@ABCDEFGHIJKLMNO" /* 4x */
+ "PQRSTUVWXYZ[\\]^_" /* 5x */
+ "`abcdefghijklmno" /* 6x */
+ "pqrstuvwxyz{|}~." /* 7x */
+ "................" /* 8x */
+ "................" /* 9x */
+ "................" /* ax */
+ "................" /* bx */
+ "................" /* cx */
+ "................" /* dx */
+ "................" /* ex */
+ "................" /* fx */
+};
+
+void ssl_PrintBuf(sslSocket *ss, const char *msg, const void *vp, int len)
+{
+ const unsigned char *cp = (const unsigned char *)vp;
+ char buf[80];
+ char *bp;
+ char *ap;
+
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: %s [Len: %d]", SSL_GETPID(), ss->fd,
+ msg, len));
+ } else {
+ SSL_TRACE(("%d: SSL: %s [Len: %d]", SSL_GETPID(), msg, len));
+ }
+ memset(buf, ' ', sizeof buf);
+ bp = buf;
+ ap = buf + 50;
+ while (--len >= 0) {
+ unsigned char ch = *cp++;
+ *bp++ = hex[(ch >> 4) & 0xf];
+ *bp++ = hex[ch & 0xf];
+ *bp++ = ' ';
+ *ap++ = printable[ch];
+ if (ap - buf >= 66) {
+ *ap = 0;
+ SSL_TRACE((" %s", buf));
+ memset(buf, ' ', sizeof buf);
+ bp = buf;
+ ap = buf + 50;
+ }
+ }
+ if (bp > buf) {
+ *ap = 0;
+ SSL_TRACE((" %s", buf));
+ }
+}
+
+#define LEN(cp) (((cp)[0] << 8) | ((cp)[1]))
+
+static void PrintType(sslSocket *ss, char *msg)
+{
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: dump-msg: %s", SSL_GETPID(), ss->fd,
+ msg));
+ } else {
+ SSL_TRACE(("%d: SSL: dump-msg: %s", SSL_GETPID(), msg));
+ }
+}
+
+static void PrintInt(sslSocket *ss, char *msg, unsigned v)
+{
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: %s=%u", SSL_GETPID(), ss->fd,
+ msg, v));
+ } else {
+ SSL_TRACE(("%d: SSL: %s=%u", SSL_GETPID(), msg, v));
+ }
+}
+
+/* PrintBuf is just like ssl_PrintBuf above, except that:
+ * a) It prefixes each line of the buffer with "XX: SSL[xxx] "
+ * b) It dumps only hex, not ASCII.
+ */
+static void PrintBuf(sslSocket *ss, char *msg, unsigned char *cp, int len)
+{
+ char buf[80];
+ char *bp;
+
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: %s [Len: %d]",
+ SSL_GETPID(), ss->fd, msg, len));
+ } else {
+ SSL_TRACE(("%d: SSL: %s [Len: %d]",
+ SSL_GETPID(), msg, len));
+ }
+ bp = buf;
+ while (--len >= 0) {
+ unsigned char ch = *cp++;
+ *bp++ = hex[(ch >> 4) & 0xf];
+ *bp++ = hex[ch & 0xf];
+ *bp++ = ' ';
+ if (bp + 4 > buf + 50) {
+ *bp = 0;
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: %s",
+ SSL_GETPID(), ss->fd, buf));
+ } else {
+ SSL_TRACE(("%d: SSL: %s", SSL_GETPID(), buf));
+ }
+ bp = buf;
+ }
+ }
+ if (bp > buf) {
+ *bp = 0;
+ if (ss) {
+ SSL_TRACE(("%d: SSL[%d]: %s",
+ SSL_GETPID(), ss->fd, buf));
+ } else {
+ SSL_TRACE(("%d: SSL: %s", SSL_GETPID(), buf));
+ }
+ }
+}
+
+void ssl_DumpMsg(sslSocket *ss, unsigned char *bp, unsigned len)
+{
+ switch (bp[0]) {
+ case SSL_MT_ERROR:
+ PrintType(ss, "Error");
+ PrintInt(ss, "error", LEN(bp+1));
+ break;
+
+ case SSL_MT_CLIENT_HELLO:
+ {
+ unsigned lcs = LEN(bp+3);
+ unsigned ls = LEN(bp+5);
+ unsigned lc = LEN(bp+7);
+
+ PrintType(ss, "Client-Hello");
+
+ PrintInt(ss, "version (Major)", bp[1]);
+ PrintInt(ss, "version (minor)", bp[2]);
+
+ PrintBuf(ss, "cipher-specs", bp+9, lcs);
+ PrintBuf(ss, "session-id", bp+9+lcs, ls);
+ PrintBuf(ss, "challenge", bp+9+lcs+ls, lc);
+ }
+ break;
+ case SSL_MT_CLIENT_MASTER_KEY:
+ {
+ unsigned lck = LEN(bp+4);
+ unsigned lek = LEN(bp+6);
+ unsigned lka = LEN(bp+8);
+
+ PrintType(ss, "Client-Master-Key");
+
+ PrintInt(ss, "cipher-choice", bp[1]);
+ PrintInt(ss, "key-length", LEN(bp+2));
+
+ PrintBuf(ss, "clear-key", bp+10, lck);
+ PrintBuf(ss, "encrypted-key", bp+10+lck, lek);
+ PrintBuf(ss, "key-arg", bp+10+lck+lek, lka);
+ }
+ break;
+ case SSL_MT_CLIENT_FINISHED:
+ PrintType(ss, "Client-Finished");
+ PrintBuf(ss, "connection-id", bp+1, len-1);
+ break;
+ case SSL_MT_SERVER_HELLO:
+ {
+ unsigned lc = LEN(bp+5);
+ unsigned lcs = LEN(bp+7);
+ unsigned lci = LEN(bp+9);
+
+ PrintType(ss, "Server-Hello");
+
+ PrintInt(ss, "session-id-hit", bp[1]);
+ PrintInt(ss, "certificate-type", bp[2]);
+ PrintInt(ss, "version (Major)", bp[3]);
+ PrintInt(ss, "version (minor)", bp[3]);
+ PrintBuf(ss, "certificate", bp+11, lc);
+ PrintBuf(ss, "cipher-specs", bp+11+lc, lcs);
+ PrintBuf(ss, "connection-id", bp+11+lc+lcs, lci);
+ }
+ break;
+ case SSL_MT_SERVER_VERIFY:
+ PrintType(ss, "Server-Verify");
+ PrintBuf(ss, "challenge", bp+1, len-1);
+ break;
+ case SSL_MT_SERVER_FINISHED:
+ PrintType(ss, "Server-Finished");
+ PrintBuf(ss, "session-id", bp+1, len-1);
+ break;
+ case SSL_MT_REQUEST_CERTIFICATE:
+ PrintType(ss, "Request-Certificate");
+ PrintInt(ss, "authentication-type", bp[1]);
+ PrintBuf(ss, "certificate-challenge", bp+2, len-2);
+ break;
+ case SSL_MT_CLIENT_CERTIFICATE:
+ {
+ unsigned lc = LEN(bp+2);
+ unsigned lr = LEN(bp+4);
+ PrintType(ss, "Client-Certificate");
+ PrintInt(ss, "certificate-type", bp[1]);
+ PrintBuf(ss, "certificate", bp+6, lc);
+ PrintBuf(ss, "response", bp+6+lc, lr);
+ }
+ break;
+ default:
+ ssl_PrintBuf(ss, "sending *unknown* message type", bp, len);
+ return;
+ }
+}
+
+void
+ssl_Trace(const char *format, ... )
+{
+ char buf[2000];
+ va_list args;
+
+ if (ssl_trace_iob) {
+ va_start(args, format);
+ PR_vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+
+ fputs(buf, ssl_trace_iob);
+ fputs("\n", ssl_trace_iob);
+ }
+}
+#endif
diff --git a/chromium/net/third_party/nss/ssl/sslver.c b/chromium/net/third_party/nss/ssl/sslver.c
new file mode 100644
index 00000000000..35e0317eac2
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/sslver.c
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Library identity and versioning */
+
+#include "nss.h"
+
+#if defined(DEBUG)
+#define _DEBUG_STRING " (debug)"
+#else
+#define _DEBUG_STRING ""
+#endif
+
+/*
+ * Version information for the 'ident' and 'what commands
+ *
+ * NOTE: the first component of the concatenated rcsid string
+ * must not end in a '$' to prevent rcs keyword substitution.
+ */
+const char __nss_ssl_rcsid[] = "$Header: NSS " NSS_VERSION _DEBUG_STRING
+ " " __DATE__ " " __TIME__ " $";
+const char __nss_ssl_sccsid[] = "@(#)NSS " NSS_VERSION _DEBUG_STRING
+ " " __DATE__ " " __TIME__;
diff --git a/chromium/net/third_party/nss/ssl/unix_err.c b/chromium/net/third_party/nss/ssl/unix_err.c
new file mode 100644
index 00000000000..1857cfefc70
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/unix_err.c
@@ -0,0 +1,517 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * this code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if 0
+#include "primpl.h"
+#else
+#define _PR_POLL_AVAILABLE 1
+#include "prerror.h"
+#endif
+
+#if defined (__bsdi__) || defined(NTO) || defined(DARWIN) || defined(BEOS)
+#undef _PR_POLL_AVAILABLE
+#endif
+
+#if defined(_PR_POLL_AVAILABLE)
+#include <poll.h>
+#endif
+#include <errno.h>
+
+/* forward declarations. */
+void nss_MD_unix_map_default_error(int err);
+
+void nss_MD_unix_map_opendir_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_closedir_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_readdir_error(int err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+ case ENOENT: prError = PR_NO_MORE_FILES_ERROR; break;
+#ifdef EOVERFLOW
+ case EOVERFLOW: prError = PR_IO_ERROR; break;
+#endif
+ case EINVAL: prError = PR_IO_ERROR; break;
+ case ENXIO: prError = PR_IO_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_unlink_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EPERM: prError = PR_IS_DIRECTORY_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_stat_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_fstat_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_rename_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EEXIST: prError = PR_DIRECTORY_NOT_EMPTY_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_access_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_mkdir_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_rmdir_error(int err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+ case EEXIST: prError = PR_DIRECTORY_NOT_EMPTY_ERROR; break;
+ case EINVAL: prError = PR_DIRECTORY_NOT_EMPTY_ERROR; break;
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_read_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_INVALID_METHOD_ERROR; break;
+ case ENXIO: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_write_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_INVALID_METHOD_ERROR; break;
+ case ENXIO: prError = PR_INVALID_METHOD_ERROR; break;
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_lseek_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_fsync_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ case EINVAL: prError = PR_INVALID_METHOD_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_close_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_socket_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_socketavailable_error(int err)
+{
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, err);
+}
+
+void nss_MD_unix_map_recv_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_recvfrom_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_send_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_sendto_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_writev_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_accept_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ENODEV: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_connect_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EACCES: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+#if defined(UNIXWARE) || defined(SNI) || defined(NEC)
+ /*
+ * On some platforms, if we connect to a port on the local host
+ * (the loopback address) that no process is listening on, we get
+ * EIO instead of ECONNREFUSED.
+ */
+ case EIO: prError = PR_CONNECT_REFUSED_ERROR; break;
+#endif
+ case ELOOP: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ENOENT: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ENXIO: prError = PR_IO_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_bind_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_SOCKET_ADDRESS_IS_BOUND_ERROR; break;
+ /*
+ * UNIX domain sockets are not supported in NSPR
+ */
+ case EIO: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case EISDIR: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ELOOP: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ENOENT: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ENOTDIR: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case EROFS: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_listen_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_shutdown_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_socketpair_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_getsockname_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_getpeername_error(int err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_getsockopt_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_BUFFER_OVERFLOW_ERROR; break;
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_setsockopt_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_BUFFER_OVERFLOW_ERROR; break;
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_open_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EAGAIN: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case EBUSY: prError = PR_IO_ERROR; break;
+ case ENODEV: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case ENOMEM: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case ETIMEDOUT: prError = PR_REMOTE_FILE_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_mmap_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EAGAIN: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case EMFILE: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case ENODEV: prError = PR_OPERATION_NOT_SUPPORTED_ERROR; break;
+ case ENXIO: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_gethostname_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+void nss_MD_unix_map_select_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+
+#ifdef _PR_POLL_AVAILABLE
+void nss_MD_unix_map_poll_error(int err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+ case EAGAIN: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_poll_revents_error(int err)
+{
+ if (err & POLLNVAL)
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, EBADF);
+ else if (err & POLLHUP)
+ PR_SetError(PR_CONNECT_RESET_ERROR, EPIPE);
+ else if (err & POLLERR)
+ PR_SetError(PR_IO_ERROR, EIO);
+ else
+ PR_SetError(PR_UNKNOWN_ERROR, err);
+}
+#endif /* _PR_POLL_AVAILABLE */
+
+
+void nss_MD_unix_map_flock_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EINVAL: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ case EWOULDBLOCK: prError = PR_FILE_IS_LOCKED_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_unix_map_lockf_error(int err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case EACCES: prError = PR_FILE_IS_LOCKED_ERROR; break;
+ case EDEADLK: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ default: nss_MD_unix_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+#ifdef HPUX11
+void nss_MD_hpux_map_sendfile_error(int err)
+{
+ nss_MD_unix_map_default_error(err);
+}
+#endif /* HPUX11 */
+
+
+void nss_MD_unix_map_default_error(int err)
+{
+ PRErrorCode prError;
+ switch (err ) {
+ case EACCES: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case EADDRINUSE: prError = PR_ADDRESS_IN_USE_ERROR; break;
+ case EADDRNOTAVAIL: prError = PR_ADDRESS_NOT_AVAILABLE_ERROR; break;
+ case EAFNOSUPPORT: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case EAGAIN: prError = PR_WOULD_BLOCK_ERROR; break;
+ /*
+ * On QNX and Neutrino, EALREADY is defined as EBUSY.
+ */
+#if EALREADY != EBUSY
+ case EALREADY: prError = PR_ALREADY_INITIATED_ERROR; break;
+#endif
+ case EBADF: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+#ifdef EBADMSG
+ case EBADMSG: prError = PR_IO_ERROR; break;
+#endif
+ case EBUSY: prError = PR_FILESYSTEM_MOUNTED_ERROR; break;
+ case ECONNREFUSED: prError = PR_CONNECT_REFUSED_ERROR; break;
+ case ECONNRESET: prError = PR_CONNECT_RESET_ERROR; break;
+ case EDEADLK: prError = PR_DEADLOCK_ERROR; break;
+#ifdef EDIRCORRUPTED
+ case EDIRCORRUPTED: prError = PR_DIRECTORY_CORRUPTED_ERROR; break;
+#endif
+#ifdef EDQUOT
+ case EDQUOT: prError = PR_NO_DEVICE_SPACE_ERROR; break;
+#endif
+ case EEXIST: prError = PR_FILE_EXISTS_ERROR; break;
+ case EFAULT: prError = PR_ACCESS_FAULT_ERROR; break;
+ case EFBIG: prError = PR_FILE_TOO_BIG_ERROR; break;
+ case EINPROGRESS: prError = PR_IN_PROGRESS_ERROR; break;
+ case EINTR: prError = PR_PENDING_INTERRUPT_ERROR; break;
+ case EINVAL: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case EIO: prError = PR_IO_ERROR; break;
+ case EISCONN: prError = PR_IS_CONNECTED_ERROR; break;
+ case EISDIR: prError = PR_IS_DIRECTORY_ERROR; break;
+ case ELOOP: prError = PR_LOOP_ERROR; break;
+ case EMFILE: prError = PR_PROC_DESC_TABLE_FULL_ERROR; break;
+ case EMLINK: prError = PR_MAX_DIRECTORY_ENTRIES_ERROR; break;
+ case EMSGSIZE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+#ifdef EMULTIHOP
+ case EMULTIHOP: prError = PR_REMOTE_FILE_ERROR; break;
+#endif
+ case ENAMETOOLONG: prError = PR_NAME_TOO_LONG_ERROR; break;
+ case ENETUNREACH: prError = PR_NETWORK_UNREACHABLE_ERROR; break;
+ case ENFILE: prError = PR_SYS_DESC_TABLE_FULL_ERROR; break;
+#if !defined(SCO)
+ case ENOBUFS: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+#endif
+ case ENODEV: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case ENOENT: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case ENOLCK: prError = PR_FILE_IS_LOCKED_ERROR; break;
+#ifdef ENOLINK
+ case ENOLINK: prError = PR_REMOTE_FILE_ERROR; break;
+#endif
+ case ENOMEM: prError = PR_OUT_OF_MEMORY_ERROR; break;
+ case ENOPROTOOPT: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case ENOSPC: prError = PR_NO_DEVICE_SPACE_ERROR; break;
+#ifdef ENOSR
+ case ENOSR: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+#endif
+ case ENOTCONN: prError = PR_NOT_CONNECTED_ERROR; break;
+ case ENOTDIR: prError = PR_NOT_DIRECTORY_ERROR; break;
+ case ENOTSOCK: prError = PR_NOT_SOCKET_ERROR; break;
+ case ENXIO: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case EOPNOTSUPP: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+#ifdef EOVERFLOW
+ case EOVERFLOW: prError = PR_BUFFER_OVERFLOW_ERROR; break;
+#endif
+ case EPERM: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case EPIPE: prError = PR_CONNECT_RESET_ERROR; break;
+#ifdef EPROTO
+ case EPROTO: prError = PR_IO_ERROR; break;
+#endif
+ case EPROTONOSUPPORT: prError = PR_PROTOCOL_NOT_SUPPORTED_ERROR; break;
+ case EPROTOTYPE: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case ERANGE: prError = PR_INVALID_METHOD_ERROR; break;
+ case EROFS: prError = PR_READ_ONLY_FILESYSTEM_ERROR; break;
+ case ESPIPE: prError = PR_INVALID_METHOD_ERROR; break;
+ case ETIMEDOUT: prError = PR_IO_TIMEOUT_ERROR; break;
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK: prError = PR_WOULD_BLOCK_ERROR; break;
+#endif
+ case EXDEV: prError = PR_NOT_SAME_DEVICE_ERROR; break;
+
+ default: prError = PR_UNKNOWN_ERROR; break;
+ }
+ PR_SetError(prError, err);
+}
diff --git a/chromium/net/third_party/nss/ssl/unix_err.h b/chromium/net/third_party/nss/ssl/unix_err.h
new file mode 100644
index 00000000000..be7fe292178
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/unix_err.h
@@ -0,0 +1,57 @@
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * this code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* NSPR doesn't make these functions public, so we have to duplicate
+** them in NSS.
+*/
+extern void nss_MD_hpux_map_sendfile_error(int err);
+extern void nss_MD_unix_map_accept_error(int err);
+extern void nss_MD_unix_map_access_error(int err);
+extern void nss_MD_unix_map_bind_error(int err);
+extern void nss_MD_unix_map_close_error(int err);
+extern void nss_MD_unix_map_closedir_error(int err);
+extern void nss_MD_unix_map_connect_error(int err);
+extern void nss_MD_unix_map_default_error(int err);
+extern void nss_MD_unix_map_flock_error(int err);
+extern void nss_MD_unix_map_fstat_error(int err);
+extern void nss_MD_unix_map_fsync_error(int err);
+extern void nss_MD_unix_map_gethostname_error(int err);
+extern void nss_MD_unix_map_getpeername_error(int err);
+extern void nss_MD_unix_map_getsockname_error(int err);
+extern void nss_MD_unix_map_getsockopt_error(int err);
+extern void nss_MD_unix_map_listen_error(int err);
+extern void nss_MD_unix_map_lockf_error(int err);
+extern void nss_MD_unix_map_lseek_error(int err);
+extern void nss_MD_unix_map_mkdir_error(int err);
+extern void nss_MD_unix_map_mmap_error(int err);
+extern void nss_MD_unix_map_open_error(int err);
+extern void nss_MD_unix_map_opendir_error(int err);
+extern void nss_MD_unix_map_poll_error(int err);
+extern void nss_MD_unix_map_poll_revents_error(int err);
+extern void nss_MD_unix_map_read_error(int err);
+extern void nss_MD_unix_map_readdir_error(int err);
+extern void nss_MD_unix_map_recv_error(int err);
+extern void nss_MD_unix_map_recvfrom_error(int err);
+extern void nss_MD_unix_map_rename_error(int err);
+extern void nss_MD_unix_map_rmdir_error(int err);
+extern void nss_MD_unix_map_select_error(int err);
+extern void nss_MD_unix_map_send_error(int err);
+extern void nss_MD_unix_map_sendto_error(int err);
+extern void nss_MD_unix_map_setsockopt_error(int err);
+extern void nss_MD_unix_map_shutdown_error(int err);
+extern void nss_MD_unix_map_socket_error(int err);
+extern void nss_MD_unix_map_socketavailable_error(int err);
+extern void nss_MD_unix_map_socketpair_error(int err);
+extern void nss_MD_unix_map_stat_error(int err);
+extern void nss_MD_unix_map_unlink_error(int err);
+extern void nss_MD_unix_map_write_error(int err);
+extern void nss_MD_unix_map_writev_error(int err);
diff --git a/chromium/net/third_party/nss/ssl/win32err.c b/chromium/net/third_party/nss/ssl/win32err.c
new file mode 100644
index 00000000000..a70010d370a
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/win32err.c
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * this code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prerror.h"
+#include "prlog.h"
+#include <errno.h>
+#include <windows.h>
+
+/*
+ * On Win32, we map three kinds of error codes:
+ * - GetLastError(): for Win32 functions
+ * - WSAGetLastError(): for Winsock functions
+ * - errno: for standard C library functions
+ *
+ * We do not check for WSAEINPROGRESS and WSAEINTR because we do not
+ * use blocking Winsock 1.1 calls.
+ *
+ * Except for the 'socket' call, we do not check for WSAEINITIALISED.
+ * It is assumed that if Winsock is not initialized, that fact will
+ * be detected at the time we create new sockets.
+ */
+
+/* forward declaration. */
+void nss_MD_win32_map_default_error(PRInt32 err);
+
+void nss_MD_win32_map_opendir_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_closedir_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_readdir_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_delete_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+/* The error code for stat() is in errno. */
+void nss_MD_win32_map_stat_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_fstat_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_rename_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+/* The error code for access() is in errno. */
+void nss_MD_win32_map_access_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_mkdir_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_rmdir_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_read_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_transmitfile_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_write_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_lseek_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_fsync_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+/*
+ * For both CloseHandle() and closesocket().
+ */
+void nss_MD_win32_map_close_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_socket_error(PRInt32 err)
+{
+ PR_ASSERT(err != WSANOTINITIALISED);
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_recv_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_recvfrom_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_send_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEMSGSIZE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_sendto_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEMSGSIZE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_accept_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEOPNOTSUPP: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+ case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_acceptex_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_connect_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEWOULDBLOCK: prError = PR_IN_PROGRESS_ERROR; break;
+ case WSAEINVAL: prError = PR_ALREADY_INITIATED_ERROR; break;
+ case WSAETIMEDOUT: prError = PR_IO_TIMEOUT_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_bind_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEINVAL: prError = PR_SOCKET_ADDRESS_IS_BOUND_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_listen_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEOPNOTSUPP: prError = PR_NOT_TCP_SOCKET_ERROR; break;
+ case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_shutdown_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_getsockname_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAEINVAL: prError = PR_INVALID_STATE_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_getpeername_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_getsockopt_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_setsockopt_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_open_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+void nss_MD_win32_map_gethostname_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+/* Win32 select() only works on sockets. So in this
+** context, WSAENOTSOCK is equivalent to EBADF on Unix.
+*/
+void nss_MD_win32_map_select_error(PRInt32 err)
+{
+ PRErrorCode prError;
+ switch (err) {
+ case WSAENOTSOCK: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ default: nss_MD_win32_map_default_error(err); return;
+ }
+ PR_SetError(prError, err);
+}
+
+void nss_MD_win32_map_lockf_error(PRInt32 err)
+{
+ nss_MD_win32_map_default_error(err);
+}
+
+
+
+void nss_MD_win32_map_default_error(PRInt32 err)
+{
+ PRErrorCode prError;
+
+ switch (err) {
+ case EACCES: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case ENOENT: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case ERROR_ACCESS_DENIED: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case ERROR_ALREADY_EXISTS: prError = PR_FILE_EXISTS_ERROR; break;
+ case ERROR_DISK_CORRUPT: prError = PR_IO_ERROR; break;
+ case ERROR_DISK_FULL: prError = PR_NO_DEVICE_SPACE_ERROR; break;
+ case ERROR_DISK_OPERATION_FAILED: prError = PR_IO_ERROR; break;
+ case ERROR_DRIVE_LOCKED: prError = PR_FILE_IS_LOCKED_ERROR; break;
+ case ERROR_FILENAME_EXCED_RANGE: prError = PR_NAME_TOO_LONG_ERROR; break;
+ case ERROR_FILE_CORRUPT: prError = PR_IO_ERROR; break;
+ case ERROR_FILE_EXISTS: prError = PR_FILE_EXISTS_ERROR; break;
+ case ERROR_FILE_INVALID: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+#if ERROR_FILE_NOT_FOUND != ENOENT
+ case ERROR_FILE_NOT_FOUND: prError = PR_FILE_NOT_FOUND_ERROR; break;
+#endif
+ case ERROR_HANDLE_DISK_FULL: prError = PR_NO_DEVICE_SPACE_ERROR; break;
+ case ERROR_INVALID_ADDRESS: prError = PR_ACCESS_FAULT_ERROR; break;
+ case ERROR_INVALID_HANDLE: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ case ERROR_INVALID_NAME: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case ERROR_INVALID_PARAMETER: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case ERROR_INVALID_USER_BUFFER: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case ERROR_LOCKED: prError = PR_FILE_IS_LOCKED_ERROR; break;
+ case ERROR_NETNAME_DELETED: prError = PR_CONNECT_RESET_ERROR; break;
+ case ERROR_NOACCESS: prError = PR_ACCESS_FAULT_ERROR; break;
+ case ERROR_NOT_ENOUGH_MEMORY: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case ERROR_NOT_ENOUGH_QUOTA: prError = PR_OUT_OF_MEMORY_ERROR; break;
+ case ERROR_NOT_READY: prError = PR_IO_ERROR; break;
+ case ERROR_NO_MORE_FILES: prError = PR_NO_MORE_FILES_ERROR; break;
+ case ERROR_OPEN_FAILED: prError = PR_IO_ERROR; break;
+ case ERROR_OPEN_FILES: prError = PR_IO_ERROR; break;
+ case ERROR_OUTOFMEMORY: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case ERROR_PATH_BUSY: prError = PR_IO_ERROR; break;
+ case ERROR_PATH_NOT_FOUND: prError = PR_FILE_NOT_FOUND_ERROR; break;
+ case ERROR_SEEK_ON_DEVICE: prError = PR_IO_ERROR; break;
+ case ERROR_SHARING_VIOLATION: prError = PR_FILE_IS_BUSY_ERROR; break;
+ case ERROR_STACK_OVERFLOW: prError = PR_ACCESS_FAULT_ERROR; break;
+ case ERROR_TOO_MANY_OPEN_FILES: prError = PR_SYS_DESC_TABLE_FULL_ERROR; break;
+ case ERROR_WRITE_PROTECT: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case WSAEACCES: prError = PR_NO_ACCESS_RIGHTS_ERROR; break;
+ case WSAEADDRINUSE: prError = PR_ADDRESS_IN_USE_ERROR; break;
+ case WSAEADDRNOTAVAIL: prError = PR_ADDRESS_NOT_AVAILABLE_ERROR; break;
+ case WSAEAFNOSUPPORT: prError = PR_ADDRESS_NOT_SUPPORTED_ERROR; break;
+ case WSAEALREADY: prError = PR_ALREADY_INITIATED_ERROR; break;
+ case WSAEBADF: prError = PR_BAD_DESCRIPTOR_ERROR; break;
+ case WSAECONNABORTED: prError = PR_CONNECT_ABORTED_ERROR; break;
+ case WSAECONNREFUSED: prError = PR_CONNECT_REFUSED_ERROR; break;
+ case WSAECONNRESET: prError = PR_CONNECT_RESET_ERROR; break;
+ case WSAEDESTADDRREQ: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case WSAEFAULT: prError = PR_ACCESS_FAULT_ERROR; break;
+ case WSAEHOSTUNREACH: prError = PR_HOST_UNREACHABLE_ERROR; break;
+ case WSAEINVAL: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case WSAEISCONN: prError = PR_IS_CONNECTED_ERROR; break;
+ case WSAEMFILE: prError = PR_PROC_DESC_TABLE_FULL_ERROR; break;
+ case WSAEMSGSIZE: prError = PR_BUFFER_OVERFLOW_ERROR; break;
+ case WSAENETDOWN: prError = PR_NETWORK_DOWN_ERROR; break;
+ case WSAENETRESET: prError = PR_CONNECT_ABORTED_ERROR; break;
+ case WSAENETUNREACH: prError = PR_NETWORK_UNREACHABLE_ERROR; break;
+ case WSAENOBUFS: prError = PR_INSUFFICIENT_RESOURCES_ERROR; break;
+ case WSAENOPROTOOPT: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case WSAENOTCONN: prError = PR_NOT_CONNECTED_ERROR; break;
+ case WSAENOTSOCK: prError = PR_NOT_SOCKET_ERROR; break;
+ case WSAEOPNOTSUPP: prError = PR_OPERATION_NOT_SUPPORTED_ERROR; break;
+ case WSAEPROTONOSUPPORT: prError = PR_PROTOCOL_NOT_SUPPORTED_ERROR; break;
+ case WSAEPROTOTYPE: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case WSAESHUTDOWN: prError = PR_SOCKET_SHUTDOWN_ERROR; break;
+ case WSAESOCKTNOSUPPORT: prError = PR_INVALID_ARGUMENT_ERROR; break;
+ case WSAETIMEDOUT: prError = PR_CONNECT_ABORTED_ERROR; break;
+ case WSAEWOULDBLOCK: prError = PR_WOULD_BLOCK_ERROR; break;
+ default: prError = PR_UNKNOWN_ERROR; break;
+ }
+ PR_SetError(prError, err);
+}
+
diff --git a/chromium/net/third_party/nss/ssl/win32err.h b/chromium/net/third_party/nss/ssl/win32err.h
new file mode 100644
index 00000000000..8ce588ec5a1
--- /dev/null
+++ b/chromium/net/third_party/nss/ssl/win32err.h
@@ -0,0 +1,51 @@
+/*
+ * This file essentially replicates NSPR's source for the functions that
+ * map system-specific error codes to NSPR error codes. We would use
+ * NSPR's functions, instead of duplicating them, but they're private.
+ * As long as SSL's server session cache code must do platform native I/O
+ * to accomplish its job, and NSPR's error mapping functions remain private,
+ * This code will continue to need to be replicated.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* NSPR doesn't make these functions public, so we have to duplicate
+** them in NSS.
+*/
+extern void nss_MD_win32_map_accept_error(PRInt32 err);
+extern void nss_MD_win32_map_acceptex_error(PRInt32 err);
+extern void nss_MD_win32_map_access_error(PRInt32 err);
+extern void nss_MD_win32_map_bind_error(PRInt32 err);
+extern void nss_MD_win32_map_close_error(PRInt32 err);
+extern void nss_MD_win32_map_closedir_error(PRInt32 err);
+extern void nss_MD_win32_map_connect_error(PRInt32 err);
+extern void nss_MD_win32_map_default_error(PRInt32 err);
+extern void nss_MD_win32_map_delete_error(PRInt32 err);
+extern void nss_MD_win32_map_fstat_error(PRInt32 err);
+extern void nss_MD_win32_map_fsync_error(PRInt32 err);
+extern void nss_MD_win32_map_gethostname_error(PRInt32 err);
+extern void nss_MD_win32_map_getpeername_error(PRInt32 err);
+extern void nss_MD_win32_map_getsockname_error(PRInt32 err);
+extern void nss_MD_win32_map_getsockopt_error(PRInt32 err);
+extern void nss_MD_win32_map_listen_error(PRInt32 err);
+extern void nss_MD_win32_map_lockf_error(PRInt32 err);
+extern void nss_MD_win32_map_lseek_error(PRInt32 err);
+extern void nss_MD_win32_map_mkdir_error(PRInt32 err);
+extern void nss_MD_win32_map_open_error(PRInt32 err);
+extern void nss_MD_win32_map_opendir_error(PRInt32 err);
+extern void nss_MD_win32_map_read_error(PRInt32 err);
+extern void nss_MD_win32_map_readdir_error(PRInt32 err);
+extern void nss_MD_win32_map_recv_error(PRInt32 err);
+extern void nss_MD_win32_map_recvfrom_error(PRInt32 err);
+extern void nss_MD_win32_map_rename_error(PRInt32 err);
+extern void nss_MD_win32_map_rmdir_error(PRInt32 err);
+extern void nss_MD_win32_map_select_error(PRInt32 err);
+extern void nss_MD_win32_map_send_error(PRInt32 err);
+extern void nss_MD_win32_map_sendto_error(PRInt32 err);
+extern void nss_MD_win32_map_setsockopt_error(PRInt32 err);
+extern void nss_MD_win32_map_shutdown_error(PRInt32 err);
+extern void nss_MD_win32_map_socket_error(PRInt32 err);
+extern void nss_MD_win32_map_stat_error(PRInt32 err);
+extern void nss_MD_win32_map_transmitfile_error(PRInt32 err);
+extern void nss_MD_win32_map_write_error(PRInt32 err);
diff --git a/chromium/net/tools/DEPS b/chromium/net/tools/DEPS
new file mode 100644
index 00000000000..90fa4c0e138
--- /dev/null
+++ b/chromium/net/tools/DEPS
@@ -0,0 +1,3 @@
+skip_child_includes = [
+ "flip_server",
+]
diff --git a/chromium/net/tools/crash_cache/crash_cache.cc b/chromium/net/tools/crash_cache/crash_cache.cc
new file mode 100644
index 00000000000..700e822aeba
--- /dev/null
+++ b/chromium/net/tools/crash_cache/crash_cache.cc
@@ -0,0 +1,382 @@
+// Copyright (c) 2011 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.
+
+// This command-line program generates the set of files needed for the crash-
+// cache unit tests (DiskCacheTest,CacheBackend_Recover*). This program only
+// works properly on debug mode, because the crash functionality is not compiled
+// on release builds of the cache.
+
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/disk_cache_test_util.h"
+#include "net/disk_cache/rankings.h"
+
+using base::Time;
+
+enum Errors {
+ GENERIC = -1,
+ ALL_GOOD = 0,
+ INVALID_ARGUMENT = 1,
+ CRASH_OVERWRITE,
+ NOT_REACHED
+};
+
+using disk_cache::RankCrashes;
+
+// Starts a new process, to generate the files.
+int RunSlave(RankCrashes action) {
+ base::FilePath exe;
+ PathService::Get(base::FILE_EXE, &exe);
+
+ CommandLine cmdline(exe);
+ cmdline.AppendArg(base::IntToString(action));
+
+ base::ProcessHandle handle;
+ if (!base::LaunchProcess(cmdline, base::LaunchOptions(), &handle)) {
+ printf("Unable to run test %d\n", action);
+ return GENERIC;
+ }
+
+ int exit_code;
+
+ if (!base::WaitForExitCode(handle, &exit_code)) {
+ printf("Unable to get return code, test %d\n", action);
+ return GENERIC;
+ }
+ if (ALL_GOOD != exit_code)
+ printf("Test %d failed, code %d\n", action, exit_code);
+
+ return exit_code;
+}
+
+// Main loop for the master process.
+int MasterCode() {
+ for (int i = disk_cache::NO_CRASH + 1; i < disk_cache::MAX_CRASH; i++) {
+ int ret = RunSlave(static_cast<RankCrashes>(i));
+ if (ALL_GOOD != ret)
+ return ret;
+ }
+
+ return ALL_GOOD;
+}
+
+// -----------------------------------------------------------------------
+
+namespace disk_cache {
+NET_EXPORT_PRIVATE extern RankCrashes g_rankings_crash;
+}
+
+const char* kCrashEntryName = "the first key";
+
+// Creates the destinaton folder for this run, and returns it on full_path.
+bool CreateTargetFolder(const base::FilePath& path, RankCrashes action,
+ base::FilePath* full_path) {
+ const char* folders[] = {
+ "",
+ "insert_empty1",
+ "insert_empty2",
+ "insert_empty3",
+ "insert_one1",
+ "insert_one2",
+ "insert_one3",
+ "insert_load1",
+ "insert_load2",
+ "remove_one1",
+ "remove_one2",
+ "remove_one3",
+ "remove_one4",
+ "remove_head1",
+ "remove_head2",
+ "remove_head3",
+ "remove_head4",
+ "remove_tail1",
+ "remove_tail2",
+ "remove_tail3",
+ "remove_load1",
+ "remove_load2",
+ "remove_load3"
+ };
+ COMPILE_ASSERT(arraysize(folders) == disk_cache::MAX_CRASH, sync_folders);
+ DCHECK(action > disk_cache::NO_CRASH && action < disk_cache::MAX_CRASH);
+
+ *full_path = path.AppendASCII(folders[action]);
+
+ if (base::PathExists(*full_path))
+ return false;
+
+ return file_util::CreateDirectory(*full_path);
+}
+
+// Makes sure that any pending task is processed.
+void FlushQueue(disk_cache::Backend* cache) {
+ net::TestCompletionCallback cb;
+ int rv =
+ reinterpret_cast<disk_cache::BackendImpl*>(cache)->FlushQueueForTest(
+ cb.callback());
+ cb.GetResult(rv); // Ignore the result;
+}
+
+bool CreateCache(const base::FilePath& path,
+ base::Thread* thread,
+ disk_cache::Backend** cache,
+ net::TestCompletionCallback* cb) {
+ int size = 1024 * 1024;
+ disk_cache::BackendImpl* backend = new disk_cache::BackendImpl(
+ path, thread->message_loop_proxy().get(), NULL);
+ backend->SetMaxSize(size);
+ backend->SetType(net::DISK_CACHE);
+ backend->SetFlags(disk_cache::kNoRandom);
+ int rv = backend->Init(cb->callback());
+ *cache = backend;
+ return (cb->GetResult(rv) == net::OK && !(*cache)->GetEntryCount());
+}
+
+// Generates the files for an empty and one item cache.
+int SimpleInsert(const base::FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ if (!CreateCache(path, cache_thread, &cache, &cb))
+ return GENERIC;
+
+ const char* test_name = "some other key";
+
+ if (action <= disk_cache::INSERT_EMPTY_3) {
+ test_name = kCrashEntryName;
+ disk_cache::g_rankings_crash = action;
+ }
+
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry(test_name, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ entry->Close();
+ FlushQueue(cache);
+
+ DCHECK(action <= disk_cache::INSERT_ONE_3);
+ disk_cache::g_rankings_crash = action;
+ test_name = kCrashEntryName;
+
+ rv = cache->CreateEntry(test_name, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ return NOT_REACHED;
+}
+
+// Generates the files for a one item cache, and removing the head.
+int SimpleRemove(const base::FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
+ DCHECK(action >= disk_cache::REMOVE_ONE_1);
+ DCHECK(action <= disk_cache::REMOVE_TAIL_3);
+
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ if (!CreateCache(path, cache_thread, &cache, &cb))
+ return GENERIC;
+
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ entry->Close();
+ FlushQueue(cache);
+
+ if (action >= disk_cache::REMOVE_TAIL_1) {
+ rv = cache->CreateEntry("some other key", &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ entry->Close();
+ FlushQueue(cache);
+ }
+
+ rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ disk_cache::g_rankings_crash = action;
+ entry->Doom();
+ entry->Close();
+ FlushQueue(cache);
+
+ return NOT_REACHED;
+}
+
+int HeadRemove(const base::FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
+ DCHECK(action >= disk_cache::REMOVE_HEAD_1);
+ DCHECK(action <= disk_cache::REMOVE_HEAD_4);
+
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ if (!CreateCache(path, cache_thread, &cache, &cb))
+ return GENERIC;
+
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry("some other key", &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ entry->Close();
+ FlushQueue(cache);
+ rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ entry->Close();
+ FlushQueue(cache);
+
+ rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ disk_cache::g_rankings_crash = action;
+ entry->Doom();
+ entry->Close();
+ FlushQueue(cache);
+
+ return NOT_REACHED;
+}
+
+// Generates the files for insertion and removals on heavy loaded caches.
+int LoadOperations(const base::FilePath& path, RankCrashes action,
+ base::Thread* cache_thread) {
+ DCHECK(action >= disk_cache::INSERT_LOAD_1);
+
+ // Work with a tiny index table (16 entries).
+ disk_cache::BackendImpl* cache = new disk_cache::BackendImpl(
+ path, 0xf, cache_thread->message_loop_proxy().get(), NULL);
+ if (!cache || !cache->SetMaxSize(0x100000))
+ return GENERIC;
+
+ // No experiments and use a simple LRU.
+ cache->SetFlags(disk_cache::kNoRandom);
+ net::TestCompletionCallback cb;
+ int rv = cache->Init(cb.callback());
+ if (cb.GetResult(rv) != net::OK || cache->GetEntryCount())
+ return GENERIC;
+
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+
+ disk_cache::Entry* entry;
+ for (int i = 0; i < 100; i++) {
+ std::string key = GenerateKey(true);
+ rv = cache->CreateEntry(key, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+ entry->Close();
+ FlushQueue(cache);
+ if (50 == i && action >= disk_cache::REMOVE_LOAD_1) {
+ rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+ entry->Close();
+ FlushQueue(cache);
+ }
+ }
+
+ if (action <= disk_cache::INSERT_LOAD_2) {
+ disk_cache::g_rankings_crash = action;
+
+ rv = cache->CreateEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+ }
+
+ rv = cache->OpenEntry(kCrashEntryName, &entry, cb.callback());
+ if (cb.GetResult(rv) != net::OK)
+ return GENERIC;
+
+ disk_cache::g_rankings_crash = action;
+
+ entry->Doom();
+ entry->Close();
+ FlushQueue(cache);
+
+ return NOT_REACHED;
+}
+
+// Main function on the child process.
+int SlaveCode(const base::FilePath& path, RankCrashes action) {
+ base::MessageLoopForIO message_loop;
+
+ base::FilePath full_path;
+ if (!CreateTargetFolder(path, action, &full_path)) {
+ printf("Destination folder found, please remove it.\n");
+ return CRASH_OVERWRITE;
+ }
+
+ base::Thread cache_thread("CacheThread");
+ if (!cache_thread.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)))
+ return GENERIC;
+
+ if (action <= disk_cache::INSERT_ONE_3)
+ return SimpleInsert(full_path, action, &cache_thread);
+
+ if (action <= disk_cache::INSERT_LOAD_2)
+ return LoadOperations(full_path, action, &cache_thread);
+
+ if (action <= disk_cache::REMOVE_ONE_4)
+ return SimpleRemove(full_path, action, &cache_thread);
+
+ if (action <= disk_cache::REMOVE_HEAD_4)
+ return HeadRemove(full_path, action, &cache_thread);
+
+ if (action <= disk_cache::REMOVE_TAIL_3)
+ return SimpleRemove(full_path, action, &cache_thread);
+
+ if (action <= disk_cache::REMOVE_LOAD_3)
+ return LoadOperations(full_path, action, &cache_thread);
+
+ return NOT_REACHED;
+}
+
+// -----------------------------------------------------------------------
+
+int main(int argc, const char* argv[]) {
+ // Setup an AtExitManager so Singleton objects will be destructed.
+ base::AtExitManager at_exit_manager;
+
+ if (argc < 2)
+ return MasterCode();
+
+ char* end;
+ RankCrashes action = static_cast<RankCrashes>(strtol(argv[1], &end, 0));
+ if (action <= disk_cache::NO_CRASH || action >= disk_cache::MAX_CRASH) {
+ printf("Invalid action\n");
+ return INVALID_ARGUMENT;
+ }
+
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("cache_tests");
+ path = path.AppendASCII("new_crashes");
+
+ return SlaveCode(path, action);
+}
diff --git a/chromium/net/tools/crl_set_dump/crl_set_dump.cc b/chromium/net/tools/crl_set_dump/crl_set_dump.cc
new file mode 100644
index 00000000000..191840803b6
--- /dev/null
+++ b/chromium/net/tools/crl_set_dump/crl_set_dump.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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.
+
+// This utility can dump the contents of CRL set, optionally augmented with a
+// delta CRL set.
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/cert/crl_set.h"
+
+static int Usage(const char* argv0) {
+ fprintf(stderr, "Usage: %s <crl-set file> [<delta file>]"
+ " [<resulting output file>]\n", argv0);
+ return 1;
+}
+
+int main(int argc, char** argv) {
+ base::AtExitManager at_exit_manager;
+
+ base::FilePath crl_set_filename, delta_filename, output_filename;
+
+ if (argc < 2 || argc > 4)
+ return Usage(argv[0]);
+
+ crl_set_filename = base::FilePath::FromUTF8Unsafe(argv[1]);
+ if (argc >= 3)
+ delta_filename = base::FilePath::FromUTF8Unsafe(argv[2]);
+ if (argc >= 4)
+ output_filename = base::FilePath::FromUTF8Unsafe(argv[3]);
+
+ std::string crl_set_bytes, delta_bytes;
+ if (!file_util::ReadFileToString(crl_set_filename, &crl_set_bytes))
+ return 1;
+ if (!delta_filename.empty() &&
+ !file_util::ReadFileToString(delta_filename, &delta_bytes)) {
+ return 1;
+ }
+
+ scoped_refptr<net::CRLSet> crl_set, final_crl_set;
+ if (!net::CRLSet::Parse(crl_set_bytes, &crl_set)) {
+ fprintf(stderr, "Failed to parse CRLSet\n");
+ return 1;
+ }
+
+ if (!delta_bytes.empty()) {
+ if (!crl_set->ApplyDelta(delta_bytes, &final_crl_set)) {
+ fprintf(stderr, "Failed to apply delta to CRLSet\n");
+ return 1;
+ }
+ } else {
+ final_crl_set = crl_set;
+ }
+
+ if (!output_filename.empty()) {
+ const std::string out = final_crl_set->Serialize();
+ if (file_util::WriteFile(output_filename, out.data(),
+ out.size()) == -1) {
+ fprintf(stderr, "Failed to write resulting CRL set\n");
+ return 1;
+ }
+ }
+
+ const net::CRLSet::CRLList& crls = final_crl_set->crls();
+ for (net::CRLSet::CRLList::const_iterator i = crls.begin(); i != crls.end();
+ i++) {
+ printf("%s\n", base::HexEncode(i->first.data(), i->first.size()).c_str());
+ for (std::vector<std::string>::const_iterator j = i->second.begin();
+ j != i->second.end(); j++) {
+ printf(" %s\n", base::HexEncode(j->data(), j->size()).c_str());
+ }
+ }
+
+ return 0;
+}
diff --git a/chromium/net/tools/dns_fuzz_stub/dns_fuzz_stub.cc b/chromium/net/tools/dns_fuzz_stub/dns_fuzz_stub.cc
new file mode 100644
index 00000000000..bcdb7b72dce
--- /dev/null
+++ b/chromium/net/tools/dns_fuzz_stub/dns_fuzz_stub.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 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.
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/base/dns_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+
+namespace {
+
+void CrashDoubleFree(void) {
+ // Cause ASAN to detect a double-free
+ void *p = malloc(1);
+ LOG(INFO) << "Allocated p=" << p << ". Double-freeing...";
+ free(p);
+ free(p);
+}
+
+void CrashNullPointerDereference(void) {
+ // Cause the program to segfault with a NULL pointer dereference
+ int *p = NULL;
+ *p = 0;
+}
+
+bool FitsUint8(int num) {
+ return (num >= 0) && (num <= kuint8max);
+}
+
+bool FitsUint16(int num) {
+ return (num >= 0) && (num <= kuint16max);
+}
+
+bool ReadTestCase(const char* filename,
+ uint16* id, std::string* qname, uint16* qtype,
+ std::vector<char>* resp_buf,
+ bool* crash_test) {
+ base::FilePath filepath = base::FilePath::FromUTF8Unsafe(filename);
+
+ std::string json;
+ if (!file_util::ReadFileToString(filepath, &json)) {
+ LOG(ERROR) << filename << ": couldn't read file.";
+ return false;
+ }
+
+ scoped_ptr<Value> value(base::JSONReader::Read(json));
+ if (!value.get()) {
+ LOG(ERROR) << filename << ": couldn't parse JSON.";
+ return false;
+ }
+
+ base::DictionaryValue* dict;
+ if (!value->GetAsDictionary(&dict)) {
+ LOG(ERROR) << filename << ": test case is not a dictionary.";
+ return false;
+ }
+
+ *crash_test = dict->HasKey("crash_test");
+ if (*crash_test) {
+ LOG(INFO) << filename << ": crash_test is set!";
+ return true;
+ }
+
+ int id_int;
+ if (!dict->GetInteger("id", &id_int)) {
+ LOG(ERROR) << filename << ": id is missing or not an integer.";
+ return false;
+ }
+ if (!FitsUint16(id_int)) {
+ LOG(ERROR) << filename << ": id is out of range.";
+ return false;
+ }
+ *id = static_cast<uint16>(id_int);
+
+ if (!dict->GetStringASCII("qname", qname)) {
+ LOG(ERROR) << filename << ": qname is missing or not a string.";
+ return false;
+ }
+
+ int qtype_int;
+ if (!dict->GetInteger("qtype", &qtype_int)) {
+ LOG(ERROR) << filename << ": qtype is missing or not an integer.";
+ return false;
+ }
+ if (!FitsUint16(qtype_int)) {
+ LOG(ERROR) << filename << ": qtype is out of range.";
+ return false;
+ }
+ *qtype = static_cast<uint16>(qtype_int);
+
+ base::ListValue* resp_list;
+ if (!dict->GetList("response", &resp_list)) {
+ LOG(ERROR) << filename << ": response is missing or not a list.";
+ return false;
+ }
+
+ size_t resp_size = resp_list->GetSize();
+ resp_buf->clear();
+ resp_buf->reserve(resp_size);
+ for (size_t i = 0; i < resp_size; i++) {
+ int resp_byte_int;
+ if ((!resp_list->GetInteger(i, &resp_byte_int))) {
+ LOG(ERROR) << filename << ": response[" << i << "] is not an integer.";
+ return false;
+ }
+ if (!FitsUint8(resp_byte_int)) {
+ LOG(ERROR) << filename << ": response[" << i << "] is out of range.";
+ return false;
+ }
+ resp_buf->push_back(static_cast<char>(resp_byte_int));
+ }
+ DCHECK(resp_buf->size() == resp_size);
+
+ LOG(INFO) << "Query: id=" << id_int << ", "
+ << "qname=" << *qname << ", "
+ << "qtype=" << qtype_int << ", "
+ << "resp_size=" << resp_size;
+
+ return true;
+}
+
+void RunTestCase(uint16 id, std::string& qname, uint16 qtype,
+ std::vector<char>& resp_buf) {
+ net::DnsQuery query(id, qname, qtype);
+ net::DnsResponse response;
+ std::copy(resp_buf.begin(), resp_buf.end(), response.io_buffer()->data());
+
+ if (!response.InitParse(resp_buf.size(), query)) {
+ LOG(INFO) << "InitParse failed.";
+ return;
+ }
+
+ net::AddressList address_list;
+ base::TimeDelta ttl;
+ net::DnsResponse::Result result = response.ParseToAddressList(
+ &address_list, &ttl);
+ if (result != net::DnsResponse::DNS_PARSE_OK) {
+ LOG(INFO) << "ParseToAddressList failed: " << result;
+ return;
+ }
+
+ // Print the response in one compact line.
+ std::stringstream result_line;
+ result_line << "Response: address_list={ ";
+ for (unsigned int i = 0; i < address_list.size(); i++)
+ result_line << address_list[i].ToString() << " ";
+ result_line << "}, ttl=" << ttl.InSeconds() << "s";
+
+ LOG(INFO) << result_line.str();
+}
+
+bool ReadAndRunTestCase(const char* filename) {
+ uint16 id = 0;
+ std::string qname;
+ uint16 qtype = 0;
+ std::vector<char> resp_buf;
+ bool crash_test = false;
+
+ LOG(INFO) << "Test case: " << filename;
+
+ // ReadTestCase will print a useful error message if it fails.
+ if (!ReadTestCase(filename, &id, &qname, &qtype, &resp_buf, &crash_test))
+ return false;
+
+ if (crash_test) {
+ LOG(INFO) << "Crashing.";
+ CrashDoubleFree();
+ // if we're not running under ASAN, that might not have worked
+ CrashNullPointerDereference();
+ NOTREACHED();
+ return true;
+ }
+
+ std::string qname_dns;
+ if (!net::DNSDomainFromDot(qname, &qname_dns)) {
+ LOG(ERROR) << filename << ": DNSDomainFromDot(" << qname << ") failed.";
+ return false;
+ }
+
+ RunTestCase(id, qname_dns, qtype, resp_buf);
+
+ return true;
+}
+
+}
+
+int main(int argc, char** argv) {
+ int ret = 0;
+
+ for (int i = 1; i < argc; i++)
+ if (!ReadAndRunTestCase(argv[i]))
+ ret = 2;
+
+ // Cluster-Fuzz likes "#EOF" as the last line of output to help distinguish
+ // successful runs from crashes.
+ printf("#EOF\n");
+
+ return ret;
+}
+
diff --git a/chromium/net/tools/dump_cache/cache_dumper.cc b/chromium/net/tools/dump_cache/cache_dumper.cc
new file mode 100644
index 00000000000..e6bac0aeda7
--- /dev/null
+++ b/chromium/net/tools/dump_cache/cache_dumper.cc
@@ -0,0 +1,223 @@
+// Copyright (c) 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.
+
+#include "net/tools/dump_cache/cache_dumper.h"
+
+#include "base/file_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+CacheDumper::CacheDumper(disk_cache::Backend* cache)
+ : cache_(cache) {
+}
+
+int CacheDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) {
+ return cache_->CreateEntry(key, entry, callback);
+}
+
+int CacheDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ return entry->WriteData(index, offset, buf, buf_len, callback, false);
+}
+
+void CacheDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) {
+ if (entry) {
+ static_cast<disk_cache::EntryImpl*>(entry)->SetTimes(last_used,
+ last_modified);
+ entry->Close();
+ }
+}
+
+// A version of CreateDirectory which supports lengthy filenames.
+// Returns true on success, false on failure.
+bool SafeCreateDirectory(const base::FilePath& path) {
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ // Due to large paths on windows, it can't simply do a
+ // CreateDirectory("a/b/c"). Instead, create each subdirectory manually.
+ bool rv = false;
+ std::wstring::size_type pos(0);
+ std::wstring backslash(L"\\");
+
+ // If the path starts with the long file header, skip over that
+ const std::wstring kLargeFilenamePrefix(L"\\\\?\\");
+ std::wstring header(kLargeFilenamePrefix);
+ if (path.value().find(header) == 0)
+ pos = 4;
+
+ // Create the subdirectories individually
+ while ((pos = path.value().find(backslash, pos)) != std::wstring::npos) {
+ base::FilePath::StringType subdir = path.value().substr(0, pos);
+ CreateDirectoryW(subdir.c_str(), NULL);
+ // we keep going even if directory creation failed.
+ pos++;
+ }
+ // Now create the full path
+ return CreateDirectoryW(path.value().c_str(), NULL) == TRUE;
+#else
+ return file_util::CreateDirectory(path);
+#endif
+}
+
+DiskDumper::DiskDumper(const base::FilePath& path)
+ : path_(path), entry_(NULL) {
+ file_util::CreateDirectory(path);
+}
+
+int DiskDumper::CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) {
+ // The URL may not start with a valid protocol; search for it.
+ int urlpos = key.find("http");
+ std::string url = urlpos > 0 ? key.substr(urlpos) : key;
+ std::string base_path = path_.MaybeAsASCII();
+ std::string new_path =
+ net::UrlToFilenameEncoder::Encode(url, base_path, false);
+ entry_path_ = base::FilePath::FromUTF8Unsafe(new_path);
+
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ // In order for long filenames to work, we'll need to prepend
+ // the windows magic token.
+ const std::wstring kLongFilenamePrefix(L"\\\\?\\");
+ // There is no way to prepend to a filename. We simply *have*
+ // to convert to a wstring to do this.
+ std::wstring name = kLongFilenamePrefix;
+ name.append(entry_path_.value());
+ entry_path_ = base::FilePath(name);
+#endif
+
+ entry_url_ = key;
+
+ SafeCreateDirectory(entry_path_.DirName());
+
+ base::FilePath::StringType file = entry_path_.value();
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ entry_ = CreateFileW(file.c_str(), GENERIC_WRITE|GENERIC_READ, 0, 0,
+ CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (entry_ == INVALID_HANDLE_VALUE)
+ wprintf(L"CreateFileW (%s) failed: %d\n", file.c_str(), GetLastError());
+ return (entry_ != INVALID_HANDLE_VALUE) ? net::OK : net::ERR_FAILED;
+#else
+ entry_ = file_util::OpenFile(entry_path_, "w+");
+ return (entry_ != NULL) ? net::OK : net::ERR_FAILED;
+#endif
+}
+
+// Utility Function to create a normalized header string from a
+// HttpResponseInfo. The output will be formatted exactly
+// like so:
+// HTTP/<version> <status_code> <status_text>\n
+// [<header-name>: <header-values>\n]*
+// meaning, each line is \n-terminated, and there is no extra whitespace
+// beyond the single space separators shown (of course, values can contain
+// whitespace within them). If a given header-name appears more than once
+// in the set of headers, they are combined into a single line like so:
+// <header-name>: <header-value1>, <header-value2>, ...<header-valueN>\n
+//
+// DANGER: For some headers (e.g., "Set-Cookie"), the normalized form can be
+// a lossy format. This is due to the fact that some servers generate
+// Set-Cookie headers that contain unquoted commas (usually as part of the
+// value of an "expires" attribute). So, use this function with caution. Do
+// not expect to be able to re-parse Set-Cookie headers from this output.
+//
+// NOTE: Do not make any assumptions about the encoding of this output
+// string. It may be non-ASCII, and the encoding used by the server is not
+// necessarily known to us. Do not assume that this output is UTF-8!
+void GetNormalizedHeaders(const net::HttpResponseInfo& info,
+ std::string* output) {
+ // Start with the status line
+ output->assign(info.headers->GetStatusLine());
+ output->append("\r\n");
+
+ // Enumerate the headers
+ void* iter = 0;
+ std::string name, value;
+ while (info.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ output->append(name);
+ output->append(": ");
+ output->append(value);
+ output->append("\r\n");
+ }
+
+ // Mark the end of headers
+ output->append("\r\n");
+}
+
+int DiskDumper::WriteEntry(disk_cache::Entry* entry, int index, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ if (!entry_)
+ return 0;
+
+ std::string headers;
+ const char *data;
+ size_t len;
+ if (index == 0) { // Stream 0 is the headers.
+ net::HttpResponseInfo response_info;
+ bool truncated;
+ if (!net::HttpCache::ParseResponseInfo(buf->data(), buf_len,
+ &response_info, &truncated))
+ return 0;
+
+ // Skip this entry if it was truncated (results in an empty file).
+ if (truncated)
+ return buf_len;
+
+ // Remove the size headers.
+ response_info.headers->RemoveHeader("transfer-encoding");
+ response_info.headers->RemoveHeader("content-length");
+ response_info.headers->RemoveHeader("x-original-url");
+
+ // Convert the headers into a string ending with LF.
+ GetNormalizedHeaders(response_info, &headers);
+
+ // Append a header for the original URL.
+ std::string url = entry_url_;
+ // strip off the "XXGET" which may be in the key.
+ std::string::size_type pos(0);
+ if ((pos = url.find("http")) != 0) {
+ if (pos != std::string::npos)
+ url = url.substr(pos);
+ }
+ std::string x_original_url = "X-Original-Url: " + url + "\r\n";
+ // we know that the last two bytes are CRLF.
+ headers.replace(headers.length() - 2, 0, x_original_url);
+
+ data = headers.c_str();
+ len = headers.size();
+ } else if (index == 1) {
+ data = buf->data();
+ len = buf_len;
+ } else {
+ return 0;
+ }
+
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ DWORD bytes;
+ if (!WriteFile(entry_, data, len, &bytes, 0))
+ return 0;
+
+ return bytes;
+#else
+ return fwrite(data, 1, len, entry_);
+#endif
+}
+
+void DiskDumper::CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) {
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ CloseHandle(entry_);
+#else
+ file_util::CloseFile(entry_);
+#endif
+}
diff --git a/chromium/net/tools/dump_cache/cache_dumper.h b/chromium/net/tools/dump_cache/cache_dumper.h
new file mode 100644
index 00000000000..4bdf4a1bd2f
--- /dev/null
+++ b/chromium/net/tools/dump_cache/cache_dumper.h
@@ -0,0 +1,89 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
+#define NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "net/disk_cache/backend_impl.h"
+
+#ifdef WIN32
+// Dumping the cache often creates very large filenames, which are tricky
+// on windows. Most API calls don't support large filenames, including
+// most of the base library functions. Unfortunately, adding "\\?\" into
+// the filename support is tricky. Instead, if WIN32_LARGE_FILENAME_SUPPORT
+// is set, we use direct WIN32 APIs for manipulating the files.
+#define WIN32_LARGE_FILENAME_SUPPORT
+#endif
+
+// An abstract class for writing cache dump data.
+class CacheDumpWriter {
+ public:
+ virtual ~CacheDumpWriter() {}
+
+ // Creates an entry to be written.
+ // On success, populates the |entry|.
+ // Returns a net error code.
+ virtual int CreateEntry(const std::string& key,
+ disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) = 0;
+
+ // Write to the current entry.
+ // Returns a net error code.
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) = 0;
+
+ // Close the current entry.
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) = 0;
+};
+
+// Writes data to a cache.
+class CacheDumper : public CacheDumpWriter {
+ public:
+ explicit CacheDumper(disk_cache::Backend* cache);
+
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) OVERRIDE;
+
+ private:
+ disk_cache::Backend* cache_;
+};
+
+// Writes data to a disk.
+class DiskDumper : public CacheDumpWriter {
+ public:
+ explicit DiskDumper(const base::FilePath& path);
+
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int WriteEntry(disk_cache::Entry* entry, int stream, int offset,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual void CloseEntry(disk_cache::Entry* entry, base::Time last_used,
+ base::Time last_modified) OVERRIDE;
+
+ private:
+ base::FilePath path_;
+ // This is a bit of a hack. As we get a CreateEntry, we coin the current
+ // entry_path_ where we write that entry to disk. Subsequent calls to
+ // WriteEntry() utilize this path for writing to disk.
+ base::FilePath entry_path_;
+ std::string entry_url_;
+#ifdef WIN32_LARGE_FILENAME_SUPPORT
+ HANDLE entry_;
+#else
+ FILE* entry_;
+#endif
+};
+
+#endif // NET_TOOLS_DUMP_CACHE_CACHE_DUMPER_H_
diff --git a/chromium/net/tools/dump_cache/dump_cache.cc b/chromium/net/tools/dump_cache/dump_cache.cc
new file mode 100644
index 00000000000..dd423b7a950
--- /dev/null
+++ b/chromium/net/tools/dump_cache/dump_cache.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 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.
+
+// This command-line program dumps the contents of a set of cache files, either
+// to stdout or to another set of cache files.
+
+#include <stdio.h>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/tools/dump_cache/dump_files.h"
+#include "net/tools/dump_cache/simple_cache_dumper.h"
+
+#if defined(OS_WIN)
+#include "base/process/launch.h"
+#include "base/win/scoped_handle.h"
+#include "net/tools/dump_cache/upgrade_win.h"
+#endif
+
+enum Errors {
+ GENERIC = -1,
+ ALL_GOOD = 0,
+ INVALID_ARGUMENT = 1,
+ FILE_ACCESS_ERROR,
+ UNKNOWN_VERSION,
+ TOOL_NOT_FOUND,
+};
+
+const char kUpgradeHelp[] =
+ "\nIn order to use the upgrade function, a version of this tool that\n"
+ "understands the file format of the files to upgrade is needed. For\n"
+ "instance, to upgrade files saved with file format 3.4 to version 5.2,\n"
+ "a version of this program that was compiled with version 3.4 has to be\n"
+ "located beside this executable, and named dump_cache_3.exe, and this\n"
+ "executable should be compiled with version 5.2 being the current one.";
+
+// Folders to read and write cache files.
+const char kInputPath[] = "input";
+const char kOutputPath[] = "output";
+
+// Dumps the file headers to stdout.
+const char kDumpHeaders[] = "dump-headers";
+
+// Dumps all entries to stdout.
+const char kDumpContents[] = "dump-contents";
+
+// Convert the cache to files.
+const char kDumpToFiles[] = "dump-to-files";
+
+// Upgrade an old version to the current one.
+const char kUpgrade[] = "upgrade";
+
+// Internal use:
+const char kSlave[] = "slave";
+const char kPipe[] = "pipe";
+
+int Help() {
+ printf("warning: input files are modified by this tool\n");
+ printf("dump_cache --input=path1 [--output=path2]\n");
+ printf("--dump-headers: display file headers\n");
+ printf("--dump-contents: display all entries\n");
+ printf("--upgrade: copy contents to the output path\n");
+ printf("--dump-to-files: write the contents of the cache to files\n");
+ return INVALID_ARGUMENT;
+}
+
+#if defined(OS_WIN)
+
+// Starts a new process, to generate the files.
+int LaunchSlave(CommandLine command_line,
+ const base::string16& pipe_number,
+ int version) {
+ bool do_upgrade = command_line.HasSwitch(kUpgrade);
+ bool do_convert_to_text = command_line.HasSwitch(kDumpToFiles);
+
+ if (do_upgrade) {
+ base::FilePath program(
+ base::StringPrintf(L"%ls%d", L"dump_cache", version));
+ command_line.SetProgram(program);
+ }
+
+ if (do_upgrade || do_convert_to_text)
+ command_line.AppendSwitch(kSlave);
+
+ command_line.AppendSwitchNative(kPipe, pipe_number);
+ if (!base::LaunchProcess(command_line, base::LaunchOptions(), NULL)) {
+ printf("Unable to launch the needed version of this tool: %ls\n",
+ command_line.GetProgram().value().c_str());
+ printf(kUpgradeHelp);
+ return TOOL_NOT_FOUND;
+ }
+ return ALL_GOOD;
+}
+
+#endif
+
+// -----------------------------------------------------------------------
+
+int main(int argc, const char* argv[]) {
+ // Setup an AtExitManager so Singleton objects will be destroyed.
+ base::AtExitManager at_exit_manager;
+
+ CommandLine::Init(argc, argv);
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ base::FilePath input_path = command_line.GetSwitchValuePath(kInputPath);
+ if (input_path.empty())
+ return Help();
+
+ bool dump_to_files = command_line.HasSwitch(kDumpToFiles);
+ bool upgrade = command_line.HasSwitch(kUpgrade);
+
+ base::FilePath output_path = command_line.GetSwitchValuePath(kOutputPath);
+ if ((dump_to_files || upgrade) && output_path.empty())
+ return Help();
+
+ int version = GetMajorVersion(input_path);
+ if (!version)
+ return FILE_ACCESS_ERROR;
+
+ bool slave_required = upgrade;
+ if (version != disk_cache::kCurrentVersion >> 16) {
+ if (command_line.HasSwitch(kSlave)) {
+ printf("Unknown version\n");
+ return UNKNOWN_VERSION;
+ }
+ slave_required = true;
+ }
+
+#if defined(OS_WIN)
+ base::string16 pipe_number = command_line.GetSwitchValueNative(kPipe);
+ if (command_line.HasSwitch(kSlave) && slave_required)
+ return RunSlave(input_path, pipe_number);
+
+ base::win::ScopedHandle server;
+ if (slave_required) {
+ server.Set(CreateServer(&pipe_number));
+ if (!server.IsValid()) {
+ printf("Unable to create the server pipe\n");
+ return GENERIC;
+ }
+
+ int ret = LaunchSlave(command_line, pipe_number, version);
+ if (ret)
+ return ret;
+ }
+
+ if (upgrade)
+ return UpgradeCache(output_path, server);
+
+ if (slave_required) {
+ // Wait until the slave starts dumping data before we quit. Lazy "fix" for a
+ // console quirk.
+ Sleep(500);
+ return ALL_GOOD;
+ }
+#else // defined(OS_WIN)
+ if (slave_required) {
+ printf("Unsupported operation\n");
+ return INVALID_ARGUMENT;
+ }
+#endif
+
+ if (dump_to_files) {
+ net::SimpleCacheDumper dumper(input_path, output_path);
+ dumper.Run();
+ return ALL_GOOD;
+ }
+
+ if (command_line.HasSwitch(kDumpContents))
+ return DumpContents(input_path);
+
+ if (command_line.HasSwitch(kDumpHeaders))
+ return DumpHeaders(input_path);
+
+ return Help();
+}
diff --git a/chromium/net/tools/dump_cache/dump_files.cc b/chromium/net/tools/dump_cache/dump_files.cc
new file mode 100644
index 00000000000..7f4fb58343d
--- /dev/null
+++ b/chromium/net/tools/dump_cache/dump_files.cc
@@ -0,0 +1,374 @@
+// Copyright (c) 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.
+
+// Performs basic inspection of the disk cache files with minimal disruption
+// to the actual files (they still may change if an error is detected on the
+// files).
+
+#include "net/tools/dump_cache/dump_files.h"
+
+#include <stdio.h>
+
+#include <set>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/file_stream.h"
+#include "net/disk_cache/block_files.h"
+#include "net/disk_cache/disk_format.h"
+#include "net/disk_cache/mapped_file.h"
+#include "net/disk_cache/stats.h"
+#include "net/disk_cache/storage_block-inl.h"
+#include "net/disk_cache/storage_block.h"
+
+namespace {
+
+const base::FilePath::CharType kIndexName[] = FILE_PATH_LITERAL("index");
+
+// Reads the |header_size| bytes from the beginning of file |name|.
+bool ReadHeader(const base::FilePath& name, char* header, int header_size) {
+ net::FileStream file(NULL);
+ file.OpenSync(name, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ);
+ if (!file.IsOpen()) {
+ printf("Unable to open file %s\n", name.MaybeAsASCII().c_str());
+ return false;
+ }
+
+ int read = file.ReadSync(header, header_size);
+ if (read != header_size) {
+ printf("Unable to read file %s\n", name.MaybeAsASCII().c_str());
+ return false;
+ }
+ return true;
+}
+
+int GetMajorVersionFromFile(const base::FilePath& name) {
+ disk_cache::IndexHeader header;
+ if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
+ return 0;
+
+ return header.version >> 16;
+}
+
+// Dumps the contents of the Stats record.
+void DumpStats(const base::FilePath& path, disk_cache::CacheAddr addr) {
+ // We need a message loop, although we really don't run any task.
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+
+ disk_cache::BlockFiles block_files(path);
+ if (!block_files.Init(false)) {
+ printf("Unable to init block files\n");
+ return;
+ }
+
+ disk_cache::Addr address(addr);
+ disk_cache::MappedFile* file = block_files.GetFile(address);
+ if (!file)
+ return;
+
+ size_t length = (2 + disk_cache::Stats::kDataSizesLength) * sizeof(int32) +
+ disk_cache::Stats::MAX_COUNTER * sizeof(int64);
+
+ size_t offset = address.start_block() * address.BlockSize() +
+ disk_cache::kBlockHeaderSize;
+
+ scoped_ptr<int32[]> buffer(new int32[length]);
+ if (!file->Read(buffer.get(), length, offset))
+ return;
+
+ printf("Stats:\nSignatrure: 0x%x\n", buffer[0]);
+ printf("Total size: %d\n", buffer[1]);
+ for (int i = 0; i < disk_cache::Stats::kDataSizesLength; i++)
+ printf("Size(%d): %d\n", i, buffer[i + 2]);
+
+ int64* counters = reinterpret_cast<int64*>(
+ buffer.get() + 2 + disk_cache::Stats::kDataSizesLength);
+ for (int i = 0; i < disk_cache::Stats::MAX_COUNTER; i++)
+ printf("Count(%d): %" PRId64 "\n", i, *counters++);
+ printf("-------------------------\n\n");
+}
+
+// Dumps the contents of the Index-file header.
+void DumpIndexHeader(const base::FilePath& name,
+ disk_cache::CacheAddr* stats_addr) {
+ disk_cache::IndexHeader header;
+ if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
+ return;
+
+ printf("Index file:\n");
+ printf("magic: %x\n", header.magic);
+ printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
+ printf("entries: %d\n", header.num_entries);
+ printf("total bytes: %d\n", header.num_bytes);
+ printf("last file number: %d\n", header.last_file);
+ printf("current id: %d\n", header.this_id);
+ printf("table length: %d\n", header.table_len);
+ printf("last crash: %d\n", header.crash);
+ printf("experiment: %d\n", header.experiment);
+ printf("stats: %x\n", header.stats);
+ for (int i = 0; i < 5; i++) {
+ printf("head %d: 0x%x\n", i, header.lru.heads[i]);
+ printf("tail %d: 0x%x\n", i, header.lru.tails[i]);
+ printf("size %d: 0x%x\n", i, header.lru.sizes[i]);
+ }
+ printf("transaction: 0x%x\n", header.lru.transaction);
+ printf("operation: %d\n", header.lru.operation);
+ printf("operation list: %d\n", header.lru.operation_list);
+ printf("-------------------------\n\n");
+
+ *stats_addr = header.stats;
+}
+
+// Dumps the contents of a block-file header.
+void DumpBlockHeader(const base::FilePath& name) {
+ disk_cache::BlockFileHeader header;
+ if (!ReadHeader(name, reinterpret_cast<char*>(&header), sizeof(header)))
+ return;
+
+ printf("Block file: %s\n", name.BaseName().MaybeAsASCII().c_str());
+ printf("magic: %x\n", header.magic);
+ printf("version: %d.%d\n", header.version >> 16, header.version & 0xffff);
+ printf("file id: %d\n", header.this_file);
+ printf("next file id: %d\n", header.next_file);
+ printf("entry size: %d\n", header.entry_size);
+ printf("current entries: %d\n", header.num_entries);
+ printf("max entries: %d\n", header.max_entries);
+ printf("updating: %d\n", header.updating);
+ printf("empty sz 1: %d\n", header.empty[0]);
+ printf("empty sz 2: %d\n", header.empty[1]);
+ printf("empty sz 3: %d\n", header.empty[2]);
+ printf("empty sz 4: %d\n", header.empty[3]);
+ printf("user 0: 0x%x\n", header.user[0]);
+ printf("user 1: 0x%x\n", header.user[1]);
+ printf("user 2: 0x%x\n", header.user[2]);
+ printf("user 3: 0x%x\n", header.user[3]);
+ printf("-------------------------\n\n");
+}
+
+// Simple class that interacts with the set of cache files.
+class CacheDumper {
+ public:
+ explicit CacheDumper(const base::FilePath& path)
+ : path_(path),
+ block_files_(path),
+ index_(NULL),
+ current_hash_(0),
+ next_addr_(0) {
+ }
+
+ bool Init();
+
+ // Reads an entry from disk. Return false when all entries have been already
+ // returned.
+ bool GetEntry(disk_cache::EntryStore* entry);
+
+ // Loads a specific block from the block files.
+ bool LoadEntry(disk_cache::CacheAddr addr, disk_cache::EntryStore* entry);
+ bool LoadRankings(disk_cache::CacheAddr addr,
+ disk_cache::RankingsNode* rankings);
+
+ private:
+ base::FilePath path_;
+ disk_cache::BlockFiles block_files_;
+ scoped_refptr<disk_cache::MappedFile> index_file_;
+ disk_cache::Index* index_;
+ int current_hash_;
+ disk_cache::CacheAddr next_addr_;
+ std::set<disk_cache::CacheAddr> dumped_entries_;
+ DISALLOW_COPY_AND_ASSIGN(CacheDumper);
+};
+
+bool CacheDumper::Init() {
+ if (!block_files_.Init(false)) {
+ printf("Unable to init block files\n");
+ return false;
+ }
+
+ base::FilePath index_name(path_.Append(kIndexName));
+ index_file_ = new disk_cache::MappedFile;
+ index_ = reinterpret_cast<disk_cache::Index*>(
+ index_file_->Init(index_name, 0));
+ if (!index_) {
+ printf("Unable to map index\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool CacheDumper::GetEntry(disk_cache::EntryStore* entry) {
+ if (dumped_entries_.find(next_addr_) != dumped_entries_.end()) {
+ printf("Loop detected\n");
+ next_addr_ = 0;
+ current_hash_++;
+ }
+
+ if (next_addr_) {
+ if (LoadEntry(next_addr_, entry))
+ return true;
+
+ printf("Unable to load entry at address 0x%x\n", next_addr_);
+ next_addr_ = 0;
+ current_hash_++;
+ }
+
+ for (int i = current_hash_; i < index_->header.table_len; i++) {
+ // Yes, we'll crash if the table is shorter than expected, but only after
+ // dumping every entry that we can find.
+ if (index_->table[i]) {
+ current_hash_ = i;
+ if (LoadEntry(index_->table[i], entry))
+ return true;
+
+ printf("Unable to load entry at address 0x%x\n", index_->table[i]);
+ }
+ }
+ return false;
+}
+
+bool CacheDumper::LoadEntry(disk_cache::CacheAddr addr,
+ disk_cache::EntryStore* entry) {
+ disk_cache::Addr address(addr);
+ disk_cache::MappedFile* file = block_files_.GetFile(address);
+ if (!file)
+ return false;
+
+ disk_cache::StorageBlock<disk_cache::EntryStore> entry_block(file, address);
+ if (!entry_block.Load())
+ return false;
+
+ memcpy(entry, entry_block.Data(), sizeof(*entry));
+ printf("Entry at 0x%x\n", addr);
+
+ // Prepare for the next entry to load.
+ next_addr_ = entry->next;
+ if (next_addr_) {
+ dumped_entries_.insert(addr);
+ } else {
+ current_hash_++;
+ dumped_entries_.clear();
+ }
+ return true;
+}
+
+bool CacheDumper::LoadRankings(disk_cache::CacheAddr addr,
+ disk_cache::RankingsNode* rankings) {
+ disk_cache::Addr address(addr);
+ disk_cache::MappedFile* file = block_files_.GetFile(address);
+ if (!file)
+ return false;
+
+ disk_cache::StorageBlock<disk_cache::RankingsNode> rank_block(file, address);
+ if (!rank_block.Load())
+ return false;
+
+ memcpy(rankings, rank_block.Data(), sizeof(*rankings));
+ printf("Rankings at 0x%x\n", addr);
+ return true;
+}
+
+void DumpEntry(const disk_cache::EntryStore& entry) {
+ std::string key;
+ if (!entry.long_key) {
+ key = entry.key;
+ if (key.size() > 50)
+ key.resize(50);
+ }
+
+ printf("hash: 0x%x\n", entry.hash);
+ printf("next entry: 0x%x\n", entry.next);
+ printf("rankings: 0x%x\n", entry.rankings_node);
+ printf("key length: %d\n", entry.key_len);
+ printf("key: \"%s\"\n", key.c_str());
+ printf("key addr: 0x%x\n", entry.long_key);
+ printf("reuse count: %d\n", entry.reuse_count);
+ printf("refetch count: %d\n", entry.refetch_count);
+ printf("state: %d\n", entry.state);
+ for (int i = 0; i < 4; i++) {
+ printf("data size %d: %d\n", i, entry.data_size[i]);
+ printf("data addr %d: 0x%x\n", i, entry.data_addr[i]);
+ }
+ printf("----------\n\n");
+}
+
+void DumpRankings(const disk_cache::RankingsNode& rankings) {
+ printf("next: 0x%x\n", rankings.next);
+ printf("prev: 0x%x\n", rankings.prev);
+ printf("entry: 0x%x\n", rankings.contents);
+ printf("dirty: %d\n", rankings.dirty);
+ printf("hash: 0x%x\n", rankings.self_hash);
+ printf("----------\n\n");
+}
+
+} // namespace.
+
+// -----------------------------------------------------------------------
+
+int GetMajorVersion(const base::FilePath& input_path) {
+ base::FilePath index_name(input_path.Append(kIndexName));
+
+ int version = GetMajorVersionFromFile(index_name);
+ if (!version)
+ return 0;
+
+ base::FilePath data_name(input_path.Append(FILE_PATH_LITERAL("data_0")));
+ if (version != GetMajorVersionFromFile(data_name))
+ return 0;
+
+ data_name = input_path.Append(FILE_PATH_LITERAL("data_1"));
+ if (version != GetMajorVersionFromFile(data_name))
+ return 0;
+
+ data_name = input_path.Append(FILE_PATH_LITERAL("data_2"));
+ if (version != GetMajorVersionFromFile(data_name))
+ return 0;
+
+ data_name = input_path.Append(FILE_PATH_LITERAL("data_3"));
+ if (version != GetMajorVersionFromFile(data_name))
+ return 0;
+
+ return version;
+}
+
+// Dumps the headers of all files.
+int DumpHeaders(const base::FilePath& input_path) {
+ base::FilePath index_name(input_path.Append(kIndexName));
+ disk_cache::CacheAddr stats_addr = 0;
+ DumpIndexHeader(index_name, &stats_addr);
+
+ base::FileEnumerator iter(input_path, false,
+ base::FileEnumerator::FILES,
+ FILE_PATH_LITERAL("data_*"));
+ for (base::FilePath file = iter.Next(); !file.empty(); file = iter.Next())
+ DumpBlockHeader(file);
+
+ DumpStats(input_path, stats_addr);
+ return 0;
+}
+
+// Dumps all entries from the cache.
+int DumpContents(const base::FilePath& input_path) {
+ DumpHeaders(input_path);
+
+ // We need a message loop, although we really don't run any task.
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+ CacheDumper dumper(input_path);
+ if (!dumper.Init())
+ return -1;
+
+ disk_cache::EntryStore entry;
+ while (dumper.GetEntry(&entry)) {
+ DumpEntry(entry);
+ disk_cache::RankingsNode rankings;
+ if (dumper.LoadRankings(entry.rankings_node, &rankings))
+ DumpRankings(rankings);
+ }
+
+ printf("Done.\n");
+
+ return 0;
+}
diff --git a/chromium/net/tools/dump_cache/dump_files.h b/chromium/net/tools/dump_cache/dump_files.h
new file mode 100644
index 00000000000..49df4f2f96a
--- /dev/null
+++ b/chromium/net/tools/dump_cache/dump_files.h
@@ -0,0 +1,23 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_DUMP_FILES_H_
+#define NET_TOOLS_DUMP_CACHE_DUMP_FILES_H_
+
+// Performs basic inspection of the disk cache files with minimal disruption
+// to the actual files (they still may change if an error is detected on the
+// files).
+
+#include "base/files/file_path.h"
+
+// Returns the major version of the specified cache.
+int GetMajorVersion(const base::FilePath& input_path);
+
+// Dumps all entries from the cache.
+int DumpContents(const base::FilePath& input_path);
+
+// Dumps the headers of all files.
+int DumpHeaders(const base::FilePath& input_path);
+
+#endif // NET_TOOLS_DUMP_CACHE_DUMP_FILES_H_
diff --git a/chromium/net/tools/dump_cache/simple_cache_dumper.cc b/chromium/net/tools/dump_cache/simple_cache_dumper.cc
new file mode 100644
index 00000000000..56162ca3e10
--- /dev/null
+++ b/chromium/net/tools/dump_cache/simple_cache_dumper.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 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.
+
+#include "net/tools/dump_cache/simple_cache_dumper.h"
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/threading/thread.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/tools/dump_cache/cache_dumper.h"
+
+namespace net {
+
+SimpleCacheDumper::SimpleCacheDumper(base::FilePath input_path,
+ base::FilePath output_path)
+ : state_(STATE_NONE),
+ input_path_(input_path),
+ output_path_(output_path),
+ writer_(new DiskDumper(output_path)),
+ cache_thread_(new base::Thread("CacheThead")),
+ iter_(NULL),
+ src_entry_(NULL),
+ dst_entry_(NULL),
+ io_callback_(base::Bind(&SimpleCacheDumper::OnIOComplete,
+ base::Unretained(this))),
+ rv_(0) {
+}
+
+SimpleCacheDumper::~SimpleCacheDumper() {
+}
+
+int SimpleCacheDumper::Run() {
+ base::MessageLoopForIO main_message_loop;
+
+ LOG(INFO) << "Reading cache from: " << input_path_.value();
+ LOG(INFO) << "Writing cache to: " << output_path_.value();
+
+ if (!cache_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) {
+ LOG(ERROR) << "Unable to start thread";
+ return ERR_UNEXPECTED;
+ }
+ state_ = STATE_CREATE_CACHE;
+ int rv = DoLoop(OK);
+ if (rv == ERR_IO_PENDING) {
+ main_message_loop.Run();
+ return rv_;
+ }
+ return rv;
+}
+
+int SimpleCacheDumper::DoLoop(int rv) {
+ do {
+ State state = state_;
+ state_ = STATE_NONE;
+ switch (state) {
+ case STATE_CREATE_CACHE:
+ CHECK_EQ(OK, rv);
+ rv = DoCreateCache();
+ break;
+ case STATE_CREATE_CACHE_COMPLETE:
+ rv = DoCreateCacheComplete(rv);
+ break;
+ case STATE_OPEN_ENTRY:
+ CHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_CREATE_ENTRY:
+ CHECK_EQ(OK, rv);
+ rv = DoCreateEntry();
+ break;
+ case STATE_CREATE_ENTRY_COMPLETE:
+ rv = DoCreateEntryComplete(rv);
+ break;
+ case STATE_READ_HEADERS:
+ CHECK_EQ(OK, rv);
+ rv = DoReadHeaders();
+ break;
+ case STATE_READ_HEADERS_COMPLETE:
+ rv = DoReadHeadersComplete(rv);
+ break;
+ case STATE_WRITE_HEADERS:
+ CHECK_EQ(OK, rv);
+ rv = DoWriteHeaders();
+ break;
+ case STATE_WRITE_HEADERS_COMPLETE:
+ rv = DoWriteHeadersComplete(rv);
+ break;
+ case STATE_READ_BODY:
+ CHECK_EQ(OK, rv);
+ rv = DoReadBody();
+ break;
+ case STATE_READ_BODY_COMPLETE:
+ rv = DoReadBodyComplete(rv);
+ break;
+ case STATE_WRITE_BODY:
+ CHECK_EQ(OK, rv);
+ rv = DoWriteBody();
+ break;
+ case STATE_WRITE_BODY_COMPLETE:
+ rv = DoWriteBodyComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "state_: " << state_;
+ break;
+ }
+ } while (state_ != STATE_NONE && rv != ERR_IO_PENDING);
+ return rv;
+}
+
+int SimpleCacheDumper::DoCreateCache() {
+ DCHECK(!cache_);
+ state_ = STATE_CREATE_CACHE_COMPLETE;
+ return disk_cache::CreateCacheBackend(
+ DISK_CACHE,
+ CACHE_BACKEND_DEFAULT,
+ input_path_,
+ 0,
+ false,
+ cache_thread_->message_loop_proxy().get(),
+ NULL,
+ &cache_,
+ io_callback_);
+}
+
+int SimpleCacheDumper::DoCreateCacheComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ reinterpret_cast<disk_cache::BackendImpl*>(cache_.get())->SetUpgradeMode();
+ reinterpret_cast<disk_cache::BackendImpl*>(cache_.get())->SetFlags(
+ disk_cache::kNoRandom);
+
+ state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int SimpleCacheDumper::DoOpenEntry() {
+ DCHECK(!dst_entry_);
+ DCHECK(!src_entry_);
+ state_ = STATE_OPEN_ENTRY_COMPLETE;
+ return cache_->OpenNextEntry(&iter_, &src_entry_, io_callback_);
+}
+
+int SimpleCacheDumper::DoOpenEntryComplete(int rv) {
+ // ERR_FAILED indicates iteration finished.
+ if (rv == ERR_FAILED) {
+ cache_->EndEnumeration(&iter_);
+ return OK;
+ }
+
+ if (rv < 0)
+ return rv;
+
+ state_ = STATE_CREATE_ENTRY;
+ return OK;
+}
+
+int SimpleCacheDumper::DoCreateEntry() {
+ DCHECK(!dst_entry_);
+ state_ = STATE_CREATE_ENTRY_COMPLETE;
+
+ return writer_->CreateEntry(src_entry_->GetKey(), &dst_entry_,
+ io_callback_);
+}
+
+int SimpleCacheDumper::DoCreateEntryComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ state_ = STATE_READ_HEADERS;
+ return OK;
+}
+
+int SimpleCacheDumper::DoReadHeaders() {
+ state_ = STATE_READ_HEADERS_COMPLETE;
+ int32 size = src_entry_->GetDataSize(0);
+ buf_ = new IOBufferWithSize(size);
+ return src_entry_->ReadData(0, 0, buf_.get(), size, io_callback_);
+}
+
+int SimpleCacheDumper::DoReadHeadersComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ state_ = STATE_WRITE_HEADERS;
+ return OK;
+}
+
+int SimpleCacheDumper::DoWriteHeaders() {
+ int rv = writer_->WriteEntry(
+ dst_entry_, 0, 0, buf_.get(), buf_->size(), io_callback_);
+ if (rv == 0)
+ return ERR_FAILED;
+
+ state_ = STATE_WRITE_HEADERS_COMPLETE;
+ return OK;
+}
+
+int SimpleCacheDumper::DoWriteHeadersComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ state_ = STATE_READ_BODY;
+ return OK;
+}
+
+int SimpleCacheDumper::DoReadBody() {
+ state_ = STATE_READ_BODY_COMPLETE;
+ int32 size = src_entry_->GetDataSize(1);
+ // If the body is empty, we can neither read nor write it, so
+ // just move to the next.
+ if (size <= 0) {
+ state_ = STATE_WRITE_BODY_COMPLETE;
+ return OK;
+ }
+ buf_ = new IOBufferWithSize(size);
+ return src_entry_->ReadData(1, 0, buf_.get(), size, io_callback_);
+}
+
+int SimpleCacheDumper::DoReadBodyComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ state_ = STATE_WRITE_BODY;
+ return OK;
+}
+
+int SimpleCacheDumper::DoWriteBody() {
+ int rv = writer_->WriteEntry(
+ dst_entry_, 1, 0, buf_.get(), buf_->size(), io_callback_);
+ if (rv == 0)
+ return ERR_FAILED;
+
+ state_ = STATE_WRITE_BODY_COMPLETE;
+ return OK;
+}
+
+int SimpleCacheDumper::DoWriteBodyComplete(int rv) {
+ if (rv < 0)
+ return rv;
+
+ src_entry_->Close();
+ writer_->CloseEntry(dst_entry_, base::Time::Now(), base::Time::Now());
+ src_entry_ = NULL;
+ dst_entry_ = NULL;
+
+ state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+void SimpleCacheDumper::OnIOComplete(int rv) {
+ rv = DoLoop(rv);
+
+ if (rv != ERR_IO_PENDING) {
+ rv_ = rv;
+ cache_.reset();
+ base::MessageLoop::current()->Quit();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/tools/dump_cache/simple_cache_dumper.h b/chromium/net/tools/dump_cache/simple_cache_dumper.h
new file mode 100644
index 00000000000..c95e6a96168
--- /dev/null
+++ b/chromium/net/tools/dump_cache/simple_cache_dumper.h
@@ -0,0 +1,94 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_DUMP_CACHE_SIMPLE_CACHE_DUMPER_H_
+#define NET_TOOLS_DUMP_CACHE_SIMPLE_CACHE_DUMPER_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread.h"
+#include "net/base/completion_callback.h"
+
+class DiskDumper;
+
+namespace disk_cache {
+class Backend;
+class Entry;
+} // namespace disk_cache
+
+namespace net {
+
+class IOBufferWithSize;
+
+// A class for dumping the contents of a disk cache to a series of text
+// files. The files will contain the response headers, followed by the
+// response body, as if the HTTP response were written directly to disk.
+class SimpleCacheDumper {
+ public:
+ SimpleCacheDumper(base::FilePath input_path, base::FilePath output_path);
+ ~SimpleCacheDumper();
+
+ // Dumps the cache to disk. Returns OK if the operation was successful,
+ // and a net error code otherwise.
+ int Run();
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_CREATE_CACHE,
+ STATE_CREATE_CACHE_COMPLETE,
+ STATE_OPEN_ENTRY,
+ STATE_OPEN_ENTRY_COMPLETE,
+ STATE_CREATE_ENTRY,
+ STATE_CREATE_ENTRY_COMPLETE,
+ STATE_READ_HEADERS,
+ STATE_READ_HEADERS_COMPLETE,
+ STATE_WRITE_HEADERS,
+ STATE_WRITE_HEADERS_COMPLETE,
+ STATE_READ_BODY,
+ STATE_READ_BODY_COMPLETE,
+ STATE_WRITE_BODY,
+ STATE_WRITE_BODY_COMPLETE,
+ STATE_DONE,
+ };
+
+ int DoLoop(int rv);
+
+ int DoCreateCache();
+ int DoCreateCacheComplete(int rv);
+ int DoOpenEntry();
+ int DoOpenEntryComplete(int rv);
+ int DoCreateEntry();
+ int DoCreateEntryComplete(int rv);
+ int DoReadHeaders();
+ int DoReadHeadersComplete(int rv);
+ int DoWriteHeaders();
+ int DoWriteHeadersComplete(int rv);
+ int DoReadBody();
+ int DoReadBodyComplete(int rv);
+ int DoWriteBody();
+ int DoWriteBodyComplete(int rv);
+ int DoDone();
+
+ void OnIOComplete(int rv);
+
+ State state_;
+ base::FilePath input_path_;
+ base::FilePath output_path_;
+ scoped_ptr<disk_cache::Backend> cache_;
+ scoped_ptr<DiskDumper> writer_;
+ base::Thread* cache_thread_;
+ void* iter_;
+ disk_cache::Entry* src_entry_;
+ disk_cache::Entry* dst_entry_;
+ CompletionCallback io_callback_;
+ scoped_refptr<IOBufferWithSize> buf_;
+ int rv_;
+
+ DISALLOW_COPY_AND_ASSIGN(SimpleCacheDumper);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_DUMP_CACHE_SIMPLE_CACHE_DUMPER_H_
diff --git a/chromium/net/tools/dump_cache/upgrade_win.cc b/chromium/net/tools/dump_cache/upgrade_win.cc
new file mode 100644
index 00000000000..5135592429b
--- /dev/null
+++ b/chromium/net/tools/dump_cache/upgrade_win.cc
@@ -0,0 +1,927 @@
+// Copyright (c) 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.
+
+#include "net/tools/dump_cache/upgrade_win.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread.h"
+#include "base/win/scoped_handle.h"
+#include "net/base/cache_type.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/backend_impl.h"
+#include "net/disk_cache/entry_impl.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/tools/dump_cache/cache_dumper.h"
+#include "url/gurl.h"
+
+namespace {
+
+const wchar_t kPipePrefix[] = L"\\\\.\\pipe\\dump_cache_";
+const int kChannelSize = 64 * 1024;
+const int kNumStreams = 4;
+
+// Simple macro to print out formatted debug messages. It is similar to a DLOG
+// except that it doesn't include a header.
+#ifdef NDEBUG
+#define DEBUGMSG(...) {}
+#else
+#define DEBUGMSG(...) { printf(__VA_ARGS__); }
+#endif
+
+HANDLE OpenServer(const base::string16& pipe_number) {
+ base::string16 pipe_name(kPipePrefix);
+ pipe_name.append(pipe_number);
+ return CreateFile(pipe_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+}
+
+// This is the basic message to use between the two processes. It is intended
+// to transmit a single action (like "get the key name for entry xx"), with up
+// to 5 32-bit arguments and 4 64-bit arguments. After this structure, the rest
+// of the message has |buffer_bytes| of length with the actual data.
+struct Message {
+ int32 command;
+ int32 result;
+ int32 buffer_bytes;
+ int32 arg1;
+ int32 arg2;
+ int32 arg3;
+ int32 arg4;
+ int32 arg5;
+ int64 long_arg1;
+ int64 long_arg2;
+ int64 long_arg3;
+ int64 long_arg4;
+ Message() {
+ memset(this, 0, sizeof(*this));
+ }
+ Message& operator= (const Message& other) {
+ memcpy(this, &other, sizeof(*this));
+ return *this;
+ }
+};
+
+const int kBufferSize = kChannelSize - sizeof(Message);
+struct IoBuffer {
+ Message msg;
+ char buffer[kBufferSize];
+};
+COMPILE_ASSERT(sizeof(IoBuffer) == kChannelSize, invalid_io_buffer);
+
+
+// The list of commands.
+// Currently, there is support for working ONLY with one entry at a time.
+enum {
+ // Get the entry from list |arg1| that follows |long_arg1|.
+ // The result is placed on |long_arg1| (closes the previous one).
+ GET_NEXT_ENTRY = 1,
+ // Get the entry from list |arg1| that precedes |long_arg1|.
+ // The result is placed on |long_arg1| (closes the previous one).
+ GET_PREV_ENTRY,
+ // Closes the entry |long_arg1|.
+ CLOSE_ENTRY,
+ // Get the key of the entry |long_arg1|.
+ GET_KEY,
+ // Get last used (long_arg2) and last modified (long_arg3) times for the
+ // entry at |long_arg1|.
+ GET_USE_TIMES,
+ // Returns on |arg2| the data size in bytes if the stream |arg1| of entry at
+ // |long_arg1|.
+ GET_DATA_SIZE,
+ // Returns |arg2| bytes of the stream |arg1| for the entry at |long_arg1|,
+ // starting at offset |arg3|.
+ READ_DATA,
+ // End processing requests.
+ QUIT
+};
+
+// The list of return codes.
+enum {
+ RESULT_OK = 0,
+ RESULT_UNKNOWN_COMMAND,
+ RESULT_INVALID_PARAMETER,
+ RESULT_NAME_OVERFLOW,
+ RESULT_PENDING // This error code is NOT expected by the master process.
+};
+
+// -----------------------------------------------------------------------
+
+class BaseSM : public base::MessageLoopForIO::IOHandler {
+ public:
+ explicit BaseSM(HANDLE channel);
+ virtual ~BaseSM();
+
+ protected:
+ bool SendMsg(const Message& msg);
+ bool ReceiveMsg();
+ bool ConnectChannel();
+ bool IsPending();
+
+ base::MessageLoopForIO::IOContext in_context_;
+ base::MessageLoopForIO::IOContext out_context_;
+ disk_cache::EntryImpl* entry_;
+ HANDLE channel_;
+ int state_;
+ int pending_count_;
+ scoped_ptr<char[]> in_buffer_;
+ scoped_ptr<char[]> out_buffer_;
+ IoBuffer* input_;
+ IoBuffer* output_;
+ base::Thread cache_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseSM);
+};
+
+BaseSM::BaseSM(HANDLE channel)
+ : entry_(NULL), channel_(channel), state_(0), pending_count_(0),
+ cache_thread_("cache") {
+ in_buffer_.reset(new char[kChannelSize]);
+ out_buffer_.reset(new char[kChannelSize]);
+ input_ = reinterpret_cast<IoBuffer*>(in_buffer_.get());
+ output_ = reinterpret_cast<IoBuffer*>(out_buffer_.get());
+
+ memset(&in_context_, 0, sizeof(in_context_));
+ memset(&out_context_, 0, sizeof(out_context_));
+ in_context_.handler = this;
+ out_context_.handler = this;
+ base::MessageLoopForIO::current()->RegisterIOHandler(channel_, this);
+ CHECK(cache_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0)));
+}
+
+BaseSM::~BaseSM() {
+ if (entry_)
+ entry_->Close();
+}
+
+bool BaseSM::SendMsg(const Message& msg) {
+ // Only one command will be in-flight at a time. Let's start the Read IO here
+ // when we know that it will be pending.
+ if (!ReceiveMsg())
+ return false;
+
+ output_->msg = msg;
+ DWORD written;
+ if (!WriteFile(channel_, output_, sizeof(msg) + msg.buffer_bytes, &written,
+ &out_context_.overlapped)) {
+ if (ERROR_IO_PENDING != GetLastError())
+ return false;
+ }
+ pending_count_++;
+ return true;
+}
+
+bool BaseSM::ReceiveMsg() {
+ DWORD read;
+ if (!ReadFile(channel_, input_, kChannelSize, &read,
+ &in_context_.overlapped)) {
+ if (ERROR_IO_PENDING != GetLastError())
+ return false;
+ }
+ pending_count_++;
+ return true;
+}
+
+bool BaseSM::ConnectChannel() {
+ if (!ConnectNamedPipe(channel_, &in_context_.overlapped)) {
+ DWORD error = GetLastError();
+ if (ERROR_PIPE_CONNECTED == error)
+ return true;
+ // By returning true in case of a generic error, we allow the operation to
+ // fail while sending the first message.
+ if (ERROR_IO_PENDING != error)
+ return true;
+ }
+ pending_count_++;
+ return false;
+}
+
+bool BaseSM::IsPending() {
+ return pending_count_ != 0;
+}
+
+// -----------------------------------------------------------------------
+
+class MasterSM : public BaseSM {
+ public:
+ MasterSM(const base::FilePath& path, HANDLE channel)
+ : BaseSM(channel),
+ path_(path) {
+ }
+ virtual ~MasterSM() {
+ delete writer_;
+ }
+
+ bool DoInit();
+ virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error);
+
+ private:
+ enum {
+ MASTER_INITIAL = 0,
+ MASTER_CONNECT,
+ MASTER_GET_ENTRY,
+ MASTER_GET_NEXT_ENTRY,
+ MASTER_GET_KEY,
+ MASTER_GET_USE_TIMES,
+ MASTER_GET_DATA_SIZE,
+ MASTER_READ_DATA,
+ MASTER_END
+ };
+
+ void SendGetPrevEntry();
+ void DoGetEntry();
+ void DoGetKey(int bytes_read);
+ void DoCreateEntryComplete(int result);
+ void DoGetUseTimes();
+ void SendGetDataSize();
+ void DoGetDataSize();
+ void CloseEntry();
+ void SendReadData();
+ void DoReadData(int bytes_read);
+ void DoReadDataComplete(int ret);
+ void SendQuit();
+ void DoEnd();
+ void Fail();
+
+ base::Time last_used_;
+ base::Time last_modified_;
+ int64 remote_entry_;
+ int stream_;
+ int bytes_remaining_;
+ int offset_;
+ int copied_entries_;
+ int read_size_;
+ scoped_ptr<disk_cache::Backend> cache_;
+ CacheDumpWriter* writer_;
+ const base::FilePath path_;
+};
+
+void MasterSM::OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error) {
+ pending_count_--;
+ if (context == &out_context_) {
+ if (!error)
+ return;
+ return Fail();
+ }
+
+ int bytes_read = static_cast<int>(bytes_transfered);
+ if (bytes_read < sizeof(Message) && state_ != MASTER_END &&
+ state_ != MASTER_CONNECT) {
+ printf("Communication breakdown\n");
+ return Fail();
+ }
+
+ switch (state_) {
+ case MASTER_CONNECT:
+ SendGetPrevEntry();
+ break;
+ case MASTER_GET_ENTRY:
+ DoGetEntry();
+ break;
+ case MASTER_GET_KEY:
+ DoGetKey(bytes_read);
+ break;
+ case MASTER_GET_USE_TIMES:
+ DoGetUseTimes();
+ break;
+ case MASTER_GET_DATA_SIZE:
+ DoGetDataSize();
+ break;
+ case MASTER_READ_DATA:
+ DoReadData(bytes_read);
+ break;
+ case MASTER_END:
+ if (!IsPending())
+ DoEnd();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool MasterSM::DoInit() {
+ DEBUGMSG("Master DoInit\n");
+ DCHECK(state_ == MASTER_INITIAL);
+
+ scoped_ptr<disk_cache::Backend> cache;
+ net::TestCompletionCallback cb;
+ int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_DEFAULT, path_, 0,
+ false,
+ cache_thread_.message_loop_proxy(),
+ NULL, &cache, cb.callback());
+ if (cb.GetResult(rv) != net::OK) {
+ printf("Unable to initialize new files\n");
+ return false;
+ }
+ cache_ = cache.Pass();
+ writer_ = new CacheDumper(cache_.get());
+
+ copied_entries_ = 0;
+ remote_entry_ = 0;
+
+ if (ConnectChannel()) {
+ SendGetPrevEntry();
+ // If we don't have pending operations we couldn't connect.
+ return IsPending();
+ }
+
+ state_ = MASTER_CONNECT;
+ return true;
+}
+
+void MasterSM::SendGetPrevEntry() {
+ DEBUGMSG("Master SendGetPrevEntry\n");
+ state_ = MASTER_GET_ENTRY;
+ Message msg;
+ msg.command = GET_PREV_ENTRY;
+ msg.long_arg1 = remote_entry_;
+ SendMsg(msg);
+}
+
+void MasterSM::DoGetEntry() {
+ DEBUGMSG("Master DoGetEntry\n");
+ DCHECK(state_ == MASTER_GET_ENTRY);
+ DCHECK(input_->msg.command == GET_PREV_ENTRY);
+ if (input_->msg.result != RESULT_OK)
+ return Fail();
+
+ if (!input_->msg.long_arg1) {
+ printf("Done: %d entries copied over.\n", copied_entries_);
+ return SendQuit();
+ }
+ remote_entry_ = input_->msg.long_arg1;
+ state_ = MASTER_GET_KEY;
+ Message msg;
+ msg.command = GET_KEY;
+ msg.long_arg1 = remote_entry_;
+ SendMsg(msg);
+}
+
+void MasterSM::DoGetKey(int bytes_read) {
+ DEBUGMSG("Master DoGetKey\n");
+ DCHECK(state_ == MASTER_GET_KEY);
+ DCHECK(input_->msg.command == GET_KEY);
+ if (input_->msg.result == RESULT_NAME_OVERFLOW) {
+ // The key is too long. Just move on.
+ printf("Skipping entry (name too long)\n");
+ return SendGetPrevEntry();
+ }
+
+ if (input_->msg.result != RESULT_OK)
+ return Fail();
+
+ std::string key(input_->buffer);
+ DCHECK(key.size() == static_cast<size_t>(input_->msg.buffer_bytes - 1));
+
+ int rv = writer_->CreateEntry(
+ key, reinterpret_cast<disk_cache::Entry**>(&entry_),
+ base::Bind(&MasterSM::DoCreateEntryComplete, base::Unretained(this)));
+
+ if (rv != net::ERR_IO_PENDING)
+ DoCreateEntryComplete(rv);
+}
+
+void MasterSM::DoCreateEntryComplete(int result) {
+ std::string key(input_->buffer);
+ if (result != net::OK) {
+ printf("Skipping entry \"%s\": %d\n", key.c_str(), GetLastError());
+ return SendGetPrevEntry();
+ }
+
+ if (key.size() >= 64) {
+ key[60] = '.';
+ key[61] = '.';
+ key[62] = '.';
+ key[63] = '\0';
+ }
+ DEBUGMSG("Entry \"%s\" created\n", key.c_str());
+ state_ = MASTER_GET_USE_TIMES;
+ Message msg;
+ msg.command = GET_USE_TIMES;
+ msg.long_arg1 = remote_entry_;
+ SendMsg(msg);
+}
+
+void MasterSM::DoGetUseTimes() {
+ DEBUGMSG("Master DoGetUseTimes\n");
+ DCHECK(state_ == MASTER_GET_USE_TIMES);
+ DCHECK(input_->msg.command == GET_USE_TIMES);
+ if (input_->msg.result != RESULT_OK)
+ return Fail();
+
+ last_used_ = base::Time::FromInternalValue(input_->msg.long_arg2);
+ last_modified_ = base::Time::FromInternalValue(input_->msg.long_arg3);
+ stream_ = 0;
+ SendGetDataSize();
+}
+
+void MasterSM::SendGetDataSize() {
+ DEBUGMSG("Master SendGetDataSize (%d)\n", stream_);
+ state_ = MASTER_GET_DATA_SIZE;
+ Message msg;
+ msg.command = GET_DATA_SIZE;
+ msg.arg1 = stream_;
+ msg.long_arg1 = remote_entry_;
+ SendMsg(msg);
+}
+
+void MasterSM::DoGetDataSize() {
+ DEBUGMSG("Master DoGetDataSize: %d\n", input_->msg.arg2);
+ DCHECK(state_ == MASTER_GET_DATA_SIZE);
+ DCHECK(input_->msg.command == GET_DATA_SIZE);
+ if (input_->msg.result == RESULT_INVALID_PARAMETER)
+ // No more streams, move to the next entry.
+ return CloseEntry();
+
+ if (input_->msg.result != RESULT_OK)
+ return Fail();
+
+ bytes_remaining_ = input_->msg.arg2;
+ offset_ = 0;
+ SendReadData();
+}
+
+void MasterSM::CloseEntry() {
+ DEBUGMSG("Master CloseEntry\n");
+ printf("%c\r", copied_entries_ % 2 ? 'x' : '+');
+ writer_->CloseEntry(entry_, last_used_, last_modified_);
+ entry_ = NULL;
+ copied_entries_++;
+ SendGetPrevEntry();
+}
+
+void MasterSM::SendReadData() {
+ int read_size = std::min(bytes_remaining_, kBufferSize);
+ DEBUGMSG("Master SendReadData (%d): %d bytes at %d\n", stream_, read_size,
+ offset_);
+ if (bytes_remaining_ <= 0) {
+ stream_++;
+ if (stream_ >= kNumStreams)
+ return CloseEntry();
+ return SendGetDataSize();
+ }
+
+ state_ = MASTER_READ_DATA;
+ Message msg;
+ msg.command = READ_DATA;
+ msg.arg1 = stream_;
+ msg.arg2 = read_size;
+ msg.arg3 = offset_;
+ msg.long_arg1 = remote_entry_;
+ SendMsg(msg);
+}
+
+void MasterSM::DoReadData(int bytes_read) {
+ DEBUGMSG("Master DoReadData: %d bytes\n", input_->msg.buffer_bytes);
+ DCHECK(state_ == MASTER_READ_DATA);
+ DCHECK(input_->msg.command == READ_DATA);
+ if (input_->msg.result != RESULT_OK)
+ return Fail();
+
+ int read_size = input_->msg.buffer_bytes;
+ if (!read_size) {
+ printf("Read failed, entry \"%s\" truncated!\n", entry_->GetKey().c_str());
+ bytes_remaining_ = 0;
+ return SendReadData();
+ }
+
+ scoped_refptr<net::WrappedIOBuffer> buf =
+ new net::WrappedIOBuffer(input_->buffer);
+ int rv = writer_->WriteEntry(
+ entry_, stream_, offset_, buf, read_size,
+ base::Bind(&MasterSM::DoReadDataComplete, base::Unretained(this)));
+ if (rv == net::ERR_IO_PENDING) {
+ // We'll continue in DoReadDataComplete.
+ read_size_ = read_size;
+ return;
+ }
+
+ if (rv <= 0)
+ return Fail();
+
+ offset_ += read_size;
+ bytes_remaining_ -= read_size;
+ // Read some more.
+ SendReadData();
+}
+
+void MasterSM::DoReadDataComplete(int ret) {
+ if (ret != read_size_)
+ return Fail();
+
+ offset_ += ret;
+ bytes_remaining_ -= ret;
+ // Read some more.
+ SendReadData();
+}
+
+void MasterSM::SendQuit() {
+ DEBUGMSG("Master SendQuit\n");
+ state_ = MASTER_END;
+ Message msg;
+ msg.command = QUIT;
+ SendMsg(msg);
+ if (!IsPending())
+ DoEnd();
+}
+
+void MasterSM::DoEnd() {
+ DEBUGMSG("Master DoEnd\n");
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+}
+
+void MasterSM::Fail() {
+ DEBUGMSG("Master Fail\n");
+ printf("Unexpected failure\n");
+ SendQuit();
+}
+
+// -----------------------------------------------------------------------
+
+class SlaveSM : public BaseSM {
+ public:
+ SlaveSM(const base::FilePath& path, HANDLE channel);
+ virtual ~SlaveSM();
+
+ bool DoInit();
+ virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error);
+
+ private:
+ enum {
+ SLAVE_INITIAL = 0,
+ SLAVE_WAITING,
+ SLAVE_END
+ };
+
+ void DoGetNextEntry();
+ void DoGetPrevEntry();
+ int32 GetEntryFromList();
+ void DoGetEntryComplete(int result);
+ void DoCloseEntry();
+ void DoGetKey();
+ void DoGetUseTimes();
+ void DoGetDataSize();
+ void DoReadData();
+ void DoReadDataComplete(int ret);
+ void DoEnd();
+ void Fail();
+
+ void* iterator_;
+ Message msg_; // Used for DoReadDataComplete and DoGetEntryComplete.
+
+ scoped_ptr<disk_cache::BackendImpl> cache_;
+};
+
+SlaveSM::SlaveSM(const base::FilePath& path, HANDLE channel)
+ : BaseSM(channel), iterator_(NULL) {
+ scoped_ptr<disk_cache::Backend> cache;
+ net::TestCompletionCallback cb;
+ int rv = disk_cache::CreateCacheBackend(net::DISK_CACHE,
+ net::CACHE_BACKEND_BLOCKFILE, path, 0,
+ false,
+ cache_thread_.message_loop_proxy(),
+ NULL, &cache, cb.callback());
+ if (cb.GetResult(rv) != net::OK) {
+ printf("Unable to open cache files\n");
+ return;
+ }
+ cache_.reset(reinterpret_cast<disk_cache::BackendImpl*>(cache.release()));
+ cache_->SetUpgradeMode();
+}
+
+SlaveSM::~SlaveSM() {
+ if (iterator_)
+ cache_->EndEnumeration(&iterator_);
+}
+
+void SlaveSM::OnIOCompleted(base::MessageLoopForIO::IOContext* context,
+ DWORD bytes_transfered,
+ DWORD error) {
+ pending_count_--;
+ if (state_ == SLAVE_END) {
+ if (IsPending())
+ return;
+ return DoEnd();
+ }
+
+ if (context == &out_context_) {
+ if (!error)
+ return;
+ return Fail();
+ }
+
+ int bytes_read = static_cast<int>(bytes_transfered);
+ if (bytes_read < sizeof(Message)) {
+ printf("Communication breakdown\n");
+ return Fail();
+ }
+ DCHECK(state_ == SLAVE_WAITING);
+
+ switch (input_->msg.command) {
+ case GET_NEXT_ENTRY:
+ DoGetNextEntry();
+ break;
+ case GET_PREV_ENTRY:
+ DoGetPrevEntry();
+ break;
+ case CLOSE_ENTRY:
+ DoCloseEntry();
+ break;
+ case GET_KEY:
+ DoGetKey();
+ break;
+ case GET_USE_TIMES:
+ DoGetUseTimes();
+ break;
+ case GET_DATA_SIZE:
+ DoGetDataSize();
+ break;
+ case READ_DATA:
+ DoReadData();
+ break;
+ case QUIT:
+ DoEnd();
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+bool SlaveSM::DoInit() {
+ DEBUGMSG("\t\t\tSlave DoInit\n");
+ DCHECK(state_ == SLAVE_INITIAL);
+ state_ = SLAVE_WAITING;
+ if (!cache_.get())
+ return false;
+
+ return ReceiveMsg();
+}
+
+void SlaveSM::DoGetNextEntry() {
+ DEBUGMSG("\t\t\tSlave DoGetNextEntry\n");
+ Message msg;
+ msg.command = GET_NEXT_ENTRY;
+
+ if (input_->msg.arg1) {
+ // We only support one list.
+ msg.result = RESULT_UNKNOWN_COMMAND;
+ } else {
+ msg.result = GetEntryFromList();
+ msg.long_arg1 = reinterpret_cast<int64>(entry_);
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoGetPrevEntry() {
+ DEBUGMSG("\t\t\tSlave DoGetPrevEntry\n");
+ Message msg;
+ msg.command = GET_PREV_ENTRY;
+
+ if (input_->msg.arg1) {
+ // We only support one list.
+ msg.result = RESULT_UNKNOWN_COMMAND;
+ } else {
+ msg.result = GetEntryFromList();
+ if (msg.result == RESULT_PENDING) {
+ // We are not done yet.
+ msg_ = msg;
+ return;
+ }
+ msg.long_arg1 = reinterpret_cast<int64>(entry_);
+ }
+ SendMsg(msg);
+}
+
+// Move to the next or previous entry on the list.
+int32 SlaveSM::GetEntryFromList() {
+ DEBUGMSG("\t\t\tSlave GetEntryFromList\n");
+ if (input_->msg.long_arg1 != reinterpret_cast<int64>(entry_))
+ return RESULT_INVALID_PARAMETER;
+
+ // We know that the current iteration is valid.
+ if (entry_)
+ entry_->Close();
+
+ int rv;
+ if (input_->msg.command == GET_NEXT_ENTRY) {
+ rv = cache_->OpenNextEntry(
+ &iterator_, reinterpret_cast<disk_cache::Entry**>(&entry_),
+ base::Bind(&SlaveSM::DoGetEntryComplete, base::Unretained(this)));
+ } else {
+ DCHECK(input_->msg.command == GET_PREV_ENTRY);
+ rv = cache_->OpenPrevEntry(&iterator_,
+ reinterpret_cast<disk_cache::Entry**>(&entry_),
+ base::Bind(&SlaveSM::DoGetEntryComplete,
+ base::Unretained(this)));
+ }
+ DCHECK_EQ(net::ERR_IO_PENDING, rv);
+ return RESULT_PENDING;
+}
+
+void SlaveSM::DoGetEntryComplete(int result) {
+ DEBUGMSG("\t\t\tSlave DoGetEntryComplete\n");
+ if (result != net::OK) {
+ entry_ = NULL;
+ DEBUGMSG("\t\t\tSlave end of list\n");
+ }
+
+ msg_.result = RESULT_OK;
+ msg_.long_arg1 = reinterpret_cast<int64>(entry_);
+ SendMsg(msg_);
+}
+
+void SlaveSM::DoCloseEntry() {
+ DEBUGMSG("\t\t\tSlave DoCloseEntry\n");
+ Message msg;
+ msg.command = GET_KEY;
+
+ if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) {
+ msg.result = RESULT_INVALID_PARAMETER;
+ } else {
+ entry_->Close();
+ entry_ = NULL;
+ cache_->EndEnumeration(&iterator_);
+ msg.result = RESULT_OK;
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoGetKey() {
+ DEBUGMSG("\t\t\tSlave DoGetKey\n");
+ Message msg;
+ msg.command = GET_KEY;
+
+ if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) {
+ msg.result = RESULT_INVALID_PARAMETER;
+ } else {
+ std::string key = entry_->GetKey();
+ msg.buffer_bytes = std::min(key.size() + 1,
+ static_cast<size_t>(kBufferSize));
+ memcpy(output_->buffer, key.c_str(), msg.buffer_bytes);
+ if (msg.buffer_bytes != static_cast<int32>(key.size() + 1)) {
+ // We don't support moving this entry. Just tell the master.
+ msg.result = RESULT_NAME_OVERFLOW;
+ } else {
+ msg.result = RESULT_OK;
+ }
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoGetUseTimes() {
+ DEBUGMSG("\t\t\tSlave DoGetUseTimes\n");
+ Message msg;
+ msg.command = GET_USE_TIMES;
+
+ if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_)) {
+ msg.result = RESULT_INVALID_PARAMETER;
+ } else {
+ msg.long_arg2 = entry_->GetLastUsed().ToInternalValue();
+ msg.long_arg3 = entry_->GetLastModified().ToInternalValue();
+ msg.result = RESULT_OK;
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoGetDataSize() {
+ DEBUGMSG("\t\t\tSlave DoGetDataSize\n");
+ Message msg;
+ msg.command = GET_DATA_SIZE;
+
+ int stream = input_->msg.arg1;
+ if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_) ||
+ stream < 0 || stream >= kNumStreams) {
+ msg.result = RESULT_INVALID_PARAMETER;
+ } else {
+ msg.arg1 = stream;
+ msg.arg2 = entry_->GetDataSize(stream);
+ msg.result = RESULT_OK;
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoReadData() {
+ DEBUGMSG("\t\t\tSlave DoReadData\n");
+ Message msg;
+ msg.command = READ_DATA;
+
+ int stream = input_->msg.arg1;
+ int size = input_->msg.arg2;
+ if (!entry_ || input_->msg.long_arg1 != reinterpret_cast<int64>(entry_) ||
+ stream < 0 || stream > 1 || size > kBufferSize) {
+ msg.result = RESULT_INVALID_PARAMETER;
+ } else {
+ scoped_refptr<net::WrappedIOBuffer> buf =
+ new net::WrappedIOBuffer(output_->buffer);
+ int ret = entry_->ReadData(stream, input_->msg.arg3, buf, size,
+ base::Bind(&SlaveSM::DoReadDataComplete,
+ base::Unretained(this)));
+ if (ret == net::ERR_IO_PENDING) {
+ // Save the message so we can continue were we left.
+ msg_ = msg;
+ return;
+ }
+
+ msg.buffer_bytes = (ret < 0) ? 0 : ret;
+ msg.result = RESULT_OK;
+ }
+ SendMsg(msg);
+}
+
+void SlaveSM::DoReadDataComplete(int ret) {
+ DEBUGMSG("\t\t\tSlave DoReadDataComplete\n");
+ DCHECK_EQ(READ_DATA, msg_.command);
+ msg_.buffer_bytes = (ret < 0) ? 0 : ret;
+ msg_.result = RESULT_OK;
+ SendMsg(msg_);
+}
+
+void SlaveSM::DoEnd() {
+ DEBUGMSG("\t\t\tSlave DoEnd\n");
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+}
+
+void SlaveSM::Fail() {
+ DEBUGMSG("\t\t\tSlave Fail\n");
+ printf("Unexpected failure\n");
+ state_ = SLAVE_END;
+ if (IsPending()) {
+ CancelIo(channel_);
+ } else {
+ DoEnd();
+ }
+}
+
+} // namespace.
+
+// -----------------------------------------------------------------------
+
+HANDLE CreateServer(base::string16* pipe_number) {
+ base::string16 pipe_name(kPipePrefix);
+ srand(static_cast<int>(base::Time::Now().ToInternalValue()));
+ *pipe_number = base::IntToString16(rand());
+ pipe_name.append(*pipe_number);
+
+ DWORD mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE |
+ FILE_FLAG_OVERLAPPED;
+
+ return CreateNamedPipe(pipe_name.c_str(), mode, 0, 1, kChannelSize,
+ kChannelSize, 0, NULL);
+}
+
+// This is the controller process for an upgrade operation.
+int UpgradeCache(const base::FilePath& output_path, HANDLE pipe) {
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+
+ MasterSM master(output_path, pipe);
+ if (!master.DoInit()) {
+ printf("Unable to talk with the helper\n");
+ return -1;
+ }
+
+ loop.Run();
+ return 0;
+}
+
+// This process will only execute commands from the controller.
+int RunSlave(const base::FilePath& input_path,
+ const base::string16& pipe_number) {
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+
+ base::win::ScopedHandle pipe(OpenServer(pipe_number));
+ if (!pipe.IsValid()) {
+ printf("Unable to open the server pipe\n");
+ return -1;
+ }
+
+ SlaveSM slave(input_path, pipe);
+ if (!slave.DoInit()) {
+ printf("Unable to talk with the main process\n");
+ return -1;
+ }
+
+ loop.Run();
+ return 0;
+}
diff --git a/chromium/net/tools/dump_cache/upgrade_win.h b/chromium/net/tools/dump_cache/upgrade_win.h
new file mode 100644
index 00000000000..8b45d845b83
--- /dev/null
+++ b/chromium/net/tools/dump_cache/upgrade_win.h
@@ -0,0 +1,20 @@
+// Copyright (c) 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.
+
+#include <windows.h>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/strings/string16.h"
+
+// Creates a new server, and returns a new named pipe to communicate with it.
+HANDLE CreateServer(base::string16* pipe_number);
+
+// Runs a loop to write a new cache with all the data available from a slave
+// process connected through the provided |pipe|.
+int UpgradeCache(const base::FilePath& output_path, HANDLE pipe);
+
+// This process will only execute commands from the controller.
+int RunSlave(const base::FilePath& input_path,
+ const base::string16& pipe_number);
diff --git a/chromium/net/tools/dump_cache/url_to_filename_encoder.cc b/chromium/net/tools/dump_cache/url_to_filename_encoder.cc
new file mode 100644
index 00000000000..b807ec09267
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_to_filename_encoder.cc
@@ -0,0 +1,292 @@
+// Copyright (c) 2011 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 <stdlib.h>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+using std::string;
+
+namespace {
+
+// Returns 1 if buf is prefixed by "num_digits" of hex digits
+// Teturns 0 otherwise.
+// The function checks for '\0' for string termination.
+int HexDigitsPrefix(const char* buf, int num_digits) {
+ for (int i = 0; i < num_digits; i++) {
+ if (!IsHexDigit(buf[i]))
+ return 0; // This also detects end of string as '\0' is not xdigit.
+ }
+ return 1;
+}
+
+#ifdef WIN32
+#define strtoull _strtoui64
+#endif
+
+// A simple parser for long long values. Returns the parsed value if a
+// valid integer is found; else returns deflt
+// UInt64 and Int64 cannot handle decimal numbers with leading 0s.
+uint64 ParseLeadingHex64Value(const char *str, uint64 deflt) {
+ char *error = NULL;
+ const uint64 value = strtoull(str, &error, 16);
+ return (error == str) ? deflt : value;
+}
+
+}
+
+namespace net {
+
+// The escape character choice is made here -- all code and tests in this
+// directory are based off of this constant. However, our testdata
+// has tons of dependencies on this, so it cannot be changed without
+// re-running those tests and fixing them.
+const char UrlToFilenameEncoder::kEscapeChar = ',';
+const char UrlToFilenameEncoder::kTruncationChar = '-';
+const size_t UrlToFilenameEncoder::kMaximumSubdirectoryLength = 128;
+
+void UrlToFilenameEncoder::AppendSegment(string* segment, string* dest) {
+ CHECK(!segment->empty());
+ if ((*segment == ".") || (*segment == "..")) {
+ dest->append(1, kEscapeChar);
+ dest->append(*segment);
+ segment->clear();
+ } else {
+ size_t segment_size = segment->size();
+ if (segment_size > kMaximumSubdirectoryLength) {
+ // We need to inject ",-" at the end of the segment to signify that
+ // we are inserting an artificial '/'. This means we have to chop
+ // off at least two characters to make room.
+ segment_size = kMaximumSubdirectoryLength - 2;
+
+ // But we don't want to break up an escape sequence that happens to lie at
+ // the end. Escape sequences are at most 2 characters.
+ if ((*segment)[segment_size - 1] == kEscapeChar) {
+ segment_size -= 1;
+ } else if ((*segment)[segment_size - 2] == kEscapeChar) {
+ segment_size -= 2;
+ }
+ dest->append(segment->data(), segment_size);
+ dest->append(1, kEscapeChar);
+ dest->append(1, kTruncationChar);
+ segment->erase(0, segment_size);
+
+ // At this point, if we had segment_size=3, and segment="abcd",
+ // then after this erase, we will have written "abc,-" and set segment="d"
+ } else {
+ dest->append(*segment);
+ segment->clear();
+ }
+ }
+}
+
+void UrlToFilenameEncoder::EncodeSegment(const string& filename_prefix,
+ const string& escaped_ending,
+ char dir_separator,
+ string* encoded_filename) {
+ string filename_ending = UrlUtilities::Unescape(escaped_ending);
+
+ char encoded[3];
+ int encoded_len;
+ string segment;
+
+ // TODO(jmarantz): This code would be a bit simpler if we disallowed
+ // Instaweb allowing filename_prefix to not end in "/". We could
+ // then change the is routine to just take one input string.
+ size_t start_of_segment = filename_prefix.find_last_of(dir_separator);
+ if (start_of_segment == string::npos) {
+ segment = filename_prefix;
+ } else {
+ segment = filename_prefix.substr(start_of_segment + 1);
+ *encoded_filename = filename_prefix.substr(0, start_of_segment + 1);
+ }
+
+ size_t index = 0;
+ // Special case the first / to avoid adding a leading kEscapeChar.
+ if (!filename_ending.empty() && (filename_ending[0] == dir_separator)) {
+ encoded_filename->append(segment);
+ segment.clear();
+ encoded_filename->append(1, dir_separator);
+ ++index;
+ }
+
+ for (; index < filename_ending.length(); ++index) {
+ unsigned char ch = static_cast<unsigned char>(filename_ending[index]);
+
+ // Note: instead of outputing an empty segment, we let the second slash
+ // be escaped below.
+ if ((ch == dir_separator) && !segment.empty()) {
+ AppendSegment(&segment, encoded_filename);
+ encoded_filename->append(1, dir_separator);
+ segment.clear();
+ } else {
+ // After removing unsafe chars the only safe ones are _.=+- and alphanums.
+ if ((ch == '_') || (ch == '.') || (ch == '=') || (ch == '+') ||
+ (ch == '-') || (('0' <= ch) && (ch <= '9')) ||
+ (('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) {
+ encoded[0] = ch;
+ encoded_len = 1;
+ } else {
+ encoded[0] = kEscapeChar;
+ encoded[1] = ch / 16;
+ encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+ encoded[2] = ch % 16;
+ encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+ encoded_len = 3;
+ }
+ segment.append(encoded, encoded_len);
+
+ // If segment is too big, we must chop it into chunks.
+ if (segment.size() > kMaximumSubdirectoryLength) {
+ AppendSegment(&segment, encoded_filename);
+ encoded_filename->append(1, dir_separator);
+ }
+ }
+ }
+
+ // Append "," to the leaf filename so the leaf can also be a branch., e.g.
+ // allow http://a/b/c and http://a/b/c/d to co-exist as files "/a/b/c," and
+ // /a/b/c/d". So we will rename the "d" here to "d,". If doing that pushed
+ // us over the 128 char limit, then we will need to append "/" and the
+ // remaining chars.
+ segment += kEscapeChar;
+ AppendSegment(&segment, encoded_filename);
+ if (!segment.empty()) {
+ // The last overflow segment is special, because we appended in
+ // kEscapeChar above. We won't need to check it again for size
+ // or further escaping.
+ encoded_filename->append(1, dir_separator);
+ encoded_filename->append(segment);
+ }
+}
+
+// Note: this decoder is not the exact inverse of the EncodeSegment above,
+// because it does not take into account a prefix.
+bool UrlToFilenameEncoder::Decode(const string& encoded_filename,
+ char dir_separator,
+ string* decoded_url) {
+ enum State {
+ kStart,
+ kEscape,
+ kFirstDigit,
+ kTruncate,
+ kEscapeDot
+ };
+ State state = kStart;
+ char hex_buffer[3];
+ hex_buffer[2] = '\0';
+ for (size_t i = 0; i < encoded_filename.size(); ++i) {
+ char ch = encoded_filename[i];
+ switch (state) {
+ case kStart:
+ if (ch == kEscapeChar) {
+ state = kEscape;
+ } else if (ch == dir_separator) {
+ decoded_url->append(1, '/'); // URLs only use '/' not '\\'
+ } else {
+ decoded_url->append(1, ch);
+ }
+ break;
+ case kEscape:
+ if (HexDigitsPrefix(&ch, 1) == 1) {
+ hex_buffer[0] = ch;
+ state = kFirstDigit;
+ } else if (ch == kTruncationChar) {
+ state = kTruncate;
+ } else if (ch == '.') {
+ decoded_url->append(1, '.');
+ state = kEscapeDot; // Look for at most one more dot.
+ } else if (ch == dir_separator) {
+ // Consider url "//x". This was once encoded to "/,/x,".
+ // This code is what skips the first Escape.
+ decoded_url->append(1, '/'); // URLs only use '/' not '\\'
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kFirstDigit:
+ if (HexDigitsPrefix(&ch, 1) == 1) {
+ hex_buffer[1] = ch;
+ uint64 hex_value = ParseLeadingHex64Value(hex_buffer, 0);
+ decoded_url->append(1, static_cast<char>(hex_value));
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kTruncate:
+ if (ch == dir_separator) {
+ // Skip this separator, it was only put in to break up long
+ // path segments, but is not part of the URL.
+ state = kStart;
+ } else {
+ return false;
+ }
+ break;
+ case kEscapeDot:
+ decoded_url->append(1, ch);
+ state = kStart;
+ break;
+ }
+ }
+
+ // All legal encoded filenames end in kEscapeChar.
+ return (state == kEscape);
+}
+
+// Escape the given input |path| and chop any individual components
+// of the path which are greater than kMaximumSubdirectoryLength characters
+// into two chunks.
+//
+// This legacy version has several issues with aliasing of different URLs,
+// inability to represent both /a/b/c and /a/b/c/d, and inability to decode
+// the filenames back into URLs.
+//
+// But there is a large body of slurped data which depends on this format,
+// so leave it as the default for spdy_in_mem_edsm_server.
+string UrlToFilenameEncoder::LegacyEscape(const string& path) {
+ string output;
+
+ // Note: We also chop paths into medium sized 'chunks'.
+ // This is due to the incompetence of the windows
+ // filesystem, which still hasn't figured out how
+ // to deal with long filenames.
+ int last_slash = 0;
+ for (size_t index = 0; index < path.length(); index++) {
+ char ch = path[index];
+ if (ch == 0x5C)
+ last_slash = index;
+ if ((ch == 0x2D) || // hyphen
+ (ch == 0x5C) || (ch == 0x5F) || // backslash, underscore
+ ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9]
+ ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z]
+ ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z]
+ output.append(&path[index], 1);
+ } else {
+ char encoded[3];
+ encoded[0] = 'x';
+ encoded[1] = ch / 16;
+ encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0';
+ encoded[2] = ch % 16;
+ encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0';
+ output.append(encoded, 3);
+ }
+ if (index - last_slash > kMaximumSubdirectoryLength) {
+#ifdef WIN32
+ char slash = '\\';
+#else
+ char slash = '/';
+#endif
+ output.append(&slash, 1);
+ last_slash = index;
+ }
+ }
+ return output;
+}
+
+} // namespace net
diff --git a/chromium/net/tools/dump_cache/url_to_filename_encoder.h b/chromium/net/tools/dump_cache/url_to_filename_encoder.h
new file mode 100644
index 00000000000..7918418d5b2
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_to_filename_encoder.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2010 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.
+
+// URL filename encoder goals:
+//
+// 1. Allow URLs with arbitrary path-segment length, generating filenames
+// with a maximum of 128 characters.
+// 2. Provide a somewhat human readable filenames, for easy debugging flow.
+// 3. Provide reverse-mapping from filenames back to URLs.
+// 4. Be able to distinguish http://x from http://x/ from http://x/index.html.
+// Those can all be different URLs.
+// 5. Be able to represent http://a/b/c and http://a/b/c/d, a pattern seen
+// with Facebook Connect.
+//
+// We need an escape-character for representing characters that are legal
+// in URL paths, but not in filenames, such as '?'.
+//
+// We can pick any legal character as an escape, as long as we escape it too.
+// But as we have a goal of having filenames that humans can correlate with
+// URLs, we should pick one that doesn't show up frequently in URLs. Candidates
+// are ~`!@#$%^&()-=_+{}[],. but we would prefer to avoid characters that are
+// shell escapes or that various build tools use.
+//
+// .#&%-=_+ occur frequently in URLs.
+// <>:"/\|?* are illegal in Windows
+// See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+// ~`!$^&(){}[]'; are special to Unix shells
+// In addition, build tools do not like ^@#%
+//
+// Josh took a quick look at the frequency of some special characters in
+// Sadeesh's slurped directory from Fall 09 and found the following occurances:
+//
+// ^ 3 build tool doesn't like ^ in testdata filenames
+// @ 10 build tool doesn't like @ in testdata filenames
+// . 1676 too frequent in URLs
+// , 76 THE WINNER
+// # 0 build tool doesn't like it
+// & 487 Prefer to avoid shell escapes
+// % 374 g4 doesn't like it
+// = 579 very frequent in URLs -- leave unmodified
+// - 464 very frequent in URLs -- leave unmodified
+// _ 798 very frequent in URLs -- leave unmodified
+//
+//
+// The escaping algorithm is:
+// 1) Escape all unfriendly symbols as ,XX where XX is the hex code.
+// 2) Add a ',' at the end (We do not allow ',' at end of any directory name,
+// so this assures that e.g. /a and /a/b can coexist in the filesystem).
+// 3) Go through the path segment by segment (where a segment is one directory
+// or leaf in the path) and
+// 3a) If the segment is empty, escape the second slash. i.e. if it was
+// www.foo.com//a then we escape the second / like www.foo.com/,2Fa,
+// 3a) If it is "." or ".." prepend with ',' (so that we have a non-
+// empty and non-reserved filename).
+// 3b) If it is over 128 characters, break it up into smaller segments by
+// inserting ,-/ (Windows limits paths to 128 chars, other OSes also
+// have limits that would restrict us)
+//
+// For example:
+// URL File
+// / /,
+// /index.html /index.html,
+// /. /.,
+// /a/b /a/b,
+// /a/b/ /a/b/,
+// /a/b/c /a/b/c, Note: no prefix problem
+// /u?foo=bar /u,3Ffoo=bar,
+// // /,2F,
+// /./ /,./,
+// /../ /,../,
+// /, /,2C,
+// /,./ /,2C./,
+// /very...longname/ /very...long,-/name If very...long is about 126 long.
+
+// NOTE: we avoid using some classes here (like FilePath and GURL) because we
+// share this code with other projects externally.
+
+#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+#define NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
+
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "net/tools/dump_cache/url_utilities.h"
+
+namespace net {
+
+// Helper class for converting a URL into a filename.
+class UrlToFilenameEncoder {
+ public:
+ // Given a |url| and a |base_path|, returns a filename which represents this
+ // |url|. |url| may include URL escaping such as %21 for !
+ // |legacy_escape| indicates that this function should use the old-style
+ // of encoding.
+ // TODO(mbelshe): delete the legacy_escape code.
+ static std::string Encode(const std::string& url, std::string base_path,
+ bool legacy_escape) {
+ std::string filename;
+ if (!legacy_escape) {
+ std::string url_no_scheme = UrlUtilities::GetUrlHostPath(url);
+ EncodeSegment(base_path, url_no_scheme, '/', &filename);
+#ifdef WIN32
+ ReplaceAll(&filename, "/", "\\");
+#endif
+ } else {
+ std::string clean_url(url);
+ if (clean_url.length() && clean_url[clean_url.length()-1] == '/')
+ clean_url.append("index.html");
+
+ std::string host = UrlUtilities::GetUrlHost(clean_url);
+ filename.append(base_path);
+ filename.append(host);
+#ifdef WIN32
+ filename.append("\\");
+#else
+ filename.append("/");
+#endif
+
+ std::string url_filename = UrlUtilities::GetUrlPath(clean_url);
+ // Strip the leading '/'.
+ if (url_filename[0] == '/')
+ url_filename = url_filename.substr(1);
+
+ // Replace '/' with '\'.
+ ConvertToSlashes(&url_filename);
+
+ // Strip double back-slashes ("\\\\").
+ StripDoubleSlashes(&url_filename);
+
+ // Save path as filesystem-safe characters.
+ url_filename = LegacyEscape(url_filename);
+ filename.append(url_filename);
+
+#ifndef WIN32
+ // Last step - convert to native slashes.
+ const std::string slash("/");
+ const std::string backslash("\\");
+ ReplaceAll(&filename, backslash, slash);
+#endif
+ }
+
+ return filename;
+ }
+
+ // Rewrite HTML in a form that the SPDY in-memory server
+ // can read.
+ // |filename_prefix| is prepended without escaping.
+ // |escaped_ending| is the URL to be encoded into a filename. It may have URL
+ // escaped characters (like %21 for !).
+ // |dir_separator| is "/" on Unix, "\" on Windows.
+ // |encoded_filename| is the resultant filename.
+ static void EncodeSegment(
+ const std::string& filename_prefix,
+ const std::string& escaped_ending,
+ char dir_separator,
+ std::string* encoded_filename);
+
+ // Decodes a filename that was encoded with EncodeSegment,
+ // yielding back the original URL.
+ static bool Decode(const std::string& encoded_filename,
+ char dir_separator,
+ std::string* decoded_url);
+
+ static const char kEscapeChar;
+ static const char kTruncationChar;
+ static const size_t kMaximumSubdirectoryLength;
+
+ friend class UrlToFilenameEncoderTest;
+
+ private:
+ // Appends a segment of the path, special-casing "." and "..", and
+ // ensuring that the segment does not exceed the path length. If it does,
+ // it chops the end off the segment, writes the segment with a separator of
+ // ",-/", and then rewrites segment to contain just the truncated piece so
+ // it can be used in the next iteration.
+ // |segment| is a read/write parameter containing segment to write
+ // Note: this should not be called with empty segment.
+ static void AppendSegment(std::string* segment, std::string* dest);
+
+ // Allow reading of old slurped files.
+ static std::string LegacyEscape(const std::string& path);
+
+ // Replace all instances of |from| within |str| as |to|.
+ static void ReplaceAll(std::string* str, const std::string& from,
+ const std::string& to) {
+ std::string::size_type pos(0);
+ while ((pos = str->find(from, pos)) != std::string::npos) {
+ str->replace(pos, from.size(), to);
+ pos += from.size();
+ }
+ }
+
+ // Replace all instances of "/" with "\" in |path|.
+ static void ConvertToSlashes(std::string* path) {
+ const std::string slash("/");
+ const std::string backslash("\\");
+ ReplaceAll(path, slash, backslash);
+ }
+
+ // Replace all instances of "\\" with "%5C%5C" in |path|.
+ static void StripDoubleSlashes(std::string* path) {
+ const std::string doubleslash("\\\\");
+ const std::string escaped_doubleslash("%5C%5C");
+ ReplaceAll(path, doubleslash, escaped_doubleslash);
+ }
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
diff --git a/chromium/net/tools/dump_cache/url_to_filename_encoder_unittest.cc b/chromium/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
new file mode 100644
index 00000000000..a1a96651da3
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2010 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/tools/dump_cache/url_to_filename_encoder.h"
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_piece.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+
+#ifdef WIN32
+char kDirSeparator = '\\';
+char kOtherDirSeparator = '/';
+#else
+char kDirSeparator = '/';
+char kOtherDirSeparator = '\\';
+#endif
+
+class UrlToFilenameEncoderTest : public ::testing::Test {
+ protected:
+ UrlToFilenameEncoderTest() : escape_(1, UrlToFilenameEncoder::kEscapeChar),
+ dir_sep_(1, kDirSeparator) {
+ }
+
+ void CheckSegmentLength(const StringPiece& escaped_word) {
+ std::vector<StringPiece> components;
+ Tokenize(escaped_word, StringPiece("/"), &components);
+ for (size_t i = 0; i < components.size(); ++i) {
+ EXPECT_GE(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+ components[i].size());
+ }
+ }
+
+ void CheckValidChars(const StringPiece& escaped_word, char invalid_slash) {
+ // These characters are invalid in Windows. We add in ', as that's pretty
+ // inconvenient in a Unix filename.
+ //
+ // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
+ const string kInvalidChars = "<>:\"|?*'";
+ for (size_t i = 0; i < escaped_word.size(); ++i) {
+ char c = escaped_word[i];
+ EXPECT_EQ(string::npos, kInvalidChars.find(c));
+ EXPECT_NE(invalid_slash, c);
+ EXPECT_NE('\0', c); // only invalid character in Posix
+ EXPECT_GT(0x7E, c); // only English printable characters
+ }
+ }
+
+ void Validate(const string& in_word, const string& gold_word) {
+ string escaped_word, url;
+ UrlToFilenameEncoder::EncodeSegment(
+ std::string(), in_word, '/', &escaped_word);
+ EXPECT_EQ(gold_word, escaped_word);
+ CheckSegmentLength(escaped_word);
+ CheckValidChars(escaped_word, '\\');
+ UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+ EXPECT_EQ(in_word, url);
+ }
+
+ void ValidateAllSegmentsSmall(const string& in_word) {
+ string escaped_word, url;
+ UrlToFilenameEncoder::EncodeSegment(
+ std::string(), in_word, '/', &escaped_word);
+ CheckSegmentLength(escaped_word);
+ CheckValidChars(escaped_word, '\\');
+ UrlToFilenameEncoder::Decode(escaped_word, '/', &url);
+ EXPECT_EQ(in_word, url);
+ }
+
+ void ValidateNoChange(const string& word) {
+ // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+ Validate(word, word + escape_);
+ }
+
+ void ValidateEscaped(unsigned char ch) {
+ // We always suffix the leaf with kEscapeChar, unless the leaf is empty.
+ char escaped[100];
+ const char escape = UrlToFilenameEncoder::kEscapeChar;
+ base::snprintf(escaped, sizeof(escaped), "%c%02X%c", escape, ch, escape);
+ Validate(string(1, ch), escaped);
+ }
+
+ void ValidateUrl(const string& url, const string& base_path,
+ bool legacy_escape, const string& gold_filename) {
+ string encoded_filename = UrlToFilenameEncoder::Encode(
+ url, base_path, legacy_escape);
+ EXPECT_EQ(gold_filename, encoded_filename);
+ if (!legacy_escape) {
+ CheckSegmentLength(encoded_filename);
+ CheckValidChars(encoded_filename, kOtherDirSeparator);
+ string decoded_url;
+ UrlToFilenameEncoder::Decode(encoded_filename, kDirSeparator,
+ &decoded_url);
+ if (url != decoded_url) {
+ EXPECT_EQ(url, "http://" + decoded_url);
+ }
+ }
+ }
+
+ void ValidateUrlOldNew(const string& url, const string& gold_old_filename,
+ const string& gold_new_filename) {
+ ValidateUrl(url, std::string(), true, gold_old_filename);
+ ValidateUrl(url, std::string(), false, gold_new_filename);
+ }
+
+ void ValidateEncodeSame(const string& url1, const string& url2) {
+ string filename1 = UrlToFilenameEncoder::Encode(url1, std::string(), false);
+ string filename2 = UrlToFilenameEncoder::Encode(url2, std::string(), false);
+ EXPECT_EQ(filename1, filename2);
+ }
+
+ string escape_;
+ string dir_sep_;
+};
+
+TEST_F(UrlToFilenameEncoderTest, DoesNotEscape) {
+ ValidateNoChange(std::string());
+ ValidateNoChange("abcdefg");
+ ValidateNoChange("abcdefghijklmnopqrstuvwxyz");
+ ValidateNoChange("ZYXWVUT");
+ ValidateNoChange("ZYXWVUTSRQPONMLKJIHGFEDCBA");
+ ValidateNoChange("01234567689");
+ ValidateNoChange("_.=+-");
+ ValidateNoChange("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA"
+ "01234567689_.=+-");
+ ValidateNoChange("index.html");
+ ValidateNoChange("/");
+ ValidateNoChange("/.");
+ ValidateNoChange(".");
+ ValidateNoChange("..");
+}
+
+TEST_F(UrlToFilenameEncoderTest, Escapes) {
+ const string bad_chars =
+ "<>:\"\\|?*" // Illegal on Windows
+ "~`!$^&(){}[]';" // Bad for Unix shells
+ "^@" // Build tool doesn't like
+ "#%" // Tool doesn't like
+ ","; // The escape char has to be escaped
+
+ for (size_t i = 0; i < bad_chars.size(); ++i) {
+ ValidateEscaped(bad_chars[i]);
+ }
+
+ // Check non-printable characters.
+ ValidateEscaped('\0');
+ for (size_t i = 127; i < 256; ++i) {
+ ValidateEscaped(static_cast<char>(i));
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, DoesEscapeCorrectly) {
+ Validate("mysite.com&x", "mysite.com" + escape_ + "26x" + escape_);
+ Validate("/./", "/" + escape_ + "./" + escape_);
+ Validate("/../", "/" + escape_ + "../" + escape_);
+ Validate("//", "/" + escape_ + "2F" + escape_);
+ Validate("/./leaf", "/" + escape_ + "./leaf" + escape_);
+ Validate("/../leaf", "/" + escape_ + "../leaf" + escape_);
+ Validate("//leaf", "/" + escape_ + "2Fleaf" + escape_);
+ Validate("mysite/u?param1=x&param2=y",
+ "mysite/u" + escape_ + "3Fparam1=x" + escape_ + "26param2=y" +
+ escape_);
+ Validate("search?q=dogs&go=&form=QBLH&qs=n", // from Latency Labs bing test.
+ "search" + escape_ + "3Fq=dogs" + escape_ + "26go=" + escape_ +
+ "26form=QBLH" + escape_ + "26qs=n" + escape_);
+ Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true",
+ "" + escape_ + "7Ejoebob/my_neeto-website+with_stuff.asp" + escape_ +
+ "3Fid=138" + escape_ + "26content=true" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, EncodeUrlCorrectly) {
+ ValidateUrlOldNew("http://www.google.com/index.html",
+ "www.google.com" + dir_sep_ + "indexx2Ehtml",
+ "www.google.com" + dir_sep_ + "index.html" + escape_);
+ ValidateUrlOldNew("http://www.google.com/x/search?hl=en&q=dogs&oq=",
+ "www.google.com" + dir_sep_ + "x" + dir_sep_ +
+ "searchx3Fhlx3Denx26qx3Ddogsx26oqx3D",
+
+ "www.google.com" + dir_sep_ + "x" + dir_sep_ + "search" +
+ escape_ + "3Fhl=en" + escape_ + "26q=dogs" + escape_ +
+ "26oq=" + escape_);
+ ValidateUrlOldNew("http://www.foo.com/a//",
+ "www.foo.com" + dir_sep_ + "ax255Cx255Cindexx2Ehtml",
+ "www.foo.com" + dir_sep_ + "a" + dir_sep_ + escape_ + "2F" +
+ escape_);
+
+ // From bug: Double slash preserved.
+ ValidateUrl("http://www.foo.com/u?site=http://www.google.com/index.html",
+ std::string(),
+ false,
+ "www.foo.com" + dir_sep_ + "u" + escape_ + "3Fsite=http" +
+ escape_ + "3A" + dir_sep_ + escape_ + "2Fwww.google.com" +
+ dir_sep_ + "index.html" + escape_);
+ ValidateUrlOldNew(
+ "http://blogutils.net/olct/online.php?"
+ "site=http://thelwordfanfics.blogspot.&interval=600",
+
+ "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "onlinex2Ephpx3F"
+ "sitex3Dhttpx3Ax255Cx255Cthelwordfanficsx2Eblogspotx2Ex26intervalx3D600",
+
+ "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "online.php" + escape_ +
+ "3Fsite=http" + escape_ + "3A" + dir_sep_ + escape_ +
+ "2Fthelwordfanfics.blogspot." + escape_ + "26interval=600" + escape_);
+}
+
+// From bug: Escapes treated the same as normal char.
+TEST_F(UrlToFilenameEncoderTest, UnescapeUrlsBeforeEncode) {
+ for (int i = 0; i < 128; ++i) {
+ string unescaped(1, static_cast<char>(i));
+ string escaped = base::StringPrintf("%%%02X", i);
+ ValidateEncodeSame(unescaped, escaped);
+ }
+
+ ValidateEncodeSame(
+ "http://www.blogger.com/navbar.g?bName=God!&Mode=FOO&searchRoot"
+ "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch",
+
+ "http://www.blogger.com/navbar.g?bName=God%21&Mode=FOO&searchRoot"
+ "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch");
+}
+
+// From bug: Filename encoding is not prefix-free.
+TEST_F(UrlToFilenameEncoderTest, EscapeSecondSlash) {
+ Validate("/", "/" + escape_);
+ Validate("//", "/" + escape_ + "2F" + escape_);
+ Validate("///", "/" + escape_ + "2F" + "/" + escape_);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTail) {
+ static char long_word[] =
+ "~joebob/briggs/12345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890123456789012345678901234567890";
+
+ // the long lines in the string below are 64 characters, so we can see
+ // the slashes every 128.
+ string gold_long_word =
+ escape_ + "7Ejoebob/briggs/"
+ "1234567890123456789012345678901234567890123456789012345678901234"
+ "56789012345678901234567890123456789012345678901234567890123456" +
+ escape_ + "-/"
+ "7890123456789012345678901234567890123456789012345678901234567890"
+ "12345678901234567890123456789012345678901234567890123456789012" +
+ escape_ + "-/"
+ "3456789012345678901234567890123456789012345678901234567890123456"
+ "78901234567890123456789012345678901234567890123456789012345678" +
+ escape_ + "-/"
+ "9012345678901234567890" + escape_;
+ EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+ sizeof(long_word));
+ Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, LongTailQuestion) {
+ // Here the '?' in the last path segment expands to @3F, making
+ // it hit 128 chars before the input segment gets that big.
+ static char long_word[] =
+ "~joebob/briggs/1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"
+ "1234567?1234567?1234567?1234567?1234567?1234567?1234567?";
+
+ // Notice that at the end of the third segment, we avoid splitting
+ // the (escape_ + "3F") that was generated from the "?", so that segment is
+ // only 127 characters.
+ string pattern = "1234567" + escape_ + "3F"; // 10 characters
+ string gold_long_word =
+ escape_ + "7Ejoebob/briggs/" +
+ pattern + pattern + pattern + pattern + pattern + pattern + "1234"
+ "567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+ "123456" + escape_ + "-/"
+ "7" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern +
+ pattern + pattern + pattern + pattern + pattern + pattern + pattern +
+ "12" +
+ escape_ + "-/"
+ "34567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern
+ + "1234567" + escape_ + "3F" + pattern + pattern + pattern + pattern
+ + pattern + "1234567" +
+ escape_ + "-/" +
+ escape_ + "3F" + pattern + pattern + escape_;
+ EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+ sizeof(long_word));
+ Validate(long_word, gold_long_word);
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenNoEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen
+ for (int i = -4; i <= 4; ++i) {
+ string input;
+ input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength, 'x');
+ ValidateAllSegmentsSmall(input);
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenWithEscape) {
+ // hit corner cases, +/- 4 characters from kMaxLen. This time we
+ // leave off the last 'x' and put in a '.', which ensures that we
+ // are truncating with '/' *after* the expansion.
+ for (int i = -4; i <= 4; ++i) {
+ string input;
+ input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength - 1, 'x');
+ input.append(1, '.'); // this will expand to 3 characters.
+ ValidateAllSegmentsSmall(input);
+ }
+}
+
+TEST_F(UrlToFilenameEncoderTest, LeafBranchAlias) {
+ Validate("/a/b/c", "/a/b/c" + escape_); // c is leaf file "c,"
+ Validate("/a/b/c/d", "/a/b/c/d" + escape_); // c is directory "c"
+ Validate("/a/b/c/d/", "/a/b/c/d/" + escape_);
+}
+
+
+TEST_F(UrlToFilenameEncoderTest, BackslashSeparator) {
+ string long_word;
+ string escaped_word;
+ long_word.append(UrlToFilenameEncoder::kMaximumSubdirectoryLength + 1, 'x');
+ UrlToFilenameEncoder::EncodeSegment(
+ std::string(), long_word, '\\', &escaped_word);
+
+ // check that one backslash, plus the escape ",-", and the ending , got added.
+ EXPECT_EQ(long_word.size() + 4, escaped_word.size());
+ ASSERT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength,
+ escaped_word.size());
+ // Check that the backslash got inserted at the correct spot.
+ EXPECT_EQ('\\', escaped_word[
+ UrlToFilenameEncoder::kMaximumSubdirectoryLength]);
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/dump_cache/url_utilities.cc b/chromium/net/tools/dump_cache/url_utilities.cc
new file mode 100644
index 00000000000..76044943ce7
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_utilities.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 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/tools/dump_cache/url_utilities.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+
+namespace net {
+
+std::string UrlUtilities::GetUrlHost(const std::string& url) {
+ size_t b = url.find("//");
+ if (b == std::string::npos)
+ b = 0;
+ else
+ b += 2;
+ size_t next_slash = url.find_first_of('/', b);
+ size_t next_colon = url.find_first_of(':', b);
+ if (next_slash != std::string::npos
+ && next_colon != std::string::npos
+ && next_colon < next_slash) {
+ return std::string(url, b, next_colon - b);
+ }
+ if (next_slash == std::string::npos) {
+ if (next_colon != std::string::npos) {
+ return std::string(url, b, next_colon - b);
+ } else {
+ next_slash = url.size();
+ }
+ }
+ return std::string(url, b, next_slash - b);
+}
+
+std::string UrlUtilities::GetUrlHostPath(const std::string& url) {
+ size_t b = url.find("//");
+ if (b == std::string::npos)
+ b = 0;
+ else
+ b += 2;
+ return std::string(url, b);
+}
+
+std::string UrlUtilities::GetUrlPath(const std::string& url) {
+ size_t b = url.find("//");
+ if (b == std::string::npos)
+ b = 0;
+ else
+ b += 2;
+ b = url.find("/", b);
+ if (b == std::string::npos)
+ return "/";
+
+ size_t e = url.find("#", b+1);
+ if (e != std::string::npos)
+ return std::string(url, b, (e - b));
+ return std::string(url, b);
+}
+
+namespace {
+
+// Parsing states for UrlUtilities::Unescape
+enum UnescapeState {
+ NORMAL, // We are not in the middle of parsing an escape.
+ ESCAPE1, // We just parsed % .
+ ESCAPE2 // We just parsed %X for some hex digit X.
+};
+
+} // namespace
+
+std::string UrlUtilities::Unescape(const std::string& escaped_url) {
+ std::string unescaped_url, escape_text;
+ int escape_value;
+ UnescapeState state = NORMAL;
+ std::string::const_iterator iter = escaped_url.begin();
+ while (iter < escaped_url.end()) {
+ char c = *iter;
+ switch (state) {
+ case NORMAL:
+ if (c == '%') {
+ escape_text.clear();
+ state = ESCAPE1;
+ } else {
+ unescaped_url.push_back(c);
+ }
+ ++iter;
+ break;
+ case ESCAPE1:
+ if (IsHexDigit(c)) {
+ escape_text.push_back(c);
+ state = ESCAPE2;
+ ++iter;
+ } else {
+ // Unexpected, % followed by non-hex chars, pass it through.
+ unescaped_url.push_back('%');
+ state = NORMAL;
+ }
+ break;
+ case ESCAPE2:
+ if (IsHexDigit(c)) {
+ escape_text.push_back(c);
+ bool ok = base::HexStringToInt(escape_text, &escape_value);
+ DCHECK(ok);
+ unescaped_url.push_back(static_cast<unsigned char>(escape_value));
+ state = NORMAL;
+ ++iter;
+ } else {
+ // Unexpected, % followed by non-hex chars, pass it through.
+ unescaped_url.push_back('%');
+ unescaped_url.append(escape_text);
+ state = NORMAL;
+ }
+ break;
+ }
+ }
+ // Unexpected, % followed by end of string, pass it through.
+ if (state == ESCAPE1 || state == ESCAPE2) {
+ unescaped_url.push_back('%');
+ unescaped_url.append(escape_text);
+ }
+ return unescaped_url;
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/dump_cache/url_utilities.h b/chromium/net/tools/dump_cache/url_utilities.h
new file mode 100644
index 00000000000..c9d8ea5f8e6
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_utilities.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 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_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+#define NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
+
+#include <string>
+
+namespace net {
+
+struct UrlUtilities {
+ // Gets the host from an url, strips the port number as well if the url
+ // has one.
+ // For example: calling GetUrlHost(www.foo.com:8080/boo) returns www.foo.com
+ static std::string GetUrlHost(const std::string& url);
+
+ // Get the host + path portion of an url
+ // e.g http://www.foo.com/path
+ // returns www.foo.com/path
+ static std::string GetUrlHostPath(const std::string& url);
+
+ // Gets the path portion of an url.
+ // e.g http://www.foo.com/path
+ // returns /path
+ static std::string GetUrlPath(const std::string& url);
+
+ // Unescape a url, converting all %XX to the the actual char 0xXX.
+ // For example, this will convert "foo%21bar" to "foo!bar".
+ static std::string Unescape(const std::string& escaped_url);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
diff --git a/chromium/net/tools/dump_cache/url_utilities_unittest.cc b/chromium/net/tools/dump_cache/url_utilities_unittest.cc
new file mode 100644
index 00000000000..3220472b901
--- /dev/null
+++ b/chromium/net/tools/dump_cache/url_utilities_unittest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2010 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/tools/dump_cache/url_utilities.h"
+
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+TEST(UrlUtilitiesTest, GetUrlHost) {
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("http://www.foo.com"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("http://www.foo.com:80"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("http://www.foo.com:80/"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("http://www.foo.com/news"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("www.foo.com:80/news?q=hello"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("www.foo.com/news?q=a:b"));
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHost("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlHostPath) {
+ EXPECT_EQ("www.foo.com",
+ UrlUtilities::GetUrlHostPath("http://www.foo.com"));
+ EXPECT_EQ("www.foo.com:80",
+ UrlUtilities::GetUrlHostPath("http://www.foo.com:80"));
+ EXPECT_EQ("www.foo.com:80/",
+ UrlUtilities::GetUrlHostPath("http://www.foo.com:80/"));
+ EXPECT_EQ("www.foo.com/news",
+ UrlUtilities::GetUrlHostPath("http://www.foo.com/news"));
+ EXPECT_EQ("www.foo.com:80/news?q=hello",
+ UrlUtilities::GetUrlHostPath("www.foo.com:80/news?q=hello"));
+ EXPECT_EQ("www.foo.com/news?q=a:b",
+ UrlUtilities::GetUrlHostPath("www.foo.com/news?q=a:b"));
+ EXPECT_EQ("www.foo.com:80",
+ UrlUtilities::GetUrlHostPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, GetUrlPath) {
+ EXPECT_EQ("/",
+ UrlUtilities::GetUrlPath("http://www.foo.com"));
+ EXPECT_EQ("/",
+ UrlUtilities::GetUrlPath("http://www.foo.com:80"));
+ EXPECT_EQ("/",
+ UrlUtilities::GetUrlPath("http://www.foo.com:80/"));
+ EXPECT_EQ("/news",
+ UrlUtilities::GetUrlPath("http://www.foo.com/news"));
+ EXPECT_EQ("/news?q=hello",
+ UrlUtilities::GetUrlPath("www.foo.com:80/news?q=hello"));
+ EXPECT_EQ("/news?q=a:b",
+ UrlUtilities::GetUrlPath("www.foo.com/news?q=a:b"));
+ EXPECT_EQ("/",
+ UrlUtilities::GetUrlPath("www.foo.com:80"));
+}
+
+TEST(UrlUtilitiesTest, Unescape) {
+ // Basic examples are left alone.
+ EXPECT_EQ("http://www.foo.com",
+ UrlUtilities::Unescape("http://www.foo.com"));
+ EXPECT_EQ("www.foo.com:80/news?q=hello",
+ UrlUtilities::Unescape("www.foo.com:80/news?q=hello"));
+
+ // All chars can be unescaped.
+ EXPECT_EQ("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/",
+ UrlUtilities::Unescape("%7E%60%21%40%23%24%25%5E%26%2A%28%29%5F%2D"
+ "%2B%3D%7B%5B%7D%5D%7C%5C%3A%3B%22%27%3C%2C"
+ "%3E%2E%3F%2F"));
+ for (int c = 0; c < 256; ++c) {
+ std::string unescaped_char(1, implicit_cast<unsigned char>(c));
+ std::string escaped_char = base::StringPrintf("%%%02X", c);
+ EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+ << "escaped_char = " << escaped_char;
+ escaped_char = base::StringPrintf("%%%02x", c);
+ EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char))
+ << "escaped_char = " << escaped_char;
+ }
+
+ // All non-% chars are left alone.
+ EXPECT_EQ("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/",
+ UrlUtilities::Unescape("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/"));
+ for (int c = 0; c < 256; ++c) {
+ if (c != '%') {
+ std::string just_char(1, implicit_cast<unsigned char>(c));
+ EXPECT_EQ(just_char, UrlUtilities::Unescape(just_char));
+ }
+ }
+
+ // Some examples to unescape.
+ EXPECT_EQ("Hello, world!", UrlUtilities::Unescape("Hello%2C world%21"));
+
+ // Not actually escapes.
+ EXPECT_EQ("%", UrlUtilities::Unescape("%"));
+ EXPECT_EQ("%www", UrlUtilities::Unescape("%www"));
+ EXPECT_EQ("%foo", UrlUtilities::Unescape("%foo"));
+ EXPECT_EQ("%1", UrlUtilities::Unescape("%1"));
+ EXPECT_EQ("%1x", UrlUtilities::Unescape("%1x"));
+ EXPECT_EQ("%%", UrlUtilities::Unescape("%%"));
+ // Escapes following non-escapes.
+ EXPECT_EQ("%!", UrlUtilities::Unescape("%%21"));
+ EXPECT_EQ("%2!", UrlUtilities::Unescape("%2%21"));
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/fetch/fetch_client.cc b/chromium/net/tools/fetch/fetch_client.cc
new file mode 100644
index 00000000000..12fae24399a
--- /dev/null
+++ b/chromium/net/tools/fetch/fetch_client.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 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.
+
+#include "build/build_config.h"
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/stats_counters.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_stream_factory.h"
+#include "net/http/http_transaction.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+
+void usage(const char* program_name) {
+ printf("usage: %s --url=<url> [--n=<clients>] [--stats] [--use_cache]\n",
+ program_name);
+ exit(1);
+}
+
+// Test Driver
+class Driver {
+ public:
+ Driver()
+ : clients_(0) {}
+
+ void ClientStarted() { clients_++; }
+ void ClientStopped() {
+ if (!--clients_) {
+ base::MessageLoop::current()->Quit();
+ }
+ }
+
+ private:
+ int clients_;
+};
+
+static base::LazyInstance<Driver> g_driver = LAZY_INSTANCE_INITIALIZER;
+
+// A network client
+class Client {
+ public:
+ Client(net::HttpTransactionFactory* factory, const std::string& url) :
+ url_(url),
+ buffer_(new net::IOBuffer(kBufferSize)) {
+ int rv = factory->CreateTransaction(
+ net::DEFAULT_PRIORITY, &transaction_, NULL);
+ DCHECK_EQ(net::OK, rv);
+ buffer_->AddRef();
+ g_driver.Get().ClientStarted();
+ request_info_.url = url_;
+ request_info_.method = "GET";
+ int state = transaction_->Start(
+ &request_info_,
+ base::Bind(&Client::OnConnectComplete, base::Unretained(this)),
+ net::BoundNetLog());
+ DCHECK(state == net::ERR_IO_PENDING);
+ };
+
+ private:
+ void OnConnectComplete(int result) {
+ // Do work here.
+ int state = transaction_->Read(
+ buffer_.get(), kBufferSize,
+ base::Bind(&Client::OnReadComplete, base::Unretained(this)));
+ if (state == net::ERR_IO_PENDING)
+ return; // IO has started.
+ if (state < 0)
+ return; // ERROR!
+ OnReadComplete(state);
+ }
+
+ void OnReadComplete(int result) {
+ if (result == 0) {
+ OnRequestComplete(result);
+ return;
+ }
+
+ // Deal with received data here.
+ base::StatsCounter bytes_read("FetchClient.bytes_read");
+ bytes_read.Add(result);
+
+ // Issue a read for more data.
+ int state = transaction_->Read(
+ buffer_.get(), kBufferSize,
+ base::Bind(&Client::OnReadComplete, base::Unretained(this)));
+ if (state == net::ERR_IO_PENDING)
+ return; // IO has started.
+ if (state < 0)
+ return; // ERROR!
+ OnReadComplete(state);
+ }
+
+ void OnRequestComplete(int result) {
+ base::StatsCounter requests("FetchClient.requests");
+ requests.Increment();
+ g_driver.Get().ClientStopped();
+ printf(".");
+ }
+
+ static const int kBufferSize = (16 * 1024);
+ GURL url_;
+ net::HttpRequestInfo request_info_;
+ scoped_ptr<net::HttpTransaction> transaction_;
+ scoped_refptr<net::IOBuffer> buffer_;
+};
+
+int main(int argc, char** argv) {
+ base::AtExitManager exit;
+ base::StatsTable table("fetchclient", 50, 1000);
+ table.set_current(&table);
+
+ CommandLine::Init(argc, argv);
+ const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
+ std::string url = parsed_command_line.GetSwitchValueASCII("url");
+ if (!url.length())
+ usage(argv[0]);
+ int client_limit = 1;
+ if (parsed_command_line.HasSwitch("n")) {
+ base::StringToInt(parsed_command_line.GetSwitchValueASCII("n"),
+ &client_limit);
+ }
+ bool use_cache = parsed_command_line.HasSwitch("use-cache");
+
+ // Do work here.
+ base::MessageLoop loop(base::MessageLoop::TYPE_IO);
+
+ net::HttpStreamFactory::EnableNpnHttp2Draft04();
+
+ scoped_ptr<net::HostResolver> host_resolver(
+ net::HostResolver::CreateDefaultResolver(NULL));
+ scoped_ptr<net::CertVerifier> cert_verifier(
+ net::CertVerifier::CreateDefault());
+ scoped_ptr<net::TransportSecurityState> transport_security_state(
+ new net::TransportSecurityState);
+ scoped_ptr<net::ProxyService> proxy_service(
+ net::ProxyService::CreateDirect());
+ scoped_refptr<net::SSLConfigService> ssl_config_service(
+ new net::SSLConfigServiceDefaults);
+ net::HttpTransactionFactory* factory = NULL;
+ scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory(
+ net::HttpAuthHandlerFactory::CreateDefault(host_resolver.get()));
+ net::HttpServerPropertiesImpl http_server_properties;
+
+ net::HttpNetworkSession::Params session_params;
+ session_params.host_resolver = host_resolver.get();
+ session_params.cert_verifier = cert_verifier.get();
+ session_params.transport_security_state = transport_security_state.get();
+ session_params.proxy_service = proxy_service.get();
+ session_params.http_auth_handler_factory = http_auth_handler_factory.get();
+ session_params.http_server_properties = http_server_properties.GetWeakPtr();
+ session_params.ssl_config_service = ssl_config_service.get();
+
+ scoped_refptr<net::HttpNetworkSession> network_session(
+ new net::HttpNetworkSession(session_params));
+ if (use_cache) {
+ factory = new net::HttpCache(network_session.get(),
+ net::HttpCache::DefaultBackend::InMemory(0));
+ } else {
+ factory = new net::HttpNetworkLayer(network_session.get());
+ }
+
+ {
+ base::StatsCounterTimer driver_time("FetchClient.total_time");
+ base::StatsScope<base::StatsCounterTimer> scope(driver_time);
+
+ Client** clients = new Client*[client_limit];
+ for (int i = 0; i < client_limit; i++)
+ clients[i] = new Client(factory, url);
+
+ base::MessageLoop::current()->Run();
+ }
+
+ // Print Statistics here.
+ int num_clients = table.GetCounterValue("c:FetchClient.requests");
+ int test_time = table.GetCounterValue("t:FetchClient.total_time");
+ int bytes_read = table.GetCounterValue("c:FetchClient.bytes_read");
+
+ printf("\n");
+ printf("Clients : %d\n", num_clients);
+ printf("Time : %dms\n", test_time);
+ printf("Bytes Read : %d\n", bytes_read);
+ if (test_time > 0) {
+ const char *units = "bps";
+ double bps = static_cast<float>(bytes_read * 8) /
+ (static_cast<float>(test_time) / 1000.0);
+
+ if (bps > (1024*1024)) {
+ bps /= (1024*1024);
+ units = "Mbps";
+ } else if (bps > 1024) {
+ bps /= 1024;
+ units = "Kbps";
+ }
+ printf("Bandwidth : %.2f%s\n", bps, units);
+ }
+
+ if (parsed_command_line.HasSwitch("stats")) {
+ // Dump the stats table.
+ printf("<stats>\n");
+ int counter_max = table.GetMaxCounters();
+ for (int index = 0; index < counter_max; index++) {
+ std::string name(table.GetRowName(index));
+ if (name.length() > 0) {
+ int value = table.GetRowValue(index);
+ printf("%s:\t%d\n", name.c_str(), value);
+ }
+ }
+ printf("</stats>\n");
+ }
+ return 0;
+}
diff --git a/chromium/net/tools/fetch/fetch_server.cc b/chromium/net/tools/fetch/fetch_server.cc
new file mode 100644
index 00000000000..a2ffc5b5708
--- /dev/null
+++ b/chromium/net/tools/fetch/fetch_server.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 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 "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/stats_counters.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/winsock_init.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_transaction.h"
+#include "net/proxy/proxy_service.h"
+#include "net/tools/fetch/http_server.h"
+
+void usage(const char* program_name) {
+ printf("usage: %s\n", program_name);
+ exit(-1);
+}
+
+int main(int argc, char**argv) {
+ base::AtExitManager exit;
+ base::StatsTable table("fetchserver", 50, 1000);
+ table.set_current(&table);
+
+#if defined(OS_WIN)
+ net::EnsureWinsockInit();
+#endif // defined(OS_WIN)
+
+ CommandLine::Init(0, NULL);
+ const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
+
+ // Do work here.
+ base::MessageLoop loop;
+ HttpServer server(std::string(),
+ 80); // TODO(mbelshe): make port configurable
+ base::MessageLoop::current()->Run();
+
+ if (parsed_command_line.HasSwitch("stats")) {
+ // Dump the stats table.
+ printf("<stats>\n");
+ int counter_max = table.GetMaxCounters();
+ for (int index=0; index < counter_max; index++) {
+ std::string name(table.GetRowName(index));
+ if (name.length() > 0) {
+ int value = table.GetRowValue(index);
+ printf("%s:\t%d\n", name.c_str(), value);
+ }
+ }
+ printf("</stats>\n");
+ }
+
+}
diff --git a/chromium/net/tools/fetch/http_listen_socket.cc b/chromium/net/tools/fetch/http_listen_socket.cc
new file mode 100644
index 00000000000..10b601eeca2
--- /dev/null
+++ b/chromium/net/tools/fetch/http_listen_socket.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 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.
+
+#include "net/tools/fetch/http_listen_socket.h"
+
+#include <map>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/tools/fetch/http_server_request_info.h"
+#include "net/tools/fetch/http_server_response_info.h"
+
+HttpListenSocket::HttpListenSocket(SocketDescriptor s,
+ HttpListenSocket::Delegate* delegate)
+ : net::TCPListenSocket(s, this),
+ delegate_(delegate) {
+}
+
+HttpListenSocket::~HttpListenSocket() {
+}
+
+void HttpListenSocket::Accept() {
+ SocketDescriptor conn = net::TCPListenSocket::AcceptSocket();
+ DCHECK_NE(conn, net::TCPListenSocket::kInvalidSocket);
+ if (conn == net::TCPListenSocket::kInvalidSocket) {
+ // TODO
+ } else {
+ scoped_refptr<HttpListenSocket> sock(
+ new HttpListenSocket(conn, delegate_));
+ // It's up to the delegate to AddRef if it wants to keep it around.
+ DidAccept(this, sock.get());
+ }
+}
+
+// static
+scoped_refptr<HttpListenSocket> HttpListenSocket::CreateAndListen(
+ const std::string& ip,
+ int port,
+ HttpListenSocket::Delegate* delegate) {
+ SocketDescriptor s = net::TCPListenSocket::CreateAndBind(ip, port);
+ if (s == net::TCPListenSocket::kInvalidSocket) {
+ // TODO (ibrar): error handling.
+ } else {
+ scoped_refptr<HttpListenSocket> serv = new HttpListenSocket(s, delegate);
+ serv->Listen();
+ return serv;
+ }
+ return NULL;
+}
+
+//
+// HTTP Request Parser
+// This HTTP request parser uses a simple state machine to quickly parse
+// through the headers. The parser is not 100% complete, as it is designed
+// for use in this simple test driver.
+//
+// Known issues:
+// - does not handle whitespace on first HTTP line correctly. Expects
+// a single space between the method/url and url/protocol.
+
+// Input character types.
+enum header_parse_inputs {
+ INPUT_SPACE,
+ INPUT_CR,
+ INPUT_LF,
+ INPUT_COLON,
+ INPUT_DEFAULT,
+ MAX_INPUTS
+};
+
+// Parser states.
+enum header_parse_states {
+ ST_METHOD, // Receiving the method.
+ ST_URL, // Receiving the URL.
+ ST_PROTO, // Receiving the protocol.
+ ST_HEADER, // Starting a Request Header.
+ ST_NAME, // Receiving a request header name.
+ ST_SEPARATOR, // Receiving the separator between header name and value.
+ ST_VALUE, // Receiving a request header value.
+ ST_DONE, // Parsing is complete and successful.
+ ST_ERR, // Parsing encountered invalid syntax.
+ MAX_STATES
+};
+
+// State transition table.
+int parser_state[MAX_STATES][MAX_INPUTS] = {
+/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_METHOD },
+/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_URL },
+/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_PROTO },
+/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR },
+/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_SEPARATOR, ST_NAME },
+/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_SEPARATOR, ST_VALUE },
+/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_VALUE },
+/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE },
+/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR }
+};
+
+// Convert an input character to the parser's input token.
+int charToInput(char ch) {
+ switch(ch) {
+ case ' ':
+ return INPUT_SPACE;
+ case '\r':
+ return INPUT_CR;
+ case '\n':
+ return INPUT_LF;
+ case ':':
+ return INPUT_COLON;
+ }
+ return INPUT_DEFAULT;
+}
+
+HttpServerRequestInfo* HttpListenSocket::ParseHeaders() {
+ int pos = 0;
+ int data_len = recv_data_.length();
+ int state = ST_METHOD;
+ HttpServerRequestInfo* info = new HttpServerRequestInfo();
+ std::string buffer;
+ std::string header_name;
+ std::string header_value;
+ while (pos < data_len) {
+ char ch = recv_data_[pos++];
+ int input = charToInput(ch);
+ int next_state = parser_state[state][input];
+
+ bool transition = (next_state != state);
+ if (transition) {
+ // Do any actions based on state transitions.
+ switch (state) {
+ case ST_METHOD:
+ info->method = buffer;
+ buffer.clear();
+ break;
+ case ST_URL:
+ info->url = GURL(buffer);
+ buffer.clear();
+ break;
+ case ST_PROTO:
+ // TODO(mbelshe): Deal better with parsing protocol.
+ DCHECK(buffer == "HTTP/1.1");
+ buffer.clear();
+ break;
+ case ST_NAME:
+ header_name = buffer;
+ buffer.clear();
+ break;
+ case ST_VALUE:
+ header_value = buffer;
+ // TODO(mbelshe): Deal better with duplicate headers.
+ DCHECK(info->headers.find(header_name) == info->headers.end());
+ info->headers[header_name] = header_value;
+ buffer.clear();
+ break;
+ }
+ state = next_state;
+ } else {
+ // Do any actions based on current state.
+ switch (state) {
+ case ST_METHOD:
+ case ST_URL:
+ case ST_PROTO:
+ case ST_VALUE:
+ case ST_NAME:
+ buffer.append(&ch, 1);
+ break;
+ case ST_DONE:
+ recv_data_ = recv_data_.substr(pos);
+ return info;
+ case ST_ERR:
+ delete info;
+ return NULL;
+ }
+ }
+ }
+ // No more characters, but we haven't finished parsing yet.
+ delete info;
+ return NULL;
+}
+
+void HttpListenSocket::DidAccept(net::StreamListenSocket* server,
+ net::StreamListenSocket* connection) {
+ connection->AddRef();
+}
+
+void HttpListenSocket::DidRead(net::StreamListenSocket* connection,
+ const char* data,
+ int len) {
+ recv_data_.append(data, len);
+ while (recv_data_.length()) {
+ HttpServerRequestInfo* request = ParseHeaders();
+ if (!request)
+ break;
+ delegate_->OnRequest(this, request);
+ delete request;
+ }
+}
+
+void HttpListenSocket::DidClose(net::StreamListenSocket* sock) {
+ sock->Release();
+}
+
+// Convert the numeric status code to a string.
+// e.g. 200 -> "200 OK".
+std::string ServerStatus(int code) {
+ switch(code) {
+ case 200:
+ return std::string("200 OK");
+ // TODO(mbelshe): handle other codes.
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+void HttpListenSocket::Respond(HttpServerResponseInfo* info,
+ std::string& data) {
+ std::string response;
+
+ // Status line.
+ response = info->protocol + " ";
+ response += ServerStatus(info->status);
+ response += "\r\n";
+
+ // Standard headers.
+ if (info->content_type.length())
+ response += "Content-type: " + info->content_type + "\r\n";
+
+ if (info->content_length > 0)
+ response += "Content-length: " + base::IntToString(info->content_length) +
+ "\r\n";
+
+ if (info->connection_close)
+ response += "Connection: close\r\n";
+
+ // TODO(mbelshe): support additional headers.
+
+ // End of headers.
+ response += "\r\n";
+
+ // Add data.
+ response += data;
+
+ // Write it all out.
+ this->Send(response, false);
+}
diff --git a/chromium/net/tools/fetch/http_listen_socket.h b/chromium/net/tools/fetch/http_listen_socket.h
new file mode 100644
index 00000000000..379f73cfda7
--- /dev/null
+++ b/chromium/net/tools/fetch/http_listen_socket.h
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#ifndef NET_BASE_TOOLS_HTTP_LISTEN_SOCKET_H_
+#define NET_BASE_TOOLS_HTTP_LISTEN_SOCKET_H_
+
+#include "base/message_loop/message_loop.h"
+#include "net/socket/stream_listen_socket.h"
+#include "net/socket/tcp_listen_socket.h"
+
+class HttpServerRequestInfo;
+class HttpServerResponseInfo;
+
+// Implements a simple HTTP listen socket on top of the raw socket interface.
+class HttpListenSocket : public net::TCPListenSocket,
+ public net::StreamListenSocket::Delegate {
+ public:
+ class Delegate {
+ public:
+ virtual void OnRequest(HttpListenSocket* connection,
+ HttpServerRequestInfo* info) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ static scoped_refptr<HttpListenSocket> CreateAndListen(
+ const std::string& ip, int port, HttpListenSocket::Delegate* delegate);
+
+ // Send a server response.
+ // TODO(mbelshe): make this capable of non-ascii data.
+ void Respond(HttpServerResponseInfo* info, std::string& data);
+
+ // StreamListenSocket::Delegate.
+ virtual void DidAccept(net::StreamListenSocket* server,
+ net::StreamListenSocket* connection) OVERRIDE;
+ virtual void DidRead(net::StreamListenSocket* connection,
+ const char* data, int len) OVERRIDE;
+ virtual void DidClose(net::StreamListenSocket* sock) OVERRIDE;
+
+ protected:
+ // Overrides TCPListenSocket::Accept().
+ virtual void Accept() OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<net::StreamListenSocket>;
+
+ static const int kReadBufSize = 16 * 1024;
+
+ // Must run in the IO thread.
+ HttpListenSocket(SocketDescriptor s, HttpListenSocket::Delegate* del);
+ virtual ~HttpListenSocket();
+
+ // Expects the raw data to be stored in recv_data_. If parsing is successful,
+ // will remove the data parsed from recv_data_, leaving only the unused
+ // recv data.
+ HttpServerRequestInfo* ParseHeaders();
+
+ HttpListenSocket::Delegate* const delegate_;
+ std::string recv_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpListenSocket);
+};
+
+#endif // NET_BASE_TOOLS_HTTP_LISTEN_SOCKET_H_
diff --git a/chromium/net/tools/fetch/http_server.cc b/chromium/net/tools/fetch/http_server.cc
new file mode 100644
index 00000000000..71afb67bc30
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 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.
+
+#include "net/tools/fetch/http_server.h"
+
+HttpServer::HttpServer(std::string ip, int port)
+ : session_(new HttpSession(ip, port)) {
+}
+
+HttpServer::~HttpServer() {
+}
diff --git a/chromium/net/tools/fetch/http_server.h b/chromium/net/tools/fetch/http_server.h
new file mode 100644
index 00000000000..691413078e5
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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_BASE_TOOLS_HTTP_SERVER_H_
+#define NET_BASE_TOOLS_HTTP_SERVER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/tools/fetch/http_session.h"
+
+// Implements a simple, single-threaded HttpServer.
+// Right now, this class implements no functionality above and beyond
+// the HttpSession.
+class HttpServer {
+public:
+ HttpServer(std::string ip, int port);
+ ~HttpServer();
+
+private:
+ scoped_ptr<HttpSession> session_;
+ DISALLOW_COPY_AND_ASSIGN(HttpServer);
+};
+
+#endif // NET_BASE_TOOLS_HTTP_SERVER_H_
+
diff --git a/chromium/net/tools/fetch/http_server_request_info.cc b/chromium/net/tools/fetch/http_server_request_info.cc
new file mode 100644
index 00000000000..52b6860e0a4
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server_request_info.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2011 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/tools/fetch/http_server_request_info.h"
+
+HttpServerRequestInfo::HttpServerRequestInfo()
+ : net::HttpRequestInfo() {
+}
+
+HttpServerRequestInfo::~HttpServerRequestInfo() {}
diff --git a/chromium/net/tools/fetch/http_server_request_info.h b/chromium/net/tools/fetch/http_server_request_info.h
new file mode 100644
index 00000000000..9b0fae18d16
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server_request_info.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
+#define NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
+
+#include <map>
+#include <string>
+
+#include "net/http/http_request_info.h"
+
+// Meta information about an HTTP request.
+// This is geared toward servers in that it keeps a map of the headers and
+// values rather than just a list of header strings (which net::HttpRequestInfo
+// does).
+class HttpServerRequestInfo : public net::HttpRequestInfo {
+ public:
+ HttpServerRequestInfo();
+ virtual ~HttpServerRequestInfo();
+
+ // A map of the names -> values for HTTP headers.
+ std::map<std::string, std::string> headers;
+};
+
+#endif // NET_BASE_TOOLS_HTTP_SERVER_REQUEST_INFO_H_
diff --git a/chromium/net/tools/fetch/http_server_response_info.cc b/chromium/net/tools/fetch/http_server_response_info.cc
new file mode 100644
index 00000000000..d9ca02a6d0d
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server_response_info.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 2011 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/tools/fetch/http_server_response_info.h"
+
+HttpServerResponseInfo::HttpServerResponseInfo()
+ : status(200), content_length(0), connection_close(false) {
+}
+
+HttpServerResponseInfo::~HttpServerResponseInfo() {}
diff --git a/chromium/net/tools/fetch/http_server_response_info.h b/chromium/net/tools/fetch/http_server_response_info.h
new file mode 100644
index 00000000000..e01f7300d97
--- /dev/null
+++ b/chromium/net/tools/fetch/http_server_response_info.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 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_HTTP_HTTP_RESPONSE_INFO_H_
+#define NET_HTTP_HTTP_RESPONSE_INFO_H_
+
+#include <map>
+#include <string>
+
+// Meta information about a server response.
+class HttpServerResponseInfo {
+ public:
+ HttpServerResponseInfo();
+ ~HttpServerResponseInfo();
+
+ // The response protocol.
+ std::string protocol;
+
+ // The status code.
+ int status;
+
+ // The server identifier.
+ std::string server_name;
+
+ // The content type.
+ std::string content_type;
+
+ // The content length.
+ int content_length;
+
+ // Should we close the connection.
+ bool connection_close;
+
+ // Additional response headers.
+ std::map<std::string, std::string> headers;
+};
+
+#endif // NET_HTTP_HTTP_RESPONSE_INFO_H_
diff --git a/chromium/net/tools/fetch/http_session.cc b/chromium/net/tools/fetch/http_session.cc
new file mode 100644
index 00000000000..d9e991b798a
--- /dev/null
+++ b/chromium/net/tools/fetch/http_session.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#include "net/tools/fetch/http_session.h"
+#include "net/tools/fetch/http_server_response_info.h"
+
+HttpSession::HttpSession(const std::string& ip, int port)
+ : socket_(HttpListenSocket::CreateAndListen(ip, port, this)) {
+}
+
+HttpSession::~HttpSession() {
+}
+
+void HttpSession::OnRequest(HttpListenSocket* connection,
+ HttpServerRequestInfo* info) {
+ // TODO(mbelshe): Make this function more interesting.
+
+ // Generate a 10KB sequence of data.
+ CR_DEFINE_STATIC_LOCAL(std::string, data, ());
+ if (data.length() == 0) {
+ while (data.length() < (10 * 1024))
+ data += 'a' + (rand() % 26);
+ }
+
+ HttpServerResponseInfo response_info;
+ response_info.protocol = "HTTP/1.1";
+ response_info.status = 200;
+ response_info.content_type = "text/plain";
+ response_info.content_length = data.length();
+
+ connection->Respond(&response_info, data);
+}
diff --git a/chromium/net/tools/fetch/http_session.h b/chromium/net/tools/fetch/http_session.h
new file mode 100644
index 00000000000..7d87e05c70b
--- /dev/null
+++ b/chromium/net/tools/fetch/http_session.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 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_BASE_TOOLS_HTTP_SESSION_H_
+#define NET_BASE_TOOLS_HTTP_SESSION_H_
+
+#include "base/basictypes.h"
+#include "net/http/http_request_info.h"
+#include "net/tools/fetch/http_listen_socket.h"
+
+// An HttpSession encapsulates a server-side HTTP listen socket.
+class HttpSession : HttpListenSocket::Delegate {
+ public:
+ HttpSession(const std::string& ip, int port);
+ virtual ~HttpSession();
+
+ virtual void OnRequest(HttpListenSocket* connection,
+ HttpServerRequestInfo* info) OVERRIDE;
+
+ private:
+ scoped_refptr<HttpListenSocket> socket_;
+ DISALLOW_COPY_AND_ASSIGN(HttpSession);
+};
+
+#endif // NET_BASE_TOOLS_HTTP_SESSION_H_
+
diff --git a/chromium/net/tools/flip_server/acceptor_thread.cc b/chromium/net/tools/flip_server/acceptor_thread.cc
new file mode 100644
index 00000000000..ed8a3ec86f3
--- /dev/null
+++ b/chromium/net/tools/flip_server/acceptor_thread.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 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.
+
+#include "net/tools/flip_server/acceptor_thread.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h> // For TCP_NODELAY
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/spdy_ssl.h"
+#include "openssl/err.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+SMAcceptorThread::SMAcceptorThread(FlipAcceptor *acceptor,
+ MemoryCache* memory_cache)
+ : SimpleThread("SMAcceptorThread"),
+ acceptor_(acceptor),
+ ssl_state_(NULL),
+ use_ssl_(false),
+ idle_socket_timeout_s_(acceptor->idle_socket_timeout_s_),
+ quitting_(false),
+ memory_cache_(memory_cache) {
+ if (!acceptor->ssl_cert_filename_.empty() &&
+ !acceptor->ssl_key_filename_.empty()) {
+ ssl_state_ = new SSLState;
+ bool use_npn = true;
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ use_npn = false;
+ }
+ InitSSL(ssl_state_,
+ acceptor_->ssl_cert_filename_,
+ acceptor_->ssl_key_filename_,
+ use_npn,
+ acceptor_->ssl_session_expiry_,
+ acceptor_->ssl_disable_compression_);
+ use_ssl_ = true;
+ }
+}
+
+SMAcceptorThread::~SMAcceptorThread() {
+ for (std::vector<SMConnection*>::iterator i =
+ allocated_server_connections_.begin();
+ i != allocated_server_connections_.end();
+ ++i) {
+ delete *i;
+ }
+ delete ssl_state_;
+}
+
+SMConnection* SMAcceptorThread::NewConnection() {
+ SMConnection* server =
+ SMConnection::NewSMConnection(&epoll_server_, ssl_state_,
+ memory_cache_, acceptor_,
+ "client_conn: ");
+ allocated_server_connections_.push_back(server);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Making new server.";
+ return server;
+}
+
+SMConnection* SMAcceptorThread::FindOrMakeNewSMConnection() {
+ if (unused_server_connections_.empty()) {
+ return NewConnection();
+ }
+ SMConnection* server = unused_server_connections_.back();
+ unused_server_connections_.pop_back();
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Reusing server.";
+ return server;
+}
+
+void SMAcceptorThread::InitWorker() {
+ epoll_server_.RegisterFD(acceptor_->listen_fd_, this, EPOLLIN | EPOLLET);
+}
+
+void SMAcceptorThread::HandleConnection(int server_fd,
+ struct sockaddr_in *remote_addr) {
+ int on = 1;
+ int rc;
+ if (acceptor_->disable_nagle_) {
+ rc = setsockopt(server_fd, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<char*>(&on), sizeof(on));
+ if (rc < 0) {
+ close(server_fd);
+ LOG(ERROR) << "setsockopt() failed fd=" << server_fd;
+ return;
+ }
+ }
+
+ SMConnection* server_connection = FindOrMakeNewSMConnection();
+ if (server_connection == NULL) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Closing fd " << server_fd;
+ close(server_fd);
+ return;
+ }
+ std::string remote_ip = inet_ntoa(remote_addr->sin_addr);
+ server_connection->InitSMConnection(this,
+ NULL,
+ &epoll_server_,
+ server_fd,
+ std::string(),
+ std::string(),
+ remote_ip,
+ use_ssl_);
+ if (server_connection->initialized())
+ active_server_connections_.push_back(server_connection);
+}
+
+void SMAcceptorThread::AcceptFromListenFD() {
+ if (acceptor_->accepts_per_wake_ > 0) {
+ for (int i = 0; i < acceptor_->accepts_per_wake_; ++i) {
+ struct sockaddr address;
+ socklen_t socklen = sizeof(address);
+ int fd = accept(acceptor_->listen_fd_, &address, &socklen);
+ if (fd == -1) {
+ if (errno != 11) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: accept fail("
+ << acceptor_->listen_fd_ << "): " << errno << ": "
+ << strerror(errno);
+ }
+ break;
+ }
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << " Accepted connection";
+ HandleConnection(fd, (struct sockaddr_in *)&address);
+ }
+ } else {
+ while (true) {
+ struct sockaddr address;
+ socklen_t socklen = sizeof(address);
+ int fd = accept(acceptor_->listen_fd_, &address, &socklen);
+ if (fd == -1) {
+ if (errno != 11) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: accept fail("
+ << acceptor_->listen_fd_ << "): " << errno << ": "
+ << strerror(errno);
+ }
+ break;
+ }
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Accepted connection";
+ HandleConnection(fd, (struct sockaddr_in *)&address);
+ }
+ }
+}
+
+void SMAcceptorThread::HandleConnectionIdleTimeout() {
+ static time_t oldest_time = time(NULL);
+
+ int cur_time = time(NULL);
+ // Only iterate the list if we speculate that a connection is ready to be
+ // expired
+ if ((cur_time - oldest_time) < idle_socket_timeout_s_)
+ return;
+
+ // TODO(mbelshe): This code could be optimized, active_server_connections_
+ // is already in-order.
+ std::list<SMConnection*>::iterator iter = active_server_connections_.begin();
+ while (iter != active_server_connections_.end()) {
+ SMConnection *conn = *iter;
+ int elapsed_time = (cur_time - conn->last_read_time_);
+ if (elapsed_time > idle_socket_timeout_s_) {
+ conn->Cleanup("Connection idle timeout reached.");
+ iter = active_server_connections_.erase(iter);
+ continue;
+ }
+ if (conn->last_read_time_ < oldest_time)
+ oldest_time = conn->last_read_time_;
+ iter++;
+ }
+ if ((cur_time - oldest_time) >= idle_socket_timeout_s_)
+ oldest_time = cur_time;
+}
+
+void SMAcceptorThread::Run() {
+ while (!quitting_.HasBeenNotified()) {
+ epoll_server_.set_timeout_in_us(10 * 1000); // 10 ms
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ if (tmp_unused_server_connections_.size()) {
+ VLOG(2) << "have " << tmp_unused_server_connections_.size()
+ << " additional unused connections. Total = "
+ << unused_server_connections_.size();
+ unused_server_connections_.insert(unused_server_connections_.end(),
+ tmp_unused_server_connections_.begin(),
+ tmp_unused_server_connections_.end());
+ tmp_unused_server_connections_.clear();
+ }
+ HandleConnectionIdleTimeout();
+ }
+}
+
+void SMAcceptorThread::OnEvent(int fd, EpollEvent* event) {
+ if (event->in_events | EPOLLIN) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "Acceptor: Accepting based upon epoll events";
+ AcceptFromListenFD();
+ }
+}
+
+void SMAcceptorThread::SMConnectionDone(SMConnection* sc) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Done with connection.";
+ tmp_unused_server_connections_.push_back(sc);
+}
+
+} // namespace net
+
+
diff --git a/chromium/net/tools/flip_server/acceptor_thread.h b/chromium/net/tools/flip_server/acceptor_thread.h
new file mode 100644
index 00000000000..f967fac94fa
--- /dev/null
+++ b/chromium/net/tools/flip_server/acceptor_thread.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
+#define NET_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/threading/simple_thread.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "openssl/ssl.h"
+
+struct sockaddr_in;
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+class SMConnection;
+struct SSLState;
+
+// TODO(mbelshe): Get rid of this class; we don't need a lock just to set
+// a bool cross threads - especially one which only is set once...
+class Notification {
+ public:
+ explicit Notification(bool value) : value_(value) {}
+
+ void Notify() {
+ base::AutoLock al(lock_);
+ value_ = true;
+ }
+ bool HasBeenNotified() {
+ base::AutoLock al(lock_);
+ return value_;
+ }
+ bool value_;
+ base::Lock lock_;
+};
+
+class SMAcceptorThread : public base::SimpleThread,
+ public EpollCallbackInterface,
+ public SMConnectionPoolInterface {
+ public:
+ SMAcceptorThread(FlipAcceptor *acceptor, MemoryCache* memory_cache);
+ virtual ~SMAcceptorThread();
+
+ // EpollCallbackInteface interface
+ virtual void OnRegistration(EpollServer* eps,
+ int fd,
+ int event_mask) OVERRIDE {}
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {}
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {}
+
+ // SMConnectionPool interface
+ virtual void SMConnectionDone(SMConnection* sc) OVERRIDE;
+
+ // TODO(mbelshe): figure out if we can move these to private functions.
+ SMConnection* NewConnection();
+ SMConnection* FindOrMakeNewSMConnection();
+ void InitWorker();
+ void HandleConnection(int server_fd, struct sockaddr_in *remote_addr);
+ void AcceptFromListenFD();
+
+ // Notify the Accept thread that it is time to terminate.
+ void Quit() { quitting_.Notify(); }
+
+ // Iterates through a list of active connections expiring any that have been
+ // idle longer than the configured timeout.
+ void HandleConnectionIdleTimeout();
+
+ virtual void Run() OVERRIDE;
+
+ private:
+ EpollServer epoll_server_;
+ FlipAcceptor* acceptor_;
+ SSLState* ssl_state_;
+ bool use_ssl_;
+ int idle_socket_timeout_s_;
+
+ std::vector<SMConnection*> unused_server_connections_;
+ std::vector<SMConnection*> tmp_unused_server_connections_;
+ std::vector<SMConnection*> allocated_server_connections_;
+ std::list<SMConnection*> active_server_connections_;
+ Notification quitting_;
+ MemoryCache* memory_cache_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
diff --git a/chromium/net/tools/flip_server/balsa_enums.h b/chromium/net/tools/flip_server/balsa_enums.h
new file mode 100644
index 00000000000..4273ee4871d
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_enums.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_BALSA_ENUMS_H_
+#define NET_TOOLS_FLIP_SERVER_BALSA_ENUMS_H_
+
+namespace net {
+
+struct BalsaFrameEnums {
+ enum ParseState {
+ PARSE_ERROR,
+ READING_HEADER_AND_FIRSTLINE,
+ READING_CHUNK_LENGTH,
+ READING_CHUNK_EXTENSION,
+ READING_CHUNK_DATA,
+ READING_CHUNK_TERM,
+ READING_LAST_CHUNK_TERM,
+ READING_TRAILER,
+ READING_UNTIL_CLOSE,
+ READING_CONTENT,
+ MESSAGE_FULLY_READ,
+ NUM_STATES,
+ };
+
+ enum ErrorCode {
+ NO_ERROR = 0, // A sentinel value for convenience, none of the callbacks
+ // should ever see this error code.
+ // Header parsing errors
+ // Note that adding one to many of the REQUEST errors yields the
+ // appropriate RESPONSE error.
+ // Particularly, when parsing the first line of a request or response,
+ // there are three sequences of non-whitespace regardless of whether or
+ // not it is a request or response. These are listed below, in order.
+ //
+ // firstline_a firstline_b firstline_c
+ // REQ: method request_uri version
+ // RESP: version statuscode reason
+ //
+ // As you can see, the first token is the 'method' field for a request,
+ // and 'version' field for a response. We call the first non whitespace
+ // token firstline_a, the second firstline_b, and the third token
+ // followed by [^\r\n]*) firstline_c.
+ //
+ // This organization is important, as it lets us determine the error code
+ // to use without a branch based on is_response. Instead, we simply add
+ // is_response to the response error code-- If is_response is true, then
+ // we'll get the response error code, thanks to the fact that the error
+ // code numbers are organized to ensure that response error codes always
+ // precede request error codes.
+ // | Triggered
+ // | while processing
+ // | this NONWS
+ // | sequence...
+ NO_STATUS_LINE_IN_RESPONSE, // |
+ NO_REQUEST_LINE_IN_REQUEST, // |
+ FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION, // | firstline_a
+ FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD, // | firstline_a
+ FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE, // | firstline_b
+ FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI, // | firstline_b
+ FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE, // | firstline_c
+ FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION, // | firstline_c
+
+ FAILED_CONVERTING_STATUS_CODE_TO_INT,
+ REQUEST_URI_TOO_LONG, // Request URI greater than kMaxUrlLen.
+
+ HEADERS_TOO_LONG,
+ UNPARSABLE_CONTENT_LENGTH,
+ // Warning: there may be a body but there was no content-length/chunked
+ // encoding
+ MAYBE_BODY_BUT_NO_CONTENT_LENGTH,
+
+ // This is used if a body is required for a request.
+ REQUIRED_BODY_BUT_NO_CONTENT_LENGTH,
+
+ HEADER_MISSING_COLON,
+
+ // Chunking errors
+ INVALID_CHUNK_LENGTH,
+ CHUNK_LENGTH_OVERFLOW,
+
+ // Other errors.
+ CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO,
+ CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
+ MULTIPLE_CONTENT_LENGTH_KEYS,
+ MULTIPLE_TRANSFER_ENCODING_KEYS,
+ UNKNOWN_TRANSFER_ENCODING,
+ INVALID_HEADER_FORMAT,
+
+ // A detected internal inconsistency was found.
+ INTERNAL_LOGIC_ERROR,
+
+ NUM_ERROR_CODES
+ };
+ static const char* ParseStateToString(ParseState error_code);
+ static const char* ErrorCodeToString(ErrorCode error_code);
+};
+
+struct BalsaHeadersEnums {
+ enum ContentLengthStatus {
+ INVALID_CONTENT_LENGTH,
+ CONTENT_LENGTH_OVERFLOW,
+ NO_CONTENT_LENGTH,
+ VALID_CONTENT_LENGTH,
+ };
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BALSA_ENUMS_H_
+
diff --git a/chromium/net/tools/flip_server/balsa_frame.cc b/chromium/net/tools/flip_server/balsa_frame.cc
new file mode 100644
index 00000000000..dead665771b
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_frame.cc
@@ -0,0 +1,1597 @@
+// Copyright (c) 2011 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/tools/flip_server/balsa_frame.h"
+
+#include <assert.h>
+#if __SSE2__
+#include <emmintrin.h>
+#endif // __SSE2__
+#include <strings.h>
+
+#include <limits>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/buffer_interface.h"
+#include "net/tools/flip_server/simple_buffer.h"
+#include "net/tools/flip_server/split.h"
+#include "net/tools/flip_server/string_piece_utils.h"
+
+namespace net {
+
+// Constants holding some header names for headers which can affect the way the
+// HTTP message is framed, and so must be processed specially:
+static const char kContentLength[] = "content-length";
+static const size_t kContentLengthSize = sizeof(kContentLength) - 1;
+static const char kTransferEncoding[] = "transfer-encoding";
+static const size_t kTransferEncodingSize = sizeof(kTransferEncoding) - 1;
+
+BalsaFrame::BalsaFrame()
+ : last_char_was_slash_r_(false),
+ saw_non_newline_char_(false),
+ start_was_space_(true),
+ chunk_length_character_extracted_(false),
+ is_request_(true),
+ request_was_head_(false),
+ max_header_length_(16 * 1024),
+ max_request_uri_length_(2048),
+ visitor_(&do_nothing_visitor_),
+ chunk_length_remaining_(0),
+ content_length_remaining_(0),
+ last_slash_n_loc_(NULL),
+ last_recorded_slash_n_loc_(NULL),
+ last_slash_n_idx_(0),
+ term_chars_(0),
+ parse_state_(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE),
+ last_error_(BalsaFrameEnums::NO_ERROR),
+ headers_(NULL) {
+}
+
+BalsaFrame::~BalsaFrame() {}
+
+void BalsaFrame::Reset() {
+ last_char_was_slash_r_ = false;
+ saw_non_newline_char_ = false;
+ start_was_space_ = true;
+ chunk_length_character_extracted_ = false;
+ // is_request_ = true; // not reset between messages.
+ // request_was_head_ = false; // not reset between messages.
+ // max_header_length_ = 4096; // not reset between messages.
+ // max_request_uri_length_ = 2048; // not reset between messages.
+ // visitor_ = &do_nothing_visitor_; // not reset between messages.
+ chunk_length_remaining_ = 0;
+ content_length_remaining_ = 0;
+ last_slash_n_loc_ = NULL;
+ last_recorded_slash_n_loc_ = NULL;
+ last_slash_n_idx_ = 0;
+ term_chars_ = 0;
+ parse_state_ = BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE;
+ last_error_ = BalsaFrameEnums::NO_ERROR;
+ lines_.clear();
+ if (headers_ != NULL) {
+ headers_->Clear();
+ }
+}
+
+const char* BalsaFrameEnums::ParseStateToString(
+ BalsaFrameEnums::ParseState error_code) {
+ switch (error_code) {
+ case PARSE_ERROR:
+ return "PARSE_ERROR";
+ case READING_HEADER_AND_FIRSTLINE:
+ return "READING_HEADER_AND_FIRSTLINE";
+ case READING_CHUNK_LENGTH:
+ return "READING_CHUNK_LENGTH";
+ case READING_CHUNK_EXTENSION:
+ return "READING_CHUNK_EXTENSION";
+ case READING_CHUNK_DATA:
+ return "READING_CHUNK_DATA";
+ case READING_CHUNK_TERM:
+ return "READING_CHUNK_TERM";
+ case READING_LAST_CHUNK_TERM:
+ return "READING_LAST_CHUNK_TERM";
+ case READING_TRAILER:
+ return "READING_TRAILER";
+ case READING_UNTIL_CLOSE:
+ return "READING_UNTIL_CLOSE";
+ case READING_CONTENT:
+ return "READING_CONTENT";
+ case MESSAGE_FULLY_READ:
+ return "MESSAGE_FULLY_READ";
+ case NUM_STATES:
+ return "UNKNOWN_STATE";
+ }
+ return "UNKNOWN_STATE";
+}
+
+const char* BalsaFrameEnums::ErrorCodeToString(
+ BalsaFrameEnums::ErrorCode error_code) {
+ switch (error_code) {
+ case NO_ERROR:
+ return "NO_ERROR";
+ case NO_STATUS_LINE_IN_RESPONSE:
+ return "NO_STATUS_LINE_IN_RESPONSE";
+ case NO_REQUEST_LINE_IN_REQUEST:
+ return "NO_REQUEST_LINE_IN_REQUEST";
+ case FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION:
+ return "FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION";
+ case FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD:
+ return "FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD";
+ case FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE:
+ return "FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE";
+ case FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI:
+ return "FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI";
+ case FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE:
+ return "FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE";
+ case FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION:
+ return "FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION";
+ case FAILED_CONVERTING_STATUS_CODE_TO_INT:
+ return "FAILED_CONVERTING_STATUS_CODE_TO_INT";
+ case REQUEST_URI_TOO_LONG:
+ return "REQUEST_URI_TOO_LONG";
+ case HEADERS_TOO_LONG:
+ return "HEADERS_TOO_LONG";
+ case UNPARSABLE_CONTENT_LENGTH:
+ return "UNPARSABLE_CONTENT_LENGTH";
+ case MAYBE_BODY_BUT_NO_CONTENT_LENGTH:
+ return "MAYBE_BODY_BUT_NO_CONTENT_LENGTH";
+ case REQUIRED_BODY_BUT_NO_CONTENT_LENGTH:
+ return "REQUIRED_BODY_BUT_NO_CONTENT_LENGTH";
+ case HEADER_MISSING_COLON:
+ return "HEADER_MISSING_COLON";
+ case INVALID_CHUNK_LENGTH:
+ return "INVALID_CHUNK_LENGTH";
+ case CHUNK_LENGTH_OVERFLOW:
+ return "CHUNK_LENGTH_OVERFLOW";
+ case CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO:
+ return "CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO";
+ case CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT:
+ return "CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT";
+ case MULTIPLE_CONTENT_LENGTH_KEYS:
+ return "MULTIPLE_CONTENT_LENGTH_KEYS";
+ case MULTIPLE_TRANSFER_ENCODING_KEYS:
+ return "MULTIPLE_TRANSFER_ENCODING_KEYS";
+ case UNKNOWN_TRANSFER_ENCODING:
+ return "UNKNOWN_TRANSFER_ENCODING";
+ case INVALID_HEADER_FORMAT:
+ return "INVALID_HEADER_FORMAT";
+ case INTERNAL_LOGIC_ERROR:
+ return "INTERNAL_LOGIC_ERROR";
+ case NUM_ERROR_CODES:
+ return "UNKNOWN_ERROR";
+ }
+ return "UNKNOWN_ERROR";
+}
+
+// Summary:
+// Parses the first line of either a request or response.
+// Note that in the case of a detected warning, error_code will be set
+// but the function will not return false.
+// Exactly zero or one warning or error (but not both) may be detected
+// by this function.
+// Note that this function will not write the data of the first-line
+// into the header's buffer (that should already have been done elsewhere).
+//
+// Pre-conditions:
+// begin != end
+// *begin should be a character which is > ' '. This implies that there
+// is at least one non-whitespace characters between [begin, end).
+// headers is a valid pointer to a BalsaHeaders class.
+// error_code is a valid pointer to a BalsaFrameEnums::ErrorCode value.
+// Entire first line must exist between [begin, end)
+// Exactly zero or one newlines -may- exist between [begin, end)
+// [begin, end) should exist in the header's buffer.
+//
+// Side-effects:
+// headers will be modified
+// error_code may be modified if either a warning or error is detected
+//
+// Returns:
+// True if no error (as opposed to warning) is detected.
+// False if an error (as opposed to warning) is detected.
+
+//
+// If there is indeed non-whitespace in the line, then the following
+// will take care of this for you:
+// while (*begin <= ' ') ++begin;
+// ProcessFirstLine(begin, end, is_request, &headers, &error_code);
+//
+bool ParseHTTPFirstLine(const char* begin,
+ const char* end,
+ bool is_request,
+ size_t max_request_uri_length,
+ BalsaHeaders* headers,
+ BalsaFrameEnums::ErrorCode* error_code) {
+ const char* current = begin;
+ // HTTP firstlines all have the following structure:
+ // LWS NONWS LWS NONWS LWS NONWS NOTCRLF CRLF
+ // [\t \r\n]+ [^\t ]+ [\t ]+ [^\t ]+ [\t ]+ [^\t ]+ [^\r\n]+ "\r\n"
+ // ws1 nws1 ws2 nws2 ws3 nws3 ws4
+ // | [-------) [-------) [----------------)
+ // REQ: method request_uri version
+ // RESP: version statuscode reason
+ //
+ // The first NONWS->LWS component we'll call firstline_a.
+ // The second firstline_b, and the third firstline_c.
+ //
+ // firstline_a goes from nws1 to (but not including) ws2
+ // firstline_b goes from nws2 to (but not including) ws3
+ // firstline_c goes from nws3 to (but not including) ws4
+ //
+ // In the code:
+ // ws1 == whitespace_1_idx_
+ // nws1 == non_whitespace_1_idx_
+ // ws2 == whitespace_2_idx_
+ // nws2 == non_whitespace_2_idx_
+ // ws3 == whitespace_3_idx_
+ // nws3 == non_whitespace_3_idx_
+ // ws4 == whitespace_4_idx_
+
+ // Kill all whitespace (including '\r\n') at the end of the line.
+ --end;
+ if (*end != '\n') {
+ *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR;
+ LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n"
+ << headers->OriginalHeadersForDebugging();
+ return false;
+ }
+ while (begin < end && *end <= ' ') {
+ --end;
+ }
+ DCHECK(*end != '\n');
+ if (*end == '\n') {
+ *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR;
+ LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n"
+ << headers->OriginalHeadersForDebugging();
+ return false;
+ }
+ ++end;
+
+ // The two following statements should not be possible.
+ if (end == begin) {
+ *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR;
+ LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n"
+ << headers->OriginalHeadersForDebugging();
+ return false;
+ }
+
+ // whitespace_1_idx_
+ headers->whitespace_1_idx_ = current - begin;
+ // This loop is commented out as it is never used in current code. This is
+ // true only because we don't begin parsing the headers at all until we've
+ // encountered a non whitespace character at the beginning of the stream, at
+ // which point we begin our demarcation of header-start. If we did -not- do
+ // this (for instance, only looked for [\r\n] instead of (< ' ')), this loop
+ // would be necessary for the proper functioning of this parsing.
+ // This is left here as this function may (in the future) be refactored out
+ // of the BalsaFrame class so that it may be shared between code in
+ // BalsaFrame and BalsaHeaders (where it would be used in some variant of the
+ // set_first_line() function (at which point it would be necessary).
+#if 0
+ while (*current <= ' ') {
+ ++current;
+ }
+#endif
+ // non_whitespace_1_idx_
+ headers->non_whitespace_1_idx_ = current - begin;
+ do {
+ // The first time through, we're guaranteed that the current character
+ // won't be a whitespace (else the loop above wouldn't have terminated).
+ // That implies that we're guaranteed to get at least one non-whitespace
+ // character if we get into this loop at all.
+ ++current;
+ if (current == end) {
+ headers->whitespace_2_idx_ = current - begin;
+ headers->non_whitespace_2_idx_ = current - begin;
+ headers->whitespace_3_idx_ = current - begin;
+ headers->non_whitespace_3_idx_ = current - begin;
+ headers->whitespace_4_idx_ = current - begin;
+ // FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD for request
+ // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION for response
+ *error_code =
+ static_cast<BalsaFrameEnums::ErrorCode>(
+ BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION +
+ is_request);
+ if (!is_request) { // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION
+ return false;
+ }
+ goto output_exhausted;
+ }
+ } while (*current > ' ');
+ // whitespace_2_idx_
+ headers->whitespace_2_idx_ = current - begin;
+ do {
+ ++current;
+ // Note that due to the loop which consumes all of the whitespace
+ // at the end of the line, current can never == end while in this function.
+ } while (*current <= ' ');
+ // non_whitespace_2_idx_
+ headers->non_whitespace_2_idx_ = current - begin;
+ do {
+ ++current;
+ if (current == end) {
+ headers->whitespace_3_idx_ = current - begin;
+ headers->non_whitespace_3_idx_ = current - begin;
+ headers->whitespace_4_idx_ = current - begin;
+ // FAILED_TO_FIND_START_OF_REQUEST_REQUEST_URI for request
+ // FAILED_TO_FIND_START_OF_RESPONSE_STATUSCODE for response
+ *error_code =
+ static_cast<BalsaFrameEnums::ErrorCode>(
+ BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE
+ + is_request);
+ goto output_exhausted;
+ }
+ } while (*current > ' ');
+ // whitespace_3_idx_
+ headers->whitespace_3_idx_ = current - begin;
+ do {
+ ++current;
+ // Note that due to the loop which consumes all of the whitespace
+ // at the end of the line, current can never == end while in this function.
+ } while (*current <= ' ');
+ // non_whitespace_3_idx_
+ headers->non_whitespace_3_idx_ = current - begin;
+ headers->whitespace_4_idx_ = end - begin;
+
+ output_exhausted:
+ // Note that we don't fail the parse immediately when parsing of the
+ // firstline fails. Depending on the protocol type, we may want to accept
+ // a firstline with only one or two elements, e.g., for HTTP/0.9:
+ // GET\r\n
+ // or
+ // GET /\r\n
+ // should be parsed without issue (though the visitor should know that
+ // parsing the entire line was not exactly as it should be).
+ //
+ // Eventually, these errors may be removed alltogether, as the visitor can
+ // detect them on its own by examining the size of the various fields.
+ // headers->set_first_line(non_whitespace_1_idx_, current);
+
+ if (is_request) {
+ if ((headers->whitespace_3_idx_ - headers->non_whitespace_2_idx_) >
+ max_request_uri_length) {
+ // For requests, we need at least the method. We could assume that a
+ // blank URI means "/". If version isn't stated, it should be assumed
+ // to be HTTP/0.9 by the visitor.
+ *error_code = BalsaFrameEnums::REQUEST_URI_TOO_LONG;
+ return false;
+ }
+ } else {
+ headers->parsed_response_code_ = 0;
+ {
+ const char* parsed_response_code_current =
+ begin + headers->non_whitespace_2_idx_;
+ const char* parsed_response_code_end = begin + headers->whitespace_3_idx_;
+ const size_t kMaxDiv10 = std::numeric_limits<size_t>::max() / 10;
+
+ // Convert a string of [0-9]* into an int.
+ // Note that this allows for the conversion of response codes which
+ // are outside the bounds of normal HTTP response codes (no checking
+ // is done to ensure that these are valid-- they're merely parsed)!
+ while (parsed_response_code_current < parsed_response_code_end) {
+ if (*parsed_response_code_current < '0' ||
+ *parsed_response_code_current > '9') {
+ *error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
+ return false;
+ }
+ size_t status_code_x_10 = headers->parsed_response_code_ * 10;
+ uint8 c = *parsed_response_code_current - '0';
+ if ((headers->parsed_response_code_ > kMaxDiv10) ||
+ (std::numeric_limits<size_t>::max() - status_code_x_10) < c) {
+ // overflow.
+ *error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT;
+ return false;
+ }
+ headers->parsed_response_code_ = status_code_x_10 + c;
+ ++parsed_response_code_current;
+ }
+ }
+ }
+ return true;
+}
+
+// begin - beginning of the firstline
+// end - end of the firstline
+//
+// A precondition for this function is that there is non-whitespace between
+// [begin, end). If this precondition is not met, the function will not perform
+// as expected (and bad things may happen, and it will eat your first, second,
+// and third unborn children!).
+//
+// Another precondition for this function is that [begin, end) includes
+// at most one newline, which must be at the end of the line.
+void BalsaFrame::ProcessFirstLine(const char* begin, const char* end) {
+ BalsaFrameEnums::ErrorCode previous_error = last_error_;
+ if (!ParseHTTPFirstLine(begin,
+ end,
+ is_request_,
+ max_request_uri_length_,
+ headers_,
+ &last_error_)) {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleHeaderError(this);
+ return;
+ }
+ if (previous_error != last_error_) {
+ visitor_->HandleHeaderWarning(this);
+ }
+
+ if (is_request_) {
+ int version_length =
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_;
+ visitor_->ProcessRequestFirstLine(
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_2_idx_,
+ headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_,
+ begin + headers_->non_whitespace_3_idx_,
+ version_length);
+ if (version_length == 0)
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ } else {
+ visitor_->ProcessResponseFirstLine(
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_1_idx_,
+ headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_,
+ begin + headers_->non_whitespace_2_idx_,
+ headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_,
+ begin + headers_->non_whitespace_3_idx_,
+ headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_);
+ }
+}
+
+// 'stream_begin' points to the first character of the headers buffer.
+// 'line_begin' points to the first character of the line.
+// 'current' points to a char which is ':'.
+// 'line_end' points to the position of '\n' + 1.
+// 'line_begin' points to the position of first character of line.
+void BalsaFrame::CleanUpKeyValueWhitespace(
+ const char* stream_begin,
+ const char* line_begin,
+ const char* current,
+ const char* line_end,
+ HeaderLineDescription* current_header_line) {
+ const char* colon_loc = current;
+ DCHECK_LT(colon_loc, line_end);
+ DCHECK_EQ(':', *colon_loc);
+ DCHECK_EQ(':', *current);
+ DCHECK_GE(' ', *line_end)
+ << "\"" << std::string(line_begin, line_end) << "\"";
+
+ // TODO(fenix): Investigate whether or not the bounds tests in the
+ // while loops here are redundant, and if so, remove them.
+ --current;
+ while (current > line_begin && *current <= ' ') --current;
+ current += (current != colon_loc);
+ current_header_line->key_end_idx = current - stream_begin;
+
+ current = colon_loc;
+ DCHECK_EQ(':', *current);
+ ++current;
+ while (current < line_end && *current <= ' ') ++current;
+ current_header_line->value_begin_idx = current - stream_begin;
+
+ DCHECK_GE(current_header_line->key_end_idx,
+ current_header_line->first_char_idx);
+ DCHECK_GE(current_header_line->value_begin_idx,
+ current_header_line->key_end_idx);
+ DCHECK_GE(current_header_line->last_char_idx,
+ current_header_line->value_begin_idx);
+}
+
+inline void BalsaFrame::FindColonsAndParseIntoKeyValue() {
+ DCHECK(!lines_.empty());
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+ // The last line is always just a newline (and is uninteresting).
+ const Lines::size_type lines_size_m1 = lines_.size() - 1;
+#if __SSE2__
+ const __v16qi colons = { ':', ':', ':', ':', ':', ':', ':', ':',
+ ':', ':', ':', ':', ':', ':', ':', ':'};
+ const char* header_lines_end_m16 = headers_->OriginalHeaderStreamEnd() - 16;
+#endif // __SSE2__
+ const char* current = stream_begin + lines_[1].first;
+ // This code is a bit more subtle than it may appear at first glance.
+ // This code looks for a colon in the current line... but it also looks
+ // beyond the current line. If there is no colon in the current line, then
+ // for each subsequent line (until the colon which -has- been found is
+ // associated with a line), no searching for a colon will be performed. In
+ // this way, we minimize the amount of bytes we have scanned for a colon.
+ for (Lines::size_type i = 1; i < lines_size_m1;) {
+ const char* line_begin = stream_begin + lines_[i].first;
+
+ // Here we handle possible continuations. Note that we do not replace
+ // the '\n' in the line before a continuation (at least, as of now),
+ // which implies that any code which looks for a value must deal with
+ // "\r\n", etc -within- the line (and not just at the end of it).
+ for (++i; i < lines_size_m1; ++i) {
+ const char c = *(stream_begin + lines_[i].first);
+ if (c > ' ') {
+ // Not a continuation, so stop. Note that if the 'original' i = 1,
+ // and the next line is not a continuation, we'll end up with i = 2
+ // when we break. This handles the incrementing of i for the outer
+ // loop.
+ break;
+ }
+ }
+ const char* line_end = stream_begin + lines_[i - 1].second;
+ DCHECK_LT(line_begin - stream_begin, line_end - stream_begin);
+
+ // We cleanup the whitespace at the end of the line before doing anything
+ // else of interest as it allows us to do nothing when irregularly formatted
+ // headers are parsed (e.g. those with only keys, only values, or no colon).
+ //
+ // We're guaranteed to have *line_end > ' ' while line_end >= line_begin.
+ --line_end;
+ DCHECK_EQ('\n', *line_end)
+ << "\"" << std::string(line_begin, line_end) << "\"";
+ while (*line_end <= ' ' && line_end > line_begin) {
+ --line_end;
+ }
+ ++line_end;
+ DCHECK_GE(' ', *line_end);
+ DCHECK_LT(line_begin, line_end);
+
+ // We use '0' for the block idx, because we're always writing to the first
+ // block from the framer (we do this because the framer requires that the
+ // entire header sequence be in a contiguous buffer).
+ headers_->header_lines_.push_back(
+ HeaderLineDescription(line_begin - stream_begin,
+ line_end - stream_begin,
+ line_end - stream_begin,
+ line_end - stream_begin,
+ 0));
+ if (current >= line_end) {
+ last_error_ = BalsaFrameEnums::HEADER_MISSING_COLON;
+ visitor_->HandleHeaderWarning(this);
+ // Then the next colon will not be found within this header line-- time
+ // to try again with another header-line.
+ continue;
+ } else if (current < line_begin) {
+ // When this condition is true, the last detected colon was part of a
+ // previous line. We reset to the beginning of the line as we don't care
+ // about the presence of any colon before the beginning of the current
+ // line.
+ current = line_begin;
+ }
+#if __SSE2__
+ while (current < header_lines_end_m16) {
+ __m128i header_bytes =
+ _mm_loadu_si128(reinterpret_cast<const __m128i *>(current));
+ __m128i colon_cmp =
+ _mm_cmpeq_epi8(header_bytes, reinterpret_cast<__m128i>(colons));
+ int colon_msk = _mm_movemask_epi8(colon_cmp);
+ if (colon_msk == 0) {
+ current += 16;
+ continue;
+ }
+ current += (ffs(colon_msk) - 1);
+ if (current > line_end) {
+ break;
+ }
+ goto found_colon;
+ }
+#endif // __SSE2__
+ for (; current < line_end; ++current) {
+ if (*current != ':') {
+ continue;
+ }
+ goto found_colon;
+ }
+ // If we've gotten to here, then there was no colon
+ // in the line. The arguments we passed into the construction
+ // for the HeaderLineDescription object should be OK-- it assumes
+ // that the entire content is 'key' by default (which is true, as
+ // there was no colon, there can be no value). Note that this is a
+ // construct which is technically not allowed by the spec.
+ last_error_ = BalsaFrameEnums::HEADER_MISSING_COLON;
+ visitor_->HandleHeaderWarning(this);
+ continue;
+ found_colon:
+ DCHECK_EQ(*current, ':');
+ DCHECK_LE(current - stream_begin, line_end - stream_begin);
+ DCHECK_LE(stream_begin - stream_begin, current - stream_begin);
+
+ HeaderLineDescription& current_header_line = headers_->header_lines_.back();
+ current_header_line.key_end_idx = current - stream_begin;
+ current_header_line.value_begin_idx = current_header_line.key_end_idx;
+ if (current < line_end) {
+ ++current_header_line.key_end_idx;
+
+ CleanUpKeyValueWhitespace(stream_begin,
+ line_begin,
+ current,
+ line_end,
+ &current_header_line);
+ }
+ }
+}
+
+void BalsaFrame::ProcessContentLengthLine(
+ HeaderLines::size_type line_idx,
+ BalsaHeadersEnums::ContentLengthStatus* status,
+ size_t* length) {
+ const HeaderLineDescription& header_line = headers_->header_lines_[line_idx];
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+ const char* line_end = stream_begin + header_line.last_char_idx;
+ const char* value_begin = (stream_begin + header_line.value_begin_idx);
+
+ if (value_begin >= line_end) {
+ // There is no non-whitespace value data.
+#if DEBUGFRAMER
+ LOG(INFO) << "invalid content-length -- no non-whitespace value data";
+#endif
+ *status = BalsaHeadersEnums::INVALID_CONTENT_LENGTH;
+ return;
+ }
+
+ *length = 0;
+ while (value_begin < line_end) {
+ if (*value_begin < '0' || *value_begin > '9') {
+ // bad! content-length found, and couldn't parse all of it!
+ *status = BalsaHeadersEnums::INVALID_CONTENT_LENGTH;
+#if DEBUGFRAMER
+ LOG(INFO) << "invalid content-length - non numeric character detected";
+#endif // DEBUGFRAMER
+ return;
+ }
+ const size_t kMaxDiv10 = std::numeric_limits<size_t>::max() / 10;
+ size_t length_x_10 = *length * 10;
+ const unsigned char c = *value_begin - '0';
+ if (*length > kMaxDiv10 ||
+ (std::numeric_limits<size_t>::max() - length_x_10) < c) {
+ *status = BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW;
+#if DEBUGFRAMER
+ LOG(INFO) << "content-length overflow";
+#endif // DEBUGFRAMER
+ return;
+ }
+ *length = length_x_10 + c;
+ ++value_begin;
+ }
+#if DEBUGFRAMER
+ LOG(INFO) << "content_length parsed: " << *length;
+#endif // DEBUGFRAMER
+ *status = BalsaHeadersEnums::VALID_CONTENT_LENGTH;
+}
+
+void BalsaFrame::ProcessTransferEncodingLine(HeaderLines::size_type line_idx) {
+ const HeaderLineDescription& header_line = headers_->header_lines_[line_idx];
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+ const char* line_end = stream_begin + header_line.last_char_idx;
+ const char* value_begin = stream_begin + header_line.value_begin_idx;
+ size_t value_length = line_end - value_begin;
+
+ if ((value_length == 7) &&
+ !strncasecmp(value_begin, "chunked", 7)) {
+ headers_->transfer_encoding_is_chunked_ = true;
+ } else if ((value_length == 8) &&
+ !strncasecmp(value_begin, "identity", 8)) {
+ headers_->transfer_encoding_is_chunked_ = false;
+ } else {
+ last_error_ = BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING;
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleHeaderError(this);
+ return;
+ }
+}
+
+namespace {
+bool SplitStringPiece(base::StringPiece original, char delim,
+ base::StringPiece* before, base::StringPiece* after) {
+ const char* p = original.data();
+ const char* end = p + original.size();
+
+ while (p != end) {
+ if (*p == delim) {
+ ++p;
+ } else {
+ const char* start = p;
+ while (++p != end && *p != delim) {
+ // Skip to the next occurence of the delimiter.
+ }
+ *before = base::StringPiece(start, p - start);
+ if (p != end)
+ *after = base::StringPiece(p + 1, end - (p + 1));
+ else
+ *after = base::StringPiece("");
+ StringPieceUtils::RemoveWhitespaceContext(before);
+ StringPieceUtils::RemoveWhitespaceContext(after);
+ return true;
+ }
+ }
+
+ *before = original;
+ *after = "";
+ return false;
+}
+
+// TODO(phython): Fix this function to properly deal with quoted values.
+// E.g. ";;foo", "\";;\"", or \"aa;
+// The last example, the semi-colon is a separator between extensions.
+void ProcessChunkExtensionsManual(base::StringPiece all_extensions,
+ BalsaHeaders* extensions) {
+ base::StringPiece extension;
+ base::StringPiece remaining;
+ StringPieceUtils::RemoveWhitespaceContext(&all_extensions);
+ SplitStringPiece(all_extensions, ';', &extension, &remaining);
+ while (!extension.empty()) {
+ base::StringPiece key;
+ base::StringPiece value;
+ SplitStringPiece(extension, '=', &key, &value);
+ if (!value.empty()) {
+ // Strip quotation marks if they exist.
+ if (!value.empty() && value[0] == '"')
+ value.remove_prefix(1);
+ if (!value.empty() && value[value.length() - 1] == '"')
+ value.remove_suffix(1);
+ }
+
+ extensions->AppendHeader(key, value);
+
+ StringPieceUtils::RemoveWhitespaceContext(&remaining);
+ SplitStringPiece(remaining, ';', &extension, &remaining);
+ }
+}
+
+// TODO(phython): Fix this function to properly deal with quoted values.
+// E.g. ";;foo", "\";;\"", or \"aa;
+// The last example, the semi-colon is a separator between extensions.
+void ProcessChunkExtensionsGoogle3(const char* input, size_t size,
+ BalsaHeaders* extensions) {
+ std::vector<base::StringPiece> key_values;
+ SplitStringPieceToVector(base::StringPiece(input, size), ";",
+ &key_values, true);
+ for (unsigned int i = 0; i < key_values.size(); ++i) {
+ base::StringPiece key = key_values[i].substr(0, key_values[i].find('='));
+ base::StringPiece value;
+ if (key.length() < key_values[i].length()) {
+ value = key_values[i].substr(key.length() + 1);
+ // Remove any leading and trailing whitespace.
+ StringPieceUtils::RemoveWhitespaceContext(&value);
+
+ // Strip quotation marks if they exist.
+ if (!value.empty() && value[0] == '"')
+ value.remove_prefix(1);
+ if (!value.empty() && value[value.length() - 1] == '"')
+ value.remove_suffix(1);
+ }
+
+ // Strip the key whitespace after checking that there is a value.
+ StringPieceUtils::RemoveWhitespaceContext(&key);
+ extensions->AppendHeader(key, value);
+ }
+}
+
+} // anonymous namespace
+
+void BalsaFrame::ProcessChunkExtensions(const char* input, size_t size,
+ BalsaHeaders* extensions) {
+#if 0
+ ProcessChunkExtensionsGoogle3(input, size, extensions);
+#else
+ ProcessChunkExtensionsManual(base::StringPiece(input, size), extensions);
+#endif
+}
+
+void BalsaFrame::ProcessHeaderLines() {
+ HeaderLines::size_type content_length_idx = 0;
+ HeaderLines::size_type transfer_encoding_idx = 0;
+
+ DCHECK(!lines_.empty());
+#if DEBUGFRAMER
+ LOG(INFO) << "******@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@**********\n";
+#endif // DEBUGFRAMER
+
+ // There is no need to attempt to process headers if no header lines exist.
+ // There are at least two lines in the message which are not header lines.
+ // These two non-header lines are the first line of the message, and the
+ // last line of the message (which is an empty line).
+ // Thus, we test to see if we have more than two lines total before attempting
+ // to parse any header lines.
+ if (lines_.size() > 2) {
+ const char* stream_begin = headers_->OriginalHeaderStreamBegin();
+
+ // Then, for the rest of the header data, we parse these into key-value
+ // pairs.
+ FindColonsAndParseIntoKeyValue();
+ // At this point, we've parsed all of the headers. Time to look for those
+ // headers which we require for framing.
+ const HeaderLines::size_type
+ header_lines_size = headers_->header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < header_lines_size; ++i) {
+ const HeaderLineDescription& current_header_line =
+ headers_->header_lines_[i];
+ const char* key_begin =
+ (stream_begin + current_header_line.first_char_idx);
+ const char* key_end = (stream_begin + current_header_line.key_end_idx);
+ const size_t key_len = key_end - key_begin;
+ const char c = *key_begin;
+#if DEBUGFRAMER
+ LOG(INFO) << "[" << i << "]: " << std::string(key_begin, key_len)
+ << " c: '" << c << "' key_len: " << key_len;
+#endif // DEBUGFRAMER
+ // If a header begins with either lowercase or uppercase 'c' or 't', then
+ // the header may be one of content-length, connection, content-encoding
+ // or transfer-encoding. These headers are special, as they change the way
+ // that the message is framed, and so the framer is required to search
+ // for them.
+
+
+ if (c == 'c' || c == 'C') {
+ if ((key_len == kContentLengthSize) &&
+ 0 == strncasecmp(key_begin, kContentLength, kContentLengthSize)) {
+ BalsaHeadersEnums::ContentLengthStatus content_length_status =
+ BalsaHeadersEnums::NO_CONTENT_LENGTH;
+ size_t length = 0;
+ ProcessContentLengthLine(i, &content_length_status, &length);
+ if (content_length_idx != 0) { // then we've already seen one!
+ if ((headers_->content_length_status_ != content_length_status) ||
+ ((headers_->content_length_status_ ==
+ BalsaHeadersEnums::VALID_CONTENT_LENGTH) &&
+ length != headers_->content_length_)) {
+ last_error_ = BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS;
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleHeaderError(this);
+ return;
+ }
+ continue;
+ } else {
+ content_length_idx = i + 1;
+ headers_->content_length_status_ = content_length_status;
+ headers_->content_length_ = length;
+ content_length_remaining_ = length;
+ }
+
+ }
+ } else if (c == 't' || c == 'T') {
+ if ((key_len == kTransferEncodingSize) &&
+ 0 == strncasecmp(key_begin, kTransferEncoding,
+ kTransferEncodingSize)) {
+ if (transfer_encoding_idx != 0) {
+ last_error_ = BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS;
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleHeaderError(this);
+ return;
+ }
+ transfer_encoding_idx = i + 1;
+ }
+ } else if (i == 0 && (key_len == 0 || c == ' ')) {
+ last_error_ = BalsaFrameEnums::INVALID_HEADER_FORMAT;
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleHeaderError(this);
+ return;
+ }
+ }
+ if (headers_->transfer_encoding_is_chunked_) {
+ headers_->content_length_ = 0;
+ headers_->content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
+ content_length_remaining_ = 0;
+ }
+ if (transfer_encoding_idx != 0) {
+ ProcessTransferEncodingLine(transfer_encoding_idx - 1);
+ }
+ }
+}
+
+void BalsaFrame::AssignParseStateAfterHeadersHaveBeenParsed() {
+ // For responses, can't have a body if the request was a HEAD, or if it is
+ // one of these response-codes. rfc2616 section 4.3
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ if (is_request_ ||
+ !(request_was_head_ ||
+ (headers_->parsed_response_code_ >= 100 &&
+ headers_->parsed_response_code_ < 200) ||
+ (headers_->parsed_response_code_ == 204) ||
+ (headers_->parsed_response_code_ == 304))) {
+ // Then we can have a body.
+ if (headers_->transfer_encoding_is_chunked_) {
+ // Note that
+ // if ( Transfer-Encoding: chunked && Content-length: )
+ // then Transfer-Encoding: chunked trumps.
+ // This is as specified in the spec.
+ // rfc2616 section 4.4.3
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH;
+ } else {
+ // Errors parsing content-length definitely can cause
+ // protocol errors/warnings
+ switch (headers_->content_length_status_) {
+ // If we have a content-length, and it is parsed
+ // properly, there are two options.
+ // 1) zero content, in which case the message is done, and
+ // 2) nonzero content, in which case we have to
+ // consume the body.
+ case BalsaHeadersEnums::VALID_CONTENT_LENGTH:
+ if (headers_->content_length_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ } else {
+ parse_state_ = BalsaFrameEnums::READING_CONTENT;
+ }
+ break;
+ case BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW:
+ case BalsaHeadersEnums::INVALID_CONTENT_LENGTH:
+ // If there were characters left-over after parsing the
+ // content length, we should flag an error and stop.
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH;
+ visitor_->HandleHeaderError(this);
+ break;
+ // We can have: no transfer-encoding, no content length, and no
+ // connection: close...
+ // Unfortunately, this case doesn't seem to be covered in the spec.
+ // We'll assume that the safest thing to do here is what the google
+ // binaries before 2008 already do, which is to assume that
+ // everything until the connection is closed is body.
+ case BalsaHeadersEnums::NO_CONTENT_LENGTH:
+ if (is_request_) {
+ base::StringPiece method = headers_->request_method();
+ // POSTs and PUTs should have a detectable body length. If they
+ // do not we consider it an error.
+ if ((method.size() == 4 &&
+ strncmp(method.data(), "POST", 4) == 0) ||
+ (method.size() == 3 &&
+ strncmp(method.data(), "PUT", 3) == 0)) {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ =
+ BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH;
+ visitor_->HandleHeaderError(this);
+ break;
+ }
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ } else {
+ parse_state_ = BalsaFrameEnums::READING_UNTIL_CLOSE;
+ last_error_ = BalsaFrameEnums::MAYBE_BODY_BUT_NO_CONTENT_LENGTH;
+ visitor_->HandleHeaderWarning(this);
+ }
+ break;
+ // The COV_NF_... statements here provide hints to the apparatus
+ // which computes coverage reports/ratios that this code is never
+ // intended to be executed, and should technically be impossible.
+ // COV_NF_START
+ default:
+ LOG(FATAL) << "Saw a content_length_status: "
+ << headers_->content_length_status_ << " which is unknown.";
+ // COV_NF_END
+ }
+ }
+ }
+}
+
+size_t BalsaFrame::ProcessHeaders(const char* message_start,
+ size_t message_length) {
+ const char* const original_message_start = message_start;
+ const char* const message_end = message_start + message_length;
+ const char* message_current = message_start;
+ const char* checkpoint = message_start;
+
+ if (message_length == 0) {
+ goto bottom;
+ }
+
+ while (message_current < message_end) {
+ size_t base_idx = headers_->GetReadableBytesFromHeaderStream();
+
+ // Yes, we could use strchr (assuming null termination), or
+ // memchr, but as it turns out that is slower than this tight loop
+ // for the input that we see.
+ if (!saw_non_newline_char_) {
+ do {
+ const char c = *message_current;
+ if (c != '\r' && c != '\n') {
+ if (c <= ' ') {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST;
+ visitor_->HandleHeaderError(this);
+ goto bottom;
+ } else {
+ saw_non_newline_char_ = true;
+ checkpoint = message_start = message_current;
+ goto read_real_message;
+ }
+ }
+ ++message_current;
+ } while (message_current < message_end);
+ goto bottom; // this is necessary to skip 'last_char_was_slash_r' checks
+ } else {
+ read_real_message:
+ // Note that SSE2 can be enabled on certain piii platforms.
+#if __SSE2__
+ {
+ const char* const message_end_m16 = message_end - 16;
+ __v16qi newlines = { '\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n',
+ '\n', '\n', '\n', '\n', '\n', '\n', '\n', '\n' };
+ while (message_current < message_end_m16) {
+ // What this does (using compiler intrinsics):
+ //
+ // Load 16 '\n's into an xmm register
+ // Load 16 bytes of currennt message into an xmm register
+ // Do byte-wise equals on those two xmm registers
+ // Take the first bit of each byte, and put that into the first
+ // 16 bits of a mask
+ // If the mask is zero, no '\n' found. increment by 16 and try again
+ // Else scan forward to find the first set bit.
+ // Increment current by the index of the first set bit
+ // (ffs returns index of first set bit + 1)
+ __m128i msg_bytes =
+ _mm_loadu_si128(const_cast<__m128i *>(
+ reinterpret_cast<const __m128i *>(message_current)));
+ __m128i newline_cmp =
+ _mm_cmpeq_epi8(msg_bytes, reinterpret_cast<__m128i>(newlines));
+ int newline_msk = _mm_movemask_epi8(newline_cmp);
+ if (newline_msk == 0) {
+ message_current += 16;
+ continue;
+ }
+ message_current += (ffs(newline_msk) - 1);
+ const size_t relative_idx = message_current - message_start;
+ const size_t message_current_idx = 1 + base_idx + relative_idx;
+ lines_.push_back(std::make_pair(last_slash_n_idx_,
+ message_current_idx));
+ if (lines_.size() == 1) {
+ headers_->WriteFromFramer(checkpoint,
+ 1 + message_current - checkpoint);
+ checkpoint = message_current + 1;
+ const char* begin = headers_->OriginalHeaderStreamBegin();
+#if DEBUGFRAMER
+ LOG(INFO) << "First line " << std::string(begin, lines_[0].second);
+ LOG(INFO) << "is_request_: " << is_request_;
+#endif
+ ProcessFirstLine(begin, begin + lines_[0].second);
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ)
+ goto process_lines;
+ else if (parse_state_ == BalsaFrameEnums::PARSE_ERROR)
+ goto bottom;
+ }
+ const size_t chars_since_last_slash_n = (message_current_idx -
+ last_slash_n_idx_);
+ last_slash_n_idx_ = message_current_idx;
+ if (chars_since_last_slash_n > 2) {
+ // We have a slash-n, but the last slash n was
+ // more than 2 characters away from this. Thus, we know
+ // that this cannot be an end-of-header.
+ ++message_current;
+ continue;
+ }
+ if ((chars_since_last_slash_n == 1) ||
+ (((message_current > message_start) &&
+ (*(message_current - 1) == '\r')) ||
+ (last_char_was_slash_r_))) {
+ goto process_lines;
+ }
+ ++message_current;
+ }
+ }
+#endif // __SSE2__
+ while (message_current < message_end) {
+ if (*message_current != '\n') {
+ ++message_current;
+ continue;
+ }
+ const size_t relative_idx = message_current - message_start;
+ const size_t message_current_idx = 1 + base_idx + relative_idx;
+ lines_.push_back(std::make_pair(last_slash_n_idx_,
+ message_current_idx));
+ if (lines_.size() == 1) {
+ headers_->WriteFromFramer(checkpoint,
+ 1 + message_current - checkpoint);
+ checkpoint = message_current + 1;
+ const char* begin = headers_->OriginalHeaderStreamBegin();
+#if DEBUGFRAMER
+ LOG(INFO) << "First line " << std::string(begin, lines_[0].second);
+ LOG(INFO) << "is_request_: " << is_request_;
+#endif
+ ProcessFirstLine(begin, begin + lines_[0].second);
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ)
+ goto process_lines;
+ else if (parse_state_ == BalsaFrameEnums::PARSE_ERROR)
+ goto bottom;
+ }
+ const size_t chars_since_last_slash_n = (message_current_idx -
+ last_slash_n_idx_);
+ last_slash_n_idx_ = message_current_idx;
+ if (chars_since_last_slash_n > 2) {
+ // false positive.
+ ++message_current;
+ continue;
+ }
+ if ((chars_since_last_slash_n == 1) ||
+ (((message_current > message_start) &&
+ (*(message_current - 1) == '\r')) ||
+ (last_char_was_slash_r_))) {
+ goto process_lines;
+ }
+ ++message_current;
+ }
+ }
+ continue;
+ process_lines:
+ ++message_current;
+ DCHECK(message_current >= message_start);
+ if (message_current > message_start) {
+ headers_->WriteFromFramer(checkpoint, message_current - checkpoint);
+ }
+
+ // Check if we have exceeded maximum headers length
+ // Although we check for this limit before and after we call this function
+ // we check it here as well to make sure that in case the visitor changed
+ // the max_header_length_ (for example after processing the first line)
+ // we handle it gracefully.
+ if (headers_->GetReadableBytesFromHeaderStream() > max_header_length_) {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG;
+ visitor_->HandleHeaderError(this);
+ goto bottom;
+ }
+
+ // Since we know that we won't be writing any more bytes of the header,
+ // we tell that to the headers object. The headers object may make
+ // more efficient allocation decisions when this is signaled.
+ headers_->DoneWritingFromFramer();
+ {
+ const char* readable_ptr = NULL;
+ size_t readable_size = 0;
+ headers_->GetReadablePtrFromHeaderStream(&readable_ptr, &readable_size);
+ visitor_->ProcessHeaderInput(readable_ptr, readable_size);
+ }
+
+ // Ok, now that we've written everything into our header buffer, it is
+ // time to process the header lines (extract proper values for headers
+ // which are important for framing).
+ ProcessHeaderLines();
+ if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) {
+ goto bottom;
+ }
+ AssignParseStateAfterHeadersHaveBeenParsed();
+ if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) {
+ goto bottom;
+ }
+ visitor_->ProcessHeaders(*headers_);
+ visitor_->HeaderDone();
+ if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) {
+ visitor_->MessageDone();
+ }
+ goto bottom;
+ }
+ // If we've gotten to here, it means that we've consumed all of the
+ // available input. We need to record whether or not the last character we
+ // saw was a '\r' so that a subsequent call to ProcessInput correctly finds
+ // a header framing that is split across the two calls.
+ last_char_was_slash_r_ = (*(message_end - 1) == '\r');
+ DCHECK(message_current >= message_start);
+ if (message_current > message_start) {
+ headers_->WriteFromFramer(checkpoint, message_current - checkpoint);
+ }
+ bottom:
+ return message_current - original_message_start;
+}
+
+
+size_t BalsaFrame::BytesSafeToSplice() const {
+ switch (parse_state_) {
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ return chunk_length_remaining_;
+ case BalsaFrameEnums::READING_UNTIL_CLOSE:
+ return std::numeric_limits<size_t>::max();
+ case BalsaFrameEnums::READING_CONTENT:
+ return content_length_remaining_;
+ default:
+ return 0;
+ }
+}
+
+void BalsaFrame::BytesSpliced(size_t bytes_spliced) {
+ switch (parse_state_) {
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ if (chunk_length_remaining_ >= bytes_spliced) {
+ chunk_length_remaining_ -= bytes_spliced;
+ if (chunk_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM;
+ }
+ return;
+ } else {
+ last_error_ =
+ BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT;
+ goto error_exit;
+ }
+
+ case BalsaFrameEnums::READING_UNTIL_CLOSE:
+ return;
+
+ case BalsaFrameEnums::READING_CONTENT:
+ if (content_length_remaining_ >= bytes_spliced) {
+ content_length_remaining_ -= bytes_spliced;
+ if (content_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->MessageDone();
+ }
+ return;
+ } else {
+ last_error_ =
+ BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT;
+ goto error_exit;
+ }
+
+ default:
+ last_error_ = BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO;
+ goto error_exit;
+ }
+
+ error_exit:
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ visitor_->HandleBodyError(this);
+};
+
+// You may note that the state-machine contained within this function has both
+// switch and goto labels for nearly the same thing. For instance, the
+// following two labels refer to the same code block:
+// label_reading_chunk_data:
+// case BalsaFrameEnums::READING_CHUNK_DATA:
+// The 'case' statement is required for the switch statement which occurs when
+// ProcessInput is invoked. The goto label is required as the state-machine
+// does not use a computed goto in any subsequent operations.
+//
+// Since several states exit the state machine for various reasons, there is
+// also one label at the bottom of the function. When it is appropriate to
+// return from the function, that part of the state machine instead issues a
+// goto bottom; This results in less code duplication, and makes debugging
+// easier (as you can add a statement to a section of code which is guaranteed
+// to be invoked when the function is exiting.
+size_t BalsaFrame::ProcessInput(const char* input, size_t size) {
+ const char* current = input;
+ const char* on_entry = current;
+ const char* end = current + size;
+#if DEBUGFRAMER
+ LOG(INFO) << "\n=============="
+ << BalsaFrameEnums::ParseStateToString(parse_state_)
+ << "===============\n";
+#endif // DEBUGFRAMER
+
+ DCHECK(headers_ != NULL);
+ if (headers_ == NULL) return 0;
+
+ if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) {
+ const size_t header_length = headers_->GetReadableBytesFromHeaderStream();
+ // Yes, we still have to check this here as the user can change the
+ // max_header_length amount!
+ // Also it is possible that we have reached the maximum allowed header size,
+ // and we have more to consume (remember we are still inside
+ // READING_HEADER_AND_FIRSTLINE) in which case we directly declare an error.
+ if (header_length > max_header_length_ ||
+ (header_length == max_header_length_ && size > 0)) {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG;
+ visitor_->HandleHeaderError(this);
+ goto bottom;
+ }
+ size_t bytes_to_process = max_header_length_ - header_length;
+ if (bytes_to_process > size) {
+ bytes_to_process = size;
+ }
+ current += ProcessHeaders(input, bytes_to_process);
+ // If we are still reading headers check if we have crossed the headers
+ // limit. Note that we check for >= as opposed to >. This is because if
+ // header_length_after equals max_header_length_ and we are still in the
+ // parse_state_ BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE we know for
+ // sure that the headers limit will be crossed later on
+ if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) {
+ // Note that headers_ is valid only if we are still reading headers.
+ const size_t header_length_after =
+ headers_->GetReadableBytesFromHeaderStream();
+ if (header_length_after >= max_header_length_) {
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG;
+ visitor_->HandleHeaderError(this);
+ }
+ }
+ goto bottom;
+ } else if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ ||
+ parse_state_ == BalsaFrameEnums::PARSE_ERROR) {
+ // Can do nothing more 'till we're reset.
+ goto bottom;
+ }
+
+ while (current < end) {
+ switch (parse_state_) {
+ label_reading_chunk_length:
+ case BalsaFrameEnums::READING_CHUNK_LENGTH:
+ // In this state we read the chunk length.
+ // Note that once we hit a character which is not in:
+ // [0-9;A-Fa-f\n], we transition to a different state.
+ //
+ {
+ // If we used strtol, etc, we'd have to buffer this line.
+ // This is more annoying than simply doing the conversion
+ // here. This code accounts for overflow.
+ static const signed char buf[] = {
+ // %0 %1 %2 %3 %4 %5 %6 %7 %8 \t \n %b %c \r %e %f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1,
+ // %10 %11 %12 %13 %14 %15 %16 %17 %18 %19 %1a %1b %1c %1d %1e %1f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // ' ' %21 %22 %23 %24 %25 %26 %27 %28 %29 %2a %2b %2c %2d %2e %2f
+ -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // %30 %31 %32 %33 %34 %35 %36 %37 %38 %39 %3a ';' %3c %3d %3e %3f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -2, -1, -1, -1, -1,
+ // %40 'A' 'B' 'C' 'D' 'E' 'F' %47 %48 %49 %4a %4b %4c %4d %4e %4f
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // %50 %51 %52 %53 %54 %55 %56 %57 %58 %59 %5a %5b %5c %5d %5e %5f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // %60 'a' 'b' 'c' 'd' 'e' 'f' %67 %68 %69 %6a %6b %6c %6d %6e %6f
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // %70 %71 %72 %73 %74 %75 %76 %77 %78 %79 %7a %7b %7c %7d %7e %7f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+ // valid cases:
+ // "09123\n" // -> 09123
+ // "09123\r\n" // -> 09123
+ // "09123 \n" // -> 09123
+ // "09123 \r\n" // -> 09123
+ // "09123 12312\n" // -> 09123
+ // "09123 12312\r\n" // -> 09123
+ // "09123; foo=bar\n" // -> 09123
+ // "09123; foo=bar\r\n" // -> 09123
+ // "FFFFFFFFFFFFFFFF\r\n" // -> FFFFFFFFFFFFFFFF
+ // "FFFFFFFFFFFFFFFF 22\r\n" // -> FFFFFFFFFFFFFFFF
+ // invalid cases:
+ // "[ \t]+[^\n]*\n"
+ // "FFFFFFFFFFFFFFFFF\r\n" (would overflow)
+ // "\r\n"
+ // "\n"
+ while (current < end) {
+ const char c = *current;
+ ++current;
+ const signed char addition = buf[static_cast<int>(c)];
+ if (addition >= 0) {
+ chunk_length_character_extracted_ = true;
+ size_t length_x_16 = chunk_length_remaining_ * 16;
+ const size_t kMaxDiv16 = std::numeric_limits<size_t>::max() / 16;
+ if ((chunk_length_remaining_ > kMaxDiv16) ||
+ ((std::numeric_limits<size_t>::max() - length_x_16) <
+ static_cast<size_t>(addition))) {
+ // overflow -- asked for a chunk-length greater than 2^64 - 1!!
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW;
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ visitor_->HandleChunkingError(this);
+ goto bottom;
+ }
+ chunk_length_remaining_ = length_x_16 + addition;
+ continue;
+ }
+
+ if (!chunk_length_character_extracted_ || addition == -1) {
+ // ^[0-9;A-Fa-f][ \t\n] -- was not matched, either because no
+ // characters were converted, or an unexpected character was
+ // seen.
+ parse_state_ = BalsaFrameEnums::PARSE_ERROR;
+ last_error_ = BalsaFrameEnums::INVALID_CHUNK_LENGTH;
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ visitor_->HandleChunkingError(this);
+ goto bottom;
+ }
+
+ --current;
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_EXTENSION;
+ visitor_->ProcessChunkLength(chunk_length_remaining_);
+ goto label_reading_chunk_extension;
+ }
+ }
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ goto bottom; // case BalsaFrameEnums::READING_CHUNK_LENGTH
+
+ label_reading_chunk_extension:
+ case BalsaFrameEnums::READING_CHUNK_EXTENSION:
+ {
+ // TODO(phython): Convert this scanning to be 16 bytes at a time if
+ // there is data to be read.
+ const char* extensions_start = current;
+ size_t extensions_length = 0;
+ while (current < end) {
+ const char c = *current;
+ if (c == '\r' || c == '\n') {
+ extensions_length =
+ (extensions_start == current) ?
+ 0 :
+ current - extensions_start - 1;
+ }
+
+ ++current;
+ if (c == '\n') {
+ chunk_length_character_extracted_ = false;
+ visitor_->ProcessChunkExtensions(
+ extensions_start, extensions_length);
+ if (chunk_length_remaining_ != 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_DATA;
+ goto label_reading_chunk_data;
+ }
+ HeaderFramingFound('\n');
+ parse_state_ = BalsaFrameEnums::READING_LAST_CHUNK_TERM;
+ goto label_reading_last_chunk_term;
+ }
+ }
+ visitor_->ProcessChunkExtensions(
+ extensions_start, extensions_length);
+ }
+
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ goto bottom; // case BalsaFrameEnums::READING_CHUNK_EXTENSION
+
+ label_reading_chunk_data:
+ case BalsaFrameEnums::READING_CHUNK_DATA:
+ while (current < end) {
+ if (chunk_length_remaining_ == 0) {
+ break;
+ }
+ // read in the chunk
+ size_t bytes_remaining = end - current;
+ size_t consumed_bytes = (chunk_length_remaining_ < bytes_remaining) ?
+ chunk_length_remaining_ : bytes_remaining;
+ const char* tmp_current = current + consumed_bytes;
+ visitor_->ProcessBodyInput(on_entry, tmp_current - on_entry);
+ visitor_->ProcessBodyData(current, consumed_bytes);
+ on_entry = current = tmp_current;
+ chunk_length_remaining_ -= consumed_bytes;
+ }
+ if (chunk_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM;
+ goto label_reading_chunk_term;
+ }
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ goto bottom; // case BalsaFrameEnums::READING_CHUNK_DATA
+
+ label_reading_chunk_term:
+ case BalsaFrameEnums::READING_CHUNK_TERM:
+ while (current < end) {
+ const char c = *current;
+ ++current;
+
+ if (c == '\n') {
+ parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH;
+ goto label_reading_chunk_length;
+ }
+ }
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ goto bottom; // case BalsaFrameEnums::READING_CHUNK_TERM
+
+ label_reading_last_chunk_term:
+ case BalsaFrameEnums::READING_LAST_CHUNK_TERM:
+ while (current < end) {
+ const char c = *current;
+
+ if (!HeaderFramingFound(c)) {
+ // If not, however, since the spec only suggests that the
+ // client SHOULD indicate the presence of trailers, we get to
+ // *test* that they did or didn't.
+ // If all of the bytes we've seen since:
+ // OPTIONAL_WS 0 OPTIONAL_STUFF CRLF
+ // are either '\r', or '\n', then we can assume that we don't yet
+ // know if we need to parse headers, or if the next byte will make
+ // the HeaderFramingFound condition (above) true.
+ if (HeaderFramingMayBeFound()) {
+ // If true, then we have seen only characters '\r' or '\n'.
+ ++current;
+
+ // Lets try again! There is no state change here.
+ continue;
+ } else {
+ // If (!HeaderFramingMayBeFound()), then we know that we must be
+ // reading the first non CRLF character of a trailer.
+ parse_state_ = BalsaFrameEnums::READING_TRAILER;
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ on_entry = current;
+ goto label_reading_trailer;
+ }
+ } else {
+ // If we've found a "\r\n\r\n", then the message
+ // is done.
+ ++current;
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ visitor_->MessageDone();
+ goto bottom;
+ }
+ break; // from while loop
+ }
+ visitor_->ProcessBodyInput(on_entry, current - on_entry);
+ goto bottom; // case BalsaFrameEnums::READING_LAST_CHUNK_TERM
+
+ label_reading_trailer:
+ case BalsaFrameEnums::READING_TRAILER:
+ while (current < end) {
+ const char c = *current;
+ ++current;
+ // TODO(fenix): If we ever care about trailers as part of framing,
+ // deal with them here (see below for part of the 'solution')
+ // if (LineFramingFound(c)) {
+ // trailer_lines_.push_back(make_pair(start_of_line_,
+ // trailer_length_ - 1));
+ // start_of_line_ = trailer_length_;
+ // }
+ if (HeaderFramingFound(c)) {
+ // ProcessTrailers(visitor_, &trailers_);
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->ProcessTrailerInput(on_entry, current - on_entry);
+ visitor_->MessageDone();
+ goto bottom;
+ }
+ }
+ visitor_->ProcessTrailerInput(on_entry, current - on_entry);
+ break; // case BalsaFrameEnums::READING_TRAILER
+
+ // Note that there is no label:
+ // 'label_reading_until_close'
+ // here. This is because the state-machine exists immediately after
+ // reading the headers instead of transitioning here (as it would
+ // do if it was consuming all the data it could, all the time).
+ case BalsaFrameEnums::READING_UNTIL_CLOSE:
+ {
+ const size_t bytes_remaining = end - current;
+ if (bytes_remaining > 0) {
+ visitor_->ProcessBodyInput(current, bytes_remaining);
+ visitor_->ProcessBodyData(current, bytes_remaining);
+ current += bytes_remaining;
+ }
+ }
+ goto bottom; // case BalsaFrameEnums::READING_UNTIL_CLOSE
+
+ // label_reading_content:
+ case BalsaFrameEnums::READING_CONTENT:
+#if DEBUGFRAMER
+ LOG(INFO) << "ReadingContent: " << content_length_remaining_;
+#endif // DEBUGFRAMER
+ while (content_length_remaining_ && current < end) {
+ // read in the content
+ const size_t bytes_remaining = end - current;
+ const size_t consumed_bytes =
+ (content_length_remaining_ < bytes_remaining) ?
+ content_length_remaining_ : bytes_remaining;
+ visitor_->ProcessBodyInput(current, consumed_bytes);
+ visitor_->ProcessBodyData(current, consumed_bytes);
+ current += consumed_bytes;
+ content_length_remaining_ -= consumed_bytes;
+ }
+ if (content_length_remaining_ == 0) {
+ parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ;
+ visitor_->MessageDone();
+ }
+ goto bottom; // case BalsaFrameEnums::READING_CONTENT
+
+ default:
+ // The state-machine should never be in a state that isn't handled
+ // above. This is a glaring logic error, and we should do something
+ // drastic to ensure that this gets looked-at and fixed.
+ LOG(FATAL) << "Unknown state: " << parse_state_ // COV_NF_LINE
+ << " memory corruption?!"; // COV_NF_LINE
+ }
+ }
+ bottom:
+#if DEBUGFRAMER
+ LOG(INFO) << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n"
+ << std::string(input, current)
+ << "\n$$$$$$$$$$$$$$"
+ << BalsaFrameEnums::ParseStateToString(parse_state_)
+ << "$$$$$$$$$$$$$$$"
+ << " consumed: " << (current - input);
+ if (Error()) {
+ LOG(INFO) << BalsaFrameEnums::ErrorCodeToString(ErrorCode());
+ }
+#endif // DEBUGFRAMER
+ return current - input;
+}
+
+const uint32 BalsaFrame::kValidTerm1;
+const uint32 BalsaFrame::kValidTerm1Mask;
+const uint32 BalsaFrame::kValidTerm2;
+const uint32 BalsaFrame::kValidTerm2Mask;
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/balsa_frame.h b/chromium/net/tools/flip_server/balsa_frame.h
new file mode 100644
index 00000000000..0d3b372ef9f
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_frame.h
@@ -0,0 +1,265 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_BALSA_FRAME_H_
+#define NET_TOOLS_FLIP_SERVER_BALSA_FRAME_H_
+
+#include <strings.h>
+
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/port.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/buffer_interface.h"
+#include "net/tools/flip_server/http_message_constants.h"
+#include "net/tools/flip_server/simple_buffer.h"
+
+// For additional debug output, uncomment the following:
+// #define DEBUGFRAMER 1
+
+namespace net {
+
+// BalsaFrame is a 'Model' of a framer (haha).
+// It exists as a proof of concept headers framer.
+class BalsaFrame {
+ public:
+ typedef std::vector<std::pair<size_t, size_t> > Lines;
+
+ typedef BalsaHeaders::HeaderLineDescription HeaderLineDescription;
+ typedef BalsaHeaders::HeaderLines HeaderLines;
+ typedef BalsaHeaders::HeaderTokenList HeaderTokenList;
+
+ // TODO(fenix): get rid of the 'kValidTerm*' stuff by using the 'since last
+ // index' strategy. Note that this implies getting rid of the HeaderFramed()
+
+ static const uint32 kValidTerm1 = '\n' << 16 |
+ '\r' << 8 |
+ '\n';
+ static const uint32 kValidTerm1Mask = 0xFF << 16 |
+ 0xFF << 8 |
+ 0xFF;
+ static const uint32 kValidTerm2 = '\n' << 8 |
+ '\n';
+ static const uint32 kValidTerm2Mask = 0xFF << 8 |
+ 0xFF;
+ BalsaFrame();
+ ~BalsaFrame();
+
+ // Reset reinitializes all the member variables of the framer and clears the
+ // attached header object (but doesn't change the pointer value headers_).
+ void Reset();
+
+ const BalsaHeaders* const_balsa_headers() const { return headers_; }
+ BalsaHeaders* balsa_headers() { return headers_; }
+ // The method set_balsa_headers clears the headers provided and attaches them
+ // to the framer. This is a required step before the framer will process any
+ // input message data.
+ // To detach the header object from the framer, use set_balsa_headers(NULL).
+ void set_balsa_headers(BalsaHeaders* headers) {
+ if (headers_ != headers) {
+ headers_ = headers;
+ }
+ if (headers_) {
+ // Clear the headers if they are non-null, even if the new headers are
+ // the same as the old.
+ headers_->Clear();
+ }
+ }
+
+ void set_balsa_visitor(BalsaVisitorInterface* visitor) {
+ visitor_ = visitor;
+ if (visitor_ == NULL) {
+ visitor_ = &do_nothing_visitor_;
+ }
+ }
+
+ void set_is_request(bool is_request) { is_request_ = is_request; }
+
+ bool is_request() const {
+ return is_request_;
+ }
+
+ void set_request_was_head(bool request_was_head) {
+ request_was_head_ = request_was_head;
+ }
+
+ bool request_was_head() const {
+ return request_was_head_;
+ }
+
+ void set_max_header_length(size_t max_header_length) {
+ max_header_length_ = max_header_length;
+ }
+
+ size_t max_header_length() const {
+ return max_header_length_;
+ }
+
+ void set_max_request_uri_length(size_t max_request_uri_length) {
+ max_request_uri_length_ = max_request_uri_length;
+ }
+
+ size_t max_request_uri_length() const {
+ return max_request_uri_length_;
+ }
+
+
+ bool MessageFullyRead() {
+ return parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ;
+ }
+
+ BalsaFrameEnums::ParseState ParseState() const { return parse_state_; }
+
+
+ bool Error() {
+ return parse_state_ == BalsaFrameEnums::PARSE_ERROR;
+ }
+
+ BalsaFrameEnums::ErrorCode ErrorCode() const { return last_error_; }
+
+ const BalsaHeaders* headers() const { return headers_; }
+ BalsaHeaders* mutable_headers() { return headers_; }
+
+ size_t BytesSafeToSplice() const;
+ void BytesSpliced(size_t bytes_spliced);
+
+ size_t ProcessInput(const char* input, size_t size);
+
+ // Parses input and puts the key, value chunk extensions into extensions.
+ // TODO(phython): Find a better data structure to put the extensions into.
+ static void ProcessChunkExtensions(const char* input, size_t size,
+ BalsaHeaders* extensions);
+
+ protected:
+ // The utils object needs access to the ParseTokenList in order to do its
+ // job.
+ friend class BalsaHeadersTokenUtils;
+
+ inline void ProcessContentLengthLine(
+ size_t line_idx,
+ BalsaHeadersEnums::ContentLengthStatus* status,
+ size_t* length);
+
+ inline void ProcessTransferEncodingLine(size_t line_idx);
+
+ void ProcessFirstLine(const char* begin,
+ const char* end);
+
+ void CleanUpKeyValueWhitespace(
+ const char* stream_begin,
+ const char* line_begin,
+ const char* current,
+ const char* line_end,
+ HeaderLineDescription* current_header_line);
+
+ void FindColonsAndParseIntoKeyValue();
+
+ void ProcessHeaderLines();
+
+ inline size_t ProcessHeaders(const char* message_start,
+ size_t message_length);
+
+ void AssignParseStateAfterHeadersHaveBeenParsed();
+
+ inline bool LineFramingFound(char current_char) {
+ return current_char == '\n';
+ }
+
+ // TODO(fenix): get rid of the following function and its uses (and
+ // replace with something more efficient)
+ inline bool HeaderFramingFound(char current_char) {
+ // Note that the 'if (current_char == '\n' ...)' test exists to ensure that
+ // the HeaderFramingMayBeFound test works properly. In benchmarking done on
+ // 2/13/2008, the 'if' actually speeds up performance of the function
+ // anyway..
+ if (current_char == '\n' || current_char == '\r') {
+ term_chars_ <<= 8;
+ // This is necessary IFF architecture has > 8 bit char. Alas, I'm
+ // paranoid.
+ term_chars_ |= current_char & 0xFF;
+
+ if ((term_chars_ & kValidTerm1Mask) == kValidTerm1) {
+ term_chars_ = 0;
+ return true;
+ }
+ if ((term_chars_ & kValidTerm2Mask) == kValidTerm2) {
+ term_chars_ = 0;
+ return true;
+ }
+ } else {
+ term_chars_ = 0;
+ }
+ return false;
+ }
+
+ inline bool HeaderFramingMayBeFound() const {
+ return term_chars_ != 0;
+ }
+
+ private:
+ class DoNothingBalsaVisitor : public BalsaVisitorInterface {
+ virtual void ProcessBodyInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessBodyData(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaderInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE {}
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char *line_input,
+ size_t line_length,
+ const char *version_input,
+ size_t version_length,
+ const char *status_input,
+ size_t status_length,
+ const char *reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char *input,
+ size_t size) OVERRIDE {}
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void MessageDone() OVERRIDE {}
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE {}
+ };
+
+ bool last_char_was_slash_r_;
+ bool saw_non_newline_char_;
+ bool start_was_space_;
+ bool chunk_length_character_extracted_;
+ bool is_request_; // This is not reset in Reset()
+ bool request_was_head_; // This is not reset in Reset()
+ size_t max_header_length_; // This is not reset in Reset()
+ size_t max_request_uri_length_; // This is not reset in Reset()
+ BalsaVisitorInterface* visitor_;
+ size_t chunk_length_remaining_;
+ size_t content_length_remaining_;
+ const char* last_slash_n_loc_;
+ const char* last_recorded_slash_n_loc_;
+ size_t last_slash_n_idx_;
+ uint32 term_chars_;
+ BalsaFrameEnums::ParseState parse_state_;
+ BalsaFrameEnums::ErrorCode last_error_;
+
+ Lines lines_;
+
+ BalsaHeaders* headers_; // This is not reset to NULL in Reset().
+ DoNothingBalsaVisitor do_nothing_visitor_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BALSA_FRAME_H_
+
diff --git a/chromium/net/tools/flip_server/balsa_frame_test.cc b/chromium/net/tools/flip_server/balsa_frame_test.cc
new file mode 100644
index 00000000000..3bb9a420b7e
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_frame_test.cc
@@ -0,0 +1,599 @@
+// Copyright 2013 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/tools/flip_server/balsa_frame.h"
+
+#include <iterator>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+using ::base::StringPiece;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::SaveArg;
+
+class Visitor : public BalsaVisitorInterface {
+ public:
+ virtual ~Visitor() {}
+ MOCK_METHOD2(ProcessBodyInput, void(const char*, size_t));
+ MOCK_METHOD2(ProcessBodyData, void(const char*, size_t));
+ MOCK_METHOD2(ProcessHeaderInput, void(const char*, size_t));
+ MOCK_METHOD2(ProcessTrailerInput, void(const char*, size_t));
+ MOCK_METHOD1(ProcessHeaders, void(const BalsaHeaders&));
+ MOCK_METHOD8(ProcessRequestFirstLine, void(const char*,
+ size_t,
+ const char*,
+ size_t,
+ const char*,
+ size_t,
+ const char*,
+ size_t));
+ MOCK_METHOD8(ProcessResponseFirstLine, void(const char*,
+ size_t,
+ const char*,
+ size_t,
+ const char*,
+ size_t,
+ const char*,
+ size_t));
+ MOCK_METHOD2(ProcessChunkExtensions, void(const char*, size_t));
+ MOCK_METHOD1(ProcessChunkLength, void(size_t));
+ MOCK_METHOD0(HeaderDone, void());
+ MOCK_METHOD0(MessageDone, void());
+ MOCK_METHOD1(HandleHeaderError, void(BalsaFrame*));
+ MOCK_METHOD1(HandleHeaderWarning, void(BalsaFrame*));
+ MOCK_METHOD1(HandleChunkingError, void(BalsaFrame*));
+ MOCK_METHOD1(HandleBodyError, void(BalsaFrame*));
+};
+
+class BalsaFrameTest : public ::testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ frame_.reset(new BalsaFrame);
+ frame_headers_.reset(new BalsaHeaders);
+ visitor_.reset(new Visitor);
+ frame_->set_balsa_visitor(visitor_.get());
+ };
+
+ protected:
+ scoped_ptr<BalsaFrame> frame_;
+ scoped_ptr<BalsaHeaders> frame_headers_;
+ scoped_ptr<Visitor> visitor_;
+};
+
+TEST_F(BalsaFrameTest, EmptyFrame) {
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ frame_->ParseState());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(NULL, frame_->const_balsa_headers());
+ ASSERT_EQ(NULL, frame_->balsa_headers());
+ ASSERT_EQ(NULL, frame_->headers());
+ ASSERT_EQ(NULL, frame_->mutable_headers());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+ ASSERT_TRUE(frame_->is_request());
+ ASSERT_FALSE(frame_->request_was_head());
+}
+
+TEST_F(BalsaFrameTest, EmptyRequest) {
+ const char input[] = "\r\n";
+ frame_->set_balsa_headers(frame_headers_.get());
+
+ {
+ InSequence s;
+ // No visitor callback should be called.
+ }
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ EXPECT_EQ(2u, read);
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ frame_->ParseState());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(BalsaFrameEnums::NO_ERROR, frame_->ErrorCode());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+TEST_F(BalsaFrameTest, GetRequest) {
+ const char input[] = "GET / HTTP/1.0\r\nkey1: value1\r\n\r\n";
+ const char* line = NULL;
+ size_t line_length = 0;
+ const char* method = NULL;
+ size_t method_length = 0;
+ const char* request_uri = NULL;
+ size_t request_uri_length = 0;
+ const char* version = NULL;
+ size_t version_length = 0;
+ const char* header = NULL;
+ size_t header_length = 0;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessRequestFirstLine(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<0>(&line),
+ SaveArg<1>(&line_length),
+ SaveArg<2>(&method),
+ SaveArg<3>(&method_length),
+ SaveArg<4>(&request_uri),
+ SaveArg<5>(&request_uri_length),
+ SaveArg<6>(&version),
+ SaveArg<7>(&version_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&header), SaveArg<1>(&header_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ ASSERT_EQ(frame_headers_.get(), frame_->const_balsa_headers());
+ ASSERT_EQ(frame_headers_.get(), frame_->balsa_headers());
+ ASSERT_EQ(frame_headers_.get(), frame_->headers());
+ ASSERT_EQ(frame_headers_.get(), frame_->mutable_headers());
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+ ASSERT_EQ("GET / HTTP/1.0", StringPiece(line, line_length));
+ ASSERT_EQ("GET", StringPiece(method, method_length));
+ ASSERT_EQ("/", StringPiece(request_uri, request_uri_length));
+ ASSERT_EQ("HTTP/1.0", StringPiece(version, version_length));
+ ASSERT_EQ(input, StringPiece(header, header_length));
+}
+
+TEST_F(BalsaFrameTest, HeadResponse) {
+ const char input[] = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n";
+ const char* line = NULL;
+ size_t line_length = 0;
+ const char* version = NULL;
+ size_t version_length = 0;
+ const char* status = NULL;
+ size_t status_length = 0;
+ const char* reason = NULL;
+ size_t reason_length = 0;
+ const char* header = NULL;
+ size_t header_length = 0;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+ frame_->set_request_was_head(true);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessResponseFirstLine(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<0>(&line),
+ SaveArg<1>(&line_length),
+ SaveArg<2>(&version),
+ SaveArg<3>(&version_length),
+ SaveArg<4>(&status),
+ SaveArg<5>(&status_length),
+ SaveArg<6>(&reason),
+ SaveArg<7>(&reason_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&header), SaveArg<1>(&header_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+
+ ASSERT_EQ("HTTP/1.1 200 OK", StringPiece(line, line_length));
+ ASSERT_EQ("HTTP/1.1", StringPiece(version, version_length));
+ ASSERT_EQ("200", StringPiece(status, status_length));
+ ASSERT_EQ("OK", StringPiece(reason, reason_length));
+ ASSERT_EQ("HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n",
+ StringPiece(header, header_length));
+}
+
+TEST_F(BalsaFrameTest, GetResponse) {
+ const char input[] = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello, world\r\n";
+ const char* line = NULL;
+ size_t line_length = 0;
+ const char* version = NULL;
+ size_t version_length = 0;
+ const char* status = NULL;
+ size_t status_length = 0;
+ const char* reason = NULL;
+ size_t reason_length = 0;
+ const char* header = NULL;
+ size_t header_length = 0;
+ const char* body = NULL;
+ size_t body_length = 0;
+ const char* body_data = NULL;
+ size_t body_data_length = 0;
+ testing::MockFunction<void(int)> checkpoint;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessResponseFirstLine(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<0>(&line),
+ SaveArg<1>(&line_length),
+ SaveArg<2>(&version),
+ SaveArg<3>(&version_length),
+ SaveArg<4>(&status),
+ SaveArg<5>(&status_length),
+ SaveArg<6>(&reason),
+ SaveArg<7>(&reason_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&header), SaveArg<1>(&header_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*visitor_, ProcessBodyInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body), SaveArg<1>(&body_length)));
+ EXPECT_CALL(*visitor_, ProcessBodyData(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body_data), SaveArg<1>(&body_data_length)));
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(65u, read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ checkpoint.Call(0);
+ read += frame_->ProcessInput(&input[read], strlen(input) - read);
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+
+ ASSERT_EQ("HTTP/1.1 200 OK", StringPiece(line, line_length));
+ ASSERT_EQ("HTTP/1.1", StringPiece(version, version_length));
+ ASSERT_EQ("200", StringPiece(status, status_length));
+ ASSERT_EQ("OK", StringPiece(reason, reason_length));
+ ASSERT_EQ("HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n",
+ StringPiece(header, header_length));
+ ASSERT_EQ("hello, world\r\n", StringPiece(body, body_length));
+ ASSERT_EQ("hello, world\r\n", StringPiece(body_data, body_data_length));
+}
+
+TEST_F(BalsaFrameTest, Reset) {
+ const char input[] = "GET / HTTP/1.0\r\nkey1: value1\r\n\r\n";
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessRequestFirstLine(_, _, _, _, _, _, _, _));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ frame_->set_balsa_headers(frame_headers_.get());
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+
+ frame_->Reset();
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ frame_->ParseState());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+}
+
+TEST_F(BalsaFrameTest, InvalidStatusCode) {
+ const char input[] = "HTTP/1.1 InvalidStatusCode OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello, world\r\n";
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, HandleHeaderError(frame_.get()));
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(30u, read);
+ ASSERT_EQ(BalsaFrameEnums::PARSE_ERROR, frame_->ParseState());
+ ASSERT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
+ frame_->ErrorCode());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_TRUE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+TEST_F(BalsaFrameTest, ResetError) {
+ const char input[] = "HTTP/1.1 InvalidStatusCode OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello, world\r\n";
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, HandleHeaderError(frame_.get()));
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(30u, read);
+ ASSERT_EQ(BalsaFrameEnums::PARSE_ERROR, frame_->ParseState());
+ ASSERT_EQ(BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT,
+ frame_->ErrorCode());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_TRUE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+
+ frame_->Reset();
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ frame_->ParseState());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+}
+
+TEST_F(BalsaFrameTest, RequestURITooLong) {
+ const char input[] = "GET / HTTP/1.0\r\n\r\n";
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_max_request_uri_length(0);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, HandleHeaderError(frame_.get()));
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(15u, read);
+ ASSERT_EQ(BalsaFrameEnums::PARSE_ERROR, frame_->ParseState());
+ ASSERT_EQ(BalsaFrameEnums::REQUEST_URI_TOO_LONG, frame_->ErrorCode());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_TRUE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+TEST_F(BalsaFrameTest, HeadersTooLong) {
+ const char input[] = "GET / HTTP/1.0\r\n\r\n";
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_max_header_length(0);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, HandleHeaderError(frame_.get()));
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(0u, read);
+ ASSERT_EQ(BalsaFrameEnums::PARSE_ERROR, frame_->ParseState());
+ ASSERT_EQ(BalsaFrameEnums::HEADERS_TOO_LONG, frame_->ErrorCode());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_TRUE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+TEST_F(BalsaFrameTest, InvalidHeader) {
+ const char input[] = "GET / HTTP/1.0\r\n"
+ "foo bar baz\r\n"
+ "Content-Type: text/plain\r\n\r\n";
+ const char* line = NULL;
+ size_t line_length = 0;
+ const char* method = NULL;
+ size_t method_length = 0;
+ const char* request_uri = NULL;
+ size_t request_uri_length = 0;
+ const char* version = NULL;
+ size_t version_length = 0;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessRequestFirstLine(_, _, _, _, _, _, _, _))
+ .WillOnce(DoAll(SaveArg<0>(&line),
+ SaveArg<1>(&line_length),
+ SaveArg<2>(&method),
+ SaveArg<3>(&method_length),
+ SaveArg<4>(&request_uri),
+ SaveArg<5>(&request_uri_length),
+ SaveArg<6>(&version),
+ SaveArg<7>(&version_length)));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _));
+ EXPECT_CALL(*visitor_, HandleHeaderWarning(frame_.get()));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_EQ(BalsaFrameEnums::HEADER_MISSING_COLON, frame_->ErrorCode());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+ ASSERT_EQ("GET / HTTP/1.0", StringPiece(line, line_length));
+ ASSERT_EQ("GET", StringPiece(method, method_length));
+ ASSERT_EQ("/", StringPiece(request_uri, request_uri_length));
+ ASSERT_EQ("HTTP/1.0", StringPiece(version, version_length));
+ ASSERT_EQ(2, std::distance(frame_headers_->header_lines_begin(),
+ frame_headers_->header_lines_end()));
+}
+
+TEST_F(BalsaFrameTest, GetResponseSplit) {
+ const char input[] = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello";
+ const char input2[] = ", world\r\n";
+ const char* body1 = NULL;
+ size_t body1_length = 0;
+ const char* body1_data = NULL;
+ size_t body1_data_length = 0;
+ const char* body2 = NULL;
+ size_t body2_length = 0;
+ const char* body2_data = NULL;
+ size_t body2_data_length = 0;
+ testing::MockFunction<void(int)> checkpoint;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessResponseFirstLine(_, _, _, _, _, _, _, _));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*visitor_, ProcessBodyInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body1), SaveArg<1>(&body1_length)));
+ EXPECT_CALL(*visitor_, ProcessBodyData(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body1_data),
+ SaveArg<1>(&body1_data_length)));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*visitor_, ProcessBodyInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body2), SaveArg<1>(&body2_length)));
+ EXPECT_CALL(*visitor_, ProcessBodyData(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&body2_data),
+ SaveArg<1>(&body2_data_length)));
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(65u, read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ checkpoint.Call(0);
+ read += frame_->ProcessInput(&input[read], strlen(input) - read);
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ checkpoint.Call(1);
+ ASSERT_EQ(9u, frame_->BytesSafeToSplice());
+ read = frame_->ProcessInput(input2, strlen(input2));
+ ASSERT_EQ(strlen(input2), read);
+
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+ ASSERT_EQ("hello", StringPiece(body1, body1_length));
+ ASSERT_EQ("hello", StringPiece(body1_data, body1_data_length));
+ ASSERT_EQ(", world\r\n", StringPiece(body2, body2_length));
+ ASSERT_EQ(", world\r\n", StringPiece(body2_data, body2_data_length));
+}
+
+TEST_F(BalsaFrameTest, GetResponseBytesSpliced) {
+ const char input[] = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello";
+ testing::MockFunction<void(int)> checkpoint;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessResponseFirstLine(_, _, _, _, _, _, _, _));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*visitor_, ProcessBodyInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessBodyData(_, _));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(checkpoint, Call(2));
+ EXPECT_CALL(*visitor_, MessageDone());
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(65u, read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ checkpoint.Call(0);
+ read += frame_->ProcessInput(&input[read], strlen(input) - read);
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ ASSERT_EQ(9u, frame_->BytesSafeToSplice());
+ checkpoint.Call(1);
+ frame_->BytesSpliced(5);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ ASSERT_EQ(4u, frame_->BytesSafeToSplice());
+ checkpoint.Call(2);
+ frame_->BytesSpliced(4);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ, frame_->ParseState());
+
+ ASSERT_TRUE(frame_->MessageFullyRead());
+ ASSERT_FALSE(frame_->Error());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+TEST_F(BalsaFrameTest, GetResponseBytesSplicedTooMany) {
+ const char input[] = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/plain\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello";
+ testing::MockFunction<void(int)> checkpoint;
+
+ frame_->set_balsa_headers(frame_headers_.get());
+ frame_->set_is_request(false);
+
+ {
+ InSequence s;
+ EXPECT_CALL(*visitor_, ProcessResponseFirstLine(_, _, _, _, _, _, _, _));
+ EXPECT_CALL(*visitor_, ProcessHeaderInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessHeaders(_));
+ EXPECT_CALL(*visitor_, HeaderDone());
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*visitor_, ProcessBodyInput(_, _));
+ EXPECT_CALL(*visitor_, ProcessBodyData(_, _));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*visitor_, HandleBodyError(frame_.get()));
+ }
+
+ size_t read = frame_->ProcessInput(input, strlen(input));
+ ASSERT_EQ(65u, read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ checkpoint.Call(0);
+ read += frame_->ProcessInput(&input[read], strlen(input) - read);
+ ASSERT_EQ(strlen(input), read);
+ ASSERT_EQ(BalsaFrameEnums::READING_CONTENT, frame_->ParseState());
+ ASSERT_EQ(9u, frame_->BytesSafeToSplice());
+ checkpoint.Call(1);
+ frame_->BytesSpliced(99);
+ ASSERT_EQ(BalsaFrameEnums::PARSE_ERROR, frame_->ParseState());
+ ASSERT_FALSE(frame_->MessageFullyRead());
+ ASSERT_TRUE(frame_->Error());
+ ASSERT_EQ(
+ BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT,
+ frame_->ErrorCode());
+ ASSERT_EQ(0u, frame_->BytesSafeToSplice());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/balsa_headers.cc b/chromium/net/tools/flip_server/balsa_headers.cc
new file mode 100644
index 00000000000..040c0128e11
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_headers.cc
@@ -0,0 +1,965 @@
+// Copyright (c) 2011 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/tools/flip_server/balsa_headers.h"
+
+#include <stdio.h>
+#include <algorithm>
+#include <ext/hash_set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/buffer_interface.h"
+#include "net/tools/flip_server/simple_buffer.h"
+#include "third_party/tcmalloc/chromium/src/base/googleinit.h"
+// #include "util/gtl/iterator_adaptors-inl.h"
+// #include "util/gtl/map-util.h"
+
+namespace {
+
+const char kContentLength[] = "Content-Length";
+const char kTransferEncoding[] = "Transfer-Encoding";
+const char kSpaceChar = ' ';
+
+__gnu_cxx::hash_set<base::StringPiece,
+ net::StringPieceCaseHash,
+ net::StringPieceCaseEqual> g_multivalued_headers;
+
+void InitMultivaluedHeaders() {
+ g_multivalued_headers.insert("accept");
+ g_multivalued_headers.insert("accept-charset");
+ g_multivalued_headers.insert("accept-encoding");
+ g_multivalued_headers.insert("accept-language");
+ g_multivalued_headers.insert("accept-ranges");
+ g_multivalued_headers.insert("allow");
+ g_multivalued_headers.insert("cache-control");
+ g_multivalued_headers.insert("connection");
+ g_multivalued_headers.insert("content-encoding");
+ g_multivalued_headers.insert("content-language");
+ g_multivalued_headers.insert("expect");
+ g_multivalued_headers.insert("if-match");
+ g_multivalued_headers.insert("if-none-match");
+ g_multivalued_headers.insert("pragma");
+ g_multivalued_headers.insert("proxy-authenticate");
+ g_multivalued_headers.insert("te");
+ g_multivalued_headers.insert("trailer");
+ g_multivalued_headers.insert("transfer-encoding");
+ g_multivalued_headers.insert("upgrade");
+ g_multivalued_headers.insert("vary");
+ g_multivalued_headers.insert("via");
+ g_multivalued_headers.insert("warning");
+ g_multivalued_headers.insert("www-authenticate");
+ // Not mentioned in RFC 2616, but it can have multiple values.
+ g_multivalued_headers.insert("set-cookie");
+}
+
+REGISTER_MODULE_INITIALIZER(multivalued_headers, InitMultivaluedHeaders());
+
+const int kFastToBufferSize = 32; // I think 22 is adequate, but anyway..
+
+} // namespace
+
+namespace net {
+
+const size_t BalsaBuffer::kDefaultBlocksize;
+
+BalsaHeaders::iterator_base::iterator_base() : headers_(NULL), idx_(0) { }
+
+BalsaHeaders::iterator_base::iterator_base(const iterator_base& it)
+ : headers_(it.headers_),
+ idx_(it.idx_) {
+}
+
+std::ostream& BalsaHeaders::iterator_base::operator<<(std::ostream& os) const {
+ os << "[" << this->headers_ << ", " << this->idx_ << "]";
+ return os;
+}
+
+BalsaHeaders::iterator_base::iterator_base(const BalsaHeaders* headers,
+ HeaderLines::size_type index)
+ : headers_(headers),
+ idx_(index) {
+}
+
+BalsaBuffer::~BalsaBuffer() {
+ CleanupBlocksStartingFrom(0);
+}
+
+// Returns the total amount of memory used by the buffer blocks.
+size_t BalsaBuffer::GetTotalBufferBlockSize() const {
+ size_t buffer_size = 0;
+ for (Blocks::const_iterator iter = blocks_.begin();
+ iter != blocks_.end();
+ ++iter) {
+ buffer_size += iter->buffer_size;
+ }
+ return buffer_size;
+}
+
+void BalsaBuffer::WriteToContiguousBuffer(const base::StringPiece& sp) {
+ if (sp.empty()) {
+ return;
+ }
+ CHECK(can_write_to_contiguous_buffer_);
+ DCHECK_GE(blocks_.size(), 1u);
+ if (blocks_[0].buffer == NULL && sp.size() <= blocksize_) {
+ blocks_[0] = AllocBlock();
+ memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size());
+ } else if (blocks_[0].bytes_free < sp.size()) {
+ // the first block isn't big enough, resize it.
+ const size_t old_storage_size_used = blocks_[0].bytes_used();
+ const size_t new_storage_size = old_storage_size_used + sp.size();
+ char* new_storage = new char[new_storage_size];
+ char* old_storage = blocks_[0].buffer;
+ if (old_storage_size_used) {
+ memcpy(new_storage, old_storage, old_storage_size_used);
+ }
+ memcpy(new_storage + old_storage_size_used, sp.data(), sp.size());
+ blocks_[0].buffer = new_storage;
+ blocks_[0].bytes_free = sp.size();
+ blocks_[0].buffer_size = new_storage_size;
+ delete[] old_storage;
+ } else {
+ memcpy(blocks_[0].start_of_unused_bytes(), sp.data(), sp.size());
+ }
+ blocks_[0].bytes_free -= sp.size();
+}
+
+base::StringPiece BalsaBuffer::Write(const base::StringPiece& sp,
+ Blocks::size_type* block_buffer_idx) {
+ if (sp.empty()) {
+ return sp;
+ }
+ char* storage = Reserve(sp.size(), block_buffer_idx);
+ memcpy(storage, sp.data(), sp.size());
+ return base::StringPiece(storage, sp.size());
+}
+
+char* BalsaBuffer::Reserve(size_t size,
+ Blocks::size_type* block_buffer_idx) {
+ // There should always be a 'first_block', even if it
+ // contains nothing.
+ DCHECK_GE(blocks_.size(), 1u);
+ BufferBlock* block = NULL;
+ Blocks::size_type block_idx = can_write_to_contiguous_buffer_ ? 1 : 0;
+ for (; block_idx < blocks_.size(); ++block_idx) {
+ if (blocks_[block_idx].bytes_free >= size) {
+ block = &blocks_[block_idx];
+ break;
+ }
+ }
+ if (block == NULL) {
+ if (blocksize_ < size) {
+ blocks_.push_back(AllocCustomBlock(size));
+ } else {
+ blocks_.push_back(AllocBlock());
+ }
+ block = &blocks_.back();
+ }
+
+ char* storage = block->start_of_unused_bytes();
+ block->bytes_free -= size;
+ if (block_buffer_idx) {
+ *block_buffer_idx = block_idx;
+ }
+ return storage;
+}
+
+void BalsaBuffer::Clear() {
+ CHECK(!blocks_.empty());
+ if (blocksize_ == blocks_[0].buffer_size) {
+ CleanupBlocksStartingFrom(1);
+ blocks_[0].bytes_free = blocks_[0].buffer_size;
+ } else {
+ CleanupBlocksStartingFrom(0);
+ blocks_.push_back(AllocBlock());
+ }
+ DCHECK_GE(blocks_.size(), 1u);
+ can_write_to_contiguous_buffer_ = true;
+}
+
+void BalsaBuffer::Swap(BalsaBuffer* b) {
+ blocks_.swap(b->blocks_);
+ std::swap(can_write_to_contiguous_buffer_,
+ b->can_write_to_contiguous_buffer_);
+ std::swap(blocksize_, b->blocksize_);
+}
+
+void BalsaBuffer::CopyFrom(const BalsaBuffer& b) {
+ CleanupBlocksStartingFrom(0);
+ blocks_.resize(b.blocks_.size());
+ for (Blocks::size_type i = 0; i < blocks_.size(); ++i) {
+ blocks_[i] = CopyBlock(b.blocks_[i]);
+ }
+ blocksize_ = b.blocksize_;
+ can_write_to_contiguous_buffer_ = b.can_write_to_contiguous_buffer_;
+}
+
+BalsaBuffer::BalsaBuffer()
+ : blocksize_(kDefaultBlocksize), can_write_to_contiguous_buffer_(true) {
+ blocks_.push_back(AllocBlock());
+}
+
+BalsaBuffer::BalsaBuffer(size_t blocksize) :
+ blocksize_(blocksize), can_write_to_contiguous_buffer_(true) {
+ blocks_.push_back(AllocBlock());
+}
+
+BalsaBuffer::BufferBlock BalsaBuffer::AllocBlock() {
+ return AllocCustomBlock(blocksize_);
+}
+
+BalsaBuffer::BufferBlock BalsaBuffer::AllocCustomBlock(size_t blocksize) {
+ return BufferBlock(new char[blocksize], blocksize, blocksize);
+}
+
+BalsaBuffer::BufferBlock BalsaBuffer::CopyBlock(const BufferBlock& b) {
+ BufferBlock block = b;
+ if (b.buffer == NULL) {
+ return block;
+ }
+
+ block.buffer = new char[b.buffer_size];
+ memcpy(block.buffer, b.buffer, b.bytes_used());
+ return block;
+}
+
+void BalsaBuffer::CleanupBlocksStartingFrom(Blocks::size_type start_idx) {
+ for (Blocks::size_type i = start_idx; i < blocks_.size(); ++i) {
+ delete[] blocks_[i].buffer;
+ }
+ blocks_.resize(start_idx);
+}
+
+BalsaHeaders::const_header_lines_key_iterator::const_header_lines_key_iterator(
+ const const_header_lines_key_iterator& other)
+ : iterator_base(other),
+ key_(other.key_) {
+}
+
+BalsaHeaders::const_header_lines_key_iterator::const_header_lines_key_iterator(
+ const BalsaHeaders* headers,
+ HeaderLines::size_type index,
+ const base::StringPiece& key)
+ : iterator_base(headers, index),
+ key_(key) {
+}
+
+BalsaHeaders::const_header_lines_key_iterator::const_header_lines_key_iterator(
+ const BalsaHeaders* headers,
+ HeaderLines::size_type index)
+ : iterator_base(headers, index) {
+}
+
+BalsaHeaders::BalsaHeaders()
+ : balsa_buffer_(4096),
+ content_length_(0),
+ content_length_status_(BalsaHeadersEnums::NO_CONTENT_LENGTH),
+ parsed_response_code_(0),
+ firstline_buffer_base_idx_(0),
+ whitespace_1_idx_(0),
+ non_whitespace_1_idx_(0),
+ whitespace_2_idx_(0),
+ non_whitespace_2_idx_(0),
+ whitespace_3_idx_(0),
+ non_whitespace_3_idx_(0),
+ whitespace_4_idx_(0),
+ end_of_firstline_idx_(0),
+ transfer_encoding_is_chunked_(false) {
+}
+
+BalsaHeaders::~BalsaHeaders() {}
+
+void BalsaHeaders::Clear() {
+ balsa_buffer_.Clear();
+ transfer_encoding_is_chunked_ = false;
+ content_length_ = 0;
+ content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
+ parsed_response_code_ = 0;
+ firstline_buffer_base_idx_ = 0;
+ whitespace_1_idx_ = 0;
+ non_whitespace_1_idx_ = 0;
+ whitespace_2_idx_ = 0;
+ non_whitespace_2_idx_ = 0;
+ whitespace_3_idx_ = 0;
+ non_whitespace_3_idx_ = 0;
+ whitespace_4_idx_ = 0;
+ end_of_firstline_idx_ = 0;
+ header_lines_.clear();
+}
+
+void BalsaHeaders::Swap(BalsaHeaders* other) {
+ // Protect against swapping with self.
+ if (this == other) return;
+
+ balsa_buffer_.Swap(&other->balsa_buffer_);
+
+ bool tmp_bool = transfer_encoding_is_chunked_;
+ transfer_encoding_is_chunked_ = other->transfer_encoding_is_chunked_;
+ other->transfer_encoding_is_chunked_ = tmp_bool;
+
+ size_t tmp_size_t = content_length_;
+ content_length_ = other->content_length_;
+ other->content_length_ = tmp_size_t;
+
+ BalsaHeadersEnums::ContentLengthStatus tmp_status =
+ content_length_status_;
+ content_length_status_ = other->content_length_status_;
+ other->content_length_status_ = tmp_status;
+
+ tmp_size_t = parsed_response_code_;
+ parsed_response_code_ = other->parsed_response_code_;
+ other->parsed_response_code_ = tmp_size_t;
+
+ BalsaBuffer::Blocks::size_type tmp_blk_idx = firstline_buffer_base_idx_;
+ firstline_buffer_base_idx_ = other->firstline_buffer_base_idx_;
+ other->firstline_buffer_base_idx_ = tmp_blk_idx;
+
+ tmp_size_t = whitespace_1_idx_;
+ whitespace_1_idx_ = other->whitespace_1_idx_;
+ other->whitespace_1_idx_ = tmp_size_t;
+
+ tmp_size_t = non_whitespace_1_idx_;
+ non_whitespace_1_idx_ = other->non_whitespace_1_idx_;
+ other->non_whitespace_1_idx_ = tmp_size_t;
+
+ tmp_size_t = whitespace_2_idx_;
+ whitespace_2_idx_ = other->whitespace_2_idx_;
+ other->whitespace_2_idx_ = tmp_size_t;
+
+ tmp_size_t = non_whitespace_2_idx_;
+ non_whitespace_2_idx_ = other->non_whitespace_2_idx_;
+ other->non_whitespace_2_idx_ = tmp_size_t;
+
+ tmp_size_t = whitespace_3_idx_;
+ whitespace_3_idx_ = other->whitespace_3_idx_;
+ other->whitespace_3_idx_ = tmp_size_t;
+
+ tmp_size_t = non_whitespace_3_idx_;
+ non_whitespace_3_idx_ = other->non_whitespace_3_idx_;
+ other->non_whitespace_3_idx_ = tmp_size_t;
+
+ tmp_size_t = whitespace_4_idx_;
+ whitespace_4_idx_ = other->whitespace_4_idx_;
+ other->whitespace_4_idx_ = tmp_size_t;
+
+ tmp_size_t = end_of_firstline_idx_;
+ end_of_firstline_idx_ = other->end_of_firstline_idx_;
+ other->end_of_firstline_idx_ = tmp_size_t;
+
+ swap(header_lines_, other->header_lines_);
+}
+
+void BalsaHeaders::CopyFrom(const BalsaHeaders& other) {
+ // Protect against copying with self.
+ if (this == &other) return;
+
+ balsa_buffer_.CopyFrom(other.balsa_buffer_);
+ transfer_encoding_is_chunked_ = other.transfer_encoding_is_chunked_;
+ content_length_ = other.content_length_;
+ content_length_status_ = other.content_length_status_;
+ parsed_response_code_ = other.parsed_response_code_;
+ firstline_buffer_base_idx_ = other.firstline_buffer_base_idx_;
+ whitespace_1_idx_ = other.whitespace_1_idx_;
+ non_whitespace_1_idx_ = other.non_whitespace_1_idx_;
+ whitespace_2_idx_ = other.whitespace_2_idx_;
+ non_whitespace_2_idx_ = other.non_whitespace_2_idx_;
+ whitespace_3_idx_ = other.whitespace_3_idx_;
+ non_whitespace_3_idx_ = other.non_whitespace_3_idx_;
+ whitespace_4_idx_ = other.whitespace_4_idx_;
+ end_of_firstline_idx_ = other.end_of_firstline_idx_;
+ header_lines_ = other.header_lines_;
+}
+
+void BalsaHeaders::AddAndMakeDescription(const base::StringPiece& key,
+ const base::StringPiece& value,
+ HeaderLineDescription* d) {
+ CHECK(d != NULL);
+ // + 2 to size for ": "
+ size_t line_size = key.size() + 2 + value.size();
+ BalsaBuffer::Blocks::size_type block_buffer_idx = 0;
+ char* storage = balsa_buffer_.Reserve(line_size, &block_buffer_idx);
+ size_t base_idx = storage - GetPtr(block_buffer_idx);
+
+ char* cur_loc = storage;
+ memcpy(cur_loc, key.data(), key.size());
+ cur_loc += key.size();
+ *cur_loc = ':';
+ ++cur_loc;
+ *cur_loc = ' ';
+ ++cur_loc;
+ memcpy(cur_loc, value.data(), value.size());
+ *d = HeaderLineDescription(base_idx,
+ base_idx + key.size(),
+ base_idx + key.size() + 2,
+ base_idx + key.size() + 2 + value.size(),
+ block_buffer_idx);
+}
+
+void BalsaHeaders::AppendOrPrependAndMakeDescription(
+ const base::StringPiece& key,
+ const base::StringPiece& value,
+ bool append,
+ HeaderLineDescription* d) {
+ // Figure out how much space we need to reserve for the new header size.
+ size_t old_value_size = d->last_char_idx - d->value_begin_idx;
+ if (old_value_size == 0) {
+ AddAndMakeDescription(key, value, d);
+ return;
+ }
+ base::StringPiece old_value(GetPtr(d->buffer_base_idx) + d->value_begin_idx,
+ old_value_size);
+
+ BalsaBuffer::Blocks::size_type block_buffer_idx = 0;
+ // + 3 because we potentially need to add ": ", and "," to the line.
+ size_t new_size = key.size() + 3 + old_value_size + value.size();
+ char* storage = balsa_buffer_.Reserve(new_size, &block_buffer_idx);
+ size_t base_idx = storage - GetPtr(block_buffer_idx);
+
+ base::StringPiece first_value = old_value;
+ base::StringPiece second_value = value;
+ if (!append) { // !append == prepend
+ first_value = value;
+ second_value = old_value;
+ }
+ char* cur_loc = storage;
+ memcpy(cur_loc, key.data(), key.size());
+ cur_loc += key.size();
+ *cur_loc = ':';
+ ++cur_loc;
+ *cur_loc = ' ';
+ ++cur_loc;
+ memcpy(cur_loc, first_value.data(), first_value.size());
+ cur_loc += first_value.size();
+ *cur_loc = ',';
+ ++cur_loc;
+ memcpy(cur_loc, second_value.data(), second_value.size());
+
+ *d = HeaderLineDescription(base_idx,
+ base_idx + key.size(),
+ base_idx + key.size() + 2,
+ base_idx + new_size,
+ block_buffer_idx);
+}
+
+// Removes all keys value pairs with key 'key' starting at 'start'.
+void BalsaHeaders::RemoveAllOfHeaderStartingAt(const base::StringPiece& key,
+ HeaderLines::iterator start) {
+ while (start != header_lines_.end()) {
+ start->skip = true;
+ ++start;
+ start = GetHeaderLinesIterator(key, start);
+ }
+}
+
+void BalsaHeaders::HackHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ // See TODO in balsa_headers.h
+ const HeaderLines::iterator end = header_lines_.end();
+ const HeaderLines::iterator begin = header_lines_.begin();
+ HeaderLines::iterator i = GetHeaderLinesIteratorNoSkip(key, begin);
+ if (i != end) {
+ // First, remove all of the header lines including this one. We want to
+ // remove before replacing, in case our replacement ends up being appended
+ // at the end (and thus would be removed by this call)
+ RemoveAllOfHeaderStartingAt(key, i);
+ // Now add the replacement, at this location.
+ AddAndMakeDescription(key, value, &(*i));
+ return;
+ }
+ AppendHeader(key, value);
+}
+
+void BalsaHeaders::HackAppendToHeader(const base::StringPiece& key,
+ const base::StringPiece& append_value) {
+ // See TODO in balsa_headers.h
+ const HeaderLines::iterator end = header_lines_.end();
+ const HeaderLines::iterator begin = header_lines_.begin();
+
+ HeaderLines::iterator i = GetHeaderLinesIterator(key, begin);
+ if (i == end) {
+ HackHeader(key, append_value);
+ return;
+ }
+
+ AppendOrPrependAndMakeDescription(key, append_value, true, &(*i));
+}
+
+void BalsaHeaders::ReplaceOrAppendHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ const HeaderLines::iterator end = header_lines_.end();
+ const HeaderLines::iterator begin = header_lines_.begin();
+ HeaderLines::iterator i = GetHeaderLinesIterator(key, begin);
+ if (i != end) {
+ // First, remove all of the header lines including this one. We want to
+ // remove before replacing, in case our replacement ends up being appended
+ // at the end (and thus would be removed by this call)
+ RemoveAllOfHeaderStartingAt(key, i);
+ // Now, take the first instance and replace it. This will remove the
+ // 'skipped' tag if the replacement is done in-place.
+ AddAndMakeDescription(key, value, &(*i));
+ return;
+ }
+ AppendHeader(key, value);
+}
+
+void BalsaHeaders::AppendHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ HeaderLineDescription hld;
+ AddAndMakeDescription(key, value, &hld);
+ header_lines_.push_back(hld);
+}
+
+void BalsaHeaders::AppendToHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ AppendOrPrependToHeader(key, value, true);
+}
+
+void BalsaHeaders::PrependToHeader(const base::StringPiece& key,
+ const base::StringPiece& value) {
+ AppendOrPrependToHeader(key, value, false);
+}
+
+base::StringPiece BalsaHeaders::GetValueFromHeaderLineDescription(
+ const HeaderLineDescription& line) const {
+ DCHECK_GE(line.last_char_idx, line.value_begin_idx);
+ return base::StringPiece(GetPtr(line.buffer_base_idx) + line.value_begin_idx,
+ line.last_char_idx - line.value_begin_idx);
+}
+
+const base::StringPiece BalsaHeaders::GetHeader(
+ const base::StringPiece& key) const {
+ DCHECK(!IsMultivaluedHeader(key))
+ << "Header '" << key << "' may consist of multiple lines. Do not "
+ << "use BalsaHeaders::GetHeader() or you may be missing some of its "
+ << "values.";
+ const HeaderLines::const_iterator end = header_lines_.end();
+ const HeaderLines::const_iterator begin = header_lines_.begin();
+ HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key, begin);
+ if (i == end) {
+ return base::StringPiece(NULL, 0);
+ }
+ return GetValueFromHeaderLineDescription(*i);
+}
+
+BalsaHeaders::const_header_lines_iterator BalsaHeaders::GetHeaderPosition(
+ const base::StringPiece& key) const {
+ const HeaderLines::const_iterator end = header_lines_.end();
+ const HeaderLines::const_iterator begin = header_lines_.begin();
+ HeaderLines::const_iterator i = GetConstHeaderLinesIterator(key, begin);
+ if (i == end) {
+ return header_lines_end();
+ }
+
+ return const_header_lines_iterator(this, (i - begin));
+}
+
+BalsaHeaders::const_header_lines_key_iterator BalsaHeaders::GetIteratorForKey(
+ const base::StringPiece& key) const {
+ HeaderLines::const_iterator i =
+ GetConstHeaderLinesIterator(key, header_lines_.begin());
+ if (i == header_lines_.end()) {
+ return header_lines_key_end();
+ }
+
+ const HeaderLines::const_iterator begin = header_lines_.begin();
+ return const_header_lines_key_iterator(this, (i - begin), key);
+}
+
+void BalsaHeaders::AppendOrPrependToHeader(const base::StringPiece& key,
+ const base::StringPiece& value,
+ bool append) {
+ HeaderLines::iterator i = GetHeaderLinesIterator(key, header_lines_.begin());
+ if (i == header_lines_.end()) {
+ // The header did not exist already. Instead of appending to an existing
+ // header simply append the key/value pair to the headers.
+ AppendHeader(key, value);
+ return;
+ }
+ HeaderLineDescription hld = *i;
+
+ AppendOrPrependAndMakeDescription(key, value, append, &hld);
+
+ // Invalidate the old header line and add the new one.
+ i->skip = true;
+ header_lines_.push_back(hld);
+}
+
+BalsaHeaders::HeaderLines::const_iterator
+BalsaHeaders::GetConstHeaderLinesIterator(
+ const base::StringPiece& key,
+ BalsaHeaders::HeaderLines::const_iterator start) const {
+ const HeaderLines::const_iterator end = header_lines_.end();
+ for (HeaderLines::const_iterator i = start; i != end; ++i) {
+ const HeaderLineDescription& line = *i;
+ if (line.skip) {
+ continue;
+ }
+ const size_t key_len = line.key_end_idx - line.first_char_idx;
+
+ if (key_len != key.size()) {
+ continue;
+ }
+ if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx,
+ key.data(), key_len) == 0) {
+ DCHECK_GE(line.last_char_idx, line.value_begin_idx);
+ return i;
+ }
+ }
+ return end;
+}
+
+BalsaHeaders::HeaderLines::iterator BalsaHeaders::GetHeaderLinesIteratorNoSkip(
+ const base::StringPiece& key,
+ BalsaHeaders::HeaderLines::iterator start) {
+ const HeaderLines::iterator end = header_lines_.end();
+ for (HeaderLines::iterator i = start; i != end; ++i) {
+ const HeaderLineDescription& line = *i;
+ const size_t key_len = line.key_end_idx - line.first_char_idx;
+
+ if (key_len != key.size()) {
+ continue;
+ }
+ if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx,
+ key.data(), key_len) == 0) {
+ DCHECK_GE(line.last_char_idx, line.value_begin_idx);
+ return i;
+ }
+ }
+ return end;
+}
+
+BalsaHeaders::HeaderLines::iterator BalsaHeaders::GetHeaderLinesIterator(
+ const base::StringPiece& key,
+ BalsaHeaders::HeaderLines::iterator start) {
+ const HeaderLines::iterator end = header_lines_.end();
+ for (HeaderLines::iterator i = start; i != end; ++i) {
+ const HeaderLineDescription& line = *i;
+ if (line.skip) {
+ continue;
+ }
+ const size_t key_len = line.key_end_idx - line.first_char_idx;
+
+ if (key_len != key.size()) {
+ continue;
+ }
+ if (strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx,
+ key.data(), key_len) == 0) {
+ DCHECK_GE(line.last_char_idx, line.value_begin_idx);
+ return i;
+ }
+ }
+ return end;
+}
+
+void BalsaHeaders::GetAllOfHeader(
+ const base::StringPiece& key, std::vector<base::StringPiece>* out) const {
+ for (const_header_lines_key_iterator it = GetIteratorForKey(key);
+ it != header_lines_end(); ++it) {
+ out->push_back(it->second);
+ }
+}
+
+bool BalsaHeaders::HasNonEmptyHeader(const base::StringPiece& key) const {
+ for (const_header_lines_key_iterator it = GetIteratorForKey(key);
+ it != header_lines_key_end(); ++it) {
+ if (!it->second.empty())
+ return true;
+ }
+ return false;
+}
+
+void BalsaHeaders::GetAllOfHeaderAsString(const base::StringPiece& key,
+ std::string* out) const {
+ const_header_lines_iterator it = header_lines_begin();
+ const_header_lines_iterator end = header_lines_end();
+
+ for (; it != end; ++it) {
+ if (key == it->first) {
+ if (!out->empty()) {
+ out->append(",");
+ }
+ out->append(std::string(it->second.data(), it->second.size()));
+ }
+ }
+}
+
+// static
+bool BalsaHeaders::IsMultivaluedHeader(const base::StringPiece& header) {
+ return g_multivalued_headers.find(header) != g_multivalued_headers.end();
+}
+
+void BalsaHeaders::RemoveAllOfHeader(const base::StringPiece& key) {
+ HeaderLines::iterator it = GetHeaderLinesIterator(key, header_lines_.begin());
+ RemoveAllOfHeaderStartingAt(key, it);
+}
+
+void BalsaHeaders::RemoveAllHeadersWithPrefix(const base::StringPiece& key) {
+ for (HeaderLines::size_type i = 0; i < header_lines_.size(); ++i) {
+ if (header_lines_[i].skip) {
+ continue;
+ }
+ HeaderLineDescription& line = header_lines_[i];
+ const size_t key_len = line.key_end_idx - line.first_char_idx;
+ if (key_len < key.size()) {
+ // If the key given to us is longer than this header, don't consider it.
+ continue;
+ }
+ if (!strncasecmp(GetPtr(line.buffer_base_idx) + line.first_char_idx,
+ key.data(), key.size())) {
+ line.skip = true;
+ }
+ }
+}
+
+size_t BalsaHeaders::GetMemoryUsedLowerBound() const {
+ return (sizeof(*this) +
+ balsa_buffer_.GetTotalBufferBlockSize() +
+ header_lines_.capacity() * sizeof(HeaderLineDescription));
+}
+
+size_t BalsaHeaders::GetSizeForWriteBuffer() const {
+ // First add the space required for the first line + CRLF
+ size_t write_buf_size = whitespace_4_idx_ - non_whitespace_1_idx_ + 2;
+ // Then add the space needed for each header line to write out + CRLF.
+ const HeaderLines::size_type end = header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < end; ++i) {
+ const HeaderLineDescription& line = header_lines_[i];
+ if (!line.skip) {
+ // Add the key size and ": ".
+ write_buf_size += line.key_end_idx - line.first_char_idx + 2;
+ // Add the value size and the CRLF
+ write_buf_size += line.last_char_idx - line.value_begin_idx + 2;
+ }
+ }
+ // Finally tag on the terminal CRLF.
+ return write_buf_size + 2;
+}
+
+void BalsaHeaders::DumpToString(std::string* str) const {
+ const base::StringPiece firstline = first_line();
+ const int buffer_length =
+ OriginalHeaderStreamEnd() - OriginalHeaderStreamBegin();
+ // First check whether the header object is empty.
+ if (firstline.empty() && buffer_length == 0) {
+ str->append("\n<empty header>\n");
+ return;
+ }
+
+ // Then check whether the header is in a partially parsed state. If so, just
+ // dump the raw data.
+ if (balsa_buffer_.can_write_to_contiguous_buffer()) {
+ base::StringAppendF(str, "\n<incomplete header len: %d>\n%.*s\n",
+ buffer_length, buffer_length,
+ OriginalHeaderStreamBegin());
+ return;
+ }
+
+ // If the header is complete, then just dump them with the logical key value
+ // pair.
+ str->reserve(str->size() + GetSizeForWriteBuffer());
+ base::StringAppendF(str, "\n %.*s\n",
+ static_cast<int>(firstline.size()),
+ firstline.data());
+ BalsaHeaders::const_header_lines_iterator i = header_lines_begin();
+ for (; i != header_lines_end(); ++i) {
+ base::StringAppendF(str, " %.*s: %.*s\n",
+ static_cast<int>(i->first.size()), i->first.data(),
+ static_cast<int>(i->second.size()), i->second.data());
+ }
+}
+
+void BalsaHeaders::SetFirstLine(const base::StringPiece& line) {
+ base::StringPiece new_line = balsa_buffer_.Write(line,
+ &firstline_buffer_base_idx_);
+ whitespace_1_idx_ = new_line.data() - GetPtr(firstline_buffer_base_idx_);
+ non_whitespace_1_idx_ = whitespace_1_idx_;
+ whitespace_4_idx_ = whitespace_1_idx_ + line.size();
+ whitespace_2_idx_ = whitespace_4_idx_;
+ non_whitespace_2_idx_ = whitespace_4_idx_;
+ whitespace_3_idx_ = whitespace_4_idx_;
+ non_whitespace_3_idx_ = whitespace_4_idx_;
+ end_of_firstline_idx_ = whitespace_4_idx_;
+}
+
+void BalsaHeaders::SetContentLength(size_t length) {
+ // If the content-length is already the one we want, don't do anything.
+ if (content_length_status_ == BalsaHeadersEnums::VALID_CONTENT_LENGTH &&
+ content_length_ == length) {
+ return;
+ }
+ const base::StringPiece content_length(kContentLength,
+ sizeof(kContentLength) - 1);
+ // If header state indicates that there is either a content length or
+ // transfer encoding header, remove them before adding the new content
+ // length. There is always the possibility that client can manually add
+ // either header directly and cause content_length_status_ or
+ // transfer_encoding_is_chunked_ to be inconsistent with the actual header.
+ // In the interest of efficiency, however, we will assume that clients will
+ // use the header object correctly and thus we will not scan the all headers
+ // each time this function is called.
+ if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH) {
+ RemoveAllOfHeader(content_length);
+ } else if (transfer_encoding_is_chunked_) {
+ const base::StringPiece transfer_encoding(kTransferEncoding,
+ sizeof(kTransferEncoding) - 1);
+ RemoveAllOfHeader(transfer_encoding);
+ transfer_encoding_is_chunked_ = false;
+ }
+ content_length_status_ = BalsaHeadersEnums::VALID_CONTENT_LENGTH;
+ content_length_ = length;
+ // FastUInt64ToBuffer is supposed to use a maximum of kFastToBufferSize bytes.
+ char buffer[kFastToBufferSize];
+ int len_converted = snprintf(buffer, sizeof(buffer), "%zd", length);
+ CHECK_GT(len_converted, 0);
+ const base::StringPiece length_str(buffer, len_converted);
+ AppendHeader(content_length, length_str);
+}
+
+void BalsaHeaders::SetChunkEncoding(bool chunk_encode) {
+ if (transfer_encoding_is_chunked_ == chunk_encode) {
+ return;
+ }
+ if (content_length_status_ != BalsaHeadersEnums::NO_CONTENT_LENGTH &&
+ chunk_encode) {
+ // Want to change to chunk encoding, but have content length. Arguably we
+ // can leave this step out, since transfer-encoding overrides
+ // content-length.
+ const base::StringPiece content_length(kContentLength,
+ sizeof(kContentLength) - 1);
+ RemoveAllOfHeader(content_length);
+ content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH;
+ content_length_ = 0;
+ }
+ const base::StringPiece transfer_encoding(kTransferEncoding,
+ sizeof(kTransferEncoding) - 1);
+ if (chunk_encode) {
+ const char kChunked[] = "chunked";
+ const base::StringPiece chunked(kChunked, sizeof(kChunked) - 1);
+ AppendHeader(transfer_encoding, chunked);
+ } else {
+ RemoveAllOfHeader(transfer_encoding);
+ }
+ transfer_encoding_is_chunked_ = chunk_encode;
+}
+
+// See the comment about this function in the header file for a
+// warning about its usage.
+void BalsaHeaders::SetFirstlineFromStringPieces(
+ const base::StringPiece& firstline_a,
+ const base::StringPiece& firstline_b,
+ const base::StringPiece& firstline_c) {
+ size_t line_size = (firstline_a.size() +
+ firstline_b.size() +
+ firstline_c.size() +
+ 2);
+ char* storage = balsa_buffer_.Reserve(line_size, &firstline_buffer_base_idx_);
+ char* cur_loc = storage;
+
+ memcpy(cur_loc, firstline_a.data(), firstline_a.size());
+ cur_loc += firstline_a.size();
+
+ *cur_loc = ' ';
+ ++cur_loc;
+
+ memcpy(cur_loc, firstline_b.data(), firstline_b.size());
+ cur_loc += firstline_b.size();
+
+ *cur_loc = ' ';
+ ++cur_loc;
+
+ memcpy(cur_loc, firstline_c.data(), firstline_c.size());
+
+ whitespace_1_idx_ = storage - GetPtr(firstline_buffer_base_idx_);
+ non_whitespace_1_idx_ = whitespace_1_idx_;
+ whitespace_2_idx_ = non_whitespace_1_idx_ + firstline_a.size();
+ non_whitespace_2_idx_ = whitespace_2_idx_ + 1;
+ whitespace_3_idx_ = non_whitespace_2_idx_ + firstline_b.size();
+ non_whitespace_3_idx_ = whitespace_3_idx_ + 1;
+ whitespace_4_idx_ = non_whitespace_3_idx_ + firstline_c.size();
+ end_of_firstline_idx_ = whitespace_4_idx_;
+}
+
+void BalsaHeaders::SetRequestMethod(const base::StringPiece& method) {
+ // This is the first of the three parts of the firstline.
+ if (method.size() <= (whitespace_2_idx_ - non_whitespace_1_idx_)) {
+ non_whitespace_1_idx_ = whitespace_2_idx_ - method.size();
+ char* stream_begin = GetPtr(firstline_buffer_base_idx_);
+ memcpy(stream_begin + non_whitespace_1_idx_,
+ method.data(),
+ method.size());
+ } else {
+ // The new method is too large to fit in the space available for the old
+ // one, so we have to reformat the firstline.
+ SetFirstlineFromStringPieces(method, request_uri(), request_version());
+ }
+}
+
+void BalsaHeaders::SetResponseVersion(const base::StringPiece& version) {
+ // Note: There is no difference between request_method() and
+ // response_Version(). Thus, a function to set one is equivalent to a
+ // function to set the other. We maintain two functions for this as it is
+ // much more descriptive, and makes code more understandable.
+ SetRequestMethod(version);
+}
+
+void BalsaHeaders::SetRequestUri(const base::StringPiece& uri) {
+ SetFirstlineFromStringPieces(request_method(), uri, request_version());
+}
+
+void BalsaHeaders::SetResponseCode(const base::StringPiece& code) {
+ // Note: There is no difference between request_uri() and response_code().
+ // Thus, a function to set one is equivalent to a function to set the other.
+ // We maintain two functions for this as it is much more descriptive, and
+ // makes code more understandable.
+ SetRequestUri(code);
+}
+
+void BalsaHeaders::SetParsedResponseCodeAndUpdateFirstline(
+ size_t parsed_response_code) {
+ char buffer[kFastToBufferSize];
+ int len_converted = snprintf(buffer, sizeof(buffer),
+ "%zd", parsed_response_code);
+ CHECK_GT(len_converted, 0);
+ SetResponseCode(base::StringPiece(buffer, len_converted));
+}
+
+void BalsaHeaders::SetRequestVersion(const base::StringPiece& version) {
+ // This is the last of the three parts of the firstline.
+ // Since whitespace_3_idx and non_whitespace_3_idx may point to the same
+ // place, we ensure below that any available space includes space for a
+ // litteral space (' ') character between the second component and the third
+ // component. If the space between whitespace_3_idx_ and
+ // end_of_firstline_idx_ is >= to version.size() + 1 (for the space), then we
+ // can update the firstline in-place.
+ char* stream_begin = GetPtr(firstline_buffer_base_idx_);
+ if (version.size() + 1 <= end_of_firstline_idx_ - whitespace_3_idx_) {
+ *(stream_begin + whitespace_3_idx_) = kSpaceChar;
+ non_whitespace_3_idx_ = whitespace_3_idx_ + 1;
+ whitespace_4_idx_ = non_whitespace_3_idx_ + version.size();
+ memcpy(stream_begin + non_whitespace_3_idx_,
+ version.data(),
+ version.size());
+ } else {
+ // The new version is to large to fit in the space available for the old
+ // one, so we have to reformat the firstline.
+ SetFirstlineFromStringPieces(request_method(), request_uri(), version);
+ }
+}
+
+void BalsaHeaders::SetResponseReasonPhrase(const base::StringPiece& reason) {
+ // Note: There is no difference between request_version() and
+ // response_reason_phrase(). Thus, a function to set one is equivalent to a
+ // function to set the other. We maintain two functions for this as it is
+ // much more descriptive, and makes code more understandable.
+ SetRequestVersion(reason);
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/balsa_headers.h b/chromium/net/tools/flip_server/balsa_headers.h
new file mode 100644
index 00000000000..2c8b08362c7
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_headers.h
@@ -0,0 +1,1138 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_BALSA_HEADERS_H_
+#define NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_H_
+
+#include <algorithm>
+#include <iosfwd>
+#include <iterator>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/port.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/string_piece_utils.h"
+
+namespace net {
+
+// WARNING:
+// Note that -no- char* returned by any function in this
+// file is null-terminated.
+
+// This class exists to service the specific needs of BalsaHeaders.
+//
+// Functional goals:
+// 1) provide a backing-store for all of the StringPieces that BalsaHeaders
+// returns. Every StringPiece returned from BalsaHeaders should remain
+// valid until the BalsaHeader's object is cleared, or the header-line is
+// erased.
+// 2) provide a backing-store for BalsaFrame, which requires contiguous memory
+// for its fast-path parsing functions. Note that the cost of copying is
+// less than the cost of requiring the parser to do slow-path parsing, as
+// it would have to check for bounds every byte, instead of every 16 bytes.
+//
+// This class is optimized for the case where headers are stored in one of two
+// buffers. It doesn't make a lot of effort to densely pack memory-- in fact,
+// it -may- be somewhat memory inefficient. This possible inefficiency allows a
+// certain simplicity of implementation and speed which makes it worthwhile.
+// If, in the future, better memory density is required, it should be possible
+// to reuse the abstraction presented by this object to achieve those goals.
+//
+// In the most common use-case, this memory inefficiency should be relatively
+// small.
+//
+// Alternate implementations of BalsaBuffer may include:
+// - vector of strings, one per header line (similar to HTTPHeaders)
+// - densely packed strings:
+// - keep a sorted array/map of free-space linked lists or numbers.
+// - use the entry that most closely first your needs.
+// - at this point, perhaps just use a vector of strings, and let
+// the allocator do the right thing.
+//
+class BalsaBuffer {
+ public:
+ static const size_t kDefaultBlocksize = 4096;
+ // We have two friends here. These exist as friends as we
+ // want to allow access to the constructors for the test
+ // class and the Balsa* classes. We put this into the
+ // header file as we want this class to be inlined into the
+ // BalsaHeaders implementation, yet be testable.
+ friend class BalsaBufferTestSpouse;
+ friend class BalsaHeaders;
+ friend class BalsaBufferTest;
+
+ // The BufferBlock is a structure used internally by the
+ // BalsaBuffer class to store the base buffer pointers to
+ // each block, as well as the important metadata for buffer
+ // sizes and bytes free.
+ struct BufferBlock {
+ public:
+ char* buffer;
+ size_t buffer_size;
+ size_t bytes_free;
+
+ size_t bytes_used() const {
+ return buffer_size - bytes_free;
+ }
+ char* start_of_unused_bytes() const {
+ return buffer + bytes_used();
+ }
+
+ BufferBlock() : buffer(NULL), buffer_size(0), bytes_free(0) {}
+ ~BufferBlock() {}
+
+ BufferBlock(char* buf, size_t size, size_t free) :
+ buffer(buf), buffer_size(size), bytes_free(free) {}
+ // Yes we want this to be copyable (it gets stuck into vectors).
+ // For this reason, we don't use scoped ptrs, etc. here-- it
+ // is more efficient to manage this memory externally to this
+ // object.
+ };
+
+ typedef std::vector<BufferBlock> Blocks;
+
+ ~BalsaBuffer();
+
+ // Returns the total amount of memory used by the buffer blocks.
+ size_t GetTotalBufferBlockSize() const;
+
+ const char* GetPtr(Blocks::size_type block_idx) const {
+ DCHECK_LT(block_idx, blocks_.size())
+ << block_idx << ", " << blocks_.size();
+ return blocks_[block_idx].buffer;
+ }
+
+ char* GetPtr(Blocks::size_type block_idx) {
+ DCHECK_LT(block_idx, blocks_.size())
+ << block_idx << ", " << blocks_.size();
+ return blocks_[block_idx].buffer;
+ }
+
+ // This function is different from Write(), as it ensures that the data
+ // stored via subsequent calls to this function are all contiguous (and in
+ // the order in which these writes happened). This is essentially the same
+ // as a string append.
+ //
+ // You may call this function at any time between object
+ // construction/Clear(), and the calling of the
+ // NoMoreWriteToContiguousBuffer() function.
+ //
+ // You must not call this function after the NoMoreWriteToContiguousBuffer()
+ // function is called, unless a Clear() has been called since.
+ // If you do, the program will abort().
+ //
+ // This condition is placed upon this code so that calls to Write() can
+ // append to the buffer in the first block safely, and without invaliding
+ // the StringPiece which it returns.
+ //
+ // This function's main intended user is the BalsaFrame class, which,
+ // for reasons of efficiency, requires that the buffer from which it parses
+ // the headers be contiguous.
+ //
+ void WriteToContiguousBuffer(const base::StringPiece& sp);
+
+ void NoMoreWriteToContiguousBuffer() {
+ can_write_to_contiguous_buffer_ = false;
+ }
+
+ // Takes a StringPiece and writes it to "permanent" storage, then returns a
+ // StringPiece which points to that data. If block_idx != NULL, it will be
+ // assigned the index of the block into which the data was stored.
+ // Note that the 'permanent' storage in which it stores data may be in
+ // the first block IFF the NoMoreWriteToContiguousBuffer function has
+ // been called since the last Clear/Construction.
+ base::StringPiece Write(const base::StringPiece& sp,
+ Blocks::size_type* block_buffer_idx);
+
+ // Reserves "permanent" storage of the size indicated. Returns a pointer to
+ // the beginning of that storage, and assigns the index of the block used to
+ // block_buffer_idx. This function uses the first block IFF the
+ // NoMoreWriteToContiguousBuffer function has been called since the last
+ // Clear/Construction.
+ char* Reserve(size_t size, Blocks::size_type* block_buffer_idx);
+
+ void Clear();
+
+ void Swap(BalsaBuffer* b);
+
+ void CopyFrom(const BalsaBuffer& b);
+
+ const char* StartOfFirstBlock() const {
+ return blocks_[0].buffer;
+ }
+
+ const char* EndOfFirstBlock() const {
+ return blocks_[0].buffer + blocks_[0].bytes_used();
+ }
+
+ bool can_write_to_contiguous_buffer() const {
+ return can_write_to_contiguous_buffer_;
+ }
+ size_t blocksize() const { return blocksize_; }
+ Blocks::size_type num_blocks() const { return blocks_.size(); }
+ size_t buffer_size(size_t idx) const { return blocks_[idx].buffer_size; }
+ size_t bytes_used(size_t idx) const { return blocks_[idx].bytes_used(); }
+
+ protected:
+ BalsaBuffer();
+
+ explicit BalsaBuffer(size_t blocksize);
+
+ BufferBlock AllocBlock();
+
+ BufferBlock AllocCustomBlock(size_t blocksize);
+
+ BufferBlock CopyBlock(const BufferBlock& b);
+
+ // Cleans up the object.
+ // The block at start_idx, and all subsequent blocks
+ // will be cleared and have associated memory deleted.
+ void CleanupBlocksStartingFrom(Blocks::size_type start_idx);
+
+ // A container of BufferBlocks
+ Blocks blocks_;
+
+ // The default allocation size for a block.
+ // In general, blocksize_ bytes will be allocated for
+ // each buffer.
+ size_t blocksize_;
+
+ // If set to true, then the first block cannot be used for Write() calls as
+ // the WriteToContiguous... function will modify the base pointer for this
+ // block, and the Write() calls need to be sure that the base pointer will
+ // not be changing in order to provide the user with StringPieces which
+ // continue to be valid.
+ bool can_write_to_contiguous_buffer_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// All of the functions in the BalsaHeaders class use string pieces, by either
+// using the StringPiece class, or giving an explicit size and char* (as these
+// are the native representation for these string pieces).
+// This is done for several reasons.
+// 1) This minimizes copying/allocation/deallocation as compared to using
+// string parameters
+// 2) This reduces the number of strlen() calls done (as the length of any
+// string passed in is relatively likely to be known at compile time, and for
+// those strings passed back we obviate the need for a strlen() to determine
+// the size of new storage allocations if a new allocation is required.
+// 3) This class attempts to store all of its data in two linear buffers in
+// order to enhance the speed of parsing and writing out to a buffer. As a
+// result, many string pieces are -not- terminated by '\0', and are not
+// c-strings. Since this is the case, we must delineate the length of the
+// string explicitly via a length.
+//
+// WARNING: The side effect of using StringPiece is that if the underlying
+// buffer changes (due to modifying the headers) the StringPieces which point
+// to the data which was modified, may now contain "garbage", and should not
+// be dereferenced.
+// For example, If you fetch some component of the first-line, (request or
+// response), and then you modify the first line, the StringPieces you
+// originally received from the original first-line may no longer be valid).
+//
+// StringPieces pointing to pieces of header lines which have not been
+// erased() or modified should be valid until the object is cleared or
+// destroyed.
+
+class BalsaHeaders {
+ public:
+ struct HeaderLineDescription {
+ HeaderLineDescription(size_t first_character_index,
+ size_t key_end_index,
+ size_t value_begin_index,
+ size_t last_character_index,
+ size_t buffer_base_index) :
+ first_char_idx(first_character_index),
+ key_end_idx(key_end_index),
+ value_begin_idx(value_begin_index),
+ last_char_idx(last_character_index),
+ buffer_base_idx(buffer_base_index),
+ skip(false) {}
+
+ HeaderLineDescription() :
+ first_char_idx(0),
+ key_end_idx(0),
+ value_begin_idx(0),
+ last_char_idx(0),
+ buffer_base_idx(0),
+ skip(false) {}
+
+ size_t first_char_idx;
+ size_t key_end_idx;
+ size_t value_begin_idx;
+ size_t last_char_idx;
+ BalsaBuffer::Blocks::size_type buffer_base_idx;
+ bool skip;
+ };
+
+ typedef std::vector<base::StringPiece> HeaderTokenList;
+ friend bool ParseHTTPFirstLine(const char* begin,
+ const char* end,
+ bool is_request,
+ size_t max_request_uri_length,
+ BalsaHeaders* headers,
+ BalsaFrameEnums::ErrorCode* error_code);
+
+ protected:
+ typedef std::vector<HeaderLineDescription> HeaderLines;
+
+ // Why these base classes (iterator_base, reverse_iterator_base)? Well, if
+ // we do want to export both iterator and const_iterator types (currently we
+ // only have const_iterator), then this is useful to avoid code duplication.
+ // Additionally, having this base class makes comparisons of iterators of
+ // different types (they're different types to ensure that operator= and
+ // constructors do not work in the places where they're expected to not work)
+ // work properly. There could be as many as 4 iterator types, all based on
+ // the same data as iterator_base... so it makes sense to simply have some
+ // base classes.
+
+ class iterator_base {
+ public:
+ friend class BalsaHeaders;
+ friend class reverse_iterator_base;
+ typedef std::pair<base::StringPiece, base::StringPiece> StringPiecePair;
+ typedef StringPiecePair value_type;
+ typedef value_type& reference;
+ typedef value_type* pointer;
+
+ typedef std::forward_iterator_tag iterator_category;
+ typedef ptrdiff_t difference_type;
+
+ typedef iterator_base self;
+
+ // default constructor.
+ iterator_base();
+
+ // copy constructor.
+ iterator_base(const iterator_base& it);
+
+ reference operator*() const {
+ return Lookup(idx_);
+ }
+
+ pointer operator->() const {
+ return &(this->operator*());
+ }
+
+ bool operator==(const self& it) const {
+ return idx_ == it.idx_;
+ }
+
+ bool operator<(const self& it) const {
+ return idx_ < it.idx_;
+ }
+
+ bool operator<=(const self& it) const {
+ return idx_ <= it.idx_;
+ }
+
+ bool operator!=(const self& it) const {
+ return !(*this == it);
+ }
+
+ bool operator>(const self& it) const {
+ return it < *this;
+ }
+
+ bool operator>=(const self& it) const {
+ return it <= *this;
+ }
+
+ // This mainly exists so that we can have interesting output for
+ // unittesting. The EXPECT_EQ, EXPECT_NE functions require that
+ // operator<< work for the classes it sees. It would be better if there
+ // was an additional traits-like system for the gUnit output... but oh
+ // well.
+ std::ostream& operator<<(std::ostream& os) const;
+
+ protected:
+ iterator_base(const BalsaHeaders* headers, HeaderLines::size_type index);
+
+ void increment() {
+ const HeaderLines& header_lines = headers_->header_lines_;
+ const HeaderLines::size_type header_lines_size = header_lines.size();
+ const HeaderLines::size_type original_idx = idx_;
+ do {
+ ++idx_;
+ } while (idx_ < header_lines_size && header_lines[idx_].skip == true);
+ // The condition below exists so that ++(end() - 1) == end(), even
+ // if there are only 'skip == true' elements between the end() iterator
+ // and the end of the vector of HeaderLineDescriptions.
+ // TODO(fenix): refactor this list so that we don't have to do
+ // linear scanning through skipped headers (and this condition is
+ // then unnecessary)
+ if (idx_ == header_lines_size) {
+ idx_ = original_idx + 1;
+ }
+ }
+
+ void decrement() {
+ const HeaderLines& header_lines = headers_->header_lines_;
+ const HeaderLines::size_type header_lines_size = header_lines.size();
+ const HeaderLines::size_type original_idx = idx_;
+ do {
+ --idx_;
+ } while (idx_ < header_lines_size && header_lines[idx_].skip == true);
+ // The condition below exists so that --(rbegin() + 1) == rbegin(), even
+ // if there are only 'skip == true' elements between the rbegin() iterator
+ // and the beginning of the vector of HeaderLineDescriptions.
+ // TODO(fenix): refactor this list so that we don't have to do
+ // linear scanning through skipped headers (and this condition is
+ // then unnecessary)
+ if (idx_ > header_lines_size) {
+ idx_ = original_idx - 1;
+ }
+ }
+
+ reference Lookup(HeaderLines::size_type index) const {
+ DCHECK_LT(index, headers_->header_lines_.size());
+ const HeaderLineDescription& line = headers_->header_lines_[index];
+ const char* stream_begin = headers_->GetPtr(line.buffer_base_idx);
+ value_ = value_type(
+ base::StringPiece(stream_begin + line.first_char_idx,
+ line.key_end_idx - line.first_char_idx),
+ base::StringPiece(stream_begin + line.value_begin_idx,
+ line.last_char_idx - line.value_begin_idx));
+ DCHECK_GE(line.key_end_idx, line.first_char_idx);
+ DCHECK_GE(line.last_char_idx, line.value_begin_idx);
+ return value_;
+ }
+
+ const BalsaHeaders* headers_;
+ HeaderLines::size_type idx_;
+ mutable StringPiecePair value_;
+ };
+
+ class reverse_iterator_base : public iterator_base {
+ public:
+ typedef reverse_iterator_base self;
+ typedef iterator_base::reference reference;
+ typedef iterator_base::pointer pointer;
+ using iterator_base::headers_;
+ using iterator_base::idx_;
+
+ reverse_iterator_base() : iterator_base() {}
+
+ // This constructor is no explicit purposely.
+ reverse_iterator_base(const iterator_base& it) : // NOLINT
+ iterator_base(it) {
+ }
+
+ self& operator=(const iterator_base& it) {
+ idx_ = it.idx_;
+ headers_ = it.headers_;
+ return *this;
+ }
+
+ self& operator=(const reverse_iterator_base& it) {
+ idx_ = it.idx_;
+ headers_ = it.headers_;
+ return *this;
+ }
+
+ reference operator*() const {
+ return Lookup(idx_ - 1);
+ }
+
+ pointer operator->() const {
+ return &(this->operator*());
+ }
+
+ reverse_iterator_base(const reverse_iterator_base& it) :
+ iterator_base(it) { }
+
+ protected:
+ void increment() {
+ --idx_;
+ iterator_base::decrement();
+ ++idx_;
+ }
+
+ void decrement() {
+ ++idx_;
+ iterator_base::increment();
+ --idx_;
+ }
+
+ reverse_iterator_base(const BalsaHeaders* headers,
+ HeaderLines::size_type index) :
+ iterator_base(headers, index) {}
+ };
+
+ public:
+ class const_header_lines_iterator : public iterator_base {
+ friend class BalsaHeaders;
+ public:
+ typedef const_header_lines_iterator self;
+ const_header_lines_iterator() : iterator_base() {}
+
+ const_header_lines_iterator(const const_header_lines_iterator& it) :
+ iterator_base(it.headers_, it.idx_) {}
+
+ self& operator++() {
+ iterator_base::increment();
+ return *this;
+ }
+
+ self& operator--() {
+ iterator_base::decrement();
+ return *this;
+ }
+ protected:
+ const_header_lines_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index) :
+ iterator_base(headers, index) {}
+ };
+
+ class const_reverse_header_lines_iterator : public reverse_iterator_base {
+ public:
+ typedef const_reverse_header_lines_iterator self;
+ const_reverse_header_lines_iterator() : reverse_iterator_base() {}
+
+ const_reverse_header_lines_iterator(
+ const const_header_lines_iterator& it) :
+ reverse_iterator_base(it.headers_, it.idx_) {}
+
+ const_reverse_header_lines_iterator(
+ const const_reverse_header_lines_iterator& it) :
+ reverse_iterator_base(it.headers_, it.idx_) {}
+
+ const_header_lines_iterator base() {
+ return const_header_lines_iterator(headers_, idx_);
+ }
+
+ self& operator++() {
+ reverse_iterator_base::increment();
+ return *this;
+ }
+
+ self& operator--() {
+ reverse_iterator_base::decrement();
+ return *this;
+ }
+ protected:
+ const_reverse_header_lines_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index) :
+ reverse_iterator_base(headers, index) {}
+
+ friend class BalsaHeaders;
+ };
+
+ // An iterator that only stops at lines with a particular key.
+ // See also GetIteratorForKey.
+ //
+ // Check against header_lines_key_end() to determine when iteration is
+ // finished. header_lines_end() will also work.
+ class const_header_lines_key_iterator : public iterator_base {
+ friend class BalsaHeaders;
+ public:
+ typedef const_header_lines_key_iterator self;
+ const_header_lines_key_iterator(const const_header_lines_key_iterator&);
+
+ self& operator++() {
+ do {
+ iterator_base::increment();
+ } while (!AtEnd() &&
+ !StringPieceUtils::EqualIgnoreCase(key_, (**this).first));
+ return *this;
+ }
+
+ void operator++(int ignore) {
+ ++(*this);
+ }
+
+ // Only forward-iteration makes sense, so no operator-- defined.
+
+ private:
+ const_header_lines_key_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index,
+ const base::StringPiece& key);
+
+ // Should only be used for creating an end iterator.
+ const_header_lines_key_iterator(const BalsaHeaders* headers,
+ HeaderLines::size_type index);
+
+ bool AtEnd() const {
+ return *this >= headers_->header_lines_end();
+ }
+
+ base::StringPiece key_;
+ };
+
+ // TODO(fenix): Revisit the amount of bytes initially allocated to the second
+ // block of the balsa_buffer_. It may make sense to pre-allocate some amount
+ // (roughly the amount we'd append in new headers such as X-User-Ip, etc.)
+ BalsaHeaders();
+ ~BalsaHeaders();
+
+ const_header_lines_iterator header_lines_begin() {
+ return HeaderLinesBeginHelper<const_header_lines_iterator>();
+ }
+
+ const_header_lines_iterator header_lines_begin() const {
+ return HeaderLinesBeginHelper<const_header_lines_iterator>();
+ }
+
+ const_header_lines_iterator header_lines_end() {
+ return HeaderLinesEndHelper<const_header_lines_iterator>();
+ }
+
+ const_header_lines_iterator header_lines_end() const {
+ return HeaderLinesEndHelper<const_header_lines_iterator>();
+ }
+
+ const_reverse_header_lines_iterator header_lines_rbegin() {
+ return const_reverse_header_lines_iterator(header_lines_end());
+ }
+
+ const_reverse_header_lines_iterator header_lines_rbegin() const {
+ return const_reverse_header_lines_iterator(header_lines_end());
+ }
+
+ const_reverse_header_lines_iterator header_lines_rend() {
+ return const_reverse_header_lines_iterator(header_lines_begin());
+ }
+
+ const_reverse_header_lines_iterator header_lines_rend() const {
+ return const_reverse_header_lines_iterator(header_lines_begin());
+ }
+
+ const_header_lines_key_iterator header_lines_key_end() const {
+ return HeaderLinesEndHelper<const_header_lines_key_iterator>();
+ }
+
+ void erase(const const_header_lines_iterator& it) {
+ DCHECK_EQ(it.headers_, this);
+ DCHECK_LT(it.idx_, header_lines_.size());
+ DCHECK_GE(it.idx_, 0u);
+ header_lines_[it.idx_].skip = true;
+ }
+
+ void Clear();
+
+ void Swap(BalsaHeaders* other);
+
+ void CopyFrom(const BalsaHeaders& other);
+
+ void HackHeader(const base::StringPiece& key, const base::StringPiece& value);
+
+ // Same as AppendToHeader, except that it will attempt to preserve
+ // header ordering.
+ // Note that this will always append to an existing header, if available,
+ // without moving the header around, or collapsing multiple header lines
+ // with the same key together. For this reason, it only 'attempts' to
+ // preserve header ordering.
+ // TODO(fenix): remove this function and rename all occurances
+ // of it in the code to AppendToHeader when the condition above
+ // has been satisified.
+ void HackAppendToHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ // Replaces header entries with key 'key' if they exist, or appends
+ // a new header if none exist. See 'AppendHeader' below for additional
+ // comments about ContentLength and TransferEncoding headers. Note that this
+ // will allocate new storage every time that it is called.
+ // TODO(fenix): modify this function to reuse existing storage
+ // if it is available.
+ void ReplaceOrAppendHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ // Append a new header entry to the header object. Clients who wish to append
+ // Content-Length header should use SetContentLength() method instead of
+ // adding the content length header using AppendHeader (manually adding the
+ // content length header will not update the content_length_ and
+ // content_length_status_ values).
+ // Similarly, clients who wish to add or remove the transfer encoding header
+ // in order to apply or remove chunked encoding should use SetChunkEncoding()
+ // instead.
+ void AppendHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ // Appends ',value' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ void AppendToHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ // Prepends 'value,' to an existing header named 'key'. If no header with the
+ // correct key exists, it will call AppendHeader(key, value). Calling this
+ // function on a key which exists several times in the headers will produce
+ // unpredictable results.
+ void PrependToHeader(const base::StringPiece& key,
+ const base::StringPiece& value);
+
+ const base::StringPiece GetHeader(const base::StringPiece& key) const;
+
+ // Iterates over all currently valid header lines, appending their
+ // values into the vector 'out', in top-to-bottom order.
+ // Header-lines which have been erased are not currently valid, and
+ // will not have their values appended. Empty values will be
+ // represented as empty string. If 'key' doesn't exist in the headers at
+ // all, out will not be changed. We do not clear the vector out
+ // before adding new entries. If there are header lines with matching
+ // key but empty value then they are also added to the vector out.
+ // (Basically empty values are not treated in any special manner).
+ //
+ // Example:
+ // Input header:
+ // "GET / HTTP/1.0\r\n"
+ // "key1: v1\r\n"
+ // "key1: \r\n"
+ // "key1:\r\n"
+ // "key1: v1\r\n"
+ // "key1:v2\r\n"
+ //
+ // vector out is initially: ["foo"]
+ // vector out after GetAllOfHeader("key1", &out) is:
+ // ["foo", "v1", "", "", "v2", "v1", "v2"]
+
+ void GetAllOfHeader(const base::StringPiece& key,
+ std::vector<base::StringPiece>* out) const;
+
+ // Joins all values for key into a comma-separated string in out.
+ // More efficient than calling JoinStrings on result of GetAllOfHeader if
+ // you don't need the intermediate vector<StringPiece>.
+ void GetAllOfHeaderAsString(const base::StringPiece& key,
+ std::string* out) const;
+
+ // Returns true if RFC 2616 Section 14 indicates that header can
+ // have multiple values.
+ static bool IsMultivaluedHeader(const base::StringPiece& header);
+
+ // Determine if a given header is present.
+ inline bool HasHeader(const base::StringPiece& key) const {
+ return (GetConstHeaderLinesIterator(key, header_lines_.begin()) !=
+ header_lines_.end());
+ }
+
+ // Returns true iff any header 'key' exists with non-empty value.
+ bool HasNonEmptyHeader(const base::StringPiece& key) const;
+
+ const_header_lines_iterator GetHeaderPosition(
+ const base::StringPiece& key) const;
+
+ // Returns a forward-only iterator that only stops at lines matching key.
+ // String backing 'key' must remain valid for lifetime of iterator.
+ //
+ // Check returned iterator against header_lines_key_end() to determine when
+ // iteration is finished.
+ const_header_lines_key_iterator GetIteratorForKey(
+ const base::StringPiece& key) const;
+
+ void RemoveAllOfHeader(const base::StringPiece& key);
+
+ // Removes all headers starting with 'key' [case insensitive]
+ void RemoveAllHeadersWithPrefix(const base::StringPiece& key);
+
+ // Returns the lower bound of memory used by this header object, including
+ // all internal buffers and data structure. Some of the memory used cannot be
+ // directly measure. For example, memory used for bookkeeping by standard
+ // containers.
+ size_t GetMemoryUsedLowerBound() const;
+
+ // Returns the upper bound on the required buffer space to fully write out
+ // the header object (this include the first line, all header lines, and the
+ // final CRLF that marks the ending of the header).
+ size_t GetSizeForWriteBuffer() const;
+
+ // The following WriteHeader* methods are template member functions that
+ // place one requirement on the Buffer class: it must implement a Write
+ // method that takes a pointer and a length. The buffer passed in is not
+ // required to be stretchable. For non-stretchable buffers, the user must
+ // call GetSizeForWriteBuffer() to find out the upper bound on the output
+ // buffer space required to make sure that the entire header is serialized.
+ // BalsaHeaders will not check that there is adequate space in the buffer
+ // object during the write.
+
+ // Writes the entire header and the final CRLF that marks the end of the HTTP
+ // header section to the buffer. After this method returns, no more header
+ // data should be written to the buffer.
+ template <typename Buffer>
+ void WriteHeaderAndEndingToBuffer(Buffer* buffer) const {
+ WriteToBuffer(buffer);
+ WriteHeaderEndingToBuffer(buffer);
+ }
+
+ // Writes the final CRLF to the buffer to terminate the HTTP header section.
+ // After this method returns, no more header data should be written to the
+ // buffer.
+ template <typename Buffer>
+ static void WriteHeaderEndingToBuffer(Buffer* buffer) {
+ buffer->Write("\r\n", 2);
+ }
+
+ // Writes the entire header to the buffer without the CRLF that terminates
+ // the HTTP header. This lets users append additional header lines using
+ // WriteHeaderLineToBuffer and then terminate the header with
+ // WriteHeaderEndingToBuffer as the header is serialized to the
+ // buffer, without having to first copy the header.
+ template <typename Buffer>
+ void WriteToBuffer(Buffer* buffer) const {
+ // write the first line.
+ const size_t firstline_len = whitespace_4_idx_ - non_whitespace_1_idx_;
+ const char* stream_begin = GetPtr(firstline_buffer_base_idx_);
+ buffer->Write(stream_begin + non_whitespace_1_idx_, firstline_len);
+ buffer->Write("\r\n", 2);
+ const HeaderLines::size_type end = header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < end; ++i) {
+ const HeaderLineDescription& line = header_lines_[i];
+ if (line.skip) {
+ continue;
+ }
+ const char* line_ptr = GetPtr(line.buffer_base_idx);
+ WriteHeaderLineToBuffer(
+ buffer,
+ base::StringPiece(line_ptr + line.first_char_idx,
+ line.key_end_idx - line.first_char_idx),
+ base::StringPiece(line_ptr + line.value_begin_idx,
+ line.last_char_idx - line.value_begin_idx));
+ }
+ }
+
+ // Takes a header line in the form of a key/value pair and append it to the
+ // buffer. This function should be called after WriteToBuffer to
+ // append additional header lines to the header without copying the header.
+ // When the user is done with appending to the buffer,
+ // WriteHeaderEndingToBuffer must be used to terminate the HTTP
+ // header in the buffer. This method is a no-op if key is empty.
+ template <typename Buffer>
+ static void WriteHeaderLineToBuffer(Buffer* buffer,
+ const base::StringPiece& key,
+ const base::StringPiece& value) {
+ // if the key is empty, we don't want to write the rest because it
+ // will not be a well-formed header line.
+ if (!key.empty()) {
+ buffer->Write(key.data(), key.size());
+ buffer->Write(": ", 2);
+ buffer->Write(value.data(), value.size());
+ buffer->Write("\r\n", 2);
+ }
+ }
+
+ // Dump the textural representation of the header object to a string, which
+ // is suitable for writing out to logs. All CRLF will be printed out as \n.
+ // This function can be called on a header object in any state. Raw header
+ // data will be printed out if the header object is not completely parsed,
+ // e.g., when there was an error in the middle of parsing.
+ // The header content is appended to the string; the original content is not
+ // cleared.
+ void DumpToString(std::string* str) const;
+
+ const base::StringPiece first_line() const {
+ DCHECK_GE(whitespace_4_idx_, non_whitespace_1_idx_);
+ return base::StringPiece(BeginningOfFirstLine() + non_whitespace_1_idx_,
+ whitespace_4_idx_ - non_whitespace_1_idx_);
+ }
+
+ // Returns the parsed value of the response code if it has been parsed.
+ // Guaranteed to return 0 when unparsed (though it is a much better idea to
+ // verify that the BalsaFrame had no errors while parsing).
+ // This may return response codes which are outside the normal bounds of
+ // HTTP response codes-- it is up to the user of this class to ensure that
+ // the response code is one which is interpretable.
+ size_t parsed_response_code() const { return parsed_response_code_; }
+
+ const base::StringPiece request_method() const {
+ DCHECK_GE(whitespace_2_idx_, non_whitespace_1_idx_);
+ return base::StringPiece(BeginningOfFirstLine() + non_whitespace_1_idx_,
+ whitespace_2_idx_ - non_whitespace_1_idx_);
+ }
+
+ const base::StringPiece response_version() const {
+ // Note: There is no difference between request_method() and
+ // response_version(). They both could be called
+ // GetFirstTokenFromFirstline()... but that wouldn't be anywhere near as
+ // descriptive.
+ return request_method();
+ }
+
+ const base::StringPiece request_uri() const {
+ DCHECK_GE(whitespace_3_idx_, non_whitespace_2_idx_);
+ return base::StringPiece(BeginningOfFirstLine() + non_whitespace_2_idx_,
+ whitespace_3_idx_ - non_whitespace_2_idx_);
+ }
+
+ const base::StringPiece response_code() const {
+ // Note: There is no difference between request_uri() and response_code().
+ // They both could be called GetSecondtTokenFromFirstline(), but, as noted
+ // in an earlier comment, that wouldn't be as descriptive.
+ return request_uri();
+ }
+
+ const base::StringPiece request_version() const {
+ DCHECK_GE(whitespace_4_idx_, non_whitespace_3_idx_);
+ return base::StringPiece(BeginningOfFirstLine() + non_whitespace_3_idx_,
+ whitespace_4_idx_ - non_whitespace_3_idx_);
+ }
+
+ const base::StringPiece response_reason_phrase() const {
+ // Note: There is no difference between request_version() and
+ // response_reason_phrase(). They both could be called
+ // GetThirdTokenFromFirstline(), but, as noted in an earlier comment, that
+ // wouldn't be as descriptive.
+ return request_version();
+ }
+
+ // Note that SetFirstLine will not update the internal indices for the
+ // various bits of the first-line (and may set them all to zero).
+ // If you'd like to use the accessors for the various bits of the firstline,
+ // then you should use the Set* functions, or SetFirstlineFromStringPieces,
+ // below, instead.
+ //
+ void SetFirstlineFromStringPieces(const base::StringPiece& firstline_a,
+ const base::StringPiece& firstline_b,
+ const base::StringPiece& firstline_c);
+
+ void SetRequestFirstlineFromStringPieces(const base::StringPiece& method,
+ const base::StringPiece& uri,
+ const base::StringPiece& version) {
+ SetFirstlineFromStringPieces(method, uri, version);
+ }
+
+ void SetResponseFirstlineFromStringPieces(
+ const base::StringPiece& version,
+ const base::StringPiece& code,
+ const base::StringPiece& reason_phrase) {
+ SetFirstlineFromStringPieces(version, code, reason_phrase);
+ }
+
+ // These functions are exactly the same, except that their names are
+ // different. This is done so that the code using this class is more
+ // expressive.
+ void SetRequestMethod(const base::StringPiece& method);
+ void SetResponseVersion(const base::StringPiece& version);
+
+ void SetRequestUri(const base::StringPiece& uri);
+ void SetResponseCode(const base::StringPiece& code);
+ void set_parsed_response_code(size_t parsed_response_code) {
+ parsed_response_code_ = parsed_response_code;
+ }
+ void SetParsedResponseCodeAndUpdateFirstline(size_t parsed_response_code);
+
+ // These functions are exactly the same, except that their names are
+ // different. This is done so that the code using this class is more
+ // expressive.
+ void SetRequestVersion(const base::StringPiece& version);
+ void SetResponseReasonPhrase(const base::StringPiece& reason_phrase);
+
+ // The biggest problem with SetFirstLine is that we don't want to use a
+ // separate buffer for it. The second biggest problem with it is that the
+ // first biggest problem requires that we store offsets into a buffer instead
+ // of pointers into a buffer. Cuteness aside, SetFirstLine doesn't parse
+ // the individual fields of the firstline, and so accessors to those fields
+ // will not work properly after calling SetFirstLine. If you want those
+ // accessors to work, use the Set* functions above this one.
+ // SetFirstLine is stuff useful, however, if all you care about is correct
+ // serialization with the rest of the header object.
+ void SetFirstLine(const base::StringPiece& line);
+
+ // Simple accessors to some of the internal state
+ bool transfer_encoding_is_chunked() const {
+ return transfer_encoding_is_chunked_;
+ }
+
+ static bool ResponseCodeImpliesNoBody(int code) {
+ // From HTTP spec section 6.1.1 all 1xx responses must not have a body,
+ // as well as 204 No Content and 304 Not Modified.
+ return ((code >= 100) && (code <= 199)) || (code == 204) || (code == 304);
+ }
+
+ // Note: never check this for requests. Nothing bad will happen if you do,
+ // but spec does not allow requests framed by connection close.
+ // TODO(vitaliyl): refactor.
+ bool is_framed_by_connection_close() const {
+ // We declare that response is framed by connection close if it has no
+ // content-length, no transfer encoding, and is allowed to have a body by
+ // the HTTP spec.
+ // parsed_response_code_ is 0 for requests, so ResponseCodeImpliesNoBody
+ // will return false.
+ return (content_length_status_ == BalsaHeadersEnums::NO_CONTENT_LENGTH) &&
+ !transfer_encoding_is_chunked_ &&
+ !ResponseCodeImpliesNoBody(parsed_response_code_);
+ }
+
+ size_t content_length() const { return content_length_; }
+ BalsaHeadersEnums::ContentLengthStatus content_length_status() const {
+ return content_length_status_;
+ }
+
+ // SetContentLength and SetChunkEncoding modifies the header object to use
+ // content-length and transfer-encoding headers in a consistent manner. They
+ // set all internal flags and status so client can get a consistent view from
+ // various accessors.
+ void SetContentLength(size_t length);
+ void SetChunkEncoding(bool chunk_encode);
+
+ protected:
+ friend class BalsaFrame;
+ friend class SpdyFrame;
+ friend class HTTPMessage;
+ friend class BalsaHeadersTokenUtils;
+
+ const char* BeginningOfFirstLine() const {
+ return GetPtr(firstline_buffer_base_idx_);
+ }
+
+ char* GetPtr(BalsaBuffer::Blocks::size_type block_idx) {
+ return balsa_buffer_.GetPtr(block_idx);
+ }
+
+ const char* GetPtr(BalsaBuffer::Blocks::size_type block_idx) const {
+ return balsa_buffer_.GetPtr(block_idx);
+ }
+
+ void WriteFromFramer(const char* ptr, size_t size) {
+ balsa_buffer_.WriteToContiguousBuffer(base::StringPiece(ptr, size));
+ }
+
+ void DoneWritingFromFramer() {
+ balsa_buffer_.NoMoreWriteToContiguousBuffer();
+ }
+
+ const char* OriginalHeaderStreamBegin() const {
+ return balsa_buffer_.StartOfFirstBlock();
+ }
+
+ const char* OriginalHeaderStreamEnd() const {
+ return balsa_buffer_.EndOfFirstBlock();
+ }
+
+ size_t GetReadableBytesFromHeaderStream() const {
+ return OriginalHeaderStreamEnd() - OriginalHeaderStreamBegin();
+ }
+
+ void GetReadablePtrFromHeaderStream(const char** p, size_t* s) {
+ *p = OriginalHeaderStreamBegin();
+ *s = GetReadableBytesFromHeaderStream();
+ }
+
+ base::StringPiece GetValueFromHeaderLineDescription(
+ const HeaderLineDescription& line) const;
+
+ void AddAndMakeDescription(const base::StringPiece& key,
+ const base::StringPiece& value,
+ HeaderLineDescription* d);
+
+ void AppendOrPrependAndMakeDescription(const base::StringPiece& key,
+ const base::StringPiece& value,
+ bool append,
+ HeaderLineDescription* d);
+
+ // Removes all header lines with the given key starting at start.
+ void RemoveAllOfHeaderStartingAt(const base::StringPiece& key,
+ HeaderLines::iterator start);
+
+ // If the 'key' does not exist in the headers, calls
+ // AppendHeader(key, value). Otherwise if append is true, appends ',value'
+ // to the first existing header with key 'key'. If append is false, prepends
+ // 'value,' to the first existing header with key 'key'.
+ void AppendOrPrependToHeader(const base::StringPiece& key,
+ const base::StringPiece& value,
+ bool append);
+
+ HeaderLines::const_iterator GetConstHeaderLinesIterator(
+ const base::StringPiece& key,
+ HeaderLines::const_iterator start) const;
+
+ HeaderLines::iterator GetHeaderLinesIteratorNoSkip(
+ const base::StringPiece& key,
+ HeaderLines::iterator start);
+
+ HeaderLines::iterator GetHeaderLinesIterator(
+ const base::StringPiece& key,
+ HeaderLines::iterator start);
+
+ template <typename IteratorType>
+ const IteratorType HeaderLinesBeginHelper() const {
+ if (header_lines_.empty()) {
+ return IteratorType(this, 0);
+ }
+ const HeaderLines::size_type header_lines_size = header_lines_.size();
+ for (HeaderLines::size_type i = 0; i < header_lines_size; ++i) {
+ if (header_lines_[i].skip == false) {
+ return IteratorType(this, i);
+ }
+ }
+ return IteratorType(this, 0);
+ }
+
+ template <typename IteratorType>
+ const IteratorType HeaderLinesEndHelper() const {
+ if (header_lines_.empty()) {
+ return IteratorType(this, 0);
+ }
+ const HeaderLines::size_type header_lines_size = header_lines_.size();
+ HeaderLines::size_type i = header_lines_size;
+ do {
+ --i;
+ if (header_lines_[i].skip == false) {
+ return IteratorType(this, i + 1);
+ }
+ } while (i != 0);
+ return IteratorType(this, 0);
+ }
+
+ // At the moment, this function will always return the original headers.
+ // In the future, it may not do so after erasing header lines, modifying
+ // header lines, or modifying the first line.
+ // For this reason, it is strongly suggested that use of this function is
+ // only acceptable for the purpose of debugging parse errors seen by the
+ // BalsaFrame class.
+ base::StringPiece OriginalHeadersForDebugging() const {
+ return base::StringPiece(OriginalHeaderStreamBegin(),
+ OriginalHeaderStreamEnd() - OriginalHeaderStreamBegin());
+ }
+
+ BalsaBuffer balsa_buffer_;
+
+ size_t content_length_;
+ BalsaHeadersEnums::ContentLengthStatus content_length_status_;
+ size_t parsed_response_code_;
+ // HTTP firstlines all have the following structure:
+ // LWS NONWS LWS NONWS LWS NONWS NOTCRLF CRLF
+ // [\t \r\n]+ [^\t ]+ [\t ]+ [^\t ]+ [\t ]+ [^\t ]+ [^\r\n]+ "\r\n"
+ // ws1 nws1 ws2 nws2 ws3 nws3 ws4
+ // | [-------) [-------) [----------------)
+ // REQ: method request_uri version
+ // RESP: version statuscode reason
+ //
+ // The first NONWS->LWS component we'll call firstline_a.
+ // The second firstline_b, and the third firstline_c.
+ //
+ // firstline_a goes from nws1 to (but not including) ws2
+ // firstline_b goes from nws2 to (but not including) ws3
+ // firstline_c goes from nws3 to (but not including) ws4
+ //
+ // In the code:
+ // ws1 == whitespace_1_idx_
+ // nws1 == non_whitespace_1_idx_
+ // ws2 == whitespace_2_idx_
+ // nws2 == non_whitespace_2_idx_
+ // ws3 == whitespace_3_idx_
+ // nws3 == non_whitespace_3_idx_
+ // ws4 == whitespace_4_idx_
+ BalsaBuffer::Blocks::size_type firstline_buffer_base_idx_;
+ size_t whitespace_1_idx_;
+ size_t non_whitespace_1_idx_;
+ size_t whitespace_2_idx_;
+ size_t non_whitespace_2_idx_;
+ size_t whitespace_3_idx_;
+ size_t non_whitespace_3_idx_;
+ size_t whitespace_4_idx_;
+ size_t end_of_firstline_idx_;
+
+ bool transfer_encoding_is_chunked_;
+
+ HeaderLines header_lines_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_H_
diff --git a/chromium/net/tools/flip_server/balsa_headers_test.cc b/chromium/net/tools/flip_server/balsa_headers_test.cc
new file mode 100644
index 00000000000..241fee17792
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_headers_test.cc
@@ -0,0 +1,392 @@
+// Copyright 2013 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/tools/flip_server/balsa_headers.h"
+
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+using ::base::StringPiece;
+
+class BalsaBufferTest : public ::testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ buffer_.reset(new BalsaBuffer);
+ anotherBuffer_.reset(new BalsaBuffer);
+ }
+
+ protected:
+ scoped_ptr<BalsaBuffer> buffer_;
+ scoped_ptr<BalsaBuffer> anotherBuffer_;
+};
+
+namespace {
+
+class BalsaHeadersTest: public ::testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ headers_.reset(new BalsaHeaders);
+ }
+
+ protected:
+ scoped_ptr<BalsaHeaders> headers_;
+};
+
+class StringBuffer {
+ public:
+ void Write(const char* p, size_t size) {
+ string_ += std::string(p, size);
+ }
+ const std::string& string() {return string_;}
+
+ private:
+ std::string string_;
+};
+
+TEST_F(BalsaBufferTest, EmptyBuffer) {
+ ASSERT_EQ(1u, buffer_->num_blocks());
+}
+
+TEST_F(BalsaBufferTest, Write) {
+ size_t index1, index2;
+ StringPiece sp1 = buffer_->Write(StringPiece("hello"), &index1);
+ StringPiece sp2 = buffer_->Write(StringPiece(", world"), &index2);
+
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ ASSERT_EQ("hello", sp1);
+ ASSERT_EQ(", world", sp2);
+ ASSERT_EQ(1u, index1);
+ ASSERT_EQ(1u, index2);
+ ASSERT_EQ("hello, world",
+ StringPiece(buffer_->GetPtr(1), buffer_->bytes_used(1)));
+}
+
+TEST_F(BalsaBufferTest, WriteLongData) {
+ size_t index1, index2, index3;
+ std::string as(2, 'a');
+ std::string bs(BalsaBuffer::kDefaultBlocksize + 1, 'b');
+ std::string cs(4, 'c');
+
+ StringPiece sp1 = buffer_->Write(as, &index1);
+ StringPiece sp2 = buffer_->Write(bs, &index2);
+ StringPiece sp3 = buffer_->Write(cs, &index3);
+
+ ASSERT_EQ(3u, buffer_->num_blocks());
+ ASSERT_EQ(as, sp1);
+ ASSERT_EQ(bs, sp2);
+ ASSERT_EQ(cs, sp3);
+ ASSERT_EQ(1u, index1);
+ ASSERT_EQ(2u, index2);
+ ASSERT_EQ(1u, index3);
+ ASSERT_EQ("aacccc", StringPiece(buffer_->GetPtr(1), buffer_->bytes_used(1)));
+ ASSERT_EQ(sp2, StringPiece(buffer_->GetPtr(2), buffer_->bytes_used(2)));
+}
+
+TEST_F(BalsaBufferTest, WriteToContiguousBuffer) {
+ std::string as(2, 'a');
+ std::string bs(BalsaBuffer::kDefaultBlocksize + 1, 'b');
+ std::string cs(4, 'c');
+
+ buffer_->WriteToContiguousBuffer(as);
+ buffer_->WriteToContiguousBuffer(bs);
+ buffer_->WriteToContiguousBuffer(cs);
+
+ ASSERT_EQ(1u, buffer_->num_blocks());
+ ASSERT_EQ(as + bs + cs,
+ StringPiece(buffer_->GetPtr(0), buffer_->bytes_used(0)));
+}
+
+TEST_F(BalsaBufferTest, NoMoreWriteToContiguousBuffer) {
+ size_t index1, index2;
+ StringPiece sp1 = buffer_->Write(StringPiece("hello"), &index1);
+ buffer_->NoMoreWriteToContiguousBuffer();
+ StringPiece sp2 = buffer_->Write(StringPiece(", world"), &index2);
+
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ ASSERT_EQ("hello", sp1);
+ ASSERT_EQ(", world", sp2);
+ ASSERT_EQ(1u, index1);
+ ASSERT_EQ(0u, index2);
+ ASSERT_EQ(sp1, StringPiece(buffer_->GetPtr(1), buffer_->bytes_used(1)));
+ ASSERT_EQ(sp2, StringPiece(buffer_->GetPtr(0), buffer_->bytes_used(0)));
+}
+
+TEST_F(BalsaBufferTest, Clear) {
+ buffer_->Write("hello", NULL);
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ buffer_->Clear();
+ ASSERT_EQ(1u, buffer_->num_blocks());
+}
+
+TEST_F(BalsaBufferTest, Swap) {
+ buffer_->Write("hello", NULL);
+
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ ASSERT_EQ(1u, anotherBuffer_->num_blocks());
+
+ buffer_->Swap(anotherBuffer_.get());
+
+ ASSERT_EQ(1u, buffer_->num_blocks());
+ ASSERT_EQ(2u, anotherBuffer_->num_blocks());
+ ASSERT_EQ("hello",
+ StringPiece(anotherBuffer_->GetPtr(1),
+ anotherBuffer_->bytes_used(1)));
+}
+
+TEST_F(BalsaBufferTest, CopyFrom) {
+ buffer_->Write("hello", NULL);
+
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ ASSERT_EQ(1u, anotherBuffer_->num_blocks());
+
+ anotherBuffer_->CopyFrom(*buffer_);
+
+ ASSERT_EQ(2u, buffer_->num_blocks());
+ ASSERT_EQ(2u, anotherBuffer_->num_blocks());
+ ASSERT_EQ("hello", StringPiece(buffer_->GetPtr(1), buffer_->bytes_used(1)));
+ ASSERT_EQ("hello",
+ StringPiece(anotherBuffer_->GetPtr(1),
+ anotherBuffer_->bytes_used(1)));
+}
+
+TEST_F(BalsaHeadersTest, AppendHeader) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key3", "value3");
+ headers_->AppendHeader("key3", "value3.1");
+ headers_->AppendHeader("key3", "value3.2");
+
+ ASSERT_EQ(5, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ ASSERT_EQ("value1", headers_->GetHeader("key1"));
+ ASSERT_EQ("value2", headers_->GetHeader("key2"));
+ ASSERT_EQ("value3", headers_->GetHeader("key3"));
+
+ std::vector<base::StringPiece> v1, v2, v3;
+ std::string s1, s2, s3;
+ headers_->GetAllOfHeader("key1", &v1);
+ headers_->GetAllOfHeader("key2", &v2);
+ headers_->GetAllOfHeader("key3", &v3);
+ headers_->GetAllOfHeaderAsString("key1", &s1);
+ headers_->GetAllOfHeaderAsString("key2", &s2);
+ headers_->GetAllOfHeaderAsString("key3", &s3);
+
+ ASSERT_EQ(1u, v1.size());
+ ASSERT_EQ(1u, v2.size());
+ ASSERT_EQ(3u, v3.size());
+ ASSERT_EQ("value1", v1[0]);
+ ASSERT_EQ("value2", v2[0]);
+ ASSERT_EQ("value3", v3[0]);
+ ASSERT_EQ("value3.1", v3[1]);
+ ASSERT_EQ("value3.2", v3[2]);
+ ASSERT_EQ("value1", s1);
+ ASSERT_EQ("value2", s2);
+ ASSERT_EQ("value3,value3.1,value3.2", s3);
+}
+
+TEST_F(BalsaHeadersTest, ReplaceOrAppendHeader) {
+ headers_->ReplaceOrAppendHeader("key1", "value1");
+ headers_->ReplaceOrAppendHeader("key1", "value2");
+
+ ASSERT_EQ(1, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ ASSERT_EQ("value2", headers_->GetHeader("key1"));
+
+ std::vector<base::StringPiece> v;
+ headers_->GetAllOfHeader("key1", &v);
+
+ ASSERT_EQ(1u, v.size());
+ ASSERT_EQ("value2", v[0]);
+}
+
+TEST_F(BalsaHeadersTest, AppendToHeader) {
+ headers_->AppendToHeader("key1", "value1");
+ headers_->AppendToHeader("keY1", "value2");
+
+ ASSERT_EQ(1, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ ASSERT_EQ("value1,value2", headers_->GetHeader("key1"));
+
+ std::vector<base::StringPiece> v;
+ std::string s;
+ headers_->GetAllOfHeader("key1", &v);
+ headers_->GetAllOfHeaderAsString("keY1", &s);
+
+ ASSERT_EQ(1u, v.size());
+ ASSERT_EQ("value1,value2", v[0]);
+ ASSERT_EQ("value1,value2", s);
+}
+
+TEST_F(BalsaHeadersTest, PrepentToHeader) {
+ headers_->PrependToHeader("key1", "value1");
+ headers_->PrependToHeader("key1", "value2");
+
+ ASSERT_EQ(1, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ ASSERT_EQ("value2,value1", headers_->GetHeader("key1"));
+
+ std::vector<base::StringPiece> v;
+ std::string s;
+ headers_->GetAllOfHeader("key1", &v);
+ headers_->GetAllOfHeaderAsString("key1", &s);
+
+ ASSERT_EQ(1u, v.size());
+ ASSERT_EQ("value2,value1", v[0]);
+ ASSERT_EQ("value2,value1", s);
+}
+
+TEST_F(BalsaHeadersTest, HasHeader) {
+ headers_->AppendHeader("key1", "value1");
+
+ ASSERT_TRUE(headers_->HasHeader("key1"));
+ ASSERT_FALSE(headers_->HasHeader("value1"));
+ ASSERT_FALSE(headers_->HasHeader("key2"));
+}
+
+TEST_F(BalsaHeadersTest, HasNonEmptyHeader) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "");
+
+ ASSERT_TRUE(headers_->HasNonEmptyHeader("key1"));
+ ASSERT_FALSE(headers_->HasNonEmptyHeader("key2"));
+ ASSERT_FALSE(headers_->HasNonEmptyHeader("key3"));
+}
+
+TEST_F(BalsaHeadersTest, GetHeaderPosition) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key3", "value3");
+
+ BalsaHeaders::const_header_lines_iterator i =
+ headers_->GetHeaderPosition("key2");
+
+ ASSERT_EQ(headers_->header_lines_end(),
+ headers_->GetHeaderPosition("foobar"));
+ ASSERT_EQ(headers_->header_lines_begin(),
+ headers_->GetHeaderPosition("key1"));
+ ASSERT_NE(headers_->header_lines_end(), i);
+ ASSERT_EQ("key2", i->first);
+ ASSERT_EQ("value2", i->second);
+ ++i;
+ ASSERT_EQ("key3", i->first);
+ ASSERT_EQ("value3", i->second);
+ ++i;
+ ASSERT_EQ(headers_->header_lines_end(), i);
+}
+
+TEST_F(BalsaHeadersTest, GetIteratorForKey) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key1", "value1.1");
+ headers_->AppendHeader("key3", "value3");
+ headers_->AppendHeader("KEY1", "value1.2");
+
+ BalsaHeaders::const_header_lines_key_iterator i =
+ headers_->GetIteratorForKey("key1");
+
+ ASSERT_EQ(headers_->header_lines_key_end(),
+ headers_->GetIteratorForKey("foobar"));
+ ASSERT_NE(headers_->header_lines_key_end(), i);
+ ASSERT_EQ("key1", i->first);
+ ASSERT_EQ("value1", i->second);
+ ++i;
+ ASSERT_EQ("key1", i->first);
+ ASSERT_EQ("value1.1", i->second);
+ ++i;
+ ASSERT_EQ("KEY1", i->first);
+ ASSERT_EQ("value1.2", i->second);
+ ++i;
+ ASSERT_EQ(headers_->header_lines_key_end(), i);
+}
+
+TEST_F(BalsaHeadersTest, RemoveAllOfHeader) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key1", "value1.1");
+ headers_->AppendHeader("key3", "value3");
+ headers_->AppendHeader("key1", "value1.2");
+ headers_->AppendHeader("kEY1", "value1.3");
+
+ ASSERT_EQ(6, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ headers_->RemoveAllOfHeader("key1");
+ ASSERT_EQ(2, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+}
+
+TEST_F(BalsaHeadersTest, RemoveAllHeadersWithPrefix) {
+ headers_->AppendHeader("1key", "value1");
+ headers_->AppendHeader("2key", "value2");
+ headers_->AppendHeader("1kEz", "value1.1");
+ headers_->AppendHeader("key3", "value3");
+ headers_->AppendHeader("1KEEjkladf", "value1.2");
+
+ ASSERT_EQ(5, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+ headers_->RemoveAllHeadersWithPrefix("1ke");
+ ASSERT_EQ(2, std::distance(headers_->header_lines_begin(),
+ headers_->header_lines_end()));
+}
+
+TEST_F(BalsaHeadersTest, WriteRequestHeaderAndEndingToBuffer) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key1", "value1.1");
+
+ headers_->SetRequestFirstlineFromStringPieces("GET", "/", "HTTP/1.0");
+
+ std::string expected = "GET / HTTP/1.0\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n"
+ "key1: value1.1\r\n\r\n";
+ StringBuffer buffer;
+ headers_->WriteHeaderAndEndingToBuffer(&buffer);
+ ASSERT_EQ(expected, buffer.string());
+}
+
+TEST_F(BalsaHeadersTest, WriteResponseHeaderAndEndingToBuffer) {
+ headers_->AppendHeader("key1", "value1");
+ headers_->AppendHeader("key2", "value2");
+ headers_->AppendHeader("key1", "value1.1");
+
+ headers_->SetResponseFirstlineFromStringPieces("HTTP/1.0", "200", "OK");
+
+ std::string expected = "HTTP/1.0 200 OK\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n"
+ "key1: value1.1\r\n\r\n";
+ StringBuffer buffer;
+ headers_->WriteHeaderAndEndingToBuffer(&buffer);
+ ASSERT_EQ(expected, buffer.string());
+}
+
+TEST_F(BalsaHeadersTest, RequestFirstLine) {
+ headers_->SetRequestFirstlineFromStringPieces("HEAD", "/path", "HTTP/1.1");
+
+ ASSERT_EQ("HEAD /path HTTP/1.1", headers_->first_line());
+ ASSERT_EQ("HEAD", headers_->request_method());
+ ASSERT_EQ("/path", headers_->request_uri());
+ ASSERT_EQ("HTTP/1.1", headers_->request_version());
+}
+
+TEST_F(BalsaHeadersTest, ResponseFirstLine) {
+ headers_->SetRequestFirstlineFromStringPieces("HTTP/1.0", "403", "FORBIDDEN");
+
+ ASSERT_EQ("HTTP/1.0 403 FORBIDDEN", headers_->first_line());
+ ASSERT_EQ("HTTP/1.0", headers_->response_version());
+ ASSERT_EQ("403", headers_->response_code());
+ ASSERT_EQ("FORBIDDEN", headers_->response_reason_phrase());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/balsa_headers_token_utils.cc b/chromium/net/tools/flip_server/balsa_headers_token_utils.cc
new file mode 100644
index 00000000000..b47debf89ab
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_headers_token_utils.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2009 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/tools/flip_server/balsa_headers_token_utils.h"
+#include "net/tools/flip_server/string_piece_utils.h"
+
+namespace net {
+
+inline void BalsaHeadersTokenUtils::TokenizeHeaderLine(
+ const BalsaHeaders& headers,
+ const BalsaHeaders::HeaderLineDescription& header_line,
+ BalsaHeaders::HeaderTokenList* tokens) {
+ CHECK(tokens);
+
+ // Find where this line is stored
+ const char* stream_begin = headers.GetPtr(header_line.buffer_base_idx);
+
+ // Determine the boundaries of the value
+ const char* value_begin = stream_begin + header_line.value_begin_idx;
+ const char* line_end = stream_begin + header_line.last_char_idx;
+
+ // Tokenize
+ ParseTokenList(value_begin, line_end, tokens);
+}
+
+void BalsaHeadersTokenUtils::RemoveLastTokenFromHeaderValue(
+ const base::StringPiece& key, BalsaHeaders* headers) {
+ BalsaHeaders::HeaderLines::iterator it =
+ headers->GetHeaderLinesIterator(key, headers->header_lines_.begin());
+ if (it == headers->header_lines_.end()) {
+ DLOG(WARNING) << "Attempting to remove last token from a non-existent "
+ << "header \"" << key << "\"";
+ return;
+ }
+
+ // Find the last line with that key.
+ BalsaHeaders::HeaderLines::iterator header_line;
+ do {
+ header_line = it;
+ it = headers->GetHeaderLinesIterator(key, it + 1);
+ }
+ while (it != headers->header_lines_.end());
+
+ // Tokenize just that line.
+ BalsaHeaders::HeaderTokenList tokens;
+ TokenizeHeaderLine(*headers, *header_line, &tokens);
+
+ if (tokens.empty()) {
+ DLOG(WARNING) << "Attempting to remove a token from an empty header value "
+ << "for header \"" << key << "\"";
+ header_line->skip = true; // remove the whole line
+ } else if (tokens.size() == 1) {
+ header_line->skip = true; // remove the whole line
+ } else {
+ // Shrink the line size and leave the extra data in the buffer.
+ const base::StringPiece& new_last_token = tokens[tokens.size() - 2];
+ const char* last_char_address =
+ new_last_token.data() + new_last_token.size() - 1;
+ const char* stream_begin = headers->GetPtr(header_line->buffer_base_idx);
+
+ header_line->last_char_idx = last_char_address - stream_begin + 1;
+ }
+}
+
+bool BalsaHeadersTokenUtils::CheckHeaderForLastToken(
+ const BalsaHeaders& headers,
+ const base::StringPiece& key,
+ const base::StringPiece& token) {
+ BalsaHeaders::const_header_lines_key_iterator it =
+ headers.GetIteratorForKey(key);
+ if (it == headers.header_lines_key_end())
+ return false;
+
+ // Find the last line
+ BalsaHeaders::const_header_lines_key_iterator header_line = it;
+ do {
+ header_line = it;
+ ++it;
+ }
+ while (it != headers.header_lines_key_end());
+
+ // Tokenize just that line
+ BalsaHeaders::HeaderTokenList tokens;
+ ParseTokenList(header_line->second.begin(), header_line->second.end(),
+ &tokens);
+
+ return !tokens.empty() &&
+ StringPieceUtils::StartsWithIgnoreCase(tokens.back(), token);
+}
+
+void BalsaHeadersTokenUtils::TokenizeHeaderValue(
+ const BalsaHeaders& headers,
+ const base::StringPiece& key,
+ BalsaHeaders::HeaderTokenList* tokens) {
+ CHECK(tokens);
+ tokens->clear();
+
+ // We may have more then 1 line with the same header key. Tokenize them all
+ // and stick all the tokens into the same list.
+ for (BalsaHeaders::const_header_lines_key_iterator header_line =
+ headers.GetIteratorForKey(key);
+ header_line != headers.header_lines_key_end(); ++header_line) {
+ ParseTokenList(header_line->second.begin(), header_line->second.end(),
+ tokens);
+ }
+}
+
+void BalsaHeadersTokenUtils::ParseTokenList(
+ const char* start,
+ const char* end,
+ BalsaHeaders::HeaderTokenList* tokens) {
+ if (start == end) {
+ return;
+ }
+ while (true) {
+ // search for first nonwhitespace, non separator char.
+ while (*start == ',' || *start <= ' ') {
+ ++start;
+ if (start == end) {
+ return;
+ }
+ }
+ // found. marked.
+ const char* nws = start;
+
+ // search for next whitspace or separator char.
+ while (*start != ',' && *start > ' ') {
+ ++start;
+ if (start == end) {
+ if (nws != start) {
+ tokens->push_back(base::StringPiece(nws, start - nws));
+ }
+ return;
+ }
+ }
+ tokens->push_back(base::StringPiece(nws, start - nws));
+ }
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/balsa_headers_token_utils.h b/chromium/net/tools/flip_server/balsa_headers_token_utils.h
new file mode 100644
index 00000000000..f50c6065bbd
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_headers_token_utils.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2009 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.
+
+// Utility class that performs basic operations on header value tokens: parsing
+// them out, checking for presense of certain tokens, and removing them.
+
+#ifndef NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_TOKEN_UTILS_H_
+#define NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_TOKEN_UTILS_H_
+
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+
+class BalsaHeadersTokenUtils {
+ public:
+ // All the functions below respect multiple header lines with the same key.
+
+ // Checks whether the last header token matches a given value. Useful to
+ // check the outer-most content or transfer-encoding, for example. In the
+ // presence of multiple header lines with given key, the last token of the
+ // last line is compared.
+ static bool CheckHeaderForLastToken(const BalsaHeaders& headers,
+ const base::StringPiece& key,
+ const base::StringPiece& token);
+
+ // Tokenizes header value for a given key. In the presence of multiple lines
+ // with that key, all of them will be tokenized and tokens will be added to
+ // the list in the order in which they are encountered.
+ static void TokenizeHeaderValue(const BalsaHeaders& headers,
+ const base::StringPiece& key,
+ BalsaHeaders::HeaderTokenList* tokens);
+
+ // Removes the last token from the header value. In the presence of multiple
+ // header lines with given key, will remove the last token of the last line.
+ // Can be useful if the last encoding has to be removed.
+ static void RemoveLastTokenFromHeaderValue(const base::StringPiece& key,
+ BalsaHeaders* headers);
+
+ // Given a pointer to the beginning and the end of the header value
+ // in some buffer, populates tokens list with beginning and end indices
+ // of all tokens present in the value string.
+ static void ParseTokenList(const char* start,
+ const char* end,
+ BalsaHeaders::HeaderTokenList* tokens);
+
+ private:
+ // Helper function to tokenize a header line once we have its description.
+ static void TokenizeHeaderLine(
+ const BalsaHeaders& headers,
+ const BalsaHeaders::HeaderLineDescription& line,
+ BalsaHeaders::HeaderTokenList* tokens);
+
+ BalsaHeadersTokenUtils(); // Prohibit instantiation
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BALSA_HEADERS_TOKEN_UTILS_H_
+
diff --git a/chromium/net/tools/flip_server/balsa_visitor_interface.h b/chromium/net/tools/flip_server/balsa_visitor_interface.h
new file mode 100644
index 00000000000..75283acf7e2
--- /dev/null
+++ b/chromium/net/tools/flip_server/balsa_visitor_interface.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_BALSA_VISITOR_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_BALSA_VISITOR_INTERFACE_H_
+
+#include <cstddef>
+
+namespace net {
+
+class BalsaFrame;
+class BalsaHeaders;
+
+// By default the BalsaFrame instantiates a class derived from this interface
+// which does absolutely nothing. If you'd prefer to have interesting
+// functionality execute when any of the below functions are called by the
+// BalsaFrame, then you should subclass it, and set an instantiation of your
+// subclass as the current visitor for the BalsaFrame class using
+// BalsaFrame::set_visitor().
+class BalsaVisitorInterface {
+ public:
+ virtual ~BalsaVisitorInterface() {}
+
+ // Summary:
+ // This is how the BalsaFrame passes you the raw input which it knows to
+ // be a part of the body. To be clear, every byte of the Balsa which isn't
+ // part of the header (or its framing), or trailers will be passed through
+ // this function. This includes data as well as chunking framing.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void ProcessBodyInput(const char *input, size_t size) = 0;
+
+ // Summary:
+ // This is like ProcessBodyInput, but it will only include those parts of
+ // the body which would be stored by a program such as wget, i.e. the bytes
+ // indicating chunking (it will have been omitted). Trailers will not be
+ // passed in through this function-- they'll be passed in through
+ // ProcessTrailers.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void ProcessBodyData(const char *input, size_t size) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw header data through this function. This is
+ // not cleaned up in any way.
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void ProcessHeaderInput(const char *input, size_t size) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw trailer data through this function. This is
+ // not cleaned up in any way. Note that trailers only occur in a message
+ // if there was a chunked encoding, and not always then.
+ //
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void ProcessTrailerInput(const char *input, size_t size) = 0;
+
+ // Summary:
+ // Since the BalsaFrame already has to parse the headers in order to
+ // determine proper framing, it might as well pass the parsed and
+ // cleaned-up results to whatever might need it. This function exists for
+ // that purpose-- parsed headers are passed into this function.
+ // Arguments:
+ // headers - contains the parsed headers in the order in which
+ // they occured in the header.
+ virtual void ProcessHeaders(const BalsaHeaders& headers) = 0;
+
+ // Summary:
+ // Called when the first line of the message is parsed, in this case, for a
+ // request.
+ // Arguments:
+ // line_input - pointer to the beginning of the first line string.
+ // line_length - length of the first line string. (i.e. the numer of
+ // bytes it is safe to read from line_ptr)
+ // method_input - pointer to the beginning of the method string
+ // method_length - length of the method string (i.e. the number
+ // of bytes it is safe to read from method_input)
+ // request_uri_input - pointer to the beginning of the request uri
+ // string.
+ // request_uri_length - length of the method string (i.e. the number
+ // of bytes it is safe to read from method_input)
+ // version_input - pointer to the beginning of the version string.
+ // version_length - length of the version string (i.e. the number
+ // of bytes it i ssafe to read from version_input)
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) = 0;
+
+ // Summary:
+ // Called when the first line of the message is parsed, in this case, for a
+ // response.
+ // Arguments:
+ // line_input - pointer to the beginning of the first line string.
+ // line_length - length of the first line string. (i.e. the numer of
+ // bytes it is safe to read from line_ptr)
+ // version_input - pointer to the beginning of the version string.
+ // version_length - length of the version string (i.e. the number
+ // of bytes it i ssafe to read from version_input)
+ // status_input - pointer to the beginning of the status string
+ // status_length - length of the status string (i.e. the number
+ // of bytes it is safe to read from status_input)
+ // reason_input - pointer to the beginning of the reason string
+ // reason_length - length of the reason string (i.e. the number
+ // of bytes it is safe to read from reason_input)
+ virtual void ProcessResponseFirstLine(const char *line_input,
+ size_t line_length,
+ const char *version_input,
+ size_t version_length,
+ const char *status_input,
+ size_t status_length,
+ const char *reason_input,
+ size_t reason_length) = 0;
+
+ // Called when a chunk length is parsed.
+ // Arguments:
+ // chunk length - the length of the next incoming chunk.
+ virtual void ProcessChunkLength(size_t chunk_length) = 0;
+
+ // Summary:
+ // BalsaFrame passes the raw chunk extension data through this function.
+ // The data is not cleaned up at all, use
+ // BalsaFrame::ProcessChunkExtentions to get the parsed and cleaned up
+ // chunk extensions.
+ //
+ // Arguments:
+ // input - contains the bytes available for read.
+ // size - contains the number of bytes it is safe to read from input.
+ virtual void ProcessChunkExtensions(const char* input, size_t size) = 0;
+
+ // Summary:
+ // Called when the header is framed and processed.
+ virtual void HeaderDone() = 0;
+
+ // Summary:
+ // Called when the message is framed and processed.
+ virtual void MessageDone() = 0;
+
+ // Summary:
+ // Called when an error is detected while parsing the header.
+ // Arguments:
+ // framer - the framer in which an error occured.
+ virtual void HandleHeaderError(BalsaFrame* framer) = 0;
+
+ // Summary:
+ // Called when something meriting a warning is detected while
+ // parsing the header.
+ // Arguments:
+ // framer - the framer in which an error occured.
+ virtual void HandleHeaderWarning(BalsaFrame* framer) = 0;
+
+ // Summary:
+ // Called when an error is detected while parsing a chunk.
+ // Arguments:
+ // framer - the framer in which an error occured.
+ virtual void HandleChunkingError(BalsaFrame* framer) = 0;
+
+ // Summary:
+ // Called when an error is detected while handling the entity-body.
+ // Currently, this can only be called when there is an error
+ // with the BytesSpliced() function, but in the future other interesting
+ // errors could occur.
+ // Arguments:
+ // framer - the framer in which an error occured.
+ virtual void HandleBodyError(BalsaFrame* framer) = 0;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BALSA_VISITOR_INTERFACE_H_
+
diff --git a/chromium/net/tools/flip_server/buffer_interface.h b/chromium/net/tools/flip_server/buffer_interface.h
new file mode 100644
index 00000000000..ec061c94aee
--- /dev/null
+++ b/chromium/net/tools/flip_server/buffer_interface.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_BUFFER_INTERFACE_H__
+#define NET_TOOLS_FLIP_SERVER_BUFFER_INTERFACE_H__
+
+namespace net {
+
+class BufferInterface {
+ public:
+
+ // Returns the bytes which can be read from the buffer. There is no
+ // guarantee that the bytes are contiguous.
+ virtual int ReadableBytes() const = 0;
+
+ // Summary:
+ // returns the size of this buffer
+ // Returns:
+ // size of this buffer.
+ virtual int BufferSize() const = 0;
+
+ // Summary:
+ // returns the number of bytes free in this buffer.
+ // Returns:
+ // number of bytes free.
+ virtual int BytesFree() const = 0;
+
+ // Summary:
+ // Returns true if empty.
+ // Returns:
+ // true - if empty
+ // false - otherwise
+ virtual bool Empty() const = 0;
+
+ // Summary:
+ // Returns true if the buffer is full.
+ virtual bool Full() const = 0;
+
+ // Summary:
+ // returns the number of characters written.
+ // appends up-to-'size' bytes to the buffer.
+ // Args:
+ // bytes - bytes which are read, and copied into the buffer.
+ // size - number of bytes which are read and copied.
+ // this number shall be >= 0.
+ virtual int Write(const char* bytes, int size) = 0;
+
+ // Summary:
+ // Gets a pointer which can be written to (assigned to).
+ // this pointer (and size) can be used in functions like
+ // recv() or read(), etc.
+ // If *size is zero upon returning from this function, that it
+ // is unsafe to dereference *ptr.
+ // Args:
+ // ptr - assigned a pointer to which we can write
+ // size - the amount of data (in bytes) that it is safe to write to ptr.
+ virtual void GetWritablePtr(char **ptr, int* size) const = 0;
+
+ // Summary:
+ // Gets a pointer which can be read from
+ // this pointer (and size) can be used in functions like
+ // send() or write(), etc.
+ // If *size is zero upon returning from this function, that it
+ // is unsafe to dereference *ptr.
+ // Args:
+ // ptr - assigned a pointer from which we may read
+ // size - the amount of data (in bytes) that it is safe to read
+ virtual void GetReadablePtr(char **ptr, int* size) const = 0;
+
+ // Summary:
+ // Reads bytes out of the buffer, and writes them into 'bytes'.
+ // Returns the number of bytes read.
+ // Consumes bytes from the buffer (possibly, but not necessarily
+ // rendering them free)
+ // Args:
+ // bytes - the pointer into which bytes are read from this buffer
+ // and written into
+ // size - number of bytes which are read and copied.
+ // this number shall be >= 0.
+ // Returns:
+ // the number of bytes read from 'bytes'
+ virtual int Read(char* bytes, int size) = 0;
+
+ // Summary:
+ // removes all data from the buffer
+ virtual void Clear() = 0;
+
+ // Summary:
+ // reserves contiguous writable empty space in the buffer of size bytes.
+ // Returns true if the reservation is successful.
+ // If a derive class chooses not to implement reservation, its
+ // implementation should return false.
+ virtual bool Reserve(int size) = 0;
+
+ // Summary:
+ // removes the oldest 'amount_to_consume' characters from this buffer,
+ // Args:
+ // amount_to_advance - .. this should be self-explanatory =)
+ // this number shall be >= 0.
+ virtual void AdvanceReadablePtr(int amount_to_advance) = 0;
+
+ // Summary:
+ // Moves the internal pointers around such that the
+ // amount of data specified here is expected to
+ // already be resident (as if it was Written)
+ // Args:
+ // amount_to_advance - self explanatory.
+ // this number shall be >= 0.
+ virtual void AdvanceWritablePtr(int amount_to_advance) = 0;
+
+ virtual ~BufferInterface() {}
+
+ protected:
+ BufferInterface() {}
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_BUFFER_INTERFACE__H__
+
diff --git a/chromium/net/tools/flip_server/constants.h b/chromium/net/tools/flip_server/constants.h
new file mode 100644
index 00000000000..8b770a60773
--- /dev/null
+++ b/chromium/net/tools/flip_server/constants.h
@@ -0,0 +1,30 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_FLIP_SERVER_CONSTANTS_H_
+#define NET_TOOLS_FLIP_SERVER_CONSTANTS_H_
+
+#include "net/spdy/spdy_protocol.h"
+
+const int kMSS = 1460;
+const int kSSLOverhead = 25;
+const int kSpdyOverhead = 8;
+const int kInitialDataSendersThreshold = (2 * kMSS) - kSpdyOverhead;
+const int kSSLSegmentSize = (1 * kMSS) - kSSLOverhead;
+const int kSpdySegmentSize = kSSLSegmentSize - kSpdyOverhead;
+
+#define ACCEPTOR_CLIENT_IDENT \
+ acceptor_->listen_ip_ << ":" \
+ << acceptor_->listen_port_ << " "
+
+#define NEXT_PROTO_STRING "\x06spdy/2\x08http/1.1\x08http/1.0"
+
+#define SSL_CIPHER_LIST "!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH"
+
+#define IPV4_PRINTABLE_FORMAT(IP) (((IP)>>0)&0xff), (((IP)>>8)&0xff), \
+ (((IP)>>16)&0xff), (((IP)>>24)&0xff)
+
+#define PIDFILE "/var/run/flip-server.pid"
+
+#endif // NET_TOOLS_FLIP_SERVER_CONSTANTS_H_
diff --git a/chromium/net/tools/flip_server/create_listener.cc b/chromium/net/tools/flip_server/create_listener.cc
new file mode 100644
index 00000000000..4676912c117
--- /dev/null
+++ b/chromium/net/tools/flip_server/create_listener.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 2009 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/tools/flip_server/create_listener.h"
+
+#include <arpa/inet.h> // for inet_ntop
+#include <errno.h> // for strerror
+#include <netdb.h> // for getaddrinfo and getnameinfo
+#include <netinet/in.h> // for IPPROTO_*, etc.
+#include <stdlib.h> // for EXIT_FAILURE
+#include <netinet/tcp.h> // For TCP_NODELAY
+#include <sys/socket.h> // for getaddrinfo and getnameinfo
+#include <sys/types.h> // "
+#include <fcntl.h>
+#include <unistd.h> // for exit()
+#include <ostream>
+
+#include "base/logging.h"
+
+namespace net {
+
+// used to ensure we delete the addrinfo structure
+// alloc'd by getaddrinfo
+class AddrinfoGuard {
+ protected:
+ struct addrinfo * addrinfo_ptr_;
+ public:
+
+ explicit AddrinfoGuard(struct addrinfo* addrinfo_ptr) :
+ addrinfo_ptr_(addrinfo_ptr) {}
+
+ ~AddrinfoGuard() {
+ freeaddrinfo(addrinfo_ptr_);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+// Summary:
+// Closes a socket, with option to attempt it multiple times.
+// Why do this? Well, if the system-call gets interrupted, close
+// can fail with EINTR. In that case you should just retry.. Unfortunately,
+// we can't be sure that errno is properly set since we're using a
+// multithreaded approach in the filter proxy, so we should just retry.
+// Args:
+// fd - the socket to close
+// tries - the number of tries to close the socket.
+// Returns:
+// true - if socket was closed
+// false - if socket was NOT closed.
+// Side-effects:
+// sets *fd to -1 if socket was closed.
+//
+bool CloseSocket(int *fd, int tries) {
+ for (int i = 0; i < tries; ++i) {
+ if (!close(*fd)) {
+ *fd = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+// Sets an FD to be nonblocking.
+void SetNonBlocking(int fd) {
+ DCHECK_GE(fd, 0);
+
+ int fcntl_return = fcntl(fd, F_GETFL, 0);
+ CHECK_NE(fcntl_return, -1)
+ << "error doing fcntl(fd, F_GETFL, 0) fd: " << fd
+ << " errno=" << errno;
+
+ if (fcntl_return & O_NONBLOCK)
+ return;
+
+ fcntl_return = fcntl(fd, F_SETFL, fcntl_return | O_NONBLOCK);
+ CHECK_NE(fcntl_return, -1)
+ << "error doing fcntl(fd, F_SETFL, fcntl_return) fd: " << fd
+ << " errno=" << errno;
+}
+
+int SetDisableNagle(int fd) {
+ int on = 1;
+ int rc;
+ rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
+ reinterpret_cast<char*>(&on), sizeof(on));
+ if (rc < 0) {
+ close(fd);
+ LOG(FATAL) << "setsockopt() TCP_NODELAY: failed on fd " << fd;
+ return 0;
+ }
+ return 1;
+}
+
+// see header for documentation of this function.
+int CreateListeningSocket(const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ int backlog,
+ bool reuseaddr,
+ bool reuseport,
+ bool wait_for_iface,
+ bool disable_nagle,
+ int * listen_fd ) {
+ // start out by assuming things will fail.
+ *listen_fd = -1;
+
+ const char* node = NULL;
+ const char* service = NULL;
+
+ if (!host.empty()) node = host.c_str();
+ if (!port.empty()) service = port.c_str();
+
+ struct addrinfo *results = 0;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+
+ if (is_numeric_host_address) {
+ hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
+ }
+ hints.ai_flags |= AI_PASSIVE;
+
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = 0;
+ if ((err=getaddrinfo(node, service, &hints, &results))) {
+ // gai_strerror -is- threadsafe, so we get to use it here.
+ LOG(ERROR) << "getaddrinfo " << " for (" << host << ":" << port
+ << ") " << gai_strerror(err) << "\n";
+ return -1;
+ }
+ // this will delete the addrinfo memory when we return from this function.
+ AddrinfoGuard addrinfo_guard(results);
+
+ int sock = socket(results->ai_family,
+ results->ai_socktype,
+ results->ai_protocol);
+ if (sock == -1) {
+ LOG(ERROR) << "Unable to create socket for (" << host << ":"
+ << port << "): " << strerror(errno) << "\n";
+ return -1;
+ }
+
+ if (reuseaddr) {
+ // set SO_REUSEADDR on the listening socket.
+ int on = 1;
+ int rc;
+ rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<char *>(&on), sizeof(on));
+ if (rc < 0) {
+ close(sock);
+ LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
+ }
+ }
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT 15
+#endif
+ if (reuseport) {
+ // set SO_REUSEADDR on the listening socket.
+ int on = 1;
+ int rc;
+ rc = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ reinterpret_cast<char *>(&on), sizeof(on));
+ if (rc < 0) {
+ close(sock);
+ LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
+ }
+ }
+
+ if (bind(sock, results->ai_addr, results->ai_addrlen)) {
+ // If we are waiting for the interface to be raised, such as in an
+ // HA environment, ignore reporting any errors.
+ int saved_errno = errno;
+ if ( !wait_for_iface || errno != EADDRNOTAVAIL) {
+ LOG(ERROR) << "Bind was unsuccessful for (" << host << ":"
+ << port << "): " << strerror(errno) << "\n";
+ }
+ // if we knew that we were not multithreaded, we could do the following:
+ // " : " << strerror(errno) << "\n";
+ if (CloseSocket(&sock, 100)) {
+ if ( saved_errno == EADDRNOTAVAIL ) {
+ return -3;
+ }
+ return -2;
+ } else {
+ // couldn't even close the dang socket?!
+ LOG(ERROR) << "Unable to close the socket.. Considering this a fatal "
+ "error, and exiting\n";
+ exit(EXIT_FAILURE);
+ return -1;
+ }
+ }
+
+ if (disable_nagle) {
+ if (!SetDisableNagle(sock)) {
+ return -1;
+ }
+ }
+
+ if (listen(sock, backlog)) {
+ // listen was unsuccessful.
+ LOG(ERROR) << "Listen was unsuccessful for (" << host << ":"
+ << port << "): " << strerror(errno) << "\n";
+ // if we knew that we were not multithreaded, we could do the following:
+ // " : " << strerror(errno) << "\n";
+
+ if (CloseSocket(&sock, 100)) {
+ sock = -1;
+ return -1;
+ } else {
+ // couldn't even close the dang socket?!
+ LOG(FATAL) << "Unable to close the socket.. Considering this a fatal "
+ "error, and exiting\n";
+ }
+ }
+
+ // If we've gotten to here, Yeay! Success!
+ *listen_fd = sock;
+
+ return 0;
+}
+
+int CreateConnectedSocket( int *connect_fd,
+ const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ bool disable_nagle ) {
+ const char* node = NULL;
+ const char* service = NULL;
+
+ *connect_fd = -1;
+ if (!host.empty())
+ node = host.c_str();
+ if (!port.empty())
+ service = port.c_str();
+
+ struct addrinfo *results = 0;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+
+ if (is_numeric_host_address)
+ hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
+ hints.ai_flags |= AI_PASSIVE;
+
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = 0;
+ if ((err=getaddrinfo(node, service, &hints, &results))) {
+ // gai_strerror -is- threadsafe, so we get to use it here.
+ LOG(ERROR) << "getaddrinfo for (" << node << ":" << service << "): "
+ << gai_strerror(err);
+ return -1;
+ }
+ // this will delete the addrinfo memory when we return from this function.
+ AddrinfoGuard addrinfo_guard(results);
+
+ int sock = socket(results->ai_family,
+ results->ai_socktype,
+ results->ai_protocol);
+ if (sock == -1) {
+ LOG(ERROR) << "Unable to create socket for (" << node << ":" << service
+ << "): " << strerror( errno );
+ return -1;
+ }
+
+ SetNonBlocking( sock );
+
+ if (disable_nagle) {
+ if (!SetDisableNagle(sock)) {
+ return -1;
+ }
+ }
+
+ int ret_val = 0;
+ if ( connect( sock, results->ai_addr, results->ai_addrlen ) ) {
+ if ( errno != EINPROGRESS ) {
+ LOG(ERROR) << "Connect was unsuccessful for (" << node << ":" << service
+ << "): " << strerror(errno);
+ close( sock );
+ return -1;
+ }
+ } else {
+ ret_val = 1;
+ }
+
+ // If we've gotten to here, Yeay! Success!
+ *connect_fd = sock;
+
+ return ret_val;
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/create_listener.h b/chromium/net/tools/flip_server/create_listener.h
new file mode 100644
index 00000000000..4a4a6e16cbd
--- /dev/null
+++ b/chromium/net/tools/flip_server/create_listener.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
+#define NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
+
+#include <iosfwd>
+#include <string>
+
+namespace net {
+
+void SetNonBlocking(int fd);
+
+// Summary:
+// creates a socket for listening, and bind()s and listen()s it.
+// Args:
+// host - hostname or numeric address, or empty-string if you want
+// to bind to listen on all addresses
+// port - a port number or service name. By service name I mean a
+// -real- service name, not a Google service name. I'd suggest
+// you just stick to a numeric representation like "80"
+// is_numeric_host_address -
+// if you know that the host address has already been looked-up,
+// and will be provided in numeric form like "130.207.244.244",
+// then you can set this to true, and it will save you the time
+// of a DNS lookup.
+// backlog - passed into listen. This is the number of pending incoming
+// connections a socket which is listening may have acquired before
+// the OS starts rejecting new incoming connections.
+// reuseaddr - if true sets SO_REUSEADDR on the listening socket
+// reuseport - if true sets SO_REUSEPORT on the listening socket
+// wait_for_iface - A boolean indicating that CreateListeningSocket should
+// block until the interface that it will bind to has been
+// raised. This is intended for HA environments.
+// disable_nagle - if true sets TCP_NODELAY on the listening socket.
+// listen_fd - this will be assigned a positive value if the socket is
+// successfully created, else it will be assigned -1.
+int CreateListeningSocket(const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ int backlog,
+ bool reuseaddr,
+ bool reuseport,
+ bool wait_for_iface,
+ bool disable_nagle,
+ int * listen_fd);
+
+int CreateConnectedSocket(int *connect_fd,
+ const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ bool disable_nagle);
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
+
diff --git a/chromium/net/tools/flip_server/epoll_server.cc b/chromium/net/tools/flip_server/epoll_server.cc
new file mode 100644
index 00000000000..af09c143b29
--- /dev/null
+++ b/chromium/net/tools/flip_server/epoll_server.cc
@@ -0,0 +1,822 @@
+// Copyright (c) 2011 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/tools/flip_server/epoll_server.h"
+
+#include <stdlib.h> // for abort
+#include <errno.h> // for errno and strerror_r
+#include <algorithm>
+#include <ostream>
+#include <unistd.h> // For read, pipe, close and write.
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/timer/timer.h"
+
+// Design notes: An efficient implementation of ready list has the following
+// desirable properties:
+//
+// A. O(1) insertion into/removal from the list in any location.
+// B. Once the callback is found by hash lookup using the fd, the lookup of
+// corresponding entry in the list is O(1).
+// C. Safe insertion into/removal from the list during list iteration. (The
+// ready list's purpose is to enable completely event driven I/O model.
+// Thus, all the interesting bits happen in the callback. It is critical
+// to not place any restriction on the API during list iteration.
+//
+// The current implementation achieves these goals with the following design:
+//
+// - The ready list is constructed as a doubly linked list to enable O(1)
+// insertion/removal (see man 3 queue).
+// - The forward and backward links are directly embedded inside the
+// CBAndEventMask struct. This enables O(1) lookup in the list for a given
+// callback. (Techincally, we could've used std::list of hash_set::iterator,
+// and keep a list::iterator in CBAndEventMask to achieve the same effect.
+// However, iterators have two problems: no way to portably invalidate them,
+// and no way to tell whether an iterator is singular or not. The only way to
+// overcome these issues is to keep bools in both places, but that throws off
+// memory alignment (up to 7 wasted bytes for each bool). The extra level of
+// indirection will also likely be less cache friendly. Direct manipulation
+// of link pointers makes it easier to retrieve the CBAndEventMask from the
+// list, easier to check whether an CBAndEventMask is in the list, uses less
+// memory (save 32 bytes/fd), and does not affect cache usage (we need to
+// read in the struct to use the callback anyway).)
+// - Embed the fd directly into CBAndEventMask and switch to using hash_set.
+// This removes the need to store hash_map::iterator in the list just so that
+// we can get both the fd and the callback.
+// - The ready list is "one shot": each entry is removed before OnEvent is
+// called. This removes the mutation-while-iterating problem.
+// - Use two lists to keep track of callbacks. The ready_list_ is the one used
+// for registration. Before iteration, the ready_list_ is swapped into the
+// tmp_list_. Once iteration is done, tmp_list_ will be empty, and
+// ready_list_ will have all the new ready fds.
+
+// The size we use for buffers passed to strerror_r
+static const int kErrorBufferSize = 256;
+
+namespace net {
+
+// Clears the pipe and returns. Used for waking the epoll server up.
+class ReadPipeCallback : public EpollCallbackInterface {
+ public:
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE {
+ DCHECK(event->in_events == EPOLLIN);
+ int data;
+ int data_read = 1;
+ // Read until the pipe is empty.
+ while (data_read > 0) {
+ data_read = read(fd, &data, sizeof(data));
+ }
+ }
+ virtual void OnShutdown(EpollServer *eps, int fd) OVERRIDE {}
+ virtual void OnRegistration(EpollServer*, int, int) OVERRIDE {}
+ virtual void OnModification(int, int) OVERRIDE {} // COV_NF_LINE
+ virtual void OnUnregistration(int, bool) OVERRIDE {} // COV_NF_LINE
+};
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+EpollServer::EpollServer()
+ : epoll_fd_(epoll_create(1024)),
+ timeout_in_us_(0),
+ recorded_now_in_us_(0),
+ ready_list_size_(0),
+ wake_cb_(new ReadPipeCallback),
+ read_fd_(-1),
+ write_fd_(-1),
+ in_wait_for_events_and_execute_callbacks_(false),
+ in_shutdown_(false) {
+ // ensure that the epoll_fd_ is valid.
+ CHECK_NE(epoll_fd_, -1);
+ LIST_INIT(&ready_list_);
+ LIST_INIT(&tmp_list_);
+
+ int pipe_fds[2];
+ if (pipe(pipe_fds) < 0) {
+ // Unfortunately, it is impossible to test any such initialization in
+ // a constructor (as virtual methods do not yet work).
+ // This -could- be solved by moving initialization to an outside
+ // call...
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Error " << saved_errno
+ << " in pipe(): " << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+ read_fd_ = pipe_fds[0];
+ write_fd_ = pipe_fds[1];
+ RegisterFD(read_fd_, wake_cb_.get(), EPOLLIN);
+}
+
+void EpollServer::CleanupFDToCBMap() {
+ FDToCBMap::iterator cb_iter = cb_map_.begin();
+ while (cb_iter != cb_map_.end()) {
+ int fd = cb_iter->fd;
+ CB* cb = cb_iter->cb;
+
+ cb_iter->in_use = true;
+ if (cb) {
+ cb->OnShutdown(this, fd);
+ }
+
+ cb_map_.erase(cb_iter);
+ cb_iter = cb_map_.begin();
+ }
+}
+
+void EpollServer::CleanupTimeToAlarmCBMap() {
+ TimeToAlarmCBMap::iterator erase_it;
+
+ // Call OnShutdown() on alarms. Note that the structure of the loop
+ // is similar to the structure of loop in the function HandleAlarms()
+ for (TimeToAlarmCBMap::iterator i = alarm_map_.begin();
+ i != alarm_map_.end();
+ ) {
+ // Note that OnShutdown() can call UnregisterAlarm() on
+ // other iterators. OnShutdown() should not call UnregisterAlarm()
+ // on self because by definition the iterator is not valid any more.
+ i->second->OnShutdown(this);
+ erase_it = i;
+ ++i;
+ alarm_map_.erase(erase_it);
+ }
+}
+
+EpollServer::~EpollServer() {
+ DCHECK_EQ(in_shutdown_, false);
+ in_shutdown_ = true;
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ LOG(INFO) << "\n" << event_recorder_;
+#endif
+ VLOG(2) << "Shutting down epoll server ";
+ CleanupFDToCBMap();
+
+ LIST_INIT(&ready_list_);
+ LIST_INIT(&tmp_list_);
+
+ CleanupTimeToAlarmCBMap();
+
+ close(read_fd_);
+ close(write_fd_);
+ close(epoll_fd_);
+}
+
+// Whether a CBAandEventMask is on the ready list is determined by a non-NULL
+// le_prev pointer (le_next being NULL indicates end of list).
+inline void EpollServer::AddToReadyList(CBAndEventMask* cb_and_mask) {
+ if (cb_and_mask->entry.le_prev == NULL) {
+ LIST_INSERT_HEAD(&ready_list_, cb_and_mask, entry);
+ ++ready_list_size_;
+ }
+}
+
+inline void EpollServer::RemoveFromReadyList(
+ const CBAndEventMask& cb_and_mask) {
+ if (cb_and_mask.entry.le_prev != NULL) {
+ LIST_REMOVE(&cb_and_mask, entry);
+ // Clean up all the ready list states. Don't bother with the other fields
+ // as they are initialized when the CBAandEventMask is added to the ready
+ // list. This saves a few cycles in the inner loop.
+ cb_and_mask.entry.le_prev = NULL;
+ --ready_list_size_;
+ if (ready_list_size_ == 0) {
+ DCHECK(ready_list_.lh_first == NULL);
+ DCHECK(tmp_list_.lh_first == NULL);
+ }
+ }
+}
+
+void EpollServer::RegisterFD(int fd, CB* cb, int event_mask) {
+ CHECK(cb);
+ VLOG(3) << "RegisterFD fd=" << fd << " event_mask=" << event_mask;
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (cb_map_.end() != fd_i) {
+ // do we just abort, or do we just unregister the other guy?
+ // for now, lets just unregister the other guy.
+
+ // unregister any callback that may already be registered for this FD.
+ CB* other_cb = fd_i->cb;
+ if (other_cb) {
+ // Must remove from the ready list before erasing.
+ RemoveFromReadyList(*fd_i);
+ other_cb->OnUnregistration(fd, true);
+ ModFD(fd, event_mask);
+ } else {
+ // already unregistered, so just recycle the node.
+ AddFD(fd, event_mask);
+ }
+ fd_i->cb = cb;
+ fd_i->event_mask = event_mask;
+ fd_i->events_to_fake = 0;
+ } else {
+ AddFD(fd, event_mask);
+ cb_map_.insert(CBAndEventMask(cb, event_mask, fd));
+ }
+
+
+ // set the FD to be non-blocking.
+ SetNonblocking(fd);
+
+ cb->OnRegistration(this, fd, event_mask);
+}
+
+int EpollServer::GetFlags(int fd) {
+ return fcntl(fd, F_GETFL, 0);
+}
+
+void EpollServer::SetNonblocking(int fd) {
+ int flags = GetFlags(fd);
+ if (flags == -1) {
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Error " << saved_errno
+ << " doing fcntl(" << fd << ", F_GETFL, 0): "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+ if (!(flags & O_NONBLOCK)) {
+ int saved_flags = flags;
+ flags = SetFlags(fd, flags | O_NONBLOCK);
+ if (flags == -1) {
+ // bad.
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Error " << saved_errno
+ << " doing fcntl(" << fd << ", F_SETFL, " << saved_flags << "): "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+ }
+}
+
+int EpollServer::epoll_wait_impl(int epfd,
+ struct epoll_event* events,
+ int max_events,
+ int timeout_in_ms) {
+ return epoll_wait(epfd, events, max_events, timeout_in_ms);
+}
+
+void EpollServer::RegisterFDForWrite(int fd, CB* cb) {
+ RegisterFD(fd, cb, EPOLLOUT);
+}
+
+void EpollServer::RegisterFDForReadWrite(int fd, CB* cb) {
+ RegisterFD(fd, cb, EPOLLIN | EPOLLOUT);
+}
+
+void EpollServer::RegisterFDForRead(int fd, CB* cb) {
+ RegisterFD(fd, cb, EPOLLIN);
+}
+
+void EpollServer::UnregisterFD(int fd) {
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (cb_map_.end() == fd_i || fd_i->cb == NULL) {
+ // Doesn't exist in server, or has gone through UnregisterFD once and still
+ // inside the callchain of OnEvent.
+ return;
+ }
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordUnregistration(fd);
+#endif
+ CB* cb = fd_i->cb;
+ // Since the links are embedded within the struct, we must remove it from the
+ // list before erasing it from the hash_set.
+ RemoveFromReadyList(*fd_i);
+ DelFD(fd);
+ cb->OnUnregistration(fd, false);
+ // fd_i->cb is NULL if that fd is unregistered inside the callchain of
+ // OnEvent. Since the EpollServer needs a valid CBAndEventMask after OnEvent
+ // returns in order to add it to the ready list, we cannot have UnregisterFD
+ // erase the entry if it is in use. Thus, a NULL fd_i->cb is used as a
+ // condition that tells the EpollServer that this entry is unused at a later
+ // point.
+ if (!fd_i->in_use) {
+ cb_map_.erase(fd_i);
+ } else {
+ // Remove all trace of the registration, and just keep the node alive long
+ // enough so the code that calls OnEvent doesn't have to worry about
+ // figuring out whether the CBAndEventMask is valid or not.
+ fd_i->cb = NULL;
+ fd_i->event_mask = 0;
+ fd_i->events_to_fake = 0;
+ }
+}
+
+void EpollServer::ModifyCallback(int fd, int event_mask) {
+ ModifyFD(fd, ~0, event_mask);
+}
+
+void EpollServer::StopRead(int fd) {
+ ModifyFD(fd, EPOLLIN, 0);
+}
+
+void EpollServer::StartRead(int fd) {
+ ModifyFD(fd, 0, EPOLLIN);
+}
+
+void EpollServer::StopWrite(int fd) {
+ ModifyFD(fd, EPOLLOUT, 0);
+}
+
+void EpollServer::StartWrite(int fd) {
+ ModifyFD(fd, 0, EPOLLOUT);
+}
+
+void EpollServer::HandleEvent(int fd, int event_mask) {
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordEpollEvent(fd, event_mask);
+#endif
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (fd_i == cb_map_.end() || fd_i->cb == NULL) {
+ // Ignore the event.
+ // This could occur if epoll() returns a set of events, and
+ // while processing event A (earlier) we removed the callback
+ // for event B (and are now processing event B).
+ return;
+ }
+ fd_i->events_asserted = event_mask;
+ CBAndEventMask* cb_and_mask = const_cast<CBAndEventMask*>(&*fd_i);
+ AddToReadyList(cb_and_mask);
+}
+
+class TrueFalseGuard {
+ public:
+ explicit TrueFalseGuard(bool* guarded_bool) : guarded_bool_(guarded_bool) {
+ DCHECK(guarded_bool_ != NULL);
+ DCHECK(*guarded_bool_ == false);
+ *guarded_bool_ = true;
+ }
+ ~TrueFalseGuard() {
+ *guarded_bool_ = false;
+ }
+ private:
+ bool* guarded_bool_;
+};
+
+void EpollServer::WaitForEventsAndExecuteCallbacks() {
+ if (in_wait_for_events_and_execute_callbacks_) {
+ LOG(DFATAL) <<
+ "Attempting to call WaitForEventsAndExecuteCallbacks"
+ " when an ancestor to the current function is already"
+ " WaitForEventsAndExecuteCallbacks!";
+ // The line below is actually tested, but in coverage mode,
+ // we never see it.
+ return; // COV_NF_LINE
+ }
+ TrueFalseGuard recursion_guard(&in_wait_for_events_and_execute_callbacks_);
+ if (alarm_map_.empty()) {
+ // no alarms, this is business as usual.
+ WaitForEventsAndCallHandleEvents(timeout_in_us_,
+ events_,
+ events_size_);
+ recorded_now_in_us_ = 0;
+ return;
+ }
+
+ // store the 'now'. If we recomputed 'now' every iteration
+ // down below, then we might never exit that loop-- any
+ // long-running alarms might install other long-running
+ // alarms, etc. By storing it here now, we ensure that
+ // a more reasonable amount of work is done here.
+ int64 now_in_us = NowInUsec();
+
+ // Get the first timeout from the alarm_map where it is
+ // stored in absolute time.
+ int64 next_alarm_time_in_us = alarm_map_.begin()->first;
+ VLOG(4) << "next_alarm_time = " << next_alarm_time_in_us
+ << " now = " << now_in_us
+ << " timeout_in_us = " << timeout_in_us_;
+
+ int64 wait_time_in_us;
+ int64 alarm_timeout_in_us = next_alarm_time_in_us - now_in_us;
+
+ // If the next alarm is sooner than the default timeout, or if there is no
+ // timeout (timeout_in_us_ == -1), wake up when the alarm should fire.
+ // Otherwise use the default timeout.
+ if (alarm_timeout_in_us < timeout_in_us_ || timeout_in_us_ < 0) {
+ wait_time_in_us = std::max(alarm_timeout_in_us, static_cast<int64>(0));
+ } else {
+ wait_time_in_us = timeout_in_us_;
+ }
+
+ VLOG(4) << "wait_time_in_us = " << wait_time_in_us;
+
+ // wait for events.
+
+ WaitForEventsAndCallHandleEvents(wait_time_in_us,
+ events_,
+ events_size_);
+ CallAndReregisterAlarmEvents();
+ recorded_now_in_us_ = 0;
+}
+
+void EpollServer::SetFDReady(int fd, int events_to_fake) {
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (cb_map_.end() != fd_i && fd_i->cb != NULL) {
+ // This const_cast is necessary for LIST_HEAD_INSERT to work. Declaring
+ // entry mutable is insufficient because LIST_HEAD_INSERT assigns the
+ // forward pointer of the list head to the current cb_and_mask, and the
+ // compiler complains that it can't assign a const T* to a T*.
+ CBAndEventMask* cb_and_mask = const_cast<CBAndEventMask*>(&*fd_i);
+ // Note that there is no clearly correct behavior here when
+ // cb_and_mask->events_to_fake != 0 and this function is called.
+ // Of the two operations:
+ // cb_and_mask->events_to_fake = events_to_fake
+ // cb_and_mask->events_to_fake |= events_to_fake
+ // the first was picked because it discourages users from calling
+ // SetFDReady repeatedly to build up the correct event set as it is more
+ // efficient to call SetFDReady once with the correct, final mask.
+ cb_and_mask->events_to_fake = events_to_fake;
+ AddToReadyList(cb_and_mask);
+ }
+}
+
+void EpollServer::SetFDNotReady(int fd) {
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (cb_map_.end() != fd_i) {
+ RemoveFromReadyList(*fd_i);
+ }
+}
+
+bool EpollServer::IsFDReady(int fd) const {
+ FDToCBMap::const_iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ return (cb_map_.end() != fd_i &&
+ fd_i->cb != NULL &&
+ fd_i->entry.le_prev != NULL);
+}
+
+void EpollServer::VerifyReadyList() const {
+ int count = 0;
+ CBAndEventMask* cur = ready_list_.lh_first;
+ for (; cur; cur = cur->entry.le_next) {
+ ++count;
+ }
+ for (cur = tmp_list_.lh_first; cur; cur = cur->entry.le_next) {
+ ++count;
+ }
+ CHECK_EQ(ready_list_size_, count) << "Ready list size does not match count";
+}
+
+void EpollServer::RegisterAlarm(int64 timeout_time_in_us, AlarmCB* ac) {
+ CHECK(ac);
+ if (ContainsAlarm(ac)) {
+ LOG(FATAL) << "Alarm already exists " << ac;
+ }
+ VLOG(4) << "RegisteringAlarm at : " << timeout_time_in_us;
+
+ TimeToAlarmCBMap::iterator alarm_iter =
+ alarm_map_.insert(std::make_pair(timeout_time_in_us, ac));
+
+ all_alarms_.insert(ac);
+ // Pass the iterator to the EpollAlarmCallbackInterface.
+ ac->OnRegistration(alarm_iter, this);
+}
+
+// Unregister a specific alarm callback: iterator_token must be a
+// valid iterator. The caller must ensure the validity of the iterator.
+void EpollServer::UnregisterAlarm(const AlarmRegToken& iterator_token) {
+ AlarmCB* cb = iterator_token->second;
+ alarm_map_.erase(iterator_token);
+ all_alarms_.erase(cb);
+ cb->OnUnregistration();
+}
+
+int EpollServer::NumFDsRegistered() const {
+ DCHECK(cb_map_.size() >= 1);
+ // Omit the internal FD (read_fd_)
+ return cb_map_.size() - 1;
+}
+
+void EpollServer::Wake() {
+ char data = 'd'; // 'd' is for data. It's good enough for me.
+ int rv = write(write_fd_, &data, 1);
+ DCHECK(rv == 1);
+}
+
+int64 EpollServer::NowInUsec() const {
+ return base::Time::Now().ToInternalValue();
+}
+
+int64 EpollServer::ApproximateNowInUsec() const {
+ if (recorded_now_in_us_ != 0) {
+ return recorded_now_in_us_;
+ }
+ return this->NowInUsec();
+}
+
+std::string EpollServer::EventMaskToString(int event_mask) {
+ std::string s;
+ if (event_mask & EPOLLIN) s += "EPOLLIN ";
+ if (event_mask & EPOLLPRI) s += "EPOLLPRI ";
+ if (event_mask & EPOLLOUT) s += "EPOLLOUT ";
+ if (event_mask & EPOLLRDNORM) s += "EPOLLRDNORM ";
+ if (event_mask & EPOLLRDBAND) s += "EPOLLRDBAND ";
+ if (event_mask & EPOLLWRNORM) s += "EPOLLWRNORM ";
+ if (event_mask & EPOLLWRBAND) s += "EPOLLWRBAND ";
+ if (event_mask & EPOLLMSG) s += "EPOLLMSG ";
+ if (event_mask & EPOLLERR) s += "EPOLLERR ";
+ if (event_mask & EPOLLHUP) s += "EPOLLHUP ";
+ if (event_mask & EPOLLONESHOT) s += "EPOLLONESHOT ";
+ if (event_mask & EPOLLET) s += "EPOLLET ";
+ return s;
+}
+
+void EpollServer::LogStateOnCrash() {
+ LOG(ERROR) << "----------------------Epoll Server---------------------------";
+ LOG(ERROR) << "Epoll server " << this << " polling on fd " << epoll_fd_;
+ LOG(ERROR) << "timeout_in_us_: " << timeout_in_us_;
+
+ // Log sessions with alarms.
+ LOG(ERROR) << alarm_map_.size() << " alarms registered.";
+ for (TimeToAlarmCBMap::iterator it = alarm_map_.begin();
+ it != alarm_map_.end();
+ ++it) {
+ const bool skipped =
+ alarms_reregistered_and_should_be_skipped_.find(it->second)
+ != alarms_reregistered_and_should_be_skipped_.end();
+ LOG(ERROR) << "Alarm " << it->second << " registered at time " << it->first
+ << " and should be skipped = " << skipped;
+ }
+
+ LOG(ERROR) << cb_map_.size() << " fd callbacks registered.";
+ for (FDToCBMap::iterator it = cb_map_.begin();
+ it != cb_map_.end();
+ ++it) {
+ LOG(ERROR) << "fd: " << it->fd << " with mask " << it->event_mask
+ << " registered with cb: " << it->cb;
+ }
+ LOG(ERROR) << "----------------------/Epoll Server--------------------------";
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+void EpollServer::DelFD(int fd) const {
+ struct epoll_event ee;
+ memset(&ee, 0, sizeof(ee));
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordFDMaskEvent(fd, 0, "DelFD");
+#endif
+ if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, &ee)) {
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Epoll set removal error for fd " << fd << ": "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+}
+
+////////////////////////////////////////
+
+void EpollServer::AddFD(int fd, int event_mask) const {
+ struct epoll_event ee;
+ memset(&ee, 0, sizeof(ee));
+ ee.events = event_mask | EPOLLERR | EPOLLHUP;
+ ee.data.fd = fd;
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordFDMaskEvent(fd, ee.events, "AddFD");
+#endif
+ if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ee)) {
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Epoll set insertion error for fd " << fd << ": "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+}
+
+////////////////////////////////////////
+
+void EpollServer::ModFD(int fd, int event_mask) const {
+ struct epoll_event ee;
+ memset(&ee, 0, sizeof(ee));
+ ee.events = event_mask | EPOLLERR | EPOLLHUP;
+ ee.data.fd = fd;
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordFDMaskEvent(fd, ee.events, "ModFD");
+#endif
+ VLOG(3) << "modifying fd= " << fd << " "
+ << EventMaskToString(ee.events);
+ if (epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &ee)) {
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Epoll set modification error for fd " << fd << ": "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+}
+
+////////////////////////////////////////
+
+void EpollServer::ModifyFD(int fd, int remove_event, int add_event) {
+ FDToCBMap::iterator fd_i = cb_map_.find(CBAndEventMask(NULL, 0, fd));
+ if (cb_map_.end() == fd_i) {
+ VLOG(2) << "Didn't find the fd " << fd << "in internal structures";
+ return;
+ }
+
+ if (fd_i->cb != NULL) {
+ int & event_mask = fd_i->event_mask;
+ VLOG(3) << "fd= " << fd
+ << " event_mask before: " << EventMaskToString(event_mask);
+ event_mask &= ~remove_event;
+ event_mask |= add_event;
+
+ VLOG(3) << " event_mask after: " << EventMaskToString(event_mask);
+
+ ModFD(fd, event_mask);
+
+ fd_i->cb->OnModification(fd, event_mask);
+ }
+}
+
+void EpollServer::WaitForEventsAndCallHandleEvents(int64 timeout_in_us,
+ struct epoll_event events[],
+ int events_size) {
+ if (timeout_in_us == 0 || ready_list_.lh_first != NULL) {
+ // If ready list is not empty, then don't sleep at all.
+ timeout_in_us = 0;
+ } else if (timeout_in_us < 0) {
+ LOG(INFO) << "Negative epoll timeout: " << timeout_in_us
+ << "us; epoll will wait forever for events.";
+ // If timeout_in_us is < 0 we are supposed to Wait forever. This means we
+ // should set timeout_in_us to -1000 so we will
+ // Wait(-1000/1000) == Wait(-1) == Wait forever.
+ timeout_in_us = -1000;
+ } else {
+ // If timeout is specified, and the ready list is empty.
+ if (timeout_in_us < 1000) {
+ timeout_in_us = 1000;
+ }
+ }
+ const int timeout_in_ms = timeout_in_us / 1000;
+ int nfds = epoll_wait_impl(epoll_fd_,
+ events,
+ events_size,
+ timeout_in_ms);
+ VLOG(3) << "nfds=" << nfds;
+
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ event_recorder_.RecordEpollWaitEvent(timeout_in_ms, nfds);
+#endif
+
+ // If you're wondering why the NowInUsec() is recorded here, the answer is
+ // simple: If we did it before the epoll_wait_impl, then the max error for
+ // the ApproximateNowInUs() call would be as large as the maximum length of
+ // epoll_wait, which can be arbitrarily long. Since this would make
+ // ApproximateNowInUs() worthless, we instead record the time -after- we've
+ // done epoll_wait, which guarantees that the maximum error is the amount of
+ // time it takes to process all the events generated by epoll_wait.
+ recorded_now_in_us_ = NowInUsec();
+ if (nfds > 0) {
+ for (int i = 0; i < nfds; ++i) {
+ int event_mask = events[i].events;
+ int fd = events[i].data.fd;
+ HandleEvent(fd, event_mask);
+ }
+ } else if (nfds < 0) {
+ // Catch interrupted syscall and just ignore it and move on.
+ if (errno != EINTR && errno != 0) {
+ int saved_errno = errno;
+ char buf[kErrorBufferSize];
+ LOG(FATAL) << "Error " << saved_errno << " in epoll_wait: "
+ << strerror_r(saved_errno, buf, sizeof(buf));
+ }
+ }
+
+ // Now run through the ready list.
+ if (ready_list_.lh_first) {
+ CallReadyListCallbacks();
+ }
+}
+
+void EpollServer::CallReadyListCallbacks() {
+ // Check pre-conditions.
+ DCHECK(tmp_list_.lh_first == NULL);
+ // Swap out the ready_list_ into the tmp_list_ before traversing the list to
+ // enable SetFDReady() to just push new items into the ready_list_.
+ std::swap(ready_list_.lh_first, tmp_list_.lh_first);
+ if (tmp_list_.lh_first) {
+ tmp_list_.lh_first->entry.le_prev = &tmp_list_.lh_first;
+ EpollEvent event(0, false);
+ while (tmp_list_.lh_first != NULL) {
+ DCHECK_GT(ready_list_size_, 0);
+ CBAndEventMask* cb_and_mask = tmp_list_.lh_first;
+ RemoveFromReadyList(*cb_and_mask);
+
+ event.out_ready_mask = 0;
+ event.in_events =
+ cb_and_mask->events_asserted | cb_and_mask->events_to_fake;
+ // TODO(fenix): get rid of the two separate fields in cb_and_mask.
+ cb_and_mask->events_asserted = 0;
+ cb_and_mask->events_to_fake = 0;
+ {
+ // OnEvent() may call UnRegister, so we set in_use, here. Any
+ // UnRegister call will now simply set the cb to NULL instead of
+ // invalidating the cb_and_mask object (by deleting the object in the
+ // map to which cb_and_mask refers)
+ TrueFalseGuard in_use_guard(&(cb_and_mask->in_use));
+ cb_and_mask->cb->OnEvent(cb_and_mask->fd, &event);
+ }
+
+ // Since OnEvent may have called UnregisterFD, we must check here that
+ // the callback is still valid. If it isn't, then UnregisterFD *was*
+ // called, and we should now get rid of the object.
+ if (cb_and_mask->cb == NULL) {
+ cb_map_.erase(*cb_and_mask);
+ } else if (event.out_ready_mask != 0) {
+ cb_and_mask->events_to_fake = event.out_ready_mask;
+ AddToReadyList(cb_and_mask);
+ }
+ }
+ }
+ DCHECK(tmp_list_.lh_first == NULL);
+}
+
+void EpollServer::CallAndReregisterAlarmEvents() {
+ int64 now_in_us = recorded_now_in_us_;
+ DCHECK_NE(0, recorded_now_in_us_);
+
+ TimeToAlarmCBMap::iterator erase_it;
+
+ // execute alarms.
+ for (TimeToAlarmCBMap::iterator i = alarm_map_.begin();
+ i != alarm_map_.end();
+ ) {
+ if (i->first > now_in_us) {
+ break;
+ }
+ AlarmCB* cb = i->second;
+ // Execute the OnAlarm() only if we did not register
+ // it in this loop itself.
+ const bool added_in_this_round =
+ alarms_reregistered_and_should_be_skipped_.find(cb)
+ != alarms_reregistered_and_should_be_skipped_.end();
+ if (added_in_this_round) {
+ ++i;
+ continue;
+ }
+ all_alarms_.erase(cb);
+ const int64 new_timeout_time_in_us = cb->OnAlarm();
+
+ erase_it = i;
+ ++i;
+ alarm_map_.erase(erase_it);
+
+ if (new_timeout_time_in_us > 0) {
+ // We add to hash_set only if the new timeout is <= now_in_us.
+ // if timeout is > now_in_us then we have no fear that this alarm
+ // can be reexecuted in this loop, and hence we do not need to
+ // worry about a recursive loop.
+ DVLOG(3) << "Reregistering alarm "
+ << " " << cb
+ << " " << new_timeout_time_in_us
+ << " " << now_in_us;
+ if (new_timeout_time_in_us <= now_in_us) {
+ alarms_reregistered_and_should_be_skipped_.insert(cb);
+ }
+ RegisterAlarm(new_timeout_time_in_us, cb);
+ }
+ }
+ alarms_reregistered_and_should_be_skipped_.clear();
+}
+
+EpollAlarm::EpollAlarm() : eps_(NULL), registered_(false) {
+}
+
+EpollAlarm::~EpollAlarm() {
+ UnregisterIfRegistered();
+}
+
+int64 EpollAlarm::OnAlarm() {
+ registered_ = false;
+ return 0;
+}
+
+void EpollAlarm::OnRegistration(const EpollServer::AlarmRegToken& token,
+ EpollServer* eps) {
+ DCHECK_EQ(false, registered_);
+
+ token_ = token;
+ eps_ = eps;
+ registered_ = true;
+}
+
+void EpollAlarm::OnUnregistration() {
+ registered_ = false;
+}
+
+void EpollAlarm::OnShutdown(EpollServer* eps) {
+ registered_ = false;
+ eps_ = NULL;
+}
+
+// If the alarm was registered, unregister it.
+void EpollAlarm::UnregisterIfRegistered() {
+ if (!registered_) {
+ return;
+ }
+ eps_->UnregisterAlarm(token_);
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/epoll_server.h b/chromium/net/tools/flip_server/epoll_server.h
new file mode 100644
index 00000000000..2fb7c62089a
--- /dev/null
+++ b/chromium/net/tools/flip_server/epoll_server.h
@@ -0,0 +1,1054 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_EPOLL_SERVER_H_
+#define NET_TOOLS_FLIP_SERVER_EPOLL_SERVER_H_
+
+#include <fcntl.h>
+#include <sys/queue.h>
+#include <map>
+#include <string>
+#include <utility>
+#include <set>
+#include <vector>
+
+// #define EPOLL_SERVER_EVENT_TRACING 1
+//
+// Defining EPOLL_SERVER_EVENT_TRACING
+// causes code to exist which didn't before.
+// This code tracks each event generated by the epollserver,
+// as well as providing a per-fd-registered summary of
+// events. Note that enabling this code vastly slows
+// down operations, and uses substantially more
+// memory. For these reasons, it should only be enabled when doing
+// developer debugging at his/her workstation.
+//
+// A structure called 'EventRecorder' will exist when
+// the macro is defined. See the EventRecorder class interface
+// within the EpollServer class for more details.
+#ifdef EPOLL_SERVER_EVENT_TRACING
+#include <ostream>
+#include "base/logging.h"
+#endif
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include <sys/epoll.h>
+
+namespace net {
+
+class EpollServer;
+class EpollAlarmCallbackInterface;
+class ReadPipeCallback;
+
+struct EpollEvent {
+ EpollEvent(int events, bool is_epoll_wait)
+ : in_events(events),
+ out_ready_mask(0) {
+ }
+
+ int in_events; // incoming events
+ int out_ready_mask; // the new event mask for ready list (0 means don't
+ // get on the ready list). This field is always
+ // initialized to 0 when the event is passed to
+ // OnEvent.
+};
+
+// Callbacks which go into EpollServers are expected to derive from this class.
+class EpollCallbackInterface {
+ public:
+ // Summary:
+ // Called when the callback is registered into a EpollServer.
+ // Args:
+ // eps - the poll server into which this callback was registered
+ // fd - the file descriptor which was registered
+ // event_mask - the event mask (composed of EPOLLIN, EPOLLOUT, etc)
+ // which was registered (and will initially be used
+ // in the epoll() calls)
+ virtual void OnRegistration(EpollServer* eps, int fd, int event_mask) = 0;
+
+ // Summary:
+ // Called when the event_mask is modified (for a file-descriptor)
+ // Args:
+ // fd - the file descriptor which was registered
+ // event_mask - the event mask (composed of EPOLLIN, EPOLLOUT, etc)
+ // which was is now curren (and will be used
+ // in subsequent epoll() calls)
+ virtual void OnModification(int fd, int event_mask) = 0;
+
+ // Summary:
+ // Called whenever an event occurs on the file-descriptor.
+ // This is where the bulk of processing is expected to occur.
+ // Args:
+ // fd - the file descriptor which was registered
+ // event - a struct that contains the event mask (composed of EPOLLIN,
+ // EPOLLOUT, etc), a flag that indicates whether this is a true
+ // epoll_wait event vs one from the ready list, and an output
+ // parameter for OnEvent to inform the EpollServer whether to put
+ // this fd on the ready list.
+ virtual void OnEvent(int fd, EpollEvent* event) = 0;
+
+ // Summary:
+ // Called when the file-descriptor is unregistered from the poll-server.
+ // Args:
+ // fd - the file descriptor which was registered, and of this call, is now
+ // unregistered.
+ // replaced - If true, this callback is being replaced by another, otherwise
+ // it is simply being removed.
+ virtual void OnUnregistration(int fd, bool replaced) = 0;
+
+ // Summary:
+ // Called when the epoll server is shutting down. This is different from
+ // OnUnregistration because the subclass may want to clean up memory.
+ // This is called in leiu of OnUnregistration.
+ // Args:
+ // fd - the file descriptor which was registered.
+ virtual void OnShutdown(EpollServer* eps, int fd) = 0;
+
+ virtual ~EpollCallbackInterface() {}
+
+ protected:
+ EpollCallbackInterface() {}
+};
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+class EpollServer {
+ public:
+ typedef EpollAlarmCallbackInterface AlarmCB;
+ typedef EpollCallbackInterface CB;
+
+ typedef std::multimap<int64, AlarmCB*> TimeToAlarmCBMap;
+ typedef TimeToAlarmCBMap::iterator AlarmRegToken;
+
+ // Summary:
+ // Constructor:
+ // By default, we don't wait any amount of time for events, and
+ // we suggest to the epoll-system that we're going to use on-the-order
+ // of 1024 FDs.
+ EpollServer();
+
+ ////////////////////////////////////////
+
+ // Destructor
+ virtual ~EpollServer();
+
+ ////////////////////////////////////////
+
+ // Summary
+ // Register a callback to be called whenever an event contained
+ // in the set of events included in event_mask occurs on the
+ // file-descriptor 'fd'
+ //
+ // Note that only one callback is allowed to be registered for
+ // any specific file-decriptor.
+ //
+ // If a callback is registered for a file-descriptor which has already
+ // been registered, then the previous callback is unregistered with
+ // the 'replaced' flag set to true. I.e. the previous callback's
+ // OnUnregistration() function is called like so:
+ // OnUnregistration(fd, true);
+ //
+ // The epoll server does NOT take on ownership of the callback: the callback
+ // creator is responsible for managing that memory.
+ //
+ // Args:
+ // fd - a valid file-descriptor
+ // cb - an instance of a subclass of EpollCallbackInterface
+ // event_mask - a combination of (EPOLLOUT, EPOLLIN.. etc) indicating
+ // the events for which the callback would like to be
+ // called.
+ virtual void RegisterFD(int fd, CB* cb, int event_mask);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // A shortcut for RegisterFD which sets things up such that the
+ // callback is called when 'fd' is available for writing.
+ // Args:
+ // fd - a valid file-descriptor
+ // cb - an instance of a subclass of EpollCallbackInterface
+ virtual void RegisterFDForWrite(int fd, CB* cb);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // A shortcut for RegisterFD which sets things up such that the
+ // callback is called when 'fd' is available for reading or writing.
+ // Args:
+ // fd - a valid file-descriptor
+ // cb - an instance of a subclass of EpollCallbackInterface
+ virtual void RegisterFDForReadWrite(int fd, CB* cb);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // A shortcut for RegisterFD which sets things up such that the
+ // callback is called when 'fd' is available for reading.
+ // Args:
+ // fd - a valid file-descriptor
+ // cb - an instance of a subclass of EpollCallbackInterface
+ virtual void RegisterFDForRead(int fd, CB* cb);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Removes the FD and the associated callback from the pollserver.
+ // If the callback is registered with other FDs, they will continue
+ // to be processed using the callback without modification.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the file-descriptor which should no-longer be monitored.
+ virtual void UnregisterFD(int fd);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies the event mask for the file-descriptor, replacing
+ // the old event_mask with the new one specified here.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the fd whose event mask should be modified.
+ // event_mask - the new event mask.
+ virtual void ModifyCallback(int fd, int event_mask);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies the event mask for the file-descriptor such that we
+ // no longer request events when 'fd' is readable.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the fd whose event mask should be modified.
+ virtual void StopRead(int fd);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies the event mask for the file-descriptor such that we
+ // request events when 'fd' is readable.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the fd whose event mask should be modified.
+ virtual void StartRead(int fd);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies the event mask for the file-descriptor such that we
+ // no longer request events when 'fd' is writable.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the fd whose event mask should be modified.
+ virtual void StopWrite(int fd);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies the event mask for the file-descriptor such that we
+ // request events when 'fd' is writable.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the fd whose event mask should be modified.
+ virtual void StartWrite(int fd);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Looks up the callback associated with the file-desriptor 'fd'.
+ // If a callback is associated with this file-descriptor, then
+ // it's OnEvent() method is called with the file-descriptor 'fd',
+ // and event_mask 'event_mask'
+ //
+ // If no callback is registered for this file-descriptor, nothing
+ // will happen as a result of this call.
+ //
+ // This function is used internally by the EpollServer, but is
+ // available publically so that events might be 'faked'. Calling
+ // this function with an fd and event_mask is equivalent (as far
+ // as the callback is concerned) to having a real event generated
+ // by epoll (except, of course, that read(), etc won't necessarily
+ // be able to read anything)
+ // Args:
+ // fd - the file-descriptor on which an event has occured.
+ // event_mask - a bitmask representing the events which have occured
+ // on/for this fd. This bitmask is composed of
+ // POLLIN, POLLOUT, etc.
+ //
+ void HandleEvent(int fd, int event_mask);
+
+ // Summary:
+ // Call this when you want the pollserver to
+ // wait for events and execute the callbacks associated with
+ // the file-descriptors on which those events have occured.
+ // Depending on the value of timeout_in_us_, this may or may
+ // not return immediately. Please reference the set_timeout()
+ // function for the specific behaviour.
+ virtual void WaitForEventsAndExecuteCallbacks();
+
+ // Summary:
+ // When an fd is registered to use edge trigger notification, the ready
+ // list can be used to simulate level trigger semantics. Edge trigger
+ // registration doesn't send an initial event, and only rising edge (going
+ // from blocked to unblocked) events are sent. A callback can put itself on
+ // the ready list by calling SetFDReady() after calling RegisterFD(). The
+ // OnEvent method of all callbacks associated with the fds on the ready
+ // list will be called immediately after processing the events returned by
+ // epoll_wait(). The fd is removed from the ready list before the
+ // callback's OnEvent() method is invoked. To stay on the ready list, the
+ // OnEvent() (or some function in that call chain) must call SetFDReady
+ // again. When a fd is unregistered using UnregisterFD(), the fd is
+ // automatically removed from the ready list.
+ //
+ // When the callback for a edge triggered fd hits the falling edge (about
+ // to block, either because of it got an EAGAIN, or had a short read/write
+ // operation), it should remove itself from the ready list using
+ // SetFDNotReady() (since OnEvent cannot distinguish between invocation
+ // from the ready list vs from a normal epoll event). All four ready list
+ // methods are safe to be called within the context of the callbacks.
+ //
+ // Since the ready list invokes EpollCallbackInterface::OnEvent, only fds
+ // that are registered with the EpollServer will be put on the ready list.
+ // SetFDReady() and SetFDNotReady() will do nothing if the EpollServer
+ // doesn't know about the fd passed in.
+ //
+ // Since the ready list cannot reliably determine proper set of events
+ // which should be sent to the callback, SetFDReady() requests the caller
+ // to provide the ready list with the event mask, which will be used later
+ // when OnEvent() is invoked by the ready list. Hence, the event_mask
+ // passedto SetFDReady() does not affect the actual epoll registration of
+ // the fd with the kernel. If a fd is already put on the ready list, and
+ // SetFDReady() is called again for that fd with a different event_mask,
+ // the event_mask will be updated.
+ virtual void SetFDReady(int fd, int events_to_fake);
+
+ virtual void SetFDNotReady(int fd);
+
+ // Summary:
+ // IsFDReady(), ReadyListSize(), and VerifyReadyList are intended as
+ // debugging tools and for writing unit tests.
+ // ISFDReady() returns whether a fd is in the ready list.
+ // ReadyListSize() returns the number of fds on the ready list.
+ // VerifyReadyList() checks the consistency of internal data structure. It
+ // will CHECK if it finds an error.
+ virtual bool IsFDReady(int fd) const;
+
+ size_t ReadyListSize() const { return ready_list_size_; }
+
+ void VerifyReadyList() const;
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Registers an alarm 'ac' to go off at time 'timeout_time_in_us'.
+ // If the callback returns a positive number from its OnAlarm() function,
+ // then the callback will be re-registered at that time, else the alarm
+ // owner is responsible for freeing up memory.
+ //
+ // Important: A give AlarmCB* can not be registered again if it is already
+ // registered. If a user wants to register a callback again it should first
+ // unregister the previous callback before calling RegisterAlarm again.
+ // Args:
+ // timeout_time_in_us - the absolute time at which the alarm should go off
+ // ac - the alarm which will be called.
+ virtual void RegisterAlarm(int64 timeout_time_in_us, AlarmCB* ac);
+
+ // Summary:
+ // Registers an alarm 'ac' to go off at time: (ApproximateNowInUs() +
+ // delta_in_us). While this is somewhat less accurate (see the description
+ // for ApproximateNowInUs() to see how 'approximate'), the error is never
+ // worse than the amount of time it takes to process all events in one
+ // WaitForEvents. As with 'RegisterAlarm()', if the callback returns a
+ // positive number from its OnAlarm() function, then the callback will be
+ // re-registered at that time, else the alarm owner is responsible for
+ // freeing up memory.
+ // Note that this function is purely a convienence. The
+ // same thing may be accomplished by using RegisterAlarm with
+ // ApproximateNowInUs() directly.
+ //
+ // Important: A give AlarmCB* can not be registered again if it is already
+ // registered. If a user wants to register a callback again it should first
+ // unregister the previous callback before calling RegisterAlarm again.
+ // Args:
+ // delta_in_us - the delta in microseconds from the ApproximateTimeInUs() at
+ // which point the alarm should go off.
+ // ac - the alarm which will be called.
+ void RegisterAlarmApproximateDelta(int64 delta_in_us, AlarmCB* ac) {
+ RegisterAlarm(ApproximateNowInUsec() + delta_in_us, ac);
+ }
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Unregister the alarm referred to by iterator_token; Callers should
+ // be warned that a token may have become already invalid when OnAlarm()
+ // is called, was unregistered, or OnShutdown was called on that alarm.
+ // Args:
+ // iterator_token - iterator to the alarm callback to unregister.
+ virtual void UnregisterAlarm(
+ const EpollServer::AlarmRegToken& iterator_token);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // returns the number of file-descriptors registered in this EpollServer.
+ // Returns:
+ // number of FDs registered (discounting the internal pipe used for Wake)
+ virtual int NumFDsRegistered() const;
+
+ // Summary:
+ // Force the epoll server to wake up (by writing to an internal pipe).
+ virtual void Wake();
+
+ // Summary:
+ // Wrapper around WallTimer's NowInUsec. We do this so that we can test
+ // EpollServer without using the system clock (and can avoid the flakiness
+ // that would ensue)
+ // Returns:
+ // the current time as number of microseconds since the Unix epoch.
+ virtual int64 NowInUsec() const;
+
+ // Summary:
+ // Since calling NowInUsec() many thousands of times per
+ // WaitForEventsAndExecuteCallbacks function call is, to say the least,
+ // inefficient, we allow users to use an approximate time instead. The
+ // time returned from this function is as accurate as NowInUsec() when
+ // WaitForEventsAndExecuteCallbacks is not an ancestor of the caller's
+ // callstack.
+ // However, when WaitForEventsAndExecuteCallbacks -is- an ancestor, then
+ // this function returns the time at which the
+ // WaitForEventsAndExecuteCallbacks function started to process events or
+ // alarms.
+ //
+ // Essentially, this function makes available a fast and mostly accurate
+ // mechanism for getting the time for any function handling an event or
+ // alarm. When functions which are not handling callbacks or alarms call
+ // this function, they get the slow and "absolutely" accurate time.
+ //
+ // Users should be encouraged to use this function.
+ // Returns:
+ // the "approximate" current time as number of microseconds since the Unix
+ // epoch.
+ virtual int64 ApproximateNowInUsec() const;
+
+ static std::string EventMaskToString(int event_mask);
+
+ // Summary:
+ // Logs the state of the epoll server with LOG(ERROR).
+ void LogStateOnCrash();
+
+ // Summary:
+ // Set the timeout to the value specified.
+ // If the timeout is set to a negative number,
+ // WaitForEventsAndExecuteCallbacks() will only return when an event has
+ // occured
+ // If the timeout is set to zero,
+ // WaitForEventsAndExecuteCallbacks() will return immediately
+ // If the timeout is set to a positive number,
+ // WaitForEventsAndExecuteCallbacks() will return when an event has
+ // occured, or when timeout_in_us microseconds has elapsed, whichever
+ // is first.
+ // Args:
+ // timeout_in_us - value specified depending on behaviour desired.
+ // See above.
+ void set_timeout_in_us(int64 timeout_in_us) {
+ timeout_in_us_ = timeout_in_us;
+ }
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Accessor for the current value of timeout_in_us.
+ int timeout_in_us() const { return timeout_in_us_; }
+
+ // Summary:
+ // Returns true when the EpollServer() is being destroyed.
+ bool in_shutdown() const { return in_shutdown_; }
+
+ bool ContainsAlarm(EpollAlarmCallbackInterface* alarm) const {
+ return all_alarms_.find(alarm) != all_alarms_.end();
+ }
+
+ // Summary:
+ // A function for implementing the ready list. It invokes OnEvent for each
+ // of the fd in the ready list, and takes care of adding them back to the
+ // ready list if the callback requests it (by checking that out_ready_mask
+ // is non-zero).
+ void CallReadyListCallbacks();
+
+ protected:
+ virtual int GetFlags(int fd);
+ inline int SetFlags(int fd, int flags) {
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ }
+
+ virtual void SetNonblocking(int fd);
+
+ // This exists here so that we can override this function in unittests
+ // in order to make effective mock EpollServer objects.
+ virtual int epoll_wait_impl(int epfd,
+ struct epoll_event* events,
+ int max_events,
+ int timeout_in_ms);
+
+ // this struct is used internally, and is never used by anything external
+ // to this class. Some of its members are declared mutable to get around the
+ // restriction imposed by hash_set. Since hash_set knows nothing about the
+ // objects it stores, it has to assume that every bit of the object is used
+ // in the hash function and equal_to comparison. Thus hash_set::iterator is a
+ // const iterator. In this case, the only thing that must stay constant is
+ // fd. Everything else are just along for the ride and changing them doesn't
+ // compromise the hash_set integrity.
+ struct CBAndEventMask {
+ CBAndEventMask()
+ : cb(NULL),
+ fd(-1),
+ event_mask(0),
+ events_asserted(0),
+ events_to_fake(0),
+ in_use(false) {
+ entry.le_next = NULL;
+ entry.le_prev = NULL;
+ }
+
+ CBAndEventMask(EpollCallbackInterface* cb,
+ int event_mask,
+ int fd)
+ : cb(cb), fd(fd), event_mask(event_mask), events_asserted(0),
+ events_to_fake(0), in_use(false) {
+ entry.le_next = NULL;
+ entry.le_prev = NULL;
+ }
+
+ // Required operator for hash_set. Normally operator== should be a free
+ // standing function. However, since CBAndEventMask is a protected type and
+ // it will never be a base class, it makes no difference.
+ bool operator==(const CBAndEventMask& cb_and_mask) const {
+ return fd == cb_and_mask.fd;
+ }
+ // A callback. If the fd is unregistered inside the callchain of OnEvent,
+ // the cb will be set to NULL.
+ mutable EpollCallbackInterface* cb;
+
+ mutable LIST_ENTRY(CBAndEventMask) entry;
+ // file descriptor registered with the epoll server.
+ int fd;
+ // the current event_mask registered for this callback.
+ mutable int event_mask;
+ // the event_mask that was returned by epoll
+ mutable int events_asserted;
+ // the event_mask for the ready list to use to call OnEvent.
+ mutable int events_to_fake;
+ // toggle around calls to OnEvent to tell UnregisterFD to not erase the
+ // iterator because HandleEvent is using it.
+ mutable bool in_use;
+ };
+
+ // Custom hash function to be used by hash_set.
+ struct CBAndEventMaskHash {
+ size_t operator()(const CBAndEventMask& cb_and_eventmask) const {
+ return static_cast<size_t>(cb_and_eventmask.fd);
+ }
+ };
+
+ typedef base::hash_set<CBAndEventMask, CBAndEventMaskHash> FDToCBMap;
+
+ // the following four functions are OS-specific, and are likely
+ // to be changed in a subclass if the poll/select method is changed
+ // from epoll.
+
+ // Summary:
+ // Deletes a file-descriptor from the set of FDs that should be
+ // monitored with epoll.
+ // Note that this only deals with modifying data relating -directly-
+ // with the epoll call-- it does not modify any data within the
+ // epoll_server.
+ // Args:
+ // fd - the file descriptor to-be-removed from the monitoring set
+ virtual void DelFD(int fd) const;
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Adds a file-descriptor to the set of FDs that should be
+ // monitored with epoll.
+ // Note that this only deals with modifying data relating -directly-
+ // with the epoll call.
+ // Args:
+ // fd - the file descriptor to-be-added to the monitoring set
+ // event_mask - the event mask (consisting of EPOLLIN, EPOLLOUT, etc
+ // OR'd together) which will be associated with this
+ // FD initially.
+ virtual void AddFD(int fd, int event_mask) const;
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modifies a file-descriptor in the set of FDs that should be
+ // monitored with epoll.
+ // Note that this only deals with modifying data relating -directly-
+ // with the epoll call.
+ // Args:
+ // fd - the file descriptor to-be-added to the monitoring set
+ // event_mask - the event mask (consisting of EPOLLIN, EPOLLOUT, etc
+ // OR'd together) which will be associated with this
+ // FD after this call.
+ virtual void ModFD(int fd, int event_mask) const;
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Modified the event mask associated with an FD in the set of
+ // data needed by epoll.
+ // Events are removed before they are added, thus, if ~0 is put
+ // in 'remove_event', whatever is put in 'add_event' will be
+ // the new event mask.
+ // If the file-descriptor specified is not registered in the
+ // epoll_server, then nothing happens as a result of this call.
+ // Args:
+ // fd - the file descriptor whose event mask is to be modified
+ // remove_event - the events which are to be removed from the current
+ // event_mask
+ // add_event - the events which are to be added to the current event_mask
+ //
+ //
+ virtual void ModifyFD(int fd, int remove_event, int add_event);
+
+ ////////////////////////////////////////
+
+ // Summary:
+ // Waits for events, and calls HandleEvents() for each
+ // fd, event pair discovered to possibly have an event.
+ // Note that a callback (B) may get a spurious event if
+ // another callback (A) has closed a file-descriptor N, and
+ // the callback (B) has a newly opened file-descriptor, which
+ // also happens to be N.
+ virtual void WaitForEventsAndCallHandleEvents(int64 timeout_in_us,
+ struct epoll_event events[],
+ int events_size);
+
+
+
+ // Summary:
+ // An internal function for implementing the ready list. It adds a fd's
+ // CBAndEventMask to the ready list. If the fd is already on the ready
+ // list, it is a no-op.
+ void AddToReadyList(CBAndEventMask* cb_and_mask);
+
+ // Summary:
+ // An internal function for implementing the ready list. It remove a fd's
+ // CBAndEventMask from the ready list. If the fd is not on the ready list,
+ // it is a no-op.
+ void RemoveFromReadyList(const CBAndEventMask& cb_and_mask);
+
+ // Summary:
+ // Calls any pending alarms that should go off and reregisters them if they
+ // were recurring.
+ virtual void CallAndReregisterAlarmEvents();
+
+ // The file-descriptor created for epolling
+ int epoll_fd_;
+
+ // The mapping of file-descriptor to CBAndEventMasks
+ FDToCBMap cb_map_;
+
+ // Custom hash function to be used by hash_set.
+ struct AlarmCBHash {
+ size_t operator()(AlarmCB*const& p) const {
+ return reinterpret_cast<size_t>(p);
+ }
+ };
+
+
+ // TOOD(sushantj): Having this hash_set is avoidable. We currently have it
+ // only so that we can enforce stringent checks that a caller can not register
+ // the same alarm twice. One option is to have an implementation in which
+ // this hash_set is used only in the debug mode.
+ typedef base::hash_set<AlarmCB*, AlarmCBHash> AlarmCBMap;
+ AlarmCBMap all_alarms_;
+
+ TimeToAlarmCBMap alarm_map_;
+
+ // The amount of time in microseconds that we'll wait before returning
+ // from the WaitForEventsAndExecuteCallbacks() function.
+ // If this is positive, wait that many microseconds.
+ // If this is negative, wait forever, or for the first event that occurs
+ // If this is zero, never wait for an event.
+ int64 timeout_in_us_;
+
+ // This is nonzero only after the invocation of epoll_wait_impl within
+ // WaitForEventsAndCallHandleEvents and before the function
+ // WaitForEventsAndExecuteCallbacks returns. At all other times, this is
+ // zero. This enables us to have relatively accurate time returned from the
+ // ApproximateNowInUs() function. See that function for more details.
+ int64 recorded_now_in_us_;
+
+ // This is used to implement CallAndReregisterAlarmEvents. This stores
+ // all alarms that were reregistered because OnAlarm() returned a
+ // value > 0 and the time at which they should be executed is less that
+ // the current time. By storing such alarms in this map we ensure
+ // that while calling CallAndReregisterAlarmEvents we do not call
+ // OnAlarm on any alarm in this set. This ensures that we do not
+ // go in an infinite loop.
+ AlarmCBMap alarms_reregistered_and_should_be_skipped_;
+
+ LIST_HEAD(ReadyList, CBAndEventMask) ready_list_;
+ LIST_HEAD(TmpList, CBAndEventMask) tmp_list_;
+ int ready_list_size_;
+ // TODO(alyssar): make this into something that scales up.
+ static const int events_size_ = 256;
+ struct epoll_event events_[256];
+
+#ifdef EPOLL_SERVER_EVENT_TRACING
+ struct EventRecorder {
+ public:
+ EventRecorder() : num_records_(0), record_threshold_(10000) {}
+
+ ~EventRecorder() {
+ Clear();
+ }
+
+ // When a number of events equals the record threshold,
+ // the collected data summary for all FDs will be written
+ // to LOG(INFO). Note that this does not include the
+ // individual events (if you'reinterested in those, you'll
+ // have to get at them programmatically).
+ // After any such flushing to LOG(INFO) all events will
+ // be cleared.
+ // Note that the definition of an 'event' is a bit 'hazy',
+ // as it includes the 'Unregistration' event, and perhaps
+ // others.
+ void set_record_threshold(int64 new_threshold) {
+ record_threshold_ = new_threshold;
+ }
+
+ void Clear() {
+ for (int i = 0; i < debug_events_.size(); ++i) {
+ delete debug_events_[i];
+ }
+ debug_events_.clear();
+ unregistered_fds_.clear();
+ event_counts_.clear();
+ }
+
+ void MaybeRecordAndClear() {
+ ++num_records_;
+ if ((num_records_ > record_threshold_) &&
+ (record_threshold_ > 0)) {
+ LOG(INFO) << "\n" << *this;
+ num_records_ = 0;
+ Clear();
+ }
+ }
+
+ void RecordFDMaskEvent(int fd, int mask, const char* function) {
+ FDMaskOutput* fdmo = new FDMaskOutput(fd, mask, function);
+ debug_events_.push_back(fdmo);
+ MaybeRecordAndClear();
+ }
+
+ void RecordEpollWaitEvent(int timeout_in_ms,
+ int num_events_generated) {
+ EpollWaitOutput* ewo = new EpollWaitOutput(timeout_in_ms,
+ num_events_generated);
+ debug_events_.push_back(ewo);
+ MaybeRecordAndClear();
+ }
+
+ void RecordEpollEvent(int fd, int event_mask) {
+ Events& events_for_fd = event_counts_[fd];
+ events_for_fd.AssignFromMask(event_mask);
+ MaybeRecordAndClear();
+ }
+
+ friend ostream& operator<<(ostream& os, const EventRecorder& er) {
+ for (int i = 0; i < er.unregistered_fds_.size(); ++i) {
+ os << "fd: " << er.unregistered_fds_[i] << "\n";
+ os << er.unregistered_fds_[i];
+ }
+ for (EventCountsMap::const_iterator i = er.event_counts_.begin();
+ i != er.event_counts_.end();
+ ++i) {
+ os << "fd: " << i->first << "\n";
+ os << i->second;
+ }
+ for (int i = 0; i < er.debug_events_.size(); ++i) {
+ os << *(er.debug_events_[i]) << "\n";
+ }
+ return os;
+ }
+
+ void RecordUnregistration(int fd) {
+ EventCountsMap::iterator i = event_counts_.find(fd);
+ if (i != event_counts_.end()) {
+ unregistered_fds_.push_back(i->second);
+ event_counts_.erase(i);
+ }
+ MaybeRecordAndClear();
+ }
+
+ protected:
+ class DebugOutput {
+ public:
+ friend ostream& operator<<(ostream& os, const DebugOutput& debug_output) {
+ debug_output.OutputToStream(os);
+ return os;
+ }
+ virtual void OutputToStream(ostream* os) const = 0;
+ virtual ~DebugOutput() {}
+ };
+
+ class FDMaskOutput : public DebugOutput {
+ public:
+ FDMaskOutput(int fd, int mask, const char* function) :
+ fd_(fd), mask_(mask), function_(function) {}
+ virtual void OutputToStream(ostream* os) const {
+ (*os) << "func: " << function_
+ << "\tfd: " << fd_;
+ if (mask_ != 0) {
+ (*os) << "\tmask: " << EventMaskToString(mask_);
+ }
+ }
+ int fd_;
+ int mask_;
+ const char* function_;
+ };
+
+ class EpollWaitOutput : public DebugOutput {
+ public:
+ EpollWaitOutput(int timeout_in_ms,
+ int num_events_generated) :
+ timeout_in_ms_(timeout_in_ms),
+ num_events_generated_(num_events_generated) {}
+ virtual void OutputToStream(ostream* os) const {
+ (*os) << "timeout_in_ms: " << timeout_in_ms_
+ << "\tnum_events_generated: " << num_events_generated_;
+ }
+ protected:
+ int timeout_in_ms_;
+ int num_events_generated_;
+ };
+
+ struct Events {
+ Events() :
+ epoll_in(0),
+ epoll_pri(0),
+ epoll_out(0),
+ epoll_rdnorm(0),
+ epoll_rdband(0),
+ epoll_wrnorm(0),
+ epoll_wrband(0),
+ epoll_msg(0),
+ epoll_err(0),
+ epoll_hup(0),
+ epoll_oneshot(0),
+ epoll_et(0) {}
+
+ void AssignFromMask(int event_mask) {
+ if (event_mask & EPOLLIN) ++epoll_in;
+ if (event_mask & EPOLLPRI) ++epoll_pri;
+ if (event_mask & EPOLLOUT) ++epoll_out;
+ if (event_mask & EPOLLRDNORM) ++epoll_rdnorm;
+ if (event_mask & EPOLLRDBAND) ++epoll_rdband;
+ if (event_mask & EPOLLWRNORM) ++epoll_wrnorm;
+ if (event_mask & EPOLLWRBAND) ++epoll_wrband;
+ if (event_mask & EPOLLMSG) ++epoll_msg;
+ if (event_mask & EPOLLERR) ++epoll_err;
+ if (event_mask & EPOLLHUP) ++epoll_hup;
+ if (event_mask & EPOLLONESHOT) ++epoll_oneshot;
+ if (event_mask & EPOLLET) ++epoll_et;
+ };
+
+ friend ostream& operator<<(ostream& os, const Events& ev) {
+ if (ev.epoll_in) {
+ os << "\t EPOLLIN: " << ev.epoll_in << "\n";
+ }
+ if (ev.epoll_pri) {
+ os << "\t EPOLLPRI: " << ev.epoll_pri << "\n";
+ }
+ if (ev.epoll_out) {
+ os << "\t EPOLLOUT: " << ev.epoll_out << "\n";
+ }
+ if (ev.epoll_rdnorm) {
+ os << "\t EPOLLRDNORM: " << ev.epoll_rdnorm << "\n";
+ }
+ if (ev.epoll_rdband) {
+ os << "\t EPOLLRDBAND: " << ev.epoll_rdband << "\n";
+ }
+ if (ev.epoll_wrnorm) {
+ os << "\t EPOLLWRNORM: " << ev.epoll_wrnorm << "\n";
+ }
+ if (ev.epoll_wrband) {
+ os << "\t EPOLLWRBAND: " << ev.epoll_wrband << "\n";
+ }
+ if (ev.epoll_msg) {
+ os << "\t EPOLLMSG: " << ev.epoll_msg << "\n";
+ }
+ if (ev.epoll_err) {
+ os << "\t EPOLLERR: " << ev.epoll_err << "\n";
+ }
+ if (ev.epoll_hup) {
+ os << "\t EPOLLHUP: " << ev.epoll_hup << "\n";
+ }
+ if (ev.epoll_oneshot) {
+ os << "\t EPOLLONESHOT: " << ev.epoll_oneshot << "\n";
+ }
+ if (ev.epoll_et) {
+ os << "\t EPOLLET: " << ev.epoll_et << "\n";
+ }
+ return os;
+ }
+
+ unsigned int epoll_in;
+ unsigned int epoll_pri;
+ unsigned int epoll_out;
+ unsigned int epoll_rdnorm;
+ unsigned int epoll_rdband;
+ unsigned int epoll_wrnorm;
+ unsigned int epoll_wrband;
+ unsigned int epoll_msg;
+ unsigned int epoll_err;
+ unsigned int epoll_hup;
+ unsigned int epoll_oneshot;
+ unsigned int epoll_et;
+ };
+
+ std::vector<DebugOutput*> debug_events_;
+ std::vector<Events> unregistered_fds_;
+ typedef base::hash_map<int, Events> EventCountsMap;
+ EventCountsMap event_counts_;
+ int64 num_records_;
+ int64 record_threshold_;
+ };
+
+ void ClearEventRecords() {
+ event_recorder_.Clear();
+ }
+ void WriteEventRecords(ostream* os) const {
+ (*os) << event_recorder_;
+ }
+
+ mutable EventRecorder event_recorder_;
+
+#endif
+
+ private:
+ // Helper functions used in the destructor.
+ void CleanupFDToCBMap();
+ void CleanupTimeToAlarmCBMap();
+
+ // The callback registered to the fds below. As the purpose of their
+ // registration is to wake the epoll server it just clears the pipe and
+ // returns.
+ scoped_ptr<ReadPipeCallback> wake_cb_;
+
+ // A pipe owned by the epoll server. The server will be registered to listen
+ // on read_fd_ and can be woken by Wake() which writes to write_fd_.
+ int read_fd_;
+ int write_fd_;
+
+ // This boolean is checked to see if it is false at the top of the
+ // WaitForEventsAndExecuteCallbacks function. If not, then it either returns
+ // without doing work, and logs to ERROR, or aborts the program (in
+ // DEBUG mode). If so, then it sets the bool to true, does work, and
+ // sets it back to false when done. This catches unwanted recursion.
+ bool in_wait_for_events_and_execute_callbacks_;
+
+ // Returns true when the EpollServer() is being destroyed.
+ bool in_shutdown_;
+
+ DISALLOW_COPY_AND_ASSIGN(EpollServer);
+};
+
+class EpollAlarmCallbackInterface {
+ public:
+ // Summary:
+ // Called when an alarm times out. Invalidates an AlarmRegToken.
+ // WARNING: If a token was saved to refer to an alarm callback, OnAlarm must
+ // delete it, as the reference is no longer valid.
+ // Returns:
+ // the unix time (in microseconds) at which this alarm should be signaled
+ // again, or 0 if the alarm should be removed.
+ virtual int64 OnAlarm() = 0;
+
+ // Summary:
+ // Called when the an alarm is registered. Invalidates an AlarmRegToken.
+ // Args:
+ // token: the iterator to the the alarm registered in the alarm map.
+ // WARNING: this token becomes invalid when the alarm fires, is
+ // unregistered, or OnShutdown is called on that alarm.
+ // eps: the epoll server the alarm is registered with.
+ virtual void OnRegistration(const EpollServer::AlarmRegToken& token,
+ EpollServer* eps) = 0;
+
+ // Summary:
+ // Called when the an alarm is unregistered.
+ // WARNING: It is not valid to unregister a callback and then use the token
+ // that was saved to refer to the callback.
+ virtual void OnUnregistration() = 0;
+
+ // Summary:
+ // Called when the epoll server is shutting down.
+ // Invalidates the AlarmRegToken that was given when this alarm was
+ // registered.
+ virtual void OnShutdown(EpollServer* eps) = 0;
+
+ virtual ~EpollAlarmCallbackInterface() {}
+
+ protected:
+ EpollAlarmCallbackInterface() {}
+};
+
+// A simple alarm which unregisters itself on destruction.
+//
+// PLEASE NOTE:
+// Any classes overriding these functions must either call the implementation
+// of the parent class, or is must otherwise make sure that the 'registered_'
+// boolean and the token, 'token_', are updated appropriately.
+class EpollAlarm : public EpollAlarmCallbackInterface {
+ public:
+ EpollAlarm();
+
+ virtual ~EpollAlarm();
+
+ // Marks the alarm as unregistered and returns 0. The return value may be
+ // safely ignored by subclasses.
+ virtual int64 OnAlarm() OVERRIDE;
+
+ // Marks the alarm as registered, and stores the token.
+ virtual void OnRegistration(const EpollServer::AlarmRegToken& token,
+ EpollServer* eps) OVERRIDE;
+
+ // Marks the alarm as unregistered.
+ virtual void OnUnregistration() OVERRIDE;
+
+ // Marks the alarm as unregistered.
+ virtual void OnShutdown(EpollServer* eps) OVERRIDE;
+
+ // If the alarm was registered, unregister it.
+ void UnregisterIfRegistered();
+
+ bool registered() const { return registered_; }
+
+ const EpollServer* eps() const { return eps_; }
+
+ private:
+ EpollServer::AlarmRegToken token_;
+ EpollServer* eps_;
+ bool registered_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_EPOLL_SERVER_H_
+
diff --git a/chromium/net/tools/flip_server/flip_config.cc b/chromium/net/tools/flip_server/flip_config.cc
new file mode 100644
index 00000000000..eb5c3caff69
--- /dev/null
+++ b/chromium/net/tools/flip_server/flip_config.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2011 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/tools/flip_server/flip_config.h"
+
+#include <unistd.h>
+
+namespace net {
+
+FlipAcceptor::FlipAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void *memory_cache)
+ : flip_handler_type_(flip_handler_type),
+ listen_ip_(listen_ip),
+ listen_port_(listen_port),
+ ssl_cert_filename_(ssl_cert_filename),
+ ssl_key_filename_(ssl_key_filename),
+ http_server_ip_(http_server_ip),
+ http_server_port_(http_server_port),
+ https_server_ip_(https_server_ip),
+ https_server_port_(https_server_port),
+ spdy_only_(spdy_only),
+ accept_backlog_size_(accept_backlog_size),
+ disable_nagle_(disable_nagle),
+ accepts_per_wake_(accepts_per_wake),
+ memory_cache_(memory_cache),
+ ssl_session_expiry_(300), // TODO(mbelshe): Hook these up!
+ ssl_disable_compression_(false),
+ idle_socket_timeout_s_(300) {
+ VLOG(1) << "Attempting to listen on " << listen_ip_.c_str() << ":"
+ << listen_port_.c_str();
+ if (!https_server_ip_.size())
+ https_server_ip_ = http_server_ip_;
+ if (!https_server_port_.size())
+ https_server_port_ = http_server_port_;
+
+ while (1) {
+ int ret = CreateListeningSocket(listen_ip_,
+ listen_port_,
+ true,
+ accept_backlog_size_,
+ true,
+ reuseport,
+ wait_for_iface,
+ disable_nagle_,
+ &listen_fd_);
+ if ( ret == 0 ) {
+ break;
+ } else if ( ret == -3 && wait_for_iface ) {
+ // Binding error EADDRNOTAVAIL was encounted. We need
+ // to wait for the interfaces to raised. try again.
+ usleep(200000);
+ } else {
+ LOG(ERROR) << "Unable to create listening socket for: ret = " << ret
+ << ": " << listen_ip_.c_str() << ":"
+ << listen_port_.c_str();
+ return;
+ }
+ }
+
+ SetNonBlocking(listen_fd_);
+ VLOG(1) << "Listening on socket: ";
+ if (flip_handler_type == FLIP_HANDLER_PROXY)
+ VLOG(1) << "\tType : Proxy";
+ else if (FLIP_HANDLER_SPDY_SERVER)
+ VLOG(1) << "\tType : SPDY Server";
+ else if (FLIP_HANDLER_HTTP_SERVER)
+ VLOG(1) << "\tType : HTTP Server";
+ VLOG(1) << "\tIP : " << listen_ip_;
+ VLOG(1) << "\tPort : " << listen_port_;
+ VLOG(1) << "\tHTTP Server : " << http_server_ip_ << ":"
+ << http_server_port_;
+ VLOG(1) << "\tHTTPS Server : " << https_server_ip_ << ":"
+ << https_server_port_;
+ VLOG(1) << "\tSSL : "
+ << (ssl_cert_filename.size()?"true":"false");
+ VLOG(1) << "\tCertificate : " << ssl_cert_filename;
+ VLOG(1) << "\tKey : " << ssl_key_filename;
+ VLOG(1) << "\tSpdy Only : " << (spdy_only?"true":"flase");
+}
+
+FlipAcceptor::~FlipAcceptor() {}
+
+FlipConfig::FlipConfig()
+ : server_think_time_in_s_(0),
+ log_destination_(logging::LOG_TO_SYSTEM_DEBUG_LOG),
+ wait_for_iface_(false) {
+}
+
+FlipConfig::~FlipConfig() {}
+
+void FlipConfig::AddAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void *memory_cache) {
+ // TODO(mbelshe): create a struct FlipConfigArgs{} for the arguments.
+ acceptors_.push_back(new FlipAcceptor(flip_handler_type,
+ listen_ip,
+ listen_port,
+ ssl_cert_filename,
+ ssl_key_filename,
+ http_server_ip,
+ http_server_port,
+ https_server_ip,
+ https_server_port,
+ spdy_only,
+ accept_backlog_size,
+ disable_nagle,
+ accepts_per_wake,
+ reuseport,
+ wait_for_iface,
+ memory_cache));
+}
+
+} // namespace
diff --git a/chromium/net/tools/flip_server/flip_config.h b/chromium/net/tools/flip_server/flip_config.h
new file mode 100644
index 00000000000..454c4bf6469
--- /dev/null
+++ b/chromium/net/tools/flip_server/flip_config.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_PROXY_CONFIG_H
+#define NET_TOOLS_FLIP_PROXY_CONFIG_H
+
+#include <arpa/inet.h> // in_addr_t
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/tools/flip_server/create_listener.h"
+
+namespace net {
+
+enum FlipHandlerType {
+ FLIP_HANDLER_PROXY,
+ FLIP_HANDLER_SPDY_SERVER,
+ FLIP_HANDLER_HTTP_SERVER
+};
+
+class FlipAcceptor {
+ public:
+ FlipAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void *memory_cache);
+ ~FlipAcceptor();
+
+ enum FlipHandlerType flip_handler_type_;
+ std::string listen_ip_;
+ std::string listen_port_;
+ std::string ssl_cert_filename_;
+ std::string ssl_key_filename_;
+ std::string http_server_ip_;
+ std::string http_server_port_;
+ std::string https_server_ip_;
+ std::string https_server_port_;
+ int spdy_only_;
+ int accept_backlog_size_;
+ bool disable_nagle_;
+ int accepts_per_wake_;
+ int listen_fd_;
+ void* memory_cache_;
+ int ssl_session_expiry_;
+ bool ssl_disable_compression_;
+ int idle_socket_timeout_s_;
+};
+
+class FlipConfig {
+ public:
+ FlipConfig();
+ ~FlipConfig();
+
+ void AddAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void *memory_cache);
+
+ std::vector<FlipAcceptor*> acceptors_;
+ double server_think_time_in_s_;
+ enum logging::LoggingDestination log_destination_;
+ std::string log_filename_;
+ bool wait_for_iface_;
+ int ssl_session_expiry_;
+ bool ssl_disable_compression_;
+ int idle_socket_timeout_s_;
+};
+
+} // namespace
+
+#endif // NET_TOOLS_FLIP_PROXY_CONFIG_H
+
diff --git a/chromium/net/tools/flip_server/flip_in_mem_edsm_server.cc b/chromium/net/tools/flip_server/flip_in_mem_edsm_server.cc
new file mode 100644
index 00000000000..ce7ffb8b056
--- /dev/null
+++ b/chromium/net/tools/flip_server/flip_in_mem_edsm_server.cc
@@ -0,0 +1,424 @@
+// Copyright (c) 2011 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 <errno.h>
+#include <signal.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "base/timer/timer.h"
+#include "net/tools/flip_server/acceptor_thread.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "net/tools/flip_server/spdy_interface.h"
+#include "net/tools/flip_server/split.h"
+#include "net/tools/flip_server/streamer_interface.h"
+
+using std::cout;
+using std::cerr;
+
+// If true, then disables the nagle algorithm);
+bool FLAGS_disable_nagle = true;
+
+// The number of times that accept() will be called when the
+// alarm goes off when the accept_using_alarm flag is set to true.
+// If set to 0, accept() will be performed until the accept queue
+// is completely drained and the accept() call returns an error);
+int32 FLAGS_accepts_per_wake = 0;
+
+// The size of the TCP accept backlog);
+int32 FLAGS_accept_backlog_size = 1024;
+
+// If set to false a single socket will be used. If set to true
+// then a new socket will be created for each accept thread.
+// Note that this only works with kernels that support
+// SO_REUSEPORT);
+bool FLAGS_reuseport = false;
+
+// Flag to force spdy, even if NPN is not negotiated.
+bool FLAGS_force_spdy = false;
+
+// The amount of time the server delays before sending back the
+// reply);
+double FLAGS_server_think_time_in_s = 0;
+
+net::FlipConfig g_proxy_config;
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::vector<std::string> &split(const std::string &s,
+ char delim,
+ std::vector<std::string> &elems) {
+ std::stringstream ss(s);
+ std::string item;
+ while(std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+ return elems;
+}
+
+std::vector<std::string> split(const std::string &s, char delim) {
+ std::vector<std::string> elems;
+ return split(s, delim, elems);
+}
+
+bool GotQuitFromStdin() {
+ // Make stdin nonblocking. Yes this is done each time. Oh well.
+ fcntl(0, F_SETFL, O_NONBLOCK);
+ char c;
+ std::string maybequit;
+ while (read(0, &c, 1) > 0) {
+ maybequit += c;
+ }
+ if (maybequit.size()) {
+ VLOG(1) << "scanning string: \"" << maybequit << "\"";
+ }
+ return (maybequit.size() > 1 &&
+ (maybequit.c_str()[0] == 'q' ||
+ maybequit.c_str()[0] == 'Q'));
+}
+
+const char* BoolToStr(bool b) {
+ if (b)
+ return "true";
+ return "false";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static bool wantExit = false;
+static bool wantLogClose = false;
+void SignalHandler(int signum)
+{
+ switch(signum) {
+ case SIGTERM:
+ case SIGINT:
+ wantExit = true;
+ break;
+ case SIGHUP:
+ wantLogClose = true;
+ break;
+ }
+}
+
+static int OpenPidFile(const char *pidfile)
+{
+ int fd;
+ struct stat pid_stat;
+ int ret;
+
+ fd = open(pidfile, O_RDWR | O_CREAT, 0600);
+ if (fd == -1) {
+ cerr << "Could not open pid file '" << pidfile << "' for reading.\n";
+ exit(1);
+ }
+
+ ret = flock(fd, LOCK_EX | LOCK_NB);
+ if (ret == -1) {
+ if (errno == EWOULDBLOCK) {
+ cerr << "Flip server is already running.\n";
+ } else {
+ cerr << "Error getting lock on pid file: " << strerror(errno) << "\n";
+ }
+ exit(1);
+ }
+
+ if (fstat(fd, &pid_stat) == -1) {
+ cerr << "Could not stat pid file '" << pidfile << "': " << strerror(errno)
+ << "\n";
+ }
+ if (pid_stat.st_size != 0) {
+ if (ftruncate(fd, pid_stat.st_size) == -1) {
+ cerr << "Could not truncate pid file '" << pidfile << "': "
+ << strerror(errno) << "\n";
+ }
+ }
+
+ char pid_str[8];
+ snprintf(pid_str, sizeof(pid_str), "%d", getpid());
+ int bytes = static_cast<int>(strlen(pid_str));
+ if (write(fd, pid_str, strlen(pid_str)) != bytes) {
+ cerr << "Could not write pid file: " << strerror(errno) << "\n";
+ close(fd);
+ exit(1);
+ }
+
+ return fd;
+}
+
+int main (int argc, char**argv)
+{
+ unsigned int i = 0;
+ bool wait_for_iface = false;
+ int pidfile_fd;
+
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGTERM, SignalHandler);
+ signal(SIGINT, SignalHandler);
+ signal(SIGHUP, SignalHandler);
+
+ CommandLine::Init(argc, argv);
+ CommandLine cl(argc, argv);
+
+ if (cl.HasSwitch("help") || argc < 2) {
+ cout << argv[0] << " <options>\n";
+ cout << " Proxy options:\n";
+ cout << "\t--proxy<1..n>=\"<listen ip>,<listen port>,"
+ << "<ssl cert filename>,\n"
+ << "\t <ssl key filename>,<http server ip>,"
+ << "<http server port>,\n"
+ << "\t [https server ip],[https server port],"
+ << "<spdy only 0|1>\"\n";
+ cout << "\t * The https server ip and port may be left empty if they are"
+ << " the same as\n"
+ << "\t the http server fields.\n";
+ cout << "\t * spdy only prevents non-spdy https connections from being"
+ << " passed\n"
+ << "\t through the proxy listen ip:port.\n";
+ cout << "\t--forward-ip-header=<header name>\n";
+ cout << "\n Server options:\n";
+ cout << "\t--spdy-server=\"<listen ip>,<listen port>,[ssl cert filename],"
+ << "\n\t [ssl key filename]\"\n";
+ cout << "\t--http-server=\"<listen ip>,<listen port>,[ssl cert filename],"
+ << "\n\t [ssl key filename]\"\n";
+ cout << "\t * Leaving the ssl cert and key fields empty will disable ssl"
+ << " for the\n"
+ << "\t http and spdy flip servers\n";
+ cout << "\n Global options:\n";
+ cout << "\t--logdest=<file|system|both>\n";
+ cout << "\t--logfile=<logfile>\n";
+ cout << "\t--wait-for-iface\n";
+ cout << "\t * The flip server will block until the listen ip has been"
+ << " raised.\n";
+ cout << "\t--ssl-session-expiry=<seconds> (default is 300)\n";
+ cout << "\t--ssl-disable-compression\n";
+ cout << "\t--idle-timeout=<seconds> (default is 300)\n";
+ cout << "\t--pidfile=<filepath> (default /var/run/flip-server.pid)\n";
+ cout << "\t--help\n";
+ exit(0);
+ }
+
+ if (cl.HasSwitch("pidfile")) {
+ pidfile_fd = OpenPidFile(cl.GetSwitchValueASCII("pidfile").c_str());
+ } else {
+ pidfile_fd = OpenPidFile(PIDFILE);
+ }
+
+ net::OutputOrdering::set_server_think_time_in_s(FLAGS_server_think_time_in_s);
+
+ if (cl.HasSwitch("forward-ip-header")) {
+ net::SpdySM::set_forward_ip_header(
+ cl.GetSwitchValueASCII("forward-ip-header"));
+ net::StreamerSM::set_forward_ip_header(
+ cl.GetSwitchValueASCII("forward-ip-header"));
+ }
+
+ if (cl.HasSwitch("logdest")) {
+ std::string log_dest_value = cl.GetSwitchValueASCII("logdest");
+ if (log_dest_value.compare("file") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_FILE;
+ } else if (log_dest_value.compare("system") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ } else if (log_dest_value.compare("both") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_ALL;
+ } else {
+ LOG(FATAL) << "Invalid logging destination value: " << log_dest_value;
+ }
+ } else {
+ g_proxy_config.log_destination_ = logging::LOG_NONE;
+ }
+
+ if (cl.HasSwitch("logfile")) {
+ g_proxy_config.log_filename_ = cl.GetSwitchValueASCII("logfile");
+ if (g_proxy_config.log_destination_ == logging::LOG_NONE) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_FILE;
+ }
+ } else if ((g_proxy_config.log_destination_ & logging::LOG_TO_FILE) != 0) {
+ LOG(FATAL) << "Logging destination requires a log file to be specified.";
+ }
+
+ if (cl.HasSwitch("wait-for-iface")) {
+ wait_for_iface = true;
+ }
+
+ if (cl.HasSwitch("ssl-session-expiry")) {
+ std::string session_expiry = cl.GetSwitchValueASCII("ssl-session-expiry");
+ g_proxy_config.ssl_session_expiry_ = atoi(session_expiry.c_str());
+ }
+
+ if (cl.HasSwitch("ssl-disable-compression")) {
+ g_proxy_config.ssl_disable_compression_ = true;
+ }
+
+ if (cl.HasSwitch("idle-timeout")) {
+ g_proxy_config.idle_socket_timeout_s_ =
+ atoi(cl.GetSwitchValueASCII("idle-timeout").c_str());
+ }
+
+ if (cl.HasSwitch("force_spdy"))
+ net::SMConnection::set_force_spdy(true);
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = g_proxy_config.log_destination_;
+ settings.log_file = g_proxy_config.log_filename_.c_str();
+ settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ logging::InitLogging(settings);
+
+ LOG(INFO) << "Flip SPDY proxy started with configuration:";
+ LOG(INFO) << "Logging destination : " << g_proxy_config.log_destination_;
+ LOG(INFO) << "Log file : " << g_proxy_config.log_filename_;
+ LOG(INFO) << "Forward IP Header : "
+ << (net::SpdySM::forward_ip_header().length() ?
+ net::SpdySM::forward_ip_header() : "<disabled>");
+ LOG(INFO) << "Wait for interfaces : " << (wait_for_iface?"true":"false");
+ LOG(INFO) << "Accept backlog size : " << FLAGS_accept_backlog_size;
+ LOG(INFO) << "Accepts per wake : " << FLAGS_accepts_per_wake;
+ LOG(INFO) << "Disable nagle : "
+ << (FLAGS_disable_nagle?"true":"false");
+ LOG(INFO) << "Reuseport : "
+ << (FLAGS_reuseport?"true":"false");
+ LOG(INFO) << "Force SPDY : "
+ << (FLAGS_force_spdy?"true":"false");
+ LOG(INFO) << "SSL session expiry : "
+ << g_proxy_config.ssl_session_expiry_;
+ LOG(INFO) << "SSL disable compression : "
+ << g_proxy_config.ssl_disable_compression_;
+ LOG(INFO) << "Connection idle timeout : "
+ << g_proxy_config.idle_socket_timeout_s_;
+
+ // Proxy Acceptors
+ while (true) {
+ i += 1;
+ std::stringstream name;
+ name << "proxy" << i;
+ if (!cl.HasSwitch(name.str())) {
+ break;
+ }
+ std::string value = cl.GetSwitchValueASCII(name.str());
+ std::vector<std::string> valueArgs = split(value, ',');
+ CHECK_EQ((unsigned int)9, valueArgs.size());
+ int spdy_only = atoi(valueArgs[8].c_str());
+ // If wait_for_iface is enabled, then this call will block
+ // indefinitely until the interface is raised.
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_PROXY,
+ valueArgs[0], valueArgs[1],
+ valueArgs[2], valueArgs[3],
+ valueArgs[4], valueArgs[5],
+ valueArgs[6], valueArgs[7],
+ spdy_only,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ NULL);
+ }
+
+ // Spdy Server Acceptor
+ net::MemoryCache spdy_memory_cache;
+ if (cl.HasSwitch("spdy-server")) {
+ spdy_memory_cache.AddFiles();
+ std::string value = cl.GetSwitchValueASCII("spdy-server");
+ std::vector<std::string> valueArgs = split(value, ',');
+ while (valueArgs.size() < 4)
+ valueArgs.push_back(std::string());
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_SPDY_SERVER,
+ valueArgs[0],
+ valueArgs[1],
+ valueArgs[2],
+ valueArgs[3],
+ std::string(),
+ std::string(),
+ std::string(),
+ std::string(),
+ 0,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ &spdy_memory_cache);
+ }
+
+ // Spdy Server Acceptor
+ net::MemoryCache http_memory_cache;
+ if (cl.HasSwitch("http-server")) {
+ http_memory_cache.AddFiles();
+ std::string value = cl.GetSwitchValueASCII("http-server");
+ std::vector<std::string> valueArgs = split(value, ',');
+ while (valueArgs.size() < 4)
+ valueArgs.push_back(std::string());
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_HTTP_SERVER,
+ valueArgs[0],
+ valueArgs[1],
+ valueArgs[2],
+ valueArgs[3],
+ std::string(),
+ std::string(),
+ std::string(),
+ std::string(),
+ 0,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ &http_memory_cache);
+ }
+
+ std::vector<net::SMAcceptorThread*> sm_worker_threads_;
+
+ for (i = 0; i < g_proxy_config.acceptors_.size(); i++) {
+ net::FlipAcceptor *acceptor = g_proxy_config.acceptors_[i];
+
+ sm_worker_threads_.push_back(
+ new net::SMAcceptorThread(acceptor,
+ (net::MemoryCache *)acceptor->memory_cache_));
+ // Note that spdy_memory_cache is not threadsafe, it is merely
+ // thread compatible. Thus, if ever we are to spawn multiple threads,
+ // we either must make the MemoryCache threadsafe, or use
+ // a separate MemoryCache for each thread.
+ //
+ // The latter is what is currently being done as we spawn
+ // a separate thread for each http and spdy server acceptor.
+
+ sm_worker_threads_.back()->InitWorker();
+ sm_worker_threads_.back()->Start();
+ }
+
+ while (!wantExit) {
+ // Close logfile when HUP signal is received. Logging system will
+ // automatically reopen on next log message.
+ if ( wantLogClose ) {
+ wantLogClose = false;
+ VLOG(1) << "HUP received, reopening log file.";
+ logging::CloseLogFile();
+ }
+ if (GotQuitFromStdin()) {
+ for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) {
+ sm_worker_threads_[i]->Quit();
+ }
+ for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) {
+ sm_worker_threads_[i]->Join();
+ }
+ break;
+ }
+ usleep(1000*10); // 10 ms
+ }
+
+ unlink(PIDFILE);
+ close(pidfile_fd);
+ return 0;
+}
+
diff --git a/chromium/net/tools/flip_server/http_interface.cc b/chromium/net/tools/flip_server/http_interface.cc
new file mode 100644
index 00000000000..7a44c0313af
--- /dev/null
+++ b/chromium/net/tools/flip_server/http_interface.cc
@@ -0,0 +1,362 @@
+// Copyright (c) 2009 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/tools/flip_server/http_interface.h"
+
+#include "net/tools/dump_cache/url_utilities.h"
+#include "net/tools/flip_server/balsa_frame.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/spdy_util.h"
+
+namespace net {
+
+HttpSM::HttpSM(SMConnection* connection,
+ SMInterface* sm_spdy_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor)
+ : seq_num_(0),
+ http_framer_(new BalsaFrame),
+ stream_id_(0),
+ server_idx_(-1),
+ connection_(connection),
+ sm_spdy_interface_(sm_spdy_interface),
+ output_list_(connection->output_list()),
+ output_ordering_(connection),
+ memory_cache_(connection->memory_cache()),
+ acceptor_(acceptor) {
+ http_framer_->set_balsa_visitor(this);
+ http_framer_->set_balsa_headers(&headers_);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
+ http_framer_->set_is_request(false);
+}
+HttpSM::~HttpSM() {
+ Reset();
+ delete http_framer_;
+}
+
+void HttpSM::ProcessBodyData(const char *input, size_t size) {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process Body Data: stream "
+ << stream_id_ << ": size " << size;
+ sm_spdy_interface_->SendDataFrame(stream_id_, input, size, 0, false);
+ }
+}
+
+void HttpSM::ProcessHeaders(const BalsaHeaders& headers) {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ std::string host =
+ UrlUtilities::GetUrlHost(headers.GetHeader("Host").as_string());
+ std::string method = headers.request_method().as_string();
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Received Request: "
+ << headers.request_uri().as_string() << " " << method;
+ std::string filename = EncodeURL(headers.request_uri().as_string(),
+ host, method);
+ NewStream(stream_id_, 0, filename);
+ stream_id_ += 2;
+ } else {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Received Response from "
+ << connection_->server_ip_ << ":"
+ << connection_->server_port_ << " ";
+ sm_spdy_interface_->SendSynReply(stream_id_, headers);
+ }
+}
+
+void HttpSM::MessageDone() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: MessageDone. Sending EOF: "
+ << "stream " << stream_id_;
+ sm_spdy_interface_->SendEOF(stream_id_);
+ } else {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: MessageDone.";
+ }
+}
+
+void HttpSM::HandleHeaderError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void HttpSM::HandleChunkingError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void HttpSM::HandleBodyError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void HttpSM::HandleError() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Error detected";
+}
+
+void HttpSM::AddToOutputOrder(const MemCacheIter& mci) {
+ output_ordering_.AddToOutputOrder(mci);
+}
+
+void HttpSM::SendOKResponse(uint32 stream_id, std::string* output) {
+ SendOKResponseImpl(stream_id, output);
+}
+
+void HttpSM::InitSMInterface(SMInterface* sm_spdy_interface,
+ int32 server_idx) {
+ sm_spdy_interface_ = sm_spdy_interface;
+ server_idx_ = server_idx;
+}
+
+void HttpSM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Initializing server "
+ << "connection.";
+ connection_->InitSMConnection(connection_pool,
+ sm_interface,
+ epoll_server,
+ fd,
+ server_ip,
+ server_port,
+ remote_ip,
+ use_ssl);
+}
+
+size_t HttpSM::ProcessReadInput(const char* data, size_t len) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process read input: stream "
+ << stream_id_;
+ return http_framer_->ProcessInput(data, len);
+}
+
+size_t HttpSM::ProcessWriteInput(const char* data, size_t len) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process write input: size "
+ << len << ": stream " << stream_id_;
+ char * dataPtr = new char[len];
+ memcpy(dataPtr, data, len);
+ DataFrame* data_frame = new DataFrame;
+ data_frame->data = (const char *)dataPtr;
+ data_frame->size = len;
+ data_frame->delete_when_done = true;
+ connection_->EnqueueDataFrame(data_frame);
+ return len;
+}
+
+bool HttpSM::MessageFullyRead() const {
+ return http_framer_->MessageFullyRead();
+}
+
+void HttpSM::SetStreamID(uint32 stream_id) {
+ stream_id_ = stream_id;
+}
+
+bool HttpSM::Error() const {
+ return http_framer_->Error();
+}
+
+const char* HttpSM::ErrorAsString() const {
+ return BalsaFrameEnums::ErrorCodeToString(http_framer_->ErrorCode());
+}
+
+void HttpSM::Reset() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Reset: stream "
+ << stream_id_;
+ http_framer_->Reset();
+}
+
+void HttpSM::ResetForNewConnection() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Server connection closing "
+ << "to: " << connection_->server_ip_ << ":"
+ << connection_->server_port_ << " ";
+ }
+ // Message has not been fully read, either it is incomplete or the
+ // server is closing the connection to signal message end.
+ if (!MessageFullyRead()) {
+ VLOG(2) << "HTTP response closed before end of file detected. "
+ << "Sending EOF to spdy.";
+ sm_spdy_interface_->SendEOF(stream_id_);
+ }
+ seq_num_ = 0;
+ output_ordering_.Reset();
+ http_framer_->Reset();
+ if (sm_spdy_interface_) {
+ sm_spdy_interface_->ResetForNewInterface(server_idx_);
+ }
+}
+
+void HttpSM::Cleanup() {
+ if (!(acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER)) {
+ VLOG(2) << "HttpSM Request Fully Read; stream_id: " << stream_id_;
+ connection_->Cleanup("request complete");
+ }
+}
+
+int HttpSM::PostAcceptHook() {
+ return 1;
+}
+
+void HttpSM::NewStream(uint32 stream_id, uint32 priority,
+ const std::string& filename) {
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+ mci.priority = priority;
+ if (!memory_cache_->AssignFileData(filename, &mci)) {
+ // error creating new stream.
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
+ SendErrorNotFound(stream_id);
+ } else {
+ AddToOutputOrder(mci);
+ }
+}
+
+void HttpSM::SendEOF(uint32 stream_id) {
+ SendEOFImpl(stream_id);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ sm_spdy_interface_->ResetForNewInterface(server_idx_);
+ }
+}
+
+void HttpSM::SendErrorNotFound(uint32 stream_id) {
+ SendErrorNotFoundImpl(stream_id);
+}
+
+size_t HttpSM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+}
+
+size_t HttpSM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynReplyImpl(stream_id, headers);
+}
+
+void HttpSM::SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) {
+ SendDataFrameImpl(stream_id, data, len, flags, compress);
+}
+
+void HttpSM::SendEOFImpl(uint32 stream_id) {
+ DataFrame* df = new DataFrame;
+ df->data = "0\r\n\r\n";
+ df->size = 5;
+ df->delete_when_done = false;
+ EnqueueDataFrame(df);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ Reset();
+ }
+}
+
+void HttpSM::SendErrorNotFoundImpl(uint32 stream_id) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
+ my_headers.RemoveAllOfHeader("content-length");
+ my_headers.AppendHeader("transfer-encoding", "chunked");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(stream_id, "page not found", 14, 0, false);
+ SendEOFImpl(stream_id);
+ output_ordering_.RemoveStreamId(stream_id);
+}
+
+void HttpSM::SendOKResponseImpl(uint32 stream_id, std::string* output) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
+ my_headers.RemoveAllOfHeader("content-length");
+ my_headers.AppendHeader("transfer-encoding", "chunked");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(stream_id, output->c_str(), output->size(), 0, false);
+ SendEOFImpl(stream_id);
+ output_ordering_.RemoveStreamId(stream_id);
+}
+
+size_t HttpSM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ SimpleBuffer sb;
+ headers.WriteHeaderAndEndingToBuffer(&sb);
+ DataFrame* df = new DataFrame;
+ df->size = sb.ReadableBytes();
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ sb.Read(buffer, df->size);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending HTTP Reply header "
+ << stream_id_;
+ size_t df_size = df->size;
+ EnqueueDataFrame(df);
+ return df_size;
+}
+
+size_t HttpSM::SendSynStreamImpl(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ SimpleBuffer sb;
+ headers.WriteHeaderAndEndingToBuffer(&sb);
+ DataFrame* df = new DataFrame;
+ df->size = sb.ReadableBytes();
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ sb.Read(buffer, df->size);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending HTTP Reply header "
+ << stream_id_;
+ size_t df_size = df->size;
+ EnqueueDataFrame(df);
+ return df_size;
+}
+
+void HttpSM::SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) {
+ char chunk_buf[128];
+ snprintf(chunk_buf, sizeof(chunk_buf), "%x\r\n", (unsigned int)len);
+ std::string chunk_description(chunk_buf);
+ DataFrame* df = new DataFrame;
+ df->size = chunk_description.size() + len + 2;
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ memcpy(buffer, chunk_description.data(), chunk_description.size());
+ memcpy(buffer + chunk_description.size(), data, len);
+ memcpy(buffer + chunk_description.size() + len, "\r\n", 2);
+ EnqueueDataFrame(df);
+}
+
+void HttpSM::EnqueueDataFrame(DataFrame* df) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Enqueue data frame: stream "
+ << stream_id_;
+ connection_->EnqueueDataFrame(df);
+}
+
+void HttpSM::GetOutput() {
+ MemCacheIter* mci = output_ordering_.GetIter();
+ if (mci == NULL) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput: nothing to "
+ << "output!?: stream " << stream_id_;
+ return;
+ }
+ if (!mci->transformed_header) {
+ mci->bytes_sent = SendSynReply(mci->stream_id,
+ *(mci->file_data->headers()));
+ mci->transformed_header = true;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput transformed "
+ << "header stream_id: [" << mci->stream_id << "]";
+ return;
+ }
+ if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
+ SendEOF(mci->stream_id);
+ output_ordering_.RemoveStreamId(mci->stream_id);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "GetOutput remove_stream_id: ["
+ << mci->stream_id << "]";
+ return;
+ }
+ size_t num_to_write =
+ mci->file_data->body().size() - mci->body_bytes_consumed;
+ if (num_to_write > mci->max_segment_size)
+ num_to_write = mci->max_segment_size;
+
+ SendDataFrame(mci->stream_id,
+ mci->file_data->body().data() + mci->body_bytes_consumed,
+ num_to_write, 0, true);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput SendDataFrame["
+ << mci->stream_id << "]: " << num_to_write;
+ mci->body_bytes_consumed += num_to_write;
+ mci->bytes_sent += num_to_write;
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/http_interface.h b/chromium/net/tools/flip_server/http_interface.h
new file mode 100644
index 00000000000..e311881e342
--- /dev/null
+++ b/chromium/net/tools/flip_server/http_interface.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_HTTP_INTERFACE_
+#define NET_TOOLS_FLIP_SERVER_HTTP_INTERFACE_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class BalsaFrame;
+class DataFrame;
+class EpollServer;
+class FlipAcceptor;
+class MemoryCache;
+
+class HttpSM : public BalsaVisitorInterface,
+ public SMInterface {
+ public:
+ HttpSM(SMConnection* connection,
+ SMInterface* sm_spdy_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor);
+ virtual ~HttpSM();
+
+ private:
+ // BalsaVisitorInterface:
+ virtual void ProcessBodyInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessBodyData(const char *input, size_t size) OVERRIDE;
+ virtual void ProcessHeaderInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE;
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char *line_input,
+ size_t line_length,
+ const char *version_input,
+ size_t version_length,
+ const char *status_input,
+ size_t status_length,
+ const char *reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char *input,
+ size_t size) OVERRIDE {}
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void MessageDone() OVERRIDE;
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+
+ void HandleError();
+
+ public:
+ void AddToOutputOrder(const MemCacheIter& mci);
+ void SendOKResponse(uint32 stream_id, std::string* output);
+ BalsaFrame* spdy_framer() { return http_framer_; }
+ virtual void set_is_request() OVERRIDE {}
+
+ // SMInterface:
+ virtual void InitSMInterface(SMInterface* sm_spdy_interface,
+ int32 server_idx) OVERRIDE;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE;
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE {}
+ virtual void ResetForNewConnection() OVERRIDE;
+ virtual void Cleanup() OVERRIDE;
+ virtual int PostAcceptHook() OVERRIDE;
+
+ virtual void NewStream(uint32 stream_id, uint32 priority,
+ const std::string& filename) OVERRIDE;
+ virtual void SendEOF(uint32 stream_id) OVERRIDE;
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE;
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) OVERRIDE;
+
+ private:
+ void SendEOFImpl(uint32 stream_id);
+ void SendErrorNotFoundImpl(uint32 stream_id);
+ void SendOKResponseImpl(uint32 stream_id, std::string* output);
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers);
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers);
+ void SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress);
+ void EnqueueDataFrame(DataFrame* df);
+ virtual void GetOutput() OVERRIDE;
+
+ private:
+ uint64 seq_num_;
+ BalsaFrame* http_framer_;
+ BalsaHeaders headers_;
+ uint32 stream_id_;
+ int32 server_idx_;
+
+ SMConnection* connection_;
+ SMInterface* sm_spdy_interface_;
+ OutputList* output_list_;
+ OutputOrdering output_ordering_;
+ MemoryCache* memory_cache_;
+ FlipAcceptor* acceptor_;
+};
+
+} // namespace
+
+#endif // NET_TOOLS_FLIP_SERVER_HTTP_INTERFACE_
+
diff --git a/chromium/net/tools/flip_server/http_message_constants.cc b/chromium/net/tools/flip_server/http_message_constants.cc
new file mode 100644
index 00000000000..e60fb1cd451
--- /dev/null
+++ b/chromium/net/tools/flip_server/http_message_constants.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2009 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/tools/flip_server/http_message_constants.h"
+
+namespace net {
+
+const char* get_http_status_message(int status_message) {
+ switch (status_message) {
+ case 100:
+ return "Continue";
+ case 101:
+ return "Switching Protocols";
+ case 200:
+ return "OK";
+ case 201:
+ return "Created";
+ case 202:
+ return "Accepted";
+ case 203:
+ return "Non-Authoritative Information";
+ case 204:
+ return "No Content";
+ case 205:
+ return "Reset Content";
+ case 206:
+ return "Partial Content";
+ case 300:
+ return "Multiple Choices";
+ case 301:
+ return "Moved Permanently";
+ case 302:
+ return "Found";
+ case 303:
+ return "See Other";
+ case 304:
+ return "Not Modified";
+ case 305:
+ return "Use Proxy";
+ case 307:
+ return "Temporary Redirect";
+ case 400:
+ return "Bad Request";
+ case 401:
+ return "Unauthorized";
+ case 402:
+ return "Payment Required";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 405:
+ return "Method Not Allowed";
+ case 406:
+ return "Not Acceptable";
+ case 407:
+ return "Proxy Authentication Required";
+ case 408:
+ return "Request Time-out";
+ case 409:
+ return "Conflict";
+ case 410:
+ return "Gone";
+ case 411:
+ return "Length Required";
+ case 412:
+ return "Precondition Failed";
+ case 413:
+ return "Request Entity Too Large";
+ case 414:
+ return "Request-URI Too Large";
+ case 415:
+ return "Unsupported Media Type";
+ case 416:
+ return "Requested range not satisfiable";
+ case 417:
+ return "Expectation Failed";
+ case 500:
+ return "Internal Server Error";
+ case 501:
+ return "Not Implemented";
+ case 502:
+ return "Bad Gateway";
+ case 503:
+ return "Service Unavailable";
+ case 504:
+ return "Gateway Time-out";
+ case 505:
+ return "HTTP Version not supported";
+ }
+ return "unknown";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const int http_status_codes[] = {
+ 100,
+ 101,
+ 200,
+ 201,
+ 202,
+ 203,
+ 204,
+ 205,
+ 206,
+ 300,
+ 301,
+ 302,
+ 303,
+ 304,
+ 305,
+ 307,
+ 400,
+ 401,
+ 402,
+ 403,
+ 404,
+ 405,
+ 406,
+ 407,
+ 408,
+ 409,
+ 410,
+ 411,
+ 412,
+ 413,
+ 414,
+ 415,
+ 416,
+ 417,
+ 500,
+ 501,
+ 502,
+ 503,
+ 504,
+ 505
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+const int http_status_code_count = sizeof(http_status_codes) /
+ sizeof(http_status_codes[0]);
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/http_message_constants.h b/chromium/net/tools/flip_server/http_message_constants.h
new file mode 100644
index 00000000000..de700ccedd8
--- /dev/null
+++ b/chromium/net/tools/flip_server/http_message_constants.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_HTTP_MESSAGE_CONSTANTS_H__
+#define NET_TOOLS_FLIP_SERVER_HTTP_MESSAGE_CONSTANTS_H__
+
+namespace net {
+
+const char* get_http_status_message(int status_message);
+extern const int http_status_codes[];
+extern const int http_status_code_count;
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_HTTP_MESSAGE_CONSTANTS_H__
+
diff --git a/chromium/net/tools/flip_server/loadtime_measurement.h b/chromium/net/tools/flip_server/loadtime_measurement.h
new file mode 100644
index 00000000000..b46217e4fe1
--- /dev/null
+++ b/chromium/net/tools/flip_server/loadtime_measurement.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H__
+#define NET_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H__
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+// Class to handle loadtime measure related urls, which all start with testing
+// The in memory server has a singleton object of this class. It includes a
+// html file containing javascript to go through a list of urls and upload the
+// loadtime. The users can modify urls.txt to define the urls they want to
+// measure and start with downloading the html file from browser.
+class LoadtimeMeasurement {
+ public:
+ LoadtimeMeasurement(const std::string& urls_file,
+ const std::string& pageload_html_file)
+ : num_urls_(0), pageload_html_file_(pageload_html_file) {
+ std::string urls_string;
+ read_file_to_string(urls_file.c_str(), &urls_string);
+ split_string(urls_string, '\n', &urls_);
+ num_urls_ = urls_.size();
+ }
+
+ // This is the entry function for all the loadtime measure related urls
+ // It handles the request to html file, get_total_iteration to get number
+ // of urls in the urls file, get each url, report the loadtime for
+ // each url, and the test is completed.
+ void ProcessRequest(const std::string& uri, std::string& output) {
+ // remove "/testing/" from uri to get the action
+ std::string action = uri.substr(9);
+ if (pageload_html_file_.find(action) != std::string::npos) {
+ read_file_to_string(pageload_html_file_.c_str(), &output);
+ return;
+ }
+ if (action.find("get_total_iteration") == 0) {
+ char buffer[16];
+ snprintf(buffer, 16, "%d", num_urls_);
+ output.append(buffer, strlen(buffer));
+ return;
+ }
+ if (action.find("geturl") == 0) {
+ size_t b = action.find_first_of('=');
+ if (b != std::string::npos) {
+ int num = atoi(action.substr(b + 1).c_str());
+ if (num < num_urls_) {
+ output.append(urls_[num]);
+ }
+ }
+ return;
+ }
+ if (action.find("test_complete") == 0) {
+ for (std::map<std::string, int>::const_iterator it = loadtimes_.begin();
+ it != loadtimes_.end(); ++it) {
+ LOG(INFO) << it->first << " " << it->second;
+ }
+ loadtimes_.clear();
+ output.append("OK");
+ return;
+ }
+ if (action.find("record_page_load") == 0) {
+ std::vector<std::string> query;
+ split_string(action, '?', &query);
+ std::vector<std::string> params;
+ split_string(query[1], '&', &params);
+ std::vector<std::string> url;
+ std::vector<std::string> loadtime;
+ split_string(params[1], '=', &url);
+ split_string(params[2], '=', &loadtime);
+ loadtimes_[url[1]] = atoi(loadtime[1].c_str());
+ output.append("OK");
+ return;
+ }
+ }
+
+ private:
+ void read_file_to_string(const char* filename, std::string* output) {
+ output->clear();
+ int fd = open(filename, 0, "r");
+ if (fd == -1)
+ return;
+ char buffer[4096];
+ ssize_t read_status = read(fd, buffer, sizeof(buffer));
+ while (read_status > 0) {
+ output->append(buffer, static_cast<size_t>(read_status));
+ do {
+ read_status = read(fd, buffer, sizeof(buffer));
+ } while (read_status <= 0 && errno == EINTR);
+ }
+ close(fd);
+ }
+
+ void split_string(std::string& str, char sepa,
+ std::vector<std::string>* sub_strs) {
+ size_t b = 0;
+ size_t e = str.find_first_of(sepa, b);
+ while (e != std::string::npos && e > b) {
+ sub_strs->push_back(str.substr(b, e - b));
+ b = e + 1;
+ e = str.find_first_of(sepa, b);
+ }
+ if (b < str.size()) {
+ sub_strs->push_back(str.substr(b));
+ }
+ }
+
+ int num_urls_;
+ std::vector<std::string> urls_;
+ std::map<std::string, int> loadtimes_;
+ const std::string pageload_html_file_;
+};
+
+#endif // NET_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H__
+
diff --git a/chromium/net/tools/flip_server/mem_cache.cc b/chromium/net/tools/flip_server/mem_cache.cc
new file mode 100644
index 00000000000..924920825c3
--- /dev/null
+++ b/chromium/net/tools/flip_server/mem_cache.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2009 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/tools/flip_server/mem_cache.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+#include "net/tools/dump_cache/url_utilities.h"
+#include "net/tools/flip_server/balsa_frame.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace {
+// The directory where cache locates);
+const char FLAGS_cache_base_dir[] = ".";
+} // namespace
+
+namespace net {
+
+void StoreBodyAndHeadersVisitor::ProcessBodyData(const char *input,
+ size_t size) {
+ body.append(input, size);
+}
+
+void StoreBodyAndHeadersVisitor::HandleHeaderError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleHeaderWarning(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleChunkingError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleBodyError(BalsaFrame* framer) {
+ HandleError();
+}
+
+FileData::FileData(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body)
+ : filename_(filename)
+ , body_(body) {
+ if (headers) {
+ headers_.reset(new BalsaHeaders);
+ headers_->CopyFrom(*headers);
+ }
+}
+
+FileData::FileData() {}
+
+FileData::~FileData() {}
+
+MemoryCache::MemoryCache() : cwd_(FLAGS_cache_base_dir) {}
+
+MemoryCache::~MemoryCache() {
+ ClearFiles();
+}
+
+void MemoryCache::CloneFrom(const MemoryCache& mc) {
+ DCHECK_NE(this, &mc);
+ ClearFiles();
+ files_ = mc.files_;
+ cwd_ = mc.cwd_;
+}
+
+void MemoryCache::AddFiles() {
+ std::deque<std::string> paths;
+ paths.push_back(cwd_ + "/GET_");
+ DIR* current_dir = NULL;
+ while (!paths.empty()) {
+ while (current_dir == NULL && !paths.empty()) {
+ std::string current_dir_name = paths.front();
+ VLOG(1) << "Attempting to open dir: \"" << current_dir_name << "\"";
+ current_dir = opendir(current_dir_name.c_str());
+ paths.pop_front();
+
+ if (current_dir == NULL) {
+ perror("Unable to open directory. ");
+ current_dir_name.clear();
+ continue;
+ }
+
+ if (current_dir) {
+ VLOG(1) << "Succeeded opening";
+ for (struct dirent* dir_data = readdir(current_dir);
+ dir_data != NULL;
+ dir_data = readdir(current_dir)) {
+ std::string current_entry_name =
+ current_dir_name + "/" + dir_data->d_name;
+ if (dir_data->d_type == DT_REG) {
+ VLOG(1) << "Found file: " << current_entry_name;
+ ReadAndStoreFileContents(current_entry_name.c_str());
+ } else if (dir_data->d_type == DT_DIR) {
+ VLOG(1) << "Found subdir: " << current_entry_name;
+ if (std::string(dir_data->d_name) != "." &&
+ std::string(dir_data->d_name) != "..") {
+ VLOG(1) << "Adding to search path: " << current_entry_name;
+ paths.push_front(current_entry_name);
+ }
+ }
+ }
+ VLOG(1) << "Oops, no data left. Closing dir.";
+ closedir(current_dir);
+ current_dir = NULL;
+ }
+ }
+ }
+}
+
+void MemoryCache::ReadToString(const char* filename, std::string* output) {
+ output->clear();
+ int fd = open(filename, 0, "r");
+ if (fd == -1)
+ return;
+ char buffer[4096];
+ ssize_t read_status = read(fd, buffer, sizeof(buffer));
+ while (read_status > 0) {
+ output->append(buffer, static_cast<size_t>(read_status));
+ do {
+ read_status = read(fd, buffer, sizeof(buffer));
+ } while (read_status <= 0 && errno == EINTR);
+ }
+ close(fd);
+}
+
+void MemoryCache::ReadAndStoreFileContents(const char* filename) {
+ StoreBodyAndHeadersVisitor visitor;
+ BalsaFrame framer;
+ framer.set_balsa_visitor(&visitor);
+ framer.set_balsa_headers(&(visitor.headers));
+ std::string filename_contents;
+ ReadToString(filename, &filename_contents);
+
+ // Ugly hack to make everything look like 1.1.
+ if (filename_contents.find("HTTP/1.0") == 0)
+ filename_contents[7] = '1';
+
+ size_t pos = 0;
+ size_t old_pos = 0;
+ while (true) {
+ old_pos = pos;
+ pos += framer.ProcessInput(filename_contents.data() + pos,
+ filename_contents.size() - pos);
+ if (framer.Error() || pos == old_pos) {
+ LOG(ERROR) << "Unable to make forward progress, or error"
+ " framing file: " << filename;
+ if (framer.Error()) {
+ LOG(INFO) << "********************************************ERROR!";
+ return;
+ }
+ return;
+ }
+ if (framer.MessageFullyRead()) {
+ // If no Content-Length or Transfer-Encoding was captured in the
+ // file, then the rest of the data is the body. Many of the captures
+ // from within Chrome don't have content-lengths.
+ if (!visitor.body.length())
+ visitor.body = filename_contents.substr(pos);
+ break;
+ }
+ }
+ visitor.headers.RemoveAllOfHeader("content-length");
+ visitor.headers.RemoveAllOfHeader("transfer-encoding");
+ visitor.headers.RemoveAllOfHeader("connection");
+ visitor.headers.AppendHeader("transfer-encoding", "chunked");
+ visitor.headers.AppendHeader("connection", "keep-alive");
+
+ // Experiment with changing headers for forcing use of cached
+ // versions of content.
+ // TODO(mbelshe) REMOVE ME
+#if 0
+ // TODO(mbelshe) append current date.
+ visitor.headers.RemoveAllOfHeader("date");
+ if (visitor.headers.HasHeader("expires")) {
+ visitor.headers.RemoveAllOfHeader("expires");
+ visitor.headers.AppendHeader("expires",
+ "Fri, 30 Aug, 2019 12:00:00 GMT");
+ }
+#endif
+ DCHECK_GE(std::string(filename).size(), cwd_.size() + 1);
+ DCHECK_EQ(std::string(filename).substr(0, cwd_.size()), cwd_);
+ DCHECK_EQ(filename[cwd_.size()], '/');
+ std::string filename_stripped = std::string(filename).substr(cwd_.size() + 1);
+ LOG(INFO) << "Adding file (" << visitor.body.length() << " bytes): "
+ << filename_stripped;
+ size_t slash_pos = filename_stripped.find('/');
+ if (slash_pos == std::string::npos) {
+ slash_pos = filename_stripped.size();
+ }
+ FileData* data =
+ new FileData(&visitor.headers,
+ filename_stripped.substr(0, slash_pos),
+ visitor.body);
+ Files::iterator it = files_.find(filename_stripped);
+ if (it != files_.end()) {
+ delete it->second;
+ it->second = data;
+ } else {
+ files_.insert(std::make_pair(filename_stripped, data));
+ }
+}
+
+FileData* MemoryCache::GetFileData(const std::string& filename) {
+ Files::iterator fi = files_.end();
+ if (EndsWith(filename, ".html", true)) {
+ fi = files_.find(filename.substr(0, filename.size() - 5) + ".http");
+ }
+ if (fi == files_.end())
+ fi = files_.find(filename);
+
+ if (fi == files_.end()) {
+ return NULL;
+ }
+ return fi->second;
+}
+
+bool MemoryCache::AssignFileData(const std::string& filename,
+ MemCacheIter* mci) {
+ mci->file_data = GetFileData(filename);
+ if (mci->file_data == NULL) {
+ LOG(ERROR) << "Could not find file data for " << filename;
+ return false;
+ }
+ return true;
+}
+
+void MemoryCache::ClearFiles() {
+ for (Files::const_iterator i = files_.begin(); i != files_.end(); ++i) {
+ delete i->second;
+ }
+ files_.clear();
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/mem_cache.h b/chromium/net/tools/flip_server/mem_cache.h
new file mode 100644
index 00000000000..806ae53689b
--- /dev/null
+++ b/chromium/net/tools/flip_server/mem_cache.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_MEM_CACHE_H_
+#define NET_TOOLS_FLIP_SERVER_MEM_CACHE_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/constants.h"
+
+namespace net {
+
+class StoreBodyAndHeadersVisitor: public BalsaVisitorInterface {
+ public:
+ void HandleError() { error_ = true; }
+
+ // BalsaVisitorInterface:
+ virtual void ProcessBodyInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessBodyData(const char *input, size_t size) OVERRIDE;
+ virtual void ProcessHeaderInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE {
+ // nothing to do here-- we're assuming that the BalsaFrame has
+ // been handed our headers.
+ }
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char *line_input,
+ size_t line_length,
+ const char *version_input,
+ size_t version_length,
+ const char *status_input,
+ size_t status_length,
+ const char *reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char *input,
+ size_t size) OVERRIDE {}
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void MessageDone() OVERRIDE {}
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+
+ BalsaHeaders headers;
+ std::string body;
+ bool error_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+class FileData {
+ public:
+ FileData();
+ FileData(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body);
+ ~FileData();
+
+ BalsaHeaders* headers() { return headers_.get(); }
+ const BalsaHeaders* headers() const { return headers_.get(); }
+
+ const std::string& filename() { return filename_; }
+ const std::string& body() { return body_; }
+
+ private:
+ scoped_ptr<BalsaHeaders> headers_;
+ std::string filename_;
+ std::string body_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileData);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MemCacheIter {
+ public:
+ MemCacheIter() :
+ file_data(NULL),
+ priority(0),
+ transformed_header(false),
+ body_bytes_consumed(0),
+ stream_id(0),
+ max_segment_size(kInitialDataSendersThreshold),
+ bytes_sent(0) {}
+ explicit MemCacheIter(FileData* fd) :
+ file_data(fd),
+ priority(0),
+ transformed_header(false),
+ body_bytes_consumed(0),
+ stream_id(0),
+ max_segment_size(kInitialDataSendersThreshold),
+ bytes_sent(0) {}
+ FileData* file_data;
+ int priority;
+ bool transformed_header;
+ size_t body_bytes_consumed;
+ uint32 stream_id;
+ uint32 max_segment_size;
+ size_t bytes_sent;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class MemoryCache {
+ public:
+ typedef std::map<std::string, FileData*> Files;
+
+ public:
+ MemoryCache();
+ virtual ~MemoryCache();
+
+ void CloneFrom(const MemoryCache& mc);
+
+ void AddFiles();
+
+ // virtual for unittests
+ virtual void ReadToString(const char* filename, std::string* output);
+
+ void ReadAndStoreFileContents(const char* filename);
+
+ FileData* GetFileData(const std::string& filename);
+
+ bool AssignFileData(const std::string& filename, MemCacheIter* mci);
+
+ private:
+ void ClearFiles();
+
+ Files files_;
+ std::string cwd_;
+};
+
+class NotifierInterface {
+ public:
+ virtual ~NotifierInterface() {}
+ virtual void Notify() = 0;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_MEM_CACHE_H_
diff --git a/chromium/net/tools/flip_server/mem_cache_test.cc b/chromium/net/tools/flip_server/mem_cache_test.cc
new file mode 100644
index 00000000000..d5601ac14f5
--- /dev/null
+++ b/chromium/net/tools/flip_server/mem_cache_test.cc
@@ -0,0 +1,98 @@
+// Copyright 2013 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/tools/flip_server/mem_cache.h"
+
+#include "net/tools/flip_server/balsa_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MemoryCacheWithFakeReadToString : public MemoryCache {
+ public:
+ virtual ~MemoryCacheWithFakeReadToString() {}
+
+ virtual void ReadToString(const char* filename, std::string* output)
+ OVERRIDE {
+ *output = data_map_[filename];
+ }
+
+ std::map<std::string, std::string> data_map_;
+};
+
+class FlipMemoryCacheTest : public ::testing::Test {
+ public:
+ FlipMemoryCacheTest(): mem_cache_(new MemoryCacheWithFakeReadToString) {}
+
+ protected:
+ scoped_ptr<MemoryCacheWithFakeReadToString> mem_cache_;
+};
+
+TEST_F(FlipMemoryCacheTest, EmptyCache) {
+ MemCacheIter mci;
+ mci.stream_id = 0;
+ ASSERT_EQ(NULL, mem_cache_->GetFileData("./foo"));
+ ASSERT_EQ(NULL, mem_cache_->GetFileData("./bar"));
+ ASSERT_FALSE(mem_cache_->AssignFileData("./hello", &mci));
+}
+
+TEST_F(FlipMemoryCacheTest, ReadAndStoreFileContents) {
+ FileData* foo;
+ FileData* hello;
+
+ mem_cache_->data_map_["./foo"] = "bar";
+ mem_cache_->data_map_["./hello"] = "HTTP/1.0 200 OK\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n\r\n"
+ "body: body\r\n";
+ mem_cache_->ReadAndStoreFileContents("./foo");
+ mem_cache_->ReadAndStoreFileContents("./hello");
+
+ foo = mem_cache_->GetFileData("foo");
+ hello = mem_cache_->GetFileData("hello");
+
+ // "./foo" content is broken.
+ ASSERT_EQ(NULL, foo);
+ ASSERT_FALSE(NULL == hello);
+ ASSERT_EQ(hello, mem_cache_->GetFileData("hello"));
+
+ // "HTTP/1.0" is rewritten to "HTTP/1.1".
+ ASSERT_EQ("HTTP/1.1", hello->headers()->response_version());
+ ASSERT_EQ("200", hello->headers()->response_code());
+ ASSERT_EQ("OK", hello->headers()->response_reason_phrase());
+ ASSERT_EQ(4, std::distance(hello->headers()->header_lines_begin(),
+ hello->headers()->header_lines_end()));
+ ASSERT_TRUE(hello->headers()->HasHeader("key1"));
+ ASSERT_TRUE(hello->headers()->HasHeader("key2"));
+ ASSERT_TRUE(hello->headers()->HasHeader("transfer-encoding"));
+ ASSERT_TRUE(hello->headers()->HasHeader("connection"));
+ ASSERT_EQ("value1", hello->headers()->GetHeaderPosition("key1")->second);
+ ASSERT_EQ("value2", hello->headers()->GetHeaderPosition("key2")->second);
+ ASSERT_EQ("chunked",
+ hello->headers()->GetHeaderPosition("transfer-encoding")->second);
+ ASSERT_EQ("keep-alive",
+ hello->headers()->GetHeaderPosition("connection")->second);
+ ASSERT_EQ("body: body\r\n", hello->body());
+ ASSERT_EQ("hello", hello->filename());
+}
+
+TEST_F(FlipMemoryCacheTest, GetFileDataForHtmlFile) {
+ FileData* hello_html;
+
+ mem_cache_->data_map_["./hello.http"] = "HTTP/1.0 200 OK\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n\r\n"
+ "body: body\r\n";
+
+ mem_cache_->ReadAndStoreFileContents("./hello.http");
+ hello_html = mem_cache_->GetFileData("hello.html");
+ ASSERT_FALSE(NULL == hello_html);
+ ASSERT_EQ(hello_html, mem_cache_->GetFileData("hello.http"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/output_ordering.cc b/chromium/net/tools/flip_server/output_ordering.cc
new file mode 100644
index 00000000000..22fd08ac1c1
--- /dev/null
+++ b/chromium/net/tools/flip_server/output_ordering.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2009 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/tools/flip_server/output_ordering.h"
+
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+
+
+namespace net {
+
+OutputOrdering::PriorityMapPointer::PriorityMapPointer()
+ : ring(NULL),
+ alarm_enabled(false) {
+}
+
+// static
+double OutputOrdering::server_think_time_in_s_ = 0.0;
+
+OutputOrdering::OutputOrdering(SMConnectionInterface* connection)
+ : first_data_senders_threshold_(kInitialDataSendersThreshold),
+ connection_(connection) {
+ if (connection)
+ epoll_server_ = connection->epoll_server();
+}
+
+OutputOrdering::~OutputOrdering() {}
+
+void OutputOrdering::Reset() {
+ while (!stream_ids_.empty()) {
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.begin();
+ PriorityMapPointer& pmp = sitpmi->second;
+ if (pmp.alarm_enabled) {
+ epoll_server_->UnregisterAlarm(pmp.alarm_token);
+ }
+ stream_ids_.erase(sitpmi);
+ }
+ priority_map_.clear();
+ first_data_senders_.clear();
+}
+
+bool OutputOrdering::ExistsInPriorityMaps(uint32 stream_id) {
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
+ return sitpmi != stream_ids_.end();
+}
+
+OutputOrdering::BeginOutputtingAlarm::BeginOutputtingAlarm(
+ OutputOrdering* oo,
+ OutputOrdering::PriorityMapPointer* pmp,
+ const MemCacheIter& mci)
+ : output_ordering_(oo),
+ pmp_(pmp),
+ mci_(mci),
+ epoll_server_(NULL) {
+}
+
+OutputOrdering::BeginOutputtingAlarm::~BeginOutputtingAlarm() {
+ if (epoll_server_ && pmp_->alarm_enabled)
+ epoll_server_->UnregisterAlarm(pmp_->alarm_token);
+}
+
+int64 OutputOrdering::BeginOutputtingAlarm::OnAlarm() {
+ OnUnregistration();
+ output_ordering_->MoveToActive(pmp_, mci_);
+ VLOG(2) << "ON ALARM! Should now start to output...";
+ delete this;
+ return 0;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnRegistration(
+ const EpollServer::AlarmRegToken& tok,
+ EpollServer* eps) {
+ epoll_server_ = eps;
+ pmp_->alarm_token = tok;
+ pmp_->alarm_enabled = true;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnUnregistration() {
+ pmp_->alarm_enabled = false;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnShutdown(EpollServer* eps) {
+ OnUnregistration();
+}
+
+void OutputOrdering::MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci) {
+ VLOG(2) << "Moving to active!";
+ first_data_senders_.push_back(mci);
+ pmp->ring = &first_data_senders_;
+ pmp->it = first_data_senders_.end();
+ --pmp->it;
+ connection_->ReadyToSend();
+}
+
+void OutputOrdering::AddToOutputOrder(const MemCacheIter& mci) {
+ if (ExistsInPriorityMaps(mci.stream_id))
+ LOG(ERROR) << "OOps, already was inserted here?!";
+
+ double think_time_in_s = server_think_time_in_s_;
+ std::string x_server_latency =
+ mci.file_data->headers()->GetHeader("X-Server-Latency").as_string();
+ if (!x_server_latency.empty()) {
+ char* endp;
+ double tmp_think_time_in_s = strtod(x_server_latency.c_str(), &endp);
+ if (endp != x_server_latency.c_str() + x_server_latency.size()) {
+ LOG(ERROR) << "Unable to understand X-Server-Latency of: "
+ << x_server_latency << " for resource: "
+ << mci.file_data->filename().c_str();
+ } else {
+ think_time_in_s = tmp_think_time_in_s;
+ }
+ }
+ StreamIdToPriorityMap::iterator sitpmi;
+ sitpmi = stream_ids_.insert(
+ std::pair<uint32, PriorityMapPointer>(mci.stream_id,
+ PriorityMapPointer())).first;
+ PriorityMapPointer& pmp = sitpmi->second;
+
+ BeginOutputtingAlarm* boa = new BeginOutputtingAlarm(this, &pmp, mci);
+ VLOG(1) << "Server think time: " << think_time_in_s;
+ epoll_server_->RegisterAlarmApproximateDelta(
+ think_time_in_s * 1000000, boa);
+}
+
+void OutputOrdering::SpliceToPriorityRing(PriorityRing::iterator pri) {
+ MemCacheIter& mci = *pri;
+ PriorityMap::iterator pmi = priority_map_.find(mci.priority);
+ if (pmi == priority_map_.end()) {
+ pmi = priority_map_.insert(
+ std::pair<uint32, PriorityRing>(mci.priority, PriorityRing())).first;
+ }
+
+ pmi->second.splice(pmi->second.end(),
+ first_data_senders_,
+ pri);
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(mci.stream_id);
+ sitpmi->second.ring = &(pmi->second);
+}
+
+MemCacheIter* OutputOrdering::GetIter() {
+ while (!first_data_senders_.empty()) {
+ MemCacheIter& mci = first_data_senders_.front();
+ if (mci.bytes_sent >= first_data_senders_threshold_) {
+ SpliceToPriorityRing(first_data_senders_.begin());
+ } else {
+ first_data_senders_.splice(first_data_senders_.end(),
+ first_data_senders_,
+ first_data_senders_.begin());
+ mci.max_segment_size = kInitialDataSendersThreshold;
+ return &mci;
+ }
+ }
+ while (!priority_map_.empty()) {
+ PriorityRing& first_ring = priority_map_.begin()->second;
+ if (first_ring.empty()) {
+ priority_map_.erase(priority_map_.begin());
+ continue;
+ }
+ MemCacheIter& mci = first_ring.front();
+ first_ring.splice(first_ring.end(),
+ first_ring,
+ first_ring.begin());
+ mci.max_segment_size = kSpdySegmentSize;
+ return &mci;
+ }
+ return NULL;
+}
+
+void OutputOrdering::RemoveStreamId(uint32 stream_id) {
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
+ if (sitpmi == stream_ids_.end())
+ return;
+
+ PriorityMapPointer& pmp = sitpmi->second;
+ if (pmp.alarm_enabled)
+ epoll_server_->UnregisterAlarm(pmp.alarm_token);
+ else
+ pmp.ring->erase(pmp.it);
+ stream_ids_.erase(sitpmi);
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/output_ordering.h b/chromium/net/tools/flip_server/output_ordering.h
new file mode 100644
index 00000000000..0558e3e4e8d
--- /dev/null
+++ b/chromium/net/tools/flip_server/output_ordering.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
+#define NET_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/flip_server/mem_cache.h"
+
+namespace net {
+
+class SMConnectionInterface;
+
+class OutputOrdering {
+ public:
+ typedef std::list<MemCacheIter> PriorityRing;
+ typedef std::map<uint32, PriorityRing> PriorityMap;
+
+ struct PriorityMapPointer {
+ PriorityMapPointer();
+ PriorityRing* ring;
+ PriorityRing::iterator it;
+ bool alarm_enabled;
+ EpollServer::AlarmRegToken alarm_token;
+ };
+
+ typedef std::map<uint32, PriorityMapPointer> StreamIdToPriorityMap;
+
+ StreamIdToPriorityMap stream_ids_;
+ PriorityMap priority_map_;
+ PriorityRing first_data_senders_;
+ uint32 first_data_senders_threshold_; // when you've passed this, you're no
+ // longer a first_data_sender...
+ SMConnectionInterface* connection_;
+ EpollServer* epoll_server_;
+
+ explicit OutputOrdering(SMConnectionInterface* connection);
+ ~OutputOrdering();
+ void Reset();
+ bool ExistsInPriorityMaps(uint32 stream_id);
+
+ struct BeginOutputtingAlarm : public EpollAlarmCallbackInterface {
+ public:
+ BeginOutputtingAlarm(OutputOrdering* oo,
+ OutputOrdering::PriorityMapPointer* pmp,
+ const MemCacheIter& mci);
+ virtual ~BeginOutputtingAlarm();
+
+ // EpollAlarmCallbackInterface:
+ virtual int64 OnAlarm() OVERRIDE;
+ virtual void OnRegistration(const EpollServer::AlarmRegToken& tok,
+ EpollServer* eps) OVERRIDE;
+ virtual void OnUnregistration() OVERRIDE;
+ virtual void OnShutdown(EpollServer* eps) OVERRIDE;
+
+ private:
+ OutputOrdering* output_ordering_;
+ OutputOrdering::PriorityMapPointer* pmp_;
+ MemCacheIter mci_;
+ EpollServer* epoll_server_;
+ };
+
+ void MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci);
+ void AddToOutputOrder(const MemCacheIter& mci);
+ void SpliceToPriorityRing(PriorityRing::iterator pri);
+ MemCacheIter* GetIter();
+ void RemoveStreamId(uint32 stream_id);
+
+ static double server_think_time_in_s() { return server_think_time_in_s_; }
+ static void set_server_think_time_in_s(double value) {
+ server_think_time_in_s_ = value;
+ }
+
+ private:
+ static double server_think_time_in_s_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
diff --git a/chromium/net/tools/flip_server/ring_buffer.cc b/chromium/net/tools/flip_server/ring_buffer.cc
new file mode 100644
index 00000000000..81e9e9ee481
--- /dev/null
+++ b/chromium/net/tools/flip_server/ring_buffer.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 2009 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/tools/flip_server/ring_buffer.h"
+#include "base/logging.h"
+
+namespace net {
+
+RingBuffer::RingBuffer(int buffer_size)
+ : buffer_(new char[buffer_size]),
+ buffer_size_(buffer_size),
+ bytes_used_(0),
+ read_idx_(0),
+ write_idx_(0) {
+}
+
+RingBuffer::~RingBuffer() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int RingBuffer::ReadableBytes() const {
+ return bytes_used_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int RingBuffer::BufferSize() const {
+ return buffer_size_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int RingBuffer::BytesFree() const {
+ return BufferSize() - ReadableBytes();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool RingBuffer::Empty() const {
+ return ReadableBytes() == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool RingBuffer::Full() const {
+ return ReadableBytes() == BufferSize();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Returns the number of characters written.
+// Appends up-to-'size' bytes to the ringbuffer.
+int RingBuffer::Write(const char* bytes, int size) {
+ CHECK_GE(size, 0);
+#if 1
+ char* wptr;
+ int wsize;
+ GetWritablePtr(&wptr, &wsize);
+ int bytes_remaining = size;
+ int bytes_written = 0;
+
+ while (wsize && bytes_remaining) {
+ if (wsize > bytes_remaining) {
+ wsize = bytes_remaining;
+ }
+ memcpy(wptr, bytes + bytes_written, wsize);
+ bytes_written += wsize;
+ bytes_remaining -= wsize;
+ AdvanceWritablePtr(wsize);
+ GetWritablePtr(&wptr, &wsize);
+ }
+ return bytes_written;
+#else
+ const char* p = bytes;
+
+ int bytes_to_write = size;
+ int bytes_available = BytesFree();
+ if (bytes_available < bytes_to_write) {
+ bytes_to_write = bytes_available;
+ }
+ const char* end = bytes + bytes_to_write;
+
+ while (p != end) {
+ this->buffer_[this->write_idx_] = *p;
+ ++p;
+ ++this->write_idx_;
+ if (this->write_idx_ >= this->buffer_size_) {
+ this->write_idx_ = 0;
+ }
+ }
+ bytes_used_ += bytes_to_write;
+ return bytes_to_write;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Sets *ptr to the beginning of writable memory, and sets *size to the size
+// available for writing using this pointer.
+void RingBuffer::GetWritablePtr(char** ptr, int* size) const {
+ *ptr = buffer_.get() + write_idx_;
+
+ if (bytes_used_ == buffer_size_) {
+ *size = 0;
+ } else if (read_idx_ > write_idx_) {
+ *size = read_idx_ - write_idx_;
+ } else {
+ *size = buffer_size_ - write_idx_;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Sets *ptr to the beginning of readable memory, and sets *size to the size
+// available for reading using this pointer.
+void RingBuffer::GetReadablePtr(char** ptr, int* size) const {
+ *ptr = buffer_.get() + read_idx_;
+
+ if (bytes_used_ == 0) {
+ *size = 0;
+ } else if (write_idx_ > read_idx_) {
+ *size = write_idx_ - read_idx_;
+ } else {
+ *size = buffer_size_ - read_idx_;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// returns the number of bytes read into
+int RingBuffer::Read(char* bytes, int size) {
+ CHECK_GE(size, 0);
+#if 1
+ char* rptr;
+ int rsize;
+ GetReadablePtr(&rptr, &rsize);
+ int bytes_remaining = size;
+ int bytes_read = 0;
+
+ while (rsize && bytes_remaining) {
+ if (rsize > bytes_remaining) {
+ rsize = bytes_remaining;
+ }
+ memcpy(bytes + bytes_read, rptr, rsize);
+ bytes_read += rsize;
+ bytes_remaining -= rsize;
+ AdvanceReadablePtr(rsize);
+ GetReadablePtr(&rptr, &rsize);
+ }
+ return bytes_read;
+#else
+ char* p = bytes;
+ int bytes_to_read = size;
+ int bytes_used = ReadableBytes();
+ if (bytes_used < bytes_to_read) {
+ bytes_to_read = bytes_used;
+ }
+ char* end = bytes + bytes_to_read;
+
+ while (p != end) {
+ *p = this->buffer_[this->read_idx_];
+ ++p;
+ ++this->read_idx_;
+ if (this->read_idx_ >= this->buffer_size_) {
+ this->read_idx_ = 0;
+ }
+ }
+ this->bytes_used_ -= bytes_to_read;
+ return bytes_to_read;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void RingBuffer::Clear() {
+ bytes_used_ = 0;
+ write_idx_ = 0;
+ read_idx_ = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool RingBuffer::Reserve(int size) {
+ DCHECK(size > 0);
+ char* write_ptr = NULL;
+ int write_size = 0;
+ GetWritablePtr(&write_ptr, &write_size);
+
+ if (write_size < size) {
+ char* read_ptr = NULL;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+ if (size <= BytesFree()) {
+ // The fact that the total Free size is big enough but writable size is
+ // not means that the writeable region is broken into two pieces: only
+ // possible if the read_idx < write_idx. If write_idx < read_idx, then
+ // the writeable region must be contiguous: [write_idx, read_idx). There
+ // is no work to be done for the latter.
+ DCHECK(read_idx_ <= write_idx_);
+ DCHECK(read_size == ReadableBytes());
+ if (read_idx_ < write_idx_) {
+ // Writeable area fragmented, consolidate it.
+ memmove(buffer_.get(), read_ptr, read_size);
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ } else if (read_idx_ == write_idx_) {
+ // No unconsumed data in the buffer, simply reset the indexes.
+ DCHECK(ReadableBytes() == 0);
+ read_idx_ = 0;
+ write_idx_ = 0;
+ }
+ } else {
+ Resize(ReadableBytes() + size);
+ }
+ }
+ DCHECK_LE(size, buffer_size_ - write_idx_);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void RingBuffer::AdvanceReadablePtr(int amount_to_consume) {
+ CHECK_GE(amount_to_consume, 0);
+ if (amount_to_consume >= bytes_used_) {
+ Clear();
+ return;
+ }
+ read_idx_ += amount_to_consume;
+ read_idx_ %= buffer_size_;
+ bytes_used_ -= amount_to_consume;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void RingBuffer::AdvanceWritablePtr(int amount_to_produce) {
+ CHECK_GE(amount_to_produce, 0);
+ CHECK_LE(amount_to_produce, BytesFree());
+ write_idx_ += amount_to_produce;
+ write_idx_ %= buffer_size_;
+ bytes_used_ += amount_to_produce;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void RingBuffer::Resize(int buffer_size) {
+ CHECK_GE(buffer_size, 0);
+ if (buffer_size == buffer_size_) return;
+
+ char* new_buffer = new char[buffer_size];
+ if (buffer_size < bytes_used_) {
+ // consume the oldest data.
+ AdvanceReadablePtr(bytes_used_ - buffer_size);
+ }
+
+ int bytes_written = 0;
+ int bytes_used = bytes_used_;
+ while (true) {
+ int size;
+ char* ptr;
+ GetReadablePtr(&ptr, &size);
+ if (size == 0) break;
+ if (size > buffer_size) {
+ size = buffer_size;
+ }
+ memcpy(new_buffer + bytes_written, ptr, size);
+ bytes_written += size;
+ AdvanceReadablePtr(size);
+ }
+ buffer_.reset(new_buffer);
+
+ buffer_size_ = buffer_size;
+ bytes_used_ = bytes_used;
+ read_idx_ = 0;
+ write_idx_ = bytes_used_ % buffer_size_;
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/ring_buffer.h b/chromium/net/tools/flip_server/ring_buffer.h
new file mode 100644
index 00000000000..03cbde7c8dd
--- /dev/null
+++ b/chromium/net/tools/flip_server/ring_buffer.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_RING_BUFFER_H__
+#define NET_TOOLS_FLIP_SERVER_RING_BUFFER_H__
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/tools/flip_server/buffer_interface.h"
+
+namespace net {
+
+// The ring buffer is a circular buffer, that is, reads or writes may wrap
+// around the end of the linear memory contained by the class (and back to
+// the beginning). This is a good choice when you want to use a fixed amount
+// of buffering and don't want to be moving memory around a lot.
+//
+// What is the penalty for using this over a normal, linear buffer?
+// Reading all the data may take two operations, and
+// writing all the data may take two operations.
+//
+// In the proxy, this class is used as a fixed size buffer between
+// clients and servers (so that the memory size is constrained).
+
+class RingBuffer : public BufferInterface {
+ public:
+ explicit RingBuffer(int buffer_size);
+ virtual ~RingBuffer();
+
+ // Resize the buffer to the size specified here. If the buffer_size passed
+ // in here is smaller than the amount of data in the buffer, then the oldest
+ // data will be dropped, but all other data will be saved.
+ // This means: If the buffer size is increasing, all data that was resident
+ // in the buffer prior to this call will be resident after this call.
+ void Resize(int buffer_size);
+
+ // The following functions all override pure virtual functions
+ // in BufferInterface. See buffer_interface.h for a description
+ // of what they do if the function isn't documented here.
+ virtual int ReadableBytes() const OVERRIDE;
+ virtual int BufferSize() const OVERRIDE;
+ virtual int BytesFree() const OVERRIDE;
+
+ virtual bool Empty() const OVERRIDE;
+ virtual bool Full() const OVERRIDE;
+
+ // returns the number of characters written.
+ // appends up-to-'size' bytes to the ringbuffer.
+ virtual int Write(const char * bytes, int size) OVERRIDE;
+
+ // Stores a pointer into the ring buffer in *ptr, and stores the number of
+ // characters which are allowed to be written in *size.
+ // If there are no writable bytes available, then *size will contain 0.
+ virtual void GetWritablePtr(char** ptr, int* size) const OVERRIDE;
+
+ // Stores a pointer into the ring buffer in *ptr, and stores the number of
+ // characters which are allowed to be read in *size.
+ // If there are no readable bytes available, then *size will contain 0.
+ virtual void GetReadablePtr(char** ptr, int* size) const OVERRIDE;
+
+ // Returns the number of bytes read into 'bytes'.
+ virtual int Read(char* bytes, int size) OVERRIDE;
+
+ // Removes all data from the ring buffer.
+ virtual void Clear() OVERRIDE;
+
+ // Reserves contiguous writable empty space in the buffer of size bytes.
+ // Since the point of this class is to have a fixed size buffer, be careful
+ // not to inadvertently resize the buffer using Reserve(). If the reserve
+ // size is <= BytesFree(), it is guaranteed that the buffer size will not
+ // change.
+ // This can be an expensive operation, it may new a buffer copy all existing
+ // data and delete the old data. Even if the existing buffer does not need
+ // to be resized, unread data may still need to be non-destructively copied
+ // to consolidate fragmented free space. If the size requested is less than
+ // or equal to BytesFree(), it is guaranteed that the buffer size will not
+ // change.
+ virtual bool Reserve(int size) OVERRIDE;
+
+ // Removes the oldest 'amount_to_advance' characters.
+ // If amount_to_consume > ReadableBytes(), this performs a Clear() instead.
+ virtual void AdvanceReadablePtr(int amount_to_advance) OVERRIDE;
+
+ // Moves the internal pointers around such that the amount of data specified
+ // here is expected to already be resident (as if it was Written).
+ virtual void AdvanceWritablePtr(int amount_to_advance) OVERRIDE;
+
+ protected:
+ int read_idx() const { return read_idx_; }
+ int write_idx() const { return write_idx_; }
+ int bytes_used() const { return bytes_used_; }
+ int buffer_size() const { return buffer_size_; }
+ const char* buffer() const { return buffer_.get(); }
+
+ int set_read_idx(int idx) { return read_idx_ = idx; }
+ int set_write_idx(int idx) { return write_idx_ = idx; }
+
+ private:
+ scoped_ptr<char[]> buffer_;
+ int buffer_size_;
+ int bytes_used_;
+ int read_idx_;
+ int write_idx_;
+
+ RingBuffer(const RingBuffer&);
+ void operator=(const RingBuffer&);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_RING_BUFFER_H__
+
diff --git a/chromium/net/tools/flip_server/simple_buffer.cc b/chromium/net/tools/flip_server/simple_buffer.cc
new file mode 100644
index 00000000000..d94153b29ec
--- /dev/null
+++ b/chromium/net/tools/flip_server/simple_buffer.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2009 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/tools/flip_server/simple_buffer.h"
+#include "base/logging.h"
+
+// Some of the following member functions are marked inlined, even though they
+// are virtual. This may seem counter-intuitive, since virtual functions are
+// generally not eligible for inlining. Profiling results indicate that these
+// large amount of runtime is spent on virtual function dispatch on these
+// simple functions. They are virtual because of the interface this class
+// inherits from. However, it is very unlikely that anyone will sub-class
+// SimpleBuffer and change their implementation. To get rid of this baggage,
+// internal implementation (e.g., Write) explicitly use SimpleBuffer:: to
+// qualify the method calls, thus disabling the virtual dispatch and enable
+// inlining.
+
+namespace net {
+
+static const int kInitialSimpleBufferSize = 10;
+
+SimpleBuffer::SimpleBuffer()
+ : storage_(new char[kInitialSimpleBufferSize]),
+ write_idx_(0),
+ read_idx_(0),
+ storage_size_(kInitialSimpleBufferSize) {
+}
+
+SimpleBuffer::SimpleBuffer(int size)
+ : write_idx_(0),
+ read_idx_(0),
+ storage_size_(size) {
+ // Callers may try to allocate overly large blocks, but negative sizes are
+ // obviously wrong.
+ CHECK_GE(size, 0);
+ storage_ = new char[size];
+}
+
+SimpleBuffer::~SimpleBuffer() {
+ delete[] storage_;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+int SimpleBuffer::ReadableBytes() const {
+ return write_idx_ - read_idx_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::string SimpleBuffer::str() const {
+ std::string s;
+ char * readable_ptr;
+ int readable_size;
+ GetReadablePtr(&readable_ptr, &readable_size);
+ s.append(readable_ptr, readable_ptr + readable_size);
+ return s;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+int SimpleBuffer::BufferSize() const {
+ return storage_size_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline int SimpleBuffer::BytesFree() const {
+ return (storage_size_ - write_idx_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SimpleBuffer::Empty() const {
+ return (read_idx_ == write_idx_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool SimpleBuffer::Full() const {
+ return ((write_idx_ == storage_size_) && (read_idx_ != write_idx_));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// returns the number of characters written.
+// appends up-to-'size' bytes to the simplebuffer.
+int SimpleBuffer::Write(const char* bytes, int size) {
+ bool has_room = ((storage_size_ - write_idx_) >= size);
+ if (!has_room) {
+ (void)Reserve(size);
+ }
+ memcpy(storage_ + write_idx_, bytes, size);
+ SimpleBuffer::AdvanceWritablePtr(size);
+ return size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// stores a pointer into the simple buffer in *ptr,
+// and stores the number of characters which are allowed
+// to be written in *size.
+inline void SimpleBuffer::GetWritablePtr(char **ptr, int* size) const {
+ *ptr = storage_ + write_idx_;
+ *size = SimpleBuffer::BytesFree();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// stores a pointer into the simple buffer in *ptr,
+// and stores the number of characters which are allowed
+// to be read in *size.
+void SimpleBuffer::GetReadablePtr(char **ptr, int* size) const {
+ *ptr = storage_ + read_idx_;
+ *size = write_idx_ - read_idx_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// returns the number of bytes read into 'bytes'
+int SimpleBuffer::Read(char* bytes, int size) {
+ char * read_ptr = NULL;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+ if (read_size > size) {
+ read_size = size;
+ }
+ memcpy(bytes, read_ptr, read_size);
+ AdvanceReadablePtr(read_size);
+ return read_size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// removes all data from the simple buffer
+void SimpleBuffer::Clear() {
+ read_idx_ = write_idx_ = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Attempts to reserve a contiguous block of buffer space by either reclaiming
+// old data that is already read, and reallocate large storage as needed.
+bool SimpleBuffer::Reserve(int size) {
+ if (size > 0 && BytesFree() < size) {
+ char * read_ptr = NULL;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+
+ if (read_size + size <= BufferSize()) {
+ // Can reclaim space from already read bytes by shifting
+ memmove(storage_, read_ptr, read_size);
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ CHECK_GE(BytesFree(), size);
+ } else {
+ // what we need is to have at least size bytes available for writing.
+ // This implies that the buffer needs to be at least size bytes +
+ // read_size bytes long. Since we want linear time extensions in the case
+ // that we're extending this thing repeatedly, we should extend to twice
+ // the current size (if that is big enough), or the size + read_size
+ // bytes, whichever is larger.
+ int new_storage_size = 2 * storage_size_;
+ if (new_storage_size < size + read_size) {
+ new_storage_size = size + read_size;
+ }
+
+ // have to extend the thing
+ char* new_storage = new char[new_storage_size];
+
+ // copy still useful info to the new buffer.
+ memcpy(new_storage, read_ptr, read_size);
+ // reset pointers.
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ delete[] storage_;
+ storage_ = new_storage;
+ storage_size_ = new_storage_size;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// removes the oldest 'amount_to_consume' characters.
+void SimpleBuffer::AdvanceReadablePtr(int amount_to_advance) {
+ read_idx_ += amount_to_advance;
+ if (read_idx_ > storage_size_) {
+ read_idx_ = storage_size_;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Moves the internal pointers around such that the
+// amount of data specified here is expected to
+// already be resident (as if it was Written)
+inline void SimpleBuffer::AdvanceWritablePtr(int amount_to_advance) {
+ write_idx_ += amount_to_advance;
+ if (write_idx_ > storage_size_) {
+ write_idx_ = storage_size_;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/simple_buffer.h b/chromium/net/tools/flip_server/simple_buffer.h
new file mode 100644
index 00000000000..08f7d3819ba
--- /dev/null
+++ b/chromium/net/tools/flip_server/simple_buffer.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_SIMPLE_BUFFER_H__
+#define NET_TOOLS_FLIP_SERVER_SIMPLE_BUFFER_H__
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/flip_server/buffer_interface.h"
+
+namespace net {
+
+class SimpleBuffer : public BufferInterface {
+ public:
+ SimpleBuffer();
+ explicit SimpleBuffer(int size);
+ virtual ~SimpleBuffer();
+
+ std::string str() const;
+
+ typedef char * iterator;
+ typedef const char * const_iterator;
+
+ iterator begin() { return storage_ + read_idx_; }
+ const_iterator begin() const { return storage_ + read_idx_; }
+
+ iterator end() { return storage_ + write_idx_; }
+ const_iterator end() const { return storage_ + write_idx_; }
+
+ // The following functions all override pure virtual functions
+ // in BufferInterface. See buffer_interface.h for a description
+ // of what they do.
+ virtual int ReadableBytes() const OVERRIDE;
+ virtual int BufferSize() const OVERRIDE;
+ virtual int BytesFree() const OVERRIDE;
+
+ virtual bool Empty() const OVERRIDE;
+ virtual bool Full() const OVERRIDE;
+
+ virtual int Write(const char* bytes, int size) OVERRIDE;
+
+ virtual void GetWritablePtr(char **ptr, int* size) const OVERRIDE;
+
+ virtual void GetReadablePtr(char **ptr, int* size) const OVERRIDE;
+
+ virtual int Read(char* bytes, int size) OVERRIDE;
+
+ virtual void Clear() OVERRIDE;
+
+ // This can be an expensive operation: costing a new/delete, and copying of
+ // all existing data. Even if the existing buffer does not need to be
+ // resized, unread data may still need to be non-destructively copied to
+ // consolidate fragmented free space.
+ virtual bool Reserve(int size) OVERRIDE;
+
+ virtual void AdvanceReadablePtr(int amount_to_advance) OVERRIDE;
+
+ virtual void AdvanceWritablePtr(int amount_to_advance) OVERRIDE;
+
+ void Swap(SimpleBuffer* other) {
+ char* tmp = storage_;
+ storage_ = other->storage_;
+ other->storage_ = tmp;
+
+ int tmp_int = write_idx_;
+ write_idx_ = other->write_idx_;
+ other->write_idx_ = tmp_int;
+
+ tmp_int = read_idx_;
+ read_idx_ = other->read_idx_;
+ other->read_idx_ = tmp_int;
+
+ tmp_int = storage_size_;
+ storage_size_ = other->storage_size_;
+ other->storage_size_ = tmp_int;
+ }
+
+ protected:
+ char* storage_;
+ int write_idx_;
+ int read_idx_;
+ int storage_size_;
+
+ private:
+ //DISALLOW_COPY_AND_ASSIGN(SimpleBuffer);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SIMPLE_BUFFER_H__
diff --git a/chromium/net/tools/flip_server/sm_connection.cc b/chromium/net/tools/flip_server/sm_connection.cc
new file mode 100644
index 00000000000..71e81a08ae9
--- /dev/null
+++ b/chromium/net/tools/flip_server/sm_connection.cc
@@ -0,0 +1,667 @@
+// Copyright (c) 2009 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/tools/flip_server/sm_connection.h"
+
+#include <errno.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <list>
+#include <string>
+
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/http_interface.h"
+#include "net/tools/flip_server/spdy_interface.h"
+#include "net/tools/flip_server/spdy_ssl.h"
+#include "net/tools/flip_server/streamer_interface.h"
+
+namespace net {
+
+// static
+bool SMConnection::force_spdy_ = false;
+
+DataFrame::~DataFrame() {
+ if (delete_when_done)
+ delete[] data;
+}
+
+SMConnection::SMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix)
+ : last_read_time_(0),
+ fd_(-1),
+ events_(0),
+ registered_in_epoll_server_(false),
+ initialized_(false),
+ protocol_detected_(false),
+ connection_complete_(false),
+ connection_pool_(NULL),
+ epoll_server_(epoll_server),
+ ssl_state_(ssl_state),
+ memory_cache_(memory_cache),
+ acceptor_(acceptor),
+ read_buffer_(kSpdySegmentSize * 40),
+ sm_spdy_interface_(NULL),
+ sm_http_interface_(NULL),
+ sm_streamer_interface_(NULL),
+ sm_interface_(NULL),
+ log_prefix_(log_prefix),
+ max_bytes_sent_per_dowrite_(4096),
+ ssl_(NULL) {
+}
+
+SMConnection::~SMConnection() {
+ if (initialized())
+ Reset();
+}
+
+EpollServer* SMConnection::epoll_server() {
+ return epoll_server_;
+}
+
+void SMConnection::ReadyToSend() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Setting ready to send: EPOLLIN | EPOLLOUT";
+ epoll_server_->SetFDReady(fd_, EPOLLIN | EPOLLOUT);
+}
+
+void SMConnection::EnqueueDataFrame(DataFrame* df) {
+ output_list_.push_back(df);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "EnqueueDataFrame: "
+ << "size = " << df->size << ": Setting FD ready.";
+ ReadyToSend();
+}
+
+void SMConnection::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ if (initialized_) {
+ LOG(FATAL) << "Attempted to initialize already initialized server";
+ return;
+ }
+
+ client_ip_ = remote_ip;
+
+ if (fd == -1) {
+ // If fd == -1, then we are initializing a new connection that will
+ // connect to the backend.
+ //
+ // ret: -1 == error
+ // 0 == connection in progress
+ // 1 == connection complete
+ // TODO(kelindsay): is_numeric_host_address value needs to be detected
+ server_ip_ = server_ip;
+ server_port_ = server_port;
+ int ret = CreateConnectedSocket(&fd_,
+ server_ip,
+ server_port,
+ true,
+ acceptor_->disable_nagle_);
+
+ if (ret < 0) {
+ LOG(ERROR) << "-1 Could not create connected socket";
+ return;
+ } else if (ret == 1) {
+ DCHECK_NE(-1, fd_);
+ connection_complete_ = true;
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connection complete to: " << server_ip_ << ":"
+ << server_port_ << " ";
+ }
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connecting to server: " << server_ip_ << ":"
+ << server_port_ << " ";
+ } else {
+ // If fd != -1 then we are initializing a connection that has just been
+ // accepted from the listen socket.
+ connection_complete_ = true;
+ if (epoll_server_ && registered_in_epoll_server_ && fd_ != -1) {
+ epoll_server_->UnregisterFD(fd_);
+ }
+ if (fd_ != -1) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Closing pre-existing fd";
+ close(fd_);
+ fd_ = -1;
+ }
+
+ fd_ = fd;
+ }
+
+ registered_in_epoll_server_ = false;
+ // Set the last read time here as the idle checker will start from
+ // now.
+ last_read_time_ = time(NULL);
+ initialized_ = true;
+
+ connection_pool_ = connection_pool;
+ epoll_server_ = epoll_server;
+
+ if (sm_interface) {
+ sm_interface_ = sm_interface;
+ protocol_detected_ = true;
+ }
+
+ read_buffer_.Clear();
+
+ epoll_server_->RegisterFD(fd_, this, EPOLLIN | EPOLLOUT | EPOLLET);
+
+ if (use_ssl) {
+ ssl_ = CreateSSLContext(ssl_state_->ssl_ctx);
+ SSL_set_fd(ssl_, fd_);
+ PrintSslError();
+ }
+}
+
+void SMConnection::CorkSocket() {
+ int state = 1;
+ int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
+ if (rv < 0)
+ VLOG(1) << "setsockopt(CORK): " << errno;
+}
+
+void SMConnection::UncorkSocket() {
+ int state = 0;
+ int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
+ if (rv < 0)
+ VLOG(1) << "setsockopt(CORK): " << errno;
+}
+
+int SMConnection::Send(const char* data, int len, int flags) {
+ int rv = 0;
+ CorkSocket();
+ if (ssl_) {
+ ssize_t bytes_written = 0;
+ // Write smallish chunks to SSL so that we don't have large
+ // multi-packet TLS records to receive before being able to handle
+ // the data. We don't have to be too careful here, because our data
+ // frames are already getting chunked appropriately, and those are
+ // the most likely "big" frames.
+ while (len > 0) {
+ const int kMaxTLSRecordSize = 1500;
+ const char* ptr = &(data[bytes_written]);
+ int chunksize = std::min(len, kMaxTLSRecordSize);
+ rv = SSL_write(ssl_, ptr, chunksize);
+ VLOG(2) << "SSLWrite(" << chunksize << " bytes): " << rv;
+ if (rv <= 0) {
+ switch (SSL_get_error(ssl_, rv)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_ACCEPT:
+ case SSL_ERROR_WANT_CONNECT:
+ rv = -2;
+ break;
+ default:
+ PrintSslError();
+ break;
+ }
+ break;
+ }
+ bytes_written += rv;
+ len -= rv;
+ if (rv != chunksize)
+ break; // If we couldn't write everything, we're implicitly stalled
+ }
+ // If we wrote some data, return that count. Otherwise
+ // return the stall error.
+ if (bytes_written > 0)
+ rv = bytes_written;
+ } else {
+ rv = send(fd_, data, len, flags);
+ }
+ if (!(flags & MSG_MORE))
+ UncorkSocket();
+ return rv;
+}
+
+void SMConnection::OnRegistration(EpollServer* eps, int fd, int event_mask) {
+ registered_in_epoll_server_ = true;
+}
+
+void SMConnection::OnEvent(int fd, EpollEvent* event) {
+ events_ |= event->in_events;
+ HandleEvents();
+ if (events_) {
+ event->out_ready_mask = events_;
+ events_ = 0;
+ }
+}
+
+void SMConnection::OnUnregistration(int fd, bool replaced) {
+ registered_in_epoll_server_ = false;
+}
+
+void SMConnection::OnShutdown(EpollServer* eps, int fd) {
+ Cleanup("OnShutdown");
+ return;
+}
+
+void SMConnection::Cleanup(const char* cleanup) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Cleanup: " << cleanup;
+ if (!initialized_)
+ return;
+ Reset();
+ if (connection_pool_)
+ connection_pool_->SMConnectionDone(this);
+ if (sm_interface_)
+ sm_interface_->ResetForNewConnection();
+ last_read_time_ = 0;
+}
+
+void SMConnection::HandleEvents() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Received: "
+ << EpollServer::EventMaskToString(events_).c_str();
+
+ if (events_ & EPOLLIN) {
+ if (!DoRead())
+ goto handle_close_or_error;
+ }
+
+ if (events_ & EPOLLOUT) {
+ // Check if we have connected or not
+ if (connection_complete_ == false) {
+ int sock_error;
+ socklen_t sock_error_len = sizeof(sock_error);
+ int ret = getsockopt(fd_, SOL_SOCKET, SO_ERROR, &sock_error,
+ &sock_error_len);
+ if (ret != 0) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "getsockopt error: " << errno << ": " << strerror(errno);
+ goto handle_close_or_error;
+ }
+ if (sock_error == 0) {
+ connection_complete_ = true;
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connection complete to " << server_ip_ << ":"
+ << server_port_ << " ";
+ } else if (sock_error == EINPROGRESS) {
+ return;
+ } else {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "error connecting to server";
+ goto handle_close_or_error;
+ }
+ }
+ if (!DoWrite())
+ goto handle_close_or_error;
+ }
+
+ if (events_ & (EPOLLHUP | EPOLLERR)) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "!!! Got HUP or ERR";
+ goto handle_close_or_error;
+ }
+ return;
+
+ handle_close_or_error:
+ Cleanup("HandleEvents");
+}
+
+// Decide if SPDY was negotiated.
+bool SMConnection::WasSpdyNegotiated() {
+ if (force_spdy())
+ return true;
+
+ // If this is an SSL connection, check if NPN specifies SPDY.
+ if (ssl_) {
+ const unsigned char *npn_proto;
+ unsigned int npn_proto_len;
+ SSL_get0_next_proto_negotiated(ssl_, &npn_proto, &npn_proto_len);
+ if (npn_proto_len > 0) {
+ std::string npn_proto_str((const char *)npn_proto, npn_proto_len);
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "NPN protocol detected: " << npn_proto_str;
+ if (!strncmp(reinterpret_cast<const char*>(npn_proto),
+ "spdy/2", npn_proto_len))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool SMConnection::SetupProtocolInterfaces() {
+ DCHECK(!protocol_detected_);
+ protocol_detected_ = true;
+
+ bool spdy_negotiated = WasSpdyNegotiated();
+ bool using_ssl = ssl_ != NULL;
+
+ if (using_ssl)
+ VLOG(1) << (SSL_session_reused(ssl_) ? "Resumed" : "Renegotiated")
+ << " SSL Session.";
+
+ if (acceptor_->spdy_only_ && !spdy_negotiated) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "SPDY proxy only, closing HTTPS connection.";
+ return false;
+ }
+
+ switch (acceptor_->flip_handler_type_) {
+ case FLIP_HANDLER_HTTP_SERVER:
+ {
+ DCHECK(!spdy_negotiated);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_http_interface_ ? "Creating" : "Reusing")
+ << " HTTP interface.";
+ if (!sm_http_interface_)
+ sm_http_interface_ = new HttpSM(this,
+ NULL,
+ epoll_server_,
+ memory_cache_,
+ acceptor_);
+ sm_interface_ = sm_http_interface_;
+ }
+ break;
+ case FLIP_HANDLER_PROXY:
+ {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_streamer_interface_ ? "Creating" : "Reusing")
+ << " PROXY Streamer interface.";
+ if (!sm_streamer_interface_) {
+ sm_streamer_interface_ = new StreamerSM(this,
+ NULL,
+ epoll_server_,
+ acceptor_);
+ sm_streamer_interface_->set_is_request();
+ }
+ sm_interface_ = sm_streamer_interface_;
+ // If spdy is not negotiated, the streamer interface will proxy all
+ // data to the origin server.
+ if (!spdy_negotiated)
+ break;
+ }
+ // Otherwise fall through into the case below.
+ case FLIP_HANDLER_SPDY_SERVER:
+ {
+ DCHECK(spdy_negotiated);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_spdy_interface_ ? "Creating" : "Reusing")
+ << " SPDY interface.";
+ if (!sm_spdy_interface_)
+ sm_spdy_interface_ = new SpdySM(this,
+ NULL,
+ epoll_server_,
+ memory_cache_,
+ acceptor_);
+ sm_interface_ = sm_spdy_interface_;
+ }
+ break;
+ }
+
+ CorkSocket();
+ if (!sm_interface_->PostAcceptHook())
+ return false;
+
+ return true;
+}
+
+bool SMConnection::DoRead() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead()";
+ while (!read_buffer_.Full()) {
+ char* bytes;
+ int size;
+ if (fd_ == -1) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead(): fd_ == -1. Invalid FD. Returning false";
+ return false;
+ }
+ read_buffer_.GetWritablePtr(&bytes, &size);
+ ssize_t bytes_read = 0;
+ if (ssl_) {
+ bytes_read = SSL_read(ssl_, bytes, size);
+ if (bytes_read < 0) {
+ int err = SSL_get_error(ssl_, bytes_read);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_ACCEPT:
+ case SSL_ERROR_WANT_CONNECT:
+ events_ &= ~EPOLLIN;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead: SSL WANT_XXX: " << err;
+ goto done;
+ default:
+ PrintSslError();
+ goto error_or_close;
+ }
+ }
+ } else {
+ bytes_read = recv(fd_, bytes, size, MSG_DONTWAIT);
+ }
+ int stored_errno = errno;
+ if (bytes_read == -1) {
+ switch (stored_errno) {
+ case EAGAIN:
+ events_ &= ~EPOLLIN;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EAGAIN while reading";
+ goto done;
+ case EINTR:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EINTR while reading";
+ continue;
+ default:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "While calling recv, got error: "
+ << (ssl_?"(ssl error)":strerror(stored_errno));
+ goto error_or_close;
+ }
+ } else if (bytes_read > 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "read " << bytes_read
+ << " bytes";
+ last_read_time_ = time(NULL);
+ // If the protocol hasn't been detected yet, set up the handlers
+ // we'll need.
+ if (!protocol_detected_) {
+ if (!SetupProtocolInterfaces()) {
+ LOG(ERROR) << "Error setting up protocol interfaces.";
+ goto error_or_close;
+ }
+ }
+ read_buffer_.AdvanceWritablePtr(bytes_read);
+ if (!DoConsumeReadData())
+ goto error_or_close;
+ continue;
+ } else { // bytes_read == 0
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "0 bytes read with recv call.";
+ }
+ goto error_or_close;
+ }
+ done:
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead done!";
+ return true;
+
+ error_or_close:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead(): error_or_close. "
+ << "Cleaning up, then returning false";
+ Cleanup("DoRead");
+ return false;
+}
+
+bool SMConnection::DoConsumeReadData() {
+ char* bytes;
+ int size;
+ read_buffer_.GetReadablePtr(&bytes, &size);
+ while (size != 0) {
+ size_t bytes_consumed = sm_interface_->ProcessReadInput(bytes, size);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "consumed "
+ << bytes_consumed << " bytes";
+ if (bytes_consumed == 0) {
+ break;
+ }
+ read_buffer_.AdvanceReadablePtr(bytes_consumed);
+ if (sm_interface_->MessageFullyRead()) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "HandleRequestFullyRead: Setting EPOLLOUT";
+ HandleResponseFullyRead();
+ events_ |= EPOLLOUT;
+ } else if (sm_interface_->Error()) {
+ LOG(ERROR) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Framer error detected: Setting EPOLLOUT: "
+ << sm_interface_->ErrorAsString();
+ // this causes everything to be closed/cleaned up.
+ events_ |= EPOLLOUT;
+ return false;
+ }
+ read_buffer_.GetReadablePtr(&bytes, &size);
+ }
+ return true;
+}
+
+void SMConnection::HandleResponseFullyRead() {
+ sm_interface_->Cleanup();
+}
+
+bool SMConnection::DoWrite() {
+ size_t bytes_sent = 0;
+ int flags = MSG_NOSIGNAL | MSG_DONTWAIT;
+ if (fd_ == -1) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoWrite: fd == -1. Returning false.";
+ return false;
+ }
+ if (output_list_.empty()) {
+ VLOG(2) << log_prefix_ << "DoWrite: Output list empty.";
+ if (sm_interface_) {
+ sm_interface_->GetOutput();
+ }
+ if (output_list_.empty()) {
+ events_ &= ~EPOLLOUT;
+ }
+ }
+ while (!output_list_.empty()) {
+ VLOG(2) << log_prefix_ << "DoWrite: Items in output list: "
+ << output_list_.size();
+ if (bytes_sent >= max_bytes_sent_per_dowrite_) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << " byte sent >= max bytes sent per write: Setting EPOLLOUT: "
+ << bytes_sent;
+ events_ |= EPOLLOUT;
+ break;
+ }
+ if (sm_interface_ && output_list_.size() < 2) {
+ sm_interface_->GetOutput();
+ }
+ DataFrame* data_frame = output_list_.front();
+ const char* bytes = data_frame->data;
+ int size = data_frame->size;
+ bytes += data_frame->index;
+ size -= data_frame->index;
+ DCHECK_GE(size, 0);
+ if (size <= 0) {
+ output_list_.pop_front();
+ delete data_frame;
+ continue;
+ }
+
+ flags = MSG_NOSIGNAL | MSG_DONTWAIT;
+ // Look for a queue size > 1 because |this| frame is remains on the list
+ // until it has finished sending.
+ if (output_list_.size() > 1) {
+ VLOG(2) << log_prefix_ << "Outlist size: " << output_list_.size()
+ << ": Adding MSG_MORE flag";
+ flags |= MSG_MORE;
+ }
+ VLOG(2) << log_prefix_ << "Attempting to send " << size << " bytes.";
+ ssize_t bytes_written = Send(bytes, size, flags);
+ int stored_errno = errno;
+ if (bytes_written == -1) {
+ switch (stored_errno) {
+ case EAGAIN:
+ events_ &= ~EPOLLOUT;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EAGAIN while writing";
+ goto done;
+ case EINTR:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EINTR while writing";
+ continue;
+ default:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "While calling send, got error: " << stored_errno
+ << ": " << (ssl_?"":strerror(stored_errno));
+ goto error_or_close;
+ }
+ } else if (bytes_written > 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Wrote: "
+ << bytes_written << " bytes";
+ data_frame->index += bytes_written;
+ bytes_sent += bytes_written;
+ continue;
+ } else if (bytes_written == -2) {
+ // -2 handles SSL_ERROR_WANT_* errors
+ events_ &= ~EPOLLOUT;
+ goto done;
+ }
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "0 bytes written with send call.";
+ goto error_or_close;
+ }
+ done:
+ UncorkSocket();
+ return true;
+
+ error_or_close:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoWrite: error_or_close. Returning false "
+ << "after cleaning up";
+ Cleanup("DoWrite");
+ UncorkSocket();
+ return false;
+}
+
+void SMConnection::Reset() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Resetting";
+ if (ssl_) {
+ SSL_shutdown(ssl_);
+ PrintSslError();
+ SSL_free(ssl_);
+ PrintSslError();
+ ssl_ = NULL;
+ }
+ if (registered_in_epoll_server_) {
+ epoll_server_->UnregisterFD(fd_);
+ registered_in_epoll_server_ = false;
+ }
+ if (fd_ >= 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Closing connection";
+ close(fd_);
+ fd_ = -1;
+ }
+ read_buffer_.Clear();
+ initialized_ = false;
+ protocol_detected_ = false;
+ events_ = 0;
+ for (std::list<DataFrame*>::iterator i =
+ output_list_.begin();
+ i != output_list_.end();
+ ++i) {
+ delete *i;
+ }
+ output_list_.clear();
+}
+
+// static
+SMConnection* SMConnection::NewSMConnection(EpollServer* epoll_server,
+ SSLState *ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor *acceptor,
+ std::string log_prefix) {
+ return new SMConnection(epoll_server, ssl_state, memory_cache,
+ acceptor, log_prefix);
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/sm_connection.h b/chromium/net/tools/flip_server/sm_connection.h
new file mode 100644
index 00000000000..e7f1e99bfa8
--- /dev/null
+++ b/chromium/net/tools/flip_server/sm_connection.h
@@ -0,0 +1,162 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
+#define NET_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
+
+#include <arpa/inet.h> // in_addr_t
+#include <time.h>
+
+#include <list>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/flip_server/create_listener.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/flip_server/mem_cache.h"
+#include "net/tools/flip_server/ring_buffer.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+struct SSLState;
+
+// A frame of data to be sent.
+class DataFrame {
+ public:
+ const char* data;
+ size_t size;
+ bool delete_when_done;
+ size_t index;
+ DataFrame() : data(NULL), size(0), delete_when_done(false), index(0) {}
+ virtual ~DataFrame();
+};
+
+typedef std::list<DataFrame*> OutputList;
+
+class SMConnection : public SMConnectionInterface,
+ public EpollCallbackInterface,
+ public NotifierInterface {
+ public:
+ virtual ~SMConnection();
+
+ static SMConnection* NewSMConnection(EpollServer* epoll_server,
+ SSLState *ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor *acceptor,
+ std::string log_prefix);
+
+ // TODO(mbelshe): Make these private.
+ time_t last_read_time_;
+ std::string server_ip_;
+ std::string server_port_;
+
+ virtual EpollServer* epoll_server() OVERRIDE;
+ OutputList* output_list() { return &output_list_; }
+ MemoryCache* memory_cache() { return memory_cache_; }
+ virtual void ReadyToSend() OVERRIDE;
+ void EnqueueDataFrame(DataFrame* df);
+
+ int fd() const { return fd_; }
+ bool initialized() const { return initialized_; }
+ std::string client_ip() const { return client_ip_; }
+
+ void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl);
+
+ void CorkSocket();
+ void UncorkSocket();
+
+ int Send(const char* data, int len, int flags);
+
+ // EpollCallbackInterface interface.
+ virtual void OnRegistration(EpollServer* eps,
+ int fd,
+ int event_mask) OVERRIDE;
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE;
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE;
+
+ // NotifierInterface interface.
+ virtual void Notify() OVERRIDE {}
+
+ void Cleanup(const char* cleanup);
+
+ // Flag indicating if we should force spdy on all connections.
+ static bool force_spdy() { return force_spdy_; }
+ static void set_force_spdy(bool value) { force_spdy_ = value; }
+
+ private:
+ // Decide if SPDY was negotiated.
+ bool WasSpdyNegotiated();
+
+ // Initialize the protocol interfaces we'll need for this connection.
+ // Returns true if successful, false otherwise.
+ bool SetupProtocolInterfaces();
+
+ bool DoRead();
+ bool DoWrite();
+ bool DoConsumeReadData();
+ void Reset();
+
+ void HandleEvents();
+ void HandleResponseFullyRead();
+
+ protected:
+ friend std::ostream& operator<<(std::ostream& os, const SMConnection& c) {
+ os << &c << "\n";
+ return os;
+ }
+
+ private:
+ SMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix);
+ int fd_;
+ int events_;
+
+ bool registered_in_epoll_server_;
+ bool initialized_;
+ bool protocol_detected_;
+ bool connection_complete_;
+
+ SMConnectionPoolInterface* connection_pool_;
+
+ EpollServer *epoll_server_;
+ SSLState *ssl_state_;
+ MemoryCache* memory_cache_;
+ FlipAcceptor *acceptor_;
+ std::string client_ip_;
+
+ RingBuffer read_buffer_;
+
+ OutputList output_list_;
+ SMInterface* sm_spdy_interface_;
+ SMInterface* sm_http_interface_;
+ SMInterface* sm_streamer_interface_;
+ SMInterface* sm_interface_;
+ std::string log_prefix_;
+
+ size_t max_bytes_sent_per_dowrite_;
+
+ SSL* ssl_;
+
+ static bool force_spdy_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
diff --git a/chromium/net/tools/flip_server/sm_interface.h b/chromium/net/tools/flip_server/sm_interface.h
new file mode 100644
index 00000000000..5bc942f8fa0
--- /dev/null
+++ b/chromium/net/tools/flip_server/sm_interface.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
+
+// State Machine Interfaces
+
+#include <string>
+
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+
+class EpollServer;
+class SMConnectionPoolInterface;
+class SMConnection;
+
+class SMInterface {
+ public:
+ virtual void InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) = 0;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) = 0;
+ virtual size_t ProcessReadInput(const char* data, size_t len) = 0;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) = 0;
+ virtual void SetStreamID(uint32 stream_id) = 0;
+ virtual bool MessageFullyRead() const = 0;
+ virtual bool Error() const = 0;
+ virtual const char* ErrorAsString() const = 0;
+ virtual void Reset() = 0;
+ virtual void ResetForNewInterface(int32 server_idx) = 0;
+ // ResetForNewConnection is used for interfaces which control SMConnection
+ // objects. When called an interface may put its connection object into
+ // a reusable instance pool. Currently this is what the HttpSM interface
+ // does.
+ virtual void ResetForNewConnection() = 0;
+ virtual void Cleanup() = 0;
+
+ virtual int PostAcceptHook() = 0;
+
+ virtual void NewStream(uint32 stream_id, uint32 priority,
+ const std::string& filename) = 0;
+ virtual void SendEOF(uint32 stream_id) = 0;
+ virtual void SendErrorNotFound(uint32 stream_id) = 0;
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) = 0;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) = 0;
+ virtual void SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) = 0;
+ virtual void GetOutput() = 0;
+ virtual void set_is_request() = 0;
+
+ virtual ~SMInterface() {}
+};
+
+class SMConnectionInterface {
+ public:
+ virtual ~SMConnectionInterface() {}
+ virtual void ReadyToSend() = 0;
+ virtual EpollServer* epoll_server() = 0;
+};
+
+class SMConnectionPoolInterface {
+ public:
+ virtual ~SMConnectionPoolInterface() {}
+ virtual void SMConnectionDone(SMConnection* connection) = 0;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
+
diff --git a/chromium/net/tools/flip_server/spdy_interface.cc b/chromium/net/tools/flip_server/spdy_interface.cc
new file mode 100644
index 00000000000..b4e36bed229
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_interface.cc
@@ -0,0 +1,569 @@
+// Copyright (c) 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.
+
+#include "net/tools/flip_server/spdy_interface.h"
+
+#include <algorithm>
+#include <string>
+
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/dump_cache/url_utilities.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/http_interface.h"
+#include "net/tools/flip_server/spdy_util.h"
+
+namespace net {
+
+// static
+std::string SpdySM::forward_ip_header_;
+
+class SpdyFrameDataFrame : public DataFrame {
+ public:
+ SpdyFrameDataFrame(SpdyFrame* spdy_frame)
+ : frame(spdy_frame) {
+ data = spdy_frame->data();
+ size = spdy_frame->size();
+ }
+
+ virtual ~SpdyFrameDataFrame() {
+ delete frame;
+ }
+
+ const SpdyFrame* frame;
+};
+
+SpdySM::SpdySM(SMConnection* connection,
+ SMInterface* sm_http_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor)
+ : buffered_spdy_framer_(new BufferedSpdyFramer(SPDY2, true)),
+ valid_spdy_session_(false),
+ connection_(connection),
+ client_output_list_(connection->output_list()),
+ client_output_ordering_(connection),
+ next_outgoing_stream_id_(2),
+ epoll_server_(epoll_server),
+ acceptor_(acceptor),
+ memory_cache_(memory_cache),
+ close_on_error_(false) {
+ buffered_spdy_framer_->set_visitor(this);
+}
+
+SpdySM::~SpdySM() {
+ delete buffered_spdy_framer_;
+}
+
+void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "SpdySM: Initializing server connection.";
+ connection_->InitSMConnection(connection_pool, sm_interface,
+ epoll_server, fd, server_ip, server_port,
+ remote_ip, use_ssl);
+}
+
+SMInterface* SpdySM::NewConnectionInterface() {
+ SMConnection* server_connection =
+ SMConnection::NewSMConnection(epoll_server_,
+ NULL,
+ memory_cache_,
+ acceptor_,
+ "http_conn: ");
+ if (server_connection == NULL) {
+ LOG(ERROR) << "SpdySM: Could not create server connection";
+ return NULL;
+ }
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface";
+ SMInterface *sm_http_interface = new HttpSM(server_connection,
+ this,
+ epoll_server_,
+ memory_cache_,
+ acceptor_);
+ return sm_http_interface;
+}
+
+SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface(
+ std::string server_ip, std::string server_port) {
+ SMInterface *sm_http_interface;
+ int32 server_idx;
+ if (unused_server_interface_list.empty()) {
+ sm_http_interface = NewConnectionInterface();
+ server_idx = server_interface_list.size();
+ server_interface_list.push_back(sm_http_interface);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "SpdySM: Making new server connection on index: "
+ << server_idx;
+ } else {
+ server_idx = unused_server_interface_list.back();
+ unused_server_interface_list.pop_back();
+ sm_http_interface = server_interface_list.at(server_idx);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on "
+ << "index: " << server_idx;
+ }
+
+ sm_http_interface->InitSMInterface(this, server_idx);
+ sm_http_interface->InitSMConnection(NULL,
+ sm_http_interface,
+ epoll_server_,
+ -1,
+ server_ip,
+ server_port,
+ std::string(),
+ false);
+
+ return sm_http_interface;
+}
+
+int SpdySM::SpdyHandleNewStream(
+ SpdyStreamId stream_id,
+ SpdyPriority priority,
+ const SpdyHeaderBlock& headers,
+ std::string &http_data,
+ bool* is_https_scheme) {
+ *is_https_scheme = false;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn("
+ << stream_id << ")";
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: "
+ << headers.size();
+
+ SpdyHeaderBlock::const_iterator url = headers.find("url");
+ SpdyHeaderBlock::const_iterator method = headers.find("method");
+ if (url == headers.end() || method == headers.end()) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: didn't find method or url "
+ << "or method. Not creating stream";
+ return 0;
+ }
+
+ SpdyHeaderBlock::const_iterator scheme = headers.find("scheme");
+ if (scheme->second.compare("https") == 0) {
+ *is_https_scheme = true;
+ }
+
+ // url->second here only ever seems to contain just the path. When this
+ // path contains a query string with a http:// in one of its values,
+ // UrlUtilities::GetUrlPath will fail and always return a / breaking
+ // the request. GetUrlPath assumes the absolute URL is being passed in.
+ std::string uri;
+ if (url->second.compare(0,4,"http") == 0)
+ uri = UrlUtilities::GetUrlPath(url->second);
+ else
+ uri = std::string(url->second);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
+ std::string host = UrlUtilities::GetUrlHost(url->second);
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second
+ << " " << uri;
+ std::string filename = EncodeURL(uri, host, method->second);
+ NewStream(stream_id, priority, filename);
+ } else {
+ SpdyHeaderBlock::const_iterator version = headers.find("version");
+ http_data += method->second + " " + uri + " " + version->second + "\r\n";
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " "
+ << uri << " " << version->second;
+ for (SpdyHeaderBlock::const_iterator i = headers.begin();
+ i != headers.end(); ++i) {
+ http_data += i->first + ": " + i->second + "\r\n";
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":"
+ << i->second.c_str();
+ }
+ if (forward_ip_header_.length()) {
+ // X-Client-Cluster-IP header
+ http_data += forward_ip_header_ + ": " +
+ connection_->client_ip() + "\r\n";
+ }
+ http_data += "\r\n";
+ }
+
+ VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data;
+ return 1;
+}
+
+void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
+ << ", [" << len << "])";
+ StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
+ if (it == stream_to_smif_.end()) {
+ VLOG(2) << "Dropping frame from unknown stream " << stream_id;
+ if (!valid_spdy_session_)
+ close_on_error_ = true;
+ return;
+ }
+
+ SMInterface* interface = it->second;
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
+ interface->ProcessWriteInput(data, len);
+}
+
+void SpdySM::OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ std::string http_data;
+ bool is_https_scheme;
+ int ret = SpdyHandleNewStream(stream_id, priority, headers, http_data,
+ &is_https_scheme);
+ if (!ret) {
+ LOG(ERROR) << "SpdySM: Could not convert spdy into http.";
+ return;
+ }
+ // We've seen a valid looking SYN_STREAM, consider this to have
+ // been a real spdy session.
+ valid_spdy_session_ = true;
+
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ std::string server_ip;
+ std::string server_port;
+ if (is_https_scheme) {
+ server_ip = acceptor_->https_server_ip_;
+ server_port = acceptor_->https_server_port_;
+ } else {
+ server_ip = acceptor_->http_server_ip_;
+ server_port = acceptor_->http_server_port_;
+ }
+ SMInterface* sm_http_interface =
+ FindOrMakeNewSMConnectionInterface(server_ip, server_port);
+ stream_to_smif_[stream_id] = sm_http_interface;
+ sm_http_interface->SetStreamID(stream_id);
+ sm_http_interface->ProcessWriteInput(http_data.c_str(),
+ http_data.size());
+ }
+}
+
+void SpdySM::OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ // TODO(willchan): if there is an error parsing headers, we
+ // should send a RST_STREAM.
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply("
+ << stream_id << ")";
+}
+
+void SpdySM::OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders("
+ << stream_id << ")";
+}
+
+void SpdySM::OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRstStream("
+ << stream_id << ")";
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
+ return buffered_spdy_framer_->ProcessInput(data, len);
+}
+
+size_t SpdySM::ProcessWriteInput(const char* data, size_t len) {
+ return 0;
+}
+
+bool SpdySM::MessageFullyRead() const {
+ return buffered_spdy_framer_->MessageFullyRead();
+}
+
+bool SpdySM::Error() const {
+ return close_on_error_ || buffered_spdy_framer_->HasError();
+}
+
+const char* SpdySM::ErrorAsString() const {
+ DCHECK(Error());
+ return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code());
+}
+
+void SpdySM::ResetForNewInterface(int32 server_idx) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: "
+ << "server_idx: " << server_idx;
+ unused_server_interface_list.push_back(server_idx);
+}
+
+void SpdySM::ResetForNewConnection() {
+ // seq_num is not cleared, intentionally.
+ delete buffered_spdy_framer_;
+ buffered_spdy_framer_ = new BufferedSpdyFramer(SPDY2, true);
+ buffered_spdy_framer_->set_visitor(this);
+ valid_spdy_session_ = false;
+ client_output_ordering_.Reset();
+ next_outgoing_stream_id_ = 2;
+}
+
+// Send a settings frame
+int SpdySM::PostAcceptHook() {
+ SettingsMap settings;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 100);
+ SpdyFrame* settings_frame =
+ buffered_spdy_framer_->CreateSettings(settings);
+
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame";
+ EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame));
+ return 1;
+}
+
+void SpdySM::NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) {
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+ mci.priority = priority;
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
+ if (!memory_cache_->AssignFileData(filename, &mci)) {
+ // error creating new stream.
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
+ SendErrorNotFound(stream_id);
+ } else {
+ AddToOutputOrder(mci);
+ }
+ } else {
+ AddToOutputOrder(mci);
+ }
+}
+
+void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
+ client_output_ordering_.AddToOutputOrder(mci);
+}
+
+void SpdySM::SendEOF(uint32 stream_id) {
+ SendEOFImpl(stream_id);
+}
+
+void SpdySM::SendErrorNotFound(uint32 stream_id) {
+ SendErrorNotFoundImpl(stream_id);
+}
+
+void SpdySM::SendOKResponse(uint32 stream_id, std::string* output) {
+ SendOKResponseImpl(stream_id, output);
+}
+
+size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynStreamImpl(stream_id, headers);
+}
+
+size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynReplyImpl(stream_id, headers);
+}
+
+void SpdySM::SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) {
+ SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
+ SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
+}
+
+void SpdySM::SendEOFImpl(uint32 stream_id) {
+ SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id;
+ KillStream(stream_id);
+ stream_to_smif_.erase(stream_id);
+}
+
+void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false);
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+void SpdySM::SendOKResponseImpl(uint32 stream_id, std::string* output) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(
+ stream_id, output->c_str(), output->size(), DATA_FLAG_FIN, false);
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+void SpdySM::KillStream(uint32 stream_id) {
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
+ for (BalsaHeaders::const_header_lines_iterator hi =
+ headers.header_lines_begin();
+ hi != headers.header_lines_end();
+ ++hi) {
+ // It is illegal to send SPDY headers with empty value or header
+ // names.
+ if (!hi->first.length() || !hi->second.length())
+ continue;
+
+ // Key must be all lower case in SPDY headers.
+ std::string key = hi->first.as_string();
+ std::transform(key.begin(), key.end(), key.begin(), ::tolower);
+ SpdyHeaderBlock::iterator fhi = dest.find(key);
+ if (fhi == dest.end()) {
+ dest[key] = hi->second.as_string();
+ } else {
+ dest[key] = (
+ std::string(fhi->second.data(), fhi->second.size()) + "\0" +
+ std::string(hi->second.data(), hi->second.size()));
+ }
+ }
+
+ // These headers have no value
+ dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive
+ dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive
+}
+
+size_t SpdySM::SendSynStreamImpl(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ SpdyHeaderBlock block;
+ block["method"] = headers.request_method().as_string();
+ if (!headers.HasHeader("status"))
+ block["status"] = headers.response_code().as_string();
+ if (!headers.HasHeader("version"))
+ block["version"] =headers.response_version().as_string();
+ if (headers.HasHeader("X-Original-Url")) {
+ std::string original_url = headers.GetHeader("X-Original-Url").as_string();
+ block["path"] = UrlUtilities::GetUrlPath(original_url);
+ } else {
+ block["path"] = headers.request_uri().as_string();
+ }
+ CopyHeaders(block, headers);
+
+ SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynStream(
+ stream_id, 0, 0, 0, CONTROL_FLAG_NONE, true, &block);
+ size_t df_size = fsrcf->size();
+ EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader "
+ << stream_id;
+ return df_size;
+}
+
+size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ SpdyHeaderBlock block;
+ CopyHeaders(block, headers);
+ block["status"] = headers.response_code().as_string() + " " +
+ headers.response_reason_phrase().as_string();
+ block["version"] = headers.response_version().as_string();
+
+ SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynReply(
+ stream_id, CONTROL_FLAG_NONE, true, &block);
+ size_t df_size = fsrcf->size();
+ EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader "
+ << stream_id;
+ return df_size;
+}
+
+void SpdySM::SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
+ SpdyDataFlags flags, bool compress) {
+ // TODO(mbelshe): We can't compress here - before going into the
+ // priority queue. Compression needs to be done
+ // with late binding.
+ if (len == 0) {
+ SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
+ stream_id, data, len, flags);
+ EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
+ return;
+ }
+
+ // Chop data frames into chunks so that one stream can't monopolize the
+ // output channel.
+ while (len > 0) {
+ int64 size = std::min(len, static_cast<int64>(kSpdySegmentSize));
+ SpdyDataFlags chunk_flags = flags;
+
+ // If we chunked this block, and the FIN flag was set, there is more
+ // data coming. So, remove the flag.
+ if ((size < len) && (flags & DATA_FLAG_FIN))
+ chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN);
+
+ SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
+ stream_id, data, size, chunk_flags);
+ EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame "
+ << stream_id << " [" << size << "] shrunk to "
+ << (fdf->size() - kSpdyOverhead) << ", flags=" << flags;
+
+ data += size;
+ len -= size;
+ }
+}
+
+void SpdySM::EnqueueDataFrame(DataFrame* df) {
+ connection_->EnqueueDataFrame(df);
+}
+
+void SpdySM::GetOutput() {
+ while (client_output_list_->size() < 2) {
+ MemCacheIter* mci = client_output_ordering_.GetIter();
+ if (mci == NULL) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "SpdySM: GetOutput: nothing to output!?";
+ return;
+ }
+ if (!mci->transformed_header) {
+ mci->transformed_header = true;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed "
+ << "header stream_id: [" << mci->stream_id << "]";
+ if ((mci->stream_id % 2) == 0) {
+ // this is a server initiated stream.
+ // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
+ BalsaHeaders headers;
+ headers.CopyFrom(*(mci->file_data->headers()));
+ headers.ReplaceOrAppendHeader("status", "200");
+ headers.ReplaceOrAppendHeader("version", "http/1.1");
+ headers.SetRequestFirstlineFromStringPieces("PUSH",
+ mci->file_data->filename(),
+ "");
+ mci->bytes_sent = SendSynStream(mci->stream_id, headers);
+ } else {
+ BalsaHeaders headers;
+ headers.CopyFrom(*(mci->file_data->headers()));
+ mci->bytes_sent = SendSynReply(mci->stream_id, headers);
+ }
+ return;
+ }
+ if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput "
+ << "remove_stream_id: [" << mci->stream_id << "]";
+ SendEOF(mci->stream_id);
+ return;
+ }
+ size_t num_to_write =
+ mci->file_data->body().size() - mci->body_bytes_consumed;
+ if (num_to_write > mci->max_segment_size)
+ num_to_write = mci->max_segment_size;
+
+ bool should_compress = false;
+ if (!mci->file_data->headers()->HasHeader("content-encoding")) {
+ if (mci->file_data->headers()->HasHeader("content-type")) {
+ std::string content_type =
+ mci->file_data->headers()->GetHeader("content-type").as_string();
+ if (content_type.find("image") == content_type.npos)
+ should_compress = true;
+ }
+ }
+
+ SendDataFrame(mci->stream_id,
+ mci->file_data->body().data() + mci->body_bytes_consumed,
+ num_to_write, 0, should_compress);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame["
+ << mci->stream_id << "]: " << num_to_write;
+ mci->body_bytes_consumed += num_to_write;
+ mci->bytes_sent += num_to_write;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/tools/flip_server/spdy_interface.h b/chromium/net/tools/flip_server/spdy_interface.h
new file mode 100644
index 00000000000..a83dab7f406
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_interface.h
@@ -0,0 +1,198 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+
+class SpdySM : public BufferedSpdyFramerVisitorInterface,
+ public SMInterface {
+ public:
+ SpdySM(SMConnection* connection,
+ SMInterface* sm_http_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor);
+ virtual ~SpdySM();
+
+ virtual void InitSMInterface(SMInterface* sm_http_interface,
+ int32 server_idx) OVERRIDE {}
+
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+
+ private:
+ virtual void set_is_request() OVERRIDE {}
+ SMInterface* NewConnectionInterface();
+ SMInterface* FindOrMakeNewSMConnectionInterface(std::string server_ip,
+ std::string server_port);
+ int SpdyHandleNewStream(SpdyStreamId stream_id,
+ SpdyPriority priority,
+ const SpdyHeaderBlock& headers,
+ std::string& http_data,
+ bool* is_https_scheme);
+
+ // BufferedSpdyFramerVisitorInterface:
+ virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE {}
+ virtual void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) OVERRIDE {}
+ // Called after all the header data for SYN_STREAM control frame is received.
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ uint8 credential_slot,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called after all the header data for SYN_REPLY control frame is received.
+ virtual void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called after all the header data for HEADERS control frame is received.
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ // When the other side has finished sending data on this stream,
+ // this method will be called with a zero-length buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE;
+
+ // Called when a SETTINGS frame is received.
+ // |clear_persisted| True if the respective flag is set on the SETTINGS frame.
+ virtual void OnSettings(bool clear_persisted) OVERRIDE {}
+
+ // Called when an individual setting within a SETTINGS frame has been parsed
+ // and validated.
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {}
+
+ // Called when a PING frame has been parsed.
+ virtual void OnPing(uint32 unique_id) OVERRIDE {}
+
+ // Called when a RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE;
+
+ // Called when a GOAWAY frame has been parsed.
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {}
+
+ // Called when a WINDOW_UPDATE frame has been parsed.
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {}
+
+ // Called when a PUSH_PROMISE frame has been parsed.
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) OVERRIDE {}
+
+ public:
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE {}
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE {}
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE;
+ virtual void ResetForNewConnection() OVERRIDE;
+ // SMInterface's Cleanup is currently only called by SMConnection after a
+ // protocol message as been fully read. Spdy's SMInterface does not need
+ // to do any cleanup at this time.
+ // TODO(klindsay) This method is probably not being used properly and
+ // some logic review and method renaming is probably in order.
+ virtual void Cleanup() OVERRIDE {}
+ // Send a settings frame
+ virtual int PostAcceptHook() OVERRIDE;
+ virtual void NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) OVERRIDE;
+ void AddToOutputOrder(const MemCacheIter& mci);
+ virtual void SendEOF(uint32 stream_id) OVERRIDE;
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE;
+ void SendOKResponse(uint32 stream_id, std::string* output);
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) OVERRIDE;
+ BufferedSpdyFramer* spdy_framer() {
+ return buffered_spdy_framer_;
+ }
+
+ static std::string forward_ip_header() { return forward_ip_header_; }
+ static void set_forward_ip_header(std::string value) {
+ forward_ip_header_ = value;
+ }
+
+ private:
+ void SendEOFImpl(uint32 stream_id);
+ void SendErrorNotFoundImpl(uint32 stream_id);
+ void SendOKResponseImpl(uint32 stream_id, std::string* output);
+ void KillStream(uint32 stream_id);
+ void CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers);
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers);
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers);
+ void SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
+ SpdyDataFlags flags, bool compress);
+ void EnqueueDataFrame(DataFrame* df);
+ virtual void GetOutput() OVERRIDE;
+ private:
+ BufferedSpdyFramer* buffered_spdy_framer_;
+ bool valid_spdy_session_; // True if we have seen valid data on this session.
+ // Use this to fail fast when junk is sent to our
+ // port.
+
+ SMConnection* connection_;
+ OutputList* client_output_list_;
+ OutputOrdering client_output_ordering_;
+ uint32 next_outgoing_stream_id_;
+ EpollServer* epoll_server_;
+ FlipAcceptor* acceptor_;
+ MemoryCache* memory_cache_;
+ std::vector<SMInterface*> server_interface_list;
+ std::vector<int32> unused_server_interface_list;
+ typedef std::map<uint32, SMInterface*> StreamToSmif;
+ StreamToSmif stream_to_smif_;
+ bool close_on_error_;
+
+ static std::string forward_ip_header_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
diff --git a/chromium/net/tools/flip_server/spdy_ssl.cc b/chromium/net/tools/flip_server/spdy_ssl.cc
new file mode 100644
index 00000000000..292bbe0eb0e
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_ssl.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 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.
+
+#include "net/tools/flip_server/spdy_ssl.h"
+
+#include "base/logging.h"
+#include "openssl/err.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+#define NEXT_PROTO_STRING "\x06spdy/2\x08http/1.1\x08http/1.0"
+#define SSL_CIPHER_LIST "!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH"
+
+int ssl_set_npn_callback(SSL *s,
+ const unsigned char **data,
+ unsigned int *len,
+ void *arg) {
+ VLOG(1) << "SSL NPN callback: advertising protocols.";
+ *data = (const unsigned char *) NEXT_PROTO_STRING;
+ *len = strlen(NEXT_PROTO_STRING);
+ return SSL_TLSEXT_ERR_OK;
+}
+
+void InitSSL(SSLState* state,
+ std::string ssl_cert_name,
+ std::string ssl_key_name,
+ bool use_npn,
+ int session_expiration_time,
+ bool disable_ssl_compression) {
+ SSL_library_init();
+ PrintSslError();
+
+ SSL_load_error_strings();
+ PrintSslError();
+
+ state->ssl_method = SSLv23_method();
+ // TODO(joth): Remove const_cast when the openssl 1.0.0 upgrade is complete.
+ // (See http://codereview.chromium.org/9254031).
+ state->ssl_ctx = SSL_CTX_new(const_cast<SSL_METHOD*>(state->ssl_method));
+ if (!state->ssl_ctx) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to create SSL context";
+ }
+ // Disable SSLv2 support.
+ SSL_CTX_set_options(state->ssl_ctx,
+ SSL_OP_NO_SSLv2 | SSL_OP_CIPHER_SERVER_PREFERENCE);
+ if (SSL_CTX_use_certificate_chain_file(state->ssl_ctx,
+ ssl_cert_name.c_str()) <= 0) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to use cert.pem as SSL cert.";
+ }
+ if (SSL_CTX_use_PrivateKey_file(state->ssl_ctx,
+ ssl_key_name.c_str(),
+ SSL_FILETYPE_PEM) <= 0) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to use key.pem as SSL key.";
+ }
+ if (!SSL_CTX_check_private_key(state->ssl_ctx)) {
+ PrintSslError();
+ LOG(FATAL) << "The cert.pem and key.pem files don't match";
+ }
+ if (use_npn) {
+ SSL_CTX_set_next_protos_advertised_cb(state->ssl_ctx,
+ ssl_set_npn_callback, NULL);
+ }
+ VLOG(1) << "SSL CTX default cipher list: " << SSL_CIPHER_LIST;
+ SSL_CTX_set_cipher_list(state->ssl_ctx, SSL_CIPHER_LIST);
+
+ VLOG(1) << "SSL CTX session expiry: " << session_expiration_time
+ << " seconds";
+ SSL_CTX_set_timeout(state->ssl_ctx, session_expiration_time);
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ VLOG(1) << "SSL CTX: Setting Release Buffers mode.";
+ SSL_CTX_set_mode(state->ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+ // Proper methods to disable compression don't exist until 0.9.9+. For now
+ // we must manipulate the stack of compression methods directly.
+ if (disable_ssl_compression) {
+ STACK_OF(SSL_COMP) *ssl_comp_methods = SSL_COMP_get_compression_methods();
+ int num_methods = sk_SSL_COMP_num(ssl_comp_methods);
+ int i;
+ for (i = 0; i < num_methods; i++) {
+ static_cast<void>(sk_SSL_COMP_delete(ssl_comp_methods, i));
+ }
+ }
+}
+
+SSL* CreateSSLContext(SSL_CTX* ssl_ctx) {
+ SSL* ssl = SSL_new(ssl_ctx);
+ SSL_set_accept_state(ssl);
+ PrintSslError();
+ return ssl;
+}
+
+void PrintSslError() {
+ char buf[128]; // this buffer must be at least 120 chars long.
+ int error_num = ERR_get_error();
+ while (error_num != 0) {
+ ERR_error_string_n(error_num, buf, sizeof(buf));
+ LOG(ERROR) << buf;
+ error_num = ERR_get_error();
+ }
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/spdy_ssl.h b/chromium/net/tools/flip_server/spdy_ssl.h
new file mode 100644
index 00000000000..8db0d8ccc95
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_ssl.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_FLIP_SERVER_SPDY_SSL_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_SSL_H_
+
+#include <string>
+
+#include "openssl/ssl.h"
+
+namespace net {
+
+struct SSLState {
+ const SSL_METHOD* ssl_method;
+ SSL_CTX* ssl_ctx;
+};
+
+void InitSSL(SSLState* state,
+ std::string ssl_cert_name,
+ std::string ssl_key_name,
+ bool use_npn,
+ int session_expiration_time,
+ bool disable_ssl_compression);
+SSL* CreateSSLContext(SSL_CTX* ssl_ctx);
+void PrintSslError();
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_SSL_H_
+
diff --git a/chromium/net/tools/flip_server/spdy_util.cc b/chromium/net/tools/flip_server/spdy_util.cc
new file mode 100644
index 00000000000..d4a789a9735
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_util.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2009 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/tools/flip_server/spdy_util.h"
+
+#include <string>
+
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+namespace net {
+
+bool g_need_to_encode_url = false;
+
+// Encode the URL.
+std::string EncodeURL(std::string uri, std::string host, std::string method) {
+ if (!g_need_to_encode_url) {
+ // TODO(mbelshe): if uri is fully qualified, need to strip protocol/host.
+ return std::string(method + "_" + uri);
+ }
+
+ std::string filename;
+ if (uri[0] == '/') {
+ // uri is not fully qualified.
+ filename = UrlToFilenameEncoder::Encode(
+ "http://" + host + uri, method + "_/", false);
+ } else {
+ filename = UrlToFilenameEncoder::Encode(uri, method + "_/", false);
+ }
+ return filename;
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/spdy_util.h b/chromium/net/tools/flip_server/spdy_util.h
new file mode 100644
index 00000000000..537053d50bc
--- /dev/null
+++ b/chromium/net/tools/flip_server/spdy_util.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
+
+#include <string>
+
+namespace net {
+
+// Flag indicating if we need to encode urls into filenames (legacy).
+extern bool g_need_to_encode_url;
+
+// Encode the URL.
+std::string EncodeURL(std::string uri, std::string host, std::string method);
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
+
diff --git a/chromium/net/tools/flip_server/split.cc b/chromium/net/tools/flip_server/split.cc
new file mode 100644
index 00000000000..790051384d5
--- /dev/null
+++ b/chromium/net/tools/flip_server/split.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2009 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/tools/flip_server/split.h"
+
+#include <string.h>
+
+#include <vector>
+
+#include "base/strings/string_piece.h"
+
+namespace net {
+
+// Yea, this could be done with less code duplication using
+// template magic, I know.
+void SplitStringPieceToVector(const base::StringPiece& full,
+ const char* delim,
+ std::vector<base::StringPiece>* vec,
+ bool omit_empty_strings) {
+ vec->clear();
+ if (full.empty() || delim[0] == '\0')
+ return;
+
+ if (delim[1] == '\0') {
+ base::StringPiece::const_iterator s = full.begin();
+ base::StringPiece::const_iterator e = s;
+ for (;e != full.end(); ++e) {
+ if (*e == delim[0]) {
+ if (e != s || !omit_empty_strings) {
+ vec->push_back(base::StringPiece(s, e - s));
+ }
+ s = e;
+ ++s;
+ }
+ }
+ if (s != e) {
+ --e;
+ if (e != s || !omit_empty_strings) {
+ vec->push_back(base::StringPiece(s, e - s));
+ }
+ }
+ } else {
+ base::StringPiece::const_iterator s = full.begin();
+ base::StringPiece::const_iterator e = s;
+ for (;e != full.end(); ++e) {
+ bool one_matched = false;
+ for (const char *d = delim; *d != '\0'; ++d) {
+ if (*d == *e) {
+ one_matched = true;
+ break;
+ }
+ }
+ if (one_matched) {
+ if (e != s || !omit_empty_strings) {
+ vec->push_back(base::StringPiece(s, e - s));
+ }
+ s = e;
+ ++s;
+ }
+ }
+ if (s != e) {
+ --e;
+ if (e != s || !omit_empty_strings) {
+ vec->push_back(base::StringPiece(s, e - s));
+ }
+ }
+ }
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/split.h b/chromium/net/tools/flip_server/split.h
new file mode 100644
index 00000000000..aae20b062a5
--- /dev/null
+++ b/chromium/net/tools/flip_server/split.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_SPLIT_H_
+#define NET_TOOLS_FLIP_SERVER_SPLIT_H_
+
+#include <vector>
+#include "base/strings/string_piece.h"
+
+namespace net {
+
+// Yea, this could be done with less code duplication using
+// template magic, I know.
+void SplitStringPieceToVector(const base::StringPiece& full,
+ const char* delim,
+ std::vector<base::StringPiece>* vec,
+ bool omit_empty_strings);
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPLIT_H_
+
diff --git a/chromium/net/tools/flip_server/streamer_interface.cc b/chromium/net/tools/flip_server/streamer_interface.cc
new file mode 100644
index 00000000000..b1612e73321
--- /dev/null
+++ b/chromium/net/tools/flip_server/streamer_interface.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 2009 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/tools/flip_server/streamer_interface.h"
+
+#include <string>
+
+#include "net/tools/flip_server/balsa_frame.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+
+namespace net {
+
+std::string StreamerSM::forward_ip_header_;
+
+StreamerSM::StreamerSM(SMConnection* connection,
+ SMInterface* sm_other_interface,
+ EpollServer* epoll_server,
+ FlipAcceptor* acceptor)
+ : connection_(connection),
+ sm_other_interface_(sm_other_interface),
+ epoll_server_(epoll_server),
+ acceptor_(acceptor),
+ is_request_(false),
+ http_framer_(new BalsaFrame) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Creating StreamerSM object";
+ http_framer_->set_balsa_visitor(this);
+ http_framer_->set_balsa_headers(&headers_);
+ http_framer_->set_is_request(false);
+}
+
+StreamerSM::~StreamerSM() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Destroying StreamerSM object";
+ Reset();
+ delete http_framer_;
+}
+
+void StreamerSM::set_is_request() {
+ is_request_ = true;
+ http_framer_->set_is_request(true);
+}
+
+void StreamerSM::InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) {
+ sm_other_interface_ = sm_other_interface;
+}
+
+void StreamerSM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Initializing server "
+ << "connection.";
+ connection_->InitSMConnection(connection_pool, sm_interface,
+ epoll_server, fd, server_ip,
+ server_port, remote_ip, use_ssl);
+}
+
+size_t StreamerSM::ProcessReadInput(const char* data, size_t len) {
+ // For now we only want to parse http requests. Just stream responses
+ if (is_request_) {
+ return http_framer_->ProcessInput(data, len);
+ } else {
+ return sm_other_interface_->ProcessWriteInput(data, len);
+ }
+}
+
+size_t StreamerSM::ProcessWriteInput(const char* data, size_t len) {
+ char * dataPtr = new char[len];
+ memcpy(dataPtr, data, len);
+ DataFrame* df = new DataFrame;
+ df->data = (const char *)dataPtr;
+ df->size = len;
+ df->delete_when_done = true;
+ connection_->EnqueueDataFrame(df);
+ return len;
+}
+
+bool StreamerSM::Error() const {
+ return false;
+}
+
+const char* StreamerSM::ErrorAsString() const {
+ return "(none)";
+}
+
+bool StreamerSM::MessageFullyRead() const {
+ if (is_request_) {
+ return http_framer_->MessageFullyRead();
+ } else {
+ return false;
+ }
+}
+
+void StreamerSM::Reset() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Reset";
+ connection_->Cleanup("Server Reset");
+ http_framer_->Reset();
+}
+
+void StreamerSM::ResetForNewConnection() {
+ http_framer_->Reset();
+ sm_other_interface_->Reset();
+}
+
+void StreamerSM::Cleanup() {
+ if (is_request_)
+ http_framer_->Reset();
+}
+
+int StreamerSM::PostAcceptHook() {
+ if (!sm_other_interface_) {
+ SMConnection *server_connection =
+ SMConnection::NewSMConnection(epoll_server_, NULL, NULL,
+ acceptor_, "server_conn: ");
+ if (server_connection == NULL) {
+ LOG(ERROR) << "StreamerSM: Could not create server conenction.";
+ return 0;
+ }
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Creating new server "
+ << "connection.";
+ sm_other_interface_ = new StreamerSM(server_connection, this,
+ epoll_server_, acceptor_);
+ sm_other_interface_->InitSMInterface(this, 0);
+ }
+ // The Streamer interface is used to stream HTTPS connections, so we
+ // will always use the https_server_ip/port here.
+ sm_other_interface_->InitSMConnection(NULL,
+ sm_other_interface_,
+ epoll_server_,
+ -1,
+ acceptor_->https_server_ip_,
+ acceptor_->https_server_port_,
+ std::string(),
+ false);
+
+ return 1;
+}
+
+size_t StreamerSM::SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ return 0;
+}
+
+size_t StreamerSM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+}
+
+void StreamerSM::ProcessBodyInput(const char *input, size_t size) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "StreamerHttpSM: Process Body Input Data: "
+ << "size " << size;
+ sm_other_interface_->ProcessWriteInput(input, size);
+}
+
+void StreamerSM::MessageDone() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerHttpSM: MessageDone.";
+ // TODO(kelindsay): anything need to be done ehre?
+ } else {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StraemerHttpSM: MessageDone.";
+ }
+}
+
+void StreamerSM::ProcessHeaders(const BalsaHeaders& headers) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpStreamerSM: Process Headers";
+ BalsaHeaders mod_headers;
+ mod_headers.CopyFrom(headers);
+ if (forward_ip_header_.length()) {
+ LOG(INFO) << "Adding forward header: " << forward_ip_header_;
+ mod_headers.ReplaceOrAppendHeader(forward_ip_header_,
+ connection_->client_ip());
+ } else {
+ LOG(INFO) << "NOT adding forward header.";
+ }
+ SimpleBuffer sb;
+ char* buffer;
+ int size;
+ mod_headers.WriteHeaderAndEndingToBuffer(&sb);
+ sb.GetReadablePtr(&buffer, &size);
+ sm_other_interface_->ProcessWriteInput(buffer, size);
+}
+
+void StreamerSM::HandleHeaderError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StreamerSM::HandleChunkingError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StreamerSM::HandleBodyError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StreamerSM::HandleError() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Error detected";
+}
+
+} // namespace net
+
diff --git a/chromium/net/tools/flip_server/streamer_interface.h b/chromium/net/tools/flip_server/streamer_interface.h
new file mode 100644
index 00000000000..4f09b4558e8
--- /dev/null
+++ b/chromium/net/tools/flip_server/streamer_interface.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_
+#define NET_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/flip_server/balsa_visitor_interface.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class BalsaFrame;
+class FlipAcceptor;
+class MemCacheIter;
+class SMConnection;
+class EpollServer;
+
+class StreamerSM : public BalsaVisitorInterface,
+ public SMInterface {
+ public:
+ StreamerSM(SMConnection* connection,
+ SMInterface* sm_other_interface,
+ EpollServer* epoll_server,
+ FlipAcceptor* acceptor);
+ virtual ~StreamerSM();
+
+ void AddToOutputOrder(const MemCacheIter& mci) {}
+
+ virtual void InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) OVERRIDE;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE {}
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE {}
+ virtual void ResetForNewConnection() OVERRIDE;
+ virtual void Cleanup() OVERRIDE;
+ virtual int PostAcceptHook() OVERRIDE;
+ virtual void NewStream(uint32 stream_id, uint32 priority,
+ const std::string& filename) OVERRIDE {}
+ virtual void SendEOF(uint32 stream_id) OVERRIDE {}
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE {}
+ virtual void SendOKResponse(uint32 stream_id, std::string output) {}
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) OVERRIDE {}
+ virtual void set_is_request() OVERRIDE;
+ static std::string forward_ip_header() { return forward_ip_header_; }
+ static void set_forward_ip_header(std::string value) {
+ forward_ip_header_ = value;
+ }
+
+ private:
+ void SendEOFImpl(uint32 stream_id) {}
+ void SendErrorNotFoundImpl(uint32 stream_id) {}
+ void SendOKResponseImpl(uint32 stream_id, std::string* output) {}
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+ }
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+ }
+ void SendDataFrameImpl(uint32 stream_id, const char* data, int64 len,
+ uint32 flags, bool compress) {}
+ virtual void GetOutput() OVERRIDE {}
+
+ virtual void ProcessBodyInput(const char *input, size_t size) OVERRIDE;
+ virtual void MessageDone() OVERRIDE;
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE;
+ virtual void ProcessBodyData(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaderInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char *input, size_t size) OVERRIDE {}
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char *line_input,
+ size_t line_length,
+ const char *version_input,
+ size_t version_length,
+ const char *status_input,
+ size_t status_length,
+ const char *reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char *input,
+ size_t size) OVERRIDE {}
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+ void HandleError();
+
+ SMConnection* connection_;
+ SMInterface* sm_other_interface_;
+ EpollServer* epoll_server_;
+ FlipAcceptor* acceptor_;
+ bool is_request_;
+ BalsaFrame* http_framer_;
+ BalsaHeaders headers_;
+ static std::string forward_ip_header_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_
+
diff --git a/chromium/net/tools/flip_server/string_piece_utils.h b/chromium/net/tools/flip_server/string_piece_utils.h
new file mode 100644
index 00000000000..3c46dd91904
--- /dev/null
+++ b/chromium/net/tools/flip_server/string_piece_utils.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2010 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_TOOLS_FLIP_SERVER_STRING_PIECE_UTILS_H_
+#define NET_TOOLS_FLIP_SERVER_STRING_PIECE_UTILS_H_
+
+#include <ctype.h>
+
+#include "base/port.h"
+#include "base/strings/string_piece.h"
+
+namespace net {
+
+struct StringPieceCaseHash {
+ size_t operator()(const base::StringPiece& sp) const {
+ // based on __stl_string_hash in http://www.sgi.com/tech/stl/string
+ size_t hash_val = 0;
+ for (base::StringPiece::const_iterator it = sp.begin();
+ it != sp.end(); ++it) {
+ hash_val = 5 * hash_val + tolower(*it);
+ }
+ return hash_val;
+ }
+};
+
+struct StringPieceUtils {
+ static bool EqualIgnoreCase(const base::StringPiece& piece1,
+ const base::StringPiece& piece2) {
+ base::StringPiece::const_iterator p1i = piece1.begin();
+ base::StringPiece::const_iterator p2i = piece2.begin();
+ if (piece1.empty() && piece2.empty()) {
+ return true;
+ } else if (piece1.size() != piece2.size()) {
+ return false;
+ }
+ while (p1i != piece1.end() && p2i != piece2.end()) {
+ if (tolower(*p1i) != tolower(*p2i))
+ return false;
+ ++p1i;
+ ++p2i;
+ }
+ return true;
+ }
+
+ static void RemoveWhitespaceContext(base::StringPiece* piece1) {
+ base::StringPiece::const_iterator c = piece1->begin();
+ base::StringPiece::const_iterator e = piece1->end();
+ while (c != e && isspace(*c)) {
+ ++c;
+ }
+ if (c == e) {
+ *piece1 = base::StringPiece(c, e-c);
+ return;
+ }
+ --e;
+ while (c != e &&isspace(*e)) {
+ --e;
+ }
+ ++e;
+ *piece1 = base::StringPiece(c, e-c);
+ }
+
+ static bool StartsWithIgnoreCase(const base::StringPiece& text,
+ const base::StringPiece& starts_with) {
+ if (text.size() < starts_with.size())
+ return false;
+ return EqualIgnoreCase(text.substr(0, starts_with.size()), starts_with);
+ }
+};
+struct StringPieceCaseEqual {
+ bool operator()(const base::StringPiece& piece1,
+ const base::StringPiece& piece2) const {
+ return StringPieceUtils::EqualIgnoreCase(piece1, piece2);
+ }
+};
+
+
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_STRING_PIECE_UTILS_H_
+
diff --git a/chromium/net/tools/gdig/file_net_log.cc b/chromium/net/tools/gdig/file_net_log.cc
new file mode 100644
index 00000000000..7c755c855fe
--- /dev/null
+++ b/chromium/net/tools/gdig/file_net_log.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 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.
+
+#include <stdio.h>
+
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "net/tools/gdig/file_net_log.h"
+
+namespace net {
+
+FileNetLogObserver::FileNetLogObserver(FILE* destination)
+ : destination_(destination) {
+ DCHECK(destination != NULL);
+}
+
+FileNetLogObserver::~FileNetLogObserver() {
+}
+
+void FileNetLogObserver::OnAddEntry(const net::NetLog::Entry& entry) {
+ // Only BoundNetLogs without a NetLog should have an invalid source.
+ DCHECK(entry.source().IsValid());
+
+ const char* source = NetLog::SourceTypeToString(entry.source().type);
+ const char* type = NetLog::EventTypeToString(entry.type());
+
+ scoped_ptr<Value> param_value(entry.ParametersToValue());
+ std::string params;
+ if (param_value.get() != NULL) {
+ JSONStringValueSerializer serializer(&params);
+ bool ret = serializer.Serialize(*param_value);
+ DCHECK(ret);
+ }
+ base::Time now = base::Time::NowFromSystemTime();
+ base::AutoLock lock(lock_);
+ if (first_event_time_.is_null()) {
+ first_event_time_ = now;
+ }
+ base::TimeDelta elapsed_time = now - first_event_time_;
+ fprintf(destination_ , "%u\t%u\t%s\t%s\t%s\n",
+ static_cast<unsigned>(elapsed_time.InMilliseconds()),
+ entry.source().id, source, type, params.c_str());
+}
+
+} // namespace net
diff --git a/chromium/net/tools/gdig/file_net_log.h b/chromium/net/tools/gdig/file_net_log.h
new file mode 100644
index 00000000000..0f858bbea3f
--- /dev/null
+++ b/chromium/net/tools/gdig/file_net_log.h
@@ -0,0 +1,37 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_GDIG_FILE_NET_LOG_H_
+#define NET_TOOLS_GDIG_FILE_NET_LOG_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// FileNetLogObserver is a simple implementation of NetLog::ThreadSafeObserver
+// that prints out all the events received into the stream passed
+// to the constructor.
+class FileNetLogObserver : public NetLog::ThreadSafeObserver {
+ public:
+ explicit FileNetLogObserver(FILE* destination);
+ virtual ~FileNetLogObserver();
+
+ // NetLog::ThreadSafeObserver implementation:
+ virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE;
+
+ private:
+ FILE* const destination_;
+ base::Lock lock_;
+
+ base::Time first_event_time_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_GDIG_FILE_NET_LOG_H_
diff --git a/chromium/net/tools/gdig/gdig.cc b/chromium/net/tools/gdig/gdig.cc
new file mode 100644
index 00000000000..0dec8d7aaa0
--- /dev/null
+++ b/chromium/net/tools/gdig/gdig.cc
@@ -0,0 +1,511 @@
+// Copyright (c) 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.
+
+#include <stdio.h>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/cancelable_callback.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/host_cache.h"
+#include "net/dns/host_resolver_impl.h"
+#include "net/tools/gdig/file_net_log.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#endif
+
+namespace net {
+
+namespace {
+
+bool StringToIPEndPoint(const std::string& ip_address_and_port,
+ IPEndPoint* ip_end_point) {
+ DCHECK(ip_end_point);
+
+ std::string ip;
+ int port;
+ if (!ParseHostAndPort(ip_address_and_port, &ip, &port))
+ return false;
+ if (port == -1)
+ port = dns_protocol::kDefaultPort;
+
+ net::IPAddressNumber ip_number;
+ if (!net::ParseIPLiteralToNumber(ip, &ip_number))
+ return false;
+
+ *ip_end_point = net::IPEndPoint(ip_number, port);
+ return true;
+}
+
+// Convert DnsConfig to human readable text omitting the hosts member.
+std::string DnsConfigToString(const DnsConfig& dns_config) {
+ std::string output;
+ output.append("search ");
+ for (size_t i = 0; i < dns_config.search.size(); ++i) {
+ output.append(dns_config.search[i] + " ");
+ }
+ output.append("\n");
+
+ for (size_t i = 0; i < dns_config.nameservers.size(); ++i) {
+ output.append("nameserver ");
+ output.append(dns_config.nameservers[i].ToString()).append("\n");
+ }
+
+ base::StringAppendF(&output, "options ndots:%d\n", dns_config.ndots);
+ base::StringAppendF(&output, "options timeout:%d\n",
+ static_cast<int>(dns_config.timeout.InMilliseconds()));
+ base::StringAppendF(&output, "options attempts:%d\n", dns_config.attempts);
+ if (dns_config.rotate)
+ output.append("options rotate\n");
+ if (dns_config.edns0)
+ output.append("options edns0\n");
+ return output;
+}
+
+// Convert DnsConfig hosts member to a human readable text.
+std::string DnsHostsToString(const DnsHosts& dns_hosts) {
+ std::string output;
+ for (DnsHosts::const_iterator i = dns_hosts.begin();
+ i != dns_hosts.end();
+ ++i) {
+ const DnsHostsKey& key = i->first;
+ std::string host_name = key.first;
+ output.append(IPEndPoint(i->second, -1).ToStringWithoutPort());
+ output.append(" ").append(host_name).append("\n");
+ }
+ return output;
+}
+
+struct ReplayLogEntry {
+ base::TimeDelta start_time;
+ std::string domain_name;
+};
+
+typedef std::vector<ReplayLogEntry> ReplayLog;
+
+// Loads and parses a replay log file and fills |replay_log| with a structured
+// representation. Returns whether the operation was successful. If not, the
+// contents of |replay_log| are undefined.
+//
+// The replay log is a text file where each line contains
+//
+// timestamp_in_milliseconds domain_name
+//
+// The timestamp_in_milliseconds needs to be an integral delta from start of
+// resolution and is in milliseconds. domain_name is the name to be resolved.
+//
+// The file should be sorted by timestamp in ascending time.
+bool LoadReplayLog(const base::FilePath& file_path, ReplayLog* replay_log) {
+ std::string original_replay_log_contents;
+ if (!file_util::ReadFileToString(file_path, &original_replay_log_contents)) {
+ fprintf(stderr, "Unable to open replay file %s\n",
+ file_path.MaybeAsASCII().c_str());
+ return false;
+ }
+
+ // Strip out \r characters for Windows files. This isn't as efficient as a
+ // smarter line splitter, but this particular use does not need to target
+ // efficiency.
+ std::string replay_log_contents;
+ RemoveChars(original_replay_log_contents, "\r", &replay_log_contents);
+
+ std::vector<std::string> lines;
+ base::SplitString(replay_log_contents, '\n', &lines);
+ base::TimeDelta previous_delta;
+ bool bad_parse = false;
+ for (unsigned i = 0; i < lines.size(); ++i) {
+ if (lines[i].empty())
+ continue;
+ std::vector<std::string> time_and_name;
+ base::SplitString(lines[i], ' ', &time_and_name);
+ if (time_and_name.size() != 2) {
+ fprintf(
+ stderr,
+ "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
+ file_path.MaybeAsASCII().c_str(),
+ i + 1);
+ bad_parse = true;
+ continue;
+ }
+
+ int64 delta_in_milliseconds;
+ if (!base::StringToInt64(time_and_name[0], &delta_in_milliseconds)) {
+ fprintf(
+ stderr,
+ "[%s %u] replay log should have format 'timestamp domain_name\\n'\n",
+ file_path.MaybeAsASCII().c_str(),
+ i + 1);
+ bad_parse = true;
+ continue;
+ }
+
+ base::TimeDelta delta =
+ base::TimeDelta::FromMilliseconds(delta_in_milliseconds);
+ if (delta < previous_delta) {
+ fprintf(
+ stderr,
+ "[%s %u] replay log should be sorted by time\n",
+ file_path.MaybeAsASCII().c_str(),
+ i + 1);
+ bad_parse = true;
+ continue;
+ }
+
+ previous_delta = delta;
+ ReplayLogEntry entry;
+ entry.start_time = delta;
+ entry.domain_name = time_and_name[1];
+ replay_log->push_back(entry);
+ }
+ return !bad_parse;
+}
+
+class GDig {
+ public:
+ GDig();
+ ~GDig();
+
+ enum Result {
+ RESULT_NO_RESOLVE = -3,
+ RESULT_NO_CONFIG = -2,
+ RESULT_WRONG_USAGE = -1,
+ RESULT_OK = 0,
+ RESULT_PENDING = 1,
+ };
+
+ Result Main(int argc, const char* argv[]);
+
+ private:
+ bool ParseCommandLine(int argc, const char* argv[]);
+
+ void Start();
+ void Finish(Result);
+
+ void OnDnsConfig(const DnsConfig& dns_config_const);
+ void OnResolveComplete(unsigned index, AddressList* address_list,
+ base::TimeDelta time_since_start, int val);
+ void OnTimeout();
+ void ReplayNextEntry();
+
+ base::TimeDelta config_timeout_;
+ bool print_config_;
+ bool print_hosts_;
+ net::IPEndPoint nameserver_;
+ base::TimeDelta timeout_;
+ int parallellism_;
+ ReplayLog replay_log_;
+ unsigned replay_log_index_;
+ base::Time start_time_;
+ int active_resolves_;
+ Result result_;
+
+ base::CancelableClosure timeout_closure_;
+ scoped_ptr<DnsConfigService> dns_config_service_;
+ scoped_ptr<FileNetLogObserver> log_observer_;
+ scoped_ptr<NetLog> log_;
+ scoped_ptr<HostResolver> resolver_;
+
+#if defined(OS_MACOSX)
+ // Without this there will be a mem leak on osx.
+ base::mac::ScopedNSAutoreleasePool scoped_pool_;
+#endif
+
+ // Need AtExitManager to support AsWeakPtr (in NetLog).
+ base::AtExitManager exit_manager_;
+};
+
+GDig::GDig()
+ : config_timeout_(base::TimeDelta::FromSeconds(5)),
+ print_config_(false),
+ print_hosts_(false),
+ parallellism_(6),
+ replay_log_index_(0u),
+ active_resolves_(0) {
+}
+
+GDig::~GDig() {
+ if (log_)
+ log_->RemoveThreadSafeObserver(log_observer_.get());
+}
+
+GDig::Result GDig::Main(int argc, const char* argv[]) {
+ if (!ParseCommandLine(argc, argv)) {
+ fprintf(stderr,
+ "usage: %s [--net_log[=<basic|no_bytes|all>]]"
+ " [--print_config] [--print_hosts]"
+ " [--nameserver=<ip_address[:port]>]"
+ " [--timeout=<milliseconds>]"
+ " [--config_timeout=<seconds>]"
+ " [--j=<parallel resolves>]"
+ " [--replay_file=<path>]"
+ " [domain_name]\n",
+ argv[0]);
+ return RESULT_WRONG_USAGE;
+ }
+
+ base::MessageLoopForIO loop;
+
+ result_ = RESULT_PENDING;
+ Start();
+ if (result_ == RESULT_PENDING)
+ base::MessageLoop::current()->Run();
+
+ // Destroy it while MessageLoopForIO is alive.
+ dns_config_service_.reset();
+ return result_;
+}
+
+bool GDig::ParseCommandLine(int argc, const char* argv[]) {
+ CommandLine::Init(argc, argv);
+ const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
+
+ if (parsed_command_line.HasSwitch("config_timeout")) {
+ int timeout_seconds = 0;
+ bool parsed = base::StringToInt(
+ parsed_command_line.GetSwitchValueASCII("config_timeout"),
+ &timeout_seconds);
+ if (parsed && timeout_seconds > 0) {
+ config_timeout_ = base::TimeDelta::FromSeconds(timeout_seconds);
+ } else {
+ fprintf(stderr, "Invalid config_timeout parameter\n");
+ return false;
+ }
+ }
+
+ if (parsed_command_line.HasSwitch("net_log")) {
+ std::string log_param = parsed_command_line.GetSwitchValueASCII("net_log");
+ NetLog::LogLevel level = NetLog::LOG_ALL_BUT_BYTES;
+
+ if (log_param.length() > 0) {
+ std::map<std::string, NetLog::LogLevel> log_levels;
+ log_levels["all"] = NetLog::LOG_ALL;
+ log_levels["no_bytes"] = NetLog::LOG_ALL_BUT_BYTES;
+ log_levels["basic"] = NetLog::LOG_BASIC;
+
+ if (log_levels.find(log_param) != log_levels.end()) {
+ level = log_levels[log_param];
+ } else {
+ fprintf(stderr, "Invalid net_log parameter\n");
+ return false;
+ }
+ }
+ log_.reset(new NetLog);
+ log_observer_.reset(new FileNetLogObserver(stderr));
+ log_->AddThreadSafeObserver(log_observer_.get(), level);
+ }
+
+ print_config_ = parsed_command_line.HasSwitch("print_config");
+ print_hosts_ = parsed_command_line.HasSwitch("print_hosts");
+
+ if (parsed_command_line.HasSwitch("nameserver")) {
+ std::string nameserver =
+ parsed_command_line.GetSwitchValueASCII("nameserver");
+ if (!StringToIPEndPoint(nameserver, &nameserver_)) {
+ fprintf(stderr,
+ "Cannot parse the namerserver string into an IPEndPoint\n");
+ return false;
+ }
+ }
+
+ if (parsed_command_line.HasSwitch("timeout")) {
+ int timeout_millis = 0;
+ bool parsed = base::StringToInt(
+ parsed_command_line.GetSwitchValueASCII("timeout"),
+ &timeout_millis);
+ if (parsed && timeout_millis > 0) {
+ timeout_ = base::TimeDelta::FromMilliseconds(timeout_millis);
+ } else {
+ fprintf(stderr, "Invalid timeout parameter\n");
+ return false;
+ }
+ }
+
+ if (parsed_command_line.HasSwitch("replay_file")) {
+ base::FilePath replay_path =
+ parsed_command_line.GetSwitchValuePath("replay_file");
+ if (!LoadReplayLog(replay_path, &replay_log_))
+ return false;
+ }
+
+ if (parsed_command_line.HasSwitch("j")) {
+ int parallellism = 0;
+ bool parsed = base::StringToInt(
+ parsed_command_line.GetSwitchValueASCII("j"),
+ &parallellism);
+ if (parsed && parallellism > 0) {
+ parallellism_ = parallellism;
+ } else {
+ fprintf(stderr, "Invalid parallellism parameter\n");
+ }
+ }
+
+ if (parsed_command_line.GetArgs().size() == 1) {
+ ReplayLogEntry entry;
+ entry.start_time = base::TimeDelta();
+#if defined(OS_WIN)
+ entry.domain_name = WideToASCII(parsed_command_line.GetArgs()[0]);
+#else
+ entry.domain_name = parsed_command_line.GetArgs()[0];
+#endif
+ replay_log_.push_back(entry);
+ } else if (parsed_command_line.GetArgs().size() != 0) {
+ return false;
+ }
+ return print_config_ || print_hosts_ || !replay_log_.empty();
+}
+
+void GDig::Start() {
+ if (nameserver_.address().size() > 0) {
+ DnsConfig dns_config;
+ dns_config.attempts = 1;
+ dns_config.nameservers.push_back(nameserver_);
+ OnDnsConfig(dns_config);
+ } else {
+ dns_config_service_ = DnsConfigService::CreateSystemService();
+ dns_config_service_->ReadConfig(base::Bind(&GDig::OnDnsConfig,
+ base::Unretained(this)));
+ timeout_closure_.Reset(base::Bind(&GDig::OnTimeout,
+ base::Unretained(this)));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, timeout_closure_.callback(), config_timeout_);
+ }
+}
+
+void GDig::Finish(Result result) {
+ DCHECK_NE(RESULT_PENDING, result);
+ result_ = result;
+ if (base::MessageLoop::current())
+ base::MessageLoop::current()->Quit();
+}
+
+void GDig::OnDnsConfig(const DnsConfig& dns_config_const) {
+ timeout_closure_.Cancel();
+ DCHECK(dns_config_const.IsValid());
+ DnsConfig dns_config = dns_config_const;
+
+ if (timeout_.InMilliseconds() > 0)
+ dns_config.timeout = timeout_;
+ if (print_config_) {
+ printf("# Dns Configuration\n"
+ "%s", DnsConfigToString(dns_config).c_str());
+ }
+ if (print_hosts_) {
+ printf("# Host Database\n"
+ "%s", DnsHostsToString(dns_config.hosts).c_str());
+ }
+
+ if (replay_log_.empty()) {
+ Finish(RESULT_OK);
+ return;
+ }
+
+ scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
+ dns_client->SetConfig(dns_config);
+ scoped_ptr<HostResolverImpl> resolver(
+ new HostResolverImpl(
+ HostCache::CreateDefaultCache(),
+ PrioritizedDispatcher::Limits(NUM_PRIORITIES, parallellism_),
+ HostResolverImpl::ProcTaskParams(NULL, 1),
+ log_.get()));
+ resolver->SetDnsClient(dns_client.Pass());
+ resolver_ = resolver.Pass();
+
+ start_time_ = base::Time::Now();
+
+ ReplayNextEntry();
+}
+
+void GDig::ReplayNextEntry() {
+ DCHECK_LT(replay_log_index_, replay_log_.size());
+
+ base::TimeDelta time_since_start = base::Time::Now() - start_time_;
+ while (replay_log_index_ < replay_log_.size()) {
+ const ReplayLogEntry& entry = replay_log_[replay_log_index_];
+ if (time_since_start < entry.start_time) {
+ // Delay call to next time and return.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&GDig::ReplayNextEntry, base::Unretained(this)),
+ entry.start_time - time_since_start);
+ return;
+ }
+
+ HostResolver::RequestInfo info(HostPortPair(entry.domain_name.c_str(), 80));
+ AddressList* addrlist = new AddressList();
+ unsigned current_index = replay_log_index_;
+ CompletionCallback callback = base::Bind(&GDig::OnResolveComplete,
+ base::Unretained(this),
+ current_index,
+ base::Owned(addrlist),
+ time_since_start);
+ ++active_resolves_;
+ ++replay_log_index_;
+ int ret = resolver_->Resolve(
+ info, addrlist, callback, NULL,
+ BoundNetLog::Make(log_.get(), net::NetLog::SOURCE_NONE));
+ if (ret != ERR_IO_PENDING)
+ callback.Run(ret);
+ }
+}
+
+void GDig::OnResolveComplete(unsigned entry_index,
+ AddressList* address_list,
+ base::TimeDelta resolve_start_time,
+ int val) {
+ DCHECK_GT(active_resolves_, 0);
+ DCHECK(address_list);
+ DCHECK_LT(entry_index, replay_log_.size());
+ --active_resolves_;
+ base::TimeDelta resolve_end_time = base::Time::Now() - start_time_;
+ base::TimeDelta resolve_time = resolve_end_time - resolve_start_time;
+ printf("%u %d %d %s %d ",
+ entry_index,
+ static_cast<int>(resolve_end_time.InMilliseconds()),
+ static_cast<int>(resolve_time.InMilliseconds()),
+ replay_log_[entry_index].domain_name.c_str(), val);
+ if (val != OK) {
+ printf("%s", ErrorToString(val));
+ } else {
+ for (size_t i = 0; i < address_list->size(); ++i) {
+ if (i != 0)
+ printf(" ");
+ printf("%s", (*address_list)[i].ToStringWithoutPort().c_str());
+ }
+ }
+ printf("\n");
+ if (active_resolves_ == 0 && replay_log_index_ >= replay_log_.size())
+ Finish(RESULT_OK);
+}
+
+void GDig::OnTimeout() {
+ fprintf(stderr, "Timed out waiting to load the dns config\n");
+ Finish(RESULT_NO_CONFIG);
+}
+
+} // empty namespace
+
+} // namespace net
+
+int main(int argc, const char* argv[]) {
+ net::GDig dig;
+ return dig.Main(argc, argv);
+}
diff --git a/chromium/net/tools/get_server_time/get_server_time.cc b/chromium/net/tools/get_server_time/get_server_time.cc
new file mode 100644
index 00000000000..3fe6129b66c
--- /dev/null
+++ b/chromium/net/tools/get_server_time/get_server_time.cc
@@ -0,0 +1,354 @@
+// Copyright (c) 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.
+
+// This is a small utility that snarfs the server time from the
+// response headers of an http/https HEAD request and compares it to
+// the local time.
+//
+// TODO(akalin): Also snarf the server time from the TLS handshake, if
+// any (http://crbug.com/146090).
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/i18n/time_formatting.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#elif defined(OS_LINUX)
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#endif
+
+namespace {
+
+// base::TimeTicks::Now() is documented to have a resolution of
+// ~1-15ms.
+const int64 kTicksResolutionMs = 15;
+
+// For the sources that are supported (HTTP date headers, TLS
+// handshake), the resolution of the server time is 1 second.
+const int64 kServerTimeResolutionMs = 1000;
+
+// Assume base::Time::Now() has the same resolution as
+// base::TimeTicks::Now().
+//
+// TODO(akalin): Figure out the real resolution.
+const int64 kTimeResolutionMs = kTicksResolutionMs;
+
+// Simply quits the current message loop when finished. Used to make
+// URLFetcher synchronous.
+class QuitDelegate : public net::URLFetcherDelegate {
+ public:
+ QuitDelegate() {}
+
+ virtual ~QuitDelegate() {}
+
+ // net::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE {
+ base::MessageLoop::current()->Quit();
+ }
+
+ virtual void OnURLFetchDownloadProgress(
+ const net::URLFetcher* source,
+ int64 current, int64 total) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual void OnURLFetchDownloadData(
+ const net::URLFetcher* source,
+ scoped_ptr<std::string> download_data) OVERRIDE{
+ NOTREACHED();
+ }
+
+ virtual bool ShouldSendDownloadData() OVERRIDE {
+ NOTREACHED();
+ return false;
+ }
+
+ virtual void OnURLFetchUploadProgress(const net::URLFetcher* source,
+ int64 current, int64 total) OVERRIDE {
+ NOTREACHED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuitDelegate);
+};
+
+// NetLog::ThreadSafeObserver implementation that simply prints events
+// to the logs.
+class PrintingLogObserver : public net::NetLog::ThreadSafeObserver {
+ public:
+ PrintingLogObserver() {}
+
+ virtual ~PrintingLogObserver() {
+ // This is guaranteed to be safe as this program is single threaded.
+ net_log()->RemoveThreadSafeObserver(this);
+ }
+
+ // NetLog::ThreadSafeObserver implementation:
+ virtual void OnAddEntry(const net::NetLog::Entry& entry) OVERRIDE {
+ // The log level of the entry is unknown, so just assume it maps
+ // to VLOG(1).
+ if (!VLOG_IS_ON(1))
+ return;
+
+ const char* const source_type =
+ net::NetLog::SourceTypeToString(entry.source().type);
+ const char* const event_type =
+ net::NetLog::EventTypeToString(entry.type());
+ const char* const event_phase =
+ net::NetLog::EventPhaseToString(entry.phase());
+ scoped_ptr<base::Value> params(entry.ParametersToValue());
+ std::string params_str;
+ if (params.get()) {
+ base::JSONWriter::Write(params.get(), &params_str);
+ params_str.insert(0, ": ");
+ }
+
+ VLOG(1) << source_type << "(" << entry.source().id << "): "
+ << event_type << ": " << event_phase << params_str;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PrintingLogObserver);
+};
+
+// Builds a URLRequestContext assuming there's only a single loop.
+scoped_ptr<net::URLRequestContext>
+BuildURLRequestContext(net::NetLog* net_log) {
+ net::URLRequestContextBuilder builder;
+#if defined(OS_LINUX)
+ // On Linux, use a fixed ProxyConfigService, since the default one
+ // depends on glib.
+ //
+ // TODO(akalin): Remove this once http://crbug.com/146421 is fixed.
+ builder.set_proxy_config_service(
+ new net::ProxyConfigServiceFixed(net::ProxyConfig()));
+#endif
+ scoped_ptr<net::URLRequestContext> context(builder.Build());
+ context->set_net_log(net_log);
+ return context.Pass();
+}
+
+class SingleThreadRequestContextGetter : public net::URLRequestContextGetter {
+ public:
+ // Since there's only a single thread, there's no need to worry
+ // about when |context_| gets created.
+ SingleThreadRequestContextGetter(
+ net::NetLog* net_log,
+ const scoped_refptr<base::SingleThreadTaskRunner>& main_task_runner)
+ : context_(BuildURLRequestContext(net_log)),
+ main_task_runner_(main_task_runner) {}
+
+ virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE {
+ return context_.get();
+ }
+
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const OVERRIDE {
+ return main_task_runner_;
+ }
+
+ private:
+ virtual ~SingleThreadRequestContextGetter() {}
+
+ const scoped_ptr<net::URLRequestContext> context_;
+ const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+};
+
+// Assuming that the time |server_time| was received from a server,
+// that the request for the server was started on |start_ticks|, and
+// that it ended on |end_ticks|, fills |server_now| with an estimate
+// of the current time and |server_now_uncertainty| with a
+// conservative estimate of the uncertainty.
+void EstimateServerTimeNow(base::Time server_time,
+ base::TimeTicks start_ticks,
+ base::TimeTicks end_ticks,
+ base::Time* server_now,
+ base::TimeDelta* server_now_uncertainty) {
+ const base::TimeDelta delta_ticks = end_ticks - start_ticks;
+ const base::TimeTicks mid_ticks = start_ticks + delta_ticks / 2;
+ const base::TimeDelta estimated_elapsed = base::TimeTicks::Now() - mid_ticks;
+
+ *server_now = server_time + estimated_elapsed;
+
+ *server_now_uncertainty =
+ base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs) +
+ delta_ticks + 3 * base::TimeDelta::FromMilliseconds(kTicksResolutionMs);
+}
+
+// Assuming that the time of the server is |server_now| with
+// uncertainty |server_now_uncertainty| and that the local time is
+// |now|, fills |skew| with the skew of the local clock (i.e., add
+// |*skew| to a client time to get a server time) and
+// |skew_uncertainty| with a conservative estimate of the uncertainty.
+void EstimateSkew(base::Time server_now,
+ base::TimeDelta server_now_uncertainty,
+ base::Time now,
+ base::TimeDelta now_uncertainty,
+ base::TimeDelta* skew,
+ base::TimeDelta* skew_uncertainty) {
+ *skew = server_now - now;
+ *skew_uncertainty = server_now_uncertainty + now_uncertainty;
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+#if defined(OS_MACOSX)
+ base::mac::ScopedNSAutoreleasePool pool;
+#endif
+
+ base::AtExitManager exit_manager;
+ CommandLine::Init(argc, argv);
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+
+ const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
+ GURL url(parsed_command_line.GetSwitchValueASCII("url"));
+ if (!url.is_valid() ||
+ (url.scheme() != "http" && url.scheme() != "https")) {
+ std::fprintf(
+ stderr,
+ "Usage: %s --url=[http|https]://www.example.com [--v=[1|2]]\n",
+ argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ base::MessageLoopForIO main_loop;
+
+ // NOTE: A NetworkChangeNotifier could be instantiated here, but
+ // that interferes with the request that will be sent; some
+ // implementations always send out an OnIPAddressChanged() message,
+ // which causes the DNS resolution to abort. It's simpler to just
+ // not instantiate one, since only a single request is sent anyway.
+
+ // The declaration order for net_log and printing_log_observer is
+ // important. The destructor of PrintingLogObserver removes itself
+ // from net_log, so net_log must be available for entire lifetime of
+ // printing_log_observer.
+ net::NetLog net_log;
+ PrintingLogObserver printing_log_observer;
+ net_log.AddThreadSafeObserver(&printing_log_observer, net::NetLog::LOG_ALL);
+ scoped_refptr<SingleThreadRequestContextGetter> context_getter(
+ new SingleThreadRequestContextGetter(&net_log,
+ main_loop.message_loop_proxy()));
+
+ QuitDelegate delegate;
+ scoped_ptr<net::URLFetcher> fetcher(
+ net::URLFetcher::Create(url, net::URLFetcher::HEAD, &delegate));
+ fetcher->SetRequestContext(context_getter.get());
+
+ const base::Time start_time = base::Time::Now();
+ const base::TimeTicks start_ticks = base::TimeTicks::Now();
+
+ fetcher->Start();
+ std::printf(
+ "Request started at %s (ticks = %" PRId64 ")\n",
+ UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(start_time)).c_str(),
+ start_ticks.ToInternalValue());
+
+ // |delegate| quits |main_loop| when the request is done.
+ main_loop.Run();
+
+ const base::Time end_time = base::Time::Now();
+ const base::TimeTicks end_ticks = base::TimeTicks::Now();
+
+ std::printf(
+ "Request ended at %s (ticks = %" PRId64 ")\n",
+ UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(end_time)).c_str(),
+ end_ticks.ToInternalValue());
+
+ const int64 delta_ticks_internal =
+ end_ticks.ToInternalValue() - start_ticks.ToInternalValue();
+ const base::TimeDelta delta_ticks = end_ticks - start_ticks;
+
+ std::printf(
+ "Request took %" PRId64 " ticks (%.2f ms)\n",
+ delta_ticks_internal, delta_ticks.InMillisecondsF());
+
+ const net::URLRequestStatus status = fetcher->GetStatus();
+ if (status.status() != net::URLRequestStatus::SUCCESS) {
+ LOG(ERROR) << "Request failed with error code: "
+ << net::ErrorToString(status.error());
+ return EXIT_FAILURE;
+ }
+
+ const net::HttpResponseHeaders* const headers =
+ fetcher->GetResponseHeaders();
+ if (!headers) {
+ LOG(ERROR) << "Response does not have any headers";
+ return EXIT_FAILURE;
+ }
+
+ void* iter = NULL;
+ std::string date_header;
+ while (headers->EnumerateHeader(&iter, "Date", &date_header)) {
+ std::printf("Got date header: %s\n", date_header.c_str());
+ }
+
+ base::Time server_time;
+ if (!headers->GetDateValue(&server_time)) {
+ LOG(ERROR) << "Could not parse time from server response headers";
+ return EXIT_FAILURE;
+ }
+
+ std::printf(
+ "Got time %s from server\n",
+ UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_time)).c_str());
+
+ base::Time server_now;
+ base::TimeDelta server_now_uncertainty;
+ EstimateServerTimeNow(server_time, start_ticks, end_ticks,
+ &server_now, &server_now_uncertainty);
+ base::Time now = base::Time::Now();
+
+ std::printf(
+ "According to the server, it is now %s with uncertainty %.2f ms\n",
+ UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_now)).c_str(),
+ server_now_uncertainty.InMillisecondsF());
+
+ base::TimeDelta skew;
+ base::TimeDelta skew_uncertainty;
+ EstimateSkew(server_now, server_now_uncertainty, now,
+ base::TimeDelta::FromMilliseconds(kTimeResolutionMs),
+ &skew, &skew_uncertainty);
+
+ std::printf(
+ "An estimate for the local clock skew is %.2f ms with "
+ "uncertainty %.2f ms\n",
+ skew.InMillisecondsF(),
+ skew_uncertainty.InMillisecondsF());
+
+ return EXIT_SUCCESS;
+}
diff --git a/chromium/net/tools/net_watcher/net_watcher.cc b/chromium/net/tools/net_watcher/net_watcher.cc
new file mode 100644
index 00000000000..0a8b4dfbb7d
--- /dev/null
+++ b/chromium/net/tools/net_watcher/net_watcher.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 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.
+
+// This is a small utility that watches for and logs network changes.
+
+#include <string>
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "net/base/network_change_notifier.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_service.h"
+
+#if (defined(OS_LINUX) || defined(OS_OPENBSD)) && !defined(OS_CHROMEOS)
+#include <glib-object.h>
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#endif
+
+namespace {
+
+// Conversions from various network-related types to string.
+
+const char* ConnectionTypeToString(
+ net::NetworkChangeNotifier::ConnectionType type) {
+ switch (type) {
+ case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+ return "CONNECTION_UNKNOWN";
+ case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+ return "CONNECTION_ETHERNET";
+ case net::NetworkChangeNotifier::CONNECTION_WIFI:
+ return "CONNECTION_WIFI";
+ case net::NetworkChangeNotifier::CONNECTION_2G:
+ return "CONNECTION_2G";
+ case net::NetworkChangeNotifier::CONNECTION_3G:
+ return "CONNECTION_3G";
+ case net::NetworkChangeNotifier::CONNECTION_4G:
+ return "CONNECTION_4G";
+ case net::NetworkChangeNotifier::CONNECTION_NONE:
+ return "CONNECTION_NONE";
+ default:
+ return "CONNECTION_UNEXPECTED";
+ }
+}
+
+std::string ProxyConfigToString(const net::ProxyConfig& config) {
+ scoped_ptr<base::Value> config_value(config.ToValue());
+ std::string str;
+ base::JSONWriter::Write(config_value.get(), &str);
+ return str;
+}
+
+const char* ConfigAvailabilityToString(
+ net::ProxyConfigService::ConfigAvailability availability) {
+ switch (availability) {
+ case net::ProxyConfigService::CONFIG_PENDING:
+ return "CONFIG_PENDING";
+ case net::ProxyConfigService::CONFIG_VALID:
+ return "CONFIG_VALID";
+ case net::ProxyConfigService::CONFIG_UNSET:
+ return "CONFIG_UNSET";
+ default:
+ return "CONFIG_UNEXPECTED";
+ }
+}
+
+// The main observer class that logs network events.
+class NetWatcher :
+ public net::NetworkChangeNotifier::IPAddressObserver,
+ public net::NetworkChangeNotifier::ConnectionTypeObserver,
+ public net::NetworkChangeNotifier::DNSObserver,
+ public net::NetworkChangeNotifier::NetworkChangeObserver,
+ public net::ProxyConfigService::Observer {
+ public:
+ NetWatcher() {}
+
+ virtual ~NetWatcher() {}
+
+ // net::NetworkChangeNotifier::IPAddressObserver implementation.
+ virtual void OnIPAddressChanged() OVERRIDE {
+ LOG(INFO) << "OnIPAddressChanged()";
+ }
+
+ // net::NetworkChangeNotifier::ConnectionTypeObserver implementation.
+ virtual void OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::ConnectionType type) OVERRIDE {
+ LOG(INFO) << "OnConnectionTypeChanged("
+ << ConnectionTypeToString(type) << ")";
+ }
+
+ // net::NetworkChangeNotifier::DNSObserver implementation.
+ virtual void OnDNSChanged() OVERRIDE {
+ LOG(INFO) << "OnDNSChanged()";
+ }
+
+ // net::NetworkChangeNotifier::NetworkChangeObserver implementation.
+ virtual void OnNetworkChanged(
+ net::NetworkChangeNotifier::ConnectionType type) OVERRIDE {
+ LOG(INFO) << "OnNetworkChanged("
+ << ConnectionTypeToString(type) << ")";
+ }
+
+ // net::ProxyConfigService::Observer implementation.
+ virtual void OnProxyConfigChanged(
+ const net::ProxyConfig& config,
+ net::ProxyConfigService::ConfigAvailability availability) OVERRIDE {
+ LOG(INFO) << "OnProxyConfigChanged("
+ << ProxyConfigToString(config) << ", "
+ << ConfigAvailabilityToString(availability) << ")";
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetWatcher);
+};
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+#if defined(OS_MACOSX)
+ base::mac::ScopedNSAutoreleasePool pool;
+#endif
+#if (defined(OS_LINUX) || defined(OS_OPENBSD)) && !defined(OS_CHROMEOS)
+ // Needed so ProxyConfigServiceLinux can use gconf.
+ // Normally handled by BrowserMainLoop::InitializeToolkit().
+ // From glib version 2.36 onwards, g_type_init is implicitly called and it is
+ // deprecated.
+ // TODO(yael) Simplify this once Ubuntu 10.04 is no longer supported.
+#if defined(G_GNUC_BEGIN_IGNORE_DEPRECATIONS) && \
+ defined(G_GNUC_END_IGNORE_DEPRECATIONS)
+#define USE_GLIB_DEPRECATIONS_MACROS
+#endif
+
+#if defined(USE_GLIB_DEPRECATIONS_MACROS)
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+#endif
+ g_type_init();
+#if defined(USE_GLIB_DEPRECATIONS_MACROS)
+G_GNUC_END_IGNORE_DEPRECATIONS
+#endif
+#undef USE_GLIB_DEPRECATIONS_MACROS
+#endif // (defined(OS_LINUX) || defined(OS_OPENBSD)) && !defined(OS_CHROMEOS)
+ base::AtExitManager exit_manager;
+ CommandLine::Init(argc, argv);
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ logging::InitLogging(settings);
+
+ // Just make the main message loop the network loop.
+ base::MessageLoopForIO network_loop;
+
+ NetWatcher net_watcher;
+
+ scoped_ptr<net::NetworkChangeNotifier> network_change_notifier(
+ net::NetworkChangeNotifier::Create());
+
+ // Use the network loop as the file loop also.
+ scoped_ptr<net::ProxyConfigService> proxy_config_service(
+ net::ProxyService::CreateSystemProxyConfigService(
+ network_loop.message_loop_proxy().get(), &network_loop));
+
+ // Uses |network_change_notifier|.
+ net::NetworkChangeNotifier::AddIPAddressObserver(&net_watcher);
+ net::NetworkChangeNotifier::AddConnectionTypeObserver(&net_watcher);
+ net::NetworkChangeNotifier::AddDNSObserver(&net_watcher);
+ net::NetworkChangeNotifier::AddNetworkChangeObserver(&net_watcher);
+
+ proxy_config_service->AddObserver(&net_watcher);
+
+ LOG(INFO) << "Initial connection type: "
+ << ConnectionTypeToString(
+ network_change_notifier->GetCurrentConnectionType());
+
+ {
+ net::ProxyConfig config;
+ const net::ProxyConfigService::ConfigAvailability availability =
+ proxy_config_service->GetLatestProxyConfig(&config);
+ LOG(INFO) << "Initial proxy config: "
+ << ProxyConfigToString(config) << ", "
+ << ConfigAvailabilityToString(availability);
+ }
+
+ LOG(INFO) << "Watching for network events...";
+
+ // Start watching for events.
+ network_loop.Run();
+
+ proxy_config_service->RemoveObserver(&net_watcher);
+
+ // Uses |network_change_notifier|.
+ net::NetworkChangeNotifier::RemoveDNSObserver(&net_watcher);
+ net::NetworkChangeNotifier::RemoveConnectionTypeObserver(&net_watcher);
+ net::NetworkChangeNotifier::RemoveIPAddressObserver(&net_watcher);
+ net::NetworkChangeNotifier::RemoveNetworkChangeObserver(&net_watcher);
+
+ return 0;
+}
diff --git a/chromium/net/tools/quic/benchmark/run_client.py b/chromium/net/tools/quic/benchmark/run_client.py
new file mode 100755
index 00000000000..7df37ab7fa0
--- /dev/null
+++ b/chromium/net/tools/quic/benchmark/run_client.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+
+# Copyright 2013 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.
+
+import csv
+import datetime
+import json
+import os
+import shlex
+import subprocess
+import sys
+from optparse import OptionParser
+
+"""Start a client to fetch web pages either using wget or using quic_client.
+If --use_wget is set, it uses wget.
+Usage: This invocation
+ run_client.py --quic_binary_dir=../../../../out/Debug \
+ --address=127.0.0.1 --port=5000 --infile=test_urls.json \
+ --delay_file=delay.csv --packets_file=packets.csv
+ fetches pages listed in test_urls.json from a quic server running at
+ 127.0.0.1 on port 5000 using quic binary ../../../../out/Debug/quic_client
+ and stores the delay in delay.csv and the max received packet number (for
+ QUIC) in packets.csv.
+ If --use_wget is present, it will fetch the URLs using wget and ignores
+ the flags --address, --port, --quic_binary_dir, etc.
+"""
+
+def Timestamp(datetm=None):
+ """Get the timestamp in microseconds.
+ Args:
+ datetm: the date and time to be converted to timestamp.
+ If not set, use the current UTC time.
+ Returns:
+ The timestamp in microseconds.
+ """
+ datetm = datetm or datetime.datetime.utcnow()
+ diff = datetm - datetime.datetime.utcfromtimestamp(0)
+ timestamp = (diff.days * 86400 + diff.seconds) * 1000000 + diff.microseconds
+ return timestamp
+
+class PageloadExperiment:
+ def __init__(self, use_wget, quic_binary_dir, quic_server_address,
+ quic_server_port):
+ """Initialize PageloadExperiment.
+
+ Args:
+ use_wget: Whether to use wget.
+ quic_binary_dir: Directory for quic_binary.
+ quic_server_address: IP address of quic server.
+ quic_server_port: Port of the quic server.
+ """
+ self.use_wget = use_wget
+ self.quic_binary_dir = quic_binary_dir
+ self.quic_server_address = quic_server_address
+ self.quic_server_port = quic_server_port
+ if not use_wget and not os.path.isfile(quic_binary_dir + '/quic_client'):
+ raise IOError('There is no quic_client in the given dir: %s.'
+ % quic_binary_dir)
+
+ @classmethod
+ def ReadPages(cls, json_file):
+ """Return the list of URLs from the json_file.
+
+ One entry of the list may contain a html link and multiple resources.
+ """
+ page_list = []
+ with open(json_file) as f:
+ data = json.load(f)
+ for page in data['pages']:
+ url = page['url']
+ if 'resources' in page:
+ resources = page['resources']
+ else:
+ resources = None
+ if not resources:
+ page_list.append([url])
+ else:
+ urls = [url]
+ # For url http://x.com/z/y.html, url_dir is http://x.com/z
+ url_dir = url.rsplit('/', 1)[0]
+ for resource in resources:
+ urls.append(url_dir + '/' + resource)
+ page_list.append(urls)
+ return page_list
+
+ def DownloadOnePage(self, urls):
+ """Download a page emulated by a list of urls.
+
+ Args:
+ urls: list of URLs to fetch.
+ Returns:
+ A tuple (page download time, max packet number).
+ """
+ if self.use_wget:
+ cmd = 'wget -O -'
+ else:
+ cmd = '%s/quic_client --port=%s --address=%s' % (
+ self.quic_binary_dir, self.quic_server_port, self.quic_server_address)
+ cmd_in_list = shlex.split(cmd)
+ cmd_in_list.extend(urls)
+ start_time = Timestamp()
+ ps_proc = subprocess.Popen(cmd_in_list,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ _std_out, std_err = ps_proc.communicate()
+ end_time = Timestamp()
+ delta_time = end_time - start_time
+ max_packets = 0
+ if not self.use_wget:
+ for line in std_err.splitlines():
+ if line.find('Client: Got packet') >= 0:
+ elems = line.split()
+ packet_num = int(elems[4])
+ max_packets = max(max_packets, packet_num)
+ return delta_time, max_packets
+
+ def RunExperiment(self, infile, delay_file, packets_file=None, num_it=1):
+ """Run the pageload experiment.
+
+ Args:
+ infile: Input json file describing the page list.
+ delay_file: Output file storing delay in csv format.
+ packets_file: Output file storing max packet number in csv format.
+ num_it: Number of iterations to run in this experiment.
+ """
+ page_list = self.ReadPages(infile)
+ header = [urls[0].rsplit('/', 1)[1] for urls in page_list]
+ header0 = 'wget' if self.use_wget else 'quic'
+ header = [header0] + header
+
+ plt_list = []
+ packets_list = []
+ for i in range(num_it):
+ plt_one_row = [str(i)]
+ packets_one_row = [str(i)]
+ for urls in page_list:
+ time_micros, num_packets = self.DownloadOnePage(urls)
+ time_secs = time_micros / 1000000.0
+ plt_one_row.append('%6.3f' % time_secs)
+ packets_one_row.append('%5d' % num_packets)
+ plt_list.append(plt_one_row)
+ packets_list.append(packets_one_row)
+
+ with open(delay_file, 'w') as f:
+ csv_writer = csv.writer(f, delimiter=',')
+ csv_writer.writerow(header)
+ for one_row in plt_list:
+ csv_writer.writerow(one_row)
+ if packets_file:
+ with open(packets_file, 'w') as f:
+ csv_writer = csv.writer(f, delimiter=',')
+ csv_writer.writerow(header)
+ for one_row in packets_list:
+ csv_writer.writerow(one_row)
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option('--use_wget', dest='use_wget', action='store_true',
+ default=False)
+ # Note that only debug version generates the log containing packets
+ # information.
+ parser.add_option('--quic_binary_dir', dest='quic_binary_dir',
+ default='../../../../out/Debug')
+ # For whatever server address you specify, you need to run the
+ # quic_server on that machine and populate it with the cache containing
+ # the URLs requested in the --infile.
+ parser.add_option('--address', dest='quic_server_address',
+ default='127.0.0.1')
+ parser.add_option('--port', dest='quic_server_port',
+ default='5002')
+ parser.add_option('--delay_file', dest='delay_file', default='delay.csv')
+ parser.add_option('--packets_file', dest='packets_file',
+ default='packets.csv')
+ parser.add_option('--infile', dest='infile', default='test_urls.json')
+ (options, _) = parser.parse_args()
+
+ exp = PageloadExperiment(options.use_wget, options.quic_binary_dir,
+ options.quic_server_address,
+ options.quic_server_port)
+ exp.RunExperiment(options.infile, options.delay_file, options.packets_file)
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/chromium/net/tools/quic/benchmark/test_urls.json b/chromium/net/tools/quic/benchmark/test_urls.json
new file mode 100644
index 00000000000..79c20cfe3d1
--- /dev/null
+++ b/chromium/net/tools/quic/benchmark/test_urls.json
@@ -0,0 +1,41 @@
+{
+ "description": "List of pages (URLs) for measuring quic performance using standalone quic client and server. These URLs are for testing only and are provided without guarantee.",
+ "pages": [
+ {
+ "url": "http://dev1.mdw.la/test/warmup.html",
+ "why": "A warmup page."
+ },
+ {
+ "url": "http://dev1.mdw.la/test/test_1KB.jpg",
+ "why": "A tiny page, about 1K Bytes."
+ },
+ {
+ "url": "http://dev1.mdw.la/test/test_10KB.jpg",
+ "why": "A small page, about 10K Bytes."
+ },
+ {
+ "url": "http://dev1.mdw.la/test/test_100KB.jpg",
+ "why": "A medium page, about 100K Bytes."
+ },
+ {
+ "url": "http://dev1.mdw.la/test/test_1MB.jpg",
+ "why": "A large page, about 1M Bytes."
+ },
+ {
+ "url": "http://dev1.mdw.la/test/ten_img.html",
+ "why": "A large page, with 1 html and 10 images totaling about 1M Bytes.",
+ "resources": [
+ "imgs/test_100KB_0.jpg",
+ "imgs/test_100KB_1.jpg",
+ "imgs/test_100KB_2.jpg",
+ "imgs/test_100KB_3.jpg",
+ "imgs/test_100KB_4.jpg",
+ "imgs/test_100KB_5.jpg",
+ "imgs/test_100KB_6.jpg",
+ "imgs/test_100KB_7.jpg",
+ "imgs/test_100KB_8.jpg",
+ "imgs/test_100KB_9.jpg"
+ ]
+ }
+ ]
+}
diff --git a/chromium/net/tools/quic/end_to_end_test.cc b/chromium/net/tools/quic/end_to_end_test.cc
new file mode 100644
index 00000000000..3903df965ee
--- /dev/null
+++ b/chromium/net/tools/quic/end_to_end_test.cc
@@ -0,0 +1,651 @@
+// Copyright (c) 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.
+
+#include <stddef.h>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/crypto/null_encrypter.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_session_peer.h"
+#include "net/quic/test_tools/reliable_quic_stream_peer.h"
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_server.h"
+#include "net/tools/quic/quic_socket_utils.h"
+#include "net/tools/quic/test_tools/http_message_test_utils.h"
+#include "net/tools/quic/test_tools/quic_client_peer.h"
+#include "net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h"
+#include "net/tools/quic/test_tools/quic_test_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using base::WaitableEvent;
+using net::test::QuicConnectionPeer;
+using net::test::QuicSessionPeer;
+using net::test::ReliableQuicStreamPeer;
+using std::string;
+
+namespace net {
+namespace tools {
+namespace test {
+namespace {
+
+const char* kFooResponseBody = "Artichoke hearts make me happy.";
+const char* kBarResponseBody = "Palm hearts are pretty delicious, also.";
+const size_t kCongestionFeedbackFrameSize = 25;
+// If kCongestionFeedbackFrameSize increase we need to expand this string
+// accordingly.
+const char* kLargeRequest =
+ "https://www.google.com/foo/test/a/request/string/longer/than/25/bytes";
+
+void GenerateBody(string* body, int length) {
+ body->clear();
+ body->reserve(length);
+ for (int i = 0; i < length; ++i) {
+ body->append(1, static_cast<char>(32 + i % (126 - 32)));
+ }
+}
+
+
+// Simple wrapper class to run server in a thread.
+class ServerThread : public base::SimpleThread {
+ public:
+ explicit ServerThread(IPEndPoint address, const QuicConfig& config)
+ : SimpleThread("server_thread"),
+ listening_(true, false),
+ quit_(true, false),
+ server_(config),
+ address_(address),
+ port_(0) {
+ }
+ virtual ~ServerThread() {
+ }
+
+ virtual void Run() OVERRIDE {
+ server_.Listen(address_);
+
+ port_lock_.Acquire();
+ port_ = server_.port();
+ port_lock_.Release();
+
+ listening_.Signal();
+ while (!quit_.IsSignaled()) {
+ server_.WaitForEvents();
+ }
+ server_.Shutdown();
+ }
+
+ int GetPort() {
+ port_lock_.Acquire();
+ int rc = port_;
+ port_lock_.Release();
+ return rc;
+ }
+
+ WaitableEvent* listening() { return &listening_; }
+ WaitableEvent* quit() { return &quit_; }
+
+ private:
+ WaitableEvent listening_;
+ WaitableEvent quit_;
+ base::Lock port_lock_;
+ QuicServer server_;
+ IPEndPoint address_;
+ int port_;
+
+ DISALLOW_COPY_AND_ASSIGN(ServerThread);
+};
+
+class EndToEndTest : public ::testing::TestWithParam<QuicVersion> {
+ public:
+ static void SetUpTestCase() {
+ QuicInMemoryCache::GetInstance()->ResetForTests();
+ }
+
+ protected:
+ EndToEndTest()
+ : server_hostname_("example.com"),
+ server_started_(false) {
+ net::IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &ip));
+ server_address_ = IPEndPoint(ip, 0);
+ client_config_.SetDefaults();
+ server_config_.SetDefaults();
+
+ AddToCache("GET", kLargeRequest, "HTTP/1.1", "200", "OK", kFooResponseBody);
+ AddToCache("GET", "https://www.google.com/foo",
+ "HTTP/1.1", "200", "OK", kFooResponseBody);
+ AddToCache("GET", "https://www.google.com/bar",
+ "HTTP/1.1", "200", "OK", kBarResponseBody);
+ version_ = GetParam();
+ }
+
+ virtual QuicTestClient* CreateQuicClient() {
+ QuicTestClient* client = new QuicTestClient(server_address_,
+ server_hostname_,
+ false, // not secure
+ client_config_,
+ version_);
+ client->Connect();
+ return client;
+ }
+
+ virtual bool Initialize() {
+ // Start the server first, because CreateQuicClient() attempts
+ // to connect to the server.
+ StartServer();
+ client_.reset(CreateQuicClient());
+ return client_->client()->connected();
+ }
+
+ virtual void TearDown() {
+ StopServer();
+ }
+
+ void StartServer() {
+ server_thread_.reset(new ServerThread(server_address_, server_config_));
+ server_thread_->Start();
+ server_thread_->listening()->Wait();
+ server_address_ = IPEndPoint(server_address_.address(),
+ server_thread_->GetPort());
+ server_started_ = true;
+ }
+
+ void StopServer() {
+ if (!server_started_)
+ return;
+ if (server_thread_.get()) {
+ server_thread_->quit()->Signal();
+ server_thread_->Join();
+ }
+ }
+
+ void AddToCache(const StringPiece& method,
+ const StringPiece& path,
+ const StringPiece& version,
+ const StringPiece& response_code,
+ const StringPiece& response_detail,
+ const StringPiece& body) {
+ BalsaHeaders request_headers, response_headers;
+ request_headers.SetRequestFirstlineFromStringPieces(method,
+ path,
+ version);
+ response_headers.SetRequestFirstlineFromStringPieces(version,
+ response_code,
+ response_detail);
+ response_headers.AppendHeader("content-length",
+ base::IntToString(body.length()));
+
+ // Check if response already exists and matches.
+ QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance();
+ const QuicInMemoryCache::Response* cached_response =
+ cache->GetResponse(request_headers);
+ if (cached_response != NULL) {
+ string cached_response_headers_str, response_headers_str;
+ cached_response->headers().DumpToString(&cached_response_headers_str);
+ response_headers.DumpToString(&response_headers_str);
+ CHECK_EQ(cached_response_headers_str, response_headers_str);
+ CHECK_EQ(cached_response->body(), body);
+ return;
+ }
+ cache->AddResponse(request_headers, response_headers, body);
+ }
+
+ IPEndPoint server_address_;
+ string server_hostname_;
+ scoped_ptr<ServerThread> server_thread_;
+ scoped_ptr<QuicTestClient> client_;
+ bool server_started_;
+ QuicConfig client_config_;
+ QuicConfig server_config_;
+ QuicVersion version_;
+};
+
+// Run all end to end tests with all supported versions.
+INSTANTIATE_TEST_CASE_P(EndToEndTests,
+ EndToEndTest,
+ ::testing::ValuesIn(kSupportedQuicVersions));
+
+TEST_P(EndToEndTest, SimpleRequestResponse) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+// TODO(rch): figure out how to detect missing v6 supprt (like on the linux
+// try bots) and selectively disable this test.
+TEST_P(EndToEndTest, DISABLED_SimpleRequestResponsev6) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("::1", &ip));
+ server_address_ = IPEndPoint(ip, server_address_.port());
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, SeparateFinPacket) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.set_has_complete_message(false);
+
+ client_->SendMessage(request);
+
+ client_->SendData(string(), true);
+
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+
+ request.AddBody("foo", true);
+
+ client_->SendMessage(request);
+ client_->SendData(string(), true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, MultipleRequestResponse) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, MultipleClients) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+ scoped_ptr<QuicTestClient> client2(CreateQuicClient());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_has_complete_message(false);
+
+ client_->SendMessage(request);
+ client2->SendMessage(request);
+
+ client_->SendData("bar", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client_->response_body());
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+
+ client2->SendData("eep", true);
+ client2->WaitForResponse();
+ EXPECT_EQ(kFooResponseBody, client2->response_body());
+ EXPECT_EQ(200u, client2->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, RequestOverMultiplePackets) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+ // Set things up so we have a small payload, to guarantee fragmentation.
+ // A congestion feedback frame can't be split into multiple packets, make sure
+ // that our packet have room for at least this amount after the normal headers
+ // are added.
+
+ // TODO(rch) handle this better when we have different encryption options.
+ const size_t kStreamDataLength = 3;
+ const QuicStreamId kStreamId = 1u;
+ const QuicStreamOffset kStreamOffset = 0u;
+ size_t stream_payload_size =
+ QuicFramer::GetMinStreamFrameSize(
+ GetParam(), kStreamId, kStreamOffset, true) + kStreamDataLength;
+ size_t min_payload_size =
+ std::max(kCongestionFeedbackFrameSize, stream_payload_size);
+ size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size);
+ // TODO(satyashekhar): Fix this when versioning is implemented.
+ client_->options()->max_packet_length =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) +
+ ciphertext_size;
+
+ // Make sure our request is too large to fit in one packet.
+ EXPECT_GT(strlen(kLargeRequest), min_payload_size);
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, MultipleFramesRandomOrder) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+ // Set things up so we have a small payload, to guarantee fragmentation.
+ // A congestion feedback frame can't be split into multiple packets, make sure
+ // that our packet have room for at least this amount after the normal headers
+ // are added.
+
+ // TODO(rch) handle this better when we have different encryption options.
+ const size_t kStreamDataLength = 3;
+ const QuicStreamId kStreamId = 1u;
+ const QuicStreamOffset kStreamOffset = 0u;
+ size_t stream_payload_size =
+ QuicFramer::GetMinStreamFrameSize(
+ GetParam(), kStreamId, kStreamOffset, true) + kStreamDataLength;
+ size_t min_payload_size =
+ std::max(kCongestionFeedbackFrameSize, stream_payload_size);
+ size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size);
+ // TODO(satyashekhar): Fix this when versioning is implemented.
+ client_->options()->max_packet_length =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) +
+ ciphertext_size;
+ client_->options()->random_reorder = true;
+
+ // Make sure our request is too large to fit in one packet.
+ EXPECT_GT(strlen(kLargeRequest), min_payload_size);
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, PostMissingBytes) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ // Add a content length header with no body.
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_skip_message_validation(true);
+
+ // This should be detected as stream fin without complete request,
+ // triggering an error response.
+ client_->SendCustomSynchronousRequest(request);
+ EXPECT_EQ("bad", client_->response_body());
+ EXPECT_EQ(500u, client_->response_headers()->parsed_response_code());
+}
+
+TEST_P(EndToEndTest, LargePost) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ // Connect with lower fake packet loss than we'd like to test. Until
+ // b/10126687 is fixed, losing handshake packets is pretty brutal.
+ // FLAGS_fake_packet_loss_percentage = 5;
+ ASSERT_TRUE(Initialize());
+
+ // Wait for the server SHLO before upping the packet loss.
+ client_->client()->WaitForCryptoHandshakeConfirmed();
+ // FLAGS_fake_packet_loss_percentage = 30;
+
+ string body;
+ GenerateBody(&body, 10240);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+
+ EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
+}
+
+// TODO(ianswett): Enable once b/9295090 is fixed.
+TEST_P(EndToEndTest, DISABLED_LargePostFEC) {
+ // FLAGS_fake_packet_loss_percentage = 30;
+ ASSERT_TRUE(Initialize());
+ client_->options()->max_packets_per_fec_group = 6;
+
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ // FLAGS_fake_packet_loss_percentage = 30;
+ ASSERT_TRUE(Initialize());
+ client_->options()->max_packets_per_fec_group = 6;
+
+ string body;
+ GenerateBody(&body, 10240);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+
+ EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request));
+}
+
+/*TEST_P(EndToEndTest, PacketTooLarge) {
+ FLAGS_quic_allow_oversized_packets_for_test = true;
+ ASSERT_TRUE(Initialize());
+
+ string body;
+ GenerateBody(&body, kMaxPacketSize);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+ client_->options()->max_packet_length = 20480;
+
+ EXPECT_EQ("", client_->SendCustomSynchronousRequest(request));
+ EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_PACKET_TOO_LARGE, client_->connection_error());
+}*/
+
+TEST_P(EndToEndTest, InvalidStream) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ string body;
+ GenerateBody(&body, kMaxPacketSize);
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddBody(body, true);
+ // Force the client to write with a stream ID belonging to a nonexistent
+ // server-side stream.
+ QuicSessionPeer::SetNextStreamId(client_->client()->session(), 2);
+
+ client_->SendCustomSynchronousRequest(request);
+// EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_PACKET_FOR_NONEXISTENT_STREAM, client_->connection_error());
+}
+
+// TODO(rch): this test seems to cause net_unittests timeouts :|
+TEST_P(EndToEndTest, DISABLED_MultipleTermination) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+ scoped_ptr<QuicTestClient> client2(CreateQuicClient());
+
+ HTTPMessage request(HttpConstants::HTTP_1_1,
+ HttpConstants::POST, "/foo");
+ request.AddHeader("content-length", "3");
+ request.set_has_complete_message(false);
+
+ // Set the offset so we won't frame. Otherwise when we pick up termination
+ // before HTTP framing is complete, we send an error and close the stream,
+ // and the second write is picked up as writing on a closed stream.
+ QuicReliableClientStream* stream = client_->GetOrCreateStream();
+ ASSERT_TRUE(stream != NULL);
+ ReliableQuicStreamPeer::SetStreamBytesWritten(3, stream);
+
+ client_->SendData("bar", true);
+
+ // By default the stream protects itself from writes after terminte is set.
+ // Override this to test the server handling buggy clients.
+ ReliableQuicStreamPeer::SetWriteSideClosed(
+ false, client_->GetOrCreateStream());
+
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+ EXPECT_DEBUG_DEATH({
+ client_->SendData("eep", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(QUIC_MULTIPLE_TERMINATION_OFFSETS, client_->stream_error());
+ },
+ "Check failed: !fin_buffered_");
+#else
+ EXPECT_DEATH({
+ client_->SendData("eep", true);
+ client_->WaitForResponse();
+ EXPECT_EQ(QUIC_MULTIPLE_TERMINATION_OFFSETS, client_->stream_error());
+ },
+ "Check failed: !fin_buffered_");
+#endif
+#endif
+}
+
+TEST_P(EndToEndTest, Timeout) {
+ client_config_.set_idle_connection_state_lifetime(
+ QuicTime::Delta::FromMicroseconds(500),
+ QuicTime::Delta::FromMicroseconds(500));
+ // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake:
+ // that's enough to validate timeout in this case.
+ Initialize();
+ while (client_->client()->connected()) {
+ client_->client()->WaitForEvents();
+ }
+}
+
+TEST_P(EndToEndTest, LimitMaxOpenStreams) {
+ // Server limits the number of max streams to 2.
+ server_config_.set_max_streams_per_connection(2, 2);
+ // Client tries to negotiate for 10.
+ client_config_.set_max_streams_per_connection(10, 5);
+
+ ASSERT_TRUE(Initialize());
+ client_->client()->WaitForCryptoHandshakeConfirmed();
+ QuicConfig* client_negotiated_config = client_->client()->session()->config();
+ EXPECT_EQ(2u, client_negotiated_config->max_streams_per_connection());
+}
+
+TEST_P(EndToEndTest, ResetConnection) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+ client_->ResetConnection();
+ EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+}
+
+class WrongAddressWriter : public QuicPacketWriter {
+ public:
+ explicit WrongAddressWriter(int fd) : fd_(fd) {
+ IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("127.0.0.2", &ip));
+ self_address_ = IPEndPoint(ip, 0);
+ }
+
+ virtual int WritePacket(const char* buffer, size_t buf_len,
+ const IPAddressNumber& real_self_address,
+ const IPEndPoint& peer_address,
+ QuicBlockedWriterInterface* blocked_writer,
+ int* error) OVERRIDE {
+ return QuicSocketUtils::WritePacket(fd_, buffer, buf_len,
+ self_address_.address(), peer_address,
+ error);
+ }
+
+ IPEndPoint self_address_;
+ int fd_;
+};
+
+TEST_P(EndToEndTest, ConnectionMigration) {
+ // TODO(rtenneti): Delete this when NSS is supported.
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ ASSERT_TRUE(Initialize());
+
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ(200u, client_->response_headers()->parsed_response_code());
+
+ WrongAddressWriter writer(QuicClientPeer::GetFd(client_->client()));
+ QuicEpollConnectionHelper* helper =
+ reinterpret_cast<QuicEpollConnectionHelper*>(
+ QuicConnectionPeer::GetHelper(
+ client_->client()->session()->connection()));
+ QuicEpollConnectionHelperPeer::SetWriter(helper, &writer);
+
+ client_->SendSynchronousRequest("/bar");
+ QuicEpollConnectionHelperPeer::SetWriter(helper, NULL);
+
+ EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error());
+ EXPECT_EQ(QUIC_ERROR_MIGRATING_ADDRESS, client_->connection_error());
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_client.cc b/chromium/net/tools/quic/quic_client.cc
new file mode 100644
index 00000000000..86fa6c48b75
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client.cc
@@ -0,0 +1,289 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_client.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+#include "net/tools/quic/quic_socket_utils.h"
+
+#ifndef SO_RXQ_OVFL
+#define SO_RXQ_OVFL 40
+#endif
+
+namespace net {
+namespace tools {
+
+const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET;
+
+QuicClient::QuicClient(IPEndPoint server_address,
+ const string& server_hostname,
+ const QuicVersion version)
+ : server_address_(server_address),
+ server_hostname_(server_hostname),
+ local_port_(0),
+ fd_(-1),
+ initialized_(false),
+ packets_dropped_(0),
+ overflow_supported_(false),
+ version_(version) {
+ config_.SetDefaults();
+}
+
+QuicClient::QuicClient(IPEndPoint server_address,
+ const string& server_hostname,
+ const QuicConfig& config,
+ const QuicVersion version)
+ : server_address_(server_address),
+ server_hostname_(server_hostname),
+ config_(config),
+ local_port_(0),
+ fd_(-1),
+ initialized_(false),
+ packets_dropped_(0),
+ overflow_supported_(false),
+ version_(version) {
+}
+
+QuicClient::~QuicClient() {
+ if (connected()) {
+ session()->connection()->SendConnectionClosePacket(
+ QUIC_PEER_GOING_AWAY, "");
+ }
+}
+
+bool QuicClient::Initialize() {
+ DCHECK(!initialized_);
+
+ epoll_server_.set_timeout_in_us(50 * 1000);
+ crypto_config_.SetDefaults();
+
+ int address_family = server_address_.GetSockAddrFamily();
+ fd_ = socket(address_family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
+ if (fd_ < 0) {
+ LOG(ERROR) << "CreateSocket() failed: " << strerror(errno);
+ return false;
+ }
+
+ int get_overflow = 1;
+ int rc = setsockopt(fd_, SOL_SOCKET, SO_RXQ_OVFL, &get_overflow,
+ sizeof(get_overflow));
+ if (rc < 0) {
+ DLOG(WARNING) << "Socket overflow detection not supported";
+ } else {
+ overflow_supported_ = true;
+ }
+
+ int get_local_ip = 1;
+ if (address_family == AF_INET) {
+ rc = setsockopt(fd_, IPPROTO_IP, IP_PKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ } else {
+ rc = setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ }
+
+ if (rc < 0) {
+ LOG(ERROR) << "IP detection not supported" << strerror(errno);
+ return false;
+ }
+
+ if (bind_to_address_.size() != 0) {
+ client_address_ = IPEndPoint(bind_to_address_, local_port_);
+ } else if (address_family == AF_INET) {
+ IPAddressNumber any4;
+ CHECK(net::ParseIPLiteralToNumber("0.0.0.0", &any4));
+ client_address_ = IPEndPoint(any4, local_port_);
+ } else {
+ IPAddressNumber any6;
+ CHECK(net::ParseIPLiteralToNumber("::", &any6));
+ client_address_ = IPEndPoint(any6, local_port_);
+ }
+
+ sockaddr_storage raw_addr;
+ socklen_t raw_addr_len = sizeof(raw_addr);
+ CHECK(client_address_.ToSockAddr(reinterpret_cast<sockaddr*>(&raw_addr),
+ &raw_addr_len));
+ rc = bind(fd_,
+ reinterpret_cast<const sockaddr*>(&raw_addr),
+ sizeof(raw_addr));
+ if (rc < 0) {
+ LOG(ERROR) << "Bind failed: " << strerror(errno);
+ return false;
+ }
+
+ SockaddrStorage storage;
+ if (getsockname(fd_, storage.addr, &storage.addr_len) != 0 ||
+ !client_address_.FromSockAddr(storage.addr, storage.addr_len)) {
+ LOG(ERROR) << "Unable to get self address. Error: " << strerror(errno);
+ }
+
+ epoll_server_.RegisterFD(fd_, this, kEpollFlags);
+ initialized_ = true;
+ return true;
+}
+
+bool QuicClient::Connect() {
+ if (!StartConnect()) {
+ return false;
+ }
+ while (EncryptionBeingEstablished()) {
+ WaitForEvents();
+ }
+ return session_->connection()->connected();
+}
+
+bool QuicClient::StartConnect() {
+ DCHECK(!connected() && initialized_);
+
+ QuicGuid guid = QuicRandom::GetInstance()->RandUint64();
+ session_.reset(new QuicClientSession(
+ server_hostname_,
+ config_,
+ new QuicConnection(guid, server_address_,
+ CreateQuicConnectionHelper(), false,
+ version_),
+ &crypto_config_));
+ return session_->CryptoConnect();
+}
+
+bool QuicClient::EncryptionBeingEstablished() {
+ return !session_->IsEncryptionEstablished() &&
+ session_->connection()->connected();
+}
+
+void QuicClient::Disconnect() {
+ DCHECK(connected());
+
+ session()->connection()->SendConnectionClose(QUIC_PEER_GOING_AWAY);
+ epoll_server_.UnregisterFD(fd_);
+ close(fd_);
+ fd_ = -1;
+ initialized_ = false;
+}
+
+void QuicClient::SendRequestsAndWaitForResponse(
+ const CommandLine::StringVector& args) {
+ for (uint32_t i = 0; i < args.size(); i++) {
+ BalsaHeaders headers;
+ headers.SetRequestFirstlineFromStringPieces("GET", args[i], "HTTP/1.1");
+ CreateReliableClientStream()->SendRequest(headers, "", true);
+ }
+
+ while (WaitForEvents()) { }
+}
+
+QuicReliableClientStream* QuicClient::CreateReliableClientStream() {
+ if (!connected()) {
+ return NULL;
+ }
+
+ return session_->CreateOutgoingReliableStream();
+}
+
+void QuicClient::WaitForStreamToClose(QuicStreamId id) {
+ DCHECK(connected());
+
+ while (!session_->IsClosedStream(id)) {
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ }
+}
+
+void QuicClient::WaitForCryptoHandshakeConfirmed() {
+ DCHECK(connected());
+
+ while (!session_->IsCryptoHandshakeConfirmed()) {
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ }
+}
+
+bool QuicClient::WaitForEvents() {
+ DCHECK(connected());
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ return session_->num_active_requests() != 0;
+}
+
+void QuicClient::OnEvent(int fd, EpollEvent* event) {
+ DCHECK_EQ(fd, fd_);
+
+ if (event->in_events & EPOLLIN) {
+ while (connected() && ReadAndProcessPacket()) {
+ }
+ }
+ if (connected() && (event->in_events & EPOLLOUT)) {
+ session_->connection()->OnCanWrite();
+ }
+ if (event->in_events & EPOLLERR) {
+ DLOG(INFO) << "Epollerr";
+ }
+}
+
+QuicPacketCreator::Options* QuicClient::options() {
+ if (session() == NULL) {
+ return NULL;
+ }
+ return session_->options();
+}
+
+bool QuicClient::connected() const {
+ return session_.get() && session_->connection() &&
+ session_->connection()->connected();
+}
+
+QuicEpollConnectionHelper* QuicClient::CreateQuicConnectionHelper() {
+ return new QuicEpollConnectionHelper(fd_, &epoll_server_);
+}
+
+bool QuicClient::ReadAndProcessPacket() {
+ // Allocate some extra space so we can send an error if the server goes over
+ // the limit.
+ char buf[2 * kMaxPacketSize];
+
+ IPEndPoint server_address;
+ IPAddressNumber client_ip;
+
+ int bytes_read = QuicSocketUtils::ReadPacket(
+ fd_, buf, arraysize(buf), overflow_supported_ ? &packets_dropped_ : NULL,
+ &client_ip, &server_address);
+
+ if (bytes_read < 0) {
+ return false;
+ }
+
+ QuicEncryptedPacket packet(buf, bytes_read, false);
+ QuicGuid our_guid = session_->connection()->guid();
+ QuicGuid packet_guid;
+
+ if (!QuicFramer::ReadGuidFromPacket(packet, &packet_guid)) {
+ DLOG(INFO) << "Could not read GUID from packet";
+ return true;
+ }
+ if (packet_guid != our_guid) {
+ DLOG(INFO) << "Ignoring packet from unexpected GUID: "
+ << packet_guid << " instead of " << our_guid;
+ return true;
+ }
+
+ IPEndPoint client_address(client_ip, client_address_.port());
+ session_->connection()->ProcessUdpPacket(
+ client_address, server_address, packet);
+ return true;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_client.h b/chromium/net/tools/quic/quic_client.h
new file mode 100644
index 00000000000..ca20a8d2158
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client.h
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+//
+// A toy client, which connects to a specified port and sends QUIC
+// request to that endpoint.
+
+#ifndef NET_TOOLS_QUIC_QUIC_CLIENT_H_
+#define NET_TOOLS_QUIC_QUIC_CLIENT_H_
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_client_session.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+class ProofVerifier;
+
+namespace tools {
+
+class QuicEpollConnectionHelper;
+
+namespace test {
+class QuicClientPeer;
+} // namespace test
+
+class QuicClient : public EpollCallbackInterface {
+ public:
+ QuicClient(IPEndPoint server_address, const std::string& server_hostname,
+ const QuicVersion version);
+ QuicClient(IPEndPoint server_address,
+ const std::string& server_hostname,
+ const QuicConfig& config,
+ const QuicVersion version);
+
+ virtual ~QuicClient();
+
+ // Initializes the client to create a connection. Should be called exactly
+ // once before calling StartConnect or Connect. Returns true if the
+ // initialization succeeds, false otherwise.
+ bool Initialize();
+
+ // "Connect" to the QUIC server, including performing synchronous crypto
+ // handshake.
+ bool Connect();
+
+ // Start the crypto handshake. This can be done in place of the synchronous
+ // Connect(), but callers are responsible for making sure the crypto handshake
+ // completes.
+ bool StartConnect();
+
+ // Returns true if the crypto handshake has yet to establish encryption.
+ // Returns false if encryption is active (even if the server hasn't confirmed
+ // the handshake) or if the connection has been closed.
+ bool EncryptionBeingEstablished();
+
+ // Disconnects from the QUIC server.
+ void Disconnect();
+
+ // Sends a request simple GET for each URL in arg, and then waits for
+ // each to complete.
+ void SendRequestsAndWaitForResponse(const CommandLine::StringVector& args);
+
+ // Returns a newly created CreateReliableClientStream, owned by the
+ // QuicClient.
+ QuicReliableClientStream* CreateReliableClientStream();
+
+ // Wait for events until the stream with the given ID is closed.
+ void WaitForStreamToClose(QuicStreamId id);
+
+ // Wait for events until the handshake is confirmed.
+ void WaitForCryptoHandshakeConfirmed();
+
+ // Wait up to 50ms, and handle any events which occur.
+ // Returns true if there are any outstanding requests.
+ bool WaitForEvents();
+
+ // From EpollCallbackInterface
+ virtual void OnRegistration(
+ EpollServer* eps, int fd, int event_mask) OVERRIDE {}
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ // |fd_| can be unregistered without the client being disconnected. This
+ // happens in b3m QuicProber where we unregister |fd_| to feed in events to
+ // the client from the SelectServer.
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {}
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {}
+
+ QuicPacketCreator::Options* options();
+
+ QuicClientSession* session() { return session_.get(); }
+
+ bool connected() const;
+
+ int packets_dropped() { return packets_dropped_; }
+
+ void set_bind_to_address(IPAddressNumber address) {
+ bind_to_address_ = address;
+ }
+
+ IPAddressNumber bind_to_address() const { return bind_to_address_; }
+
+ void set_local_port(int local_port) { local_port_ = local_port; }
+
+ int local_port() { return local_port_; }
+
+ const IPEndPoint& server_address() const { return server_address_; }
+
+ const IPEndPoint& client_address() const { return client_address_; }
+
+ EpollServer* epoll_server() { return &epoll_server_; }
+
+ int fd() { return fd_; }
+
+ // This should only be set before the initial Connect()
+ void set_server_hostname(const string& hostname) {
+ server_hostname_ = hostname;
+ }
+
+ // SetProofVerifier sets the ProofVerifier that will be used to verify the
+ // server's certificate and takes ownership of |verifier|.
+ void SetProofVerifier(ProofVerifier* verifier) {
+ // TODO(rtenneti): We should set ProofVerifier in QuicClientSession.
+ crypto_config_.SetProofVerifier(verifier);
+ }
+
+ // SetChannelIDSigner sets a ChannelIDSigner that will be called when the
+ // server supports channel IDs to sign a message proving possession of the
+ // given ChannelID. This object takes ownership of |signer|.
+ void SetChannelIDSigner(ChannelIDSigner* signer) {
+ crypto_config_.SetChannelIDSigner(signer);
+ }
+
+ protected:
+ virtual QuicEpollConnectionHelper* CreateQuicConnectionHelper();
+
+ private:
+ friend class net::tools::test::QuicClientPeer;
+
+ // Read a UDP packet and hand it to the framer.
+ bool ReadAndProcessPacket();
+
+ // Set of streams created (and owned) by this client
+ base::hash_set<QuicReliableClientStream*> streams_;
+
+ // Address of the server.
+ const IPEndPoint server_address_;
+
+ // Hostname of the server. This may be a DNS name or an IP address literal.
+ std::string server_hostname_;
+
+ // config_ and crypto_config_ contain configuration and cached state about
+ // servers.
+ QuicConfig config_;
+ QuicCryptoClientConfig crypto_config_;
+
+ // Address of the client if the client is connected to the server.
+ IPEndPoint client_address_;
+
+ // If initialized, the address to bind to.
+ IPAddressNumber bind_to_address_;
+ // Local port to bind to. Initialize to 0.
+ int local_port_;
+
+ // Session which manages streams.
+ scoped_ptr<QuicClientSession> session_;
+ // Listens for events on the client socket.
+ EpollServer epoll_server_;
+ // UDP socket.
+ int fd_;
+
+ // Tracks if the client is initialized to connect.
+ bool initialized_;
+
+ // If overflow_supported_ is true, this will be the number of packets dropped
+ // during the lifetime of the server. This may overflow if enough packets
+ // are dropped.
+ int packets_dropped_;
+
+ // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped
+ // because the socket would otherwise overflow.
+ bool overflow_supported_;
+
+ // Which QUIC version does this client talk?
+ QuicVersion version_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicClient);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_CLIENT_H_
diff --git a/chromium/net/tools/quic/quic_client_bin.cc b/chromium/net/tools/quic/quic_client_bin.cc
new file mode 100644
index 00000000000..e13bea5e8c1
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client_bin.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+
+// A binary wrapper for QuicClient. Connects to --hostname or --address on
+// --port and requests URLs specified on the command line.
+//
+// For example:
+// quic_client --port=6122 /index.html /favicon.ico
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/quic/quic_client.h"
+
+int32 FLAGS_port = 6121;
+std::string FLAGS_address = "127.0.0.1";
+std::string FLAGS_hostname = "localhost";
+
+int main(int argc, char *argv[]) {
+ CommandLine::Init(argc, argv);
+ CommandLine* line = CommandLine::ForCurrentProcess();
+ if (line->HasSwitch("port")) {
+ int port;
+ if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) {
+ FLAGS_port = port;
+ }
+ }
+ if (line->HasSwitch("address")) {
+ FLAGS_address = line->GetSwitchValueASCII("address");
+ }
+ if (line->HasSwitch("hostname")) {
+ FLAGS_hostname = line->GetSwitchValueASCII("hostname");
+ }
+ LOG(INFO) << "server port: " << FLAGS_port
+ << " address: " << FLAGS_address
+ << " hostname: " << FLAGS_hostname;
+
+ base::AtExitManager exit_manager;
+
+ net::IPAddressNumber addr;
+ CHECK(net::ParseIPLiteralToNumber(FLAGS_address, &addr));
+ // TODO(rjshade): Set version on command line.
+ net::tools::QuicClient client(
+ net::IPEndPoint(addr, FLAGS_port), FLAGS_hostname, net::QuicVersionMax());
+
+ client.Initialize();
+
+ if (!client.Connect()) return 1;
+
+ client.SendRequestsAndWaitForResponse(line->GetArgs());
+ return 0;
+}
diff --git a/chromium/net/tools/quic/quic_client_session.cc b/chromium/net/tools/quic/quic_client_session.cc
new file mode 100644
index 00000000000..c41df6702b5
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client_session.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_client_session.h"
+
+#include "base/logging.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+#include "net/tools/quic/quic_spdy_client_stream.h"
+
+using std::string;
+
+namespace net {
+namespace tools {
+
+QuicClientSession::QuicClientSession(
+ const string& server_hostname,
+ const QuicConfig& config,
+ QuicConnection* connection,
+ QuicCryptoClientConfig* crypto_config)
+ : QuicSession(connection, config, false),
+ crypto_stream_(server_hostname, this, crypto_config) {
+}
+
+QuicClientSession::~QuicClientSession() {
+}
+
+QuicReliableClientStream* QuicClientSession::CreateOutgoingReliableStream() {
+ if (!crypto_stream_.encryption_established()) {
+ DLOG(INFO) << "Encryption not active so no outgoing stream created.";
+ return NULL;
+ }
+ if (GetNumOpenStreams() >= get_max_open_streams()) {
+ DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already " << GetNumOpenStreams() << " open.";
+ return NULL;
+ }
+ if (goaway_received()) {
+ DLOG(INFO) << "Failed to create a new outgoing stream. "
+ << "Already received goaway.";
+ return NULL;
+ }
+ QuicReliableClientStream* stream
+ = new QuicSpdyClientStream(GetNextStreamId(), this);
+ ActivateStream(stream);
+ return stream;
+}
+
+QuicCryptoClientStream* QuicClientSession::GetCryptoStream() {
+ return &crypto_stream_;
+}
+
+bool QuicClientSession::CryptoConnect() {
+ return crypto_stream_.CryptoConnect();
+}
+
+ReliableQuicStream* QuicClientSession::CreateIncomingReliableStream(
+ QuicStreamId id) {
+ DLOG(ERROR) << "Server push not supported";
+ return NULL;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_client_session.h b/chromium/net/tools/quic/quic_client_session.h
new file mode 100644
index 00000000000..f51aeeaff1b
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client_session.h
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+//
+// A client specific QuicSession subclass.
+
+#ifndef NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_
+#define NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_
+
+#include <string>
+
+#include "net/quic/quic_crypto_client_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+class QuicConnection;
+class ReliableQuicStream;
+
+namespace tools {
+
+class QuicReliableClientStream;
+
+class QuicClientSession : public QuicSession {
+ public:
+ QuicClientSession(const std::string& server_hostname,
+ const QuicConfig& config,
+ QuicConnection* connection,
+ QuicCryptoClientConfig* crypto_config);
+ virtual ~QuicClientSession();
+
+ // QuicSession methods:
+ virtual QuicReliableClientStream* CreateOutgoingReliableStream() OVERRIDE;
+ virtual QuicCryptoClientStream* GetCryptoStream() OVERRIDE;
+
+ // Performs a crypto handshake with the server. Returns true if the crypto
+ // handshake is started successfully.
+ bool CryptoConnect();
+
+ protected:
+ // QuicSession methods:
+ virtual ReliableQuicStream* CreateIncomingReliableStream(
+ QuicStreamId id) OVERRIDE;
+
+ private:
+ QuicCryptoClientStream crypto_stream_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicClientSession);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_
diff --git a/chromium/net/tools/quic/quic_client_session_test.cc b/chromium/net/tools/quic/quic_client_session_test.cc
new file mode 100644
index 00000000000..1b1ece6597e
--- /dev/null
+++ b/chromium/net/tools/quic/quic_client_session_test.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_client_session.h"
+
+#include <vector>
+
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/aes_128_gcm_12_encrypter.h"
+#include "net/quic/test_tools/crypto_test_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using net::test::CryptoTestUtils;
+using net::test::PacketSavingConnection;
+
+namespace net {
+namespace tools {
+namespace test {
+namespace {
+
+const char kServerHostname[] = "www.example.com";
+
+class ToolsQuicClientSessionTest : public ::testing::Test {
+ protected:
+ ToolsQuicClientSessionTest()
+ : guid_(1),
+ connection_(new PacketSavingConnection(guid_, IPEndPoint(), false)) {
+ crypto_config_.SetDefaults();
+ session_.reset(new QuicClientSession(kServerHostname, QuicConfig(),
+ connection_, &crypto_config_));
+ session_->config()->SetDefaults();
+ }
+
+ void CompleteCryptoHandshake() {
+ ASSERT_TRUE(session_->CryptoConnect());
+ CryptoTestUtils::HandshakeWithFakeServer(
+ connection_, session_->GetCryptoStream());
+ }
+
+ QuicGuid guid_;
+ PacketSavingConnection* connection_;
+ scoped_ptr<QuicClientSession> session_;
+ QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(ToolsQuicClientSessionTest, CryptoConnect) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+ CompleteCryptoHandshake();
+}
+
+TEST_F(ToolsQuicClientSessionTest, MaxNumStreams) {
+ session_->config()->set_max_streams_per_connection(1, 1);
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+ // FLAGS_max_streams_per_connection = 1;
+ // Initialize crypto before the client session will create a stream.
+ CompleteCryptoHandshake();
+
+ QuicReliableClientStream* stream =
+ session_->CreateOutgoingReliableStream();
+ ASSERT_TRUE(stream);
+ EXPECT_FALSE(session_->CreateOutgoingReliableStream());
+
+ // Close a stream and ensure I can now open a new one.
+ session_->CloseStream(stream->id());
+ stream = session_->CreateOutgoingReliableStream();
+ EXPECT_TRUE(stream);
+}
+
+TEST_F(ToolsQuicClientSessionTest, GoAwayReceived) {
+ if (!Aes128Gcm12Encrypter::IsSupported()) {
+ LOG(INFO) << "AES GCM not supported. Test skipped.";
+ return;
+ }
+
+ CompleteCryptoHandshake();
+
+ // After receiving a GoAway, I should no longer be able to create outgoing
+ // streams.
+ session_->OnGoAway(QuicGoAwayFrame(QUIC_PEER_GOING_AWAY, 1u, "Going away."));
+ EXPECT_EQ(NULL, session_->CreateOutgoingReliableStream());
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_dispatcher.cc b/chromium/net/tools/quic/quic_dispatcher.cc
new file mode 100644
index 00000000000..68691f7dc58
--- /dev/null
+++ b/chromium/net/tools/quic/quic_dispatcher.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_dispatcher.h"
+
+#include <errno.h>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_utils.h"
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+#include "net/tools/quic/quic_socket_utils.h"
+
+namespace net {
+namespace tools {
+
+using std::make_pair;
+
+class DeleteSessionsAlarm : public EpollAlarm {
+ public:
+ explicit DeleteSessionsAlarm(QuicDispatcher* dispatcher)
+ : dispatcher_(dispatcher) {
+ }
+
+ virtual int64 OnAlarm() OVERRIDE {
+ EpollAlarm::OnAlarm();
+ dispatcher_->DeleteSessions();
+ return 0;
+ }
+
+ private:
+ QuicDispatcher* dispatcher_;
+};
+
+QuicDispatcher::QuicDispatcher(const QuicConfig& config,
+ const QuicCryptoServerConfig& crypto_config,
+ int fd,
+ EpollServer* epoll_server)
+ : config_(config),
+ crypto_config_(crypto_config),
+ time_wait_list_manager_(
+ new QuicTimeWaitListManager(this, epoll_server)),
+ delete_sessions_alarm_(new DeleteSessionsAlarm(this)),
+ epoll_server_(epoll_server),
+ fd_(fd),
+ write_blocked_(false) {
+}
+
+QuicDispatcher::~QuicDispatcher() {
+ STLDeleteValues(&session_map_);
+ STLDeleteElements(&closed_session_list_);
+}
+
+int QuicDispatcher::WritePacket(const char* buffer, size_t buf_len,
+ const IPAddressNumber& self_address,
+ const IPEndPoint& peer_address,
+ QuicBlockedWriterInterface* writer,
+ int* error) {
+ if (write_blocked_) {
+ write_blocked_list_.AddBlockedObject(writer);
+ *error = EAGAIN;
+ return -1;
+ }
+
+ int rc = QuicSocketUtils::WritePacket(fd_, buffer, buf_len,
+ self_address, peer_address,
+ error);
+ if (rc == -1 && (*error == EWOULDBLOCK || *error == EAGAIN)) {
+ write_blocked_list_.AddBlockedObject(writer);
+ write_blocked_ = true;
+ }
+ return rc;
+}
+
+void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet) {
+ QuicSession* session;
+
+ SessionMap::iterator it = session_map_.find(guid);
+ if (it == session_map_.end()) {
+ if (time_wait_list_manager_->IsGuidInTimeWait(guid)) {
+ time_wait_list_manager_->ProcessPacket(server_address,
+ client_address,
+ guid,
+ packet);
+ return;
+ }
+ session = CreateQuicSession(guid, client_address, fd_, epoll_server_);
+
+ if (session == NULL) {
+ DLOG(INFO) << "Failed to create session for " << guid;
+ // Add this guid fo the time-wait state, to safely nack future packets.
+ // We don't know the version here, so assume latest.
+ time_wait_list_manager_->AddGuidToTimeWait(guid, QuicVersionMax());
+ time_wait_list_manager_->ProcessPacket(server_address,
+ client_address,
+ guid,
+ packet);
+ return;
+ }
+ DLOG(INFO) << "Created new session for " << guid;
+ session_map_.insert(make_pair(guid, session));
+ } else {
+ session = it->second;
+ }
+
+ session->connection()->ProcessUdpPacket(
+ server_address, client_address, packet);
+}
+
+void QuicDispatcher::CleanUpSession(SessionMap::iterator it) {
+ QuicSession* session = it->second;
+ write_blocked_list_.RemoveBlockedObject(session->connection());
+ time_wait_list_manager_->AddGuidToTimeWait(it->first,
+ session->connection()->version());
+ session_map_.erase(it);
+}
+
+void QuicDispatcher::DeleteSessions() {
+ STLDeleteElements(&closed_session_list_);
+}
+
+bool QuicDispatcher::OnCanWrite() {
+ // We got an EPOLLOUT: the socket should not be blocked.
+ write_blocked_ = false;
+
+ // Give each writer one attempt to write.
+ int num_writers = write_blocked_list_.NumObjects();
+ for (int i = 0; i < num_writers; ++i) {
+ if (write_blocked_list_.IsEmpty()) {
+ break;
+ }
+ QuicBlockedWriterInterface* writer =
+ write_blocked_list_.GetNextBlockedObject();
+ bool can_write_more = writer->OnCanWrite();
+ if (write_blocked_) {
+ // We were unable to write. Wait for the next EPOLLOUT.
+ // In this case, the session would have been added to the blocked list
+ // up in WritePacket.
+ return false;
+ }
+ // The socket is not blocked but the writer has ceded work. Add it to the
+ // end of the list.
+ if (can_write_more) {
+ write_blocked_list_.AddBlockedObject(writer);
+ }
+ }
+
+ // We're not write blocked. Return true if there's more work to do.
+ return !write_blocked_list_.IsEmpty();
+}
+
+void QuicDispatcher::Shutdown() {
+ while (!session_map_.empty()) {
+ QuicSession* session = session_map_.begin()->second;
+ session->connection()->SendConnectionClose(QUIC_PEER_GOING_AWAY);
+ // Validate that the session removes itself from the session map on close.
+ DCHECK(session_map_.empty() || session_map_.begin()->second != session);
+ }
+ DeleteSessions();
+}
+
+void QuicDispatcher::OnConnectionClose(QuicGuid guid, QuicErrorCode error) {
+ SessionMap::iterator it = session_map_.find(guid);
+ if (it == session_map_.end()) {
+ LOG(DFATAL) << "GUID " << guid << " does not exist in the session map. "
+ << "Error: " << QuicUtils::ErrorToString(error);
+ return;
+ }
+
+ DLOG_IF(INFO, error != QUIC_NO_ERROR) << "Closing connection due to error: "
+ << QuicUtils::ErrorToString(error);
+
+ if (closed_session_list_.empty()) {
+ epoll_server_->RegisterAlarmApproximateDelta(
+ 0, delete_sessions_alarm_.get());
+ }
+ closed_session_list_.push_back(it->second);
+ CleanUpSession(it);
+}
+
+QuicSession* QuicDispatcher::CreateQuicSession(
+ QuicGuid guid,
+ const IPEndPoint& client_address,
+ int fd,
+ EpollServer* epoll_server) {
+ QuicConnectionHelperInterface* helper =
+ new QuicEpollConnectionHelper(this, epoll_server);
+ QuicServerSession* session = new QuicServerSession(
+ config_, new QuicConnection(guid, client_address, helper, true,
+ QuicVersionMax()), this);
+ session->InitializeSession(crypto_config_);
+ return session;
+}
+
+} // namespace tools
+} // namespace net
+
+
diff --git a/chromium/net/tools/quic/quic_dispatcher.h b/chromium/net/tools/quic/quic_dispatcher.h
new file mode 100644
index 00000000000..bbf8d9b4941
--- /dev/null
+++ b/chromium/net/tools/quic/quic_dispatcher.h
@@ -0,0 +1,147 @@
+// Copyright (c) 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.
+//
+// A server side dispatcher which dispatches a given client's data to their
+// stream.
+
+#ifndef NET_TOOLS_QUIC_QUIC_DISPATCHER_H_
+#define NET_TOOLS_QUIC_QUIC_DISPATCHER_H_
+
+#include <list>
+
+#include "base/containers/hash_tables.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/blocked_list.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_packet_writer.h"
+#include "net/tools/quic/quic_server_session.h"
+#include "net/tools/quic/quic_time_wait_list_manager.h"
+
+#if defined(COMPILER_GCC)
+namespace BASE_HASH_NAMESPACE {
+template<>
+struct hash<net::QuicBlockedWriterInterface*> {
+ std::size_t operator()(
+ const net::QuicBlockedWriterInterface* ptr) const {
+ return hash<size_t>()(reinterpret_cast<size_t>(ptr));
+ }
+};
+}
+#endif
+
+namespace net {
+
+class EpollServer;
+class QuicConfig;
+class QuicCryptoServerConfig;
+class QuicSession;
+
+namespace tools {
+
+namespace test {
+class QuicDispatcherPeer;
+} // namespace test
+
+class DeleteSessionsAlarm;
+class QuicDispatcher : public QuicPacketWriter, public QuicSessionOwner {
+ public:
+ typedef BlockedList<QuicBlockedWriterInterface*> WriteBlockedList;
+
+ // Due to the way delete_sessions_closure_ is registered, the Dispatcher
+ // must live until epoll_server Shutdown.
+ QuicDispatcher(const QuicConfig& config,
+ const QuicCryptoServerConfig& crypto_config,
+ int fd,
+ EpollServer* epoll_server);
+ virtual ~QuicDispatcher();
+
+ // QuicPacketWriter
+ virtual int WritePacket(const char* buffer, size_t buf_len,
+ const IPAddressNumber& self_address,
+ const IPEndPoint& peer_address,
+ QuicBlockedWriterInterface* writer,
+ int* error) OVERRIDE;
+
+ virtual void ProcessPacket(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet);
+
+ // Called when the underyling connection becomes writable to allow
+ // queued writes to happen.
+ //
+ // Returns true if more writes are possible, false otherwise.
+ virtual bool OnCanWrite();
+
+ // Sends ConnectionClose frames to all connected clients.
+ void Shutdown();
+
+ // Ensure that the closed connection is cleaned up asynchronously.
+ virtual void OnConnectionClose(QuicGuid guid, QuicErrorCode error) OVERRIDE;
+
+ int fd() { return fd_; }
+ void set_fd(int fd) { fd_ = fd; }
+
+ typedef base::hash_map<QuicGuid, QuicSession*> SessionMap;
+
+ virtual QuicSession* CreateQuicSession(
+ QuicGuid guid,
+ const IPEndPoint& client_address,
+ int fd,
+ EpollServer* epoll_server);
+
+ // Deletes all sessions on the closed session list and clears the list.
+ void DeleteSessions();
+
+ const SessionMap& session_map() const { return session_map_; }
+
+ WriteBlockedList* write_blocked_list() { return &write_blocked_list_; }
+
+ protected:
+ const QuicConfig& config_;
+ const QuicCryptoServerConfig& crypto_config_;
+
+ QuicTimeWaitListManager* time_wait_list_manager() {
+ return time_wait_list_manager_.get();
+ }
+
+ private:
+ friend class net::tools::test::QuicDispatcherPeer;
+
+ // Removes the session from the session map and write blocked list, and
+ // adds the GUID to the time-wait list.
+ void CleanUpSession(SessionMap::iterator it);
+
+ // The list of connections waiting to write.
+ WriteBlockedList write_blocked_list_;
+
+ SessionMap session_map_;
+
+ // Entity that manages guids in time wait state.
+ scoped_ptr<QuicTimeWaitListManager> time_wait_list_manager_;
+
+ // An alarm which deletes closed sessions.
+ scoped_ptr<DeleteSessionsAlarm> delete_sessions_alarm_;
+
+ // The list of closed but not-yet-deleted sessions.
+ std::list<QuicSession*> closed_session_list_;
+
+ EpollServer* epoll_server_; // Owned by the server.
+
+ // The connection for client-server communication
+ int fd_;
+
+ // True if the session is write blocked due to the socket returning EAGAIN.
+ // False if we have gotten a call to OnCanWrite after the last failed write.
+ bool write_blocked_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicDispatcher);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_DISPATCHER_H_
diff --git a/chromium/net/tools/quic/quic_dispatcher_test.cc b/chromium/net/tools/quic/quic_dispatcher_test.cc
new file mode 100644
index 00000000000..059d8334dc0
--- /dev/null
+++ b/chromium/net/tools/quic/quic_dispatcher_test.cc
@@ -0,0 +1,390 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_dispatcher.h"
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_time_wait_list_manager.h"
+#include "net/tools/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using net::EpollServer;
+using net::test::MockSession;
+using net::tools::test::MockConnection;
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::InSequence;
+using testing::Return;
+using testing::WithoutArgs;
+
+namespace net {
+namespace tools {
+namespace test {
+class QuicDispatcherPeer {
+ public:
+ static void SetTimeWaitListManager(
+ QuicDispatcher* dispatcher,
+ QuicTimeWaitListManager* time_wait_list_manager) {
+ dispatcher->time_wait_list_manager_.reset(time_wait_list_manager);
+ }
+
+ static void SetWriteBlocked(QuicDispatcher* dispatcher) {
+ dispatcher->write_blocked_ = true;
+ }
+};
+
+namespace {
+
+class TestDispatcher : public QuicDispatcher {
+ public:
+ explicit TestDispatcher(const QuicConfig& config,
+ const QuicCryptoServerConfig& crypto_config,
+ EpollServer* eps)
+ : QuicDispatcher(config, crypto_config, 1, eps) {}
+
+ MOCK_METHOD4(CreateQuicSession, QuicSession*(
+ QuicGuid guid,
+ const IPEndPoint& client_address,
+ int fd,
+ EpollServer* eps));
+ using QuicDispatcher::write_blocked_list;
+};
+
+// A Connection class which unregisters the session from the dispatcher
+// when sending connection close.
+// It'd be slightly more realistic to do this from the Session but it would
+// involve a lot more mocking.
+class MockServerConnection : public MockConnection {
+ public:
+ MockServerConnection(QuicGuid guid,
+ IPEndPoint address,
+ int fd,
+ EpollServer* eps,
+ QuicDispatcher* dispatcher)
+ : MockConnection(guid, address, fd, eps, true),
+ dispatcher_(dispatcher) {
+ }
+ void UnregisterOnConnectionClose() {
+ LOG(ERROR) << "Unregistering " << guid();
+ dispatcher_->OnConnectionClose(guid(), QUIC_NO_ERROR);
+ }
+ private:
+ QuicDispatcher* dispatcher_;
+};
+
+QuicSession* CreateSession(QuicDispatcher* dispatcher,
+ QuicGuid guid,
+ const IPEndPoint& addr,
+ MockSession** session,
+ EpollServer* eps) {
+ MockServerConnection* connection =
+ new MockServerConnection(guid, addr, 0, eps, dispatcher);
+ *session = new MockSession(connection, true);
+ ON_CALL(*connection, SendConnectionClose(_)).WillByDefault(
+ WithoutArgs(Invoke(
+ connection, &MockServerConnection::UnregisterOnConnectionClose)));
+ EXPECT_CALL(*reinterpret_cast<MockConnection*>((*session)->connection()),
+ ProcessUdpPacket(_, addr, _));
+
+ return *session;
+}
+
+class QuicDispatcherTest : public ::testing::Test {
+ public:
+ QuicDispatcherTest()
+ : crypto_config_(QuicCryptoServerConfig::TESTING,
+ QuicRandom::GetInstance()),
+ dispatcher_(config_, crypto_config_, &eps_),
+ session1_(NULL),
+ session2_(NULL) {
+ }
+
+ virtual ~QuicDispatcherTest() {}
+
+ MockConnection* connection1() {
+ return reinterpret_cast<MockConnection*>(session1_->connection());
+ }
+
+ MockConnection* connection2() {
+ return reinterpret_cast<MockConnection*>(session2_->connection());
+ }
+
+ void ProcessPacket(IPEndPoint addr,
+ QuicGuid guid,
+ const string& data) {
+ QuicEncryptedPacket packet(data.data(), data.length());
+ dispatcher_.ProcessPacket(IPEndPoint(), addr, guid, packet);
+ }
+
+ void ValidatePacket(const QuicEncryptedPacket& packet) {
+ EXPECT_TRUE(packet.AsStringPiece().find(data_) != StringPiece::npos);
+ }
+
+ IPAddressNumber Loopback4() {
+ net::IPAddressNumber addr;
+ CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &addr));
+ return addr;
+ }
+
+ EpollServer eps_;
+ QuicConfig config_;
+ QuicCryptoServerConfig crypto_config_;
+ TestDispatcher dispatcher_;
+ MockSession* session1_;
+ MockSession* session2_;
+ string data_;
+};
+
+TEST_F(QuicDispatcherTest, ProcessPackets) {
+ IPEndPoint addr(Loopback4(), 1);
+
+ EXPECT_CALL(dispatcher_, CreateQuicSession(1, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, 1, addr, &session1_, &eps_)));
+ ProcessPacket(addr, 1, "foo");
+
+ EXPECT_CALL(dispatcher_, CreateQuicSession(2, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, 2, addr, &session2_, &eps_)));
+ ProcessPacket(addr, 2, "bar");
+
+ data_ = "eep";
+ EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _)).Times(1).
+ WillOnce(testing::WithArgs<2>(Invoke(
+ this, &QuicDispatcherTest::ValidatePacket)));
+ ProcessPacket(addr, 1, "eep");
+}
+
+TEST_F(QuicDispatcherTest, Shutdown) {
+ IPEndPoint addr(Loopback4(), 1);
+
+ EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, 1, addr, &session1_, &eps_)));
+
+ ProcessPacket(addr, 1, "foo");
+
+ EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()),
+ SendConnectionClose(QUIC_PEER_GOING_AWAY));
+
+ dispatcher_.Shutdown();
+}
+
+class MockTimeWaitListManager : public QuicTimeWaitListManager {
+ public:
+ MockTimeWaitListManager(QuicPacketWriter* writer,
+ EpollServer* eps)
+ : QuicTimeWaitListManager(writer, eps) {
+ }
+
+ MOCK_METHOD4(ProcessPacket, void(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet));
+};
+
+TEST_F(QuicDispatcherTest, TimeWaitListManager) {
+ MockTimeWaitListManager* time_wait_list_manager =
+ new MockTimeWaitListManager(&dispatcher_, &eps_);
+ // dispatcher takes the ownership of time_wait_list_manager.
+ QuicDispatcherPeer::SetTimeWaitListManager(&dispatcher_,
+ time_wait_list_manager);
+ // Create a new session.
+ IPEndPoint addr(Loopback4(), 1);
+ QuicGuid guid = 1;
+ EXPECT_CALL(dispatcher_, CreateQuicSession(guid, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, guid, addr, &session1_, &eps_)));
+ ProcessPacket(addr, guid, "foo");
+
+ // Close the connection by sending public reset packet.
+ QuicPublicResetPacket packet;
+ packet.public_header.guid = guid;
+ packet.public_header.reset_flag = true;
+ packet.public_header.version_flag = false;
+ packet.rejected_sequence_number = 19191;
+ packet.nonce_proof = 132232;
+ scoped_ptr<QuicEncryptedPacket> encrypted(
+ QuicFramer::BuildPublicResetPacket(packet));
+ EXPECT_CALL(*session1_, ConnectionClose(QUIC_PUBLIC_RESET, true)).Times(1)
+ .WillOnce(WithoutArgs(Invoke(
+ reinterpret_cast<MockServerConnection*>(session1_->connection()),
+ &MockServerConnection::UnregisterOnConnectionClose)));
+ EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(Invoke(
+ reinterpret_cast<MockConnection*>(session1_->connection()),
+ &MockConnection::ReallyProcessUdpPacket));
+ dispatcher_.ProcessPacket(IPEndPoint(), addr, guid, *encrypted);
+ EXPECT_TRUE(time_wait_list_manager->IsGuidInTimeWait(guid));
+
+ // Dispatcher forwards subsequent packets for this guid to the time wait list
+ // manager.
+ EXPECT_CALL(*time_wait_list_manager, ProcessPacket(_, _, guid, _)).Times(1);
+ ProcessPacket(addr, guid, "foo");
+}
+
+class WriteBlockedListTest : public QuicDispatcherTest {
+ public:
+ virtual void SetUp() {
+ IPEndPoint addr(Loopback4(), 1);
+
+ EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, 1, addr, &session1_, &eps_)));
+ ProcessPacket(addr, 1, "foo");
+
+ EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_))
+ .WillOnce(testing::Return(CreateSession(
+ &dispatcher_, 2, addr, &session2_, &eps_)));
+ ProcessPacket(addr, 2, "bar");
+
+ blocked_list_ = dispatcher_.write_blocked_list();
+ }
+
+ virtual void TearDown() {
+ EXPECT_CALL(*connection1(), SendConnectionClose(QUIC_PEER_GOING_AWAY));
+ EXPECT_CALL(*connection2(), SendConnectionClose(QUIC_PEER_GOING_AWAY));
+ dispatcher_.Shutdown();
+ }
+
+ bool SetBlocked() {
+ QuicDispatcherPeer::SetWriteBlocked(&dispatcher_);
+ return true;
+ }
+
+ protected:
+ QuicDispatcher::WriteBlockedList* blocked_list_;
+};
+
+TEST_F(WriteBlockedListTest, BasicOnCanWrite) {
+ // No OnCanWrite calls because no connections are blocked.
+ dispatcher_.OnCanWrite();
+
+ // Register connection 1 for events, and make sure it's nofitied.
+ blocked_list_->AddBlockedObject(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+
+ // It should get only one notification.
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ EXPECT_FALSE(dispatcher_.OnCanWrite());
+}
+
+TEST_F(WriteBlockedListTest, OnCanWriteOrder) {
+ // Make sure we handle events in order.
+ InSequence s;
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+
+ // Check the other ordering.
+ blocked_list_->AddBlockedObject(connection2());
+ blocked_list_->AddBlockedObject(connection1());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+}
+
+TEST_F(WriteBlockedListTest, OnCanWriteRemove) {
+ // Add and remove one connction.
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->RemoveBlockedObject(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ dispatcher_.OnCanWrite();
+
+ // Add and remove one connction and make sure it doesn't affect others.
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection2());
+ blocked_list_->RemoveBlockedObject(connection1());
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+
+ // Add it, remove it, and add it back and make sure things are OK.
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->RemoveBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(1);
+ dispatcher_.OnCanWrite();
+}
+
+TEST_F(WriteBlockedListTest, DoubleAdd) {
+ // Make sure a double add does not necessitate a double remove.
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->RemoveBlockedObject(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(0);
+ dispatcher_.OnCanWrite();
+
+ // Make sure a double add does not result in two OnCanWrite calls.
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection1());
+ EXPECT_CALL(*connection1(), OnCanWrite()).Times(1);
+ dispatcher_.OnCanWrite();
+}
+
+TEST_F(WriteBlockedListTest, OnCanWriteHandleBlock) {
+ // Finally make sure if we write block on a write call, we stop calling.
+ InSequence s;
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce(
+ Invoke(this, &WriteBlockedListTest::SetBlocked));
+ EXPECT_CALL(*connection2(), OnCanWrite()).Times(0);
+ dispatcher_.OnCanWrite();
+
+ // And we'll resume where we left off when we get another call.
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+}
+
+TEST_F(WriteBlockedListTest, LimitedWrites) {
+ // Make sure we call both writers. The first will register for more writing
+ // but should not be immediately called due to limits.
+ InSequence s;
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce(Return(true));
+ EXPECT_CALL(*connection2(), OnCanWrite()).WillOnce(Return(false));
+ dispatcher_.OnCanWrite();
+
+ // Now call OnCanWrite again, and connection1 should get its second chance
+ EXPECT_CALL(*connection1(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+}
+
+TEST_F(WriteBlockedListTest, TestWriteLimits) {
+ // Finally make sure if we write block on a write call, we stop calling.
+ InSequence s;
+ blocked_list_->AddBlockedObject(connection1());
+ blocked_list_->AddBlockedObject(connection2());
+ EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce(
+ Invoke(this, &WriteBlockedListTest::SetBlocked));
+ EXPECT_CALL(*connection2(), OnCanWrite()).Times(0);
+ dispatcher_.OnCanWrite();
+
+ // And we'll resume where we left off when we get another call.
+ EXPECT_CALL(*connection2(), OnCanWrite());
+ dispatcher_.OnCanWrite();
+}
+
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_epoll_clock.cc b/chromium/net/tools/quic/quic_epoll_clock.cc
new file mode 100644
index 00000000000..2a3abf1f1f5
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_clock.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_epoll_clock.h"
+
+#include "net/tools/flip_server/epoll_server.h"
+
+namespace net {
+namespace tools {
+
+QuicEpollClock::QuicEpollClock(EpollServer* epoll_server)
+ : epoll_server_(epoll_server) {
+}
+
+QuicEpollClock::~QuicEpollClock() {}
+
+QuicTime QuicEpollClock::ApproximateNow() const {
+ return QuicTime::Zero().Add(
+ QuicTime::Delta::FromMicroseconds(epoll_server_->ApproximateNowInUsec()));
+}
+
+QuicTime QuicEpollClock::Now() const {
+ return QuicTime::Zero().Add(
+ QuicTime::Delta::FromMicroseconds(epoll_server_->NowInUsec()));
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_epoll_clock.h b/chromium/net/tools/quic/quic_epoll_clock.h
new file mode 100644
index 00000000000..fb21354a9e0
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_clock.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_
+#define NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_
+
+#include "base/compiler_specific.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_time.h"
+
+namespace net {
+
+class EpollServer;
+
+namespace tools {
+
+// Clock to efficiently retrieve an approximately accurate time from an
+// EpollServer.
+class QuicEpollClock : public QuicClock {
+ public:
+ explicit QuicEpollClock(EpollServer* epoll_server);
+ virtual ~QuicEpollClock();
+
+ // Returns the approximate current time as a QuicTime object.
+ virtual QuicTime ApproximateNow() const OVERRIDE;
+
+ // Returns the current time as a QuicTime object.
+ // Note: this use significant resources please use only if needed.
+ virtual QuicTime Now() const OVERRIDE;
+
+ protected:
+ EpollServer* epoll_server_;
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_
diff --git a/chromium/net/tools/quic/quic_epoll_clock_test.cc b/chromium/net/tools/quic/quic_epoll_clock_test.cc
new file mode 100644
index 00000000000..c774d0adba2
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_clock_test.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_epoll_clock.h"
+
+#include "net/tools/quic/test_tools/mock_epoll_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+TEST(QuicEpollClockTest, ApproximateNowInUsec) {
+ MockEpollServer epoll_server;
+ QuicEpollClock clock(&epoll_server);
+
+ epoll_server.set_now_in_usec(1000000);
+ EXPECT_EQ(1000000,
+ clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds());
+
+ epoll_server.AdvanceBy(5);
+ EXPECT_EQ(1000005,
+ clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds());
+}
+
+TEST(QuicEpollClockTest, NowInUsec) {
+ MockEpollServer epoll_server;
+ QuicEpollClock clock(&epoll_server);
+
+ epoll_server.set_now_in_usec(1000000);
+ EXPECT_EQ(1000000,
+ clock.Now().Subtract(QuicTime::Zero()).ToMicroseconds());
+
+ epoll_server.AdvanceBy(5);
+ EXPECT_EQ(1000005,
+ clock.Now().Subtract(QuicTime::Zero()).ToMicroseconds());
+}
+
+TEST(QuicClockTest, WallNow) {
+ MockEpollServer epoll_server;
+ QuicEpollClock clock(&epoll_server);
+
+ base::Time start = base::Time::Now();
+ QuicWallTime now = clock.WallNow();
+ base::Time end = base::Time::Now();
+
+ // If end > start, then we can check now is between start and end.
+ if (end > start) {
+ EXPECT_LE(static_cast<uint64>(start.ToTimeT()), now.ToUNIXSeconds());
+ EXPECT_LE(now.ToUNIXSeconds(), static_cast<uint64>(end.ToTimeT()));
+ }
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper.cc b/chromium/net/tools/quic/quic_epoll_connection_helper.cc
new file mode 100644
index 00000000000..4db1d4cd426
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_connection_helper.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+
+#include <errno.h>
+#include <sys/socket.h>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_socket_utils.h"
+
+namespace net {
+namespace tools {
+
+namespace {
+
+class QuicEpollAlarm : public QuicAlarm {
+ public:
+ QuicEpollAlarm(EpollServer* epoll_server,
+ QuicAlarm::Delegate* delegate)
+ : QuicAlarm(delegate),
+ epoll_server_(epoll_server),
+ epoll_alarm_impl_(this) {}
+
+ protected:
+ virtual void SetImpl() OVERRIDE {
+ DCHECK(deadline().IsInitialized());
+ epoll_server_->RegisterAlarm(
+ deadline().Subtract(QuicTime::Zero()).ToMicroseconds(),
+ &epoll_alarm_impl_);
+ }
+
+ virtual void CancelImpl() OVERRIDE {
+ DCHECK(!deadline().IsInitialized());
+ epoll_alarm_impl_.UnregisterIfRegistered();
+ }
+
+ private:
+ class EpollAlarmImpl : public EpollAlarm {
+ public:
+ explicit EpollAlarmImpl(QuicEpollAlarm* alarm) : alarm_(alarm) {}
+
+ virtual int64 OnAlarm() OVERRIDE {
+ EpollAlarm::OnAlarm();
+ alarm_->Fire();
+ // Fire will take care of registering the alarm, if needed.
+ return 0;
+ }
+
+ private:
+ QuicEpollAlarm* alarm_;
+ };
+
+ EpollServer* epoll_server_;
+ EpollAlarmImpl epoll_alarm_impl_;
+};
+
+} // namespace
+
+QuicEpollConnectionHelper::QuicEpollConnectionHelper(
+ int fd, EpollServer* epoll_server)
+ : writer_(NULL),
+ epoll_server_(epoll_server),
+ fd_(fd),
+ connection_(NULL),
+ clock_(epoll_server),
+ random_generator_(QuicRandom::GetInstance()) {
+}
+
+QuicEpollConnectionHelper::QuicEpollConnectionHelper(QuicPacketWriter* writer,
+ EpollServer* epoll_server)
+ : writer_(writer),
+ epoll_server_(epoll_server),
+ fd_(-1),
+ connection_(NULL),
+ clock_(epoll_server),
+ random_generator_(QuicRandom::GetInstance()) {
+}
+
+QuicEpollConnectionHelper::~QuicEpollConnectionHelper() {
+}
+
+void QuicEpollConnectionHelper::SetConnection(QuicConnection* connection) {
+ DCHECK(!connection_);
+ connection_ = connection;
+}
+
+const QuicClock* QuicEpollConnectionHelper::GetClock() const {
+ return &clock_;
+}
+
+QuicRandom* QuicEpollConnectionHelper::GetRandomGenerator() {
+ return random_generator_;
+}
+
+int QuicEpollConnectionHelper::WritePacketToWire(
+ const QuicEncryptedPacket& packet,
+ int* error) {
+ if (connection_->ShouldSimulateLostPacket()) {
+ DLOG(INFO) << "Dropping packet due to fake packet loss.";
+ *error = 0;
+ return packet.length();
+ }
+
+ // If we have a writer, delgate the write to it.
+ if (writer_) {
+ return writer_->WritePacket(packet.data(), packet.length(),
+ connection_->self_address().address(),
+ connection_->peer_address(),
+ connection_,
+ error);
+ } else {
+ return QuicSocketUtils::WritePacket(
+ fd_, packet.data(), packet.length(),
+ connection_->self_address().address(),
+ connection_->peer_address(),
+ error);
+ }
+}
+
+bool QuicEpollConnectionHelper::IsWriteBlockedDataBuffered() {
+ return false;
+}
+
+bool QuicEpollConnectionHelper::IsWriteBlocked(int error) {
+ return error == EAGAIN || error == EWOULDBLOCK;
+}
+
+QuicAlarm* QuicEpollConnectionHelper::CreateAlarm(
+ QuicAlarm::Delegate* delegate) {
+ return new QuicEpollAlarm(epoll_server_, delegate);
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper.h b/chromium/net/tools/quic/quic_epoll_connection_helper.h
new file mode 100644
index 00000000000..5bd526f8469
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_connection_helper.h
@@ -0,0 +1,72 @@
+// Copyright (c) 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.
+//
+// The Google-specific helper for QuicConnection which uses
+// EpollAlarm for alarms, and used an int fd_ for writing data.
+
+#ifndef NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_
+#define NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_
+
+#include <sys/types.h>
+#include <set>
+
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_time.h"
+#include "net/tools/quic/quic_epoll_clock.h"
+#include "net/tools/quic/quic_packet_writer.h"
+
+namespace net {
+
+class EpollServer;
+class QuicRandom;
+
+namespace tools {
+
+class AckAlarm;
+class RetransmissionAlarm;
+class SendAlarm;
+class TimeoutAlarm;
+
+namespace test {
+class QuicEpollConnectionHelperPeer;
+} // namespace test
+
+class QuicEpollConnectionHelper : public QuicConnectionHelperInterface {
+ public:
+ QuicEpollConnectionHelper(int fd, EpollServer* eps);
+ QuicEpollConnectionHelper(QuicPacketWriter* writer, EpollServer* eps);
+ virtual ~QuicEpollConnectionHelper();
+
+ // QuicEpollConnectionHelperInterface
+ virtual void SetConnection(QuicConnection* connection) OVERRIDE;
+ virtual const QuicClock* GetClock() const OVERRIDE;
+ virtual QuicRandom* GetRandomGenerator() OVERRIDE;
+ virtual int WritePacketToWire(const QuicEncryptedPacket& packet,
+ int* error) OVERRIDE;
+ virtual bool IsWriteBlockedDataBuffered() OVERRIDE;
+ virtual bool IsWriteBlocked(int error) OVERRIDE;
+ virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE;
+
+ EpollServer* epoll_server() { return epoll_server_; }
+
+ private:
+ friend class QuicConnectionPeer;
+ friend class net::tools::test::QuicEpollConnectionHelperPeer;
+
+ QuicPacketWriter* writer_; // Not owned
+ EpollServer* epoll_server_; // Not owned.
+ int fd_;
+
+ QuicConnection* connection_;
+ const QuicEpollClock clock_;
+ QuicRandom* random_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicEpollConnectionHelper);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_
diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc b/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc
new file mode 100644
index 00000000000..0bfaee27d07
--- /dev/null
+++ b/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc
@@ -0,0 +1,207 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/test_tools/quic_connection_peer.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/quic/test_tools/mock_epoll_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::test::GetMinStreamFrameSize;
+using net::test::FramerVisitorCapturingFrames;
+using net::test::MockSendAlgorithm;
+using net::test::QuicConnectionPeer;
+using net::test::MockConnectionVisitor;
+using net::tools::test::MockEpollServer;
+using testing::_;
+using testing::Return;
+
+namespace net {
+namespace tools {
+namespace test {
+namespace {
+
+const char data1[] = "foo";
+const bool kFromPeer = true;
+
+class TestConnectionHelper : public QuicEpollConnectionHelper {
+ public:
+ TestConnectionHelper(int fd, EpollServer* eps)
+ : QuicEpollConnectionHelper(fd, eps) {
+ }
+
+ virtual int WritePacketToWire(const QuicEncryptedPacket& packet,
+ int* error) OVERRIDE {
+ QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), true);
+ FramerVisitorCapturingFrames visitor;
+ framer.set_visitor(&visitor);
+ EXPECT_TRUE(framer.ProcessPacket(packet));
+ header_ = *visitor.header();
+ *error = 0;
+ return packet.length();
+ }
+
+ QuicPacketHeader* header() { return &header_; }
+
+ private:
+ QuicPacketHeader header_;
+};
+
+class TestConnection : public QuicConnection {
+ public:
+ TestConnection(QuicGuid guid,
+ IPEndPoint address,
+ TestConnectionHelper* helper)
+ : QuicConnection(guid, address, helper, false, QuicVersionMax()) {
+ }
+
+ void SendAck() {
+ QuicConnectionPeer::SendAck(this);
+ }
+
+ void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) {
+ QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm);
+ }
+
+ using QuicConnection::SendOrQueuePacket;
+};
+
+class QuicEpollConnectionHelperTest : public ::testing::Test {
+ protected:
+ QuicEpollConnectionHelperTest()
+ : guid_(42),
+ framer_(QuicVersionMax(), QuicTime::Zero(), false),
+ send_algorithm_(new testing::StrictMock<MockSendAlgorithm>),
+ helper_(new TestConnectionHelper(0, &epoll_server_)),
+ connection_(guid_, IPEndPoint(), helper_),
+ frame1_(1, false, 0, data1) {
+ connection_.set_visitor(&visitor_);
+ connection_.SetSendAlgorithm(send_algorithm_);
+ epoll_server_.set_timeout_in_us(-1);
+ EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)).
+ WillRepeatedly(Return(QuicTime::Delta::Zero()));
+ }
+
+ QuicPacket* ConstructDataPacket(QuicPacketSequenceNumber number,
+ QuicFecGroupNumber fec_group) {
+ header_.public_header.version_flag = false;
+ header_.public_header.reset_flag = false;
+ header_.fec_flag = false;
+ header_.entropy_flag = false;
+ header_.packet_sequence_number = number;
+ header_.is_in_fec_group = fec_group == 0 ? NOT_IN_FEC_GROUP : IN_FEC_GROUP;
+ header_.fec_group = fec_group;
+
+ QuicFrames frames;
+ QuicFrame frame(&frame1_);
+ frames.push_back(frame);
+ return framer_.BuildUnsizedDataPacket(header_, frames).packet;
+ }
+
+ QuicGuid guid_;
+ QuicFramer framer_;
+
+ MockEpollServer epoll_server_;
+ testing::StrictMock<MockSendAlgorithm>* send_algorithm_;
+ TestConnectionHelper* helper_;
+ TestConnection connection_;
+ testing::StrictMock<MockConnectionVisitor> visitor_;
+
+ QuicPacketHeader header_;
+ QuicStreamFrame frame1_;
+};
+
+TEST_F(QuicEpollConnectionHelperTest, DISABLED_TestRetransmission) {
+ //FLAGS_fake_packet_loss_percentage = 100;
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ Return(QuicTime::Delta::Zero()));
+ const int64 kDefaultRetransmissionTimeMs = 500;
+
+ const char buffer[] = "foo";
+ const size_t packet_size =
+ GetPacketHeaderSize(PACKET_8BYTE_GUID, kIncludeVersion,
+ PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) +
+ GetMinStreamFrameSize(framer_.version()) + arraysize(buffer) - 1;
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, 1, packet_size, NOT_RETRANSMISSION));
+ EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, packet_size));
+ connection_.SendStreamData(1, buffer, 0, false);
+ EXPECT_EQ(1u, helper_->header()->packet_sequence_number);
+ EXPECT_CALL(*send_algorithm_,
+ SentPacket(_, 2, packet_size, IS_RETRANSMISSION));
+ epoll_server_.AdvanceByAndCallCallbacks(kDefaultRetransmissionTimeMs * 1000);
+
+ EXPECT_EQ(2u, helper_->header()->packet_sequence_number);
+}
+
+TEST_F(QuicEpollConnectionHelperTest, InitialTimeout) {
+ EXPECT_TRUE(connection_.connected());
+
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer));
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_FALSE(connection_.connected());
+ EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000, epoll_server_.NowInUsec());
+}
+
+TEST_F(QuicEpollConnectionHelperTest, TimeoutAfterSend) {
+ EXPECT_TRUE(connection_.connected());
+ EXPECT_EQ(0, epoll_server_.NowInUsec());
+
+ // When we send a packet, the timeout will change to 5000 +
+ // kDefaultInitialTimeoutSecs.
+ epoll_server_.AdvanceBy(5000);
+ EXPECT_EQ(5000, epoll_server_.NowInUsec());
+
+ // Send an ack so we don't set the retransmission alarm.
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ connection_.SendAck();
+
+ // The original alarm will fire. We should not time out because we had a
+ // network event at t=5000. The alarm will reregister.
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000, epoll_server_.NowInUsec());
+
+ // This time, we should time out.
+ EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer));
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION));
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000 + 5000,
+ epoll_server_.NowInUsec());
+ EXPECT_FALSE(connection_.connected());
+}
+
+TEST_F(QuicEpollConnectionHelperTest, SendSchedulerDelayThenSend) {
+ // Test that if we send a packet with a delay, it ends up queued.
+ EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly(
+ Return(QuicTime::Delta::Zero()));
+ QuicPacket* packet = ConstructDataPacket(1, 0);
+ EXPECT_CALL(
+ *send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce(
+ testing::Return(QuicTime::Delta::FromMicroseconds(1)));
+ connection_.SendOrQueuePacket(ENCRYPTION_NONE, 1, packet, 0,
+ HAS_RETRANSMITTABLE_DATA);
+ EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION));
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Advance the clock to fire the alarm, and configure the scheduler
+ // to permit the packet to be sent.
+ EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).
+ WillRepeatedly(testing::Return(QuicTime::Delta::Zero()));
+ EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true));
+ epoll_server_.AdvanceByAndCallCallbacks(1);
+ EXPECT_EQ(0u, connection_.NumQueuedPackets());
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_in_memory_cache.cc b/chromium/net/tools/quic/quic_in_memory_cache.cc
new file mode 100644
index 00000000000..b840d79370d
--- /dev/null
+++ b/chromium/net/tools/quic/quic_in_memory_cache.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_in_memory_cache.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/stl_util.h"
+
+using base::FilePath;
+using base::StringPiece;
+using std::string;
+
+// Specifies the directory used during QuicInMemoryCache
+// construction to seed the cache. Cache directory can be
+// generated using `wget -p --save-headers <url>
+
+namespace net {
+namespace tools {
+
+std::string FLAGS_quic_in_memory_cache_dir = "/tmp/quic-data";
+
+namespace {
+
+// BalsaVisitor implementation (glue) which caches response bodies.
+class CachingBalsaVisitor : public BalsaVisitorInterface {
+ public:
+ CachingBalsaVisitor() : done_framing_(false) {}
+ virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE {
+ AppendToBody(input, size);
+ }
+ virtual void ProcessTrailers(const BalsaHeaders& trailer) {
+ LOG(DFATAL) << "Trailers not supported.";
+ }
+ virtual void MessageDone() OVERRIDE {
+ done_framing_ = true;
+ }
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE {
+ UnhandledError();
+ }
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {
+ UnhandledError();
+ }
+ virtual void HandleTrailerError(BalsaFrame* framer) { UnhandledError(); }
+ virtual void HandleTrailerWarning(BalsaFrame* framer) { UnhandledError(); }
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE {
+ UnhandledError();
+ }
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE {
+ UnhandledError();
+ }
+ void UnhandledError() {
+ LOG(DFATAL) << "Unhandled error framing HTTP.";
+ }
+ virtual void ProcessBodyInput(const char*, size_t) OVERRIDE {}
+ virtual void ProcessHeaderInput(const char*, size_t) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char*, size_t) OVERRIDE {}
+ virtual void ProcessHeaders(const net::BalsaHeaders&) OVERRIDE {}
+ virtual void ProcessRequestFirstLine(
+ const char*, size_t, const char*, size_t,
+ const char*, size_t, const char*, size_t) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(
+ const char*, size_t, const char*,
+ size_t, const char*, size_t, const char*, size_t) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char*, size_t) OVERRIDE {}
+ virtual void HeaderDone() OVERRIDE {}
+
+ void AppendToBody(const char* input, size_t size) {
+ body_.append(input, size);
+ }
+ bool done_framing() const { return done_framing_; }
+ const string& body() const { return body_; }
+
+ private:
+ bool done_framing_;
+ string body_;
+};
+
+} // namespace
+
+QuicInMemoryCache* QuicInMemoryCache::GetInstance() {
+ return Singleton<QuicInMemoryCache>::get();
+}
+
+const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse(
+ const BalsaHeaders& request_headers) const {
+ ResponseMap::const_iterator it = responses_.find(GetKey(request_headers));
+ if (it == responses_.end()) {
+ return NULL;
+ }
+ return it->second;
+}
+
+void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers,
+ const BalsaHeaders& response_headers,
+ StringPiece response_body) {
+ LOG(INFO) << "Adding response for: " << GetKey(request_headers);
+ if (ContainsKey(responses_, GetKey(request_headers))) {
+ LOG(DFATAL) << "Response for given request already exists!";
+ }
+ Response* new_response = new Response();
+ new_response->set_headers(response_headers);
+ new_response->set_body(response_body);
+ responses_[GetKey(request_headers)] = new_response;
+}
+
+void QuicInMemoryCache::ResetForTests() {
+ STLDeleteValues(&responses_);
+ Initialize();
+}
+
+QuicInMemoryCache::QuicInMemoryCache() {
+ Initialize();
+}
+
+void QuicInMemoryCache::Initialize() {
+ // If there's no defined cache dir, we have no initialization to do.
+ if (FLAGS_quic_in_memory_cache_dir.empty()) {
+ LOG(WARNING) << "No cache directory found. Skipping initialization.";
+ return;
+ }
+ LOG(INFO) << "Attempting to initialize QuicInMemoryCache from directory: "
+ << FLAGS_quic_in_memory_cache_dir;
+
+ FilePath directory(FLAGS_quic_in_memory_cache_dir);
+ base::FileEnumerator file_list(directory,
+ true,
+ base::FileEnumerator::FILES);
+
+ FilePath file = file_list.Next();
+ while (!file.empty()) {
+ // Need to skip files in .svn directories
+ if (file.value().find("/.svn/") != std::string::npos) {
+ file = file_list.Next();
+ continue;
+ }
+
+ BalsaHeaders request_headers, response_headers;
+
+ string file_contents;
+ file_util::ReadFileToString(file, &file_contents);
+
+ // Frame HTTP.
+ CachingBalsaVisitor caching_visitor;
+ BalsaFrame framer;
+ framer.set_balsa_headers(&response_headers);
+ framer.set_balsa_visitor(&caching_visitor);
+ size_t processed = 0;
+ while (processed < file_contents.length() &&
+ !caching_visitor.done_framing()) {
+ processed += framer.ProcessInput(file_contents.c_str() + processed,
+ file_contents.length() - processed);
+ }
+
+ string response_headers_str;
+ response_headers.DumpToString(&response_headers_str);
+ if (!caching_visitor.done_framing()) {
+ LOG(DFATAL) << "Did not frame entire message from file: " << file.value()
+ << " (" << processed << " of " << file_contents.length()
+ << " bytes).";
+ }
+ if (processed < file_contents.length()) {
+ // Didn't frame whole file. Assume remainder is body.
+ // This sometimes happens as a result of incompatibilities between
+ // BalsaFramer and wget's serialization of HTTP sans content-length.
+ caching_visitor.AppendToBody(file_contents.c_str() + processed,
+ file_contents.length() - processed);
+ processed += file_contents.length();
+ }
+
+ StringPiece base = file.value();
+ if (response_headers.HasHeader("X-Original-Url")) {
+ base = response_headers.GetHeader("X-Original-Url");
+ response_headers.RemoveAllOfHeader("X-Original-Url");
+ // Remove the protocol so that the string is of the form host + path,
+ // which is parsed properly below.
+ if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) {
+ base.remove_prefix(8);
+ } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) {
+ base.remove_prefix(7);
+ }
+ }
+ int path_start = base.find_first_of('/');
+ DCHECK_LT(0, path_start);
+ StringPiece host(base.substr(0, path_start));
+ StringPiece path(base.substr(path_start));
+ if (path[path.length() - 1] == ',') {
+ path.remove_suffix(1);
+ }
+ // Set up request headers. Assume method is GET and protocol is HTTP/1.1.
+ request_headers.SetRequestFirstlineFromStringPieces("GET",
+ path,
+ "HTTP/1.1");
+ request_headers.ReplaceOrAppendHeader("host", host);
+
+ LOG(INFO) << "Inserting 'http://" << GetKey(request_headers)
+ << "' into QuicInMemoryCache.";
+
+ AddResponse(request_headers, response_headers, caching_visitor.body());
+
+ file = file_list.Next();
+ }
+}
+
+QuicInMemoryCache::~QuicInMemoryCache() {
+ STLDeleteValues(&responses_);
+}
+
+string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const {
+ StringPiece uri = request_headers.request_uri();
+ StringPiece host;
+ if (uri[0] == '/') {
+ host = request_headers.GetHeader("host");
+ } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) {
+ uri.remove_prefix(8);
+ } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) {
+ uri.remove_prefix(7);
+ }
+ return host.as_string() + uri.as_string();
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_in_memory_cache.h b/chromium/net/tools/quic/quic_in_memory_cache.h
new file mode 100644
index 00000000000..6322e2da49d
--- /dev/null
+++ b/chromium/net/tools/quic/quic_in_memory_cache.h
@@ -0,0 +1,88 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_
+#define NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_
+
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_frame.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace net {
+namespace tools {
+
+extern std::string FLAGS_quic_in_memory_cache_dir;
+
+class QuicServer;
+
+// In-memory cache for HTTP responses.
+// Reads from disk cache generated by:
+// `wget -p --save_headers <url>`
+class QuicInMemoryCache {
+ public:
+ // Container for response header/body pairs.
+ class Response {
+ public:
+ Response() {}
+ ~Response() {}
+
+ const BalsaHeaders& headers() const { return headers_; }
+ const base::StringPiece body() const { return base::StringPiece(body_); }
+
+ private:
+ friend class QuicInMemoryCache;
+
+ void set_headers(const BalsaHeaders& headers) {
+ headers_.CopyFrom(headers);
+ }
+ void set_body(base::StringPiece body) {
+ body.CopyToString(&body_);
+ }
+
+ BalsaHeaders headers_;
+ std::string body_;
+
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+ static QuicInMemoryCache* GetInstance();
+
+ // Retrieve a response from this cache for a given request.
+ // If no appropriate response exists, NULL is returned.
+ // Currently, responses are selected based on request URI only.
+ const Response* GetResponse(const BalsaHeaders& request_headers) const;
+
+ // Add a response to the cache.
+ void AddResponse(const BalsaHeaders& request_headers,
+ const BalsaHeaders& response_headers,
+ base::StringPiece response_body);
+
+ void ResetForTests();
+
+ private:
+ typedef base::hash_map<std::string, Response*> ResponseMap;
+
+
+ QuicInMemoryCache();
+ friend struct DefaultSingletonTraits<QuicInMemoryCache>;
+ ~QuicInMemoryCache();
+
+ void Initialize();
+ std::string GetKey(const BalsaHeaders& response_headers) const;
+
+ // Cached responses.
+ ResponseMap responses_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicInMemoryCache);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_
diff --git a/chromium/net/tools/quic/quic_in_memory_cache_test.cc b/chromium/net/tools/quic/quic_in_memory_cache_test.cc
new file mode 100644
index 00000000000..065ecc3b437
--- /dev/null
+++ b/chromium/net/tools/quic/quic_in_memory_cache_test.cc
@@ -0,0 +1,137 @@
+// Copyright 2013 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 <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/singleton.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::IntToString;
+using base::StringPiece;
+
+namespace net {
+namespace tools {
+namespace test {
+
+class QuicInMemoryCacheTest : public ::testing::Test {
+ protected:
+ QuicInMemoryCacheTest() {
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net").AppendASCII("data")
+ .AppendASCII("quic_in_memory_cache_data");
+ // The file path is known to be an ascii string.
+ FLAGS_quic_in_memory_cache_dir = path.MaybeAsASCII();
+ }
+
+ void CreateRequest(std::string host,
+ std::string path,
+ net::BalsaHeaders* headers) {
+ headers->SetRequestFirstlineFromStringPieces("GET", path, "HTTP/1.1");
+ headers->ReplaceOrAppendHeader("host", host);
+ }
+
+ virtual void SetUp() {
+ QuicInMemoryCache::GetInstance()->ResetForTests();
+ }
+
+ // This method was copied from end_to_end_test.cc in this directory.
+ void AddToCache(const StringPiece& method,
+ const StringPiece& path,
+ const StringPiece& version,
+ const StringPiece& response_code,
+ const StringPiece& response_detail,
+ const StringPiece& body) {
+ BalsaHeaders request_headers, response_headers;
+ request_headers.SetRequestFirstlineFromStringPieces(method,
+ path,
+ version);
+ response_headers.SetRequestFirstlineFromStringPieces(version,
+ response_code,
+ response_detail);
+ response_headers.AppendHeader("content-length",
+ base::IntToString(body.length()));
+
+ // Check if response already exists and matches.
+ QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance();
+ const QuicInMemoryCache::Response* cached_response =
+ cache->GetResponse(request_headers);
+ if (cached_response != NULL) {
+ std::string cached_response_headers_str, response_headers_str;
+ cached_response->headers().DumpToString(&cached_response_headers_str);
+ response_headers.DumpToString(&response_headers_str);
+ CHECK_EQ(cached_response_headers_str, response_headers_str);
+ CHECK_EQ(cached_response->body(), body);
+ return;
+ }
+ cache->AddResponse(request_headers, response_headers, body);
+ }
+};
+
+TEST_F(QuicInMemoryCacheTest, AddResponseGetResponse) {
+ std::string response_body("hello response");
+ AddToCache("GET", "https://www.google.com/bar",
+ "HTTP/1.1", "200", "OK", response_body);
+ net::BalsaHeaders request_headers;
+ CreateRequest("www.google.com", "/bar", &request_headers);
+ QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance();
+ const QuicInMemoryCache::Response* response =
+ cache->GetResponse(request_headers);
+ ASSERT_TRUE(response);
+ EXPECT_EQ("200", response->headers().response_code());
+ EXPECT_EQ(response_body.size(), response->body().length());
+
+ CreateRequest("", "https://www.google.com/bar", &request_headers);
+ response = cache->GetResponse(request_headers);
+ ASSERT_TRUE(response);
+ EXPECT_EQ("200", response->headers().response_code());
+ EXPECT_EQ(response_body.size(), response->body().length());
+}
+
+TEST_F(QuicInMemoryCacheTest, ReadsCacheDir) {
+ net::BalsaHeaders request_headers;
+ CreateRequest("quic.test.url", "/index.html", &request_headers);
+
+ const QuicInMemoryCache::Response* response =
+ QuicInMemoryCache::GetInstance()->GetResponse(request_headers);
+ ASSERT_TRUE(response);
+ std::string value;
+ response->headers().GetAllOfHeaderAsString("Connection", &value);
+ EXPECT_EQ("200", response->headers().response_code());
+ EXPECT_EQ("Keep-Alive", value);
+ EXPECT_LT(0U, response->body().length());
+}
+
+TEST_F(QuicInMemoryCacheTest, ReadsCacheDirHttp) {
+ net::BalsaHeaders request_headers;
+ CreateRequest("", "http://quic.test.url/index.html", &request_headers);
+
+ const QuicInMemoryCache::Response* response =
+ QuicInMemoryCache::GetInstance()->GetResponse(request_headers);
+ ASSERT_TRUE(response);
+ std::string value;
+ response->headers().GetAllOfHeaderAsString("Connection", &value);
+ EXPECT_EQ("200", response->headers().response_code());
+ EXPECT_EQ("Keep-Alive", value);
+ EXPECT_LT(0U, response->body().length());
+}
+
+TEST_F(QuicInMemoryCacheTest, GetResponseNoMatch) {
+ net::BalsaHeaders request_headers;
+ CreateRequest("www.google.com", "/index.html", &request_headers);
+
+ const QuicInMemoryCache::Response* response =
+ QuicInMemoryCache::GetInstance()->GetResponse(request_headers);
+ ASSERT_FALSE(response);
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_packet_writer.h b/chromium/net/tools/quic/quic_packet_writer.h
new file mode 100644
index 00000000000..b10e852d299
--- /dev/null
+++ b/chromium/net/tools/quic/quic_packet_writer.h
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_
+#define NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_
+
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+
+class QuicBlockedWriterInterface;
+
+namespace tools {
+
+// An interface between writers and the entity managing the
+// socket (in our case the QuicDispatcher). This allows the Dispatcher to
+// control writes, and manage any writers who end up write blocked.
+class QuicPacketWriter {
+ public:
+ virtual ~QuicPacketWriter() {}
+
+ virtual int WritePacket(const char* buffer, size_t buf_len,
+ const net::IPAddressNumber& self_address,
+ const net::IPEndPoint& peer_address,
+ QuicBlockedWriterInterface* blocked_writer,
+ int* error) = 0;
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_
diff --git a/chromium/net/tools/quic/quic_reliable_client_stream.cc b/chromium/net/tools/quic/quic_reliable_client_stream.cc
new file mode 100644
index 00000000000..359fec4dd90
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_client_stream.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_reliable_client_stream.h"
+
+using std::string;
+
+namespace net {
+namespace tools {
+
+// Sends body data to the server and returns the number of bytes sent.
+ssize_t QuicReliableClientStream::SendBody(const string& data, bool fin) {
+ return WriteData(data, fin).bytes_consumed;
+}
+
+bool QuicReliableClientStream::OnStreamFrame(const QuicStreamFrame& frame) {
+ if (!write_side_closed()) {
+ DLOG(INFO) << "Got a response before the request was complete. "
+ << "Aborting request.";
+ CloseWriteSide();
+ }
+ return ReliableQuicStream::OnStreamFrame(frame);
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_reliable_client_stream.h b/chromium/net/tools/quic/quic_reliable_client_stream.h
new file mode 100644
index 00000000000..10b60c2f696
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_client_stream.h
@@ -0,0 +1,66 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+#define NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
+
+#include <sys/types.h>
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/tools/flip_server/balsa_frame.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+
+class QuicSession;
+
+namespace tools {
+
+class QuicClientSession;
+
+// A base class for spdy/http client streams which handles the concept
+// of sending and receiving headers and bodies.
+class QuicReliableClientStream : public ReliableQuicStream {
+ public:
+ QuicReliableClientStream(QuicStreamId id, QuicSession* session)
+ : ReliableQuicStream(id, session) {
+ }
+
+ // Serializes the headers and body, sends it to the server, and
+ // returns the number of bytes sent.
+ virtual ssize_t SendRequest(const BalsaHeaders& headers,
+ base::StringPiece body,
+ bool fin) = 0;
+ // Sends body data to the server and returns the number of bytes sent.
+ virtual ssize_t SendBody(const std::string& data, bool fin);
+
+ // Override the base class to close the Write side as soon as we get a
+ // response.
+ // SPDY/HTTP do not support bidirectional streaming.
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+
+ // Returns the response data.
+ const std::string& data() { return data_; }
+
+ // Returns whatever headers have been received for this stream.
+ const BalsaHeaders& headers() { return headers_; }
+
+ protected:
+ std::string* mutable_data() { return &data_; }
+ BalsaHeaders* mutable_headers() { return &headers_; }
+
+ private:
+ BalsaHeaders headers_;
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicReliableClientStream);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_
diff --git a/chromium/net/tools/quic/quic_reliable_client_stream_test.cc b/chromium/net/tools/quic/quic_reliable_client_stream_test.cc
new file mode 100644
index 00000000000..af0ffd58f81
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_client_stream_test.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_reliable_client_stream.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_client_session.h"
+#include "net/tools/quic/quic_spdy_client_stream.h"
+#include "net/tools/quic/spdy_utils.h"
+#include "net/tools/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::TestWithParam;
+
+namespace net {
+namespace tools {
+namespace test {
+namespace {
+
+class QuicClientStreamTest : public ::testing::Test {
+ public:
+ QuicClientStreamTest()
+ : session_("example.com", QuicConfig(),
+ new MockConnection(1, IPEndPoint(), 0, &eps_, false),
+ &crypto_config_),
+ body_("hello world") {
+ session_.config()->SetDefaults();
+ crypto_config_.SetDefaults();
+
+ headers_.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "Ok");
+ headers_.ReplaceOrAppendHeader("content-length", "11");
+
+ headers_string_ = SpdyUtils::SerializeResponseHeaders(headers_);
+ stream_.reset(new QuicSpdyClientStream(3, &session_));
+ }
+
+ EpollServer eps_;
+ QuicClientSession session_;
+ scoped_ptr<QuicReliableClientStream> stream_;
+ BalsaHeaders headers_;
+ string headers_string_;
+ string body_;
+ QuicCryptoClientConfig crypto_config_;
+};
+
+TEST_F(QuicClientStreamTest, TestFraming) {
+ EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
+ headers_string_.c_str(), headers_string_.size()));
+ EXPECT_EQ(body_.size(),
+ stream_->ProcessData(body_.c_str(), body_.size()));
+ EXPECT_EQ(200u, stream_->headers().parsed_response_code());
+ EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_F(QuicClientStreamTest, TestFramingOnePacket) {
+ string message = headers_string_ + body_;
+
+ EXPECT_EQ(message.size(), stream_->ProcessData(
+ message.c_str(), message.size()));
+ EXPECT_EQ(200u, stream_->headers().parsed_response_code());
+ EXPECT_EQ(body_, stream_->data());
+}
+
+TEST_F(QuicClientStreamTest, TestFramingExtraData) {
+ string large_body = "hello world!!!!!!";
+
+ EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
+ headers_string_.c_str(), headers_string_.size()));
+ // The headers should parse successfully.
+ EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+ EXPECT_EQ(200u, stream_->headers().parsed_response_code());
+
+ stream_->ProcessData(large_body.c_str(), large_body.size());
+ stream_->TerminateFromPeer(true);
+
+ EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error());
+}
+
+TEST_F(QuicClientStreamTest, TestNoBidirectionalStreaming) {
+ QuicStreamFrame frame(3, false, 3, "asd");
+
+ EXPECT_FALSE(stream_->write_side_closed());
+ EXPECT_TRUE(stream_->OnStreamFrame(frame));
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
+
diff --git a/chromium/net/tools/quic/quic_reliable_server_stream.cc b/chromium/net/tools/quic/quic_reliable_server_stream.cc
new file mode 100644
index 00000000000..58b884a1082
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_server_stream.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_reliable_server_stream.h"
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+
+using base::StringPiece;
+
+namespace net {
+namespace tools {
+
+QuicReliableServerStream::QuicReliableServerStream(QuicStreamId id,
+ QuicSession* session)
+ : ReliableQuicStream(id, session) {
+}
+
+
+void QuicReliableServerStream::SendResponse() {
+ // Find response in cache. If not found, send error response.
+ const QuicInMemoryCache::Response* response =
+ QuicInMemoryCache::GetInstance()->GetResponse(headers_);
+ if (response == NULL) {
+ SendErrorResponse();
+ return;
+ }
+
+ DLOG(INFO) << "Sending response for stream " << id();
+ SendHeaders(response->headers());
+ WriteData(response->body(), true);
+}
+
+void QuicReliableServerStream::SendErrorResponse() {
+ DLOG(INFO) << "Sending error response for stream " << id();
+ BalsaHeaders headers;
+ headers.SetResponseFirstlineFromStringPieces(
+ "HTTP/1.1", "500", "Server Error");
+ headers.ReplaceOrAppendHeader("content-length", "3");
+ SendHeaders(headers);
+ WriteData("bad", true);
+}
+
+QuicConsumedData QuicReliableServerStream::WriteData(StringPiece data,
+ bool fin) {
+ // We only support SPDY and HTTP, and neither handles bidirectional streaming.
+ if (!read_side_closed()) {
+ CloseReadSide();
+ }
+ return ReliableQuicStream::WriteData(data, fin);
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_reliable_server_stream.h b/chromium/net/tools/quic/quic_reliable_server_stream.h
new file mode 100644
index 00000000000..2669f42649c
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_server_stream.h
@@ -0,0 +1,65 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_
+#define NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_
+
+#include <string>
+
+#include "net/quic/quic_protocol.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+
+class QuicSession;
+
+namespace tools {
+
+namespace test {
+class QuicReliableServerStreamPeer;
+} // namespace test
+
+// A base class for spdy/http server streams which handles the concept
+// of sending and receiving headers and bodies.
+class QuicReliableServerStream : public ReliableQuicStream {
+ public:
+ QuicReliableServerStream(QuicStreamId id, QuicSession* session);
+ virtual ~QuicReliableServerStream() {}
+
+ // Subclasses should process and frame data when this is called, returning
+ // how many bytes are processed.
+ virtual uint32 ProcessData(const char* data, uint32 data_len) = 0;
+ // Subclasses should implement this to serialize headers in a
+ // protocol-specific manner, and send it out to the client.
+ virtual void SendHeaders(const BalsaHeaders& response_headers) = 0;
+
+ // Sends a basic 200 response using SendHeaders for the headers and WriteData
+ // for the body.
+ void SendResponse();
+ // Sends a basic 500 response using SendHeaders for the headers and WriteData
+ // for the body
+ void SendErrorResponse();
+ // Make sure that as soon as we start writing data, we stop reading.
+ virtual QuicConsumedData WriteData(base::StringPiece data, bool fin) OVERRIDE;
+
+ // Returns whatever headers have been received for this stream.
+ const BalsaHeaders& headers() { return headers_; }
+
+ const string& body() { return body_; }
+ protected:
+ BalsaHeaders* mutable_headers() { return &headers_; }
+ string* mutable_body() { return &body_; }
+
+ private:
+ friend class test::QuicReliableServerStreamPeer;
+
+ BalsaHeaders headers_;
+ string body_;
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_
diff --git a/chromium/net/tools/quic/quic_reliable_server_stream_test.cc b/chromium/net/tools/quic/quic_reliable_server_stream_test.cc
new file mode 100644
index 00000000000..b946d94cfc3
--- /dev/null
+++ b/chromium/net/tools/quic/quic_reliable_server_stream_test.cc
@@ -0,0 +1,219 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_reliable_server_stream.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "net/quic/quic_spdy_compressor.h"
+#include "net/quic/quic_utils.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_spdy_server_stream.h"
+#include "net/tools/quic/spdy_utils.h"
+#include "net/tools/quic/test_tools/quic_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using net::tools::test::MockConnection;
+using net::test::MockSession;
+using std::string;
+using testing::_;
+using testing::AnyNumber;
+using testing::Invoke;
+using testing::InvokeArgument;
+using testing::InSequence;
+using testing::Return;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace net {
+namespace tools {
+namespace test {
+
+class QuicReliableServerStreamPeer {
+ public:
+ static BalsaHeaders* GetMutableHeaders(
+ QuicReliableServerStream* stream) {
+ return &(stream->headers_);
+ }
+};
+
+namespace {
+
+class QuicReliableServerStreamTest : public ::testing::Test {
+ public:
+ QuicReliableServerStreamTest()
+ : session_(new MockConnection(1, IPEndPoint(), 0, &eps_, true), true),
+ body_("hello world") {
+ BalsaHeaders request_headers;
+ request_headers.SetRequestFirstlineFromStringPieces(
+ "POST", "https://www.google.com/", "HTTP/1.1");
+ request_headers.ReplaceOrAppendHeader("content-length", "11");
+
+ headers_string_ = SpdyUtils::SerializeRequestHeaders(request_headers);
+ stream_.reset(new QuicSpdyServerStream(3, &session_));
+ }
+
+ QuicConsumedData ValidateHeaders(StringPiece headers) {
+ headers_string_ = SpdyUtils::SerializeResponseHeaders(
+ response_headers_);
+ QuicSpdyDecompressor decompressor;
+ TestDecompressorVisitor visitor;
+
+ // First the header id, then the compressed data.
+ EXPECT_EQ(1, headers[0]);
+ EXPECT_EQ(0, headers[1]);
+ EXPECT_EQ(0, headers[2]);
+ EXPECT_EQ(0, headers[3]);
+ EXPECT_EQ(static_cast<size_t>(headers.length() - 4),
+ decompressor.DecompressData(headers.substr(4), &visitor));
+
+ EXPECT_EQ(headers_string_, visitor.data());
+
+ return QuicConsumedData(headers.size(), false);
+ }
+
+ static void SetUpTestCase() {
+ QuicInMemoryCache::GetInstance()->ResetForTests();
+ }
+
+ virtual void SetUp() {
+ QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance();
+
+ BalsaHeaders request_headers, response_headers;
+ StringPiece body("Yum");
+ request_headers.SetRequestFirstlineFromStringPieces(
+ "GET",
+ "https://www.google.com/foo",
+ "HTTP/1.1");
+ response_headers.SetRequestFirstlineFromStringPieces("HTTP/1.1",
+ "200",
+ "OK");
+ response_headers.AppendHeader("content-length",
+ base::IntToString(body.length()));
+
+ // Check if response already exists and matches.
+ const QuicInMemoryCache::Response* cached_response =
+ cache->GetResponse(request_headers);
+ if (cached_response != NULL) {
+ string cached_response_headers_str, response_headers_str;
+ cached_response->headers().DumpToString(&cached_response_headers_str);
+ response_headers.DumpToString(&response_headers_str);
+ CHECK_EQ(cached_response_headers_str, response_headers_str);
+ CHECK_EQ(cached_response->body(), body);
+ return;
+ }
+
+ cache->AddResponse(request_headers, response_headers, body);
+ }
+
+ BalsaHeaders response_headers_;
+ EpollServer eps_;
+ StrictMock<MockSession> session_;
+ scoped_ptr<QuicReliableServerStream> stream_;
+ string headers_string_;
+ string body_;
+};
+
+QuicConsumedData ConsumeAllData(QuicStreamId id, StringPiece data,
+ QuicStreamOffset offset, bool fin) {
+ return QuicConsumedData(data.size(), fin);
+}
+
+TEST_F(QuicReliableServerStreamTest, TestFraming) {
+ EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()).
+ WillRepeatedly(Invoke(ConsumeAllData));
+
+ EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
+ headers_string_.c_str(), headers_string_.size()));
+ EXPECT_EQ(body_.size(), stream_->ProcessData(body_.c_str(), body_.size()));
+ EXPECT_EQ(11u, stream_->headers().content_length());
+ EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri());
+ EXPECT_EQ("POST", stream_->headers().request_method());
+ EXPECT_EQ(body_, stream_->body());
+}
+
+TEST_F(QuicReliableServerStreamTest, TestFramingOnePacket) {
+ EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()).
+ WillRepeatedly(Invoke(ConsumeAllData));
+
+ string message = headers_string_ + body_;
+
+ EXPECT_EQ(message.size(), stream_->ProcessData(
+ message.c_str(), message.size()));
+ EXPECT_EQ(11u, stream_->headers().content_length());
+ EXPECT_EQ("https://www.google.com/",
+ stream_->headers().request_uri());
+ EXPECT_EQ("POST", stream_->headers().request_method());
+ EXPECT_EQ(body_, stream_->body());
+}
+
+TEST_F(QuicReliableServerStreamTest, TestFramingExtraData) {
+ string large_body = "hello world!!!!!!";
+
+ // We'll automatically write out an error (headers + body)
+ EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(2).
+ WillRepeatedly(Invoke(ConsumeAllData));
+
+ EXPECT_EQ(headers_string_.size(), stream_->ProcessData(
+ headers_string_.c_str(), headers_string_.size()));
+ // Content length is still 11. This will register as an error and we won't
+ // accept the bytes.
+ stream_->ProcessData(large_body.c_str(), large_body.size());
+ stream_->TerminateFromPeer(true);
+ EXPECT_EQ(11u, stream_->headers().content_length());
+ EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri());
+ EXPECT_EQ("POST", stream_->headers().request_method());
+}
+
+TEST_F(QuicReliableServerStreamTest, TestSendResponse) {
+ BalsaHeaders* request_headers =
+ QuicReliableServerStreamPeer::GetMutableHeaders(stream_.get());
+ request_headers->SetRequestFirstlineFromStringPieces(
+ "GET",
+ "https://www.google.com/foo",
+ "HTTP/1.1");
+
+ response_headers_.SetResponseFirstlineFromStringPieces(
+ "HTTP/1.1", "200", "OK");
+ response_headers_.ReplaceOrAppendHeader("content-length", "3");
+
+ InSequence s;
+ EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1)
+ .WillOnce(WithArgs<1>(Invoke(
+ this, &QuicReliableServerStreamTest::ValidateHeaders)));
+ StringPiece kBody = "Yum";
+ EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1).
+ WillOnce(Return(QuicConsumedData(3, true)));
+
+ stream_->SendResponse();
+ EXPECT_TRUE(stream_->read_side_closed());
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+TEST_F(QuicReliableServerStreamTest, TestSendErrorResponse) {
+ response_headers_.SetResponseFirstlineFromStringPieces(
+ "HTTP/1.1", "500", "Server Error");
+ response_headers_.ReplaceOrAppendHeader("content-length", "3");
+
+ InSequence s;
+ EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1)
+ .WillOnce(WithArgs<1>(Invoke(
+ this, &QuicReliableServerStreamTest::ValidateHeaders)));
+ StringPiece kBody = "bad";
+ EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1).
+ WillOnce(Return(QuicConsumedData(3, true)));
+
+ stream_->SendErrorResponse();
+ EXPECT_TRUE(stream_->read_side_closed());
+ EXPECT_TRUE(stream_->write_side_closed());
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_server.cc b/chromium/net/tools/quic/quic_server.cc
new file mode 100644
index 00000000000..d18525f634f
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server.cc
@@ -0,0 +1,223 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_server.h"
+
+#include <errno.h>
+#include <features.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_handshake.h"
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_crypto_stream.h"
+#include "net/quic/quic_data_reader.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_socket_utils.h"
+
+#define MMSG_MORE 0
+
+#ifndef SO_RXQ_OVFL
+#define SO_RXQ_OVFL 40
+#endif
+
+const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET;
+const int kNumPacketsPerReadCall = 5; // Arbitrary
+static const char kSourceAddressTokenSecret[] = "secret";
+
+namespace net {
+namespace tools {
+
+QuicServer::QuicServer()
+ : port_(0),
+ packets_dropped_(0),
+ overflow_supported_(false),
+ use_recvmmsg_(false),
+ crypto_config_(kSourceAddressTokenSecret, QuicRandom::GetInstance()) {
+ // Use hardcoded crypto parameters for now.
+ config_.SetDefaults();
+ Initialize();
+}
+
+QuicServer::QuicServer(const QuicConfig& config)
+ : port_(0),
+ packets_dropped_(0),
+ overflow_supported_(false),
+ use_recvmmsg_(false),
+ config_(config),
+ crypto_config_(kSourceAddressTokenSecret, QuicRandom::GetInstance()) {
+ Initialize();
+}
+
+void QuicServer::Initialize() {
+#if MMSG_MORE
+ use_recvmmsg_ = true;
+#endif
+ epoll_server_.set_timeout_in_us(50 * 1000);
+ // Initialize the in memory cache now.
+ QuicInMemoryCache::GetInstance();
+
+ QuicEpollClock clock(&epoll_server_);
+
+ scoped_ptr<CryptoHandshakeMessage> scfg(
+ crypto_config_.AddDefaultConfig(
+ QuicRandom::GetInstance(), &clock,
+ QuicCryptoServerConfig::ConfigOptions()));
+}
+
+QuicServer::~QuicServer() {
+}
+
+bool QuicServer::Listen(const IPEndPoint& address) {
+ port_ = address.port();
+ int address_family = address.GetSockAddrFamily();
+ fd_ = socket(address_family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
+ if (fd_ < 0) {
+ LOG(ERROR) << "CreateSocket() failed: " << strerror(errno);
+ return false;
+ }
+
+ int rc = QuicSocketUtils::SetGetAddressInfo(fd_, address_family);
+
+ if (rc < 0) {
+ LOG(ERROR) << "IP detection not supported" << strerror(errno);
+ return false;
+ }
+
+ int get_overflow = 1;
+ rc = setsockopt(
+ fd_, SOL_SOCKET, SO_RXQ_OVFL, &get_overflow, sizeof(get_overflow));
+
+ if (rc < 0) {
+ DLOG(WARNING) << "Socket overflow detection not supported";
+ } else {
+ overflow_supported_ = true;
+ }
+
+ // Enable the socket option that allows the local address to be
+ // returned if the socket is bound to more than on address.
+ int get_local_ip = 1;
+ rc = setsockopt(fd_, IPPROTO_IP, IP_PKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ if (rc == 0 && address_family == AF_INET6) {
+ rc = setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ }
+ if (rc != 0) {
+ LOG(ERROR) << "Failed to set required socket options";
+ return false;
+ }
+
+ sockaddr_storage raw_addr;
+ socklen_t raw_addr_len = sizeof(raw_addr);
+ CHECK(address.ToSockAddr(reinterpret_cast<sockaddr*>(&raw_addr),
+ &raw_addr_len));
+ rc = bind(fd_,
+ reinterpret_cast<const sockaddr*>(&raw_addr),
+ sizeof(raw_addr));
+ if (rc < 0) {
+ LOG(ERROR) << "Bind failed: " << strerror(errno);
+ return false;
+ }
+
+ LOG(INFO) << "Listening on " << address.ToString();
+ if (port_ == 0) {
+ SockaddrStorage storage;
+ IPEndPoint server_address;
+ if (getsockname(fd_, storage.addr, &storage.addr_len) != 0 ||
+ !server_address.FromSockAddr(storage.addr, storage.addr_len)) {
+ LOG(ERROR) << "Unable to get self address. Error: " << strerror(errno);
+ return false;
+ }
+ port_ = server_address.port();
+ LOG(INFO) << "Kernel assigned port is " << port_;
+ }
+
+ epoll_server_.RegisterFD(fd_, this, kEpollFlags);
+ dispatcher_.reset(new QuicDispatcher(config_, crypto_config_, fd_,
+ &epoll_server_));
+
+ return true;
+}
+
+void QuicServer::WaitForEvents() {
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+}
+
+void QuicServer::Shutdown() {
+ // Before we shut down the epoll server, give all active sessions a chance to
+ // notify clients that they're closing.
+ dispatcher_->Shutdown();
+}
+
+void QuicServer::OnEvent(int fd, EpollEvent* event) {
+ DCHECK_EQ(fd, fd_);
+ event->out_ready_mask = 0;
+
+ if (event->in_events & EPOLLIN) {
+ LOG(ERROR) << "EPOLLIN";
+ bool read = true;
+ while (read) {
+ read = ReadAndDispatchSinglePacket(
+ fd_, port_, dispatcher_.get(),
+ overflow_supported_ ? &packets_dropped_ : NULL);
+ }
+ }
+ if (event->in_events & EPOLLOUT) {
+ bool can_write_more = dispatcher_->OnCanWrite();
+ if (can_write_more) {
+ event->out_ready_mask |= EPOLLOUT;
+ }
+ }
+ if (event->in_events & EPOLLERR) {
+ }
+}
+
+/* static */
+void QuicServer::MaybeDispatchPacket(QuicDispatcher* dispatcher,
+ const QuicEncryptedPacket& packet,
+ const IPEndPoint& server_address,
+ const IPEndPoint& client_address) {
+ QuicGuid guid;
+ if (!QuicFramer::ReadGuidFromPacket(packet, &guid)) {
+ return;
+ }
+
+ dispatcher->ProcessPacket(server_address, client_address, guid, packet);
+}
+
+bool QuicServer::ReadAndDispatchSinglePacket(int fd,
+ int port,
+ QuicDispatcher* dispatcher,
+ int* packets_dropped) {
+ // Allocate some extra space so we can send an error if the client goes over
+ // the limit.
+ char buf[2 * kMaxPacketSize];
+
+ IPEndPoint client_address;
+ IPAddressNumber server_ip;
+ int bytes_read =
+ QuicSocketUtils::ReadPacket(fd, buf, arraysize(buf),
+ packets_dropped,
+ &server_ip, &client_address);
+
+ if (bytes_read < 0) {
+ return false; // We failed to read.
+ }
+
+ QuicEncryptedPacket packet(buf, bytes_read, false);
+
+ IPEndPoint server_address(server_ip, port);
+ MaybeDispatchPacket(dispatcher, packet, server_address, client_address);
+
+ return true;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_server.h b/chromium/net/tools/quic/quic_server.h
new file mode 100644
index 00000000000..142d1d1572c
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server.h
@@ -0,0 +1,115 @@
+// Copyright (c) 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.
+//
+// A toy server, which listens on a specified address for QUIC traffic and
+// handles incoming responses.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SERVER_H_
+#define NET_TOOLS_QUIC_QUIC_SERVER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_framer.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_dispatcher.h"
+
+namespace net {
+
+class QuicCryptoServerConfig;
+
+namespace tools {
+
+class QuicDispatcher;
+
+class QuicServer : public EpollCallbackInterface {
+ public:
+ QuicServer();
+ explicit QuicServer(const QuicConfig& config);
+
+ virtual ~QuicServer();
+
+ // Start listening on the specified address.
+ bool Listen(const IPEndPoint& address);
+
+ // Wait up to 50ms, and handle any events which occur.
+ void WaitForEvents();
+
+ // Server deletion is imminent. Start cleaning up the epoll server.
+ void Shutdown();
+
+ // From EpollCallbackInterface
+ virtual void OnRegistration(
+ EpollServer* eps, int fd, int event_mask) OVERRIDE {}
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {}
+
+ // Reads a packet from the given fd, and then passes it off to
+ // the QuicDispatcher. Returns true if a packet is read, false
+ // otherwise.
+ // If packets_dropped is non-null, the socket is configured to track
+ // dropped packets, and some packets are read, it will be set to the number of
+ // dropped packets.
+ static bool ReadAndDispatchSinglePacket(int fd, int port,
+ QuicDispatcher* dispatcher,
+ int* packets_dropped);
+
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {}
+
+ // Dispatches the given packet only if it looks like a valid QUIC packet.
+ // TODO(rjshade): Return a status describing why a packet was dropped, and log
+ // somehow. Maybe expose as a varz.
+ static void MaybeDispatchPacket(QuicDispatcher* dispatcher,
+ const QuicEncryptedPacket& packet,
+ const IPEndPoint& server_address,
+ const IPEndPoint& client_address);
+
+ bool overflow_supported() { return overflow_supported_; }
+
+ int packets_dropped() { return packets_dropped_; }
+
+ int port() { return port_; }
+
+ private:
+ // Initialize the internal state of the server.
+ void Initialize();
+
+ // Accepts data from the framer and demuxes clients to sessions.
+ scoped_ptr<QuicDispatcher> dispatcher_;
+ // Frames incoming packets and hands them to the dispatcher.
+ EpollServer epoll_server_;
+
+ // The port the server is listening on.
+ int port_;
+
+ // Listening connection. Also used for outbound client communication.
+ int fd_;
+
+ // If overflow_supported_ is true this will be the number of packets dropped
+ // during the lifetime of the server. This may overflow if enough packets
+ // are dropped.
+ int packets_dropped_;
+
+ // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped
+ // because the socket would otherwise overflow.
+ bool overflow_supported_;
+
+ // If true, use recvmmsg for reading.
+ bool use_recvmmsg_;
+
+ // config_ contains non-crypto parameters that are negotiated in the crypto
+ // handshake.
+ QuicConfig config_;
+ // crypto_config_ contains crypto parameters for the handshake.
+ QuicCryptoServerConfig crypto_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicServer);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_SERVER_H_
diff --git a/chromium/net/tools/quic/quic_server_bin.cc b/chromium/net/tools/quic/quic_server_bin.cc
new file mode 100644
index 00000000000..cccf57819f5
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server_bin.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 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.
+//
+// A binary wrapper for QuicServer. It listens forever on --port
+// (default 6121) until it's killed or ctrl-cd to death.
+
+#include "base/at_exit.h"
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/ip_endpoint.h"
+#include "net/tools/quic/quic_in_memory_cache.h"
+#include "net/tools/quic/quic_server.h"
+
+// The port the quic server will listen on.
+
+int32 FLAGS_port = 6121;
+
+int main(int argc, char *argv[]) {
+ CommandLine::Init(argc, argv);
+ CommandLine* line = CommandLine::ForCurrentProcess();
+ if (line->HasSwitch("quic_in_memory_cache_dir")) {
+ net::tools::FLAGS_quic_in_memory_cache_dir =
+ line->GetSwitchValueASCII("quic_in_memory_cache_dir");
+ }
+
+ if (line->HasSwitch("port")) {
+ int port;
+ if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) {
+ FLAGS_port = port;
+ }
+ }
+
+ base::AtExitManager exit_manager;
+
+ net::IPAddressNumber ip;
+ CHECK(net::ParseIPLiteralToNumber("::", &ip));
+
+ net::tools::QuicServer server;
+
+ if (!server.Listen(net::IPEndPoint(ip, FLAGS_port))) {
+ return 1;
+ }
+
+ while (1) {
+ server.WaitForEvents();
+ }
+
+ return 0;
+}
diff --git a/chromium/net/tools/quic/quic_server_session.cc b/chromium/net/tools/quic/quic_server_session.cc
new file mode 100644
index 00000000000..7ec991c4aa9
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server_session.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_server_session.h"
+
+#include "base/logging.h"
+#include "net/quic/reliable_quic_stream.h"
+#include "net/tools/quic/quic_spdy_server_stream.h"
+
+namespace net {
+namespace tools {
+
+QuicServerSession::QuicServerSession(
+ const QuicConfig& config,
+ QuicConnection* connection,
+ QuicSessionOwner* owner)
+ : QuicSession(connection, config, true),
+ owner_(owner) {
+}
+
+QuicServerSession::~QuicServerSession() {
+}
+
+void QuicServerSession::InitializeSession(
+ const QuicCryptoServerConfig& crypto_config) {
+ crypto_stream_.reset(CreateQuicCryptoServerStream(crypto_config));
+}
+
+QuicCryptoServerStream* QuicServerSession::CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig& crypto_config) {
+ return new QuicCryptoServerStream(crypto_config, this);
+}
+
+void QuicServerSession::ConnectionClose(QuicErrorCode error, bool from_peer) {
+ QuicSession::ConnectionClose(error, from_peer);
+ owner_->OnConnectionClose(connection()->guid(), error);
+}
+
+bool QuicServerSession::ShouldCreateIncomingReliableStream(QuicStreamId id) {
+ if (id % 2 == 0) {
+ DLOG(INFO) << "Invalid incoming even stream_id:" << id;
+ connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID);
+ return false;
+ }
+ if (GetNumOpenStreams() >= get_max_open_streams()) {
+ DLOG(INFO) << "Failed to create a new incoming stream with id:" << id
+ << " Already " << GetNumOpenStreams() << " open.";
+ connection()->SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS);
+ return false;
+ }
+ return true;
+}
+
+ReliableQuicStream* QuicServerSession::CreateIncomingReliableStream(
+ QuicStreamId id) {
+ if (!ShouldCreateIncomingReliableStream(id)) {
+ return NULL;
+ }
+
+ return new QuicSpdyServerStream(id, this);
+}
+
+ReliableQuicStream* QuicServerSession::CreateOutgoingReliableStream() {
+ DLOG(ERROR) << "Server push not yet supported";
+ return NULL;
+}
+
+QuicCryptoServerStream* QuicServerSession::GetCryptoStream() {
+ return crypto_stream_.get();
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_server_session.h b/chromium/net/tools/quic/quic_server_session.h
new file mode 100644
index 00000000000..604b9fc0c42
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server_session.h
@@ -0,0 +1,78 @@
+// Copyright (c) 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.
+//
+// A server specific QuicSession subclass.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_
+#define NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_
+
+#include <set>
+#include <vector>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_crypto_server_stream.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_session.h"
+
+namespace net {
+
+class QuicConfig;
+class QuicConnection;
+class QuicCryptoServerConfig;
+class ReliableQuicStream;
+
+namespace tools {
+
+// An interface from the session to the entity owning the session.
+// This lets the session notify its owner (the Dispatcher) when the connection
+// is closed.
+class QuicSessionOwner {
+ public:
+ virtual ~QuicSessionOwner() {}
+
+ virtual void OnConnectionClose(QuicGuid guid, QuicErrorCode error) = 0;
+};
+
+class QuicServerSession : public QuicSession {
+ public:
+ QuicServerSession(const QuicConfig& config,
+ QuicConnection *connection,
+ QuicSessionOwner* owner);
+
+ // Override the base class to notify the owner of the connection close.
+ virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE;
+
+ virtual ~QuicServerSession();
+
+ virtual void InitializeSession(const QuicCryptoServerConfig& crypto_config);
+
+ const QuicCryptoServerStream* crypto_stream() { return crypto_stream_.get(); }
+
+ protected:
+ // QuicSession methods:
+ virtual ReliableQuicStream* CreateIncomingReliableStream(
+ QuicStreamId id) OVERRIDE;
+ virtual ReliableQuicStream* CreateOutgoingReliableStream() OVERRIDE;
+ virtual QuicCryptoServerStream* GetCryptoStream() OVERRIDE;
+
+ // If we should create an incoming stream, returns true. Otherwise
+ // does error handling, including communicating the error to the client and
+ // possibly closing the connection, and returns false.
+ virtual bool ShouldCreateIncomingReliableStream(QuicStreamId id);
+
+ virtual QuicCryptoServerStream* CreateQuicCryptoServerStream(
+ const QuicCryptoServerConfig& crypto_config);
+
+ private:
+ scoped_ptr<QuicCryptoServerStream> crypto_stream_;
+ QuicSessionOwner* owner_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicServerSession);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_
diff --git a/chromium/net/tools/quic/quic_server_test.cc b/chromium/net/tools/quic/quic_server_test.cc
new file mode 100644
index 00000000000..7d1d66939c0
--- /dev/null
+++ b/chromium/net/tools/quic/quic_server_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2013 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/tools/quic/quic_server.h"
+
+#include "net/quic/crypto/quic_random.h"
+#include "net/quic/quic_utils.h"
+#include "net/tools/quic/test_tools/mock_quic_dispatcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace net {
+namespace tools {
+namespace test {
+
+namespace {
+
+class QuicServerDispatchPacketTest : public ::testing::Test {
+ public:
+ QuicServerDispatchPacketTest()
+ : crypto_config_("blah", QuicRandom::GetInstance()),
+ dispatcher_(config_, crypto_config_, 1234, &eps_) {}
+
+
+ void MaybeDispatchPacket(const QuicEncryptedPacket& packet) {
+ IPEndPoint client_addr, server_addr;
+ QuicServer::MaybeDispatchPacket(&dispatcher_, packet,
+ client_addr, server_addr);
+ }
+
+ protected:
+ QuicConfig config_;
+ QuicCryptoServerConfig crypto_config_;
+ EpollServer eps_;
+ MockQuicDispatcher dispatcher_;
+};
+
+TEST_F(QuicServerDispatchPacketTest, DoNotDispatchPacketWithoutGUID) {
+ // Packet too short to be considered valid.
+ unsigned char invalid_packet[] = { 0x00 };
+ QuicEncryptedPacket encrypted_invalid_packet(
+ QuicUtils::AsChars(invalid_packet), arraysize(invalid_packet), false);
+
+ // We expect the invalid packet to be dropped, and ProcessPacket should never
+ // be called.
+ EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _, _)).Times(0);
+ MaybeDispatchPacket(encrypted_invalid_packet);
+}
+
+TEST_F(QuicServerDispatchPacketTest, DispatchValidPacket) {
+ unsigned char valid_packet[] = {
+ // public flags (8 byte guid)
+ 0x3C,
+ // guid
+ 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC, 0xFE,
+ // packet sequence number
+ 0xBC, 0x9A, 0x78, 0x56,
+ 0x34, 0x12,
+ // private flags
+ 0x00 };
+ QuicEncryptedPacket encrypted_valid_packet(QuicUtils::AsChars(valid_packet),
+ arraysize(valid_packet), false);
+
+ EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _, _)).Times(1);
+ MaybeDispatchPacket(encrypted_valid_packet);
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_socket_utils.cc b/chromium/net/tools/quic/quic_socket_utils.cc
new file mode 100644
index 00000000000..4d7d3600760
--- /dev/null
+++ b/chromium/net/tools/quic/quic_socket_utils.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_socket_utils.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <string>
+
+#include "base/logging.h"
+
+#ifndef SO_RXQ_OVFL
+#define SO_RXQ_OVFL 40
+#endif
+
+namespace net {
+namespace tools {
+
+// static
+IPAddressNumber QuicSocketUtils::GetAddressFromMsghdr(struct msghdr *hdr) {
+ IPAddressNumber ret;
+ if (hdr->msg_controllen > 0) {
+ for (cmsghdr* cmsg = CMSG_FIRSTHDR(hdr);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(hdr, cmsg)) {
+ const uint8* addr_data = reinterpret_cast<const uint8*>CMSG_DATA(cmsg);
+ int len = 0;
+ if (cmsg->cmsg_type == IPV6_PKTINFO) {
+ len = sizeof(in6_pktinfo);
+ } else if (cmsg->cmsg_type == IP_PKTINFO) {
+ len = sizeof(in_pktinfo);
+ }
+ ret.assign(addr_data, addr_data + len);
+ break;
+ }
+ }
+ return ret;
+}
+
+// static
+bool QuicSocketUtils::GetOverflowFromMsghdr(struct msghdr *hdr,
+ int *dropped_packets) {
+ if (hdr->msg_controllen > 0) {
+ struct cmsghdr *cmsg;
+ for (cmsg = CMSG_FIRSTHDR(hdr);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(hdr, cmsg)) {
+ if (cmsg->cmsg_type == SO_RXQ_OVFL) {
+ *dropped_packets = *(reinterpret_cast<int*>CMSG_DATA(cmsg));
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// static
+int QuicSocketUtils::SetGetAddressInfo(int fd, int address_family) {
+ int get_local_ip = 1;
+ if (address_family == AF_INET) {
+ return setsockopt(fd, IPPROTO_IP, IP_PKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ } else {
+ return setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &get_local_ip, sizeof(get_local_ip));
+ }
+}
+
+// static
+int QuicSocketUtils::ReadPacket(int fd, char* buffer, size_t buf_len,
+ int* dropped_packets,
+ IPAddressNumber* self_address,
+ IPEndPoint* peer_address) {
+ CHECK(peer_address != NULL);
+ const int kSpaceForOverflowAndIp =
+ CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo));
+ char cbuf[kSpaceForOverflowAndIp];
+ memset(cbuf, 0, arraysize(cbuf));
+
+ iovec iov = {buffer, buf_len};
+ struct sockaddr_storage raw_address;
+ msghdr hdr;
+
+ hdr.msg_name = &raw_address;
+ hdr.msg_namelen = sizeof(sockaddr_storage);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ hdr.msg_flags = 0;
+
+ struct cmsghdr *cmsg = (struct cmsghdr *) cbuf;
+ cmsg->cmsg_len = arraysize(cbuf);
+ hdr.msg_control = cmsg;
+ hdr.msg_controllen = arraysize(cbuf);
+
+ int bytes_read = recvmsg(fd, &hdr, 0);
+
+ // Return before setting dropped packets: if we get EAGAIN, it will
+ // be 0.
+ if (bytes_read < 0 && errno != 0) {
+ if (errno != EAGAIN) {
+ LOG(ERROR) << "Error reading " << strerror(errno);
+ }
+ return -1;
+ }
+
+ if (dropped_packets != NULL) {
+ GetOverflowFromMsghdr(&hdr, dropped_packets);
+ }
+ if (self_address != NULL) {
+ *self_address = QuicSocketUtils::GetAddressFromMsghdr(&hdr);
+ }
+
+ if (raw_address.ss_family == AF_INET) {
+ CHECK(peer_address->FromSockAddr(
+ reinterpret_cast<const sockaddr*>(&raw_address),
+ sizeof(struct sockaddr_in)));
+ } else if (raw_address.ss_family == AF_INET6) {
+ CHECK(peer_address->FromSockAddr(
+ reinterpret_cast<const sockaddr*>(&raw_address),
+ sizeof(struct sockaddr_in6)));
+ }
+
+ return bytes_read;
+}
+
+// static
+int QuicSocketUtils::WritePacket(int fd, const char* buffer, size_t buf_len,
+ const IPAddressNumber& self_address,
+ const IPEndPoint& peer_address,
+ int* error) {
+ sockaddr_storage raw_address;
+ socklen_t address_len = sizeof(raw_address);
+ CHECK(peer_address.ToSockAddr(
+ reinterpret_cast<struct sockaddr*>(&raw_address),
+ &address_len));
+ iovec iov = {const_cast<char*>(buffer), buf_len};
+
+ msghdr hdr;
+ hdr.msg_name = &raw_address;
+ hdr.msg_namelen = address_len;
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+ hdr.msg_flags = 0;
+
+ const int kSpaceForIpv4 = CMSG_SPACE(sizeof(in_pktinfo));
+ const int kSpaceForIpv6 = CMSG_SPACE(sizeof(in6_pktinfo));
+ // kSpaceForIp should be big enough to hold both IPv4 and IPv6 packet info.
+ const int kSpaceForIp =
+ (kSpaceForIpv4 < kSpaceForIpv6) ? kSpaceForIpv6 : kSpaceForIpv4;
+ char cbuf[kSpaceForIp];
+ if (self_address.empty()) {
+ hdr.msg_control = 0;
+ hdr.msg_controllen = 0;
+ } else if (GetAddressFamily(self_address) == ADDRESS_FAMILY_IPV4) {
+ hdr.msg_control = cbuf;
+ hdr.msg_controllen = kSpaceForIp;
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
+
+ cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
+ memset(pktinfo, 0, sizeof(in_pktinfo));
+ pktinfo->ipi_ifindex = 0;
+ memcpy(&pktinfo->ipi_spec_dst, &self_address[0], self_address.size());
+ hdr.msg_controllen = cmsg->cmsg_len;
+ } else {
+ hdr.msg_control = cbuf;
+ hdr.msg_controllen = kSpaceForIp;
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
+
+ cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
+ memset(pktinfo, 0, sizeof(in6_pktinfo));
+ memcpy(&pktinfo->ipi6_addr, &self_address[0], self_address.size());
+ hdr.msg_controllen = cmsg->cmsg_len;
+ }
+
+ int rc = sendmsg(fd, &hdr, 0);
+ *error = (rc >= 0) ? 0 : errno;
+ return rc;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_socket_utils.h b/chromium/net/tools/quic/quic_socket_utils.h
new file mode 100644
index 00000000000..bfacc6c4719
--- /dev/null
+++ b/chromium/net/tools/quic/quic_socket_utils.h
@@ -0,0 +1,59 @@
+// Copyright (c) 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.
+//
+// Some socket related helper methods for quic.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_
+#define NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_
+
+#include <stddef.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+namespace tools {
+
+class QuicSocketUtils {
+ public:
+ // If the msghdr contains IP_PKTINFO or IPV6_PKTINFO, this will return the
+ // IPAddressNumber in that header. Returns an uninitialized IPAddress on
+ // failure.
+ static IPAddressNumber GetAddressFromMsghdr(struct msghdr *hdr);
+
+ // If the msghdr contains an SO_RXQ_OVFL entry, this will set dropped_packets
+ // to the correct value and return true. Otherwise it will return false.
+ static bool GetOverflowFromMsghdr(struct msghdr *hdr, int *dropped_packets);
+
+ // Sets either IP_PKTINFO or IPV6_PKTINFO on the socket, based on
+ // address_family. Returns the return code from setsockopt.
+ static int SetGetAddressInfo(int fd, int address_family);
+
+ // Reads buf_len from the socket. If reading is successful, returns bytes
+ // read and sets peer_address to the peer address. Otherwise returns -1.
+ //
+ // If dropped_packets is non-null, it will be set to the number of packets
+ // dropped on the socket since the socket was created, assuming the kernel
+ // supports this feature.
+ //
+ // If self_address is non-null, it will be set to the address the peer sent
+ // packets to, assuming a packet was read.
+ static int ReadPacket(int fd, char* buffer, size_t buf_len,
+ int* dropped_packets,
+ IPAddressNumber* self_address,
+ IPEndPoint* peer_address);
+
+ // Writes buf_len to the socket. If writing is successful returns the number
+ // of bytes written otherwise returns -1 and sets error to errno.
+ static int WritePacket(int fd, const char* buffer, size_t buf_len,
+ const IPAddressNumber& self_address,
+ const IPEndPoint& peer_address,
+ int* error);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_
diff --git a/chromium/net/tools/quic/quic_spdy_client_stream.cc b/chromium/net/tools/quic/quic_spdy_client_stream.cc
new file mode 100644
index 00000000000..62949534b3d
--- /dev/null
+++ b/chromium/net/tools/quic/quic_spdy_client_stream.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_spdy_client_stream.h"
+
+#include "net/spdy/spdy_framer.h"
+#include "net/tools/quic/quic_client_session.h"
+#include "net/tools/quic/spdy_utils.h"
+
+using base::StringPiece;
+using std::string;
+
+namespace net {
+namespace tools {
+
+static const size_t kHeaderBufInitialSize = 4096;
+
+QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id,
+ QuicClientSession* session)
+ : QuicReliableClientStream(id, session),
+ read_buf_(new GrowableIOBuffer()),
+ response_headers_received_(false) {
+}
+
+QuicSpdyClientStream::~QuicSpdyClientStream() {
+}
+
+uint32 QuicSpdyClientStream::ProcessData(const char* data, uint32 length) {
+ uint32 total_bytes_processed = 0;
+
+ // Are we still reading the response headers.
+ if (!response_headers_received_) {
+ // Grow the read buffer if necessary.
+ if (read_buf_->RemainingCapacity() < (int)length) {
+ read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize);
+ }
+ memcpy(read_buf_->data(), data, length);
+ read_buf_->set_offset(read_buf_->offset() + length);
+ ParseResponseHeaders();
+ } else {
+ mutable_data()->append(data + total_bytes_processed,
+ length - total_bytes_processed);
+ }
+ return length;
+}
+
+void QuicSpdyClientStream::TerminateFromPeer(bool half_close) {
+ ReliableQuicStream::TerminateFromPeer(half_close);
+ if (!response_headers_received_) {
+ Close(QUIC_BAD_APPLICATION_PAYLOAD);
+ } else if ((headers().content_length_status() ==
+ BalsaHeadersEnums::VALID_CONTENT_LENGTH) &&
+ mutable_data()->size() != headers().content_length()) {
+ Close(QUIC_BAD_APPLICATION_PAYLOAD);
+ }
+}
+
+ssize_t QuicSpdyClientStream::SendRequest(const BalsaHeaders& headers,
+ StringPiece body,
+ bool fin) {
+ SpdyHeaderBlock header_block =
+ SpdyUtils::RequestHeadersToSpdyHeaders(headers);
+
+ string headers_string =
+ session()->compressor()->CompressHeaders(header_block);
+
+ bool has_body = !body.empty();
+
+ WriteData(headers_string, fin && !has_body); // last_data
+
+ if (has_body) {
+ WriteData(body, fin);
+ }
+
+ return headers_string.size() + body.size();
+}
+
+int QuicSpdyClientStream::ParseResponseHeaders() {
+ size_t read_buf_len = static_cast<size_t>(read_buf_->offset());
+ SpdyFramer framer(SPDY3);
+ SpdyHeaderBlock headers;
+ char* data = read_buf_->StartOfBuffer();
+ size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(),
+ &headers);
+ if (len == 0) {
+ return -1;
+ }
+
+ if (!SpdyUtils::FillBalsaResponseHeaders(headers, mutable_headers())) {
+ Close(QUIC_BAD_APPLICATION_PAYLOAD);
+ return -1;
+ }
+
+ size_t delta = read_buf_len - len;
+ if (delta > 0) {
+ mutable_data()->append(data + len, delta);
+ }
+
+ return len;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_spdy_client_stream.h b/chromium/net/tools/quic/quic_spdy_client_stream.h
new file mode 100644
index 00000000000..ec4d25747f7
--- /dev/null
+++ b/chromium/net/tools/quic/quic_spdy_client_stream.h
@@ -0,0 +1,47 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_
+#define NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_
+
+#include "base/strings/string_piece.h"
+#include "net/base/io_buffer.h"
+#include "net/tools/quic/quic_reliable_client_stream.h"
+
+namespace net {
+
+class BalsaHeaders;
+
+namespace tools {
+
+class QuicClientSession;
+
+// All this does right now is send an SPDY request, and aggregate the
+// SPDY response.
+class QuicSpdyClientStream : public QuicReliableClientStream {
+ public:
+ QuicSpdyClientStream(QuicStreamId id, QuicClientSession* session);
+ virtual ~QuicSpdyClientStream();
+
+ // ReliableQuicStream implementation called by the session when there's
+ // data for us.
+ virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+
+ virtual void TerminateFromPeer(bool half_close) OVERRIDE;
+
+ virtual ssize_t SendRequest(const BalsaHeaders& headers,
+ base::StringPiece body,
+ bool fin) OVERRIDE;
+
+ private:
+ int ParseResponseHeaders();
+
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+ bool response_headers_received_;
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_
diff --git a/chromium/net/tools/quic/quic_spdy_server_stream.cc b/chromium/net/tools/quic/quic_spdy_server_stream.cc
new file mode 100644
index 00000000000..d6f3b7590ff
--- /dev/null
+++ b/chromium/net/tools/quic/quic_spdy_server_stream.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_spdy_server_stream.h"
+
+#include "net/quic/quic_session.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/tools/quic/spdy_utils.h"
+
+using std::string;
+
+namespace net {
+namespace tools {
+
+static const size_t kHeaderBufInitialSize = 4096;
+
+QuicSpdyServerStream::QuicSpdyServerStream(QuicStreamId id,
+ QuicSession* session)
+ : QuicReliableServerStream(id, session),
+ read_buf_(new GrowableIOBuffer()),
+ request_headers_received_(false) {
+}
+
+QuicSpdyServerStream::~QuicSpdyServerStream() {
+}
+
+uint32 QuicSpdyServerStream::ProcessData(const char* data, uint32 length) {
+ uint32 total_bytes_processed = 0;
+
+ // Are we still reading the request headers.
+ if (!request_headers_received_) {
+ // Grow the read buffer if necessary.
+ if (read_buf_->RemainingCapacity() < (int)length) {
+ read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize);
+ }
+ memcpy(read_buf_->data(), data, length);
+ read_buf_->set_offset(read_buf_->offset() + length);
+ ParseRequestHeaders();
+ } else {
+ mutable_body()->append(data + total_bytes_processed,
+ length - total_bytes_processed);
+ }
+ return length;
+}
+
+void QuicSpdyServerStream::TerminateFromPeer(bool half_close) {
+ ReliableQuicStream::TerminateFromPeer(half_close);
+ // This is a full close: do not send a response.
+ if (!half_close) {
+ return;
+ }
+ if (write_side_closed() || fin_buffered()) {
+ return;
+ }
+
+ if (!request_headers_received_) {
+ SendErrorResponse(); // We're not done writing headers.
+ } else if ((headers().content_length_status() ==
+ BalsaHeadersEnums::VALID_CONTENT_LENGTH) &&
+ mutable_body()->size() != headers().content_length()) {
+ SendErrorResponse(); // Invalid content length
+ } else {
+ SendResponse();
+ }
+}
+
+void QuicSpdyServerStream::SendHeaders(
+ const BalsaHeaders& response_headers) {
+ SpdyHeaderBlock header_block =
+ SpdyUtils::ResponseHeadersToSpdyHeaders(response_headers);
+ string headers =
+ session()->compressor()->CompressHeaders(header_block);
+
+ WriteData(headers, false);
+}
+
+int QuicSpdyServerStream::ParseRequestHeaders() {
+ size_t read_buf_len = static_cast<size_t>(read_buf_->offset());
+ SpdyFramer framer(SPDY3);
+ SpdyHeaderBlock headers;
+ char* data = read_buf_->StartOfBuffer();
+ size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(),
+ &headers);
+ if (len == 0) {
+ return -1;
+ }
+
+ if (!SpdyUtils::FillBalsaRequestHeaders(headers, mutable_headers())) {
+ SendErrorResponse();
+ return -1;
+ }
+
+ size_t delta = read_buf_len - len;
+ if (delta > 0) {
+ mutable_body()->append(data + len, delta);
+ }
+
+ request_headers_received_ = true;
+ return len;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_spdy_server_stream.h b/chromium/net/tools/quic/quic_spdy_server_stream.h
new file mode 100644
index 00000000000..8ed2a8f3d65
--- /dev/null
+++ b/chromium/net/tools/quic/quic_spdy_server_stream.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_
+#define NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_
+
+#include <string>
+
+#include "net/base/io_buffer.h"
+#include "net/tools/quic/quic_reliable_server_stream.h"
+
+namespace net {
+
+class QuicSession;
+
+namespace tools {
+
+// All this does right now is aggregate data, and on fin, send a cached
+// response.
+class QuicSpdyServerStream : public QuicReliableServerStream {
+ public:
+ QuicSpdyServerStream(QuicStreamId id, QuicSession* session);
+ virtual ~QuicSpdyServerStream();
+
+ // ReliableQuicStream implementation called by the session when there's
+ // data for us.
+ virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE;
+
+ virtual void SendHeaders(const BalsaHeaders& response_headers) OVERRIDE;
+
+ int ParseRequestHeaders();
+
+ protected:
+ virtual void TerminateFromPeer(bool half_close) OVERRIDE;
+
+ // Buffer into which response header data is read.
+ scoped_refptr<GrowableIOBuffer> read_buf_;
+ bool request_headers_received_;
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_
diff --git a/chromium/net/tools/quic/quic_spdy_server_stream_test.cc b/chromium/net/tools/quic/quic_spdy_server_stream_test.cc
new file mode 100644
index 00000000000..8168fcbd4c1
--- /dev/null
+++ b/chromium/net/tools/quic/quic_spdy_server_stream_test.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2013 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/tools/quic/quic_spdy_server_stream.h"
+
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/quic/test_tools/quic_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPiece;
+using net::tools::test::MockConnection;
+using net::test::MockSession;
+
+namespace net {
+namespace tools {
+namespace test {
+namespace {
+
+class QuicSpdyServerStreamTest : public ::testing::Test {
+ public:
+ QuicSpdyServerStreamTest()
+ : connection_(new MockConnection(1, IPEndPoint(), false)),
+ session_(connection_, true),
+ stream_(1, &session_) {
+ }
+
+ MockConnection* connection_;
+ MockSession session_;
+ QuicSpdyServerStream stream_;
+};
+
+TEST_F(QuicSpdyServerStreamTest, InvalidHeadersWithFin) {
+ char arr[] = {
+ 0x00, 0x00, 0x00, 0x05, // ....
+ 0x00, 0x00, 0x00, 0x05, // ....
+ 0x3a, 0x68, 0x6f, 0x73, // :hos
+ 0x74, 0x00, 0x00, 0x00, // t...
+ 0x00, 0x00, 0x00, 0x00, // ....
+ 0x07, 0x3a, 0x6d, 0x65, // .:me
+ 0x74, 0x68, 0x6f, 0x64, // thod
+ 0x00, 0x00, 0x00, 0x03, // ....
+ 0x47, 0x45, 0x54, 0x00, // GET.
+ 0x00, 0x00, 0x05, 0x3a, // ...:
+ 0x70, 0x61, 0x74, 0x68, // path
+ 0x00, 0x00, 0x00, 0x04, // ....
+ 0x2f, 0x66, 0x6f, 0x6f, // /foo
+ 0x00, 0x00, 0x00, 0x07, // ....
+ 0x3a, 0x73, 0x63, 0x68, // :sch
+ 0x65, 0x6d, 0x65, 0x00, // eme.
+ 0x00, 0x00, 0x00, 0x00, // ....
+ 0x00, 0x00, 0x08, 0x3a, // ...:
+ 0x76, 0x65, 0x72, 0x73, // vers
+ '\x96', 0x6f, 0x6e, 0x00, // <i(69)>on.
+ 0x00, 0x00, 0x08, 0x48, // ...H
+ 0x54, 0x54, 0x50, 0x2f, // TTP/
+ 0x31, 0x2e, 0x31, // 1.1
+ };
+ QuicStreamFrame frame(1, true, 0, StringPiece(arr, arraysize(arr)));
+ // Verify that we don't crash when we get a invalid headers in stream frame.
+ stream_.OnStreamFrame(frame);
+}
+
+} // namespace
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_time_wait_list_manager.cc b/chromium/net/tools/quic/quic_time_wait_list_manager.cc
new file mode 100644
index 00000000000..a2fc4f84cb6
--- /dev/null
+++ b/chromium/net/tools/quic/quic_time_wait_list_manager.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/quic_time_wait_list_manager.h"
+
+#include <errno.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_protocol.h"
+#include "net/quic/crypto/quic_decrypter.h"
+#include "net/quic/crypto/quic_encrypter.h"
+#include "net/quic/quic_clock.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/quic/quic_utils.h"
+
+using std::make_pair;
+
+namespace net {
+namespace tools {
+
+namespace {
+
+// Time period for which the guid should live in time wait state..
+const int kTimeWaitSeconds = 5;
+
+} // namespace
+
+// A very simple alarm that just informs the QuicTimeWaitListManager to clean
+// up old guids. This alarm should be unregistered and deleted before the
+// QuicTimeWaitListManager is deleted.
+class GuidCleanUpAlarm : public EpollAlarm {
+ public:
+ explicit GuidCleanUpAlarm(QuicTimeWaitListManager* time_wait_list_manager)
+ : time_wait_list_manager_(time_wait_list_manager) {
+ }
+
+ virtual int64 OnAlarm() OVERRIDE {
+ EpollAlarm::OnAlarm();
+ time_wait_list_manager_->CleanUpOldGuids();
+ // Let the time wait manager register the alarm at appropriate time.
+ return 0;
+ }
+
+ private:
+ // Not owned.
+ QuicTimeWaitListManager* time_wait_list_manager_;
+};
+
+struct QuicTimeWaitListManager::GuidAddTime {
+ GuidAddTime(QuicGuid guid, const QuicTime& time)
+ : guid(guid),
+ time_added(time) {
+ }
+
+ QuicGuid guid;
+ QuicTime time_added;
+};
+
+// This class stores pending public reset packets to be sent to clients.
+// server_address - server address on which a packet what was received for
+// a guid in time wait state.
+// client_address - address of the client that sent that packet. Needed to send
+// the public reset packet back to the client.
+// packet - the pending public reset packet that is to be sent to the client.
+// created instance takes the ownership of this packet.
+class QuicTimeWaitListManager::QueuedPacket {
+ public:
+ QueuedPacket(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicEncryptedPacket* packet)
+ : server_address_(server_address),
+ client_address_(client_address),
+ packet_(packet) {
+ }
+
+ const IPEndPoint& server_address() const { return server_address_; }
+ const IPEndPoint& client_address() const { return client_address_; }
+ QuicEncryptedPacket* packet() { return packet_.get(); }
+
+ private:
+ const IPEndPoint server_address_;
+ const IPEndPoint client_address_;
+ scoped_ptr<QuicEncryptedPacket> packet_;
+
+ DISALLOW_COPY_AND_ASSIGN(QueuedPacket);
+};
+
+QuicTimeWaitListManager::QuicTimeWaitListManager(
+ QuicPacketWriter* writer,
+ EpollServer* epoll_server)
+ : framer_(QuicVersionMax(),
+ QuicTime::Zero(), // unused
+ true),
+ epoll_server_(epoll_server),
+ kTimeWaitPeriod_(QuicTime::Delta::FromSeconds(kTimeWaitSeconds)),
+ guid_clean_up_alarm_(new GuidCleanUpAlarm(this)),
+ clock_(epoll_server),
+ writer_(writer),
+ is_write_blocked_(false) {
+ framer_.set_visitor(this);
+ SetGuidCleanUpAlarm();
+}
+
+QuicTimeWaitListManager::~QuicTimeWaitListManager() {
+ guid_clean_up_alarm_->UnregisterIfRegistered();
+ STLDeleteElements(&time_ordered_guid_list_);
+ STLDeleteElements(&pending_packets_queue_);
+}
+
+void QuicTimeWaitListManager::AddGuidToTimeWait(QuicGuid guid,
+ QuicVersion version) {
+ DCHECK(!IsGuidInTimeWait(guid));
+ // Initialize the guid with 0 packets received.
+ GuidData data(0, version);
+ guid_map_.insert(make_pair(guid, data));
+ time_ordered_guid_list_.push_back(new GuidAddTime(guid,
+ clock_.ApproximateNow()));
+}
+
+bool QuicTimeWaitListManager::IsGuidInTimeWait(QuicGuid guid) const {
+ return guid_map_.find(guid) != guid_map_.end();
+}
+
+void QuicTimeWaitListManager::ProcessPacket(
+ const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet) {
+ DCHECK(IsGuidInTimeWait(guid));
+ server_address_ = server_address;
+ client_address_ = client_address;
+
+ // Set the framer to the appropriate version for this GUID, before processing.
+ QuicVersion version = GetQuicVersionFromGuid(guid);
+ framer_.set_version(version);
+
+ framer_.ProcessPacket(packet);
+}
+
+QuicVersion QuicTimeWaitListManager::GetQuicVersionFromGuid(QuicGuid guid) {
+ GuidMapIterator it = guid_map_.find(guid);
+ DCHECK(it != guid_map_.end());
+ return (it->second).version;
+}
+
+bool QuicTimeWaitListManager::OnCanWrite() {
+ is_write_blocked_ = false;
+ while (!is_write_blocked_ && !pending_packets_queue_.empty()) {
+ QueuedPacket* queued_packet = pending_packets_queue_.front();
+ WriteToWire(queued_packet);
+ if (!is_write_blocked_) {
+ pending_packets_queue_.pop_front();
+ delete queued_packet;
+ }
+ }
+
+ return !is_write_blocked_;
+}
+
+void QuicTimeWaitListManager::OnError(QuicFramer* framer) {
+ DLOG(INFO) << QuicUtils::ErrorToString(framer->error());
+}
+
+bool QuicTimeWaitListManager::OnProtocolVersionMismatch(
+ QuicVersion received_version) {
+ // Drop such packets whose version don't match.
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnStreamFrame(const QuicStreamFrame& frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnAckFrame(const QuicAckFrame& frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnRstStreamFrame(
+ const QuicRstStreamFrame& frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame & frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnGoAwayFrame(const QuicGoAwayFrame& frame) {
+ return false;
+}
+
+bool QuicTimeWaitListManager::OnPacketHeader(const QuicPacketHeader& header) {
+ // TODO(satyamshekhar): Think about handling packets from different client
+ // addresses.
+ GuidMapIterator it = guid_map_.find(header.public_header.guid);
+ DCHECK(it != guid_map_.end());
+ // Increment the received packet count.
+ ++((it->second).num_packets);
+ if (ShouldSendPublicReset((it->second).num_packets)) {
+ // We don't need the packet anymore. Just tell the client what sequence
+ // number we rejected.
+ SendPublicReset(server_address_,
+ client_address_,
+ header.public_header.guid,
+ header.packet_sequence_number);
+ }
+ // Never process the body of the packet in time wait state.
+ return false;
+}
+
+// Returns true if the number of packets received for this guid is a power of 2
+// to throttle the number of public reset packets we send to a client.
+bool QuicTimeWaitListManager::ShouldSendPublicReset(int received_packet_count) {
+ return (received_packet_count & (received_packet_count - 1)) == 0;
+}
+
+void QuicTimeWaitListManager::SendPublicReset(
+ const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ QuicPacketSequenceNumber rejected_sequence_number) {
+ QuicPublicResetPacket packet;
+ packet.public_header.guid = guid;
+ packet.public_header.reset_flag = true;
+ packet.public_header.version_flag = false;
+ packet.rejected_sequence_number = rejected_sequence_number;
+ // TODO(satyamshekhar): generate a valid nonce for this guid.
+ packet.nonce_proof = 1010101;
+ QueuedPacket* queued_packet = new QueuedPacket(
+ server_address,
+ client_address,
+ framer_.BuildPublicResetPacket(packet));
+ // Takes ownership of the packet.
+ SendOrQueuePacket(queued_packet);
+}
+
+// Either sends the packet and deletes it or makes pending queue the
+// owner of the packet.
+void QuicTimeWaitListManager::SendOrQueuePacket(QueuedPacket* packet) {
+ if (!is_write_blocked_) {
+ // TODO(satyamshekhar): Handle packets that fail due to error other than
+ // EAGAIN or EWOULDBLOCK.
+ WriteToWire(packet);
+ }
+
+ if (is_write_blocked_) {
+ // pending_packets_queue takes the ownership of the queued packet.
+ pending_packets_queue_.push_back(packet);
+ } else {
+ delete packet;
+ }
+}
+
+void QuicTimeWaitListManager::WriteToWire(QueuedPacket* queued_packet) {
+ DCHECK(!is_write_blocked_);
+ int error;
+ int rc = writer_->WritePacket(queued_packet->packet()->data(),
+ queued_packet->packet()->length(),
+ queued_packet->server_address().address(),
+ queued_packet->client_address(),
+ this,
+ &error);
+
+ if (rc == -1) {
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ is_write_blocked_ = true;
+ } else {
+ LOG(WARNING) << "Received unknown error while sending reset packet to "
+ << queued_packet->client_address().ToString() << ": "
+ << strerror(error);
+ }
+ }
+}
+
+void QuicTimeWaitListManager::SetGuidCleanUpAlarm() {
+ guid_clean_up_alarm_->UnregisterIfRegistered();
+ int64 next_alarm_interval;
+ if (!time_ordered_guid_list_.empty()) {
+ GuidAddTime* oldest_guid = time_ordered_guid_list_.front();
+ QuicTime now = clock_.ApproximateNow();
+ DCHECK(now.Subtract(oldest_guid->time_added) < kTimeWaitPeriod_);
+ next_alarm_interval = oldest_guid->time_added
+ .Add(kTimeWaitPeriod_)
+ .Subtract(now)
+ .ToMicroseconds();
+ } else {
+ // No guids added so none will expire before kTimeWaitPeriod_.
+ next_alarm_interval = kTimeWaitPeriod_.ToMicroseconds();
+ }
+
+ epoll_server_->RegisterAlarmApproximateDelta(next_alarm_interval,
+ guid_clean_up_alarm_.get());
+}
+
+void QuicTimeWaitListManager::CleanUpOldGuids() {
+ QuicTime now = clock_.ApproximateNow();
+ while (time_ordered_guid_list_.size() > 0) {
+ DCHECK_EQ(time_ordered_guid_list_.size(), guid_map_.size());
+ GuidAddTime* oldest_guid = time_ordered_guid_list_.front();
+ if (now.Subtract(oldest_guid->time_added) < kTimeWaitPeriod_) {
+ break;
+ }
+ // This guid has lived its age, retire it now.
+ guid_map_.erase(oldest_guid->guid);
+ time_ordered_guid_list_.pop_front();
+ delete oldest_guid;
+ }
+ SetGuidCleanUpAlarm();
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/quic_time_wait_list_manager.h b/chromium/net/tools/quic/quic_time_wait_list_manager.h
new file mode 100644
index 00000000000..815b9d913bf
--- /dev/null
+++ b/chromium/net/tools/quic/quic_time_wait_list_manager.h
@@ -0,0 +1,194 @@
+// Copyright (c) 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.
+//
+// Handles packets for guids in time wait state by discarding the packet and
+// sending the clients a public reset packet with exponential backoff.
+
+#ifndef NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
+#define NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
+
+#include <deque>
+
+#include "base/containers/hash_tables.h"
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_blocked_writer_interface.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_epoll_clock.h"
+#include "net/tools/quic/quic_packet_writer.h"
+
+namespace net {
+namespace tools {
+
+class GuidCleanUpAlarm;
+
+// Maintains a list of all guids that have been recently closed. A guid lives in
+// this state for kTimeWaitPeriod. All packets received for guids in this state
+// are handed over to the QuicTimeWaitListManager by the QuicDispatcher. It also
+// decides whether we should send a public reset packet to the client which sent
+// a packet with the guid in time wait state and sends it when appropriate.
+// After the guid expires its time wait period, a new connection/session will be
+// created if a packet is received for this guid.
+class QuicTimeWaitListManager : public QuicBlockedWriterInterface,
+ public QuicFramerVisitorInterface {
+ public:
+ // writer - the entity that writes to the socket. (Owned by the dispatcher)
+ // epoll_server - used to run clean up alarms. (Owned by the dispatcher)
+ QuicTimeWaitListManager(QuicPacketWriter* writer,
+ EpollServer* epoll_server);
+ virtual ~QuicTimeWaitListManager();
+
+ // Adds the given guid to time wait state for kTimeWaitPeriod. Henceforth,
+ // any packet bearing this guid should not be processed while the guid remains
+ // in this list. Public reset packets are sent to the clients by the time wait
+ // list manager that send packets to guids in this state. DCHECKs that guid is
+ // not already on the list. Pass in the version as well so that if a public
+ // reset packet needs to be sent the framer version can be set first.
+ void AddGuidToTimeWait(QuicGuid guid, QuicVersion version);
+
+ // Returns true if the guid is in time wait state, false otherwise. Packets
+ // received for this guid should not lead to creation of new QuicSessions.
+ bool IsGuidInTimeWait(QuicGuid guid) const;
+
+ // Called when a packet is received for a guid that is in time wait state.
+ // Sends a public reset packet to the client which sent this guid. Sending
+ // of the public reset packet is throttled by using exponential back off.
+ // DCHECKs for the guid to be in time wait state.
+ // virtual to override in tests.
+ virtual void ProcessPacket(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet);
+
+ // Called by the dispatcher when the underlying socket becomes writable again,
+ // since we might need to send pending public reset packets which we didn't
+ // send because the underlying socket was write blocked.
+ virtual bool OnCanWrite() OVERRIDE;
+
+ // Used to delete guid entries that have outlived their time wait period.
+ void CleanUpOldGuids();
+
+ // FramerVisitorInterface
+ virtual void OnError(QuicFramer* framer) OVERRIDE;
+ virtual bool OnProtocolVersionMismatch(QuicVersion received_version) OVERRIDE;
+ virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE;
+ virtual void OnPacket() OVERRIDE {}
+ virtual void OnPublicResetPacket(
+ const QuicPublicResetPacket& packet) OVERRIDE {}
+ virtual void OnVersionNegotiationPacket(
+ const QuicVersionNegotiationPacket& /*packet*/) OVERRIDE {}
+
+ virtual void OnPacketComplete() OVERRIDE {}
+ // The following methods should never get called because we always return
+ // false from OnPacketHeader(). We never need to process body of a packet.
+ virtual void OnRevivedPacket() OVERRIDE {}
+ virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE {}
+ virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE;
+ virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE;
+ virtual bool OnCongestionFeedbackFrame(
+ const QuicCongestionFeedbackFrame& frame) OVERRIDE;
+ virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE;
+ virtual bool OnConnectionCloseFrame(
+ const QuicConnectionCloseFrame & frame) OVERRIDE;
+ virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE;
+ virtual void OnFecData(const QuicFecData& fec) OVERRIDE {}
+
+ QuicVersion version() const { return framer_.version(); }
+
+ protected:
+ // Exposed for tests.
+ bool is_write_blocked() const { return is_write_blocked_; }
+
+ // Decides if public reset packet should be sent for this guid based on the
+ // number of received pacekts.
+ bool ShouldSendPublicReset(int received_packet_count);
+
+ // Exposed for tests.
+ const QuicTime::Delta time_wait_period() const { return kTimeWaitPeriod_; }
+
+ // Given a GUID that exists in the time wait list, returns the QuicVersion
+ // associated with it. Used internally to set the framer version before
+ // writing the public reset packet.
+ QuicVersion GetQuicVersionFromGuid(QuicGuid guid);
+
+ private:
+ // Stores the guid and the time it was added to time wait state.
+ struct GuidAddTime;
+ // Internal structure to store pending public reset packets.
+ class QueuedPacket;
+
+ // Creates a public reset packet and sends it or queues it to be sent later.
+ void SendPublicReset(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ QuicPacketSequenceNumber rejected_sequence_number);
+
+ // Either sends the packet and deletes it or makes pending_packets_queue_ the
+ // owner of the packet.
+ void SendOrQueuePacket(QueuedPacket* packet);
+
+ // Should only be called when write_blocked_ == false. We only care if the
+ // writing was unsuccessful because the socket got blocked, which can be
+ // tested using write_blocked_ == true. In case of all other errors we drop
+ // the packet. Hence, we return void.
+ void WriteToWire(QueuedPacket* packet);
+
+ // Register the alarm with the epoll server to wake up at appropriate time.
+ void SetGuidCleanUpAlarm();
+
+ // A map from a recently closed guid to the number of packets received after
+ // the termination of the connection bound to the guid.
+ struct GuidData {
+ GuidData(int num_packets_, QuicVersion version_)
+ : num_packets(num_packets_), version(version_) {}
+ int num_packets;
+ QuicVersion version;
+ };
+ base::hash_map<QuicGuid, GuidData> guid_map_;
+ typedef base::hash_map<QuicGuid, GuidData>::iterator GuidMapIterator;
+
+ // Maintains a list of GuidAddTime elements which it owns, in the
+ // order they should be deleted.
+ std::deque<GuidAddTime*> time_ordered_guid_list_;
+
+ // Pending public reset packets that need to be sent out to the client
+ // when we are given a chance to write by the dispatcher.
+ std::deque<QueuedPacket*> pending_packets_queue_;
+
+ // Used to parse incoming packets.
+ QuicFramer framer_;
+
+ // Server and client address of the last packet processed.
+ IPEndPoint server_address_;
+ IPEndPoint client_address_;
+
+ // Used to schedule alarms to delete old guids which have been in the list for
+ // too long. Owned by the dispatcher.
+ EpollServer* epoll_server_;
+
+ // Time period for which guids should remain in time wait state.
+ const QuicTime::Delta kTimeWaitPeriod_;
+
+ // Alarm registered with the epoll server to clean up guids that have out
+ // lived their duration in time wait state.
+ scoped_ptr<GuidCleanUpAlarm> guid_clean_up_alarm_;
+
+ // Clock to efficiently measure approximate time from the epoll server.
+ QuicEpollClock clock_;
+
+ // Interface that writes given buffer to the socket. Owned by the dispatcher.
+ QuicPacketWriter* writer_;
+
+ // True if the underlying udp socket is write blocked, i.e will return EAGAIN
+ // on sendmsg.
+ bool is_write_blocked_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicTimeWaitListManager);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_
diff --git a/chromium/net/tools/quic/spdy_utils.cc b/chromium/net/tools/quic/spdy_utils.cc
new file mode 100644
index 00000000000..13d05e5e706
--- /dev/null
+++ b/chromium/net/tools/quic/spdy_utils.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/spdy_utils.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "url/gurl.h"
+
+using base::StringPiece;
+using std::pair;
+using std::string;
+
+namespace net {
+namespace tools {
+
+const char* const kV3Host = ":host";
+const char* const kV3Path = ":path";
+const char* const kV3Scheme = ":scheme";
+const char* const kV3Status = ":status";
+const char* const kV3Method = ":method";
+const char* const kV3Version = ":version";
+
+void PopulateSpdyHeaderBlock(const BalsaHeaders& headers,
+ SpdyHeaderBlock* block,
+ bool allow_empty_values) {
+ for (BalsaHeaders::const_header_lines_iterator hi =
+ headers.header_lines_begin();
+ hi != headers.header_lines_end();
+ ++hi) {
+ if ((hi->second.length() == 0) && !allow_empty_values) {
+ DLOG(INFO) << "Dropping empty header " << hi->first.as_string()
+ << " from headers";
+ continue;
+ }
+
+ // This unfortunately involves loads of copying, but its the simplest way
+ // to sort the headers and leverage the framer.
+ string name = hi->first.as_string();
+ StringToLowerASCII(&name);
+ SpdyHeaderBlock::iterator it = block->find(name);
+ if (it != block->end()) {
+ it->second.reserve(it->second.size() + 1 + hi->second.size());
+ it->second.append("\0", 1);
+ it->second.append(hi->second.data(), hi->second.size());
+ } else {
+ block->insert(make_pair(name, hi->second.as_string()));
+ }
+ }
+}
+
+void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders& headers,
+ const string& scheme,
+ const string& host_and_port,
+ const string& path,
+ SpdyHeaderBlock* block) {
+ PopulateSpdyHeaderBlock(headers, block, true);
+ StringPiece host_header = headers.GetHeader("Host");
+ if (!host_header.empty()) {
+ DCHECK(host_and_port.empty() || host_header == host_and_port);
+ block->insert(make_pair(kV3Host, host_header.as_string()));
+ } else {
+ block->insert(make_pair(kV3Host, host_and_port));
+ }
+ block->insert(make_pair(kV3Path, path));
+ block->insert(make_pair(kV3Scheme, scheme));
+
+ if (!headers.request_method().empty()) {
+ block->insert(make_pair(kV3Method, headers.request_method().as_string()));
+ }
+
+ if (!headers.request_version().empty()) {
+ (*block)[kV3Version] = headers.request_version().as_string();
+ }
+}
+
+void PopulateSpdyResponseHeaderBlock(const BalsaHeaders& headers,
+ SpdyHeaderBlock* block) {
+ string status = headers.response_code().as_string();
+ status.append(" ");
+ status.append(headers.response_reason_phrase().as_string());
+ (*block)[kV3Status] = status;
+ (*block)[kV3Version] =
+ headers.response_version().as_string();
+
+ // Empty header values are only allowed because this is spdy3.
+ PopulateSpdyHeaderBlock(headers, block, true);
+}
+
+// static
+SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdyHeaders(
+ const BalsaHeaders& request_headers) {
+ string scheme;
+ string host_and_port;
+ string path;
+
+ string url = request_headers.request_uri().as_string();
+ if (url.empty() || url[0] == '/') {
+ path = url;
+ } else {
+ GURL request_uri(url);
+ if (request_headers.request_method() == "CONNECT") {
+ path = url;
+ } else {
+ path = request_uri.path();
+ if (!request_uri.query().empty()) {
+ path = path + "?" + request_uri.query();
+ }
+ host_and_port = request_uri.host();
+ scheme = request_uri.scheme();
+ }
+ }
+
+ DCHECK(!scheme.empty());
+ DCHECK(!host_and_port.empty());
+ DCHECK(!path.empty());
+
+ SpdyHeaderBlock block;
+ PopulateSpdy3RequestHeaderBlock(
+ request_headers, scheme, host_and_port, path, &block);
+ if (block.find("host") != block.end()) {
+ block.erase(block.find("host"));
+ }
+ return block;
+}
+
+// static
+string SpdyUtils::SerializeRequestHeaders(const BalsaHeaders& request_headers) {
+ SpdyHeaderBlock block = RequestHeadersToSpdyHeaders(request_headers);
+ return SerializeUncompressedHeaders(block);
+}
+
+// static
+SpdyHeaderBlock SpdyUtils::ResponseHeadersToSpdyHeaders(
+ const BalsaHeaders& response_headers) {
+ SpdyHeaderBlock block;
+ PopulateSpdyResponseHeaderBlock(response_headers, &block);
+ return block;
+}
+
+// static
+string SpdyUtils::SerializeResponseHeaders(
+ const BalsaHeaders& response_headers) {
+ SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers);
+
+ return SerializeUncompressedHeaders(block);
+}
+
+// static
+string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) {
+ int length = SpdyFramer::GetSerializedLength(SPDY3, &headers);
+ SpdyFrameBuilder builder(length);
+ SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers);
+ scoped_ptr<SpdyFrame> block(builder.take());
+ return string(block->data(), length);
+}
+
+bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header,
+ BalsaHeaders* headers) {
+ if (header->first.empty() || header->second.empty()) {
+ return true;
+ }
+ const string& header_name = header->first;
+ return header_name.c_str()[0] == ':';
+}
+
+bool SpdyUtils::FillBalsaRequestHeaders(
+ const SpdyHeaderBlock& header_block,
+ BalsaHeaders* request_headers) {
+ typedef SpdyHeaderBlock::const_iterator BlockIt;
+
+ BlockIt host_it = header_block.find(kV3Host);
+ BlockIt path_it = header_block.find(kV3Path);
+ BlockIt scheme_it = header_block.find(kV3Scheme);
+ BlockIt method_it = header_block.find(kV3Method);
+ BlockIt end_it = header_block.end();
+ if (host_it == end_it || path_it == end_it || scheme_it == end_it ||
+ method_it == end_it) {
+ return false;
+ }
+ string url = scheme_it->second;
+ url.append("://");
+ url.append(host_it->second);
+ url.append(path_it->second);
+ request_headers->SetRequestUri(url);
+ request_headers->SetRequestMethod(method_it->second);
+
+ BlockIt cl_it = header_block.find("content-length");
+ if (cl_it != header_block.end()) {
+ int content_length;
+ if (!base::StringToInt(cl_it->second, &content_length)) {
+ return false;
+ }
+ request_headers->SetContentLength(content_length);
+ }
+
+ for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) {
+ if (!IsSpecialSpdyHeader(it, request_headers)) {
+ request_headers->AppendHeader(it->first, it->second);
+ }
+ }
+
+ return true;
+}
+
+// The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will
+// fail to parse it.
+bool ParseReasonAndStatus(StringPiece status_and_reason,
+ BalsaHeaders* headers) {
+ if (status_and_reason.size() < 5)
+ return false;
+
+ if (status_and_reason[3] != ' ')
+ return false;
+
+ const StringPiece status_str = StringPiece(status_and_reason.data(), 3);
+ int status;
+ if (!base::StringToInt(status_str, &status)) {
+ return false;
+ }
+
+ headers->SetResponseCode(status_str);
+ headers->set_parsed_response_code(status);
+
+ StringPiece reason(status_and_reason.data() + 4,
+ status_and_reason.length() - 4);
+
+ headers->SetResponseReasonPhrase(reason);
+ return true;
+}
+
+bool SpdyUtils::FillBalsaResponseHeaders(
+ const SpdyHeaderBlock& header_block,
+ BalsaHeaders* request_headers) {
+ typedef SpdyHeaderBlock::const_iterator BlockIt;
+
+ BlockIt status_it = header_block.find(kV3Status);
+ BlockIt version_it = header_block.find(kV3Version);
+ BlockIt end_it = header_block.end();
+ if (status_it == end_it || version_it == end_it) {
+ return false;
+ }
+
+ request_headers->SetRequestVersion(version_it->second);
+ if (!ParseReasonAndStatus(status_it->second, request_headers)) {
+ return false;
+ }
+ for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) {
+ if (!IsSpecialSpdyHeader(it, request_headers)) {
+ request_headers->AppendHeader(it->first, it->second);
+ }
+ }
+ return true;
+}
+
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/spdy_utils.h b/chromium/net/tools/quic/spdy_utils.h
new file mode 100644
index 00000000000..44782744526
--- /dev/null
+++ b/chromium/net/tools/quic/spdy_utils.h
@@ -0,0 +1,45 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_SPDY_UTILS_H_
+#define NET_TOOLS_QUIC_SPDY_UTILS_H_
+
+#include <string>
+
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+namespace tools {
+
+class SpdyUtils {
+ public:
+ static std::string SerializeRequestHeaders(
+ const BalsaHeaders& request_headers);
+
+ static std::string SerializeResponseHeaders(
+ const BalsaHeaders& response_headers);
+
+ static bool FillBalsaRequestHeaders(const SpdyHeaderBlock& header_block,
+ BalsaHeaders* request_headers);
+
+ static bool FillBalsaResponseHeaders(const SpdyHeaderBlock& header_block,
+ BalsaHeaders* response_headers);
+
+ static SpdyHeaderBlock RequestHeadersToSpdyHeaders(
+ const BalsaHeaders& request_headers);
+
+ static SpdyHeaderBlock ResponseHeadersToSpdyHeaders(
+ const BalsaHeaders& response_headers);
+
+ static std::string SerializeUncompressedHeaders(
+ const SpdyHeaderBlock& headers);
+};
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_SPDY_UTILS_H_
diff --git a/chromium/net/tools/quic/test_tools/http_message_test_utils.cc b/chromium/net/tools/quic/test_tools/http_message_test_utils.cc
new file mode 100644
index 00000000000..7d6df7a7649
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/http_message_test_utils.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/test_tools/http_message_test_utils.h"
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+
+using base::StringPiece;
+using std::string;
+using std::vector;
+
+namespace net {
+namespace tools {
+namespace test {
+
+namespace {
+
+//const char* kContentEncoding = "content-encoding";
+const char* kContentLength = "content-length";
+const char* kTransferCoding = "transfer-encoding";
+
+// Both kHTTPVersionString and kMethodString arrays are constructed to match
+// the enum values defined in Version and Method of HTTPMessage.
+const char* kHTTPVersionString[] = {
+ "",
+ "HTTP/0.9",
+ "HTTP/1.0",
+ "HTTP/1.1"
+};
+
+const char* kMethodString[] = {
+ "",
+ "OPTIONS",
+ "GET",
+ "HEAD",
+ "POST",
+ "PUT",
+ "DELETE",
+ "TRACE",
+ "CONNECT",
+ "MKCOL",
+ "UNLOCK",
+};
+
+// Returns true if the message represents a complete request or response.
+// Messages are considered complete if:
+// - Transfer-Encoding: chunked is present and message has a final chunk.
+// - Content-Length header is present and matches the message body length.
+// - Neither Transfer-Encoding nor Content-Length is present and message
+// is tagged as complete.
+bool IsCompleteMessage(const HTTPMessage& message) {
+ return true;
+ const BalsaHeaders* headers = message.headers();
+ StringPiece content_length = headers->GetHeader(kContentLength);
+ if (!content_length.empty()) {
+ int parsed_content_length;
+ if (!base::StringToInt(content_length, &parsed_content_length)) {
+ return false;
+ }
+ return (message.body().size() == (uint)parsed_content_length);
+ } else {
+ // Assume messages without transfer coding or content-length are
+ // tagged correctly.
+ return message.has_complete_message();
+ }
+}
+
+} // namespace
+
+HTTPMessage::Method HTTPMessage::StringToMethod(StringPiece str) {
+ // Skip the first element of the array since it is empty string.
+ for (unsigned long i = 1; i < arraysize(kMethodString); ++i) {
+ if (strncmp(str.data(), kMethodString[i], str.length()) == 0) {
+ return static_cast<HTTPMessage::Method>(i);
+ }
+ }
+ return HttpConstants::UNKNOWN_METHOD;
+}
+
+HTTPMessage::Version HTTPMessage::StringToVersion(StringPiece str) {
+ // Skip the first element of the array since it is empty string.
+ for (unsigned long i = 1; i < arraysize(kHTTPVersionString); ++i) {
+ if (strncmp(str.data(), kHTTPVersionString[i], str.length()) == 0) {
+ return static_cast<HTTPMessage::Version>(i);
+ }
+ }
+ return HttpConstants::HTTP_UNKNOWN;
+}
+
+const char* HTTPMessage::MethodToString(Method method) {
+ CHECK_LT(static_cast<size_t>(method), arraysize(kMethodString));
+ return kMethodString[method];
+}
+
+const char* HTTPMessage::VersionToString(Version version) {
+ CHECK_LT(static_cast<size_t>(version), arraysize(kHTTPVersionString));
+ return kHTTPVersionString[version];
+}
+
+HTTPMessage::HTTPMessage()
+ : is_request_(true) {
+ InitializeFields();
+}
+
+HTTPMessage::HTTPMessage(Version ver, Method request, const string& path)
+ : is_request_(true) {
+ InitializeFields();
+ if (ver != HttpConstants::HTTP_0_9) {
+ headers()->SetRequestVersion(VersionToString(ver));
+ }
+ headers()->SetRequestMethod(MethodToString(request));
+ headers()->SetRequestUri(path);
+}
+
+HTTPMessage::~HTTPMessage() {
+}
+
+void HTTPMessage::InitializeFields() {
+ has_complete_message_ = true;
+ skip_message_validation_ = false;
+}
+
+void HTTPMessage::AddHeader(const string& header, const string& value) {
+ headers()->AppendHeader(header, value);
+}
+
+void HTTPMessage::RemoveHeader(const string& header) {
+ headers()->RemoveAllOfHeader(header);
+}
+
+void HTTPMessage::ReplaceHeader(const string& header, const string& value) {
+ headers()->ReplaceOrAppendHeader(header, value);
+}
+
+void HTTPMessage::AddBody(const string& body, bool add_content_length) {
+ body_ = body;
+ // Remove any transfer-encoding that was left by a previous body.
+ RemoveHeader(kTransferCoding);
+ if (add_content_length) {
+ ReplaceHeader(kContentLength, base::IntToString(body.size()));
+ } else {
+ RemoveHeader(kContentLength);
+ }
+}
+
+void HTTPMessage::ValidateMessage() const {
+ if (skip_message_validation_) {
+ return;
+ }
+
+ vector<StringPiece> transfer_encodings;
+ headers()->GetAllOfHeader(kTransferCoding, &transfer_encodings);
+ CHECK_GE(1ul, transfer_encodings.size());
+ for (vector<StringPiece>::iterator it = transfer_encodings.begin();
+ it != transfer_encodings.end();
+ ++it) {
+ CHECK(StringPieceUtils::EqualIgnoreCase("identity", *it) ||
+ StringPieceUtils::EqualIgnoreCase("chunked", *it)) << *it;
+ }
+
+ vector<StringPiece> content_lengths;
+ headers()->GetAllOfHeader(kContentLength, &content_lengths);
+ CHECK_GE(1ul, content_lengths.size());
+
+ CHECK_EQ(has_complete_message_, IsCompleteMessage(*this));
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/http_message_test_utils.h b/chromium/net/tools/quic/test_tools/http_message_test_utils.h
new file mode 100644
index 00000000000..d389e21c8f2
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/http_message_test_utils.h
@@ -0,0 +1,133 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "net/tools/flip_server/balsa_enums.h"
+#include "net/tools/flip_server/balsa_headers.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+class HttpConstants {
+ public:
+ enum Version {
+ HTTP_UNKNOWN = 0,
+ HTTP_0_9,
+ HTTP_1_0,
+ HTTP_1_1
+ };
+
+ enum Method {
+ UNKNOWN_METHOD = 0,
+ OPTIONS,
+ GET,
+ HEAD,
+ POST,
+ PUT,
+ DELETE,
+ TRACE,
+ CONNECT,
+
+ MKCOL,
+ UNLOCK,
+ };
+};
+
+// Stripped down wrapper class which basically contains headers and a body.
+class HTTPMessage {
+ public:
+ typedef HttpConstants::Version Version;
+ typedef HttpConstants::Method Method;
+
+ // Convenient functions to map strings into enums. The string passed in is
+ // not assumed to be NULL-terminated.
+ static Version StringToVersion(base::StringPiece str);
+ static Method StringToMethod(base::StringPiece str);
+
+ static const char* MethodToString(Method method);
+ static const char* VersionToString(Version version);
+
+ // Default constructor makes an empty HTTP/1.1 GET request. This is typically
+ // used to construct a message that will be Initialize()-ed.
+ HTTPMessage();
+
+ // Build a request message
+ HTTPMessage(Version version, Method request, const std::string& path);
+
+ virtual ~HTTPMessage();
+
+ const std::string& body() const { return body_; }
+
+ // Adds a header line to the message.
+ void AddHeader(const std::string& header, const std::string& value);
+
+ // Removes a header line from the message.
+ void RemoveHeader(const std::string& header);
+
+ // A utility function which calls RemoveHeader followed by AddHeader.
+ void ReplaceHeader(const std::string& header, const std::string& value);
+
+ // Adds a body and the optional content-length header field (omitted to test
+ // read until close test case). To generate a message that has a header field
+ // of 0 content-length, call AddBody("", true).
+ // Multiple calls to AddBody()/AddChunkedBody() has the effect of overwriting
+ // the previous entry without warning.
+ void AddBody(const std::string& body, bool add_content_length);
+
+ bool has_complete_message() const { return has_complete_message_; }
+ void set_has_complete_message(bool value) { has_complete_message_ = value; }
+
+ // Do some basic http message consistency checks like:
+ // - Valid transfer-encoding header
+ // - Valid content-length header
+ // - Messages we expect to be complete are complete.
+ // This check can be disabled by setting skip_message_validation.
+ void ValidateMessage() const;
+
+ bool skip_message_validation() const { return skip_message_validation_; }
+ void set_skip_message_validation(bool value) {
+ skip_message_validation_ = value;
+ }
+
+ // Allow direct access to the body string. This should be used with caution:
+ // it will not update the request headers like AddBody and AddChunkedBody do.
+ void set_body(const std::string& body) { body_ = body; }
+
+ const BalsaHeaders* headers() const { return &headers_; }
+ BalsaHeaders* headers() { return &headers_; }
+
+ protected:
+ BalsaHeaders headers_;
+
+ std::string body_; // the body with chunked framing/gzip compression
+
+ bool is_request_;
+
+ // True if the message should be considered complete during serialization.
+ // Used by SPDY and Streamed RPC clients to decide wherever or not
+ // to include fin flags and during message validation (if enabled).
+ bool has_complete_message_;
+
+ // Allows disabling message validation when creating test messages
+ // that are intentionally invalid.
+ bool skip_message_validation_;
+
+ private:
+ void InitializeFields();
+
+ DISALLOW_COPY_AND_ASSIGN(HTTPMessage);
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_
diff --git a/chromium/net/tools/quic/test_tools/mock_epoll_server.cc b/chromium/net/tools/quic/test_tools/mock_epoll_server.cc
new file mode 100644
index 00000000000..4101c5e9ab0
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/mock_epoll_server.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/test_tools/mock_epoll_server.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+FakeTimeEpollServer::FakeTimeEpollServer(): now_in_usec_(0) {
+}
+
+FakeTimeEpollServer::~FakeTimeEpollServer() {
+}
+
+int64 FakeTimeEpollServer::NowInUsec() const {
+ return now_in_usec_;
+}
+
+MockEpollServer::MockEpollServer() : until_in_usec_(-1) {
+}
+
+MockEpollServer::~MockEpollServer() {
+}
+
+int MockEpollServer::epoll_wait_impl(int epfd,
+ struct epoll_event* events,
+ int max_events,
+ int timeout_in_ms) {
+ int num_events = 0;
+ while (!event_queue_.empty() &&
+ num_events < max_events &&
+ event_queue_.begin()->first <= NowInUsec() &&
+ ((until_in_usec_ == -1) ||
+ (event_queue_.begin()->first < until_in_usec_))
+ ) {
+ int64 event_time_in_usec = event_queue_.begin()->first;
+ events[num_events] = event_queue_.begin()->second;
+ if (event_time_in_usec > NowInUsec()) {
+ set_now_in_usec(event_time_in_usec);
+ }
+ event_queue_.erase(event_queue_.begin());
+ ++num_events;
+ }
+ if (num_events == 0) { // then we'd have waited 'till the timeout.
+ if (until_in_usec_ < 0) { // then we don't care what the final time is.
+ if (timeout_in_ms > 0) {
+ AdvanceBy(timeout_in_ms * 1000);
+ }
+ } else { // except we assume that we don't wait for the timeout
+ // period if until_in_usec_ is a positive number.
+ set_now_in_usec(until_in_usec_);
+ // And reset until_in_usec_ to signal no waiting (as
+ // the AdvanceByExactly* stuff is meant to be one-shot,
+ // as are all similar EpollServer functions)
+ until_in_usec_ = -1;
+ }
+ }
+ if (until_in_usec_ >= 0) {
+ CHECK(until_in_usec_ >= NowInUsec());
+ }
+ return num_events;
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/mock_epoll_server.h b/chromium/net/tools/quic/test_tools/mock_epoll_server.h
new file mode 100644
index 00000000000..710d5fdf772
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/mock_epoll_server.h
@@ -0,0 +1,106 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_
+
+#include "base/logging.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+// Unlike the full MockEpollServer, this only lies about the time but lets
+// fd events operate normally. Usefully when interacting with real backends
+// but wanting to skip forward in time to trigger timeouts.
+class FakeTimeEpollServer : public EpollServer {
+ public:
+ FakeTimeEpollServer();
+ virtual ~FakeTimeEpollServer();
+
+ // Replaces the EpollServer NowInUsec.
+ virtual int64 NowInUsec() const OVERRIDE;
+
+ void set_now_in_usec(int64 nius) { now_in_usec_ = nius; }
+
+ // Advances the virtual 'now' by advancement_usec.
+ void AdvanceBy(int64 advancement_usec) {
+ set_now_in_usec(NowInUsec() + advancement_usec);
+ }
+
+ // Advances the virtual 'now' by advancement_usec, and
+ // calls WaitForEventAndExecteCallbacks.
+ // Note that the WaitForEventsAndExecuteCallbacks invocation
+ // may cause NowInUs to advance beyond what was specified here.
+ // If that is not desired, use the AdvanceByExactly calls.
+ void AdvanceByAndCallCallbacks(int64 advancement_usec) {
+ AdvanceBy(advancement_usec);
+ WaitForEventsAndExecuteCallbacks();
+ }
+
+ private:
+ int64 now_in_usec_;
+};
+
+class MockEpollServer : public FakeTimeEpollServer {
+ public: // type definitions
+ typedef base::hash_multimap<int64, struct epoll_event> EventQueue;
+
+ MockEpollServer();
+ virtual ~MockEpollServer();
+
+ // time_in_usec is the time at which the event specified
+ // by 'ee' will be delivered. Note that it -is- possible
+ // to add an event for a time which has already been passed..
+ // .. upon the next time that the callbacks are invoked,
+ // all events which are in the 'past' will be delivered.
+ void AddEvent(int64 time_in_usec, const struct epoll_event& ee) {
+ event_queue_.insert(std::make_pair(time_in_usec, ee));
+ }
+
+ // Advances the virtual 'now' by advancement_usec,
+ // and ensure that the next invocation of
+ // WaitForEventsAndExecuteCallbacks goes no farther than
+ // advancement_usec from the current time.
+ void AdvanceByExactly(int64 advancement_usec) {
+ until_in_usec_ = NowInUsec() + advancement_usec;
+ set_now_in_usec(NowInUsec() + advancement_usec);
+ }
+
+ // As above, except calls WaitForEventsAndExecuteCallbacks.
+ void AdvanceByExactlyAndCallCallbacks(int64 advancement_usec) {
+ AdvanceByExactly(advancement_usec);
+ WaitForEventsAndExecuteCallbacks();
+ }
+
+ base::hash_set<AlarmCB*>::size_type NumberOfAlarms() const {
+ return all_alarms_.size();
+ }
+
+ protected: // functions
+ // These functions do nothing here, as we're not actually
+ // using the epoll_* syscalls.
+ virtual void DelFD(int fd) const OVERRIDE { }
+ virtual void AddFD(int fd, int event_mask) const OVERRIDE { }
+ virtual void ModFD(int fd, int event_mask) const OVERRIDE { }
+
+ // Replaces the epoll_server's epoll_wait_impl.
+ virtual int epoll_wait_impl(int epfd,
+ struct epoll_event* events,
+ int max_events,
+ int timeout_in_ms) OVERRIDE;
+ virtual void SetNonblocking (int fd) OVERRIDE { }
+
+ private: // members
+ EventQueue event_queue_;
+ int64 until_in_usec_;
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_
diff --git a/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc
new file mode 100644
index 00000000000..3a2b1d98252
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc
@@ -0,0 +1,21 @@
+// Copyright 2013 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/tools/quic/test_tools/mock_quic_dispatcher.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+MockQuicDispatcher::MockQuicDispatcher(
+ const QuicConfig& config,
+ const QuicCryptoServerConfig& crypto_config,
+ QuicGuid guid,
+ EpollServer* eps)
+ : QuicDispatcher(config, crypto_config, guid, eps) { }
+MockQuicDispatcher::~MockQuicDispatcher() {}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h
new file mode 100644
index 00000000000..563ab0de5fd
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h
@@ -0,0 +1,38 @@
+// Copyright 2013 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_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
+
+#include "net/base/ip_endpoint.h"
+#include "net/quic/crypto/crypto_server_config.h"
+#include "net/quic/quic_config.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/flip_server/epoll_server.h"
+#include "net/tools/quic/quic_dispatcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+class MockQuicDispatcher : public QuicDispatcher {
+ public:
+ MockQuicDispatcher(const QuicConfig& config,
+ const QuicCryptoServerConfig& crypto_config,
+ QuicGuid guid,
+ EpollServer* eps);
+ virtual ~MockQuicDispatcher();
+
+ MOCK_METHOD4(ProcessPacket, void(const IPEndPoint& server_address,
+ const IPEndPoint& client_address,
+ QuicGuid guid,
+ const QuicEncryptedPacket& packet));
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_
diff --git a/chromium/net/tools/quic/test_tools/quic_client_peer.cc b/chromium/net/tools/quic/test_tools/quic_client_peer.cc
new file mode 100644
index 00000000000..858359474c8
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_client_peer.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 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/tools/quic/test_tools/quic_client_peer.h"
+
+#include "net/tools/quic/quic_client.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+// static
+void QuicClientPeer::Reinitialize(QuicClient* client) {
+ client->initialized_ = false;
+ client->epoll_server_.UnregisterFD(client->fd_);
+ client->Initialize();
+}
+
+// static
+int QuicClientPeer::GetFd(QuicClient* client) {
+ return client->fd_;
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/quic_client_peer.h b/chromium/net/tools/quic/test_tools/quic_client_peer.h
new file mode 100644
index 00000000000..8eaa17e675d
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_client_peer.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 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_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_
+
+namespace net {
+namespace tools {
+
+class QuicClient;
+
+namespace test {
+
+class QuicClientPeer {
+ public:
+ static void Reinitialize(QuicClient* client);
+ static int GetFd(QuicClient* client);
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_
diff --git a/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc
new file mode 100644
index 00000000000..e358273d0a2
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc
@@ -0,0 +1,21 @@
+// Copyright (c) 2013 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/tools/quic/test_tools/quic_epoll_connection_helper_peer.h"
+
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+
+namespace net {
+namespace tools {
+namespace test {
+
+// static
+void QuicEpollConnectionHelperPeer::SetWriter(QuicEpollConnectionHelper* helper,
+ QuicPacketWriter* writer) {
+ helper->writer_ = writer;
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h
new file mode 100644
index 00000000000..72085dfde1c
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2013 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_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_
+
+#include "base/basictypes.h"
+
+namespace net {
+namespace tools {
+
+class QuicPacketWriter;
+class QuicEpollConnectionHelper;
+
+namespace test {
+
+class QuicEpollConnectionHelperPeer {
+ public:
+ static void SetWriter(QuicEpollConnectionHelper* helper,
+ QuicPacketWriter* writer);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuicEpollConnectionHelperPeer);
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_
diff --git a/chromium/net/tools/quic/test_tools/quic_test_client.cc b/chromium/net/tools/quic/test_tools/quic_test_client.cc
new file mode 100644
index 00000000000..859d7a56044
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_test_client.cc
@@ -0,0 +1,342 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/test_tools/quic_test_client.h"
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/cert/cert_verify_result.h"
+#include "net/cert/x509_certificate.h"
+#include "net/quic/crypto/proof_verifier.h"
+#include "net/tools/flip_server/balsa_headers.h"
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+#include "net/tools/quic/test_tools/http_message_test_utils.h"
+#include "url/gurl.h"
+
+using std::string;
+using std::vector;
+using base::StringPiece;
+
+namespace {
+
+// RecordingProofVerifier accepts any certificate chain and records the common
+// name of the leaf.
+class RecordingProofVerifier : public net::ProofVerifier {
+ public:
+ // ProofVerifier interface.
+ virtual net::ProofVerifier::Status VerifyProof(
+ net::QuicVersion version,
+ const string& hostname,
+ const string& server_config,
+ const vector<string>& certs,
+ const string& signature,
+ string* error_details,
+ scoped_ptr<net::ProofVerifyDetails>* details,
+ net::ProofVerifierCallback* callback) OVERRIDE {
+ delete callback;
+
+ common_name_.clear();
+ if (certs.empty()) {
+ return FAILURE;
+ }
+
+ // Convert certs to X509Certificate.
+ vector<StringPiece> cert_pieces(certs.size());
+ for (unsigned i = 0; i < certs.size(); i++) {
+ cert_pieces[i] = StringPiece(certs[i]);
+ }
+ scoped_refptr<net::X509Certificate> cert =
+ net::X509Certificate::CreateFromDERCertChain(cert_pieces);
+ if (!cert.get()) {
+ return FAILURE;
+ }
+
+ common_name_ = cert->subject().GetDisplayName();
+ return SUCCESS;
+ }
+
+ const string& common_name() const { return common_name_; }
+
+ private:
+ string common_name_;
+};
+
+} // anonymous namespace
+
+namespace net {
+namespace tools {
+namespace test {
+
+BalsaHeaders* MungeHeaders(const BalsaHeaders* const_headers,
+ bool secure) {
+ StringPiece uri = const_headers->request_uri();
+ if (uri.empty()) {
+ return NULL;
+ }
+ if (const_headers->request_method() == "CONNECT") {
+ return NULL;
+ }
+ BalsaHeaders* headers = new BalsaHeaders;
+ headers->CopyFrom(*const_headers);
+ if (!uri.starts_with("https://") &&
+ !uri.starts_with("http://")) {
+ // If we have a relative URL, set some defaults.
+ string full_uri = secure ? "https://www.google.com" :
+ "http://www.google.com";
+ full_uri.append(uri.as_string());
+ headers->SetRequestUri(full_uri);
+ }
+ return headers;
+}
+
+// A quic client which allows mocking out writes.
+class QuicEpollClient : public QuicClient {
+ public:
+ typedef QuicClient Super;
+
+ QuicEpollClient(IPEndPoint server_address,
+ const string& server_hostname,
+ const QuicVersion version)
+ : Super(server_address, server_hostname, version) {
+ }
+
+ QuicEpollClient(IPEndPoint server_address,
+ const string& server_hostname,
+ const QuicConfig& config,
+ const QuicVersion version)
+ : Super(server_address, server_hostname, config, version) {
+ }
+
+ virtual ~QuicEpollClient() {
+ if (connected()) {
+ Disconnect();
+ }
+ }
+
+ virtual QuicEpollConnectionHelper* CreateQuicConnectionHelper() OVERRIDE {
+ if (writer_.get() != NULL) {
+ writer_->set_fd(fd());
+ return new QuicEpollConnectionHelper(writer_.get(), epoll_server());
+ } else {
+ return Super::CreateQuicConnectionHelper();
+ }
+ }
+
+ void UseWriter(QuicTestWriter* writer) { writer_.reset(writer); }
+
+ private:
+ scoped_ptr<QuicTestWriter> writer_;
+};
+
+QuicTestClient::QuicTestClient(IPEndPoint address, const string& hostname,
+ const QuicVersion version)
+ : client_(new QuicEpollClient(address, hostname, version)) {
+ Initialize(address, hostname, true);
+}
+
+QuicTestClient::QuicTestClient(IPEndPoint address,
+ const string& hostname,
+ bool secure,
+ const QuicVersion version)
+ : client_(new QuicEpollClient(address, hostname, version)) {
+ Initialize(address, hostname, secure);
+}
+
+QuicTestClient::QuicTestClient(IPEndPoint address,
+ const string& hostname,
+ bool secure,
+ const QuicConfig& config,
+ const QuicVersion version)
+ : client_(new QuicEpollClient(address, hostname, config, version)) {
+ Initialize(address, hostname, secure);
+}
+
+void QuicTestClient::Initialize(IPEndPoint address,
+ const string& hostname,
+ bool secure) {
+ server_address_ = address;
+ stream_ = NULL;
+ stream_error_ = QUIC_STREAM_NO_ERROR;
+ bytes_read_ = 0;
+ bytes_written_= 0;
+ never_connected_ = true;
+ secure_ = secure;
+ auto_reconnect_ = false;
+ proof_verifier_ = NULL;
+ ExpectCertificates(secure_);
+}
+
+QuicTestClient::~QuicTestClient() {
+ if (stream_) {
+ stream_->set_visitor(NULL);
+ }
+}
+
+void QuicTestClient::ExpectCertificates(bool on) {
+ if (on) {
+ proof_verifier_ = new RecordingProofVerifier;
+ client_->SetProofVerifier(proof_verifier_);
+ } else {
+ proof_verifier_ = NULL;
+ client_->SetProofVerifier(NULL);
+ }
+}
+
+ssize_t QuicTestClient::SendRequest(const string& uri) {
+ HTTPMessage message(HttpConstants::HTTP_1_1, HttpConstants::GET, uri);
+ return SendMessage(message);
+}
+
+ssize_t QuicTestClient::SendMessage(const HTTPMessage& message) {
+ stream_ = NULL; // Always force creation of a stream for SendMessage.
+
+ // If we're not connected, try to find an sni hostname.
+ if (!connected()) {
+ GURL url(message.headers()->request_uri().as_string());
+ if (!url.host().empty()) {
+ client_->set_server_hostname(url.host());
+ }
+ }
+
+ QuicReliableClientStream* stream = GetOrCreateStream();
+ if (!stream) { return 0; }
+
+ scoped_ptr<BalsaHeaders> munged_headers(MungeHeaders(message.headers(),
+ secure_));
+ return GetOrCreateStream()->SendRequest(
+ munged_headers.get() ? *munged_headers.get() : *message.headers(),
+ message.body(),
+ message.has_complete_message());
+}
+
+ssize_t QuicTestClient::SendData(string data, bool last_data) {
+ QuicReliableClientStream* stream = GetOrCreateStream();
+ if (!stream) { return 0; }
+ GetOrCreateStream()->SendBody(data, last_data);
+ return data.length();
+}
+
+string QuicTestClient::SendCustomSynchronousRequest(
+ const HTTPMessage& message) {
+ SendMessage(message);
+ WaitForResponse();
+ return response_;
+}
+
+string QuicTestClient::SendSynchronousRequest(const string& uri) {
+ if (SendRequest(uri) == 0) {
+ DLOG(ERROR) << "Failed the request for uri:" << uri;
+ return "";
+ }
+ WaitForResponse();
+ return response_;
+}
+
+QuicReliableClientStream* QuicTestClient::GetOrCreateStream() {
+ if (never_connected_ == true || auto_reconnect_) {
+ if (!connected()) {
+ Connect();
+ }
+ if (!connected()) {
+ return NULL;
+ }
+ }
+ if (!stream_) {
+ stream_ = client_->CreateReliableClientStream();
+ if (stream_ != NULL) {
+ stream_->set_visitor(this);
+ }
+ }
+ return stream_;
+}
+
+const string& QuicTestClient::cert_common_name() const {
+ return reinterpret_cast<RecordingProofVerifier*>(proof_verifier_)
+ ->common_name();
+}
+
+bool QuicTestClient::connected() const {
+ return client_->connected();
+}
+
+void QuicTestClient::WaitForResponse() {
+ if (stream_ == NULL) {
+ // The client has likely disconnected.
+ return;
+ }
+ client_->WaitForStreamToClose(stream_->id());
+}
+
+void QuicTestClient::Connect() {
+ DCHECK(!connected());
+ client_->Initialize();
+ client_->Connect();
+ never_connected_ = false;
+}
+
+void QuicTestClient::ResetConnection() {
+ Disconnect();
+ Connect();
+}
+
+void QuicTestClient::Disconnect() {
+ client_->Disconnect();
+}
+
+IPEndPoint QuicTestClient::LocalSocketAddress() const {
+ return client_->client_address();
+}
+
+void QuicTestClient::ClearPerRequestState() {
+ stream_error_ = QUIC_STREAM_NO_ERROR;
+ stream_ = NULL;
+ response_ = "";
+ headers_.Clear();
+ bytes_read_ = 0;
+ bytes_written_ = 0;
+}
+
+void QuicTestClient::WaitForInitialResponse() {
+ DCHECK(stream_ != NULL);
+ while (stream_ && stream_->stream_bytes_read() == 0) {
+ client_->WaitForEvents();
+ }
+}
+
+ssize_t QuicTestClient::Send(const void *buffer, size_t size) {
+ return SendData(string(static_cast<const char*>(buffer), size), false);
+}
+
+int QuicTestClient::response_size() const {
+ return bytes_read_;
+}
+
+size_t QuicTestClient::bytes_read() const {
+ return bytes_read_;
+}
+
+size_t QuicTestClient::bytes_written() const {
+ return bytes_written_;
+}
+
+void QuicTestClient::OnClose(ReliableQuicStream* stream) {
+ if (stream_ != stream) {
+ return;
+ }
+ response_ = stream_->data();
+ headers_.CopyFrom(stream_->headers());
+ stream_error_ = stream_->stream_error();
+ bytes_read_ = stream_->stream_bytes_read();
+ bytes_written_ = stream_->stream_bytes_written();
+ stream_ = NULL;
+}
+
+void QuicTestClient::UseWriter(QuicTestWriter* writer) {
+ DCHECK(!connected());
+ reinterpret_cast<QuicEpollClient*>(client_.get())->UseWriter(writer);
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/quic_test_client.h b/chromium/net/tools/quic/test_tools/quic_test_client.h
new file mode 100644
index 00000000000..74bfc24646a
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_test_client.h
@@ -0,0 +1,143 @@
+// Copyright (c) 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.
+
+#ifndef NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
+#define NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/quic/quic_framer.h"
+#include "net/quic/quic_packet_creator.h"
+#include "net/quic/quic_protocol.h"
+#include "net/tools/quic/quic_client.h"
+#include "net/tools/quic/quic_packet_writer.h"
+
+namespace net {
+
+class ProofVerifier;
+
+namespace tools {
+
+namespace test {
+
+// Allows setting a writer for the client's QuicConnectionHelper, to allow
+// fine-grained control of writes.
+class QuicTestWriter : public QuicPacketWriter {
+ public:
+ virtual ~QuicTestWriter() {}
+ virtual void set_fd(int fd) = 0;
+};
+
+class HTTPMessage;
+
+// A toy QUIC client used for testing.
+class QuicTestClient : public ReliableQuicStream::Visitor {
+ public:
+ QuicTestClient(IPEndPoint server_address, const string& server_hostname,
+ const QuicVersion version);
+ QuicTestClient(IPEndPoint server_address,
+ const string& server_hostname,
+ bool secure,
+ const QuicVersion version);
+ QuicTestClient(IPEndPoint server_address,
+ const string& server_hostname,
+ bool secure,
+ const QuicConfig& config,
+ const QuicVersion version);
+
+ virtual ~QuicTestClient();
+
+ // ExpectCertificates controls whether the server is expected to provide
+ // certificates. The certificates, if any, are not verified, but the common
+ // name is recorded and available with |cert_common_name()|.
+ void ExpectCertificates(bool on);
+
+ // Clears any outstanding state and sends a simple GET of 'uri' to the
+ // server. Returns 0 if the request failed and no bytes were written.
+ ssize_t SendRequest(const string& uri);
+ ssize_t SendMessage(const HTTPMessage& message);
+
+ string SendCustomSynchronousRequest(const HTTPMessage& message);
+ string SendSynchronousRequest(const string& uri);
+
+ // Wraps data in a quic packet and sends it.
+ ssize_t SendData(string data, bool last_data);
+
+ QuicPacketCreator::Options* options() { return client_->options(); }
+
+ const BalsaHeaders *response_headers() const {return &headers_;}
+
+ void WaitForResponse();
+
+ void Connect();
+ void ResetConnection();
+ void Disconnect();
+ IPEndPoint LocalSocketAddress() const;
+ void ClearPerRequestState();
+ void WaitForInitialResponse();
+ ssize_t Send(const void *buffer, size_t size);
+ int response_size() const;
+ size_t bytes_read() const;
+ size_t bytes_written() const;
+
+ // From ReliableQuicStream::Visitor
+ virtual void OnClose(ReliableQuicStream* stream) OVERRIDE;
+
+ // Configures client_ to take ownership of and use the writer.
+ // Must be called before initial connect.
+ void UseWriter(QuicTestWriter* writer);
+
+ // Returns NULL if the maximum number of streams have already been created.
+ QuicReliableClientStream* GetOrCreateStream();
+
+ QuicRstStreamErrorCode stream_error() { return stream_error_; }
+ QuicErrorCode connection_error() { return client()->session()->error(); }
+
+ QuicClient* client() { return client_.get(); }
+
+ // cert_common_name returns the common name value of the server's certificate,
+ // or the empty string if no certificate was presented.
+ const string& cert_common_name() const;
+
+ const string& response_body() {return response_;}
+ bool connected() const;
+
+ void set_auto_reconnect(bool reconnect) { auto_reconnect_ = reconnect; }
+
+ private:
+ void Initialize(IPEndPoint address, const string& hostname, bool secure);
+
+ IPEndPoint server_address_;
+ IPEndPoint client_address_;
+ scoped_ptr<QuicClient> client_; // The actual client
+ QuicReliableClientStream* stream_;
+
+ QuicRstStreamErrorCode stream_error_;
+
+ BalsaHeaders headers_;
+ string response_;
+ uint64 bytes_read_;
+ uint64 bytes_written_;
+ // True if the client has never connected before. The client will
+ // auto-connect exactly once before sending data. If something causes a
+ // connection reset, it will not automatically reconnect.
+ bool never_connected_;
+ bool secure_;
+ // If true, the client will always reconnect if necessary before creating a
+ // stream.
+ bool auto_reconnect_;
+
+ // proof_verifier_ points to a RecordingProofVerifier that is owned by
+ // client_.
+ ProofVerifier* proof_verifier_;
+};
+
+} // namespace test
+
+} // namespace tools
+} // namespace net
+
+#endif // NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_
diff --git a/chromium/net/tools/quic/test_tools/quic_test_utils.cc b/chromium/net/tools/quic/test_tools/quic_test_utils.cc
new file mode 100644
index 00000000000..95f1fb215ea
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_test_utils.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 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.
+
+#include "net/tools/quic/test_tools/quic_test_utils.h"
+
+#include "net/quic/test_tools/quic_test_utils.h"
+#include "net/tools/quic/quic_epoll_connection_helper.h"
+
+using base::StringPiece;
+using net::test::MockHelper;
+
+namespace net {
+namespace tools {
+namespace test {
+
+MockConnection::MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ int fd,
+ EpollServer* eps,
+ bool is_server)
+ : QuicConnection(guid, address,
+ new QuicEpollConnectionHelper(fd, eps), is_server,
+ QuicVersionMax()),
+ has_mock_helper_(false) {
+}
+
+MockConnection::MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ bool is_server)
+ : QuicConnection(guid, address, new testing::NiceMock<MockHelper>(),
+ is_server, QuicVersionMax()),
+ has_mock_helper_(true) {
+}
+
+MockConnection::MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper,
+ bool is_server)
+ : QuicConnection(guid, address, helper, is_server, QuicVersionMax()),
+ has_mock_helper_(false) {
+}
+
+MockConnection::~MockConnection() {
+}
+
+void MockConnection::AdvanceTime(QuicTime::Delta delta) {
+ CHECK(has_mock_helper_) << "Cannot advance time unless a MockClock is being"
+ " used";
+ static_cast<MockHelper*>(helper())->AdvanceTime(delta);
+}
+
+bool TestDecompressorVisitor::OnDecompressedData(StringPiece data) {
+ data.AppendToString(&data_);
+ return true;
+}
+
+void TestDecompressorVisitor::OnDecompressionError() {
+ error_ = true;
+}
+
+TestSession::TestSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server)
+ : QuicSession(connection, config, is_server),
+ crypto_stream_(NULL) {
+}
+
+TestSession::~TestSession() {}
+
+void TestSession::SetCryptoStream(QuicCryptoStream* stream) {
+ crypto_stream_ = stream;
+}
+
+QuicCryptoStream* TestSession::GetCryptoStream() {
+ return crypto_stream_;
+}
+
+} // namespace test
+} // namespace tools
+} // namespace net
diff --git a/chromium/net/tools/quic/test_tools/quic_test_utils.h b/chromium/net/tools/quic/test_tools/quic_test_utils.h
new file mode 100644
index 00000000000..31ea1815e06
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/quic_test_utils.h
@@ -0,0 +1,112 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "net/quic/quic_connection.h"
+#include "net/quic/quic_session.h"
+#include "net/quic/quic_spdy_decompressor.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace net {
+
+class EpollServer;
+class IPEndPoint;
+
+namespace tools {
+namespace test {
+
+std::string SerializeUncompressedHeaders(const SpdyHeaderBlock& headers);
+
+class MockConnection : public QuicConnection {
+ public:
+ // Uses a QuicConnectionHelper created with fd and eps.
+ MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ int fd,
+ EpollServer* eps,
+ bool is_server);
+ // Uses a MockHelper.
+ MockConnection(QuicGuid guid, IPEndPoint address, bool is_server);
+ MockConnection(QuicGuid guid,
+ IPEndPoint address,
+ QuicConnectionHelperInterface* helper, bool is_server);
+ virtual ~MockConnection();
+
+ // If the constructor that uses a MockHelper has been used then this method
+ // will advance the time of the MockClock.
+ void AdvanceTime(QuicTime::Delta delta);
+
+ MOCK_METHOD3(ProcessUdpPacket, void(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet));
+ MOCK_METHOD1(SendConnectionClose, void(QuicErrorCode error));
+ MOCK_METHOD2(SendConnectionCloseWithDetails, void(
+ QuicErrorCode error,
+ const std::string& details));
+ MOCK_METHOD2(SendRstStream, void(QuicStreamId id,
+ QuicRstStreamErrorCode error));
+ MOCK_METHOD3(SendGoAway, void(QuicErrorCode error,
+ QuicStreamId last_good_stream_id,
+ const std::string& reason));
+ MOCK_METHOD0(OnCanWrite, bool());
+
+ void ReallyProcessUdpPacket(const IPEndPoint& self_address,
+ const IPEndPoint& peer_address,
+ const QuicEncryptedPacket& packet) {
+ return QuicConnection::ProcessUdpPacket(self_address, peer_address, packet);
+ }
+
+ virtual bool OnProtocolVersionMismatch(QuicVersion version) { return false; }
+
+ private:
+ const bool has_mock_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockConnection);
+};
+
+class TestDecompressorVisitor : public QuicSpdyDecompressor::Visitor {
+ public:
+ virtual ~TestDecompressorVisitor() {}
+ virtual bool OnDecompressedData(base::StringPiece data) OVERRIDE;
+ virtual void OnDecompressionError() OVERRIDE;
+
+ std::string data() { return data_; }
+ bool error() { return error_; }
+
+ private:
+ std::string data_;
+ bool error_;
+};
+
+class TestSession : public QuicSession {
+ public:
+ TestSession(QuicConnection* connection,
+ const QuicConfig& config,
+ bool is_server);
+ virtual ~TestSession();
+
+ MOCK_METHOD1(CreateIncomingReliableStream,
+ ReliableQuicStream*(QuicStreamId id));
+ MOCK_METHOD0(CreateOutgoingReliableStream, ReliableQuicStream*());
+
+ void SetCryptoStream(QuicCryptoStream* stream);
+
+ virtual QuicCryptoStream* GetCryptoStream();
+
+ private:
+ QuicCryptoStream* crypto_stream_;
+ DISALLOW_COPY_AND_ASSIGN(TestSession);
+};
+
+} // namespace test
+} // namespace tools
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_
diff --git a/chromium/net/tools/quic/test_tools/run_all_unittests.cc b/chromium/net/tools/quic/test_tools/run_all_unittests.cc
new file mode 100644
index 00000000000..6d42d33eb8f
--- /dev/null
+++ b/chromium/net/tools/quic/test_tools/run_all_unittests.cc
@@ -0,0 +1,11 @@
+// Copyright (c) 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.
+
+#include "base/test/test_suite.h"
+
+int main(int argc, char** argv) {
+ base::TestSuite test_suite(argc, argv);
+
+ return test_suite.Run();
+}
diff --git a/chromium/net/tools/spdyshark/AUTHORS b/chromium/net/tools/spdyshark/AUTHORS
new file mode 100644
index 00000000000..5643b2000b9
--- /dev/null
+++ b/chromium/net/tools/spdyshark/AUTHORS
@@ -0,0 +1,2 @@
+Author:
+Eric Shienbrood <ers@google.com>
diff --git a/chromium/net/tools/spdyshark/COPYING b/chromium/net/tools/spdyshark/COPYING
new file mode 100644
index 00000000000..d60c31a97a5
--- /dev/null
+++ b/chromium/net/tools/spdyshark/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/chromium/net/tools/spdyshark/ChangeLog b/chromium/net/tools/spdyshark/ChangeLog
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/tools/spdyshark/ChangeLog
diff --git a/chromium/net/tools/spdyshark/INSTALL b/chromium/net/tools/spdyshark/INSTALL
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/tools/spdyshark/INSTALL
diff --git a/chromium/net/tools/spdyshark/Makefile.am b/chromium/net/tools/spdyshark/Makefile.am
new file mode 100644
index 00000000000..b707baea732
--- /dev/null
+++ b/chromium/net/tools/spdyshark/Makefile.am
@@ -0,0 +1,126 @@
+# Makefile.am
+# Automake file for SPDY plugin
+#
+# $Id$
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+INCLUDES = -I$(top_srcdir) -I$(includedir)
+
+include Makefile.common
+
+if HAVE_WARNINGS_AS_ERRORS
+AM_CFLAGS = -Werror
+endif
+
+plugin_LTLIBRARIES = spdy.la
+spdy_la_SOURCES = \
+ plugin.c \
+ moduleinfo.h \
+ $(DISSECTOR_SRC) \
+ $(DISSECTOR_SUPPORT_SRC) \
+ $(DISSECTOR_INCLUDES)
+spdy_la_LDFLAGS = -module -avoid-version
+spdy_la_LIBADD = @PLUGIN_LIBS@
+
+# Libs must be cleared, or else libtool won't create a shared module.
+# If your module needs to be linked against any particular libraries,
+# add them here.
+LIBS =
+
+#
+# Build plugin.c, which contains the plugin version[] string, a
+# function plugin_register() that calls the register routines for all
+# protocols, and a function plugin_reg_handoff() that calls the handoff
+# registration routines for all protocols.
+#
+# We do this by scanning sources. If that turns out to be too slow,
+# maybe we could just require every .o file to have an register routine
+# of a given name (packet-aarp.o -> proto_register_aarp, etc.).
+#
+# Formatting conventions: The name of the proto_register_* routines an
+# proto_reg_handoff_* routines must start in column zero, or must be
+# preceded only by "void " starting in column zero, and must not be
+# inside #if.
+#
+# DISSECTOR_SRC is assumed to have all the files that need to be scanned.
+#
+# For some unknown reason, having a big "for" loop in the Makefile
+# to scan all the files doesn't work with some "make"s; they seem to
+# pass only the first few names in the list to the shell, for some
+# reason.
+#
+# Therefore, we have a script to generate the plugin.c file.
+# The shell script runs slowly, as multiple greps and seds are run
+# for each input file; this is especially slow on Windows. Therefore,
+# if Python is present (as indicated by PYTHON being defined), we run
+# a faster Python script to do that work instead.
+#
+# The first argument is the directory in which the source files live.
+# The second argument is "plugin", to indicate that we should build
+# a plugin.c file for a plugin.
+# All subsequent arguments are the files to scan.
+#
+plugin.c: $(DISSECTOR_SRC) $(top_srcdir)/tools/make-dissector-reg \
+ $(top_srcdir)/tools/make-dissector-reg.py
+ @if test -n "$(PYTHON)"; then \
+ echo Making plugin.c with python ; \
+ $(PYTHON) $(top_srcdir)/tools/make-dissector-reg.py $(srcdir) \
+ plugin $(DISSECTOR_SRC) ; \
+ else \
+ echo Making plugin.c with shell script ; \
+ $(top_srcdir)/tools/make-dissector-reg $(srcdir) \
+ $(plugin_src) plugin $(DISSECTOR_SRC) ; \
+ fi
+
+#
+# Currently plugin.c can be included in the distribution because
+# we always build all protocol dissectors. We used to have to check
+# whether or not to build the snmp dissector. If we again need to
+# variably build something, making plugin.c non-portable, uncomment
+# the dist-hook line below.
+#
+# Oh, yuk. We don't want to include "plugin.c" in the distribution, as
+# its contents depend on the configuration, and therefore we want it
+# to be built when the first "make" is done; however, Automake insists
+# on putting *all* source into the distribution.
+#
+# We work around this by having a "dist-hook" rule that deletes
+# "plugin.c", so that "dist" won't pick it up.
+#
+#dist-hook:
+# @rm -f $(distdir)/plugin.c
+
+CLEANFILES = \
+ spdy \
+ *~
+
+MAINTAINERCLEANFILES = \
+ Makefile.in \
+ plugin.c
+
+EXTRA_DIST = \
+ Makefile.common \
+ Makefile.nmake \
+ moduleinfo.nmake \
+ plugin.rc.in
+
+checkapi:
+ $(PERL) $(top_srcdir)/tools/checkAPIs.pl -g abort -g termoutput $(DISSECTOR_SRC)
diff --git a/chromium/net/tools/spdyshark/Makefile.common b/chromium/net/tools/spdyshark/Makefile.common
new file mode 100644
index 00000000000..9386f46f125
--- /dev/null
+++ b/chromium/net/tools/spdyshark/Makefile.common
@@ -0,0 +1,40 @@
+# Makefile.common for SPDY plugin
+# Contains the stuff from Makefile.am and Makefile.nmake that is
+# a) common to both files and
+# b) portable between both files
+#
+# $Id$
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# the name of the plugin
+PLUGIN_NAME = spdy
+
+# the dissector sources (without any helpers)
+DISSECTOR_SRC = \
+ packet-spdy.c
+
+# corresponding headers
+DISSECTOR_INCLUDES = \
+ packet-spdy.h
+
+# Dissector helpers. They're included in the source files in this
+# directory, but they're not dissectors themselves, i.e. they're not
+# used to generate "register.c").
+#DISSECTOR_SUPPORT_SRC =
diff --git a/chromium/net/tools/spdyshark/Makefile.nmake b/chromium/net/tools/spdyshark/Makefile.nmake
new file mode 100644
index 00000000000..d554918cef4
--- /dev/null
+++ b/chromium/net/tools/spdyshark/Makefile.nmake
@@ -0,0 +1,104 @@
+# Makefile.nmake
+# nmake file for Wireshark plugin
+#
+# $Id: Makefile.nmake 27579 2009-03-02 18:57:35Z gerald $
+#
+
+include ..\..\config.nmake
+include moduleinfo.nmake
+
+include Makefile.common
+
+CFLAGS=/WX /Zi /DHAVE_CONFIG_H /I../.. $(GLIB_CFLAGS) $(ZLIB_CFLAGS) \
+ /I$(PCAP_DIR)\include -D_U_="" $(LOCAL_CFLAGS)
+
+.c.obj::
+ $(CC) $(CFLAGS) -Fd.\ -c $<
+
+LDFLAGS = $(PLUGIN_LDFLAGS)
+
+!IFDEF ENABLE_LIBWIRESHARK
+LINK_PLUGIN_WITH=..\..\epan\libwireshark.lib $(ZLIB_LIBS)
+CFLAGS=/DHAVE_WIN32_LIBWIRESHARK_LIB /D_NEED_VAR_IMPORT_ $(CFLAGS)
+
+DISSECTOR_OBJECTS = $(DISSECTOR_SRC:.c=.obj)
+
+DISSECTOR_SUPPORT_OBJECTS = $(DISSECTOR_SUPPORT_SRC:.c=.obj)
+
+OBJECTS = $(DISSECTOR_OBJECTS) $(DISSECTOR_SUPPORT_OBJECTS) plugin.obj
+
+RESOURCE=$(PLUGIN_NAME).res
+
+all: $(PLUGIN_NAME).dll
+
+$(PLUGIN_NAME).rc : moduleinfo.nmake
+ sed -e s/@PLUGIN_NAME@/$(PLUGIN_NAME)/ \
+ -e s/@RC_MODULE_VERSION@/$(RC_MODULE_VERSION)/ \
+ -e s/@RC_VERSION@/$(RC_VERSION)/ \
+ -e s/@MODULE_VERSION@/$(MODULE_VERSION)/ \
+ -e s/@PACKAGE@/$(PACKAGE)/ \
+ -e s/@VERSION@/$(VERSION)/ \
+ -e s/@MSVC_VARIANT@/$(MSVC_VARIANT)/ \
+ < plugin.rc.in > $@
+
+$(PLUGIN_NAME).dll $(PLUGIN_NAME).exp $(PLUGIN_NAME).lib : $(OBJECTS) $(LINK_PLUGIN_WITH) $(RESOURCE)
+ link -dll /out:$(PLUGIN_NAME).dll $(LDFLAGS) $(OBJECTS) $(LINK_PLUGIN_WITH) \
+ $(GLIB_LIBS) $(RESOURCE)
+
+#
+# Build plugin.c, which contains the plugin version[] string, a
+# function plugin_register() that calls the register routines for all
+# protocols, and a function plugin_reg_handoff() that calls the handoff
+# registration routines for all protocols.
+#
+# We do this by scanning sources. If that turns out to be too slow,
+# maybe we could just require every .o file to have an register routine
+# of a given name (packet-aarp.o -> proto_register_aarp, etc.).
+#
+# Formatting conventions: The name of the proto_register_* routines an
+# proto_reg_handoff_* routines must start in column zero, or must be
+# preceded only by "void " starting in column zero, and must not be
+# inside #if.
+#
+# DISSECTOR_SRC is assumed to have all the files that need to be scanned.
+#
+# For some unknown reason, having a big "for" loop in the Makefile
+# to scan all the files doesn't work with some "make"s; they seem to
+# pass only the first few names in the list to the shell, for some
+# reason.
+#
+# Therefore, we have a script to generate the plugin.c file.
+# The shell script runs slowly, as multiple greps and seds are run
+# for each input file; this is especially slow on Windows. Therefore,
+# if Python is present (as indicated by PYTHON being defined), we run
+# a faster Python script to do that work instead.
+#
+# The first argument is the directory in which the source files live.
+# The second argument is "plugin", to indicate that we should build
+# a plugin.c file for a plugin.
+# All subsequent arguments are the files to scan.
+#
+!IFDEF PYTHON
+plugin.c: $(DISSECTOR_SRC) moduleinfo.h ../../tools/make-dissector-reg.py
+ @echo Making plugin.c (using python)
+ @$(PYTHON) "../../tools/make-dissector-reg.py" . plugin $(DISSECTOR_SRC)
+!ELSE
+plugin.c: $(DISSECTOR_SRC) moduleinfo.h ../../tools/make-dissector-reg
+ @echo Making plugin.c (using sh)
+ @$(SH) ../../tools/make-dissector-reg . plugin $(DISSECTOR_SRC)
+!ENDIF
+
+!ENDIF
+
+clean:
+ rm -f $(OBJECTS) $(RESOURCE) plugin.c *.pdb \
+ $(PLUGIN_NAME).dll $(PLUGIN_NAME).dll.manifest $(PLUGIN_NAME).lib \
+ $(PLUGIN_NAME).exp $(PLUGIN_NAME).rc
+
+distclean: clean
+
+maintainer-clean: distclean
+
+checkapi:
+# TODO: Fix api's :)
+# $(PERL) ../../tools/checkAPIs.pl -g abort -g termoutput $(DISSECTOR_SRC)
diff --git a/chromium/net/tools/spdyshark/NEWS b/chromium/net/tools/spdyshark/NEWS
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/chromium/net/tools/spdyshark/NEWS
diff --git a/chromium/net/tools/spdyshark/README b/chromium/net/tools/spdyshark/README
new file mode 100644
index 00000000000..e06515e78a5
--- /dev/null
+++ b/chromium/net/tools/spdyshark/README
@@ -0,0 +1,49 @@
+How to build SPDY dissector for Wireshark (Windows directions).
+
+1) Retrieve wireshark source code.
+
+> mkdir D:\src\wireshark
+> cd D:\src\wireshark
+> svn co http://anonsvn.wireshark.org/wireshark/trunk/ trunk
+
+2) Retrieve the spdyshark source code.
+
+> cd D:\src
+> svn co http://src.chromium.org/svn/trunk/src/net/tools/spdyshark
+
+3) Follow directions to build Wireshark from
+
+http://www.wireshark.org/docs/wsdg_html_chunked/ChSetupWin32.html
+
+ - Read all the directions, there are a number of preliminary steps.
+
+ - You may need to explicitly make C:\wireshark-win32-libs [or
+ C:\wireshark-win64-libs]
+
+ - Make sure PYTHON environment variable is set (this may not be the
+ case if using the one from depot_tools). Although wireshark seems
+ to require it, it tries to workaround missing PYTHON environment
+ variables unsuccessfully.
+
+4) Make a symbolic link for spdyshark to Wireshark
+
+> mklink /D D:\src\wireshark\trunk\plugins\spdyshark D:\src\spdyshark
+
+5) Build the plugin.
+
+> cd D:\src\wireshark\trunk\plugins\spdyshark
+> nmake -f Makefile.nmake all
+
+6) Move the plugin into the wireshark-gtk directory [should automate]
+
+> copy D:\src\wireshark\trunk\plugins\spdyshark.dll
+ D:\src\wireshark\trunk\wireshark-gtk2\plugins
+
+7) Start wireshark, and confirm that SPDY plugin is loaded.
+
+> D:\src\wireshark\trunk\wireshark-gtk2\wireshark.exe
+
+Choose Edit > Preferences to bring up the Preferences dialog.
+
+Exand "Protocols" in the left pane of the preferences dialog and
+confirm that spdyshark is there.
diff --git a/chromium/net/tools/spdyshark/README.chromium b/chromium/net/tools/spdyshark/README.chromium
new file mode 100644
index 00000000000..d8dbcf16bb7
--- /dev/null
+++ b/chromium/net/tools/spdyshark/README.chromium
@@ -0,0 +1,4 @@
+Name: Spdyshark
+URL: http://anonsvn.wireshark.org/wireshark/trunk
+License: GPL v2
+Security Critical: no
diff --git a/chromium/net/tools/spdyshark/moduleinfo.h b/chromium/net/tools/spdyshark/moduleinfo.h
new file mode 100644
index 00000000000..9e5f7c816f8
--- /dev/null
+++ b/chromium/net/tools/spdyshark/moduleinfo.h
@@ -0,0 +1,16 @@
+/* Included *after* config.h, in order to re-define these macros */
+
+#ifdef PACKAGE
+#undef PACKAGE
+#endif
+
+/* Name of package */
+#define PACKAGE "spdy"
+
+
+#ifdef VERSION
+#undef VERSION
+#endif
+
+/* Version number of package */
+#define VERSION "0.1.0"
diff --git a/chromium/net/tools/spdyshark/moduleinfo.nmake b/chromium/net/tools/spdyshark/moduleinfo.nmake
new file mode 100644
index 00000000000..bbdf7660970
--- /dev/null
+++ b/chromium/net/tools/spdyshark/moduleinfo.nmake
@@ -0,0 +1,28 @@
+#
+# $Id$
+#
+
+# The name
+PACKAGE=spdy
+
+# The version
+MODULE_VERSION_MAJOR=0
+MODULE_VERSION_MINOR=1
+MODULE_VERSION_MICRO=0
+MODULE_VERSION_EXTRA=0
+
+#
+# The RC_VERSION should be comma-separated, not dot-separated,
+# as per Graham Bloice's message in
+#
+# http://www.ethereal.com/lists/ethereal-dev/200303/msg00283.html
+#
+# "The RC_VERSION variable in config.nmake should be comma separated.
+# This allows the resources to be built correctly and the version
+# number to be correctly displayed in the explorer properties dialog
+# for the executables, and XP's tooltip, rather than 0.0.0.0."
+#
+
+MODULE_VERSION=$(MODULE_VERSION_MAJOR).$(MODULE_VERSION_MINOR).$(MODULE_VERSION_MICRO).$(MODULE_VERSION_EXTRA)
+RC_MODULE_VERSION=$(MODULE_VERSION_MAJOR),$(MODULE_VERSION_MINOR),$(MODULE_VERSION_MICRO),$(MODULE_VERSION_EXTRA)
+
diff --git a/chromium/net/tools/spdyshark/packet-spdy.c b/chromium/net/tools/spdyshark/packet-spdy.c
new file mode 100644
index 00000000000..becc585c0cb
--- /dev/null
+++ b/chromium/net/tools/spdyshark/packet-spdy.c
@@ -0,0 +1,1532 @@
+/* packet-spdy.c
+ * Routines for SPDY packet disassembly
+ * For now, the protocol spec can be found at
+ * http://dev.chromium.org/spdy/spdy-protocol
+ *
+ * Copyright 2010, Google Inc.
+ * Eric Shienbrood <ers@google.com>
+ *
+ * $Id$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * Originally based on packet-http.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <ctype.h>
+
+#include <glib.h>
+#include <epan/conversation.h>
+#include <epan/packet.h>
+#include <epan/strutil.h>
+#include <epan/base64.h>
+#include <epan/emem.h>
+#include <epan/stats_tree.h>
+
+#include <epan/req_resp_hdrs.h>
+#include "packet-spdy.h"
+#include <epan/dissectors/packet-tcp.h>
+#include <epan/dissectors/packet-ssl.h>
+#include <epan/prefs.h>
+#include <epan/expert.h>
+#include <epan/uat.h>
+
+#define SPDY_FIN 0x01
+
+/* The types of SPDY control frames */
+typedef enum _spdy_type {
+ SPDY_DATA,
+ SPDY_SYN_STREAM,
+ SPDY_SYN_REPLY,
+ SPDY_FIN_STREAM,
+ SPDY_HELLO,
+ SPDY_NOOP,
+ SPDY_PING,
+ SPDY_INVALID
+} spdy_frame_type_t;
+
+static const char *frame_type_names[] = {
+ "DATA", "SYN_STREAM", "SYN_REPLY", "FIN_STREAM", "HELLO", "NOOP",
+ "PING", "INVALID"
+};
+
+/*
+ * This structure will be tied to each SPDY frame.
+ * Note that there may be multiple SPDY frames
+ * in one packet.
+ */
+typedef struct _spdy_frame_info_t {
+ guint32 stream_id;
+ guint8 *header_block;
+ guint header_block_len;
+ guint16 frame_type;
+} spdy_frame_info_t;
+
+/*
+ * This structures keeps track of all the data frames
+ * associated with a stream, so that they can be
+ * reassembled into a single chunk.
+ */
+typedef struct _spdy_data_frame_t {
+ guint8 *data;
+ guint32 length;
+ guint32 framenum;
+} spdy_data_frame_t;
+
+typedef struct _spdy_stream_info_t {
+ gchar *content_type;
+ gchar *content_type_parameters;
+ gchar *content_encoding;
+ GSList *data_frames;
+ tvbuff_t *assembled_data;
+ guint num_data_frames;
+} spdy_stream_info_t;
+
+#include <epan/tap.h>
+
+
+static int spdy_tap = -1;
+static int spdy_eo_tap = -1;
+
+static int proto_spdy = -1;
+static int hf_spdy_syn_stream = -1;
+static int hf_spdy_syn_reply = -1;
+static int hf_spdy_control_bit = -1;
+static int hf_spdy_version = -1;
+static int hf_spdy_type = -1;
+static int hf_spdy_flags = -1;
+static int hf_spdy_flags_fin = -1;
+static int hf_spdy_length = -1;
+static int hf_spdy_header = -1;
+static int hf_spdy_header_name = -1;
+static int hf_spdy_header_name_text = -1;
+static int hf_spdy_header_value = -1;
+static int hf_spdy_header_value_text = -1;
+static int hf_spdy_streamid = -1;
+static int hf_spdy_associated_streamid = -1;
+static int hf_spdy_priority = -1;
+static int hf_spdy_num_headers = -1;
+static int hf_spdy_num_headers_string = -1;
+
+static gint ett_spdy = -1;
+static gint ett_spdy_syn_stream = -1;
+static gint ett_spdy_syn_reply = -1;
+static gint ett_spdy_fin_stream = -1;
+static gint ett_spdy_flags = -1;
+static gint ett_spdy_header = -1;
+static gint ett_spdy_header_name = -1;
+static gint ett_spdy_header_value = -1;
+
+static gint ett_spdy_encoded_entity = -1;
+
+static dissector_handle_t data_handle;
+static dissector_handle_t media_handle;
+static dissector_handle_t spdy_handle;
+
+/* Stuff for generation/handling of fields for custom HTTP headers */
+typedef struct _header_field_t {
+ gchar* header_name;
+ gchar* header_desc;
+} header_field_t;
+
+/*
+ * desegmentation of SPDY control frames
+ * (when we are over TCP or another protocol providing the desegmentation API)
+ */
+static gboolean spdy_desegment_control_frames = TRUE;
+
+/*
+ * desegmentation of SPDY data frames bodies
+ * (when we are over TCP or another protocol providing the desegmentation API)
+ * TODO let the user filter on content-type the bodies he wants desegmented
+ */
+static gboolean spdy_desegment_data_frames = TRUE;
+
+static gboolean spdy_assemble_entity_bodies = TRUE;
+
+/*
+ * Decompression of zlib encoded entities.
+ */
+#ifdef HAVE_LIBZ
+static gboolean spdy_decompress_body = TRUE;
+static gboolean spdy_decompress_headers = TRUE;
+#else
+static gboolean spdy_decompress_body = FALSE;
+static gboolean spdy_decompress_headers = FALSE;
+#endif
+static gboolean spdy_debug = FALSE;
+
+#define TCP_PORT_DAAP 3689
+
+/*
+ * SSDP is implemented atop HTTP (yes, it really *does* run over UDP).
+ */
+#define TCP_PORT_SSDP 1900
+#define UDP_PORT_SSDP 1900
+
+/*
+ * tcp and ssl ports
+ */
+
+#define TCP_DEFAULT_RANGE "80,8080"
+#define SSL_DEFAULT_RANGE "443"
+
+static range_t *global_spdy_tcp_range = NULL;
+static range_t *global_spdy_ssl_range = NULL;
+
+static range_t *spdy_tcp_range = NULL;
+static range_t *spdy_ssl_range = NULL;
+
+static const value_string vals_status_code[] = {
+ { 100, "Continue" },
+ { 101, "Switching Protocols" },
+ { 102, "Processing" },
+ { 199, "Informational - Others" },
+
+ { 200, "OK"},
+ { 201, "Created"},
+ { 202, "Accepted"},
+ { 203, "Non-authoritative Information"},
+ { 204, "No Content"},
+ { 205, "Reset Content"},
+ { 206, "Partial Content"},
+ { 207, "Multi-Status"},
+ { 299, "Success - Others"},
+
+ { 300, "Multiple Choices"},
+ { 301, "Moved Permanently"},
+ { 302, "Found"},
+ { 303, "See Other"},
+ { 304, "Not Modified"},
+ { 305, "Use Proxy"},
+ { 307, "Temporary Redirect"},
+ { 399, "Redirection - Others"},
+
+ { 400, "Bad Request"},
+ { 401, "Unauthorized"},
+ { 402, "Payment Required"},
+ { 403, "Forbidden"},
+ { 404, "Not Found"},
+ { 405, "Method Not Allowed"},
+ { 406, "Not Acceptable"},
+ { 407, "Proxy Authentication Required"},
+ { 408, "Request Time-out"},
+ { 409, "Conflict"},
+ { 410, "Gone"},
+ { 411, "Length Required"},
+ { 412, "Precondition Failed"},
+ { 413, "Request Entity Too Large"},
+ { 414, "Request-URI Too Long"},
+ { 415, "Unsupported Media Type"},
+ { 416, "Requested Range Not Satisfiable"},
+ { 417, "Expectation Failed"},
+ { 418, "I'm a teapot"}, /* RFC 2324 */
+ { 422, "Unprocessable Entity"},
+ { 423, "Locked"},
+ { 424, "Failed Dependency"},
+ { 499, "Client Error - Others"},
+
+ { 500, "Internal Server Error"},
+ { 501, "Not Implemented"},
+ { 502, "Bad Gateway"},
+ { 503, "Service Unavailable"},
+ { 504, "Gateway Time-out"},
+ { 505, "HTTP Version not supported"},
+ { 507, "Insufficient Storage"},
+ { 599, "Server Error - Others"},
+
+ { 0, NULL}
+};
+
+static const char spdy_dictionary[] =
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+ ".1statusversionurl";
+
+static void reset_decompressors(void)
+{
+ if (spdy_debug) printf("Should reset SPDY decompressors\n");
+}
+
+static spdy_conv_t *
+get_spdy_conversation_data(packet_info *pinfo)
+{
+ conversation_t *conversation;
+ spdy_conv_t *conv_data;
+ int retcode;
+
+ conversation = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+ if (spdy_debug) {
+ printf("\n===========================================\n\n");
+ printf("Conversation for frame #%d is %p\n", pinfo->fd->num, conversation);
+ if (conversation)
+ printf(" conv_data=%p\n", conversation_get_proto_data(conversation, proto_spdy));
+ }
+
+ if(!conversation) /* Conversation does not exist yet - create it */
+ conversation = conversation_new(pinfo->fd->num, &pinfo->src, &pinfo->dst, pinfo->ptype, pinfo->srcport, pinfo->destport, 0);
+
+ /* Retrieve information from conversation
+ */
+ conv_data = conversation_get_proto_data(conversation, proto_spdy);
+ if(!conv_data) {
+ /* Setup the conversation structure itself */
+ conv_data = se_alloc0(sizeof(spdy_conv_t));
+
+ conv_data->streams = NULL;
+ if (spdy_decompress_headers) {
+ conv_data->rqst_decompressor = se_alloc0(sizeof(z_stream));
+ conv_data->rply_decompressor = se_alloc0(sizeof(z_stream));
+ retcode = inflateInit(conv_data->rqst_decompressor);
+ if (retcode == Z_OK)
+ retcode = inflateInit(conv_data->rply_decompressor);
+ if (retcode != Z_OK)
+ printf("frame #%d: inflateInit() failed: %d\n", pinfo->fd->num, retcode);
+ else if (spdy_debug)
+ printf("created decompressor\n");
+ conv_data->dictionary_id = adler32(0L, Z_NULL, 0);
+ conv_data->dictionary_id = adler32(conv_data->dictionary_id,
+ spdy_dictionary,
+ sizeof(spdy_dictionary));
+ }
+
+ conversation_add_proto_data(conversation, proto_spdy, conv_data);
+ register_postseq_cleanup_routine(reset_decompressors);
+ }
+ return conv_data;
+}
+
+static void
+spdy_save_stream_info(spdy_conv_t *conv_data,
+ guint32 stream_id,
+ gchar *content_type,
+ gchar *content_type_params,
+ gchar *content_encoding)
+{
+ spdy_stream_info_t *si;
+
+ if (conv_data->streams == NULL)
+ conv_data->streams = g_array_new(FALSE, TRUE, sizeof(spdy_stream_info_t *));
+ if (stream_id < conv_data->streams->len)
+ DISSECTOR_ASSERT(g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) == NULL);
+ else
+ g_array_set_size(conv_data->streams, stream_id+1);
+ si = se_alloc(sizeof(spdy_stream_info_t));
+ si->content_type = content_type;
+ si->content_type_parameters = content_type_params;
+ si->content_encoding = content_encoding;
+ si->data_frames = NULL;
+ si->num_data_frames = 0;
+ si->assembled_data = NULL;
+ g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id) = si;
+ if (spdy_debug)
+ printf("Saved stream info for ID %u, content type %s\n", stream_id, content_type);
+}
+
+static spdy_stream_info_t *
+spdy_get_stream_info(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ if (conv_data->streams == NULL || stream_id >= conv_data->streams->len)
+ return NULL;
+ else
+ return g_array_index(conv_data->streams, spdy_stream_info_t*, stream_id);
+}
+
+static void
+spdy_add_data_chunk(spdy_conv_t *conv_data, guint32 stream_id, guint32 frame,
+ guint8 *data, guint32 length)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+
+ if (si == NULL) {
+ if (spdy_debug) printf("No stream_info found for stream %d\n", stream_id);
+ } else {
+ spdy_data_frame_t *df = g_malloc(sizeof(spdy_data_frame_t));
+ df->data = data;
+ df->length = length;
+ df->framenum = frame;
+ si->data_frames = g_slist_append(si->data_frames, df);
+ ++si->num_data_frames;
+ if (spdy_debug)
+ printf("Saved %u bytes of data for stream %u frame %u\n",
+ length, stream_id, df->framenum);
+ }
+}
+
+static void
+spdy_increment_data_chunk_count(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+ if (si != NULL)
+ ++si->num_data_frames;
+}
+
+/*
+ * Return the number of data frames saved so far for the specified stream.
+ */
+static guint
+spdy_get_num_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+
+ return si == NULL ? 0 : si->num_data_frames;
+}
+
+static spdy_stream_info_t *
+spdy_assemble_data_frames(spdy_conv_t *conv_data, guint32 stream_id)
+{
+ spdy_stream_info_t *si = spdy_get_stream_info(conv_data, stream_id);
+ tvbuff_t *tvb;
+
+ if (si == NULL)
+ return NULL;
+
+ /*
+ * Compute the total amount of data and concatenate the
+ * data chunks, if it hasn't already been done.
+ */
+ if (si->assembled_data == NULL) {
+ spdy_data_frame_t *df;
+ guint8 *data;
+ guint32 datalen;
+ guint32 offset;
+ guint32 framenum;
+ GSList *dflist = si->data_frames;
+ if (dflist == NULL)
+ return si;
+ dflist = si->data_frames;
+ datalen = 0;
+ /*
+ * I'd like to use a composite tvbuff here, but since
+ * only a real-data tvbuff can be the child of another
+ * tvb, I can't. It would be nice if this limitation
+ * could be fixed.
+ */
+ while (dflist != NULL) {
+ df = dflist->data;
+ datalen += df->length;
+ dflist = g_slist_next(dflist);
+ }
+ if (datalen != 0) {
+ data = se_alloc(datalen);
+ dflist = si->data_frames;
+ offset = 0;
+ framenum = 0;
+ while (dflist != NULL) {
+ df = dflist->data;
+ memcpy(data+offset, df->data, df->length);
+ offset += df->length;
+ dflist = g_slist_next(dflist);
+ }
+ tvb = tvb_new_real_data(data, datalen, datalen);
+ si->assembled_data = tvb;
+ }
+ }
+ return si;
+}
+
+static void
+spdy_discard_data_frames(spdy_stream_info_t *si)
+{
+ GSList *dflist = si->data_frames;
+ spdy_data_frame_t *df;
+
+ if (dflist == NULL)
+ return;
+ while (dflist != NULL) {
+ df = dflist->data;
+ if (df->data != NULL) {
+ g_free(df->data);
+ df->data = NULL;
+ }
+ dflist = g_slist_next(dflist);
+ }
+ /*g_slist_free(si->data_frames);
+ si->data_frames = NULL; */
+}
+
+// TODO(cbentzel): tvb_child_uncompress should be exported by wireshark.
+static tvbuff_t* spdy_tvb_child_uncompress(tvbuff_t *parent _U_, tvbuff_t *tvb,
+ int offset, int comprlen)
+{
+ tvbuff_t *new_tvb = tvb_uncompress(tvb, offset, comprlen);
+ if (new_tvb)
+ tvb_set_child_real_data_tvbuff (parent, new_tvb);
+ return new_tvb;
+}
+
+static int
+dissect_spdy_data_frame(tvbuff_t *tvb, int offset,
+ packet_info *pinfo,
+ proto_tree *top_level_tree,
+ proto_tree *spdy_tree,
+ proto_item *spdy_proto,
+ spdy_conv_t *conv_data)
+{
+ guint32 stream_id;
+ guint8 flags;
+ guint32 frame_length;
+ proto_item *ti;
+ proto_tree *flags_tree;
+ guint32 reported_datalen;
+ guint32 datalen;
+ dissector_table_t media_type_subdissector_table;
+ dissector_table_t port_subdissector_table;
+ dissector_handle_t handle;
+ guint num_data_frames;
+ gboolean dissected;
+
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ flags = tvb_get_guint8(tvb, offset+4);
+ frame_length = tvb_get_ntoh24(tvb, offset+5);
+
+ if (spdy_debug)
+ printf("Data frame [stream_id=%u flags=0x%x length=%d]\n",
+ stream_id, flags, frame_length);
+ if (spdy_tree) proto_item_append_text(spdy_tree, ", data frame");
+ col_add_fstr(pinfo->cinfo, COL_INFO, "DATA[%u] length=%d",
+ stream_id, frame_length);
+
+ proto_item_append_text(spdy_proto, ":%s stream=%d length=%d",
+ flags & SPDY_FIN ? " [FIN]" : "",
+ stream_id, frame_length);
+
+ proto_tree_add_boolean(spdy_tree, hf_spdy_control_bit, tvb, offset, 1, 0);
+ proto_tree_add_uint(spdy_tree, hf_spdy_streamid, tvb, offset, 4, stream_id);
+ ti = proto_tree_add_uint_format(spdy_tree, hf_spdy_flags, tvb, offset+4, 1, flags,
+ "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");
+
+ flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
+ proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, offset+4, 1, flags);
+ proto_tree_add_uint(spdy_tree, hf_spdy_length, tvb, offset+5, 3, frame_length);
+
+ datalen = tvb_length_remaining(tvb, offset);
+ if (datalen > frame_length)
+ datalen = frame_length;
+
+ reported_datalen = tvb_reported_length_remaining(tvb, offset);
+ if (reported_datalen > frame_length)
+ reported_datalen = frame_length;
+
+ num_data_frames = spdy_get_num_data_frames(conv_data, stream_id);
+ if (datalen != 0 || num_data_frames != 0) {
+ /*
+ * There's stuff left over; process it.
+ */
+ tvbuff_t *next_tvb = NULL;
+ tvbuff_t *data_tvb = NULL;
+ spdy_stream_info_t *si = NULL;
+ void *save_private_data = NULL;
+ guint8 *copied_data;
+ gboolean private_data_changed = FALSE;
+ gboolean is_single_chunk = FALSE;
+ gboolean have_entire_body;
+
+ /*
+ * Create a tvbuff for the payload.
+ */
+ if (datalen != 0) {
+ next_tvb = tvb_new_subset(tvb, offset+8, datalen,
+ reported_datalen);
+ is_single_chunk = num_data_frames == 0 && (flags & SPDY_FIN) != 0;
+ if (!pinfo->fd->flags.visited) {
+ if (!is_single_chunk) {
+ if (spdy_assemble_entity_bodies) {
+ copied_data = tvb_memdup(next_tvb, 0, datalen);
+ spdy_add_data_chunk(conv_data, stream_id, pinfo->fd->num,
+ copied_data, datalen);
+ } else
+ spdy_increment_data_chunk_count(conv_data, stream_id);
+ }
+ }
+ } else
+ is_single_chunk = (num_data_frames == 1);
+
+ if (!(flags & SPDY_FIN)) {
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ col_add_fstr(pinfo->cinfo, COL_INFO, " (partial entity)");
+ proto_item_append_text(spdy_proto, " (partial entity body)");
+ /* would like the proto item to say */
+ /* " (entity body fragment N of M)" */
+ goto body_dissected;
+ }
+ have_entire_body = is_single_chunk;
+ /*
+ * On seeing the last data frame in a stream, we can
+ * reassemble the frames into one data block.
+ */
+ si = spdy_assemble_data_frames(conv_data, stream_id);
+ if (si == NULL)
+ goto body_dissected;
+ data_tvb = si->assembled_data;
+ if (spdy_assemble_entity_bodies)
+ have_entire_body = TRUE;
+
+ if (!have_entire_body)
+ goto body_dissected;
+
+ if (data_tvb == NULL)
+ data_tvb = next_tvb;
+ else
+ add_new_data_source(pinfo, data_tvb, "Assembled entity body");
+
+ if (have_entire_body && si->content_encoding != NULL &&
+ g_ascii_strcasecmp(si->content_encoding, "identity") != 0) {
+ /*
+ * We currently can't handle, for example, "compress";
+ * just handle them as data for now.
+ *
+ * After July 7, 2004 the LZW patent expires, so support
+ * might be added then. However, I don't think that
+ * anybody ever really implemented "compress", due to
+ * the aforementioned patent.
+ */
+ tvbuff_t *uncomp_tvb = NULL;
+ proto_item *e_ti = NULL;
+ proto_item *ce_ti = NULL;
+ proto_tree *e_tree = NULL;
+
+ if (spdy_decompress_body &&
+ (g_ascii_strcasecmp(si->content_encoding, "gzip") == 0 ||
+ g_ascii_strcasecmp(si->content_encoding, "deflate")
+ == 0)) {
+ uncomp_tvb = spdy_tvb_child_uncompress(tvb, data_tvb, 0,
+ tvb_length(data_tvb));
+ }
+ /*
+ * Add the encoded entity to the protocol tree
+ */
+ e_ti = proto_tree_add_text(top_level_tree, data_tvb,
+ 0, tvb_length(data_tvb),
+ "Content-encoded entity body (%s): %u bytes",
+ si->content_encoding,
+ tvb_length(data_tvb));
+ e_tree = proto_item_add_subtree(e_ti, ett_spdy_encoded_entity);
+ if (si->num_data_frames > 1) {
+ GSList *dflist;
+ spdy_data_frame_t *df;
+ guint32 framenum;
+ ce_ti = proto_tree_add_text(e_tree, data_tvb, 0,
+ tvb_length(data_tvb),
+ "Assembled from %d frames in packet(s)", si->num_data_frames);
+ dflist = si->data_frames;
+ framenum = 0;
+ while (dflist != NULL) {
+ df = dflist->data;
+ if (framenum != df->framenum) {
+ proto_item_append_text(ce_ti, " #%u", df->framenum);
+ framenum = df->framenum;
+ }
+ dflist = g_slist_next(dflist);
+ }
+ }
+
+ if (uncomp_tvb != NULL) {
+ /*
+ * Decompression worked
+ */
+
+ /* XXX - Don't free this, since it's possible
+ * that the data was only partially
+ * decompressed, such as when desegmentation
+ * isn't enabled.
+ *
+ tvb_free(next_tvb);
+ */
+ proto_item_append_text(e_ti, " -> %u bytes", tvb_length(uncomp_tvb));
+ data_tvb = uncomp_tvb;
+ add_new_data_source(pinfo, data_tvb, "Uncompressed entity body");
+ } else {
+ if (spdy_decompress_body)
+ proto_item_append_text(e_ti, " [Error: Decompression failed]");
+ call_dissector(data_handle, data_tvb, pinfo, e_tree);
+
+ goto body_dissected;
+ }
+ }
+ if (si != NULL)
+ spdy_discard_data_frames(si);
+ /*
+ * Do subdissector checks.
+ *
+ * First, check whether some subdissector asked that they
+ * be called if something was on some particular port.
+ */
+
+ port_subdissector_table = find_dissector_table("http.port");
+ media_type_subdissector_table = find_dissector_table("media_type");
+ if (have_entire_body && port_subdissector_table != NULL)
+ handle = dissector_get_port_handle(port_subdissector_table,
+ pinfo->match_port);
+ else
+ handle = NULL;
+ if (handle == NULL && have_entire_body && si->content_type != NULL &&
+ media_type_subdissector_table != NULL) {
+ /*
+ * We didn't find any subdissector that
+ * registered for the port, and we have a
+ * Content-Type value. Is there any subdissector
+ * for that content type?
+ */
+ save_private_data = pinfo->private_data;
+ private_data_changed = TRUE;
+
+ if (si->content_type_parameters)
+ pinfo->private_data = ep_strdup(si->content_type_parameters);
+ else
+ pinfo->private_data = NULL;
+ /*
+ * Calling the string handle for the media type
+ * dissector table will set pinfo->match_string
+ * to si->content_type for us.
+ */
+ pinfo->match_string = si->content_type;
+ handle = dissector_get_string_handle(
+ media_type_subdissector_table,
+ si->content_type);
+ }
+ if (handle != NULL) {
+ /*
+ * We have a subdissector - call it.
+ */
+ dissected = call_dissector(handle, data_tvb, pinfo, top_level_tree);
+ } else
+ dissected = FALSE;
+
+ if (dissected) {
+ /*
+ * The subdissector dissected the body.
+ * Fix up the top-level item so that it doesn't
+ * include the stuff for that protocol.
+ */
+ if (ti != NULL)
+ proto_item_set_len(ti, offset);
+ } else if (have_entire_body && si->content_type != NULL) {
+ /*
+ * Calling the default media handle if there is a content-type that
+ * wasn't handled above.
+ */
+ call_dissector(media_handle, next_tvb, pinfo, top_level_tree);
+ } else {
+ /* Call the default data dissector */
+ call_dissector(data_handle, next_tvb, pinfo, top_level_tree);
+ }
+
+body_dissected:
+ /*
+ * Do *not* attempt at freeing the private data;
+ * it may be in use by subdissectors.
+ */
+ if (private_data_changed) /*restore even NULL value*/
+ pinfo->private_data = save_private_data;
+ /*
+ * We've processed "datalen" bytes worth of data
+ * (which may be no data at all); advance the
+ * offset past whatever data we've processed.
+ */
+ }
+ return frame_length + 8;
+}
+
+static guint8 *
+spdy_decompress_header_block(tvbuff_t *tvb, z_streamp decomp,
+ guint32 dictionary_id, int offset,
+ guint32 length, guint *uncomp_length)
+{
+ int retcode;
+ size_t bufsize = 16384;
+ const guint8 *hptr = tvb_get_ptr(tvb, offset, length);
+ guint8 *uncomp_block = ep_alloc(bufsize);
+ decomp->next_in = (Bytef *)hptr;
+ decomp->avail_in = length;
+ decomp->next_out = uncomp_block;
+ decomp->avail_out = bufsize;
+ retcode = inflate(decomp, Z_SYNC_FLUSH);
+ if (retcode == Z_NEED_DICT) {
+ if (decomp->adler != dictionary_id) {
+ printf("decompressor wants dictionary %#x, but we have %#x\n",
+ (guint)decomp->adler, dictionary_id);
+ } else {
+ retcode = inflateSetDictionary(decomp,
+ spdy_dictionary,
+ sizeof(spdy_dictionary));
+ if (retcode == Z_OK)
+ retcode = inflate(decomp, Z_SYNC_FLUSH);
+ }
+ }
+
+ if (retcode != Z_OK) {
+ return NULL;
+ } else {
+ *uncomp_length = bufsize - decomp->avail_out;
+ if (spdy_debug)
+ printf("Inflation SUCCEEDED. uncompressed size=%d\n", *uncomp_length);
+ if (decomp->avail_in != 0)
+ if (spdy_debug)
+ printf(" but there were %d input bytes left over\n", decomp->avail_in);
+ }
+ return se_memdup(uncomp_block, *uncomp_length);
+}
+
+/*
+ * Try to determine heuristically whether the header block is
+ * compressed. For an uncompressed block, the first two bytes
+ * gives the number of headers. Each header name and value is
+ * a two-byte length followed by ASCII characters.
+ */
+static gboolean
+spdy_check_header_compression(tvbuff_t *tvb,
+ int offset,
+ guint32 frame_length)
+{
+ guint16 length;
+ if (!tvb_bytes_exist(tvb, offset, 6))
+ return 1;
+ length = tvb_get_ntohs(tvb, offset);
+ if (length > frame_length)
+ return 1;
+ length = tvb_get_ntohs(tvb, offset+2);
+ if (length > frame_length)
+ return 1;
+ if (spdy_debug) printf("Looks like the header block is not compressed\n");
+ return 0;
+}
+
+// TODO(cbentzel): Change wireshark to export p_remove_proto_data, rather
+// than duplicating code here.
+typedef struct _spdy_frame_proto_data {
+ int proto;
+ void *proto_data;
+} spdy_frame_proto_data;
+
+static gint spdy_p_compare(gconstpointer a, gconstpointer b)
+{
+ const spdy_frame_proto_data *ap = (const spdy_frame_proto_data *)a;
+ const spdy_frame_proto_data *bp = (const spdy_frame_proto_data *)b;
+
+ if (ap -> proto > bp -> proto)
+ return 1;
+ else if (ap -> proto == bp -> proto)
+ return 0;
+ else
+ return -1;
+
+}
+
+static void spdy_p_remove_proto_data(frame_data *fd, int proto)
+{
+ spdy_frame_proto_data temp;
+ GSList *item;
+
+ temp.proto = proto;
+ temp.proto_data = NULL;
+
+ item = g_slist_find_custom(fd->pfd, (gpointer *)&temp, spdy_p_compare);
+
+ if (item) {
+ fd->pfd = g_slist_remove(fd->pfd, item->data);
+ }
+}
+
+static spdy_frame_info_t *
+spdy_save_header_block(frame_data *fd,
+ guint32 stream_id,
+ guint frame_type,
+ guint8 *header,
+ guint length)
+{
+ GSList *filist = p_get_proto_data(fd, proto_spdy);
+ spdy_frame_info_t *frame_info = se_alloc(sizeof(spdy_frame_info_t));
+ if (filist != NULL)
+ spdy_p_remove_proto_data(fd, proto_spdy);
+ frame_info->stream_id = stream_id;
+ frame_info->header_block = header;
+ frame_info->header_block_len = length;
+ frame_info->frame_type = frame_type;
+ filist = g_slist_append(filist, frame_info);
+ p_add_proto_data(fd, proto_spdy, filist);
+ return frame_info;
+ /* TODO(ers) these need to get deleted when no longer needed */
+}
+
+static spdy_frame_info_t *
+spdy_find_saved_header_block(frame_data *fd,
+ guint32 stream_id,
+ guint16 frame_type)
+{
+ GSList *filist = p_get_proto_data(fd, proto_spdy);
+ while (filist != NULL) {
+ spdy_frame_info_t *fi = filist->data;
+ if (fi->stream_id == stream_id && fi->frame_type == frame_type)
+ return fi;
+ filist = g_slist_next(filist);
+ }
+ return NULL;
+}
+
+/*
+ * Given a content type string that may contain optional parameters,
+ * return the parameter string, if any, otherwise return NULL. This
+ * also has the side effect of null terminating the content type
+ * part of the original string.
+ */
+static gchar *
+spdy_parse_content_type(gchar *content_type)
+{
+ gchar *cp = content_type;
+
+ while (*cp != '\0' && *cp != ';' && !isspace(*cp)) {
+ *cp = tolower(*cp);
+ ++cp;
+ }
+ if (*cp == '\0')
+ cp = NULL;
+
+ if (cp != NULL) {
+ *cp++ = '\0';
+ while (*cp == ';' || isspace(*cp))
+ ++cp;
+ if (*cp != '\0')
+ return cp;
+ }
+ return NULL;
+}
+
+static int
+dissect_spdy_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
+ proto_tree *tree, spdy_conv_t *conv_data)
+{
+ guint8 control_bit;
+ guint16 version;
+ guint16 frame_type;
+ guint8 flags;
+ guint32 frame_length;
+ guint32 stream_id;
+ guint32 associated_stream_id;
+ gint priority;
+ guint16 num_headers;
+ guint32 fin_status;
+ guint8 *frame_header;
+ const char *proto_tag;
+ const char *frame_type_name;
+ proto_tree *spdy_tree = NULL;
+ proto_item *ti = NULL;
+ proto_item *spdy_proto = NULL;
+ int orig_offset;
+ int hoffset;
+ int hdr_offset = 0;
+ spdy_frame_type_t spdy_type;
+ proto_tree *sub_tree;
+ proto_tree *flags_tree;
+ tvbuff_t *header_tvb = NULL;
+ gboolean headers_compressed;
+ gchar *hdr_verb = NULL;
+ gchar *hdr_url = NULL;
+ gchar *hdr_version = NULL;
+ gchar *content_type = NULL;
+ gchar *content_encoding = NULL;
+
+ /*
+ * Minimum size for a SPDY frame is 8 bytes.
+ */
+ if (tvb_reported_length_remaining(tvb, offset) < 8)
+ return -1;
+
+ proto_tag = "SPDY";
+
+ if (check_col(pinfo->cinfo, COL_PROTOCOL))
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_tag);
+
+ /*
+ * Is this a control frame or a data frame?
+ */
+ orig_offset = offset;
+ control_bit = tvb_get_bits8(tvb, offset << 3, 1);
+ if (control_bit) {
+ version = tvb_get_bits16(tvb, (offset << 3) + 1, 15, FALSE);
+ frame_type = tvb_get_ntohs(tvb, offset+2);
+ if (frame_type >= SPDY_INVALID) {
+ return -1;
+ }
+ frame_header = ep_tvb_memdup(tvb, offset, 16);
+ } else {
+ version = 1; /* avoid gcc warning */
+ frame_type = SPDY_DATA;
+ frame_header = NULL; /* avoid gcc warning */
+ }
+ frame_type_name = frame_type_names[frame_type];
+ offset += 4;
+ flags = tvb_get_guint8(tvb, offset);
+ frame_length = tvb_get_ntoh24(tvb, offset+1);
+ offset += 4;
+ /*
+ * Make sure there's as much data as the frame header says there is.
+ */
+ if ((guint)tvb_reported_length_remaining(tvb, offset) < frame_length) {
+ if (spdy_debug)
+ printf("Not enough header data: %d vs. %d\n",
+ frame_length, tvb_reported_length_remaining(tvb, offset));
+ return -1;
+ }
+ if (tree) {
+ spdy_proto = proto_tree_add_item(tree, proto_spdy, tvb, orig_offset, frame_length+8, FALSE);
+ spdy_tree = proto_item_add_subtree(spdy_proto, ett_spdy);
+ }
+
+ if (control_bit) {
+ if (spdy_debug)
+ printf("Control frame [version=%d type=%d flags=0x%x length=%d]\n",
+ version, frame_type, flags, frame_length);
+ if (tree) proto_item_append_text(spdy_tree, ", control frame");
+ } else {
+ return dissect_spdy_data_frame(tvb, orig_offset, pinfo, tree,
+ spdy_tree, spdy_proto, conv_data);
+ }
+ num_headers = 0;
+ sub_tree = NULL; /* avoid gcc warning */
+ switch (frame_type) {
+ case SPDY_SYN_STREAM:
+ case SPDY_SYN_REPLY:
+ if (tree) {
+ int hf;
+ hf = frame_type == SPDY_SYN_STREAM ? hf_spdy_syn_stream : hf_spdy_syn_reply;
+ ti = proto_tree_add_bytes(spdy_tree, hf, tvb,
+ orig_offset, 16, frame_header);
+ sub_tree = proto_item_add_subtree(ti, ett_spdy_syn_stream);
+ }
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ offset += 4;
+ if (frame_type == SPDY_SYN_STREAM) {
+ associated_stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ offset += 4;
+ priority = tvb_get_bits8(tvb, offset << 3, 2);
+ offset += 2;
+ } else {
+ // The next two bytes have no meaning in SYN_REPLY
+ offset += 2;
+ }
+ if (tree) {
+ proto_tree_add_boolean(sub_tree, hf_spdy_control_bit, tvb, orig_offset, 1, control_bit);
+ proto_tree_add_uint(sub_tree, hf_spdy_version, tvb, orig_offset, 2, version);
+ proto_tree_add_uint(sub_tree, hf_spdy_type, tvb, orig_offset+2, 2, frame_type);
+ ti = proto_tree_add_uint_format(sub_tree, hf_spdy_flags, tvb, orig_offset+4, 1, flags,
+ "Flags: 0x%02x%s", flags, flags&SPDY_FIN ? " (FIN)" : "");
+ flags_tree = proto_item_add_subtree(ti, ett_spdy_flags);
+ proto_tree_add_boolean(flags_tree, hf_spdy_flags_fin, tvb, orig_offset+4, 1, flags);
+ proto_tree_add_uint(sub_tree, hf_spdy_length, tvb, orig_offset+5, 3, frame_length);
+ proto_tree_add_uint(sub_tree, hf_spdy_streamid, tvb, orig_offset+8, 4, stream_id);
+ if (frame_type == SPDY_SYN_STREAM) {
+ proto_tree_add_uint(sub_tree, hf_spdy_associated_streamid, tvb, orig_offset+12, 4, associated_stream_id);
+ proto_tree_add_uint(sub_tree, hf_spdy_priority, tvb, orig_offset+16, 1, priority);
+ }
+ proto_item_append_text(spdy_proto, ": %s%s stream=%d length=%d",
+ frame_type_name,
+ flags & SPDY_FIN ? " [FIN]" : "",
+ stream_id, frame_length);
+ if (spdy_debug)
+ printf(" stream ID=%u priority=%d\n", stream_id, priority);
+ }
+ break;
+
+ case SPDY_FIN_STREAM:
+ stream_id = tvb_get_bits32(tvb, (offset << 3) + 1, 31, FALSE);
+ fin_status = tvb_get_ntohl(tvb, offset);
+ // TODO(ers) fill in tree and summary
+ offset += 8;
+ break;
+
+ case SPDY_HELLO:
+ // TODO(ers) fill in tree and summary
+ stream_id = 0; /* avoid gcc warning */
+ break;
+
+ default:
+ stream_id = 0; /* avoid gcc warning */
+ return -1;
+ break;
+ }
+
+ /*
+ * Process the name-value pairs one at a time, after possibly
+ * decompressing the header block.
+ */
+ if (frame_type == SPDY_SYN_STREAM || frame_type == SPDY_SYN_REPLY) {
+ headers_compressed = spdy_check_header_compression(tvb, offset, frame_length);
+ if (!spdy_decompress_headers || !headers_compressed) {
+ header_tvb = tvb;
+ hdr_offset = offset;
+ } else {
+ spdy_frame_info_t *per_frame_info =
+ spdy_find_saved_header_block(pinfo->fd,
+ stream_id,
+ frame_type == SPDY_SYN_REPLY);
+ if (per_frame_info == NULL) {
+ guint uncomp_length;
+ z_streamp decomp = frame_type == SPDY_SYN_STREAM ?
+ conv_data->rqst_decompressor : conv_data->rply_decompressor;
+ guint8 *uncomp_ptr =
+ spdy_decompress_header_block(tvb, decomp,
+ conv_data->dictionary_id,
+ offset,
+ frame_length + 8 - (offset - orig_offset),
+ &uncomp_length);
+ if (uncomp_ptr == NULL) { /* decompression failed */
+ if (spdy_debug)
+ printf("Frame #%d: Inflation failed\n", pinfo->fd->num);
+ proto_item_append_text(spdy_proto, " [Error: Header decompression failed]");
+ // Should we just bail here?
+ } else {
+ if (spdy_debug)
+ printf("Saving %u bytes of uncomp hdr\n", uncomp_length);
+ per_frame_info =
+ spdy_save_header_block(pinfo->fd, stream_id, frame_type == SPDY_SYN_REPLY,
+ uncomp_ptr, uncomp_length);
+ }
+ } else if (spdy_debug) {
+ printf("Found uncompressed header block len %u for stream %u frame_type=%d\n",
+ per_frame_info->header_block_len,
+ per_frame_info->stream_id,
+ per_frame_info->frame_type);
+ }
+ if (per_frame_info != NULL) {
+ header_tvb = tvb_new_child_real_data(tvb,
+ per_frame_info->header_block,
+ per_frame_info->header_block_len,
+ per_frame_info->header_block_len);
+ add_new_data_source(pinfo, header_tvb, "Uncompressed headers");
+ hdr_offset = 0;
+ }
+ }
+ offset = orig_offset + 8 + frame_length;
+ num_headers = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ if (header_tvb == NULL ||
+ (headers_compressed && !spdy_decompress_headers)) {
+ num_headers = 0;
+ ti = proto_tree_add_string(sub_tree, hf_spdy_num_headers_string,
+ tvb,
+ frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset + 14,
+ 2,
+ "Unknown (header block is compressed)");
+ } else
+ ti = proto_tree_add_uint(sub_tree, hf_spdy_num_headers,
+ tvb,
+ frame_type == SPDY_SYN_STREAM ? orig_offset+18 : orig_offset +14,
+ 2, num_headers);
+ }
+ spdy_type = SPDY_INVALID; /* type not known yet */
+ if (spdy_debug)
+ printf(" %d Headers:\n", num_headers);
+ if (num_headers > frame_length) {
+ printf("Number of headers is greater than frame length!\n");
+ proto_item_append_text(ti, " [Error: Number of headers is larger than frame length]");
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
+ return frame_length+8;
+ }
+ hdr_verb = hdr_url = hdr_version = content_type = content_encoding = NULL;
+ while (num_headers-- && tvb_reported_length_remaining(header_tvb, hdr_offset) != 0) {
+ gchar *header_name;
+ gchar *header_value;
+ proto_tree *header_tree;
+ proto_tree *name_tree;
+ proto_tree *value_tree;
+ proto_item *header;
+ gint16 length;
+ gint header_length = 0;
+
+ hoffset = hdr_offset;
+
+ header = proto_tree_add_item(spdy_tree, hf_spdy_header, header_tvb,
+ hdr_offset, frame_length, FALSE);
+ header_tree = proto_item_add_subtree(header, ett_spdy_header);
+
+ length = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ header_name = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
+ hdr_offset += length;
+ header_length += hdr_offset - hoffset;
+ if (tree) {
+ ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Name: %s",
+ header_name);
+ name_tree = proto_item_add_subtree(ti, ett_spdy_header_name);
+ proto_tree_add_uint(name_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
+ proto_tree_add_string_format(name_tree, hf_spdy_header_name_text, header_tvb, hoffset+2, length,
+ header_name, "Text: %s", format_text(header_name, length));
+ }
+
+ hoffset = hdr_offset;
+ length = tvb_get_ntohs(header_tvb, hdr_offset);
+ hdr_offset += 2;
+ header_value = (gchar *)tvb_get_ephemeral_string(header_tvb, hdr_offset, length);
+ hdr_offset += length;
+ header_length += hdr_offset - hoffset;
+ if (tree) {
+ ti = proto_tree_add_text(header_tree, header_tvb, hoffset, length+2, "Value: %s",
+ header_value);
+ value_tree = proto_item_add_subtree(ti, ett_spdy_header_value);
+ proto_tree_add_uint(value_tree, hf_spdy_length, header_tvb, hoffset, 2, length);
+ proto_tree_add_string_format(value_tree, hf_spdy_header_value_text, header_tvb, hoffset+2, length,
+ header_value, "Text: %s", format_text(header_value, length));
+ proto_item_append_text(header, ": %s: %s", header_name, header_value);
+ proto_item_set_len(header, header_length);
+ }
+ if (spdy_debug) printf(" %s: %s\n", header_name, header_value);
+ /*
+ * TODO(ers) check that the header name contains only legal characters.
+ */
+ if (g_ascii_strcasecmp(header_name, "method") == 0 ||
+ g_ascii_strcasecmp(header_name, "status") == 0) {
+ hdr_verb = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "url") == 0) {
+ hdr_url = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "version") == 0) {
+ hdr_version = header_value;
+ } else if (g_ascii_strcasecmp(header_name, "content-type") == 0) {
+ content_type = se_strdup(header_value);
+ } else if (g_ascii_strcasecmp(header_name, "content-encoding") == 0) {
+ content_encoding = se_strdup(header_value);
+ }
+ }
+ if (hdr_version != NULL) {
+ if (hdr_url != NULL) {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s %s",
+ frame_type_name, stream_id, hdr_verb, hdr_url, hdr_version);
+ } else {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]: %s %s",
+ frame_type_name, stream_id, hdr_verb, hdr_version);
+ }
+ } else {
+ col_add_fstr(pinfo->cinfo, COL_INFO, "%s[%d]", frame_type_name, stream_id);
+ }
+ /*
+ * If we expect data on this stream, we need to remember the content
+ * type and content encoding.
+ */
+ if (content_type != NULL && !pinfo->fd->flags.visited) {
+ gchar *content_type_params = spdy_parse_content_type(content_type);
+ spdy_save_stream_info(conv_data, stream_id, content_type,
+ content_type_params, content_encoding);
+ }
+
+ return offset - orig_offset;
+}
+
+static int
+dissect_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ spdy_conv_t *conv_data;
+ int offset = 0;
+ int len;
+ int firstpkt = 1;
+
+ /*
+ * The first byte of a SPDY packet must be either 0 or
+ * 0x80. If it's not, assume that this is not SPDY.
+ * (In theory, a data frame could have a stream ID
+ * >= 2^24, in which case it won't have 0 for a first
+ * byte, but this is a pretty reliable heuristic for
+ * now.)
+ */
+ guint8 first_byte = tvb_get_guint8(tvb, 0);
+ if (first_byte != 0x80 && first_byte != 0x0)
+ return 0;
+
+ conv_data = get_spdy_conversation_data(pinfo);
+
+ while (tvb_reported_length_remaining(tvb, offset) != 0) {
+ if (!firstpkt) {
+ col_add_fstr(pinfo->cinfo, COL_INFO, " >> ");
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ }
+ len = dissect_spdy_message(tvb, offset, pinfo, tree, conv_data);
+ if (len <= 0)
+ return 0;
+ offset += len;
+ /*
+ * OK, we've set the Protocol and Info columns for the
+ * first SPDY message; set a fence so that subsequent
+ * SPDY messages don't overwrite the Info column.
+ */
+ col_set_fence(pinfo->cinfo, COL_INFO);
+ firstpkt = 0;
+ }
+ return 1;
+}
+
+static gboolean
+dissect_spdy_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ if (!value_is_in_range(global_spdy_tcp_range, pinfo->destport) &&
+ !value_is_in_range(global_spdy_tcp_range, pinfo->srcport))
+ return FALSE;
+ return dissect_spdy(tvb, pinfo, tree) != 0;
+}
+
+static void reinit_spdy(void)
+{
+}
+
+// NMAKE complains about flags_set_truth not being constant. Duplicate
+// the values inside of it.
+static const true_false_string tfs_spdy_set_notset = { "Set", "Not set" };
+
+void
+proto_register_spdy(void)
+{
+ static hf_register_info hf[] = {
+ { &hf_spdy_syn_stream,
+ { "Syn Stream", "spdy.syn_stream",
+ FT_BYTES, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_syn_reply,
+ { "Syn Reply", "spdy.syn_reply",
+ FT_BYTES, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_control_bit,
+ { "Control bit", "spdy.control_bit",
+ FT_BOOLEAN, BASE_NONE, NULL, 0x0,
+ "TRUE if SPDY control frame", HFILL }},
+ { &hf_spdy_version,
+ { "Version", "spdy.version",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_type,
+ { "Type", "spdy.type",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_flags,
+ { "Flags", "spdy.flags",
+ FT_UINT8, BASE_HEX, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_flags_fin,
+ { "Fin", "spdy.flags.fin",
+ FT_BOOLEAN, 8, TFS(&tfs_spdy_set_notset),
+ SPDY_FIN, "", HFILL }},
+ { &hf_spdy_length,
+ { "Length", "spdy.length",
+ FT_UINT24, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header,
+ { "Header", "spdy.header",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_name,
+ { "Name", "spdy.header.name",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_name_text,
+ { "Text", "spdy.header.name.text",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_value,
+ { "Value", "spdy.header.value",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_header_value_text,
+ { "Text", "spdy.header.value.text",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_streamid,
+ { "Stream ID", "spdy.streamid",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_associated_streamid,
+ { "Associated Stream ID", "spdy.associated.streamid",
+ FT_UINT32, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_priority,
+ { "Priority", "spdy.priority",
+ FT_UINT8, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_num_headers,
+ { "Number of headers", "spdy.numheaders",
+ FT_UINT16, BASE_DEC, NULL, 0x0,
+ "", HFILL }},
+ { &hf_spdy_num_headers_string,
+ { "Number of headers", "spdy.numheaders",
+ FT_STRING, BASE_NONE, NULL, 0x0,
+ "", HFILL }},
+ };
+ static gint *ett[] = {
+ &ett_spdy,
+ &ett_spdy_syn_stream,
+ &ett_spdy_syn_reply,
+ &ett_spdy_fin_stream,
+ &ett_spdy_flags,
+ &ett_spdy_header,
+ &ett_spdy_header_name,
+ &ett_spdy_header_value,
+ &ett_spdy_encoded_entity,
+ };
+
+ module_t *spdy_module;
+
+ proto_spdy = proto_register_protocol("SPDY", "SPDY", "spdy");
+ proto_register_field_array(proto_spdy, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+ new_register_dissector("spdy", dissect_spdy, proto_spdy);
+ spdy_module = prefs_register_protocol(proto_spdy, reinit_spdy);
+ prefs_register_bool_preference(spdy_module, "desegment_headers",
+ "Reassemble SPDY control frames spanning multiple TCP segments",
+ "Whether the SPDY dissector should reassemble control frames "
+ "spanning multiple TCP segments. "
+ "To use this option, you must also enable "
+ "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
+ &spdy_desegment_control_frames);
+ prefs_register_bool_preference(spdy_module, "desegment_body",
+ "Reassemble SPDY bodies spanning multiple TCP segments",
+ "Whether the SPDY dissector should reassemble "
+ "data frames spanning multiple TCP segments. "
+ "To use this option, you must also enable "
+ "\"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
+ &spdy_desegment_data_frames);
+ prefs_register_bool_preference(spdy_module, "assemble_data_frames",
+ "Assemble SPDY bodies that consist of multiple DATA frames",
+ "Whether the SPDY dissector should reassemble multiple "
+ "data frames into an entity body.",
+ &spdy_assemble_entity_bodies);
+#ifdef HAVE_LIBZ
+ prefs_register_bool_preference(spdy_module, "decompress_headers",
+ "Uncompress SPDY headers",
+ "Whether to uncompress SPDY headers.",
+ &spdy_decompress_headers);
+ prefs_register_bool_preference(spdy_module, "decompress_body",
+ "Uncompress entity bodies",
+ "Whether to uncompress entity bodies that are compressed "
+ "using \"Content-Encoding: \"",
+ &spdy_decompress_body);
+#endif
+ prefs_register_bool_preference(spdy_module, "debug_output",
+ "Print debug info on stdout",
+ "Print debug info on stdout",
+ &spdy_debug);
+#if 0
+ prefs_register_string_preference(ssl_module, "debug_file", "SPDY debug file",
+ "Redirect SPDY debug to file name; "
+ "leave empty to disable debugging, "
+ "or use \"" SPDY_DEBUG_USE_STDOUT "\""
+ " to redirect output to stdout\n",
+ (const gchar **)&sdpy_debug_file_name);
+#endif
+ prefs_register_obsolete_preference(spdy_module, "tcp_alternate_port");
+
+ range_convert_str(&global_spdy_tcp_range, TCP_DEFAULT_RANGE, 65535);
+ spdy_tcp_range = range_empty();
+ prefs_register_range_preference(spdy_module, "tcp.port", "TCP Ports",
+ "TCP Ports range",
+ &global_spdy_tcp_range, 65535);
+
+ range_convert_str(&global_spdy_ssl_range, SSL_DEFAULT_RANGE, 65535);
+ spdy_ssl_range = range_empty();
+ prefs_register_range_preference(spdy_module, "ssl.port", "SSL/TLS Ports",
+ "SSL/TLS Ports range",
+ &global_spdy_ssl_range, 65535);
+
+ spdy_handle = new_create_dissector_handle(dissect_spdy, proto_spdy);
+ /*
+ * Register for tapping
+ */
+ spdy_tap = register_tap("spdy"); /* SPDY statistics tap */
+ spdy_eo_tap = register_tap("spdy_eo"); /* SPDY Export Object tap */
+}
+
+void
+proto_reg_handoff_spdy(void)
+{
+ data_handle = find_dissector("data");
+ media_handle = find_dissector("media");
+ heur_dissector_add("tcp", dissect_spdy_heur, proto_spdy);
+}
+
+/*
+ * Content-Type: message/http
+ */
+
+static gint proto_message_spdy = -1;
+static gint ett_message_spdy = -1;
+
+static void
+dissect_message_spdy(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ proto_tree *subtree;
+ proto_item *ti;
+ gint offset = 0, next_offset;
+ gint len;
+
+ if (check_col(pinfo->cinfo, COL_INFO))
+ col_append_str(pinfo->cinfo, COL_INFO, " (message/spdy)");
+ if (tree) {
+ ti = proto_tree_add_item(tree, proto_message_spdy,
+ tvb, 0, -1, FALSE);
+ subtree = proto_item_add_subtree(ti, ett_message_spdy);
+ while (tvb_reported_length_remaining(tvb, offset) != 0) {
+ len = tvb_find_line_end(tvb, offset,
+ tvb_ensure_length_remaining(tvb, offset),
+ &next_offset, FALSE);
+ if (len == -1)
+ break;
+ proto_tree_add_text(subtree, tvb, offset, next_offset - offset,
+ "%s", tvb_format_text(tvb, offset, len));
+ offset = next_offset;
+ }
+ }
+}
+
+void
+proto_register_message_spdy(void)
+{
+ static gint *ett[] = {
+ &ett_message_spdy,
+ };
+
+ proto_message_spdy = proto_register_protocol(
+ "Media Type: message/spdy",
+ "message/spdy",
+ "message-spdy"
+ );
+ proto_register_subtree_array(ett, array_length(ett));
+}
+
+void
+proto_reg_handoff_message_spdy(void)
+{
+ dissector_handle_t message_spdy_handle;
+
+ message_spdy_handle = create_dissector_handle(dissect_message_spdy,
+ proto_message_spdy);
+
+ dissector_add_string("media_type", "message/spdy", message_spdy_handle);
+
+ reinit_spdy();
+}
diff --git a/chromium/net/tools/spdyshark/packet-spdy.h b/chromium/net/tools/spdyshark/packet-spdy.h
new file mode 100644
index 00000000000..02b586b9ab0
--- /dev/null
+++ b/chromium/net/tools/spdyshark/packet-spdy.h
@@ -0,0 +1,46 @@
+/* packet-spdy.h
+ *
+ * Copyright 2010, Google Inc.
+ * Eric Shienbrood <ers@google.com>
+ *
+ * $Id$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PACKET_SPDY_H__
+#define __PACKET_SPDY_H__
+
+#include <epan/packet.h>
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+
+/*
+ * Conversation data - used for assembling multi-data-frame
+ * entities and for decompressing request & reply header blocks.
+ */
+typedef struct _spdy_conv_t {
+ z_streamp rqst_decompressor;
+ z_streamp rply_decompressor;
+ guint32 dictionary_id;
+ GArray *streams;
+} spdy_conv_t;
+
+#endif /* __PACKET_SPDY_H__ */
diff --git a/chromium/net/tools/spdyshark/plugin.rc.in b/chromium/net/tools/spdyshark/plugin.rc.in
new file mode 100644
index 00000000000..568dc07b498
--- /dev/null
+++ b/chromium/net/tools/spdyshark/plugin.rc.in
@@ -0,0 +1,34 @@
+#include "winver.h"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION @RC_MODULE_VERSION@
+ PRODUCTVERSION @RC_VERSION@
+ FILEFLAGSMASK 0x0L
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "The Wireshark developer community, http://www.wireshark.org/\0"
+ VALUE "FileDescription", "@PACKAGE@ dissector\0"
+ VALUE "FileVersion", "@MODULE_VERSION@\0"
+ VALUE "InternalName", "@PACKAGE@ @MODULE_VERSION@\0"
+ VALUE "LegalCopyright", "Copyright © 1998 Gerald Combs <gerald@wireshark.org>, Gilbert Ramirez <gram@alumni.rice.edu> and others\0"
+ VALUE "OriginalFilename", "@PLUGIN_NAME@.dll\0"
+ VALUE "ProductName", "Wireshark\0"
+ VALUE "ProductVersion", "@VERSION@\0"
+ VALUE "Comments", "Build with @MSVC_VARIANT@\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/chromium/net/tools/testserver/OWNERS b/chromium/net/tools/testserver/OWNERS
new file mode 100644
index 00000000000..78ad9aa0f3d
--- /dev/null
+++ b/chromium/net/tools/testserver/OWNERS
@@ -0,0 +1,3 @@
+# net/ OWNERS can also review testserver changes
+# General reviewer.
+phajdan.jr@chromium.org
diff --git a/chromium/net/tools/testserver/asn1.py b/chromium/net/tools/testserver/asn1.py
new file mode 100644
index 00000000000..c0e0398626c
--- /dev/null
+++ b/chromium/net/tools/testserver/asn1.py
@@ -0,0 +1,165 @@
+# Copyright (c) 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.
+
+# This file implements very minimal ASN.1, DER serialization.
+
+import types
+
+
+def ToDER(obj):
+ '''ToDER converts the given object into DER encoding'''
+ if type(obj) == types.NoneType:
+ # None turns into NULL
+ return TagAndLength(5, 0)
+ if type(obj) == types.StringType:
+ # Strings are PRINTABLESTRING
+ return TagAndLength(19, len(obj)) + obj
+ if type(obj) == types.BooleanType:
+ val = "\x00"
+ if obj:
+ val = "\xff"
+ return TagAndLength(1, 1) + val
+ if type(obj) == types.IntType or type(obj) == types.LongType:
+ big_endian = []
+ val = obj
+ while val != 0:
+ big_endian.append(val & 0xff)
+ val >>= 8
+
+ if len(big_endian) == 0 or big_endian[-1] >= 128:
+ big_endian.append(0)
+
+ big_endian.reverse()
+ return TagAndLength(2, len(big_endian)) + ToBytes(big_endian)
+
+ return obj.ToDER()
+
+
+def ToBytes(array_of_bytes):
+ '''ToBytes converts the array of byte values into a binary string'''
+ return ''.join([chr(x) for x in array_of_bytes])
+
+
+def TagAndLength(tag, length):
+ der = [tag]
+ if length < 128:
+ der.append(length)
+ elif length < 256:
+ der.append(0x81)
+ der.append(length)
+ elif length < 65535:
+ der.append(0x82)
+ der.append(length >> 8)
+ der.append(length & 0xff)
+ else:
+ assert False
+
+ return ToBytes(der)
+
+
+class Raw(object):
+ '''Raw contains raw DER encoded bytes that are used verbatim'''
+ def __init__(self, der):
+ self.der = der
+
+ def ToDER(self):
+ return self.der
+
+
+class Explicit(object):
+ '''Explicit prepends an explicit tag'''
+ def __init__(self, tag, child):
+ self.tag = tag
+ self.child = child
+
+ def ToDER(self):
+ der = ToDER(self.child)
+ tag = self.tag
+ tag |= 0x80 # content specific
+ tag |= 0x20 # complex
+ return TagAndLength(tag, len(der)) + der
+
+
+class ENUMERATED(object):
+ def __init__(self, value):
+ self.value = value
+
+ def ToDER(self):
+ return TagAndLength(10, 1) + chr(self.value)
+
+
+class SEQUENCE(object):
+ def __init__(self, children):
+ self.children = children
+
+ def ToDER(self):
+ der = ''.join([ToDER(x) for x in self.children])
+ return TagAndLength(0x30, len(der)) + der
+
+
+class SET(object):
+ def __init__(self, children):
+ self.children = children
+
+ def ToDER(self):
+ der = ''.join([ToDER(x) for x in self.children])
+ return TagAndLength(0x31, len(der)) + der
+
+
+class OCTETSTRING(object):
+ def __init__(self, val):
+ self.val = val
+
+ def ToDER(self):
+ return TagAndLength(4, len(self.val)) + self.val
+
+
+class OID(object):
+ def __init__(self, parts):
+ self.parts = parts
+
+ def ToDER(self):
+ if len(self.parts) < 2 or self.parts[0] > 6 or self.parts[1] >= 40:
+ assert False
+
+ der = [self.parts[0]*40 + self.parts[1]]
+ for x in self.parts[2:]:
+ if x == 0:
+ der.append(0)
+ else:
+ octets = []
+ while x != 0:
+ v = x & 0x7f
+ if len(octets) > 0:
+ v |= 0x80
+ octets.append(v)
+ x >>= 7
+ octets.reverse()
+ der = der + octets
+
+ return TagAndLength(6, len(der)) + ToBytes(der)
+
+
+class UTCTime(object):
+ def __init__(self, time_str):
+ self.time_str = time_str
+
+ def ToDER(self):
+ return TagAndLength(23, len(self.time_str)) + self.time_str
+
+
+class GeneralizedTime(object):
+ def __init__(self, time_str):
+ self.time_str = time_str
+
+ def ToDER(self):
+ return TagAndLength(24, len(self.time_str)) + self.time_str
+
+
+class BitString(object):
+ def __init__(self, bits):
+ self.bits = bits
+
+ def ToDER(self):
+ return TagAndLength(3, 1 + len(self.bits)) + "\x00" + self.bits
diff --git a/chromium/net/tools/testserver/backoff_server.py b/chromium/net/tools/testserver/backoff_server.py
new file mode 100755
index 00000000000..ca2c57cbf12
--- /dev/null
+++ b/chromium/net/tools/testserver/backoff_server.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+# Copyright (c) 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.
+
+"""This is a simple HTTP server for manually testing exponential
+back-off functionality in Chrome.
+"""
+
+
+import BaseHTTPServer
+import sys
+import urlparse
+
+
+AJAX_TEST_PAGE = '''
+<html>
+<head>
+<script>
+
+function reportResult(txt) {
+ var element = document.createElement('p');
+ element.innerHTML = txt;
+ document.body.appendChild(element);
+}
+
+function fetch() {
+ var response_code = document.getElementById('response_code');
+
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET",
+ "http://%s:%d/%s?code=" + response_code.value,
+ true);
+ xmlhttp.onreadystatechange = function() {
+ reportResult(
+ 'readyState=' + xmlhttp.readyState + ', status=' + xmlhttp.status);
+ }
+ try {
+ xmlhttp.send(null);
+ } catch (e) {
+ reportResult('Exception: ' + e);
+ }
+}
+
+</script>
+</head>
+<body>
+<form action="javascript:fetch()">
+ Response code to get: <input id="response_code" type="text" value="503">
+ <input type="submit">
+</form>
+</body>
+</html>'''
+
+
+class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ keep_running = True
+ local_ip = ''
+ port = 0
+
+ def do_GET(self):
+ if self.path == '/quitquitquit':
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+ self.wfile.write('QUITTING')
+ RequestHandler.keep_running = False
+ return
+
+ if self.path.startswith('/ajax/'):
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write(AJAX_TEST_PAGE % (self.local_ip,
+ self.port,
+ self.path[6:]))
+ return
+
+ params = urlparse.parse_qs(urlparse.urlparse(self.path).query)
+
+ if not params or not 'code' in params or params['code'][0] == '200':
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+ self.wfile.write('OK')
+ else:
+ status_code = int(params['code'][0])
+ self.send_response(status_code)
+ self.end_headers()
+ self.wfile.write('Error %d' % int(status_code))
+
+
+def main():
+ if len(sys.argv) != 3:
+ print "Usage: %s LOCAL_IP PORT" % sys.argv[0]
+ sys.exit(1)
+ RequestHandler.local_ip = sys.argv[1]
+ port = int(sys.argv[2])
+ RequestHandler.port = port
+ print "To stop the server, go to http://localhost:%d/quitquitquit" % port
+ httpd = BaseHTTPServer.HTTPServer(('', port), RequestHandler)
+ while RequestHandler.keep_running:
+ httpd.handle_request()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/chromium/net/tools/testserver/dist/_socket.pyd b/chromium/net/tools/testserver/dist/_socket.pyd
new file mode 100644
index 00000000000..5ae91b70ac5
--- /dev/null
+++ b/chromium/net/tools/testserver/dist/_socket.pyd
Binary files differ
diff --git a/chromium/net/tools/testserver/dist/_ssl.pyd b/chromium/net/tools/testserver/dist/_ssl.pyd
new file mode 100644
index 00000000000..6a9b73c5178
--- /dev/null
+++ b/chromium/net/tools/testserver/dist/_ssl.pyd
Binary files differ
diff --git a/chromium/net/tools/testserver/echo_message.py b/chromium/net/tools/testserver/echo_message.py
new file mode 100644
index 00000000000..b2f7b04e8a1
--- /dev/null
+++ b/chromium/net/tools/testserver/echo_message.py
@@ -0,0 +1,385 @@
+# Copyright (c) 2011 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.
+
+"""Provides utility functions for TCP/UDP echo servers and clients.
+
+This program has classes and functions to encode, decode, calculate checksum
+and verify the "echo request" and "echo response" messages. "echo request"
+message is an echo message sent from the client to the server. "echo response"
+message is a response from the server to the "echo request" message from the
+client.
+
+The format of "echo request" message is
+<version><checksum><payload_size><payload>. <version> is the version number
+of the "echo request" protocol. <checksum> is the checksum of the <payload>.
+<payload_size> is the size of the <payload>. <payload> is the echo message.
+
+The format of "echo response" message is
+<version><checksum><payload_size><key><encoded_payload>.<version>,
+<checksum> and <payload_size> are same as what is in the "echo request" message.
+<encoded_payload> is encoded version of the <payload>. <key> is a randomly
+generated key that is used to encode/decode the <payload>.
+"""
+
+__author__ = 'rtenneti@google.com (Raman Tenneti)'
+
+
+from itertools import cycle
+from itertools import izip
+import random
+
+
+class EchoHeader(object):
+ """Class to keep header info of the EchoRequest and EchoResponse messages.
+
+ This class knows how to parse the checksum, payload_size from the
+ "echo request" and "echo response" messages. It holds the checksum,
+ payload_size of the "echo request" and "echo response" messages.
+ """
+
+ # This specifies the version.
+ VERSION_STRING = '01'
+
+ # This specifies the starting position of the checksum and length of the
+ # checksum. Maximum value for the checksum is less than (2 ** 31 - 1).
+ CHECKSUM_START = 2
+ CHECKSUM_LENGTH = 10
+ CHECKSUM_FORMAT = '%010d'
+ CHECKSUM_END = CHECKSUM_START + CHECKSUM_LENGTH
+
+ # This specifies the starting position of the <payload_size> and length of the
+ # <payload_size>. Maximum number of bytes that can be sent in the <payload> is
+ # 9,999,999.
+ PAYLOAD_SIZE_START = CHECKSUM_END
+ PAYLOAD_SIZE_LENGTH = 7
+ PAYLOAD_SIZE_FORMAT = '%07d'
+ PAYLOAD_SIZE_END = PAYLOAD_SIZE_START + PAYLOAD_SIZE_LENGTH
+
+ def __init__(self, checksum=0, payload_size=0):
+ """Initializes the checksum and payload_size of self (EchoHeader).
+
+ Args:
+ checksum: (int)
+ The checksum of the payload.
+ payload_size: (int)
+ The size of the payload.
+ """
+ self.checksum = checksum
+ self.payload_size = payload_size
+
+ def ParseAndInitialize(self, echo_message):
+ """Parses the echo_message and initializes self with the parsed data.
+
+ This method extracts checksum, and payload_size from the echo_message
+ (echo_message could be either echo_request or echo_response messages) and
+ initializes self (EchoHeader) with checksum and payload_size.
+
+ Args:
+ echo_message: (string)
+ The string representation of EchoRequest or EchoResponse objects.
+ Raises:
+ ValueError: Invalid data
+ """
+ if not echo_message or len(echo_message) < EchoHeader.PAYLOAD_SIZE_END:
+ raise ValueError('Invalid data:%s' % echo_message)
+ self.checksum = int(echo_message[
+ EchoHeader.CHECKSUM_START:EchoHeader.CHECKSUM_END])
+ self.payload_size = int(echo_message[
+ EchoHeader.PAYLOAD_SIZE_START:EchoHeader.PAYLOAD_SIZE_END])
+
+ def InitializeFromPayload(self, payload):
+ """Initializes the EchoHeader object with the payload.
+
+ It calculates checksum for the payload and initializes self (EchoHeader)
+ with the calculated checksum and size of the payload.
+
+ This method is used by the client code during testing.
+
+ Args:
+ payload: (string)
+ The payload is the echo string (like 'hello').
+ Raises:
+ ValueError: Invalid data
+ """
+ if not payload:
+ raise ValueError('Invalid data:%s' % payload)
+ self.payload_size = len(payload)
+ self.checksum = Checksum(payload, self.payload_size)
+
+ def __str__(self):
+ """String representation of the self (EchoHeader).
+
+ Returns:
+ A string representation of self (EchoHeader).
+ """
+ checksum_string = EchoHeader.CHECKSUM_FORMAT % self.checksum
+ payload_size_string = EchoHeader.PAYLOAD_SIZE_FORMAT % self.payload_size
+ return EchoHeader.VERSION_STRING + checksum_string + payload_size_string
+
+
+class EchoRequest(EchoHeader):
+ """Class holds data specific to the "echo request" message.
+
+ This class holds the payload extracted from the "echo request" message.
+ """
+
+ # This specifies the starting position of the <payload>.
+ PAYLOAD_START = EchoHeader.PAYLOAD_SIZE_END
+
+ def __init__(self):
+ """Initializes EchoRequest object."""
+ EchoHeader.__init__(self)
+ self.payload = ''
+
+ def ParseAndInitialize(self, echo_request_data):
+ """Parses and Initializes the EchoRequest object from the echo_request_data.
+
+ This method extracts the header information (checksum and payload_size) and
+ payload from echo_request_data.
+
+ Args:
+ echo_request_data: (string)
+ The string representation of EchoRequest object.
+ Raises:
+ ValueError: Invalid data
+ """
+ EchoHeader.ParseAndInitialize(self, echo_request_data)
+ if len(echo_request_data) <= EchoRequest.PAYLOAD_START:
+ raise ValueError('Invalid data:%s' % echo_request_data)
+ self.payload = echo_request_data[EchoRequest.PAYLOAD_START:]
+
+ def InitializeFromPayload(self, payload):
+ """Initializes the EchoRequest object with payload.
+
+ It calculates checksum for the payload and initializes self (EchoRequest)
+ object.
+
+ Args:
+ payload: (string)
+ The payload string for which "echo request" needs to be constructed.
+ """
+ EchoHeader.InitializeFromPayload(self, payload)
+ self.payload = payload
+
+ def __str__(self):
+ """String representation of the self (EchoRequest).
+
+ Returns:
+ A string representation of self (EchoRequest).
+ """
+ return EchoHeader.__str__(self) + self.payload
+
+
+class EchoResponse(EchoHeader):
+ """Class holds data specific to the "echo response" message.
+
+ This class knows how to parse the "echo response" message. This class holds
+ key, encoded_payload and decoded_payload of the "echo response" message.
+ """
+
+ # This specifies the starting position of the |key_| and length of the |key_|.
+ # Minimum and maximum values for the |key_| are 100,000 and 999,999.
+ KEY_START = EchoHeader.PAYLOAD_SIZE_END
+ KEY_LENGTH = 6
+ KEY_FORMAT = '%06d'
+ KEY_END = KEY_START + KEY_LENGTH
+ KEY_MIN_VALUE = 0
+ KEY_MAX_VALUE = 999999
+
+ # This specifies the starting position of the <encoded_payload> and length
+ # of the <encoded_payload>.
+ ENCODED_PAYLOAD_START = KEY_END
+
+ def __init__(self, key='', encoded_payload='', decoded_payload=''):
+ """Initializes the EchoResponse object."""
+ EchoHeader.__init__(self)
+ self.key = key
+ self.encoded_payload = encoded_payload
+ self.decoded_payload = decoded_payload
+
+ def ParseAndInitialize(self, echo_response_data=None):
+ """Parses and Initializes the EchoResponse object from echo_response_data.
+
+ This method calls EchoHeader to extract header information from the
+ echo_response_data and it then extracts key and encoded_payload from the
+ echo_response_data. It holds the decoded payload of the encoded_payload.
+
+ Args:
+ echo_response_data: (string)
+ The string representation of EchoResponse object.
+ Raises:
+ ValueError: Invalid echo_request_data
+ """
+ EchoHeader.ParseAndInitialize(self, echo_response_data)
+ if len(echo_response_data) <= EchoResponse.ENCODED_PAYLOAD_START:
+ raise ValueError('Invalid echo_response_data:%s' % echo_response_data)
+ self.key = echo_response_data[EchoResponse.KEY_START:EchoResponse.KEY_END]
+ self.encoded_payload = echo_response_data[
+ EchoResponse.ENCODED_PAYLOAD_START:]
+ self.decoded_payload = Crypt(self.encoded_payload, self.key)
+
+ def InitializeFromEchoRequest(self, echo_request):
+ """Initializes EchoResponse with the data from the echo_request object.
+
+ It gets the checksum, payload_size and payload from the echo_request object
+ and then encodes the payload with a random key. It also saves the payload
+ as decoded_payload.
+
+ Args:
+ echo_request: (EchoRequest)
+ The EchoRequest object which has "echo request" message.
+ """
+ self.checksum = echo_request.checksum
+ self.payload_size = echo_request.payload_size
+ self.key = (EchoResponse.KEY_FORMAT %
+ random.randrange(EchoResponse.KEY_MIN_VALUE,
+ EchoResponse.KEY_MAX_VALUE))
+ self.encoded_payload = Crypt(echo_request.payload, self.key)
+ self.decoded_payload = echo_request.payload
+
+ def __str__(self):
+ """String representation of the self (EchoResponse).
+
+ Returns:
+ A string representation of self (EchoResponse).
+ """
+ return EchoHeader.__str__(self) + self.key + self.encoded_payload
+
+
+def Crypt(payload, key):
+ """Encodes/decodes the payload with the key and returns encoded payload.
+
+ This method loops through the payload and XORs each byte with the key.
+
+ Args:
+ payload: (string)
+ The string to be encoded/decoded.
+ key: (string)
+ The key used to encode/decode the payload.
+
+ Returns:
+ An encoded/decoded string.
+ """
+ return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in izip(payload, cycle(key)))
+
+
+def Checksum(payload, payload_size):
+ """Calculates the checksum of the payload.
+
+ Args:
+ payload: (string)
+ The payload string for which checksum needs to be calculated.
+ payload_size: (int)
+ The number of bytes in the payload.
+
+ Returns:
+ The checksum of the payload.
+ """
+ checksum = 0
+ length = min(payload_size, len(payload))
+ for i in range (0, length):
+ checksum += ord(payload[i])
+ return checksum
+
+
+def GetEchoRequestData(payload):
+ """Constructs an "echo request" message from the payload.
+
+ It builds an EchoRequest object from the payload and then returns a string
+ representation of the EchoRequest object.
+
+ This is used by the TCP/UDP echo clients to build the "echo request" message.
+
+ Args:
+ payload: (string)
+ The payload string for which "echo request" needs to be constructed.
+
+ Returns:
+ A string representation of the EchoRequest object.
+ Raises:
+ ValueError: Invalid payload
+ """
+ try:
+ echo_request = EchoRequest()
+ echo_request.InitializeFromPayload(payload)
+ return str(echo_request)
+ except (IndexError, ValueError):
+ raise ValueError('Invalid payload:%s' % payload)
+
+
+def GetEchoResponseData(echo_request_data):
+ """Verifies the echo_request_data and returns "echo response" message.
+
+ It builds the EchoRequest object from the echo_request_data and then verifies
+ the checksum of the EchoRequest is same as the calculated checksum of the
+ payload. If the checksums don't match then it returns None. It checksums
+ match, it builds the echo_response object from echo_request object and returns
+ string representation of the EchoResponse object.
+
+ This is used by the TCP/UDP echo servers.
+
+ Args:
+ echo_request_data: (string)
+ The string that echo servers send to the clients.
+
+ Returns:
+ A string representation of the EchoResponse object. It returns None if the
+ echo_request_data is not valid.
+ Raises:
+ ValueError: Invalid echo_request_data
+ """
+ try:
+ if not echo_request_data:
+ raise ValueError('Invalid payload:%s' % echo_request_data)
+
+ echo_request = EchoRequest()
+ echo_request.ParseAndInitialize(echo_request_data)
+
+ if Checksum(echo_request.payload,
+ echo_request.payload_size) != echo_request.checksum:
+ return None
+
+ echo_response = EchoResponse()
+ echo_response.InitializeFromEchoRequest(echo_request)
+
+ return str(echo_response)
+ except (IndexError, ValueError):
+ raise ValueError('Invalid payload:%s' % echo_request_data)
+
+
+def DecodeAndVerify(echo_request_data, echo_response_data):
+ """Decodes and verifies the echo_response_data.
+
+ It builds EchoRequest and EchoResponse objects from the echo_request_data and
+ echo_response_data. It returns True if the EchoResponse's payload and
+ checksum match EchoRequest's.
+
+ This is used by the TCP/UDP echo clients for testing purposes.
+
+ Args:
+ echo_request_data: (string)
+ The request clients sent to echo servers.
+ echo_response_data: (string)
+ The response clients received from the echo servers.
+
+ Returns:
+ True if echo_request_data and echo_response_data match.
+ Raises:
+ ValueError: Invalid echo_request_data or Invalid echo_response
+ """
+
+ try:
+ echo_request = EchoRequest()
+ echo_request.ParseAndInitialize(echo_request_data)
+ except (IndexError, ValueError):
+ raise ValueError('Invalid echo_request:%s' % echo_request_data)
+
+ try:
+ echo_response = EchoResponse()
+ echo_response.ParseAndInitialize(echo_response_data)
+ except (IndexError, ValueError):
+ raise ValueError('Invalid echo_response:%s' % echo_response_data)
+
+ return (echo_request.checksum == echo_response.checksum and
+ echo_request.payload == echo_response.decoded_payload)
diff --git a/chromium/net/tools/testserver/minica.py b/chromium/net/tools/testserver/minica.py
new file mode 100644
index 00000000000..2dd38ef9814
--- /dev/null
+++ b/chromium/net/tools/testserver/minica.py
@@ -0,0 +1,349 @@
+# Copyright (c) 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.
+
+import asn1
+import hashlib
+import os
+
+
+# This file implements very minimal certificate and OCSP generation. It's
+# designed to test revocation checking.
+
+def RandomNumber(length_in_bytes):
+ '''RandomNumber returns a random number of length 8*|length_in_bytes| bits'''
+ rand = os.urandom(length_in_bytes)
+ n = 0
+ for x in rand:
+ n <<= 8
+ n |= ord(x)
+ return n
+
+
+def ModExp(n, e, p):
+ '''ModExp returns n^e mod p'''
+ r = 1
+ while e != 0:
+ if e & 1:
+ r = (r*n) % p
+ e >>= 1
+ n = (n*n) % p
+ return r
+
+# PKCS1v15_SHA1_PREFIX is the ASN.1 prefix for a SHA1 signature.
+PKCS1v15_SHA1_PREFIX = '3021300906052b0e03021a05000414'.decode('hex')
+
+class RSA(object):
+ def __init__(self, modulus, e, d):
+ self.m = modulus
+ self.e = e
+ self.d = d
+
+ self.modlen = 0
+ m = modulus
+ while m != 0:
+ self.modlen += 1
+ m >>= 8
+
+ def Sign(self, message):
+ digest = hashlib.sha1(message).digest()
+ prefix = PKCS1v15_SHA1_PREFIX
+
+ em = ['\xff'] * (self.modlen - 1 - len(prefix) - len(digest))
+ em[0] = '\x00'
+ em[1] = '\x01'
+ em += "\x00" + prefix + digest
+
+ n = 0
+ for x in em:
+ n <<= 8
+ n |= ord(x)
+
+ s = ModExp(n, self.d, self.m)
+ out = []
+ while s != 0:
+ out.append(s & 0xff)
+ s >>= 8
+ out.reverse()
+ return '\x00' * (self.modlen - len(out)) + asn1.ToBytes(out)
+
+ def ToDER(self):
+ return asn1.ToDER(asn1.SEQUENCE([self.m, self.e]))
+
+
+def Name(cn = None, c = None, o = None):
+ names = asn1.SEQUENCE([])
+
+ if cn is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ COMMON_NAME, cn,
+ ])
+ ])
+ )
+
+ if c is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ COUNTRY, c,
+ ])
+ ])
+ )
+
+ if o is not None:
+ names.children.append(
+ asn1.SET([
+ asn1.SEQUENCE([
+ ORGANIZATION, o,
+ ])
+ ])
+ )
+
+ return names
+
+
+# The private key and root certificate name are hard coded here:
+
+# This is the private key
+KEY = RSA(0x00a71998f2930bfe73d031a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da999de4ed65832afcb0807f98787506539d258a0ce3c2c77967653099a9034a9b115a876c39a8c4e4ed4acd0c64095946fb39eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1afc45519ab9eca47c30185c771c64aa5ecf07d,
+ 3,
+ 0x6f6665f70cb2a9a28acbc5aa0cd374cfb49f49e371a542de0a86aa4a0554cc87f7e71113edf399021ca875aaffbafaf8aee268c3b15ded2c84fb9a4375bbc6011d841e57833bc6f998d25daf6fa7f166b233e3e54a4bae7a5aaaba21431324967d5ff3e1d4f413827994262115ca54396e7068d0afa7af787a5782bc7040e6d3)
+
+# And the same thing in PEM format
+KEY_PEM = '''-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCnGZjykwv+c9AxqH8TPS83ju7u1Sp35E0Pyf9vB/8yy/PamZ3k
+7WWDKvywgH+YeHUGU50ligzjwsd5Z2UwmakDSpsRWodsOajE5O1KzQxkCVlG+znu
+60egcE27AYrPSMOhxLiV/ECftKNAqYaxr8RVGaueykfDAYXHccZKpezwfQIBAwKB
+gG9mZfcMsqmiisvFqgzTdM+0n0njcaVC3gqGqkoFVMyH9+cRE+3zmQIcqHWq/7r6
++K7iaMOxXe0shPuaQ3W7xgEdhB5XgzvG+ZjSXa9vp/FmsjPj5UpLrnpaqrohQxMk
+ln1f8+HU9BOCeZQmIRXKVDlucGjQr6eveHpXgrxwQObTAkEA2wBAfuduw5G0/VfN
+Wx66D5fbPccfYFqLM5LuTimLmNqzK2gIKXckB2sm44gJZ6wVlumaB1CSNug2LNYx
+3cAjUwJBAMNUo1hbI8ugqqwI9kpxv9+2Heea4BlnXbS6tYF8pvkHMoliuxNbXmmB
+u4zNB5iZ6V0ZZ4nvtUNo2cGr/h/Lcu8CQQCSACr/RPSCYSNTj948vya1D+d+hL+V
+kbIiYfQ0G7Jl5yIc8AVw+hgE8hntBVuacrkPRmaviwwkms7IjsvpKsI3AkEAgjhs
+5ZIX3RXHHVtO3EvVP86+mmdAEO+TzdHOVlMZ+1ohsOx8t5I+8QEnszNaZbvw6Lua
+W/UjgkXmgR1UFTJMnwJBAKErmAw21/g3SST0a4wlyaGT/MbXL8Ouwnb5IOKQVe55
+CZdeVeSh6cJ4hAcQKfr2s1JaZTJFIBPGKAif5HqpydA=
+-----END RSA PRIVATE KEY-----
+'''
+
+# Root certificate CN
+ISSUER_CN = "Testing CA"
+
+# All certificates are issued under this policy OID, in the Google arc:
+CERT_POLICY_OID = asn1.OID([1, 3, 6, 1, 4, 1, 11129, 2, 4, 1])
+
+# These result in the following root certificate:
+# -----BEGIN CERTIFICATE-----
+# MIIB0TCCATqgAwIBAgIBATANBgkqhkiG9w0BAQUFADAVMRMwEQYDVQQDEwpUZXN0aW5nIENBMB4X
+# DTEwMDEwMTA2MDAwMFoXDTMyMTIwMTA2MDAwMFowFTETMBEGA1UEAxMKVGVzdGluZyBDQTCBnTAN
+# BgkqhkiG9w0BAQEFAAOBiwAwgYcCgYEApxmY8pML/nPQMah/Ez0vN47u7tUqd+RND8n/bwf/Msvz
+# 2pmd5O1lgyr8sIB/mHh1BlOdJYoM48LHeWdlMJmpA0qbEVqHbDmoxOTtSs0MZAlZRvs57utHoHBN
+# uwGKz0jDocS4lfxAn7SjQKmGsa/EVRmrnspHwwGFx3HGSqXs8H0CAQOjMzAxMBIGA1UdEwEB/wQI
+# MAYBAf8CAQAwGwYDVR0gAQEABBEwDzANBgsrBgEEAdZ5AgHODzANBgkqhkiG9w0BAQUFAAOBgQA/
+# STb40A6D+93jMfLGQzXc997IsaJZdoPt7tYa8PqGJBL62EiTj+erd/H5pDZx/2/bcpOG4m9J56yg
+# wOohbllw2TM+oeEd8syzV6X+1SIPnGI56JRrm3UXcHYx1Rq5loM9WKAiz/WmIWmskljsEQ7+542p
+# q0pkHjs8nuXovSkUYA==
+# -----END CERTIFICATE-----
+
+# If you update any of the above, you can generate a new root with the
+# following line:
+# print DERToPEM(MakeCertificate(ISSUER_CN, ISSUER_CN, 1, KEY, KEY, None))
+
+
+# Various OIDs
+
+AIA_OCSP = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1])
+AUTHORITY_INFORMATION_ACCESS = asn1.OID([1, 3, 6, 1, 5, 5, 7, 1, 1])
+BASIC_CONSTRAINTS = asn1.OID([2, 5, 29, 19])
+CERT_POLICIES = asn1.OID([2, 5, 29, 32])
+COMMON_NAME = asn1.OID([2, 5, 4, 3])
+COUNTRY = asn1.OID([2, 5, 4, 6])
+HASH_SHA1 = asn1.OID([1, 3, 14, 3, 2, 26])
+OCSP_TYPE_BASIC = asn1.OID([1, 3, 6, 1, 5, 5, 7, 48, 1, 1])
+ORGANIZATION = asn1.OID([2, 5, 4, 10])
+PUBLIC_KEY_RSA = asn1.OID([1, 2, 840, 113549, 1, 1, 1])
+SHA1_WITH_RSA_ENCRYPTION = asn1.OID([1, 2, 840, 113549, 1, 1, 5])
+
+
+def MakeCertificate(
+ issuer_cn, subject_cn, serial, pubkey, privkey, ocsp_url = None):
+ '''MakeCertificate returns a DER encoded certificate, signed by privkey.'''
+ extensions = asn1.SEQUENCE([])
+
+ # Default subject name fields
+ c = "XX"
+ o = "Testing Org"
+
+ if issuer_cn == subject_cn:
+ # Root certificate.
+ c = None
+ o = None
+ extensions.children.append(
+ asn1.SEQUENCE([
+ basic_constraints,
+ True,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ True, # IsCA
+ 0, # Path len
+ ]))),
+ ]))
+
+ if ocsp_url is not None:
+ extensions.children.append(
+ asn1.SEQUENCE([
+ AUTHORITY_INFORMATION_ACCESS,
+ False,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ asn1.SEQUENCE([
+ AIA_OCSP,
+ asn1.Raw(asn1.TagAndLength(0x86, len(ocsp_url)) + ocsp_url),
+ ]),
+ ]))),
+ ]))
+
+ extensions.children.append(
+ asn1.SEQUENCE([
+ CERT_POLICIES,
+ False,
+ asn1.OCTETSTRING(asn1.ToDER(asn1.SEQUENCE([
+ asn1.SEQUENCE([ # PolicyInformation
+ CERT_POLICY_OID,
+ ]),
+ ]))),
+ ])
+ )
+
+ tbsCert = asn1.ToDER(asn1.SEQUENCE([
+ asn1.Explicit(0, 2), # Version
+ serial,
+ asn1.SEQUENCE([SHA1_WITH_RSA_ENCRYPTION, None]), # SignatureAlgorithm
+ Name(cn = issuer_cn), # Issuer
+ asn1.SEQUENCE([ # Validity
+ asn1.UTCTime("100101060000Z"), # NotBefore
+ asn1.UTCTime("321201060000Z"), # NotAfter
+ ]),
+ Name(cn = subject_cn, c = c, o = o), # Subject
+ asn1.SEQUENCE([ # SubjectPublicKeyInfo
+ asn1.SEQUENCE([ # Algorithm
+ PUBLIC_KEY_RSA,
+ None,
+ ]),
+ asn1.BitString(asn1.ToDER(pubkey)),
+ ]),
+ asn1.Explicit(3, extensions),
+ ]))
+
+ return asn1.ToDER(asn1.SEQUENCE([
+ asn1.Raw(tbsCert),
+ asn1.SEQUENCE([
+ SHA1_WITH_RSA_ENCRYPTION,
+ None,
+ ]),
+ asn1.BitString(privkey.Sign(tbsCert)),
+ ]))
+
+
+def MakeOCSPResponse(issuer_cn, issuer_key, serial, ocsp_state):
+ # https://tools.ietf.org/html/rfc2560
+ issuer_name_hash = asn1.OCTETSTRING(
+ hashlib.sha1(asn1.ToDER(Name(cn = issuer_cn))).digest())
+
+ issuer_key_hash = asn1.OCTETSTRING(
+ hashlib.sha1(asn1.ToDER(issuer_key)).digest())
+
+ cert_status = None
+ if ocsp_state == OCSP_STATE_REVOKED:
+ cert_status = asn1.Explicit(1, asn1.GeneralizedTime("20100101060000Z"))
+ elif ocsp_state == OCSP_STATE_UNKNOWN:
+ cert_status = asn1.Raw(asn1.TagAndLength(0x80 | 2, 0))
+ elif ocsp_state == OCSP_STATE_GOOD:
+ cert_status = asn1.Raw(asn1.TagAndLength(0x80 | 0, 0))
+ else:
+ raise ValueError('Bad OCSP state: ' + str(ocsp_state))
+
+ basic_resp_data_der = asn1.ToDER(asn1.SEQUENCE([
+ asn1.Explicit(2, issuer_key_hash),
+ asn1.GeneralizedTime("20100101060000Z"), # producedAt
+ asn1.SEQUENCE([
+ asn1.SEQUENCE([ # SingleResponse
+ asn1.SEQUENCE([ # CertID
+ asn1.SEQUENCE([ # hashAlgorithm
+ HASH_SHA1,
+ None,
+ ]),
+ issuer_name_hash,
+ issuer_key_hash,
+ serial,
+ ]),
+ cert_status,
+ asn1.GeneralizedTime("20100101060000Z"), # thisUpdate
+ asn1.Explicit(0, asn1.GeneralizedTime("20300101060000Z")), # nextUpdate
+ ]),
+ ]),
+ ]))
+
+ basic_resp = asn1.SEQUENCE([
+ asn1.Raw(basic_resp_data_der),
+ asn1.SEQUENCE([
+ SHA1_WITH_RSA_ENCRYPTION,
+ None,
+ ]),
+ asn1.BitString(issuer_key.Sign(basic_resp_data_der)),
+ ])
+
+ resp = asn1.SEQUENCE([
+ asn1.ENUMERATED(0),
+ asn1.Explicit(0, asn1.SEQUENCE([
+ OCSP_TYPE_BASIC,
+ asn1.OCTETSTRING(asn1.ToDER(basic_resp)),
+ ]))
+ ])
+
+ return asn1.ToDER(resp)
+
+
+def DERToPEM(der):
+ pem = '-----BEGIN CERTIFICATE-----\n'
+ pem += der.encode('base64')
+ pem += '-----END CERTIFICATE-----\n'
+ return pem
+
+OCSP_STATE_GOOD = 1
+OCSP_STATE_REVOKED = 2
+OCSP_STATE_INVALID = 3
+OCSP_STATE_UNAUTHORIZED = 4
+OCSP_STATE_UNKNOWN = 5
+
+# unauthorizedDER is an OCSPResponse with a status of 6:
+# SEQUENCE { ENUM(6) }
+unauthorizedDER = '30030a0106'.decode('hex')
+
+def GenerateCertKeyAndOCSP(subject = "127.0.0.1",
+ ocsp_url = "http://127.0.0.1",
+ ocsp_state = OCSP_STATE_GOOD,
+ serial = 0):
+ '''GenerateCertKeyAndOCSP returns a (cert_and_key_pem, ocsp_der) where:
+ * cert_and_key_pem contains a certificate and private key in PEM format
+ with the given subject common name and OCSP URL.
+ * ocsp_der contains a DER encoded OCSP response or None if ocsp_url is
+ None'''
+
+ if serial == 0:
+ serial = RandomNumber(16)
+ cert_der = MakeCertificate(ISSUER_CN, bytes(subject), serial, KEY, KEY,
+ bytes(ocsp_url))
+ cert_pem = DERToPEM(cert_der)
+
+ ocsp_der = None
+ if ocsp_url is not None:
+ if ocsp_state == OCSP_STATE_UNAUTHORIZED:
+ ocsp_der = unauthorizedDER
+ elif ocsp_state == OCSP_STATE_INVALID:
+ ocsp_der = '3'
+ else:
+ ocsp_der = MakeOCSPResponse(ISSUER_CN, KEY, serial, ocsp_state)
+
+ return (cert_pem + KEY_PEM, ocsp_der)
diff --git a/chromium/net/tools/testserver/run_testserver.cc b/chromium/net/tools/testserver/run_testserver.cc
new file mode 100644
index 00000000000..acf4177a8dd
--- /dev/null
+++ b/chromium/net/tools/testserver/run_testserver.cc
@@ -0,0 +1,125 @@
+// Copyright 2013 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 <stdio.h>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+static void PrintUsage() {
+ printf("run_testserver --doc-root=relpath\n"
+ " [--http|--https|--ws|--wss|--ftp]\n"
+ " [--ssl-cert=ok|mismatched-name|expired]\n");
+ printf("(NOTE: relpath should be relative to the 'src' directory.\n");
+}
+
+int main(int argc, const char* argv[]) {
+ base::AtExitManager at_exit_manager;
+ base::MessageLoopForIO message_loop;
+
+ // Process command line
+ CommandLine::Init(argc, argv);
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = logging::LOG_TO_ALL;
+ settings.log_file = FILE_PATH_LITERAL("testserver.log");
+ if (!logging::InitLogging(settings)) {
+ printf("Error: could not initialize logging. Exiting.\n");
+ return -1;
+ }
+
+ TestTimeouts::Initialize();
+
+ if (command_line->GetSwitches().empty() ||
+ command_line->HasSwitch("help")) {
+ PrintUsage();
+ return -1;
+ }
+
+ net::SpawnedTestServer::Type server_type;
+ if (command_line->HasSwitch("http")) {
+ server_type = net::SpawnedTestServer::TYPE_HTTP;
+ } else if (command_line->HasSwitch("https")) {
+ server_type = net::SpawnedTestServer::TYPE_HTTPS;
+ } else if (command_line->HasSwitch("ws")) {
+ server_type = net::SpawnedTestServer::TYPE_WS;
+ } else if (command_line->HasSwitch("wss")) {
+ server_type = net::SpawnedTestServer::TYPE_WSS;
+ } else if (command_line->HasSwitch("ftp")) {
+ server_type = net::SpawnedTestServer::TYPE_FTP;
+ } else {
+ // If no scheme switch is specified, select http or https scheme.
+ // TODO(toyoshim): Remove this estimation.
+ if (command_line->HasSwitch("ssl-cert"))
+ server_type = net::SpawnedTestServer::TYPE_HTTPS;
+ else
+ server_type = net::SpawnedTestServer::TYPE_HTTP;
+ }
+
+ net::SpawnedTestServer::SSLOptions ssl_options;
+ if (command_line->HasSwitch("ssl-cert")) {
+ if (!net::SpawnedTestServer::UsingSSL(server_type)) {
+ printf("Error: --ssl-cert is specified on non-secure scheme\n");
+ PrintUsage();
+ return -1;
+ }
+ std::string cert_option = command_line->GetSwitchValueASCII("ssl-cert");
+ if (cert_option == "ok") {
+ ssl_options.server_certificate =
+ net::SpawnedTestServer::SSLOptions::CERT_OK;
+ } else if (cert_option == "mismatched-name") {
+ ssl_options.server_certificate =
+ net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME;
+ } else if (cert_option == "expired") {
+ ssl_options.server_certificate =
+ net::SpawnedTestServer::SSLOptions::CERT_EXPIRED;
+ } else {
+ printf("Error: --ssl-cert has invalid value %s\n", cert_option.c_str());
+ PrintUsage();
+ return -1;
+ }
+ }
+
+ base::FilePath doc_root = command_line->GetSwitchValuePath("doc-root");
+ if (doc_root.empty()) {
+ printf("Error: --doc-root must be specified\n");
+ PrintUsage();
+ return -1;
+ }
+
+ scoped_ptr<net::SpawnedTestServer> test_server;
+ if (net::SpawnedTestServer::UsingSSL(server_type)) {
+ test_server.reset(
+ new net::SpawnedTestServer(server_type, ssl_options, doc_root));
+ } else {
+ test_server.reset(new net::SpawnedTestServer(
+ server_type,
+ net::SpawnedTestServer::kLocalhost,
+ doc_root));
+ }
+
+ if (!test_server->Start()) {
+ printf("Error: failed to start test server. Exiting.\n");
+ return -1;
+ }
+
+ if (!base::DirectoryExists(test_server->document_root())) {
+ printf("Error: invalid doc root: \"%s\" does not exist!\n",
+ UTF16ToUTF8(test_server->document_root().LossyDisplayName()).c_str());
+ return -1;
+ }
+
+ printf("testserver running at %s (type ctrl+c to exit)\n",
+ test_server->host_port_pair().ToString().c_str());
+
+ message_loop.Run();
+ return 0;
+}
diff --git a/chromium/net/tools/testserver/testserver.py b/chromium/net/tools/testserver/testserver.py
new file mode 100755
index 00000000000..77a31426300
--- /dev/null
+++ b/chromium/net/tools/testserver/testserver.py
@@ -0,0 +1,2070 @@
+#!/usr/bin/env python
+# Copyright 2013 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.
+
+"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
+testing Chrome.
+
+It supports several test URLs, as specified by the handlers in TestPageHandler.
+By default, it listens on an ephemeral port and sends the port number back to
+the originating process over a pipe. The originating process can specify an
+explicit port if necessary.
+It can use https if you specify the flag --https=CERT where CERT is the path
+to a pem file containing the certificate and private key that should be used.
+"""
+
+import base64
+import BaseHTTPServer
+import cgi
+import hashlib
+import logging
+import minica
+import os
+import random
+import re
+import select
+import socket
+import SocketServer
+import struct
+import sys
+import threading
+import time
+import urllib
+import urlparse
+import zlib
+
+import echo_message
+import pyftpdlib.ftpserver
+import testserver_base
+import tlslite
+import tlslite.api
+
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(
+ 0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
+from mod_pywebsocket.standalone import WebSocketServer
+
+SERVER_HTTP = 0
+SERVER_FTP = 1
+SERVER_TCP_ECHO = 2
+SERVER_UDP_ECHO = 3
+SERVER_BASIC_AUTH_PROXY = 4
+SERVER_WEBSOCKET = 5
+
+# Default request queue size for WebSocketServer.
+_DEFAULT_REQUEST_QUEUE_SIZE = 128
+
+class WebSocketOptions:
+ """Holds options for WebSocketServer."""
+
+ def __init__(self, host, port, data_dir):
+ self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
+ self.server_host = host
+ self.port = port
+ self.websock_handlers = data_dir
+ self.scan_dir = None
+ self.allow_handlers_outside_root_dir = False
+ self.websock_handlers_map_file = None
+ self.cgi_directories = []
+ self.is_executable_method = None
+ self.allow_draft75 = False
+ self.strict = True
+
+ self.use_tls = False
+ self.private_key = None
+ self.certificate = None
+ self.tls_client_auth = False
+ self.tls_client_ca = None
+ self.use_basic_auth = False
+
+
+class RecordingSSLSessionCache(object):
+ """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
+ lookups and inserts in order to test session cache behaviours."""
+
+ def __init__(self):
+ self.log = []
+
+ def __getitem__(self, sessionID):
+ self.log.append(('lookup', sessionID))
+ raise KeyError()
+
+ def __setitem__(self, sessionID, session):
+ self.log.append(('insert', sessionID))
+
+
+class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
+ testserver_base.BrokenPipeHandlerMixIn,
+ testserver_base.StoppableHTTPServer):
+ """This is a specialization of StoppableHTTPServer that adds client
+ verification."""
+
+ pass
+
+class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
+ testserver_base.BrokenPipeHandlerMixIn,
+ BaseHTTPServer.HTTPServer):
+ """This is a specialization of HTTPServer that serves an
+ OCSP response"""
+
+ def serve_forever_on_thread(self):
+ self.thread = threading.Thread(target = self.serve_forever,
+ name = "OCSPServerThread")
+ self.thread.start()
+
+ def stop_serving(self):
+ self.shutdown()
+ self.thread.join()
+
+
+class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
+ testserver_base.ClientRestrictingServerMixIn,
+ testserver_base.BrokenPipeHandlerMixIn,
+ testserver_base.StoppableHTTPServer):
+ """This is a specialization of StoppableHTTPServer that add https support and
+ client verification."""
+
+ def __init__(self, server_address, request_hander_class, pem_cert_and_key,
+ ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
+ record_resume_info, tls_intolerant):
+ self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
+ # Force using only python implementation - otherwise behavior is different
+ # depending on whether m2crypto Python module is present (error is thrown
+ # when it is). m2crypto uses a C (based on OpenSSL) implementation under
+ # the hood.
+ self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key,
+ private=True,
+ implementations=['python'])
+ self.ssl_client_auth = ssl_client_auth
+ self.ssl_client_cas = []
+ self.tls_intolerant = tls_intolerant
+
+ for ca_file in ssl_client_cas:
+ s = open(ca_file).read()
+ x509 = tlslite.api.X509()
+ x509.parse(s)
+ self.ssl_client_cas.append(x509.subject)
+ self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
+ if ssl_bulk_ciphers is not None:
+ self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
+
+ if record_resume_info:
+ # If record_resume_info is true then we'll replace the session cache with
+ # an object that records the lookups and inserts that it sees.
+ self.session_cache = RecordingSSLSessionCache()
+ else:
+ self.session_cache = tlslite.api.SessionCache()
+ testserver_base.StoppableHTTPServer.__init__(self,
+ server_address,
+ request_hander_class)
+
+ def handshake(self, tlsConnection):
+ """Creates the SSL connection."""
+
+ try:
+ self.tlsConnection = tlsConnection
+ tlsConnection.handshakeServer(certChain=self.cert_chain,
+ privateKey=self.private_key,
+ sessionCache=self.session_cache,
+ reqCert=self.ssl_client_auth,
+ settings=self.ssl_handshake_settings,
+ reqCAs=self.ssl_client_cas,
+ tlsIntolerant=self.tls_intolerant)
+ tlsConnection.ignoreAbruptClose = True
+ return True
+ except tlslite.api.TLSAbruptCloseError:
+ # Ignore abrupt close.
+ return True
+ except tlslite.api.TLSError, error:
+ print "Handshake failure:", str(error)
+ return False
+
+
+class FTPServer(testserver_base.ClientRestrictingServerMixIn,
+ pyftpdlib.ftpserver.FTPServer):
+ """This is a specialization of FTPServer that adds client verification."""
+
+ pass
+
+
+class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
+ SocketServer.TCPServer):
+ """A TCP echo server that echoes back what it has received."""
+
+ def server_bind(self):
+ """Override server_bind to store the server name."""
+
+ SocketServer.TCPServer.server_bind(self)
+ host, port = self.socket.getsockname()[:2]
+ self.server_name = socket.getfqdn(host)
+ self.server_port = port
+
+ def serve_forever(self):
+ self.stop = False
+ self.nonce_time = None
+ while not self.stop:
+ self.handle_request()
+ self.socket.close()
+
+
+class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
+ SocketServer.UDPServer):
+ """A UDP echo server that echoes back what it has received."""
+
+ def server_bind(self):
+ """Override server_bind to store the server name."""
+
+ SocketServer.UDPServer.server_bind(self)
+ host, port = self.socket.getsockname()[:2]
+ self.server_name = socket.getfqdn(host)
+ self.server_port = port
+
+ def serve_forever(self):
+ self.stop = False
+ self.nonce_time = None
+ while not self.stop:
+ self.handle_request()
+ self.socket.close()
+
+
+class TestPageHandler(testserver_base.BasePageHandler):
+ # Class variables to allow for persistence state between page handler
+ # invocations
+ rst_limits = {}
+ fail_precondition = {}
+
+ def __init__(self, request, client_address, socket_server):
+ connect_handlers = [
+ self.RedirectConnectHandler,
+ self.ServerAuthConnectHandler,
+ self.DefaultConnectResponseHandler]
+ get_handlers = [
+ self.NoCacheMaxAgeTimeHandler,
+ self.NoCacheTimeHandler,
+ self.CacheTimeHandler,
+ self.CacheExpiresHandler,
+ self.CacheProxyRevalidateHandler,
+ self.CachePrivateHandler,
+ self.CachePublicHandler,
+ self.CacheSMaxAgeHandler,
+ self.CacheMustRevalidateHandler,
+ self.CacheMustRevalidateMaxAgeHandler,
+ self.CacheNoStoreHandler,
+ self.CacheNoStoreMaxAgeHandler,
+ self.CacheNoTransformHandler,
+ self.DownloadHandler,
+ self.DownloadFinishHandler,
+ self.EchoHeader,
+ self.EchoHeaderCache,
+ self.EchoAllHandler,
+ self.ZipFileHandler,
+ self.FileHandler,
+ self.SetCookieHandler,
+ self.SetManyCookiesHandler,
+ self.ExpectAndSetCookieHandler,
+ self.SetHeaderHandler,
+ self.AuthBasicHandler,
+ self.AuthDigestHandler,
+ self.SlowServerHandler,
+ self.ChunkedServerHandler,
+ self.ContentTypeHandler,
+ self.NoContentHandler,
+ self.ServerRedirectHandler,
+ self.ClientRedirectHandler,
+ self.MultipartHandler,
+ self.GetSSLSessionCacheHandler,
+ self.SSLManySmallRecords,
+ self.GetChannelID,
+ self.CloseSocketHandler,
+ self.RangeResetHandler,
+ self.DefaultResponseHandler]
+ post_handlers = [
+ self.EchoTitleHandler,
+ self.EchoHandler,
+ self.PostOnlyFileHandler] + get_handlers
+ put_handlers = [
+ self.EchoTitleHandler,
+ self.EchoHandler] + get_handlers
+ head_handlers = [
+ self.FileHandler,
+ self.DefaultResponseHandler]
+
+ self._mime_types = {
+ 'crx' : 'application/x-chrome-extension',
+ 'exe' : 'application/octet-stream',
+ 'gif': 'image/gif',
+ 'jpeg' : 'image/jpeg',
+ 'jpg' : 'image/jpeg',
+ 'json': 'application/json',
+ 'pdf' : 'application/pdf',
+ 'wav' : 'audio/wav',
+ 'xml' : 'text/xml'
+ }
+ self._default_mime_type = 'text/html'
+
+ testserver_base.BasePageHandler.__init__(self, request, client_address,
+ socket_server, connect_handlers,
+ get_handlers, head_handlers,
+ post_handlers, put_handlers)
+
+ def GetMIMETypeFromName(self, file_name):
+ """Returns the mime type for the specified file_name. So far it only looks
+ at the file extension."""
+
+ (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
+ if len(extension) == 0:
+ # no extension.
+ return self._default_mime_type
+
+ # extension starts with a dot, so we need to remove it
+ return self._mime_types.get(extension[1:], self._default_mime_type)
+
+ def NoCacheMaxAgeTimeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and no caching requested."""
+
+ if not self._ShouldHandleRequest("/nocachetime/maxage"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Cache-Control', 'max-age=0')
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def NoCacheTimeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and no caching requested."""
+
+ if not self._ShouldHandleRequest("/nocachetime"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Cache-Control', 'no-cache')
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheTimeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and allows caching for one minute."""
+
+ if not self._ShouldHandleRequest("/cachetime"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Cache-Control', 'max-age=60')
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheExpiresHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and set the page to expire on 1 Jan 2099."""
+
+ if not self._ShouldHandleRequest("/cache/expires"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheProxyRevalidateHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and allows caching for 60 seconds"""
+
+ if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CachePrivateHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and allows caching for 5 seconds."""
+
+ if not self._ShouldHandleRequest("/cache/private"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=3, private')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CachePublicHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and allows caching for 5 seconds."""
+
+ if not self._ShouldHandleRequest("/cache/public"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=3, public')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheSMaxAgeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow for caching."""
+
+ if not self._ShouldHandleRequest("/cache/s-maxage"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheMustRevalidateHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow caching."""
+
+ if not self._ShouldHandleRequest("/cache/must-revalidate"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'must-revalidate')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheMustRevalidateMaxAgeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow caching event though max-age of 60
+ seconds is specified."""
+
+ if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=60, must-revalidate')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheNoStoreHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow the page to be stored."""
+
+ if not self._ShouldHandleRequest("/cache/no-store"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'no-store')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def CacheNoStoreMaxAgeHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow the page to be stored even though max-age
+ of 60 seconds is specified."""
+
+ if not self._ShouldHandleRequest("/cache/no-store/max-age"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=60, no-store')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+
+ def CacheNoTransformHandler(self):
+ """This request handler yields a page with the title set to the current
+ system time, and does not allow the content to transformed during
+ user-agent caching"""
+
+ if not self._ShouldHandleRequest("/cache/no-transform"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'no-transform')
+ self.end_headers()
+
+ self.wfile.write('<html><head><title>%s</title></head></html>' %
+ time.time())
+
+ return True
+
+ def EchoHeader(self):
+ """This handler echoes back the value of a specific request header."""
+
+ return self.EchoHeaderHelper("/echoheader")
+
+ def EchoHeaderCache(self):
+ """This function echoes back the value of a specific request header while
+ allowing caching for 16 hours."""
+
+ return self.EchoHeaderHelper("/echoheadercache")
+
+ def EchoHeaderHelper(self, echo_header):
+ """This function echoes back the value of the request header passed in."""
+
+ if not self._ShouldHandleRequest(echo_header):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char != -1:
+ header_name = self.path[query_char+1:]
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ if echo_header == '/echoheadercache':
+ self.send_header('Cache-control', 'max-age=60000')
+ else:
+ self.send_header('Cache-control', 'no-cache')
+ # insert a vary header to properly indicate that the cachability of this
+ # request is subject to value of the request header being echoed.
+ if len(header_name) > 0:
+ self.send_header('Vary', header_name)
+ self.end_headers()
+
+ if len(header_name) > 0:
+ self.wfile.write(self.headers.getheader(header_name))
+
+ return True
+
+ def ReadRequestBody(self):
+ """This function reads the body of the current HTTP request, handling
+ both plain and chunked transfer encoded requests."""
+
+ if self.headers.getheader('transfer-encoding') != 'chunked':
+ length = int(self.headers.getheader('content-length'))
+ return self.rfile.read(length)
+
+ # Read the request body as chunks.
+ body = ""
+ while True:
+ line = self.rfile.readline()
+ length = int(line, 16)
+ if length == 0:
+ self.rfile.readline()
+ break
+ body += self.rfile.read(length)
+ self.rfile.read(2)
+ return body
+
+ def EchoHandler(self):
+ """This handler just echoes back the payload of the request, for testing
+ form submission."""
+
+ if not self._ShouldHandleRequest("/echo"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write(self.ReadRequestBody())
+ return True
+
+ def EchoTitleHandler(self):
+ """This handler is like Echo, but sets the page title to the request."""
+
+ if not self._ShouldHandleRequest("/echotitle"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ request = self.ReadRequestBody()
+ self.wfile.write('<html><head><title>')
+ self.wfile.write(request)
+ self.wfile.write('</title></head></html>')
+ return True
+
+ def EchoAllHandler(self):
+ """This handler yields a (more) human-readable page listing information
+ about the request header & contents."""
+
+ if not self._ShouldHandleRequest("/echoall"):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><head><style>'
+ 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
+ '</style></head><body>'
+ '<div style="float: right">'
+ '<a href="/echo">back to referring page</a></div>'
+ '<h1>Request Body:</h1><pre>')
+
+ if self.command == 'POST' or self.command == 'PUT':
+ qs = self.ReadRequestBody()
+ params = cgi.parse_qs(qs, keep_blank_values=1)
+
+ for param in params:
+ self.wfile.write('%s=%s\n' % (param, params[param][0]))
+
+ self.wfile.write('</pre>')
+
+ self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
+
+ self.wfile.write('</body></html>')
+ return True
+
+ def DownloadHandler(self):
+ """This handler sends a downloadable file with or without reporting
+ the size (6K)."""
+
+ if self.path.startswith("/download-unknown-size"):
+ send_length = False
+ elif self.path.startswith("/download-known-size"):
+ send_length = True
+ else:
+ return False
+
+ #
+ # The test which uses this functionality is attempting to send
+ # small chunks of data to the client. Use a fairly large buffer
+ # so that we'll fill chrome's IO buffer enough to force it to
+ # actually write the data.
+ # See also the comments in the client-side of this test in
+ # download_uitest.cc
+ #
+ size_chunk1 = 35*1024
+ size_chunk2 = 10*1024
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/octet-stream')
+ self.send_header('Cache-Control', 'max-age=0')
+ if send_length:
+ self.send_header('Content-Length', size_chunk1 + size_chunk2)
+ self.end_headers()
+
+ # First chunk of data:
+ self.wfile.write("*" * size_chunk1)
+ self.wfile.flush()
+
+ # handle requests until one of them clears this flag.
+ self.server.wait_for_download = True
+ while self.server.wait_for_download:
+ self.server.handle_request()
+
+ # Second chunk of data:
+ self.wfile.write("*" * size_chunk2)
+ return True
+
+ def DownloadFinishHandler(self):
+ """This handler just tells the server to finish the current download."""
+
+ if not self._ShouldHandleRequest("/download-finish"):
+ return False
+
+ self.server.wait_for_download = False
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-Control', 'max-age=0')
+ self.end_headers()
+ return True
+
+ def _ReplaceFileData(self, data, query_parameters):
+ """Replaces matching substrings in a file.
+
+ If the 'replace_text' URL query parameter is present, it is expected to be
+ of the form old_text:new_text, which indicates that any old_text strings in
+ the file are replaced with new_text. Multiple 'replace_text' parameters may
+ be specified.
+
+ If the parameters are not present, |data| is returned.
+ """
+
+ query_dict = cgi.parse_qs(query_parameters)
+ replace_text_values = query_dict.get('replace_text', [])
+ for replace_text_value in replace_text_values:
+ replace_text_args = replace_text_value.split(':')
+ if len(replace_text_args) != 2:
+ raise ValueError(
+ 'replace_text must be of form old_text:new_text. Actual value: %s' %
+ replace_text_value)
+ old_text_b64, new_text_b64 = replace_text_args
+ old_text = base64.urlsafe_b64decode(old_text_b64)
+ new_text = base64.urlsafe_b64decode(new_text_b64)
+ data = data.replace(old_text, new_text)
+ return data
+
+ def ZipFileHandler(self):
+ """This handler sends the contents of the requested file in compressed form.
+ Can pass in a parameter that specifies that the content length be
+ C - the compressed size (OK),
+ U - the uncompressed size (Non-standard, but handled),
+ S - less than compressed (OK because we keep going),
+ M - larger than compressed but less than uncompressed (an error),
+ L - larger than uncompressed (an error)
+ Example: compressedfiles/Picture_1.doc?C
+ """
+
+ prefix = "/compressedfiles/"
+ if not self.path.startswith(prefix):
+ return False
+
+ # Consume a request body if present.
+ if self.command == 'POST' or self.command == 'PUT' :
+ self.ReadRequestBody()
+
+ _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
+
+ if not query in ('C', 'U', 'S', 'M', 'L'):
+ return False
+
+ sub_path = url_path[len(prefix):]
+ entries = sub_path.split('/')
+ file_path = os.path.join(self.server.data_dir, *entries)
+ if os.path.isdir(file_path):
+ file_path = os.path.join(file_path, 'index.html')
+
+ if not os.path.isfile(file_path):
+ print "File not found " + sub_path + " full path:" + file_path
+ self.send_error(404)
+ return True
+
+ f = open(file_path, "rb")
+ data = f.read()
+ uncompressed_len = len(data)
+ f.close()
+
+ # Compress the data.
+ data = zlib.compress(data)
+ compressed_len = len(data)
+
+ content_length = compressed_len
+ if query == 'U':
+ content_length = uncompressed_len
+ elif query == 'S':
+ content_length = compressed_len / 2
+ elif query == 'M':
+ content_length = (compressed_len + uncompressed_len) / 2
+ elif query == 'L':
+ content_length = compressed_len + uncompressed_len
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/msword')
+ self.send_header('Content-encoding', 'deflate')
+ self.send_header('Connection', 'close')
+ self.send_header('Content-Length', content_length)
+ self.send_header('ETag', '\'' + file_path + '\'')
+ self.end_headers()
+
+ self.wfile.write(data)
+
+ return True
+
+ def FileHandler(self):
+ """This handler sends the contents of the requested file. Wow, it's like
+ a real webserver!"""
+
+ prefix = self.server.file_root_url
+ if not self.path.startswith(prefix):
+ return False
+ return self._FileHandlerHelper(prefix)
+
+ def PostOnlyFileHandler(self):
+ """This handler sends the contents of the requested file on a POST."""
+
+ prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
+ if not self.path.startswith(prefix):
+ return False
+ return self._FileHandlerHelper(prefix)
+
+ def _FileHandlerHelper(self, prefix):
+ request_body = ''
+ if self.command == 'POST' or self.command == 'PUT':
+ # Consume a request body if present.
+ request_body = self.ReadRequestBody()
+
+ _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
+ query_dict = cgi.parse_qs(query)
+
+ expected_body = query_dict.get('expected_body', [])
+ if expected_body and request_body not in expected_body:
+ self.send_response(404)
+ self.end_headers()
+ self.wfile.write('')
+ return True
+
+ expected_headers = query_dict.get('expected_headers', [])
+ for expected_header in expected_headers:
+ header_name, expected_value = expected_header.split(':')
+ if self.headers.getheader(header_name) != expected_value:
+ self.send_response(404)
+ self.end_headers()
+ self.wfile.write('')
+ return True
+
+ sub_path = url_path[len(prefix):]
+ entries = sub_path.split('/')
+ file_path = os.path.join(self.server.data_dir, *entries)
+ if os.path.isdir(file_path):
+ file_path = os.path.join(file_path, 'index.html')
+
+ if not os.path.isfile(file_path):
+ print "File not found " + sub_path + " full path:" + file_path
+ self.send_error(404)
+ return True
+
+ f = open(file_path, "rb")
+ data = f.read()
+ f.close()
+
+ data = self._ReplaceFileData(data, query)
+
+ old_protocol_version = self.protocol_version
+
+ # If file.mock-http-headers exists, it contains the headers we
+ # should send. Read them in and parse them.
+ headers_path = file_path + '.mock-http-headers'
+ if os.path.isfile(headers_path):
+ f = open(headers_path, "r")
+
+ # "HTTP/1.1 200 OK"
+ response = f.readline()
+ http_major, http_minor, status_code = re.findall(
+ 'HTTP/(\d+).(\d+) (\d+)', response)[0]
+ self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
+ self.send_response(int(status_code))
+
+ for line in f:
+ header_values = re.findall('(\S+):\s*(.*)', line)
+ if len(header_values) > 0:
+ # "name: value"
+ name, value = header_values[0]
+ self.send_header(name, value)
+ f.close()
+ else:
+ # Could be more generic once we support mime-type sniffing, but for
+ # now we need to set it explicitly.
+
+ range_header = self.headers.get('Range')
+ if range_header and range_header.startswith('bytes='):
+ # Note this doesn't handle all valid byte range_header values (i.e.
+ # left open ended ones), just enough for what we needed so far.
+ range_header = range_header[6:].split('-')
+ start = int(range_header[0])
+ if range_header[1]:
+ end = int(range_header[1])
+ else:
+ end = len(data) - 1
+
+ self.send_response(206)
+ content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
+ str(len(data)))
+ self.send_header('Content-Range', content_range)
+ data = data[start: end + 1]
+ else:
+ self.send_response(200)
+
+ self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
+ self.send_header('Accept-Ranges', 'bytes')
+ self.send_header('Content-Length', len(data))
+ self.send_header('ETag', '\'' + file_path + '\'')
+ self.end_headers()
+
+ if (self.command != 'HEAD'):
+ self.wfile.write(data)
+
+ self.protocol_version = old_protocol_version
+ return True
+
+ def SetCookieHandler(self):
+ """This handler just sets a cookie, for testing cookie handling."""
+
+ if not self._ShouldHandleRequest("/set-cookie"):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char != -1:
+ cookie_values = self.path[query_char + 1:].split('&')
+ else:
+ cookie_values = ("",)
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ for cookie_value in cookie_values:
+ self.send_header('Set-Cookie', '%s' % cookie_value)
+ self.end_headers()
+ for cookie_value in cookie_values:
+ self.wfile.write('%s' % cookie_value)
+ return True
+
+ def SetManyCookiesHandler(self):
+ """This handler just sets a given number of cookies, for testing handling
+ of large numbers of cookies."""
+
+ if not self._ShouldHandleRequest("/set-many-cookies"):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char != -1:
+ num_cookies = int(self.path[query_char + 1:])
+ else:
+ num_cookies = 0
+ self.send_response(200)
+ self.send_header('', 'text/html')
+ for _i in range(0, num_cookies):
+ self.send_header('Set-Cookie', 'a=')
+ self.end_headers()
+ self.wfile.write('%d cookies were sent' % num_cookies)
+ return True
+
+ def ExpectAndSetCookieHandler(self):
+ """Expects some cookies to be sent, and if they are, sets more cookies.
+
+ The expect parameter specifies a required cookie. May be specified multiple
+ times.
+ The set parameter specifies a cookie to set if all required cookies are
+ preset. May be specified multiple times.
+ The data parameter specifies the response body data to be returned."""
+
+ if not self._ShouldHandleRequest("/expect-and-set-cookie"):
+ return False
+
+ _, _, _, _, query, _ = urlparse.urlparse(self.path)
+ query_dict = cgi.parse_qs(query)
+ cookies = set()
+ if 'Cookie' in self.headers:
+ cookie_header = self.headers.getheader('Cookie')
+ cookies.update([s.strip() for s in cookie_header.split(';')])
+ got_all_expected_cookies = True
+ for expected_cookie in query_dict.get('expect', []):
+ if expected_cookie not in cookies:
+ got_all_expected_cookies = False
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ if got_all_expected_cookies:
+ for cookie_value in query_dict.get('set', []):
+ self.send_header('Set-Cookie', '%s' % cookie_value)
+ self.end_headers()
+ for data_value in query_dict.get('data', []):
+ self.wfile.write(data_value)
+ return True
+
+ def SetHeaderHandler(self):
+ """This handler sets a response header. Parameters are in the
+ key%3A%20value&key2%3A%20value2 format."""
+
+ if not self._ShouldHandleRequest("/set-header"):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char != -1:
+ headers_values = self.path[query_char + 1:].split('&')
+ else:
+ headers_values = ("",)
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ for header_value in headers_values:
+ header_value = urllib.unquote(header_value)
+ (key, value) = header_value.split(': ', 1)
+ self.send_header(key, value)
+ self.end_headers()
+ for header_value in headers_values:
+ self.wfile.write('%s' % header_value)
+ return True
+
+ def AuthBasicHandler(self):
+ """This handler tests 'Basic' authentication. It just sends a page with
+ title 'user/pass' if you succeed."""
+
+ if not self._ShouldHandleRequest("/auth-basic"):
+ return False
+
+ username = userpass = password = b64str = ""
+ expected_password = 'secret'
+ realm = 'testrealm'
+ set_cookie_if_challenged = False
+
+ _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
+ query_params = cgi.parse_qs(query, True)
+ if 'set-cookie-if-challenged' in query_params:
+ set_cookie_if_challenged = True
+ if 'password' in query_params:
+ expected_password = query_params['password'][0]
+ if 'realm' in query_params:
+ realm = query_params['realm'][0]
+
+ auth = self.headers.getheader('authorization')
+ try:
+ if not auth:
+ raise Exception('no auth')
+ b64str = re.findall(r'Basic (\S+)', auth)[0]
+ userpass = base64.b64decode(b64str)
+ username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
+ if password != expected_password:
+ raise Exception('wrong password')
+ except Exception, e:
+ # Authentication failed.
+ self.send_response(401)
+ self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
+ self.send_header('Content-Type', 'text/html')
+ if set_cookie_if_challenged:
+ self.send_header('Set-Cookie', 'got_challenged=true')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('<title>Denied: %s</title>' % e)
+ self.wfile.write('</head><body>')
+ self.wfile.write('auth=%s<p>' % auth)
+ self.wfile.write('b64str=%s<p>' % b64str)
+ self.wfile.write('username: %s<p>' % username)
+ self.wfile.write('userpass: %s<p>' % userpass)
+ self.wfile.write('password: %s<p>' % password)
+ self.wfile.write('You sent:<br>%s<p>' % self.headers)
+ self.wfile.write('</body></html>')
+ return True
+
+ # Authentication successful. (Return a cachable response to allow for
+ # testing cached pages that require authentication.)
+ old_protocol_version = self.protocol_version
+ self.protocol_version = "HTTP/1.1"
+
+ if_none_match = self.headers.getheader('if-none-match')
+ if if_none_match == "abc":
+ self.send_response(304)
+ self.end_headers()
+ elif url_path.endswith(".gif"):
+ # Using chrome/test/data/google/logo.gif as the test image
+ test_image_path = ['google', 'logo.gif']
+ gif_path = os.path.join(self.server.data_dir, *test_image_path)
+ if not os.path.isfile(gif_path):
+ self.send_error(404)
+ self.protocol_version = old_protocol_version
+ return True
+
+ f = open(gif_path, "rb")
+ data = f.read()
+ f.close()
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'image/gif')
+ self.send_header('Cache-control', 'max-age=60000')
+ self.send_header('Etag', 'abc')
+ self.end_headers()
+ self.wfile.write(data)
+ else:
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Cache-control', 'max-age=60000')
+ self.send_header('Etag', 'abc')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('<title>%s/%s</title>' % (username, password))
+ self.wfile.write('</head><body>')
+ self.wfile.write('auth=%s<p>' % auth)
+ self.wfile.write('You sent:<br>%s<p>' % self.headers)
+ self.wfile.write('</body></html>')
+
+ self.protocol_version = old_protocol_version
+ return True
+
+ def GetNonce(self, force_reset=False):
+ """Returns a nonce that's stable per request path for the server's lifetime.
+ This is a fake implementation. A real implementation would only use a given
+ nonce a single time (hence the name n-once). However, for the purposes of
+ unittesting, we don't care about the security of the nonce.
+
+ Args:
+ force_reset: Iff set, the nonce will be changed. Useful for testing the
+ "stale" response.
+ """
+
+ if force_reset or not self.server.nonce_time:
+ self.server.nonce_time = time.time()
+ return hashlib.md5('privatekey%s%d' %
+ (self.path, self.server.nonce_time)).hexdigest()
+
+ def AuthDigestHandler(self):
+ """This handler tests 'Digest' authentication.
+
+ It just sends a page with title 'user/pass' if you succeed.
+
+ A stale response is sent iff "stale" is present in the request path.
+ """
+
+ if not self._ShouldHandleRequest("/auth-digest"):
+ return False
+
+ stale = 'stale' in self.path
+ nonce = self.GetNonce(force_reset=stale)
+ opaque = hashlib.md5('opaque').hexdigest()
+ password = 'secret'
+ realm = 'testrealm'
+
+ auth = self.headers.getheader('authorization')
+ pairs = {}
+ try:
+ if not auth:
+ raise Exception('no auth')
+ if not auth.startswith('Digest'):
+ raise Exception('not digest')
+ # Pull out all the name="value" pairs as a dictionary.
+ pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
+
+ # Make sure it's all valid.
+ if pairs['nonce'] != nonce:
+ raise Exception('wrong nonce')
+ if pairs['opaque'] != opaque:
+ raise Exception('wrong opaque')
+
+ # Check the 'response' value and make sure it matches our magic hash.
+ # See http://www.ietf.org/rfc/rfc2617.txt
+ hash_a1 = hashlib.md5(
+ ':'.join([pairs['username'], realm, password])).hexdigest()
+ hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
+ if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
+ response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
+ pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
+ else:
+ response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
+
+ if pairs['response'] != response:
+ raise Exception('wrong password')
+ except Exception, e:
+ # Authentication failed.
+ self.send_response(401)
+ hdr = ('Digest '
+ 'realm="%s", '
+ 'domain="/", '
+ 'qop="auth", '
+ 'algorithm=MD5, '
+ 'nonce="%s", '
+ 'opaque="%s"') % (realm, nonce, opaque)
+ if stale:
+ hdr += ', stale="TRUE"'
+ self.send_header('WWW-Authenticate', hdr)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('<title>Denied: %s</title>' % e)
+ self.wfile.write('</head><body>')
+ self.wfile.write('auth=%s<p>' % auth)
+ self.wfile.write('pairs=%s<p>' % pairs)
+ self.wfile.write('You sent:<br>%s<p>' % self.headers)
+ self.wfile.write('We are replying:<br>%s<p>' % hdr)
+ self.wfile.write('</body></html>')
+ return True
+
+ # Authentication successful.
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
+ self.wfile.write('</head><body>')
+ self.wfile.write('auth=%s<p>' % auth)
+ self.wfile.write('pairs=%s<p>' % pairs)
+ self.wfile.write('</body></html>')
+
+ return True
+
+ def SlowServerHandler(self):
+ """Wait for the user suggested time before responding. The syntax is
+ /slow?0.5 to wait for half a second."""
+
+ if not self._ShouldHandleRequest("/slow"):
+ return False
+ query_char = self.path.find('?')
+ wait_sec = 1.0
+ if query_char >= 0:
+ try:
+ wait_sec = int(self.path[query_char + 1:])
+ except ValueError:
+ pass
+ time.sleep(wait_sec)
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+ self.wfile.write("waited %d seconds" % wait_sec)
+ return True
+
+ def ChunkedServerHandler(self):
+ """Send chunked response. Allows to specify chunks parameters:
+ - waitBeforeHeaders - ms to wait before sending headers
+ - waitBetweenChunks - ms to wait between chunks
+ - chunkSize - size of each chunk in bytes
+ - chunksNumber - number of chunks
+ Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
+ waits one second, then sends headers and five chunks five bytes each."""
+
+ if not self._ShouldHandleRequest("/chunked"):
+ return False
+ query_char = self.path.find('?')
+ chunkedSettings = {'waitBeforeHeaders' : 0,
+ 'waitBetweenChunks' : 0,
+ 'chunkSize' : 5,
+ 'chunksNumber' : 5}
+ if query_char >= 0:
+ params = self.path[query_char + 1:].split('&')
+ for param in params:
+ keyValue = param.split('=')
+ if len(keyValue) == 2:
+ try:
+ chunkedSettings[keyValue[0]] = int(keyValue[1])
+ except ValueError:
+ pass
+ time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
+ self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.send_header('Connection', 'close')
+ self.send_header('Transfer-Encoding', 'chunked')
+ self.end_headers()
+ # Chunked encoding: sending all chunks, then final zero-length chunk and
+ # then final CRLF.
+ for i in range(0, chunkedSettings['chunksNumber']):
+ if i > 0:
+ time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
+ self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
+ self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
+ self.sendChunkHelp('')
+ return True
+
+ def ContentTypeHandler(self):
+ """Returns a string of html with the given content type. E.g.,
+ /contenttype?text/css returns an html file with the Content-Type
+ header set to text/css."""
+
+ if not self._ShouldHandleRequest("/contenttype"):
+ return False
+ query_char = self.path.find('?')
+ content_type = self.path[query_char + 1:].strip()
+ if not content_type:
+ content_type = 'text/html'
+ self.send_response(200)
+ self.send_header('Content-Type', content_type)
+ self.end_headers()
+ self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
+ return True
+
+ def NoContentHandler(self):
+ """Returns a 204 No Content response."""
+
+ if not self._ShouldHandleRequest("/nocontent"):
+ return False
+ self.send_response(204)
+ self.end_headers()
+ return True
+
+ def ServerRedirectHandler(self):
+ """Sends a server redirect to the given URL. The syntax is
+ '/server-redirect?http://foo.bar/asdf' to redirect to
+ 'http://foo.bar/asdf'"""
+
+ test_name = "/server-redirect"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char < 0 or len(self.path) <= query_char + 1:
+ self.sendRedirectHelp(test_name)
+ return True
+ dest = self.path[query_char + 1:]
+
+ self.send_response(301) # moved permanently
+ self.send_header('Location', dest)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
+
+ return True
+
+ def ClientRedirectHandler(self):
+ """Sends a client redirect to the given URL. The syntax is
+ '/client-redirect?http://foo.bar/asdf' to redirect to
+ 'http://foo.bar/asdf'"""
+
+ test_name = "/client-redirect"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ query_char = self.path.find('?')
+ if query_char < 0 or len(self.path) <= query_char + 1:
+ self.sendRedirectHelp(test_name)
+ return True
+ dest = self.path[query_char + 1:]
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><head>')
+ self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
+ self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
+
+ return True
+
+ def MultipartHandler(self):
+ """Send a multipart response (10 text/html pages)."""
+
+ test_name = '/multipart'
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ num_frames = 10
+ bound = '12345'
+ self.send_response(200)
+ self.send_header('Content-Type',
+ 'multipart/x-mixed-replace;boundary=' + bound)
+ self.end_headers()
+
+ for i in xrange(num_frames):
+ self.wfile.write('--' + bound + '\r\n')
+ self.wfile.write('Content-Type: text/html\r\n\r\n')
+ self.wfile.write('<title>page ' + str(i) + '</title>')
+ self.wfile.write('page ' + str(i))
+
+ self.wfile.write('--' + bound + '--')
+ return True
+
+ def GetSSLSessionCacheHandler(self):
+ """Send a reply containing a log of the session cache operations."""
+
+ if not self._ShouldHandleRequest('/ssl-session-cache'):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+ try:
+ for (action, sessionID) in self.server.session_cache.log:
+ self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
+ except AttributeError:
+ self.wfile.write('Pass --https-record-resume in order to use' +
+ ' this request')
+ return True
+
+ def SSLManySmallRecords(self):
+ """Sends a reply consisting of a variety of small writes. These will be
+ translated into a series of small SSL records when used over an HTTPS
+ server."""
+
+ if not self._ShouldHandleRequest('/ssl-many-small-records'):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+
+ # Write ~26K of data, in 1350 byte chunks
+ for i in xrange(20):
+ self.wfile.write('*' * 1350)
+ self.wfile.flush()
+ return True
+
+ def GetChannelID(self):
+ """Send a reply containing the hashed ChannelID that the client provided."""
+
+ if not self._ShouldHandleRequest('/channel-id'):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/plain')
+ self.end_headers()
+ channel_id = self.server.tlsConnection.channel_id.tostring()
+ self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
+ return True
+
+ def CloseSocketHandler(self):
+ """Closes the socket without sending anything."""
+
+ if not self._ShouldHandleRequest('/close-socket'):
+ return False
+
+ self.wfile.close()
+ return True
+
+ def RangeResetHandler(self):
+ """Send data broken up by connection resets every N (default 4K) bytes.
+ Support range requests. If the data requested doesn't straddle a reset
+ boundary, it will all be sent. Used for testing resuming downloads."""
+
+ def DataForRange(start, end):
+ """Data to be provided for a particular range of bytes."""
+ # Offset and scale to avoid too obvious (and hence potentially
+ # collidable) data.
+ return ''.join([chr(y % 256)
+ for y in range(start * 2 + 15, end * 2 + 15, 2)])
+
+ if not self._ShouldHandleRequest('/rangereset'):
+ return False
+
+ _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
+
+ # Defaults
+ size = 8000
+ # Note that the rst is sent just before sending the rst_boundary byte.
+ rst_boundary = 4000
+ respond_to_range = True
+ hold_for_signal = False
+ rst_limit = -1
+ token = 'DEFAULT'
+ fail_precondition = 0
+ send_verifiers = True
+
+ # Parse the query
+ qdict = urlparse.parse_qs(query, True)
+ if 'size' in qdict:
+ size = int(qdict['size'][0])
+ if 'rst_boundary' in qdict:
+ rst_boundary = int(qdict['rst_boundary'][0])
+ if 'token' in qdict:
+ # Identifying token for stateful tests.
+ token = qdict['token'][0]
+ if 'rst_limit' in qdict:
+ # Max number of rsts for a given token.
+ rst_limit = int(qdict['rst_limit'][0])
+ if 'bounce_range' in qdict:
+ respond_to_range = False
+ if 'hold' in qdict:
+ # Note that hold_for_signal will not work with null range requests;
+ # see TODO below.
+ hold_for_signal = True
+ if 'no_verifiers' in qdict:
+ send_verifiers = False
+ if 'fail_precondition' in qdict:
+ fail_precondition = int(qdict['fail_precondition'][0])
+
+ # Record already set information, or set it.
+ rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
+ if rst_limit != 0:
+ TestPageHandler.rst_limits[token] -= 1
+ fail_precondition = TestPageHandler.fail_precondition.setdefault(
+ token, fail_precondition)
+ if fail_precondition != 0:
+ TestPageHandler.fail_precondition[token] -= 1
+
+ first_byte = 0
+ last_byte = size - 1
+
+ # Does that define what we want to return, or do we need to apply
+ # a range?
+ range_response = False
+ range_header = self.headers.getheader('range')
+ if range_header and respond_to_range:
+ mo = re.match("bytes=(\d*)-(\d*)", range_header)
+ if mo.group(1):
+ first_byte = int(mo.group(1))
+ if mo.group(2):
+ last_byte = int(mo.group(2))
+ if last_byte > size - 1:
+ last_byte = size - 1
+ range_response = True
+ if last_byte < first_byte:
+ return False
+
+ if (fail_precondition and
+ (self.headers.getheader('If-Modified-Since') or
+ self.headers.getheader('If-Match'))):
+ self.send_response(412)
+ self.end_headers()
+ return True
+
+ if range_response:
+ self.send_response(206)
+ self.send_header('Content-Range',
+ 'bytes %d-%d/%d' % (first_byte, last_byte, size))
+ else:
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/octet-stream')
+ self.send_header('Content-Length', last_byte - first_byte + 1)
+ if send_verifiers:
+ self.send_header('Etag', '"XYZZY"')
+ self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
+ self.end_headers()
+
+ if hold_for_signal:
+ # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
+ # a single byte, the self.server.handle_request() below hangs
+ # without processing new incoming requests.
+ self.wfile.write(DataForRange(first_byte, first_byte + 1))
+ first_byte = first_byte + 1
+ # handle requests until one of them clears this flag.
+ self.server.wait_for_download = True
+ while self.server.wait_for_download:
+ self.server.handle_request()
+
+ possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
+ if possible_rst >= last_byte or rst_limit == 0:
+ # No RST has been requested in this range, so we don't need to
+ # do anything fancy; just write the data and let the python
+ # infrastructure close the connection.
+ self.wfile.write(DataForRange(first_byte, last_byte + 1))
+ self.wfile.flush()
+ return True
+
+ # We're resetting the connection part way in; go to the RST
+ # boundary and then send an RST.
+ # Because socket semantics do not guarantee that all the data will be
+ # sent when using the linger semantics to hard close a socket,
+ # we send the data and then wait for our peer to release us
+ # before sending the reset.
+ data = DataForRange(first_byte, possible_rst)
+ self.wfile.write(data)
+ self.wfile.flush()
+ self.server.wait_for_download = True
+ while self.server.wait_for_download:
+ self.server.handle_request()
+ l_onoff = 1 # Linger is active.
+ l_linger = 0 # Seconds to linger for.
+ self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
+ struct.pack('ii', l_onoff, l_linger))
+
+ # Close all duplicates of the underlying socket to force the RST.
+ self.wfile.close()
+ self.rfile.close()
+ self.connection.close()
+
+ return True
+
+ def DefaultResponseHandler(self):
+ """This is the catch-all response handler for requests that aren't handled
+ by one of the special handlers above.
+ Note that we specify the content-length as without it the https connection
+ is not closed properly (and the browser keeps expecting data)."""
+
+ contents = "Default response given for path: " + self.path
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Content-Length', len(contents))
+ self.end_headers()
+ if (self.command != 'HEAD'):
+ self.wfile.write(contents)
+ return True
+
+ def RedirectConnectHandler(self):
+ """Sends a redirect to the CONNECT request for www.redirect.com. This
+ response is not specified by the RFC, so the browser should not follow
+ the redirect."""
+
+ if (self.path.find("www.redirect.com") < 0):
+ return False
+
+ dest = "http://www.destination.com/foo.js"
+
+ self.send_response(302) # moved temporarily
+ self.send_header('Location', dest)
+ self.send_header('Connection', 'close')
+ self.end_headers()
+ return True
+
+ def ServerAuthConnectHandler(self):
+ """Sends a 401 to the CONNECT request for www.server-auth.com. This
+ response doesn't make sense because the proxy server cannot request
+ server authentication."""
+
+ if (self.path.find("www.server-auth.com") < 0):
+ return False
+
+ challenge = 'Basic realm="WallyWorld"'
+
+ self.send_response(401) # unauthorized
+ self.send_header('WWW-Authenticate', challenge)
+ self.send_header('Connection', 'close')
+ self.end_headers()
+ return True
+
+ def DefaultConnectResponseHandler(self):
+ """This is the catch-all response handler for CONNECT requests that aren't
+ handled by one of the special handlers above. Real Web servers respond
+ with 400 to CONNECT requests."""
+
+ contents = "Your client has issued a malformed or illegal request."
+ self.send_response(400) # bad request
+ self.send_header('Content-Type', 'text/html')
+ self.send_header('Content-Length', len(contents))
+ self.end_headers()
+ self.wfile.write(contents)
+ return True
+
+ # called by the redirect handling function when there is no parameter
+ def sendRedirectHelp(self, redirect_name):
+ self.send_response(200)
+ self.send_header('Content-Type', 'text/html')
+ self.end_headers()
+ self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
+ self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
+ self.wfile.write('</body></html>')
+
+ # called by chunked handling function
+ def sendChunkHelp(self, chunk):
+ # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
+ self.wfile.write('%X\r\n' % len(chunk))
+ self.wfile.write(chunk)
+ self.wfile.write('\r\n')
+
+
+class OCSPHandler(testserver_base.BasePageHandler):
+ def __init__(self, request, client_address, socket_server):
+ handlers = [self.OCSPResponse]
+ self.ocsp_response = socket_server.ocsp_response
+ testserver_base.BasePageHandler.__init__(self, request, client_address,
+ socket_server, [], handlers, [],
+ handlers, [])
+
+ def OCSPResponse(self):
+ self.send_response(200)
+ self.send_header('Content-Type', 'application/ocsp-response')
+ self.send_header('Content-Length', str(len(self.ocsp_response)))
+ self.end_headers()
+
+ self.wfile.write(self.ocsp_response)
+
+
+class TCPEchoHandler(SocketServer.BaseRequestHandler):
+ """The RequestHandler class for TCP echo server.
+
+ It is instantiated once per connection to the server, and overrides the
+ handle() method to implement communication to the client.
+ """
+
+ def handle(self):
+ """Handles the request from the client and constructs a response."""
+
+ data = self.request.recv(65536).strip()
+ # Verify the "echo request" message received from the client. Send back
+ # "echo response" message if "echo request" message is valid.
+ try:
+ return_data = echo_message.GetEchoResponseData(data)
+ if not return_data:
+ return
+ except ValueError:
+ return
+
+ self.request.send(return_data)
+
+
+class UDPEchoHandler(SocketServer.BaseRequestHandler):
+ """The RequestHandler class for UDP echo server.
+
+ It is instantiated once per connection to the server, and overrides the
+ handle() method to implement communication to the client.
+ """
+
+ def handle(self):
+ """Handles the request from the client and constructs a response."""
+
+ data = self.request[0].strip()
+ request_socket = self.request[1]
+ # Verify the "echo request" message received from the client. Send back
+ # "echo response" message if "echo request" message is valid.
+ try:
+ return_data = echo_message.GetEchoResponseData(data)
+ if not return_data:
+ return
+ except ValueError:
+ return
+ request_socket.sendto(return_data, self.client_address)
+
+
+class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """A request handler that behaves as a proxy server which requires
+ basic authentication. Only CONNECT, GET and HEAD is supported for now.
+ """
+
+ _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
+
+ def parse_request(self):
+ """Overrides parse_request to check credential."""
+
+ if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
+ return False
+
+ auth = self.headers.getheader('Proxy-Authorization')
+ if auth != self._AUTH_CREDENTIAL:
+ self.send_response(407)
+ self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
+ self.end_headers()
+ return False
+
+ return True
+
+ def _start_read_write(self, sock):
+ sock.setblocking(0)
+ self.request.setblocking(0)
+ rlist = [self.request, sock]
+ while True:
+ ready_sockets, _unused, errors = select.select(rlist, [], [])
+ if errors:
+ self.send_response(500)
+ self.end_headers()
+ return
+ for s in ready_sockets:
+ received = s.recv(1024)
+ if len(received) == 0:
+ return
+ if s == self.request:
+ other = sock
+ else:
+ other = self.request
+ other.send(received)
+
+ def _do_common_method(self):
+ url = urlparse.urlparse(self.path)
+ port = url.port
+ if not port:
+ if url.scheme == 'http':
+ port = 80
+ elif url.scheme == 'https':
+ port = 443
+ if not url.hostname or not port:
+ self.send_response(400)
+ self.end_headers()
+ return
+
+ if len(url.path) == 0:
+ path = '/'
+ else:
+ path = url.path
+ if len(url.query) > 0:
+ path = '%s?%s' % (url.path, url.query)
+
+ sock = None
+ try:
+ sock = socket.create_connection((url.hostname, port))
+ sock.send('%s %s %s\r\n' % (
+ self.command, path, self.protocol_version))
+ for header in self.headers.headers:
+ header = header.strip()
+ if (header.lower().startswith('connection') or
+ header.lower().startswith('proxy')):
+ continue
+ sock.send('%s\r\n' % header)
+ sock.send('\r\n')
+ self._start_read_write(sock)
+ except Exception:
+ self.send_response(500)
+ self.end_headers()
+ finally:
+ if sock is not None:
+ sock.close()
+
+ def do_CONNECT(self):
+ try:
+ pos = self.path.rfind(':')
+ host = self.path[:pos]
+ port = int(self.path[pos+1:])
+ except Exception:
+ self.send_response(400)
+ self.end_headers()
+
+ try:
+ sock = socket.create_connection((host, port))
+ self.send_response(200, 'Connection established')
+ self.end_headers()
+ self._start_read_write(sock)
+ except Exception:
+ self.send_response(500)
+ self.end_headers()
+ finally:
+ sock.close()
+
+ def do_GET(self):
+ self._do_common_method()
+
+ def do_HEAD(self):
+ self._do_common_method()
+
+
+class ServerRunner(testserver_base.TestServerRunner):
+ """TestServerRunner for the net test servers."""
+
+ def __init__(self):
+ super(ServerRunner, self).__init__()
+ self.__ocsp_server = None
+
+ def __make_data_dir(self):
+ if self.options.data_dir:
+ if not os.path.isdir(self.options.data_dir):
+ raise testserver_base.OptionError('specified data dir not found: ' +
+ self.options.data_dir + ' exiting...')
+ my_data_dir = self.options.data_dir
+ else:
+ # Create the default path to our data dir, relative to the exe dir.
+ my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
+ "test", "data")
+
+ #TODO(ibrar): Must use Find* funtion defined in google\tools
+ #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
+
+ return my_data_dir
+
+ def create_server(self, server_data):
+ port = self.options.port
+ host = self.options.host
+
+ if self.options.server_type == SERVER_HTTP:
+ if self.options.https:
+ pem_cert_and_key = None
+ if self.options.cert_and_key_file:
+ if not os.path.isfile(self.options.cert_and_key_file):
+ raise testserver_base.OptionError(
+ 'specified server cert file not found: ' +
+ self.options.cert_and_key_file + ' exiting...')
+ pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
+ else:
+ # generate a new certificate and run an OCSP server for it.
+ self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
+ print ('OCSP server started on %s:%d...' %
+ (host, self.__ocsp_server.server_port))
+
+ ocsp_der = None
+ ocsp_state = None
+
+ if self.options.ocsp == 'ok':
+ ocsp_state = minica.OCSP_STATE_GOOD
+ elif self.options.ocsp == 'revoked':
+ ocsp_state = minica.OCSP_STATE_REVOKED
+ elif self.options.ocsp == 'invalid':
+ ocsp_state = minica.OCSP_STATE_INVALID
+ elif self.options.ocsp == 'unauthorized':
+ ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
+ elif self.options.ocsp == 'unknown':
+ ocsp_state = minica.OCSP_STATE_UNKNOWN
+ else:
+ raise testserver_base.OptionError('unknown OCSP status: ' +
+ self.options.ocsp_status)
+
+ (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
+ subject = "127.0.0.1",
+ ocsp_url = ("http://%s:%d/ocsp" %
+ (host, self.__ocsp_server.server_port)),
+ ocsp_state = ocsp_state,
+ serial = self.options.cert_serial)
+
+ self.__ocsp_server.ocsp_response = ocsp_der
+
+ for ca_cert in self.options.ssl_client_ca:
+ if not os.path.isfile(ca_cert):
+ raise testserver_base.OptionError(
+ 'specified trusted client CA file not found: ' + ca_cert +
+ ' exiting...')
+ server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
+ self.options.ssl_client_auth,
+ self.options.ssl_client_ca,
+ self.options.ssl_bulk_cipher,
+ self.options.record_resume,
+ self.options.tls_intolerant)
+ print 'HTTPS server started on %s:%d...' % (host, server.server_port)
+ else:
+ server = HTTPServer((host, port), TestPageHandler)
+ print 'HTTP server started on %s:%d...' % (host, server.server_port)
+
+ server.data_dir = self.__make_data_dir()
+ server.file_root_url = self.options.file_root_url
+ server_data['port'] = server.server_port
+ elif self.options.server_type == SERVER_WEBSOCKET:
+ # Launch pywebsocket via WebSocketServer.
+ logger = logging.getLogger()
+ logger.addHandler(logging.StreamHandler())
+ # TODO(toyoshim): Remove following os.chdir. Currently this operation
+ # is required to work correctly. It should be fixed from pywebsocket side.
+ os.chdir(self.__make_data_dir())
+ websocket_options = WebSocketOptions(host, port, '.')
+ if self.options.cert_and_key_file:
+ websocket_options.use_tls = True
+ websocket_options.private_key = self.options.cert_and_key_file
+ websocket_options.certificate = self.options.cert_and_key_file
+ if self.options.ssl_client_auth:
+ websocket_options.tls_client_auth = True
+ if len(self.options.ssl_client_ca) != 1:
+ raise testserver_base.OptionError(
+ 'one trusted client CA file should be specified')
+ if not os.path.isfile(self.options.ssl_client_ca[0]):
+ raise testserver_base.OptionError(
+ 'specified trusted client CA file not found: ' +
+ self.options.ssl_client_ca[0] + ' exiting...')
+ websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
+ server = WebSocketServer(websocket_options)
+ print 'WebSocket server started on %s:%d...' % (host, server.server_port)
+ server_data['port'] = server.server_port
+ elif self.options.server_type == SERVER_TCP_ECHO:
+ # Used for generating the key (randomly) that encodes the "echo request"
+ # message.
+ random.seed()
+ server = TCPEchoServer((host, port), TCPEchoHandler)
+ print 'Echo TCP server started on port %d...' % server.server_port
+ server_data['port'] = server.server_port
+ elif self.options.server_type == SERVER_UDP_ECHO:
+ # Used for generating the key (randomly) that encodes the "echo request"
+ # message.
+ random.seed()
+ server = UDPEchoServer((host, port), UDPEchoHandler)
+ print 'Echo UDP server started on port %d...' % server.server_port
+ server_data['port'] = server.server_port
+ elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
+ server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
+ print 'BasicAuthProxy server started on port %d...' % server.server_port
+ server_data['port'] = server.server_port
+ elif self.options.server_type == SERVER_FTP:
+ my_data_dir = self.__make_data_dir()
+
+ # Instantiate a dummy authorizer for managing 'virtual' users
+ authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
+
+ # Define a new user having full r/w permissions and a read-only
+ # anonymous user
+ authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
+
+ authorizer.add_anonymous(my_data_dir)
+
+ # Instantiate FTP handler class
+ ftp_handler = pyftpdlib.ftpserver.FTPHandler
+ ftp_handler.authorizer = authorizer
+
+ # Define a customized banner (string returned when client connects)
+ ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
+ pyftpdlib.ftpserver.__ver__)
+
+ # Instantiate FTP server class and listen to address:port
+ server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
+ server_data['port'] = server.socket.getsockname()[1]
+ print 'FTP server started on port %d...' % server_data['port']
+ else:
+ raise testserver_base.OptionError('unknown server type' +
+ self.options.server_type)
+
+ return server
+
+ def run_server(self):
+ if self.__ocsp_server:
+ self.__ocsp_server.serve_forever_on_thread()
+
+ testserver_base.TestServerRunner.run_server(self)
+
+ if self.__ocsp_server:
+ self.__ocsp_server.stop_serving()
+
+ def add_options(self):
+ testserver_base.TestServerRunner.add_options(self)
+ self.option_parser.add_option('-f', '--ftp', action='store_const',
+ const=SERVER_FTP, default=SERVER_HTTP,
+ dest='server_type',
+ help='start up an FTP server.')
+ self.option_parser.add_option('--tcp-echo', action='store_const',
+ const=SERVER_TCP_ECHO, default=SERVER_HTTP,
+ dest='server_type',
+ help='start up a tcp echo server.')
+ self.option_parser.add_option('--udp-echo', action='store_const',
+ const=SERVER_UDP_ECHO, default=SERVER_HTTP,
+ dest='server_type',
+ help='start up a udp echo server.')
+ self.option_parser.add_option('--basic-auth-proxy', action='store_const',
+ const=SERVER_BASIC_AUTH_PROXY,
+ default=SERVER_HTTP, dest='server_type',
+ help='start up a proxy server which requires '
+ 'basic authentication.')
+ self.option_parser.add_option('--websocket', action='store_const',
+ const=SERVER_WEBSOCKET, default=SERVER_HTTP,
+ dest='server_type',
+ help='start up a WebSocket server.')
+ self.option_parser.add_option('--https', action='store_true',
+ dest='https', help='Specify that https '
+ 'should be used.')
+ self.option_parser.add_option('--cert-and-key-file',
+ dest='cert_and_key_file', help='specify the '
+ 'path to the file containing the certificate '
+ 'and private key for the server in PEM '
+ 'format')
+ self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
+ help='The type of OCSP response generated '
+ 'for the automatically generated '
+ 'certificate. One of [ok,revoked,invalid]')
+ self.option_parser.add_option('--cert-serial', dest='cert_serial',
+ default=0, type=int,
+ help='If non-zero then the generated '
+ 'certificate will have this serial number')
+ self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
+ default='0', type='int',
+ help='If nonzero, certain TLS connections '
+ 'will be aborted in order to test version '
+ 'fallback. 1 means all TLS versions will be '
+ 'aborted. 2 means TLS 1.1 or higher will be '
+ 'aborted. 3 means TLS 1.2 or higher will be '
+ 'aborted.')
+ self.option_parser.add_option('--https-record-resume',
+ dest='record_resume', const=True,
+ default=False, action='store_const',
+ help='Record resumption cache events rather '
+ 'than resuming as normal. Allows the use of '
+ 'the /ssl-session-cache request')
+ self.option_parser.add_option('--ssl-client-auth', action='store_true',
+ help='Require SSL client auth on every '
+ 'connection.')
+ self.option_parser.add_option('--ssl-client-ca', action='append',
+ default=[], help='Specify that the client '
+ 'certificate request should include the CA '
+ 'named in the subject of the DER-encoded '
+ 'certificate contained in the specified '
+ 'file. This option may appear multiple '
+ 'times, indicating multiple CA names should '
+ 'be sent in the request.')
+ self.option_parser.add_option('--ssl-bulk-cipher', action='append',
+ help='Specify the bulk encryption '
+ 'algorithm(s) that will be accepted by the '
+ 'SSL server. Valid values are "aes256", '
+ '"aes128", "3des", "rc4". If omitted, all '
+ 'algorithms will be used. This option may '
+ 'appear multiple times, indicating '
+ 'multiple algorithms should be enabled.');
+ self.option_parser.add_option('--file-root-url', default='/files/',
+ help='Specify a root URL for files served.')
+
+
+if __name__ == '__main__':
+ sys.exit(ServerRunner().main())
diff --git a/chromium/net/tools/testserver/testserver_base.py b/chromium/net/tools/testserver/testserver_base.py
new file mode 100644
index 00000000000..455ca5c3202
--- /dev/null
+++ b/chromium/net/tools/testserver/testserver_base.py
@@ -0,0 +1,250 @@
+# Copyright 2013 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.
+
+import BaseHTTPServer
+import errno
+import json
+import optparse
+import os
+import re
+import socket
+import SocketServer
+import struct
+import sys
+import warnings
+
+# Ignore deprecation warnings, they make our output more cluttered.
+warnings.filterwarnings("ignore", category=DeprecationWarning)
+
+if sys.platform == 'win32':
+ import msvcrt
+
+# Using debug() seems to cause hangs on XP: see http://crbug.com/64515.
+debug_output = sys.stderr
+def debug(string):
+ debug_output.write(string + "\n")
+ debug_output.flush()
+
+
+class Error(Exception):
+ """Error class for this module."""
+
+
+class OptionError(Error):
+ """Error for bad command line options."""
+
+
+class FileMultiplexer(object):
+ def __init__(self, fd1, fd2) :
+ self.__fd1 = fd1
+ self.__fd2 = fd2
+
+ def __del__(self) :
+ if self.__fd1 != sys.stdout and self.__fd1 != sys.stderr:
+ self.__fd1.close()
+ if self.__fd2 != sys.stdout and self.__fd2 != sys.stderr:
+ self.__fd2.close()
+
+ def write(self, text) :
+ self.__fd1.write(text)
+ self.__fd2.write(text)
+
+ def flush(self) :
+ self.__fd1.flush()
+ self.__fd2.flush()
+
+
+class ClientRestrictingServerMixIn:
+ """Implements verify_request to limit connections to our configured IP
+ address."""
+
+ def verify_request(self, _request, client_address):
+ return client_address[0] == self.server_address[0]
+
+
+class BrokenPipeHandlerMixIn:
+ """Allows the server to deal with "broken pipe" errors (which happen if the
+ browser quits with outstanding requests, like for the favicon). This mix-in
+ requires the class to derive from SocketServer.BaseServer and not override its
+ handle_error() method. """
+
+ def handle_error(self, request, client_address):
+ value = sys.exc_info()[1]
+ if isinstance(value, socket.error):
+ err = value.args[0]
+ if sys.platform in ('win32', 'cygwin'):
+ # "An established connection was aborted by the software in your host."
+ pipe_err = 10053
+ else:
+ pipe_err = errno.EPIPE
+ if err == pipe_err:
+ print "testserver.py: Broken pipe"
+ return
+ SocketServer.BaseServer.handle_error(self, request, client_address)
+
+
+class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
+ """This is a specialization of BaseHTTPServer to allow it
+ to be exited cleanly (by setting its "stop" member to True)."""
+
+ def serve_forever(self):
+ self.stop = False
+ self.nonce_time = None
+ while not self.stop:
+ self.handle_request()
+ self.socket.close()
+
+
+def MultiplexerHack(std_fd, log_fd):
+ """Creates a FileMultiplexer that will write to both specified files.
+
+ When running on Windows XP bots, stdout and stderr will be invalid file
+ handles, so log_fd will be returned directly. (This does not occur if you
+ run the test suite directly from a console, but only if the output of the
+ test executable is redirected.)
+ """
+ if std_fd.fileno() <= 0:
+ return log_fd
+ return FileMultiplexer(std_fd, log_fd)
+
+
+class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+ def __init__(self, request, client_address, socket_server,
+ connect_handlers, get_handlers, head_handlers, post_handlers,
+ put_handlers):
+ self._connect_handlers = connect_handlers
+ self._get_handlers = get_handlers
+ self._head_handlers = head_handlers
+ self._post_handlers = post_handlers
+ self._put_handlers = put_handlers
+ BaseHTTPServer.BaseHTTPRequestHandler.__init__(
+ self, request, client_address, socket_server)
+
+ def log_request(self, *args, **kwargs):
+ # Disable request logging to declutter test log output.
+ pass
+
+ def _ShouldHandleRequest(self, handler_name):
+ """Determines if the path can be handled by the handler.
+
+ We consider a handler valid if the path begins with the
+ handler name. It can optionally be followed by "?*", "/*".
+ """
+
+ pattern = re.compile('%s($|\?|/).*' % handler_name)
+ return pattern.match(self.path)
+
+ def do_CONNECT(self):
+ for handler in self._connect_handlers:
+ if handler():
+ return
+
+ def do_GET(self):
+ for handler in self._get_handlers:
+ if handler():
+ return
+
+ def do_HEAD(self):
+ for handler in self._head_handlers:
+ if handler():
+ return
+
+ def do_POST(self):
+ for handler in self._post_handlers:
+ if handler():
+ return
+
+ def do_PUT(self):
+ for handler in self._put_handlers:
+ if handler():
+ return
+
+
+class TestServerRunner(object):
+ """Runs a test server and communicates with the controlling C++ test code.
+
+ Subclasses should override the create_server method to create their server
+ object, and the add_options method to add their own options.
+ """
+
+ def __init__(self):
+ self.option_parser = optparse.OptionParser()
+ self.add_options()
+
+ def main(self):
+ self.options, self.args = self.option_parser.parse_args()
+
+ logfile = open(self.options.log_file, 'w')
+ sys.stderr = MultiplexerHack(sys.stderr, logfile)
+ if self.options.log_to_console:
+ sys.stdout = MultiplexerHack(sys.stdout, logfile)
+ else:
+ sys.stdout = logfile
+
+ server_data = {
+ 'host': self.options.host,
+ }
+ self.server = self.create_server(server_data)
+ self._notify_startup_complete(server_data)
+ self.run_server()
+
+ def create_server(self, server_data):
+ """Creates a server object and returns it.
+
+ Must populate server_data['port'], and can set additional server_data
+ elements if desired."""
+ raise NotImplementedError()
+
+ def run_server(self):
+ try:
+ self.server.serve_forever()
+ except KeyboardInterrupt:
+ print 'shutting down server'
+ self.server.stop = True
+
+ def add_options(self):
+ self.option_parser.add_option('--startup-pipe', type='int',
+ dest='startup_pipe',
+ help='File handle of pipe to parent process')
+ self.option_parser.add_option('--log-to-console', action='store_const',
+ const=True, default=False,
+ dest='log_to_console',
+ help='Enables or disables sys.stdout logging '
+ 'to the console.')
+ self.option_parser.add_option('--log-file', default='testserver.log',
+ dest='log_file',
+ help='The name of the server log file.')
+ self.option_parser.add_option('--port', default=0, type='int',
+ help='Port used by the server. If '
+ 'unspecified, the server will listen on an '
+ 'ephemeral port.')
+ self.option_parser.add_option('--host', default='127.0.0.1',
+ dest='host',
+ help='Hostname or IP upon which the server '
+ 'will listen. Client connections will also '
+ 'only be allowed from this address.')
+ self.option_parser.add_option('--data-dir', dest='data_dir',
+ help='Directory from which to read the '
+ 'files.')
+
+ def _notify_startup_complete(self, server_data):
+ # Notify the parent that we've started. (BaseServer subclasses
+ # bind their sockets on construction.)
+ if self.options.startup_pipe is not None:
+ server_data_json = json.dumps(server_data)
+ server_data_len = len(server_data_json)
+ print 'sending server_data: %s (%d bytes)' % (
+ server_data_json, server_data_len)
+ if sys.platform == 'win32':
+ fd = msvcrt.open_osfhandle(self.options.startup_pipe, 0)
+ else:
+ fd = self.options.startup_pipe
+ startup_pipe = os.fdopen(fd, "w")
+ # First write the data length as an unsigned 4-byte value. This
+ # is _not_ using network byte ordering since the other end of the
+ # pipe is on the same machine.
+ startup_pipe.write(struct.pack('=L', server_data_len))
+ startup_pipe.write(server_data_json)
+ startup_pipe.close()
diff --git a/chromium/net/tools/tld_cleanup/OWNERS b/chromium/net/tools/tld_cleanup/OWNERS
new file mode 100644
index 00000000000..f6e734a6894
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/OWNERS
@@ -0,0 +1 @@
+pam@chromium.org
diff --git a/chromium/net/tools/tld_cleanup/README b/chromium/net/tools/tld_cleanup/README
new file mode 100644
index 00000000000..a7f137d7660
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/README
@@ -0,0 +1,31 @@
+When updating src/net/base/registry_controlled_domains/effective_tld_names.dat:
+
+1. Obtain the new effective_tld_names.dat, probably by downloading
+ http://goo.gl/Ji2bB
+
+2. Remove whitespace from the ends of the lines.
+ You could possibly use something like:
+ sed -i -e "s/\s*$//g" \
+ src/net/base/registry_controlled_domains/effective_tld_names.dat
+
+3. Add the Chromium note back in just after the license at the top, and just
+ before '===BEGIN ICANN DOMAINS==='. Ensure there is an empty line above and
+ two empty lines below the note. The note should say:
+// Chromium note: this is based on Mozilla's file:
+// http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1
+
+4. Build tld_cleanup (the "(net)" > "tld_cleanup" project)
+
+5. Run it (no arguments needed), typically from src/build/Release or
+ src/build/Debug. It will re-generate
+ src/net/base/registry_controlled_domains/effective_tld_names.gperf.
+
+6. Run gperf on the new effective_tld_names.gperf:
+ pushd src/net/base/registry_controlled_domains;
+ gperf -a -L "C++" -C -c -o -t -k '*' -NFindDomain -P -K name_offset -D -m 10 \
+ effective_tld_names.gperf > effective_tld_names.cc;
+ popd;
+ It will produce a new effective_tld_names.cc.
+
+7. Check in the updated effective_tld_names.dat, effective_tld_names.gperf,
+ and effective_tld_names.cc together.
diff --git a/chromium/net/tools/tld_cleanup/tld_cleanup.cc b/chromium/net/tools/tld_cleanup/tld_cleanup.cc
new file mode 100644
index 00000000000..9d5337c6d7c
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/tld_cleanup.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 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.
+
+// This command-line program converts an effective-TLD data file in UTF-8 from
+// the format provided by Mozilla to the format expected by Chrome. This
+// program generates an intermediate file which is then used by gperf to
+// generate a perfect hash map. The benefit of this approach is that no time is
+// spent on program initialization to generate the map of this data.
+//
+// Running this program finds "effective_tld_names.dat" in the expected location
+// in the source checkout and generates "effective_tld_names.gperf" next to it.
+//
+// Any errors or warnings from this program are recorded in tld_cleanup.log.
+//
+// In particular, it
+// * Strips blank lines and comments, as well as notes for individual rules.
+// * Strips a single leading and/or trailing dot from each rule, if present.
+// * Logs a warning if a rule contains '!' or '*.' other than at the beginning
+// of the rule. (This also catches multiple ! or *. at the start of a rule.)
+// * Logs a warning if GURL reports a rule as invalid, but keeps the rule.
+// * Canonicalizes each rule's domain by converting it to a GURL and back.
+// * Adds explicit rules for true TLDs found in any rule.
+// * Marks entries in the file between "// ===BEGIN PRIVATE DOMAINS==="
+// and "// ===END PRIVATE DOMAINS===" as private.
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/i18n/icu_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process/memory.h"
+#include "net/tools/tld_cleanup/tld_cleanup_util.h"
+
+int main(int argc, const char* argv[]) {
+ base::EnableTerminationOnHeapCorruption();
+ if (argc != 1) {
+ fprintf(stderr, "Normalizes and verifies UTF-8 TLD data files\n");
+ fprintf(stderr, "Usage: %s\n", argv[0]);
+ return 1;
+ }
+
+ // Manages the destruction of singletons.
+ base::AtExitManager exit_manager;
+
+ // Only use OutputDebugString in debug mode.
+#ifdef NDEBUG
+ logging::LoggingDestination destination = logging::LOG_TO_FILE;
+#else
+ logging::LoggingDestination destination =
+ logging::LOG_TO_ALL;
+#endif
+
+ CommandLine::Init(argc, argv);
+
+ base::FilePath log_filename;
+ PathService::Get(base::DIR_EXE, &log_filename);
+ log_filename = log_filename.AppendASCII("tld_cleanup.log");
+ logging::LoggingSettings settings;
+ settings.logging_dest = destination;
+ settings.log_file = log_filename.value().c_str();
+ settings.delete_old = logging::DELETE_OLD_LOG_FILE;
+ logging::InitLogging(settings);
+
+ icu_util::Initialize();
+
+ base::FilePath input_file;
+ PathService::Get(base::DIR_SOURCE_ROOT, &input_file);
+ input_file = input_file.Append(FILE_PATH_LITERAL("net"))
+ .Append(FILE_PATH_LITERAL("base"))
+ .Append(FILE_PATH_LITERAL(
+ "registry_controlled_domains"))
+ .Append(FILE_PATH_LITERAL("effective_tld_names.dat"));
+ base::FilePath output_file;
+ PathService::Get(base::DIR_SOURCE_ROOT, &output_file);
+ output_file = output_file.Append(FILE_PATH_LITERAL("net"))
+ .Append(FILE_PATH_LITERAL("base"))
+ .Append(FILE_PATH_LITERAL(
+ "registry_controlled_domains"))
+ .Append(FILE_PATH_LITERAL(
+ "effective_tld_names.gperf"));
+ net::tld_cleanup::NormalizeResult result =
+ net::tld_cleanup::NormalizeFile(input_file, output_file);
+ if (result != net::tld_cleanup::kSuccess) {
+ fprintf(stderr,
+ "Errors or warnings processing file. See log in tld_cleanup.log.");
+ }
+
+ if (result == net::tld_cleanup::kError)
+ return 1;
+ return 0;
+}
diff --git a/chromium/net/tools/tld_cleanup/tld_cleanup.gyp b/chromium/net/tools/tld_cleanup/tld_cleanup.gyp
new file mode 100644
index 00000000000..c6985501ddc
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/tld_cleanup.gyp
@@ -0,0 +1,23 @@
+# Copyright (c) 2009 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'tld_cleanup_util',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../../../url/url.gyp:url_lib',
+ ],
+ 'sources': [
+ 'tld_cleanup_util.h',
+ 'tld_cleanup_util.cc',
+ ],
+ },
+ ],
+}
diff --git a/chromium/net/tools/tld_cleanup/tld_cleanup_util.cc b/chromium/net/tools/tld_cleanup/tld_cleanup_util.cc
new file mode 100644
index 00000000000..dfa26206f98
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/tld_cleanup_util.cc
@@ -0,0 +1,254 @@
+// Copyright 2013 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/tools/tld_cleanup/tld_cleanup_util.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "url/gurl.h"
+#include "url/url_parse.h"
+
+namespace {
+
+const char kBeginPrivateDomainsComment[] = "// ===BEGIN PRIVATE DOMAINS===";
+const char kEndPrivateDomainsComment[] = "// ===END PRIVATE DOMAINS===";
+
+const int kExceptionRule = 1;
+const int kWildcardRule = 2;
+const int kPrivateRule = 4;
+}
+
+namespace net {
+namespace tld_cleanup {
+
+// Writes the list of domain rules contained in the 'rules' set to the
+// 'outfile', with each rule terminated by a LF. The file must already have
+// been created with write access.
+bool WriteRules(const RuleMap& rules, const base::FilePath& outfile) {
+ std::string data;
+ data.append("%{\n"
+ "// Copyright 2012 The Chromium Authors. All rights reserved.\n"
+ "// Use of this source code is governed by a BSD-style license "
+ "that can be\n"
+ "// found in the LICENSE file.\n\n"
+ "// This file is generated by net/tools/tld_cleanup/.\n"
+ "// DO NOT MANUALLY EDIT!\n"
+ "%}\n"
+ "struct DomainRule {\n"
+ " int name_offset;\n"
+ " int type; // flags: 1: exception, 2: wildcard, 4: private\n"
+ "};\n"
+ "%%\n");
+
+ for (RuleMap::const_iterator i = rules.begin(); i != rules.end(); ++i) {
+ data.append(i->first);
+ data.append(", ");
+ int type = 0;
+ if (i->second.exception) {
+ type = kExceptionRule;
+ } else if (i->second.wildcard) {
+ type = kWildcardRule;
+ }
+ if (i->second.is_private) {
+ type += kPrivateRule;
+ }
+ data.append(base::IntToString(type));
+ data.append("\n");
+ }
+
+ data.append("%%\n");
+
+ int written = file_util::WriteFile(outfile,
+ data.data(),
+ static_cast<int>(data.size()));
+
+ return written == static_cast<int>(data.size());
+}
+
+// Adjusts the rule to a standard form: removes single extraneous dots and
+// canonicalizes it using GURL. Returns kSuccess if the rule is interpreted as
+// valid; logs a warning and returns kWarning if it is probably invalid; and
+// logs an error and returns kError if the rule is (almost) certainly invalid.
+NormalizeResult NormalizeRule(std::string* domain, Rule* rule) {
+ NormalizeResult result = kSuccess;
+
+ // Strip single leading and trailing dots.
+ if (domain->at(0) == '.')
+ domain->erase(0, 1);
+ if (domain->empty()) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+ if (domain->at(domain->size() - 1) == '.')
+ domain->erase(domain->size() - 1, 1);
+ if (domain->empty()) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+
+ // Allow single leading '*.' or '!', saved here so it's not canonicalized.
+ size_t start_offset = 0;
+ if (domain->at(0) == '!') {
+ domain->erase(0, 1);
+ rule->exception = true;
+ } else if (domain->find("*.") == 0) {
+ domain->erase(0, 2);
+ rule->wildcard = true;
+ }
+ if (domain->empty()) {
+ LOG(WARNING) << "Ignoring empty rule";
+ return kWarning;
+ }
+
+ // Warn about additional '*.' or '!'.
+ if (domain->find("*.", start_offset) != std::string::npos ||
+ domain->find('!', start_offset) != std::string::npos) {
+ LOG(WARNING) << "Keeping probably invalid rule: " << *domain;
+ result = kWarning;
+ }
+
+ // Make a GURL and normalize it, then get the host back out.
+ std::string url = "http://";
+ url.append(*domain);
+ GURL gurl(url);
+ const std::string& spec = gurl.possibly_invalid_spec();
+ url_parse::Component host = gurl.parsed_for_possibly_invalid_spec().host;
+ if (host.len < 0) {
+ LOG(ERROR) << "Ignoring rule that couldn't be normalized: " << *domain;
+ return kError;
+ }
+ if (!gurl.is_valid()) {
+ LOG(WARNING) << "Keeping rule that GURL says is invalid: " << *domain;
+ result = kWarning;
+ }
+ domain->assign(spec.substr(host.begin, host.len));
+
+ return result;
+}
+
+NormalizeResult NormalizeDataToRuleMap(const std::string data,
+ RuleMap* rules) {
+ CHECK(rules);
+ // We do a lot of string assignment during parsing, but simplicity is more
+ // important than performance here.
+ std::string domain;
+ NormalizeResult result = kSuccess;
+ size_t line_start = 0;
+ size_t line_end = 0;
+ bool is_private = false;
+ RuleMap extra_rules;
+ int begin_private_length = arraysize(kBeginPrivateDomainsComment) - 1;
+ int end_private_length = arraysize(kEndPrivateDomainsComment) - 1;
+ while (line_start < data.size()) {
+ if (line_start + begin_private_length < data.size() &&
+ !data.compare(line_start, begin_private_length,
+ kBeginPrivateDomainsComment)) {
+ is_private = true;
+ line_end = line_start + begin_private_length;
+ } else if (line_start + end_private_length < data.size() &&
+ !data.compare(line_start, end_private_length,
+ kEndPrivateDomainsComment)) {
+ is_private = false;
+ line_end = line_start + end_private_length;
+ } else if (line_start + 1 < data.size() &&
+ data[line_start] == '/' &&
+ data[line_start + 1] == '/') {
+ // Skip comments.
+ line_end = data.find_first_of("\r\n", line_start);
+ if (line_end == std::string::npos)
+ line_end = data.size();
+ } else {
+ // Truncate at first whitespace.
+ line_end = data.find_first_of("\r\n \t", line_start);
+ if (line_end == std::string::npos)
+ line_end = data.size();
+ domain.assign(data.data(), line_start, line_end - line_start);
+
+ Rule rule;
+ rule.wildcard = false;
+ rule.exception = false;
+ rule.is_private = is_private;
+ NormalizeResult new_result = NormalizeRule(&domain, &rule);
+ if (new_result != kError) {
+ // Check the existing rules to make sure we don't have an exception and
+ // wildcard for the same rule, or that the same domain is listed as both
+ // private and not private. If we did, we'd have to update our
+ // parsing code to handle this case.
+ CHECK(rules->find(domain) == rules->end());
+
+ (*rules)[domain] = rule;
+ // Add true TLD for multi-level rules. We don't add them right now, in
+ // case there's an exception or wild card that either exists or might be
+ // added in a later iteration. In those cases, there's no need to add
+ // it and it would just slow down parsing the data.
+ size_t tld_start = domain.find_last_of('.');
+ if (tld_start != std::string::npos && tld_start + 1 < domain.size()) {
+ std::string extra_rule_domain = domain.substr(tld_start + 1);
+ RuleMap::const_iterator iter = extra_rules.find(extra_rule_domain);
+ Rule extra_rule;
+ extra_rule.exception = false;
+ extra_rule.wildcard = false;
+ if (iter == extra_rules.end()) {
+ extra_rule.is_private = is_private;
+ } else {
+ // A rule already exists, so we ensure that if any of the entries is
+ // not private the result should be that the entry is not private.
+ // An example is .au which is not listed as a real TLD, but only
+ // lists second-level domains such as com.au. Subdomains of .au
+ // (eg. blogspot.com.au) are also listed in the private section,
+ // which is processed later, so this ensures that the real TLD
+ // (eg. .au) is listed as public.
+ extra_rule.is_private = is_private && iter->second.is_private;
+ }
+ extra_rules[extra_rule_domain] = extra_rule;
+ }
+ }
+ result = std::max(result, new_result);
+ }
+
+ // Find beginning of next non-empty line.
+ line_start = data.find_first_of("\r\n", line_end);
+ if (line_start == std::string::npos)
+ line_start = data.size();
+ line_start = data.find_first_not_of("\r\n", line_start);
+ if (line_start == std::string::npos)
+ line_start = data.size();
+ }
+
+ for (RuleMap::const_iterator iter = extra_rules.begin();
+ iter != extra_rules.end();
+ ++iter) {
+ if (rules->find(iter->first) == rules->end()) {
+ (*rules)[iter->first] = iter->second;
+ }
+ }
+
+ return result;
+}
+
+NormalizeResult NormalizeFile(const base::FilePath& in_filename,
+ const base::FilePath& out_filename) {
+ RuleMap rules;
+ std::string data;
+ if (!file_util::ReadFileToString(in_filename, &data)) {
+ LOG(ERROR) << "Unable to read file";
+ // We return success since we've already reported the error.
+ return kSuccess;
+ }
+
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+
+ if (!WriteRules(rules, out_filename)) {
+ LOG(ERROR) << "Error(s) writing output file";
+ result = kError;
+ }
+
+ return result;
+}
+
+
+} // namespace tld_cleanup
+} // namespace net
diff --git a/chromium/net/tools/tld_cleanup/tld_cleanup_util.h b/chromium/net/tools/tld_cleanup/tld_cleanup_util.h
new file mode 100644
index 00000000000..5900206bac8
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/tld_cleanup_util.h
@@ -0,0 +1,48 @@
+// Copyright 2013 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_TOOLS_TLD_CLEANUP_TLD_CLEANUP_UTIL_H_
+#define NET_TOOLS_TLD_CLEANUP_TLD_CLEANUP_UTIL_H_
+
+#include <map>
+#include <string>
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace net {
+namespace tld_cleanup {
+
+struct Rule {
+ bool exception;
+ bool wildcard;
+ bool is_private;
+};
+
+typedef std::map<std::string, Rule> RuleMap;
+
+// These result codes should be in increasing order of severity.
+typedef enum {
+ kSuccess,
+ kWarning,
+ kError,
+} NormalizeResult;
+
+// Loads the file described by |in_filename|, converts it to the desired format
+// (see the file comments in tld_cleanup.cc), and saves it into |out_filename|.
+// Returns the most severe of the result codes encountered when normalizing the
+// rules.
+NormalizeResult NormalizeFile(const base::FilePath& in_filename,
+ const base::FilePath& out_filename);
+
+// Parses |data|, and converts it to the internal data format RuleMap. Returns
+// the most severe of the result codes encountered when normalizing the rules.
+NormalizeResult NormalizeDataToRuleMap(const std::string data,
+ RuleMap* rules);
+
+} // namespace tld_cleanup
+} // namespace net
+
+#endif // NET_TOOLS_TLD_CLEANUP_TLD_CLEANUP_UTIL_H_
diff --git a/chromium/net/tools/tld_cleanup/tld_cleanup_util_unittest.cc b/chromium/net/tools/tld_cleanup/tld_cleanup_util_unittest.cc
new file mode 100644
index 00000000000..6b1d02a8471
--- /dev/null
+++ b/chromium/net/tools/tld_cleanup/tld_cleanup_util_unittest.cc
@@ -0,0 +1,168 @@
+// Copyright 2013 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/tools/tld_cleanup/tld_cleanup_util.h"
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace tld_cleanup {
+
+std::string SetupData(std::string icann_domains, std::string private_domains) {
+ return "// ===BEGIN ICANN DOMAINS===\n" +
+ icann_domains +
+ "// ===END ICANN DOMAINS===\n" +
+ "// ===BEGIN PRIVATE DOMAINS===\n" +
+ private_domains +
+ "// ===END PRIVATE DOMAINS===\n";
+}
+
+TEST(TldCleanupUtilTest, TwoRealTldsSuccessfullyRead) {
+ std::string icann_domains = "foo\n"
+ "bar\n";
+ std::string private_domains = "";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(2U, rules.size());
+ RuleMap::const_iterator foo_iter = rules.find("foo");
+ ASSERT_FALSE(rules.end() == foo_iter);
+ EXPECT_FALSE(foo_iter->second.wildcard);
+ EXPECT_FALSE(foo_iter->second.exception);
+ EXPECT_FALSE(foo_iter->second.is_private);
+ RuleMap::const_iterator bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_FALSE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+}
+
+TEST(TldCleanupUtilTest, RealTldAutomaticallyAddedForSubdomain) {
+ std::string icann_domains = "foo.bar\n";
+ std::string private_domains = "";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(2U, rules.size());
+ RuleMap::const_iterator foo_bar_iter = rules.find("foo.bar");
+ ASSERT_FALSE(rules.end() == foo_bar_iter);
+ EXPECT_FALSE(foo_bar_iter->second.wildcard);
+ EXPECT_FALSE(foo_bar_iter->second.exception);
+ EXPECT_FALSE(foo_bar_iter->second.is_private);
+ RuleMap::const_iterator bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_FALSE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+}
+
+TEST(TldCleanupUtilTest, PrivateTldMarkedAsPrivate) {
+ std::string icann_domains = "foo\n"
+ "bar\n";
+ std::string private_domains = "baz\n";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(3U, rules.size());
+ RuleMap::const_iterator foo_iter = rules.find("foo");
+ ASSERT_FALSE(rules.end() == foo_iter);
+ EXPECT_FALSE(foo_iter->second.wildcard);
+ EXPECT_FALSE(foo_iter->second.exception);
+ EXPECT_FALSE(foo_iter->second.is_private);
+ RuleMap::const_iterator bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_FALSE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+ RuleMap::const_iterator baz_iter = rules.find("baz");
+ ASSERT_FALSE(rules.end() == baz_iter);
+ EXPECT_FALSE(baz_iter->second.wildcard);
+ EXPECT_FALSE(baz_iter->second.exception);
+ EXPECT_TRUE(baz_iter->second.is_private);
+}
+
+TEST(TldCleanupUtilTest, PrivateDomainMarkedAsPrivate) {
+ std::string icann_domains = "bar\n";
+ std::string private_domains = "foo.bar\n";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(2U, rules.size());
+ RuleMap::const_iterator bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_FALSE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+ RuleMap::const_iterator foo_bar_iter = rules.find("foo.bar");
+ ASSERT_FALSE(rules.end() == foo_bar_iter);
+ EXPECT_FALSE(foo_bar_iter->second.wildcard);
+ EXPECT_FALSE(foo_bar_iter->second.exception);
+ EXPECT_TRUE(foo_bar_iter->second.is_private);
+}
+
+TEST(TldCleanupUtilTest, ExtraTldRuleIsNotMarkedPrivate) {
+ std::string icann_domains = "foo.bar\n"
+ "baz.bar\n";
+ std::string private_domains = "qux.bar\n";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(4U, rules.size());
+ RuleMap::const_iterator foo_bar_iter = rules.find("foo.bar");
+ ASSERT_FALSE(rules.end() == foo_bar_iter);
+ EXPECT_FALSE(foo_bar_iter->second.wildcard);
+ EXPECT_FALSE(foo_bar_iter->second.exception);
+ EXPECT_FALSE(foo_bar_iter->second.is_private);
+ RuleMap::const_iterator baz_bar_iter = rules.find("baz.bar");
+ ASSERT_FALSE(rules.end() == baz_bar_iter);
+ EXPECT_FALSE(baz_bar_iter->second.wildcard);
+ EXPECT_FALSE(baz_bar_iter->second.exception);
+ EXPECT_FALSE(baz_bar_iter->second.is_private);
+ RuleMap::const_iterator bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_FALSE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+ RuleMap::const_iterator qux_bar_iter = rules.find("qux.bar");
+ ASSERT_FALSE(rules.end() == qux_bar_iter);
+ EXPECT_FALSE(qux_bar_iter->second.wildcard);
+ EXPECT_FALSE(qux_bar_iter->second.exception);
+ EXPECT_TRUE(qux_bar_iter->second.is_private);
+}
+
+TEST(TldCleanupUtilTest, WildcardAndExceptionParsedCorrectly) {
+ std::string icann_domains = "*.bar\n"
+ "!foo.bar\n";
+ std::string private_domains = "!baz.bar\n";
+ std::string data = SetupData(icann_domains, private_domains);
+ RuleMap rules;
+ NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
+ ASSERT_EQ(kSuccess, result);
+ ASSERT_EQ(3U, rules.size());
+ RuleMap::const_iterator foo_bar_iter = rules.find("bar");
+ ASSERT_FALSE(rules.end() == foo_bar_iter);
+ EXPECT_TRUE(foo_bar_iter->second.wildcard);
+ EXPECT_FALSE(foo_bar_iter->second.exception);
+ EXPECT_FALSE(foo_bar_iter->second.is_private);
+ RuleMap::const_iterator bar_iter = rules.find("foo.bar");
+ ASSERT_FALSE(rules.end() == bar_iter);
+ EXPECT_FALSE(bar_iter->second.wildcard);
+ EXPECT_TRUE(bar_iter->second.exception);
+ EXPECT_FALSE(bar_iter->second.is_private);
+ RuleMap::const_iterator baz_bar_iter = rules.find("baz.bar");
+ ASSERT_FALSE(rules.end() == baz_bar_iter);
+ EXPECT_FALSE(baz_bar_iter->second.wildcard);
+ EXPECT_TRUE(baz_bar_iter->second.exception);
+ EXPECT_TRUE(baz_bar_iter->second.is_private);
+}
+
+} // namespace tld_cleanup
+} // namespace net
diff --git a/chromium/net/udp/datagram_client_socket.h b/chromium/net/udp/datagram_client_socket.h
new file mode 100644
index 00000000000..31fc43f4954
--- /dev/null
+++ b/chromium/net/udp/datagram_client_socket.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 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_UDP_DATAGRAM_CLIENT_SOCKET_H_
+#define NET_UDP_DATAGRAM_CLIENT_SOCKET_H_
+
+#include "net/socket/socket.h"
+#include "net/udp/datagram_socket.h"
+
+namespace net {
+
+class IPEndPoint;
+
+class NET_EXPORT_PRIVATE DatagramClientSocket : public DatagramSocket,
+ public Socket {
+ public:
+ virtual ~DatagramClientSocket() {}
+
+ // Initialize this socket as a client socket to server at |address|.
+ // Returns a network error code.
+ virtual int Connect(const IPEndPoint& address) = 0;
+};
+
+} // namespace net
+
+#endif // NET_UDP_DATAGRAM_CLIENT_SOCKET_H_
diff --git a/chromium/net/udp/datagram_server_socket.h b/chromium/net/udp/datagram_server_socket.h
new file mode 100644
index 00000000000..f2016c0e0d0
--- /dev/null
+++ b/chromium/net/udp/datagram_server_socket.h
@@ -0,0 +1,100 @@
+// Copyright (c) 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.
+
+#ifndef NET_UDP_DATAGRAM_SERVER_SOCKET_H_
+#define NET_UDP_DATAGRAM_SERVER_SOCKET_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_util.h"
+#include "net/udp/datagram_socket.h"
+
+namespace net {
+
+class IPEndPoint;
+class IOBuffer;
+
+// A UDP Socket.
+class NET_EXPORT DatagramServerSocket : public DatagramSocket {
+ public:
+ virtual ~DatagramServerSocket() {}
+
+ // Initialize this socket as a server socket listening at |address|.
+ // Returns a network error code.
+ virtual int Listen(const IPEndPoint& address) = 0;
+
+ // Read from a socket and receive sender address information.
+ // |buf| is the buffer to read data into.
+ // |buf_len| is the maximum amount of data to read.
+ // |address| is a buffer provided by the caller for receiving the sender
+ // address information about the received data. This buffer must be kept
+ // alive by the caller until the callback is placed.
+ // |address_length| is a ptr to the length of the |address| buffer. This
+ // is an input parameter containing the maximum size |address| can hold
+ // and also an output parameter for the size of |address| upon completion.
+ // |callback| the callback on completion of the Recv.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf|, |address|,
+ // and |address_length| alive until the callback is called.
+ virtual int RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback) = 0;
+
+ // Send to a socket with a particular destination.
+ // |buf| is the buffer to send
+ // |buf_len| is the number of bytes to send
+ // |address| is the recipient address.
+ // |address_length| is the size of the recipient address
+ // |callback| is the user callback function to call on complete.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf| and |address|
+ // alive until the callback is called.
+ virtual int SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) = 0;
+
+ // Set the receive buffer size (in bytes) for the socket.
+ virtual bool SetReceiveBufferSize(int32 size) = 0;
+
+ // Set the send buffer size (in bytes) for the socket.
+ virtual bool SetSendBufferSize(int32 size) = 0;
+
+ // Allow the socket to share the local address to which the socket will
+ // be bound with other processes. Should be called before Listen().
+ virtual void AllowAddressReuse() = 0;
+
+ // Allow sending and receiving packets to and from broadcast addresses.
+ // Should be called before Listen().
+ virtual void AllowBroadcast() = 0;
+
+ // Join the multicast group with address |group_address|.
+ // Returns a network error code.
+ virtual int JoinGroup(const IPAddressNumber& group_address) const = 0;
+
+ // Leave the multicast group with address |group_address|.
+ // If the socket hasn't joined the group, it will be ignored.
+ // It's optional to leave the multicast group before destroying
+ // the socket. It will be done by the OS.
+ // Returns a network error code.
+ virtual int LeaveGroup(const IPAddressNumber& group_address) const = 0;
+
+ // Set the time-to-live option for UDP packets sent to the multicast
+ // group address. The default value of this option is 1.
+ // Cannot be negative or more than 255.
+ // Should be called before Bind().
+ // Returns a network error code.
+ virtual int SetMulticastTimeToLive(int time_to_live) = 0;
+
+ // Set the loopback flag for UDP socket. If this flag is true, the host
+ // will receive packets sent to the joined group from itself.
+ // The default value of this option is true.
+ // Should be called before Bind().
+ // Returns a network error code.
+ virtual int SetMulticastLoopbackMode(bool loopback) = 0;
+};
+
+} // namespace net
+
+#endif // NET_UDP_DATAGRAM_SERVER_SOCKET_H_
diff --git a/chromium/net/udp/datagram_socket.h b/chromium/net/udp/datagram_socket.h
new file mode 100644
index 00000000000..f0e9fa10baa
--- /dev/null
+++ b/chromium/net/udp/datagram_socket.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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_UDP_DATAGRAM_SOCKET_H_
+#define NET_UDP_DATAGRAM_SOCKET_H_
+
+#include "net/base/net_export.h"
+
+namespace net {
+
+class BoundNetLog;
+class IPEndPoint;
+
+// A datagram socket is an interface to a protocol which exchanges
+// datagrams, like UDP.
+class NET_EXPORT_PRIVATE DatagramSocket {
+ public:
+ // Type of source port binding to use.
+ enum BindType {
+ RANDOM_BIND,
+ DEFAULT_BIND,
+ };
+
+ virtual ~DatagramSocket() {}
+
+ // Close the socket.
+ virtual void Close() = 0;
+
+ // Copy the remote udp address into |address| and return a network error code.
+ virtual int GetPeerAddress(IPEndPoint* address) const = 0;
+
+ // Copy the local udp address into |address| and return a network error code.
+ // (similar to getsockname)
+ virtual int GetLocalAddress(IPEndPoint* address) const = 0;
+
+ // Gets the NetLog for this socket.
+ virtual const BoundNetLog& NetLog() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_UDP_DATAGRAM_SOCKET_H_
diff --git a/chromium/net/udp/udp_client_socket.cc b/chromium/net/udp/udp_client_socket.cc
new file mode 100644
index 00000000000..bbc32d4f28e
--- /dev/null
+++ b/chromium/net/udp/udp_client_socket.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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/udp/udp_client_socket.h"
+
+#include "net/base/net_log.h"
+
+namespace net {
+
+UDPClientSocket::UDPClientSocket(DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(bind_type, rand_int_cb, net_log, source) {
+}
+
+UDPClientSocket::~UDPClientSocket() {
+}
+
+int UDPClientSocket::Connect(const IPEndPoint& address) {
+ return socket_.Connect(address);
+}
+
+int UDPClientSocket::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return socket_.Read(buf, buf_len, callback);
+}
+
+int UDPClientSocket::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return socket_.Write(buf, buf_len, callback);
+}
+
+void UDPClientSocket::Close() {
+ socket_.Close();
+}
+
+int UDPClientSocket::GetPeerAddress(IPEndPoint* address) const {
+ return socket_.GetPeerAddress(address);
+}
+
+int UDPClientSocket::GetLocalAddress(IPEndPoint* address) const {
+ return socket_.GetLocalAddress(address);
+}
+
+bool UDPClientSocket::SetReceiveBufferSize(int32 size) {
+ return socket_.SetReceiveBufferSize(size);
+}
+
+bool UDPClientSocket::SetSendBufferSize(int32 size) {
+ return socket_.SetSendBufferSize(size);
+}
+
+const BoundNetLog& UDPClientSocket::NetLog() const {
+ return socket_.NetLog();
+}
+
+} // namespace net
diff --git a/chromium/net/udp/udp_client_socket.h b/chromium/net/udp/udp_client_socket.h
new file mode 100644
index 00000000000..e5cbdab23a5
--- /dev/null
+++ b/chromium/net/udp/udp_client_socket.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2011 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_SOCKET_UDP_CLIENT_SOCKET_H_
+#define NET_SOCKET_UDP_CLIENT_SOCKET_H_
+
+#include "net/base/net_log.h"
+#include "net/base/rand_callback.h"
+#include "net/udp/datagram_client_socket.h"
+#include "net/udp/udp_socket.h"
+
+namespace net {
+
+class BoundNetLog;
+
+// A client socket that uses UDP as the transport layer.
+class NET_EXPORT_PRIVATE UDPClientSocket : public DatagramClientSocket {
+ public:
+ UDPClientSocket(DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ virtual ~UDPClientSocket();
+
+ // DatagramClientSocket implementation.
+ virtual int Connect(const IPEndPoint& address) OVERRIDE;
+ virtual int Read(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buf, int buf_len,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+
+ private:
+ UDPSocket socket_;
+ DISALLOW_COPY_AND_ASSIGN(UDPClientSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_UDP_CLIENT_SOCKET_H_
diff --git a/chromium/net/udp/udp_net_log_parameters.cc b/chromium/net/udp/udp_net_log_parameters.cc
new file mode 100644
index 00000000000..c258823f177
--- /dev/null
+++ b/chromium/net/udp/udp_net_log_parameters.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 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.
+
+#include "net/udp/udp_net_log_parameters.h"
+
+#include "base/bind.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+
+namespace {
+
+base::Value* NetLogUDPDataTranferCallback(int byte_count,
+ const char* bytes,
+ const IPEndPoint* address,
+ NetLog::LogLevel log_level) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetInteger("byte_count", byte_count);
+ if (NetLog::IsLoggingBytes(log_level))
+ dict->SetString("hex_encoded_bytes", base::HexEncode(bytes, byte_count));
+ if (address)
+ dict->SetString("address", address->ToString());
+ return dict;
+}
+
+base::Value* NetLogUDPConnectCallback(const IPEndPoint* address,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("address", address->ToString());
+ return dict;
+}
+
+} // namespace
+
+NetLog::ParametersCallback CreateNetLogUDPDataTranferCallback(
+ int byte_count,
+ const char* bytes,
+ const IPEndPoint* address) {
+ DCHECK(bytes);
+ return base::Bind(&NetLogUDPDataTranferCallback, byte_count, bytes, address);
+}
+
+NetLog::ParametersCallback CreateNetLogUDPConnectCallback(
+ const IPEndPoint* address) {
+ DCHECK(address);
+ return base::Bind(&NetLogUDPConnectCallback, address);
+}
+
+} // namespace net
diff --git a/chromium/net/udp/udp_net_log_parameters.h b/chromium/net/udp/udp_net_log_parameters.h
new file mode 100644
index 00000000000..8eb82a2a437
--- /dev/null
+++ b/chromium/net/udp/udp_net_log_parameters.h
@@ -0,0 +1,31 @@
+// Copyright (c) 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.
+
+#ifndef NET_UDP_UDP_NET_LOG_PARAMETERS_H_
+#define NET_UDP_UDP_NET_LOG_PARAMETERS_H_
+
+#include "net/base/net_log.h"
+
+namespace net {
+
+class IPEndPoint;
+
+// Creates a NetLog callback that returns parameters describing a UDP
+// receive/send event. |bytes| are only logged when byte logging is
+// enabled. |address| may be NULL. |address| (if given) and |bytes|
+// must be valid for the life of the callback.
+NetLog::ParametersCallback CreateNetLogUDPDataTranferCallback(
+ int byte_count,
+ const char* bytes,
+ const IPEndPoint* address);
+
+// Creates a NetLog callback that returns parameters describing a UDP
+// connect event. |address| cannot be NULL, and must remain valid for
+// the lifetime of the callback.
+NetLog::ParametersCallback CreateNetLogUDPConnectCallback(
+ const IPEndPoint* address);
+
+} // namespace net
+
+#endif // NET_UDP_UDP_NET_LOG_PARAMETERS_H_
diff --git a/chromium/net/udp/udp_server_socket.cc b/chromium/net/udp/udp_server_socket.cc
new file mode 100644
index 00000000000..16e4061f09c
--- /dev/null
+++ b/chromium/net/udp/udp_server_socket.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 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.
+
+#include "net/udp/udp_server_socket.h"
+
+#include "net/base/rand_callback.h"
+
+namespace net {
+
+UDPServerSocket::UDPServerSocket(net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ net_log,
+ source) {
+}
+
+UDPServerSocket::~UDPServerSocket() {
+}
+
+int UDPServerSocket::Listen(const IPEndPoint& address) {
+ return socket_.Bind(address);
+}
+
+int UDPServerSocket::RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback) {
+ return socket_.RecvFrom(buf, buf_len, address, callback);
+}
+
+int UDPServerSocket::SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) {
+ return socket_.SendTo(buf, buf_len, address, callback);
+}
+
+bool UDPServerSocket::SetReceiveBufferSize(int32 size) {
+ return socket_.SetReceiveBufferSize(size);
+}
+
+bool UDPServerSocket::SetSendBufferSize(int32 size) {
+ return socket_.SetSendBufferSize(size);
+}
+
+void UDPServerSocket::Close() {
+ socket_.Close();
+}
+
+int UDPServerSocket::GetPeerAddress(IPEndPoint* address) const {
+ return socket_.GetPeerAddress(address);
+}
+
+int UDPServerSocket::GetLocalAddress(IPEndPoint* address) const {
+ return socket_.GetLocalAddress(address);
+}
+
+const BoundNetLog& UDPServerSocket::NetLog() const {
+ return socket_.NetLog();
+}
+
+void UDPServerSocket::AllowAddressReuse() {
+ socket_.AllowAddressReuse();
+}
+
+void UDPServerSocket::AllowBroadcast() {
+ socket_.AllowBroadcast();
+}
+
+int UDPServerSocket::JoinGroup(const IPAddressNumber& group_address) const {
+ return socket_.JoinGroup(group_address);
+}
+
+int UDPServerSocket::LeaveGroup(const IPAddressNumber& group_address) const {
+ return socket_.LeaveGroup(group_address);
+}
+
+int UDPServerSocket::SetMulticastTimeToLive(int time_to_live) {
+ return socket_.SetMulticastTimeToLive(time_to_live);
+}
+
+int UDPServerSocket::SetMulticastLoopbackMode(bool loopback) {
+ return socket_.SetMulticastLoopbackMode(loopback);
+}
+
+} // namespace net
diff --git a/chromium/net/udp/udp_server_socket.h b/chromium/net/udp/udp_server_socket.h
new file mode 100644
index 00000000000..cc475f4f6b1
--- /dev/null
+++ b/chromium/net/udp/udp_server_socket.h
@@ -0,0 +1,55 @@
+// Copyright (c) 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.
+
+#ifndef NET_SOCKET_UDP_SERVER_SOCKET_H_
+#define NET_SOCKET_UDP_SERVER_SOCKET_H_
+
+#include "net/base/completion_callback.h"
+#include "net/base/net_util.h"
+#include "net/udp/datagram_server_socket.h"
+#include "net/udp/udp_socket.h"
+
+namespace net {
+
+class IPEndPoint;
+class BoundNetLog;
+
+// A client socket that uses UDP as the transport layer.
+class NET_EXPORT UDPServerSocket : public DatagramServerSocket {
+ public:
+ UDPServerSocket(net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ virtual ~UDPServerSocket();
+
+ // Implement DatagramServerSocket:
+ virtual int Listen(const IPEndPoint& address) OVERRIDE;
+ virtual int RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+ virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+ virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+ virtual const BoundNetLog& NetLog() const OVERRIDE;
+ virtual void AllowAddressReuse() OVERRIDE;
+ virtual void AllowBroadcast() OVERRIDE;
+ virtual int JoinGroup(const IPAddressNumber& group_address) const OVERRIDE;
+ virtual int LeaveGroup(const IPAddressNumber& group_address) const OVERRIDE;
+ virtual int SetMulticastTimeToLive(int time_to_live) OVERRIDE;
+ virtual int SetMulticastLoopbackMode(bool loopback) OVERRIDE;
+
+ private:
+ UDPSocket socket_;
+ DISALLOW_COPY_AND_ASSIGN(UDPServerSocket);
+};
+
+} // namespace net
+
+#endif // NET_SOCKET_UDP_SERVER_SOCKET_H_
diff --git a/chromium/net/udp/udp_socket.h b/chromium/net/udp/udp_socket.h
new file mode 100644
index 00000000000..97a4bc57bb2
--- /dev/null
+++ b/chromium/net/udp/udp_socket.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 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_UDP_UDP_SOCKET_H_
+#define NET_UDP_UDP_SOCKET_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "net/udp/udp_socket_win.h"
+#elif defined(OS_POSIX)
+#include "net/udp/udp_socket_libevent.h"
+#endif
+
+namespace net {
+
+// UDPSocket
+// Accessor API for a UDP socket in either client or server form.
+//
+// Client form:
+// In this case, we're connecting to a specific server, so the client will
+// usually use:
+// Connect(address) // Connect to a UDP server
+// Read/Write // Reads/Writes all go to a single destination
+//
+// Server form:
+// In this case, we want to read/write to many clients which are connecting
+// to this server. First the server 'binds' to an addres, then we read from
+// clients and write responses to them.
+// Example:
+// Bind(address/port) // Binds to port for reading from clients
+// RecvFrom/SendTo // Each read can come from a different client
+// // Writes need to be directed to a specific
+// // address.
+#if defined(OS_WIN)
+typedef UDPSocketWin UDPSocket;
+#elif defined(OS_POSIX)
+typedef UDPSocketLibevent UDPSocket;
+#endif
+
+} // namespace net
+
+#endif // NET_UDP_UDP_SOCKET_H_
diff --git a/chromium/net/udp/udp_socket_libevent.cc b/chromium/net/udp/udp_socket_libevent.cc
new file mode 100644
index 00000000000..90c7da65041
--- /dev/null
+++ b/chromium/net/udp/udp_socket_libevent.cc
@@ -0,0 +1,654 @@
+// Copyright (c) 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.
+
+#include "net/udp/udp_socket_libevent.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/stats_counters.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/rand_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/udp/udp_net_log_parameters.h"
+
+namespace {
+
+const int kBindRetries = 10;
+const int kPortStart = 1024;
+const int kPortEnd = 65535;
+
+} // namespace
+
+namespace net {
+
+UDPSocketLibevent::UDPSocketLibevent(
+ DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(kInvalidSocket),
+ addr_family_(0),
+ socket_options_(SOCKET_OPTION_MULTICAST_LOOP),
+ multicast_time_to_live_(1),
+ bind_type_(bind_type),
+ rand_int_cb_(rand_int_cb),
+ read_watcher_(this),
+ write_watcher_(this),
+ read_buf_len_(0),
+ recv_from_address_(NULL),
+ write_buf_len_(0),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_UDP_SOCKET)) {
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+ if (bind_type == DatagramSocket::RANDOM_BIND)
+ DCHECK(!rand_int_cb.is_null());
+}
+
+UDPSocketLibevent::~UDPSocketLibevent() {
+ Close();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+void UDPSocketLibevent::Close() {
+ DCHECK(CalledOnValidThread());
+
+ if (!is_connected())
+ return;
+
+ // Zero out any pending read/write callback state.
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ read_callback_.Reset();
+ recv_from_address_ = NULL;
+ write_buf_ = NULL;
+ write_buf_len_ = 0;
+ write_callback_.Reset();
+ send_to_address_.reset();
+
+ bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ ok = write_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+
+ if (HANDLE_EINTR(close(socket_)) < 0)
+ PLOG(ERROR) << "close";
+
+ socket_ = kInvalidSocket;
+ addr_family_ = 0;
+}
+
+int UDPSocketLibevent::GetPeerAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ if (!remote_address_.get()) {
+ SockaddrStorage storage;
+ if (getpeername(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(errno);
+ scoped_ptr<IPEndPoint> address(new IPEndPoint());
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+ remote_address_.reset(address.release());
+ }
+
+ *address = *remote_address_;
+ return OK;
+}
+
+int UDPSocketLibevent::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ if (!local_address_.get()) {
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(errno);
+ scoped_ptr<IPEndPoint> address(new IPEndPoint());
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_FAILED;
+ local_address_.reset(address.release());
+ net_log_.AddEvent(NetLog::TYPE_UDP_LOCAL_ADDRESS,
+ CreateNetLogUDPConnectCallback(local_address_.get()));
+ }
+
+ *address = *local_address_;
+ return OK;
+}
+
+int UDPSocketLibevent::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return RecvFrom(buf, buf_len, NULL, callback);
+}
+
+int UDPSocketLibevent::RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_);
+ DCHECK(read_callback_.is_null());
+ DCHECK(!recv_from_address_);
+ DCHECK(!callback.is_null()); // Synchronous operation not supported
+ DCHECK_GT(buf_len, 0);
+
+ int nread = InternalRecvFrom(buf, buf_len, address);
+ if (nread != ERR_IO_PENDING)
+ return nread;
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_READ,
+ &read_socket_watcher_, &read_watcher_)) {
+ PLOG(ERROR) << "WatchFileDescriptor failed on read";
+ int result = MapSystemError(errno);
+ LogRead(result, NULL, 0, NULL);
+ return result;
+ }
+
+ read_buf_ = buf;
+ read_buf_len_ = buf_len;
+ recv_from_address_ = address;
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketLibevent::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return SendToOrWrite(buf, buf_len, NULL, callback);
+}
+
+int UDPSocketLibevent::SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) {
+ return SendToOrWrite(buf, buf_len, &address, callback);
+}
+
+int UDPSocketLibevent::SendToOrWrite(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint* address,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(kInvalidSocket, socket_);
+ DCHECK(write_callback_.is_null());
+ DCHECK(!callback.is_null()); // Synchronous operation not supported
+ DCHECK_GT(buf_len, 0);
+
+ int result = InternalSendTo(buf, buf_len, address);
+ if (result != ERR_IO_PENDING)
+ return result;
+
+ if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
+ socket_, true, base::MessageLoopForIO::WATCH_WRITE,
+ &write_socket_watcher_, &write_watcher_)) {
+ DVLOG(1) << "WatchFileDescriptor failed on write, errno " << errno;
+ int result = MapSystemError(errno);
+ LogWrite(result, NULL, NULL);
+ return result;
+ }
+
+ write_buf_ = buf;
+ write_buf_len_ = buf_len;
+ DCHECK(!send_to_address_.get());
+ if (address) {
+ send_to_address_.reset(new IPEndPoint(*address));
+ }
+ write_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketLibevent::Connect(const IPEndPoint& address) {
+ net_log_.BeginEvent(NetLog::TYPE_UDP_CONNECT,
+ CreateNetLogUDPConnectCallback(&address));
+ int rv = InternalConnect(address);
+ if (rv != OK)
+ Close();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_UDP_CONNECT, rv);
+ return rv;
+}
+
+int UDPSocketLibevent::InternalConnect(const IPEndPoint& address) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+ DCHECK(!remote_address_.get());
+ int rv = CreateSocket(address);
+ if (rv < 0)
+ return rv;
+
+ if (bind_type_ == DatagramSocket::RANDOM_BIND)
+ rv = RandomBind(address);
+ // else connect() does the DatagramSocket::DEFAULT_BIND
+
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len)) {
+ Close();
+ return ERR_ADDRESS_INVALID;
+ }
+
+ rv = HANDLE_EINTR(connect(socket_, storage.addr, storage.addr_len));
+ if (rv < 0) {
+ // Close() may change the current errno. Map errno beforehand.
+ int result = MapSystemError(errno);
+ Close();
+ return result;
+ }
+
+ remote_address_.reset(new IPEndPoint(address));
+ return rv;
+}
+
+int UDPSocketLibevent::Bind(const IPEndPoint& address) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+ int rv = CreateSocket(address);
+ if (rv < 0)
+ return rv;
+
+ rv = SetSocketOptions();
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+ rv = DoBind(address);
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+ local_address_.reset();
+ return rv;
+}
+
+bool UDPSocketLibevent::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket receive buffer size: " << errno;
+ return rv == 0;
+}
+
+bool UDPSocketLibevent::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket send buffer size: " << errno;
+ return rv == 0;
+}
+
+void UDPSocketLibevent::AllowAddressReuse() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+
+ socket_options_ |= SOCKET_OPTION_REUSE_ADDRESS;
+}
+
+void UDPSocketLibevent::AllowBroadcast() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+
+ socket_options_ |= SOCKET_OPTION_BROADCAST;
+}
+
+void UDPSocketLibevent::ReadWatcher::OnFileCanReadWithoutBlocking(int) {
+ if (!socket_->read_callback_.is_null())
+ socket_->DidCompleteRead();
+}
+
+void UDPSocketLibevent::WriteWatcher::OnFileCanWriteWithoutBlocking(int) {
+ if (!socket_->write_callback_.is_null())
+ socket_->DidCompleteWrite();
+}
+
+void UDPSocketLibevent::DoReadCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!read_callback_.is_null());
+
+ // since Run may result in Read being called, clear read_callback_ up front.
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ c.Run(rv);
+}
+
+void UDPSocketLibevent::DoWriteCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!write_callback_.is_null());
+
+ // since Run may result in Write being called, clear write_callback_ up front.
+ CompletionCallback c = write_callback_;
+ write_callback_.Reset();
+ c.Run(rv);
+}
+
+void UDPSocketLibevent::DidCompleteRead() {
+ int result =
+ InternalRecvFrom(read_buf_.get(), read_buf_len_, recv_from_address_);
+ if (result != ERR_IO_PENDING) {
+ read_buf_ = NULL;
+ read_buf_len_ = 0;
+ recv_from_address_ = NULL;
+ bool ok = read_socket_watcher_.StopWatchingFileDescriptor();
+ DCHECK(ok);
+ DoReadCallback(result);
+ }
+}
+
+void UDPSocketLibevent::LogRead(int result,
+ const char* bytes,
+ socklen_t addr_len,
+ const sockaddr* addr) const {
+ if (result < 0) {
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_UDP_RECEIVE_ERROR, result);
+ return;
+ }
+
+ if (net_log_.IsLoggingAllEvents()) {
+ DCHECK(addr_len > 0);
+ DCHECK(addr);
+
+ IPEndPoint address;
+ bool is_address_valid = address.FromSockAddr(addr, addr_len);
+ net_log_.AddEvent(
+ NetLog::TYPE_UDP_BYTES_RECEIVED,
+ CreateNetLogUDPDataTranferCallback(
+ result, bytes,
+ is_address_valid ? &address : NULL));
+ }
+
+ base::StatsCounter read_bytes("udp.read_bytes");
+ read_bytes.Add(result);
+}
+
+int UDPSocketLibevent::CreateSocket(const IPEndPoint& address) {
+ addr_family_ = address.GetSockAddrFamily();
+ socket_ = socket(addr_family_, SOCK_DGRAM, 0);
+ if (socket_ == kInvalidSocket)
+ return MapSystemError(errno);
+ if (SetNonBlocking(socket_)) {
+ const int err = MapSystemError(errno);
+ Close();
+ return err;
+ }
+ return OK;
+}
+
+void UDPSocketLibevent::DidCompleteWrite() {
+ int result =
+ InternalSendTo(write_buf_.get(), write_buf_len_, send_to_address_.get());
+
+ if (result != ERR_IO_PENDING) {
+ write_buf_ = NULL;
+ write_buf_len_ = 0;
+ send_to_address_.reset();
+ write_socket_watcher_.StopWatchingFileDescriptor();
+ DoWriteCallback(result);
+ }
+}
+
+void UDPSocketLibevent::LogWrite(int result,
+ const char* bytes,
+ const IPEndPoint* address) const {
+ if (result < 0) {
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_UDP_SEND_ERROR, result);
+ return;
+ }
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_UDP_BYTES_SENT,
+ CreateNetLogUDPDataTranferCallback(result, bytes, address));
+ }
+
+ base::StatsCounter write_bytes("udp.write_bytes");
+ write_bytes.Add(result);
+}
+
+int UDPSocketLibevent::InternalRecvFrom(IOBuffer* buf, int buf_len,
+ IPEndPoint* address) {
+ int bytes_transferred;
+ int flags = 0;
+
+ SockaddrStorage storage;
+
+ bytes_transferred =
+ HANDLE_EINTR(recvfrom(socket_,
+ buf->data(),
+ buf_len,
+ flags,
+ storage.addr,
+ &storage.addr_len));
+ int result;
+ if (bytes_transferred >= 0) {
+ result = bytes_transferred;
+ if (address && !address->FromSockAddr(storage.addr, storage.addr_len))
+ result = ERR_FAILED;
+ } else {
+ result = MapSystemError(errno);
+ }
+ if (result != ERR_IO_PENDING)
+ LogRead(result, buf->data(), storage.addr_len, storage.addr);
+ return result;
+}
+
+int UDPSocketLibevent::InternalSendTo(IOBuffer* buf, int buf_len,
+ const IPEndPoint* address) {
+ SockaddrStorage storage;
+ struct sockaddr* addr = storage.addr;
+ if (!address) {
+ addr = NULL;
+ storage.addr_len = 0;
+ } else {
+ if (!address->ToSockAddr(storage.addr, &storage.addr_len)) {
+ int result = ERR_FAILED;
+ LogWrite(result, NULL, NULL);
+ return result;
+ }
+ }
+
+ int result = HANDLE_EINTR(sendto(socket_,
+ buf->data(),
+ buf_len,
+ 0,
+ addr,
+ storage.addr_len));
+ if (result < 0)
+ result = MapSystemError(errno);
+ if (result != ERR_IO_PENDING)
+ LogWrite(result, buf->data(), address);
+ return result;
+}
+
+int UDPSocketLibevent::SetSocketOptions() {
+ int true_value = 1;
+ if (socket_options_ & SOCKET_OPTION_REUSE_ADDRESS) {
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &true_value,
+ sizeof(true_value));
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+ if (socket_options_ & SOCKET_OPTION_BROADCAST) {
+ int rv;
+#if defined(OS_MACOSX)
+ // SO_REUSEPORT on OSX permits multiple processes to each receive
+ // UDP multicast or broadcast datagrams destined for the bound
+ // port.
+ rv = setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &true_value,
+ sizeof(true_value));
+#else
+ rv = setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &true_value,
+ sizeof(true_value));
+#endif // defined(OS_MACOSX)
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+
+ if (!(socket_options_ & SOCKET_OPTION_MULTICAST_LOOP)) {
+ int rv;
+ if (addr_family_ == AF_INET) {
+ u_char loop = 0;
+ rv = setsockopt(socket_, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &loop, sizeof(loop));
+ } else {
+ u_int loop = 0;
+ rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &loop, sizeof(loop));
+ }
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+ if (multicast_time_to_live_ != IP_DEFAULT_MULTICAST_TTL) {
+ int rv;
+ if (addr_family_ == AF_INET) {
+ u_char ttl = multicast_time_to_live_;
+ rv = setsockopt(socket_, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl));
+ } else {
+ // Signed interger. -1 to use route default.
+ int ttl = multicast_time_to_live_;
+ rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &ttl, sizeof(ttl));
+ }
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+ return OK;
+}
+
+int UDPSocketLibevent::DoBind(const IPEndPoint& address) {
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_ADDRESS_INVALID;
+ int rv = bind(socket_, storage.addr, storage.addr_len);
+ return rv < 0 ? MapSystemError(errno) : rv;
+}
+
+int UDPSocketLibevent::RandomBind(const IPEndPoint& address) {
+ DCHECK(bind_type_ == DatagramSocket::RANDOM_BIND && !rand_int_cb_.is_null());
+
+ // Construct IPAddressNumber of appropriate size (IPv4 or IPv6) of 0s.
+ IPAddressNumber ip(address.address().size());
+
+ for (int i = 0; i < kBindRetries; ++i) {
+ int rv = DoBind(IPEndPoint(ip, rand_int_cb_.Run(kPortStart, kPortEnd)));
+ if (rv == OK || rv != ERR_ADDRESS_IN_USE)
+ return rv;
+ }
+ return DoBind(IPEndPoint(ip, 0));
+}
+
+int UDPSocketLibevent::JoinGroup(const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketLibevent::LeaveGroup(const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketLibevent::SetMulticastTimeToLive(int time_to_live) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_SOCKET_IS_CONNECTED;
+
+ if (time_to_live < 0 || time_to_live > 255)
+ return ERR_INVALID_ARGUMENT;
+ multicast_time_to_live_ = time_to_live;
+ return OK;
+}
+
+int UDPSocketLibevent::SetMulticastLoopbackMode(bool loopback) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_SOCKET_IS_CONNECTED;
+
+ if (loopback)
+ socket_options_ |= SOCKET_OPTION_MULTICAST_LOOP;
+ else
+ socket_options_ &= ~SOCKET_OPTION_MULTICAST_LOOP;
+ return OK;
+}
+} // namespace net
diff --git a/chromium/net/udp/udp_socket_libevent.h b/chromium/net/udp/udp_socket_libevent.h
new file mode 100644
index 00000000000..8f68a9b57a9
--- /dev/null
+++ b/chromium/net/udp/udp_socket_libevent.h
@@ -0,0 +1,283 @@
+// Copyright (c) 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.
+
+#ifndef NET_UDP_UDP_SOCKET_LIBEVENT_H_
+#define NET_UDP_UDP_SOCKET_LIBEVENT_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/rand_callback.h"
+#include "net/udp/datagram_socket.h"
+
+namespace net {
+
+class NET_EXPORT UDPSocketLibevent : public base::NonThreadSafe {
+ public:
+ UDPSocketLibevent(DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ virtual ~UDPSocketLibevent();
+
+ // Connect the socket to connect with a certain |address|.
+ // Returns a net error code.
+ int Connect(const IPEndPoint& address);
+
+ // Bind the address/port for this socket to |address|. This is generally
+ // only used on a server.
+ // Returns a net error code.
+ int Bind(const IPEndPoint& address);
+
+ // Close the socket.
+ void Close();
+
+ // Copy the remote udp address into |address| and return a network error code.
+ int GetPeerAddress(IPEndPoint* address) const;
+
+ // Copy the local udp address into |address| and return a network error code.
+ // (similar to getsockname)
+ int GetLocalAddress(IPEndPoint* address) const;
+
+ // IO:
+ // Multiple outstanding read requests are not supported.
+ // Full duplex mode (reading and writing at the same time) is supported
+
+ // Read from the socket.
+ // Only usable from the client-side of a UDP socket, after the socket
+ // has been connected.
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Write to the socket.
+ // Only usable from the client-side of a UDP socket, after the socket
+ // has been connected.
+ int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Read from a socket and receive sender address information.
+ // |buf| is the buffer to read data into.
+ // |buf_len| is the maximum amount of data to read.
+ // |address| is a buffer provided by the caller for receiving the sender
+ // address information about the received data. This buffer must be kept
+ // alive by the caller until the callback is placed.
+ // |address_length| is a ptr to the length of the |address| buffer. This
+ // is an input parameter containing the maximum size |address| can hold
+ // and also an output parameter for the size of |address| upon completion.
+ // |callback| the callback on completion of the Recv.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf|, |address|,
+ // and |address_length| alive until the callback is called.
+ int RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ // Send to a socket with a particular destination.
+ // |buf| is the buffer to send
+ // |buf_len| is the number of bytes to send
+ // |address| is the recipient address.
+ // |address_length| is the size of the recipient address
+ // |callback| is the user callback function to call on complete.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf| and |address|
+ // alive until the callback is called.
+ int SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback);
+
+ // Set the receive buffer size (in bytes) for the socket.
+ bool SetReceiveBufferSize(int32 size);
+
+ // Set the send buffer size (in bytes) for the socket.
+ bool SetSendBufferSize(int32 size);
+
+ // Returns true if the socket is already connected or bound.
+ bool is_connected() const { return socket_ != kInvalidSocket; }
+
+ const BoundNetLog& NetLog() const { return net_log_; }
+
+ // Sets corresponding flags in |socket_options_| to allow the socket
+ // to share the local address to which the socket will be bound with
+ // other processes. Should be called before Bind().
+ void AllowAddressReuse();
+
+ // Sets corresponding flags in |socket_options_| to allow sending
+ // and receiving packets to and from broadcast addresses. Should be
+ // called before Bind().
+ void AllowBroadcast();
+
+ // Join the multicast group.
+ // |group_address| is the group address to join, could be either
+ // an IPv4 or IPv6 address.
+ // Return a network error code.
+ int JoinGroup(const IPAddressNumber& group_address) const;
+
+ // Leave the multicast group.
+ // |group_address| is the group address to leave, could be either
+ // an IPv4 or IPv6 address. If the socket hasn't joined the group,
+ // it will be ignored.
+ // It's optional to leave the multicast group before destroying
+ // the socket. It will be done by the OS.
+ // Return a network error code.
+ int LeaveGroup(const IPAddressNumber& group_address) const;
+
+ // Set the time-to-live option for UDP packets sent to the multicast
+ // group address. The default value of this option is 1.
+ // Cannot be negative or more than 255.
+ // Should be called before Bind().
+ // Return a network error code.
+ int SetMulticastTimeToLive(int time_to_live);
+
+ // Set the loopback flag for UDP socket. If this flag is true, the host
+ // will receive packets sent to the joined group from itself.
+ // The default value of this option is true.
+ // Should be called before Bind().
+ // Return a network error code.
+ //
+ // Note: the behavior of |SetMulticastLoopbackMode| is slightly
+ // different between Windows and Unix-like systems. The inconsistency only
+ // happens when there are more than one applications on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ int SetMulticastLoopbackMode(bool loopback);
+
+ private:
+ static const int kInvalidSocket = -1;
+
+ enum SocketOptions {
+ SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
+ SOCKET_OPTION_BROADCAST = 1 << 1,
+ SOCKET_OPTION_MULTICAST_LOOP = 1 << 2
+ };
+
+ class ReadWatcher : public base::MessageLoopForIO::Watcher {
+ public:
+ explicit ReadWatcher(UDPSocketLibevent* socket) : socket_(socket) {}
+
+ // MessageLoopForIO::Watcher methods
+
+ virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE;
+
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE {}
+
+ private:
+ UDPSocketLibevent* const socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReadWatcher);
+ };
+
+ class WriteWatcher : public base::MessageLoopForIO::Watcher {
+ public:
+ explicit WriteWatcher(UDPSocketLibevent* socket) : socket_(socket) {}
+
+ // MessageLoopForIO::Watcher methods
+
+ virtual void OnFileCanReadWithoutBlocking(int /* fd */) OVERRIDE {}
+
+ virtual void OnFileCanWriteWithoutBlocking(int /* fd */) OVERRIDE;
+
+ private:
+ UDPSocketLibevent* const socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteWatcher);
+ };
+
+ void DoReadCallback(int rv);
+ void DoWriteCallback(int rv);
+ void DidCompleteRead();
+ void DidCompleteWrite();
+
+ // Handles stats and logging. |result| is the number of bytes transferred, on
+ // success, or the net error code on failure. On success, LogRead takes in a
+ // sockaddr and its length, which are mandatory, while LogWrite takes in an
+ // optional IPEndPoint.
+ void LogRead(int result, const char* bytes, socklen_t addr_len,
+ const sockaddr* addr) const;
+ void LogWrite(int result, const char* bytes, const IPEndPoint* address) const;
+
+ // Returns the OS error code (or 0 on success).
+ int CreateSocket(const IPEndPoint& address);
+
+ // Same as SendTo(), except that address is passed by pointer
+ // instead of by reference. It is called from Write() with |address|
+ // set to NULL.
+ int SendToOrWrite(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ int InternalConnect(const IPEndPoint& address);
+ int InternalRecvFrom(IOBuffer* buf, int buf_len, IPEndPoint* address);
+ int InternalSendTo(IOBuffer* buf, int buf_len, const IPEndPoint* address);
+
+ // Applies |socket_options_| to |socket_|. Should be called before
+ // Bind().
+ int SetSocketOptions();
+ int DoBind(const IPEndPoint& address);
+ int RandomBind(const IPEndPoint& address);
+
+ int socket_;
+ int addr_family_;
+
+ // Bitwise-or'd combination of SocketOptions. Specifies the set of
+ // options that should be applied to |socket_| before Bind().
+ int socket_options_;
+
+ // Multicast socket options cached for SetSocketOption.
+ // Cannot be used after Bind().
+ int multicast_time_to_live_;
+
+ // How to do source port binding, used only when UDPSocket is part of
+ // UDPClientSocket, since UDPServerSocket provides Bind.
+ DatagramSocket::BindType bind_type_;
+
+ // PRNG function for generating port numbers.
+ RandIntCallback rand_int_cb_;
+
+ // These are mutable since they're just cached copies to make
+ // GetPeerAddress/GetLocalAddress smarter.
+ mutable scoped_ptr<IPEndPoint> local_address_;
+ mutable scoped_ptr<IPEndPoint> remote_address_;
+
+ // The socket's libevent wrappers
+ base::MessageLoopForIO::FileDescriptorWatcher read_socket_watcher_;
+ base::MessageLoopForIO::FileDescriptorWatcher write_socket_watcher_;
+
+ // The corresponding watchers for reads and writes.
+ ReadWatcher read_watcher_;
+ WriteWatcher write_watcher_;
+
+ // The buffer used by InternalRead() to retry Read requests
+ scoped_refptr<IOBuffer> read_buf_;
+ int read_buf_len_;
+ IPEndPoint* recv_from_address_;
+
+ // The buffer used by InternalWrite() to retry Write requests
+ scoped_refptr<IOBuffer> write_buf_;
+ int write_buf_len_;
+ scoped_ptr<IPEndPoint> send_to_address_;
+
+ // External callback; called when read is complete.
+ CompletionCallback read_callback_;
+
+ // External callback; called when write is complete.
+ CompletionCallback write_callback_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(UDPSocketLibevent);
+};
+
+} // namespace net
+
+#endif // NET_UDP_UDP_SOCKET_LIBEVENT_H_
diff --git a/chromium/net/udp/udp_socket_unittest.cc b/chromium/net/udp/udp_socket_unittest.cc
new file mode 100644
index 00000000000..ffb56d98141
--- /dev/null
+++ b/chromium/net/udp/udp_socket_unittest.cc
@@ -0,0 +1,585 @@
+// Copyright (c) 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.
+
+#include "net/udp/udp_client_socket.h"
+#include "net/udp/udp_server_socket.h"
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/net_util.h"
+#include "net/base/test_completion_callback.h"
+#include "net/test/net_test_suite.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace {
+
+class UDPSocketTest : public PlatformTest {
+ public:
+ UDPSocketTest()
+ : buffer_(new IOBufferWithSize(kMaxRead)) {
+ }
+
+ // Blocks until data is read from the socket.
+ std::string RecvFromSocket(UDPServerSocket* socket) {
+ TestCompletionCallback callback;
+
+ int rv = socket->RecvFrom(
+ buffer_.get(), kMaxRead, &recv_from_address_, callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv < 0)
+ return std::string(); // error!
+ return std::string(buffer_->data(), rv);
+ }
+
+ // Loop until |msg| has been written to the socket or until an
+ // error occurs.
+ // If |address| is specified, then it is used for the destination
+ // to send to. Otherwise, will send to the last socket this server
+ // received from.
+ int SendToSocket(UDPServerSocket* socket, std::string msg) {
+ return SendToSocket(socket, msg, recv_from_address_);
+ }
+
+ int SendToSocket(UDPServerSocket* socket,
+ std::string msg,
+ const IPEndPoint& address) {
+ TestCompletionCallback callback;
+
+ int length = msg.length();
+ scoped_refptr<StringIOBuffer> io_buffer(new StringIOBuffer(msg));
+ scoped_refptr<DrainableIOBuffer> buffer(
+ new DrainableIOBuffer(io_buffer.get(), length));
+
+ int bytes_sent = 0;
+ while (buffer->BytesRemaining()) {
+ int rv = socket->SendTo(
+ buffer.get(), buffer->BytesRemaining(), address, callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv <= 0)
+ return bytes_sent > 0 ? bytes_sent : rv;
+ bytes_sent += rv;
+ buffer->DidConsume(rv);
+ }
+ return bytes_sent;
+ }
+
+ std::string ReadSocket(UDPClientSocket* socket) {
+ TestCompletionCallback callback;
+
+ int rv = socket->Read(buffer_.get(), kMaxRead, callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv < 0)
+ return std::string(); // error!
+ return std::string(buffer_->data(), rv);
+ }
+
+ // Loop until |msg| has been written to the socket or until an
+ // error occurs.
+ int WriteSocket(UDPClientSocket* socket, std::string msg) {
+ TestCompletionCallback callback;
+
+ int length = msg.length();
+ scoped_refptr<StringIOBuffer> io_buffer(new StringIOBuffer(msg));
+ scoped_refptr<DrainableIOBuffer> buffer(
+ new DrainableIOBuffer(io_buffer.get(), length));
+
+ int bytes_sent = 0;
+ while (buffer->BytesRemaining()) {
+ int rv = socket->Write(
+ buffer.get(), buffer->BytesRemaining(), callback.callback());
+ if (rv == ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv <= 0)
+ return bytes_sent > 0 ? bytes_sent : rv;
+ bytes_sent += rv;
+ buffer->DidConsume(rv);
+ }
+ return bytes_sent;
+ }
+
+ protected:
+ static const int kMaxRead = 1024;
+ scoped_refptr<IOBufferWithSize> buffer_;
+ IPEndPoint recv_from_address_;
+};
+
+// Creates and address from an ip/port and returns it in |address|.
+void CreateUDPAddress(std::string ip_str, int port, IPEndPoint* address) {
+ IPAddressNumber ip_number;
+ bool rv = ParseIPLiteralToNumber(ip_str, &ip_number);
+ if (!rv)
+ return;
+ *address = IPEndPoint(ip_number, port);
+}
+
+TEST_F(UDPSocketTest, Connect) {
+ const int kPort = 9999;
+ std::string simple_message("hello world!");
+
+ // Setup the server to listen.
+ IPEndPoint bind_address;
+ CreateUDPAddress("127.0.0.1", kPort, &bind_address);
+ CapturingNetLog server_log;
+ scoped_ptr<UDPServerSocket> server(
+ new UDPServerSocket(&server_log, NetLog::Source()));
+ server->AllowAddressReuse();
+ int rv = server->Listen(bind_address);
+ ASSERT_EQ(OK, rv);
+
+ // Setup the client.
+ IPEndPoint server_address;
+ CreateUDPAddress("127.0.0.1", kPort, &server_address);
+ CapturingNetLog client_log;
+ scoped_ptr<UDPClientSocket> client(
+ new UDPClientSocket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ &client_log,
+ NetLog::Source()));
+ rv = client->Connect(server_address);
+ EXPECT_EQ(OK, rv);
+
+ // Client sends to the server.
+ rv = WriteSocket(client.get(), simple_message);
+ EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+ // Server waits for message.
+ std::string str = RecvFromSocket(server.get());
+ DCHECK(simple_message == str);
+
+ // Server echoes reply.
+ rv = SendToSocket(server.get(), simple_message);
+ EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+ // Client waits for response.
+ str = ReadSocket(client.get());
+ DCHECK(simple_message == str);
+
+ // Delete sockets so they log their final events.
+ server.reset();
+ client.reset();
+
+ // Check the server's log.
+ CapturingNetLog::CapturedEntryList server_entries;
+ server_log.GetEntries(&server_entries);
+ EXPECT_EQ(4u, server_entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ server_entries, 0, NetLog::TYPE_SOCKET_ALIVE));
+ EXPECT_TRUE(LogContainsEvent(
+ server_entries, 1, NetLog::TYPE_UDP_BYTES_RECEIVED, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ server_entries, 2, NetLog::TYPE_UDP_BYTES_SENT, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ server_entries, 3, NetLog::TYPE_SOCKET_ALIVE));
+
+ // Check the client's log.
+ CapturingNetLog::CapturedEntryList client_entries;
+ client_log.GetEntries(&client_entries);
+ EXPECT_EQ(6u, client_entries.size());
+ EXPECT_TRUE(LogContainsBeginEvent(
+ client_entries, 0, NetLog::TYPE_SOCKET_ALIVE));
+ EXPECT_TRUE(LogContainsBeginEvent(
+ client_entries, 1, NetLog::TYPE_UDP_CONNECT));
+ EXPECT_TRUE(LogContainsEndEvent(
+ client_entries, 2, NetLog::TYPE_UDP_CONNECT));
+ EXPECT_TRUE(LogContainsEvent(
+ client_entries, 3, NetLog::TYPE_UDP_BYTES_SENT, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEvent(
+ client_entries, 4, NetLog::TYPE_UDP_BYTES_RECEIVED, NetLog::PHASE_NONE));
+ EXPECT_TRUE(LogContainsEndEvent(
+ client_entries, 5, NetLog::TYPE_SOCKET_ALIVE));
+}
+
+#if defined(OS_MACOSX)
+// UDPSocketPrivate_Broadcast is disabled for OSX because it requires
+// root permissions on OSX 10.7+.
+TEST_F(UDPSocketTest, DISABLED_Broadcast) {
+#elif defined(OS_ANDROID)
+// It is also disabled for Android because it is extremely flaky.
+// The first call to SendToSocket returns -109 (Address not reachable)
+// in some unpredictable cases. crbug.com/139144.
+TEST_F(UDPSocketTest, DISABLED_Broadcast) {
+#else
+TEST_F(UDPSocketTest, Broadcast) {
+#endif
+ const int kPort = 9999;
+ std::string first_message("first message"), second_message("second message");
+
+ IPEndPoint broadcast_address;
+ CreateUDPAddress("255.255.255.255", kPort, &broadcast_address);
+ IPEndPoint listen_address;
+ CreateUDPAddress("0.0.0.0", kPort, &listen_address);
+
+ CapturingNetLog server1_log, server2_log;
+ scoped_ptr<UDPServerSocket> server1(
+ new UDPServerSocket(&server1_log, NetLog::Source()));
+ scoped_ptr<UDPServerSocket> server2(
+ new UDPServerSocket(&server2_log, NetLog::Source()));
+ server1->AllowAddressReuse();
+ server1->AllowBroadcast();
+ server2->AllowAddressReuse();
+ server2->AllowBroadcast();
+
+ int rv = server1->Listen(listen_address);
+ EXPECT_EQ(OK, rv);
+ rv = server2->Listen(listen_address);
+ EXPECT_EQ(OK, rv);
+
+ rv = SendToSocket(server1.get(), first_message, broadcast_address);
+ ASSERT_EQ(static_cast<int>(first_message.size()), rv);
+ std::string str = RecvFromSocket(server1.get());
+ ASSERT_EQ(first_message, str);
+ str = RecvFromSocket(server2.get());
+ ASSERT_EQ(first_message, str);
+
+ rv = SendToSocket(server2.get(), second_message, broadcast_address);
+ ASSERT_EQ(static_cast<int>(second_message.size()), rv);
+ str = RecvFromSocket(server1.get());
+ ASSERT_EQ(second_message, str);
+ str = RecvFromSocket(server2.get());
+ ASSERT_EQ(second_message, str);
+}
+
+// In this test, we verify that random binding logic works, which attempts
+// to bind to a random port and returns if succeeds, otherwise retries for
+// |kBindRetries| number of times.
+
+// To generate the scenario, we first create |kBindRetries| number of
+// UDPClientSockets with default binding policy and connect to the same
+// peer and save the used port numbers. Then we get rid of the last
+// socket, making sure that the local port it was bound to is available.
+// Finally, we create a socket with random binding policy, passing it a
+// test PRNG that would serve used port numbers in the array, one after
+// another. At the end, we make sure that the test socket was bound to the
+// port that became available after deleting the last socket with default
+// binding policy.
+
+// We do not test the randomness of bound ports, but that we are using
+// passed in PRNG correctly, thus, it's the duty of PRNG to produce strong
+// random numbers.
+static const int kBindRetries = 10;
+
+class TestPrng {
+ public:
+ explicit TestPrng(const std::deque<int>& numbers) : numbers_(numbers) {}
+ int GetNext(int /* min */, int /* max */) {
+ DCHECK(!numbers_.empty());
+ int rv = numbers_.front();
+ numbers_.pop_front();
+ return rv;
+ }
+ private:
+ std::deque<int> numbers_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestPrng);
+};
+
+#if defined(OS_ANDROID)
+// Disabled on Android for lack of 192.168.1.13. crbug.com/161245
+TEST_F(UDPSocketTest, DISABLED_ConnectRandomBind) {
+#else
+TEST_F(UDPSocketTest, ConnectRandomBind) {
+#endif
+ std::vector<UDPClientSocket*> sockets;
+ IPEndPoint peer_address;
+ CreateUDPAddress("192.168.1.13", 53, &peer_address);
+
+ // Create and connect sockets and save port numbers.
+ std::deque<int> used_ports;
+ for (int i = 0; i < kBindRetries; ++i) {
+ UDPClientSocket* socket =
+ new UDPClientSocket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ sockets.push_back(socket);
+ EXPECT_EQ(OK, socket->Connect(peer_address));
+
+ IPEndPoint client_address;
+ EXPECT_EQ(OK, socket->GetLocalAddress(&client_address));
+ used_ports.push_back(client_address.port());
+ }
+
+ // Free the last socket, its local port is still in |used_ports|.
+ delete sockets.back();
+ sockets.pop_back();
+
+ TestPrng test_prng(used_ports);
+ RandIntCallback rand_int_cb =
+ base::Bind(&TestPrng::GetNext, base::Unretained(&test_prng));
+
+ // Create a socket with random binding policy and connect.
+ scoped_ptr<UDPClientSocket> test_socket(
+ new UDPClientSocket(DatagramSocket::RANDOM_BIND,
+ rand_int_cb,
+ NULL,
+ NetLog::Source()));
+ EXPECT_EQ(OK, test_socket->Connect(peer_address));
+
+ // Make sure that the last port number in the |used_ports| was used.
+ IPEndPoint client_address;
+ EXPECT_EQ(OK, test_socket->GetLocalAddress(&client_address));
+ EXPECT_EQ(used_ports.back(), client_address.port());
+
+ STLDeleteElements(&sockets);
+}
+
+// Return a privileged port (under 1024) so binding will fail.
+int PrivilegedRand(int min, int max) {
+ // Chosen by fair dice roll. Guaranteed to be random.
+ return 4;
+}
+
+TEST_F(UDPSocketTest, ConnectFail) {
+ IPEndPoint peer_address;
+ CreateUDPAddress("0.0.0.0", 53, &peer_address);
+
+ scoped_ptr<UDPSocket> socket(
+ new UDPSocket(DatagramSocket::RANDOM_BIND,
+ base::Bind(&PrivilegedRand),
+ NULL,
+ NetLog::Source()));
+ int rv = socket->Connect(peer_address);
+ // Connect should have failed since we couldn't bind to that port,
+ EXPECT_NE(OK, rv);
+ // Make sure that UDPSocket actually closed the socket.
+ EXPECT_FALSE(socket->is_connected());
+}
+
+// In this test, we verify that connect() on a socket will have the effect
+// of filtering reads on this socket only to data read from the destination
+// we connected to.
+//
+// The purpose of this test is that some documentation indicates that connect
+// binds the client's sends to send to a particular server endpoint, but does
+// not bind the client's reads to only be from that endpoint, and that we need
+// to always use recvfrom() to disambiguate.
+TEST_F(UDPSocketTest, VerifyConnectBindsAddr) {
+ const int kPort1 = 9999;
+ const int kPort2 = 10000;
+ std::string simple_message("hello world!");
+ std::string foreign_message("BAD MESSAGE TO GET!!");
+
+ // Setup the first server to listen.
+ IPEndPoint bind_address;
+ CreateUDPAddress("127.0.0.1", kPort1, &bind_address);
+ UDPServerSocket server1(NULL, NetLog::Source());
+ server1.AllowAddressReuse();
+ int rv = server1.Listen(bind_address);
+ ASSERT_EQ(OK, rv);
+
+ // Setup the second server to listen.
+ CreateUDPAddress("127.0.0.1", kPort2, &bind_address);
+ UDPServerSocket server2(NULL, NetLog::Source());
+ server2.AllowAddressReuse();
+ rv = server2.Listen(bind_address);
+ ASSERT_EQ(OK, rv);
+
+ // Setup the client, connected to server 1.
+ IPEndPoint server_address;
+ CreateUDPAddress("127.0.0.1", kPort1, &server_address);
+ UDPClientSocket client(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ rv = client.Connect(server_address);
+ EXPECT_EQ(OK, rv);
+
+ // Client sends to server1.
+ rv = WriteSocket(&client, simple_message);
+ EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+ // Server1 waits for message.
+ std::string str = RecvFromSocket(&server1);
+ DCHECK(simple_message == str);
+
+ // Get the client's address.
+ IPEndPoint client_address;
+ rv = client.GetLocalAddress(&client_address);
+ EXPECT_EQ(OK, rv);
+
+ // Server2 sends reply.
+ rv = SendToSocket(&server2, foreign_message,
+ client_address);
+ EXPECT_EQ(foreign_message.length(), static_cast<size_t>(rv));
+
+ // Server1 sends reply.
+ rv = SendToSocket(&server1, simple_message,
+ client_address);
+ EXPECT_EQ(simple_message.length(), static_cast<size_t>(rv));
+
+ // Client waits for response.
+ str = ReadSocket(&client);
+ DCHECK(simple_message == str);
+}
+
+TEST_F(UDPSocketTest, ClientGetLocalPeerAddresses) {
+ struct TestData {
+ std::string remote_address;
+ std::string local_address;
+ bool may_fail;
+ } tests[] = {
+ { "127.0.00.1", "127.0.0.1", false },
+ { "::1", "::1", true },
+#if !defined(OS_ANDROID)
+ // Addresses below are disabled on Android. See crbug.com/161248
+ { "192.168.1.1", "127.0.0.1", false },
+ { "2001:db8:0::42", "::1", true },
+#endif
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); i++) {
+ SCOPED_TRACE(std::string("Connecting from ") + tests[i].local_address +
+ std::string(" to ") + tests[i].remote_address);
+
+ IPAddressNumber ip_number;
+ ParseIPLiteralToNumber(tests[i].remote_address, &ip_number);
+ IPEndPoint remote_address(ip_number, 80);
+ ParseIPLiteralToNumber(tests[i].local_address, &ip_number);
+ IPEndPoint local_address(ip_number, 80);
+
+ UDPClientSocket client(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ int rv = client.Connect(remote_address);
+ if (tests[i].may_fail && rv == ERR_ADDRESS_UNREACHABLE) {
+ // Connect() may return ERR_ADDRESS_UNREACHABLE for IPv6
+ // addresses if IPv6 is not configured.
+ continue;
+ }
+
+ EXPECT_LE(ERR_IO_PENDING, rv);
+
+ IPEndPoint fetched_local_address;
+ rv = client.GetLocalAddress(&fetched_local_address);
+ EXPECT_EQ(OK, rv);
+
+ // TODO(mbelshe): figure out how to verify the IP and port.
+ // The port is dynamically generated by the udp stack.
+ // The IP is the real IP of the client, not necessarily
+ // loopback.
+ //EXPECT_EQ(local_address.address(), fetched_local_address.address());
+
+ IPEndPoint fetched_remote_address;
+ rv = client.GetPeerAddress(&fetched_remote_address);
+ EXPECT_EQ(OK, rv);
+
+ EXPECT_EQ(remote_address, fetched_remote_address);
+ }
+}
+
+TEST_F(UDPSocketTest, ServerGetLocalAddress) {
+ IPEndPoint bind_address;
+ CreateUDPAddress("127.0.0.1", 0, &bind_address);
+ UDPServerSocket server(NULL, NetLog::Source());
+ int rv = server.Listen(bind_address);
+ EXPECT_EQ(OK, rv);
+
+ IPEndPoint local_address;
+ rv = server.GetLocalAddress(&local_address);
+ EXPECT_EQ(rv, 0);
+
+ // Verify that port was allocated.
+ EXPECT_GT(local_address.port(), 0);
+ EXPECT_EQ(local_address.address(), bind_address.address());
+}
+
+TEST_F(UDPSocketTest, ServerGetPeerAddress) {
+ IPEndPoint bind_address;
+ CreateUDPAddress("127.0.0.1", 0, &bind_address);
+ UDPServerSocket server(NULL, NetLog::Source());
+ int rv = server.Listen(bind_address);
+ EXPECT_EQ(OK, rv);
+
+ IPEndPoint peer_address;
+ rv = server.GetPeerAddress(&peer_address);
+ EXPECT_EQ(rv, ERR_SOCKET_NOT_CONNECTED);
+}
+
+// Close the socket while read is pending.
+TEST_F(UDPSocketTest, CloseWithPendingRead) {
+ IPEndPoint bind_address;
+ CreateUDPAddress("127.0.0.1", 0, &bind_address);
+ UDPServerSocket server(NULL, NetLog::Source());
+ int rv = server.Listen(bind_address);
+ EXPECT_EQ(OK, rv);
+
+ TestCompletionCallback callback;
+ IPEndPoint from;
+ rv = server.RecvFrom(buffer_.get(), kMaxRead, &from, callback.callback());
+ EXPECT_EQ(rv, ERR_IO_PENDING);
+
+ server.Close();
+
+ EXPECT_FALSE(callback.have_result());
+}
+
+#if defined(OS_ANDROID)
+// Some Android devices do not support multicast socket.
+// The ones supporting multicast need WifiManager.MulitcastLock to enable it.
+// http://goo.gl/jjAk9
+#define MAYBE_JoinMulticastGroup DISABLED_JoinMulticastGroup
+#else
+#define MAYBE_JoinMulticastGroup JoinMulticastGroup
+#endif // defined(OS_ANDROID)
+
+TEST_F(UDPSocketTest, MAYBE_JoinMulticastGroup) {
+ const int kPort = 9999;
+ const char* const kGroup = "237.132.100.17";
+
+ IPEndPoint bind_address;
+ CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+ IPAddressNumber group_ip;
+ EXPECT_TRUE(ParseIPLiteralToNumber(kGroup, &group_ip));
+
+ UDPSocket socket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ EXPECT_EQ(OK, socket.Bind(bind_address));
+ EXPECT_EQ(OK, socket.JoinGroup(group_ip));
+ // Joining group multiple times.
+ EXPECT_NE(OK, socket.JoinGroup(group_ip));
+ EXPECT_EQ(OK, socket.LeaveGroup(group_ip));
+ // Leaving group multiple times.
+ EXPECT_NE(OK, socket.LeaveGroup(group_ip));
+
+ socket.Close();
+}
+
+TEST_F(UDPSocketTest, MulticastOptions) {
+ const int kPort = 9999;
+ IPEndPoint bind_address;
+ CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+
+ UDPSocket socket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ // Before binding.
+ EXPECT_EQ(OK, socket.SetMulticastLoopbackMode(false));
+ EXPECT_EQ(OK, socket.SetMulticastLoopbackMode(true));
+ EXPECT_EQ(OK, socket.SetMulticastTimeToLive(0));
+ EXPECT_EQ(OK, socket.SetMulticastTimeToLive(3));
+ EXPECT_NE(OK, socket.SetMulticastTimeToLive(-1));
+
+ EXPECT_EQ(OK, socket.Bind(bind_address));
+
+ socket.Close();
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/udp/udp_socket_win.cc b/chromium/net/udp/udp_socket_win.cc
new file mode 100644
index 00000000000..1f0c337fd4a
--- /dev/null
+++ b/chromium/net/udp/udp_socket_win.cc
@@ -0,0 +1,755 @@
+// Copyright (c) 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.
+
+#include "net/udp/udp_socket_win.h"
+
+#include <mstcpip.h>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/rand_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/winsock_init.h"
+#include "net/base/winsock_util.h"
+#include "net/udp/udp_net_log_parameters.h"
+
+namespace {
+
+const int kBindRetries = 10;
+const int kPortStart = 1024;
+const int kPortEnd = 65535;
+
+} // namespace
+
+namespace net {
+
+// This class encapsulates all the state that has to be preserved as long as
+// there is a network IO operation in progress. If the owner UDPSocketWin
+// is destroyed while an operation is in progress, the Core is detached and it
+// lives until the operation completes and the OS doesn't reference any resource
+// declared on this class anymore.
+class UDPSocketWin::Core : public base::RefCounted<Core> {
+ public:
+ explicit Core(UDPSocketWin* socket);
+
+ // Start watching for the end of a read or write operation.
+ void WatchForRead();
+ void WatchForWrite();
+
+ // The UDPSocketWin is going away.
+ void Detach() { socket_ = NULL; }
+
+ // The separate OVERLAPPED variables for asynchronous operation.
+ OVERLAPPED read_overlapped_;
+ OVERLAPPED write_overlapped_;
+
+ // The buffers used in Read() and Write().
+ scoped_refptr<IOBuffer> read_iobuffer_;
+ scoped_refptr<IOBuffer> write_iobuffer_;
+
+ // The address storage passed to WSARecvFrom().
+ SockaddrStorage recv_addr_storage_;
+
+ private:
+ friend class base::RefCounted<Core>;
+
+ class ReadDelegate : public base::win::ObjectWatcher::Delegate {
+ public:
+ explicit ReadDelegate(Core* core) : core_(core) {}
+ virtual ~ReadDelegate() {}
+
+ // base::ObjectWatcher::Delegate methods:
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ Core* const core_;
+ };
+
+ class WriteDelegate : public base::win::ObjectWatcher::Delegate {
+ public:
+ explicit WriteDelegate(Core* core) : core_(core) {}
+ virtual ~WriteDelegate() {}
+
+ // base::ObjectWatcher::Delegate methods:
+ virtual void OnObjectSignaled(HANDLE object);
+
+ private:
+ Core* const core_;
+ };
+
+ ~Core();
+
+ // The socket that created this object.
+ UDPSocketWin* socket_;
+
+ // |reader_| handles the signals from |read_watcher_|.
+ ReadDelegate reader_;
+ // |writer_| handles the signals from |write_watcher_|.
+ WriteDelegate writer_;
+
+ // |read_watcher_| watches for events from Read().
+ base::win::ObjectWatcher read_watcher_;
+ // |write_watcher_| watches for events from Write();
+ base::win::ObjectWatcher write_watcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+UDPSocketWin::Core::Core(UDPSocketWin* socket)
+ : socket_(socket),
+ reader_(this),
+ writer_(this) {
+ memset(&read_overlapped_, 0, sizeof(read_overlapped_));
+ memset(&write_overlapped_, 0, sizeof(write_overlapped_));
+
+ read_overlapped_.hEvent = WSACreateEvent();
+ write_overlapped_.hEvent = WSACreateEvent();
+}
+
+UDPSocketWin::Core::~Core() {
+ // Make sure the message loop is not watching this object anymore.
+ read_watcher_.StopWatching();
+ write_watcher_.StopWatching();
+
+ WSACloseEvent(read_overlapped_.hEvent);
+ memset(&read_overlapped_, 0xaf, sizeof(read_overlapped_));
+ WSACloseEvent(write_overlapped_.hEvent);
+ memset(&write_overlapped_, 0xaf, sizeof(write_overlapped_));
+}
+
+void UDPSocketWin::Core::WatchForRead() {
+ // We grab an extra reference because there is an IO operation in progress.
+ // Balanced in ReadDelegate::OnObjectSignaled().
+ AddRef();
+ read_watcher_.StartWatching(read_overlapped_.hEvent, &reader_);
+}
+
+void UDPSocketWin::Core::WatchForWrite() {
+ // We grab an extra reference because there is an IO operation in progress.
+ // Balanced in WriteDelegate::OnObjectSignaled().
+ AddRef();
+ write_watcher_.StartWatching(write_overlapped_.hEvent, &writer_);
+}
+
+void UDPSocketWin::Core::ReadDelegate::OnObjectSignaled(HANDLE object) {
+ DCHECK_EQ(object, core_->read_overlapped_.hEvent);
+ if (core_->socket_)
+ core_->socket_->DidCompleteRead();
+
+ core_->Release();
+}
+
+void UDPSocketWin::Core::WriteDelegate::OnObjectSignaled(HANDLE object) {
+ DCHECK_EQ(object, core_->write_overlapped_.hEvent);
+ if (core_->socket_)
+ core_->socket_->DidCompleteWrite();
+
+ core_->Release();
+}
+
+//-----------------------------------------------------------------------------
+
+UDPSocketWin::UDPSocketWin(DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source)
+ : socket_(INVALID_SOCKET),
+ addr_family_(0),
+ socket_options_(SOCKET_OPTION_MULTICAST_LOOP),
+ multicast_time_to_live_(1),
+ bind_type_(bind_type),
+ rand_int_cb_(rand_int_cb),
+ recv_from_address_(NULL),
+ net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_UDP_SOCKET)) {
+ EnsureWinsockInit();
+ net_log_.BeginEvent(NetLog::TYPE_SOCKET_ALIVE,
+ source.ToEventParametersCallback());
+ if (bind_type == DatagramSocket::RANDOM_BIND)
+ DCHECK(!rand_int_cb.is_null());
+}
+
+UDPSocketWin::~UDPSocketWin() {
+ Close();
+ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE);
+}
+
+void UDPSocketWin::Close() {
+ DCHECK(CalledOnValidThread());
+
+ if (!is_connected())
+ return;
+
+ // Zero out any pending read/write callback state.
+ read_callback_.Reset();
+ recv_from_address_ = NULL;
+ write_callback_.Reset();
+
+ base::TimeTicks start_time = base::TimeTicks::Now();
+ closesocket(socket_);
+ UMA_HISTOGRAM_TIMES("Net.UDPSocketWinClose",
+ base::TimeTicks::Now() - start_time);
+ socket_ = INVALID_SOCKET;
+ addr_family_ = 0;
+
+ core_->Detach();
+ core_ = NULL;
+}
+
+int UDPSocketWin::GetPeerAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ // TODO(szym): Simplify. http://crbug.com/126152
+ if (!remote_address_.get()) {
+ SockaddrStorage storage;
+ if (getpeername(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(WSAGetLastError());
+ scoped_ptr<IPEndPoint> address(new IPEndPoint());
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_ADDRESS_INVALID;
+ remote_address_.reset(address.release());
+ }
+
+ *address = *remote_address_;
+ return OK;
+}
+
+int UDPSocketWin::GetLocalAddress(IPEndPoint* address) const {
+ DCHECK(CalledOnValidThread());
+ DCHECK(address);
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ // TODO(szym): Simplify. http://crbug.com/126152
+ if (!local_address_.get()) {
+ SockaddrStorage storage;
+ if (getsockname(socket_, storage.addr, &storage.addr_len))
+ return MapSystemError(WSAGetLastError());
+ scoped_ptr<IPEndPoint> address(new IPEndPoint());
+ if (!address->FromSockAddr(storage.addr, storage.addr_len))
+ return ERR_ADDRESS_INVALID;
+ local_address_.reset(address.release());
+ net_log_.AddEvent(NetLog::TYPE_UDP_LOCAL_ADDRESS,
+ CreateNetLogUDPConnectCallback(local_address_.get()));
+ }
+
+ *address = *local_address_;
+ return OK;
+}
+
+int UDPSocketWin::Read(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return RecvFrom(buf, buf_len, NULL, callback);
+}
+
+int UDPSocketWin::RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(INVALID_SOCKET, socket_);
+ DCHECK(read_callback_.is_null());
+ DCHECK(!recv_from_address_);
+ DCHECK(!callback.is_null()); // Synchronous operation not supported.
+ DCHECK_GT(buf_len, 0);
+
+ int nread = InternalRecvFrom(buf, buf_len, address);
+ if (nread != ERR_IO_PENDING)
+ return nread;
+
+ read_callback_ = callback;
+ recv_from_address_ = address;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketWin::Write(IOBuffer* buf,
+ int buf_len,
+ const CompletionCallback& callback) {
+ return SendToOrWrite(buf, buf_len, NULL, callback);
+}
+
+int UDPSocketWin::SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback) {
+ return SendToOrWrite(buf, buf_len, &address, callback);
+}
+
+int UDPSocketWin::SendToOrWrite(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint* address,
+ const CompletionCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_NE(INVALID_SOCKET, socket_);
+ DCHECK(write_callback_.is_null());
+ DCHECK(!callback.is_null()); // Synchronous operation not supported.
+ DCHECK_GT(buf_len, 0);
+ DCHECK(!send_to_address_.get());
+
+ int nwrite = InternalSendTo(buf, buf_len, address);
+ if (nwrite != ERR_IO_PENDING)
+ return nwrite;
+
+ if (address)
+ send_to_address_.reset(new IPEndPoint(*address));
+ write_callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketWin::Connect(const IPEndPoint& address) {
+ net_log_.BeginEvent(NetLog::TYPE_UDP_CONNECT,
+ CreateNetLogUDPConnectCallback(&address));
+ int rv = InternalConnect(address);
+ if (rv != OK)
+ Close();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_UDP_CONNECT, rv);
+ return rv;
+}
+
+int UDPSocketWin::InternalConnect(const IPEndPoint& address) {
+ DCHECK(!is_connected());
+ DCHECK(!remote_address_.get());
+ int rv = CreateSocket(address);
+ if (rv < 0)
+ return rv;
+
+ if (bind_type_ == DatagramSocket::RANDOM_BIND)
+ rv = RandomBind(address);
+ // else connect() does the DatagramSocket::DEFAULT_BIND
+
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_ADDRESS_INVALID;
+
+ rv = connect(socket_, storage.addr, storage.addr_len);
+ if (rv < 0) {
+ // Close() may change the last error. Map it beforehand.
+ int result = MapSystemError(WSAGetLastError());
+ Close();
+ return result;
+ }
+
+ remote_address_.reset(new IPEndPoint(address));
+ return rv;
+}
+
+int UDPSocketWin::Bind(const IPEndPoint& address) {
+ DCHECK(!is_connected());
+ int rv = CreateSocket(address);
+ if (rv < 0)
+ return rv;
+ rv = SetSocketOptions();
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+ rv = DoBind(address);
+ if (rv < 0) {
+ Close();
+ return rv;
+ }
+ local_address_.reset();
+ return rv;
+}
+
+int UDPSocketWin::CreateSocket(const IPEndPoint& address) {
+ addr_family_ = address.GetSockAddrFamily();
+ socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, IPPROTO_UDP);
+ if (socket_ == INVALID_SOCKET)
+ return MapSystemError(WSAGetLastError());
+ core_ = new Core(this);
+ return OK;
+}
+
+bool UDPSocketWin::SetReceiveBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket receive buffer size: " << errno;
+ return rv == 0;
+}
+
+bool UDPSocketWin::SetSendBufferSize(int32 size) {
+ DCHECK(CalledOnValidThread());
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF,
+ reinterpret_cast<const char*>(&size), sizeof(size));
+ DCHECK(!rv) << "Could not set socket send buffer size: " << errno;
+ return rv == 0;
+}
+
+void UDPSocketWin::AllowAddressReuse() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+
+ socket_options_ |= SOCKET_OPTION_REUSE_ADDRESS;
+}
+
+void UDPSocketWin::AllowBroadcast() {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!is_connected());
+
+ socket_options_ |= SOCKET_OPTION_BROADCAST;
+}
+
+void UDPSocketWin::DoReadCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!read_callback_.is_null());
+
+ // since Run may result in Read being called, clear read_callback_ up front.
+ CompletionCallback c = read_callback_;
+ read_callback_.Reset();
+ c.Run(rv);
+}
+
+void UDPSocketWin::DoWriteCallback(int rv) {
+ DCHECK_NE(rv, ERR_IO_PENDING);
+ DCHECK(!write_callback_.is_null());
+
+ // since Run may result in Write being called, clear write_callback_ up front.
+ CompletionCallback c = write_callback_;
+ write_callback_.Reset();
+ c.Run(rv);
+}
+
+void UDPSocketWin::DidCompleteRead() {
+ DWORD num_bytes, flags;
+ BOOL ok = WSAGetOverlappedResult(socket_, &core_->read_overlapped_,
+ &num_bytes, FALSE, &flags);
+ WSAResetEvent(core_->read_overlapped_.hEvent);
+ int result = ok ? num_bytes : MapSystemError(WSAGetLastError());
+ // Convert address.
+ if (recv_from_address_ && result >= 0) {
+ if (!ReceiveAddressToIPEndpoint(recv_from_address_))
+ result = ERR_ADDRESS_INVALID;
+ }
+ LogRead(result, core_->read_iobuffer_->data());
+ core_->read_iobuffer_ = NULL;
+ recv_from_address_ = NULL;
+ DoReadCallback(result);
+}
+
+void UDPSocketWin::LogRead(int result, const char* bytes) const {
+ if (result < 0) {
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_UDP_RECEIVE_ERROR, result);
+ return;
+ }
+
+ if (net_log_.IsLoggingAllEvents()) {
+ // Get address for logging, if |address| is NULL.
+ IPEndPoint address;
+ bool is_address_valid = ReceiveAddressToIPEndpoint(&address);
+ net_log_.AddEvent(
+ NetLog::TYPE_UDP_BYTES_RECEIVED,
+ CreateNetLogUDPDataTranferCallback(
+ result, bytes,
+ is_address_valid ? &address : NULL));
+ }
+
+ base::StatsCounter read_bytes("udp.read_bytes");
+ read_bytes.Add(result);
+}
+
+void UDPSocketWin::DidCompleteWrite() {
+ DWORD num_bytes, flags;
+ BOOL ok = WSAGetOverlappedResult(socket_, &core_->write_overlapped_,
+ &num_bytes, FALSE, &flags);
+ WSAResetEvent(core_->write_overlapped_.hEvent);
+ int result = ok ? num_bytes : MapSystemError(WSAGetLastError());
+ LogWrite(result, core_->write_iobuffer_->data(), send_to_address_.get());
+
+ send_to_address_.reset();
+ core_->write_iobuffer_ = NULL;
+ DoWriteCallback(result);
+}
+
+void UDPSocketWin::LogWrite(int result,
+ const char* bytes,
+ const IPEndPoint* address) const {
+ if (result < 0) {
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_UDP_SEND_ERROR, result);
+ return;
+ }
+
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_UDP_BYTES_SENT,
+ CreateNetLogUDPDataTranferCallback(result, bytes, address));
+ }
+
+ base::StatsCounter write_bytes("udp.write_bytes");
+ write_bytes.Add(result);
+}
+
+int UDPSocketWin::InternalRecvFrom(IOBuffer* buf, int buf_len,
+ IPEndPoint* address) {
+ DCHECK(!core_->read_iobuffer_);
+ SockaddrStorage& storage = core_->recv_addr_storage_;
+ storage.addr_len = sizeof(storage.addr_storage);
+
+ WSABUF read_buffer;
+ read_buffer.buf = buf->data();
+ read_buffer.len = buf_len;
+
+ DWORD flags = 0;
+ DWORD num;
+ CHECK_NE(INVALID_SOCKET, socket_);
+ AssertEventNotSignaled(core_->read_overlapped_.hEvent);
+ int rv = WSARecvFrom(socket_, &read_buffer, 1, &num, &flags, storage.addr,
+ &storage.addr_len, &core_->read_overlapped_, NULL);
+ if (rv == 0) {
+ if (ResetEventIfSignaled(core_->read_overlapped_.hEvent)) {
+ int result = num;
+ // Convert address.
+ if (address && result >= 0) {
+ if (!ReceiveAddressToIPEndpoint(address))
+ result = ERR_FAILED;
+ }
+ LogRead(result, buf->data());
+ return result;
+ }
+ } else {
+ int os_error = WSAGetLastError();
+ if (os_error != WSA_IO_PENDING) {
+ int result = MapSystemError(os_error);
+ LogRead(result, NULL);
+ return result;
+ }
+ }
+ core_->WatchForRead();
+ core_->read_iobuffer_ = buf;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketWin::InternalSendTo(IOBuffer* buf, int buf_len,
+ const IPEndPoint* address) {
+ DCHECK(!core_->write_iobuffer_);
+ SockaddrStorage storage;
+ struct sockaddr* addr = storage.addr;
+ // Convert address.
+ if (!address) {
+ addr = NULL;
+ storage.addr_len = 0;
+ } else {
+ if (!address->ToSockAddr(addr, &storage.addr_len)) {
+ int result = ERR_FAILED;
+ LogWrite(result, NULL, NULL);
+ return result;
+ }
+ }
+
+ WSABUF write_buffer;
+ write_buffer.buf = buf->data();
+ write_buffer.len = buf_len;
+
+ DWORD flags = 0;
+ DWORD num;
+ AssertEventNotSignaled(core_->write_overlapped_.hEvent);
+ int rv = WSASendTo(socket_, &write_buffer, 1, &num, flags,
+ addr, storage.addr_len, &core_->write_overlapped_, NULL);
+ if (rv == 0) {
+ if (ResetEventIfSignaled(core_->write_overlapped_.hEvent)) {
+ int result = num;
+ LogWrite(result, buf->data(), address);
+ return result;
+ }
+ } else {
+ int os_error = WSAGetLastError();
+ if (os_error != WSA_IO_PENDING) {
+ int result = MapSystemError(os_error);
+ LogWrite(result, NULL, NULL);
+ return result;
+ }
+ }
+
+ core_->WatchForWrite();
+ core_->write_iobuffer_ = buf;
+ return ERR_IO_PENDING;
+}
+
+int UDPSocketWin::SetSocketOptions() {
+ BOOL true_value = 1;
+ if (socket_options_ & SOCKET_OPTION_REUSE_ADDRESS) {
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR,
+ reinterpret_cast<const char*>(&true_value),
+ sizeof(true_value));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
+ }
+ if (socket_options_ & SOCKET_OPTION_BROADCAST) {
+ int rv = setsockopt(socket_, SOL_SOCKET, SO_BROADCAST,
+ reinterpret_cast<const char*>(&true_value),
+ sizeof(true_value));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
+ }
+ if (!(socket_options_ & SOCKET_OPTION_MULTICAST_LOOP)) {
+ DWORD loop = 0;
+ int protocol_level =
+ addr_family_ == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
+ int option =
+ addr_family_ == AF_INET ? IP_MULTICAST_LOOP: IPV6_MULTICAST_LOOP;
+ int rv = setsockopt(socket_, protocol_level, option,
+ reinterpret_cast<const char*>(&loop), sizeof(loop));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
+ }
+ if (multicast_time_to_live_ != 1) {
+ DWORD hops = multicast_time_to_live_;
+ int protocol_level =
+ addr_family_ == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
+ int option =
+ addr_family_ == AF_INET ? IP_MULTICAST_TTL: IPV6_MULTICAST_HOPS;
+ int rv = setsockopt(socket_, protocol_level, option,
+ reinterpret_cast<const char*>(&hops), sizeof(hops));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
+ }
+ return OK;
+}
+
+int UDPSocketWin::DoBind(const IPEndPoint& address) {
+ SockaddrStorage storage;
+ if (!address.ToSockAddr(storage.addr, &storage.addr_len))
+ return ERR_ADDRESS_INVALID;
+ int rv = bind(socket_, storage.addr, storage.addr_len);
+ return rv < 0 ? MapSystemError(WSAGetLastError()) : rv;
+}
+
+int UDPSocketWin::RandomBind(const IPEndPoint& address) {
+ DCHECK(bind_type_ == DatagramSocket::RANDOM_BIND && !rand_int_cb_.is_null());
+
+ // Construct IPAddressNumber of appropriate size (IPv4 or IPv6) of 0s.
+ IPAddressNumber ip(address.address().size());
+
+ for (int i = 0; i < kBindRetries; ++i) {
+ int rv = DoBind(IPEndPoint(ip, rand_int_cb_.Run(kPortStart, kPortEnd)));
+ if (rv == OK || rv != ERR_ADDRESS_IN_USE)
+ return rv;
+ }
+ return DoBind(IPEndPoint(ip, 0));
+}
+
+bool UDPSocketWin::ReceiveAddressToIPEndpoint(IPEndPoint* address) const {
+ SockaddrStorage& storage = core_->recv_addr_storage_;
+ return address->FromSockAddr(storage.addr, storage.addr_len);
+}
+
+int UDPSocketWin::JoinGroup(
+ const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketWin::LeaveGroup(
+ const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IP_DROP_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketWin::SetMulticastTimeToLive(int time_to_live) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_SOCKET_IS_CONNECTED;
+
+ if (time_to_live < 0 || time_to_live > 255)
+ return ERR_INVALID_ARGUMENT;
+ multicast_time_to_live_ = time_to_live;
+ return OK;
+}
+
+int UDPSocketWin::SetMulticastLoopbackMode(bool loopback) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_SOCKET_IS_CONNECTED;
+
+ if (loopback)
+ socket_options_ |= SOCKET_OPTION_MULTICAST_LOOP;
+ else
+ socket_options_ &= ~SOCKET_OPTION_MULTICAST_LOOP;
+ return OK;
+}
+
+} // namespace net
diff --git a/chromium/net/udp/udp_socket_win.h b/chromium/net/udp/udp_socket_win.h
new file mode 100644
index 00000000000..807b403034e
--- /dev/null
+++ b/chromium/net/udp/udp_socket_win.h
@@ -0,0 +1,246 @@
+// Copyright (c) 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.
+
+#ifndef NET_UDP_UDP_SOCKET_WIN_H_
+#define NET_UDP_UDP_SOCKET_WIN_H_
+
+#include <winsock2.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/win/object_watcher.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/base/rand_callback.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/udp/datagram_socket.h"
+
+namespace net {
+
+class NET_EXPORT UDPSocketWin : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ UDPSocketWin(DatagramSocket::BindType bind_type,
+ const RandIntCallback& rand_int_cb,
+ net::NetLog* net_log,
+ const net::NetLog::Source& source);
+ virtual ~UDPSocketWin();
+
+ // Connect the socket to connect with a certain |address|.
+ // Returns a net error code.
+ int Connect(const IPEndPoint& address);
+
+ // Bind the address/port for this socket to |address|. This is generally
+ // only used on a server.
+ // Returns a net error code.
+ int Bind(const IPEndPoint& address);
+
+ // Close the socket.
+ void Close();
+
+ // Copy the remote udp address into |address| and return a network error code.
+ int GetPeerAddress(IPEndPoint* address) const;
+
+ // Copy the local udp address into |address| and return a network error code.
+ // (similar to getsockname)
+ int GetLocalAddress(IPEndPoint* address) const;
+
+ // IO:
+ // Multiple outstanding read requests are not supported.
+ // Full duplex mode (reading and writing at the same time) is supported
+
+ // Read from the socket.
+ // Only usable from the client-side of a UDP socket, after the socket
+ // has been connected.
+ int Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Write to the socket.
+ // Only usable from the client-side of a UDP socket, after the socket
+ // has been connected.
+ int Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback);
+
+ // Read from a socket and receive sender address information.
+ // |buf| is the buffer to read data into.
+ // |buf_len| is the maximum amount of data to read.
+ // |address| is a buffer provided by the caller for receiving the sender
+ // address information about the received data. This buffer must be kept
+ // alive by the caller until the callback is placed.
+ // |address_length| is a ptr to the length of the |address| buffer. This
+ // is an input parameter containing the maximum size |address| can hold
+ // and also an output parameter for the size of |address| upon completion.
+ // |callback| the callback on completion of the Recv.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf|, |address|,
+ // and |address_length| alive until the callback is called.
+ int RecvFrom(IOBuffer* buf,
+ int buf_len,
+ IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ // Send to a socket with a particular destination.
+ // |buf| is the buffer to send
+ // |buf_len| is the number of bytes to send
+ // |address| is the recipient address.
+ // |address_length| is the size of the recipient address
+ // |callback| is the user callback function to call on complete.
+ // Returns a net error code, or ERR_IO_PENDING if the IO is in progress.
+ // If ERR_IO_PENDING is returned, the caller must keep |buf| and |address|
+ // alive until the callback is called.
+ int SendTo(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint& address,
+ const CompletionCallback& callback);
+
+ // Set the receive buffer size (in bytes) for the socket.
+ bool SetReceiveBufferSize(int32 size);
+
+ // Set the send buffer size (in bytes) for the socket.
+ bool SetSendBufferSize(int32 size);
+
+ // Returns true if the socket is already connected or bound.
+ bool is_connected() const { return socket_ != INVALID_SOCKET; }
+
+ const BoundNetLog& NetLog() const { return net_log_; }
+
+ // Sets corresponding flags in |socket_options_| to allow the socket
+ // to share the local address to which the socket will be bound with
+ // other processes. Should be called before Bind().
+ void AllowAddressReuse();
+
+ // Sets corresponding flags in |socket_options_| to allow sending
+ // and receiving packets to and from broadcast addresses. Should be
+ // called before Bind().
+ void AllowBroadcast();
+
+ // Join the multicast group.
+ // |group_address| is the group address to join, could be either
+ // an IPv4 or IPv6 address.
+ // Return a network error code.
+ int JoinGroup(const IPAddressNumber& group_address) const;
+
+ // Leave the multicast group.
+ // |group_address| is the group address to leave, could be either
+ // an IPv4 or IPv6 address. If the socket hasn't joined the group,
+ // it will be ignored.
+ // It's optional to leave the multicast group before destroying
+ // the socket. It will be done by the OS.
+ // Return a network error code.
+ int LeaveGroup(const IPAddressNumber& group_address) const;
+
+ // Set the time-to-live option for UDP packets sent to the multicast
+ // group address. The default value of this option is 1.
+ // Cannot be negative or more than 255.
+ // Should be called before Bind().
+ int SetMulticastTimeToLive(int time_to_live);
+
+ // Set the loopback flag for UDP socket. If this flag is true, the host
+ // will receive packets sent to the joined group from itself.
+ // The default value of this option is true.
+ // Should be called before Bind().
+ //
+ // Note: the behavior of |SetMulticastLoopbackMode| is slightly
+ // different between Windows and Unix-like systems. The inconsistency only
+ // happens when there are more than one applications on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ int SetMulticastLoopbackMode(bool loopback);
+
+ private:
+ enum SocketOptions {
+ SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
+ SOCKET_OPTION_BROADCAST = 1 << 1,
+ SOCKET_OPTION_MULTICAST_LOOP = 1 << 2
+ };
+
+ class Core;
+
+ void DoReadCallback(int rv);
+ void DoWriteCallback(int rv);
+ void DidCompleteRead();
+ void DidCompleteWrite();
+
+ // Handles stats and logging. |result| is the number of bytes transferred, on
+ // success, or the net error code on failure. LogRead retrieves the address
+ // from |recv_addr_storage_|, while LogWrite takes it as an optional argument.
+ void LogRead(int result, const char* bytes) const;
+ void LogWrite(int result, const char* bytes, const IPEndPoint* address) const;
+
+ // Returns the OS error code (or 0 on success).
+ int CreateSocket(const IPEndPoint& address);
+
+ // Same as SendTo(), except that address is passed by pointer
+ // instead of by reference. It is called from Write() with |address|
+ // set to NULL.
+ int SendToOrWrite(IOBuffer* buf,
+ int buf_len,
+ const IPEndPoint* address,
+ const CompletionCallback& callback);
+
+ int InternalConnect(const IPEndPoint& address);
+ int InternalRecvFrom(IOBuffer* buf, int buf_len, IPEndPoint* address);
+ int InternalSendTo(IOBuffer* buf, int buf_len, const IPEndPoint* address);
+
+ // Applies |socket_options_| to |socket_|. Should be called before
+ // Bind().
+ int SetSocketOptions();
+ int DoBind(const IPEndPoint& address);
+ int RandomBind(const IPEndPoint& address);
+
+ // Attempts to convert the data in |recv_addr_storage_| and |recv_addr_len_|
+ // to an IPEndPoint and writes it to |address|. Returns true on success.
+ bool ReceiveAddressToIPEndpoint(IPEndPoint* address) const;
+
+ SOCKET socket_;
+ int addr_family_;
+
+ // Bitwise-or'd combination of SocketOptions. Specifies the set of
+ // options that should be applied to |socket_| before Bind().
+ int socket_options_;
+
+ // Multicast socket options cached for SetSocketOption.
+ // Cannot be used after Bind().
+ int multicast_time_to_live_;
+
+ // How to do source port binding, used only when UDPSocket is part of
+ // UDPClientSocket, since UDPServerSocket provides Bind.
+ DatagramSocket::BindType bind_type_;
+
+ // PRNG function for generating port numbers.
+ RandIntCallback rand_int_cb_;
+
+ // These are mutable since they're just cached copies to make
+ // GetPeerAddress/GetLocalAddress smarter.
+ mutable scoped_ptr<IPEndPoint> local_address_;
+ mutable scoped_ptr<IPEndPoint> remote_address_;
+
+ // The core of the socket that can live longer than the socket itself. We pass
+ // resources to the Windows async IO functions and we have to make sure that
+ // they are not destroyed while the OS still references them.
+ scoped_refptr<Core> core_;
+
+ IPEndPoint* recv_from_address_;
+
+ // Cached copy of the current address we're sending to, if any. Used for
+ // logging.
+ scoped_ptr<IPEndPoint> send_to_address_;
+
+ // External callback; called when read is complete.
+ CompletionCallback read_callback_;
+
+ // External callback; called when write is complete.
+ CompletionCallback write_callback_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(UDPSocketWin);
+};
+
+} // namespace net
+
+#endif // NET_UDP_UDP_SOCKET_WIN_H_
diff --git a/chromium/net/url_request/data_protocol_handler.cc b/chromium/net/url_request/data_protocol_handler.cc
new file mode 100644
index 00000000000..3222f725920
--- /dev/null
+++ b/chromium/net/url_request/data_protocol_handler.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 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.
+
+#include "net/url_request/data_protocol_handler.h"
+
+#include "net/url_request/url_request_data_job.h"
+
+namespace net {
+
+DataProtocolHandler::DataProtocolHandler() {
+}
+
+URLRequestJob* DataProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ return new URLRequestDataJob(request, network_delegate);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/data_protocol_handler.h b/chromium/net/url_request/data_protocol_handler.h
new file mode 100644
index 00000000000..abb5abe2990
--- /dev/null
+++ b/chromium/net/url_request/data_protocol_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class URLRequestJob;
+
+// Implements a ProtocolHandler for Data jobs.
+class NET_EXPORT DataProtocolHandler
+ : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ DataProtocolHandler();
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
diff --git a/chromium/net/url_request/file_protocol_handler.cc b/chromium/net/url_request/file_protocol_handler.cc
new file mode 100644
index 00000000000..dc5b16f1bbe
--- /dev/null
+++ b/chromium/net/url_request/file_protocol_handler.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 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.
+
+#include "net/url_request/file_protocol_handler.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_dir_job.h"
+#include "net/url_request/url_request_file_job.h"
+
+namespace net {
+
+FileProtocolHandler::FileProtocolHandler() { }
+
+URLRequestJob* FileProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ base::FilePath file_path;
+ const bool is_file = FileURLToFilePath(request->url(), &file_path);
+
+ // Check file access permissions.
+ if (!network_delegate ||
+ !network_delegate->CanAccessFile(*request, file_path)) {
+ return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED);
+ }
+
+ // We need to decide whether to create URLRequestFileJob for file access or
+ // URLRequestFileDirJob for directory access. To avoid accessing the
+ // filesystem, we only look at the path string here.
+ // The code in the URLRequestFileJob::Start() method discovers that a path,
+ // which doesn't end with a slash, should really be treated as a directory,
+ // and it then redirects to the URLRequestFileDirJob.
+ if (is_file &&
+ file_path.EndsWithSeparator() &&
+ file_path.IsAbsolute()) {
+ return new URLRequestFileDirJob(request, network_delegate, file_path);
+ }
+
+ // Use a regular file request job for all non-directories (including invalid
+ // file names).
+ return new URLRequestFileJob(request, network_delegate, file_path);
+}
+
+bool FileProtocolHandler::IsSafeRedirectTarget(const GURL& location) const {
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/file_protocol_handler.h b/chromium/net/url_request/file_protocol_handler.h
new file mode 100644
index 00000000000..8087a6ee5a9
--- /dev/null
+++ b/chromium/net/url_request/file_protocol_handler.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request_job_factory.h"
+
+class GURL;
+
+namespace net {
+
+class NetworkDelegate;
+class URLRequestJob;
+
+// Implements a ProtocolHandler for File jobs. If |network_delegate_| is NULL,
+// then all file requests will fail with ERR_ACCESS_DENIED.
+class NET_EXPORT FileProtocolHandler :
+ public URLRequestJobFactory::ProtocolHandler {
+ public:
+ FileProtocolHandler();
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
diff --git a/chromium/net/url_request/fraudulent_certificate_reporter.h b/chromium/net/url_request/fraudulent_certificate_reporter.h
new file mode 100644
index 00000000000..007f660d98d
--- /dev/null
+++ b/chromium/net/url_request/fraudulent_certificate_reporter.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 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_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+#define NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+
+#include <string>
+
+namespace net {
+
+class SSLInfo;
+
+// FraudulentCertificateReporter is an interface for asynchronously
+// reporting certificate chains that fail the certificate pinning
+// check.
+class FraudulentCertificateReporter {
+ public:
+ virtual ~FraudulentCertificateReporter() {}
+
+ // Sends a report to the report collection server containing the |ssl_info|
+ // associated with a connection to |hostname|. If |sni_available| is true,
+ // searches the SNI transport security metadata as well as the usual
+ // transport security metadata when determining policy for sending the report.
+ virtual void SendReport(const std::string& hostname,
+ const SSLInfo& ssl_info,
+ bool sni_available) = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+
diff --git a/chromium/net/url_request/ftp_protocol_handler.cc b/chromium/net/url_request/ftp_protocol_handler.cc
new file mode 100644
index 00000000000..56d15e6d31b
--- /dev/null
+++ b/chromium/net/url_request/ftp_protocol_handler.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 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.
+
+#include "net/url_request/ftp_protocol_handler.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_auth_cache.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "url/gurl.h"
+
+namespace net {
+
+FtpProtocolHandler::FtpProtocolHandler(
+ FtpTransactionFactory* ftp_transaction_factory)
+ : ftp_transaction_factory_(ftp_transaction_factory),
+ ftp_auth_cache_(new FtpAuthCache) {
+ DCHECK(ftp_transaction_factory_);
+}
+
+FtpProtocolHandler::~FtpProtocolHandler() {
+}
+
+URLRequestJob* FtpProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ int port = request->url().IntPort();
+ if (request->url().has_port() &&
+ !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) {
+ return new URLRequestErrorJob(request, network_delegate, ERR_UNSAFE_PORT);
+ }
+
+ return new URLRequestFtpJob(request,
+ network_delegate,
+ ftp_transaction_factory_,
+ ftp_auth_cache_.get());
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/ftp_protocol_handler.h b/chromium/net/url_request/ftp_protocol_handler.h
new file mode 100644
index 00000000000..7c2278ac4cc
--- /dev/null
+++ b/chromium/net/url_request/ftp_protocol_handler.h
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class FtpAuthCache;
+class FtpTransactionFactory;
+class NetworkDelegate;
+class URLRequestJob;
+
+// Implements a ProtocolHandler for FTP.
+class NET_EXPORT FtpProtocolHandler :
+ public URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit FtpProtocolHandler(FtpTransactionFactory* ftp_transaction_factory);
+ virtual ~FtpProtocolHandler();
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ friend class FtpTestURLRequestContext;
+
+ FtpTransactionFactory* ftp_transaction_factory_;
+ scoped_ptr<FtpAuthCache> ftp_auth_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
diff --git a/chromium/net/url_request/http_user_agent_settings.h b/chromium/net/url_request/http_user_agent_settings.h
new file mode 100644
index 00000000000..d475a88ebd8
--- /dev/null
+++ b/chromium/net/url_request/http_user_agent_settings.h
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+#define NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// The interface used by HTTP jobs to retrieve HTTP Accept-Language
+// and User-Agent header values.
+class NET_EXPORT HttpUserAgentSettings {
+ public:
+ HttpUserAgentSettings() {}
+ virtual ~HttpUserAgentSettings() {}
+
+ // Gets the value of 'Accept-Language' header field.
+ virtual std::string GetAcceptLanguage() const = 0;
+
+ // Gets the UA string to use for the given URL. Pass an empty URL to get
+ // the default UA string.
+ virtual std::string GetUserAgent(const GURL& url) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpUserAgentSettings);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+
diff --git a/chromium/net/url_request/protocol_intercept_job_factory.cc b/chromium/net/url_request/protocol_intercept_job_factory.cc
new file mode 100644
index 00000000000..d0f92f4bff3
--- /dev/null
+++ b/chromium/net/url_request/protocol_intercept_job_factory.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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/url_request/protocol_intercept_job_factory.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+ProtocolInterceptJobFactory::ProtocolInterceptJobFactory(
+ scoped_ptr<URLRequestJobFactory> job_factory,
+ scoped_ptr<ProtocolHandler> protocol_handler)
+ : job_factory_(job_factory.Pass()),
+ protocol_handler_(protocol_handler.Pass()) {
+}
+
+ProtocolInterceptJobFactory::~ProtocolInterceptJobFactory() {}
+
+URLRequestJob* ProtocolInterceptJobFactory::MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ URLRequestJob* job = protocol_handler_->MaybeCreateJob(request,
+ network_delegate);
+ if (job)
+ return job;
+ return job_factory_->MaybeCreateJobWithProtocolHandler(
+ scheme, request, network_delegate);
+}
+
+bool ProtocolInterceptJobFactory::IsHandledProtocol(
+ const std::string& scheme) const {
+ return job_factory_->IsHandledProtocol(scheme);
+}
+
+bool ProtocolInterceptJobFactory::IsHandledURL(const GURL& url) const {
+ return job_factory_->IsHandledURL(url);
+}
+
+bool ProtocolInterceptJobFactory::IsSafeRedirectTarget(
+ const GURL& location) const {
+ return job_factory_->IsSafeRedirectTarget(location);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/protocol_intercept_job_factory.h b/chromium/net/url_request/protocol_intercept_job_factory.h
new file mode 100644
index 00000000000..abc2fb88d69
--- /dev/null
+++ b/chromium/net/url_request/protocol_intercept_job_factory.h
@@ -0,0 +1,51 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_PROTOCOL_INTERCEPT_JOB_FACTORY_H_
+#define NET_URL_REQUEST_PROTOCOL_INTERCEPT_JOB_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job_factory.h"
+
+class GURL;
+
+namespace net {
+
+class URLRequest;
+class URLRequestJob;
+
+// This class acts as a wrapper for URLRequestJobFactory. |protocol_handler_| is
+// given the option of creating a URLRequestJob for each potential URLRequest.
+// If |protocol_handler_| does not create a job (i.e. MaybeCreateJob() returns
+// NULL) the URLRequest is forwarded to the |job_factory_| to be handled there.
+// Only the MaybeCreateJob() member of |protocol_handler_| is called; the
+// IsSafeRedirectTarget() member is not used.
+class NET_EXPORT ProtocolInterceptJobFactory : public URLRequestJobFactory {
+ public:
+ ProtocolInterceptJobFactory(scoped_ptr<URLRequestJobFactory> job_factory,
+ scoped_ptr<ProtocolHandler> protocol_handler);
+ virtual ~ProtocolInterceptJobFactory();
+
+ // URLRequestJobFactory implementation
+ virtual URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE;
+ virtual bool IsHandledURL(const GURL& url) const OVERRIDE;
+ virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE;
+
+ private:
+ scoped_ptr<URLRequestJobFactory> job_factory_;
+ scoped_ptr<ProtocolHandler> protocol_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtocolInterceptJobFactory);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_PROTOCOL_INTERCEPT_JOB_FACTORY_H_
diff --git a/chromium/net/url_request/static_http_user_agent_settings.cc b/chromium/net/url_request/static_http_user_agent_settings.cc
new file mode 100644
index 00000000000..1fd199234da
--- /dev/null
+++ b/chromium/net/url_request/static_http_user_agent_settings.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 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.
+
+#include "net/url_request/static_http_user_agent_settings.h"
+
+namespace net {
+
+StaticHttpUserAgentSettings::StaticHttpUserAgentSettings(
+ const std::string& accept_language,
+ const std::string& user_agent)
+ : accept_language_(accept_language),
+ user_agent_(user_agent) {
+}
+
+StaticHttpUserAgentSettings::~StaticHttpUserAgentSettings() {
+}
+
+std::string StaticHttpUserAgentSettings::GetAcceptLanguage() const {
+ return accept_language_;
+}
+
+std::string StaticHttpUserAgentSettings::GetUserAgent(const GURL& url) const {
+ return user_agent_;
+}
+
+} // namespace net
+
diff --git a/chromium/net/url_request/static_http_user_agent_settings.h b/chromium/net/url_request/static_http_user_agent_settings.h
new file mode 100644
index 00000000000..8819daa8942
--- /dev/null
+++ b/chromium/net/url_request/static_http_user_agent_settings.h
@@ -0,0 +1,39 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+#define NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/url_request/http_user_agent_settings.h"
+
+namespace net {
+
+// An implementation of |HttpUserAgentSettings| that always provides the
+// same constant values for the HTTP Accept-Language and User-Agent headers.
+class NET_EXPORT StaticHttpUserAgentSettings : public HttpUserAgentSettings {
+ public:
+ StaticHttpUserAgentSettings(const std::string& accept_language,
+ const std::string& user_agent);
+ virtual ~StaticHttpUserAgentSettings();
+
+ // HttpUserAgentSettings implementation
+ virtual std::string GetAcceptLanguage() const OVERRIDE;
+ virtual std::string GetUserAgent(const GURL& url) const OVERRIDE;
+
+ private:
+ const std::string accept_language_;
+ const std::string user_agent_;
+
+ DISALLOW_COPY_AND_ASSIGN(StaticHttpUserAgentSettings);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+
diff --git a/chromium/net/url_request/test_url_fetcher_factory.cc b/chromium/net/url_request/test_url_fetcher_factory.cc
new file mode 100644
index 00000000000..e0394c44d18
--- /dev/null
+++ b/chromium/net/url_request/test_url_fetcher_factory.cc
@@ -0,0 +1,383 @@
+// Copyright (c) 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.
+
+#include "net/url_request/test_url_fetcher_factory.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/host_port_pair.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_impl.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+ScopedURLFetcherFactory::ScopedURLFetcherFactory(
+ URLFetcherFactory* factory) {
+ DCHECK(!URLFetcherImpl::factory());
+ URLFetcherImpl::set_factory(factory);
+}
+
+ScopedURLFetcherFactory::~ScopedURLFetcherFactory() {
+ DCHECK(URLFetcherImpl::factory());
+ URLFetcherImpl::set_factory(NULL);
+}
+
+TestURLFetcher::TestURLFetcher(int id,
+ const GURL& url,
+ URLFetcherDelegate* d)
+ : owner_(NULL),
+ id_(id),
+ original_url_(url),
+ delegate_(d),
+ delegate_for_tests_(NULL),
+ did_receive_last_chunk_(false),
+ fake_load_flags_(0),
+ fake_response_code_(-1),
+ fake_response_destination_(STRING),
+ fake_was_fetched_via_proxy_(false),
+ fake_max_retries_(0) {
+}
+
+TestURLFetcher::~TestURLFetcher() {
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnRequestEnd(id_);
+ if (owner_)
+ owner_->RemoveFetcherFromMap(id_);
+}
+
+void TestURLFetcher::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ upload_data_ = upload_content;
+}
+
+void TestURLFetcher::SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ upload_file_path_ = file_path;
+}
+
+void TestURLFetcher::SetChunkedUpload(const std::string& upload_content_type) {
+}
+
+void TestURLFetcher::AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) {
+ DCHECK(!did_receive_last_chunk_);
+ did_receive_last_chunk_ = is_last_chunk;
+ chunks_.push_back(data);
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnChunkUpload(id_);
+}
+
+void TestURLFetcher::SetLoadFlags(int load_flags) {
+ fake_load_flags_= load_flags;
+}
+
+int TestURLFetcher::GetLoadFlags() const {
+ return fake_load_flags_;
+}
+
+void TestURLFetcher::SetReferrer(const std::string& referrer) {
+}
+
+void TestURLFetcher::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ fake_extra_request_headers_.Clear();
+ fake_extra_request_headers_.AddHeadersFromString(extra_request_headers);
+}
+
+void TestURLFetcher::AddExtraRequestHeader(const std::string& header_line) {
+ fake_extra_request_headers_.AddHeaderFromString(header_line);
+}
+
+void TestURLFetcher::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ *headers = fake_extra_request_headers_;
+}
+
+void TestURLFetcher::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+}
+
+void TestURLFetcher::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+}
+
+void TestURLFetcher::SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) {
+}
+
+void TestURLFetcher::SetStopOnRedirect(bool stop_on_redirect) {
+}
+
+void TestURLFetcher::SetAutomaticallyRetryOn5xx(bool retry) {
+}
+
+void TestURLFetcher::SetMaxRetriesOn5xx(int max_retries) {
+ fake_max_retries_ = max_retries;
+}
+
+int TestURLFetcher::GetMaxRetriesOn5xx() const {
+ return fake_max_retries_;
+}
+
+base::TimeDelta TestURLFetcher::GetBackoffDelay() const {
+ return fake_backoff_delay_;
+}
+
+void TestURLFetcher::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+}
+
+void TestURLFetcher::SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+}
+
+void TestURLFetcher::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+}
+
+HttpResponseHeaders* TestURLFetcher::GetResponseHeaders() const {
+ return fake_response_headers_.get();
+}
+
+HostPortPair TestURLFetcher::GetSocketAddress() const {
+ NOTIMPLEMENTED();
+ return HostPortPair();
+}
+
+bool TestURLFetcher::WasFetchedViaProxy() const {
+ return fake_was_fetched_via_proxy_;
+}
+
+void TestURLFetcher::Start() {
+ // Overriden to do nothing. It is assumed the caller will notify the delegate.
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnRequestStart(id_);
+}
+
+const GURL& TestURLFetcher::GetOriginalURL() const {
+ return original_url_;
+}
+
+const GURL& TestURLFetcher::GetURL() const {
+ return fake_url_;
+}
+
+const URLRequestStatus& TestURLFetcher::GetStatus() const {
+ return fake_status_;
+}
+
+int TestURLFetcher::GetResponseCode() const {
+ return fake_response_code_;
+}
+
+const ResponseCookies& TestURLFetcher::GetCookies() const {
+ return fake_cookies_;
+}
+
+bool TestURLFetcher::FileErrorOccurred(int* out_error_code) const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void TestURLFetcher::ReceivedContentWasMalformed() {
+}
+
+bool TestURLFetcher::GetResponseAsString(
+ std::string* out_response_string) const {
+ if (fake_response_destination_ != STRING)
+ return false;
+
+ *out_response_string = fake_response_string_;
+ return true;
+}
+
+bool TestURLFetcher::GetResponseAsFilePath(
+ bool take_ownership, base::FilePath* out_response_path) const {
+ if (fake_response_destination_ != TEMP_FILE)
+ return false;
+
+ *out_response_path = fake_response_file_path_;
+ return true;
+}
+
+void TestURLFetcher::set_status(const URLRequestStatus& status) {
+ fake_status_ = status;
+}
+
+void TestURLFetcher::set_was_fetched_via_proxy(bool flag) {
+ fake_was_fetched_via_proxy_ = flag;
+}
+
+void TestURLFetcher::set_response_headers(
+ scoped_refptr<HttpResponseHeaders> headers) {
+ fake_response_headers_ = headers;
+}
+
+void TestURLFetcher::set_backoff_delay(base::TimeDelta backoff_delay) {
+ fake_backoff_delay_ = backoff_delay;
+}
+
+void TestURLFetcher::SetDelegateForTests(DelegateForTests* delegate_for_tests) {
+ delegate_for_tests_ = delegate_for_tests;
+}
+
+void TestURLFetcher::SetResponseString(const std::string& response) {
+ fake_response_destination_ = STRING;
+ fake_response_string_ = response;
+}
+
+void TestURLFetcher::SetResponseFilePath(const base::FilePath& path) {
+ fake_response_destination_ = TEMP_FILE;
+ fake_response_file_path_ = path;
+}
+
+TestURLFetcherFactory::TestURLFetcherFactory()
+ : ScopedURLFetcherFactory(this),
+ delegate_for_tests_(NULL),
+ remove_fetcher_on_delete_(false) {
+}
+
+TestURLFetcherFactory::~TestURLFetcherFactory() {}
+
+URLFetcher* TestURLFetcherFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ TestURLFetcher* fetcher = new TestURLFetcher(id, url, d);
+ if (remove_fetcher_on_delete_)
+ fetcher->set_owner(this);
+ fetcher->SetDelegateForTests(delegate_for_tests_);
+ fetchers_[id] = fetcher;
+ return fetcher;
+}
+
+TestURLFetcher* TestURLFetcherFactory::GetFetcherByID(int id) const {
+ Fetchers::const_iterator i = fetchers_.find(id);
+ return i == fetchers_.end() ? NULL : i->second;
+}
+
+void TestURLFetcherFactory::RemoveFetcherFromMap(int id) {
+ Fetchers::iterator i = fetchers_.find(id);
+ DCHECK(i != fetchers_.end());
+ fetchers_.erase(i);
+}
+
+void TestURLFetcherFactory::SetDelegateForTests(
+ TestURLFetcherDelegateForTests* delegate_for_tests) {
+ delegate_for_tests_ = delegate_for_tests;
+}
+
+FakeURLFetcher::FakeURLFetcher(const GURL& url,
+ URLFetcherDelegate* d,
+ const std::string& response_data,
+ bool success)
+ : TestURLFetcher(0, url, d),
+ weak_factory_(this) {
+ set_status(URLRequestStatus(
+ success ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED,
+ 0));
+ set_response_code(success ? 200 : 500);
+ SetResponseString(response_data);
+}
+
+FakeURLFetcher::~FakeURLFetcher() {}
+
+void FakeURLFetcher::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeURLFetcher::RunDelegate, weak_factory_.GetWeakPtr()));
+}
+
+void FakeURLFetcher::RunDelegate() {
+ delegate()->OnURLFetchComplete(this);
+}
+
+const GURL& FakeURLFetcher::GetURL() const {
+ return TestURLFetcher::GetOriginalURL();
+}
+
+FakeURLFetcherFactory::FakeURLFetcherFactory(
+ URLFetcherFactory* default_factory)
+ : ScopedURLFetcherFactory(this),
+ creator_(base::Bind(&DefaultFakeURLFetcherCreator)),
+ default_factory_(default_factory) {
+}
+
+FakeURLFetcherFactory::FakeURLFetcherFactory(
+ URLFetcherFactory* default_factory,
+ const FakeURLFetcherCreator& creator)
+ : ScopedURLFetcherFactory(this),
+ creator_(creator),
+ default_factory_(default_factory) {
+}
+
+scoped_ptr<FakeURLFetcher> FakeURLFetcherFactory::DefaultFakeURLFetcherCreator(
+ const GURL& url,
+ URLFetcherDelegate* delegate,
+ const std::string& response,
+ bool success) {
+ return scoped_ptr<FakeURLFetcher>(new FakeURLFetcher(url, delegate,
+ response, success));
+}
+
+FakeURLFetcherFactory::~FakeURLFetcherFactory() {}
+
+URLFetcher* FakeURLFetcherFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ FakeResponseMap::const_iterator it = fake_responses_.find(url);
+ if (it == fake_responses_.end()) {
+ if (default_factory_ == NULL) {
+ // If we don't have a baked response for that URL we return NULL.
+ DLOG(ERROR) << "No baked response for URL: " << url.spec();
+ return NULL;
+ } else {
+ return default_factory_->CreateURLFetcher(id, url, request_type, d);
+ }
+ }
+
+ scoped_ptr<FakeURLFetcher> fake_fetcher =
+ creator_.Run(url, d, it->second.first, it->second.second);
+ // TODO: Make URLFetcherFactory::CreateURLFetcher return a scoped_ptr
+ return fake_fetcher.release();
+}
+
+void FakeURLFetcherFactory::SetFakeResponse(const std::string& url,
+ const std::string& response_data,
+ bool success) {
+ // Overwrite existing URL if it already exists.
+ fake_responses_[GURL(url)] = std::make_pair(response_data, success);
+}
+
+void FakeURLFetcherFactory::ClearFakeResponses() {
+ fake_responses_.clear();
+}
+
+URLFetcherImplFactory::URLFetcherImplFactory() {}
+
+URLFetcherImplFactory::~URLFetcherImplFactory() {}
+
+URLFetcher* URLFetcherImplFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ return new URLFetcherImpl(url, request_type, d);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/test_url_fetcher_factory.h b/chromium/net/url_request/test_url_fetcher_factory.h
new file mode 100644
index 00000000000..89f74c2f590
--- /dev/null
+++ b/chromium/net/url_request/test_url_fetcher_factory.h
@@ -0,0 +1,422 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
+#define NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+namespace net {
+
+// Changes URLFetcher's Factory for the lifetime of the object.
+// Note that this scoper cannot be nested (to make it even harder to misuse).
+class ScopedURLFetcherFactory : public base::NonThreadSafe {
+ public:
+ explicit ScopedURLFetcherFactory(URLFetcherFactory* factory);
+ virtual ~ScopedURLFetcherFactory();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedURLFetcherFactory);
+};
+
+// TestURLFetcher and TestURLFetcherFactory are used for testing consumers of
+// URLFetcher. TestURLFetcherFactory is a URLFetcherFactory that creates
+// TestURLFetchers. TestURLFetcher::Start is overriden to do nothing. It is
+// expected that you'll grab the delegate from the TestURLFetcher and invoke
+// the callback method when appropriate. In this way it's easy to mock a
+// URLFetcher.
+// Typical usage:
+// // TestURLFetcher requires a MessageLoop.
+// MessageLoop message_loop;
+// // And an IO thread to release URLRequestContextGetter in URLFetcher::Core.
+// BrowserThreadImpl io_thread(BrowserThread::IO, &message_loop);
+// // Create factory (it automatically sets itself as URLFetcher's factory).
+// TestURLFetcherFactory factory;
+// // Do something that triggers creation of a URLFetcher.
+// ...
+// TestURLFetcher* fetcher = factory.GetFetcherByID(expected_id);
+// DCHECK(fetcher);
+// // Notify delegate with whatever data you want.
+// fetcher->delegate()->OnURLFetchComplete(...);
+// // Make sure consumer of URLFetcher does the right thing.
+// ...
+//
+// Note: if you don't know when your request objects will be created you
+// might want to use the FakeURLFetcher and FakeURLFetcherFactory classes
+// below.
+
+class TestURLFetcherFactory;
+class TestURLFetcher : public URLFetcher {
+ public:
+ // Interface for tests to intercept production code classes using URLFetcher.
+ // Allows even-driven mock server classes to analyze the correctness of
+ // requests / uploads events and forge responses back at the right moment.
+ class DelegateForTests {
+ public:
+ // Callback issued correspondingly to the call to the |Start()| method.
+ virtual void OnRequestStart(int fetcher_id) = 0;
+
+ // Callback issued correspondingly to the call to |AppendChunkToUpload|.
+ // Uploaded chunks can be retrieved with the |upload_chunks()| getter.
+ virtual void OnChunkUpload(int fetcher_id) = 0;
+
+ // Callback issued correspondingly to the destructor.
+ virtual void OnRequestEnd(int fetcher_id) = 0;
+ };
+
+ TestURLFetcher(int id,
+ const GURL& url,
+ URLFetcherDelegate* d);
+ virtual ~TestURLFetcher();
+
+ // URLFetcher implementation
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) OVERRIDE;
+ virtual void SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SetChunkedUpload(
+ const std::string& upload_content_type) OVERRIDE;
+ // Overriden to cache the chunks uploaded. Caller can read back the uploaded
+ // chunks with the upload_chunks() accessor.
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) OVERRIDE;
+ virtual void SetLoadFlags(int load_flags) OVERRIDE;
+ virtual int GetLoadFlags() const OVERRIDE;
+ virtual void SetReferrer(const std::string& referrer) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) OVERRIDE;
+ virtual void AddExtraRequestHeader(const std::string& header_line) OVERRIDE;
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) OVERRIDE;
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) OVERRIDE;
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) OVERRIDE;
+ virtual void SetStopOnRedirect(bool stop_on_redirect) OVERRIDE;
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) OVERRIDE;
+ virtual void SetMaxRetriesOn5xx(int max_retries) OVERRIDE;
+ virtual int GetMaxRetriesOn5xx() const OVERRIDE;
+ virtual base::TimeDelta GetBackoffDelay() const OVERRIDE;
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) OVERRIDE;
+ virtual void SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual HttpResponseHeaders* GetResponseHeaders() const OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual bool WasFetchedViaProxy() const OVERRIDE;
+ virtual void Start() OVERRIDE;
+
+ // URL we were created with. Because of how we're using URLFetcher GetURL()
+ // always returns an empty URL. Chances are you'll want to use
+ // GetOriginalURL() in your tests.
+ virtual const GURL& GetOriginalURL() const OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const URLRequestStatus& GetStatus() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual const ResponseCookies& GetCookies() const OVERRIDE;
+ virtual bool FileErrorOccurred(int* out_error_code) const OVERRIDE;
+ virtual void ReceivedContentWasMalformed() OVERRIDE;
+ // Override response access functions to return fake data.
+ virtual bool GetResponseAsString(
+ std::string* out_response_string) const OVERRIDE;
+ virtual bool GetResponseAsFilePath(
+ bool take_ownership, base::FilePath* out_response_path) const OVERRIDE;
+
+ // Sets owner of this class. Set it to a non-NULL value if you want
+ // to automatically unregister this fetcher from the owning factory
+ // upon destruction.
+ void set_owner(TestURLFetcherFactory* owner) { owner_ = owner; }
+
+ // Unique ID in our factory.
+ int id() const { return id_; }
+
+ // Returns the data uploaded on this URLFetcher.
+ const std::string& upload_data() const { return upload_data_; }
+ const base::FilePath& upload_file_path() const { return upload_file_path_; }
+
+ // Returns the chunks of data uploaded on this URLFetcher.
+ const std::list<std::string>& upload_chunks() const { return chunks_; }
+
+ // Checks whether the last call to |AppendChunkToUpload(...)| was final.
+ bool did_receive_last_chunk() const { return did_receive_last_chunk_; }
+
+ // Returns the delegate installed on the URLFetcher.
+ URLFetcherDelegate* delegate() const { return delegate_; }
+
+ void set_url(const GURL& url) { fake_url_ = url; }
+ void set_status(const URLRequestStatus& status);
+ void set_response_code(int response_code) {
+ fake_response_code_ = response_code;
+ }
+ void set_cookies(const ResponseCookies& c) { fake_cookies_ = c; }
+ void set_was_fetched_via_proxy(bool flag);
+ void set_response_headers(scoped_refptr<HttpResponseHeaders> headers);
+ void set_backoff_delay(base::TimeDelta backoff_delay);
+ void SetDelegateForTests(DelegateForTests* delegate_for_tests);
+
+ // Set string data.
+ void SetResponseString(const std::string& response);
+
+ // Set File data.
+ void SetResponseFilePath(const base::FilePath& path);
+
+ private:
+ enum ResponseDestinationType {
+ STRING, // Default: In a std::string
+ TEMP_FILE // Write to a temp file
+ };
+
+ TestURLFetcherFactory* owner_;
+ const int id_;
+ const GURL original_url_;
+ URLFetcherDelegate* delegate_;
+ DelegateForTests* delegate_for_tests_;
+ std::string upload_data_;
+ base::FilePath upload_file_path_;
+ std::list<std::string> chunks_;
+ bool did_receive_last_chunk_;
+
+ // User can use set_* methods to provide values returned by getters.
+ // Setting the real values is not possible, because the real class
+ // has no setters. The data is a private member of a class defined
+ // in a .cc file, so we can't get at it with friendship.
+ int fake_load_flags_;
+ GURL fake_url_;
+ URLRequestStatus fake_status_;
+ int fake_response_code_;
+ ResponseCookies fake_cookies_;
+ ResponseDestinationType fake_response_destination_;
+ std::string fake_response_string_;
+ base::FilePath fake_response_file_path_;
+ bool fake_was_fetched_via_proxy_;
+ scoped_refptr<HttpResponseHeaders> fake_response_headers_;
+ HttpRequestHeaders fake_extra_request_headers_;
+ int fake_max_retries_;
+ base::TimeDelta fake_backoff_delay_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestURLFetcher);
+};
+
+typedef TestURLFetcher::DelegateForTests TestURLFetcherDelegateForTests;
+
+// Simple URLFetcherFactory method that creates TestURLFetchers. All fetchers
+// are registered in a map by the id passed to the create method.
+// Optionally, a fetcher may be automatically unregistered from the map upon
+// its destruction.
+class TestURLFetcherFactory : public URLFetcherFactory,
+ public ScopedURLFetcherFactory {
+ public:
+ TestURLFetcherFactory();
+ virtual ~TestURLFetcherFactory();
+
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+ TestURLFetcher* GetFetcherByID(int id) const;
+ void RemoveFetcherFromMap(int id);
+ void SetDelegateForTests(TestURLFetcherDelegateForTests* delegate_for_tests);
+ void set_remove_fetcher_on_delete(bool remove_fetcher_on_delete) {
+ remove_fetcher_on_delete_ = remove_fetcher_on_delete;
+ }
+
+ private:
+ // Maps from id passed to create to the returned URLFetcher.
+ typedef std::map<int, TestURLFetcher*> Fetchers;
+ Fetchers fetchers_;
+ TestURLFetcherDelegateForTests* delegate_for_tests_;
+ // Whether to automatically unregister a fetcher from this factory upon its
+ // destruction, false by default.
+ bool remove_fetcher_on_delete_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestURLFetcherFactory);
+};
+
+// The FakeURLFetcher and FakeURLFetcherFactory classes are similar to the
+// ones above but don't require you to know when exactly the URLFetcher objects
+// will be created.
+//
+// These classes let you set pre-baked HTTP responses for particular URLs.
+// E.g., if the user requests http://a.com/ then respond with an HTTP/500.
+//
+// We assume that the thread that is calling Start() on the URLFetcher object
+// has a message loop running.
+
+// FakeURLFetcher can be used to create a URLFetcher that will emit a fake
+// response when started. This class can be used in place of an actual
+// URLFetcher.
+//
+// Example usage:
+// FakeURLFetcher fake_fetcher("http://a.com", some_delegate,
+// "<html><body>hello world</body></html>",
+// true);
+//
+// // Will schedule a call to some_delegate->OnURLFetchComplete(&fake_fetcher).
+// fake_fetcher.Start();
+class FakeURLFetcher : public TestURLFetcher {
+ public:
+ // Normal URL fetcher constructor but also takes in a pre-baked response.
+ FakeURLFetcher(const GURL& url,
+ URLFetcherDelegate* d,
+ const std::string& response_data, bool success);
+
+ // Start the request. This will call the given delegate asynchronously
+ // with the pre-baked response as parameter.
+ virtual void Start() OVERRIDE;
+
+ virtual const GURL& GetURL() const OVERRIDE;
+
+ virtual ~FakeURLFetcher();
+
+ private:
+ // This is the method which actually calls the delegate that is passed in the
+ // constructor.
+ void RunDelegate();
+
+ base::WeakPtrFactory<FakeURLFetcher> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeURLFetcher);
+};
+
+
+// FakeURLFetcherFactory is a factory for FakeURLFetcher objects. When
+// instantiated, it sets itself up as the default URLFetcherFactory. Fake
+// responses for given URLs can be set using SetFakeResponse.
+//
+// This class is not thread-safe. You should not call SetFakeResponse or
+// ClearFakeResponse at the same time you call CreateURLFetcher. However, it is
+// OK to start URLFetcher objects while setting or clearning fake responses
+// since already created URLFetcher objects will not be affected by any changes
+// made to the fake responses (once a URLFetcher object is created you cannot
+// change its fake response).
+//
+// Example usage:
+// FakeURLFetcherFactory factory;
+//
+// // You know that class SomeService will request url http://a.com/ and you
+// // want to test the service class by returning an error.
+// factory.SetFakeResponse("http://a.com/", "", false);
+// // But if the service requests http://b.com/asdf you want to respond with
+// // a simple html page and an HTTP/200 code.
+// factory.SetFakeResponse("http://b.com/asdf",
+// "<html><body>hello world</body></html>",
+// true);
+//
+// SomeService service;
+// service.Run(); // Will eventually request these two URLs.
+class FakeURLFetcherFactory : public URLFetcherFactory,
+ public ScopedURLFetcherFactory {
+ public:
+ // Parameters to FakeURLFetcherCreator: url, delegate, response_data, success
+ // |url| URL for instantiated FakeURLFetcher
+ // |delegate| Delegate for FakeURLFetcher
+ // |response_data| response data for FakeURLFetcher
+ // |success| bool indicating response code. true = 200 and false = 500.
+ // These argument should by default be used in instantiating FakeURLFetcher
+ // as follows: new FakeURLFetcher(url, delegate, response_data, success)
+ typedef base::Callback<scoped_ptr<FakeURLFetcher>(
+ const GURL&,
+ URLFetcherDelegate*,
+ const std::string&,
+ bool)> FakeURLFetcherCreator;
+
+ // |default_factory|, which can be NULL, is a URLFetcherFactory that
+ // will be used to construct a URLFetcher in case the URL being created
+ // has no pre-baked response. If it is NULL, a URLFetcherImpl will be
+ // created in this case.
+ explicit FakeURLFetcherFactory(URLFetcherFactory* default_factory);
+
+ // |default_factory|, which can be NULL, is a URLFetcherFactory that
+ // will be used to construct a URLFetcher in case the URL being created
+ // has no pre-baked response. If it is NULL, a URLFetcherImpl will be
+ // created in this case.
+ // |creator| is a callback that returns will be called to create a
+ // FakeURLFetcher if a response is found to a given URL. It can be
+ // set to MakeFakeURLFetcher.
+ FakeURLFetcherFactory(URLFetcherFactory* default_factory,
+ const FakeURLFetcherCreator& creator);
+
+ virtual ~FakeURLFetcherFactory();
+
+ // If no fake response is set for the given URL this method will delegate the
+ // call to |default_factory_| if it is not NULL, or return NULL if it is
+ // NULL.
+ // Otherwise, it will return a URLFetcher object which will respond with the
+ // pre-baked response that the client has set by calling SetFakeResponse().
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+
+ // Sets the fake response for a given URL. If success is true we will serve
+ // an HTTP/200 and an HTTP/500 otherwise. The |response_data| may be empty.
+ void SetFakeResponse(const std::string& url,
+ const std::string& response_data,
+ bool success);
+
+ // Clear all the fake responses that were previously set via
+ // SetFakeResponse().
+ void ClearFakeResponses();
+
+ private:
+ const FakeURLFetcherCreator creator_;
+ typedef std::map<GURL, std::pair<std::string, bool> > FakeResponseMap;
+ FakeResponseMap fake_responses_;
+ URLFetcherFactory* const default_factory_;
+
+ static scoped_ptr<FakeURLFetcher> DefaultFakeURLFetcherCreator(
+ const GURL& url,
+ URLFetcherDelegate* delegate,
+ const std::string& response,
+ bool success);
+ DISALLOW_COPY_AND_ASSIGN(FakeURLFetcherFactory);
+};
+
+// This is an implementation of URLFetcherFactory that will create a
+// URLFetcherImpl. It can be use in conjunction with a FakeURLFetcherFactory in
+// integration tests to control the behavior of some requests but execute
+// all the other ones.
+class URLFetcherImplFactory : public URLFetcherFactory {
+ public:
+ URLFetcherImplFactory();
+ virtual ~URLFetcherImplFactory();
+
+ // This method will create a real URLFetcher.
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
diff --git a/chromium/net/url_request/url_fetcher.cc b/chromium/net/url_request/url_fetcher.cc
new file mode 100644
index 00000000000..96a25104c3b
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_fetcher.h"
+
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_impl.h"
+
+namespace net {
+
+URLFetcher::~URLFetcher() {}
+
+// static
+URLFetcher* net::URLFetcher::Create(
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ return URLFetcher::Create(0, url, request_type, d);
+}
+
+// static
+URLFetcher* net::URLFetcher::Create(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ URLFetcherFactory* factory = URLFetcherImpl::factory();
+ return factory ? factory->CreateURLFetcher(id, url, request_type, d)
+ : new URLFetcherImpl(url, request_type, d);
+}
+
+// static
+void net::URLFetcher::CancelAll() {
+ URLFetcherImpl::CancelAll();
+}
+
+// static
+void net::URLFetcher::SetEnableInterceptionForTests(bool enabled) {
+ URLFetcherImpl::SetEnableInterceptionForTests(enabled);
+}
+
+// static
+void net::URLFetcher::SetIgnoreCertificateRequests(bool ignored) {
+ URLFetcherImpl::SetIgnoreCertificateRequests(ignored);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher.h b/chromium/net/url_request/url_fetcher.h
new file mode 100644
index 00000000000..c8a587dbc88
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher.h
@@ -0,0 +1,300 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_H_
+#define NET_URL_REQUEST_URL_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/supports_user_data.h"
+#include "base/task_runner.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class MessageLoopProxy;
+class TimeDelta;
+}
+
+namespace net {
+class HostPortPair;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+class URLFetcherDelegate;
+class URLRequestContextGetter;
+class URLRequestStatus;
+typedef std::vector<std::string> ResponseCookies;
+
+// To use this class, create an instance with the desired URL and a pointer to
+// the object to be notified when the URL has been loaded:
+// URLFetcher* fetcher = URLFetcher::Create("http://www.google.com",
+// URLFetcher::GET, this);
+//
+// You must also set a request context getter:
+//
+// fetcher->SetRequestContext(&my_request_context_getter);
+//
+// Then, optionally set properties on this object, like the request context or
+// extra headers:
+// fetcher->set_extra_request_headers("X-Foo: bar");
+//
+// Finally, start the request:
+// fetcher->Start();
+//
+//
+// The object you supply as a delegate must inherit from
+// URLFetcherDelegate; when the fetch is completed,
+// OnURLFetchComplete() will be called with a pointer to the URLFetcher. From
+// that point until the original URLFetcher instance is destroyed, you may use
+// accessor methods to see the result of the fetch. You should copy these
+// objects if you need them to live longer than the URLFetcher instance. If the
+// URLFetcher instance is destroyed before the callback happens, the fetch will
+// be canceled and no callback will occur.
+//
+// You may create the URLFetcher instance on any thread; OnURLFetchComplete()
+// will be called back on the same thread you use to create the instance.
+//
+//
+// NOTE: By default URLFetcher requests are NOT intercepted, except when
+// interception is explicitly enabled in tests.
+class NET_EXPORT URLFetcher {
+ public:
+ // Imposible http response code. Used to signal that no http response code
+ // was received.
+ enum ResponseCode {
+ RESPONSE_CODE_INVALID = -1
+ };
+
+ enum RequestType {
+ GET,
+ POST,
+ HEAD,
+ DELETE_REQUEST, // DELETE is already taken on Windows.
+ // <winnt.h> defines a DELETE macro.
+ PUT,
+ PATCH,
+ };
+
+ // Used by SetURLRequestUserData. The callback should make a fresh
+ // base::SupportsUserData::Data object every time it's called.
+ typedef base::Callback<base::SupportsUserData::Data*()> CreateDataCallback;
+
+ virtual ~URLFetcher();
+
+ // |url| is the URL to send the request to.
+ // |request_type| is the type of request to make.
+ // |d| the object that will receive the callback on fetch completion.
+ static URLFetcher* Create(const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Like above, but if there's a URLFetcherFactory registered with the
+ // implementation it will be used. |id| may be used during testing to identify
+ // who is creating the URLFetcher.
+ static URLFetcher* Create(int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Cancels all existing URLFetchers. Will notify the URLFetcherDelegates.
+ // Note that any new URLFetchers created while this is running will not be
+ // cancelled. Typically, one would call this in the CleanUp() method of an IO
+ // thread, so that no new URLRequests would be able to start on the IO thread
+ // anyway. This doesn't prevent new URLFetchers from trying to post to the IO
+ // thread though, even though the task won't ever run.
+ static void CancelAll();
+
+ // Normally interception is disabled for URLFetcher, but you can use this
+ // to enable it for tests. Also see ScopedURLFetcherFactory for another way
+ // of testing code that uses an URLFetcher.
+ static void SetEnableInterceptionForTests(bool enabled);
+
+ // Normally, URLFetcher will abort loads that request SSL client certificate
+ // authentication, but this method may be used to cause URLFetchers to ignore
+ // requests for client certificates and continue anonymously. Because such
+ // behaviour affects the URLRequestContext's shared network state and socket
+ // pools, it should only be used for testing.
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ // Sets data only needed by POSTs. All callers making POST requests should
+ // call one of the SetUpload* methods before the request is started.
+ // |upload_content_type| is the MIME type of the content, while
+ // |upload_content| is the data to be sent (the Content-Length header value
+ // will be set to the length of this data).
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) = 0;
+
+ // Sets data only needed by POSTs. All callers making POST requests should
+ // call one of the SetUpload* methods before the request is started.
+ // |upload_content_type| is the MIME type of the content, while
+ // |file_path| is the path to the file containing the data to be sent (the
+ // Content-Length header value will be set to the length of this file).
+ // |range_offset| and |range_length| specify the range of the part
+ // to be uploaded. To upload the whole file, (0, kuint64max) can be used.
+ // |file_task_runner| will be used for all file operations.
+ virtual void SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) = 0;
+
+ // Indicates that the POST data is sent via chunked transfer encoding.
+ // This may only be called before calling Start().
+ // Use AppendChunkToUpload() to give the data chunks after calling Start().
+ virtual void SetChunkedUpload(const std::string& upload_content_type) = 0;
+
+ // Adds the given bytes to a request's POST data transmitted using chunked
+ // transfer encoding.
+ // This method should be called ONLY after calling Start().
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) = 0;
+
+ // Set one or more load flags as defined in net/base/load_flags.h. Must be
+ // called before the request is started.
+ virtual void SetLoadFlags(int load_flags) = 0;
+
+ // Returns the current load flags.
+ virtual int GetLoadFlags() const = 0;
+
+ // The referrer URL for the request. Must be called before the request is
+ // started.
+ virtual void SetReferrer(const std::string& referrer) = 0;
+
+ // Set extra headers on the request. Must be called before the request
+ // is started.
+ // This replaces the entire extra request headers.
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) = 0;
+
+ // Add header (with format field-name ":" [ field-value ]) to the request
+ // headers. Must be called before the request is started.
+ // This appends the header to the current extra request headers.
+ virtual void AddExtraRequestHeader(const std::string& header_line) = 0;
+
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const = 0;
+
+ // Set the URLRequestContext on the request. Must be called before the
+ // request is started.
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) = 0;
+
+ // Set the URL that should be consulted for the third-party cookie
+ // blocking policy.
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) = 0;
+
+ // Set the key and data callback that is used when setting the user
+ // data on any URLRequest objects this object creates.
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) = 0;
+
+ // If |stop_on_redirect| is true, 3xx responses will cause the fetch to halt
+ // immediately rather than continue through the redirect. OnURLFetchComplete
+ // will be called, with the URLFetcher's URL set to the redirect destination,
+ // its status set to CANCELED, and its response code set to the relevant 3xx
+ // server response code.
+ virtual void SetStopOnRedirect(bool stop_on_redirect) = 0;
+
+ // If |retry| is false, 5xx responses will be propagated to the observer,
+ // if it is true URLFetcher will automatically re-execute the request,
+ // after backoff_delay() elapses. URLFetcher has it set to true by default.
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) = 0;
+
+ virtual void SetMaxRetriesOn5xx(int max_retries) = 0;
+ virtual int GetMaxRetriesOn5xx() const = 0;
+
+ // Returns the back-off delay before the request will be retried,
+ // when a 5xx response was received.
+ virtual base::TimeDelta GetBackoffDelay() const = 0;
+
+ // Retries up to |max_retries| times when requests fail with
+ // ERR_NETWORK_CHANGED. If ERR_NETWORK_CHANGED is received after having
+ // retried |max_retries| times then it is propagated to the observer.
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) = 0;
+
+ // By default, the response is saved in a string. Call this method to save the
+ // response to a file instead. Must be called before Start().
+ // |file_task_runner| will be used for all file operations.
+ // To save to a temporary file, use SaveResponseToTemporaryFile().
+ // The created file is removed when the URLFetcher is deleted unless you
+ // take ownership by calling GetResponseAsFilePath().
+ virtual void SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) = 0;
+
+ // By default, the response is saved in a string. Call this method to save the
+ // response to a temporary file instead. Must be called before Start().
+ // |file_task_runner| will be used for all file operations.
+ // The created file is removed when the URLFetcher is deleted unless you
+ // take ownership by calling GetResponseAsFilePath().
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) = 0;
+
+ // Retrieve the response headers from the request. Must only be called after
+ // the OnURLFetchComplete callback has run.
+ virtual HttpResponseHeaders* GetResponseHeaders() const = 0;
+
+ // Retrieve the remote socket address from the request. Must only
+ // be called after the OnURLFetchComplete callback has run and if
+ // the request has not failed.
+ virtual HostPortPair GetSocketAddress() const = 0;
+
+ // Returns true if the request was delivered through a proxy. Must only
+ // be called after the OnURLFetchComplete callback has run and the request
+ // has not failed.
+ virtual bool WasFetchedViaProxy() const = 0;
+
+ // Start the request. After this is called, you may not change any other
+ // settings.
+ virtual void Start() = 0;
+
+ // Return the URL that we were asked to fetch.
+ virtual const GURL& GetOriginalURL() const = 0;
+
+ // Return the URL that this fetcher is processing.
+ virtual const GURL& GetURL() const = 0;
+
+ // The status of the URL fetch.
+ virtual const URLRequestStatus& GetStatus() const = 0;
+
+ // The http response code received. Will return RESPONSE_CODE_INVALID
+ // if an error prevented any response from being received.
+ virtual int GetResponseCode() const = 0;
+
+ // Cookies recieved.
+ virtual const ResponseCookies& GetCookies() const = 0;
+
+ // Return true if any file system operation failed. If so, set |error_code|
+ // to the net error code. File system errors are only possible if user called
+ // SaveResponseToTemporaryFile().
+ virtual bool FileErrorOccurred(int* out_error_code) const = 0;
+
+ // Reports that the received content was malformed.
+ virtual void ReceivedContentWasMalformed() = 0;
+
+ // Get the response as a string. Return false if the fetcher was not
+ // set to store the response as a string.
+ virtual bool GetResponseAsString(std::string* out_response_string) const = 0;
+
+ // Get the path to the file containing the response body. Returns false
+ // if the response body was not saved to a file. If take_ownership is
+ // true, caller takes responsibility for the file, and it will not
+ // be removed once the URLFetcher is destroyed. User should not take
+ // ownership more than once, or call this method after taking ownership.
+ virtual bool GetResponseAsFilePath(
+ bool take_ownership,
+ base::FilePath* out_response_path) const = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_H_
diff --git a/chromium/net/url_request/url_fetcher_core.cc b/chromium/net/url_request/url_fetcher_core.cc
new file mode 100644
index 00000000000..8f0e28dcd93
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_core.cc
@@ -0,0 +1,940 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_fetcher_core.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/tracked_objects.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_response_writer.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace {
+
+const int kBufferSize = 4096;
+const int kUploadProgressTimerInterval = 100;
+bool g_interception_enabled = false;
+bool g_ignore_certificate_requests = false;
+
+} // namespace
+
+namespace net {
+
+// URLFetcherCore::Registry ---------------------------------------------------
+
+URLFetcherCore::Registry::Registry() {}
+URLFetcherCore::Registry::~Registry() {}
+
+void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) {
+ DCHECK(!ContainsKey(fetchers_, core));
+ fetchers_.insert(core);
+}
+
+void URLFetcherCore::Registry::RemoveURLFetcherCore(URLFetcherCore* core) {
+ DCHECK(ContainsKey(fetchers_, core));
+ fetchers_.erase(core);
+}
+
+void URLFetcherCore::Registry::CancelAll() {
+ while (!fetchers_.empty())
+ (*fetchers_.begin())->CancelURLRequest();
+}
+
+// URLFetcherCore -------------------------------------------------------------
+
+// static
+base::LazyInstance<URLFetcherCore::Registry>
+ URLFetcherCore::g_registry = LAZY_INSTANCE_INITIALIZER;
+
+URLFetcherCore::URLFetcherCore(URLFetcher* fetcher,
+ const GURL& original_url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d)
+ : fetcher_(fetcher),
+ original_url_(original_url),
+ request_type_(request_type),
+ delegate_(d),
+ delegate_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ load_flags_(LOAD_NORMAL),
+ response_code_(URLFetcher::RESPONSE_CODE_INVALID),
+ buffer_(new IOBuffer(kBufferSize)),
+ url_request_data_key_(NULL),
+ was_fetched_via_proxy_(false),
+ upload_content_set_(false),
+ upload_range_offset_(0),
+ upload_range_length_(0),
+ is_chunked_upload_(false),
+ was_cancelled_(false),
+ file_writer_(NULL),
+ response_destination_(STRING),
+ stop_on_redirect_(false),
+ stopped_on_redirect_(false),
+ automatically_retry_on_5xx_(true),
+ num_retries_on_5xx_(0),
+ max_retries_on_5xx_(0),
+ num_retries_on_network_changes_(0),
+ max_retries_on_network_changes_(0),
+ current_upload_bytes_(-1),
+ current_response_bytes_(0),
+ total_response_bytes_(-1) {
+ CHECK(original_url_.is_valid());
+}
+
+void URLFetcherCore::Start() {
+ DCHECK(delegate_task_runner_.get());
+ DCHECK(request_context_getter_.get()) << "We need an URLRequestContext!";
+ if (network_task_runner_.get()) {
+ DCHECK_EQ(network_task_runner_,
+ request_context_getter_->GetNetworkTaskRunner());
+ } else {
+ network_task_runner_ = request_context_getter_->GetNetworkTaskRunner();
+ }
+ DCHECK(network_task_runner_.get()) << "We need an IO task runner";
+
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this));
+}
+
+void URLFetcherCore::Stop() {
+ if (delegate_task_runner_.get()) // May be NULL in tests.
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+
+ delegate_ = NULL;
+ fetcher_ = NULL;
+ if (!network_task_runner_.get())
+ return;
+ if (network_task_runner_->RunsTasksOnCurrentThread()) {
+ CancelURLRequest();
+ } else {
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::CancelURLRequest, this));
+ }
+}
+
+void URLFetcherCore::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ DCHECK(!is_chunked_upload_);
+ DCHECK(!upload_content_set_);
+ DCHECK(upload_content_.empty());
+ DCHECK(upload_file_path_.empty());
+ DCHECK(upload_content_type_.empty());
+
+ // Empty |upload_content_type| is allowed iff the |upload_content| is empty.
+ DCHECK(upload_content.empty() || !upload_content_type.empty());
+
+ upload_content_type_ = upload_content_type;
+ upload_content_ = upload_content;
+ upload_content_set_ = true;
+}
+
+void URLFetcherCore::SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ DCHECK(!is_chunked_upload_);
+ DCHECK(!upload_content_set_);
+ DCHECK(upload_content_.empty());
+ DCHECK(upload_file_path_.empty());
+ DCHECK_EQ(upload_range_offset_, 0ULL);
+ DCHECK_EQ(upload_range_length_, 0ULL);
+ DCHECK(upload_content_type_.empty());
+ DCHECK(!upload_content_type.empty());
+
+ upload_content_type_ = upload_content_type;
+ upload_file_path_ = file_path;
+ upload_range_offset_ = range_offset;
+ upload_range_length_ = range_length;
+ upload_file_task_runner_ = file_task_runner;
+ upload_content_set_ = true;
+}
+
+void URLFetcherCore::SetChunkedUpload(const std::string& content_type) {
+ DCHECK(is_chunked_upload_ ||
+ (upload_content_type_.empty() &&
+ upload_content_.empty()));
+
+ // Empty |content_type| is not allowed here, because it is impossible
+ // to ensure non-empty upload content as it is not yet supplied.
+ DCHECK(!content_type.empty());
+
+ upload_content_type_ = content_type;
+ upload_content_.clear();
+ is_chunked_upload_ = true;
+}
+
+void URLFetcherCore::AppendChunkToUpload(const std::string& content,
+ bool is_last_chunk) {
+ DCHECK(delegate_task_runner_.get());
+ DCHECK(network_task_runner_.get());
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::CompleteAddingUploadDataChunk, this, content,
+ is_last_chunk));
+}
+
+void URLFetcherCore::SetLoadFlags(int load_flags) {
+ load_flags_ = load_flags;
+}
+
+int URLFetcherCore::GetLoadFlags() const {
+ return load_flags_;
+}
+
+void URLFetcherCore::SetReferrer(const std::string& referrer) {
+ referrer_ = referrer;
+}
+
+void URLFetcherCore::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ extra_request_headers_.Clear();
+ extra_request_headers_.AddHeadersFromString(extra_request_headers);
+}
+
+void URLFetcherCore::AddExtraRequestHeader(const std::string& header_line) {
+ extra_request_headers_.AddHeaderFromString(header_line);
+}
+
+void URLFetcherCore::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ headers->CopyFrom(extra_request_headers_);
+}
+
+void URLFetcherCore::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+ DCHECK(!request_context_getter_.get());
+ DCHECK(request_context_getter);
+ request_context_getter_ = request_context_getter;
+}
+
+void URLFetcherCore::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+ DCHECK(first_party_for_cookies_.is_empty());
+ first_party_for_cookies_ = first_party_for_cookies;
+}
+
+void URLFetcherCore::SetURLRequestUserData(
+ const void* key,
+ const URLFetcher::CreateDataCallback& create_data_callback) {
+ DCHECK(key);
+ DCHECK(!create_data_callback.is_null());
+ url_request_data_key_ = key;
+ url_request_create_data_callback_ = create_data_callback;
+}
+
+void URLFetcherCore::SetStopOnRedirect(bool stop_on_redirect) {
+ stop_on_redirect_ = stop_on_redirect;
+}
+
+void URLFetcherCore::SetAutomaticallyRetryOn5xx(bool retry) {
+ automatically_retry_on_5xx_ = retry;
+}
+
+void URLFetcherCore::SetMaxRetriesOn5xx(int max_retries) {
+ max_retries_on_5xx_ = max_retries;
+}
+
+int URLFetcherCore::GetMaxRetriesOn5xx() const {
+ return max_retries_on_5xx_;
+}
+
+base::TimeDelta URLFetcherCore::GetBackoffDelay() const {
+ return backoff_delay_;
+}
+
+void URLFetcherCore::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+ max_retries_on_network_changes_ = max_retries;
+}
+
+void URLFetcherCore::SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ file_task_runner_ = file_task_runner;
+ response_destination_ = URLFetcherCore::PERMANENT_FILE;
+ response_destination_file_path_ = file_path;
+}
+
+void URLFetcherCore::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ file_task_runner_ = file_task_runner;
+ response_destination_ = URLFetcherCore::TEMP_FILE;
+}
+
+HttpResponseHeaders* URLFetcherCore::GetResponseHeaders() const {
+ return response_headers_.get();
+}
+
+// TODO(panayiotis): socket_address_ is written in the IO thread,
+// if this is accessed in the UI thread, this could result in a race.
+// Same for response_headers_ above and was_fetched_via_proxy_ below.
+HostPortPair URLFetcherCore::GetSocketAddress() const {
+ return socket_address_;
+}
+
+bool URLFetcherCore::WasFetchedViaProxy() const {
+ return was_fetched_via_proxy_;
+}
+
+const GURL& URLFetcherCore::GetOriginalURL() const {
+ return original_url_;
+}
+
+const GURL& URLFetcherCore::GetURL() const {
+ return url_;
+}
+
+const URLRequestStatus& URLFetcherCore::GetStatus() const {
+ return status_;
+}
+
+int URLFetcherCore::GetResponseCode() const {
+ return response_code_;
+}
+
+const ResponseCookies& URLFetcherCore::GetCookies() const {
+ return cookies_;
+}
+
+bool URLFetcherCore::FileErrorOccurred(int* out_error_code) const {
+ // Can't have a file error if no file is being created or written to.
+ if (!file_writer_)
+ return false;
+
+ int error_code = file_writer_->error_code();
+ if (error_code == OK)
+ return false;
+
+ *out_error_code = error_code;
+ return true;
+}
+
+void URLFetcherCore::ReceivedContentWasMalformed() {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (network_task_runner_.get()) {
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::NotifyMalformedContent, this));
+ }
+}
+
+bool URLFetcherCore::GetResponseAsString(
+ std::string* out_response_string) const {
+ if (response_destination_ != URLFetcherCore::STRING)
+ return false;
+
+ *out_response_string = data_;
+ UMA_HISTOGRAM_MEMORY_KB("UrlFetcher.StringResponseSize",
+ (data_.length() / 1024));
+
+ return true;
+}
+
+bool URLFetcherCore::GetResponseAsFilePath(bool take_ownership,
+ base::FilePath* out_response_path) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ const bool destination_is_file =
+ response_destination_ == URLFetcherCore::TEMP_FILE ||
+ response_destination_ == URLFetcherCore::PERMANENT_FILE;
+ if (!destination_is_file || !file_writer_)
+ return false;
+
+ *out_response_path = file_writer_->file_path();
+
+ if (take_ownership) {
+ // Intentionally calling a file_writer_ method directly without posting
+ // the task to network_task_runner_.
+ //
+ // This is for correctly handling the case when file_writer_->DisownFile()
+ // is soon followed by URLFetcherCore::Stop(). We have to make sure that
+ // DisownFile takes effect before Stop deletes file_writer_.
+ //
+ // This direct call should be thread-safe, since DisownFile itself does no
+ // file operation. It just flips the state to be referred in destruction.
+ file_writer_->DisownFile();
+ }
+ return true;
+}
+
+void URLFetcherCore::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (stop_on_redirect_) {
+ stopped_on_redirect_ = true;
+ url_ = new_url;
+ response_code_ = request_->GetResponseCode();
+ was_fetched_via_proxy_ = request_->was_fetched_via_proxy();
+ request->Cancel();
+ OnReadCompleted(request, 0);
+ }
+}
+
+void URLFetcherCore::OnResponseStarted(URLRequest* request) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (request_->status().is_success()) {
+ response_code_ = request_->GetResponseCode();
+ response_headers_ = request_->response_headers();
+ socket_address_ = request_->GetSocketAddress();
+ was_fetched_via_proxy_ = request_->was_fetched_via_proxy();
+ total_response_bytes_ = request_->GetExpectedContentSize();
+ }
+
+ ReadResponse();
+}
+
+void URLFetcherCore::OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (g_ignore_certificate_requests) {
+ request->ContinueWithCertificate(NULL);
+ } else {
+ request->Cancel();
+ }
+}
+
+void URLFetcherCore::OnReadCompleted(URLRequest* request,
+ int bytes_read) {
+ DCHECK(request == request_);
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (!stopped_on_redirect_)
+ url_ = request->url();
+ URLRequestThrottlerManager* throttler_manager =
+ request->context()->throttler_manager();
+ if (throttler_manager) {
+ url_throttler_entry_ = throttler_manager->RegisterRequestUrl(url_);
+ }
+
+ bool waiting_on_write = false;
+ do {
+ if (!request_->status().is_success() || bytes_read <= 0)
+ break;
+
+ current_response_bytes_ += bytes_read;
+ InformDelegateDownloadProgress();
+ InformDelegateDownloadDataIfNecessary(bytes_read);
+
+ const int result =
+ WriteBuffer(new DrainableIOBuffer(buffer_.get(), bytes_read));
+ if (result < 0) {
+ // Write failed or waiting for write completion.
+ if (result == ERR_IO_PENDING)
+ waiting_on_write = true;
+ break;
+ }
+ } while (request_->Read(buffer_.get(), kBufferSize, &bytes_read));
+
+ const URLRequestStatus status = request_->status();
+
+ if (status.is_success())
+ request_->GetResponseCookies(&cookies_);
+
+ // See comments re: HEAD requests in ReadResponse().
+ if ((!status.is_io_pending() && !waiting_on_write) ||
+ (request_type_ == URLFetcher::HEAD)) {
+ status_ = status;
+ ReleaseRequest();
+
+ // No more data to write.
+ const int result = response_writer_->Finish(
+ base::Bind(&URLFetcherCore::DidFinishWriting, this));
+ if (result != ERR_IO_PENDING)
+ DidFinishWriting(result);
+ }
+}
+
+void URLFetcherCore::CancelAll() {
+ g_registry.Get().CancelAll();
+}
+
+int URLFetcherCore::GetNumFetcherCores() {
+ return g_registry.Get().size();
+}
+
+void URLFetcherCore::SetEnableInterceptionForTests(bool enabled) {
+ g_interception_enabled = enabled;
+}
+
+void URLFetcherCore::SetIgnoreCertificateRequests(bool ignored) {
+ g_ignore_certificate_requests = ignored;
+}
+
+URLFetcherCore::~URLFetcherCore() {
+ // |request_| should be NULL. If not, it's unsafe to delete it here since we
+ // may not be on the IO thread.
+ DCHECK(!request_.get());
+}
+
+void URLFetcherCore::StartOnIOThread() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ switch (response_destination_) {
+ case STRING:
+ response_writer_.reset(new URLFetcherStringWriter(&data_));
+ break;
+
+ case PERMANENT_FILE:
+ case TEMP_FILE:
+ DCHECK(file_task_runner_.get())
+ << "Need to set the file task runner.";
+
+ file_writer_ = new URLFetcherFileWriter(file_task_runner_);
+
+ // If the file is successfully created,
+ // URLFetcherCore::StartURLRequestWhenAppropriate() will be called.
+ if (response_destination_ == PERMANENT_FILE) {
+ file_writer_->set_destination_file_path(
+ response_destination_file_path_);
+ }
+ response_writer_.reset(file_writer_);
+ break;
+
+ default:
+ NOTREACHED();
+ }
+ DCHECK(response_writer_);
+ const int result = response_writer_->Initialize(
+ base::Bind(&URLFetcherCore::DidInitializeWriter, this));
+ if (result != ERR_IO_PENDING)
+ DidInitializeWriter(result);
+}
+
+void URLFetcherCore::StartURLRequest() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (was_cancelled_) {
+ // Since StartURLRequest() is posted as a *delayed* task, it may
+ // run after the URLFetcher was already stopped.
+ return;
+ }
+
+ DCHECK(request_context_getter_.get());
+ DCHECK(!request_.get());
+
+ g_registry.Get().AddURLFetcherCore(this);
+ current_response_bytes_ = 0;
+ request_.reset(request_context_getter_->GetURLRequestContext()->CreateRequest(
+ original_url_, this));
+ request_->set_stack_trace(stack_trace_);
+ int flags = request_->load_flags() | load_flags_;
+ if (!g_interception_enabled)
+ flags = flags | LOAD_DISABLE_INTERCEPT;
+
+ if (is_chunked_upload_)
+ request_->EnableChunkedUpload();
+ request_->set_load_flags(flags);
+ request_->SetReferrer(referrer_);
+ request_->set_first_party_for_cookies(first_party_for_cookies_.is_empty() ?
+ original_url_ : first_party_for_cookies_);
+ if (url_request_data_key_ && !url_request_create_data_callback_.is_null()) {
+ request_->SetUserData(url_request_data_key_,
+ url_request_create_data_callback_.Run());
+ }
+
+ switch (request_type_) {
+ case URLFetcher::GET:
+ break;
+
+ case URLFetcher::POST:
+ case URLFetcher::PUT:
+ case URLFetcher::PATCH:
+ // Upload content must be set.
+ DCHECK(is_chunked_upload_ || upload_content_set_);
+
+ request_->set_method(
+ request_type_ == URLFetcher::POST ? "POST" :
+ request_type_ == URLFetcher::PUT ? "PUT" : "PATCH");
+ extra_request_headers_.SetHeader(HttpRequestHeaders::kContentType,
+ upload_content_type_);
+ if (!upload_content_type_.empty()) {
+ extra_request_headers_.SetHeader(HttpRequestHeaders::kContentType,
+ upload_content_type_);
+ }
+ if (!upload_content_.empty()) {
+ scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader(
+ upload_content_.data(), upload_content_.size()));
+ request_->set_upload(make_scoped_ptr(
+ UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ } else if (!upload_file_path_.empty()) {
+ scoped_ptr<UploadElementReader> reader(
+ new UploadFileElementReader(upload_file_task_runner_.get(),
+ upload_file_path_,
+ upload_range_offset_,
+ upload_range_length_,
+ base::Time()));
+ request_->set_upload(make_scoped_ptr(
+ UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ }
+
+ current_upload_bytes_ = -1;
+ // TODO(kinaba): http://crbug.com/118103. Implement upload callback in the
+ // layer and avoid using timer here.
+ upload_progress_checker_timer_.reset(
+ new base::RepeatingTimer<URLFetcherCore>());
+ upload_progress_checker_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kUploadProgressTimerInterval),
+ this,
+ &URLFetcherCore::InformDelegateUploadProgress);
+ break;
+
+ case URLFetcher::HEAD:
+ request_->set_method("HEAD");
+ break;
+
+ case URLFetcher::DELETE_REQUEST:
+ request_->set_method("DELETE");
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ if (!extra_request_headers_.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_request_headers_);
+
+ // There might be data left over from a previous request attempt.
+ data_.clear();
+
+ // If we are writing the response to a file, the only caller
+ // of this function should have created it and not written yet.
+ DCHECK(!file_writer_ || file_writer_->total_bytes_written() == 0);
+
+ request_->Start();
+}
+
+void URLFetcherCore::DidInitializeWriter(int result) {
+ if (result != OK) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this));
+ return;
+ }
+ StartURLRequestWhenAppropriate();
+}
+
+void URLFetcherCore::StartURLRequestWhenAppropriate() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (was_cancelled_)
+ return;
+
+ DCHECK(request_context_getter_.get());
+
+ int64 delay = 0LL;
+ if (original_url_throttler_entry_.get() == NULL) {
+ URLRequestThrottlerManager* manager =
+ request_context_getter_->GetURLRequestContext()->throttler_manager();
+ if (manager) {
+ original_url_throttler_entry_ =
+ manager->RegisterRequestUrl(original_url_);
+ }
+ }
+ if (original_url_throttler_entry_.get() != NULL) {
+ delay = original_url_throttler_entry_->ReserveSendingTimeForNextRequest(
+ GetBackoffReleaseTime());
+ }
+
+ if (delay == 0) {
+ StartURLRequest();
+ } else {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartURLRequest, this),
+ base::TimeDelta::FromMilliseconds(delay));
+ }
+}
+
+void URLFetcherCore::CancelURLRequest() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (request_.get()) {
+ request_->Cancel();
+ ReleaseRequest();
+ }
+ // Release the reference to the request context. There could be multiple
+ // references to URLFetcher::Core at this point so it may take a while to
+ // delete the object, but we cannot delay the destruction of the request
+ // context.
+ request_context_getter_ = NULL;
+ first_party_for_cookies_ = GURL();
+ url_request_data_key_ = NULL;
+ url_request_create_data_callback_.Reset();
+ was_cancelled_ = true;
+ response_writer_.reset();
+ file_writer_ = NULL;
+}
+
+void URLFetcherCore::OnCompletedURLRequest(
+ base::TimeDelta backoff_delay) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+
+ // Save the status and backoff_delay so that delegates can read it.
+ if (delegate_) {
+ backoff_delay_ = backoff_delay;
+ InformDelegateFetchIsComplete();
+ }
+}
+
+void URLFetcherCore::InformDelegateFetchIsComplete() {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchComplete(fetcher_);
+}
+
+void URLFetcherCore::NotifyMalformedContent() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (url_throttler_entry_.get() != NULL) {
+ int status_code = response_code_;
+ if (status_code == URLFetcher::RESPONSE_CODE_INVALID) {
+ // The status code will generally be known by the time clients
+ // call the |ReceivedContentWasMalformed()| function (which ends up
+ // calling the current function) but if it's not, we need to assume
+ // the response was successful so that the total failure count
+ // used to calculate exponential back-off goes up.
+ status_code = 200;
+ }
+ url_throttler_entry_->ReceivedContentWasMalformed(status_code);
+ }
+}
+
+void URLFetcherCore::DidFinishWriting(int result) {
+ if (result != OK) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this));
+ return;
+ }
+ // If the file was successfully closed, then the URL request is complete.
+ RetryOrCompleteUrlFetch();
+}
+
+void URLFetcherCore::RetryOrCompleteUrlFetch() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ base::TimeDelta backoff_delay;
+
+ // Checks the response from server.
+ if (response_code_ >= 500 ||
+ status_.error() == ERR_TEMPORARILY_THROTTLED) {
+ // When encountering a server error, we will send the request again
+ // after backoff time.
+ ++num_retries_on_5xx_;
+
+ // Note that backoff_delay may be 0 because (a) the
+ // URLRequestThrottlerManager and related code does not
+ // necessarily back off on the first error, (b) it only backs off
+ // on some of the 5xx status codes, (c) not all URLRequestContexts
+ // have a throttler manager.
+ base::TimeTicks backoff_release_time = GetBackoffReleaseTime();
+ backoff_delay = backoff_release_time - base::TimeTicks::Now();
+ if (backoff_delay < base::TimeDelta())
+ backoff_delay = base::TimeDelta();
+
+ if (automatically_retry_on_5xx_ &&
+ num_retries_on_5xx_ <= max_retries_on_5xx_) {
+ StartOnIOThread();
+ return;
+ }
+ } else {
+ backoff_delay = base::TimeDelta();
+ }
+
+ // Retry if the request failed due to network changes.
+ if (status_.error() == ERR_NETWORK_CHANGED &&
+ num_retries_on_network_changes_ < max_retries_on_network_changes_) {
+ ++num_retries_on_network_changes_;
+
+ // Retry soon, after flushing all the current tasks which may include
+ // further network change observers.
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this));
+ return;
+ }
+
+ request_context_getter_ = NULL;
+ first_party_for_cookies_ = GURL();
+ url_request_data_key_ = NULL;
+ url_request_create_data_callback_.Reset();
+ bool posted = delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::OnCompletedURLRequest, this, backoff_delay));
+
+ // If the delegate message loop does not exist any more, then the delegate
+ // should be gone too.
+ DCHECK(posted || !delegate_);
+}
+
+void URLFetcherCore::ReleaseRequest() {
+ upload_progress_checker_timer_.reset();
+ request_.reset();
+ g_registry.Get().RemoveURLFetcherCore(this);
+}
+
+base::TimeTicks URLFetcherCore::GetBackoffReleaseTime() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (original_url_throttler_entry_.get()) {
+ base::TimeTicks original_url_backoff =
+ original_url_throttler_entry_->GetExponentialBackoffReleaseTime();
+ base::TimeTicks destination_url_backoff;
+ if (url_throttler_entry_.get() != NULL &&
+ original_url_throttler_entry_.get() != url_throttler_entry_.get()) {
+ destination_url_backoff =
+ url_throttler_entry_->GetExponentialBackoffReleaseTime();
+ }
+
+ return original_url_backoff > destination_url_backoff ?
+ original_url_backoff : destination_url_backoff;
+ } else {
+ return base::TimeTicks();
+ }
+}
+
+void URLFetcherCore::CompleteAddingUploadDataChunk(
+ const std::string& content, bool is_last_chunk) {
+ if (was_cancelled_) {
+ // Since CompleteAddingUploadDataChunk() is posted as a *delayed* task, it
+ // may run after the URLFetcher was already stopped.
+ return;
+ }
+ DCHECK(is_chunked_upload_);
+ DCHECK(request_.get());
+ DCHECK(!content.empty());
+ request_->AppendChunkToUpload(content.data(),
+ static_cast<int>(content.length()),
+ is_last_chunk);
+}
+
+int URLFetcherCore::WriteBuffer(scoped_refptr<DrainableIOBuffer> data) {
+ while (data->BytesRemaining() > 0) {
+ const int result = response_writer_->Write(
+ data.get(),
+ data->BytesRemaining(),
+ base::Bind(&URLFetcherCore::DidWriteBuffer, this, data));
+ if (result < 0)
+ return result;
+ data->DidConsume(result);
+ }
+ return OK;
+}
+
+void URLFetcherCore::DidWriteBuffer(scoped_refptr<DrainableIOBuffer> data,
+ int result) {
+ if (result >= 0) { // Continue writing.
+ data->DidConsume(result);
+ result = WriteBuffer(data);
+ if (result == ERR_IO_PENDING)
+ return;
+ }
+
+ if (result < 0) { // Handle errors.
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, this));
+ return;
+ }
+ // Finished writing buffer_. Read some more.
+ DCHECK_EQ(0, data->BytesRemaining());
+ ReadResponse();
+}
+
+void URLFetcherCore::ReadResponse() {
+ // Some servers may treat HEAD requests as GET requests. To free up the
+ // network connection as soon as possible, signal that the request has
+ // completed immediately, without trying to read any data back (all we care
+ // about is the response code and headers, which we already have).
+ int bytes_read = 0;
+ if (request_->status().is_success() &&
+ (request_type_ != URLFetcher::HEAD))
+ request_->Read(buffer_.get(), kBufferSize, &bytes_read);
+ OnReadCompleted(request_.get(), bytes_read);
+}
+
+void URLFetcherCore::InformDelegateUploadProgress() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (request_.get()) {
+ int64 current = request_->GetUploadProgress().position();
+ if (current_upload_bytes_ != current) {
+ current_upload_bytes_ = current;
+ int64 total = -1;
+ if (!is_chunked_upload_) {
+ total = static_cast<int64>(request_->GetUploadProgress().size());
+ // Total may be zero if the UploadDataStream::Init has not been called
+ // yet. Don't send the upload progress until the size is initialized.
+ if (!total)
+ return;
+ }
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateUploadProgressInDelegateThread,
+ this, current, total));
+ }
+ }
+}
+
+void URLFetcherCore::InformDelegateUploadProgressInDelegateThread(
+ int64 current, int64 total) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchUploadProgress(fetcher_, current, total);
+}
+
+void URLFetcherCore::InformDelegateDownloadProgress() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateDownloadProgressInDelegateThread,
+ this, current_response_bytes_, total_response_bytes_));
+}
+
+void URLFetcherCore::InformDelegateDownloadProgressInDelegateThread(
+ int64 current, int64 total) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchDownloadProgress(fetcher_, current, total);
+}
+
+void URLFetcherCore::InformDelegateDownloadDataIfNecessary(int bytes_read) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (delegate_ && delegate_->ShouldSendDownloadData()) {
+ scoped_ptr<std::string> download_data(
+ new std::string(buffer_->data(), bytes_read));
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateDownloadDataInDelegateThread,
+ this, base::Passed(&download_data)));
+ }
+}
+
+void URLFetcherCore::InformDelegateDownloadDataInDelegateThread(
+ scoped_ptr<std::string> download_data) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchDownloadData(fetcher_, download_data.Pass());
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher_core.h b/chromium/net/url_request/url_fetcher_core.h
new file mode 100644
index 00000000000..27e253fbe54
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_core.h
@@ -0,0 +1,349 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_CORE_H_
+#define NET_URL_REQUEST_URL_FETCHER_CORE_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/timer/timer.h"
+#include "net/base/host_port_pair.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+class DrainableIOBuffer;
+class HttpResponseHeaders;
+class IOBuffer;
+class URLFetcherDelegate;
+class URLFetcherFileWriter;
+class URLFetcherResponseWriter;
+class URLRequestContextGetter;
+class URLRequestThrottlerEntryInterface;
+
+class URLFetcherCore
+ : public base::RefCountedThreadSafe<URLFetcherCore>,
+ public URLRequest::Delegate {
+ public:
+ URLFetcherCore(URLFetcher* fetcher,
+ const GURL& original_url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Starts the load. It's important that this not happen in the constructor
+ // because it causes the IO thread to begin AddRef()ing and Release()ing
+ // us. If our caller hasn't had time to fully construct us and take a
+ // reference, the IO thread could interrupt things, run a task, Release()
+ // us, and destroy us, leaving the caller with an already-destroyed object
+ // when construction finishes.
+ void Start();
+
+ // Stops any in-progress load and ensures no callback will happen. It is
+ // safe to call this multiple times.
+ void Stop();
+
+ // URLFetcher-like functions.
+
+ // For POST requests, set |content_type| to the MIME type of the
+ // content and set |content| to the data to upload.
+ void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content);
+ void SetUploadFilePath(const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner);
+ void SetChunkedUpload(const std::string& upload_content_type);
+ // Adds a block of data to be uploaded in a POST body. This can only be
+ // called after Start().
+ void AppendChunkToUpload(const std::string& data, bool is_last_chunk);
+ // |flags| are flags to apply to the load operation--these should be
+ // one or more of the LOAD_* flags defined in net/base/load_flags.h.
+ void SetLoadFlags(int load_flags);
+ int GetLoadFlags() const;
+ void SetReferrer(const std::string& referrer);
+ void SetExtraRequestHeaders(const std::string& extra_request_headers);
+ void AddExtraRequestHeader(const std::string& header_line);
+ void GetExtraRequestHeaders(HttpRequestHeaders* headers) const;
+ void SetRequestContext(URLRequestContextGetter* request_context_getter);
+ // Set the URL that should be consulted for the third-party cookie
+ // blocking policy.
+ void SetFirstPartyForCookies(const GURL& first_party_for_cookies);
+ // Set the key and data callback that is used when setting the user
+ // data on any URLRequest objects this object creates.
+ void SetURLRequestUserData(
+ const void* key,
+ const URLFetcher::CreateDataCallback& create_data_callback);
+ void SetStopOnRedirect(bool stop_on_redirect);
+ void SetAutomaticallyRetryOn5xx(bool retry);
+ void SetMaxRetriesOn5xx(int max_retries);
+ int GetMaxRetriesOn5xx() const;
+ base::TimeDelta GetBackoffDelay() const;
+ void SetAutomaticallyRetryOnNetworkChanges(int max_retries);
+ void SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner);
+ void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner);
+ HttpResponseHeaders* GetResponseHeaders() const;
+ HostPortPair GetSocketAddress() const;
+ bool WasFetchedViaProxy() const;
+ const GURL& GetOriginalURL() const;
+ const GURL& GetURL() const;
+ const URLRequestStatus& GetStatus() const;
+ int GetResponseCode() const;
+ const ResponseCookies& GetCookies() const;
+ bool FileErrorOccurred(int* out_error_code) const;
+ // Reports that the received content was malformed (i.e. failed parsing
+ // or validation). This makes the throttling logic that does exponential
+ // back-off when servers are having problems treat the current request as
+ // a failure. Your call to this method will be ignored if your request is
+ // already considered a failure based on the HTTP response code or response
+ // headers.
+ void ReceivedContentWasMalformed();
+ bool GetResponseAsString(std::string* out_response_string) const;
+ bool GetResponseAsFilePath(bool take_ownership,
+ base::FilePath* out_response_path);
+
+ // Overridden from URLRequest::Delegate:
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request,
+ int bytes_read) OVERRIDE;
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+
+ URLFetcherDelegate* delegate() const { return delegate_; }
+ static void CancelAll();
+ static int GetNumFetcherCores();
+ static void SetEnableInterceptionForTests(bool enabled);
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ private:
+ friend class base::RefCountedThreadSafe<URLFetcherCore>;
+
+ // How should the response be stored?
+ enum ResponseDestinationType {
+ STRING, // Default: In a std::string
+ PERMANENT_FILE, // Write to a permanent file.
+ TEMP_FILE, // Write to a temporary file.
+ };
+
+ class Registry {
+ public:
+ Registry();
+ ~Registry();
+
+ void AddURLFetcherCore(URLFetcherCore* core);
+ void RemoveURLFetcherCore(URLFetcherCore* core);
+
+ void CancelAll();
+
+ int size() const {
+ return fetchers_.size();
+ }
+
+ private:
+ std::set<URLFetcherCore*> fetchers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Registry);
+ };
+
+ virtual ~URLFetcherCore();
+
+ // Wrapper functions that allow us to ensure actions happen on the right
+ // thread.
+ void StartOnIOThread();
+ void StartURLRequest();
+ void DidInitializeWriter(int result);
+ void StartURLRequestWhenAppropriate();
+ void CancelURLRequest();
+ void OnCompletedURLRequest(base::TimeDelta backoff_delay);
+ void InformDelegateFetchIsComplete();
+ void NotifyMalformedContent();
+ void DidFinishWriting(int result);
+ void RetryOrCompleteUrlFetch();
+
+ // Deletes the request, removes it from the registry, and removes the
+ // destruction observer.
+ void ReleaseRequest();
+
+ // Returns the max value of exponential back-off release time for
+ // |original_url_| and |url_|.
+ base::TimeTicks GetBackoffReleaseTime();
+
+ void CompleteAddingUploadDataChunk(const std::string& data,
+ bool is_last_chunk);
+
+ // Writes all bytes stored in |data| with |response_writer_|.
+ // Returns OK if all bytes in |data| get written synchronously. Otherwise,
+ // returns ERR_IO_PENDING or a network error code.
+ int WriteBuffer(scoped_refptr<DrainableIOBuffer> data);
+
+ // Used to implement WriteBuffer().
+ void DidWriteBuffer(scoped_refptr<DrainableIOBuffer> data, int result);
+
+ // Read response bytes from the request.
+ void ReadResponse();
+
+ // Notify Delegate about the progress of upload/download.
+ void InformDelegateUploadProgress();
+ void InformDelegateUploadProgressInDelegateThread(int64 current, int64 total);
+ void InformDelegateDownloadProgress();
+ void InformDelegateDownloadProgressInDelegateThread(int64 current,
+ int64 total);
+ void InformDelegateDownloadDataIfNecessary(int bytes_read);
+ void InformDelegateDownloadDataInDelegateThread(
+ scoped_ptr<std::string> download_data);
+
+ URLFetcher* fetcher_; // Corresponding fetcher object
+ GURL original_url_; // The URL we were asked to fetch
+ GURL url_; // The URL we eventually wound up at
+ URLFetcher::RequestType request_type_; // What type of request is this?
+ URLRequestStatus status_; // Status of the request
+ URLFetcherDelegate* delegate_; // Object to notify on completion
+ scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_;
+ // Task runner for the creating thread.
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+ // Task runner for download file access.
+ scoped_refptr<base::TaskRunner> file_task_runner_;
+ // Task runner for upload file access.
+ scoped_refptr<base::TaskRunner> upload_file_task_runner_;
+ scoped_ptr<URLRequest> request_; // The actual request this wraps
+ int load_flags_; // Flags for the load operation
+ int response_code_; // HTTP status code for the request
+ std::string data_; // Results of the request, when we are
+ // storing the response as a string.
+ scoped_refptr<IOBuffer> buffer_;
+ // Read buffer
+ scoped_refptr<URLRequestContextGetter> request_context_getter_;
+ // Cookie/cache info for the request
+ GURL first_party_for_cookies_; // The first party URL for the request
+ // The user data to add to each newly-created URLRequest.
+ const void* url_request_data_key_;
+ URLFetcher::CreateDataCallback url_request_create_data_callback_;
+ ResponseCookies cookies_; // Response cookies
+ HttpRequestHeaders extra_request_headers_;
+ scoped_refptr<HttpResponseHeaders> response_headers_;
+ bool was_fetched_via_proxy_;
+ HostPortPair socket_address_;
+
+ bool upload_content_set_; // SetUploadData has been called
+ std::string upload_content_; // HTTP POST payload
+ base::FilePath upload_file_path_; // Path to file containing POST payload
+ uint64 upload_range_offset_; // Offset from the beginning of the file
+ // to be uploaded.
+ uint64 upload_range_length_; // The length of the part of file to be
+ // uploaded.
+ std::string upload_content_type_; // MIME type of POST payload
+ std::string referrer_; // HTTP Referer header value
+ bool is_chunked_upload_; // True if using chunked transfer encoding
+
+ // Used to determine how long to wait before making a request or doing a
+ // retry.
+ //
+ // Both of them can only be accessed on the IO thread.
+ //
+ // We need not only the throttler entry for |original_URL|, but also
+ // the one for |url|. For example, consider the case that URL A
+ // redirects to URL B, for which the server returns a 500
+ // response. In this case, the exponential back-off release time of
+ // URL A won't increase. If we retry without considering the
+ // back-off constraint of URL B, we may send out too many requests
+ // for URL A in a short period of time.
+ //
+ // Both of these will be NULL if
+ // URLRequestContext::throttler_manager() is NULL.
+ scoped_refptr<URLRequestThrottlerEntryInterface>
+ original_url_throttler_entry_;
+ scoped_refptr<URLRequestThrottlerEntryInterface> url_throttler_entry_;
+
+ // True if the URLFetcher has been cancelled.
+ bool was_cancelled_;
+
+ // Writer object to write response to the destination like file and string.
+ scoped_ptr<URLFetcherResponseWriter> response_writer_;
+
+ // If writing results to a file, |file_writer_| will manage creation,
+ // writing, and destruction of that file.
+ // |file_writer_| points to the same object as |response_writer_| when writing
+ // response to a file, otherwise, |file_writer_| is NULL.
+ URLFetcherFileWriter* file_writer_;
+
+ // Where should responses be saved?
+ ResponseDestinationType response_destination_;
+
+ // Path to the file where the response is written.
+ base::FilePath response_destination_file_path_;
+
+ // By default any server-initiated redirects are automatically followed. If
+ // this flag is set to true, however, a redirect will halt the fetch and call
+ // back to to the delegate immediately.
+ bool stop_on_redirect_;
+ // True when we're actually stopped due to a redirect halted by the above. We
+ // use this to ensure that |url_| is set to the redirect destination rather
+ // than the originally-fetched URL.
+ bool stopped_on_redirect_;
+
+ // If |automatically_retry_on_5xx_| is false, 5xx responses will be
+ // propagated to the observer, if it is true URLFetcher will automatically
+ // re-execute the request, after the back-off delay has expired.
+ // true by default.
+ bool automatically_retry_on_5xx_;
+ // |num_retries_on_5xx_| indicates how many times we've failed to successfully
+ // fetch this URL due to 5xx responses. Once this value exceeds the maximum
+ // number of retries specified by the owner URLFetcher instance,
+ // we'll give up.
+ int num_retries_on_5xx_;
+ // Maximum retries allowed when 5xx responses are received.
+ int max_retries_on_5xx_;
+ // Back-off time delay. 0 by default.
+ base::TimeDelta backoff_delay_;
+
+ // The number of retries that have been attempted due to ERR_NETWORK_CHANGED.
+ int num_retries_on_network_changes_;
+ // Maximum retries allowed when the request fails with ERR_NETWORK_CHANGED.
+ // 0 by default.
+ int max_retries_on_network_changes_;
+
+ // Timer to poll the progress of uploading for POST and PUT requests.
+ // When crbug.com/119629 is fixed, scoped_ptr is not necessary here.
+ scoped_ptr<base::RepeatingTimer<URLFetcherCore> >
+ upload_progress_checker_timer_;
+ // Number of bytes sent so far.
+ int64 current_upload_bytes_;
+ // Number of bytes received so far.
+ int64 current_response_bytes_;
+ // Total expected bytes to receive (-1 if it cannot be determined).
+ int64 total_response_bytes_;
+
+ // TODO(willchan): Get rid of this after debugging crbug.com/90971.
+ base::debug::StackTrace stack_trace_;
+
+ static base::LazyInstance<Registry> g_registry;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherCore);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_CORE_H_
diff --git a/chromium/net/url_request/url_fetcher_delegate.cc b/chromium/net/url_request/url_fetcher_delegate.cc
new file mode 100644
index 00000000000..1cc58e7f3e5
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_delegate.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+
+void URLFetcherDelegate::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 current, int64 total) {}
+
+void URLFetcherDelegate::OnURLFetchDownloadData(
+ const URLFetcher* source, scoped_ptr<std::string> download_data) {}
+
+void URLFetcherDelegate::OnURLFetchUploadProgress(
+ const URLFetcher* source, int64 current, int64 total) {}
+
+bool URLFetcherDelegate::ShouldSendDownloadData() {
+ return false;
+}
+
+URLFetcherDelegate::~URLFetcherDelegate() {}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher_delegate.h b/chromium/net/url_request/url_fetcher_delegate.h
new file mode 100644
index 00000000000..ac7b2a60ef7
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_delegate.h
@@ -0,0 +1,56 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
+#define NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class URLFetcher;
+
+// A delegate interface for users of URLFetcher.
+class NET_EXPORT URLFetcherDelegate {
+ public:
+ // This will be called when the URL has been fetched, successfully or not.
+ // Use accessor methods on |source| to get the results.
+ virtual void OnURLFetchComplete(const URLFetcher* source) = 0;
+
+ // This will be called when some part of the response is read. |current|
+ // denotes the number of bytes received up to the call, and |total| is the
+ // expected total size of the response (or -1 if not determined).
+ virtual void OnURLFetchDownloadProgress(const URLFetcher* source,
+ int64 current, int64 total);
+
+ // This will be called when some part of the response is read.
+ // |download_data| contains the current bytes received since the last call.
+ // This will be called after ShouldSendDownloadData() and only if the latter
+ // returns true.
+ virtual void OnURLFetchDownloadData(const URLFetcher* source,
+ scoped_ptr<std::string> download_data);
+
+ // This indicates if OnURLFetchDownloadData should be called.
+ // This will be called before OnURLFetchDownloadData is called, and only if
+ // this returns true.
+ // Default implementation is false.
+ virtual bool ShouldSendDownloadData();
+
+ // This will be called when uploading of POST or PUT requests proceeded.
+ // |current| denotes the number of bytes sent so far, and |total| is the
+ // total size of uploading data (or -1 if chunked upload is enabled).
+ virtual void OnURLFetchUploadProgress(const URLFetcher* source,
+ int64 current, int64 total);
+
+ protected:
+ virtual ~URLFetcherDelegate();
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
diff --git a/chromium/net/url_request/url_fetcher_factory.h b/chromium/net/url_request/url_fetcher_factory.h
new file mode 100644
index 00000000000..0c5a5eb1f4f
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
+#define NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
+
+#include "net/url_request/url_fetcher.h"
+
+namespace net {
+class URLFetcherDelegate;
+
+// URLFetcher::Create uses the currently registered Factory to create the
+// URLFetcher. Factory is intended for testing.
+class URLFetcherFactory {
+ public:
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* delegate) = 0;
+
+ protected:
+ virtual ~URLFetcherFactory() {}
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
diff --git a/chromium/net/url_request/url_fetcher_impl.cc b/chromium/net/url_request/url_fetcher_impl.cc
new file mode 100644
index 00000000000..e8f9d32f05a
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_impl.cc
@@ -0,0 +1,221 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_fetcher_impl.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/url_request/url_fetcher_core.h"
+#include "net/url_request/url_fetcher_factory.h"
+
+namespace net {
+
+static URLFetcherFactory* g_factory = NULL;
+
+URLFetcherImpl::URLFetcherImpl(const GURL& url,
+ RequestType request_type,
+ URLFetcherDelegate* d)
+ : core_(new URLFetcherCore(this, url, request_type, d)) {
+}
+
+URLFetcherImpl::~URLFetcherImpl() {
+ core_->Stop();
+}
+
+void URLFetcherImpl::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ core_->SetUploadData(upload_content_type, upload_content);
+}
+
+void URLFetcherImpl::SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ core_->SetUploadFilePath(upload_content_type,
+ file_path,
+ range_offset,
+ range_length,
+ file_task_runner);
+}
+
+void URLFetcherImpl::SetChunkedUpload(const std::string& content_type) {
+ core_->SetChunkedUpload(content_type);
+}
+
+void URLFetcherImpl::AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) {
+ DCHECK(data.length());
+ core_->AppendChunkToUpload(data, is_last_chunk);
+}
+
+void URLFetcherImpl::SetReferrer(const std::string& referrer) {
+ core_->SetReferrer(referrer);
+}
+
+void URLFetcherImpl::SetLoadFlags(int load_flags) {
+ core_->SetLoadFlags(load_flags);
+}
+
+int URLFetcherImpl::GetLoadFlags() const {
+ return core_->GetLoadFlags();
+}
+
+void URLFetcherImpl::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ core_->SetExtraRequestHeaders(extra_request_headers);
+}
+
+void URLFetcherImpl::AddExtraRequestHeader(const std::string& header_line) {
+ core_->AddExtraRequestHeader(header_line);
+}
+
+void URLFetcherImpl::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ GetExtraRequestHeaders(headers);
+}
+
+void URLFetcherImpl::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+ core_->SetRequestContext(request_context_getter);
+}
+
+void URLFetcherImpl::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+ core_->SetFirstPartyForCookies(first_party_for_cookies);
+}
+
+void URLFetcherImpl::SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) {
+ core_->SetURLRequestUserData(key, create_data_callback);
+}
+
+void URLFetcherImpl::SetStopOnRedirect(bool stop_on_redirect) {
+ core_->SetStopOnRedirect(stop_on_redirect);
+}
+
+void URLFetcherImpl::SetAutomaticallyRetryOn5xx(bool retry) {
+ core_->SetAutomaticallyRetryOn5xx(retry);
+}
+
+void URLFetcherImpl::SetMaxRetriesOn5xx(int max_retries) {
+ core_->SetMaxRetriesOn5xx(max_retries);
+}
+
+int URLFetcherImpl::GetMaxRetriesOn5xx() const {
+ return core_->GetMaxRetriesOn5xx();
+}
+
+
+base::TimeDelta URLFetcherImpl::GetBackoffDelay() const {
+ return core_->GetBackoffDelay();
+}
+
+void URLFetcherImpl::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+ core_->SetAutomaticallyRetryOnNetworkChanges(max_retries);
+}
+
+void URLFetcherImpl::SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ core_->SaveResponseToFileAtPath(file_path, file_task_runner);
+}
+
+void URLFetcherImpl::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ core_->SaveResponseToTemporaryFile(file_task_runner);
+}
+
+HttpResponseHeaders* URLFetcherImpl::GetResponseHeaders() const {
+ return core_->GetResponseHeaders();
+}
+
+HostPortPair URLFetcherImpl::GetSocketAddress() const {
+ return core_->GetSocketAddress();
+}
+
+bool URLFetcherImpl::WasFetchedViaProxy() const {
+ return core_->WasFetchedViaProxy();
+}
+
+void URLFetcherImpl::Start() {
+ core_->Start();
+}
+
+const GURL& URLFetcherImpl::GetOriginalURL() const {
+ return core_->GetOriginalURL();
+}
+
+const GURL& URLFetcherImpl::GetURL() const {
+ return core_->GetURL();
+}
+
+const URLRequestStatus& URLFetcherImpl::GetStatus() const {
+ return core_->GetStatus();
+}
+
+int URLFetcherImpl::GetResponseCode() const {
+ return core_->GetResponseCode();
+}
+
+const ResponseCookies& URLFetcherImpl::GetCookies() const {
+ return core_->GetCookies();
+}
+
+bool URLFetcherImpl::FileErrorOccurred(int* out_error_code) const {
+ return core_->FileErrorOccurred(out_error_code);
+}
+
+void URLFetcherImpl::ReceivedContentWasMalformed() {
+ core_->ReceivedContentWasMalformed();
+}
+
+bool URLFetcherImpl::GetResponseAsString(
+ std::string* out_response_string) const {
+ return core_->GetResponseAsString(out_response_string);
+}
+
+bool URLFetcherImpl::GetResponseAsFilePath(
+ bool take_ownership,
+ base::FilePath* out_response_path) const {
+ return core_->GetResponseAsFilePath(take_ownership, out_response_path);
+}
+
+// static
+void URLFetcherImpl::CancelAll() {
+ URLFetcherCore::CancelAll();
+}
+
+// static
+void URLFetcherImpl::SetEnableInterceptionForTests(bool enabled) {
+ URLFetcherCore::SetEnableInterceptionForTests(enabled);
+}
+
+// static
+void URLFetcherImpl::SetIgnoreCertificateRequests(bool ignored) {
+ URLFetcherCore::SetIgnoreCertificateRequests(ignored);
+}
+
+// static
+int URLFetcherImpl::GetNumFetcherCores() {
+ return URLFetcherCore::GetNumFetcherCores();
+}
+
+URLFetcherDelegate* URLFetcherImpl::delegate() const {
+ return core_->delegate();
+}
+
+// static
+URLFetcherFactory* URLFetcherImpl::factory() {
+ return g_factory;
+}
+
+// static
+void URLFetcherImpl::set_factory(URLFetcherFactory* factory) {
+ g_factory = factory;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher_impl.h b/chromium/net/url_request/url_fetcher_impl.h
new file mode 100644
index 00000000000..d643a94580f
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_impl.h
@@ -0,0 +1,128 @@
+// Copyright (c) 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.
+
+// This file contains URLFetcher, a wrapper around URLRequest that handles
+// low-level details like thread safety, ref counting, and incremental buffer
+// reading. This is useful for callers who simply want to get the data from a
+// URL and don't care about all the nitty-gritty details.
+//
+// NOTE(willchan): Only one "IO" thread is supported for URLFetcher. This is a
+// temporary situation. We will work on allowing support for multiple "io"
+// threads per process.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_IMPL_H_
+#define NET_URL_REQUEST_URL_FETCHER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_fetcher.h"
+
+namespace net {
+class URLFetcherCore;
+class URLFetcherDelegate;
+class URLFetcherFactory;
+
+class NET_EXPORT_PRIVATE URLFetcherImpl : public URLFetcher {
+ public:
+ // |url| is the URL to send the request to.
+ // |request_type| is the type of request to make.
+ // |d| the object that will receive the callback on fetch completion.
+ URLFetcherImpl(const GURL& url,
+ RequestType request_type,
+ URLFetcherDelegate* d);
+ virtual ~URLFetcherImpl();
+
+ // URLFetcher implementation:
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) OVERRIDE;
+ virtual void SetUploadFilePath(
+ const std::string& upload_content_type,
+ const base::FilePath& file_path,
+ uint64 range_offset,
+ uint64 range_length,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SetChunkedUpload(
+ const std::string& upload_content_type) OVERRIDE;
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) OVERRIDE;
+ virtual void SetLoadFlags(int load_flags) OVERRIDE;
+ virtual int GetLoadFlags() const OVERRIDE;
+ virtual void SetReferrer(const std::string& referrer) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) OVERRIDE;
+ virtual void AddExtraRequestHeader(const std::string& header_line) OVERRIDE;
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) OVERRIDE;
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) OVERRIDE;
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) OVERRIDE;
+ virtual void SetStopOnRedirect(bool stop_on_redirect) OVERRIDE;
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) OVERRIDE;
+ virtual void SetMaxRetriesOn5xx(int max_retries) OVERRIDE;
+ virtual int GetMaxRetriesOn5xx() const OVERRIDE;
+ virtual base::TimeDelta GetBackoffDelay() const OVERRIDE;
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) OVERRIDE;
+ virtual void SaveResponseToFileAtPath(
+ const base::FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual HttpResponseHeaders* GetResponseHeaders() const OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual bool WasFetchedViaProxy() const OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual const GURL& GetOriginalURL() const OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const URLRequestStatus& GetStatus() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual const ResponseCookies& GetCookies() const OVERRIDE;
+ virtual bool FileErrorOccurred(int* out_error_code) const OVERRIDE;
+ virtual void ReceivedContentWasMalformed() OVERRIDE;
+ virtual bool GetResponseAsString(
+ std::string* out_response_string) const OVERRIDE;
+ virtual bool GetResponseAsFilePath(
+ bool take_ownership,
+ base::FilePath* out_response_path) const OVERRIDE;
+
+ static void CancelAll();
+
+ static void SetEnableInterceptionForTests(bool enabled);
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ // TODO(akalin): Make these private again once URLFetcher::Create()
+ // is in net/.
+
+ static URLFetcherFactory* factory();
+
+ // Sets the factory used by the static method Create to create a URLFetcher.
+ // URLFetcher does not take ownership of |factory|. A value of NULL results
+ // in a URLFetcher being created directly.
+ //
+ // NOTE: for safety, this should only be used through ScopedURLFetcherFactory!
+ static void set_factory(URLFetcherFactory* factory);
+
+ protected:
+ // Returns the delegate.
+ URLFetcherDelegate* delegate() const;
+
+ private:
+ friend class URLFetcherTest;
+
+ // Only used by URLFetcherTest, returns the number of URLFetcher::Core objects
+ // actively running.
+ static int GetNumFetcherCores();
+
+ const scoped_refptr<URLFetcherCore> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherImpl);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_IMPL_H_
diff --git a/chromium/net/url_request/url_fetcher_impl_unittest.cc b/chromium/net/url_request/url_fetcher_impl_unittest.cc
new file mode 100644
index 00000000000..7cc1674b181
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_impl_unittest.cc
@@ -0,0 +1,1541 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_fetcher_impl.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "crypto/nss_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_response_headers.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include "net/ocsp/nss_ocsp.h"
+#endif
+
+namespace net {
+
+using base::Time;
+using base::TimeDelta;
+
+// TODO(eroman): Add a regression test for http://crbug.com/40505.
+
+namespace {
+
+// TODO(akalin): Move all the test data to somewhere under net/.
+const base::FilePath::CharType kDocRoot[] =
+ FILE_PATH_LITERAL("chrome/test/data");
+const char kTestServerFilePrefix[] = "files/";
+
+class ThrottlingTestURLRequestContext : public TestURLRequestContext {
+ public:
+ ThrottlingTestURLRequestContext() : TestURLRequestContext(true) {
+ set_throttler_manager(&throttler_manager_);
+ Init();
+ DCHECK(throttler_manager() != NULL);
+ }
+
+ private:
+ URLRequestThrottlerManager throttler_manager_;
+};
+
+class ThrottlingTestURLRequestContextGetter
+ : public TestURLRequestContextGetter {
+ public:
+ ThrottlingTestURLRequestContextGetter(
+ base::MessageLoopProxy* io_message_loop_proxy,
+ TestURLRequestContext* request_context)
+ : TestURLRequestContextGetter(io_message_loop_proxy),
+ context_(request_context) {
+ }
+
+ // TestURLRequestContextGetter:
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE {
+ return context_;
+ }
+
+ protected:
+ virtual ~ThrottlingTestURLRequestContextGetter() {}
+
+ TestURLRequestContext* const context_;
+};
+
+} // namespace
+
+class URLFetcherTest : public testing::Test,
+ public URLFetcherDelegate {
+ public:
+ URLFetcherTest() : fetcher_(NULL) {}
+
+ static int GetNumFetcherCores() {
+ return URLFetcherImpl::GetNumFetcherCores();
+ }
+
+ // Creates a URLFetcher, using the program's main thread to do IO.
+ virtual void CreateFetcher(const GURL& url);
+
+ // URLFetcherDelegate:
+ // Subclasses that override this should either call this function or
+ // CleanupAfterFetchComplete() at the end of their processing, depending on
+ // whether they want to check for a non-empty HTTP 200 response or not.
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ // Deletes |fetcher| and terminates the message loop.
+ void CleanupAfterFetchComplete();
+
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy() {
+ return io_message_loop_proxy_;
+ }
+
+ TestURLRequestContext* request_context() {
+ return context_.get();
+ }
+
+ protected:
+ // testing::Test:
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+
+ context_.reset(new ThrottlingTestURLRequestContext());
+ io_message_loop_proxy_ = base::MessageLoopProxy::current();
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ crypto::EnsureNSSInit();
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ virtual void TearDown() OVERRIDE {
+#if defined(USE_NSS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+
+ // URLFetcher is designed to run on the main UI thread, but in our tests
+ // we assume that the current thread is the IO thread where the URLFetcher
+ // dispatches its requests to. When we wish to simulate being used from
+ // a UI thread, we dispatch a worker thread to do so.
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_;
+
+ URLFetcherImpl* fetcher_;
+ scoped_ptr<TestURLRequestContext> context_;
+};
+
+// A test fixture that uses a MockHostResolver, so that name resolutions can
+// be manipulated by the tests to keep connections in the resolving state.
+class URLFetcherMockDnsTest : public URLFetcherTest {
+ public:
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ GURL test_url_;
+ scoped_ptr<SpawnedTestServer> test_server_;
+ MockHostResolver resolver_;
+ scoped_ptr<URLFetcher> completed_fetcher_;
+};
+
+void URLFetcherTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->Start();
+}
+
+void URLFetcherTest::OnURLFetchComplete(const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherTest::CleanupAfterFetchComplete() {
+ delete fetcher_; // Have to delete this here and not in the destructor,
+ // because the destructor won't necessarily run on the
+ // same thread that CreateFetcher() did.
+
+ io_message_loop_proxy()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ // If the current message loop is not the IO loop, it will be shut down when
+ // the main loop returns and this thread subsequently goes out of scope.
+}
+
+void URLFetcherMockDnsTest::SetUp() {
+ URLFetcherTest::SetUp();
+
+ resolver_.set_ondemand_mode(true);
+ resolver_.rules()->AddRule("example.com", "127.0.0.1");
+
+ context_.reset(new TestURLRequestContext(true));
+ context_->set_host_resolver(&resolver_);
+ context_->Init();
+
+ test_server_.reset(new SpawnedTestServer(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot)));
+ ASSERT_TRUE(test_server_->Start());
+
+ // test_server_.GetURL() returns a URL with 127.0.0.1 (kLocalhost), that is
+ // immediately resolved by the MockHostResolver. Use a hostname instead to
+ // trigger an async resolve.
+ test_url_ = GURL(
+ base::StringPrintf("http://example.com:%d/defaultresponse",
+ test_server_->host_port_pair().port()));
+ ASSERT_TRUE(test_url_.is_valid());
+}
+
+void URLFetcherMockDnsTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+}
+
+void URLFetcherMockDnsTest::OnURLFetchComplete(const URLFetcher* source) {
+ io_message_loop_proxy()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ ASSERT_EQ(fetcher_, source);
+ EXPECT_EQ(test_url_, source->GetOriginalURL());
+ completed_fetcher_.reset(fetcher_);
+}
+
+namespace {
+
+// Version of URLFetcherTest that does a POST instead
+class URLFetcherPostTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that does a POST of a file using
+// SetUploadDataStream
+class URLFetcherPostFileTest : public URLFetcherTest {
+ public:
+ URLFetcherPostFileTest();
+
+ void SetUploadRange(uint64 range_offset, uint64 range_length) {
+ range_offset_ = range_offset;
+ range_length_ = range_length;
+ }
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ private:
+ base::FilePath path_;
+ uint64 range_offset_;
+ uint64 range_length_;
+};
+
+// Version of URLFetcherTest that does a POST instead with empty upload body
+class URLFetcherEmptyPostTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that tests download progress reports.
+class URLFetcherDownloadProgressTest : public URLFetcherTest {
+ public:
+ URLFetcherDownloadProgressTest()
+ : previous_progress_(0),
+ expected_total_(0) {
+ }
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchDownloadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+
+ protected:
+ // Download progress returned by the previous callback.
+ int64 previous_progress_;
+ // Size of the file being downloaded, known in advance (provided by each test
+ // case).
+ int64 expected_total_;
+};
+
+// Version of URLFetcherTest that tests progress reports at cancellation.
+class URLFetcherDownloadProgressCancelTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ virtual void OnURLFetchDownloadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+ protected:
+ bool cancelled_;
+};
+
+// Version of URLFetcherTest that tests upload progress reports.
+class URLFetcherUploadProgressTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchUploadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+ protected:
+ int64 previous_progress_;
+ std::string chunk_;
+ int64 number_of_chunks_added_;
+};
+
+// Version of URLFetcherTest that tests headers.
+class URLFetcherHeadersTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that tests SocketAddress.
+class URLFetcherSocketAddressTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ protected:
+ std::string expected_host_;
+ uint16 expected_port_;
+};
+
+// Version of URLFetcherTest that tests stopping on a redirect.
+class URLFetcherStopOnRedirectTest : public URLFetcherTest {
+ public:
+ URLFetcherStopOnRedirectTest();
+ virtual ~URLFetcherStopOnRedirectTest();
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ // The URL we should be redirected to.
+ static const char* kRedirectTarget;
+
+ bool callback_called_; // Set to true in OnURLFetchComplete().
+};
+
+// Version of URLFetcherTest that tests overload protection.
+class URLFetcherProtectTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ Time start_time_;
+};
+
+// Version of URLFetcherTest that tests overload protection, when responses
+// passed through.
+class URLFetcherProtectTestPassedThrough : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ Time start_time_;
+};
+
+// Version of URLFetcherTest that tests bad HTTPS requests.
+class URLFetcherBadHTTPSTest : public URLFetcherTest {
+ public:
+ URLFetcherBadHTTPSTest();
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ private:
+ base::FilePath cert_dir_;
+};
+
+// Version of URLFetcherTest that tests request cancellation on shutdown.
+class URLFetcherCancelTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ void CancelRequest();
+};
+
+// Version of TestURLRequestContext that posts a Quit task to the IO
+// thread once it is deleted.
+class CancelTestURLRequestContext : public ThrottlingTestURLRequestContext {
+ public:
+ explicit CancelTestURLRequestContext() {
+ }
+
+ private:
+ virtual ~CancelTestURLRequestContext() {
+ // The d'tor should execute in the IO thread. Post the quit task to the
+ // current thread.
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ }
+};
+
+class CancelTestURLRequestContextGetter
+ : public TestURLRequestContextGetter {
+ public:
+ CancelTestURLRequestContextGetter(
+ base::MessageLoopProxy* io_message_loop_proxy,
+ const GURL& throttle_for_url)
+ : TestURLRequestContextGetter(io_message_loop_proxy),
+ io_message_loop_proxy_(io_message_loop_proxy),
+ context_created_(false, false),
+ throttle_for_url_(throttle_for_url) {
+ }
+
+ // TestURLRequestContextGetter:
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE {
+ if (!context_.get()) {
+ context_.reset(new CancelTestURLRequestContext());
+ DCHECK(context_->throttler_manager());
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // The initial backoff is 2 seconds and maximum backoff is 4 seconds.
+ // Maximum retries allowed is set to 2.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(context_->throttler_manager(),
+ std::string(),
+ 200,
+ 3,
+ 2000,
+ 2.0,
+ 0.0,
+ 4000));
+ context_->throttler_manager()
+ ->OverrideEntryForTests(throttle_for_url_, entry.get());
+
+ context_created_.Signal();
+ }
+ return context_.get();
+ }
+
+ virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const {
+ return io_message_loop_proxy_;
+ }
+
+ void WaitForContextCreation() {
+ context_created_.Wait();
+ }
+
+ protected:
+ virtual ~CancelTestURLRequestContextGetter() {}
+
+ private:
+ scoped_ptr<TestURLRequestContext> context_;
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_;
+ base::WaitableEvent context_created_;
+ GURL throttle_for_url_;
+};
+
+// Version of URLFetcherTest that tests retying the same request twice.
+class URLFetcherMultipleAttemptTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ std::string data_;
+};
+
+class URLFetcherFileTest : public URLFetcherTest {
+ public:
+ URLFetcherFileTest() : take_ownership_of_file_(false),
+ expected_file_error_(OK) {}
+
+ void CreateFetcherForFile(const GURL& url, const base::FilePath& file_path);
+ void CreateFetcherForTempFile(const GURL& url);
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ base::FilePath expected_file_;
+ base::FilePath file_path_;
+
+ // Set by the test. Used in OnURLFetchComplete() to decide if
+ // the URLFetcher should own the temp file, so that we can test
+ // disowning prevents the file from being deleted.
+ bool take_ownership_of_file_;
+
+ // Expected file error code for the test. OK when expecting success.
+ int expected_file_error_;
+};
+
+void URLFetcherPostTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->SetUploadData("application/x-www-form-urlencoded", "bobsyeruncle");
+ fetcher_->Start();
+}
+
+void URLFetcherPostTest::OnURLFetchComplete(const URLFetcher* source) {
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_EQ(std::string("bobsyeruncle"), data);
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+URLFetcherPostFileTest::URLFetcherPostFileTest()
+ : range_offset_(0),
+ range_length_(kuint64max) {
+ PathService::Get(base::DIR_SOURCE_ROOT, &path_);
+ path_ = path_.Append(FILE_PATH_LITERAL("net"));
+ path_ = path_.Append(FILE_PATH_LITERAL("data"));
+ path_ = path_.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path_ = path_.Append(FILE_PATH_LITERAL("BullRunSpeech.txt"));
+}
+
+void URLFetcherPostFileTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->SetUploadFilePath("application/x-www-form-urlencoded",
+ path_,
+ range_offset_,
+ range_length_,
+ base::MessageLoopProxy::current());
+ fetcher_->Start();
+}
+
+void URLFetcherPostFileTest::OnURLFetchComplete(const URLFetcher* source) {
+ std::string expected;
+ ASSERT_TRUE(file_util::ReadFileToString(path_, &expected));
+ ASSERT_LE(range_offset_, expected.size());
+ uint64 expected_size =
+ std::min(range_length_, expected.size() - range_offset_);
+
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_EQ(expected.substr(range_offset_, expected_size), data);
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+void URLFetcherEmptyPostTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new TestURLRequestContextGetter(
+ io_message_loop_proxy()));
+ fetcher_->SetUploadData("text/plain", std::string());
+ fetcher_->Start();
+}
+
+void URLFetcherEmptyPostTest::OnURLFetchComplete(const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_TRUE(data.empty());
+
+ CleanupAfterFetchComplete();
+ // Do not call the super class method URLFetcherTest::OnURLFetchComplete,
+ // since it expects a non-empty response.
+}
+
+void URLFetcherDownloadProgressTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->Start();
+}
+
+void URLFetcherDownloadProgressTest::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 progress, int64 total) {
+ // Increasing between 0 and total.
+ EXPECT_LE(0, progress);
+ EXPECT_GE(total, progress);
+ EXPECT_LE(previous_progress_, progress);
+ EXPECT_EQ(expected_total_, total);
+ previous_progress_ = progress;
+}
+
+void URLFetcherDownloadProgressCancelTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ cancelled_ = false;
+ fetcher_->Start();
+}
+
+void URLFetcherDownloadProgressCancelTest::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 current, int64 total) {
+ EXPECT_FALSE(cancelled_);
+ if (!cancelled_) {
+ cancelled_ = true;
+ CleanupAfterFetchComplete();
+ }
+}
+
+void URLFetcherDownloadProgressCancelTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // Should have been cancelled.
+ ADD_FAILURE();
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherUploadProgressTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ previous_progress_ = 0;
+ // Large enough data to require more than one read from UploadDataStream.
+ chunk_.assign(1<<16, 'a');
+ // Use chunked upload to wait for a timer event of progress notification.
+ fetcher_->SetChunkedUpload("application/x-www-form-urlencoded");
+ fetcher_->Start();
+ number_of_chunks_added_ = 1;
+ fetcher_->AppendChunkToUpload(chunk_, false);
+}
+
+void URLFetcherUploadProgressTest::OnURLFetchUploadProgress(
+ const URLFetcher* source, int64 current, int64 total) {
+ // Increasing between 0 and total.
+ EXPECT_LE(0, current);
+ EXPECT_GE(static_cast<int64>(chunk_.size()) * number_of_chunks_added_,
+ current);
+ EXPECT_LE(previous_progress_, current);
+ previous_progress_ = current;
+ EXPECT_EQ(-1, total);
+
+ if (number_of_chunks_added_ < 2) {
+ number_of_chunks_added_ += 1;
+ fetcher_->AppendChunkToUpload(chunk_, true);
+ }
+}
+
+void URLFetcherHeadersTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ std::string header;
+ EXPECT_TRUE(source->GetResponseHeaders()->GetNormalizedHeader("cache-control",
+ &header));
+ EXPECT_EQ("private", header);
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+void URLFetcherSocketAddressTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ EXPECT_EQ("127.0.0.1", source->GetSocketAddress().host());
+ EXPECT_EQ(expected_port_, source->GetSocketAddress().port());
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+// static
+const char* URLFetcherStopOnRedirectTest::kRedirectTarget =
+ "http://redirect.target.com";
+
+URLFetcherStopOnRedirectTest::URLFetcherStopOnRedirectTest()
+ : callback_called_(false) {
+}
+
+URLFetcherStopOnRedirectTest::~URLFetcherStopOnRedirectTest() {
+}
+
+void URLFetcherStopOnRedirectTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->SetStopOnRedirect(true);
+ fetcher_->Start();
+}
+
+void URLFetcherStopOnRedirectTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ callback_called_ = true;
+ EXPECT_EQ(GURL(kRedirectTarget), source->GetURL());
+ EXPECT_EQ(URLRequestStatus::CANCELED, source->GetStatus().status());
+ EXPECT_EQ(ERR_ABORTED, source->GetStatus().error());
+ EXPECT_EQ(301, source->GetResponseCode());
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherProtectTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ start_time_ = Time::Now();
+ fetcher_->SetMaxRetriesOn5xx(11);
+ fetcher_->Start();
+}
+
+void URLFetcherProtectTest::OnURLFetchComplete(const URLFetcher* source) {
+ const TimeDelta one_second = TimeDelta::FromMilliseconds(1000);
+ if (source->GetResponseCode() >= 500) {
+ // Now running ServerUnavailable test.
+ // It takes more than 1 second to finish all 11 requests.
+ EXPECT_TRUE(Time::Now() - start_time_ >= one_second);
+ EXPECT_TRUE(source->GetStatus().is_success());
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ CleanupAfterFetchComplete();
+ } else {
+ // Now running Overload test.
+ static int count = 0;
+ count++;
+ if (count < 20) {
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->Start();
+ } else {
+ // We have already sent 20 requests continuously. And we expect that
+ // it takes more than 1 second due to the overload protection settings.
+ EXPECT_TRUE(Time::Now() - start_time_ >= one_second);
+ URLFetcherTest::OnURLFetchComplete(source);
+ }
+ }
+}
+
+void URLFetcherProtectTestPassedThrough::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->SetAutomaticallyRetryOn5xx(false);
+ start_time_ = Time::Now();
+ fetcher_->SetMaxRetriesOn5xx(11);
+ fetcher_->Start();
+}
+
+void URLFetcherProtectTestPassedThrough::OnURLFetchComplete(
+ const URLFetcher* source) {
+ const TimeDelta one_minute = TimeDelta::FromMilliseconds(60000);
+ if (source->GetResponseCode() >= 500) {
+ // Now running ServerUnavailable test.
+ // It should get here on the first attempt, so almost immediately and
+ // *not* to attempt to execute all 11 requests (2.5 minutes).
+ EXPECT_TRUE(Time::Now() - start_time_ < one_minute);
+ EXPECT_TRUE(source->GetStatus().is_success());
+ // Check that suggested back off time is bigger than 0.
+ EXPECT_GT(fetcher_->GetBackoffDelay().InMicroseconds(), 0);
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ } else {
+ // We should not get here!
+ ADD_FAILURE();
+ }
+
+ CleanupAfterFetchComplete();
+}
+
+
+URLFetcherBadHTTPSTest::URLFetcherBadHTTPSTest() {
+ PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_);
+ cert_dir_ = cert_dir_.AppendASCII("chrome");
+ cert_dir_ = cert_dir_.AppendASCII("test");
+ cert_dir_ = cert_dir_.AppendASCII("data");
+ cert_dir_ = cert_dir_.AppendASCII("ssl");
+ cert_dir_ = cert_dir_.AppendASCII("certificates");
+}
+
+// The "server certificate expired" error should result in automatic
+// cancellation of the request by
+// URLRequest::Delegate::OnSSLCertificateError.
+void URLFetcherBadHTTPSTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // This part is different from URLFetcherTest::OnURLFetchComplete
+ // because this test expects the request to be cancelled.
+ EXPECT_EQ(URLRequestStatus::CANCELED, source->GetStatus().status());
+ EXPECT_EQ(ERR_ABORTED, source->GetStatus().error());
+ EXPECT_EQ(-1, source->GetResponseCode());
+ EXPECT_TRUE(source->GetCookies().empty());
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_TRUE(data.empty());
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherCancelTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ CancelTestURLRequestContextGetter* context_getter =
+ new CancelTestURLRequestContextGetter(io_message_loop_proxy().get(), url);
+ fetcher_->SetRequestContext(context_getter);
+ fetcher_->SetMaxRetriesOn5xx(2);
+ fetcher_->Start();
+ // We need to wait for the creation of the URLRequestContext, since we
+ // rely on it being destroyed as a signal to end the test.
+ context_getter->WaitForContextCreation();
+ CancelRequest();
+}
+
+void URLFetcherCancelTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // We should have cancelled the request before completion.
+ ADD_FAILURE();
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherCancelTest::CancelRequest() {
+ delete fetcher_;
+ // The URLFetcher's test context will post a Quit task once it is
+ // deleted. So if this test simply hangs, it means cancellation
+ // did not work.
+}
+
+void URLFetcherMultipleAttemptTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ if (!data.empty() && data_.empty()) {
+ data_ = data;
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+ fetcher_->Start();
+ } else {
+ EXPECT_EQ(data, data_);
+ CleanupAfterFetchComplete();
+ }
+}
+
+void URLFetcherFileTest::CreateFetcherForFile(const GURL& url,
+ const base::FilePath& file_path) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+
+ // Use the IO message loop to do the file operations in this test.
+ fetcher_->SaveResponseToFileAtPath(file_path, io_message_loop_proxy());
+ fetcher_->Start();
+}
+
+void URLFetcherFileTest::CreateFetcherForTempFile(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy().get(), request_context()));
+
+ // Use the IO message loop to do the file operations in this test.
+ fetcher_->SaveResponseToTemporaryFile(io_message_loop_proxy());
+ fetcher_->Start();
+}
+
+void URLFetcherFileTest::OnURLFetchComplete(const URLFetcher* source) {
+ if (expected_file_error_ == OK) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(source->GetResponseCode(), 200);
+
+ int error_code = OK;
+ EXPECT_FALSE(fetcher_->FileErrorOccurred(&error_code));
+
+ EXPECT_TRUE(source->GetResponseAsFilePath(
+ take_ownership_of_file_, &file_path_));
+
+ EXPECT_TRUE(base::ContentsEqual(expected_file_, file_path_));
+ } else {
+ int error_code = OK;
+ EXPECT_TRUE(fetcher_->FileErrorOccurred(&error_code));
+ EXPECT_EQ(expected_file_error_, error_code);
+ }
+ CleanupAfterFetchComplete();
+}
+
+TEST_F(URLFetcherTest, SameThreadsTest) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create the fetcher on the main thread. Since IO will happen on the main
+ // thread, this will test URLFetcher's ability to do everything on one
+ // thread.
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherTest, DifferentThreadsTest) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create a separate thread that will create the URLFetcher. The current
+ // (main) thread will do the IO, and when the fetch is complete it will
+ // terminate the main thread's message loop; then the other thread's
+ // message loop will be shut down automatically as the thread goes out of
+ // scope.
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherTest::CreateFetcher,
+ base::Unretained(this),
+ test_server.GetURL("defaultresponse")));
+
+ base::MessageLoop::current()->Run();
+}
+
+void CancelAllOnIO() {
+ EXPECT_EQ(1, URLFetcherTest::GetNumFetcherCores());
+ URLFetcherImpl::CancelAll();
+ EXPECT_EQ(0, URLFetcherTest::GetNumFetcherCores());
+}
+
+// Tests to make sure CancelAll() will successfully cancel existing URLFetchers.
+TEST_F(URLFetcherTest, CancelAll) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+ EXPECT_EQ(0, GetNumFetcherCores());
+
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+ io_message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&CancelAllOnIO), base::MessageLoop::QuitClosure());
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ delete fetcher_;
+}
+
+TEST_F(URLFetcherMockDnsTest, DontRetryOnNetworkChangedByDefault) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // And the owner of the fetcher gets the ERR_NETWORK_CHANGED error.
+ EXPECT_EQ(ERR_NETWORK_CHANGED, completed_fetcher_->GetStatus().error());
+}
+
+TEST_F(URLFetcherMockDnsTest, RetryOnNetworkChangedAndFail) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // Make it fail 3 times.
+ for (int i = 0; i < 3; ++i) {
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // But the fetcher retries automatically.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+ }
+
+ // A 4th failure doesn't trigger another retry, and propagates the error
+ // to the owner of the fetcher.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // And the owner of the fetcher gets the ERR_NETWORK_CHANGED error.
+ EXPECT_EQ(ERR_NETWORK_CHANGED, completed_fetcher_->GetStatus().error());
+}
+
+TEST_F(URLFetcherMockDnsTest, RetryOnNetworkChangedAndSucceed) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // Make it fail 3 times.
+ for (int i = 0; i < 3; ++i) {
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // But the fetcher retries automatically.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+ }
+
+ // Now let it succeed by resolving the pending request.
+ resolver_.ResolveAllPending();
+ base::MessageLoop::current()->Run();
+
+ // URLFetcherMockDnsTest::OnURLFetchComplete() will quit the loop.
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // This time the request succeeded.
+ EXPECT_EQ(OK, completed_fetcher_->GetStatus().error());
+ EXPECT_EQ(200, completed_fetcher_->GetResponseCode());
+}
+
+TEST_F(URLFetcherPostTest, Basic) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherPostFileTest, Basic) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherPostFileTest, Range) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ SetUploadRange(30, 100);
+
+ CreateFetcher(test_server.GetURL("echo"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherEmptyPostTest, Basic) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherUploadProgressTest, Basic) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherDownloadProgressTest, Basic) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ // Hardcoded file size - it cannot be easily fetched when a remote http server
+ // is used for testing.
+ static const int64 kFileSize = 19021;
+
+ expected_total_ = kFileSize;
+
+ CreateFetcher(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherDownloadProgressCancelTest, CancelWhileProgressReport) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ CreateFetcher(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherHeadersTest, Headers) {
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("files/with-headers.html"));
+ base::MessageLoop::current()->Run();
+ // The actual tests are in the URLFetcherHeadersTest fixture.
+}
+
+TEST_F(URLFetcherSocketAddressTest, SocketAddress) {
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(test_server.Start());
+ expected_port_ = test_server.host_port_pair().port();
+
+ // Reusing "with-headers.html" but doesn't really matter.
+ CreateFetcher(test_server.GetURL("files/with-headers.html"));
+ base::MessageLoop::current()->Run();
+ // The actual tests are in the URLFetcherSocketAddressTest fixture.
+}
+
+TEST_F(URLFetcherStopOnRedirectTest, StopOnRedirect) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(
+ test_server.GetURL(std::string("server-redirect?") + kRedirectTarget));
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(callback_called_);
+}
+
+TEST_F(URLFetcherProtectTest, Overload) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("defaultresponse"));
+
+ // Registers an entry for test url. It only allows 3 requests to be sent
+ // in 200 milliseconds.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(request_context()->throttler_manager(),
+ std::string(),
+ 200,
+ 3,
+ 1,
+ 2.0,
+ 0.0,
+ 256));
+ request_context()->throttler_manager()
+ ->OverrideEntryForTests(url, entry.get());
+
+ CreateFetcher(url);
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherProtectTest, ServerUnavailable) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // and maximum backoff time is 256 milliseconds.
+ // Maximum retries allowed is set to 11.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(request_context()->throttler_manager(),
+ std::string(),
+ 200,
+ 3,
+ 1,
+ 2.0,
+ 0.0,
+ 256));
+ request_context()->throttler_manager()
+ ->OverrideEntryForTests(url, entry.get());
+
+ CreateFetcher(url);
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherProtectTestPassedThrough, ServerUnavailablePropagateResponse) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // and maximum backoff time is 150000 milliseconds.
+ // Maximum retries allowed is set to 11.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(request_context()->throttler_manager(),
+ std::string(),
+ 200,
+ 3,
+ 100,
+ 2.0,
+ 0.0,
+ 150000));
+ // Total time if *not* for not doing automatic backoff would be 150s.
+ // In reality it should be "as soon as server responds".
+ request_context()->throttler_manager()
+ ->OverrideEntryForTests(url, entry.get());
+
+ CreateFetcher(url);
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherBadHTTPSTest, BadHTTPSTest) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_EXPIRED);
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherCancelTest, ReleasesContext) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Create a separate thread that will create the URLFetcher. The current
+ // (main) thread will do the IO, and when the fetch is complete it will
+ // terminate the main thread's message loop; then the other thread's
+ // message loop will be shut down automatically as the thread goes out of
+ // scope.
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCancelTest::CreateFetcher,
+ base::Unretained(this), url));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherCancelTest, CancelWhileDelayedStartTaskPending) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Register an entry for test url.
+ // Using a sliding window of 4 seconds, and max of 1 request, under a fast
+ // run we expect to have a 4 second delay when posting the Start task.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(request_context()->throttler_manager(),
+ std::string(),
+ 4000,
+ 1,
+ 2000,
+ 2.0,
+ 0.0,
+ 4000));
+ request_context()->throttler_manager()
+ ->OverrideEntryForTests(url, entry.get());
+ // Fake that a request has just started.
+ entry->ReserveSendingTimeForNextRequest(base::TimeTicks());
+
+ // The next request we try to send will be delayed by ~4 seconds.
+ // The slower the test runs, the less the delay will be (since it takes the
+ // time difference from now).
+
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherTest::CreateFetcher, base::Unretained(this), url));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherMultipleAttemptTest, SameData) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create the fetcher on the main thread. Since IO will happen on the main
+ // thread, this will test URLFetcher's ability to do everything on one
+ // thread.
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherFileTest, SmallGet) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ ASSERT_FALSE(base::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+TEST_F(URLFetcherFileTest, LargeGet) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, SavedOutputFileOwnerhisp) {
+ // If the caller takes the ownership of the output file, the file should
+ // persist even after URLFetcher is gone. If not, the file must be deleted.
+ const bool kTake[] = {false, true};
+ for (size_t i = 0; i < arraysize(kTake); ++i) {
+ take_ownership_of_file_ = kTake[i];
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_EQ(kTake[i], base::PathExists(file_path_)) <<
+ "FilePath: " << file_path_.value();
+ }
+}
+
+TEST_F(URLFetcherFileTest, OverwriteExistingFile) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Create a file before trying to fetch.
+ static const char kFileToFetch[] = "simple.html";
+ std::string data(10000, '?'); // Meant to be larger than simple.html.
+ file_path_ = temp_dir.path().AppendASCII(kFileToFetch);
+ ASSERT_EQ(static_cast<int>(data.size()),
+ file_util::WriteFile(file_path_, data.data(), data.size()));
+ ASSERT_TRUE(base::PathExists(file_path_));
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ ASSERT_FALSE(base::ContentsEqual(file_path_, expected_file_));
+
+ // Get a small file.
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ file_path_);
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, TryToOverwriteDirectory) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Create a directory before trying to fetch.
+ static const char kFileToFetch[] = "simple.html";
+ file_path_ = temp_dir.path().AppendASCII(kFileToFetch);
+ ASSERT_TRUE(file_util::CreateDirectory(file_path_));
+ ASSERT_TRUE(base::PathExists(file_path_));
+
+ // Get a small file.
+ expected_file_error_ = ERR_ACCESS_DENIED;
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ file_path_);
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(URLFetcherFileTest, SmallGetToTempFile) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ ASSERT_FALSE(base::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+TEST_F(URLFetcherFileTest, LargeGetToTempFile) {
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, SavedOutputTempFileOwnerhisp) {
+ // If the caller takes the ownership of the temp file, the file should persist
+ // even after URLFetcher is gone. If not, the file must be deleted.
+ const bool kTake[] = {false, true};
+ for (size_t i = 0; i < arraysize(kTake); ++i) {
+ take_ownership_of_file_ = kTake[i];
+
+ SpawnedTestServer test_server(SpawnedTestServer::TYPE_HTTP,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ base::MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_EQ(kTake[i], base::PathExists(file_path_)) <<
+ "FilePath: " << file_path_.value();
+ }
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher_response_writer.cc b/chromium/net/url_request/url_fetcher_response_writer.cc
new file mode 100644
index 00000000000..cb30dad8089
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_response_writer.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2013 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/url_request/url_fetcher_response_writer.h"
+
+#include "base/file_util.h"
+#include "base/location.h"
+#include "base/task_runner.h"
+#include "base/task_runner_util.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+URLFetcherStringWriter::URLFetcherStringWriter(std::string* string)
+ : string_(string) {
+}
+
+URLFetcherStringWriter::~URLFetcherStringWriter() {
+}
+
+int URLFetcherStringWriter::Initialize(const CompletionCallback& callback) {
+ // Do nothing.
+ return OK;
+}
+
+int URLFetcherStringWriter::Write(IOBuffer* buffer,
+ int num_bytes,
+ const CompletionCallback& callback) {
+ string_->append(buffer->data(), num_bytes);
+ return num_bytes;
+}
+
+int URLFetcherStringWriter::Finish(const CompletionCallback& callback) {
+ // Do nothing.
+ return OK;
+}
+
+URLFetcherFileWriter::URLFetcherFileWriter(
+ scoped_refptr<base::TaskRunner> file_task_runner)
+ : error_code_(OK),
+ weak_factory_(this),
+ file_task_runner_(file_task_runner),
+ owns_file_(false),
+ total_bytes_written_(0) {
+ DCHECK(file_task_runner_.get());
+}
+
+URLFetcherFileWriter::~URLFetcherFileWriter() {
+ CloseAndDeleteFile();
+}
+
+int URLFetcherFileWriter::Initialize(const CompletionCallback& callback) {
+ DCHECK(!file_stream_);
+ DCHECK(!owns_file_);
+
+ file_stream_.reset(new FileStream(NULL));
+
+ int result = ERR_IO_PENDING;
+ if (file_path_.empty()) {
+ base::FilePath* temp_file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ file_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&file_util::CreateTemporaryFile, temp_file_path),
+ base::Bind(&URLFetcherFileWriter::DidCreateTempFile,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(temp_file_path)));
+ } else {
+ result = file_stream_->Open(
+ file_path_,
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_CREATE_ALWAYS,
+ base::Bind(&URLFetcherFileWriter::DidOpenFile,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ DCHECK_NE(OK, result);
+ if (result != ERR_IO_PENDING)
+ error_code_ = result;
+ }
+ return result;
+}
+
+int URLFetcherFileWriter::Write(IOBuffer* buffer,
+ int num_bytes,
+ const CompletionCallback& callback) {
+ DCHECK(file_stream_);
+ DCHECK(owns_file_);
+
+ int result = file_stream_->Write(buffer, num_bytes,
+ base::Bind(&URLFetcherFileWriter::DidWrite,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result < 0 && result != ERR_IO_PENDING) {
+ error_code_ = result;
+ CloseAndDeleteFile();
+ }
+ return result;
+}
+
+int URLFetcherFileWriter::Finish(const CompletionCallback& callback) {
+ file_stream_.reset();
+ return OK;
+}
+
+void URLFetcherFileWriter::DidWrite(const CompletionCallback& callback,
+ int result) {
+ if (result < 0) {
+ error_code_ = result;
+ CloseAndDeleteFile();
+ }
+ callback.Run(result);
+}
+
+void URLFetcherFileWriter::DisownFile() {
+ // Disowning is done by the delegate's OnURLFetchComplete method.
+ // The file should be closed by the time that method is called.
+ DCHECK(!file_stream_);
+
+ owns_file_ = false;
+}
+
+void URLFetcherFileWriter::CloseAndDeleteFile() {
+ if (!owns_file_)
+ return;
+
+ file_stream_.reset();
+ DisownFile();
+ file_task_runner_->PostTask(FROM_HERE,
+ base::Bind(base::IgnoreResult(&base::DeleteFile),
+ file_path_,
+ false /* recursive */));
+}
+
+void URLFetcherFileWriter::DidCreateTempFile(const CompletionCallback& callback,
+ base::FilePath* temp_file_path,
+ bool success) {
+ if (!success) {
+ error_code_ = ERR_FILE_NOT_FOUND;
+ callback.Run(error_code_);
+ return;
+ }
+ file_path_ = *temp_file_path;
+ owns_file_ = true;
+ const int result = file_stream_->Open(
+ file_path_,
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_OPEN,
+ base::Bind(&URLFetcherFileWriter::DidOpenFile,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != ERR_IO_PENDING)
+ DidOpenFile(callback, result);
+}
+
+void URLFetcherFileWriter::DidOpenFile(const CompletionCallback& callback,
+ int result) {
+ if (result == OK) {
+ total_bytes_written_ = 0;
+ owns_file_ = true;
+ } else {
+ error_code_ = result;
+ CloseAndDeleteFile();
+ }
+ callback.Run(result);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_fetcher_response_writer.h b/chromium/net/url_request/url_fetcher_response_writer.h
new file mode 100644
index 00000000000..3b7fe9eb546
--- /dev/null
+++ b/chromium/net/url_request/url_fetcher_response_writer.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2013 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_URL_REQUEST_URL_FETCHER_RESPONSE_WRITER_H_
+#define NET_URL_REQUEST_URL_FETCHER_RESPONSE_WRITER_H_
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+
+namespace base {
+class TaskRunner;
+} // namespace base
+
+namespace net {
+
+class DrainableIOBuffer;
+class FileStream;
+class IOBuffer;
+
+// This class encapsulates all state involved in writing URLFetcher response
+// bytes to the destination.
+class URLFetcherResponseWriter {
+ public:
+ virtual ~URLFetcherResponseWriter() {}
+
+ // Initializes this instance. If ERR_IO_PENDING is returned, |callback| will
+ // be run later with the result.
+ virtual int Initialize(const CompletionCallback& callback) = 0;
+
+ // Writes |num_bytes| bytes in |buffer|, and returns the number of bytes
+ // written or an error code. If ERR_IO_PENDING is returned, |callback| will be
+ // run later with the result.
+ virtual int Write(IOBuffer* buffer,
+ int num_bytes,
+ const CompletionCallback& callback) = 0;
+
+ // Finishes writing. If ERR_IO_PENDING is returned, |callback| will be run
+ // later with the result.
+ virtual int Finish(const CompletionCallback& callback) = 0;
+};
+
+// URLFetcherResponseWriter implementation for std::string.
+class URLFetcherStringWriter : public URLFetcherResponseWriter {
+ public:
+ URLFetcherStringWriter(std::string* string);
+ virtual ~URLFetcherStringWriter();
+
+ // URLFetcherResponseWriter overrides:
+ virtual int Initialize(const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buffer,
+ int num_bytes,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Finish(const CompletionCallback& callback) OVERRIDE;
+
+ private:
+ std::string* const string_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherStringWriter);
+};
+
+// URLFetcherResponseWriter implementation for files.
+class URLFetcherFileWriter : public URLFetcherResponseWriter {
+ public:
+ URLFetcherFileWriter(scoped_refptr<base::TaskRunner> file_task_runner);
+ virtual ~URLFetcherFileWriter();
+
+ // Sets destination file path.
+ // Call this method before Initialize() to set the destination path,
+ // if this method is not called before Initialize(), Initialize() will create
+ // a temporary file.
+ void set_destination_file_path(const base::FilePath& file_path) {
+ file_path_ = file_path;
+ }
+
+ // URLFetcherResponseWriter overrides:
+ virtual int Initialize(const CompletionCallback& callback) OVERRIDE;
+ virtual int Write(IOBuffer* buffer,
+ int num_bytes,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual int Finish(const CompletionCallback& callback) OVERRIDE;
+
+ // Called when a write has been done.
+ void DidWrite(const CompletionCallback& callback, int result);
+
+ // Drops ownership of the file at |file_path_|.
+ // This class will not delete it or write to it again.
+ void DisownFile();
+
+ // Closes the file if it is open.
+ // |callback| is run with the result upon completion.
+ void CloseFile(const CompletionCallback& callback);
+
+ // Closes the file if it is open and then delete it.
+ void CloseAndDeleteFile();
+
+ const base::FilePath& file_path() const { return file_path_; }
+ int64 total_bytes_written() { return total_bytes_written_; }
+ int error_code() const { return error_code_; }
+
+ private:
+ // Callback which gets the result of a temporary file creation.
+ void DidCreateTempFile(const CompletionCallback& callback,
+ base::FilePath* temp_file_path,
+ bool success);
+
+ // Callback which gets the result of FileStream::Open.
+ void DidOpenFile(const CompletionCallback& callback,
+ int result);
+
+ // The last error encountered on a file operation. OK if no error occurred.
+ int error_code_;
+
+ // Callbacks are created for use with base::FileUtilProxy.
+ base::WeakPtrFactory<URLFetcherFileWriter> weak_factory_;
+
+ // Task runner on which file operations should happen.
+ scoped_refptr<base::TaskRunner> file_task_runner_;
+
+ // Destination file path.
+ // Initialize() creates a temporary file if this variable is empty.
+ base::FilePath file_path_;
+
+ // True when this instance is responsible to delete the file at |file_path_|.
+ bool owns_file_;
+
+ scoped_ptr<FileStream> file_stream_;
+
+ // We always append to the file. Track the total number of bytes
+ // written, so that writes know the offset to give.
+ int64 total_bytes_written_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherFileWriter);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_RESPONSE_WRITER_H_
diff --git a/chromium/net/url_request/url_request.cc b/chromium/net/url_request/url_request.cc
new file mode 100644
index 00000000000..ce361c69838
--- /dev/null
+++ b/chromium/net/url_request/url_request.cc
@@ -0,0 +1,1117 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/lazy_instance.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_delegate.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_manager.h"
+#include "net/url_request/url_request_netlog_params.h"
+#include "net/url_request/url_request_redirect_job.h"
+
+using base::Time;
+using std::string;
+
+namespace net {
+
+namespace {
+
+// Max number of http redirects to follow. Same number as gecko.
+const int kMaxRedirects = 20;
+
+// Discard headers which have meaning in POST (Content-Length, Content-Type,
+// Origin).
+void StripPostSpecificHeaders(HttpRequestHeaders* headers) {
+ // These are headers that may be attached to a POST.
+ headers->RemoveHeader(HttpRequestHeaders::kContentLength);
+ headers->RemoveHeader(HttpRequestHeaders::kContentType);
+ headers->RemoveHeader(HttpRequestHeaders::kOrigin);
+}
+
+// TODO(battre): Delete this, see http://crbug.com/89321:
+// This counter keeps track of the identifiers used for URL requests so far.
+// 0 is reserved to represent an invalid ID.
+uint64 g_next_url_request_identifier = 1;
+
+// This lock protects g_next_url_request_identifier.
+base::LazyInstance<base::Lock>::Leaky
+ g_next_url_request_identifier_lock = LAZY_INSTANCE_INITIALIZER;
+
+// Returns an prior unused identifier for URL requests.
+uint64 GenerateURLRequestIdentifier() {
+ base::AutoLock lock(g_next_url_request_identifier_lock.Get());
+ return g_next_url_request_identifier++;
+}
+
+// True once the first URLRequest was started.
+bool g_url_requests_started = false;
+
+// True if cookies are accepted by default.
+bool g_default_can_use_cookies = true;
+
+// When the URLRequest first assempts load timing information, it has the times
+// at which each event occurred. The API requires the time which the request
+// was blocked on each phase. This function handles the conversion.
+//
+// In the case of reusing a SPDY session or HTTP pipeline, old proxy results may
+// have been reused, so proxy resolution times may be before the request was
+// started.
+//
+// Due to preconnect and late binding, it is also possible for the connection
+// attempt to start before a request has been started, or proxy resolution
+// completed.
+//
+// This functions fixes both those cases.
+void ConvertRealLoadTimesToBlockingTimes(
+ net::LoadTimingInfo* load_timing_info) {
+ DCHECK(!load_timing_info->request_start.is_null());
+
+ // Earliest time possible for the request to be blocking on connect events.
+ base::TimeTicks block_on_connect = load_timing_info->request_start;
+
+ if (!load_timing_info->proxy_resolve_start.is_null()) {
+ DCHECK(!load_timing_info->proxy_resolve_end.is_null());
+
+ // Make sure the proxy times are after request start.
+ if (load_timing_info->proxy_resolve_start < load_timing_info->request_start)
+ load_timing_info->proxy_resolve_start = load_timing_info->request_start;
+ if (load_timing_info->proxy_resolve_end < load_timing_info->request_start)
+ load_timing_info->proxy_resolve_end = load_timing_info->request_start;
+
+ // Connect times must also be after the proxy times.
+ block_on_connect = load_timing_info->proxy_resolve_end;
+ }
+
+ // Make sure connection times are after start and proxy times.
+
+ net::LoadTimingInfo::ConnectTiming* connect_timing =
+ &load_timing_info->connect_timing;
+ if (!connect_timing->dns_start.is_null()) {
+ DCHECK(!connect_timing->dns_end.is_null());
+ if (connect_timing->dns_start < block_on_connect)
+ connect_timing->dns_start = block_on_connect;
+ if (connect_timing->dns_end < block_on_connect)
+ connect_timing->dns_end = block_on_connect;
+ }
+
+ if (!connect_timing->connect_start.is_null()) {
+ DCHECK(!connect_timing->connect_end.is_null());
+ if (connect_timing->connect_start < block_on_connect)
+ connect_timing->connect_start = block_on_connect;
+ if (connect_timing->connect_end < block_on_connect)
+ connect_timing->connect_end = block_on_connect;
+ }
+
+ if (!connect_timing->ssl_start.is_null()) {
+ DCHECK(!connect_timing->ssl_end.is_null());
+ if (connect_timing->ssl_start < block_on_connect)
+ connect_timing->ssl_start = block_on_connect;
+ if (connect_timing->ssl_end < block_on_connect)
+ connect_timing->ssl_end = block_on_connect;
+ }
+}
+
+} // namespace
+
+URLRequest::ProtocolFactory*
+URLRequest::Deprecated::RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory) {
+ return URLRequest::RegisterProtocolFactory(scheme, factory);
+}
+
+void URLRequest::Deprecated::RegisterRequestInterceptor(
+ Interceptor* interceptor) {
+ URLRequest::RegisterRequestInterceptor(interceptor);
+}
+
+void URLRequest::Deprecated::UnregisterRequestInterceptor(
+ Interceptor* interceptor) {
+ URLRequest::UnregisterRequestInterceptor(interceptor);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest::Interceptor
+
+URLRequestJob* URLRequest::Interceptor::MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) {
+ return NULL;
+}
+
+URLRequestJob* URLRequest::Interceptor::MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) {
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest::Delegate
+
+void URLRequest::Delegate::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+}
+
+void URLRequest::Delegate::OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ request->CancelAuth();
+}
+
+void URLRequest::Delegate::OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) {
+ request->Cancel();
+}
+
+void URLRequest::Delegate::OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool is_hsts_ok) {
+ request->Cancel();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest
+
+// TODO(shalev): Get rid of this constructor in favour of the one below it.
+URLRequest::URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context)
+ : context_(context),
+ network_delegate_(context->network_delegate()),
+ net_log_(BoundNetLog::Make(context->net_log(),
+ NetLog::SOURCE_URL_REQUEST)),
+ url_chain_(1, url),
+ method_("GET"),
+ referrer_policy_(CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE),
+ load_flags_(LOAD_NORMAL),
+ delegate_(delegate),
+ is_pending_(false),
+ is_redirecting_(false),
+ redirect_limit_(kMaxRedirects),
+ priority_(DEFAULT_PRIORITY),
+ identifier_(GenerateURLRequestIdentifier()),
+ blocked_on_delegate_(false),
+ before_request_callback_(base::Bind(&URLRequest::BeforeRequestComplete,
+ base::Unretained(this))),
+ has_notified_completion_(false),
+ received_response_content_length_(0),
+ creation_time_(base::TimeTicks::Now()) {
+ SIMPLE_STATS_COUNTER("URLRequestCount");
+
+ // Sanity check out environment.
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+
+ CHECK(context);
+ context->url_requests()->insert(this);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE);
+}
+
+URLRequest::URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context,
+ NetworkDelegate* network_delegate)
+ : context_(context),
+ network_delegate_(network_delegate),
+ net_log_(BoundNetLog::Make(context->net_log(),
+ NetLog::SOURCE_URL_REQUEST)),
+ url_chain_(1, url),
+ method_("GET"),
+ referrer_policy_(CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE),
+ load_flags_(LOAD_NORMAL),
+ delegate_(delegate),
+ is_pending_(false),
+ is_redirecting_(false),
+ redirect_limit_(kMaxRedirects),
+ priority_(DEFAULT_PRIORITY),
+ identifier_(GenerateURLRequestIdentifier()),
+ blocked_on_delegate_(false),
+ before_request_callback_(base::Bind(&URLRequest::BeforeRequestComplete,
+ base::Unretained(this))),
+ has_notified_completion_(false),
+ received_response_content_length_(0),
+ creation_time_(base::TimeTicks::Now()) {
+ SIMPLE_STATS_COUNTER("URLRequestCount");
+
+ // Sanity check out environment.
+ DCHECK(base::MessageLoop::current())
+ << "The current base::MessageLoop must exist";
+
+ CHECK(context);
+ context->url_requests()->insert(this);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE);
+}
+
+URLRequest::~URLRequest() {
+ Cancel();
+
+ if (network_delegate_) {
+ network_delegate_->NotifyURLRequestDestroyed(this);
+ if (job_.get())
+ job_->NotifyURLRequestDestroyed();
+ }
+
+ if (job_.get())
+ OrphanJob();
+
+ int deleted = context_->url_requests()->erase(this);
+ CHECK_EQ(1, deleted);
+
+ int net_error = OK;
+ // Log error only on failure, not cancellation, as even successful requests
+ // are "cancelled" on destruction.
+ if (status_.status() == URLRequestStatus::FAILED)
+ net_error = status_.error();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_REQUEST_ALIVE, net_error);
+}
+
+// static
+URLRequest::ProtocolFactory* URLRequest::RegisterProtocolFactory(
+ const string& scheme, ProtocolFactory* factory) {
+ return URLRequestJobManager::GetInstance()->RegisterProtocolFactory(scheme,
+ factory);
+}
+
+// static
+void URLRequest::RegisterRequestInterceptor(Interceptor* interceptor) {
+ URLRequestJobManager::GetInstance()->RegisterRequestInterceptor(interceptor);
+}
+
+// static
+void URLRequest::UnregisterRequestInterceptor(Interceptor* interceptor) {
+ URLRequestJobManager::GetInstance()->UnregisterRequestInterceptor(
+ interceptor);
+}
+
+void URLRequest::EnableChunkedUpload() {
+ DCHECK(!upload_data_stream_ || upload_data_stream_->is_chunked());
+ if (!upload_data_stream_) {
+ upload_data_stream_.reset(
+ new UploadDataStream(UploadDataStream::CHUNKED, 0));
+ }
+}
+
+void URLRequest::AppendChunkToUpload(const char* bytes,
+ int bytes_len,
+ bool is_last_chunk) {
+ DCHECK(upload_data_stream_);
+ DCHECK(upload_data_stream_->is_chunked());
+ DCHECK_GT(bytes_len, 0);
+ upload_data_stream_->AppendChunk(bytes, bytes_len, is_last_chunk);
+}
+
+void URLRequest::set_upload(scoped_ptr<UploadDataStream> upload) {
+ DCHECK(!upload->is_chunked());
+ upload_data_stream_ = upload.Pass();
+}
+
+const UploadDataStream* URLRequest::get_upload() const {
+ return upload_data_stream_.get();
+}
+
+bool URLRequest::has_upload() const {
+ return upload_data_stream_.get() != NULL;
+}
+
+void URLRequest::SetExtraRequestHeaderById(int id, const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::SetExtraRequestHeaderByName(const string& name,
+ const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ if (overwrite) {
+ extra_request_headers_.SetHeader(name, value);
+ } else {
+ extra_request_headers_.SetHeaderIfMissing(name, value);
+ }
+}
+
+void URLRequest::RemoveRequestHeaderByName(const string& name) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ extra_request_headers_.RemoveHeader(name);
+}
+
+void URLRequest::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ DCHECK(!is_pending_);
+ extra_request_headers_ = headers;
+
+ // NOTE: This method will likely become non-trivial once the other setters
+ // for request headers are implemented.
+}
+
+bool URLRequest::GetFullRequestHeaders(HttpRequestHeaders* headers) const {
+ if (!job_.get())
+ return false;
+
+ return job_->GetFullRequestHeaders(headers);
+}
+
+LoadStateWithParam URLRequest::GetLoadState() const {
+ if (blocked_on_delegate_) {
+ return LoadStateWithParam(LOAD_STATE_WAITING_FOR_DELEGATE,
+ load_state_param_);
+ }
+ return LoadStateWithParam(job_.get() ? job_->GetLoadState() : LOAD_STATE_IDLE,
+ base::string16());
+}
+
+UploadProgress URLRequest::GetUploadProgress() const {
+ if (!job_.get()) {
+ // We haven't started or the request was cancelled
+ return UploadProgress();
+ }
+ if (final_upload_progress_.position()) {
+ // The first job completed and none of the subsequent series of
+ // GETs when following redirects will upload anything, so we return the
+ // cached results from the initial job, the POST.
+ return final_upload_progress_;
+ }
+ return job_->GetUploadProgress();
+}
+
+void URLRequest::GetResponseHeaderById(int id, string* value) {
+ DCHECK(job_.get());
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::GetResponseHeaderByName(const string& name, string* value) {
+ DCHECK(value);
+ if (response_info_.headers.get()) {
+ response_info_.headers->GetNormalizedHeader(name, value);
+ } else {
+ value->clear();
+ }
+}
+
+void URLRequest::GetAllResponseHeaders(string* headers) {
+ DCHECK(headers);
+ if (response_info_.headers.get()) {
+ response_info_.headers->GetNormalizedHeaders(headers);
+ } else {
+ headers->clear();
+ }
+}
+
+HostPortPair URLRequest::GetSocketAddress() const {
+ DCHECK(job_.get());
+ return job_->GetSocketAddress();
+}
+
+HttpResponseHeaders* URLRequest::response_headers() const {
+ return response_info_.headers.get();
+}
+
+void URLRequest::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ *load_timing_info = load_timing_info_;
+}
+
+bool URLRequest::GetResponseCookies(ResponseCookies* cookies) {
+ DCHECK(job_.get());
+ return job_->GetResponseCookies(cookies);
+}
+
+void URLRequest::GetMimeType(string* mime_type) {
+ DCHECK(job_.get());
+ job_->GetMimeType(mime_type);
+}
+
+void URLRequest::GetCharset(string* charset) {
+ DCHECK(job_.get());
+ job_->GetCharset(charset);
+}
+
+int URLRequest::GetResponseCode() {
+ DCHECK(job_.get());
+ return job_->GetResponseCode();
+}
+
+// static
+void URLRequest::SetDefaultCookiePolicyToBlock() {
+ CHECK(!g_url_requests_started);
+ g_default_can_use_cookies = false;
+}
+
+// static
+bool URLRequest::IsHandledProtocol(const std::string& scheme) {
+ return URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
+}
+
+// static
+bool URLRequest::IsHandledURL(const GURL& url) {
+ if (!url.is_valid()) {
+ // We handle error cases.
+ return true;
+ }
+
+ return IsHandledProtocol(url.scheme());
+}
+
+void URLRequest::set_first_party_for_cookies(
+ const GURL& first_party_for_cookies) {
+ first_party_for_cookies_ = first_party_for_cookies;
+}
+
+void URLRequest::set_method(const std::string& method) {
+ DCHECK(!is_pending_);
+ method_ = method;
+}
+
+// static
+std::string URLRequest::ComputeMethodForRedirect(
+ const std::string& method,
+ int http_status_code) {
+ // For 303 redirects, all request methods except HEAD are converted to GET,
+ // as per the latest httpbis draft. The draft also allows POST requests to
+ // be converted to GETs when following 301/302 redirects, for historical
+ // reasons. Most major browsers do this and so shall we. Both RFC 2616 and
+ // the httpbis draft say to prompt the user to confirm the generation of new
+ // requests, other than GET and HEAD requests, but IE omits these prompts and
+ // so shall we.
+ // See: https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-17#section-7.3
+ if ((http_status_code == 303 && method != "HEAD") ||
+ ((http_status_code == 301 || http_status_code == 302) &&
+ method == "POST")) {
+ return "GET";
+ }
+ return method;
+}
+
+void URLRequest::SetReferrer(const std::string& referrer) {
+ DCHECK(!is_pending_);
+ referrer_ = referrer;
+ // Ensure that we do not send URL fragment, username and password
+ // fields in the referrer.
+ GURL referrer_url(referrer);
+ UMA_HISTOGRAM_BOOLEAN("Net.URLRequest_SetReferrer_IsEmptyOrValid",
+ referrer_url.is_empty() || referrer_url.is_valid());
+ if (referrer_url.is_valid() && (referrer_url.has_ref() ||
+ referrer_url.has_username() || referrer_url.has_password())) {
+ GURL::Replacements referrer_mods;
+ referrer_mods.ClearRef();
+ referrer_mods.ClearUsername();
+ referrer_mods.ClearPassword();
+ referrer_url = referrer_url.ReplaceComponents(referrer_mods);
+ referrer_ = referrer_url.spec();
+ }
+}
+
+void URLRequest::set_referrer_policy(ReferrerPolicy referrer_policy) {
+ DCHECK(!is_pending_);
+ referrer_policy_ = referrer_policy;
+}
+
+void URLRequest::set_delegate(Delegate* delegate) {
+ delegate_ = delegate;
+}
+
+void URLRequest::Start() {
+ DCHECK_EQ(network_delegate_, context_->network_delegate());
+
+ g_url_requests_started = true;
+ response_info_.request_time = base::Time::Now();
+
+ load_timing_info_ = LoadTimingInfo();
+ load_timing_info_.request_start_time = response_info_.request_time;
+ load_timing_info_.request_start = base::TimeTicks::Now();
+
+ // Only notify the delegate for the initial request.
+ if (network_delegate_) {
+ int error = network_delegate_->NotifyBeforeURLRequest(
+ this, before_request_callback_, &delegate_redirect_url_);
+ if (error == net::ERR_IO_PENDING) {
+ // Paused on the delegate, will invoke |before_request_callback_| later.
+ SetBlockedOnDelegate();
+ } else {
+ BeforeRequestComplete(error);
+ }
+ return;
+ }
+
+ StartJob(URLRequestJobManager::GetInstance()->CreateJob(
+ this, network_delegate_));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void URLRequest::BeforeRequestComplete(int error) {
+ DCHECK(!job_.get());
+ DCHECK_NE(ERR_IO_PENDING, error);
+ DCHECK_EQ(network_delegate_, context_->network_delegate());
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, status_.status());
+
+ if (blocked_on_delegate_)
+ SetUnblockedOnDelegate();
+
+ if (error != OK) {
+ std::string source("delegate");
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ StartJob(new URLRequestErrorJob(this, network_delegate_, error));
+ } else if (!delegate_redirect_url_.is_empty()) {
+ GURL new_url;
+ new_url.Swap(&delegate_redirect_url_);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ this, network_delegate_, new_url,
+ // Use status code 307 to preserve the method, so POST requests work.
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ StartJob(job);
+ } else {
+ StartJob(URLRequestJobManager::GetInstance()->CreateJob(
+ this, network_delegate_));
+ }
+}
+
+void URLRequest::StartJob(URLRequestJob* job) {
+ DCHECK(!is_pending_);
+ DCHECK(!job_.get());
+
+ net_log_.BeginEvent(
+ NetLog::TYPE_URL_REQUEST_START_JOB,
+ base::Bind(&NetLogURLRequestStartCallback,
+ &url(), &method_, load_flags_, priority_,
+ upload_data_stream_ ? upload_data_stream_->identifier() : -1));
+
+ job_ = job;
+ job_->SetExtraRequestHeaders(extra_request_headers_);
+ job_->SetPriority(priority_);
+
+ if (upload_data_stream_.get())
+ job_->SetUpload(upload_data_stream_.get());
+
+ is_pending_ = true;
+ is_redirecting_ = false;
+
+ response_info_.was_cached = false;
+
+ // Don't allow errors to be sent from within Start().
+ // TODO(brettw) this may cause NotifyDone to be sent synchronously,
+ // we probably don't want this: they should be sent asynchronously so
+ // the caller does not get reentered.
+ job_->Start();
+}
+
+void URLRequest::Restart() {
+ // Should only be called if the original job didn't make any progress.
+ DCHECK(job_.get() && !job_->has_response_started());
+ RestartWithJob(
+ URLRequestJobManager::GetInstance()->CreateJob(this, network_delegate_));
+}
+
+void URLRequest::RestartWithJob(URLRequestJob *job) {
+ DCHECK(job->request() == this);
+ PrepareToRestart();
+ StartJob(job);
+}
+
+void URLRequest::Cancel() {
+ DoCancel(ERR_ABORTED, SSLInfo());
+}
+
+void URLRequest::CancelWithError(int error) {
+ DoCancel(error, SSLInfo());
+}
+
+void URLRequest::CancelWithSSLError(int error, const SSLInfo& ssl_info) {
+ // This should only be called on a started request.
+ if (!is_pending_ || !job_.get() || job_->has_response_started()) {
+ NOTREACHED();
+ return;
+ }
+ DoCancel(error, ssl_info);
+}
+
+void URLRequest::DoCancel(int error, const SSLInfo& ssl_info) {
+ DCHECK(error < 0);
+
+ // If the URL request already has an error status, then canceling is a no-op.
+ // Plus, we don't want to change the error status once it has been set.
+ if (status_.is_success()) {
+ status_.set_status(URLRequestStatus::CANCELED);
+ status_.set_error(error);
+ response_info_.ssl_info = ssl_info;
+
+ // If the request hasn't already been completed, log a cancellation event.
+ if (!has_notified_completion_) {
+ // Don't log an error code on ERR_ABORTED, since that's redundant.
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_CANCELLED,
+ error == ERR_ABORTED ? OK : error);
+ }
+ }
+
+ if (is_pending_ && job_.get())
+ job_->Kill();
+
+ // We need to notify about the end of this job here synchronously. The
+ // Job sends an asynchronous notification but by the time this is processed,
+ // our |context_| is NULL.
+ NotifyRequestCompleted();
+
+ // The Job will call our NotifyDone method asynchronously. This is done so
+ // that the Delegate implementation can call Cancel without having to worry
+ // about being called recursively.
+}
+
+bool URLRequest::Read(IOBuffer* dest, int dest_size, int* bytes_read) {
+ DCHECK(job_.get());
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ // This handles a cancel that happens while paused.
+ // TODO(ahendrickson): DCHECK() that it is not done after
+ // http://crbug.com/115705 is fixed.
+ if (job_->is_done())
+ return false;
+
+ if (dest_size == 0) {
+ // Caller is not too bright. I guess we've done what they asked.
+ return true;
+ }
+
+ // Once the request fails or is cancelled, read will just return 0 bytes
+ // to indicate end of stream.
+ if (!status_.is_success()) {
+ return true;
+ }
+
+ bool rv = job_->Read(dest, dest_size, bytes_read);
+ // If rv is false, the status cannot be success.
+ DCHECK(rv || status_.status() != URLRequestStatus::SUCCESS);
+ if (rv && *bytes_read <= 0 && status_.is_success())
+ NotifyRequestCompleted();
+ return rv;
+}
+
+void URLRequest::StopCaching() {
+ DCHECK(job_.get());
+ job_->StopCaching();
+}
+
+void URLRequest::NotifyReceivedRedirect(const GURL& location,
+ bool* defer_redirect) {
+ is_redirecting_ = true;
+
+ URLRequestJob* job =
+ URLRequestJobManager::GetInstance()->MaybeInterceptRedirect(
+ this, network_delegate_, location);
+ if (job) {
+ RestartWithJob(job);
+ } else if (delegate_) {
+ delegate_->OnReceivedRedirect(this, location, defer_redirect);
+ // |this| may be have been destroyed here.
+ }
+}
+
+void URLRequest::NotifyResponseStarted() {
+ int net_error = OK;
+ if (!status_.is_success())
+ net_error = status_.error();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_URL_REQUEST_START_JOB,
+ net_error);
+
+ URLRequestJob* job =
+ URLRequestJobManager::GetInstance()->MaybeInterceptResponse(
+ this, network_delegate_);
+ if (job) {
+ RestartWithJob(job);
+ } else {
+ if (delegate_) {
+ // In some cases (e.g. an event was canceled), we might have sent the
+ // completion event and receive a NotifyResponseStarted() later.
+ if (!has_notified_completion_ && status_.is_success()) {
+ if (network_delegate_)
+ network_delegate_->NotifyResponseStarted(this);
+ }
+
+ // Notify in case the entire URL Request has been finished.
+ if (!has_notified_completion_ && !status_.is_success())
+ NotifyRequestCompleted();
+
+ delegate_->OnResponseStarted(this);
+ // Nothing may appear below this line as OnResponseStarted may delete
+ // |this|.
+ }
+ }
+}
+
+void URLRequest::FollowDeferredRedirect() {
+ CHECK(job_.get());
+ CHECK(status_.is_success());
+
+ job_->FollowDeferredRedirect();
+}
+
+void URLRequest::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(job_.get());
+ DCHECK(job_->NeedsAuth());
+
+ job_->SetAuth(credentials);
+}
+
+void URLRequest::CancelAuth() {
+ DCHECK(job_.get());
+ DCHECK(job_->NeedsAuth());
+
+ job_->CancelAuth();
+}
+
+void URLRequest::ContinueWithCertificate(X509Certificate* client_cert) {
+ DCHECK(job_.get());
+
+ job_->ContinueWithCertificate(client_cert);
+}
+
+void URLRequest::ContinueDespiteLastError() {
+ DCHECK(job_.get());
+
+ job_->ContinueDespiteLastError();
+}
+
+void URLRequest::PrepareToRestart() {
+ DCHECK(job_.get());
+
+ // Close the current URL_REQUEST_START_JOB, since we will be starting a new
+ // one.
+ net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_START_JOB);
+
+ OrphanJob();
+
+ response_info_ = HttpResponseInfo();
+ response_info_.request_time = base::Time::Now();
+
+ load_timing_info_ = LoadTimingInfo();
+ load_timing_info_.request_start_time = response_info_.request_time;
+ load_timing_info_.request_start = base::TimeTicks::Now();
+
+ status_ = URLRequestStatus();
+ is_pending_ = false;
+}
+
+void URLRequest::OrphanJob() {
+ // When calling this function, please check that URLRequestHttpJob is
+ // not in between calling NetworkDelegate::NotifyHeadersReceived receiving
+ // the call back. This is currently guaranteed by the following strategies:
+ // - OrphanJob is called on JobRestart, in this case the URLRequestJob cannot
+ // be receiving any headers at that time.
+ // - OrphanJob is called in ~URLRequest, in this case
+ // NetworkDelegate::NotifyURLRequestDestroyed notifies the NetworkDelegate
+ // that the callback becomes invalid.
+ job_->Kill();
+ job_->DetachRequest(); // ensures that the job will not call us again
+ job_ = NULL;
+}
+
+int URLRequest::Redirect(const GURL& location, int http_status_code) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_URL_REQUEST_REDIRECTED,
+ NetLog::StringCallback("location", &location.possibly_invalid_spec()));
+ }
+
+ if (network_delegate_)
+ network_delegate_->NotifyBeforeRedirect(this, location);
+
+ if (redirect_limit_ <= 0) {
+ DVLOG(1) << "disallowing redirect: exceeds limit";
+ return ERR_TOO_MANY_REDIRECTS;
+ }
+
+ if (!location.is_valid())
+ return ERR_INVALID_URL;
+
+ if (!job_->IsSafeRedirect(location)) {
+ DVLOG(1) << "disallowing redirect: unsafe protocol";
+ return ERR_UNSAFE_REDIRECT;
+ }
+
+ if (!final_upload_progress_.position())
+ final_upload_progress_ = job_->GetUploadProgress();
+ PrepareToRestart();
+
+ std::string new_method(ComputeMethodForRedirect(method_, http_status_code));
+ if (new_method != method_) {
+ if (method_ == "POST") {
+ // If being switched from POST, must remove headers that were specific to
+ // the POST and don't have meaning in other methods. For example the
+ // inclusion of a multipart Content-Type header in GET can cause problems
+ // with some servers:
+ // http://code.google.com/p/chromium/issues/detail?id=843
+ StripPostSpecificHeaders(&extra_request_headers_);
+ }
+ upload_data_stream_.reset();
+ method_.swap(new_method);
+ }
+
+ // Suppress the referrer if we're redirecting out of https.
+ if (referrer_policy_ ==
+ CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE &&
+ GURL(referrer_).SchemeIsSecure() && !location.SchemeIsSecure()) {
+ referrer_.clear();
+ }
+
+ url_chain_.push_back(location);
+ --redirect_limit_;
+
+ Start();
+ return OK;
+}
+
+const URLRequestContext* URLRequest::context() const {
+ return context_;
+}
+
+int64 URLRequest::GetExpectedContentSize() const {
+ int64 expected_content_size = -1;
+ if (job_.get())
+ expected_content_size = job_->expected_content_size();
+
+ return expected_content_size;
+}
+
+void URLRequest::SetPriority(RequestPriority priority) {
+ DCHECK_GE(priority, MINIMUM_PRIORITY);
+ DCHECK_LT(priority, NUM_PRIORITIES);
+ if (priority_ == priority)
+ return;
+
+ priority_ = priority;
+ if (job_.get()) {
+ net_log_.AddEvent(NetLog::TYPE_URL_REQUEST_SET_PRIORITY,
+ NetLog::IntegerCallback("priority", priority_));
+ job_->SetPriority(priority_);
+ }
+}
+
+bool URLRequest::GetHSTSRedirect(GURL* redirect_url) const {
+ const GURL& url = this->url();
+ if (!url.SchemeIs("http"))
+ return false;
+ TransportSecurityState::DomainState domain_state;
+ if (context()->transport_security_state() &&
+ context()->transport_security_state()->GetDomainState(
+ url.host(),
+ SSLConfigService::IsSNIAvailable(context()->ssl_config_service()),
+ &domain_state) &&
+ domain_state.ShouldUpgradeToSSL()) {
+ url_canon::Replacements<char> replacements;
+ const char kNewScheme[] = "https";
+ replacements.SetScheme(kNewScheme,
+ url_parse::Component(0, strlen(kNewScheme)));
+ *redirect_url = url.ReplaceComponents(replacements);
+ return true;
+ }
+ return false;
+}
+
+void URLRequest::NotifyAuthRequired(AuthChallengeInfo* auth_info) {
+ NetworkDelegate::AuthRequiredResponse rv =
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ auth_info_ = auth_info;
+ if (network_delegate_) {
+ rv = network_delegate_->NotifyAuthRequired(
+ this,
+ *auth_info,
+ base::Bind(&URLRequest::NotifyAuthRequiredComplete,
+ base::Unretained(this)),
+ &auth_credentials_);
+ }
+
+ if (rv == NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING) {
+ SetBlockedOnDelegate();
+ } else {
+ NotifyAuthRequiredComplete(rv);
+ }
+}
+
+void URLRequest::NotifyAuthRequiredComplete(
+ NetworkDelegate::AuthRequiredResponse result) {
+ SetUnblockedOnDelegate();
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, status_.status());
+
+ // NotifyAuthRequired may be called multiple times, such as
+ // when an authentication attempt fails. Clear out the data
+ // so it can be reset on another round.
+ AuthCredentials credentials = auth_credentials_;
+ auth_credentials_ = AuthCredentials();
+ scoped_refptr<AuthChallengeInfo> auth_info;
+ auth_info.swap(auth_info_);
+
+ switch (result) {
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION:
+ // Defer to the URLRequest::Delegate, since the NetworkDelegate
+ // didn't take an action.
+ if (delegate_)
+ delegate_->OnAuthRequired(this, auth_info.get());
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH:
+ SetAuth(credentials);
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH:
+ CancelAuth();
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING:
+ NOTREACHED();
+ break;
+ }
+}
+
+void URLRequest::NotifyCertificateRequested(
+ SSLCertRequestInfo* cert_request_info) {
+ if (delegate_)
+ delegate_->OnCertificateRequested(this, cert_request_info);
+}
+
+void URLRequest::NotifySSLCertificateError(const SSLInfo& ssl_info,
+ bool fatal) {
+ if (delegate_)
+ delegate_->OnSSLCertificateError(this, ssl_info, fatal);
+}
+
+bool URLRequest::CanGetCookies(const CookieList& cookie_list) const {
+ DCHECK(!(load_flags_ & LOAD_DO_NOT_SEND_COOKIES));
+ if (network_delegate_) {
+ return network_delegate_->CanGetCookies(*this, cookie_list);
+ }
+ return g_default_can_use_cookies;
+}
+
+bool URLRequest::CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const {
+ DCHECK(!(load_flags_ & LOAD_DO_NOT_SAVE_COOKIES));
+ if (network_delegate_) {
+ return network_delegate_->CanSetCookie(*this, cookie_line, options);
+ }
+ return g_default_can_use_cookies;
+}
+
+bool URLRequest::CanEnablePrivacyMode() const {
+ if (network_delegate_) {
+ return network_delegate_->CanEnablePrivacyMode(url(),
+ first_party_for_cookies_);
+ }
+ return !g_default_can_use_cookies;
+}
+
+
+void URLRequest::NotifyReadCompleted(int bytes_read) {
+ // Notify in case the entire URL Request has been finished.
+ if (bytes_read <= 0)
+ NotifyRequestCompleted();
+
+ // Notify NetworkChangeNotifier that we just received network data.
+ // This is to identify cases where the NetworkChangeNotifier thinks we
+ // are off-line but we are still receiving network data (crbug.com/124069),
+ // and to get rough network connection measurements.
+ if (bytes_read > 0 && !was_cached())
+ NetworkChangeNotifier::NotifyDataReceived(*this, bytes_read);
+
+ if (delegate_)
+ delegate_->OnReadCompleted(this, bytes_read);
+
+ // Nothing below this line as OnReadCompleted may delete |this|.
+}
+
+void URLRequest::OnHeadersComplete() {
+ // Cache load timing information now, as information will be lost once the
+ // socket is closed and the ClientSocketHandle is Reset, which will happen
+ // once the body is complete. The start times should already be populated.
+ if (job_.get()) {
+ // Keep a copy of the two times the URLRequest sets.
+ base::TimeTicks request_start = load_timing_info_.request_start;
+ base::Time request_start_time = load_timing_info_.request_start_time;
+
+ // Clear load times. Shouldn't be neded, but gives the GetLoadTimingInfo a
+ // consistent place to start from.
+ load_timing_info_ = LoadTimingInfo();
+ job_->GetLoadTimingInfo(&load_timing_info_);
+
+ load_timing_info_.request_start = request_start;
+ load_timing_info_.request_start_time = request_start_time;
+
+ ConvertRealLoadTimesToBlockingTimes(&load_timing_info_);
+ }
+}
+
+void URLRequest::NotifyRequestCompleted() {
+ // TODO(battre): Get rid of this check, according to willchan it should
+ // not be needed.
+ if (has_notified_completion_)
+ return;
+
+ is_pending_ = false;
+ is_redirecting_ = false;
+ has_notified_completion_ = true;
+ if (network_delegate_)
+ network_delegate_->NotifyCompleted(this, job_.get() != NULL);
+}
+
+void URLRequest::SetBlockedOnDelegate() {
+ blocked_on_delegate_ = true;
+ if (!load_state_param_.empty()) {
+ net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE,
+ NetLog::StringCallback("delegate", &load_state_param_));
+ } else {
+ net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE);
+ }
+}
+
+void URLRequest::SetUnblockedOnDelegate() {
+ if (!blocked_on_delegate_)
+ return;
+ blocked_on_delegate_ = false;
+ load_state_param_.clear();
+ net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE);
+}
+
+void URLRequest::set_stack_trace(const base::debug::StackTrace& stack_trace) {
+ base::debug::StackTrace* stack_trace_copy =
+ new base::debug::StackTrace(NULL, 0);
+ *stack_trace_copy = stack_trace;
+ stack_trace_.reset(stack_trace_copy);
+}
+
+const base::debug::StackTrace* URLRequest::stack_trace() const {
+ return stack_trace_.get();
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request.h b/chromium/net/url_request/url_request.h
new file mode 100644
index 00000000000..03978d6b1d2
--- /dev/null
+++ b/chromium/net/url_request/url_request.h
@@ -0,0 +1,868 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_H_
+#define NET_URL_REQUEST_URL_REQUEST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/debug/leak_tracker.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/supports_user_data.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/network_delegate.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_progress.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+class ChildProcessSecurityPolicyTest;
+class TestAutomationProvider;
+class URLRequestAutomationJob;
+
+namespace base {
+namespace debug {
+class StackTrace;
+}
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace appcache {
+class AppCacheInterceptor;
+class AppCacheRequestHandlerTest;
+class AppCacheURLRequestJobTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace content {
+class ResourceDispatcherHostTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace fileapi {
+class FileSystemDirURLRequestJobTest;
+class FileSystemURLRequestJobTest;
+class FileWriterDelegateTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace webkit_blob {
+class BlobURLRequestJobTest;
+}
+
+namespace net {
+
+class CookieOptions;
+class HostPortPair;
+class IOBuffer;
+struct LoadTimingInfo;
+class SSLCertRequestInfo;
+class SSLInfo;
+class UploadDataStream;
+class URLRequestContext;
+class URLRequestJob;
+class X509Certificate;
+
+// This stores the values of the Set-Cookie headers received during the request.
+// Each item in the vector corresponds to a Set-Cookie: line received,
+// excluding the "Set-Cookie:" part.
+typedef std::vector<std::string> ResponseCookies;
+
+//-----------------------------------------------------------------------------
+// A class representing the asynchronous load of a data stream from an URL.
+//
+// The lifetime of an instance of this class is completely controlled by the
+// consumer, and the instance is not required to live on the heap or be
+// allocated in any special way. It is also valid to delete an URLRequest
+// object during the handling of a callback to its delegate. Of course, once
+// the URLRequest is deleted, no further callbacks to its delegate will occur.
+//
+// NOTE: All usage of all instances of this class should be on the same thread.
+//
+class NET_EXPORT URLRequest : NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public base::SupportsUserData {
+ public:
+ // Callback function implemented by protocol handlers to create new jobs.
+ // The factory may return NULL to indicate an error, which will cause other
+ // factories to be queried. If no factory handles the request, then the
+ // default job will be used.
+ typedef URLRequestJob* (ProtocolFactory)(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ // HTTP request/response header IDs (via some preprocessor fun) for use with
+ // SetRequestHeaderById and GetResponseHeaderById.
+ enum {
+#define HTTP_ATOM(x) HTTP_ ## x,
+#include "net/http/http_atom_list.h"
+#undef HTTP_ATOM
+ };
+
+ // Referrer policies (see set_referrer_policy): During server redirects, the
+ // referrer header might be cleared, if the protocol changes from HTTPS to
+ // HTTP. This is the default behavior of URLRequest, corresponding to
+ // CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE. Alternatively, the
+ // referrer policy can be set to never change the referrer header. This
+ // behavior corresponds to NEVER_CLEAR_REFERRER. Embedders will want to use
+ // NEVER_CLEAR_REFERRER when implementing the meta-referrer support
+ // (http://wiki.whatwg.org/wiki/Meta_referrer) and sending requests with a
+ // non-default referrer policy. Only the default referrer policy requires
+ // the referrer to be cleared on transitions from HTTPS to HTTP.
+ enum ReferrerPolicy {
+ CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
+ NEVER_CLEAR_REFERRER,
+ };
+
+ // This class handles network interception. Use with
+ // (Un)RegisterRequestInterceptor.
+ class NET_EXPORT Interceptor {
+ public:
+ virtual ~Interceptor() {}
+
+ // Called for every request made. Should return a new job to handle the
+ // request if it should be intercepted, or NULL to allow the request to
+ // be handled in the normal manner.
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request, NetworkDelegate* network_delegate) = 0;
+
+ // Called after having received a redirect response, but prior to the
+ // the request delegate being informed of the redirect. Can return a new
+ // job to replace the existing job if it should be intercepted, or NULL
+ // to allow the normal handling to continue. If a new job is provided,
+ // the delegate never sees the original redirect response, instead the
+ // response produced by the intercept job will be returned.
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location);
+
+ // Called after having received a final response, but prior to the
+ // the request delegate being informed of the response. This is also
+ // called when there is no server response at all to allow interception
+ // on dns or network errors. Can return a new job to replace the existing
+ // job if it should be intercepted, or NULL to allow the normal handling to
+ // continue. If a new job is provided, the delegate never sees the original
+ // response, instead the response produced by the intercept job will be
+ // returned.
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate);
+ };
+
+ // Deprecated interfaces in net::URLRequest. They have been moved to
+ // URLRequest's private section to prevent new uses. Existing uses are
+ // explicitly friended here and should be removed over time.
+ class NET_EXPORT Deprecated {
+ private:
+ // TODO(willchan): Kill off these friend declarations.
+ friend class ::ChildProcessSecurityPolicyTest;
+ friend class ::TestAutomationProvider;
+ friend class ::URLRequestAutomationJob;
+ friend class TestInterceptor;
+ friend class URLRequestFilter;
+ friend class appcache::AppCacheInterceptor;
+ friend class appcache::AppCacheRequestHandlerTest;
+ friend class appcache::AppCacheURLRequestJobTest;
+ friend class content::ResourceDispatcherHostTest;
+ friend class fileapi::FileSystemDirURLRequestJobTest;
+ friend class fileapi::FileSystemURLRequestJobTest;
+ friend class fileapi::FileWriterDelegateTest;
+ friend class webkit_blob::BlobURLRequestJobTest;
+
+ // Use URLRequestJobFactory::ProtocolHandler instead.
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ // TODO(pauljensen): Remove this when AppCacheInterceptor is a
+ // ProtocolHandler, see crbug.com/161547.
+ static void RegisterRequestInterceptor(Interceptor* interceptor);
+ static void UnregisterRequestInterceptor(Interceptor* interceptor);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Deprecated);
+ };
+
+ // The delegate's methods are called from the message loop of the thread
+ // on which the request's Start() method is called. See above for the
+ // ordering of callbacks.
+ //
+ // The callbacks will be called in the following order:
+ // Start()
+ // - OnCertificateRequested* (zero or more calls, if the SSL server and/or
+ // SSL proxy requests a client certificate for authentication)
+ // - OnSSLCertificateError* (zero or one call, if the SSL server's
+ // certificate has an error)
+ // - OnReceivedRedirect* (zero or more calls, for the number of redirects)
+ // - OnAuthRequired* (zero or more calls, for the number of
+ // authentication failures)
+ // - OnResponseStarted
+ // Read() initiated by delegate
+ // - OnReadCompleted* (zero or more calls until all data is read)
+ //
+ // Read() must be called at least once. Read() returns true when it completed
+ // immediately, and false if an IO is pending or if there is an error. When
+ // Read() returns false, the caller can check the Request's status() to see
+ // if an error occurred, or if the IO is just pending. When Read() returns
+ // true with zero bytes read, it indicates the end of the response.
+ //
+ class NET_EXPORT Delegate {
+ public:
+ // Called upon a server-initiated redirect. The delegate may call the
+ // request's Cancel method to prevent the redirect from being followed.
+ // Since there may be multiple chained redirects, there may also be more
+ // than one redirect call.
+ //
+ // When this function is called, the request will still contain the
+ // original URL, the destination of the redirect is provided in 'new_url'.
+ // If the delegate does not cancel the request and |*defer_redirect| is
+ // false, then the redirect will be followed, and the request's URL will be
+ // changed to the new URL. Otherwise if the delegate does not cancel the
+ // request and |*defer_redirect| is true, then the redirect will be
+ // followed once FollowDeferredRedirect is called on the URLRequest.
+ //
+ // The caller must set |*defer_redirect| to false, so that delegates do not
+ // need to set it if they are happy with the default behavior of not
+ // deferring redirect.
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect);
+
+ // Called when we receive an authentication failure. The delegate should
+ // call request->SetAuth() with the user's credentials once it obtains them,
+ // or request->CancelAuth() to cancel the login and display the error page.
+ // When it does so, the request will be reissued, restarting the sequence
+ // of On* callbacks.
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info);
+
+ // Called when we receive an SSL CertificateRequest message for client
+ // authentication. The delegate should call
+ // request->ContinueWithCertificate() with the client certificate the user
+ // selected, or request->ContinueWithCertificate(NULL) to continue the SSL
+ // handshake without a client certificate.
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info);
+
+ // Called when using SSL and the server responds with a certificate with
+ // an error, for example, whose common name does not match the common name
+ // we were expecting for that host. The delegate should either do the
+ // safe thing and Cancel() the request or decide to proceed by calling
+ // ContinueDespiteLastError(). cert_error is a ERR_* error code
+ // indicating what's wrong with the certificate.
+ // If |fatal| is true then the host in question demands a higher level
+ // of security (due e.g. to HTTP Strict Transport Security, user
+ // preference, or built-in policy). In this case, errors must not be
+ // bypassable by the user.
+ virtual void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal);
+
+ // After calling Start(), the delegate will receive an OnResponseStarted
+ // callback when the request has completed. If an error occurred, the
+ // request->status() will be set. On success, all redirects have been
+ // followed and the final response is beginning to arrive. At this point,
+ // meta data about the response is available, including for example HTTP
+ // response headers if this is a request for a HTTP resource.
+ virtual void OnResponseStarted(URLRequest* request) = 0;
+
+ // Called when the a Read of the response body is completed after an
+ // IO_PENDING status from a Read() call.
+ // The data read is filled into the buffer which the caller passed
+ // to Read() previously.
+ //
+ // If an error occurred, request->status() will contain the error,
+ // and bytes read will be -1.
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // TODO(shalev): Get rid of this constructor in favour of the one below it.
+ // Initialize an URL request.
+ URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context);
+
+ URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context,
+ NetworkDelegate* network_delegate);
+
+ // If destroyed after Start() has been called but while IO is pending,
+ // then the request will be effectively canceled and the delegate
+ // will not have any more of its methods called.
+ virtual ~URLRequest();
+
+ // Changes the default cookie policy from allowing all cookies to blocking all
+ // cookies. Embedders that want to implement a more flexible policy should
+ // change the default to blocking all cookies, and provide a NetworkDelegate
+ // with the URLRequestContext that maintains the CookieStore.
+ // The cookie policy default has to be set before the first URLRequest is
+ // started. Once it was set to block all cookies, it cannot be changed back.
+ static void SetDefaultCookiePolicyToBlock();
+
+ // Returns true if the scheme can be handled by URLRequest. False otherwise.
+ static bool IsHandledProtocol(const std::string& scheme);
+
+ // Returns true if the url can be handled by URLRequest. False otherwise.
+ // The function returns true for invalid urls because URLRequest knows how
+ // to handle those.
+ // NOTE: This will also return true for URLs that are handled by
+ // ProtocolFactories that only work for requests that are scoped to a
+ // Profile.
+ static bool IsHandledURL(const GURL& url);
+
+ // The original url is the url used to initialize the request, and it may
+ // differ from the url if the request was redirected.
+ const GURL& original_url() const { return url_chain_.front(); }
+ // The chain of urls traversed by this request. If the request had no
+ // redirects, this vector will contain one element.
+ const std::vector<GURL>& url_chain() const { return url_chain_; }
+ const GURL& url() const { return url_chain_.back(); }
+
+ // The URL that should be consulted for the third-party cookie blocking
+ // policy.
+ //
+ // WARNING: This URL must only be used for the third-party cookie blocking
+ // policy. It MUST NEVER be used for any kind of SECURITY check.
+ //
+ // For example, if a top-level navigation is redirected, the
+ // first-party for cookies will be the URL of the first URL in the
+ // redirect chain throughout the whole redirect. If it was used for
+ // a security check, an attacker might try to get around this check
+ // by starting from some page that redirects to the
+ // host-to-be-attacked.
+ const GURL& first_party_for_cookies() const {
+ return first_party_for_cookies_;
+ }
+ // This method may be called before Start() or FollowDeferredRedirect() is
+ // called.
+ void set_first_party_for_cookies(const GURL& first_party_for_cookies);
+
+ // The request method, as an uppercase string. "GET" is the default value.
+ // The request method may only be changed before Start() is called and
+ // should only be assigned an uppercase value.
+ const std::string& method() const { return method_; }
+ void set_method(const std::string& method);
+
+ // Determines the new method of the request afer following a redirect.
+ // |method| is the method used to arrive at the redirect,
+ // |http_status_code| is the status code associated with the redirect.
+ static std::string ComputeMethodForRedirect(const std::string& method,
+ int http_status_code);
+
+ // The referrer URL for the request. This header may actually be suppressed
+ // from the underlying network request for security reasons (e.g., a HTTPS
+ // URL will not be sent as the referrer for a HTTP request). The referrer
+ // may only be changed before Start() is called.
+ const std::string& referrer() const { return referrer_; }
+ // Referrer is sanitized to remove URL fragment, user name and password.
+ void SetReferrer(const std::string& referrer);
+
+ // The referrer policy to apply when updating the referrer during redirects.
+ // The referrer policy may only be changed before Start() is called.
+ void set_referrer_policy(ReferrerPolicy referrer_policy);
+
+ // Sets the delegate of the request. This value may be changed at any time,
+ // and it is permissible for it to be null.
+ void set_delegate(Delegate* delegate);
+
+ // Indicates that the request body should be sent using chunked transfer
+ // encoding. This method may only be called before Start() is called.
+ void EnableChunkedUpload();
+
+ // Appends the given bytes to the request's upload data to be sent
+ // immediately via chunked transfer encoding. When all data has been sent,
+ // call MarkEndOfChunks() to indicate the end of upload data.
+ //
+ // This method may be called only after calling EnableChunkedUpload().
+ void AppendChunkToUpload(const char* bytes,
+ int bytes_len,
+ bool is_last_chunk);
+
+ // Sets the upload data.
+ void set_upload(scoped_ptr<UploadDataStream> upload);
+
+ // Gets the upload data.
+ const UploadDataStream* get_upload() const;
+
+ // Returns true if the request has a non-empty message body to upload.
+ bool has_upload() const;
+
+ // Set an extra request header by ID or name, or remove one by name. These
+ // methods may only be called before Start() is called, or before a new
+ // redirect in the request chain.
+ void SetExtraRequestHeaderById(int header_id, const std::string& value,
+ bool overwrite);
+ void SetExtraRequestHeaderByName(const std::string& name,
+ const std::string& value, bool overwrite);
+ void RemoveRequestHeaderByName(const std::string& name);
+
+ // Sets all extra request headers. Any extra request headers set by other
+ // methods are overwritten by this method. This method may only be called
+ // before Start() is called. It is an error to call it later.
+ void SetExtraRequestHeaders(const HttpRequestHeaders& headers);
+
+ const HttpRequestHeaders& extra_request_headers() const {
+ return extra_request_headers_;
+ }
+
+ // Gets the full request headers sent to the server.
+ //
+ // Return true and overwrites headers if it can get the request headers;
+ // otherwise, returns false and does not modify headers. (Always returns
+ // false for request types that don't have headers, like file requests.)
+ //
+ // This is guaranteed to succeed if:
+ //
+ // 1. A redirect or auth callback is currently running. Once it ends, the
+ // headers may become unavailable as a new request with the new address
+ // or credentials is made.
+ //
+ // 2. The OnResponseStarted callback is currently running or has run.
+ bool GetFullRequestHeaders(HttpRequestHeaders* headers) const;
+
+ // Returns the current load state for the request. |param| is an optional
+ // parameter describing details related to the load state. Not all load states
+ // have a parameter.
+ LoadStateWithParam GetLoadState() const;
+ void SetLoadStateParam(const base::string16& param) {
+ load_state_param_ = param;
+ }
+
+ // Returns the current upload progress in bytes. When the upload data is
+ // chunked, size is set to zero, but position will not be.
+ UploadProgress GetUploadProgress() const;
+
+ // Get response header(s) by ID or name. These methods may only be called
+ // once the delegate's OnResponseStarted method has been called. Headers
+ // that appear more than once in the response are coalesced, with values
+ // separated by commas (per RFC 2616). This will not work with cookies since
+ // comma can be used in cookie values.
+ // TODO(darin): add API to enumerate response headers.
+ void GetResponseHeaderById(int header_id, std::string* value);
+ void GetResponseHeaderByName(const std::string& name, std::string* value);
+
+ // Get all response headers, \n-delimited and \n\0-terminated. This includes
+ // the response status line. Restrictions on GetResponseHeaders apply.
+ void GetAllResponseHeaders(std::string* headers);
+
+ // The time when |this| was constructed.
+ base::TimeTicks creation_time() const { return creation_time_; }
+
+ // The time at which the returned response was requested. For cached
+ // responses, this is the last time the cache entry was validated.
+ const base::Time& request_time() const {
+ return response_info_.request_time;
+ }
+
+ // The time at which the returned response was generated. For cached
+ // responses, this is the last time the cache entry was validated.
+ const base::Time& response_time() const {
+ return response_info_.response_time;
+ }
+
+ // Indicate if this response was fetched from disk cache.
+ bool was_cached() const { return response_info_.was_cached; }
+
+ // Returns true if the URLRequest was delivered through a proxy.
+ bool was_fetched_via_proxy() const {
+ return response_info_.was_fetched_via_proxy;
+ }
+
+ // Returns the host and port that the content was fetched from. See
+ // http_response_info.h for caveats relating to cached content.
+ HostPortPair GetSocketAddress() const;
+
+ // Get all response headers, as a HttpResponseHeaders object. See comments
+ // in HttpResponseHeaders class as to the format of the data.
+ HttpResponseHeaders* response_headers() const;
+
+ // Get the SSL connection info.
+ const SSLInfo& ssl_info() const {
+ return response_info_.ssl_info;
+ }
+
+ // Gets timing information related to the request. Events that have not yet
+ // occurred are left uninitialized. After a second request starts, due to
+ // a redirect or authentication, values will be reset.
+ //
+ // LoadTimingInfo only contains ConnectTiming information and socket IDs for
+ // non-cached HTTP responses.
+ void GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const;
+
+ // Returns the cookie values included in the response, if the request is one
+ // that can have cookies. Returns true if the request is a cookie-bearing
+ // type, false otherwise. This method may only be called once the
+ // delegate's OnResponseStarted method has been called.
+ bool GetResponseCookies(ResponseCookies* cookies);
+
+ // Get the mime type. This method may only be called once the delegate's
+ // OnResponseStarted method has been called.
+ void GetMimeType(std::string* mime_type);
+
+ // Get the charset (character encoding). This method may only be called once
+ // the delegate's OnResponseStarted method has been called.
+ void GetCharset(std::string* charset);
+
+ // Returns the HTTP response code (e.g., 200, 404, and so on). This method
+ // may only be called once the delegate's OnResponseStarted method has been
+ // called. For non-HTTP requests, this method returns -1.
+ int GetResponseCode();
+
+ // Get the HTTP response info in its entirety.
+ const HttpResponseInfo& response_info() const { return response_info_; }
+
+ // Access the LOAD_* flags modifying this request (see load_flags.h).
+ int load_flags() const { return load_flags_; }
+ void set_load_flags(int flags) { load_flags_ = flags; }
+
+ // Returns true if the request is "pending" (i.e., if Start() has been called,
+ // and the response has not yet been called).
+ bool is_pending() const { return is_pending_; }
+
+ // Returns true if the request is in the process of redirecting to a new
+ // URL but has not yet initiated the new request.
+ bool is_redirecting() const { return is_redirecting_; }
+
+ // Returns the error status of the request.
+ const URLRequestStatus& status() const { return status_; }
+
+ // Returns a globally unique identifier for this request.
+ uint64 identifier() const { return identifier_; }
+
+ // This method is called to start the request. The delegate will receive
+ // a OnResponseStarted callback when the request is started.
+ void Start();
+
+ // This method may be called at any time after Start() has been called to
+ // cancel the request. This method may be called many times, and it has
+ // no effect once the response has completed. It is guaranteed that no
+ // methods of the delegate will be called after the request has been
+ // cancelled, except that this may call the delegate's OnReadCompleted()
+ // during the call to Cancel itself.
+ void Cancel();
+
+ // Cancels the request and sets the error to |error| (see net_error_list.h
+ // for values).
+ void CancelWithError(int error);
+
+ // Cancels the request and sets the error to |error| (see net_error_list.h
+ // for values) and attaches |ssl_info| as the SSLInfo for that request. This
+ // is useful to attach a certificate and certificate error to a canceled
+ // request.
+ void CancelWithSSLError(int error, const SSLInfo& ssl_info);
+
+ // Read initiates an asynchronous read from the response, and must only
+ // be called after the OnResponseStarted callback is received with a
+ // successful status.
+ // If data is available, Read will return true, and the data and length will
+ // be returned immediately. If data is not available, Read returns false,
+ // and an asynchronous Read is initiated. The Read is finished when
+ // the caller receives the OnReadComplete callback. Unless the request was
+ // cancelled, OnReadComplete will always be called, even if the read failed.
+ //
+ // The buf parameter is a buffer to receive the data. If the operation
+ // completes asynchronously, the implementation will reference the buffer
+ // until OnReadComplete is called. The buffer must be at least max_bytes in
+ // length.
+ //
+ // The max_bytes parameter is the maximum number of bytes to read.
+ //
+ // The bytes_read parameter is an output parameter containing the
+ // the number of bytes read. A value of 0 indicates that there is no
+ // more data available to read from the stream.
+ //
+ // If a read error occurs, Read returns false and the request->status
+ // will be set to an error.
+ bool Read(IOBuffer* buf, int max_bytes, int* bytes_read);
+
+ // If this request is being cached by the HTTP cache, stop subsequent caching.
+ // Note that this method has no effect on other (simultaneous or not) requests
+ // for the same resource. The typical example is a request that results in
+ // the data being stored to disk (downloaded instead of rendered) so we don't
+ // want to store it twice.
+ void StopCaching();
+
+ // This method may be called to follow a redirect that was deferred in
+ // response to an OnReceivedRedirect call.
+ void FollowDeferredRedirect();
+
+ // One of the following two methods should be called in response to an
+ // OnAuthRequired() callback (and only then).
+ // SetAuth will reissue the request with the given credentials.
+ // CancelAuth will give up and display the error page.
+ void SetAuth(const AuthCredentials& credentials);
+ void CancelAuth();
+
+ // This method can be called after the user selects a client certificate to
+ // instruct this URLRequest to continue with the request with the
+ // certificate. Pass NULL if the user doesn't have a client certificate.
+ void ContinueWithCertificate(X509Certificate* client_cert);
+
+ // This method can be called after some error notifications to instruct this
+ // URLRequest to ignore the current error and continue with the request. To
+ // cancel the request instead, call Cancel().
+ void ContinueDespiteLastError();
+
+ // Used to specify the context (cookie store, cache) for this request.
+ const URLRequestContext* context() const;
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ // Returns the expected content size if available
+ int64 GetExpectedContentSize() const;
+
+ // Returns the priority level for this request.
+ RequestPriority priority() const { return priority_; }
+
+ // Sets the priority level for this request and any related jobs.
+ void SetPriority(RequestPriority priority);
+
+ // Returns true iff this request would be internally redirected to HTTPS
+ // due to HSTS. If so, |redirect_url| is rewritten to the new HTTPS URL.
+ bool GetHSTSRedirect(GURL* redirect_url) const;
+
+ // TODO(willchan): Undo this. Only temporarily public.
+ bool has_delegate() const { return delegate_ != NULL; }
+
+ // NOTE(willchan): This is just temporary for debugging
+ // http://crbug.com/90971.
+ // Allows to setting debug info into the URLRequest.
+ void set_stack_trace(const base::debug::StackTrace& stack_trace);
+ const base::debug::StackTrace* stack_trace() const;
+
+ void set_received_response_content_length(int64 received_content_length) {
+ received_response_content_length_ = received_content_length;
+ }
+ int64 received_response_content_length() {
+ return received_response_content_length_;
+ }
+
+ protected:
+ // Allow the URLRequestJob class to control the is_pending() flag.
+ void set_is_pending(bool value) { is_pending_ = value; }
+
+ // Allow the URLRequestJob class to set our status too
+ void set_status(const URLRequestStatus& value) { status_ = value; }
+
+ // Allow the URLRequestJob to redirect this request. Returns OK if
+ // successful, otherwise an error code is returned.
+ int Redirect(const GURL& location, int http_status_code);
+
+ // Called by URLRequestJob to allow interception when a redirect occurs.
+ void NotifyReceivedRedirect(const GURL& location, bool* defer_redirect);
+
+ // Allow an interceptor's URLRequestJob to restart this request.
+ // Should only be called if the original job has not started a response.
+ void Restart();
+
+ private:
+ friend class URLRequestJob;
+
+ // Registers a new protocol handler for the given scheme. If the scheme is
+ // already handled, this will overwrite the given factory. To delete the
+ // protocol factory, use NULL for the factory BUT this WILL NOT put back
+ // any previously registered protocol factory. It will have returned
+ // the previously registered factory (or NULL if none is registered) when
+ // the scheme was first registered so that the caller can manually put it
+ // back if desired.
+ //
+ // The scheme must be all-lowercase ASCII. See the ProtocolFactory
+ // declaration for its requirements.
+ //
+ // The registered protocol factory may return NULL, which will cause the
+ // regular "built-in" protocol factory to be used.
+ //
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ // Registers or unregisters a network interception class.
+ static void RegisterRequestInterceptor(Interceptor* interceptor);
+ static void UnregisterRequestInterceptor(Interceptor* interceptor);
+
+ // Resumes or blocks a request paused by the NetworkDelegate::OnBeforeRequest
+ // handler. If |blocked| is true, the request is blocked and an error page is
+ // returned indicating so. This should only be called after Start is called
+ // and OnBeforeRequest returns true (signalling that the request should be
+ // paused).
+ void BeforeRequestComplete(int error);
+
+ void StartJob(URLRequestJob* job);
+
+ // Restarting involves replacing the current job with a new one such as what
+ // happens when following a HTTP redirect.
+ void RestartWithJob(URLRequestJob* job);
+ void PrepareToRestart();
+
+ // Detaches the job from this request in preparation for this object going
+ // away or the job being replaced. The job will not call us back when it has
+ // been orphaned.
+ void OrphanJob();
+
+ // Cancels the request and set the error and ssl info for this request to the
+ // passed values.
+ void DoCancel(int error, const SSLInfo& ssl_info);
+
+ // Called by the URLRequestJob when the headers are received, before any other
+ // method, to allow caching of load timing information.
+ void OnHeadersComplete();
+
+ // Notifies the network delegate that the request has been completed.
+ // This does not imply a successful completion. Also a canceled request is
+ // considered completed.
+ void NotifyRequestCompleted();
+
+ // Called by URLRequestJob to allow interception when the final response
+ // occurs.
+ void NotifyResponseStarted();
+
+ // These functions delegate to |delegate_| and may only be used if
+ // |delegate_| is not NULL. See URLRequest::Delegate for the meaning
+ // of these functions.
+ void NotifyAuthRequired(AuthChallengeInfo* auth_info);
+ void NotifyAuthRequiredComplete(NetworkDelegate::AuthRequiredResponse result);
+ void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
+ void NotifySSLCertificateError(const SSLInfo& ssl_info, bool fatal);
+ void NotifyReadCompleted(int bytes_read);
+
+ // These functions delegate to |network_delegate_| if it is not NULL.
+ // If |network_delegate_| is NULL, cookies can be used unless
+ // SetDefaultCookiePolicyToBlock() has been called.
+ bool CanGetCookies(const CookieList& cookie_list) const;
+ bool CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const;
+ bool CanEnablePrivacyMode() const;
+
+ // Called when the delegate blocks or unblocks this request when intercepting
+ // certain requests.
+ void SetBlockedOnDelegate();
+ void SetUnblockedOnDelegate();
+
+ // Contextual information used for this request. Cannot be NULL. This contains
+ // most of the dependencies which are shared between requests (disk cache,
+ // cookie store, socket pool, etc.)
+ const URLRequestContext* context_;
+
+ NetworkDelegate* network_delegate_;
+
+ // Tracks the time spent in various load states throughout this request.
+ BoundNetLog net_log_;
+
+ scoped_refptr<URLRequestJob> job_;
+ scoped_ptr<UploadDataStream> upload_data_stream_;
+ std::vector<GURL> url_chain_;
+ GURL first_party_for_cookies_;
+ GURL delegate_redirect_url_;
+ std::string method_; // "GET", "POST", etc. Should be all uppercase.
+ std::string referrer_;
+ ReferrerPolicy referrer_policy_;
+ HttpRequestHeaders extra_request_headers_;
+ int load_flags_; // Flags indicating the request type for the load;
+ // expected values are LOAD_* enums above.
+
+ // Never access methods of the |delegate_| directly. Always use the
+ // Notify... methods for this.
+ Delegate* delegate_;
+
+ // Current error status of the job. When no error has been encountered, this
+ // will be SUCCESS. If multiple errors have been encountered, this will be
+ // the first non-SUCCESS status seen.
+ URLRequestStatus status_;
+
+ // The HTTP response info, lazily initialized.
+ HttpResponseInfo response_info_;
+
+ // Tells us whether the job is outstanding. This is true from the time
+ // Start() is called to the time we dispatch RequestComplete and indicates
+ // whether the job is active.
+ bool is_pending_;
+
+ // Indicates if the request is in the process of redirecting to a new
+ // location. It is true from the time the headers complete until a
+ // new request begins.
+ bool is_redirecting_;
+
+ // Number of times we're willing to redirect. Used to guard against
+ // infinite redirects.
+ int redirect_limit_;
+
+ // Cached value for use after we've orphaned the job handling the
+ // first transaction in a request involving redirects.
+ UploadProgress final_upload_progress_;
+
+ // The priority level for this request. Objects like ClientSocketPool use
+ // this to determine which URLRequest to allocate sockets to first.
+ RequestPriority priority_;
+
+ // TODO(battre): The only consumer of the identifier_ is currently the
+ // web request API. We need to match identifiers of requests between the
+ // web request API and the web navigation API. As the URLRequest does not
+ // exist when the web navigation API is triggered, the tracking probably
+ // needs to be done outside of the URLRequest anyway. Therefore, this
+ // identifier should be deleted here. http://crbug.com/89321
+ // A globally unique identifier for this request.
+ const uint64 identifier_;
+
+ // True if this request is blocked waiting for the network delegate to resume
+ // it.
+ bool blocked_on_delegate_;
+
+ // An optional parameter that provides additional information about the load
+ // state. Only used with the LOAD_STATE_WAITING_FOR_DELEGATE state.
+ base::string16 load_state_param_;
+
+ base::debug::LeakTracker<URLRequest> leak_tracker_;
+
+ // Callback passed to the network delegate to notify us when a blocked request
+ // is ready to be resumed or canceled.
+ CompletionCallback before_request_callback_;
+
+ // Safe-guard to ensure that we do not send multiple "I am completed"
+ // messages to network delegate.
+ // TODO(battre): Remove this. http://crbug.com/89049
+ bool has_notified_completion_;
+
+ // Authentication data used by the NetworkDelegate for this request,
+ // if one is present. |auth_credentials_| may be filled in when calling
+ // |NotifyAuthRequired| on the NetworkDelegate. |auth_info_| holds
+ // the authentication challenge being handled by |NotifyAuthRequired|.
+ AuthCredentials auth_credentials_;
+ scoped_refptr<AuthChallengeInfo> auth_info_;
+
+ int64 received_response_content_length_;
+
+ base::TimeTicks creation_time_;
+
+ // Timing information for the most recent request. Its start times are
+ // populated during Start(), and the rest are populated in OnResponseReceived.
+ LoadTimingInfo load_timing_info_;
+
+ scoped_ptr<const base::debug::StackTrace> stack_trace_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequest);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_H_
diff --git a/chromium/net/url_request/url_request_about_job.cc b/chromium/net/url_request/url_request_about_job.cc
new file mode 100644
index 00000000000..52a069a767f
--- /dev/null
+++ b/chromium/net/url_request/url_request_about_job.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 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.
+
+// Simple implementation of about: protocol handler that treats everything as
+// about:blank. No other about: features should be available to web content,
+// so they're not implemented here.
+
+#include "net/url_request/url_request_about_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+
+namespace net {
+
+URLRequestAboutJob::URLRequestAboutJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ weak_factory_(this) {
+}
+
+void URLRequestAboutJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestAboutJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestAboutJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = "text/html";
+ return true;
+}
+
+URLRequestAboutJob::~URLRequestAboutJob() {
+}
+
+void URLRequestAboutJob::StartAsync() {
+ NotifyHeadersComplete();
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_about_job.h b/chromium/net/url_request/url_request_about_job.h
new file mode 100644
index 00000000000..66a888af041
--- /dev/null
+++ b/chromium/net/url_request/url_request_about_job.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestAboutJob : public URLRequestJob {
+ public:
+ URLRequestAboutJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ // URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+
+ private:
+ virtual ~URLRequestAboutJob();
+
+ void StartAsync();
+
+ base::WeakPtrFactory<URLRequestAboutJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
diff --git a/chromium/net/url_request/url_request_context.cc b/chromium/net/url_request/url_request_context.cc
new file mode 100644
index 00000000000..45ac83f3752
--- /dev/null
+++ b/chromium/net/url_request/url_request_context.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_context.h"
+
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
+#include "base/strings/string_util.h"
+#include "net/cookies/cookie_store.h"
+#include "net/dns/host_resolver.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+URLRequestContext::URLRequestContext()
+ : net_log_(NULL),
+ host_resolver_(NULL),
+ cert_verifier_(NULL),
+ server_bound_cert_service_(NULL),
+ fraudulent_certificate_reporter_(NULL),
+ http_auth_handler_factory_(NULL),
+ proxy_service_(NULL),
+ network_delegate_(NULL),
+ http_user_agent_settings_(NULL),
+ transport_security_state_(NULL),
+ http_transaction_factory_(NULL),
+ job_factory_(NULL),
+ throttler_manager_(NULL),
+ url_requests_(new std::set<const URLRequest*>) {
+}
+
+URLRequestContext::~URLRequestContext() {
+ AssertNoURLRequests();
+}
+
+void URLRequestContext::CopyFrom(const URLRequestContext* other) {
+ // Copy URLRequestContext parameters.
+ set_net_log(other->net_log_);
+ set_host_resolver(other->host_resolver_);
+ set_cert_verifier(other->cert_verifier_);
+ set_server_bound_cert_service(other->server_bound_cert_service_);
+ set_fraudulent_certificate_reporter(other->fraudulent_certificate_reporter_);
+ set_http_auth_handler_factory(other->http_auth_handler_factory_);
+ set_proxy_service(other->proxy_service_);
+ set_ssl_config_service(other->ssl_config_service_.get());
+ set_network_delegate(other->network_delegate_);
+ set_http_server_properties(other->http_server_properties_);
+ set_cookie_store(other->cookie_store_.get());
+ set_transport_security_state(other->transport_security_state_);
+ set_http_transaction_factory(other->http_transaction_factory_);
+ set_job_factory(other->job_factory_);
+ set_throttler_manager(other->throttler_manager_);
+ set_http_user_agent_settings(other->http_user_agent_settings_);
+}
+
+const HttpNetworkSession::Params* URLRequestContext::GetNetworkSessionParams(
+ ) const {
+ HttpTransactionFactory* transaction_factory = http_transaction_factory();
+ if (!transaction_factory)
+ return NULL;
+ HttpNetworkSession* network_session = transaction_factory->GetSession();
+ if (!network_session)
+ return NULL;
+ return &network_session->params();
+}
+
+URLRequest* URLRequestContext::CreateRequest(
+ const GURL& url, URLRequest::Delegate* delegate) const {
+ return new URLRequest(url, delegate, this, network_delegate_);
+}
+
+void URLRequestContext::set_cookie_store(CookieStore* cookie_store) {
+ cookie_store_ = cookie_store;
+}
+
+std::string URLRequestContext::GetAcceptLanguage() const {
+ return http_user_agent_settings_ ?
+ http_user_agent_settings_->GetAcceptLanguage() : EmptyString();
+}
+
+std::string URLRequestContext::GetUserAgent(const GURL& url) const {
+ return http_user_agent_settings_ ?
+ http_user_agent_settings_->GetUserAgent(url) : EmptyString();
+}
+
+void URLRequestContext::AssertNoURLRequests() const {
+ int num_requests = url_requests_->size();
+ if (num_requests != 0) {
+ // We're leaking URLRequests :( Dump the URL of the first one and record how
+ // many we leaked so we have an idea of how bad it is.
+ char url_buf[128];
+ const URLRequest* request = *url_requests_->begin();
+ base::strlcpy(url_buf, request->url().spec().c_str(), arraysize(url_buf));
+ bool has_delegate = request->has_delegate();
+ int load_flags = request->load_flags();
+ base::debug::StackTrace stack_trace(NULL, 0);
+ if (request->stack_trace())
+ stack_trace = *request->stack_trace();
+ base::debug::Alias(url_buf);
+ base::debug::Alias(&num_requests);
+ base::debug::Alias(&has_delegate);
+ base::debug::Alias(&load_flags);
+ base::debug::Alias(&stack_trace);
+ CHECK(false) << "Leaked " << num_requests << " URLRequest(s). First URL: "
+ << request->url().spec().c_str() << ".";
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_context.h b/chromium/net/url_request/url_request_context.h
new file mode 100644
index 00000000000..bf9921e4f76
--- /dev/null
+++ b/chromium/net/url_request/url_request_context.h
@@ -0,0 +1,239 @@
+// Copyright (c) 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.
+
+// This class represents contextual information (cookies, cache, etc.)
+// that's useful when processing resource requests.
+// The class is reference-counted so that it can be cleaned up after any
+// requests that are using it have been completed.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/transport_security_state.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+class CertVerifier;
+class CookieStore;
+class FraudulentCertificateReporter;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpTransactionFactory;
+class HttpUserAgentSettings;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class URLRequest;
+class URLRequestJobFactory;
+class URLRequestThrottlerManager;
+
+// Subclass to provide application-specific context for URLRequest
+// instances. Note that URLRequestContext typically does not provide storage for
+// these member variables, since they may be shared. For the ones that aren't
+// shared, URLRequestContextStorage can be helpful in defining their storage.
+class NET_EXPORT URLRequestContext
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ URLRequestContext();
+ virtual ~URLRequestContext();
+
+ // Copies the state from |other| into this context.
+ void CopyFrom(const URLRequestContext* other);
+
+ // May return NULL if this context doesn't have an associated network session.
+ const HttpNetworkSession::Params* GetNetworkSessionParams() const;
+
+ URLRequest* CreateRequest(
+ const GURL& url, URLRequest::Delegate* delegate) const;
+
+ NetLog* net_log() const {
+ return net_log_;
+ }
+
+ void set_net_log(NetLog* net_log) {
+ net_log_ = net_log;
+ }
+
+ HostResolver* host_resolver() const {
+ return host_resolver_;
+ }
+
+ void set_host_resolver(HostResolver* host_resolver) {
+ host_resolver_ = host_resolver;
+ }
+
+ CertVerifier* cert_verifier() const {
+ return cert_verifier_;
+ }
+
+ void set_cert_verifier(CertVerifier* cert_verifier) {
+ cert_verifier_ = cert_verifier;
+ }
+
+ ServerBoundCertService* server_bound_cert_service() const {
+ return server_bound_cert_service_;
+ }
+
+ void set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service) {
+ server_bound_cert_service_ = server_bound_cert_service;
+ }
+
+ FraudulentCertificateReporter* fraudulent_certificate_reporter() const {
+ return fraudulent_certificate_reporter_;
+ }
+ void set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter) {
+ fraudulent_certificate_reporter_ = fraudulent_certificate_reporter;
+ }
+
+ // Get the proxy service for this context.
+ ProxyService* proxy_service() const { return proxy_service_; }
+ void set_proxy_service(ProxyService* proxy_service) {
+ proxy_service_ = proxy_service;
+ }
+
+ // Get the ssl config service for this context.
+ SSLConfigService* ssl_config_service() const {
+ return ssl_config_service_.get();
+ }
+ void set_ssl_config_service(SSLConfigService* service) {
+ ssl_config_service_ = service;
+ }
+
+ // Gets the HTTP Authentication Handler Factory for this context.
+ // The factory is only valid for the lifetime of this URLRequestContext
+ HttpAuthHandlerFactory* http_auth_handler_factory() const {
+ return http_auth_handler_factory_;
+ }
+ void set_http_auth_handler_factory(HttpAuthHandlerFactory* factory) {
+ http_auth_handler_factory_ = factory;
+ }
+
+ // Gets the http transaction factory for this context.
+ HttpTransactionFactory* http_transaction_factory() const {
+ return http_transaction_factory_;
+ }
+ void set_http_transaction_factory(HttpTransactionFactory* factory) {
+ http_transaction_factory_ = factory;
+ }
+
+ void set_network_delegate(NetworkDelegate* network_delegate) {
+ network_delegate_ = network_delegate;
+ }
+ NetworkDelegate* network_delegate() const { return network_delegate_; }
+
+ void set_http_server_properties(
+ const base::WeakPtr<HttpServerProperties>& http_server_properties) {
+ http_server_properties_ = http_server_properties;
+ }
+ base::WeakPtr<HttpServerProperties> http_server_properties() const {
+ return http_server_properties_;
+ }
+
+ // Gets the cookie store for this context (may be null, in which case
+ // cookies are not stored).
+ CookieStore* cookie_store() const { return cookie_store_.get(); }
+ void set_cookie_store(CookieStore* cookie_store);
+
+ TransportSecurityState* transport_security_state() const {
+ return transport_security_state_;
+ }
+ void set_transport_security_state(
+ TransportSecurityState* state) {
+ transport_security_state_ = state;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Legacy accessors that delegate to http_user_agent_settings_.
+ // TODO(pauljensen): Remove after all clients are updated to directly access
+ // http_user_agent_settings_.
+ // Gets the value of 'Accept-Language' header field.
+ std::string GetAcceptLanguage() const;
+ // Gets the UA string to use for the given URL. Pass an invalid URL (such as
+ // GURL()) to get the default UA string.
+ std::string GetUserAgent(const GURL& url) const;
+ // ---------------------------------------------------------------------------
+
+ const URLRequestJobFactory* job_factory() const { return job_factory_; }
+ void set_job_factory(const URLRequestJobFactory* job_factory) {
+ job_factory_ = job_factory;
+ }
+
+ // May be NULL.
+ URLRequestThrottlerManager* throttler_manager() const {
+ return throttler_manager_;
+ }
+ void set_throttler_manager(URLRequestThrottlerManager* throttler_manager) {
+ throttler_manager_ = throttler_manager;
+ }
+
+ // Gets the URLRequest objects that hold a reference to this
+ // URLRequestContext.
+ std::set<const URLRequest*>* url_requests() const {
+ return url_requests_.get();
+ }
+
+ void AssertNoURLRequests() const;
+
+ // Get the underlying |HttpUserAgentSettings| implementation that provides
+ // the HTTP Accept-Language and User-Agent header values.
+ const HttpUserAgentSettings* http_user_agent_settings() const {
+ return http_user_agent_settings_;
+ }
+ void set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings) {
+ http_user_agent_settings_ = http_user_agent_settings;
+ }
+
+ private:
+ // ---------------------------------------------------------------------------
+ // Important: When adding any new members below, consider whether they need to
+ // be added to CopyFrom.
+ // ---------------------------------------------------------------------------
+
+ // Ownership for these members are not defined here. Clients should either
+ // provide storage elsewhere or have a subclass take ownership.
+ NetLog* net_log_;
+ HostResolver* host_resolver_;
+ CertVerifier* cert_verifier_;
+ ServerBoundCertService* server_bound_cert_service_;
+ FraudulentCertificateReporter* fraudulent_certificate_reporter_;
+ HttpAuthHandlerFactory* http_auth_handler_factory_;
+ ProxyService* proxy_service_;
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ NetworkDelegate* network_delegate_;
+ base::WeakPtr<HttpServerProperties> http_server_properties_;
+ HttpUserAgentSettings* http_user_agent_settings_;
+ scoped_refptr<CookieStore> cookie_store_;
+ TransportSecurityState* transport_security_state_;
+ HttpTransactionFactory* http_transaction_factory_;
+ const URLRequestJobFactory* job_factory_;
+ URLRequestThrottlerManager* throttler_manager_;
+
+ // ---------------------------------------------------------------------------
+ // Important: When adding any new members below, consider whether they need to
+ // be added to CopyFrom.
+ // ---------------------------------------------------------------------------
+
+ scoped_ptr<std::set<const URLRequest*> > url_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContext);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
diff --git a/chromium/net/url_request/url_request_context_builder.cc b/chromium/net/url_request/url_request_context_builder.cc
new file mode 100644
index 00000000000..540dfc1082b
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_builder.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_context_builder.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread.h"
+#include "net/base/cache_type.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/dns/host_resolver.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/url_request/data_protocol_handler.h"
+#include "net/url_request/file_protocol_handler.h"
+#include "net/url_request/ftp_protocol_handler.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+
+namespace net {
+
+namespace {
+
+class BasicNetworkDelegate : public NetworkDelegate {
+ public:
+ BasicNetworkDelegate() {}
+ virtual ~BasicNetworkDelegate() {}
+
+ private:
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE {
+ return OK;
+ }
+
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE {}
+
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers)
+ OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE {}
+
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE {}
+
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE {}
+
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE {}
+
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE {}
+
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) OVERRIDE {}
+
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE {
+ return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanAccessFile(const net::URLRequest& request,
+ const base::FilePath& path) const OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanThrottleRequest(const URLRequest& request) const OVERRIDE {
+ return false;
+ }
+
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) OVERRIDE {
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BasicNetworkDelegate);
+};
+
+class BasicURLRequestContext : public URLRequestContext {
+ public:
+ BasicURLRequestContext()
+ : cache_thread_("Cache Thread"),
+ file_thread_("File Thread"),
+ storage_(this) {}
+
+ URLRequestContextStorage* storage() {
+ return &storage_;
+ }
+
+ void StartCacheThread() {
+ cache_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+ }
+
+ scoped_refptr<base::MessageLoopProxy> cache_message_loop_proxy() {
+ DCHECK(cache_thread_.IsRunning());
+ return cache_thread_.message_loop_proxy();
+ }
+
+ void StartFileThread() {
+ file_thread_.StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_DEFAULT, 0));
+ }
+
+ base::MessageLoop* file_message_loop() {
+ DCHECK(file_thread_.IsRunning());
+ return file_thread_.message_loop();
+ }
+
+ protected:
+ virtual ~BasicURLRequestContext() {}
+
+ private:
+ base::Thread cache_thread_;
+ base::Thread file_thread_;
+ URLRequestContextStorage storage_;
+ DISALLOW_COPY_AND_ASSIGN(BasicURLRequestContext);
+};
+
+} // namespace
+
+URLRequestContextBuilder::HttpCacheParams::HttpCacheParams()
+ : type(IN_MEMORY),
+ max_size(0) {}
+URLRequestContextBuilder::HttpCacheParams::~HttpCacheParams() {}
+
+URLRequestContextBuilder::HttpNetworkSessionParams::HttpNetworkSessionParams()
+ : ignore_certificate_errors(false),
+ host_mapping_rules(NULL),
+ http_pipelining_enabled(false),
+ testing_fixed_http_port(0),
+ testing_fixed_https_port(0),
+ trusted_spdy_proxy() {}
+
+URLRequestContextBuilder::HttpNetworkSessionParams::~HttpNetworkSessionParams()
+{}
+
+URLRequestContextBuilder::URLRequestContextBuilder()
+ : data_enabled_(false),
+ file_enabled_(false),
+#if !defined(DISABLE_FTP_SUPPORT)
+ ftp_enabled_(false),
+#endif
+ http_cache_enabled_(true) {}
+URLRequestContextBuilder::~URLRequestContextBuilder() {}
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+void URLRequestContextBuilder::set_proxy_config_service(
+ ProxyConfigService* proxy_config_service) {
+ proxy_config_service_.reset(proxy_config_service);
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+URLRequestContext* URLRequestContextBuilder::Build() {
+ BasicURLRequestContext* context = new BasicURLRequestContext;
+ URLRequestContextStorage* storage = context->storage();
+
+ storage->set_http_user_agent_settings(new StaticHttpUserAgentSettings(
+ accept_language_, user_agent_));
+
+ if (!network_delegate_)
+ network_delegate_.reset(new BasicNetworkDelegate);
+ NetworkDelegate* network_delegate = network_delegate_.release();
+ storage->set_network_delegate(network_delegate);
+
+ storage->set_host_resolver(net::HostResolver::CreateDefaultResolver(NULL));
+
+ context->StartFileThread();
+
+ // TODO(willchan): Switch to using this code when
+ // ProxyService::CreateSystemProxyConfigService()'s signature doesn't suck.
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ ProxyConfigService* proxy_config_service = proxy_config_service_.release();
+#else
+ ProxyConfigService* proxy_config_service =
+ ProxyService::CreateSystemProxyConfigService(
+ base::ThreadTaskRunnerHandle::Get().get(),
+ context->file_message_loop());
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ storage->set_proxy_service(
+ ProxyService::CreateUsingSystemProxyResolver(
+ proxy_config_service,
+ 4, // TODO(willchan): Find a better constant somewhere.
+ context->net_log()));
+ storage->set_ssl_config_service(new net::SSLConfigServiceDefaults);
+ storage->set_http_auth_handler_factory(
+ net::HttpAuthHandlerRegistryFactory::CreateDefault(
+ context->host_resolver()));
+ storage->set_cookie_store(new CookieMonster(NULL, NULL));
+ storage->set_transport_security_state(new net::TransportSecurityState());
+ storage->set_http_server_properties(
+ scoped_ptr<net::HttpServerProperties>(
+ new net::HttpServerPropertiesImpl()));
+ storage->set_cert_verifier(CertVerifier::CreateDefault());
+
+ net::HttpNetworkSession::Params network_session_params;
+ network_session_params.host_resolver = context->host_resolver();
+ network_session_params.cert_verifier = context->cert_verifier();
+ network_session_params.transport_security_state =
+ context->transport_security_state();
+ network_session_params.proxy_service = context->proxy_service();
+ network_session_params.ssl_config_service =
+ context->ssl_config_service();
+ network_session_params.http_auth_handler_factory =
+ context->http_auth_handler_factory();
+ network_session_params.network_delegate = network_delegate;
+ network_session_params.http_server_properties =
+ context->http_server_properties();
+ network_session_params.net_log = context->net_log();
+ network_session_params.ignore_certificate_errors =
+ http_network_session_params_.ignore_certificate_errors;
+ network_session_params.host_mapping_rules =
+ http_network_session_params_.host_mapping_rules;
+ network_session_params.http_pipelining_enabled =
+ http_network_session_params_.http_pipelining_enabled;
+ network_session_params.testing_fixed_http_port =
+ http_network_session_params_.testing_fixed_http_port;
+ network_session_params.testing_fixed_https_port =
+ http_network_session_params_.testing_fixed_https_port;
+ network_session_params.trusted_spdy_proxy =
+ http_network_session_params_.trusted_spdy_proxy;
+
+ HttpTransactionFactory* http_transaction_factory = NULL;
+ if (http_cache_enabled_) {
+ network_session_params.server_bound_cert_service =
+ context->server_bound_cert_service();
+ HttpCache::BackendFactory* http_cache_backend = NULL;
+ if (http_cache_params_.type == HttpCacheParams::DISK) {
+ context->StartCacheThread();
+ http_cache_backend = new HttpCache::DefaultBackend(
+ DISK_CACHE,
+ net::CACHE_BACKEND_DEFAULT,
+ http_cache_params_.path,
+ http_cache_params_.max_size,
+ context->cache_message_loop_proxy().get());
+ } else {
+ http_cache_backend =
+ HttpCache::DefaultBackend::InMemory(http_cache_params_.max_size);
+ }
+
+ http_transaction_factory = new HttpCache(
+ network_session_params, http_cache_backend);
+ } else {
+ scoped_refptr<net::HttpNetworkSession> network_session(
+ new net::HttpNetworkSession(network_session_params));
+
+ http_transaction_factory = new HttpNetworkLayer(network_session.get());
+ }
+ storage->set_http_transaction_factory(http_transaction_factory);
+
+ URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl;
+ if (data_enabled_)
+ job_factory->SetProtocolHandler("data", new DataProtocolHandler);
+ if (file_enabled_)
+ job_factory->SetProtocolHandler("file", new FileProtocolHandler);
+#if !defined(DISABLE_FTP_SUPPORT)
+ if (ftp_enabled_) {
+ ftp_transaction_factory_.reset(
+ new FtpNetworkLayer(context->host_resolver()));
+ job_factory->SetProtocolHandler("ftp",
+ new FtpProtocolHandler(ftp_transaction_factory_.get()));
+ }
+#endif
+ storage->set_job_factory(job_factory);
+
+ // TODO(willchan): Support sdch.
+
+ return context;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_context_builder.h b/chromium/net/url_request/url_request_context_builder.h
new file mode 100644
index 00000000000..20fc30ea9f6
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_builder.h
@@ -0,0 +1,152 @@
+// Copyright (c) 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.
+
+// This class is useful for building a simple URLRequestContext. Most creators
+// of new URLRequestContexts should use this helper class to construct it. Call
+// any configuration params, and when done, invoke Build() to construct the
+// URLRequestContext. This URLRequestContext will own all its own storage.
+//
+// URLRequestContextBuilder and its associated params classes are initially
+// populated with "sane" default values. Read through the comments to figure out
+// what these are.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class FtpTransactionFactory;
+class HostMappingRules;
+class ProxyConfigService;
+class URLRequestContext;
+class NetworkDelegate;
+
+class NET_EXPORT URLRequestContextBuilder {
+ public:
+ struct NET_EXPORT HttpCacheParams {
+ enum Type {
+ IN_MEMORY,
+ DISK,
+ };
+
+ HttpCacheParams();
+ ~HttpCacheParams();
+
+ // The type of HTTP cache. Default is IN_MEMORY.
+ Type type;
+
+ // The max size of the cache in bytes. Default is algorithmically determined
+ // based off available disk space.
+ int max_size;
+
+ // The cache path (when type is DISK).
+ base::FilePath path;
+ };
+
+ struct NET_EXPORT HttpNetworkSessionParams {
+ HttpNetworkSessionParams();
+ ~HttpNetworkSessionParams();
+
+ // These fields mirror those in net::HttpNetworkSession::Params;
+ bool ignore_certificate_errors;
+ HostMappingRules* host_mapping_rules;
+ bool http_pipelining_enabled;
+ uint16 testing_fixed_http_port;
+ uint16 testing_fixed_https_port;
+ std::string trusted_spdy_proxy;
+ };
+
+ URLRequestContextBuilder();
+ ~URLRequestContextBuilder();
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ void set_proxy_config_service(ProxyConfigService* proxy_config_service);
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // Call these functions to specify hard-coded Accept-Language
+ // or User-Agent header values for all requests that don't
+ // have the headers already set.
+ void set_accept_language(const std::string& accept_language) {
+ accept_language_ = accept_language;
+ }
+ void set_user_agent(const std::string& user_agent) {
+ user_agent_ = user_agent;
+ }
+
+ // Control support for data:// requests. By default it's disabled.
+ void set_data_enabled(bool enable) {
+ data_enabled_ = enable;
+ }
+
+ // Control support for file:// requests. By default it's disabled.
+ void set_file_enabled(bool enable) {
+ file_enabled_ = enable;
+ }
+
+#if !defined(DISABLE_FTP_SUPPORT)
+ // Control support for ftp:// requests. By default it's disabled.
+ void set_ftp_enabled(bool enable) {
+ ftp_enabled_ = enable;
+ }
+#endif
+
+ // Uses BasicNetworkDelegate by default. Note that calling Build will unset
+ // any custom delegate in builder, so this must be called each time before
+ // Build is called.
+ void set_network_delegate(NetworkDelegate* delegate) {
+ network_delegate_.reset(delegate);
+ }
+
+ // By default HttpCache is enabled with a default constructed HttpCacheParams.
+ void EnableHttpCache(const HttpCacheParams& params) {
+ http_cache_params_ = params;
+ }
+
+ void DisableHttpCache() {
+ http_cache_params_ = HttpCacheParams();
+ }
+
+ // Override default net::HttpNetworkSession::Params settings.
+ void set_http_network_session_params(
+ const HttpNetworkSessionParams& http_network_session_params) {
+ http_network_session_params_ = http_network_session_params;
+ }
+
+ URLRequestContext* Build();
+
+ private:
+ std::string accept_language_;
+ std::string user_agent_;
+ // Include support for data:// requests.
+ bool data_enabled_;
+ // Include support for file:// requests.
+ bool file_enabled_;
+#if !defined(DISABLE_FTP_SUPPORT)
+ // Include support for ftp:// requests.
+ bool ftp_enabled_;
+#endif
+ bool http_cache_enabled_;
+ HttpCacheParams http_cache_params_;
+ HttpNetworkSessionParams http_network_session_params_;
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ scoped_ptr<ProxyConfigService> proxy_config_service_;
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ scoped_ptr<NetworkDelegate> network_delegate_;
+ scoped_ptr<FtpTransactionFactory> ftp_transaction_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContextBuilder);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
diff --git a/chromium/net/url_request/url_request_context_builder_unittest.cc b/chromium/net/url_request/url_request_context_builder_unittest.cc
new file mode 100644
index 00000000000..f747b4de705
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_builder_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_context_builder.h"
+
+#include "build/build_config.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+namespace net {
+
+namespace {
+
+// A subclass of SpawnedTestServer that uses a statically-configured hostname.
+// This is to work around mysterious failures in chrome_frame_net_tests. See:
+// http://crbug.com/114369
+class LocalHttpTestServer : public SpawnedTestServer {
+ public:
+ explicit LocalHttpTestServer(const base::FilePath& document_root)
+ : SpawnedTestServer(SpawnedTestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ document_root) {}
+ LocalHttpTestServer()
+ : SpawnedTestServer(SpawnedTestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ base::FilePath()) {}
+};
+
+class URLRequestContextBuilderTest : public PlatformTest {
+ protected:
+ URLRequestContextBuilderTest()
+ : test_server_(
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest"))) {
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ builder_.set_proxy_config_service(
+ new ProxyConfigServiceFixed(ProxyConfig::CreateDirect()));
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ }
+
+ LocalHttpTestServer test_server_;
+ URLRequestContextBuilder builder_;
+};
+
+TEST_F(URLRequestContextBuilderTest, DefaultSettings) {
+ ASSERT_TRUE(test_server_.Start());
+
+ scoped_ptr<URLRequestContext> context(builder_.Build());
+ TestDelegate delegate;
+ URLRequest request(
+ test_server_.GetURL("echoheader?Foo"), &delegate, context.get());
+ request.set_method("GET");
+ request.SetExtraRequestHeaderByName("Foo", "Bar", false);
+ request.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("Bar", delegate.data_received());
+}
+
+TEST_F(URLRequestContextBuilderTest, UserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ builder_.set_user_agent("Bar");
+ scoped_ptr<URLRequestContext> context(builder_.Build());
+ TestDelegate delegate;
+ URLRequest request(
+ test_server_.GetURL("echoheader?User-Agent"), &delegate, context.get());
+ request.set_method("GET");
+ request.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("Bar", delegate.data_received());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_context_getter.cc b/chromium/net/url_request/url_request_context_getter.cc
new file mode 100644
index 00000000000..88a6f3d6490
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_getter.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_context_getter.h"
+
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "net/url_request/url_request_context.h"
+
+namespace net {
+
+URLRequestContextGetter::URLRequestContextGetter() {}
+
+URLRequestContextGetter::~URLRequestContextGetter() {}
+
+void URLRequestContextGetter::OnDestruct() const {
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner =
+ GetNetworkTaskRunner();
+ DCHECK(network_task_runner.get());
+ if (network_task_runner.get()) {
+ if (network_task_runner->BelongsToCurrentThread()) {
+ delete this;
+ } else {
+ if (!network_task_runner->DeleteSoon(FROM_HERE, this)) {
+ // Can't force-delete the object here, because some derived classes
+ // can only be deleted on the owning thread, so just emit a warning to
+ // aid in debugging.
+ DLOG(WARNING) << "URLRequestContextGetter leaking due to no owning"
+ << " thread.";
+ }
+ }
+ }
+ // If no IO message loop proxy was available, we will just leak memory.
+ // This is also true if the IO thread is gone.
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_context_getter.h b/chromium/net/url_request/url_request_context_getter.h
new file mode 100644
index 00000000000..4c8e4af55c3
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_getter.h
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#ifndef CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
+#define CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+class CookieStore;
+class URLRequestContext;
+
+struct URLRequestContextGetterTraits;
+
+// Interface for retrieving an net::URLRequestContext.
+class NET_EXPORT URLRequestContextGetter
+ : public base::RefCountedThreadSafe<URLRequestContextGetter,
+ URLRequestContextGetterTraits> {
+ public:
+ virtual URLRequestContext* GetURLRequestContext() = 0;
+
+ // Returns a SingleThreadTaskRunner corresponding to the thread on
+ // which the network IO happens (the thread on which the returned
+ // net::URLRequestContext may be used).
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<URLRequestContextGetter,
+ URLRequestContextGetterTraits>;
+ friend class base::DeleteHelper<URLRequestContextGetter>;
+ friend struct URLRequestContextGetterTraits;
+
+ URLRequestContextGetter();
+ virtual ~URLRequestContextGetter();
+
+ private:
+ // OnDestruct is meant to ensure deletion on the thread on which the request
+ // IO happens.
+ void OnDestruct() const;
+};
+
+struct URLRequestContextGetterTraits {
+ static void Destruct(const URLRequestContextGetter* context_getter) {
+ context_getter->OnDestruct();
+ }
+};
+
+} // namespace net
+
+#endif // CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/chromium/net/url_request/url_request_context_storage.cc b/chromium/net/url_request/url_request_context_storage.cc
new file mode 100644
index 00000000000..2bb508c28eb
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_storage.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_context_storage.h"
+
+#include "base/logging.h"
+#include "net/base/net_log.h"
+#include "net/base/network_delegate.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cookies/cookie_store.h"
+#include "net/dns/host_resolver.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "net/url_request/fraudulent_certificate_reporter.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace net {
+
+URLRequestContextStorage::URLRequestContextStorage(URLRequestContext* context)
+ : context_(context) {
+ DCHECK(context);
+}
+
+URLRequestContextStorage::~URLRequestContextStorage() {}
+
+void URLRequestContextStorage::set_net_log(NetLog* net_log) {
+ context_->set_net_log(net_log);
+ net_log_.reset(net_log);
+}
+
+void URLRequestContextStorage::set_host_resolver(
+ scoped_ptr<HostResolver> host_resolver) {
+ context_->set_host_resolver(host_resolver.get());
+ host_resolver_ = host_resolver.Pass();
+}
+
+void URLRequestContextStorage::set_cert_verifier(CertVerifier* cert_verifier) {
+ context_->set_cert_verifier(cert_verifier);
+ cert_verifier_.reset(cert_verifier);
+}
+
+void URLRequestContextStorage::set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service) {
+ context_->set_server_bound_cert_service(server_bound_cert_service);
+ server_bound_cert_service_.reset(server_bound_cert_service);
+}
+
+void URLRequestContextStorage::set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter) {
+ context_->set_fraudulent_certificate_reporter(
+ fraudulent_certificate_reporter);
+ fraudulent_certificate_reporter_.reset(fraudulent_certificate_reporter);
+}
+
+void URLRequestContextStorage::set_http_auth_handler_factory(
+ HttpAuthHandlerFactory* http_auth_handler_factory) {
+ context_->set_http_auth_handler_factory(http_auth_handler_factory);
+ http_auth_handler_factory_.reset(http_auth_handler_factory);
+}
+
+void URLRequestContextStorage::set_proxy_service(ProxyService* proxy_service) {
+ context_->set_proxy_service(proxy_service);
+ proxy_service_.reset(proxy_service);
+}
+
+void URLRequestContextStorage::set_ssl_config_service(
+ SSLConfigService* ssl_config_service) {
+ context_->set_ssl_config_service(ssl_config_service);
+ ssl_config_service_ = ssl_config_service;
+}
+
+void URLRequestContextStorage::set_network_delegate(
+ NetworkDelegate* network_delegate) {
+ context_->set_network_delegate(network_delegate);
+ network_delegate_.reset(network_delegate);
+}
+
+void URLRequestContextStorage::set_http_server_properties(
+ scoped_ptr<HttpServerProperties> http_server_properties) {
+ http_server_properties_ = http_server_properties.Pass();
+ context_->set_http_server_properties(http_server_properties_->GetWeakPtr());
+}
+
+void URLRequestContextStorage::set_cookie_store(CookieStore* cookie_store) {
+ context_->set_cookie_store(cookie_store);
+ cookie_store_ = cookie_store;
+}
+
+void URLRequestContextStorage::set_transport_security_state(
+ TransportSecurityState* transport_security_state) {
+ context_->set_transport_security_state(transport_security_state);
+ transport_security_state_.reset(transport_security_state);
+}
+
+void URLRequestContextStorage::set_http_transaction_factory(
+ HttpTransactionFactory* http_transaction_factory) {
+ context_->set_http_transaction_factory(http_transaction_factory);
+ http_transaction_factory_.reset(http_transaction_factory);
+}
+
+void URLRequestContextStorage::set_job_factory(
+ URLRequestJobFactory* job_factory) {
+ context_->set_job_factory(job_factory);
+ job_factory_.reset(job_factory);
+}
+
+void URLRequestContextStorage::set_throttler_manager(
+ URLRequestThrottlerManager* throttler_manager) {
+ context_->set_throttler_manager(throttler_manager);
+ throttler_manager_.reset(throttler_manager);
+}
+
+void URLRequestContextStorage::set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings) {
+ context_->set_http_user_agent_settings(http_user_agent_settings);
+ http_user_agent_settings_.reset(http_user_agent_settings);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_context_storage.h b/chromium/net/url_request/url_request_context_storage.h
new file mode 100644
index 00000000000..9c25f3e44e5
--- /dev/null
+++ b/chromium/net/url_request/url_request_context_storage.h
@@ -0,0 +1,103 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class CertVerifier;
+class CookieStore;
+class FraudulentCertificateReporter;
+class FtpTransactionFactory;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpServerProperties;
+class HttpTransactionFactory;
+class HttpUserAgentSettings;
+class NetLog;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class SSLConfigService;
+class TransportSecurityState;
+class URLRequestContext;
+class URLRequestJobFactory;
+class URLRequestThrottlerManager;
+
+// URLRequestContextStorage is a helper class that provides storage for unowned
+// member variables of URLRequestContext.
+class NET_EXPORT URLRequestContextStorage {
+ public:
+ // Note that URLRequestContextStorage does not acquire a reference to
+ // URLRequestContext, since it is often designed to be embedded in a
+ // URLRequestContext subclass.
+ explicit URLRequestContextStorage(URLRequestContext* context);
+ ~URLRequestContextStorage();
+
+ // These setters will set both the member variables and call the setter on the
+ // URLRequestContext object. In all cases, ownership is passed to |this|.
+
+ void set_net_log(NetLog* net_log);
+ void set_host_resolver(scoped_ptr<HostResolver> host_resolver);
+ void set_cert_verifier(CertVerifier* cert_verifier);
+ void set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service);
+ void set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter);
+ void set_http_auth_handler_factory(
+ HttpAuthHandlerFactory* http_auth_handler_factory);
+ void set_proxy_service(ProxyService* proxy_service);
+ void set_ssl_config_service(SSLConfigService* ssl_config_service);
+ void set_network_delegate(NetworkDelegate* network_delegate);
+ void set_http_server_properties(
+ scoped_ptr<HttpServerProperties> http_server_properties);
+ void set_cookie_store(CookieStore* cookie_store);
+ void set_transport_security_state(
+ TransportSecurityState* transport_security_state);
+ void set_http_transaction_factory(
+ HttpTransactionFactory* http_transaction_factory);
+ void set_job_factory(URLRequestJobFactory* job_factory);
+ void set_throttler_manager(URLRequestThrottlerManager* throttler_manager);
+ void set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings);
+
+ private:
+ // We use a raw pointer to prevent reference cycles, since
+ // URLRequestContextStorage can often be contained within a URLRequestContext
+ // subclass.
+ URLRequestContext* const context_;
+
+ // Owned members.
+ scoped_ptr<NetLog> net_log_;
+ scoped_ptr<HostResolver> host_resolver_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ // The ServerBoundCertService must outlive the HttpTransactionFactory.
+ scoped_ptr<ServerBoundCertService> server_bound_cert_service_;
+ scoped_ptr<FraudulentCertificateReporter> fraudulent_certificate_reporter_;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_;
+ scoped_ptr<ProxyService> proxy_service_;
+ // TODO(willchan): Remove refcounting on these members.
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ scoped_ptr<NetworkDelegate> network_delegate_;
+ scoped_ptr<HttpServerProperties> http_server_properties_;
+ scoped_ptr<HttpUserAgentSettings> http_user_agent_settings_;
+ scoped_refptr<CookieStore> cookie_store_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+
+ scoped_ptr<HttpTransactionFactory> http_transaction_factory_;
+ scoped_ptr<URLRequestJobFactory> job_factory_;
+ scoped_ptr<URLRequestThrottlerManager> throttler_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContextStorage);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
diff --git a/chromium/net/url_request/url_request_data_job.cc b/chromium/net/url_request/url_request_data_job.cc
new file mode 100644
index 00000000000..fd248c7d07a
--- /dev/null
+++ b/chromium/net/url_request/url_request_data_job.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+// Simple implementation of a data: protocol handler.
+
+#include "net/url_request/url_request_data_job.h"
+
+#include "net/base/data_url.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+URLRequestDataJob::URLRequestDataJob(
+ URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestSimpleJob(request, network_delegate) {
+}
+
+int URLRequestDataJob::GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const {
+ // Check if data URL is valid. If not, don't bother to try to extract data.
+ // Otherwise, parse the data from the data URL.
+ const GURL& url = request_->url();
+ if (!url.is_valid())
+ return ERR_INVALID_URL;
+ return DataURL::Parse(url, mime_type, charset, data)? OK: ERR_INVALID_URL;
+}
+
+URLRequestDataJob::~URLRequestDataJob() {
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_data_job.h b/chromium/net/url_request/url_request_data_job.h
new file mode 100644
index 00000000000..d9b26214b34
--- /dev/null
+++ b/chromium/net/url_request/url_request_data_job.h
@@ -0,0 +1,35 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
+
+#include <string>
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_simple_job.h"
+
+namespace net {
+
+class URLRequest;
+
+class URLRequestDataJob : public URLRequestSimpleJob {
+ public:
+ URLRequestDataJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ // URLRequestSimpleJob
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const OVERRIDE;
+
+ private:
+ virtual ~URLRequestDataJob();
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestDataJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
diff --git a/chromium/net/url_request/url_request_error_job.cc b/chromium/net/url_request/url_request_error_job.cc
new file mode 100644
index 00000000000..0b2749bd933
--- /dev/null
+++ b/chromium/net/url_request/url_request_error_job.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_error_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+URLRequestErrorJob::URLRequestErrorJob(
+ URLRequest* request, NetworkDelegate* network_delegate, int error)
+ : URLRequestJob(request, network_delegate),
+ error_(error),
+ weak_factory_(this) {}
+
+URLRequestErrorJob::~URLRequestErrorJob() {}
+
+void URLRequestErrorJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestErrorJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestErrorJob::StartAsync() {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error_));
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_error_job.h b/chromium/net/url_request/url_request_error_job.h
new file mode 100644
index 00000000000..7c162f0c493
--- /dev/null
+++ b/chromium/net/url_request/url_request_error_job.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 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.
+//
+// Invalid URLs go through this URLRequestJob class rather than being
+// passed to the default job handler.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestErrorJob : public URLRequestJob {
+ public:
+ URLRequestErrorJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ int error);
+
+ virtual void Start() OVERRIDE;
+
+ private:
+ virtual ~URLRequestErrorJob();
+
+ void StartAsync();
+
+ int error_;
+
+ base::WeakPtrFactory<URLRequestErrorJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
diff --git a/chromium/net/url_request/url_request_file_dir_job.cc b/chromium/net/url_request/url_request_file_dir_job.cc
new file mode 100644
index 00000000000..39093701095
--- /dev/null
+++ b/chromium/net/url_request/url_request_file_dir_job.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_file_dir_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_status.h"
+#include "url/gurl.h"
+
+#if defined(OS_POSIX)
+#include <sys/stat.h>
+#endif
+
+namespace net {
+
+URLRequestFileDirJob::URLRequestFileDirJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const base::FilePath& dir_path)
+ : URLRequestJob(request, network_delegate),
+ lister_(dir_path, this),
+ dir_path_(dir_path),
+ canceled_(false),
+ list_complete_(false),
+ wrote_header_(false),
+ read_pending_(false),
+ read_buffer_length_(0),
+ weak_factory_(this) {
+}
+
+void URLRequestFileDirJob::StartAsync() {
+ lister_.Start();
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileDirJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFileDirJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestFileDirJob::Kill() {
+ if (canceled_)
+ return;
+
+ canceled_ = true;
+
+ if (!list_complete_)
+ lister_.Cancel();
+
+ URLRequestJob::Kill();
+
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool URLRequestFileDirJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (FillReadBuffer(buf->data(), buf_size, bytes_read))
+ return true;
+
+ // We are waiting for more data
+ read_pending_ = true;
+ read_buffer_ = buf;
+ read_buffer_length_ = buf_size;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return false;
+}
+
+bool URLRequestFileDirJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = "text/html";
+ return true;
+}
+
+bool URLRequestFileDirJob::GetCharset(std::string* charset) {
+ // All the filenames are converted to UTF-8 before being added.
+ *charset = "utf-8";
+ return true;
+}
+
+void URLRequestFileDirJob::OnListFile(
+ const DirectoryLister::DirectoryListerData& data) {
+ // We wait to write out the header until we get the first file, so that we
+ // can catch errors from DirectoryLister and show an error page.
+ if (!wrote_header_) {
+#if defined(OS_WIN)
+ const base::string16& title = dir_path_.value();
+#elif defined(OS_POSIX)
+ // TODO(jungshik): Add SysNativeMBToUTF16 to sys_string_conversions.
+ // On Mac, need to add NFKC->NFC conversion either here or in file_path.
+ // On Linux, the file system encoding is not defined, but we assume that
+ // SysNativeMBToWide takes care of it at least for now. We can try something
+ // more sophisticated if necessary later.
+ const base::string16& title = WideToUTF16(
+ base::SysNativeMBToWide(dir_path_.value()));
+#endif
+ data_.append(GetDirectoryListingHeader(title));
+ wrote_header_ = true;
+ }
+
+#if defined(OS_WIN)
+ std::string raw_bytes; // Empty on Windows means UTF-8 encoded name.
+#elif defined(OS_POSIX)
+ // TOOD(jungshik): The same issue as for the directory name.
+ const std::string& raw_bytes = data.info.GetName().value();
+#endif
+ data_.append(GetDirectoryListingEntry(
+ data.info.GetName().LossyDisplayName(),
+ raw_bytes,
+ data.info.IsDirectory(),
+ data.info.GetSize(),
+ data.info.GetLastModifiedTime()));
+
+ // TODO(darin): coalesce more?
+ CompleteRead();
+}
+
+void URLRequestFileDirJob::OnListDone(int error) {
+ DCHECK(!canceled_);
+ if (error != OK) {
+ read_pending_ = false;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error));
+ } else {
+ list_complete_ = true;
+ CompleteRead();
+ }
+}
+
+URLRequestFileDirJob::~URLRequestFileDirJob() {}
+
+void URLRequestFileDirJob::CompleteRead() {
+ if (read_pending_) {
+ int bytes_read;
+ if (FillReadBuffer(read_buffer_->data(), read_buffer_length_,
+ &bytes_read)) {
+ // We completed the read, so reset the read buffer.
+ read_pending_ = false;
+ read_buffer_ = NULL;
+ read_buffer_length_ = 0;
+
+ SetStatus(URLRequestStatus());
+ NotifyReadComplete(bytes_read);
+ } else {
+ NOTREACHED();
+ // TODO: Better error code.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 0));
+ }
+ }
+}
+
+bool URLRequestFileDirJob::FillReadBuffer(char* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+
+ *bytes_read = 0;
+
+ int count = std::min(buf_size, static_cast<int>(data_.size()));
+ if (count) {
+ memcpy(buf, &data_[0], count);
+ data_.erase(0, count);
+ *bytes_read = count;
+ return true;
+ } else if (list_complete_) {
+ // EOF
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_file_dir_job.h b/chromium/net/url_request/url_request_file_dir_job.h
new file mode 100644
index 00000000000..f8974930d01
--- /dev/null
+++ b/chromium/net/url_request/url_request_file_dir_job.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/directory_lister.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class URLRequestFileDirJob
+ : public URLRequestJob,
+ public DirectoryLister::DirectoryListerDelegate {
+ public:
+ URLRequestFileDirJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const base::FilePath& dir_path);
+
+ bool list_complete() const { return list_complete_; }
+
+ virtual void StartAsync();
+
+ // Overridden from URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ // Overridden from DirectoryLister::DirectoryListerDelegate:
+ virtual void OnListFile(
+ const DirectoryLister::DirectoryListerData& data) OVERRIDE;
+ virtual void OnListDone(int error) OVERRIDE;
+
+ private:
+ virtual ~URLRequestFileDirJob();
+
+ void CloseLister();
+
+ // When we have data and a read has been pending, this function
+ // will fill the response buffer and notify the request
+ // appropriately.
+ void CompleteRead();
+
+ // Fills a buffer with the output.
+ bool FillReadBuffer(char *buf, int buf_size, int *bytes_read);
+
+ DirectoryLister lister_;
+ base::FilePath dir_path_;
+ std::string data_;
+ bool canceled_;
+
+ // Indicates whether we have the complete list of the dir
+ bool list_complete_;
+
+ // Indicates whether we have written the HTML header
+ bool wrote_header_;
+
+ // To simulate Async IO, we hold onto the Reader's buffer while
+ // we wait for IO to complete. When done, we fill the buffer
+ // manually.
+ bool read_pending_;
+ scoped_refptr<IOBuffer> read_buffer_;
+ int read_buffer_length_;
+ base::WeakPtrFactory<URLRequestFileDirJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFileDirJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
diff --git a/chromium/net/url_request/url_request_file_job.cc b/chromium/net/url_request/url_request_file_job.cc
new file mode 100644
index 00000000000..437f962393e
--- /dev/null
+++ b/chromium/net/url_request/url_request_file_job.cc
@@ -0,0 +1,300 @@
+// Copyright (c) 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.
+
+// For loading files, we make use of overlapped i/o to ensure that reading from
+// the filesystem (e.g., a network filesystem) does not block the calling
+// thread. An alternative approach would be to use a background thread or pool
+// of threads, but it seems better to leverage the operating system's ability
+// to do background file reads for us.
+//
+// Since overlapped reads require a 'static' buffer for the duration of the
+// asynchronous read, the URLRequestFileJob keeps a buffer as a member var. In
+// URLRequestFileJob::Read, data is simply copied from the object's buffer into
+// the given buffer. If there is no data to copy, the URLRequestFileJob
+// attempts to read more from the file to fill its buffer. If reading from the
+// file does not complete synchronously, then the URLRequestFileJob waits for a
+// signal from the OS that the overlapped read has completed. It does so by
+// leveraging the MessageLoop::WatchObject API.
+
+#include "net/url_request/url_request_file_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
+#include "build/build_config.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_dir_job.h"
+#include "url/gurl.h"
+
+#if defined(OS_WIN)
+#include "base/win/shortcut.h"
+#endif
+
+namespace net {
+
+URLRequestFileJob::FileMetaInfo::FileMetaInfo()
+ : file_size(0),
+ mime_type_result(false),
+ file_exists(false),
+ is_directory(false) {
+}
+
+URLRequestFileJob::URLRequestFileJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const base::FilePath& file_path)
+ : URLRequestJob(request, network_delegate),
+ file_path_(file_path),
+ stream_(new FileStream(NULL)),
+ remaining_bytes_(0),
+ weak_ptr_factory_(this) {
+}
+
+void URLRequestFileJob::Start() {
+ FileMetaInfo* meta_info = new FileMetaInfo();
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&URLRequestFileJob::FetchMetaInfo, file_path_,
+ base::Unretained(meta_info)),
+ base::Bind(&URLRequestFileJob::DidFetchMetaInfo,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(meta_info)),
+ true);
+}
+
+void URLRequestFileJob::Kill() {
+ stream_.reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ URLRequestJob::Kill();
+}
+
+bool URLRequestFileJob::ReadRawData(IOBuffer* dest, int dest_size,
+ int *bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ int rv = stream_->Read(dest, dest_size,
+ base::Bind(&URLRequestFileJob::DidRead,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv >= 0) {
+ // Data is immediately available.
+ *bytes_read = rv;
+ remaining_bytes_ -= rv;
+ DCHECK_GE(remaining_bytes_, 0);
+ return true;
+ }
+
+ // Otherwise, a read error occured. We may just need to wait...
+ if (rv == ERR_IO_PENDING) {
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+bool URLRequestFileJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (meta_info_.is_directory) {
+ // This happens when we discovered the file is a directory, so needs a
+ // slash at the end of the path.
+ std::string new_path = request_->url().path();
+ new_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(new_path);
+
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+
+#if defined(OS_WIN)
+ // Follow a Windows shortcut.
+ // We just resolve .lnk file, ignore others.
+ if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk"))
+ return false;
+
+ base::FilePath new_path = file_path_;
+ bool resolved;
+ resolved = base::win::ResolveShortcut(new_path, &new_path, NULL);
+
+ // If shortcut is not resolved succesfully, do not redirect.
+ if (!resolved)
+ return false;
+
+ *location = FilePathToFileURL(new_path);
+ *http_status_code = 301;
+ return true;
+#else
+ return false;
+#endif
+}
+
+Filter* URLRequestFileJob::SetupFilter() const {
+ // Bug 9936 - .svgz files needs to be decompressed.
+ return LowerCaseEqualsASCII(file_path_.Extension(), ".svgz")
+ ? Filter::GZipFactory() : NULL;
+}
+
+bool URLRequestFileJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(request_);
+ if (meta_info_.mime_type_result) {
+ *mime_type = meta_info_.mime_type;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestFileJob::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(HttpRequestHeaders::kRange, &range_header)) {
+ // We only care about "Range" header here.
+ std::vector<HttpByteRange> ranges;
+ if (HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ // TODO(hclam): decide whether we want to support multiple range
+ // requests.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
+ }
+ }
+}
+
+URLRequestFileJob::~URLRequestFileJob() {
+}
+
+void URLRequestFileJob::FetchMetaInfo(const base::FilePath& file_path,
+ FileMetaInfo* meta_info) {
+ base::PlatformFileInfo platform_info;
+ meta_info->file_exists = file_util::GetFileInfo(file_path, &platform_info);
+ if (meta_info->file_exists) {
+ meta_info->file_size = platform_info.size;
+ meta_info->is_directory = platform_info.is_directory;
+ }
+ // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be
+ // done in WorkerPool.
+ meta_info->mime_type_result = GetMimeTypeFromFile(file_path,
+ &meta_info->mime_type);
+}
+
+void URLRequestFileJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) {
+ meta_info_ = *meta_info;
+
+ // We use URLRequestFileJob to handle files as well as directories without
+ // trailing slash.
+ // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise,
+ // we will append trailing slash and redirect to FileDirJob.
+ // A special case is "\" on Windows. We should resolve as invalid.
+ // However, Windows resolves "\" to "C:\", thus reports it as existent.
+ // So what happens is we append it with trailing slash and redirect it to
+ // FileDirJob where it is resolved as invalid.
+ if (!meta_info_.file_exists) {
+ DidOpen(ERR_FILE_NOT_FOUND);
+ return;
+ }
+ if (meta_info_.is_directory) {
+ DidOpen(OK);
+ return;
+ }
+
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ int rv = stream_->Open(file_path_, flags,
+ base::Bind(&URLRequestFileJob::DidOpen,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != ERR_IO_PENDING)
+ DidOpen(rv);
+}
+
+void URLRequestFileJob::DidOpen(int result) {
+ if (result != OK) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ if (!byte_range_.ComputeBounds(meta_info_.file_size)) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) {
+ int rv = stream_->Seek(FROM_BEGIN, byte_range_.first_byte_position(),
+ base::Bind(&URLRequestFileJob::DidSeek,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != ERR_IO_PENDING) {
+ // stream_->Seek() failed, so pass an intentionally erroneous value
+ // into DidSeek().
+ DidSeek(-1);
+ }
+ } else {
+ // We didn't need to call stream_->Seek() at all, so we pass to DidSeek()
+ // the value that would mean seek success. This way we skip the code
+ // handling seek failure.
+ DidSeek(byte_range_.first_byte_position());
+ }
+}
+
+void URLRequestFileJob::DidSeek(int64 result) {
+ if (result != byte_range_.first_byte_position()) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ set_expected_content_size(remaining_bytes_);
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileJob::DidRead(int result) {
+ if (result > 0) {
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+ } else if (result == 0) {
+ NotifyDone(URLRequestStatus());
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ NotifyReadComplete(result);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_file_job.h b/chromium/net/url_request/url_request_file_job.h
new file mode 100644
index 00000000000..6fc7fb91980
--- /dev/null
+++ b/chromium/net/url_request/url_request_file_job.h
@@ -0,0 +1,105 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace base{
+struct PlatformFileInfo;
+}
+namespace file_util {
+struct FileInfo;
+}
+
+namespace net {
+
+class FileStream;
+
+// A request job that handles reading file URLs
+class NET_EXPORT URLRequestFileJob : public URLRequestJob {
+ public:
+ URLRequestFileJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const base::FilePath& file_path);
+
+ // URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+ virtual Filter* SetupFilter() const OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestFileJob();
+
+ // The OS-specific full path name of the file
+ base::FilePath file_path_;
+
+ private:
+ // Meta information about the file. It's used as a member in the
+ // URLRequestFileJob and also passed between threads because disk access is
+ // necessary to obtain it.
+ struct FileMetaInfo {
+ FileMetaInfo();
+
+ // Size of the file.
+ int64 file_size;
+ // Mime type associated with the file.
+ std::string mime_type;
+ // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether
+ // obtaining of the mime type was successful.
+ bool mime_type_result;
+ // Flag showing whether the file exists.
+ bool file_exists;
+ // Flag showing whether the file name actually refers to a directory.
+ bool is_directory;
+ };
+
+ // Fetches file info on a background thread.
+ static void FetchMetaInfo(const base::FilePath& file_path,
+ FileMetaInfo* meta_info);
+
+ // Callback after fetching file info on a background thread.
+ void DidFetchMetaInfo(const FileMetaInfo* meta_info);
+
+ // Callback after opening file on a background thread.
+ void DidOpen(int result);
+
+ // Callback after seeking to the beginning of |byte_range_| in the file
+ // on a background thread.
+ void DidSeek(int64 result);
+
+ // Callback after data is asynchronously read from the file.
+ void DidRead(int result);
+
+ scoped_ptr<FileStream> stream_;
+ FileMetaInfo meta_info_;
+
+ HttpByteRange byte_range_;
+ int64 remaining_bytes_;
+
+ base::WeakPtrFactory<URLRequestFileJob> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFileJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
diff --git a/chromium/net/url_request/url_request_filter.cc b/chromium/net/url_request/url_request_filter.cc
new file mode 100644
index 00000000000..da0b5803ab5
--- /dev/null
+++ b/chromium/net/url_request/url_request_filter.cc
@@ -0,0 +1,195 @@
+// Copyright (c) 2011 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/url_request/url_request_filter.h"
+
+#include <set>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace net {
+
+namespace {
+
+class URLRequestFilterProtocolHandler
+ : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit URLRequestFilterProtocolHandler(URLRequest::ProtocolFactory* factory)
+ : factory_(factory) {}
+ virtual ~URLRequestFilterProtocolHandler() {}
+
+ // URLRequestJobFactory::ProtocolHandler implementation
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ return factory_(request, network_delegate, request->url().scheme());
+ }
+
+ private:
+ URLRequest::ProtocolFactory* factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFilterProtocolHandler);
+};
+
+} // namespace
+
+URLRequestFilter* URLRequestFilter::shared_instance_ = NULL;
+
+URLRequestFilter::~URLRequestFilter() {}
+
+// static
+URLRequestJob* URLRequestFilter::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ // Returning null here just means that the built-in handler will be used.
+ return GetInstance()->FindRequestHandler(request, network_delegate, scheme);
+}
+
+// static
+URLRequestFilter* URLRequestFilter::GetInstance() {
+ if (!shared_instance_)
+ shared_instance_ = new URLRequestFilter;
+ return shared_instance_;
+}
+
+void URLRequestFilter::AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname, URLRequest::ProtocolFactory* factory) {
+ AddHostnameProtocolHandler(
+ scheme, hostname,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler>(
+ new URLRequestFilterProtocolHandler(factory)));
+}
+
+void URLRequestFilter::AddHostnameProtocolHandler(
+ const std::string& scheme,
+ const std::string& hostname,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler> protocol_handler) {
+ DCHECK_EQ(0u, hostname_handler_map_.count(make_pair(scheme, hostname)));
+ hostname_handler_map_[make_pair(scheme, hostname)] =
+ protocol_handler.release();
+
+ // Register with the ProtocolFactory.
+ URLRequest::Deprecated::RegisterProtocolFactory(
+ scheme, &URLRequestFilter::Factory);
+
+#ifndef NDEBUG
+ // Check to see if we're masking URLs in the url_handler_map_.
+ for (UrlHandlerMap::const_iterator i = url_handler_map_.begin();
+ i != url_handler_map_.end(); ++i) {
+ const GURL& url = GURL(i->first);
+ HostnameHandlerMap::iterator host_it =
+ hostname_handler_map_.find(make_pair(url.scheme(), url.host()));
+ if (host_it != hostname_handler_map_.end())
+ NOTREACHED();
+ }
+#endif // !NDEBUG
+}
+
+void URLRequestFilter::RemoveHostnameHandler(const std::string& scheme,
+ const std::string& hostname) {
+ HostnameHandlerMap::iterator iter =
+ hostname_handler_map_.find(make_pair(scheme, hostname));
+ DCHECK(iter != hostname_handler_map_.end());
+
+ delete iter->second;
+ hostname_handler_map_.erase(iter);
+ // Note that we don't unregister from the URLRequest ProtocolFactory as
+ // this would left no protocol factory for the scheme.
+ // URLRequestFilter::Factory will keep forwarding the requests to the
+ // URLRequestInetJob.
+}
+
+bool URLRequestFilter::AddUrlHandler(
+ const GURL& url,
+ URLRequest::ProtocolFactory* factory) {
+ return AddUrlProtocolHandler(
+ url,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler>(
+ new URLRequestFilterProtocolHandler(factory)));
+}
+
+
+bool URLRequestFilter::AddUrlProtocolHandler(
+ const GURL& url,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler> protocol_handler) {
+ if (!url.is_valid())
+ return false;
+ DCHECK_EQ(0u, url_handler_map_.count(url.spec()));
+ url_handler_map_[url.spec()] = protocol_handler.release();
+
+ // Register with the ProtocolFactory.
+ URLRequest::Deprecated::RegisterProtocolFactory(url.scheme(),
+ &URLRequestFilter::Factory);
+ // Check to see if this URL is masked by a hostname handler.
+ DCHECK_EQ(0u, hostname_handler_map_.count(make_pair(url.scheme(),
+ url.host())));
+
+ return true;
+}
+
+void URLRequestFilter::RemoveUrlHandler(const GURL& url) {
+ UrlHandlerMap::iterator iter = url_handler_map_.find(url.spec());
+ DCHECK(iter != url_handler_map_.end());
+
+ delete iter->second;
+ url_handler_map_.erase(iter);
+ // Note that we don't unregister from the URLRequest ProtocolFactory as
+ // this would left no protocol factory for the scheme.
+ // URLRequestFilter::Factory will keep forwarding the requests to the
+ // URLRequestInetJob.
+}
+
+void URLRequestFilter::ClearHandlers() {
+ // Unregister with the ProtocolFactory.
+ std::set<std::string> schemes;
+ for (UrlHandlerMap::const_iterator i = url_handler_map_.begin();
+ i != url_handler_map_.end(); ++i) {
+ schemes.insert(GURL(i->first).scheme());
+ }
+ for (HostnameHandlerMap::const_iterator i = hostname_handler_map_.begin();
+ i != hostname_handler_map_.end(); ++i) {
+ schemes.insert(i->first.first);
+ }
+ for (std::set<std::string>::const_iterator scheme = schemes.begin();
+ scheme != schemes.end(); ++scheme) {
+ URLRequest::Deprecated::RegisterProtocolFactory(*scheme, NULL);
+ }
+
+ STLDeleteValues(&url_handler_map_);
+ STLDeleteValues(&hostname_handler_map_);
+ hit_count_ = 0;
+}
+
+URLRequestFilter::URLRequestFilter() : hit_count_(0) { }
+
+URLRequestJob* URLRequestFilter::FindRequestHandler(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ URLRequestJob* job = NULL;
+ if (request->url().is_valid()) {
+ // Check the hostname map first.
+ const std::string& hostname = request->url().host();
+
+ HostnameHandlerMap::iterator i =
+ hostname_handler_map_.find(make_pair(scheme, hostname));
+ if (i != hostname_handler_map_.end())
+ job = i->second->MaybeCreateJob(request, network_delegate);
+
+ if (!job) {
+ // Not in the hostname map, check the url map.
+ const std::string& url = request->url().spec();
+ UrlHandlerMap::iterator i = url_handler_map_.find(url);
+ if (i != url_handler_map_.end())
+ job = i->second->MaybeCreateJob(request, network_delegate);
+ }
+ }
+ if (job) {
+ DVLOG(1) << "URLRequestFilter hit for " << request->url().spec();
+ hit_count_++;
+ }
+ return job;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_filter.h b/chromium/net/url_request/url_request_filter.h
new file mode 100644
index 00000000000..2859c98c1d8
--- /dev/null
+++ b/chromium/net/url_request/url_request_filter.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2011 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.
+//
+// A class to help filter URLRequest jobs based on the URL of the request
+// rather than just the scheme. Example usage:
+//
+// // Use as an "http" handler.
+// URLRequest::RegisterProtocolFactory("http", &URLRequestFilter::Factory);
+// // Add special handling for the URL http://foo.com/
+// URLRequestFilter::GetInstance()->AddUrlHandler(
+// GURL("http://foo.com/"),
+// &URLRequestCustomJob::Factory);
+//
+// If URLRequestFilter::Factory can't find a handle for the request, it passes
+// it through to URLRequestInetJob::Factory and lets the default network stack
+// handle it.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILTER_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILTER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_factory.h"
+
+class GURL;
+
+namespace net {
+class URLRequestJob;
+
+class NET_EXPORT URLRequestFilter {
+ public:
+ // scheme,hostname -> ProtocolHandler
+ typedef std::map<std::pair<std::string, std::string>,
+ URLRequestJobFactory::ProtocolHandler* > HostnameHandlerMap;
+ // URL -> ProtocolHandler
+ typedef base::hash_map<std::string, URLRequestJobFactory::ProtocolHandler*>
+ UrlHandlerMap;
+
+ ~URLRequestFilter();
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // Singleton instance for use.
+ static URLRequestFilter* GetInstance();
+
+ void AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname,
+ URLRequest::ProtocolFactory* factory);
+ void AddHostnameProtocolHandler(
+ const std::string& scheme,
+ const std::string& hostname,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler> protocol_handler);
+ void RemoveHostnameHandler(const std::string& scheme,
+ const std::string& hostname);
+
+ // Returns true if we successfully added the URL handler. This will replace
+ // old handlers for the URL if one existed.
+ bool AddUrlHandler(const GURL& url,
+ URLRequest::ProtocolFactory* factory);
+ bool AddUrlProtocolHandler(
+ const GURL& url,
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler> protocol_handler);
+
+ void RemoveUrlHandler(const GURL& url);
+
+ // Clear all the existing URL handlers and unregister with the
+ // ProtocolFactory. Resets the hit count.
+ void ClearHandlers();
+
+ // Returns the number of times a handler was used to service a request.
+ int hit_count() const { return hit_count_; }
+
+ protected:
+ URLRequestFilter();
+
+ // Helper method that looks up the request in the url_handler_map_.
+ URLRequestJob* FindRequestHandler(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ // Maps hostnames to factories. Hostnames take priority over URLs.
+ HostnameHandlerMap hostname_handler_map_;
+
+ // Maps URLs to factories.
+ UrlHandlerMap url_handler_map_;
+
+ int hit_count_;
+
+ private:
+ // Singleton instance.
+ static URLRequestFilter* shared_instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFilter);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILTER_H_
diff --git a/chromium/net/url_request/url_request_filter_unittest.cc b/chromium/net/url_request/url_request_filter_unittest.cc
new file mode 100644
index 00000000000..3a4146672ad
--- /dev/null
+++ b/chromium/net/url_request/url_request_filter_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_filter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+URLRequestTestJob* job_a;
+
+URLRequestJob* FactoryA(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ job_a = new URLRequestTestJob(request, network_delegate);
+ return job_a;
+}
+
+URLRequestTestJob* job_b;
+
+URLRequestJob* FactoryB(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ job_b = new URLRequestTestJob(request, network_delegate);
+ return job_b;
+}
+
+URLRequestTestJob* job_c;
+
+class TestProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ virtual ~TestProtocolHandler() {}
+
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ job_c = new URLRequestTestJob(request, network_delegate);
+ return job_c;
+ }
+};
+
+TEST(URLRequestFilter, BasicMatching) {
+ TestDelegate delegate;
+ TestURLRequestContext request_context;
+
+ GURL url_1("http://foo.com/");
+ TestURLRequest request_1(url_1, &delegate, &request_context, NULL);
+
+ GURL url_2("http://bar.com/");
+ TestURLRequest request_2(url_2, &delegate, &request_context, NULL);
+
+ // Check AddUrlHandler checks for invalid URLs.
+ EXPECT_FALSE(URLRequestFilter::GetInstance()->AddUrlHandler(GURL(),
+ &FactoryA));
+
+ // Check URL matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_TRUE(URLRequestFilter::GetInstance()->AddUrlHandler(url_1,
+ &FactoryA));
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, NULL, url_1.scheme());
+ EXPECT_EQ(job_a, found);
+ EXPECT_TRUE(job_a != NULL);
+ job_a = NULL;
+ }
+ EXPECT_EQ(URLRequestFilter::GetInstance()->hit_count(), 1);
+
+ // Check we don't match other URLs.
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_2, NULL, url_2.scheme()) == NULL);
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we can remove URL matching.
+ URLRequestFilter::GetInstance()->RemoveUrlHandler(url_1);
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_1, NULL, url_1.scheme()) == NULL);
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check hostname matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_EQ(0, URLRequestFilter::GetInstance()->hit_count());
+ URLRequestFilter::GetInstance()->AddHostnameHandler(url_1.scheme(),
+ url_1.host(),
+ &FactoryB);
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, NULL, url_1.scheme());
+ EXPECT_EQ(job_b, found);
+ EXPECT_TRUE(job_b != NULL);
+ job_b = NULL;
+ }
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we don't match other hostnames.
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_2, NULL, url_2.scheme()) == NULL);
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we can remove hostname matching.
+ URLRequestFilter::GetInstance()->RemoveHostnameHandler(url_1.scheme(),
+ url_1.host());
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_1, NULL, url_1.scheme()) == NULL);
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check ProtocolHandler hostname matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_EQ(0, URLRequestFilter::GetInstance()->hit_count());
+ URLRequestFilter::GetInstance()->AddHostnameProtocolHandler(
+ url_1.scheme(), url_1.host(),
+ scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ new TestProtocolHandler()));
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, NULL, url_1.scheme());
+ EXPECT_EQ(job_c, found);
+ EXPECT_TRUE(job_c != NULL);
+ job_c = NULL;
+ }
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check ProtocolHandler URL matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_EQ(0, URLRequestFilter::GetInstance()->hit_count());
+ URLRequestFilter::GetInstance()->AddUrlProtocolHandler(
+ url_2,
+ scoped_ptr<net::URLRequestJobFactory::ProtocolHandler>(
+ new TestProtocolHandler()));
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_2, NULL, url_2.scheme());
+ EXPECT_EQ(job_c, found);
+ EXPECT_TRUE(job_c != NULL);
+ job_c = NULL;
+ }
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ URLRequestFilter::GetInstance()->ClearHandlers();
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_ftp_job.cc b/chromium/net/url_request/url_request_ftp_job.cc
new file mode 100644
index 00000000000..eda245ca761
--- /dev/null
+++ b/chromium/net/url_request/url_request_ftp_job.cc
@@ -0,0 +1,407 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_ftp_job.h"
+
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_auth_cache.h"
+#include "net/ftp/ftp_response_info.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+
+namespace net {
+
+URLRequestFtpJob::URLRequestFtpJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache)
+ : URLRequestJob(request, network_delegate),
+ priority_(DEFAULT_PRIORITY),
+ proxy_service_(request_->context()->proxy_service()),
+ pac_request_(NULL),
+ http_response_info_(NULL),
+ read_in_progress_(false),
+ weak_factory_(this),
+ ftp_transaction_factory_(ftp_transaction_factory),
+ ftp_auth_cache_(ftp_auth_cache) {
+ DCHECK(proxy_service_);
+ DCHECK(ftp_transaction_factory);
+ DCHECK(ftp_auth_cache);
+}
+
+URLRequestFtpJob::~URLRequestFtpJob() {
+ if (pac_request_)
+ proxy_service_->CancelPacRequest(pac_request_);
+}
+
+bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
+ // Disallow all redirects.
+ return false;
+}
+
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
+ if (proxy_info_.is_direct()) {
+ if (ftp_transaction_->GetResponseInfo()->is_directory_listing) {
+ *mime_type = "text/vnd.chromium.ftp-dir";
+ return true;
+ }
+ } else {
+ // No special handling of MIME type is needed. As opposed to direct FTP
+ // transaction, we do not get a raw directory listing to parse.
+ return http_transaction_->GetResponseInfo()->
+ headers->GetMimeType(mime_type);
+ }
+ return false;
+}
+
+void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
+ if (http_response_info_)
+ *info = *http_response_info_;
+}
+
+HostPortPair URLRequestFtpJob::GetSocketAddress() const {
+ if (proxy_info_.is_direct()) {
+ if (!ftp_transaction_)
+ return HostPortPair();
+ return ftp_transaction_->GetResponseInfo()->socket_address;
+ } else {
+ if (!http_transaction_)
+ return HostPortPair();
+ return http_transaction_->GetResponseInfo()->socket_address;
+ }
+}
+
+void URLRequestFtpJob::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+ if (http_transaction_)
+ http_transaction_->SetPriority(priority);
+}
+
+void URLRequestFtpJob::Start() {
+ DCHECK(!pac_request_);
+ DCHECK(!ftp_transaction_);
+ DCHECK(!http_transaction_);
+
+ int rv = OK;
+ if (request_->load_flags() & LOAD_BYPASS_PROXY) {
+ proxy_info_.UseDirect();
+ } else {
+ DCHECK_EQ(request_->context()->proxy_service(), proxy_service_);
+ rv = proxy_service_->ResolveProxy(
+ request_->url(),
+ &proxy_info_,
+ base::Bind(&URLRequestFtpJob::OnResolveProxyComplete,
+ base::Unretained(this)),
+ &pac_request_,
+ request_->net_log());
+
+ if (rv == ERR_IO_PENDING)
+ return;
+ }
+ OnResolveProxyComplete(rv);
+}
+
+void URLRequestFtpJob::Kill() {
+ if (ftp_transaction_)
+ ftp_transaction_.reset();
+ if (http_transaction_)
+ http_transaction_.reset();
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+void URLRequestFtpJob::OnResolveProxyComplete(int result) {
+ pac_request_ = NULL;
+
+ if (result != OK) {
+ OnStartCompletedAsync(result);
+ return;
+ }
+
+ // Remove unsupported proxies from the list.
+ proxy_info_.RemoveProxiesWithoutScheme(
+ ProxyServer::SCHEME_DIRECT |
+ ProxyServer::SCHEME_HTTP |
+ ProxyServer::SCHEME_HTTPS);
+
+ // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
+ if (proxy_info_.is_direct())
+ StartFtpTransaction();
+ else if (proxy_info_.is_http() || proxy_info_.is_https())
+ StartHttpTransaction();
+ else
+ OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES);
+}
+
+void URLRequestFtpJob::StartFtpTransaction() {
+ // Create a transaction.
+ DCHECK(!ftp_transaction_);
+
+ ftp_request_info_.url = request_->url();
+ ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction());
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ int rv;
+ if (ftp_transaction_) {
+ rv = ftp_transaction_->Start(
+ &ftp_request_info_,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)),
+ request_->net_log());
+ if (rv == ERR_IO_PENDING)
+ return;
+ } else {
+ rv = ERR_FAILED;
+ }
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ OnStartCompletedAsync(rv);
+}
+
+void URLRequestFtpJob::StartHttpTransaction() {
+ // Create a transaction.
+ DCHECK(!http_transaction_);
+
+ // Do not cache FTP responses sent through HTTP proxy.
+ request_->set_load_flags(request_->load_flags() |
+ LOAD_DISABLE_CACHE |
+ LOAD_DO_NOT_SAVE_COOKIES |
+ LOAD_DO_NOT_SEND_COOKIES);
+
+ http_request_info_.url = request_->url();
+ http_request_info_.method = request_->method();
+ http_request_info_.load_flags = request_->load_flags();
+ http_request_info_.request_id = request_->identifier();
+
+ int rv = request_->context()->http_transaction_factory()->CreateTransaction(
+ priority_, &http_transaction_, NULL);
+ if (rv == OK) {
+ rv = http_transaction_->Start(
+ &http_request_info_,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)),
+ request_->net_log());
+ if (rv == ERR_IO_PENDING)
+ return;
+ }
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ OnStartCompletedAsync(rv);
+}
+
+void URLRequestFtpJob::OnStartCompleted(int result) {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ // Note that ftp_transaction_ may be NULL due to a creation failure.
+ if (ftp_transaction_) {
+ // FTP obviously doesn't have HTTP Content-Length header. We have to pass
+ // the content size information manually.
+ set_expected_content_size(
+ ftp_transaction_->GetResponseInfo()->expected_content_size);
+ }
+
+ if (result == OK) {
+ if (http_transaction_) {
+ http_response_info_ = http_transaction_->GetResponseInfo();
+
+ if (http_response_info_->headers->response_code() == 401 ||
+ http_response_info_->headers->response_code() == 407) {
+ HandleAuthNeededResponse();
+ return;
+ }
+ }
+ NotifyHeadersComplete();
+ } else if (ftp_transaction_ &&
+ ftp_transaction_->GetResponseInfo()->needs_auth) {
+ HandleAuthNeededResponse();
+ return;
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestFtpJob::OnStartCompletedAsync(int result) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), result));
+}
+
+void URLRequestFtpJob::OnReadCompleted(int result) {
+ read_in_progress_ = false;
+ if (result == 0) {
+ NotifyDone(URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ }
+ NotifyReadComplete(result);
+}
+
+void URLRequestFtpJob::RestartTransactionWithAuth() {
+ DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH);
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv;
+ if (proxy_info_.is_direct()) {
+ rv = ftp_transaction_->RestartWithAuth(
+ auth_data_->credentials,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)));
+ } else {
+ rv = http_transaction_->RestartWithAuth(
+ auth_data_->credentials,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)));
+ }
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ OnStartCompletedAsync(rv);
+}
+
+LoadState URLRequestFtpJob::GetLoadState() const {
+ if (proxy_info_.is_direct()) {
+ return ftp_transaction_ ?
+ ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE;
+ } else {
+ return http_transaction_ ?
+ http_transaction_->GetLoadState() : LOAD_STATE_IDLE;
+ }
+}
+
+bool URLRequestFtpJob::NeedsAuth() {
+ return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
+}
+
+void URLRequestFtpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK(NeedsAuth());
+
+ if (http_response_info_) {
+ *result = http_response_info_->auth_challenge;
+ return;
+ }
+
+ scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
+ auth_info->is_proxy = false;
+ auth_info->challenger = HostPortPair::FromURL(request_->url());
+ // scheme and realm are kept empty.
+ DCHECK(auth_info->scheme.empty());
+ DCHECK(auth_info->realm.empty());
+ result->swap(auth_info);
+}
+
+void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(ftp_transaction_ || http_transaction_);
+ DCHECK(NeedsAuth());
+
+ auth_data_->state = AUTH_STATE_HAVE_AUTH;
+ auth_data_->credentials = credentials;
+
+ if (ftp_transaction_) {
+ ftp_auth_cache_->Add(request_->url().GetOrigin(),
+ auth_data_->credentials);
+ }
+
+ RestartTransactionWithAuth();
+}
+
+void URLRequestFtpJob::CancelAuth() {
+ DCHECK(ftp_transaction_ || http_transaction_);
+ DCHECK(NeedsAuth());
+
+ auth_data_->state = AUTH_STATE_CANCELED;
+
+ // Once the auth is cancelled, we proceed with the request as though
+ // there were no auth. Schedule this for later so that we don't cause
+ // any recursing into the caller as a result of this call.
+ OnStartCompletedAsync(OK);
+}
+
+UploadProgress URLRequestFtpJob::GetUploadProgress() const {
+ return UploadProgress();
+}
+
+bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) {
+ DCHECK_NE(buf_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!read_in_progress_);
+
+ int rv;
+ if (proxy_info_.is_direct()) {
+ rv = ftp_transaction_->Read(buf, buf_size,
+ base::Bind(&URLRequestFtpJob::OnReadCompleted,
+ base::Unretained(this)));
+ } else {
+ rv = http_transaction_->Read(buf, buf_size,
+ base::Bind(&URLRequestFtpJob::OnReadCompleted,
+ base::Unretained(this)));
+ }
+
+ if (rv >= 0) {
+ *bytes_read = rv;
+ return true;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+void URLRequestFtpJob::HandleAuthNeededResponse() {
+ GURL origin = request_->url().GetOrigin();
+
+ if (auth_data_.get()) {
+ if (auth_data_->state == AUTH_STATE_CANCELED) {
+ NotifyHeadersComplete();
+ return;
+ }
+
+ if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH)
+ ftp_auth_cache_->Remove(origin, auth_data_->credentials);
+ } else {
+ auth_data_ = new AuthData;
+ }
+ auth_data_->state = AUTH_STATE_NEED_AUTH;
+
+ FtpAuthCache::Entry* cached_auth = NULL;
+ if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
+ cached_auth = ftp_auth_cache_->Lookup(origin);
+ if (cached_auth) {
+ // Retry using cached auth data.
+ SetAuth(cached_auth->credentials);
+ } else {
+ // Prompt for a username/password.
+ NotifyHeadersComplete();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_ftp_job.h b/chromium/net/url_request/url_request_ftp_job.h
new file mode 100644
index 00000000000..4a064a01cba
--- /dev/null
+++ b/chromium/net/url_request/url_request_ftp_job.h
@@ -0,0 +1,107 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/auth.h"
+#include "net/base/net_export.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/ftp/ftp_transaction.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_transaction.h"
+#include "net/proxy/proxy_info.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NetworkDelegate;
+class FtpTransactionFactory;
+class FtpAuthCache;
+
+// A URLRequestJob subclass that is built on top of FtpTransaction. It
+// provides an implementation for FTP.
+class NET_EXPORT_PRIVATE URLRequestFtpJob : public URLRequestJob {
+ public:
+ URLRequestFtpJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache);
+
+ protected:
+ virtual ~URLRequestFtpJob();
+
+ // Overridden from URLRequestJob:
+ virtual bool IsSafeRedirect(const GURL& location) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(HttpResponseInfo* info) OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+
+ RequestPriority priority() const { return priority_; }
+
+ private:
+ void OnResolveProxyComplete(int result);
+
+ void StartFtpTransaction();
+ void StartHttpTransaction();
+
+ void OnStartCompleted(int result);
+ void OnStartCompletedAsync(int result);
+ void OnReadCompleted(int result);
+
+ void RestartTransactionWithAuth();
+
+ void LogFtpServerType(char server_type);
+
+ // Overridden from URLRequestJob:
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual bool NeedsAuth() OVERRIDE;
+ virtual void GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info) OVERRIDE;
+ virtual void SetAuth(const AuthCredentials& credentials) OVERRIDE;
+ virtual void CancelAuth() OVERRIDE;
+
+ // TODO(ibrar): Yet to give another look at this function.
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+
+ void HandleAuthNeededResponse();
+
+ RequestPriority priority_;
+
+ ProxyService* proxy_service_;
+ ProxyInfo proxy_info_;
+ ProxyService::PacRequest* pac_request_;
+
+ FtpRequestInfo ftp_request_info_;
+ scoped_ptr<FtpTransaction> ftp_transaction_;
+
+ HttpRequestInfo http_request_info_;
+ scoped_ptr<HttpTransaction> http_transaction_;
+ const HttpResponseInfo* http_response_info_;
+
+ bool read_in_progress_;
+
+ scoped_refptr<AuthData> auth_data_;
+
+ base::WeakPtrFactory<URLRequestFtpJob> weak_factory_;
+
+ FtpTransactionFactory* ftp_transaction_factory_;
+ FtpAuthCache* ftp_auth_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFtpJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
diff --git a/chromium/net/url_request/url_request_ftp_job_unittest.cc b/chromium/net/url_request/url_request_ftp_job_unittest.cc
new file mode 100644
index 00000000000..f9eafab54ba
--- /dev/null
+++ b/chromium/net/url_request/url_request_ftp_job_unittest.cc
@@ -0,0 +1,710 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_ftp_job.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
+#include "base/run_loop.h"
+#include "net/ftp/ftp_auth_cache.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/proxy/mock_proxy_resolver.h"
+#include "net/proxy/proxy_config_service.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#include "net/socket/socket_test_util.h"
+#include "net/url_request/ftp_protocol_handler.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class FtpTestURLRequestContext : public TestURLRequestContext {
+ public:
+ FtpTestURLRequestContext(ClientSocketFactory* socket_factory,
+ ProxyService* proxy_service,
+ NetworkDelegate* network_delegate,
+ FtpTransactionFactory* ftp_transaction_factory)
+ : TestURLRequestContext(true),
+ ftp_protocol_handler_(new FtpProtocolHandler(ftp_transaction_factory)) {
+ set_client_socket_factory(socket_factory);
+ context_storage_.set_proxy_service(proxy_service);
+ set_network_delegate(network_delegate);
+ URLRequestJobFactoryImpl* job_factory = new URLRequestJobFactoryImpl;
+ job_factory->SetProtocolHandler("ftp", ftp_protocol_handler_);
+ context_storage_.set_job_factory(job_factory);
+ Init();
+ }
+
+ FtpAuthCache* GetFtpAuthCache() {
+ return ftp_protocol_handler_->ftp_auth_cache_.get();
+ }
+
+ void set_proxy_service(ProxyService* proxy_service) {
+ context_storage_.set_proxy_service(proxy_service);
+ }
+
+ private:
+ FtpProtocolHandler* ftp_protocol_handler_;
+};
+
+namespace {
+
+class SimpleProxyConfigService : public ProxyConfigService {
+ public:
+ SimpleProxyConfigService() {
+ // Any FTP requests that ever go through HTTP paths are proxied requests.
+ config_.proxy_rules().ParseFromString("ftp=localhost");
+ }
+
+ virtual void AddObserver(Observer* observer) OVERRIDE {
+ observer_ = observer;
+ }
+
+ virtual void RemoveObserver(Observer* observer) OVERRIDE {
+ if (observer_ == observer) {
+ observer_ = NULL;
+ }
+ }
+
+ virtual ConfigAvailability GetLatestProxyConfig(
+ ProxyConfig* config) OVERRIDE {
+ *config = config_;
+ return CONFIG_VALID;
+ }
+
+ void IncrementConfigId() {
+ config_.set_id(config_.id() + 1);
+ observer_->OnProxyConfigChanged(config_, ProxyConfigService::CONFIG_VALID);
+ }
+
+ private:
+ ProxyConfig config_;
+ Observer* observer_;
+};
+
+// Inherit from URLRequestFtpJob to expose the priority and some
+// other hidden functions.
+class TestURLRequestFtpJob : public URLRequestFtpJob {
+ public:
+ TestURLRequestFtpJob(URLRequest* request,
+ FtpTransactionFactory* ftp_factory,
+ FtpAuthCache* ftp_auth_cache)
+ : URLRequestFtpJob(request, NULL, ftp_factory, ftp_auth_cache) {}
+
+ using URLRequestFtpJob::SetPriority;
+ using URLRequestFtpJob::Start;
+ using URLRequestFtpJob::Kill;
+ using URLRequestFtpJob::priority;
+
+ protected:
+ virtual ~TestURLRequestFtpJob() {}
+};
+
+class MockFtpTransactionFactory : public FtpTransactionFactory {
+ public:
+ virtual FtpTransaction* CreateTransaction() OVERRIDE {
+ return NULL;
+ }
+
+ virtual void Suspend(bool suspend) OVERRIDE {}
+};
+
+// Fixture for priority-related tests. Priority matters when there is
+// an HTTP proxy.
+class URLRequestFtpJobPriorityTest : public testing::Test {
+ protected:
+ URLRequestFtpJobPriorityTest()
+ : proxy_service_(new SimpleProxyConfigService, NULL, NULL),
+ req_(GURL("ftp://ftp.example.com"), &delegate_, &context_, NULL) {
+ context_.set_proxy_service(&proxy_service_);
+ context_.set_http_transaction_factory(&network_layer_);
+ }
+
+ ProxyService proxy_service_;
+ MockNetworkLayer network_layer_;
+ MockFtpTransactionFactory ftp_factory_;
+ FtpAuthCache ftp_auth_cache_;
+ TestURLRequestContext context_;
+ TestDelegate delegate_;
+ TestURLRequest req_;
+};
+
+// Make sure that SetPriority actually sets the URLRequestFtpJob's
+// priority, both before and after start.
+TEST_F(URLRequestFtpJobPriorityTest, SetPriorityBasic) {
+ scoped_refptr<TestURLRequestFtpJob> job(new TestURLRequestFtpJob(
+ &req_, &ftp_factory_, &ftp_auth_cache_));
+ EXPECT_EQ(DEFAULT_PRIORITY, job->priority());
+
+ job->SetPriority(LOWEST);
+ EXPECT_EQ(LOWEST, job->priority());
+
+ job->SetPriority(LOW);
+ EXPECT_EQ(LOW, job->priority());
+
+ job->Start();
+ EXPECT_EQ(LOW, job->priority());
+
+ job->SetPriority(MEDIUM);
+ EXPECT_EQ(MEDIUM, job->priority());
+}
+
+// Make sure that URLRequestFtpJob passes on its priority to its
+// transaction on start.
+TEST_F(URLRequestFtpJobPriorityTest, SetTransactionPriorityOnStart) {
+ scoped_refptr<TestURLRequestFtpJob> job(new TestURLRequestFtpJob(
+ &req_, &ftp_factory_, &ftp_auth_cache_));
+ job->SetPriority(LOW);
+
+ EXPECT_FALSE(network_layer_.last_transaction());
+
+ job->Start();
+
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+}
+
+// Make sure that URLRequestFtpJob passes on its priority updates to
+// its transaction.
+TEST_F(URLRequestFtpJobPriorityTest, SetTransactionPriority) {
+ scoped_refptr<TestURLRequestFtpJob> job(new TestURLRequestFtpJob(
+ &req_, &ftp_factory_, &ftp_auth_cache_));
+ job->SetPriority(LOW);
+ job->Start();
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+
+ job->SetPriority(HIGHEST);
+ EXPECT_EQ(HIGHEST, network_layer_.last_transaction()->priority());
+}
+
+// Make sure that URLRequestFtpJob passes on its priority updates to
+// newly-created transactions after the first one.
+TEST_F(URLRequestFtpJobPriorityTest, SetSubsequentTransactionPriority) {
+ scoped_refptr<TestURLRequestFtpJob> job(new TestURLRequestFtpJob(
+ &req_, &ftp_factory_, &ftp_auth_cache_));
+ job->Start();
+
+ job->SetPriority(LOW);
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+
+ job->Kill();
+ network_layer_.ClearLastTransaction();
+
+ // Creates a second transaction.
+ job->Start();
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+}
+
+class URLRequestFtpJobTest : public testing::Test {
+ public:
+ URLRequestFtpJobTest()
+ : request_context_(&socket_factory_,
+ new ProxyService(
+ new SimpleProxyConfigService, NULL, NULL),
+ &network_delegate_,
+ &ftp_transaction_factory_) {
+ }
+
+ virtual ~URLRequestFtpJobTest() {
+ // Clean up any remaining tasks that mess up unrelated tests.
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ }
+
+ void AddSocket(MockRead* reads, size_t reads_size,
+ MockWrite* writes, size_t writes_size) {
+ DeterministicSocketData* socket_data = new DeterministicSocketData(
+ reads, reads_size, writes, writes_size);
+ socket_data->set_connect_data(MockConnect(SYNCHRONOUS, OK));
+ socket_data->StopAfter(reads_size + writes_size - 1);
+ socket_factory_.AddSocketDataProvider(socket_data);
+
+ socket_data_.push_back(socket_data);
+ }
+
+ FtpTestURLRequestContext* request_context() { return &request_context_; }
+ TestNetworkDelegate* network_delegate() { return &network_delegate_; }
+ DeterministicSocketData* socket_data(size_t index) {
+ return socket_data_[index];
+ }
+
+ private:
+ ScopedVector<DeterministicSocketData> socket_data_;
+ DeterministicMockClientSocketFactory socket_factory_;
+ TestNetworkDelegate network_delegate_;
+ MockFtpTransactionFactory ftp_transaction_factory_;
+
+ FtpTestURLRequestContext request_context_;
+};
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequest) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 3, "test.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(4);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_FALSE(request_delegate.auth_required_called());
+ EXPECT_EQ("test.html", request_delegate.data_received());
+}
+
+// Regression test for http://crbug.com/237526 .
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestOrphanJob) {
+ // Use a PAC URL so that URLRequestFtpJob's |pac_request_| field is non-NULL.
+ request_context()->set_proxy_service(
+ new ProxyService(
+ new ProxyConfigServiceFixed(
+ ProxyConfig::CreateFromCustomPacURL(GURL("http://foo"))),
+ new MockAsyncProxyResolver, NULL));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+
+ // Now |url_request| will be deleted before its completion,
+ // resulting in it being orphaned. It should not crash.
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAuthNoCredentials) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead(ASYNC, 2, "Proxy-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(5);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_TRUE(request_delegate.auth_required_called());
+ EXPECT_EQ("test.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAuthWithCredentials) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead(ASYNC, 2, "Proxy-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+
+ // Second response.
+ MockRead(ASYNC, 6, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 7, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 8, "test2.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ request_delegate.set_credentials(
+ AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(9);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_TRUE(request_delegate.auth_required_called());
+ EXPECT_EQ("test2.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedServerAuthNoCredentials) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead(ASYNC, 2, "WWW-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(5);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_TRUE(request_delegate.auth_required_called());
+ EXPECT_EQ("test.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedServerAuthWithCredentials) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead(ASYNC, 2, "WWW-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+
+ // Second response.
+ MockRead(ASYNC, 6, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 7, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 8, "test2.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ request_delegate.set_credentials(
+ AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(9);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_TRUE(request_delegate.auth_required_called());
+ EXPECT_EQ("test2.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAndServerAuth) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic "
+ "cHJveHl1c2VyOnByb3h5cGFzcw==\r\n\r\n"),
+ MockWrite(ASYNC, 10, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n"
+ "Proxy-Authorization: Basic "
+ "cHJveHl1c2VyOnByb3h5cGFzcw==\r\n"
+ "Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ // No credentials.
+ MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead(ASYNC, 2, "Proxy-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+
+ // Second response.
+ MockRead(ASYNC, 6, "HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead(ASYNC, 7, "WWW-Authenticate: Basic "
+ "realm=\"MyRealm1\"\r\n"),
+ MockRead(ASYNC, 8, "Content-Length: 9\r\n\r\n"),
+ MockRead(ASYNC, 9, "test.html"),
+
+ // Third response.
+ MockRead(ASYNC, 11, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 12, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 13, "test2.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ GURL url("ftp://ftp.example.com");
+
+ // Make sure cached FTP credentials are not used for proxy authentication.
+ request_context()->GetFtpAuthCache()->Add(
+ url.GetOrigin(),
+ AuthCredentials(ASCIIToUTF16("userdonotuse"),
+ ASCIIToUTF16("passworddonotuse")));
+
+ TestDelegate request_delegate;
+ request_delegate.set_credentials(
+ AuthCredentials(ASCIIToUTF16("proxyuser"), ASCIIToUTF16("proxypass")));
+ URLRequest url_request(url,
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+ socket_data(0)->RunFor(5);
+
+ request_delegate.set_credentials(
+ AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
+ socket_data(0)->RunFor(9);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_TRUE(request_delegate.auth_required_called());
+ EXPECT_EQ("test2.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestDoNotSaveCookies) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 9\r\n"),
+ MockRead(ASYNC, 3, "Set-Cookie: name=value\r\n\r\n"),
+ MockRead(ASYNC, 4, "test.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ ASSERT_TRUE(url_request.is_pending());
+
+ socket_data(0)->RunFor(5);
+
+ EXPECT_TRUE(url_request.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+
+ // Make sure we do not accept cookies.
+ EXPECT_EQ(0, network_delegate()->set_cookie_count());
+
+ EXPECT_FALSE(request_delegate.auth_required_called());
+ EXPECT_EQ("test.html", request_delegate.data_received());
+}
+
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestDoNotFollowRedirects) {
+ MockWrite writes[] = {
+ MockWrite(SYNCHRONOUS, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(SYNCHRONOUS, 1, "HTTP/1.1 302 Found\r\n"),
+ MockRead(ASYNC, 2, "Location: http://other.example.com/\r\n\r\n"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate;
+ URLRequest url_request(GURL("ftp://ftp.example.com/"),
+ &request_delegate,
+ request_context(),
+ network_delegate());
+ url_request.Start();
+ EXPECT_TRUE(url_request.is_pending());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_TRUE(url_request.is_pending());
+ EXPECT_EQ(0, request_delegate.response_started_count());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ ASSERT_TRUE(url_request.status().is_success());
+
+ socket_data(0)->RunFor(1);
+
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(1, network_delegate()->error_count());
+ EXPECT_FALSE(url_request.status().is_success());
+ EXPECT_EQ(ERR_UNSAFE_REDIRECT, url_request.status().error());
+}
+
+// We should re-use socket for requests using the same scheme, host, and port.
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestReuseSocket) {
+ MockWrite writes[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/first HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ MockWrite(ASYNC, 4, "GET ftp://ftp.example.com/second HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 3, "test1.html"),
+ MockRead(ASYNC, 5, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 6, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 7, "test2.html"),
+ };
+
+ AddSocket(reads, arraysize(reads), writes, arraysize(writes));
+
+ TestDelegate request_delegate1;
+ URLRequest url_request1(GURL("ftp://ftp.example.com/first"),
+ &request_delegate1,
+ request_context(),
+ network_delegate());
+ url_request1.Start();
+ ASSERT_TRUE(url_request1.is_pending());
+ socket_data(0)->RunFor(4);
+
+ EXPECT_TRUE(url_request1.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_FALSE(request_delegate1.auth_required_called());
+ EXPECT_EQ("test1.html", request_delegate1.data_received());
+
+ TestDelegate request_delegate2;
+ URLRequest url_request2(GURL("ftp://ftp.example.com/second"),
+ &request_delegate2,
+ request_context(),
+ network_delegate());
+ url_request2.Start();
+ ASSERT_TRUE(url_request2.is_pending());
+ socket_data(0)->RunFor(4);
+
+ EXPECT_TRUE(url_request2.status().is_success());
+ EXPECT_EQ(2, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_FALSE(request_delegate2.auth_required_called());
+ EXPECT_EQ("test2.html", request_delegate2.data_received());
+}
+
+// We should not re-use socket when there are two requests to the same host,
+// but one is FTP and the other is HTTP.
+TEST_F(URLRequestFtpJobTest, FtpProxyRequestDoNotReuseSocket) {
+ MockWrite writes1[] = {
+ MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/first HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Proxy-Connection: keep-alive\r\n\r\n"),
+ };
+ MockWrite writes2[] = {
+ MockWrite(ASYNC, 0, "GET /second HTTP/1.1\r\n"
+ "Host: ftp.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent:\r\n"
+ "Accept-Encoding: gzip,deflate\r\n"
+ "Accept-Language: en-us,fr\r\n\r\n"),
+ };
+ MockRead reads1[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 3, "test1.html"),
+ };
+ MockRead reads2[] = {
+ MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"),
+ MockRead(ASYNC, 2, "Content-Length: 10\r\n\r\n"),
+ MockRead(ASYNC, 3, "test2.html"),
+ };
+
+ AddSocket(reads1, arraysize(reads1), writes1, arraysize(writes1));
+ AddSocket(reads2, arraysize(reads2), writes2, arraysize(writes2));
+
+ TestDelegate request_delegate1;
+ URLRequest url_request1(GURL("ftp://ftp.example.com/first"),
+ &request_delegate1,
+ request_context(),
+ network_delegate());
+ url_request1.Start();
+ ASSERT_TRUE(url_request1.is_pending());
+ socket_data(0)->RunFor(4);
+
+ EXPECT_TRUE(url_request1.status().is_success());
+ EXPECT_EQ(1, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_FALSE(request_delegate1.auth_required_called());
+ EXPECT_EQ("test1.html", request_delegate1.data_received());
+
+ TestDelegate request_delegate2;
+ URLRequest url_request2(GURL("http://ftp.example.com/second"),
+ &request_delegate2,
+ request_context(),
+ network_delegate());
+ url_request2.Start();
+ ASSERT_TRUE(url_request2.is_pending());
+ socket_data(1)->RunFor(4);
+
+ EXPECT_TRUE(url_request2.status().is_success());
+ EXPECT_EQ(2, network_delegate()->completed_requests());
+ EXPECT_EQ(0, network_delegate()->error_count());
+ EXPECT_FALSE(request_delegate2.auth_required_called());
+ EXPECT_EQ("test2.html", request_delegate2.data_received());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_http_job.cc b/chromium/net/url_request/url_request_http_job.cc
new file mode 100644
index 00000000000..dd11a1b6ef2
--- /dev/null
+++ b/chromium/net/url_request/url_request_http_job.cc
@@ -0,0 +1,1513 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_http_job.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/file_version_info.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/base/filter.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/network_delegate.h"
+#include "net/base/sdch_manager.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_delegate.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/http_util.h"
+#include "net/ssl/ssl_cert_request_info.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/fraudulent_certificate_reporter.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_redirect_job.h"
+#include "net/url_request/url_request_throttler_header_adapter.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+static const char kAvailDictionaryHeader[] = "Avail-Dictionary";
+
+namespace net {
+
+class URLRequestHttpJob::HttpFilterContext : public FilterContext {
+ public:
+ explicit HttpFilterContext(URLRequestHttpJob* job);
+ virtual ~HttpFilterContext();
+
+ // FilterContext implementation.
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetURL(GURL* gurl) const OVERRIDE;
+ virtual base::Time GetRequestTime() const OVERRIDE;
+ virtual bool IsCachedContent() const OVERRIDE;
+ virtual bool IsDownload() const OVERRIDE;
+ virtual bool IsSdchResponse() const OVERRIDE;
+ virtual int64 GetByteReadCount() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void RecordPacketStats(StatisticSelector statistic) const OVERRIDE;
+
+ // Method to allow us to reset filter context for a response that should have
+ // been SDCH encoded when there is an update due to an explicit HTTP header.
+ void ResetSdchResponseToFalse();
+
+ private:
+ URLRequestHttpJob* job_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpFilterContext);
+};
+
+class URLRequestHttpJob::HttpTransactionDelegateImpl
+ : public HttpTransactionDelegate {
+ public:
+ HttpTransactionDelegateImpl(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : request_(request),
+ network_delegate_(network_delegate),
+ state_(NONE_ACTIVE) {}
+ virtual ~HttpTransactionDelegateImpl() { OnDetachRequest(); }
+ void OnDetachRequest() {
+ if (!IsRequestAndDelegateActive())
+ return;
+ NotifyStateChange(NetworkDelegate::REQUEST_WAIT_STATE_RESET);
+ state_ = NONE_ACTIVE;
+ request_ = NULL;
+ }
+ virtual void OnCacheActionStart() OVERRIDE {
+ HandleStateChange(NONE_ACTIVE,
+ CACHE_ACTIVE,
+ NetworkDelegate::REQUEST_WAIT_STATE_CACHE_START);
+ }
+ virtual void OnCacheActionFinish() OVERRIDE {
+ HandleStateChange(CACHE_ACTIVE,
+ NONE_ACTIVE,
+ NetworkDelegate::REQUEST_WAIT_STATE_CACHE_FINISH);
+ }
+ virtual void OnNetworkActionStart() OVERRIDE {
+ HandleStateChange(NONE_ACTIVE,
+ NETWORK_ACTIVE,
+ NetworkDelegate::REQUEST_WAIT_STATE_NETWORK_START);
+ }
+ virtual void OnNetworkActionFinish() OVERRIDE {
+ HandleStateChange(NETWORK_ACTIVE,
+ NONE_ACTIVE,
+ NetworkDelegate::REQUEST_WAIT_STATE_NETWORK_FINISH);
+ }
+
+ private:
+ enum State {
+ NONE_ACTIVE,
+ CACHE_ACTIVE,
+ NETWORK_ACTIVE
+ };
+
+ // Returns true if this object still has an active request and network
+ // delegate.
+ bool IsRequestAndDelegateActive() const {
+ return request_ && network_delegate_;
+ }
+
+ // Notifies the |network_delegate_| object of a change in the state of the
+ // |request_| to the state given by the |request_wait_state| argument.
+ void NotifyStateChange(NetworkDelegate::RequestWaitState request_wait_state) {
+ network_delegate_->NotifyRequestWaitStateChange(*request_,
+ request_wait_state);
+ }
+
+ // Checks the request and delegate are still active, changes |state_| from
+ // |expected_state| to |next_state|, and then notifies the network delegate of
+ // the change to |request_wait_state|.
+ void HandleStateChange(State expected_state,
+ State next_state,
+ NetworkDelegate::RequestWaitState request_wait_state) {
+ if (!IsRequestAndDelegateActive())
+ return;
+ DCHECK_EQ(expected_state, state_);
+ state_ = next_state;
+ NotifyStateChange(request_wait_state);
+ }
+
+ URLRequest* request_;
+ NetworkDelegate* network_delegate_;
+ // Internal state tracking, for sanity checking.
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpTransactionDelegateImpl);
+};
+
+URLRequestHttpJob::HttpFilterContext::HttpFilterContext(URLRequestHttpJob* job)
+ : job_(job) {
+ DCHECK(job_);
+}
+
+URLRequestHttpJob::HttpFilterContext::~HttpFilterContext() {
+}
+
+bool URLRequestHttpJob::HttpFilterContext::GetMimeType(
+ std::string* mime_type) const {
+ return job_->GetMimeType(mime_type);
+}
+
+bool URLRequestHttpJob::HttpFilterContext::GetURL(GURL* gurl) const {
+ if (!job_->request())
+ return false;
+ *gurl = job_->request()->url();
+ return true;
+}
+
+base::Time URLRequestHttpJob::HttpFilterContext::GetRequestTime() const {
+ return job_->request() ? job_->request()->request_time() : base::Time();
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsCachedContent() const {
+ return job_->is_cached_content_;
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsDownload() const {
+ return (job_->request_info_.load_flags & LOAD_IS_DOWNLOAD) != 0;
+}
+
+void URLRequestHttpJob::HttpFilterContext::ResetSdchResponseToFalse() {
+ DCHECK(job_->sdch_dictionary_advertised_);
+ job_->sdch_dictionary_advertised_ = false;
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsSdchResponse() const {
+ return job_->sdch_dictionary_advertised_;
+}
+
+int64 URLRequestHttpJob::HttpFilterContext::GetByteReadCount() const {
+ return job_->filter_input_byte_count();
+}
+
+int URLRequestHttpJob::HttpFilterContext::GetResponseCode() const {
+ return job_->GetResponseCode();
+}
+
+void URLRequestHttpJob::HttpFilterContext::RecordPacketStats(
+ StatisticSelector statistic) const {
+ job_->RecordPacketStats(statistic);
+}
+
+// TODO(darin): make sure the port blocking code is not lost
+// static
+URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ DCHECK(scheme == "http" || scheme == "https");
+
+ if (!request->context()->http_transaction_factory()) {
+ NOTREACHED() << "requires a valid context";
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_INVALID_ARGUMENT);
+ }
+
+ GURL redirect_url;
+ if (request->GetHSTSRedirect(&redirect_url)) {
+ return new URLRequestRedirectJob(
+ request, network_delegate, redirect_url,
+ // Use status code 307 to preserve the method, so POST requests work.
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ }
+ return new URLRequestHttpJob(request,
+ network_delegate,
+ request->context()->http_user_agent_settings());
+}
+
+URLRequestHttpJob::URLRequestHttpJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const HttpUserAgentSettings* http_user_agent_settings)
+ : URLRequestJob(request, network_delegate),
+ priority_(DEFAULT_PRIORITY),
+ response_info_(NULL),
+ response_cookies_save_index_(0),
+ proxy_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ server_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ start_callback_(base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ base::Unretained(this))),
+ notify_before_headers_sent_callback_(
+ base::Bind(&URLRequestHttpJob::NotifyBeforeSendHeadersCallback,
+ base::Unretained(this))),
+ read_in_progress_(false),
+ throttling_entry_(NULL),
+ sdch_dictionary_advertised_(false),
+ sdch_test_activated_(false),
+ sdch_test_control_(false),
+ is_cached_content_(false),
+ request_creation_time_(),
+ packet_timing_enabled_(false),
+ done_(false),
+ bytes_observed_in_packets_(0),
+ request_time_snapshot_(),
+ final_packet_time_(),
+ filter_context_(new HttpFilterContext(this)),
+ weak_factory_(this),
+ on_headers_received_callback_(
+ base::Bind(&URLRequestHttpJob::OnHeadersReceivedCallback,
+ base::Unretained(this))),
+ awaiting_callback_(false),
+ http_transaction_delegate_(
+ new HttpTransactionDelegateImpl(request, network_delegate)),
+ http_user_agent_settings_(http_user_agent_settings) {
+ URLRequestThrottlerManager* manager = request->context()->throttler_manager();
+ if (manager)
+ throttling_entry_ = manager->RegisterRequestUrl(request->url());
+
+ ResetTimer();
+}
+
+URLRequestHttpJob::~URLRequestHttpJob() {
+ CHECK(!awaiting_callback_);
+
+ DCHECK(!sdch_test_control_ || !sdch_test_activated_);
+ if (!is_cached_content_) {
+ if (sdch_test_control_)
+ RecordPacketStats(FilterContext::SDCH_EXPERIMENT_HOLDBACK);
+ if (sdch_test_activated_)
+ RecordPacketStats(FilterContext::SDCH_EXPERIMENT_DECODE);
+ }
+ // Make sure SDCH filters are told to emit histogram data while
+ // filter_context_ is still alive.
+ DestroyFilters();
+
+ if (sdch_dictionary_url_.is_valid()) {
+ // Prior to reaching the destructor, request_ has been set to a NULL
+ // pointer, so request_->url() is no longer valid in the destructor, and we
+ // use an alternate copy |request_info_.url|.
+ SdchManager* manager = SdchManager::Global();
+ // To be extra safe, since this is a "different time" from when we decided
+ // to get the dictionary, we'll validate that an SdchManager is available.
+ // At shutdown time, care is taken to be sure that we don't delete this
+ // globally useful instance "too soon," so this check is just defensive
+ // coding to assure that IF the system is shutting down, we don't have any
+ // problem if the manager was deleted ahead of time.
+ if (manager) // Defensive programming.
+ manager->FetchDictionary(request_info_.url, sdch_dictionary_url_);
+ }
+ DoneWithRequest(ABORTED);
+}
+
+void URLRequestHttpJob::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+ if (transaction_)
+ transaction_->SetPriority(priority_);
+}
+
+void URLRequestHttpJob::Start() {
+ DCHECK(!transaction_.get());
+
+ // URLRequest::SetReferrer ensures that we do not send username and password
+ // fields in the referrer.
+ GURL referrer(request_->referrer());
+
+ request_info_.url = request_->url();
+ request_info_.method = request_->method();
+ request_info_.load_flags = request_->load_flags();
+ request_info_.request_id = request_->identifier();
+ // Enable privacy mode if cookie settings or flags tell us not send or
+ // save cookies.
+ bool enable_privacy_mode =
+ (request_info_.load_flags & LOAD_DO_NOT_SEND_COOKIES) ||
+ (request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) ||
+ CanEnablePrivacyMode();
+ // Privacy mode could still be disabled in OnCookiesLoaded if we are going
+ // to send previously saved cookies.
+ request_info_.privacy_mode = enable_privacy_mode ?
+ kPrivacyModeEnabled : kPrivacyModeDisabled;
+
+ // Strip Referer from request_info_.extra_headers to prevent, e.g., plugins
+ // from overriding headers that are controlled using other means. Otherwise a
+ // plugin could set a referrer although sending the referrer is inhibited.
+ request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kReferer);
+
+ // Our consumer should have made sure that this is a safe referrer. See for
+ // instance WebCore::FrameLoader::HideReferrer.
+ if (referrer.is_valid()) {
+ request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer,
+ referrer.spec());
+ }
+
+ request_info_.extra_headers.SetHeaderIfMissing(
+ HttpRequestHeaders::kUserAgent,
+ http_user_agent_settings_ ?
+ http_user_agent_settings_->GetUserAgent(request_->url()) :
+ EmptyString());
+
+ AddExtraHeaders();
+ AddCookieHeaderAndStart();
+}
+
+void URLRequestHttpJob::Kill() {
+ http_transaction_delegate_->OnDetachRequest();
+
+ if (!transaction_.get())
+ return;
+
+ weak_factory_.InvalidateWeakPtrs();
+ DestroyTransaction();
+ URLRequestJob::Kill();
+}
+
+void URLRequestHttpJob::NotifyHeadersComplete() {
+ DCHECK(!response_info_);
+
+ response_info_ = transaction_->GetResponseInfo();
+
+ // Save boolean, as we'll need this info at destruction time, and filters may
+ // also need this info.
+ is_cached_content_ = response_info_->was_cached;
+
+ if (!is_cached_content_ && throttling_entry_.get()) {
+ URLRequestThrottlerHeaderAdapter response_adapter(GetResponseHeaders());
+ throttling_entry_->UpdateWithResponse(request_info_.url.host(),
+ &response_adapter);
+ }
+
+ // The ordering of these calls is not important.
+ ProcessStrictTransportSecurityHeader();
+ ProcessPublicKeyPinsHeader();
+
+ if (SdchManager::Global() &&
+ SdchManager::Global()->IsInSupportedDomain(request_->url())) {
+ const std::string name = "Get-Dictionary";
+ std::string url_text;
+ void* iter = NULL;
+ // TODO(jar): We need to not fetch dictionaries the first time they are
+ // seen, but rather wait until we can justify their usefulness.
+ // For now, we will only fetch the first dictionary, which will at least
+ // require multiple suggestions before we get additional ones for this site.
+ // Eventually we should wait until a dictionary is requested several times
+ // before we even download it (so that we don't waste memory or bandwidth).
+ if (GetResponseHeaders()->EnumerateHeader(&iter, name, &url_text)) {
+ // request_->url() won't be valid in the destructor, so we use an
+ // alternate copy.
+ DCHECK_EQ(request_->url(), request_info_.url);
+ // Resolve suggested URL relative to request url.
+ sdch_dictionary_url_ = request_info_.url.Resolve(url_text);
+ }
+ }
+
+ // The HTTP transaction may be restarted several times for the purposes
+ // of sending authorization information. Each time it restarts, we get
+ // notified of the headers completion so that we can update the cookie store.
+ if (transaction_->IsReadyToRestartForAuth()) {
+ DCHECK(!response_info_->auth_challenge.get());
+ // TODO(battre): This breaks the webrequest API for
+ // URLRequestTestHTTP.BasicAuthWithCookies
+ // where OnBeforeSendHeaders -> OnSendHeaders -> OnBeforeSendHeaders
+ // occurs.
+ RestartTransactionWithAuth(AuthCredentials());
+ return;
+ }
+
+ URLRequestJob::NotifyHeadersComplete();
+}
+
+void URLRequestHttpJob::NotifyDone(const URLRequestStatus& status) {
+ DoneWithRequest(FINISHED);
+ URLRequestJob::NotifyDone(status);
+}
+
+void URLRequestHttpJob::DestroyTransaction() {
+ DCHECK(transaction_.get());
+
+ DoneWithRequest(ABORTED);
+ transaction_.reset();
+ response_info_ = NULL;
+ receive_headers_end_ = base::TimeTicks();
+}
+
+void URLRequestHttpJob::StartTransaction() {
+ if (network_delegate()) {
+ int rv = network_delegate()->NotifyBeforeSendHeaders(
+ request_, notify_before_headers_sent_callback_,
+ &request_info_.extra_headers);
+ // If an extension blocks the request, we rely on the callback to
+ // MaybeStartTransactionInternal().
+ if (rv == ERR_IO_PENDING) {
+ SetBlockedOnDelegate();
+ return;
+ }
+ MaybeStartTransactionInternal(rv);
+ return;
+ }
+ StartTransactionInternal();
+}
+
+void URLRequestHttpJob::NotifyBeforeSendHeadersCallback(int result) {
+ SetUnblockedOnDelegate();
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, GetStatus().status());
+
+ MaybeStartTransactionInternal(result);
+}
+
+void URLRequestHttpJob::MaybeStartTransactionInternal(int result) {
+ if (result == OK) {
+ StartTransactionInternal();
+ } else {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ NotifyCanceled();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestHttpJob::StartTransactionInternal() {
+ // NOTE: This method assumes that request_info_ is already setup properly.
+
+ // If we already have a transaction, then we should restart the transaction
+ // with auth provided by auth_credentials_.
+
+ int rv;
+
+ if (network_delegate()) {
+ network_delegate()->NotifySendHeaders(
+ request_, request_info_.extra_headers);
+ }
+
+ if (transaction_.get()) {
+ rv = transaction_->RestartWithAuth(auth_credentials_, start_callback_);
+ auth_credentials_ = AuthCredentials();
+ } else {
+ DCHECK(request_->context()->http_transaction_factory());
+
+ rv = request_->context()->http_transaction_factory()->CreateTransaction(
+ priority_, &transaction_, http_transaction_delegate_.get());
+ if (rv == OK) {
+ if (!throttling_entry_.get() ||
+ !throttling_entry_->ShouldRejectRequest(*request_)) {
+ rv = transaction_->Start(
+ &request_info_, start_callback_, request_->net_log());
+ start_time_ = base::TimeTicks::Now();
+ } else {
+ // Special error code for the exponential back-off module.
+ rv = ERR_TEMPORARILY_THROTTLED;
+ }
+ }
+ }
+
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestHttpJob::AddExtraHeaders() {
+ // Supply Accept-Encoding field only if it is not already provided.
+ // It should be provided IF the content is known to have restrictions on
+ // potential encoding, such as streaming multi-media.
+ // For details see bug 47381.
+ // TODO(jar, enal): jpeg files etc. should set up a request header if
+ // possible. Right now it is done only by buffered_resource_loader and
+ // simple_data_source.
+ if (!request_info_.extra_headers.HasHeader(
+ HttpRequestHeaders::kAcceptEncoding)) {
+ bool advertise_sdch = SdchManager::Global() &&
+ SdchManager::Global()->IsInSupportedDomain(request_->url());
+ std::string avail_dictionaries;
+ if (advertise_sdch) {
+ SdchManager::Global()->GetAvailDictionaryList(request_->url(),
+ &avail_dictionaries);
+
+ // The AllowLatencyExperiment() is only true if we've successfully done a
+ // full SDCH compression recently in this browser session for this host.
+ // Note that for this path, there might be no applicable dictionaries,
+ // and hence we can't participate in the experiment.
+ if (!avail_dictionaries.empty() &&
+ SdchManager::Global()->AllowLatencyExperiment(request_->url())) {
+ // We are participating in the test (or control), and hence we'll
+ // eventually record statistics via either SDCH_EXPERIMENT_DECODE or
+ // SDCH_EXPERIMENT_HOLDBACK, and we'll need some packet timing data.
+ packet_timing_enabled_ = true;
+ if (base::RandDouble() < .01) {
+ sdch_test_control_ = true; // 1% probability.
+ advertise_sdch = false;
+ } else {
+ sdch_test_activated_ = true;
+ }
+ }
+ }
+
+ // Supply Accept-Encoding headers first so that it is more likely that they
+ // will be in the first transmitted packet. This can sometimes make it
+ // easier to filter and analyze the streams to assure that a proxy has not
+ // damaged these headers. Some proxies deliberately corrupt Accept-Encoding
+ // headers.
+ if (!advertise_sdch) {
+ // Tell the server what compression formats we support (other than SDCH).
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kAcceptEncoding, "gzip,deflate");
+ } else {
+ // Include SDCH in acceptable list.
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kAcceptEncoding, "gzip,deflate,sdch");
+ if (!avail_dictionaries.empty()) {
+ request_info_.extra_headers.SetHeader(
+ kAvailDictionaryHeader,
+ avail_dictionaries);
+ sdch_dictionary_advertised_ = true;
+ // Since we're tagging this transaction as advertising a dictionary,
+ // we'll definitely employ an SDCH filter (or tentative sdch filter)
+ // when we get a response. When done, we'll record histograms via
+ // SDCH_DECODE or SDCH_PASSTHROUGH. Hence we need to record packet
+ // arrival times.
+ packet_timing_enabled_ = true;
+ }
+ }
+ }
+
+ if (http_user_agent_settings_) {
+ // Only add default Accept-Language if the request didn't have it
+ // specified.
+ std::string accept_language =
+ http_user_agent_settings_->GetAcceptLanguage();
+ if (!accept_language.empty()) {
+ request_info_.extra_headers.SetHeaderIfMissing(
+ HttpRequestHeaders::kAcceptLanguage,
+ accept_language);
+ }
+ }
+}
+
+void URLRequestHttpJob::AddCookieHeaderAndStart() {
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_)
+ return;
+
+ CookieStore* cookie_store = request_->context()->cookie_store();
+ if (cookie_store && !(request_info_.load_flags & LOAD_DO_NOT_SEND_COOKIES)) {
+ net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster();
+ if (cookie_monster) {
+ cookie_monster->GetAllCookiesForURLAsync(
+ request_->url(),
+ base::Bind(&URLRequestHttpJob::CheckCookiePolicyAndLoad,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ CheckCookiePolicyAndLoad(CookieList());
+ }
+ } else {
+ DoStartTransaction();
+ }
+}
+
+void URLRequestHttpJob::DoLoadCookies() {
+ CookieOptions options;
+ options.set_include_httponly();
+ request_->context()->cookie_store()->GetCookiesWithOptionsAsync(
+ request_->url(), options,
+ base::Bind(&URLRequestHttpJob::OnCookiesLoaded,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestHttpJob::CheckCookiePolicyAndLoad(
+ const CookieList& cookie_list) {
+ if (CanGetCookies(cookie_list))
+ DoLoadCookies();
+ else
+ DoStartTransaction();
+}
+
+void URLRequestHttpJob::OnCookiesLoaded(const std::string& cookie_line) {
+ if (!cookie_line.empty()) {
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kCookie, cookie_line);
+ // Disable privacy mode as we are sending cookies anyway.
+ request_info_.privacy_mode = kPrivacyModeDisabled;
+ }
+ DoStartTransaction();
+}
+
+void URLRequestHttpJob::DoStartTransaction() {
+ // We may have been canceled while retrieving cookies.
+ if (GetStatus().is_success()) {
+ StartTransaction();
+ } else {
+ NotifyCanceled();
+ }
+}
+
+void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) {
+ if (result != net::OK) {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ DCHECK(transaction_.get());
+
+ const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
+ DCHECK(response_info);
+
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ FetchResponseCookies(&response_cookies_);
+
+ if (!GetResponseHeaders()->GetDateValue(&response_date_))
+ response_date_ = base::Time();
+
+ // Now, loop over the response cookies, and attempt to persist each.
+ SaveNextCookie();
+}
+
+// If the save occurs synchronously, SaveNextCookie will loop and save the next
+// cookie. If the save is deferred, the callback is responsible for continuing
+// to iterate through the cookies.
+// TODO(erikwright): Modify the CookieStore API to indicate via return value
+// whether it completed synchronously or asynchronously.
+// See http://crbug.com/131066.
+void URLRequestHttpJob::SaveNextCookie() {
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ // Used to communicate with the callback. See the implementation of
+ // OnCookieSaved.
+ scoped_refptr<SharedBoolean> callback_pending = new SharedBoolean(false);
+ scoped_refptr<SharedBoolean> save_next_cookie_running =
+ new SharedBoolean(true);
+
+ if (!(request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) &&
+ request_->context()->cookie_store() &&
+ response_cookies_.size() > 0) {
+ CookieOptions options;
+ options.set_include_httponly();
+ options.set_server_time(response_date_);
+
+ net::CookieStore::SetCookiesCallback callback(
+ base::Bind(&URLRequestHttpJob::OnCookieSaved,
+ weak_factory_.GetWeakPtr(),
+ save_next_cookie_running,
+ callback_pending));
+
+ // Loop through the cookies as long as SetCookieWithOptionsAsync completes
+ // synchronously.
+ while (!callback_pending->data &&
+ response_cookies_save_index_ < response_cookies_.size()) {
+ if (CanSetCookie(
+ response_cookies_[response_cookies_save_index_], &options)) {
+ callback_pending->data = true;
+ request_->context()->cookie_store()->SetCookieWithOptionsAsync(
+ request_->url(), response_cookies_[response_cookies_save_index_],
+ options, callback);
+ }
+ ++response_cookies_save_index_;
+ }
+ }
+
+ save_next_cookie_running->data = false;
+
+ if (!callback_pending->data) {
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+ NotifyHeadersComplete();
+ return;
+ }
+}
+
+// |save_next_cookie_running| is true when the callback is bound and set to
+// false when SaveNextCookie exits, allowing the callback to determine if the
+// save occurred synchronously or asynchronously.
+// |callback_pending| is false when the callback is invoked and will be set to
+// true by the callback, allowing SaveNextCookie to detect whether the save
+// occurred synchronously.
+// See SaveNextCookie() for more information.
+void URLRequestHttpJob::OnCookieSaved(
+ scoped_refptr<SharedBoolean> save_next_cookie_running,
+ scoped_refptr<SharedBoolean> callback_pending,
+ bool cookie_status) {
+ callback_pending->data = false;
+
+ // If we were called synchronously, return.
+ if (save_next_cookie_running->data) {
+ return;
+ }
+
+ // We were called asynchronously, so trigger the next save.
+ // We may have been canceled within OnSetCookie.
+ if (GetStatus().is_success()) {
+ SaveNextCookie();
+ } else {
+ NotifyCanceled();
+ }
+}
+
+void URLRequestHttpJob::FetchResponseCookies(
+ std::vector<std::string>* cookies) {
+ const std::string name = "Set-Cookie";
+ std::string value;
+
+ void* iter = NULL;
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ while (headers->EnumerateHeader(&iter, name, &value)) {
+ if (!value.empty())
+ cookies->push_back(value);
+ }
+}
+
+// NOTE: |ProcessStrictTransportSecurityHeader| and
+// |ProcessPublicKeyPinsHeader| have very similar structures, by design.
+void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() {
+ DCHECK(response_info_);
+ TransportSecurityState* security_state =
+ request_->context()->transport_security_state();
+ const SSLInfo& ssl_info = response_info_->ssl_info;
+
+ // Only accept HSTS headers on HTTPS connections that have no
+ // certificate errors.
+ if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
+ !security_state)
+ return;
+
+ // http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec:
+ //
+ // If a UA receives more than one STS header field in a HTTP response
+ // message over secure transport, then the UA MUST process only the
+ // first such header field.
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ std::string value;
+ if (headers->EnumerateHeader(NULL, "Strict-Transport-Security", &value))
+ security_state->AddHSTSHeader(request_info_.url.host(), value);
+}
+
+void URLRequestHttpJob::ProcessPublicKeyPinsHeader() {
+ DCHECK(response_info_);
+ TransportSecurityState* security_state =
+ request_->context()->transport_security_state();
+ const SSLInfo& ssl_info = response_info_->ssl_info;
+
+ // Only accept HPKP headers on HTTPS connections that have no
+ // certificate errors.
+ if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
+ !security_state)
+ return;
+
+ // http://tools.ietf.org/html/draft-ietf-websec-key-pinning:
+ //
+ // If a UA receives more than one PKP header field in an HTTP
+ // response message over secure transport, then the UA MUST process
+ // only the first such header field.
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ std::string value;
+ if (headers->EnumerateHeader(NULL, "Public-Key-Pins", &value))
+ security_state->AddHPKPHeader(request_info_.url.host(), value, ssl_info);
+}
+
+void URLRequestHttpJob::OnStartCompleted(int result) {
+ RecordTimer();
+
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_)
+ return;
+
+ // If the transaction was destroyed, then the job was cancelled, and
+ // we can just ignore this notification.
+ if (!transaction_.get())
+ return;
+
+ receive_headers_end_ = base::TimeTicks::Now();
+
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ const URLRequestContext* context = request_->context();
+
+ if (result == ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN &&
+ transaction_->GetResponseInfo() != NULL) {
+ FraudulentCertificateReporter* reporter =
+ context->fraudulent_certificate_reporter();
+ if (reporter != NULL) {
+ const SSLInfo& ssl_info = transaction_->GetResponseInfo()->ssl_info;
+ bool sni_available = SSLConfigService::IsSNIAvailable(
+ context->ssl_config_service());
+ const std::string& host = request_->url().host();
+
+ reporter->SendReport(host, ssl_info, sni_available);
+ }
+ }
+
+ if (result == OK) {
+ scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders();
+ if (network_delegate()) {
+ // Note that |this| may not be deleted until
+ // |on_headers_received_callback_| or
+ // |NetworkDelegate::URLRequestDestroyed()| has been called.
+ int error = network_delegate()->NotifyHeadersReceived(
+ request_,
+ on_headers_received_callback_,
+ headers.get(),
+ &override_response_headers_);
+ if (error != net::OK) {
+ if (error == net::ERR_IO_PENDING) {
+ awaiting_callback_ = true;
+ SetBlockedOnDelegate();
+ } else {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source",
+ &source));
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error));
+ }
+ return;
+ }
+ }
+
+ SaveCookiesAndNotifyHeadersComplete(net::OK);
+ } else if (IsCertificateError(result)) {
+ // We encountered an SSL certificate error. Ask our delegate to decide
+ // what we should do.
+
+ TransportSecurityState::DomainState domain_state;
+ const URLRequestContext* context = request_->context();
+ const bool fatal = context->transport_security_state() &&
+ context->transport_security_state()->GetDomainState(
+ request_info_.url.host(),
+ SSLConfigService::IsSNIAvailable(context->ssl_config_service()),
+ &domain_state) &&
+ domain_state.ShouldSSLErrorsBeFatal();
+ NotifySSLCertificateError(transaction_->GetResponseInfo()->ssl_info, fatal);
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ NotifyCertificateRequested(
+ transaction_->GetResponseInfo()->cert_request_info.get());
+ } else {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestHttpJob::OnHeadersReceivedCallback(int result) {
+ SetUnblockedOnDelegate();
+ awaiting_callback_ = false;
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, GetStatus().status());
+
+ SaveCookiesAndNotifyHeadersComplete(result);
+}
+
+void URLRequestHttpJob::OnReadCompleted(int result) {
+ read_in_progress_ = false;
+
+ if (ShouldFixMismatchedContentLength(result))
+ result = OK;
+
+ if (result == OK) {
+ NotifyDone(URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ }
+
+ NotifyReadComplete(result);
+}
+
+void URLRequestHttpJob::RestartTransactionWithAuth(
+ const AuthCredentials& credentials) {
+ auth_credentials_ = credentials;
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ receive_headers_end_ = base::TimeTicks();
+ response_cookies_.clear();
+
+ ResetTimer();
+
+ // Update the cookies, since the cookie store may have been updated from the
+ // headers in the 401/407. Since cookies were already appended to
+ // extra_headers, we need to strip them out before adding them again.
+ request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kCookie);
+
+ AddCookieHeaderAndStart();
+}
+
+void URLRequestHttpJob::SetUpload(UploadDataStream* upload) {
+ DCHECK(!transaction_.get()) << "cannot change once started";
+ request_info_.upload_data_stream = upload;
+}
+
+void URLRequestHttpJob::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ DCHECK(!transaction_.get()) << "cannot change once started";
+ request_info_.extra_headers.CopyFrom(headers);
+}
+
+LoadState URLRequestHttpJob::GetLoadState() const {
+ return transaction_.get() ?
+ transaction_->GetLoadState() : LOAD_STATE_IDLE;
+}
+
+UploadProgress URLRequestHttpJob::GetUploadProgress() const {
+ return transaction_.get() ?
+ transaction_->GetUploadProgress() : UploadProgress();
+}
+
+bool URLRequestHttpJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ return GetResponseHeaders()->GetMimeType(mime_type);
+}
+
+bool URLRequestHttpJob::GetCharset(std::string* charset) {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ return GetResponseHeaders()->GetCharset(charset);
+}
+
+void URLRequestHttpJob::GetResponseInfo(HttpResponseInfo* info) {
+ DCHECK(request_);
+ DCHECK(transaction_.get());
+
+ if (response_info_) {
+ *info = *response_info_;
+ if (override_response_headers_.get())
+ info->headers = override_response_headers_;
+ }
+}
+
+void URLRequestHttpJob::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ // If haven't made it far enough to receive any headers, don't return
+ // anything. This makes for more consistent behavior in the case of errors.
+ if (!transaction_ || receive_headers_end_.is_null())
+ return;
+ if (transaction_->GetLoadTimingInfo(load_timing_info))
+ load_timing_info->receive_headers_end = receive_headers_end_;
+}
+
+bool URLRequestHttpJob::GetResponseCookies(std::vector<std::string>* cookies) {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ // TODO(darin): Why are we extracting response cookies again? Perhaps we
+ // should just leverage response_cookies_.
+
+ cookies->clear();
+ FetchResponseCookies(cookies);
+ return true;
+}
+
+int URLRequestHttpJob::GetResponseCode() const {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return -1;
+
+ return GetResponseHeaders()->response_code();
+}
+
+Filter* URLRequestHttpJob::SetupFilter() const {
+ DCHECK(transaction_.get());
+ if (!response_info_)
+ return NULL;
+
+ std::vector<Filter::FilterType> encoding_types;
+ std::string encoding_type;
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, "Content-Encoding", &encoding_type)) {
+ encoding_types.push_back(Filter::ConvertEncodingToType(encoding_type));
+ }
+
+ if (filter_context_->IsSdchResponse()) {
+ // We are wary of proxies that discard or damage SDCH encoding. If a server
+ // explicitly states that this is not SDCH content, then we can correct our
+ // assumption that this is an SDCH response, and avoid the need to recover
+ // as though the content is corrupted (when we discover it is not SDCH
+ // encoded).
+ std::string sdch_response_status;
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "X-Sdch-Encode",
+ &sdch_response_status)) {
+ if (sdch_response_status == "0") {
+ filter_context_->ResetSdchResponseToFalse();
+ break;
+ }
+ }
+ }
+
+ // Even if encoding types are empty, there is a chance that we need to add
+ // some decoding, as some proxies strip encoding completely. In such cases,
+ // we may need to add (for example) SDCH filtering (when the context suggests
+ // it is appropriate).
+ Filter::FixupEncodingTypes(*filter_context_, &encoding_types);
+
+ return !encoding_types.empty()
+ ? Filter::Factory(encoding_types, *filter_context_) : NULL;
+}
+
+bool URLRequestHttpJob::IsSafeRedirect(const GURL& location) {
+ // HTTP is always safe.
+ // TODO(pauljensen): Remove once crbug.com/146591 is fixed.
+ if (location.is_valid() &&
+ (location.scheme() == "http" || location.scheme() == "https")) {
+ return true;
+ }
+ // Query URLRequestJobFactory as to whether |location| would be safe to
+ // redirect to.
+ return request_->context()->job_factory() &&
+ request_->context()->job_factory()->IsSafeRedirectTarget(location);
+}
+
+bool URLRequestHttpJob::NeedsAuth() {
+ int code = GetResponseCode();
+ if (code == -1)
+ return false;
+
+ // Check if we need either Proxy or WWW Authentication. This could happen
+ // because we either provided no auth info, or provided incorrect info.
+ switch (code) {
+ case 407:
+ if (proxy_auth_state_ == AUTH_STATE_CANCELED)
+ return false;
+ proxy_auth_state_ = AUTH_STATE_NEED_AUTH;
+ return true;
+ case 401:
+ if (server_auth_state_ == AUTH_STATE_CANCELED)
+ return false;
+ server_auth_state_ = AUTH_STATE_NEED_AUTH;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestHttpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK(transaction_.get());
+ DCHECK(response_info_);
+
+ // sanity checks:
+ DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH ||
+ server_auth_state_ == AUTH_STATE_NEED_AUTH);
+ DCHECK((GetResponseHeaders()->response_code() == HTTP_UNAUTHORIZED) ||
+ (GetResponseHeaders()->response_code() ==
+ HTTP_PROXY_AUTHENTICATION_REQUIRED));
+
+ *result = response_info_->auth_challenge;
+}
+
+void URLRequestHttpJob::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(transaction_.get());
+
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ } else {
+ DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ }
+
+ RestartTransactionWithAuth(credentials);
+}
+
+void URLRequestHttpJob::CancelAuth() {
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_CANCELED;
+ } else {
+ DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_CANCELED;
+ }
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ receive_headers_end_ = base::TimeTicks::Now();
+ response_cookies_.clear();
+
+ ResetTimer();
+
+ // OK, let the consumer read the error page...
+ //
+ // Because we set the AUTH_STATE_CANCELED flag, NeedsAuth will return false,
+ // which will cause the consumer to receive OnResponseStarted instead of
+ // OnAuthRequired.
+ //
+ // We have to do this via InvokeLater to avoid "recursing" the consumer.
+ //
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), OK));
+}
+
+void URLRequestHttpJob::ContinueWithCertificate(
+ X509Certificate* client_cert) {
+ DCHECK(transaction_.get());
+
+ DCHECK(!response_info_) << "should not have a response yet";
+ receive_headers_end_ = base::TimeTicks();
+
+ ResetTimer();
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartWithCertificate(client_cert, start_callback_);
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestHttpJob::ContinueDespiteLastError() {
+ // If the transaction was destroyed, then the job was cancelled.
+ if (!transaction_.get())
+ return;
+
+ DCHECK(!response_info_) << "should not have a response yet";
+ receive_headers_end_ = base::TimeTicks();
+
+ ResetTimer();
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartIgnoringLastError(start_callback_);
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+bool URLRequestHttpJob::ShouldFixMismatchedContentLength(int rv) const {
+ // Some servers send the body compressed, but specify the content length as
+ // the uncompressed size. Although this violates the HTTP spec we want to
+ // support it (as IE and FireFox do), but *only* for an exact match.
+ // See http://crbug.com/79694.
+ if (rv == net::ERR_CONTENT_LENGTH_MISMATCH ||
+ rv == net::ERR_INCOMPLETE_CHUNKED_ENCODING) {
+ if (request_ && request_->response_headers()) {
+ int64 expected_length = request_->response_headers()->GetContentLength();
+ VLOG(1) << __FUNCTION__ << "() "
+ << "\"" << request_->url().spec() << "\""
+ << " content-length = " << expected_length
+ << " pre total = " << prefilter_bytes_read()
+ << " post total = " << postfilter_bytes_read();
+ if (postfilter_bytes_read() == expected_length) {
+ // Clear the error.
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool URLRequestHttpJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK_NE(buf_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!read_in_progress_);
+
+ int rv = transaction_->Read(
+ buf, buf_size,
+ base::Bind(&URLRequestHttpJob::OnReadCompleted, base::Unretained(this)));
+
+ if (ShouldFixMismatchedContentLength(rv))
+ rv = 0;
+
+ if (rv >= 0) {
+ *bytes_read = rv;
+ if (!rv)
+ DoneWithRequest(FINISHED);
+ return true;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+
+ return false;
+}
+
+void URLRequestHttpJob::StopCaching() {
+ if (transaction_.get())
+ transaction_->StopCaching();
+}
+
+bool URLRequestHttpJob::GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ if (!transaction_)
+ return false;
+
+ return transaction_->GetFullRequestHeaders(headers);
+}
+
+void URLRequestHttpJob::DoneReading() {
+ if (transaction_.get())
+ transaction_->DoneReading();
+ DoneWithRequest(FINISHED);
+}
+
+HostPortPair URLRequestHttpJob::GetSocketAddress() const {
+ return response_info_ ? response_info_->socket_address : HostPortPair();
+}
+
+void URLRequestHttpJob::RecordTimer() {
+ if (request_creation_time_.is_null()) {
+ NOTREACHED()
+ << "The same transaction shouldn't start twice without new timing.";
+ return;
+ }
+
+ base::TimeDelta to_start = base::Time::Now() - request_creation_time_;
+ request_creation_time_ = base::Time();
+
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpTimeToFirstByte", to_start);
+}
+
+void URLRequestHttpJob::ResetTimer() {
+ if (!request_creation_time_.is_null()) {
+ NOTREACHED()
+ << "The timer was reset before it was recorded.";
+ return;
+ }
+ request_creation_time_ = base::Time::Now();
+}
+
+void URLRequestHttpJob::UpdatePacketReadTimes() {
+ if (!packet_timing_enabled_)
+ return;
+
+ if (filter_input_byte_count() <= bytes_observed_in_packets_) {
+ DCHECK_EQ(filter_input_byte_count(), bytes_observed_in_packets_);
+ return; // No new bytes have arrived.
+ }
+
+ final_packet_time_ = base::Time::Now();
+ if (!bytes_observed_in_packets_)
+ request_time_snapshot_ = request_ ? request_->request_time() : base::Time();
+
+ bytes_observed_in_packets_ = filter_input_byte_count();
+}
+
+void URLRequestHttpJob::RecordPacketStats(
+ FilterContext::StatisticSelector statistic) const {
+ if (!packet_timing_enabled_ || (final_packet_time_ == base::Time()))
+ return;
+
+ base::TimeDelta duration = final_packet_time_ - request_time_snapshot_;
+ switch (statistic) {
+ case FilterContext::SDCH_DECODE: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Sdch3.Network_Decode_Bytes_Processed_b",
+ static_cast<int>(bytes_observed_in_packets_), 500, 100000, 100);
+ return;
+ }
+ case FilterContext::SDCH_PASSTHROUGH: {
+ // Despite advertising a dictionary, we handled non-sdch compressed
+ // content.
+ return;
+ }
+
+ case FilterContext::SDCH_EXPERIMENT_DECODE: {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Sdch3.Experiment2_Decode",
+ duration,
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMinutes(10), 100);
+ return;
+ }
+ case FilterContext::SDCH_EXPERIMENT_HOLDBACK: {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Sdch3.Experiment2_Holdback",
+ duration,
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMinutes(10), 100);
+ return;
+ }
+ default:
+ NOTREACHED();
+ return;
+ }
+}
+
+// The common type of histogram we use for all compression-tracking histograms.
+#define COMPRESSION_HISTOGRAM(name, sample) \
+ do { \
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Compress." name, sample, \
+ 500, 1000000, 100); \
+ } while (0)
+
+void URLRequestHttpJob::RecordCompressionHistograms() {
+ DCHECK(request_);
+ if (!request_)
+ return;
+
+ if (is_cached_content_ || // Don't record cached content
+ !GetStatus().is_success() || // Don't record failed content
+ !IsCompressibleContent() || // Only record compressible content
+ !prefilter_bytes_read()) // Zero-byte responses aren't useful.
+ return;
+
+ // Miniature requests aren't really compressible. Don't count them.
+ const int kMinSize = 16;
+ if (prefilter_bytes_read() < kMinSize)
+ return;
+
+ // Only record for http or https urls.
+ bool is_http = request_->url().SchemeIs("http");
+ bool is_https = request_->url().SchemeIs("https");
+ if (!is_http && !is_https)
+ return;
+
+ int compressed_B = prefilter_bytes_read();
+ int decompressed_B = postfilter_bytes_read();
+ bool was_filtered = HasFilter();
+
+ // We want to record how often downloaded resources are compressed.
+ // But, we recognize that different protocols may have different
+ // properties. So, for each request, we'll put it into one of 3
+ // groups:
+ // a) SSL resources
+ // Proxies cannot tamper with compression headers with SSL.
+ // b) Non-SSL, loaded-via-proxy resources
+ // In this case, we know a proxy might have interfered.
+ // c) Non-SSL, loaded-without-proxy resources
+ // In this case, we know there was no explicit proxy. However,
+ // it is possible that a transparent proxy was still interfering.
+ //
+ // For each group, we record the same 3 histograms.
+
+ if (is_https) {
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("SSL.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("SSL.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("SSL.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (request_->was_fetched_via_proxy()) {
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("Proxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("Proxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("Proxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("NoProxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("NoProxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("NoProxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+}
+
+bool URLRequestHttpJob::IsCompressibleContent() const {
+ std::string mime_type;
+ return GetMimeType(&mime_type) &&
+ (IsSupportedJavascriptMimeType(mime_type.c_str()) ||
+ IsSupportedNonImageMimeType(mime_type.c_str()));
+}
+
+void URLRequestHttpJob::RecordPerfHistograms(CompletionCause reason) {
+ if (start_time_.is_null())
+ return;
+
+ base::TimeDelta total_time = base::TimeTicks::Now() - start_time_;
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTime", total_time);
+
+ if (reason == FINISHED) {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeSuccess", total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCancel", total_time);
+ }
+
+ if (response_info_) {
+ if (response_info_->was_cached) {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCached", total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeNotCached", total_time);
+ }
+ }
+
+ start_time_ = base::TimeTicks();
+}
+
+void URLRequestHttpJob::DoneWithRequest(CompletionCause reason) {
+ if (done_)
+ return;
+ done_ = true;
+ RecordPerfHistograms(reason);
+ if (reason == FINISHED) {
+ request_->set_received_response_content_length(prefilter_bytes_read());
+ RecordCompressionHistograms();
+ }
+}
+
+HttpResponseHeaders* URLRequestHttpJob::GetResponseHeaders() const {
+ DCHECK(transaction_.get());
+ DCHECK(transaction_->GetResponseInfo());
+ return override_response_headers_.get() ?
+ override_response_headers_.get() :
+ transaction_->GetResponseInfo()->headers.get();
+}
+
+void URLRequestHttpJob::NotifyURLRequestDestroyed() {
+ awaiting_callback_ = false;
+}
+
+void URLRequestHttpJob::OnDetachRequest() {
+ http_transaction_delegate_->OnDetachRequest();
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_http_job.h b/chromium/net/url_request/url_request_http_job.h
new file mode 100644
index 00000000000..d5f46e0174b
--- /dev/null
+++ b/chromium/net/url_request/url_request_http_job.h
@@ -0,0 +1,273 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_request_info.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_throttler_entry_interface.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+class HttpResponseInfo;
+class HttpTransaction;
+class HttpUserAgentSettings;
+class UploadDataStream;
+class URLRequestContext;
+
+// A URLRequestJob subclass that is built on top of HttpTransaction. It
+// provides an implementation for both HTTP and HTTPS.
+class NET_EXPORT_PRIVATE URLRequestHttpJob : public URLRequestJob {
+ public:
+ static URLRequestJob* Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ protected:
+ URLRequestHttpJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const HttpUserAgentSettings* http_user_agent_settings);
+
+ virtual ~URLRequestHttpJob();
+
+ // Overridden from URLRequestJob:
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+
+ RequestPriority priority() const {
+ return priority_;
+ }
+
+ private:
+ enum CompletionCause {
+ ABORTED,
+ FINISHED
+ };
+
+ typedef base::RefCountedData<bool> SharedBoolean;
+
+ class HttpFilterContext;
+ class HttpTransactionDelegateImpl;
+
+ // Shadows URLRequestJob's version of this method so we can grab cookies.
+ void NotifyHeadersComplete();
+
+ // Shadows URLRequestJob's method so we can record histograms.
+ void NotifyDone(const URLRequestStatus& status);
+
+ void DestroyTransaction();
+
+ void AddExtraHeaders();
+ void AddCookieHeaderAndStart();
+ void SaveCookiesAndNotifyHeadersComplete(int result);
+ void SaveNextCookie();
+ void FetchResponseCookies(std::vector<std::string>* cookies);
+
+ // Processes the Strict-Transport-Security header, if one exists.
+ void ProcessStrictTransportSecurityHeader();
+
+ // Processes the Public-Key-Pins header, if one exists.
+ void ProcessPublicKeyPinsHeader();
+
+ // |result| should be net::OK, or the request is canceled.
+ void OnHeadersReceivedCallback(int result);
+ void OnStartCompleted(int result);
+ void OnReadCompleted(int result);
+ void NotifyBeforeSendHeadersCallback(int result);
+
+ void RestartTransactionWithAuth(const AuthCredentials& credentials);
+
+ // Overridden from URLRequestJob:
+ virtual void SetUpload(UploadDataStream* upload) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+ virtual void GetResponseInfo(HttpResponseInfo* info) OVERRIDE;
+ virtual void GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual Filter* SetupFilter() const OVERRIDE;
+ virtual bool IsSafeRedirect(const GURL& location) OVERRIDE;
+ virtual bool NeedsAuth() OVERRIDE;
+ virtual void GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo>*) OVERRIDE;
+ virtual void SetAuth(const AuthCredentials& credentials) OVERRIDE;
+ virtual void CancelAuth() OVERRIDE;
+ virtual void ContinueWithCertificate(X509Certificate* client_cert) OVERRIDE;
+ virtual void ContinueDespiteLastError() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual void StopCaching() OVERRIDE;
+ virtual bool GetFullRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void DoneReading() OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual void NotifyURLRequestDestroyed() OVERRIDE;
+
+ void RecordTimer();
+ void ResetTimer();
+
+ virtual void UpdatePacketReadTimes() OVERRIDE;
+ void RecordPacketStats(FilterContext::StatisticSelector statistic) const;
+
+ void RecordCompressionHistograms();
+ bool IsCompressibleContent() const;
+
+ // Starts the transaction if extensions using the webrequest API do not
+ // object.
+ void StartTransaction();
+ // If |result| is net::OK, calls StartTransactionInternal. Otherwise notifies
+ // cancellation.
+ void MaybeStartTransactionInternal(int result);
+ void StartTransactionInternal();
+
+ void RecordPerfHistograms(CompletionCause reason);
+ void DoneWithRequest(CompletionCause reason);
+
+ // Callback functions for Cookie Monster
+ void DoLoadCookies();
+ void CheckCookiePolicyAndLoad(const CookieList& cookie_list);
+ void OnCookiesLoaded(const std::string& cookie_line);
+ void DoStartTransaction();
+
+ // See the implementation for a description of save_next_cookie_running and
+ // callback_pending.
+ void OnCookieSaved(scoped_refptr<SharedBoolean> save_next_cookie_running,
+ scoped_refptr<SharedBoolean> callback_pending,
+ bool cookie_status);
+
+ // Some servers send the body compressed, but specify the content length as
+ // the uncompressed size. If this is the case, we return true in order
+ // to request to work around this non-adherence to the HTTP standard.
+ // |rv| is the standard return value of a read function indicating the number
+ // of bytes read or, if negative, an error code.
+ bool ShouldFixMismatchedContentLength(int rv) const;
+
+ // Returns the effective response headers, considering that they may be
+ // overridden by |override_response_headers_|.
+ HttpResponseHeaders* GetResponseHeaders() const;
+
+ // Override of the private interface of URLRequestJob.
+ virtual void OnDetachRequest() OVERRIDE;
+
+ RequestPriority priority_;
+
+ HttpRequestInfo request_info_;
+ const HttpResponseInfo* response_info_;
+
+ std::vector<std::string> response_cookies_;
+ size_t response_cookies_save_index_;
+ base::Time response_date_;
+
+ // Auth states for proxy and origin server.
+ AuthState proxy_auth_state_;
+ AuthState server_auth_state_;
+ AuthCredentials auth_credentials_;
+
+ CompletionCallback start_callback_;
+ CompletionCallback notify_before_headers_sent_callback_;
+
+ bool read_in_progress_;
+
+ // An URL for an SDCH dictionary as suggested in a Get-Dictionary HTTP header.
+ GURL sdch_dictionary_url_;
+
+ scoped_ptr<HttpTransaction> transaction_;
+
+ // This is used to supervise traffic and enforce exponential
+ // back-off. May be NULL.
+ scoped_refptr<URLRequestThrottlerEntryInterface> throttling_entry_;
+
+ // Indicated if an SDCH dictionary was advertised, and hence an SDCH
+ // compressed response is expected. We use this to help detect (accidental?)
+ // proxy corruption of a response, which sometimes marks SDCH content as
+ // having no content encoding <oops>.
+ bool sdch_dictionary_advertised_;
+
+ // For SDCH latency experiments, when we are able to do SDCH, we may enable
+ // either an SDCH latency test xor a pass through test. The following bools
+ // indicate what we decided on for this instance.
+ bool sdch_test_activated_; // Advertising a dictionary for sdch.
+ bool sdch_test_control_; // Not even accepting-content sdch.
+
+ // For recording of stats, we need to remember if this is cached content.
+ bool is_cached_content_;
+
+ base::Time request_creation_time_;
+
+ // Data used for statistics gathering. This data is only used for histograms
+ // and is not required. It is only gathered if packet_timing_enabled_ == true.
+ //
+ // TODO(jar): improve the quality of the gathered info by gathering most times
+ // at a lower point in the network stack, assuring we have actual packet
+ // boundaries, rather than approximations. Also note that input byte count
+ // as gathered here is post-SSL, and post-cache-fetch, and does not reflect
+ // true packet arrival times in such cases.
+
+ // Enable recording of packet arrival times for histogramming.
+ bool packet_timing_enabled_;
+ bool done_; // True when we are done doing work.
+
+ // The number of bytes that have been accounted for in packets (where some of
+ // those packets may possibly have had their time of arrival recorded).
+ int64 bytes_observed_in_packets_;
+
+ // The request time may not be available when we are being destroyed, so we
+ // snapshot it early on.
+ base::Time request_time_snapshot_;
+
+ // Since we don't save all packet times in packet_times_, we save the
+ // last time for use in histograms.
+ base::Time final_packet_time_;
+
+ // The start time for the job, ignoring re-starts.
+ base::TimeTicks start_time_;
+
+ // When the transaction finished reading the request headers.
+ base::TimeTicks receive_headers_end_;
+
+ scoped_ptr<HttpFilterContext> filter_context_;
+ base::WeakPtrFactory<URLRequestHttpJob> weak_factory_;
+
+ CompletionCallback on_headers_received_callback_;
+
+ // We allow the network delegate to modify a copy of the response headers.
+ // This prevents modifications of headers that are shared with the underlying
+ // layers of the network stack.
+ scoped_refptr<HttpResponseHeaders> override_response_headers_;
+
+ // Flag used to verify that |this| is not deleted while we are awaiting
+ // a callback from the NetworkDelegate. Used as a fail-fast mechanism.
+ // True if we are waiting a callback and
+ // NetworkDelegate::NotifyURLRequestDestroyed has not been called, yet,
+ // to inform the NetworkDelegate that it may not call back.
+ bool awaiting_callback_;
+
+ scoped_ptr<HttpTransactionDelegateImpl> http_transaction_delegate_;
+
+ const HttpUserAgentSettings* http_user_agent_settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestHttpJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
diff --git a/chromium/net/url_request/url_request_http_job_unittest.cc b/chromium/net/url_request/url_request_http_job_unittest.cc
new file mode 100644
index 00000000000..ce7205407cf
--- /dev/null
+++ b/chromium/net/url_request/url_request_http_job_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2013 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/url_request/url_request_http_job.h"
+
+#include <cstddef>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/auth.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+// Inherit from URLRequestHttpJob to expose the priority and some
+// other hidden functions.
+class TestURLRequestHttpJob : public URLRequestHttpJob {
+ public:
+ explicit TestURLRequestHttpJob(URLRequest* request)
+ : URLRequestHttpJob(request, NULL,
+ request->context()->http_user_agent_settings()) {}
+
+ using URLRequestHttpJob::SetPriority;
+ using URLRequestHttpJob::Start;
+ using URLRequestHttpJob::Kill;
+ using URLRequestHttpJob::priority;
+
+ protected:
+ virtual ~TestURLRequestHttpJob() {}
+};
+
+class URLRequestHttpJobTest : public ::testing::Test {
+ protected:
+ URLRequestHttpJobTest()
+ : req_(GURL("http://www.example.com"), &delegate_, &context_, NULL) {
+ context_.set_http_transaction_factory(&network_layer_);
+ }
+
+ MockNetworkLayer network_layer_;
+ TestURLRequestContext context_;
+ TestDelegate delegate_;
+ TestURLRequest req_;
+};
+
+// Make sure that SetPriority actually sets the URLRequestHttpJob's
+// priority, both before and after start.
+TEST_F(URLRequestHttpJobTest, SetPriorityBasic) {
+ scoped_refptr<TestURLRequestHttpJob> job(new TestURLRequestHttpJob(&req_));
+ EXPECT_EQ(DEFAULT_PRIORITY, job->priority());
+
+ job->SetPriority(LOWEST);
+ EXPECT_EQ(LOWEST, job->priority());
+
+ job->SetPriority(LOW);
+ EXPECT_EQ(LOW, job->priority());
+
+ job->Start();
+ EXPECT_EQ(LOW, job->priority());
+
+ job->SetPriority(MEDIUM);
+ EXPECT_EQ(MEDIUM, job->priority());
+}
+
+// Make sure that URLRequestHttpJob passes on its priority to its
+// transaction on start.
+TEST_F(URLRequestHttpJobTest, SetTransactionPriorityOnStart) {
+ scoped_refptr<TestURLRequestHttpJob> job(new TestURLRequestHttpJob(&req_));
+ job->SetPriority(LOW);
+
+ EXPECT_FALSE(network_layer_.last_transaction());
+
+ job->Start();
+
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+}
+
+// Make sure that URLRequestHttpJob passes on its priority updates to
+// its transaction.
+TEST_F(URLRequestHttpJobTest, SetTransactionPriority) {
+ scoped_refptr<TestURLRequestHttpJob> job(new TestURLRequestHttpJob(&req_));
+ job->SetPriority(LOW);
+ job->Start();
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+
+ job->SetPriority(HIGHEST);
+ EXPECT_EQ(HIGHEST, network_layer_.last_transaction()->priority());
+}
+
+// Make sure that URLRequestHttpJob passes on its priority updates to
+// newly-created transactions after the first one.
+TEST_F(URLRequestHttpJobTest, SetSubsequentTransactionPriority) {
+ scoped_refptr<TestURLRequestHttpJob> job(new TestURLRequestHttpJob(&req_));
+ job->Start();
+
+ job->SetPriority(LOW);
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+
+ job->Kill();
+ network_layer_.ClearLastTransaction();
+
+ // Creates a second transaction.
+ job->Start();
+ ASSERT_TRUE(network_layer_.last_transaction());
+ EXPECT_EQ(LOW, network_layer_.last_transaction()->priority());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job.cc b/chromium/net/url_request/url_request_job.cc
new file mode 100644
index 00000000000..669c845de98
--- /dev/null
+++ b/chromium/net/url_request/url_request_job.cc
@@ -0,0 +1,736 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+URLRequestJob::URLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : request_(request),
+ done_(false),
+ prefilter_bytes_read_(0),
+ postfilter_bytes_read_(0),
+ filter_input_byte_count_(0),
+ filter_needs_more_output_space_(false),
+ filtered_read_buffer_len_(0),
+ has_handled_response_(false),
+ expected_content_size_(-1),
+ deferred_redirect_status_code_(-1),
+ network_delegate_(network_delegate),
+ weak_factory_(this) {
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ if (power_monitor)
+ power_monitor->AddObserver(this);
+}
+
+void URLRequestJob::SetUpload(UploadDataStream* upload) {
+}
+
+void URLRequestJob::SetExtraRequestHeaders(const HttpRequestHeaders& headers) {
+}
+
+void URLRequestJob::SetPriority(RequestPriority priority) {
+}
+
+void URLRequestJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ // Make sure the request is notified that we are done. We assume that the
+ // request took care of setting its error status before calling Kill.
+ if (request_)
+ NotifyCanceled();
+}
+
+void URLRequestJob::DetachRequest() {
+ request_ = NULL;
+ OnDetachRequest();
+}
+
+// This function calls ReadData to get stream data. If a filter exists, passes
+// the data to the attached filter. Then returns the output from filter back to
+// the caller.
+bool URLRequestJob::Read(IOBuffer* buf, int buf_size, int *bytes_read) {
+ bool rv = false;
+
+ DCHECK_LT(buf_size, 1000000); // Sanity check.
+ DCHECK(buf);
+ DCHECK(bytes_read);
+ DCHECK(filtered_read_buffer_.get() == NULL);
+ DCHECK_EQ(0, filtered_read_buffer_len_);
+
+ *bytes_read = 0;
+
+ // Skip Filter if not present.
+ if (!filter_.get()) {
+ rv = ReadRawDataHelper(buf, buf_size, bytes_read);
+ } else {
+ // Save the caller's buffers while we do IO
+ // in the filter's buffers.
+ filtered_read_buffer_ = buf;
+ filtered_read_buffer_len_ = buf_size;
+
+ if (ReadFilteredData(bytes_read)) {
+ rv = true; // We have data to return.
+
+ // It is fine to call DoneReading even if ReadFilteredData receives 0
+ // bytes from the net, but we avoid making that call if we know for
+ // sure that's the case (ReadRawDataHelper path).
+ if (*bytes_read == 0)
+ DoneReading();
+ } else {
+ rv = false; // Error, or a new IO is pending.
+ }
+ }
+ if (rv && *bytes_read == 0)
+ NotifyDone(URLRequestStatus());
+ return rv;
+}
+
+void URLRequestJob::StopCaching() {
+ // Nothing to do here.
+}
+
+bool URLRequestJob::GetFullRequestHeaders(HttpRequestHeaders* headers) const {
+ // Most job types don't send request headers.
+ return false;
+}
+
+LoadState URLRequestJob::GetLoadState() const {
+ return LOAD_STATE_IDLE;
+}
+
+UploadProgress URLRequestJob::GetUploadProgress() const {
+ return UploadProgress();
+}
+
+bool URLRequestJob::GetCharset(std::string* charset) {
+ return false;
+}
+
+void URLRequestJob::GetResponseInfo(HttpResponseInfo* info) {
+}
+
+void URLRequestJob::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ // Only certain request types return more than just request start times.
+}
+
+bool URLRequestJob::GetResponseCookies(std::vector<std::string>* cookies) {
+ return false;
+}
+
+Filter* URLRequestJob::SetupFilter() const {
+ return NULL;
+}
+
+bool URLRequestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ // For non-HTTP jobs, headers will be null.
+ HttpResponseHeaders* headers = request_->response_headers();
+ if (!headers)
+ return false;
+
+ std::string value;
+ if (!headers->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = headers->response_code();
+ return true;
+}
+
+bool URLRequestJob::IsSafeRedirect(const GURL& location) {
+ return true;
+}
+
+bool URLRequestJob::NeedsAuth() {
+ return false;
+}
+
+void URLRequestJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info) {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::SetAuth(const AuthCredentials& credentials) {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::CancelAuth() {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::ContinueWithCertificate(
+ X509Certificate* client_cert) {
+ // The derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::ContinueDespiteLastError() {
+ // Implementations should know how to recover from errors they generate.
+ // If this code was reached, we are trying to recover from an error that
+ // we don't know how to recover from.
+ NOTREACHED();
+}
+
+void URLRequestJob::FollowDeferredRedirect() {
+ DCHECK(deferred_redirect_status_code_ != -1);
+
+ // NOTE: deferred_redirect_url_ may be invalid, and attempting to redirect to
+ // such an URL will fail inside FollowRedirect. The DCHECK above asserts
+ // that we called OnReceivedRedirect.
+
+ // It is also possible that FollowRedirect will drop the last reference to
+ // this job, so we need to reset our members before calling it.
+
+ SetUnblockedOnDelegate();
+
+ GURL redirect_url = deferred_redirect_url_;
+ int redirect_status_code = deferred_redirect_status_code_;
+
+ deferred_redirect_url_ = GURL();
+ deferred_redirect_status_code_ = -1;
+
+ FollowRedirect(redirect_url, redirect_status_code);
+}
+
+bool URLRequestJob::GetMimeType(std::string* mime_type) const {
+ return false;
+}
+
+int URLRequestJob::GetResponseCode() const {
+ return -1;
+}
+
+HostPortPair URLRequestJob::GetSocketAddress() const {
+ return HostPortPair();
+}
+
+void URLRequestJob::OnSuspend() {
+ Kill();
+}
+
+void URLRequestJob::NotifyURLRequestDestroyed() {
+}
+
+URLRequestJob::~URLRequestJob() {
+ base::PowerMonitor* power_monitor = base::PowerMonitor::Get();
+ if (power_monitor)
+ power_monitor->RemoveObserver(this);
+}
+
+void URLRequestJob::NotifyCertificateRequested(
+ SSLCertRequestInfo* cert_request_info) {
+ if (!request_)
+ return; // The request was destroyed, so there is no more work to do.
+
+ request_->NotifyCertificateRequested(cert_request_info);
+}
+
+void URLRequestJob::NotifySSLCertificateError(const SSLInfo& ssl_info,
+ bool fatal) {
+ if (!request_)
+ return; // The request was destroyed, so there is no more work to do.
+
+ request_->NotifySSLCertificateError(ssl_info, fatal);
+}
+
+bool URLRequestJob::CanGetCookies(const CookieList& cookie_list) const {
+ if (!request_)
+ return false; // The request was destroyed, so there is no more work to do.
+
+ return request_->CanGetCookies(cookie_list);
+}
+
+bool URLRequestJob::CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const {
+ if (!request_)
+ return false; // The request was destroyed, so there is no more work to do.
+
+ return request_->CanSetCookie(cookie_line, options);
+}
+
+bool URLRequestJob::CanEnablePrivacyMode() const {
+ if (!request_)
+ return false; // The request was destroyed, so there is no more work to do.
+
+ return request_->CanEnablePrivacyMode();
+}
+
+void URLRequestJob::NotifyHeadersComplete() {
+ if (!request_ || !request_->has_delegate())
+ return; // The request was destroyed, so there is no more work to do.
+
+ if (has_handled_response_)
+ return;
+
+ DCHECK(!request_->status().is_io_pending());
+
+ // Initialize to the current time, and let the subclass optionally override
+ // the time stamps if it has that information. The default request_time is
+ // set by URLRequest before it calls our Start method.
+ request_->response_info_.response_time = base::Time::Now();
+ GetResponseInfo(&request_->response_info_);
+
+ // When notifying the delegate, the delegate can release the request
+ // (and thus release 'this'). After calling to the delgate, we must
+ // check the request pointer to see if it still exists, and return
+ // immediately if it has been destroyed. self_preservation ensures our
+ // survival until we can get out of this method.
+ scoped_refptr<URLRequestJob> self_preservation(this);
+
+ if (request_)
+ request_->OnHeadersComplete();
+
+ GURL new_location;
+ int http_status_code;
+ if (IsRedirectResponse(&new_location, &http_status_code)) {
+ const GURL& url = request_->url();
+
+ // Move the reference fragment of the old location to the new one if the
+ // new one has none. This duplicates mozilla's behavior.
+ if (url.is_valid() && url.has_ref() && !new_location.has_ref()) {
+ GURL::Replacements replacements;
+ // Reference the |ref| directly out of the original URL to avoid a
+ // malloc.
+ replacements.SetRef(url.spec().data(),
+ url.parsed_for_possibly_invalid_spec().ref);
+ new_location = new_location.ReplaceComponents(replacements);
+ }
+
+ bool defer_redirect = false;
+ request_->NotifyReceivedRedirect(new_location, &defer_redirect);
+
+ // Ensure that the request wasn't detached or destroyed in
+ // NotifyReceivedRedirect
+ if (!request_ || !request_->has_delegate())
+ return;
+
+ // If we were not cancelled, then maybe follow the redirect.
+ if (request_->status().is_success()) {
+ if (defer_redirect) {
+ deferred_redirect_url_ = new_location;
+ deferred_redirect_status_code_ = http_status_code;
+ SetBlockedOnDelegate();
+ } else {
+ FollowRedirect(new_location, http_status_code);
+ }
+ return;
+ }
+ } else if (NeedsAuth()) {
+ scoped_refptr<AuthChallengeInfo> auth_info;
+ GetAuthChallengeInfo(&auth_info);
+ // Need to check for a NULL auth_info because the server may have failed
+ // to send a challenge with the 401 response.
+ if (auth_info.get()) {
+ request_->NotifyAuthRequired(auth_info.get());
+ // Wait for SetAuth or CancelAuth to be called.
+ return;
+ }
+ }
+
+ has_handled_response_ = true;
+ if (request_->status().is_success())
+ filter_.reset(SetupFilter());
+
+ if (!filter_.get()) {
+ std::string content_length;
+ request_->GetResponseHeaderByName("content-length", &content_length);
+ if (!content_length.empty())
+ base::StringToInt64(content_length, &expected_content_size_);
+ }
+
+ request_->NotifyResponseStarted();
+}
+
+void URLRequestJob::NotifyReadComplete(int bytes_read) {
+ if (!request_ || !request_->has_delegate())
+ return; // The request was destroyed, so there is no more work to do.
+
+ // TODO(darin): Bug 1004233. Re-enable this test once all of the chrome
+ // unit_tests have been fixed to not trip this.
+ //DCHECK(!request_->status().is_io_pending());
+
+ // The headers should be complete before reads complete
+ DCHECK(has_handled_response_);
+
+ OnRawReadComplete(bytes_read);
+
+ // Don't notify if we had an error.
+ if (!request_->status().is_success())
+ return;
+
+ // When notifying the delegate, the delegate can release the request
+ // (and thus release 'this'). After calling to the delegate, we must
+ // check the request pointer to see if it still exists, and return
+ // immediately if it has been destroyed. self_preservation ensures our
+ // survival until we can get out of this method.
+ scoped_refptr<URLRequestJob> self_preservation(this);
+
+ if (filter_.get()) {
+ // Tell the filter that it has more data
+ FilteredDataRead(bytes_read);
+
+ // Filter the data.
+ int filter_bytes_read = 0;
+ if (ReadFilteredData(&filter_bytes_read)) {
+ if (!filter_bytes_read)
+ DoneReading();
+ request_->NotifyReadCompleted(filter_bytes_read);
+ }
+ } else {
+ request_->NotifyReadCompleted(bytes_read);
+ }
+ DVLOG(1) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " pre bytes read = " << bytes_read
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = " << postfilter_bytes_read_;
+}
+
+void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
+ DCHECK(!has_handled_response_);
+ has_handled_response_ = true;
+ if (request_) {
+ request_->set_status(status);
+ request_->NotifyResponseStarted();
+ }
+}
+
+void URLRequestJob::NotifyDone(const URLRequestStatus &status) {
+ DCHECK(!done_) << "Job sending done notification twice";
+ if (done_)
+ return;
+ done_ = true;
+
+ // Unless there was an error, we should have at least tried to handle
+ // the response before getting here.
+ DCHECK(has_handled_response_ || !status.is_success());
+
+ // As with NotifyReadComplete, we need to take care to notice if we were
+ // destroyed during a delegate callback.
+ if (request_) {
+ request_->set_is_pending(false);
+ // With async IO, it's quite possible to have a few outstanding
+ // requests. We could receive a request to Cancel, followed shortly
+ // by a successful IO. For tracking the status(), once there is
+ // an error, we do not change the status back to success. To
+ // enforce this, only set the status if the job is so far
+ // successful.
+ if (request_->status().is_success()) {
+ if (status.status() == URLRequestStatus::FAILED) {
+ request_->net_log().AddEventWithNetErrorCode(NetLog::TYPE_FAILED,
+ status.error());
+ }
+ request_->set_status(status);
+ }
+ }
+
+ // Complete this notification later. This prevents us from re-entering the
+ // delegate if we're done because of a synchronous call.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestJob::CompleteNotifyDone,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestJob::CompleteNotifyDone() {
+ // Check if we should notify the delegate that we're done because of an error.
+ if (request_ &&
+ !request_->status().is_success() &&
+ request_->has_delegate()) {
+ // We report the error differently depending on whether we've called
+ // OnResponseStarted yet.
+ if (has_handled_response_) {
+ // We signal the error by calling OnReadComplete with a bytes_read of -1.
+ request_->NotifyReadCompleted(-1);
+ } else {
+ has_handled_response_ = true;
+ request_->NotifyResponseStarted();
+ }
+ }
+}
+
+void URLRequestJob::NotifyCanceled() {
+ if (!done_) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::CANCELED, ERR_ABORTED));
+ }
+}
+
+void URLRequestJob::NotifyRestartRequired() {
+ DCHECK(!has_handled_response_);
+ if (GetStatus().status() != URLRequestStatus::CANCELED)
+ request_->Restart();
+}
+
+void URLRequestJob::SetBlockedOnDelegate() {
+ request_->SetBlockedOnDelegate();
+}
+
+void URLRequestJob::SetUnblockedOnDelegate() {
+ request_->SetUnblockedOnDelegate();
+}
+
+bool URLRequestJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int *bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+ return true;
+}
+
+void URLRequestJob::DoneReading() {
+ // Do nothing.
+}
+
+void URLRequestJob::FilteredDataRead(int bytes_read) {
+ DCHECK(filter_.get()); // don't add data if there is no filter
+ filter_->FlushStreamBuffer(bytes_read);
+}
+
+bool URLRequestJob::ReadFilteredData(int* bytes_read) {
+ DCHECK(filter_.get()); // don't add data if there is no filter
+ DCHECK(filtered_read_buffer_.get() !=
+ NULL); // we need to have a buffer to fill
+ DCHECK_GT(filtered_read_buffer_len_, 0); // sanity check
+ DCHECK_LT(filtered_read_buffer_len_, 1000000); // sanity check
+ DCHECK(raw_read_buffer_.get() ==
+ NULL); // there should be no raw read buffer yet
+
+ bool rv = false;
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (!filter_needs_more_output_space_ && !filter_->stream_data_len()) {
+ // We don't have any raw data to work with, so
+ // read from the socket.
+ int filtered_data_read;
+ if (ReadRawDataForFilter(&filtered_data_read)) {
+ if (filtered_data_read > 0) {
+ filter_->FlushStreamBuffer(filtered_data_read); // Give data to filter.
+ } else {
+ return true; // EOF
+ }
+ } else {
+ return false; // IO Pending (or error)
+ }
+ }
+
+ if ((filter_->stream_data_len() || filter_needs_more_output_space_)
+ && !is_done()) {
+ // Get filtered data.
+ int filtered_data_len = filtered_read_buffer_len_;
+ Filter::FilterStatus status;
+ int output_buffer_size = filtered_data_len;
+ status = filter_->ReadData(filtered_read_buffer_->data(),
+ &filtered_data_len);
+
+ if (filter_needs_more_output_space_ && 0 == filtered_data_len) {
+ // filter_needs_more_output_space_ was mistaken... there are no more bytes
+ // and we should have at least tried to fill up the filter's input buffer.
+ // Correct the state, and try again.
+ filter_needs_more_output_space_ = false;
+ return ReadFilteredData(bytes_read);
+ }
+
+ switch (status) {
+ case Filter::FILTER_DONE: {
+ filter_needs_more_output_space_ = false;
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_NEED_MORE_DATA: {
+ filter_needs_more_output_space_ =
+ (filtered_data_len == output_buffer_size);
+ // We have finished filtering all data currently in the buffer.
+ // There might be some space left in the output buffer. One can
+ // consider reading more data from the stream to feed the filter
+ // and filling up the output buffer. This leads to more complicated
+ // buffer management and data notification mechanisms.
+ // We can revisit this issue if there is a real perf need.
+ if (filtered_data_len > 0) {
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ } else {
+ // Read again since we haven't received enough data yet (e.g., we may
+ // not have a complete gzip header yet)
+ rv = ReadFilteredData(bytes_read);
+ }
+ break;
+ }
+ case Filter::FILTER_OK: {
+ filter_needs_more_output_space_ =
+ (filtered_data_len == output_buffer_size);
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_ERROR: {
+ DVLOG(1) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " Filter Error";
+ filter_needs_more_output_space_ = false;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_CONTENT_DECODING_FAILED));
+ rv = false;
+ break;
+ }
+ default: {
+ NOTREACHED();
+ filter_needs_more_output_space_ = false;
+ rv = false;
+ break;
+ }
+ }
+ DVLOG(2) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " rv = " << rv
+ << " post bytes read = " << filtered_data_len
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = "
+ << postfilter_bytes_read_;
+ // If logging all bytes is enabled, log the filtered bytes read.
+ if (rv && request() && request()->net_log().IsLoggingBytes() &&
+ filtered_data_len > 0) {
+ request()->net_log().AddByteTransferEvent(
+ NetLog::TYPE_URL_REQUEST_JOB_FILTERED_BYTES_READ,
+ filtered_data_len, filtered_read_buffer_->data());
+ }
+ } else {
+ // we are done, or there is no data left.
+ rv = true;
+ }
+
+ if (rv) {
+ // When we successfully finished a read, we no longer need to
+ // save the caller's buffers. Release our reference.
+ filtered_read_buffer_ = NULL;
+ filtered_read_buffer_len_ = 0;
+ }
+ return rv;
+}
+
+const URLRequestStatus URLRequestJob::GetStatus() {
+ if (request_)
+ return request_->status();
+ // If the request is gone, we must be cancelled.
+ return URLRequestStatus(URLRequestStatus::CANCELED,
+ ERR_ABORTED);
+}
+
+void URLRequestJob::SetStatus(const URLRequestStatus &status) {
+ if (request_)
+ request_->set_status(status);
+}
+
+bool URLRequestJob::ReadRawDataForFilter(int* bytes_read) {
+ bool rv = false;
+
+ DCHECK(bytes_read);
+ DCHECK(filter_.get());
+
+ *bytes_read = 0;
+
+ // Get more pre-filtered data if needed.
+ // TODO(mbelshe): is it possible that the filter needs *MORE* data
+ // when there is some data already in the buffer?
+ if (!filter_->stream_data_len() && !is_done()) {
+ IOBuffer* stream_buffer = filter_->stream_buffer();
+ int stream_buffer_size = filter_->stream_buffer_size();
+ rv = ReadRawDataHelper(stream_buffer, stream_buffer_size, bytes_read);
+ }
+ return rv;
+}
+
+bool URLRequestJob::ReadRawDataHelper(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(!request_->status().is_io_pending());
+ DCHECK(raw_read_buffer_.get() == NULL);
+
+ // Keep a pointer to the read buffer, so we have access to it in the
+ // OnRawReadComplete() callback in the event that the read completes
+ // asynchronously.
+ raw_read_buffer_ = buf;
+ bool rv = ReadRawData(buf, buf_size, bytes_read);
+
+ if (!request_->status().is_io_pending()) {
+ // If |filter_| is NULL, and logging all bytes is enabled, log the raw
+ // bytes read.
+ if (!filter_.get() && request() && request()->net_log().IsLoggingBytes() &&
+ *bytes_read > 0) {
+ request()->net_log().AddByteTransferEvent(
+ NetLog::TYPE_URL_REQUEST_JOB_BYTES_READ,
+ *bytes_read, raw_read_buffer_->data());
+ }
+
+ // If the read completes synchronously, either success or failure,
+ // invoke the OnRawReadComplete callback so we can account for the
+ // completed read.
+ OnRawReadComplete(*bytes_read);
+ }
+ return rv;
+}
+
+void URLRequestJob::FollowRedirect(const GURL& location, int http_status_code) {
+ int rv = request_->Redirect(location, http_status_code);
+ if (rv != OK)
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+}
+
+void URLRequestJob::OnRawReadComplete(int bytes_read) {
+ DCHECK(raw_read_buffer_.get());
+ if (bytes_read > 0) {
+ RecordBytesRead(bytes_read);
+ }
+ raw_read_buffer_ = NULL;
+}
+
+void URLRequestJob::RecordBytesRead(int bytes_read) {
+ filter_input_byte_count_ += bytes_read;
+ prefilter_bytes_read_ += bytes_read;
+ if (!filter_.get())
+ postfilter_bytes_read_ += bytes_read;
+ DVLOG(2) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " pre bytes read = " << bytes_read
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = " << postfilter_bytes_read_;
+ UpdatePacketReadTimes(); // Facilitate stats recording if it is active.
+ if (network_delegate_)
+ network_delegate_->NotifyRawBytesRead(*request_, bytes_read);
+}
+
+bool URLRequestJob::FilterHasData() {
+ return filter_.get() && filter_->stream_data_len();
+}
+
+void URLRequestJob::UpdatePacketReadTimes() {
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job.h b/chromium/net/url_request/url_request_job.h
new file mode 100644
index 00000000000..1acf4750d11
--- /dev/null
+++ b/chromium/net/url_request/url_request_job.h
@@ -0,0 +1,411 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/power_monitor/power_observer.h"
+#include "net/base/filter.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_progress.h"
+#include "net/cookies/canonical_cookie.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class AuthChallengeInfo;
+class AuthCredentials;
+class CookieOptions;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class IOBuffer;
+struct LoadTimingInfo;
+class NetworkDelegate;
+class SSLCertRequestInfo;
+class SSLInfo;
+class URLRequest;
+class UploadDataStream;
+class URLRequestStatus;
+class X509Certificate;
+
+class NET_EXPORT URLRequestJob
+ : public base::RefCounted<URLRequestJob>,
+ public base::PowerObserver {
+ public:
+ explicit URLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate);
+
+ // Returns the request that owns this job. THIS POINTER MAY BE NULL if the
+ // request was destroyed.
+ URLRequest* request() const {
+ return request_;
+ }
+
+ // Sets the upload data, most requests have no upload data, so this is a NOP.
+ // Job types supporting upload data will override this.
+ virtual void SetUpload(UploadDataStream* upload_data_stream);
+
+ // Sets extra request headers for Job types that support request
+ // headers. Called once before Start() is called.
+ virtual void SetExtraRequestHeaders(const HttpRequestHeaders& headers);
+
+ // Sets the priority of the job. Called once before Start() is
+ // called, but also when the priority of the parent request changes.
+ virtual void SetPriority(RequestPriority priority);
+
+ // If any error occurs while starting the Job, NotifyStartError should be
+ // called.
+ // This helps ensure that all errors follow more similar notification code
+ // paths, which should simplify testing.
+ virtual void Start() = 0;
+
+ // This function MUST somehow call NotifyDone/NotifyCanceled or some requests
+ // will get leaked. Certain callers use that message to know when they can
+ // delete their URLRequest object, even when doing a cancel. The default
+ // Kill implementation calls NotifyCanceled, so it is recommended that
+ // subclasses call URLRequestJob::Kill() after doing any additional work.
+ //
+ // The job should endeavor to stop working as soon as is convenient, but must
+ // not send and complete notifications from inside this function. Instead,
+ // complete notifications (including "canceled") should be sent from a
+ // callback run from the message loop.
+ //
+ // The job is not obliged to immediately stop sending data in response to
+ // this call, nor is it obliged to fail with "canceled" unless not all data
+ // was sent as a result. A typical case would be where the job is almost
+ // complete and can succeed before the canceled notification can be
+ // dispatched (from the message loop).
+ //
+ // The job should be prepared to receive multiple calls to kill it, but only
+ // one notification must be issued.
+ virtual void Kill();
+
+ // Called to detach the request from this Job. Results in the Job being
+ // killed off eventually. The job must not use the request pointer any more.
+ void DetachRequest();
+
+ // Called to read post-filtered data from this Job, returning the number of
+ // bytes read, 0 when there is no more data, or -1 if there was an error.
+ // This is just the backend for URLRequest::Read, see that function for
+ // more info.
+ bool Read(IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // Stops further caching of this request, if any. For more info, see
+ // URLRequest::StopCaching().
+ virtual void StopCaching();
+
+ virtual bool GetFullRequestHeaders(HttpRequestHeaders* headers) const;
+
+ // Called to fetch the current load state for the job.
+ virtual LoadState GetLoadState() const;
+
+ // Called to get the upload progress in bytes.
+ virtual UploadProgress GetUploadProgress() const;
+
+ // Called to fetch the charset for this request. Only makes sense for some
+ // types of requests. Returns true on success. Calling this on a type that
+ // doesn't have a charset will return false.
+ virtual bool GetCharset(std::string* charset);
+
+ // Called to get response info.
+ virtual void GetResponseInfo(HttpResponseInfo* info);
+
+ // This returns the times when events actually occurred, rather than the time
+ // each event blocked the request. See FixupLoadTimingInfo in url_request.h
+ // for more information on the difference.
+ virtual void GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const;
+
+ // Returns the cookie values included in the response, if applicable.
+ // Returns true if applicable.
+ // NOTE: This removes the cookies from the job, so it will only return
+ // useful results once per job.
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies);
+
+ // Called to setup a stream filter for this request. An example of filter is
+ // content encoding/decoding.
+ // Subclasses should return the appropriate Filter, or NULL for no Filter.
+ // This class takes ownership of the returned Filter.
+ //
+ // The default implementation returns NULL.
+ virtual Filter* SetupFilter() const;
+
+ // Called to determine if this response is a redirect. Only makes sense
+ // for some types of requests. This method returns true if the response
+ // is a redirect, and fills in the location param with the URL of the
+ // redirect. The HTTP status code (e.g., 302) is filled into
+ // |*http_status_code| to signify the type of redirect.
+ //
+ // The caller is responsible for following the redirect by setting up an
+ // appropriate replacement Job. Note that the redirected location may be
+ // invalid, the caller should be sure it can handle this.
+ //
+ // The default implementation inspects the response_info_.
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
+
+ // Called to determine if it is okay to redirect this job to the specified
+ // location. This may be used to implement protocol-specific restrictions.
+ // If this function returns false, then the URLRequest will fail
+ // reporting ERR_UNSAFE_REDIRECT.
+ virtual bool IsSafeRedirect(const GURL& location);
+
+ // Called to determine if this response is asking for authentication. Only
+ // makes sense for some types of requests. The caller is responsible for
+ // obtaining the credentials passing them to SetAuth.
+ virtual bool NeedsAuth();
+
+ // Fills the authentication info with the server's response.
+ virtual void GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info);
+
+ // Resend the request with authentication credentials.
+ virtual void SetAuth(const AuthCredentials& credentials);
+
+ // Display the error page without asking for credentials again.
+ virtual void CancelAuth();
+
+ virtual void ContinueWithCertificate(X509Certificate* client_cert);
+
+ // Continue processing the request ignoring the last error.
+ virtual void ContinueDespiteLastError();
+
+ void FollowDeferredRedirect();
+
+ // Returns true if the Job is done producing response data and has called
+ // NotifyDone on the request.
+ bool is_done() const { return done_; }
+
+ // Get/Set expected content size
+ int64 expected_content_size() const { return expected_content_size_; }
+ void set_expected_content_size(const int64& size) {
+ expected_content_size_ = size;
+ }
+
+ // Whether we have processed the response for that request yet.
+ bool has_response_started() const { return has_handled_response_; }
+
+ // These methods are not applicable to all connections.
+ virtual bool GetMimeType(std::string* mime_type) const;
+ virtual int GetResponseCode() const;
+
+ // Returns the socket address for the connection.
+ // See url_request.h for details.
+ virtual HostPortPair GetSocketAddress() const;
+
+ // base::PowerObserver methods:
+ // We invoke URLRequestJob::Kill on suspend (crbug.com/4606).
+ virtual void OnSuspend() OVERRIDE;
+
+ // Called after a NetworkDelegate has been informed that the URLRequest
+ // will be destroyed. This is used to track that no pending callbacks
+ // exist at destruction time of the URLRequestJob, unless they have been
+ // canceled by an explicit NetworkDelegate::NotifyURLRequestDestroyed() call.
+ virtual void NotifyURLRequestDestroyed();
+
+ protected:
+ friend class base::RefCounted<URLRequestJob>;
+ virtual ~URLRequestJob();
+
+ // Notifies the job that a certificate is requested.
+ void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
+
+ // Notifies the job about an SSL certificate error.
+ void NotifySSLCertificateError(const SSLInfo& ssl_info, bool fatal);
+
+ // Delegates to URLRequest::Delegate.
+ bool CanGetCookies(const CookieList& cookie_list) const;
+
+ // Delegates to URLRequest::Delegate.
+ bool CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const;
+
+ // Delegates to URLRequest::Delegate.
+ bool CanEnablePrivacyMode() const;
+
+ // Notifies the job that headers have been received.
+ void NotifyHeadersComplete();
+
+ // Notifies the request that the job has completed a Read operation.
+ void NotifyReadComplete(int bytes_read);
+
+ // Notifies the request that a start error has occurred.
+ void NotifyStartError(const URLRequestStatus& status);
+
+ // NotifyDone marks when we are done with a request. It is really
+ // a glorified set_status, but also does internal state checking and
+ // job tracking. It should be called once per request, when the job is
+ // finished doing all IO.
+ void NotifyDone(const URLRequestStatus& status);
+
+ // Some work performed by NotifyDone must be completed on a separate task
+ // so as to avoid re-entering the delegate. This method exists to perform
+ // that work.
+ void CompleteNotifyDone();
+
+ // Used as an asynchronous callback for Kill to notify the URLRequest
+ // that we were canceled.
+ void NotifyCanceled();
+
+ // Notifies the job the request should be restarted.
+ // Should only be called if the job has not started a resposne.
+ void NotifyRestartRequired();
+
+ // Called when the network delegate blocks or unblocks this request when
+ // intercepting certain requests.
+ void SetBlockedOnDelegate();
+ void SetUnblockedOnDelegate();
+
+ // Called to read raw (pre-filtered) data from this Job.
+ // If returning true, data was read from the job. buf will contain
+ // the data, and bytes_read will receive the number of bytes read.
+ // If returning true, and bytes_read is returned as 0, there is no
+ // additional data to be read.
+ // If returning false, an error occurred or an async IO is now pending.
+ // If async IO is pending, the status of the request will be
+ // URLRequestStatus::IO_PENDING, and buf must remain available until the
+ // operation is completed. See comments on URLRequest::Read for more
+ // info.
+ virtual bool ReadRawData(IOBuffer* buf, int buf_size, int *bytes_read);
+
+ // Called to tell the job that a filter has successfully reached the end of
+ // the stream.
+ virtual void DoneReading();
+
+ // Informs the filter that data has been read into its buffer
+ void FilteredDataRead(int bytes_read);
+
+ // Reads filtered data from the request. Returns true if successful,
+ // false otherwise. Note, if there is not enough data received to
+ // return data, this call can issue a new async IO request under
+ // the hood.
+ bool ReadFilteredData(int *bytes_read);
+
+ // Whether the response is being filtered in this job.
+ // Only valid after NotifyHeadersComplete() has been called.
+ bool HasFilter() { return filter_ != NULL; }
+
+ // At or near destruction time, a derived class may request that the filters
+ // be destroyed so that statistics can be gathered while the derived class is
+ // still present to assist in calculations. This is used by URLRequestHttpJob
+ // to get SDCH to emit stats.
+ void DestroyFilters() { filter_.reset(); }
+
+ // Provides derived classes with access to the request's network delegate.
+ NetworkDelegate* network_delegate() { return network_delegate_; }
+
+ // The status of the job.
+ const URLRequestStatus GetStatus();
+
+ // Set the status of the job.
+ void SetStatus(const URLRequestStatus& status);
+
+ // The number of bytes read before passing to the filter.
+ int prefilter_bytes_read() const { return prefilter_bytes_read_; }
+
+ // The number of bytes read after passing through the filter.
+ int postfilter_bytes_read() const { return postfilter_bytes_read_; }
+
+ // Total number of bytes read from network (or cache) and typically handed
+ // to filter to process. Used to histogram compression ratios, and error
+ // recovery scenarios in filters.
+ int64 filter_input_byte_count() const { return filter_input_byte_count_; }
+
+ // The request that initiated this job. This value MAY BE NULL if the
+ // request was released by DetachRequest().
+ URLRequest* request_;
+
+ private:
+ // When data filtering is enabled, this function is used to read data
+ // for the filter. Returns true if raw data was read. Returns false if
+ // an error occurred (or we are waiting for IO to complete).
+ bool ReadRawDataForFilter(int *bytes_read);
+
+ // Invokes ReadRawData and records bytes read if the read completes
+ // synchronously.
+ bool ReadRawDataHelper(IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // Called in response to a redirect that was not canceled to follow the
+ // redirect. The current job will be replaced with a new job loading the
+ // given redirect destination.
+ void FollowRedirect(const GURL& location, int http_status_code);
+
+ // Called after every raw read. If |bytes_read| is > 0, this indicates
+ // a successful read of |bytes_read| unfiltered bytes. If |bytes_read|
+ // is 0, this indicates that there is no additional data to read. If
+ // |bytes_read| is < 0, an error occurred and no bytes were read.
+ void OnRawReadComplete(int bytes_read);
+
+ // Updates the profiling info and notifies observers that an additional
+ // |bytes_read| unfiltered bytes have been read for this job.
+ void RecordBytesRead(int bytes_read);
+
+ // Called to query whether there is data available in the filter to be read
+ // out.
+ bool FilterHasData();
+
+ // Subclasses may implement this method to record packet arrival times.
+ // The default implementation does nothing.
+ virtual void UpdatePacketReadTimes();
+
+ // Custom handler for derived classes when the request is detached.
+ virtual void OnDetachRequest() {}
+
+ // Indicates that the job is done producing data, either it has completed
+ // all the data or an error has been encountered. Set exclusively by
+ // NotifyDone so that it is kept in sync with the request.
+ bool done_;
+
+ int prefilter_bytes_read_;
+ int postfilter_bytes_read_;
+ int64 filter_input_byte_count_;
+
+ // The data stream filter which is enabled on demand.
+ scoped_ptr<Filter> filter_;
+
+ // If the filter filled its output buffer, then there is a change that it
+ // still has internal data to emit, and this flag is set.
+ bool filter_needs_more_output_space_;
+
+ // When we filter data, we receive data into the filter buffers. After
+ // processing the filtered data, we return the data in the caller's buffer.
+ // While the async IO is in progress, we save the user buffer here, and
+ // when the IO completes, we fill this in.
+ scoped_refptr<IOBuffer> filtered_read_buffer_;
+ int filtered_read_buffer_len_;
+
+ // We keep a pointer to the read buffer while asynchronous reads are
+ // in progress, so we are able to pass those bytes to job observers.
+ scoped_refptr<IOBuffer> raw_read_buffer_;
+
+ // Used by HandleResponseIfNecessary to track whether we've sent the
+ // OnResponseStarted callback and potentially redirect callbacks as well.
+ bool has_handled_response_;
+
+ // Expected content size
+ int64 expected_content_size_;
+
+ // Set when a redirect is deferred.
+ GURL deferred_redirect_url_;
+ int deferred_redirect_status_code_;
+
+ // The network delegate to use with this request, if any.
+ NetworkDelegate* network_delegate_;
+
+ base::WeakPtrFactory<URLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_H_
diff --git a/chromium/net/url_request/url_request_job_factory.cc b/chromium/net/url_request/url_request_job_factory.cc
new file mode 100644
index 00000000000..e203cb17d6c
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_factory.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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/url_request/url_request_job_factory.h"
+
+namespace net {
+
+URLRequestJobFactory::ProtocolHandler::~ProtocolHandler() {}
+
+bool URLRequestJobFactory::ProtocolHandler::IsSafeRedirectTarget(
+ const GURL& location) const {
+ return true;
+}
+
+URLRequestJobFactory::URLRequestJobFactory() {}
+
+URLRequestJobFactory::~URLRequestJobFactory() {}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job_factory.h b/chromium/net/url_request/url_request_job_factory.h
new file mode 100644
index 00000000000..d4cc49e0582
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_factory.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+
+class NET_EXPORT URLRequestJobFactory
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // TODO(shalev): Move this to URLRequestJobFactoryImpl.
+ class NET_EXPORT ProtocolHandler {
+ public:
+ virtual ~ProtocolHandler();
+
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+
+ // Indicates if it should be safe to redirect to |location|. Should handle
+ // protocols handled by MaybeCreateJob(). Only called when registered with
+ // URLRequestJobFactoryImpl::SetProtocolHandler() not called when used with
+ // ProtocolInterceptJobFactory.
+ // NOTE(pauljensen): Default implementation returns true.
+ virtual bool IsSafeRedirectTarget(const GURL& location) const;
+ };
+
+ URLRequestJobFactory();
+ virtual ~URLRequestJobFactory();
+
+ virtual URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const = 0;
+
+ virtual bool IsHandledProtocol(const std::string& scheme) const = 0;
+
+ virtual bool IsHandledURL(const GURL& url) const = 0;
+
+ virtual bool IsSafeRedirectTarget(const GURL& location) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobFactory);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
diff --git a/chromium/net/url_request/url_request_job_factory_impl.cc b/chromium/net/url_request/url_request_job_factory_impl.cc
new file mode 100644
index 00000000000..f456da2798f
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_factory_impl.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 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/url_request/url_request_job_factory_impl.h"
+
+#include "base/stl_util.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_request_job_manager.h"
+#include "url/gurl.h"
+
+namespace net {
+
+URLRequestJobFactoryImpl::URLRequestJobFactoryImpl() {}
+
+URLRequestJobFactoryImpl::~URLRequestJobFactoryImpl() {
+ STLDeleteValues(&protocol_handler_map_);
+}
+
+bool URLRequestJobFactoryImpl::SetProtocolHandler(
+ const std::string& scheme,
+ ProtocolHandler* protocol_handler) {
+ DCHECK(CalledOnValidThread());
+
+ if (!protocol_handler) {
+ ProtocolHandlerMap::iterator it = protocol_handler_map_.find(scheme);
+ if (it == protocol_handler_map_.end())
+ return false;
+
+ delete it->second;
+ protocol_handler_map_.erase(it);
+ return true;
+ }
+
+ if (ContainsKey(protocol_handler_map_, scheme))
+ return false;
+ protocol_handler_map_[scheme] = protocol_handler;
+ return true;
+}
+
+URLRequestJob* URLRequestJobFactoryImpl::MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme);
+ if (it == protocol_handler_map_.end())
+ return NULL;
+ return it->second->MaybeCreateJob(request, network_delegate);
+}
+
+bool URLRequestJobFactoryImpl::IsHandledProtocol(
+ const std::string& scheme) const {
+ DCHECK(CalledOnValidThread());
+ return ContainsKey(protocol_handler_map_, scheme) ||
+ URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
+}
+
+bool URLRequestJobFactoryImpl::IsHandledURL(const GURL& url) const {
+ if (!url.is_valid()) {
+ // We handle error cases.
+ return true;
+ }
+ return IsHandledProtocol(url.scheme());
+}
+
+bool URLRequestJobFactoryImpl::IsSafeRedirectTarget(
+ const GURL& location) const {
+ DCHECK(CalledOnValidThread());
+ if (!location.is_valid()) {
+ // Error cases are safely handled.
+ return true;
+ }
+ ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(
+ location.scheme());
+ if (it == protocol_handler_map_.end()) {
+ // Unhandled cases are safely handled.
+ return true;
+ }
+ return it->second->IsSafeRedirectTarget(location);
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job_factory_impl.h b/chromium/net/url_request/url_request_job_factory_impl.h
new file mode 100644
index 00000000000..4f03fb073d4
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_factory_impl.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestJobFactoryImpl : public URLRequestJobFactory {
+ public:
+ URLRequestJobFactoryImpl();
+ virtual ~URLRequestJobFactoryImpl();
+
+ // Sets the ProtocolHandler for a scheme. Returns true on success, false on
+ // failure (a ProtocolHandler already exists for |scheme|). On success,
+ // URLRequestJobFactory takes ownership of |protocol_handler|.
+ bool SetProtocolHandler(const std::string& scheme,
+ ProtocolHandler* protocol_handler);
+
+ // URLRequestJobFactory implementation
+ virtual URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE;
+ virtual bool IsHandledURL(const GURL& url) const OVERRIDE;
+ virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE;
+
+ private:
+ typedef std::map<std::string, ProtocolHandler*> ProtocolHandlerMap;
+
+ ProtocolHandlerMap protocol_handler_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobFactoryImpl);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
diff --git a/chromium/net/url_request/url_request_job_factory_impl_unittest.cc b/chromium/net/url_request/url_request_job_factory_impl_unittest.cc
new file mode 100644
index 00000000000..078e16a8df0
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_factory_impl_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_job_factory_impl.h"
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockURLRequestJob : public URLRequestJob {
+ public:
+ MockURLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const URLRequestStatus& status)
+ : URLRequestJob(request, network_delegate),
+ status_(status),
+ weak_factory_(this) {}
+
+ virtual void Start() OVERRIDE {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockURLRequestJob::StartAsync, weak_factory_.GetWeakPtr()));
+ }
+
+ protected:
+ virtual ~MockURLRequestJob() {}
+
+ private:
+ void StartAsync() {
+ SetStatus(status_);
+ NotifyHeadersComplete();
+ }
+
+ URLRequestStatus status_;
+ base::WeakPtrFactory<MockURLRequestJob> weak_factory_;
+};
+
+class DummyProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ return new MockURLRequestJob(
+ request,
+ network_delegate,
+ URLRequestStatus(URLRequestStatus::SUCCESS, OK));
+ }
+};
+
+TEST(URLRequestJobFactoryTest, NoProtocolHandler) {
+ TestDelegate delegate;
+ TestURLRequestContext request_context;
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context, NULL);
+ request.Start();
+
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, BasicProtocolHandler) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.SetProtocolHandler("foo", new DummyProtocolHandler);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context, NULL);
+ request.Start();
+
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::SUCCESS, request.status().status());
+ EXPECT_EQ(OK, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, DeleteProtocolHandler) {
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.SetProtocolHandler("foo", new DummyProtocolHandler);
+ job_factory.SetProtocolHandler("foo", NULL);
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job_manager.cc b/chromium/net/url_request/url_request_job_manager.cc
new file mode 100644
index 00000000000..a089c6b4f5c
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_manager.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_job_manager.h"
+
+#include <algorithm>
+
+#include "base/memory/singleton.h"
+#include "build/build_config.h"
+#include "base/strings/string_util.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_http_job.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+// The built-in set of protocol factories
+namespace {
+
+struct SchemeToFactory {
+ const char* scheme;
+ URLRequest::ProtocolFactory* factory;
+};
+
+} // namespace
+
+static const SchemeToFactory kBuiltinFactories[] = {
+ { "http", URLRequestHttpJob::Factory },
+ { "https", URLRequestHttpJob::Factory },
+};
+
+// static
+URLRequestJobManager* URLRequestJobManager::GetInstance() {
+ return Singleton<URLRequestJobManager>::get();
+}
+
+URLRequestJob* URLRequestJobManager::CreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(IsAllowedThread());
+
+ // If we are given an invalid URL, then don't even try to inspect the scheme.
+ if (!request->url().is_valid())
+ return new URLRequestErrorJob(request, network_delegate, ERR_INVALID_URL);
+
+ // We do this here to avoid asking interceptors about unsupported schemes.
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_UNKNOWN_URL_SCHEME);
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_UNKNOWN_URL_SCHEME);
+ }
+
+ // THREAD-SAFETY NOTICE:
+ // We do not need to acquire the lock here since we are only reading our
+ // data structures. They should only be modified on the current thread.
+
+ // See if the request should be intercepted.
+ //
+
+ // TODO(pauljensen): Remove this when AppCacheInterceptor is a
+ // ProtocolHandler, see crbug.com/161547.
+ if (!(request->load_flags() & LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeIntercept(request, network_delegate);
+ if (job)
+ return job;
+ }
+ }
+
+ if (job_factory) {
+ URLRequestJob* job = job_factory->MaybeCreateJobWithProtocolHandler(
+ scheme, request, network_delegate);
+ if (job)
+ return job;
+ }
+
+ // TODO(willchan): Remove this in favor of
+ // URLRequestJobFactory::ProtocolHandler.
+ // See if the request should be handled by a registered protocol factory.
+ // If the registered factory returns null, then we want to fall-back to the
+ // built-in protocol factory.
+ FactoryMap::const_iterator i = factories_.find(scheme);
+ if (i != factories_.end()) {
+ URLRequestJob* job = i->second(request, network_delegate, scheme);
+ if (job)
+ return job;
+ }
+
+ // See if the request should be handled by a built-in protocol factory.
+ for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i) {
+ if (scheme == kBuiltinFactories[i].scheme) {
+ URLRequestJob* job = (kBuiltinFactories[i].factory)(
+ request, network_delegate, scheme);
+ DCHECK(job); // The built-in factories are not expected to fail!
+ return job;
+ }
+ }
+
+ // If we reached here, then it means that a registered protocol factory
+ // wasn't interested in handling the URL. That is fairly unexpected, and we
+ // don't have a specific error to report here :-(
+ LOG(WARNING) << "Failed to map: " << request->url().spec();
+ return new URLRequestErrorJob(request, network_delegate, ERR_FAILED);
+}
+
+URLRequestJob* URLRequestJobManager::MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) const {
+ DCHECK(IsAllowedThread());
+ if (!request->url().is_valid() ||
+ request->load_flags() & LOAD_DISABLE_INTERCEPT ||
+ request->status().status() == URLRequestStatus::CANCELED) {
+ return NULL;
+ }
+
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return NULL;
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return NULL;
+ }
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeInterceptRedirect(request,
+ network_delegate,
+ location);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
+URLRequestJob* URLRequestJobManager::MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(IsAllowedThread());
+ if (!request->url().is_valid() ||
+ request->load_flags() & LOAD_DISABLE_INTERCEPT ||
+ request->status().status() == URLRequestStatus::CANCELED) {
+ return NULL;
+ }
+
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return NULL;
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return NULL;
+ }
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeInterceptResponse(request,
+ network_delegate);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
+bool URLRequestJobManager::SupportsScheme(const std::string& scheme) const {
+ // The set of registered factories may change on another thread.
+ {
+ base::AutoLock locked(lock_);
+ if (factories_.find(scheme) != factories_.end())
+ return true;
+ }
+
+ for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i)
+ if (LowerCaseEqualsASCII(scheme, kBuiltinFactories[i].scheme))
+ return true;
+
+ return false;
+}
+
+URLRequest::ProtocolFactory* URLRequestJobManager::RegisterProtocolFactory(
+ const std::string& scheme,
+ URLRequest::ProtocolFactory* factory) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ URLRequest::ProtocolFactory* old_factory;
+ FactoryMap::iterator i = factories_.find(scheme);
+ if (i != factories_.end()) {
+ old_factory = i->second;
+ } else {
+ old_factory = NULL;
+ }
+ if (factory) {
+ factories_[scheme] = factory;
+ } else if (i != factories_.end()) { // uninstall any old one
+ factories_.erase(i);
+ }
+ return old_factory;
+}
+
+void URLRequestJobManager::RegisterRequestInterceptor(
+ URLRequest::Interceptor* interceptor) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ DCHECK(std::find(interceptors_.begin(), interceptors_.end(), interceptor) ==
+ interceptors_.end());
+ interceptors_.push_back(interceptor);
+}
+
+void URLRequestJobManager::UnregisterRequestInterceptor(
+ URLRequest::Interceptor* interceptor) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ InterceptorList::iterator i =
+ std::find(interceptors_.begin(), interceptors_.end(), interceptor);
+ DCHECK(i != interceptors_.end());
+ interceptors_.erase(i);
+}
+
+URLRequestJobManager::URLRequestJobManager()
+ : allowed_thread_(0),
+ allowed_thread_initialized_(false) {
+}
+
+URLRequestJobManager::~URLRequestJobManager() {}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_job_manager.h b/chromium/net/url_request/url_request_job_manager.h
new file mode 100644
index 00000000000..ea441bea316
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_manager.h
@@ -0,0 +1,116 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "net/url_request/url_request.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace net {
+
+// This class is responsible for managing the set of protocol factories and
+// request interceptors that determine how an URLRequestJob gets created to
+// handle an URLRequest.
+//
+// MULTI-THREADING NOTICE:
+// URLRequest is designed to have all consumers on a single thread, and
+// so no attempt is made to support ProtocolFactory or Interceptor instances
+// being registered/unregistered or in any way poked on multiple threads.
+// However, we do support checking for supported schemes FROM ANY THREAD
+// (i.e., it is safe to call SupportsScheme on any thread).
+//
+class URLRequestJobManager {
+ public:
+ // Returns the singleton instance.
+ static URLRequestJobManager* GetInstance();
+
+ // Instantiate an URLRequestJob implementation based on the registered
+ // interceptors and protocol factories. This will always succeed in
+ // returning a job unless we are--in the extreme case--out of memory.
+ URLRequestJob* CreateJob(URLRequest* request,
+ NetworkDelegate* network_delegate) const;
+
+ // Allows interceptors to hijack the request after examining the new location
+ // of a redirect. Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) const;
+
+ // Allows interceptors to hijack the request after examining the response
+ // status and headers. This is also called when there is no server response
+ // at all to allow interception of failed requests due to network errors.
+ // Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const;
+
+ // Returns true if there is a protocol factory registered for the given
+ // scheme. Note: also returns true if there is a built-in handler for the
+ // given scheme.
+ bool SupportsScheme(const std::string& scheme) const;
+
+ // Register a protocol factory associated with the given scheme. The factory
+ // parameter may be null to clear any existing association. Returns the
+ // previously registered protocol factory if any.
+ URLRequest::ProtocolFactory* RegisterProtocolFactory(
+ const std::string& scheme, URLRequest::ProtocolFactory* factory);
+
+ // Register/unregister a request interceptor.
+ void RegisterRequestInterceptor(URLRequest::Interceptor* interceptor);
+ void UnregisterRequestInterceptor(URLRequest::Interceptor* interceptor);
+
+ private:
+ typedef std::map<std::string, URLRequest::ProtocolFactory*> FactoryMap;
+ typedef std::vector<URLRequest::Interceptor*> InterceptorList;
+ friend struct DefaultSingletonTraits<URLRequestJobManager>;
+
+ URLRequestJobManager();
+ ~URLRequestJobManager();
+
+ // The first guy to call this function sets the allowed thread. This way we
+ // avoid needing to define that thread externally. Since we expect all
+ // callers to be on the same thread, we don't worry about threads racing to
+ // set the allowed thread.
+ bool IsAllowedThread() const {
+#if 0
+ if (!allowed_thread_initialized_) {
+ allowed_thread_ = base::PlatformThread::CurrentId();
+ allowed_thread_initialized_ = true;
+ }
+ return allowed_thread_ == base::PlatformThread::CurrentId();
+#else
+ // The previous version of this check used GetCurrentThread on Windows to
+ // get thread handles to compare. Unfortunately, GetCurrentThread returns
+ // a constant pseudo-handle (0xFFFFFFFE), and therefore IsAllowedThread
+ // always returned true. The above code that's turned off is the correct
+ // code, but causes the tree to turn red because some caller isn't
+ // respecting our thread requirements. We're turning off the check for now;
+ // bug http://b/issue?id=1338969 has been filed to fix things and turn the
+ // check back on.
+ return true;
+ }
+
+ // We use this to assert that CreateJob and the registration functions all
+ // run on the same thread.
+ mutable base::PlatformThreadId allowed_thread_;
+ mutable bool allowed_thread_initialized_;
+#endif
+
+ mutable base::Lock lock_;
+ FactoryMap factories_;
+ InterceptorList interceptors_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobManager);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
diff --git a/chromium/net/url_request/url_request_job_unittest.cc b/chromium/net/url_request/url_request_job_unittest.cc
new file mode 100644
index 00000000000..354915fe12b
--- /dev/null
+++ b/chromium/net/url_request/url_request_job_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_job.h"
+
+#include "net/http/http_transaction_unittest.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// This is a header that signals the end of the data.
+const char kGzipGata[] = "\x1f\x08b\x08\0\0\0\0\0\0\3\3\0\0\0\0\0\0\0\0";
+
+void GZipServer(const net::HttpRequestInfo* request,
+ std::string* response_status, std::string* response_headers,
+ std::string* response_data) {
+ response_data->assign(kGzipGata, sizeof(kGzipGata));
+}
+
+const MockTransaction kGZip_Transaction = {
+ "http://www.google.com/gzyp",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n"
+ "Content-Encoding: gzip\n"
+ "Content-Length: 30\n", // Intentionally wrong.
+ base::Time(),
+ "",
+ TEST_MODE_NORMAL,
+ &GZipServer,
+ 0,
+ net::OK
+};
+
+} // namespace
+
+TEST(URLRequestJob, TransactionNotifiedWhenDone) {
+ MockNetworkLayer network_layer;
+ net::TestURLRequestContext context;
+ context.set_http_transaction_factory(&network_layer);
+
+ net::TestDelegate d;
+ net::TestURLRequest req(GURL(kGZip_Transaction.url), &d, &context, NULL);
+ AddMockTransaction(&kGZip_Transaction);
+
+ req.set_method("GET");
+ req.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(network_layer.done_reading_called());
+
+ RemoveMockTransaction(&kGZip_Transaction);
+}
+
+TEST(URLRequestJob, SyncTransactionNotifiedWhenDone) {
+ MockNetworkLayer network_layer;
+ net::TestURLRequestContext context;
+ context.set_http_transaction_factory(&network_layer);
+
+ net::TestDelegate d;
+ net::TestURLRequest req(GURL(kGZip_Transaction.url), &d, &context, NULL);
+ MockTransaction transaction(kGZip_Transaction);
+ transaction.test_mode = TEST_MODE_SYNC_ALL;
+ AddMockTransaction(&transaction);
+
+ req.set_method("GET");
+ req.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(network_layer.done_reading_called());
+
+ RemoveMockTransaction(&transaction);
+}
diff --git a/chromium/net/url_request/url_request_netlog_params.cc b/chromium/net/url_request/url_request_netlog_params.cc
new file mode 100644
index 00000000000..12c0d5d3d65
--- /dev/null
+++ b/chromium/net/url_request/url_request_netlog_params.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_netlog_params.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "url/gurl.h"
+
+namespace net {
+
+base::Value* NetLogURLRequestStartCallback(const GURL* url,
+ const std::string* method,
+ int load_flags,
+ RequestPriority priority,
+ int64 upload_id,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("url", url->possibly_invalid_spec());
+ dict->SetString("method", *method);
+ dict->SetInteger("load_flags", load_flags);
+ dict->SetInteger("priority", static_cast<int>(priority));
+ if (upload_id > -1)
+ dict->SetString("upload_id", base::Int64ToString(upload_id));
+ return dict;
+}
+
+bool StartEventLoadFlagsFromEventParams(const base::Value* event_params,
+ int* load_flags) {
+ const base::DictionaryValue* dict;
+ if (!event_params->GetAsDictionary(&dict) ||
+ !dict->GetInteger("load_flags", load_flags)) {
+ *load_flags = 0;
+ return false;
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_netlog_params.h b/chromium/net/url_request/url_request_netlog_params.h
new file mode 100644
index 00000000000..f2e0dceda28
--- /dev/null
+++ b/chromium/net/url_request/url_request_netlog_params.h
@@ -0,0 +1,40 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+#define NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+// Returns a Value containing NetLog parameters for starting a URLRequest.
+NET_EXPORT base::Value* NetLogURLRequestStartCallback(
+ const GURL* url,
+ const std::string* method,
+ int load_flags,
+ RequestPriority priority,
+ int64 upload_id,
+ NetLog::LogLevel /* log_level */);
+
+// Attempts to extract the load flags from a Value created by the above
+// function. On success, sets |load_flags| accordingly and returns true.
+// On failure, sets |load_flags| to 0.
+NET_EXPORT bool StartEventLoadFlagsFromEventParams(
+ const base::Value* event_params,
+ int* load_flags);
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
diff --git a/chromium/net/url_request/url_request_redirect_job.cc b/chromium/net/url_request/url_request_redirect_job.cc
new file mode 100644
index 00000000000..6454e36191c
--- /dev/null
+++ b/chromium/net/url_request/url_request_redirect_job.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_redirect_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/load_timing_info.h"
+
+namespace net {
+
+URLRequestRedirectJob::URLRequestRedirectJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& redirect_destination,
+ StatusCode http_status_code)
+ : URLRequestJob(request, network_delegate),
+ redirect_destination_(redirect_destination),
+ http_status_code_(http_status_code),
+ weak_factory_(this) {}
+
+void URLRequestRedirectJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestRedirectJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestRedirectJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ *location = redirect_destination_;
+ *http_status_code = http_status_code_;
+ return true;
+}
+
+URLRequestRedirectJob::~URLRequestRedirectJob() {}
+
+void URLRequestRedirectJob::StartAsync() {
+ receive_headers_end_ = base::TimeTicks::Now();
+ NotifyHeadersComplete();
+}
+
+void URLRequestRedirectJob::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ // Set send_start and send_end to receive_headers_end_ to keep consistent
+ // with network cache behavior.
+ load_timing_info->send_start = receive_headers_end_;
+ load_timing_info->send_end = receive_headers_end_;
+ load_timing_info->receive_headers_end = receive_headers_end_;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_redirect_job.h b/chromium/net/url_request/url_request_redirect_job.h
new file mode 100644
index 00000000000..1c18a7f65be
--- /dev/null
+++ b/chromium/net/url_request/url_request_redirect_job.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+class GURL;
+
+namespace net {
+
+// A URLRequestJob that will redirect the request to the specified
+// URL. This is useful to restart a request at a different URL based
+// on the result of another job.
+class NET_EXPORT URLRequestRedirectJob : public URLRequestJob {
+ public:
+ // Valid status codes for the redirect job. Other 30x codes are theoretically
+ // valid, but unused so far. Both 302 and 307 are temporary redirects, with
+ // the difference being that 302 converts POSTs to GETs and removes upload
+ // data.
+ enum StatusCode {
+ REDIRECT_302_FOUND = 302,
+ REDIRECT_307_TEMPORARY_REDIRECT = 307,
+ };
+
+ // Constructs a job that redirects to the specified URL.
+ URLRequestRedirectJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& redirect_destination,
+ StatusCode http_status_code);
+
+ virtual void Start() OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+
+ virtual void GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+
+ private:
+ virtual ~URLRequestRedirectJob();
+
+ void StartAsync();
+
+ const GURL redirect_destination_;
+ const int http_status_code_;
+ base::TimeTicks receive_headers_end_;
+
+ base::WeakPtrFactory<URLRequestRedirectJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
diff --git a/chromium/net/url_request/url_request_simple_job.cc b/chromium/net/url_request/url_request_simple_job.cc
new file mode 100644
index 00000000000..bd945557e3d
--- /dev/null
+++ b/chromium/net/url_request/url_request_simple_job.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_simple_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+URLRequestSimpleJob::URLRequestSimpleJob(
+ URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ data_offset_(0),
+ weak_factory_(this) {}
+
+void URLRequestSimpleJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestSimpleJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestSimpleJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = mime_type_;
+ return true;
+}
+
+bool URLRequestSimpleJob::GetCharset(std::string* charset) {
+ *charset = charset_;
+ return true;
+}
+
+URLRequestSimpleJob::~URLRequestSimpleJob() {}
+
+bool URLRequestSimpleJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+ int remaining = static_cast<int>(data_.size()) - data_offset_;
+ if (buf_size > remaining)
+ buf_size = remaining;
+ memcpy(buf->data(), data_.data() + data_offset_, buf_size);
+ data_offset_ += buf_size;
+ *bytes_read = buf_size;
+ return true;
+}
+
+void URLRequestSimpleJob::StartAsync() {
+ if (!request_)
+ return;
+
+ int result = GetData(&mime_type_, &charset_, &data_,
+ base::Bind(&URLRequestSimpleJob::OnGetDataCompleted,
+ weak_factory_.GetWeakPtr()));
+ if (result != ERR_IO_PENDING)
+ OnGetDataCompleted(result);
+}
+
+void URLRequestSimpleJob::OnGetDataCompleted(int result) {
+ if (result == OK) {
+ // Notify that the headers are complete
+ NotifyHeadersComplete();
+ } else {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_simple_job.h b/chromium/net/url_request/url_request_simple_job.h
new file mode 100644
index 00000000000..7577151debe
--- /dev/null
+++ b/chromium/net/url_request/url_request_simple_job.h
@@ -0,0 +1,63 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class URLRequest;
+
+class NET_EXPORT URLRequestSimpleJob : public URLRequestJob {
+ public:
+ URLRequestSimpleJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ virtual void Start() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestSimpleJob();
+
+ // Subclasses must override the way response data is determined.
+ // The return value should be:
+ // - OK if data is obtained;
+ // - ERR_IO_PENDING if async processing is needed to finish obtaining data.
+ // This is the only case when |callback| should be called after
+ // completion of the operation. In other situations |callback| should
+ // never be called;
+ // - any other ERR_* code to indicate an error. This code will be used
+ // as the error code in the URLRequestStatus when the URLRequest
+ // is finished.
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const = 0;
+
+ protected:
+ void StartAsync();
+
+ private:
+ void OnGetDataCompleted(int result);
+
+ std::string mime_type_;
+ std::string charset_;
+ std::string data_;
+ int data_offset_;
+ base::WeakPtrFactory<URLRequestSimpleJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
diff --git a/chromium/net/url_request/url_request_status.h b/chromium/net/url_request/url_request_status.h
new file mode 100644
index 00000000000..521a3d45f5f
--- /dev/null
+++ b/chromium/net/url_request/url_request_status.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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.
+//
+// This file's dependencies should be kept to a minimum so that it can be
+// included in WebKit code that doesn't rely on much of common.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_STATUS_H_
+#define NET_URL_REQUEST_URL_REQUEST_STATUS_H_
+
+namespace net {
+
+// Represents the result of a URL request. It encodes errors and various
+// types of success.
+class URLRequestStatus {
+ public:
+ enum Status {
+ // Request succeeded, |error_| will be 0.
+ SUCCESS = 0,
+
+ // An IO request is pending, and the caller will be informed when it is
+ // completed.
+ IO_PENDING,
+
+ // Request was cancelled programatically.
+ CANCELED,
+
+ // The request failed for some reason. |error_| may have more information.
+ FAILED,
+ };
+
+ URLRequestStatus() : status_(SUCCESS), error_(0) {}
+ URLRequestStatus(Status s, int e) : status_(s), error_(e) {}
+
+ Status status() const { return status_; }
+ void set_status(Status s) { status_ = s; }
+
+ int error() const { return error_; }
+ void set_error(int e) { error_ = e; }
+
+ // Returns true if the status is success, which makes some calling code more
+ // convenient because this is the most common test.
+ bool is_success() const {
+ return status_ == SUCCESS || status_ == IO_PENDING;
+ }
+
+ // Returns true if the request is waiting for IO.
+ bool is_io_pending() const {
+ return status_ == IO_PENDING;
+ }
+
+ private:
+ // Application level status.
+ Status status_;
+
+ // Error code from the network layer if an error was encountered.
+ int error_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_STATUS_H_
diff --git a/chromium/net/url_request/url_request_test_job.cc b/chromium/net/url_request/url_request_test_job.cc
new file mode 100644
index 00000000000..e0a32b38db6
--- /dev/null
+++ b/chromium/net/url_request/url_request_test_job.cc
@@ -0,0 +1,320 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_test_job.h"
+
+#include <algorithm>
+#include <list>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+namespace {
+
+typedef std::list<URLRequestTestJob*> URLRequestJobList;
+base::LazyInstance<URLRequestJobList>::Leaky
+ g_pending_jobs = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static getters for known URLs
+GURL URLRequestTestJob::test_url_1() {
+ return GURL("test:url1");
+}
+GURL URLRequestTestJob::test_url_2() {
+ return GURL("test:url2");
+}
+GURL URLRequestTestJob::test_url_3() {
+ return GURL("test:url3");
+}
+GURL URLRequestTestJob::test_url_error() {
+ return GURL("test:error");
+}
+
+// static getters for known URL responses
+std::string URLRequestTestJob::test_data_1() {
+ return std::string("<html><title>Test One</title></html>");
+}
+std::string URLRequestTestJob::test_data_2() {
+ return std::string("<html><title>Test Two Two</title></html>");
+}
+std::string URLRequestTestJob::test_data_3() {
+ return std::string("<html><title>Test Three Three Three</title></html>");
+}
+
+// static getter for simple response headers
+std::string URLRequestTestJob::test_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 200 OK\0"
+ "Content-type: text/html\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static getter for redirect response headers
+std::string URLRequestTestJob::test_redirect_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 302 MOVED\0"
+ "Location: somewhere\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static getter for error response headers
+std::string URLRequestTestJob::test_error_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 500 BOO HOO\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static
+URLRequestJob* URLRequestTestJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ return new URLRequestTestJob(request, network_delegate);
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(false),
+ stage_(WAITING),
+ priority_(DEFAULT_PRIORITY),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ weak_factory_(this) {
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ bool auto_advance)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ priority_(DEFAULT_PRIORITY),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ weak_factory_(this) {
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ priority_(DEFAULT_PRIORITY),
+ response_headers_(new HttpResponseHeaders(response_headers)),
+ response_data_(response_data),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ weak_factory_(this) {
+}
+
+URLRequestTestJob::~URLRequestTestJob() {
+ g_pending_jobs.Get().erase(
+ std::remove(
+ g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this),
+ g_pending_jobs.Get().end());
+}
+
+bool URLRequestTestJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(mime_type);
+ if (!response_headers_.get())
+ return false;
+ return response_headers_->GetMimeType(mime_type);
+}
+
+void URLRequestTestJob::SetPriority(RequestPriority priority) {
+ priority_ = priority;
+}
+
+void URLRequestTestJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&URLRequestTestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestTestJob::StartAsync() {
+ if (!response_headers_.get()) {
+ response_headers_ = new HttpResponseHeaders(test_headers());
+ if (request_->url().spec() == test_url_1().spec()) {
+ response_data_ = test_data_1();
+ stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
+ } else if (request_->url().spec() == test_url_2().spec()) {
+ response_data_ = test_data_2();
+ } else if (request_->url().spec() == test_url_3().spec()) {
+ response_data_ = test_data_3();
+ } else {
+ AdvanceJob();
+
+ // unexpected url, return error
+ // FIXME(brettw) we may want to use WININET errors or have some more types
+ // of errors
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_INVALID_URL));
+ // FIXME(brettw): this should emulate a network error, and not just fail
+ // initiating a connection
+ return;
+ }
+ }
+
+ AdvanceJob();
+
+ this->NotifyHeadersComplete();
+}
+
+bool URLRequestTestJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int *bytes_read) {
+ if (stage_ == WAITING) {
+ async_buf_ = buf;
+ async_buf_size_ = buf_size;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return false;
+ }
+
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ if (offset_ >= static_cast<int>(response_data_.length())) {
+ return true; // done reading
+ }
+
+ int to_read = buf_size;
+ if (to_read + offset_ > static_cast<int>(response_data_.length()))
+ to_read = static_cast<int>(response_data_.length()) - offset_;
+
+ memcpy(buf->data(), &response_data_.c_str()[offset_], to_read);
+ offset_ += to_read;
+
+ *bytes_read = to_read;
+ return true;
+}
+
+void URLRequestTestJob::GetResponseInfo(HttpResponseInfo* info) {
+ if (response_headers_.get())
+ info->headers = response_headers_;
+}
+
+void URLRequestTestJob::GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const {
+ // Preserve the times the URLRequest is responsible for, but overwrite all
+ // the others.
+ base::TimeTicks request_start = load_timing_info->request_start;
+ base::Time request_start_time = load_timing_info->request_start_time;
+ *load_timing_info = load_timing_info_;
+ load_timing_info->request_start = request_start;
+ load_timing_info->request_start_time = request_start_time;
+}
+
+int URLRequestTestJob::GetResponseCode() const {
+ if (response_headers_.get())
+ return response_headers_->response_code();
+ return -1;
+}
+
+bool URLRequestTestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (!response_headers_.get())
+ return false;
+
+ std::string value;
+ if (!response_headers_->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = response_headers_->response_code();
+ return true;
+}
+
+void URLRequestTestJob::Kill() {
+ stage_ = DONE;
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+ g_pending_jobs.Get().erase(
+ std::remove(
+ g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this),
+ g_pending_jobs.Get().end());
+}
+
+void URLRequestTestJob::ProcessNextOperation() {
+ switch (stage_) {
+ case WAITING:
+ // Must call AdvanceJob() prior to NotifyReadComplete() since that may
+ // delete |this|.
+ AdvanceJob();
+ stage_ = DATA_AVAILABLE;
+ // OK if ReadRawData wasn't called yet.
+ if (async_buf_) {
+ int bytes_read;
+ if (!ReadRawData(async_buf_, async_buf_size_, &bytes_read))
+ NOTREACHED() << "This should not return false in DATA_AVAILABLE.";
+ SetStatus(URLRequestStatus()); // clear the io pending flag
+ if (NextReadAsync()) {
+ // Make all future reads return io pending until the next
+ // ProcessNextOperation().
+ stage_ = WAITING;
+ }
+ NotifyReadComplete(bytes_read);
+ }
+ break;
+ case DATA_AVAILABLE:
+ AdvanceJob();
+ stage_ = ALL_DATA; // done sending data
+ break;
+ case ALL_DATA:
+ stage_ = DONE;
+ return;
+ case DONE:
+ return;
+ default:
+ NOTREACHED() << "Invalid stage";
+ return;
+ }
+}
+
+bool URLRequestTestJob::NextReadAsync() {
+ return false;
+}
+
+void URLRequestTestJob::AdvanceJob() {
+ if (auto_advance_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&URLRequestTestJob::ProcessNextOperation,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ g_pending_jobs.Get().push_back(this);
+}
+
+// static
+bool URLRequestTestJob::ProcessOnePendingMessage() {
+ if (g_pending_jobs.Get().empty())
+ return false;
+
+ URLRequestTestJob* next_job(g_pending_jobs.Get().front());
+ g_pending_jobs.Get().pop_front();
+
+ DCHECK(!next_job->auto_advance()); // auto_advance jobs should be in this q
+ next_job->ProcessNextOperation();
+ return true;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_test_job.h b/chromium/net/url_request/url_request_test_job.h
new file mode 100644
index 00000000000..d7f33d70400
--- /dev/null
+++ b/chromium/net/url_request/url_request_test_job.h
@@ -0,0 +1,169 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/load_timing_info.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+// This job type is designed to help with simple unit tests. To use, you
+// probably want to inherit from it to set up the state you want. Then install
+// it as the protocol handler for the "test" scheme.
+//
+// It will respond to three URLs, which you can retrieve using the test_url*
+// getters, which will in turn respond with the corresponding responses returned
+// by test_data*. Any other URLs that begin with "test:" will return an error,
+// which might also be useful, you can use test_url_error() to retreive a
+// standard one.
+//
+// You can override the known URLs or the response data by overriding Start().
+//
+// Optionally, you can also construct test jobs to return a headers and data
+// provided to the contstructor in response to any request url.
+//
+// When a job is created, it gets put on a queue of pending test jobs. To
+// process jobs on this queue, use ProcessOnePendingMessage, which will process
+// one step of the next job. If the job is incomplete, it will be added to the
+// end of the queue.
+//
+// Optionally, you can also construct test jobs that advance automatically
+// without having to call ProcessOnePendingMessage.
+class NET_EXPORT_PRIVATE URLRequestTestJob : public URLRequestJob {
+ public:
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, with auto advance disabled.
+ URLRequestTestJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, optionally with auto advance enabled.
+ URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ bool auto_advance);
+
+ // Constructs a job to return the given response regardless of the request
+ // url. The headers should include the HTTP status line and be formatted as
+ // expected by HttpResponseHeaders.
+ URLRequestTestJob(URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance);
+
+ // The three canned URLs this handler will respond to without having been
+ // explicitly initialized with response headers and data.
+ // FIXME(brettw): we should probably also have a redirect one
+ static GURL test_url_1();
+ static GURL test_url_2();
+ static GURL test_url_3();
+ static GURL test_url_error();
+
+ // The data that corresponds to each of the URLs above
+ static std::string test_data_1();
+ static std::string test_data_2();
+ static std::string test_data_3();
+
+ // The headers that correspond to each of the URLs above
+ static std::string test_headers();
+
+ // The headers for a redirect response
+ static std::string test_redirect_headers();
+
+ // The headers for a server error response
+ static std::string test_error_headers();
+
+ // Processes one pending message from the stack, returning true if any
+ // message was processed, or false if there are no more pending request
+ // notifications to send. This is not applicable when using auto_advance.
+ static bool ProcessOnePendingMessage();
+
+ // With auto advance enabled, the job will advance thru the stages without
+ // the caller having to call ProcessOnePendingMessage. Auto advance depends
+ // on having a message loop running. The default is to not auto advance.
+ // Should not be altered after the job has started.
+ bool auto_advance() { return auto_advance_; }
+ void set_auto_advance(bool auto_advance) { auto_advance_ = auto_advance; }
+
+ void set_load_timing_info(const LoadTimingInfo& load_timing_info) {
+ load_timing_info_ = load_timing_info;
+ }
+
+ RequestPriority priority() const { return priority_; }
+
+ // Factory method for protocol factory registration if callers don't subclass
+ static URLRequest::ProtocolFactory Factory;
+
+ // Job functions
+ virtual void SetPriority(RequestPriority priority) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(HttpResponseInfo* info) OVERRIDE;
+ virtual void GetLoadTimingInfo(
+ LoadTimingInfo* load_timing_info) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+
+ protected:
+ // Override to specify whether the next read done from this job will
+ // return IO pending. This controls whether or not the WAITING state will
+ // transition back to WAITING or to DATA_AVAILABLE after an asynchronous
+ // read is processed.
+ virtual bool NextReadAsync();
+
+ // This is what operation we are going to do next when this job is handled.
+ // When the stage is DONE, this job will not be put on the queue.
+ enum Stage { WAITING, DATA_AVAILABLE, ALL_DATA, DONE };
+
+ virtual ~URLRequestTestJob();
+
+ // Call to process the next opeation, usually sending a notification, and
+ // advancing the stage if necessary. THIS MAY DELETE THE OBJECT.
+ void ProcessNextOperation();
+
+ // Call to move the job along to the next operation.
+ void AdvanceJob();
+
+ // Called via InvokeLater to cause callbacks to occur after Start() returns.
+ virtual void StartAsync();
+
+ bool auto_advance_;
+
+ Stage stage_;
+
+ RequestPriority priority_;
+
+ // The headers the job should return, will be set in Start() if not provided
+ // in the explicit ctor.
+ scoped_refptr<HttpResponseHeaders> response_headers_;
+
+ // The data to send, will be set in Start() if not provided in the explicit
+ // ctor.
+ std::string response_data_;
+
+ // current offset within response_data_
+ int offset_;
+
+ // Holds the buffer for an asynchronous ReadRawData call
+ IOBuffer* async_buf_;
+ int async_buf_size_;
+
+ LoadTimingInfo load_timing_info_;
+
+ base::WeakPtrFactory<URLRequestTestJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
diff --git a/chromium/net/url_request/url_request_test_util.cc b/chromium/net/url_request/url_request_test_util.cc
new file mode 100644
index 00000000000..8b209f9c6dc
--- /dev/null
+++ b/chromium/net/url_request/url_request_test_util.cc
@@ -0,0 +1,607 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_test_util.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/host_port_pair.h"
+#include "net/cert/cert_verifier.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/transport_security_state.h"
+#include "net/ssl/default_server_bound_cert_store.h"
+#include "net/ssl/server_bound_cert_service.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// These constants put the NetworkDelegate events of TestNetworkDelegate
+// into an order. They are used in conjunction with
+// |TestNetworkDelegate::next_states_| to check that we do not send
+// events in the wrong order.
+const int kStageBeforeURLRequest = 1 << 0;
+const int kStageBeforeSendHeaders = 1 << 1;
+const int kStageSendHeaders = 1 << 2;
+const int kStageHeadersReceived = 1 << 3;
+const int kStageAuthRequired = 1 << 4;
+const int kStageBeforeRedirect = 1 << 5;
+const int kStageResponseStarted = 1 << 6;
+const int kStageCompletedSuccess = 1 << 7;
+const int kStageCompletedError = 1 << 8;
+const int kStageURLRequestDestroyed = 1 << 9;
+const int kStageDestruction = 1 << 10;
+
+} // namespace
+
+TestURLRequestContext::TestURLRequestContext()
+ : initialized_(false),
+ client_socket_factory_(NULL),
+ context_storage_(this) {
+ Init();
+}
+
+TestURLRequestContext::TestURLRequestContext(bool delay_initialization)
+ : initialized_(false),
+ client_socket_factory_(NULL),
+ context_storage_(this) {
+ if (!delay_initialization)
+ Init();
+}
+
+TestURLRequestContext::~TestURLRequestContext() {
+ DCHECK(initialized_);
+}
+
+void TestURLRequestContext::Init() {
+ DCHECK(!initialized_);
+ initialized_ = true;
+
+ if (!host_resolver())
+ context_storage_.set_host_resolver(
+ scoped_ptr<HostResolver>(new MockCachingHostResolver()));
+ if (!proxy_service())
+ context_storage_.set_proxy_service(ProxyService::CreateDirect());
+ if (!cert_verifier())
+ context_storage_.set_cert_verifier(CertVerifier::CreateDefault());
+ if (!transport_security_state())
+ context_storage_.set_transport_security_state(new TransportSecurityState);
+ if (!ssl_config_service())
+ context_storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+ if (!http_auth_handler_factory()) {
+ context_storage_.set_http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver()));
+ }
+ if (!http_server_properties()) {
+ context_storage_.set_http_server_properties(
+ scoped_ptr<HttpServerProperties>(new HttpServerPropertiesImpl()));
+ }
+ if (!transport_security_state()) {
+ context_storage_.set_transport_security_state(
+ new TransportSecurityState());
+ }
+ if (http_transaction_factory()) {
+ // Make sure we haven't been passed an object we're not going to use.
+ EXPECT_FALSE(client_socket_factory_);
+ } else {
+ HttpNetworkSession::Params params;
+ params.client_socket_factory = client_socket_factory();
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ params.transport_security_state = transport_security_state();
+ params.proxy_service = proxy_service();
+ params.ssl_config_service = ssl_config_service();
+ params.http_auth_handler_factory = http_auth_handler_factory();
+ params.network_delegate = network_delegate();
+ params.http_server_properties = http_server_properties();
+ params.net_log = net_log();
+ context_storage_.set_http_transaction_factory(new HttpCache(
+ new HttpNetworkSession(params),
+ HttpCache::DefaultBackend::InMemory(0)));
+ }
+ // In-memory cookie store.
+ if (!cookie_store())
+ context_storage_.set_cookie_store(new CookieMonster(NULL, NULL));
+ // In-memory origin bound cert service.
+ if (!server_bound_cert_service()) {
+ context_storage_.set_server_bound_cert_service(
+ new ServerBoundCertService(
+ new DefaultServerBoundCertStore(NULL),
+ base::WorkerPool::GetTaskRunner(true)));
+ }
+ if (!http_user_agent_settings()) {
+ context_storage_.set_http_user_agent_settings(
+ new StaticHttpUserAgentSettings("en-us,fr", EmptyString()));
+ }
+ if (!job_factory())
+ context_storage_.set_job_factory(new URLRequestJobFactoryImpl);
+}
+
+TestURLRequest::TestURLRequest(const GURL& url,
+ Delegate* delegate,
+ TestURLRequestContext* context,
+ NetworkDelegate* network_delegate)
+ : URLRequest(url, delegate, context, network_delegate) {
+}
+
+TestURLRequest::~TestURLRequest() {
+}
+
+TestURLRequestContextGetter::TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner)
+ : network_task_runner_(network_task_runner) {
+ DCHECK(network_task_runner_.get());
+}
+
+TestURLRequestContextGetter::TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner,
+ scoped_ptr<TestURLRequestContext> context)
+ : network_task_runner_(network_task_runner), context_(context.Pass()) {
+ DCHECK(network_task_runner_.get());
+}
+
+TestURLRequestContextGetter::~TestURLRequestContextGetter() {}
+
+TestURLRequestContext* TestURLRequestContextGetter::GetURLRequestContext() {
+ if (!context_.get())
+ context_.reset(new TestURLRequestContext);
+ return context_.get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+TestURLRequestContextGetter::GetNetworkTaskRunner() const {
+ return network_task_runner_;
+}
+
+TestDelegate::TestDelegate()
+ : cancel_in_rr_(false),
+ cancel_in_rs_(false),
+ cancel_in_rd_(false),
+ cancel_in_rd_pending_(false),
+ quit_on_complete_(true),
+ quit_on_redirect_(false),
+ allow_certificate_errors_(false),
+ response_started_count_(0),
+ received_bytes_count_(0),
+ received_redirect_count_(0),
+ received_data_before_response_(false),
+ request_failed_(false),
+ have_certificate_errors_(false),
+ certificate_errors_are_fatal_(false),
+ auth_required_(false),
+ have_full_request_headers_(false),
+ buf_(new IOBuffer(kBufferSize)) {
+}
+
+TestDelegate::~TestDelegate() {}
+
+void TestDelegate::ClearFullRequestHeaders() {
+ full_request_headers_.Clear();
+ have_full_request_headers_ = false;
+}
+
+void TestDelegate::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ EXPECT_TRUE(request->is_redirecting());
+
+ have_full_request_headers_ =
+ request->GetFullRequestHeaders(&full_request_headers_);
+
+ received_redirect_count_++;
+ if (quit_on_redirect_) {
+ *defer_redirect = true;
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ } else if (cancel_in_rr_) {
+ request->Cancel();
+ }
+}
+
+void TestDelegate::OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ auth_required_ = true;
+ if (!credentials_.Empty()) {
+ request->SetAuth(credentials_);
+ } else {
+ request->CancelAuth();
+ }
+}
+
+void TestDelegate::OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) {
+ // The caller can control whether it needs all SSL requests to go through,
+ // independent of any possible errors, or whether it wants SSL errors to
+ // cancel the request.
+ have_certificate_errors_ = true;
+ certificate_errors_are_fatal_ = fatal;
+ if (allow_certificate_errors_)
+ request->ContinueDespiteLastError();
+ else
+ request->Cancel();
+}
+
+void TestDelegate::OnResponseStarted(URLRequest* request) {
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK(!request->status().is_io_pending());
+ EXPECT_FALSE(request->is_redirecting());
+
+ have_full_request_headers_ =
+ request->GetFullRequestHeaders(&full_request_headers_);
+
+ response_started_count_++;
+ if (cancel_in_rs_) {
+ request->Cancel();
+ OnResponseCompleted(request);
+ } else if (!request->status().is_success()) {
+ DCHECK(request->status().status() == URLRequestStatus::FAILED ||
+ request->status().status() == URLRequestStatus::CANCELED);
+ request_failed_ = true;
+ OnResponseCompleted(request);
+ } else {
+ // Initiate the first read.
+ int bytes_read = 0;
+ if (request->Read(buf_.get(), kBufferSize, &bytes_read))
+ OnReadCompleted(request, bytes_read);
+ else if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ }
+}
+
+void TestDelegate::OnReadCompleted(URLRequest* request, int bytes_read) {
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK(!request->status().is_io_pending());
+
+ if (response_started_count_ == 0)
+ received_data_before_response_ = true;
+
+ if (cancel_in_rd_)
+ request->Cancel();
+
+ if (bytes_read >= 0) {
+ // There is data to read.
+ received_bytes_count_ += bytes_read;
+
+ // consume the data
+ data_received_.append(buf_->data(), bytes_read);
+ }
+
+ // If it was not end of stream, request to read more.
+ if (request->status().is_success() && bytes_read > 0) {
+ bytes_read = 0;
+ while (request->Read(buf_.get(), kBufferSize, &bytes_read)) {
+ if (bytes_read > 0) {
+ data_received_.append(buf_->data(), bytes_read);
+ received_bytes_count_ += bytes_read;
+ } else {
+ break;
+ }
+ }
+ }
+ if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ else if (cancel_in_rd_pending_)
+ request->Cancel();
+}
+
+void TestDelegate::OnResponseCompleted(URLRequest* request) {
+ if (quit_on_complete_)
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+}
+
+TestNetworkDelegate::TestNetworkDelegate()
+ : last_error_(0),
+ error_count_(0),
+ created_requests_(0),
+ destroyed_requests_(0),
+ completed_requests_(0),
+ cookie_options_bit_mask_(0),
+ blocked_get_cookies_count_(0),
+ blocked_set_cookie_count_(0),
+ set_cookie_count_(0),
+ has_load_timing_info_before_redirect_(false),
+ has_load_timing_info_before_auth_(false) {
+}
+
+TestNetworkDelegate::~TestNetworkDelegate() {
+ for (std::map<int, int>::iterator i = next_states_.begin();
+ i != next_states_.end(); ++i) {
+ event_order_[i->first] += "~TestNetworkDelegate\n";
+ EXPECT_TRUE(i->second & kStageDestruction) << event_order_[i->first];
+ }
+}
+
+bool TestNetworkDelegate::GetLoadTimingInfoBeforeRedirect(
+ LoadTimingInfo* load_timing_info_before_redirect) const {
+ *load_timing_info_before_redirect = load_timing_info_before_redirect_;
+ return has_load_timing_info_before_redirect_;
+}
+
+bool TestNetworkDelegate::GetLoadTimingInfoBeforeAuth(
+ LoadTimingInfo* load_timing_info_before_auth) const {
+ *load_timing_info_before_auth = load_timing_info_before_auth_;
+ return has_load_timing_info_before_auth_;
+}
+
+void TestNetworkDelegate::InitRequestStatesIfNew(int request_id) {
+ if (next_states_.find(request_id) == next_states_.end()) {
+ next_states_[request_id] = kStageBeforeURLRequest;
+ event_order_[request_id] = "";
+ }
+}
+
+int TestNetworkDelegate::OnBeforeURLRequest(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url ) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeURLRequest\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeURLRequest) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeSendHeaders |
+ kStageResponseStarted | // data: URLs do not trigger sending headers
+ kStageBeforeRedirect | // a delegate can trigger a redirection
+ kStageCompletedError | // request canceled by delegate
+ kStageAuthRequired; // Auth can come next for FTP requests
+ created_requests_++;
+ return OK;
+}
+
+int TestNetworkDelegate::OnBeforeSendHeaders(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeSendHeaders\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeSendHeaders) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageSendHeaders |
+ kStageCompletedError; // request canceled by delegate
+
+ return OK;
+}
+
+void TestNetworkDelegate::OnSendHeaders(
+ URLRequest* request,
+ const HttpRequestHeaders& headers) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnSendHeaders\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageSendHeaders) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageHeadersReceived |
+ kStageCompletedError;
+}
+
+int TestNetworkDelegate::OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) {
+ int req_id = request->identifier();
+ event_order_[req_id] += "OnHeadersReceived\n";
+ InitRequestStatesIfNew(req_id);
+ EXPECT_TRUE(next_states_[req_id] & kStageHeadersReceived) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeRedirect |
+ kStageResponseStarted |
+ kStageAuthRequired |
+ kStageCompletedError; // e.g. proxy resolution problem
+
+ // Basic authentication sends a second request from the URLRequestHttpJob
+ // layer before the URLRequest reports that a response has started.
+ next_states_[req_id] |= kStageBeforeSendHeaders;
+
+ return OK;
+}
+
+void TestNetworkDelegate::OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) {
+ load_timing_info_before_redirect_ = LoadTimingInfo();
+ request->GetLoadTimingInfo(&load_timing_info_before_redirect_);
+ has_load_timing_info_before_redirect_ = true;
+ EXPECT_FALSE(load_timing_info_before_redirect_.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info_before_redirect_.request_start.is_null());
+
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeRedirect\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeRedirect) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeURLRequest | // HTTP redirects trigger this.
+ kStageBeforeSendHeaders | // Redirects from the network delegate do not
+ // trigger onBeforeURLRequest.
+ kStageCompletedError;
+
+ // A redirect can lead to a file or a data URL. In this case, we do not send
+ // headers.
+ next_states_[req_id] |= kStageResponseStarted;
+}
+
+void TestNetworkDelegate::OnResponseStarted(URLRequest* request) {
+ LoadTimingInfo load_timing_info;
+ request->GetLoadTimingInfo(&load_timing_info);
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnResponseStarted\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageResponseStarted) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageCompletedSuccess | kStageCompletedError;
+ if (request->status().status() == URLRequestStatus::FAILED) {
+ error_count_++;
+ last_error_ = request->status().error();
+ }
+}
+
+void TestNetworkDelegate::OnRawBytesRead(const URLRequest& request,
+ int bytes_read) {
+}
+
+void TestNetworkDelegate::OnCompleted(URLRequest* request, bool started) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnCompleted\n";
+ // Expect "Success -> (next_states_ & kStageCompletedSuccess)"
+ // is logically identical to
+ // Expect "!(Success) || (next_states_ & kStageCompletedSuccess)"
+ EXPECT_TRUE(!request->status().is_success() ||
+ (next_states_[req_id] & kStageCompletedSuccess)) <<
+ event_order_[req_id];
+ EXPECT_TRUE(request->status().is_success() ||
+ (next_states_[req_id] & kStageCompletedError)) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageURLRequestDestroyed;
+ completed_requests_++;
+ if (request->status().status() == URLRequestStatus::FAILED) {
+ error_count_++;
+ last_error_ = request->status().error();
+ }
+}
+
+void TestNetworkDelegate::OnURLRequestDestroyed(URLRequest* request) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnURLRequestDestroyed\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageURLRequestDestroyed) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageDestruction;
+ destroyed_requests_++;
+}
+
+void TestNetworkDelegate::OnPACScriptError(int line_number,
+ const base::string16& error) {
+}
+
+NetworkDelegate::AuthRequiredResponse TestNetworkDelegate::OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) {
+ load_timing_info_before_auth_ = LoadTimingInfo();
+ request->GetLoadTimingInfo(&load_timing_info_before_auth_);
+ has_load_timing_info_before_auth_ = true;
+ EXPECT_FALSE(load_timing_info_before_auth_.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info_before_auth_.request_start.is_null());
+
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnAuthRequired\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageAuthRequired) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageBeforeSendHeaders |
+ kStageAuthRequired | // For example, proxy auth followed by server auth.
+ kStageHeadersReceived | // Request canceled by delegate simulates empty
+ // response.
+ kStageResponseStarted | // data: URLs do not trigger sending headers
+ kStageBeforeRedirect | // a delegate can trigger a redirection
+ kStageCompletedError; // request cancelled before callback
+ return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+}
+
+bool TestNetworkDelegate::OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) {
+ bool allow = true;
+ if (cookie_options_bit_mask_ & NO_GET_COOKIES)
+ allow = false;
+
+ if (!allow) {
+ blocked_get_cookies_count_++;
+ }
+
+ return allow;
+}
+
+bool TestNetworkDelegate::OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) {
+ bool allow = true;
+ if (cookie_options_bit_mask_ & NO_SET_COOKIE)
+ allow = false;
+
+ if (!allow) {
+ blocked_set_cookie_count_++;
+ } else {
+ set_cookie_count_++;
+ }
+
+ return allow;
+}
+
+bool TestNetworkDelegate::OnCanAccessFile(const URLRequest& request,
+ const base::FilePath& path) const {
+ return true;
+}
+
+bool TestNetworkDelegate::OnCanThrottleRequest(
+ const URLRequest& request) const {
+ return true;
+}
+
+int TestNetworkDelegate::OnBeforeSocketStreamConnect(
+ SocketStream* socket,
+ const CompletionCallback& callback) {
+ return OK;
+}
+
+void TestNetworkDelegate::OnRequestWaitStateChange(
+ const URLRequest& request,
+ RequestWaitState state) {
+}
+
+// static
+std::string ScopedCustomUrlRequestTestHttpHost::value_("127.0.0.1");
+
+ScopedCustomUrlRequestTestHttpHost::ScopedCustomUrlRequestTestHttpHost(
+ const std::string& new_value)
+ : old_value_(value_),
+ new_value_(new_value) {
+ value_ = new_value_;
+}
+
+ScopedCustomUrlRequestTestHttpHost::~ScopedCustomUrlRequestTestHttpHost() {
+ DCHECK_EQ(value_, new_value_);
+ value_ = old_value_;
+}
+
+// static
+const std::string& ScopedCustomUrlRequestTestHttpHost::value() {
+ return value_;
+}
+
+TestJobInterceptor::TestJobInterceptor() : main_intercept_job_(NULL) {
+}
+
+URLRequestJob* TestJobInterceptor::MaybeCreateJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ URLRequestJob* job = main_intercept_job_;
+ main_intercept_job_ = NULL;
+ return job;
+}
+
+void TestJobInterceptor::set_main_intercept_job(URLRequestJob* job) {
+ main_intercept_job_ = job;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_test_util.h b/chromium/net/url_request/url_request_test_util.h
new file mode 100644
index 00000000000..796b9131387
--- /dev/null
+++ b/chromium/net/url_request/url_request_test_util.h
@@ -0,0 +1,353 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
+#define NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
+
+#include <stdlib.h>
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_request_headers.h"
+#include "net/proxy/proxy_service.h"
+#include "net/ssl/ssl_config_service_defaults.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_context_storage.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "url/url_util.h"
+
+using base::TimeDelta;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext();
+ // Default constructor like TestURLRequestContext() but does not call
+ // Init() in case |delay_initialization| is true. This allows modifying the
+ // URLRequestContext before it is constructed completely. If
+ // |delay_initialization| is true, Init() needs be be called manually.
+ explicit TestURLRequestContext(bool delay_initialization);
+ virtual ~TestURLRequestContext();
+
+ void Init();
+
+ ClientSocketFactory* client_socket_factory() {
+ return client_socket_factory_;
+ }
+ void set_client_socket_factory(ClientSocketFactory* factory) {
+ client_socket_factory_ = factory;
+ }
+
+ private:
+ bool initialized_;
+
+ // Not owned:
+ ClientSocketFactory* client_socket_factory_;
+
+ protected:
+ URLRequestContextStorage context_storage_;
+};
+
+//-----------------------------------------------------------------------------
+
+// Used to return a dummy context, which lives on the message loop
+// given in the constructor.
+class TestURLRequestContextGetter : public URLRequestContextGetter {
+ public:
+ // |network_task_runner| must not be NULL.
+ explicit TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner);
+
+ // Use to pass a pre-initialized |context|.
+ TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner,
+ scoped_ptr<TestURLRequestContext> context);
+
+ // URLRequestContextGetter implementation.
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE;
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const OVERRIDE;
+
+ protected:
+ virtual ~TestURLRequestContextGetter();
+
+ private:
+ const scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+ scoped_ptr<TestURLRequestContext> context_;
+};
+
+//-----------------------------------------------------------------------------
+
+class TestURLRequest : public URLRequest {
+ public:
+ TestURLRequest(
+ const GURL& url, Delegate* delegate,
+ TestURLRequestContext* context, NetworkDelegate* network_delegate);
+ virtual ~TestURLRequest();
+};
+
+//-----------------------------------------------------------------------------
+
+class TestDelegate : public URLRequest::Delegate {
+ public:
+ TestDelegate();
+ virtual ~TestDelegate();
+
+ void set_cancel_in_received_redirect(bool val) { cancel_in_rr_ = val; }
+ void set_cancel_in_response_started(bool val) { cancel_in_rs_ = val; }
+ void set_cancel_in_received_data(bool val) { cancel_in_rd_ = val; }
+ void set_cancel_in_received_data_pending(bool val) {
+ cancel_in_rd_pending_ = val;
+ }
+ void set_quit_on_complete(bool val) { quit_on_complete_ = val; }
+ void set_quit_on_redirect(bool val) { quit_on_redirect_ = val; }
+ void set_allow_certificate_errors(bool val) {
+ allow_certificate_errors_ = val;
+ }
+ void set_credentials(const AuthCredentials& credentials) {
+ credentials_ = credentials;
+ }
+
+ // query state
+ const std::string& data_received() const { return data_received_; }
+ int bytes_received() const { return static_cast<int>(data_received_.size()); }
+ int response_started_count() const { return response_started_count_; }
+ int received_redirect_count() const { return received_redirect_count_; }
+ bool received_data_before_response() const {
+ return received_data_before_response_;
+ }
+ bool request_failed() const { return request_failed_; }
+ bool have_certificate_errors() const { return have_certificate_errors_; }
+ bool certificate_errors_are_fatal() const {
+ return certificate_errors_are_fatal_;
+ }
+ bool auth_required_called() const { return auth_required_; }
+ bool have_full_request_headers() const { return have_full_request_headers_; }
+ const HttpRequestHeaders& full_request_headers() const {
+ return full_request_headers_;
+ }
+ void ClearFullRequestHeaders();
+
+ // URLRequest::Delegate:
+ virtual void OnReceivedRedirect(URLRequest* request, const GURL& new_url,
+ bool* defer_redirect) OVERRIDE;
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) OVERRIDE;
+ // NOTE: |fatal| causes |certificate_errors_are_fatal_| to be set to true.
+ // (Unit tests use this as a post-condition.) But for policy, this method
+ // consults |allow_certificate_errors_|.
+ virtual void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request,
+ int bytes_read) OVERRIDE;
+
+ private:
+ static const int kBufferSize = 4096;
+
+ virtual void OnResponseCompleted(URLRequest* request);
+
+ // options for controlling behavior
+ bool cancel_in_rr_;
+ bool cancel_in_rs_;
+ bool cancel_in_rd_;
+ bool cancel_in_rd_pending_;
+ bool quit_on_complete_;
+ bool quit_on_redirect_;
+ bool allow_certificate_errors_;
+ AuthCredentials credentials_;
+
+ // tracks status of callbacks
+ int response_started_count_;
+ int received_bytes_count_;
+ int received_redirect_count_;
+ bool received_data_before_response_;
+ bool request_failed_;
+ bool have_certificate_errors_;
+ bool certificate_errors_are_fatal_;
+ bool auth_required_;
+ std::string data_received_;
+ bool have_full_request_headers_;
+ HttpRequestHeaders full_request_headers_;
+
+ // our read buffer
+ scoped_refptr<IOBuffer> buf_;
+};
+
+//-----------------------------------------------------------------------------
+
+class TestNetworkDelegate : public NetworkDelegate {
+ public:
+ enum Options {
+ NO_GET_COOKIES = 1 << 0,
+ NO_SET_COOKIE = 1 << 1,
+ };
+
+ TestNetworkDelegate();
+ virtual ~TestNetworkDelegate();
+
+ // Writes the LoadTimingInfo during the most recent call to OnBeforeRedirect.
+ bool GetLoadTimingInfoBeforeRedirect(
+ LoadTimingInfo* load_timing_info_before_redirect) const;
+
+ // Same as GetLoadTimingInfoBeforeRedirect, except for calls to
+ // AuthRequiredResponse.
+ bool GetLoadTimingInfoBeforeAuth(
+ LoadTimingInfo* load_timing_info_before_auth) const;
+
+ void set_cookie_options(int o) {cookie_options_bit_mask_ = o; }
+
+ int last_error() const { return last_error_; }
+ int error_count() const { return error_count_; }
+ int created_requests() const { return created_requests_; }
+ int destroyed_requests() const { return destroyed_requests_; }
+ int completed_requests() const { return completed_requests_; }
+ int blocked_get_cookies_count() const { return blocked_get_cookies_count_; }
+ int blocked_set_cookie_count() const { return blocked_set_cookie_count_; }
+ int set_cookie_count() const { return set_cookie_count_; }
+
+ protected:
+ // NetworkDelegate:
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE;
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE;
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE;
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE;
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE;
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE;
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE;
+ virtual void OnPACScriptError(int line_number,
+ const base::string16& error) OVERRIDE;
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE;
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE;
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE;
+ virtual bool OnCanAccessFile(const URLRequest& request,
+ const base::FilePath& path) const OVERRIDE;
+ virtual bool OnCanThrottleRequest(
+ const URLRequest& request) const OVERRIDE;
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void OnRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) OVERRIDE;
+
+ void InitRequestStatesIfNew(int request_id);
+
+ int last_error_;
+ int error_count_;
+ int created_requests_;
+ int destroyed_requests_;
+ int completed_requests_;
+ int cookie_options_bit_mask_;
+ int blocked_get_cookies_count_;
+ int blocked_set_cookie_count_;
+ int set_cookie_count_;
+
+ // NetworkDelegate callbacks happen in a particular order (e.g.
+ // OnBeforeURLRequest is always called before OnBeforeSendHeaders).
+ // This bit-set indicates for each request id (key) what events may be sent
+ // next.
+ std::map<int, int> next_states_;
+
+ // A log that records for each request id (key) the order in which On...
+ // functions were called.
+ std::map<int, std::string> event_order_;
+
+ LoadTimingInfo load_timing_info_before_redirect_;
+ bool has_load_timing_info_before_redirect_;
+
+ LoadTimingInfo load_timing_info_before_auth_;
+ bool has_load_timing_info_before_auth_;
+};
+
+// Overrides the host used by the LocalHttpTestServer in
+// url_request_unittest.cc . This is used by the chrome_frame_net_tests due to
+// a mysterious bug when tests execute over the loopback adapter. See
+// http://crbug.com/114369 .
+class ScopedCustomUrlRequestTestHttpHost {
+ public:
+ // Sets the host name to be used. The previous hostname will be stored and
+ // restored upon destruction. Note that if the lifetimes of two or more
+ // instances of this class overlap, they must be strictly nested.
+ explicit ScopedCustomUrlRequestTestHttpHost(const std::string& new_value);
+
+ ~ScopedCustomUrlRequestTestHttpHost();
+
+ // Returns the current value to be used by HTTP tests in
+ // url_request_unittest.cc .
+ static const std::string& value();
+
+ private:
+ static std::string value_;
+ const std::string old_value_;
+ const std::string new_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedCustomUrlRequestTestHttpHost);
+};
+
+//-----------------------------------------------------------------------------
+
+// A simple ProtocolHandler that returns a pre-built URLRequestJob only once.
+class TestJobInterceptor : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ TestJobInterceptor();
+
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ void set_main_intercept_job(URLRequestJob* job);
+
+ private:
+ mutable URLRequestJob* main_intercept_job_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
diff --git a/chromium/net/url_request/url_request_throttler_entry.cc b/chromium/net/url_request/url_request_throttler_entry.cc
new file mode 100644
index 00000000000..b7c06122df2
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_entry.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_throttler_entry.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_log.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace net {
+
+const int URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000;
+const int URLRequestThrottlerEntry::kDefaultMaxSendThreshold = 20;
+
+// This set of back-off parameters will (at maximum values, i.e. without
+// the reduction caused by jitter) add 0-41% (distributed uniformly
+// in that range) to the "perceived downtime" of the remote server, once
+// exponential back-off kicks in and is throttling requests for more than
+// about a second at a time. Once the maximum back-off is reached, the added
+// perceived downtime decreases rapidly, percentage-wise.
+//
+// Another way to put it is that the maximum additional perceived downtime
+// with these numbers is a couple of seconds shy of 15 minutes, and such
+// a delay would not occur until the remote server has been actually
+// unavailable at the end of each back-off period for a total of about
+// 48 minutes.
+//
+// Ignoring the first couple of errors is just a conservative measure to
+// avoid false positives. It should help avoid back-off from kicking in e.g.
+// on flaky connections.
+const int URLRequestThrottlerEntry::kDefaultNumErrorsToIgnore = 2;
+const int URLRequestThrottlerEntry::kDefaultInitialDelayMs = 700;
+const double URLRequestThrottlerEntry::kDefaultMultiplyFactor = 1.4;
+const double URLRequestThrottlerEntry::kDefaultJitterFactor = 0.4;
+const int URLRequestThrottlerEntry::kDefaultMaximumBackoffMs = 15 * 60 * 1000;
+const int URLRequestThrottlerEntry::kDefaultEntryLifetimeMs = 2 * 60 * 1000;
+const char URLRequestThrottlerEntry::kExponentialThrottlingHeader[] =
+ "X-Chrome-Exponential-Throttling";
+const char URLRequestThrottlerEntry::kExponentialThrottlingDisableValue[] =
+ "disable";
+
+// Returns NetLog parameters when a request is rejected by throttling.
+base::Value* NetLogRejectedRequestCallback(const std::string* url_id,
+ int num_failures,
+ int release_after_ms,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ dict->SetString("url", *url_id);
+ dict->SetInteger("num_failures", num_failures);
+ dict->SetInteger("release_after_ms", release_after_ms);
+ return dict;
+}
+
+URLRequestThrottlerEntry::URLRequestThrottlerEntry(
+ URLRequestThrottlerManager* manager,
+ const std::string& url_id)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
+ max_send_threshold_(kDefaultMaxSendThreshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id),
+ net_log_(BoundNetLog::Make(
+ manager->net_log(), NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING)) {
+ DCHECK(manager_);
+ Initialize();
+}
+
+URLRequestThrottlerEntry::URLRequestThrottlerEntry(
+ URLRequestThrottlerManager* manager,
+ const std::string& url_id,
+ int sliding_window_period_ms,
+ int max_send_threshold,
+ int initial_backoff_ms,
+ double multiply_factor,
+ double jitter_factor,
+ int maximum_backoff_ms)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(sliding_window_period_ms)),
+ max_send_threshold_(max_send_threshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id) {
+ DCHECK_GT(sliding_window_period_ms, 0);
+ DCHECK_GT(max_send_threshold_, 0);
+ DCHECK_GE(initial_backoff_ms, 0);
+ DCHECK_GT(multiply_factor, 0);
+ DCHECK_GE(jitter_factor, 0.0);
+ DCHECK_LT(jitter_factor, 1.0);
+ DCHECK_GE(maximum_backoff_ms, 0);
+ DCHECK(manager_);
+
+ Initialize();
+ backoff_policy_.initial_delay_ms = initial_backoff_ms;
+ backoff_policy_.multiply_factor = multiply_factor;
+ backoff_policy_.jitter_factor = jitter_factor;
+ backoff_policy_.maximum_backoff_ms = maximum_backoff_ms;
+ backoff_policy_.entry_lifetime_ms = -1;
+ backoff_policy_.num_errors_to_ignore = 0;
+ backoff_policy_.always_use_initial_delay = false;
+}
+
+bool URLRequestThrottlerEntry::IsEntryOutdated() const {
+ // This function is called by the URLRequestThrottlerManager to determine
+ // whether entries should be discarded from its url_entries_ map. We
+ // want to ensure that it does not remove entries from the map while there
+ // are clients (objects other than the manager) holding references to
+ // the entry, otherwise separate clients could end up holding separate
+ // entries for a request to the same URL, which is undesirable. Therefore,
+ // if an entry has more than one reference (the map will always hold one),
+ // it should not be considered outdated.
+ //
+ // We considered whether to make URLRequestThrottlerEntry objects
+ // non-refcounted, but since any means of knowing whether they are
+ // currently in use by others than the manager would be more or less
+ // equivalent to a refcount, we kept them refcounted.
+ if (!HasOneRef())
+ return false;
+
+ // If there are send events in the sliding window period, we still need this
+ // entry.
+ if (!send_log_.empty() &&
+ send_log_.back() + sliding_window_period_ > ImplGetTimeNow()) {
+ return false;
+ }
+
+ return GetBackoffEntry()->CanDiscard();
+}
+
+void URLRequestThrottlerEntry::DisableBackoffThrottling() {
+ is_backoff_disabled_ = true;
+}
+
+void URLRequestThrottlerEntry::DetachManager() {
+ manager_ = NULL;
+}
+
+bool URLRequestThrottlerEntry::ShouldRejectRequest(
+ const URLRequest& request) const {
+ bool reject_request = false;
+ if (!is_backoff_disabled_ && !ExplicitUserRequest(request.load_flags()) &&
+ (!request.context()->network_delegate() ||
+ request.context()->network_delegate()->CanThrottleRequest(request)) &&
+ GetBackoffEntry()->ShouldRejectRequest()) {
+ int num_failures = GetBackoffEntry()->failure_count();
+ int release_after_ms =
+ GetBackoffEntry()->GetTimeUntilRelease().InMilliseconds();
+
+ net_log_.AddEvent(
+ NetLog::TYPE_THROTTLING_REJECTED_REQUEST,
+ base::Bind(&NetLogRejectedRequestCallback,
+ &url_id_, num_failures, release_after_ms));
+
+ reject_request = true;
+ }
+
+ int reject_count = reject_request ? 1 : 0;
+ UMA_HISTOGRAM_ENUMERATION(
+ "Throttling.RequestThrottled", reject_count, 2);
+
+ return reject_request;
+}
+
+int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) {
+ base::TimeTicks now = ImplGetTimeNow();
+
+ // If a lot of requests were successfully made recently,
+ // sliding_window_release_time_ may be greater than
+ // exponential_backoff_release_time_.
+ base::TimeTicks recommended_sending_time =
+ std::max(std::max(now, earliest_time),
+ std::max(GetBackoffEntry()->GetReleaseTime(),
+ sliding_window_release_time_));
+
+ DCHECK(send_log_.empty() ||
+ recommended_sending_time >= send_log_.back());
+ // Log the new send event.
+ send_log_.push(recommended_sending_time);
+
+ sliding_window_release_time_ = recommended_sending_time;
+
+ // Drop the out-of-date events in the event list.
+ // We don't need to worry that the queue may become empty during this
+ // operation, since the last element is sliding_window_release_time_.
+ while ((send_log_.front() + sliding_window_period_ <=
+ sliding_window_release_time_) ||
+ send_log_.size() > static_cast<unsigned>(max_send_threshold_)) {
+ send_log_.pop();
+ }
+
+ // Check if there are too many send events in recent time.
+ if (send_log_.size() == static_cast<unsigned>(max_send_threshold_))
+ sliding_window_release_time_ = send_log_.front() + sliding_window_period_;
+
+ return (recommended_sending_time - now).InMillisecondsRoundedUp();
+}
+
+base::TimeTicks
+ URLRequestThrottlerEntry::GetExponentialBackoffReleaseTime() const {
+ // If a site opts out, it's likely because they have problems that trigger
+ // the back-off mechanism when it shouldn't be triggered, in which case
+ // returning the calculated back-off release time would probably be the
+ // wrong thing to do (i.e. it would likely be too long). Therefore, we
+ // return "now" so that retries are not delayed.
+ if (is_backoff_disabled_)
+ return ImplGetTimeNow();
+
+ return GetBackoffEntry()->GetReleaseTime();
+}
+
+void URLRequestThrottlerEntry::UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) {
+ if (IsConsideredError(response->GetResponseCode())) {
+ GetBackoffEntry()->InformOfRequest(false);
+ } else {
+ GetBackoffEntry()->InformOfRequest(true);
+
+ std::string throttling_header = response->GetNormalizedValue(
+ kExponentialThrottlingHeader);
+ if (!throttling_header.empty())
+ HandleThrottlingHeader(throttling_header, host);
+ }
+}
+
+void URLRequestThrottlerEntry::ReceivedContentWasMalformed(int response_code) {
+ // A malformed body can only occur when the request to fetch a resource
+ // was successful. Therefore, in such a situation, we will receive one
+ // call to ReceivedContentWasMalformed() and one call to
+ // UpdateWithResponse() with a response categorized as "good". To end
+ // up counting one failure, we need to count two failures here against
+ // the one success in UpdateWithResponse().
+ //
+ // We do nothing for a response that is already being considered an error
+ // based on its status code (otherwise we would count 3 errors instead of 1).
+ if (!IsConsideredError(response_code)) {
+ GetBackoffEntry()->InformOfRequest(false);
+ GetBackoffEntry()->InformOfRequest(false);
+ }
+}
+
+URLRequestThrottlerEntry::~URLRequestThrottlerEntry() {
+}
+
+void URLRequestThrottlerEntry::Initialize() {
+ sliding_window_release_time_ = base::TimeTicks::Now();
+ backoff_policy_.num_errors_to_ignore = kDefaultNumErrorsToIgnore;
+ backoff_policy_.initial_delay_ms = kDefaultInitialDelayMs;
+ backoff_policy_.multiply_factor = kDefaultMultiplyFactor;
+ backoff_policy_.jitter_factor = kDefaultJitterFactor;
+ backoff_policy_.maximum_backoff_ms = kDefaultMaximumBackoffMs;
+ backoff_policy_.entry_lifetime_ms = kDefaultEntryLifetimeMs;
+ backoff_policy_.always_use_initial_delay = false;
+}
+
+bool URLRequestThrottlerEntry::IsConsideredError(int response_code) {
+ // We throttle only for the status codes most likely to indicate the server
+ // is failing because it is too busy or otherwise are likely to be
+ // because of DDoS.
+ //
+ // 500 is the generic error when no better message is suitable, and
+ // as such does not necessarily indicate a temporary state, but
+ // other status codes cover most of the permanent error states.
+ // 503 is explicitly documented as a temporary state where the server
+ // is either overloaded or down for maintenance.
+ // 509 is the (non-standard but widely implemented) Bandwidth Limit Exceeded
+ // status code, which might indicate DDoS.
+ //
+ // We do not back off on 502 or 504, which are reported by gateways
+ // (proxies) on timeouts or failures, because in many cases these requests
+ // have not made it to the destination server and so we do not actually
+ // know that it is down or busy. One degenerate case could be a proxy on
+ // localhost, where you are not actually connected to the network.
+ return (response_code == 500 ||
+ response_code == 503 ||
+ response_code == 509);
+}
+
+base::TimeTicks URLRequestThrottlerEntry::ImplGetTimeNow() const {
+ return base::TimeTicks::Now();
+}
+
+void URLRequestThrottlerEntry::HandleThrottlingHeader(
+ const std::string& header_value,
+ const std::string& host) {
+ if (header_value == kExponentialThrottlingDisableValue) {
+ DisableBackoffThrottling();
+ if (manager_)
+ manager_->AddToOptOutList(host);
+ }
+}
+
+const BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() const {
+ return &backoff_entry_;
+}
+
+BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() {
+ return &backoff_entry_;
+}
+
+// static
+bool URLRequestThrottlerEntry::ExplicitUserRequest(const int load_flags) {
+ return (load_flags & LOAD_MAYBE_USER_GESTURE) != 0;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_throttler_entry.h b/chromium/net/url_request/url_request_throttler_entry.h
new file mode 100644
index 00000000000..88006e788b4
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_entry.h
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
+
+#include <queue>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_log.h"
+#include "net/url_request/url_request_throttler_entry_interface.h"
+
+namespace net {
+
+class URLRequestThrottlerManager;
+
+// URLRequestThrottlerEntry represents an entry of URLRequestThrottlerManager.
+// It analyzes requests of a specific URL over some period of time, in order to
+// deduce the back-off time for every request.
+// The back-off algorithm consists of two parts. Firstly, exponential back-off
+// is used when receiving 5XX server errors or malformed response bodies.
+// The exponential back-off rule is enforced by URLRequestHttpJob. Any
+// request sent during the back-off period will be cancelled.
+// Secondly, a sliding window is used to count recent requests to a given
+// destination and provide guidance (to the application level only) on whether
+// too many requests have been sent and when a good time to send the next one
+// would be. This is never used to deny requests at the network level.
+class NET_EXPORT URLRequestThrottlerEntry
+ : public URLRequestThrottlerEntryInterface {
+ public:
+ // Sliding window period.
+ static const int kDefaultSlidingWindowPeriodMs;
+
+ // Maximum number of requests allowed in sliding window period.
+ static const int kDefaultMaxSendThreshold;
+
+ // Number of initial errors to ignore before starting exponential back-off.
+ static const int kDefaultNumErrorsToIgnore;
+
+ // Initial delay for exponential back-off.
+ static const int kDefaultInitialDelayMs;
+
+ // Factor by which the waiting time will be multiplied.
+ static const double kDefaultMultiplyFactor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ static const double kDefaultJitterFactor;
+
+ // Maximum amount of time we are willing to delay our request.
+ static const int kDefaultMaximumBackoffMs;
+
+ // Time after which the entry is considered outdated.
+ static const int kDefaultEntryLifetimeMs;
+
+ // Name of the header that sites can use to opt out of exponential back-off
+ // throttling.
+ static const char kExponentialThrottlingHeader[];
+
+ // Value for exponential throttling header that can be used to opt out of
+ // exponential back-off throttling.
+ static const char kExponentialThrottlingDisableValue[];
+
+ // The manager object's lifetime must enclose the lifetime of this object.
+ URLRequestThrottlerEntry(URLRequestThrottlerManager* manager,
+ const std::string& url_id);
+
+ // The life span of instances created with this constructor is set to
+ // infinite, and the number of initial errors to ignore is set to 0.
+ // It is only used by unit tests.
+ URLRequestThrottlerEntry(URLRequestThrottlerManager* manager,
+ const std::string& url_id,
+ int sliding_window_period_ms,
+ int max_send_threshold,
+ int initial_backoff_ms,
+ double multiply_factor,
+ double jitter_factor,
+ int maximum_backoff_ms);
+
+ // Used by the manager, returns true if the entry needs to be garbage
+ // collected.
+ bool IsEntryOutdated() const;
+
+ // Causes this entry to never reject requests due to back-off.
+ void DisableBackoffThrottling();
+
+ // Causes this entry to NULL its manager pointer.
+ void DetachManager();
+
+ // Implementation of URLRequestThrottlerEntryInterface.
+ virtual bool ShouldRejectRequest(const URLRequest& request) const OVERRIDE;
+ virtual int64 ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) OVERRIDE;
+ virtual base::TimeTicks GetExponentialBackoffReleaseTime() const OVERRIDE;
+ virtual void UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) OVERRIDE;
+ virtual void ReceivedContentWasMalformed(int response_code) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestThrottlerEntry();
+
+ void Initialize();
+
+ // Returns true if the given response code is considered an error for
+ // throttling purposes.
+ bool IsConsideredError(int response_code);
+
+ // Equivalent to TimeTicks::Now(), virtual to be mockable for testing purpose.
+ virtual base::TimeTicks ImplGetTimeNow() const;
+
+ // Used internally to handle the opt-out header.
+ void HandleThrottlingHeader(const std::string& header_value,
+ const std::string& host);
+
+ // Retrieves the back-off entry object we're using. Used to enable a
+ // unit testing seam for dependency injection in tests.
+ virtual const BackoffEntry* GetBackoffEntry() const;
+ virtual BackoffEntry* GetBackoffEntry();
+
+ // Returns true if |load_flags| contains a flag that indicates an
+ // explicit request by the user to load the resource. We never
+ // throttle requests with such load flags.
+ static bool ExplicitUserRequest(const int load_flags);
+
+ // Used by tests.
+ base::TimeTicks sliding_window_release_time() const {
+ return sliding_window_release_time_;
+ }
+
+ // Used by tests.
+ void set_sliding_window_release_time(const base::TimeTicks& release_time) {
+ sliding_window_release_time_ = release_time;
+ }
+
+ // Valid and immutable after construction time.
+ BackoffEntry::Policy backoff_policy_;
+
+ private:
+ // Timestamp calculated by the sliding window algorithm for when we advise
+ // clients the next request should be made, at the earliest. Advisory only,
+ // not used to deny requests.
+ base::TimeTicks sliding_window_release_time_;
+
+ // A list of the recent send events. We use them to decide whether there are
+ // too many requests sent in sliding window.
+ std::queue<base::TimeTicks> send_log_;
+
+ const base::TimeDelta sliding_window_period_;
+ const int max_send_threshold_;
+
+ // True if DisableBackoffThrottling() has been called on this object.
+ bool is_backoff_disabled_;
+
+ // Access it through GetBackoffEntry() to allow a unit test seam.
+ BackoffEntry backoff_entry_;
+
+ // Weak back-reference to the manager object managing us.
+ URLRequestThrottlerManager* manager_;
+
+ // Canonicalized URL string that this entry is for; used for logging only.
+ std::string url_id_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerEntry);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
diff --git a/chromium/net/url_request/url_request_throttler_entry_interface.h b/chromium/net/url_request/url_request_throttler_entry_interface.h
new file mode 100644
index 00000000000..48152e1d44b
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_entry_interface.h
@@ -0,0 +1,73 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class URLRequest;
+class URLRequestThrottlerHeaderInterface;
+
+// Interface provided on entries of the URL request throttler manager.
+class NET_EXPORT URLRequestThrottlerEntryInterface
+ : public base::RefCountedThreadSafe<URLRequestThrottlerEntryInterface> {
+ public:
+ URLRequestThrottlerEntryInterface() {}
+
+ // Returns true when we have encountered server errors and are doing
+ // exponential back-off, unless the request has load flags that mean
+ // it is likely to be user-initiated, or the NetworkDelegate returns
+ // false for |CanThrottleRequest(request)|.
+ //
+ // URLRequestHttpJob checks this method prior to every request; it
+ // cancels requests if this method returns true.
+ virtual bool ShouldRejectRequest(const URLRequest& request) const = 0;
+
+ // Calculates a recommended sending time for the next request and reserves it.
+ // The sending time is not earlier than the current exponential back-off
+ // release time or |earliest_time|. Moreover, the previous results of
+ // the method are taken into account, in order to make sure they are spread
+ // properly over time.
+ // Returns the recommended delay before sending the next request, in
+ // milliseconds. The return value is always positive or 0.
+ // Although it is not mandatory, respecting the value returned by this method
+ // is helpful to avoid traffic overload.
+ virtual int64 ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) = 0;
+
+ // Returns the time after which requests are allowed.
+ virtual base::TimeTicks GetExponentialBackoffReleaseTime() const = 0;
+
+ // This method needs to be called each time a response is received.
+ virtual void UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) = 0;
+
+ // Lets higher-level modules, that know how to parse particular response
+ // bodies, notify of receiving malformed content for the given URL. This will
+ // be handled by the throttler as if an HTTP 503 response had been received to
+ // the request, i.e. it will count as a failure, unless the HTTP response code
+ // indicated is already one of those that will be counted as an error.
+ virtual void ReceivedContentWasMalformed(int response_code) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<URLRequestThrottlerEntryInterface>;
+ virtual ~URLRequestThrottlerEntryInterface() {}
+
+ private:
+ friend class base::RefCounted<URLRequestThrottlerEntryInterface>;
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerEntryInterface);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
diff --git a/chromium/net/url_request/url_request_throttler_header_adapter.cc b/chromium/net/url_request/url_request_throttler_header_adapter.cc
new file mode 100644
index 00000000000..51bbc746fac
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_header_adapter.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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/url_request/url_request_throttler_header_adapter.h"
+
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+URLRequestThrottlerHeaderAdapter::URLRequestThrottlerHeaderAdapter(
+ HttpResponseHeaders* headers)
+ : response_header_(headers) {
+}
+
+URLRequestThrottlerHeaderAdapter::~URLRequestThrottlerHeaderAdapter() {}
+
+std::string URLRequestThrottlerHeaderAdapter::GetNormalizedValue(
+ const std::string& key) const {
+ std::string return_value;
+ response_header_->GetNormalizedHeader(key, &return_value);
+ return return_value;
+}
+
+int URLRequestThrottlerHeaderAdapter::GetResponseCode() const {
+ return response_header_->response_code();
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_throttler_header_adapter.h b/chromium/net/url_request/url_request_throttler_header_adapter.h
new file mode 100644
index 00000000000..17a13a1fb26
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_header_adapter.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+
+// Adapter for the HTTP header interface of the URL request throttler component.
+class URLRequestThrottlerHeaderAdapter
+ : public URLRequestThrottlerHeaderInterface {
+ public:
+ explicit URLRequestThrottlerHeaderAdapter(HttpResponseHeaders* headers);
+ virtual ~URLRequestThrottlerHeaderAdapter();
+
+ // Implementation of URLRequestThrottlerHeaderInterface
+ virtual std::string GetNormalizedValue(const std::string& key) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+
+ private:
+ const scoped_refptr<HttpResponseHeaders> response_header_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
diff --git a/chromium/net/url_request/url_request_throttler_header_interface.h b/chromium/net/url_request/url_request_throttler_header_interface.h
new file mode 100644
index 00000000000..c69d1855f25
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_header_interface.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
+
+#include <string>
+
+namespace net {
+
+// Interface to an HTTP header to enforce we have the methods we need.
+class URLRequestThrottlerHeaderInterface {
+ public:
+ virtual ~URLRequestThrottlerHeaderInterface() {}
+
+ // Method that enables us to fetch the header value by its key.
+ // ex: location: www.example.com -> key = "location" value = "www.example.com"
+ // If the key does not exist, it returns an empty string.
+ virtual std::string GetNormalizedValue(const std::string& key) const = 0;
+
+ // Returns the HTTP response code associated with the request.
+ virtual int GetResponseCode() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
diff --git a/chromium/net/url_request/url_request_throttler_manager.cc b/chromium/net/url_request/url_request_throttler_manager.cc
new file mode 100644
index 00000000000..e6dd6580c36
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_manager.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_throttler_manager.h"
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+const unsigned int URLRequestThrottlerManager::kMaximumNumberOfEntries = 1500;
+const unsigned int URLRequestThrottlerManager::kRequestsBetweenCollecting = 200;
+
+URLRequestThrottlerManager::URLRequestThrottlerManager()
+ : requests_since_last_gc_(0),
+ enable_thread_checks_(false),
+ logged_for_localhost_disabled_(false),
+ registered_from_thread_(base::kInvalidThreadId) {
+ url_id_replacements_.ClearPassword();
+ url_id_replacements_.ClearUsername();
+ url_id_replacements_.ClearQuery();
+ url_id_replacements_.ClearRef();
+
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ NetworkChangeNotifier::AddConnectionTypeObserver(this);
+}
+
+URLRequestThrottlerManager::~URLRequestThrottlerManager() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+
+ // Since the manager object might conceivably go away before the
+ // entries, detach the entries' back-pointer to the manager.
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if (i->second.get() != NULL) {
+ i->second->DetachManager();
+ }
+ ++i;
+ }
+
+ // Delete all entries.
+ url_entries_.clear();
+}
+
+scoped_refptr<URLRequestThrottlerEntryInterface>
+ URLRequestThrottlerManager::RegisterRequestUrl(const GURL &url) {
+ DCHECK(!enable_thread_checks_ || CalledOnValidThread());
+
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ // Find the entry in the map or create a new NULL entry.
+ scoped_refptr<URLRequestThrottlerEntry>& entry = url_entries_[url_id];
+
+ // If the entry exists but could be garbage collected at this point, we
+ // start with a fresh entry so that we possibly back off a bit less
+ // aggressively (i.e. this resets the error count when the entry's URL
+ // hasn't been requested in long enough).
+ if (entry.get() && entry->IsEntryOutdated()) {
+ entry = NULL;
+ }
+
+ // Create the entry if needed.
+ if (entry.get() == NULL) {
+ entry = new URLRequestThrottlerEntry(this, url_id);
+
+ // We only disable back-off throttling on an entry that we have
+ // just constructed. This is to allow unit tests to explicitly override
+ // the entry for localhost URLs. Given that we do not attempt to
+ // disable throttling for entries already handed out (see comment
+ // in AddToOptOutList), this is not a problem.
+ std::string host = url.host();
+ if (opt_out_hosts_.find(host) != opt_out_hosts_.end() ||
+ IsLocalhost(host)) {
+ if (!logged_for_localhost_disabled_ && IsLocalhost(host)) {
+ logged_for_localhost_disabled_ = true;
+ net_log_.AddEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST,
+ NetLog::StringCallback("host", &host));
+ }
+
+ // TODO(joi): Once sliding window is separate from back-off throttling,
+ // we can simply return a dummy implementation of
+ // URLRequestThrottlerEntryInterface here that never blocks anything (and
+ // not keep entries in url_entries_ for opted-out sites).
+ entry->DisableBackoffThrottling();
+ }
+ }
+
+ return entry;
+}
+
+void URLRequestThrottlerManager::AddToOptOutList(const std::string& host) {
+ // There is an edge case here that we are not handling, to keep things
+ // simple. If a host starts adding the opt-out header to its responses
+ // after there are already one or more entries in url_entries_ for that
+ // host, the pre-existing entries may still perform back-off throttling.
+ // In practice, this would almost never occur.
+ if (opt_out_hosts_.find(host) == opt_out_hosts_.end()) {
+ UMA_HISTOGRAM_COUNTS("Throttling.SiteOptedOut", 1);
+
+ net_log_.EndEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST,
+ NetLog::StringCallback("host", &host));
+ opt_out_hosts_.insert(host);
+ }
+}
+
+void URLRequestThrottlerManager::OverrideEntryForTests(
+ const GURL& url,
+ URLRequestThrottlerEntry* entry) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ url_entries_[url_id] = entry;
+}
+
+void URLRequestThrottlerManager::EraseEntryForTests(const GURL& url) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+ url_entries_.erase(url_id);
+}
+
+void URLRequestThrottlerManager::set_enable_thread_checks(bool enable) {
+ enable_thread_checks_ = enable;
+}
+
+bool URLRequestThrottlerManager::enable_thread_checks() const {
+ return enable_thread_checks_;
+}
+
+void URLRequestThrottlerManager::set_net_log(NetLog* net_log) {
+ DCHECK(net_log);
+ net_log_ = BoundNetLog::Make(net_log,
+ NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING);
+}
+
+NetLog* URLRequestThrottlerManager::net_log() const {
+ return net_log_.net_log();
+}
+
+void URLRequestThrottlerManager::OnIPAddressChanged() {
+ OnNetworkChange();
+}
+
+void URLRequestThrottlerManager::OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType type) {
+ OnNetworkChange();
+}
+
+std::string URLRequestThrottlerManager::GetIdFromUrl(const GURL& url) const {
+ if (!url.is_valid())
+ return url.possibly_invalid_spec();
+
+ GURL id = url.ReplaceComponents(url_id_replacements_);
+ return StringToLowerASCII(id.spec()).c_str();
+}
+
+void URLRequestThrottlerManager::GarbageCollectEntriesIfNecessary() {
+ requests_since_last_gc_++;
+ if (requests_since_last_gc_ < kRequestsBetweenCollecting)
+ return;
+ requests_since_last_gc_ = 0;
+
+ GarbageCollectEntries();
+}
+
+void URLRequestThrottlerManager::GarbageCollectEntries() {
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if ((i->second)->IsEntryOutdated()) {
+ url_entries_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ // In case something broke we want to make sure not to grow indefinitely.
+ while (url_entries_.size() > kMaximumNumberOfEntries) {
+ url_entries_.erase(url_entries_.begin());
+ }
+}
+
+void URLRequestThrottlerManager::OnNetworkChange() {
+ // Remove all entries. Any entries that in-flight requests have a reference
+ // to will live until those requests end, and these entries may be
+ // inconsistent with new entries for the same URLs, but since what we
+ // want is a clean slate for the new connection type, this is OK.
+ url_entries_.clear();
+ requests_since_last_gc_ = 0;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_throttler_manager.h b/chromium/net/url_request/url_request_throttler_manager.h
new file mode 100644
index 00000000000..31d28ef3e77
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_manager.h
@@ -0,0 +1,166 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/platform_thread.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_request_throttler_entry.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class BoundNetLog;
+class NetLog;
+
+// Class that registers URL request throttler entries for URLs being accessed
+// in order to supervise traffic. URL requests for HTTP contents should
+// register their URLs in this manager on each request.
+//
+// URLRequestThrottlerManager maintains a map of URL IDs to URL request
+// throttler entries. It creates URL request throttler entries when new URLs
+// are registered, and does garbage collection from time to time in order to
+// clean out outdated entries. URL ID consists of lowercased scheme, host, port
+// and path. All URLs converted to the same ID will share the same entry.
+class NET_EXPORT URLRequestThrottlerManager
+ : NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ URLRequestThrottlerManager();
+ virtual ~URLRequestThrottlerManager();
+
+ // Must be called for every request, returns the URL request throttler entry
+ // associated with the URL. The caller must inform this entry of some events.
+ // Please refer to url_request_throttler_entry_interface.h for further
+ // informations.
+ scoped_refptr<URLRequestThrottlerEntryInterface> RegisterRequestUrl(
+ const GURL& url);
+
+ // Adds the given host to a list of sites for which exponential back-off
+ // throttling will be disabled. Subdomains are not included, so they
+ // must be added separately.
+ void AddToOptOutList(const std::string& host);
+
+ // Registers a new entry in this service and overrides the existing entry (if
+ // any) for the URL. The service will hold a reference to the entry.
+ // It is only used by unit tests.
+ void OverrideEntryForTests(const GURL& url, URLRequestThrottlerEntry* entry);
+
+ // Explicitly erases an entry.
+ // This is useful to remove those entries which have got infinite lifetime and
+ // thus won't be garbage collected.
+ // It is only used by unit tests.
+ void EraseEntryForTests(const GURL& url);
+
+ // Turns threading model verification on or off. Any code that correctly
+ // uses the network stack should preferably call this function to enable
+ // verification of correct adherence to the network stack threading model.
+ void set_enable_thread_checks(bool enable);
+ bool enable_thread_checks() const;
+
+ // Whether throttling is enabled or not.
+ void set_enforce_throttling(bool enforce);
+ bool enforce_throttling();
+
+ // Sets the NetLog instance to use.
+ void set_net_log(NetLog* net_log);
+ NetLog* net_log() const;
+
+ // IPAddressObserver interface.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // ConnectionTypeObserver interface.
+ virtual void OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType type) OVERRIDE;
+
+ // Method that allows us to transform a URL into an ID that can be used in our
+ // map. Resulting IDs will be lowercase and consist of the scheme, host, port
+ // and path (without query string, fragment, etc.).
+ // If the URL is invalid, the invalid spec will be returned, without any
+ // transformation.
+ std::string GetIdFromUrl(const GURL& url) const;
+
+ // Method that ensures the map gets cleaned from time to time. The period at
+ // which garbage collecting happens is adjustable with the
+ // kRequestBetweenCollecting constant.
+ void GarbageCollectEntriesIfNecessary();
+
+ // Method that does the actual work of garbage collecting.
+ void GarbageCollectEntries();
+
+ // When we switch from online to offline or change IP addresses, we
+ // clear all back-off history. This is a precaution in case the change in
+ // online state now lets us communicate without error with servers that
+ // we were previously getting 500 or 503 responses from (perhaps the
+ // responses are from a badly-written proxy that should have returned a
+ // 502 or 504 because it's upstream connection was down or it had no route
+ // to the server).
+ void OnNetworkChange();
+
+ // Used by tests.
+ int GetNumberOfEntriesForTests() const { return url_entries_.size(); }
+
+ private:
+ // From each URL we generate an ID composed of the scheme, host, port and path
+ // that allows us to uniquely map an entry to it.
+ typedef std::map<std::string, scoped_refptr<URLRequestThrottlerEntry> >
+ UrlEntryMap;
+
+ // We maintain a set of hosts that have opted out of exponential
+ // back-off throttling.
+ typedef std::set<std::string> OptOutHosts;
+
+ // Maximum number of entries that we are willing to collect in our map.
+ static const unsigned int kMaximumNumberOfEntries;
+ // Number of requests that will be made between garbage collection.
+ static const unsigned int kRequestsBetweenCollecting;
+
+ // Map that contains a list of URL ID and their matching
+ // URLRequestThrottlerEntry.
+ UrlEntryMap url_entries_;
+
+ // Set of hosts that have opted out.
+ OptOutHosts opt_out_hosts_;
+
+ // This keeps track of how many requests have been made. Used with
+ // GarbageCollectEntries.
+ unsigned int requests_since_last_gc_;
+
+ // Valid after construction.
+ GURL::Replacements url_id_replacements_;
+
+ // Certain tests do not obey the net component's threading policy, so we
+ // keep track of whether we're being used by tests, and turn off certain
+ // checks.
+ //
+ // TODO(joi): See if we can fix the offending unit tests and remove this
+ // workaround.
+ bool enable_thread_checks_;
+
+ // Initially false, switches to true once we have logged because of back-off
+ // being disabled for localhost.
+ bool logged_for_localhost_disabled_;
+
+ // NetLog to use, if configured.
+ BoundNetLog net_log_;
+
+ // Valid once we've registered for network notifications.
+ base::PlatformThreadId registered_from_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerManager);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
diff --git a/chromium/net/url_request/url_request_throttler_simulation_unittest.cc b/chromium/net/url_request/url_request_throttler_simulation_unittest.cc
new file mode 100644
index 00000000000..059c80fd647
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_simulation_unittest.cc
@@ -0,0 +1,755 @@
+// Copyright (c) 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.
+
+// The tests in this file attempt to verify the following through simulation:
+// a) That a server experiencing overload will actually benefit from the
+// anti-DDoS throttling logic, i.e. that its traffic spike will subside
+// and be distributed over a longer period of time;
+// b) That "well-behaved" clients of a server under DDoS attack actually
+// benefit from the anti-DDoS throttling logic; and
+// c) That the approximate increase in "perceived downtime" introduced by
+// anti-DDoS throttling for various different actual downtimes is what
+// we expect it to be.
+
+#include <cmath>
+#include <limits>
+#include <vector>
+
+#include "base/environment.h"
+#include "base/memory/scoped_vector.h"
+#include "base/rand_util.h"
+#include "base/time/time.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_manager.h"
+#include "net/url_request/url_request_throttler_test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+namespace {
+
+// Set this variable in your environment if you want to see verbose results
+// of the simulation tests.
+const char kShowSimulationVariableName[] = "SHOW_SIMULATION_RESULTS";
+
+// Prints output only if a given environment variable is set. We use this
+// to not print any output for human evaluation when the test is run without
+// supervision.
+void VerboseOut(const char* format, ...) {
+ static bool have_checked_environment = false;
+ static bool should_print = false;
+ if (!have_checked_environment) {
+ have_checked_environment = true;
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ if (env->HasVar(kShowSimulationVariableName))
+ should_print = true;
+ }
+
+ if (should_print) {
+ va_list arglist;
+ va_start(arglist, format);
+ vprintf(format, arglist);
+ va_end(arglist);
+ }
+}
+
+// A simple two-phase discrete time simulation. Actors are added in the order
+// they should take action at every tick of the clock. Ticks of the clock
+// are two-phase:
+// - Phase 1 advances every actor's time to a new absolute time.
+// - Phase 2 asks each actor to perform their action.
+class DiscreteTimeSimulation {
+ public:
+ class Actor {
+ public:
+ virtual ~Actor() {}
+ virtual void AdvanceTime(const TimeTicks& absolute_time) = 0;
+ virtual void PerformAction() = 0;
+ };
+
+ DiscreteTimeSimulation() {}
+
+ // Adds an |actor| to the simulation. The client of the simulation maintains
+ // ownership of |actor| and must ensure its lifetime exceeds that of the
+ // simulation. Actors should be added in the order you wish for them to
+ // act at each tick of the simulation.
+ void AddActor(Actor* actor) {
+ actors_.push_back(actor);
+ }
+
+ // Runs the simulation for, pretending |time_between_ticks| passes from one
+ // tick to the next. The start time will be the current real time. The
+ // simulation will stop when the simulated duration is equal to or greater
+ // than |maximum_simulated_duration|.
+ void RunSimulation(const TimeDelta& maximum_simulated_duration,
+ const TimeDelta& time_between_ticks) {
+ TimeTicks start_time = TimeTicks();
+ TimeTicks now = start_time;
+ while ((now - start_time) <= maximum_simulated_duration) {
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end();
+ ++it) {
+ (*it)->AdvanceTime(now);
+ }
+
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end();
+ ++it) {
+ (*it)->PerformAction();
+ }
+
+ now += time_between_ticks;
+ }
+ }
+
+ private:
+ std::vector<Actor*> actors_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscreteTimeSimulation);
+};
+
+// Represents a web server in a simulation of a server under attack by
+// a lot of clients. Must be added to the simulation's list of actors
+// after all |Requester| objects.
+class Server : public DiscreteTimeSimulation::Actor {
+ public:
+ Server(int max_queries_per_tick,
+ double request_drop_ratio)
+ : max_queries_per_tick_(max_queries_per_tick),
+ request_drop_ratio_(request_drop_ratio),
+ num_overloaded_ticks_remaining_(0),
+ num_current_tick_queries_(0),
+ num_overloaded_ticks_(0),
+ max_experienced_queries_per_tick_(0),
+ mock_request_(GURL(), NULL, &context_, NULL) {
+ }
+
+ void SetDowntime(const TimeTicks& start_time, const TimeDelta& duration) {
+ start_downtime_ = start_time;
+ end_downtime_ = start_time + duration;
+ }
+
+ virtual void AdvanceTime(const TimeTicks& absolute_time) OVERRIDE {
+ now_ = absolute_time;
+ }
+
+ virtual void PerformAction() OVERRIDE {
+ // We are inserted at the end of the actor's list, so all Requester
+ // instances have already done their bit.
+ if (num_current_tick_queries_ > max_experienced_queries_per_tick_)
+ max_experienced_queries_per_tick_ = num_current_tick_queries_;
+
+ if (num_current_tick_queries_ > max_queries_per_tick_) {
+ // We pretend the server fails for the next several ticks after it
+ // gets overloaded.
+ num_overloaded_ticks_remaining_ = 5;
+ ++num_overloaded_ticks_;
+ } else if (num_overloaded_ticks_remaining_ > 0) {
+ --num_overloaded_ticks_remaining_;
+ }
+
+ requests_per_tick_.push_back(num_current_tick_queries_);
+ num_current_tick_queries_ = 0;
+ }
+
+ // This is called by Requester. It returns the response code from
+ // the server.
+ int HandleRequest() {
+ ++num_current_tick_queries_;
+ if (!start_downtime_.is_null() &&
+ start_downtime_ < now_ && now_ < end_downtime_) {
+ // For the simulation measuring the increase in perceived
+ // downtime, it might be interesting to count separately the
+ // queries seen by the server (assuming a front-end reverse proxy
+ // is what actually serves up the 503s in this case) so that we could
+ // visualize the traffic spike seen by the server when it comes up,
+ // which would in many situations be ameliorated by the anti-DDoS
+ // throttling.
+ return 503;
+ }
+
+ if ((num_overloaded_ticks_remaining_ > 0 ||
+ num_current_tick_queries_ > max_queries_per_tick_) &&
+ base::RandDouble() < request_drop_ratio_) {
+ return 503;
+ }
+
+ return 200;
+ }
+
+ int num_overloaded_ticks() const {
+ return num_overloaded_ticks_;
+ }
+
+ int max_experienced_queries_per_tick() const {
+ return max_experienced_queries_per_tick_;
+ }
+
+ const URLRequest& mock_request() const {
+ return mock_request_;
+ }
+
+ std::string VisualizeASCII(int terminal_width) {
+ // Account for | characters we place at left of graph.
+ terminal_width -= 1;
+
+ VerboseOut("Overloaded for %d of %d ticks.\n",
+ num_overloaded_ticks_, requests_per_tick_.size());
+ VerboseOut("Got maximum of %d requests in a tick.\n\n",
+ max_experienced_queries_per_tick_);
+
+ VerboseOut("Traffic graph:\n\n");
+
+ // Printing the graph like this is a bit overkill, but was very useful
+ // while developing the various simulations to see if they were testing
+ // the corner cases we want to simulate.
+
+ // Find the smallest number of whole ticks we need to group into a
+ // column that will let all ticks fit into the column width we have.
+ int num_ticks = requests_per_tick_.size();
+ double ticks_per_column_exact =
+ static_cast<double>(num_ticks) / static_cast<double>(terminal_width);
+ int ticks_per_column = std::ceil(ticks_per_column_exact);
+ DCHECK_GE(ticks_per_column * terminal_width, num_ticks);
+
+ // Sum up the column values.
+ int num_columns = num_ticks / ticks_per_column;
+ if (num_ticks % ticks_per_column)
+ ++num_columns;
+ DCHECK_LE(num_columns, terminal_width);
+ scoped_ptr<int[]> columns(new int[num_columns]);
+ for (int tx = 0; tx < num_ticks; ++tx) {
+ int cx = tx / ticks_per_column;
+ if (tx % ticks_per_column == 0)
+ columns[cx] = 0;
+ columns[cx] += requests_per_tick_[tx];
+ }
+
+ // Find the lowest integer divisor that will let the column values
+ // be represented in a graph of maximum height 50.
+ int max_value = 0;
+ for (int cx = 0; cx < num_columns; ++cx)
+ max_value = std::max(max_value, columns[cx]);
+ const int kNumRows = 50;
+ double row_divisor_exact = max_value / static_cast<double>(kNumRows);
+ int row_divisor = std::ceil(row_divisor_exact);
+ DCHECK_GE(row_divisor * kNumRows, max_value);
+
+ // To show the overload line, we calculate the appropriate value.
+ int overload_value = max_queries_per_tick_ * ticks_per_column;
+
+ // When num_ticks is not a whole multiple of ticks_per_column, the last
+ // column includes fewer ticks than the others. In this case, don't
+ // print it so that we don't show an inconsistent value.
+ int num_printed_columns = num_columns;
+ if (num_ticks % ticks_per_column)
+ --num_printed_columns;
+
+ // This is a top-to-bottom traversal of rows, left-to-right per row.
+ std::string output;
+ for (int rx = 0; rx < kNumRows; ++rx) {
+ int range_min = (kNumRows - rx) * row_divisor;
+ int range_max = range_min + row_divisor;
+ if (range_min == 0)
+ range_min = -1; // Make 0 values fit in the bottom range.
+ output.append("|");
+ for (int cx = 0; cx < num_printed_columns; ++cx) {
+ char block = ' ';
+ // Show the overload line.
+ if (range_min < overload_value && overload_value <= range_max)
+ block = '-';
+
+ // Preferentially, show the graph line.
+ if (range_min < columns[cx] && columns[cx] <= range_max)
+ block = '#';
+
+ output.append(1, block);
+ }
+ output.append("\n");
+ }
+ output.append("|");
+ output.append(num_printed_columns, '=');
+
+ return output;
+ }
+
+ private:
+ TimeTicks now_;
+ TimeTicks start_downtime_; // Can be 0 to say "no downtime".
+ TimeTicks end_downtime_;
+ const int max_queries_per_tick_;
+ const double request_drop_ratio_; // Ratio of requests to 503 when failing.
+ int num_overloaded_ticks_remaining_;
+ int num_current_tick_queries_;
+ int num_overloaded_ticks_;
+ int max_experienced_queries_per_tick_;
+ std::vector<int> requests_per_tick_;
+
+ TestURLRequestContext context_;
+ TestURLRequest mock_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+// Mock throttler entry used by Requester class.
+class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
+ public:
+ explicit MockURLRequestThrottlerEntry(URLRequestThrottlerManager* manager)
+ : URLRequestThrottlerEntry(manager, std::string()),
+ mock_backoff_entry_(&backoff_policy_) {}
+
+ virtual const BackoffEntry* GetBackoffEntry() const OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ virtual BackoffEntry* GetBackoffEntry() OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ virtual TimeTicks ImplGetTimeNow() const OVERRIDE {
+ return fake_now_;
+ }
+
+ void SetFakeNow(const TimeTicks& fake_time) {
+ fake_now_ = fake_time;
+ mock_backoff_entry_.set_fake_now(fake_time);
+ }
+
+ TimeTicks fake_now() const {
+ return fake_now_;
+ }
+
+ protected:
+ virtual ~MockURLRequestThrottlerEntry() {}
+
+ private:
+ TimeTicks fake_now_;
+ MockBackoffEntry mock_backoff_entry_;
+};
+
+// Registry of results for a class of |Requester| objects (e.g. attackers vs.
+// regular clients).
+class RequesterResults {
+public:
+ RequesterResults()
+ : num_attempts_(0), num_successful_(0), num_failed_(0), num_blocked_(0) {
+ }
+
+ void AddSuccess() {
+ ++num_attempts_;
+ ++num_successful_;
+ }
+
+ void AddFailure() {
+ ++num_attempts_;
+ ++num_failed_;
+ }
+
+ void AddBlocked() {
+ ++num_attempts_;
+ ++num_blocked_;
+ }
+
+ int num_attempts() const { return num_attempts_; }
+ int num_successful() const { return num_successful_; }
+ int num_failed() const { return num_failed_; }
+ int num_blocked() const { return num_blocked_; }
+
+ double GetBlockedRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_blocked_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ double GetSuccessRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_successful_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ void PrintResults(const char* class_description) {
+ if (num_attempts_ == 0) {
+ VerboseOut("No data for %s\n", class_description);
+ return;
+ }
+
+ VerboseOut("Requester results for %s\n", class_description);
+ VerboseOut(" %d attempts\n", num_attempts_);
+ VerboseOut(" %d successes\n", num_successful_);
+ VerboseOut(" %d 5xx responses\n", num_failed_);
+ VerboseOut(" %d requests blocked\n", num_blocked_);
+ VerboseOut(" %.2f success ratio\n", GetSuccessRatio());
+ VerboseOut(" %.2f blocked ratio\n", GetBlockedRatio());
+ VerboseOut("\n");
+ }
+
+ private:
+ int num_attempts_;
+ int num_successful_;
+ int num_failed_;
+ int num_blocked_;
+};
+
+// Represents an Requester in a simulated DDoS situation, that periodically
+// requests a specific resource.
+class Requester : public DiscreteTimeSimulation::Actor {
+ public:
+ Requester(MockURLRequestThrottlerEntry* throttler_entry,
+ const TimeDelta& time_between_requests,
+ Server* server,
+ RequesterResults* results)
+ : throttler_entry_(throttler_entry),
+ time_between_requests_(time_between_requests),
+ last_attempt_was_failure_(false),
+ server_(server),
+ results_(results) {
+ DCHECK(server_);
+ }
+
+ virtual void AdvanceTime(const TimeTicks& absolute_time) OVERRIDE {
+ if (time_of_last_success_.is_null())
+ time_of_last_success_ = absolute_time;
+
+ throttler_entry_->SetFakeNow(absolute_time);
+ }
+
+ virtual void PerformAction() OVERRIDE {
+ TimeDelta effective_delay = time_between_requests_;
+ TimeDelta current_jitter = TimeDelta::FromMilliseconds(
+ request_jitter_.InMilliseconds() * base::RandDouble());
+ if (base::RandInt(0, 1)) {
+ effective_delay -= current_jitter;
+ } else {
+ effective_delay += current_jitter;
+ }
+
+ if (throttler_entry_->fake_now() - time_of_last_attempt_ >
+ effective_delay) {
+ if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
+ int status_code = server_->HandleRequest();
+ MockURLRequestThrottlerHeaderAdapter response_headers(status_code);
+ throttler_entry_->UpdateWithResponse(std::string(), &response_headers);
+
+ if (status_code == 200) {
+ if (results_)
+ results_->AddSuccess();
+
+ if (last_attempt_was_failure_) {
+ last_downtime_duration_ =
+ throttler_entry_->fake_now() - time_of_last_success_;
+ }
+
+ time_of_last_success_ = throttler_entry_->fake_now();
+ last_attempt_was_failure_ = false;
+ } else {
+ if (results_)
+ results_->AddFailure();
+ last_attempt_was_failure_ = true;
+ }
+ } else {
+ if (results_)
+ results_->AddBlocked();
+ last_attempt_was_failure_ = true;
+ }
+
+ time_of_last_attempt_ = throttler_entry_->fake_now();
+ }
+ }
+
+ // Adds a delay until the first request, equal to a uniformly distributed
+ // value between now and now + max_delay.
+ void SetStartupJitter(const TimeDelta& max_delay) {
+ int delay_ms = base::RandInt(0, max_delay.InMilliseconds());
+ time_of_last_attempt_ = TimeTicks() +
+ TimeDelta::FromMilliseconds(delay_ms) - time_between_requests_;
+ }
+
+ void SetRequestJitter(const TimeDelta& request_jitter) {
+ request_jitter_ = request_jitter;
+ }
+
+ TimeDelta last_downtime_duration() const { return last_downtime_duration_; }
+
+ private:
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry_;
+ const TimeDelta time_between_requests_;
+ TimeDelta request_jitter_;
+ TimeTicks time_of_last_attempt_;
+ TimeTicks time_of_last_success_;
+ bool last_attempt_was_failure_;
+ TimeDelta last_downtime_duration_;
+ Server* const server_;
+ RequesterResults* const results_; // May be NULL.
+
+ DISALLOW_COPY_AND_ASSIGN(Requester);
+};
+
+void SimulateAttack(Server* server,
+ RequesterResults* attacker_results,
+ RequesterResults* client_results,
+ bool enable_throttling) {
+ const size_t kNumAttackers = 50;
+ const size_t kNumClients = 50;
+ DiscreteTimeSimulation simulation;
+ URLRequestThrottlerManager manager;
+ ScopedVector<Requester> requesters;
+ for (size_t i = 0; i < kNumAttackers; ++i) {
+ // Use a tiny time_between_requests so the attackers will ping the
+ // server at every tick of the simulation.
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* attacker = new Requester(throttler_entry.get(),
+ TimeDelta::FromMilliseconds(1),
+ server,
+ attacker_results);
+ attacker->SetStartupJitter(TimeDelta::FromSeconds(120));
+ requesters.push_back(attacker);
+ simulation.AddActor(attacker);
+ }
+ for (size_t i = 0; i < kNumClients; ++i) {
+ // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* client = new Requester(throttler_entry.get(),
+ TimeDelta::FromMinutes(2),
+ server,
+ client_results);
+ client->SetStartupJitter(TimeDelta::FromSeconds(120));
+ client->SetRequestJitter(TimeDelta::FromMinutes(1));
+ requesters.push_back(client);
+ simulation.AddActor(client);
+ }
+ simulation.AddActor(server);
+
+ simulation.RunSimulation(TimeDelta::FromMinutes(6),
+ TimeDelta::FromSeconds(1));
+}
+
+TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
+ Server unprotected_server(30, 1.0);
+ RequesterResults unprotected_attacker_results;
+ RequesterResults unprotected_client_results;
+ Server protected_server(30, 1.0);
+ RequesterResults protected_attacker_results;
+ RequesterResults protected_client_results;
+ SimulateAttack(&unprotected_server,
+ &unprotected_attacker_results,
+ &unprotected_client_results,
+ false);
+ SimulateAttack(&protected_server,
+ &protected_attacker_results,
+ &protected_client_results,
+ true);
+
+ // These assert that the DDoS protection actually benefits the
+ // server. Manual inspection of the traffic graphs will show this
+ // even more clearly.
+ EXPECT_GT(unprotected_server.num_overloaded_ticks(),
+ protected_server.num_overloaded_ticks());
+ EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
+ protected_server.max_experienced_queries_per_tick());
+
+ // These assert that the DDoS protection actually benefits non-malicious
+ // (and non-degenerate/accidentally DDoSing) users.
+ EXPECT_LT(protected_client_results.GetBlockedRatio(),
+ protected_attacker_results.GetBlockedRatio());
+ EXPECT_GT(protected_client_results.GetSuccessRatio(),
+ unprotected_client_results.GetSuccessRatio());
+
+ // The rest is just for optional manual evaluation of the results;
+ // in particular the traffic pattern is interesting.
+
+ VerboseOut("\nUnprotected server's results:\n\n");
+ VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+ VerboseOut("Protected server's results:\n\n");
+ VerboseOut(protected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+
+ unprotected_attacker_results.PrintResults(
+ "attackers attacking unprotected server.");
+ unprotected_client_results.PrintResults(
+ "normal clients making requests to unprotected server.");
+ protected_attacker_results.PrintResults(
+ "attackers attacking protected server.");
+ protected_client_results.PrintResults(
+ "normal clients making requests to protected server.");
+}
+
+// Returns the downtime perceived by the client, as a ratio of the
+// actual downtime.
+double SimulateDowntime(const TimeDelta& duration,
+ const TimeDelta& average_client_interval,
+ bool enable_throttling) {
+ TimeDelta time_between_ticks = duration / 200;
+ TimeTicks start_downtime = TimeTicks() + (duration / 2);
+
+ // A server that never rejects requests, but will go down for maintenance.
+ Server server(std::numeric_limits<int>::max(), 1.0);
+ server.SetDowntime(start_downtime, duration);
+
+ URLRequestThrottlerManager manager;
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester requester(
+ throttler_entry.get(), average_client_interval, &server, NULL);
+ requester.SetStartupJitter(duration / 3);
+ requester.SetRequestJitter(average_client_interval);
+
+ DiscreteTimeSimulation simulation;
+ simulation.AddActor(&requester);
+ simulation.AddActor(&server);
+
+ simulation.RunSimulation(duration * 2, time_between_ticks);
+
+ return static_cast<double>(
+ requester.last_downtime_duration().InMilliseconds()) /
+ static_cast<double>(duration.InMilliseconds());
+}
+
+TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
+ struct Stats {
+ // Expected interval that we expect the ratio of downtime when anti-DDoS
+ // is enabled and downtime when anti-DDoS is not enabled to fall within.
+ //
+ // The expected interval depends on two things: The exponential back-off
+ // policy encoded in URLRequestThrottlerEntry, and the test or set of
+ // tests that the Stats object is tracking (e.g. a test where the client
+ // retries very rapidly on a very long downtime will tend to increase the
+ // number).
+ //
+ // To determine an appropriate new interval when parameters have changed,
+ // run the test a few times (you may have to Ctrl-C out of it after a few
+ // seconds) and choose an interval that the test converges quickly and
+ // reliably to. Then set the new interval, and run the test e.g. 20 times
+ // in succession to make sure it never takes an obscenely long time to
+ // converge to this interval.
+ double expected_min_increase;
+ double expected_max_increase;
+
+ size_t num_runs;
+ double total_ratio_unprotected;
+ double total_ratio_protected;
+
+ bool DidConverge(double* increase_ratio_out) {
+ double unprotected_ratio = total_ratio_unprotected / num_runs;
+ double protected_ratio = total_ratio_protected / num_runs;
+ double increase_ratio = protected_ratio / unprotected_ratio;
+ if (increase_ratio_out)
+ *increase_ratio_out = increase_ratio;
+ return expected_min_increase <= increase_ratio &&
+ increase_ratio <= expected_max_increase;
+ }
+
+ void ReportTrialResult(double increase_ratio) {
+ VerboseOut(
+ " Perceived downtime with throttling is %.4f times without.\n",
+ increase_ratio);
+ VerboseOut(" Test result after %d trials.\n", num_runs);
+ }
+ };
+
+ Stats global_stats = { 1.08, 1.15 };
+
+ struct Trial {
+ TimeDelta duration;
+ TimeDelta average_client_interval;
+ Stats stats;
+
+ void PrintTrialDescription() {
+ double duration_minutes =
+ static_cast<double>(duration.InSeconds()) / 60.0;
+ double interval_minutes =
+ static_cast<double>(average_client_interval.InSeconds()) / 60.0;
+ VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
+ duration_minutes, interval_minutes);
+ }
+ };
+
+ // We don't set or check expected ratio intervals on individual
+ // experiments as this might make the test too fragile, but we
+ // print them out at the end for manual evaluation (we want to be
+ // able to make claims about the expected ratios depending on the
+ // type of behavior of the client and the downtime, e.g. the difference
+ // in behavior between a client making requests every few minutes vs.
+ // one that makes a request every 15 seconds).
+ Trial trials[] = {
+ { TimeDelta::FromSeconds(10), TimeDelta::FromSeconds(3) },
+ { TimeDelta::FromSeconds(30), TimeDelta::FromSeconds(7) },
+ { TimeDelta::FromMinutes(5), TimeDelta::FromSeconds(30) },
+ { TimeDelta::FromMinutes(10), TimeDelta::FromSeconds(20) },
+ { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(50) },
+ { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(5) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(7) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(7) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(20) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(3) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromSeconds(15) },
+
+ // Most brutal?
+ { TimeDelta::FromMinutes(45), TimeDelta::FromMilliseconds(500) },
+ };
+
+ // If things don't converge by the time we've done 100K trials, then
+ // clearly one or more of the expected intervals are wrong.
+ while (global_stats.num_runs < 100000) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(trials); ++i) {
+ ++global_stats.num_runs;
+ ++trials[i].stats.num_runs;
+ double ratio_unprotected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, false);
+ double ratio_protected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, true);
+ global_stats.total_ratio_unprotected += ratio_unprotected;
+ global_stats.total_ratio_protected += ratio_protected;
+ trials[i].stats.total_ratio_unprotected += ratio_unprotected;
+ trials[i].stats.total_ratio_protected += ratio_protected;
+ }
+
+ double increase_ratio;
+ if (global_stats.DidConverge(&increase_ratio))
+ break;
+
+ if (global_stats.num_runs > 200) {
+ VerboseOut("Test has not yet converged on expected interval.\n");
+ global_stats.ReportTrialResult(increase_ratio);
+ }
+ }
+
+ double average_increase_ratio;
+ EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
+
+ // Print individual trial results for optional manual evaluation.
+ double max_increase_ratio = 0.0;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(trials); ++i) {
+ double increase_ratio;
+ trials[i].stats.DidConverge(&increase_ratio);
+ max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
+ trials[i].PrintTrialDescription();
+ trials[i].stats.ReportTrialResult(increase_ratio);
+ }
+
+ VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
+ VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/url_request/url_request_throttler_test_support.cc b/chromium/net/url_request/url_request_throttler_test_support.cc
new file mode 100644
index 00000000000..9d3f4e253bc
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_test_support.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_throttler_test_support.h"
+
+#include "net/url_request/url_request_throttler_entry.h"
+
+namespace net {
+
+MockBackoffEntry::MockBackoffEntry(const BackoffEntry::Policy* const policy)
+ : BackoffEntry(policy) {
+}
+
+MockBackoffEntry::~MockBackoffEntry() {
+}
+
+base::TimeTicks MockBackoffEntry::ImplGetTimeNow() const {
+ return fake_now_;
+}
+
+void MockBackoffEntry::set_fake_now(const base::TimeTicks& now) {
+ fake_now_ = now;
+}
+
+MockURLRequestThrottlerHeaderAdapter::MockURLRequestThrottlerHeaderAdapter(
+ int response_code)
+ : fake_response_code_(response_code) {
+}
+
+MockURLRequestThrottlerHeaderAdapter::MockURLRequestThrottlerHeaderAdapter(
+ const std::string& retry_value,
+ const std::string& opt_out_value,
+ int response_code)
+ : fake_retry_value_(retry_value),
+ fake_opt_out_value_(opt_out_value),
+ fake_response_code_(response_code) {
+}
+
+MockURLRequestThrottlerHeaderAdapter::~MockURLRequestThrottlerHeaderAdapter() {
+}
+
+std::string MockURLRequestThrottlerHeaderAdapter::GetNormalizedValue(
+ const std::string& key) const {
+ if (key ==
+ URLRequestThrottlerEntry::kExponentialThrottlingHeader &&
+ !fake_opt_out_value_.empty()) {
+ return fake_opt_out_value_;
+ }
+
+ return std::string();
+}
+
+int MockURLRequestThrottlerHeaderAdapter::GetResponseCode() const {
+ return fake_response_code_;
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_throttler_test_support.h b/chromium/net/url_request/url_request_throttler_test_support.h
new file mode 100644
index 00000000000..fc280686b8f
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_test_support.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
+
+#include <string>
+
+#include "base/time/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+
+namespace net {
+
+class MockBackoffEntry : public BackoffEntry {
+ public:
+ explicit MockBackoffEntry(const BackoffEntry::Policy* const policy);
+ virtual ~MockBackoffEntry();
+
+ // BackoffEntry overrides.
+ virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE;
+
+ void set_fake_now(const base::TimeTicks& now);
+
+ private:
+ base::TimeTicks fake_now_;
+};
+
+// Mocks the URLRequestThrottlerHeaderInterface, allowing testing code to
+// pass arbitrary fake headers to the throttling code.
+class MockURLRequestThrottlerHeaderAdapter
+ : public URLRequestThrottlerHeaderInterface {
+ public:
+ // Constructs mock response headers with the given |response_code| and no
+ // custom response header fields.
+ explicit MockURLRequestThrottlerHeaderAdapter(int response_code);
+
+ // Constructs mock response headers with the given |response_code| and
+ // with a custom-retry header value |retry_value| if it is non-empty, and
+ // a custom opt-out header value |opt_out_value| if it is non-empty.
+ MockURLRequestThrottlerHeaderAdapter(const std::string& retry_value,
+ const std::string& opt_out_value,
+ int response_code);
+ virtual ~MockURLRequestThrottlerHeaderAdapter();
+
+ // URLRequestThrottlerHeaderInterface overrides.
+ virtual std::string GetNormalizedValue(const std::string& key) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+
+ private:
+ std::string fake_retry_value_;
+ std::string fake_opt_out_value_;
+ int fake_response_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockURLRequestThrottlerHeaderAdapter);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
diff --git a/chromium/net/url_request/url_request_throttler_unittest.cc b/chromium/net/url_request/url_request_throttler_unittest.cc
new file mode 100644
index 00000000000..e47da30ef9a
--- /dev/null
+++ b/chromium/net/url_request/url_request_throttler_unittest.cc
@@ -0,0 +1,566 @@
+// Copyright (c) 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.
+
+#include "net/url_request/url_request_throttler_manager.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/load_flags.h"
+#include "net/base/test_completion_callback.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+#include "net/url_request/url_request_throttler_test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+
+namespace {
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::StatisticsRecorder;
+
+class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
+ public:
+ explicit MockURLRequestThrottlerEntry(
+ net::URLRequestThrottlerManager* manager)
+ : net::URLRequestThrottlerEntry(manager, std::string()),
+ mock_backoff_entry_(&backoff_policy_) {
+ InitPolicy();
+ }
+ MockURLRequestThrottlerEntry(
+ net::URLRequestThrottlerManager* manager,
+ const TimeTicks& exponential_backoff_release_time,
+ const TimeTicks& sliding_window_release_time,
+ const TimeTicks& fake_now)
+ : net::URLRequestThrottlerEntry(manager, std::string()),
+ fake_time_now_(fake_now),
+ mock_backoff_entry_(&backoff_policy_) {
+ InitPolicy();
+
+ mock_backoff_entry_.set_fake_now(fake_now);
+ set_exponential_backoff_release_time(exponential_backoff_release_time);
+ set_sliding_window_release_time(sliding_window_release_time);
+ }
+
+ void InitPolicy() {
+ // Some tests become flaky if we have jitter.
+ backoff_policy_.jitter_factor = 0.0;
+
+ // This lets us avoid having to make multiple failures initially (this
+ // logic is already tested in the BackoffEntry unit tests).
+ backoff_policy_.num_errors_to_ignore = 0;
+ }
+
+ virtual const BackoffEntry* GetBackoffEntry() const OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ virtual BackoffEntry* GetBackoffEntry() OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ static bool ExplicitUserRequest(int load_flags) {
+ return URLRequestThrottlerEntry::ExplicitUserRequest(load_flags);
+ }
+
+ void ResetToBlank(const TimeTicks& time_now) {
+ fake_time_now_ = time_now;
+ mock_backoff_entry_.set_fake_now(time_now);
+
+ GetBackoffEntry()->Reset();
+ GetBackoffEntry()->SetCustomReleaseTime(time_now);
+ set_sliding_window_release_time(time_now);
+ }
+
+ // Overridden for tests.
+ virtual TimeTicks ImplGetTimeNow() const OVERRIDE { return fake_time_now_; }
+
+ void set_exponential_backoff_release_time(
+ const base::TimeTicks& release_time) {
+ GetBackoffEntry()->SetCustomReleaseTime(release_time);
+ }
+
+ base::TimeTicks sliding_window_release_time() const {
+ return URLRequestThrottlerEntry::sliding_window_release_time();
+ }
+
+ void set_sliding_window_release_time(
+ const base::TimeTicks& release_time) {
+ URLRequestThrottlerEntry::set_sliding_window_release_time(
+ release_time);
+ }
+
+ TimeTicks fake_time_now_;
+ MockBackoffEntry mock_backoff_entry_;
+
+ protected:
+ virtual ~MockURLRequestThrottlerEntry() {}
+};
+
+class MockURLRequestThrottlerManager : public URLRequestThrottlerManager {
+ public:
+ MockURLRequestThrottlerManager() : create_entry_index_(0) {}
+
+ // Method to process the URL using URLRequestThrottlerManager protected
+ // method.
+ std::string DoGetUrlIdFromUrl(const GURL& url) { return GetIdFromUrl(url); }
+
+ // Method to use the garbage collecting method of URLRequestThrottlerManager.
+ void DoGarbageCollectEntries() { GarbageCollectEntries(); }
+
+ // Returns the number of entries in the map.
+ int GetNumberOfEntries() const { return GetNumberOfEntriesForTests(); }
+
+ void CreateEntry(bool is_outdated) {
+ TimeTicks time = TimeTicks::Now();
+ if (is_outdated) {
+ time -= TimeDelta::FromMilliseconds(
+ MockURLRequestThrottlerEntry::kDefaultEntryLifetimeMs + 1000);
+ }
+ std::string fake_url_string("http://www.fakeurl.com/");
+ fake_url_string.append(base::IntToString(create_entry_index_++));
+ GURL fake_url(fake_url_string);
+ OverrideEntryForTests(
+ fake_url,
+ new MockURLRequestThrottlerEntry(this, time, TimeTicks::Now(),
+ TimeTicks::Now()));
+ }
+
+ private:
+ int create_entry_index_;
+};
+
+struct TimeAndBool {
+ TimeAndBool(const TimeTicks& time_value, bool expected, int line_num) {
+ time = time_value;
+ result = expected;
+ line = line_num;
+ }
+ TimeTicks time;
+ bool result;
+ int line;
+};
+
+struct GurlAndString {
+ GurlAndString(const GURL& url_value,
+ const std::string& expected,
+ int line_num) {
+ url = url_value;
+ result = expected;
+ line = line_num;
+ }
+ GURL url;
+ std::string result;
+ int line;
+};
+
+} // namespace
+
+class URLRequestThrottlerEntryTest : public testing::Test {
+ protected:
+ URLRequestThrottlerEntryTest() : request_(GURL(), NULL, &context_, NULL) {
+ }
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ // After calling this function, histogram snapshots in |samples_| contain
+ // only the delta caused by the test case currently running.
+ void CalculateHistogramDeltas();
+
+ TimeTicks now_;
+ MockURLRequestThrottlerManager manager_; // Dummy object, not used.
+ scoped_refptr<MockURLRequestThrottlerEntry> entry_;
+
+ std::map<std::string, HistogramSamples*> original_samples_;
+ std::map<std::string, HistogramSamples*> samples_;
+
+ TestURLRequestContext context_;
+ TestURLRequest request_;
+};
+
+// List of all histograms we care about in these unit tests.
+const char* kHistogramNames[] = {
+ "Throttling.FailureCountAtSuccess",
+ "Throttling.PerceivedDowntime",
+ "Throttling.RequestThrottled",
+ "Throttling.SiteOptedOut",
+};
+
+void URLRequestThrottlerEntryTest::SetUp() {
+ request_.set_load_flags(0);
+
+ now_ = TimeTicks::Now();
+ entry_ = new MockURLRequestThrottlerEntry(&manager_);
+ entry_->ResetToBlank(now_);
+
+ for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
+ // Must retrieve original samples for each histogram for comparison
+ // as other tests may affect them.
+ const char* name = kHistogramNames[i];
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ original_samples_[name] = histogram->SnapshotSamples().release();
+ } else {
+ original_samples_[name] = NULL;
+ }
+ }
+}
+
+void URLRequestThrottlerEntryTest::TearDown() {
+ STLDeleteValues(&original_samples_);
+ STLDeleteValues(&samples_);
+}
+
+void URLRequestThrottlerEntryTest::CalculateHistogramDeltas() {
+ for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
+ const char* name = kHistogramNames[i];
+ HistogramSamples* original = original_samples_[name];
+
+ HistogramBase* histogram = StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ ASSERT_EQ(HistogramBase::kUmaTargetedHistogramFlag, histogram->flags());
+
+ scoped_ptr<HistogramSamples> samples(histogram->SnapshotSamples());
+ if (original)
+ samples->Subtract(*original);
+ samples_[name] = samples.release();
+ }
+ }
+
+ // Ensure we don't accidentally use the originals in our tests.
+ STLDeleteValues(&original_samples_);
+ original_samples_.clear();
+}
+
+std::ostream& operator<<(std::ostream& out, const base::TimeTicks& time) {
+ return out << time.ToInternalValue();
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(
+ entry_->fake_time_now_ + TimeDelta::FromMilliseconds(1));
+ EXPECT_TRUE(entry_->ShouldRejectRequest(request_));
+
+ // Also end-to-end test the load flags exceptions.
+ request_.set_load_flags(LOAD_MAYBE_USER_GESTURE);
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+
+ CalculateHistogramDeltas();
+ ASSERT_EQ(1, samples_["Throttling.RequestThrottled"]->GetCount(0));
+ ASSERT_EQ(1, samples_["Throttling.RequestThrottled"]->GetCount(1));
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceNotDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(entry_->fake_time_now_);
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+ entry_->set_exponential_backoff_release_time(
+ entry_->fake_time_now_ - TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+
+ CalculateHistogramDeltas();
+ ASSERT_EQ(2, samples_["Throttling.RequestThrottled"]->GetCount(0));
+ ASSERT_EQ(0, samples_["Throttling.RequestThrottled"]->GetCount(1));
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateFailure) {
+ MockURLRequestThrottlerHeaderAdapter failure_response(503);
+ entry_->UpdateWithResponse(std::string(), &failure_response);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "A failure should increase the release_time";
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateSuccess) {
+ MockURLRequestThrottlerHeaderAdapter success_response(200);
+ entry_->UpdateWithResponse(std::string(), &success_response);
+ EXPECT_EQ(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "A success should not add any delay";
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateSuccessThenFailure) {
+ MockURLRequestThrottlerHeaderAdapter failure_response(503);
+ MockURLRequestThrottlerHeaderAdapter success_response(200);
+ entry_->UpdateWithResponse(std::string(), &success_response);
+ entry_->UpdateWithResponse(std::string(), &failure_response);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "This scenario should add delay";
+ entry_->UpdateWithResponse(std::string(), &success_response);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, IsEntryReallyOutdated) {
+ TimeDelta lifetime = TimeDelta::FromMilliseconds(
+ MockURLRequestThrottlerEntry::kDefaultEntryLifetimeMs);
+ const TimeDelta kFiveMs = TimeDelta::FromMilliseconds(5);
+
+ TimeAndBool test_values[] = {
+ TimeAndBool(now_, false, __LINE__),
+ TimeAndBool(now_ - kFiveMs, false, __LINE__),
+ TimeAndBool(now_ + kFiveMs, false, __LINE__),
+ TimeAndBool(now_ - (lifetime - kFiveMs), false, __LINE__),
+ TimeAndBool(now_ - lifetime, true, __LINE__),
+ TimeAndBool(now_ - (lifetime + kFiveMs), true, __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ entry_->set_exponential_backoff_release_time(test_values[i].time);
+ EXPECT_EQ(entry_->IsEntryOutdated(), test_values[i].result) <<
+ "Test case #" << i << " line " << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(URLRequestThrottlerEntryTest, MaxAllowedBackoff) {
+ for (int i = 0; i < 30; ++i) {
+ MockURLRequestThrottlerHeaderAdapter response_adapter(503);
+ entry_->UpdateWithResponse(std::string(), &response_adapter);
+ }
+
+ TimeDelta delay = entry_->GetExponentialBackoffReleaseTime() - now_;
+ EXPECT_EQ(delay.InMilliseconds(),
+ MockURLRequestThrottlerEntry::kDefaultMaximumBackoffMs);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, MalformedContent) {
+ MockURLRequestThrottlerHeaderAdapter response_adapter(503);
+ for (int i = 0; i < 5; ++i)
+ entry_->UpdateWithResponse(std::string(), &response_adapter);
+
+ TimeTicks release_after_failures = entry_->GetExponentialBackoffReleaseTime();
+
+ // Inform the entry that a response body was malformed, which is supposed to
+ // increase the back-off time. Note that we also submit a successful
+ // UpdateWithResponse to pair with ReceivedContentWasMalformed() since that
+ // is what happens in practice (if a body is received, then a non-500
+ // response must also have been received).
+ entry_->ReceivedContentWasMalformed(200);
+ MockURLRequestThrottlerHeaderAdapter success_adapter(200);
+ entry_->UpdateWithResponse(std::string(), &success_adapter);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), release_after_failures);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, SlidingWindow) {
+ int max_send = URLRequestThrottlerEntry::kDefaultMaxSendThreshold;
+ int sliding_window =
+ URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs;
+
+ TimeTicks time_1 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window / 3);
+ TimeTicks time_2 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(2 * sliding_window / 3);
+ TimeTicks time_3 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window);
+ TimeTicks time_4 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window + 2 * sliding_window / 3);
+
+ entry_->set_exponential_backoff_release_time(time_1);
+
+ for (int i = 0; i < max_send / 2; ++i) {
+ EXPECT_EQ(2 * sliding_window / 3,
+ entry_->ReserveSendingTimeForNextRequest(time_2));
+ }
+ EXPECT_EQ(time_2, entry_->sliding_window_release_time());
+
+ entry_->fake_time_now_ = time_3;
+
+ for (int i = 0; i < (max_send + 1) / 2; ++i)
+ EXPECT_EQ(0, entry_->ReserveSendingTimeForNextRequest(TimeTicks()));
+
+ EXPECT_EQ(time_4, entry_->sliding_window_release_time());
+}
+
+TEST_F(URLRequestThrottlerEntryTest, ExplicitUserRequest) {
+ ASSERT_FALSE(MockURLRequestThrottlerEntry::ExplicitUserRequest(0));
+ ASSERT_TRUE(MockURLRequestThrottlerEntry::ExplicitUserRequest(
+ LOAD_MAYBE_USER_GESTURE));
+ ASSERT_FALSE(MockURLRequestThrottlerEntry::ExplicitUserRequest(
+ ~LOAD_MAYBE_USER_GESTURE));
+}
+
+class URLRequestThrottlerManagerTest : public testing::Test {
+ protected:
+ URLRequestThrottlerManagerTest()
+ : request_(GURL(), NULL, &context_, NULL) {
+ }
+
+ virtual void SetUp() {
+ request_.set_load_flags(0);
+ }
+
+ // context_ must be declared before request_.
+ TestURLRequestContext context_;
+ TestURLRequest request_;
+};
+
+TEST_F(URLRequestThrottlerManagerTest, IsUrlStandardised) {
+ MockURLRequestThrottlerManager manager;
+ GurlAndString test_values[] = {
+ GurlAndString(GURL("http://www.example.com"),
+ std::string("http://www.example.com/"),
+ __LINE__),
+ GurlAndString(GURL("http://www.Example.com"),
+ std::string("http://www.example.com/"),
+ __LINE__),
+ GurlAndString(GURL("http://www.ex4mple.com/Pr4c71c41"),
+ std::string("http://www.ex4mple.com/pr4c71c41"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/0/token/false"),
+ std::string("http://www.example.com/0/token/false"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=javascript"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=1#superEntry"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php#superEntry"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com:1234/"),
+ std::string("http://www.example.com:1234/"),
+ __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ std::string temp = manager.DoGetUrlIdFromUrl(test_values[i].url);
+ EXPECT_EQ(temp, test_values[i].result) <<
+ "Test case #" << i << " line " << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(URLRequestThrottlerManagerTest, AreEntriesBeingCollected) {
+ MockURLRequestThrottlerManager manager;
+
+ manager.CreateEntry(true); // true = Entry is outdated.
+ manager.CreateEntry(true);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(0, manager.GetNumberOfEntries());
+
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+TEST_F(URLRequestThrottlerManagerTest, IsHostBeingRegistered) {
+ MockURLRequestThrottlerManager manager;
+
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0?code=1"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0#lolsaure"));
+
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+void ExpectEntryAllowsAllOnErrorIfOptedOut(
+ net::URLRequestThrottlerEntryInterface* entry,
+ bool opted_out,
+ const URLRequest& request) {
+ EXPECT_FALSE(entry->ShouldRejectRequest(request));
+ MockURLRequestThrottlerHeaderAdapter failure_adapter(503);
+ for (int i = 0; i < 10; ++i) {
+ // Host doesn't really matter in this scenario so we skip it.
+ entry->UpdateWithResponse(std::string(), &failure_adapter);
+ }
+ EXPECT_NE(opted_out, entry->ShouldRejectRequest(request));
+
+ if (opted_out) {
+ // We're not mocking out GetTimeNow() in this scenario
+ // so add a 100 ms buffer to avoid flakiness (that should always
+ // give enough time to get from the TimeTicks::Now() call here
+ // to the TimeTicks::Now() call in the entry class).
+ EXPECT_GT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ } else {
+ // As above, add 100 ms.
+ EXPECT_LT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ }
+}
+
+TEST_F(URLRequestThrottlerManagerTest, OptOutHeader) {
+ MockURLRequestThrottlerManager manager;
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry =
+ manager.RegisterRequestUrl(GURL("http://www.google.com/yodude"));
+
+ // Fake a response with the opt-out header.
+ MockURLRequestThrottlerHeaderAdapter response_adapter(
+ std::string(),
+ MockURLRequestThrottlerEntry::kExponentialThrottlingDisableValue,
+ 200);
+ entry->UpdateWithResponse("www.google.com", &response_adapter);
+
+ // Ensure that the same entry on error always allows everything.
+ ExpectEntryAllowsAllOnErrorIfOptedOut(entry.get(), true, request_);
+
+ // Ensure that a freshly created entry (for a different URL on an
+ // already opted-out host) also gets "always allow" behavior.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> other_entry =
+ manager.RegisterRequestUrl(GURL("http://www.google.com/bingobob"));
+ ExpectEntryAllowsAllOnErrorIfOptedOut(other_entry.get(), true, request_);
+
+ // Fake a response with the opt-out header incorrectly specified.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> no_opt_out_entry =
+ manager.RegisterRequestUrl(GURL("http://www.nike.com/justdoit"));
+ MockURLRequestThrottlerHeaderAdapter wrong_adapter(
+ std::string(), "yesplease", 200);
+ no_opt_out_entry->UpdateWithResponse("www.nike.com", &wrong_adapter);
+ ExpectEntryAllowsAllOnErrorIfOptedOut(
+ no_opt_out_entry.get(), false, request_);
+
+ // A localhost entry should always be opted out.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> localhost_entry =
+ manager.RegisterRequestUrl(GURL("http://localhost/hello"));
+ ExpectEntryAllowsAllOnErrorIfOptedOut(localhost_entry.get(), true, request_);
+}
+
+TEST_F(URLRequestThrottlerManagerTest, ClearOnNetworkChange) {
+ for (int i = 0; i < 3; ++i) {
+ MockURLRequestThrottlerManager manager;
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry_before =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ MockURLRequestThrottlerHeaderAdapter failure_adapter(503);
+ for (int j = 0; j < 10; ++j) {
+ // Host doesn't really matter in this scenario so we skip it.
+ entry_before->UpdateWithResponse(std::string(), &failure_adapter);
+ }
+ EXPECT_TRUE(entry_before->ShouldRejectRequest(request_));
+
+ switch (i) {
+ case 0:
+ manager.OnIPAddressChanged();
+ break;
+ case 1:
+ manager.OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+ break;
+ case 2:
+ manager.OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::CONNECTION_NONE);
+ break;
+ default:
+ FAIL();
+ }
+
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry_after =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ EXPECT_FALSE(entry_after->ShouldRejectRequest(request_));
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/url_request/url_request_unittest.cc b/chromium/net/url_request/url_request_unittest.cc
new file mode 100644
index 00000000000..64f5ac1d477
--- /dev/null
+++ b/chromium/net/url_request/url_request_unittest.cc
@@ -0,0 +1,6251 @@
+// Copyright (c) 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.
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <shlobj.h>
+#endif
+
+#include <algorithm>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/capturing_net_log.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_timing_info.h"
+#include "net/base/load_timing_info_test_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/net_module.h"
+#include "net/base/net_util.h"
+#include "net/base/test_data_directory.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/cert/ev_root_ca_metadata.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store_test_helpers.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/ocsp/nss_ocsp.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+#include "net/url_request/data_protocol_handler.h"
+#include "net/url_request/file_protocol_handler.h"
+#include "net/url_request/ftp_protocol_handler.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_file_dir_job.h"
+#include "net/url_request/url_request_http_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_redirect_job.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/windows_version.h"
+#endif
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+const base::string16 kChrome(ASCIIToUTF16("chrome"));
+const base::string16 kSecret(ASCIIToUTF16("secret"));
+const base::string16 kUser(ASCIIToUTF16("user"));
+
+// Tests load timing information in the case a fresh connection was used, with
+// no proxy.
+void TestLoadTimingNotReused(const net::LoadTimingInfo& load_timing_info,
+ int connect_timing_flags) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ EXPECT_LE(load_timing_info.request_start,
+ load_timing_info.connect_timing.connect_start);
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ connect_timing_flags);
+ EXPECT_LE(load_timing_info.connect_timing.connect_end,
+ load_timing_info.send_start);
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+ EXPECT_LE(load_timing_info.send_end, load_timing_info.receive_headers_end);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+}
+
+// Same as above, but with proxy times.
+void TestLoadTimingNotReusedWithProxy(
+ const net::LoadTimingInfo& load_timing_info,
+ int connect_timing_flags) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ EXPECT_LE(load_timing_info.request_start,
+ load_timing_info.proxy_resolve_start);
+ EXPECT_LE(load_timing_info.proxy_resolve_start,
+ load_timing_info.proxy_resolve_end);
+ EXPECT_LE(load_timing_info.proxy_resolve_end,
+ load_timing_info.connect_timing.connect_start);
+ ExpectConnectTimingHasTimes(load_timing_info.connect_timing,
+ connect_timing_flags);
+ EXPECT_LE(load_timing_info.connect_timing.connect_end,
+ load_timing_info.send_start);
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+ EXPECT_LE(load_timing_info.send_end, load_timing_info.receive_headers_end);
+}
+
+// Same as above, but with a reused socket and proxy times.
+void TestLoadTimingReusedWithProxy(
+ const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_TRUE(load_timing_info.socket_reused);
+ EXPECT_NE(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+
+ EXPECT_LE(load_timing_info.request_start,
+ load_timing_info.proxy_resolve_start);
+ EXPECT_LE(load_timing_info.proxy_resolve_start,
+ load_timing_info.proxy_resolve_end);
+ EXPECT_LE(load_timing_info.proxy_resolve_end,
+ load_timing_info.send_start);
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+ EXPECT_LE(load_timing_info.send_end, load_timing_info.receive_headers_end);
+}
+
+// Tests load timing information in the case of a cache hit, when no cache
+// validation request was sent over the wire.
+void TestLoadTimingCacheHitNoNetwork(
+ const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_EQ(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+ EXPECT_LE(load_timing_info.request_start, load_timing_info.send_start);
+ EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end);
+ EXPECT_LE(load_timing_info.send_end, load_timing_info.receive_headers_end);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+}
+
+// Tests load timing in the case that there is no HTTP response. This can be
+// used to test in the case of errors or non-HTTP requests.
+void TestLoadTimingNoHttpResponse(
+ const net::LoadTimingInfo& load_timing_info) {
+ EXPECT_FALSE(load_timing_info.socket_reused);
+ EXPECT_EQ(net::NetLog::Source::kInvalidId, load_timing_info.socket_log_id);
+
+ // Only the request times should be non-null.
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
+
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
+
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
+ EXPECT_TRUE(load_timing_info.send_start.is_null());
+ EXPECT_TRUE(load_timing_info.send_end.is_null());
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
+}
+
+base::StringPiece TestNetResourceProvider(int key) {
+ return "header";
+}
+
+// Do a case-insensitive search through |haystack| for |needle|.
+bool ContainsString(const std::string& haystack, const char* needle) {
+ std::string::const_iterator it =
+ std::search(haystack.begin(),
+ haystack.end(),
+ needle,
+ needle + strlen(needle),
+ base::CaseInsensitiveCompare<char>());
+ return it != haystack.end();
+}
+
+void FillBuffer(char* buffer, size_t len) {
+ static bool called = false;
+ if (!called) {
+ called = true;
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ buffer[i] = static_cast<char>(rand());
+ if (!buffer[i])
+ buffer[i] = 'g';
+ }
+}
+
+UploadDataStream* CreateSimpleUploadData(const char* data) {
+ scoped_ptr<UploadElementReader> reader(
+ new UploadBytesElementReader(data, strlen(data)));
+ return UploadDataStream::CreateWithReader(reader.Pass(), 0);
+}
+
+// Verify that the SSLInfo of a successful SSL connection has valid values.
+void CheckSSLInfo(const SSLInfo& ssl_info) {
+ // Allow ChromeFrame fake SSLInfo to get through.
+ if (ssl_info.cert.get() &&
+ ssl_info.cert.get()->issuer().GetDisplayName() == "Chrome Internal") {
+ // -1 means unknown.
+ EXPECT_EQ(ssl_info.security_bits, -1);
+ return;
+ }
+
+ // -1 means unknown. 0 means no encryption.
+ EXPECT_GT(ssl_info.security_bits, 0);
+
+ // The cipher suite TLS_NULL_WITH_NULL_NULL (0) must not be negotiated.
+ int cipher_suite = SSLConnectionStatusToCipherSuite(
+ ssl_info.connection_status);
+ EXPECT_NE(0, cipher_suite);
+}
+
+void CheckFullRequestHeaders(const HttpRequestHeaders& headers,
+ const GURL& host_url) {
+ std::string sent_value;
+
+ EXPECT_TRUE(headers.GetHeader("Host", &sent_value));
+ EXPECT_EQ(GetHostAndOptionalPort(host_url), sent_value);
+
+ EXPECT_TRUE(headers.GetHeader("Connection", &sent_value));
+ EXPECT_EQ("keep-alive", sent_value);
+}
+
+bool FingerprintsEqual(const HashValueVector& a, const HashValueVector& b) {
+ size_t size = a.size();
+
+ if (size != b.size())
+ return false;
+
+ for (size_t i = 0; i < size; ++i) {
+ if (!a[i].Equals(b[i]))
+ return false;
+ }
+
+ return true;
+}
+
+// A network delegate that allows the user to choose a subset of request stages
+// to block in. When blocking, the delegate can do one of the following:
+// * synchronously return a pre-specified error code, or
+// * asynchronously return that value via an automatically called callback,
+// or
+// * block and wait for the user to do a callback.
+// Additionally, the user may also specify a redirect URL -- then each request
+// with the current URL different from the redirect target will be redirected
+// to that target, in the on-before-URL-request stage, independent of whether
+// the delegate blocks in ON_BEFORE_URL_REQUEST or not.
+class BlockingNetworkDelegate : public TestNetworkDelegate {
+ public:
+ // Stages in which the delegate can block.
+ enum Stage {
+ NOT_BLOCKED = 0,
+ ON_BEFORE_URL_REQUEST = 1 << 0,
+ ON_BEFORE_SEND_HEADERS = 1 << 1,
+ ON_HEADERS_RECEIVED = 1 << 2,
+ ON_AUTH_REQUIRED = 1 << 3
+ };
+
+ // Behavior during blocked stages. During other stages, just
+ // returns net::OK or NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION.
+ enum BlockMode {
+ SYNCHRONOUS, // No callback, returns specified return values.
+ AUTO_CALLBACK, // |this| posts a task to run the callback using the
+ // specified return codes.
+ USER_CALLBACK, // User takes care of doing a callback. |retval_| and
+ // |auth_retval_| are ignored. In every blocking stage the
+ // message loop is quit.
+ };
+
+ // Creates a delegate which does not block at all.
+ explicit BlockingNetworkDelegate(BlockMode block_mode);
+
+ // For users to trigger a callback returning |response|.
+ // Side-effects: resets |stage_blocked_for_callback_| and stored callbacks.
+ // Only call if |block_mode_| == USER_CALLBACK.
+ void DoCallback(int response);
+ void DoAuthCallback(NetworkDelegate::AuthRequiredResponse response);
+
+ // Setters.
+ void set_retval(int retval) {
+ ASSERT_NE(USER_CALLBACK, block_mode_);
+ ASSERT_NE(ERR_IO_PENDING, retval);
+ ASSERT_NE(OK, retval);
+ retval_ = retval;
+ }
+
+ // If |auth_retval| == AUTH_REQUIRED_RESPONSE_SET_AUTH, then
+ // |auth_credentials_| will be passed with the response.
+ void set_auth_retval(AuthRequiredResponse auth_retval) {
+ ASSERT_NE(USER_CALLBACK, block_mode_);
+ ASSERT_NE(AUTH_REQUIRED_RESPONSE_IO_PENDING, auth_retval);
+ auth_retval_ = auth_retval;
+ }
+ void set_auth_credentials(const AuthCredentials& auth_credentials) {
+ auth_credentials_ = auth_credentials;
+ }
+
+ void set_redirect_url(const GURL& url) {
+ redirect_url_ = url;
+ }
+
+ void set_block_on(int block_on) {
+ block_on_ = block_on;
+ }
+
+ // Allows the user to check in which state did we block.
+ Stage stage_blocked_for_callback() const {
+ EXPECT_EQ(USER_CALLBACK, block_mode_);
+ return stage_blocked_for_callback_;
+ }
+
+ private:
+ void RunCallback(int response, const CompletionCallback& callback);
+ void RunAuthCallback(AuthRequiredResponse response,
+ const AuthCallback& callback);
+
+ // TestNetworkDelegate implementation.
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE;
+
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE;
+
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE;
+
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE;
+
+ // Resets the callbacks and |stage_blocked_for_callback_|.
+ void Reset();
+
+ // Checks whether we should block in |stage|. If yes, returns an error code
+ // and optionally sets up callback based on |block_mode_|. If no, returns OK.
+ int MaybeBlockStage(Stage stage, const CompletionCallback& callback);
+
+ // Configuration parameters, can be adjusted by public methods:
+ const BlockMode block_mode_;
+
+ // Values returned on blocking stages when mode is SYNCHRONOUS or
+ // AUTO_CALLBACK. For USER_CALLBACK these are set automatically to IO_PENDING.
+ int retval_; // To be returned in non-auth stages.
+ AuthRequiredResponse auth_retval_;
+
+ GURL redirect_url_; // Used if non-empty.
+ int block_on_; // Bit mask: in which stages to block.
+
+ // |auth_credentials_| will be copied to |*target_auth_credential_| on
+ // callback.
+ AuthCredentials auth_credentials_;
+ AuthCredentials* target_auth_credentials_;
+
+ // Internal variables, not set by not the user:
+ // Last blocked stage waiting for user callback (unused if |block_mode_| !=
+ // USER_CALLBACK).
+ Stage stage_blocked_for_callback_;
+
+ // Callback objects stored during blocking stages.
+ CompletionCallback callback_;
+ AuthCallback auth_callback_;
+
+ base::WeakPtrFactory<BlockingNetworkDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockingNetworkDelegate);
+};
+
+BlockingNetworkDelegate::BlockingNetworkDelegate(BlockMode block_mode)
+ : block_mode_(block_mode),
+ retval_(OK),
+ auth_retval_(AUTH_REQUIRED_RESPONSE_NO_ACTION),
+ block_on_(0),
+ target_auth_credentials_(NULL),
+ stage_blocked_for_callback_(NOT_BLOCKED),
+ weak_factory_(this) {
+}
+
+void BlockingNetworkDelegate::DoCallback(int response) {
+ ASSERT_EQ(USER_CALLBACK, block_mode_);
+ ASSERT_NE(NOT_BLOCKED, stage_blocked_for_callback_);
+ ASSERT_NE(ON_AUTH_REQUIRED, stage_blocked_for_callback_);
+ CompletionCallback callback = callback_;
+ Reset();
+ RunCallback(response, callback);
+}
+
+void BlockingNetworkDelegate::DoAuthCallback(
+ NetworkDelegate::AuthRequiredResponse response) {
+ ASSERT_EQ(USER_CALLBACK, block_mode_);
+ ASSERT_EQ(ON_AUTH_REQUIRED, stage_blocked_for_callback_);
+ AuthCallback auth_callback = auth_callback_;
+ Reset();
+ RunAuthCallback(response, auth_callback);
+}
+
+void BlockingNetworkDelegate::RunCallback(int response,
+ const CompletionCallback& callback) {
+ callback.Run(response);
+}
+
+void BlockingNetworkDelegate::RunAuthCallback(AuthRequiredResponse response,
+ const AuthCallback& callback) {
+ if (auth_retval_ == AUTH_REQUIRED_RESPONSE_SET_AUTH) {
+ ASSERT_TRUE(target_auth_credentials_ != NULL);
+ *target_auth_credentials_ = auth_credentials_;
+ }
+ callback.Run(response);
+}
+
+int BlockingNetworkDelegate::OnBeforeURLRequest(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) {
+ if (redirect_url_ == request->url())
+ return OK; // We've already seen this request and redirected elsewhere.
+
+ TestNetworkDelegate::OnBeforeURLRequest(request, callback, new_url);
+
+ if (!redirect_url_.is_empty())
+ *new_url = redirect_url_;
+
+ return MaybeBlockStage(ON_BEFORE_URL_REQUEST, callback);
+}
+
+int BlockingNetworkDelegate::OnBeforeSendHeaders(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) {
+ TestNetworkDelegate::OnBeforeSendHeaders(request, callback, headers);
+
+ return MaybeBlockStage(ON_BEFORE_SEND_HEADERS, callback);
+}
+
+int BlockingNetworkDelegate::OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) {
+ TestNetworkDelegate::OnHeadersReceived(
+ request, callback, original_response_headers,
+ override_response_headers);
+
+ return MaybeBlockStage(ON_HEADERS_RECEIVED, callback);
+}
+
+NetworkDelegate::AuthRequiredResponse BlockingNetworkDelegate::OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) {
+ TestNetworkDelegate::OnAuthRequired(request, auth_info, callback,
+ credentials);
+ // Check that the user has provided callback for the previous blocked stage.
+ EXPECT_EQ(NOT_BLOCKED, stage_blocked_for_callback_);
+
+ if ((block_on_ & ON_AUTH_REQUIRED) == 0) {
+ return AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ target_auth_credentials_ = credentials;
+
+ switch (block_mode_) {
+ case SYNCHRONOUS:
+ if (auth_retval_ == AUTH_REQUIRED_RESPONSE_SET_AUTH)
+ *target_auth_credentials_ = auth_credentials_;
+ return auth_retval_;
+
+ case AUTO_CALLBACK:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingNetworkDelegate::RunAuthCallback,
+ weak_factory_.GetWeakPtr(), auth_retval_, callback));
+ return AUTH_REQUIRED_RESPONSE_IO_PENDING;
+
+ case USER_CALLBACK:
+ auth_callback_ = callback;
+ stage_blocked_for_callback_ = ON_AUTH_REQUIRED;
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ return AUTH_REQUIRED_RESPONSE_IO_PENDING;
+ }
+ NOTREACHED();
+ return AUTH_REQUIRED_RESPONSE_NO_ACTION; // Dummy value.
+}
+
+void BlockingNetworkDelegate::Reset() {
+ EXPECT_NE(NOT_BLOCKED, stage_blocked_for_callback_);
+ stage_blocked_for_callback_ = NOT_BLOCKED;
+ callback_.Reset();
+ auth_callback_.Reset();
+}
+
+int BlockingNetworkDelegate::MaybeBlockStage(
+ BlockingNetworkDelegate::Stage stage,
+ const CompletionCallback& callback) {
+ // Check that the user has provided callback for the previous blocked stage.
+ EXPECT_EQ(NOT_BLOCKED, stage_blocked_for_callback_);
+
+ if ((block_on_ & stage) == 0) {
+ return OK;
+ }
+
+ switch (block_mode_) {
+ case SYNCHRONOUS:
+ EXPECT_NE(OK, retval_);
+ return retval_;
+
+ case AUTO_CALLBACK:
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingNetworkDelegate::RunCallback,
+ weak_factory_.GetWeakPtr(), retval_, callback));
+ return ERR_IO_PENDING;
+
+ case USER_CALLBACK:
+ callback_ = callback;
+ stage_blocked_for_callback_ = stage;
+ base::MessageLoop::current()->PostTask(FROM_HERE,
+ base::MessageLoop::QuitClosure());
+ return ERR_IO_PENDING;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+class TestURLRequestContextWithProxy : public TestURLRequestContext {
+ public:
+ // Does not own |delegate|.
+ TestURLRequestContextWithProxy(const std::string& proxy,
+ NetworkDelegate* delegate)
+ : TestURLRequestContext(true) {
+ context_storage_.set_proxy_service(ProxyService::CreateFixed(proxy));
+ set_network_delegate(delegate);
+ Init();
+ }
+ virtual ~TestURLRequestContextWithProxy() {}
+};
+
+} // namespace
+
+// Inherit PlatformTest since we require the autorelease pool on Mac OS X.
+class URLRequestTest : public PlatformTest {
+ public:
+ URLRequestTest() : default_context_(true) {
+ default_context_.set_network_delegate(&default_network_delegate_);
+ default_context_.set_net_log(&net_log_);
+ job_factory_.SetProtocolHandler("data", new DataProtocolHandler);
+ job_factory_.SetProtocolHandler("file", new FileProtocolHandler);
+ default_context_.set_job_factory(&job_factory_);
+ default_context_.Init();
+ }
+ virtual ~URLRequestTest() {}
+
+ // Adds the TestJobInterceptor to the default context.
+ TestJobInterceptor* AddTestInterceptor() {
+ TestJobInterceptor* protocol_handler_ = new TestJobInterceptor();
+ job_factory_.SetProtocolHandler("http", NULL);
+ job_factory_.SetProtocolHandler("http", protocol_handler_);
+ return protocol_handler_;
+ }
+
+ protected:
+ CapturingNetLog net_log_;
+ TestNetworkDelegate default_network_delegate_; // Must outlive URLRequest.
+ URLRequestJobFactoryImpl job_factory_;
+ TestURLRequestContext default_context_;
+};
+
+TEST_F(URLRequestTest, AboutBlankTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("about:blank"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+
+ HttpRequestHeaders headers;
+ EXPECT_FALSE(r.GetFullRequestHeaders(&headers));
+ }
+}
+
+TEST_F(URLRequestTest, DataURLImageTest) {
+ TestDelegate d;
+ {
+ // Use our nice little Chrome logo.
+ URLRequest r(GURL(
+ "data:image/png;base64,"
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADVklEQVQ4jX2TfUwUBBjG3"
+ "w1y+HGcd9dxhXR8T4awOccJGgOSWclHImznLkTlSw0DDQXkrmgYgbUYnlQTqQxIEVxitD"
+ "5UMCATRA1CEEg+Qjw3bWDxIauJv/5oumqs39/P827vnucRmYN0gyF01GI5MpCVdW0gO7t"
+ "vNC+vqSEtbZefk5NuLv1jdJ46p/zw0HeH4+PHr3h7c1mjoV2t5rKzMx1+fg9bAgK6zHq9"
+ "cU5z+LpA3xOtx34+vTeT21onRuzssC3zxbbSwC13d/pFuC7CkIMDxQpF7r/MWq12UctI1"
+ "dWWm99ypqSYmRUBdKem8MkrO/kgaTt1O7YzlpzE5GIVd0WYUqt57yWf2McHTObYPbVD+Z"
+ "wbtlLTVMZ3BW+TnLyXLaWtmEq6WJVbT3HBh3Svj2HQQcm43XwmtoYM6vVKleh0uoWvnzW"
+ "3v3MpidruPTQPf0bia7sJOtBM0ufTWNvus/nkDFHF9ZS+uYVjRUasMeHUmyLYtcklTvzW"
+ "GFZnNOXczThvpKIzjcahSqIzkvDLayDq6D3eOjtBbNUEIZYyqsvj4V4wY92eNJ4IoyhTb"
+ "xXX1T5xsV9tm9r4TQwHLiZw/pdDZJea8TKmsmR/K0uLh/GwnCHghTja6lPhphezPfO5/5"
+ "MrVvMzNaI3+ERHfrFzPKQukrQGI4d/3EFD/3E2mVNYvi4at7CXWREaxZGD+3hg28zD3gV"
+ "Md6q5c8GdosynKmSeRuGzpjyl1/9UDGtPR5HeaKT8Wjo17WXk579BXVUhN64ehF9fhRtq"
+ "/uxxZKzNiZFGD0wRC3NFROZ5mwIPL/96K/rKMMLrIzF9uhHr+/sYH7DAbwlgC4J+R2Z7F"
+ "Ux1qLnV7MGF40smVSoJ/jvHRfYhQeUJd/SnYtGWhPHR0Sz+GE2F2yth0B36Vcz2KpnufB"
+ "JbsysjjW4kblBUiIjiURUWqJY65zxbnTy57GQyH58zgy0QBtTQv5gH15XMdKkYu+TGaJM"
+ "nlm2O34uI4b9tflqp1+QEFGzoW/ulmcofcpkZCYJhDfSpme7QcrHa+Xfji8paEQkTkSfm"
+ "moRWRNZr/F1KfVMjW+IKEnv2FwZfKdzt0BQR6lClcZR0EfEXEfv/G6W9iLiIyCoReV5En"
+ "hORIBHx+ufPj/gLB/zGI/G4Bk0AAAAASUVORK5CYII="),
+ &d,
+ &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 911);
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+
+ HttpRequestHeaders headers;
+ EXPECT_FALSE(r.GetFullRequestHeaders(&headers));
+ }
+}
+
+TEST_F(URLRequestTest, FileTest) {
+ base::FilePath app_path;
+ PathService::Get(base::FILE_EXE, &app_path);
+ GURL app_url = FilePathToFileURL(app_path);
+
+ TestDelegate d;
+ {
+ URLRequest r(app_url, &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = -1;
+ EXPECT_TRUE(file_util::GetFileSize(app_path, &file_size));
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+
+ HttpRequestHeaders headers;
+ EXPECT_FALSE(r.GetFullRequestHeaders(&headers));
+ }
+}
+
+TEST_F(URLRequestTest, FileTestCancel) {
+ base::FilePath app_path;
+ PathService::Get(base::FILE_EXE, &app_path);
+ GURL app_url = FilePathToFileURL(app_path);
+
+ TestDelegate d;
+ {
+ URLRequest r(app_url, &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+ r.Cancel();
+ }
+ // Async cancellation should be safe even when URLRequest has been already
+ // destroyed.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(URLRequestTest, FileTestFullSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ base::FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - first_byte_position;
+ const size_t content_length = last_byte_position - first_byte_position + 1;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ base::StringPrintf(
+ "bytes=%" PRIuS "-%" PRIuS,
+ first_byte_position, last_byte_position));
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(static_cast<int>(content_length), d.bytes_received());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == d.data_received());
+ }
+
+ EXPECT_TRUE(base::DeleteFile(temp_path, false));
+}
+
+TEST_F(URLRequestTest, FileTestHalfSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ base::FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - 1;
+ const size_t content_length = last_byte_position - first_byte_position + 1;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ base::StringPrintf("bytes=%" PRIuS "-",
+ first_byte_position));
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(static_cast<int>(content_length), d.bytes_received());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == d.data_received());
+ }
+
+ EXPECT_TRUE(base::DeleteFile(temp_path, false));
+}
+
+TEST_F(URLRequestTest, FileTestMultipleRanges) {
+ const size_t buffer_size = 400000;
+ scoped_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ base::FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ "bytes=0-0,10-200,200-300");
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+
+ EXPECT_TRUE(base::DeleteFile(temp_path, false));
+}
+
+TEST_F(URLRequestTest, InvalidUrlTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("invalid url"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+}
+
+#if defined(OS_WIN)
+TEST_F(URLRequestTest, ResolveShortcutTest) {
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("net");
+ app_path = app_path.AppendASCII("data");
+ app_path = app_path.AppendASCII("url_request_unittest");
+ app_path = app_path.AppendASCII("with-headers.html");
+
+ std::wstring lnk_path = app_path.value() + L".lnk";
+
+ base::win::ScopedCOMInitializer com_initializer;
+
+ // Temporarily create a shortcut for test
+ {
+ base::win::ScopedComPtr<IShellLink> shell;
+ ASSERT_TRUE(SUCCEEDED(shell.CreateInstance(CLSID_ShellLink, NULL,
+ CLSCTX_INPROC_SERVER)));
+ base::win::ScopedComPtr<IPersistFile> persist;
+ ASSERT_TRUE(SUCCEEDED(shell.QueryInterface(persist.Receive())));
+ EXPECT_TRUE(SUCCEEDED(shell->SetPath(app_path.value().c_str())));
+ EXPECT_TRUE(SUCCEEDED(shell->SetDescription(L"ResolveShortcutTest")));
+ EXPECT_TRUE(SUCCEEDED(persist->Save(lnk_path.c_str(), TRUE)));
+ }
+
+ TestDelegate d;
+ {
+ URLRequest r(FilePathToFileURL(base::FilePath(lnk_path)), &d,
+ &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ GetFileAttributesEx(app_path.value().c_str(),
+ GetFileExInfoStandard, &data);
+ HANDLE file = CreateFile(app_path.value().c_str(), GENERIC_READ,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ EXPECT_NE(INVALID_HANDLE_VALUE, file);
+ scoped_ptr<char[]> buffer(new char[data.nFileSizeLow]);
+ DWORD read_size;
+ BOOL result;
+ result = ReadFile(file, buffer.get(), data.nFileSizeLow,
+ &read_size, NULL);
+ std::string content(buffer.get(), read_size);
+ CloseHandle(file);
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_EQ(content, d.data_received());
+ }
+
+ // Clean the shortcut
+ DeleteFile(lnk_path.c_str());
+}
+#endif // defined(OS_WIN)
+
+TEST_F(URLRequestTest, FileDirCancelTest) {
+ // Put in mock resource provider.
+ NetModule::SetResourceProvider(TestNetResourceProvider);
+
+ TestDelegate d;
+ {
+ base::FilePath file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_path = file_path.Append(FILE_PATH_LITERAL("net"));
+ file_path = file_path.Append(FILE_PATH_LITERAL("data"));
+
+ URLRequest req(FilePathToFileURL(file_path), &d, &default_context_);
+ req.Start();
+ EXPECT_TRUE(req.is_pending());
+
+ d.set_cancel_in_received_data_pending(true);
+
+ base::MessageLoop::current()->Run();
+ }
+
+ // Take out mock resource provider.
+ NetModule::SetResourceProvider(NULL);
+}
+
+TEST_F(URLRequestTest, FileDirRedirectNoCrash) {
+ // There is an implicit redirect when loading a file path that matches a
+ // directory and does not end with a slash. Ensure that following such
+ // redirects does not crash. See http://crbug.com/18686.
+
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+
+ TestDelegate d;
+ URLRequest req(FilePathToFileURL(path), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_LT(0, d.bytes_received());
+ ASSERT_FALSE(d.request_failed());
+ ASSERT_TRUE(req.status().is_success());
+}
+
+#if defined(OS_WIN)
+// Don't accept the url "file:///" on windows. See http://crbug.com/1474.
+TEST_F(URLRequestTest, FileDirRedirectSingleSlash) {
+ TestDelegate d;
+ URLRequest req(GURL("file:///"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_FALSE(req.status().is_success());
+}
+#endif
+
+// Custom URLRequestJobs for use with interceptor tests
+class RestartTestJob : public URLRequestTestJob {
+ public:
+ RestartTestJob(URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {}
+ protected:
+ virtual void StartAsync() OVERRIDE {
+ this->NotifyRestartRequired();
+ }
+ private:
+ virtual ~RestartTestJob() {}
+};
+
+class CancelTestJob : public URLRequestTestJob {
+ public:
+ explicit CancelTestJob(URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {}
+ protected:
+ virtual void StartAsync() OVERRIDE {
+ request_->Cancel();
+ }
+ private:
+ virtual ~CancelTestJob() {}
+};
+
+class CancelThenRestartTestJob : public URLRequestTestJob {
+ public:
+ explicit CancelThenRestartTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {
+ }
+ protected:
+ virtual void StartAsync() OVERRIDE {
+ request_->Cancel();
+ this->NotifyRestartRequired();
+ }
+ private:
+ virtual ~CancelThenRestartTestJob() {}
+};
+
+// An Interceptor for use with interceptor tests
+class TestInterceptor : URLRequest::Interceptor {
+ public:
+ TestInterceptor()
+ : intercept_main_request_(false), restart_main_request_(false),
+ cancel_main_request_(false), cancel_then_restart_main_request_(false),
+ simulate_main_network_error_(false),
+ intercept_redirect_(false), cancel_redirect_request_(false),
+ intercept_final_response_(false), cancel_final_request_(false),
+ did_intercept_main_(false), did_restart_main_(false),
+ did_cancel_main_(false), did_cancel_then_restart_main_(false),
+ did_simulate_error_main_(false),
+ did_intercept_redirect_(false), did_cancel_redirect_(false),
+ did_intercept_final_(false), did_cancel_final_(false) {
+ URLRequest::Deprecated::RegisterRequestInterceptor(this);
+ }
+
+ virtual ~TestInterceptor() {
+ URLRequest::Deprecated::UnregisterRequestInterceptor(this);
+ }
+
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) OVERRIDE {
+ if (restart_main_request_) {
+ restart_main_request_ = false;
+ did_restart_main_ = true;
+ return new RestartTestJob(request, network_delegate);
+ }
+ if (cancel_main_request_) {
+ cancel_main_request_ = false;
+ did_cancel_main_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (cancel_then_restart_main_request_) {
+ cancel_then_restart_main_request_ = false;
+ did_cancel_then_restart_main_ = true;
+ return new CancelThenRestartTestJob(request, network_delegate);
+ }
+ if (simulate_main_network_error_) {
+ simulate_main_network_error_ = false;
+ did_simulate_error_main_ = true;
+ // will error since the requeted url is not one of its canned urls
+ return new URLRequestTestJob(request, network_delegate, true);
+ }
+ if (!intercept_main_request_)
+ return NULL;
+ intercept_main_request_ = false;
+ did_intercept_main_ = true;
+ URLRequestTestJob* job = new URLRequestTestJob(request,
+ network_delegate,
+ main_headers_,
+ main_data_,
+ true);
+ job->set_load_timing_info(main_request_load_timing_info_);
+ return job;
+ }
+
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) OVERRIDE {
+ if (cancel_redirect_request_) {
+ cancel_redirect_request_ = false;
+ did_cancel_redirect_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (!intercept_redirect_)
+ return NULL;
+ intercept_redirect_ = false;
+ did_intercept_redirect_ = true;
+ return new URLRequestTestJob(request,
+ network_delegate,
+ redirect_headers_,
+ redirect_data_,
+ true);
+ }
+
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) OVERRIDE {
+ if (cancel_final_request_) {
+ cancel_final_request_ = false;
+ did_cancel_final_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (!intercept_final_response_)
+ return NULL;
+ intercept_final_response_ = false;
+ did_intercept_final_ = true;
+ return new URLRequestTestJob(request,
+ network_delegate,
+ final_headers_,
+ final_data_,
+ true);
+ }
+
+ // Whether to intercept the main request, and if so the response to return and
+ // the LoadTimingInfo to use.
+ bool intercept_main_request_;
+ std::string main_headers_;
+ std::string main_data_;
+ LoadTimingInfo main_request_load_timing_info_;
+
+ // Other actions we take at MaybeIntercept time
+ bool restart_main_request_;
+ bool cancel_main_request_;
+ bool cancel_then_restart_main_request_;
+ bool simulate_main_network_error_;
+
+ // Whether to intercept redirects, and if so the response to return.
+ bool intercept_redirect_;
+ std::string redirect_headers_;
+ std::string redirect_data_;
+
+ // Other actions we can take at MaybeInterceptRedirect time
+ bool cancel_redirect_request_;
+
+ // Whether to intercept final response, and if so the response to return.
+ bool intercept_final_response_;
+ std::string final_headers_;
+ std::string final_data_;
+
+ // Other actions we can take at MaybeInterceptResponse time
+ bool cancel_final_request_;
+
+ // If we did something or not
+ bool did_intercept_main_;
+ bool did_restart_main_;
+ bool did_cancel_main_;
+ bool did_cancel_then_restart_main_;
+ bool did_simulate_error_main_;
+ bool did_intercept_redirect_;
+ bool did_cancel_redirect_;
+ bool did_intercept_final_;
+ bool did_cancel_final_;
+
+ // Static getters for canned response header and data strings
+
+ static std::string ok_data() {
+ return URLRequestTestJob::test_data_1();
+ }
+
+ static std::string ok_headers() {
+ return URLRequestTestJob::test_headers();
+ }
+
+ static std::string redirect_data() {
+ return std::string();
+ }
+
+ static std::string redirect_headers() {
+ return URLRequestTestJob::test_redirect_headers();
+ }
+
+ static std::string error_data() {
+ return std::string("ohhh nooooo mr. bill!");
+ }
+
+ static std::string error_headers() {
+ return URLRequestTestJob::test_error_headers();
+ }
+};
+
+TEST_F(URLRequestTest, Intercept) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a simple response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ base::SupportsUserData::Data* user_data0 = new base::SupportsUserData::Data();
+ base::SupportsUserData::Data* user_data1 = new base::SupportsUserData::Data();
+ base::SupportsUserData::Data* user_data2 = new base::SupportsUserData::Data();
+ req.SetUserData(NULL, user_data0);
+ req.SetUserData(&user_data1, user_data1);
+ req.SetUserData(&user_data2, user_data2);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Make sure we can retrieve our specific user data
+ EXPECT_EQ(user_data0, req.GetUserData(NULL));
+ EXPECT_EQ(user_data1, req.GetUserData(&user_data1));
+ EXPECT_EQ(user_data2, req.GetUserData(&user_data2));
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept that redirect and respond a final OK response
+ interceptor.intercept_redirect_ = true;
+ interceptor.redirect_headers_ = TestInterceptor::ok_headers();
+ interceptor.redirect_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_redirect_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ if (req.status().is_success()) {
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ }
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptServerError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to generate a server error response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::error_headers();
+ interceptor.main_data_ = TestInterceptor::error_data();
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptNetworkError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRestartRequired) {
+ TestInterceptor interceptor;
+
+ // restart the main request
+ interceptor.restart_main_request_ = true;
+
+ // then intercept the new main request and respond with an OK response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_restart_main_);
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ if (req.status().is_success()) {
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ }
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelMain) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel from within the restarted job
+ interceptor.cancel_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept the redirect and cancel from within that job
+ interceptor.cancel_redirect_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_cancel_redirect_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelFinal) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // setup to intercept final response and cancel from within that job
+ interceptor.cancel_final_request_ = true;
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_cancel_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelInRestart) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel then restart from within that job
+ interceptor.cancel_then_restart_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_then_restart_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+LoadTimingInfo RunLoadTimingTest(const LoadTimingInfo& job_load_timing,
+ URLRequestContext* context) {
+ TestInterceptor interceptor;
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_request_load_timing_info_ = job_load_timing;
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ LoadTimingInfo resulting_load_timing;
+ req.GetLoadTimingInfo(&resulting_load_timing);
+
+ // None of these should be modified by the URLRequest.
+ EXPECT_EQ(job_load_timing.socket_reused, resulting_load_timing.socket_reused);
+ EXPECT_EQ(job_load_timing.socket_log_id, resulting_load_timing.socket_log_id);
+ EXPECT_EQ(job_load_timing.send_start, resulting_load_timing.send_start);
+ EXPECT_EQ(job_load_timing.send_end, resulting_load_timing.send_end);
+ EXPECT_EQ(job_load_timing.receive_headers_end,
+ resulting_load_timing.receive_headers_end);
+
+ return resulting_load_timing;
+}
+
+// "Normal" LoadTimingInfo as returned by a job. Everything is in order, not
+// reused. |connect_time_flags| is used to indicate if there should be dns
+// or SSL times, and |used_proxy| is used for proxy times.
+LoadTimingInfo NormalLoadTimingInfo(base::TimeTicks now,
+ int connect_time_flags,
+ bool used_proxy) {
+ LoadTimingInfo load_timing;
+ load_timing.socket_log_id = 1;
+
+ if (used_proxy) {
+ load_timing.proxy_resolve_start = now + base::TimeDelta::FromDays(1);
+ load_timing.proxy_resolve_end = now + base::TimeDelta::FromDays(2);
+ }
+
+ LoadTimingInfo::ConnectTiming& connect_timing = load_timing.connect_timing;
+ if (connect_time_flags & CONNECT_TIMING_HAS_DNS_TIMES) {
+ connect_timing.dns_start = now + base::TimeDelta::FromDays(3);
+ connect_timing.dns_end = now + base::TimeDelta::FromDays(4);
+ }
+ connect_timing.connect_start = now + base::TimeDelta::FromDays(5);
+ if (connect_time_flags & CONNECT_TIMING_HAS_SSL_TIMES) {
+ connect_timing.ssl_start = now + base::TimeDelta::FromDays(6);
+ connect_timing.ssl_end = now + base::TimeDelta::FromDays(7);
+ }
+ connect_timing.connect_end = now + base::TimeDelta::FromDays(8);
+
+ load_timing.send_start = now + base::TimeDelta::FromDays(9);
+ load_timing.send_end = now + base::TimeDelta::FromDays(10);
+ load_timing.receive_headers_end = now + base::TimeDelta::FromDays(11);
+ return load_timing;
+}
+
+// Same as above, but in the case of a reused socket.
+LoadTimingInfo NormalLoadTimingInfoReused(base::TimeTicks now,
+ bool used_proxy) {
+ LoadTimingInfo load_timing;
+ load_timing.socket_log_id = 1;
+ load_timing.socket_reused = true;
+
+ if (used_proxy) {
+ load_timing.proxy_resolve_start = now + base::TimeDelta::FromDays(1);
+ load_timing.proxy_resolve_end = now + base::TimeDelta::FromDays(2);
+ }
+
+ load_timing.send_start = now + base::TimeDelta::FromDays(9);
+ load_timing.send_end = now + base::TimeDelta::FromDays(10);
+ load_timing.receive_headers_end = now + base::TimeDelta::FromDays(11);
+ return load_timing;
+}
+
+// Basic test that the intercept + load timing tests work.
+TEST_F(URLRequestTest, InterceptLoadTiming) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing =
+ NormalLoadTimingInfo(now, CONNECT_TIMING_HAS_DNS_TIMES, false);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Nothing should have been changed by the URLRequest.
+ EXPECT_EQ(job_load_timing.proxy_resolve_start,
+ load_timing_result.proxy_resolve_start);
+ EXPECT_EQ(job_load_timing.proxy_resolve_end,
+ load_timing_result.proxy_resolve_end);
+ EXPECT_EQ(job_load_timing.connect_timing.dns_start,
+ load_timing_result.connect_timing.dns_start);
+ EXPECT_EQ(job_load_timing.connect_timing.dns_end,
+ load_timing_result.connect_timing.dns_end);
+ EXPECT_EQ(job_load_timing.connect_timing.connect_start,
+ load_timing_result.connect_timing.connect_start);
+ EXPECT_EQ(job_load_timing.connect_timing.connect_end,
+ load_timing_result.connect_timing.connect_end);
+ EXPECT_EQ(job_load_timing.connect_timing.ssl_start,
+ load_timing_result.connect_timing.ssl_start);
+ EXPECT_EQ(job_load_timing.connect_timing.ssl_end,
+ load_timing_result.connect_timing.ssl_end);
+
+ // Redundant sanity check.
+ TestLoadTimingNotReused(load_timing_result, CONNECT_TIMING_HAS_DNS_TIMES);
+}
+
+// Another basic test, with proxy and SSL times, but no DNS times.
+TEST_F(URLRequestTest, InterceptLoadTimingProxy) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing =
+ NormalLoadTimingInfo(now, CONNECT_TIMING_HAS_SSL_TIMES, true);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Nothing should have been changed by the URLRequest.
+ EXPECT_EQ(job_load_timing.proxy_resolve_start,
+ load_timing_result.proxy_resolve_start);
+ EXPECT_EQ(job_load_timing.proxy_resolve_end,
+ load_timing_result.proxy_resolve_end);
+ EXPECT_EQ(job_load_timing.connect_timing.dns_start,
+ load_timing_result.connect_timing.dns_start);
+ EXPECT_EQ(job_load_timing.connect_timing.dns_end,
+ load_timing_result.connect_timing.dns_end);
+ EXPECT_EQ(job_load_timing.connect_timing.connect_start,
+ load_timing_result.connect_timing.connect_start);
+ EXPECT_EQ(job_load_timing.connect_timing.connect_end,
+ load_timing_result.connect_timing.connect_end);
+ EXPECT_EQ(job_load_timing.connect_timing.ssl_start,
+ load_timing_result.connect_timing.ssl_start);
+ EXPECT_EQ(job_load_timing.connect_timing.ssl_end,
+ load_timing_result.connect_timing.ssl_end);
+
+ // Redundant sanity check.
+ TestLoadTimingNotReusedWithProxy(load_timing_result,
+ CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+// Make sure that URLRequest correctly adjusts proxy times when they're before
+// |request_start|, due to already having a connected socket. This happens in
+// the case of reusing a SPDY session or HTTP pipeline. The connected socket is
+// not considered reused in this test (May be a preconnect).
+//
+// To mix things up from the test above, assumes DNS times but no SSL times.
+TEST_F(URLRequestTest, InterceptLoadTimingEarlyProxyResolution) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing =
+ NormalLoadTimingInfo(now, CONNECT_TIMING_HAS_DNS_TIMES, true);
+ job_load_timing.proxy_resolve_start = now - base::TimeDelta::FromDays(6);
+ job_load_timing.proxy_resolve_end = now - base::TimeDelta::FromDays(5);
+ job_load_timing.connect_timing.dns_start = now - base::TimeDelta::FromDays(4);
+ job_load_timing.connect_timing.dns_end = now - base::TimeDelta::FromDays(3);
+ job_load_timing.connect_timing.connect_start =
+ now - base::TimeDelta::FromDays(2);
+ job_load_timing.connect_timing.connect_end =
+ now - base::TimeDelta::FromDays(1);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Proxy times, connect times, and DNS times should all be replaced with
+ // request_start.
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.proxy_resolve_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.proxy_resolve_end);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.dns_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.dns_end);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.connect_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.connect_end);
+
+ // Other times should have been left null.
+ TestLoadTimingNotReusedWithProxy(load_timing_result,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+}
+
+// Same as above, but in the reused case.
+TEST_F(URLRequestTest, InterceptLoadTimingEarlyProxyResolutionReused) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing = NormalLoadTimingInfoReused(now, true);
+ job_load_timing.proxy_resolve_start = now - base::TimeDelta::FromDays(4);
+ job_load_timing.proxy_resolve_end = now - base::TimeDelta::FromDays(3);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Proxy times and connect times should all be replaced with request_start.
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.proxy_resolve_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.proxy_resolve_end);
+
+ // Other times should have been left null.
+ TestLoadTimingReusedWithProxy(load_timing_result);
+}
+
+// Make sure that URLRequest correctly adjusts connect times when they're before
+// |request_start|, due to reusing a connected socket. The connected socket is
+// not considered reused in this test (May be a preconnect).
+//
+// To mix things up, the request has SSL times, but no DNS times.
+TEST_F(URLRequestTest, InterceptLoadTimingEarlyConnect) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing =
+ NormalLoadTimingInfo(now, CONNECT_TIMING_HAS_SSL_TIMES, false);
+ job_load_timing.connect_timing.connect_start =
+ now - base::TimeDelta::FromDays(1);
+ job_load_timing.connect_timing.ssl_start = now - base::TimeDelta::FromDays(2);
+ job_load_timing.connect_timing.ssl_end = now - base::TimeDelta::FromDays(3);
+ job_load_timing.connect_timing.connect_end =
+ now - base::TimeDelta::FromDays(4);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Connect times, and SSL times should be replaced with request_start.
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.connect_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.ssl_start);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.ssl_end);
+ EXPECT_EQ(load_timing_result.request_start,
+ load_timing_result.connect_timing.connect_end);
+
+ // Other times should have been left null.
+ TestLoadTimingNotReused(load_timing_result, CONNECT_TIMING_HAS_SSL_TIMES);
+}
+
+// Make sure that URLRequest correctly adjusts connect times when they're before
+// |request_start|, due to reusing a connected socket in the case that there
+// are also proxy times. The connected socket is not considered reused in this
+// test (May be a preconnect).
+//
+// In this test, there are no SSL or DNS times.
+TEST_F(URLRequestTest, InterceptLoadTimingEarlyConnectWithProxy) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ LoadTimingInfo job_load_timing =
+ NormalLoadTimingInfo(now, CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY, true);
+ job_load_timing.connect_timing.connect_start =
+ now - base::TimeDelta::FromDays(1);
+ job_load_timing.connect_timing.connect_end =
+ now - base::TimeDelta::FromDays(2);
+
+ LoadTimingInfo load_timing_result = RunLoadTimingTest(job_load_timing,
+ &default_context_);
+
+ // Connect times should be replaced with proxy_resolve_end.
+ EXPECT_EQ(load_timing_result.proxy_resolve_end,
+ load_timing_result.connect_timing.connect_start);
+ EXPECT_EQ(load_timing_result.proxy_resolve_end,
+ load_timing_result.connect_timing.connect_end);
+
+ // Other times should have been left null.
+ TestLoadTimingNotReusedWithProxy(load_timing_result,
+ CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY);
+}
+
+// Check that two different URL requests have different identifiers.
+TEST_F(URLRequestTest, Identifiers) {
+ TestDelegate d;
+ TestURLRequestContext context;
+ TestURLRequest req(GURL("http://example.com"), &d, &context, NULL);
+ TestURLRequest other_req(GURL("http://example.com"), &d, &context, NULL);
+
+ ASSERT_NE(req.identifier(), other_req.identifier());
+}
+
+// Check that a failure to connect to the proxy is reported to the network
+// delegate.
+TEST_F(URLRequestTest, NetworkDelegateProxyError) {
+ MockHostResolver host_resolver;
+ host_resolver.rules()->AddSimulatedFailure("*");
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContextWithProxy context("myproxy:70", &network_delegate);
+
+ TestDelegate d;
+ URLRequest req(GURL("http://example.com"), &d, &context);
+ req.set_method("GET");
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ // Check we see a failed request.
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, req.status().error());
+
+ EXPECT_EQ(1, network_delegate.error_count());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, network_delegate.last_error());
+ EXPECT_EQ(1, network_delegate.completed_requests());
+}
+
+// Make sure that net::NetworkDelegate::NotifyCompleted is called if
+// content is empty.
+TEST_F(URLRequestTest, RequestCompletionForEmptyResponse) {
+ TestDelegate d;
+ URLRequest req(GURL("data:,"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("", d.data_received());
+ EXPECT_EQ(1, default_network_delegate_.completed_requests());
+}
+
+// Make sure that SetPriority actually sets the URLRequest's priority
+// correctly, both before and after start.
+TEST_F(URLRequestTest, SetPriorityBasic) {
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ EXPECT_EQ(DEFAULT_PRIORITY, req.priority());
+
+ req.SetPriority(LOW);
+ EXPECT_EQ(LOW, req.priority());
+
+ req.Start();
+ EXPECT_EQ(LOW, req.priority());
+
+ req.SetPriority(MEDIUM);
+ EXPECT_EQ(MEDIUM, req.priority());
+}
+
+// Make sure that URLRequest calls SetPriority on a job before calling
+// Start on it.
+TEST_F(URLRequestTest, SetJobPriorityBeforeJobStart) {
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ EXPECT_EQ(DEFAULT_PRIORITY, req.priority());
+
+ scoped_refptr<URLRequestTestJob> job =
+ new URLRequestTestJob(&req, &default_network_delegate_);
+ AddTestInterceptor()->set_main_intercept_job(job.get());
+ EXPECT_EQ(DEFAULT_PRIORITY, job->priority());
+
+ req.SetPriority(LOW);
+
+ req.Start();
+ EXPECT_EQ(LOW, job->priority());
+}
+
+// Make sure that URLRequest passes on its priority updates to its
+// job.
+TEST_F(URLRequestTest, SetJobPriority) {
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+
+ scoped_refptr<URLRequestTestJob> job =
+ new URLRequestTestJob(&req, &default_network_delegate_);
+ AddTestInterceptor()->set_main_intercept_job(job.get());
+
+ req.SetPriority(LOW);
+ req.Start();
+ EXPECT_EQ(LOW, job->priority());
+
+ req.SetPriority(MEDIUM);
+ EXPECT_EQ(MEDIUM, req.priority());
+ EXPECT_EQ(MEDIUM, job->priority());
+}
+
+// TODO(droger): Support SpawnedTestServer on iOS (see http://crbug.com/148666).
+#if !defined(OS_IOS)
+// A subclass of SpawnedTestServer that uses a statically-configured hostname.
+// This is to work around mysterious failures in chrome_frame_net_tests. See:
+// http://crbug.com/114369
+class LocalHttpTestServer : public SpawnedTestServer {
+ public:
+ explicit LocalHttpTestServer(const base::FilePath& document_root)
+ : SpawnedTestServer(SpawnedTestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ document_root) {}
+ LocalHttpTestServer()
+ : SpawnedTestServer(SpawnedTestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ base::FilePath()) {}
+};
+
+TEST_F(URLRequestTest, DelayedCookieCallback) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ TestURLRequestContext context;
+ scoped_refptr<DelayedCookieMonster> delayed_cm =
+ new DelayedCookieMonster();
+ scoped_refptr<CookieStore> cookie_store = delayed_cm;
+ context.set_cookie_store(delayed_cm.get());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ context.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSend=1"), &d, &context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ context.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("echoheader?Cookie"), &d, &context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent when LOAD_DO_NOT_SEND_COOKIES is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.set_load_flags(LOAD_DO_NOT_SEND_COOKIES);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ // LOAD_DO_NOT_SEND_COOKIES does not trigger OnGetCookies.
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.set_load_flags(LOAD_DO_NOT_SAVE_COOKIES);
+ req.Start();
+
+ base::MessageLoop::current()->Run();
+
+ // LOAD_DO_NOT_SAVE_COOKIES does not trigger OnSetCookie.
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies_ViaPolicy) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_GET_COOKIES);
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ EXPECT_EQ(1, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies_ViaPolicy) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_SET_COOKIE);
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(2, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveEmptyCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up an empty cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies_ViaPolicy_Async) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_GET_COOKIES);
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ EXPECT_EQ(1, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies_ViaPolicy_Async) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_SET_COOKIE);
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(2, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+// FixedDateNetworkDelegate swaps out the server's HTTP Date response header
+// value for the |fixed_date| argument given to the constructor.
+class FixedDateNetworkDelegate : public TestNetworkDelegate {
+ public:
+ explicit FixedDateNetworkDelegate(const std::string& fixed_date)
+ : fixed_date_(fixed_date) {}
+ virtual ~FixedDateNetworkDelegate() {}
+
+ // net::NetworkDelegate implementation
+ virtual int OnHeadersReceived(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers)
+ OVERRIDE;
+
+ private:
+ std::string fixed_date_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedDateNetworkDelegate);
+};
+
+int FixedDateNetworkDelegate::OnHeadersReceived(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers) {
+ net::HttpResponseHeaders* new_response_headers =
+ new net::HttpResponseHeaders(original_response_headers->raw_headers());
+
+ new_response_headers->RemoveHeader("Date");
+ new_response_headers->AddHeader("Date: " + fixed_date_);
+
+ *override_response_headers = new_response_headers;
+ return TestNetworkDelegate::OnHeadersReceived(request,
+ callback,
+ original_response_headers,
+ override_response_headers);
+}
+
+// Test that cookie expiration times are adjusted for server/client clock
+// skew and that we handle incorrect timezone specifier "UTC" in HTTP Date
+// headers by defaulting to GMT. (crbug.com/135131)
+TEST_F(URLRequestTest, AcceptClockSkewCookieWithWrongDateTimezone) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up an expired cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL(
+ "set-cookie?StillGood=1;expires=Mon,18-Apr-1977,22:50:13,GMT"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ }
+ // Verify that the cookie is not set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("StillGood=1") == std::string::npos);
+ }
+ // Set up a cookie with clock skew and "UTC" HTTP Date timezone specifier.
+ {
+ FixedDateNetworkDelegate network_delegate("18-Apr-1977 22:49:13 UTC");
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL(
+ "set-cookie?StillGood=1;expires=Mon,18-Apr-1977,22:50:13,GMT"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ }
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("StillGood=1") != std::string::npos);
+ }
+}
+
+
+// Check that it is impossible to change the referrer in the extra headers of
+// an URLRequest.
+TEST_F(URLRequestTest, DoNotOverrideReferrer) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // If extra headers contain referer and the request contains a referer,
+ // only the latter shall be respected.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.SetReferrer("http://foo.com/");
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kReferer, "http://bar.com/");
+ req.SetExtraRequestHeaders(headers);
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ("http://foo.com/", d.data_received());
+ }
+
+ // If extra headers contain a referer but the request does not, no referer
+ // shall be sent in the header.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Referer"), &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kReferer, "http://bar.com/");
+ req.SetExtraRequestHeaders(headers);
+ req.set_load_flags(LOAD_VALIDATE_CACHE);
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ("None", d.data_received());
+ }
+}
+
+class URLRequestTestHTTP : public URLRequestTest {
+ public:
+ URLRequestTestHTTP()
+ : test_server_(base::FilePath(FILE_PATH_LITERAL(
+ "net/data/url_request_unittest"))) {
+ }
+
+ protected:
+ // Requests |redirect_url|, which must return a HTTP 3xx redirect.
+ // |request_method| is the method to use for the initial request.
+ // |redirect_method| is the method that is expected to be used for the second
+ // request, after redirection.
+ // If |include_data| is true, data is uploaded with the request. The
+ // response body is expected to match it exactly, if and only if
+ // |request_method| == |redirect_method|.
+ void HTTPRedirectMethodTest(const GURL& redirect_url,
+ const std::string& request_method,
+ const std::string& redirect_method,
+ bool include_data) {
+ static const char kData[] = "hello world";
+ TestDelegate d;
+ URLRequest req(redirect_url, &d, &default_context_);
+ req.set_method(request_method);
+ if (include_data) {
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+ }
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(redirect_method, req.method());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req.status().status());
+ EXPECT_EQ(OK, req.status().error());
+ if (include_data) {
+ if (request_method == redirect_method) {
+ EXPECT_EQ(kData, d.data_received());
+ } else {
+ EXPECT_NE(kData, d.data_received());
+ }
+ }
+ if (HasFailure())
+ LOG(WARNING) << "Request method was: " << request_method;
+ }
+
+ void HTTPUploadDataOperationTest(const std::string& method) {
+ const int kMsgSize = 20000; // multiple of 10
+ const int kIterations = 50;
+ char* uploadBytes = new char[kMsgSize+1];
+ char* ptr = uploadBytes;
+ char marker = 'a';
+ for (int idx = 0; idx < kMsgSize/10; idx++) {
+ memcpy(ptr, "----------", 10);
+ ptr += 10;
+ if (idx % 100 == 0) {
+ ptr--;
+ *ptr++ = marker;
+ if (++marker > 'z')
+ marker = 'a';
+ }
+ }
+ uploadBytes[kMsgSize] = '\0';
+
+ for (int i = 0; i < kIterations; ++i) {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method(method.c_str());
+
+ r.set_upload(make_scoped_ptr(CreateSimpleUploadData(uploadBytes)));
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", os error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(uploadBytes, d.data_received());
+ }
+ delete[] uploadBytes;
+ }
+
+ void AddChunksToUpload(URLRequest* r) {
+ r->AppendChunkToUpload("a", 1, false);
+ r->AppendChunkToUpload("bcd", 3, false);
+ r->AppendChunkToUpload("this is a longer chunk than before.", 35, false);
+ r->AppendChunkToUpload("\r\n\r\n", 4, false);
+ r->AppendChunkToUpload("0", 1, false);
+ r->AppendChunkToUpload("2323", 4, true);
+ }
+
+ void VerifyReceivedDataMatchesChunks(URLRequest* r, TestDelegate* d) {
+ // This should match the chunks sent by AddChunksToUpload().
+ const std::string expected_data =
+ "abcdthis is a longer chunk than before.\r\n\r\n02323";
+
+ ASSERT_EQ(1, d->response_started_count())
+ << "request failed: " << r->status().status()
+ << ", os error: " << r->status().error();
+
+ EXPECT_FALSE(d->received_data_before_response());
+
+ EXPECT_EQ(expected_data.size(), static_cast<size_t>(d->bytes_received()));
+ EXPECT_EQ(expected_data, d->data_received());
+ }
+
+ bool DoManyCookiesRequest(int num_cookies) {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("set-many-cookies?" +
+ base::IntToString(num_cookies)),
+ &d,
+ &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ bool is_success = r.status().is_success();
+
+ if (!is_success) {
+ // Requests handled by ChromeFrame send a less precise error message,
+ // ERR_CONNECTION_ABORTED.
+ EXPECT_TRUE(r.status().error() == ERR_RESPONSE_HEADERS_TOO_BIG ||
+ r.status().error() == ERR_CONNECTION_ABORTED);
+ // The test server appears to be unable to handle subsequent requests
+ // after this error is triggered. Force it to restart.
+ EXPECT_TRUE(test_server_.Stop());
+ EXPECT_TRUE(test_server_.Start());
+ }
+
+ return is_success;
+ }
+
+ LocalHttpTestServer test_server_;
+};
+
+// In this unit test, we're using the HTTPTestServer as a proxy server and
+// issuing a CONNECT request with the magic host name "www.redirect.com".
+// The HTTPTestServer will return a 302 response, which we should not
+// follow.
+TEST_F(URLRequestTestHTTP, ProxyTunnelRedirectTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.redirect.com/"), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ EXPECT_EQ(1, d.response_started_count());
+ // We should not have followed the redirect.
+ EXPECT_EQ(0, d.received_redirect_count());
+ }
+}
+
+// This is the same as the previous test, but checks that the network delegate
+// registers the error.
+TEST_F(URLRequestTestHTTP, NetworkDelegateTunnelConnectionFailed) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.redirect.com/"), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ EXPECT_EQ(1, d.response_started_count());
+ // We should not have followed the redirect.
+ EXPECT_EQ(0, d.received_redirect_count());
+
+ EXPECT_EQ(1, network_delegate.error_count());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, network_delegate.last_error());
+ }
+}
+
+// Tests that we can block and asynchronously return OK in various stages.
+TEST_F(URLRequestTestHTTP, NetworkDelegateBlockAsynchronously) {
+ static const BlockingNetworkDelegate::Stage blocking_stages[] = {
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED
+ };
+ static const size_t blocking_stages_length = arraysize(blocking_stages);
+
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST |
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS |
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL("empty.html"), &d, &context);
+
+ r.Start();
+ for (size_t i = 0; i < blocking_stages_length; ++i) {
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(blocking_stages[i],
+ network_delegate.stage_blocked_for_callback());
+ network_delegate.DoCallback(OK);
+ }
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can block and cancel a request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+ network_delegate.set_retval(ERR_EMPTY_RESPONSE);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Helper function for NetworkDelegateCancelRequestAsynchronously and
+// NetworkDelegateCancelRequestSynchronously. Sets up a blocking network
+// delegate operating in |block_mode| and a request for |url|. It blocks the
+// request in |stage| and cancels it with ERR_BLOCKED_BY_CLIENT.
+void NetworkDelegateCancelRequest(BlockingNetworkDelegate::BlockMode block_mode,
+ BlockingNetworkDelegate::Stage stage,
+ const GURL& url) {
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(block_mode);
+ network_delegate.set_retval(ERR_BLOCKED_BY_CLIENT);
+ network_delegate.set_block_on(stage);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(url, &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_BLOCKED_BY_CLIENT, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// The following 3 tests check that the network delegate can cancel a request
+// synchronously in various stages of the request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously1) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ test_server_.GetURL(std::string()));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously2) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ test_server_.GetURL(std::string()));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously3) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ test_server_.GetURL(std::string()));
+}
+
+// The following 3 tests check that the network delegate can cancel a request
+// asynchronously in various stages of the request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously1) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ test_server_.GetURL(std::string()));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously2) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ test_server_.GetURL(std::string()));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously3) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ test_server_.GetURL(std::string()));
+}
+
+// Tests that the network delegate can block and redirect a request to a new
+// URL.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+ GURL redirect_url(test_server_.GetURL("simple.html"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ GURL original_url(test_server_.GetURL("empty.html"));
+ URLRequest r(original_url, &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(redirect_url, r.url());
+ EXPECT_EQ(original_url, r.original_url());
+ EXPECT_EQ(2U, r.url_chain().size());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can block and redirect a request to a new
+// URL by setting a redirect_url and returning in OnBeforeURLRequest directly.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestSynchronously) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ GURL redirect_url(test_server_.GetURL("simple.html"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ GURL original_url(test_server_.GetURL("empty.html"));
+ URLRequest r(original_url, &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(redirect_url, r.url());
+ EXPECT_EQ(original_url, r.original_url());
+ EXPECT_EQ(2U, r.url_chain().size());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that redirects caused by the network delegate preserve POST data.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestPost) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+ GURL redirect_url(test_server_.GetURL("echo"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL original_url(test_server_.GetURL("empty.html"));
+ URLRequest r(original_url, &d, &context);
+ r.set_method("POST");
+ r.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(redirect_url, r.url());
+ EXPECT_EQ(original_url, r.original_url());
+ EXPECT_EQ(2U, r.url_chain().size());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ EXPECT_EQ("POST", r.method());
+ EXPECT_EQ(kData, d.data_received());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can synchronously complete OnAuthRequired
+// by taking no action. This indicates that the NetworkDelegate does not want to
+// handle the challenge, and is passing the buck along to the
+// URLRequest::Delegate.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncNoAction) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_TRUE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+TEST_F(URLRequestTestHTTP,
+ NetworkDelegateOnAuthRequiredSyncNoAction_GetFullRequestHeaders) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+
+ {
+ HttpRequestHeaders headers;
+ EXPECT_TRUE(r.GetFullRequestHeaders(&headers));
+ EXPECT_FALSE(headers.HasHeader("Authorization"));
+ }
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_TRUE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can synchronously complete OnAuthRequired
+// by setting credentials.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncSetAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
+
+ network_delegate.set_auth_credentials(AuthCredentials(kUser, kSecret));
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Same as above, but also tests that GetFullRequestHeaders returns the proper
+// headers (for the first or second request) when called at the proper times.
+TEST_F(URLRequestTestHTTP,
+ NetworkDelegateOnAuthRequiredSyncSetAuth_GetFullRequestHeaders) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
+
+ network_delegate.set_auth_credentials(AuthCredentials(kUser, kSecret));
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+
+ {
+ HttpRequestHeaders headers;
+ EXPECT_TRUE(r.GetFullRequestHeaders(&headers));
+ EXPECT_TRUE(headers.HasHeader("Authorization"));
+ }
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can synchronously complete OnAuthRequired
+// by cancelling authentication.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncCancel) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(OK, r.status().error());
+ EXPECT_EQ(401, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously complete OnAuthRequired
+// by taking no action. This indicates that the NetworkDelegate does not want
+// to handle the challenge, and is passing the buck along to the
+// URLRequest::Delegate.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncNoAction) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_TRUE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously complete OnAuthRequired
+// by setting credentials.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncSetAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
+
+ AuthCredentials auth_credentials(kUser, kSecret);
+ network_delegate.set_auth_credentials(auth_credentials);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously complete OnAuthRequired
+// by cancelling authentication.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncCancel) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(OK, r.status().error());
+ EXPECT_EQ(401, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 1: Request is cancelled while waiting for OnBeforeURLRequest callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting1) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 2: Request is cancelled while waiting for OnBeforeSendHeaders callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting2) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 3: Request is cancelled while waiting for OnHeadersReceived callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting3) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_HEADERS_RECEIVED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 4: Request is cancelled while waiting for OnAuthRequired callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting4) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &context);
+
+ r.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_AUTH_REQUIRED,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// In this unit test, we're using the HTTPTestServer as a proxy server and
+// issuing a CONNECT request with the magic host name "www.server-auth.com".
+// The HTTPTestServer will return a 401 response, which we should balk at.
+TEST_F(URLRequestTestHTTP, UnexpectedServerAuthTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.server-auth.com/"), &d, &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetTest_NoCache) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+
+ // TODO(eroman): Add back the NetLog tests...
+ }
+}
+
+// This test has the server send a large number of cookies to the client.
+// To ensure that no number of cookies causes a crash, a galloping binary
+// search is used to estimate that maximum number of cookies that are accepted
+// by the browser. Beyond the maximum number, the request will fail with
+// ERR_RESPONSE_HEADERS_TOO_BIG.
+#if defined(OS_WIN)
+// http://crbug.com/177916
+#define MAYBE_GetTest_ManyCookies DISABLED_GetTest_ManyCookies
+#else
+#define MAYBE_GetTest_ManyCookies GetTest_ManyCookies
+#endif // defined(OS_WIN)
+TEST_F(URLRequestTestHTTP, MAYBE_GetTest_ManyCookies) {
+ ASSERT_TRUE(test_server_.Start());
+
+ int lower_bound = 0;
+ int upper_bound = 1;
+
+ // Double the number of cookies until the response header limits are
+ // exceeded.
+ while (DoManyCookiesRequest(upper_bound)) {
+ lower_bound = upper_bound;
+ upper_bound *= 2;
+ ASSERT_LT(upper_bound, 1000000);
+ }
+
+ int tolerance = upper_bound * 0.005;
+ if (tolerance < 2)
+ tolerance = 2;
+
+ // Perform a binary search to find the highest possible number of cookies,
+ // within the desired tolerance.
+ while (upper_bound - lower_bound >= tolerance) {
+ int num_cookies = (lower_bound + upper_bound) / 2;
+
+ if (DoManyCookiesRequest(num_cookies))
+ lower_bound = num_cookies;
+ else
+ upper_bound = num_cookies;
+ }
+ // Success: the test did not crash.
+}
+
+TEST_F(URLRequestTestHTTP, GetTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetTest_GetFullRequestHeaders) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ GURL test_url(test_server_.GetURL(std::string()));
+ URLRequest r(test_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ EXPECT_FALSE(r.GetFullRequestHeaders(&headers));
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+
+ EXPECT_TRUE(d.have_full_request_headers());
+ CheckFullRequestHeaders(d.full_request_headers(), test_url);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetTestLoadTiming) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ LoadTimingInfo load_timing_info;
+ r.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetZippedTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // Parameter that specifies the Content-Length field in the response:
+ // C - Compressed length.
+ // U - Uncompressed length.
+ // L - Large length (larger than both C & U).
+ // M - Medium length (between C & U).
+ // S - Small length (smaller than both C & U).
+ const char test_parameters[] = "CULMS";
+ const int num_tests = arraysize(test_parameters)- 1; // Skip NULL.
+ // C & U should be OK.
+ // L & M are larger than the data sent, and show an error.
+ // S has too little data, but we seem to accept it.
+ const bool test_expect_success[num_tests] =
+ { true, true, false, false, true };
+
+ for (int i = 0; i < num_tests ; i++) {
+ TestDelegate d;
+ {
+ std::string test_file =
+ base::StringPrintf("compressedfiles/BullRunSpeech.txt?%c",
+ test_parameters[i]);
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ URLRequest r(test_server_.GetURL(test_file), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ VLOG(1) << " Received " << d.bytes_received() << " bytes"
+ << " status = " << r.status().status()
+ << " error = " << r.status().error();
+ if (test_expect_success[i]) {
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status())
+ << " Parameter = \"" << test_file << "\"";
+ } else {
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, r.status().error())
+ << " Parameter = \"" << test_file << "\"";
+ }
+ }
+ }
+}
+
+TEST_F(URLRequestTestHTTP, HTTPSToHTTPRedirectNoRefererTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS, SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ // An https server is sent a request with an https referer,
+ // and responds with a redirect to an http url. The http
+ // server should not be sent the referer.
+ GURL http_destination = test_server_.GetURL(std::string());
+ TestDelegate d;
+ URLRequest req(https_test_server.GetURL(
+ "server-redirect?" + http_destination.spec()), &d, &default_context_);
+ req.SetReferrer("https://www.referrer.com/");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_EQ(http_destination, req.url());
+ EXPECT_EQ(std::string(), req.referrer());
+}
+
+TEST_F(URLRequestTestHTTP, RedirectLoadTiming) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(std::string());
+ GURL original_url =
+ test_server_.GetURL("server-redirect?" + destination_url.spec());
+ TestDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_EQ(destination_url, req.url());
+ EXPECT_EQ(original_url, req.original_url());
+ ASSERT_EQ(2U, req.url_chain().size());
+ EXPECT_EQ(original_url, req.url_chain()[0]);
+ EXPECT_EQ(destination_url, req.url_chain()[1]);
+
+ LoadTimingInfo load_timing_info_before_redirect;
+ EXPECT_TRUE(default_network_delegate_.GetLoadTimingInfoBeforeRedirect(
+ &load_timing_info_before_redirect));
+ TestLoadTimingNotReused(load_timing_info_before_redirect,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+
+ LoadTimingInfo load_timing_info;
+ req.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+
+ // Check that a new socket was used on redirect, since the server does not
+ // supposed keep-alive sockets, and that the times before the redirect are
+ // before the ones recorded for the second request.
+ EXPECT_NE(load_timing_info_before_redirect.socket_log_id,
+ load_timing_info.socket_log_id);
+ EXPECT_LE(load_timing_info_before_redirect.receive_headers_end,
+ load_timing_info.connect_timing.connect_start);
+}
+
+TEST_F(URLRequestTestHTTP, MultipleRedirectTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(std::string());
+ GURL middle_redirect_url =
+ test_server_.GetURL("server-redirect?" + destination_url.spec());
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + middle_redirect_url.spec());
+ TestDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(2, d.received_redirect_count());
+ EXPECT_EQ(destination_url, req.url());
+ EXPECT_EQ(original_url, req.original_url());
+ ASSERT_EQ(3U, req.url_chain().size());
+ EXPECT_EQ(original_url, req.url_chain()[0]);
+ EXPECT_EQ(middle_redirect_url, req.url_chain()[1]);
+ EXPECT_EQ(destination_url, req.url_chain()[2]);
+}
+
+namespace {
+
+const char kExtraHeader[] = "Allow-Snafu";
+const char kExtraValue[] = "fubar";
+
+class RedirectWithAdditionalHeadersDelegate : public TestDelegate {
+ virtual void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE {
+ TestDelegate::OnReceivedRedirect(request, new_url, defer_redirect);
+ request->SetExtraRequestHeaderByName(kExtraHeader, kExtraValue, false);
+ }
+};
+
+} // namespace
+
+TEST_F(URLRequestTestHTTP, RedirectWithAdditionalHeadersTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(
+ "echoheader?" + std::string(kExtraHeader));
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + destination_url.spec());
+ RedirectWithAdditionalHeadersDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ std::string value;
+ const HttpRequestHeaders& headers = req.extra_request_headers();
+ EXPECT_TRUE(headers.GetHeader(kExtraHeader, &value));
+ EXPECT_EQ(kExtraValue, value);
+ EXPECT_FALSE(req.is_pending());
+ EXPECT_FALSE(req.is_redirecting());
+ EXPECT_EQ(kExtraValue, d.data_received());
+}
+
+namespace {
+
+const char kExtraHeaderToRemove[] = "To-Be-Removed";
+
+class RedirectWithHeaderRemovalDelegate : public TestDelegate {
+ virtual void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE {
+ TestDelegate::OnReceivedRedirect(request, new_url, defer_redirect);
+ request->RemoveRequestHeaderByName(kExtraHeaderToRemove);
+ }
+};
+
+} // namespace
+
+TEST_F(URLRequestTestHTTP, RedirectWithHeaderRemovalTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(
+ "echoheader?" + std::string(kExtraHeaderToRemove));
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + destination_url.spec());
+ RedirectWithHeaderRemovalDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.SetExtraRequestHeaderByName(kExtraHeaderToRemove, "dummy", false);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ std::string value;
+ const HttpRequestHeaders& headers = req.extra_request_headers();
+ EXPECT_FALSE(headers.GetHeader(kExtraHeaderToRemove, &value));
+ EXPECT_FALSE(req.is_pending());
+ EXPECT_FALSE(req.is_redirecting());
+ EXPECT_EQ("None", d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("http://www.google.com/"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ r.Cancel();
+
+ base::MessageLoop::current()->Run();
+
+ // We expect to receive OnResponseStarted even though the request has been
+ // cancelled.
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest2) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ d.set_cancel_in_response_started(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest3) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ d.set_cancel_in_received_data(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ // There is no guarantee about how much data was received
+ // before the cancel was issued. It could have been 0 bytes,
+ // or it could have been all the bytes.
+ // EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest4) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ // The request will be implicitly canceled when it is destroyed. The
+ // test delegate must not post a quit message when this happens because
+ // this test doesn't actually have a message loop. The quit message would
+ // get put on this thread's message queue and the next test would exit
+ // early, causing problems.
+ d.set_quit_on_complete(false);
+ }
+ // expect things to just cleanup properly.
+
+ // we won't actually get a received reponse here because we've never run the
+ // message loop
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(0, d.bytes_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest5) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate cache
+ {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("cachetime"), &d, &default_context_);
+ r.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ }
+
+ // cancel read from cache (see bug 990242)
+ {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("cachetime"), &d, &default_context_);
+ r.Start();
+ r.Cancel();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, PostTest) {
+ ASSERT_TRUE(test_server_.Start());
+ HTTPUploadDataOperationTest("POST");
+}
+
+TEST_F(URLRequestTestHTTP, PutTest) {
+ ASSERT_TRUE(test_server_.Start());
+ HTTPUploadDataOperationTest("PUT");
+}
+
+TEST_F(URLRequestTestHTTP, PostEmptyTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method("POST");
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.data_received().empty());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, PostFileTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method("POST");
+
+ base::FilePath dir;
+ PathService::Get(base::DIR_EXE, &dir);
+ file_util::SetCurrentDirectory(dir);
+
+ ScopedVector<UploadElementReader> element_readers;
+
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path = path.Append(FILE_PATH_LITERAL("with-headers.html"));
+ element_readers.push_back(
+ new UploadFileElementReader(base::MessageLoopProxy::current().get(),
+ path,
+ 0,
+ kuint64max,
+ base::Time()));
+
+ // This file should just be ignored in the upload stream.
+ element_readers.push_back(new UploadFileElementReader(
+ base::MessageLoopProxy::current().get(),
+ base::FilePath(FILE_PATH_LITERAL(
+ "c:\\path\\to\\non\\existant\\file.randomness.12345")),
+ 0,
+ kuint64max,
+ base::Time()));
+ r.set_upload(make_scoped_ptr(new UploadDataStream(&element_readers, 0)));
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 size = 0;
+ ASSERT_EQ(true, file_util::GetFileSize(path, &size));
+ scoped_ptr<char[]> buf(new char[size]);
+
+ ASSERT_EQ(size, file_util::ReadFile(path, buf.get(), size));
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+
+ EXPECT_EQ(size, d.bytes_received());
+ EXPECT_EQ(std::string(&buf[0], size), d.data_received());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataBeforeStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ AddChunksToUpload(&r);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataJustAfterStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+ AddChunksToUpload(&r);
+ base::MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataAfterStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->RunUntilIdle();
+ AddChunksToUpload(&r);
+ base::MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, ResponseHeadersTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("files/with-headers.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ const HttpResponseHeaders* headers = req.response_headers();
+
+ // Simple sanity check that response_info() accesses the same data.
+ EXPECT_EQ(headers, req.response_info().headers.get());
+
+ std::string header;
+ EXPECT_TRUE(headers->GetNormalizedHeader("cache-control", &header));
+ EXPECT_EQ("private", header);
+
+ header.clear();
+ EXPECT_TRUE(headers->GetNormalizedHeader("content-type", &header));
+ EXPECT_EQ("text/html; charset=ISO-8859-1", header);
+
+ // The response has two "X-Multiple-Entries" headers.
+ // This verfies our output has them concatenated together.
+ header.clear();
+ EXPECT_TRUE(headers->GetNormalizedHeader("x-multiple-entries", &header));
+ EXPECT_EQ("a, b", header);
+}
+
+TEST_F(URLRequestTestHTTP, ProcessSTS) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+ EXPECT_TRUE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+#if defined(OS_ANDROID)
+ // Android's CertVerifyProc does not (yet) handle pins.
+#else
+ EXPECT_FALSE(domain_state.HasPublicKeyPins());
+#endif
+}
+
+// Android's CertVerifyProc does not (yet) handle pins. Therefore, it will
+// reject HPKP headers, and a test setting only HPKP headers will fail (no
+// DomainState present because header rejected).
+#if defined(OS_ANDROID)
+#define MAYBE_ProcessPKP DISABLED_ProcessPKP
+#else
+#define MAYBE_ProcessPKP ProcessPKP
+#endif
+
+// Tests that enabling HPKP on a domain does not affect the HSTS
+// validity/expiration.
+TEST_F(URLRequestTestHTTP, MAYBE_ProcessPKP) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hpkp-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_DEFAULT,
+ domain_state.upgrade_mode);
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+ EXPECT_NE(domain_state.upgrade_expiry,
+ domain_state.dynamic_spki_hashes_expiry);
+}
+
+TEST_F(URLRequestTestHTTP, ProcessSTSOnce) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-multiple-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ // We should have set parameters from the first header, not the second.
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+}
+
+TEST_F(URLRequestTestHTTP, ProcessSTSAndPKP) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-and-hpkp-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ // We should have set parameters from the first header, not the second.
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+#if defined(OS_ANDROID)
+ // Android's CertVerifyProc does not (yet) handle pins.
+#else
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+#endif
+ EXPECT_NE(domain_state.upgrade_expiry,
+ domain_state.dynamic_spki_hashes_expiry);
+
+ // Even though there is an HSTS header asserting includeSubdomains, it is
+ // the *second* such header, and we MUST process only the first.
+ EXPECT_FALSE(domain_state.sts_include_subdomains);
+ // includeSubdomains does not occur in the test HPKP header.
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+}
+
+// Tests that when multiple HPKP headers are present, asserting different
+// policies, that only the first such policy is processed.
+TEST_F(URLRequestTestHTTP, ProcessSTSAndPKP2) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ SpawnedTestServer https_test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-and-hpkp-headers2.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ base::MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ SpawnedTestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+#if defined(OS_ANDROID)
+ // Android's CertVerifyProc does not (yet) handle pins.
+#else
+ EXPECT_TRUE(domain_state.HasPublicKeyPins());
+#endif
+ EXPECT_NE(domain_state.upgrade_expiry,
+ domain_state.dynamic_spki_hashes_expiry);
+
+ EXPECT_TRUE(domain_state.sts_include_subdomains);
+ EXPECT_FALSE(domain_state.pkp_include_subdomains);
+}
+
+TEST_F(URLRequestTestHTTP, ContentTypeNormalizationTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/content-type-normalization.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ std::string mime_type;
+ req.GetMimeType(&mime_type);
+ EXPECT_EQ("text/html", mime_type);
+
+ std::string charset;
+ req.GetCharset(&charset);
+ EXPECT_EQ("utf-8", charset);
+ req.Cancel();
+}
+
+TEST_F(URLRequestTestHTTP, ProtocolHandlerAndFactoryRestrictRedirects) {
+ // Test URLRequestJobFactory::ProtocolHandler::IsSafeRedirectTarget().
+ GURL file_url("file:///foo.txt");
+ GURL data_url("data:,foo");
+ FileProtocolHandler file_protocol_handler;
+ EXPECT_FALSE(file_protocol_handler.IsSafeRedirectTarget(file_url));
+ DataProtocolHandler data_protocol_handler;
+ EXPECT_TRUE(data_protocol_handler.IsSafeRedirectTarget(data_url));
+
+ // Test URLRequestJobFactoryImpl::IsSafeRedirectTarget().
+ EXPECT_FALSE(job_factory_.IsSafeRedirectTarget(file_url));
+ EXPECT_TRUE(job_factory_.IsSafeRedirectTarget(data_url));
+}
+
+TEST_F(URLRequestTestHTTP, RestrictRedirects) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/redirect-to-file.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_UNSAFE_REDIRECT, req.status().error());
+}
+
+TEST_F(URLRequestTestHTTP, RedirectToInvalidURL) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/redirect-to-invalid-url.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_INVALID_URL, req.status().error());
+}
+
+TEST_F(URLRequestTestHTTP, NoUserPassInReferrer) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.SetReferrer("http://user:pass@foo.com/");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(std::string("http://foo.com/"), d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, NoFragmentInReferrer) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.SetReferrer("http://foo.com/test#fragment");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(std::string("http://foo.com/test"), d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, EmptyReferrerAfterValidReferrer) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.SetReferrer("http://foo.com/test#fragment");
+ req.SetReferrer("");
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(std::string("None"), d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_cancel_in_received_redirect(true);
+ URLRequest req(
+ test_server_.GetURL("files/redirect-test.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, DeferredRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_quit_on_redirect(true);
+ GURL test_url(test_server_.GetURL("files/redirect-test.html"));
+ URLRequest req(test_url, &d, &default_context_);
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ req.FollowDeferredRedirect();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req.status().status());
+
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path = path.Append(FILE_PATH_LITERAL("with-headers.html"));
+
+ std::string contents;
+ EXPECT_TRUE(file_util::ReadFileToString(path, &contents));
+ EXPECT_EQ(contents, d.data_received());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, DeferredRedirect_GetFullRequestHeaders) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_quit_on_redirect(true);
+ GURL test_url(test_server_.GetURL("files/redirect-test.html"));
+ URLRequest req(test_url, &d, &default_context_);
+
+ EXPECT_FALSE(d.have_full_request_headers());
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_TRUE(d.have_full_request_headers());
+ CheckFullRequestHeaders(d.full_request_headers(), test_url);
+ d.ClearFullRequestHeaders();
+
+ req.FollowDeferredRedirect();
+ base::MessageLoop::current()->Run();
+
+ GURL target_url(test_server_.GetURL("files/with-headers.html"));
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_TRUE(d.have_full_request_headers());
+ CheckFullRequestHeaders(d.full_request_headers(), target_url);
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req.status().status());
+
+ base::FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path = path.Append(FILE_PATH_LITERAL("with-headers.html"));
+
+ std::string contents;
+ EXPECT_TRUE(file_util::ReadFileToString(path, &contents));
+ EXPECT_EQ(contents, d.data_received());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelDeferredRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_quit_on_redirect(true);
+ URLRequest req(
+ test_server_.GetURL("files/redirect-test.html"), &d, &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ req.Cancel();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, VaryHeader) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // Populate the cache.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ LoadTimingInfo load_timing_info;
+ req.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ }
+
+ // Expect a cache hit.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(req.was_cached());
+
+ LoadTimingInfo load_timing_info;
+ req.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingCacheHitNoNetwork(load_timing_info);
+ }
+
+ // Expect a cache miss.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "2");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_FALSE(req.was_cached());
+
+ LoadTimingInfo load_timing_info;
+ req.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, BasicAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate the cache
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+ }
+
+ // repeat request with end-to-end validation. since auth-basic results in a
+ // cachable page, we expect this test to result in a 304. in which case, the
+ // response should be fetched from the cache.
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.set_load_flags(LOAD_VALIDATE_CACHE);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Should be the same cached document.
+ EXPECT_TRUE(r.was_cached());
+ }
+}
+
+// Check that Set-Cookie headers in 401 responses are respected.
+// http://crbug.com/6450
+TEST_F(URLRequestTestHTTP, BasicAuthWithCookies) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL url_requiring_auth =
+ test_server_.GetURL("auth-basic?set-cookie-if-challenged");
+
+ // Request a page that will give a 401 containing a Set-Cookie header.
+ // Verify that when the transaction is restarted, it includes the new cookie.
+ {
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(url_requiring_auth, &d, &context);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+
+ // Same test as above, except this time the restart is initiated earlier
+ // (without user intervention since identity is embedded in the URL).
+ {
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+
+ GURL::Replacements replacements;
+ std::string username("user2");
+ std::string password("secret");
+ replacements.SetUsernameStr(username);
+ replacements.SetPasswordStr(password);
+ GURL url_with_identity = url_requiring_auth.ReplaceComponents(replacements);
+
+ URLRequest r(url_with_identity, &d, &context);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user2/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+}
+
+// Tests that load timing works as expected with auth and the cache.
+TEST_F(URLRequestTestHTTP, BasicAuthLoadTiming) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate the cache
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ LoadTimingInfo load_timing_info_before_auth;
+ EXPECT_TRUE(default_network_delegate_.GetLoadTimingInfoBeforeAuth(
+ &load_timing_info_before_auth));
+ TestLoadTimingNotReused(load_timing_info_before_auth,
+ CONNECT_TIMING_HAS_DNS_TIMES);
+
+ LoadTimingInfo load_timing_info;
+ r.GetLoadTimingInfo(&load_timing_info);
+ // The test server does not support keep alive sockets, so the second
+ // request with auth should use a new socket.
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ EXPECT_NE(load_timing_info_before_auth.socket_log_id,
+ load_timing_info.socket_log_id);
+ EXPECT_LE(load_timing_info_before_auth.receive_headers_end,
+ load_timing_info.connect_timing.connect_start);
+ }
+
+ // Repeat request with end-to-end validation. Since auth-basic results in a
+ // cachable page, we expect this test to result in a 304. In which case, the
+ // response should be fetched from the cache.
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.set_load_flags(LOAD_VALIDATE_CACHE);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Should be the same cached document.
+ EXPECT_TRUE(r.was_cached());
+
+ // Since there was a request that went over the wire, the load timing
+ // information should include connection times.
+ LoadTimingInfo load_timing_info;
+ r.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNotReused(load_timing_info, CONNECT_TIMING_HAS_DNS_TIMES);
+ }
+}
+
+// In this test, we do a POST which the server will 302 redirect.
+// The subsequent transaction should use GET, and should not send the
+// Content-Type header.
+// http://code.google.com/p/chromium/issues/detail?id=843
+TEST_F(URLRequestTestHTTP, Post302RedirectGet) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("files/redirect-to-echoall"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+
+ // Set headers (some of which are specific to the POST).
+ HttpRequestHeaders headers;
+ headers.AddHeadersFromString(
+ "Content-Type: multipart/form-data; "
+ "boundary=----WebKitFormBoundaryAADeAA+NAAWMAAwZ\r\n"
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,"
+ "text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
+ "Accept-Language: en-US,en\r\n"
+ "Accept-Charset: ISO-8859-1,*,utf-8\r\n"
+ "Content-Length: 11\r\n"
+ "Origin: http://localhost:1337/");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ std::string mime_type;
+ req.GetMimeType(&mime_type);
+ EXPECT_EQ("text/html", mime_type);
+
+ const std::string& data = d.data_received();
+
+ // Check that the post-specific headers were stripped:
+ EXPECT_FALSE(ContainsString(data, "Content-Length:"));
+ EXPECT_FALSE(ContainsString(data, "Content-Type:"));
+ EXPECT_FALSE(ContainsString(data, "Origin:"));
+
+ // These extra request headers should not have been stripped.
+ EXPECT_TRUE(ContainsString(data, "Accept:"));
+ EXPECT_TRUE(ContainsString(data, "Accept-Language:"));
+ EXPECT_TRUE(ContainsString(data, "Accept-Charset:"));
+}
+
+// The following tests check that we handle mutating the request method for
+// HTTP redirects as expected.
+// See http://crbug.com/56373 and http://crbug.com/102130.
+
+TEST_F(URLRequestTestHTTP, Redirect301Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect301-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect302Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect302-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect303Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect303-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "GET", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect307Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect307-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "POST", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, InterceptPost302RedirectGet) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("empty.html"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ &req, &default_network_delegate_, test_server_.GetURL("echo"),
+ URLRequestRedirectJob::REDIRECT_302_FOUND);
+ AddTestInterceptor()->set_main_intercept_job(job);
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("GET", req.method());
+}
+
+TEST_F(URLRequestTestHTTP, InterceptPost307RedirectPost) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("empty.html"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ &req, &default_network_delegate_, test_server_.GetURL("echo"),
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ AddTestInterceptor()->set_main_intercept_job(job);
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("POST", req.method());
+ EXPECT_EQ(kData, d.data_received());
+}
+
+// Check that default A-L header is sent.
+TEST_F(URLRequestTestHTTP, DefaultAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings("en", EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_http_user_agent_settings(&settings);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Accept-Language"), &d, &context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("en", d.data_received());
+}
+
+// Check that an empty A-L header is not sent. http://crbug.com/77365.
+TEST_F(URLRequestTestHTTP, EmptyAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings(EmptyString(), EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+ // We override the language after initialization because empty entries
+ // get overridden by Init().
+ context.set_http_user_agent_settings(&settings);
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Accept-Language"), &d, &context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ("None", d.data_received());
+}
+
+// Check that if request overrides the A-L header, the default is not appended.
+// See http://crbug.com/20894
+TEST_F(URLRequestTestHTTP, OverrideAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Language"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptLanguage, "ru");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(std::string("ru"), d.data_received());
+}
+
+// Check that default A-E header is sent.
+TEST_F(URLRequestTestHTTP, DefaultAcceptEncoding) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Encoding"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_TRUE(ContainsString(d.data_received(), "gzip"));
+}
+
+// Check that if request overrides the A-E header, the default is not appended.
+// See http://crbug.com/47381
+TEST_F(URLRequestTestHTTP, OverrideAcceptEncoding) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Encoding"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptEncoding, "identity");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_FALSE(ContainsString(d.data_received(), "gzip"));
+ EXPECT_TRUE(ContainsString(d.data_received(), "identity"));
+}
+
+// Check that setting the A-C header sends the proper header.
+TEST_F(URLRequestTestHTTP, SetAcceptCharset) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Charset"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptCharset, "koi-8r");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(std::string("koi-8r"), d.data_received());
+}
+
+// Check that default User-Agent header is sent.
+TEST_F(URLRequestTestHTTP, DefaultUserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?User-Agent"),
+ &d,
+ &default_context_);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(req.context()->GetUserAgent(req.url()), d.data_received());
+}
+
+// Check that if request overrides the User-Agent header,
+// the default is not appended.
+TEST_F(URLRequestTestHTTP, OverrideUserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?User-Agent"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kUserAgent, "Lynx (textmode)");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ // If the net tests are being run with ChromeFrame then we need to allow for
+ // the 'chromeframe' suffix which is added to the user agent before the
+ // closing parentheses.
+ EXPECT_TRUE(StartsWithASCII(d.data_received(), "Lynx (textmode", true));
+}
+
+// Check that a NULL HttpUserAgentSettings causes the corresponding empty
+// User-Agent header to be sent but does not send the Accept-Language and
+// Accept-Charset headers.
+TEST_F(URLRequestTestHTTP, EmptyHttpUserAgentSettings) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+ // We override the HttpUserAgentSettings after initialization because empty
+ // entries get overridden by Init().
+ context.set_http_user_agent_settings(NULL);
+
+ struct {
+ const char* request;
+ const char* expected_response;
+ } tests[] = { { "echoheader?Accept-Language", "None" },
+ { "echoheader?Accept-Charset", "None" },
+ { "echoheader?User-Agent", "" } };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); i++) {
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(tests[i].request), &d, &context);
+ req.Start();
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(tests[i].expected_response, d.data_received())
+ << " Request = \"" << tests[i].request << "\"";
+ }
+}
+
+// Make sure that URLRequest passes on its priority updates to
+// newly-created jobs after the first one.
+TEST_F(URLRequestTestHTTP, SetSubsequentJobPriority) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("empty.html"), &d, &default_context_);
+ EXPECT_EQ(DEFAULT_PRIORITY, req.priority());
+
+ scoped_refptr<URLRequestRedirectJob> redirect_job =
+ new URLRequestRedirectJob(
+ &req, &default_network_delegate_, test_server_.GetURL("echo"),
+ URLRequestRedirectJob::REDIRECT_302_FOUND);
+ AddTestInterceptor()->set_main_intercept_job(redirect_job.get());
+
+ req.SetPriority(LOW);
+ req.Start();
+ EXPECT_TRUE(req.is_pending());
+
+ scoped_refptr<URLRequestTestJob> job =
+ new URLRequestTestJob(&req, &default_network_delegate_);
+ AddTestInterceptor()->set_main_intercept_job(job.get());
+
+ // Should trigger |job| to be started.
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(LOW, job->priority());
+}
+
+class HTTPSRequestTest : public testing::Test {
+ public:
+ HTTPSRequestTest() : default_context_(true) {
+ default_context_.set_network_delegate(&default_network_delegate_);
+ default_context_.Init();
+ }
+ virtual ~HTTPSRequestTest() {}
+
+ protected:
+ TestNetworkDelegate default_network_delegate_; // Must outlive URLRequest.
+ TestURLRequestContext default_context_;
+};
+
+TEST_F(HTTPSRequestTest, HTTPSGetTest) {
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ SpawnedTestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server.GetURL(std::string()), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ EXPECT_EQ(test_server.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(HTTPSRequestTest, HTTPSMismatchedTest) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ bool err_allowed = true;
+ for (int i = 0; i < 2 ; i++, err_allowed = !err_allowed) {
+ TestDelegate d;
+ {
+ d.set_allow_certificate_errors(err_allowed);
+ URLRequest r(test_server.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ if (err_allowed) {
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ } else {
+ EXPECT_EQ(0, d.bytes_received());
+ }
+ }
+ }
+}
+
+TEST_F(HTTPSRequestTest, HTTPSExpiredTest) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_EXPIRED);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // Iterate from false to true, just so that we do the opposite of the
+ // previous test in order to increase test coverage.
+ bool err_allowed = false;
+ for (int i = 0; i < 2 ; i++, err_allowed = !err_allowed) {
+ TestDelegate d;
+ {
+ d.set_allow_certificate_errors(err_allowed);
+ URLRequest r(test_server.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ if (err_allowed) {
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ } else {
+ EXPECT_EQ(0, d.bytes_received());
+ }
+ }
+ }
+}
+
+// Tests TLSv1.1 -> TLSv1 fallback. Verifies that we don't fall back more
+// than necessary.
+TEST_F(HTTPSRequestTest, TLSv1Fallback) {
+ uint16 default_version_max = SSLConfigService::default_version_max();
+ // The OpenSSL library in use may not support TLS 1.1.
+#if !defined(USE_OPENSSL)
+ EXPECT_GT(default_version_max, SSL_PROTOCOL_VERSION_TLS1);
+#endif
+ if (default_version_max <= SSL_PROTOCOL_VERSION_TLS1)
+ return;
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant =
+ SpawnedTestServer::SSLOptions::TLS_INTOLERANT_TLS1_1;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ TestURLRequestContext context(true);
+ context.Init();
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(std::string()), &d, &context);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(static_cast<int>(SSL_CONNECTION_VERSION_TLS1),
+ SSLConnectionStatusToVersion(r.ssl_info().connection_status));
+ EXPECT_TRUE(r.ssl_info().connection_status & SSL_CONNECTION_VERSION_FALLBACK);
+}
+
+// This tests that a load of www.google.com with a certificate error sets
+// the |certificate_errors_are_fatal| flag correctly. This flag will cause
+// the interstitial to be fatal.
+TEST_F(HTTPSRequestTest, HTTPSPreloadedHSTSTest) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // We require that the URL be www.google.com in order to pick up the
+ // preloaded HSTS entries in the TransportSecurityState. This means that we
+ // have to use a MockHostResolver in order to direct www.google.com to the
+ // testserver. By default, MockHostResolver maps all hosts to 127.0.0.1.
+
+ MockHostResolver host_resolver;
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_host_resolver(&host_resolver);
+ TransportSecurityState transport_security_state;
+ context.set_transport_security_state(&transport_security_state);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest r(GURL(base::StringPrintf("https://www.google.com:%d",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ EXPECT_TRUE(d.certificate_errors_are_fatal());
+}
+
+// This tests that cached HTTPS page loads do not cause any updates to the
+// TransportSecurityState.
+TEST_F(HTTPSRequestTest, HTTPSErrorsNoClobberTSSTest) {
+ // The actual problem -- CERT_MISMATCHED_NAME in this case -- doesn't
+ // matter. It just has to be any error.
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // We require that the URL be www.google.com in order to pick up the
+ // preloaded and dynamic HSTS and public key pin entries in the
+ // TransportSecurityState. This means that we have to use a
+ // MockHostResolver in order to direct www.google.com to the testserver.
+ // By default, MockHostResolver maps all hosts to 127.0.0.1.
+
+ MockHostResolver host_resolver;
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_host_resolver(&host_resolver);
+ TransportSecurityState transport_security_state;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true,
+ &domain_state));
+ context.set_transport_security_state(&transport_security_state);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest r(GURL(base::StringPrintf("https://www.google.com:%d",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ EXPECT_TRUE(d.certificate_errors_are_fatal());
+
+ // Get a fresh copy of the state, and check that it hasn't been updated.
+ TransportSecurityState::DomainState new_domain_state;
+ EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true,
+ &new_domain_state));
+ EXPECT_EQ(new_domain_state.upgrade_mode, domain_state.upgrade_mode);
+ EXPECT_EQ(new_domain_state.sts_include_subdomains,
+ domain_state.sts_include_subdomains);
+ EXPECT_EQ(new_domain_state.pkp_include_subdomains,
+ domain_state.pkp_include_subdomains);
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.static_spki_hashes,
+ domain_state.static_spki_hashes));
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.dynamic_spki_hashes,
+ domain_state.dynamic_spki_hashes));
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.bad_static_spki_hashes,
+ domain_state.bad_static_spki_hashes));
+}
+
+// Make sure HSTS preserves a POST request's method and body.
+TEST_F(HTTPSRequestTest, HSTSPreservesPosts) {
+ static const char kData[] = "hello world";
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_OK);
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+
+ // Per spec, TransportSecurityState expects a domain name, rather than an IP
+ // address, so a MockHostResolver is needed to redirect www.somewhere.com to
+ // the SpawnedTestServer. By default, MockHostResolver maps all hosts
+ // to 127.0.0.1.
+ MockHostResolver host_resolver;
+
+ // Force https for www.somewhere.com.
+ TransportSecurityState transport_security_state;
+ base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
+ bool include_subdomains = false;
+ transport_security_state.AddHSTS("www.somewhere.com", expiry,
+ include_subdomains);
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+
+ TestURLRequestContext context(true);
+ context.set_host_resolver(&host_resolver);
+ context.set_transport_security_state(&transport_security_state);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+ // Navigating to https://www.somewhere.com instead of https://127.0.0.1 will
+ // cause a certificate error. Ignore the error.
+ d.set_allow_certificate_errors(true);
+
+ URLRequest req(GURL(base::StringPrintf("http://www.somewhere.com:%d/echo",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+
+ req.Start();
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ("https", req.url().scheme());
+ EXPECT_EQ("POST", req.method());
+ EXPECT_EQ(kData, d.data_received());
+
+ LoadTimingInfo load_timing_info;
+ network_delegate.GetLoadTimingInfoBeforeRedirect(&load_timing_info);
+ // LoadTimingInfo of HSTS redirects is similar to that of network cache hits
+ TestLoadTimingCacheHitNoNetwork(load_timing_info);
+}
+
+TEST_F(HTTPSRequestTest, SSLv3Fallback) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant =
+ SpawnedTestServer::SSLOptions::TLS_INTOLERANT_ALL;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ TestURLRequestContext context(true);
+ context.Init();
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(std::string()), &d, &context);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(static_cast<int>(SSL_CONNECTION_VERSION_SSL3),
+ SSLConnectionStatusToVersion(r.ssl_info().connection_status));
+ EXPECT_TRUE(r.ssl_info().connection_status & SSL_CONNECTION_VERSION_FALLBACK);
+}
+
+namespace {
+
+class SSLClientAuthTestDelegate : public TestDelegate {
+ public:
+ SSLClientAuthTestDelegate() : on_certificate_requested_count_(0) {
+ }
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) OVERRIDE {
+ on_certificate_requested_count_++;
+ base::MessageLoop::current()->Quit();
+ }
+ int on_certificate_requested_count() {
+ return on_certificate_requested_count_;
+ }
+ private:
+ int on_certificate_requested_count_;
+};
+
+} // namespace
+
+// TODO(davidben): Test the rest of the code. Specifically,
+// - Filtering which certificates to select.
+// - Sending a certificate back.
+// - Getting a certificate request in an SSL renegotiation sending the
+// HTTP request.
+TEST_F(HTTPSRequestTest, ClientAuthTest) {
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientAuthTestDelegate d;
+ {
+ URLRequest r(test_server.GetURL(std::string()), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.on_certificate_requested_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(0, d.bytes_received());
+
+ // Send no certificate.
+ // TODO(davidben): Get temporary client cert import (with keys) working on
+ // all platforms so we can test sending a cert as well.
+ r.ContinueWithCertificate(NULL);
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ }
+}
+
+TEST_F(HTTPSRequestTest, ResumeTest) {
+ // Test that we attempt a session resume when making two connections to the
+ // same host.
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.record_resume = true;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientSocket::ClearSessionCache();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ }
+
+ reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())->
+ CloseAllConnections();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ // The response will look like;
+ // insert abc
+ // lookup abc
+ // insert xyz
+ //
+ // With a newline at the end which makes the split think that there are
+ // four lines.
+
+ EXPECT_EQ(1, d.response_started_count());
+ std::vector<std::string> lines;
+ base::SplitString(d.data_received(), '\n', &lines);
+ ASSERT_EQ(4u, lines.size()) << d.data_received();
+
+ std::string session_id;
+
+ for (size_t i = 0; i < 2; i++) {
+ std::vector<std::string> parts;
+ base::SplitString(lines[i], '\t', &parts);
+ ASSERT_EQ(2u, parts.size());
+ if (i == 0) {
+ EXPECT_EQ("insert", parts[0]);
+ session_id = parts[1];
+ } else {
+ EXPECT_EQ("lookup", parts[0]);
+ EXPECT_EQ(session_id, parts[1]);
+ }
+ }
+ }
+}
+
+TEST_F(HTTPSRequestTest, SSLSessionCacheShardTest) {
+ // Test that sessions aren't resumed when the value of ssl_session_cache_shard
+ // differs.
+ SpawnedTestServer::SSLOptions ssl_options;
+ ssl_options.record_resume = true;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientSocket::ClearSessionCache();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ }
+
+ // Now create a new HttpCache with a different ssl_session_cache_shard value.
+ HttpNetworkSession::Params params;
+ params.host_resolver = default_context_.host_resolver();
+ params.cert_verifier = default_context_.cert_verifier();
+ params.transport_security_state = default_context_.transport_security_state();
+ params.proxy_service = default_context_.proxy_service();
+ params.ssl_config_service = default_context_.ssl_config_service();
+ params.http_auth_handler_factory =
+ default_context_.http_auth_handler_factory();
+ params.network_delegate = &default_network_delegate_;
+ params.http_server_properties = default_context_.http_server_properties();
+ params.ssl_session_cache_shard = "alternate";
+
+ scoped_ptr<net::HttpCache> cache(new net::HttpCache(
+ new net::HttpNetworkSession(params),
+ net::HttpCache::DefaultBackend::InMemory(0)));
+
+ default_context_.set_http_transaction_factory(cache.get());
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ // The response will look like;
+ // insert abc
+ // insert xyz
+ //
+ // With a newline at the end which makes the split think that there are
+ // three lines.
+
+ EXPECT_EQ(1, d.response_started_count());
+ std::vector<std::string> lines;
+ base::SplitString(d.data_received(), '\n', &lines);
+ ASSERT_EQ(3u, lines.size());
+
+ std::string session_id;
+ for (size_t i = 0; i < 2; i++) {
+ std::vector<std::string> parts;
+ base::SplitString(lines[i], '\t', &parts);
+ ASSERT_EQ(2u, parts.size());
+ EXPECT_EQ("insert", parts[0]);
+ if (i == 0) {
+ session_id = parts[1];
+ } else {
+ EXPECT_NE(session_id, parts[1]);
+ }
+ }
+ }
+}
+
+class TestSSLConfigService : public SSLConfigService {
+ public:
+ TestSSLConfigService(bool ev_enabled,
+ bool online_rev_checking,
+ bool rev_checking_required_local_anchors)
+ : ev_enabled_(ev_enabled),
+ online_rev_checking_(online_rev_checking),
+ rev_checking_required_local_anchors_(
+ rev_checking_required_local_anchors) {}
+
+ // SSLConfigService:
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {
+ *config = SSLConfig();
+ config->rev_checking_enabled = online_rev_checking_;
+ config->verify_ev_cert = ev_enabled_;
+ config->rev_checking_required_local_anchors =
+ rev_checking_required_local_anchors_;
+ }
+
+ protected:
+ virtual ~TestSSLConfigService() {}
+
+ private:
+ const bool ev_enabled_;
+ const bool online_rev_checking_;
+ const bool rev_checking_required_local_anchors_;
+};
+
+// This the fingerprint of the "Testing CA" certificate used by the testserver.
+// See net/data/ssl/certificates/ocsp-test-root.pem.
+static const SHA1HashValue kOCSPTestCertFingerprint =
+ { { 0xf1, 0xad, 0xf6, 0xce, 0x42, 0xac, 0xe7, 0xb4, 0xf4, 0x24,
+ 0xdb, 0x1a, 0xf7, 0xa0, 0x9f, 0x09, 0xa1, 0xea, 0xf1, 0x5c } };
+
+// This is the SHA256, SPKI hash of the "Testing CA" certificate used by the
+// testserver.
+static const SHA256HashValue kOCSPTestCertSPKI = { {
+ 0xee, 0xe6, 0x51, 0x2d, 0x4c, 0xfa, 0xf7, 0x3e,
+ 0x6c, 0xd8, 0xca, 0x67, 0xed, 0xb5, 0x5d, 0x49,
+ 0x76, 0xe1, 0x52, 0xa7, 0x6e, 0x0e, 0xa0, 0x74,
+ 0x09, 0x75, 0xe6, 0x23, 0x24, 0xbd, 0x1b, 0x28,
+} };
+
+// This is the policy OID contained in the certificates that testserver
+// generates.
+static const char kOCSPTestCertPolicy[] = "1.3.6.1.4.1.11129.2.4.1";
+
+class HTTPSOCSPTest : public HTTPSRequestTest {
+ public:
+ HTTPSOCSPTest()
+ : context_(true),
+ ev_test_policy_(
+ new ScopedTestEVPolicy(EVRootCAMetadata::GetInstance(),
+ kOCSPTestCertFingerprint,
+ kOCSPTestCertPolicy)) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ SetupContext(&context_);
+ context_.Init();
+
+ scoped_refptr<net::X509Certificate> root_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
+ CHECK_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ test_root_.reset(new ScopedTestRoot(root_cert.get()));
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ SetURLRequestContextForNSSHttpIO(&context_);
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ void DoConnection(const SpawnedTestServer::SSLOptions& ssl_options,
+ CertStatus* out_cert_status) {
+ // We always overwrite out_cert_status.
+ *out_cert_status = 0;
+ SpawnedTestServer test_server(
+ SpawnedTestServer::TYPE_HTTPS,
+ ssl_options,
+ base::FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(std::string()), &d, &context_);
+ r.Start();
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ *out_cert_status = r.ssl_info().cert_status;
+ }
+
+ virtual ~HTTPSOCSPTest() {
+#if defined(USE_NSS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+
+ protected:
+ // SetupContext configures the URLRequestContext that will be used for making
+ // connetions to testserver. This can be overridden in test subclasses for
+ // different behaviour.
+ virtual void SetupContext(URLRequestContext* context) {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(true /* check for EV */,
+ true /* online revocation checking */,
+ false /* require rev. checking for local
+ anchors */));
+ }
+
+ scoped_ptr<ScopedTestRoot> test_root_;
+ TestURLRequestContext context_;
+ scoped_ptr<ScopedTestEVPolicy> ev_test_policy_;
+};
+
+static CertStatus ExpectedCertStatusForFailedOnlineRevocationCheck() {
+#if defined(OS_WIN)
+ // Windows can return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION but we don't
+ // have that ability on other platforms.
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+#else
+ return 0;
+#endif
+}
+
+// SystemSupportsHardFailRevocationChecking returns true iff the current
+// operating system supports revocation checking and can distinguish between
+// situations where a given certificate lacks any revocation information (eg:
+// no CRLDistributionPoints and no OCSP Responder AuthorityInfoAccess) and when
+// revocation information cannot be obtained (eg: the CRL was unreachable).
+// If it does not, then tests which rely on 'hard fail' behaviour should be
+// skipped.
+static bool SystemSupportsHardFailRevocationChecking() {
+#if defined(OS_WIN) || defined(USE_NSS) || defined(OS_IOS)
+ return true;
+#else
+ return false;
+#endif
+}
+
+// SystemUsesChromiumEVMetadata returns true iff the current operating system
+// uses Chromium's EV metadata (i.e. EVRootCAMetadata). If it does not, then
+// several tests are effected because our testing EV certificate won't be
+// recognised as EV.
+static bool SystemUsesChromiumEVMetadata() {
+#if defined(USE_OPENSSL)
+ // http://crbug.com/117478 - OpenSSL does not support EV validation.
+ return false;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OS X, we use the system to tell us whether a certificate is EV or not
+ // and the system won't recognise our testing root.
+ return false;
+#else
+ return true;
+#endif
+}
+
+static bool SystemSupportsOCSP() {
+#if defined(USE_OPENSSL)
+ // http://crbug.com/117478 - OpenSSL does not support OCSP.
+ return false;
+#elif defined(OS_WIN)
+ return base::win::GetVersion() >= base::win::VERSION_VISTA;
+#elif defined(OS_ANDROID)
+ // TODO(jnd): http://crbug.com/117478 - EV verification is not yet supported.
+ return false;
+#else
+ return true;
+#endif
+}
+
+TEST_F(HTTPSOCSPTest, Valid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_OK;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Revoked) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_REVOKED;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+#if !(defined(OS_MACOSX) && !defined(OS_IOS))
+ // Doesn't pass on OS X yet for reasons that need to be investigated.
+ EXPECT_EQ(CERT_STATUS_REVOKED, cert_status & CERT_STATUS_ALL_ERRORS);
+#endif
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Invalid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ // Without a positive OCSP response, we shouldn't show the EV status.
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+class HTTPSHardFailTest : public HTTPSOCSPTest {
+ protected:
+ virtual void SetupContext(URLRequestContext* context) OVERRIDE {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(false /* check for EV */,
+ false /* online revocation checking */,
+ true /* require rev. checking for local
+ anchors */));
+ }
+};
+
+
+TEST_F(HTTPSHardFailTest, FailsOnOCSPInvalid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ if (!SystemSupportsHardFailRevocationChecking()) {
+ LOG(WARNING) << "Skipping test because system doesn't support hard fail "
+ << "revocation checking";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(CERT_STATUS_REVOKED,
+ cert_status & CERT_STATUS_REVOKED);
+
+ // Without a positive OCSP response, we shouldn't show the EV status.
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+class HTTPSEVCRLSetTest : public HTTPSOCSPTest {
+ protected:
+ virtual void SetupContext(URLRequestContext* context) OVERRIDE {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(true /* check for EV */,
+ false /* online revocation checking */,
+ false /* require rev. checking for local
+ anchors */));
+ }
+};
+
+TEST_F(HTTPSEVCRLSetTest, MissingCRLSetAndInvalidOCSP) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(scoped_refptr<CRLSet>());
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, MissingCRLSetAndGoodOCSP) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_OK;
+ SSLConfigService::SetCRLSet(scoped_refptr<CRLSet>());
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, ExpiredCRLSet) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, FreshCRLSetCovered) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ForTesting(
+ false, &kOCSPTestCertSPKI, "")));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ // With a fresh CRLSet that covers the issuing certificate, we shouldn't do a
+ // revocation check for EV.
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+ EXPECT_FALSE(
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, FreshCRLSetNotCovered) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::EmptyCRLSetForTesting()));
+
+ CertStatus cert_status = 0;
+ DoConnection(ssl_options, &cert_status);
+
+ // Even with a fresh CRLSet, we should still do online revocation checks when
+ // the certificate chain isn't covered by the CRLSet, which it isn't in this
+ // test.
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, ExpiredCRLSetAndRevokedNonEVCert) {
+ // Test that when EV verification is requested, but online revocation
+ // checking is disabled, and the leaf certificate is not in fact EV, that
+ // no revocation checking actually happens.
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ // Unmark the certificate's OID as EV, which should disable revocation
+ // checking (as per the user preference)
+ ev_test_policy_.reset();
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_REVOKED;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_FALSE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+class HTTPSCRLSetTest : public HTTPSOCSPTest {
+ protected:
+ virtual void SetupContext(URLRequestContext* context) OVERRIDE {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(false /* check for EV */,
+ false /* online revocation checking */,
+ false /* require rev. checking for local
+ anchors */));
+ }
+};
+
+TEST_F(HTTPSCRLSetTest, ExpiredCRLSet) {
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ // If we're not trying EV verification then, even if the CRLSet has expired,
+ // we don't fall back to online revocation checks.
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_FALSE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSCRLSetTest, CRLSetRevoked) {
+#if defined(USE_OPENSSL)
+ LOG(WARNING) << "Skipping test because system doesn't support CRLSets";
+ return;
+#endif
+
+ SpawnedTestServer::SSLOptions ssl_options(
+ SpawnedTestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = SpawnedTestServer::SSLOptions::OCSP_OK;
+ ssl_options.cert_serial = 10;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ForTesting(
+ false, &kOCSPTestCertSPKI, "\x0a")));
+
+ CertStatus cert_status = 0;
+ DoConnection(ssl_options, &cert_status);
+
+ // If the certificate is recorded as revoked in the CRLSet, that should be
+ // reflected without online revocation checking.
+ EXPECT_EQ(CERT_STATUS_REVOKED, cert_status & CERT_STATUS_ALL_ERRORS);
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_FALSE(
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+#endif // !defined(OS_IOS)
+
+#if !defined(DISABLE_FTP_SUPPORT)
+class URLRequestTestFTP : public URLRequestTest {
+ public:
+ URLRequestTestFTP()
+ : test_server_(SpawnedTestServer::TYPE_FTP, SpawnedTestServer::kLocalhost,
+ base::FilePath()) {
+ }
+
+ protected:
+ SpawnedTestServer test_server_;
+};
+
+// Make sure an FTP request using an unsafe ports fails.
+TEST_F(URLRequestTestFTP, UnsafePort) {
+ ASSERT_TRUE(test_server_.Start());
+
+ URLRequestJobFactoryImpl job_factory;
+ FtpNetworkLayer ftp_transaction_factory(default_context_.host_resolver());
+
+ GURL url("ftp://127.0.0.1:7");
+ job_factory.SetProtocolHandler(
+ "ftp",
+ new FtpProtocolHandler(&ftp_transaction_factory));
+ default_context_.set_job_factory(&job_factory);
+
+ TestDelegate d;
+ {
+ URLRequest r(url, &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_UNSAFE_PORT, r.status().error());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPDirectoryListing) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("/"), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_LT(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPGetTestAnonymous) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("/LICENSE"), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPGetTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE", "chrome", "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+
+ LoadTimingInfo load_timing_info;
+ r.GetLoadTimingInfo(&load_timing_info);
+ TestLoadTimingNoHttpResponse(load_timing_info);
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongPassword) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongPasswordRestart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongUser) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "wrong_user",
+ "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongUserRestart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "wrong_user",
+ "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCacheURLCredentials) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+
+ scoped_ptr<TestDelegate> d(new TestDelegate);
+ {
+ // Pass correct login identity in the URL.
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "chrome"),
+ d.get(),
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+
+ d.reset(new TestDelegate);
+ {
+ // This request should use cached identity from previous request.
+ URLRequest r(test_server_.GetURL("/LICENSE"), d.get(), &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCacheLoginBoxCredentials) {
+ ASSERT_TRUE(test_server_.Start());
+
+ base::FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+
+ scoped_ptr<TestDelegate> d(new TestDelegate);
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d->set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ d.get(),
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+
+ // Use a new delegate without explicit credentials. The cached ones should be
+ // used.
+ d.reset(new TestDelegate);
+ {
+ // Don't pass wrong credentials in the URL, they would override valid cached
+ // ones.
+ URLRequest r(test_server_.GetURL("/LICENSE"), d.get(), &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ base::MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+}
+#endif // !defined(DISABLE_FTP_SUPPORT)
+
+} // namespace net
diff --git a/chromium/net/url_request/view_cache_helper.cc b/chromium/net/url_request/view_cache_helper.cc
new file mode 100644
index 00000000000..346c7cdb50c
--- /dev/null
+++ b/chromium/net/url_request/view_cache_helper.cc
@@ -0,0 +1,367 @@
+// Copyright (c) 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.
+
+#include "net/url_request/view_cache_helper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/escape.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_context.h"
+
+#define VIEW_CACHE_HEAD \
+ "<html><meta charset=\"utf-8\">" \
+ "<meta http-equiv=\"Content-Security-Policy\" " \
+ " content=\"object-src 'none'; script-src 'none' 'unsafe-eval'\">" \
+ "<body><table>"
+
+#define VIEW_CACHE_TAIL \
+ "</table></body></html>"
+
+namespace net {
+
+namespace {
+
+std::string FormatEntryInfo(disk_cache::Entry* entry,
+ const std::string& url_prefix) {
+ std::string key = entry->GetKey();
+ GURL url = GURL(url_prefix + key);
+ std::string row =
+ "<tr><td><a href=\"" + url.spec() + "\">" + EscapeForHTML(key) +
+ "</a></td></tr>";
+ return row;
+}
+
+} // namespace.
+
+ViewCacheHelper::ViewCacheHelper()
+ : context_(NULL),
+ disk_cache_(NULL),
+ entry_(NULL),
+ iter_(NULL),
+ buf_len_(0),
+ index_(0),
+ data_(NULL),
+ next_state_(STATE_NONE),
+ weak_factory_(this) {
+}
+
+ViewCacheHelper::~ViewCacheHelper() {
+ if (entry_)
+ entry_->Close();
+}
+
+int ViewCacheHelper::GetEntryInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ std::string* out,
+ const CompletionCallback& callback) {
+ return GetInfoHTML(key, context, std::string(), out, callback);
+}
+
+int ViewCacheHelper::GetContentsHTML(const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback) {
+ return GetInfoHTML(std::string(), context, url_prefix, out, callback);
+}
+
+// static
+void ViewCacheHelper::HexDump(const char *buf, size_t buf_len,
+ std::string* result) {
+ const size_t kMaxRows = 16;
+ int offset = 0;
+
+ const unsigned char *p;
+ while (buf_len) {
+ base::StringAppendF(result, "%08x: ", offset);
+ offset += kMaxRows;
+
+ p = (const unsigned char *) buf;
+
+ size_t i;
+ size_t row_max = std::min(kMaxRows, buf_len);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i)
+ base::StringAppendF(result, "%02x ", *p++);
+ for (i = row_max; i < kMaxRows; ++i)
+ result->append(" ");
+ result->append(" ");
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (*p < 0x7F && *p > 0x1F) {
+ AppendEscapedCharForHTML(*p, result);
+ } else {
+ result->push_back('.');
+ }
+ }
+
+ result->push_back('\n');
+
+ buf += row_max;
+ buf_len -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+int ViewCacheHelper::GetInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback) {
+ DCHECK(callback_.is_null());
+ DCHECK(context);
+ key_ = key;
+ context_ = context;
+ url_prefix_ = url_prefix;
+ data_ = out;
+ next_state_ = STATE_GET_BACKEND;
+ int rv = DoLoop(OK);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+void ViewCacheHelper::DoCallback(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!callback_.is_null());
+
+ callback_.Run(rv);
+ callback_.Reset();
+}
+
+void ViewCacheHelper::HandleResult(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK_NE(ERR_FAILED, rv);
+ context_ = NULL;
+ if (!callback_.is_null())
+ DoCallback(rv);
+}
+
+int ViewCacheHelper::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GET_BACKEND:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetBackend();
+ break;
+ case STATE_GET_BACKEND_COMPLETE:
+ rv = DoGetBackendComplete(rv);
+ break;
+ case STATE_OPEN_NEXT_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenNextEntry();
+ break;
+ case STATE_OPEN_NEXT_ENTRY_COMPLETE:
+ rv = DoOpenNextEntryComplete(rv);
+ break;
+ case STATE_OPEN_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadResponse();
+ break;
+ case STATE_READ_RESPONSE_COMPLETE:
+ rv = DoReadResponseComplete(rv);
+ break;
+ case STATE_READ_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadData();
+ break;
+ case STATE_READ_DATA_COMPLETE:
+ rv = DoReadDataComplete(rv);
+ break;
+
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ if (rv != ERR_IO_PENDING)
+ HandleResult(rv);
+
+ return rv;
+}
+
+int ViewCacheHelper::DoGetBackend() {
+ next_state_ = STATE_GET_BACKEND_COMPLETE;
+
+ if (!context_->http_transaction_factory())
+ return ERR_FAILED;
+
+ HttpCache* http_cache = context_->http_transaction_factory()->GetCache();
+ if (!http_cache)
+ return ERR_FAILED;
+
+ return http_cache->GetBackend(
+ &disk_cache_, base::Bind(&ViewCacheHelper::OnIOComplete,
+ base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoGetBackendComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no disk cache");
+ return OK;
+ }
+
+ DCHECK_EQ(OK, result);
+ if (key_.empty()) {
+ data_->assign(VIEW_CACHE_HEAD);
+ DCHECK(!iter_);
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int ViewCacheHelper::DoOpenNextEntry() {
+ next_state_ = STATE_OPEN_NEXT_ENTRY_COMPLETE;
+ return disk_cache_->OpenNextEntry(
+ &iter_, &entry_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoOpenNextEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append(VIEW_CACHE_TAIL);
+ return OK;
+ }
+
+ DCHECK_EQ(OK, result);
+ data_->append(FormatEntryInfo(entry_, url_prefix_));
+ entry_->Close();
+ entry_ = NULL;
+
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+}
+
+int ViewCacheHelper::DoOpenEntry() {
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ return disk_cache_->OpenEntry(
+ key_, &entry_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoOpenEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no matching cache entry for: " + EscapeForHTML(key_));
+ return OK;
+ }
+
+ data_->assign(VIEW_CACHE_HEAD);
+ data_->append(EscapeForHTML(entry_->GetKey()));
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ buf_len_ = entry_->GetDataSize(0);
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new IOBuffer(buf_len_);
+ return entry_->ReadData(
+ 0,
+ 0,
+ buf_.get(),
+ buf_len_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int ViewCacheHelper::DoReadResponseComplete(int result) {
+ if (result && result == buf_len_) {
+ HttpResponseInfo response;
+ bool truncated;
+ if (HttpCache::ParseResponseInfo(
+ buf_->data(), buf_len_, &response, &truncated) &&
+ response.headers.get()) {
+ if (truncated)
+ data_->append("<pre>RESPONSE_INFO_TRUNCATED</pre>");
+
+ data_->append("<hr><pre>");
+ data_->append(EscapeForHTML(response.headers->GetStatusLine()));
+ data_->push_back('\n');
+
+ void* iter = NULL;
+ std::string name, value;
+ while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ data_->append(EscapeForHTML(name));
+ data_->append(": ");
+ data_->append(EscapeForHTML(value));
+ data_->push_back('\n');
+ }
+ data_->append("</pre>");
+ }
+ }
+
+ index_ = 0;
+ next_state_ = STATE_READ_DATA;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadData() {
+ data_->append("<hr><pre>");
+
+ next_state_ = STATE_READ_DATA_COMPLETE;
+ buf_len_ = entry_->GetDataSize(index_);
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new IOBuffer(buf_len_);
+ return entry_->ReadData(
+ index_,
+ 0,
+ buf_.get(),
+ buf_len_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int ViewCacheHelper::DoReadDataComplete(int result) {
+ if (result && result == buf_len_) {
+ HexDump(buf_->data(), buf_len_, data_);
+ }
+ data_->append("</pre>");
+ index_++;
+ if (index_ < HttpCache::kNumCacheEntryDataIndices) {
+ next_state_ = STATE_READ_DATA;
+ } else {
+ data_->append(VIEW_CACHE_TAIL);
+ entry_->Close();
+ entry_ = NULL;
+ }
+ return OK;
+}
+
+void ViewCacheHelper::OnIOComplete(int result) {
+ DoLoop(result);
+}
+
+} // namespace net.
diff --git a/chromium/net/url_request/view_cache_helper.h b/chromium/net/url_request/view_cache_helper.h
new file mode 100644
index 00000000000..d5a7c423d68
--- /dev/null
+++ b/chromium/net/url_request/view_cache_helper.h
@@ -0,0 +1,124 @@
+// Copyright (c) 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.
+
+#ifndef NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
+#define NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+class Backend;
+class Entry;
+} // namespace disk_cache
+
+namespace net {
+
+class URLRequestContext;
+
+class NET_EXPORT ViewCacheHelper {
+ public:
+ ViewCacheHelper();
+ ~ViewCacheHelper();
+
+ // Formats the cache information for |key| as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed.
+ int GetEntryInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // Formats the cache contents as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed. |url_prefix| will be prepended to each entry
+ // key as a link to the entry.
+ int GetContentsHTML(const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // Lower-level helper to produce a textual representation of binary data.
+ // The results are appended to |result| and can be used in HTML pages
+ // provided the dump is contained within <pre></pre> tags.
+ static void HexDump(const char *buf, size_t buf_len, std::string* result);
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_GET_BACKEND,
+ STATE_GET_BACKEND_COMPLETE,
+ STATE_OPEN_NEXT_ENTRY,
+ STATE_OPEN_NEXT_ENTRY_COMPLETE,
+ STATE_OPEN_ENTRY,
+ STATE_OPEN_ENTRY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_READ_DATA,
+ STATE_READ_DATA_COMPLETE
+ };
+
+ // Implements GetEntryInfoHTML and GetContentsHTML.
+ int GetInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // This is a helper function used to trigger a completion callback. It may
+ // only be called if callback_ is non-null.
+ void DoCallback(int rv);
+
+ // This will trigger the completion callback if appropriate.
+ void HandleResult(int rv);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. If there is an
+ // argument, the value corresponds to the return of the previous state or
+ // corresponding callback.
+ int DoGetBackend();
+ int DoGetBackendComplete(int result);
+ int DoOpenNextEntry();
+ int DoOpenNextEntryComplete(int result);
+ int DoOpenEntry();
+ int DoOpenEntryComplete(int result);
+ int DoReadResponse();
+ int DoReadResponseComplete(int result);
+ int DoReadData();
+ int DoReadDataComplete(int result);
+
+ // Called to signal completion of asynchronous IO.
+ void OnIOComplete(int result);
+
+ const URLRequestContext* context_;
+ disk_cache::Backend* disk_cache_;
+ disk_cache::Entry* entry_;
+ void* iter_;
+ scoped_refptr<IOBuffer> buf_;
+ int buf_len_;
+ int index_;
+
+ std::string key_;
+ std::string url_prefix_;
+ std::string* data_;
+ CompletionCallback callback_;
+
+ State next_state_;
+
+ base::WeakPtrFactory<ViewCacheHelper> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewCacheHelper);
+};
+
+} // namespace net.
+
+#endif // NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
diff --git a/chromium/net/url_request/view_cache_helper_unittest.cc b/chromium/net/url_request/view_cache_helper_unittest.cc
new file mode 100644
index 00000000000..db26f70ca05
--- /dev/null
+++ b/chromium/net/url_request/view_cache_helper_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 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.
+
+#include "net/url_request/view_cache_helper.h"
+
+#include "base/pickle.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext();
+ virtual ~TestURLRequestContext() {}
+
+ // Gets a pointer to the cache backend.
+ disk_cache::Backend* GetBackend();
+
+ private:
+ HttpCache cache_;
+};
+
+TestURLRequestContext::TestURLRequestContext()
+ : cache_(reinterpret_cast<HttpTransactionFactory*>(NULL), NULL,
+ HttpCache::DefaultBackend::InMemory(0)) {
+ set_http_transaction_factory(&cache_);
+}
+
+void WriteHeaders(disk_cache::Entry* entry, int flags, const std::string data) {
+ if (data.empty())
+ return;
+
+ Pickle pickle;
+ pickle.WriteInt(flags | 1); // Version 1.
+ pickle.WriteInt64(0);
+ pickle.WriteInt64(0);
+ pickle.WriteString(data);
+
+ scoped_refptr<WrappedIOBuffer> buf(new WrappedIOBuffer(
+ reinterpret_cast<const char*>(pickle.data())));
+ int len = static_cast<int>(pickle.size());
+
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(0, 0, buf.get(), len, cb.callback(), true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteData(disk_cache::Entry* entry, int index, const std::string data) {
+ if (data.empty())
+ return;
+
+ int len = data.length();
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ memcpy(buf->data(), data.data(), data.length());
+
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(index, 0, buf.get(), len, cb.callback(), true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteToEntry(disk_cache::Backend* cache, const std::string key,
+ const std::string data0, const std::string data1,
+ const std::string data2) {
+ net::TestCompletionCallback cb;
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry(key, &entry, cb.callback());
+ rv = cb.GetResult(rv);
+ if (rv != OK) {
+ rv = cache->OpenEntry(key, &entry, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+ }
+
+ WriteHeaders(entry, 0, data0);
+ WriteData(entry, 1, data1);
+ WriteData(entry, 2, data2);
+
+ entry->Close();
+}
+
+void FillCache(URLRequestContext* context) {
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context->http_transaction_factory()->GetCache()->GetBackend(
+ &cache, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ std::string empty;
+ WriteToEntry(cache, "first", "some", empty, empty);
+ WriteToEntry(cache, "second", "only hex_dumped", "same", "kind");
+ WriteToEntry(cache, "third", empty, "another", "thing");
+}
+
+} // namespace.
+
+TEST(ViewCacheHelper, EmptyCache) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ TestCompletionCallback cb;
+ std::string prefix, data;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+ EXPECT_FALSE(data.empty());
+}
+
+TEST(ViewCacheHelper, ListContents) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string prefix, data;
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("first"));
+ EXPECT_NE(std::string::npos, data.find("second"));
+ EXPECT_NE(std::string::npos, data.find("third"));
+
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("same"));
+ EXPECT_EQ(std::string::npos, data.find("thing"));
+}
+
+TEST(ViewCacheHelper, DumpEntry) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string data;
+ TestCompletionCallback cb;
+ int rv = helper.GetEntryInfoHTML("second", &context, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+
+ EXPECT_NE(std::string::npos, data.find("hex_dumped"));
+ EXPECT_NE(std::string::npos, data.find("same"));
+ EXPECT_NE(std::string::npos, data.find("kind"));
+
+ EXPECT_EQ(std::string::npos, data.find("first"));
+ EXPECT_EQ(std::string::npos, data.find("third"));
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("another"));
+}
+
+// Makes sure the links are correct.
+TEST(ViewCacheHelper, Prefix) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string key, data;
+ std::string prefix("prefix:");
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:first\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:second\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:third\">"));
+}
+
+TEST(ViewCacheHelper, TruncatedFlag) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context.http_transaction_factory()->GetCache()->GetBackend(
+ &cache, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ std::string key("the key");
+ disk_cache::Entry* entry;
+ rv = cache->CreateEntry(key, &entry, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ // RESPONSE_INFO_TRUNCATED defined on response_info.cc
+ int flags = 1 << 12;
+ WriteHeaders(entry, flags, "something");
+ entry->Close();
+
+ std::string data;
+ TestCompletionCallback cb1;
+ rv = helper.GetEntryInfoHTML(key, &context, &data, cb1.callback());
+ EXPECT_EQ(OK, cb1.GetResult(rv));
+
+ EXPECT_NE(std::string::npos, data.find("RESPONSE_INFO_TRUNCATED"));
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/OWNERS b/chromium/net/websockets/OWNERS
new file mode 100644
index 00000000000..8ef489a002e
--- /dev/null
+++ b/chromium/net/websockets/OWNERS
@@ -0,0 +1,8 @@
+tyoshino@chromium.org
+
+# Have been inactive for a while.
+yutak@chromium.org
+toyoshim@chromium.org
+
+# On leave
+bashi@chromium.org
diff --git a/chromium/net/websockets/PRESUBMIT.py b/chromium/net/websockets/PRESUBMIT.py
new file mode 100644
index 00000000000..1da441cb757
--- /dev/null
+++ b/chromium/net/websockets/PRESUBMIT.py
@@ -0,0 +1,57 @@
+# Copyright 2013 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.
+
+"""Chromium presubmit script for src/net/websockets.
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into gcl.
+"""
+
+
+# TODO(ricea): Remove this once the old implementation has been removed and the
+# list of files in the README file is no longer needed.
+def _CheckReadMeComplete(input_api, output_api):
+ """Verifies that any new files have been added to the README file.
+
+ Checks that if any source files were added in this CL, that they were
+ also added to the README file. We do not warn about pre-existing
+ errors, as that would be annoying.
+
+ Args:
+ input_api: The InputApi object provided by the presubmit framework.
+ output_api: The OutputApi object provided by the framework.
+
+ Returns:
+ A list of zero or more PresubmitPromptWarning objects.
+ """
+ # None passed to AffectedSourceFiles means "use the default filter", which
+ # does what we want, ie. returns files in the CL with filenames that look like
+ # source code.
+ added_source_filenames = set(input_api.basename(af.LocalPath())
+ for af in input_api.AffectedSourceFiles(None)
+ if af.Action().startswith('A'))
+ if not added_source_filenames:
+ return []
+ readme = input_api.AffectedSourceFiles(
+ lambda af: af.LocalPath().endswith('/README'))
+ if not readme:
+ return [output_api.PresubmitPromptWarning(
+ 'One or more files were added to net/websockets without being added\n'
+ 'to net/websockets/README.\n', added_source_filenames)]
+ readme_added_filenames = set(line.strip() for line in readme[0].NewContents()
+ if line.strip() in added_source_filenames)
+ if readme_added_filenames < added_source_filenames:
+ return [output_api.PresubmitPromptWarning(
+ 'One or more files added to net/websockets but not found in the README '
+ 'file.\n', added_source_filenames - readme_added_filenames)]
+ else:
+ return []
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return _CheckReadMeComplete(input_api, output_api)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return _CheckReadMeComplete(input_api, output_api)
diff --git a/chromium/net/websockets/README b/chromium/net/websockets/README
new file mode 100644
index 00000000000..558a4511968
--- /dev/null
+++ b/chromium/net/websockets/README
@@ -0,0 +1,53 @@
+This directory contains files related to Chromium's WebSocket
+implementation. See http://www.websocket.org/ for an explanation of WebSockets.
+
+As of April 2013, the contents of this directory are in a transitional state,
+and contain parts of two different WebSocket implementations.
+
+The following files are part of the legacy implementation. The legacy
+implementation performs WebSocket framing within Blink and presents a
+low-level socket-like interface to the renderer process. It is described in the
+design doc at
+https://docs.google.com/a/google.com/document/d/1_R6YjCIrm4kikJ3YeapcOU2Keqr3lVUPd-OeaIJ93qQ/preview
+
+websocket_handshake_handler.cc
+websocket_handshake_handler.h
+websocket_handshake_handler_unittest.cc
+websocket_handshake_handler_spdy2_unittest.cc
+websocket_handshake_handler_spdy3_unittest.cc
+websocket_job.cc
+websocket_job.h
+websocket_job_unittest.cc
+websocket_net_log_params.cc
+websocket_net_log_params.h
+websocket_net_log_params_unittest.cc
+websocket_throttle.cc
+websocket_throttle.h
+websocket_throttle_unittest.cc
+
+The following files are part of the new implementation. The new implementation
+performs framing and implements protocol semantics in the browser process, and
+presents a high-level interface to the renderer process similar to a
+multiplexing proxy. This is not yet used in any stable Chromium version.
+
+websocket_channel.cc
+websocket_channel.h
+websocket_channel_test.cc
+websocket_errors.cc
+websocket_errors.h
+websocket_errors_unittest.cc
+websocket_event_interface.h
+websocket_frame.cc
+websocket_frame.h
+websocket_frame_parser.cc
+websocket_frame_parser.h
+websocket_frame_parser_unittest.cc
+websocket_frame_unittest.cc
+websocket_mux.h
+websocket_stream_base.h
+websocket_stream.cc
+websocket_stream.h
+
+A pre-submit check helps us keep this README file up-to-date:
+
+PRESUBMIT.py
diff --git a/chromium/net/websockets/websocket_channel.cc b/chromium/net/websockets/websocket_channel.cc
new file mode 100644
index 00000000000..fd845f92c3b
--- /dev/null
+++ b/chromium/net/websockets/websocket_channel.cc
@@ -0,0 +1,703 @@
+// Copyright 2013 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/websockets/websocket_channel.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h" // for size_t
+#include "base/bind.h"
+#include "base/safe_numerics.h"
+#include "base/strings/string_util.h"
+#include "net/base/big_endian.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_log.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_mux.h"
+#include "net/websockets/websocket_stream.h"
+
+namespace net {
+
+namespace {
+
+const int kDefaultSendQuotaLowWaterMark = 1 << 16;
+const int kDefaultSendQuotaHighWaterMark = 1 << 17;
+const size_t kWebSocketCloseCodeLength = 2;
+
+// This uses type uint64 to match the definition of
+// WebSocketFrameHeader::payload_length in websocket_frame.h.
+const uint64 kMaxControlFramePayload = 125;
+
+// Concatenate the data from two IOBufferWithSize objects into a single one.
+IOBufferWithSize* ConcatenateIOBuffers(
+ const scoped_refptr<IOBufferWithSize>& part1,
+ const scoped_refptr<IOBufferWithSize>& part2) {
+ int newsize = part1->size() + part2->size();
+ IOBufferWithSize* newbuffer = new IOBufferWithSize(newsize);
+ std::copy(part1->data(), part1->data() + part1->size(), newbuffer->data());
+ std::copy(part2->data(),
+ part2->data() + part2->size(),
+ newbuffer->data() + part1->size());
+ return newbuffer;
+}
+
+} // namespace
+
+// A class to encapsulate a set of frames and information about the size of
+// those frames.
+class WebSocketChannel::SendBuffer {
+ public:
+ SendBuffer() : total_bytes_(0) {}
+
+ // Add a WebSocketFrameChunk to the buffer and increase total_bytes_.
+ void AddFrame(scoped_ptr<WebSocketFrameChunk> chunk);
+
+ // Return a pointer to the frames_ for write purposes.
+ ScopedVector<WebSocketFrameChunk>* frames() { return &frames_; }
+
+ private:
+ // The frames_ that will be sent in the next call to WriteFrames().
+ ScopedVector<WebSocketFrameChunk> frames_;
+
+ // The total size of the buffers in |frames_|. This will be used to measure
+ // the throughput of the link.
+ // TODO(ricea): Measure the throughput of the link.
+ size_t total_bytes_;
+};
+
+void WebSocketChannel::SendBuffer::AddFrame(
+ scoped_ptr<WebSocketFrameChunk> chunk) {
+ total_bytes_ += chunk->data->size();
+ frames_.push_back(chunk.release());
+}
+
+// Implementation of WebSocketStream::ConnectDelegate that simply forwards the
+// calls on to the WebSocketChannel that created it.
+class WebSocketChannel::ConnectDelegate
+ : public WebSocketStream::ConnectDelegate {
+ public:
+ explicit ConnectDelegate(WebSocketChannel* creator) : creator_(creator) {}
+
+ virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) OVERRIDE {
+ creator_->OnConnectSuccess(stream.Pass());
+ }
+
+ virtual void OnFailure(uint16 websocket_error) OVERRIDE {
+ creator_->OnConnectFailure(websocket_error);
+ }
+
+ private:
+ // A pointer to the WebSocketChannel that created us. We do not need to worry
+ // about this pointer being stale, because deleting WebSocketChannel cancels
+ // the connect process, deleting this object and preventing its callbacks from
+ // being called.
+ WebSocketChannel* const creator_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectDelegate);
+};
+
+WebSocketChannel::WebSocketChannel(
+ const GURL& socket_url,
+ scoped_ptr<WebSocketEventInterface> event_interface)
+ : socket_url_(socket_url),
+ event_interface_(event_interface.Pass()),
+ send_quota_low_water_mark_(kDefaultSendQuotaLowWaterMark),
+ send_quota_high_water_mark_(kDefaultSendQuotaHighWaterMark),
+ current_send_quota_(0),
+ closing_code_(0),
+ state_(FRESHLY_CONSTRUCTED) {}
+
+WebSocketChannel::~WebSocketChannel() {
+ // The stream may hold a pointer to read_frame_chunks_, and so it needs to be
+ // destroyed first.
+ stream_.reset();
+}
+
+void WebSocketChannel::SendAddChannelRequest(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context) {
+ // Delegate to the tested version.
+ SendAddChannelRequestWithFactory(
+ requested_subprotocols,
+ origin,
+ url_request_context,
+ base::Bind(&WebSocketStream::CreateAndConnectStream));
+}
+
+bool WebSocketChannel::InClosingState() const {
+ // We intentionally do not support state RECV_CLOSED here, because it is only
+ // used in one code path and should not leak into the code in general.
+ DCHECK_NE(RECV_CLOSED, state_)
+ << "InClosingState called with state_ == RECV_CLOSED";
+ return state_ == SEND_CLOSED || state_ == CLOSE_WAIT || state_ == CLOSED;
+}
+
+void WebSocketChannel::SendFrame(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const std::vector<char>& data) {
+ if (data.size() > INT_MAX) {
+ NOTREACHED() << "Frame size sanity check failed";
+ return;
+ }
+ if (stream_ == NULL) {
+ LOG(DFATAL) << "Got SendFrame without a connection established; "
+ << "misbehaving renderer? fin=" << fin << " op_code=" << op_code
+ << " data.size()=" << data.size();
+ return;
+ }
+ if (InClosingState()) {
+ VLOG(1) << "SendFrame called in state " << state_
+ << ". This may be a bug, or a harmless race.";
+ return;
+ }
+ if (state_ != CONNECTED) {
+ NOTREACHED() << "SendFrame() called in state " << state_;
+ return;
+ }
+ if (data.size() > base::checked_numeric_cast<size_t>(current_send_quota_)) {
+ FailChannel(SEND_GOING_AWAY,
+ kWebSocketMuxErrorSendQuotaViolation,
+ "Send quota exceeded");
+ return;
+ }
+ if (!WebSocketFrameHeader::IsKnownDataOpCode(op_code)) {
+ LOG(DFATAL) << "Got SendFrame with bogus op_code " << op_code
+ << "; misbehaving renderer? fin=" << fin
+ << " data.size()=" << data.size();
+ return;
+ }
+ current_send_quota_ -= data.size();
+ // TODO(ricea): If current_send_quota_ has dropped below
+ // send_quota_low_water_mark_, we may want to consider increasing the "low
+ // water mark" and "high water mark", but only if we think we are not
+ // saturating the link to the WebSocket server.
+ // TODO(ricea): For kOpCodeText, do UTF-8 validation?
+ scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(data.size()));
+ std::copy(data.begin(), data.end(), buffer->data());
+ SendIOBufferWithSize(fin, op_code, buffer);
+}
+
+void WebSocketChannel::SendFlowControl(int64 quota) {
+ DCHECK_EQ(CONNECTED, state_);
+ // TODO(ricea): Add interface to WebSocketStream and implement.
+ // stream_->SendFlowControl(quota);
+}
+
+void WebSocketChannel::StartClosingHandshake(uint16 code,
+ const std::string& reason) {
+ if (InClosingState()) {
+ VLOG(1) << "StartClosingHandshake called in state " << state_
+ << ". This may be a bug, or a harmless race.";
+ return;
+ }
+ if (state_ != CONNECTED) {
+ NOTREACHED() << "StartClosingHandshake() called in state " << state_;
+ return;
+ }
+ // TODO(ricea): Validate |code|? Check that |reason| is valid UTF-8?
+ // TODO(ricea): There should be a timeout for the closing handshake.
+ SendClose(code, reason); // Sets state_ to SEND_CLOSED
+}
+
+void WebSocketChannel::SendAddChannelRequestForTesting(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory) {
+ SendAddChannelRequestWithFactory(
+ requested_subprotocols, origin, url_request_context, factory);
+}
+
+void WebSocketChannel::SendAddChannelRequestWithFactory(
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory) {
+ DCHECK_EQ(FRESHLY_CONSTRUCTED, state_);
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate(
+ new ConnectDelegate(this));
+ stream_request_ = factory.Run(socket_url_,
+ requested_subprotocols,
+ origin,
+ url_request_context,
+ BoundNetLog(),
+ connect_delegate.Pass());
+ state_ = CONNECTING;
+}
+
+void WebSocketChannel::OnConnectSuccess(scoped_ptr<WebSocketStream> stream) {
+ DCHECK(stream);
+ DCHECK_EQ(CONNECTING, state_);
+ stream_ = stream.Pass();
+ state_ = CONNECTED;
+ event_interface_->OnAddChannelResponse(false, stream_->GetSubProtocol());
+
+ // TODO(ricea): Get flow control information from the WebSocketStream once we
+ // have a multiplexing WebSocketStream.
+ current_send_quota_ = send_quota_high_water_mark_;
+ event_interface_->OnFlowControl(send_quota_high_water_mark_);
+
+ // We don't need this any more.
+ stream_request_.reset();
+ ReadFrames();
+}
+
+void WebSocketChannel::OnConnectFailure(uint16 websocket_error) {
+ DCHECK_EQ(CONNECTING, state_);
+ state_ = CLOSED;
+ stream_request_.reset();
+ event_interface_->OnAddChannelResponse(true, "");
+}
+
+void WebSocketChannel::WriteFrames() {
+ int result = OK;
+ do {
+ // This use of base::Unretained is safe because we own the WebSocketStream
+ // and destroying it cancels all callbacks.
+ result = stream_->WriteFrames(
+ data_being_sent_->frames(),
+ base::Bind(
+ &WebSocketChannel::OnWriteDone, base::Unretained(this), false));
+ if (result != ERR_IO_PENDING) {
+ OnWriteDone(true, result);
+ }
+ } while (result == OK && data_being_sent_);
+}
+
+void WebSocketChannel::OnWriteDone(bool synchronous, int result) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ DCHECK_NE(ERR_IO_PENDING, result);
+ DCHECK(data_being_sent_);
+ switch (result) {
+ case OK:
+ if (data_to_send_next_) {
+ data_being_sent_ = data_to_send_next_.Pass();
+ if (!synchronous) {
+ WriteFrames();
+ }
+ } else {
+ data_being_sent_.reset();
+ if (current_send_quota_ < send_quota_low_water_mark_) {
+ // TODO(ricea): Increase low_water_mark and high_water_mark if
+ // throughput is high, reduce them if throughput is low. Low water
+ // mark needs to be >= the bandwidth delay product *of the IPC
+ // channel*. Because factors like context-switch time, thread wake-up
+ // time, and bus speed come into play it is complex and probably needs
+ // to be determined empirically.
+ DCHECK_LE(send_quota_low_water_mark_, send_quota_high_water_mark_);
+ // TODO(ricea): Truncate quota by the quota specified by the remote
+ // server, if the protocol in use supports quota.
+ int fresh_quota = send_quota_high_water_mark_ - current_send_quota_;
+ current_send_quota_ += fresh_quota;
+ event_interface_->OnFlowControl(fresh_quota);
+ }
+ }
+ return;
+
+ // If a recoverable error condition existed, it would go here.
+
+ default:
+ DCHECK_LT(result, 0)
+ << "WriteFrames() should only return OK or ERR_ codes";
+ stream_->Close();
+ if (state_ != CLOSED) {
+ state_ = CLOSED;
+ event_interface_->OnDropChannel(kWebSocketErrorAbnormalClosure,
+ "Abnormal Closure");
+ }
+ return;
+ }
+}
+
+void WebSocketChannel::ReadFrames() {
+ int result = OK;
+ do {
+ // This use of base::Unretained is safe because we own the WebSocketStream,
+ // and any pending reads will be cancelled when it is destroyed.
+ result = stream_->ReadFrames(
+ &read_frame_chunks_,
+ base::Bind(
+ &WebSocketChannel::OnReadDone, base::Unretained(this), false));
+ if (result != ERR_IO_PENDING) {
+ OnReadDone(true, result);
+ }
+ } while (result == OK && state_ != CLOSED);
+}
+
+void WebSocketChannel::OnReadDone(bool synchronous, int result) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ DCHECK_NE(ERR_IO_PENDING, result);
+ switch (result) {
+ case OK:
+ // ReadFrames() must use ERR_CONNECTION_CLOSED for a closed connection
+ // with no data read, not an empty response.
+ DCHECK(!read_frame_chunks_.empty())
+ << "ReadFrames() returned OK, but nothing was read.";
+ for (size_t i = 0; i < read_frame_chunks_.size(); ++i) {
+ scoped_ptr<WebSocketFrameChunk> chunk(read_frame_chunks_[i]);
+ read_frame_chunks_[i] = NULL;
+ ProcessFrameChunk(chunk.Pass());
+ }
+ read_frame_chunks_.clear();
+ // We need to always keep a call to ReadFrames pending.
+ if (!synchronous && state_ != CLOSED) {
+ ReadFrames();
+ }
+ return;
+
+ default:
+ DCHECK_LT(result, 0)
+ << "ReadFrames() should only return OK or ERR_ codes";
+ stream_->Close();
+ if (state_ != CLOSED) {
+ state_ = CLOSED;
+ uint16 code = kWebSocketErrorAbnormalClosure;
+ std::string reason = "Abnormal Closure";
+ if (closing_code_ != 0) {
+ code = closing_code_;
+ reason = closing_reason_;
+ }
+ event_interface_->OnDropChannel(code, reason);
+ }
+ return;
+ }
+}
+
+void WebSocketChannel::ProcessFrameChunk(
+ scoped_ptr<WebSocketFrameChunk> chunk) {
+ bool is_first_chunk = false;
+ if (chunk->header) {
+ DCHECK(current_frame_header_ == NULL)
+ << "Received the header for a new frame without notification that "
+ << "the previous frame was complete.";
+ is_first_chunk = true;
+ current_frame_header_.swap(chunk->header);
+ if (current_frame_header_->masked) {
+ // RFC6455 Section 5.1 "A client MUST close a connection if it detects a
+ // masked frame."
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "Masked frame from server");
+ return;
+ }
+ }
+ if (!current_frame_header_) {
+ // If we rejected the previous chunk as invalid, then we will have reset
+ // current_frame_header_ to avoid using it. More chunks of the invalid frame
+ // may still arrive, so this is not necessarily a bug on our side. However,
+ // if this happens when state_ is CONNECTED, it is definitely a bug.
+ DCHECK(state_ != CONNECTED) << "Unexpected header-less frame received "
+ << "(final_chunk = " << chunk->final_chunk
+ << ", data size = " << chunk->data->size()
+ << ")";
+ return;
+ }
+ scoped_refptr<IOBufferWithSize> data_buffer;
+ data_buffer.swap(chunk->data);
+ const bool is_final_chunk = chunk->final_chunk;
+ chunk.reset();
+ WebSocketFrameHeader::OpCode opcode = current_frame_header_->opcode;
+ if (WebSocketFrameHeader::IsKnownControlOpCode(opcode)) {
+ if (!current_frame_header_->final) {
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "Control message with FIN bit unset received");
+ return;
+ }
+ if (current_frame_header_->payload_length > kMaxControlFramePayload) {
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ "Control message has payload over 125 bytes");
+ return;
+ }
+ if (!is_final_chunk) {
+ VLOG(2) << "Encountered a split control frame, opcode " << opcode;
+ if (incomplete_control_frame_body_) {
+ // The really horrid case. We need to create a new IOBufferWithSize
+ // combining the new one and the old one. This should virtually never
+ // happen.
+ // TODO(ricea): This algorithm is O(N^2). Use a fixed 127-byte buffer
+ // instead.
+ VLOG(3) << "Hit the really horrid case";
+ incomplete_control_frame_body_ =
+ ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
+ } else {
+ // The merely horrid case. Store the IOBufferWithSize to use when the
+ // rest of the control frame arrives.
+ incomplete_control_frame_body_.swap(data_buffer);
+ }
+ return; // Handle when complete.
+ }
+ if (incomplete_control_frame_body_) {
+ VLOG(2) << "Rejoining a split control frame, opcode " << opcode;
+ data_buffer =
+ ConcatenateIOBuffers(incomplete_control_frame_body_, data_buffer);
+ incomplete_control_frame_body_ = NULL; // Frame now complete.
+ }
+ }
+
+ // Apply basic sanity checks to the |payload_length| field from the frame
+ // header. We can only apply a strict check when we know we have the whole
+ // frame in one chunk.
+ DCHECK_GE(current_frame_header_->payload_length,
+ base::checked_numeric_cast<uint64>(data_buffer->size()));
+ DCHECK(!is_first_chunk || !is_final_chunk ||
+ current_frame_header_->payload_length ==
+ base::checked_numeric_cast<uint64>(data_buffer->size()));
+
+ // Respond to the frame appropriately to its type.
+ HandleFrame(opcode, is_first_chunk, is_final_chunk, data_buffer);
+
+ if (is_final_chunk) {
+ // Make sure we do not apply this frame header to any future chunks.
+ current_frame_header_.reset();
+ }
+}
+
+void WebSocketChannel::HandleFrame(
+ const WebSocketFrameHeader::OpCode opcode,
+ bool is_first_chunk,
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data_buffer) {
+ DCHECK_NE(RECV_CLOSED, state_)
+ << "HandleFrame() does not support being called re-entrantly from within "
+ "SendClose()";
+ if (state_ == CLOSED || state_ == CLOSE_WAIT) {
+ DVLOG_IF(1, state_ == CLOSED) << "A frame was received while in the CLOSED "
+ "state. This is possible after a channel "
+ "failed, but should be very rare.";
+ std::string frame_name;
+ switch (opcode) {
+ case WebSocketFrameHeader::kOpCodeText: // fall-thru
+ case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
+ case WebSocketFrameHeader::kOpCodeContinuation:
+ frame_name = "Data frame";
+ break;
+
+ case WebSocketFrameHeader::kOpCodePing:
+ frame_name = "Ping";
+ break;
+
+ case WebSocketFrameHeader::kOpCodePong:
+ frame_name = "Pong";
+ break;
+
+ case WebSocketFrameHeader::kOpCodeClose:
+ frame_name = "Close";
+ break;
+
+ default:
+ frame_name = "Unknown frame type";
+ break;
+ }
+ // SEND_REAL_ERROR makes no difference here, as we won't send another Close
+ // frame.
+ FailChannel(SEND_REAL_ERROR,
+ kWebSocketErrorProtocolError,
+ frame_name + " received after close");
+ return;
+ }
+ switch (opcode) {
+ case WebSocketFrameHeader::kOpCodeText: // fall-thru
+ case WebSocketFrameHeader::kOpCodeBinary: // fall-thru
+ case WebSocketFrameHeader::kOpCodeContinuation:
+ if (state_ == CONNECTED) {
+ const bool final = is_final_chunk && current_frame_header_->final;
+ // TODO(ricea): Need to fail the connection if UTF-8 is invalid
+ // post-reassembly. Requires a streaming UTF-8 validator.
+ // TODO(ricea): Can this copy be eliminated?
+ const char* const data_begin = data_buffer->data();
+ const char* const data_end = data_begin + data_buffer->size();
+ const std::vector<char> data(data_begin, data_end);
+ // TODO(ricea): Handle the (improbable) case when ReadFrames returns far
+ // more data at once than we want to send in a single IPC (in which case
+ // we need to buffer the data and return to the event loop with a
+ // callback to send the rest in 32K chunks).
+
+ // Send the received frame to the renderer process.
+ event_interface_->OnDataFrame(
+ final,
+ is_first_chunk ? opcode : WebSocketFrameHeader::kOpCodeContinuation,
+ data);
+ } else {
+ VLOG(3) << "Ignored data packet received in state " << state_;
+ }
+ return;
+
+ case WebSocketFrameHeader::kOpCodePing:
+ VLOG(1) << "Got Ping of size " << data_buffer->size();
+ if (state_ == CONNECTED) {
+ SendIOBufferWithSize(
+ true, WebSocketFrameHeader::kOpCodePong, data_buffer);
+ } else {
+ VLOG(3) << "Ignored ping in state " << state_;
+ }
+ return;
+
+ case WebSocketFrameHeader::kOpCodePong:
+ VLOG(1) << "Got Pong of size " << data_buffer->size();
+ // We do not need to do anything with pong messages.
+ return;
+
+ case WebSocketFrameHeader::kOpCodeClose: {
+ uint16 code = kWebSocketNormalClosure;
+ std::string reason;
+ ParseClose(data_buffer, &code, &reason);
+ // TODO(ricea): Find a way to safely log the message from the close
+ // message (escape control codes and so on).
+ VLOG(1) << "Got Close with code " << code;
+ switch (state_) {
+ case CONNECTED:
+ state_ = RECV_CLOSED;
+ SendClose(code, reason); // Sets state_ to CLOSE_WAIT
+ event_interface_->OnClosingHandshake();
+ closing_code_ = code;
+ closing_reason_ = reason;
+ break;
+
+ case SEND_CLOSED:
+ state_ = CLOSE_WAIT;
+ // From RFC6455 section 7.1.5: "Each endpoint
+ // will see the status code sent by the other end as _The WebSocket
+ // Connection Close Code_."
+ closing_code_ = code;
+ closing_reason_ = reason;
+ break;
+
+ default:
+ LOG(DFATAL) << "Got Close in unexpected state " << state_;
+ break;
+ }
+ return;
+ }
+
+ default:
+ FailChannel(
+ SEND_REAL_ERROR, kWebSocketErrorProtocolError, "Unknown opcode");
+ return;
+ }
+}
+
+void WebSocketChannel::SendIOBufferWithSize(
+ bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBufferWithSize>& buffer) {
+ DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
+ DCHECK(stream_);
+ scoped_ptr<WebSocketFrameHeader> header(new WebSocketFrameHeader(op_code));
+ header->final = fin;
+ header->masked = true;
+ header->payload_length = buffer->size();
+ scoped_ptr<WebSocketFrameChunk> chunk(new WebSocketFrameChunk());
+ chunk->header = header.Pass();
+ chunk->final_chunk = true;
+ chunk->data = buffer;
+ if (data_being_sent_) {
+ // Either the link to the WebSocket server is saturated, or we are simply
+ // processing a batch of messages.
+ // TODO(ricea): We need to keep some statistics to work out which situation
+ // we are in and adjust quota appropriately.
+ if (!data_to_send_next_)
+ data_to_send_next_.reset(new SendBuffer);
+ data_to_send_next_->AddFrame(chunk.Pass());
+ } else {
+ data_being_sent_.reset(new SendBuffer);
+ data_being_sent_->AddFrame(chunk.Pass());
+ WriteFrames();
+ }
+}
+
+void WebSocketChannel::FailChannel(ExposeError expose,
+ uint16 code,
+ const std::string& reason) {
+ DCHECK_NE(FRESHLY_CONSTRUCTED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ // TODO(ricea): Logging.
+ State old_state = state_;
+ if (state_ == CONNECTED) {
+ uint16 send_code = kWebSocketErrorGoingAway;
+ std::string send_reason = "Internal Error";
+ if (expose == SEND_REAL_ERROR) {
+ send_code = code;
+ send_reason = reason;
+ }
+ SendClose(send_code, send_reason); // Sets state_ to SEND_CLOSED
+ }
+ // Careful study of RFC6455 section 7.1.7 and 7.1.1 indicates we should close
+ // the connection ourselves without waiting for the closing handshake.
+ stream_->Close();
+ state_ = CLOSED;
+
+ // We may be in the middle of processing several chunks. We should not re-use
+ // the frame header.
+ current_frame_header_.reset();
+ if (old_state != CLOSED) {
+ event_interface_->OnDropChannel(code, reason);
+ }
+}
+
+void WebSocketChannel::SendClose(uint16 code, const std::string& reason) {
+ DCHECK(state_ == CONNECTED || state_ == RECV_CLOSED);
+ // TODO(ricea): Ensure reason.length() <= 123
+ scoped_refptr<IOBufferWithSize> body;
+ if (code == kWebSocketErrorNoStatusReceived) {
+ // Special case: translate kWebSocketErrorNoStatusReceived into a Close
+ // frame with no payload.
+ body = new IOBufferWithSize(0);
+ } else {
+ const size_t payload_length = kWebSocketCloseCodeLength + reason.length();
+ body = new IOBufferWithSize(payload_length);
+ WriteBigEndian(body->data(), code);
+ COMPILE_ASSERT(sizeof(code) == kWebSocketCloseCodeLength,
+ they_should_both_be_two);
+ std::copy(
+ reason.begin(), reason.end(), body->data() + kWebSocketCloseCodeLength);
+ }
+ SendIOBufferWithSize(true, WebSocketFrameHeader::kOpCodeClose, body);
+ state_ = (state_ == CONNECTED) ? SEND_CLOSED : CLOSE_WAIT;
+}
+
+void WebSocketChannel::ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
+ uint16* code,
+ std::string* reason) {
+ const char* data = buffer->data();
+ size_t size = base::checked_numeric_cast<size_t>(buffer->size());
+ reason->clear();
+ if (size < kWebSocketCloseCodeLength) {
+ *code = kWebSocketErrorNoStatusReceived;
+ if (size != 0) {
+ VLOG(1) << "Close frame with payload size " << size << " received "
+ << "(the first byte is " << std::hex << static_cast<int>(data[0])
+ << ")";
+ return;
+ }
+ return;
+ }
+ uint16 unchecked_code = 0;
+ ReadBigEndian(data, &unchecked_code);
+ COMPILE_ASSERT(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
+ they_should_both_be_two_bytes);
+ if (unchecked_code >= static_cast<uint16>(kWebSocketNormalClosure) &&
+ unchecked_code <=
+ static_cast<uint16>(kWebSocketErrorPrivateReservedMax)) {
+ *code = unchecked_code;
+ } else {
+ VLOG(1) << "Close frame contained code outside of the valid range: "
+ << unchecked_code;
+ *code = kWebSocketErrorAbnormalClosure;
+ }
+ std::string text(data + kWebSocketCloseCodeLength, data + size);
+ // TODO(ricea): Is this check strict enough? In particular, check the
+ // "Security Considerations" from RFC3629.
+ if (IsStringUTF8(text)) {
+ reason->swap(text);
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_channel.h b/chromium/net/websockets/websocket_channel.h
new file mode 100644
index 00000000000..d81f83a1821
--- /dev/null
+++ b/chromium/net/websockets/websocket_channel.h
@@ -0,0 +1,267 @@
+// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
+#define NET_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_frame.h"
+#include "net/websockets/websocket_stream.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class URLRequestContext;
+class WebSocketEventInterface;
+
+// Transport-independent implementation of WebSockets. Implements protocol
+// semantics that do not depend on the underlying transport. Provides the
+// interface to the content layer. Some WebSocket concepts are used here without
+// definition; please see the RFC at http://tools.ietf.org/html/rfc6455 for
+// clarification.
+class NET_EXPORT WebSocketChannel {
+ public:
+ // The type of a WebSocketStream factory callback. Must match the signature of
+ // WebSocketStream::CreateAndConnectStream().
+ typedef base::Callback<scoped_ptr<WebSocketStreamRequest>(
+ const GURL&,
+ const std::vector<std::string>&,
+ const GURL&,
+ URLRequestContext*,
+ const BoundNetLog&,
+ scoped_ptr<WebSocketStream::ConnectDelegate>)> WebSocketStreamFactory;
+
+ // Creates a new WebSocketChannel with the specified parameters.
+ // SendAddChannelRequest() must be called immediately afterwards to start the
+ // connection process.
+ WebSocketChannel(const GURL& socket_url,
+ scoped_ptr<WebSocketEventInterface> event_interface);
+ virtual ~WebSocketChannel();
+
+ // Starts the connection process.
+ void SendAddChannelRequest(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context);
+
+ // Sends a data frame to the remote side. The frame should usually be no
+ // larger than 32KB to prevent the time required to copy the buffers from from
+ // unduly delaying other tasks that need to run on the IO thread. This method
+ // has a hard limit of 2GB. It is the responsibility of the caller to ensure
+ // that they have sufficient send quota to send this data, otherwise the
+ // connection will be closed without sending. |fin| indicates the last frame
+ // in a message, equivalent to "FIN" as specified in section 5.2 of
+ // RFC6455. |data| is the "Payload Data". If |op_code| is kOpCodeText, or it
+ // is kOpCodeContinuation and the type the message is Text, then |data| must
+ // be a chunk of a valid UTF-8 message, however there is no requirement for
+ // |data| to be split on character boundaries.
+ void SendFrame(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const std::vector<char>& data);
+
+ // Sends |quota| units of flow control to the remote side. If the underlying
+ // transport has a concept of |quota|, then it permits the remote server to
+ // send up to |quota| units of data.
+ void SendFlowControl(int64 quota);
+
+ // Start the closing handshake for a client-initiated shutdown of the
+ // connection. There is no API to close the connection without a closing
+ // handshake, but destroying the WebSocketChannel object while connected will
+ // effectively do that. |code| must be in the range 1000-4999. |reason| should
+ // be a valid UTF-8 string or empty.
+ //
+ // This does *not* trigger the event OnClosingHandshake(). The caller should
+ // assume that the closing handshake has started and perform the equivalent
+ // processing to OnClosingHandshake() if necessary.
+ void StartClosingHandshake(uint16 code, const std::string& reason);
+
+ // Starts the connection process, using a specified factory function rather
+ // than the default. This is exposed for testing.
+ void SendAddChannelRequestForTesting(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory);
+
+ private:
+ // We have a simple linear progression of states from FRESHLY_CONSTRUCTED to
+ // CLOSED, except that the SEND_CLOSED and RECV_CLOSED states may be skipped
+ // in case of error.
+ enum State {
+ FRESHLY_CONSTRUCTED,
+ CONNECTING,
+ CONNECTED,
+ SEND_CLOSED, // We have sent a Close frame but not received a Close frame.
+ RECV_CLOSED, // Used briefly between receiving a Close frame and sending
+ // the response. Once we have responded, the state changes
+ // to CLOSED.
+ CLOSE_WAIT, // The Closing Handshake has completed, but the remote server
+ // has not yet closed the connection.
+ CLOSED, // The Closing Handshake has completed and the connection
+ // has been closed; or the connection is failed.
+ };
+
+ // When failing a channel, we may or may not want to send the real reason for
+ // failing to the remote server. This enum is used by FailChannel() to
+ // choose.
+ enum ExposeError {
+ SEND_REAL_ERROR,
+ SEND_GOING_AWAY,
+ };
+
+ // Our implementation of WebSocketStream::ConnectDelegate. We do not inherit
+ // from WebSocketStream::ConnectDelegate directly to avoid cluttering our
+ // public interface with the implementation of those methods, and because the
+ // lifetime of a WebSocketChannel is longer than the lifetime of the
+ // connection process.
+ class ConnectDelegate;
+
+ // Starts the connection progress, using a specified factory function.
+ void SendAddChannelRequestWithFactory(
+ const std::vector<std::string>& requested_protocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const WebSocketStreamFactory& factory);
+
+ // Success callback from WebSocketStream::CreateAndConnectStream(). Reports
+ // success to the event interface.
+ void OnConnectSuccess(scoped_ptr<WebSocketStream> stream);
+
+ // Failure callback from WebSocketStream::CreateAndConnectStream(). Reports
+ // failure to the event interface.
+ void OnConnectFailure(uint16 websocket_error);
+
+ // Returns true if state_ is SEND_CLOSED, CLOSE_WAIT or CLOSED.
+ bool InClosingState() const;
+
+ // Calls WebSocketStream::WriteFrames() with the appropriate arguments
+ void WriteFrames();
+
+ // Callback from WebSocketStream::WriteFrames. Sends pending data or adjusts
+ // the send quota of the renderer channel as appropriate. |result| is a net
+ // error code, usually OK. If |synchronous| is true, then OnWriteDone() is
+ // being called from within the WriteFrames() loop and does not need to call
+ // WriteFrames() itself.
+ void OnWriteDone(bool synchronous, int result);
+
+ // Calls WebSocketStream::ReadFrames() with the appropriate arguments.
+ void ReadFrames();
+
+ // Callback from WebSocketStream::ReadFrames. Handles any errors and processes
+ // the returned chunks appropriately to their type. |result| is a net error
+ // code. If |synchronous| is true, then OnReadDone() is being called from
+ // within the ReadFrames() loop and does not need to call ReadFrames() itself.
+ void OnReadDone(bool synchronous, int result);
+
+ // Processes a single chunk that has been read from the stream.
+ void ProcessFrameChunk(scoped_ptr<WebSocketFrameChunk> chunk);
+
+ // Handle a frame that we have received enough of to process. May call
+ // event_interface_ methods, send responses to the server, and change the
+ // value of state_.
+ void HandleFrame(const WebSocketFrameHeader::OpCode opcode,
+ bool is_first_chunk,
+ bool is_final_chunk,
+ const scoped_refptr<IOBufferWithSize>& data_buffer);
+
+ // Low-level method to send a single frame. Used for both data and control
+ // frames. Either sends the frame immediately or buffers it to be scheduled
+ // when the current write finishes. |fin| and |op_code| are defined as for
+ // SendFrame() above, except that |op_code| may also be a control frame
+ // opcode.
+ void SendIOBufferWithSize(bool fin,
+ WebSocketFrameHeader::OpCode op_code,
+ const scoped_refptr<IOBufferWithSize>& buffer);
+
+ // Perform the "Fail the WebSocket Connection" operation as defined in
+ // RFC6455. The supplied code and reason are sent back to the renderer in an
+ // OnDropChannel message. If state_ is CONNECTED then a Close message is sent
+ // to the remote host. If |expose| is SEND_REAL_ERROR then the remote host is
+ // given the same status code we gave the renderer; otherwise it is sent a
+ // fixed "Going Away" code. Resets current_frame_header_, closes the
+ // stream_, and sets state_ to CLOSED.
+ void FailChannel(ExposeError expose, uint16 code, const std::string& reason);
+
+ // Sends a Close frame to Start the WebSocket Closing Handshake, or to respond
+ // to a Close frame from the server. As a special case, setting |code| to
+ // kWebSocketErrorNoStatusReceived will create a Close frame with no payload;
+ // this is symmetric with the behaviour of ParseClose.
+ void SendClose(uint16 code, const std::string& reason);
+
+ // Parses a Close frame. If no status code is supplied, then |code| is set to
+ // 1005 (No status code) with empty |reason|. If the supplied code is
+ // outside the valid range, then 1002 (Protocol error) is set instead. If the
+ // reason text is not valid UTF-8, then |reason| is set to an empty string
+ // instead.
+ void ParseClose(const scoped_refptr<IOBufferWithSize>& buffer,
+ uint16* code,
+ std::string* reason);
+
+ // The URL to which we connect.
+ const GURL socket_url_;
+
+ // The object receiving events.
+ const scoped_ptr<WebSocketEventInterface> event_interface_;
+
+ // The WebSocketStream to which we are sending/receiving data.
+ scoped_ptr<WebSocketStream> stream_;
+
+ // A data structure containing a vector of frames to be sent and the total
+ // number of bytes contained in the vector.
+ class SendBuffer;
+ // Data that is currently pending write, or NULL if no write is pending.
+ scoped_ptr<SendBuffer> data_being_sent_;
+ // Data that is queued up to write after the current write completes.
+ // Only non-NULL when such data actually exists.
+ scoped_ptr<SendBuffer> data_to_send_next_;
+
+ // Destination for the current call to WebSocketStream::ReadFrames
+ ScopedVector<WebSocketFrameChunk> read_frame_chunks_;
+ // Frame header for the frame currently being received. Only non-NULL while we
+ // are processing the frame. If the frame arrives in multiple chunks, can
+ // remain non-NULL while we wait for additional chunks to arrive. If the
+ // header of the frame was invalid, this is set to NULL, the channel is
+ // failed, and subsequent chunks of the same frame will be ignored.
+ scoped_ptr<WebSocketFrameHeader> current_frame_header_;
+ // Handle to an in-progress WebSocketStream creation request. Only non-NULL
+ // during the connection process.
+ scoped_ptr<WebSocketStreamRequest> stream_request_;
+ // Although it will almost never happen in practice, we can be passed an
+ // incomplete control frame, in which case we need to keep the data around
+ // long enough to reassemble it. This variable will be NULL the rest of the
+ // time.
+ scoped_refptr<IOBufferWithSize> incomplete_control_frame_body_;
+ // The point at which we give the renderer a quota refresh (quota units).
+ // "quota units" are currently bytes. TODO(ricea): Update the definition of
+ // quota units when necessary.
+ int send_quota_low_water_mark_;
+ // The amount which we refresh the quota to when it reaches the
+ // low_water_mark (quota units).
+ int send_quota_high_water_mark_;
+ // The current amount of quota that the renderer has available for sending
+ // on this logical channel (quota units).
+ int current_send_quota_;
+
+ // Storage for the status code and reason from the time we receive the Close
+ // frame until the connection is closed and we can call OnDropChannel().
+ uint16 closing_code_;
+ std::string closing_reason_;
+
+ // The current state of the channel. Mainly used for sanity checking, but also
+ // used to track the close state.
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketChannel);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_CHANNEL_H_
diff --git a/chromium/net/websockets/websocket_channel_test.cc b/chromium/net/websockets/websocket_channel_test.cc
new file mode 100644
index 00000000000..25e9cdc0508
--- /dev/null
+++ b/chromium/net/websockets/websocket_channel_test.cc
@@ -0,0 +1,1886 @@
+// Copyright 2013 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/websockets/websocket_channel.h"
+
+#include <string.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/safe_numerics.h"
+#include "base/strings/string_piece.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_event_interface.h"
+#include "net/websockets/websocket_mux.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// Hacky macros to construct the body of a Close message from a code and a
+// string, while ensuring the result is a compile-time constant string.
+// Use like CLOSE_DATA(NORMAL_CLOSURE, "Explanation String")
+#define CLOSE_DATA(code, string) WEBSOCKET_CLOSE_CODE_AS_STRING_##code string
+#define WEBSOCKET_CLOSE_CODE_AS_STRING_NORMAL_CLOSURE "\x03\xe8"
+#define WEBSOCKET_CLOSE_CODE_AS_STRING_GOING_AWAY "\x03\xe9"
+#define WEBSOCKET_CLOSE_CODE_AS_STRING_SERVER_ERROR "\x03\xf3"
+
+namespace net {
+
+// Printing helpers to allow GoogleMock to print frame chunks. These are
+// explicitly designed to look like the static initialisation format we use in
+// these tests. They have to live in the net namespace in order to be found by
+// GoogleMock; a nested anonymous namespace will not work.
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameHeader& header) {
+ return os << "{" << (header.final ? "FINAL_FRAME" : "NOT_FINAL_FRAME") << ", "
+ << header.opcode << ", "
+ << (header.masked ? "MASKED" : "NOT_MASKED") << ", "
+ << header.payload_length << "}";
+}
+
+std::ostream& operator<<(std::ostream& os, const WebSocketFrameChunk& chunk) {
+ os << "{";
+ if (chunk.header) {
+ os << *chunk.header;
+ } else {
+ os << "{NO_HEADER}";
+ }
+ return os << ", " << (chunk.final_chunk ? "FINAL_CHUNK" : "NOT_FINAL_CHUNK")
+ << ", \""
+ << base::StringPiece(chunk.data->data(), chunk.data->size())
+ << "\"}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const ScopedVector<WebSocketFrameChunk>& vector) {
+ os << "{";
+ bool first = true;
+ for (ScopedVector<WebSocketFrameChunk>::const_iterator it = vector.begin();
+ it != vector.end();
+ ++it) {
+ if (!first) {
+ os << ",\n";
+ } else {
+ first = false;
+ }
+ os << **it;
+ }
+ return os << "}";
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const ScopedVector<WebSocketFrameChunk>* vector) {
+ return os << '&' << *vector;
+}
+
+namespace {
+
+using ::testing::AnyNumber;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+using ::testing::_;
+
+// A selection of characters that have traditionally been mangled in some
+// environment or other, for testing 8-bit cleanliness.
+const char kBinaryBlob[] = {'\n', '\r', // BACKWARDS CRNL
+ '\0', // nul
+ '\x7F', // DEL
+ '\x80', '\xFF', // NOT VALID UTF-8
+ '\x1A', // Control-Z, EOF on DOS
+ '\x03', // Control-C
+ '\x04', // EOT, special for Unix terms
+ '\x1B', // ESC, often special
+ '\b', // backspace
+ '\'', // single-quote, special in PHP
+};
+const size_t kBinaryBlobSize = arraysize(kBinaryBlob);
+
+// The amount of quota a new connection gets by default.
+// TODO(ricea): If kDefaultSendQuotaHighWaterMark changes, then this value will
+// need to be updated.
+const size_t kDefaultInitialQuota = 1 << 17;
+// The amount of bytes we need to send after the initial connection to trigger a
+// quota refresh. TODO(ricea): Change this if kDefaultSendQuotaHighWaterMark or
+// kDefaultSendQuotaLowWaterMark change.
+const size_t kDefaultQuotaRefreshTrigger = (1 << 16) + 1;
+
+// This mock is for testing expectations about how the EventInterface is used.
+class MockWebSocketEventInterface : public WebSocketEventInterface {
+ public:
+ MOCK_METHOD2(OnAddChannelResponse, void(bool, const std::string&));
+ MOCK_METHOD3(OnDataFrame,
+ void(bool, WebSocketMessageType, const std::vector<char>&));
+ MOCK_METHOD1(OnFlowControl, void(int64));
+ MOCK_METHOD0(OnClosingHandshake, void(void));
+ MOCK_METHOD2(OnDropChannel, void(uint16, const std::string&));
+};
+
+// This fake EventInterface is for tests which need a WebSocketEventInterface
+// implementation but are not verifying how it is used.
+class FakeWebSocketEventInterface : public WebSocketEventInterface {
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol) OVERRIDE {}
+ virtual void OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) OVERRIDE {}
+ virtual void OnFlowControl(int64 quota) OVERRIDE {}
+ virtual void OnClosingHandshake() OVERRIDE {}
+ virtual void OnDropChannel(uint16 code, const std::string& reason) OVERRIDE {}
+};
+
+// This fake WebSocketStream is for tests that require a WebSocketStream but are
+// not testing the way it is used. It has minimal functionality to return
+// the |protocol| and |extensions| that it was constructed with.
+class FakeWebSocketStream : public WebSocketStream {
+ public:
+ // Constructs with empty protocol and extensions.
+ FakeWebSocketStream() {}
+
+ // Constructs with specified protocol and extensions.
+ FakeWebSocketStream(const std::string& protocol,
+ const std::string& extensions)
+ : protocol_(protocol), extensions_(extensions) {}
+
+ virtual int SendHandshakeRequest(
+ const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadHandshakeResponse(
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_IO_PENDING;
+ }
+
+ virtual void Close() OVERRIDE {}
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetSubProtocol() const OVERRIDE { return protocol_; }
+
+ // Returns the string passed to the constructor.
+ virtual std::string GetExtensions() const OVERRIDE { return extensions_; }
+
+ private:
+ // The string to return from GetSubProtocol().
+ std::string protocol_;
+
+ // The string to return from GetExtensions().
+ std::string extensions_;
+};
+
+// To make the static initialisers easier to read, we use enums rather than
+// bools.
+
+// NO_HEADER means there shouldn't be a header included in the generated
+// WebSocketFrameChunk. The static initialiser always has a header, but we can
+// avoid specifying the rest of the fields.
+enum IsFinal {
+ NO_HEADER,
+ NOT_FINAL_FRAME,
+ FINAL_FRAME
+};
+
+enum IsMasked {
+ NOT_MASKED,
+ MASKED
+};
+
+enum IsFinalChunk {
+ NOT_FINAL_CHUNK,
+ FINAL_CHUNK
+};
+
+// This is used to initialise a WebSocketFrameChunk but is statically
+// initialisable.
+struct InitFrameChunk {
+ struct FrameHeader {
+ IsFinal final;
+ // Reserved fields omitted for now. Add them if you need them.
+ WebSocketFrameHeader::OpCode opcode;
+ IsMasked masked;
+ // payload_length is the length of the whole frame. The length of the data
+ // members from every chunk in the frame must add up to the payload_length.
+ uint64 payload_length;
+ };
+ FrameHeader header;
+
+ // Directly equivalent to WebSocketFrameChunk::final_chunk
+ IsFinalChunk final_chunk;
+
+ // Will be used to create the IOBuffer member. Can be NULL for NULL data. Is a
+ // nul-terminated string for ease-of-use. This means it is not 8-bit clean,
+ // but this is not an issue for test data.
+ const char* const data;
+};
+
+// For GoogleMock
+std::ostream& operator<<(std::ostream& os, const InitFrameChunk& chunk) {
+ os << "{";
+ if (chunk.header.final != NO_HEADER) {
+ os << "{" << (chunk.header.final == FINAL_FRAME ? "FINAL_FRAME"
+ : "NOT_FINAL_FRAME") << ", "
+ << chunk.header.opcode << ", "
+ << (chunk.header.masked == MASKED ? "MASKED" : "NOT_MASKED") << ", "
+ << chunk.header.payload_length << "}";
+
+ } else {
+ os << "{NO_HEADER}";
+ }
+ return os << ", " << (chunk.final_chunk == FINAL_CHUNK ? "FINAL_CHUNK"
+ : "NOT_FINAL_CHUNK")
+ << ", \"" << chunk.data << "\"}";
+}
+
+template <size_t N>
+std::ostream& operator<<(std::ostream& os, const InitFrameChunk (&chunks)[N]) {
+ os << "{";
+ bool first = true;
+ for (size_t i = 0; i < N; ++i) {
+ if (!first) {
+ os << ",\n";
+ } else {
+ first = false;
+ }
+ os << chunks[i];
+ }
+ return os << "}";
+}
+
+// Convert a const array of InitFrameChunks to the format used at
+// runtime. Templated on the size of the array to save typing.
+template <size_t N>
+ScopedVector<WebSocketFrameChunk> CreateFrameChunkVector(
+ const InitFrameChunk (&source_chunks)[N]) {
+ ScopedVector<WebSocketFrameChunk> result_chunks;
+ result_chunks.reserve(N);
+ for (size_t i = 0; i < N; ++i) {
+ scoped_ptr<WebSocketFrameChunk> result_chunk(new WebSocketFrameChunk);
+ size_t chunk_length =
+ source_chunks[i].data ? strlen(source_chunks[i].data) : 0;
+ if (source_chunks[i].header.final != NO_HEADER) {
+ const InitFrameChunk::FrameHeader& source_header =
+ source_chunks[i].header;
+ scoped_ptr<WebSocketFrameHeader> result_header(
+ new WebSocketFrameHeader(source_header.opcode));
+ result_header->final = (source_header.final == FINAL_FRAME);
+ result_header->masked = (source_header.masked == MASKED);
+ result_header->payload_length = source_header.payload_length;
+ DCHECK(chunk_length <= source_header.payload_length);
+ result_chunk->header.swap(result_header);
+ }
+ result_chunk->final_chunk = (source_chunks[i].final_chunk == FINAL_CHUNK);
+ if (source_chunks[i].data) {
+ result_chunk->data = new IOBufferWithSize(chunk_length);
+ memcpy(result_chunk->data->data(), source_chunks[i].data, chunk_length);
+ }
+ result_chunks.push_back(result_chunk.release());
+ }
+ return result_chunks.Pass();
+}
+
+// A GoogleMock action which can be used to respond to call to ReadFrames with
+// some frames. Use like ReadFrames(_, _).WillOnce(ReturnChunks(&chunks));
+// |chunks| is an array of InitFrameChunks needs to be passed by pointer because
+// otherwise it will be reduced to a pointer and lose the array size
+// information.
+ACTION_P(ReturnChunks, source_chunks) {
+ *arg0 = CreateFrameChunkVector(*source_chunks);
+ return OK;
+}
+
+// The implementation of a GoogleMock matcher which can be used to compare a
+// ScopedVector<WebSocketFrameChunk>* against an expectation defined as an array
+// of InitFrameChunks. Although it is possible to compose built-in GoogleMock
+// matchers to check the contents of a WebSocketFrameChunk, the results are so
+// unreadable that it is better to use this matcher.
+template <size_t N>
+class EqualsChunksMatcher
+ : public ::testing::MatcherInterface<ScopedVector<WebSocketFrameChunk>*> {
+ public:
+ EqualsChunksMatcher(const InitFrameChunk (*expect_chunks)[N])
+ : expect_chunks_(expect_chunks) {}
+
+ virtual bool MatchAndExplain(ScopedVector<WebSocketFrameChunk>* actual_chunks,
+ ::testing::MatchResultListener* listener) const {
+ if (actual_chunks->size() != N) {
+ *listener << "the vector size is " << actual_chunks->size();
+ return false;
+ }
+ for (size_t i = 0; i < N; ++i) {
+ const WebSocketFrameChunk& actual_chunk = *(*actual_chunks)[i];
+ const InitFrameChunk& expected_chunk = (*expect_chunks_)[i];
+ // Testing that the absence or presence of a header is the same for both.
+ if ((!actual_chunk.header) !=
+ (expected_chunk.header.final == NO_HEADER)) {
+ *listener << "the header is "
+ << (actual_chunk.header ? "present" : "absent");
+ return false;
+ }
+ if (actual_chunk.header) {
+ if (actual_chunk.header->final !=
+ (expected_chunk.header.final == FINAL_FRAME)) {
+ *listener << "the frame is marked as "
+ << (actual_chunk.header->final ? "" : "not ") << "final";
+ return false;
+ }
+ if (actual_chunk.header->opcode != expected_chunk.header.opcode) {
+ *listener << "the opcode is " << actual_chunk.header->opcode;
+ return false;
+ }
+ if (actual_chunk.header->masked !=
+ (expected_chunk.header.masked == MASKED)) {
+ *listener << "the frame is "
+ << (actual_chunk.header->masked ? "masked" : "not masked");
+ return false;
+ }
+ if (actual_chunk.header->payload_length !=
+ expected_chunk.header.payload_length) {
+ *listener << "the payload length is "
+ << actual_chunk.header->payload_length;
+ return false;
+ }
+ }
+ if (actual_chunk.final_chunk !=
+ (expected_chunk.final_chunk == FINAL_CHUNK)) {
+ *listener << "the chunk is marked as "
+ << (actual_chunk.final_chunk ? "" : "not ") << "final";
+ return false;
+ }
+ if (actual_chunk.data->size() !=
+ base::checked_numeric_cast<int>(strlen(expected_chunk.data))) {
+ *listener << "the data size is " << actual_chunk.data->size();
+ return false;
+ }
+ if (memcmp(actual_chunk.data->data(),
+ expected_chunk.data,
+ actual_chunk.data->size()) != 0) {
+ *listener << "the data content differs";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ virtual void DescribeTo(std::ostream* os) const {
+ *os << "matches " << *expect_chunks_;
+ }
+
+ virtual void DescribeNegationTo(std::ostream* os) const {
+ *os << "does not match " << *expect_chunks_;
+ }
+
+ private:
+ const InitFrameChunk (*expect_chunks_)[N];
+};
+
+// The definition of EqualsChunks GoogleMock matcher. Unlike the ReturnChunks
+// action, this can take the array by reference.
+template <size_t N>
+::testing::Matcher<ScopedVector<WebSocketFrameChunk>*> EqualsChunks(
+ const InitFrameChunk (&chunks)[N]) {
+ return ::testing::MakeMatcher(new EqualsChunksMatcher<N>(&chunks));
+}
+
+// A FakeWebSocketStream whose ReadFrames() function returns data.
+class ReadableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ enum IsSync {
+ SYNC,
+ ASYNC
+ };
+
+ // After constructing the object, call PrepareReadFrames() once for each
+ // time you wish it to return from the test.
+ ReadableFakeWebSocketStream() : index_(0), read_frames_pending_(false) {}
+
+ // Check that all the prepared responses have been consumed.
+ virtual ~ReadableFakeWebSocketStream() {
+ CHECK(index_ >= responses_.size());
+ CHECK(!read_frames_pending_);
+ }
+
+ // Prepares a fake responses. Fake responses will be returned from
+ // ReadFrames() in the same order they were prepared with PrepareReadFrames()
+ // and PrepareReadFramesError(). If |async| is ASYNC, then ReadFrames() will
+ // return ERR_IO_PENDING and the callback will be scheduled to run on the
+ // message loop. This requires the test case to run the message loop. If
+ // |async| is SYNC, the response will be returned synchronously. |error| is
+ // returned directly from ReadFrames() in the synchronous case, or passed to
+ // the callback in the asynchronous case. |chunks| will be converted to a
+ // ScopedVector<WebSocketFrameChunks> and copied to the pointer that was
+ // passed to ReadFrames().
+ template <size_t N>
+ void PrepareReadFrames(IsSync async,
+ int error,
+ const InitFrameChunk (&chunks)[N]) {
+ responses_.push_back(
+ new Response(async, error, CreateFrameChunkVector(chunks)));
+ }
+
+ // An alternate version of PrepareReadFrames for when we need to construct
+ // the frames manually.
+ void PrepareRawReadFrames(IsSync async,
+ int error,
+ ScopedVector<WebSocketFrameChunk> chunks) {
+ responses_.push_back(new Response(async, error, chunks.Pass()));
+ }
+
+ // Prepares a fake error response (ie. there is no data).
+ void PrepareReadFramesError(IsSync async, int error) {
+ responses_.push_back(
+ new Response(async, error, ScopedVector<WebSocketFrameChunk>()));
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ CHECK(!read_frames_pending_);
+ if (index_ >= responses_.size())
+ return ERR_IO_PENDING;
+ if (responses_[index_]->async == ASYNC) {
+ read_frames_pending_ = true;
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ReadableFakeWebSocketStream::DoCallback,
+ base::Unretained(this),
+ frame_chunks,
+ callback));
+ return ERR_IO_PENDING;
+ } else {
+ frame_chunks->swap(responses_[index_]->chunks);
+ return responses_[index_++]->error;
+ }
+ }
+
+ private:
+ void DoCallback(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) {
+ read_frames_pending_ = false;
+ frame_chunks->swap(responses_[index_]->chunks);
+ callback.Run(responses_[index_++]->error);
+ return;
+ }
+
+ struct Response {
+ Response(IsSync async, int error, ScopedVector<WebSocketFrameChunk> chunks)
+ : async(async), error(error), chunks(chunks.Pass()) {}
+
+ IsSync async;
+ int error;
+ ScopedVector<WebSocketFrameChunk> chunks;
+
+ private:
+ // Bad things will happen if we attempt to copy or assign "chunks".
+ DISALLOW_COPY_AND_ASSIGN(Response);
+ };
+ ScopedVector<Response> responses_;
+
+ // The index into the responses_ array of the next response to be returned.
+ size_t index_;
+
+ // True when an async response from ReadFrames() is pending. This only applies
+ // to "real" async responses. Once all the prepared responses have been
+ // returned, ReadFrames() returns ERR_IO_PENDING but read_frames_pending_ is
+ // not set to true.
+ bool read_frames_pending_;
+};
+
+// A FakeWebSocketStream where writes always complete successfully and
+// synchronously.
+class WriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+};
+
+// A FakeWebSocketStream where writes always fail.
+class UnWriteableFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ return ERR_CONNECTION_RESET;
+ }
+};
+
+// A FakeWebSocketStream which echoes any frames written back. Clears the
+// "masked" header bit, but makes no other checks for validity. Tests using this
+// must run the MessageLoop to receive the callback(s). If a message with opcode
+// Close is echoed, then an ERR_CONNECTION_CLOSED is returned in the next
+// callback. The test must do something to cause WriteFrames() to be called,
+// otherwise the ReadFrames() callback will never be called.
+class EchoeyFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ EchoeyFakeWebSocketStream() : read_frame_chunks_(NULL), done_(false) {}
+
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ // Users of WebSocketStream will not expect the ReadFrames() callback to be
+ // called from within WriteFrames(), so post it to the message loop instead.
+ stored_frame_chunks_.insert(
+ stored_frame_chunks_.end(), frame_chunks->begin(), frame_chunks->end());
+ frame_chunks->weak_clear();
+ PostCallback();
+ return OK;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ read_callback_ = callback;
+ read_frame_chunks_ = frame_chunks;
+ if (done_)
+ PostCallback();
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ void PostCallback() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&EchoeyFakeWebSocketStream::DoCallback,
+ base::Unretained(this)));
+ }
+
+ void DoCallback() {
+ if (done_) {
+ read_callback_.Run(ERR_CONNECTION_CLOSED);
+ } else if (!stored_frame_chunks_.empty()) {
+ done_ = MoveFrameChunks(read_frame_chunks_);
+ read_frame_chunks_ = NULL;
+ read_callback_.Run(OK);
+ }
+ }
+
+ // Copy the chunks stored in stored_frame_chunks_ to |out|, while clearing the
+ // "masked" header bit. Returns true if a Close Frame was seen, false
+ // otherwise.
+ bool MoveFrameChunks(ScopedVector<WebSocketFrameChunk>* out) {
+ bool seen_close = false;
+ *out = stored_frame_chunks_.Pass();
+ for (ScopedVector<WebSocketFrameChunk>::iterator it = out->begin();
+ it != out->end();
+ ++it) {
+ WebSocketFrameHeader* header = (*it)->header.get();
+ if (header) {
+ header->masked = false;
+ if (header->opcode == WebSocketFrameHeader::kOpCodeClose)
+ seen_close = true;
+ }
+ }
+ return seen_close;
+ }
+
+ ScopedVector<WebSocketFrameChunk> stored_frame_chunks_;
+ CompletionCallback read_callback_;
+ // Owned by the caller of ReadFrames().
+ ScopedVector<WebSocketFrameChunk>* read_frame_chunks_;
+ // True if we should close the connection.
+ bool done_;
+};
+
+// A FakeWebSocketStream where writes trigger a connection reset.
+// This differs from UnWriteableFakeWebSocketStream in that it is asynchronous
+// and triggers ReadFrames to return a reset as well. Tests using this need to
+// run the message loop.
+class ResetOnWriteFakeWebSocketStream : public FakeWebSocketStream {
+ public:
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, ERR_CONNECTION_RESET));
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(read_callback_, ERR_CONNECTION_RESET));
+ return ERR_IO_PENDING;
+ }
+
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) OVERRIDE {
+ read_callback_ = callback;
+ return ERR_IO_PENDING;
+ }
+
+ private:
+ CompletionCallback read_callback_;
+};
+
+// This mock is for verifying that WebSocket protocol semantics are obeyed (to
+// the extent that they are implemented in WebSocketCommon).
+class MockWebSocketStream : public WebSocketStream {
+ public:
+ MOCK_METHOD2(ReadFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD2(WriteFrames,
+ int(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback));
+ MOCK_METHOD0(Close, void());
+ MOCK_CONST_METHOD0(GetSubProtocol, std::string());
+ MOCK_CONST_METHOD0(GetExtensions, std::string());
+ MOCK_METHOD0(AsWebSocketStream, WebSocketStream*());
+ MOCK_METHOD4(SendHandshakeRequest,
+ int(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback));
+ MOCK_METHOD1(ReadHandshakeResponse, int(const CompletionCallback& callback));
+};
+
+struct ArgumentCopyingWebSocketFactory {
+ scoped_ptr<WebSocketStreamRequest> Factory(
+ const GURL& socket_url,
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const BoundNetLog& net_log,
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate) {
+ this->socket_url = socket_url;
+ this->requested_subprotocols = requested_subprotocols;
+ this->origin = origin;
+ this->url_request_context = url_request_context;
+ this->net_log = net_log;
+ this->connect_delegate = connect_delegate.Pass();
+ return make_scoped_ptr(new WebSocketStreamRequest);
+ }
+
+ GURL socket_url;
+ GURL origin;
+ std::vector<std::string> requested_subprotocols;
+ URLRequestContext* url_request_context;
+ BoundNetLog net_log;
+ scoped_ptr<WebSocketStream::ConnectDelegate> connect_delegate;
+};
+
+// Converts a std::string to a std::vector<char>. For test purposes, it is
+// convenient to be able to specify data as a string, but the
+// WebSocketEventInterface requires the vector<char> type.
+std::vector<char> AsVector(const std::string& s) {
+ return std::vector<char>(s.begin(), s.end());
+}
+
+// Base class for all test fixtures.
+class WebSocketChannelTest : public ::testing::Test {
+ protected:
+ WebSocketChannelTest() : stream_(new FakeWebSocketStream) {}
+
+ // Creates a new WebSocketChannel and connects it, using the settings stored
+ // in |connect_data_|.
+ void CreateChannelAndConnect() {
+ channel_.reset(
+ new WebSocketChannel(connect_data_.url, CreateEventInterface()));
+ channel_->SendAddChannelRequestForTesting(
+ connect_data_.requested_subprotocols,
+ connect_data_.origin,
+ &connect_data_.url_request_context,
+ base::Bind(&ArgumentCopyingWebSocketFactory::Factory,
+ base::Unretained(&connect_data_.factory)));
+ }
+
+ // Same as CreateChannelAndConnect(), but calls the on_success callback as
+ // well. This method is virtual so that subclasses can also set the stream.
+ virtual void CreateChannelAndConnectSuccessfully() {
+ CreateChannelAndConnect();
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+ }
+
+ // Returns a WebSocketEventInterface to be passed to the WebSocketChannel.
+ // This implementation returns a newly-created fake. Subclasses may return a
+ // mock instead.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(new FakeWebSocketEventInterface);
+ }
+
+ // This method serves no other purpose than to provide a nice syntax for
+ // assigning to stream_. class T must be a subclass of WebSocketStream or you
+ // will have unpleasant compile errors.
+ template <class T>
+ void set_stream(scoped_ptr<T> stream) {
+ // Since the definition of "PassAs" depends on the type T, the C++ standard
+ // requires the "template" keyword to indicate that "PassAs" should be
+ // parsed as a template method.
+ stream_ = stream.template PassAs<WebSocketStream>();
+ }
+
+ // A struct containing the data that will be used to connect the channel.
+ struct ConnectData {
+ // URL to (pretend to) connect to.
+ GURL url;
+ // Origin of the request
+ GURL origin;
+ // Requested protocols for the request.
+ std::vector<std::string> requested_subprotocols;
+ // URLRequestContext object.
+ URLRequestContext url_request_context;
+ // A fake WebSocketFactory that just records its arguments.
+ ArgumentCopyingWebSocketFactory factory;
+ };
+ ConnectData connect_data_;
+
+ // The channel we are testing. Not initialised until SetChannel() is called.
+ scoped_ptr<WebSocketChannel> channel_;
+
+ // A mock or fake stream for tests that need one.
+ scoped_ptr<WebSocketStream> stream_;
+};
+
+class WebSocketChannelDeletingTest : public WebSocketChannelTest {
+ public:
+ void ResetChannel() { channel_.reset(); }
+
+ protected:
+ // Create a ChannelDeletingFakeWebSocketEventInterface. Defined out-of-line to
+ // avoid circular dependency.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE;
+};
+
+// A FakeWebSocketEventInterface that deletes the WebSocketChannel on failure to
+// connect.
+class ChannelDeletingFakeWebSocketEventInterface
+ : public FakeWebSocketEventInterface {
+ public:
+ ChannelDeletingFakeWebSocketEventInterface(
+ WebSocketChannelDeletingTest* fixture)
+ : fixture_(fixture) {}
+
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_protocol) OVERRIDE {
+ if (fail) {
+ fixture_->ResetChannel();
+ }
+ }
+
+ private:
+ // A pointer to the test fixture. Owned by the test harness; this object will
+ // be deleted before it is.
+ WebSocketChannelDeletingTest* fixture_;
+};
+
+scoped_ptr<WebSocketEventInterface>
+WebSocketChannelDeletingTest::CreateEventInterface() {
+ return scoped_ptr<WebSocketEventInterface>(
+ new ChannelDeletingFakeWebSocketEventInterface(this));
+}
+
+// Base class for tests which verify that EventInterface methods are called
+// appropriately.
+class WebSocketChannelEventInterfaceTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelEventInterfaceTest()
+ : event_interface_(new StrictMock<MockWebSocketEventInterface>) {}
+
+ // Tests using this fixture must set expectations on the event_interface_ mock
+ // object before calling CreateChannelAndConnect() or
+ // CreateChannelAndConnectSuccessfully(). This will only work once per test
+ // case, but once should be enough.
+ virtual scoped_ptr<WebSocketEventInterface> CreateEventInterface() OVERRIDE {
+ return scoped_ptr<WebSocketEventInterface>(event_interface_.release());
+ }
+
+ scoped_ptr<MockWebSocketEventInterface> event_interface_;
+};
+
+// Base class for tests which verify that WebSocketStream methods are called
+// appropriately by using a MockWebSocketStream.
+class WebSocketChannelStreamTest : public WebSocketChannelTest {
+ protected:
+ WebSocketChannelStreamTest()
+ : mock_stream_(new StrictMock<MockWebSocketStream>) {}
+
+ virtual void CreateChannelAndConnectSuccessfully() OVERRIDE {
+ set_stream(mock_stream_.Pass());
+ WebSocketChannelTest::CreateChannelAndConnectSuccessfully();
+ }
+
+ scoped_ptr<MockWebSocketStream> mock_stream_;
+};
+
+// Simple test that everything that should be passed to the factory function is
+// passed to the factory function.
+TEST_F(WebSocketChannelTest, EverythingIsPassedToTheFactoryFunction) {
+ connect_data_.url = GURL("ws://example.com/test");
+ connect_data_.origin = GURL("http://example.com/test");
+ connect_data_.requested_subprotocols.push_back("Sinbad");
+
+ CreateChannelAndConnect();
+
+ EXPECT_EQ(connect_data_.url, connect_data_.factory.socket_url);
+ EXPECT_EQ(connect_data_.origin, connect_data_.factory.origin);
+ EXPECT_EQ(connect_data_.requested_subprotocols,
+ connect_data_.factory.requested_subprotocols);
+ EXPECT_EQ(&connect_data_.url_request_context,
+ connect_data_.factory.url_request_context);
+}
+
+// The documentation for WebSocketEventInterface::OnAddChannelResponse() says
+// that if the first argument is true, ie. the connection failed, then we can
+// safely synchronously delete the WebSocketChannel. This test will only
+// reliably find problems if run with a memory debugger such as
+// AddressSanitizer.
+TEST_F(WebSocketChannelDeletingTest, DeletingFromOnAddChannelResponseWorks) {
+ CreateChannelAndConnect();
+ connect_data_.factory.connect_delegate
+ ->OnFailure(kWebSocketErrorNoStatusReceived);
+ EXPECT_EQ(NULL, channel_.get());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectSuccessReported) {
+ // false means success.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, ""));
+ // OnFlowControl is always called immediately after connect to provide initial
+ // quota to the renderer.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(stream_.Pass());
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectFailureReported) {
+ // true means failure.
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(true, ""));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate
+ ->OnFailure(kWebSocketErrorNoStatusReceived);
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, ProtocolPassed) {
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, "Bob"));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ CreateChannelAndConnect();
+
+ connect_data_.factory.connect_delegate->OnSuccess(
+ scoped_ptr<WebSocketStream>(new FakeWebSocketStream("Bob", "")));
+}
+
+// The first frames from the server can arrive together with the handshake, in
+// which case they will be available as soon as ReadFrames() is called the first
+// time.
+TEST_F(WebSocketChannelEventInterfaceTest, DataLeftFromHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could accept the handshake, but then immediately send a
+// Close frame.
+TEST_F(WebSocketChannelEventInterfaceTest, CloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 23},
+ FINAL_CHUNK, CLOSE_DATA(SERVER_ERROR, "Internal Server Error")}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorInternalServerError,
+ "Internal Server Error"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A remote server could close the connection immediately after sending the
+// handshake response (most likely a bug in the server).
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionCloseAfterHandshake) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+TEST_F(WebSocketChannelEventInterfaceTest, NormalAsyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ // We use this checkpoint object to verify that the callback isn't called
+ // until we expect it to be.
+ MockFunction<void(int)> checkpoint;
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ base::MessageLoop::current()->RunUntilIdle();
+ checkpoint.Call(2);
+}
+
+// Extra data can arrive while a read is being processed, resulting in the next
+// read completing synchronously.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncThenSyncRead) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "HELLO"}};
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "WORLD"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks2);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("HELLO")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("WORLD")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Data frames that arrive in fragments are turned into individual frames
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedFrames) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // Here we have one message split into 3 frames which arrive in 3 chunks. The
+ // first frame is entirely in the first chunk, the second frame is split
+ // across all the chunks, and the final frame is entirely in the final
+ // chunk. The frame fragments are converted to separate frames so that they
+ // can be delivered immediatedly. So the EventInterface should see a Text
+ // message with 5 frames.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 5},
+ FINAL_CHUNK, "THREE"},
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED,
+ 7},
+ NOT_FINAL_CHUNK, " "}};
+ static const InitFrameChunk chunks2[] = {
+ {{NO_HEADER}, NOT_FINAL_CHUNK, "SMALL"}};
+ static const InitFrameChunk chunks3[] = {
+ {{NO_HEADER}, FINAL_CHUNK, " "},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 6},
+ FINAL_CHUNK, "FRAMES"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("THREE")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("SMALL")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeContinuation, AsVector(" ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("FRAMES")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// In the case when a single-frame message because fragmented, it must be
+// correctly transformed to multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, MessageFragmentation) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // A single-frame Text message arrives in three chunks. This should be
+ // delivered as three frames.
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 12},
+ NOT_FINAL_CHUNK, "TIME"}};
+ static const InitFrameChunk chunks2[] = {
+ {{NO_HEADER}, NOT_FINAL_CHUNK, " FOR "}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "TEA"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("TIME")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(false,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector(" FOR ")));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("TEA")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a control message is fragmented, it must be re-assembled before being
+// delivered. A control message can only be fragmented at the network level; it
+// is not permitted to be split into multiple frames.
+TEST_F(WebSocketChannelEventInterfaceTest, FragmentedControlMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}};
+ static const InitFrameChunk chunks2[] = {
+ {{NO_HEADER}, NOT_FINAL_CHUNK, "Clo"}};
+ static const InitFrameChunk chunks3[] = {{{NO_HEADER}, FINAL_CHUNK, "se"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Close"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// The payload of a control frame is not permitted to exceed 125 bytes. RFC6455
+// 5.5 "All control frames MUST have a payload length of 125 bytes or less"
+TEST_F(WebSocketChannelEventInterfaceTest, OversizeControlMessageIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const size_t kPayloadLen = 126;
+ char payload[kPayloadLen + 1]; // allow space for trailing NUL
+ std::fill(payload, payload + kPayloadLen, 'A');
+ payload[kPayloadLen] = '\0';
+ // Not static because "payload" is constructed at runtime.
+ const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED,
+ kPayloadLen},
+ FINAL_CHUNK, payload}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ set_stream(stream.Pass());
+
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// A control frame is not permitted to be split into multiple frames. RFC6455
+// 5.5 "All control frames ... MUST NOT be fragmented."
+TEST_F(WebSocketChannelEventInterfaceTest, MultiFrameControlMessageIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 2},
+ FINAL_CHUNK, "Pi"},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 2},
+ FINAL_CHUNK, "ng"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed by the remote host without a closing handshake.
+TEST_F(WebSocketChannelEventInterfaceTest, AsyncAbnormalClosure) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// A connection reset should produce the same event as an unexpected closure.
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionReset) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_RESET);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// Connection closed in the middle of a Close message (server bug, etc.)
+TEST_F(WebSocketChannelEventInterfaceTest, ConnectionClosedInMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ NOT_FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "")}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::ASYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.1 "A client MUST close a connection if it detects a masked frame."
+TEST_F(WebSocketChannelEventInterfaceTest, MaskedFramesAreRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK,
+ "HELLO"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.2 "If an unknown opcode is received, the receiving endpoint MUST
+// _Fail the WebSocket Connection_."
+TEST_F(WebSocketChannelEventInterfaceTest, UnknownOpCodeIsRejected) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, 4, NOT_MASKED, 5}, FINAL_CHUNK, "HELLO"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// RFC6455 5.4 "Control frames ... MAY be injected in the middle of a
+// fragmented message."
+TEST_F(WebSocketChannelEventInterfaceTest, ControlFrameInDataMessage) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ // We have one message of type Text split into two frames. In the middle is a
+ // control message of type Pong.
+ static const InitFrameChunk chunks1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, NOT_MASKED, 6},
+ FINAL_CHUNK, "SPLIT "}};
+ static const InitFrameChunk chunks2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, NOT_MASKED, 0},
+ FINAL_CHUNK, ""}};
+ static const InitFrameChunk chunks3[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, NOT_MASKED, 7},
+ FINAL_CHUNK, "MESSAGE"}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks1);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks2);
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks3);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(
+ *event_interface_,
+ OnDataFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("SPLIT ")));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeContinuation,
+ AsVector("MESSAGE")));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If a chunk has an invalid header, then the connection is closed and
+// subsequent chunks must not trigger events.
+TEST_F(WebSocketChannelEventInterfaceTest, HeaderlessChunkAfterInvalidChunk) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 11},
+ NOT_FINAL_CHUNK, "HELLO"},
+ {{NO_HEADER}, FINAL_CHUNK, " WORLD"}};
+
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::ASYNC, OK, chunks);
+ set_stream(stream.Pass());
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorProtocolError, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// If the renderer sends lots of small writes, we don't want to update the quota
+// for each one.
+TEST_F(WebSocketChannelEventInterfaceTest, SmallWriteDoesntUpdateQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("B"));
+}
+
+// If we send enough to go below send_quota_low_water_mask_ we should get our
+// quota refreshed.
+TEST_F(WebSocketChannelEventInterfaceTest, LargeWriteUpdatesQuota) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ // We use this checkpoint object to verify that the quota update comes after
+ // the write.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota, 'B'));
+ checkpoint.Call(2);
+}
+
+// Verify that our quota actually is refreshed when we are told it is.
+TEST_F(WebSocketChannelEventInterfaceTest, QuotaReallyIsRefreshed) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(2));
+ // If quota was not really refreshed, we would get an OnDropChannel()
+ // message.
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultQuotaRefreshTrigger, 'D'));
+ checkpoint.Call(2);
+ // We should have received more quota at this point.
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultQuotaRefreshTrigger, 'E'));
+ checkpoint.Call(3);
+}
+
+// If we send more than the available quota then the connection will be closed
+// with an error.
+TEST_F(WebSocketChannelEventInterfaceTest, WriteOverQuotaIsRejected) {
+ set_stream(make_scoped_ptr(new WriteableFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(kDefaultInitialQuota));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketMuxErrorSendQuotaViolation, _));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota + 1, 'C'));
+}
+
+// If a write fails, the channel is dropped.
+TEST_F(WebSocketChannelEventInterfaceTest, FailedWrite) {
+ set_stream(make_scoped_ptr(new UnWriteableFakeWebSocketStream));
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, _));
+ EXPECT_CALL(checkpoint, Call(2));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("H"));
+ checkpoint.Call(2);
+}
+
+// OnDropChannel() is called exactly once when StartClosingHandshake() is used.
+TEST_F(WebSocketChannelEventInterfaceTest, SendCloseDropsChannel) {
+ set_stream(make_scoped_ptr(new EchoeyFakeWebSocketStream));
+ {
+ InSequence s;
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketNormalClosure, "Fred"));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "Fred");
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// OnDropChannel() is only called once when a write() on the socket triggers a
+// connection reset.
+TEST_F(WebSocketChannelEventInterfaceTest, OnDropChannelCalledOnce) {
+ set_stream(make_scoped_ptr(new ResetOnWriteFakeWebSocketStream));
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorAbnormalClosure, "Abnormal Closure"))
+ .Times(1);
+
+ CreateChannelAndConnectSuccessfully();
+
+ channel_->SendFrame(true, WebSocketFrameHeader::kOpCodeText, AsVector("yt?"));
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+// When the remote server sends a Close frame with an empty payload,
+// WebSocketChannel should report code 1005, kWebSocketErrorNoStatusReceived.
+TEST_F(WebSocketChannelEventInterfaceTest, CloseWithNoPayloadGivesStatus1005) {
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0},
+ FINAL_CHUNK, ""}};
+ stream->PrepareReadFrames(ReadableFakeWebSocketStream::SYNC, OK, chunks);
+ stream->PrepareReadFramesError(ReadableFakeWebSocketStream::SYNC,
+ ERR_CONNECTION_CLOSED);
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_, OnClosingHandshake());
+ EXPECT_CALL(*event_interface_,
+ OnDropChannel(kWebSocketErrorNoStatusReceived, _));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// RFC6455 5.1 "a client MUST mask all frames that it sends to the server".
+// WebSocketChannel actually only sets the mask bit in the header, it doesn't
+// perform masking itself (not all transports actually use masking).
+TEST_F(WebSocketChannelStreamTest, SentFramesAreMasked) {
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 13},
+ FINAL_CHUNK, "NEEDS MASKING"}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("NEEDS MASKING"));
+}
+
+// RFC6455 5.5.1 "The application MUST NOT send any more data frames after
+// sending a Close frame."
+TEST_F(WebSocketChannelStreamTest, NothingIsSentAfterClose) {
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 9},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Success")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->StartClosingHandshake(1000, "Success");
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("SHOULD BE IGNORED"));
+}
+
+// RFC6455 5.5.1 "If an endpoint receives a Close frame and did not previously
+// send a Close frame, the endpoint MUST send a Close frame in response."
+TEST_F(WebSocketChannelStreamTest, CloseIsEchoedBack) {
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// The converse of the above case; after sending a Close frame, we should not
+// send another one.
+TEST_F(WebSocketChannelStreamTest, CloseOnlySentOnce) {
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 7},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 7},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "Close")}};
+
+ // We store the parameters that were passed to ReadFrames() so that we can
+ // call them explicitly later.
+ CompletionCallback read_callback;
+ ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL;
+
+ // Use a checkpoint to make the ordering of events clearer.
+ MockFunction<void(int)> checkpoint;
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&frame_chunks),
+ SaveArg<1>(&read_callback),
+ Return(ERR_IO_PENDING)));
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(checkpoint, Call(2));
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(checkpoint, Call(3));
+ // WriteFrames() must not be called again. GoogleMock will ensure that the
+ // test fails if it is.
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->StartClosingHandshake(kWebSocketNormalClosure, "Close");
+ checkpoint.Call(2);
+
+ *frame_chunks = CreateFrameChunkVector(chunks);
+ read_callback.Run(OK);
+ checkpoint.Call(3);
+}
+
+// We generate code 1005, kWebSocketErrorNoStatusReceived, when there is no
+// status in the Close message from the other side. Code 1005 is not allowed to
+// appear on the wire, so we should not echo it back. See test
+// CloseWithNoPayloadGivesStatus1005, above, for confirmation that code 1005 is
+// correctly generated internally.
+TEST_F(WebSocketChannelStreamTest, Code1005IsNotEchoed) {
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 0},
+ FINAL_CHUNK, ""}};
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 0},
+ FINAL_CHUNK, ""}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// RFC6455 5.5.2 "Upon receipt of a Ping frame, an endpoint MUST send a Pong
+// frame in response"
+// 5.5.3 "A Pong frame sent in response to a Ping frame must have identical
+// "Application data" as found in the message body of the Ping frame being
+// replied to."
+TEST_F(WebSocketChannelStreamTest, PingRepliedWithPong) {
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16},
+ FINAL_CHUNK, "Application data"}};
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16},
+ FINAL_CHUNK, "Application data"}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+TEST_F(WebSocketChannelStreamTest, PongInTheMiddleOfDataMessage) {
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 16},
+ FINAL_CHUNK, "Application data"}};
+ static const InitFrameChunk expected1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6},
+ FINAL_CHUNK, "Hello "}};
+ static const InitFrameChunk expected2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePong, MASKED, 16},
+ FINAL_CHUNK, "Application data"}};
+ static const InitFrameChunk expected3[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeContinuation, MASKED, 5},
+ FINAL_CHUNK, "World"}};
+ ScopedVector<WebSocketFrameChunk>* read_chunks;
+ CompletionCallback read_callback;
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&read_chunks),
+ SaveArg<1>(&read_callback),
+ Return(ERR_IO_PENDING)))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ {
+ InSequence s;
+
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected3), _))
+ .WillOnce(Return(OK));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello "));
+ *read_chunks = CreateFrameChunkVector(chunks);
+ read_callback.Run(OK);
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeContinuation, AsVector("World"));
+}
+
+// WriteFrames() may not be called until the previous write has completed.
+// WebSocketChannel must buffer writes that happen in the meantime.
+TEST_F(WebSocketChannelStreamTest, WriteFramesOneAtATime) {
+ static const InitFrameChunk expected1[] = {
+ {{NOT_FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 6},
+ FINAL_CHUNK, "Hello "}};
+ static const InitFrameChunk expected2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 5}, FINAL_CHUNK,
+ "World"}};
+ CompletionCallback write_callback;
+ MockFunction<void(int)> checkpoint;
+
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ {
+ InSequence s;
+ EXPECT_CALL(checkpoint, Call(1));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
+ EXPECT_CALL(checkpoint, Call(2));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(checkpoint, Call(3));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ checkpoint.Call(1);
+ channel_->SendFrame(
+ false, WebSocketFrameHeader::kOpCodeText, AsVector("Hello "));
+ channel_->SendFrame(
+ true, WebSocketFrameHeader::kOpCodeText, AsVector("World"));
+ checkpoint.Call(2);
+ write_callback.Run(OK);
+ checkpoint.Call(3);
+}
+
+// WebSocketChannel must buffer frames while it is waiting for a write to
+// complete, and then send them in a single batch. The batching behaviour is
+// important to get good throughput in the "many small messages" case.
+TEST_F(WebSocketChannelStreamTest, WaitingMessagesAreBatched) {
+ static const char input_letters[] = "Hello";
+ static const InitFrameChunk expected1[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
+ "H"}};
+ static const InitFrameChunk expected2[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
+ "e"},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
+ "l"},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
+ "l"},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeText, MASKED, 1}, FINAL_CHUNK,
+ "o"}};
+ CompletionCallback write_callback;
+
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected1), _))
+ .WillOnce(DoAll(SaveArg<1>(&write_callback), Return(ERR_IO_PENDING)));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected2), _))
+ .WillOnce(Return(ERR_IO_PENDING));
+ }
+
+ CreateChannelAndConnectSuccessfully();
+ for (size_t i = 0; i < strlen(input_letters); ++i) {
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(1, input_letters[i]));
+ }
+ write_callback.Run(OK);
+}
+
+// When the renderer sends more on a channel than it has quota for, then we send
+// a kWebSocketMuxErrorSendQuotaViolation status code (from the draft websocket
+// mux specification) back to the renderer. This should not be sent to the
+// remote server, which may not even implement the mux specification, and could
+// even be using a different extension which uses that code to mean something
+// else.
+TEST_F(WebSocketChannelStreamTest, MuxErrorIsNotSentToStream) {
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 16},
+ FINAL_CHUNK, CLOSE_DATA(GOING_AWAY, "Internal Error")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close());
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(true,
+ WebSocketFrameHeader::kOpCodeText,
+ std::vector<char>(kDefaultInitialQuota + 1, 'C'));
+}
+
+// For convenience, most of these tests use Text frames. However, the WebSocket
+// protocol also has Binary frames and those need to be 8-bit clean. For the
+// sake of completeness, this test verifies that they are.
+TEST_F(WebSocketChannelStreamTest, WrittenBinaryFramesAre8BitClean) {
+ ScopedVector<WebSocketFrameChunk>* frame_chunks = NULL;
+
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _)).WillOnce(Return(ERR_IO_PENDING));
+ EXPECT_CALL(*mock_stream_, WriteFrames(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&frame_chunks), Return(ERR_IO_PENDING)));
+
+ CreateChannelAndConnectSuccessfully();
+ channel_->SendFrame(
+ true,
+ WebSocketFrameHeader::kOpCodeBinary,
+ std::vector<char>(kBinaryBlob, kBinaryBlob + kBinaryBlobSize));
+ ASSERT_TRUE(frame_chunks != NULL);
+ ASSERT_EQ(1U, frame_chunks->size());
+ const WebSocketFrameChunk* out_chunk = (*frame_chunks)[0];
+ ASSERT_TRUE(out_chunk->header);
+ EXPECT_EQ(kBinaryBlobSize, out_chunk->header->payload_length);
+ ASSERT_TRUE(out_chunk->data);
+ EXPECT_EQ(kBinaryBlobSize, static_cast<size_t>(out_chunk->data->size()));
+ EXPECT_EQ(0, memcmp(kBinaryBlob, out_chunk->data->data(), kBinaryBlobSize));
+}
+
+// Test the read path for 8-bit cleanliness as well.
+TEST_F(WebSocketChannelEventInterfaceTest, ReadBinaryFramesAre8BitClean) {
+ scoped_ptr<WebSocketFrameHeader> frame_header(
+ new WebSocketFrameHeader(WebSocketFrameHeader::kOpCodeBinary));
+ frame_header->final = true;
+ frame_header->payload_length = kBinaryBlobSize;
+ scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk);
+ frame_chunk->header = frame_header.Pass();
+ frame_chunk->final_chunk = true;
+ frame_chunk->data = new IOBufferWithSize(kBinaryBlobSize);
+ memcpy(frame_chunk->data->data(), kBinaryBlob, kBinaryBlobSize);
+ ScopedVector<WebSocketFrameChunk> chunks;
+ chunks.push_back(frame_chunk.release());
+ scoped_ptr<ReadableFakeWebSocketStream> stream(
+ new ReadableFakeWebSocketStream);
+ stream->PrepareRawReadFrames(
+ ReadableFakeWebSocketStream::SYNC, OK, chunks.Pass());
+ set_stream(stream.Pass());
+ EXPECT_CALL(*event_interface_, OnAddChannelResponse(false, _));
+ EXPECT_CALL(*event_interface_, OnFlowControl(_));
+ EXPECT_CALL(*event_interface_,
+ OnDataFrame(true,
+ WebSocketFrameHeader::kOpCodeBinary,
+ std::vector<char>(kBinaryBlob,
+ kBinaryBlob + kBinaryBlobSize)));
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+// If we receive another frame after Close, it is not valid. It is not
+// completely clear what behaviour is required from the standard in this case,
+// but the current implementation fails the connection. Since a Close has
+// already been sent, this just means closing the connection.
+TEST_F(WebSocketChannelStreamTest, PingAfterCloseIsRejected) {
+ static const InitFrameChunk chunks[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, NOT_MASKED, 4},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")},
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodePing, NOT_MASKED, 9},
+ FINAL_CHUNK, "Ping body"}};
+ static const InitFrameChunk expected[] = {
+ {{FINAL_FRAME, WebSocketFrameHeader::kOpCodeClose, MASKED, 4},
+ FINAL_CHUNK, CLOSE_DATA(NORMAL_CLOSURE, "OK")}};
+ EXPECT_CALL(*mock_stream_, GetSubProtocol()).Times(AnyNumber());
+ EXPECT_CALL(*mock_stream_, ReadFrames(_, _))
+ .WillOnce(ReturnChunks(&chunks))
+ .WillRepeatedly(Return(ERR_IO_PENDING));
+ {
+ // We only need to verify the relative order of WriteFrames() and
+ // Close(). The current implementation calls WriteFrames() for the Close
+ // frame before calling ReadFrames() again, but that is an implementation
+ // detail and better not to consider required behaviour.
+ InSequence s;
+ EXPECT_CALL(*mock_stream_, WriteFrames(EqualsChunks(expected), _))
+ .WillOnce(Return(OK));
+ EXPECT_CALL(*mock_stream_, Close()).Times(1);
+ }
+
+ CreateChannelAndConnectSuccessfully();
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/websockets/websocket_errors.cc b/chromium/net/websockets/websocket_errors.cc
new file mode 100644
index 00000000000..b7ca7bc5636
--- /dev/null
+++ b/chromium/net/websockets/websocket_errors.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_errors.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+Error WebSocketErrorToNetError(WebSocketError error) {
+ switch (error) {
+ case kWebSocketNormalClosure:
+ return OK;
+
+ case kWebSocketErrorGoingAway: // TODO(ricea): More specific code?
+ case kWebSocketErrorProtocolError:
+ case kWebSocketErrorUnsupportedData:
+ case kWebSocketErrorInvalidFramePayloadData:
+ case kWebSocketErrorPolicyViolation:
+ case kWebSocketErrorMandatoryExtension:
+ case kWebSocketErrorInternalServerError:
+ return ERR_WS_PROTOCOL_ERROR;
+
+ case kWebSocketErrorNoStatusReceived:
+ case kWebSocketErrorAbnormalClosure:
+ return ERR_CONNECTION_CLOSED;
+
+ case kWebSocketErrorTlsHandshake:
+ // This error will probably be reported with more detail at a lower layer;
+ // this is the best we can do at this layer.
+ return ERR_SSL_PROTOCOL_ERROR;
+
+ case kWebSocketErrorMessageTooBig:
+ return ERR_MSG_TOO_BIG;
+
+ default:
+ return ERR_UNEXPECTED;
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_errors.h b/chromium/net/websockets/websocket_errors.h
new file mode 100644
index 00000000000..7d62cc008e2
--- /dev/null
+++ b/chromium/net/websockets/websocket_errors.h
@@ -0,0 +1,53 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_ERRORS_H_
+#define NET_WEBSOCKETS_WEBSOCKET_ERRORS_H_
+
+#include "net/base/net_errors.h"
+
+namespace net {
+
+// Reason codes used with close messages. NoStatusReceived,
+// AbnormalClosure and TlsHandshake are special in that they
+// should never be sent on the wire; they are only used within the
+// implementation.
+enum WebSocketError {
+ // Status codes in the range 0 to 999 are not used.
+
+ // The following are defined by RFC6455.
+ kWebSocketNormalClosure = 1000,
+ kWebSocketErrorGoingAway = 1001,
+ kWebSocketErrorProtocolError = 1002,
+ kWebSocketErrorUnsupportedData = 1003,
+ kWebSocketErrorNoStatusReceived = 1005,
+ kWebSocketErrorAbnormalClosure = 1006,
+ kWebSocketErrorInvalidFramePayloadData = 1007,
+ kWebSocketErrorPolicyViolation = 1008,
+ kWebSocketErrorMessageTooBig = 1009,
+ kWebSocketErrorMandatoryExtension = 1010,
+ kWebSocketErrorInternalServerError = 1011,
+ kWebSocketErrorTlsHandshake = 1015,
+
+ // The range 1000-2999 is reserved by RFC6455 for use by the WebSocket
+ // protocol and public extensions.
+ kWebSocketErrorProtocolReservedMax = 2999,
+
+ // The range 3000-3999 is reserved by RFC6455 for registered use by libraries,
+ // frameworks and applications.
+ kWebSocketErrorRegisteredReservedMin = 3000,
+ kWebSocketErrorRegisteredReservedMax = 3999,
+
+ // The range 4000-4999 is reserved by RFC6455 for private use by prior
+ // agreement of the endpoints.
+ kWebSocketErrorPrivateReservedMin = 4000,
+ kWebSocketErrorPrivateReservedMax = 4999,
+};
+
+// Convert WebSocketError to net::Error defined in net/base/net_errors.h.
+NET_EXPORT_PRIVATE Error WebSocketErrorToNetError(WebSocketError error);
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_ERRORS_H_
diff --git a/chromium/net/websockets/websocket_errors_unittest.cc b/chromium/net/websockets/websocket_errors_unittest.cc
new file mode 100644
index 00000000000..1a4fca61453
--- /dev/null
+++ b/chromium/net/websockets/websocket_errors_unittest.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 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/websockets/websocket_errors.h"
+
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+namespace {
+
+// Confirm that the principle classes of errors are converted correctly. We
+// don't exhaustively test every error code, as it would be long, repetitive,
+// and add little value.
+TEST(WebSocketErrorToNetErrorTest, ResultsAreCorrect) {
+ EXPECT_EQ(OK, WebSocketErrorToNetError(kWebSocketNormalClosure));
+ EXPECT_EQ(ERR_WS_PROTOCOL_ERROR,
+ WebSocketErrorToNetError(kWebSocketErrorProtocolError));
+ EXPECT_EQ(ERR_MSG_TOO_BIG,
+ WebSocketErrorToNetError(kWebSocketErrorMessageTooBig));
+ EXPECT_EQ(ERR_CONNECTION_CLOSED,
+ WebSocketErrorToNetError(kWebSocketErrorNoStatusReceived));
+ EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR,
+ WebSocketErrorToNetError(kWebSocketErrorTlsHandshake));
+}
+
+} // namespace
+} // namespace net
diff --git a/chromium/net/websockets/websocket_event_interface.h b/chromium/net/websockets/websocket_event_interface.h
new file mode 100644
index 00000000000..7eb54bf8b15
--- /dev/null
+++ b/chromium/net/websockets/websocket_event_interface.h
@@ -0,0 +1,76 @@
+// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_EVENT_INTERFACE_H_
+#define NET_WEBSOCKETS_WEBSOCKET_EVENT_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Interface for events sent from the network layer to the content layer. These
+// events will generally be sent as-is to the renderer process.
+class NET_EXPORT WebSocketEventInterface {
+ public:
+ typedef int WebSocketMessageType;
+ virtual ~WebSocketEventInterface() {}
+ // Called in response to an AddChannelRequest. This generally means that a
+ // response has been received from the remote server, but the response might
+ // have been generated internally. If |fail| is true, the channel cannot be
+ // used and it is valid to delete the WebSocketChannel from within this
+ // callback.
+ virtual void OnAddChannelResponse(
+ bool fail,
+ const std::string& selected_subprotocol) = 0;
+
+ // Called when a data frame has been received from the remote host and needs
+ // to be forwarded to the renderer process. It is not safe to delete the
+ // WebSocketChannel object from within this callback.
+ virtual void OnDataFrame(bool fin,
+ WebSocketMessageType type,
+ const std::vector<char>& data) = 0;
+
+ // Called to provide more send quota for this channel to the renderer
+ // process. Currently the quota units are always bytes of message body
+ // data. In future it might depend on the type of multiplexing in use. It is
+ // not safe to delete the WebSocketChannel from within this callback.
+ virtual void OnFlowControl(int64 quota) = 0;
+
+ // Called when the remote server has Started the WebSocket Closing
+ // Handshake. The client should not attempt to send any more messages after
+ // receiving this message. It will be followed by OnDropChannel() when the
+ // closing handshake is complete. It is not safe to delete the
+ // WebSocketChannel from within this callback.
+ virtual void OnClosingHandshake() = 0;
+
+ // Called when the channel has been dropped, either due to a network close, a
+ // network error, or a protocol error. This may or may not be preceeded by a
+ // call to OnClosingHandshake().
+ //
+ // Warning: Both the |code| and |reason| are passed through to Javascript, so
+ // callers must take care not to provide details that could be useful to
+ // attackers attempting to use WebSockets to probe networks.
+ //
+ // The channel should not be used again after OnDropChannel() has been
+ // called.
+ //
+ // It is not safe to delete the WebSocketChannel from within this
+ // callback. It is recommended to delete the channel after returning to the
+ // event loop.
+ virtual void OnDropChannel(uint16 code, const std::string& reason) = 0;
+
+ protected:
+ WebSocketEventInterface() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocketEventInterface);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_EVENT_INTERFACE_H_
diff --git a/chromium/net/websockets/websocket_frame.cc b/chromium/net/websockets/websocket_frame.cc
new file mode 100644
index 00000000000..f05b5b76152
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame.cc
@@ -0,0 +1,230 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_frame.h"
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "net/base/big_endian.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace {
+
+const uint8 kFinalBit = 0x80;
+const uint8 kReserved1Bit = 0x40;
+const uint8 kReserved2Bit = 0x20;
+const uint8 kReserved3Bit = 0x10;
+const uint8 kOpCodeMask = 0xF;
+const uint8 kMaskBit = 0x80;
+const uint64 kMaxPayloadLengthWithoutExtendedLengthField = 125;
+const uint64 kPayloadLengthWithTwoByteExtendedLengthField = 126;
+const uint64 kPayloadLengthWithEightByteExtendedLengthField = 127;
+
+inline void MaskWebSocketFramePayloadByBytes(
+ const net::WebSocketMaskingKey& masking_key,
+ size_t masking_key_offset,
+ char* const begin,
+ char* const end) {
+ for (char* masked = begin; masked != end; ++masked) {
+ *masked ^= masking_key.key[masking_key_offset++];
+ if (masking_key_offset == net::WebSocketFrameHeader::kMaskingKeyLength)
+ masking_key_offset = 0;
+ }
+}
+
+} // Unnamed namespace.
+
+namespace net {
+
+scoped_ptr<WebSocketFrameHeader> WebSocketFrameHeader::Clone() {
+ scoped_ptr<WebSocketFrameHeader> ret(new WebSocketFrameHeader(opcode));
+ ret->final = final;
+ ret->reserved1 = reserved1;
+ ret->reserved2 = reserved2;
+ ret->reserved3 = reserved3;
+ ret->opcode = opcode;
+ ret->masked = masked;
+ ret->payload_length = payload_length;
+ return ret.Pass();
+}
+
+WebSocketFrameChunk::WebSocketFrameChunk() : final_chunk(false) {}
+
+WebSocketFrameChunk::~WebSocketFrameChunk() {}
+
+int GetWebSocketFrameHeaderSize(const WebSocketFrameHeader& header) {
+ int extended_length_size = 0;
+ if (header.payload_length > kMaxPayloadLengthWithoutExtendedLengthField &&
+ header.payload_length <= kuint16max) {
+ extended_length_size = 2;
+ } else if (header.payload_length > kuint16max) {
+ extended_length_size = 8;
+ }
+
+ return (WebSocketFrameHeader::kBaseHeaderSize + extended_length_size +
+ (header.masked ? WebSocketFrameHeader::kMaskingKeyLength : 0));
+}
+
+int WriteWebSocketFrameHeader(const WebSocketFrameHeader& header,
+ const WebSocketMaskingKey* masking_key,
+ char* buffer,
+ int buffer_size) {
+ DCHECK((header.opcode & kOpCodeMask) == header.opcode)
+ << "header.opcode must fit to kOpCodeMask.";
+ DCHECK(header.payload_length <= static_cast<uint64>(kint64max))
+ << "WebSocket specification doesn't allow a frame longer than "
+ << "kint64max (0x7FFFFFFFFFFFFFFF) bytes.";
+ DCHECK_GE(buffer_size, 0);
+
+ // WebSocket frame format is as follows:
+ // - Common header (2 bytes)
+ // - Optional extended payload length
+ // (2 or 8 bytes, present if actual payload length is more than 125 bytes)
+ // - Optional masking key (4 bytes, present if MASK bit is on)
+ // - Actual payload (XOR masked with masking key if MASK bit is on)
+ //
+ // This function constructs frame header (the first three in the list
+ // above).
+
+ int header_size = GetWebSocketFrameHeaderSize(header);
+ if (header_size > buffer_size)
+ return ERR_INVALID_ARGUMENT;
+
+ int buffer_index = 0;
+
+ uint8 first_byte = 0u;
+ first_byte |= header.final ? kFinalBit : 0u;
+ first_byte |= header.reserved1 ? kReserved1Bit : 0u;
+ first_byte |= header.reserved2 ? kReserved2Bit : 0u;
+ first_byte |= header.reserved3 ? kReserved3Bit : 0u;
+ first_byte |= header.opcode & kOpCodeMask;
+ buffer[buffer_index++] = first_byte;
+
+ int extended_length_size = 0;
+ uint8 second_byte = 0u;
+ second_byte |= header.masked ? kMaskBit : 0u;
+ if (header.payload_length <= kMaxPayloadLengthWithoutExtendedLengthField) {
+ second_byte |= header.payload_length;
+ } else if (header.payload_length <= kuint16max) {
+ second_byte |= kPayloadLengthWithTwoByteExtendedLengthField;
+ extended_length_size = 2;
+ } else {
+ second_byte |= kPayloadLengthWithEightByteExtendedLengthField;
+ extended_length_size = 8;
+ }
+ buffer[buffer_index++] = second_byte;
+
+ // Writes "extended payload length" field.
+ if (extended_length_size == 2) {
+ uint16 payload_length_16 = static_cast<uint16>(header.payload_length);
+ WriteBigEndian(buffer + buffer_index, payload_length_16);
+ buffer_index += sizeof(payload_length_16);
+ } else if (extended_length_size == 8) {
+ WriteBigEndian(buffer + buffer_index, header.payload_length);
+ buffer_index += sizeof(header.payload_length);
+ }
+
+ // Writes "masking key" field, if needed.
+ if (header.masked) {
+ DCHECK(masking_key);
+ std::copy(masking_key->key,
+ masking_key->key + WebSocketFrameHeader::kMaskingKeyLength,
+ buffer + buffer_index);
+ buffer_index += WebSocketFrameHeader::kMaskingKeyLength;
+ } else {
+ DCHECK(!masking_key);
+ }
+
+ DCHECK_EQ(header_size, buffer_index);
+ return header_size;
+}
+
+WebSocketMaskingKey GenerateWebSocketMaskingKey() {
+ // Masking keys should be generated from a cryptographically secure random
+ // number generator, which means web application authors should not be able
+ // to guess the next value of masking key.
+ WebSocketMaskingKey masking_key;
+ base::RandBytes(masking_key.key, WebSocketFrameHeader::kMaskingKeyLength);
+ return masking_key;
+}
+
+void MaskWebSocketFramePayload(const WebSocketMaskingKey& masking_key,
+ uint64 frame_offset,
+ char* const data,
+ int data_size) {
+ static const size_t kMaskingKeyLength =
+ WebSocketFrameHeader::kMaskingKeyLength;
+
+ DCHECK_GE(data_size, 0);
+
+ // Most of the masking is done one word at a time, except for the beginning
+ // and the end of the buffer which may be unaligned. We use size_t to get the
+ // word size for this architecture. We require it be a multiple of
+ // kMaskingKeyLength in size.
+ typedef size_t PackedMaskType;
+ PackedMaskType packed_mask_key = 0;
+ static const size_t kPackedMaskKeySize = sizeof(packed_mask_key);
+ COMPILE_ASSERT((kPackedMaskKeySize >= kMaskingKeyLength &&
+ kPackedMaskKeySize % kMaskingKeyLength == 0),
+ word_size_is_not_multiple_of_mask_length);
+ char* const end = data + data_size;
+ // If the buffer is too small for the vectorised version to be useful, revert
+ // to the byte-at-a-time implementation early.
+ if (data_size <= static_cast<int>(kPackedMaskKeySize * 2)) {
+ MaskWebSocketFramePayloadByBytes(
+ masking_key, frame_offset % kMaskingKeyLength, data, end);
+ return;
+ }
+ const size_t data_modulus =
+ reinterpret_cast<size_t>(data) % kPackedMaskKeySize;
+ char* const aligned_begin =
+ data_modulus == 0 ? data : (data + kPackedMaskKeySize - data_modulus);
+ // Guaranteed by the above check for small data_size.
+ DCHECK(aligned_begin < end);
+ MaskWebSocketFramePayloadByBytes(
+ masking_key, frame_offset % kMaskingKeyLength, data, aligned_begin);
+ const size_t end_modulus = reinterpret_cast<size_t>(end) % kPackedMaskKeySize;
+ char* const aligned_end = end - end_modulus;
+ // Guaranteed by the above check for small data_size.
+ DCHECK(aligned_end > aligned_begin);
+ // Create a version of the mask which is rotated by the appropriate offset
+ // for our alignment. The "trick" here is that 0 XORed with the mask will
+ // give the value of the mask for the appropriate byte.
+ char realigned_mask[kMaskingKeyLength] = { 0 };
+ MaskWebSocketFramePayloadByBytes(
+ masking_key,
+ (frame_offset + aligned_begin - data) % kMaskingKeyLength,
+ realigned_mask,
+ realigned_mask + kMaskingKeyLength);
+
+ for (size_t i = 0; i < kPackedMaskKeySize; i += kMaskingKeyLength) {
+ // memcpy() is allegedly blessed by the C++ standard for type-punning.
+ memcpy(reinterpret_cast<char*>(&packed_mask_key) + i,
+ realigned_mask,
+ kMaskingKeyLength);
+ }
+
+ // The main loop.
+ for (char* merged = aligned_begin; merged != aligned_end;
+ merged += kPackedMaskKeySize) {
+ // This is not quite standard-compliant C++. However, the standard-compliant
+ // equivalent (using memcpy()) compiles to slower code using g++. In
+ // practice, this will work for the compilers and architectures currently
+ // supported by Chromium, and the tests are extremely unlikely to pass if a
+ // future compiler/architecture breaks it.
+ *reinterpret_cast<PackedMaskType*>(merged) ^= packed_mask_key;
+ }
+
+ MaskWebSocketFramePayloadByBytes(
+ masking_key,
+ (frame_offset + (aligned_end - data)) % kMaskingKeyLength,
+ aligned_end,
+ end);
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_frame.h b/chromium/net/websockets/websocket_frame.h
new file mode 100644
index 00000000000..8c04f7dce44
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame.h
@@ -0,0 +1,175 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class IOBufferWithSize;
+
+// Represents a WebSocket frame header.
+//
+// Members of this class correspond to each element in WebSocket frame header
+// (see http://tools.ietf.org/html/rfc6455#section-5.2).
+struct NET_EXPORT WebSocketFrameHeader {
+ typedef int OpCode;
+
+ // Originally these constants were static const int, but to make it possible
+ // to use them in a switch statement they were changed to an enum.
+ enum OpCodeEnum {
+ kOpCodeContinuation = 0x0,
+ kOpCodeText = 0x1,
+ kOpCodeBinary = 0x2,
+ kOpCodeDataUnused = 0x3,
+ kOpCodeClose = 0x8,
+ kOpCodePing = 0x9,
+ kOpCodePong = 0xA,
+ kOpCodeControlUnused = 0xB,
+ };
+
+ // Return true if |opcode| is one of the data opcodes known to this
+ // implementation.
+ static bool IsKnownDataOpCode(OpCode opcode) {
+ return opcode == kOpCodeContinuation || opcode == kOpCodeText ||
+ opcode == kOpCodeBinary;
+ }
+
+ // Return true if |opcode| is one of the control opcodes known to this
+ // implementation.
+ static bool IsKnownControlOpCode(OpCode opcode) {
+ return opcode == kOpCodeClose || opcode == kOpCodePing ||
+ opcode == kOpCodePong;
+ }
+
+ // These values must be a compile-time constant. "enum hack" is used here
+ // to make MSVC happy.
+ enum {
+ kBaseHeaderSize = 2,
+ kMaximumExtendedLengthSize = 8,
+ kMaskingKeyLength = 4
+ };
+
+ // Constructor to avoid a lot of repetitive initialisation.
+ explicit WebSocketFrameHeader(OpCode opCode)
+ : final(false),
+ reserved1(false),
+ reserved2(false),
+ reserved3(false),
+ opcode(opCode),
+ masked(false),
+ payload_length(0) {}
+
+ // Create a clone of this object on the heap.
+ scoped_ptr<WebSocketFrameHeader> Clone();
+
+ // Members below correspond to each item in WebSocket frame header.
+ // See <http://tools.ietf.org/html/rfc6455#section-5.2> for details.
+ bool final;
+ bool reserved1;
+ bool reserved2;
+ bool reserved3;
+ OpCode opcode;
+ bool masked;
+ uint64 payload_length;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameHeader);
+};
+
+// Contains payload data of part of a WebSocket frame.
+//
+// Payload of a WebSocket frame may be divided into multiple chunks.
+// You need to look at |final_chunk| member variable to detect the end of a
+// series of chunk objects of a WebSocket frame.
+//
+// Frame dissection is necessary to handle WebSocket frame stream containing
+// abritrarily large frames in the browser process. Because the server may send
+// a huge frame that doesn't fit in the memory, we cannot store the entire
+// payload data in the memory.
+//
+// Users of this struct should treat WebSocket frames as a data stream; it's
+// important to keep the frame data flowing, especially in the browser process.
+// Users should not let the data stuck somewhere in the pipeline.
+//
+// This struct is used for reading WebSocket frame data (created by
+// WebSocketFrameParser). To construct WebSocket frames, use functions below.
+struct NET_EXPORT WebSocketFrameChunk {
+ WebSocketFrameChunk();
+ ~WebSocketFrameChunk();
+
+ // Non-null |header| is provided only if this chunk is the first part of
+ // a series of chunks.
+ scoped_ptr<WebSocketFrameHeader> header;
+
+ // Indicates this part is the last chunk of a frame.
+ bool final_chunk;
+
+ // |data| is always unmasked even if the frame is masked. |data| might be
+ // null in the first chunk.
+ scoped_refptr<IOBufferWithSize> data;
+};
+
+// Contains four-byte data representing "masking key" of WebSocket frames.
+struct WebSocketMaskingKey {
+ char key[WebSocketFrameHeader::kMaskingKeyLength];
+};
+
+// Returns the size of WebSocket frame header. The size of WebSocket frame
+// header varies from 2 bytes to 14 bytes depending on the payload length
+// and maskedness.
+NET_EXPORT int GetWebSocketFrameHeaderSize(const WebSocketFrameHeader& header);
+
+// Writes wire format of a WebSocket frame header into |output|, and returns
+// the number of bytes written.
+//
+// WebSocket frame format is defined at:
+// <http://tools.ietf.org/html/rfc6455#section-5.2>. This function writes
+// everything but payload data in a WebSocket frame to |buffer|.
+//
+// If |header->masked| is true, |masking_key| must point to a valid
+// WebSocketMaskingKey object containing the masking key for that frame
+// (possibly generated by GenerateWebSocketMaskingKey() function below).
+// Otherwise, |masking_key| must be NULL.
+//
+// |buffer| should have enough size to contain the frame header.
+// GetWebSocketFrameHeaderSize() can be used to know the size of header
+// beforehand. If the size of |buffer| is insufficient, this function returns
+// ERR_INVALID_ARGUMENT and does not write any data to |buffer|.
+NET_EXPORT int WriteWebSocketFrameHeader(const WebSocketFrameHeader& header,
+ const WebSocketMaskingKey* masking_key,
+ char* buffer,
+ int buffer_size);
+
+// Generates a masking key suitable for use in a new WebSocket frame.
+NET_EXPORT WebSocketMaskingKey GenerateWebSocketMaskingKey();
+
+// Masks WebSocket frame payload.
+//
+// A client must mask every WebSocket frame by XOR'ing the frame payload
+// with four-byte random data (masking key). This function applies the masking
+// to the given payload data.
+//
+// This function masks |data| with |masking_key|, assuming |data| is partial
+// data starting from |frame_offset| bytes from the beginning of the payload
+// data.
+//
+// Since frame masking is a reversible operation, this function can also be
+// used for unmasking a WebSocket frame.
+NET_EXPORT void MaskWebSocketFramePayload(
+ const WebSocketMaskingKey& masking_key,
+ uint64 frame_offset,
+ char* data,
+ int data_size);
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_H_
diff --git a/chromium/net/websockets/websocket_frame_parser.cc b/chromium/net/websockets/websocket_frame_parser.cc
new file mode 100644
index 00000000000..3b199128b42
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame_parser.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_frame_parser.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/big_endian.h"
+#include "net/base/io_buffer.h"
+#include "net/websockets/websocket_frame.h"
+
+namespace {
+
+const uint8 kFinalBit = 0x80;
+const uint8 kReserved1Bit = 0x40;
+const uint8 kReserved2Bit = 0x20;
+const uint8 kReserved3Bit = 0x10;
+const uint8 kOpCodeMask = 0xF;
+const uint8 kMaskBit = 0x80;
+const uint8 kPayloadLengthMask = 0x7F;
+const uint64 kMaxPayloadLengthWithoutExtendedLengthField = 125;
+const uint64 kPayloadLengthWithTwoByteExtendedLengthField = 126;
+const uint64 kPayloadLengthWithEightByteExtendedLengthField = 127;
+
+} // Unnamed namespace.
+
+namespace net {
+
+WebSocketFrameParser::WebSocketFrameParser()
+ : current_read_pos_(0),
+ frame_offset_(0),
+ websocket_error_(kWebSocketNormalClosure) {
+ std::fill(masking_key_.key,
+ masking_key_.key + WebSocketFrameHeader::kMaskingKeyLength,
+ '\0');
+}
+
+WebSocketFrameParser::~WebSocketFrameParser() {}
+
+bool WebSocketFrameParser::Decode(
+ const char* data,
+ size_t length,
+ ScopedVector<WebSocketFrameChunk>* frame_chunks) {
+ if (websocket_error_ != kWebSocketNormalClosure)
+ return false;
+ if (!length)
+ return true;
+
+ // TODO(yutak): Remove copy.
+ buffer_.insert(buffer_.end(), data, data + length);
+
+ while (current_read_pos_ < buffer_.size()) {
+ bool first_chunk = false;
+ if (!current_frame_header_.get()) {
+ DecodeFrameHeader();
+ if (websocket_error_ != kWebSocketNormalClosure)
+ return false;
+ // If frame header is incomplete, then carry over the remaining
+ // data to the next round of Decode().
+ if (!current_frame_header_.get())
+ break;
+ first_chunk = true;
+ }
+
+ scoped_ptr<WebSocketFrameChunk> frame_chunk =
+ DecodeFramePayload(first_chunk);
+ DCHECK(frame_chunk.get());
+ frame_chunks->push_back(frame_chunk.release());
+
+ if (current_frame_header_.get()) {
+ DCHECK(current_read_pos_ == buffer_.size());
+ break;
+ }
+ }
+
+ // Drain unnecessary data. TODO(yutak): Remove copy. (but how?)
+ buffer_.erase(buffer_.begin(), buffer_.begin() + current_read_pos_);
+ current_read_pos_ = 0;
+
+ // Sanity check: the size of carried-over data should not exceed
+ // the maximum possible length of a frame header.
+ static const size_t kMaximumFrameHeaderSize =
+ WebSocketFrameHeader::kBaseHeaderSize +
+ WebSocketFrameHeader::kMaximumExtendedLengthSize +
+ WebSocketFrameHeader::kMaskingKeyLength;
+ DCHECK_LT(buffer_.size(), kMaximumFrameHeaderSize);
+
+ return true;
+}
+
+void WebSocketFrameParser::DecodeFrameHeader() {
+ typedef WebSocketFrameHeader::OpCode OpCode;
+ static const int kMaskingKeyLength = WebSocketFrameHeader::kMaskingKeyLength;
+
+ DCHECK(!current_frame_header_.get());
+
+ const char* start = &buffer_.front() + current_read_pos_;
+ const char* current = start;
+ const char* end = &buffer_.front() + buffer_.size();
+
+ // Header needs 2 bytes at minimum.
+ if (end - current < 2)
+ return;
+
+ uint8 first_byte = *current++;
+ uint8 second_byte = *current++;
+
+ bool final = (first_byte & kFinalBit) != 0;
+ bool reserved1 = (first_byte & kReserved1Bit) != 0;
+ bool reserved2 = (first_byte & kReserved2Bit) != 0;
+ bool reserved3 = (first_byte & kReserved3Bit) != 0;
+ OpCode opcode = first_byte & kOpCodeMask;
+
+ bool masked = (second_byte & kMaskBit) != 0;
+ uint64 payload_length = second_byte & kPayloadLengthMask;
+ if (payload_length == kPayloadLengthWithTwoByteExtendedLengthField) {
+ if (end - current < 2)
+ return;
+ uint16 payload_length_16;
+ ReadBigEndian(current, &payload_length_16);
+ current += 2;
+ payload_length = payload_length_16;
+ if (payload_length <= kMaxPayloadLengthWithoutExtendedLengthField)
+ websocket_error_ = kWebSocketErrorProtocolError;
+ } else if (payload_length == kPayloadLengthWithEightByteExtendedLengthField) {
+ if (end - current < 8)
+ return;
+ ReadBigEndian(current, &payload_length);
+ current += 8;
+ if (payload_length <= kuint16max ||
+ payload_length > static_cast<uint64>(kint64max)) {
+ websocket_error_ = kWebSocketErrorProtocolError;
+ } else if (payload_length > static_cast<uint64>(kint32max)) {
+ websocket_error_ = kWebSocketErrorMessageTooBig;
+ }
+ }
+ if (websocket_error_ != kWebSocketNormalClosure) {
+ buffer_.clear();
+ current_read_pos_ = 0;
+ current_frame_header_.reset();
+ frame_offset_ = 0;
+ return;
+ }
+
+ if (masked) {
+ if (end - current < kMaskingKeyLength)
+ return;
+ std::copy(current, current + kMaskingKeyLength, masking_key_.key);
+ current += kMaskingKeyLength;
+ } else {
+ std::fill(masking_key_.key, masking_key_.key + kMaskingKeyLength, '\0');
+ }
+
+ current_frame_header_.reset(new WebSocketFrameHeader(opcode));
+ current_frame_header_->final = final;
+ current_frame_header_->reserved1 = reserved1;
+ current_frame_header_->reserved2 = reserved2;
+ current_frame_header_->reserved3 = reserved3;
+ current_frame_header_->masked = masked;
+ current_frame_header_->payload_length = payload_length;
+ current_read_pos_ += current - start;
+ DCHECK_EQ(0u, frame_offset_);
+}
+
+scoped_ptr<WebSocketFrameChunk> WebSocketFrameParser::DecodeFramePayload(
+ bool first_chunk) {
+ const char* current = &buffer_.front() + current_read_pos_;
+ const char* end = &buffer_.front() + buffer_.size();
+ uint64 next_size = std::min<uint64>(
+ end - current, current_frame_header_->payload_length - frame_offset_);
+ // This check must pass because |payload_length| is already checked to be
+ // less than std::numeric_limits<int>::max() when the header is parsed.
+ DCHECK_LE(next_size, static_cast<uint64>(kint32max));
+
+ scoped_ptr<WebSocketFrameChunk> frame_chunk(new WebSocketFrameChunk);
+ if (first_chunk) {
+ frame_chunk->header = current_frame_header_->Clone();
+ }
+ frame_chunk->final_chunk = false;
+ if (next_size) {
+ frame_chunk->data = new IOBufferWithSize(static_cast<int>(next_size));
+ char* io_data = frame_chunk->data->data();
+ memcpy(io_data, current, next_size);
+ if (current_frame_header_->masked) {
+ // The masking function is its own inverse, so we use the same function to
+ // unmask as to mask.
+ MaskWebSocketFramePayload(
+ masking_key_, frame_offset_, io_data, next_size);
+ }
+
+ current_read_pos_ += next_size;
+ frame_offset_ += next_size;
+ }
+
+ DCHECK_LE(frame_offset_, current_frame_header_->payload_length);
+ if (frame_offset_ == current_frame_header_->payload_length) {
+ frame_chunk->final_chunk = true;
+ current_frame_header_.reset();
+ frame_offset_ = 0;
+ }
+
+ return frame_chunk.Pass();
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_frame_parser.h b/chromium/net/websockets/websocket_frame_parser.h
new file mode 100644
index 00000000000..52e6f85a6bb
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame_parser.h
@@ -0,0 +1,87 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_errors.h"
+#include "net/websockets/websocket_frame.h"
+
+namespace net {
+
+// Parses WebSocket frames from byte stream.
+//
+// Specification of WebSocket frame format is available at
+// <http://tools.ietf.org/html/rfc6455#section-5>.
+
+class NET_EXPORT WebSocketFrameParser {
+ public:
+ WebSocketFrameParser();
+ ~WebSocketFrameParser();
+
+ // Decodes the given byte stream and stores parsed WebSocket frames in
+ // |frame_chunks|.
+ //
+ // If the parser encounters invalid payload length format, Decode() fails
+ // and returns false. Once Decode() has failed, the parser refuses to decode
+ // any more data and future invocations of Decode() will simply return false.
+ //
+ // Payload data of parsed WebSocket frames may be incomplete; see comments in
+ // websocket_frame.h for more details.
+ bool Decode(const char* data,
+ size_t length,
+ ScopedVector<WebSocketFrameChunk>* frame_chunks);
+
+ // Returns kWebSocketNormalClosure if the parser has not failed to decode
+ // WebSocket frames. Otherwise returns WebSocketError which is defined in
+ // websocket_errors.h. We can convert net::WebSocketError to net::Error by
+ // using WebSocketErrorToNetError().
+ WebSocketError websocket_error() const { return websocket_error_; }
+
+ private:
+ // Tries to decode a frame header from |current_read_pos_|.
+ // If successful, this function updates |current_read_pos_|,
+ // |current_frame_header_|, and |masking_key_| (if available).
+ // This function may set |failed_| to true if it observes a corrupt frame.
+ // If there is not enough data in the remaining buffer to parse a frame
+ // header, this function returns without doing anything.
+ void DecodeFrameHeader();
+
+ // Decodes frame payload and creates a WebSocketFrameChunk object.
+ // This function updates |current_read_pos_| and |frame_offset_| after
+ // parsing. This function returns a frame object even if no payload data is
+ // available at this moment, so the receiver could make use of frame header
+ // information. If the end of frame is reached, this function clears
+ // |current_frame_header_|, |frame_offset_| and |masking_key_|.
+ scoped_ptr<WebSocketFrameChunk> DecodeFramePayload(bool first_chunk);
+
+ // Internal buffer to store the data to parse.
+ std::vector<char> buffer_;
+
+ // Position in |buffer_| where the next round of parsing starts.
+ size_t current_read_pos_;
+
+ // Frame header and masking key of the current frame.
+ // |masking_key_| is filled with zeros if the current frame is not masked.
+ scoped_ptr<WebSocketFrameHeader> current_frame_header_;
+ WebSocketMaskingKey masking_key_;
+
+ // Amount of payload data read so far for the current frame.
+ uint64 frame_offset_;
+
+ WebSocketError websocket_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameParser);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_FRAME_PARSER_H_
diff --git a/chromium/net/websockets/websocket_frame_parser_unittest.cc b/chromium/net/websockets/websocket_frame_parser_unittest.cc
new file mode 100644
index 00000000000..b2a804027a7
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame_parser_unittest.cc
@@ -0,0 +1,576 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_frame_parser.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "base/port.h"
+#include "net/base/io_buffer.h"
+#include "net/websockets/websocket_frame.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+const char kHello[] = "Hello, world!";
+const uint64 kHelloLength = arraysize(kHello) - 1;
+const char kHelloFrame[] = "\x81\x0DHello, world!";
+const uint64 kHelloFrameLength = arraysize(kHelloFrame) - 1;
+const char kMaskedHelloFrame[] =
+ "\x81\x8D\xDE\xAD\xBE\xEF"
+ "\x96\xC8\xD2\x83\xB1\x81\x9E\x98\xB1\xDF\xD2\x8B\xFF";
+const uint64 kMaskedHelloFrameLength = arraysize(kMaskedHelloFrame) - 1;
+
+struct FrameHeaderTestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ uint64 frame_length;
+ WebSocketError error_code;
+};
+
+const FrameHeaderTestCase kFrameHeaderTests[] = {
+ { "\x81\x00", 2, GG_UINT64_C(0), kWebSocketNormalClosure },
+ { "\x81\x7D", 2, GG_UINT64_C(125), kWebSocketNormalClosure },
+ { "\x81\x7E\x00\x7E", 4, GG_UINT64_C(126), kWebSocketNormalClosure },
+ { "\x81\x7E\xFF\xFF", 4, GG_UINT64_C(0xFFFF), kWebSocketNormalClosure },
+ { "\x81\x7F\x00\x00\x00\x00\x00\x01\x00\x00", 10, GG_UINT64_C(0x10000),
+ kWebSocketNormalClosure },
+ { "\x81\x7F\x00\x00\x00\x00\x7F\xFF\xFF\xFF", 10, GG_UINT64_C(0x7FFFFFFF),
+ kWebSocketNormalClosure },
+ { "\x81\x7F\x00\x00\x00\x00\x80\x00\x00\x00", 10, GG_UINT64_C(0x80000000),
+ kWebSocketErrorMessageTooBig },
+ { "\x81\x7F\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 10,
+ GG_UINT64_C(0x7FFFFFFFFFFFFFFF), kWebSocketErrorMessageTooBig }
+};
+const int kNumFrameHeaderTests = arraysize(kFrameHeaderTests);
+
+TEST(WebSocketFrameParserTest, DecodeNormalFrame) {
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(kHelloFrame, kHelloFrameLength, &frames));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ ASSERT_EQ(1u, frames.size());
+ WebSocketFrameChunk* frame = frames[0];
+ ASSERT_TRUE(frame != NULL);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (header) {
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(kHelloLength, header->payload_length);
+ }
+ EXPECT_TRUE(frame->final_chunk);
+
+ ASSERT_EQ(static_cast<int>(kHelloLength), frame->data->size());
+ EXPECT_TRUE(std::equal(kHello, kHello + kHelloLength, frame->data->data()));
+}
+
+TEST(WebSocketFrameParserTest, DecodeMaskedFrame) {
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(
+ parser.Decode(kMaskedHelloFrame, kMaskedHelloFrameLength, &frames));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ ASSERT_EQ(1u, frames.size());
+ WebSocketFrameChunk* frame = frames[0];
+ ASSERT_TRUE(frame != NULL);
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (header) {
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_TRUE(header->masked);
+ EXPECT_EQ(kHelloLength, header->payload_length);
+ }
+ EXPECT_TRUE(frame->final_chunk);
+
+ ASSERT_EQ(static_cast<int>(kHelloLength), frame->data->size());
+ EXPECT_TRUE(std::equal(kHello, kHello + kHelloLength, frame->data->data()));
+}
+
+TEST(WebSocketFrameParserTest, DecodeManyFrames) {
+ struct Input {
+ const char* frame;
+ size_t frame_length;
+ const char* expected_payload;
+ size_t expected_payload_length;
+ };
+ static const Input kInputs[] = {
+ // Each |frame| data is split into two string literals because C++ lexers
+ // consume unlimited number of hex characters in a hex character escape
+ // (e.g. "\x05F" is not treated as { '\x5', 'F', '\0' } but as
+ // { '\x5F', '\0' }).
+ { "\x81\x05" "First", 7, "First", 5 },
+ { "\x81\x06" "Second", 8, "Second", 6 },
+ { "\x81\x05" "Third", 7, "Third", 5 },
+ { "\x81\x06" "Fourth", 8, "Fourth", 6 },
+ { "\x81\x05" "Fifth", 7, "Fifth", 5 },
+ { "\x81\x05" "Sixth", 7, "Sixth", 5 },
+ { "\x81\x07" "Seventh", 9, "Seventh", 7 },
+ { "\x81\x06" "Eighth", 8, "Eighth", 6 },
+ { "\x81\x05" "Ninth", 7, "Ninth", 5 },
+ { "\x81\x05" "Tenth", 7, "Tenth", 5 }
+ };
+ static const int kNumInputs = ARRAYSIZE_UNSAFE(kInputs);
+
+ std::vector<char> input;
+ // Concatenate all frames.
+ for (int i = 0; i < kNumInputs; ++i) {
+ input.insert(input.end(),
+ kInputs[i].frame,
+ kInputs[i].frame + kInputs[i].frame_length);
+ }
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(&input.front(), input.size(), &frames));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ ASSERT_EQ(static_cast<size_t>(kNumInputs), frames.size());
+
+ for (int i = 0; i < kNumInputs; ++i) {
+ WebSocketFrameChunk* frame = frames[i];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ ASSERT_EQ(kInputs[i].expected_payload_length,
+ static_cast<uint64>(frame->data->size()));
+ EXPECT_TRUE(std::equal(
+ kInputs[i].expected_payload,
+ kInputs[i].expected_payload + kInputs[i].expected_payload_length,
+ frame->data->data()));
+
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(kInputs[i].expected_payload_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialFrame) {
+ static const size_t kFrameHeaderSize = 2;
+
+ for (size_t cutting_pos = 0; cutting_pos < kHelloLength; ++cutting_pos) {
+ std::vector<char> input1(kHelloFrame,
+ kHelloFrame + kFrameHeaderSize + cutting_pos);
+ std::vector<char> input2(kHelloFrame + input1.size(),
+ kHelloFrame + kHelloFrameLength);
+
+ std::vector<char> expected1(kHello, kHello + cutting_pos);
+ std::vector<char> expected2(kHello + cutting_pos, kHello + kHelloLength);
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames1;
+ EXPECT_TRUE(parser.Decode(&input1.front(), input1.size(), &frames1));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames1.size());
+ if (frames1.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame1 = frames1[0];
+ EXPECT_TRUE(frame1 != NULL);
+ if (!frame1)
+ continue;
+ EXPECT_FALSE(frame1->final_chunk);
+ if (expected1.size() == 0) {
+ EXPECT_EQ(NULL, frame1->data.get());
+ } else {
+ ASSERT_EQ(cutting_pos, static_cast<size_t>(frame1->data->size()));
+ EXPECT_TRUE(
+ std::equal(expected1.begin(), expected1.end(), frame1->data->data()));
+ }
+ const WebSocketFrameHeader* header1 = frame1->header.get();
+ EXPECT_TRUE(header1 != NULL);
+ if (!header1)
+ continue;
+ EXPECT_TRUE(header1->final);
+ EXPECT_FALSE(header1->reserved1);
+ EXPECT_FALSE(header1->reserved2);
+ EXPECT_FALSE(header1->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header1->opcode);
+ EXPECT_FALSE(header1->masked);
+ EXPECT_EQ(kHelloLength, header1->payload_length);
+
+ ScopedVector<WebSocketFrameChunk> frames2;
+ EXPECT_TRUE(parser.Decode(&input2.front(), input2.size(), &frames2));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames2.size());
+ if (frames2.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame2 = frames2[0];
+ EXPECT_TRUE(frame2 != NULL);
+ if (!frame2)
+ continue;
+ EXPECT_TRUE(frame2->final_chunk);
+ if (expected2.size() == 0) {
+ EXPECT_EQ(NULL, frame2->data.get());
+ } else {
+ ASSERT_EQ(expected2.size(), static_cast<uint64>(frame2->data->size()));
+ EXPECT_TRUE(
+ std::equal(expected2.begin(), expected2.end(), frame2->data->data()));
+ }
+ const WebSocketFrameHeader* header2 = frame2->header.get();
+ EXPECT_TRUE(header2 == NULL);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialMaskedFrame) {
+ static const size_t kFrameHeaderSize = 6;
+
+ for (size_t cutting_pos = 0; cutting_pos < kHelloLength; ++cutting_pos) {
+ std::vector<char> input1(
+ kMaskedHelloFrame, kMaskedHelloFrame + kFrameHeaderSize + cutting_pos);
+ std::vector<char> input2(kMaskedHelloFrame + input1.size(),
+ kMaskedHelloFrame + kMaskedHelloFrameLength);
+
+ std::vector<char> expected1(kHello, kHello + cutting_pos);
+ std::vector<char> expected2(kHello + cutting_pos, kHello + kHelloLength);
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames1;
+ EXPECT_TRUE(parser.Decode(&input1.front(), input1.size(), &frames1));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames1.size());
+ if (frames1.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame1 = frames1[0];
+ EXPECT_TRUE(frame1 != NULL);
+ if (!frame1)
+ continue;
+ EXPECT_FALSE(frame1->final_chunk);
+ if (expected1.size() == 0) {
+ EXPECT_EQ(NULL, frame1->data.get());
+ } else {
+ ASSERT_EQ(expected1.size(), static_cast<uint64>(frame1->data->size()));
+ EXPECT_TRUE(
+ std::equal(expected1.begin(), expected1.end(), frame1->data->data()));
+ }
+ const WebSocketFrameHeader* header1 = frame1->header.get();
+ EXPECT_TRUE(header1 != NULL);
+ if (!header1)
+ continue;
+ EXPECT_TRUE(header1->final);
+ EXPECT_FALSE(header1->reserved1);
+ EXPECT_FALSE(header1->reserved2);
+ EXPECT_FALSE(header1->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header1->opcode);
+ EXPECT_TRUE(header1->masked);
+ EXPECT_EQ(kHelloLength, header1->payload_length);
+
+ ScopedVector<WebSocketFrameChunk> frames2;
+ EXPECT_TRUE(parser.Decode(&input2.front(), input2.size(), &frames2));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames2.size());
+ if (frames2.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame2 = frames2[0];
+ EXPECT_TRUE(frame2 != NULL);
+ if (!frame2)
+ continue;
+ EXPECT_TRUE(frame2->final_chunk);
+ if (expected2.size() == 0) {
+ EXPECT_EQ(NULL, frame2->data.get());
+ } else {
+ ASSERT_EQ(expected2.size(), static_cast<uint64>(frame2->data->size()));
+ EXPECT_TRUE(
+ std::equal(expected2.begin(), expected2.end(), frame2->data->data()));
+ }
+ const WebSocketFrameHeader* header2 = frame2->header.get();
+ EXPECT_TRUE(header2 == NULL);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodeFramesOfVariousLengths) {
+ for (int i = 0; i < kNumFrameHeaderTests; ++i) {
+ const char* frame_header = kFrameHeaderTests[i].frame_header;
+ size_t frame_header_length = kFrameHeaderTests[i].frame_header_length;
+ uint64 frame_length = kFrameHeaderTests[i].frame_length;
+
+ std::vector<char> input(frame_header, frame_header + frame_header_length);
+ // Limit the payload size not to flood the console on failure.
+ static const uint64 kMaxPayloadSize = 200;
+ uint64 input_payload_size = std::min(frame_length, kMaxPayloadSize);
+ input.insert(input.end(), input_payload_size, 'a');
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_EQ(kFrameHeaderTests[i].error_code == kWebSocketNormalClosure,
+ parser.Decode(&input.front(), input.size(), &frames));
+ EXPECT_EQ(kFrameHeaderTests[i].error_code, parser.websocket_error());
+ if (kFrameHeaderTests[i].error_code != kWebSocketNormalClosure) {
+ EXPECT_EQ(0u, frames.size());
+ } else {
+ EXPECT_EQ(1u, frames.size());
+ }
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ if (frame_length == input_payload_size) {
+ EXPECT_TRUE(frame->final_chunk);
+ } else {
+ EXPECT_FALSE(frame->final_chunk);
+ }
+ std::vector<char> expected_payload(input_payload_size, 'a');
+ if (expected_payload.size() == 0) {
+ EXPECT_EQ(NULL, frame->data.get());
+ } else {
+ ASSERT_EQ(expected_payload.size(),
+ static_cast<uint64>(frame->data->size()));
+ EXPECT_TRUE(std::equal(expected_payload.begin(),
+ expected_payload.end(),
+ frame->data->data()));
+ }
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(frame_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, DecodePartialHeader) {
+ for (int i = 0; i < kNumFrameHeaderTests; ++i) {
+ const char* frame_header = kFrameHeaderTests[i].frame_header;
+ size_t frame_header_length = kFrameHeaderTests[i].frame_header_length;
+ uint64 frame_length = kFrameHeaderTests[i].frame_length;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ // Feed each byte to the parser to see if the parser behaves correctly
+ // when it receives partial frame header.
+ size_t last_byte_offset = frame_header_length - 1;
+ for (size_t j = 0; j < frame_header_length; ++j) {
+ bool failed =
+ kFrameHeaderTests[i].error_code != kWebSocketNormalClosure &&
+ j == last_byte_offset;
+ EXPECT_EQ(!failed, parser.Decode(frame_header + j, 1, &frames));
+ if (failed) {
+ EXPECT_EQ(kFrameHeaderTests[i].error_code, parser.websocket_error());
+ } else {
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ }
+ if (kFrameHeaderTests[i].error_code == kWebSocketNormalClosure &&
+ j == last_byte_offset) {
+ EXPECT_EQ(1u, frames.size());
+ } else {
+ EXPECT_EQ(0u, frames.size());
+ }
+ }
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ if (frame_length == 0u) {
+ EXPECT_TRUE(frame->final_chunk);
+ } else {
+ EXPECT_FALSE(frame->final_chunk);
+ }
+ EXPECT_EQ(NULL, frame->data.get());
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(frame_length, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, InvalidLengthEncoding) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ };
+ static const TestCase kTests[] = {
+ // For frames with two-byte extended length field, the payload length
+ // should be 126 (0x7E) bytes or more.
+ { "\x81\x7E\x00\x00", 4 },
+ { "\x81\x7E\x00\x7D", 4 },
+ // For frames with eight-byte extended length field, the payload length
+ // should be 0x10000 bytes or more.
+ { "\x81\x7F\x00\x00\x00\x00\x00\x00\x00\x00", 10 },
+ { "\x81\x7E\x00\x00\x00\x00\x00\x00\xFF\xFF", 10 },
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_FALSE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_EQ(kWebSocketErrorProtocolError, parser.websocket_error());
+ EXPECT_EQ(0u, frames.size());
+
+ // Once the parser has failed, it no longer accepts any input (even if
+ // the input is empty).
+ EXPECT_FALSE(parser.Decode("", 0, &frames));
+ EXPECT_EQ(kWebSocketErrorProtocolError, parser.websocket_error());
+ EXPECT_EQ(0u, frames.size());
+ }
+}
+
+TEST(WebSocketFrameParserTest, FrameTypes) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ WebSocketFrameHeader::OpCode opcode;
+ };
+ static const TestCase kTests[] = {
+ { "\x80\x00", 2, WebSocketFrameHeader::kOpCodeContinuation },
+ { "\x81\x00", 2, WebSocketFrameHeader::kOpCodeText },
+ { "\x82\x00", 2, WebSocketFrameHeader::kOpCodeBinary },
+ { "\x88\x00", 2, WebSocketFrameHeader::kOpCodeClose },
+ { "\x89\x00", 2, WebSocketFrameHeader::kOpCodePing },
+ { "\x8A\x00", 2, WebSocketFrameHeader::kOpCodePong },
+ // These are undefined opcodes, but the parser needs to be able to parse
+ // them anyway.
+ { "\x83\x00", 2, 0x3 },
+ { "\x84\x00", 2, 0x4 },
+ { "\x85\x00", 2, 0x5 },
+ { "\x86\x00", 2, 0x6 },
+ { "\x87\x00", 2, 0x7 },
+ { "\x8B\x00", 2, 0xB },
+ { "\x8C\x00", 2, 0xC },
+ { "\x8D\x00", 2, 0xD },
+ { "\x8E\x00", 2, 0xE },
+ { "\x8F\x00", 2, 0xF }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+ WebSocketFrameHeader::OpCode opcode = kTests[i].opcode;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames.size());
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ EXPECT_EQ(NULL, frame->data.get());
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_TRUE(header->final);
+ EXPECT_FALSE(header->reserved1);
+ EXPECT_FALSE(header->reserved2);
+ EXPECT_FALSE(header->reserved3);
+ EXPECT_EQ(opcode, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(0u, header->payload_length);
+ }
+}
+
+TEST(WebSocketFrameParserTest, FinalBitAndReservedBits) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ bool final;
+ bool reserved1;
+ bool reserved2;
+ bool reserved3;
+ };
+ static const TestCase kTests[] = {
+ { "\x81\x00", 2, true, false, false, false },
+ { "\x01\x00", 2, false, false, false, false },
+ { "\xC1\x00", 2, true, true, false, false },
+ { "\xA1\x00", 2, true, false, true, false },
+ { "\x91\x00", 2, true, false, false, true },
+ { "\x71\x00", 2, false, true, true, true },
+ { "\xF1\x00", 2, true, true, true, true }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ const char* frame_header = kTests[i].frame_header;
+ size_t frame_header_length = kTests[i].frame_header_length;
+ bool final = kTests[i].final;
+ bool reserved1 = kTests[i].reserved1;
+ bool reserved2 = kTests[i].reserved2;
+ bool reserved3 = kTests[i].reserved3;
+
+ WebSocketFrameParser parser;
+
+ ScopedVector<WebSocketFrameChunk> frames;
+ EXPECT_TRUE(parser.Decode(frame_header, frame_header_length, &frames));
+ EXPECT_EQ(kWebSocketNormalClosure, parser.websocket_error());
+ EXPECT_EQ(1u, frames.size());
+ if (frames.size() != 1u)
+ continue;
+ WebSocketFrameChunk* frame = frames[0];
+ EXPECT_TRUE(frame != NULL);
+ if (!frame)
+ continue;
+ EXPECT_TRUE(frame->final_chunk);
+ EXPECT_EQ(NULL, frame->data.get());
+ const WebSocketFrameHeader* header = frame->header.get();
+ EXPECT_TRUE(header != NULL);
+ if (!header)
+ continue;
+ EXPECT_EQ(final, header->final);
+ EXPECT_EQ(reserved1, header->reserved1);
+ EXPECT_EQ(reserved2, header->reserved2);
+ EXPECT_EQ(reserved3, header->reserved3);
+ EXPECT_EQ(WebSocketFrameHeader::kOpCodeText, header->opcode);
+ EXPECT_FALSE(header->masked);
+ EXPECT_EQ(0u, header->payload_length);
+ }
+}
+
+} // Unnamed namespace
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_frame_unittest.cc b/chromium/net/websockets/websocket_frame_unittest.cc
new file mode 100644
index 00000000000..1652b3b24f9
--- /dev/null
+++ b/chromium/net/websockets/websocket_frame_unittest.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_frame.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/memory/aligned_memory.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Run
+// out/Release/net_unittests --websocket-mask-iterations=100000
+// --gtest_filter='WebSocketFrameTestMaskBenchmark.*'
+// to benchmark the MaskWebSocketFramePayload() function.
+static const char kBenchmarkIterations[] = "websocket-mask-iterations";
+static const int kDefaultIterations = 10;
+static const int kLongPayloadSize = 1 << 16;
+
+namespace net {
+
+TEST(WebSocketFrameHeaderTest, FrameLengths) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ uint64 frame_length;
+ };
+ static const TestCase kTests[] = {
+ { "\x81\x00", 2, GG_UINT64_C(0) },
+ { "\x81\x7D", 2, GG_UINT64_C(125) },
+ { "\x81\x7E\x00\x7E", 4, GG_UINT64_C(126) },
+ { "\x81\x7E\xFF\xFF", 4, GG_UINT64_C(0xFFFF) },
+ { "\x81\x7F\x00\x00\x00\x00\x00\x01\x00\x00", 10, GG_UINT64_C(0x10000) },
+ { "\x81\x7F\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 10,
+ GG_UINT64_C(0x7FFFFFFFFFFFFFFF) }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketFrameHeader header(WebSocketFrameHeader::kOpCodeText);
+ header.final = true;
+ header.payload_length = kTests[i].frame_length;
+
+ std::vector<char> expected_output(
+ kTests[i].frame_header,
+ kTests[i].frame_header + kTests[i].frame_header_length);
+ std::vector<char> output(expected_output.size());
+ EXPECT_EQ(static_cast<int>(expected_output.size()),
+ WriteWebSocketFrameHeader(
+ header, NULL, &output.front(), output.size()));
+ EXPECT_EQ(expected_output, output);
+ }
+}
+
+TEST(WebSocketFrameHeaderTest, FrameLengthsWithMasking) {
+ static const char kMaskingKey[] = "\xDE\xAD\xBE\xEF";
+ COMPILE_ASSERT(ARRAYSIZE_UNSAFE(kMaskingKey) - 1 ==
+ WebSocketFrameHeader::kMaskingKeyLength,
+ incorrect_masking_key_size);
+
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ uint64 frame_length;
+ };
+ static const TestCase kTests[] = {
+ { "\x81\x80\xDE\xAD\xBE\xEF", 6, GG_UINT64_C(0) },
+ { "\x81\xFD\xDE\xAD\xBE\xEF", 6, GG_UINT64_C(125) },
+ { "\x81\xFE\x00\x7E\xDE\xAD\xBE\xEF", 8, GG_UINT64_C(126) },
+ { "\x81\xFE\xFF\xFF\xDE\xAD\xBE\xEF", 8, GG_UINT64_C(0xFFFF) },
+ { "\x81\xFF\x00\x00\x00\x00\x00\x01\x00\x00\xDE\xAD\xBE\xEF", 14,
+ GG_UINT64_C(0x10000) },
+ { "\x81\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xDE\xAD\xBE\xEF", 14,
+ GG_UINT64_C(0x7FFFFFFFFFFFFFFF) }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ WebSocketMaskingKey masking_key;
+ std::copy(kMaskingKey,
+ kMaskingKey + WebSocketFrameHeader::kMaskingKeyLength,
+ masking_key.key);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketFrameHeader header(WebSocketFrameHeader::kOpCodeText);
+ header.final = true;
+ header.masked = true;
+ header.payload_length = kTests[i].frame_length;
+
+ std::vector<char> expected_output(
+ kTests[i].frame_header,
+ kTests[i].frame_header + kTests[i].frame_header_length);
+ std::vector<char> output(expected_output.size());
+ EXPECT_EQ(static_cast<int>(expected_output.size()),
+ WriteWebSocketFrameHeader(
+ header, &masking_key, &output.front(), output.size()));
+ EXPECT_EQ(expected_output, output);
+ }
+}
+
+TEST(WebSocketFrameHeaderTest, FrameOpCodes) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ WebSocketFrameHeader::OpCode opcode;
+ };
+ static const TestCase kTests[] = {
+ { "\x80\x00", 2, WebSocketFrameHeader::kOpCodeContinuation },
+ { "\x81\x00", 2, WebSocketFrameHeader::kOpCodeText },
+ { "\x82\x00", 2, WebSocketFrameHeader::kOpCodeBinary },
+ { "\x88\x00", 2, WebSocketFrameHeader::kOpCodeClose },
+ { "\x89\x00", 2, WebSocketFrameHeader::kOpCodePing },
+ { "\x8A\x00", 2, WebSocketFrameHeader::kOpCodePong },
+ // These are undefined opcodes, but the builder should accept them anyway.
+ { "\x83\x00", 2, 0x3 },
+ { "\x84\x00", 2, 0x4 },
+ { "\x85\x00", 2, 0x5 },
+ { "\x86\x00", 2, 0x6 },
+ { "\x87\x00", 2, 0x7 },
+ { "\x8B\x00", 2, 0xB },
+ { "\x8C\x00", 2, 0xC },
+ { "\x8D\x00", 2, 0xD },
+ { "\x8E\x00", 2, 0xE },
+ { "\x8F\x00", 2, 0xF }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketFrameHeader header(kTests[i].opcode);
+ header.final = true;
+ header.payload_length = 0;
+
+ std::vector<char> expected_output(
+ kTests[i].frame_header,
+ kTests[i].frame_header + kTests[i].frame_header_length);
+ std::vector<char> output(expected_output.size());
+ EXPECT_EQ(static_cast<int>(expected_output.size()),
+ WriteWebSocketFrameHeader(
+ header, NULL, &output.front(), output.size()));
+ EXPECT_EQ(expected_output, output);
+ }
+}
+
+TEST(WebSocketFrameHeaderTest, FinalBitAndReservedBits) {
+ struct TestCase {
+ const char* frame_header;
+ size_t frame_header_length;
+ bool final;
+ bool reserved1;
+ bool reserved2;
+ bool reserved3;
+ };
+ static const TestCase kTests[] = {
+ { "\x81\x00", 2, true, false, false, false },
+ { "\x01\x00", 2, false, false, false, false },
+ { "\xC1\x00", 2, true, true, false, false },
+ { "\xA1\x00", 2, true, false, true, false },
+ { "\x91\x00", 2, true, false, false, true },
+ { "\x71\x00", 2, false, true, true, true },
+ { "\xF1\x00", 2, true, true, true, true }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketFrameHeader header(WebSocketFrameHeader::kOpCodeText);
+ header.final = kTests[i].final;
+ header.reserved1 = kTests[i].reserved1;
+ header.reserved2 = kTests[i].reserved2;
+ header.reserved3 = kTests[i].reserved3;
+ header.payload_length = 0;
+
+ std::vector<char> expected_output(
+ kTests[i].frame_header,
+ kTests[i].frame_header + kTests[i].frame_header_length);
+ std::vector<char> output(expected_output.size());
+ EXPECT_EQ(static_cast<int>(expected_output.size()),
+ WriteWebSocketFrameHeader(
+ header, NULL, &output.front(), output.size()));
+ EXPECT_EQ(expected_output, output);
+ }
+}
+
+TEST(WebSocketFrameHeaderTest, InsufficientBufferSize) {
+ struct TestCase {
+ uint64 payload_length;
+ bool masked;
+ size_t expected_header_size;
+ };
+ static const TestCase kTests[] = {
+ { GG_UINT64_C(0), false, 2u },
+ { GG_UINT64_C(125), false, 2u },
+ { GG_UINT64_C(126), false, 4u },
+ { GG_UINT64_C(0xFFFF), false, 4u },
+ { GG_UINT64_C(0x10000), false, 10u },
+ { GG_UINT64_C(0x7FFFFFFFFFFFFFFF), false, 10u },
+ { GG_UINT64_C(0), true, 6u },
+ { GG_UINT64_C(125), true, 6u },
+ { GG_UINT64_C(126), true, 8u },
+ { GG_UINT64_C(0xFFFF), true, 8u },
+ { GG_UINT64_C(0x10000), true, 14u },
+ { GG_UINT64_C(0x7FFFFFFFFFFFFFFF), true, 14u }
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketFrameHeader header(WebSocketFrameHeader::kOpCodeText);
+ header.final = true;
+ header.opcode = WebSocketFrameHeader::kOpCodeText;
+ header.masked = kTests[i].masked;
+ header.payload_length = kTests[i].payload_length;
+
+ char dummy_buffer[14];
+ // Set an insufficient size to |buffer_size|.
+ EXPECT_EQ(
+ ERR_INVALID_ARGUMENT,
+ WriteWebSocketFrameHeader(
+ header, NULL, dummy_buffer, kTests[i].expected_header_size - 1));
+ }
+}
+
+TEST(WebSocketFrameTest, MaskPayload) {
+ struct TestCase {
+ const char* masking_key;
+ uint64 frame_offset;
+ const char* input;
+ const char* output;
+ size_t data_length;
+ };
+ static const TestCase kTests[] = {
+ { "\xDE\xAD\xBE\xEF", 0, "FooBar", "\x98\xC2\xD1\xAD\xBF\xDF", 6 },
+ { "\xDE\xAD\xBE\xEF", 1, "FooBar", "\xEB\xD1\x80\x9C\xCC\xCC", 6 },
+ { "\xDE\xAD\xBE\xEF", 2, "FooBar", "\xF8\x80\xB1\xEF\xDF\x9D", 6 },
+ { "\xDE\xAD\xBE\xEF", 3, "FooBar", "\xA9\xB1\xC2\xFC\x8E\xAC", 6 },
+ { "\xDE\xAD\xBE\xEF", 4, "FooBar", "\x98\xC2\xD1\xAD\xBF\xDF", 6 },
+ { "\xDE\xAD\xBE\xEF", 42, "FooBar", "\xF8\x80\xB1\xEF\xDF\x9D", 6 },
+ { "\xDE\xAD\xBE\xEF", 0, "", "", 0 },
+ { "\xDE\xAD\xBE\xEF", 0, "\xDE\xAD\xBE\xEF", "\x00\x00\x00\x00", 4 },
+ { "\xDE\xAD\xBE\xEF", 0, "\x00\x00\x00\x00", "\xDE\xAD\xBE\xEF", 4 },
+ { "\x00\x00\x00\x00", 0, "FooBar", "FooBar", 6 },
+ { "\xFF\xFF\xFF\xFF", 0, "FooBar", "\xB9\x90\x90\xBD\x9E\x8D", 6 },
+ };
+ static const int kNumTests = ARRAYSIZE_UNSAFE(kTests);
+
+ for (int i = 0; i < kNumTests; ++i) {
+ WebSocketMaskingKey masking_key;
+ std::copy(kTests[i].masking_key,
+ kTests[i].masking_key + WebSocketFrameHeader::kMaskingKeyLength,
+ masking_key.key);
+ std::vector<char> frame_data(kTests[i].input,
+ kTests[i].input + kTests[i].data_length);
+ std::vector<char> expected_output(kTests[i].output,
+ kTests[i].output + kTests[i].data_length);
+ MaskWebSocketFramePayload(masking_key,
+ kTests[i].frame_offset,
+ frame_data.empty() ? NULL : &frame_data.front(),
+ frame_data.size());
+ EXPECT_EQ(expected_output, frame_data);
+ }
+}
+
+// Check that all combinations of alignment, frame offset and chunk size work
+// correctly for MaskWebSocketFramePayload(). This is mainly used to ensure that
+// vectorisation optimisations don't break anything. We could take a "white box"
+// approach and only test the edge cases, but since the exhaustive "black box"
+// approach runs in acceptable time, we don't have to take the risk of being
+// clever.
+//
+// This brute-force approach runs in O(N^3) time where N is the size of the
+// maximum vector size we want to test again. This might need reconsidering if
+// MaskWebSocketFramePayload() is ever optimised for a dedicated vector
+// architecture.
+TEST(WebSocketFrameTest, MaskPayloadAlignment) {
+ // This reflects what might be implemented in the future, rather than
+ // the current implementation. FMA3 and FMA4 support 256-bit vector ops.
+ static const size_t kMaxVectorSizeInBits = 256;
+ static const size_t kMaxVectorSize = kMaxVectorSizeInBits / 8;
+ static const size_t kMaxVectorAlignment = kMaxVectorSize;
+ static const size_t kMaskingKeyLength =
+ WebSocketFrameHeader::kMaskingKeyLength;
+ static const size_t kScratchBufferSize =
+ kMaxVectorAlignment + kMaxVectorSize * 2;
+ static const char kTestMask[] = "\xd2\xba\x5a\xbe";
+ // We use 786 bits of random input to reduce the risk of correlated errors.
+ static const char kTestInput[] = {
+ "\x3d\x77\x1d\x1b\x19\x8c\x48\xa3\x19\x6d\xf7\xcc\x39\xe7\x57\x0b"
+ "\x69\x8c\xda\x4b\xfc\xac\x2c\xd3\x49\x96\x6e\x8a\x7b\x5a\x32\x76"
+ "\xd0\x11\x43\xa0\x89\xfc\x76\x2b\x10\x2f\x4c\x7b\x4f\xa6\xdd\xe4"
+ "\xfc\x8e\xd8\x72\xcf\x7e\x37\xcd\x31\xcd\xc1\xc0\x89\x0c\xa7\x4c"
+ "\xda\xa8\x4b\x75\xa1\xcb\xa9\x77\x19\x4d\x6e\xdf\xc8\x08\x1c\xb6"
+ "\x6d\xfb\x38\x04\x44\xd5\xba\x57\x9f\x76\xb0\x2e\x07\x91\xe6\xa8"
+ };
+ static const size_t kTestInputSize = arraysize(kTestInput) - 1;
+ static const char kTestOutput[] = {
+ "\xef\xcd\x47\xa5\xcb\x36\x12\x1d\xcb\xd7\xad\x72\xeb\x5d\x0d\xb5"
+ "\xbb\x36\x80\xf5\x2e\x16\x76\x6d\x9b\x2c\x34\x34\xa9\xe0\x68\xc8"
+ "\x02\xab\x19\x1e\x5b\x46\x2c\x95\xc2\x95\x16\xc5\x9d\x1c\x87\x5a"
+ "\x2e\x34\x82\xcc\x1d\xc4\x6d\x73\xe3\x77\x9b\x7e\x5b\xb6\xfd\xf2"
+ "\x08\x12\x11\xcb\x73\x71\xf3\xc9\xcb\xf7\x34\x61\x1a\xb2\x46\x08"
+ "\xbf\x41\x62\xba\x96\x6f\xe0\xe9\x4d\xcc\xea\x90\xd5\x2b\xbc\x16"
+ };
+ COMPILE_ASSERT(arraysize(kTestInput) == arraysize(kTestOutput),
+ output_and_input_arrays_have_the_same_length);
+ scoped_ptr_malloc<char, base::ScopedPtrAlignedFree> scratch(
+ static_cast<char*>(
+ base::AlignedAlloc(kScratchBufferSize, kMaxVectorAlignment)));
+ WebSocketMaskingKey masking_key;
+ std::copy(kTestMask, kTestMask + kMaskingKeyLength, masking_key.key);
+ for (size_t frame_offset = 0; frame_offset < kMaskingKeyLength;
+ ++frame_offset) {
+ for (size_t alignment = 0; alignment < kMaxVectorAlignment; ++alignment) {
+ char* const aligned_scratch = scratch.get() + alignment;
+ const size_t aligned_len = std::min(kScratchBufferSize - alignment,
+ kTestInputSize - frame_offset);
+ for (size_t chunk_size = 1; chunk_size < kMaxVectorSize; ++chunk_size) {
+ memcpy(aligned_scratch, kTestInput + frame_offset, aligned_len);
+ for (size_t chunk_start = 0; chunk_start < aligned_len;
+ chunk_start += chunk_size) {
+ const size_t this_chunk_size =
+ std::min(chunk_size, aligned_len - chunk_start);
+ MaskWebSocketFramePayload(masking_key,
+ frame_offset + chunk_start,
+ aligned_scratch + chunk_start,
+ this_chunk_size);
+ }
+ // Stop the test if it fails, since we don't want to spew thousands of
+ // failures.
+ ASSERT_TRUE(std::equal(aligned_scratch,
+ aligned_scratch + aligned_len,
+ kTestOutput + frame_offset))
+ << "Output failed to match for frame_offset=" << frame_offset
+ << ", alignment=" << alignment << ", chunk_size=" << chunk_size;
+ }
+ }
+ }
+}
+
+class WebSocketFrameTestMaskBenchmark : public testing::Test {
+ public:
+ WebSocketFrameTestMaskBenchmark() : iterations_(kDefaultIterations) {}
+
+ virtual void SetUp() {
+ std::string iterations(
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kBenchmarkIterations));
+ int benchmark_iterations = 0;
+ if (!iterations.empty() &&
+ base::StringToInt(iterations, &benchmark_iterations)) {
+ iterations_ = benchmark_iterations;
+ }
+ }
+
+ void Benchmark(const char* const payload, size_t size) {
+ std::vector<char> scratch(payload, payload + size);
+ static const char kMaskingKey[] = "\xFE\xED\xBE\xEF";
+ COMPILE_ASSERT(
+ arraysize(kMaskingKey) == WebSocketFrameHeader::kMaskingKeyLength + 1,
+ incorrect_masking_key_size);
+ WebSocketMaskingKey masking_key;
+ std::copy(kMaskingKey,
+ kMaskingKey + WebSocketFrameHeader::kMaskingKeyLength,
+ masking_key.key);
+ LOG(INFO) << "Benchmarking MaskWebSocketFramePayload() for " << iterations_
+ << " iterations";
+ using base::TimeTicks;
+ TimeTicks start = TimeTicks::HighResNow();
+ for (int x = 0; x < iterations_; ++x) {
+ MaskWebSocketFramePayload(
+ masking_key, x % size, &scratch.front(), scratch.size());
+ }
+ double total_time_ms =
+ 1000 * (TimeTicks::HighResNow() - start).InMillisecondsF() /
+ iterations_;
+ LOG(INFO) << "Payload size " << size
+ << base::StringPrintf(" took %.03f microseconds per iteration",
+ total_time_ms);
+ }
+
+ private:
+ int iterations_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketFrameTestMaskBenchmark);
+};
+
+TEST_F(WebSocketFrameTestMaskBenchmark, BenchmarkMaskShortPayload) {
+ static const char kShortPayload[] = "Short Payload";
+ Benchmark(kShortPayload, arraysize(kShortPayload));
+}
+
+TEST_F(WebSocketFrameTestMaskBenchmark, BenchmarkMaskLongPayload) {
+ scoped_ptr<char[]> payload(new char[kLongPayloadSize]);
+ std::fill(payload.get(), payload.get() + kLongPayloadSize, 'a');
+ Benchmark(payload.get(), kLongPayloadSize);
+}
+
+// "IsKnownDataOpCode" is currently implemented in an "obviously correct"
+// manner, but we test is anyway in case it changes to a more complex
+// implementation in future.
+TEST(WebSocketFrameHeaderTest, IsKnownDataOpCode) {
+ // Make the test less verbose.
+ typedef WebSocketFrameHeader Frame;
+
+ // Known opcode, is used for data frames
+ EXPECT_TRUE(Frame::IsKnownDataOpCode(Frame::kOpCodeContinuation));
+ EXPECT_TRUE(Frame::IsKnownDataOpCode(Frame::kOpCodeText));
+ EXPECT_TRUE(Frame::IsKnownDataOpCode(Frame::kOpCodeBinary));
+
+ // Known opcode, is used for control frames
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(Frame::kOpCodeClose));
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(Frame::kOpCodePing));
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(Frame::kOpCodePong));
+
+ // Check that unused opcodes return false
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(Frame::kOpCodeDataUnused));
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(Frame::kOpCodeControlUnused));
+
+ // Check that opcodes with the 4 bit set return false
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(0x6));
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(0xF));
+
+ // Check that out-of-range opcodes return false
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(-1));
+ EXPECT_FALSE(Frame::IsKnownDataOpCode(0xFF));
+}
+
+// "IsKnownControlOpCode" is implemented in an "obviously correct" manner but
+// might be optimised in future.
+TEST(WebSocketFrameHeaderTest, IsKnownControlOpCode) {
+ // Make the test less verbose.
+ typedef WebSocketFrameHeader Frame;
+
+ // Known opcode, is used for data frames
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(Frame::kOpCodeContinuation));
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(Frame::kOpCodeText));
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(Frame::kOpCodeBinary));
+
+ // Known opcode, is used for control frames
+ EXPECT_TRUE(Frame::IsKnownControlOpCode(Frame::kOpCodeClose));
+ EXPECT_TRUE(Frame::IsKnownControlOpCode(Frame::kOpCodePing));
+ EXPECT_TRUE(Frame::IsKnownControlOpCode(Frame::kOpCodePong));
+
+ // Check that unused opcodes return false
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(Frame::kOpCodeDataUnused));
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(Frame::kOpCodeControlUnused));
+
+ // Check that opcodes with the 4 bit set return false
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(0x6));
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(0xF));
+
+ // Check that out-of-range opcodes return false
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(-1));
+ EXPECT_FALSE(Frame::IsKnownControlOpCode(0xFF));
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_handshake_handler.cc b/chromium/net/websockets/websocket_handshake_handler.cc
new file mode 100644
index 00000000000..e69bb6dba48
--- /dev/null
+++ b/chromium/net/websockets/websocket_handshake_handler.cc
@@ -0,0 +1,573 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_handshake_handler.h"
+
+#include <limits>
+
+#include "base/base64.h"
+#include "base/md5.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "url/gurl.h"
+
+namespace {
+
+const size_t kRequestKey3Size = 8U;
+const size_t kResponseKeySize = 16U;
+
+// First version that introduced new WebSocket handshake which does not
+// require sending "key3" or "response key" data after headers.
+const int kMinVersionOfHybiNewHandshake = 4;
+
+// Used when we calculate the value of Sec-WebSocket-Accept.
+const char* const kWebSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+void ParseHandshakeHeader(
+ const char* handshake_message, int len,
+ std::string* status_line,
+ std::string* headers) {
+ size_t i = base::StringPiece(handshake_message, len).find_first_of("\r\n");
+ if (i == base::StringPiece::npos) {
+ *status_line = std::string(handshake_message, len);
+ *headers = "";
+ return;
+ }
+ // |status_line| includes \r\n.
+ *status_line = std::string(handshake_message, i + 2);
+
+ int header_len = len - (i + 2) - 2;
+ if (header_len > 0) {
+ // |handshake_message| includes trailing \r\n\r\n.
+ // |headers| doesn't include 2nd \r\n.
+ *headers = std::string(handshake_message + i + 2, header_len);
+ } else {
+ *headers = "";
+ }
+}
+
+void FetchHeaders(const std::string& headers,
+ const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values) {
+ net::HttpUtil::HeadersIterator iter(headers.begin(), headers.end(), "\r\n");
+ while (iter.GetNext()) {
+ for (size_t i = 0; i < headers_to_get_len; i++) {
+ if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(),
+ headers_to_get[i])) {
+ values->push_back(iter.values());
+ }
+ }
+ }
+}
+
+bool GetHeaderName(std::string::const_iterator line_begin,
+ std::string::const_iterator line_end,
+ std::string::const_iterator* name_begin,
+ std::string::const_iterator* name_end) {
+ std::string::const_iterator colon = std::find(line_begin, line_end, ':');
+ if (colon == line_end) {
+ return false;
+ }
+ *name_begin = line_begin;
+ *name_end = colon;
+ if (*name_begin == *name_end || net::HttpUtil::IsLWS(**name_begin))
+ return false;
+ net::HttpUtil::TrimLWS(name_begin, name_end);
+ return true;
+}
+
+// Similar to HttpUtil::StripHeaders, but it preserves malformed headers, that
+// is, lines that are not formatted as "<name>: <value>\r\n".
+std::string FilterHeaders(
+ const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ std::string filtered_headers;
+
+ base::StringTokenizer lines(headers.begin(), headers.end(), "\r\n");
+ while (lines.GetNext()) {
+ std::string::const_iterator line_begin = lines.token_begin();
+ std::string::const_iterator line_end = lines.token_end();
+ std::string::const_iterator name_begin;
+ std::string::const_iterator name_end;
+ bool should_remove = false;
+ if (GetHeaderName(line_begin, line_end, &name_begin, &name_end)) {
+ for (size_t i = 0; i < headers_to_remove_len; ++i) {
+ if (LowerCaseEqualsASCII(name_begin, name_end, headers_to_remove[i])) {
+ should_remove = true;
+ break;
+ }
+ }
+ }
+ if (!should_remove) {
+ filtered_headers.append(line_begin, line_end);
+ filtered_headers.append("\r\n");
+ }
+ }
+ return filtered_headers;
+}
+
+int GetVersionFromRequest(const std::string& request_headers) {
+ std::vector<std::string> values;
+ const char* const headers_to_get[2] = { "sec-websocket-version",
+ "sec-websocket-draft" };
+ FetchHeaders(request_headers, headers_to_get, 2, &values);
+ DCHECK_LE(values.size(), 1U);
+ if (values.empty())
+ return 0;
+ int version;
+ bool conversion_success = base::StringToInt(values[0], &version);
+ DCHECK(conversion_success);
+ DCHECK_GE(version, 1);
+ return version;
+}
+
+} // namespace
+
+namespace net {
+
+namespace internal {
+
+void GetKeyNumber(const std::string& key, std::string* challenge) {
+ uint32 key_number = 0;
+ uint32 spaces = 0;
+ for (size_t i = 0; i < key.size(); ++i) {
+ if (isdigit(key[i])) {
+ // key_number should not overflow. (it comes from
+ // WebCore/websockets/WebSocketHandshake.cpp).
+ // Trust, but verify.
+ DCHECK_GE((std::numeric_limits<uint32>::max() - (key[i] - '0')) / 10,
+ key_number) << "Supplied key would overflow";
+ key_number = key_number * 10 + key[i] - '0';
+ } else if (key[i] == ' ') {
+ ++spaces;
+ }
+ }
+ DCHECK_NE(0u, spaces) << "Key must contain at least one space";
+ if (spaces == 0)
+ return;
+ DCHECK_EQ(0u, key_number % spaces) << "Key number must be an integral "
+ << "multiple of the number of spaces";
+ key_number /= spaces;
+
+ char part[4];
+ for (int i = 0; i < 4; i++) {
+ part[3 - i] = key_number & 0xFF;
+ key_number >>= 8;
+ }
+ challenge->append(part, 4);
+}
+
+} // namespace internal
+
+WebSocketHandshakeRequestHandler::WebSocketHandshakeRequestHandler()
+ : original_length_(0),
+ raw_length_(0),
+ protocol_version_(-1) {}
+
+bool WebSocketHandshakeRequestHandler::ParseRequest(
+ const char* data, int length) {
+ DCHECK_GT(length, 0);
+ std::string input(data, length);
+ int input_header_length =
+ HttpUtil::LocateEndOfHeaders(input.data(), input.size(), 0);
+ if (input_header_length <= 0)
+ return false;
+
+ ParseHandshakeHeader(input.data(),
+ input_header_length,
+ &status_line_,
+ &headers_);
+
+ // WebSocket protocol drafts hixie-76 (hybi-00), hybi-01, 02 and 03 require
+ // the clients to send key3 after the handshake request header fields.
+ // Hybi-04 and later drafts, on the other hand, no longer have key3
+ // in the handshake format.
+ protocol_version_ = GetVersionFromRequest(headers_);
+ DCHECK_GE(protocol_version_, 0);
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ key3_ = "";
+ original_length_ = input_header_length;
+ return true;
+ }
+
+ if (input_header_length + kRequestKey3Size > input.size())
+ return false;
+
+ // Assumes WebKit doesn't send any data after handshake request message
+ // until handshake is finished.
+ // Thus, |key3_| is part of handshake message, and not in part
+ // of WebSocket frame stream.
+ DCHECK_EQ(kRequestKey3Size, input.size() - input_header_length);
+ key3_ = std::string(input.data() + input_header_length,
+ input.size() - input_header_length);
+ original_length_ = input.size();
+ return true;
+}
+
+size_t WebSocketHandshakeRequestHandler::original_length() const {
+ return original_length_;
+}
+
+void WebSocketHandshakeRequestHandler::AppendHeaderIfMissing(
+ const std::string& name, const std::string& value) {
+ DCHECK(!headers_.empty());
+ HttpUtil::AppendHeaderIfMissing(name.c_str(), value, &headers_);
+}
+
+void WebSocketHandshakeRequestHandler::RemoveHeaders(
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ DCHECK(!headers_.empty());
+ headers_ = FilterHeaders(
+ headers_, headers_to_remove, headers_to_remove_len);
+}
+
+HttpRequestInfo WebSocketHandshakeRequestHandler::GetRequestInfo(
+ const GURL& url, std::string* challenge) {
+ HttpRequestInfo request_info;
+ request_info.url = url;
+ size_t method_end = base::StringPiece(status_line_).find_first_of(" ");
+ if (method_end != base::StringPiece::npos)
+ request_info.method = std::string(status_line_.data(), method_end);
+
+ request_info.extra_headers.Clear();
+ request_info.extra_headers.AddHeadersFromString(headers_);
+
+ request_info.extra_headers.RemoveHeader("Upgrade");
+ request_info.extra_headers.RemoveHeader("Connection");
+
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ std::string key;
+ bool header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key");
+ *challenge = key;
+ } else {
+ challenge->clear();
+ std::string key;
+ bool header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key1", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key1");
+ internal::GetKeyNumber(key, challenge);
+
+ header_present =
+ request_info.extra_headers.GetHeader("Sec-WebSocket-Key2", &key);
+ DCHECK(header_present);
+ request_info.extra_headers.RemoveHeader("Sec-WebSocket-Key2");
+ internal::GetKeyNumber(key, challenge);
+
+ challenge->append(key3_);
+ }
+
+ return request_info;
+}
+
+bool WebSocketHandshakeRequestHandler::GetRequestHeaderBlock(
+ const GURL& url,
+ SpdyHeaderBlock* headers,
+ std::string* challenge,
+ int spdy_protocol_version) {
+ // Construct opening handshake request headers as a SPDY header block.
+ // For details, see WebSocket Layering over SPDY/3 Draft 8.
+ if (spdy_protocol_version <= 2) {
+ (*headers)["path"] = url.path();
+ (*headers)["version"] =
+ base::StringPrintf("%s%d", "WebSocket/", protocol_version_);
+ (*headers)["scheme"] = url.scheme();
+ } else {
+ (*headers)[":path"] = url.path();
+ (*headers)[":version"] =
+ base::StringPrintf("%s%d", "WebSocket/", protocol_version_);
+ (*headers)[":scheme"] = url.scheme();
+ }
+
+ HttpUtil::HeadersIterator iter(headers_.begin(), headers_.end(), "\r\n");
+ while (iter.GetNext()) {
+ if (LowerCaseEqualsASCII(iter.name_begin(), iter.name_end(), "upgrade") ||
+ LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "connection") ||
+ LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "sec-websocket-version")) {
+ // These headers must be ignored.
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "sec-websocket-key")) {
+ *challenge = iter.values();
+ // Sec-WebSocket-Key is not sent to the server.
+ continue;
+ } else if (LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "host") ||
+ LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "origin") ||
+ LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "sec-websocket-protocol") ||
+ LowerCaseEqualsASCII(iter.name_begin(),
+ iter.name_end(),
+ "sec-websocket-extensions")) {
+ // TODO(toyoshim): Some WebSocket extensions may not be compatible with
+ // SPDY. We should omit them from a Sec-WebSocket-Extension header.
+ std::string name;
+ if (spdy_protocol_version <= 2)
+ name = StringToLowerASCII(iter.name());
+ else
+ name = ":" + StringToLowerASCII(iter.name());
+ (*headers)[name] = iter.values();
+ continue;
+ }
+ // Others should be sent out to |headers|.
+ std::string name = StringToLowerASCII(iter.name());
+ SpdyHeaderBlock::iterator found = headers->find(name);
+ if (found == headers->end()) {
+ (*headers)[name] = iter.values();
+ } else {
+ // For now, websocket doesn't use multiple headers, but follows to http.
+ found->second.append(1, '\0'); // +=() doesn't append 0's
+ found->second.append(iter.values());
+ }
+ }
+
+ return true;
+}
+
+std::string WebSocketHandshakeRequestHandler::GetRawRequest() {
+ DCHECK(!status_line_.empty());
+ DCHECK(!headers_.empty());
+ // The following works on both hybi-04 and older handshake,
+ // because |key3_| is guaranteed to be empty if the handshake was hybi-04's.
+ std::string raw_request = status_line_ + headers_ + "\r\n" + key3_;
+ raw_length_ = raw_request.size();
+ return raw_request;
+}
+
+size_t WebSocketHandshakeRequestHandler::raw_length() const {
+ DCHECK_GT(raw_length_, 0);
+ return raw_length_;
+}
+
+int WebSocketHandshakeRequestHandler::protocol_version() const {
+ DCHECK_GE(protocol_version_, 0);
+ return protocol_version_;
+}
+
+WebSocketHandshakeResponseHandler::WebSocketHandshakeResponseHandler()
+ : original_header_length_(0),
+ protocol_version_(0) {}
+
+WebSocketHandshakeResponseHandler::~WebSocketHandshakeResponseHandler() {}
+
+int WebSocketHandshakeResponseHandler::protocol_version() const {
+ DCHECK_GE(protocol_version_, 0);
+ return protocol_version_;
+}
+
+void WebSocketHandshakeResponseHandler::set_protocol_version(
+ int protocol_version) {
+ DCHECK_GE(protocol_version, 0);
+ protocol_version_ = protocol_version;
+}
+
+size_t WebSocketHandshakeResponseHandler::ParseRawResponse(
+ const char* data, int length) {
+ DCHECK_GT(length, 0);
+ if (HasResponse()) {
+ DCHECK(!status_line_.empty());
+ // headers_ might be empty for wrong response from server.
+ return 0;
+ }
+
+ size_t old_original_length = original_.size();
+
+ original_.append(data, length);
+ // TODO(ukai): fail fast when response gives wrong status code.
+ original_header_length_ = HttpUtil::LocateEndOfHeaders(
+ original_.data(), original_.size(), 0);
+ if (!HasResponse())
+ return length;
+
+ ParseHandshakeHeader(original_.data(),
+ original_header_length_,
+ &status_line_,
+ &headers_);
+ int header_size = status_line_.size() + headers_.size();
+ DCHECK_GE(original_header_length_, header_size);
+ header_separator_ = std::string(original_.data() + header_size,
+ original_header_length_ - header_size);
+ key_ = std::string(original_.data() + original_header_length_,
+ GetResponseKeySize());
+ return original_header_length_ + GetResponseKeySize() - old_original_length;
+}
+
+bool WebSocketHandshakeResponseHandler::HasResponse() const {
+ return original_header_length_ > 0 &&
+ original_header_length_ + GetResponseKeySize() <= original_.size();
+}
+
+bool WebSocketHandshakeResponseHandler::ParseResponseInfo(
+ const HttpResponseInfo& response_info,
+ const std::string& challenge) {
+ if (!response_info.headers.get())
+ return false;
+
+ std::string response_message;
+ response_message = response_info.headers->GetStatusLine();
+ response_message += "\r\n";
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake)
+ response_message += "Upgrade: websocket\r\n";
+ else
+ response_message += "Upgrade: WebSocket\r\n";
+ response_message += "Connection: Upgrade\r\n";
+
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake) {
+ std::string hash = base::SHA1HashString(challenge + kWebSocketGuid);
+ std::string websocket_accept;
+ bool encode_success = base::Base64Encode(hash, &websocket_accept);
+ DCHECK(encode_success);
+ response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n";
+ }
+
+ void* iter = NULL;
+ std::string name;
+ std::string value;
+ while (response_info.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ response_message += name + ": " + value + "\r\n";
+ }
+ response_message += "\r\n";
+
+ if (protocol_version_ < kMinVersionOfHybiNewHandshake) {
+ base::MD5Digest digest;
+ base::MD5Sum(challenge.data(), challenge.size(), &digest);
+
+ const char* digest_data = reinterpret_cast<char*>(digest.a);
+ response_message.append(digest_data, sizeof(digest.a));
+ }
+
+ return ParseRawResponse(response_message.data(),
+ response_message.size()) == response_message.size();
+}
+
+bool WebSocketHandshakeResponseHandler::ParseResponseHeaderBlock(
+ const SpdyHeaderBlock& headers,
+ const std::string& challenge,
+ int spdy_protocol_version) {
+ SpdyHeaderBlock::const_iterator status;
+ if (spdy_protocol_version <= 2)
+ status = headers.find("status");
+ else
+ status = headers.find(":status");
+ if (status == headers.end())
+ return false;
+ std::string response_message;
+ response_message =
+ base::StringPrintf("%s%s\r\n", "HTTP/1.1 ", status->second.c_str());
+ response_message += "Upgrade: websocket\r\n";
+ response_message += "Connection: Upgrade\r\n";
+
+ std::string hash = base::SHA1HashString(challenge + kWebSocketGuid);
+ std::string websocket_accept;
+ bool encode_success = base::Base64Encode(hash, &websocket_accept);
+ DCHECK(encode_success);
+ response_message += "Sec-WebSocket-Accept: " + websocket_accept + "\r\n";
+
+ for (SpdyHeaderBlock::const_iterator iter = headers.begin();
+ iter != headers.end();
+ ++iter) {
+ // For each value, if the server sends a NUL-separated list of values,
+ // we separate that back out into individual headers for each value
+ // in the list.
+ if ((spdy_protocol_version <= 2 &&
+ LowerCaseEqualsASCII(iter->first, "status")) ||
+ (spdy_protocol_version >= 3 &&
+ LowerCaseEqualsASCII(iter->first, ":status"))) {
+ // The status value is already handled as the first line of
+ // |response_message|. Just skip here.
+ continue;
+ }
+ const std::string& value = iter->second;
+ size_t start = 0;
+ size_t end = 0;
+ do {
+ end = value.find('\0', start);
+ std::string tval;
+ if (end != std::string::npos)
+ tval = value.substr(start, (end - start));
+ else
+ tval = value.substr(start);
+ if (spdy_protocol_version >= 3 &&
+ (LowerCaseEqualsASCII(iter->first, ":sec-websocket-protocol") ||
+ LowerCaseEqualsASCII(iter->first, ":sec-websocket-extensions")))
+ response_message += iter->first.substr(1) + ": " + tval + "\r\n";
+ else
+ response_message += iter->first + ": " + tval + "\r\n";
+ start = end + 1;
+ } while (end != std::string::npos);
+ }
+ response_message += "\r\n";
+
+ return ParseRawResponse(response_message.data(),
+ response_message.size()) == response_message.size();
+}
+
+void WebSocketHandshakeResponseHandler::GetHeaders(
+ const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values) {
+ DCHECK(HasResponse());
+ DCHECK(!status_line_.empty());
+ // headers_ might be empty for wrong response from server.
+ if (headers_.empty())
+ return;
+
+ FetchHeaders(headers_, headers_to_get, headers_to_get_len, values);
+}
+
+void WebSocketHandshakeResponseHandler::RemoveHeaders(
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ DCHECK(HasResponse());
+ DCHECK(!status_line_.empty());
+ // headers_ might be empty for wrong response from server.
+ if (headers_.empty())
+ return;
+
+ headers_ = FilterHeaders(headers_, headers_to_remove, headers_to_remove_len);
+}
+
+std::string WebSocketHandshakeResponseHandler::GetRawResponse() const {
+ DCHECK(HasResponse());
+ return std::string(original_.data(),
+ original_header_length_ + GetResponseKeySize());
+}
+
+std::string WebSocketHandshakeResponseHandler::GetResponse() {
+ DCHECK(HasResponse());
+ DCHECK(!status_line_.empty());
+ // headers_ might be empty for wrong response from server.
+ DCHECK_EQ(GetResponseKeySize(), key_.size());
+
+ return status_line_ + headers_ + header_separator_ + key_;
+}
+
+size_t WebSocketHandshakeResponseHandler::GetResponseKeySize() const {
+ if (protocol_version_ >= kMinVersionOfHybiNewHandshake)
+ return 0;
+ return kResponseKeySize;
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_handshake_handler.h b/chromium/net/websockets/websocket_handshake_handler.h
new file mode 100644
index 00000000000..14c0fcec4cc
--- /dev/null
+++ b/chromium/net/websockets/websocket_handshake_handler.h
@@ -0,0 +1,171 @@
+// Copyright (c) 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.
+//
+// WebSocketHandshake*Handler handles WebSocket handshake request message
+// from WebKit renderer process, and WebSocket handshake response message
+// from WebSocket server.
+// It modifies messages for the following reason:
+// - We don't trust WebKit renderer process, so we'll not expose HttpOnly
+// cookies to the renderer process, so handles HttpOnly cookies in
+// browser process.
+//
+// The classes below support two styles of handshake: handshake based
+// on hixie-76 draft and one based on hybi-04 draft. The critical difference
+// between these two is how they pass challenge and response values. Hixie-76
+// based handshake appends a few bytes of binary data after header fields of
+// handshake request and response. These data are called "key3" (for request)
+// or "response key" (for response). On the other hand, handshake based on
+// hybi-04 and later drafts put challenge and response values into handshake
+// header fields, thus we do not need to send or receive extra bytes after
+// handshake headers.
+//
+// While we are working on updating WebSocket implementation in WebKit to
+// conform to the latest protocol draft, we need to accept both styles of
+// handshake. After we land the protocol changes in WebKit, we will be able to
+// drop codes handling old-style handshake.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
+#define NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "net/base/net_export.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_header_block.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE WebSocketHandshakeRequestHandler {
+ public:
+ WebSocketHandshakeRequestHandler();
+ ~WebSocketHandshakeRequestHandler() {}
+
+ // Parses WebSocket handshake request from renderer process.
+ // It assumes a WebSocket handshake request message is given at once, and
+ // no other data is added to the request message.
+ bool ParseRequest(const char* data, int length);
+
+ size_t original_length() const;
+
+ // Appends the header value pair for |name| and |value|, if |name| doesn't
+ // exist.
+ void AppendHeaderIfMissing(const std::string& name,
+ const std::string& value);
+ // Removes the headers that matches (case insensitive).
+ void RemoveHeaders(const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
+ // Gets request info to open WebSocket connection.
+ // Fills challenge data (concatenation of key1, 2 and 3 for hybi-03 and
+ // earlier, or Sec-WebSocket-Key header value for hybi-04 and later)
+ // in |challenge|.
+ HttpRequestInfo GetRequestInfo(const GURL& url, std::string* challenge);
+ // Gets request as SpdyHeaderBlock.
+ // Also, fills challenge data in |challenge|.
+ bool GetRequestHeaderBlock(const GURL& url,
+ SpdyHeaderBlock* headers,
+ std::string* challenge,
+ int spdy_protocol_version);
+ // Gets WebSocket handshake raw request message to open WebSocket
+ // connection.
+ std::string GetRawRequest();
+ // Calling raw_length is valid only after GetRawRequest() call.
+ size_t raw_length() const;
+
+ // Returns the value of Sec-WebSocket-Version or Sec-WebSocket-Draft header
+ // (the latter is an old name of the former). Returns 0 if both headers were
+ // absent, which means the handshake was based on hybi-00 (= hixie-76).
+ // Should only be called after the handshake has been parsed.
+ int protocol_version() const;
+
+ private:
+ std::string status_line_;
+ std::string headers_;
+ std::string key3_;
+ int original_length_;
+ int raw_length_;
+ int protocol_version_; // "-1" means we haven't parsed the handshake yet.
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeRequestHandler);
+};
+
+class NET_EXPORT_PRIVATE WebSocketHandshakeResponseHandler {
+ public:
+ WebSocketHandshakeResponseHandler();
+ ~WebSocketHandshakeResponseHandler();
+
+ // Set WebSocket protocol version before parsing the response.
+ // Default is 0 (hybi-00, which is same as hixie-76).
+ int protocol_version() const;
+ void set_protocol_version(int protocol_version);
+
+ // Parses WebSocket handshake response from WebSocket server.
+ // Returns number of bytes in |data| used for WebSocket handshake response
+ // message, including response key. If it already got whole WebSocket
+ // handshake response message, returns zero. In other words,
+ // [data + returned value, data + length) will be WebSocket frame data
+ // after handshake response message.
+ // TODO(ukai): fail fast when response gives wrong status code.
+ size_t ParseRawResponse(const char* data, int length);
+ // Returns true if it already parses full handshake response message.
+ bool HasResponse() const;
+ // Parses WebSocket handshake response info given as HttpResponseInfo.
+ bool ParseResponseInfo(const HttpResponseInfo& response_info,
+ const std::string& challenge);
+ // Parses WebSocket handshake response as SpdyHeaderBlock.
+ bool ParseResponseHeaderBlock(const SpdyHeaderBlock& headers,
+ const std::string& challenge,
+ int spdy_protocol_version);
+
+ // Gets the headers value.
+ void GetHeaders(const char* const headers_to_get[],
+ size_t headers_to_get_len,
+ std::vector<std::string>* values);
+ // Removes the headers that matches (case insensitive).
+ void RemoveHeaders(const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
+ // Gets raw WebSocket handshake response received from WebSocket server.
+ std::string GetRawResponse() const;
+
+ // Gets WebSocket handshake response message sent to renderer process.
+ std::string GetResponse();
+
+ private:
+ // Returns the length of response key. This function will return 0
+ // if the specified WebSocket protocol version does not require
+ // sending response key.
+ size_t GetResponseKeySize() const;
+
+ std::string original_;
+ int original_header_length_;
+ std::string status_line_;
+ std::string headers_;
+ std::string header_separator_;
+ std::string key_;
+ int protocol_version_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketHandshakeResponseHandler);
+};
+
+namespace internal {
+
+// Private Functions (Exposed for Unit Testing) -------------------------------
+
+// Gets a key number from |key| and appends the number to |challenge|.
+// The key number (/part_N/) is extracted as step per 4.-8. in
+// "5.2. Sending the server's opening handshake" of
+// http://tools.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
+// TODO(ricea): Remove this when we remove support for pre-RFC6455 versions of
+// WebSockets.
+void NET_EXPORT_PRIVATE GetKeyNumber(const std::string& key,
+ std::string* challenge);
+
+} // namespace internal
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_HANDSHAKE_HANDLER_H_
diff --git a/chromium/net/websockets/websocket_handshake_handler_spdy_unittest.cc b/chromium/net/websockets/websocket_handshake_handler_spdy_unittest.cc
new file mode 100644
index 00000000000..a8276cd220a
--- /dev/null
+++ b/chromium/net/websockets/websocket_handshake_handler_spdy_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_handshake_handler.h"
+
+#include <string>
+
+#include "net/socket/next_proto.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_websocket_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+class WebSocketHandshakeHandlerSpdyTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<NextProto> {
+ protected:
+ WebSocketHandshakeHandlerSpdyTest() : spdy_util_(GetParam()) {}
+
+ SpdyWebSocketTestUtil spdy_util_;
+};
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ WebSocketHandshakeHandlerSpdyTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(WebSocketHandshakeHandlerSpdyTest, RequestResponse) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Extensions: foo\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(13, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url,
+ &headers,
+ &challenge,
+ spdy_util_.spdy_version()));
+
+ EXPECT_EQ(url.path(), spdy_util_.GetHeader(headers, "path"));
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "upgrade").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Upgrade").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "connection").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Connection").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Key").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-websocket-key").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Version").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-webSocket-version").empty());
+ EXPECT_EQ("example.com", spdy_util_.GetHeader(headers, "host"));
+ EXPECT_EQ("http://example.com", spdy_util_.GetHeader(headers, "origin"));
+ EXPECT_EQ("sample", spdy_util_.GetHeader(headers, "sec-websocket-protocol"));
+ EXPECT_EQ("foo", spdy_util_.GetHeader(headers, "sec-websocket-extensions"));
+ EXPECT_EQ("ws", spdy_util_.GetHeader(headers, "scheme"));
+ EXPECT_EQ("WebSocket/13", spdy_util_.GetHeader(headers, "version"));
+
+ static const char expected_challenge[] = "dGhlIHNhbXBsZSBub25jZQ==";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ headers.clear();
+
+ spdy_util_.SetHeader("status", "101 Switching Protocols", &headers);
+ spdy_util_.SetHeader("sec-websocket-protocol", "sample", &headers);
+ spdy_util_.SetHeader("sec-websocket-extensions", "foo", &headers);
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(13);
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(
+ headers, challenge, spdy_util_.spdy_version()));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "sec-websocket-extensions: foo\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+TEST_P(WebSocketHandshakeHandlerSpdyTest, RequestResponseWithCookies) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ // Note that websocket won't use multiple headers in request now.
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Extensions: foo\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(13, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ SpdyHeaderBlock headers;
+ ASSERT_TRUE(request_handler.GetRequestHeaderBlock(url,
+ &headers,
+ &challenge,
+ spdy_util_.spdy_version()));
+
+ EXPECT_EQ(url.path(), spdy_util_.GetHeader(headers, "path"));
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "upgrade").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Upgrade").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "connection").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Connection").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Key").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-websocket-key").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "Sec-WebSocket-Version").empty());
+ EXPECT_TRUE(spdy_util_.GetHeader(headers, "sec-webSocket-version").empty());
+ EXPECT_EQ("example.com", spdy_util_.GetHeader(headers, "host"));
+ EXPECT_EQ("http://example.com", spdy_util_.GetHeader(headers, "origin"));
+ EXPECT_EQ("sample", spdy_util_.GetHeader(headers, "sec-websocket-protocol"));
+ EXPECT_EQ("foo", spdy_util_.GetHeader(headers, "sec-websocket-extensions"));
+ EXPECT_EQ("ws", spdy_util_.GetHeader(headers, "scheme"));
+ EXPECT_EQ("WebSocket/13", spdy_util_.GetHeader(headers, "version"));
+ EXPECT_EQ("WK-websocket-test=1; WK-websocket-test-httponly=1",
+ headers["cookie"]);
+
+ const char expected_challenge[] = "dGhlIHNhbXBsZSBub25jZQ==";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ headers.clear();
+
+ spdy_util_.SetHeader("status", "101 Switching Protocols", &headers);
+ spdy_util_.SetHeader("sec-websocket-protocol", "sample", &headers);
+ spdy_util_.SetHeader("sec-websocket-extensions", "foo", &headers);
+ std::string cookie = "WK-websocket-test=1";
+ cookie.append(1, '\0');
+ cookie += "WK-websocket-test-httponly=1; HttpOnly";
+ headers["set-cookie"] = cookie;
+
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(13);
+ EXPECT_TRUE(response_handler.ParseResponseHeaderBlock(
+ headers, challenge, spdy_util_.spdy_version()));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ // Note that order of sec-websocket-* is sensitive with hash_map order.
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "sec-websocket-extensions: foo\r\n"
+ "sec-websocket-protocol: sample\r\n"
+ "set-cookie: WK-websocket-test=1\r\n"
+ "set-cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_handshake_handler_unittest.cc b/chromium/net/websockets/websocket_handshake_handler_unittest.cc
new file mode 100644
index 00000000000..e7d2d75ae21
--- /dev/null
+++ b/chromium/net/websockets/websocket_handshake_handler_unittest.cc
@@ -0,0 +1,583 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_handshake_handler.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "url/gurl.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char* const kCookieHeaders[] = {
+ "cookie", "cookie2"
+};
+
+const char* const kSetCookieHeaders[] = {
+ "set-cookie", "set-cookie2"
+};
+
+// A test fixture to simplify tests for GetKeyNumber().
+class WebSocketHandshakeGetKeyNumberTest : public ::testing::Test {
+ protected:
+ static const char kExampleFromDraftKey1[];
+
+ // The object is default-initialised with an empty challenge and the example
+ // key from draft-ietf-hybi-thewebsocketprotocol-00. These can be changed
+ // using set_challenge() and set_key().
+ WebSocketHandshakeGetKeyNumberTest()
+ : challenge_(), key_(kExampleFromDraftKey1) {}
+
+ // A convenience wrapper for the function under test which automatically
+ // passes in the arguments stored in the object.
+ void GetKeyNumber() { ::net::internal::GetKeyNumber(key_, &challenge_); }
+
+ // Read current challenge.
+ const std::string& challenge() const { return challenge_; }
+
+ // Overwrite challenge.
+ void set_challenge(const std::string& challenge) { challenge_ = challenge; }
+
+ // Reset the challenge to be empty.
+ void reset_challenge() { challenge_.clear(); }
+
+ // Change key.
+ void set_key(const std::string& key) { key_ = key; }
+
+ private:
+ std::string challenge_;
+ std::string key_;
+};
+
+const char WebSocketHandshakeGetKeyNumberTest::kExampleFromDraftKey1[] =
+ "3e6b263 4 17 80";
+
+// A version of the above fixture for death tests.
+class WebSocketHandshakeGetKeyNumberDeathTest
+ : public WebSocketHandshakeGetKeyNumberTest {
+};
+
+} // namespace
+
+namespace net {
+
+TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequest) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeRequestMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeRequestHandlerTest, SimpleRequestHybi06Handshake) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeRequestMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeRequestHandlerTest, ReplaceRequestCookies) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-websocket-test=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ handler.AppendHeaderIfMissing("Cookie",
+ "WK-websocket-test=1; "
+ "WK-websocket-test-httponly=1");
+
+ static const char kHandshakeRequestExpectedMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeRequestHandlerTest,
+ ReplaceRequestCookiesHybi06Handshake) {
+ WebSocketHandshakeRequestHandler handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "Cookie: WK-websocket-test=1\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, handler.protocol_version());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ handler.AppendHeaderIfMissing("Cookie",
+ "WK-websocket-test=1; "
+ "WK-websocket-test-httponly=1");
+
+ static const char kHandshakeRequestExpectedMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "Cookie: WK-websocket-test=1; WK-websocket-test-httponly=1\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeRequestExpectedMessage, handler.GetRawRequest());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponse) {
+ WebSocketHandshakeResponseHandler handler;
+ EXPECT_EQ(0, handler.protocol_version());
+
+ static const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, SimpleResponseHybi06Handshake) {
+ WebSocketHandshakeResponseHandler handler;
+ handler.set_protocol_version(6);
+ EXPECT_EQ(6, handler.protocol_version());
+
+ static const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+
+ handler.RemoveHeaders(kCookieHeaders, arraysize(kCookieHeaders));
+
+ EXPECT_EQ(kHandshakeResponseMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, ReplaceResponseCookies) {
+ WebSocketHandshakeResponseHandler handler;
+ EXPECT_EQ(0, handler.protocol_version());
+
+ static const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: WK-websocket-test-1\r\n"
+ "Set-Cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ std::vector<std::string> cookies;
+ handler.GetHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders), &cookies);
+ ASSERT_EQ(2U, cookies.size());
+ EXPECT_EQ("WK-websocket-test-1", cookies[0]);
+ EXPECT_EQ("WK-websocket-test-httponly=1; HttpOnly", cookies[1]);
+ handler.RemoveHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders));
+
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest,
+ ReplaceResponseCookiesHybi06Handshake) {
+ WebSocketHandshakeResponseHandler handler;
+ handler.set_protocol_version(6);
+ EXPECT_EQ(6, handler.protocol_version());
+
+ static const char kHandshakeResponseMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: WK-websocket-test-1\r\n"
+ "Set-Cookie: WK-websocket-test-httponly=1; HttpOnly\r\n"
+ "\r\n";
+
+ EXPECT_EQ(strlen(kHandshakeResponseMessage),
+ handler.ParseRawResponse(kHandshakeResponseMessage,
+ strlen(kHandshakeResponseMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ std::vector<std::string> cookies;
+ handler.GetHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders), &cookies);
+ ASSERT_EQ(2U, cookies.size());
+ EXPECT_EQ("WK-websocket-test-1", cookies[0]);
+ EXPECT_EQ("WK-websocket-test-httponly=1; HttpOnly", cookies[1]);
+ handler.RemoveHeaders(kSetCookieHeaders, arraysize(kSetCookieHeaders));
+
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, BadResponse) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char kBadMessage[] = "\n\n\r\net-Location: w";
+ EXPECT_EQ(strlen(kBadMessage),
+ handler.ParseRawResponse(kBadMessage, strlen(kBadMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ EXPECT_EQ(kBadMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeResponseHandlerTest, BadResponse2) {
+ WebSocketHandshakeResponseHandler handler;
+
+ static const char kBadMessage[] = "\n\r\n\r\net-Location: w";
+ EXPECT_EQ(strlen(kBadMessage),
+ handler.ParseRawResponse(kBadMessage, strlen(kBadMessage)));
+ EXPECT_TRUE(handler.HasResponse());
+ EXPECT_EQ(kBadMessage, handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeHandlerTest, HttpRequestResponse) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n"
+ "^n:ds[4U";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(0, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ const HttpRequestInfo& request_info =
+ request_handler.GetRequestInfo(url, &challenge);
+
+ EXPECT_EQ(url, request_info.url);
+ EXPECT_EQ("GET", request_info.method);
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Upgrade"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Connection"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key1"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key2"));
+ std::string value;
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Host", &value));
+ EXPECT_EQ("example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Origin", &value));
+ EXPECT_EQ("http://example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Protocol",
+ &value));
+ EXPECT_EQ("sample", value);
+
+ const char expected_challenge[] = "\x31\x6e\x41\x13\x0f\x7e\xd6\x3c^n:ds[4U";
+
+ EXPECT_EQ(expected_challenge, challenge);
+
+ static const char kHandshakeResponseHeader[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n";
+
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(kHandshakeResponseHeader,
+ strlen(kHandshakeResponseHeader));
+ HttpResponseInfo response_info;
+ response_info.headers = new HttpResponseHeaders(raw_headers);
+
+ EXPECT_TRUE(StartsWithASCII(response_info.headers->GetStatusLine(),
+ "HTTP/1.1 101 ", false));
+ EXPECT_FALSE(response_info.headers->HasHeader("Upgrade"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Connection"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Origin",
+ "http://example.com"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Location",
+ "ws://example.com/demo"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Protocol",
+ "sample"));
+
+ WebSocketHandshakeResponseHandler response_handler;
+ EXPECT_EQ(0, response_handler.protocol_version());
+ EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Location: ws://example.com/demo\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+TEST(WebSocketHandshakeHandlerTest, HttpRequestResponseHybi06Handshake) {
+ WebSocketHandshakeRequestHandler request_handler;
+
+ static const char kHandshakeRequestMessage[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Sec-WebSocket-Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 6\r\n"
+ "\r\n";
+
+ EXPECT_TRUE(request_handler.ParseRequest(kHandshakeRequestMessage,
+ strlen(kHandshakeRequestMessage)));
+ EXPECT_EQ(6, request_handler.protocol_version());
+
+ GURL url("ws://example.com/demo");
+ std::string challenge;
+ const HttpRequestInfo& request_info =
+ request_handler.GetRequestInfo(url, &challenge);
+
+ EXPECT_EQ(url, request_info.url);
+ EXPECT_EQ("GET", request_info.method);
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Upgrade"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Connection"));
+ EXPECT_FALSE(request_info.extra_headers.HasHeader("Sec-WebSocket-Key"));
+ std::string value;
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Host", &value));
+ EXPECT_EQ("example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Origin",
+ &value));
+ EXPECT_EQ("http://example.com", value);
+ EXPECT_TRUE(request_info.extra_headers.GetHeader("Sec-WebSocket-Protocol",
+ &value));
+ EXPECT_EQ("sample", value);
+
+ EXPECT_EQ("dGhlIHNhbXBsZSBub25jZQ==", challenge);
+
+ static const char kHandshakeResponseHeader[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n";
+
+ std::string raw_headers =
+ HttpUtil::AssembleRawHeaders(kHandshakeResponseHeader,
+ strlen(kHandshakeResponseHeader));
+ HttpResponseInfo response_info;
+ response_info.headers = new HttpResponseHeaders(raw_headers);
+
+ EXPECT_TRUE(StartsWithASCII(response_info.headers->GetStatusLine(),
+ "HTTP/1.1 101 ", false));
+ EXPECT_FALSE(response_info.headers->HasHeader("Upgrade"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Connection"));
+ EXPECT_FALSE(response_info.headers->HasHeader("Sec-WebSocket-Accept"));
+ EXPECT_TRUE(response_info.headers->HasHeaderValue("Sec-WebSocket-Protocol",
+ "sample"));
+
+ WebSocketHandshakeResponseHandler response_handler;
+ response_handler.set_protocol_version(request_handler.protocol_version());
+ EXPECT_EQ(6, response_handler.protocol_version());
+
+ EXPECT_TRUE(response_handler.ParseResponseInfo(response_info, challenge));
+ EXPECT_TRUE(response_handler.HasResponse());
+
+ static const char kHandshakeResponseExpectedMessage[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+ EXPECT_EQ(kHandshakeResponseExpectedMessage, response_handler.GetResponse());
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, AppendsToString) {
+ set_challenge("hello");
+ GetKeyNumber();
+ EXPECT_EQ("hello", challenge().substr(0, 5));
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, AppendsFourBytes) {
+ set_challenge("hello");
+ set_key("1 1");
+ GetKeyNumber();
+ EXPECT_EQ(9u, challenge().length());
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, IsBigEndian) {
+ set_key(base::StringPrintf("%u ", 0x61626364));
+ GetKeyNumber();
+ EXPECT_EQ("abcd", challenge());
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, IgnoresLetters) {
+ set_key("1b 1");
+ GetKeyNumber();
+ char expected_response[] = { 0, 0, 0, 11 };
+ EXPECT_EQ(std::string(expected_response, 4), challenge());
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, DividesBySpaces) {
+ set_key("1 2");
+ GetKeyNumber();
+ EXPECT_EQ(12, challenge()[3]);
+ reset_challenge();
+ set_key("1 2");
+ GetKeyNumber();
+ EXPECT_EQ(6, challenge()[3]);
+ reset_challenge();
+ set_key(" 1 2");
+ GetKeyNumber();
+ EXPECT_EQ(4, challenge()[3]);
+ reset_challenge();
+ set_key(" 1 2 ");
+ GetKeyNumber();
+ EXPECT_EQ(3, challenge()[3]);
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, MatchesExampleFromDraft) {
+ set_key("3e6b263 4 17 80");
+ GetKeyNumber();
+ char expected_response[] = { 0x36, 0x09, 0x65, 0x65 };
+ EXPECT_EQ(std::string(expected_response, 4), challenge());
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberTest, Maximum32bitInteger) {
+ set_key("4294967295 ");
+ GetKeyNumber();
+ char expected_response[] = { '\xFF', '\xFF', '\xFF', '\xFF' };
+ EXPECT_EQ(std::string(expected_response, 4), challenge());
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST_F(WebSocketHandshakeGetKeyNumberDeathTest, ThirtyThreeBitIntegerNoGood) {
+ set_key(" 4294967296");
+ EXPECT_DEBUG_DEATH(GetKeyNumber(), "overflow");
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberDeathTest, NoSpacesNoGood) {
+ set_key("11");
+ EXPECT_DEBUG_DEATH(GetKeyNumber(), "space");
+}
+
+TEST_F(WebSocketHandshakeGetKeyNumberDeathTest, MustBeIntegralMultiple) {
+ set_key("1 1");
+ EXPECT_DEBUG_DEATH(GetKeyNumber(), "spaces");
+}
+#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_job.cc b/chromium/net/websockets/websocket_job.cc
new file mode 100644
index 00000000000..50d121837ac
--- /dev/null
+++ b/chromium/net/websockets/websocket_job.cc
@@ -0,0 +1,704 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_job.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/http_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_handshake_handler.h"
+#include "net/websockets/websocket_net_log_params.h"
+#include "net/websockets/websocket_throttle.h"
+#include "url/gurl.h"
+
+static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes.
+
+namespace {
+
+// lower-case header names.
+const char* const kCookieHeaders[] = {
+ "cookie", "cookie2"
+};
+const char* const kSetCookieHeaders[] = {
+ "set-cookie", "set-cookie2"
+};
+
+net::SocketStreamJob* WebSocketJobFactory(
+ const GURL& url, net::SocketStream::Delegate* delegate) {
+ net::WebSocketJob* job = new net::WebSocketJob(delegate);
+ job->InitSocketStream(new net::SocketStream(url, job));
+ return job;
+}
+
+class WebSocketJobInitSingleton {
+ private:
+ friend struct base::DefaultLazyInstanceTraits<WebSocketJobInitSingleton>;
+ WebSocketJobInitSingleton() {
+ net::SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory);
+ net::SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory);
+ }
+};
+
+static base::LazyInstance<WebSocketJobInitSingleton> g_websocket_job_init =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // anonymous namespace
+
+namespace net {
+
+bool WebSocketJob::websocket_over_spdy_enabled_ = false;
+
+// static
+void WebSocketJob::EnsureInit() {
+ g_websocket_job_init.Get();
+}
+
+// static
+void WebSocketJob::set_websocket_over_spdy_enabled(bool enabled) {
+ websocket_over_spdy_enabled_ = enabled;
+}
+
+WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate)
+ : delegate_(delegate),
+ state_(INITIALIZED),
+ waiting_(false),
+ handshake_request_(new WebSocketHandshakeRequestHandler),
+ handshake_response_(new WebSocketHandshakeResponseHandler),
+ started_to_send_handshake_request_(false),
+ handshake_request_sent_(0),
+ response_cookies_save_index_(0),
+ spdy_protocol_version_(0),
+ save_next_cookie_running_(false),
+ callback_pending_(false),
+ weak_ptr_factory_(this),
+ weak_ptr_factory_for_send_pending_(this) {
+}
+
+WebSocketJob::~WebSocketJob() {
+ DCHECK_EQ(CLOSED, state_);
+ DCHECK(!delegate_);
+ DCHECK(!socket_.get());
+}
+
+void WebSocketJob::Connect() {
+ DCHECK(socket_.get());
+ DCHECK_EQ(state_, INITIALIZED);
+ state_ = CONNECTING;
+ socket_->Connect();
+}
+
+bool WebSocketJob::SendData(const char* data, int len) {
+ switch (state_) {
+ case INITIALIZED:
+ return false;
+
+ case CONNECTING:
+ return SendHandshakeRequest(data, len);
+
+ case OPEN:
+ {
+ scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(len);
+ memcpy(buffer->data(), data, len);
+ if (current_send_buffer_.get() || !send_buffer_queue_.empty()) {
+ send_buffer_queue_.push_back(buffer);
+ return true;
+ }
+ current_send_buffer_ = new DrainableIOBuffer(buffer.get(), len);
+ return SendDataInternal(current_send_buffer_->data(),
+ current_send_buffer_->BytesRemaining());
+ }
+
+ case CLOSING:
+ case CLOSED:
+ return false;
+ }
+ return false;
+}
+
+void WebSocketJob::Close() {
+ if (state_ == CLOSED)
+ return;
+
+ state_ = CLOSING;
+ if (current_send_buffer_.get()) {
+ // Will close in SendPending.
+ return;
+ }
+ state_ = CLOSED;
+ CloseInternal();
+}
+
+void WebSocketJob::RestartWithAuth(const AuthCredentials& credentials) {
+ state_ = CONNECTING;
+ socket_->RestartWithAuth(credentials);
+}
+
+void WebSocketJob::DetachDelegate() {
+ state_ = CLOSED;
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(this);
+
+ scoped_refptr<WebSocketJob> protect(this);
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ weak_ptr_factory_for_send_pending_.InvalidateWeakPtrs();
+
+ delegate_ = NULL;
+ if (socket_.get())
+ socket_->DetachDelegate();
+ socket_ = NULL;
+ if (!callback_.is_null()) {
+ waiting_ = false;
+ callback_.Reset();
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+}
+
+int WebSocketJob::OnStartOpenConnection(
+ SocketStream* socket, const CompletionCallback& callback) {
+ DCHECK(callback_.is_null());
+ state_ = CONNECTING;
+
+ addresses_ = socket->address_list();
+ if (!WebSocketThrottle::GetInstance()->PutInQueue(this)) {
+ return ERR_WS_THROTTLE_QUEUE_TOO_LARGE;
+ }
+
+ if (delegate_) {
+ int result = delegate_->OnStartOpenConnection(socket, callback);
+ DCHECK_EQ(OK, result);
+ }
+ if (waiting_) {
+ // PutInQueue() may set |waiting_| true for throttling. In this case,
+ // Wakeup() will be called later.
+ callback_ = callback;
+ AddRef(); // Balanced when callback_ is cleared.
+ return ERR_IO_PENDING;
+ }
+ return TrySpdyStream();
+}
+
+void WebSocketJob::OnConnected(
+ SocketStream* socket, int max_pending_send_allowed) {
+ if (state_ == CLOSED)
+ return;
+ DCHECK_EQ(CONNECTING, state_);
+ if (delegate_)
+ delegate_->OnConnected(socket, max_pending_send_allowed);
+}
+
+void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) {
+ DCHECK_NE(INITIALIZED, state_);
+ DCHECK_GT(amount_sent, 0);
+ if (state_ == CLOSED)
+ return;
+ if (state_ == CONNECTING) {
+ OnSentHandshakeRequest(socket, amount_sent);
+ return;
+ }
+ if (delegate_) {
+ DCHECK(state_ == OPEN || state_ == CLOSING);
+ if (!current_send_buffer_.get()) {
+ VLOG(1)
+ << "OnSentData current_send_buffer=NULL amount_sent=" << amount_sent;
+ return;
+ }
+ current_send_buffer_->DidConsume(amount_sent);
+ if (current_send_buffer_->BytesRemaining() > 0)
+ return;
+
+ // We need to report amount_sent of original buffer size, instead of
+ // amount sent to |socket|.
+ amount_sent = current_send_buffer_->size();
+ DCHECK_GT(amount_sent, 0);
+ current_send_buffer_ = NULL;
+ if (!weak_ptr_factory_for_send_pending_.HasWeakPtrs()) {
+ base::MessageLoopForIO::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebSocketJob::SendPending,
+ weak_ptr_factory_for_send_pending_.GetWeakPtr()));
+ }
+ delegate_->OnSentData(socket, amount_sent);
+ }
+}
+
+void WebSocketJob::OnReceivedData(
+ SocketStream* socket, const char* data, int len) {
+ DCHECK_NE(INITIALIZED, state_);
+ if (state_ == CLOSED)
+ return;
+ if (state_ == CONNECTING) {
+ OnReceivedHandshakeResponse(socket, data, len);
+ return;
+ }
+ DCHECK(state_ == OPEN || state_ == CLOSING);
+ if (delegate_ && len > 0)
+ delegate_->OnReceivedData(socket, data, len);
+}
+
+void WebSocketJob::OnClose(SocketStream* socket) {
+ state_ = CLOSED;
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(this);
+
+ scoped_refptr<WebSocketJob> protect(this);
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ SocketStream::Delegate* delegate = delegate_;
+ delegate_ = NULL;
+ socket_ = NULL;
+ if (!callback_.is_null()) {
+ waiting_ = false;
+ callback_.Reset();
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+ if (delegate)
+ delegate->OnClose(socket);
+}
+
+void WebSocketJob::OnAuthRequired(
+ SocketStream* socket, AuthChallengeInfo* auth_info) {
+ if (delegate_)
+ delegate_->OnAuthRequired(socket, auth_info);
+}
+
+void WebSocketJob::OnSSLCertificateError(
+ SocketStream* socket, const SSLInfo& ssl_info, bool fatal) {
+ if (delegate_)
+ delegate_->OnSSLCertificateError(socket, ssl_info, fatal);
+}
+
+void WebSocketJob::OnError(const SocketStream* socket, int error) {
+ if (delegate_ && error != ERR_PROTOCOL_SWITCHED)
+ delegate_->OnError(socket, error);
+}
+
+void WebSocketJob::OnCreatedSpdyStream(int result) {
+ DCHECK(spdy_websocket_stream_.get());
+ DCHECK(socket_.get());
+ DCHECK_NE(ERR_IO_PENDING, result);
+
+ if (state_ == CLOSED) {
+ result = ERR_ABORTED;
+ } else if (result == OK) {
+ state_ = CONNECTING;
+ result = ERR_PROTOCOL_SWITCHED;
+ } else {
+ spdy_websocket_stream_.reset();
+ }
+
+ CompleteIO(result);
+}
+
+void WebSocketJob::OnSentSpdyHeaders() {
+ DCHECK_NE(INITIALIZED, state_);
+ if (state_ != CONNECTING)
+ return;
+ if (delegate_)
+ delegate_->OnSentData(socket_.get(), handshake_request_->original_length());
+ handshake_request_.reset();
+}
+
+void WebSocketJob::OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ DCHECK_NE(INITIALIZED, state_);
+ if (state_ != CONNECTING)
+ return;
+ // TODO(toyoshim): Fallback to non-spdy connection?
+ handshake_response_->ParseResponseHeaderBlock(response_headers,
+ challenge_,
+ spdy_protocol_version_);
+
+ SaveCookiesAndNotifyHeadersComplete();
+}
+
+void WebSocketJob::OnSentSpdyData(size_t bytes_sent) {
+ DCHECK_NE(INITIALIZED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ if (state_ == CLOSED)
+ return;
+ if (!spdy_websocket_stream_.get())
+ return;
+ OnSentData(socket_.get(), static_cast<int>(bytes_sent));
+}
+
+void WebSocketJob::OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) {
+ DCHECK_NE(INITIALIZED, state_);
+ DCHECK_NE(CONNECTING, state_);
+ if (state_ == CLOSED)
+ return;
+ if (!spdy_websocket_stream_.get())
+ return;
+ if (buffer) {
+ OnReceivedData(
+ socket_.get(), buffer->GetRemainingData(), buffer->GetRemainingSize());
+ } else {
+ OnReceivedData(socket_.get(), NULL, 0);
+ }
+}
+
+void WebSocketJob::OnCloseSpdyStream() {
+ spdy_websocket_stream_.reset();
+ OnClose(socket_.get());
+}
+
+bool WebSocketJob::SendHandshakeRequest(const char* data, int len) {
+ DCHECK_EQ(state_, CONNECTING);
+ if (started_to_send_handshake_request_)
+ return false;
+ if (!handshake_request_->ParseRequest(data, len))
+ return false;
+
+ // handshake message is completed.
+ handshake_response_->set_protocol_version(
+ handshake_request_->protocol_version());
+ AddCookieHeaderAndSend();
+ return true;
+}
+
+void WebSocketJob::AddCookieHeaderAndSend() {
+ bool allow = true;
+ if (delegate_ && !delegate_->CanGetCookies(socket_.get(), GetURLForCookies()))
+ allow = false;
+
+ if (socket_.get() && delegate_ && state_ == CONNECTING) {
+ handshake_request_->RemoveHeaders(kCookieHeaders,
+ arraysize(kCookieHeaders));
+ if (allow && socket_->context()->cookie_store()) {
+ // Add cookies, including HttpOnly cookies.
+ CookieOptions cookie_options;
+ cookie_options.set_include_httponly();
+ socket_->context()->cookie_store()->GetCookiesWithOptionsAsync(
+ GetURLForCookies(), cookie_options,
+ base::Bind(&WebSocketJob::LoadCookieCallback,
+ weak_ptr_factory_.GetWeakPtr()));
+ } else {
+ DoSendData();
+ }
+ }
+}
+
+void WebSocketJob::LoadCookieCallback(const std::string& cookie) {
+ if (!cookie.empty())
+ // TODO(tyoshino): Sending cookie means that connection doesn't need
+ // kPrivacyModeEnabled as cookies may be server-bound and channel id
+ // wouldn't negatively affect privacy anyway. Need to restart connection
+ // or refactor to determine cookie status prior to connecting.
+ handshake_request_->AppendHeaderIfMissing("Cookie", cookie);
+ DoSendData();
+}
+
+void WebSocketJob::DoSendData() {
+ if (spdy_websocket_stream_.get()) {
+ scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+ handshake_request_->GetRequestHeaderBlock(
+ socket_->url(), headers.get(), &challenge_, spdy_protocol_version_);
+ spdy_websocket_stream_->SendRequest(headers.Pass());
+ } else {
+ const std::string& handshake_request =
+ handshake_request_->GetRawRequest();
+ handshake_request_sent_ = 0;
+ socket_->net_log()->AddEvent(
+ NetLog::TYPE_WEB_SOCKET_SEND_REQUEST_HEADERS,
+ base::Bind(&NetLogWebSocketHandshakeCallback, &handshake_request));
+ socket_->SendData(handshake_request.data(),
+ handshake_request.size());
+ }
+ // Just buffered in |handshake_request_|.
+ started_to_send_handshake_request_ = true;
+}
+
+void WebSocketJob::OnSentHandshakeRequest(
+ SocketStream* socket, int amount_sent) {
+ DCHECK_EQ(state_, CONNECTING);
+ handshake_request_sent_ += amount_sent;
+ DCHECK_LE(handshake_request_sent_, handshake_request_->raw_length());
+ if (handshake_request_sent_ >= handshake_request_->raw_length()) {
+ // handshake request has been sent.
+ // notify original size of handshake request to delegate.
+ if (delegate_)
+ delegate_->OnSentData(
+ socket,
+ handshake_request_->original_length());
+ handshake_request_.reset();
+ }
+}
+
+void WebSocketJob::OnReceivedHandshakeResponse(
+ SocketStream* socket, const char* data, int len) {
+ DCHECK_EQ(state_, CONNECTING);
+ if (handshake_response_->HasResponse()) {
+ // If we already has handshake response, received data should be frame
+ // data, not handshake message.
+ received_data_after_handshake_.insert(
+ received_data_after_handshake_.end(), data, data + len);
+ return;
+ }
+
+ size_t response_length = handshake_response_->ParseRawResponse(data, len);
+ if (!handshake_response_->HasResponse()) {
+ // not yet. we need more data.
+ return;
+ }
+ // handshake message is completed.
+ std::string raw_response = handshake_response_->GetRawResponse();
+ socket_->net_log()->AddEvent(
+ NetLog::TYPE_WEB_SOCKET_READ_RESPONSE_HEADERS,
+ base::Bind(&NetLogWebSocketHandshakeCallback, &raw_response));
+ if (len - response_length > 0) {
+ // If we received extra data, it should be frame data.
+ DCHECK(received_data_after_handshake_.empty());
+ received_data_after_handshake_.assign(data + response_length, data + len);
+ }
+ SaveCookiesAndNotifyHeadersComplete();
+}
+
+void WebSocketJob::SaveCookiesAndNotifyHeadersComplete() {
+ // handshake message is completed.
+ DCHECK(handshake_response_->HasResponse());
+
+ // Extract cookies from the handshake response into a temporary vector.
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ handshake_response_->GetHeaders(
+ kSetCookieHeaders, arraysize(kSetCookieHeaders), &response_cookies_);
+
+ // Now, loop over the response cookies, and attempt to persist each.
+ SaveNextCookie();
+}
+
+void WebSocketJob::NotifyHeadersComplete() {
+ // Remove cookie headers, with malformed headers preserved.
+ // Actual handshake should be done in Blink.
+ handshake_response_->RemoveHeaders(
+ kSetCookieHeaders, arraysize(kSetCookieHeaders));
+ std::string handshake_response = handshake_response_->GetResponse();
+ handshake_response_.reset();
+ std::vector<char> received_data(handshake_response.begin(),
+ handshake_response.end());
+ received_data.insert(received_data.end(),
+ received_data_after_handshake_.begin(),
+ received_data_after_handshake_.end());
+ received_data_after_handshake_.clear();
+
+ state_ = OPEN;
+
+ DCHECK(!received_data.empty());
+ if (delegate_)
+ delegate_->OnReceivedData(
+ socket_.get(), &received_data.front(), received_data.size());
+
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(this);
+}
+
+void WebSocketJob::SaveNextCookie() {
+ if (!socket_.get() || !delegate_ || state_ != CONNECTING)
+ return;
+
+ callback_pending_ = false;
+ save_next_cookie_running_ = true;
+
+ if (socket_->context()->cookie_store()) {
+ GURL url_for_cookies = GetURLForCookies();
+
+ CookieOptions options;
+ options.set_include_httponly();
+
+ // Loop as long as SetCookieWithOptionsAsync completes synchronously. Since
+ // CookieMonster's asynchronous operation APIs queue the callback to run it
+ // on the thread where the API was called, there won't be race. I.e. unless
+ // the callback is run synchronously, it won't be run in parallel with this
+ // method.
+ while (!callback_pending_ &&
+ response_cookies_save_index_ < response_cookies_.size()) {
+ std::string cookie = response_cookies_[response_cookies_save_index_];
+ response_cookies_save_index_++;
+
+ if (!delegate_->CanSetCookie(
+ socket_.get(), url_for_cookies, cookie, &options))
+ continue;
+
+ callback_pending_ = true;
+ socket_->context()->cookie_store()->SetCookieWithOptionsAsync(
+ url_for_cookies, cookie, options,
+ base::Bind(&WebSocketJob::OnCookieSaved,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+ }
+
+ save_next_cookie_running_ = false;
+
+ if (callback_pending_)
+ return;
+
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ NotifyHeadersComplete();
+}
+
+void WebSocketJob::OnCookieSaved(bool cookie_status) {
+ // Tell the caller of SetCookieWithOptionsAsync() that this completion
+ // callback is invoked.
+ // - If the caller checks callback_pending earlier than this callback, the
+ // caller exits to let this method continue iteration.
+ // - Otherwise, the caller continues iteration.
+ callback_pending_ = false;
+
+ // Resume SaveNextCookie if the caller of SetCookieWithOptionsAsync() exited
+ // the loop. Otherwise, return.
+ if (save_next_cookie_running_)
+ return;
+
+ SaveNextCookie();
+}
+
+GURL WebSocketJob::GetURLForCookies() const {
+ GURL url = socket_->url();
+ std::string scheme = socket_->is_secure() ? "https" : "http";
+ url_canon::Replacements<char> replacements;
+ replacements.SetScheme(scheme.c_str(),
+ url_parse::Component(0, scheme.length()));
+ return url.ReplaceComponents(replacements);
+}
+
+const AddressList& WebSocketJob::address_list() const {
+ return addresses_;
+}
+
+int WebSocketJob::TrySpdyStream() {
+ if (!socket_.get())
+ return ERR_FAILED;
+
+ if (!websocket_over_spdy_enabled_)
+ return OK;
+
+ // Check if we have a SPDY session available.
+ HttpTransactionFactory* factory =
+ socket_->context()->http_transaction_factory();
+ if (!factory)
+ return OK;
+ scoped_refptr<HttpNetworkSession> session = factory->GetSession();
+ if (!session.get())
+ return OK;
+ SpdySessionPool* spdy_pool = session->spdy_session_pool();
+ PrivacyMode privacy_mode = socket_->privacy_mode();
+ const SpdySessionKey key(HostPortPair::FromURL(socket_->url()),
+ socket_->proxy_server(), privacy_mode);
+ // Forbid wss downgrade to SPDY without SSL.
+ // TODO(toyoshim): Does it realize the same policy with HTTP?
+ base::WeakPtr<SpdySession> spdy_session =
+ spdy_pool->FindAvailableSession(key, *socket_->net_log());
+ if (!spdy_session)
+ return OK;
+
+ SSLInfo ssl_info;
+ bool was_npn_negotiated;
+ NextProto protocol_negotiated = kProtoUnknown;
+ bool use_ssl = spdy_session->GetSSLInfo(
+ &ssl_info, &was_npn_negotiated, &protocol_negotiated);
+ if (socket_->is_secure() && !use_ssl)
+ return OK;
+
+ // Create SpdyWebSocketStream.
+ spdy_protocol_version_ = spdy_session->GetProtocolVersion();
+ spdy_websocket_stream_.reset(new SpdyWebSocketStream(spdy_session, this));
+
+ int result = spdy_websocket_stream_->InitializeStream(
+ socket_->url(), MEDIUM, *socket_->net_log());
+ if (result == OK) {
+ OnConnected(socket_.get(), kMaxPendingSendAllowed);
+ return ERR_PROTOCOL_SWITCHED;
+ }
+ if (result != ERR_IO_PENDING) {
+ spdy_websocket_stream_.reset();
+ return OK;
+ }
+
+ return ERR_IO_PENDING;
+}
+
+void WebSocketJob::SetWaiting() {
+ waiting_ = true;
+}
+
+bool WebSocketJob::IsWaiting() const {
+ return waiting_;
+}
+
+void WebSocketJob::Wakeup() {
+ if (!waiting_)
+ return;
+ waiting_ = false;
+ DCHECK(!callback_.is_null());
+ base::MessageLoopForIO::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&WebSocketJob::RetryPendingIO,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void WebSocketJob::RetryPendingIO() {
+ int result = TrySpdyStream();
+
+ // In the case of ERR_IO_PENDING, CompleteIO() will be called from
+ // OnCreatedSpdyStream().
+ if (result != ERR_IO_PENDING)
+ CompleteIO(result);
+}
+
+void WebSocketJob::CompleteIO(int result) {
+ // |callback_| may be null if OnClose() or DetachDelegate() was called.
+ if (!callback_.is_null()) {
+ CompletionCallback callback = callback_;
+ callback_.Reset();
+ callback.Run(result);
+ Release(); // Balanced with OnStartOpenConnection().
+ }
+}
+
+bool WebSocketJob::SendDataInternal(const char* data, int length) {
+ if (spdy_websocket_stream_.get())
+ return ERR_IO_PENDING == spdy_websocket_stream_->SendData(data, length);
+ if (socket_.get())
+ return socket_->SendData(data, length);
+ return false;
+}
+
+void WebSocketJob::CloseInternal() {
+ if (spdy_websocket_stream_.get())
+ spdy_websocket_stream_->Close();
+ if (socket_.get())
+ socket_->Close();
+}
+
+void WebSocketJob::SendPending() {
+ if (current_send_buffer_.get())
+ return;
+
+ // Current buffer has been sent. Try next if any.
+ if (send_buffer_queue_.empty()) {
+ // No more data to send.
+ if (state_ == CLOSING)
+ CloseInternal();
+ return;
+ }
+
+ scoped_refptr<IOBufferWithSize> next_buffer = send_buffer_queue_.front();
+ send_buffer_queue_.pop_front();
+ current_send_buffer_ =
+ new DrainableIOBuffer(next_buffer.get(), next_buffer->size());
+ SendDataInternal(current_send_buffer_->data(),
+ current_send_buffer_->BytesRemaining());
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_job.h b/chromium/net/websockets/websocket_job.h
new file mode 100644
index 00000000000..119c4dcfaa9
--- /dev/null
+++ b/chromium/net/websockets/websocket_job.h
@@ -0,0 +1,163 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_JOB_H_
+#define NET_WEBSOCKETS_WEBSOCKET_JOB_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_callback.h"
+#include "net/socket_stream/socket_stream_job.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_websocket_stream.h"
+
+class GURL;
+
+namespace net {
+
+class DrainableIOBuffer;
+class SSLInfo;
+class WebSocketHandshakeRequestHandler;
+class WebSocketHandshakeResponseHandler;
+
+// WebSocket protocol specific job on SocketStream.
+// It captures WebSocket handshake message and handles cookie operations.
+// Chrome security policy doesn't allow renderer process (except dev tools)
+// see HttpOnly cookies, so it injects cookie header in handshake request and
+// strips set-cookie headers in handshake response.
+// TODO(ukai): refactor websocket.cc to use this.
+class NET_EXPORT WebSocketJob
+ : public SocketStreamJob,
+ public SocketStream::Delegate,
+ public SpdyWebSocketStream::Delegate {
+ public:
+ // This is state of WebSocket, not SocketStream.
+ enum State {
+ INITIALIZED = -1,
+ CONNECTING = 0,
+ OPEN = 1,
+ CLOSING = 2,
+ CLOSED = 3,
+ };
+
+ explicit WebSocketJob(SocketStream::Delegate* delegate);
+
+ static void EnsureInit();
+
+ // Enable or Disable WebSocket over SPDY feature.
+ // This function is intended to be called before I/O thread starts.
+ static void set_websocket_over_spdy_enabled(bool enabled);
+
+ State state() const { return state_; }
+ virtual void Connect() OVERRIDE;
+ virtual bool SendData(const char* data, int len) OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual void RestartWithAuth(const AuthCredentials& credentials) OVERRIDE;
+ virtual void DetachDelegate() OVERRIDE;
+
+ // SocketStream::Delegate methods.
+ virtual int OnStartOpenConnection(
+ SocketStream* socket, const CompletionCallback& callback) OVERRIDE;
+ virtual void OnConnected(SocketStream* socket,
+ int max_pending_send_allowed) OVERRIDE;
+ virtual void OnSentData(SocketStream* socket, int amount_sent) OVERRIDE;
+ virtual void OnReceivedData(SocketStream* socket,
+ const char* data,
+ int len) OVERRIDE;
+ virtual void OnClose(SocketStream* socket) OVERRIDE;
+ virtual void OnAuthRequired(
+ SocketStream* socket, AuthChallengeInfo* auth_info) OVERRIDE;
+ virtual void OnSSLCertificateError(SocketStream* socket,
+ const SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+ virtual void OnError(const SocketStream* socket, int error) OVERRIDE;
+
+ // SpdyWebSocketStream::Delegate methods.
+ virtual void OnCreatedSpdyStream(int status) OVERRIDE;
+ virtual void OnSentSpdyHeaders() OVERRIDE;
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE;
+ virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual void OnCloseSpdyStream() OVERRIDE;
+
+ private:
+ friend class WebSocketThrottle;
+ friend class WebSocketJobTest;
+ virtual ~WebSocketJob();
+
+ bool SendHandshakeRequest(const char* data, int len);
+ void AddCookieHeaderAndSend();
+ void LoadCookieCallback(const std::string& cookie);
+
+ void OnSentHandshakeRequest(SocketStream* socket, int amount_sent);
+ // Parses received data into handshake_response_. When finished receiving the
+ // response, calls SaveCookiesAndNotifyHeadersComplete().
+ void OnReceivedHandshakeResponse(
+ SocketStream* socket, const char* data, int len);
+ // Saves received cookies to the cookie store, and then notifies the
+ // delegate_ of completion of handshake.
+ void SaveCookiesAndNotifyHeadersComplete();
+ void SaveNextCookie();
+ void OnCookieSaved(bool cookie_status);
+ // Clears variables for handling cookies, rebuilds handshake string excluding
+ // cookies, and then pass the handshake string to delegate_.
+ void NotifyHeadersComplete();
+ void DoSendData();
+
+ GURL GetURLForCookies() const;
+
+ const AddressList& address_list() const;
+ int TrySpdyStream();
+ void SetWaiting();
+ bool IsWaiting() const;
+ void Wakeup();
+ void RetryPendingIO();
+ void CompleteIO(int result);
+
+ bool SendDataInternal(const char* data, int length);
+ void CloseInternal();
+ void SendPending();
+
+ static bool websocket_over_spdy_enabled_;
+
+ SocketStream::Delegate* delegate_;
+ State state_;
+ bool waiting_;
+ AddressList addresses_;
+ CompletionCallback callback_; // for throttling.
+
+ scoped_ptr<WebSocketHandshakeRequestHandler> handshake_request_;
+ scoped_ptr<WebSocketHandshakeResponseHandler> handshake_response_;
+
+ bool started_to_send_handshake_request_;
+ size_t handshake_request_sent_;
+
+ std::vector<std::string> response_cookies_;
+ size_t response_cookies_save_index_;
+
+ std::deque<scoped_refptr<IOBufferWithSize> > send_buffer_queue_;
+ scoped_refptr<DrainableIOBuffer> current_send_buffer_;
+ std::vector<char> received_data_after_handshake_;
+
+ int spdy_protocol_version_;
+ scoped_ptr<SpdyWebSocketStream> spdy_websocket_stream_;
+ std::string challenge_;
+
+ bool save_next_cookie_running_;
+ bool callback_pending_;
+
+ base::WeakPtrFactory<WebSocketJob> weak_ptr_factory_;
+ base::WeakPtrFactory<WebSocketJob> weak_ptr_factory_for_send_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketJob);
+};
+
+} // namespace
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_JOB_H_
diff --git a/chromium/net/websockets/websocket_job_unittest.cc b/chromium/net/websockets/websocket_job_unittest.cc
new file mode 100644
index 00000000000..434796dbcbc
--- /dev/null
+++ b/chromium/net/websockets/websocket_job_unittest.cc
@@ -0,0 +1,1112 @@
+// Copyright 2013 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/websockets/websocket_job.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/cookies/cookie_store.h"
+#include "net/cookies/cookie_store_test_helpers.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/transport_security_state.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/socket_test_util.h"
+#include "net/socket_stream/socket_stream.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_websocket_test_util.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/url_request/url_request_context.h"
+#include "net/websockets/websocket_throttle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+
+class MockSocketStream : public SocketStream {
+ public:
+ MockSocketStream(const GURL& url, SocketStream::Delegate* delegate)
+ : SocketStream(url, delegate) {}
+
+ virtual void Connect() OVERRIDE {}
+ virtual bool SendData(const char* data, int len) OVERRIDE {
+ sent_data_ += std::string(data, len);
+ return true;
+ }
+
+ virtual void Close() OVERRIDE {}
+ virtual void RestartWithAuth(
+ const AuthCredentials& credentials) OVERRIDE {
+ }
+
+ virtual void DetachDelegate() OVERRIDE {
+ delegate_ = NULL;
+ }
+
+ const std::string& sent_data() const {
+ return sent_data_;
+ }
+
+ protected:
+ virtual ~MockSocketStream() {}
+
+ private:
+ std::string sent_data_;
+};
+
+class MockSocketStreamDelegate : public SocketStream::Delegate {
+ public:
+ MockSocketStreamDelegate()
+ : amount_sent_(0), allow_all_cookies_(true) {}
+ void set_allow_all_cookies(bool allow_all_cookies) {
+ allow_all_cookies_ = allow_all_cookies;
+ }
+ virtual ~MockSocketStreamDelegate() {}
+
+ void SetOnStartOpenConnection(const base::Closure& callback) {
+ on_start_open_connection_ = callback;
+ }
+ void SetOnConnected(const base::Closure& callback) {
+ on_connected_ = callback;
+ }
+ void SetOnSentData(const base::Closure& callback) {
+ on_sent_data_ = callback;
+ }
+ void SetOnReceivedData(const base::Closure& callback) {
+ on_received_data_ = callback;
+ }
+ void SetOnClose(const base::Closure& callback) {
+ on_close_ = callback;
+ }
+
+ virtual int OnStartOpenConnection(
+ SocketStream* socket,
+ const CompletionCallback& callback) OVERRIDE {
+ if (!on_start_open_connection_.is_null())
+ on_start_open_connection_.Run();
+ return OK;
+ }
+ virtual void OnConnected(SocketStream* socket,
+ int max_pending_send_allowed) OVERRIDE {
+ if (!on_connected_.is_null())
+ on_connected_.Run();
+ }
+ virtual void OnSentData(SocketStream* socket,
+ int amount_sent) OVERRIDE {
+ amount_sent_ += amount_sent;
+ if (!on_sent_data_.is_null())
+ on_sent_data_.Run();
+ }
+ virtual void OnReceivedData(SocketStream* socket,
+ const char* data, int len) OVERRIDE {
+ received_data_ += std::string(data, len);
+ if (!on_received_data_.is_null())
+ on_received_data_.Run();
+ }
+ virtual void OnClose(SocketStream* socket) OVERRIDE {
+ if (!on_close_.is_null())
+ on_close_.Run();
+ }
+ virtual bool CanGetCookies(SocketStream* socket,
+ const GURL& url) OVERRIDE {
+ return allow_all_cookies_;
+ }
+ virtual bool CanSetCookie(SocketStream* request,
+ const GURL& url,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE {
+ return allow_all_cookies_;
+ }
+
+ size_t amount_sent() const { return amount_sent_; }
+ const std::string& received_data() const { return received_data_; }
+
+ private:
+ int amount_sent_;
+ bool allow_all_cookies_;
+ std::string received_data_;
+ base::Closure on_start_open_connection_;
+ base::Closure on_connected_;
+ base::Closure on_sent_data_;
+ base::Closure on_received_data_;
+ base::Closure on_close_;
+};
+
+class MockCookieStore : public CookieStore {
+ public:
+ struct Entry {
+ GURL url;
+ std::string cookie_line;
+ CookieOptions options;
+ };
+
+ MockCookieStore() {}
+
+ bool SetCookieWithOptions(const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options) {
+ Entry entry;
+ entry.url = url;
+ entry.cookie_line = cookie_line;
+ entry.options = options;
+ entries_.push_back(entry);
+ return true;
+ }
+
+ std::string GetCookiesWithOptions(const GURL& url,
+ const CookieOptions& options) {
+ std::string result;
+ for (size_t i = 0; i < entries_.size(); i++) {
+ Entry& entry = entries_[i];
+ if (url == entry.url) {
+ if (!result.empty()) {
+ result += "; ";
+ }
+ result += entry.cookie_line;
+ }
+ }
+ return result;
+ }
+
+ // CookieStore:
+ virtual void SetCookieWithOptionsAsync(
+ const GURL& url,
+ const std::string& cookie_line,
+ const CookieOptions& options,
+ const SetCookiesCallback& callback) OVERRIDE {
+ bool result = SetCookieWithOptions(url, cookie_line, options);
+ if (!callback.is_null())
+ callback.Run(result);
+ }
+
+ virtual void GetCookiesWithOptionsAsync(
+ const GURL& url,
+ const CookieOptions& options,
+ const GetCookiesCallback& callback) OVERRIDE {
+ if (!callback.is_null())
+ callback.Run(GetCookiesWithOptions(url, options));
+ }
+
+ virtual void DeleteCookieAsync(const GURL& url,
+ const std::string& cookie_name,
+ const base::Closure& callback) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual void DeleteAllCreatedBetweenAsync(
+ const base::Time& delete_begin,
+ const base::Time& delete_end,
+ const DeleteCallback& callback) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual void DeleteSessionCookiesAsync(const DeleteCallback&) OVERRIDE {
+ ADD_FAILURE();
+ }
+
+ virtual CookieMonster* GetCookieMonster() OVERRIDE { return NULL; }
+
+ const std::vector<Entry>& entries() const { return entries_; }
+
+ private:
+ friend class base::RefCountedThreadSafe<MockCookieStore>;
+ virtual ~MockCookieStore() {}
+
+ std::vector<Entry> entries_;
+};
+
+class MockSSLConfigService : public SSLConfigService {
+ public:
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {}
+
+ protected:
+ virtual ~MockSSLConfigService() {}
+};
+
+class MockURLRequestContext : public URLRequestContext {
+ public:
+ explicit MockURLRequestContext(CookieStore* cookie_store)
+ : transport_security_state_() {
+ set_cookie_store(cookie_store);
+ set_transport_security_state(&transport_security_state_);
+ base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000);
+ bool include_subdomains = false;
+ transport_security_state_.AddHSTS("upgrademe.com", expiry,
+ include_subdomains);
+ }
+
+ virtual ~MockURLRequestContext() {}
+
+ private:
+ TransportSecurityState transport_security_state_;
+};
+
+class MockHttpTransactionFactory : public HttpTransactionFactory {
+ public:
+ MockHttpTransactionFactory(NextProto next_proto, OrderedSocketData* data) {
+ data_ = data;
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data_->set_connect_data(connect_data);
+ session_deps_.reset(new SpdySessionDependencies(next_proto));
+ session_deps_->socket_factory->AddSocketDataProvider(data_);
+ http_session_ =
+ SpdySessionDependencies::SpdyCreateSession(session_deps_.get());
+ host_port_pair_.set_host("example.com");
+ host_port_pair_.set_port(80);
+ spdy_session_key_ = SpdySessionKey(host_port_pair_,
+ ProxyServer::Direct(),
+ kPrivacyModeDisabled);
+ session_ = CreateInsecureSpdySession(
+ http_session_, spdy_session_key_, BoundNetLog());
+ }
+
+ virtual int CreateTransaction(
+ RequestPriority priority,
+ scoped_ptr<HttpTransaction>* trans,
+ HttpTransactionDelegate* delegate) OVERRIDE {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
+
+ virtual HttpCache* GetCache() OVERRIDE {
+ NOTREACHED();
+ return NULL;
+ }
+
+ virtual HttpNetworkSession* GetSession() OVERRIDE {
+ return http_session_.get();
+ }
+
+ private:
+ OrderedSocketData* data_;
+ scoped_ptr<SpdySessionDependencies> session_deps_;
+ scoped_refptr<HttpNetworkSession> http_session_;
+ base::WeakPtr<SpdySession> session_;
+ HostPortPair host_port_pair_;
+ SpdySessionKey spdy_session_key_;
+};
+
+} // namespace
+
+class WebSocketJobTest : public PlatformTest,
+ public ::testing::WithParamInterface<NextProto> {
+ public:
+ WebSocketJobTest() : spdy_util_(GetParam()) {}
+
+ virtual void SetUp() OVERRIDE {
+ stream_type_ = STREAM_INVALID;
+ cookie_store_ = new MockCookieStore;
+ context_.reset(new MockURLRequestContext(cookie_store_.get()));
+ }
+ virtual void TearDown() OVERRIDE {
+ cookie_store_ = NULL;
+ context_.reset();
+ websocket_ = NULL;
+ socket_ = NULL;
+ }
+ void DoSendRequest() {
+ EXPECT_TRUE(websocket_->SendData(kHandshakeRequestWithoutCookie,
+ kHandshakeRequestWithoutCookieLength));
+ }
+ void DoSendData() {
+ if (received_data().size() == kHandshakeResponseWithoutCookieLength)
+ websocket_->SendData(kDataHello, kDataHelloLength);
+ }
+ void DoSync() {
+ sync_test_callback_.callback().Run(OK);
+ }
+ int WaitForResult() {
+ return sync_test_callback_.WaitForResult();
+ }
+ protected:
+ enum StreamType {
+ STREAM_INVALID,
+ STREAM_MOCK_SOCKET,
+ STREAM_SOCKET,
+ STREAM_SPDY_WEBSOCKET,
+ };
+ enum ThrottlingOption {
+ THROTTLING_OFF,
+ THROTTLING_ON,
+ };
+ enum SpdyOption {
+ SPDY_OFF,
+ SPDY_ON,
+ };
+ void InitWebSocketJob(const GURL& url,
+ MockSocketStreamDelegate* delegate,
+ StreamType stream_type) {
+ DCHECK_NE(STREAM_INVALID, stream_type);
+ stream_type_ = stream_type;
+ websocket_ = new WebSocketJob(delegate);
+
+ if (stream_type == STREAM_MOCK_SOCKET)
+ socket_ = new MockSocketStream(url, websocket_.get());
+
+ if (stream_type == STREAM_SOCKET || stream_type == STREAM_SPDY_WEBSOCKET) {
+ if (stream_type == STREAM_SPDY_WEBSOCKET) {
+ http_factory_.reset(
+ new MockHttpTransactionFactory(GetParam(), data_.get()));
+ context_->set_http_transaction_factory(http_factory_.get());
+ }
+
+ ssl_config_service_ = new MockSSLConfigService();
+ context_->set_ssl_config_service(ssl_config_service_.get());
+ proxy_service_.reset(ProxyService::CreateDirect());
+ context_->set_proxy_service(proxy_service_.get());
+ host_resolver_.reset(new MockHostResolver);
+ context_->set_host_resolver(host_resolver_.get());
+
+ socket_ = new SocketStream(url, websocket_.get());
+ socket_factory_.reset(new MockClientSocketFactory);
+ DCHECK(data_.get());
+ socket_factory_->AddSocketDataProvider(data_.get());
+ socket_->SetClientSocketFactory(socket_factory_.get());
+ }
+
+ websocket_->InitSocketStream(socket_.get());
+ websocket_->set_context(context_.get());
+ // MockHostResolver resolves all hosts to 127.0.0.1; however, when we create
+ // a WebSocketJob purely to block another one in a throttling test, we don't
+ // perform a real connect. In that case, the following address is used
+ // instead.
+ IPAddressNumber ip;
+ ParseIPLiteralToNumber("127.0.0.1", &ip);
+ websocket_->addresses_ = AddressList::CreateFromIPAddress(ip, 80);
+ }
+ void SkipToConnecting() {
+ websocket_->state_ = WebSocketJob::CONNECTING;
+ ASSERT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(websocket_.get()));
+ }
+ WebSocketJob::State GetWebSocketJobState() {
+ return websocket_->state_;
+ }
+ void CloseWebSocketJob() {
+ if (websocket_->socket_.get()) {
+ websocket_->socket_->DetachDelegate();
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(websocket_.get());
+ }
+ websocket_->state_ = WebSocketJob::CLOSED;
+ websocket_->delegate_ = NULL;
+ websocket_->socket_ = NULL;
+ }
+ SocketStream* GetSocket(SocketStreamJob* job) {
+ return job->socket_.get();
+ }
+ const std::string& sent_data() const {
+ DCHECK_EQ(STREAM_MOCK_SOCKET, stream_type_);
+ MockSocketStream* socket =
+ static_cast<MockSocketStream*>(socket_.get());
+ DCHECK(socket);
+ return socket->sent_data();
+ }
+ const std::string& received_data() const {
+ DCHECK_NE(STREAM_INVALID, stream_type_);
+ MockSocketStreamDelegate* delegate =
+ static_cast<MockSocketStreamDelegate*>(websocket_->delegate_);
+ DCHECK(delegate);
+ return delegate->received_data();
+ }
+
+ void TestSimpleHandshake();
+ void TestSlowHandshake();
+ void TestHandshakeWithCookie();
+ void TestHandshakeWithCookieButNotAllowed();
+ void TestHSTSUpgrade();
+ void TestInvalidSendData();
+ void TestConnectByWebSocket(ThrottlingOption throttling);
+ void TestConnectBySpdy(SpdyOption spdy, ThrottlingOption throttling);
+ void TestThrottlingLimit();
+
+ SpdyWebSocketTestUtil spdy_util_;
+ StreamType stream_type_;
+ scoped_refptr<MockCookieStore> cookie_store_;
+ scoped_ptr<MockURLRequestContext> context_;
+ scoped_refptr<WebSocketJob> websocket_;
+ scoped_refptr<SocketStream> socket_;
+ scoped_ptr<MockClientSocketFactory> socket_factory_;
+ scoped_ptr<OrderedSocketData> data_;
+ TestCompletionCallback sync_test_callback_;
+ scoped_refptr<MockSSLConfigService> ssl_config_service_;
+ scoped_ptr<ProxyService> proxy_service_;
+ scoped_ptr<MockHostResolver> host_resolver_;
+ scoped_ptr<MockHttpTransactionFactory> http_factory_;
+
+ static const char kHandshakeRequestWithoutCookie[];
+ static const char kHandshakeRequestWithCookie[];
+ static const char kHandshakeRequestWithFilteredCookie[];
+ static const char kHandshakeResponseWithoutCookie[];
+ static const char kHandshakeResponseWithCookie[];
+ static const char kDataHello[];
+ static const char kDataWorld[];
+ static const char* const kHandshakeRequestForSpdy[];
+ static const char* const kHandshakeResponseForSpdy[];
+ static const size_t kHandshakeRequestWithoutCookieLength;
+ static const size_t kHandshakeRequestWithCookieLength;
+ static const size_t kHandshakeRequestWithFilteredCookieLength;
+ static const size_t kHandshakeResponseWithoutCookieLength;
+ static const size_t kHandshakeResponseWithCookieLength;
+ static const size_t kDataHelloLength;
+ static const size_t kDataWorldLength;
+};
+
+const char WebSocketJobTest::kHandshakeRequestWithoutCookie[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n";
+
+const char WebSocketJobTest::kHandshakeRequestWithCookie[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Cookie: WK-test=1\r\n"
+ "\r\n";
+
+const char WebSocketJobTest::kHandshakeRequestWithFilteredCookie[] =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+ "Origin: http://example.com\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Cookie: CR-test=1; CR-test-httponly=1\r\n"
+ "\r\n";
+
+const char WebSocketJobTest::kHandshakeResponseWithoutCookie[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "\r\n";
+
+const char WebSocketJobTest::kHandshakeResponseWithCookie[] =
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Set-Cookie: CR-set-test=1\r\n"
+ "\r\n";
+
+const char WebSocketJobTest::kDataHello[] = "Hello, ";
+
+const char WebSocketJobTest::kDataWorld[] = "World!\n";
+
+const size_t WebSocketJobTest::kHandshakeRequestWithoutCookieLength =
+ arraysize(kHandshakeRequestWithoutCookie) - 1;
+const size_t WebSocketJobTest::kHandshakeRequestWithCookieLength =
+ arraysize(kHandshakeRequestWithCookie) - 1;
+const size_t WebSocketJobTest::kHandshakeRequestWithFilteredCookieLength =
+ arraysize(kHandshakeRequestWithFilteredCookie) - 1;
+const size_t WebSocketJobTest::kHandshakeResponseWithoutCookieLength =
+ arraysize(kHandshakeResponseWithoutCookie) - 1;
+const size_t WebSocketJobTest::kHandshakeResponseWithCookieLength =
+ arraysize(kHandshakeResponseWithCookie) - 1;
+const size_t WebSocketJobTest::kDataHelloLength =
+ arraysize(kDataHello) - 1;
+const size_t WebSocketJobTest::kDataWorldLength =
+ arraysize(kDataWorld) - 1;
+
+void WebSocketJobTest::TestSimpleHandshake() {
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ DoSendRequest();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(),
+ kHandshakeRequestWithoutCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseWithoutCookie,
+ kHandshakeResponseWithoutCookieLength);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+ CloseWebSocketJob();
+}
+
+void WebSocketJobTest::TestSlowHandshake() {
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ DoSendRequest();
+ // We assume request is sent in one data chunk (from WebKit)
+ // We don't support streaming request.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(),
+ kHandshakeRequestWithoutCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
+
+ std::vector<std::string> lines;
+ base::SplitString(kHandshakeResponseWithoutCookie, '\n', &lines);
+ for (size_t i = 0; i < lines.size() - 2; i++) {
+ std::string line = lines[i] + "\r\n";
+ SCOPED_TRACE("Line: " + line);
+ websocket_->OnReceivedData(socket_.get(), line.c_str(), line.size());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(delegate.received_data().empty());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ }
+ websocket_->OnReceivedData(socket_.get(), "\r\n", 2);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(delegate.received_data().empty());
+ EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+ CloseWebSocketJob();
+}
+
+INSTANTIATE_TEST_CASE_P(
+ NextProto,
+ WebSocketJobTest,
+ testing::Values(kProtoSPDY2, kProtoSPDY3, kProtoSPDY31, kProtoSPDY4a2,
+ kProtoHTTP2Draft04));
+
+TEST_P(WebSocketJobTest, DelayedCookies) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ GURL url("ws://example.com/demo");
+ GURL cookieUrl("http://example.com/demo");
+ CookieOptions cookie_options;
+ scoped_refptr<DelayedCookieMonster> cookie_store = new DelayedCookieMonster();
+ context_->set_cookie_store(cookie_store.get());
+ cookie_store->SetCookieWithOptionsAsync(cookieUrl,
+ "CR-test=1",
+ cookie_options,
+ CookieMonster::SetCookiesCallback());
+ cookie_options.set_include_httponly();
+ cookie_store->SetCookieWithOptionsAsync(
+ cookieUrl, "CR-test-httponly=1", cookie_options,
+ CookieMonster::SetCookiesCallback());
+
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
+ kHandshakeRequestWithCookieLength);
+ EXPECT_TRUE(sent);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(),
+ kHandshakeRequestWithFilteredCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithCookieLength,
+ delegate.amount_sent());
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseWithCookie,
+ kHandshakeResponseWithCookieLength);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+
+ CloseWebSocketJob();
+}
+
+void WebSocketJobTest::TestHandshakeWithCookie() {
+ GURL url("ws://example.com/demo");
+ GURL cookieUrl("http://example.com/demo");
+ CookieOptions cookie_options;
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test=1", cookie_options);
+ cookie_options.set_include_httponly();
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test-httponly=1", cookie_options);
+
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
+ kHandshakeRequestWithCookieLength);
+ EXPECT_TRUE(sent);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithFilteredCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(),
+ kHandshakeRequestWithFilteredCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithCookieLength,
+ delegate.amount_sent());
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseWithCookie,
+ kHandshakeResponseWithCookieLength);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+
+ EXPECT_EQ(3U, cookie_store_->entries().size());
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[2].url);
+ EXPECT_EQ("CR-set-test=1", cookie_store_->entries()[2].cookie_line);
+
+ CloseWebSocketJob();
+}
+
+void WebSocketJobTest::TestHandshakeWithCookieButNotAllowed() {
+ GURL url("ws://example.com/demo");
+ GURL cookieUrl("http://example.com/demo");
+ CookieOptions cookie_options;
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test=1", cookie_options);
+ cookie_options.set_include_httponly();
+ cookie_store_->SetCookieWithOptions(
+ cookieUrl, "CR-test-httponly=1", cookie_options);
+
+ MockSocketStreamDelegate delegate;
+ delegate.set_allow_all_cookies(false);
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ bool sent = websocket_->SendData(kHandshakeRequestWithCookie,
+ kHandshakeRequestWithCookieLength);
+ EXPECT_TRUE(sent);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(), kHandshakeRequestWithoutCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithCookieLength, delegate.amount_sent());
+
+ websocket_->OnReceivedData(socket_.get(),
+ kHandshakeResponseWithCookie,
+ kHandshakeResponseWithCookieLength);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeResponseWithoutCookie, delegate.received_data());
+ EXPECT_EQ(WebSocketJob::OPEN, GetWebSocketJobState());
+
+ EXPECT_EQ(2U, cookie_store_->entries().size());
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[0].url);
+ EXPECT_EQ("CR-test=1", cookie_store_->entries()[0].cookie_line);
+ EXPECT_EQ(cookieUrl, cookie_store_->entries()[1].url);
+ EXPECT_EQ("CR-test-httponly=1", cookie_store_->entries()[1].cookie_line);
+
+ CloseWebSocketJob();
+}
+
+void WebSocketJobTest::TestHSTSUpgrade() {
+ GURL url("ws://upgrademe.com/");
+ MockSocketStreamDelegate delegate;
+ scoped_refptr<SocketStreamJob> job =
+ SocketStreamJob::CreateSocketStreamJob(
+ url, &delegate, context_->transport_security_state(),
+ context_->ssl_config_service());
+ EXPECT_TRUE(GetSocket(job.get())->is_secure());
+ job->DetachDelegate();
+
+ url = GURL("ws://donotupgrademe.com/");
+ job = SocketStreamJob::CreateSocketStreamJob(
+ url, &delegate, context_->transport_security_state(),
+ context_->ssl_config_service());
+ EXPECT_FALSE(GetSocket(job.get())->is_secure());
+ job->DetachDelegate();
+}
+
+void WebSocketJobTest::TestInvalidSendData() {
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ InitWebSocketJob(url, &delegate, STREAM_MOCK_SOCKET);
+ SkipToConnecting();
+
+ DoSendRequest();
+ // We assume request is sent in one data chunk (from WebKit)
+ // We don't support streaming request.
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(kHandshakeRequestWithoutCookie, sent_data());
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ websocket_->OnSentData(socket_.get(),
+ kHandshakeRequestWithoutCookieLength);
+ EXPECT_EQ(kHandshakeRequestWithoutCookieLength, delegate.amount_sent());
+
+ // We could not send any data until connection is established.
+ bool sent = websocket_->SendData(kHandshakeRequestWithoutCookie,
+ kHandshakeRequestWithoutCookieLength);
+ EXPECT_FALSE(sent);
+ EXPECT_EQ(WebSocketJob::CONNECTING, GetWebSocketJobState());
+ CloseWebSocketJob();
+}
+
+// Following tests verify cooperation between WebSocketJob and SocketStream.
+// Other former tests use MockSocketStream as SocketStream, so we could not
+// check SocketStream behavior.
+// OrderedSocketData provide socket level verifiation by checking out-going
+// packets in comparison with the MockWrite array and emulating in-coming
+// packets with MockRead array.
+
+void WebSocketJobTest::TestConnectByWebSocket(
+ ThrottlingOption throttling) {
+ // This is a test for verifying cooperation between WebSocketJob and
+ // SocketStream. If |throttling| was |THROTTLING_OFF|, it test basic
+ // situation. If |throttling| was |THROTTLING_ON|, throttling limits the
+ // latter connection.
+ MockWrite writes[] = {
+ MockWrite(ASYNC,
+ kHandshakeRequestWithoutCookie,
+ kHandshakeRequestWithoutCookieLength,
+ 1),
+ MockWrite(ASYNC,
+ kDataHello,
+ kDataHelloLength,
+ 3)
+ };
+ MockRead reads[] = {
+ MockRead(ASYNC,
+ kHandshakeResponseWithoutCookie,
+ kHandshakeResponseWithoutCookieLength,
+ 2),
+ MockRead(ASYNC,
+ kDataWorld,
+ kDataWorldLength,
+ 4),
+ MockRead(SYNCHRONOUS, 0, 5) // EOF
+ };
+ data_.reset(new OrderedSocketData(
+ reads, arraysize(reads), writes, arraysize(writes)));
+
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ WebSocketJobTest* test = this;
+ if (throttling == THROTTLING_ON)
+ delegate.SetOnStartOpenConnection(
+ base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
+ delegate.SetOnConnected(
+ base::Bind(&WebSocketJobTest::DoSendRequest,
+ base::Unretained(test)));
+ delegate.SetOnReceivedData(
+ base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
+ delegate.SetOnClose(
+ base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
+ InitWebSocketJob(url, &delegate, STREAM_SOCKET);
+
+ scoped_refptr<WebSocketJob> block_websocket;
+ if (throttling == THROTTLING_ON) {
+ // Create former WebSocket object which obstructs the latter one.
+ block_websocket = new WebSocketJob(NULL);
+ block_websocket->addresses_ = AddressList(websocket_->address_list());
+ ASSERT_TRUE(
+ WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
+ }
+
+ websocket_->Connect();
+
+ if (throttling == THROTTLING_ON) {
+ EXPECT_EQ(OK, WaitForResult());
+ EXPECT_TRUE(websocket_->IsWaiting());
+
+ // Remove the former WebSocket object from throttling queue to unblock the
+ // latter.
+ block_websocket->state_ = WebSocketJob::CLOSED;
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
+ block_websocket = NULL;
+ }
+
+ EXPECT_EQ(OK, WaitForResult());
+ EXPECT_TRUE(data_->at_read_eof());
+ EXPECT_TRUE(data_->at_write_eof());
+ EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
+}
+
+void WebSocketJobTest::TestConnectBySpdy(
+ SpdyOption spdy, ThrottlingOption throttling) {
+ // This is a test for verifying cooperation between WebSocketJob and
+ // SocketStream in the situation we have SPDY session to the server. If
+ // |throttling| was |THROTTLING_ON|, throttling limits the latter connection.
+ // If you enabled spdy, you should specify |spdy| as |SPDY_ON|. Expected
+ // results depend on its configuration.
+ MockWrite writes_websocket[] = {
+ MockWrite(ASYNC,
+ kHandshakeRequestWithoutCookie,
+ kHandshakeRequestWithoutCookieLength,
+ 1),
+ MockWrite(ASYNC,
+ kDataHello,
+ kDataHelloLength,
+ 3)
+ };
+ MockRead reads_websocket[] = {
+ MockRead(ASYNC,
+ kHandshakeResponseWithoutCookie,
+ kHandshakeResponseWithoutCookieLength,
+ 2),
+ MockRead(ASYNC,
+ kDataWorld,
+ kDataWorldLength,
+ 4),
+ MockRead(SYNCHRONOUS, 0, 5) // EOF
+ };
+
+ scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock());
+ spdy_util_.SetHeader("path", "/demo", request_headers.get());
+ spdy_util_.SetHeader("version", "WebSocket/13", request_headers.get());
+ spdy_util_.SetHeader("scheme", "ws", request_headers.get());
+ spdy_util_.SetHeader("host", "example.com", request_headers.get());
+ spdy_util_.SetHeader("origin", "http://example.com", request_headers.get());
+ spdy_util_.SetHeader("sec-websocket-protocol", "sample",
+ request_headers.get());
+
+ scoped_ptr<SpdyHeaderBlock> response_headers(new SpdyHeaderBlock());
+ spdy_util_.SetHeader("status", "101 Switching Protocols",
+ response_headers.get());
+ spdy_util_.SetHeader("sec-websocket-protocol", "sample",
+ response_headers.get());
+
+ const SpdyStreamId kStreamId = 1;
+ scoped_ptr<SpdyFrame> request_frame(
+ spdy_util_.ConstructSpdyWebSocketHandshakeRequestFrame(
+ request_headers.Pass(),
+ kStreamId,
+ MEDIUM));
+ scoped_ptr<SpdyFrame> response_frame(
+ spdy_util_.ConstructSpdyWebSocketHandshakeResponseFrame(
+ response_headers.Pass(),
+ kStreamId,
+ MEDIUM));
+ scoped_ptr<SpdyFrame> data_hello_frame(
+ spdy_util_.ConstructSpdyWebSocketDataFrame(
+ kDataHello,
+ kDataHelloLength,
+ kStreamId,
+ false));
+ scoped_ptr<SpdyFrame> data_world_frame(
+ spdy_util_.ConstructSpdyWebSocketDataFrame(
+ kDataWorld,
+ kDataWorldLength,
+ kStreamId,
+ false));
+ MockWrite writes_spdy[] = {
+ CreateMockWrite(*request_frame.get(), 1),
+ CreateMockWrite(*data_hello_frame.get(), 3),
+ };
+ MockRead reads_spdy[] = {
+ CreateMockRead(*response_frame.get(), 2),
+ CreateMockRead(*data_world_frame.get(), 4),
+ MockRead(SYNCHRONOUS, 0, 5) // EOF
+ };
+
+ if (spdy == SPDY_ON)
+ data_.reset(new OrderedSocketData(
+ reads_spdy, arraysize(reads_spdy),
+ writes_spdy, arraysize(writes_spdy)));
+ else
+ data_.reset(new OrderedSocketData(
+ reads_websocket, arraysize(reads_websocket),
+ writes_websocket, arraysize(writes_websocket)));
+
+ GURL url("ws://example.com/demo");
+ MockSocketStreamDelegate delegate;
+ WebSocketJobTest* test = this;
+ if (throttling == THROTTLING_ON)
+ delegate.SetOnStartOpenConnection(
+ base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
+ delegate.SetOnConnected(
+ base::Bind(&WebSocketJobTest::DoSendRequest,
+ base::Unretained(test)));
+ delegate.SetOnReceivedData(
+ base::Bind(&WebSocketJobTest::DoSendData, base::Unretained(test)));
+ delegate.SetOnClose(
+ base::Bind(&WebSocketJobTest::DoSync, base::Unretained(test)));
+ InitWebSocketJob(url, &delegate, STREAM_SPDY_WEBSOCKET);
+
+ scoped_refptr<WebSocketJob> block_websocket;
+ if (throttling == THROTTLING_ON) {
+ // Create former WebSocket object which obstructs the latter one.
+ block_websocket = new WebSocketJob(NULL);
+ block_websocket->addresses_ = AddressList(websocket_->address_list());
+ ASSERT_TRUE(
+ WebSocketThrottle::GetInstance()->PutInQueue(block_websocket.get()));
+ }
+
+ websocket_->Connect();
+
+ if (throttling == THROTTLING_ON) {
+ EXPECT_EQ(OK, WaitForResult());
+ EXPECT_TRUE(websocket_->IsWaiting());
+
+ // Remove the former WebSocket object from throttling queue to unblock the
+ // latter.
+ block_websocket->state_ = WebSocketJob::CLOSED;
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(block_websocket.get());
+ block_websocket = NULL;
+ }
+
+ EXPECT_EQ(OK, WaitForResult());
+ EXPECT_TRUE(data_->at_read_eof());
+ EXPECT_TRUE(data_->at_write_eof());
+ EXPECT_EQ(WebSocketJob::CLOSED, GetWebSocketJobState());
+}
+
+void WebSocketJobTest::TestThrottlingLimit() {
+ std::vector<scoped_refptr<WebSocketJob> > jobs;
+ const int kMaxWebSocketJobsThrottled = 1024;
+ IPAddressNumber ip;
+ ParseIPLiteralToNumber("127.0.0.1", &ip);
+ for (int i = 0; i < kMaxWebSocketJobsThrottled + 1; ++i) {
+ scoped_refptr<WebSocketJob> job = new WebSocketJob(NULL);
+ job->addresses_ = AddressList(AddressList::CreateFromIPAddress(ip, 80));
+ if (i >= kMaxWebSocketJobsThrottled)
+ EXPECT_FALSE(WebSocketThrottle::GetInstance()->PutInQueue(job));
+ else
+ EXPECT_TRUE(WebSocketThrottle::GetInstance()->PutInQueue(job));
+ jobs.push_back(job);
+ }
+
+ // Close the jobs in reverse order. Otherwise, We need to make them prepared
+ // for Wakeup call.
+ for (std::vector<scoped_refptr<WebSocketJob> >::reverse_iterator iter =
+ jobs.rbegin();
+ iter != jobs.rend();
+ ++iter) {
+ WebSocketJob* job = (*iter).get();
+ job->state_ = WebSocketJob::CLOSED;
+ WebSocketThrottle::GetInstance()->RemoveFromQueue(job);
+ }
+}
+
+// Execute tests in both spdy-disabled mode and spdy-enabled mode.
+TEST_P(WebSocketJobTest, SimpleHandshake) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestSimpleHandshake();
+}
+
+TEST_P(WebSocketJobTest, SlowHandshake) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestSlowHandshake();
+}
+
+TEST_P(WebSocketJobTest, HandshakeWithCookie) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestHandshakeWithCookie();
+}
+
+TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowed) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestHandshakeWithCookieButNotAllowed();
+}
+
+TEST_P(WebSocketJobTest, HSTSUpgrade) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestHSTSUpgrade();
+}
+
+TEST_P(WebSocketJobTest, InvalidSendData) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestInvalidSendData();
+}
+
+TEST_P(WebSocketJobTest, SimpleHandshakeSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestSimpleHandshake();
+}
+
+TEST_P(WebSocketJobTest, SlowHandshakeSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestSlowHandshake();
+}
+
+TEST_P(WebSocketJobTest, HandshakeWithCookieSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestHandshakeWithCookie();
+}
+
+TEST_P(WebSocketJobTest, HandshakeWithCookieButNotAllowedSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestHandshakeWithCookieButNotAllowed();
+}
+
+TEST_P(WebSocketJobTest, HSTSUpgradeSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestHSTSUpgrade();
+}
+
+TEST_P(WebSocketJobTest, InvalidSendDataSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestInvalidSendData();
+}
+
+TEST_P(WebSocketJobTest, ConnectByWebSocket) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestConnectByWebSocket(THROTTLING_OFF);
+}
+
+TEST_P(WebSocketJobTest, ConnectByWebSocketSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestConnectByWebSocket(THROTTLING_OFF);
+}
+
+TEST_P(WebSocketJobTest, ConnectBySpdy) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestConnectBySpdy(SPDY_OFF, THROTTLING_OFF);
+}
+
+TEST_P(WebSocketJobTest, ConnectBySpdySpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestConnectBySpdy(SPDY_ON, THROTTLING_OFF);
+}
+
+TEST_P(WebSocketJobTest, ThrottlingWebSocket) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestConnectByWebSocket(THROTTLING_ON);
+}
+
+TEST_P(WebSocketJobTest, ThrottlingMaxNumberOfThrottledJobLimit) {
+ TestThrottlingLimit();
+}
+
+TEST_P(WebSocketJobTest, ThrottlingWebSocketSpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestConnectByWebSocket(THROTTLING_ON);
+}
+
+TEST_P(WebSocketJobTest, ThrottlingSpdy) {
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+ TestConnectBySpdy(SPDY_OFF, THROTTLING_ON);
+}
+
+TEST_P(WebSocketJobTest, ThrottlingSpdySpdyEnabled) {
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+ TestConnectBySpdy(SPDY_ON, THROTTLING_ON);
+}
+
+// TODO(toyoshim): Add tests to verify throttling, SPDY stream limitation.
+// TODO(toyoshim,yutak): Add tests to verify closing handshake.
+} // namespace net
diff --git a/chromium/net/websockets/websocket_mux.h b/chromium/net/websockets/websocket_mux.h
new file mode 100644
index 00000000000..9fc1f674fad
--- /dev/null
+++ b/chromium/net/websockets/websocket_mux.h
@@ -0,0 +1,39 @@
+// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_MUX_H_
+#define NET_WEBSOCKETS_WEBSOCKET_MUX_H_
+
+namespace net {
+
+// Reason codes used by the mux extension.
+enum WebSocketMuxError {
+ // Codes starting with 2000 apply to the physical connection. They are used
+ // for dropping the control channel.
+ kWebSocketMuxErrorPhysicalConnectionFailed = 2000,
+ kWebSocketMuxErrorInvalidEncapsulatingMessage = 2001,
+ kWebSocketMuxErrorChannelIdTruncated = 2002,
+ kWebSocketMuxErrorEncapsulatedFrameIsTruncated = 2003,
+ kWebSocketMuxErrorUnknownMuxOpcode = 2004,
+ kWebSocketMuxErrorInvalidMuxControlBlock = 2005,
+ kWebSocketMuxErrorChannelAlreadyExists = 2006,
+ kWebSocketMuxErrorNewChannelSlotViolation = 2007,
+ kWebSocketMuxErrorNewChannelSlotOverflow = 2008,
+ kWebSocketMuxErrorBadRequest = 2009,
+ kWebSocketMuxErrorUnknownRequestEncoding = 2010,
+ kWebSocketMuxErrorBadResponse = 2011,
+ kWebSocketMuxErrorUnknownResponseEncoding = 2012,
+
+ // Codes starting with 3000 apply to the logical connection.
+ kWebSocketMuxErrorLogicalChannelFailed = 3000,
+ kWebSocketMuxErrorSendQuotaViolation = 3005,
+ kWebSocketMuxErrorSendQuotaOverflow = 3006,
+ kWebSocketMuxErrorIdleTimeout = 3007,
+ kWebSocketMuxErrorDropChannelAck = 3008,
+ kWebSocketMuxErrorBadFragmentation = 3009,
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_MUX_H_
diff --git a/chromium/net/websockets/websocket_net_log_params.cc b/chromium/net/websockets/websocket_net_log_params.cc
new file mode 100644
index 00000000000..dd9bddebf6b
--- /dev/null
+++ b/chromium/net/websockets/websocket_net_log_params.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_net_log_params.h"
+
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+
+namespace net {
+
+base::Value* NetLogWebSocketHandshakeCallback(
+ const std::string* headers,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ base::ListValue* header_list = new base::ListValue();
+
+ size_t last = 0;
+ size_t headers_size = headers->size();
+ size_t pos = 0;
+ while (pos <= headers_size) {
+ if (pos == headers_size ||
+ ((*headers)[pos] == '\r' &&
+ pos + 1 < headers_size && (*headers)[pos + 1] == '\n')) {
+ std::string entry = headers->substr(last, pos - last);
+ pos += 2;
+ last = pos;
+
+ header_list->Append(new base::StringValue(entry));
+
+ if (entry.empty()) {
+ // Dump WebSocket key3.
+ std::string key;
+ for (; pos < headers_size; ++pos) {
+ key += base::StringPrintf("\\x%02x", (*headers)[pos] & 0xff);
+ }
+ header_list->Append(new base::StringValue(key));
+ break;
+ }
+ } else {
+ ++pos;
+ }
+ }
+
+ dict->Set("headers", header_list);
+ return dict;
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_net_log_params.h b/chromium/net/websockets/websocket_net_log_params.h
new file mode 100644
index 00000000000..45dabb05b95
--- /dev/null
+++ b/chromium/net/websockets/websocket_net_log_params.h
@@ -0,0 +1,21 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_
+#define NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+NET_EXPORT_PRIVATE base::Value* NetLogWebSocketHandshakeCallback(
+ const std::string* headers,
+ NetLog::LogLevel /* log_level */);
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_NET_LOG_PARAMS_H_
diff --git a/chromium/net/websockets/websocket_net_log_params_unittest.cc b/chromium/net/websockets/websocket_net_log_params_unittest.cc
new file mode 100644
index 00000000000..b1c98570402
--- /dev/null
+++ b/chromium/net/websockets/websocket_net_log_params_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_net_log_params.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(NetLogWebSocketHandshakeParameterTest, ToValue) {
+ base::ListValue* list = new base::ListValue();
+ list->Append(new base::StringValue("GET /demo HTTP/1.1"));
+ list->Append(new base::StringValue("Host: example.com"));
+ list->Append(new base::StringValue("Connection: Upgrade"));
+ list->Append(new base::StringValue("Sec-WebSocket-Key2: 12998 5 Y3 1 .P00"));
+ list->Append(new base::StringValue("Sec-WebSocket-Protocol: sample"));
+ list->Append(new base::StringValue("Upgrade: WebSocket"));
+ list->Append(new base::StringValue(
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5"));
+ list->Append(new base::StringValue("Origin: http://example.com"));
+ list->Append(new base::StringValue(std::string()));
+ list->Append(new base::StringValue(
+ "\\x00\\x01\\x0a\\x0d\\xff\\xfe\\x0d\\x0a"));
+
+ base::DictionaryValue expected;
+ expected.Set("headers", list);
+
+ const std::string key("\x00\x01\x0a\x0d\xff\xfe\x0d\x0a", 8);
+ const std::string testInput =
+ "GET /demo HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
+ "Sec-WebSocket-Protocol: sample\r\n"
+ "Upgrade: WebSocket\r\n"
+ "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
+ "Origin: http://example.com\r\n"
+ "\r\n" +
+ key;
+
+ scoped_ptr<base::Value> actual(
+ net::NetLogWebSocketHandshakeCallback(&testInput,
+ net::NetLog::LOG_BASIC));
+
+ EXPECT_TRUE(expected.Equals(actual.get()));
+}
diff --git a/chromium/net/websockets/websocket_stream.cc b/chromium/net/websockets/websocket_stream.cc
new file mode 100644
index 00000000000..b2d316bb2ae
--- /dev/null
+++ b/chromium/net/websockets/websocket_stream.cc
@@ -0,0 +1,32 @@
+// Copyright 2013 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/websockets/websocket_stream.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+WebSocketStreamRequest::~WebSocketStreamRequest() {}
+
+WebSocketStream::WebSocketStream() {}
+WebSocketStream::~WebSocketStream() {}
+
+WebSocketStream::ConnectDelegate::~ConnectDelegate() {}
+
+// Placeholder until the real implementation is ready.
+scoped_ptr<WebSocketStreamRequest> WebSocketStream::CreateAndConnectStream(
+ const GURL& socket_url,
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const BoundNetLog& net_log,
+ scoped_ptr<ConnectDelegate> connect_delegate) {
+ NOTIMPLEMENTED();
+ return make_scoped_ptr(new WebSocketStreamRequest());
+}
+
+WebSocketStream* WebSocketStream::AsWebSocketStream() { return this; }
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_stream.h b/chromium/net/websockets/websocket_stream.h
new file mode 100644
index 00000000000..6c2a4ff8ef1
--- /dev/null
+++ b/chromium/net/websockets/websocket_stream.h
@@ -0,0 +1,211 @@
+// Copyright (c) 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.
+
+#ifndef NET_WEBSOCKETS_WEBSOCKET_STREAM_H_
+#define NET_WEBSOCKETS_WEBSOCKET_STREAM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/websockets/websocket_stream_base.h"
+
+class GURL;
+
+namespace net {
+
+class BoundNetLog;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class URLRequestContext;
+struct WebSocketFrameChunk;
+
+// WebSocketStreamRequest is the caller's handle to the process of creation of a
+// WebSocketStream. Deleting the object before the OnSuccess or OnFailure
+// callbacks are called will cancel the request (and neither callback will be
+// called). After OnSuccess or OnFailure have been called, this object may be
+// safely deleted without side-effects.
+class NET_EXPORT_PRIVATE WebSocketStreamRequest {
+ public:
+ virtual ~WebSocketStreamRequest();
+};
+
+// WebSocketStream is a transport-agnostic interface for reading and writing
+// WebSocket frames. This class provides an abstraction for WebSocket streams
+// based on various transport layers, such as normal WebSocket connections
+// (WebSocket protocol upgraded from HTTP handshake), SPDY transports, or
+// WebSocket connections with multiplexing extension. Subtypes of
+// WebSocketStream are responsible for managing the underlying transport
+// appropriately.
+//
+// All functions except Close() can be asynchronous. If an operation cannot
+// be finished synchronously, the function returns ERR_IO_PENDING, and
+// |callback| will be called when the operation is finished. Non-null |callback|
+// must be provided to these functions.
+
+class NET_EXPORT_PRIVATE WebSocketStream : public WebSocketStreamBase {
+ public:
+ // A concrete object derived from ConnectDelegate is supplied by the caller to
+ // CreateAndConnectStream() to receive the result of the connection.
+ class ConnectDelegate {
+ public:
+ virtual ~ConnectDelegate();
+ // Called on successful connection. The parameter is an object derived from
+ // WebSocketStream.
+ virtual void OnSuccess(scoped_ptr<WebSocketStream> stream) = 0;
+
+ // Called on failure to connect. The parameter is either one of the values
+ // defined in net::WebSocketError, or an error defined by some WebSocket
+ // extension protocol that we implement.
+ virtual void OnFailure(unsigned short websocket_error) = 0;
+ };
+
+ // Create and connect a WebSocketStream of an appropriate type. The actual
+ // concrete type returned depends on whether multiplexing or SPDY are being
+ // used to communicate with the remote server. If the handshake completed
+ // successfully, then connect_delegate->OnSuccess() is called with a
+ // WebSocketStream instance. If it failed, then connect_delegate->OnFailure()
+ // is called with a WebSocket result code corresponding to the error. Deleting
+ // the returned WebSocketStreamRequest object will cancel the connection, in
+ // which case the |connect_delegate| object that the caller passed will be
+ // deleted without any of its methods being called. Unless cancellation is
+ // required, the caller should keep the WebSocketStreamRequest object alive
+ // until connect_delegate->OnSuccess() or OnFailure() have been called, then
+ // it is safe to delete.
+ static scoped_ptr<WebSocketStreamRequest> CreateAndConnectStream(
+ const GURL& socket_url,
+ const std::vector<std::string>& requested_subprotocols,
+ const GURL& origin,
+ URLRequestContext* url_request_context,
+ const BoundNetLog& net_log,
+ scoped_ptr<ConnectDelegate> connect_delegate);
+
+ // Derived classes must make sure Close() is called when the stream is not
+ // closed on destruction.
+ virtual ~WebSocketStream();
+
+ // Reads WebSocket frame data. This operation finishes when new frame data
+ // becomes available. Each frame message might be chopped off in the middle
+ // as specified in the description of WebSocketFrameChunk struct.
+ // |frame_chunks| remains owned by the caller and must be valid until the
+ // operation completes or Close() is called. |frame_chunks| must be empty on
+ // calling.
+ //
+ // This function should not be called while the previous call of ReadFrames()
+ // is still pending.
+ //
+ // Returns net::OK or one of the net::ERR_* codes.
+ //
+ // frame_chunks->size() >= 1 if the result is OK.
+ //
+ // A frame with an incomplete header will never be inserted into
+ // |frame_chunks|. If the currently available bytes of a new frame do not form
+ // a complete frame header, then the implementation will buffer them until all
+ // the fields in the WebSocketFrameHeader object can be filled. If
+ // ReadFrames() is freshly called in this situation, it will return
+ // ERR_IO_PENDING exactly as if no data was available.
+ //
+ // Every WebSocketFrameChunk in the vector except the first and last is
+ // guaranteed to be a complete frame. The first chunk may be the final part
+ // of the previous frame. The last chunk may be the first part of a new
+ // frame. If there is only one chunk, then it may consist of data from the
+ // middle part of a frame.
+ //
+ // When the socket is closed on the remote side, this method will return
+ // ERR_CONNECTION_CLOSED. It will not return OK with an empty vector.
+ //
+ // If the connection is closed in the middle of receiving an incomplete frame,
+ // ReadFrames may discard the incomplete frame. Since the renderer will
+ // discard any incomplete messages when the connection is closed, this makes
+ // no difference to the overall semantics.
+ virtual int ReadFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) = 0;
+
+ // Writes WebSocket frame data. |frame_chunks| must obey the rule specified
+ // in the documentation of WebSocketFrameChunk struct: the first chunk of
+ // a WebSocket frame must contain non-NULL |header|, and the last chunk must
+ // have |final_chunk| field set to true. Series of chunks representing a
+ // WebSocket frame must be consistent (the total length of |data| fields must
+ // match |header->payload_length|). |frame_chunks| must be valid until the
+ // operation completes or Close() is called.
+ //
+ // This function should not be called while previous call of WriteFrames() is
+ // still pending.
+ //
+ // Support for incomplete frames is not guaranteed and may be removed from
+ // future iterations of the API.
+ //
+ // This method will only return OK if all frames were written completely.
+ // Otherwise it will return an appropriate net error code.
+ virtual int WriteFrames(ScopedVector<WebSocketFrameChunk>* frame_chunks,
+ const CompletionCallback& callback) = 0;
+
+ // Closes the stream. All pending I/O operations (if any) are cancelled
+ // at this point, so |frame_chunks| can be freed.
+ virtual void Close() = 0;
+
+ // The subprotocol that was negotiated for the stream. If no protocol was
+ // negotiated, then the empty string is returned.
+ virtual std::string GetSubProtocol() const = 0;
+
+ // The extensions that were negotiated for the stream. Since WebSocketStreams
+ // can be layered, this may be different from what this particular
+ // WebSocketStream implements. The primary purpose of this accessor is to make
+ // the data available to Javascript. The format of the string is identical to
+ // the contents of the Sec-WebSocket-Extensions header supplied by the server,
+ // with some canonicalisations applied (leading and trailing whitespace
+ // removed, multiple headers concatenated into one comma-separated list). See
+ // RFC6455 section 9.1 for the exact format specification. If no
+ // extensions were negotiated, the empty string is returned.
+ virtual std::string GetExtensions() const = 0;
+
+ // TODO(yutak): Add following interfaces:
+ // - RenewStreamForAuth for authentication (is this necessary?)
+ // - GetSSLInfo, GetSSLCertRequestInfo for SSL
+
+ // WebSocketStreamBase derived functions
+ virtual WebSocketStream* AsWebSocketStream() OVERRIDE;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Methods used during the stream handshake. These must not be called once a
+ // WebSocket protocol stream has been established (ie. after the
+ // SuccessCallback or FailureCallback has been called.)
+
+ // Writes WebSocket handshake request to the underlying socket. Must be called
+ // before ReadHandshakeResponse().
+ //
+ // |callback| will only be called if this method returns ERR_IO_PENDING.
+ //
+ // |response_info| must remain valid until the callback from
+ // ReadHandshakeResponse has been called.
+ //
+ // TODO(ricea): This function is only used during the handshake and is
+ // probably only applicable to certain subclasses of WebSocketStream. Move it
+ // somewhere else? Also applies to ReadHandshakeResponse.
+ virtual int SendHandshakeRequest(const GURL& url,
+ const HttpRequestHeaders& headers,
+ HttpResponseInfo* response_info,
+ const CompletionCallback& callback) = 0;
+
+ // Reads WebSocket handshake response from the underlying socket. Must be
+ // called after SendHandshakeRequest() completes.
+ //
+ // |callback| will only be called if this method returns ERR_IO_PENDING.
+ virtual int ReadHandshakeResponse(const CompletionCallback& callback) = 0;
+
+ protected:
+ WebSocketStream();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocketStream);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_STREAM_H_
diff --git a/chromium/net/websockets/websocket_stream_base.h b/chromium/net/websockets/websocket_stream_base.h
new file mode 100644
index 00000000000..dc863d20001
--- /dev/null
+++ b/chromium/net/websockets/websocket_stream_base.h
@@ -0,0 +1,55 @@
+// Copyright 2013 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_WEBSOCKETS_WEBSOCKET_STREAM_BASE_H_
+#define NET_WEBSOCKETS_WEBSOCKET_STREAM_BASE_H_
+
+// This file is included from net/http files.
+// Since net/http can be built without linking net/websockets code,
+// this file should not depend on net/websockets.
+
+#include <base/basictypes.h>
+
+namespace net {
+
+class ClientSocketHandle;
+class SpdySession;
+class WebSocketStream;
+
+// WebSocketStreamBase is the base class of WebSocketStream.
+// net/http code uses this interface to handle WebSocketStream.
+class NET_EXPORT WebSocketStreamBase {
+ public:
+ class Factory {
+ public:
+ virtual ~Factory() {}
+
+ // Create a WebSocketBasicStream.
+ // This function (or the returned object) takes the ownership
+ // of |connection|.
+ virtual WebSocketStreamBase* CreateBasicStream(
+ ClientSocketHandle* connection,
+ bool using_proxy) = 0;
+
+ // Create a WebSocketSpdyStream.
+ virtual WebSocketStreamBase* CreateSpdyStream(
+ const base::WeakPtr<SpdySession>& session,
+ bool use_relative_url) = 0;
+ };
+
+ virtual ~WebSocketStreamBase() {}
+
+ // Return this object as a WebSocketStream.
+ virtual WebSocketStream* AsWebSocketStream() = 0;
+
+ protected:
+ WebSocketStreamBase() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebSocketStreamBase);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_STREAM_BASE_H_
diff --git a/chromium/net/websockets/websocket_throttle.cc b/chromium/net/websockets/websocket_throttle.cc
new file mode 100644
index 00000000000..59e73fda3c9
--- /dev/null
+++ b/chromium/net/websockets/websocket_throttle.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_throttle.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/socket_stream/socket_stream.h"
+#include "net/websockets/websocket_job.h"
+
+namespace net {
+
+namespace {
+
+const size_t kMaxWebSocketJobsThrottled = 1024;
+
+} // namespace
+
+WebSocketThrottle::WebSocketThrottle() {
+}
+
+WebSocketThrottle::~WebSocketThrottle() {
+ DCHECK(queue_.empty());
+ DCHECK(addr_map_.empty());
+}
+
+// static
+WebSocketThrottle* WebSocketThrottle::GetInstance() {
+ return Singleton<WebSocketThrottle>::get();
+}
+
+bool WebSocketThrottle::PutInQueue(WebSocketJob* job) {
+ if (queue_.size() >= kMaxWebSocketJobsThrottled)
+ return false;
+
+ queue_.push_back(job);
+ const AddressList& address_list = job->address_list();
+ std::set<IPEndPoint> address_set;
+ for (AddressList::const_iterator addr_iter = address_list.begin();
+ addr_iter != address_list.end();
+ ++addr_iter) {
+ const IPEndPoint& address = *addr_iter;
+ // If |address| is already processed, don't do it again.
+ if (!address_set.insert(address).second)
+ continue;
+
+ ConnectingAddressMap::iterator iter = addr_map_.find(address);
+ if (iter == addr_map_.end()) {
+ ConnectingAddressMap::iterator new_queue =
+ addr_map_.insert(make_pair(address, ConnectingQueue())).first;
+ new_queue->second.push_back(job);
+ } else {
+ DCHECK(!iter->second.empty());
+ iter->second.push_back(job);
+ job->SetWaiting();
+ DVLOG(1) << "Waiting on " << address.ToString();
+ }
+ }
+
+ return true;
+}
+
+void WebSocketThrottle::RemoveFromQueue(WebSocketJob* job) {
+ ConnectingQueue::iterator queue_iter =
+ std::find(queue_.begin(), queue_.end(), job);
+ if (queue_iter == queue_.end())
+ return;
+ queue_.erase(queue_iter);
+
+ std::set<WebSocketJob*> wakeup_candidates;
+
+ const AddressList& resolved_address_list = job->address_list();
+ std::set<IPEndPoint> address_set;
+ for (AddressList::const_iterator addr_iter = resolved_address_list.begin();
+ addr_iter != resolved_address_list.end();
+ ++addr_iter) {
+ const IPEndPoint& address = *addr_iter;
+ // If |address| is already processed, don't do it again.
+ if (!address_set.insert(address).second)
+ continue;
+
+ ConnectingAddressMap::iterator map_iter = addr_map_.find(address);
+ DCHECK(map_iter != addr_map_.end());
+
+ ConnectingQueue& per_address_queue = map_iter->second;
+ DCHECK(!per_address_queue.empty());
+ // Job may not be front of the queue if the socket is closed while waiting.
+ ConnectingQueue::iterator per_address_queue_iter =
+ std::find(per_address_queue.begin(), per_address_queue.end(), job);
+ bool was_front = false;
+ if (per_address_queue_iter != per_address_queue.end()) {
+ was_front = (per_address_queue_iter == per_address_queue.begin());
+ per_address_queue.erase(per_address_queue_iter);
+ }
+ if (per_address_queue.empty()) {
+ addr_map_.erase(map_iter);
+ } else if (was_front) {
+ // The new front is a wake-up candidate.
+ wakeup_candidates.insert(per_address_queue.front());
+ }
+ }
+
+ WakeupSocketIfNecessary(wakeup_candidates);
+}
+
+void WebSocketThrottle::WakeupSocketIfNecessary(
+ const std::set<WebSocketJob*>& wakeup_candidates) {
+ for (std::set<WebSocketJob*>::const_iterator iter = wakeup_candidates.begin();
+ iter != wakeup_candidates.end();
+ ++iter) {
+ WebSocketJob* job = *iter;
+ if (!job->IsWaiting())
+ continue;
+
+ bool should_wakeup = true;
+ const AddressList& resolved_address_list = job->address_list();
+ for (AddressList::const_iterator addr_iter = resolved_address_list.begin();
+ addr_iter != resolved_address_list.end();
+ ++addr_iter) {
+ const IPEndPoint& address = *addr_iter;
+ ConnectingAddressMap::iterator map_iter = addr_map_.find(address);
+ DCHECK(map_iter != addr_map_.end());
+ const ConnectingQueue& per_address_queue = map_iter->second;
+ if (job != per_address_queue.front()) {
+ should_wakeup = false;
+ break;
+ }
+ }
+ if (should_wakeup)
+ job->Wakeup();
+ }
+}
+
+} // namespace net
diff --git a/chromium/net/websockets/websocket_throttle.h b/chromium/net/websockets/websocket_throttle.h
new file mode 100644
index 00000000000..0b7a39f3b6b
--- /dev/null
+++ b/chromium/net/websockets/websocket_throttle.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 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_WEBSOCKETS_WEBSOCKET_THROTTLE_H_
+#define NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace net {
+
+class SocketStream;
+class WebSocketJob;
+
+// SocketStreamThrottle for WebSocket protocol.
+// Implements the client-side requirements in the spec.
+// http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
+// 4.1 Handshake
+// 1. If the user agent already has a Web Socket connection to the
+// remote host (IP address) identified by /host/, even if known by
+// another name, wait until that connection has been established or
+// for that connection to have failed.
+class NET_EXPORT_PRIVATE WebSocketThrottle {
+ public:
+ // Returns the singleton instance.
+ static WebSocketThrottle* GetInstance();
+
+ // Puts |job| in |queue_| and queues for the destination addresses
+ // of |job|.
+ // If other job is using the same destination address, set |job| waiting.
+ //
+ // Returns true if successful. If the number of pending jobs will exceed
+ // the limit, does nothing and returns false.
+ bool PutInQueue(WebSocketJob* job);
+
+ // Removes |job| from |queue_| and queues for the destination addresses
+ // of |job|, and then wakes up jobs that can now resume establishing a
+ // connection.
+ void RemoveFromQueue(WebSocketJob* job);
+
+ private:
+ typedef std::deque<WebSocketJob*> ConnectingQueue;
+ typedef std::map<IPEndPoint, ConnectingQueue> ConnectingAddressMap;
+
+ WebSocketThrottle();
+ ~WebSocketThrottle();
+ friend struct DefaultSingletonTraits<WebSocketThrottle>;
+
+ // Examines if any of the given jobs can resume establishing a connection. If
+ // for all per-address queues for each resolved addresses
+ // (job->address_list()) of a job, the job is at the front of the queues, the
+ // job can resume establishing a connection, so wakes up the job.
+ void WakeupSocketIfNecessary(
+ const std::set<WebSocketJob*>& wakeup_candidates);
+
+ // Key: string of host's address. Value: queue of sockets for the address.
+ ConnectingAddressMap addr_map_;
+
+ // Queue of sockets for websockets in opening state.
+ ConnectingQueue queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebSocketThrottle);
+};
+
+} // namespace net
+
+#endif // NET_WEBSOCKETS_WEBSOCKET_THROTTLE_H_
diff --git a/chromium/net/websockets/websocket_throttle_unittest.cc b/chromium/net/websockets/websocket_throttle_unittest.cc
new file mode 100644
index 00000000000..fbd89caf9b7
--- /dev/null
+++ b/chromium/net/websockets/websocket_throttle_unittest.cc
@@ -0,0 +1,357 @@
+// Copyright (c) 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.
+
+#include "net/websockets/websocket_throttle.h"
+
+#include <string>
+
+#include "base/message_loop/message_loop.h"
+#include "net/base/address_list.h"
+#include "net/base/test_completion_callback.h"
+#include "net/socket_stream/socket_stream.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/websockets/websocket_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+
+class DummySocketStreamDelegate : public net::SocketStream::Delegate {
+ public:
+ DummySocketStreamDelegate() {}
+ virtual ~DummySocketStreamDelegate() {}
+ virtual void OnConnected(
+ net::SocketStream* socket, int max_pending_send_allowed) OVERRIDE {}
+ virtual void OnSentData(net::SocketStream* socket,
+ int amount_sent) OVERRIDE {}
+ virtual void OnReceivedData(net::SocketStream* socket,
+ const char* data, int len) OVERRIDE {}
+ virtual void OnClose(net::SocketStream* socket) OVERRIDE {}
+};
+
+namespace net {
+
+class WebSocketThrottleTest : public PlatformTest {
+ protected:
+ static IPEndPoint MakeAddr(int a1, int a2, int a3, int a4) {
+ IPAddressNumber ip;
+ ip.push_back(a1);
+ ip.push_back(a2);
+ ip.push_back(a3);
+ ip.push_back(a4);
+ return IPEndPoint(ip, 0);
+ }
+
+ static void MockSocketStreamConnect(
+ SocketStream* socket, const AddressList& list) {
+ socket->set_addresses(list);
+ // TODO(toyoshim): We should introduce additional tests on cases via proxy.
+ socket->proxy_info_.UseDirect();
+ // In SocketStream::Connect(), it adds reference to socket, which is
+ // balanced with SocketStream::Finish() that is finally called from
+ // SocketStream::Close() or SocketStream::DetachDelegate(), when
+ // next_state_ is not STATE_NONE.
+ // If next_state_ is STATE_NONE, SocketStream::Close() or
+ // SocketStream::DetachDelegate() won't call SocketStream::Finish(),
+ // so Release() won't be called. Thus, we don't need socket->AddRef()
+ // here.
+ DCHECK_EQ(socket->next_state_, SocketStream::STATE_NONE);
+ }
+};
+
+TEST_F(WebSocketThrottleTest, Throttle) {
+ TestURLRequestContext context;
+ DummySocketStreamDelegate delegate;
+ // TODO(toyoshim): We need to consider both spdy-enabled and spdy-disabled
+ // configuration.
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+
+ // For host1: 1.2.3.4, 1.2.3.5, 1.2.3.6
+ AddressList addr;
+ addr.push_back(MakeAddr(1, 2, 3, 4));
+ addr.push_back(MakeAddr(1, 2, 3, 5));
+ addr.push_back(MakeAddr(1, 2, 3, 6));
+ scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s1(
+ new SocketStream(GURL("ws://host1/"), w1.get()));
+ s1->set_context(&context);
+ w1->InitSocketStream(s1.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr);
+
+ DVLOG(1) << "socket1";
+ TestCompletionCallback callback_s1;
+ // Trying to open connection to host1 will start without wait.
+ EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
+
+ // Now connecting to host1, so waiting queue looks like
+ // Address | head -> tail
+ // 1.2.3.4 | w1
+ // 1.2.3.5 | w1
+ // 1.2.3.6 | w1
+
+ // For host2: 1.2.3.4
+ addr.clear();
+ addr.push_back(MakeAddr(1, 2, 3, 4));
+ scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s2(
+ new SocketStream(GURL("ws://host2/"), w2.get()));
+ s2->set_context(&context);
+ w2->InitSocketStream(s2.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s2.get(), addr);
+
+ DVLOG(1) << "socket2";
+ TestCompletionCallback callback_s2;
+ // Trying to open connection to host2 will wait for w1.
+ EXPECT_EQ(ERR_IO_PENDING,
+ w2->OnStartOpenConnection(s2.get(), callback_s2.callback()));
+ // Now waiting queue looks like
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2
+ // 1.2.3.5 | w1
+ // 1.2.3.6 | w1
+
+ // For host3: 1.2.3.5
+ addr.clear();
+ addr.push_back(MakeAddr(1, 2, 3, 5));
+ scoped_refptr<WebSocketJob> w3(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s3(
+ new SocketStream(GURL("ws://host3/"), w3.get()));
+ s3->set_context(&context);
+ w3->InitSocketStream(s3.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s3.get(), addr);
+
+ DVLOG(1) << "socket3";
+ TestCompletionCallback callback_s3;
+ // Trying to open connection to host3 will wait for w1.
+ EXPECT_EQ(ERR_IO_PENDING,
+ w3->OnStartOpenConnection(s3.get(), callback_s3.callback()));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1
+
+ // For host4: 1.2.3.4, 1.2.3.6
+ addr.clear();
+ addr.push_back(MakeAddr(1, 2, 3, 4));
+ addr.push_back(MakeAddr(1, 2, 3, 6));
+ scoped_refptr<WebSocketJob> w4(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s4(
+ new SocketStream(GURL("ws://host4/"), w4.get()));
+ s4->set_context(&context);
+ w4->InitSocketStream(s4.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s4.get(), addr);
+
+ DVLOG(1) << "socket4";
+ TestCompletionCallback callback_s4;
+ // Trying to open connection to host4 will wait for w1, w2.
+ EXPECT_EQ(ERR_IO_PENDING,
+ w4->OnStartOpenConnection(s4.get(), callback_s4.callback()));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4
+
+ // For host5: 1.2.3.6
+ addr.clear();
+ addr.push_back(MakeAddr(1, 2, 3, 6));
+ scoped_refptr<WebSocketJob> w5(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s5(
+ new SocketStream(GURL("ws://host5/"), w5.get()));
+ s5->set_context(&context);
+ w5->InitSocketStream(s5.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s5.get(), addr);
+
+ DVLOG(1) << "socket5";
+ TestCompletionCallback callback_s5;
+ // Trying to open connection to host5 will wait for w1, w4
+ EXPECT_EQ(ERR_IO_PENDING,
+ w5->OnStartOpenConnection(s5.get(), callback_s5.callback()));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4 w5
+
+ // For host6: 1.2.3.6
+ addr.clear();
+ addr.push_back(MakeAddr(1, 2, 3, 6));
+ scoped_refptr<WebSocketJob> w6(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s6(
+ new SocketStream(GURL("ws://host6/"), w6.get()));
+ s6->set_context(&context);
+ w6->InitSocketStream(s6.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s6.get(), addr);
+
+ DVLOG(1) << "socket6";
+ TestCompletionCallback callback_s6;
+ // Trying to open connection to host6 will wait for w1, w4, w5
+ EXPECT_EQ(ERR_IO_PENDING,
+ w6->OnStartOpenConnection(s6.get(), callback_s6.callback()));
+ // Address | head -> tail
+ // 1.2.3.4 | w1 w2 w4
+ // 1.2.3.5 | w1 w3
+ // 1.2.3.6 | w1 w4 w5 w6
+
+ // Receive partial response on w1, still connecting.
+ DVLOG(1) << "socket1 1";
+ static const char kHeader[] = "HTTP/1.1 101 WebSocket Protocol\r\n";
+ w1->OnReceivedData(s1.get(), kHeader, sizeof(kHeader) - 1);
+ EXPECT_FALSE(callback_s2.have_result());
+ EXPECT_FALSE(callback_s3.have_result());
+ EXPECT_FALSE(callback_s4.have_result());
+ EXPECT_FALSE(callback_s5.have_result());
+ EXPECT_FALSE(callback_s6.have_result());
+
+ // Receive rest of handshake response on w1.
+ DVLOG(1) << "socket1 2";
+ static const char kHeader2[] =
+ "Upgrade: WebSocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Origin: http://www.google.com\r\n"
+ "Sec-WebSocket-Location: ws://websocket.chromium.org\r\n"
+ "\r\n"
+ "8jKS'y:G*Co,Wxa-";
+ w1->OnReceivedData(s1.get(), kHeader2, sizeof(kHeader2) - 1);
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ // Now, w1 is open.
+ EXPECT_EQ(WebSocketJob::OPEN, w1->state());
+ // So, w2 and w3 can start connecting. w4 needs to wait w2 (1.2.3.4)
+ EXPECT_TRUE(callback_s2.have_result());
+ EXPECT_TRUE(callback_s3.have_result());
+ EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w5 w6
+
+ // Closing s1 doesn't change waiting queue.
+ DVLOG(1) << "socket1 close";
+ w1->OnClose(s1.get());
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ EXPECT_FALSE(callback_s4.have_result());
+ s1->DetachDelegate();
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w5 w6
+
+ // w5 can close while waiting in queue.
+ DVLOG(1) << "socket5 close";
+ // w5 close() closes SocketStream that change state to STATE_CLOSE, calls
+ // DoLoop(), so OnClose() callback will be called.
+ w5->OnClose(s5.get());
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4 w6
+ s5->DetachDelegate();
+
+ // w6 close abnormally (e.g. renderer finishes) while waiting in queue.
+ DVLOG(1) << "socket6 close abnormally";
+ w6->DetachDelegate();
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ EXPECT_FALSE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w2 w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4
+
+ // Closing s2 kicks w4 to start connecting.
+ DVLOG(1) << "socket2 close";
+ w2->OnClose(s2.get());
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ EXPECT_TRUE(callback_s4.have_result());
+ // Address | head -> tail
+ // 1.2.3.4 | w4
+ // 1.2.3.5 | w3
+ // 1.2.3.6 | w4
+ s2->DetachDelegate();
+
+ DVLOG(1) << "socket3 close";
+ w3->OnClose(s3.get());
+ base::MessageLoopForIO::current()->RunUntilIdle();
+ s3->DetachDelegate();
+ w4->OnClose(s4.get());
+ s4->DetachDelegate();
+ DVLOG(1) << "Done";
+ base::MessageLoopForIO::current()->RunUntilIdle();
+}
+
+TEST_F(WebSocketThrottleTest, NoThrottleForDuplicateAddress) {
+ TestURLRequestContext context;
+ DummySocketStreamDelegate delegate;
+ WebSocketJob::set_websocket_over_spdy_enabled(true);
+
+ // For localhost: 127.0.0.1, 127.0.0.1
+ AddressList addr;
+ addr.push_back(MakeAddr(127, 0, 0, 1));
+ addr.push_back(MakeAddr(127, 0, 0, 1));
+ scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s1(
+ new SocketStream(GURL("ws://localhost/"), w1.get()));
+ s1->set_context(&context);
+ w1->InitSocketStream(s1.get());
+ WebSocketThrottleTest::MockSocketStreamConnect(s1.get(), addr);
+
+ DVLOG(1) << "socket1";
+ TestCompletionCallback callback_s1;
+ // Trying to open connection to localhost will start without wait.
+ EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
+
+ DVLOG(1) << "socket1 close";
+ w1->OnClose(s1.get());
+ s1->DetachDelegate();
+ DVLOG(1) << "Done";
+ base::MessageLoopForIO::current()->RunUntilIdle();
+}
+
+// A connection should not be blocked by another connection to the same IP
+// with a different port.
+TEST_F(WebSocketThrottleTest, NoThrottleForDistinctPort) {
+ TestURLRequestContext context;
+ DummySocketStreamDelegate delegate;
+ IPAddressNumber localhost;
+ ParseIPLiteralToNumber("127.0.0.1", &localhost);
+ WebSocketJob::set_websocket_over_spdy_enabled(false);
+
+ // socket1: 127.0.0.1:80
+ scoped_refptr<WebSocketJob> w1(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s1(
+ new SocketStream(GURL("ws://localhost:80/"), w1.get()));
+ s1->set_context(&context);
+ w1->InitSocketStream(s1.get());
+ MockSocketStreamConnect(s1.get(),
+ AddressList::CreateFromIPAddress(localhost, 80));
+
+ DVLOG(1) << "connecting socket1";
+ TestCompletionCallback callback_s1;
+ // Trying to open connection to localhost:80 will start without waiting.
+ EXPECT_EQ(OK, w1->OnStartOpenConnection(s1.get(), callback_s1.callback()));
+
+ // socket2: 127.0.0.1:81
+ scoped_refptr<WebSocketJob> w2(new WebSocketJob(&delegate));
+ scoped_refptr<SocketStream> s2(
+ new SocketStream(GURL("ws://localhost:81/"), w2.get()));
+ s2->set_context(&context);
+ w2->InitSocketStream(s2.get());
+ MockSocketStreamConnect(s2.get(),
+ AddressList::CreateFromIPAddress(localhost, 81));
+
+ DVLOG(1) << "connecting socket2";
+ TestCompletionCallback callback_s2;
+ // Trying to open connection to localhost:81 will start without waiting.
+ EXPECT_EQ(OK, w2->OnStartOpenConnection(s2.get(), callback_s2.callback()));
+
+ DVLOG(1) << "closing socket1";
+ w1->OnClose(s1.get());
+ s1->DetachDelegate();
+
+ DVLOG(1) << "closing socket2";
+ w2->OnClose(s2.get());
+ s2->DetachDelegate();
+ DVLOG(1) << "Done";
+ base::MessageLoopForIO::current()->RunUntilIdle();
+}
+
+}